1 簡(jiǎn)述
????在沒(méi)有監(jiān)督數(shù)據(jù)的時(shí)候,采用無(wú)監(jiān)督算法的方式可以計(jì)算兩句話的相似度球及,即通過(guò)一些因子粒竖,比如語(yǔ)序颅崩、詞性、共現(xiàn)詞比例等等進(jìn)行打分蕊苗,最后通過(guò)加權(quán)計(jì)算的方式得到最終的相似分值沿后,最終結(jié)果主要依賴因子即特征的提取和加權(quán)公式的設(shè)計(jì),相關(guān)項(xiàng)目可以參考Kaggle Quora比賽華人第一名的解決方案朽砰,里面有一些優(yōu)秀的可借鑒特征尖滚。
????但是最終想要更好的效果必然要使用到有監(jiān)督的算法,而現(xiàn)有較好的技術(shù)便是TextCNN瞧柔。本文主要是整理捋順一下TextCNN的整個(gè)過(guò)程以及背后的一些原理漆弄,包括自己踩坑的點(diǎn),還有Tensorflow的一些框架問(wèn)題造锅。
2 項(xiàng)目
????CNN做文本分類的項(xiàng)目及代碼可以參考: TextCNN
3 論文
????CNN做文本分類的論文可以參看: Convolutional Neural Networks for Sentence Classification
????上圖是論文中給出的模型結(jié)構(gòu)撼唾,但是這張圖不是很清楚,下圖可以更清楚的看清TextCNN的結(jié)構(gòu)哥蔚。
????算法的主要流程是通過(guò)使用不同kernel_sizes的卷積核對(duì)文本embedding二維向量進(jìn)行卷積操作倒谷,每一種kernel_sizes的卷積核有多個(gè),這樣就可以獲得類似n-gram的句法特征糙箍,最后經(jīng)過(guò)max_pooling恨锚,在進(jìn)行一次拼接,即可以得到文檔向量倍靡,經(jīng)過(guò)全連接和softmax即可進(jìn)行分類猴伶。
????由上圖所示,文檔二維embedding向量為(7x5),卷積核的kernel_sizes有三種他挎,分別為2,3,4筝尾,每種卷積核有2個(gè),一共有2x3=6個(gè)卷積核办桨。kernel_sizes=4的卷積核經(jīng)過(guò)卷積之后得到(7-4+1)x1即4x1的向量筹淫,通過(guò)max_pooling之后得到一個(gè)數(shù)值,同理其余的5個(gè)卷積核分別進(jìn)行卷積和池化操作呢撞,這樣可以得到6個(gè)數(shù)值损姜,然后將這6個(gè)數(shù)值拼接一起,注意拼接時(shí)候的維度殊霞,既可以得到文本新的向量摧阅。然后可以接全連接再接softmax即可以分類。
????論文中另一case是針對(duì)算法的輸入即embedding向量進(jìn)行優(yōu)化绷蹲,可以使用Pre-trained的詞向量代替隨機(jī)初始化的embedding向量棒卷,分別對(duì)比隨機(jī)初始化、靜態(tài)預(yù)訓(xùn)練向量(不參與訓(xùn)練)祝钢、微調(diào)預(yù)訓(xùn)練向量(參與訓(xùn)練)和通過(guò)設(shè)置通道channels輸入不同向量四種方式比规。最終效果個(gè)人感覺(jué)只能做一個(gè)參考,具體還要視業(yè)務(wù)場(chǎng)景而定拦英。比如針對(duì)業(yè)務(wù)垂直領(lǐng)域很強(qiáng)的文本領(lǐng)域蜒什,使用基于大數(shù)據(jù)集的預(yù)訓(xùn)練向量可能不會(huì)有很好的效果,比如針對(duì)數(shù)碼領(lǐng)域疤估,如果隨機(jī)初始化embedding向量吃谣,在通過(guò)train的方式更新,那么類似“蘋果”之類的詞語(yǔ)更會(huì)傾向“蘋果手機(jī)”做裙,而使用大數(shù)據(jù)集預(yù)訓(xùn)練的詞向量岗憋,“蘋果”這個(gè)詞則可能更會(huì)傾向水果,這種引入預(yù)訓(xùn)練的方式可能會(huì)打破原有數(shù)據(jù)的結(jié)構(gòu)平衡锚贱,當(dāng)然如果對(duì)領(lǐng)域多元化的業(yè)務(wù)可能引入預(yù)訓(xùn)練效果會(huì)更好仔戈。另一個(gè)引入預(yù)訓(xùn)練詞向量的好處是可以加快訓(xùn)練速度,這樣即使效果得不到提升拧廊,但是速度得到提升也是蠻不錯(cuò)的监徘。
4 代碼
????下面是一個(gè)標(biāo)準(zhǔn)的TextCNN的代碼:
# coding: utf-8
import tensorflow as tf
import warnings
warnings.filterwarnings("ignore")
class TCNNConfig(object):
embedding_dim = 64 # 詞向量維度
seq_length = 600 # 序列長(zhǎng)度
num_classes = 10 # 類別數(shù)
num_filters = 256 # 卷積核數(shù)目
# kernel_sizes = [3, 4, 5] # 卷積核尺寸
kernel_sizes = 5 # 卷積核尺寸
vocab_size = 5000 # 詞匯表達(dá)小
hidden_dim = 128 # 全連接層神經(jīng)元
dropout_keep_prob = 0.5 # dropout保留比例
learning_rate = 1e-3 # 學(xué)習(xí)率
batch_size = 64 # 每批訓(xùn)練大小
num_epochs = 10 # 總迭代輪次
print_per_batch = 100 # 每多少輪輸出一次結(jié)果
save_per_batch = 10 # 每多少輪存入tensorboard
class TextCNN(object):
def __init__(self, config):
self.config = config
# 三個(gè)待輸入的數(shù)據(jù)
self.input_x = tf.placeholder(tf.int32, [None, self.config.seq_length], name='input_x')
self.input_y = tf.placeholder(tf.float32, [None, self.config.num_classes], name='input_y')
self.keep_prob = tf.placeholder(tf.float32, name='keep_prob')
self.cnn()
def cnn(self):
# 詞向量映射
with tf.device('/cpu:0'):
embedding = tf.get_variable('embedding', [self.config.vocab_size, self.config.embedding_dim])
embedding_inputs = tf.nn.embedding_lookup(embedding, self.input_x)
with tf.name_scope("cnn"):
# CNN layer
conv = tf.layers.conv1d(embedding_inputs, self.config.num_filters, self.config.kernel_size, name='conv')
# global max pooling layer
gmp = tf.reduce_max(conv, reduction_indices=[1], name='gmp')
'''
for kernel_size in self.config.kernel_sizes:
gmps = []
with tf.name_scope("cnn-%s" % kernel_size):
# CNN layer
conv = tf.layers.conv1d(embedding_inputs, self.config.num_filters, kernel_size)
# global max pooling layer
gmp = tf.reduce_max(conv, reduction_indices=[1])
gmps.append(gmp)
gmp = tf.concat(values=gmps, name='last_pool_layer', axis=3)
'''
with tf.name_scope("score"):
# 全連接層,后面接dropout以及relu激活
fc1 = tf.layers.dense(gmp, self.config.hidden_dim, name='fc1')
fc2 = tf.contrib.layers.dropout(fc1, self.keep_prob)
fc = tf.nn.relu(fc2)
# 分類器
self.logits = tf.layers.dense(fc, self.config.num_classes, name='fc2')
self.y_pred_cls = tf.argmax(tf.nn.softmax(self.logits), 1) # 預(yù)測(cè)類別
with tf.name_scope("optimize"):
# 損失函數(shù)吧碾,交叉熵
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=self.logits, labels=self.input_y)
self.loss = tf.reduce_mean(cross_entropy)
# 優(yōu)化器
self.optim = tf.train.AdamOptimizer(learning_rate=self.config.learning_rate).minimize(self.loss)
with tf.name_scope("accuracy"):
# 準(zhǔn)確率
correct_pred = tf.equal(tf.argmax(self.input_y, 1), self.y_pred_cls)
self.acc = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
????代碼中的TextCNN并沒(méi)有設(shè)置可變kernel_sizes凰盔,統(tǒng)一設(shè)置kernel_sizes=5。通過(guò)修改注釋代碼即可以實(shí)現(xiàn)可變kernel_sizes倦春。實(shí)驗(yàn)之后發(fā)現(xiàn)加入可變的kernel_sizes可以在實(shí)驗(yàn)的那個(gè)數(shù)據(jù)集上略微提升一點(diǎn)效果户敬,但是提升不大落剪,因?yàn)楣潭╧ernel_sizes=5效果就已經(jīng)很好了,具體對(duì)比可以看GitHub的對(duì)比尿庐,所以在新的數(shù)據(jù)集上可以實(shí)驗(yàn)對(duì)比兩種效果忠怖,kernel_sizes一般選2,3,4,5。
????另一個(gè)困擾我比較久的是數(shù)據(jù)傳輸問(wèn)題抄瑟,數(shù)據(jù)在傳送過(guò)程中的維度變化》财現(xiàn)在就基于以上代碼(tensorflow)捋順一下數(shù)據(jù)傳送過(guò)程中的維度變化。batch_size=64皮假,seq_length=600鞋拟,可得input_x (64, 600);vocab_size=5000惹资,embedding_dim =64贺纲,可得embedding (5000, 64);embedding_inputs (64, 600, 64)布轿;num_filters=256哮笆,kernel_size=5来颤,可得conv (64, 596, 256)汰扭;gmp (64, 256);fc1 (64, 128)福铅;fc2(64, 128)萝毛,fc1到fc2只經(jīng)過(guò)一個(gè)dropout所以維度并沒(méi)有變化;fc(64, 128)滑黔,fu2到fc只經(jīng)過(guò)一個(gè)relu所以維度沒(méi)有變化笆包。之后就是進(jìn)行softmax操作了。
5 踩坑
???? - python2 對(duì)漢字真是太不友好了略荡,各種編碼問(wèn)題
???? - 盡量不要并聯(lián)太多and庵佣,不然速度會(huì)很慢,比如分詞的時(shí)候可以先通過(guò)詞長(zhǎng)過(guò)濾掉一部分汛兜,然后全部收集巴粪,最后再遍歷,篩掉停用詞粥谬。如果分詞的時(shí)候直接篩停用詞肛根,速度超級(jí)慢。
???? - 字符級(jí)和詞語(yǔ)級(jí)從當(dāng)前公開(kāi)數(shù)據(jù)集上對(duì)比漏策,效果相當(dāng)派哲,詞語(yǔ)級(jí)速度更快,但是需要預(yù)先分詞
???? - 目前還沒(méi)有實(shí)驗(yàn)預(yù)訓(xùn)練詞向量掺喻,未完待續(xù)芭届。储矩。。