[R語言專題] 運用R語言玩轉文字探勘 – 分詞篇

0

Last Updated on 2023-10-05

Home » R語言教學 » R語言專題 » [R語言專題] 運用R語言玩轉文字探勘 – 分詞篇

要讓電腦能夠理解並處理這些文字資料,我們首先必須將其分割成更小、更結構化的單位,這就是所謂的「分詞」。


在現今的資訊時代,文字資料無所不在,從社交媒體、新聞報導到學術研究,文字都是我們最主要的資訊來源。然而,要讓電腦能夠理解並處理這些文字資料,我們首先必須將其分割成更小、更結構化的單位,這就是所謂的「分詞」。

分詞介紹

分詞在英文稱作「tokenization」,指的是將長文字或句子分割成單詞、詞組或其他有意義的單位的過程。例如,句子「我愛台灣」經過分詞後,可能會被分割成「我」、「愛」和「台灣」三個單詞。

詳細一點解釋,文本資料並不像數字或表格,它沒有固定的格式或結構,這種資料就是非結構化(unstructured),平常的表格則是結構化的(structured)。

透過分詞,我們可以將這些文本資料轉換成一種更結構化的形式,使其更容易被分析和處理。例如,一篇文章可以被分割成句子,句子又可以被分割成單詞或詞組。這樣的層次結構,使我們能夠更深入地了解文本資料的內容和結構。

從人的觀點來看,分詞後的文本資料更易於閱讀和理解。而從機器的角度,分詞讓文本資料更容易被處理和分析。例如,當我們想要計算某個單詞在文本中出現的頻率,或是想要了解哪些單詞常常一起出現時,分詞就成了首要的步驟。此外,許多自然語言處理的技術,如文字雲、情感分析或主題模型,都需要先進行分詞。

分詞方法

基於規則的分詞

這種方法主要依賴語言學的規則來進行分詞。例如,中文中的「,」和「。」常常是句子的分隔符號,而英文則可能使用空格來分隔單詞。通常,基於規則的分詞會有一套預定義的規則,並按照這些規則來分割文本。雖然這種方法相對簡單,但可能不適合所有的文本或語言,特別是當文本中存在大量俚語、新詞或縮寫時。

基於統計的分詞

這種方法主要是透過分析大量的文本資料,來了解哪些詞組或單詞常常一起出現。透過這些統計資料,可以推測出可能的詞組邊界。例如,「中信兄弟」這個詞組在許多文本中經常一起出現,所以系統可能會將其識別為一個詞組,而不是分開為「中信」和「兄弟」。這種方法特別適用於那些沒有固定語法規則或大量新詞的語言。

混合型的分詞

混合型分詞結合了基於規則和統計的方法,它可能首先使用規則進行初步的分詞,然後再使用統計方法進行微調,也有可能順序對調。

分詞流程

分詞流程牽涉到的不只有利用規則和統計分詞而已,若在預處理上(pre-processing)上多下功夫,將可以大幅提升分詞品質。

另外,分詞後也可以適當篩選結果,這樣一來,將分詞結果應用到後續分析時,也能提取更多洞見。

我們同樣運用總統演說的資料,來展示分詞流程!

library(tidytext)
library(tidyverse)
df_speech_clean <- read_csv("data/df_speech_clean.csv")
df_speech_clean %>% glimpse()
#> Rows: 24
#> Columns: 5
#> $ id        <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 1…
#> $ text      <chr> "大會主席、各位貴賓、各位親愛的父老兄弟姐妹:\n今天是中華民…
#> $ title     <chr> "總統蒞臨中華民國八十六年國慶大會致詞", "總統蒞臨中華民國八…
#> $ date      <date> 1997-10-15, 1998-10-14, 1999-10-13, 2000-10-18, 2001-10-17,…
#> $ president <chr> "李登輝", "李登輝", "李登輝", "陳水扁", "陳水扁", "陳水扁", …
df_speech_sample <- df_speech_clean %>% select(text) %>% head(1)

分詞之前

以中文和英文語料來說,至少會有這些任務:

  • 繁體轉簡體:有些分詞引擎專門針對繁/簡體中文訓練,因此最好先轉換。
  • 大寫轉小寫:這個以英文為主,確保分詞後得到的詞彙是一致的。
  • 去除標點符號和數字:若語料規模巨大,預先篩掉後可以省下不少時間,但中文斷詞不一定要。
  • 去除或替換特定詞彙:例如網址、電子郵件地址等,可能會影響分詞結果。
  • 處理空白和縮寫:避免不必要的空白和縮寫,這都能確保分詞的一致性。
  • 編碼轉換:有些語料混雜不同編碼,在分詞前要先完成轉換。
  • 處理HTML/XML標記:有時候爬蟲時下載整包原始碼,文本就會不夠乾淨。
  • 去除非文字字元:以語音轉文字的逐字稿來說,可能有音樂或者笑聲標記。
  • 標準化:確保語料中用法一致,替換特定詞彙如台灣變成臺灣、“u”替換為”you”。
  • 去除或處理特殊字元和符號:如@、#、$等。

