当前位置:网站首页>CSDN问答标签技能树(二) —— 效果优化

CSDN问答标签技能树(二) —— 效果优化

2022-07-06 09:11:00 Alexxinlu

系列文章


团队博客: CSDN AI小组


1. 问题背景

本篇文章承接上一篇文章《CSDN问答标签技能树(一) —— 基本框架的构建》。简而言之,是对CSDN问答模块中的各个领域标签,构建一个完整的知识体系,并将问答模块中的内容、用户等,与知识体系进行关联,最终形成一个包含异构结点的只是图谱,更好地为下游NLP任务提供资源基础。

本篇文章主要介绍技能树结构的优化,以及问答内容与技能树匹配的效果优化。

2. 技能树优化

本章主要包括两个部分,一个是技能树结构的优化,另一个是问答内容与技能树匹配的效果优化。

技能树已经扩充到16种语言,但是由于效果较差,故当前重点挑选了2种在CSDN问答模块较流行的编程语言 PythonJava,以及现在比较流行的一个概念 云原生 ,该概念包含多个标签,是范围较大的一个知识领域。

2.1 技能树结构优化

技能树的结构主要有两个要求,一个是知识的覆盖度要全,另一个是需要有等级体系。

2.1.1 知识覆盖率

知识的覆盖率在上一篇文章2.1节中已经提到过,主要通过爬取书籍的目录以及学习论坛的目录实现技能树的构建。

这一次更新中,又爬取了更多的书籍目录,用于扩充知识的覆盖率。在后续也会不断增量扩充更多的知识,并且也会考虑增加用户编辑的功能,让用户来编辑和新增知识点。

2.1.2 等级体系

知识是有初中高等级之分的,以python语言为例,大致有如下所示的初步知识骨架:

python
├── 初阶
│   ├── 预备知识
│   ├── 基础语法
│   ├── 进阶语法
│   └── 面向对象编程
├── 中阶
│   ├── 基本技能
│   ├── Web应用开发
│   ├── 网络爬虫
│   └── 桌面应用开发
└── 高阶
    ├── 科学计算基础软件包NumPy
    ├── 结构化数据分析工具Pandas
    ├── 绘图库Matplotlib
    ├── 科学计算工具包SciPy
    ├── 机器学习工具包Scikit-learn
    ├── 深度学习
    ├── 计算机视觉
    └── 自然语言处理

为了让知识体系有等级结构,本文的构思是:首先将构建一个如上图所示的知识骨架,再将获取的所有书籍或学习论坛的目录进行拆分和整合,挂到骨架上相应的结点上。例如:对于一本 python入门 的书籍,将其目录进行拆分并挂到骨架中 初阶 相应的结点上,如果骨架未覆盖相关知识点,则新增结点,如果新增的知识点存在冗余,则无须挂到骨架上。

在具体实施阶段,当前的策略还较为简单,只是通过人工指定的方式,将目录挂到相应的等级结点上,如下所示:

初阶 --> using_python
初阶 --> tutorial_python
初阶 --> reference_python
初阶 --> library_python
初阶 --> liaoxuefeng_python
初阶 --> Python基础教程_第3版
初阶 --> Python编程快速上手_让繁琐工作自动化_第2版
初阶 --> 流畅的Python
初阶 --> Python编程_从入门到实践_第2版
初阶 --> Python从入门到精通
初阶 --> Python_Cookbook_第3版
中阶 --> Python进阶编程
中阶 --> Python核心编程_第3版
中阶 --> Python极客项目编程
中阶 --> Flask_Web开发_基于Python的Web应用开发实战_第2版
中阶 --> 网络爬虫 --> Python3网络爬虫开发实战
高阶 --> Python3高级教程_第3版
高阶 --> Python数据分析基础
高阶 --> 利用Python进行数据分析_原书第2版
高阶 --> Python高级数据分析:机器学习、深度学习和NLP实例
高阶 --> 计算机视觉 --> 实用卷积神经网络:运用Python实现高级深度学习模型

后续将考虑借鉴 决策树 算法构建树的思路,来构建一棵冗余度更低,结构更加合理的技能树。

2.2 匹配算法效果优化

在构建好技能树之后,需要将问答模块中已采纳的问题,基于匹配算法,将其挂到相应的结点上去,作为该结点的数据资源。对于用户新的提问,基于匹配算法,将最相关结点上的已采纳问题推荐给用户,并进一步推荐用户学习技能树上与该结点相邻的相关知识。

