使用gensim和sklearn搭建一個(gè)文本分類器

總的來(lái)講,一個(gè)完整的文本分類器主要由兩個(gè)階段,或者說(shuō)兩個(gè)部分組成:一是將文本向量化,將一個(gè)字符串轉(zhuǎn)化成向量形式;二是傳統(tǒng)的分類器,包括線性分類器,SVM, 神經(jīng)網(wǎng)絡(luò)分類器等等。

之前看的THUCTC的技術(shù)棧是使用 tf-idf 來(lái)進(jìn)行文本向量化,使用卡方校驗(yàn)(chi-square)來(lái)降低向量維度,使用liblinear(采用線性核的svm) 來(lái)進(jìn)行分類。而這里所述的文本分類器,使用lsi (latent semantic analysis, 隱性語(yǔ)義分析) 來(lái)進(jìn)行向量化, 不需要降維, 因?yàn)榭梢灾苯又付ňS度, 然后使用線性核svm進(jìn)行分類。lsi的部分主要使用gensim來(lái)進(jìn)行, 分類主要由sklearn來(lái)完成。具體實(shí)現(xiàn)可見(jiàn)使用gensim和sklearn搭建一個(gè)文本分類器(二):代碼和注釋這邊主要敘述流程

1. 文檔向量化

這部分的內(nèi)容主要由gensim來(lái)完成。gensim庫(kù)的一些基本用法在我之前的文章中已經(jīng)有過(guò)介紹點(diǎn)這里這里就不再詳述, 直接按照流程來(lái)寫了。采用lsi進(jìn)行向量化的流程主要有下面幾步:

將各文檔分詞,從字符串轉(zhuǎn)化為單詞列表

統(tǒng)計(jì)各文檔單詞,生成詞典(dictionary)

利用詞典將文檔轉(zhuǎn)化成詞頻表示的向量,即指向量中的各值對(duì)應(yīng)于詞典中對(duì)應(yīng)位置單詞在該文檔中出現(xiàn)次數(shù)

再進(jìn)行進(jìn)一步處理,將詞頻表示的向量轉(zhuǎn)化成tf-idf表示的向量

由tf-idf表示的向量轉(zhuǎn)化成lsi表示的向量

接下來(lái)按照上述流程來(lái)分別闡述

1.1 文檔分詞及預(yù)處理

分詞有很多種方法,也有很多現(xiàn)成的庫(kù),這里僅介紹結(jié)巴的簡(jiǎn)單用法

import jieba

content ="""面對(duì)當(dāng)前挑戰(zhàn),我們應(yīng)該落實(shí)2030年可持續(xù)發(fā)展議程,促進(jìn)包容性發(fā)展"""

content = list(jieba.cut(content, cut_all=False))

print(content)

>>>['面對(duì)','當(dāng)前','挑戰(zhàn)',',','我們','應(yīng)該','落實(shí)','2030','年','可','持續(xù)','發(fā)展','議程',',','促進(jìn)','包容性','發(fā)展']

注意上面的cut_all選項(xiàng),如果cut_all=False, 則會(huì)列出最優(yōu)的分割選項(xiàng); 如果cut_all=True, 則會(huì)列出所有可能出現(xiàn)的詞

content =list(jieba.cut(content, cut_all=True))

print(content)

>>>['面對(duì)','當(dāng)前','挑戰(zhàn)','','','我們','應(yīng)該','落實(shí)','2030','年','可','持續(xù)','發(fā)展','議程','','','促進(jìn)','包容','包容性','容性','發(fā)展']

應(yīng)該觀察到,在分詞后的直接結(jié)果中,有大量的無(wú)效項(xiàng),例如空格,逗號(hào)等等。因此,一般在分詞以后,還要進(jìn)行預(yù)處理。例如去掉停用詞(stop words, 指的是沒(méi)什么意義的詞,例如空格,逗號(hào),句號(hào),啊,呀, 等等), 去掉出現(xiàn)出現(xiàn)頻率過(guò)低和過(guò)高的詞等等。

我這一部分的程序是

def convert_doc_to_wordlist(str_doc,cut_all):

# 分詞的主要方法

sent_list = str_doc.split('\n')

sent_list = map(rm_char, sent_list) # 去掉一些字符,例如\u3000

