TextRNN

TextRNN仅仅是将Word Embedding后,输入到双向LSTM中,然后对最后一位的输出输入到全连接层中,在对其进行softmax分类即可,模型如下图:

代码:

class RNN(nn.Module):def __init__(self, vocab_size, embedding_dim, hidden_dim, output_dim,n_layers=2, bidirectional=True, dropout=0.2, pad_idx=0):super().__init__()self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=pad_idx)self.rnn = nn.LSTM(embedding_dim, hidden_dim, num_layers=n_layers,batch_first=True,bidirectional=bidirectional)self.fc = nn.Linear(hidden_dim * 2, output_dim)# 这里hidden_dim乘以2是因为是双向,需要拼接两个方向,跟n_layers的层数无关。self.dropout = nn.Dropout(dropout)def forward(self, text):# text.shape=[seq_len, batch_size]embedded = self.dropout(self.embedding(text))# output: [batch,seq,2*hidden if bidirection else hidden]# hidden/cell: [bidirec * n_layers, batch, hidden]output, (hidden, cell) = self.rnn(embedded)# concat the final forward (hidden[-2,:,:]) and backward (hidden[-1,:,:]) hidden layershidden = self.dropout(torch.cat((hidden[-2, :, :], hidden[-1, :, :]), dim=1))# hidden = [batch size, hid dim * num directions],return self.fc(hidden.squeeze(0))  # 在接一个全连接层,最终输出[batch size, output_dim]

TextRNN_ATT

在TextRNN的基础上加入注意力机制,代码:

class RNN_ATTs(nn.Module):def __init__(self, vocab_size, embedding_dim, hidden_dim, output_dim,n_layers=2, bidirectional=True, dropout=0.2, pad_idx=0, hidden_size2=64):super().__init__()self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=pad_idx)self.lstm = nn.LSTM(embedding_dim, hidden_dim, n_layers,bidirectional=bidirectional, batch_first=True, dropout=dropout)self.tanh1 = nn.Tanh()# self.u = nn.Parameter(torch.Tensor(config.hidden_size * 2, config.hidden_size * 2))self.w = nn.Parameter(torch.zeros(hidden_dim * 2))self.tanh2 = nn.Tanh()self.fc1 = nn.Linear(hidden_dim * 2, hidden_size2)self.fc = nn.Linear(hidden_size2, output_dim)def forward(self, x):emb = self.embedding(x)  # [batch_size, seq_len, embeding]=[128, 32, 300]H, _ = self.lstm(emb)  # [batch_size, seq_len, hidden_size * num_direction]=[128, 32, 256]M = self.tanh1(H)  # [128, 32, 256]# M = torch.tanh(torch.matmul(H, self.u))alpha = F.softmax(torch.matmul(M, self.w), dim=1).unsqueeze(-1)  # [128, 32, 1]out = H * alpha  # [128, 32, 256]out = torch.sum(out, 1)  # [128, 256]out = F.relu(out)out = self.fc1(out)out = self.fc(out)  # [128, 64]return out

数据集

数据集采用cnews数据集,包含三个文件,分别是cnews.train.txt,cnews.val.txt,cnews,test.txt。类别:体育, 娱乐, 家居, 房产, 教育, 时尚, 时政, 游戏, 科技, 财经,共10个类别。网盘地址:

链接:https://pan.baidu.com/s/1awlBYclO_mxntEgL_tUF0g
提取码:rtnv

构建词向量

第一步,读取预料,做分词。

思路:

1、创建默认方式的分词对象seg。

2、打开文件,按照行读取文章。

3、去掉收尾的空格,将label和文章分割开。

4、将分词后的文章放到src_data,label放入labels里。

5、返回结果。

我对代码做了注解,如下:

def read_corpus(file_path):"""读取语料:param file_path::param type::return:"""src_data = []labels = []seg = pkuseg.pkuseg() #使用默认分词方式。with codecs.open(file_path,'r',encoding='utf-8') as fout:for line in tqdm(fout.readlines(),desc='reading corpus'):if line is not None:# line.strip()的意思是去掉每句话句首句尾的空格# .split(‘\t’)的意思是根据'\t'把label和文章内容分开,label和内容是通过‘\t’隔开的。# \t表示空四个字符,也称缩进,相当于按一下Tab键pair = line.strip().split('\t')if len(pair) != 2:print(pair)continuesrc_data.append(seg.cut(pair[1]))# 对文章内容分词。labels.append(pair[0])return (src_data, labels) #返回文章内容的分词结果和labels

经过这个步骤得到了labels和分词后的文章。如下代码:

src_sents, labels = read_corpus('cnews/cnews.train.txt')

对labels做映射:

    labels = {label: idx for idx, label in enumerate(labels)}

得到labels对应的idx的字典,idx的值是最后一次插入label的值。

第二步 构建词向量

