Coggle 30 Days of ML(23年7月)任务九:学会Bert基础,transformer库基础使用

news2024/11/30 0:36:24

Coggle 30 Days of ML(23年7月)任务九:学会Bert基础,transformer库基础使用

任务九:学会Bert基础,transformer库基础使用

  • 说明:在这个任务中,你将学习Bert模型的基础知识,并了解transformer库的基本使用方法,transformer库提供了Bert模型的实现。
  • 实践步骤:
    1. 学习Bert模型的原理和架构。
    2. 了解transformer库的基本使用方法,包括Bert模型的初始化、输入编码和特征提取等操作。

Bert模型

训练目标

BERT使用了维基百科等语料库数据,共几十GB,这是一个庞大的语料库。对于一个GB级的语料库,雇佣人力进行标注成本极高。BERT使用了两个巧妙方法来无监督地训练模型:Masked Language Modeling和Next Sentence Prediction。这两个方法可以无需花费时间和人力标注数据,以较低成本无监督地得到训练数据。图1就是一个输入输出样例。

对于Masked Language Modeling,给定一些输入句子(图1中最下面的输入层),BERT将输入句子中的一些单词盖住(图1中Masked层),经过中间的词向量和BERT层后,BERT的目标是让模型能够预测那些刚刚被盖住的词。还记得英语考试中,我们经常遇到“完形填空”题型吗?能把完形填空做对,说明已经理解了文章背后的语言逻辑。BERT的Masked Language Modeling本质上就是在做“完形填空”:预训练时,先将一部分词随机地盖住,经过模型的拟合,如果能够很好地预测那些盖住的词,模型就学到了文本的内在逻辑。

img

除了“完形填空”,BERT还需要做Next Sentence Prediction任务:预测句子B是否为句子A的下一句。Next Sentence Prediction有点像英语考试中的“段落排序”题,只不过简化到只考虑两句话。如果模型无法正确地基于当前句子预测Next Sentence,而是生硬地把两个不相关的句子拼到一起,两个句子在语义上是毫不相关的,说明模型没有读懂文本背后的意思。

词向量

在基于深度学习的NLP方法中,文本中的词通常都用一维向量来表示。某两个词向量的 Cosine 距离较小,说明两个词在语义上相似。

词向量一般由Token转换而成。英文中,一个句子中的词由空格、句号等标点隔开,我们很容易从句子中获得词。英文的词通常有前缀、后缀、词根等,在获得英文的词后,还需要抽出词根,比如图1所展示的,将“playing”切分为“play”和“##ing”。如果不对英文词进行类似词根抽取,词表过大,不容易拟合。对于英文,“play”和“##ing”分别对应两个Token。

中文一般由多个字组成一个词,传统的中文文本任务通常使用一些分词工具,得到严格意义上的词。在原始的BERT中,对于中文,并没有使用分词工具,而是直接以字为粒度得到词向量的。所以,原始的中文BERT(bert-base-chinese)输入到BERT模型的是字向量,Token就是字。后续有专门的研究去探讨,是否应该对中文进行必要的分词,以词的形式进行切分,得到向量放入BERT模型。

为了方面说明,本文不明确区分字向量还是词向量,都统称为词向量。

我们首先需要将文本中每个Token都转换成一维词向量。假如词向量的维度为hidden_size,句子的Token长度为seq_len,或者说句子共包含seq_len个Token,那么上图中,输入就是seq_len * hidden_size。再加上batch_size,那么输入就是batch_size * seq_len * hidden_size。上图只展示了一个样本,未体现出batch_size,或者可以理解成batch_size = 1,即每次只处理一条文本。为便于理解,本文的图解中不考虑batch_size这个维度,实际模型训练时,batch_size通常大于1。

词向量经过BERT模型一系列复杂的转换后,模型最后仍然以词向量的形式输出,用以对文本进行语义表示。输入的词向量是seq_len * hidden_size,句子共seq_len个Token,将每个Token都转换成词向量,送入BERT模型。经过BERT模型后,得到的输出仍然是seq_len * hidden_size维度。输出仍然是seq_len的长度,其中输出的i 个位置(0 < i < seq_len)的词向量,表示经过了拟合后的第i个Token的语义表示。后续可以用输出中每个位置的词向量来进行一些其他任务,比如命名实体识别等。

