FFerryman
← 返回作品
2026年10月2日

hugging face简单的使用

今天学习了一下BERT的部署,相当于是练习了一下Huggingface的使用

FFerryman☕ 约 25 分钟
项目实践
一般transformer模型有三个部分组成:1.tokennizer,2.Model,3.Post processing。如下图所示,图中第二层和第三层是每个部件的输入/输出以及具体的案例。我们可以看到三个部分的具体作用:Tokenizer就是把输入的文本做切分,然后变成向量,Model负责根据输入的变量提取语义信息,输出logits;最后Post Processing根据模型输出的语义信息,执行具体的nlp(natural language processing)任务,比如情感分析,文本自动打标签等

import transformers#记得先装库,用pip,还有accelerate,pip install transformers accelerate torch
import torch
MODEL_PATH = r"D:\transformr_files\bert-base-uncased/"
# a.通过词典导入分词器
tokenizer = transformers.BertTokenizer.from_pretrained(r"D:\transformr_files\bert-base-uncased\bert-base-uncased-vocab.txt")
# b. 导入配置文件(这是在HF上下载的模型放在电脑的路径)
model_config = transformers.BertConfig.from_pretrained(MODEL_PATH)
# 修改配置
model_config.output_hidden_states = True
model_config.output_attentions = True
# 通过配置和路径导入模型
model = transformers.BertModel.from_pretrained(MODEL_PATH,config = model_config)
# encode_plus返回所有编码信息
encoded = tokenizer.encode_plus("i like you", "but not him")#encoded = 的作用:接收函数 return 返回出来的字典,把 return 出来的字典,绑定到变量名 `encoded``encoded` 现在指向这份字典数据,后面代码随时可以读取
input_ids=torch.tensor([encoded["input_ids"]])
token_type_ids=torch.tensor([encoded["token_type_ids"]])
'''Out :
    {'input_ids': [101, 1045, 2066, 2017, 102, 2021, 2025, 2032, 102],
     'token_type_ids': [0, 0, 0, 0, 0, 1, 1, 1, 1],
     'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}'''
# 添加batch维度并转化为tensor
input_ids = torch.tensor([input_ids])
token_type_ids = torch.tensor([token_type_ids])
# 将模型转化为eval模式
model.eval()
# 将模型和数据转移到cuda, 若无cuda,可更换为cpu
device = 'cuda'
tokens_tensor = input_ids.to(device)
segments_tensors = token_type_ids.to(device)
model.to(device)

# 进行编码
with torch.no_grad():
    # See the models docstrings for the detail of the inputs
    outputs = model(tokens_tensor, token_type_ids=segments_tensors)
    # Transformers models always output tuples.
    # See the models docstrings for the detail of all the outputs
    # In our case, the first element is the hidden state of the last layer of the Bert model
    encoded_layers = outputs
# 得到最终的编码结果encoded_layers

这里是基本代码和一点小笔记

readme一般是模型的介绍,包括使用方法都会放到里面,不介绍了。其他最重要的组成部分,大概分为三类:

1.config
控制模型的名称、最终输出的样式、隐藏层宽度和深度、激活函数的类别等。这些参数我补齐了说明,对于初学者来说,大家一般不需要调整。这些参数都可以通过configuration类更改。

2. tokenizer(包含三个文件)
vocab.txt是词典文件(打开就是单个字符,我这里用的是bert-base-chinsese,可以看到里面都是保留符号和单个汉字索引,字符)tokenizer.json和config是分词的配置文件,根据vocab信息和你的设置更新,里面把vocab都按顺序做了索引,将来可以根据编码生成one-hot向量,然后跟embeding训练的矩阵相乘,就可以得到该字符的向量。下图是tokenizer.json内容。

3.模型使用

导入模型

利用官方的hub导入模型;下面导入了一个BertModel;在官方的教程中推进使用pipeline导入模型的方法;

import torch
from transformers import BertModel, BertTokenizer, BertConfig
# 首先要import进来
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
config = BertConfig.from_pretrained('bert-base-chinese')
config.update({'output_hidden_states':True}) # 这里直接更改模型配置
model = BertModel.from_pretrained("bert-base-chinese",config=config)
利用pipeline的方式也是一样的可以导入模型哈,方式如下:
from transformers import AutoModel
checkpoint = "bert-base-chinese"
model = AutoModel.from_pretrained(checkpoint)


