lyrics_generation复现-----Tensorflow

一、项目简介

Github上的一个项目lyrics_generation,基于LSTM语言模型和seq2seq序列模型的歌词生成,包括数据爬取、数据处理、模型训练和歌词生成。站在2024大模型爆发,微调技术主流的角度,虽然该技术已经过时,但可以看成是目前大语言模型的微缩版demo,了解其原理。

https://github.com/Nana0606/lyrics_generation

原作者使用说明

设计方案实现步骤:

Step1: 读取训练文件,并分词。

Step2: 将分词结果和下标对应,得到char-to-indexindex-to-char(或者phrase-to-indexword-to-index)用于后续索引

Step3: 生成训练数据和验证数据用于后续训练和验证。

Step4: 模型构建、训练和保存

Step5: 调参

Step6: 数据accuracy分析

Step7: 歌词生成,加载已训练模型生成数据。

二、使用说明( https://github.com/Nana0606/lyrics_generation

1、训练数据

# lstm模型训练

> cd lstm_model

> python train_lstm_word_based.py

# seq2seq模型训练

> cd seq2seq_model

> python train_seq2seq_word_based.py

2、生成歌词

# 将训练好的参数或者百度云盘上下载的文件放入相应文件夹下

# lstm模型生成歌词

> cd lstm_model

> python generate_lyrics_word_based.py

# seq2seq模型生成歌词

> cd seq2seq_model

> python generation_word_based.py

二、个人实测复现过程中若干问题及解决

2.1运行generate功能,生成歌词

1,将作者训练好的参数模型对应拷贝到承载模型py程序的文件夹中(lstm_model/generate_lyrics_word_based.py文件所在的文件夹)。拷贝后的两个文件夹文件应该多了以下几个文件:

index_to_word seq2seq_input.txt

index_to_word seq2seq_target.txt

model_seq2seq_100epoch_based.py

word_to_index_seq2seq_input.txt

word_to_index_seq2seq_target.txt

2,以lstm模型生成歌词为例,在lstm_model文件夹中打开powershell,然后输入“python train_lstm_word_based.py

”(如没安装keras会提示,此时百度后安上即可,不显示keras错误)

注意要用管理员权限打开Anaconda prompt或者 powershell,要不然会提示没写的权限。

3,有提示“ModuleNotFoundError: No module named ‘tensorflow”,

但,实际用 import tensorflow as tf tf.__version__也有正确显示tf版本号。

于是,换方法,用pycharm直接打开,运行。显示ModuleNotFoundError: No module named ‘tensorflow。确定是配置问题。将pycharm解释器改成TensorFlow,部分解决这个问题。

——》新问题: 没有tensorflow.python.trackable模块。在百度搜索,出的损招重新安装,后来发现不对。又去google搜索,首页第一个给出答案是“这个错误是由pip install -r requirements.txt以最近发布的Keras 2.10版本为例,该版本可能假定它运行在TensorFlow 2.10旁边,并且轻微的不匹配导致了此导入错误。“【google查代码错+华为网页翻译==NB】

解决方案:有很多解决方案,最后一行最小化工作量尝试是:在2.9.0版本中安装keras所以在Anaconda prompt中输入pip install keras==2.9.0

3,回到pycharm,打开了generate_lyrics_word_based.py ,点运行此文件,右图泪奔了。虽有报错,但歌词生成了。

lstm_model,三次有三个结果,每个不一样

seq2seq_model,两次两个结果,每个不一样

但两个结果都比较拉垮,没法看。

改进后可直接run的源代码如下generate_lyrics_word_based.py:

# python3
# -*- coding: utf-8 -*-
# @Author  : lina
# @Time    : 2018/12/2 15:14

"""
功能:使用train_lstm_word_based.py保存的模型和参数生成歌词。
"""

from keras.models import load_model
import numpy as np
import json

def load_param(model_file, word2index_file, index2word_file):
    """
    load model and word2index_file, index2word_file
    :param model_file:
    :param word2index_file:
    :param index2word_file:
    :return:
    """
    # get model.
    model = load_model(model_file)
    # get the word2index and index2word data.
    with open(word2index_file, 'r', encoding='utf8') as f:
        json_obj = f.read()
        word2index = json.loads(json_obj)
        f.close()
    with open(index2word_file, 'r', encoding='utf8') as f:
        json_obj = f.read()
        index2word = json.loads(json_obj)
        f.close()
    index2word_new = {}
    for key, value in index2word.items():
        index2word_new[int(key)] = value
    return model, word2index, index2word_new

def sample(preds, diversity = 1.0):
    """
    get the max probability index.
    :param preds: 预测结果
    :param diversity:
    :return:
    """
    preds = np.asarray(preds).astype("float64")
    preds = np.log(preds + 1e-10) / diversity
    exp_preds = np.exp(preds)
    preds = exp_preds / np.sum(exp_preds)
    probas = np.random.multinomial(1, preds, 1)
    return np.argmax(probas)

def generate(start, model, word2index, index2word, SEQ_LENGTH, generate_maxlen):
    """
    generate lyrics according start sentence.
    :param start: startWith sentence
    :param model:
    :param word2index:
    :param index2word:
    :param maxlen: the length of generating sentence.
    :return:
    """
    sentence = start[:SEQ_LENGTH]   # 限制最开始的长度,sentence用于存储
    diversity = 1.0
    while len(sentence) < generate_maxlen:
        # 将最开始的句子分词,并存储到x_pred中
        x_pred = np.zeros((1, SEQ_LENGTH))    # 使用PAD填充

        min_index = max(0, len(sentence) - SEQ_LENGTH)    # 因为要通过前10个字,预测后一个字,获取前10个字的index
        for idx in range(min_index, len(sentence)):
            x_pred[0, SEQ_LENGTH - len(sentence) + idx] = word2index.get(sentence[idx], 1)   # '<UNK>' is 1

        preds = model.predict(x_pred, verbose=0)[0]   # 预测的概率
        next_index = sample(preds, diversity)   # 根据预测的概率采样确定下一个字
        next_word = index2word[next_index]
        if not (next_word == '。' and sentence[-1] == '。'):   # 防止出现一句话没有内容,只有句号
            sentence = sentence + next_word   # 每次都往后取一个
    return sentence


if __name__ == '__main__':
    model_file = './model_epoch50_2lstm_1dense_seq10_word_based_best.h5'
    word2index_file = './word_to_index_word.txt'
    index2word_file = './index_to_word_word.txt'
    model, word2index, index2word = load_param(model_file, word2index_file, index2word_file)
    start = "繁华声遁入空门"
    generate_maxlen = 200
    SEQ_LENGTH = 10
    sentence = generate(start, model, word2index, index2word, SEQ_LENGTH, generate_maxlen)
    print(sentence.replace("。", '\n'))  # 显示的时候使用换行更可观

2.2运行train功能,训练模型

如下的lstm_model\traini_lstm_word_based.py代码,测试成功。(修改了部分V1到V2的适配语句)

# python3
# -*- coding: utf-8 -*-
# @Author  : lina
# @Time    : 2018/12/2 13:37

"""
本文主要使用把歌词生成当做分类任务来做,使用2层lstm+1层dense,最后使用softmax。
代码包括模型及参数的保存以及tensorboard-log的保存。
参考:https://github.com/shiwusong/keras_lstm_generation/blob/master/main.py#L24
"""

import numpy as np
from keras.utils import np_utils
from sklearn.model_selection import train_test_split
from keras.models import Model
from keras.layers import Embedding, LSTM, Dense, Input
from keras.optimizers import Adam
from keras.callbacks import TensorBoard
from keras.callbacks import ModelCheckpoint
import matplotlib.pyplot as plt
import json
import os

# 指定GPU和最大占用的显存比例
import tensorflow as tf
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
from keras.backend import set_session
config = tf.compat.v1.ConfigProto()
config.gpu_options.per_process_gpu_memory_fraction = 0.3
set_session(tf.compat.v1.Session(config=config))

SEQ_LENGTH = 10     # 通过前n个词,生成后一个词,SEQ_LENGTH=n                                 【超参】
MAX_NB_WORDS = 10000    # vocabulary中最多保留多少最高频的字                                   【超参】
EMBEDDING_DIM = 800     # embedding层的维度以及第一层lstm输入维度                              【超参】
EMBEDDING_DIM_2 = 1600     # 第二层lstm的输出维度                                             【超参】
BATCH_SIZE = 64    # batch的大小                                                           【超参】
EPOCHS = 50    # 迭代次数                                                                   【超参】

def cut_words(file_name):
    """
    功能:将file中的内容返回按“字”分割的列表
    :param file_name: 文件名称
    :return: 文件内容按“字”分割的列表
    """
    with open(file_name, 'r', encoding='utf8') as f:
        content = f.read().replace('\n', '。')   # 使用句号作为句子的结束符
        f.close()
    return list(content)

def map_words(cut_word_list):
    """
    将训练文本中的“字”形成字典:word2index和index2word
    :param cut_word_list:文件内容按“字”分割的列表
    :return:word2index和index2word, 2个字典类型的结果,分别以字为key,index为value和以index为key,字为value。
    """
    vocabulary = sorted(list(set(cut_word_list)))
    word_to_index = dict((w, i+2) for i, w in enumerate(vocabulary))
    word_to_index["PAD"] = 0   # 填补
    word_to_index["UNK"] = 1   # unknown
    index_to_word = dict((index, word) for word, index in word_to_index.items())

    # 这里需要将2个文件存储,以便在生成歌词时使用
    word_to_index_json = json.dumps(word_to_index)
    index_to_word_json = json.dumps(index_to_word)
    with open('./word_to_index_word.txt', 'w', encoding='utf8') as w:
        w.write(word_to_index_json)
        w.close()
    with open('./index_to_word_word.txt', 'w', encoding='utf8') as w:
        w.write(index_to_word_json)
        w.close()
    # print("len of word_to_index::", len(word_to_index))
    # print("len of index_to_word::", len(index_to_word))
    return word_to_index, index_to_word

def generate_train_data(cut_word_list, word_to_index):
    """
    构造训练集,并处理成keras可以接受的输入格式。
    :param cut_word_list: 按“字”分割之后的list
    :param word_to_index: word2index的映射字典
    :return:X_train, X_val, y_train, y_val:训练集和验证集
    """
    # 生成训练数据
    X_data = []
    y_data = []
    data_index = []
    n_all_words = len(cut_word_list)
    for i in range(0, n_all_words - SEQ_LENGTH - 1):
        seq_x_y = cut_word_list[i: i+SEQ_LENGTH + 1]   # 最后一个词是y,seq_x_y表示前seq_length个词是训练集,最后一个字是训练数据对应的y
        index_x_y = [word_to_index[elem] for elem in seq_x_y]    # 获取seq_x_y对应的index组成的列表
        data_index.append(index_x_y)
    np.random.shuffle(data_index)
    for i in range(0, len(data_index)):
        X_data.append(data_index[i][:SEQ_LENGTH])
        y_data.append(data_index[i][SEQ_LENGTH])

    # 将X_data变换成需要输入的tensor模式,将y_data变成one-hot模式
    X = np.reshape(X_data, (len(X_data), SEQ_LENGTH))
    y = np_utils.to_categorical(y_data)

    # 训练集合验证集分割。
    X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=33)

    return X_train, X_val, y_train, y_val


