HuggingFace实战(一)

视频链接:HuggingFace简明教程,BERT中文模型实战示例.NLP预训练模型,Transformers类库,datasets类库快速入门._哔哩哔哩_bilibili

文本分类

import torch
from datasets import load_from_disk
from transformers import BertTokenizer, BertModel
from torch.utils.data import Dataset, DataLoader
from torch.optim import AdamW
import os
from transformers import logging
 
# 设置transformers模块的日志等级,减少不必要的警告,对训练过程无影响,请忽略
logging.set_verbosity_error()

# 从本地加载数据集
dataset = load_from_disk('../datasets/ChnSentiCorp/data')

#定义数据集
class Dataset(Dataset):
    def __init__(self, dataset):
        self.dataset = dataset

    def __len__(self):
        return len(self.dataset)

    def __getitem__(self, i):
        text = self.dataset[i]['text']
        label = self.dataset[i]['label']

        return text, label


train = Dataset(dataset['train'])
validation = Dataset(dataset['validation']) # 不能叫test,会与test()重名,报错

#加载字典和分词工具
token = BertTokenizer.from_pretrained('bert-base-chinese')

# 定义批处理函数
def collate_fn(data):
    sents = [i[0] for i in data]
    labels = [i[1] for i in data]

    #编码
    data = token.batch_encode_plus(batch_text_or_text_pairs=sents,
                                   truncation=True,
                                   padding='max_length',
                                   max_length=500,
                                   return_tensors='pt',
                                   return_length=True)

    #input_ids:编码之后的数字
    #attention_mask:是补零的位置是0,其他位置是1
    input_ids = data['input_ids']
    attention_mask = data['attention_mask']
    token_type_ids = data['token_type_ids']
    labels = torch.LongTensor(labels)

    #print(data['length'], data['length'].max())

    return input_ids, attention_mask, token_type_ids, labels


#数据加载器
loader = DataLoader(dataset=train,
                    batch_size=16,
                    collate_fn=collate_fn,
                    shuffle=True,
                    drop_last=True)

for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader):
    break

# print(len(loader)) # 600
# print(input_ids.shape, attention_mask.shape, token_type_ids.shape, labels) # torch.Size([16, 500]) torch.Size([16, 500]) torch.Size([16, 500]) tensor([0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1])

#加载预训练模型
pretrained = BertModel.from_pretrained('bert-base-chinese')

#不训练,不需要计算梯度
for param in pretrained.parameters():
    param.requires_grad_(False)

#模型试算
# out = pretrained(input_ids=input_ids,attention_mask=attention_mask, token_type_ids=token_type_ids)

# print(out.last_hidden_state.shape) # torch.Size([16, 500, 768])

#定义下游任务模型
class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.fc = torch.nn.Linear(768, 2) # 预训练模型的隐层输出维度是768

    def forward(self, input_ids, attention_mask, token_type_ids):
        with torch.no_grad(): # 不改变预训练模型参数
            out = pretrained(input_ids=input_ids,
                       attention_mask=attention_mask,
                       token_type_ids=token_type_ids)

        out = self.fc(out.last_hidden_state[:, 0]) # 解决分类问题只需要使用到[CLS],即第一个字符

        out = out.softmax(dim=1)

        return out


model = Model()

#训练
optimizer = torch.optim.AdamW(model.parameters(), lr=5e-4)
criterion = torch.nn.CrossEntropyLoss()

model.train()
for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader):
    out = model(input_ids=input_ids,
                attention_mask=attention_mask,
                token_type_ids=token_type_ids)

    loss = criterion(out, labels)
    loss.backward()
    optimizer.step()
    optimizer.zero_grad()

    if i % 5 == 0:
        out = out.argmax(dim=1)
        accuracy = (out == labels).sum().item() / len(labels)

        print(i, loss.item(), accuracy)

    if i == 300: # 只训练300个batch ,300*16 = 4800条数据,即一半数据
        break

# 保存模型
def save_pretrained(model, path):
    # 保存模型,先利用os模块创建文件夹,后利用torch.save()写入模型文件
    os.makedirs(path, exist_ok=True)
    torch.save(model, os.path.join(path, 'model.pth'))

save_pretrained(model, './model/classifier2/')

