一、簡介
1)jieba
中文叫做結巴,是一款中文分詞工具,https://github.com/fxsjy/jieba
2)word2vec
單詞向量化工具,https://radimrehurek.com/gensim/models/word2vec.html
3)LR
LogisticRegression中文叫做邏輯迴歸模型,是一種基礎、常用的分類方法
二、步驟
0)建立jupyter notebook
桌面新建名字爲基於word2vec的文檔分類的文件夾,並進入該文件夾,按住shift,鼠標點擊右鍵,然後選擇在此處打開命令窗口,然後在dos下輸入:jupyter notebook
新建一文件:word2vecTest.ipynb
1)數據準備
鏈接:https://pan.baidu.com/s/1mR87V40bUtWgUBIoqn4lOw 密碼:lqe4
訓練集共有24000條樣本,12個分類,每個分類2000條樣本。
測試集共有12000條樣本,12個分類,每個分類1000條樣本。
下載並解壓到基於word2vec的文檔分類文件夾內:
查看數據發現文件分兩列:
?
1
2
3
|
import pandas as pd
train_df = pd.read_csv( 'sohu_train.txt' , sep = '\t' , header = None )
train_df.head()
|
查看train每個分類的名字以及樣本數量:
?
1
2
3
4
5
6
|
for name, group in train_df.groupby( 0 ):
print (name, '\t' , len (group))
#或者通過columns來查看
train_df.columns = [ 'Subject' , 'Content' ]
train_df[ 'Subject' ].value_counts().sort_index()
|
同樣的方法查看test每個分類的名字以及樣本數量:
?
1
2
3
|
test_df = pd.read_csv( 'sohu_test.txt' , sep = '\t' , header = None )
for name, group in test_df.groupby( 0 ):
print (name, '\t' , len (group))
|
關於groupby函數,我們通過查看name和group加以理解(變量group是一個GroupBy對象,它實際上還沒有進行任何計算):
?
1
2
3
|
for name, group in df_train.groupby( 0 ):
print (name)
print (group)
|
其中科技即打印出來的name,後面的內容即group對象內容,包含兩列,第一列爲科技,第二列爲內容(注意:該train數據集包含了12個分類,這裏只是展示了name爲科技的圖片,其他name結構類似)
2)分詞
安裝jieba:pip install jieba
對訓練集的24000條樣本循環遍歷,使用jieba庫的cut方法獲得分詞列表賦值給變量cutWords。
判斷分詞是否爲停頓詞,如果不爲停頓詞,則添加進變量cutWords中,查看一下stopwords.txt文件:
從上我們發現:這些停頓詞語都是沒用用的詞語,對我們文本分類沒什麼作用,所以在分詞的時候,將其從分詞列表中剔除
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import jieba, time
train_df.columns = [ '分類' , '文章' ]
#stopword_list = [k.strip() for k in open('stopwords.txt', encoding='utf-8').readlines() if k.strip() != '']
#上面的語句不建議這麼寫,因爲readlines()是一下子將所有內容讀入內存,如果文件過大,會很耗內存,建議這麼寫
stopword_list = [k.strip() for k in open ( 'stopwords.txt' , encoding = 'utf-8' ) if k.strip() ! = '']
cutWords_list = []
i = 0
startTime = time.time()
for article in train_df[ '文章' ]:
cutWords = [k for k in jieba.cut(article) if k not in stopword_list]
i + = 1
if i % 1000 = = 0 :
print ( '前%d篇文章分詞共花費%.2f秒' % (i, time.time() - startTime))
cutWords_list.append(cutWords)
|
本人電腦配置低(在linux下速度很快,本人用ubuntu,前5000篇文章分詞共花費373.56秒,快了近三分之二),用時:
然後將分詞結果保存爲本地文件cutWords_list.txt,代碼如下:
?
1
2
3
|
with open ( 'cutWords_list.txt' , 'w' ) as file :
for cutWords in cutWords_list:
file .write( ' ' .join(cutWords) + '\n' )
|
爲了節約時間,將cutWords_list.txt保存到本地,鏈接:https://pan.baidu.com/s/1zQiiJGp3helJraT3Sfxv5w 提取碼:g6c7
載入分詞文件:
?
1
2
|
with open ( 'cutWords_list.txt' ) as file :
cutWords_list = [ k.split() for k in file ]
|
查看分詞結果文件:cutWords_list.txt,可以看出中文分詞工具jieba分詞效果還不錯:
3)word2vec模型
安裝命令:pip install gensim
調用gensim.models.word2vec庫中的LineSentence方法實例化行模型對象(爲避免warning信息輸出,導入warning 模塊):
?
1
2
3
4
5
6
7
|
import warnings
warnings.filterwarnings( 'ignore' )
from gensim.models import Word2Vec
word2vec_model = Word2Vec(cutWords_list, size = 100 , iter = 10 , min_count = 20 )
|
sentences:可以是一個list,對於大語料集,建議使用BrownCorpus,Text8Corpus或lineSentence構建
size:是指特徵向量的維度,默認爲100。大的size需要更多的訓練數據,但是效果會更好,推薦值爲幾十到幾百
min_count:可以對字典做截斷,詞頻少於min_count次數的單詞會被丟棄掉, 默認值爲5
調用Word2Vec模型對象的wv.most_similar方法查看與攝影含義最相近的詞
wv.most_similar方法有2個參數,第1個參數是要搜索的詞,第2個關鍵字參數topn數據類型爲正整數,是指需要列出多少個最相關的詞彙,默認爲10,即列出10個最相關的詞彙
wv.most_similar方法返回值的數據類型爲列表,列表中的每個元素的數據類型爲元組,元組有2個元素,第1個元素爲相關詞彙,第2個元素爲相關程度,數據類型爲浮點型
?
1
|
word2vec_model.wv.most_similar( '攝影' )
|
wv.most_similar方法使用positive和negative這2個關鍵字參數的簡單示例。查看女人+先生-男人的結果,代碼如下:
?
1
|
word2vec_model.most_similar(positive = [ '女人' , '先生' ], negative = [ '男人' ], topn = 1 )
|
查看兩個詞的相關性,如下圖所示:
?
1
2
|
word2vec_model.similarity( '男人' , '女人' )
word2vec_model.similarity( '攝影' , '攝像' )
|
保存Word2Vec模型爲word2vec_model.w2v文件,代碼如下:
?
1
|
word2vec_model.save( 'word2vec_model.w2v' )
|
4)特徵工程
對於每一篇文章,獲取文章的每一個分詞在word2vec模型的相關性向量。然後把一篇文章的所有分詞在word2vec模型中的相關性向量求和取平均數,即此篇文章在word2vec模型中的相關性向量(用一篇文章分詞向量的平均數作爲該文章在模型中的相關性向量)
實例化Word2Vec對象時,關鍵字參數size定義爲100,則相關性矩陣都爲100維
getVector函數獲取每個文章的詞向量,傳入2個參數,第1個參數是每篇文章分詞的結果,第2個參數是word2vec模型對象
每當完成1000篇文章詞向量轉換的時候,打印花費時間
最終將24000篇文章的詞向量賦值給變量X,即X爲特徵矩陣
對比文章轉換爲相關性向量的4種方法花費時間。爲了節省時間,只對比前5000篇文章轉換爲相關性向量的花費時間
4.1 ) 第1種方法,用for循環常規計算
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
def getVector_v1(cutWords, word2vec_model):
count = 0
article_vector = np.zeros( word2vec_model.layer1_size )
for cutWord in cutWords:
if cutWord in word2vec_model:
article_vector + = word2vec_model[cutWord]
count + = 1
return article_vector / count
startTime = time.time()
vector_list = []
i = 0
for cutWords in cutWords_list[: 5000 ]:
i + = 1
if i % 1000 = = 0 :
print ( '前%d篇文章形成詞向量花費%.2f秒' % (i, time.time() - startTime))
vector_list.append( getVector_v1(cutWords, word2vec_model) )
X = np.array(vector_list)
print ( 'Total Time You Need To Get X:%.2f秒' % (time.time() - startTime) )
|
4.2)第2種方法,用pandas的mean方法計算
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import time
import pandas as pd
import numpy as np
def getVector_v2(cutWords, word2vec_model):
vector_list = [ word2vec_model[k] for k in cutWords if k in word2vec_model]
vector_df = pd.DataFrame(vector_list)
cutWord_vector = vector_df.mean(axis = 0 ).values
return cutWord_vector
startTime = time.time()
vector_list = []
i = 0
for cutWords in cutWords_list[: 5000 ]:
i + = 1
if i % 1000 = = 0 :
print ( '前%d篇文章形成詞向量花費%.2f秒' % (i, time.time() - startTime))
vector_list.append( getVector_v2(cutWords, word2vec_model) )
X = np.array(vector_list)
print ( 'Total Time You Need To Get X:%.2f秒' % (time.time() - startTime) )
|
4.3)用numpy的mean方法計算
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import time
import pandas as pd
import numpy as np
def getVector_v2(cutWords, word2vec_model):
vector_list = [ word2vec_model[k] for k in cutWords if k in word2vec_model]
vector_df = pd.DataFrame(vector_list)
cutWord_vector = vector_df.mean(axis = 0 ).values
return cutWord_vector
startTime = time.time()
vector_list = []
i = 0
for cutWords in cutWords_list[: 5000 ]:
i + = 1
if i % 1000 = = 0 :
print ( '前%d篇文章形成詞向量花費%.2f秒' % (i, time.time() - startTime))
vector_list.append( getVector_v2(cutWords, word2vec_model) )
X = np.array(vector_list)
print ( 'Total Time You Need To Get X:%.2f秒' % (time.time() - startTime) )
|
4.4)第4種方法,用numpy的add、divide方法計算
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
import time
import numpy as np
import pandas as pd
def getVector_v4(cutWords, word2vec_model):
i = 0
index2word_set = set (word2vec_model.wv.index2word)
article_vector = np.zeros((word2vec_model.layer1_size))
for cutWord in cutWords:
if cutWord in index2word_set:
article_vector = np.add(article_vector, word2vec_model.wv[cutWord])
i + = 1
cutWord_vector = np.divide(article_vector, i)
return cutWord_vector
startTime = time.time()
vector_list = []
i = 0
for cutWords in cutWords_list[: 5000 ]:
i + = 1
if i % 1000 = = 0 :
print ( '前%d篇文章形成詞向量花費%.2f秒' % (i, time.time() - startTime))
vector_list.append( getVector_v4(cutWords, word2vec_model) )
X = np.array(vector_list)
print ( 'Total Time You Need To Get X:%.2f秒' % (time.time() - startTime) )
|
因爲形成特徵矩陣的花費時間較長,爲了避免以後重複花費時間,把特徵矩陣保存爲文件。使用ndarray對象的dump方法,需要1個參數,數據類型爲字符串,爲保存文件的文件名,加載數據也很方便,代碼如下(保存X之前要用方法四對所有cutWords_list元素進行處理,5000只是用來比較四種方法快慢):
?
1
2
3
|
X.dump( 'articles_vector.txt' )
#加載數據可以用下面的代碼
X = np.load( 'articles_vector.txt' )
|
5)模型訓練、模型評估
1)標籤編碼
調用sklearn.preprocessing庫的LabelEncoder方法對文章分類做標籤編碼
?
1
2
3
4
5
6
7
|
import pandas as pd
from sklearn.preprocessing import LabelEncoder
train_df = pd.read_csv( 'sohu_train.txt' , sep = '\t' , header = None )
train_df.columns = [ '分類' , '文章' ]
labelEncoder = LabelEncoder()
y = labelEncoder.fit_transform(train_df[ '分類' ])
|
2)LR模型
調用sklearn.linear_model庫的LogisticRegression方法實例化模型對象
調用sklearn.model_selection庫的train_test_split方法劃分訓練集和測試集
?
1
2
3
4
5
6
7
|
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
train_X, test_X, train_y, test_y = train_test_split(X, y, test_size = 0.2 , random_state = 0 )
logistic_model = LogisticRegression()
logistic_model.fit(train_X, train_y)
logistic_model.score(test_X, test_y)
|
3)保存模型
調用sklearn.externals庫中的joblib方法保存模型爲logistic.model文件
模型持久化官方文檔示例:http://sklearn.apachecn.org/cn/0.19.0/modules/model_persistence.html
?
1
2
3
4
5
|
from sklearn.externals import joblib
joblib.dump(logistic_model, 'logistic.model' )
#加載模型
logistic_model = joblib.load( 'logistic.model' )
|
4)交叉驗證
調用sklearn.model_selection庫的ShuffleSplit方法實例化交叉驗證對象
調用sklearn.model_selection庫的cross_val_score方法獲得交叉驗證每一次的得分
?
1
2
3
4
5
6
7
8
|
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import ShuffleSplit
from sklearn.model_selection import cross_val_score
cv_split = ShuffleSplit(n_splits = 5 , train_size = 0.7 , test_size = 0.2 )
logistic_model = LogisticRegression()
score_ndarray = cross_val_score(logistic_model, X, y, cv = cv_split)
print (score_ndarray)
print (score_ndarray.mean())
|
6)模型測試
調用sklearn.externals庫的joblib對象的load方法加載模型賦值給變量logistic_model
調用DataFrame對象的groupby方法對每個分類分組,從而每種文章類別的分類準確性
調用自定義的getVector方法將文章轉換爲相關性向量
自定義getVectorMatrix方法獲得測試集的特徵矩陣
調用StandardScaler對象的transform方法將預測標籤做標籤編碼,從而獲得預測目標值
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import pandas as pd
import numpy as np
from sklearn.externals import joblib
import jieba
def getVectorMatrix(article_series):
return np.array([getVector_v4(jieba.cut(k), word2vec_model) for k in article_series])
logistic_model = joblib.load( 'logistic.model' )
test_df = pd.read_csv( 'sohu_test.txt' , sep = '\t' , header = None )
test_df.columns = [ '分類' , '文章' ]
for name, group in test_df.groupby( '分類' ):
featureMatrix = getVectorMatrix(group[ '文章' ])
target = labelEncoder.transform(group[ '分類' ])
print (name, logistic_model.score(featureMatrix, target))
|
我們來看看各個分類的精確率和召回率:
?
1
2
3
4
5
6
7
|
from sklearn.metrics import classification_report
test_df = pd.read_csv( 'sohu_test.txt' , sep = '\t' , header = None )
test_df.columns = [ '分類' , '文章' ]
test_label = labelEncoder.transform(test_df[ '分類' ])
y_pred = logistic_model.predict( getVectorMatrix(test_df[ '文章' ]) )
print (labelEncoder.inverse_transform([[x] for x in range ( 12 )]))
print (classification_report(test_label, y_pred))
|
7)結論
word2vec模型應用的第1個小型項目,訓練集數據共有24000條,測試集數據共有12000條。
經過交叉驗證,模型平均得分爲0.78左右。
測試集的驗證效果中,體育、教育、健康、旅遊、汽車、科技、房地產這7個分類得分較高,即容易被正確分類。
女人、娛樂、新聞、文化、財經這5個分類得分較低,即難以被正確分類。
想要學習如何提高文檔分類的準確率,請查看我的另外一篇文章《基於jieba,TfidfVectorizer,LogisticRegression進行搜狐新聞文本分類》
8)致謝
本文參考簡書:https://www.jianshu.com/p/96b983784dae
感謝作者的詳細過程,再次感謝!
9)流程圖
10)感興趣的可以查看利用TfIdf進行向量化後的新聞文本分類,效果有一定的提升