1. 程式人生 > >文字分類的研究學習

文字分類的研究學習

  1 #!/usr/bin/env python
  2 #!-*- coding:utf-8 -*-
  3 #[email protected]:sjl
  4 
  5 import numpy as np
  6 from sklearn.naive_bayes import MultinomialNB, BernoulliNB
  7 from sklearn.datasets import fetch_20newsgroups
  8 from sklearn.feature_extraction.text import TfidfVectorizer
  9 from
sklearn.linear_model import RidgeClassifier 10 from sklearn.neighbors import KNeighborsClassifier 11 from sklearn.svm import SVC 12 from sklearn.ensemble import RandomForestClassifier 13 from sklearn.model_selection import GridSearchCV 14 from sklearn import metrics 15 from time import time
16 from pprint import pprint 17 import matplotlib.pyplot as plt 18 import matplotlib as mpl 19 20 21 def test_clf(clf): 22 print(u'分類器:', clf) 23 alpha_can = np.logspace(-3, 2, 10) 24 model = GridSearchCV(clf, param_grid={'alpha': alpha_can}, cv=5) 25 m = alpha_can.size
26 if hasattr(clf, 'alpha'): 27 model.set_params(param_grid={'alpha': alpha_can}) 28 m = alpha_can.size 29 if hasattr(clf, 'n_neighbors'): 30 neighbors_can = np.arange(1, 15) 31 model.set_params(param_grid={'n_neighbors': neighbors_can}) 32 m = neighbors_can.size 33 if hasattr(clf, 'C'): 34 C_can = np.logspace(1, 3, 3) 35 gamma_can = np.logspace(-3, 0, 3) 36 model.set_params(param_grid={'C':C_can, 'gamma':gamma_can}) 37 m = C_can.size * gamma_can.size 38 if hasattr(clf, 'max_depth'): 39 max_depth_can = np.arange(4, 10) 40 model.set_params(param_grid={'max_depth': max_depth_can}) 41 m = max_depth_can.size 42 t_start = time() 43 model.fit(x_train, y_train) 44 t_end = time() 45 t_train = (t_end - t_start) / (5*m) 46 print(u'5折交叉驗證的訓練時間為:%.3f秒/(5*%d)=%.3f秒' % ((t_end - t_start), m, t_train)) 47 print(u'最優超引數為:', model.best_params_) 48 t_start = time() 49 y_hat = model.predict(x_test) 50 t_end = time() 51 t_test = t_end - t_start 52 print(u'測試時間:%.3f秒' % t_test) 53 acc = metrics.accuracy_score(y_test, y_hat) 54 print(u'測試集準確率:%.2f%%' % (100 * acc)) 55 name = str(clf).split('(')[0] 56 print(name) 57 index = name.find('Classifier') 58 if index != -1: 59 name = name[:index] # 去掉末尾的Classifier 60 if name == 'SVC': 61 name = 'SVM' 62 return t_train, t_test, 1-acc, name 63 64 65 if __name__ == "__main__": 66 print(u'開始下載/載入資料...') 67 t_start = time() 68 # remove = ('headers', 'footers', 'quotes') 69 remove = () 70 categories = ('alt.atheism', 'talk.religion.misc', 'comp.graphics', 'sci.space') 71 # categories = None # 若分類所有類別,請注意記憶體是否夠用 72 data_train = fetch_20newsgroups(subset='train', categories=categories, shuffle=True, random_state=0, remove=remove) 73 data_test = fetch_20newsgroups(subset='test', categories=categories, shuffle=True, random_state=0, remove=remove) 74 t_end = time() 75 print(u'下載/載入資料完成,耗時%.3f秒' % (t_end - t_start)) 76 print(u'資料型別:', type(data_train)) 77 print(u'訓練集包含的文字數目:', len(data_train.data)) 78 print(u'測試集包含的文字數目:', len(data_test.data)) 79 print(u'訓練集和測試集使用的%d個類別的名稱:' % len(categories)) 80 categories = data_train.target_names 81 pprint(categories) 82 y_train = data_train.target 83 y_test = data_test.target 84 print(u' -- 前10個文字 -- ') 85 for i in np.arange(10): 86 print(u'文字%d(屬於類別 - %s):' % (i+1, categories[y_train[i]])) 87 print(data_train.data[i]) 88 print('\n\n') 89 vectorizer = TfidfVectorizer(input='content', stop_words='english', max_df=0.5, sublinear_tf=True) 90 x_train = vectorizer.fit_transform(data_train.data) # x_train是稀疏的,scipy.sparse.csr.csr_matrix 91 x_test = vectorizer.transform(data_test.data) 92 print(u'訓練集樣本個數:%d,特徵個數:%d' % x_train.shape) 93 print(u'停止詞:\n',) 94 pprint(vectorizer.get_stop_words()) 95 feature_names = np.asarray(vectorizer.get_feature_names()) 96 97 print(u'\n\n===================\n分類器的比較:\n') 98 clfs = (MultinomialNB(), # 0.87(0.017), 0.002, 90.39% 99 BernoulliNB(), # 1.592(0.032), 0.010, 88.54% 100 KNeighborsClassifier(), # 19.737(0.282), 0.208, 86.03% 101 RidgeClassifier(), # 25.6(0.512), 0.003, 89.73% 102 RandomForestClassifier(n_estimators=200), # 59.319(1.977), 0.248, 77.01% 103 SVC() # 236.59(5.258), 1.574, 90.10% 104 ) 105 result = [] 106 for clf in clfs: 107 a = test_clf(clf) 108 result.append(a) 109 print('\n') 110 result = np.array(result) 111 time_train, time_test, err, names = result.T 112 x = np.arange(len(time_train)) 113 mpl.rcParams['font.sans-serif'] = [u'simHei'] 114 mpl.rcParams['axes.unicode_minus'] = False 115 plt.figure(figsize=(10, 7), facecolor='w') 116 ax = plt.axes() 117 b1 = ax.bar(x, err, width=0.25, color='#77E0A0') 118 ax_t = ax.twinx() 119 b2 = ax_t.bar(x+0.25, time_train, width=0.25, color='#FFA0A0') 120 b3 = ax_t.bar(x+0.5, time_test, width=0.25, color='#FF8080') 121 plt.xticks(x+0.5, names, fontsize=10) 122 leg = plt.legend([b1[0], b2[0], b3[0]], (u'錯誤率', u'訓練時間', u'測試時間'), loc='upper left', shadow=True) 123 # for lt in leg.get_texts(): 124 # lt.set_fontsize(14) 125 plt.title(u'新聞組文字資料不同分類器間的比較', fontsize=18) 126 plt.xlabel(u'分類器名稱') 127 plt.grid(True) 128 plt.tight_layout(2) 129 plt.show()

