CSDN问答机器人

文章目录

  • 前言
  • 一、背景
  • 二、总体流程
  • 三、构建知识库
  • 四、粗排
  • 五、精排
  • 六、Prompt
  • 总结
  • 相关博客


前言

先看结果:

请添加图片描述
已经连续很多周获得了第二名(万年老二), 上周终于拿了一回第一, 希望继续保持. 😁
请添加图片描述
这是今天的榜单, 采纳的数量相对较少, 之前基本上维持在100+

重点说明一下, 第二名是一名20年+经验的程序员, 第四名是ChatGPT的使用者.

整体来说还是非常不错的, 超越了99%的人类, 争取做到100%😁

断断续续优化了一年才达到今天的效果, 从git日志来看是13个月前开始做的, 不容易啊

一、背景

1、降低用户重复提问率
2、提高问题的响应速度
3、减少无人回答的问题

总的来说, 都是为了提升用户体验, 做一个能够帮助解决实际问题的AI机器人

二、总体流程

请添加图片描述
ChatGPT出来之前, 是没有prompt和之后的流程的, 也就是: 构建知识库 > 粗排 > 精排

三、构建知识库

知识库的数据来源:

  1. 已采纳的问题: 376140
  2. 编程语言的官方手册: 67015
  3. 高质量的博客: 4779402
  4. 技能树习题
  5. 学院的课程: 75302

4和5都是后面加的, 主要是为了推广技能树和课程, 主要的知识库来自1、2、3.

目前的数据是这些, 已经加了定时更新机制, 每月一号自动增量更新知识库

这里的477w博客, 是没有做结构化的量, 做完结构化后的量是12959544, 将近1300w的数据量了, 这么大的数据量, 如何存呢?

我知道你很急, 但你先别急, 听我娓娓道来!

结构化: 将博客按内容中的小标题拆分开

使用PostgresSQL存储知识库

表的整体结构

请添加图片描述

字段说明: 
	id: 博客id
	query: 博客标题
	inner_id: 小标题序号
	query_vector: 目标query的向量, 这里是 query+head_title 的向量化后的结果
	meta: 主要用于存储小标题及小标题之间的内容

我随机取的一条拆分后的博客的数据, 这是其中一个meta字段

{
  "url": "博客链接",
  "tags": "debian,linux,ubuntu,vim,编辑器",
  "content": "xxxxxxxxx",
  "head_title": "安装ctags"
}

字段说明: 
	tags: 博客标签
	content: 小标题与小标题之间的内容
	head_title: 小标题

怕大家不理解, 我又截了个图
请添加图片描述
SQL大佬也许已经看出了端倪, 这里面存了好多重复数据, 理论上应该拆分出几个关联表来存的

确实, 说的很对, 当时有想过这个问题, 在众多因素之下, 就成了现在这样了, 大家可别像我一样

四、粗排

数据我们存起来了, 如何做粗排呢?

首先, 我们需要将我们的博客数据向量化, 上面的query_vector字段, 就是用来存储我们向量化后的数据的.

我们可以利用一些预训练的句向量模型, 来将我们的博客数据向量化, 我分为两部分来介绍:

  1. 构建训练数据
  2. 训练模型

在构建训练数据之前, 我想带大家看一下huggingface上的一些预训练句向量模型的效果.

我们拿月下载量最多的一个SBERT模型来试试, 链接: sentence-transformers/all-MiniLM-L6-v2

请添加图片描述
效果非常不错啊, 那还微调个啥, 直接拿来用呗

别急, 多测几个用例试试:

请添加图片描述
相似度有点大, 理想的情况, 这种案例的相似度应该在0.5以下, 如果用户要的是C语言答案, 我们的机器人返回的却是Java的结果, 对用户来说, 用处不大.

因此, 我们需要基于该预训练模型用我们自己标注好的数据微调.

那么问题来了, 如何标注数据?

  1. 人工标注, 构造: [query, query, label]元组
  2. 利用模型粗筛, 再人工标注

