项目实训2024.04.12日志:Self-QA生成问答对

news2024/11/27 12:40:54

1. Self-QA技术

1.1. 为什么要用Self-QA技术

关于为什么要搜集问答对,我在创新实训2024.04.07日志:提取QA对这篇文章中提到过:训练大模型需要从业务侧积累的问题、资料、文档中提取出一些指令-问答对作为输入的语料。

之前我们对于问答对的提取技术是通过对易学百科全书进行文档分割与分词,根据一些特征、标志来提取问题及其相应的回答。这建立在预处理后的易学百科全书是一个结构化的文本的基础上:前一个段落是问题,那么后一个段落一定是相应的回答,再后一个段落一定是下一个问题。

而易学相关的资料远远不止这本《易学百科全书》,我们还有许多原始的、未经过人工预处理的无结构文本。这些文本的内容和形式没有规律,因此无法采用原来的方式提取QA对。例如山东大学易学研究中心筹办30余年的《周易研究》期刊,上面涉及了大量的权威易学方面的著述,都是作为模型训练所需的语料的优质文档。当问题也如上所述,这些文档都是无结构的。例如:

这样的文档并没有明确地提出一个问题及其对应的解答,而是需要读者去仔细阅读、理解,并根据文章内容归纳出问题与解答。

而30余年的资料,我们团队并没有这么多人力与时间去仔细分析。因此我们便倾向于使用大语言模型,为我们阅读这批资料。

1.2. 什么是Self-QA技术

Self-QA技术的定义

Self-QA(Self Question Answering)技术是一种自然语言处理(NLP)技术,它旨在通过生成问题并自行回答来增强机器对文本的理解。这种技术通常用于提升机器学习模型,尤其是深度学习模型在问答、文本理解和生成等领域的性能。

Self-QA技术的核心思想是通过模型自身生成的问题来测试和提高其对文本的理解能力。在这个过程中,模型需要对给定的文本内容进行深入分析,生成相关的问题,然后使用文本内容来回答这些问题。通过这种方式,模型可以在没有额外标注数据的情况下进行自我训练和优化。

Self-QA技术的特点

  1. 自我监督学习:Self-QA技术是一种自我监督学习方法,这意味着模型不需要大量的标注数据来提高性能。模型通过生成问题并自行回答来进行自我训练,从而提高对文本的理解能力。

  2. 增强理解能力:通过自我生成的问题,模型被迫深入理解文本内容,这有助于提高模型对文本的语义理解。这种深入理解对于问答系统、文本摘要和机器翻译等任务至关重要。

  3. 数据集生成:Self-QA技术可以用于生成大规模的问答数据集。通过让模型对各种文本内容进行自我提问和回答,可以创建出大量的训练数据,这对于训练更强大的NLP模型非常有用。

  4. 可解释性:Self-QA技术可以提高模型的可解释性。由于模型需要生成问题并回答问题,这可以帮助研究人员和开发者理解模型是如何理解文本的,以及模型的决策过程。

可以看到,这项技术的增强理解能力、数据集生成能力,都是我们这个项目前期准备语料时迫切需要的。

2. 实现一个简单的Self-QA应用程序

在创新实训2024.04.11日志:self-instruct生成指令这篇文章中,我介绍了智谱AI开发平台。Self-Instruct任务就是基于这个平台的API完成的。

在本次任务中,我们仍选用该开发平台实现Self-QA应用程序。如果你对于这个平台还不了解,请移步我于04.11撰写的日志以及官网了解情况。

2.1. 掉电与宕机的容错机制

首先我们先来看一下原始语料长成什么样。简单来说就是一个根目录下,放置着某些年份的文件夹,进入某个文件夹后,放的是这一年每一期的文件夹,再向下一级就是每一期的文章。

很显然这种树型结构的文件目录结构,我们要写一个深搜/广搜来处理每一个不可再拆分的子文件。将其传输给智谱AI,命令他帮我们阅读文章并提出问答对。

不过这里的问题是,我是在自己的电脑上跑这个应用程序的。众所周知软件园校区工作日一到12点就要断电,掉电之后我的电脑不久就会因没电而关机,再次重启后就不知道应用程序执行到哪里了,或者说不知道处理到哪篇文章了。

因此我们需要加上一个掉电容错的机制,或者说我们要加入一个工作日志系统。

