失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 【nlp自然语言处理实战】案例---FastText模型文本分类

【nlp自然语言处理实战】案例---FastText模型文本分类

时间:2019-07-27 01:57:31

相关推荐

【nlp自然语言处理实战】案例---FastText模型文本分类

目录

1.案例简介

2 代码

2.1 load_data.py

2.2 load_data_iter.py

2.3 FastText.py

2.4 train.py

2.5 predict.py

2.6 run.py

2.7 实验结果(部分

3 代码地址

1.案例简介

数据集从 THUCNews 上抽取 20 万条新闻标题,文本长度在 20~30 字,总计 10 个类别。每类 2 万条进行分类操作,并基于 PyTorch 完成 FastText 模型处理。

FastText模型是脸书开源的一个词向量与文本分类工具。其在开源,典型应用场景是「带监督的文本分类问题」。其可以提供简单而高效的文本分类和表征学习的方法,性能比肩深度学习而且速度更快。

FastText模型结合了自然语言处理和机器学习中最成功的理念。我们另外采用了一个softmax层级(利用了类别不均衡分布的优势)来加速运算过程。

FastText模型是一个快速文本分类模型算法,与基于神经网络的分类模型算法相比有以下优点:

1)FastText模型在保持高精度的情况下提高了训练速度和测试速度;

2)FastText模型不需要预训练好的词向量,其可以自己训练词向量,

3)FastText模型两个重要的优化是Hierarchical softmax和N-gram。

FastText模型网络分为三层:

输入层:对文档插入之后的向量,包含有N-gram特征。

隐含层:对输入数据的求和平均。

输出层:文档对应标签。

2 代码

2.1 load_data.py

将词转换为编号

比如:一开始文档中的词是长这个样子

内容 标签

对文件中的中文进行分词处理,按照一个字一个字的分开:

['中','华','女','子','学','院',':','本','科','层','次','仅','1','专','业','招','男','生']

分开之后对文件中所有的词进行频率统计,按照频率高低进行排列:

比如:

{'中': 17860, '华': 5053, '女': 8568, '子': 10246, '学': 11069, '院': 2833, ':': 28269, '本': 6649, '科': 4375, '层': 776, '次': 3159, '仅': 1793, '1': 40420, '专': 4134, '业': 9748, '招': 4648, '男': 5884, '生': 15370, '两': 4880, '天': 6662, '价': 11663, '网': 8573, '站': 1760, '背': 662, '后': 7306, '重': 5960, '迷': 1248, '雾': 81, '做': 1827, '个': 4075, '究': 1372, '竟': 541, '要': 4044, '多': 5170, '少': 2108, '钱': 1551, '东': 4555, '5': 18163, '环': 1898, '海': 6229, '棠': 65, '公': 10769, '社': 1165, '2': 31856, '3': 20291, '0': 60319, '-': 7944, '9': 15626, '平': 8207, '居': 5716, '准': 1859, '现': 7473, '房': 8181, '8': 13315, '折': 2990, '优': 1978, '惠': 1560, '卡': 3009, '佩': 467, '罗': 2536, '告': 2843, '诉': 1055, '你': 1456, '德': 3209, '国': 24079, '脚': 491, '猛': 379, '的': 8753, '原': 1782, '因': 2959, ' ': 80926, '不': 12798, '希': 1264, '望': 2504, '英': 5067, '战': 6391, '踢': 268, '点': 5623, '球': 5074, '岁': 2528, '老': 3982, '太': 1699, '为': 7095, '饭': 313, '扫': 299, '地': 7875, '4': 13832, '年': 18565, '获': 3876, '授': 381, '港': 3341, '大': 26024, '荣': 538, '誉': 265, '士': 2674, '记': 1858, '者': 3507, '回': 4644, '访': 1410, '震': 2040, '可': 5042, '乐': 2875, '孩': 1379, '将': 10546, '受': 3724, '邀': 440, '赴': 782, '美': 11941, '参': 1526, '观': 1635, '冯': 252, '伦': 831, '徐': 376, '若': 394, '�': 763, ..........}

排序之后再对词重新编号最后面两个添加<UNK>:字总数,<PAD>:字总数+1