2.2.1 匹配算法的优化

上一篇文章2.4节中,只使用了技能树的叶子结点和提问标题进行匹配,这种方式存在以下缺陷:

  • 叶子结点的祖先结点也包含了很多有用的信息,只使用叶子结点将丢失祖先的信息;
  • 有些用户的提问并没有具体到描述很详细的叶子结点,可能是概述一大类的非叶子结点;
  • 英文关键字在匹配的时候时候起着至关重要的作用,需重点考虑。

故针对以上问题,匹配算法进行了下述的改进:

  • 获取技能树中所有节点的路径集合。路径指的是根结点到当前结点的路径;
  • 计算提问标题与所有路径的相似度。由于路径中的最后一个结点为要匹配的结点,且层数越大的结点描述更加精确,信息量也更大。故在计算相似度的时候,根据路径中结点的层号来赋予结点的权重,层号越大,权重越大;
  • 匹配时,加大英文关键字的权重。

此外,由于路径有长又短,上述算法会更加倾向于匹配较短的路径,较长的路径得分会偏低,故最终的得分,需要基于路径的长度进行归一化。具体的代码实现如下所示:

def get_most_sim_node_paths_to_leaves(self, all_paths_to_leaves, node_id_seg_dict, query, lang):
    '''计算用户提问标题与技能树中所有路径的得分 '''
    # 第一层(编程语言名)和第二层结点(子目录名)无意义
    max_path_len = max([len(m_path) for m_path in all_paths_to_leaves]) - 2   

    # 对query进行分词
    query_seg = word_segmentation(query)

    # 去掉当前编程语言的名字
    lang_syn_name_list = lang_std_syn_dict[lang]
    query_seg = [m_word for m_word in query_seg if m_word not in lang_syn_name_list]   

    result = {
    'score': 0, 'path_to_leaf': []}
    for ori_path_to_leaf in all_paths_to_leaves:   
        # 第一层(编程语言名)和第二层结点(子目录名)无意义
        path_to_leaf = ori_path_to_leaf[2:]   
        score_list = []
        num_nodes = len(path_to_leaf)
        if num_nodes == 0:
            continue
        
        for m_node_id in path_to_leaf:
            # 计算两个词序列的相似度
            sim_score, _, _ = cal_simlarity(node_id_seg_dict[m_node_id], query_seg, lcs_ratio_threshold=0, alpha=0)   
            score_list.append(sim_score)

        # 在根节点到叶子结点的路径上,去除尾部得分连续为0的,到叶子结点的子路径
        score_list.reverse()
        non_zero_index = 0
        for m_index, sim_score in enumerate(score_list):
            if sim_score != 0:
                non_zero_index = m_index
                break
        score_list = score_list[non_zero_index :]
        score_list.reverse()

        # 如果存在交并比为1的结点,则直接返回根节点到该结点的路径
        value_one_flag = False
        value_one_index_list = [m_index for m_index, sim_score in enumerate(score_list) if sim_score == 1]
        if len(value_one_index_list) != 0:
            value_one_index = value_one_index_list[-1]
            score_list = score_list[: value_one_index + 1]
            value_one_flag = True
                    
        # 根据层号赋予结点的权重,同时考虑路径的长度
        num_nodes = len(score_list)
        final_score = 0.0
        denominator = (1.0 + num_nodes) * num_nodes / 2
        for m_index, m_score in enumerate(score_list):
            floor_score = (m_index + 1) / denominator
            final_score += m_score * floor_score
            
        path_len_score = num_nodes / max_path_len
        final_score *= path_len_score

        if final_score > result['score']:
            result['score'] = final_score
            result['path_to_leaf'] = ori_path_to_leaf[: num_nodes + 2]

        if value_one_flag:
            result['score'] = final_score
            result['path_to_leaf'] = ori_path_to_leaf[: num_nodes + 2]
            break

    return result

2.2.2 预处理优化

本任务主要涉及到结点描述与用户提问的匹配,而停用词的存在,以及关键词的质量,都会影响匹配的效果,故需要针对任务的具体特点,进行合适的预处理操作,只保留高质量的关键词,进而提升匹配的效果。