对此,我如是设计:

  1. 对于每个文件夹,规定:任务完成当且仅当
    1. 其下的所有子文件夹任务完成
    2. 其下的所有子文件任务完成(注意子文件仅涉及当前文件夹下的文件,而非子文件夹下的文件)
  2. 在每个文件夹下,生成一个名为"Workstation.txt”文件,其中保存了这个文件夹下的所有子文件夹名与子文件名
  3. 应用程序执行时,先查找当前目录的Workstation.txt文件,查看其中保存的任务名(子文件夹名+子文件名)。
  4. 随后,对于子文件,通过API接口喂给智谱AI平台,生成问答对并保存。
  5. 对于子文件夹,递归地调用这个过程。
  6. 当且仅当一个文件夹的任务完成时,我们清空其Workstation.txt文件中的文本。

当掉电发生时,我们重启机器与这个应用程序,至多冗余地询问某一年某一期的所有文章,但这样保证了我们一个任务都不会漏掉。

对于Workstation.txt的生成,显然也是个DFS就可以解决的。这里我要说一下,我一直都是个java程序员,因此我写代码的首选语言肯定是java,但是java做数据处理不太方便,因此有时候我会改成python。对于这个生成Workstation.txt的工作,用java很方便,我就选了java。

package Scripts;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class GenerateWorkStation {
    static TargetDocRule target;
    public static void main(String[] args) throws IOException {
        String root = args[0];

        File f = new File(root);
        assert f.isDirectory();

        target = new TargetDocRule(args[1]);

        dfs(f);
    }

    private static void dfs(File f) throws IOException {
        StringBuilder sb = new StringBuilder();

        File[] files = f.listFiles();
        assert files != null;
        for (File child : files) {
            String name = child.getName();
            if(child.isDirectory()){
                dfs(child);
                sb.append(name).append("\n");
            }
            if(target.isTargetFile(name)){
                sb.append(name).append("\n");
            }
        }

        File file = new File(f.getAbsolutePath()+"\\Workstation.txt");
        if(!file.exists()){
            boolean created = file.createNewFile();
            assert created;
        }
        try(FileWriter fw = new FileWriter(file)){
            fw.append(sb.toString());
        }
        System.out.println("成功为"+f.getName()+"创建工作目录");
    }
}

这个深搜的逻辑很简单了,创建文件并写入子文件名与子文件夹名也是基本操作。不过这里由于python对于复杂的pdf的支持不太好,因此我对于目标语料的选取,选择了docx。因此我为这个选取的过程注入了一个策略类:

class TargetDocRule{
    public String docSuffix;

    public TargetDocRule(String suffix){
        this.docSuffix = suffix;
    }

    public boolean isTargetFile(String filename){
        return filename.endsWith(docSuffix);
    }
}

每次检索到一个文件时,我们就看看他是否满足isTargetFile方法,这个方法检查了文件后缀(我的应用程序是跑在Windows操作系统上的)

举个例子,根目录下的Workstation.txt长成这样:

意味着有4个子任务待做,分别是2020-2023年。仅选择了4年的是因为智谱AI的token有限,做多了我token就不够用了,先做一部分再说。

2.2.  递归完成任务

深度优先搜索

接下来,我们就可以通过Workstation.txt的指引,完成本次Self-QA的任务。步骤如下:

  1. 打开本层目录的Workstation.txt
  2. 读取这个工作日志的内容,查看还有什么任务没有处理
  3. 读取一个未处理的任务,访问该文件(夹)
    1. 如果是文件,调用处理语料的函数
    2. 如果是文件夹,递归地调用该函数
  4. 当本文件夹地任务完成,将本层目录的Workstation.txt清空
def dfs(path):
    work_station = path + '\\' + 'Workstation.txt'
    with open(work_station, 'r+', encoding='UTF-8') as f:
        tasks = f.readlines()
        while len(tasks) > 0:
            task = path + '\\' + tasks[0].rstrip()
            if os.path.isdir(task):
                dfs(task)
            else:
                do_task(task)
            tasks = tasks[1:]
        f.truncate(0)

处理初始语料

所以重点就落在了do_task函数上:

首先,既然要调用大模型的API。我们就得想一个逻辑清晰、结构完整的Prompt。这里我先通过在线的智谱清言提问了几次,最终确定了prompt如下:

def gen_prompt(text: str) -> str:
    prompt = "你是一名研究周易的专家。现在有一篇关于周易的文章,内容为:\n[" + text + "]\n"
    prompt += ("请根据上述文章的内容生成5"
               "个关于周易的尽可能专业且多样化的问题对(即一个问题及其对应的回答)。这些问答对中的问题可以是关于事实的问题,也可以是对相关内容的理解和评价。在提问时,请不要使用“这个”、“那个”等指示代词。\n")
    prompt += "请按照以下格式生成问题与回答:\n"
    prompt += "问题1:......\n回答1:......\n\n问题2:......\n回答2:......"
    return prompt

我们要先给大模型设置一个人设,这里就是“周易的专家”,然后把文章文本喂给它,最后告诉他生成问答对的格式,就生成了一个可用的prompt。

而文档的文本内容来自于docx文件,这里我调用了python-docx库,读取docx文档。

def read_docx(docx_path):
    # 初始化一个Document对象,读取docx文件
    doc = Document(docx_path)

    # 初始化一个空字符串来存储文档中的纯文本
    full_text = []

    # 遍历文档中的每个段落
    for para in doc.paragraphs:
        # 添加段落文本到full_text列表中
        full_text.append(para.text)

        # 将列表中的文本合并成一个字符串并返回
    return '\n'.join(full_text)

这里我简单的采用了段落文本拼接'\n'换行符的方式生成字符串。

随后就可以请求API接口了:

    global response
    try:
        response = client.chat.completions.create(
            model="glm-4",  # 填写需要调用的模型名称
            messages=[
                {"role": "user",
                 "content": content}
            ],
        )
    except Exception as e:
        print(f"发生错误:{e}")

这里有时候会监测敏感词,发生错误,因此要做一下异常处理。

随后我们就可以按提问的格式提取字符串了:

def extract_qa_pairs(input_str):
    qa_pairs = []
    # 分割输入字符串为问答对
    pairs = input_str.split("\n\n")

    # 遍历每个问答对
    for i in range(len(pairs)):
        pair = pairs[i]
        # 分割问答对为问题和答案
        lines = pair.strip().split("\n")
        if len(lines) != 2:
            continue
        question = lines[0].strip("问题" + str(i + 1) + ": ")
        answer = lines[1].strip("回答" + str(i + 1) + ": ")

        # 将问答对添加到列表中
        qa_pairs.append({
            "question": question,
            "answer": answer
        })

    return qa_pairs

但是要注意到的是,这个列表里的数据可能和之前生成的QA对相似度很高,所以我们要做一下去重:

def compute_rouge_l_score(reference, summary):
    """
    计算ROUGE-L分数

    参数:
    reference (str): 参考文本。
    summary (str): 摘要文本。

    返回:
    dict: 包含ROUGE-L分数的字典,包括precision, recall, 和fmeasure。
    """
    rouge = Rouge()
    scores = rouge.get_scores(summary, reference, avg=True)
    return scores['rouge-l']


def update_source_with_extension(source, extension, rouge_l_threshold=0.7):
    """
    根据ROUGE-L分数更新source列表,将extension中与source中元素不相似(ROUGE-L分数不大于阈值)的元素添加到source中。

    参数:
        source (list): 原始的JSON对象列表。
        extension (list): 要添加的JSON对象列表。
        rouge_l_threshold (float): ROUGE-L分数的阈值,用于决定是否添加元素。

    返回:
        None (直接修改传入的source列表)
    """
    for ext_element in extension:
        ext_str = json_to_string(ext_element)
        found_similar = False

        for src_element in source:
            src_str = json_to_string(src_element)
            rouge_l = compute_rouge_l_score(src_str, ext_str)
            print(rouge_l)

            if rouge_l['f'] > rouge_l_threshold:
                found_similar = True
                break

        if not found_similar:
            source.append(ext_element)

这里和self-instruct的pipeline不同的地方在于,我采用了rouge_l的分数,这是一个基于LCS(最长公共子序列)的评判标准。比较适合长文本之间的相似度比较。其中的f1分数是召回率r和精确率p的综合分数,如果>0.7相当于两个文本之间相似度高,我们应该予以舍弃。

最终,我们以json格式把问答对写入json文件中。

    qa_pairs = extract_qa_pairs(qa)
    with open('./self_QA/target/self_qa.json', 'r+', encoding='utf-8') as target_json:
        context = target_json.read()
        print("context:"+context)
        ed = []
        if context:
            target_json.seek(0)
            ed = json.load(target_json)
        qa_json = qa_pairs_to_json(qa_pairs, ed)
        print(qa_json)
        target_json.truncate(0)
        target_json.seek(0)
        target_json.write(qa_json)