这一步主要用到vocab.py的from_corpus方法

思路:

1、创建vocab_entry对象。

2、对分词后的文章统计词频,生成一个词和词频构成的字典。

3、从字典中取出Top size - 2个元素。

4、获取元素的词。

5、执行add方法将词放入vocab_entry,生成词和id,id就是词对应的向量值。

代码如下:

    @staticmethoddef from_corpus(corpus, size, min_feq=3):"""从给定语料中创建VocabEntry"""vocab_entry = VocabEntry()# chain函数来自于itertools库,itertools库提供了非常有用的基于迭代对象的函数,而chain函数则是可以串联多个迭代对象来形成一个更大的迭代对象# *的作用:返回单个迭代器。# word_freq是个字典,key=词,value=词频word_freq = Counter(chain(*corpus))  # Counter 是实现的 dict 的一个子类,可以用来方便地计数,统计词频valid_words = word_freq.most_common(size - 2)  # most_common()函数用来实现Top n 功能,在这里选出Top size-2个词valid_words = [word for word, value in valid_words if value >= min_feq]  # 把符合要求的词找出来放到list里面。print('number of word types: {}, number of word types w/ frequency >= {}: {}'.format(len(word_freq), min_feq, len(valid_words)))for word in valid_words:  # 将词放进VocabEntry里面。vocab_entry.add(word)return vocab_entry

创建完成后将词向量保存到json文件中

 vocab = Vocab.build(src_sents, labels, 50000, 3)print('generated vocabulary, source %d words' % (len(vocab.vocab)))vocab.save('./vocab.json')

训练

训练使用Train_RNN.py,先看分析main方法的参数。

参数

    parse = argparse.ArgumentParser()parse.add_argument("--train_data_dir", default='./cnews/cnews.train.txt', type=str, required=False)parse.add_argument("--dev_data_dir", default='./cnews/cnews.val.txt', type=str, required=False)parse.add_argument("--test_data_dir", default='./cnews/cnews.test.txt', type=str, required=False)parse.add_argument("--output_file", default='deep_model.log', type=str, required=False)parse.add_argument("--batch_size", default=4, type=int)parse.add_argument("--do_train", default=True, action="store_true", help="Whether to run training.")parse.add_argument("--do_test", default=True, action="store_true", help="Whether to run training.")parse.add_argument("--learnning_rate", default=5e-4, type=float)parse.add_argument("--num_epoch", default=50, type=int)parse.add_argument("--max_vocab_size", default=50000, type=int)parse.add_argument("--min_freq", default=2, type=int)parse.add_argument("--hidden_size", default=256, type=int)parse.add_argument("--embed_size", default=300, type=int)parse.add_argument("--dropout_rate", default=0.2, type=float)parse.add_argument("--warmup_steps", default=0, type=int, help="Linear warmup over warmup_steps.")parse.add_argument("--GRAD_CLIP", default=1, type=float)parse.add_argument("--vocab_path", default='vocab.json', type=str)

参数说明:

train_data_dir:训练集路径。

dev_data_dir:验证集路径

test_data_dir:测试集路径

output_file:输出的log路径

batch_size:batchsize的大小。

do_train:是否训练,默认True、

do_test:是否测试,默认True

learnning_rate:学习率

num_epoch:epoch的数量

max_vocab_size:词向量的个数

min_freq:词频,过滤低于这个数值的词

hidden_size:隐藏层的个数

embed_size:Embedding的长度。

dropout_rate:dropout的值。

warmup_steps:设置预热的值。

vocab_path:词向量保存的路径

构建词向量

    vocab = build_vocab(args)label_map = vocab.labelsprint(label_map)

build_vocab的方法:

def build_vocab(args):if not os.path.exists(args.vocab_path):src_sents, labels = read_corpus(args.train_data_dir)labels = {label: idx for idx, label in enumerate(labels)}vocab = Vocab.build(src_sents, labels, args.max_vocab_size, args.min_freq)vocab.save(args.vocab_path)else:vocab = Vocab.load(args.vocab_path)return vocab

创建模型

创建CNN模型,将模型放到GPU上,调用train方法,训练。

  rnn_model = RNN_ATTs(len(vocab.vocab), args.embed_size, args.hidden_size,len(label_map), n_layers=1, bidirectional=True, dropout=args.dropout_rate)rnn_model.to(device)train(args, rnn_model, train_data, dev_data, vocab, dtype='RNN')

对train方法做了一些注解,如下:

def train(args, model, train_data, dev_data, vocab, dtype='CNN'):LOG_FILE = args.output_file#记录训练logwith open(LOG_FILE, "a") as fout:fout.write('\n')fout.write('==========' * 6)fout.write('start trainning: {}'.format(dtype))fout.write('\n')time_start = time.time()if not os.path.exists(os.path.join('./runs', dtype)):os.makedirs(os.path.join('./runs', dtype))tb_writer = SummaryWriter(os.path.join('./runs', dtype))# 计算总的迭代次数t_total = args.num_epoch * (math.ceil(len(train_data) / args.batch_size))#optimizer = bnb.optim.Adam8bit(model.parameters(), lr=0.001, betas=(0.9, 0.995))  # add bnb optimizeroptimizer = AdamW(model.parameters(), lr=args.learnning_rate, eps=1e-8)#设置优化器scheduler = get_linear_schedule_with_warmup(optimizer=optimizer, num_warmup_steps=args.warmup_steps,num_training_steps=t_total) #设置预热。criterion = nn.CrossEntropyLoss()# 设置loss为交叉熵global_step = 0total_loss = 0.logg_loss = 0.val_acces = []train_epoch = trange(args.num_epoch, desc='train_epoch')for epoch in train_epoch:#训练epochmodel.train()for src_sents, labels in batch_iter(train_data, args.batch_size, shuffle=True):src_sents = vocab.vocab.to_input_tensor(src_sents, args.device)global_step += 1optimizer.zero_grad()logits = model(src_sents)y_labels = torch.tensor(labels, device=args.device)example_losses = criterion(logits, y_labels)example_losses.backward()torch.nn.utils.clip_grad_norm_(model.parameters(), args.GRAD_CLIP)optimizer.step()scheduler.step()total_loss += example_losses.item()if global_step % 100 == 0:loss_scalar = (total_loss - logg_loss) / 100logg_loss = total_losswith open(LOG_FILE, "a") as fout:fout.write("epoch: {}, iter: {}, loss: {},learn_rate: {}\n".format(epoch, global_step, loss_scalar,scheduler.get_lr()[0]))print("epoch: {}, iter: {}, loss: {}, learning_rate: {}".format(epoch, global_step, loss_scalar,scheduler.get_lr()[0]))tb_writer.add_scalar("lr", scheduler.get_lr()[0], global_step)tb_writer.add_scalar("loss", loss_scalar, global_step)print("Epoch", epoch, "Training loss", total_loss / global_step)eval_loss, eval_result = evaluate(args, criterion, model, dev_data, vocab)  # 评估模型with open(LOG_FILE, "a") as fout:fout.write("EVALUATE: epoch: {}, loss: {},eval_result: {}\n".format(epoch, eval_loss, eval_result))eval_acc = eval_result['acc']if len(val_acces) == 0 or eval_acc > max(val_acces):# 如果比之前的acc要da,就保存模型print("best model on epoch: {}, eval_acc: {}".format(epoch, eval_acc))torch.save(model.state_dict(), "classifa-best-{}.th".format(dtype))val_acces.append(eval_acc)time_end = time.time()print("run model of {},taking total {} m".format(dtype, (time_end - time_start) / 60))with open(LOG_FILE, "a") as fout:fout.write("run model of {},taking total {} m\n".format(dtype, (time_end - time_start) / 60))

重点注释了一下batch_iter方法,如下:

def batch_iter(data, batch_size, shuffle=False):"""batch数据:param data: list of tuple:param batch_size::param shuffle::return:"""batch_num = math.ceil(len(data) / batch_size)# 计算迭代的次数index_array = list(range(len(data))) #按照data的长度,映射listif shuffle:#是否打乱顺序random.shuffle(index_array)for i in range(batch_num):indices = index_array[i*batch_size:(i+1)*batch_size]# 选出batchsize个indexexamples = [data[idx] for idx in indices]# 通过index找到对应的dataexamples = sorted(examples,key=lambda x: len(x[1]),reverse=True)#按照label排序src_sents = [e[0] for e in examples] #把data中的文章放到src_sentslabels = [label_map[e[1]] for e in examples] #将标题映射label_map对应的valueyield src_sents, labels

下面一个重要的方法是vocab.vocab.to_input_tensor,核心思路:

1、将数据通过 self.words2indices方法转为词对应的数值。

2、找出一个batch中最长的数据,剩下的数据后面补0,形成统一的长度。

3、将第二步得到的结果放入torch.tensor

代码如下:

 def to_input_tensor(self, sents: List[List[str]], device: torch.device):"""将原始句子list转为tensor,同时将句子PAD成max_len:param sents: list of list<str>:param device::return:"""sents = self.words2indices(sents)sents = pad_sents(sents, self.word2id['<PAD>'])sents_var = torch.tensor(sents, device=device)return sents_var

开始训练:

验证

将do_train改为False,do_test改为True就可以开启验证模型,TextRNN能达到0.96的成绩。

parse.add_argument("--do_train", default=False, action="store_true", help="Whether to run training.")


完整代码链接:
https://download.csdn.net/download/hhhhhhhhhhwwwwwwwwww/40816205

