Featured image of post 基于 RAG 技术的英语读后续写模型开发实践

基于 RAG 技术的英语读后续写模型开发实践

深度介绍我开发一个英语读后续写垂域模型的全过程

萌生开发一个英语读后续写模型的念头,是在去年11月末。当时偶然看到一篇介绍 RAG 原理的公众号推文,读罢灵光乍现,觉得一款轻量级模型在挂载知识库之后,或许能在特定领域与顶尖的大语言模型一较高下。一天晚上与舍友们谈论 AI ,突然想到了可以将 RAG 应用在英语读后续写的场景。我将这个想法提出,大家交口称赞,说这个项目贴合实际,很有潜力。由于年末的学习和备考压力,这个项目暂时搁置,直到今年一月期末考试结束后,我才有机会抽出时间,独立完成这个项目。

RAG 通识

简单来说,RAG 技术实现了用户输入一个问题或者要求后,系统会先从向量数据库中找出与问题最相关的多个文本,再根据检索到的信息生成一段新的回答,使得生成的内容更贴近真实情境,有效避免 AI 模型的“幻觉”现象。

RAG, (Retrieval Augmented Generation, 检索增强生成),是一种结合了信息检索和文本生成的混合式模型,主要包含两个模块:


  1. 检索模块 (Retrieval Module):该模块从预先构建的知识库中搜索出与当前输入 (Query) 最相关的文本片段。这一步通常利用文本检索模型(such as 阿里的 text-embedding-v3 模型和 BGE-M3 模型)将文本转化为向量,然后通过相似度计算(例如余弦相似度,即求两向量的夹角余弦值)找到最匹配的内容。在数学上表示为:

$$ \text{retrieve}(q) = { d_1, d_2, \ldots, d_k } \quad \text{使得} \quad S(q, d_i) = \cos\big(f(q), f(d_i)\big) \text{最大} $$

即输入 $q$ 个查询,返回 $k$ 个最相关的文档 ${d_1, d_2, \ldots , d_k}$,使得表示查询 $q$ 与文档之间的相似度分数 $S(q, d_i)$ 最大。

其中,$S(q, d_i)=cos(f(q), f(d_i))=\frac{f(q) \cdot f(d_i)}{|f(q)| |f(d_i)|}$

这里的 $f(q)$ 和 $f(d_i)$ 表示查询和文档被转换成的高维向量。余弦相似度计算它们的夹角相似性,值越大表示越相似。


  1. 生成模块 (Generation Module):在获取检索结果后,将其与原始查询一起输入到生成模型中,以生成更加上下文相关和富有信息量的输出。公式为:

$$ y = \arg\max_{y’} P(y’ \mid q, {d_1, d_2, \ldots, d_k}; \theta) $$

该公式表示在所有可能的输出文本 $y’$ 中,找到使得概率 $P(y’ \mid q, { d_1, d_2, \dots, d_k }; \theta)$ 最大的 $y’$,作为最终的生成结果。

其中,$y$ 表示模型的输出,$y’$ 表示所有候选输出文本,$\theta$ 代表模型的参数,$P(y’ \mid q, { d_1, d_2, \dots, d_k }; \theta)$ :表示给定查询 $q$ 和检索到的文档集合后,生成模型输出 $y$ 的概率。

通俗地讲,RAG 先用检索模型找到与 $q$ 相关的文档 ${ d_1, d_2, \dots, d_k }$ ,然后基于这些文档,由生成模型计算生成不同候选答案的概率,并选择概率最高的答案为最优答案 $y$ 。

模型实现

数据收集

好的数据集是模型成功的一半。

——我说的

一个模型的成功与否,通常取决于数据集是否具有较高的质量。没有高质量的数据,再精妙的算法也难以发挥出它的潜力。教师在课上通常使用 PPT 文档授课,我从这些高质量的教学课件中提取英文文本作为原始数据。整个过程包括两个主要步骤:

1. 文件重命名

为了便于后续使用 Python 提取文本,我先按照修改时间顺序为当前文件夹下所有的 .pptx 文件重新命名。下面是一段 VBScript 代码示例,其主要功能是遍历所有 .pptx 文件并按修改时间排序,并将文件名改为形如 数字+".pptx" 的格式。

