基于LSTM的文本分类1——模型搭建

源码

# coding: UTF-8
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np


class Config(object):
    """配置参数类,用于存储模型和训练的超参数"""
    def __init__(self, dataset, embedding):
        self.model_name = 'TextRNN'  # 模型名称
        self.train_path = dataset + '/data/train.txt'  # 训练集路径
        self.dev_path = dataset + '/data/dev.txt'      # 验证集路径
        self.test_path = dataset + '/data/test.txt'    # 测试集路径
        self.class_list = [x.strip() for x in open(
            dataset + '/data/class.txt').readlines()]  # 类别列表
        self.vocab_path = dataset + '/data/vocab.pkl'  # 词表路径
        self.save_path = dataset + '/saved_dict/' + self.model_name + '.ckpt'  # 模型保存路径
        self.log_path = dataset + '/log/' + self.model_name  # 日志保存路径
        # 加载预训练词向量(若提供)
        self.embedding_pretrained = torch.tensor(
            np.load(dataset + '/data/' + embedding)["embeddings"].astype('float32')) \
            if embedding != 'random' else None
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')  # 训练设备

        # 模型超参数
        self.dropout = 0.5              # 随机失活率
        self.require_improvement = 1000 # 若超过该batch数效果未提升,则提前终止训练
        self.num_classes = len(self.class_list)  # 类别数
        self.n_vocab = 0                # 词表大小(运行时赋值)
        self.num_epochs = 10            # 训练轮次
        self.batch_size = 128           # 批次大小
        self.pad_size = 32              # 句子填充/截断长度
        self.learning_rate = 1e-3       # 学习率
        # 词向量维度(使用预训练时与预训练维度对齐,否则设为300)
        self.embed = self.embedding_pretrained.size(1) \
            if self.embedding_pretrained is not None else 300
        self.hidden_size = 128          # LSTM隐藏层维度
        self.num_layers = 2             # LSTM层数


'''基于LSTM的文本分类模型'''
class Model(nn.Module):
    def __init__(self, config):
        super(Model, self).__init__()
        # 词嵌入层:加载预训练词向量或随机初始化
        if config.embedding_pretrained is not None:
            self.embedding = nn.Embedding.from_pretrained(config.embedding_pretrained, freeze=False)
        else:
            self.embedding = nn.Embedding(config.n_vocab, config.embed, padding_idx=config.n_vocab - 1)
        # 双向LSTM层
        self.lstm = nn.LSTM(config.embed, config.hidden_size, config.num_layers,
                            bidirectional=True, batch_first=True, dropout=config.dropout)
        # 全连接分类层
        self.fc = nn.Linear(config.hidden_size * 2, config.num_classes)  # 双向LSTM输出维度翻倍

    def forward(self, x):
        x, _ = x  # 输入x为(padded_seq, seq_len),此处取padded_seq
        out = self.embedding(x)  # [batch_size, seq_len, embed_dim]
        out, _ = self.lstm(out)  # LSTM输出维度 [batch_size, seq_len, hidden_size*2]
        # 取最后一个时间步的输出作为句子表示
        out = self.fc(out[:, -1, :])  # [batch_size, num_classes]
        return out

数据集

上图是我们这次做的文本分类。一共十个话题领域,我们的目标是输入一句话,模型能够实现对话题领域的区分。

上图是我们使用的数据集。前面的汉字部分是模型学习的文本,后面接一个tab键是对该文本的分类。

配置类

配置的重点是模型的超参数,这里分析一下模型涉及的超参数。

Dropout随机失活率

self.dropout = 0.5

在LSTM层之间随机屏蔽部分神经元输出,强迫模型学习冗余特征表示。公式:hdrop=h⊙mhdrop​=h⊙m,其中mm是伯努利分布的0-1掩码。

早停阈值

elf.require_improvement = 1000

早停阈值的思想是:连续N个batch在验证集无精度提升则终止训练。首次训练数据的时候可能摸不清楚情况,设置了较大的epoch值,浪费掉大量训练时间。假设batch_size=128,数据集1万样本 , 每个epoch大约有78个batch。1000个batch的耐心期大约是13个epoch。

序列填充长度

self.pad_size = 32

序列填充长度的作用是,将变长文本序列处理为固定长度,满足神经网络批量处理的要求 。如果文本长度小于32,则填充特定的字符。如果文本长度大于32,则进行截断,保留32个字符。