這些步驟主要會用stringr搭配dplyr完成任務。

分詞之後

  • 去除停用詞(stopwords):詞彙被分出後,可以直接串停用詞再刪掉。
  • 詞幹提取和詞形還原:英文語料中常見,例如將名詞複數、過去式/第三人稱單數動詞還原。
  • 處理n-gram:考慮單詞的相對順序與共同出現次數,可以在分詞時便指定,也可於分詞後計算。
  • 去除頻率過高或過低的詞:統計上太過罕見的詞可能對分析沒有幫助,甚至讓文本變得太過稀疏。
  • 詞袋模型(BoW)或詞向量:將單詞轉換為數字表示。

利用R語言分詞

利用R語言分詞 – 預處理

上面有提到,在斷詞前其實有些任務要先進行,我們結合dplyrstringr,再搭配其他套件展示具體怎麼做吧!

# 為了簡轉繁函數 toTrad()
library(tmcn)
df_speech_pre <- 
  df_speech_sample %>% 
  # 繁體轉簡體
  mutate(text = toTrad(text)) %>%
  # 大寫轉小寫
  mutate(text = str_to_lower(text)) %>%
  # 去除標點符號和數字
  # mutate(text = str_remove_all(text, "[:punct:]|[0-9]")) %>%
  # 處理空白
  mutate(text = str_remove_all(text, " |\\n|\\r|\\t")) %>%
  # 編碼轉換:如果有需要才做例如 BIG5 轉 UTF8
  # mutate(text = iconv(text, "BIG5", "UTF-8")) %>%
  # 處理HTML/XML標記:沒遇到但可以看範例程式碼
  # mutate(text = str_remove_all(text, "<html>|</html>")) %>%
  # 去除非文字字元:whisper API 常有這種
  # mutate(text = str_remove_all(text, "[music]")) %>%
  # 標準化
  mutate(text = str_replace_all(text, "台灣", "臺灣")) #%>%
  # 去除或處理特殊字元和符號:暫時想不到
  # mutate(text = str_replace_all(text, " "))
df_speech_pre
#> # A tibble: 1 × 1
#>   text                                                                         
#>   <chr>                                                                        
#> 1 大會主席、各位貴賓、各位親愛的父老兄弟姐妹:今天是中華民國八十六年國慶日,海…

分詞流程 – 利用 tidytext

斷詞時,tidytext會使用另一個套件tokenizers,並將分詞功能包在unnest_tokens()函數之中。為方便起見,我們只保留第一篇演講內容,再以之為分詞標的。先快速看一下分詞結果:

library(tidytext)
df_stop <- read_table("data/停用詞-繁體中文.txt", col_names = F) %>% rename(stopword = 1)

df_speech_pre %>% select(text) %>% head(1) %>%
  unnest_tokens(output = word, input = text, token = "words")
#> # A tibble: 388 × 1
#>    word  
#>    <chr> 
#>  1 大會  
#>  2 主席  
#>  3 各位  
#>  4 貴賓  
#>  5 各位  
#>  6 親愛的
#>  7 父老  
#>  8 兄弟  
#>  9 姐妹  
#> 10 今天是
#> # &#x2139; 378 more rows

其中,output參數為產出分詞結果的欄位名稱,input為分詞標的,token則是分詞方式,除了預設的words以外,常用還有characters(字母)、ngramssentencesparagraphsregex等選項。

因為文章中單一句子長度太長,我們改看另一個案例,用來說明strip_punct,這個參數可以決定是否要保留標點符號。

# 預設: 踢掉標點符號
tibble(text = "你好嗎?我很好,真的很好!") %>% select(text) %>% head(1) %>%
  unnest_tokens(output = word, input = text, token = "words", strip_punct = T)
#> # A tibble: 5 × 1
#>   word  
#>   <chr> 
#> 1 你好嗎
#> 2 我    
#> 3 很好  
#> 4 真的  
#> 5 很好

# 將參數修改為FALSE
tibble(text = "你好嗎?我很好,真的很好!") %>% select(text) %>% head(1) %>%
  unnest_tokens(output = word, input = text, token = "words", strip_punct = F)
