目标:對大約6w條微博進行分類
環境:R語言
由于時間較緊,且人手不夠,不能采用分類方法,主要是沒有時間人工分類一部分生成訓練集……所以隻能用聚類方法,聚類最簡單的方法無外乎:K-means與層次聚類。
嘗試過使用K-means方法,但結果并不好,所以最終采用的是層次聚類,也幸虧結果還不錯……⊙﹏⊙
分詞(Rwordseg包):
分詞采用的是Rwordseg包,具體安裝和一些細節請參考作者首頁 。請仔細閱讀該頁提供的使用說明pdf文檔,真是有很大幫助。
P.S.
由于我是64位機,但是配置的rj包隻能在32bit的R上使用,而且Rwordseg包貌似不支持最新版本的R(3.01),所以請在32bit的R.exe(2.15)中運行如下語句安裝0.0-4版本:
install.packages("Rwordseg", repos = "http://R-Forge.R-project.org")
貌似直接在Rstudio中運行會安裝失敗,而且直接在Rstudio中點擊install安裝,安裝的是0.0-5版本,我就一直失敗……
segmentCN(doc,recognition=F)
否則會将“中秋國慶”,分為“中”“秋國慶“
微博分詞的一些建議:
gsub(pattern="http:[a-zA-Z\\/\\.0-9]+","",doc)
library("stringr")
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)
生成語料庫之後,生成詞項-文檔矩陣(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,該方法用于評估一字詞對于一個文件集或一個語料庫中的其中一份文件的重要程度:
由于TDM大多都是稀疏的,需要用removeSparseTerms()函數(:"A term-document matrix where those terms fromxare removed which have at least asparsepercentage of empty")進行降維,值需要不斷的測試,我一般會使詞項減少到原有的一半。
層次聚類:
層次聚類的核心實際在距離矩陣的計算,一般聚類時會使用歐氏距離、闵氏距離等,但在大型數據條件下會優先選擇 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 NA In addition: Warning message: In nr * nc : NAs produced by integer overflow"
恭喜你!這個問題64位版本的R也解決不了,因為矩陣超出了R允許的最大限制~我也是遇到同樣的問題,所以沒辦法,隻能将原始數據進行拆分,不過我的情況是多個微博賬戶,但彼此之間的微博分類差不太多,所以可以進行拆分。強烈推薦大家有問題去查找!
(我看到有外國友人說可以用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)
有話要說...