当前位置:   article > 正文

【nlp自然语言处理实战】案例---FastText模型文本分类_使用faxttext模型进行文本分类训练

使用faxttext模型进行文本分类训练

目录

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

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}

保存好这个词典

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

  1. import os
  2. import pickle as pkl
  3. from tqdm import tqdm
  4. MAX_VOCAB_SIZE = 10000 #词表长度限制
  5. UNK,PAD = '<UNK>','<PAD>' #未知字,padding符号
  6. # 编辑词典函数
  7. def build_vocab(file_path,tokenizer,max_size,min_freq):
  8. vocab_dic = {}
  9. # 打开路径文件
  10. with open(file_path,'r',encoding='UTF-8') as f:
  11. for line in tqdm(f):
  12. lin = line.strip()
  13. # 去掉其中的空行
  14. if not lin:
  15. continue
  16. # 去除后面的数字
  17. content = lin.split('\t')[0]
  18. # 对单词进行分词操作(字符级别)
  19. for word in tokenizer(content):
  20. # 构建词典 统计每个词出现的频率
  21. vocab_dic[word] = vocab_dic.get(word, 0)+1
  22. # 将出现频率高的词排在前面
  23. vocab_list = sorted([_ for _ in vocab_dic.items() if _[1] >= min_freq], key=lambda x: x[1], reverse=True)[:max_size]
  24. # 将所有的词重新按照频率高到低顺序 依次编号
  25. vocab_dic = {word_count[0]: idx for idx, word_count in enumerate(vocab_list)}
  26. # 向 vocab_dic 中添加两个特殊单词的映射:UNK 表示未知单词,PAD 表示填充单词。UNK 的编号为词汇表大小,而 PAD 的编号为词汇表大小加 1。
  27. vocab_dic.update({UNK: len(vocab_dic), PAD: len(vocab_dic)+1})
  28. return vocab_dic
  29. # 编辑建立数据集函数
  30. def build_dataset(config,ues_word):
  31. # 根据 ues_word 变量的值在单词级别和字符级别之间切换分词方式
  32. if ues_word:
  33. tokenizer = lambda x: x.split('') # 以空格隔开,word-level 单词级别
  34. else:
  35. tokenizer = lambda x: [y for y in x] # char-level 字符级别
  36. # 如果存在 词典文件则直接读取
  37. if os.path.exists(config.vocab_path):
  38. vocab = pkl.load(open(config.vocab_path,'rb'))
  39. # 不存在则创建词典文件
  40. else:
  41. # config.train_path = './data/train.txt'
  42. vocab = build_vocab(config.train_path, tokenizer=tokenizer,max_size=MAX_VOCAB_SIZE, min_freq=1)
  43. pkl.dump(vocab, open(config.vocab_path, 'wb'))
  44. print(f"Vocab size:{len(vocab)}")
  45. def load_dataset(path, pad_size=32):
  46. contents = []
  47. # 读取路径
  48. with open(path, 'r', encoding='UTF-8') as f:
  49. for line in tqdm(f):
  50. lin = line.strip()
  51. if not lin:
  52. continue
  53. # 存储内容和标签
  54. content, label = lin.split('\t')
  55. words_line = []
  56. # 以字符方式进行分词处理
  57. token = tokenizer(content)
  58. # 记录所有文件中词的数量
  59. seq_len = len(token)
  60. # token = ['传', '凡', '客', '诚', '品', '裁', '员', '5', '%', ' ', '电', '商', '寒', '冬', '或', '提', '前', '到', '来']
  61. # 将token固定为同样长度
  62. if pad_size:
  63. if len(token) < pad_size:
  64. token.extend([PAD]*(pad_size - len(token)))
  65. else:
  66. token = token[:pad_size]
  67. seq_len = pad_size
  68. # 讲统一填充的词完成词到编号的转换
  69. for word in token:
  70. words_line.append(vocab.get(word, vocab.get(UNK)))
  71. contents.append((words_line, int(label), seq_len))
  72. return contents
  73. # 加载训练集
  74. train = load_dataset(config.train_path, config.pad_size)
  75. dev = load_dataset(config.dev_path, config.pad_size)
  76. test = load_dataset(config.test_path, config.pad_size)
  77. # 返回训练集、验证集和测试集
  78. return vocab, train, dev, test

