中文文本分類對比(經(jīng)典方法和CNN)

背景介紹

筆者實(shí)驗(yàn)室項(xiàng)目正好需要用到文本分類,作為NLP領(lǐng)域最經(jīng)典的場景之一赌躺,文本分類積累了大量的技術(shù)實(shí)現(xiàn)方法扛伍,如果將是否使用深度學(xué)習(xí)技術(shù)作為標(biāo)準(zhǔn)來衡量掂榔,實(shí)現(xiàn)方法大致可以分成兩類:

  • 基于傳統(tǒng)機(jī)器學(xué)習(xí)的文本分類
  • 基于深度學(xué)習(xí)的文本分類

facebook之前開源的fastText屬于簡化版的第二類探颈,詞向量取平均直接進(jìn)softmax層熟丸,還有業(yè)界研究上使用比較多的TextCNN模型屬于第二類。有一個(gè)github項(xiàng)目很好的把這些模型都集中到了一起伪节,并做了一些簡單的性能比較光羞,想要進(jìn)一步了解這些高大上模型的同學(xué)可以查看如下鏈接:

all kinds of text classificaiton models and more with deep learning

本文的目的主要記錄筆者自己構(gòu)建文本分類系統(tǒng)的過程,分別構(gòu)建基于傳統(tǒng)機(jī)器學(xué)習(xí)的文本分類和基于深度學(xué)習(xí)的文本分類系統(tǒng)怀大,并在同一數(shù)據(jù)集上進(jìn)行測試纱兑。

經(jīng)典的機(jī)器學(xué)習(xí)方法采用獲取tf-idf文本特征,分別喂入logistic regression分類器和隨機(jī)森林分類器的思路化借,并對兩種方法做性能對比潜慎。

基于深度學(xué)習(xí)的文本分類,這里主要采用CNN對文本分類屏鳍,考慮到RNN模型相較CNN模型性能差異不大并且耗時(shí)還比較久勘纯,這里就不多做實(shí)驗(yàn)了。

實(shí)驗(yàn)過程有些比較有用的small trick分享钓瞭,包括多進(jìn)程分詞、訓(xùn)練全量tf-idf淫奔、python2對中文編碼的處理技巧等等山涡,在下文都會(huì)仔細(xì)介紹。

食材準(zhǔn)備

本文采用的數(shù)據(jù)集是很流行的搜狗新聞數(shù)據(jù)集唆迁,get到的時(shí)候已經(jīng)是經(jīng)過預(yù)處理的了鸭丛,所以省去了很多數(shù)據(jù)預(yù)處理的麻煩,數(shù)據(jù)集下載鏈接如下:

(感謝張凱強(qiáng)同學(xué)指出了我的錯(cuò)誤唐责,數(shù)據(jù)集是THUCnews的鳞溉,清華大學(xué)根據(jù)新浪新聞RSS訂閱頻道2005-2011年間的歷史數(shù)據(jù)篩選過濾生成,非常感謝鼠哥,下面的鏈接也更新過一次熟菲,參考鏈接中有原始我參考的博文,如果鏈接再失效朴恳,數(shù)據(jù)集也可以去那里找找看抄罕,由于我的學(xué)業(yè)和實(shí)習(xí)導(dǎo)致我的生活越來越忙,不能及時(shí)回復(fù)大家了于颖,請多見諒呆贿,謝謝!)

新聞文本分類數(shù)據(jù)集下載

密碼:kxxa

數(shù)據(jù)集一共包括10類新聞森渐,每類新聞65000條文本數(shù)據(jù)做入,訓(xùn)練集50000條冒晰,測試集10000條,驗(yàn)證集5000條竟块。

經(jīng)典機(jī)器學(xué)習(xí)方法

分詞翩剪、去停用詞

調(diào)用之前短文本分類博文中提到的分詞工具類,對訓(xùn)練集彩郊、測試集前弯、驗(yàn)證集進(jìn)行多進(jìn)程分詞,以節(jié)省時(shí)間:

import multiprocessing


tmp_catalog = '/home/zhouchengyu/haiNan/textClassifier/data/cnews/'
file_list = [tmp_catalog+'cnews.train.txt', tmp_catalog+'cnews.test.txt']
write_list = [tmp_catalog+'train_token.txt', tmp_catalog+'test_token.txt']

