Kaggle 实战:Quora Question Pairs

作者:郭小发

前言

这次比赛的题目是 Quora 公司举办的线上实际问题比赛。Quora 给出两个问题,然后参赛者判断这两个问题是否是重复的问题,即判断两个问题的意思是否一致。Quora 公司本身用了一个 Random Forest 模型来判断问题是否重复。

这次题目需要用到自然语言处理(natural language processing) NLP 的知识。而我对于 NLP 完全处于门外汉状态,本着学习的态度尝试一下这个题目。

题目地址:https://www.kaggle.com/c/quora-question-pairs#description

这个训练数据有大致 40W 条记录,而测试数据有 240W 之多,其中有很大一部分是为了防止参赛者人工标注而用电脑自动生成的题目。

评价标准

这次的评价标准用的是 LogLoss

详细公式见:https://www.kaggle.com/wiki/LogLoss

这个损失函数需要针对每行记录计算预测出问题重复的概率值,而不是 0-1 二分值。它的特点就是相比准确率这个指标来说能够更好标识预测的精度,这个损失函数值随着预测精度的提高而减少。

本文工作

由于这次数据量太大,并且涉及到自然语言处理,我自己一台 4G 内存的笔记本完全无法完成大计算量的处理。所以这次文章主要介绍几个简单特征的计算以及可视化,算是给自己这个半途而废的项目做个小总结。

特征计算

训练集的字段如下:

  • id - 训练集的记录 ID
  • qid1, qid2 - 每个问题的唯一 ID,只存在在训练集中
  • question1, question2 - 问题的文本内容,分别对应两个问题
  • is_duplicate - 目标值, 1 表示两个问题意思一致,0 则表示不一致

下图给出训练集的前 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)
    }

相同 Word 占比

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

  • R 包 syuzhet 提供了四个情绪相关的字典。我们所使用的函数 get_nrc_sentiment 调用的是 Saif Mohammad's NRC Emotion lexicon。 这个字典将词语附上 8 种情绪(anger, fear, anticipation, trust, surprise, sadness, joy, and disgust)以及两种分类(正面情绪和负面情绪)
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 由于基于语义的相似度计算量太大,我们从基于字符串的相似度度量中获取我们的特征变量。

  • 莱文斯坦距离,又称 Levenshtein 距离,是编辑距离(edit distance)的一种。指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。
# 使用 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 等,主要由于这些特征计算量都比较大而没有实施。

参考文献

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏yl 成长笔记

相似图像搜索从训练到服务全过程

最近完成了一个以图搜图的项目,项目总共用时三个多月。记录一下项目中用到机器学习的地方,以及各种踩过的坑。

1233
来自专栏PaddlePaddle

PaddlePaddle发布新版API,简化深度学习编程

PaddlePaddle是百度于2016年9月开源的一款分布式深度学习平台,为百度内部多项产品提供深度学习算法支持。为了使PaddlePaddle更加易用,我们...

3278
来自专栏用户2442861的专栏

Caffe学习:Blobs, Layers, and Nets

-注意:网络结构是设备无关的,Blob和Layer=隐藏了模型定义的具体实现细节。定义网络结构后,可以通过Caffe::mode()或者Caffe::set_m...

400
来自专栏人人都是极客

想让你的微控制器AI效率提升5倍吗?

目前,在许多需要在本地进行数据分析的“永远在线”的物联网边缘设备中,神经网络正在变得越来越普及,主要是因为可以有效地同时减少数据传输导致的延时和功耗。而谈到针对...

752
来自专栏人工智能头条

张雨石:Adam - 大规模分布式机器学习框架

2807
来自专栏PaddlePaddle

转载|在TensorFlow和PaddleFluid中使用多块GPU卡进行训练

前四篇文章我们介绍了 PaddleFluid 和 TensorFlow 的设计原理基本使用概念,分别通过在两个平台上实现完全相同的模型完成图像分类,语言模型和序...

1183
来自专栏技术专栏

自己实现一个滑动窗口

上述计算中的alpha的值是一个0~1之间的常量,aplha值决定了一段时间内的平滑水平,alpha越趋于1,历史值对当前的平均值的影响越大,反之亦然

511
来自专栏机器之心

学界 | 密歇根州立大学提出NestDNN:动态分配多任务资源的移动端深度学习框架

论文:NestDNN: Resource-Aware Multi-Tenant On-Device Deep Learning for Continuous M...

983
来自专栏青枫的专栏

Mealy和moore型状态机的主要区别

状态机一般分为三种类型:   1、Moore型状态机:下一状态只由当前状态决定,即次态=f(现状,输入),输出=f(现状);   2、Mealy 型状态机:下一...

1361
来自专栏闪电gogogo的专栏

IEEE Trans 2009 Stagewise Weak Gradient Pursuits论文学习

论文在第二部分先提出了贪婪算法框架,如下截图所示: ? 接着根据原子选择的方法不同,提出了SWOMP(分段弱正交匹配追踪)算法,以下部分为转载《压缩感知重构算法...

3308

扫码关注云+社区