2.2 load_data_iter.py

将数据按照批量进行。

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

  1. import torch
  2. class DatasetIterater(object):
  3. def __init__(self, batches, batch_size, device):
  4. # 批次大小(在config中定义)
  5. self.batch_size = batch_size
  6. # 数据
  7. self.batches = batches
  8. # //整除操作符 // 操作符会向下取整,舍弃余数。
  9. self.n_batches = len(batches) // batch_size
  10. self.residue = False # 记录batch数量是否为整数
  11. if len(batches) % self.n_batches != 0:
  12. self.residue = True
  13. self.index = 0
  14. self.device = device
  15. def _to_tensor(self, datas):
  16. # 讲数据集转为tensor
  17. x = torch.LongTensor([_[0] for _ in datas]).to(self.device)
  18. y = torch.LongTensor([_[1] for _ in datas]).to(self.device)
  19. # pad 前的长度(超过pad_size的设为pad_size)
  20. seq_len = torch.LongTensor([_[2] for _ in datas]).to(self.device)
  21. return (x, seq_len), y
  22. def __next__(self):
  23. # 有剩余数据并且当前索引小于批次大小
  24. if self.residue and self.index < self.n_batches:
  25. batches = self.batches[self.index * self.batch_size: len(self.batches)]
  26. self.index += 1
  27. batches = self._to_tensor(batches)
  28. return batches
  29. elif self.index >= self.n_batches:
  30. self.index = 0
  31. raise StopIteration
  32. else:
  33. batches = self.batches[self.index * self.batch_size: (self.index + 1) * self.batch_size]
  34. self.index += 1
  35. batches = self._to_tensor(batches)
  36. return batches
  37. def __iter__(self):
  38. return self
  39. def __len__(self):
  40. if self.residue:
  41. return self.n_batches + 1
  42. else:
  43. return self.n_batches
  44. def build_iterator(dataset, config, predict):
  45. if predict is True:
  46. config.batch_size = 1
  47. iter = DatasetIterater(dataset, config.batch_size, config.device)
  48. return iter

2.3 FastText.py

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

  1. import torch
  2. import torch.nn as nn
  3. import torch.nn.functional as F
  4. import numpy as np
  5. # 编写参数配置类
  6. class Config(object):
  7. # 配置参数
  8. def __init__(self):
  9. self.model_name = 'FastText'
  10. self.train_path = './data/train.txt'
  11. # 训练集
  12. self.dev_path = './data/dev.txt'
  13. # 验证集
  14. self.test_path = './data/test.txt'
  15. # 测试集
  16. self.predict_path = './data/predict.txt'
  17. self.class_list = [x.strip() for x in open('./data/class.txt', encoding='utf-8').readlines()]
  18. self.vocab_path = './data/vocab.pkl' # 词表
  19. # 模型训练结果
  20. self.save_path = './saved dict/' + self.model_name + '.ckpt'
  21. self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  22. self.dropout = 0.5 #随机失活
  23. #若超过1000 patch效果还没提升,则提前结束训练
  24. self.require_improvement = 1000
  25. self.num_classes = len(self.class_list)#类别数
  26. self.n_vocab = 0 #词表大小,在运行时赋值
  27. self.num_epochs = 5 #epoch数
  28. self.batch_size = 32 #mini-batch大小
  29. self.pad_size = 32 #每句话处理成的长度(短填长切)
  30. self.learning_rate = 1e-3 #学习率
  31. self.embed = 300 #字向量维度
  32. self.hidden_size =256 #隐藏层大小
  33. self.filter_sizes = (2, 3, 4) # 卷积核尺寸
  34. self.num_filters = 256 # 卷积核数量(channels数)
  35. # 编写模型类
  36. class Model(nn.Module):
  37. def __init__(self,config):
  38. super(Model,self).__init__()
  39. self.embedding = nn.Embedding(
  40. config.n_vocab, # 词汇表达大小
  41. config.embed, # 词向量维度
  42. padding_idx=config.n_vocab-1 # 填充
  43. )
  44. self.dropout = nn.Dropout(config.dropout) # 丢弃
  45. self.fc1 = nn.Linear(config.embed, config.hidden_size) # 全连接层
  46. self.dropout = nn.Dropout(config.dropout) # 丢弃
  47. self.fc2 = nn.Linear(config.hidden_size, config.num_classes) #全连接层
  48. # 前向传播计算
  49. def forward(self, x):
  50. # 词嵌入
  51. out_word = self.embedding(x[0])
  52. out = out_word.mean(dim=1)
  53. out = self.dropout(out)
  54. # print(out.shape)
  55. out = self.fc1(out)
  56. out = F.relu(out)
  57. out = self.fc2(out)
  58. return out

