致敬趙雷:基於TensorFlow讓機器生成趙雷曲風的歌詞

  

null

  寫在技術算法前面的話:

  我們基本上收集了趙雷所有唱過的歌曲的歌詞。

null

  【無法長大】共收錄了10支單曲:

  《朵》、《八十年代的歌》、《無法長大》、《瑪麗》、《阿刁》、《鼓樓》、《孤獨》、《成都》、《窯上路》、《再見北京》

null

  【吉姆餐廳】共收錄了10支單曲:

  《吉姆餐廳》、《少年錦時》、《夢中的哈德森》、《我們的時光》、《理想》、《三十歲的女人》、《家鄉》、《浮游》、《小屋》、《北京的冬天》

null

  【趙小雷】共收錄了12支單曲:

  《人家》、《未給姐姐遞出的信》、《畫》、《不開的脣》、《趙小雷》、《南方姑娘》、《Over》、《開往北京的火車》、《背影》、《媽媽》、《南方姑娘(彈唱版)》、《民謠》

null

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

  《19歲時候的歌》、《已是兩條路上的人》、《再也不會去麗江》、《讓我偷偷看你》、《咬春》、《難受》、《辭行》、《保存》、《雪人》、《青春無處安放》、《憑什麼說愛你》、《過年》、《何必》、《2012年之前》、《別》、《愛人你在哪裡》、《夏天》、《飛來飛去》、《逆流而上》、《花朵》、《罪》、《不能自主》

  共計53首單曲,1560行歌詞。

  1.原理回顧

  機器作詞是序列建模(以下簡稱seq2seq)的典型應用,其基本思想就是給定序列A,機器負責產生序列B,並且再將序列B作為輸入,機器負責生成序列C...如此循環下去即可生成無限長度的序列。seq2seq模型圖如下所示,左邊是編碼器,右邊是解碼器。

null

  假設問題是從序列A到序列B之間的映射,那麼seq2seq模型的工作流程如下:

  

  序列A中的每一個單詞通過word_embedding操作以後,作為input進入編碼器,編碼器可以是一個多層RNN結構,編碼器輸出一個向量;

  訓練的時候,解碼器的輸入跟編碼器的輸入是一樣的,然後解碼器的輸出與序列B之間的交叉熵作為模型的目標函數;

  生成的時候,首先給定一個種子序列作為編碼器的輸入,並且解碼器的上一時刻的輸出作為下一時刻的輸入,如此循環往復,直到生成給定數量的序列。

  

  本文創建的模型就是基於以上原理。

  2.模型代碼設計

  要完成機器生成歌詞的工作看上去是一個生成模型,而生成模型一般都是無監督問題,但是我們需要將它轉化成有監督問題,原因是使用有監督學習可以發現數據內在的關聯性,比如上下文的銜接,然後用預測學習來代替無監督學習。

  就有監督學習而言,通常我們需要準備好具有映射關係的數據集:X和Y。這裡我們事先只有周杰倫的歌詞文本,它是一個整體,如何確定X和Y?雖然它是一個整體,但是這個整體是序列組成的,序列與序列之間會有一定的時序關係。比如對於

  

  讓我掉下眼淚的 不止昨夜的酒

  

  我們是不是可以把“讓我掉下眼淚的 ”看作X,把“ 不止昨夜的酒”看作Y,如果我們將X輸入進網絡,而網絡輸出的是Y,那就說明我們構建的網絡已經具備寫歌詞的能力了。這就是我們劃分數據集為X和Y的原理。一般情況下,數據需要劃分為訓練集和測試集,由於時間的緣故,這裡沒有劃分測試集了。

  當我們把數據預處理做好了,接下來就是構建模型了,構建模型主要是圍繞seq2seq模型,而在編碼器和解碼器部分,我們可以自由構造,如可以選擇不同的rnn_cell,或者選擇不同的層數、神經元個數,具體情況因子據量大小而定。構建有監督學習模型的最重要部分就是目標函數,並且要確保目標函數對於所有要訓練的參數是可微的,這樣我們就可以構建端對端的基於後向誤差更新的深度學習系統。

  當有監督學習訓練的模型的誤差已經滿足我們的要求了,就可以把參數保存下來,以便利用這個模型去生成歌詞。生成模型的構建其實就是一個抽樣的過程,給定種子序列,選好特定的抽樣方法,即可生成無限多個漢字組成的序列。

  為了瞭解訓練過程中的誤差更新趨勢,我們還需要創建日誌記錄以及日誌可視化的部分,這樣以便於我們做後期的模型性能分析,本文中會粗略提及。

  本項目的文件結構如下圖所示:

null

  它們的功能分別如下:

  

  主目錄下面的utils.py是公共函數庫,preprocess.py是數據預處理代碼,seq2seq_rnn.py是模型代碼,sample.py是抽樣生成過程,train.py是訓練過程;

  log目錄中存儲的是訓練過程中的日誌文件;

  save目錄中存儲的是訓練過程中的模型存儲文件;

  data目錄中存放的是原始歌詞數據庫以及處理過的數據庫;

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

  analysis目錄中存放的是用於可視化的代碼文件。

  

  3.數據預處理

  原始歌詞文件是網絡中下載的,其中包括了一些不必要的文本,由此我們過濾了所有的非中文字符,並且使用空格分隔相鄰句子。例如下面一段代碼的作用就是剔除源歌詞文件中的多餘空格以及非中文字符:

  

  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_batches * self.batch_size * self.seq_length]
xdata = self.context
ydata = np.copy(self.context)
ydata[:-1] = xdata[1:]
ydata[-1] = xdata[0]
self.x_batches = np.split(xdata.reshape(self.batch_size, -1), self.num_batches, 1)
self.y_batches = np.split(ydata.reshape(self.batch_size, -1), self.num_batches, 1)
self.pointer = 0

  

  可以看到Y的最後一個數是設置為X的第一個數,因此我們在數據集的開頭插入了一個空格使得整體連貫。pointer是作為標記來用的,它的作用是標記當前訓練的是哪一個mini-batch,如果所有mini-batch都訓練過了,即完成了一個Epoch,那麼pointer將置零,如下面代碼所示:

  

  def next_batch(self):
''' pointer for outputing mini-batches when training

  '''
x, y = self.x_batches[self.pointer], self.y_batches[self.pointer]
self.pointer += 1
if self.pointer == self.num_batches:
self.pointer = 0
return x, y

  

  4.編寫基於LSTM的seq2seq模型

  數據預處理完成以後,接下來就是創建seq2seq模型了。創建模型主要分為三步:

  

  確定好編碼器和解碼器中cell的結構,即採用什麼循環單元,多少個神經元以及多少個循環層;

  將輸入數據轉化成tensorflow的seq2seq.rnn_decoder需要的格式,並得到最終的輸出以及最後一個隱含狀態;

  將輸出數據經過softmax層得到概率分佈,並且得到誤差函數,確定梯度下降優化器。

  

  由於tensorflow提供的rnncell共有三種,分別是RNN、GRU、LSTM,因此這裡我們也提供三種選擇,並且每一種都可以使用多層結構,即MultiRNNCell,如下代碼所示:

  

  if args.rnncell == 'rnn':
cell_fn = rnn_cell.BasicRNNCellelif args.rnncell == 'gru':
cell_fn = rnn_cell.GRUCellelif args.rnncell == 'lstm':
cell_fn = rnn_cell.BasicLSTMCellelse:
raise Exception("rnncell type error: {}".format(args.rnncell))

  cell = cell_fn(args.rnn_size)

  self.cell = rnn_cell.MultiRNNCell([cell] * args.num_layers)

  

  選擇好了cell的結構以後,接下來就是將輸入數據傳遞的seq2seq模型中了,tensorflow的seq2seq.py文件中提供了多個用於創建seq2seq的函數,這裡我選擇了兩個,分別是rnn_decoder以及attention_decoder,下面以rnn_decoder為例。從tensorflow源碼中可以看到,rnn_decoder函數主要有四個參數,它們的註釋如下:

  

  decoder_inputs: A list of 2D Tensors [batch_size x input_size].