def model_lstm(X_train, X_val, y_train, y_val, word_to_index):
    """
    训练模型并保存参数设置。
    :param X_train:训练集X
    :param X_val:验证集X
    :param y_train:训练集y
    :param y_val:验证集y
    :param word_to_index:word索引
    :return:history_record,这个主要为了后续画图获取loss使用(也可以不画图,直接使用tensorboard)
    """
    input_shape = (SEQ_LENGTH,)
    x_train_in = Input(input_shape, dtype='int32', name="x_train")

    # word_index存储的是所有vocabulary的映射关系
    nb_words = min(MAX_NB_WORDS, len(word_to_index))
    embedding_layer = Embedding(nb_words, EMBEDDING_DIM, input_length=SEQ_LENGTH)(x_train_in)
    print("embedding layer is::", embedding_layer)
    print("build model.....")

    # return_sequences=True表示返回的是序列,否则下面的LSTM无法使用,但是如果下一层不是LSTM,则可以不写
    lstm_1 = LSTM(EMBEDDING_DIM, name="LSTM_1", return_sequences=True)(embedding_layer)
    lstm_2 = LSTM(EMBEDDING_DIM_2, name="LSTM_2")(lstm_1)
    dense = Dense(nb_words, activation="softmax", name="Dense_1")(lstm_2)                      # softmax【超参】

    model = Model(inputs=x_train_in, outputs=dense)
    print(model.summary())

    adam = Adam(lr=0.0001, beta_1=0.9, beta_2=0.99, epsilon=1e-08)                            #优化器【超参】
    model.compile(loss='categorical_crossentropy',
                  optimizer=adam,
                  metrics=['accuracy'])
    print("Train....")

    # save tensorboard info                                                              设置后续分析时Tensorboard要显示的东西
    tensorboard = TensorBoard(log_dir='./tensorboard_log/')  #Tensorboard的日志存储目录位置
    # save best model.
    checkpoint = ModelCheckpoint(filepath='./model_epoch50_2lstm_1dense_seq50_phrase_based_best.h5',
                                 monitor='val_loss', mode='min', save_best_only=True, save_weights_only=False, period=1, verbose=1)
    callback_list = [tensorboard, checkpoint]

    history_record = model.fit(X_train,
                              y_train,
                              batch_size=BATCH_SIZE,
                              epochs=EPOCHS,
                              validation_data=(X_val, y_val),
                              callbacks=callback_list
                              )
    model.save('./model_epoch50_2lstm_1dense_seq50_phrase_based_best.h5')
    return history_record

