文本聚類是將一個個文檔由原有的自然語言文字信息轉化成數學信息,以高維空間點的形式展現出來,通過計算哪些點距離比較近,從而將那些點聚成一個簇,簇的中心叫做簇心。一個好的聚類要保證簇內點的距離儘量的近,但簇與簇之間的點要儘量的遠。
如下圖,以 K、M、N 三個點分別爲聚類的簇心,將結果聚爲三類,使得簇內點的距離儘量的近,但簇與簇之間的點儘量的遠。
文本無監督聚類
整個過程分爲以下幾個步驟:
- 語料加載
- 分詞
- 去停用詞
- 抽取詞向量特徵
- 實戰 TF-IDF 的中文文本 K-means 聚類
1. 首先進行語料加載,在這之前,引入所需要的 Python 依賴包,並將全部語料和停用詞字典讀入內存中。
第一步,引入依賴庫,有隨機數庫、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 文件,可以根據場景自己在該文本里面添加要去除的詞(比如冠詞、人稱、數字等特定詞):
#加載停用詞
stopwords=pd.read_csv('stopwords.txt',index_col=False,quoting=3,sep="\t",names=['stopword'], encoding='utf-8')
stopwords=stopwords['stopword'].values
第三步,加載語料,語料是4個已經分好類的 csv 文件,直接用 pandas 加載即可,加載之後可以首先刪除 NaN行,並提取要分詞的 content 列轉換爲 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)
#轉換
laogong = laogong_df.segment.values.tolist()
laopo = laopo_df.segment.values.tolist()
erzi = erzi_df.segment.values.tolist()
nver = nver_df.segment.values.tolist()
2. 分詞和去停用詞。
第一步,定義分詞、去停用詞的函數,函數包含兩個參數:content_lines
參數爲語料列表;sentences 參數爲預先定義的 list,用來存儲分詞後的結果:
#定義分詞函數preprocess_text
#參數content_lines即爲上面轉換的list
#參數sentences是定義的空list,用來儲存分詞後的數據
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()]#去數字
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
第二步,調用函數、生成訓練數據,根據司法語料數據,分爲報警人被老公打,報警人被老婆打,報警人被兒子打,報警人被女兒打,具體如下:
sentences = []
preprocess_text(laogong, sentences)
preprocess_text(laopo, sentences)
preprocess_text(erzi, sentences)
preprocess_text(nver, sentences)
第三步,將得到的數據集打散,生成更可靠的訓練集分佈,避免同類數據分佈不均勻:
random.shuffle(sentences)
第四步,控制檯輸出前10條數據
for sentence in sentences[:10]:
print(sentence)
得到的結果聚類和分類是不同的,這裏沒有標籤
3. 抽取詞向量特徵。
抽取特徵,將文本中的詞語轉換爲詞頻矩陣,統計每個詞語的 tf-idf
權值,獲得詞在對應文本中的 tf-idf
權重:
#將文本中的詞語轉換爲詞頻矩陣 矩陣元素a[i][j] 表示j詞在i類文本下的詞頻
vectorizer = TfidfVectorizer(sublinear_tf=True, max_df=0.5)
#統計每個詞語的tf-idf權值
transformer = TfidfTransformer()
# 第一個fit_transform是計算tf-idf 第二個fit_transform是將文本轉爲詞頻矩陣
tfidf = transformer.fit_transform(vectorizer.fit_transform(sentences))
# 獲取詞袋模型中的所有詞語
word = vectorizer.get_feature_names()
# 將tf-idf矩陣抽取出來,元素w[i][j]表示j詞在i類文本中的tf-idf權重
weight = tfidf.toarray()
#查看特徵大小
print ('Features length: ' + str(len(word)))
4. 實戰 TF-IDF
的中文文本 K-means
聚類
第一步,使用 k-means++
來初始化模型,當然也可以選擇隨機初始化,即 init="random"
,然後通過 PCA 降維把上面的權重 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)
第二步,定義聚類結果可視化函數 plot_cluster(result,newData,numClass)
,該函數包含3個參數,其中 result 表示聚類擬合的結果集;newData 表示權重 weight 降維的結果,這裏需要降維到2維,即平面可視化;numClass 表示聚類分爲幾簇,繪製代碼第一部分繪製結果 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()
第三步,對數據降維到2維,然後獲得結果,最後繪製聚類結果圖:
pca = PCA(n_components=2) # 輸出兩維
newData = pca.fit_transform(weight) # 載入N維
result = list(clf.predict(TnewData))
plot_cluster(result,newData,numClass)
第五步,上面演示的可視化過程,降維使用了 PCA,可以試試 TSNE,兩者同爲降維工具
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
因爲原理不同,導致 TSNE 保留下的屬性信息,更具代表性,也即最能體現樣本間的差異,但是 TSNE 運行極慢,PCA 則相對較快,下面看看 TSNE 運行的可視化結果:
from sklearn.manifold import TSNE
ts =TSNE(2)
newData = ts.fit_transform(weight)
result = list(clf.predict(TnewData))
plot_cluster(result,newData,numClass)
得到的可視化結果,爲一箇中心點,不同簇落在圍繞中心點的不同半徑之內,結果並不是很好:
第六步,爲了更好的表達和獲取更具有代表性的信息,在展示(可視化)高維數據時,更爲一般的處理,常常先用 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)
不同簇落在圍繞中心點的不同半徑之內