前言
Phi-Mini 是微软(Microsoft)开发的一款较小型的大语言模型(LLM):
https://huggingface.co/microsoft/Phi-3.5-mini-instruct
流程
使用大语言模型(LLM)和嵌入生成模型(如BGE)来处理和分析测试问题的复杂流程。以下是步骤的详细解释和可能的实现方法:
1. **创建支持问题的数据框架(DF)**:
- 使用训练数据,为每个测试问题找到类似的支持问题。这可能涉及分析问题的“结构”和“主题”等特征。
- 可以使用自然语言处理技术,如向量化和余弦相似度,来识别和聚类相似的问题。
2. **生成消息序列**:
- 对于数据框架中的每对问题/答案,生成一个消息序列。
- 每个问题旁边附带3个错误答案,这些错误答案被呈现为模型已经回答过的问题。
3. **将消息输入Phi-Mini 3.5**:
- 将这些消息序列输入到Phi-Mini 3.5模型中。
- 作为最终消息,给出模型需要预测的问题/答案对。
4. **收集Phi的误解预测**:
- 从Phi-Mini模型中收集关于误解的预测结果。
5. **使用BGE生成嵌入**:
- 使用BGE模型为`misconception_mapping.csv`文件中的所有误解生成嵌入向量。
- 这将帮助将文本数据转换为可以进行数学运算的数值表示。
6. **为LLM预测的误解生成嵌入**:
- 对LLM模型预测出的误解也使用BGE生成嵌入向量。
7. **使用余弦相似度识别最接近的误解**:
- 使用余弦相似度度量方法,将LLM预测出的误解的嵌入向量与`misconception_mapping.csv`中的误解嵌入向量进行比较。
- 识别出25个与LLM预测最相似的误解。
这个流程涉及多个复杂的步骤,包括自然语言处理、机器学习模型的应用、以及向量空间的相似度计算。实现这一流程需要对这些领域有深入的理解和实践经验。
配置文件
# 要在没有互联网连接的情况下运行 Phi 模型,你需要提前下载所需的 .whl 文件
!pip install -q -U transformers --no-index --find-links /kaggle/input/hf-libraries/transformers
-
-q
: 这是--quiet
的简写形式,意味着在安装过程中减少输出,保持安静模式。 -
-U
: 这是--upgrade
的简写形式,它会升级包到最新版本。 -
--no-index
: 这个选项告诉pip忽略Python包索引(PyPI),默认情况下pip会从PyPI查找和安装包。 -
--find-links
: 这个选项后面跟着的是一个URL或者路径,pip会从这个指定的位置查找和安装包。在这个例子中,它指向了一个Kaggle输入目录,这通常意味着在Kaggle_KERNEL环境中,该目录可能包含了预先下载好的transformers
库,以便于快速安装。
# 通用库导入
import sys
# sys
模块提供了访问与Python解释器密切相关的变量和函数。常用于访问命令行参############(sys.argv
)、执行环境变量(sys.path
)等。
import torch
import random
import numpy as np
import pandas as pd
import gc
# gc
模块提供了垃圾收集相关的功能。可以用来手动触发垃圾回收,或获取垃圾回收相关的信息。
import time
import random
from tqdm import tqdm
# tqdm
是一个快速,可扩展的Python进度条库,用于在长循环中添加进度条,提供视觉反馈。
from pprint import pprint
# pprint
模块提供了一个函数,用于美化打印Python数据结构,使其更易于阅读。
from IPython.display import display
# IPython.display
模块中的display
函数用于在Jupyter Notebook中显示对象,使其更加美观。
from sklearn.metrics.pairwise import cosine_similarity
# cosine_similarity
函数来自sklearn
库,用于计算两个或多个向量之间的余弦相似度
from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM, AutoModel
#transformers
库由Hugging Face开发,用于NLP任务,如文本生成、翻译等。pipeline
用创 建 # 预配置的模型管道,AutoTokenizer
用于自动处理文本的分词,AutoModelForCausalLM
和# AutoModel
用于加载预训练的语言模型。
if (not torch.cuda.is_available()): print("Sorry - GPU required!")
import logging
# logging
模块提供了灵活的日志记录系统。可以用于跟踪程序的执行过程,记录错误信息等。
logging.getLogger('transformers').setLevel(logging.ERROR)
- 这行代码配置了日志记录器的行为。
logging
模块用于记录程序运行时的事件,可以通过不同的日志级别来过滤这些事件。 getLogger('transformers')
获取名为transformers
的日志记录器。如果该日志记录器不存在,则创建一个新的。setLevel(logging.ERROR)
设置日志记录器的级别为ERROR
。这意味着只有ERROR
级别及以上(如CRITICAL
)的日志消息会被记录。低于这个级别的日志消息(如WARNING
、INFO
、DEBUG
)将被忽略。- 这个设置通常用于减少控制台或日志文件中的输出量,只关注错误级别的消息。
pd.set_option('display.max_colwidth', None)
pd
是Pandas库的常见别名。set_option
函数用于设置Pandas的全局选项。display.max_colwidth
选项控制DataFrame列内容的最大显示宽度。如果设置为None
,则没有限制,列内容将完整显示,而不会被截断。
pd.set_option('display.max_rows', None)
display.max_rows
选项控制DataFrame在输出时显示的最大行数。如果设置为None
,则没有限制,将显示DataFrame的所有行。
pd.set_option('display.width', None)
display.width
选项控制输出DataFrame时的屏幕宽度。如果设置为None
,则Pandas会自动检测并使用屏幕的宽度,而不是限制输出宽度。- 这可以确保在输出DataFrame时,不会因为屏幕宽度限制而导致列之间的数据显示不完整。
一下配置文件
# 最小值/最大值示例问题,用于提示生成
# 对于每个问题,所有答案中非NaN的误解将被使用
min_example_questions = 5
max_example_questions = 8
# 示例问题消息限制在这么多单词以内
# 确保我们不会耗尽GPU RAM(如果笔记本抛出异常 - 尝试减少...)
max_words_for_examples = 1400
# phi模型回答的最大tokens数
max_new_tokens = 55
#如果我们不进行评分,我们将在训练数据上进行评估,而不是在测试数据上。
eval_on_train_if_not_scoring = True
# 当在MAP@25分数估计中用训练数据替换测试数据时,使用多少个训练问题
# 使用100个问题大约需要30分钟(问题越多,评分估计越准确)
questions_for_train_eval = 100
加载BGE(Bloom's General English)大型英文模型
device = "cuda:0" # "cuda:0"
表示使用第一个GPU设备
bge_tokenizer = AutoTokenizer.from_pretrained('/kaggle/input/bge-large-en-v1.5/transformers/default/1/bge-large-en-v1.5')
bge_model = AutoModel.from_pretrained('/kaggle/input/bge-large-en-v1.5/transformers/default/1/bge-large-en-v1.5')
bge_model.eval()
bge_model.to(device)
print("BGE Loaded!")
# 加载tokenizer, 模型, 进入评估模式
加载 Phi Mini 模型!
# 清除 GPU 内存并删除现有对象
if torch.cuda.is_available():
torch.cuda.empty_cache()
# globals() 返回一个字典,包含了当前全局符号表,即当前模块(或全局)作用域中的所有变量。
for obj in ['model', 'pipe', 'tokenizer']:
if obj in globals():
del globals()[obj]
# 模型配置
model_name = '/kaggle/input/phi-3.5-mini-instruct/pytorch/default/1'
# 加载 tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 加载模型
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.bfloat16,
device_map="auto",
trust_remote_code=True
)
# 创建管道
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, trust_remote_code=True, max_new_tokens=max_new_tokens)
这是一个用于文本生成的管道(pipeline),它使用了某个模型(model
)和分词器(tokenizer
)来生成文本。
"text-generation"
:这指定了管道的类型,表明这个管道用于文本生成任务。model=model
:这是要使用的预训练模型。这个模型可能是专门为文本生成任务训练的,比如 GPT(Generative Pre-trained Transformer)系列模型。tokenizer=tokenizer
:这是与模型配套的分词器,用于将文本分割成模型可以理解的格式(通常是token),并将生成的token重新组合成文本。trust_remote_code=True
:这个参数允许执行远程代码。在文本生成的上下文中,这可能意味着管道可以执行一些远程的代码或者调用远程的资源来辅助生成文本。请注意,出于安全考虑,通常不建议在生产环境中启用这个选项,因为它可能会带来安全风险。max_new_tokens=max_new_tokens
:这个参数指定了生成文本时模型可以生成的最大新token数量。Token是文本的基本单位,一个token可以是一个字符、一个词或者一个子词,具体取决于分词器的设计。
为什么在加载phi模型前要清空gpu?
在加载大型模型如Phi模型之前清空GPU内存的原因主要包括以下几点:
-
内存管理:GPU内存是有限的资源。当运行深度学习模型,尤其是大型模型时,它们可能会占用大量的GPU内存。如果GPU内存已被其他进程或模型占用,新模型可能无法加载,或者可能导致内存不足的错误。
-
避免内存泄漏:在深度学习应用中,如果之前加载的模型没有被正确释放,它们可能会继续占用GPU内存,即使这些模型已经不再需要。这可能导致内存泄漏,随着时间的推移,会逐渐耗尽GPU资源。
-
提高性能:清空GPU内存可以确保新加载的模型有充足的内存空间,这有助于提高模型运行的性能。足够的内存空间可以减少内存碎片,提高数据传输效率,从而加快模型的推理速度。
-
防止数据溢出:如果GPU内存不足,模型在运行过程中可能会遇到数据溢出的问题,这会导致程序崩溃或产生错误结果。
-
重复运行的一致性:在交互式环境(如Jupyter Notebook)中,用户可能会多次运行相同的代码单元。清空GPU内存可以确保每次运行环境都是一致的,避免由于前一次运行遗留的内存占用导致的问题。
因此,在加载Phi模型或任何大型模型之前,通过删除不再需要的全局对象或使用特定的命令来清空GPU内存,是一种良好的编程实践,有助于确保模型能够顺利加载并高效运行。
Phi 模型测试输出
messages = [
{"role": "user", "content": "Tell me about your math skills."},
]
pipe(messages)
结合之前的 messages
列表,我们可以推断 pipe(messages)
调用的目的是对 messages
列表中的每个消息进行文本生成。在这个例子中,messages
列表只包含一条消息,即用户询问关于数学技能的问题。通过调用 pipe
,这条消息将被传递给模型,模型将基于这条消息生成响应文本。
总结来说,pipe
实现了一个文本生成的管道,它接收用户的消息,使用预训练的模型和分词器来生成响应,并返回生成的文本。这个过程通常用于聊天机器人、自动回复系统或其他需要自动文本生成的应用。
查看数据
查看 Misconception Map 文件
misc_map_df = pd.read_csv("/kaggle/input/eedi-mining-misconceptions-in-mathematics/misconception_mapping.csv")
misc_map_df.head(10)
# 查看训练数据
train_df = pd.read_csv("/kaggle/input/eedi-mining-misconceptions-in-mathematics/train.csv")
train_df.head(10)
# 查看测试数据
* 并确定是否提交答案
# 假设我们正在评分
scoring = True
test_df = pd.read_csv("/kaggle/input/eedi-mining-misconceptions-in-mathematics/test.csv")
# 如果只是测试桩(test stub)— 那么我们就不会进行评分...
if len(test_df) < 10:
scoring = False
test_df.head(10)
在软件开发和测试中,"test stub" 是一个术语,指的是一种特殊的程序或代码片段,它用于模拟外部组件或系统的行为。这样做的目的是为了在开发过程中,当外部组件尚未完成或不易访问时,能够测试和开发其他部分的代码。
"Test stub" 的主要作用包括:
-
模拟外部依赖:在等待外部系统或组件开发完成时,test stub可以模拟这些外部依赖的行为,使得开发和测试可以继续进行。
-
隔离测试:通过使用test stub,可以隔离正在测试的代码部分,避免测试受到外部系统的影响,提高测试的可控性和准确性。
-
提供接口定义:test stub定义了与外部组件交互的接口,即使外部组件尚未实现,开发人员也可以根据这个接口编写和测试自己的代码。
-
返回预定义的响应:test stub可以根据预定义的逻辑返回特定的响应,这样开发人员可以预测和测试不同情况下的代码行为。
-
提高开发效率:通过使用test stub,开发人员可以在外部组件尚未就绪的情况下,继续进行开发和测试工作,从而加快开发进度。
总的来说,test stub是一种在软件开发中常用的技术,用于在外部依赖尚未完成或不易访问时,模拟这些依赖的行为,以支持开发和测试工作的进行。
关于评分
# 如果不进行评分——实际上应该在训练集的一个子集上进行评估,而不是使用测试占位符。
* 如果你们有答案数据,那么可以创建一个MAP@25(Mean Average Precision at 25)的评分估计。
MAP@25是一种评估信息检索系统性能的指标,它计算在返回的前25个结果中的平均精度。这里的"平均"是指对所有查询的平均,而"精度"是指在召回的相关文档中,排在前25位的相关文档所占的比例。
MAP@25的计算步骤通常包括:
- 对于每个查询,计算召回的相关文档的精度。
- 对于每个查询,计算前25个结果的精度。
- 对所有查询的这些精度值求平均,得到MAP@25。
MAP@25评分估计可以帮助你了解模型在实际应用中的表现,尤其是在你想要评估模型在前25个结果中检索相关文档的能力时。这在很多应用场景中都是非常重要的,比如搜索引擎优化、推荐系统等。
MAP@25(Mean Average Precision at 25)和准确率(Precision@k)是评估信息检索系统性能的两种不同指标,它们衡量的是模型在不同方面的表现。
1. **准确率(Precision@k)**:
- 准确率@k是针对单个查询而言的,它衡量的是模型返回的前k个结果中有多少是相关的。
- 公式为:\[ \text{Precision@k} = \frac{\text{Number of relevant documents in top k results}}{k} \]
- 例如,如果模型返回的前10个结果中有7个是相关的,那么Precision@10就是0.7。
2. **MAP@25**:
- MAP@25是所有查询的平均精度的平均值,它考虑了每个查询返回的所有相关文档,而不仅仅是前25个。
- 计算MAP@25时,首先计算每个查询的AP(Average Precision),即在所有相关文档被召回之前,精度的平均值。然后,对所有查询的AP值求平均,得到MAP。
- 公式为:\[ \text{MAP} = \frac{1}{N} \sum_{i=1}^{N} \text{AP}_i \]
- 其中,\( N \) 是查询的总数,\( \text{AP}_i \) 是第i个查询的平均精度。
MAP@25与准确率@k的主要区别在于:
- **考虑范围**:MAP@25考虑了所有相关文档,而准确率@k只考虑前k个结果。
- **平均方式**:MAP@25是对所有查询的平均精度的平均值,而准确率@k是针对单个查询的精度。
- **信息量**:MAP@25提供了关于模型在整个结果集中表现的信息,而准确率@k只提供了关于模型在前k个结果中表现的信息。
总的来说,MAP@25是一个更全面的评估指标,它考虑了模型在整个结果集中的表现,而准确率@k是一个更局部的评估指标,它只考虑了模型在前k个结果中的表现。
* 为了避免数据泄露,我们不会将正在评估的问题包含在大型语言模型(LLM)的示例问题中
数据泄露是指在模型训练或评估过程中,模型以某种方式接触到了它不应该接触到的信息,这可能会导致模型的性能评估不准确。在机器学习中,确保训练集、验证集和测试集之间的数据隔离是非常重要的,以避免模型对数据泄露的依赖。
在评估一个大型语言模型(LLM)时,如果我们使用包含正在评估的问题的示例问题,那么模型可能会“记住”这些问题的答案,从而在评估时表现得比实际更好。为了避免这种情况,我们应该确保在评估模型时,不使用任何模型可能已经接触过的数据。这样可以确保评估结果的公正性和准确性。
部分代码
evaluating_on_train = False
#设置 evaluating_on_train 为 True 并用训练集替换测试集
if scoring == False and eval_on_train_if_not_scoring:
evaluating_on_train = True
print("Doing evaluation / scoring on the train data")
test_df = train_df.head(questions_for_train_eval)
在机器学习或软件开发的上下文中,设置 `evaluating_on_train` 为 `True` 并用训练集替换测试集,通常意味着你打算在训练集上进行模型的评估,而不是在通常用于评估的测试集上。这可能出于几个原因:
1. **调试和开发**:在开发阶段,开发者可能希望在训练集上快速评估模型,以便快速迭代和调整模型参数。
2. **性能分析**:有时候,开发者可能想要了解模型在训练集上的表现,以分析模型是否过拟合或欠拟合。
3. **资源限制**:在资源有限的情况下,可能没有足够的数据来分割出一个单独的测试集,因此可能会在训练集上进行评估。
4. **初步评估**:在初步评估模型概念或快速原型设计时,可能会在训练集上进行评估,以节省时间和计算资源。
然而,需要注意的是,通常不建议在训练集上评估模型的最终性能,因为模型已经在这些数据上进行了训练,所以评估结果可能会过于乐观,不能准确反映模型在未见过的数据上的表现。最佳实践是在独立的测试集上进行评估,以获得对模型泛化能力的更准确估计。
如果你的代码中有这样的设置,它可能看起来像这样:
```python
evaluating_on_train = True
if evaluating_on_train:
# 使用训练集进行评估
dataset = train_dataset
else:
# 使用测试集进行评估
dataset = test_dataset
```
在这段代码中,`evaluating_on_train` 是一个布尔变量,用于控制评估是在训练集还是测试集上进行。如果设置为 `True`,则使用训练集;如果设置为 `False`,则使用测试集。
生成过滤后的数据框架(仅在测试时使用)
为给定的输入问题生成一个优化的DF,以用作大型语言模型(LLM)的提示文本。
- 生成包含请求数量的问题的消息(每个问题将有多个答案)
- 优先处理具有特定结构的问题
- 其次,优先处理与请求的主题相关的问题
- 如果还需要更多的问题,那么将随机选择问题
代码:
def generate_filtered_df(df, question, min_rows=5, max_rows=10, verbose=False, random_seed=42):
# Set the random seed for numpy and pandas
np.random.seed(random_seed)
result_df = pd.DataFrame()
construct_count = 0
subject_count = 0
random_count = 0
question_id = question["QuestionId"]
subject_id = question["SubjectId"]
construct_id = question["ConstructId"]
# Step 1: 生成过滤后的数据框架(DataFrame)时,不包括当前正在处理的问题
df = df[df['QuestionId'] != question_id]
construct_df = df[df['ConstructId'] == construct_id]
result_df = pd.concat([result_df, construct_df]) # pd.concat()函数可以沿着指定的轴将多个dataframe或者series拼接到一起。
construct_count = len(result_df)
if verbose: print(f"Matched ConstructId {construct_id}: {construct_count} rows")
# Step 2: If we don't have enough rows, add rows with the specified SubjectId
if len(result_df) < min_rows:
subject_df = df[(df['SubjectId'] == subject_id) & ~df.index.isin(result_df.index)]
# df 中所有与特定 subject_id 匹配,但尚未被添加到结果数据框架 result_df 中的行。
rows_to_add = min(len(subject_df), min_rows - len(result_df)) ## 我觉得这里可以写max_rows
result_df = pd.concat([result_df, subject_df.head(rows_to_add)]) # Use head() instead of sample()
subject_count = len(result_df) - construct_count
if verbose: print(f"Added rows from SubjectId {subject_id}: {subject_count} rows")
# Step 3: If we still don't have enough rows, add random rows
if len(result_df) < min_rows:
remaining_df = df[~df.index.isin(result_df.index)]
rows_to_add = min(len(remaining_df), min_rows - len(result_df))
result_df = pd.concat([result_df, remaining_df.head(rows_to_add)]) # Use head() instead of sample()
random_count = len(result_df) - (construct_count + subject_count)
if verbose: print(f"Added random rows to meet minimum: {random_count} rows")
# Step 4: If we have more than max_rows, use the first max_rows
if len(result_df) > max_rows:
result_df = result_df.head(max_rows)
if verbose: print(f"Reduced to maximum: {max_rows} rows")
if verbose:
print(f"\nFinal DataFrame composition:")
print(f"ConstructId matches: {construct_count}")
print(f"SubjectId matches: {subject_count}")
print(f"Random additions: {random_count}")
print(f"Total rows: {len(result_df)}")
return result_df.reset_index(drop=True)
# 用法
test_question = train_df.iloc[38]
filtered_df = generate_filtered_df(train_df, test_question, min_rows=min_example_questions, max_rows=max_example_questions, verbose=True, random_seed=42)
filtered_df.head(10)
解释:
这段Python代码定义了一个名为 `generate_filtered_df` 的函数,其目的是从一个给定的数据框架(DataFrame)中筛选出与特定问题相关的行,以创建一个新的数据框架。这个函数考虑了问题的结构(ConstructId)、主题(SubjectId)和随机性,以确保生成的数据框架满足特定的行数要求。下面是对代码的逐行分析:
1. 函数定义了多个参数:
- `df`:原始数据框架。
- `question`:包含特定问题的字典,其中包含问题ID(QuestionId)、主题ID(SubjectId)和结构ID(ConstructId)。
- `min_rows`:结果数据框架的最小行数,默认为5。
- `max_rows`:结果数据框架的最大行数,默认为10。
- `verbose`:是否打印详细信息,默认为False。
- `random_seed`:随机数生成的种子,默认为42。
2. 设置随机种子,以确保结果的可重复性。
3. 初始化一个空的结果数据框架 `result_df` 和三个计数器 `construct_count`、`subject_count`、`random_count`。
4. 从 `question` 字典中提取问题ID、主题ID和结构ID。
5. 从原始数据框架中排除与当前问题相同的问题。
6. 筛选出与特定结构ID匹配的行,并将其添加到结果数据框架中。
7. 如果结果数据框架的行数少于最小行数要求,继续筛选与特定主题ID匹配的行,并将其添加到结果数据框架中。
8. 如果结果数据框架的行数仍然少于最小行数要求,从剩余的行中随机选择行,直到达到最小行数要求。
9. 如果结果数据框架的行数超过最大行数限制,只保留前 `max_rows` 行。
10. 如果 `verbose` 参数为True,打印详细信息,包括结构ID匹配的行数、主题ID匹配的行数、随机添加的行数、总行数和最终的数据框架组成。
11. 返回重置索引的结果数据框架。
在代码的最后部分,展示了如何使用这个函数。首先,从训练数据框架 `train_df` 中选择一个问题作为测试问题。然后,调用 `generate_filtered_df` 函数,传入训练数据框架、测试问题、最小和最大行数、是否打印详细信息和随机种子。最后,打印筛选后的数据框架的前10行。
这段代码的目的是从一个较大的数据框架中筛选出与特定问题相关的行,以创建一个新的数据框架,用于训练或测试机器学习模型。通过考虑问题的结构、主题和随机性,确保生成的数据框架满足特定的行数要求。
从给定的DF数据中生成训练消息片段的数组
消息格式
## 对于 DF 中的每个问题/答案对(具有非 nan 误解):
* Question:结合 ConstructName 和 QuestionText,这可能是问题的上下文描述
* Correct Answer
* Incorrect Answer (test question)
* Misconception:对于给定错误答案的常见误解解释
代码:
def get_train_messages_for_df(filtered_train_df, skip_nan_misconceptions=True, answers=['A', 'B', 'C', 'D'], verbose = False):
messages = []
current_size = 0
for _, row in filtered_train_df.iterrows(): # df.iterrows()适用于小型数据,速度慢。返回索引和数据
for answer_choice in answers:
if answer_choice == row['CorrectAnswer']:
continue
misconception_id = row[f'Misconception{answer_choice}Id']
if pd.isna(misconception_id) and skip_nan_misconceptions: # pd.isna() 函数用于检测缺失值(即 NaN 或 None)。
continue
if not pd.isna(misconception_id): # 当你使用 pd.isna(misconception_id) 时,它会返回一个与 misconception_id 同样大小的布尔型Series,其中的每个元素都表示 misconception_id 中相应位置的值是否是缺失值。
new_message = [
f"{row['ConstructName']}: {row['QuestionText']}", # 结构:问题
row[f'Answer{row["CorrectAnswer"]}Text'], # 正确答案描述 ? 为什么要加入这个
row[f'Answer{answer_choice}Text'], # 错误答案描述
misc_map_df.loc[int(misconception_id), 'MisconceptionName'] # 错误理解描述
]
# 计算消息大小
new_message_size = sum(sys.getsizeof(item) for item in new_message)
messages.append(new_message)
current_size += new_message_size # 作用?
# Print size of returned data
if verbose: print(f"Size of returned data: {current_size} bytes")
return messages
example_sequences = get_train_messages_for_df(filtered_df, verbose = True)
example_sequences
定义提示组件
作用:
* 告诉大型语言模型(LLM)我们正在寻找什么
* 添加到消息前的前缀
代码:
#原始文本前缀
question_prefix = "Question:"
#LLM "响应"
llm_correct_response_for_rewrite = "Provide me with the correct answer for a baseline." # baseline 基线
llm_incorrect_response_for_rewrite = "Now - provide the incorrect answer and I will anaylze the difference to infer the misconception."
#修改的文本前缀
incorrect_answer_prefix = "Incorrect Answer:"
correct_answer_prefix = "Correct Answer:"
#将此作为响应的开头有助于保持内容的相关性
response_start = "Misconception for incorrect answer: "
清理大型语言模型(LLM)的输出:
def clean_response(my_string, response_start):
# 先修剪前导空格
my_string = my_string.lstrip()
# 如果存在,移除response_start。
if my_string.startswith(response_start):
my_string = my_string[len(response_start):]
# 找到字符串中第一个句号(.)和换行符(\n)的索引位置
period_index = my_string.find('.')
linefeed_index = my_string.find('\n')
# 确定截断位置
truncate_index = len(my_string) # Default to end of string
if period_index != -1:
truncate_index = period_index
if linefeed_index != -1 and linefeed_index < truncate_index:
truncate_index = linefeed_index
# Truncate(截断) the string
my_string = my_string[:truncate_index]
return my_string.strip()
检测逻辑
作用:
* 输入问题、答案和示例消息片段
* 为LLM生成实际消息
* 输出预测的误解!
代码:
def predict_misconception(question, question_letter_to_test, example_sequences, max_word_count=max_words_for_examples, verbose=False):
correct_question_letter = question["CorrectAnswer"]
question_text = f"{question['ConstructName']}: \n {question['QuestionText']}\n"
correct_answer_text = question[f"Answer{correct_question_letter}Text"]
incorrect_answer_text = question[f"Answer{question_letter_to_test}Text"]
if correct_question_letter == question_letter_to_test:
print("WARNING: Tested letter is for a correct answer!")
def calculate_word_count(text):
return len(text.split())
# Construct the actual prompt messages
actual_prompt_messages = [
{"role": "user", "content": f"{question_prefix} {question_text}"},
{"role": "assistant", "content": llm_correct_response_for_rewrite},
{"role": "user", "content": f"{correct_answer_prefix} {correct_answer_text}"},
{"role": "assistant", "content": llm_incorrect_response_for_rewrite},
{"role": "user", "content": f"{incorrect_answer_prefix} {incorrect_answer_text}"}
]
# Calculate the word count of actual prompt messages
actual_prompt_word_count = sum(calculate_word_count(msg["content"]) for msg in actual_prompt_messages)
# Construct example messages, stopping if we reach the word limit
example_messages = []
current_word_count = actual_prompt_word_count
for examp_question, examp_correct_answer, examp_incorrect_answer, examp_misconception in example_sequences:
example_set = [
{"role": "user", "content": f"{question_prefix} {examp_question}"},
{"role": "assistant", "content": llm_correct_response_for_rewrite},
{"role": "user", "content": f"{correct_answer_prefix} {examp_correct_answer}"},
{"role": "assistant", "content": llm_incorrect_response_for_rewrite},
{"role": "user", "content": f"{incorrect_answer_prefix} {examp_incorrect_answer}"},
{"role": "assistant", "content": f"{response_start} {examp_misconception}"}
]
example_set_word_count = sum(calculate_word_count(msg["content"]) for msg in example_set)
if current_word_count + example_set_word_count > max_word_count:
if verbose: print("Word count limit reached.")
break # Stop adding new example sets if we would exceed the limit
example_messages.extend(example_set)
current_word_count += example_set_word_count
# Combine example messages and actual prompt messages
messages = example_messages + actual_prompt_messages
if verbose:
print("Example Messages:")
for message in example_messages:
display(message)
print("\nActual Prompt Messages:")
for message in actual_prompt_messages:
display(message)
print(f"\nTotal word count: {current_word_count}")
decoded = pipe(messages)
return decoded