def plot_accuray(history_record):
    """
    plot the accuracy and loss line. 若使用tensorboard,则可以不使用
    :param history_record:
    :return:
    """
    accuracy_train = history_record.history["acc"]
    accuracy_val= history_record.history["val_acc"]
    loss_train = history_record.history["loss"]
    loss_val = history_record.history["val_loss"]
    epochs = range(len(accuracy_train))
    plt.plot(epochs, accuracy_train, 'bo', label='Training accuracy')
    plt.plot(epochs, accuracy_val, 'b', label='Validation accuracy')
    plt.title('Training and validation accuracy')
    plt.legend()
    plt.figure()
    plt.plot(epochs, loss_train, 'bo', label='Training loss')
    plt.plot(epochs, loss_val, 'b', label='Validation loss')
    plt.title('Training and validation loss')
    plt.legend()
    plt.show()

if __name__ == '__main__':
    file_name = "../train_data/all_5.txt"
    cut_word_list = cut_words(file_name)
    word_to_index, index_to_word = map_words(cut_word_list)
    X_train, X_val, y_train, y_val = generate_train_data(cut_word_list, word_to_index)
    history_record = model_lstm(X_train, X_val, y_train, y_val, word_to_index)
    # plot_accuray(history_record)



以下是一些故障排除方法:

1,点击train_lstm_word_based.py运行后报错:

ModuleNotFoundError: No module named 'sklearn

——》解决方法:conda install scikit-learn,然后报错.

——》解决方案:在pycharm中安装scikit-learn模块,然后又报错,如图1[这个问题,先放着不解决]

2,由于代码是用tensorflow v1.0写的。有tensorflow V1.0V2.0不兼容问题。如图2遇到的问题。

——》解决方案:把如下语句划线部分摘除from keras.backend.tensorflow_backend import set_session

把如下语句加上划线部分

Config=tf.compat.v1.Configproto()

Tf.compat.v1.Session()

3,报错 No module named 'matplotlib

——》查找:在prompt中测试matplotlib,是正常能发现的如图3】,但是在pycharm的解释器TensorFlow中没有发现该package说明该包没有被安装到tensorflow环境中

——》解决方案:在prompt中切换环境到tensorflow,再“conda install matplotlib”安装。虽然有报错,但该训练模型真run起来了。如右下图所示:

CPU满跑,但计算器不卡,内存也还好,占用了4.8G内存。

在训练过程中,在训练文件夹中生成了Tensorboard文件夹。可以利用tensorflow自带的工具去打开。

     启动TensorBoardTensorBoard不需要额外安装,在TensorFlow安装时已自动完成,在Anaconda Prompt中先进入日志存放的目录(注:非常重要),再运行TensorBoard,并将日志的地址指向程序日志输出的地址。命令:tensorboard --logdir=/path/log /path/log为产生日志文件的目录)启动服务的端口默认为6006;使用 --port 参数可以改编启动服务的端口。TensorBoard是一个在本地启动的服务,启动完成后在浏览器网址:http://localhost:6006即可进行访问。

     在浏览器中打开tensorboard,除了不能代替中途保存epoch训练文件,都可以分析诸如lossAaccuragy随着训练进程的变化数据。

注意:

seq2seq的训练相较于LSTM

1,内存开销,这个极大,开始运行不起来,提示内存满了。后来电脑重开机,并且启用系统虚拟内存。物理内存还是接近满跑状况。

2,50次训练,总共耗时9小时。最后也得到了一堆不知道什么的东西。句式长度随机、意思随机、连韵脚也随机。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值