news 2026/6/15 20:07:36

Bert理论讲解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Bert理论讲解

参考

B站尚硅谷,bert论文链接,李哥深度学习

概述

BERT(Bidirectional Encoder Representations from Transformers)是由 Google 于 2018 年提出的一种语言预训练模型。其核心创新在于采用 Transformer 的编码器(Encoder)结构,通过双向自注意力机制,在建模每个 token 表示时同时整合左右两个方向的上下文信息,从而获得更准确、更丰富的语义表示。

bert和g p t的区别在于是否能观察到全局信息还是只能观察到上文的信息

具体实现细节

预处理

  • 输入序号
  • 掩码:我们输入的是固定长的句子但是掩码表示有效的句子长度
  • sequence ids用于分割句子

ids需要通过一个embedding

输入层表示,这个和tranfo,er不太一样,tranformer输入的有两个部分 一个是embedding 一个是postional encoding 变成 token embedding

模型参数

本次实验做的是bert-base

模型版本层数 (Layers)模型维度 (d_model)注意力头数 (Heads)参数量
BERT-base12768121.1 亿
BERT-large241024163.4 亿
Embedding类型描述/作用维度/形状备注
Token Embedding词元嵌入,表示输入序列中的每个token(句子长度, 768)768为模型隐藏层维度 (d_model)
Position Embedding位置嵌入(最大限制)(512, 768)512为模型支持的最大输入长度
Segment Embedding段嵌入,用于区分不同的句子片段(如NSP任务)(2, 768)2代表两句话

严谨起见(如果单纯了解可忽略)

还需要经过dense层进一步提取

图片解析

左边的Dense层结构在功能上对应右边FFN层的整体架构,但FFN层通过升维再降维的方式增强了模型的表达能力。
Dense层(全连接层)的核心作用是将前一层提取的特征进行整合与转化,最终输出任务所需的预测结果。它通过权重和偏置参数对输入特征进行线性变换,并结合非线性激活函数,增强模型的表达能力,使其能够拟合复杂的决策边界。
FFN层:升维度再降维

[ 输入 x ] shape: (L, 768) | v +=========================================+ | >>> FEED FORWARD NETWORK (FFN) <<< | | | | +-------------+ | | | Linear 1 | <-- 权重 W1: (768, 3072) | | (Dense) | 把门打开,让信息量变大4倍 | +-------------+ | | | | | v | | shape: (L, 3072) <-- 最宽处 | | | | | [ GELU/ReLU ] <-- 激活函数 | | | | | v | | shape: (L, 3072) | | | | | v | | +-------------+ | | | Linear 2 | <-- 权重 W2: (3072, 768) | | (Dense) | 把门关上,恢复原状 | +-------------+ | | | +=========================================+ | v [ 输出 y ] shape: (L, 768)

所以模型的参数分为3部分


直接运行理解参数

配置运行环境

conda create-nbertpython=3.11-yconda activate bert pipinstalltorch==2.6.0 --index-url https://download.pytorch.org/whl/cpu pipinstalltransformers-ihttps://pypi.tuna.tsinghua.edu.cn/simple

前置知识

model.parameters() 返回一个生成器,生成参数

p.numl()

pytorch计算张量的方法

  • 一个形状为 (768, 3072) 的权重矩阵,调用 .numel() 会返回 768 * 3072 = 2,359,296。
  • 一个形状为 (768,) 的偏置向量,调用 .numel() 会返回 768。

bert的代码 非常长,有时候能达到1000行,所以我们直接懂原理然后调用就可以

代码

importos# 设置 Hugging Face 镜像源,加速下载fromtransformersimportBertModel,BertTokenizer# 1. 加载预训练模型(从 Hugging Face 镜像加载中文 BERT)bert=BertModel.from_pretrained(r"E:\25.第二十五章 ⾃然语⾔处理通⽤框架-BERT实战\课件、源码\BERT开源项目及数据\bert-base-chinese")# 2. 定义一个函数来统计模型参数defget_parameter_number(model):# 计算总参数量 (Total Parameters)total_num=sum(p.numel()forpinmodel.parameters())# 计算可训练参数量 (Trainable Parameters)trainable_num=sum(p.numel()forpinmodel.parameters()ifp.requires_grad)# 返回包含两个统计值的字典return{'Total':total_num,'Trainable':trainable_num}print(get_parameter_number(bert))# 嵌入层三个输入 不用说了转换emb_num=21128*768+2*768+512*768# 忽略了bias# 参数说明 输入:每个词先通过嵌入层变成768维向量W_Q = 768×768 的矩阵 Query权重#768 * 768 输出线性层# 最后两个参数是前馈神经网络的升维和降维self_att_num=768*768*3+768*768+768*3072+3072*768all_att_num=12*self_att_num# 12层Transformerpooler_num=768*768#经过12层Transformer后,每个词都变成了768维向量#取[CLS] token :BERT会在句首添加一个特殊标记 [CLS]print(emb_num+all_att_num+pooler_num)# for name, para in bert.named_parameters():# print(name, para.shape)

