BERT-多标签文本分类实战之六——数据加载与模型代码
Posted 征途黯然.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BERT-多标签文本分类实战之六——数据加载与模型代码相关的知识,希望对你有一定的参考价值。
·请参考本系列目录:【BERT-多标签文本分类实战】之一——实战项目总览
·下载本实战项目资源:>=点击此处=<
前5篇文章中,介绍了实战项目的前置知识,下面正式介绍项目的代码。本项目主要分为6部分:
1、bert-base-uncased
:bert
的预训练文件;
2、model
:存放bert
模型代码;
3、Reuters-21578
:存放数据集;
4、run.py
:项目运行主程序;
5、utils.py
:处理数据集并且预加载;
6、train_eval.py
:模型训练、验证、测试代码。
本篇介绍:5、utils.py:处理数据集并且预加载与2、model:存放bert模型代码。
[1] 数据集文件的构成
实战项目中数据集文件共6个:
其中,reutersNLTK.xlsx
是原数据集文件,训练集train.csv
、验证集dev.csv
、测试集test.csv
是之前拆分好的,class.txt
是标签目录,label.pkl
是压缩存储的标签,方便快速读取用的。
[2] 加载数据集
加载数据集的目标是:1)把文本数据转化成BERT
模型的词序、Mask 码,为输入进BERT
作准备;2)把文本标签转化成独热数组。
def build_dataset(config):
# ## 读取标签
label_list = pkl.load(open(config.label_path, 'rb'))
print(f"标签个数======== len(label_list)")
def convert_to_one_hot(Y, C):
list = [[0 for i in C] for j in Y]
for i, a in enumerate(Y):
for b in a:
if b in C:
list[i][C.index(b)] = 1
else:
list[i][len(C) - 1] = 1
return list
def load_dataset(path, pad_size=32):
df = pd.read_csv(path, encoding='utf-8', sep=',')
data = df['content']
sentences = data.values
labels = []
# 把标签读成数组
for ls in df['label']:
labels.append(re.compile(r"'(.*?)'").findall(ls))
# 把数组转成独热
labels_id = convert_to_one_hot(labels, label_list)
contents = []
count = 0
for i, content in tqdm(enumerate(sentences)):
label = labels_id[i]
encoded_dict = config.tokenizer.encode_plus(
content, # 输入文本
add_special_tokens=True, # 添加 '[CLS]' 和 '[SEP]'
max_length=pad_size, # 填充 & 截断长度
pad_to_max_length=True,
padding='max_length',
truncation='only_first',
return_attention_mask=True, # 返回 attn. masks.
return_tensors='pt' # 返回 pytorch tensors 格式的数据
)
token = config.tokenizer.tokenize(content)
seq_len = len(token)
count += seq_len
contents.append((torch.squeeze(encoded_dict['input_ids'],0), label, seq_len, torch.squeeze(encoded_dict['attention_mask'],0)))
print(f"数据集地址========path")
print(f"数据集总词数========count")
print(f"数据集文本数========len(sentences)")
print(f"数据集文本平均词数========count / len(sentences)")
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
代码如上。
首先,加载label.pkl
文件。对于每一条文本,先提取它的标签,然后转化成独热数组。接下来通过tokenizer.encode_plus
编码文本,得到input_ids
与attention_mask
。最后把这些数据都存到数组contents
中。
[3] 数据集加载器
在第二节中,只是把显式的文本数据,转化成了数字化的Tensor
格式。如何控制一个batch中有多少文本?如何控制数据的随机性等等?
这就需要数据集加载器。
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].detach().numpy() 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].detach().numpy() 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
这个完全是自定义的数据加载器,直接用就可以,不展开介绍。
到这里,数据加载的部分就结束了。我们需要在数字化数据外套一个数据加载器的原因是,回头在调
epoch
、batch_size
这些参数的时候,数据加载器能够自动帮我们分配好这些文本数据。
[4] BERT模型代码
BERT
模型代码分为两个文件,一个是BaseConfig.py
保存通用配置,一个是bert.py
保存实际代码。
BaseConfig.py
class BaseConfig(object):
"""配置参数"""
def __init__(self, dataset):
self.train_path = dataset + '/data/train.csv' # 训练集
self.dev_path = dataset + '/data/dev.csv' # 验证集
self.test_path = dataset + '/data/test.csv' # 测试集
self.label_path = dataset + '/data/label.pkl' # 标签集
self.vocab_path = dataset + '/data/vocab.pkl' # 词表
self.class_list = [x.strip() for x in open(
dataset + '/data/class.txt', encoding='utf-8').readlines()] # 类别名单
self.num_classes = len(self.class_list) # 类别数
self.n_vocab = 0 # 词表大小,在运行时赋值
""""""
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 设备
""""""
pretrained_path = './bert-base-uncased'
self.bert = BertModel.from_pretrained(pretrained_path)
self.tokenizer = BertTokenizer.from_pretrained(pretrained_path)
""""""
self.require_improvement = 1000 # 若超过1000batch效果还没提升,则提前结束训练
self.num_epochs = 100 # epoch数
self.batch_size = 32 # mini-batch大小
self.pad_size = 150 # 每句话处理成的长度(短填长切)
self.learning_rate = 5e-3 # 学习率
self.embed = 768
bert.py
class Config(BaseConfig):
"""配置参数"""
def __init__(self, dataset):
BaseConfig.__init__(self, dataset)
self.model_name = 'bert'
self.save_path = dataset + '/saved_dict/' + self.model_name + '.ckpt' # 模型训练结果
self.log_path = dataset + '/log/' + self.model_name
class Model(nn.Module):
def __init__(self, config):
super(Model, self).__init__()
self.bert = config.bert
for param in self.bert.parameters():
param.requires_grad = False
self.fc = nn.Linear(config.embed, config.num_classes)
def forward(self, x):
context = x[0] # 输入的句子
mask = x[2] # 对padding部分进行mask,和句子一个size,padding部分用0表示,如:[1, 1, 1, 1, 0, 0]
_ = self.bert(context, attention_mask=mask)
out = self.fc(_[1]) # [batch_size, hidden_size * 2] = [128, 256]
return out
这里就是定义了一个bert
模型,和一个全连接层。把bert
的输出放到fc
中做分类。很朴素但是很吊。。。。。效果是真的甩开CNN、RNN系模型一截。
【注意】最终的输出,理解为概率!!
[5] 进行下一篇实战
【BERT-多标签文本分类实战】之七——训练-评估-测试与运行主程序
以上是关于BERT-多标签文本分类实战之六——数据加载与模型代码的主要内容,如果未能解决你的问题,请参考以下文章