phi-mini3.5 学习笔记

前言

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)的日志消息会被记录。低于这个级别的日志消息(如WARNINGINFODEBUG)将被忽略。
  • 这个设置通常用于减少控制台或日志文件中的输出量,只关注错误级别的消息。

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内存的原因主要包括以下几点:

  1. 内存管理:GPU内存是有限的资源。当运行深度学习模型,尤其是大型模型时,它们可能会占用大量的GPU内存。如果GPU内存已被其他进程或模型占用,新模型可能无法加载,或者可能导致内存不足的错误。

  2. 避免内存泄漏:在深度学习应用中,如果之前加载的模型没有被正确释放,它们可能会继续占用GPU内存,即使这些模型已经不再需要。这可能导致内存泄漏,随着时间的推移,会逐渐耗尽GPU资源。

  3. 提高性能:清空GPU内存可以确保新加载的模型有充足的内存空间,这有助于提高模型运行的性能。足够的内存空间可以减少内存碎片,提高数据传输效率,从而加快模型的推理速度。

  4. 防止数据溢出:如果GPU内存不足,模型在运行过程中可能会遇到数据溢出的问题,这会导致程序崩溃或产生错误结果。

  5. 重复运行的一致性:在交互式环境(如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" 的主要作用包括:

  1. 模拟外部依赖:在等待外部系统或组件开发完成时,test stub可以模拟这些外部依赖的行为,使得开发和测试可以继续进行。

  2. 隔离测试:通过使用test stub,可以隔离正在测试的代码部分,避免测试受到外部系统的影响,提高测试的可控性和准确性。

  3. 提供接口定义:test stub定义了与外部组件交互的接口,即使外部组件尚未实现,开发人员也可以根据这个接口编写和测试自己的代码。

  4. 返回预定义的响应:test stub可以根据预定义的逻辑返回特定的响应,这样开发人员可以预测和测试不同情况下的代码行为。

  5. 提高开发效率:通过使用test stub,开发人员可以在外部组件尚未就绪的情况下,继续进行开发和测试工作,从而加快开发进度。

总的来说,test stub是一种在软件开发中常用的技术,用于在外部依赖尚未完成或不易访问时,模拟这些依赖的行为,以支持开发和测试工作的进行。

关于评分

# 如果不进行评分——实际上应该在训练集的一个子集上进行评估,而不是使用测试占位符。


* 如果你们有答案数据,那么可以创建一个MAP@25(Mean Average Precision at 25)的评分估计。

MAP@25是一种评估信息检索系统性能的指标,它计算在返回的前25个结果中的平均精度。这里的"平均"是指对所有查询的平均,而"精度"是指在召回的相关文档中,排在前25位的相关文档所占的比例。

MAP@25的计算步骤通常包括:

  1. 对于每个查询,计算召回的相关文档的精度。
  2. 对于每个查询,计算前25个结果的精度。
  3. 对所有查询的这些精度值求平均,得到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

未完待续~~~

<think>好的,用户之前已经询问过如何安装Ollama和大模型,现在他们遇到了新的问题:在使用PHI-2模型时,回答是英文的,想要设置成中文。我需要先了解PHI-2模型本身是否支持中文。根据之前的知识,PHI-2是微软开发的模型,主要针对英语训练,可能对中文的支持有限。但用户还是想尝试让它生成中文回答。 首先,我应该考虑用户的需求是什么。他们可能希望用中文与模型交互,或者需要模型处理中文任务。但PHI-2可能没有专门的中文训练数据,所以直接切换语言设置可能不可行。这时候需要指导用户通过提示词工程来引导模型用中文回答。 接下来,要思考可能的解决方案。比如在提问时明确指定用中文回答,或者在系统提示中设置语言参数。虽然PHI-2没有官方的中文支持,但通过指令调整可能有效。此外,用户可能需要调整参数如temperature来改善输出质量,或者尝试其他更支持中文的模型,比如Qwen或ChatGLM。 还需要考虑用户的技术水平。他们可能已经安装好了Ollama和PHI-2,但对如何调整提示词不熟悉。所以需要给出具体的示例,比如在提问前添加“请用中文回答:”或者更详细的系统指令。同时,提醒用户如果频繁失败,可能需要换用其他模型,并提供安装这些模型的命令。 另外,用户可能不知道如何查看已安装的模型,或者如何正确传递系统消息。因此,在回答中需要包括这些步骤,比如使用ollama list查看模型,使用API时的参数设置,以及直接修改Modelfile的方法。 最后,确保回答结构清晰,分点说明,并提供备选方案。同时,保持语言简洁易懂,避免使用过多技术术语,让用户能够轻松跟随步骤操作。</think>如果使用 **Phi-2** 等英文倾向的模型时,默认回答是英文,可以通过以下方法尝试引导模型输出中文: --- ### **方法 1:直接指令法** 在提问时明确要求模型用中文回答: ```bash # 示例(通过命令行交互): 用户输入:请用中文回答:太阳系最大的行星是什么? 模型输出:太阳系中最大的行星是木星。 ``` --- ### **方法 2:系统消息设定** 通过 **System Prompt** 设置语言环境(需使用 API 或代码调用): ```python # Python 示例(需安装 ollama 库): from ollama import Client client = Client(host=&#39;http://localhost:11434&#39;) response = client.generate( model="phi2", system="你是一个中文助手,所有回答必须使用简体中文。", # 强制语言指令 prompt="太阳系最大的行星是什么?" ) print(response["response"]) ``` --- ### **方法 3:微调模型参数** 调整生成参数(如 `temperature`)提升中文输出的稳定性: ```bash # 通过命令行附加参数: ollama run phi2 --temperature 0.7 --seed 123 # 交互时输入: [System: 请用中文回答] 解释量子力学的基本概念 ``` --- ### **方法 4:使用中文优化模型** 如果 Phi-2 中文效果不佳,可改用**原生支持中文的模型**: ```bash # 安装中文优化模型(需先确认模型支持中文): ollama run qwen:7b # 阿里千问-7B(中文友好) ollama run chatglm3 # 智谱AI的中英双语模型 ``` --- ### **注意事项** 1. **模型局限性**: Phi-2 主要训练数据为英文,中文能力有限,复杂问题可能生成错误或中英混杂内容。 2. **强制指令技巧**: 若模型仍回复英文,尝试更明确的指令(如:“用简体中文回答,不要使用任何英文单词”)。 3. **模型选择建议**: 如果依赖中文场景,建议优先选择 `qwen`、`chatglm3` 或 `baichuan2` 等专为中文优化的模型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值