使用 LSTM 智慧作詩送新年祝福

Momodel發表於2019-04-30

LSTM 介紹

序列化資料即每個樣本和它之前的樣本存在關聯,前一資料和後一個資料有順序關係。深度學習中有一個重要的分支是專門用來處理這樣的資料的——迴圈神經網路。迴圈神經網路廣泛應用在自然語言處理領域(NLP),今天我們帶你從一個實際的例子出發,介紹迴圈神經網路一個重要的改進演算法模型-LSTM。本文章不對LSTM的原理進行深入,想詳細瞭解LSTM可以參考這篇 [譯] 理解 LSTM 網路。本文重點從古詩詞自動生成的例項出發,一步一步帶你從資料處理到模型搭建,再到訓練出古詩詞生成模型,最後實現從古詩詞自動生成新春祝福詩詞。

資料處理

我們使用76748首古詩詞作為資料集,資料集下載連結,原始的古詩詞的儲存形式如下:

image
我們可以看到原始的古詩詞是文字符號的形式,無法直接進行機器學習,所以我們第一步需要把文字資訊轉換為資料形式,這種轉換方式就叫詞嵌入(word embedding),我們採用一種常用的詞巢狀(word embedding)演算法-Word2vec對古詩詞進行編碼。關於Word2Vec這裡不詳細講解,感興趣可以參考 [NLP] 秒懂詞向量Word2vec的本質。在詞巢狀過程中,為了避免最終的分類數過於龐大,可以選擇去掉出現頻率較小的字,比如可以去掉只出現過一次的字。Word2vec演算法經過訓練後會產生一個模型檔案,我們就可以利用這個模型檔案對古詩詞文字進行詞巢狀編碼。

經過第一步的處理已經把古詩詞詞語轉換為可以機器學習建模的數字形式,因為我們採用LSTM演算法進行古詩詞生成,所以還需要構建輸入到輸出的對映處理。例如: “[長河落日圓]”作為train_data,而相應的train_label就是“長河落日圓]]”,也就是 “[”->“長”,“長”->“河”,“河”->“落”,“落”->“日”,“日”->“圓”,“圓”->“]”,“]”->“]”,這樣子先後順序一一對相。這也是迴圈神經網路的一個重要的特徵。 這裡的“[”和“]”是開始符和結束符,用於生成古詩的開始與結束標記。

總結一下資料處理的步驟:

  • 讀取原始的古詩詞文字,統計出所有不同的字,使用 Word2Vec 演算法進行對應編碼;
  • 對於每首詩,將每個字、標點都轉換為字典中對應的編號,構成神經網路的輸入資料 train_data;
  • 將輸入資料左移動構成輸出標籤 train_label;

經過資料處理後我們得到以下資料檔案:

  • poems_edge_split.txt:原始古詩詞檔案,按行排列,每行為一首詩詞;
  • vectors_poem.bin:利用 Word2Vec訓練好的詞向量模型,以開頭,按詞頻排列,去除低頻詞;
  • poem_ids.txt:按輸入輸出關係對映處理之後的語料庫檔案;
  • rhyme_words.txt: 押韻詞儲存,用於押韻詩的生成;

在提供的原始碼中已經提供了以上四個資料檔案放在data資料夾下,資料處理程式碼見 data_loader.py 檔案,原始碼連結

模型構建及訓練

這裡我們使用2層的LSTM框架,每層有128個隱藏層節點,我們使用tensorflow.nn模組庫來定義網路結構層,其中RNNcell是tensorflow中實現RNN的基本單元,是一個抽象類,在實際應用中多用RNNcell的實現子類BasicRNNCell或者BasicLSTMCell,BasicGRUCell;如果需要構建多層的RNN,在TensorFlow中,可以使用tf.nn.rnn_cell.MultiRNNCell函式對RNNCell進行堆疊。模型網路的第一層要對輸入資料進行 embedding,可以理解為資料的維度變換,經過兩層LSTM後,接著softMax得到一個在全字典上的輸出概率。 模型網路結構如下:

image

定義網路的類的程式程式碼如下:


class CharRNNLM(object):
    def __init__(self, is_training, batch_size, vocab_size, w2v_model,
                 hidden_size, max_grad_norm, embedding_size, num_layers,
                 learning_rate, cell_type, dropout=0.0, input_dropout=0.0, infer=False):
        self.batch_size = batch_size
        self.hidden_size = hidden_size
        self.vocab_size = vocab_size
        self.max_grad_norm = max_grad_norm
        self.num_layers = num_layers
        self.embedding_size = embedding_size
        self.cell_type = cell_type
        self.dropout = dropout
        self.input_dropout = input_dropout
        self.w2v_model = w2v_model

        if embedding_size <= 0:
            self.input_size = vocab_size
            self.input_dropout = 0.0
        else:
            self.input_size = embedding_size

        # 輸入和輸入定義
        self.input_data = tf.placeholder(tf.int64, [self.batch_size, self.num_unrollings], name='inputs')
        self.targets = tf.placeholder(tf.int64, [self.batch_size, self.num_unrollings], name='targets')

        # 根據定義選擇不同的迴圈神經網路核心單元
        if self.cell_type == 'rnn':
            cell_fn = tf.nn.rnn_cell.BasicRNNCell
        elif self.cell_type == 'lstm':
            cell_fn = tf.nn.rnn_cell.LSTMCell
        elif self.cell_type == 'gru':
            cell_fn = tf.nn.rnn_cell.GRUCell

        params = dict()
        if self.cell_type == 'lstm':
            params['forget_bias'] = 1.0
        cell = cell_fn(self.hidden_size, **params)

        cells = [cell]
        for i in range(self.num_layers-1):
            higher_layer_cell = cell_fn(self.hidden_size, **params)
            cells.append(higher_layer_cell)

        # 訓練時是否進行 Dropout
        if is_training and self.dropout > 0:
            cells = [tf.nn.rnn_cell.DropoutWrapper(cell, output_keep_prob=1.0-self.dropout) for cell in cells]

        # 對lstm層進行堆疊
        multi_cell = tf.nn.rnn_cell.MultiRNNCell(cells)

        # 定義網路模型初始狀態
        with tf.name_scope('initial_state'):
            self.zero_state = multi_cell.zero_state(self.batch_size, tf.float32)
            if self.cell_type == 'rnn' or self.cell_type == 'gru':
                self.initial_state = tuple(
                        [tf.placeholder(tf.float32,
                            [self.batch_size, multi_cell.state_size[idx]],
                            'initial_state_'+str(idx+1)) for idx in range(self.num_layers)])
            elif self.cell_type == 'lstm':
                self.initial_state = tuple(
                        [tf.nn.rnn_cell.LSTMStateTuple(
                            tf.placeholder(tf.float32, [self.batch_size, multi_cell.state_size[idx][0]],
                                          'initial_lstm_state_'+str(idx+1)),
                            tf.placeholder(tf.float32, [self.batch_size, multi_cell.state_size[idx][1]],
                                           'initial_lstm_state_'+str(idx+1)))
                            for idx in range(self.num_layers)])

        # 定義 embedding 層
        with tf.name_scope('embedding_layer'):
            if embedding_size > 0:
                # self.embedding = tf.get_variable('embedding', [self.vocab_size, self.embedding_size])
                self.embedding = tf.get_variable("word_embeddings",
                    initializer=self.w2v_model.vectors.astype(np.float32))
            else:
                self.embedding = tf.constant(np.eye(self.vocab_size), dtype=tf.float32)

            inputs = tf.nn.embedding_lookup(self.embedding, self.input_data)
            if is_training and self.input_dropout > 0:
                inputs = tf.nn.dropout(inputs, 1-self.input_dropout)

        # 建立每個切分通道網路層
        with tf.name_scope('slice_inputs'):
            sliced_inputs = [tf.squeeze(input_, [1]) for input_ in tf.split(
                axis = 1, num_or_size_splits = self.num_unrollings, value = inputs)]

        outputs, final_state = tf.nn.static_rnn(
                cell = multi_cell,
                inputs = sliced_inputs,
                initial_state=self.initial_state)
        self.final_state = final_state

        # 資料變換層,把經過迴圈神經網路的資料拉伸降維
        with tf.name_scope('flatten_outputs'):
            flat_outputs = tf.reshape(tf.concat(axis = 1, values = outputs), [-1, hidden_size])

        with tf.name_scope('flatten_targets'):
            flat_targets = tf.reshape(tf.concat(axis = 1, values = self.targets), [-1])

        # 定義 softmax 輸出層
        with tf.variable_scope('softmax') as sm_vs:
            softmax_w = tf.get_variable('softmax_w', [hidden_size, vocab_size])
            softmax_b = tf.get_variable('softmax_b', [vocab_size])
            self.logits = tf.matmul(flat_outputs, softmax_w) + softmax_b
            self.probs = tf.nn.softmax(self.logits)

        # 定義 loss 損失函式
        with tf.name_scope('loss'):
            loss = tf.nn.sparse_softmax_cross_entropy_with_logits(
                    logits = self.logits, labels = flat_targets)
            self.mean_loss = tf.reduce_mean(loss)

        # tensorBoard 損失函式視覺化
        with tf.name_scope('loss_montor'):
            count = tf.Variable(1.0, name='count')
            sum_mean_loss = tf.Variable(1.0, name='sum_mean_loss')

            self.reset_loss_monitor = tf.group(sum_mean_loss.assign(0.0),
                                               count.assign(0.0), name='reset_loss_monitor')
            self.update_loss_monitor = tf.group(sum_mean_loss.assign(sum_mean_loss+self.mean_loss),
                                                count.assign(count+1), name='update_loss_monitor')

            with tf.control_dependencies([self.update_loss_monitor]):
                self.average_loss = sum_mean_loss / count
                self.ppl = tf.exp(self.average_loss)

            average_loss_summary = tf.summary.scalar(
                    name = 'average loss', tensor = self.average_loss)
            ppl_summary = tf.summary.scalar(
                    name = 'perplexity', tensor = self.ppl)

        self.summaries = tf.summary.merge(
                inputs = [average_loss_summary, ppl_summary], name='loss_monitor')

        self.global_step = tf.get_variable('global_step', [], initializer=tf.constant_initializer(0.0))
        self.learning_rate = tf.placeholder(tf.float32, [], name='learning_rate')

        if is_training:
            tvars = tf.trainable_variables()
            grads, _ = tf.clip_by_global_norm(tf.gradients(self.mean_loss, tvars), self.max_grad_norm)
            optimizer = tf.train.AdamOptimizer(self.learning_rate)
            self.train_op = optimizer.apply_gradients(zip(grads, tvars), global_step=self.global_step)


複製程式碼

訓練時可以定義batch_size的值,是否進行dropout,為了結果的多樣性,訓練時在softmax輸出層每次可以選擇topK概率的字元作為輸出。訓練完成後可以使用tensorboard 對網路結構和訓練過程視覺化展示。這裡推薦大家一個線上人工智慧建模平臺momodel.cn,帶有完整的Python和機器學習框架執行環境,並且有免費的GPU可以使用,大家可以訓練的時候可以在這個平臺上試一下。訓練部分的程式碼和訓練好的模型見連結

詩詞生成

呼叫前面訓練好的模型我們就可以實現一個古詩詞的應用了,我這裡利用 Mo平臺 實現了藏頭詩和藏子詩自動生成的功能,執行的效果如下:

image
image

PC端檢視完整程式碼

參考文章: www.jianshu.com/p/9dc9f41f0… zhuanlan.zhihu.com/p/26306795 github.com/norybaby/po… ————————————————————————————————————Mo (網址:momodel.cn)是一個支援 Python 的人工智慧建模平臺,能幫助你快速開發訓練並部署 AI 應用。

相關文章