简洁高效的 NLP 入门指南: 200 行实现 Bert 文本分类 (Pytorch 版)

简洁高效的 NLP 入门指南: 100 行实现 Bert 文本分类 Pytorch 版

  • 概述
  • NLP 的不同任务
  • Bert 概述
  • MLM 任务 (Masked Language Modeling)
    • Tokenize
    • MLM 的工作原理
    • 为什么使用 MLM
  • NSP 任务 (Next Sentence Prediction)
    • NSP 任务的工作原理
    • NSP 任务栗子
    • NSP 任务的调整和局限性
  • 安装和环境配置
    • PyTorch
    • Transformers
  • Bert 架构
    • Transformer 模型基础
    • Transformer 的两个主要组成部分
    • Transformer Encoder
    • Bert 的 TransformerEncoder 工作流程
  • 200 行实现 Bert 文本分类 (Pytorch)

概述

在当今信息时代, 自然语言处理 (NLP, Natural Linguistic Processing) 已经称为人工智能领域的一个关键分支. NLP 的目标是使计算机能够理解, 解释和操作人类语言, 从而在各种应用中发挥作用, 如语音识别, 机器翻译, 情感分析等. 随着技术的进步, NLP 已经从简单的规则和统计方法发展到使用复杂的深度学习模型, 今天我们要来介绍的就是 Bert.

Bert 文本分类

NLP 的不同任务

NLP 的不同任务包含:

  • 文本分类 (Text Classification): 根据文本主题, 将文本分为不同的类别, 李儒新闻分类
  • 情感分析 (Sentiment Analysis): 根据文本的情感倾向, 输出一个数, 表示文本的情感强度, 例如 0~5
  • 机器翻译 (Machine Translation): 根据源语言的文本, 生成目标语言的文本, 例如 zh->en
  • 命名实体识别 (Named Entity Recognition): 将文本中的实体 (例如人名, 地名, 组织名等) 进行标注
  • 句法分析 (Parsing): 根据句子的句法结构, 将句子分解为句子成分
  • 词性标注 (Part-of-speech Tagging): 根据词的语法特征, 给词标注一个词性

今年我们主要介绍的是文本分类任务.

Bert 概述

Bert (Bidirectional Encoder Representations from Transformers) 是一种基于 Transformer 架构的的模型. 在 2018 年由 Google 提出. Bert 采用了双向训练方法, 在模型学习给定的词时, 会考虑其上下文.

Bert 的双向训练方法包括下面两个方面:

  • 模型结构: Bert 模型结构采用了双向 Transformer 编码器, 即模型可以从输入两端同时进行编码
  • 预训练任务: Bert 的预训练任务包括 MLM (Masked Language Modeling) 任务和 NSP 任务, 这两个任务都需要 Bert 模型能够从文本的两端进行推理

Bert 架构

MLM 任务 (Masked Language Modeling)

MLM (Masked Language Modeling) 任务: 在 MLM 任务重, 会在输入文本中随机屏蔽一部分单词, 然后要求 Bert 模型预测被 Masked 单词的正确值.

Tokenize

分词 (Tokenization): 将文本按词 (Word) 为单位进行分割, 并转换为数字数据.
- 常见单词, 例如数据中的人名:
- Rachel对应 token id 5586
- Chandler对应 token id 13814
- Phoebe对应 token id 18188
- 上述 token id 对应 bert 的 vocab 中, roberta 的 vocab 表在服务器上, 懒得找了
- 特殊字符:
- [CLS]: token id 101, 表示句子的开始
- [SEP]: token id 102, 表示分隔句子或文本片段
- [PAD]: token id 0, 表示填充 (Padding), 当文本为达到指定长度时, 例如 512, 会用[PAD]进行填充
- [MASK]: token id 0, 表示填充 (Padding), 当文本为达到指定长度时, 例如 512, 会用[PAD]进行填充

上述字符在 Bert & Bert-like 模型中扮演着至关重要的角色, 在不同的任务重, 这些 Token ID 都是固定的, 例如 Bert 为 30522 个.