這個案例是一個新聞標題分類的案例,NLPCC 2017 Shared Task也有一個類似的案例。因此我們先拿這個下手了。整個過程概括起來分為以下幾步:

  1. 資料採集
  2. 特徵提取
  3. 模型訓練
  4. 模型評估

接下來我們對這4個部分的程式碼進行詳細的講解。

2.1 資料採集

從上面的程式碼中,我們可以看到獲取資料很簡單:

 1 print(u'開始下載/載入資料...')
 2     t_start = time()
 3     # remove = ('headers', 'footers', 'quotes')
 4     remove = ()
 5     categories = ('alt.atheism', 'talk.religion.misc', 'comp.graphics', 'sci.space')
 6     # categories = None     # 若分類所有類別,請注意記憶體是否夠用
 7     data_train = fetch_20newsgroups(subset='train', categories=categories, shuffle=True, random_state=0, remove=remove)
 8     data_test = fetch_20newsgroups(subset='test', categories=categories, shuffle=True, random_state=0, remove=remove)
 9     t_end = time()
10     print(u'下載/載入資料完成,耗時%.3f秒' % (t_end - t_start))
11     print(u'資料型別:', type(data_train))
12     print(u'訓練集包含的文字數目:', len(data_train.data))
13     print(u'測試集包含的文字數目:', len(data_test.data))
14     print(u'訓練集和測試集使用的%d個類別的名稱:' % len(categories))
15     categories = data_train.target_names
16     pprint(categories)
17     y_train = data_train.target
18     y_test = data_test.target
19     print(u' -- 前10個文字 -- ')
20     for i in np.arange(10):
21         print(u'文字%d(屬於類別 - %s):' % (i+1, categories[y_train[i]]))
22         print(data_train.data[i])
23         print('\n\n')
24     vectorizer = TfidfVectorizer(input='content', stop_words='english', max_df=0.5, sublinear_tf=True)

