python資料分析:內容資料化運營(下)——基於多項式貝葉斯增量學習分類文字
案例背景及資料
見上一篇
案例實現
匯入模組
import re import tarfile import os import numpy as np from bs4 import BeautifulSoup from sklearn.feature_extraction.text import HashingVectorizer # 文字轉稀疏矩陣 from sklearn.naive_bayes import MultinomialNB #貝葉斯分類器 from sklearn.metrics import accuracy_score # 分類評估
解壓檔案
# 解壓縮檔案
if not os.path.exists('./news_data'): # 如果不存在資料目錄,則先解壓資料檔案
print ('extract data from news_data.tar.gz...')
tar = tarfile.open('news_data.tar.gz') # 開啟tar.gz壓縮包物件
names = tar.getnames() # 獲得壓縮包內的每個檔案物件的名稱
for name in names: # 迴圈讀出每個檔案
tar.extract(name, path= './') # 將檔案解壓到指定目錄
tar.close() # 關閉壓縮包物件
# 定義物件
all_content = [] # 列表,用於儲存所有訓練集的文字內容
all_label = [] # 列表,用於儲存所有訓練集的標籤
score_list = [] # 列表,用於儲存每次交叉檢驗得分
pre_list = [] # 列表,用於儲存每次增量計算後的預測標籤
unique_list = ['sports', 'house', 'news'] # 標籤唯一值列表
print ('unique label:', unique_list)
model_nb = MultinomialNB( ) # 建立MultinomialNB模型物件
unique label: [‘sports’, ‘house’, ‘news’]
測試資料預處理
# 全形轉半形
def str_convert(content):
'''
將內容中的全形字元,包含英文字母、數字鍵、符號等轉換為半形字元
:param content: 要轉換的字串內容
:return: 轉換後的半形字串
'''
new_str = '' # 新字串
for each_char in content: # 迴圈讀取每個字元
code_num = ord(each_char) # 讀取字元的ASCII值或Unicode值
if code_num == 12288: # 全形空格直接轉換
code_num = 32
elif (code_num >= 65281 and code_num <= 65374): # 全形字元(除空格)根據關係轉化
code_num -= 65248
new_str += chr(code_num)
return new_str
# 解析檔案內容
def data_parse(data):
'''
從原始檔案中解析出文字內容和標籤資料
:param data: 包含程式碼的原始內容
:return: 以列表形式返回文字中的所有內容和對應標籤
'''
raw_code = BeautifulSoup(data, "lxml") # 建立BeautifulSoup物件
doc_code = raw_code.find_all('doc') # 從包含文字的程式碼塊中找到doc標籤
content_list = [] # 建立空列表,用來儲存每個content標籤的內容
label_list = [] # 建立空列表,用來儲存每個content對應的label的內容
for each_doc in doc_code: # 迴圈讀出每個doc標籤
if len(each_doc) > 0: # 如果dco標籤的內容不為空
content_code = each_doc.find('content') # 從包含文字的程式碼塊中找到doc標籤
raw_content = content_code.text # 獲取原始內容字串
convert_content = str_convert(raw_content) # 將全形轉換為半形
content_list.append(convert_content) # 將content文字內容加入列表
label_code = each_doc.find('url') # 從包含文字的程式碼塊中找到url標籤
label_content = label_code.text # 獲取url資訊
label = re.split('[/|.]', label_content)[2] # 將URL做分割並提取子域名
label_list.append(label) # 將子域名加入列表
return content_list, label_list
# 交叉檢驗和預測資料集預處理
# 交叉檢驗集
with open('test_sets.txt', encoding='utf-8') as f:
test_data = f.read()
test_content, test_label = data_parse(test_data) # 解析文字內容和標籤
文字向量化
# 文字向量化
def word_to_vector(data):
'''
將訓練集文字資料轉換為稀疏矩陣
:param data: 輸入的文字列表
:return: 稀疏矩陣
'''
model_vector = HashingVectorizer(non_negative=True) # 建立HashingVectorizer物件
vector_data = model_vector.fit_transform(data) # 將輸入文字轉化為稀疏矩陣
return vector_data
# 標籤向量化
def label_to_vector(label, unique_list):
'''
將文字標籤轉換為向量標籤
:param label: 文字列表
:unique_list: 唯一值列表
:return: 向量標籤列表
'''
for each_index, each_data in enumerate(label): # 迴圈讀取每個標籤的索引及對應值
label[each_index] = unique_list.index(each_data) # 將值替換為其索引
return label
# 將文字內容向量化
test_data_vector = word_to_vector(test_content)
# 將標籤內容向量化
test_label_vecotr = label_to_vector(test_label, unique_list)
預測集準備
# 預測集轉向量
with open('article.txt', encoding='utf-8') as f:
new_data = f.read()
new_content, new_label = data_parse(new_data) # 解析文字內容和標籤
new_data_vector = word_to_vector(new_content) # 將文字內容向量化
增量學習輸出準確率
# 交叉檢驗
def cross_val(model_object, data, label):
'''
通過交叉檢驗計算每次增量學習後的模型得分
:param model_object: 每次增量學習後的模型物件
:param data: 訓練資料集
:param label: 訓練資料集對應的標籤
:return: 交叉檢驗得分
'''
predict_label = model_object.predict(data) # 預測測試集標籤
score_tmp = round(accuracy_score(label, predict_label), 4) # 計算預測準確率
return score_tmp
# 增量學習
print ('{:*^60}'.format('incremental learning...'))
for root, dirs, files in os.walk('./news_data'): # 分別讀取遍歷目錄下的根目錄、子目錄和檔案列表
for file in files: # 讀取每個檔案
file_name = os.path.join(root, file) # 將目錄路徑與檔名合併為帶有完整路徑的檔名
print ('training file: %s' % file)
# 增量訓練
with open(file_name, encoding='utf-8') as f: # 以只讀方式開啟檔案
data = f.read() # 讀取檔案內容
content, label = data_parse(data) # 解析文字內容和標籤
data_vector = word_to_vector(content) # 將文字內容向量化
label_vecotr = label_to_vector(label, unique_list) # 將標籤內容向量化
model_nb.partial_fit(data_vector, label_vecotr, classes=np.array([0, 1, 2])) # 增量學習
# 交叉檢驗
score_list.append(cross_val(model_nb, test_data_vector, test_label_vecotr)) # 將交叉檢驗結果存入列表
# 增量預測
predict_y = model_nb.predict(new_data_vector) # 預測內容標籤
pre_list.append(predict_y.tolist())
print ('{:*^60}'.format('cross validation score:'))
print (score_list) # 列印輸出每次交叉檢驗得分
print ('{:*^60}'.format('predicted labels:'))
print (pre_list) # 列印輸出每次預測標籤索引值
print ('{:*^60}'.format('true labels:'))
print (new_label) # 列印輸出正確的標籤值
結果:
******************incremental learning...*******************
training file: news.sohunews.010806.txt
training file: news.sohunews.020806.txt
training file: news.sohunews.030806.txt
training file: news.sohunews.040806.txt
training file: news.sohunews.050806.txt
training file: news.sohunews.060806.txt
training file: news.sohunews.070806.txt
training file: news.sohunews.080806.txt
training file: news.sohunews.110806.txt
training file: news.sohunews.120806.txt
******************cross validation score:*******************
[0.8707, 0.9013, 0.9067, 0.9088, 0.9099, 0.912, 0.9147, 0.9142, 0.9147, 0.9158]
*********************predicted labels:**********************
[[0], [0], [0], [0], [0], [0], [0], [0], [0], [0]]
************************true labels:************************
['sports']
視覺化等分趨勢
import matplotlib.pyplot as plt
%matplotlib inline
## 視覺化準確率
plt.figure(figsize=(8, 5))
plt.style.use('ggplot')
plt.plot(score_list, marker='o', linestyle=':', c='g')
plt.savefig('xx.png')
plt.show()
結論
從cross validation score得到的結果看,隨著每次資料量的增加,交叉檢驗的得分趨勢不斷提高,這也證實了增量學習本身對於準確率的提升貢獻,但在第8次訓練時,總體得分從0.9147下降到0.9142,其中可能包含以下原因:
- 第8次的資料集本身是有誤的(或者不準確的),導致檢驗結果下降。
- 之前的資料中可能存在有誤資訊,而第8次本身的資訊是準確的,導致第8次的結果略有下降。
從10次的檢驗結果來看,整體趨勢的增長是良好的。
對新資料集的預測時,無論哪個階段都能準確的預測出其類別歸屬('sports’對應的索引值為0)。
補充
關於增量學習的價值
增量學習的優點並不是通過演算法或模型本身來提供較高的準確率,而是通過不斷有新資料的加入來提高模型的準確率,因此在一定意義上,模型本身的選擇以及調參等動作都變得“不那麼重要”,因為只要資料足夠大,即使再差的模型也會由於掌握了足夠的多的資料規律而更加精準的預測新樣本,這是增量學習的關鍵所在。
當然,增量學習還能實現在物理硬體限制(尤其是記憶體)及其他軟硬體不作任何優化的條件下,對於海量資料的訓練的支援,是一種非常好的解決大資料量計算問題的有效方法。
關於本案例中涉及到的方法
訓練集的文字跟預測集的文字不一致,會導致訓練時的中間過程或分類模型無法適用於預測過程,這點在文字分類時非常常見。案例中使用的HashingVectorizer能將詞語出現的頻率對映到固定維度空間,即使出現新的詞語也不會影響固定維度空間的模式,因此非常適合預測應用時新詞較多的場景。
HashingVectorizer本身能提供壓縮後的稀疏矩陣,其本身就能大量降低對於系統記憶體的佔用,非常適合大資料集下的計算和處理。
貝葉斯分類器廣泛應用於文字分類領域,其效果較好。除了本文提到的MultinomialNB外,還包括BernoulliNB和GaussianNB兩種方法,他們各自有其適用場景。
參考:
《python資料分析與資料化運營》 宋天龍