#> # A tibble: 8 × 1
#>   word  
#>   <chr> 
#> 1 你好嗎
#> 2 ?    
#> 3 我    
#> 4 很好  
#> 5 ,    
#> 6 真的  
#> 7 很好  
#> 8 !

除了words以外,我們最常使用的分詞單位為ngrams,它的意思是「詞彙的連續序列」。這是什麼意思呢?舉例來說,如果我們在分詞後計算每個詞會出現的頻率,可能可以看到總統提了許多次中國,也提到很多次台灣,同時在演講中還提到人民與政府。

然而,若我們關注到底總統講的是「中國政府」還是「中國人民」、著墨更多在「台灣政府」抑或「台灣人民」,則我們就是在看bigram,也就是兩個連續詞彙的排列。依此類推我們可以看到trigram,甚至更多。

為什麼我們不直接數「中國」和「政府出現的次數」?因為「政府」可能來自「台灣政府」而非「中國政府」,查看n-gram可以幫助我們看到詞彙共同出現的關係。底下我們來看從n=1到n=3的分詞結果。

df_speech_pre %>% select(text) %>% head(1) %>%
  unnest_tokens(output = word, input = text, token = "ngrams", n = 1)
#> # A tibble: 388 × 1
#>    word  
#>    <chr> 
#>  1 大會  
#>  2 主席  
#>  3 各位  
#>  4 貴賓  
#>  5 各位  
#>  6 親愛的
#>  7 父老  
#>  8 兄弟  
#>  9 姐妹  
#> 10 今天是
#> # &#x2139; 378 more rows
df_speech_pre %>% select(text) %>% head(1) %>%
  unnest_tokens(output = bigram, input = text, token = "ngrams", n = 2)
#> # A tibble: 387 × 1
#>    bigram         
#>    <chr>          
#>  1 大會 主席      
#>  2 主席 各位      
#>  3 各位 貴賓      
#>  4 貴賓 各位      
#>  5 各位 親愛的    
#>  6 親愛的 父老    
#>  7 父老 兄弟      
#>  8 兄弟 姐妹      
#>  9 姐妹 今天是    
#> 10 今天是 中華民國
#> # &#x2139; 377 more rows
df_speech_pre %>% select(text) %>% head(1) %>%
  unnest_tokens(output = trigram, input = text, token = "ngrams", n = 3)
#> # A tibble: 386 × 1
#>    trigram             
#>    <chr>               
#>  1 大會 主席 各位      
#>  2 主席 各位 貴賓      
#>  3 各位 貴賓 各位      
#>  4 貴賓 各位 親愛的    
#>  5 各位 親愛的 父老    
#>  6 親愛的 父老 兄弟    
#>  7 父老 兄弟 姐妹      
#>  8 兄弟 姐妹 今天是    
#>  9 姐妹 今天是 中華民國
#> 10 今天是 中華民國 八十
#> # &#x2139; 376 more rows

除了分成詞彙單元,我們也可以分成句子、段落等,現在還看不太出用途,但之後有其他應用,例如從句子長度評估演講風格、查找特定句型等,都是可能應用方式。

df_speech_pre %>% select(text) %>% head(1) %>%
  unnest_tokens(output = word, input = text, token = "sentences")
#> # A tibble: 13 × 1
#>    word                                                                         
#>    <chr>                                                                        
#>  1 大會主席、各位貴賓、各位親愛的父老兄弟姐妹:今天是中華民國八十六年國慶日,海…
#>  2 八十六年來,中華民國經歷了許多的挫折和危難,但是,在全體同胞的精誠團結、齊心…
#>  3 今天,在政治上,我們已經實現了「主權在民」的理想,昂然邁進全民民主的新時代;…
#>  4 然而,歷史的巨輪不斷向前,國家的發展也永無止境。                             
#>  5 面對即將來臨的二十一世紀,我們不能自滿於既有的成就,而必須以更前瞻的思維,和…
#>  6 我們相信,自由是全人類共同追求的目標,民主是世界文明發展的方向。             
#>  7 中華民國在實踐自由民主的理想之後,也願善盡心力,與國際社會分享發展經驗,為全…
#>  8 同時,我們也秉持善意,積極推動兩岸關係,希望兩岸的中國人能在自由、民主、均富…
#>  9 深盼中共當局能體認世界潮流趨勢,務實面對兩岸分治的事實,以民眾的福祉為依歸,…
#> 10 各位親愛的父老兄弟姐妹:在這個深具歷史意義的偉大日子,我們回顧過去,對自己的…
#> 11 隻要我們繼續堅持民主改革的理想,發揮團結奮鬥的精神,就一定能掌握發展契機,開…
#> 12 敬祝中華民國國運昌隆!                                                       
#> 13 大家健康愉快,謝謝各位!::::::::::::