FYI: 上面的超链接是 jieba 分词的一个简单示例.

MLM 的工作原理

在 MLM 任务重, 输入文本首先被 Tokenize (分词), 词被转换为一个个数字数据, 文本由常见单词和特殊字符组成. 在处理过程中, 模型随机选择文本中的一定比例的 token (栗如: 15%). 并将这些标记替换为一个特定的特殊标记, 如[MASK](token id 0). 模型的任务是啥预测这些 mask token 的原始值.

为什么使用 MLM

MLM 的主要目的是使模型能够更好的理解语言的上下文和语义. 在传统的语言模型 (如 N-gram, 隐马可夫模型 HMM, 循环神经网络 RNN) 训练中模型都是单向的, 即模型只能考虑单词的前面或后面的上下文. 通过 MLM, 模型被迫学习使用一个单词前后的上下文来预测这个单词, 从而获得更全面的语言理解能力.

NSP 任务 (Next Sentence Prediction)

NSP (Next Sentence Prediction) 是 Bert 模型中的一个关键组成部分. NSP 用于改善模型对句子关系的理解, 特别是在理解段落或文档中句子关系方面. 这种能力对许多 NLP 任务至关重要, 例如: 问答系统, 文本摘要, 对话系统等.

NSP 任务的工作原理

在 NSP 任务重, 模型被训练来预测两个句子是否在原始文本中相邻. 这个过程涉及对句子间和语义关系的深入理解. 个栗子: A & B 俩句子, 模型需要判断 B 是否是紧跟在 A 后面的下一句. 在 Training 过冲中, Half time B 确实是 A 的下一句, 另一半时间 B 则是从语料库中随机选取的与 A 无关的句子. NSP 就是基于这些句子判断他们是否是连续的, 强迫模型学习识别句子的连贯性和上下文关系.

NSP 任务栗子

连续:
- 句子 A: “我是小白呀今年才 18 岁”
- 句子 B: “真年轻”
- NSP: 连续, B 是对 A 的回应 (年龄), 表达了作者 “我” 十分年轻

不连续:
- 句子 A: “意大利面要拌”
- 句子 B: “42 号混凝土”
- NSP: 不连续, B 和 A 内容完全无关

NSP 任务的调整和局限性

尽管在 NSP 和 Bert 的初期奔波中被广泛使用, 但是 NSP 也存在一些局限性. NSP 任务有时可能过于简化, 无法完全捕捉复杂文本中的细微关系.

随着 NLP 模型的发展, 一些研究发现去除 NSP 对某些模型的性能影响不大, 例如: Roberta, Xlnet, 和 Deberta 等后续模型都去除了 NSP 任务. 因为这些模型的底层双向结构已经足够强大, 能欧在没有 NSP 的情况下理解句子间的复杂关系.

安装和环境配置

PyTorch

pip install pytorch

Transformers

pip install transformers

Bert 架构

Bert 架构

Transformer 模型基础

Transformer 模型在 2017 年被提出, 是一种基于注意力机制 (Attention) 的架构, 用于处理序列数据. 与之前的序列处理模型 (RNN 和 LSTM) 不同, Transformer 完全依赖于注意力机制来捕获序列的全局依赖关系, 这使得模型在处理长距离依赖时更加有效.

Transformer 的两个主要组成部分

  1. Encoder (编码器): 负责处理输入数据
  2. Decoder (解码器): 负责生成输出数据

Transformer Encoder

Bert 的核心组成部分之一是基于 Transformer 的编码器, 即 TrasnformerEncoder.

class TransformerEncoder(Layer):def __init__(self, encoder_layer, num_layers, norm=None):super(TransformerEncoder, self).__init__()# 由多层encoder_layer组成,论文中给出,bert-base是12层,bert-large是24层,一层结构就如上图中蓝色框里的结构# num_layers = 12 or 24# LayerList称之为容器,使用方法和python里的list类似self.layers = LayerList([(encoder_layer if i == 0 else type(encoder_layer)(**encoder_layer._config)) for i in range(num_layers)])self.num_layers = num_layers