2.4 train.py

训练数据集

  1. import numpy as np
  2. import torch
  3. import torch.nn.functional as F
  4. from sklearn import metrics
  5. #编写训练函数
  6. # 传入的是 测试集和验证集
  7. def train(config,model,train_iter,dev_iter):
  8. print("begin")
  9. model.train()
  10. # 优化器
  11. optimizer = torch.optim.Adam(model.parameters(), lr=config.learning_rate)
  12. total_batch = 0 # 记录进行到多少batch
  13. dev_best_loss = float('inf')
  14. last_improve = 0 # 记录上次验证集loss下降的batch数
  15. flag = False # 记录是否很久没有效果提升
  16. for epoch in range(config.num_epochs):
  17. print('Epoch[{}/{}]'.format(epoch+1,config.num_epochs))
  18. # 批量训练
  19. for i ,(trains ,labels) in enumerate(train_iter):
  20. # 将训练集放在模型中
  21. outputs = model(trains)
  22. # 清空模型梯度信息 在每次迭代时,需要将参数的梯度清空,以免当前的梯度信息影响到下一次迭代
  23. model.zero_grad()
  24. # 计算损失函数
  25. loss = F.cross_entropy(outputs, labels)
  26. # 反向传播
  27. loss.backward()
  28. # 根据上面计算得到的梯度信息和学习率 对模型的参数进行优化
  29. optimizer.step()
  30. if total_batch % 100 == 0:
  31. # 每多少轮输出在训练集和验证集上的效果
  32. true = labels.data.cpu()
  33. predict = torch.max(outputs.data, 1)[1].cpu()
  34. train_acc = metrics.accuracy_score(true, predict)
  35. dev_acc, dev_loss = evaluate(config, model, dev_iter)
  36. if dev_loss < dev_best_loss:
  37. dev_best_loss = dev_loss
  38. # 存储模型
  39. torch.save(model.state_dict(), config.save_path)
  40. # 记录batch数
  41. last_improve = total_batch
  42. # {2:6.2%} 是一个格式化字符串语法,表示将第三个参数格式化为一个百分数,并使用右对齐方式,并在左侧填充空格,总宽度为 6 个字符,保留两位小数。
  43. msg = 'Iter: {0:>6}, Train Loss: {1:>5.2}, Train Acc: {2:6.2%} ,''Val Loss :{3:>5.2}, Val Acc: {4:>6.2%}'
  44. print(msg.format(total_batch,loss.item(),train_acc,dev_loss,dev_acc))
  45. model.train()
  46. total_batch +=1
  47. if total_batch - last_improve > config.require_improvement:
  48. # 验证集1oss超过1000 batch没下降,结束训练
  49. print("No optimization for a long time,auto-stopping...")
  50. flag = True
  51. break
  52. if flag:
  53. break
  54. # 编写评价函数
  55. def evaluate(config,model,data_iter,test=False):
  56. # 将模型切换到评估模式 在评估模式下,模型的行为与训练模式下略有不同。具体来说,评估模式下模型会关闭一些对训练过程的辅助功能,例如 dropout 和 batch normalization 等,并且不会对模型的参数进行更新。
  57. model.eval()
  58. loss_total = 0
  59. predict_all = np.array([], dtype=int)
  60. labels_all = np.array([], dtype=int)
  61. # 防止模型参数更新
  62. with torch.no_grad():
  63. for texts, labels in data_iter:
  64. outputs = model(texts)
  65. # 损失函数
  66. loss = F.cross_entropy(outputs, labels)
  67. # 损失值累加
  68. loss_total += loss
  69. labels = labels.data.cpu().numpy()
  70. predict = torch.max(outputs.data, 1)[1].cpu().numpy()
  71. # labels_all 是所有样本的真实标签
  72. labels_all = np.append(labels_all, labels)
  73. # predict_all 是所有样本的预测标签
  74. predict_all = np.append(predict_all, predict)
  75. acc = metrics.accuracy_score(labels_all, predict_all)
  76. if test:
  77. # config.class_list 是所有可能的类别列表
  78. report = metrics.classification_report(labels_all, predict_all, target_names=config.class_list, digits=4)
  79. # 混淆矩阵
  80. confusion = metrics.confusion_matrix(labels_all, predict_all)
  81. return acc, loss_total / len(data_iter), report, confusion
  82. return acc, loss_total / len(data_iter)