{'中': 1, '华': 2, '女': 3, '子': 4,.......,<UNK>:字总数,<PAD>:字总数+1}

保存好这个词典

在建立数据集的时候,对每一行的数据都要进行填充或者删除就是用词典来进行的。

import osimport pickle as pklfrom tqdm import tqdmMAX_VOCAB_SIZE = 10000 #词表长度限制UNK,PAD = '<UNK>','<PAD>' #未知字,padding符号# 编辑词典函数def build_vocab(file_path,tokenizer,max_size,min_freq):vocab_dic = {}# 打开路径文件with open(file_path,'r',encoding='UTF-8') as f:for line in tqdm(f):lin = line.strip()# 去掉其中的空行if not lin:continue# 去除后面的数字content = lin.split('\t')[0]# 对单词进行分词操作(字符级别)for word in tokenizer(content):# 构建词典 统计每个词出现的频率vocab_dic[word] = vocab_dic.get(word, 0)+1# 将出现频率高的词排在前面vocab_list = sorted([_ for _ in vocab_dic.items() if _[1] >= min_freq], key=lambda x: x[1], reverse=True)[:max_size]# 将所有的词重新按照频率高到低顺序 依次编号vocab_dic = {word_count[0]: idx for idx, word_count in enumerate(vocab_list)}# 向 vocab_dic 中添加两个特殊单词的映射:UNK 表示未知单词,PAD 表示填充单词。UNK 的编号为词汇表大小,而 PAD 的编号为词汇表大小加 1。vocab_dic.update({UNK: len(vocab_dic), PAD: len(vocab_dic)+1})return vocab_dic# 编辑建立数据集函数def build_dataset(config,ues_word):# 根据 ues_word 变量的值在单词级别和字符级别之间切换分词方式if ues_word:tokenizer = lambda x: x.split('') # 以空格隔开,word-level 单词级别else:tokenizer = lambda x: [y for y in x] # char-level 字符级别# 如果存在 词典文件则直接读取if os.path.exists(config.vocab_path):vocab = pkl.load(open(config.vocab_path,'rb'))# 不存在则创建词典文件else:# config.train_path = './data/train.txt'vocab = build_vocab(config.train_path, tokenizer=tokenizer,max_size=MAX_VOCAB_SIZE, min_freq=1)pkl.dump(vocab, open(config.vocab_path, 'wb'))print(f"Vocab size:{len(vocab)}")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')words_line = []# 以字符方式进行分词处理token = tokenizer(content)# 记录所有文件中词的数量seq_len = len(token)# token = ['传', '凡', '客', '诚', '品', '裁', '员', '5', '%', ' ', '电', '商', '寒', '冬', '或', '提', '前', '到', '来']# 将token固定为同样长度if pad_size:if len(token) < pad_size:token.extend([PAD]*(pad_size - len(token)))else:token = token[:pad_size]seq_len = pad_size# 讲统一填充的词完成词到编号的转换for word in token:words_line.append(vocab.get(word, vocab.get(UNK)))contents.append((words_line, int(label), seq_len))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 vocab, train, dev, test

2.2 load_data_iter.py

将数据按照批量进行。

批量记载数据的原因:深度学习模型的参数非常多,为了得到模型的参数,需要用大量的数据对模型进行训练,所以数据量一般是相当大的,不可能一次性加载到内存中对所有数据进行向前传播和反向传播,因此需要分批次将数据加载到内存中对模型进行训练。使用数据加载器的目的就是方便分批次将数据加载到模型,以分批次的方式对模型进行迭代训练。

import torchclass DatasetIterater(object):def __init__(self, batches, batch_size, device):# 批次大小(在config中定义)self.batch_size = batch_size# 数据self.batches = batches# //整除操作符 // 操作符会向下取整,舍弃余数。self.n_batches = len(batches) // batch_sizeself.residue = False # 记录batch数量是否为整数if len(batches) % self.n_batches != 0:self.residue = Trueself.index = 0self.device = devicedef _to_tensor(self, datas):# 讲数据集转为tensorx = 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)return (x, seq_len), ydef __next__(self):# 有剩余数据并且当前索引小于批次大小if self.residue and self.index < self.n_batches:batches = self.batches[self.index * self.batch_size: len(self.batches)]self.index += 1batches = self._to_tensor(batches)return batcheselif self.index >= self.n_batches:self.index = 0raise StopIterationelse:batches = self.batches[self.index * self.batch_size: (self.index + 1) * self.batch_size]self.index += 1batches = self._to_tensor(batches)return batchesdef __iter__(self):return selfdef __len__(self):if self.residue:return self.n_batches + 1else:return self.n_batchesdef build_iterator(dataset, config, predict):if predict is True:config.batch_size = 1iter = DatasetIterater(dataset, config.batch_size, config.device)return iter