def tokenFile(file_path, write_path):
    word_divider = WordCut()
    with open(write_path, 'w') as w:
        with open(file_path, 'r') as f:
            for line in f.readlines():
                line = line.decode('utf-8').strip()
                token_sen = word_divider.seg_sentence(line.split('\t')[1])
                w.write(line.split('\t')[0].encode('utf-8') + '\t' + token_sen.encode('utf-8') + '\n') 
    print file_path + ' has been token and token_file_name is ' + write_path

pool = multiprocessing.Pool(processes=4)
for file_path, write_path in zip(file_list, write_list):
    pool.apply_async(tokenFile, (file_path, write_path, ))
pool.close()
pool.join() # 調(diào)用join()之前必須先調(diào)用close()
print "Sub-process(es) done."

計(jì)算tf-idf

這里有幾點(diǎn)需要注意的秫逝,一是計(jì)算tf-idf是全量計(jì)算恕出,所以需要將train+test+val的所有corpus都相加,再進(jìn)行計(jì)算违帆,二是為了防止文本特征過大浙巫,需要去低頻詞,因?yàn)槭窃趈upyter上寫的刷后,所以測試代碼的時(shí)候的畴,先是選擇最小的val數(shù)據(jù)集,成功后尝胆,再對test,train數(shù)據(jù)集迭代操作丧裁,希望不要給大家留下代碼冗余的影響...[悲傷臉]。實(shí)現(xiàn)代碼如下:

def constructDataset(path):
    """
    path: file path
    rtype: lable_list and corpus_list
    """
    label_list = []
    corpus_list = []
    with open(path, 'r') as p:
        for line in p.readlines():
            label_list.append(line.split('\t')[0])
            corpus_list.append(line.split('\t')[1])
    return label_list, corpus_list
    
tmp_catalog = '/home/zhouchengyu/haiNan/textClassifier/data/cnews/'
file_path = 'val_token.txt'
val_label, val_set = constructDataset(tmp_catalog+file_path)
print len(val_set)

from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_extraction.text import CountVectorizer


tmp_catalog = '/home/zhouchengyu/haiNan/textClassifier/data/cnews/'
write_list = [tmp_catalog+'train_token.txt', tmp_catalog+'test_token.txt']

tarin_label, train_set = constructDataset(write_list[0]) # 50000
test_label, test_set = constructDataset(write_list[1]) # 10000
# 計(jì)算tf-idf
corpus_set = train_set + val_set + test_set # 全量計(jì)算tf-idf
print "length of corpus is: " + str(len(corpus_set))
vectorizer = CountVectorizer(min_df=1e-5) # drop df < 1e-5,去低頻詞
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(vectorizer.fit_transform(corpus_set))
words = vectorizer.get_feature_names()
print "how many words: {0}".format(len(words))
print "tf-idf shape: ({0},{1})".format(tfidf.shape[0], tfidf.shape[1])

"""
length of corpus is: 65000
how many words: 379000
tf-idf shape: (65000,379000)
"""

標(biāo)簽數(shù)字化含衔,抽取數(shù)據(jù)

因?yàn)楸緛砦谋揪褪且砸欢S機(jī)性抽取成3份數(shù)據(jù)集的煎娇,所以,這里就不shuffle啦贪染,偷懶一下下缓呛。。但是如果能shuffle的話杭隙,盡量還是做這一步哟绊,堅(jiān)持正途。

from sklearn import preprocessing

# encode label
corpus_label = tarin_label + val_label + test_label
encoder = preprocessing.LabelEncoder()
corpus_encode_label = encoder.fit_transform(corpus_label)
train_label = corpus_encode_label[:50000]
val_label = corpus_encode_label[50000:55000]
test_label = corpus_encode_label[55000:]
# get tf-idf dataset
train_set = tfidf[:50000]
val_set = tfidf[50000:55000]
test_set = tfidf[55000:]

喂入分類器

  • logistic regression分類器
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
# from sklearn.metrics import confusion_matrix