相信一个合格的NLPer都会选择2, 原因如下:

  1. 人工标注构造正例对非常困难, 例如, 我的数据是1w, 假如有一个query A, 需要你标注出与query A相似的句子B, 你需要遍历一遍数据后才知道哪些是相似的
  2. 人工标注周期长, 等你标完, 都猴年马月了

如何利用模型粗筛一遍数据
1、使用一些传统的相似度计算方法(如LCS), 将数据库中的博客标题, 两两计算相似度, 筛选出相似度比较高的数据, 组成 [query, query]
2、训练一个无监督的语义相似度模型(如SimCSE), 使用该模型来两两计算相似度, 筛选相似度比较高的数据

CSDN问答机器人中, 两种方法我都试过, 最后选择了2, 因为方法一计算出来的阈值, 往往偏向文本相似, 很难挖掘出我们需要高度关注的数据, 也就是上面所举的例子:

python实现二叉树
C语言实现二叉树

这种类型的数据, 通过方法1比较难以控制阈值.

模型的训练过程我就不说了, 这里我直接给大家展示一下通过SimCSE计算相似度后的数据

TypeScript生成随机数	jmeter随机数生成	0.89
TypeScript生成随机数	kotlin 生成随机数	0.88
TypeScript生成随机数	Javascript生成随机数	0.88
TypeScript生成随机数	ThreadLocalRandom生成随机数	0.88
TypeScript生成随机数	Java生成随机数SecureRandom	0.86
TypeScript生成随机数	wincc随机数的生成	0.86
TypeScript生成随机数	pytorch生成随机数	0.86
TypeScript生成随机数	golang生成随机数	0.86
TypeScript生成随机数	MATLAB 生成随机数	0.86
TypeScript生成随机数	MATLAB生成随机数	0.86
TypeScript生成随机数	Python 随机数生成	0.86
TypeScript生成随机数	python 随机数生成	0.86
TypeScript生成随机数	c#Random类生成随机数	0.85
TypeScript生成随机数	android 生成随机数	0.85
TypeScript生成随机数	Android 生成随机数	0.85
TypeScript生成随机数	随机数生成器python	0.84
TypeScript生成随机数	Swift - 随机数生成	0.84
TypeScript生成随机数	Clickhouse 生成随机数据	0.83
TypeScript生成随机数	pytorch | 生成随机数	0.82

相似度阈值设定在0.9, 筛选出来的数据:

python中分割字符串	python字符串分割
python中分割字符串	oracle分割字符串
python中分割字符串	将String字符串分割
python中分割字符串	boost 分割字符串
python中分割字符串	Arduino分割字符串
python中分割字符串	Linux Shell 分割字符串
python中分割字符串	boost 拆分字符串
python中分割字符串	sscanf分割字符串
python中分割字符串	leetcode 分割字符串
python中分割字符串	golang:字符串分割
python中分割字符串	基于Oracle的字符串分割
python中分割字符串	SQL中按分隔符拆分字符串
python中分割字符串	C++ string字符串分割
python中分割字符串	C++ string 字符串的分割
python中分割字符串	python多空格字符串分割

可以看出, 这些筛选出来的数据, 就是我们所关心的部分文本相同, 但语义完全不同的数据.

最后, 再人工标注一部分数据, 标注的数据如下:

利用python发送qq邮件	使用python发送qq邮件	1
利用python发送qq邮件	使用java发送qq邮件	0
利用python发送qq邮件	用Java发送QQ邮件	0
利用python发送qq邮件	使用python发送邮件	1
利用python发送qq邮件	C#利用QQ信箱发送EMAIL	0
利用python发送qq邮件	使用python发邮件	1
利用python发送qq邮件	用Python发送邮件	1
利用python发送qq邮件	Java使用QQ邮箱发送邮件	0
利用python发送qq邮件	python使用gmail发送邮件	0
利用python发送qq邮件	python发送QQ邮件	1
利用python发送qq邮件	PHP使用QQ邮箱发送邮件	0
利用python发送qq邮件	python 利用zmail库发送邮件	1
利用python发送qq邮件	利用Foxmail发送邮件	0
利用python发送qq邮件	python使用SMTP发送邮件	1
利用python发送qq邮件	用Python通过163邮箱发送邮件	0
利用python发送qq邮件	"Simple Java Mail的使用,发送qq邮件"	0
利用python发送qq邮件	Java实现利用QQ邮箱发送邮件	0
利用python发送qq邮件	使用Smtp来发送邮件	1