單純看斷詞後詞彙出現次數。

# 沒去除停用詞
df_speech_pre %>% select(text) %>% head(1) %>%
  unnest_tokens(output = word, input = text, token = "words") %>%
  count(word, sort = T)
#> # A tibble: 238 × 2
#>    word      n
#>    <chr> <int>
#>  1 的       39
#>  2 我們     11
#>  3 在        8
#>  4 發展      7
#>  5 民主      6
#>  6 上        5
#>  7 也        5
#>  8 更        5
#>  9 社會      5
#> 10 自由      5
#> # &#x2139; 228 more rows

# 有去除停用詞
df_speech_pre %>% select(text) %>% head(1) %>%
  unnest_tokens(output = word, input = text, token = "words") %>%
  count(word, sort = T) %>%
  anti_join(df_stop, by = c("word" = "stopword"))
#> # A tibble: 199 × 2
#>    word         n
#>    <chr>    <int>
#>  1 發展         7
#>  2 民主         6
#>  3 社會         5
#>  4 自由         5
#>  5 中華民國     4
#>  6 國家         4
#>  7 繁榮         4
#>  8 兩岸         3
#>  9 基礎         3
#> 10 成就         3
#> # &#x2139; 189 more rows

分詞流程 – 利用 quanteda

library(quanteda)
# Chinese stopwords
ch_stop <- quanteda::stopwords("zh", source = "misc")
# read text files
corp <- corpus(df_speech_pre)
# tokenize
ch_toks <- corp %>% 
    tokens(remove_punct = TRUE) %>%
    tokens_remove(pattern = ch_stop)

# construct a dfm
ch_dfm <- dfm(ch_toks)
topfeatures(ch_dfm)
#>     我們     發展     民主     自由     社會       更 中華民國     國家 
#>       11        7        6        5        5        5        4        4 
#>     繁榮     成就 
#>        4        3

分詞流程 – 利用 jiebaR

如果是改用繁體中文語料最常使用的 jiebaR 呢?

library(jiebaR)

### your code
### segment
cutter <- worker("tag", stop_word = "data/停用詞-繁體中文.txt")
vector_word <- c("中華民國", "蔡英文", "李登輝", "蔣中正", "蔣經國", "李登輝", "陳水扁", "馬英九")
new_user_word(cutter, words = vector_word)
#> [1] TRUE
# reg_space <- "%E3%80%80" %>% curl::curl_escape()

### text part
df_speech_seg <-
  df_speech_pre %>% 
  mutate(text = str_replace_all(text, "台灣|臺灣", "臺灣")) %>%
  mutate(text = str_remove_all(text, "\\n|\\r|\\t|:| | ")) %>%
  # mutate(text = str_remove_all(text, reg_space)) %>%
  mutate(text = str_remove_all(text, "[a-zA-Z0-9]+")) %>%
  mutate(text_segment = purrr::map(text, function(x)segment(x, cutter))) %>%
  mutate(text_POS = purrr::map(text_segment, function(x)names(x))) %>%
  unnest(c(text_segment, text_POS)) %>%
  select(-text, everything(), text)

df_speech_seg %>% count(text_segment, sort = T) %>%
  anti_join(df_stop, by = c("text_segment" = "stopword"))
#> # A tibble: 187 × 2
#>    text_segment     n
#>    <chr>        <int>
#>  1 發展             7
#>  2 國家             5
#>  3 社會             5
#>  4 中華民國         4
#>  5 民主             4
#>  6 繁榮             4
#>  7 自由             4
#>  8 兩岸             3
#>  9 基礎             3
#> 10 歷史             3
#> # &#x2139; 177 more rows

分詞品質與限制

我們先來實際查看斷詞實際結果差異。

tibble(rank = 1:10) %>%
  bind_cols(df_speech_seg %>% count(text_segment, sort = T) %>%
  anti_join(df_stop, by = c("text_segment" = "stopword")) %>%
  select(jieba = 1) %>% head(10)) %>%
  bind_cols(df_speech_pre %>% select(text) %>% head(1) %>%
  unnest_tokens(output = word, input = text, token = "words") %>%
  count(word, sort = T) %>%
  anti_join(df_stop, by = c("word" = "stopword")) %>%
  select(tidyext = 1) %>% head(10)) %>%
  bind_cols(tibble(quanteda = names(topfeatures(ch_dfm))))
