网站怎么上百度,网络营销方式有哪些分类,wordpress 去掉分页,三维动画设计制作公司准备数据集
使用编码工具
首先需要加载编码工具#xff0c;编码工具可以将抽象的文字转成数字#xff0c;便于神经网络后续的处理#xff0c;其代码如下#xff1a;
# 定义数据集
from transformers import BertTokenizer, BertModel, AdamW
# 加载tokenizer
token Ber…准备数据集
使用编码工具
首先需要加载编码工具编码工具可以将抽象的文字转成数字便于神经网络后续的处理其代码如下
# 定义数据集
from transformers import BertTokenizer, BertModel, AdamW
# 加载tokenizer
token BertTokenizer.from_pretrained(bert-base-chinese)
print(token, token)out: token BertTokenizer(name_or_path‘bert-base-chinese’, vocab_size21128, model_max_length512, is_fastFalse, padding_side‘right’, truncation_side‘right’, special_tokens{‘unk_token’: ‘[UNK]’, ‘sep_token’: ‘[SEP]’, ‘pad_token’: ‘[PAD]’, ‘cls_token’: ‘[CLS]’, ‘mask_token’: ‘[MASK]’}, clean_up_tokenization_spacesTrue), added_tokens_decoder{ 0: AddedToken(“[PAD]”, rstripFalse, lstripFalse, single_wordFalse, normalizedFalse, specialTrue), 100: AddedToken(“[UNK]”, rstripFalse, lstripFalse, single_wordFalse, normalizedFalse, specialTrue), 101: AddedToken(“[CLS]”, rstripFalse, lstripFalse, single_wordFalse, normalizedFalse, specialTrue), 102: AddedToken(“[SEP]”, rstripFalse, lstripFalse, single_wordFalse, normalizedFalse, specialTrue), 103: AddedToken(“[MASK]”, rstripFalse, lstripFalse, single_wordFalse, normalizedFalse, specialTrue), } 由上可知bert-base-chinese模型的字典中共有21128个词编码器编码句子的最大长度为512个词并且能够看到bert-base-chinese模型所使用的一些特殊符号例如SEK,PAD等。
这里使用的编码工具是bert-base-chinese编码工具和预训练模型往往是成对使用的后续将使用同名的预训练语言模型作为backbone。
编码工具的试算
加载完成编码工具之后可以进行一次试算观察编码工具的输入和输出代码如下
data token.batch_encode_plus(batch_text_or_text_pairs[关注博主不迷路。,俺要带你上高速。], truncationTrue,paddingmax_length,max_length12,return_tensorspt,return_lengthTrue)
# 查看编码输出
for k,v in out.items():print(k,v.shape)
# 把编码还原成句子
print(token.decode(out[input_ids][0]))out: input_ids torch.Size([2, 17]) token_type_ids torch.Size([2, 17]) length torch.Size([2]) attention_mask torch.Size([2, 17]) [CLS] 关 注 博 主 不 迷 路 。 [SEP] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [CLS] 俺 要 带 你 上 高 速 。 [SEP] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] 编码工具的参数说明
对于编码工具的使用特别是参数值的含义可以参考下面的两段代码
使用简单的编码
# 编码两个句子
out tokenizer.encode(# 句子1text sents[0],text_pair sents[1],# 当句子长度大于max_length时进行截断truncationTrue,# 一律补充pad到max_length长度padding max_length,add_special_tokens True,# 许多大模型的阶段也是使用512作为最终的max_lengthmax_length30,return_tensorsNone,
)增强的编码函数
# 增强的编码函数
out tokenizer.encode_plus(text sents[0],text_pair sents[1],#当句子长度大于max_length时进行截断操作truncation True,#一律补零到max_length长度paddingmax_length,max_length30,add_special_tokensTrue,#可以取值tf,pt,np,默认返回list---tensorflow,pytorch,numpyreturn_tensorsNone,#返回token_type_idsreturn_token_type_idsTrue,#返回attention_maskreturn_attention_maskTrue,#返回special_tokens_mask 特殊符号标识return_special_tokens_maskTrue,#返回offset_mapping标识每个词的起始和结束位置---》这个参数只能BertTokenizerFast使用#return_offsets_mappingTrue,#返回length 标识长度return_lengthTrue
)从上面的代码中的参数max_length500可以看出经过编码后的句子的长度一定是12个词的长度。如果源句子超出则会进行截断如果源句子不足则会进行填充PAD其运行结果如下
{input_ids: tensor([[ 101, 1068, 3800, 1300, 712, 8024, 679, 6837, 6662, 511, 102, 0],[ 101, 939, 6206, 2372, 872, 677, 7770, 6862, 511, 102, 0, 0]]), token_type_ids: tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), length: tensor([11, 10]), attention_mask: tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0]])}
input_ids torch.Size([2, 12])
token_type_ids torch.Size([2, 12])
length torch.Size([2])
attention_mask torch.Size([2, 12])
[CLS] 关 注 博 主 不 迷 路 。 [SEP] [PAD]
[CLS] 俺 要 带 你 上 高 速 。 [SEP] [PAD] [PAD]编码工具首先是对一条完整的句子进行了tokenizer把句子分成了一个个token。同时对于不同的编码工具分词的结果也不一定一致。这里采用的bert-base-chinese编码工具中它是以字为词即把每个字当做一个词进行处理。 这些编码的结果对于预训练模型的计算十分重要在后面将会使用编码器将所有的句子进行编码用于输入到预训练模型中进行计算。
定义数据集
这里使用的数据集为ChnSentiCorp数据集Dataset类如下
# import torch
from datasets import load_dataset
class Dataset(torch.utils.data.Dataset):def __init__(self, split):self.dataset load_dataset(pathlansinuote/ChnSentiCorp, splitsplit)def __len__(self):return len(self.dataset)def __getitem__(self, i):text self.dataset[i][text]label self.dataset[i][label]return text, label
dataset Dataset(train)
print(len(dataset))
print(dataset[0])在上述代码中加载了ChnSentiCorp数据集并使用Pytorch中的Dataset对象进行封装利用__getitem__()得到每一条数据每条数据中包含text和labels两个字段最后初始化训练数据集并查看训练数据集的长度和第一条数据样例。
out: 9600
(选择珠江花园的原因就是方便有电动扶梯直接到达海边周围餐馆、食廊、商场、超市、摊位一应俱全。酒店装修一般但还算整洁。 泳池在大堂的屋顶因此很小不过女儿倒是喜欢。 包的早餐是西式的还算丰富。 服务吗一般, 1)由上面的输出可知训练数据集包括9600条数据每条数据包含一条评论文本和一个标识表明这一条评论是好评还是差评。注意这里的数据集是单纯的原始文本数据并没有进行编码。
定义计算设备
这里将使用CUDA作为计算设备这样可以极大加速模型的训练和测试的过程代码如下
device cpu
if torch.cuda.is_available():device CUDA
print(选用的计算设备,device)在该段代码中默认使用CPU进行计算如果存在CUDA的话则选用CUDA作为计算设备。
定义数据整理函数
正如上面所述的那样ChnSentiCorp数据集中的每一条数据是抽象的文本数据并没有进行任何的编码操作而预训练模型是需要编码之后的数据才能进行计算所以需要一个将文本句子转成编码的过程。 另外在训练模型时数据集往往很大如果一条一条地处理则效率会太低在现实中我们往往一批一批地处理数据这样可以快速地处理数据集同时从梯度下降的角度来讲批数据的梯度方差相较于一条条数据的梯度小可以让模型更加稳定地更新参数。
# 定义批处理函数
def collate_fn(data):sents [i[0] for i in data]labels [i[1] for i in data]# 编码data token.batch_encode_plus(batch_text_or_text_pairssents, truncationTrue,paddingmax_length,max_length500,return_tensorspt,return_lengthTrue)# input_ids:编码之后的数字# attention_masks:补0的位置都是0其他位置都是1input_ids data[input_ids]attention_mask data[attention_mask]token_type_ids data[token_type_ids]labels torch.LongTensor(labels)# print(data[length],data[length].max())return input_ids, attention_mask, token_type_ids, labels
在这段代码中参数data表示一批数据取出其中的句子和标识它们都是list类型在上述代码中会将两者分别赋给sents和labels然后是使用编码器编码该批句子在参数中将编码后的结果指定为固定的500个词的大小与上面的例子同理超出500个词的部分会被截断这里是通过truncationTrue控制同时少于500个词的句子会被[PAD]填充这里主要是通过 paddingmax_length控制。另外在编码过程中通过 return_tensorspt参数将编码后的结果返回torch中的tensor类型免去了后面转换数据格式的麻烦也就是说后面可以通过数据格式转换可以将‘tf’转成‘pt’格式。 之后取出编码后的结果并将labels也转成Pytorch中的Tensor格式再把它们移动到之前已经定义好的计算设备device上最后把这些数据全部返回到这里数据整理函数的工作已经全部完成。
数据处理函数的例子
上述定义了数据处理函数为了实验其效果也可使用下面的例子本用例已加狗头保命~
data [(选择新大的原因当然不是为了延毕。,1),(笔记本的内存确实小。,0),(宿舍没有风扇。其他都很好。,1),(今天才知道这本书还有第10000卷,真是太屌了。,1),(机器的背面似乎被撕了张什么标签残胶还在。,0),(为什么有人在校园里尖叫是疯了还是giao。,0)
]# 狗头保命版试算
input_ids,attention_mask,token_type_ids,labels collate_fn(data)
print(input_ids.shape,input_ids.shape)
print(attention_mask.shape,attention_mask.shape)
print(token_type_ids.shape,token_type_ids.shape)
print(labels:,labels)在该段代码中首先是模拟了一批数据这批数据中包含4个句子通过将该批数据输入到整理函数以后运行结果如下
input_ids.shape torch.Size([6, 500])
attention_mask.shape torch.Size([6, 500])
token_type_ids.shape torch.Size([6, 500])
labels: tensor([1, 0, 1, 1, 0, 0])可见编码之后的结果都是确定的500个词的长度并且每个结果都会被移动到可用的计算设备上这样可以方便后续的计算。
定义数据加载器
上述代码中定义了数据集和数据整理函数以后下面我们将定义一个数据加载器DataLoader它可以使用数据整理函数来完成成批地处理数据集中的数据通俗来讲每一批的数据我们可以称为batch。
# 定义数据加载器并查看数据样例
loader torch.utils.data.DataLoader(datasetdataset, batch_size16,collate_fncollate_fn,shuffleTrue,drop_lastTrue)对于上述代码我们使用了Pytorch提供的工具类定义数据集加载器其参数说明可参考下图
数据加载器的例子
为了更好地使用数据加载器这里我们查看一批数据样例将这批数据输入到数据加载器中可以发现其结果会与数据整理函数的运行结果相似只不过是句子的数量增多了。
上述代码依次打印了加载器中批次数目、加载器中输入数据的input_ids和掩蔽注意力的形状 attention_mask_shape、词元的ids类型形状token_type_ids_shape以及标签labels
for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader):break
print(len(loader))
print(input_ids, input_ids)
print(attention_mask_shape, attention_mask.shape)
print(token_type_ids_shape, token_type_ids.shape)
print(labels, labels)input_ids 就是编码后的词token_type_ids 第一个句子和特殊符号的位置是0第二个句子的位置是1attention_mask pad的位置是0其他位置都是1special_tokens_mask 特殊符号的位置是1其他位置都是0
定义模型
因为我们是要利用Huggingface的预训练语言模型所以需要做两件事情加载预训练模型PLM以及定义下游任务模型。
加载预训练模型
这里使用的BERT预训练模型模型名称为bert-base-chinese这里的名称和编码器的名称是一致的因为往往模型和其编码工具配套使用。另外BERT模型不是必须的模型进行中文情感分类也可以使用其他支持中文的模型例如BART等。
from transformers import BertModel
# 加载预训练模型
pretrained BertModel.from_pretrained(bert-base-chinese)
# 统计参数量
sum(i.numel() for i in pretrained.parameters()) / 10000out:10226.7648由上可知bert-base-chinese模型的参数量超过1亿个这个模型的体量还是比较大的。由于它的体量比较大所以如果要训练它对计算资源的要求较高而对于本次的二分类任务则可以选择不训练它只是作为一个特征提取器。这样就可以避免训练这个笨重的模型不需要计算它的梯度进而不更新它的参数所以需要冻结它的参数
# 当不进行训练时不需要计算梯度
for param in pretrained.parameters():param.requires_grad_(False)定义好PLM后需要进行一次试算观察模型的输入和输出结果
# 设定计算设备
pretrained.to(device)
# 模型试算
out pretrained(input_idsinput_ids, attention_maskattention_mask, token_type_idstoken_type_ids)
print(out.last_hidden_state.shape)这里可能会报错 RuntimeError: Expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu! (when checking argument for argument index in method wrapper_CUDA__index_select) 这是因为之前我们忘了将input_ids等参数放到cuda上所以需要改一下代码
for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader):break
input_ids input_ids.to(device)
attention_mask attention_mask.to(device)
token_type_ids token_type_ids.to(device)
labels labels.to(device)
print(len(loader))
print(input_ids, input_ids)
print(attention_mask_shape, attention_mask.shape)
print(token_type_ids_shape, token_type_ids.shape)
print(labels, labels)
print(input_ids.is_cuda)这里如果有显卡的话会输出True然后上面的程序报错也会消除同时输出 torch.Size([16, 500, 768]) 上述代码中将我们的16个样例数据在定义数据加载器部分输入到预训练模型中得到的计算结果和我们预想的是一致的。首先从第一个维度16可以看出是和我们的样例输入的句子数量有关的随后的500是指每句话中包含了500个单词因为max_length500(在数据整理函数中定义)这就把之前所有的内容都串起来了。最后的768表示将每一个词抽成一个768维的向量。到此为止我们已经通过预训练模型成功地把16句话转换成为了一个特征向量矩阵这样就可以接入下面的下游任务模型做分类或者回归任务。
定义下游任务模型
下游任务模型的任务是对backbone抽取的特征进行进一步的计算得到符合业务需求的计算结果这里做的是一个二分类的结果因为我们数据集中的labels只有两种。
# 定义下游任务模型
class Model(torch.nn.Module):def __init__(self):super(Model, self).__init__()self.fc torch.nn.Linear(768, 2).to(device)def forward(self, input_ids, attention_mask, token_type_ids):with torch.no_grad():out pretrained(input_idsinput_ids,attention_maskattention_mask,token_type_idstoken_type_ids)out self.fc(out.last_hidden_state[:, 0])out out.softmax(dim1)return outmodel Model()
# 设置计算设备
model.to(device)在这段代码中定义了一个下游任务模型该模型包括一个全连接的LinearModel权重矩阵是768x2所以它能够将一个768维度的向量转换成一个二维空间中。 上述的下游任务模型的计算流程为 这里之所以丢弃后面466个词的特征是因为BERT模型所致具体的内容可以参考BERT模型的论文BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
下游任务模型试算
在最后我们使用刚才使用的batch16的数据进行试算
# 试算
print(model(input_idsinput_ids,attention_maskattention_mask,token_type_idstoken_type_ids).shape)其输出结果为 torch.Size([16, 2]) 由此可见这就是我们一开始所要求的16句话进行二分类的结果。
训练和测试
模型训练
模型定义完成之后我们就可以对该模型进行训练了~代码如下
from transformers import AdamW
from transformers.optimization import get_scheduler
def train():# 定义优化器optimizer torch.optim.AdamW(model.parameters(), lr5e-4)# 定义损失函数criterion torch.nn.CrossEntropyLoss()# 定义学习率调节器scheduler get_scheduler(namelinear,num_warmup_steps0,num_training_stepslen(loader),optimizeroptimizer)# 将模型切换到训练模式model.train()# 按批次进行遍历训练数据集中的数据for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader):input_ids input_ids.to(device)attention_mask attention_mask.to(device)token_type_ids token_type_ids.to(device)labels labels.to(device)# 模型计算out model(input_idsinput_ids, attention_maskattention_mask, token_type_idstoken_type_ids).to(device)# 计算损失并使用梯度下降法优化模型参数loss criterion(out, labels)loss.backward()optimizer.step()scheduler.step()optimizer.zero_grad()# 输出各项数据的情况便于观察if i % 10 0:out out.argmax(dim1)accuracy (out labels).sum().item() / len(labels)lr optimizer.state_dict()[param_groups][0][lr]print(i, loss.item(),lr, accuracy)
train()在上述的代码中首先定义了优化器、损失函数、学习率调节器。其中优化器使用了HuggingFace提供的AdamW优化器这是传统的Adam优化器的改进版本在自然语言处理任务中该优化器往往取得了比Adam优化器更加好的成绩并且计算效率高。学习率调节器使用了HuggingFace提供的线性学习率调节器它能够在训练过程中让学习率缓慢下降而不是始终使用一致的学习率因为在训练的后期阶段往往需要更小的学习率来微调参数有利于损失函数下降到最低点。这里的损失函数采用了分类任务中常用的CrossEntropyLoss交叉熵损失函数。 然后将下游任务模型切换到训练模式即可开始训练。最后每当优化10次模型参数时就计算一次当前模型预测结果的正确率并输出模型的损失函数、学习率最终训练完毕的结果如下所示 由上图可见在训练到大约200个steps时模型已经能够达到85%左右的正确率损失函数也如同预期一样随着训练过程不断下降学习率亦如此。
模型测试
对于已经训练好的模型进行测试以便验证训练的有效性其测试代码如下
def test():# 定义测试数据加载器loader_test torch.utils.data.DataLoader(dataset Dataset(test),batch_size 32,collate_fn collate_fn,shuffle True,drop_last True)# 将下游任务模型切换到运行模式model.eval()correct 0total 0for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader_test):# 计算5个批次即可不需要全部遍历input_ids input_ids.to(device)attention_mask attention_mask.to(device)token_type_ids token_type_ids.to(device) labels labels.to(device) if i 5:breakprint(i)# 计算with torch.no_grad():out model(input_idsinput_ids,attention_maskattention_mask,token_type_idstoken_type_ids).to(device)# 统计正确率out out.argmax(dim1)correct (outlabels).sum().item()total len(labels)print(correct/total)test()在上述的代码中首先定义了测试数据集及其加载器同时取出5个批次的数据让模型进行预测最后将统计的正确率输出运行结果为
0
1
2
3
4
0.88125最终模型取得了88.125%的正确率这个正确率虽然不是很高但是验证了下游任务模型即使在不训练basebone的情况下也可以达到一定的成绩。
省流版-全部代码 总结
本文通过一个情感分类的例子说明了使用BERT预训练模型抽取文本特征数据的方法使用BERT作为backbone相对于传统的RNN而言其计算量会稍稍大一些但是BERT抽取的文本特征将更加完整更容易被下游任务模型识别总结出数据之间的规律。所以在不对BERT预训练模型进行训练而是简单应用于下游任务时也可以表现一个比较好的结果。