这里面存在一个包含关系, 当某个技术词里面包括了另一个词时, 我们认为是相似的, 如:

利用python发送qq邮件
python 利用zmail库发送邮件

使用zmail库可以发送163邮件、qq邮件、google邮件等

至此, 我们便有了高质量的有监督数据.

下一步, 就是微调SBERT模型了, 这里直接贴代码吧, 没什么难度, sentence_transformers库封装得太好了

import os
from sentence_transformers import SentenceTransformer, SentencesDataset
from sentence_transformers import InputExample, evaluation, losses
from torch.utils.data import DataLoader


class TrainSBert:
    def __init__(self, config, options):
        self.model_name="sentence-transformers/all-MiniLM-L6-v2"
        self.data_path = "自己的标注数据路径"
        self.model = None
        self.model_base_dir = '模型保存base路径'
        self.model_dir = os.path.join(self.model_base_dir, self.model_name.split("/")[-1])
        if not os.path.exists(self.model_dir):
            os.makedirs(self.model_dir)
        self.evaluate_path = os.path.join(self.model_dir, "result.txt")


    def load(self):
        self.model = SentenceTransformer(self.model_name)
    

    def load_train_data(self):
        file_handle = open(self.data_path, 'r')

        train_data_list = []
        dev_sentences1, dev_sentences2, dev_labels = [], [], []
        count = 0
        for line in file_handle:
            item_list = line.strip().split("\t")
            sa = item_list[0]
            sb = item_list[1]
            label = float(item_list[2])
            count += 1
            if count <= 5000:
                dn = InputExample(texts=[sa, sb], label=label)
                train_data_list.append(dn)
            else:
                dev_sentences1.append(sa)
                dev_sentences2.append(sb)
                dev_labels.append(label)

        train_dataset = SentencesDataset(train_data_list, self.model)
        train_dataloader = DataLoader(train_dataset, shuffle=True, batch_size=32)
        return train_dataloader, dev_sentences1, dev_sentences2, dev_labels
    
    
    def train(self):
        self.load()
        train_dataloader, dev_sentences1, dev_sentences2, dev_labels = self.load_train_data()

        train_loss = losses.CosineSimilarityLoss(self.model)
        evaluator = evaluation.EmbeddingSimilarityEvaluator(dev_sentences1, dev_sentences2, dev_labels)
        self.model.fit(train_objectives=[(train_dataloader, train_loss)], epochs=10, warmup_steps=100,
          evaluator=evaluator, evaluation_steps=100, output_path= self.model_dir)
        self.model.evaluate(evaluator, self.evaluate_path)

没几行代码, 训练完成后, 我们来看看效果:

s1: 二叉树的python实现 与 s2: Ribbon实现负载均衡 相似度为: 0.18773505091667175
s1: 二叉树的python实现 与 s2: 使用openFeign实现负载均衡 相似度为: -0.04088197648525238
s1: 二叉树的python实现 与 s2: Nginx负载均衡实现 相似度为: 0.018543850630521774
s1: 二叉树的python实现 与 s2: python 的二叉树实现 相似度为: 0.965272068977356
s1: 二叉树的python实现 与 s2: 请问下二叉树用python怎么实现, 求求各位大佬了, 小弟实在不会 相似度为: 0.8639361262321472
s1: 二叉树的python实现 与 s2: 二叉树的python实现 相似度为: 1.0
s1: 二叉树的python实现 与 s2: 二叉树的c++实现 相似度为: 0.21337147057056427

这效果, 绝了!

接着, 我们用微调好的SBERT, 将知识库向量化后, 存到PG数据库中, 也就是query_vector字段的部分.
pgvector的官方仓库: https://github.com/pgvector/pgvector

milvushnswlibfaiss等都可以实现向量的存储, 这一块的工具还是挺多的, 主要是索引的构建方式不同, 感兴趣的可以去了解一下