#> # A tibble: 10 × 4
#>     rank jieba    tidyext  quanteda
#>    <int> <chr>    <chr>    <chr>   
#>  1     1 發展     發展     我們    
#>  2     2 國家     民主     發展    
#>  3     3 社會     社會     民主    
#>  4     4 中華民國 自由     自由    
#>  5     5 民主     中華民國 社會    
#>  6     6 繁榮     國家     更      
#>  7     7 自由     繁榮     中華民國
#>  8     8 兩岸     兩岸     國家    
#>  9     9 基礎     基礎     繁榮    
#> 10    10 歷史     成就     成就

有幾個可能影響。

第一當然是斷詞引擎或稱分詞器(tokenizer)差異,這會大幅影響斷詞成果,因為品質取決斷詞引擎的功力是否高深,而這又往前牽涉到該分詞器如何訓練、用什麼資料訓練。

第二則是斷詞前提供辭典(dictionary)帶來差異。

第三則是停用詞辭典,在 jiebaRtidytext 中我同樣使用繁體中文語料常見停用詞,簡體中文語料可以參考其他資源。但是在 quanteda 中,套件本身就有預設停用詞辭典,因此不適用。

其他挑戰還有多語言文本、辨別專有名詞、處理俚語等(例如PTT、Dcard各自文化)。

在R語言中使用ckip

接下來,我們特別花一個小節介紹如何在R語言中使用由中研院開發的一系列斷詞工具「CKIP」。根據中研院介紹,這個工具可以斷詞(Word Segmentation)、辨識詞性(POS Tagging)與實體(NER)。

你可以參考中研院GitHub上的中文介紹英文介紹。不過,因為R語言並沒有打包成套件,若是要使用CKIP,我們得要在R語言中先呼叫Python,而這要透過套件reticulate才能夠達成。

所以我們再來看一下reticulate的官方介紹,它的用途就是通往Python的介面(R Interface to Python)。我們首先要安裝這個套件,接著安裝Python,再接著安裝Python中你想用的套件,例如常見的NympyPandas等,最後則有兩種方法使用,一種是把Python物件當成R物件使用,另一種則是直接在R語言當中撰寫Python程式碼。

即使你本來就已經Python,還是建議你重新安裝Python,因為不熟悉安裝環境與路徑的使用者,實在太容易遇到Error了。我記得初次用這個套件的時候,安裝想用的套件,滿心期待要開始在R語言當中自由使用Python,沒想到先是遇到我安裝的Python套件其實沒有安裝在調用Python版本的路徑之下,接著則是遇到套件版本錯誤的問題,一重又一重的挑戰襲來,讓人非常痛苦。

此外,不只是Python本身,還有環境的問題,例如你會發現有許多人建議要用conda,但不熟悉anaconda的朋友也會一直出錯,你可能用了好幾次py_discover_config()use_python(),接著用py_avalible()發現是TRUE,以為大功告成,沒想到再度中計。

以下就是我走完一遍上述流程,你可以嘗試看看。本段落原先是參考2019年在R語言社團中的一篇文章,但有略作更動,因為原始遇到錯誤,所以我還是重新安裝了一次python、建立不依賴conda的環境,最後將套件安裝在這個環境之中。

# 若尚未安裝記得先安裝
library(reticulate)
# 我想安裝 3.9 版本的 python
version <- "3.9.12"
# 因為我第一次執行時安裝了所以註解掉
# install_python(version)
# 創建用這個版本 python 的新環境
virtualenv_create("my-environment", version = version)
#> virtualenv: my-environment
use_virtualenv("my-environment")

# 你要找設置才會用到的函數可以嚕略
# py_discover_config()

# 確認有沒有 python
os = import("os")
os$listdir(".")
#> [1] "index.Rmd" "figs"      "data"
py_available() # TRUE 
#> [1] TRUE

# 安裝套件,因為我安裝了所以跳過
# py_install(packages = 'tensorflow', pip = T)
# py_install(packages = 'ckiptagger', pip = T)

# 匯入套件
ckip <- import("ckiptagger")

# 如果你還有下載模型檔案記得先去下載
# model:
# https://drive.google.com/drive/folders/105IKCb88evUyLKlLondvDBoh7Dy_I1tm

# 接著讀取模型檔
ws = ckip$WS("/Users/macuser/Documents/GitHub/text-mining/ckip/data") # 斷詞
pos = ckip$POS("/Users/macuser/Documents/GitHub/text-mining/ckip/data") # 詞性
ner = ckip$NER("/Users/macuser/Documents/GitHub/text-mining/ckip/data") # 實體辨識

