R語言進行中文分詞,並對6W條微博聚類
由於時間較緊,且人手不夠,不能採用分類方法,主要是沒有時間人工分類一部分生成訓練集……所以只能用聚類方法,聚類最簡單的方法無外乎:K-means與層次聚類。
嘗試過使用K-means方法,但結果並不好,所以最終採用的是層次聚類,也幸虧結果還不錯……⊙﹏⊙
分詞(Rwordseg包):
分詞采用的是Rwordseg包,具體安裝和一些細節請參考作者首頁 http://jliblog.com/app/rwordseg。請仔細閱讀該頁提供的使用說明pdf文件,真是有很大幫助。
- 安裝:
P.S.
由於我是64位機,但是配置的rj包只能在32bit的R上使用,而且Rwordseg包貌似不支援最新版本的R(3.01),所以請在32bit的R.exe中執行如下語句安裝0.0-4版本:
install.packages("Rwordseg", repos = "http://R-Forge.R-project.org")
貌似直接在Rstudio中執行會安裝失敗,而且直接在Rstudio中點選install安裝,安裝的是0.0-5版本,我就一直失敗……
- 使用:
- 分詞時儘量關閉人名識別 segmentCN(doc,recognition=F) 否則會將“中秋國慶”,分為“中”“秋國慶“
- 可以使用insertWords()函式新增臨時的詞彙
- 對文件向量進行分詞時,強烈建議用for迴圈對每一個元素執行segmentCN,而不要對整個向量執行!!!因為我蛋疼的發現對整個向量執行時,還是會出現識別人名的現象……
- 執行完後請detach()包,removeWords()函式與tm包中的同名函式衝突。
微博分詞的一些建議:
- 微博內容中經常含有url,分詞後會將url拆散當做英文單詞處理,所以我們需要用正則表示式,將url去掉: gsub(pattern="http:[a-zA-Z\/\.0-9]+","",doc)
- 微博中含有#標籤#,可以儘量保證標籤的分詞準確,可以先提取標籤,然後用insertWords()人工新增一部分詞彙: tag=str_extract(doc,"^#.+?#") #以“#”開頭,“."表示任意字元,"+"表示前面的字元至少出現一次,"?"表示不採用貪婪匹配—即之後遇到第一個#就結束 tag=na.omit(tag) #去除NA tag=unique(tag) #去重
文字挖掘(tm包):
- 語料庫:
分詞之後生成一個列表變數,用列表變數構建語料庫。
由於tm包中的停用詞()都是英文(可以輸入stopwords()檢視),所以大家可以去網上查詢中文的停用詞(一般700多個的就夠了,還有1208個詞版本的),用removeWords函式去除語料庫中的停用詞:
doc.corpus=tm_map(doc.corpus,removeWords,stopwords_CN)
- TDM:
生成語料庫之後,生成詞項-文件矩陣(Term Document Matrix,TDM),顧名思義,TDM是一個矩陣,矩陣的列對應語料庫中所有的文件,矩陣的行對應所有文件中抽取的詞項,該矩陣中,一個[i,j]位置的元素代表詞項i在文件j中出現的次數。
由於tm包是對英文文件就行統計挖掘的,所以生成TDM時會對英文文件進行分詞(即使用標點和空格分詞),之前Rwordseg包做的就是將中文語句拆分成一個個詞,並用空格間隔。
建立TDM的語句為:
control=list(removePunctuation=T,minDocFreq=5,wordLengths = c(1, Inf),weighting = weightTfIdf)
doc.tdm=TermDocumentMatrix(doc.corpus,control)
變數control是一個選項列表,控制如何抽取文件,removePunctuation表示去除標點,minDocFreq=5表示只有在文件中至少出現5次的詞才會出現在TDM的行中。
tm包預設TDM中只保留至少3個字的詞(對英文來說比較合適,中文就不適用了吧……),wordLengths = c(1, Inf)表示字的長度至少從1開始。
預設的加權方式是TF,即詞頻,這裡採用Tf-Idf,該方法用於評估一字詞對於一個檔案集或一個語料庫中的其中一份檔案的重要程度:
- 在一份給定的檔案裡,詞頻 (term frequency, TF) 指的是某一個給定的詞語在該檔案中出現的次數。這個數字通常會被歸一化,以防止它偏向長的檔案。
- 逆向檔案頻率 (inverse document frequency, IDF) 是一個詞語普遍重要性的度量。某一特定詞語的IDF,可以由總檔案數目除以包含該詞語之檔案的數目,再將得到的商取對數得到。
- 某一特定檔案內的高詞語頻率,以及該詞語在整個檔案集合中的低檔案頻率,可以產生出高權重的TF-IDF。因此,TF-IDF傾向於保留文件中較為特別的詞語,過濾常用詞。
由於TDM大多都是稀疏的,需要用removeSparseTerms()函式進行降維,值需要不斷的測試,我一般會使詞項減少到原有的一半。
層次聚類:
層次聚類的核心實際在距離陣的計算,一般聚類時會使用歐氏距離、閔氏距離等,但在大型資料條件下會優先選擇 cosine 距離,及 dissmilarity 函式:
dissimilarity(tdm_removed, method = 'cosine')
(P.S.要使用cosine方法,需要先安裝proxy包。)
層次聚類的方法也有很多,這裡選用mcquitty,大家還是多試試,本文給出的選擇不一定適合你~
注意:由於R對向量的大小有限制,所以在計算距離時,請優先使用64bit,3.0版本的R~
但如果出現如下報錯資訊:
"Error in vector(typeof(x$v), nr * nc) : vector size cannot be NAIn addition: Warning message:
In nr * nc : NAs produced by integer overflow"
恭喜你!這個問題64位版本的R也解決不了,因為矩陣超出了R允許的最大限制~我也是遇到同樣的問題,所以沒辦法,只能將原始資料進行拆分,不過我的情況是多個微博賬戶,但彼此之間的微博分類差不太多,所以可以進行拆分。強烈推薦大家有問題去stackoverflow查詢!
(我看到有國外友人說可以用int64包嘗試一下,因為tdm其實也是個list,但我沒試成功……)
好了,下面貼上全部程式碼:
################################################################## 讀取資料
col=c(rep("character",6),"NULL",NA,NA,"character",rep("NULL",4))
data=read.csv(file="text.csv",header=T,sep=",",colClasses=col)
# 將文字儲存到一個向量中
doc=c(NULL)for(i in 1:dim(data)[1]){
doc=c(doc,data$Text[i])
}################################################################## 去除微博中含有的url
doc=gsub(pattern="http:[a-zA-Z\/\.0-9]+","",doc)
# 無意義微博處理
empty_N=c(2032,2912,7518,8939,14172,14422,26786,30126,34501,35239,48029,48426,48949,49100,49365,49386,49430,50034,56818,56824,56828,57859)
doc[empty_N]="NA"
################################################################## 新增詞彙
library("Rwordseg")
textwords=c("...")
insertWords(textwords)
# removeWords(tagwords)
doc_CN=list()for(j in 1:length(doc)){
doc_CN[[j]]=c(segmentCN(doc[j],recognition=F))
}
detach("package:Rwordseg", unload=TRUE)
################################################################## 構建語料庫(Corpus物件)
library("tm")
doc.corpus=Corpus(VectorSource(doc_CN))
###########停用詞###########
data_stw=read.table(file="中文停用詞庫.txt",colClasses="character")
stopwords_CN=c(NULL)for(i in 1:dim(data_stw)[1]){
stopwords_CN=c(stopwords_CN,data_stw[i,1])
}
doc.corpus=tm_map(doc.corpus,removeWords,stopwords_CN)
# 刪除停用詞############################# 建立詞項-文件矩陣(TDM)
control=list(removePunctuation=T,minDocFreq=5,wordLengths = c(1, Inf),weighting = weightTfIdf)
doc.tdm=TermDocumentMatrix(doc.corpus,control)
length(doc.tdm$dimnames$Terms)
tdm_removed=removeSparseTerms(doc.tdm, 0.9998)
# 1-去除了低於 99.98% 的稀疏條目項
length(tdm_removed$dimnames$Terms)
################################################################## 層次聚類:
dist_tdm_removed <- dissimilarity(tdm_removed, method = 'cosine')
hc <- hclust(dist_tdm_removed, method = 'mcquitty')
cutNum = 20
ct = cutree(hc,k=cutNum)
sink(file="result.txt")
for(i in 1:cutNum){ print(paste("第",i,"類: ",sum(ct==i),"個")); print("----------------");
print(attr(ct[ct==i],"names"));
# print(doc[as.integer(names(ct[ct==i]))])
print("----------------")
}
sink()
#輸出結果
output=data.frame(clas=NULL,tag=NULL,text=NULL)for(i in 1:cutNum){
in_tag=tag[as.integer(names(ct[ct==i]))]
in_text=doc[as.integer(names(ct[ct==i]))]
cut_output=data.frame(clas=rep(i,length(in_tag)),tag=in_tag,text=in_text)
output=rbind(output,cut_output)
}
write.table(output,file="classification.csv",sep=",",row.names=F)