除了使用Masked方法故意盖住一些词外,BERT还加了一些特殊的符号:[CLS][SEP][CLS]用在句首,是句子序列中i = 0位置的Token。BERT认为输出序列的i = 0位置的Token对应的词向量包含了整个句子的信息,可对整个句子进行分类。[SEP]用在分割前后两个句子上。

微调

经过预训练后,得到的模型可以用来微调各类任务。

  • 单文本分类任务。刚才提到,BERT模型在文本前插入一个[CLS]符号,并将该符号对应的输出向量作为整篇文本的语义表示,用于文本分类,如图2所示。对于[CLS]符号,可以理解为:与文本中已有的其它字/词相比,这个无明显语义信息的符号会更“公平”地融合文本中各个字/词的语义信息。

img

  • 语句对分类任务。语句对分类任务的实际应用场景包括:问答(判断一个问题与一个答案是否匹配)、语句匹配(两句话是否表达同一个意思)等。对于该任务,BERT模型除了添加[CLS]符号并将对应的输出作为文本的语义表示,输入两句话之间用[SEP]符号作分割。

img

  • 序列标注任务。序列标注任务的实际应用场景包括:命名实体识别、中文分词、新词发现(标注每个字是词的首字、中间字或末字)、答案抽取(答案的起止位置)等。对于该任务,BERT模型利用文本中每个Token对应的输出向量对该Token进行标注(分类),如下图所示(B(Begin)、I(Inside)、E(End)分别表示一个词的第一个字、中间字和最后一个字)。

img

模型结构

Transformer是BERT的核心模块,Attention注意力机制又是Transformer中最关键的部分。这两篇文章介绍了Attention注意力机制和Transformer,这里不再赘述。BERT用到的主要是Transformer的Encoder,没有使用Transformer Decoder。

把多个Transformer Encoder组装起来,就构成了BERT。在论文中,作者分别用12个和24个Transformer Encoder组装了两套BERT模型,两套模型的参数总数分别为110M和340M。

img

HuggingFace Transformers

使用BERT和其他各类Transformer模型,绕不开HuggingFaceopen in new window提供的Transformers生态。HuggingFace提供了各类BERT的API(transformers库)、训练好的模型(HuggingFace Hub)还有数据集(datasets)。最初,HuggingFace用PyTorch实现了BERT,并提供了预训练的模型,后来。越来越多的人直接使用HuggingFace提供好的模型进行微调,将自己的模型共享到HuggingFace社区。HuggingFace的社区越来越庞大,不仅覆盖了PyTorch版,还提供TensorFlow版,主流的预训练模型都会提交到HuggingFace社区,供其他人使用。

使用transformers库进行微调,主要包括:

  • Tokenizer:使用提供好的Tokenizer对原始文本处理,得到Token序列;
  • 构建模型:在提供好的模型结构上,增加下游任务所需预测接口,构建所需模型;
  • 微调:将Token序列送入构建的模型,进行训练。

安裝库

首先需要安装transformer

pip install transformer

BertTokenizer

BertTokenizer是用于Bert模型的分词器,它包含了Bert模型的词典。在进行词向量化之前,我们需要将文本映射到词典中的对应序号,以获取相应的词向量。

下面两行代码会创建 BertTokenizer,并将所需的词表加载进来。首次使用这个模型时,transformers 会帮我们将模型从HuggingFace Hub下载到本地。

>>> from transformers import BertTokenizer
>>> tokenizer = BertTokenizer.from_pretrained('bert-base-cased')

用得到的tokenizer进行分词:

>>> encoded_input = tokenizer("我是一句话")
>>> print(encoded_input)
{'input_ids': [101, 2769, 3221, 671, 1368, 6413, 102], 
'token_type_ids': [0, 0, 0, 0, 0, 0, 0], 
'attention_mask': [1, 1, 1, 1, 1, 1, 1]}

得到的一个Python dict。其中,input_ids最容易理解,它表示的是句子中的每个Token在词表中的索引数字。词表(Vocabulary)是一个Token到索引数字的映射。可以使用decode()方法,将索引数字转换为Token。

>>> tokenizer.decode(encoded_input["input_ids"])
'[CLS] 我 是 一 句 话 [SEP]'

