网站上面怎么做链接,施工企业信用管理制度和机制,国企怎么做网站,彩页设计图片模板ML2023Spring - HW7 相关信息#xff1a; 课程主页 课程视频 Kaggle link 回来了 : ) Sample code HW07 视频 HW07 PDF 个人完整代码分享: GitHub | Gitee | GitCode P.S. HW7 的代码都很易懂#xff0c;可以和 2024 年的新课#xff1a;生成式AI导论做一个很好的衔接#… ML2023Spring - HW7 相关信息 课程主页 课程视频 Kaggle link 回来了 : ) Sample code HW07 视频 HW07 PDF 个人完整代码分享: GitHub | Gitee | GitCode P.S. HW7 的代码都很易懂可以和 2024 年的新课生成式AI导论做一个很好的衔接因为导论对于 Transformer 库的使用大多数是 HW7 所提到的一些函数。对 AIGC 感兴趣的同学可以去学习完成 HW7 之后应该能够非常快的上手。 任务目标抽取式问答
任务描述: 使用 BERT 模型进行抽取式问答。 目标: fine-tune BERT 模型使其能够从文章中抽取出具体答案完成问答任务。 具体来说可以当成是预测开始Start和结尾End的位置然后抽出这一段作为答案现在去看PDF中的图你应该更容易理解。
性能指标EM
准确率 (Exact Match): 该指标用于衡量模型的预测答案与真实答案完全一致的比例。
数据解析 两个繁体中文阅读理解资料集DRCD 和 ODSQA。 训练DRCD DRCD-backtrans包含 15,329 个段落和 26,918 个问题。一个文章段落可能对应多个问题。开发/用于验证DRCD DRCD-backtrans包含 1,255 个段落和 2,863 个问题。测试DRCD ODSQA包含 1,606 个段落和 3,504 个问题。其中段落没有给出答案需模型进行预测。
训练开发和测试数据的格式都是相同的
id问题序号paragraph_id文章段落序号question_text问题answer_text答案answer_start答案在文章中的起始点answer_end答案在文章中的终止点 数据下载kaggle To use the Kaggle API, sign up for a Kaggle account at https://www.kaggle.com. Then go to the ‘Account’ tab of your user profile (https://www.kaggle.com/username/account) and select ‘Create API Token’. This will trigger the download of kaggle.json, a file containing your API credentials. Place this file in the location ~/.kaggle/kaggle.json (on Windows in the location C:\Users\Windows-username\.kaggle\kaggle.json - you can check the exact location, sans drive, with echo %HOMEPATH%). You can define a shell environment variable KAGGLE_CONFIG_DIR to change this location to $KAGGLE_CONFIG_DIR/kaggle.json (on Windows it will be %KAGGLE_CONFIG_DIR%\kaggle.json). -- Official Kaggle API 替换username为你自己的用户名https://www.kaggle.com/username/account然后点击 Create New API Token将下载下来的文件放去应该放的位置
Mac 和 Linux 放在 ~/.kaggleWindows 放在 C:\Users\Windows-username\.kaggle
pip install kaggle
# 你需要先在 Kaggle - Account - Create New API Token 中下载 kaggle.json
# mv kaggle.json ~/.kaggle/kaggle.json
kaggle competitions download -c ml2023spring-hw7
unzip ml2023spring-hw7.zip 不过HW7的数据集比较小所以我直接上传了 你可以不用自己下载。
Gradescope
Question 1 训练/推理过程的差异 Fine-tuning: 进行梯度下降调整模型参数。模型通过多个回合epochs的训练学习特定任务的数据集。微调后的模型能够很好地解决特定任务。 In-context learning: 不进行梯度下降只依赖少量样本让预训练模型通过上下文提供答案。这是一种轻量的任务特化方式可以快速得到结果其实就是 prompt。 Label Words are Anchors: An Information Flow Perspective for Understanding In-Context Learning 2023年的这篇文章展示了 In-Context Learning 实际上真的在起作用如果感兴趣可以阅读原文或者查看24年的视频 第11讲大型语言模型在「想」什么呢 — 浅谈大型语言模型的可解释性。 拓展阅读 Encoder-Only vs Decoder-Only vs Encoder-Decoder TransformerDecoder-Only or Encoder-Decoder? Interpreting Language Model as a Regularized Encoder-Decoder A. Encoder-only 模型如 BERT 系列如何在抽取式问答任务中确定答案
在 BERT 等编码器模型中抽取式问答的工作原理如下 输入格式输入由两个部分组成 问题Question作为第一个句子句子 A。段落Passage作为第二个句子句子 B其中答案位于该段落中。 Tokenization标记化BERT 对问题和段落进行标记化并将它们合并成一个输入序列序列通常是 [CLS] Question [SEP] Passage [SEP]。 编码器处理BERT 对整个输入序列进行处理生成每个标记的上下文表示。 预测开始和结束位置 BERT 通过两个线性层来分别预测答案的起始位置和结束位置。这两个线性层输出每个标记的得分。 # 打印 model 可以看到 qa_outputs。
...
(qa_outputs): Linear(in_features768, out_features2, biasTrue)最终的答案位置是通过找出问题段落中得分最高的开始标记和结束标记的位置来确定。 start_index torch.argmax(output.start_logits, dim1)
end_index torch.argmax(output.end_logits, dim1)输出答案答案是段落中的一段文本具体由起始位置和结束位置的标记对应的子序列来表示。这些标记会被解码回原始文本从而得到最终答案。
B. Decoder-only 模型如 GPT 系列如何在抽取式问答任务中确定答案
对于 GPT 等解码器模型工作原理与 BERT 不同。GPT 生成输出的方式与 BERT 不同但在处理抽取式问答时也可以通过以下方式确定答案 输入格式 GPT 是一个生成式模型输入格式通常是将问题和段落拼接在一起。例如Question: [问题文本] Passage: [段落文本]。 生成式预测 GPT 并不是直接输出答案的起始和结束位置。相反它会根据自回归生成的方式基于问题生成答案。在抽取式问答任务中GPT 将段落和问题作为上下文开始生成答案的第一个标记并逐步生成后续标记直到完成生成答案。 潜在的问题由于 GPT 是生成式模型它可能并不会严格“抽取”段落中的原始文本而是会生成一个看似合理的答案。它依赖上下文来生成答案不一定是段落中的原文子串。 与 BERT 的差异GPT 更擅长生成性任务而不是抽取任务。相比于 BERT 在抽取式问答中的精确性GPT 更可能生成出看似合理但并不严格匹配原文的答案也就是瞎编。
总结
BERT 系列Encoder-only 模型会根据标记的上下文表示直接预测出答案在段落中的起始和结束位置。GPT 系列Decoder-only 模型通过生成的方式给出答案基于段落和问题的上下文生成答案而不是直接定位段落中的子串。
Question 2 尝试不同的 Prompt 并观察 fine-tuning 和 in-context learning 的区别。 这里代码所下载的是 facebook/xglm-1.7B实际上你也可以直接去 GPT 或者其他 AI 平台问这里的目的是让你去调整自己的 prompt从而使模型不经过微调也能获取到正确答案。 Prompt 对比错误对比
Prompt 示例 1: “根据文章找出问题的答案{问题}”。Prompt 示例 2: “请阅读文章并回答以下问题{问题}”。Prompt 示例 3: “请根据文章信息回答下列问题{问题}”。
Prompt 对比正确对比 中英对比不同 prompt 对比
代码解析 这里解释一下QA_Dataset如果觉得太长可以只查看重点部分。 QA_Dataset 类功能解析Medium / Strong 相关 初始化__init__ split 决定数据集的类型训练、验证或测试。questions, tokenized_questions, 和 tokenized_paragraphs 是原问题和 tokenized 后的问题和段落。max_question_len 和 max_paragraph_len 分别设定了问题和段落的最大长度。self.doc_stride段落的窗口滑动步长决定每个窗口之间的重叠部分。 Sample code 中将其设置为 150和 max_paragraph_len 一样意味着窗口之间完全不重叠。 self.max_seq_len定义了整个输入序列的最大长度包含问题和段落。 __getitem__ 针对给定的索引 idx获取对应问题和段落数据返回模型需要的输入。训练集定位答案的起始和结束位置将包含答案的段落部分截取为一个窗口中心在答案位置附近。然后将问题和段落合并为一个输入序列并进行填充。验证/测试集将段落分成多个窗口每个窗口之间的步长由 self.doc_stride 决定然后将每个窗口作为模型的输入。验证和测试时不需要答案位置因此只需生成多个窗口作为输入。 填充padding 输入序列可能比最大序列长度短填充部分用 0 表示。对于问题部分和段落部分token_type_ids 被用来区分它们0 表示问题1 表示段落。attention_mask 用于标记有效的输入部分防止模型对填充部分进行注意力计算。
重点
self.doc_stride 通过控制窗口之间的滑动步长确保即使答案位于窗口边缘模型也能通过多个窗口重叠的方式找到答案。训练阶段不需要使用 doc_stride因为训练时我们已经知道答案的位置可以直接截取包含答案的窗口。但在验证和测试阶段由于模型并不知道答案的位置doc_stride 保证每个窗口之间有足够的重叠避免遗漏答案。所以这里存在一个问题训练过程中模型可能学习到答案就在中间这一模式。这是我们在 Strong baseline 中需要解决的。
Baselines 论文后遗症上来了这次会包含多组对比 : ) 每次模块的增加简单基于上次最好的设置进行。 HW7 的代码更多的是在向你演示如何去微调一个能够提取正确答案的 LLM。 括号里是 Kaggle Leaderboard 中的 Public 分数。
Simple baseline (0.45573)
运行所给的 sample code。
Medium baseline (0.67820) 使用学习率调度器这里演示两种方法随意选择即可。 PyTorch 线性衰减。 from torch.optim.lr_scheduler import LambdaLRtotal_steps len(train_loader) * num_epochlr_lambda lambda step: max(0.0, 1.0 - step / total_steps)
scheduler LambdaLR(optimizer, lr_lambdalr_lambda)Hugging Face 使用 warmup。 from transformers import get_linear_schedule_with_warmuptotal_steps len(train_loader) * num_epoch
num_warmup_steps int(0.2 * total_steps) # 设置为 0 理论上等价于线性衰减设置为 1 理论上等价于线性增加scheduler get_linear_schedule_with_warmup(optimizer, num_warmup_stepsnum_warmup_steps, num_training_stepstotal_steps
)不同num_warmup_steps对学习率的影响 我在当前框架上简单进行了对比注意如果你想自己进行对比请重新运行文件而不是重复运行单元格不然会导致在之前的模型上继续训练 SchedulePrivate ScorePublic Scoreno schedule0.544260.56356Linear schedule (PyTorch)0.551640.56356Linear schedule with warmup (0)0.551640.56356Linear schedule with warmup (0.2)0.546530.56356Linear schedule with warmup (0.5)0.539160.54597Linear schedule with warmup (1.0)0.549940.55051这里简单选择Linear schedule with warmup (0)进行下一步实验。 调整 doc_stride 参数提升模型表现 doc_stride 在 sample code 代码默认为 150也就是 max_paragraph_len你可以理解为默认情况下文本的窗口不重叠也就是说第一个窗口从 0 开始 149 结束第二个窗口从 150 开始299 结束这两个窗口之间的文本不会发生重叠但这存在一个问题问题的答案可能在 140-160 中默认的设置会无法捕捉到这部分的答案。所以我们需要调整这个参数你可以将其理解为段落窗口滑动步长也可以将其理解为卷积中的 stride。 注意doc_stride 没有用在训练阶段。 这里给出一些对比你可以继续实验 self.doc_stridePrivate ScorePublic Scoremax_paragraph_len1500.551640.56356max_paragraph_len * 0.750.634500.62656max_paragraph_len * 0.50.673660.68501max_paragraph_len * 0.250.700900.68161这里让 self.doc_stridemax_paragraph_len * 0.25 进行下一步实验在你实际实验时应该仅参考 Public Score因为 Private Score 是最终的批改分数。
Strong baseline (0.76220) 修改模型的预处理过程 (TODO: Preprocessing) Sample code 对于训练的数据处理是直接以答案为中心选择文本窗口这导致模型可能学到一个不该学习的模式答案就在中间。 这里随机偏移答案的窗口位置来解决这一问题你可以修改 max_offset 的参数 ...# A single window is obtained by slicing the portion of paragraph containing the answermid (answer_start_token answer_end_token) // 2# ---- Strong -----# Introduce random offset to prevent learning that answer is always in the middlemax_offset self.max_paragraph_len # We allow up to 1/4 of the max length as offsetrandom_offset np.random.randint(-max_offset, max_offset) # Random shift between -max_offset and max_offset# Adjust paragraph start based on random offsetparagraph_start max(0, min(mid random_offset - self.max_paragraph_len // 2, len(tokenized_paragraph) - self.max_paragraph_len))paragraph_end paragraph_start self.max_paragraph_len# ---- Strong -----...max_offsetPrivate ScorePublic Score不进行偏移0.700900.68161 self.max_paragraph_len // 40.726440.72928 self.max_paragraph_len // 20.731550.72985 self.max_paragraph_len0.728140.72701这里直接选择self.max_paragraph_len // 2作为偏移进行下一步。 注意当前代码偏移 self.max_paragraph_len // 2时并没有确保窗口一定包含答案所以self.max_paragraph_len偏移效果不佳。 选择 HuggingFace 上的其他预训练模型进行微调 在这里找一个模型替换掉当前的中文模型 – Hugging Face。 Sample code当前使用的是google-bert/bert-base-chinese一个103M参数的 Fill-Mask 模型替换模型的话修改下面这行代码即可。 model AutoModelForQuestionAnswering.from_pretrained(bert-base-chinese).to(device)
tokenizer AutoTokenizer.from_pretrained(bert-base-chinese)Pre-trained ModelParamsepoch备注Private ScorePublic Scoregoogle-bert/bert-base-chinese103M10.731550.7298520.771850.75993google-bert/bert-base-multilingual-cased179M10.745170.7400620.775250.76730FacebookAI/xlm-roberta-base279M1不使用token_type_ids0.689550.67139NchuNLP/Chinese-Question-Answering103M1不进行训练0.562990.57094NchuNLP/Chinese-Question-Answering103M1进行训练0.790010.78149
注意NchuNLP/Chinese-Question-Answering 是一个基于 google-bert/bert-base-chinese 使用 DRCD dataset 进行微调后的问答模型所以在 kaggle 用的话其实有点降维打击因为其他模型的 ACC 都是从 0 开始的而这个模型是从 0.56 开始的不过可以简单将 epoch 设置高一点自己训练一下 bert-base-chinese其实没什么差别。 token_type_ids 将输入序列划分为两个部分 0 表示第一个句子句子 A 1 表示第二个句子句子 B。 Boss baseline (0.84506) 修改后处理部分 (TODO: Postprocessing) 查看 result.csv 文件时可以发现有些结果是空的。这是因为在某些情况下预测的结束位置小于开始位置导致无法捕获到答案文本。我们需要修正这个问题确保结束位置始终大于或等于开始位置继续修改 TODO 部分。 # ---- Boss -----# Ensure the start_index is less than or equal to end_index# This avoids selecting a wrong pair of start and end positionsif start_index end_index:# Calculate the combined probability of start and end positionsprob start_prob end_prob# If this window has a higher probability answer, update the resultif prob max_prob:max_prob prob# Convert token indices to the corresponding text answer# Example: [1920, 7032] -- 大 金answer tokenizer.decode(data[0][0][k][start_index : end_index 1])else:# If start_index end_index, skip this pair (potentially an error case)continue# ---- Boss -----注意这部分修改的是 evaluate() 函数所以不会影响训练你可以随意的加上自己的想法然后直接运行 Testing 模块得到 result.csv 去查看效果。Boss 增加模块的对比见章末。 梯度累积 这是一个非常简单的想法即不在每次 step 后都更新梯度这样就等于变相的增加 batchsize每 n 个 step 更新一次等价于 batchsize 设置为 n*batchsize 。适用于显存不足以跑大 batchsize 的情况。不过 batchsize 大并不意味着效果一定好。 # ---- Boss -----actual_logging_steps 0 # Track the number of steps contributing to the current logging windowfor batch_idx, data in enumerate(tqdm(train_loader)):# Load all data into GPUdata [i.to(accelerator.device) for i in data]# Model inputs and forward passoutput model(input_idsdata[0], token_type_idsdata[1], attention_maskdata[2], start_positionsdata[3], end_positionsdata[4])# Accumulate lossloss output.loss / gradient_accumulation_stepsaccelerator.backward(loss)# Update accuracy for the current mini-batchstart_index torch.argmax(output.start_logits, dim1)end_index torch.argmax(output.end_logits, dim1)batch_acc ((start_index data[3]) (end_index data[4])).float().mean().item()train_acc batch_acctrain_loss loss.item()actual_logging_steps 1# Gradient accumulation: only update weights every gradient_accumulation_stepsif (batch_idx 1) % gradient_accumulation_steps 0:optimizer.step()scheduler.step() # Apply learning rate scheduleroptimizer.zero_grad()step 1# Loggingif step % logging_step 0:# Average the loss and accuracy over all accumulated stepsavg_loss train_loss / actual_logging_stepsavg_acc train_acc / actual_logging_stepsprint(fEpoch {epoch 1} | Step {step} | loss {avg_loss:.3f}, acc {avg_acc:.3f})# Reset the accumulatorstrain_loss 0.0train_acc 0.0actual_logging_steps 0 # Reset after each logging# ---- Boss -----loss output.loss / gradient_accumulation_steps是因为默认情况下 loss 的计算实际都已经做过平均了所以我们这里也需要保持一致。 另外说一句其实你也可以直接使用 Accelerator(gradient_accumulation_stepsgradient_accumulation_steps)。 训练用上 dev 数据集 去掉验证部分你需要注意我在这里将 QA_Dataset(dev, ...) 改为了 QA_Dataset(train, ...) dev_set QA_Dataset(train, dev_questions, dev_questions_tokenized, dev_paragraphs_tokenized) # Boss...combined_train_set ConcatDataset([train_set, dev_set])
train_loader DataLoader(combined_dataset, batch_sizetrain_batch_size, shuffleTrue, pin_memoryTrue)
...# if validation:# print(Evaluating Dev Set ...)# model.eval()# with torch.no_grad():# dev_acc 0# for i, data in enumerate(tqdm(dev_loader)):# output model(input_idsdata[0].squeeze(dim0).to(device), token_type_idsdata[1].squeeze(dim0).to(device),# attention_maskdata[2].squeeze(dim0).to(device))# # prediction is correct only if answer text exactly matches# dev_acc evaluate(data, output) dev_questions[i][answer_text]# print(fValidation | Epoch {epoch 1} | acc {dev_acc / len(dev_loader):.3f})# model.train()Ensemble 和 early stop 之前我们都在作业中做过所以不选择这两种方法直接简单的拼接数据集用于训练看看效果。
简单过一个epoch看看效果粗体部分就是选择使用的模块
epochPrivate ScorePublic Score-10.790010.78149 修复后处理部分10.791710.78263 梯度累积410.791710.79114梯度累积810.790570.78603梯度累积1610.783760.77355 dev数据集10.795110.78206
现在我们直接“硬 train 一发”毕竟该完成的已经完成了看看最终成效。
作业 PDF 的描述中Boss 时间是 Simple 的 18.75 倍加上 dev 数据集后运行时间将增加 12%所以设置 epochs16 进行训练查看效果因为直到 Strong 完成我们都没有增加参数规模所以当前训练时间等于 Simple。
最终的结果epoch16
Public Score0.79114Private Score0.77525
虽然在训练集的结果上第 16 个 epoch 比第 1 个 epoch 的 ACC 高了 0.073但实际上这是过拟合的Kaggle 的最终提交结果甚至不如只训练 1 次的情况。到这里其实就足够了你已经学到了这份作业想要教你的知识后面会是一些预训练模型的结果分享或许能够帮你节省一些时间。
所以让我们换一个更大的模型 : )
Pre-trained ModelParamsepochPrivate ScorePublic ScoreDaydreamerF/chinese-macbert-base-finetuned-accelerate101M20.788300.78660IDEA-CCNL/Erlangshen-MacBERT-325M-TextMatch-Chinese324M20.788870.79228hfl/chinese-macbert-large324M10.816680.8234950.831440.83030luhua/chinese_pretrain_mrc_macbert_large324M10.836540.8229220.842220.831442 (FP32)0.841650.8297330.837770.8206550.829170.82463qalover/chinese-pert-large-open-domain-mrc324M00.564130.5414320.833140.82519
在导入大模型的时候你可能会遇到显存不够的情况这时候降低 train_batch_size 增加gradient_accumulation_steps 就可以了。
#train_batch_size 8
#gradient_accumulation_steps 4train_batch_size 4
gradient_accumulation_steps 8如果想在 2 个 epoch 下达到 Boss baseline可以寻找并选择大于 324M 的预训练模型。
最后补充一个 doc_string 的对比doc_string 也是后处理的模块下面的结果基于 luhua/chinese_pretrain_mrc_macbert_large 在 epoch2 下进行修改。
doc_stringPrivate ScorePublic Scoremax_paragraph_len*0.250.842220.83144max_paragraph_len*0.10.843350.83087max_paragraph_len*0.050.838810.82746
至此Homework7 就结束了希望能对你有所帮助。
拓展链接 Encoder-Only vs Decoder-Only vs Encoder-Decoder Transformer Decoder-Only or Encoder-Decoder? Interpreting Language Model as a Regularized Encoder-Decoder 中文模型 – Hugging Face Gradient Accumulation in PyTorch
进一步
我将 24 年的课程作业修改为了大陆可以访问的 API并攥写了所有作业的中文引导和代码镜像你可以在这里快速索引 李宏毅2024生成式人工智能导论 中文镜像版指导与作业