1. 程式人生 > 其它 >R語言自然語言處理:關鍵詞提取(TF-IDF)

R語言自然語言處理:關鍵詞提取(TF-IDF)

作者:黃天元,復旦大學博士在讀,熱愛資料科學與開源工具(R/Python),致力於利用資料科學迅速積累行業經驗優勢和科學知識發現,涉獵內容包括但不限於資訊計量、機器學習、資料視覺化、應用統計建模、知識圖譜等,著有《R語言高效資料處理指南》、《文字資料探勘——基於R語言》(《文字資料探勘 基於R語言》(黃天元)【摘要 書評 試讀】- 京東圖書)。知乎專欄:R語言資料探勘郵箱:[email protected].歡迎合作交流。

本文希望詮釋如何利用TF-IDF方法對文字中的關鍵詞進行提取。關鍵詞提取的輸入是一大段文字材料,輸出是少數的關鍵詞。比如我們日常看的論文,會有關鍵詞。但是這些一般都是作者自己根據文章內容,向雜誌社提供的關鍵詞。事實上,如果有了正文的文字,我們完全可以利用計算機自動提取關鍵詞(在一些資料庫中,這些關鍵詞的名稱叫做Index Keywords,即索引關鍵詞,區別於作者關鍵詞Author Keywords)。

從海量的文字文件中,提取少量表徵其內容的關鍵詞,這就是關鍵詞提取的主要任務。掌握了這項技能,能夠自動化地給文字貼標籤,非常有用。根據大部分從業者和學界的實踐證明,TF-IDF演算法能夠解決大部分的關鍵詞抽取場景,簡單有效,其實大部分能夠做文章的地方不是在演算法,而是在中文分詞和詞性標註的部分。所以,掌握這個簡單有效的方法,並利用它來做關鍵詞提取,是非常重要的。本文會首先對TF-IDF演算法做簡要介紹,然後提供這個演算法在R語言中的實現程式碼。

TF-IDF簡介

TF-IDF的基本思想是:詞語的重要性與它在檔案中出現的次數成正比,但同時會隨著它在語料庫中出現的頻率成反比下降。也就是說,如果在一篇論文或一次演講中,我們反覆提到一些詞,那麼這些詞可能會比其他的詞更重要。但是如果這些詞,別人也都在用,那麼這些詞就不能稱之為我們文章或者演講的特色(比如大量的常用詞)。為了能夠提取出文字中“最具特色”的表徵性關鍵詞,需要利用TF-IDF演算法,也就是說:如果某個詞或者短語在一個文件中出現多次,但是在其他文件中很少出現,就可以認為這個詞或短語具有很好的區分性,適合用來對這個文件進行表徵。

TF(Term Frequency)表示一個詞在文件中出現的次數。

DF(Document Frequency)表示整個語料庫中含有某個詞的文件個數

IDF(Inverse Document Frequency)為逆文件頻率,其計算公式為:IDF= log(語料庫中文件總數/(包含該詞的文件數+1))。如果沒有加1,那麼分母為零的時候會出錯,因此必須加1。圖中沒有加1,一般認為既然對這個詞進行統計,這個詞應該至少出現一次。這在訓練模型的時候是正確的,但是在運用模型的時候,就不一定了。為了保險,加1沒錯。

TF-IDF = TF * IDF

由公式可知:一個詞在文件中出現的次數越多,其TF值就越大,整個語料庫中包含某個詞的文件數越少,則IDF值越大,因此某個詞的TF-IDF值越大,則這個詞是關鍵詞的概率越大。

TF-IDF關鍵詞提取演算法的一大缺點是:為了精確的提取一篇文件中的關鍵詞,需要有一整個語料庫來提供支援。這個問題的解決方法,通常是在一個通用的語料庫上提前計算好所有詞的IDF值,jieba就是這麼做的。這樣的解決方案對於普通文件關鍵詞提取有一定的效果,但是對於專業性稍微強一點的文件,表現就會差很多。因此如果是一個垂直領域,需要自己先對模型進行訓練,形成一個IDF的庫(裡面裝的東西就是一個數據框,一列是詞語,一列是這個詞語的IDF)。jieba是用《人民日報》語料庫進行訓練的,對新聞類的特徵提取有一定效果。但是隨著時代的變遷,大家用語習慣的變化,無論是分詞庫還是IDF詞庫都需要定期更新,才能夠有良好的效果。

R語言中的實現

基本準備

首先安裝必要的包。

library(pacman)
p_load(tidyverse,tidytext,data.table,rio,jiebaR)

然後,匯入資料。資料在我的Github中可以下載,網址為:. 我們匯入到R環境中。

import("./hire_text.rda") -> hire_text
hire_text

這裡麵包含了網際網路公司的一些招聘資訊,一共有4102條記錄,只有一列,列名稱為hire_text,包含了企業對崗位要求的描述。

下面,我們要對這些文字進行分詞,然後提取能夠表徵這些文字的關鍵詞。這樣,我們就可以知道這些企業究竟想要什麼樣的人才。

分詞

首先,要進行高質量的分析,就需要用到外部詞庫。這裡我會用搜狗詞胞庫,網址為。經過對資料的觀察,我認為這些網際網路公司主要招聘的物件還是IT,有的則是金融行業,因此我要使用兩個詞庫:計算機詞庫(/)和財經詞庫()。下載到本地之後,我會把它們轉化為文字格式,然後統一複製貼上到使用者詞庫中(詳細方法見之前的文章R語言自然語言處理:中文分詞)。 下面先對scel檔案進行轉格式。