#测试
def test():
    model.eval()
    correct = 0
    total = 0

    loader_test = DataLoader(dataset=validation,
                            batch_size=32,
                            collate_fn=collate_fn,
                            shuffle=True,
                            drop_last=True)

    for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader_test):

        with torch.no_grad(): # 测试不更改模型参数
            out = model(input_ids=input_ids,
                        attention_mask=attention_mask,
                        token_type_ids=token_type_ids)

        out = out.argmax(dim=1)
        correct += (out == labels).sum().item()
        total += len(labels)

    print(correct / total) # 0.8631756756756757


test()

预测单词(填空)

import torch
import torch.nn as nn
from datasets import load_from_disk
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer, BertModel
from transformers import logging
from torch.optim import AdamW
import os
 
# 设置transformers模块的日志等级,减少不必要的警告,对训练过程无影响,请忽略
logging.set_verbosity_error()

# 从本地加载数据集
dataset = load_from_disk('../datasets/ChnSentiCorp/data')

dataset_train = dataset['train']
dataset_validation = dataset['validation']
dataset_test = dataset['test']

# 定义数据集
class Dataset(Dataset): # 固定写法,至少保留下述3个函数,否则无法初始化
    def __init__(self, dataset):

        def f(data): # 过滤数据,只保留text长度大于30的数据
            return len(data['text']) > 30
        
        self.dataset = dataset.filter(f) # filter()过滤函数,保留符合f条件的数据
    
    def __len__(self):
        return len(self.dataset)

    def __getitem__(self, i): # 该数据集中只保留text,做填空任务不需要label
        text = self.dataset[i]['text']
        return text

dataset = Dataset(dataset_train)
dataset_test = Dataset(dataset_test)

# print(len(dataset), dataset[0]) 
# 9192 
# 选择珠江花园的原因就是方便,有电动扶梯直接到达海边,周围餐馆、食廊、商场、超市、摊位一应俱全。酒店装修一般,但还算整洁。 泳池在大堂的屋顶,因此很小,不过女儿倒是喜欢。 包的早餐是西式的,还算丰富。 服务吗,一般

# 加载字典和分词工具
token = BertTokenizer.from_pretrained('bert-base-chinese')

# print(token)
# PreTrainedTokenizer(name_or_path='bert-base-chinese', vocab_size=21128, model_max_len=512, is_fast=False, padding_side='right', truncation_side='right', special_tokens={'unk_token': '[UNK]', 'sep_token': '[SEP]', 'pad_token': '[PAD]', 'cls_token': '[CLS]', 'mask_token': '[MASK]'})

# 定义批处理函数
def collate_fn(data):
    # 对数据集中的文本进行编码
    data = token.batch_encode_plus(
        batch_text_or_text_pairs=data,
        truncation=True,
        padding='max_length',
        max_length=30,
        return_tensors='pt',
        return_length=True
    )
    # input_ids:编码之后的数据
    # attention_mask:padding位置是0,其余位置是1
    # token_type_ids:第一句话和特殊符号位置是0,第二句话位置是1
    input_ids = data['input_ids']
    attention_mask = data['attention_mask']
    token_type_ids = data['token_type_ids']

    # 固定把第16个词替换为mask
    labels = input_ids[:, 15].reshape(-1).clone() # 获取当前batch的第16个单词作为label,并转换为行向量
    input_ids[:, 15] = token.get_vocab()[token.mask_token] # 将当前batch的第16个单词替换为[MASK]

    return input_ids, attention_mask, token_type_ids, labels

# 数据加载器
loader = DataLoader(
    dataset=dataset,
    batch_size=16,
    collate_fn=collate_fn,
    shuffle=True,
    drop_last=True
)

# for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader): # 取出第一个batch中的数据,进行试算
#     break

# 查看数据样例
# print(len(loader)) #574
# print(token.decode(input_ids[0])) #[CLS] 我 一 般 都 不 愿 意 写 点 评 的 , 但 这 [MASK] 的 是 我 入 住 过 的 五 星 级 酒 店 中 [SEP]
# print(token.decode(labels[0])) #真
# print(input_ids.shape, attention_mask.shape, token_type_ids.shape, labels.shape) #torch.Size([16, 30]) torch.Size([16, 30]) torch.Size([16, 30]) torch.Size([16])

# 加载预训练模型
pretrained = BertModel.from_pretrained('bert-base-chinese')

# 不训练,不需要计算梯度
# for param in pretrained.parameters():
#     param.requires_grad_(False)

# 模型试算
# out = pretrained(
#     input_ids=input_ids,
#     attention_mask=attention_mask,
#     token_type_ids=token_type_ids
# )