TransformerEncoder 由多个相同的层堆叠而成, 每层包含两个主要子层:

  1. 多头自注意力机制 (Multi-Head Self-Attention): 这个机制允许模型在处理每个单词时考虑到句子中的所有其他单词, 从而捕获复杂的内部依赖关系. Multi-Head 的设计使得模型能够同时从不同的表示子空间中学习信息
  2. 前馈神经网络 (Feed-Forward Neural Network): 每个注意力层后面都跟着一个简单的前馈神经网络, 这个网络对每个位置的输出进行独立处理

每个子层后面有一个残差链接 (Residual Connection) 和层归一化 (Layer Normalization). 残差连接有助于避免在深层网络中出现的梯度消失 (Vanishing Gradient) 问题, 而层归一化则有助于稳定训练过程.

Bert 的 TransformerEncoder 工作流程

  1. 输入表示: 输入文本首先被转换成词嵌入向量, 然后加上位置编码 (Positional Encoding), 以提供位置信息
  2. 通过多头自注意力 (Multi-Head Self-Attention) 层, 模型学习如何更加其他单词信息调整每个单词的表示
  3. 前馈网络: 每个位置的输出被送入前馈网络, 进一步处理每个单词的表示
  4. 重复多层处理: 过程在多个TransformersEncoder层中重复进行, 每一层都进一步增强了模型对文本的理解

200 行实现 Bert 文本分类 (Pytorch)

