文本聚類是將一個個文檔由原有的自然語言文字信息轉(zhuǎn)化成數(shù)學信息睦裳,以高維空間點的形式展現(xiàn)出來乓序,通過計算哪些點距離比較近,從而將那些點聚成一個簇裸准,簇的中心叫做簇心。一個好的聚類要保證簇內(nèi)點的距離盡量的近赔硫,但簇與簇之間的點要盡量的遠炒俱。
如下圖,以 K、M权悟、N 三個點分別為聚類的簇心砸王,將結(jié)果聚為三類,使得簇內(nèi)點的距離盡量的近峦阁,但簇與簇之間的點盡量的遠谦铃。
開發(fā)環(huán)境,我們選擇::
- MAC 系統(tǒng)
- Python 3.6
- Jupyter Notebook
本文繼續(xù)沿用上篇文本分類中的語料來進行文本無監(jiān)督聚類操作拇派。
整個過程分為以下幾個步驟:
- 語料加載
- 分詞
- 去停用詞
- 抽取詞向量特征
- 實戰(zhàn) TF-IDF 的中文文本 K-means 聚類
- 實戰(zhàn) word2Vec 的中文文本 K-means 聚類
下面開始項目實戰(zhàn)荷辕。
1. 首先進行語料加載,在這之前件豌,引入所需要的 Python 依賴包,并將全部語料和停用詞字典讀入內(nèi)存中控嗜。
第一步茧彤,引入依賴庫,有隨機數(shù)庫疆栏、jieba 分詞曾掂、pandas 庫等:
import random
import jieba
import pandas as pd
import numpy as np
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_extraction.text import TfidfVectorizer
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
import gensim
from gensim.models import Word2Vec
from sklearn.preprocessing import scale
import multiprocessing
第二步,加載停用詞字典壁顶,停用詞詞典為 stopwords.txt 文件珠洗,可以根據(jù)場景自己在該文本里面添加要去除的詞(比如冠詞、人稱若专、數(shù)字等特定詞):
#加載停用詞
stopwords=pd.read_csv('stopwords.txt',index_col=False,quoting=3,sep="\t",names=['stopword'], encoding='utf-8')
stopwords=stopwords['stopword'].values
第三步许蓖,加載語料,語料是4個已經(jīng)分好類的 csv 文件调衰,直接用 pandas 加載即可膊爪,加載之后可以首先刪除 nan 行,并提取要分詞的 content 列轉(zhuǎn)換為 list 列表:
#加載語料
laogong_df = pd.read_csv('beilaogongda.csv', encoding='utf-8', sep=',')
laopo_df = pd.read_csv('beilaogongda.csv', encoding='utf-8', sep=',')
erzi_df = pd.read_csv('beierzida.csv', encoding='utf-8', sep=',')
nver_df = pd.read_csv('beinverda.csv', encoding='utf-8', sep=',')
#刪除語料的nan行
laogong_df.dropna(inplace=True)
laopo_df.dropna(inplace=True)
erzi_df.dropna(inplace=True)
nver_df.dropna(inplace=True)
#轉(zhuǎn)換
laogong = laogong_df.segment.values.tolist()
laopo = laopo_df.segment.values.tolist()
erzi = erzi_df.segment.values.tolist()
nver = nver_df.segment.values.tolist()
- 分詞和去停用詞嚎莉。
第一步米酬,定義分詞、去停用詞的函數(shù)趋箩,函數(shù)包含兩個參數(shù):content_lines 參數(shù)為語料列表赃额;sentences 參數(shù)為預先定義的 list,用來存儲分詞后的結(jié)果:
#定義分詞函數(shù)preprocess_text
#參數(shù)content_lines即為上面轉(zhuǎn)換的list
#參數(shù)sentences是定義的空list叫确,用來儲存分詞后的數(shù)據(jù)
def preprocess_text(content_lines, sentences):
for line in content_lines:
try:
segs=jieba.lcut(line)
segs = [v for v in segs if not str(v).isdigit()]#去數(shù)字
segs = list(filter(lambda x:x.strip(), segs)) #去左右空格
segs = list(filter(lambda x:len(x)>1, segs)) #長度為1的字符
segs = list(filter(lambda x:x not in stopwords, segs)) #去掉停用詞
sentences.append(" ".join(segs))
except Exception:
print(line)
continue
第二步跳芳,調(diào)用函數(shù)、生成訓練數(shù)據(jù)启妹,根據(jù)我提供的司法語料數(shù)據(jù)筛严,分為報警人被老公打,報警人被老婆打,報警人被兒子打桨啃,報警人被女兒打车胡,具體如下:
sentences = []
preprocess_text(laogong, sentences)
preprocess_text(laopo, sentences)
preprocess_text(erzi, sentences)
preprocess_text(nver, sentences)
第三步,將得到的數(shù)據(jù)集打散照瘾,生成更可靠的訓練集分布匈棘,避免同類數(shù)據(jù)分布不均勻:
random.shuffle(sentences)
第四步,我們控制臺輸出前10條數(shù)據(jù)析命,觀察一下(因為上面進行了隨機打散主卫,你看到的前10條可能不一樣):
for sentence in sentences[:10]:
print(sentenc)
得到的結(jié)果聚類和分類是不同的,這里沒有標簽:
3. 抽取詞向量特征鹃愤。
抽取特征簇搅,將文本中的詞語轉(zhuǎn)換為詞頻矩陣,統(tǒng)計每個詞語的 tf-idf
權(quán)值软吐,獲得詞在對應文本中的 tf-idf
權(quán)重:
#將文本中的詞語轉(zhuǎn)換為詞頻矩陣 矩陣元素a[i][j] 表示j詞在i類文本下的詞頻
vectorizer = TfidfVectorizer(sublinear_tf=True, max_df=0.5)
#統(tǒng)計每個詞語的tf-idf權(quán)值
transformer = TfidfTransformer()
# 第一個fit_transform是計算tf-idf 第二個fit_transform是將文本轉(zhuǎn)為詞頻矩陣
tfidf = transformer.fit_transform(vectorizer.fit_transform(sentences))
# 獲取詞袋模型中的所有詞語
word = vectorizer.get_feature_names()
# 將tf-idf矩陣抽取出來瘩将,元素w[i][j]表示j詞在i類文本中的tf-idf權(quán)重
weight = tfidf.toarray()
#查看特征大小
print ('Features length: ' + str(len(word)))
- 實戰(zhàn) TF-IDF 的中文文本 K-means 聚類
第一步,使用 k-means++ 來初始化模型凹耙,當然也可以選擇隨機初始化姿现,即 init="random",然后通過 PCA 降維把上面的權(quán)重 weight 降到10維肖抱,進行聚類模型訓練:
numClass=4 #聚類分幾簇
clf = KMeans(n_clusters=numClass, max_iter=10000, init="k-means++", tol=1e-6) #這里也可以選擇隨機初始化init="random"
pca = PCA(n_components=10) # 降維
TnewData = pca.fit_transform(weight) # 載入N維
s = clf.fit(TnewData)
第二步备典,定義聚類結(jié)果可視化函數(shù) plot_cluster(result,newData,numClass),該函數(shù)包含3個參數(shù)意述,其中 result 表示聚類擬合的結(jié)果集提佣;newData 表示權(quán)重 weight 降維的結(jié)果,這里需要降維到2維欲险,即平面可視化镐依;numClass 表示聚類分為幾簇,繪制代碼第一部分繪制結(jié)果 newData天试,第二部分繪制聚類的中心點:
def plot_cluster(result,newData,numClass):
plt.figure(2)
Lab = [[] for i in range(numClass)]
index = 0
for labi in result:
Lab[labi].append(index)
index += 1
color = ['oy', 'ob', 'og', 'cs', 'ms', 'bs', 'ks', 'ys', 'yv', 'mv', 'bv', 'kv', 'gv', 'y^', 'm^', 'b^', 'k^',
'g^'] * 3
for i in range(numClass):
x1 = []
y1 = []
for ind1 in newData[Lab[I]]:
# print ind1
try:
y1.append(ind1[1])
x1.append(ind1[0])
except:
pass
plt.plot(x1, y1, color[I])
#繪制初始中心點
x1 = []
y1 = []
for ind1 in clf.cluster_centers_:
try:
y1.append(ind1[1])
x1.append(ind1[0])
except:
pass
plt.plot(x1, y1, "rv") #繪制中心
plt.show()
第三步槐壳,對數(shù)據(jù)降維到2維,然后獲得結(jié)果喜每,最后繪制聚類結(jié)果圖:
pca = PCA(n_components=2) # 輸出兩維
newData = pca.fit_transform(weight) # 載入N維
result = list(clf.predict(TnewData))
plot_cluster(result,newData,numClass)
第四步务唐,得到的聚類結(jié)果圖,4個中心點和4個簇带兜,我們看到結(jié)果還比較好枫笛,簇的邊界很清楚:
第五步,上面演示的可視化過程刚照,降維使用了 PCA刑巧,我們還可以試試 TSNE,兩者同為降維工具,主要區(qū)別在于啊楚,所在的包不同(也即機制和原理不同):
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
因為原理不同吠冤,導致 TSNE 保留下的屬性信息,更具代表性恭理,也即最能體現(xiàn)樣本間的差異拯辙,但是 TSNE 運行極慢,PCA 則相對較快颜价,下面看看 TSNE 運行的可視化結(jié)果:
from sklearn.manifold import TSNE
ts =TSNE(2)
newData = ts.fit_transform(weight)
result = list(clf.predict(TnewData))
plot_cluster(result,newData,numClass)
得到的可視化結(jié)果涯保,為一個中心點,不同簇落在圍繞中心點的不同半徑之內(nèi)周伦,我們看到在這里結(jié)果并不是很好:
第六步夕春,為了更好的表達和獲取更具有代表性的信息,在展示(可視化)高維數(shù)據(jù)時专挪,更為一般的處理撇他,常常先用 PCA 進行降維,再使用 TSNE:
from sklearn.manifold import TSNE
newData = PCA(n_components=4).fit_transform(weight) # 載入N維
newData =TSNE(2).fit_transform(newData)
result = list(clf.predict(TnewData))
plot_cluster(result,newData,numClass)
得到的可視化結(jié)果狈蚤,不同簇落在圍繞中心點的不同半徑之內(nèi):
總結(jié)
上面通過真實小案例,對司法數(shù)據(jù)一步步實現(xiàn)中文短文本聚類划纽,從優(yōu)化和提高模型準確率來說脆侮,主要有兩方面可以嘗試:
- 特征向量的構(gòu)建,除了詞袋模型勇劣,可以考慮使用 word2vec 和 doc2vec 等靖避;
- 模型上可以采用基于密度的 DBSCAN、層次聚類等算法比默。