【第二十周】自然语言处理的学习笔记05

文章目录

  • 摘要
  • Abstract
  • [一、OpenPrompt Demo解读](#一、OpenPrompt Demo解读)
    • [1. 准备:下载一些基础包](#1. 准备:下载一些基础包)
    • [2. 定义一个提示词模板template](#2. 定义一个提示词模板template)
    • [3. 对已包裹(wrapped)的样本进行分词处理](#3. 对已包裹(wrapped)的样本进行分词处理)
    • [4. 对所有数据进行输入处理](#4. 对所有数据进行输入处理)
    • [5. 构造数据加载器](#5. 构造数据加载器)
    • [6. 创建verbalizer表述器](#6. 创建verbalizer表述器)
    • [7. 创建分类任务的prompt learning模型](#7. 创建分类任务的prompt learning模型)
    • [8. 训练模型](#8. 训练模型)
      • [8.1 核心组件](#8.1 核心组件)
      • [8.2 参数更新流程](#8.2 参数更新流程)
    • [9. 评估模型](#9. 评估模型)
  • 总结

摘要

本周主要完成对OpenPrompt工具包的使用展开,整个流程为基础准备;定义提示词模板并包裹样本;对包裹样本分词;处理所有数据;构造数据加载器;创建表述器与分类任务的 prompt learning 模型,还说明模型训练的损失函数、优化器等,以及模型评估流程。并对代码参数进行说明及常见疑问解答以辅助理解工具包使用。


Abstract

This week I mainly focused on using the OpenPrompt toolkit. The entire process includes: basic preparation; defining the prompt template and wrapping samples; tokenizing the wrapped samples; processing all data; constructing a data loader; creating a verbalizer and a prompt learning model for classification tasks. Additionally, it also illustrates the loss function, optimizer, etc., in model training, as well as the model evaluation process. Furthermore, I explained the code parameters and answered common questions to assist in understanding the usage of the toolkit.


一、OpenPrompt Demo解读

1. 准备:下载一些基础包

python 复制代码
!pip install transformers --quiet
!pip install datasets==2.0 --quiet
!pip install openprompt --quiet
!pip install torch --quiet

(1)加载huggingface中的数据

数据格式为:

(2)加载模型和分词器

(3)搭建和输入

转换输入格式,并且按照训练集、验证集和测试集拆分存储,方便后面使用。

查看转换后的第一条训练集样本,输出的格式如下:

注:输入数据没有的内容填充null或者直接为空集。

2. 定义一个提示词模板template

将之前转换的 InputExample 样本按照模板格式进行包裹(wrap),生成可供模型输入的提示词结构。

案例使用template:基于文本令牌初始化的软提示

①模板的逻辑:将前提和假设拼接成一个完整的问题(如 "[前提] Deduction: [假设]. Is it correct? [答案]"),引导模型判断假设是否能从前提中推出。

②wrap_one_example()将 dataset['train'][0] 这个InputExample 样本按照 template_text 进行填充,生成包含模板 + 数据的结构化对象。

③输出 wrapped_example 可直观查看模板如何与具体数据结合,便于调试模板是否符合预期。

最后输出结果如下:

对输出结果说明:

(1)文本片段列表

loss_ids 字段:标记该文本片段是否参与损失计算(即是否是模型需要预测的部分):

loss_ids=0:表示该片段是 "输入",不参与损失计算。

loss_ids=1:表示该片段是 "预测目标"(即位置),参与损失计算。

shortenable_ids 字段:标记该文本片段是否可被截断(用于控制输入长度,避免超出模型上下文窗口):

shortenable_ids=1:表示该片段(如text_a、text_b的长文本)可被截断。

shortenable_ids=0:表示该片段(如固定模板文本、)不可被截断,必须保留。

text 字段:拼接后的完整输入文本,对应模板的每个部分:

第一段 text 是 text_a 字段的内容(原始文本)。

第二段 text 是模板中的固定文本 "Deduction:"。

第三段 text 是 text_b 字段的内容(推导结论)。

第四段 text 是模板中的固定文本 ". Is it correct?"。

第五段 text 是 (模型需要预测的位置)。

第六段 text 是模板中的固定文本 "."。

(2) 元信息(任务与标签)

{'guid': 0, 'label': 0}

guid:数据的唯一标识(这里是第 0 条训练数据)。

label:该数据的真实标签(0 表示 "推导不正确",具体含义需结合任务定义,如0对应 "否",1对应 "是")。

3. 对已包裹(wrapped)的样本进行分词处理

将自然语言提示词转换为模型可识别的 token ID格式,同时适配 T5 等编码器 - 解码器模型的输入要求。

初始化分词器包装器 (WrapperClass / T5TokenizerWrapper)

参数说明:

max_seq_length:限制编码器输入的长度(避免模型输入过长)。

decoder_max_length:限制解码器输入的长度(T5 生成任务中,解码器输入通常是标签的 token ID)。

truncate_method="head":当文本超长时,从开头(头部)截断(其他方式如 tail 从尾部截断)。

注:

在 Prompt Learning 中,预训练模型的原生分词器(Tokenizer)难以直接适配复杂提示结构,因此需要专门的编码机制(如 OpenPrompt 中的PromptTokenizer)来处理,而编码器的选择需与基础预训练模型(如 BERT、GPT 等)对齐。

疑问:

问题1:代码中哪里使用了预训练tokenizer?

使用了预训练 Tokenizer,并非直接单独使用,而是而是将其作为底层依赖,通过PromptTokenizer对其进行了封装和扩展。

PromptTokenizer(提示分词器)的核心功能依赖预训练模型的原生 Tokenizer(如 BERT 的BertTokenizer、GPT 的GPT2Tokenizer)。

问题2:本段代码中是使用的哪种tokenizer?

T5TokenizerWrapper 是 OpenPrompt 框架提供的封装类,它并非直接使用 OpenPrompt 自研的 Tokenizer,而是对 预训练的tokenizer( T5的原生 Tokenizer)进行了功能扩展

对单个样本进行分词并查看结果

先对wrapped_example进行分词,接着将input_ids、decoder_input_ids转换为对应的token

input_ids、decoder_input_ids的说明:

input_ids 和 decoder_input_ids 是适配 T5 模型 "编码器 - 解码器(Encoder-Decoder)" 结构的关键输入,分别对应模型的编码器输入和解码器输入。

input_ids:包含经过 Prompt 模板处理后的完整文本序列(如任务指令、输入数据、提示词等),用于让编码器理解 "任务目标" 和 "输入信息"。

decoder_input_ids:用于引导解码器生成目标输出(如分类标签、推理结果等)。

teacher_forcing 属性说明:

teacher_forcing=True(训练阶段):在训练阶段强制模型使用 "真实标签(Ground Truth)" 作为下一时间步的输入,而非模型自身生成的预测值。

补充说明:

具体举例说明:训练 "生成句子'我喜欢机器学习'" 时:

Step 1 输入:< START >,模型预测 "我";

Step 2 输入:真实标签 "我"(而非模型预测的 "我"),模型预测 "喜欢";

Step 3 输入:真实标签 "喜欢",模型预测 "机器";以此类推

teacher_forcing=False(推理阶段):解码器输入可能是默认的起始符(如< pad > 或 < s >,用于推理时让模型自主生成)。

综合说明:

(1)训练阶段

若 teacher_forcing=True,decoder_input_ids 由 任务的真实标签经过分词得到,在 "自然语言推理" 任务中,若真实标签是 "是"(表示推导正确),则 decoder_input_ids 是 "是" 对应的 Token ID(可能还包含起始 Token < s >)。

(2)推理阶段

若 teacher_forcing=False,decoder_input_ids来自 "模型自身生成",仅包含起始 Token,解码器完全依赖自身生成后续内容,模拟真实推理场景。

最后整段代码输出结果:

对于decoder_input_ids输出结果的解读:

\< pad \>, \,

其中 <extra_id_0> 是模型需要生成的核心标签(如 "是" 或 "否"),前后的< pad > 是为了满足 decoder_max_length=3 的长度要求(代码中设置了 decoder_max_length=3)。

补充:

< pad >:填充 Token,用于补齐序列长度(当实际序列短于 decoder_max_length 时),不携带语义信息。

4. 对所有数据进行输入处理

5. 构造数据加载器

PromptDataLoader 是为Prompt Learning设计的训练数据加载器,它在传统 PyTorch DataLoader 的基础上,增加了对 Prompt 模板处理、分词适配、特殊格式转换 的支持,最终输出模型可直接训练的批量数据(包含 input_ids、attention_mask、decoder_input_ids 等关键字段)。

Demo中的核心作用是将原始训练数据集(dataset["train"])按照 Prompt 模板进行格式化处理,转化为可直接输入预训练模型(如 T5)的批量数据,为后续模型训练做准备

训练配置:

shuffle=True:训练时打乱数据集顺序,避免模型学习到数据的排列规律(减少过拟合)。

predict_eos_token=False:是否预测 "结束 Token"(</ s >),此处设为 False 表示无需生成结束 Token(因任务是短序列输出,如分类标签)。

输出结果:

6. 创建verbalizer表述器

(1)ManualVerbalizer:人工定义标签词与类别的映射。需要给出tokenizer(预训练模型的分词器),num_classes(任务的类别数量),label_words每个列别对应的标签表

(2)myverbalizer.label_words_ids:标签词的 Token ID 映射。将人类可理解的标签词(如 "yes")转化为模型可识别的 Token ID。

(3)myverbalizer.process_logits(logits):将 logits 映射到类别概率

7. 创建分类任务的prompt learning模型

(1)用于分类任务的提示学习模型(PromptForClassification)

核心组件说明:

PromptForClassification 是 OpenPrompt 专为分类任务设计的模型封装类,它将三个核心组件(PLM,template,verbalizer)串联成端到端的分类模型:

预训练语言模型(plm):如 BERT、T5、LLaMA 等,是模型的 "主体",负责理解输入文本的语义。

Prompt 模板(template):即之前定义的 mytemplate,负责将原始数据转化为带提示的输入格式(如 "文本 + 指令 + 掩码")。

标签映射器(verbalizer):即之前定义的 myverbalizer,负责将模型输出的词级 logits 映射为分类标签的概率(如将 "yes""no" 的概率转化为 "类别 0""类别 1" 的概率)。

参数说明:

freeze_plm=False/True:控制是否冻结预训练模型的参数:

False 表示 "微调预训练模型"(更新 PLM 的参数),适合数据量充足的场景,能让模型更好适配任务。

True表示仅训练模板中的软令牌(Soft Token)和 Verbalizer 的参数,适合小数据场景(减少过拟合风险)。

核心原则:

小数据、低资源、简单任务→冻结;

大数据、高资源、复杂任务→微调。

(2)设备配置

use_cuda = torch.cuda.is_available():检查当前环境是否有可用的 GPU(支持 CUDA)。

if use_cuda: prompt_model = prompt_model.cuda():若有 GPU,则将模型迁移到 GPU 上运行,利用 GPU 的并行计算能力加速训练和推理。

8. 训练模型

8.1 核心组件

1,损失函数 :交叉熵损失函数

2,优化器参数分组 (optimizer_grouped_parameters)

目的:对模型不同类型的参数应用不同的优化策略(主要是权重衰减,weight_decay),提升训练稳定性和模型性能。

分组逻辑:

第一组:weight_decay=0.01(应用权重衰减)包含所有非偏差、非 LayerNorm 权重的参数(如 Transformer 层的注意力权重、线性层权重)。权重衰减是一种正则化手段,通过惩罚大权重值减少过拟合。

第二组:weight_decay=0.0(不应用权重衰减)包含偏差参数(bias) 和LayerNorm 层的权重。这些参数对模型性能影响较敏感,通常不施加权重衰减,避免破坏模型的稳定性。

实现方式:通过 named_parameters() 遍历模型所有参数,根据参数名称(n)中是否包含 bias 或 LayerNorm.weight 进行分组。

代码语法分析:

(1)named_parameters():返回模型中所有可训练参数的迭代器,每个元素是 (参数名称n, 参数值p)

(2)列表推导式语法规则:[表达式 for 变量 in 迭代对象 if 条件],其中 "表达式" 定义了列表中每个元素的形式。

代码中具体使用:[p for ...]:列表推导式的最终目的是生成一个参数值的列表。

推导式的作用:筛选出满足条件(名称不含 bias 或 LayerNorm.weight)的参数,并将这些参数的值(p) 收集成一个列表,用于优化器的参数配置。

(3)参数说明

第一组参数(应用权重衰减)

筛选条件:if not any(nd in n for nd in no_decay)

①no_decay = ['bias', 'LayerNorm.weight']:定义 "不应用权重衰减" 的参数名称关键词。

②any(nd in n for nd in no_decay):判断参数名称 n 中是否包含 no_decay 中的任何关键词(如参数名含 bias 或 LayerNorm.weight)。

③not any(...):筛选出名称中不含 bias 和 LayerNorm.weight 的参数。

第二组参数(不应用权重衰减)

筛选条件:if any(nd in n for nd in no_decay)

筛选出名称中包含 bias 或 LayerNorm.weight 的参数。

3,优化器

选择AdamW,它在 Adam 基础上改进了权重衰减的实现方式(更准确地对参数施加衰减),适合大规模参数的微调。

学习率的设置:

若冻结预训练模型(freeze_plm=True),仅训练软令牌等少量参数,学习率可设为 1e-4 ~ 5e-3(较大,加速收敛)。

若微调预训练模型(freeze_plm=False),学习率需减小(如 1e-5 ~ 5e-5),避免破坏预训练知识。

8.2 参数更新流程

  1. 数据准备(inputs = inputs.cuda())
    将批量数据(inputs,包含 input_ids、labels 等)迁移到 GPU(若可用),与模型设备保持一致,避免计算错误。
  2. 模型前向传播(logits = prompt_model(inputs))
    模型接收批量输入,经过 Prompt 模板处理、预训练模型编码、Verbalizer 映射后,输出类别 logits(形状为 (batch_size, num_classes),如 (4, 3) 表示 4 个样本、3 个类别)。
  3. 计算损失(loss = loss_func(logits, labels))
    用 CrossEntropyLoss 计算 logits 与真实标签 labels 的损失值(标量),损失越大,模型预测与真实结果差异越大。
  4. 反向传播与参数更新
    loss.backward():自动计算损失对所有可训练参数的梯度(链式法则)。
    optimizer.step():根据梯度和优化器配置(如学习率、权重衰减)更新参数。
    optimizer.zero_grad():清空当前梯度(避免与下一批数据的梯度累积,导致更新错误)。
  5. 训练日志(打印平均损失)
    每 100 个 step 打印一次当前 epoch 的平均损失,用于监控训练过程:
    若损失持续下降,说明模型在有效学习;
    若损失趋于稳定或上升,可能出现过拟合或收敛问题,需调整学习率、批量大小等。
    补充:
    疑问一:为什么要使用item方法?
    loss.item() 是 torch.Tensor(张量)对象的一个方法,用于将单元素张量转换为 Python 标量(如 float 或 int)。
    如果直接写 tot_loss += loss,会导致 tot_loss 变成一个张量(而非 Python 标量),后续计算(如求平均 tot_loss/(step+1))会产生不必要的计算图跟踪(影响性能),甚至可能导致内存泄漏。
    而 loss.item() 会剥离张量的计算图信息,做纯数值计算,也仅返回数值,既节省内存,又保证后续计算符合 Python 原生逻辑。
    疑问二:什么是张量?什么是标量?
    张量(Tensor) 和标量(Scalar) 是两种基础的数据形式,核心区别在于维度和存储的信息,具体如下:
    一,标量(Scalar)
    存储内容:0 维的单一数值
    用途:损失值、准确率等单一指标
    二,张量(Tensor)
    存储内容:多维数组(>=1)
    用途:输入数据、模型参数、中间特征等
    核心特点:
    1,张量不仅包含数值,还携带维度信息(shape)和设备信息(如 CPU/GPU)。
    2,张量支持并行计算和自动求导(PyTorch 的核心功能).

9. 评估模型

流程:

(1)加载验证集数据,进行格式化处理

(2)验证流程:预测与指标计算

逐行解读代码:

迭代验证集,收集预测与标签:

python 复制代码
for step, inputs in enumerate(validation_dataloader):
if use_cuda:
inputs = inputs.cuda() # 迁移数据到 GPU(若可用)

# 模型前向传播,输出预测值:类别 logits(形状:[batch_size, num_classes])
logits = prompt_model(inputs)  
# 获取当前批次的真实标签(形状:[batch_size]) 
labels = inputs['label'] 
# 收集真实标签:将 GPU 张量转为 CPU 列表,存入 alllabels 
alllabels.extend(labels.cpu().tolist()) 

# 收集预测结果: 
# 1. torch.argmax(logits, dim=-1):对 logits 按最后一维取最大值索引(即预测的类别) 
# 2. 转为 CPU 列表,存入 allpreds 
allpreds.extend(torch.argmax(logits, dim=-1).cpu().tolist())

疑问1:为什么要把GPU张量转为CPU列表?

模型在 GPU 上完成计算后,得到的预测结果(logits)和标签(labels)仍是 GPU 上的张量(torch.Tensor),而后续的结果存储、指标计算(如准确率)、可视化等操作通常依赖 Python 原生数据结构(如列表),且这些操作更适合在 CPU 上进行。核心目的:适配后处理需求。

疑问2:为什么一开始又要迁移到GPU?

为了让数据与模型在同一设备,利用 GPU 并行计算加速模型的前向 / 反向传播(核心目的:提升计算效率)。

(3)计算准确率(Accuracy)

zip(allpreds, alllabels)

zip() 是 Python内置函数 ,用于将两个列表(allpreds 和 alllabels)按位置一一配对,生成一个包含元组的迭代器。

具体举例说明:

  • allpreds = [0, 1, 0, 2](模型对 4 个样本的预测类别)
  • alllabels = [0, 1, 1, 2](4 个样本的真实类别)

zip(allpreds, alllabels) 生成的迭代器包含以下元组:

结果:(0, 0), (1, 1), (0, 1), (2, 2)


总结

本周主要围绕OpenPrompt工具包展开,在对工具包的使用过程中产生的疑问也通过大模型工具进行解决。本周停留在工具包的时间太长,因此下周将尽量加快速度,开启其他方面知识的学习。

相关推荐
亚里随笔8 小时前
AsyPPO_ 轻量级mini-critics如何提升大语言模型推理能力
人工智能·语言模型·自然语言处理·llm·agentic
笨鸟笃行8 小时前
百日挑战——单词篇(第十一天)
学习
yong15858553439 小时前
1. Linux C++ muduo 库学习——库的编译安装
linux·c++·学习
952369 小时前
数据结构-顺序表
java·数据结构·学习
码界奇点9 小时前
Java Web学习 第1篇前端基石HTML 入门与核心概念解析
java·前端·学习·xhtml
Brianna Home10 小时前
大模型如何变身金融风控专家
人工智能·深度学习·机器学习·自然语言处理·stable diffusion·1024程序员节
HPC_C10 小时前
Efficient Memory Management for Large Language Model with PagedAttention
人工智能·语言模型·自然语言处理
星辰大海141210 小时前
摄影入门学习笔记
笔记·数码相机·学习
沐矢羽10 小时前
upload文件上传漏洞浅析
学习·web安全·网络安全·文件上传·top10