"""
@Module Name: bert.py
@Author: CSDN@我是小白呀
@Date: December 14, 2023Description:
200 行实现 Bert 文本分类
"""
import numpy as np
import torch
from torch.utils.data import DataLoader, TensorDataset
from transformers import BertModel, AdamW
import pickle
import time
from tqdm import tqdmclass BertForSingleInput(torch.nn.Module):"""Bert 单输入模型"""def __init__(self):super(BertForSingleInput, self).__init__()self.bert = BertModel.from_pretrained("bert-large-uncased")self.classifier = torch.nn.Linear(self.bert.config.hidden_size, 24)def forward(self, input_ids, attention_mask):outputs = self.bert(input_ids, attention_mask=attention_mask)pooled_output = outputs.pooler_outputlogits = self.classifier(pooled_output)return logits# 超参数
EPOCHS = 20  # 迭代次数
BATCH_SIZE = 8  # 批次样本数
learning_rate = 3e-6  # 学习率
MAX_LENGTH = 512  # 最大长度
model = BertForSingleInput()  # 实例化模型
optimizer = AdamW(model.parameters(), lr=learning_rate)  # 优化器
loss_fn = torch.nn.CrossEntropyLoss()  # 损失函数
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)  # 到 GPU
print("GPU 加速:", torch.cuda.is_available())def get_data():"""读取 tokenize 后的数据:return: 返回分批完的训练集和测试集"""with open('train.pkl', 'rb') as f:combined_data = pickle.load(f)X_train = combined_data['X_train']X_valid = combined_data['X_valid']y_train = combined_data['y_train']y_valid = combined_data['y_valid']# 获取input/masktrain_input = X_train["input_ids"]train_mask = X_train["attention_mask"]train_input = np.asarray(train_input)train_mask = np.asarray(train_mask)val_input = X_valid["input_ids"]val_mask = X_valid["attention_mask"]val_input = np.asarray(val_input)val_mask = np.asarray(val_mask)return train_input, val_input, train_mask, val_mask, y_train, y_validdef main():train_input, val_input, train_mask, val_mask, y_train, y_valid = get_data()# 如果 y_train 和 y_valid 是独热编码的,需要转换为类别索引y_train = np.argmax(y_train, axis=1)y_valid = np.argmax(y_valid, axis=1)# 数据转换为 PyTorch 张量train_data = TensorDataset(torch.tensor(train_input), torch.tensor(train_mask), torch.tensor(y_train))val_data = TensorDataset(torch.tensor(val_input), torch.tensor(val_mask), torch.tensor(y_valid))train_dataloader = DataLoader(train_data, batch_size=BATCH_SIZE, shuffle=True)val_dataloader = DataLoader(val_data, batch_size=BATCH_SIZE)best_valid_loss = float('inf')# 训练和验证模型for epoch in range(EPOCHS):start_time = time.time()total_loss, total_accuracy = 0, 0total_val_loss, total_val_accuracy = 0, 0# 训练循环model.train()train_loop = tqdm(train_dataloader, desc=f'Epoch {epoch+1}/{EPOCHS} [Training]', leave=False)for batch in train_loop:input_ids, attention_mask, labels = [b.to(device) for b in batch]optimizer.zero_grad()outputs = model(input_ids, attention_mask)loss = loss_fn(outputs, labels)total_loss += loss.item()_, predicted = torch.max(outputs, dim=1)total_accuracy += (predicted == labels).sum().item()loss.backward()optimizer.step()# 实时更新平均损失和准确率current_avg_loss = total_loss / len(train_loop)current_avg_accuracy = total_accuracy / (len(train_loop) * BATCH_SIZE)train_loop.set_postfix(loss=current_avg_loss, accuracy=current_avg_accuracy)avg_train_loss = total_loss / len(train_dataloader)avg_train_accuracy = total_accuracy / (len(train_dataloader) * BATCH_SIZE)# 验证循环model.eval()valid_loop = tqdm(val_dataloader, desc=f'Epoch {epoch+1}/{EPOCHS} [Validation]', leave=False)with torch.no_grad():for batch in valid_loop:input_ids, attention_mask, labels = [b.to(device) for b in batch]outputs = model(input_ids, attention_mask)loss = loss_fn(outputs, labels)total_val_loss += loss.item()_, predicted = torch.max(outputs, 1)total_val_accuracy += (predicted == labels).sum().item()# 实时更新平均损失和准确率current_avg_val_loss = total_val_loss / len(valid_loop)current_avg_val_accuracy = total_val_accuracy / (len(valid_loop) * BATCH_SIZE)valid_loop.set_postfix(loss=current_avg_val_loss, accuracy=current_avg_val_accuracy)avg_valid_loss = total_val_loss / len(val_dataloader)avg_valid_accuracy = total_val_accuracy / (len(val_dataloader) * BATCH_SIZE)# 打印训练和验证结果end_time = time.time()epoch_mins, epoch_secs = divmod(end_time - start_time, 60)print(f'Epoch: {epoch+1:02}/{EPOCHS} | Epoch Time: {epoch_mins:.0f}m {epoch_secs:.0f}s')print(f'\tTrain Loss: {avg_train_loss:.4f} | Train Acc: {avg_train_accuracy*100:.2f}%')print(f'\t Val. Loss: {avg_valid_loss:.4f} |  Val. Acc: {avg_valid_accuracy*100:.2f}%')# 保存最佳模型if avg_valid_loss < best_valid_loss:best_valid_loss = avg_valid_losstorch.save(model.state_dict(), 'bert_large.pth')print(f'Epoch {epoch+1}: Validation loss improved, saving model to bert_large.pth')# 打印当前学习率for param_group in optimizer.param_groups:print(f'lr: {param_group["lr"]:.10f}')if __name__ == '__main__':main()

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

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

相关文章

MQ入门—centos 7安装RabbitMQ 安装

三&#xff1a;RabbitMQ 安装 1.环境准备 Linux 的 CentOS 7.x 版本。Xftp 传输安装包到 Linux。Xshell 连接 Linux&#xff0c;进行解压安装。 RabbitMQ安装包 链接&#xff1a;https://pan.baidu.com/s/1ZYVI4YZlvMrj458jakla9A 提取码&#xff1a;dyto xshell安装包 链接&…

HPM6750系列--第八篇 Segger Embedded Studio for RISC-V查看外设寄存器

