自然语言处理是机器理解人类情感的第一步,今天就让我们运用R语言,通过两款强大的工具——做中文分词的jieba、做大数据运算的spark,来处理自然语言,并从中提取出语言想要表达的情感。
该项目用到的资源及R语言代码已被我打包上传,感兴趣的朋友可以点击这里获取资源。
library(jiebaR)
library(cidian)
library(Matrix)
library(dplyr)
library(DBI)
library(sparklyr)
library(gmodels)
首先我们从网上搜集到了一份关于顾客购买手表之后对商家的评分与评论的数据集,这份数据集共有10万余条记录数,但只有两个字段——stars、contents,分别表示顾客对相应商家的评分(1-5星)与评论内容。我们最终的目标,就是要通过顾客的评论来预测顾客的满意度(评分等级)。
在该数据集中,有些顾客是认真评价的,当然也有些顾客不知道该如何评价随便写了几个字的、或者未来得及评论被系统默认好评的,这些评论会给我们之后的建模预测带来很大的不确定性。所以我们需要对数据集进行预处理,删除掉评论中的非中文字符,删除默认好评或评论不足10个字的观测值,最后我们需要将顾客的评分stars
转换为满意度satisfaction
:将1星与2星评论视为不满意,4星与5星评论视为满意。
comments <- read.csv('comments_raw.csv', stringsAsFactors = F)
removePattern <- function(corpus, pattern)gsub(pattern, '', corpus)
comments$contents <- removePattern(comments$contents, '[0-9a-zA-Z]')
comments$contents <- removePattern(comments$contents, '[[:punct:]]')
comments$contents <- removePattern(comments$contents, '[[:space:]]')
comments <- comments %>%
filter(!grepl('此用户未\\w+填写评价内容', contents)) %>%
filter(nchar(contents) > 10) %>%
filter(stars != 3) %>%
mutate(satisfaction = if_else(stars > 3, 'yes', 'no')) %>%
select(-stars)
接下来我们需要用到jiebaR工具包,对数据集中的评论内容进行分词。jiebaR自带的分词字典已经十分强大了,但是在处理一些专有词汇方面还可以提升。如下所示,我们使用jiebaR默认的分词字典时,“阿迪板鞋”被分成了两个词。同时,类似“我”、“的”这样的词汇在评论内容中会经常出现,但却与用户的满意度关联不大。
为了解决这些问题,我们从搜狗输入法的字典库中下载了一份”淘宝专用词库”,要将该词库转化为jiebaR所能识别的字典文件,我们需要用到cidian
工具包中的decode_scel
命令。同时我们还从网上搜集到了一份停止词字典。我们可以通过R语言自带的scan命令来查看两个字典。
decode_scel(scel = "淘宝专用词库.scel", cpp = TRUE, output = 'user.txt')
接下来,我们使用自定义的用户字典与停止词字典,可以看到,“阿迪板鞋”在用户字典中,所以分词后被分成了一个词语,“我”、“的”在停止词字典中 ,所以分词后被忽略。当然,这里我们的停止词字典还不够十分强大,一个理想的停止词字典需要帮助我们删除所有与预测目标(用户满意度)无关的单词,这样会在之后的建模运算中节省很大一部分算力。
接下来对每条评论逐一进行分词,记录评论所在的行号,从评论中提取到的单词,以及这些单词出现的频率。
rowID <- colID <- values <- vector()
for(i in 1:nrow(comments)){
words <- wk[comments$contents[i]]
words_freq <- freq(words)
rowID <- append(rowID, rep(i, nrow(words_freq)))
colID <- append(colID, words_freq$char)
values <- append(values, words_freq$freq)
}
接下来,我们需要对数据进行最后一步清洗,使数据能够符合建模所需求的格式。
理论上我们可以通过命令data.frame(rowID, colID, values)
,将上一步提取到的三个向量合并为一个长形数据,再通过reshape
转制为我们需要的宽型数据(参考这篇帖子中对长形数据与宽型数据的描述)。但实际上取决于电脑性能,对如此大型的数据集进行转制可能导致R语言崩溃。因此我们需要将先将数据转换为分散矩阵,再将分散矩阵转化为常规数据集。
同时我们需要删除掉出现频率小于15次的单词与评论词汇不足5个的观测值,这些数据对于优化模型贡献不大,并且还会浪费大量计算资源。
# 将colID修改为数值
colID <- as.factor(colID)
colLables <- levels(colID)
colID <- as.numeric(colID)
# 删除掉出现频率小于15次的单词与评论词汇不足5个的观测值
rowID_freq <- table(rowID)
row_subset <- as.numeric(names(rowID_freq[rowID_freq > 5]))
colID_freq <- table(colID)
col_subset <- as.numeric(names(colID_freq[colID_freq > 15]))
# 将分词后的数据转换为分散矩阵
comments_words <- sparseMatrix(rowID, colID, x = values)
# dimnames = list(NULL, colLables))
# 将分散矩阵转换为常规矩阵
comments_words <- as.matrix(contents_words[row_subset, col_subset])
# 汇总成最终版本的数据集
comments <- data.frame(satisfaction = comments$satisfaction[row_subset], contents_words)
我们用colLabels
记录了分散矩阵的中文列名 (准确来说应该是colLabels[col_subset]
)。在最终的模型中,我们就是通过这些词汇来预测用户是否满意的,比如,假设用户评论中出现了”不尽人意”等词汇,可以推导出用户对本次购物不满意,相反假设用户评论中出现了”不可多得”等词汇,则可以推导出用户对本次购物是满意的。当然除了这些明确表示态度的词汇,colLabels
中还含有大量中性词汇、无关词汇、意义不明的词汇等,这主要是由于之前自然语言处理的过程还不够精细化,但在大数据面前这些“噪声”影响并不是很大。
最后我们需要调用spark训练数据集。这里我们使用了 Naive Bayes朴素贝叶斯模型,该模型具有如下假设:
# 连接spark
sc <- spark_connect(master = 'local')
# 将数据拷贝进spark内存
comments_spark <- sdf_copy_to(sc, comments, 'comments')
# 将数据分割为训练集与测试集
comments_partition <- sdf_partition(comments_spark, training = 0.9, test = 0.1)
# 使用ml_naive_bayes建模
ml_model <- comments_partition$training %>%
ml_naive_bayes(satisfaction~.)
# 预测
comments_predict <- sdf_predict(comments_partition$test, ml_model)
# 为预测结果分配Hive表格
sdf_register(comments_predict, 'comments_predict')
通过以下命令查看最终版数据集的行列数,可以看出数据集共有5万余行、近3千冽,其中只有satisfaction
一列为预测目标,其余均为影响因素,与上文提到的colLabels[col_subset]
对应。
通过spark自带的模型评估方法,可以看出模型在测试集上的正确率达到了94%。
查看混淆矩阵,可以发现预测结果无论在satisfaction
取值为“yes”或是“no”时,预测正确率都远远大于失误率,模型不存在显著偏差。
总而言之,我们证明了通过用户评论来预测用户满意度的方法是可行的。当然我们的预测目标也可以不仅限于满意度,比如通过获取用户的一段语言,预测用户是否开心、是否愤怒、是否心急等。当然,用户在表达不同的情感时会采用不同的词汇,相对应的,预测用户不同情感的模型需要挖掘出相应的词汇。