示例代码如下
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
' 创建文件系统对象
Set fso = CreateObject("Scripting.FileSystemObject")

' 获取当前文件夹路径
currentFolder = fso.GetAbsolutePathName(".")

' 获取当前文件夹中的所有文件
Set folder = fso.GetFolder(currentFolder)
Set files = folder.Files

' 存储所有的 .pptx 文件
Dim pptxFiles()
i = 0

' 遍历文件,筛选出所有 .pptx 文件
For Each file In files
    If LCase(fso.GetExtensionName(file.Name)) = "pptx" Then
        ReDim Preserve pptxFiles(i)
        Set pptxFiles(i) = file  ' 使用 Set 关键字赋值对象
        i = i + 1
    End If
Next

If i = 0 Then
    WScript.Echo "无 .pptx 文件"
    WScript.Quit
End If

' 按照修改时间对文件排序
For i = 0 To UBound(pptxFiles) - 1
    For j = i + 1 To UBound(pptxFiles)
        If pptxFiles(i).DateLastModified > pptxFiles(j).DateLastModified Then
            ' 交换文件对象
            Set temp = pptxFiles(i)
            Set pptxFiles(i) = pptxFiles(j)
            Set pptxFiles(j) = temp
        End If
    Next
Next

' 重命名文件
For i = 0 To UBound(pptxFiles)
    newName = (i + 1) & ".pptx"
    fso.MoveFile pptxFiles(i).Path, currentFolder & "\" & newName
Next

WScript.Echo "文件重命名完成!"

2. 文本提取

接下来,利用 Python 中的 pptx 库提取每个 PPT 文件中所有英文文本。首先遍历当前目录下重命名后的所有 .pptx 文件,按数字顺序排序,然后读取幻灯片中的所有文本,并通过正则表达式过滤出纯英文字符和标点,最后将提取结果保存到 extract.txt 文件中:

示例代码如下
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import os
import re
from pptx import Presentation

pptx_folder = './'
output_txt = 'extract.txt'

# 匹配英文字符和标点符号
english_text_pattern = re.compile(r'[A-Za-z0-9.,!?()";: -]+')

all_english_text = []

# 获取当前目录下所有符合格式的文件
pptx_files = [f for f in os.listdir(pptx_folder) if f.endswith('.pptx') and f[:-5].isdigit()]

# 按照数字顺序排序文件名
pptx_files.sort(key=lambda x: int(x[:-5]))

# 遍历每个 PPT 文件
for pptx_filename in pptx_files:
    pptx_path = os.path.join(pptx_folder, pptx_filename)

    presentation = Presentation(pptx_path)

    # 遍历每一张幻灯片的每个形状
    for slide in presentation.slides:
        for shape in slide.shapes:
            # 确保该形状包含文本框
            if hasattr(shape, 'text'):
                # 获取文本内容
                text = shape.text.strip()

                # 如果文本内容非空,只提取英文文本
                if text:
                    # 用正则表达式提取文本中的英文部分
                    extracted_english_text = ' '.join(english_text_pattern.findall(text))
                    
                    if extracted_english_text:  # 如果有英文文本
                        all_english_text.append(extracted_english_text)

                # 如果文本为空,添加空行
                else:
                    all_english_text.append('')

# 将提取的英文文本写入到 extract.txt 文件
with open(output_txt, 'w', encoding='utf-8') as f:
    for line in all_english_text:
        f.write(line + '\n')

print(f"提取完成,所有英文文本已保存到 {output_txt}")

数据预处理

提取到的原始数据存在拼写错误、标点混用、空格不规范、大小写问题等噪音数据。我选择使用 kimi.ai 对收集到的数据进行清洗、去重、分词等预处理操作,以便后续的模型训练。

示例 Prompt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
这是一个我制作的英语读后续写语料数据集,Kimi,我需要你完成数据清洗任务,请按照以下要求逐步检查并修正数据中的错误:  