序列填充长度通常使用95分位方式获得,获取代码如下

import numpy as np
lengths = [len(text.split()) for text in train_texts]
pad_size = int(np.percentile(lengths, 95))  # 覆盖95%样本

词向量维度

self.embed = 300

词向量的维度决定了语义空间的自由度 。假设我们使用字分割,每个文字对应一个300维的向量,将向量输入到模型中完成训练。

可以得出,向量维数越多,可以包含的信息数量就越多。但是并不是维度越高越好,下面的表是高维和低维的对比。

因子低维(d=50)高维(d=1024)
语义区分度相似词易混淆可学习细粒度差异
计算复杂度O(Vd) 内存占用低GPU显存需求高
训练数据需求1M+ tokens即可需100M+ tokens
下游任务适配性适合简单分类任务适合语义匹配任务

由于我们的数据量较小,所以使用较低的词向量维度。另外,如果使用预训练模型,词向量维度的值需要和预训练模型的值相同。

LSTM隐藏层维度

self.hidden_size = 128

隐藏层维度先卖个关子,下一章LSTM模型解析的时候讲。

模型搭建

Input Text → Embedding Layer → Bidirectional LSTM → Last Timestep Output → FC Layer → Class Probabilities

文本是无法直接被计算机识别的,所以文本需要映射为稠密向量才能输入给模型。因此在输入模型前要加一步向量映射。

class Model(nn.Module):
    def __init__(self, config):
        super(Model, self).__init__()
        # 词嵌入层:加载预训练词向量或随机初始化
        if config.embedding_pretrained is not None:
            self.embedding = nn.Embedding.from_pretrained(config.embedding_pretrained, freeze=False)
        else:
            self.embedding = nn.Embedding(config.n_vocab, config.embed, padding_idx=config.n_vocab - 1)
        # 双向LSTM层
        self.lstm = nn.LSTM(config.embed, config.hidden_size, config.num_layers,
                            bidirectional=True, batch_first=True, dropout=config.dropout)
        # 全连接分类层
        self.fc = nn.Linear(config.hidden_size * 2, config.num_classes)  # 双向LSTM输出维度翻倍

    def forward(self, x):
        x, _ = x  # 输入x为(padded_seq, seq_len),此处取padded_seq
        out = self.embedding(x)  # [batch_size, seq_len, embed_dim]
        out, _ = self.lstm(out)  # LSTM输出维度 [batch_size, seq_len, hidden_size*2]
        # 取最后一个时间步的输出作为句子表示
        out = self.fc(out[:, -1, :])  # [batch_size, num_classes]
        return out

词嵌入层

首先构建词嵌入层,将本地的预训练embedding加载到pytorch里面。

双向LSTM层

我们使用双向LSTM模型,即将文本从左到右训练一次,也从右到左(倒着来)训练一次。

参数名作用说明典型值
input_size输入特征维度(等于嵌入维度)300
hidden_size隐藏层维度128/256
num_layersLSTM堆叠层数2-4
bidirectional启用双向LSTMTrue
batch_first输入输出使用(batch, seq, *)格式True
dropout层间dropout概率(仅当num_layers>1时生效)0.5

全连接分类层

self.fc = nn.Linear(config.hidden_size * 2, config.num_classes)

 全连接的输入通道数是隐藏层维度的两倍,原因是我们的模型是双向的,双向的结果都需要输出给全连接层。

前向传播

def forward(self, x):
    x, _ = x  # 解包(padded_seq, seq_len)
    out = self.embedding(x)  # [batch, seq_len, embed_dim]
    out, _ = self.lstm(out)  # [batch, seq_len, 2*hidden_size]
    out = self.fc(out[:, -1, :])  # 取最后时刻的输出
    return out

首先提取输入x的填充张量。可以看到张量里有4760这种值,这个值是我们在文字长度不够时的填充内容。

 经过embedding映射后可以看到,张量out里的数据变成128*32*300的维度,300的维度就是词向量维度,可以看到data里的数据都由原来的整数映射成了向量。

经过lstm运算后,out张量数据变成了128*32*128的维度

 最终经过全连接层,out张量变成了128*10维度的张量。128是batch_size,10个维度即代表该条数据在10个分类中的概率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值