2.5 predict.py

  1. import torch
  2. import numpy as np
  3. from train import evaluate
  4. MAX_VOCAB_SIZE = 10000
  5. UNK,PAD = '<UNK>','<PAD>'
  6. tokenizer = lambda x:[y for y in x] #char-level
  7. # 编写测试函数
  8. def test(config,model,test_iter):
  9. # test
  10. # 加载训练好的模型
  11. model.load_state_dict(torch.load(config.save_path))
  12. model.eval()#开启评价模式
  13. test_acc,test_loss,test_report,test_confusion = evaluate(config,model,test_iter,test=True)
  14. msg = 'Test Loss:{0:>5.2},Test Acc:{1:>6.28}'
  15. print(msg.format(test_loss,test_acc))
  16. print("Precision,Recall and Fl-Score...")
  17. print(test_report)
  18. print("Confusion Matrix...")
  19. print(test_confusion)
  20. # 编写加载数据函数
  21. def load_dataset(text, vocab, config, pad_size=32):
  22. contents = []
  23. for line in text:
  24. lin = line.strip()
  25. if not lin:
  26. continue
  27. words_line = []
  28. token = tokenizer(line)
  29. seq_len = len(token)
  30. if pad_size:
  31. if len(token) < pad_size:
  32. token.extend([PAD](pad_size - len(token)))
  33. else:
  34. token = token[:pad_size]
  35. seq_len = pad_size
  36. # 单词到编号的转换
  37. for word in token:
  38. words_line.append(vocab.get(word, vocab.get(UNK)))
  39. contents.append((words_line, int(0), seq_len))
  40. return contents # 数据格式为[([..],O),([.·],1),.]
  41. # 编写标签匹配函数
  42. def match_label(pred,config):
  43. label_list = config.class_list
  44. return label_list[pred]
  45. # 编写预测函数
  46. def final_predict(config, model, data_iter):
  47. map_location = lambda storage, loc: storage
  48. model.load_state_dict(torch.load(config.save_path, map_location=map_location))
  49. model.eval()
  50. predict_all = np.array([])
  51. with torch.no_grad():
  52. for texts, _ in data_iter:
  53. outputs = model(texts)
  54. pred = torch.max(outputs.data, 1)[1].cpu().numpy()
  55. pred_label = [match_label(i, config)for i in pred]
  56. predict_all = np.append(predict_all, pred_label)
  57. return predict_all

2.6 run.py

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

2.7 实验结果(部分

3 代码地址

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/342749
推荐阅读
相关标签
  

闽ICP备14008679号