RNN起名器(三)—— 程序實現

通過前面的兩篇博客,我們已經獲取了訓練數據和字向量,還了解了RNN單元的原理和代碼實現。
這篇博客繼續講解如何實現一個RNN起名器(使用LSTM)。

1. 網絡結構

先看下RNN網絡常用的基礎結構,圖片來自karpathy

解釋:

  • (1) 簡單的一對一。(嚴格的說不屬于RNN)
  • (2) 序列輸出 (例如輸入一張圖片,輸出一句句子)。
  • (3) 序列輸入 (例如輸入一句話,做情感分類)。
  • (4) 序列輸入,序列輸出 (典型例子:Machine Translation)。
  • (5) 同步的序列輸入和輸出 (例如視頻分類,給視頻的每一幀畫面打label)。

我們的起名器使用的是最后一種同步序列輸入和輸出。

2. lstm最終實現

上一篇介紹了lstm的基本實現。

接下來,我們看下我們的最終實現:

with self.graph.as_default():
    # Parameters:
    # Embedding layer
    with tf.name_scope("embedding"):
        self.Vector = tf.Variable(initial_value=self.W_value, name="Vector")
    # input to all gates
    U = tf.Variable(tf.truncated_normal([self.embedding_dim, self.hidden_dim * 4], -0.1, 0.1), name='x')
    # memory of all gates
    W = tf.Variable(tf.truncated_normal([self.hidden_dim, self.hidden_dim * 4], -0.1, 0.1), name='m')
    # biases all gates
    biases = tf.Variable(tf.zeros([1, self.hidden_dim * 4]))
    # Variables saving state across unrollings.
    saved_output = tf.Variable(tf.zeros([self.batch_size, self.hidden_dim]), trainable=False)
    saved_state = tf.Variable(tf.zeros([self.batch_size, self.hidden_dim]), trainable=False)
    # Classifier weights and biases.
    w = tf.Variable(tf.truncated_normal([self.hidden_dim, self.vocabulary_size], -0.1, 0.1))
    b = tf.Variable(tf.zeros([self.vocabulary_size]))
    self.keep_prob = tf.placeholder(tf.float32, name="kb")

    # Definition of the cell computation.
    def lstm_cell(i, o, state):
        i = tf.nn.dropout(x=i, keep_prob=self.keep_prob)
        mult = tf.matmul(i, U) + tf.matmul(o, W) + biases
        input_gate = tf.sigmoid(mult[:, :self.hidden_dim])
        forget_gate = tf.sigmoid(mult[:, self.hidden_dim:self.hidden_dim * 2])
        update = mult[:, self.hidden_dim * 3:self.hidden_dim * 4]
        state = forget_gate * state + input_gate * tf.tanh(update)
        output_gate = tf.sigmoid(mult[:, self.hidden_dim * 3:])
        output = tf.nn.dropout(output_gate * tf.tanh(state), self.keep_prob)
        return output, state

上面的代碼把iU,fU,cU,oU堆疊成U,把iW,fW,cW,oW堆疊成W。

這樣,矩陣乘法:

tf.matmul(i, iU) + tf.matmul(o, iW) + ib
tf.matmul(i, fU) + tf.matmul(o, fW) + fb
tf.matmul(i, cU) + tf.matmul(o, cW) + cb
tf.matmul(i, oU) + tf.matmul(o, oW) + ob

就可以合成下面的一步:

mult = tf.matmul(i, U) + tf.matmul(o, W) + biases

3. mini-batch

如果不使用mini-batch,一個一個樣本訓練,速度會很慢。

為了加快訓練速度,RNN通常也會采用mini-batch的方式訓練。

但是問題來了,不同的訓練語句長度不一樣怎么辦?一般采用固定batch長度,不夠的zero padding補上;多出的分割成多個。

下面的代碼生成batch數據:

class BatchGenerator(object):
    """Batch 生成器"""
    def __init__(self, X_value, Y_value, batch_size,
                 num_unrollings, vocabulary_size, char_to_index):
        self.X_value = X_value
        self.Y_value = Y_value
        self.data_len = len(X_value)
        self.batch_size = batch_size
        self.num_unrollings = num_unrollings
        self.vocabulary_size = vocabulary_size
        self.char_to_index = char_to_index
        self.start = 0
        self.end = batch_size - 1

        print "data length:", len(X_value)

    def next(self):
        X_all = self.X_value[[i % self.data_len for i in range(self.start, self.end + 1)]]
        Y_all = self.Y_value[[i % self.data_len for i in range(self.start, self.end + 1)]]
        X_all = [x + list(np.zeros(self.num_unrollings - len(x), dtype=int)) for x in X_all if len(x) != self.num_unrollings]
        Y_all = [y + list(np.zeros(self.num_unrollings - len(y), dtype=int)) for y in Y_all if len(y) != self.num_unrollings]
        X_batchs = list()
        Y_batchs = list()
        for step in range(self.num_unrollings):
            X_batch = list()
            Y_batch = np.zeros(shape=(self.batch_size, self.vocabulary_size), dtype=np.float)
            for b in range(self.batch_size):
                X_batch.append(X_all[b][step])
                Y_batch[b, Y_all[b][step]] = 1.0
            X_batchs.append(np.array(X_batch))
            Y_batchs.append(Y_batch)
        self.start = self.end + 1
        self.end += self.batch_size
        return X_batchs, Y_batchs

因為要使用字向量,所以X_batch數據是字的index(根據index查詢char embedding),而Y_batch數據是one hot向量。

所以X_batchs的尺寸是:(5, 50),即num_unrollings×batch_size;

Y_batchs的尺寸是:(5, 50, 5273),即num_unrollings×batch_size×num_chars。

4.損失函數和模型評估

損失函數:根據softmax的輸出和label計算交叉熵

logits = tf.nn.xw_plus_b(tf.concat(0, outputs), w, b)
self.loss = tf.reduce_mean(
    tf.nn.softmax_cross_entropy_with_logits(
        logits, tf.concat(0, self.train_labels)))

評估指標:perplexity

perplexity 衡量概率模型的采樣的有多好,數值越小,概率模型越好(語言模型常用)。

def logprob(predictions, labels):
    """
    計算perplexity時用到。
    Log-probability of the true labels in a predicted batch.
    """
    predictions[predictions < 1e-10] = 1e-10
    return np.sum(np.multiply(labels, -np.log(predictions))) / labels.shape[0]
print('Minibatch perplexity: %.2f' % float(
    np.exp(logprob(predictions, np.concatenate(Y_batchs)))))

5. 生成名字(sample)

def sample_distribution(distribution):
    """Sample one element from a distribution assumed to be an array of normalized
    probabilities.
    sample按照distribution的概率分布采樣下標,這里的采樣方式是針對離散的分布,相當于連續分布中求CDF。
    """
    r = random.uniform(0, 1)
    s = 0
    for i in range(len(distribution)):
        s += distribution[i]
        if s >= r:
            return i
    return len(distribution) - 1


def sample(prediction, vocabulary_size):
    """Turn a (column) prediction into 1-hot encoded samples.
    根據sample_distribution采樣得的下標值,轉換成1-hot的樣本
    """
    p = np.zeros(shape=[1, vocabulary_size], dtype=np.float)
    p[0, sample_distribution(prediction[0])] = 1.0
    return p
def sample_name(self, first_name, ckpt_file=MODEL_PRE):
    """根據現有模型,sample生成名字"""
    with tf.Session(graph=self.graph) as session:
        saver = tf.train.Saver()
        saver.restore(session, ckpt_file)
        for _ in range(NAME_NUM):
            name = first_name
            sample_input = self.char_to_index[first_name[-1]]
            self.reset_sample_state.run()
            for _ in range(NAME_LEN-1):
                prediction = self.sample_prediction.eval({self.sample_input: [sample_input], self.keep_prob: 1.0})
                one_hot = sample(prediction, self.vocabulary_size)
                sample_input = self.char_to_index[prob_to_char(one_hot, self.index_to_char)[0]]
                name += prob_to_char(one_hot, self.index_to_char)[0]
            print name

根據輸入的姓,和名字長度,獲取名字。

6. 結果展示

完整代碼

訓練模型在main函數中執行train_all()

生成名字在main函數中執行namer_lstm_c2v()

生成的陳姓男孩名字:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容