關於Convlutional Neural Networks(CNNs)在天然語言處理領域的應用。python
這一次的研究主要是針對文本分類這一個問題所展開。網絡
類比信號與系統,理解卷積的真正意義,體會信號系統與圖像處理中卷積模型所起的不一樣做用。app
卷積神經網絡本質上是由多個卷積層,通過激活函數後產生輸出的網絡。能夠將其大體劃分爲輸入層,卷積層,池化層,全鏈接層和輸出層。dom
其中卷積層的工做,是經過定義一系列卷積核,依次掃過圖像中的每一個區域,產生一個新的特徵矩陣的過程。所以,卷積核在訓練完成後,更像是一個特徵過濾器,在掃過圖像的過程當中,忽略掉與特徵不相符的部分,而將符合這種特徵的部分放大化(一般是分配一個較大的權重)。ide
池化層一般會緊跟在卷積層以後,對卷積層所產生的特徵矩陣進行子採樣(多是去平均值,或者取最大值),最終獲得一個固定大小的矩陣。函數
池化層主要的做用有兩方面。google
第一,固定大小。經由卷積層產生的特徵矩陣一般是大小不定的,因此咱們沒法將它直接投入到全鏈接層進行後續工做,而池化層在這裏起到了「鏈接器」的做用,它將一個不定大小的矩陣轉化成了固定大小的矩陣,隨時能夠餵食到後續神經網絡。spa
第二,信息提取。池化層在下降輸出維度的同時,保留了原始特徵矩陣總最顯著的信息,並不會由於維度的下降而致使信息的丟失。code
所謂全鏈接是指,本層全部的神經元都與下一層的神經元相連。由於相對簡單,這裏也再也不贅述。orm
經過觀察下圖咱們能夠發現,一個CNNs會由多個卷積層,多個池化層,經由全鏈接層產生輸出。
全鏈接層起到的做用很容易理解,一般也只是分類,迴歸等任務。那麼介於原始圖像,和分類網絡之間的卷積層和池化層究竟起了什麼做用?
我認爲,它們兩層合起來能夠用「特徵提取層」來歸納,也就是說,卷積層和池化層實際上是一個特徵抽取,數據加工的過程,將原始的圖像通過一系列的處理,加工,才能產生易於分類的數據類型,而後進行分類工做。
類比CNNs在圖像處理中的應用,NLP中的模型其實是將一個句子以圖的概念理解。
在初始化特徵矩陣時,句子中的每個單詞的word embeddings做爲矩陣的一行,獲得一個n*k的矩陣,其中n是句子中的單詞數,k是詞嵌入模型的維度,也能夠是one-hot模型,可是考慮到樣本的稀疏度,通常會採用低維的詞嵌入模型。
在CV中,通常咱們的卷積核filters會掃過圖像的局部像素,但在NLP中,一般會只改變filters的長度,而寬度始終等於詞嵌入維度k,也就是說,filter的最小單位一整行,一個完整的word。而一般狀況下,會使用一個長度爲2~5的filter,這是由於實際狀況中,不多會出現5個詞以上的長組合短語。
CV中的池化操做一般會產生一個n * m的特徵矩陣,但在NLP中,產生的特徵矩陣會是一個n * 1的矩陣。
產生這種不一樣的緣由是由於,在圖像中,傳遞信息的是一個像素塊,而在天然語言處理中,傳遞信息的是一個word,圖像自己就是二維的,因此須要用二維去傳遞信息,可是對於詞而言,第二個維度k其實是詞嵌入的維度,並不會傳遞有關於這個句子的信息。
CV處理中,咱們會遇到RGB圖分爲R,G,B三個channel分析的狀況。在NLP中,彷佛看上去一個channel就足夠來作分析。
可是在原始paper中提到,NLP中的CNNs模型也能夠有多個通道,通常是static
和fine-tuned
。
static
通道在backpropagation的過程當中是靜態的,保持不變的,可是fine-tuned
通道會隨着訓練發生變化。
除了這兩個通道以外,咱們也能夠增長另外的通道,好比改變句子的語言,或者將句子替換成其餘同義句等等。
實驗部分用tensorflow實現了CNN-rand模型,數據集採用原paper中的MR數據集,這是一個關於電影評價的數據集,數據集將評價分爲了positive和negetive兩個部分。
因爲這個數據集自己較小,並且沒有dev,所以抽取了其中的10%做爲dev。
第一部分是embedding layer,將一個詞映射成低維word embedding。這裏沒有采用google的word2vec
是由於這個模型自己較爲簡單,並且筆記本算力有限,因此本身構建了一個簡單的embedding layer。
with tf.device('/cpu:0'), tf.name_scope("embedding"):
self.W = tf.Variable(
tf.random_uniform([vocab_size, embedding_size], -1.0, 1.0),
name="W")
self.embedded_chars = tf.nn.embedding_lookup(self.W, self.input_x)
self.embedded_chars_expanded = tf.expand_dims(self.embedded_chars, -1)
複製代碼
W是一個隨機均勻分佈,維度爲vocab_size * embedding_size
的初始化矩陣。
tf.nn.embedding_lookup
方法建立了一個詞嵌入矩陣,它的維度和W一致,返回的類型爲[None, sequence_length, embedding_size]
。
tf.expand_dims(self.embedded_chars, -1)
在返回值的最後一個維度插入1,變成[None, sequence_length, embedding_size, 1]
,這裏的1表明的通道數,在後續con2d卷積方法中會使用。
卷積和池化模型的創建。
由於是CNN-rand模型,因此通道數爲1,且採用窄卷積的方式進行卷積操做。
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
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(pooled_outputs, 3)
self.h_pool_flat = tf.reshape(self.h_pool, [-1, num_filters_total])
複製代碼
這裏的filter_size = "3,4,5"
,是採用長度分別爲3,4,5的三種filter對詞嵌入矩陣進行卷積。
W是卷積核,用截斷正態分佈進行初始化。b是偏置數,h爲卷積輸出通過激活函數之後的結果。
tf.nn.conv2d
中的strides
和padding
參數,分別表示步長和卷積方式。'VALID'是窄卷積,產生的輸出類型爲[1, sequence_length - filter_size +1, 1, 1]
。
卷積結果通過一個ReLU激活後,放入池化層。
池化層是一個Max-pooling,會將卷積輸出的n * 1的矩陣中取出一個最大值。而後將同一個filter的Max-pooling結果鏈接起來,最終獲得的類型爲[filter_num, 1]
。
最後將全部filter的結果相連,獲得最終通過Convolution和Pooling的矩陣。
和原paper中給的76%比較接近,偏差產生的緣由可能在於我沒有采用交叉驗證,數據集較小的狀況下可能發生過擬合的狀況。
經過這一次課題研究,我試圖去尋找CV和NLP在使用CNNs時的共性,我我的認爲,他們都是經過特徵的部分提取去創造特徵來進行最終的分類訓練。
可是在CV中,卷積核最終的訓練形態比較容易理解,應該是一個圖像的局部特徵,好比一個圓,一個三角形,或是一條線等等。但在NLP中,卻很難去定義卷積核最終到底產生了什麼特性,它提取出來的特徵究竟是什麼,這一點是我作完這一次研究後所困擾的。
整體而言,CNNs在NLP中的應用是將文本以圖像的表示形式進行處理,雖然可能在一些方面的概念比較模糊,但卷積操做的速度,也使得CNNs模型在文本分類這一方向取得了不小的進步。