一、目的 在博客《HPM6750系列--第五篇 使用Segger Embedded Studio for RISC-V开发环境》中我们详细介绍了在SES中进行开发调试的相关步骤&#xff0c;但是在调试过程中发现未找到外设寄存器窗口&#xff0c;本篇就此问题指导大家进行设置查看寄存器信息。 二、介绍 请务必先阅…

PPT插件-超好用的插件-统一尺寸、裁剪、分布-大珩助手

超级对齐-统一尺寸、裁剪、分布 操作方法 先选中1个或多个形状&#xff0c;然后最后选择目标形状&#xff0c;若希望形状的位置也改变&#xff0c;则需要在对齐幻灯下选中对齐对象。 等比缩放 将选中的1个或多个形状的外形尺寸设置为目标形状大小&#xff0c;图像的纵横比可…

厨房革命@2023:新时代与旧观念的“互搏”

【潮汐商业评论/原创】 你家的厨房电器&#xff0c;多久没换了&#xff1f; 张姐家的灶最近彻底报废了&#xff0c;之前也找人来修过几次&#xff0c;缝缝补补算是用了八年有余。要不是这次彻底坏了&#xff0c;张姐怎么也不会买台新的。 “上次见邻居搬新房装了一台集成灶&…

【数组Array】力扣-304 二维区域和检索 - 矩阵不可变

目录 题目描述 解题过程 labuladong题解 题目描述 给定一个二维矩阵 matrix&#xff0c;以下类型的多个请求&#xff1a; 计算其子矩形范围内元素的总和&#xff0c;该子矩阵的 左上角 为 (row1, col1) &#xff0c;右下角 为 (row2, col2) 。 实现 NumMatrix 类&#xf…

win10环境下git安装和基础操作

简述 关于git的作用就不多赘述了&#xff0c;配合GitHub&#xff0c;达到方便人们日常项目维护和管理&#xff0c;每一次项目增删改查都可以看的清清楚楚&#xff0c;方便团队协作和个人项目日常维护。 下载git 首先我们自然是要到官网下载git&#xff0c;下载地址为https:/…

电脑录制高清视频文件是怎么设置的

在当今数字化的时代&#xff0c;电脑已经成为我们生活中不可或缺的工具。除了处理文档、浏览网页等常见功能外&#xff0c;它还可以轻松录制高清视频文件。那么&#xff0c;具体如何设置电脑才可以录制高清视频呢&#xff1f; 首先&#xff0c;要确保电脑的硬件配置是否能够支…

IEEE Transactions on Industrial Electronics工业电子TIE论文投稿须知

一、背景 IEEE TIE作为控制领域的TOP期刊&#xff0c;接收机器人、控制、自动驾驶、仪器和传感等方面的论文&#xff0c;当然范围不止这些&#xff0c;感兴趣的可以自行登录TIE官网查看。所投稿论文必须经过实验验证&#xff0c;偏工程应用类&#xff0c;当然也必须有方法上的…

关于“Python”的核心知识点整理大全21

9.3.2 Python 2.7 中的继承 在Python 2.7中&#xff0c;继承语法稍有不同&#xff0c;ElectricCar类的定义类似于下面这样&#xff1a; class Car(object):def __init__(self, make, model, year):--snip-- class ElectricCar(Car):def __init__(self, make, model, year):supe…

overleaf 加载pdf格式的矢量图时,visio 图片保存为pdf格式,如何确保pdf页面大小和图片一致

Overleaf支持多种矢量图形格式&#xff0c;其中一些常见的包括&#xff1a; PDF&#xff08;Portable Document Format&#xff09;&#xff1a; PDF是一种常见的矢量图形格式&#xff0c;Overleaf可以直接加载和显示PDF文件。许多绘图工具和LaTeX生成的图形都可以导出为PDF格式…

算法的四大思想之一:回溯思想