### 需要检查和修正的错误类型包括但不限于:
1. 单词拼写错误:检查是否有拼写错误,并按标准英语拼写修正  
2. 中英文符号混用:(确保全英文内容使用英文符号,全中文内容使用中文符号)  
3. 空格问题:缺少空格(如单词间或标点后未正确添加空格);多余空格(如连续多个空格和不必要的前后空格)  
4. 大小写错误:句首单词应大写,专有名词应遵循正确大小写规则  
5. 标点符号错误:检查逗号、句号、引号、括号等是否正确使用,是否有标点符号重复、缺失或误用  
6. 文本格式不规范:是否存在行首/行尾多余空格,是否有异常换行(如单个句子被拆成多行)  
7. 特殊字符和乱码:检查是否有不符合语料格式的特殊字符,移除或修正乱码  

### 任务执行前确认
在正式执行任务之前,请回答以下问题:  
1. 你是否完全理解本次任务的要求和操作流程?  
2. 你是否有任何疑问或需要进一步澄清的地方?  

在 Kimi 清洗数据结束后,还需要加以辅助性的人工标注,确保数据集的准确性。

模型训练

使用 RAG 技术,将预处理后的数据作为输入,训练一个英语读后续写模型。

我使用了一个在 GitHub 上开源的 RAG 框架,FastGPT

构建知识库

首先,将预处理后的数据集上传至知识库,并利用索引模型(例如阿里的 text-embedding-v3)将文本转化为向量表示,实现语义检索。数学上,我们希望通过一个映射函数 $ f: \text{文本} \rightarrow \mathbb{R}^d $ ,使得语义上相似的文本在向量空间中距离更近。相似度计算常用余弦相似度,其公式为:

$$ \text{sim}(a, b) = \frac{f(a) \cdot f(b)}{|f(a)| |f(b)|} $$

其中,$a$ 与 $b$ 分别为两段文本。

创建工作流

接下来,创建一个工作流来实现以下流程:

  1. 知识库检索:在接收到用户输入或测试用例后,首先从知识库中检索出与输入最相关的文本片段。
  2. 文本生成:将检索到的结果作为上下文,与用户输入共同传入文本生成模型(如 DeepSeek V3),通过预先写过的的 Prompt 生成写作内容。

生成模型的目标是最大化生成文本 $y$ 的条件概率:

$$ y = \arg\max_{y’} P\big(y’ \mid q, {d_1, d_2, \ldots, d_k}; \theta\big) $$

参数解释见前文,这里对公式含义不赘述。

借助 FastGPT 框架,部署过程与模型训练均按照其文档步骤完成。主要流程包括:

  • 数据上传与向量索引构建
  • 定义工作流、对接检索模块与生成模块
  • 调整模型参数(例如生成模型中的温度参数)

关于温度参数,简单说明:温度用于控制生成模型输出的随机性。温度越高,生成的文本越随机;反之,则更加确定。在本项目中,为了防止基于 RAG 技术的模型过拟合,建议将温度调至最大温度的 50%~70%。

模型优化

模型训练完成后,通过自定义的测试集对模型效果进行评估。主要评估指标包括生成文本的连贯性、语义相关性以及创新性。若发现效果不佳,可通过优化 Prompt 或调整模型一些参数来改善结果。这一过程也可在 FastGPT 控制台中灵活实现。

模型局限

这个模型公开发布后,受到广泛赞誉,英语老师甚至向我询问了原理,并说这是一种教学新思路。不过,坦率地说,这个模型仍存在较大的局限性:

  1. 数据集局限:样本量很少。在教师上课的课件中,只选取了16份 PPT 文档用于模型训练。数据量和数据质量有待提高,模型的泛化能力有待加强。

  2. 过拟合风险:训练数据少时,RAG 模型会过拟合。之前朋友说,做 LoRA 微调时,数据量在上千个,模型训练超过5个 epoch 很容易过拟合。我的策略是通过调高生成温度缓解这一问题,但仍需要更丰富和多样化的数据来提升模型鲁棒性。

GitHub 仓库:https://github.com/Crosssense-Lab/Kelly-s1
(数据集因版权原因不开源)

Licensed under CC BY-NC-SA 4.0