# LogisticRegression classiy model
lr_model = LogisticRegression()
lr_model.fit(train_set, train_label)
print "val mean accuracy: {0}".format(lr_model.score(val_set, val_label))
y_pred = lr_model.predict(test_set)
print classification_report(test_label, y_pred)

分類報(bào)告如下(包括準(zhǔn)確率痰憎、召回率票髓、F1值):

mean accuracy: 0.9626
             precision    recall  f1-score   support

          0       1.00      1.00      1.00      1000
          1       0.99      0.98      0.98      1000
          2       0.94      0.87      0.91      1000
          3       0.91      0.91      0.91      1000
          4       0.97      0.93      0.95      1000
          5       0.97      0.98      0.98      1000
          6       0.93      0.96      0.95      1000
          7       0.99      0.97      0.98      1000
          8       0.94      0.99      0.96      1000
          9       0.95      0.99      0.97      1000

avg / total       0.96      0.96      0.96     10000
  • Random Forest 分類器
# 隨機(jī)森林分類器
from sklearn.ensemble import RandomForestClassifier    


rf_model = RandomForestClassifier(n_estimators=200, random_state=1080)
rf_model.fit(train_set, train_label)
print "val mean accuracy: {0}".format(rf_model.score(val_set, val_label))
y_pred = rf_model.predict(test_set)
print classification_report(test_label, y_pred)

分類報(bào)告如下(包括準(zhǔn)確率、召回率信殊、F1值):

val mean accuracy: 0.9228
             precision    recall  f1-score   support

          0       1.00      1.00      1.00      1000
          1       0.98      0.98      0.98      1000
          2       0.89      0.57      0.69      1000
          3       0.81      0.97      0.88      1000
          4       0.95      0.89      0.92      1000
          5       0.97      0.96      0.97      1000
          6       0.85      0.94      0.89      1000
          7       0.95      0.97      0.96      1000
          8       0.95      0.97      0.96      1000
          9       0.91      0.99      0.95      1000

avg / total       0.93      0.92      0.92     10000

分析

上面采用邏輯回歸分類器和隨機(jī)森林分類器做對比:
可以發(fā)現(xiàn)炬称,除了個(gè)別分類隨機(jī)森林方法有較大進(jìn)步,大部分都差于邏輯回歸分類器
并且200棵樹的隨機(jī)森林耗時(shí)過長涡拘,比起邏輯回歸分類器來說玲躯,運(yùn)算效率太低

CNN文本分類

這一部分主要是參考tensorflow社區(qū)的一份博客進(jìn)行實(shí)驗(yàn)的,這里也不再贅述,博客講的非常好跷车,附上原文鏈接棘利,前去膜拜:NN-RNN中文文本分類,基于tensorflow

字符級特征提取

這里和前文差異比較大的地方朽缴,主要是提取文本特征這一塊善玫,這里的CNN模型采用的是字符級特征提取,比如data目錄下cnews_loader.py中:

def read_file(filename):
    """讀取文件數(shù)據(jù)"""
    contents, labels = [], []
    with open_file(filename) as f:
        for line in f:
            try:
                label, content = line.strip().split('\t')
                contents.append(list(content)) # 字符級特征
                labels.append(label)
            except:
                pass
    return contents, labels

def build_vocab(train_dir, vocab_dir, vocab_size=5000):
    """根據(jù)訓(xùn)練集構(gòu)建詞匯表密强,存儲"""
    data_train, _ = read_file(train_dir)

    all_data = []
    for content in data_train:
        all_data.extend(content)

    counter = Counter(all_data)
    count_pairs = counter.most_common(vocab_size - 1)
    words, _ = list(zip(*count_pairs))
    # 添加一個(gè) <PAD> 來將所有文本pad為同一長度
    words = ['<PAD>'] + list(words)

筆者做了下測試:

#! /bin/env python
# -*- coding: utf-8 -*-
from collections import Counter

"""
字符級別處理,
對于中文來說茅郎,基本不是原意的字,但是也能作為一種統(tǒng)計(jì)特征來表征文本
"""
content1 = "你好呀大家"
content2 = "你真的好嗎或渤?"
# content = "abcdefg"
all_data = []
all_data.extend(list(content1))
all_data.extend(list(content2))
# print list(content) # 字符級別處理
# print "length: " + str(len(list(content)))
counter = Counter(all_data)
count_pairs = counter.most_common(5)
words, _ = list(zip(*count_pairs))
words = ['<PAD>'] + list(words) #['<PAD>', '\xe5', '\xbd', '\xa0', '\xe4', '\xe7']