这里引用了一个大佬的项目
# 利用深度学习做情感分析,基于Imdb 的50000个电影评论数据进行;

import torch
from torch.utils.data import DataLoader,Dataset
import os
import re
from random import sample
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from transformers import BertModel, BertTokenizer
from tqdm import tqdm
 
# 路径需要根据情况修改,要看你把数据下载到哪里了
# 数据下载地址在斯坦福官网,网上搜索就有
data_base_path = r"./imdb_test/aclImdb"

# 这个里面是存储你训练出来的模型的,现在是空的
model_path = r"./imdb_test/aclImdb/mode"
        
#1. 准备dataset,这里写了一个数据读取的类,并把数据按照不同的需要进行了分类;
class ImdbDataset(Dataset):
    def __init__(self,mode,testNumber=10000,validNumber=5000):

        # 在这里我做了设置,把数据集分成三种形式,可以选择 “train”默认返回全量50000个数据,“test”默认随机返回10000个数据,
        # 如果是选择“valid”模式,随机返回相应数据
        super(ImdbDataset,self).__init__()

        # 读取所有的训练文件夹名称
        text_path =  [os.path.join(data_base_path,i)  for i in ["test/neg","test/pos"]]
        text_path.extend([os.path.join(data_base_path,i)  for i in ["train/neg","train/pos"]])

        if mode=="train":
            self.total_file_path_list = []
            # 获取训练的全量数据,因为50000个好像也不算大,就没设置返回量,后续做sentence的时候再做处理
            for i in text_path:
                self.total_file_path_list.extend([os.path.join(i,j) for j in os.listdir(i)])
        if mode=="test":
            self.total_file_path_list = []
            # 获取测试数据集,默认10000个数据
            for i in text_path:
                self.total_file_path_list.extend([os.path.join(i,j) for j in os.listdir(i)])
            self.total_file_path_list=sample(self.total_file_path_list,testNumber)
       
        if mode=="valid":
            self.total_file_path_list = []
            # 获取验证数据集,默认5000个数据集
            for i in text_path:
                self.total_file_path_list.extend([os.path.join(i,j) for j in os.listdir(i)])
            self.total_file_path_list=sample(self.total_file_path_list,validNumber)
   
    def tokenize(self,text):
    
        # 具体要过滤掉哪些字符要看你的文本质量如何
       
        # 这里定义了一个过滤器,主要是去掉一些没用的无意义字符,标点符号,html字符啥的
        fileters = ['!','"','#','$','%','&','\(','\)','\*','\+',',','-','\.','/',':',';','<','=','>','\?','@'
            ,'\[','\\','\]','^','_','`','\{','\|','\}','~','\t','\n','\x97','\x96','”','“',]
        # sub方法是替换
        text = re.sub("<.*?>"," ",text,flags=re.S)	# 去掉<...>中间的内容,主要是文本内容中存在<br/>等内容
        text = re.sub("|".join(fileters)," ",text,flags=re.S)	# 替换掉特殊字符,'|'是把所有要匹配的特殊字符连在一起
        return text	# 返回文本

    def __getitem__(self, idx):
        cur_path = self.total_file_path_list[idx]
		# 返回path最后的文件名。如果path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素。
        # cur_filename返回的是如:“0_3.txt”的文件名
        cur_filename = os.path.basename(cur_path)
        # 标题的形式是:3_4.txt	前面的3是索引,后面的4是分类
        # 如果是小于等于5分的,是负面评论,labei给值维1,否则就是1
        labels = []
        sentences = []
        if int(cur_filename.split("_")[-1].split(".")[0]) <= 5 :
            label = 0
        else:
            label = 1
        # temp.append([label])
        labels.append(label)
        text = self.tokenize(open(cur_path,encoding='UTF-8').read().strip()) #处理文本中的奇怪符号
        sentences.append(text)
        # 可见我们这里返回了一个list,这个list的第一个值是标签0或者1,第二个值是这句话;
        return sentences,labels
 
    def __len__(self):
        return len(self.total_file_path_list)
    