p_load(cidian)
decode_scel(scel = "./財經金融詞彙大全【官方推薦】.scel", output = "./finance.utf8", cpp = TRUE)
## output file: ./finance.utf8
decode_scel(scel = "./計算機詞彙大全【官方推薦】.scel", output = "./it1.utf8", cpp = TRUE)
## output file: ./it1.utf8
decode_scel(scel = "./開發大神專用詞庫【官方推薦】.scel", output = "./it2.utf8", cpp = TRUE)
## output file: ./it2.utf8

然後把這些詞典載入到使用者詞典中。進入DICTPATH所在目錄,然後找到“user.dict.utf8”,把轉格式之後的文字內容複製貼上進去。

現在,我們的分詞能力就已經得到了一定程度的提高。 現在我們要求每一個文字的關鍵詞,首先給每個文件一個ID。

hire_text %>% 
  mutate(id = 1:n()) -> hire_txt

然後,我們用jiebaR的工具開始分詞。

worker() -> wk

hire_txt %>% 
  mutate(words = map(hire_text,segment,jieba = wk)) %>% 
  select(id,words) -> corpus 

corpus
## # A tibble: 4,102 x 2
##       id words     
##    <int> <list>    
##  1     1 <chr [41]>
##  2     2 <chr [51]>
##  3     3 <chr [50]>
##  4     4 <chr [54]>
##  5     5 <chr [44]>
##  6     6 <chr [63]>
##  7     7 <chr [53]>
##  8     8 <chr [18]>
##  9     9 <chr [57]>
## 10    10 <chr [49]>
## # ... with 4,092 more rows

因為這一步不是很好理解,我先停一下解釋一下。我先構建了一個名為wk的worker,預設會呼叫我放在自定義的分詞庫和原有的詞庫。然後,我用這個分詞器,對錶格中每一個文字,都做了分詞。這裡用了map函式,它會對hire_text的每一個元素,進行segment函式的處理,而且jieba引數都會設為wk,也就是我們用同一個分詞器對所有的文字進行處理。最後得到一個新的列,我命名為words,它包含了每一個文字處理的分詞結果,不過因為每個文字分詞的長度都不一樣,我們把它們都放在一個list裡面,然後放在資料框中。

不過這個格式還是不能用的,我們最後要得到tidy的格式:也就是id是所屬的文字編號,而另一列應該是這個文字分詞的每一個詞,再一列是這個詞出現的詞頻(TF),然後我們再來計算IDF和TF-IDF。 聽起來好像很複雜,不過讓你看看它在R裡面能有多簡單。

corpus %>% 
  unnest() %>% 
  count(id,words) -> f_table

f_table
## # A tibble: 172,877 x 3
##       id words     n
##    <int> <chr> <int>
##  1     1 1228      1
##  2     1 1279      1
##  3     1 1666      1
##  4     1 360       1
##  5     1 567       1
##  6     1 6         1
##  7     1 D         1
##  8     1 大廈      1
##  9     1 棟        1
## 10     1 高寶      1
## # ... with 172,867 more rows

現在這個f_table中,id是文件編號,words是分詞結果得到的每一個詞,n則是這個詞在當前文件中出現的頻次。

求TF、IDF和TF-IDF

至此,根據原理,我們需要的資料其實全部都有了,因此無論是TF還是IDF都可以求,其乘積TF-IDF也就出來了。不過我們已經載入了tidytext這個包,因此,這個任務基本是馬上就完成了。

f_table %>%
  bind_tf_idf(term = words,document = id,n = n) -> tf_idf_table

tf_idf_table
## # A tibble: 172,877 x 6
##       id words     n     tf   idf tf_idf
##    <int> <chr> <int>  <dbl> <dbl>  <dbl>
##  1     1 1228      1 0.0244  8.32 0.203 
##  2     1 1279      1 0.0244  8.32 0.203 
##  3     1 1666      1 0.0244  7.22 0.176 
##  4     1 360       1 0.0244  5.37 0.131 
##  5     1 567       1 0.0244  8.32 0.203 
##  6     1 6         1 0.0244  2.75 0.0671
##  7     1 D         1 0.0244  5.14 0.125 
##  8     1 大廈      1 0.0244  5.23 0.128 
##  9     1 棟        1 0.0244  5.83 0.142 
## 10     1 高寶      1 0.0244  7.22 0.176 
## # ... with 172,867 more rows

我專門把形參都顯示出來,大家能夠知道應該怎麼放進去。term接收的是分詞的結果,document接收的是文件的編號,n接收的是在文件中出現的詞頻。一個bind_tf_idf函式,統統搞定。

關鍵詞提取

既然關鍵詞提取是基於TF-IDF,那麼我們現在只要把每個文件中TF-IDF最高的n個詞提出來,就是這個文件最重要的關鍵詞。比如,我需要提出最重要的3個關鍵詞,可以使用分組提取操作。

tf_idf_table %>% 
  group_by(id) %>% 
  top_n(3,tf_idf) %>% 
  ungroup() -> top3

需要明確的是,top_n函式選擇前三名的時候,如果有並列第三的,會全部納入表格中。 最後,讓我們做個詞雲來看看。

p_load(wordcloud2)

top3 %>% 
  count(words) %>%
  top_n(200) %>%    #只顯示出現次數最多的200個關鍵詞
  wordcloud2(size = 2, fontFamily = "微軟雅黑",
           color = "random-light", backgroundColor = "grey")

因為資料是隨機選的,所以不用太在意結果。此外,用jieba分詞的時候,自動呼叫了裡面的停用詞庫。但是,其實根據個性化的需求,可以定義更多的停用詞。

參考資料