回溯是最重要的算法思想之一&#xff0c;主要解决一些暴力枚举也搞不定的问题&#xff08;组合、子集、分割、排列、棋盘等等&#xff09;。性能并不高&#xff0c;但是那些暴力枚举都无法ko的问题能解出来就可以了&#x1f923;。 一、回溯思想 定义 是一个种基于深度优先搜…

免担心!如果你的处理器不支持TPM 2.0,配置一下就可以安装Windows 11了

这篇文章解释了如何使用Windows注册表编辑器将你的电脑设置为Windows 11,即使你没有支持TPM 2.0的处理器。 如何在不支持的处理器中安装Windows 11 要使你的电脑即使有不受支持的处理器也能安装Windows 11,你需要对Windows注册表进行一些更改。这并不像看上去那么复杂,但也…

YOLOv8改进 | 2023Neck篇 | 利用RepGFPN改进特征融合层(附yaml文件+添加教程)

一、本文介绍 本文给大家带来的改进机制是Damo-YOLO的RepGFPN&#xff08;重参数化泛化特征金字塔网络&#xff09;&#xff0c;利用其优化YOLOv8的Neck部分&#xff0c;可以在不影响计算量的同时大幅度涨点&#xff08;亲测在小目标和大目标检测的数据集上效果均表现良好涨点…

Redis对象——内存回收,对象共享和空转时长

一. 内存回收 因为C语言不具备内存回收功能&#xff0c;所以Redis在自己的对象系统中构建了一个引用计数技术实现内存回收机制。通过这一机制&#xff0c;程序可以通过跟踪对象的引用计数信息&#xff0c;在适当的时候自动释放对象并进行内存回收。 内每一个对象的引用计数信息…

平台工程与 DevOps 和 SRE 有何不同?

在现代软件开发和运营的动态领域中 &#xff0c;平台工程、DevOps 和站点可靠性工程 (SRE) 等术语 经常使用&#xff0c;有时可以互换使用&#xff0c;这常常会导致进入或浏览这些领域的专业人员感到困惑。了解这些概念之间的细微差别对于努力构建强大且可扩展的系统的组织至关…

国产Apple Find My「查找」认证芯片-伦茨科技ST17H6x芯片

深圳市伦茨科技有限公司&#xff08;以下简称“伦茨科技”&#xff09;发布ST17H6x Soc平台。成为继Nordic之后全球第二家取得Apple Find My「查找」认证的芯片厂家&#xff0c;该平台提供可通过Apple Find My认证的Apple查找&#xff08;Find My&#xff09;功能集成解决方案。…

连连看游戏

连通块记忆性递归的综合运用 这里x&#xff0c;y的设置反我平常的习惯&#xff0c;搞得我有点晕 实际上可以一输入就交换x&#xff0c;y的数据的 如果设置y1为全局变量的话会warning&#xff1a; warning: built-in function y1 declared as non-function 所以我改成p和q了…

一些好用的VSCode扩展

可以在扩展这里直接搜索需要的扩展&#xff0c;点击安装即可。 1.Chinese 中文扩展&#xff0c;就是说虽然咱们懂点英语&#xff0c;但还是中文看着方便 2.Auto Rename Tag 当你重命名一个HTML 标签时&#xff0c;会自动重命名与他配对的HTML 标签 当你选择h4这个标签时&…

系列三、DDL

一、DDL 1.1、概述 DDL是英文单词Data Definition Language的缩写&#xff0c;中文意思为数据定义语言&#xff0c;是用来定义数据库对象(数据库&#xff0c;表&#xff0c;字段)的。 1.2、数据库操作 1.2.1、查询所有数据库 show databases; 1.2.2、创建数据库 # 语法 cre…

云原生基础入门概念

文章目录 云原生的概念云原生的关键技术为何选择云原生&#xff1f;云原生的实际应用 当谈及现代软件开发和IT基础架构时&#xff0c;云原生成为了一个备受关注的话题。它代表了一种软件架构和开发方法&#xff0c;旨在充分利用云计算环境的优势&#xff0c;以提高应用程序的可…