用TensorFlow生成周杰伦歌词

最近深度学习在机器视觉CV、自然语言处理NLP领域表现出强大的潜力,各种深度学习/机器学习框架也层出不穷。tensorflow是google于去年(2015.11)开源的深度学习框架,截止目前(2016-11-28)github上已经有38000+的star数,称之为最近最受欢迎的深度学习框架一点也不过分。

本着学习tensorflow和RNN的目的,前些天发现了char-rnn这个有趣的项目,具体就是基于字符预测下一个字符,比日说已知hello的前四个字母hell,那我们就可以据此预测下一个字符很可能是o,因为是字符char级别的,并没有单词或句子层次上的特征提取,相对而言比较简单易学。

因为原作者的代码是基于torch写的,为了熟悉tensorflow,我就仔细地研究了一下具体代码,然后改写成基于tf的代码,github上也有基于tensorflow的char-rnn-tensorflow,恕我直言,有以下三点比较膈应:

  1. 代码写的不够直观简洁
  2. 另外因为最新tensorflow的部分api有所变化
  3. 中英文之间有一些差异,看能否成功移植到处理中文上

于是打算基于以上两个项目,自己重写!

基本原理

拿中文举例来说,每个字与每个字并不是统计上独立的,比如说如果不爱就不要再伤害 长度为10的序列,如果我们知道,下一个字有可能是,如果知道前两个字如果,第三个字就是的可能性大些,依次类推,如果知道前9个字如果不爱就不要再伤,那么最后一个就有可能是字。用图直观的表示如下。

rnn
总的来说,这是一个seq2seq的模型,训练数据用的是周杰伦01年到11年所有歌的歌词,训练数据和代码我都放到github上,可以从这里下载

代码

