【虫二堂】【转】致敬赵雷:基于TensorFlow让机器生成赵雷曲风的歌词

低调是种无声的高调 2017-02-20 07:22:24


写在技术算法前面的话:

  我们基本上收集了赵雷所有唱过的歌曲的歌词。



【无法长大】共收录了 10 支单曲:

  《朵》、《八十年代的歌》、《无法长大》、《玛丽》、《阿刁》、《鼓楼》、《孤独》、《成都》、《窑上路》、《再见北京》



【吉姆餐厅】共收录了 10 支单曲:

  《吉姆餐厅》、《少年锦时》、《梦中的哈德森》、《我们的时光》、《理想》、《三十岁的女人》、《家乡》、《浮游》、《小屋》、《北京的冬天》



【赵小雷】共收录了 12 支单曲:

  《人家》、《未给姐姐递出的信》、《画》、《不开的唇》、《赵小雷》、《南方姑娘》、《Over》、《开往北京的火车》、《背影》、《妈妈》、《南方姑娘(弹唱版)》、《民谣》



【其他单曲】共 22 支单曲:

  《19 岁时候的歌》、《已是两条路上的人》、《再也不会去丽江》、《让我偷偷看你》、《咬春》、《难受》、《辞行》、《保存》、《雪人》、《青春无处安放》、《凭什么说爱你》、《过年》、《何必》、《2012 年之前》、《别》、《爱人你在哪里》、《夏天》、《飞来飞去》、《逆流而上》、《花朵》、《罪》、《不能自主》

  共计53 首单曲,1560 行歌词。

  1. 原理回顾

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



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

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

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

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

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

  2. 模型代码设计

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

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

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

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

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

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

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

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





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



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





















  最后,抽样生成过程的具体代码如下所示,其中 start 是种子序列,attention 是判断是否加入了注意力机制。

word = start[-1]for n in range (num):

x = np.zeros ((1, 1))

x[0, 0] = words[word]

if not self.args.attention:

feed = {self.input_data: [x], self.initial_state:state}

[probs, state] = sess.run ([self.probs, self.final_state], feed)

else:

feed = {self.input_data: x, self.initial_state:state,self.attention_states:attention_states}

[probs, state] = sess.run ([self.probs, self.final_state], feed)

p = probs[0]

sample = random_pick (p,word,sampling_type)

pred = vocab[sample]

ret += pred

word = pred

  ret 数组即为最终的生成序列。

  6. 编写训练函数

  训练函数需要完成的功能主要有提供用户可设置的超参数、读取配置文件、按照 mini-batch 进行批训练、使用 saver 保存模型参数、记录训练误差等等,下面将列举部分代码进行说明。

  首先,我们使用 argparse.ArgumentParser 对象进行解析命令行参数或者设置默认参数,例如:

parser.add_argument ('--rnn_size', type=int, default=128,

help='set size of RNN hidden state')

  设置了 rnn_size 默认大小为 128,而用户也可以在命令行使用类似于以下这种方式来指定参数大小:

python train.py --rnn_size 256

  其次,我们需要提供是否继续训练的判断,也就说是从头开始训练还是导入一个已经训练过的模型继续训练,即下面的语句:

if args.keep is True:

print ('Restoring')

model.saver.restore (sess, ckpt.model_checkpoint_path)

else:

print ('Initializing')

sess.run (model.initial_op)

  然后就是将X和Y数据 feed 到模型中去运行 op 并得到误差值:

x, y = text_parser.next_batch ()

feed = {model.input_data: x, model.targets: y, model.initial_state: state}

train_loss, state, _ = sess.run ([model.cost, model.final_state, model.train_op], feed)

  训练过程比较简单,基本上就是设置参数-导入数据-导入模型-运行 op-得到误差-下一个 Epoch 继续训练,直到满足要求为止。

  7. 编写日志

  这里说的日志可以理解为保存参数、保存训练过程中的误差以及训练时间等等,仅作抛砖引玉的说明。为了使得每一次训练都不会白白浪费,我们需要设置好参数保存,如可以设置训练了多少个样本就保存一次参数、训练了多少个 Epoch 就保存一次:

if (e*text_parser.num_batches+b)%args.save_every==0 、

or (e==args.num_epochs-1 and b==text_parser.num_batches-1):

checkpoint_path = os.path.join (args.save_dir, 'model.ckpt')

model.saver.save (sess, checkpoint_path, global_step = e)