# 2. 这里开始利用huggingface搭建网络模型
# 这个类继承再nn.module,后续再详细介绍这个模块
# 
class BertClassificationModel(nn.Module):
    def __init__(self,hidden_size=768):
        super(BertClassificationModel, self).__init__()
        # 这里用了一个简化版本的bert
        model_name = 'distilbert-base-uncased'

        # 读取分词器
        self.tokenizer = BertTokenizer.from_pretrained(pretrained_model_name_or_path=model_name)
        
        # 读取预训练模型
        self.bert = BertModel.from_pretrained(pretrained_model_name_or_path=model_name)

        for p in self.bert.parameters(): # 冻结bert参数
                p.requires_grad = False
        self.fc = nn.Linear(hidden_size,2)

    def forward(self, batch_sentences):   # [batch_size,1]
        sentences_tokenizer = self.tokenizer(batch_sentences,
                                             truncation=True,
                                             padding=True,
                                             max_length=512,
                                             add_special_tokens=True)
        input_ids=torch.tensor(sentences_tokenizer['input_ids']) # 变量
        attention_mask=torch.tensor(sentences_tokenizer['attention_mask']) # 变量
        bert_out=self.bert(input_ids=input_ids,attention_mask=attention_mask) # 模型

        last_hidden_state =bert_out[0] # [batch_size, sequence_length, hidden_size] # 变量
        bert_cls_hidden_state=last_hidden_state[:,0,:] # 变量
        fc_out=self.fc(bert_cls_hidden_state) # 模型
        return fc_out

# 3. 程序入口,模型也搞完啦,我们可以开始训练,并验证模型的可用性

def main():

    testNumber = 10000    # 多少个数据参与训练模型
    validNumber = 100   # 多少个数据参与验证
    batchsize = 250  # 定义每次放多少个数据参加训练
    
    trainDatas = ImdbDataset(mode="test",testNumber=testNumber) # 加载训练集,全量加载,考虑到我的破机器,先加载个100试试吧
    validDatas = ImdbDataset(mode="valid",validNumber=validNumber) # 加载训练集

    train_loader = torch.utils.data.DataLoader(trainDatas, batch_size=batchsize, shuffle=False)#遍历train_dataloader 每次返回batch_size条数据

    val_loader = torch.utils.data.DataLoader(validDatas, batch_size=batchsize, shuffle=False)

    # 这里搭建训练循环,输出训练结果

    epoch_num = 1  # 设置循环多少次训练,可根据模型计算情况做调整,如果模型陷入了局部最优,那么循环多少次也没啥用

    print('training...(约1 hour(CPU))')
    
    # 初始化模型
    model=BertClassificationModel()
  
    optimizer = optim.AdamW(model.parameters(), lr=5e-5) # 首先定义优化器,这里用的AdamW,lr是学习率,因为bert用的就是这个

    # 这里是定义损失函数,交叉熵损失函数比较常用解决分类问题
    # 依据你解决什么问题,选择什么样的损失函数
    criterion = nn.CrossEntropyLoss()
    
    print("模型数据已经加载完成,现在开始模型训练。")
    for epoch in range(epoch_num):
        for i, (data,labels) in enumerate(train_loader, 0):

            output = model(data[0])
            optimizer.zero_grad()  # 梯度清0
            loss = criterion(output, labels[0])  # 计算误差
            loss.backward()  # 反向传播
            optimizer.step()  # 更新参数

            # 打印一下每一次数据扔进去学习的进展
            print('batch:%d loss:%.5f' % (i, loss.item()))

        # 打印一下每个epoch的深度学习的进展i
        print('epoch:%d loss:%.5f' % (epoch, loss.item()))
    
    #下面开始测试模型是不是好用哈
    print('testing...(约2000秒(CPU))')

    # 这里载入验证模型,他把数据放进去拿输出和输入比较,然后除以总数计算准确率
    # 鉴于这个模型非常简单,就只用了准确率这一个参数,没有考虑混淆矩阵这些
    num = 0
    model.eval()  # 不启用 BatchNormalization 和 Dropout,保证BN和dropout不发生变化,主要是在测试场景下使用;
    for j, (data,labels) in enumerate(val_loader, 0):

        output = model(data[0])
        # print(output)
        out = output.argmax(dim=1)
        # print(out)
        # print(labels[0])
        num += (out == labels[0]).sum().item()
        # total += len(labels)
    print('Accuracy:', num / validNumber)

if __name__ == '__main__':
    main()


— 觉得不错就点个赞吧 —

← 返回列表

把你的灵感变成作品

注册即可发布 —— Markdown 写作、照片上传、被更多人看见。

开始创作 →

© 2026 Ferryman · Built with Next.js & Supabase

内容系统运行中创作中心 →