全部代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
import os
import sys
import time
import numpy as np
import tensorflow as tf
from tensorflow.contrib.tensorboard.plugins import projector
from tensorflow.python.ops import rnn_cell, seq2seq
class HParam():
batch_size = 32
n_epoch = 100
learning_rate = 0.01
decay_steps = 1000
decay_rate = 0.9
grad_clip = 5
state_size = 100
num_layers = 3
seq_length = 20
log_dir = './logs'
metadata = 'metadata.tsv'
gen_num = 500 # how many chars to generate
class DataGenerator():
def __init__(self, datafiles, args):
self.seq_length = args.seq_length
self.batch_size = args.batch_size
with open(datafiles, encoding='utf-8') as f:
self.data = f.read()
self.total_len = len(self.data) # total data length
self.words = list(set(self.data))
self.words.sort()
# vocabulary
self.vocab_size = len(self.words) # vocabulary size
print('Vocabulary Size: ', self.vocab_size)
self.char2id_dict = {w: i for i, w in enumerate(self.words)}
self.id2char_dict = {i: w for i, w in enumerate(self.words)}
# pointer position to generate current batch
self._pointer = 0
# save metadata file
self.save_metadata(args.metadata)
def char2id(self, c):
return self.char2id_dict[c]
def id2char(self, id):
return self.id2char_dict[id]
def save_metadata(self, file):
with open(file, 'w') as f:
f.write('id\tchar\n')
for i in range(self.vocab_size):
c = self.id2char(i)
f.write('{}\t{}\n'.format(i, c))
def next_batch(self):
x_batches = []
y_batches = []
for i in range(self.batch_size):
if self._pointer + self.seq_length + 1 >= self.total_len:
self._pointer = 0
bx = self.data[self._pointer: self._pointer + self.seq_length]
by = self.data[self._pointer +
1: self._pointer + self.seq_length + 1]
self._pointer += self.seq_length # update pointer position
# convert to ids
bx = [self.char2id(c) for c in bx]
by = [self.char2id(c) for c in by]
x_batches.append(bx)
y_batches.append(by)
return x_batches, y_batches
class Model():
"""
The core recurrent neural network model.
"""
def __init__(self, args, data, infer=False):
if infer:
args.batch_size = 1
args.seq_length = 1
with tf.name_scope('inputs'):
self.input_data = tf.placeholder(
tf.int32, [args.batch_size, args.seq_length])
self.target_data = tf.placeholder(
tf.int32, [args.batch_size, args.seq_length])
with tf.name_scope('model'):
self.cell = rnn_cell.BasicLSTMCell(args.state_size)
self.cell = rnn_cell.MultiRNNCell([self.cell] * args.num_layers)
self.initial_state = self.cell.zero_state(
args.batch_size, tf.float32)
with tf.variable_scope('rnnlm'):
w = tf.get_variable(
'softmax_w', [args.state_size, data.vocab_size])
b = tf.get_variable('softmax_b', [data.vocab_size])
with tf.device("/cpu:0"):
embedding = tf.get_variable(
'embedding', [data.vocab_size, args.state_size])
inputs = tf.nn.embedding_lookup(embedding, self.input_data)
outputs, last_state = tf.nn.dynamic_rnn(
self.cell, inputs, initial_state=self.initial_state)
with tf.name_scope('loss'):
output = tf.reshape(outputs, [-1, args.state_size])
self.logits = tf.matmul(output, w) + b
self.probs = tf.nn.softmax(self.logits)
self.last_state = last_state
targets = tf.reshape(self.target_data, [-1])
loss = seq2seq.sequence_loss_by_example([self.logits],
[targets],
[tf.ones_like(targets, dtype=tf.float32)])
self.cost = tf.reduce_sum(loss) / args.batch_size
tf.scalar_summary('loss', self.cost)
with tf.name_scope('optimize'):
self.lr = tf.placeholder(tf.float32, [])
tf.scalar_summary('learning_rate', self.lr)
optimizer = tf.train.AdamOptimizer(self.lr)
tvars = tf.trainable_variables()
grads = tf.gradients(self.cost, tvars)
for g in grads:
tf.histogram_summary(g.name, g)
grads, _ = tf.clip_by_global_norm(grads, args.grad_clip)
self.train_op = optimizer.apply_gradients(zip(grads, tvars))
self.merged_op = tf.merge_all_summaries()
def train(data, model, args):
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
saver = tf.train.Saver()
writer = tf.train.SummaryWriter(args.log_dir, sess.graph)
# Add embedding tensorboard visualization. Need tensorflow version
# >= 0.12.0RC0
config = projector.ProjectorConfig()
embed = config.embeddings.add()
embed.tensor_name = 'rnnlm/embedding:0'
embed.metadata_path = args.metadata
projector.visualize_embeddings(writer, config)
max_iter = args.n_epoch * \
(data.total_len // args.seq_length) // args.batch_size
for i in range(max_iter):
learning_rate = args.learning_rate * \
(args.decay_rate ** (i // args.decay_steps))
x_batch, y_batch = data.next_batch()
feed_dict = {model.input_data: x_batch,
model.target_data: y_batch, model.lr: learning_rate}
train_loss, summary, _, _ = sess.run([model.cost, model.merged_op, model.last_state, model.train_op],
feed_dict)
if i % 10 == 0:
writer.add_summary(summary, global_step=i)
print('Step:{}/{}, training_loss:{:4f}'.format(i,
max_iter, train_loss))
if i % 2000 == 0 or (i + 1) == max_iter:
saver.save(sess, os.path.join(
args.log_dir, 'lyrics_model.ckpt'), global_step=i)
def sample(data, model, args):
saver = tf.train.Saver()
with tf.Session() as sess:
ckpt = tf.train.latest_checkpoint(args.log_dir)
print(ckpt)
saver.restore(sess, ckpt)
# initial phrase to warm RNN
prime = u'你要离开我知道很简单'
state = sess.run(model.cell.zero_state(1, tf.float32))
for word in prime[:-1]:
x = np.zeros((1, 1))
x[0, 0] = data.char2id(word)
feed = {model.input_data: x, model.initial_state: state}
state = sess.run(model.last_state, feed)
word = prime[-1]
lyrics = prime
for i in range(args.gen_num):
x = np.zeros([1, 1])
x[0, 0] = data.char2id(word)
feed_dict = {model.input_data: x, model.initial_state: state}
probs, state = sess.run([model.probs, model.last_state], feed_dict)
p = probs[0]
word = data.id2char(np.argmax(p))
print(word, end='')
sys.stdout.flush()
time.sleep(0.05)
lyrics += word
return lyrics
def main(infer):
args = HParam()
data = DataGenerator('JayLyrics.txt', args)
model = Model(args, data, infer=infer)
run_fn = sample if infer else train
run_fn(data, model, args)
if __name__ == '__main__':
msg = """
Usage:
Training:
python3 gen_lyrics.py 0
Sampling:
python3 gen_lyrics.py 1
"""
if len(sys.argv) == 2:
infer = int(sys.argv[-1])
print('--Sampling--' if infer else '--Training--')
main(infer)
else:
print(msg)
sys.exit(1)

python3 gen_lyrics 0训练差不多10几分钟,然后运行python3 gen_lyrics.py 1生成的歌词如下:

结果展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
如果离开请不要伤害
你的笑 我的笑容 我们的爱情
不要哭不到我
你不该的爱情
我的笑容 让我们 我的爱溢出就象雨踢
我的嘴角
而我们 半兽人 的里
我用第留 你说的爱我
倭~的 的平候
越着你的热情
我不要再微笑
我的笑容 让我们 我的解 我用爱什么 娘子依旧等待 你好你 听不该
彩虹的 爱酋的色里 不像连事的模 我跟一种味道
冷不留
语沉我们寻的画面
全美飘 你的笑容 拼命不是我要 我的世界
我用的意色
缺席你 我的解释请你务事
你的笑 我知道你
我不要
我不会经的爱我
我不能
我的声音
我不能有受
又多慢再我
我不能有蠢
我没要给我
你的声音
你的爱情 我不要
我的声音 我不不想要我
你不懂
脑袋瓜有一点秀容的四周叫是一瓢爱
篮地危名是太饱勾 
你说最善月 豆腐(豆腐)
二药开始来 园幕毛驴 Hia键 还不多
说你的泪 我喜欢的浪装
龙不开 扯去了 爱不需存
不要问不太你的声音
我用第一天 我用眼泪

不多说了,我要写歌词去了,争取当下一个方文山!(escape)