這裡最重要的就是這個fetch_20newsgroups方法了,下面我們來詳細講解:

##函式原型是這樣的。 '''
fetch_20newsgroups(data_home=None,subset='train',categories=None,shuffle=True,random_state=42,remove=(),download_if_missing=True)
''' '''
data_home指的是資料集的地址,如果預設的話,所有的資料都會在'~/scikit_learn_data'資料夾下.

subset就是train,test,all三種可選,分別對應訓練集、測試集和所有樣本。

categories:是指類別,如果指定類別,就會只提取出目標類,如果是預設,則是提取所有類別出來。

shuffle:是否打亂樣本順序,如果是相互獨立的話。

random_state:打亂順序的隨機種子

remove:是一個元組,用來去除一些停用詞的,例如標題引用之類的。

download_if_missing: 如果資料缺失,是否去下載。
'''

另外的一種解釋是:

fetch_20newsgroups的作用是載入檔名,載入20個新聞群組資料集中的資料
引數:data_home:可選引數,預設值為:None
指定一個電腦中的路徑來儲存載入的資料。如果選擇預設,那所有的scikit-learn資料都儲存在'~/scikit_learn_data'這個子資料夾中
      subset:'train'或者'test','all',可選引數
選擇載入得到的資料集用來做訓練還是做測試,或者是兩者都選擇,可以隨使用者需要來選擇
      categories:空集,或者是字串集合,或者是unicode碼
      shuffle:bool布林型別,可選引數
是否需要打亂資料:這一引數對於一些需要讓假設樣本資料具有獨立同分布的模型來說至關重要,如隨機梯度下降
      random_state:numpy隨機數產生器,或者是種子整數
主要是用來清洗資料
      remove:元組
包含標頭檔案(‘headers’,‘footers’,'‘quotes’)的所有子集。都是從新聞群組帖子中被檢測或者是移除的各種各樣的文字,防止分類器在利用複雜資料特徵屬性進行分類過程中過擬合
'headers'去除新聞的頭部資料, 'footers'去除新聞位置最後類似於簽名區域的一整塊區域,'quotes'移除引用其他新聞帖子的行
'headers'遵從一個精確的標準;其他的過濾器不一定一直正確
      download_if_missing:可選引數,預設值是:真(True)
如果是Flase, 資料不是本地可獲取的就會引起一個IOError,而不是嘗試著從資源網站下載。

2.2 特徵提取

資料採集完成以後,就要開始提取特徵了,我們這裡使用的是TFIDF特徵。

1 vectorizer = TfidfVectorizer(input='content', stop_words='english', max_df=0.5, sublinear_tf=True)
2     x_train = vectorizer.fit_transform(data_train.data)  # x_train是稀疏的,scipy.sparse.csr.csr_matrix
3     x_test = vectorizer.transform(data_test.data)
4     print(u'訓練集樣本個數:%d,特徵個數:%d' % x_train.shape)
5     print(u'停止詞:\n',)
6     pprint(vectorizer.get_stop_words())
7     feature_names = np.asarray(vectorizer.get_feature_names())

 這裡最重要的是TfidfVectorizer函式,其解釋如下:

關於引數:

input:string{'filename', 'file', 'content'}
    如果是'filename',序列作為引數傳遞給擬合器,預計為檔名列表,這需要讀取原始內容進行分析
    如果是'file',序列專案必須有一個”read“的方法(類似檔案的物件),被呼叫作為獲取記憶體中的位元組數
    否則,輸入預計為序列串,或位元組資料項都預計可直接進行分析。
encoding:string, ‘utf-8’by default
    如果給出要解析的位元組或檔案,此編碼將用於解碼
decode_error: {'strict', 'ignore', 'replace'}
    如果一個給出的位元組序列包含的字元不是給定的編碼,指示應該如何去做。預設情況下,它是'strict',這意味著的UnicodeDecodeError將提高,其他值是'ignore'和'replace'
strip_accents: {'ascii', 'unicode', None}
    在預處理步驟中去除編碼規則(accents),”ASCII碼“是一種快速的方法,僅適用於有一個直接的ASCII字元對映,"unicode"是一個稍慢一些的方法,None(預設)什麼都不做
analyzer:string,{'word', 'char'} or callable
    定義特徵為詞(word)或n-gram字元,如果傳遞給它的呼叫被用於抽取未處理輸入原始檔的特徵序列
preprocessor:callable or None(default)
    當保留令牌和”n-gram“生成步驟時,覆蓋預處理(字串變換)的階段
tokenizer:callable or None(default)
    當保留預處理和n-gram生成步驟時,覆蓋字串令牌步驟
ngram_range: tuple(min_n, max_n)
    要提取的n-gram的n-values的下限和上限範圍,在min_n <= n <= max_n區間的n的全部值
stop_words:string {'english'}, list, or None(default)
    如果未english,用於英語內建的停用詞列表
    如果未list,該列表被假定為包含停用詞,列表中的所有詞都將從令牌中刪除
    如果None,不使用停用詞。max_df可以被設定為範圍[0.7, 1.0)的值,基於內部預料詞頻來自動檢測和過濾停用詞
lowercase:boolean, default True
    在令牌標記前轉換所有的字元為小寫
token_pattern:string
    正則表示式顯示了”token“的構成,僅當analyzer == ‘word’時才被使用。兩個或多個字母數字字元的正則表示式(標點符號完全被忽略,始終被視為一個標記分隔符)。
max_df: float in range [0.0, 1.0] or int, optional, 1.0 by default
    當構建詞彙表時,嚴格忽略高於給出閾值的文件頻率的詞條,語料指定的停用詞。如果是浮點值,該引數代表文件的比例,整型絕對計數值,如果詞彙表不為None,此引數被忽略。
min_df:float in range [0.0, 1.0] or int, optional, 1.0 by default
當構建詞彙表時,嚴格忽略低於給出閾值的文件頻率的詞條,語料指定的停用詞。如果是浮點值,該引數代表文件的比例,整型絕對計數值,如果詞彙表不為None,此引數被忽略。
max_features: optional, None by default
    如果不為None,構建一個詞彙表,僅考慮max_features--按語料詞頻排序,如果詞彙表不為None,這個引數被忽略
vocabulary:Mapping or iterable, optional
    也是一個對映(Map)(例如,字典),其中鍵是詞條而值是在特徵矩陣中索引,或詞條中的迭代器。如果沒有給出,詞彙表被確定來自輸入檔案。在對映中索引不能有重複,並且不能在0到最大索引值之間有間斷。
binary:boolean, False by default
    如果未True,所有非零計數被設定為1,這對於離散概率模型是有用的,建立二元事件模型,而不是整型計數
dtype:type, optional
    通過fit_transform()或transform()返回矩陣的型別
norm:'l1', 'l2', or None,optional
    範數用於標準化詞條向量。None為不歸一化
use_idf:boolean, optional
    啟動inverse-document-frequency重新計算權重
smooth_idf:boolean,optional
    通過加1到文件頻率平滑idf權重,為防止除零,加入一個額外的文件
sublinear_tf:boolean, optional
    應用線性縮放TF,例如,使用1+log(tf)覆蓋tf

2.3 模型訓練

這裡選用多種模型進行訓練

 1 clfs = (MultinomialNB(),                # 0.87(0.017), 0.002, 90.39%
 2             BernoulliNB(),                  # 1.592(0.032), 0.010, 88.54%
 3             KNeighborsClassifier(),         # 19.737(0.282), 0.208, 86.03%
 4             RidgeClassifier(),              # 25.6(0.512), 0.003, 89.73%
 5             RandomForestClassifier(n_estimators=200),   # 59.319(1.977), 0.248, 77.01%
 6             SVC()                           # 236.59(5.258), 1.574, 90.10%
 7             )
 8     result = []
 9     for clf in clfs:
10         a = test_clf(clf)
11         result.append(a)
12         print('\n')
13     result = np.array(result)
14     time_train, time_test, err, names = result.T
15     x = np.arange(len(time_train))
 
 
 
 1 def test_clf(clf):
 2     print(u'分類器:', clf)
 3     alpha_can = np.logspace(-3, 2, 10)
 4     model = GridSearchCV(clf, param_grid={'alpha': alpha_can}, cv=5)
 5     m = alpha_can.size
 6     if hasattr(clf, 'alpha'):
 7         model.set_params(param_grid={'alpha': alpha_can})
 8         m = alpha_can.size
 9     if hasattr(clf, 'n_neighbors'):
10         neighbors_can = np.arange(1, 15)
11         model.set_params(param_grid={'n_neighbors': neighbors_can})
12         m = neighbors_can.size
13     if hasattr(clf, 'C'):
14         C_can = np.logspace(1, 3, 3)
15         gamma_can = np.logspace(-3, 0, 3)
16         model.set_params(param_grid={'C':C_can, 'gamma':gamma_can})
17         m = C_can.size * gamma_can.size
18     if hasattr(clf, 'max_depth'):
19         max_depth_can = np.arange(4, 10)
20         model.set_params(param_grid={'max_depth': max_depth_can})
21         m = max_depth_can.size
22     t_start = time()
23     model.fit(x_train, y_train)
24     t_end = time()
25     t_train = (t_end - t_start) / (5*m)
26     print(u'5折交叉驗證的訓練時間為:%.3f秒/(5*%d)=%.3f秒' % ((t_end - t_start), m, t_train))
27     print(u'最優超引數為:', model.best_params_)
28     t_start = time()
29     y_hat = model.predict(x_test)
30     t_end = time()
31     t_test = t_end - t_start
32     print(u'測試時間:%.3f秒' % t_test)
33     acc = metrics.accuracy_score(y_test, y_hat)
34     print(u'測試集準確率:%.2f%%' % (100 * acc))
35     name = str(clf).split('(')[0]
36     print(name)
37     index = name.find('Classifier')
38     if index != -1:
39         name = name[:index]     # 去掉末尾的Classifier
40     if name == 'SVC':
41         name = 'SVM'
42     return t_train, t_test, 1-acc, name

2.4模型的分析評估

 1 mpl.rcParams['font.sans-serif'] = [u'simHei']
 2     mpl.rcParams['axes.unicode_minus'] = False
 3     plt.figure(figsize=(10, 7), facecolor='w')
 4     ax = plt.axes()
 5     b1 = ax.bar(x, err, width=0.25, color='#77E0A0')
 6     ax_t = ax.twinx()
 7     b2 = ax_t.bar(x+0.25, time_train, width=0.25, color='#FFA0A0')
 8     b3 = ax_t.bar(x+0.5, time_test, width=0.25, color='#FF8080')
 9     plt.xticks(x+0.5, names, fontsize=10)
10     leg = plt.legend([b1[0], b2[0], b3[0]], (u'錯誤率', u'訓練時間', u'測試時間'), loc='upper left', shadow=True)
11     # for lt in leg.get_texts():
12     #     lt.set_fontsize(14)
13     plt.title(u'新聞組文字資料不同分類器間的比較', fontsize=18)
14     plt.xlabel(u'分類器名稱')
15     plt.grid(True)
16     plt.tight_layout(2)
17     plt.show()