8. bert实战

发布时间:2026/11/4 19:47:19
8. bert实战 在本次实战中我们要完成的是下游的文字分类任务不做预训练输入是语句输出是它的情感类别一句话在输入的时候首先要经过分词处理1. 训练前的知识储备1.1 分词器(tokenizer)一句话进入分词器就会被拆开变成一个字一个字的会产生三个东西input ids 汉字编号mask 这句话有多长seq_ids 句子编码模型的输入是固定的————不能一会长一会短所以要以mask为准超出就截断不够就paddingmask只是用来确定字数后面不再出现1.2 embeddinginput ids————linear21128768————Token Embeddingsseq_ids————linear2768————Segment Embeddingsidx————linear512768————position Embeddingsposition Embeddings的最大长度不是句子的长度而是模型的长度所以是512到768但是实际上使用的话一句话配的idx长度应该就是mask长度啊1.3 bert参数计算embeddinglinear21128768linear2768linear512768bert layersqkv3*768*768addnorm中的dense768*768 负责进一步提取维度特征feed forward768*4*7684*768*7681.4 hagging-face如果想自己下载模型可以直接进入hf-mirror去搜索然后从 model card 转到 files and versions 只下 pytorch 版及其他配置文件1.5 config介绍一下模型中的参数attention_probs_dropout_prob: 0.1, 残差丢弃率 directionality: bidi, hidden_act: gelu,损失函数 hidden_dropout_prob: 0.1,残差 hidden_size: 768,模型维度 initializer_range: 0.02, intermediate_size: 3072,前向传播那里的中间维度 layer_norm_eps: 1e-12, max_position_embeddings: 512,位置长度 model_type: bert, num_attention_heads: 12,注意力头数量 num_hidden_layers: 12,bert层数 pad_token_id: 0, pooler_fc_size: 768, pooler_num_attention_heads: 12, pooler_num_fc_layers: 3, pooler_size_per_head: 128, pooler_type: first_token_transform, type_vocab_size: 2,几种句子 vocab_size: 21128 词汇总数词表1.6 bert的参数量与结构bert模型代码长的原因是其中很多兼容性代码1.6.1 模型参数接下来我们先计算模型参数测试模型是否真的是使用下载下来的文件结论确实会使用自己下载来的bert模型会在同一目录文件夹下去找本地有bert模型参数量为102267648本地无bert尝试自己计算模型参数量emb_num21128*7682*768512*768 #忽略bias偏置 self_att_num768*768*3768*768768*30283028*768 all_att_num12*self_att_num print(emb_numall_att_num)差距很少因为只是少算了些偏置项还可以再看每一层的参数#每一层的参数 for name,para in bert.named_parameters(): print( name, para.shape )报错shape不用加括号size()才需要括号结果如下开头结尾可以发现其中的pooler层是有参数的所以再重新手动计算参数这次加上pooler层差距又变小了但是不是每个模型的pooler层都有参数1.6.2 分词器将padding设为max_length2. data数据集来源某酒店的评价data.py这个文件中我们要产生的是两个dataloader数据文件中没有训练集和验证集需要我们自己来分在哪里去分get_data_loader():函数中去分这里要用到sklearn这个包strip(\n) 可以去除空格split(,,1) 把这句话按 , 来分割分割次数为1stratify按标签的比例对数据进行分割比如此时比例为0.2如果使用stratify那么可以将各个标签之下的数据都分百分之二十出来作为验证集然后注意一下self.Y我们文本中的1和0都是字符型需要转为整形label [int(i) for i in label]然后转为pytorch中的LongTensordata.py代码如下from torch.utils.data import Dataset,DataLoader import torch from sklearn.model_selection import train_test_split from tqdm import tqdm import numpy as np def read_txt_data(path): label [] data [] with open(path, r, encodingutf-8) as f: for i, line in tqdm(enumerate(f)): if i 0: continue #跳过第一行 line line.strip() line line.split(,,1) #1表示分割次数 label.append(line[0]) data.append(line[1]) print(len(data)) print(len(label)) return data, label class My_jdDataset(Dataset): def __init__(self, data, label): self.X data label [int(i) for i in label] self.Y torch.LongTensor(label) def __len__(self): return len(self.X) def __getitem__(self, idx): return self.X[idx], self.Y[idx] def get_dataloader(path, batch_size, val_size0.2): data,label read_txt_data(path) train_data, val_data, train_label, val_label train_test_split(data, label, test_sizeval_size, shuffleTrue, stratifylabel) train_set My_jdDataset(train_data, train_label) val_set My_jdDataset(val_data, val_label) train_loader DataLoader(train_set, batch_sizebatch_size, shuffleTrue) val_loader DataLoader(val_set, batch_sizebatch_size, shuffleFalse) return train_loader, val_loader if __name__ __main__: get_dataloader(../jiudian.txt,batch_size4)这样最重要的模块就写好了3. model要注意的有两处注意tokenizer的输出包含input_ids, attention_mask, token_type_ids需要分别取出bert层有两个输出一个是原始输出一个是池化输出还要记得return_tensorspt 返回张量return_dictFalse 简化输出代码如下import torch import torch.nn as nn from transformers import BertModel, BertTokenizer class myBertModel(nn.Module): def __init__(self, bert_path, num_class): super(myBertModel, self).__init__() self.bert BertModel.from_pretrained(bert_path) self.cls_heads nn.Linear(768, num_class) self.tokenizer BertTokenizer.from_pretrained(bert_path) def forward(self, text): input self.tokenizer(text , return_tensorspt,truncationTrue, paddingmax_length, max_length128) # 注意tokenizer的输出包含input_ids, attention_mask, token_type_ids需要分别取出 input_ids input[input_ids] attention_mask input[attention_mask] token_type_ids input[token_type_ids] # bert层有两个输出一个是原始输出一个是池化输出 sequence_out, pooled_output self.bert(input_idsinput_ids, attention_maskattention_mask, token_type_idstoken_type_ids, return_dictFalse) out self.cls_heads(pooled_output) pass return out modelmyBertModel(../bert-base-chinese,2) predmodel(今天天气不错)4. train要注意的还是只有两点scheduler.step() 可以动态调整学习率每次优化之后就调整学习率torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) 防止梯度爆炸代码如下import torch import time import matplotlib.pyplot as plt import numpy as np from tqdm import tqdm def train_val(para): ######################################################## model para[model] train_loader para[train_loader] val_loader para[val_loader] scheduler para[scheduler] optimizer para[optimizer] loss para[loss] epoch para[epoch] device para[device] save_path para[save_path] max_acc para[max_acc] val_epoch para[val_epoch] ################################################# plt_train_loss [] plt_train_acc [] plt_val_loss [] plt_val_acc [] val_rel [] for i in range(epoch): start_time time.time() model.train() train_loss 0.0 train_acc 0.0 val_acc 0.0 val_loss 0.0 for batch in tqdm(train_loader): model.zero_grad() text, labels batch[0], batch[1].to(device) pred model(text) bat_loss loss(pred, labels) bat_loss.backward() optimizer.step() scheduler.step() optimizer.zero_grad() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) train_loss bat_loss.item() #.detach 表示去掉梯度 train_acc np.sum(np.argmax(pred.cpu().data.numpy(),axis1) labels.cpu().numpy()) plt_train_loss . append(train_loss/train_loader.dataset.__len__()) plt_train_acc.append(train_acc/train_loader.dataset.__len__()) if i % val_epoch 0: model.eval() with torch.no_grad(): for batch in tqdm(val_loader): val_text, val_labels batch[0], batch[1].to(device) val_pred model(val_text) val_bat_loss loss(val_pred, val_labels) val_loss val_bat_loss.cpu().item() val_acc np.sum(np.argmax(val_pred.cpu().data.numpy(), axis1) val_labels.cpu().numpy()) val_rel.append(val_pred) if val_acc max_acc: torch.save(model, save_pathstr(epoch)ckpt) max_acc val_acc plt_val_loss.append(val_loss/val_loader.dataset.__len__()) plt_val_acc.append(val_acc/val_loader.dataset.__len__()) print([%03d/%03d] %2.2f sec(s) TrainAcc : %3.6f TrainLoss : %3.6f | valAcc: %3.6f valLoss: %3.6f % \ (i, epoch, time.time()-start_time, plt_train_acc[-1], plt_train_loss[-1], plt_val_acc[-1], plt_val_loss[-1]) ) if i % 50 0: torch.save(model, save_path-epoch:str(i) -%.2f%plt_val_acc[-1]) else: plt_val_loss.append(plt_val_loss[-1]) plt_val_acc.append(plt_val_acc[-1]) print([%03d/%03d] %2.2f sec(s) TrainAcc : %3.6f TrainLoss : %3.6f % \ (i, epoch, time.time()-start_time, plt_train_acc[-1], plt_train_loss[-1]) ) plt.plot(plt_train_loss) plt.plot(plt_val_loss) plt.title(loss) plt.legend([train, val]) plt.show() plt.plot(plt_train_acc) plt.plot(plt_val_acc) plt.title(Accuracy) plt.legend([train, val]) plt.savefig(acc.png) plt.show()5. mian这部分用于写超参数和调试运行5.1 超参batchsize4 要小因为文字占显存使用小批量但是又不能太小会引发新的问题epochs5 文字不用太多轮5.2 调试5.2.1 报错pred还在cpu模型也要记得放入gpu报错信息5.2.2 现象val_acc的固定原因1 数据不平衡先修改data让它只读前200张结果valAcc一直为1这是不正常的导致它的原因是我们只取前200张图里面标签全为1所以模型就只会把结果设为一原因2 batchsize太小模型不知道该往哪里走结果模型一直是平的5.2.3 只用架构如果只用大佬们的模型而不用参数结果跳不出数据不平衡的坑因为自己训练的效果太差了对比一下大佬的模型结论就是使用大佬的模型实在是太重要了6. bert代码初始化如下总体结构上的三层经过embedding层16128————》16128768接下来深度分析一下转到BertModel转到BertEncoder转到BertLayer转到BertAttention如下qkv如下第一个token[cls]return_dict False 的原因简化输出作业外卖数据集自己调试bert模型变化暂无

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询