这里要说明一下, 在meta里面的tags字段, 存的是博客标签, 这样做的好处:
1、加速召回
2、在一定程度上提高召回准确率

原因: 通过传入博客标签, 我们将query库从全量数据缩小到单个标签的数据, 数据量减少, 速度当然变快, 准确率也有一定提升.

取召回后的top5结果:

query:  android jar包转dex文件
召回数据0: android jar包转dex文件
召回数据1: android jar包免费下载
召回数据2: android jar包下载地址
召回数据3: Android Jar包冲突及解决方法
召回数据4: android 反编译jar包

至此, 粗排的部分我们就完成了

五、精排

精排这部分, 其实就是人工构造特征, 作为LTR模型的输入, 在这里, 我构造了以下特征:

1、SBERT语义相似度
2、最长公共子序列
3、编辑距离
4、jaccard相似度
5、余弦相似度
6、皮尔逊相关性系数
7、欧式距离
8、KL散度

大家可以适当删减, 因为有些相似度的计算方法是类似的

    def jaccard_sim(self, str_a, str_b):
        seta = set(self.segment.segment(str_a))
        setb = set(self.segment.segment(str_b))
        sa_sb = 1.0 * len(seta & setb) / len(seta | setb)
        return sa_sb

    def cos_sim(self, a, b):
        a = np.array(a)
        b = np.array(b)
        return np.sum(a * b) / (np.sqrt(np.sum(a**2)) * np.sqrt(np.sum(b**2)))

    def eucl_sim(self, a, b):
        a = np.array(a)
        b = np.array(b)
        return 1 / (1 + np.sqrt((np.sum(a - b) ** 2)))

    def pearson_sim(self, a, b):
        a = np.array(a)
        b = np.array(b)
        a = a - np.average(a)
        b = b - np.average(b)
        return np.sum(a * b) / (np.sqrt(np.sum(a**2)) * np.sqrt(np.sum(b**2)))

    def kl_divergence(self, p, q):
        return scipy.stats.entropy(q, p)

训练数据还是我们用来微调SBERT的那部分有监督数据

LTR模型使用的是lightgbmLGBMRanker, 文档请看: LGBMRanker

不得不说, 参数是真的多, 我使用的参数:

params = {
    "boosting_type": "gbdt",
    "max_depth": 5,
    "objective": "binary",
    "num_leaves": 64,
    "learning_rate": 0.05,
    "max_bin": 512,
    "subsample_for_bin": 200,
    "subsample": 0.5,
    "subsample_freq": 5,
    "colsample_bytree": 0.8,
    "reg_alpha": 5,
    "reg_lambda": 10,
    "min_split_gain": 0.5,
    "min_child_weight": 1,
    "min_child_samples": 5,
    "scale_pos_weight": 1,
    "group": "name:groupId",
    "metric": "auc",
}

具体参数的含义及作用, 还是查看官方文档吧.

六、Prompt

我使用的prompt:

假如你是一名资深的IT专家, 请你结合以下参考资料和你现有的知识回答以下问题, 尽量给出具体的解决方案, 请将每一步都以清晰易懂的语言告诉我, 请尽可能地展示代码, 如果你没有把握解决该问题, 只需要回答: 我无法解决该问题, 请不要试图编造假的答案来忽悠我, 答案用markdown格式返回, 以下是问题和参考资料:
问题:
{query}

参考资料:
{blog_content}

prompt确实是门玄学, 多用Chatgpt, 调起来就没那么难, 据说以后会不需要自己调prompt, 由模型自己来完成, 当然这也是趋势

整体来说, 跟ChatPDF的原理类似, 都是匹配相关性文档, 然后再让Chatgpt总结答案

总结

1、重点部分是SBERT训练数据集的构建
2、可能的优化方向:

  • 结构化博客的方法更加合理
  • 增加SBERT微调数据集
  • 精排模型的优化
  • ChatGPTEmbedding接口来替代自己的句向量模型

3、项目的代码不方便开源, 涉及到的东西太多了, 大家有任何问题, 可以在评论区留言

相关博客

  • 基于Sentence-Bert的检索式问答系统
  • FAQ式问答系统

