用CNN做句子分類:CNN Sentence Classification (with Theano code)
01 Intro
本篇文章來細說CNN在NLP中的一大應用————句子分類。通過Yoon Kim的論文介紹一個應用,分析程式碼,並重構程式碼。
重構後的程式碼放在github(https://github.com/applenob/CNN_sentence),另附io博文地址(https://applenob.github.io/cnn_sc.html)
傳統的句子分類器一般使用SVM和Naive Bayes。傳統方法使用的文字表示方法大多是“詞袋模型”。即只考慮文字中詞的出現的頻率,不考慮詞的序列資訊。傳統方法也可以強行使用N-gram的方法,但是這樣會帶來稀疏問題,意義不大。
CNN(卷積神經網路),雖然出身於影象處理,但是它的思路,給我們提供了在NLP應用上的參考。“卷積”這個術語本身來自於訊號處理,它的物理意義可以參考知乎上關於“複利”的回答(https://www.zhihu.com/question/22298352?rf=21686447),或者參考colah大神的部落格(http://colah.github.io/posts/2014-07-Understanding-Convolutions/)。
簡單地說就是一系列的輸入訊號進來之後,系統也會有一系列的輸出。但是並不是某一時刻的輸出只對應該時刻的輸入,而是根據系統自身的特徵,每一個時刻的輸出,都和之前的輸入相關。那麼如果文字是一些列輸入,我們當然希望考慮詞和詞的序列特徵,比如“Tom 的 手機 ”,使用卷積,系統就會知道“手機是tom”的,而不是僅僅是一個“手機”。
或者更直觀地理解,在CNN模型中,卷積就是拿kernel在影象上到處移動,每移動一次提取一次特徵,組成feature map, 這個提取特徵的過程,就是卷積。
接下來,我們看看Yoon Kim的paper:Convolutional Neural Networks for Sentence Classification (EMNLP 2014)
02
論文框架介紹
Yoon Kim 自己畫的結構圖:
模型結構.png
具體結構介紹:
1、輸入層
可以把輸入層理解成把一句話轉化成了一個二維的影象:每一排是一個詞的word2vec向量,縱向是這句話的每個詞按序排列。輸入資料的size,也就是影象的size,n×k,n代表訓練資料中最長的句子的詞個數,這裡是64(不夠64個詞的句子採用zero padding),k是embbeding的維度,這裡是300。所謂的static和non-static的chanel解釋如下:
CNN-rand: 所有的word vector都是隨機初始化的,同時當做訓練過程中優化的引數;
CNN-static: 所有的word vector直接使用無監督學習即Google的Word2Vector工具(COW模型)得到的結果,並且是固定不變的;
CNN-non-static: 所有的word vector直接使用無監督學習即Google的Word2Vector工具(COW模型)得到的結果,但是會在訓練過程中被Fine tuned;
CNN-multichannel: CNN-static和CNN-non-static的混合版本,即兩種型別的輸入;
從輸入層還可以看出kernel的size。很明顯kernel的高(h)會有不同的值,圖上有的是2,有的是3。這很容易理解,不同的kernel想獲取不同範圍內詞的關係;和影象不同的是,nlp中的cnn的kernel的寬(w)一般都是影象的寬,也就是word2vec的維度,這也可以理解,因為我們需要獲得的是縱向的差異資訊,也就是不同範圍的詞出現會帶來什麼資訊。
2、卷積層
由於kernel的特殊形狀,因此卷積後的feature map是一個寬度是1的長條。
3、池化層
這裡使用是MaxPooling,並且一個feature map只選一個最大值留下。這被認為是按照這個kernel卷積後的最重要的特徵。
4、全連線層
這裡的全連線層是帶dropout的全連線層和softmax。
03
論文實驗介紹
資料
1.word2vec使用谷歌預訓練的GoogleNews-vectors-negative300.bin
2.資料集
資料集.png
訓練和調參
- filter window(kernel)的高度(h):3,4,5;每個高度的Feature Map的數量為100,一共300個Feature Map;
- Dropout rate 0.5;
- L2 constraint (正則化限制權值大小)不超過3;
- mini-batch size 50;
- 通過網格搜尋方法(Grid Search)得到的最優引數;
- 優化器使用Adadelta。
結果
結果.png
04
試著跑跑
Yoon Kim在GitHub上分享了自己的程式碼和資料集MR(Movie Review, 只有兩個類,neg和pos)。
讓我們動手跑跑這個程式!
1、載入資料集
python process_data.py /home/cer/Data/GoogleNews-vectors-negative300.bin
output:
loading data... data loaded!
number of sentences: 10662
vocab size: 18765
max sentence length: 56
loading word2vec vectors... word2vec loaded!
num words already in word2vec: 16448
dataset created!
2、跑模型(使用預先載入的word2vec,並且不改變)注:為了便於顯示cv個數從10減到2
THEANO_FLAGS=mode=FAST_RUN,device=gpu,floatX=float32 python conv_net_sentence.py -nonstatic -word2vec
output:
Using gpu device 0: GeForce GTX 960M (CNMeM is disabled, cuDNN not available)
loading data... data loaded!
model architecture: CNN-non-static
using: word2vec vectors
[('image shape', 64, 300), ('filter shape', [(100, 1, 3, 300), (100, 1, 4, 300), (100, 1, 5, 300)]), ('hidden_units', [100, 2]), ('dropout', [0.5]), ('batch_size', 50), ('non_static', True), ('learn_decay', 0.95), ('conv_non_linear', 'relu'), ('non_static', True), ('sqr_norm_lim', 9), ('shuffle_batch', True)]
... training
epoch: 1, training time: 10.58 secs, train perf: 79.86 %, val perf: 75.16 %
epoch: 2, training time: 10.48 secs, train perf: 86.93 %, val perf: 77.89 %
epoch: 3, training time: 11.05 secs, train perf: 88.25 %, val perf: 77.68 %
epoch: 4, training time: 10.73 secs, train perf: 95.44 %, val perf: 79.89 %
epoch: 5, training time: 10.69 secs, train perf: 97.91 %, val perf: 79.58 %
epoch: 6, training time: 11.38 secs, train perf: 99.11 %, val perf: 80.74 %
epoch: 7, training time: 10.80 secs, train perf: 99.13 %, val perf: 79.16 %
epoch: 8, training time: 11.11 secs, train perf: 99.84 %, val perf: 80.53 %
epoch: 9, training time: 11.05 secs, train perf: 99.94 %, val perf: 80.95 %
epoch: 10, training time: 11.03 secs, train perf: 99.91 %, val perf: 79.68 %
epoch: 11, training time: 10.85 secs, train perf: 99.97 %, val perf: 80.74 %
epoch: 12, training time: 11.01 secs, train perf: 99.98 %, val perf: 80.42 %
epoch: 13, training time: 10.64 secs, train perf: 99.98 %, val perf: 80.53 %
epoch: 14, training time: 11.32 secs, train perf: 99.99 %, val perf: 80.32 %
epoch: 15, training time: 11.04 secs, train perf: 99.99 %, val perf: 79.68 %
epoch: 16, training time: 10.98 secs, train perf: 99.99 %, val perf: 80.21 %
epoch: 17, training time: 11.14 secs, train perf: 99.99 %, val perf: 80.53 %
epoch: 18, training time: 11.06 secs, train perf: 99.99 %, val perf: 80.53 %
epoch: 19, training time: 12.21 secs, train perf: 99.99 %, val perf: 80.63 %
epoch: 20, training time: 10.68 secs, train perf: 100.00 %, val perf: 80.95 %
epoch: 21, training time: 10.64 secs, train perf: 100.00 %, val perf: 80.42 %
epoch: 22, training time: 11.16 secs, train perf: 100.00 %, val perf: 80.32 %
epoch: 23, training time: 10.88 secs, train perf: 100.00 %, val perf: 80.53 %
epoch: 24, training time: 10.65 secs, train perf: 100.00 %, val perf: 80.32 %
epoch: 25, training time: 10.84 secs, train perf: 100.00 %, val perf: 80.32 %
cv: 0, perf: 0.793002915452
[('image shape', 64, 300), ('filter shape', [(100, 1, 3, 300), (100, 1, 4, 300), (100, 1, 5, 300)]), ('hidden_units', [100, 2]), ('dropout', [0.5]), ('batch_size', 50), ('non_static', True), ('learn_decay', 0.95), ('conv_non_linear', 'relu'), ('non_static', True), ('sqr_norm_lim', 9), ('shuffle_batch', True)]
... training
epoch: 1, training time: 10.92 secs, train perf: 80.01 %, val perf: 77.16 %
epoch: 2, training time: 10.68 secs, train perf: 87.68 %, val perf: 79.89 %
epoch: 3, training time: 10.78 secs, train perf: 91.45 %, val perf: 80.53 %
epoch: 4, training time: 10.76 secs, train perf: 95.78 %, val perf: 80.63 %
epoch: 5, training time: 10.62 secs, train perf: 97.99 %, val perf: 80.42 %
epoch: 6, training time: 10.69 secs, train perf: 99.10 %, val perf: 79.89 %
epoch: 7, training time: 10.95 secs, train perf: 99.31 %, val perf: 79.68 %
epoch: 8, training time: 10.86 secs, train perf: 99.68 %, val perf: 79.68 %
epoch: 9, training time: 10.64 secs, train perf: 99.82 %, val perf: 79.89 %
epoch: 10, training time: 10.75 secs, train perf: 99.93 %, val perf: 80.32 %
epoch: 11, training time: 10.94 secs, train perf: 99.97 %, val perf: 80.21 %
epoch: 12, training time: 10.71 secs, train perf: 99.99 %, val perf: 80.53 %
epoch: 13, training time: 10.74 secs, train perf: 99.97 %, val perf: 80.00 %
epoch: 14, training time: 10.86 secs, train perf: 99.99 %, val perf: 80.00 %
epoch: 15, training time: 11.00 secs, train perf: 99.99 %, val perf: 79.37 %
epoch: 16, training time: 10.87 secs, train perf: 99.99 %, val perf: 80.11 %
epoch: 17, training time: 10.94 secs, train perf: 99.99 %, val perf: 79.79 %
epoch: 18, training time: 10.73 secs, train perf: 99.99 %, val perf: 79.79 %
epoch: 19, training time: 11.05 secs, train perf: 100.00 %, val perf: 79.89 %
epoch: 20, training time: 11.83 secs, train perf: 100.00 %, val perf: 79.79 %
epoch: 21, training time: 10.85 secs, train perf: 100.00 %, val perf: 80.42 %
epoch: 22, training time: 10.70 secs, train perf: 100.00 %, val perf: 79.79 %
epoch: 23, training time: 10.89 secs, train perf: 100.00 %, val perf: 80.32 %
epoch: 24, training time: 10.78 secs, train perf: 100.00 %, val perf: 80.00 %
epoch: 25, training time: 11.19 secs, train perf: 100.00 %, val perf: 80.32 %
cv: 1, perf: 0.814338235294
0.803670575373
05
程式碼梳理
接下來研究研究Yoon Kim的程式碼,看看像這樣的一個Deep NLP的應用,是怎麼實現的。
5.1
大體結構
process_data.py:
資料預處理,資料以[revs, W, W2, word_idx_map, vocab]儲存在pkl檔案“mr.p”中。
revs的單條資料格式如下:
datum = {"y": 1, "text": orig_rev, "num_words": len(orig_rev.split()), "split": np.random.randint(0, cv)}
其中y是類標;text是句子原文(經過清洗);num_words是句子長度(詞數);split是分配的cv索引。
W即word matrix,W[i]是索引為i的詞對應的詞向量。
W2類似於W,但是是隨機初始化的。
word_idx_map是一個dict,key是資料集中出現的word,value是該word的索引。
vocab是一個dict,key是資料集中出現的word,value是該word出現的次數。
conv_net_classes.py:
定義具體的模型結構,不同的結構的層用不同的類定義。
如:
class HiddenLayer(object)class MLPDropout(object)class LogisticRegression(object)
conv_net_sentences.py:
完成資料的載入,模型的構建和連線,再訓練模型。
5.2
資料流
輸入的資料來自rt-polarity.neg和rt-polarity.pos,原始資料是很多英文句子,類標從檔名獲取。以及google的word2vec。
在process_data.py中:
1、build_data_cv():接收資料集檔案,讀取兩個檔案,生成基本資料revs(rev的內容上面已經分析)。
2、load_bin_vec():從GoogleNews-vectors-negative300.bin中載入w2v矩陣。生成w2v。w2v是一個dict,key是word,value是vector。
3、get_W():接收w2v,相當於把w2v從字典轉換成矩陣W,並且生成word_idx_map。相當於原來從word到vector只用查閱w2v字典;現在需要先從word_idx_map查閱word的索引,再2用word的索引到W矩陣獲取vector。
在conv_net_sentences.py中:
4、make_idx_data_cv():讀取rev中的text欄位,傳入get_idx_from_sent()方法,將句子轉換成一個list,list裡面的元素是這句話每個詞的索引。這個list形如(filter padding) - (word indices) - (Max padding) - (filter padding),長度為max_l+2×(filter_h-1),每句句子雖然本身長度不同,經過這步都轉換成相同長度的list。然後,按照cv索引,分割訓練集和測試集。
5.3
模型架構
在conv_net_classes.py中:
定義了所有網路層次和具體實現:
- HiddenLayer
- DropoutHiddenLayer
- MLPDropout
- MLP
- LogisticRegression
- LeNetConvPoolLayer
這些類大多數的實現都在init方法中:
1、首先接收這一層的輸入輸出的尺寸和這一層的輸入資料。
2、然後初始化這層的引數,引數都是theano.shared。
3、對於給定的輸入和引數,構建這層的輸出。
在conv_net_sentences.py中
獲取訓練資料和測試資料以後,絕大部分的工作由train_conv_net()完成:
1、傳入引數分為兩部分:(1)訓練資料+W矩陣(2)模型結構引數
2、組建模型網路:每層的定義都在conv_net_classes.py中實現了,因此這裡組建網路首先要初始化一個引數list:parameters,將每層的引數加入這個list統一管理;然後對於每一層,初始化該層的類,給該層喂入資料,獲取輸出;再將輸出餵給下一層,依照輸入輸出將每一層連線起來。
3、將訓練資料抽取0.1作為val資料。
4、構建function(theano.function):(1)根據cost function構建train_model;(2)構建val集的測試函式:val_model(3)構建測試集的測試函式:test_model。
5、開始訓練。
06
程式碼重構
6.1
為什麼重構
首先要明確重構程式碼的目的:我不是真的認為Yoon Kim的程式碼寫的不好,我也不認為我重構完以後架構有多好;我的目的是learn by doing,通過重構程式碼加深對程式碼的理解,這是學習程式碼最好的方式之一。
6.2
哪裡可以重構
這份程式碼本來就是一分學術論文的實驗程式碼,可擴充套件性不高,我想用工業界的玩法去改這份程式碼,下面列出可以重構的地方:
1、如何定義神經網路某一層。
原來的程式碼用一個類定義一層,這本身沒有問題,但所有的細節都在init方法中實現,讓該方法顯得很臃腫,我們可以根據職責的不同,分開兩個方法:init_param()和build()。也就是構建某一層神經網路最重要的兩部:初始化引數和根據輸入獲取輸出。
2、train_conv_net()方法太臃腫,這一步包括了構建網路,拆分train/val,構建function,訓練。一共四大步,我們應該把每步拆分開。
3、為什麼沒有模型的類?模型的行為類似於具體某層的行為,一層可以是類,為什麼很多層組裝以後反而裝在一個方法裡?我們也可以寫一個模型類。
4、模型的結構引數為什麼由方法引數傳入?我們可以寫一個config檔案,把模型的結構引數寫在這個config檔案裡。這樣再做實驗時,調模型的引數只需修改config檔案。
6.3
重構細節
接下來按照上面的幾點,演示下重構的細節:
cer_main.py:載入資料,開始訓練。
cer_module.py:每層模型的實現細節。
cer_model.py:整體模型的實現。
1、重構單層類:
重構前:
class HiddenLayer(object):
"""
Class for HiddenLayer
"""
def __init__(self, rng, input, n_in, n_out, activation, W=None, b=None,
use_bias=False):
self.input = input
self.activation = activation
if W is None:
if activation.func_name == "ReLU":
W_values = numpy.asarray(0.01 * rng.standard_normal(size=(n_in, n_out)), dtype=theano.config.floatX)
else:
W_values = numpy.asarray(rng.uniform(low=-numpy.sqrt(6. / (n_in + n_out)), high=numpy.sqrt(6. / (n_in + n_out)),
size=(n_in, n_out)), dtype=theano.config.floatX)
W = theano.shared(value=W_values, name='W')
if b is None:
b_values = numpy.zeros((n_out,), dtype=theano.config.floatX)
b = theano.shared(value=b_values, name='b')
self.W = W
self.b = b
if use_bias:
lin_output = T.dot(input, self.W) + self.b
else:
lin_output = T.dot(input, self.W)
self.output = (lin_output if activation is None else activation(lin_output))
# parameters of the model
if use_bias:
self.params = [self.W, self.b]
else:
self.params = [self.W]
重構後:
class HiddenLayer(object):
"""
Class for HiddenLayer
"""
def __init__(self, rng, n_in, n_out, activation, W=None, b=None):
self.rng = rng
self.activation = activation
self.init_param(W, b, n_in, n_out)
def init_param(self, W, b, n_in, n_out):
if W is None:
if self.activation.func_name == "ReLU":
W_values = numpy.asarray(0.01 * self.rng.standard_normal(size=(n_in, n_out)), dtype=theano.config.floatX)
else:
W_values = numpy.asarray(
self.rng.uniform(low=-numpy.sqrt(6. / (n_in + n_out)), high=numpy.sqrt(6. / (n_in + n_out)), size=(n_in, n_out)), dtype=theano.config.floatX)
W = theano.shared(value=W_values, name='W')
if b is None:
b_values = numpy.zeros((n_out,), dtype=theano.config.floatX)
b = theano.shared(value=b_values, name='b')
self.W = W
self.b = b
def build(self, input, use_bias=False):
if use_bias:
lin_output = T.dot(input, self.W) + self.b
else:
lin_output = T.dot(input, self.W)
self.output = (lin_output if self.activation is None else self.activation(lin_output))
# parameters of the model
if use_bias:
self.params = [self.W, self.b]
else:
self.params = [self.W]
return self.output
2、重構整體模型的構建:
################################網路架構:1.初始化########################### # 1.embedding層
self.emb_layer = EmbeddingLayer(U)
# 2.卷積層
self.conv_layers = []
for i in xrange(len(self.conf['filter_hs'])):
filter_shape = filter_shapes[i]
# print "filter_shape:", filter_shape
pool_size = pool_sizes[i]
conv_layer = LeNetConvPoolLayer(rng, image_shape=(self.conf['batch_size'], 1, self.img_h, self.conf['img_w']),
filter_shape=filter_shape, poolsize=pool_size, non_linear=self.conf['conv_non_linear']) self.conv_layers.append(conv_layer)
# 3.MLP(多層神經感知機,帶dropout)
self.conf['hidden_units'][0] = feature_maps * len(self.conf['filter_hs'])
self.classifier = MLPDropout(rng, layer_sizes=self.conf['hidden_units'], activations=[eval(f_s) for f_s in self.conf['activations']], dropout_rates=self.conf['dropout_rate'])
#################################網路架構:2.連線網路######################### # 1.embbeding層
emb_output = self.emb_layer.build(self.x)
# 2.卷積層
layer0_input = emb_output
layer1_inputs = []
for i in xrange(len(self.conf['filter_hs'])):
conv_layer = self.conv_layers[i]
layer1_input = conv_layer.build(layer0_input).flatten(2)
layer1_inputs.append(layer1_input)
layer1_input = T.concatenate(layer1_inputs, 1)
self.classifier.build(layer1_input)
###################提取模型引數########################################
# define parameters of the model and update functions using adadelta
params = self.classifier.params
for conv_layer in self.conv_layers:
params += conv_layer.params
if self.conf["non_static"]:
# if word vectors are allowed to change, add them as model parameters
params += [emb_output.Words]
self.cost = self.classifier.negative_log_likelihood(self.y)
self.dropout_cost = self.classifier.dropout_negative_log_likelihood(self.y)
self.grad_updates = sgd_updates_adadelta(params, self.dropout_cost, self.conf['lr_decay'], 1e-6, self.conf['sqr_norm_lim'])
3、增加整體模型的類:CNN_Sen_Model()
類方法:
build_model()
train()
build_function()
整體模型的類和具體某層的類共同點在於build,也就是給定輸入獲取輸出的過程。不同點在於要少一個init_param()方法,因為整體模型不需要去初始化模型訓練的引數,直接從細節類獲取即可。另外還多一個train的方法用於模型的訓練。
具體可以看我的程式碼。
4、將模型引數儲存在model.json中:
{
"img_w":300,
"max_l":56,
"filter_hs":[3, 4, 5],
"hidden_units":[100, 2],
"dropout_rate":[0.5],
"shuffle_batch":true,
"n_epochs":25,
"batch_size":50,
"lr_decay":0.95,
"conv_non_linear":"relu",
"activations":["Iden"],
"sqr_norm_lim":9, "non_static":false, "word_vectors":"word2vec"}
來跑跑看:
THEANO_FLAGS=mode=FAST_RUN,device=gpu,floatX=float32 python cer_main.py
output:
Using gpu device 0: GeForce GTX 960M (CNMeM is disabled, cuDNN not available) /home/cer/anaconda2/lib/python2.7/site-packages/theano/tensor/signal/
downsample.py:6: UserWarning: downsample module has been moved to the theano.tensor.signal.pool module. "downsample module has been moved to the theano.tensor.signal.pool module.")
loading data... model architecture: CNN-static
using: word2vec vectors
model configs: {u'dropout_rate': [0.5], u'hidden_units': [100, 2], u'word_vectors': u'word2vec', u'filter_hs': [3, 4, 5], u'conv_non_linear': u'relu', u'max_l': 56, u'img_w': 300, u'batch_size': 50, u'n_epochs': 25, u'sqr_norm_lim': 9, u'non_static': False, u'shuffle_batch': True, u'activations': [u'Iden'], u'lr_decay': 0.95}
emb_output shape : [1029 1 64 300]
conv_layer shape : [1029 100 1 1]
conv_layer shape : [1029 100 1 1]
conv_layer shape : [1029 100 1 1]
... training
epoch: 1, training time: 6.09 secs, train perf: 77.54 %, val perf: 73.79 %
epoch: 2, training time: 6.05 secs, train perf: 84.10 %, val perf: 76.53 %
epoch: 3, training time: 5.84 secs, train perf: 83.85 %, val perf: 76.32 %
epoch: 4, training time: 6.36 secs, train perf: 89.45 %, val perf: 78.32 %
epoch: 5, training time: 6.01 secs, train perf: 94.51 %, val perf: 79.26 %
epoch: 6, training time: 6.72 secs, train perf: 95.07 %, val perf: 78.63 %
epoch: 7, training time: 6.96 secs, train perf: 98.09 %, val perf: 79.89 %
epoch: 8, training time: 6.41 secs, train perf: 98.91 %, val perf: 80.00 %
epoch: 9, training time: 6.19 secs, train perf: 99.39 %, val perf: 78.63 %
epoch: 10, training time: 6.57 secs, train perf: 98.83 %, val perf: 78.84 %
epoch: 11, training time: 6.84 secs, train perf: 99.68 %, val perf: 80.00 %
epoch: 12, training time: 5.84 secs, train perf: 99.84 %, val perf: 78.74 %
epoch: 13, training time: 5.93 secs, train perf: 99.82 %, val perf: 79.16 %
epoch: 14, training time: 5.94 secs, train perf: 99.95 %, val perf: 78.63 %
epoch: 15, training time: 6.39 secs, train perf: 99.94 %, val perf: 78.42 %
epoch: 16, training time: 6.92 secs, train perf: 99.95 %, val perf: 79.16 %
epoch: 17, training time: 6.83 secs, train perf: 99.98 %, val perf: 78.53 %
epoch: 18, training time: 6.72 secs, train perf: 99.98 %, val perf: 79.26 %
epoch: 19, training time: 5.97 secs, train perf: 99.98 %, val perf: 78.63 %
epoch: 20, training time: 5.92 secs, train perf: 99.98 %, val perf: 78.63 %
epoch: 21, training time: 6.56 secs, train perf: 99.98 %, val perf: 79.37 %
epoch: 22, training time: 6.05 secs, train perf: 99.98 %, val perf: 78.95 %
epoch: 23, training time: 6.69 secs, train perf: 99.98 %, val perf: 78.63 %
epoch: 24, training time: 7.03 secs, train perf: 99.98 %, val perf: 78.84 %
epoch: 25, training time: 6.06 secs, train perf: 99.98 %, val perf: 79.16 %
cv: 0, perf: 0.781341107872
07
結語
這篇文章記錄了這個CNN Sentence Classification的基礎論文和程式碼實現,並沒有關注調參,Yoon Kim的github提到了一篇關於這種模型調參的paper(http://www.wildml.com/2015/12/implementing-a-cnn-for-text-classification-in-tensorflow),有興趣可以去看看。
這個模型還有Tensorflow的實現(http://www.wildml.com/2015/12/implementing-a-cnn-for-text-classification-in-tensorflow),同樣可以看看。
最後再附上我的程式碼(https://github.com/applenob/CNN_sentence),裡面有很多中文註釋,喜歡可以star哦~~~