可以看到,BertTokenizer在给原始文本处理时,自动给文本加上了[CLS][SEP]这两个符号,分别对应在词表中的索引数字为101和102。decode()之后,也将这两个符号反向解析出来了。

token_type_ids主要用于句子对,比如下面的例子,两个句子通过[SEP]分割,0表示Token对应的input_ids属于第一个句子,1表示Token对应的input_ids属于第二个句子。不是所有的模型和场景都用得上token_type_ids

>>> encoded_input = tokenizer("您贵姓?", "免贵姓李")
>>> print(encoded_input)
{'input_ids': [101, 2644, 6586, 1998, 136, 102, 1048, 6586, 1998, 3330, 102], 
'token_type_ids': [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1], 
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}

句子通常是变长的,多个句子组成一个Batch时,attention_mask就起了至关重要的作用。

>>> batch_sentences = ["我是一句话", "我是另一句话", "我是最后一句话"]
>>> batch = tokenizer(batch_sentences, padding=True, return_tensors="pt")
>>> print(batch)
{'input_ids': 
 tensor([[ 101, 2769, 3221,  671, 1368, 6413,  102,    0,    0],
        [ 101, 2769, 3221, 1369,  671, 1368, 6413,  102,    0],
        [ 101, 2769, 3221, 3297, 1400,  671, 1368, 6413,  102]]), 
 '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, 0, 0, 0]]), 
 'attention_mask': 
 tensor([[1, 1, 1, 1, 1, 1, 1, 0, 0],
        [1, 1, 1, 1, 1, 1, 1, 1, 0],
        [1, 1, 1, 1, 1, 1, 1, 1, 1]])}

对于这种batch_size = 3的场景,不同句子的长度是不同的,padding=True表示短句子的结尾会被填充[PAD]符号,return_tensors="pt"表示返回PyTorch格式的Tensorattention_mask告诉模型,哪些Token需要被模型关注而加入到模型训练中,哪些Token是被填充进去的无意义的符号,模型无需关注

BertModel

下面两行代码会创建BertModel,并将所需的模型参数加载进来。

>>> from transformers import BertModel
>>> model = BertModel.from_pretrained("bert-base-chinese")

BertModel是一个PyTorch中用来包裹网络结构的torch.nn.ModuleBertModel里有forward()方法,forward()方法中实现了将Token转化为词向量,再将词向量进行多层的Transformer Encoder的复杂变换。

forward()方法的入参有input_idsattention_masktoken_type_ids等等,这些参数基本上是刚才Tokenizer部分的输出。

>>> bert_output = model(input_ids=batch['input_ids'])

forward()方法返回模型预测的结果,返回结果是一个tuple(torch.FloatTensor),即多个Tensor组成的tupletuple默认返回两个重要的Tensor

>>> len(bert_output)
2
  • last_hidden_state:输出序列每个位置的语义向量,形状为:(batch_size, sequence_length, hidden_size)。
  • pooler_output[CLS]符号对应的语义向量,经过了全连接层和tanh激活;该向量可用于下游分类任务。

下游任务

BERT可以进行很多下游任务,transformers库中实现了一些下游任务,我们也可以参考transformers中的实现,来做自己想做的任务。比如单文本分类,transformers库提供了BertForSequenceClassification类。

class BertForSequenceClassification(BertPreTrainedModel):
    def __init__(self, config):
        super().__init__(config)
        self.num_labels = config.num_labels
        self.config = config

        self.bert = BertModel(config)
        classifier_dropout = ...
        self.dropout = nn.Dropout(classifier_dropout)
        self.classifier = nn.Linear(config.hidden_size, config.num_labels)

        ...
        
    def forward(
        ...
    ):
        ...

        outputs = self.bert(...)
        pooled_output = outputs[1]
        pooled_output = self.dropout(pooled_output)
        logits = self.classifier(pooled_output)

        ...

在这段代码中,BertForSequenceClassificationBertModel基础上,增加了nn.Dropoutnn.Linear层,在预测时,将BertModel的输出放入nn.Linear,完成一个分类任务。除了BertForSequenceClassification,还有BertForQuestionAnswering用于问答,BertForTokenClassification用于序列标注,比如命名实体识别。

