基于TensorFlow让机器生成赵雷曲风的歌词

本文转载自公众号量化投资与机器学习(ZXL_LHTZ_JQXX)
原文:『致敬赵雷』基于TensorFlow让机器生成赵雷曲风的歌词
责编:王艺 投稿及采访请邮件wangyi@csdn.net扫描文末二维码

我们基本上收集了赵雷所有唱过的歌曲的歌词。包括《无法长大》、《吉姆餐厅》、《赵小雷》等专辑在内的共计53首单曲、1560行歌词。生成结果附在文末。

图片描述

一、原理回顾

机器作词是序列建模(以下简称seq2seq)的典型应用,其基本思想就是给定序列A,机器负责产生序列B,并且再将序列B作为输入,机器负责生成序列C…如此循环下去即可生成无限长度的序列。seq2seq模型图如下所示,左边是编码器,右边是解码器。

机器作词是序列建模(以下简称seq2seq)的典型应用,其基本思想就是给定序列A,机器负责产生序列B,并且再将序列B作为输入,机器负责生成序列C…如此循环下去即可生成无限长度的序列。seq2seq模型图如下所示,左边是编码器,右边是解码器。

假设问题是从序列A到序列B之间的映射,那么seq2seq模型的工作流程如下:

序列A中的每一个单词通过word_embedding操作以后,作为input进入编码器,编码器可以是一个多层RNN结构,编码器输出一个向量;

训练的时候,解码器的输入跟编码器的输入是一样的,然后解码器的输出与序列B之间的交叉熵作为模型的目标函数;

生成的时候,首先给定一个种子序列作为编码器的输入,并且解码器的上一时刻的输出作为下一时刻的输入,如此循环往复,直到生成给定数量的序列。

本文建立的模型就是基于以上原理。

二、模型代码设计

要完成机器生成歌词的工作看上去是一个生成模型,而生成模型一般都是无监督问题,但是我们需要将它转化成有监督问题,原因是使用有监督学习可以发现数据内在的关联性,比如上下文的衔接,然后用预测学习来代替无监督学习。

就有监督学习而言,通常我们需要准备好具有映射关系的数据集:X和Y。这里我们事先只有周杰伦的歌词文本,它是一个整体,如何确定X和Y?虽然它是一个整体,但是这个整体是序列组成的,序列与序列之间会有一定的时序关系。比如对于

让我掉下眼泪的 不止昨夜的酒

我们是不是可以把“让我掉下眼泪的 ”看作X,把“ 不止昨夜的酒”看作Y,如果我们将X输入进网络,而网络输出的是Y,那就说明我们构建的网络已经具备写歌词的能力了。这就是我们划分数据集为X和Y的原理。一般情况下,数据需要划分为训练集和测试集,由于时间的缘故,这里没有划分测试集了。

当我们把数据预处理做好了,接下来就是构建模型了,构建模型主要是围绕seq2seq模型,而在编码器和解码器部分,我们可以自由构造,如可以选择不同的rnn_cell,或者选择不同的层数、神经元个数,具体情况因数据量大小而定。构建有监督学习模型的最重要部分就是目标函数,并且要确保目标函数对于所有要训练的参数是可微的,这样我们就可以构建端对端的基于后向误差更新的深度学习系统。

当有监督学习训练的模型的误差已经满足我们的要求了,就可以把参数保存下来,以便利用这个模型去生成歌词。生成模型的构建其实就是一个抽样的过程,给定种子序列,选好特定的抽样方法,即可生成无限多个汉字组成的序列。

为了了解训练过程中的误差更新趋势,我们还需要建立日志记录以及日志可视化的部分,这样以便于我们做后期的模型性能分析,本文中会粗略提及。

本项目的文件结构如下图所示:

.
├── analysis
│   └── plot.py
├── data
│   ├── context.npy
│   ├── lyrics.txt
│   ├── origin.txt
│   ├── parse.py
│   └── vocab.pkl
├── log
│   ├── 2016-12-1022:11:22.txt
│   └── 2016-12-1022:11:22.txt.png
├── preprocess.py
├── README.md
├── result
│   └── sequence
├── sample.py
├── save
│   ├── checkpoint
│   ├── config.pkl
│   ├── model.ckpt-223
│   ├── model.ckpt-223.meta
│   └── words_vocab.pkl
├── seq2seq_rnn.py
├── train.py
└── utils.py

它们的功能分别如下:

主目录下面的utils.py是公共函数库,preprocess.py是数据预处理代码,seq2seq_rnn.py是模型代码,sample.py是抽样生成过程,train.py是训练过程;

log目录中存储的是训练过程中的日志文件;

save目录中存储的是训练过程中的模型存储文件;

data目录中存放的是原始歌词数据库以及处理过的数据库;

result目录中存放的是生成的序列;

analysis目录中存放的是用于可视化的代码文件;

三、数据预处理

原始歌词文件是网络中下载的,其中包括了一些不必要的文本,由此我们过滤了所有的非中文字符,并且使用空格分隔相邻句子。例如下面一段代码的作用就是剔除源歌词文件中的多余空格以及非中文字符:

reg = re.compile(ur"[\s+]")
c = reg.sub(' ',unicode(c))
reg = re.compile(ur"[^\u4e00-\u9fa5\s]")
c = reg.sub('',unicode(c))
c = c.strip()

将源歌词文件处理成连续的句子文件以后,下一步就是将这么多句子划分成很多对训练样本。首先我们需要统计歌词中所有不同汉字的总数(包括一个空格),并且对这些汉字进行索引,可将原文由汉字变成整型数组,这样训练的时候读取数组就可以了;另外,索引还可以用来进行word_embedding,即将每个单词映射成一个特征向量。下面一段代码就是建立词典以及上下文的过程:

def build_dataset(self):
    ''' parse all sentences to build a vocabulary 
        dictionary and vocabulary list
    '''
    with codecs.open(self.input_file, "r",encoding='utf-8') as f:
        data = f.read()
    wordCounts = collections.Counter(data)
    self.vocab_list = [x[0] for x in wordCounts.most_common()]
    self.vocab_size = len(self.vocab_list)
    self.vocab_dict = {x: i for i, x in enumerate(self.vocab_list)}    
    with codecs.open(self.vocab_file, 'wb',encoding='utf-8') as f:
        cPickle.dump(self.vocab_list, f)
    self.context = np.array(list(map(self.vocab_dict.get, data)))
    np.save(self.context_file, self.context)

然后确定我们要建立的每对样本的长度以及训练时候的batch_size大小,进而把数据集分成很多个mini-batch,可以在训练的时候依次读取。这里需要注意的是,为了预处理方便,我们选择了固定长度作为样本序列的长度,并且让X和Y的长度一致,从数据集中选取X和Y的时候每次滑动步长为1,间隔也为1,如下代码所示:

def init_batches(self):
    ''' 
        Split the dataset into mini-batches, 
        xdata and ydata should be the same length here
        we add a space before the context to make sense.
    '''
    self.num_batches = int(self.context.size / (self.batch_size * self.seq_length))
    self.context = self.context[:self.num_ba
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值