### ckip test
senten = list("傅達仁今將執行安樂死,卻突然爆出自己20年前遭緯來體育台封殺,他不懂自己哪裡得罪到電視台。",
              "美國參議院針對今天總統布什所提名的勞工部長趙小蘭展開認可聽證會,預料她將會很順利通過參議院支持,成為該國有史以來第一位的華裔女性內閣成員。",
              "土地公有政策??還是土地婆有政策。.",
              "… 你確定嗎… 不要再騙了……",
              "最多容納59,000個人,或5.9萬人,再多就不行了.這是環評的結論.",
              "科長說:1,坪數對人數為1:3。2,可以再增加。")

# ws 斷詞
word_senten = ws(senten)
word_senten
#> [[1]]
#>  [1] "傅達仁" "今"     "將"     "執行"   "安樂死" ","     "卻"     "突然"  
#>  [9] "爆出"   "自己"   "20"     "年"     "前"     "遭"     "緯來"   "體育台"
#> [17] "封殺"   ","     "他"     "不"     "懂"     "自己"   "哪裡"   "得罪到"
#> [25] "電視台" "。"    
#> 
#> [[2]]
#>  [1] "美國"     "參議院"   "針對"     "今天"     "總統"     "布什"    
#>  [7] "所"       "提名"     "的"       "勞工部長" "趙小蘭"   "展開"    
#> [13] "認可"     "聽證會"   ","       "預料"     "她"       "將"      
#> [19] "會"       "很"       "順利"     "通過"     "參議院"   "支持"    
#> [25] ","       "成為"     "該"       "國"       "有史以來" "第一"    
#> [31] "位"       "的"       "華裔"     "女性"     "內閣"     "成員"    
#> [37] "。"      
#> 
#> [[3]]
#>  [1] "土地公" "有"     "政策"   "?"      "?"     "還是"   "土地"   "婆"    
#>  [9] "有"     "政策"   "。"     "."     
#> 
#> [[4]]
#>  [1] "…"    " "    "你"   "確定" "嗎"   "…"    " "    "不要" "再"   "騙"  
#> [11] "了"   "…"    "…"   
#> 
#> [[5]]
#>  [1] "最多"   "容納"   "59,000" "個"     "人"     ","      "或"     "5.9萬" 
#>  [9] "人"     ","      "再"     "多"     "就"     "不行"   "了"     "."     
#> [17] "這"     "是"     "環評"   "的"     "結論"   "."     
#> 
#> [[6]]
#>  [1] "科長" "說"   ":1,"  "坪數" "對"   "人數" "為"   "1:3"  "。"   "2"   
#> [11] ","    "可以" "再"   "增加" "。"
# pos 詞性
pos_senten = pos(word_senten)
pos_senten
#> [[1]]
#>  [1] "Nb"             "Nd"             "D"              "VC"            
#>  [5] "Na"             "COMMACATEGORY"  "D"              "D"             
#>  [9] "VJ"             "Nh"             "Neu"            "Nf"            
#> [13] "Ng"             "P"              "Nb"             "Na"            
#> [17] "VC"             "COMMACATEGORY"  "Nh"             "D"             
#> [21] "VK"             "Nh"             "Ncd"            "VJ"            
#> [25] "Nc"             "PERIODCATEGORY"
#> 
#> [[2]]
#>  [1] "Nc"             "Nc"             "P"              "Nd"            
#>  [5] "Na"             "Nb"             "D"              "VC"            
#>  [9] "DE"             "Na"             "Nb"             "VC"            
#> [13] "VC"             "Na"             "COMMACATEGORY"  "VE"            
#> [17] "Nh"             "D"              "D"              "Dfa"           
#> [21] "VH"             "VC"             "Nc"             "VC"            
#> [25] "COMMACATEGORY"  "VG"             "Nes"            "Nc"            
#> [29] "D"              "Neu"            "Nf"             "DE"            
#> [33] "Na"             "Na"             "Na"             "Na"            
#> [37] "PERIODCATEGORY"
#> 
#> [[3]]
#>  [1] "Nb"               "V_2"              "Na"               "QUESTIONCATEGORY"
#>  [5] "QUESTIONCATEGORY" "Caa"              "Na"               "Na"              
#>  [9] "V_2"              "Na"               "PERIODCATEGORY"   "PERIODCATEGORY"  
#> 
#> [[4]]
#>  [1] "ETCCATEGORY" "WHITESPACE"  "Nh"          "VK"          "T"          
#>  [6] "ETCCATEGORY" "WHITESPACE"  "D"           "D"           "VC"         
#> [11] "Di"          "ETCCATEGORY" "ETCCATEGORY"
#> 
#> [[5]]
#>  [1] "VH"             "VJ"             "Neu"            "Nf"            
#>  [5] "Na"             "COMMACATEGORY"  "Caa"            "Neu"           
#>  [9] "Na"             "COMMACATEGORY"  "D"              "D"             
#> [13] "D"              "VH"             "T"              "PERIODCATEGORY"
#> [17] "Nep"            "SHI"            "Na"             "DE"            
#> [21] "Na"             "PERIODCATEGORY"
#> 
#> [[6]]
#>  [1] "Na"             "VE"             "Neu"            "Na"            
#>  [5] "P"              "Na"             "VG"             "Neu"           
#>  [9] "PERIODCATEGORY" "Neu"            "COMMACATEGORY"  "D"             
#> [13] "D"              "VHC"            "PERIODCATEGORY"
# ner 實體辨識
ner_senten = ner(word_senten, pos_senten)
ner_senten
#> [[1]]
#> {(18, 22, 'DATE', '20年前'), (0, 3, 'PERSON', '傅達仁'), (23, 28, 'ORG', '緯來體育台')}
#> 
#> [[2]]
#> {(11, 13, 'PERSON', '布什'), (42, 45, 'ORG', '參議院'), (21, 24, 'PERSON', '趙小蘭'), (60, 62, 'NORP', '華裔'), (56, 58, 'ORDINAL', '第一'), (0, 2, 'GPE', '美國'), (7, 9, 'DATE', '今天'), (17, 21, 'ORG', '勞工部長'), (2, 5, 'ORG', '參議院')}
#> 
#> [[3]]
#> {(0, 3, 'PERSON', '土地公')}
#> 
#> [[4]]
#> set()
#> 
#> [[5]]
#> {(14, 18, 'CARDINAL', '5.9萬'), (4, 10, 'CARDINAL', '59,000')}
#> 
#> [[6]]
#> {(16, 17, 'CARDINAL', '2'), (14, 15, 'CARDINAL', '3'), (12, 13, 'CARDINAL', '1'), (4, 6, 'CARDINAL', '1,')}