3. 运行结果

4. 可能的问题

有时候大模型没那么“听话”,可能生成的QA对不符合格式,比如应该是一个问答对之间有一个空行,但是他生成出来变成了每个问题和答案之间都有一个空行。大模型在某些细节方面上可能还是没有那么智能。不过这种现象随着任务完成数的增多逐渐减少。最终还是可以完成任务的。

如果想要优化这一方面,可以从字符串的模式匹配方面入手,优化模式匹配函数,更精准的提取QA对。这个可以作为以后优化的方向。

5. 开源与复现

https://github.com/Liyanhao1209/ZhouYiLLM.git

java_scripts分支与python_scripts分支。注意,如果你要运行self_qa的脚本,请一定要先使用java_scripts分支下生成Workstation.txt的脚本,生成Workstation.txt。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Django中间件路由映射自动加/斜杠问题原因及分析

输入 http://127.0.0.1:8000/main/index/ 输入 http://127.0.0.1:8000/main/index 路由定义情况 urlpatterns [path("index/", views.index) ]可以发现我在输入URL的index路由时,如果没有和Django定义的路由匹配规则一样的话,浏览器自…

Python——详细解析目标检测xml格式标注转换为txt格式

本文简述了目标检测xml格式标注的内容&#xff0c;以及yolo系列模型所需的txt格式标注的内容。并提供了一个简单的&#xff0c;可以将xml格式标注文件转换为txt格式标注文件的python脚本。 1. xml格式文件内容 <size>标签下为图片信息&#xff0c;包括 <width> …

【SVN】clean up报错:Cleanup failed to process the following paths 解决方法

报错来源&#xff1a;代码更新有一个文件既不能接受自己的也不能接受别人的&#xff0c;只能取消&#xff0c;再提交提醒clean up&#xff0c;随后报标题错误。 解决方法&#xff1a;参考https://www.cnblogs.com/pinpin/p/11395438.html 注&#xff1a;如果clean up的时候有…

代码随想录算法训练营DAY24|C++回溯算法Part.1|回溯算法理论基础、77.组合、组合问题的剪枝操作

文章目录 回溯算法如何理解回溯算法回溯法模版回溯算法模版框架 77.组合树形结构回溯三部曲伪代码CPP代码实现 组合问题的剪枝操作 回溯算法 如何理解回溯算法 回溯法解决的问题都可以抽象为树形结构。 因为回溯法解决的都是在集合中递归查找子集&#xff0c;集合的大小就构成…

Spring Boot集成Graphql快速入门Demo

1.Graphql介绍 GraphQL 是一个用于 API 的查询语言&#xff0c;是一个使用基于类型系统来执行查询的服务端运行时&#xff08;类型系统由你的数据定义&#xff09;。GraphQL 并没有和任何特定数据库或者存储引擎绑定&#xff0c;而是依靠你现有的代码和数据支撑。 优势 GraphQL…

Stable Diffusion 本地部署教程:详细步骤与常见问题解析

作为一位热衷于探索前沿AI技术的博主&#xff0c;近期我深度研究了Stable Diffusion模型的本地部署过程。在这篇教程中&#xff0c;我将详述从环境准备到模型运行的每个步骤&#xff0c;并针对常见的部署问题给出解决方案&#xff0c;帮助你顺利在本地开启Stable Diffusion的创…

pyplot+pandas实现操作excel及画图

1、安装jupyter lab pip install jupyterlab # 启动 建议在指定的项目文件夹下 开启cmd窗口并执行 jupyter lab 启动后会自动打开浏览器访问 2、安装依赖 pip install matplotlib pip install xlrd pip install pandas 3、读取excel import pandas as pddf pd.read_excel(hi…

C# Solidworks二次开发:几何公差IGot相关操作API详解

大家好&#xff0c;今天要介绍的是关于几何公差IGot相关操作的API。 几何公差之前没有讲过&#xff0c;具体API如下面所示&#xff1a; &#xff08;1&#xff09;第一个为GetText&#xff0c;这个API的含义为获取此几何公差的指定文本部分&#xff0c;下面是官方的具体解释&…

基于springboot实现医疗病历互换系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现医疗病历交互系统演示 摘要 进入21世纪&#xff0c;计算机技术迅速向着网络化的、集成化方向发展。传统的单机版应用软件正在逐渐退出舞台&#xff0c;取而代之的是支持网络、支持多种数据信息的新一代网络版应用软件&#xff0c;形成了信息化的社会。信息…

