查看原文
其他

作词家下岗系列:教你用 AI 做一个写歌词的软件!

李秋键 AI科技大本营 2020-10-29
作者 | 李秋键
责编 | Carol
出品 | AI科技大本营(ID:rgznai100)
自然语言处理作为人工智能的一个重要分支,在我们的生活中得到了广泛应用。其中RNN算法作为自然语言处理的经典算法之一,是文本生成的重要手段。而今天我们就将利用RNN算法建立一个写歌词的软件。其中的界面如下:
RNN指的是循环神经网络,Recurrent Neural Network。不同于前馈神经网络的是,RNN可以利用它内部的记忆来处理任意时序的输入序列,这让它可以更容易处理如不分段的手写识别、语音识别等。
RNN模型有比较多的变种,这里介绍最主流的RNN模型结构如下:
上图中左边是RNN模型没有按时间展开的图,如果按时间序列展开,则是上图中的右边部分。我们重点观察右边部分的图。
这幅图描述了在序列索引号tt附近RNN的模型。其中:
  1. x(t)x(t)代表在序列索引号tt时训练样本的输入。同样的,x(t−1)x(t−1)和x(t+1)x(t+1)代表在序列索引号t−1t−1和t+1t+1时训练样本的输入。

  2. h(t)h(t)代表在序列索引号tt时模型的隐藏状态。h(t)h(t)由x(t)x(t)和h(t−1)h(t−1)共同决定。

  3. o(t)o(t)代表在序列索引号tt时模型的输出。o(t)o(t)只由模型当前的隐藏状态h(t)h(t)决定。

  4. L(t)L(t)代表在序列索引号tt时模型的损失函数。

  5. y(t)y(t)代表在序列索引号tt时训练样本序列的真实输出。

  6. U,W,VU,W,V这三个矩阵是我们的模型的线性关系参数,它在整个RNN网络中是共享的,这点和DNN很不相同。也正因为是共享了,它体现了RNN的模型的“循环反馈”的思想。   

基于以上认知,我们开始搭建我们的软件。

实验前的准备


首先我们使用的python版本是3.6.5所用到的库有TensorFlow,是用来训练和加载神经网络常见的框架,常常用于数值计算的开源软件库。节点表示数学操作,线则表示在节点间相互联系的多维数据数组,即张量(tensor);tkinter用来绘制GUI界面的库;
Pillow库在此项目中用来处理图片和字体等问题。因为我们的软件不是空白背景的。需要借助Image函数添加背景。

RNN算法搭建


1、数据集处理和准备:

我们训练的数据集使用各种歌手的歌词本作为训练集。其中数据集放在date.txt里,其中部分数据集如下:

2、模型的训练:

模型训练的代码直接运行train.py即可训练。其中流程如下:
  1. 首先要读取数据集

  2. 设定训练批次、步数等等

  3. 数据载入RNN进行训练即可