接下來則是在R語言裡面寫python script的版本。

# 匯入套件
from ckiptagger import data_utils, construct_dictionary, WS, POS, NER
ws = WS("/Users/macuser/Documents/GitHub/text-mining/ckip/data")
pos = POS("/Users/macuser/Documents/GitHub/text-mining/ckip/data")
ner = NER("/Users/macuser/Documents/GitHub/text-mining/ckip/data")

# 建立辭典
word_to_weight = {
    "土地公": 1,
    "土地婆": 1,
    "公有": 2,
    "": 1,
    "來亂的": "啦",
    "緯來體育台": 1,
}
dictionary = construct_dictionary(word_to_weight)
print(dictionary)

#> [(2, {'公有': 2.0}), (3, {'土地公': 1.0, '土地婆': 1.0}), (5, {'緯來體育台': 1.0})]


# 執行ckip任務
sentence_list = [
    "傅達仁今將執行安樂死,卻突然爆出自己20年前遭緯來體育台封殺,他不懂自己哪裡得罪到電視台。",
    "美國參議院針對今天總統布什所提名的勞工部長趙小蘭展開認可聽證會,預料她將會很順利通過參議院支持,成為該國有史以來第一位的華裔女性內閣成員。",
    "",
    "土地公有政策??還是土地婆有政策。.",
    "… 你確定嗎… 不要再騙了……",
    "最多容納59,000個人,或5.9萬人,再多就不行了.這是環評的結論.",
    "科長說:1,坪數對人數為1:3。2,可以再增加。",
]

word_sentence_list = ws(
    sentence_list,
    # sentence_segmentation = True, # To consider delimiters
    # segment_delimiter_set = {",", "。", ":", "?", "!", ";"}), # This is the defualt set of delimiters
    # recommend_dictionary = dictionary1, # words in this dictionary are encouraged
    # coerce_dictionary = dictionary2, # words in this dictionary are forced
)
word_sentence_list

#> [['傅達仁', '今', '將', '執行', '安樂死', ',', '卻', '突然', '爆出', '自己', '20', '年', '前', '遭', '緯來', '體育台', '封殺', ',', '他', '不', '懂', '自己', '哪裡', '得罪到', '電視台', '。'], ['美國', '參議院', '針對', '今天', '總統', '布什', '所', '提名', '的', '勞工部長', '趙小蘭', '展開', '認可', '聽證會', ',', '預料', '她', '將', '會', '很', '順利', '通過', '參議院', '支持', ',', '成為', '該', '國', '有史以來', '第一', '位', '的', '華裔', '女性', '內閣', '成員', '。'], [], ['土地公', '有', '政策', '?', '?', '還是', '土地', '婆', '有', '政策', '。', '.'], ['…', ' ', '你', '確定', '嗎', '…', ' ', '不要', '再', '騙', '了', '…', '…'], ['最多', '容納', '59,000', '個', '人', ',', '或', '5.9萬', '人', ',', '再', '多', '就', '不行', '了', '.', '這', '是', '環評', '的', '結論', '.'], ['科長', '說', ':1,', '坪數', '對', '人數', '為', '1:3', '。', '2', ',', '可以', '再', '增加', '。']]

