8 中文文本資料處理

(投影片 / 程式碼 / 影片)

8.1 斷詞

jieba 是一個用於中文斷詞的 (Python) 套件。jiebaR 則是 jieba 的 R 版本。

使用 jiebaR 進行斷詞只須兩個步驟:

  1. 使用 worker() 初始化斷詞設定
  2. 使用 segment() 將文字斷詞
  • jiebaR::segment() 回傳一個 character vector,vector 內的每個元素都是一個被斷出來的詞:

    library(jiebaR)
    seg <- worker()
    txt <- "失業的熊讚陪柯文哲看銀翼殺手" 
    segment(txt, seg)
    #> [1] "失業" "的熊" "讚"   "陪"   "柯文" "哲看" "銀翼" "殺手"
  • jiebaR 的斷詞有時會不太精準,尤其是遇到專有名詞或是特殊詞彙時,這些詞彙時常會被斷開。若想避免這種情況,可以新增一份自訂辭典 (儲存在一份純文字檔,每個詞佔一行),例如 user_dict.txt 的內容如下:

    熊讚
    柯文哲
    銀翼殺手

    如此在 worker() 中設定自訂辭典,jiebaR 就不會將這些詞彙斷開:

    # With user dict
    seg <- worker(user = "assets/corpus/user_dict.txt")
    segment(txt, seg)
    #> [1] "失業"     "的"       "熊讚"     "陪"       "柯文哲"   "看"       "銀翼殺手"

8.2 使用 data frame 建立語料庫

我們的目標是建立一個 data frame 儲存文本資料。在這個 data frame 中,每一個 row 代表一篇文章,每個變項 (column) 記錄著該篇文章的某個資訊。根據文本資料的來源,該 data frame 可能會有不同數量的變項,例如,「文章發表日期」、「作者」、「標題」、「主題」等。但最重要的是,此 data frame 至少需具備兩個變項 — 「文章 id」與「(斷完詞的) 文章內文」。下方使用一個簡單的例子 (3 篇文章) 說明如何建立這種 data frame。

  • 第一步是將 docs 內的各篇文章 (character vector) 進行斷詞,並在斷完詞後,將文章的內容存入另一個等長的 character vector 中。同篇文章中,被斷開的詞之間以一個全形空白字元分隔:

    library(jiebaR)
    
    # Data: 3 篇文章
    docs <- c(
      "蝴蝶和蜜蜂們帶著花朵的蜜糖回來了,羊隊和牛群告別了田野回家了,火紅的太陽也滾著火輪子回家了,當街燈亮起來向村莊道過晚安,夏天的夜就輕輕地來了。",
      "朋友買了一件衣料,綠色的底子帶白色方格,當她拿給我們看時,一位對圍棋十分感與趣的同學說:「啊,好像棋盤似的。」「我看倒有點像稿紙。」我說。「真像一塊塊綠豆糕。」一位外號叫「大食客」的同學緊接著說。",
      "每天,天剛亮時,我母親便把我喊醒,叫我披衣坐起。我從不知道她醒來坐了多久了。她看我清醒了,便對我說昨天我做錯了什麼事,說錯了什麼話,要我認錯,要我用功讀書。"
      )
    
    # Initialize jiebaR
    seg <- worker()
    
    docs_segged <- vector("character", length = length(docs))
    for (i in seq_along(docs)) {
      # Segment each element in docs
      segged <- segment(docs[i], seg)
    
      # Collapse the character vector into a string, separated by space
      docs_segged[i] <- paste0(segged, collapse = "\u3000")
    }
    
    docs_segged
    #> [1] "蝴蝶 和 蜜蜂 們 帶 著 花朵 的 蜜糖 回來 了 羊隊 和 牛群 告別 了 田野 回家 了 火紅 的 太陽 也 滾 著火 輪子 回家 了 當 街燈 亮 起來 向 村莊 道過 晚安 夏天 的 夜 就 輕輕地 來 了"                                      
    #> [2] "朋友 買 了 一件 衣料 綠色 的 底子 帶 白色 方格 當 她 拿給 我們 看時 一位 對 圍棋 十分 感與趣 的 同 學說 啊 好像 棋盤 似的 我 看 倒 有點像 稿紙 我 說 真 像 一塊塊 綠豆糕 一位 外號 叫 大 食客 的 同學 緊接著 說"
    #> [3] "每天 天剛亮 時 我 母親 便 把 我 喊醒 叫 我 披衣 坐起 我 從不 知道 她 醒來 坐 了 多久 了 她 看 我 清醒 了 便 對 我 說 昨天 我 做 錯 了 什麼 事 說錯 了 什麼 話 要 我 認錯 要 我 用功讀書"
  • 如此,我們就能使用這個斷完詞的 docs_segged 製作 data frame:

    docs_df <- tibble::tibble(
      doc_id = seq_along(docs_segged),
      content = docs_segged
    )
    
    knitr::kable(docs_df, align = "c")
    doc_id content
    1 蝴蝶 和 蜜蜂 們 帶 著 花朵 的 蜜糖 回來 了 羊隊 和 牛群 告別 了 田野 回家 了 火紅 的 太陽 也 滾 著火 輪子 回家 了 當 街燈 亮 起來 向 村莊 道過 晚安 夏天 的 夜 就 輕輕地 來 了
    2 朋友 買 了 一件 衣料 綠色 的 底子 帶 白色 方格 當 她 拿給 我們 看時 一位 對 圍棋 十分 感與趣 的 同 學說 啊 好像 棋盤 似的 我 看 倒 有點像 稿紙 我 說 真 像 一塊塊 綠豆糕 一位 外號 叫 大 食客 的 同學 緊接著 說
    3 每天 天剛亮 時 我 母親 便 把 我 喊醒 叫 我 披衣 坐起 我 從不 知道 她 醒來 坐 了 多久 了 她 看 我 清醒 了 便 對 我 說 昨天 我 做 錯 了 什麼 事 說錯 了 什麼 話 要 我 認錯 要 我 用功讀書

