1. 程式人生 > >Tensorflor實現文本分類

Tensorflor實現文本分類

inpu zip rbo ros ftw global eight sdn 明顯

Tensorflor實現文本分類

下面我們使用CNN做文本分類

cnn實現文本分類的原理

下圖展示了如何使用cnn進行句子分類。輸入是一個句子,為了使其可以進行卷積,首先需要將其轉化為向量表示,通常使用word2vec實現。d=5表示每個詞轉化為5維的向量,矩陣的形狀是[sentence_length × 5],即[7 ×5]。6個filter(卷積核),與圖像中使用的卷積核不同的是,nlp使用的卷積核的寬與句子矩陣的寬相同,只是長度不同。這裏有(2,3,4)三種size,每種size有兩個filter,一共有6個filter。然後開始卷積,從圖中可以看出,stride是1,因為對於高是4的filter,最後生成4維的向量,(7-4)/1+1=4。對於高是3的filter,最後生成5維的向量,(7-3)/1+1=5。卷積之後,我們得到句子的特征,使用activation function和1-max-pooling得到最後的值,每個filter最後得到兩個特征。將所有特征合並後,使用softmax進行分類。圖中沒有用到chanel,下文的實驗將會使用兩個通道,static和non-static,有相關的具體解釋。

本文使用的模型


主要包括五層,第一層是embedding layer,第二層是convolutional layer,第三層是max-pooling layer,第四層是fully connected layer,最後一層是softmax layer.接下來依次介紹相關代碼實現。

Input placeholder

# Placeholders for input, output and dropout

self.input_x = tf.placeholder(tf.int32, [None, sequence_length], name="input_x")

self.input_y = tf.placeholder(tf.float32, [None, num_classes], name="input_y")

self.dropout_keep_prob = tf.placeholder(tf.float32, name="dropout_keep_prob")

tf.placeholder 創建一個占位符變量,在訓練或者測試的時候,需要將占位符輸入到網絡中進行計算,其中的第二個參數是輸入張量的形狀。None 意味著它可以是任何維度的長度,在我們的實驗中它代表批處理的大小,None使得網絡可以處理任意長度的batches。
失活率同樣也是輸入的一部分,在訓練的時候使用dropout ,測試的時候不使用dropout 。

EMBEDDING LAYER

這一層將單詞索引映射到低維的向量表示,它本質上是一個查找表,我們從數據中通過學習得到。

with tf.device(‘/cpu:0‘), tf.name_scope("embedding"):

W = tf.Variable(tf.random_uniform([vocab_size, embedding_size], -1.0, 1.0), name="W")

self.embedded_chars = tf.nn.embedding_lookup(W, self.input_x)

self.embedded_chars_expanded = tf.expand_dims(self.embedded_chars, -1)

其中,W 是 在訓練時得到的embedding matrix.,用隨機均勻分布進行初始化。tf.nn.embedding_lookup 實現embedding操作,得到 一個3-dimensional 的張量,形狀是 [None, sequence_length, embedding_size].sequence_length 是數據集中最長句子的長度,其他句子都通過添加“PAD”補充到這個長度。embedding_size是詞向量的大小。

TensorFlow的卷積函數-conv2d 需要四個參數, 分別是batch, width, height 以及channel。 embedding之後不包括 channel, 所以我們人為地添加上它,並設置為1。現在就是[None, sequence_length, embedding_size, 1]

CONVOLUTION AND MAX-POOLING LAYERS

由圖中可知, 我們有不同size的filters。因為每次卷積都會產生不同形狀的張量,所以我們要遍歷每個filter,然後將結果合並成一個大的特征向量。

pooled_outputs = []

for i, filter_size in enumerate(filter_sizes):

with tf.name_scope("conv-maxpool-%s" % filter_size):

# Convolution Layer

filter_shape = [filter_size, embedding_size, 1, num_filters]

