Vuelidate内置验证器终极指南:25+常用规则完整解析

核心内容摘要

geckodriver:开源WebDriver工具提升Firefox自动化测试效率指南
如何用GetQzonehistory实现QQ空间数据备份?数字记忆保护全指南

ChatTTS优化实战:从模型推理到生产环境部署的完整指南

本文详细介绍了使用Hugging Face Transformers库微调大语言模型的完整流程。

从Transformer模型基础概念讲起通过问答模型微调案例展示数据准备、预处理、训练和验证全过程。

理论与实践相结合提供可直接运行的代码示例帮助读者理解如何将通用大模型适应特定业务场景解决实际问题。

在自然语言处理NLP的浪潮中大型预训练模型如 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-

# 获取模型输出对应的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(

) 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(

# [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,

, (1,

, (2,

, (4,

, (0,

, (5,

, (6,

, (7,

, (8,

, (9,

...]可以看到(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, ) #

处理答案位置 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,

# 这里的 offset[context_end][1] 是当前片段最后一个 Token 对应的原文结束字符位置 if offset[context_start][0] start_char or offset[context_end][1] end_char: start_positions.append(

end_positions.append(

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 -

# 从上下文的最后一个 Token 开始往前找直到找到包含 end_char 的 Token idx context_end while idx context_start and offset[idx][1] end_char: idx - 1 end_positions.append(idx

# 将计算好的 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_decay

01, push_to_hubFalse, logging_steps10, use_mps_deviceFalse, )我们先来看一下模型训练时的一些重要参数num_train_epochs要执行的训练轮数总和。

通俗来说1 Epoch表示模型完完整整的看过进行训练的数据集一次。

如果num_train_epochs设置过多训练出来的模型将会过拟合即反反复复多次背诵训练的数据集对数据集就很熟悉但是如果遇到新的问题则可能回答不出来泛化能力差。

反之则欠拟合。

学习率 learning_rate: 决定了每次模型训练时参数的更新幅度

**简单来说模型在训练过程中的效果不够好时模型需要调整的幅度。

学习率太大 比如

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:

9616, grad_norm:

1

661741256713867, learning_rate:

7e-05, epoch:

33} {loss:

0221, grad_norm:

2

38640594482422, learning_rate:

3666666666666666e-05, epoch:

67} {loss:

7888, grad_norm:

3

200885772705078, learning_rate:

0333333333333336e-05, epoch:

1

0} {loss:

2302, grad_norm:

4

9060173034668, learning_rate:

7e-05, epoch:

1

33} {loss:

5915, grad_norm:

5

04061508178711, learning_rate:

366666666666667e-05, epoch:

1

67} {loss:

3984, grad_norm:

5519830584526062, learning_rate:

0333333333333337e-05, epoch:

2

0} {loss:

5358, grad_norm:

1

61482810974121, learning_rate:

7000000000000002e-05, epoch:

2

33} {loss:

0591, grad_norm:

2

124296188354492, learning_rate:

3666666666666668e-05, epoch:

2

67} {loss:

009, grad_norm:

022040903568267822, learning_rate:

0333333333333334e-05, epoch:

3

0} {loss:

0018, grad_norm:

025523267686367035, learning_rate:

7000000000000003e-05, epoch:

3

33} {loss:

0054, grad_norm:

21060892939567566, learning_rate:

3666666666666666e-05, epoch:

3

67} {loss:

0004, grad_norm:

01707434467971325, learning_rate:

0333333333333333e-05, epoch:

4

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 ≈

33第 20 步时打印日志此时 Epoch 20 / 3 ≈

67第 30 步时打印日志此时 Epoch 30 / 3

1

0这就是为什么你会看到

33 ,

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() #

分词 inputs tokenizer(question, context, return_tensorspt) #

模型前向传播 with torch.no_grad(): outputs model(**inputs) #

后处理获取预测结果 # 模型输出包含 start_logits 和 end_logits分别表示每个 Token 是答案开头的概率和结尾的概率 answer_start_index torch.argmax(outputs.start_logits) answer_end_index torch.argmax(outputs.end_logits) 1 # 1 是因为切片是左闭右开 #

将 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, ) #

处理答案位置 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,

# 这里的 offset[context_end][1] 是当前片段最后一个 Token 对应的原文结束字符位置 if offset[context_start][0] start_char or offset[context_end][1] end_char: start_positions.append(

end_positions.append(

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 -

# 从上下文的最后一个 Token 开始往前找直到找到包含 end_char 的 Token idx context_end while idx context_start and offset[idx][1] end_char: idx - 1 end_positions.append(idx

# 将计算好的 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() #

分词 inputs tokenizer( question, context, max_length384, truncationonly_second, return_offsets_mappingTrue, return_overflowing_tokensTure stride100, paddingmax_length ) #

模型前向传播 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 大模型突围资料包✅AI大模型学习路线图✅Agent行业报告✅100集大模型视频教程✅大模型书籍PDF✅DeepSeek教程✅AI产品经理入门资料完整的大模型学习和面试资料已经上传带到CSDN的官方了有需要的朋友可以扫描下方二维码免费领取【保证100%免费】​​为什么说现在普通人就业/升职加薪的首选是AI大模型人工智能技术的爆发式增长正以不可逆转之势重塑就业市场版图。

从DeepSeek等国产大模型引发的科技圈热议到全国两会关于AI产业发展的政策聚焦再到招聘会上排起的长队AI的热度已从技术领域渗透到就业市场的每一个角落。

智联招聘的最新数据给出了最直观的印证2025年2月AI领域求职人数同比增幅突破200%远超其他行业平均水平整个人工智能行业的求职增速达到

3

4%位居各行业榜首其中人工智能工程师岗位的求职热度更是飙升

6

6%。

AI产业的快速扩张也让人才供需矛盾愈发突出。

麦肯锡报告明确预测到2030年中国AI专业人才需求将达600万人人才缺口可能高达400万人这一缺口不仅存在于核心技术领域更蔓延至产业应用的各个环节。

​​资料包有什么①从入门到精通的全套视频教程⑤⑥包含提示词工程、RAG、Agent等技术点② AI大模型学习路线图还有视频解说全过程AI大模型学习路线③学习电子书籍和技术文档市面上的大模型书籍确实太多了这些是我精选出来的④各大厂大模型面试题目详解⑤ 这些资料真的有用吗?这份资料由我和鲁为民博士共同整理鲁为民博士先后获得了北京清华大学学士和美国加州理工学院博士学位在包括IEEE Transactions等学术期刊和诸多国际会议上发表了超过50篇学术论文、取得了多项美国和中国发明专利同时还斩获了吴文俊人工智能科学技术奖。

目前我正在和鲁博士共同进行人工智能的研究。

所有的视频教程由智泊AI老师录制且资料与智泊AI共享相互补充。

这份学习大礼包应该算是现在最全面的大模型学习资料了。

资料内容涵盖了从入门到进阶的各类视频教程和实战项目无论你是小白还是有些技术基础的这份资料都绝对能帮助你提升薪资待遇转行大模型岗位。

智泊AI始终秉持着“让每个人平等享受到优质教育资源”的育人理念‌通过动态追踪大模型开发、数据标注伦理等前沿技术趋势‌构建起前沿课程智能实训精准就业的高效培养体系。

课堂上不光教理论还带着学员做了十多个真实项目。

学员要亲自上手搞数据清洗、模型调优这些硬核操作把课本知识变成真本事‌​​​​如果说你是以下人群中的其中一类都可以来智泊AI学习人工智能找到高薪工作一次小小的“投资”换来的是终身受益应届毕业生‌无工作经验但想要系统学习AI大模型技术期待通过实战项目掌握核心技术。

零基础转型‌非技术背景但关注AI应用场景计划通过低代码工具实现“AI行业”跨界‌。

业务赋能 ‌突破瓶颈传统开发者Java/前端等学习Transformer架构与LangChain框架向AI全栈工程师转型‌。

获取方式有需要的小伙伴可以保存图片到wx扫描二v码免费领取【保证100%免费】**​

国外黄冈网站推广游戏-国外黄冈网站推广游戏应用

百度百家号客服电话人工服务

123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123