word_2dlist = [rm_tokens(jieba.cut(part,cut_all=cut_all)) for part in sent_list] # 分詞

word_list = sum(word_2dlist,[])

return word_list

def rm_char(text):

text = re.sub('\u3000','',text)

return text

def get_stop_words(path='/home/multiangle/coding/python/PyNLP/static/stop_words.txt'):

# stop_words中,每行放一個(gè)停用詞,以\n分隔

file = open(path,'rb').read().decode('utf8').split('\n')

return set(file)

def rm_tokens(words): # 去掉一些停用次和數(shù)字

words_list = list(words)

stop_words = get_stop_words()

for i in range(words_list.__len__())[::-1]:

if words_list[i] in stop_words: # 去除停用詞

words_list.pop(i)

elif words_list[i].isdigit():

words_list.pop(i)

return words_list

主程序是convert_doc_to_wordlist方法,拿到要分詞的文本以后,首先去掉一些字符,例如\u3000等等。然后進(jìn)行分詞,再去掉其中的停用詞和數(shù)字。 最后得到的單詞,其順序是打亂的,即單詞間的相關(guān)信息已經(jīng)丟失

1.2 統(tǒng)計(jì)單詞,生成詞典

一般來(lái)講, 生成詞典應(yīng)該在將所有文檔都分完詞以后統(tǒng)一進(jìn)行,不過(guò)對(duì)于規(guī)模特別大的數(shù)據(jù),可以采用邊分詞邊統(tǒng)計(jì)的方法。將文本分批讀取分詞,然后用之前生成的詞典加入新內(nèi)容的統(tǒng)計(jì)結(jié)果,如下面所示

from gensim import corpora,models

import jieba

import re

from pprint import pprint

import os

files = ["但是現(xiàn)在教育局非要治理這么一個(gè)情況",

"然而又不搞明白為什么這些詞會(huì)出現(xiàn)"]

dictionary = corpora.Dictionary()

for file in files:

file = convert_doc_to_wordlist(file, cut_all=True)

dictionary.add_documents([file])

pprint(sorted(list(dictionary.items()),key=lambda x:x[0]))

>>>[(0, '教育'),

>>> (1, '治理'),

>>> (2, '教育局'),

>>> (3, '情況'),

>>> (4, '非要'),

>>> (5, '搞'),

>>> (6, '明白'),

>>> (7, '詞')]

對(duì)于已經(jīng)存在的詞典,可以使用dictionary.add_documents來(lái)往其中增加新的內(nèi)容。當(dāng)生成詞典以后,會(huì)發(fā)現(xiàn)詞典中的詞太多了,達(dá)到了幾十萬(wàn)的數(shù)量級(jí), 因此需要去掉出現(xiàn)次數(shù)過(guò)少的單詞,因?yàn)檫@些代詞沒(méi)什么代表性。

small_freq_ids = [tokenid for tokenid, docfreqindictionary.dfs.items() if docfreq <5]

dictionary.filter_tokens(small_freq_ids)

dictionary.compactify()

1.3 將文檔轉(zhuǎn)化成按詞頻表示的向量

繼續(xù)沿著之前的思路走,接下來(lái)要用dictionary把文檔從詞語(yǔ)列表轉(zhuǎn)化成用詞頻表示的向量,也就是one-hot表示的向量。所謂one-hot,就是向量中的一維對(duì)應(yīng)于詞典中的一項(xiàng)。如果以詞頻表示,則向量中該維的值即為詞典中該單詞在文檔中出現(xiàn)的頻率。其實(shí)這個(gè)轉(zhuǎn)化很簡(jiǎn)單,使用dictionray.doc2bow方法即可。

count = 0

bow? = []

for file in files:

count += 1

if count%100 == 0 :

print('{c} at {t}'.format(c=count, t=time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())))

word_list = convert_doc_to_wordlist(file, cut_all=False)

word_bow = dictionary.doc2bow(word_list)

bow.append(word_bow)

pprint(bow)

>>>[[(1, 1), (2, 1), (4, 1)], [(5, 1), (6, 1)]]

1.4 轉(zhuǎn)化成tf-idf和lsi向量