W = tf.Variable(tf.truncated_normal(filter_shape, stddev=0.1), name="W")

b = tf.Variable(tf.constant(0.1, shape=[num_filters]), name="b")

conv = tf.nn.conv2d(

self.embedded_chars_expanded,

W,

strides=[1, 1, 1, 1],

padding="VALID",

name="conv")

# Apply nonlinearity

h = tf.nn.relu(tf.nn.bias_add(conv, b), name="relu")

# Max-pooling over the outputs

pooled = tf.nn.max_pool(

h,

ksize=[1, sequence_length - filter_size + 1, 1, 1],

strides=[1, 1, 1, 1],

padding=‘VALID‘,

name="pool")

pooled_outputs.append(pooled)

# Combine all the pooled features

num_filters_total = num_filters * len(filter_sizes)

self.h_pool = tf.concat(3, pooled_outputs)

self.h_pool_flat = tf.reshape(self.h_pool, [-1, num_filters_total])

這裏W 是filter 矩陣,h 是對卷積結果進行非線性轉換之後的結果。每個 filter都從整個embedding劃過,不同之處在於覆蓋多少單詞。 “VALID” padding意味著沒有對句子的邊緣進行padding,也就是用了narrow convolution,輸出的形狀是 [1, sequence_length - filter_size + 1, 1, 1]。narrow convolution與 wide convolution的區別是是否對邊緣進行填充。例如,對一個有7個詞的句子來說, filter size是5,使用narrow convolution,輸出的大小是(7-5)+1=3;使用wide convolution,輸出的大小是(7+2*4-5)+1=11.

對輸出進行max-pooling後得到形狀是 [batch_size, 1, 1, num_filters] 的張量,本質上是一個特征向量,最後一個維度是特征代表數量。把每一個max-pooling之後的張量合並起來之後得到一個長向量[batch_size, num_filters_total]. in tf.reshape 中的 -1表示T將向量展平。

DROPOUT LAYER

Dropout也許是cnn中最流行的正則化方法。dropout的想法很簡單,dropout layer隨機地選擇一些神經元,使其失活。這樣可以阻止co-adapting,迫使它們每一個都學習到有用的特征。失活的神經單元個數由dropout_keep_prob 決定。在訓練的時候設為 0.5 ,測試的時候設為 1 (disable dropout) .

# Add dropout

with tf.name_scope("dropout"):

self.h_drop = tf.nn.dropout(self.h_pool_flat, self.dropout_keep_prob)

SCORES AND PREDICTIONS

利用特征向量,我們可以用矩陣相乘計算兩類的得分,也可以用 softmax函數計算兩類的概率值。

with tf.name_scope("output"):

W = tf.Variable(tf.truncated_normal([num_filters_total, num_classes], stddev=0.1), name="W")

b = tf.Variable(tf.constant(0.1, shape=[num_classes]), name="b")

self.scores = tf.nn.xw_plus_b(self.h_drop, W, b, name="scores")

self.predictions = tf.argmax(self.scores, 1, name="predictions")

LOSS AND ACCURACY

可以用得分定義損失值。損失計算的是網絡的誤差,我們的目標是將其最小化,分類問題標準的損失函數是交叉熵損失。

# Calculate mean cross-entropy loss

with tf.name_scope("loss"):

losses = tf.nn.softmax_cross_entropy_with_logits(self.scores, self.input_y)

self.loss = tf.reduce_mean(losses)

計算正確率

# Calculate Accuracy

with tf.name_scope("accuracy"):

correct_predictions = tf.equal(self.predictions, tf.argmax(self.input_y, 1))

self.accuracy = tf.reduce_mean(tf.cast(correct_predictions, "float"), name="accuracy")

MINIMIZING THE LOSS

利用TensorFlow 內置的optimizers,例如 Adam optimizer,優化網絡損失。

global_step = tf.Variable(0, name="global_step", trainable=False)

optimizer = tf.train.AdamOptimizer(1e-4)