8.3 tidytext framework

  • tidytext 套件是 R 生態圈中比較近期的 text mining 套件,它將 tidyverse 的想法運用到文本資料處理上,換言之,就是使用 data frame 的資料結構去表徵和處理文本資料。

  • 使用 tidytext 的方法處理文本資料有好有壞。

    • 好處是使用者能輕易地結合 dplyrggplot2 於文本分析中,因而能快速地視覺化文本資料。

    • 壞處是,在 tidytext framework 之下,文章的內部 (i.e. 詞彙與詞彙之間的) 結構會消失,因為它對於文本的想法是 bag-of-words

      tidytext 所倡導的儲存文本資料的格式是 one-token-per-document-per-row,亦即,在一個 data frame 中,每一橫列 (row) 是一篇文章中的一個 token。因此,若有兩篇文章,第一篇被斷成 38 個詞彙,第二篇被斷成 20 個詞彙,則共需要一個 58 列 (row) 的 data frame 來儲存這兩篇文章。

  • 一般而言,tidytext 的架構適合用於與詞頻有關的分析,例如,計算文章的 lexical diversity 或是透過情緒詞的詞頻進行情緒分析。

  • 透過 tidytext::unnest_tokens(),可以將 docs_df 中儲存之 (已斷詞) 文本資料,變成 tidytext format,i.e.,one-token-per-document-per-row 的 data frame:

    library(tidytext)
    library(dplyr)
    
    tidy_text_format <- docs_df %>%
      unnest_tokens(output = "word", input = "content",
                    token = "regex", pattern = "\u3000")  # 以空白字元作為 token 分隔依據
    
    tidy_text_format
    #> # A tibble: 139 x 2
    #>    doc_id word 
    #>     <int> <chr>
    #>  1      1 蝴蝶 
    #>  2      1 和   
    #>  3      1 蜜蜂 
    #>  4      1 們   
    #>  5      1 帶   
    #>  6      1 著   
    #>  7      1 花朵 
    #>  8      1 的   
    #>  9      1 蜜糖 
    #> 10      1 回來 
    #> # … with 129 more rows

