销售产品网站有哪些,企业手机网站建设公司,杭州建设网站的公司,施工企业环境管理体系在自然语言处理#xff08;NLP#xff09;的浪潮中#xff0c;大型预训练模型#xff08;如 BERT、GPT 等#xff09;已成为驱动各类应用的核心引擎。然而#xff0c;如何让这些通用模型更好地适应我们特定的业务场景#xff1f;答案便是微调#xff08;Fine-tuningNLP的浪潮中大型预训练模型如 BERT、GPT 等已成为驱动各类应用的核心引擎。然而如何让这些通用模型更好地适应我们特定的业务场景答案便是微调Fine-tuning。Hugging Face 推出的 Transformers 库凭借其无与伦比的易用性和丰富的模型生态极大地降低了微调的技术门槛。本文不满足于对 API 的浅尝辄止而是希望为您提供一份兼具深度与可操作性的“食谱”。读完本文您将不仅能成功运行代码更能洞悉其背后的“为什么”并具备独立解决实际问题的能力。很多同学可能对模型的认知停留在应用层本文意在让大家能够有一些针对模型的认知以及训练一个模型究竟需要分为多少步骤基础概念在动手编码之前我们有必要先花些时间理解 Transformers 的核心设计哲学。这能帮助我们在遇到问题时不仅知其然更能知其所以然。Transformers 模型Transformers 模型通常规模庞大。包含数以百万计到数千万计数十亿的参数训练和部署这些模型是一项复杂的任务。再者新模型的推出几乎日新月异而每种模型都有其独特的实现方式尝试全部模型绝非易事。Transformers 库应运而生就是为了解决这个问题。它的目标是提供一个统一的 API 接口通过它可以加载、训练和保存任何 Transformer 模型。Transformers 模型用于解决各种 NLP 问题如feature-extraction获取文本的向量表示fill-mask完形填空ner命名实体识别question-answering问答sentiment-analysis情感分析summarization提取摘要text-generation文本生成translation翻译zero-shot-classification零样本分类Transformers 模型主要分为 2 层 编码器和解码器我们可以将其简单理解为输入 - 输出编码器和解码器编码器 (Encoder): 专职“理解”。它负责将输入文本如一个句子转换成富含语义信息的数字表示。非常适合做文本分类、命名实体识别等任务。代表选手BERT、RoBERTa。解码器 (Decoder): 专职“生成”。它能根据一个初始指令Prompt逐字逐句地创造出新的文本。我们熟知的 GPT 系列就是典型的解码器架构。编码器-解码器 (Encoder-Decoder): “理解”与“生成”的结合体。先用编码器消化输入文本再用解码器产出目标文本。是翻译、摘要等任务的标配。代表选手BART、T5。模型示例任务编码器BERT, DistilBERT, ELECTRA, RoBERTa句子分类、命名实体识别、抽取式问答 (从文本中提取答案)解码器CTRL, GPT, GPT-2, Transformer XL文本生成编码器-解码器BART, T5, Marian, mBART文本摘要、翻译、生成式问答 (生成问题的回答类似 chatgpt)架构和检查点(Checkpoints)架构这是模型的骨架 —— 即每个层的定义以及模型中发生的每个操作。Checkpoints检查点这些是将在给架构中结构中加载的权重参数是一些具体的数值。举个例子BERT 是一个架构而 bert-base-cased 这是谷歌团队为 BERT 的第一个版本训练的一组权重参数是一个参数。我们可以说“BERT 模型”和“ bert-base-cased 模型。”Tokenizer与其他神经网络一样Transformers 模型无法直接处理原始文本因此我们需要引入Tokenizer。**Tokenizer**是人类语言与机器语言之间的“翻译官”。其职责重大主要包括分词 (Tokenization): 将 “今天天气真好” 这样的句子拆分成模型能认识的最小单元如[今, 天, 天, 气, 真, 好]。ID 转换: 将每个词元Token映射成一个独一无二的数字 ID即input_ids。添加特殊标记: 插入模型必需的特殊符号比如[CLS]用于分类任务[SEP]用于分隔句子。生成注意力掩码 (Attention Mask): 当句子长度不一时短句子需要被“填充”Padding到与长句同样的长度。注意力掩码就是一个由 0 和 1 组成的列表告诉模型哪些是真实词元值为 1哪些是填充物值为 0计算时应忽略后者。我们先通过分词器Tokenizer把文本转换为模型能够读懂的数字。def run_tokenizer(): checkpoint distilbert-base-uncased-finetuned-sst-2-english tokenizer AutoTokenizer.from_pretrained(checkpoint) result tokenizer( [I am very urgent!, I want to complain!], paddingTrue, truncationTrue, return_tensorspt, ) # { # input_ids: # tensor([[ 101, 1045, 2572, 2200, 13661, 999, 102],[ 101, 1045, 2215, 2000, 17612, 999, 102]]), # attention_mask: # tensor([[1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1]]) # } # 输出是一个包含两个键 input_ids 和 attention_mask # input_ids 包含两行整数每个句子一行它们是每个句子中 token 的 ID。 print(result)Model模型会接收 Tokenizer 生成的数字通过模型头等进行处理最终生成对应任务的输出结果。例如在情感分类任务中生成类别概率分布分别是正面和负面。但这些不是概率而是logits对数几率是模型最后一层输出的原始的、未标准化的分数。要转换为概率它们需要经过 SoftMax 层下面是一个情感分类的一个例子def run_model(): from transformers import AutoModel, AutoModelForSequenceClassification, AutoTokenizer import torch.nn.functional as F checkpoint distilbert-base-uncased-finetuned-sst-2-english model AutoModelForSequenceClassification.from_pretrained(checkpoint) tokenizer AutoTokenizer.from_pretrained(checkpoint) # 分词得到 input_ids input tokenizer([I am very urgent!, I want to complain!], paddingTrue, return_tensorspt) res model(**input) # 处理后序输出 probabilities F.softmax(res.logits, dim-1) # 获取模型输出对应的label labels sequence_classication_model.config.id2label从微调一个小模型学起学习大模型最好的办法就是动手实践。下面从微调一个简单的问答模型为例子打开学习大模型的大门吧。智能流程总结原料数据准备我们需要一批包含context上下文、question问题和answers答案文本及其在上下文中的起始位置的数据集。预处理分词调用与预训练模型配套的Tokenizer将question和context转化为模型可消化的input_ids、attention_mask等数值输入。对于 QA 任务这一步至关重要它还需要计算出答案在分词后序列中所对应的start_positions和end_positions。送入模型将处理好的数据喂给一个专用于问答任务的模型如AutoModelForQuestionAnswering。训练微调配置TrainingArguments用于设定学习率、批次大小Batch Size等超参数。启动Trainer它会自动处理设备分配CPU/GPU、梯度更新、日志记录等一系列繁杂的后台工作。训练的核心目标是通过不断调整模型权重使得模型预测的答案起止位置与我们提供的真实标签越来越接近。出厂后处理将新的问题和上下文输入给微调完毕的模型。模型会输出两组分数start_logits和end_logits分别代表每个词元作为答案开头和结尾的可能性。我们通过一个简单的后处理逻辑找到分数最高的组合便能解码出最终的答案文本。获取数据集训练模型最重要的事情是**数据集**我们可以从Hugging Face等渠道获取各种各样的数据集。但是这里我们为了效果明显自己去构建一个极为简单的数据集。ctx 权限管理平台 ACC(Auth Config Center) 为中台提供一套运行稳定、安全可靠、界面简洁的可视化权限配置能力。包括权限配置、权限下发及鉴权功能。 其涉及了一些名词 - 权限点(keyword)权限系统中最小的权限粒度映射到业务系统对应系统操作功能。例如查询搜索删除等操作 - 功能权限树为用户提供权限下发与系统权限管控 - 菜单权限树提供页面及菜单的权限下发与管控 - 白名单无需登录、需要登录无需鉴权赋予某一特定角色功能 # 原始数据列表 raw_data [ { id: 001, context: ctx, question: 什么是 ACC, answer_text: 权限管理平台, }, { id: 002, context: ctx, question: ACC 有哪些功能, answer_text: 权限配置、权限下发及鉴权功能, }, { id: 003, context: ctx, question: ACC 有哪些名词, answer_text: 权限点, }, { id: 004, context: ctx, question: 权限点是干嘛的, answer_text: 权限系统中最小的权限粒度映射到业务系统对应系统操作功能, }, ]有了原始数据我们需要对其进行格式转换。在学术领域用于抽取式问答的最常用基准数据集是SQuAD我们可以去下载一下看看它的格式是怎样的from datasets import load_datasetraw_datasets load_dataset(squad)# DatasetDict({# train: Dataset({# features: [id, title, context, question, answers],# num_rows: 87599# })# validation: Dataset({# features: [id, title, context, question, answers],# num_rows: 10570# })# })# 其中answers格式为{text:string[], answer_start:int[]}context和question字段的使用非常简单直接。answers字段稍显复杂因为它包含一个带有两个字段的字典而这两个字段都是列表。这是评估过程中squad指标所期望的格式如果你使用自己的数据则不一定需要费心将答案设置为相同的格式。text字段的含义相当明显answer_start字段包含每个答案在上下文中的起始字符索引。我们可以把我们的原始数据也转换成这种格式。完整代码如下def create_toy_qa_dataset() - DatasetDict: ctx 权限管理平台 ACC(Auth Config Center) 为中台提供一套运行稳定、安全可靠、界面简洁的可视化权限配置能力。包括权限配置、权限下发及鉴权功能。 其涉及了一些名词 - 权限点(keyword)权限系统中最小的权限粒度映射到业务系统对应系统操作功能。例如查询搜索删除等操作 - 功能权限树为用户提供权限下发与系统权限管控 - 菜单权限树提供页面及菜单的权限下发与管控 - 白名单无需登录、需要登录无需鉴权赋予某一特定角色功能 # 原始数据列表 raw_data [ { id: 001, context: ctx, question: 什么是 ACC, answer_text: 权限管理平台, }, { id: 002, context: ctx, question: ACC 有哪些功能, answer_text: 权限配置、权限下发及鉴权功能, }, { id: 003, context: ctx, question: ACC 有哪些名词, answer_text: 权限点, }, { id: 004, context: ctx, question: 权限点是干嘛的, answer_text: 权限系统中最小的权限粒度映射到业务系统对应系统操作功能, }, ] # 格式化数据自动计算 answer_start formatted_data {id: [], context: [], question: [], answers: []} for item in raw_data: context item[context] answer_text item[answer_text] # 找到答案在原文中的起始位置 start_idx context.find(answer_text) formatted_data[id].append(item[id]) formatted_data[context].append(context) formatted_data[question].append(item[question]) formatted_data[answers].append( {text: [answer_text], answer_start: [start_idx]} ) # 创建 Dataset ds Dataset.from_dict(formatted_data) validation_ds ds.select(range(4)) dsd DatasetDict({train: ds, validation: validation_ds}) return dsd我们这次训练使用google-bert/bert-base-chinese模型虽然它并不是专门用于问答任务。先展示一下微调前的效果model_checkpoint google-bert/bert-base-chinese# 加载 Tokenizertokenizer AutoTokenizer.from_pretrained(model_checkpoint)# 加载 Datasetraw_datasets create_toy_qa_dataset()model AutoModelForQuestionAnswering.from_pretrained(model_checkpoint)test_context raw_datasets[train][1][context]test_question raw_datasets[train][1][question]answer get_answer(test_question, test_context, model, tokenizer)# answer: 这里的 get_answer 的具体代码在下文会详细讲解这里认为是模型推理即可。这里大概率为空或乱码因为该模型没学过这个任务我们需要对它进行微调来让模型能够满足我们的效果。预处理数据集我们需要将数据集中的文本信息处理成Input IDs。利用DatasetDict中的map方法可以对整个数据集做批处理tokenized_datasets raw_datasets.map( lambda x: preprocess_function(x, tokenizer), batchedTrue, # 是否批处理 remove_columnsraw_datasets[ train ].column_names, # 移除原始文本列只保留分词后的列 即 Input ID 等 ) plaintext def preprocess_function(samples, tokenizer): tokenized_inputs tokenizer( examples[question], examples[context], max_length384, truncationonly_second, return_offsets_mappingTrue, paddingmax_length, ) # ...首先tokenizer中我们传入了每一个样本 数据集中的每一条数据 的question和context这样将会对同时对两者进行处理。max_length表示tokenizer处理的最大长度这里假设答案一定在前 384 个 Token 里。如果文章很长超出部分直接扔掉。truncationonly_second跟上述一样如果超长直接对context进行丢弃。return_offsets_mappingTrue: 返回每个 Token 对应原文的字符位置 (start_char, end_char)最终输入的tokenizied_inputs.input_ids是一个长度为5的数组因为数据集只有5条数据每一项都包含 question context我们可以通过tokenized_inputs.sequence_ids(i)去获取具体某条input_id中哪一个部分代表question、哪一个部分代表context。tokenized_inputs.sequence_ids(0) # [None, 0, 0, 0, 0, None, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...]tokenized_inputs[offset_mapping]也是一个长度为5的数组它包含了每一个Token所在的索引# 对应索引0[(0, 0), (0, 1), (1, 2), (2, 3), (4, 7), (0, 0), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10)...]可以看到(0, 0)刚好对应的是 None其实就是question和context之间的特殊标记。最后我们要做的就是需要找到Token**级别下**答案所在的位置先排除question部分找到context所在的Token下的位置索引从context的首尾同时遍历直到找到包含start_char(原始数据中答案所在的位置索引) 的Token这部分相对简单代码如下。def preprocess_function(examples, tokenizer): tokenized_inputs tokenizer( examples[question], examples[context], max_length384, truncationonly_second, return_offsets_mappingTrue, paddingmax_length, ) # 2. 处理答案位置 offset_mapping tokenized_inputs.pop(offset_mapping) answers examples[answers] start_positions [] end_positions [] for i, offset in enumerate(offset_mapping): answer answers[i] start_char answer[answer_start][0] end_char start_char len(answer[text][0]) # sequence_ids 用于区分哪部分是问题哪部分是上下文 # 0 代表问题1 代表上下文None 代表特殊符号([CLS], [SEP], [PAD]) sequence_ids tokenized_inputs.sequence_ids(i) # 找到上下文在 Token 序列中的起始和结束索引 idx 0 while sequence_ids[idx] ! 1: idx 1 context_start idx while sequence_ids[idx] 1: idx 1 context_end idx - 1 # 如果答案不在当前截断的片段中针对超长文本这就标记为 (0, 0) # 这里的 offset[context_end][1] 是当前片段最后一个 Token 对应的原文结束字符位置 if offset[context_start][0] start_char or offset[context_end][1] end_char: start_positions.append(0) end_positions.append(0) else: # 否则我们需要找到 Token 的 start_index 和 end_index # 从上下文的第一个 Token 开始往后找直到找到包含 start_char 的 Token idx context_start while idx context_end and offset[idx][0] start_char: idx 1 start_positions.append(idx - 1) # 从上下文的最后一个 Token 开始往前找直到找到包含 end_char 的 Token idx context_end while idx context_start and offset[idx][1] end_char: idx - 1 end_positions.append(idx 1) # 将计算好的 Token 级别的起始和结束位置放入 inputs 中 tokenized_inputs[start_positions] start_positions tokenized_inputs[end_positions] end_positions return tokenized_inputs构建超参数开始训练args TrainingArguments( output_dirqa-model-finetuned, eval_strategyno, # 数据太少不进行分步评估 save_strategyno, learning_rate5e-5, per_device_train_batch_size2, # 小批量 per_device_eval_batch_size2, num_train_epochs50, # 增加 epoch 以确保拟合 weight_decay0.01, push_to_hubFalse, logging_steps10, use_mps_deviceFalse, )我们先来看一下模型训练时的一些重要参数num_train_epochs要执行的训练轮数总和。通俗来说1 Epoch表示模型完完整整的看过进行训练的数据集一次。如果num_train_epochs设置过多训练出来的模型将会过拟合即反反复复多次背诵训练的数据集对数据集就很熟悉但是如果遇到新的问题则可能回答不出来泛化能力差。反之则欠拟合。学习率 learning_rate: 决定了每次模型训练时参数的更新幅度0-1。**简单来说模型在训练过程中的效果不够好时模型需要调整的幅度。学习率太大 比如 0.1老师大吼一声“全错重写”学生吓得不知所措可能下次走向另一个极端永远找不到正确答案模型不收敛Loss 震荡。学习率太小 比如 1e-8老师极其温柔地说“这里稍微改一点点…”学生改了一万次才改对等到毕业了还没学会训练太慢收敛不了。合适的值 5e-5老师指出关键错误让学生做适度的调整。BERT 微调通常都用这个量级2e-5 到 5e-5因为它已经“预习”预训练过了不需要从头学只需要微调。批量大小 per_device_train_batch_size指每台设备训练时的批量大小在多GPU或分布式训练中总**Batch size per_device_train_batch_size * number_of_devices**Batch Size 1 学生做完一题老师马上批改一题。学生能立刻得到反馈但老师会很累计算慢而且如果某道题出错了脏数据学生会被带偏。Batch Size 100 学生做完100题老师统一批改告诉他“总体方向对了没有”。这样比较稳梯度稳定但对老师的脑容量显存要求很高。权重衰减 weight_decay: 通俗来说 给“死记硬背”的学生扣分惩罚项。它强制模型不要过于依赖某几个特定的特征比如不要看到“因为”两个字就无脑选后面的句子做答案。它让模型的参数尽量小且分散这样模型的泛化能力更强。如果模型过拟合了可以适当调大weight_decay****。最后我们就可以通过Trainer对模型进行训练啦... # 实例化 Trainer data_collator DefaultDataCollator() trainer Trainer( modelmodel, argsargs, train_datasettokenized_datasets[train], eval_datasettokenized_datasets[validation], tokenizertokenizer, data_collatordata_collator, ) # 开始训练 trainer.train()在模型训练过程中终端会输出一些训练时参数{loss: 3.9616, grad_norm: 17.661741256713867, learning_rate: 4.7e-05, epoch: 3.33} {loss: 2.0221, grad_norm: 26.38640594482422, learning_rate: 4.3666666666666666e-05, epoch: 6.67} {loss: 1.7888, grad_norm: 30.200885772705078, learning_rate: 4.0333333333333336e-05, epoch: 10.0} {loss: 1.2302, grad_norm: 46.9060173034668, learning_rate: 3.7e-05, epoch: 13.33} {loss: 0.5915, grad_norm: 51.04061508178711, learning_rate: 3.366666666666667e-05, epoch: 16.67} {loss: 0.3984, grad_norm: 0.5519830584526062, learning_rate: 3.0333333333333337e-05, epoch: 20.0} {loss: 0.5358, grad_norm: 16.61482810974121, learning_rate: 2.7000000000000002e-05, epoch: 23.33} {loss: 0.0591, grad_norm: 20.124296188354492, learning_rate: 2.3666666666666668e-05, epoch: 26.67} {loss: 0.009, grad_norm: 0.022040903568267822, learning_rate: 2.0333333333333334e-05, epoch: 30.0} {loss: 0.0018, grad_norm: 0.025523267686367035, learning_rate: 1.7000000000000003e-05, epoch: 33.33} {loss: 0.0054, grad_norm: 0.21060892939567566, learning_rate: 1.3666666666666666e-05, epoch: 36.67} {loss: 0.0004, grad_norm: 0.01707434467971325, learning_rate: 1.0333333333333333e-05, epoch: 40.0}loss表示模型的预测与真实答案之间的差距。这个差距值就是我们所说的“损失值”。在训练过程中我们可以发现loss逐渐减少这证明模型在训练过程中的效果越来越符合验证集中的数据。为什么这里的**epoch**是小数在上述例子中数据总量 只有 5 条数据。批次大小 ( per_device_train_batch_size ) 设为 2 。那么完成 1 个 Epoch 遍历所有数据一遍需要走几步Steps(注第1步取2条第2步取2条第3步取最后1条)设置了logging_steps10 。这意味着 Trainer 每走 10 步 Steps就会打印一次日志。我们来算算 10 步 相当于跑了多少个 Epoch所以第 10 步时打印日志此时 Epoch 10 / 3 ≈ 3.33第 20 步时打印日志此时 Epoch 20 / 3 ≈ 6.67第 30 步时打印日志此时 Epoch 30 / 3 10.0这就是为什么你会看到 3.33 , 6.67 这种非整数的 Epoch。效果验证和总结我们以本节开头的例子来验证一下那么如何去验证呢test_context raw_datasets[train][1][context]test_question raw_datasets[train][1][question]answer get_answer(test_question, test_context, model, tokenizer)print(f问题: {test_question})print(f回答: {answer})在get_answer函数中我们需要进行 **tokenizer、modelc处理以及后处理**三步骤。def get_answer(question, context, model, tokenizer): # 将模型设为评估模式 model.eval() # 1. 分词 inputs tokenizer(question, context, return_tensorspt) # 2. 模型前向传播 with torch.no_grad(): outputs model(**inputs) # 3. 后处理获取预测结果 # 模型输出包含 start_logits 和 end_logits分别表示每个 Token 是答案开头的概率和结尾的概率 answer_start_index torch.argmax(outputs.start_logits) answer_end_index torch.argmax(outputs.end_logits) 1 # 1 是因为切片是左闭右开 # 4. 将 Token ID 转换回文本 predict_answer_tokens inputs.input_ids[0, answer_start_index:answer_end_index] predicted_answer tokenizer.decode(predict_answer_tokens, skip_special_tokensTrue) return predicted_answer要注意的是这里模型输出的是start_logits和end_logits分别表示每个 Token 是答案开头的概率和结尾的概率对数几率在这里我们直接通过torch.argmax分别获取两者概率最大的索引最后在Input ids中去获取相对应的Token最后再由tokenizer解码成文本内容。# 问题: ACC 有哪些功能# 回答: 权 限 配 置 、 权 限 下 发 及 鉴 权 功 能最后看上去效果还不错这次次模型微调就结束啦拓展内容注意本次微调只是一个“玩具”流程有一些需要注意的地方正常训练时的数据集不可能这么少动则上万条文本数据的数据集只是门槛。。由于训练集小因此我们的epoch设置了50轮让模型过拟合来更好的去验证结果。正常模型训练时一般取3轮左右。拓展内容预处理训练集滑动窗口在预处理训练集过程中我们采用简单截断的方式我假设你的文本很短或者答案一定在前 384 个 Token 里。如果文章很长超出部分直接扔掉 (truncationonly_second)。如果答案不幸在被扔掉的那部分里模型就永远找不到了。因此模型不需要处理“一个样本变成多个片段”的情况代码逻辑是一对一的非常简单。如果训练集中的文本内容很长我们可以给tokenizer设置stride步长表示将文章分割为若干个切片每一个切片都有重合的一部分。这就导致了“ 一对多 ”的关系1 个问题 - N 个输入片段。在预测时则需要收集这 N 个片段的所有输出Logits统一比较找出在这个长文章中到底哪一段的哪个位置分数最高。def preprocess_function(examples, tokenizer): tokenized_inputs tokenizer( examples[question], examples[context], max_length384, truncationonly_second, return_offsets_mappingTrue, return_overflowing_tokensTure stride100, paddingmax_length, ) # 2. 处理答案位置 offset_mapping tokenized_inputs.pop(offset_mapping) answers examples[answers] start_positions [] end_positions [] sample_idxs [] for i, offset in enumerate(offset_mapping): # 表示当前片段所对应的样本索引 sample_idx inputs[overflow_to_sample_mapping][i] sample_idxs.append(sample_idx) answer answers[sample_idx] start_char answer[answer_start][0] end_char start_char len(answer[text][0]) # sequence_ids 用于区分哪部分是问题哪部分是上下文 # 0 代表问题1 代表上下文None 代表特殊符号([CLS], [SEP], [PAD]) sequence_ids tokenized_inputs.sequence_ids(i) # 找到上下文在 Token 序列中的起始和结束索引 idx 0 while sequence_ids[idx] ! 1: idx 1 context_start idx while sequence_ids[idx] 1: idx 1 context_end idx - 1 # 如果答案不在当前截断的片段中针对超长文本这就标记为 (0, 0) # 这里的 offset[context_end][1] 是当前片段最后一个 Token 对应的原文结束字符位置 if offset[context_start][0] start_char or offset[context_end][1] end_char: start_positions.append(0) end_positions.append(0) else: # 否则我们需要找到 Token 的 start_index 和 end_index # 从上下文的第一个 Token 开始往后找直到找到包含 start_char 的 Token idx context_start while idx context_end and offset[idx][0] start_char: idx 1 start_positions.append(idx - 1) # 从上下文的最后一个 Token 开始往前找直到找到包含 end_char 的 Token idx context_end while idx context_start and offset[idx][1] end_char: idx - 1 end_positions.append(idx 1) # 将计算好的 Token 级别的起始和结束位置放入 inputs 中 tokenized_inputs[start_positions] start_positions tokenized_inputs[end_positions] end_positions tokenized_inputs[sample_idxs] sample_idxs return tokenized_inputs模型后处理在上述案例中我们是基于最简单的“贪心”策略去实现的。我们假设分别获取作为开头、结尾时概率最大的Token两者中间所包含的文本就是最佳答案。这样的处理方式会有很大的问题问题1开始索引大于结束索引现象当模型预测的始位置(start_index)大于结束位置(end_index)时切片input_ids[0, start_index:end_index]会返回空张量导致解码后得到空字符串原因独立使用argmax选择start和end位置未考虑两者的依赖关系答案必须是连续片段start end问题2置信度过低现象即使模型对所有位置的预测置信度都很低如context中无相关答案代码仍会返回一个答案原因直接使用 argmax 强制选择一个位置未考虑模型的预测不确定性解决方式通过遍历每一个 Token寻找出所有的开头和结尾的组合并计算其概率找出概率最大的那对组合。def get_answer(question, context, model, tokenizer): # 将模型设为评估模式 model.eval() # 1. 分词 inputs tokenizer( question, context, max_length384, truncationonly_second, return_offsets_mappingTrue, return_overflowing_tokensTure stride100, paddingmax_length ) # 2. 模型前向传播 with torch.no_grad(): outputs model(**inputs) transform_res [] start_logits outputs.start_logits.cpu().numpy() end_logits outputs.end_logits.cpu().numpy() logits_size start_logits.shape[0] for feature_idx in range(logits_size): sample_idx inputs[overflow_to_sample_mapping][feature_idx] offset inputs[offset_mappings][feature_idx] start_logit start_logits[feature_idx] end_logit end_logits[feature_idx] sequence_ids inputs.sequence_ids(feature_idx) for start_idx in start_logit: for end_idx in end_logit: if start_idx end_idx: continue if sequence_ids[start_idx] 0 and sequence_ids[end_idx] 0: continue start_token offset[start_idx][0] end_token offset[end_idx][1] if start_token 0 and end_token 0: continue if end_token start_token: continue ans_from_ctx context[start_token:end_token] # 从原始logits中取 scroe start_logits[logit_idx][start_idx] end_logits[logit_idx][end] transform_res.append( { answer: ans_from_ctx, score: scroe, start_token: start_token, end_token: end_token, } ) return transform_res想入门 AI 大模型却找不到清晰方向备考大厂 AI 岗还在四处搜集零散资料别再浪费时间啦2025 年AI 大模型全套学习资料已整理完毕从学习路线到面试真题从工具教程到行业报告一站式覆盖你的所有需求现在全部免费分享扫码免费领取全部内容一、学习必备100本大模型电子书26 份行业报告 600 套技术PPT帮你看透 AI 趋势想了解大模型的行业动态、商业落地案例大模型电子书这份资料帮你站在 “行业高度” 学 AI1. 100本大模型方向电子书2. 26 份行业研究报告覆盖多领域实践与趋势报告包含阿里、DeepSeek 等权威机构发布的核心内容涵盖职业趋势《AI 职业趋势报告》《中国 AI 人才粮仓模型解析》商业落地《生成式 AI 商业落地白皮书》《AI Agent 应用落地技术白皮书》领域细分《AGI 在金融领域的应用报告》《AI GC 实践案例集》行业监测《2024 年中国大模型季度监测报告》《2025 年中国技术市场发展趋势》。3. 600套技术大会 PPT听行业大咖讲实战PPT 整理自 2024-2025 年热门技术大会包含百度、腾讯、字节等企业的一线实践安全方向《端侧大模型的安全建设》《大模型驱动安全升级腾讯代码安全实践》产品与创新《大模型产品如何创新与创收》《AI 时代的新范式构建 AI 产品》多模态与 Agent《Step-Video 开源模型视频生成进展》《Agentic RAG 的现在与未来》工程落地《从原型到生产AgentOps 加速字节 AI 应用落地》《智能代码助手 CodeFuse 的架构设计》。二、求职必看大厂 AI 岗面试 “弹药库”300 真题 107 道面经直接抱走想冲字节、腾讯、阿里、蔚来等大厂 AI 岗这份面试资料帮你提前 “押题”拒绝临场慌1. 107 道大厂面经覆盖 Prompt、RAG、大模型应用工程师等热门岗位面经整理自 2021-2025 年真实面试场景包含 TPlink、字节、腾讯、蔚来、虾皮、中兴、科大讯飞、京东等企业的高频考题每道题都附带思路解析2. 102 道 AI 大模型真题直击大模型核心考点针对大模型专属考题从概念到实践全面覆盖帮你理清底层逻辑3. 97 道 LLMs 真题聚焦大型语言模型高频问题专门拆解 LLMs 的核心痛点与解决方案比如让很多人头疼的 “复读机问题”三、路线必明 AI 大模型学习路线图1 张图理清核心内容刚接触 AI 大模型不知道该从哪学起这份「AI大模型 学习路线图」直接帮你划重点不用再盲目摸索路线图涵盖 5 大核心板块从基础到进阶层层递进一步步带你从入门到进阶从理论到实战。L1阶段:启航篇丨极速破界AI新时代L1阶段了解大模型的基础知识以及大模型在各个行业的应用和分析学习理解大模型的核心原理、关键技术以及大模型应用场景。L2阶段攻坚篇丨RAG开发实战工坊L2阶段AI大模型RAG应用开发工程主要学习RAG检索增强生成包括Naive RAG、Advanced-RAG以及RAG性能评估还有GraphRAG在内的多个RAG热门项目的分析。L3阶段跃迁篇丨Agent智能体架构设计L3阶段大模型Agent应用架构进阶实现主要学习LangChain、 LIamaIndex框架也会学习到AutoGPT、 MetaGPT等多Agent系统打造Agent智能体。L4阶段精进篇丨模型微调与私有化部署L4阶段大模型的微调和私有化部署更加深入的探讨Transformer架构学习大模型的微调技术利用DeepSpeed、Lamam Factory等工具快速进行模型微调并通过Ollama、vLLM等推理部署框架实现模型的快速部署。L5阶段专题集丨特训篇 【录播课】四、资料领取全套内容免费抱走学 AI 不用再找第二份不管你是 0 基础想入门 AI 大模型还是有基础想冲刺大厂、了解行业趋势这份资料都能满足你现在只需按照提示操作就能免费领取扫码免费领取全部内容2025 年想抓住 AI 大模型的风口别犹豫这份免费资料就是你的 “起跑线”