2.3 FastText.py

代码里面有所有需要模型的配置参数,以及模型类

import torchimport torch.nn as nnimport torch.nn.functional as Fimport numpy as np# 编写参数配置类class Config(object):# 配置参数def __init__(self):self.model_name = 'FastText'self.train_path = './data/train.txt'# 训练集self.dev_path = './data/dev.txt'# 验证集self.test_path = './data/test.txt'# 测试集self.predict_path = './data/predict.txt'self.class_list = [x.strip() for x in open('./data/class.txt', encoding='utf-8').readlines()]self.vocab_path = './data/vocab.pkl' # 词表# 模型训练结果self.save_path = './saved dict/' + self.model_name + '.ckpt'self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')self.dropout = 0.5 #随机失活#若超过1000 patch效果还没提升,则提前结束训练self.require_improvement = 1000self.num_classes = len(self.class_list)#类别数self.n_vocab = 0 #词表大小,在运行时赋值self.num_epochs = 5 #epoch数self.batch_size = 32 #mini-batch大小self.pad_size = 32 #每句话处理成的长度(短填长切)self.learning_rate = 1e-3 #学习率self.embed = 300 #字向量维度self.hidden_size =256 #隐藏层大小self.filter_sizes = (2, 3, 4) # 卷积核尺寸self.num_filters = 256 # 卷积核数量(channels数)# 编写模型类class Model(nn.Module):def __init__(self,config):super(Model,self).__init__()self.embedding = nn.Embedding(config.n_vocab, # 词汇表达大小config.embed, # 词向量维度padding_idx=config.n_vocab-1 # 填充)self.dropout = nn.Dropout(config.dropout) # 丢弃self.fc1 = nn.Linear(config.embed, config.hidden_size) # 全连接层self.dropout = nn.Dropout(config.dropout) # 丢弃self.fc2 = nn.Linear(config.hidden_size, config.num_classes) #全连接层# 前向传播计算def forward(self, x):# 词嵌入out_word = self.embedding(x[0])out = out_word.mean(dim=1)out = self.dropout(out)# print(out.shape)out = self.fc1(out)out = F.relu(out)out = self.fc2(out)return out

2.4 train.py

训练数据集,

import numpy as npimport torchimport torch.nn.functional as Ffrom sklearn import metrics#编写训练函数# 传入的是 测试集和验证集def train(config,model,train_iter,dev_iter):print("begin")model.train()# 优化器optimizer = torch.optim.Adam(model.parameters(), lr=config.learning_rate)total_batch = 0 # 记录进行到多少batchdev_best_loss = float('inf')last_improve = 0 # 记录上次验证集loss下降的batch数flag = False # 记录是否很久没有效果提升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()predict = torch.max(outputs.data, 1)[1].cpu()train_acc = metrics.accuracy_score(true, predict)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)# 记录batch数last_improve = total_batch# {2:6.2%} 是一个格式化字符串语法,表示将第三个参数格式化为一个百分数,并使用右对齐方式,并在左侧填充空格,总宽度为 6 个字符,保留两位小数。msg = 'Iter: {0:>6}, Train Loss: {1:>5.2}, Train Acc: {2:6.2%} ,''Val Loss :{3:>5.2}, Val Acc: {4:>6.2%}'print(msg.format(total_batch,loss.item(),train_acc,dev_loss,dev_acc))model.train()total_batch +=1if total_batch - last_improve > config.require_improvement:# 验证集1oss超过1000 batch没下降,结束训练print("No optimization for a long time,auto-stopping...")flag = Truebreakif flag:break# 编写评价函数def evaluate(config,model,data_iter,test=False):# 将模型切换到评估模式 在评估模式下,模型的行为与训练模式下略有不同。具体来说,评估模式下模型会关闭一些对训练过程的辅助功能,例如 dropout 和 batch normalization 等,并且不会对模型的参数进行更新。model.eval()loss_total = 0predict_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 += losslabels = labels.data.cpu().numpy()predict = torch.max(outputs.data, 1)[1].cpu().numpy()# labels_all 是所有样本的真实标签labels_all = np.append(labels_all, labels)# predict_all 是所有样本的预测标签predict_all = np.append(predict_all, predict)acc = metrics.accuracy_score(labels_all, predict_all)if test:# config.class_list 是所有可能的类别列表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, confusionreturn acc, loss_total / len(data_iter)