transformers 中的各个API还有很多其他参数设置,比如得到每一层Transformer Encoder的输出等等,可以访问他们的文档open in new window查看使用方法。

一词多义

import torch
from transformers import BertTokenizer, BertModel
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained("bert-base-uncased")
input_ids = torch.tensor(tokenizer.encode("good night")).unsqueeze(0)  # Batch size 1
input_ids2 = torch.tensor(tokenizer.encode("good food")).unsqueeze(0)  # Batch size 1
outputs = model(input_ids)
outputs2 = model(input_ids2)
print(outputs[0][0][1]) # 取出good night 中的 good
print(outputs2[0][0][1]) # 取出good food 中的 good

看的出来,同一个词,在不同的句子中词向量是不同的。
因此bert能够很好的解决一次多义的现象,这便是它的魅力所在

参考

  • 基于transformers的自然语言处理(NLP)入门
  • BERT

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/741281.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【使用字符串转换时间问题?】Tue Jul 11 23:59:59 CST 2023

问题展示&#xff1a;想要去除多余显示只显示&#xff08;时分秒&#xff1a;23:59:59&#xff09; 解决办法&#xff1a; 问题解决 实现代码&#xff1a; String dateString "2023-07-11 23:59:59";SimpleDateFormat inputFormat new SimpleDateFormat("…

异常处理一例

1.现象 代码片段&#xff1a; uint8_t CmdListener(char c) { #define CMD_SIZE 5static uint8_t cmdQueue[9];static uint8_t cmdReset[] { !, b, o, o, t};static uint8_t cmdYModem[] { 0x01, 0x00, 0xff };static uint8_t cmdIdx 0;int i;xlog("%c", 0xcc);…

【LeetCode】HOT 100(27)

题单介绍&#xff1a; 精选 100 道力扣&#xff08;LeetCode&#xff09;上最热门的题目&#xff0c;适合初识算法与数据结构的新手和想要在短时间内高效提升的人&#xff0c;熟练掌握这 100 道题&#xff0c;你就已经具备了在代码世界通行的基本能力。 目录 题单介绍&#…

【Linux】ELK 企业级日志分析系统

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 ELK 企业级日志分析系统 ELK 概述1、ELK 简介2、为什么要使用 ELK3、完整日志系统基本特征4、ELK 的工作原理 ELK Elasticsearch 集群部署&#xff08;在Node1、Node2节点上操…

stm32(串口知识点)

HAL串口发送/接收函数&#xff1a; HAL_UART_Transmit(); 串口发送数据&#xff0c;使用超时管理机制HAL_UART_Receive(); 串口接收数据&#xff0c;使用超时管理机制HAL_UART_Transmit_IT(); 串口中断模式发送 HAL_UART_Receive_IT(); 串口中断模式接收 HAL_UART_Transmit(…

ByteBuddy学习笔记

ByteBuddy 1.ByteBuddy的用途 ByteBuddy通过修改字节码来新增、修改、删除Java类的现有功能&#xff0c;主要用于分离功能代码和非功能代码&#xff0c;比如 比如非功能代码如下&#xff1a; public double calculatePrice(){double discount getDiscount();double price …

【python手写算法】利用梯度下降实现线性拟合

利用梯度下降实现线性拟合&#xff0c;效果和sklearn LinearRegression()差不多。 学习率初始设置为0.1结果算高的&#xff0c;直接让我的参数变成了nan。&#xff08;体会到了飞出去的感觉&#xff09; 把学习率调小了之后就正常了 # codingutf-8 import matplotlib.pyplot a…

Android多渠道打包及资源指定

多渠道打包及资源指定 由于项目涉及多个车型&#xff0c;使用的是同一个base代码&#xff0c;不同车型都有差分项&#xff0c;所以需要进行多渠道打包&#xff0c;编译不同的资源进行编译处理 一、多渠道打包方式 productFlavor 背景 Android默认提供了Gradle插件库 class…

协同套件——“ 船 ”新版本

空地协同套件自前段时间推出后&#xff0c;受到了很多开发者的关注&#xff0c;不少开发者均表示对跨域机器人协同工作非常感兴趣&#xff0c;这也加快了我们协同套件的另一块拼图-船机协同的研发进度。近期&#xff0c;我们海空协同套件也顺利完成开发测试&#xff0c;本期将给…

