2. 观点提取和聚类代码详解

1. pyhanlp介绍和简单应用

2. 观点提取和聚类代码详解

1. 前言

本文介绍如何在无监督的情况下,对文本进行简单的观点提取和聚类。

2. 观点提取

观点提取是通过依存关系的方式,根据固定的依存结构,从原文本中提取重要的结构,代表整句的主要意思。

我认为比较重要的依存关系结构是"动补结构", "动宾关系", "介宾关系"3个关系。不重要的结构是"定中关系", "状中结构", "主谓关系"。通过核心词ROOT出发,来提取观点。

观点提取的主要方法如下,完整代码请移步致github

'''
关键词观点提取,根据关键词key,找到关键处的rootpath,寻找这个root中的观点,观点提取方式和parseSentence的基本一样。
支持提取多个root的观点。
'''
def parseSentWithKey(self, sentence, key=None):
    #key是关键字,如果关键字存在,则只分析存在关键词key的句子,如果没有key,则不判断。
    if key:
        keyIndex = 0
        if key not in sentence:
            return []
    rootList = []
    parse_result = str(self.hanlp.parseDependency(sentence)).strip().split('\n')
    # 索引-1,改正确,因为从pyhanlp出来的索引是从1开始的。
    for i in range(len(parse_result)):
        parse_result[i] = parse_result[i].split('\t')
        parse_result[i][0] = int(parse_result[i][0]) - 1
        parse_result[i][6] = int(parse_result[i][6]) - 1
        if key and parse_result[i][1] == key:
            keyIndex = i

    for i in range(len(parse_result)):
        self_index = int(parse_result[i][0])
        target_index = int(parse_result[i][6])
        relation = parse_result[i][7]
        if relation in self.main_relation:
            if self_index not in rootList:
                rootList.append(self_index)
        # 寻找多个root,和root是并列关系的也是root
        elif relation == "并列关系" and target_index in rootList:
            if self_index not in rootList:
                rootList.append(self_index)

        if len(parse_result[target_index]) == 10:
            parse_result[target_index].append([])

        #对依存关系,再加一个第11项,第11项是一个当前这个依存关系指向的其他索引
        if target_index != -1 and not (relation == "并列关系" and target_index in rootList):
            parse_result[target_index][10].append(self_index)

    # 寻找key在的那一条root路径
    if key:
        rootIndex = 0
        if len(rootList) > 1:
            target = keyIndex
            while True:
                if target in rootList:
                    rootIndex = rootList.index(target)
                    break
                next_item = parse_result[target]
                target = int(next_item[6])
        loopRoot = [rootList[rootIndex]]
    else:
        loopRoot = rootList

    result = {}
    related_words = set()
    for root in loopRoot:
        # 把key和root加入到result中
        if key:
            self.addToResult(parse_result, keyIndex, result, related_words)
        self.addToResult(parse_result, root, result, related_words)

    #根据'动补结构', '动宾关系', '介宾关系',选择观点
    for item in parse_result:
        relation = item[7]
        target = int(item[6])
        index = int(item[0])
        if relation in self.reverse_relation and target in result and target not in related_words:
            self.addToResult(parse_result, index, result, related_words)

    # 加入关键词
    for item in parse_result:
        word = item[1]
        if word == key:
            result[int(item[0])] = word

    #对已经在result中的词,按照在句子中原来的顺序排列
    sorted_keys = sorted(result.items(), key=operator.itemgetter(0))
    selected_words = [w[1] for w in sorted_keys]
    return selected_words

通过这个方法,我们拿到了每个句子对应的观点了。下面对所有观点进行聚类。

2.1 观点提取效果

原句 观点
这个手机是正品吗? 手机是正品
礼品是一些什么东西? 礼品是什么东西
现在都送什么礼品啊 都送什么礼品
直接付款是怎么付的啊 付款是怎么付
如果不满意也可以退货的吧 不满意可以退货

3. 观点聚类

观点聚类的方法有几种:

  1. 直接计算2个观点的聚类。(我使用的方法)
  2. 把观点转化为向量,比较余弦距离。