FluentUI系列 - 1 - 介绍第一个窗口

介绍一个QML的UI库&#xff0c;国人编写&#xff0c;作者也耍知乎。这个UI库确实好用&#xff0c;但是教程基本等于无&#xff0c;个人在使用中顺便记录一下学习内容。这玩意儿也有Pyside6的版本&#xff0c;有需要的可以查看PySide6-FluentUI-QML。 FluentUI库地址​github.c…

开关灯---一维数组

直接看题&#xff1a; 开关灯 此题用模拟的复杂度是O(n&#xff09; &#xff0c;其实有更优解就是用完全平方数。但是我不想在C中遇到数学。。。所以用模拟解。 把数组的类型设为bool类型即可&#xff01; AC代码&#xff1a; #include<bits/stdc.h> using namespace …

Unity TMP Inputfield 输入框 框选 富文本 获取真实定位

一、带富文本标签的框选是什么 UGUI的InputField提供了selectionAnchorPosition和selectionFocusPosition&#xff0c;开始选择时的光标下标和当前光标下标 对于未添加富文本标签时&#xff0c;直接通过以上两个值&#xff0c;判断一下框选方向&#xff08;前向后/后向前&…

前端 接口返回来的照片太大 加载慢如何解决

现象 解决 1. 添加图片懒加载 背景图懒加载 对背景图懒加载做的解释 和图片懒加载不同&#xff0c;背景图懒加载需要使用 v-lazy:background-image&#xff0c;值设置为背景图片的地址&#xff0c;需要注意的是必须声明容器高度。 <div v-for"img in imageList&quo…

麒麟 V10 离线 安装 k8s 和kuboard

目录 安装文件准备 主机准备 主机配置 修改主机名&#xff08;三个节点分别执行&#xff09; 配置hosts&#xff08;所有节点&#xff09; 关闭防火墙、selinux、swap、dnsmasq(所有节点) 安装依赖包&#xff08;所有节点&#xff09; 系统参数设置(所有节点) 时间同步…

html 引入vue Element ui 的方式

第一种&#xff1a;使用CDN的方式引入 <!--引入 element-ui 的样式&#xff0c;--> <link rel"stylesheet" href"https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <!-- 必须先引入vue&#xff0c; 后使用element-ui --> <…

Pycharm通过配置隧道连接远程服务器

前言&#xff1a; 上篇有说到局域网windows和服务器互通的情况下连接远程pycharm&#xff0c;这次咱们来说下通过跳板机的方式连接服务器如何做到windows远程连接到服务器 1&#xff1a;设置SSH隧道或SSH代理 ssh -L localhost:LOCAL_PORT:FINAL_SERVER_IP:FINAL_SERVER_PORT…

【深度学习】深入探索卷积神经网络:从基础到先进架构”

卷积神经网络&#xff1a;深度学习的视觉之眼 在过去的十年中&#xff0c;深度学习已经彻底改变了我们处理和理解图像、视频及其他视觉媒体的方式。其中&#xff0c;卷积神经网络&#xff08;CNN&#xff09;无疑是这一革命的核心。本文将带您深入了解CNN的基础知识、关键发展…

CentOS 8服务器搭建L2TP服务器(over IPsec)操作指南

正文共&#xff1a;1234 字 14 图&#xff0c;预估阅读时间&#xff1a;2 分钟 之前发过把我自己的服务器搬上公网的文章&#xff08;我用100块钱把物理服务器放到了公网&#xff0c;省了几万块&#xff01;&#xff09;&#xff0c;当时L2TP拨号用的是网络上的解决方案&#x…

MySQL 修改数据

目录 数据插入-insert 不指定列名插入&#xff1a; 插入整行数据 格式&#xff1a; 多行数据插入 格式&#xff1a; 指定列名插入 插入1行 插入多行 更新字段-update 语法&#xff1a; 删除表 语法&#xff1a; 案例&#xff1a; 数据插入-insert INSERT 将数据行…

c++命令行解析开源库cxxopts上手教程

文章目录 cxxopts快速入门1. cmake环境配置2. 定义解析的规则3. 使用例子 cxxopts 简介 cxxopts是一个轻量级的C命令行解析库&#xff0c;它提供了易于使用的API来定义和解析命令行选项。它支持多种类型的选项&#xff0c;并且允许用户自定义选项的处理逻辑。 项目地址&#x…