有帮助的话, 一键三连吧, 跪谢

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/28210.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot项目实战:自定义异常和统一参数验证(附源码)

你好&#xff0c;我是田哥 在实际开发过程中&#xff0c;不可避免的是需要处理各种异常&#xff0c;异常处理方法随处可见&#xff0c;所以代码中就会出现大量的try {...} catch {...} finally {...} 代码块&#xff0c;不仅会造成大量的冗余代码&#xff0c;而且还影响代码的可…

母婴商家怎么建立自己的品牌,母婴产品传播渠道总结

随着互联网的发展逐渐深入我们的生活&#xff0c;线上传播的模式也越来越被大家熟知。越来越多的行业开始重视线上传播。那么母婴商家怎么建立自己的品牌&#xff0c;母婴产品传播渠道总结。 其实&#xff0c;母婴产品线上用户群体众多&#xff0c;且母婴产品用户目的明确&…

深入解析IT专业分类、方向及就业前景:高考毕业生如何选择适合自己的IT专业?重点探索近年来人工智能专业发展及人才需求

目录 一、IT专业的就业前景和发展趋势二、了解IT专业的分类和方向三、你对本专业的看法和感想四、本专业对人能力素养的要求五、建议和思考其它资料下载 当今社会&#xff0c;信息技术行业以其迅猛的发展和无限的潜力成为了吸引无数年轻人的热门选择。特别是对于高考毕业生来说…

你的企业还没搭建这个帮助中心网页,那你太落后了!

作为现代企业&#xff0c;拥有一个完善的帮助中心网页已经成为了不可或缺的一部分。帮助中心网页不仅可以提供给用户有关产品或服务的详细信息&#xff0c;还可以解答用户的疑问和提供技术支持&#xff0c;使用户在使用产品或服务时遇到问题可以很快地得到解决。因此&#xff0…

论文阅读和分析:Binary CorNET Accelerator for HR Estimation From Wrist-PPG

主要贡献&#xff1a; 一种完全二值化网络(bCorNET)拓扑结构及其相应的算法-架构映射和高效实现。对CorNET进行量化后&#xff0c;减少计算量&#xff0c;又能实现减轻运动伪影的效果。 该框架在22个IEEE SPC受试者上的MAE为6.675.49 bpm。该设计采用ST65 nm技术框架&#xff…

数据结构--队列2--双端队列--java双端队列

介绍 双端队列&#xff0c;和前面学的队列和栈的区别在于双端队列2端都可以进行增删&#xff0c;其他2个都是只能一端可以增/删。 实现 链表 因为2端都需要可以操作所以我们使用双向链表 我们也需要一共头节点 所以节点设置 static class Node<E>{E value;Node<E…

jetpack compose —— Card

jetpack compose Card 组件提供了一种简单而强大的方式来呈现卡片式的用户界面。 一、什么是 Card 组件 二、基本用法 三、属性和修饰符 四、嵌套使用和复杂布局 一、什么是 Card 组件 Card 是 Jetpack Compose 中的一个常用组件&#xff0c;用于呈现卡片式的用户界面。它…

Javaweb学习路线(3)——SpringBoot入门、HTTP协议与Tomcat服务器

一、SpringBoot入门 &#xff08;一&#xff09;第一个Springboot案例 1、创建Springboot工程&#xff0c;添加依赖。 2、定义类&#xff0c;添加方法并添加注释 3、运行测试。 pom.xml&#xff08;框架自动生成&#xff09; <?xml version"1.0" encoding&quo…

不同等级的Pads工程师,薪资差距有多大?

作为一种广泛应用在PCB设计的EDA工具&#xff0c;Pads软件在中国的电子设计行业中有着重要地位&#xff0c;尤其是不同等级的Pads工程师&#xff0c;在薪资、工作范围等有很大的差异&#xff0c;本文将从中国出发&#xff0c;多方面分析对比不同等级的Pads工程师&#xff0c;希…

24个Jvm面试题总结及答案

1.什么是Java虚拟机&#xff1f;为什么Java被称作是“平台无关的编程语言”&#xff1f; Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。 Java被设计成允许应用程序可以运行在任意的平台&#xff0c;而不需要程序员为每…