此外,用户提问口语化比较严重,而技能树中结点的描述较为书面化,匹配的时候只要一方的停用词去除干净即可,故本文只分析技能树中结点的描述,这大大降低了预处理操作的工作量。

  1. 停用词过滤
    之前只使用了网上几个公认的停用词词典进行停用词过滤,但是对于具体领域的任务还是不够。在经过对数据的分析之后,发现一些特定词性的词,对任务基本没有任何作用,主要包括以下词性:

    pos中文名
    c连词
    e叹词
    h前缀
    k后缀
    m数词
    o拟声词
    p介词
    r代词
    u助词
    w标点符号
    x字符串
    y语气词
    z状态词

    p.s. 以上述首字母开头的词性都算

    对于既包含有效词,又包含停用词的词性,通过统计+人工筛选的方法,找出高频的停用词,加入到停用词词典中,共新增159个停用词,如下表所示:

    第1列第2列第3列第4列
    项中新手现代
    不到新人近期
    类时菜狗当前
    萌新菜鸟过期
    求解菜鸡需要
    有配兄弟使用
    刚入高手三大
    这是自学停用词
    前辈谢谢
    一串感谢哪几个
    请求仁兄找不到
    刚刚作业萌新刚
    至少疯了刚入坑
    麻烦老师同一个
    解答牛人下个月
    急!这题帮帮忙
    急求题目看不懂
    救救源码啊啊啊
    舅舅做题在线等
    救急之间太难了
    救命容易家人们
    求求烦琐兄弟们
    球球庞大小老弟
    求助合适这道题
    求教神秘这个题
    跪求离奇考试题
    大神完整一道题
    大佬最好源代码
    指教重新编程题
    指点简单转换成
    小白复杂题怎么写
    孩子正确题怎么做
    帮忙错误求源代码
    帮帮直接haha
    哥哥间接课程设计
    想要姐姐常用从根本上
    中将妹妹所有一切都是
    初学小妹忽略臭名昭彰
    打烊小弟更加独一无二
    几个大哥一定
  2. 基于词性的bigram词组
    对于分词器错误切分的词,合并bigram词组,且bigram表义更明确,能提升匹配的正确率。具体而言,根据词性,对相邻的词进行拼接,形成更大的词组单元。通过观察数据,获得以下常见的需要拼接的词性组合:

    first possecond pos
    dq
    dv
    dn
    fq
    hn
    hv
    ha
    nk
    nn
    nq
    ns

    p.s. 以上述首字母开头的词性都算

2.2.3 增加其他类结点

有些用户的提问已经超出了当前领域,或者难以在技能树上进行穷举,故另外增加了一个其他类的结点,用于匹配该类问题。例如对于 Python 标签,其他类结点,又细分为以下三个类别:

others
├── 其他类别标签问题
├── 应用类问题
├── 第三方包问题

其他类问题的判断主要通过规则实现,对与上述三类问题,规则如下:

  • 其他类别标签问题

不包含当前领域标签,却包含其他领域标签的提问标题。

  • 应用类问题
re.compile(r'((如何|怎么).*?(计算|实现|制作)|练习|题目)')
  • 第三方包问题
re.compile(r'(工具包|(?:第三方|[a-z]+?)库|py(?!thon)[a-z0-9]+|安装[a-z0-9]+|[a-z0-9]+的安装)')

2.3 匹配效果

在进行以上的优化之后,具体的效果提升如下所示,使用的评价标准是正确率(Accuracy)。

领域标签baselinenow
Python54%78%
Java57%82%
云原生--

p.s. 由于云原生的有效数据较少,故还未构建数据集,并进行效果测试。

3. 总结与下一步计划

技能树已经支持16棵,本文重点优化了 PythonJava云原生 三个领域标签的技能树,主要包括技能树结构的优化,以及匹配算法的优化。

下一步计划:

  • 技能树结构进一步升级:借鉴 决策树 算法构建树的思路,来构建一棵冗余度更低,结构更加合理的技能树;
  • 云原生数据资源的构建;
  • 扩充其他类型的数据资源。当前只使用了问答的数据资源,后续将考虑使用CSDN其他模块的异构数据资源,包括:博客、课程、视频、专栏、用户收藏夹等。

相关链接

P.S.

该系列文章会持续进行更新。希望NLP等领域的同仁、老师和专家能够提供宝贵的建议,谢谢!

原网站

版权声明
本文为[Alexxinlu]所创,转载请带上原文链接,感谢
https://blog.csdn.net/u010280923/article/details/118463426