结果和原理:我们的验证参数是对的

PS E:\25.第二十五章 ⾃然语⾔处理通⽤框架-BERT实战\课件、源码\BERT开源项目及数据>&D:/Software/Programming/Anaconda/envs/bert/python.exe"e:/25.第二十五章 ⾃然语⾔处理通⽤框架-BERT实战/课件、源码/BERT开源项目及数据/get_parm.py"Loading weights:100%|████████████████████████████████████████████████|199/199[00:00<00:00,23911.83it/s][transformers]BertModel LOAD REPORT from: E:\25.第二十五章 ⾃然语⾔处理通⽤框架-BERT实战\课件、源码\BERT开 源项目及数据\bert-base-chinese Key|Status||-------------------------------------------+------------+--+- cls.seq_relationship.weight|UNEXPECTED||cls.predictions.bias|UNEXPECTED||cls.predictions.transform.dense.weight|UNEXPECTED||cls.predictions.decoder.weight|UNEXPECTED||cls.predictions.transform.LayerNorm.bias|UNEXPECTED||cls.seq_relationship.bias|UNEXPECTED||cls.predictions.transform.LayerNorm.weight|UNEXPECTED||cls.predictions.transform.dense.bias|UNEXPECTED||Notes: - UNEXPECTED: can be ignored when loading from different task/architecture;not okifyouexpectidentical arch.{'Total':102267648,'Trainable':102267648}embeddings.word_embeddings.weight torch.Size([21128,768])embeddings.position_embeddings.weight torch.Size([512,768])embeddings.token_type_embeddings.weight torch.Size([2,768])embeddings.LayerNorm.weight torch.Size([768])embeddings.LayerNorm.bias torch.Size([768])encoder.layer.0.attention.self.query.weight torch.Size([768,768])encoder.layer.0.attention.self.query.bias torch.Size([768])encoder.layer.0.attention.self.key.weight torch.Size([768,768])encoder.layer.0.attention.self.key.bias torch.Size([768])encoder.layer.0.attention.self.value.weight torch.Size([768,768])encoder.layer.0.attention.self.value.bias torch.Size([768])encoder.layer.0.attention.output.dense.weight torch.Size([768,768])encoder.layer.0.attention.output.dense.bias torch.Size([768])encoder.layer.0.attention.output.LayerNorm.weight torch.Size([768])encoder.layer.0.attention.output.LayerNorm.bias torch.Size([768])encoder.layer.0.intermediate.dense.weight torch.Size([3072,768])encoder.layer.0.intermediate.dense.bias torch.Size([3072])encoder.layer.0.output.dense.weight torch.Size([768,3072])encoder.layer.0.output.dense.bias torch.Size([768])encoder.layer.0.output.LayerNorm.weight torch.Size([768])encoder.layer.0.output.LayerNorm.bias torch.Size([768])encoder.layer.1.attention.self.query.weight torch.Size([768,768])encoder.layer.1.attention.self.query.bias torch.Size([768])encoder.layer.1.attention.self.key.weight torch.Size([768,768])encoder.layer.1.attention.self.key.bias torch.Size([768])encoder.layer.1.attention.self.value.weight torch.Size([768,768])encoder.layer.1.attention.self.value.bias torch.Size([768])encoder.layer.1.attention.output.dense.weight torch.Size([768,768])encoder.layer.1.attention.output.dense.bias torch.Size([768])encoder.layer.1.attention.output.LayerNorm.weight torch.Size([768])encoder.layer.1.attention.output.LayerNorm.bias torch.Size([768])encoder.layer.1.intermediate.dense.weight torch.Size([3072,768])encoder.layer.1.intermediate.dense.bias torch.Size([3072])encoder.layer.1.output.dense.weight torch.Size([768,3072])encoder.layer.1.output.dense.bias torch.Size([768])encoder.layer.1.output.LayerNorm.weight torch.Size([768])encoder.layer.1.output.LayerNorm.bias torch.Size([768])encoder.layer.2.attention.self.query.weight torch.Size([768,768])encoder.layer.2.attention.self.query.bias torch.Size([768])encoder.layer.2.attention.self.key.weight torch.Size([768,768])encoder.layer.2.attention.self.key.bias torch.Size([768])encoder.layer.2.attention.self.value.weight torch.Size([768,768])encoder.layer.2.attention.self.value.bias torch.Size([768])encoder.layer.2.attention.output.dense.weight torch.Size([768,768])encoder.layer.2.attention.output.dense.bias torch.Size([768])encoder.layer.2.attention.output.LayerNorm.weight torch.Size([768])encoder.layer.2.attention.output.LayerNorm.bias torch.Size([768])encoder.layer.2.intermediate.dense.weight torch.Size([3072,768])encoder.layer.2.intermediate.dense.bias torch.Size([3072])encoder.layer.2.output.dense.weight torch.Size([768,3072])encoder.layer.2.output.dense.bias torch.Size([768])encoder.layer.2.output.LayerNorm.weight torch.Size([768])encoder.layer.2.output.LayerNorm.bias torch.Size([768])encoder.layer.3.attention.self.query.weight torch.Size([768,768])encoder.layer.3.attention.self.query.bias torch.Size([768])encoder.layer.3.attention.self.key.weight torch.Size([768,768])encoder.layer.3.attention.self.key.bias torch.Size([768])encoder.layer.3.attention.self.value.weight torch.Size([768,768])encoder.layer.3.attention.self.value.bias torch.Size([768])encoder.layer.3.attention.output.dense.weight torch.Size([768,768])encoder.layer.3.attention.output.dense.bias torch.Size([768])encoder.layer.3.attention.output.LayerNorm.weight torch.Size([768])encoder.layer.3.attention.output.LayerNorm.bias torch.Size([768])encoder.layer.3.intermediate.dense.weight torch.Size([3072,768])encoder.layer.3.intermediate.dense.bias torch.Size([3072])encoder.layer.3.output.dense.weight torch.Size([768,3072])encoder.layer.3.output.dense.bias torch.Size([768])encoder.layer.3.output.LayerNorm.weight torch.Size([768])encoder.layer.3.output.LayerNorm.bias torch.Size([768])encoder.layer.4.attention.self.query.weight torch.Size([768,768])encoder.layer.4.attention.self.query.bias torch.Size([768])encoder.layer.4.attention.self.key.weight torch.Size([768,768])encoder.layer.4.attention.self.key.bias torch.Size([768])encoder.layer.4.attention.self.value.weight torch.Size([768,768])encoder.layer.4.attention.self.value.bias torch.Size([768])encoder.layer.4.attention.output.dense.weight torch.Size([768,768])encoder.layer.4.attention.output.dense.bias torch.

