iT邦幫忙

2023 iThome 鐵人賽

DAY 20
0
AI & Data

用R語言玩轉文字探勘系列 第 20

[Day 20] 利用R語言分析情感

  • 分享至 

  • xImage
  •  

情緒分析

情緒分析介紹

情緒/情感分析(sentiment analysis),簡單來說就是辨別話語中的情感。

最一開始的方式就像把文字拆解成積木,這塊積木是正面的、那塊積木也是正面的,相加在一起句子就會是正面的。例如「我今天吃到好吃的食物,心情很好。」句子裡面有「好吃」和「好」兩個詞,所以整句句子就是正面。除了正面以外,還有負面和中性的詞。

因此,我們要先準備一個情感辭典(dictionary),裡面有常見的正面與負面詞,把辭典和斷詞後的結果串連,就可以得到一段文本的情感。

不過,這種方法很快就碰上挑戰,例如一個常見的抵消問題,「我雖然吃到好吃的食物,卻不快樂。」從人類判斷來看,這句話應該是負面,但從詞典法卻會因為同時出現「好吃」和「不快樂」兩個詞,將情感分數抵銷,這樣這句話就變得中性了。另外還會有辭典收錄詞彙不足的問題,譬如說年輕人的流行用語、隨著時代出現新意涵的詞彙等,所以後來出現機器學習方式,建構分類模型。

這種機器學習方法屬於監督式方法,我們要先讓模型知道怎麼樣的文章屬於正向、怎麼樣的文章屬於負向,才能往後作答。模型中的參數可以很廣,除了每個出現的詞彙都能當作特徵(feature)以外,我們可以加入額外更多變數,例如出現的形容詞比例、動詞比例,還有句子的平均長短、文章的難易度,甚至是出現多少命名實體、使用幾個驚嘆號,都是考慮範圍。先請人標記好資料後,我們就可以拿著這份標記資料訓練模型,請它就著特徵,找到重要的變數與模式。

利用機器學習辨識情感,在深度學習暴衝前,也是Kaggle上、、產業界和學術論文中常見的作法。不過,他也有挑戰,包含樣本數過少、沒有標記經費、出現新樣本等幾個問題。樣本數過少,代表難以訓練模型;沒有標記經費,則代表根本沒辦法訓練資料;出現新樣本指的是若模型挑選特定詞彙當成變數,遇到新資料時未必有相關詞彙,則模型的擴展性就會是個問提。

因此,深度學習的蓬勃發展,正好能解掉機器學習模型無法克服的困難。小樣本不是問題、標記經費還能下降,再加上擴展性高,讓深度學習模型判斷情感,變成最佳實務。

當然,本文還是要從頭開始探討起。

情緒分析 - 辭典法

我們先來看預先準備好的詞典長什麼樣子,正面和負面詞彙都各取10個出來,讓你參考一下。

library(tidyverse)
df_sentiment <- read_rds("data/sentiment/df_sentiment.rds")
df_sentiment %>% sample_n(200) %>%
  group_by(type) %>% mutate(rn = row_number()) %>%
  filter(rn <= 10) %>% ungroup() %>%
  pivot_wider(names_from = type, values_from = word)

## # A tibble: 10 × 3
##       rn neg      pos     
##    <int> <chr>    <chr>   
##  1     1 再三再四 悄悄    
##  2     2 被壓壞   洞鑒古今
##  3     3 作低沈聲 貫斗雙龍
##  4     4 不劣方頭 追遠慎終
##  5     5 妖孽     借身報仇
##  6     6 次貨     遠愁近慮
##  7     7 懷恨     歸真反樸
##  8     8 扭斗     風度翩翩
##  9     9 無名小卒 家弦戶誦
## 10    10 惡煞     通元識微

那具體情感分析怎麼做呢?就像前面說的,我們要利用資料表的串接(data
joining),在文本資料中「貼上情感標籤」。

和前面步驟一樣,先分詞後,再來串資料。

library(tidytext)
library(jiebaR)
library(lubridate)

df_speech_clean <- read_csv("data/df_speech_clean.csv")

## Rows: 24 Columns: 5
## ── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (3): text, title, president
## dbl  (1): id
## date (1): date
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

df_speech_pre <- 
  df_speech_clean %>% 
  mutate(text = str_to_lower(text)) %>%
  mutate(text = str_remove_all(text, " |\\n|\\r|\\t")) %>%
  mutate(text = str_replace_all(text, "台灣", "臺灣")) #%>%

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

## 
## ── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
## cols(
##   X1 = col_character()
## )

### 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) %>%
  anti_join(df_stop, by = c("text_segment" = "stopword"))

這步就是要來串資料了!

df_speech_seg %>% select(id, text_segment) %>%
  left_join(df_sentiment, by = c("text_segment" = "word")) %>%
  count(id, type)

## Warning in left_join(., df_sentiment, by = c(text_segment = "word")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 121 of `x` matches multiple rows in `y`.
## ℹ Row 2120 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship = "many-to-many"` to silence this warning.

## # A tibble: 72 × 3
##       id type      n
##    <dbl> <chr> <int>
##  1     1 neg       8
##  2     1 pos      91
##  3     1 <NA>    142
##  4     2 neg      12
##  5     2 pos      74
##  6     2 <NA>    116
##  7     3 neg      31
##  8     3 pos     160
##  9     3 <NA>    292
## 10     4 neg      31
## # ℹ 62 more rows

如上表格所顯現的,有超過一半的詞其實都不帶有情緒,因此type欄位會是NA。詞典法的下一步就是直接把正面和負面詞相加,負面詞因為是負面所以情感分數會是-1。最後得到的總分就是文章、段落、句子的情感分數了。


上一篇
[Day 19] 利用R語言找詞彙關係 - correlation
下一篇
[Day 21] 利用R語言分類文本
系列文
用R語言玩轉文字探勘30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言