作者:郭小发
这次比赛的题目是 Quora 公司举办的线上实际问题比赛。Quora 给出两个问题,然后参赛者判断这两个问题是否是重复的问题,即判断两个问题的意思是否一致。Quora 公司本身用了一个 Random Forest 模型来判断问题是否重复。
这次题目需要用到自然语言处理(natural language processing) NLP 的知识。而我对于 NLP 完全处于门外汉状态,本着学习的态度尝试一下这个题目。
题目地址:https://www.kaggle.com/c/quora-question-pairs#description
这个训练数据有大致 40W 条记录,而测试数据有 240W 之多,其中有很大一部分是为了防止参赛者人工标注而用电脑自动生成的题目。
这次的评价标准用的是 LogLoss
这个损失函数需要针对每行记录计算预测出问题重复的概率值,而不是 0-1 二分值。它的特点就是相比准确率这个指标来说能够更好标识预测的精度,这个损失函数值随着预测精度的提高而减少。
由于这次数据量太大,并且涉及到自然语言处理,我自己一台 4G 内存的笔记本完全无法完成大计算量的处理。所以这次文章主要介绍几个简单特征的计算以及可视化,算是给自己这个半途而废的项目做个小总结。
训练集的字段如下:
下图给出训练集的前 10 条记录:
首先对每个问题进行文本处理,去除其中的数字标点以及停用词等同文本含义关系不大的部分,来获取各个问题的关键字。
这次特征计算使用了 R 包 tm。包 tm 即 text mining,是用来做文本挖掘的一个 R 包,是一个进行自然语言处理的基础包。 具体使用方法: http://www.bagualu.net/wordpress/archives/6112 以第八条记录的问题 1 为例:
# 记录 8 的问题 1
q <- da$question1[8]
[1] "How can I be a good geologist?"
# 创建语料库
library(tm)
cp <- Corpus(VectorSource(q))
去除标点
cp <- tm_map(cp, removePunctuation)
[1] How can I be a good geologist
去除数字
cp <- tm_map(cp, removeNumbers)
[1] How can I be a good geologist
转化为小写
cp <- tm_map(cp, tolower)
[1] how can i be a good geologist
去掉停用词
cp <- tm_map(cp, removeWords, stopwords("english"))
[1] can good geologist
去掉空格
cp <- tm_map(cp, stripWhitespace)
[1] can good geologist
去掉常用单词结尾,例如是 sesing 等结尾,来获取词根
cp <- tm_map(cp, stemDocument)
[1] can good geologist
经过这一番处理后,剩余的文本基本就是这个问题的关键字了。
整合成函数如下:
# 提取问题的关键字
parse_key_word <- function(q)
{
library(tm)
cp <- Corpus(VectorSource(q))
cp <- tm_map(cp, removePunctuation)
cp <- tm_map(cp, removeNumbers)
cp <- tm_map(cp, tolower)
cp <- tm_map(cp, removeWords, stopwords("english"))
cp <- tm_map(cp, stripWhitespace)
cp <- tm_map(cp, stemDocument)
# inspect(cp)
td = TermDocumentMatrix(cp)
return(td$dimnames$Terms)
}
wc1,wc2: 问题的关键字个数 diff_wc: 两个问题的关键字差异 comm_word_ratio:相同 Word 比例,两个问题相同的关键词所占整体的比例
# 计算相同 Word 占比
k1 <- parse_key_word( q1 )
k2 <- parse_key_word( q2 )
wc1 <- length(k1)
wc2 <- length(k2)
diff_wc <- abs(wc1 - wc2)
if (wc1 wc2 == 0 ) {
comm_word_ratio <- 0
}else{
comm_word_ratio <- ( sum(k1 %in% k2) sum(k2 %in% k1) )/ (wc1 wc2)
}
通过图形来看下 comm_word_ratio 的分布状况:
ggplot(train,aes(comm_word_ratio)) geom_histogram(position = 'identity', alpha=0.5, aes(y = ..density.., fill = is_duplicate))
上图可以看到 comm_word_ratio 比例高的问题对更倾向于意思一致,但是区分度并不高。
cc1、cc2: 问题的字符长度 diff_cc: 问题的字符长度之差 diff_cc_r: 问题字符长度之差所占比例
# 问题的字符长度
cc1 <- nchar(q1)
cc2 <- nchar(q2)
diff_cc <- abs(cc1 - cc2)
# 转化为 0-1 范围
diff_cc_r <- 1 - ( diff_cc / (cc1 cc2) )
# 画图
ggplot(train, aes(x = is_duplicate, y = diff_cc_r, fill = is_duplicate) ) geom_boxplot() guides(fill = FALSE)
从上图可以看到两个问题之间的字符差异对于结果基本毫无区分度。
语义的情绪分析。如果两个问题的情绪背景不同,那么其含义不同的概率也会大一些。 这里用到了 R 包 syuzhet,具体使用方法见:https://cran.rstudio.com/web/packages/syuzhet/vignettes/syuzhet-vignette.html
library(syuzhet)
r1 <- get_nrc_sentiment(q1)
r2 <- get_nrc_sentiment(q2)
# 计算正面情绪和负面情绪的 word 数
p1 <- r1$positive
p2 <- r2$positive
n1 <- r1$negative
n2 <- r2$negative
# 来判断是否两个问题的情绪是否一致
np_xor <- !xor(p1,p2)) (!xor(n1,n2)
ggplot(data=train,aes(x=np_xor,fill=is_duplicate)) geom_bar(position='fill')
从图上看出,随着情绪背景的一致状况的增高,问题意思一致的比例也在增高。
衡量字符串相似度的计算方式有很多,主要分为基于字符串的相似度和基于语义的相似度。 详细情况参见:http://wetest.qq.com/lab/view/276.html 由于基于语义的相似度计算量太大,我们从基于字符串的相似度度量中获取我们的特征变量。
# 使用 stringdist 包
library(stringdist)
# 余弦相似度
dist_cos = stringdist(q1, q2, method="cosine")
# 莱文斯坦距离
dist_lv = stringdist(q1, q2, method="lv")
# 转化为 0-1 范围
dist_lv_r <- 1 - ( dist_lv / ((cc1 cc2)) )
same_what: 两个问题的疑问词是否一致
如果两个问题的疑问词不同,那么他们相同的几率会小一些。
# 计算两个问题的疑问词是否一致
q <- c(q1, q2)
same_what = 1
for ( term in c("what","why","who","when","how","which") ){
same_num <- sum( grepl(term, q, ignore.case=TRUE) )
if ( same_num == 1){
same_what = 0
break
}else if (same_num == 2){
same_what = 1
break
}
}
由于变量 same_what 是二分值,可以直接查看它和 is_duplicate 的 confusionMatrix
# 加载包
require(caret)
confusionMatrix(train$same_what, train$is_duplicate, positive = "1")
## 结果
Confusion Matrix and Statistics
Reference
Prediction 0 1
0 121769 43830
1 133258 105433
Accuracy : 0.562
95% CI : (0.5604, 0.5635)
No Information Rate : 0.6308
P-Value [Acc > NIR] : 1
Kappa : 0.1635
Mcnemars Test P-Value : <2e-16
Sensitivity : 0.7064
Specificity : 0.4775
Pos Pred Value : 0.4417
Neg Pred Value : 0.7353
Prevalence : 0.3692
Detection Rate : 0.2608
Detection Prevalence : 0.5904
Balanced Accuracy : 0.5919
Positive Class : 1
准确率只有 0.56,基本和随机猜测没有什么区别。
我们看一下几类参数的相关系数
library(corrplot)
corr <- cor(train[, c("comm_word_ratio","dist_cos","same_what","dist_lv_r","np_xor")])
# 画图
corrplot(corr = corr,order="AOE",type="upper",tl.pos="d")
corrplot(corr = corr,add=TRUE, type="lower", method="number",order="AOE",diag=FALSE,tl.pos="n", cl.pos="n")
从上图可以看到几个特征之间的相关性比较低,都相互独立。
前面所有的特征都是基于将问题本身作为字符串来看待,特征只都是来衡量两个字符串之间的相似度。而预测目标其实是语义上的相似,所以上述的特征只能近似的来衡量两个问题的相似度。
更多的关于语义的相似度特征,例如同义词、 wordnet、 word2vec 等,主要由于这些特征计算量都比较大而没有实施。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。