# print(out.last_hidden_state.shape) # torch.Size([16, 30, 768]) 每一个batch有16条数据,每一条数据有30个单词,每个单词的维度是768

# 定义下游任务模型
class Model(nn.Module): # 固定写法,至少需要下述2个函数,否则会报错
    def __init__(self):
        super().__init__()
        self.decoder = nn.Linear(768, token.vocab_size, bias=False) # 因为要预测是哪个单词,所以输出维度为字典长度
        self.bias = nn.Parameter(torch.zeros(token.vocab_size)) # 初始化bias为0
        self.decoder.bias = self.bias
    
    def forward(self, input_ids, attention_mask, token_type_ids):
        with torch.no_grad(): # 不改变预训练模型参数,所以不需要计算梯度
            out = pretrained(
                input_ids=input_ids,
                attention_mask=attention_mask,
                token_type_ids=token_type_ids
            )
        
        out = self.decoder(out.last_hidden_state[:, 15]) # 向解码器中输入第16个字符,即要预测的字符
        return out

model = Model()
# print(model(input_ids,attention_mask,token_type_ids).shape) # torch.Size([16, 21128]) 一个batch中有16条数据,每条数据预测结果的输出维度为21128,即字典长度

# 训练下游任务模型
optimizer = AdamW(model.parameters(), lr=5e-4)
criterion = nn.CrossEntropyLoss()

model.train() # 固定写法,与训练代码之间没有空行
for epoch in range(5): # 训练5个轮次
    for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader):
        out = model(
            input_ids=input_ids,
            attention_mask=attention_mask,
            token_type_ids=token_type_ids
        )
        loss = criterion(out, labels)
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()

        if i % 50 == 0: # 每50个batch输出一次结果
            out = out.argmax(dim=1) # argmax()获取最大值的索引
            accuracy = (out == labels).sum().item() / len(labels) 

            print(epoch, i, loss.item(), accuracy) # 输出训练轮次,第几个batch,loss值,训练集上的准确率

# 保存模型
def save_pretrained(model, path):
    # 保存模型,先利用os模块创建文件夹,后利用torch.save()写入模型文件
    os.makedirs(path, exist_ok=True)
    torch.save(model, os.path.join(path, 'model.pth'))

save_pretrained(model, './model/fillBlanks/')

# 测试
def test():
    model.eval() # 固定写法,与测试代码之间没有空行
    correct = 0
    total = 0

    loader_test = DataLoader(
        dataset=dataset_test,
        batch_size=32,
        collate_fn=collate_fn,
        shuffle=True,
        drop_last=True
    )

    for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader_test):
        with torch.no_grad():
            out = model(
                input_ids=input_ids,
                attention_mask=attention_mask,
                token_type_ids=token_type_ids
            )

            out = out.argmax(dim=1) # 维度为batch_size
            correct += (out == labels).sum().item() # sum()获取相等的个数
            total += len(labels)

            # 输出当前batch的第一条数据的预测结果
            print(token.decode(input_ids[0])) 
            print(token.decode(labels[0]), token.decode(out[0])) # 真实标签 预测结果

    print(correct / total) # 0.6839285714285714

test()

是否是下一个句子

import torch
import torch.nn as nn
from datasets import load_from_disk
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer, BertModel
from transformers import logging
from torch.optim import AdamW
import os
import random

# 设置transformers模块的日志等级,减少不必要的警告,对训练过程无影响,请忽略
logging.set_verbosity_error()

# 从本地加载数据集
dataset = load_from_disk('../datasets/ChnSentiCorp/data')

dataset_train = dataset['train']
dataset_validation = dataset['validation']
dataset_test = dataset['test']

# 定义数据集
class Dataset(Dataset):
    def __init__(self, dataset):
        
        def f(data):
            return len(data['text']) > 40
        
        self.dataset = dataset.filter(f) # 只保留text长度大于40的文本数据
    
    def __len__(self):
        return len(self.dataset)
    
    def __getitem__(self, i):
        text = self.dataset[i]['text']

        # 将一句话切分为前半句和后半句
        sentence1 = text[:20]
        sentence2 = text[20:40]
        label = 0 # 两个句子相邻

        # 有一半概率将后半句话替换为一句无关的话
        if random.randint(0, 1) == 0:
            j = random.randint(0, len(self.dataset)-1) # 随机获取数据集中数据的索引
            sentence2 = self.dataset[j]['text'][20:40] # 将后半句话替换为随机句子的后半句
            label = 1 # 两个句子不相邻
        
        return sentence1, sentence2, label

