1.1 通用词向量模型的训练和调用
词向量模型的训练一般会搜集大量的文本,这些文本可以是通用文本也可以是与NLP任务相关的领域文本。然后进行分词,接着训练word2vec或者glove模型,这样训练出来的模型会有更好的泛化性。下文中简介了,基于百度提出的Lac模型进行分词,采用gensim中的word2vec构建词向量模型:
沉睡的小卡比兽. 基于LAC分词与gensim的词向量训练,pandas批量中文分词[EB/OL]. https://blog.csdn.net/chen10314/article/details/121996866
1.2 通过训练集的数据洞察生成NLP任务下的词表
from collections import Counterfrom tqdm import tqdmdef get_cutwords_list(line):sen = lac.run(line) #采用lac进行分词,返回listreturn sendef get_vocab(config):model_train = pd.read_csv(config['train_file_path'])model_train['cut'] = ''cut_df(model_train[['sentence']], get_cutwords_list)token_counter = Counter()for i in tqdm(model_train['cut'], total=len(model_train['cut']), desc='Counting token'):token_counter.update(i)vocab = set(token for token, _ in token_counter.most_common(config['vocab_size']))return vocabvocab = get_vocab(config)
1.3 构建任务词表下的预训练向量映射表
在我们统计高频的3W个词中,找寻预训练词表中有的词,构建分词后的token与预训练词表embedding之间的映射。
在token2embedding的映射过程中,有几个特别的字符需要处理:
-
填充位:pad 深度学习训练的输入是固定的,但是句子长度是不固定的,因此一般会预设一个句子的最大长度,如果句子没有满最大长度,没满的位置由pad填充。 -
未登录词:unk 分词的token结果,不在所构建预训练词表中,一般会专门设置一个token处理 -
句子开头和结尾:bos、eos 句子的开头结尾,bert之前手工构建模型的时代也不一定需要,为了和后续bert模型中cls、sep形成对应,此处先提及一下。
from gensim.models import KeyedVectorsdef get_embedding(vocab):token2embedding ={}word2vec = KeyedVectors.load('w2v/model/allw2v.model')for token in vocab:if token in word2vec.wv.key_to_index.keys():token2embedding[token] = word2vec.wv[token]meta_info = word2vec.wv[0].shape[0]token2id = {token: idx for idx, token in enumerate(token2embedding.keys(), 4)}id2embedding = {token2id[token]: embedding for token, embedding in token2embedding.items()}PAD, UNK, BOS, EOS = '<pad>', '<unk>', '<bos>', '<eos>'token2id[PAD] = 0token2id[UNK] = 1token2id[BOS] = 2token2id[EOS] = 3id2embedding[0] = [.0] * int(meta_info)id2embedding[1] = [.0] * int(meta_info)id2embedding[2] = np.random.random(int(meta_info)).tolist()id2embedding[3] = np.random.random(int(meta_info)).tolist()emb_mat = [id2embedding[idx] for idx in range(len(id2embedding))]return torch.tensor(emb_mat, dtype=torch.float), token2id, len(vocab)+4
此处将token2embedding,拆分成了token2id、id2embedding两步。句子分词成token后先转换成id,将unk的情况先处理了。然后,id2embedding就相当于一个预训练的词表,转换成tensor,通过nn.Embedding.from_pretrained(emb_mat, freeze=True)加载入模型中。
1.4 训练的基本配置
我们将一些文件路径、词表路径、随机数等变量写成在配置config里面,便于修改。
config = {'train_file_path': 'dataset/train.csv','test_file_path': 'dataset/test.csv','train_val_ratio': 0.1, # 10%用作验证集'vocab_size': 30000, # 词典 3W'batch_size': 64, # batch 大小 64'num_epochs': 2, # 2次迭代'learning_rate': 1e-3, # 学习率'logging_step': 300, # 每跑300个batch记录一次'seed': 2021 # 随机种子}config['device'] = 'cuda' if torch.cuda.is_available() else 'cpu' # cpu&gpuimport randomimport numpy as npdef seed_everything(seed):random.seed(seed)np.random.seed(seed)torch.manual_seed(seed)torch.cuda.manual_seed_all(seed)return seedseed_everything(config['seed'])
2、数据集转换
构建好token2id、id2embedding的操作后,需要将训练集(训练集)、测试集转换成id,并通过pytorch内置的数据集加载方法,批量加载训练数据用于模型训练。
2.1 训练集与测试集的批处理转换
对数据集进行分词,并映射为id,分词依旧采用百度lac分词器。
def tokenizer(sent, token2id):ids = [token2id.get(token, 1) for token in lac.run(sent)]return ids
在训练模式中,需要划分训练集与验证集,并转换成id,验证集用于在训练过程中评估模型的训练情况,看看模型是否拟合数据,是否会产生过拟合的情况等。由于上文已经尝试了pandas批处理方式,此处采用逐行分词,练练基本操作。其中,文件row[1]指数据集标签的label id列,row[-1]指带分词的句子列。
import pandas as pdfrom collections import defaultdictdef read_data(config, token2id, mode='train'):data_df = pd.read_csv(config[f'{mode}_file_path'], sep=',')if mode == 'train':X_train, y_train = defaultdict(list), []X_val, y_val = defaultdict(list), []num_val = int(config['train_val_ratio'] * len(data_df))else:X_test, y_test = defaultdict(list), []for i, row in tqdm(data_df.iterrows(), desc=f'Preprocesing {mode} data', total=len(data_df)):label=row[1] if mode == 'train' else 0sentence = row[-1]inputs = tokenizer(sentence, token2id)if mode == 'train':if i < num_val:X_val['input_ids'].append(inputs)y_val.append(label)else:X_train['input_ids'].append(inputs)y_train.append(label)else:X_test['input_ids'].append(inputs)y_test.append(label)if mode == 'train':label2id = {label: i for i, label in enumerate(np.unique(y_train))}id2label = {i: label for label, i in label2id.items()}y_train = torch.tensor([label2id[label] for label in y_train], dtype=torch.long)y_val = torch.tensor([label2id[label] for label in y_val], dtype=torch.long)return X_train, y_train, X_val, y_val, label2id, id2labelelse:y_test = torch.tensor(y_test, dtype=torch.long)return X_test, y_test
X_train, y_train, X_val, y_val, label2id, id2label = read_data(config, token2id, mode='train')X_test, y_test = read_data(config, token2id, mode='test')
2.2 构建当前NLP任务下的Dataset类
将转换好id的数据集,通过创建/继承 Dataset类提供数据集的封装, 再使用 Dataloader 实现数据并行加载,创建/继承 Dataset 必须实现python风格函数__len__()方法 返回整个数据集的长度,以及__getitem__(self, index)函数从而支持数据集索引
from torch.utils.data import Datasetclass TNEWSDataset(Dataset):def __init__(self, X, y):self.x = Xself.y = ydef __getitem__(self, idx):return {'input_ids': self.x['input_ids'][idx],'label': self.y[idx]}def __len__(self):return self.y.size(0)
pytorch Dataset类的文档解释,可见以下链接:
torch.utils.data — PyTorch 1.7.1 documentation[EB/OL]. https://pytorch.org/docs/1.7.1/data.html?highlight=dataset#torch.utils.data.Dataset
2.3 collete_fn将数据集转换为tensor
在收集Dataset的example后,需要将所有的句子id统一成相同长度的tensor并进行合并,此处将每句话的长度统一成了句子集合中长度最长的句子长度。pad操作隐含在torch.zeros的初始化操作中了。
def my_collate_fn(examples):input_ids_list =[]labels = []for example in examples:input_ids_list.append(example['input_ids'])labels.append(example['label'])# 1.找到 input_ids_list 中最长的句子max_length = max(len(input_ids) for input_ids in input_ids_list)# 2. 定义一个Tensorinput_ids_tensor = torch.zeros((len(labels), max_length), dtype=torch.long)for i, input_ids in enumerate(input_ids_list):# 3.得到当前句子长度seq_len = len(input_ids)input_ids_tensor[i, :seq_len] = torch.tensor(input_ids, dtype=torch.long)return {'input_ids': input_ids_tensor,'label': torch.tensor(labels, dtype=torch.long)}
2.4 使用dataloader读入数据
数据集映射为id、数据集类的封装、数据集转tensor合并以及DataLoader并行加载这些操作都封装在一起。
from torch.utils.data import DataLoaderdef build_dataloader(config, vocab):X_train, y_train, X_val, y_val, label2id, id2label = read_data(config, token2id, mode='train')X_test, y_test = read_data(config, token2id, mode='test')train_dataset = TNEWSDataset(X_train, y_train)val_dataset = TNEWSDataset(X_val, y_val)test_dataset = TNEWSDataset(X_test, y_test)train_dataloader = DataLoader(dataset=train_dataset, batch_size=config['batch_size'], num_workers=4, shuffle=True, collate_fn=my_collate_fn)val_dataloader = DataLoader(dataset=val_dataset, batch_size=config['batch_size'], num_workers=4, shuffle=False, collate_fn=my_collate_fn)test_dataloader = DataLoader(dataset=test_dataset, batch_size=config['batch_size'], num_workers=4, shuffle=False, collate_fn=my_collate_fn)return id2label, train_dataloader, val_dataloader, test_dataloader
id2label, train_dataloader, val_dataloader, test_dataloader = build_dataloader(config, vocab)
pytorch DataLoader类的文档解释,可见一下链接:
torch.utils.data — PyTorch 1.7.1 documentation[EB/OL]. https://pytorch.org/docs/1.7.1/data.html?highlight=dataloader#torch.utils.data.DataLoader
至此模型的输入部分已经全部处理好,textcnn原理,基于pytorch构建textcnn模型,构建训练、验证和测试的baseline可见:从零开始构建基于textcnn的文本分类模型(下)
————————————————
版权声明:本文为CSDN博主「沉睡的小卡比兽」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/chen10314/article/details/122133221
原文始发于微信公众号(黑客茶话会):从零开始构建基于textcnn的文本分类模型(上)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-


评论