我的方法是用difflib对任意两个观点进行聚类。我的时间复杂度很高\(O(n^2)\),用一个小技巧优化了下。代码如下:

def extractor(self):
    de = DependencyExtraction()
    opinionList = OpinionCluster()
    for sent in self.sentences:
        keyword = ""
        if not self.keyword:
            keyword = ""
        else:
            checkSent = []
            for word in self.keyword:
                if sent not in checkSent and word in sent:
                    keyword = word
                    checkSent.append(sent)
                    break

        opinion = "".join(de.parseSentWithKey(sent, keyword))
        if self.filterOpinion(opinion):
            opinionList.addOpinion(Opinion(sent, opinion, keyword))

    '''
        这里设置两个阈值,先用小阈值把一个大数据切成小块,由于是小阈值,所以本身是一类的基本也能分到一类里面。
        由于分成了许多小块,再对每个小块做聚类,聚类速度大大提升,thresholds=[0.2, 0.6]比thresholds=[0.6]速度高30倍左右。
        但是[0.2, 0.6]和[0.6]最后的结果不是一样的,会把一些相同的观点拆开。
    '''
    thresholds = self.json_config["thresholds"]
    clusters = [opinionList]
    for threshold in thresholds:
        newClusters = []
        for cluster in clusters:
            newClusters += self.clusterOpinion(cluster, threshold)
        clusters = newClusters

    resMaxLen = {}
    for oc in clusters:
        if len(oc.getOpinions()) >= self.json_config["minClusterLen"]:
            summaryStr = oc.getSummary(self.json_config["freqStrLen"])
            resMaxLen[summaryStr] = oc.getSentences()

    return self.sortRes(resMaxLen)

3.1 观点总结

对聚类在一起的观点,提取一个比较好的代表整个聚类的观点。

我的方法是对聚类观点里面的所有观点进行字的频率统计,对高频的字组成的字符串去和所有观点计算相似度,相似度最高的那个当做整个观点聚类的总的观点。

def getSummary(self, freqStrLen):
    opinionStrs = []
    for op in self._opinions:
        opinion = op.opinion
        opinionStrs.append(opinion)

    # 统计字频率
    word_counter = collections.Counter(list("".join(opinionStrs))).most_common()

    freqStr = ""
    for item in word_counter:
        if item[1] >= freqStrLen:
            freqStr += item[0]

    maxSim = -1
    maxOpinion = ""
    for opinion in opinionStrs:
        sim = similarity(freqStr, opinion)
        if sim > maxSim:
            maxSim = sim
            maxOpinion = opinion

    return maxOpinion

3.2 观点总结效果

聚类总结 所有观点
手机是全新正品 手机是全新正品 手机是全新 手机是不是正品 保证是全新手机
能送无线充电器 能送无线充电器 人家送无线充电器 送无线充电器 买能送无线充电器
可以优惠多少 可以优惠多少 你好可优惠多少 能优惠多少 可以优惠多少
是不是翻新机 是不是翻新机 不会是翻新机 手机是还是翻新 会不会是翻新机
花呗可以分期 花呗不够可以分期 花呗分期可以 可以花呗分期 花呗可以分期
没有给发票 我没有发票 发票有开给我 没有给发票 你们有给发票

4. 总结

以上我本人做的一些简单的观点提取和聚类,可以适用一些简单的场景中。

原文地址:https://www.cnblogs.com/huangyc/p/10279254.html

时间: 2024-10-29 03:54:16

2. 观点提取和聚类代码详解的相关文章

tiny_cnn代码详解(3)——层间继承关系

在上一篇博文中我们顺利将tiny_cnn的程序调试通过,在这篇博文中我们尝试从整体角度给出对tiny_cnn这个深度学习框架的解读,重点论述一下其各个层直接类封装的继承关系. 一.卷积神经网络快速入门 tiny_cnn作为卷积神经网络的一种实现形式,在探讨其框架结构之前,首先需要简要介绍一些卷积神经网络相关的知识.首先,给出经典卷积神经网络的网络结构: 这个是经典的LeNet-5的网络结构图,五层网络.最早用于支票上的手写数字识别,也是最早的商业化的深度学习模型.从上图中可以看出,卷积神经网络主