grads_and_vars = optimizer.compute_gradients(cnn.loss)

train_op = optimizer.apply_gradients(grads_and_vars, global_step=global_step)

train_op 是一個新建的操作,我們可以在參數上進行梯度更新。每執行一次 train_op 就是一次訓練步驟。 TensorFlow 可以自動地計算才那些變量是“可訓練的”然後計算他們的梯度。通過global_step 這個變量可以計算訓練的步數,每訓練一次自動加一。

CHECKPOINTING

TensorFlow 中可以用checkpointing 保存模型的參數。checkpointing中的參數也可以用來繼續訓練。

# Checkpointing

checkpoint_dir = os.path.abspath(os.path.join(out_dir, "checkpoints"))

checkpoint_prefix = os.path.join(checkpoint_dir, "model")

# Tensorflow assumes this directory already exists so we need to create it

if not os.path.exists(checkpoint_dir):

os.makedirs(checkpoint_dir)

saver = tf.train.Saver(tf.all_variables())

DEFINING A SINGLE TRAINING STEP

用一個batch的數據進行一次訓練。

def train_step(x_batch, y_batch):

"""

A single training step

"""

feed_dict = {

cnn.input_x: x_batch,

cnn.input_y: y_batch,

cnn.dropout_keep_prob: FLAGS.dropout_keep_prob

}

_, step, summaries, loss, accuracy = sess.run(

[train_op, global_step, train_summary_op, cnn.loss, cnn.accuracy],

feed_dict)

time_str = datetime.datetime.now().isoformat()

print("{}: step {}, loss {:g}, acc {:g}".format(time_str, step, loss, accuracy))

train_summary_writer.add_summary(summaries, step)

train_op 什麽也不返回,只是更新網絡中的參數。最終,打印出當前訓練的損失值與正確率。如果batch的size很小的話,這兩者在不同的batch中差別很大。因為使用了dropout,訓練的metrics可能要比測試的metrics糟糕。

同樣的函數也可以用在測試時。

def dev_step(x_batch, y_batch, writer=None):

"""

Evaluates model on a dev set

"""

feed_dict = {

cnn.input_x: x_batch,

cnn.input_y: y_batch,

cnn.dropout_keep_prob: 1.0

}

step, summaries, loss, accuracy = sess.run(

[global_step, dev_summary_op, cnn.loss, cnn.accuracy],

feed_dict)

time_str = datetime.datetime.now().isoformat()

print("{}: step {}, loss {:g}, acc {:g}".format(time_str, step, loss, accuracy))

if writer:

writer.add_summary(summaries, step)

TRAINING LOOP

通過叠代數據進行訓練。

# Generate batches

batches = data_helpers.batch_iter(

zip(x_train, y_train), FLAGS.batch_size, FLAGS.num_epochs)

# Training loop. For each batch...

for batch in batches:

x_batch, y_batch = zip(*batch)

train_step(x_batch, y_batch)

current_step = tf.train.global_step(sess, global_step)

if current_step % FLAGS.evaluate_every == 0:

print("\nEvaluation:")

dev_step(x_dev, y_dev, writer=dev_summary_writer)

print("")

if current_step % FLAGS.checkpoint_every == 0:

path = saver.save(sess, checkpoint_prefix, global_step=current_step)

print("Saved model checkpoint to {}\n".format(path))

VISUALIZING RESULTS IN TENSORBOARD

技術分享

技術分享

從上圖中我們可以觀察到:

  • 我們的訓練 metrics不平滑,因為用的batch sizes很小。如果用大的batches (或者在整個測試集上進行評估),會得到平滑的線。
  • 測試集的 accuracy明顯比訓練集的低,說明網絡過擬合了,我們應該用更大的數據集,更強的正則化,更少的模型參數。
  • 訓練集上的 loss 和 accuracy比測試集低的原因是用了dropout.

Tensorflor實現文本分類