用半監(jiān)督算法做文本分類(sklearn)

作者:煉己者

歡迎大家訪問 我的簡書 以及 我的博客
本博客所有內(nèi)容以學習、研究和分享為主,如需轉(zhuǎn)載,請聯(lián)系本人,標明作者和出處,并且是非商業(yè)用途,謝謝!


摘要:本文主要講述了用半監(jiān)督算法做文本分類(二分類),主要借鑒了sklearn的一個例子——用半監(jiān)督算法做數(shù)字識別。先說結(jié)論,這是一個失敗的例子,訓練到第15000條就不行了,就報錯了。如果你的數(shù)據(jù)量不是很大的話,可以操作一下。這里面有很多值得學習的地方,尤其是關(guān)于文本的預處理。后續(xù)還會更新,把這條路打通。


一. 操作流程

  • 一共100萬條數(shù)據(jù),標注7000條,剩下的全部標注為-1
  • 對文本進行預處理(jieba分詞,去掉停用詞等操作)
  • 用gensim庫將處理好的文本轉(zhuǎn)為TFIDF向量
  • 利用scipy庫轉(zhuǎn)換TFIDF向量的格式,使其可以被sklearn庫的算法包訓練
  • 把預處理的數(shù)據(jù)輸入到模型中進行訓練
  • 得到前500個不確定的數(shù)據(jù),對其進行人工標注,然后再添加回訓練集
  • 重復以上過程,直到你的分類器變得好,符合你的要求

二. 正文

前面的jieba分詞,去停用詞的操作我就贅述了,比較容易。在這里就分享一下怎么把gensim訓練的TFIDF向量轉(zhuǎn)為sklearn庫算法包所要求的格式吧。

說到這里,大家肯定會問,干啥這么麻煩,直接調(diào)用sklearn計算TFIDF不就完了。原因很簡單,一開始我也是這么干的,然后報了內(nèi)存錯誤,當時我猜測是因為向量的維度太大了,因為sklearn計算出來的TFIDF向量每句話的維度達到了30000多維,所以我打算借用gensim來訓練TFIDF向量,它有個好處,就是可以指定維度的大小。

那么如何計算TFIDF向量呢?
大家也可以看這篇文章——用不同的方法計算TFIDF值

1. 用gensim訓練TFIDF向量,并且保存起來,省的你下次用的時候還要再跑一遍代碼

from gensim import corpora
dictionary = corpora.Dictionary(word_list)
new_corpus = [dictionary.doc2bow(text) for text in word_list]

from gensim import models
tfidf = models.TfidfModel(new_corpus)
tfidf.save('my_model.tfidf')

2.載入模型,訓練你的數(shù)據(jù),得到TFIDF向量

tfidf = models.TfidfModel.load('my_model.tfidf')
tfidf_vec = []
for i in range(len(words)):
    string = words[i]
    string_bow = dictionary.doc2bow(string.split())
    string_tfidf = tfidf[string_bow]
    tfidf_vec.append(string_tfidf)

此時得到的TFIDF向量是這樣的

  • 元組的形式——(數(shù)據(jù)的id,TFIDF向量)
  • 這樣的格式sklearn的算法包是無法訓練的,怎么辦?
[[(0, 0.44219328927835233),
  (1, 0.5488488134902755),
  (2, 0.28062764931589196),
  (3, 0.5488488134902755),
  (4, 0.3510600763648036)],
 [(5, 0.2952063480959091),
  (6, 0.3085138762011414),
  (7, 0.269806482343891),
  (8, 0.21686460370108193),
  (9, 0.4621642239026475),
  (10, 0.5515758504022944),
  (11, 0.4242816486479956)],
......]

3.利用lsi模型指定維度

lsi_model = models.LsiModel(corpus = tfidf_vec,id2word = dictionary,num_topics=2)
lsi_vec = []
for i in range(len(words)):
    string = words[i]
    string_bow = dictionary.doc2bow(string.split())
    string_lsi = lsi_model[string_bow]
    lsi_vec.append(string_lsi)