initial_state: 2D Tensor with shape [batch_size x cell.state_size].
cell: rnn_cell.RNNCell defining the cell function and size.
loop_function: If not None, this function will be applied to the i-th output
in order to generate the i+1-st input, and decoder_inputs will be ignored,
except for the first element ("GO" symbol). This can be used for decoding,
but also for training to emulate [1506.03099] Scheduled Sampling for Sequence Prediction with Recurrent Neural Networks.
Signature -- loop_function(prev, i) = next
* prev is a 2D Tensor of shape [batch_size x output_size],
* i is an integer, the step number (when advanced control is needed),
* next is a 2D Tensor of shape [batch_size x input_size].

  

  可以看到,decoder_inputs其實就是輸入的數據,要求的格式為一個list,並且list中的tensor大小應該為[batch_size,input_size],換句話說這個list的長度就是seq_length;但我們原始的輸入數據的維度為[args.batch_size, args.seq_length],是不是感覺缺少了一個input_size維度,其實這個維度就是word_embedding的維度,或者說word2vec的大小,這裡需要我們手動進行word_embedding,並且這個embedding矩陣是一個可以學習的參數:

  

  self.input_data = tf.placeholder(tf.int32, [args.batch_size, args.seq_length])with tf.variable_scope('rnnlm'):

  softmax_w = build_weight([args.rnn_size, args.vocab_size],name='soft_w')

  softmax_b = build_weight([args.vocab_size],name='soft_b')

  word_embedding = build_weight([args.vocab_size, args.embedding_size],name='word_embedding')

  inputs_list = tf.split(1, args.seq_length, tf.nn.embedding_lookup(word_embedding, self.input_data))

  inputs_list = [tf.squeeze(input_, [1]) for input_ in inputs_list]

  

  initial_state是cell的初始狀態,其維度是[batch_size,cell.state_size],由於rnn_cell模塊提供了對狀態的初始化函數,因此我們可以直接調用:

  

  self.initial_state = self.cell.zero_state(args.batch_size, tf.float32)

  

  cell就是我們要構建的解碼器和編碼器的cell,上面已經提過了。最後一個參數是loop_function,其作用是在生成的時候,我們需要把解碼器上一時刻的輸出作為下一時刻的輸入,並且這個loop_function需要我們自己寫,如下所示:

  

  def loop(prev, _):
prev = tf.matmul(prev, softmax_w) + softmax_b
prev_symbol = tf.stop_gradient(tf.argmax(prev, 1))
return tf.nn.embedding_lookup(embedding, prev_symbol)

  

  最後,我們就可以構建好seq2seq的模型了,將上面參數傳入rnn_decoder函數即可:

  

  outputs, last_state = seq2seq.rnn_decoder(inputs_list, self.initial_state, self.cell, loop_function=loop if infer else None, scope='rnnlm')

  

  其中outputs是與decoder_inputs同樣維度的量,即每一時刻的輸出;last_state的維度是[batch_size,cell.state_size],即最後時刻的所有cell的狀態。接下來需要outputs來確定目標函數,而last-state的作用是作為抽樣生成函數下一時刻的狀態。

  tensorflow中提供了sequence_loss_by_example函數用於按照權重來計算整個序列中每個單詞的交叉熵,返回的是每個序列的log-perplexity。為了使用sequence_loss_by_example函數,我們首先需要將outputs通過一個前向層,同時我們需要得到一個softmax概率分佈,這個在生成中會用到:

  

  output = tf.reshape(tf.concat(1, outputs), [-1, args.rnn_size])

  self.logits = tf.matmul(output, softmax_w) + softmax_b

  self.probs = tf.nn.softmax(self.logits)

  loss = seq2seq.sequence_loss_by_example([self.logits],
[tf.reshape(self.targets, [-1])],
[tf.ones([args.batch_size * args.seq_length])],
args.vocab_size)

  # average loss for each word of each timestepself.cost = tf.reduce_sum(loss) / args.batch_size / args.seq_length

  

  最後就是創建一個op,以便訓練,例如var_op、var_trainable_op、train_op、initial_op、saver:

  

  self.lr = tf.Variable(0.0, trainable=False)

  self.var_trainable_op = tf.trainable_variables()

  grads, _ = tf.clip_by_global_norm(tf.gradients(self.cost, self.var_trainable_op),
args.grad_clip)optimizer = tf.train.AdamOptimizer(self.lr)

  self.train_op = optimizer.apply_gradients(zip(grads, self.var_trainable_op))

  self.initial_op = tf.initialize_all_variables()

  self.saver = tf.train.Saver(tf.all_variables(),max_to_keep=5,keep_checkpoint_every_n_hours=1)

  self.logfile = args.log_dir+str(datetime.datetime.strftime(datetime.datetime.now(),'%Y-%m-%d %H:%M:%S')+'.txt').replace(' ','').replace('/','')

  self.var_op = tf.all_variables()

  

  train_op即為訓練時需要運行的。

  5.編寫抽樣生成函數

  如上所述,在抽樣生成的時候,我們首先需要一個種子序列,同時在第一步的時候,我們需要向網絡傳入一個0的初始狀態,並通過種子序列的第一個字得到下一個隱含狀態,然後再結合種子的第二個字傳入下一個隱含狀態,直到種子序列傳入完畢:

  

  state = sess.run(self.cell.zero_state(1, tf.float32))

  for word in start:
x = np.zeros((1, 1))
x[0, 0] = words[word]
feed = {self.input_data: x, self.initial_state:state}
[probs, state] = sess.run([self.probs, self.final_state], feed)

  

  種子序列運行完畢以後,接下來就進入真正的抽樣過程了,即拿上一時刻的state以及上一時刻輸出probs中的最佳單詞作為下一時刻的輸入,那麼給定了一個所有單詞的概率分佈probs,該時刻的最佳單詞如何定義呢?這裡我列舉了三種情況:

  

  argmax型:即找出probs中最大值所對應的索引,然後去單詞表中找到該索引對應的單詞即為最佳單詞;

  weighted型:即隨機取樣,其工作流程如下:首先,計算此probs的累加總和S;其次,隨機生成一個0~1之間的隨機數,並將其與probs的總和相乘得到R;最後,將R依次減去probs中每個數,直到R變成負數的那個probs的索引,即為我們要挑選的最佳單詞;

  combined型: 這裡我把argmax和weighted結合起來了,即每次遇到一個空格(相當於一句歌詞的結尾),就使用weighted型,而其他時候都使用argmax型。

  

  這三種的實現方式如下所示:

  

  def random_pick(p,word,sampling_type):
def weighted_pick(weights):
t = np.cumsum(weights)
s = np.sum(weights)
return(int(np.searchsorted(t, np.random.rand(1)*s)))
if sampling_type == 'argmax':
sample = np.argmax(p)
elif sampling_type == 'weighted':
sample = weighted_pick(p)
elif sampling_type == 'combined':
if word == ' ':
sample = weighted_pick(p)
else:
sample = np.argmax(p)
return sample

  

  最後,抽樣生成過程的具體代碼如下所示,其中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的趙雷風格的歌詞如下(為了美觀,所有逗號改成了空格,句號改成了換行):

  

  平淡

  平淡

  花多好戲地說再見

  身回最這樣如何其實再邊竟會唔於動人

  世界最外表演天事

  借給你肩膀才能證明我會過怎知道

  我也有想你聽過這一切

  獻出自己事

  難受傷懂得體去寂寞

  別等別留給你奏禮

  待著過和人生或者都給你我的模糊

  有人有一個人的對子

  是場遊戲的歌

  而我這個世界我

  在誰要天際

  無法自己

  只有紅它卻如進了

  就走在笑著花兒

  星星灰銀色

  你說很少把春把月亮

  站在你去在走

  隨時流過無時笑過

  昨天的媽在這夜空沒

  