基于炬芯3019 SDK数字助听器平台驱动设计与算法实现

+v hezkz17进数字音频系统答疑裙 1 针对数字助听器进行音频信号处理,达到助听功能的需求分析,使用三种语音处理算法。 三种语音处理算法包括:自动增益控制算法、移频算法以及宽动态范围压缩算法。 通过分析三种算法的基本原理,将算法分别使用MATLAB 进行仿真实现,验证算法…

LRU缓存替换策略及C#实现

LRU缓存替换策略 缓存是一种非常常见的设计&#xff0c;通过将数据缓存到访问速度更快的存储设备中&#xff0c;来提高数据的访问速度&#xff0c;如内存、CPU缓存、硬盘缓存等。 但与缓存的高速相对的是&#xff0c;缓存的成本较高&#xff0c;因此容量往往是有限的&#xf…

sqlite3交叉编译

1、交叉编译sqllite3可以先从官网下载最新最新的源码进行编译。sqlite3下载sqlite3有两种版本的源代码&#xff0c;sqlite-amalgamation-3420000.zip这种是将所有的操作放到sqlite3中进行使用的。虽然官方推荐使用这种方法。但是对于嵌入式移植还是使用sqlite-autoconf-3420000…

23款奔驰GLE450动感型升级柏林之声音响系统,体验不一样的感觉

奔驰GLE450动感型升级柏林之声的音响效果自然非同凡响&#xff0c;在人声、交响乐音乐厅感受方面都有非常逼真的现场感受&#xff0c;结合柏林之声的界面调整&#xff0c;可以在不同方位体验的高保真的音乐之享&#xff01; 小柏林音响总共13个喇叭1台功放由4个高音、4个中音、…

Linux 创建文件的12种方法总结

在Linux中&#xff0c;可以使用多种方法来创建文件。以下是一些常见的方法&#xff1a; 1. touch命令 touch filename&#xff0c;用于创建一个空文件。如果文件已存在&#xff0c;则只更新其访问时间和修改时间。 touch 命令通常用于将文件的访问和修改时间更新为当前时间。…

基于知识蒸馏的去雪、去雾、去雨算法

今天来详细学习一篇去雪、去雨、去雾三合一的去噪算法 代码地址&#xff1a; https://github.com/fingerk28/Two-stage-Knowledge-For-Multiple-Adverse-Weather-Removal 论文地址&#xff1a; https://openaccess.thecvf.com/content/CVPR2022/papers/Chen_Learning_Multiple_…

Python(二):Python简介

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

ELFK——ELK结合filebeat日志分析系统(纵使生活万般磨难,也要笑对生活)

文章目录 一、filebeat二、ELFK1.原理简介 三、部署FilebeatELK1.解压安装2.设置 filebeat 的主配置文件3.启动 filebeat4&#xff0e;在 Logstash 组件所在节点上新建一个 Logstash 配置文件5&#xff0e;测试 一、filebeat Filebeat&#xff0c;轻量级的开源日志文件数据搜集…

高精度电流源怎么用

高精度电流源是一种用于产生高精度、高稳定性和低噪声的直流或交流电流信号的设备。它主要应用于各种实验和测试领域&#xff0c;例如半导体器件测试、传感器校准、精密测量和医疗检测等。高精度电流源的作用是提供可靠的电流输出信号&#xff0c;在实验和测试中获得精确和准确…

聚焦地下停车场污染死角|气体检测仪让您一目了然

由于地下停车场属于封闭式或半封闭式建筑&#xff0c;近年来越来越多高端住宅、办公楼宇的物业管理者收到投诉反应地下停车场的空气质量差的问题。那么地下空气污染有哪些呢&#xff1f; 根据空气监测工程师的检测表明&#xff0c;与地面不同&#xff0c;地下停车场的汽车起动…

一次元数据空间内存溢出的排查记录 | 京东云技术团队

在应用中&#xff0c;我们使用的 SpringData ES的 ElasticsearchRestTemplate来做查询&#xff0c;使用方式不对&#xff0c;导致每次ES查询时都新实例化了一个查询对象&#xff0c;会加载相关类到元数据中。最终长时间运行后元数据出现内存溢出&#xff1b; 问题原因&#xf…