# 查看数据样例
dataset = Dataset(dataset_train)
dataset_test = Dataset(dataset_test)

# print(len(dataset)) # 8001
# print(dataset[0]) # ('选择珠江花园的原因就是方便,有电动扶梯直', '接到达海边,周围餐馆、食廊、商场、超市、', 0)

# 加载字典和分词工具
token = BertTokenizer.from_pretrained('bert-base-chinese')

# 定义批处理函数
def collate_fn(data):
    sents = [i[:2] for i in data]
    labels = [i[2] for i in data]

    # 对文本数据进行编码
    data = token.batch_encode_plus(
        batch_text_or_text_pairs=sents, # 编码一对一对的句子
        truncation=True,
        padding='max_length',
        max_length=45,
        return_tensors='pt',
        return_length=True,
        add_special_tokens=True
    )
    # input_ids:编码之后的数据
    # attention_mask:padding位置是0,其余位置是1
    # token_type_ids:第一句话和特殊符号位置是0,第二句话位置是1
    input_ids = data['input_ids']
    attention_mask = data['attention_mask']
    token_type_ids = data['token_type_ids']
    labels = torch.LongTensor(labels)

    return input_ids, attention_mask, token_type_ids, labels

# 数据加载器
loader = DataLoader(
    dataset=dataset,
    batch_size=8,
    collate_fn=collate_fn,
    shuffle=True,
    drop_last=True
)

# for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader):
#     break

# 查看数据样例
# print(len(loader)) # 1000
# print(token.decode(input_ids[0])) # [CLS] 这 本 书 的 版 本 一 般 , 不 敢 贸 然 说 是 盗 版 的 , 但 [SEP] 设 施 比 较 老 旧 , 服 务 不 好 , 我 两 次 将 洗 发 液 忘 [SEP] [PAD] [PAD]
# print(input_ids.shape, attention_mask.shape, token_type_ids.shape, labels.shape) # torch.Size([8, 45]) torch.Size([8, 45]) torch.Size([8, 45]) torch.Size([8])

# 加载预训练模型
pretrained = BertModel.from_pretrained('bert-base-chinese')

# 定义下游任务模型
class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc = nn.Linear(768, 2)
    
    def forward(self, input_ids, attention_mask, token_type_ids):
        with torch.no_grad():
            out = pretrained(
                input_ids=input_ids,
                attention_mask=attention_mask,
                token_type_ids=token_type_ids
            )
        
        out = self.fc(out.last_hidden_state[:, 0])
        out = out.softmax(dim=1)
        return out

model = Model()

# 训练下游任务模型
optimizer = AdamW(model.parameters(), lr=5e-4)
criterion = nn.CrossEntropyLoss()

model.train()
for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader):
    out = model(
        input_ids=input_ids,
        attention_mask=attention_mask,
        token_type_ids=token_type_ids
    )
    loss = criterion(out, labels)
    loss.backward()
    optimizer.step()
    optimizer.zero_grad()

    if i % 5 == 0: # 每5个batch输出一次结果
        out = out.argmax(dim=1)
        accuracy = (out == labels).sum().item() / len(labels)
        print(i, loss.item(), accuracy)

    if i == 300: # 训练300个batch
        break

# 保存模型
def save_pretrained(model, path):
    # 保存模型,先利用os模块创建文件夹,后利用torch.save()写入模型文件
    os.makedirs(path, exist_ok=True)
    torch.save(model, os.path.join(path, 'model.pth'))

save_pretrained(model, './model/isNext/')

# 测试
def test():
    model.eval()
    correct = 0
    total = 0

    loader_test = DataLoader(
        dataset=dataset_test,
        batch_size=32,
        collate_fn=collate_fn,
        shuffle=True,
        drop_last=True
    )

    for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader_test):
        with torch.no_grad():
            out = model(
                input_ids=input_ids,
                attention_mask=attention_mask,
                token_type_ids=token_type_ids
            )

        pred = out.argmax(dim=1)
        correct += (pred == labels).sum().item()
        total += len(labels)
    
    print(correct / total) # 0.8583333333333333

test()

你可能感兴趣的:(nlp学习笔记,自然语言处理,深度学习,人工智能)