其中代码如下:
def train():
    filename = 'date.txt'
    with open(filename, 'r', encoding='utf-8'as f:
        text = f.read()
    reader = TxtReader(text=text, maxVocab=3500)
    reader.save('voc.data')
    array = reader.text2array(text)
    generator = GetBatch(array, n_seqs=100, n_steps=100)
    model = CharRNN(
                    numClasses = reader.vocabLen,
                    mode ='train',
                    numSeqs = 100
                    numSteps = 100,
                    lstmSize = 128
                    numLayers = 2
                    lr = 0.001
                    Trainprob = 0.5
                    useEmbedding = True
                    numEmbedding = 128
                )
    model.train(
                generator,
                logStep = 10
                saveStep = 1000,
                maxStep = 100000
                )
3、RNN网络搭建:
RNN算法的搭建,我们定义整个神经网络类,然后分别定义初始化、输入、神经元定义等函数。损失函数和优化器使用均方差和AdamOptimizer优化器即可。
部分代码如下:
# 创建输入
    def buildInputs(self):
        numSeqs = self.numSeqs
        numSteps = self.numSteps
        numClasses = self.numClasses
        numEmbedding = self.numEmbedding
        useEmbedding = self.useEmbedding
        with tf.name_scope('inputs'):
            self.inData = tf.placeholder(tf.int32, shape=(numSeqs, numSteps), name='inData')
            self.targets = tf.placeholder(tf.int32, shape=(numSeqs, numSteps), name='targets')
            self.keepProb = tf.placeholder(tf.float32, name='keepProb')
            # 中文
            if useEmbedding:
                with tf.device("/cpu:0"):
                    embedding = tf.get_variable('embedding', [numClasses, numEmbedding])
                    self.lstmInputs = tf.nn.embedding_lookup(embedding, self.inData)
            # 英文
            else:
                self.lstmInputs = tf.one_hot(self.inData, numClasses)
    # 创建单个Cell
    def buildCell(self, lstmSize, keepProb):
        basicCell = tf.nn.rnn_cell.BasicLSTMCell(lstmSize)
        drop = tf.nn.rnn_cell.DropoutWrapper(basicCell, output_keep_prob=keepProb)
        return drop
    # 将单个Cell堆叠多层
    def buildLstm(self):
        lstmSize = self.lstmSize
        numLayers = self.numLayers
        keepProb = self.keepProb
        numSeqs = self.numSeqs
        numClasses = self.numClasses
        with tf.name_scope('lstm'):
            multiCell = tf.nn.rnn_cell.MultiRNNCell(
                    [self.buildCell(lstmSize, keepProb) for _ in range(numLayers)]
                )
            self.initial_state = multiCell.zero_state(numSeqs, tf.float32)
            self.lstmOutputs, self.finalState = tf.nn.dynamic_rnn(multiCell, self.lstmInputs, initial_state=self.initial_state)
            seqOutputs = tf.concat(self.lstmOutputs, 1)
            x = tf.reshape(seqOutputs, [-1, lstmSize])
            with tf.variable_scope('softmax'):
                softmax_w = tf.Variable(tf.truncated_normal([lstmSize, numClasses], stddev=0.1))
                softmax_b = tf.Variable(tf.zeros(numClasses))
            self.logits = tf.matmul(x, softmax_w) + softmax_b
            self.prediction = tf.nn.softmax(self.logits, name='prediction')
    # 计算损失
    def buildLoss(self):
        numClasses = self.numClasses
        with tf.name_scope('loss'):
            targets = tf.one_hot(self.targets, numClasses)
            targets = tf.reshape(targets, self.logits.get_shape())
            loss = tf.nn.softmax_cross_entropy_with_logits(logits=self.logits, labels=targets)
            self.loss = tf.reduce_mean(loss)
    # 创建优化器
    def buildOptimizer(self):
        gradClip = self.gradClip
        lr = self.lr
        trainVars = tf.trainable_variables()
        # 限制权重更新
        grads, _ = tf.clip_by_global_norm(tf.gradients(self.loss, trainVars), gradClip)
        trainOp = tf.train.AdamOptimizer(lr)
        self.optimizer = trainOp.apply_gradients(zip(grads, trainVars))
    # 训练
    def train(self, data, logStep=10, saveStep=1000, savepath='./models/', maxStep=100000):
        if not os.path.exists(savepath):
            os.mkdir(savepath)
        Trainprob = self.Trainprob
        self.session = tf.Session()
        with self.session as sess:
            step = 0
            sess.run(tf.global_variables_initializer())
            state_now = sess.run(self.initial_state)
            for x, y in data:
                step += 1
                feed_dict = {
                                self.inData: x,
                                self.targets: y,
                                self.keepProb: Trainprob,
                                self.initial_state: state_now
                            }
                loss, state_now, _ = sess.run([self.loss, self.finalState, self.optimizer], feed_dict=feed_dict)
                if step % logStep == 0:
                    print('[INFO]: <step>: {}/{}, loss: {:.4f}'.format(step, maxStep, loss))
                if step % saveStep == 0:
                    self.saver.save(sess, savepath, global_step=step)
                if step > maxStep:
                    self.saver.save(sess, savepath, global_step=step)
                    break
    # 从前N个预测值中选
    def GetTopN(self, preds, size, top_n=5):
        p = np.squeeze(preds)
        p[np.argsort(p)[:-top_n]] = 0
        p = p / np.sum(p)
        c = np.random.choice(size, 1, p=p)[0]
        return c
4、歌词的生成:
设置关键词变量,读取模型文件,输出结果即可。
代码如下:
def main(_):
    reader = TxtReader(filename='voc.data')
    model = CharRNN(
                    numClasses = reader.vocabLen,
                    mode = 'test',
                    lstmSize = 128
                    numLayers = 2
                    useEmbedding = True
                    numEmbedding = 128
                )
    checkpoint = tf.train.latest_checkpoint('./models/')
    model.load(checkpoint)
    key="雪花"
    prime = reader.text2array(key)
    array = model.test(prime, size=reader.vocabLen, n_samples=300)
    print("《"+key+"》")
    print(reader.array2text(array))

界面的定义和调用


界面中我们的布局是文本框、编辑框和按钮控件。程序的调用使用批处理文件调用以达到显示运行过程的效果。因为如果没有运行过程,难免会导致用户不清楚程序流程而强制运行容易导致卡死的情况。
其中Bat里直接写入:
python song.py
其中过程效果如下:

1、界面布局:

界面布局使用canvas画布以达到添加背景图片的效果。背景图片设置为1.jpg,按钮背景图片设置为3.jpg。图片也可以自己更换掉。然后文本框作为提示的效果,分别定义字体,大小等等即可
代码如下:
root = tk.Tk()
root.title('AI写歌词')
# 背景
canvas = tk.Canvas(root, width=800, height=500, bd=0, highlightthickness=0)
imgpath = '1.jpg'
img = Image.open(imgpath)
photo = ImageTk.PhotoImage(img)

imgpath2 = '3.jpg'
img2 = Image.open(imgpath2)
photo2 = ImageTk.PhotoImage(img2)
canvas.create_image(700, 400, image=photo)
canvas.pack()
label=tk.Label(text="请输入关键词:",font=("微软雅黑",20))
entry = tk.Entry(root, insertbackground='blue', highlightthickness=2,font=("微软雅黑",15))
entry.pack()
entry1 = tk.Text(height=15,width=115)
entry1.pack()

2、功能调用:

我们使用按钮中的command参数调用已设置好的函数即可。其中函数部分我们通过生成文本和删除文本的方式读入数据和写入数据。为了防止数据重叠故在要时刻监测重复软件。定义的函数内容如下:
def song():
    ss=entry.get()
    f=open("1.txt","w")
    f.write(ss)
    f.close()
    os.startfile("1.bat")
    while True:
        if os.path.exists("2.txt"):
            f=open("2.txt")
            ws=f.read()
            f.close()
            entry1.insert("0.0", ws)
            break
    try:
        os.remove("1.txt")
        os.remove("2.txt")
    except:
        pass
3、GUI代码:
整个GUI界面代码如下:
import tkinter as tk
from PIL import ImageTk, Image
import os
try:
    os.remove("1.txt")
    os.remove("2.txt")
except:
    pass
import os
def song():
    ss=entry.get()
    f=open("1.txt","w")
    f.write(ss)
    f.close()
    os.startfile("1.bat")
    while True:
        if os.path.exists("2.txt"):
            f=open("2.txt")
            ws=f.read()
            f.close()
            entry1.insert("0.0", ws)
            break
    try:
        os.remove("1.txt")
        os.remove("2.txt")
    except:
        pass
root = tk.Tk()
root.title('AI写歌词')
# 背景
canvas = tk.Canvas(root, width=800, height=500, bd=0, highlightthickness=0)
imgpath = '1.jpg'
img = Image.open(imgpath)
photo = ImageTk.PhotoImage(img)

imgpath2 = '3.jpg'
img2 = Image.open(imgpath2)
photo2 = ImageTk.PhotoImage(img2)
canvas.create_image(700400, image=photo)
canvas.pack()
label=tk.Label(text="请输入关键词:",font=("微软雅黑",20))
entry = tk.Entry(root, insertbackground='blue', highlightthickness=2,font=("微软雅黑",15))
entry.pack()
entry1 = tk.Text(height=15,width=115)
entry1.pack()
bnt = tk.Button(width=15,height=2,image=photo2,command=song)
canvas.create_window(10050, width=200, height=30,
                     window=label)
canvas.create_window(50050, width=630, height=30,
                     window=entry)
canvas.create_window(400100, width=220, height=50,
                     window=bnt)
canvas.create_window(400335, width=600, height=400,
                     window=entry1)
root.mainloop() 
到这里,我们整体的程序就搭建完成,下面为我们程序的运行过程和结果:
源码地址:
链接:https://pan.baidu.com/s/1EJsHIXbKUmRG-MdHcqkdFg
提取码:iz5m

作者简介 :

李秋键,CSDN 博客专家,CSDN达人课作者。硕士在读于中国矿业大学,开发有taptap安卓武侠游戏一部,vip视频解析,文意转换工具,写作机器人等项目,发表论文若干,多次高数竞赛获奖等等。


推荐阅读


    你点的每个“在看”,我都认真当成了AI

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存