print ("model has been saved in:"+str (checkpoint_path))

  记录训练误差也是很重要的一步,很多时候我们需要分析 cost 曲线随时间或者是迭代次数的变化趋势,因此这里我们建立了一个 logging 函数(在 utils.py 文件中),并且在每一个 Epoch 训练结束的时候就记录一次该 Epoch 的平均误差、运行时间等等:

delta_time = end - start

ave_loss = np.array (total_loss) .mean ()

logging (model,ave_loss,e,delta_time,mode='train')

  8. 编写可视化函数

  由于时间的关系,这里仅对日志文件做了初步的可视化,即提取日志文件中的 Epoch 以及对应的误差,从而得到一条 Cost-Epoch 曲线,可视化的函数的部分代码如下:

if line.startswith ('Epoch'):

if 'validate' in line:

index2 = index2 + 1

cost = line.split (':')[2]

indexValidateList.append (index2)

validateCostList.append (float (cost))

elif 'train error rate' in line:

index1 = index1+1

cost = line.split (':')[2]

indexCostList.append (index1)

costList.append (float (cost))

  然后使用 matplotlib 库进行作图:

def plot (self):

title,indexCostList,costList = self.parse ()

p1 = plt.plot (indexCostList,costList,marker='o',color='b',label='train cost')

plt.xlabel ('Epoch')

plt.ylabel ('Cost')

plt.legend ()

plt.title (title)

if self.saveFig:

plt.savefig (self.logFile+'.png',dpi=100)

#plt.savefig (self.logFile+'.eps',dpi=100)

if self.showFig:

plt.show ()

  9. 设置训练超参

  超参的选择一直是训练深度学习的一个难点,无论是循环神经元的个数、层数还是训练样本批处理的大小,都没有一个固定的判断准则,超参设置因问题而已,而且很多时候论文中使用的经验规则,而我这里也只能根据我们做语音识别系统的经验设置的参数。

  我们选择了两层 LSTM,每层包含 128 个神经元作为 seq2seq 模型的 cell,词向量 word_embedding 的大小为 100,批处理大小设置为 32,序列长度为 16,并且使用了 Adam 随机梯度下降算法,学习率设置为 0.001,一共训练了 230 个周期。

  10. 训练环境

  本次训练的环境是 Ubuntu 16.04 操作系统,使用的 tensorflow 版本是 r0.11,所使用的 python 版本为 2.7,所用的 GPU 是 Nvidia GeForce GTX 960M。

  11. 结果展示

  我们现在选择了《我们》作为种子序列,然后让机器生成了长度为 200 的歌词,如下所示,其中有几句看起来似乎有押韵的意思,整体上看起来不知道要表达什么(选取几个示例):

  赵雷 1 num:200

  我们是不是我的压抑挣住。我爱这世间美貌的女子。可是她们不能,与心里。生命运说的太多。那是我快乐。社会更想你。说爱你在你滴话我来需要在家远。春天的阳光会让它消失无影踪。我醒来时看到白茫茫一片。堆起的雪人谁能赋予它灵魂。他睁着眼笑着看冬天走远。春天的阳光会让它消失无影踪。它变成泥土像纷飞的叶子。它去浪拥有这个世界。让我吧边朝你我的心事。我只能做在街头发地的那路是堵着。看过去这样像躲不去自己。总把失去的故烂。

  赵雷 2 num:200

  我们的话不是不是醉的我易世界。那啊坚反些等待下太多。长大后的脚都在星星下。我爱那个黄昏有几个可以走远的树子。再也不会有明月,再也不能与我希望。天空很下,岁女你。有没有你还一些部。还是你的故事。十年的年前是是再时看身影子。她是个不刚有点个世界。你不是不是我的唱和你。我知道我一直知决在石桥。一个人和人留下一个春天。再见昨夜里面雷小雨停尽。我也哭了我的故事还是星星伤。挑剔着,轮换着,你动越来越来的就把我阴了。

  赵雷 3 num:200

  我们是不是我的家乡。即使死在,我的生活,不懂难开。昨日的好狠卸倒在树。偶尔的走一走,在我性。就不会委屈了你。一切会好。为这么多的怀抱。总是觉得工作太忙。常常被冷眼灼伤。不会在这个城市我这个世界。这也清知明天爱的星空是在回家。我也只有你小屋你装满了宽恕。我的小屋我喜欢给你唱歌我喜欢坐在看头。可是你不是在这世间爱的,像路在阴暗空的活。我在来飞的太好。赵小雷小赵,哦哦,否习惯,不蓝。沉睡,不会让你往东。你敢。

  赵雷 4 num:200

  我们是原因我是个不老的混蛋了。一看到你的泪不用可以光着脚。沈浸在雨后的长路,没有人来往。那是我的自由,那是黄昏收获的麦香。那时树影下的母亲在为我缝补衣裳。那是她的天堂,那是再见之后。一路的冬天太冷我已不能体会任希望的街头。我穿着苍瑕的爱情,再也不会被子。给你一好的你。一定是你希望。如果我真的哭了,是舍不得北京。带不走在这个城市我的心。为见北京还那样往北京的火车。路上的笑站在树,谁久何时间世界。让我添上。

  12. 优化改进

  现在我们加入了林夕的所有歌词作为语言模型的预训练语料,通过训练好基于 LSTM-seqseq 的语言模型之后,再只使用赵雷的歌词来在此模型上接着训练了 40 个 Epoch。

  编辑部为了这次优化,想了很多名词作为歌曲的开头。分别是:

“平淡” : 赵雷为人就是怎么平易近人

“有你们的爱”: 给所有爱赵雷的人们

“游戏而已”: 赵雷在歌手说过的一句话

“我就是我”: 赵雷你是不一样的烟火

“北方姑娘”: 赵雷南方姑娘的对立

“我的姑娘你在哪儿”: 希望赵雷找到自己的幸福

  好啦,接下就让我们一起看看结果吧!

  作为种子序列生成的长度为 200 的赵雷风格的歌词如下(为了美观,所有逗号改成了空格,句号改成了换行):

平淡

平淡

花多好戏地说再见

身回最这样如何其实再边竟会唔于动人

世界最外表演天事

借给你肩膀才能证明我会过怎知道

我也有想你听过这一切

献出自己事

难受伤懂得体去寂寞

别等别留给你奏礼

待着过和人生或者都给你我的模糊

有人有一个人的对子

是场游戏的歌

而我这个世界我

在谁要天际

无法自己

只有红它却如进了

就走在笑着花儿

星星灰银色

你说很少把春把月亮

站在你去在走

随时流过无时笑过

昨天的妈在这夜空没

有你们的爱

有你们的爱太简单

让你拥有一丝负担的温柔

我要回的遥见时差往北

熟通的笑声敲打

我们能顾及身人

过客已没有理掉

所有的责任关心

来时又比有吸

你也明明我自己让余歌

更温柔吻看不到的理思

薰衣服里请你在过

火总有天总是谢你有过有了多少她有没有个好或

曾给我也可以难实疯的多人

起来短有我沉在多月亮

来雨后雨照把烟

一感一天面不样

熟悉的是闻亲爱的

我爱的本钱也不能

像随街换转身边少分

会转身都由面无再学

游戏而已

游戏而已高极高逐

无论世界大路口多

人被我无怨还是错

种信候。愿我问候

夏日花色还有十号

歌早不出笑谢你走过

电柔菌开那是奖上一种泪过

十个寂寞不要就这么一个

好地牺牲我已经闹事唱沦手朋友

信心有明知当日我

只愿意忘了有刻想给到你重头

便会忍心一生对不过

仍可以去自己怎会好

没有你没有什么东西可叫开心

在这里开走一把手动听你身家全世界

那么多

仍然未能被拥抱高一块较

算你或者会坐

定要指打我的身情

终要重头

我就是我

我就是我的地教我有个梦

是二么老友她拿着和你说

有你的很想都放弃

就像一切心跳

走过走一个去不分开始一样

这张开心第一生太多节奏

为你而受伤只得好怎么怎知一世

不需多管我讲过

我期待我就求运识你在手电过以未够一样

便存在谁在起来

谁想给你别给我讲多可假

才明白我愿意凭着世利宁愿挡子弹

请与其实未想过代未见

错爱情仍能要逃避

我会在意当未来也要痛苦

每次发泄也不要能得到用劲

惯去苦也不再展开始没出利保留到你

北方姑娘

北方姑娘以后忘记

独自由心清醒大几多

薰衣草

能各自吸引

什么都哭干泪人又一样的过

别留下我的眼睛已把我的温柔

我爱你你会停留住离

离阳的路擦到的怀巴

城市的幻想起浪漫阳光眼泪

从来没日落后最大地问数

让我这个挽着没有时刻它笑未必反明快乐清楚

等待我对具游戏

心事全破忘拥有

我们大丈夫

只是有期待我一分

模糊面不被疑流

为跳最怕讲你都说分