之所以把這兩部分放到一起,并不是因?yàn)檫@兩者的計(jì)算方式或者說(shuō)原理有多相似(實(shí)際上兩者完全不同),而是說(shuō)在gensim中計(jì)算這兩者的調(diào)用方法比較類似,都需要調(diào)用gensim.models庫(kù)。

tfidf_model = models.TfidfModel(corpus=corpus,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? dictionary=dictionary)corpus_tfidf = [tfidf_model[doc]fordocincorpus]lsi_model = models.LsiModel(corpus = corpus_tfidf,? ? ? ? ? ? ? ? ? ? ? ? ? ? id2word = dictionary,? ? ? ? ? ? ? ? ? ? ? ? ? ? num_topics=50)corpus_lsi = [lsi_model[doc]fordocincorpus]

1

2

3

4

5

6

7

可以看到gensim的方法還是比較簡(jiǎn)潔的。

1.5 實(shí)踐中的一些問(wèn)題

由于之前閱讀THUCTC源碼的時(shí)候下載了THUCTCNews文檔集,大概1G多點(diǎn),已經(jīng)幫你分好類,放在各個(gè)文件夾下面了。為了便于分析,各個(gè)環(huán)節(jié)的中間結(jié)果(詞頻向量,tfidf向量等)也都會(huì)存放到本地。為了便于以后標(biāo)注,各個(gè)類的中間結(jié)果也是按類別存儲(chǔ)的。

2. 分類問(wèn)題

在將文本向量化以后,就可以采用傳統(tǒng)的分類方法了, 例如線性分類法,線性核的svm,rbf核的svm,神經(jīng)網(wǎng)絡(luò)分類等方法。我在這個(gè)分類器中嘗試了前3種,都可以由sklearn庫(kù)來(lái)完成

2.1 從gensim到sklearn的格式轉(zhuǎn)換

一個(gè)很尷尬的問(wèn)題是,gensim中的corpus數(shù)據(jù)格式,sklearn是無(wú)法識(shí)別的。即gensim中對(duì)向量的表示形式與sklearn要求的不符。

在gensim中,向量是稀疏表示的。例如[(0,5),(6,3)] 意思就是說(shuō),該向量的第0個(gè)元素值為5,第6個(gè)元素值為3,其他為0.但是這種表示方式sklearn是無(wú)法識(shí)別的。sklearn的輸入一般是與numpy或者scipy配套的。如果是密集矩陣,就需要輸入numpy.array格式的; 如果是稀疏矩陣,則需要輸入scipy.sparse.csr_matrix.由于后者可以轉(zhuǎn)化成前者,而且gensim中向量本身就是稀疏表示,所以這邊只講如何將gensim中的corpus格式轉(zhuǎn)化成csr_matrix.

scipy的官網(wǎng)去找相關(guān)文檔,可以看到csr_matrix的構(gòu)造有如下幾種方法。

第一種是由現(xiàn)有的密集矩陣來(lái)構(gòu)建稀疏矩陣,第二種不是很清楚,第三種構(gòu)建一個(gè)空矩陣。第四種和第五種符合我們的要求。其中第四種最為直觀,構(gòu)建三個(gè)數(shù)組,分別存儲(chǔ)每個(gè)元素的行,列和數(shù)值即可。

官網(wǎng)給出的示例代碼如下,還是比較直觀的。

row = np.array([0,0,1,2,2,2])col = np.array([0,2,2,0,1,2])data = np.array([1,2,3,4,5,6])print(csr_matrix((data, (row, col)), shape=(3,3)).toarray())>>>array([[1, 0, 2],

[0, 0, 3],

[4, 5, 6]])

1

2

3

4

5

6

7

依樣畫葫蘆,gensim轉(zhuǎn)化到csr_matrix的程序可以寫成

data= []rows= []cols= []line_count=0forlineinlsi_corpus_total:? # lsi_corpus_total 是之前由gensim生成的lsi向量? ? for eleminline:? ? ? ? rows.append(line_count)? ? ? ? cols.append(elem[0])data.append(elem[1])line_count +=1lsi_sparse_matrix= csr_matrix((data,(rows,cols))) # 稀疏向量lsi_matrix= lsi_sparse_matrix.toarray()? # 密集向量

1

2

3

4

5

6

7

8

9

10

11

12