流程

我主要挑1些对我自己来说需要强调的就行其他的自己了解

输入和embedding

假设输入一句话:["我", "爱", "中国"]

  1. 第一步:每个词先通过嵌入层变成768维向量
    "我" → [0.1, 0.2, ..., 0.9] (768维) "爱" → [0.3, 0.5, ..., 0.1] (768维) "中国" → [0.7, 0.2, ..., 0.4] (768维)

自注意力QKV运算

bert也是利用到自注意力计算的
2.第二步:用三个权重矩阵分别计算 Q、K、V

W_Q=768×768的矩阵# Query权重W_K=768×768的矩阵# Key权重W_V=768×768的矩阵# Value权重Q=输入 × W_Q# 每个词的Query向量K=输入 × W_K# 每个词的Key向量V=输入 × W_V# 每个词的Value向量

**pooler_num = 768×768的含义**

这是池化层(Pooler)的参数,用来把整个句子的表示"浓缩"成一个固定向量。


池化层

输入:"我 爱 中 国" ↓ BERT编码器 ↓ 输出:每个词都有一个768维向量 ↓ 池化层 ↓ 输出:一个768维的句子向量 ← 用于分类等任务
  1. 输入:经过12层Transformer后,每个词都变成了768维向量

    "我" → [768维] "爱" → [768维] "中国" → [768维]
  2. 取[CLS] token:BERT会在句首添加一个特殊标记[CLS]

    [[CLS], 我, 爱, 中, 国] ↑ 取这个位置的向量
  3. 全连接层处理

    # 池化层:768 → 768 的线性变换句子向量=全连接层([CLS]768维向量)# 参数 = 768 × 768

为什么叫"池化"?

“池化”(Pooling)来源于 CNN 的概念,但这里的 BERT Pooler 其实是:

classPooler(nn.Linear):def__init__(self):super().__init__(768,768)# 768×768

它本质是一个全连接层,不是真正的池化(如Max Pooling)。但习惯上叫它"池化层"。


动手实现

tokenizer

importos# 设置 Hugging Face 镜像源,加速下载fromtransformersimportBertModel,BertTokenizer# 1. 加载预训练模型(从 Hugging Face 镜像加载中文 BERT)bert=BertModel.from_pretrained(r"E:\25.第二十五章 ⾃然语⾔处理通⽤框架-BERT实战\课件、源码\BERT开源项目及数据\bert-base-chinese")# 2. 定义一个函数来统计模型参数defget_parameter_number(model):# 计算总参数量 (Total Parameters)total_num=sum(p.numel()forpinmodel.parameters())# 计算可训练参数量 (Trainable Parameters)trainable_num=sum(p.numel()forpinmodel.parameters()ifp.requires_grad)# 返回包含两个统计值的字典return{'Total':total_num,'Trainable':trainable_num}# print(get_parameter_number(bert))# # 嵌入层三个输入 不用说了转换# emb_num = 21128*768 + 2*768 + 512*768 # 忽略了bias# # 参数说明 输入:每个词先通过嵌入层变成768维向量W_Q = 768×768 的矩阵 Query权重# #768 * 768 输出线性层# # 最后两个参数是前馈神经网络的升维和降维# self_att_num = 768*768*3 + 768*768 + 768*3072 + 3072*768# all_att_num = 12 * self_att_num # 12层Transformer# pooler_num = 768*768#经过12层Transformer后,每个词都变成了768维向量# #取[CLS] token :BERT会在句首添加一个特殊标记 [CLS]# print(emb_num + all_att_num + pooler_num)# # for name, para in bert.named_parameters():# # print(name, para.shape)# 1. 加载预训练的中文 BERT tokenizertokenizer=BertTokenizer.from_pretrained("bert-base-chinese")# 2. 定义输入文本input="我爱你"# 3. 对文本进行分词处理out=tokenizer(input,)# 4. 打印分词结果print(out)

结果和分析


[CLS]我爱你[SEP]

加一些参数

# 1. 加载预训练的中文 BERT tokenizertokenizer=BertTokenizer.from_pretrained("bert-base-chinese")# 2. 定义输入文本input="我爱你"# 3. 对文本进行分词处理out=tokenizer(input,truncation=True,padding="max_length",max_length=128)# 4. 打印分词结果print(out)


电话

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/15 20:07:35

MPC866外部总线接口:突发传输、总线仲裁与内存保留机制详解

1. MPC866外部总线接口&#xff1a;嵌入式系统通信的基石在嵌入式系统开发&#xff0c;尤其是工业控制、网络通信设备这类对实时性和可靠性要求极高的领域&#xff0c;处理器与外部存储器、外设之间的数据交换效率&#xff0c;往往是决定系统性能上限的关键。这背后&#xff0c…

作者头像 李华
网站建设 2026/6/15 20:04:53

151X1215DC20SA01电气组件

GE 151X1215DC20SA01 是通用电气生产的工业级功率电子组件&#xff0c;属于DC2000系列直流调速器的核心功率模块。中间10条集成六只晶闸管及驱动电路&#xff0c;构成三相全控桥式整流。额定输出电流20A&#xff0c;输入三相380至480VAC。输出电压可在0至460VDC范围内调节。实现…

作者头像 李华
网站建设 2026/6/15 20:01:51

AI Agent 第二篇:【2026零基础AI教程2】90%开发者都错了!Agent和Workflow不是对立?破除全网经典误区(大厂面试标准答案)

&#x1f3af; 前言如果你看完第一篇&#xff0c;已经搞懂了「普通LLM vs AI Agent」的核心区别&#xff0c;那本篇就是你进阶AI架构、搞定大厂面试的第一道关键门槛。几乎所有新手、甚至不少工作1-3年的开发者&#xff0c;都卡在同一个致命误区&#xff1a;Workflow 死板固定…

作者头像 李华
网站建设 2026/6/15 19:57:51

一行配置改了全公司炸锅:Nacos配置管理的6个救命操作

一行配置改了全公司炸锅&#xff1a;Nacos配置管理的6个救命操作把数据库连接池从 20 改成 50&#xff0c;订单系统全挂了 那天下午三点&#xff0c;DBA 说数据库连接池太满&#xff0c;让我把最大连接数放开一点。 我在 Nacos 控制台找到 order-service.yml&#xff0c;把 spr…

作者头像 李华