8.3.1 詞頻表

  • 可以使用 dplyrgroup_by()summarise() 計算詞頻表:

    tidy_text_format %>%
      group_by(word) %>%
      summarise(n = n()) %>%
      arrange(desc(n))
    #> # A tibble: 99 x 2
    #>    word      n
    #>    <chr> <int>
    #>  1 了       11
    #>  2 我       11
    #>  3 的        6
    #>  4 她        3
    #>  5 說        3
    #>  6 一位      2
    #>  7 什麼      2
    #>  8 便        2
    #>  9 叫        2
    #> 10 和        2
    #> # … with 89 more rows
    # Equivalent to ...
    tidy_text_format %>%
      count(word) %>%
      arrange(desc(n))
    #> # A tibble: 99 x 2
    #>    word      n
    #>    <chr> <int>
    #>  1 了       11
    #>  2 我       11
    #>  3 的        6
    #>  4 她        3
    #>  5 說        3
    #>  6 一位      2
    #>  7 什麼      2
    #>  8 便        2
    #>  9 叫        2
    #> 10 和        2
    #> # … with 89 more rows

8.3.2 視覺化

library(ggplot2)
tidy_text_format %>%
  count(word) %>%
  mutate(word = reorder(word, n)) %>%   # 依照 n 排序文字
  top_n(10, n) %>%                      # 取 n 排名前 10 者
  ggplot() +
    geom_bar(aes(word, n), stat = "identity") +
    coord_flip()

8.4 quanteda framework

傳統 R 的 text mining 生態圈中,使用的是另一種 (高階) 資料結構儲存文本資料 — 語料庫 (corpus)。不同的套件有自己定義 corpus 的方式,且各自進行文本分析的流程與想法差異頗大。目前最流行、支援最多的兩個套件是 quantedatm。其中,quanteda 在中文支援以及說明與教學文件的完整度較高。

下方為 quanteda 套件的一些使用範例。欲比較完整地了解 quanteda,請閱讀 quanteda tutorials

  • 使用 quanteda 的好處在於它保留了文章的內部結構,例如,可以透過 quanteda::kwic() 去檢視特定詞彙或是片語出現的語境。與此同時,quanteda 也提供許多 bag-of-words 想法之下的函數。
  • quanteda 的缺點在於其內容龐雜,需要一些語料庫語言學的背景知識以及相當的時間摸索才能掌握。
library(quanteda)

# 將 data frame 轉換成 Corpus object
quanteda_corpus <- corpus(docs_df, 
                          docid_field = "doc_id", 
                          text_field = "content")
  • Key Word in Context

    # tokenize the corpus 
    q_tokens <- tokenizers::tokenize_regex(quanteda_corpus, "\u3000") %>%
      tokens()
    
    kwic(q_tokens, "我", window = 5, valuetype = "regex") %>%
      knitr::kable(align = "c")
    docname from to pre keyword post pattern
    2 15 15 白色 方格 當 她 拿給 我們 看時 一位 對 圍棋 十分
    2 29 29 學說 啊 好像 棋盤 似的 看 倒 有點像 稿紙 我
    2 34 34 我 看 倒 有點像 稿紙 說 真 像 一塊塊 綠豆糕
    3 4 4 每天 天剛亮 時 母親 便 把 我 喊醒
    3 8 8 時 我 母親 便 把 喊醒 叫 我 披衣 坐起
    3 11 11 便 把 我 喊醒 叫 披衣 坐起 我 從不 知道
    3 14 14 喊醒 叫 我 披衣 坐起 從不 知道 她 醒來 坐
    3 25 25 了 多久 了 她 看 清醒 了 便 對 我
    3 30 30 我 清醒 了 便 對 說 昨天 我 做 錯
    3 33 33 便 對 我 說 昨天 做 錯 了 什麼 事
    3 44 44 說錯 了 什麼 話 要 認錯 要 我 用功讀書
    3 47 47 話 要 我 認錯 要 用功讀書