2.5 predict.py

import torchimport numpy as npfrom train import evaluateMAX_VOCAB_SIZE = 10000UNK,PAD = '<UNK>','<PAD>'tokenizer = lambda x:[y for y in x]#char-level# 编写测试函数def test(config,model,test_iter):# test# 加载训练好的模型model.load_state_dict(torch.load(config.save_path))model.eval()#开启评价模式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.28}'print(msg.format(test_loss,test_acc))print("Precision,Recall and Fl-Score...")print(test_report)print("Confusion Matrix...")print(test_confusion)# 编写加载数据函数def load_dataset(text, vocab, config, pad_size=32):contents = []for line in text:lin = line.strip()if not lin:continuewords_line = []token = tokenizer(line)seq_len = len(token)if pad_size:if len(token) < pad_size:token.extend([PAD](pad_size - len(token)))else:token = token[:pad_size]seq_len = pad_size# 单词到编号的转换for word in token:words_line.append(vocab.get(word, vocab.get(UNK)))contents.append((words_line, int(0), seq_len))return contents # 数据格式为[([..],O),([.·],1),.]# 编写标签匹配函数def match_label(pred,config):label_list = config.class_listreturn label_list[pred]# 编写预测函数def final_predict(config, model, data_iter):map_location = lambda storage, loc: storagemodel.load_state_dict(torch.load(config.save_path, map_location=map_location))model.eval()predict_all = np.array([])with torch.no_grad():for texts, _ in data_iter:outputs = model(texts)pred = torch.max(outputs.data, 1)[1].cpu().numpy()pred_label = [match_label(i, config)for i in pred]predict_all = np.append(predict_all, pred_label)return predict_all

2.6 run.py

from FastText import Configfrom FastText import Modelfrom load_data import build_datasetfrom load_data_iter import build_iteratorfrom train import trainfrom predict import test,load_dataset,final_predict# 测试文本text = ['国考网上报名序号查询后务必牢记。报名参加国家公务员考试的考生:如果您已通过资格审查,那么请于10月28日8:00后,登录考录专题网站查询自己的报名序号']if __name__ == "__main__":config = Config()print("Loading data...")vocab, train_data, dev_data, test_data = build_dataset(config, False)#1,批量加载测试数据# 批量记载数据的原因:深度学习模型的参数非常多,为了得到模型的参数,需要用大量的数据对模型进行训练,所以数据量一般是相当大的,# 不可能一次性加载到内存中对所有数据进行向前传播和反向传播,因此需要分批次将数据加载到内存中对模型进行训练。使用数据加载器的# 目的就是方便分批次将数据加载到模型,以分批次的方式对模型进行迭代训练。train_iter = build_iterator(train_data, config, False)dev_iter = build_iterator(dev_data, config, False)test_iter = build_iterator(test_data, config, False)config.n_vocab = len(vocab)#2,加载模型结构model = Model(config).to(config.device)train(config, model, train_iter, dev_iter)#3.测试test(config, model, test_iter)print("+++++++++++++++++")#4.预测content = load_dataset(text, vocab, config)predict_iter = build_iterator(content, config, predict=True)result = final_predict(config, model, predict_iter)for i, j in enumerate(result):print('text:{}'.format(text[i]), '\t', 'label:{}'.format(j))

2.7 实验结果(部分

3 代码地址

代码来源:《自然语言处理应用与实战》 韩少云等编著著

代码地址:c4d2/nlp_demo: nlp相关案例 ()

如果觉得《【nlp自然语言处理实战】案例---FastText模型文本分类》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。