首页 > 其他分享 >BERT口语化详解

BERT口语化详解

时间:2024-11-28 09:00:22浏览次数:13  
标签:BERT attention 矩阵 token 详解 口语化 注意力 向量

[Autho]  余胜辉
1. Bert 模型简介

        BERT是谷歌于2018年提出的预训练语言模型,它使用了Transformer编码器部分。

2. Bert 模型输入处理
        以bert-base为例,模型包含12个层,12个注意力头,隐藏层尺寸为768,模型大小约为110MB,输入长度为256。这使得BERT模型在标准Transformer的深度上更深。BERT的输入输出是256个768维的向量,每个向量对应一个输入的token。区别在于,输入BERT之前的每个token转化为768维的向量时,token之间没有相互影响。而从BERT输出之后,token之间会根据上下文语义环境产生相互影响,这是通过多头注意力机制实现的。
from transformers import BertTokenizer, BertModel
import torch

# 加载预训练的BERT分词器和模型
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')

# 输入文本
text1 = "Hello, how are you?"
text2 = "I love using BERT!"

# 对输入文本进行编码
inputs = tokenizer(text1, text2, padding=True, truncation=True, return_tensors='pt')

# 打印分词后的结果
print("Input tokens:", tokenizer.convert_ids_to_tokens(inputs['input_ids'][0]))

# 进行前向传播获取输出
with torch.no_grad():
    outputs = model(**inputs)

# 获取最后一层的隐藏状态
last_hidden_states = outputs.last_hidden_state

# 打印结果
print("Input text 1:", text1)
print("Input text 2:", text2)
print("Tokenized input IDs:", inputs['input_ids'])
print("Last hidden states shape:", last_hidden_states.shape)
print("First token's vector (CLS):", last_hidden_states[:, 0, :].numpy())

  

3. 多头注意力机制

        BERT模型的输入可以是两句话。BERT首先会在句子前面加上cls token,在两句话中间加入sep token,两句话的末尾也加入sep token。然后,将每个字转化为向量。具体做法是,将字符、句子、位置信息分别转化为随机生成的不可变的768维向量,并将三个向量相加,得到这句话里面每个字的向量。之后就是多头注意力的计算了。

为了讲清楚多头注意力的计算过程,我们先做一些简单的假设:

  • 假设输入的句子只有“你好”这两个字,也不加cls token,因为加入cls token和不加cls token,在进行多头计算的时候,是一样的。

  • 假设,我们只进行一个注意力头的计算,因为一个头的计算,和多个头的计算也是类似的。

  • 假设,我们输入的句子是:“你好”。假设转成向量后,每个字为768维,那么这句话,就变成了一个2行768列的矩阵。

在进入多头注意力机制之前,先对这个矩阵乘以WQ,WK,WV三个参数矩阵,这三个参数矩阵将来都是要参与训练的。乘以这三个矩阵有两个作用:

  • 第一个作用是,对原来的每个字的768维的向量,进行降维;

  • 第二个作用是,通过乘以WQ,WK,WV这三个矩阵,构建出K、Q、V三个矩阵。

我们真正的目的,是实现上下文语义,按照重要性,对当前这个字,产生不同程度的影响。而kqv计算,就是实现这个目的的具体手段。

Kqv具体的计算过程是,将Q矩阵乘以K矩阵的转置,然后除以根号dk,得到的结果送入softmax函数,最后乘以V矩阵。这个计算过程的具体含义是这样的:

  • Q矩阵乘以K矩阵的转置,会得到一个2行2列的矩阵,代表的是一句话里面,字与字之间两两相关性。例如,如果我们输入的这句话是:“你好”,矩阵里面的元素,就代表的是:“你”和“你”的相关性,“好”和“好”的相关性,以及“你”和“好”的相关性。

  • 但是此时的矩阵里的数值,有可能是大于1的,因此要把这个矩阵中所有的值,都压缩到0到1之间,才符合相关性矩阵的概念。

  • 为了达到这个目的,最好的办法,就是将矩阵,送入softmax函数。但是为了避免数值过大,进入softmax的饱和区,在送入softmax函数之前,要先对矩阵进行缩小,来避免进入饱和区。具体的缩小办法是,将矩阵除以根号下dk,dk是每个字转向量之后的维度。

  • 缩小之后的结果,再送入softmax函数,将所有数据压缩到0到1之间。此时矩阵就变成了一个真正的相关性矩阵了。矩阵也叫做注意力权重矩阵。

  • 接下来,按照公式,应该是做矩阵乘以V矩阵的运算了。V矩阵是原来句子降维后的结果。第一行代表的是,句子中的第一个字的向量,也就是“你”,这个字的向量;第二行代表的是,句子中的第二个字的向量,也就是“好”,这个字的向量。矩阵代表的是这句话中,字与字的相关性。则矩阵乘以V矩阵,具体含义就是,其它字的对应维度的信息,按照矩阵中的相关性,来影响当前这个字的对应维度的数值。并将影响的具体数值,按照矩阵里面的权重,加到当前这个字对应的维度上。到这里就实现了,通过上下文语义,对当前这个字影响。也就是实现了,同一个字,在不同的上下文语义中,具有不同的向量表示。再通俗点说就是,同一个字,在不同的上下文语义中,具有不同的含义。

