文本主題發現(一)-- 數據預處理

文本主題發現(一)-- 數據預處理

來自專欄 R語言中文社區

作者:趙鎮寧 R語言中文社區特約作者

主題發現能夠幫助我們處理和分析大規模信息並從中發現文本主要內容和主題,相關探測方法有文本聚類法、主題建模、多維尺度分析等等。這些分析方法的前期數據處理都不同程度的涉及分詞、建立文檔-詞條矩陣、生成詞條相似(相異)矩陣等關鍵步驟,本期主要是對前期數據預處理流程的大致總結,主要內容包括:

(1)分詞:分詞引擎+自定義詞典+停用詞詞典

(2)特徵(核心詞)提取:高頻詞法+TF*IDF演算法

(3)文檔-詞條矩陣(數據矩陣)建立:語料庫+特徵詞+生成矩陣+稀疏矩陣處理

(4)詞條相似性(相異度)矩陣建立:主要相關係數計算

本文用到的包有:

1. 數據收集

本文以《復仇者聯盟3》500條熱門豆瓣短評作為示例數據,數據抓取代碼如下:

```

library(RCurl) # 網路請求

library(XML) # 網頁解析

library(magrittr) # 管道操作

Cookie=your cookie

headers <- c(Accept=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,

Content-Type=text/html; charset=utf-8,

User=Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36,

Cookie=Cookie

)

comments<-c() # 短評

errorurl<-c() # 錯誤url

start<-0

i<-0

while(TRUE){

start=20*i

tryCatch({

url<-sprintf("movie.douban.com/subjec",start)

web<-getURL(url,httpheader=headers)%>% htmlParse()

comment<-xpathSApply(web,"//div[@class=comment]//p[@class=]",xmlValue)

if(length(comment)==0) break

comments<-c(comments,comment)

cat(sprintf("第%s頁抓取成功",i),sep = "
")

},error = function(e){

errorurl<-c(errorurl,start)

})

Sys.sleep(runif(1,0.5,1.5))

i = i +1

}

save(comments,file="comment.Rdata")

```

2 文本預處理

以聚類為例,目前大多聚類演算法通常選擇兩種代表性的數據結構,一是數據矩陣(對象-屬性結構),即用P個變數(屬性)來表現n個對象,如用年齡、身高、性別等屬性來表現對象「人」,可以看成一個n*p的矩陣;二是相異度矩陣(對象-對象結構或屬性-屬性結構),即存儲n個對象或p個屬性兩兩之間的差異性,可以看成一個n*n(對象與對象之間)或p*p(屬性與屬性之間)的矩陣。對應在主題發現中,數據矩陣表現為文檔-詞條矩陣,相異度矩陣表現為詞條-詞條矩陣。因此接下來的文本預處理包括:分詞、特徵(核心詞條)提取、建立文檔-詞條矩陣和建立詞條-詞條相異度矩陣幾個關鍵步驟。

2.1

R中常用的分詞包有Rwordseg和jiebaR,其中,Rwordseg使用rJava調用Java分詞工具Ansj,Ansj基於中科院ictclas中文分詞演算法,採用隱馬爾科夫模型(HMM),當前版本的Rwordseg完全引用了Ansj包,在這個Java包的基礎上開發了R介面,並在Rforge(r-forge.r-project.org/R)進行維護,我們可以下載安裝包進行本地安裝。jiebaR是「結巴」中文分詞(github.com/fxsjy/jieba)(Python)的R語言版本,支持隱馬爾科夫模型、混合模型、最大概率法等八種分詞引擎,同時具有詞性標註,關鍵詞提取,文本Simhash相似度比較等功能,本文選取jiebaR進行分詞。

A. 關於分詞引擎選擇

本文選擇的是混合模型,混合模型(MixSegment)結合使用最大概率法和隱馬爾科夫模型,在分詞引擎裡面分詞效果相對較好。

B. 關於自定義詞典

為提高分詞準確率,本文首先以搜狗細胞庫提供的《復仇者聯盟3》詞庫作為自定義詞典,需要注意的是,搜狗詞庫scel文件是二進位,需要把二進位的詞典轉成可以使用的文本文件,jiebaR包的作者同時開發了一個cidian項目,可以轉換搜狗的詞典,目前託管在github,可通過devtools::install_github("qinwf/cidian")進行安裝。此外,基於《復仇者聯盟3》自定義詞典進行分詞後查看初步結果,不斷往裡面添加進行完善。

```

library(jiebaR)

library(cidian)

decode_scel(scel = "mydic.scel",cpp = TRUE)#將scel文件進行轉換

output file: mydic.scel_2018-05-30_22_29_33.dict

scan(file="mydic.scel_2018-05-30_22_29_33.dict",what=character(),nlines=20,sep=
,encoding=utf-8,fileEncoding=utf-8)#查看轉換後的詞典

Read 20 items

[1] "艾瑞克 n" "艾什莉 n" "埃文斯 n" "奧創 n" "奧創紀元 n"

[6] "奧克耶 n" "巴頓 n" "班納 n" "鮑爾斯 n" "保羅 n"

[11] "貝坦尼 n" "變身巨人 n" "變種人 n" "變種人兄弟會 n" "波蒂埃 n"

[16] "波茲 n" "布魯斯 n" "布魯斯班納 n" "布思 n" "操縱物體 n"

```