此時的TFIDF向量是這樣的

[[(0, 9.98164139346566e-06), (1, 0.00017488533996265734)],
 [(0, 0.004624808817003378), (1, 0.0052712355563472625)],
 [(0, 0.005992863818284904), (1, 0.0028891269605347066)],
 [(0, 0.008813713819377964), (1, 0.004300294830187425)],
 [(0, 0.0010709978891676652), (1, 0.004264312831567625)],
 [(0, 0.005647948200006063), (1, 0.005816420698368305)],
 [(0, 1.1749284917071102e-05), (1, 0.0003525210498926822)],
 [(0, 0.05046596444596279), (1, 0.03750969796637345)],
 [(0, 0.0007876011346475033), (1, 0.008538972615602887)],
......]

4.通過scipy模塊將數(shù)據(jù)處理為sklearn可訓練的格式

from scipy.sparse import csr_matrix
data = []
rows = []
cols = []
line_count = 0
for line in lsi_vec:
    for elem in line:
        rows.append(line_count)
        cols.append(elem[0])
        data.append(elem[1])
    line_count += 1
lsi_sparse_matrix = csr_matrix((data,(rows,cols))) # 稀疏向量
lsi_matrix = lsi_sparse_matrix.toarray() # 密集向量

lsi_matrix如下所示

  • 這便是符合sklearn的格式要求了
Out[53]:
array([[9.98164139e-06, 1.74885340e-04],
       [4.62480882e-03, 5.27123556e-03],
       [5.99286382e-03, 2.88912696e-03],
       ...,
       [1.85861559e-02, 3.24888917e-01],
       [8.07737902e-04, 5.45659458e-03],
       [2.61926460e-03, 2.30210522e-02]])

5.調(diào)用sklearn的半監(jiān)督算法對數(shù)據(jù)進行訓練

  • 通過以下代碼便可以得到前2000條標簽最不確定的數(shù)據(jù)的索引,然后根據(jù)索引找到對應的數(shù)據(jù),就可以對其重新人工標注了
  • 把標好的數(shù)據(jù)再加回去,循環(huán)往復,直到你滿意為止
  • 當然這里面你得自己標注1000條數(shù)據(jù),然后再把它們賦值為-1,假裝不知道,等訓練完,得到預測的結(jié)果,再與你自己標注的真實值進行比對計算,就可以知道效果的好壞了
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
from sklearn.semi_supervised import label_propagation

y = list(result.label.values)
from scipy.sparse.csgraph import *

n_total_samples = len(y) # 1571794
n_labeled_points = 7804 # 標注好的數(shù)據(jù)共10條,只訓練10個帶標簽的數(shù)據(jù)模型
unlabeled_indices = np.arange(n_total_samples)[n_labeled_points:] # 未標注的數(shù)據(jù)

lp_model = label_propagation.LabelSpreading() # 訓練模型
lp_model.fit(lsi_matrix,y)
    
predicted_labels = lp_model.transduction_[unlabeled_indices] # 預測的標簽
    
    # 計算被轉(zhuǎn)換的標簽的分布的熵
    # lp_model.label_distributions_ : array,shape=[n_samples,n_classes]
    # Categorical distribution for each item
    
pred_entropies = stats.distributions.entropy(
lp_model.label_distributions_.T)
    
    # 選擇分類器最不確定的前2000位數(shù)字的索引
uncertainty_index = np.argsort(pred_entropies)[::1]
uncertainty_index = uncertainty_index[
    np.in1d(uncertainty_index,unlabeled_indices)][:2000] 

print(uncertainty_index)

三. 結(jié)果與討論

我最后沒有繼續(xù)往下做了,因為我的數(shù)據(jù)量很大,只能訓練這么點數(shù)據(jù)沒有意義,以上便是我的思路,希望會對大家有所幫助,后續(xù)我會更新新的方法來操作這個事情


以下是我所有文章的目錄,大家如果感興趣,也可以前往查看
??戳右邊:打開它,也許會看到很多對你有幫助的文章

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

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

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