Github-jcjohnson/torch-rnn代码详解

Github-jcjohnson/torch-rnn代码详解 [email protected] http://www.cnblogs.com/swje/ 作者:Zhouwan  2016-3-18 声明: 1)本文仅供学术交流,非商用.所以每一部分具体的参考资料并没有详细对应.如果某部分不小心侵犯了大家的利益,还望海涵,并联系博主删除. 2)本人才疏学浅,整理总结的时候难免出错,还望各位前辈不吝指正,谢谢. 请联系:[email protected] 或[email protected] 本研

jQuery选择器代码详解(四)——Expr.preFilter

原创文章,转载请注明出处,多谢! Expr.preFilter是tokenize方法中对ATTR.CHILD.PSEUDO三种选择器进行预处理的方法.具体如下: Expr.preFilter : { "ATTR" : function(match) { /* * 完成如下任务: * 1.属性名称解码 * 2.属性值解码 * 3.若判断符为~=,则在属性值两边加上空格 * 4.返回最终的mtach对象 * * match[1]表示属性名称, * match[1].replace(rune

JQuery选择器代码详解(三)——tokenize方法

原创文章,转载请注明出处,多谢! /* * tokenize函数是选择器解析的核心函数,它将选择器转换成两级数组groups * 举例: * 若选择器为"div.class,span",则解析后的结果为: * group[0][0] = {type:'TAG',value:'div',matches:match} * group[0][1] = {type:'CLASS',value:'.class',matches:match} * group[1][0] = {type:'TAG'

开胃小菜——impress.js代码详解

README 友情提醒,下面有大量代码,由于网页上代码显示都是同一个颜色,所以推荐大家复制到自己的代码编辑器中看. 今天闲来无事,研究了一番impress.js的源码.由于之前研究过jQuery,看impress.js并没有遇到太大的阻碍,读代码用了一个小时,写这篇文章用了近三个小时,果然写文章比读代码费劲多了. 个人感觉impress.js的代码量(算上注释一共不到1000行)和难度(没有jQuery的各种black magic= =)都非常适合新手学习,所以写一个总结,帮助大家理解源码. 考

jQuery选择器代码详解(七)——elementMatcher函数

要读懂Sizzle的Compile执行过程,首先需要弄清楚涉及的各个子程序的功能和关键变量和作用,我将逐一对jQuery-1.10.2版本的Compile代码进行说明,望能给予大家帮助. elementMatcher(matchers) 1.源码 function elementMatcher(matchers) { return matchers.length > 1 ? function(elem, context, xml) { var i = matchers.length; while

jQuery选择器代码详解(八)——addCombinator函数

function addCombinator(matcher, combinator, base) 1.源码 function addCombinator(matcher, combinator, base) { var dir = combinator.dir, checkNonElements = base && dir === "parentNode", doneName = done++; return combinator.first ? // Check a

jQuery选择器代码详解(五)——实例说明tokenize的解析过程

原创文章,转载请写明出处,多谢! 以下分析基于jQuery-1.10.2.js版本. 下面将以$("div:not(.class:contain('span')):eq(3)")为例,说明tokenize和preFilter各段代码是如何协调完成解析的.若想了解tokenize方法和preFilter类的每行代码的详细解释,请参看如下两篇文章: jQuery选择器代码详解(三)--tokenize方法 jQuery选择器代码详解(四)--Expr.preFilter 下面是tokeni

DeepLearning tutorial(3)MLP多层感知机原理简介+代码详解

DeepLearning tutorial(3)MLP多层感知机原理简介+代码详解 @author:wepon @blog:http://blog.csdn.net/u012162613/article/details/43221829 本文介绍多层感知机算法,特别是详细解读其代码实现,基于python theano,代码来自:Multilayer Perceptron,如果你想详细了解多层感知机算法,可以参考:UFLDL教程,或者参考本文第一部分的算法简介. 经详细注释的代码:放在我的gith