在將所有數(shù)據(jù)集都轉(zhuǎn)化成sklearn可用的格式以后,還要將其分成訓(xùn)練集和檢驗(yàn)集,比例大概在8:2.下面的代碼就是關(guān)于訓(xùn)練集和檢驗(yàn)集的生成的

data= []rows= []cols= []line_count=0forlineinlsi_corpus_total:? ? for eleminline:? ? ? ? rows.append(line_count)? ? ? ? cols.append(elem[0])data.append(elem[1])line_count +=1lsi_matrix= csr_matrix((data,(rows,cols))).toarray()rarray=np.random.random(size=line_count)train_set= []train_tag= []test_set= []test_tag= []foriinrange(line_count):ifrarray[i]<0.8:? ? ? ? train_set.append(lsi_matrix[i,:])? ? ? ? train_tag.append(tag_list[i])else:? ? ? ? test_set.append(lsi_matrix[i,:])? ? ? ? test_tag.append(tag_list[i])

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

2.2 線性判別分析

sklearn中,可以使用sklearn.discriminant_analysis.LinearDiscriminantAnalysis來(lái)進(jìn)行線性分類。

import numpy as npfrom sklearn.discriminant_analysis import LinearDiscriminantAnalysislda = LinearDiscriminantAnalysis(solver="svd", store_covariance=True)X = np.array([[-1, -1], [-2, -1], [1, 1], [2, 1]])Y = np.array([1,1,2,2])lda_res = lda.fit(X, Y)print(lda_res.predict([[-0.8, -1]]))

1

2

3

4

5

6

7

8

在上面的例子中,X代表了訓(xùn)練集。上面的X是一個(gè)4*2的矩陣,代表訓(xùn)練集中含有4各樣本,每個(gè)樣本的維度是2維。而Y代表的是訓(xùn)練集中各樣本所期望的分類結(jié)果?;氐轿谋痉诸惖娜蝿?wù),易知上面代碼的X對(duì)應(yīng)于train_set, 而Y對(duì)應(yīng)于train_tag

lda = LinearDiscriminantAnalysis(solver="svd", store_covariance=True)lda_res = lda.fit(train_set, train_tag)train_pred? = lda_res.predict(train_set)# 訓(xùn)練集的預(yù)測(cè)結(jié)果test_pred = lda_res.predict(test_set)# 檢驗(yàn)集的預(yù)測(cè)結(jié)果

1

2

3

4

lda_res即是得到的lda模型。 train_pred, test_pred 分別是訓(xùn)練集和檢驗(yàn)集根據(jù)得到的lda模型獲得的預(yù)測(cè)結(jié)果。

實(shí)驗(yàn)批次向量化方法向量長(zhǎng)度分類方法訓(xùn)練集錯(cuò)誤率檢驗(yàn)集錯(cuò)誤率

1LSI50線性判別16.78%17.18%

2LSI100線性判別14.10%14.25%

3LSI200線性判別11.74%11.73%

4LSI400線性判別10.50%10.93%

2.3 SVM分類

總的來(lái)說(shuō),使用SVM與上面LDA的使用方法比較類似。使用sklearn.svm類可以完成。不過(guò)與lda相比,svm可以接受稀疏矩陣作為輸入,這是個(gè)好消息。

# clf = svm.SVC()? # 使用RBF核clf = svm.LinearSVC()# 使用線性核clf_res = clf.fit(train_set,train_tag)train_pred? = clf_res.predict(train_set)test_pred? = clf_res.predict(test_set)

1

2

3

4

5

可以使用RBF核,也可以使用線性核。不過(guò)要注意,RBF核在數(shù)據(jù)集不太充足的情況下有很好的結(jié)果,但是當(dāng)數(shù)據(jù)量很大是就不太明顯,而且運(yùn)行速度非常非常非常的慢! 所以我推薦使用線性核,運(yùn)算速度快,而且效果比線性判別稍好一些

實(shí)驗(yàn)批次向量化方法向量長(zhǎng)度分類方法訓(xùn)練集錯(cuò)誤率檢驗(yàn)集錯(cuò)誤率

5LSI50svm_linear12.31%12.52%

6LSI100svm_linear10.13%10.20%

7LSI200svm_linear8.75%8.98%

8LSI400svm_linear7.70%7.89%

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容