有你們的愛

  有你們的愛太簡單

  讓你擁有一絲負擔的溫柔

  我要回的遙見時差往北

  熟通的笑聲敲打

  我們能顧及身人

  過客已沒有理掉

  所有的責任關心

  來時又比有吸

  你也明明我自己讓餘歌

  更溫柔吻看不到的理思

  薰衣服裡請你在過

  火總有天總是謝你有過有了多少她有沒有個好或

  曾給我也可以難實瘋的多人

  起來短有我沉在多月亮

  來雨後雨照把煙

  一感一天面不樣

  熟悉的是聞 親愛的

  我愛的本錢 也不能

  像隨街換轉身邊少分

  會轉身都由面無再學

  
遊戲而已

  遊戲而已高極高逐

  無論世界大路口多

  人被我無怨還是錯

  種信候。願我問候

  夏日花色還有十號

  歌早不出笑謝你走過

  電柔菌開那是獎上一種淚過

  十個寂寞不要就這麼一個

  好地犧牲我已經鬧事唱淪手朋友

  信心有明知當日我

  只願意忘了有刻想給到你重頭

  便會忍心一生對不過

  仍可以去自己怎會好

  沒有你沒有什麼東西可叫開心

  在這裡開走一把手動聽你身家全世界

  那麼多

  仍然未能被擁抱高一塊較

  算你或者會坐

  定要指打我的身情

  終要重頭

  
我就是我

  我就是我的地教我有個夢

  是二麼老友她拿著和你說

  有你的很想都放棄

  就像一切心跳

  走過走一個去不分開始一樣

  這張開心第一生太多節奏

  為你而受傷只得好怎麼怎知一世

  不需多管我講過

  我期待我就求運識你在手電過以未夠一樣

  便存在誰在起來

  誰想給你別給我講多可假

  才明白我願意憑著世利寧願擋子彈

  請與其實未想過代未見

  錯愛情仍能要逃避

  我會在意當未來也要痛苦

  每次發洩也不要能得到用勁

  慣去苦也不再展開始沒出利保留到你

  
北方姑娘

  北方姑娘以後忘記

  獨自由心清醒大幾多

  熏衣草

  能各自吸引

  什麼都哭幹淚人又一樣的過

  別留下我的眼睛 已把我的溫柔

  我愛你 你會停留住離

  離陽的路 擦到的懷巴

  城市的幻想起浪漫陽光眼淚

  從來沒日落後 最大地問數

  讓我這個挽著沒有時刻它笑未必反明快樂清楚

  等待我對具遊戲

  心事全破忘擁有

  我們大丈夫

  只是有期待我一分

  模糊面不被疑流

  為跳最怕講你都說分

  願你的情緒比我內得

  累這世界

  熱管你說過去做

  眼淚你找到你

  
我的姑娘你在哪兒

  

  我的姑娘你在哪兒靜別人

  而我太多 給你離開的鮮空

  在我街著一件閃爍的人

  即使北京就破

  想到紅塵忌時請放開我覺得在乎

  高遺失望也許也完全無依依

  願我更難受

  面對不需要屬於我一樣的好

  我就開始不會把

  事業不開怎麼一分

  給我愛你那麼感受

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

  在這兩個夢時今晚

  什麼不多一個

  人生最一秒鐘我像一道未見過她

  親愛的 以為我自己送給你親愛的

  啊

  趙雲藍已經過收上

  愛的過火別口真情敵已經要無人

  最大種聲

  

  結果不是特別理想,主要原因是:趙雷的歌曲還是比較少,數量沒有達到很多,以至於訓練出來的比較奇怪,有些語句不通順。但是,我們儘自己最大努力去做這一件事啦。目的是表達我們對雷子的熱愛之情。

  希望趙雷在今後的音樂道路上越走越遠,創作出更多好的作品,這樣,我們的結果會更好!向趙雷致敬!

  科技牛逼!!!

  民謠牛逼!!!

  趙雷牛逼!!!

  雷鋒網按:雷鋒網獲得授權轉載,本文原載於微信公眾號量化投資與機器學習。雷鋒網(公眾號:雷鋒網)

  雷鋒網著作權文章,未經授權禁止轉載。詳情見轉載須知。

《更多精彩内容,按讚追蹤Gooread·精選!》
喜歡這篇文章嗎?立刻分享出去讓更多人知道~

    相關閱讀


您可能感興趣