pos_sentence_list = pos(word_sentence_list)
pos_sentence_list

#> [['Nb', 'Nd', 'D', 'VC', 'Na', 'COMMACATEGORY', 'D', 'D', 'VJ', 'Nh', 'Neu', 'Nf', 'Ng', 'P', 'Nb', 'Na', 'VC', 'COMMACATEGORY', 'Nh', 'D', 'VK', 'Nh', 'Ncd', 'VJ', 'Nc', 'PERIODCATEGORY'], ['Nc', 'Nc', 'P', 'Nd', 'Na', 'Nb', 'D', 'VC', 'DE', 'Na', 'Nb', 'VC', 'VC', 'Na', 'COMMACATEGORY', 'VE', 'Nh', 'D', 'D', 'Dfa', 'VH', 'VC', 'Nc', 'VC', 'COMMACATEGORY', 'VG', 'Nes', 'Nc', 'D', 'Neu', 'Nf', 'DE', 'Na', 'Na', 'Na', 'Na', 'PERIODCATEGORY'], [], ['Nb', 'V_2', 'Na', 'QUESTIONCATEGORY', 'QUESTIONCATEGORY', 'Caa', 'Na', 'Na', 'V_2', 'Na', 'PERIODCATEGORY', 'PERIODCATEGORY'], ['ETCCATEGORY', 'WHITESPACE', 'Nh', 'VK', 'T', 'ETCCATEGORY', 'WHITESPACE', 'D', 'D', 'VC', 'Di', 'ETCCATEGORY', 'ETCCATEGORY'], ['VH', 'VJ', 'Neu', 'Nf', 'Na', 'COMMACATEGORY', 'Caa', 'Neu', 'Na', 'COMMACATEGORY', 'D', 'D', 'D', 'VH', 'T', 'PERIODCATEGORY', 'Nep', 'SHI', 'Na', 'DE', 'Na', 'PERIODCATEGORY'], ['Na', 'VE', 'Neu', 'Na', 'P', 'Na', 'VG', 'Neu', 'PERIODCATEGORY', 'Neu', 'COMMACATEGORY', 'D', 'D', 'VHC', 'PERIODCATEGORY']]

entity_sentence_list = ner(word_sentence_list, pos_sentence_list)
entity_sentence_list

#> [{(18, 22, 'DATE', '20年前'), (0, 3, 'PERSON', '傅達仁'), (23, 28, 'ORG', '緯來體育台')}, {(11, 13, 'PERSON', '布什'), (42, 45, 'ORG', '參議院'), (21, 24, 'PERSON', '趙小蘭'), (60, 62, 'NORP', '華裔'), (56, 58, 'ORDINAL', '第一'), (0, 2, 'GPE', '美國'), (7, 9, 'DATE', '今天'), (17, 21, 'ORG', '勞工部長'), (2, 5, 'ORG', '參議院')}, set(), {(0, 3, 'PERSON', '土地公')}, set(), {(14, 18, 'CARDINAL', '5.9萬'), (4, 10, 'CARDINAL', '59,000')}, {(16, 17, 'CARDINAL', '2'), (14, 15, 'CARDINAL', '3'), (12, 13, 'CARDINAL', '1'), (4, 6, 'CARDINAL', '1,')}]

以上就是利用ckip斷詞。其實和前面三個套件相比,速度會慢上非常多,那為什麼還要使用它?

它有幾大好處,第一就是專門針對繁體中文設計,訓練上利用台灣語料,所以會比沒有針對繁體中文的tidytextquantedajiebaR都還要好,第二是它的 POS 和 NER 功能很強,其他套件即使有 POS 或 NER 都比不上,像是 jiebaR 的 POS 就有點不怎麼樣,甚至連 NER 都沒有,第三是它和 jiebaR一樣都提供自建詞典的功能,對上面提到要考慮語料特性的任務來說滿好的。

至於實際上到底要選哪個,這還是要回歸基本,看你喜歡什麼。我最常用tidytext搭配jiebaR。

No Comments

Leave a Reply