【VMware】虚拟机安装centos7

目录 一、创建虚拟机 1、自定义 2、选择需要安装的操作系统 3、选择虚拟机安装位置 4、选择处理器配置&#xff08;可先默认&#xff09; 5、设置虚拟内存&#xff08;一般4096&#xff09; 6、选择网络连接方式 7、选择I/O控制器 8、选择磁盘类型 9、选择磁盘 10、指定磁盘容…

国内云服务器全面对比

想要领取优惠券购买云服务可以前往我的云服务器领券购买。 经过疫情三年&#xff0c;大多行业开始复苏&#xff0c;企业开始布局以后得发展&#xff0c;云服务器作为企业发展几乎是必须的&#xff0c;一个企业从无到有&#xff0c;要经历很多&#xff0c;比如企业官网搭建&…

解密混沌工程——混沌工程价值

在数字化转型、十四五规划的大背景 下&#xff0c;大规模上云、分布式的核心改造等“云化”逐渐走进企业。 但是&#xff0c;云化的发展&#xff0c;使企业系统的复杂度呈指数级增长&#xff0c;故障越来越多。 企业在数字化转型中拥抱云计算、 信创国产化、分布式核心等新技…

DVWA-Command Injection

大约 命令注入攻击的目的是在易受攻击的应用程序中注入和执行攻击者指定的命令。 在这种情况下&#xff0c;执行不需要的系统命令的应用程序就像一个伪系统外壳&#xff0c;攻击者可能会使用它 作为任何授权的系统用户。但是&#xff0c;命令的执行权限和环境与 Web 服务具有的…

JVM 调优分析 如何进行JVM调优

文章目录 1.为什么需要进行JVM调优&#xff1f;2.什么情况下可能需要JVM调优3.JVM调优参数4.JVM调优参数设置参考5.JVM内部结构1. 类加载器&#xff08;Class Loader&#xff09;2. 运行时数据区&#xff08;Runtime Data Area&#xff09;3. 垃圾收集器&#xff08;Garbage Co…

jmeter如何将上一个请求的结果作为下一个请求的参数

目录 1、简介 2、用途 3、下载、简单应用 4、如何将上一个请求的结果作为下一个请求的参数 1、简介 在JMeter中&#xff0c;可以通过使用变量来将上一个请求的结果作为下一个请求的参数传递。 ApacheJMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测…

adb shell 调试 Android 串口 百度AI也很

在 Android 平台上进行串口调试需要使用 Android Debug Bridge (ADB) 工具。ADB 是一个命令行工具&#xff0c;可以通过 USB 连接 Android 设备&#xff0c;并执行各种命令来调试应用程序。 以下是使用 ADB shell 进行 Android 串口调试的步骤&#xff1a; 连接 Android 设备…

低代码开发平台介绍

低代码开发平台近两年发展迅猛&#xff0c;并迅速渗透到各个细分领域。本文简要介绍低代码开发的概念以及特性&#xff0c;并结合低代码开发的应用场景介绍两个低代码开发平台。 1、低代码开发概念 1.1 低代码开发介绍 低代码开发&#xff08;Low-code Development&#xff0…

3D格式转换工具HOOPS Exchange​助力Zuken打造电子设计自动化产品

行业&#xff1a;电子制造 挑战&#xff1a;对制造商来说&#xff0c;电子设计变得越来越复杂 - 电气和机械设计的融合需要将二维和三维数据结合起来 - 需要提供对多种不同CAD格式的支持 解决方案&#xff1a;HOOPS Exchange是用于快速、准确的CAD数据转换的领先SDK&#xff…

vue Electron ArcGis 桌面应用 Sqllite3 node-grp:老旧项目的起死回生

最近接收了一个三四年前做的项目。主要技术栈就是vue2electronsqllite3node-gyp。看到这个技术栈&#xff0c;基本可以知道感知这个项目的关键词&#xff1a;vue、Gis地图、本地数据库、桌面客户端。顿时深感亚历山大。 不多说&#xff0c;开干。 第一步&#xff0c;查看项目…