# 解释多头注意力机制
def explain_attention_mechanism(model, inputs):
    # 获取模型的第一层
    layer = model.encoder.layer[0]
    
    # 获取Q, K, V矩阵
    qkv = layer.attention.self.query_key_value(inputs['input_ids'])
    q, k, v = qkv.chunk(3, dim=-1)
    
    # 分离成多个头
    num_heads = layer.attention.self.num_attention_heads
    head_dim = layer.attention.self.attention_head_size
    
    q = q.reshape(q.size(0), q.size(1), num_heads, head_dim).transpose(1, 2)
    k = k.reshape(k.size(0), k.size(1), num_heads, head_dim).transpose(1, 2)
    v = v.reshape(v.size(0), v.size(1), num_heads, head_dim).transpose(1, 2)
    
    # 计算注意力分数
    attention_scores = torch.matmul(q, k.transpose(-1, -2)) / torch.sqrt(torch.tensor(head_dim))
    attention_probs = torch.nn.functional.softmax(attention_scores, dim=-1)
    
    # 计算加权值
    context_layer = torch.matmul(attention_probs, v)
    context_layer = context_layer.transpose(1, 2).reshape(context_layer.size(0), context_layer.size(1), num_heads * head_dim)
    
    return attention_scores, attention_probs, context_layer

# 获取注意力机制的结果
attention_scores, attention_probs, context_layer = explain_attention_mechanism(model, inputs)

# 打印注意力分数和概率
print("\nAttention Scores for the first head:\n", attention_scores[0][0].detach().numpy())
print("\nAttention Probabilities for the first head:\n", attention_probs[0][0].detach().numpy())

# 打印上下文层
print("\nContext Layer for the first head:\n", context_layer.detach().numpy())
4. 残差网络和归一化

        上述KQA的计算流程,执行一次,就是一个注意力的“头”;并行执行多次,就实现了“多头注意力”,bert-base版本中,头数是12;多头注意力之后面,再加一个前馈神经网络,将多头注意力的输出结果,再调整成每个字是768为的向量。这样,就构成了一个完整的多图注意力层。多个包含多头注意力的层,串起来堆叠多次,就是多层的,多头注意力模型。也就构成了BERT的主体框架了。BERT-base版本有12层。 每个多头注意层之间在加入残差链接和归一化,构成错层堆叠的残差网络。残差网络,是将每层的输入层通过旁路,绕过当前层,直接接到当前层的输出上,与当前层的输出进行求和与归一化。加入残差网络的目的是避免当前层输出,不靠谱,用残差链接,可以将原始输入,直接链接到,该层的输出上,进行纠偏。同时也能防止梯度消失。

通过上述描述可知,BERT是一个12层,每层包含12个多头注意力,的编码器。里面包含很多参数。这些参数需要进行合理的训练,才能让BERT具备语义理解能力。我们将文本中的,85%不变,15%进行mask。再将这15%的mask掉的token,分成三种情况:其中10%不变,10%随机替换,80%被遮掩。最后 ,让BERT来预测,mask掉的字是什么。从而提高模型学习能力和上下文语义理解能力。

5. Bert 与 Word2Vec
        最终训练好的BERT,实际上是一个编码器模型,它类似于一个高级的word2vector。BERT的输入是文字转成的向量,但是此时每个字的向量,是不包含上下文语义的。BERT接收到这些向量后,借助多头注意力机制进行编码,输出的还是每个自对应的向量,输入的矩阵形状和输出的矩阵形状是一样的。区别在于,输出的每个字的向量,已经包含上下文语义了。这就是BERT的作用。而这种效果,word2vector是做不到的。

