1 输出结果要求
input:阅读文章本体与完成QAG的prompt
target:题干、题目选项及答案
2 数据处理
2.1 Python 正则表达式
需要将文件中的数据读取出来,将语文数据与英语数据整理好后存储成可以微调的数据格式(csv与jsonl类型)
正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。
Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式。
re 模块使 Python 语言拥有全部的正则表达式功能。
compile 函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。
re 模块也提供了与这些方法功能完全一致的函数,这些函数使用一个模式字符串做为它们的第一个参数。
2.1.1 Pandas库
Pandas 是 Python 语言的一个扩展程序库,用于数据分析,名字衍生自术语 "panel data"(面板数据)和 "Python data analysis"(Python 数据分析)。
Pandas 是一个开放源码、BSD 许可的库,提供高性能、易于使用的数据结构和数据分析工具。
Pandas 一个强大的分析结构化数据的工具集,基础是 Numpy(提供高性能的矩阵运算)。
Pandas 可以从各种文件格式比如 CSV、JSON、SQL、Microsoft Excel 导入数据。
Pandas 可以对各种数据进行运算操作,比如归并、再成形、选择,还有数据清洗和数据加工特征。
3 流程介绍
3.1 模型微调
3.1.1 微调范式
-
增量预训练微调 (Continue PreTraining)
使用场景:让基座模型学习到一些新知识,如某个垂类领域的常识
训练数据:文章、书籍、代码等
-
指令跟随微调 (Supervised Finetuning)
使用场景:让模型学会对话模板,根据人类指令进行对话
训练数据:高质量的对话、问答数据
3.1.2 使用微调的情况
(1) 使用的数据集和预训练模型的数据集相似,如果不太相似,效果可能就没有那么好了,特征提取是不同的,所以相应的参数训练后也是不同的。
(2) 自己搭建或者使用的模型正确率太低。
(3)数据集相似,但数据集数量太少。
(4)计算资源太少。
不同数据集下使用微调
-
数据集1 - 数据量少,但数据相似度非常高的情况下:修改最后几层或最终的softmax图层的输出类别。
-
数据集2 - 数据量少,数据相似度低在这种情况下,可以冻结预训练模型的初始层(比如k层),并再次训练剩余的(n-k)层。由于新数据集的相似度较低,因此根据新数据集对较高层进行重新训练具有重要意义。
-
数据集3 - 数据量大,数据相似度低在这种情况下,由于我们有一个大的数据集,我们的神经网络训练将会很有效。但是,由于我们的数据与用于训练我们的预训练模型的数据相比有很大不同。使用预训练模型进行的预测不会有效。因此,最好根据你的数据从头开始训练神经网络(Training from scatch)。
-
数据集4 - 数据量大,数据相似度高这是理想情况。在这种情况下,预训练模型应该是最有效的。使用模型的最好方法是保留模型的体系结构和模型的初始权重。然后,我们可以使用在预先训练的模型中的权重来重新训练该模型。
微调指导事项
1.通常的做法是截断预先训练好的网络的最后一层(softmax层),并用与我们自己的问题相关的新的softmax层替换它。例如,ImageNet上预先训练好的网络带有1000个类别的softmax图层。如果我们的任务是对10个类别的分类,则网络的新softmax层将由10个类别组成,而不是1000个类别。然后,我们在网络上运行预先训练的权重。确保执行交叉验证,以便网络能够很好地推广。
2.使用较小的学习率来训练网络。由于我们预计预先训练的权重相对于随机初始化的权重已经相当不错,我们不想过快地扭曲它们太多。通常的做法是使初始学习率比用于从头开始训练(Training from scratch)的初始学习率小10倍。
3. 如果数据集数量过少,我们进来只训练最后一层,如果数据集数量中等,冻结预训练网络的前几层的权重也是一种常见做法。
3.1.3 LoRA
LoRA 的优势
- 可以针对不同的下游任务构建小型 LoRA 模块,从而在共享预训练模型参数基础上有效地切换下游任务。
- LoRA 使用自适应优化器(Adaptive Optimizer),不需要计算梯度或维护大多数参数的优化器状态,训练更有效、硬件门槛更低。
- LoRA 使用简单的线性设计,在部署时将可训练矩阵与冻结权重合并,不存在推理延迟。
- LoRA 与其他方法正交,可以组合。
LoRA 的原理
3.2 Baseline代码解读
3.2.1 导入库
!pip install pandas openpyxl
3.2.2 数据处理
使用pandas加载xlsx的数据(在replace
方法中使用参数regex=True
时,表示希望使用正则表达式来执行替换操作)
# coding~
import pandas as pd
import re
# 读取Excel文件
df = pd.read_excel('训练集-语文.xlsx')
df = df.replace('.', '.', regex=True)
df = df.replace('(', '(', regex=True)
# 读取第二行(即第三行)“选项”列的内容
# 可以使用loc获取某行的数据
second_row_option_content = df.loc[2, '选项']
# 显示第二行“选项”列的内容
print(second_row_option_content)
3.2.3 抽取问题
正则表达式在代码中被用来识别和提取文本中的特定模式。
第一个用于识别问题编号和问题文本,第二个用于识别选择题的选项,第三个用于重新格式化问题编号和提取问题文本。
使用这些正则表达式,代码能够解析和重构输入文本,以便提取出结构化的问题和选项数据。
1. `question_pattern = re.compile(r'\d+\..*?(?=\d+\.|$)', re.DOTALL)`
- `\d+` 匹配一个或多个数字,表示问题编号。
- `\.` 匹配点号(`.`),在正则表达式中点号需要转义,因为它是一个特殊字符。
- `.*?` 是一个非贪婪匹配,匹配任意字符(除了换行符,因为使用了 `re.DOTALL` 标志),直到遇到下一个模式。
- `(?=\d+\.|$)` 是一个正向先行断言,它匹配字符串的某个位置,但不会消耗任何字符。这里它匹配一个数字后面跟着一个点号,或者字符串的结尾 `$`。这意味着匹配将结束于下一个问题编号或文本的末尾。
2. `choice_pattern = re.compile(r'([A-D])\s*(.*?)(?=[A-D]|$|\n)', re.DOTALL)`
- `([A-D])` 匹配单个字符 A、B、C 或 D,括号表示这是一个捕获组。
- `\s*` 匹配零个或多个空白字符。
- `(.*?)` 是一个非贪婪匹配,匹配任意字符,直到遇到下一个模式。这也是一个捕获组,用于捕获选项的文本。
- `(?=[A-D]|$|\n)` 是一个正向先行断言,它匹配下一个字符是 A 到 D 中的一个,或者字符串的结尾 `$`,或者换行符 `\n`。这意味着匹配将结束于下一个选项编号或文本的末尾。
3. `pattern_question = re.compile(r'(\d+)\.(.*)')`
- 这个正则表达式用于提取问题编号和问题文本。
- `\d+` 匹配问题编号,即一个或多个数字。
- `\.` 匹配点号,表示问题编号和问题文本之间的分隔符。
- `(.*)` 匹配问题文本,即点号之后的所有字符,直到行尾或字符串末尾。
def chinese_multiple_choice_questions(questions_with_answers):
# 输入的题目文本
text = questions_with_answers
question_pattern = re.compile(r'\d+\..*?(?=\d+\.|$)', re.DOTALL)
# 这一行作用是匹配一个以数字开头、后面跟着一个点字符的字符串,
#。直到遇到下一个数字和点字符或字符串结束。
choice_pattern = re.compile(r'([A-D])\s*(.*?)(?=[A-D]|$|\n)', re.DOTALL)
# 这一行作用是匹配一个以字母[A到D]开头、后面跟着一个点字符的字符串,
#直到遇到下一个[A到D]或字符串结束。
# 找到所有问题
questions = question_pattern.findall(text)
# 初始化选择题和简答题列表
multiple_choice_questions = []
short_answer_questions = []
# 处理每个问题
for id,question in enumerate(questions):
# 这里取到的question,如果是选择题会带着选择题的选项。
# 检查是否是选择题 因为选择题内有ABCD这样的选项
if re.search(r'[A-D]', question):
# 如果有选项,提取出选项的内容
choices = choice_pattern.findall(question)
# 这里提取了题目的内容,因为每个题目都会有一个打分的(X分)这样的标记
# 以左括号为目标,截取选择题选项中的内容
question_text = re.split(r'\n', question.split('(')[0])[0]
pattern_question = re.compile(r'(\d+)\.(.*)')
# 这里清洗了选择题的编号,重新用循环中的id进行编号。
# 如果不做这一步可以发现给定的数据中编号是乱序的。
matches_question = str(id+1)+'.'+ pattern_question.findall(question_text)[0][1] # 取出问题后重排序
# print(str(id+1)+'.'+matches_question)
# 这里我们实现声明好了存储的列表
# 将每个问题和选项以字典的形式存入方便我们处理
multiple_choice_questions.append({
'question': matches_question,
'choices': choices
})
else:
# 大家可以想想这里怎么用?
short_answer_questions.append(question.strip())
# 最后我们返回抽取后的选择题字典列表
return multiple_choice_questions
3.2.4 Prompt设计
原代码:
def get_prompt_cn(text):
prompt = f'''
你是⼀个⾼考选择题出题专家,你出的题有⼀定深度,你将根据阅读文本,出4道单项选择题,包含题目选项,以及对应的答案,注意:不⽤给出原文,每道题由1个问题和4个选项组成,仅存在1个正确答案,请严格按照要求执行。 阅读文本主要是中文,你出的题目需要满足以下要点,紧扣文章内容且题干和答案为中文:
### 回答要求
(1)理解文中重要概念的含义
(2)理解文中重要句子的含意
(3)分析论点、论据和论证方法
### 阅读文本
{text}
'''
return prompt
def get_prompt_en(text):
prompt = f'''
你是⼀个⾼考选择题出题专家,你出的题有⼀定深度,你将根据阅读文本,出4道单项选择题,包含题目选项,以及对应的答案,注意:不⽤给出原文,每道题由1个问题和4个选项组成,仅存在1个正确答案,请严格按照要求执行。
The reading text is mainly in English. The questions and answers you raised need to be completed in English for at least the following points:
### 回答要求
(1)Understanding the main idea of the main idea.
(2)Understand the specific information in the text.
(3)infering the meaning of words and phrases from the context
### 阅读文本
{text}
'''
return prompt
建议修改“回答要求”部分
3.2.5 数据合并
# 将两个列表转换为DataFrame
df_new = pd.DataFrame({'input': cn_input+cn_input[:30]+en_input+en_input[:20], 'output': cn_output+cn_output[:30]+en_output+en_output[:20]})
这段代码是使用Python中的Pandas库来创建一个新的DataFrame对象。DataFrame是Pandas中用于存储表格数据的主要数据结构,类似于Excel中的表格。
1. `df_new = pd.DataFrame()`
- 这行代码初始化一个新的空DataFrame对象,并将其赋值给变量 `df_new`。
2. `{'input': cn_input+cn_input[:30]+en_input+en_input[:20], 'output': cn_output+cn_output[:30]+en_output+en_output[:20]}`
- 这是一个字典,其中的键是列名('input' 和 'output'),值是列的数据。
3. `cn_input`
- 这是一个列表,存储中文输入数据。
4. `cn_input[:30]`
- 这是Python的切片操作,表示从 `cn_input` 列表的开始取前30个元素。
5. `en_input`
- 这是一个列表,存储英文输入数据。
6. `en_input[:20]`
- 这是对 `en_input` 列表的切片操作,表示从列表的开始取前20个元素。
7. `cn_output`
- 这是一个列表,存储中文输出数据。
8. `cn_output[:30]`
- 这是对 `cn_output` 列表的切片操作,表示从列表的开始取前30个元素。
9. `en_output`
- 这是一个列表,存储英文输出数据。
10. `en_output[:20]`
- 这是对 `en_output` 列表的切片操作,表示从列表的开始取前20个元素。
11. `cn_input+cn_input[:30]+en_input+en_input[:20]`
- 这行代码将 `cn_input` 列表的所有元素、`cn_input` 列表的前30个元素、`en_input` 列表的所有元素和 `en_input` 列表的前20个元素连接起来,形成一个新的列表。
12. `cn_output+cn_output[:30]+en_output+en_output[:20]`
- 这行代码将 `cn_output` 列表的所有元素、`cn_output` 列表的前30个元素、`en_output` 列表的所有元素和 `en_output` 列表的前20个元素连接起来,形成一个新的列表。
最终,这个字典被传递给 `pd.DataFrame()` 构造函数,创建一个新的DataFrame,其中包含两列:'input' 和 'output'。'input' 列包含连接后的中文和英文输入数据,'output' 列包含连接后的中文和英文输出数据。
这段代码假设 `cn_input`、`cn_output`、`en_input` 和 `en_output` 都是已经定义好的列表,且它们的元素数量足够进行切片操作。如果这些列表的长度小于切片操作中指定的数字,切片操作将只返回实际存在的元素数量。