C. 關於停用詞詞典

首先在網上下載了一份常用停用詞詞典(結合哈工大停用詞表、四川大學機器智能實驗室停用詞庫、百度停用詞表等),進行試分詞,根據初步分詞結果往停用詞表中添加會對數據結果造成較大影響的停用詞。

D. 關於特徵(核心詞條)提取

受研究工具、統計分析過程限制以及出於提高結果分析與可視化效用的目的,我們通常提取部分關鍵詞(特徵)來進行後續分析,通常用到的篩選方法有高頻詞法和TF*IDF演算法,其中高頻詞法是將所有辭彙按詞頻從高到底排序,根據經驗或某一法則(如齊普夫法則)確定某一閾值,高於該閾值的為特徵詞,其核心思想是「一個詞在文章中出現的次數越多,則它就越重要」;TF*IDF演算法則考慮到「一個詞語在一篇文章中出現次數越多, 同時在所有文檔中出現次數越少, 越能夠代表該文章」,本文利用TF*IDF演算法提取。

jiebaR包在安裝目錄中的idf.utf8文件為IDF的語料庫,idf.utf8文件每一行有2列,第一列是詞項,第二列為權重。通過計算文檔的詞頻(TF),與語料庫的IDF值相乘,就可以得到TF-IDF值,從而提取文檔的關鍵詞。

```

term<-c()

for(i in 1:500)

term<-segment(comments[i], wk ) %>% c(term,.)

termFreq<-freq(term) # 統計詞頻

termFreq<-termFreq[order(termFreq$freq,decreasing = TRUE),]

rownames(termFreq)<-as.character(1:4541)

head(termFreq)

char freq

1 滅霸 181

2 電影 96

3 漫威 96

4 反派 58

5 宇宙 57

6 英雄 48

keys<-worker(type="keywords",topn=1500) %>% vector_keywords(term,.) # 利用tf-idf提取核心詞

keydf<-data.frame("tf-idf value"=names(keys),"term"=as.character(keys),stringsAsFactors = FALSE)

head(keydf)

tf.idf.value term

1 2124.8 滅霸

2 1126.96 漫威

3 766.037 反派

4 639.156 電影

5 540.003 復聯

6 419.996 宇宙

write.csv(keydf,file="keydf.csv", fileEncoding = "GBK")

```

2.2 進行分詞

後期建立文檔詞條矩陣時需要特定的數據格式,因此利用jieba分詞後,需要設置成符合tm包格式的輸出,即分詞之後每條評論存成一個單獨的字元串,並用空格對詞語進行分隔。

```

wk = worker(type = "mix", user = "mydic.scel_2018-05-30_22_29_33.dict",

stop_word = "stopword.txt") # 載入jiebaR庫的分詞引擎

commentseg<-c()

for(i in 1:500)

commentseg[i]<-segment(comments[i], wk ) %>% paste(.,collapse=" ")

str(commentseg)

chr [1:500] "復聯 講 豬隊友 順風 浪 一波 團滅 故事 卡魔拉 倒霉 爸爸 瘋子 男朋友 傻子" ...

write.csv(commentseg,file="commentseg.csv", fileEncoding = "GBK")

```

2.3 建立文檔-詞條矩陣

A. 建立語料庫

tm包中有一個Corpus對象用以存儲原始預料,可以直接從文件中讀取或者由R中的某個對象來轉換,這裡使用分詞之後的向量對象commentseg。由於語料對象包含所有的文檔的信息,不方面使用常規的方式對它們進行查看,可以使用inspect函數來查看。

```

library(tm)

ss<-read.csv("commentseg.csv",fileEncoding = "GBK",stringsAsFactors = FALSE)

docs<-ss$x %>% tolower()

d.corpus<-VCorpus(VectorSource(docs)) # 建立語料庫

d.corpus

<>

Metadata: corpus specific: 0, document level (indexed): 0

Content: documents: 500

inspect(d.corpus[1:2]) # 查看語料庫

<>

Metadata: corpus specific: 0, document level (indexed): 0

Content: documents: 2

[[1]]

<>

Metadata: 7

Content: chars: 42

[[2]]

<>

Metadata: 7

Content: chars: 137

```

B. 建立文檔-詞條矩陣