另外,BERT在每个输入前面都加了cls token,所以cls token是一个特殊的token。在BERT与训练过程中,cls token后面可以接任何文本语料,并且cls token总是在输入的第一个位置。位置是固定的,这就给我们借助cls token做下游任务带来了方便。例如:可以给cls token后面接入一些隐藏层来做:文本分类,情感分析任务。同时,BERT还可以起到词语转向量的作用,将输入的中文文字,借助BERT转成对应向量。

bert论文领读 https://www.youtube.com/watch?v=ULD3uIb2MHQ

标签:BERT,attention,矩阵,token,详解,口语化,注意力,向量
From: https://blog.csdn.net/TrueYSH/article/details/144101521

相关文章

  • ThreeJs-04详解材质与纹理
    一.matcap材质这个材质不会受到光照影响,但是如果图片本身有光就可以一直渲染这个图片本来的样子,用来将一个图片纹理渲染到物体上的材质代码实现加载模型后,开启纹理渲染,并把它的材质变为这个材质,并且贴上纹理图二.Lambert材质Lambert网格材质是Three.js中最基本和常用的材......
  • 数据结构初阶终——七大排序法(堆排序,快速排序,归并排序,希尔排序,冒泡排序,选择排序,插入
    排序1.插入排序2.希尔排序3.冒泡排序4.选择排序(双头排序优化版)5.堆排序6.快速排序1).双指针法2).前后指针法3).非递归法7.归并排序1).递归版本(递归的回退就是归并)2).非递归版本(迭代版本)计算机执行的最多的操作之一就有排序,排序是一项极其重要的技能接下来......
  • 编程之路,从0开始:预处理详解(完结篇)
            Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路!我的博客:<但凡.我的专栏:编程之路        这一篇预处理详解是我们C语言基础内容学习的最后一篇,也是我们的专栏:编程之路的最后一篇!从今日起,我将不定期更新新的内容,开始新的章节......
  • SQL盲注攻击详解及防御措施
    文章目录基于布尔的盲注工作原理示例代码防御措施基于时间的盲注工作原理示例代码防御措施其他防御措施输入验证错误处理使用WAF(Web应用防火墙)基于布尔的盲注工作原理基于布尔的盲注通过构造SQL查询来判断数据库的响应是否满足某个条件。攻击者通过观察应用程......
  • 小白必看详解循环语句,看完必会!
    循环语句循环的概念重复的执行一段代码,避免死循环,提高效率(时间复杂度-关注和 空间复杂度-不关注)循环包含三大语句:while语句、dowhile语句、for语句循环的三要素:初始值(初始的变量)迭代量(基于初始的改变)条件(基于初始的判断)while语句while(条件表达式(返回true和false......
  • 详解 PyTorch 中的 DataLoader:功能、实现及应用示例
    详解PyTorch中的DataLoader:功能、实现及应用示例在PyTorch框架中,Dataloader是一个非常重要的类,用于高效地加载和处理来自Dataset的数据。Dataloader允许批量加载数据,支持多线程/多进程加载,并可进行数据混洗和采样,极大地提高了模型训练的效率和灵活性。Dataloader......
  • 详解 PyTorch 中的 Dataset:功能、实现及应用示例
    详解PyTorch中的Dataset:功能、实现及应用示例在机器学习和深度学习中,Dataset类是一个抽象类,通常用于封装对于数据集的各种操作,包括访问、处理和预处理数据。Dataset为数据加载提供了一个标准的接口,使其能够以一致的方式被进一步的数据处理工具和模型训练过程使用。Da......
  • Java设计模式 —— 【创建型模式】原型模式(浅拷贝、深拷贝)详解
    文章目录前言原型模式一、浅拷贝1、案例2、引用数据类型二、深拷贝1、重写clone()方法2、序列化总结前言先看一下传统的对象克隆方式:原型类:publicclassStudent{privateStringname;publicStudent(Stringname){this.name=name;......
  • Linux网络编程——epoll原理详解及epoll反应堆模型
     设想一个场景:有100万用户同时与一个进程保持着TCP连接,而每一时刻只有几十个或几百个TCP连接是活跃的(接收TCP包),也就是说在每一时刻进程只需要处理这100万连接中的一小部分连接。那么,如何才能高效的处理这种场景呢?进程是否在每次询问操作系统收集有事件发生的TCP连接时,把这10......
  • 【C++】C++11新特性详解:可变参数模板与emplace系列的应用
    C++语法相关知识点可以通过点击以下链接进行学习一起加油!命名空间缺省参数与函数重载C++相关特性类和对象-上篇类和对象-中篇类和对象-下篇日期类C/C++内存管理模板初阶String使用String模拟实现Vector使用及其模拟实现List使用及其模拟实现容器适配器Stack与QueuePriority......