這種基本不是原意的字符級別的特征系冗,也能從統(tǒng)計(jì)意義上表征文本,從而作為特征薪鹦,這一點(diǎn)需要清楚掌敬。

遷移python2

github上的版本是python3的,由于筆者一直使用的是python2,所以對上述工作做了一點(diǎn)版本遷移池磁,使得在如下環(huán)境下也能順利運(yùn)行:

Python 2.7
TensorFlow 1.3
numpy
scikit-learn

除了p3和py2差異比較明顯的類定義奔害、print、除法運(yùn)算外地熄,還有就是中文編碼华临,使用codecs模塊可以很好的解決這個(gè)問題,由于是細(xì)枝末節(jié)离斩,這里也就不展開來說了银舱。

最終,在同一數(shù)據(jù)集上跛梗,得到的測試報(bào)告如下:

Test Loss:   0.13, Test Acc:  96.06%
Precision, Recall and F1-Score...
              precision    recall  f1-score   support

      sports       0.99      0.99      0.99      1000
     finance       0.96      0.99      0.98      1000
       house       1.00      0.99      1.00      1000
      living       0.99      0.88      0.93      1000
   education       0.90      0.93      0.92      1000
        tech       0.92      0.99      0.95      1000
     fashion       0.95      0.97      0.96      1000
      policy       0.97      0.92      0.94      1000
        game       0.97      0.97      0.97      1000
entertaiment       0.95      0.98      0.96      1000

 avg / total       0.96      0.96      0.96     10000

分析

可以看出與傳統(tǒng)機(jī)器學(xué)習(xí)方法相比,貌似深度學(xué)習(xí)方法優(yōu)勢不大棋弥,但是考慮到數(shù)據(jù)集數(shù)量不多核偿、深度學(xué)習(xí)模型仍舊是個(gè)baseline,還可以通過進(jìn)一步的調(diào)節(jié)參數(shù),來達(dá)到更好的效果,深度學(xué)習(xí)在文本分類性能優(yōu)化方面顽染,依舊是大有可為的漾岳。

  • 參考資料

NN-RNN中文文本分類,基于tensorflow

詳細(xì)代碼見筆者的github:中文文本分類對比(經(jīng)典方法和CNN)

××××××××××××××××××××××××××××××××××××××××××

本文屬于筆者(EdwardChou)原創(chuàng)

轉(zhuǎn)載請注明出處

××××××××××××××××××××××××××××××××××××××××××

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末粉寞,一起剝皮案震驚了整個(gè)濱河市尼荆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌唧垦,老刑警劉巖捅儒,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡巧还,警方通過查閱死者的電腦和手機(jī)鞭莽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來麸祷,“玉大人澎怒,你說我怎么就攤上這事〗纂梗” “怎么了喷面?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長走孽。 經(jīng)常有香客問我惧辈,道長,這世上最難降的妖魔是什么融求? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任咬像,我火速辦了婚禮,結(jié)果婚禮上生宛,老公的妹妹穿的比我還像新娘县昂。我一直安慰自己,他們只是感情好陷舅,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布倒彰。 她就那樣靜靜地躺著,像睡著了一般莱睁。 火紅的嫁衣襯著肌膚如雪待讳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天仰剿,我揣著相機(jī)與錄音创淡,去河邊找鬼。 笑死南吮,一個(gè)胖子當(dāng)著我的面吹牛琳彩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播部凑,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼露乏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了涂邀?” 一聲冷哼從身側(cè)響起瘟仿,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎比勉,沒想到半個(gè)月后劳较,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驹止,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年兴想,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了幢哨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嫂便,死狀恐怖捞镰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情毙替,我是刑警寧澤岸售,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站厂画,受9級特大地震影響凸丸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜袱院,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一屎慢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧忽洛,春花似錦腻惠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至复哆,卻和暖如春欣喧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梯找。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工唆阿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锈锤。 一個(gè)月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓酷鸦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親牙咏。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容