愿你的情绪比我内得

累这世界

热管你说过去做

眼泪你找到你

我的姑娘你在哪儿

我的姑娘你在哪儿静别人

而我太多给你离开的鲜空

在我街着一件闪烁的人

即使北京就破

想到红尘忌时请放开我觉得在乎

高遗失望也许也完全无依依

愿我更难受

面对不需要属于我一样的好

我就开始不会把

事业不开怎么一分

给我爱你那么感受

狂情不合沉默的心情擦到的风

在这两个梦时今晚

什么不多一个

人生最一秒钟我像一道未见过她

亲爱的,以为我自己送给你亲爱的



赵云蓝已经过收上

爱的过火别口真情敌已经要无人

最大种声

  结果不是特别理想,主要原因是:赵雷的歌曲还是比较少,数量没有达到很多,以至于训练出来的比较奇怪,有些语句不通顺。但是,我们尽自己最大努力去做这一件事啦。目的是表达我们对雷子的热爱之情。

  希望赵雷在今后的音乐道路上越走越远,创作出更多好的作品,这样,我们的结果会更好!向赵雷致敬!

  科技牛逼!!!

  民谣牛逼!!!

  赵雷牛逼!!!
...全文
350 21 打赏 收藏 转发到动态 举报
写回复
用AI写文章
21 条回复
切换为时间正序
请发表友善的回复…
发表回复
starytx 2017-02-21
  • 打赏
  • 举报
回复
你要么今天用他们的套,要么十个月后用他们的奶。
燕-十三 2017-02-21
  • 打赏
  • 举报
回复
太长了
  • 打赏
  • 举报
回复
_nives 2017-02-21
  • 打赏
  • 举报
回复
赵雷最近有点人气哦。
懒懒的吉他手 2017-02-21
  • 打赏
  • 举报
回复
谁啊,不认识
Z_墨脱 2017-02-21
  • 打赏
  • 举报
回复
看不懂
  • 打赏
  • 举报
回复
歌手上第一次听他的歌。
  • 打赏
  • 举报
回复
木有联想算法,有点初级!不过加入了联想算法,木有高效的评价算法, 也只能靠加班拼人力
女神打Boss 2017-02-21
  • 打赏
  • 举报
回复
不懂
jiajing1990_ 2017-02-21
  • 打赏
  • 举报
回复
表示不懂
  • 打赏
  • 举报
回复
看来没几个听民谣的
爱睡觉的阿狸 2017-02-20
  • 打赏
  • 举报
回复
js14982 2017-02-20
  • 打赏
  • 举报
回复
引用 8 楼 MrMicrosoft 的回复:
引用 6 楼 js14982 的回复:
引用 5 楼 MrMicrosoft 的回复:
引用 3 楼 js14982 的回复:
应该是擎天架海系列
为了避免你猜对,我容易么
那你还要再开一贴来擎天架海
为了保持你目前的100%猜错率,我决定今天就不发了
那lice1010哥不是要灭了我啊?
  • 打赏
  • 举报
回复
引用 6 楼 js14982 的回复:
引用 5 楼 MrMicrosoft 的回复:
引用 3 楼 js14982 的回复:
应该是擎天架海系列
为了避免你猜对,我容易么
那你还要再开一贴来擎天架海
为了保持你目前的100%猜错率,我决定今天就不发了
___紫菜 2017-02-20
  • 打赏
  • 举报
回复
什么跟什么
js14982 2017-02-20
  • 打赏
  • 举报
回复
引用 5 楼 MrMicrosoft 的回复:
引用 3 楼 js14982 的回复:
应该是擎天架海系列
为了避免你猜对,我容易么
那你还要再开一贴来擎天架海
  • 打赏
  • 举报
回复
引用 3 楼 js14982 的回复:
应该是擎天架海系列
为了避免你猜对,我容易么
js14982 2017-02-20
  • 打赏
  • 举报
回复
真特么长,我拖了好半天
js14982 2017-02-20
  • 打赏
  • 举报
回复
应该是擎天架海系列
js14982 2017-02-20
  • 打赏
  • 举报
回复
喂,是公安局吗,这里有人装B,快控制不住了
加载更多回复(1)

679

社区成员

发帖
与我相关
我的任务
社区描述
智能路由器通常具有独立的操作系统,包括OpenWRT、eCos、VxWorks等,可以由用户自行安装各种应用,实现网络和设备的智能化管理。
linuxpython 技术论坛(原bbs)
社区管理员
  • 智能路由器社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