精华内容
下载资源
问答
  • bert文本分类
    万次阅读 多人点赞
    2019-10-25 17:48:34

    目录

    一、Bert 预训练模型准备

    二、Bert 模型文本分类

    1、数据准备

    2、代码实现

    3、分类过程与结果


    一、Bert 预训练模型准备

    中文预训练模型下载      当Bert遇上Keras:这可能是Bert最简单的打开姿势      keras-bert

    不同模型的性能对比如下(可根据自己的数据选择合适的模型,模型越大需要训练的时间越长)

    模型开发集测试集
    BERT83.1 (82.7) / 89.9 (89.6)82.2 (81.6) / 89.2 (88.8)
    ERNIE73.2 (73.0) / 83.9 (83.8)71.9 (71.4) / 82.5 (82.3)
    BERT-wwm84.3 (83.4) / 90.5 (90.2)82.8 (81.8) / 89.7 (89.0)
    BERT-wwm-ext85.0 (84.5) / 91.2 (90.9)83.6 (83.0) / 90.4 (89.9)
    RoBERTa-wwm-ext86.6 (85.9) / 92.5 (92.2)85.6 (85.2) / 92.0 (91.7)
    RoBERTa-wwm-ext-large89.6 (89.1) / 94.8 (94.4)89.6 (88.9) / 94.5 (94.1)

    二、Bert 模型文本分类

    1、数据准备

    使用的仍是用户评论情感极性判别的数据

    训练集:data_train.csv ,样本数为82025,情感极性标签(0:负面、1:中性、2:正面) 

    测试集:data_test.csv ,样本数为35157

    评论数据主要包括:食品餐饮类,旅游住宿类,金融服务类,医疗服务类,物流快递类;部分数据如下:

    2、代码实现

    import pandas as pd
    import codecs, gc
    import numpy as np
    from sklearn.model_selection import KFold
    from keras_bert import load_trained_model_from_checkpoint, Tokenizer
    from keras.metrics import top_k_categorical_accuracy
    from keras.layers import *
    from keras.callbacks import *
    from keras.models import Model
    import keras.backend as K
    from keras.optimizers import Adam
    from keras.utils import to_categorical
    
    #读取训练集和测试集
    train_df=pd.read_csv('data/data_train.csv', sep='\t', names=['id', 'type', 'contents', 'labels']).astype(str)
    test_df=pd.read_csv('data/data_test.csv', sep='\t', names=['id', 'type', 'contents']).astype(str)
    
    maxlen = 100  #设置序列长度为120,要保证序列长度不超过512
    
    #预训练好的模型
    config_path = 'chinese_roberta_wwm_large_ext_L-24_H-1024_A-16/bert_config.json'
    checkpoint_path = 'chinese_roberta_wwm_large_ext_L-24_H-1024_A-16/bert_model.ckpt'
    dict_path = 'chinese_roberta_wwm_large_ext_L-24_H-1024_A-16/vocab.txt'
    
    #将词表中的词编号转换为字典
    token_dict = {}
    with codecs.open(dict_path, 'r', 'utf8') as reader:
        for line in reader:
            token = line.strip()
            token_dict[token] = len(token_dict)
    
    #重写tokenizer        
    class OurTokenizer(Tokenizer):
        def _tokenize(self, text):
            R = []
            for c in text:
                if c in self._token_dict:
                    R.append(c)
                elif self._is_space(c):
                    R.append('[unused1]')  # 用[unused1]来表示空格类字符
                else:
                    R.append('[UNK]')  # 不在列表的字符用[UNK]表示
            return R
    tokenizer = OurTokenizer(token_dict)
    
    #让每条文本的长度相同,用0填充
    def seq_padding(X, padding=0):
        L = [len(x) for x in X]
        ML = max(L)
        return np.array([
            np.concatenate([x, [padding] * (ML - len(x))]) if len(x) < ML else x for x in X
        ])
    
    #data_generator只是一种为了节约内存的数据方式
    class data_generator:
        def __init__(self, data, batch_size=32, shuffle=True):
            self.data = data
            self.batch_size = batch_size
            self.shuffle = shuffle
            self.steps = len(self.data) // self.batch_size
            if len(self.data) % self.batch_size != 0:
                self.steps += 1
    
        def __len__(self):
            return self.steps
    
        def __iter__(self):
            while True:
                idxs = list(range(len(self.data)))
    
                if self.shuffle:
                    np.random.shuffle(idxs)
    
                X1, X2, Y = [], [], []
                for i in idxs:
                    d = self.data[i]
                    text = d[0][:maxlen]
                    x1, x2 = tokenizer.encode(first=text)
                    y = d[1]
                    X1.append(x1)
                    X2.append(x2)
                    Y.append([y])
                    if len(X1) == self.batch_size or i == idxs[-1]:
                        X1 = seq_padding(X1)
                        X2 = seq_padding(X2)
                        Y = seq_padding(Y)
                        yield [X1, X2], Y[:, 0, :]
                        [X1, X2, Y] = [], [], []
    
    #计算top-k正确率,当预测值的前k个值中存在目标类别即认为预测正确                 
    def acc_top2(y_true, y_pred):
        return top_k_categorical_accuracy(y_true, y_pred, k=2)
    
    #bert模型设置
    def build_bert(nclass):
        bert_model = load_trained_model_from_checkpoint(config_path, checkpoint_path, seq_len=None)  #加载预训练模型
    
        for l in bert_model.layers:
            l.trainable = True
    
        x1_in = Input(shape=(None,))
        x2_in = Input(shape=(None,))
    
        x = bert_model([x1_in, x2_in])
        x = Lambda(lambda x: x[:, 0])(x) # 取出[CLS]对应的向量用来做分类
        p = Dense(nclass, activation='softmax')(x)
    
        model = Model([x1_in, x2_in], p)
        model.compile(loss='categorical_crossentropy',
                      optimizer=Adam(1e-5),    #用足够小的学习率
                      metrics=['accuracy', acc_top2])
        print(model.summary())
        return model
    
    #训练数据、测试数据和标签转化为模型输入格式
    DATA_LIST = []
    for data_row in train_df.iloc[:].itertuples():
        DATA_LIST.append((data_row.contents, to_categorical(data_row.labels, 3)))
    DATA_LIST = np.array(DATA_LIST)
    
    DATA_LIST_TEST = []
    for data_row in test_df.iloc[:].itertuples():
        DATA_LIST_TEST.append((data_row.contents, to_categorical(0, 3)))
    DATA_LIST_TEST = np.array(DATA_LIST_TEST)
    
    #交叉验证训练和测试模型
    def run_cv(nfold, data, data_labels, data_test):
        kf = KFold(n_splits=nfold, shuffle=True, random_state=520).split(data)
        train_model_pred = np.zeros((len(data), 3))
        test_model_pred = np.zeros((len(data_test), 3))
    
        for i, (train_fold, test_fold) in enumerate(kf):
            X_train, X_valid, = data[train_fold, :], data[test_fold, :]
    
            model = build_bert(3)
            early_stopping = EarlyStopping(monitor='val_acc', patience=3)   #早停法,防止过拟合
            plateau = ReduceLROnPlateau(monitor="val_acc", verbose=1, mode='max', factor=0.5, patience=2) #当评价指标不在提升时,减少学习率
            checkpoint = ModelCheckpoint('./bert_dump/' + str(i) + '.hdf5', monitor='val_acc',verbose=2, save_best_only=True, mode='max', save_weights_only=True) #保存最好的模型
    
            train_D = data_generator(X_train, shuffle=True)
            valid_D = data_generator(X_valid, shuffle=True)
            test_D = data_generator(data_test, shuffle=False)
            #模型训练
            model.fit_generator(
                train_D.__iter__(),
                steps_per_epoch=len(train_D),
                epochs=5,
                validation_data=valid_D.__iter__(),
                validation_steps=len(valid_D),
                callbacks=[early_stopping, plateau, checkpoint],
            )
    
            # model.load_weights('./bert_dump/' + str(i) + '.hdf5')
    
            # return model
            train_model_pred[test_fold, :] = model.predict_generator(valid_D.__iter__(), steps=len(valid_D), verbose=1)
            test_model_pred += model.predict_generator(test_D.__iter__(), steps=len(test_D), verbose=1)
    
            del model
            gc.collect()   #清理内存
            K.clear_session()   #clear_session就是清除一个session
            # break
    
        return train_model_pred, test_model_pred
    
    #n折交叉验证
    train_model_pred, test_model_pred = run_cv(2, DATA_LIST, None, DATA_LIST_TEST)
    
    test_pred = [np.argmax(x) for x in test_model_pred]
    
    #将测试集预测结果写入文件
    output=pd.DataFrame({'id':test_df.id,'sentiment':test_pred})
    output.to_csv('data/results.csv', index=None)
    
    

    3、分类过程与结果

    在服务器上跑了两天,终于完成了……

    最终提交结果F1-score达到了94.90%,比使用的其他模型效果都好。

    直接看排名结果,一下子上升到了第一,哈哈哈

    Bert文本分类(keras-bert实现)源代码及数据集资源下载:

    项目实战-Bert文本分类(keras-bert实现)源代码及数据集.zip-自然语言处理文档类资源-CSDN下载

    本人博文NLP学习内容目录:

    一、NLP基础学习

    1、NLP学习路线总结

    2、TF-IDF算法介绍及实现

    3、NLTK使用方法总结

    4、英文自然语言预处理方法总结及实现

    5、中文自然语言预处理方法总结及实现

    6、NLP常见语言模型总结

    7、NLP数据增强方法总结及实现

    8、TextRank算法介绍及实现

    9、NLP关键词提取方法总结及实现

    10、NLP词向量和句向量方法总结及实现

    11、NLP句子相似性方法总结及实现

    12、NLP中文句法分析

    二、NLP项目实战

    1、项目实战-英文文本分类-电影评论情感判别

    2、项目实战-中文文本分类-商品评论情感判别

    3、项目实战-XGBoost与LightGBM文本分类

    4、项目实战-TextCNN文本分类实战

    5、项目实战-Bert文本分类实战

    6、项目实战-NLP中文句子类型判别和分类实战

    交流学习资料共享欢迎入群:955817470(群一),801295159(群二)

    更多相关内容
  • BERT文本分类数据

    2021-01-11 21:27:54
    BERT文本分类代码对应的数据
  • 自然语言处理动手学Bert文本分类,本套课程基于Pytorch最新1.4版本来实现利用Bert实现中文文本分类任务,延续动手学系列课程风格,全程手敲代码,跟着杨博一行一行代码撸起来。
  • bert文本分类 代码+数据bert文本分类 代码+数据bert文本分类 代码+数据
  • 1、内容概要:本资源主要基于bert(keras)实现文本分类,适用于初学者学习文本分类使用。 2、数据集为电商真实商品评论数据,主要包括训练集data_train,测试集data_test ,经过预处理的训练集clean_data_train和...
  • PyTorch Bert文本分类

    千次阅读 2022-03-31 23:39:16
    最近pytorch大火,而目前很少有博客完整的给出pytorch-bert的应用代码,本文从最简单的中文文本分类入手,一步一步的给出每段代码~ (代码简单清晰,读者有兴趣可上手实践) 首先安装pytorch-bert库, 即:pip ...

    改文章转载于作者:weixin_40001805
    仅供学习参考!!!

    在这里插入图片描述
    之前用bert一直都是根据keras-bert封装库操作的,操作非常简便(可参考苏剑林大佬博客当Bert遇上Keras:这可能是Bert最简单的打开姿势),这次想要来尝试一下基于pytorch的bert实践。

    最近pytorch大火,而目前很少有博客完整的给出pytorch-bert的应用代码,本文从最简单的中文文本分类入手,一步一步的给出每段代码~ (代码简单清晰,读者有兴趣可上手实践)

    • 首先安装pytorch-bert库, 即:pip install pytorch_pretrained_bert;
    • 然后下载预训练模型权重,这里下载的是 chinese_roberta_wwm_ext_pytorch
      ,下载链接为中文BERT-wwm系列模型 (这里可选择多种模型);
    • 数据集选择的THUCNews,整理出18w条数据,10类新闻文本的中文分类问题(10分类),每类新闻数据量相等,为1.8w条,数据集来自train.txt(只选择了网址里的train.txt),
      数据集的具体格式如下。
      下面进入代码阶段。(训练环境为Google Colab)

    1.导入必要的库

    # coding: UTF-8
    import torch
    import time 
    import torch.nn as nn
    import torch.nn.functional as F 
    from pytorch_pretrained_bert import BertModel, BertTokenizer, BertConfig, BertAdam
    import pandas as pd 
    import numpy as np 
    from tqdm import tqdm 
    from torch.utils.data import *
     
    path = "data/"
    bert_path = "chinese_roberta_wwm_ext_pytorch/"
    tokenizer = BertTokenizer(vocab_file=bert_path + "vocab.txt")  # 初始化分词器
    

    2.预处理数据集

    input_ids = []     # input char ids
    input_types = []   # segment ids
    input_masks = []   # attention mask
    label = []         # 标签
    pad_size = 32      # 也称为 max_len (前期统计分析,文本长度最大值为38,取32即可覆盖99%)
     
    with open(path + "train.txt", encoding='utf-8') as f:
        for i, l in tqdm(enumerate(f)): 
            x1, y = l.strip().split('t')
            x1 = tokenizer.tokenize(x1)
            tokens = ["[CLS]"] + x1 + ["[SEP]"]
            
            # 得到input_id, seg_id, att_mask
            ids = tokenizer.convert_tokens_to_ids(tokens)
            types = [0] *(len(ids))
            masks = [1] * len(ids)
            # 短则补齐,长则切断
            if len(ids) < pad_size:
                types = types + [1] * (pad_size - len(ids))  # mask部分 segment置为1
                masks = masks + [0] * (pad_size - len(ids))
                ids = ids + [0] * (pad_size - len(ids))
            else:
                types = types[:pad_size]
                masks = masks[:pad_size]
                ids = ids[:pad_size]
            input_ids.append(ids)
            input_types.append(types)
            input_masks.append(masks)
    #         print(len(ids), len(masks), len(types)) 
            assert len(ids) == len(masks) == len(types) == pad_size
            label.append([int(y)])
    

    输出:180000it [00:26, 6728.85it/s] (26秒,速度较快)

    3.切分训练集和测试集

    # 随机打乱索引
    random_order = list(range(len(input_ids)))
    np.random.seed(2020)   # 固定种子
    np.random.shuffle(random_order)
    print(random_order[:10])
     
    # 4:1 划分训练集和测试集
    input_ids_train = np.array([input_ids[i] for i in random_order[:int(len(input_ids)*0.8)]])
    input_types_train = np.array([input_types[i] for i in random_order[:int(len(input_ids)*0.8)]])
    input_masks_train = np.array([input_masks[i] for i in random_order[:int(len(input_ids)*0.8)]])
    y_train = np.array([label[i] for i in random_order[:int(len(input_ids) * 0.8)]])
    print(input_ids_train.shape, input_types_train.shape, input_masks_train.shape, y_train.shape)
     
    input_ids_test = np.array([input_ids[i] for i in random_order[int(len(input_ids)*0.8):]])
    input_types_test = np.array([input_types[i] for i in random_order[int(len(input_ids)*0.8):]])
    input_masks_test = np.array([input_masks[i] for i in random_order[int(len(input_ids)*0.8):]])
    y_test = np.array([label[i] for i in random_order[int(len(input_ids) * 0.8):]])
    print(input_ids_test.shape, input_types_test.shape, input_masks_test.shape, y_test.shape)
    

    得到结果
    在这里插入图片描述

    4.加载到高效的DataLoader

    BATCH_SIZE = 16
    train_data = TensorDataset(torch.LongTensor(input_ids_train), 
                               torch.LongTensor(input_types_train), 
                               torch.LongTensor(input_masks_train), 
                               torch.LongTensor(y_train))
    train_sampler = RandomSampler(train_data)  
    train_loader = DataLoader(train_data, sampler=train_sampler, batch_size=BATCH_SIZE)
     
    test_data = TensorDataset(torch.LongTensor(input_ids_test), 
                              torch.LongTensor(input_types_test), 
                             torch.LongTensor(input_masks_test),
                              torch.LongTensor(y_test))
    test_sampler = SequentialSampler(test_data)
    test_loader = DataLoader(test_data, sampler=test_sampler, batch_size=BATCH_SIZE)
    

    5.定义bert模型

    class Model(nn.Module):
        def __init__(self):
            super(Model, self).__init__()
            self.bert = BertModel.from_pretrained(bert_path)  # /bert_pretrain/
            for param in self.bert.parameters():
                param.requires_grad = True  # 每个参数都要 求梯度
            self.fc = nn.Linear(768, 10)   # 768 -> 2
     
        def forward(self, x):
            context = x[0]  # 输入的句子   (ids, seq_len, mask)
            types = x[1]
            mask = x[2]  # 对padding部分进行mask,和句子相同size,padding部分用0表示,如:[1, 1, 1, 1, 0, 0]
            _, pooled = self.bert(context, token_type_ids=types, 
                                  attention_mask=mask, 
                                  output_all_encoded_layers=False) # 控制是否输出所有encoder层的结果
            out = self.fc(pooled)   # 得到10分类
            return out
    

    可以发现,bert模型的定义由于高效简易的封装库存在,使得定义模型较为容易,如果想要在bert之后加入cnn/rnn等层,可在这里定义。

    6.实例化bert模型

    DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model = Model().to(DEVICE)
    print(model) 
    

    得到结果
    在这里插入图片描述

    bert模型结构,未完整输出,可根据这个输出学习bert的内部结构

    7.定义优化器

    param_optimizer = list(model.named_parameters())  # 模型参数名字列表
    no_decay = ['bias', 'LayerNorm.bias', 'LayerNorm.weight']
    optimizer_grouped_parameters = [
        {'params': [p for n, p in param_optimizer if not any(nd in n for nd in no_decay)], 'weight_decay': 0.01},
        {'params': [p for n, p in param_optimizer if any(nd in n for nd in no_decay)], 'weight_decay': 0.0}]
     
    NUM_EPOCHS = 3
    optimizer = BertAdam(optimizer_grouped_parameters,
                         lr=2e-5,
                         warmup=0.05,
                         t_total=len(train_loader) * NUM_EPOCHS
                        )
     
    # optimizer = torch.optim.Adam(model.parameters(), lr=2e-5)   # 简单起见,可用这一行代码完事
    

    8.定义训练函数和测试函数

    def train(model, device, train_loader, optimizer, epoch):   # 训练模型
        model.train()
        best_acc = 0.0 
        for batch_idx, (x1,x2,x3, y) in enumerate(train_loader):
            start_time = time.time()
            x1,x2,x3, y = x1.to(device), x2.to(device), x3.to(device), y.to(device)
            y_pred = model([x1, x2, x3])  # 得到预测结果
            model.zero_grad()             # 梯度清零
            loss = F.cross_entropy(y_pred, y.squeeze())  # 得到loss
            loss.backward()
            optimizer.step()
            if(batch_idx + 1) % 100 == 0:    # 打印loss
                print('Train Epoch: {} [{}/{} ({:.2f}%)]tLoss: {:.6f}'.format(epoch, (batch_idx+1) * len(x1), 
                                                                               len(train_loader.dataset),
                                                                               100. * batch_idx / len(train_loader), 
                                                                               loss.item()))  # 记得为loss.item()
     
    def test(model, device, test_loader):    # 测试模型, 得到测试集评估结果
        model.eval()
        test_loss = 0.0 
        acc = 0 
        for batch_idx, (x1,x2,x3, y) in enumerate(test_loader):
            x1,x2,x3, y = x1.to(device), x2.to(device), x3.to(device), y.to(device)
            with torch.no_grad():
                y_ = model([x1,x2,x3])
            test_loss += F.cross_entropy(y_, y.squeeze())
            pred = y_.max(-1, keepdim=True)[1]   # .max(): 2输出,分别为最大值和最大值的index
            acc += pred.eq(y.view_as(pred)).sum().item()    # 记得加item()
        test_loss /= len(test_loader)
        print('nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)'.format(
              test_loss, acc, len(test_loader.dataset),
              100. * acc / len(test_loader.dataset)))
        return acc / len(test_loader.dataset)
    

    9.开始训练和测试

    best_acc = 0.0 
    PATH = 'roberta_model.pth'  # 定义模型保存路径
    for epoch in range(1, NUM_EPOCHS+1):  # 3个epoch
        train(model, DEVICE, train_loader, optimizer, epoch)
        acc = test(model, DEVICE, test_loader)
        if best_acc < acc: 
            best_acc = acc 
            torch.save(model.state_dict(), PATH)  # 保存最优模型
        print("acc is: {:.4f}, best acc is {:.4f}n".format(acc, best_acc))  
    

    输出:(训练时间较长,这里只训练了一个epoch,测试集得到0.9407的accuracy)
    在这里插入图片描述

    10.加载最优模型进行测试

    model.load_state_dict(torch.load("roberta_model.pth"))
    acc = test(model, DEVICE, test_loader)
     
    # 如果打比赛的话,下面代码也可参考
    """
    # 测试集提交
    PATH = "roberta_model.pth"
    model.load_state_dict(torch.load(PATH))
    def test_for_submit(model, device, test_loader):    # 测试模型
        model.eval()
        preds = []
        for batch_idx, (x1,x2,x3) in tqdm(enumerate(test_loader)):
            x1,x2,x3 = x1.to(device), x2.to(device), x3.to(device)
            with torch.no_grad():
                y_ = model([x1,x2,x3])
            pred = y_.max(-1, keepdim=True)[1].squeeze().cpu().tolist()   
            # .max() 2输出,分别为最大值和最大值的index
            preds.extend(pred) 
        return preds 
    preds = test_for_submit(model, DEVICE, test_loader)
    """
    

    得到结果
    在这里插入图片描述


    经过以上10步,即可建立起较为完整的pytorch-bert文本分类体系,代码也较为简单易懂,对读者有帮助记得点个赞呀~

    完结-

    展开全文
  • Bert文本分类(基于keras-bert实现训练,保存,加载,预测单个文本).zip 大学生课程设计 基于python的课程设计 自己大二写的课程设计
  • Bert文本分类实战(附代码讲解)

    千次阅读 多人点赞 2022-03-23 10:52:46
    BERT全称是Bidirectional Encoder Representations from Transformers,是google最新提出的NLP预训练方法,在大型文本语料库(如维基百科)上训练通用的“语言理解”模型,然后将该模型用于我们关心的下游NLP任务...

    目录

    一、Bert简介

    1.1 Transformer模型

    1.2 Bert模型

    二、BERT的发展历程

    2.1 One-Hot 编码

    2.1.1 无法计算词相似度

    2.1.2 Sparsity(稀疏性) 

    ​2.2 Word2vec

    2.3 BERT的诞生

    三、BERT 的训练过程

    3.1  Masked LM(Language Model)

    3.2 Next Sentence Prediction

    四、BERT的用途

    4.1 文本分类

    4.2 单词分类

    4.3 判断两个句子之间的关系 

    4.4  QA(问答系统)

    五、BERT文本分类实战

    5.1 环境搭建

    5.2 模型下载

    5.3 数据准备

    5.4 整体代码架构 

    5.5 代码分解

    5.5.1 bert.py 

    5.5.2 main.py

    5.5.3 train.py

    5.5.4 utils.py

    5.5.5 结果分析

    参考文档


    一、Bert简介

    BERT全称是Bidirectional Encoder Representations from Transformers,是google最新提出的NLP预训练方法,在大型文本语料库(如维基百科)上训练通用的“语言理解”模型,然后将该模型用于我们关心的下游NLP任务(如分类、阅读理解)。 BERT优于以前的方法,因为它是用于预训练NLP的第一个**无监督深度双向**系统,从名字我们能看出该模型两个核心特质:依赖于Transformer以及双向,同时它也是木偶动画《芝麻街》里面的角色,它还有个兄弟EMLo。长右边这样:

                                                               

    关于Bert这个模型的神奇之处我就不在这里多说了,它直接颠覆了人们对Pretrained model的理解。尽管Bert模型有多得骇人听闻的参数,但是我们可以直接借助迁移学习的想法使用已经预训练好的模型参数,并根据自己的实际任务进行fine-tuning。复旦大学的一篇论文《How to Fine-Tune BERT for Text Classification》(下载地址:https://arxiv.org/pdf/1905.05583.pdf)给出了一些调节Bert参数的建议,作者针对文本分类任务的BERT微调方法,给出了微调模式的一般解决方案。最后,提出的解决方案在8个广泛研究的文本分类数据集上获取了最新的结果,具体实现步骤如下:

    (1)进一步在开放域预训练BERT;

    (2)采用多任务方式可选择性地微调BERT;

    (3)在目标任务上微调BERT。同时研究了fine-tuning技术对Bert在长文本任务、隐藏层选择、隐藏层学习率、知识遗忘、少样本学习问题上的影响。

    1. 微调策略:不同网络层包含不同的特征信息,哪一层更有助于目标任务?这是一个考虑的方向;

    2. 进一步预训练:在目标域进一步得到预训练模型;

    3. 多任务微调:多任务可以挖掘共享信息,同时对所有任务进行微调是否,使用多任务策略对结果有帮助。

    1.1 Transformer模型

    为了描述得更清楚,还是先把transformer的模型结构列在下面(来自论文《Attention Is All You Need》):

    1.2 Bert模型

    而Bert则是多个Transformer的双向叠加,中间每一个蓝色的圈圈都是一个transformer(来自论文《Pre-training of Deep Bidirectional Transformers for Language Understanding》)。

    二、BERT的发展历程

    2.1 One-Hot 编码

    One-Hot编码,又称为一位有效编码,主要是采用N位状态寄存器来对N个状态进行编码,每个状态都由他独立的寄存器位,并且在任意时候只有一位有效。

    One-Hot编码是分类变量作为二进制向量的表示。这首先要求将分类值映射到整数值。然后,每个整数值被表示为二进制向量,除了整数的索引标记为1之外,其他都是零值。

    2.1.1 无法计算词相似度

    例如One-Hot编码如下:

    我们 = 0,1,0,0,0,0)   爬山 = 0,0,1,0,0,0

    运动 = 1,0,0,0,0,0)   昨天 = 0,0,0,1,0,0)  

    Euclidean distance(欧式距离) d=\sqrt{(x_1-x_2)^{2}+(y_1-y_2)^{2}}

    d(我们,爬山)=√2

    d(我们,运动)=√2

    d(运动,爬山)=√2

    d(运动,昨天)=√2

    Cosine similarity(余弦相似度)cos\theta = \frac{<a,b>}{|a||b|} 

    sim(我们,爬山)=0

    sim(我们,运动)=0

    sim(运动,爬山)=0

    sim(运动,昨天)=0

    结论:One-hot 无法表达单词间的相似度 

    2.1.2 Sparsity(稀疏性) 

    向量大小大于等于词典大小

    比如我们要对 “hello world” 进行one-hot编码,怎么做呢?

    1.确定要编码的对象--hello world,

    2.确定分类变量--h  e  l  l  o  空格  w  o  r  l  d,共27种类别(26个小写字母 + 空格,);

    3.以上问题就相当于,有11个样本,每个样本有27个特征,将其转化为二进制向量表示,

    这里有一个前提,特征排列的顺序不同,对应的二进制向量亦不同(比如我把空格放在第一列和a放第一列,one-hot编码结果肯定是不同的)

    因此我们必须要事先约定特征排列的顺序:

    1. 27种特征首先进行整数编码:a--0,b--1,c--2,......,z--25,空格--26

    2. 27种特征按照整数编码的大小从前往后排列

    得到的one-hot编码如下:

    再比如:我们要对["中国", "美国", "日本"]进行one-hot编码,

    怎么做呢?

    1.确定要编码的对象--["中国", "美国", "日本", "美国"],

    2.确定分类变量--中国    美国    日本,共3种类别;

    3.以上问题就相当于,有3个样本,每个样本有3个特征,将其转化为二进制向量表示。

    我们首先进行特征的整数编码:中国--0,美国--1,日本--2,并将特征按照从小到大排列

    得到one-hot编码如下:

    ["中国", "美国", "日本", "美国"]  --->   [[1,0,0], [0,1,0], [0,0,1], [0,1,0]]

    2.2 Word2vec

    基于One-Hot编码存在的稀疏性和高维度性缺点,2013年,Google开源了一款用于词向量计算的工具——word2vec,引起了工业界和学术界的关注。首先,word2vec可以在百万数量级的词典和上亿的数据集上进行高效地训练;其次,该工具得到的训练结果——词向量(word embedding),可以很好地度量词与词之间的相似性。随着深度学习(Deep Learning)在自然语言处理中应用的普及,很多人误以为word2vec是一种深度学习算法。其实word2vec算法的背后是一个浅层神经网络。另外需要强调的一点是,word2vec是一个计算word vector的开源工具。当我们在说word2vec算法或模型的时候,其实指的是其背后用于计算word vector的CBoW模型和Skip-gram模型,下面我用图的方式直观描述word2vec的优越性。 

    From One-hot Respresentation to Distributed Respresentation

    维度灾难、无法保留词序信息、语义鸿沟                       词向量长度自定义(100200、300)

     

    2.3 BERT的诞生

    BERT 的思想其实很大程度上来源于 CBOW 模型,如果从准确率上说改进的话,BERT 利用更深的模型,以及海量的语料,得到的 embedding 表示,来做下游任务时的准确率是要比 word2vec 高不少的。实际上,这也离不开模型的“加码”以及数据的“巨大加码”。再从方法的意义角度来说,BERT 的重要意义在于给大量的 NLP 任务提供了一个泛化能力很强的预训练模型,而仅仅使用 word2vec 产生的词向量表示,不仅能够完成的任务比 BERT 少了很多,而且很多时候直接利用 word2vec 产生的词向量表示给下游任务提供信息,下游任务的表现不一定会很好,甚至会比较差。

    三、BERT 的训练过程

    BERT的核心过程

    1. 从句子中随机选取15%去除,作为模型预测目标,例如:

    Input: the man went to the [MASK1] . he bought a [MASK2] of milk.
    Labels: [MASK1] = store; [MASK2] = gallon
    

    2. 为了学习句子之间的关系。会从数据集抽取两个句子,其中第二句是第一句的下一句的概率是 50%

    Sentence A: the man went to the store .
    Sentence B: he bought a gallon of milk .
    Label: IsNextSentence
    
    Sentence A: the man went to the store .
    Sentence B: penguins are flightless .
    Label: NotNextSentence
    

    3.最后再将经过处理的句子传入大型 Transformer 模型,并通过两个损失函数同时学习上面两个目标就能完成训练。 

    在论文原文中,作者提出了两个预训练任务:Masked LM 和 Next Sentence Prediction,我们下面来做简单讲解。

    3.1  Masked LM(Language Model)

    Masked LM 的任务描述为:给定一句话,随机抹去这句话中的一个或几个词,要求根据剩余词汇预测被抹去的几个词分别是什么,如下图所示。

    BERT 模型的这个预训练过程其实就是在模仿我们学语言的过程,思想来源于「完形填空」的任务。具体来说,文章作者在一句话中随机选择 15% 的词汇用于预测。对于在原句中被抹去的词汇, 80% 情况下采用一个特殊符号 [MASK] 替换, 10% 情况下采用一个任意词替换,剩余 10% 情况下保持原词汇不变。

    80%的概率替换成[MASK],比如 my dog is hairy → my dog is [MASK]

    10%的概率替换成随机的一个词,比如 my dog is hairy → my dog is apple

    10%的概率替换成它本身,比如 my dog is hairy → my dog is hairy

    这么做的主要原因是:在后续微调任务中语句中并不会出现 [MASK] 标记,而且这么做的另一个好处是:预测一个词汇时,模型并不知道输入对应位置的词汇是否为正确的词汇( 10% 概率),这就迫使模型更多地依赖于上下文信息去预测词汇,并且赋予了模型一定的纠错能力。上述提到了这样做的一个缺点,其实这样做还有另外一个缺点,就是每批次数据中只有 15% 的标记被预测,这意味着模型可能需要更多的预训练步骤来收敛。

    3.2 Next Sentence Prediction

    Next Sentence Prediction 的任务描述为:给定一篇文章中的两句话,判断第二句话在文本中是否紧跟在第一句话之后,如下图所示。

    这个类似于「段落重排序」的任务,即:将一篇文章的各段打乱,让我们通过重新排序把原文还原出来,这其实需要我们对全文大意有充分、准确的理解。

    Next Sentence Prediction 任务实际上就是段落重排序的简化版:只考虑两句话,判断是否是一篇文章中的前后句。在实际预训练过程中,文章作者从文本语料库中随机选择 50% 正确语句对和 50% 错误语句对进行训练,与 Masked LM 任务相结合,让模型能够更准确地刻画语句乃至篇章层面的语义信息。

    BERT 模型通过对 Masked LM 任务和 Next Sentence Prediction 任务进行联合训练,使模型输出的每个字 / 词的向量表示都能尽可能全面、准确地刻画输入文本(单句或语句对)的整体信息,为后续的微调任务提供更好的模型参数初始值。

    四、BERT的用途

    尽管从直观上来看,Bert可能类似于Word2vec,返回单词们的稠密表示,但是根据不同的任务Bert可以有许多不同的用法。 

    4.1 文本分类

    尽管从直观上来看,Bert可能类似于Word2vec,返回单词们的稠密表示,但是根据不同的任务Bert可以有许多不同的用法。

    在这里插入图片描述

    4.2 单词分类

    每个单词都对应一个class。最典型的就是序列标注任务。

    在这里插入图片描述

    4.3 判断两个句子之间的关系 

    输入两个句子,output一个[CLS]的分类结果。

    在这里插入图片描述

    4.4  QA(问答系统)

    问答。需要答案落在输入语料里面。输入是文档以及问题,输出是答案Start的token的id(s)和结束的id(e)。
    比如,问题“什么导致的降水?”在文中的答案是第17个单词gravity(重力),所以s=17,e=17。

    在这里插入图片描述Bert如何实现这个任务呢?首先,还是学习到文档中每个单词的embedding,对应图中浅黄色的条条;然后再学一个橘黄色的条条,这个维度与单词的embedding是一毛一样的,因此这俩张量之间可以做一个dot product,这个类似于注意力。得到的结果通过softmax对每一个向量都返回一个概率,概率最高的token位置就是s。同理,蓝色的条条表示e。若e<s,那么此题无解。
    在这里插入图片描述

    五、BERT文本分类实战

    下面是代码部分,我们分别用Fine-tune和非Fine-tune模式下看一下分类效果 

     

     

    5.1 环境搭建

    pyotroch>=0.4.1

    Python 3.7

    条件允许可以使用GPU训练,我CPU、GPU分别都试了一下

    GPU服务器:3090Ti 20G 

    5.2 模型下载

    1. 下载bert:

    下载地址:GitHub - google-research/bert: TensorFlow code and pre-trained models for BERT

    2. 下载bert预训练模型:

    pytorch_model.bin
    bert_config.json
    vocab.txt 

    下载地址: https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-chinese.tar.gz

    5.3 数据准备

    将你的语料分成4个文件,分别为train.csv,test.csv,dev.csv,class.csv

    链接:https://pan.baidu.com/s/19QlxsVKNziRKvNismH4wvA 
    提取码:cx44

    具体操作:

    我的语料来自于情感分析比赛的,是判断新闻标题情感积极消极还是中性,首先使用pandas对语料进行处理,最终处理成“content+label”的格式。如图所示:

    5.4 整体代码架构 

    5.5 代码分解

    5.5.1 bert.py 

    # coding: UTF-8
    import torch
    import torch.nn as nn
    # from pytorch_pretrained_bert import BertModel, BertTokenizer
    from pytorch_pretrained import BertModel, BertTokenizer
    
    
    class Config(object):
    
        """配置参数"""
        def __init__(self, dataset):
            self.model_name = 'bert'
            self.train_path = dataset + '/data/train.txt'                                # 训练集
            self.dev_path = dataset + '/data/dev.txt'                                    # 验证集
            self.test_path = dataset + '/data/test.txt'                                  # 测试集
            self.class_list = [x.strip() for x in open(
                dataset + '/data/class.txt').readlines()]                                # 类别名单
            self.save_path = dataset + '/saved_dict/' + self.model_name + '.ckpt'        # 模型训练结果
            self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')   # 设备
    
            self.require_improvement = 1000                                 # 若超过1000batch效果还没提升,则提前结束训练
            self.num_classes = len(self.class_list)                         # 类别数
            self.num_epochs = 3                                             # epoch数
            self.batch_size = 128                                           # mini-batch大小
            self.pad_size = 32                                              # 每句话处理成的长度(短填长切)
            self.learning_rate = 1e-5                                       # 学习率
            self.bert_path = './bert_pretrain'                              # bert预训练模型位置
            self.tokenizer = BertTokenizer.from_pretrained(self.bert_path)  # bert切分词
            self.hidden_size = 768                                          # bert隐藏层个数
    
    
    class Model(nn.Module):
    
        def __init__(self, config):
            super(Model, self).__init__()
            self.bert = BertModel.from_pretrained(config.bert_path)
            for param in self.bert.parameters():
                param.requires_grad = True
            self.fc = nn.Linear(config.hidden_size, config.num_classes)
    
        def forward(self, x):
            context = x[0]  # 输入的句子
            mask = x[2]  # 对padding部分进行mask,和句子一个size,padding部分用0表示,如:[1, 1, 1, 1, 0, 0]
            _, pooled = self.bert(context, attention_mask=mask, output_all_encoded_layers=False)
            out = self.fc(pooled)
            return out
    

    5.5.2 main.py

    # coding: UTF-8
    import time
    import torch
    import numpy as np
    from train_eval import train, init_network
    from importlib import import_module
    import argparse
    from utils import build_dataset, build_iterator, get_time_dif
    
    parser = argparse.ArgumentParser(description='Bert-Chinese-Text-Classification')
    parser.add_argument('--model', type=str, default='bert', help='choose a model')
    args = parser.parse_args()
    
    
    if __name__ == '__main__':
        dataset = 'THUCNews'  # 数据集
    
        model_name = args.model  # bert
        x = import_module('models.' + model_name)
        config = x.Config(dataset)
        np.random.seed(1)
        torch.manual_seed(1)
        torch.cuda.manual_seed_all(4)
        torch.backends.cudnn.deterministic = True  # 保证每次结果一样
    
        start_time = time.time()
        print("Loading data...")
        train_data, dev_data, test_data = build_dataset(config)
        train_iter = build_iterator(train_data, config)
        dev_iter = build_iterator(dev_data, config)
        test_iter = build_iterator(test_data, config)
        time_dif = get_time_dif(start_time)
        print("Time usage:", time_dif)
    
        # train
        model = x.Model(config).to(config.device)
        train(config, model, train_iter, dev_iter, test_iter)
    

    5.5.3 train.py

    # coding: UTF-8
    import numpy as np
    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    from sklearn import metrics
    import time
    from utils import get_time_dif
    from pytorch_pretrained.optimization import BertAdam
    
    
    # 权重初始化,默认xavier
    def init_network(model, method='xavier', exclude='embedding', seed=123):
        for name, w in model.named_parameters():
            if exclude not in name:
                if len(w.size()) < 2:
                    continue
                if 'weight' in name:
                    if method == 'xavier':
                        nn.init.xavier_normal_(w)
                    elif method == 'kaiming':
                        nn.init.kaiming_normal_(w)
                    else:
                        nn.init.normal_(w)
                elif 'bias' in name:
                    nn.init.constant_(w, 0)
                else:
                    pass
    
    
    def train(config, model, train_iter, dev_iter, test_iter):
        start_time = time.time()
        model.train()
        param_optimizer = list(model.named_parameters())
        no_decay = ['bias', 'LayerNorm.bias', 'LayerNorm.weight']
        optimizer_grouped_parameters = [
            {'params': [p for n, p in param_optimizer if not any(nd in n for nd in no_decay)], 'weight_decay': 0.01},
            {'params': [p for n, p in param_optimizer if any(nd in n for nd in no_decay)], 'weight_decay': 0.0}]
        # optimizer = torch.optim.Adam(model.parameters(), lr=config.learning_rate)
        optimizer = BertAdam(optimizer_grouped_parameters,
                             lr=config.learning_rate,
                             warmup=0.05,
                             t_total=len(train_iter) * config.num_epochs)
        total_batch = 0  # 记录进行到多少batch
        dev_best_loss = float('inf')
        last_improve = 0  # 记录上次验证集loss下降的batch数
        flag = False  # 记录是否很久没有效果提升
        model.train()
        for epoch in range(config.num_epochs):
            print('Epoch [{}/{}]'.format(epoch + 1, config.num_epochs))
            for i, (trains, labels) in enumerate(train_iter):
                outputs = model(trains)
                model.zero_grad()
                loss = F.cross_entropy(outputs, labels)
                loss.backward()
                optimizer.step()
                if total_batch % 100 == 0:
                    # 每多少轮输出在训练集和验证集上的效果
                    true = labels.data.cpu()
                    predic = torch.max(outputs.data, 1)[1].cpu()
                    train_acc = metrics.accuracy_score(true, predic)
                    dev_acc, dev_loss = evaluate(config, model, dev_iter)
                    if dev_loss < dev_best_loss:
                        dev_best_loss = dev_loss
                        torch.save(model.state_dict(), config.save_path)
                        improve = '*'
                        last_improve = total_batch
                    else:
                        improve = ''
                    time_dif = get_time_dif(start_time)
                    msg = 'Iter: {0:>6},  Train Loss: {1:>5.2},  Train Acc: {2:>6.2%},  Val Loss: {3:>5.2},  Val Acc: {4:>6.2%},  Time: {5} {6}'
                    print(msg.format(total_batch, loss.item(), train_acc, dev_loss, dev_acc, time_dif, improve))
                    model.train()
                total_batch += 1
                if total_batch - last_improve > config.require_improvement:
                    # 验证集loss超过1000batch没下降,结束训练
                    print("No optimization for a long time, auto-stopping...")
                    flag = True
                    break
            if flag:
                break
        test(config, model, test_iter)
    
    
    def test(config, model, test_iter):
        # test
        model.load_state_dict(torch.load(config.save_path))
        model.eval()
        start_time = time.time()
        test_acc, test_loss, test_report, test_confusion = evaluate(config, model, test_iter, test=True)
        msg = 'Test Loss: {0:>5.2},  Test Acc: {1:>6.2%}'
        print(msg.format(test_loss, test_acc))
        print("Precision, Recall and F1-Score...")
        print(test_report)
        print("Confusion Matrix...")
        print(test_confusion)
        time_dif = get_time_dif(start_time)
        print("Time usage:", time_dif)
    
    
    def evaluate(config, model, data_iter, test=False):
        model.eval()
        loss_total = 0
        predict_all = np.array([], dtype=int)
        labels_all = np.array([], dtype=int)
        with torch.no_grad():
            for texts, labels in data_iter:
                outputs = model(texts)
                loss = F.cross_entropy(outputs, labels)
                loss_total += loss
                labels = labels.data.cpu().numpy()
                predic = torch.max(outputs.data, 1)[1].cpu().numpy()
                labels_all = np.append(labels_all, labels)
                predict_all = np.append(predict_all, predic)
    
        acc = metrics.accuracy_score(labels_all, predict_all)
        if test:
            report = metrics.classification_report(labels_all, predict_all, target_names=config.class_list, digits=4)
            confusion = metrics.confusion_matrix(labels_all, predict_all)
            return acc, loss_total / len(data_iter), report, confusion
        return acc, loss_total / len(data_iter)
    

    5.5.4 utils.py

    # coding: UTF-8
    import torch
    from tqdm import tqdm
    import time
    from datetime import timedelta
    
    PAD, CLS = '[PAD]', '[CLS]'  # padding符号, bert中综合信息符号
    
    
    def build_dataset(config):
    
        def load_dataset(path, pad_size=32):
            contents = []
            with open(path, 'r', encoding='UTF-8') as f:
                for line in tqdm(f):
                    lin = line.strip()
                    if not lin:
                        continue
                    content, label = lin.split('\t')
                    token = config.tokenizer.tokenize(content)
                    token = [CLS] + token
                    seq_len = len(token)
                    mask = []
                    token_ids = config.tokenizer.convert_tokens_to_ids(token)
    
                    if pad_size:
                        if len(token) < pad_size:
                            mask = [1] * len(token_ids) + [0] * (pad_size - len(token))
                            token_ids += ([0] * (pad_size - len(token)))
                        else:
                            mask = [1] * pad_size
                            token_ids = token_ids[:pad_size]
                            seq_len = pad_size
                    contents.append((token_ids, int(label), seq_len, mask))
            return contents
        train = load_dataset(config.train_path, config.pad_size)
        dev = load_dataset(config.dev_path, config.pad_size)
        test = load_dataset(config.test_path, config.pad_size)
        return train, dev, test
    
    
    class DatasetIterater(object):
        def __init__(self, batches, batch_size, device):
            self.batch_size = batch_size
            self.batches = batches
            self.n_batches = len(batches) // batch_size
            self.residue = False  # 记录batch数量是否为整数
            if len(batches) % self.n_batches != 0:
                self.residue = True
            self.index = 0
            self.device = device
    
        def _to_tensor(self, datas):
            x = torch.LongTensor([_[0] for _ in datas]).to(self.device)
            y = torch.LongTensor([_[1] for _ in datas]).to(self.device)
    
            # pad前的长度(超过pad_size的设为pad_size)
            seq_len = torch.LongTensor([_[2] for _ in datas]).to(self.device)
            mask = torch.LongTensor([_[3] for _ in datas]).to(self.device)
            return (x, seq_len, mask), y
    
        def __next__(self):
            if self.residue and self.index == self.n_batches:
                batches = self.batches[self.index * self.batch_size: len(self.batches)]
                self.index += 1
                batches = self._to_tensor(batches)
                return batches
    
            elif self.index >= self.n_batches:
                self.index = 0
                raise StopIteration
            else:
                batches = self.batches[self.index * self.batch_size: (self.index + 1) * self.batch_size]
                self.index += 1
                batches = self._to_tensor(batches)
                return batches
    
        def __iter__(self):
            return self
    
        def __len__(self):
            if self.residue:
                return self.n_batches + 1
            else:
                return self.n_batches
    
    
    def build_iterator(dataset, config):
        iter = DatasetIterater(dataset, config.batch_size, config.device)
        return iter
    
    
    def get_time_dif(start_time):
        """获取已使用时间"""
        end_time = time.time()
        time_dif = end_time - start_time
        return timedelta(seconds=int(round(time_dif)))
    

    详细代码和数据集均放在我Github上,大家可以去下载https://github.com/LePetitPrinceWh/Bert-Pytorch-TextClassification.git

    5.5.5 结果分析

    从运行结果可知,Bert文本分类的效果还是比较好的。 

    当我们把Bert.py文件的这一行代码将True改为False,就是模型训练变为非Fin Tune情况下

    param.requires_grad = False #非Fine Tune的情况

    提示:

    给定预训练模型(Pre_trained model),基于模型进行微调(Fine Tune)。相对于从头开始训练(Training a model from scatch),微调为你省去大量计算资源和计算时间,提高了计算效率,甚至提高准确率。 

    我们发现在非 Fine Tune 的情况下,准确率要降低很多。后面我会在Bert结合CNN、RNN等其他模型,并对比实验结果。

    参考文档

    1. BERT模型的详细介绍_IT之一小佬的博客-CSDN博客_bert模型

    2. 《How to Fine-Tune BERT for Text Classification》-阅读心得 - 今夜无风 - 博客园 

    3. BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding_Hi-Cloud的博客-CSDN博客

    4. BERT解析及文本分类应用 - xlturing - 博客园

    5. 基于BERT做中文文本分类(情感分析)_我开心呀的博客-CSDN博客_bert中文文本分类 

    6. BERT模型图解 - Milburn - 博客园 

    7. Bert原理_五月的echo的博客-CSDN博客_bert分类原理 

    8. 《https://arxiv.org/pdf/1706.03762.pdfAttention is all you need》《https://arxiv.org/pdf/1706.03762.pdf

    9. 《https://arxiv.org/pdf/1810.04805.pdfBERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》《https://arxiv.org/pdf/1810.04805.pdf 

    展开全文
  • bert 文本分类实战

    千次阅读 2021-12-05 10:33:50
    前言: ... 本次模型为监督学习模型,根据已有标签的文本数据集,对bert模型进行训练。使用训练好的模型对句子进行预测,输出得到句子的类别。本质上属于多分类问题。 大致流程为,数据集预处...

    前言:

           由于课题需要,学习自然语言处理(NLP),于是在网上找了找文章和代码进行学习,在此记录,课题代码就不展示了,使用网上的代码和大家分享。思想和代码大部分参考苏神,在此感谢。

    任务目标:

         希望bert模型解决的问题: 输入:一段话; 输出:这段话属于的类别。

    任务实现原理:

          本次模型为监督学习模型,根据已有标签的文本数据集,对bert模型进行训练。使用训练好的模型对句子进行预测,输出得到句子的类别。本质上属于多分类问题。

          大致流程为,数据集预处理;划分数据集();对数据集加工处理(文本数据编码成符合bert输入的向量);构建模型(bert模型导入与使用); 将数据送入模型进行训练和预测。

    模型总体结构:

     具体代码:

    数据集预处理:

         将数据集读入,并打乱数据的排列顺序。

    mainPath = 'bert多文本分类//'
    rc = pd.read_csv(mainPath + 'data/tnews/toutiao_news_dataset.txt', delimiter="_!_", names=['labels', 'text'], header=None, encoding='utf-8')
    rc = shuffle(rc)  #打乱顺序

    划分数据集

         将数据集划分为训练集和测试集(验证集)

    # 构建全部所需数据集
    data_list = []
    for d in rc.iloc[:].itertuples():   #itertuples(): 将DataFrame迭代为元祖。
        data_list.append((d.text, d.labels))
    
    # 取一部分数据做训练和验证
    train_data = data_list[0:20000]
    valid_data = data_list[20000:22000]

     数据加工处理:

    修改原有的字典:

    修改原因:苏神解读,本来 Tokenizer 有自己的 _tokenize 方法,我这里重写了这个方法,是要保证 tokenize 之后的结果,跟原来的字符串长度等长(如果算上两个标记,那么就是等长再加 2)。 Tokenizer 自带的 _tokenize 会自动去掉空格,然后有些字符会粘在一块输出,导致 tokenize 之后的列表不等于原来字符串的长度了,这样如果做序列标注的任务会很麻烦。主要就是用 [unused1] 来表示空格类字符,而其余的不在列表的字符用 [UNK] 表示,其中 [unused*] 这些标记是未经训练的(随即初始化),是 Bert 预留出来用来增量添加词汇的标记,所以我们可以用它们来指代任何新字符。

    #vocabPath里存储了大量的词语,每个词语对应的着一个编号  例如10640 posts
    # 将词表中的词编号转换为字典
    # 字典的形式为  '仑': 796,
    #得到最原始的字典
    tokenDict = {}
    with codecs.open(vocabPath, 'r', encoding='utf-8') as reader:
        for line in reader:
            token = line.strip()                      # 去除首尾空格
            tokenDict[token] = len(tokenDict)
    
    #原始的字典存在着瑕疵,在原始的字典上需要根据自己的数据集,创造自己的字典
    # 重写tokenizer
    class OurTokenizer(Tokenizer):
        def _tokenize(self, content):
            reList = []
            for t in content:
                if t in self._token_dict:
                    reList.append(t)
                elif self._is_space(t):
    
                    # 用[unused1]来表示空格类字符
                    reList.append('[unused1]')
                else:
                    # 不在列表的字符用[UNK]表示
                    reList.append('[UNK]')
            return reList
    
    #使用新的字典
    tokenizer = OurTokenizer(tokenDict)

     

       文本数据根据字典编码成符合bert输入的向量,逐批生成数据([X1,X2],Y),从而可以丢到模型中训练。

    def seqPadding(X, padding=0):
        L = [len(x) for x in X]
        ML = max(L)
        return np.array([np.concatenate([x, [padding] * (ML - len(x))]) if len(x) < ML else x for x in X])
    
    class data_generator:
        def __init__(self, data, batch_size=32, shuffle=True):  #构造函数,使用时执行
            self.data = data
            self.batch_size = batch_size
            self.shuffle = shuffle
            self.steps = len(self.data) // self.batch_size
            if len(self.data) % self.batch_size != 0:
                self.steps += 1
    
        def __len__(self):
            return self.steps
    
        def __iter__(self):
            while True:
                idxs = list(range(len(self.data))) #数据元组下标
    
                if self.shuffle:
                    np.random.shuffle(idxs)        #是否打乱数据下标顺序
    
                X1, X2, Y = [], [], []
                for i in idxs:
                    d = self.data[i]
                    text = d[0][:maxlen]
                    x1, x2 = tokenizer.encode(first=text)  #  encode方法可以一步到位地生成对应模型的输入。
    
                    y = d[1]  
                    X1.append(x1)                           ## x1 是字对应的索引  # x2 是句子对应的索引            
                    X2.append(x2)
                    Y.append([y])
                    if len(X1) == self.batch_size or i == idxs[-1]:
                        X1 = seqPadding(X1)             #如果等于batchsize或者最后一个值后面补充0
                        X2 = seqPadding(X2)
                        Y = seqPadding(Y)
                        yield [X1, X2], Y
                        [X1, X2, Y] = [], [], []

    构建模型和训练:

        加载bert模型,并对bert模型的输出进行调整,使bert模型能够完成我们的任务目标。

    # 设置预训练bert模型的路径
    configPath = mainPath + 'chinese_roberta_wwm_ext_L-12_H-768_A-12/bert_config.json'
    ckpPath = mainPath + 'chinese_roberta_wwm_ext_L-12_H-768_A-12/bert_model.ckpt'
    vocabPath = mainPath + 'chinese_roberta_wwm_ext_L-12_H-768_A-12/vocab.txt'
    
    # bert模型设置
    bert_model = load_trained_model_from_checkpoint(configPath, ckpPath, seq_len=None)  # 加载预训练模型
    for l in bert_model.layers:
        l.trainable = True
    
    x1_in = Input(shape=(None,))
    x2_in = Input(shape=(None,))
    
    x = bert_model([x1_in, x2_in])
    
    # 取出[CLS]对应的向量用来做分类
    x = Lambda(lambda x: x[:, 0])(x)
    p = Dense(15, activation='softmax')(x)
    
    model = Model([x1_in, x2_in], p)
    model.compile(loss='sparse_categorical_crossentropy', optimizer=Adam(1e-5), metrics=['accuracy'])
    model.summary()
    
    train_D = data_generator(train_data)
    valid_D = data_generator(valid_data)
    
    model.fit_generator(train_D.__iter__(), steps_per_epoch=len(train_D), epochs=5, validation_data=valid_D.__iter__(),
                        validation_steps=len(valid_D))
    

    模型预测:

    #测试的数据集
    str1 = "上港主场1-2负于国安,遭遇联赛两连败,上港到底输在哪?"
    str2 = "普京总统会见了拜登总统"
    str3 = "这3辆10万出头小钢炮,随便改改轻松秒奔驰,第一辆还是限量款"
    predict_D = data_generator([(str1, 0), (str2, 3), (str3, 10)], shuffle=False)
    #获取总的标签类别
    #array(['体育', '军事', '农业', '国际', '娱乐', '房产', '教育', '文化', '旅游', '民生故事', '汽车','电竞游戏', '科技', '证券股票', '财经'], dtype=object)
    output_label2id_file = os.path.join(mainPath, "model/keras_class/label2id.pkl")
    if os.path.exists(output_label2id_file):
        with open(output_label2id_file, 'rb') as w:
            labes = pickle.load(w)
    
    #加载保存的模型
    from keras_bert import get_custom_objects
    custom_objects = get_custom_objects() 
    model = load_model(mainPath + 'model/keras_class/tnews.h5', custom_objects=custom_objects)
    #使用生成器获取测试的数据
    tmpData = predict_D.__iter__()
    #预测
    preds = model.predict_generator(tmpData, steps=len(predict_D), verbose=1)
    # 求每行最大值得下标,其中,axis=1表示按行计算
    index_maxs = np.argmax(preds, axis=1)
    result = [(x, labes[x]) for x in index_maxs]
    print(result)
    

    输出preds,index_maxs, result

     完整代码

    import pickle
    from keras_bert import load_trained_model_from_checkpoint, Tokenizer
    from keras.layers import *
    from keras.models import Model
    from keras.optimizers import Adam
    from sklearn.preprocessing import LabelEncoder
    from sklearn.utils import shuffle
    from keras.utils.vis_utils import plot_model
    import codecs, gc
    import keras.backend as K
    import os
    import pandas as pd
    import numpy as np
    
    
    # 文件主路径定义
    mainPath = '你的目录/keras_bert文本分类实例/'
    
    # 从文件中读取数据,获取训练集和验证集
    rc = pd.read_csv(mainPath + 'data/tnews/toutiao_news_dataset.txt', delimiter="_!_", names=['labels', 'text'],
                     header=None, encoding='utf-8')   #delimiter
    
    rc = shuffle(rc)  # shuffle数据,打乱
    
    # 把类别转换为数字
    # 一共15个类别:"教育","科技","军事","旅游","国际","证券股票","农业","电竞游戏",
    # "民生故事","文化","娱乐","体育","财经","房产","汽车"
    class_le = LabelEncoder()
    rc.iloc[:, 0] = class_le.fit_transform(rc.iloc[:, 0].values)
    
    # 保存标签文件
    output_label2id_file = os.path.join(mainPath, "model/keras_class/label2id.pkl")
    if not os.path.exists(output_label2id_file):
        with open(output_label2id_file, 'wb') as w:
            pickle.dump(class_le.classes_, w)
    
    # 构建全部所需数据集
    data_list = []
    for d in rc.iloc[:].itertuples():
        data_list.append((d.text, d.labels))
    
    # 取一部分数据做训练和验证
    train_data = data_list[0:20000]
    valid_data = data_list[20000:22000]
    
    maxlen = 100  # 设置序列长度为100,要保证序列长度不超过512
    
    # 设置预训练模型
    configPath = mainPath + 'chinese_roberta_wwm_ext_L-12_H-768_A-12/bert_config.json'
    ckpPath = mainPath + 'chinese_roberta_wwm_ext_L-12_H-768_A-12/bert_model.ckpt'
    vocabPath = mainPath + 'chinese_roberta_wwm_ext_L-12_H-768_A-12/vocab.txt'
    
    # 将词表中的词编号转换为字典
    tokenDict = {}
    with codecs.open(vocabPath, 'r', encoding='utf-8') as reader:
        for line in reader:
            token = line.strip()
            tokenDict[token] = len(tokenDict)
    
    
    # 重写tokenizer
    class OurTokenizer(Tokenizer):
        def _tokenize(self, content):
            reList = []
            for t in content:
                if t in self._token_dict:
                    reList.append(t)
                elif self._is_space(t):
    
                    # 用[unused1]来表示空格类字符
                    reList.append('[unused1]')
                else:
                    # 不在列表的字符用[UNK]表示
                    reList.append('[UNK]')
            return reList
    
    
    tokenizer = OurTokenizer(tokenDict)
    
    
    def seqPadding(X, padding=0):
        L = [len(x) for x in X]
        ML = max(L)
        return np.array([np.concatenate([x, [padding] * (ML - len(x))]) if len(x) < ML else x for x in X])
    
    
    class data_generator:  #先将数据变成元组的形式在喂入生成器
        def __init__(self, data, batch_size=32, shuffle=True):
            self.data = data
            self.batch_size = batch_size
            self.shuffle = shuffle
            self.steps = len(self.data) // self.batch_size
            if len(self.data) % self.batch_size != 0:
                self.steps += 1
    
        def __len__(self):
            return self.steps
    
        def __iter__(self):
            while True:
                idxs = list(range(len(self.data)))
    
                if self.shuffle:
                    np.random.shuffle(idxs)
    
                X1, X2, Y = [], [], []
                for i in idxs:
                    d = self.data[i]
                    text = d[0][:maxlen]
                    x1, x2 = tokenizer.encode(first=text)
                    y = d[1]
                    X1.append(x1)
                    X2.append(x2)
                    Y.append([y])
                    if len(X1) == self.batch_size or i == idxs[-1]:
                        X1 = seqPadding(X1)
                        X2 = seqPadding(X2)
                        Y = seqPadding(Y)
                        yield [X1, X2], Y
                        [X1, X2, Y] = [], [], []
    
    
    # bert模型设置
    bert_model = load_trained_model_from_checkpoint(configPath, ckpPath, seq_len=None)  # 加载预训练模型
    
    for l in bert_model.layers:
        l.trainable = True
    
    x1_in = Input(shape=(None,))
    x2_in = Input(shape=(None,))
    
    x = bert_model([x1_in, x2_in])
    
    # 取出[CLS]对应的向量用来做分类
    x = Lambda(lambda x: x[:, 0])(x)
    p = Dense(15, activation='softmax')(x)
    
    model = Model([x1_in, x2_in], p)
    model.compile(loss='sparse_categorical_crossentropy', optimizer=Adam(1e-5), metrics=['accuracy'])
    model.summary()
    
    train_D = data_generator(train_data)
    valid_D = data_generator(valid_data)
    
    model.fit_generator(train_D.__iter__(), steps_per_epoch=len(train_D), epochs=5, validation_data=valid_D.__iter__(),
                        validation_steps=len(valid_D))
    
    model.save(mainPath + 'model/keras_class/tnews.h5', True, True)
    
    # 保存模型结构图
    plot_model(model, to_file='model/keras_class/tnews.png', show_shapes=True)
    
    

    参考链接
    https://blog.csdn.net/qq_39290990/article/details/121672141

    展开全文
  • pytorch-bert文本分类

    2021-06-18 14:50:26
    基于bert预训练模型和pytorch深度学习框架实现文本分类
  • BERT文本分类,代码超基础、超详细解析

    千次阅读 多人点赞 2022-04-10 19:26:28
    使用bert预训练模型实现文本分类,超基础,解析超详细
  • BERT详解:bert文本分类怎么做的

    千次阅读 2021-11-30 14:05:06
    BERT详解:bert文本分类怎么做的
  • Transformer课程 第7课Gavin BERT文本分类-数据预处理 Comment Length Distribution 为了决定这个数据集的截断策略,让我们首先看看评论长度的分布。为此,我们的第一步是标记训练集中的所有评论。 标记所有评论,...
  • 自然语言处理动手学Bert文本分类视频教程,本套课程基于Pytorch最新1.4版本来实现利用Bert实现中文文本分类任务,延续动手学系列课程风格,全程手敲代码,跟着杨博一行一行代码撸起来。
  • 1、run_cnews_classifier.py 原生bert实现的文本分类 原文链接: 2、run_tnews_classifier.py 基于keras_bert实现的文本分类 原文链接: 3、run_lcqmc_similarity.py 基于bert4keras实现的文本相似度计算 原文链接:...
  • 『NLP学习笔记』BERT文本分类实战

    千次阅读 2021-12-20 22:05:50
    Bert模型是Google在2018年10月发布的语言表示模型,Bert在NLP领域横扫了11项任务的最优结果,可以说是现今最近NLP中最重要的突破。Bert模型的全称是Bidirectional Encoder Representations from Transformers,是...
  • 谷歌BERT文本分类教程
  • bert文本分类

    2021-10-22 16:21:25
    # bert文本分类baseline模型 import os import numpy as np import pandas as pd import torch import torch.nn as nn import torch.utils.data as Data import torch.optim as optim import transformers from ...
  • 中文文本分类数据集 数据来源: 今日头条客户端 数据格式: 6554695793956094477_!_110_!_news_military_!_「欧洲第一陆军」法兰西帝国的欧陆霸权_!_查理八世,布列塔尼,卡佩王朝,佛兰德斯,法国 6554855520291783175_...
  • bert 文本分类

    2020-11-23 16:21:57
    github项目 pytorch https://gitee.com/jfdwd/Bert-Chinese-Text-Classification-Pytorch
  • 自然语言处理(NLP): 12 BERT文本分类

    千次阅读 多人点赞 2020-04-08 23:03:57
    BERT介绍 BERT 论文阅读 来自论文《https://arxiv.org/pdf/1810.04805.pdf》 BERT说:“我要用 transformer 的 encoders” Ernie不屑道:“呵呵,你不能像Bi-Lstm一样考虑文章” BERT自信回答道:“我们会用masks” ...
  • BERT文本分类使用指南

    万次阅读 热门讨论 2018-12-21 16:19:27
    本文档介绍了如何使用BERT实现多类别文本分类任务,适合稍微了解BERT文本分类的同学参考。 (一) 下载 首先,在github上clone谷歌的BERT项目,或者直接下载。项目地址 然后,下载中文预训练模型,地址 (二) ...
  • BERT文本分类实践Keras

    千次阅读 热门讨论 2019-05-28 22:38:42
    项目中,BERT文本分类或者多分类,总感觉效果难言理想的样子。使用了keras-bert包。 概述: 问题一: tf版bert分类似乎太重,训练、预测很不方便,要定义类什么的,很难看。 问题二: 使用bert分类,如果文本太...
  • 本篇一共7100个字摘要:本篇主要分享了项目实践中的BERT文本分类优化策略和原理。首先是背景介绍及优化方向,其中优化方向主要分成从数据层面优化和模型层面优化;然后通过实验的方式重点分析...
  • 全网最详细的bert Bert文本分类教程 数据+完整代码 可直接运行
  • 基于huggingface/transforms(pytorch)框架实现Bert文本分类

    千次阅读 多人点赞 2020-08-26 15:40:34
    基于huggingface/transforms-PyTorch框架实现Bert文本分类背景项目结构安装依赖包数据与预训练模型数据预训练模型代码部分 背景 作者在使用bert_keras实现bert文本分类模型后发现训练时并不能使用GPU加速训练,因此...
  • 简单应用Transformers的预训练模型做文本分类 import os import logging import numpy as np import transformers from datasets import Dataset from sklearn import metrics from sklearn.model_selection import ...
  • 本文介绍如何利用BERT fine-tuning一个文本情感分类模型。 二、Bert源码(BertForSequenceClassification) 源码位置:\transformers\models\bert\modeling_bert.py @add_start_docstrings( """ Bert Model ...
  • python实现Bert文本分类

    千次阅读 2019-11-15 11:53:19
    """The main BERT model and related functions.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function import collections import copy import ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 13,252
精华内容 5,300
关键字:

bert文本分类