NLP进阶,使用TextRNN和TextRNN_ATT实现文本分类相关推荐

  1. 面向可解释的NLP:北大、哈工大等提出文本分类的生成性解释框架

    作者 | Hui Liu, Qingyu Yin, William Yang Wang 译者 | Rachel 编辑 | Jane 出品 | AI科技大本营(ID: rgznai100) [导语]北大 ...

  2. 【NLP】授人以渔:分享我的文本分类经验总结

    在我们做一个项目或业务之前,需要了解为什么要做它,比如为什么要做文本分类?项目开发需要,还是文本类数据值得挖掘. 1.介绍 目前讨论文本分类几乎都是基于深度学习的方法,本质上还是一个建模的过程,包括数 ...

  3. Datawhale零基础入门NLP day5/Task5基于深度学习的文本分类2

    基于深度学习的文本分类 本章将继续学习基于深度学习的文本分类. 学习目标 学习Word2Vec的使用和基础原理 学习使用TextCNN.TextRNN进行文本表示 学习使用HAN网络结构完成文本分类 ...

  4. Datawhale零基础入门NLP赛事 - Task5 基于深度学习的文本分类2

    在上一章节,我们通过FastText快速实现了基于深度学习的文本分类模型,但是这个模型并不是最优的.在本章我们将继续深入. 基于深度学习的文本分类 本章将继续学习基于深度学习的文本分类. 学习目标 学 ...

  5. 【NLP】TensorFlow实现CNN用于中文文本分类

    代码基于 dennybritz/cnn-text-classification-tf 及 clayandgithub/zh_cnn_text_classify 参考文章 了解用于NLP的卷积神经网络( ...

  6. NLP(十六)轻松上手文本分类

    背景介绍   文本分类是NLP中的常见的重要任务之一,它的主要功能就是将输入的文本以及文本的类别训练出一个模型,使之具有一定的泛化能力,能够对新文本进行较好地预测.它的应用很广泛,在很多领域发挥着重要 ...

  7. NLP实战-基于弱标注数据的文本分类

    目录 分析现有数据 解决方案 初始语料集构建 特征选择过滤语料 1.词频逆文档评率 2.信息增益 3.卡方检验 训练模型 缺失标签数据处理 总结 最近在做CSDN文库标签的分类,文库的数据比博客数据要 ...

  8. NLP实践九:HAN原理与文本分类实践

    文章目录 HAN原理 代码实践 HAN原理 参考多层注意力模型 用于文本分类的注意力模型 整个网络结构包括五个部分: 1)词序列编码器 2)基于词级的注意力层 3)句子编码器 4)基于句子级的注意力层 ...

  9. 【NLP】第 6 章:用于文本分类的卷积神经网络

最新文章

  1. 中国工程院2021年院士增选第二轮候选人名单公布
  2. 关于如何在MyEclipse下修改项目名包名,以及类
  3. 阿里云面试官:如果是MySQL引起的CPU消耗过大,你会如何优化?
  4. 机器学习基础(1)——绪论
  5. linux下hg无法运行_在 Windows 里也可以访问 Linux 子系统文件了
  6. 中html倒入css那么套路,CSS常用套路
  7. 骁龙855加持!OPPO Reno正面照揭晓:边框窄得吓人
  8. python如何使用sdk_Python_sdk首页、文档和下载 - 优图人脸识别sdk - OSCHINA - 中文开源技术交流社区...
  9. varnish在Debian9.4安装和配置
  10. 关于Debug和Release之本质区别(转)
  11. Python模块的导入方法1
  12. intel无线网卡日志服务器,不定期找不到Intel N 2230无线网卡
  13. 智能驾驶LQR横向控制算法
  14. 代码安全 | 什么是OWASP?OWASP十大漏洞解析
  15. D365 窗体的 Lookup写法
  16. Floyd-傻子也能看懂的弗洛伊德算法(转)
  17. 期权Greeks(Delta、Gamma、Vega、Theta) 介绍与Python实现
  18. 【★】IT界8大恐怖预言
  19. mpvue开发微信小程序多级联动功能
  20. GEA 3.3 捕捉及处理错误

热门文章

  1. SEO黑帽技术只多少
  2. java代码餐馆管理系统_餐饮管理系统(包括数据库,源代码)
  3. 阿里云发布企业数字化及上云外包平台服务:阿里云众包平台
  4. php 求最大连续子序列,[HDOJ 1003]动态规划法求和最大的连续子序列
  5. 仓库码放要求_产品码放标准
  6. 淘宝短视频直播怎么去运营丨国仁网络资讯
  7. 总结:Promethus配置文件
  8. Android基础——多媒体编程
  9. 线性代数|学习笔记|18.065MIT公开课 lecture05
  10. JAVA计算机毕业设计二手手机回收平台系统Mybatis+源码+数据库+lw文档+系统+调试部署