文本分析的基礎對象是文檔-詞條矩陣,該數據結構在tm包中有一個專門的DocumentTermMatrix對象來實現,在文檔-詞條矩陣中每一行是一篇文檔(評論),每一列是一個詞,行和列的節點是該文檔中包含該詞的數目。此外,字典是一個字符集,它可以作為一個控制參數傳入DocumentTermMatrix(),從而選擇我們需要的詞條建立文檔-詞條矩陣,這裡以前面基於TF*idf演算法得出的1500個詞作為我們的詞條。```

keyword<-read.csv("keydf.csv", fileEncoding = "GBK",stringsAsFactors = FALSE)

keyword<-keyword$term %>% tolower() # 之前通過tf-idf篩選的核心辭彙

dtm <- DocumentTermMatrix(d.corpus,control = list(wordLengths=c(1,Inf),dictionary=keyword)) # 注意wordLengths的取值設定

df_mat<-as.matrix(dtm) # 轉換為矩陣形式

write.csv(df_mat,file="df_mat.csv")

```

C. 稀疏矩陣處理

```

dtm

<>

Non-/sparse entries: 4663/745337

Sparsity : 99%

Maximal term length: 22

Weighting : term frequency (tf)

```

Non-/sparse entries表示矩陣內有4663個元素大於0,而有745337個元素等於0,這種由大部分元素為0組成的矩陣成為稀疏矩陣,99%表示矩陣的稀疏程度為99%,我們可以通過tm包內的removeSparseTerms設定稀疏度大小閾值,對每個辭彙做稀疏度篩選,下文將稀疏度閾值設定為0.7,表示原始矩陣的每個辭彙稀疏度大於70%以上,就不予以放入DocumentTermMatrix中,下文得到的矩陣(500條評論,1辭彙),矩陣內有153個元素大於347個元素是0,整體稀疏度為69%。考慮到本文評論文本規模以及經稀疏度處理後辭彙量較少(小於5),因此這裡不再做稀疏處理。```dtm_sub<-removeSparseTerms(dtm,0.7)

dtm_sub

<>

Non-/sparse entries: 153/347

Sparsity : 69%

Maximal term length: 2

Weighting : term frequency (tf)

```

2.4 建立相似(相異度)矩陣

相似矩陣存儲 n 個對象兩兩之間的相似性,目前我接觸到的相似性計算方法大致可以分為三類,一是基於共現頻次這一基本統計量衍生出來的,如互信息度、association strength、inclusion index、Jaccard』s coefficient、Salton』s cosine(Ochiia係數)等;二是藉助知網(HowNet)、WordNet 等通用本體庫以及領域詞典的知識體系(包括辭彙的定義及辭彙間的相關關係),設計許多有用的演算法來衡量辭彙之間的相似程度;三是進行詞向量化,如Word2vec。本文利用Salton』s cosine(Ochiia係數)來測度詞語之間的相似性,具體演算法如下(設文檔詞條矩陣為A):

(1)由文檔-詞條矩陣A生成共現矩陣C:

(Cij表示兩兩詞條在同一文檔中共現的頻次,其中當i=j[即矩陣對角線]時表示詞條本身在所有文檔中出現的頻次)

(2)由共現矩陣C生成相似矩陣S或相異矩陣D(1-S)

(上式表示兩個詞在同一評論中的共現頻次/兩個詞出現頻次的積的平方根)

```

cooccurance_mat<-t(df_mat)%*%df_mat# 共現矩陣

similarity_mat<-matrix(nrow=ncol(df_mat),ncol=ncol(df_mat)) # 利用Salton』s cosine得相似矩陣

dimnames(similarity_mat) <- list(colnames(df_mat),colnames(df_mat))

for(i in 1:ncol(df_mat))

for(j in 1:ncol(df_mat))

ifelse(i==j,similarity_mat[i,j]<-1,similarity_mat[i,j]<-cooccurance_mat[i,j]/(sqrt(cooccurance_mat[i,i]*cooccurance_mat[j,j])))

dissimilarity_mat<-1-similarity_mat # 相異度矩陣

write.csv(similarity_mat,file="similarity_mat.csv")

write.csv(dissimilarity_mat,file="dissimilarity_mat.csv")

```

3 總結

A. 亂碼問題使用tm包建立文檔-詞條矩陣時經常遇到亂碼問題,導致數據結果不正確,嘗試過很多辦法都無效……最後採取的辦法是將語料庫和特徵詞先按特定編碼存儲到本地,然後再讀到R中。

B. 關於DocumentTermMatrix函數

(1)特別注意wordLengths參數的設置,該參數設置顯示詞的最小及最大長度,默認最小為3!!,也就意味著詞長度小於3的詞都不會被提取出來。

(2)注意tolower參數的設置,我的語料庫中所有的大寫英文單詞都不能提取出來,只有轉換為小寫才能提取。


推薦閱讀:

數據分析指北 - 前言(04)(前言完)
Kaggle Titanic 生存預測(Top1.4%)完整代碼分享
Kaggle泰坦尼克預測
從懵逼到菜逼------菜逼來談數據挖掘

TAG:科技 | 數據挖掘 | 機器學習 |