精华内容
下载资源
问答
  • 基于深度学习情感分类和智能客服研究与实现。主要是酒店和书店的评论情感分析,可以判定积极和消极,对于消极评论,还可以判断其具体类别,比如物流不好或者服务差等等。内含项目源代码和开发文档。
  • 本文来自于infoq,主要介绍了模型算法介绍,采用计算机来自动地分析文本表达的情感。更多内容请看下文。随着人们越来越喜欢在社交平台发表自己的看法和观点,这些平台成为企业发现用户兴趣,捕捉用户情绪很好的渠道。...
  • 文本情感分类(二):深度学习模型
    《文本情感分类(一):传统模型》一文中,笔者简单介绍了进行文本情感分类的传统思路。传统的思路简单易懂,而且稳定性也比较强,然而存在着两个难以克服的局限性:一、精度问题,传统思路差强人意,当然一般的应用已经足够了,但是要进一步提高精度,却缺乏比较好的方法;二、背景知识问题,传统思路需要事先提取好情感词典,而这一步骤,往往需要人工操作才能保证准确率,换句话说,做这个事情的人,不仅仅要是数据挖掘专家,还需要语言学家,这个背景知识依赖性问题会阻碍着自然语言处理的进步。
    

    庆幸的是,深度学习解决了这个问题(至少很大程度上解决了),它允许我们在几乎“零背景”的前提下,为某个领域的实际问题建立模型。本文延续上一篇文章所谈及的文本情感分类为例,简单讲解深度学习模型。其中上一篇文章已经详细讨论过的部分,本文不再详细展开。

    深度学习与自然语言处理

    近年来,深度学习算法被应用到了自然语言处理领域,获得了比传统模型更优秀的成果。如Bengio等学者基于深度学习的思想构建了神经概率语言模型,并进一步利用各种深层神经网络在大规模英文语料上进行语言模型的训练,得到了较好的语义表征,完成了句法分析和情感分类等常见的自然语言处理任务,为大数据时代的自然语言处理提供了新的思路。

    经过笔者的测试,基于深度神经网络的情感分析模型,其准确率往往有95%以上,深度学习算法的魅力和威力可见一斑!

    关于深度学习进一步的资料,请参考以下文献:

    [1] Yoshua Bengio, Réjean Ducharme Pascal Vincent, Christian Jauvin. A Neural Probabilistic Language Model, 2003
    [2] 一种新的语言模型:http://blog.sciencenet.cn/blog-795431-647334.html
    [3] Deep Learning(深度学习)学习笔记整理:http://blog.csdn.net/zouxy09/article/details/8775360
    [4] Deep Learning:http://deeplearning.net
    [5] 漫话中文自动分词和语义识别:http://www.matrix67.com/blog/archives/4212
    [6] Deep Learning 在中文分词和词性标注任务中的应用:http://blog.csdn.net/itplus/article/details/13616045

    语言的表达

    在文章《闲聊:神经网络与深度学习》中,笔者已经提到过,建模环节中最重要的一步是特征提取,在自然语言处理中也不例外。在自然语言处理中,最核心的一个问题是,如何把一个句子用数字的形式有效地表达出来?如果能够完成这一步,句子的分类就不成问题了。显然,一个最初等的思路是:给每个词语赋予唯一的编号1,2,3,4...,然后把句子看成是编号的集合,比如假设1,2,3,4分别代表“我”、“你”、“爱”、“恨”,那么“我爱你”就是[1, 3, 2],“我恨你”就是[1, 4, 2]。这种思路看起来有效,实际上非常有问题,比如一个稳定的模型会认为3跟4是很接近的,因此[1, 3, 2]和[1, 4, 2]应当给出接近的分类结果,但是按照我们的编号,3跟4所代表的词语意思完全相反,分类结果不可能相同。因此,这种编码方式不可能给出好的结果。

    读者也许会想到,我将意思相近的词语的编号凑在一堆(给予相近的编号)不就行了?嗯,确实如果,如果有办法把相近的词语编号放在一起,那么确实会大大提高模型的准确率。可是问题来了,如果给出每个词语唯一的编号,并且将相近的词语编号设为相近,实际上是假设了语义的单一性,也就是说,语义仅仅是一维的。然而事实并非如此,语义应该是多维的。

    比如我们谈到“家园”,有的人会想到近义词“家庭”,从“家庭”又会想到“亲人”,这些都是有相近意思的词语;另外,从“家园”,有的人会想到“地球”,从“地球”又会想到“火星”。换句话说,“亲人”、“火星”都可以看作是“家园”的二级近似,但是“亲人”跟“火星”本身就没有什么明显的联系了。此外,从语义上来讲,“大学”、“舒适”也可以看做是“家园”的二级近似,显然,如果仅通过一个唯一的编号,是很难把这些词语放到适合的位置的。

    词语的发散.png

    Word2Vec:高维来了

    从上面的讨论可以知道,很多词语的意思是各个方向发散开的,而不是单纯的一个方向,因此唯一的编号不是特别理想。那么,多个编号如何?换句话说,将词语对应一个多维向量?不错,这正是非常正确的思路。

    为什么多维向量可行?首先,多维向量解决了词语的多方向发散问题,仅仅是二维向量就可以360度全方位旋转了,何况是更高维呢(实际应用中一般是几百维)。其次,还有一个比较实际的问题,就是多维向量允许我们用变化较小的数字来表征词语。怎么说?我们知道,就中文而言,词语的数量就多达数十万,如果给每个词语唯一的编号,那么编号就是从1到几十万变化,变化幅度如此之大,模型的稳定性是很难保证的。如果是高维向量,比如说20维,那么仅需要0和1就可以表达220=1048576

    (100万)个词语了。变化较小则能够保证模型的稳定性。

    扯了这么多,还没有真正谈到点子上。现在思路是有了,问题是,如何把这些词语放到正确的高维向量中?而且重点是,要在没有语言背景的情况下做到这件事情?(换句话说,如果我想处理英语语言任务,并不需要先学好英语,而是只需要大量收集英语文章,这该多么方便呀!)在这里我们不可能也不必要进行更多的原理上的展开,而是要介绍:而基于这个思路,有一个Google开源的著名的工具——Word2Vec

    简单来说,Word2Vec就是完成了上面所说的我们想要做的事情——用高维向量(词向量,Word Embedding)表示词语,并把相近意思的词语放在相近的位置,而且用的是实数向量(不局限于整数)。我们只需要有大量的某语言的语料,就可以用它来训练模型,获得词向量。词向量好处前面已经提到过一些,或者说,它就是问了解决前面所提到的问题而产生的。另外的一些好处是:词向量可以方便做聚类,用欧氏距离或余弦相似度都可以找出两个具有相近意思的词语。这就相当于解决了“一义多词”的问题(遗憾的是,似乎没什么好思路可以解决一词多义的问题。)

    关于Word2Vec的数学原理,读者可以参考这系列文章。而Word2Vec的实现,Google官方提供了C语言的源代码,读者可以自行编译。而Python的Gensim库中也提供现成的Word2Vec作为子库(事实上,这个版本貌似比官方的版本更加强大)。

    表达句子:句向量

    接下来要解决的问题是:我们已经分好词,并且已经将词语转换为高维向量,那么句子就对应着词向量的集合,也就是矩阵,类似于图像处理,图像数字化后也对应一个像素矩阵;可是模型的输入一般只接受一维的特征,那怎么办呢?一个比较简单的想法是将矩阵展平,也就是将词向量一个接一个,组成一个更长的向量。这个思路是可以,但是这样就会使得我们的输入维度高达几千维甚至几万维,事实上是难以实现的。(如果说几万维对于今天的计算机来说不是问题的话,那么对于1000x1000的图像,就是高达100万维了!)

    事实上,对于图像处理来说,已经有一套成熟的方法了,叫做卷积神经网络(CNNs),它是神经网络的一种,专门用来处理矩阵输入的任务,能够将矩阵形式的输入编码为较低维度的一维向量,而保留大多数有用信息。卷积神经网络那一套也可以直接搬到自然语言处理中,尤其是文本情感分类中,效果也不错,相关的文章有《Deep Convolutional Neural Networks for Sentiment Analysis of Short Texts》。但是句子的原理不同于图像,直接将图像那一套用于语言,虽然略有小成,但总让人感觉不伦不类。因此,这并非自然语言处理中的主流方法。

    在自然语言处理中,通常用到的方法是递归神经网络循环神经网络(都叫RNNs)。它们的作用跟卷积神经网络是一样的,将矩阵形式的输入编码为较低维度的一维向量,而保留大多数有用信息。跟卷积神经网络的区别在于,卷积神经网络更注重全局的模糊感知(好比我们看一幅照片,事实上并没有看清楚某个像素,而只是整体地把握图片内容),而RNNs则是注重邻近位置的重构,由此可见,对于语言任务,RNNs更具有说服力(语言总是由相邻的字构成词,相邻的词构成短语,相邻的短语构成句子,等等,因此,需要有效地把邻近位置的信息进行有效的整合,或者叫重构)。

    说到模型的分类,可真谓无穷无尽。在RNNs这个子集之下,又有很多个变种,如普通的RNNs,以及GRU、LSTM等,读者可以参考Keras的官方文档:http://keras.io/models/,它是Python是一个深度学习库,提供了大量的深度学习模型,它的官方文档既是一个帮助教程,也是一个模型的列表——它基本实现了目前流行的深度学习模型。

    搭建LSTM模型

    吹了那么久水,是该干点实事了。现在我们基于LSTM(Long-Short Term Memory,长短期记忆人工神经网络)搭建一个文本情感分类的深度学习模型,其结构图如下:
    LSTM做情感分类.png
    模型结构很简单,没什么复杂的,实现也很容易,用的就是Keras,它都为我们实现好了现成的算法了。

    现在我们来谈谈有意思的两步。

    第一步是标注语料的收集。要注意我们的模型是监督训练的(至少也是半监督),所以需要收集一些已经分好类的句子,数量嘛,当然越多越好。而对于中文文本情感分类来说,这一步着实不容易,中文的资料往往是相当匮乏的。笔者在做模型的时候,东拼西凑,通过各种渠道(有在网上搜索下载的、有在数据堂花钱购买的)收集了两万多条中文标注语料(涉及六个领域)用来训练模型。(文末有共享)

    训练语料.png

    第二步是模型阈值选取问题。事实上,训练的预测结果是一个[0, 1]区间的连续的实数,而程序默认情况下会将0.5设为阈值,也就是将大于0.5的结果判断为正,将小于0.5的结果判断为负。这样的默认值在很多情况下并不是最好的。如下图所示,我们在研究不同的阈值对真正率和真负率的影响之时,发现在(0.391, 0.394)区间内曲线曲线了陡变。

    阈值的选取.png

    虽然从绝对值看,只是从0.99下降到了0.97,变化不大,但是其变化率是非常大的。正常来说都是平稳变化的,陡变意味着肯定出现了什么异常情况,而显然这个异常的原因我们很难发现。换句话说,这里存在一个不稳定的区域,这个区域内的预测结果事实上是不可信的,因此,保险起见,我们扔掉这个区间。只有结果大于0.394的,我们才认为是正,小于0.391的,我们才认为是负,是0.391到0.394之间的,我们待定。实验表明这个做法有助于提高模型的应用准确率。

    说点总结

    文章很长,粗略地介绍了深度学习在文本情感分类中的思路和实际应用,很多东西都是泛泛而谈。笔者并非要写关于深度学习的教程,而是只想把关键的地方指出来,至少是那些我认为是比较关键的地方。关于深度学习,有很多不错的教程,最好还是阅读英文的论文,中文的比较好的就是博客http://blog.csdn.net/itplus了,笔者就不在这方面献丑了。

    下面是我的语料和代码。读者可能会好奇我为什么会把这些“私人珍藏”共享呢?其实很简单,因为我不是干这行的哈,数据挖掘对我来说只是一个爱好,一个数学与Python结合的爱好,因此在这方面,我不用担心别人比我领先哈。

    语料下载:sentiment.zip
    采集到的评论数据:sum.zip

    搭建LSTM做文本情感分类的代码:

    import pandas as pd #导入Pandas
    import numpy as np #导入Numpy
    import jieba #导入结巴分词
    
    from keras.preprocessing import sequence
    from keras.optimizers import SGD, RMSprop, Adagrad
    from keras.utils import np_utils
    from keras.models import Sequential
    from keras.layers.core import Dense, Dropout, Activation
    from keras.layers.embeddings import Embedding
    from keras.layers.recurrent import LSTM, GRU
    
    from __future__ import absolute_import #导入3.x的特征函数
    from __future__ import print_function
    
    neg=pd.read_excel('neg.xls',header=None,index=None)
    pos=pd.read_excel('pos.xls',header=None,index=None) #读取训练语料完毕
    pos['mark']=1
    neg['mark']=0 #给训练语料贴上标签
    pn=pd.concat([pos,neg],ignore_index=True) #合并语料
    neglen=len(neg)
    poslen=len(pos) #计算语料数目
    
    cw = lambda x: list(jieba.cut(x)) #定义分词函数
    pn['words'] = pn[0].apply(cw)
    
    comment = pd.read_excel('sum.xls') #读入评论内容
    #comment = pd.read_csv('a.csv', encoding='utf-8')
    comment = comment[comment['rateContent'].notnull()] #仅读取非空评论
    comment['words'] = comment['rateContent'].apply(cw) #评论分词 
    
    d2v_train = pd.concat([pn['words'], comment['words']], ignore_index = True) 
    
    w = [] #将所有词语整合在一起
    for i in d2v_train:
      w.extend(i)
    
    dict = pd.DataFrame(pd.Series(w).value_counts()) #统计词的出现次数
    del w,d2v_train
    dict['id']=list(range(1,len(dict)+1))
    
    get_sent = lambda x: list(dict['id'][x])
    pn['sent'] = pn['words'].apply(get_sent) #速度太慢
    
    maxlen = 50
    
    print("Pad sequences (samples x time)")
    pn['sent'] = list(sequence.pad_sequences(pn['sent'], maxlen=maxlen))
    
    x = np.array(list(pn['sent']))[::2] #训练集
    y = np.array(list(pn['mark']))[::2]
    xt = np.array(list(pn['sent']))[1::2] #测试集
    yt = np.array(list(pn['mark']))[1::2]
    xa = np.array(list(pn['sent'])) #全集
    ya = np.array(list(pn['mark']))
    
    print('Build model...')
    model = Sequential()
    model.add(Embedding(len(dict)+1, 256))
    model.add(LSTM(256, 128)) # try using a GRU instead, for fun
    model.add(Dropout(0.5))
    model.add(Dense(128, 1))
    model.add(Activation('sigmoid'))
    
    model.compile(loss='binary_crossentropy', optimizer='adam', class_mode="binary")
    
    model.fit(xa, ya, batch_size=16, nb_epoch=10) #训练时间为若干个小时
    
    classes = model.predict_classes(xa)
    acc = np_utils.accuracy(classes, ya)
    print('Test accuracy:', acc)
    


    转载自:http://spaces.ac.cn/archives/3414/
    展开全文
  • Pytorch深度学习(6) -- 文本情感分类 RNN1. 文本情感分类:使用循环神经网络1.1 文本情感分类数据1.1.1 读取数据1.1.2 预处理数据1.1.3 创建数据迭代器1.2 使用循环神经网络的模型1.2.1 加载预训练的词向量1.2.2 ...

    1. 文本情感分类:使用循环神经网络

    文本分类是自然语言处理的一个常见任务,它把一段不定长的文本序列变换为文本的类别。本节关注它的一个子问题:使用文本情感分类来分析文本作者的情绪。这个问题也叫情感分析,并有着广泛的应用。例如,我们可以分析用户对产品的评论并统计用户的满意度,或者分析用户对市场行情的情绪并用以预测接下来的行情。

    同搜索近义词和类比词一样,文本分类也属于词嵌入的下游应用。在本节中,我们将应用预训练的词向量和含多个隐藏层的双向循环神经网络,来判断一段不定长的文本序列中包含的是正面还是负面的情绪。

    在实验开始前,导入所需的包或模块。

    import collections
    import os
    import random
    import tarfile
    import torch
    from torch import nn
    import torchtext.vocab as Vocab
    import torch.utils.data as Data
    
    import sys
    sys.path.append("..") 
    import d2lzh_pytorch as d2l
    
    os.environ["CUDA_VISIBLE_DEVICES"] = "0"
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    
    DATA_ROOT = "/S1/CSCL/tangss/Datasets"
    

    1.1 文本情感分类数据

    我们使用斯坦福的IMDb数据集(Stanford’s Large Movie Review Dataset)作为文本情感分类的数据集 [1]。这个数据集分为训练和测试用的两个数据集,分别包含25,000条从IMDb下载的关于电影的评论。在每个数据集中,标签为“正面”和“负面”的评论数量相等。

    1.1.1 读取数据

    首先下载这个数据集到DATA_ROOT路径下,然后解压。

    fname = os.path.join(DATA_ROOT, "aclImdb_v1.tar.gz")
    if not os.path.exists(os.path.join(DATA_ROOT, "aclImdb")):
        print("从压缩包解压...")
        with tarfile.open(fname, 'r') as f:
            f.extractall(DATA_ROOT)
    

    接下来,读取训练数据集和测试数据集。每个样本是一条评论及其对应的标签:1表示“正面”,0表示“负面”。

    from tqdm import tqdm
    def read_imdb(folder='train', data_root="/S1/CSCL/tangss/Datasets/aclImdb"): 
        data = []
        for label in ['pos', 'neg']:
            folder_name = os.path.join(data_root, folder, label)
            for file in tqdm(os.listdir(folder_name)):
                with open(os.path.join(folder_name, file), 'rb') as f:
                    review = f.read().decode('utf-8').replace('\n', '').lower()
                    data.append([review, 1 if label == 'pos' else 0])
        random.shuffle(data)
        return data
    
    train_data, test_data = read_imdb('train'), read_imdb('test')
    

    1.1.2 预处理数据

    我们需要对每条评论做分词,从而得到分好词的评论。这里定义的get_tokenized_imdb函数使用最简单的方法:基于空格进行分词。

    def get_tokenized_imdb(data):
        """
        data: list of [string, label]
        """
        def tokenizer(text):
            return [tok.lower() for tok in text.split(' ')]
        return [tokenizer(review) for review, _ in data]
    

    现在,我们可以根据分好词的训练数据集来创建词典了。我们在这里过滤掉了出现次数少于5的词。

    def get_vocab_imdb(data):
        tokenized_data = get_tokenized_imdb(data)
        counter = collections.Counter([tk for st in tokenized_data for tk in st])
        return Vocab.Vocab(counter, min_freq=5)
    
    vocab = get_vocab_imdb(train_data)
    '# words in vocab:', len(vocab)
    

    输出:

    ('# words in vocab:', 46151)
    

    因为每条评论长度不一致所以不能直接组合成小批量,我们定义preprocess_imdb函数对每条评论进行分词,并通过词典转换成词索引,然后通过截断或者补0来将每条评论长度固定成500。

    def preprocess_imdb(data, vocab):
        max_l = 500  # 将每条评论通过截断或者补0,使得长度变成500
    
        def pad(x):
            return x[:max_l] if len(x) > max_l else x + [0] * (max_l - len(x))
    
        tokenized_data = get_tokenized_imdb(data)
        features = torch.tensor([pad([vocab.stoi[word] for word in words]) for words in tokenized_data])
        labels = torch.tensor([score for _, score in data])
        return features, labels
    

    1.1.3 创建数据迭代器

    现在,我们创建数据迭代器。每次迭代将返回一个小批量的数据。

    batch_size = 64
    train_set = Data.TensorDataset(*preprocess_imdb(train_data, vocab))
    test_set = Data.TensorDataset(*preprocess_imdb(test_data, vocab))
    train_iter = Data.DataLoader(train_set, batch_size, shuffle=True)
    test_iter = Data.DataLoader(test_set, batch_size)
    

    打印第一个小批量数据的形状以及训练集中小批量的个数。

    for X, y in train_iter:
        print('X', X.shape, 'y', y.shape)
        break
    '#batches:', len(train_iter)
    

    输出:

    X torch.Size([64, 500]) y torch.Size([64])
    ('#batches:', 391)
    

    1.2 使用循环神经网络的模型

    在这个模型中,每个词先通过嵌入层得到特征向量。然后,我们使用双向循环神经网络对特征序列进一步编码得到序列信息。最后,我们将编码的序列信息通过全连接层变换为输出。具体来说,我们可以将双向长短期记忆在最初时间步和最终时间步的隐藏状态连结,作为特征序列的表征传递给输出层分类。在下面实现的BiRNN类中,Embedding实例即嵌入层,LSTM实例即为序列编码的隐藏层,Linear实例即生成分类结果的输出层。

    class BiRNN(nn.Module):
        def __init__(self, vocab, embed_size, num_hiddens, num_layers):
            super(BiRNN, self).__init__()
            self.embedding = nn.Embedding(len(vocab), embed_size)
            # bidirectional设为True即得到双向循环神经网络
            self.encoder = nn.LSTM(input_size=embed_size, 
                                    hidden_size=num_hiddens, 
                                    num_layers=num_layers,
                                    bidirectional=True)
            # 初始时间步和最终时间步的隐藏状态作为全连接层输入
            self.decoder = nn.Linear(4*num_hiddens, 2)
    
        def forward(self, inputs):
            # inputs的形状是(批量大小, 词数),因为LSTM需要将序列长度(seq_len)作为第一维,所以将输入转置后
            # 再提取词特征,输出形状为(词数, 批量大小, 词向量维度)
            embeddings = self.embedding(inputs.permute(1, 0))
            # rnn.LSTM只传入输入embeddings,因此只返回最后一层的隐藏层在各时间步的隐藏状态。
            # outputs形状是(词数, 批量大小, 2 * 隐藏单元个数)
            outputs, _ = self.encoder(embeddings) # output, (h, c)
            # 连结初始时间步和最终时间步的隐藏状态作为全连接层输入。它的形状为
            # (批量大小, 4 * 隐藏单元个数)。
            encoding = torch.cat((outputs[0], outputs[-1]), -1)
            outs = self.decoder(encoding)
            return outs
    

    创建一个含两个隐藏层的双向循环神经网络。

    embed_size, num_hiddens, num_layers = 100, 100, 2
    net = BiRNN(vocab, embed_size, num_hiddens, num_layers)
    

    1.2.1 加载预训练的词向量

    由于情感分类的训练数据集并不是很大,为应对过拟合,我们将直接使用在更大规模语料上预训练的词向量作为每个词的特征向量。这里,我们为词典vocab中的每个词加载100维的GloVe词向量。

    glove_vocab = Vocab.GloVe(name='6B', dim=100, cache=os.path.join(DATA_ROOT, "glove"))
    

    然后,我们将用这些词向量作为评论中每个词的特征向量。注意,预训练词向量的维度需要与创建的模型中的嵌入层输出大小embed_size一致。此外,在训练中我们不再更新这些词向量。

    def load_pretrained_embedding(words, pretrained_vocab):
        """从预训练好的vocab中提取出words对应的词向量"""
        embed = torch.zeros(len(words), pretrained_vocab.vectors[0].shape[0]) # 初始化为0
        oov_count = 0 # out of vocabulary
        for i, word in enumerate(words):
            try:
                idx = pretrained_vocab.stoi[word]
                embed[i, :] = pretrained_vocab.vectors[idx]
            except KeyError:
                oov_count += 1
        if oov_count > 0:
            print("There are %d oov words." % oov_count)
        return embed
    
    net.embedding.weight.data.copy_(
        load_pretrained_embedding(vocab.itos, glove_vocab))
    net.embedding.weight.requires_grad = False # 直接加载预训练好的, 所以不需要更新它
    

    输出:

    There are 21202 oov words.
    

    1.2.2 训练并评价模型

    这时候就可以开始训练模型了。

    lr, num_epochs = 0.01, 5
    # 要过滤掉不计算梯度的embedding参数
    optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, net.parameters()), lr=lr)
    loss = nn.CrossEntropyLoss()
    d2l.train(train_iter, test_iter, net, loss, optimizer, device, num_epochs)
    

    输出:

    training on  cuda
    epoch 1, loss 0.5759, train acc 0.666, test acc 0.832, time 250.8 sec
    epoch 2, loss 0.1785, train acc 0.842, test acc 0.852, time 253.3 sec
    epoch 3, loss 0.1042, train acc 0.866, test acc 0.856, time 253.7 sec
    epoch 4, loss 0.0682, train acc 0.888, test acc 0.868, time 254.2 sec
    epoch 5, loss 0.0483, train acc 0.901, test acc 0.862, time 251.4 sec
    

    最后,定义预测函数。

    def predict_sentiment(net, vocab, sentence):
        """sentence是词语的列表"""
        device = list(net.parameters())[0].device
        sentence = torch.tensor([vocab.stoi[word] for word in sentence], device=device)
        label = torch.argmax(net(sentence.view((1, -1))), dim=1)
        return 'positive' if label.item() == 1 else 'negative'
    

    下面使用训练好的模型对两个简单句子的情感进行分类。

    predict_sentiment(net, vocab, ['this', 'movie', 'is', 'so', 'great']) # positive
    
    predict_sentiment(net, vocab, ['this', 'movie', 'is', 'so', 'bad']) # negative
    

    小结

    • 文本分类把一段不定长的文本序列变换为文本的类别。它属于词嵌入的下游应用。
    • 可以应用预训练的词向量和循环神经网络对文本的情感进行分类。
    展开全文
  • Pytorch深度学习(7) -- 文本情感分类 CNN1. 文本情感分类:使用卷积神经网络(textCNN)1.1 一维卷积层1.2 时序最大池化层1.3 读取和预处理IMDb数据集1.4 textCNN模型1.4.1 加载预训练的词向量1.4.2 训练并评价...

    1. 文本情感分类:使用卷积神经网络(textCNN)

    在“卷积神经网络”一章中我们探究了如何使用二维卷积神经网络来处理二维图像数据。在之前的语言模型和文本分类任务中,我们将文本数据看作是只有一个维度的时间序列,并很自然地使用循环神经网络来表征这样的数据。其实,我们也可以将文本当作一维图像,从而可以用一维卷积神经网络来捕捉临近词之间的关联。本节将介绍将卷积神经网络应用到文本分析的开创性工作之一:textCNN [1]。

    首先导入实验所需的包和模块。

    import os
    import torch
    from torch import nn
    import torchtext.vocab as Vocab
    import torch.utils.data as Data
    import  torch.nn.functional as F
    
    import sys
    sys.path.append("..") 
    import d2lzh_pytorch as d2l
    
    os.environ["CUDA_VISIBLE_DEVICES"] = "0"
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    
    DATA_ROOT = "/S1/CSCL/tangss/Datasets"
    

    1.1 一维卷积层

    在介绍模型前我们先来解释一维卷积层的工作原理。与二维卷积层一样,一维卷积层使用一维的互相关运算。在一维互相关运算中,卷积窗口从输入数组的最左方开始,按从左往右的顺序,依次在输入数组上滑动。当卷积窗口滑动到某一位置时,窗口中的输入子数组与核数组按元素相乘并求和,得到输出数组中相应位置的元素。如图10.4所示,输入是一个宽为7的一维数组,核数组的宽为2。可以看到输出的宽度为72+1=67-2+1=6,且第一个元素是由输入的最左边的宽为2的子数组与核数组按元素相乘后再相加得到的:0×1+1×2=20\times1+1\times2=2

    图10.4 一维互相关运算
    下面我们将一维互相关运算实现在`corr1d`函数里。它接受输入数组`X`和核数组`K`,并输出数组`Y`。
    def corr1d(X, K):
        w = K.shape[0]
        Y = torch.zeros((X.shape[0] - w + 1))
        for i in range(Y.shape[0]):
            Y[i] = (X[i: i + w] * K).sum()
        return Y
    

    让我们复现图10.4中一维互相关运算的结果。

    X, K = torch.tensor([0, 1, 2, 3, 4, 5, 6]), torch.tensor([1, 2])
    corr1d(X, K)
    

    输出:

    tensor([ 2.,  5.,  8., 11., 14., 17.])
    

    多输入通道的一维互相关运算也与多输入通道的二维互相关运算类似:在每个通道上,将核与相应的输入做一维互相关运算,并将通道之间的结果相加得到输出结果。图10.5展示了含3个输入通道的一维互相关运算,其中阴影部分为第一个输出元素及其计算所使用的输入和核数组元素:0×1+1×2+1×3+2×4+2×(1)+3×(3)=20\times1+1\times2+1\times3+2\times4+2\times(-1)+3\times(-3)=2

    图10.5 含3个输入通道的一维互相关运算
    让我们复现图10.5中多输入通道的一维互相关运算的结果。
    def corr1d_multi_in(X, K):
        # 首先沿着X和K的第0维(通道维)遍历并计算一维互相关结果。然后将所有结果堆叠起来沿第0维累加
        return torch.stack([corr1d(x, k) for x, k in zip(X, K)]).sum(dim=0)
    
    X = torch.tensor([[0, 1, 2, 3, 4, 5, 6],
                  [1, 2, 3, 4, 5, 6, 7],
                  [2, 3, 4, 5, 6, 7, 8]])
    K = torch.tensor([[1, 2], [3, 4], [-1, -3]])
    corr1d_multi_in(X, K)
    

    输出:

    tensor([ 2.,  8., 14., 20., 26., 32.])
    

    由二维互相关运算的定义可知,多输入通道的一维互相关运算可以看作单输入通道的二维互相关运算。如图10.6所示,我们也可以将图10.5中多输入通道的一维互相关运算以等价的单输入通道的二维互相关运算呈现。这里核的高等于输入的高。图10.6中的阴影部分为第一个输出元素及其计算所使用的输入和核数组元素:2×(1)+3×(3)+1×3+2×4+0×1+1×2=22\times(-1)+3\times(-3)+1\times3+2\times4+0\times1+1\times2=2

    图10.6 单输入通道的二维互相关运算
    图10.4和图10.5中的输出都只有一个通道。我们在5.3节(多输入通道和多输出通道)一节中介绍了如何在二维卷积层中指定多个输出通道。类似地,我们也可以在一维卷积层指定多个输出通道,从而拓展卷积层中的模型参数。

    1.2 时序最大池化层

    类似地,我们有一维池化层。textCNN中使用的时序最大池化(max-over-time pooling)层实际上对应一维全局最大池化层:假设输入包含多个通道,各通道由不同时间步上的数值组成,各通道的输出即该通道所有时间步中最大的数值。因此,时序最大池化层的输入在各个通道上的时间步数可以不同。

    为提升计算性能,我们常常将不同长度的时序样本组成一个小批量,并通过在较短序列后附加特殊字符(如0)令批量中各时序样本长度相同。这些人为添加的特殊字符当然是无意义的。由于时序最大池化的主要目的是抓取时序中最重要的特征,它通常能使模型不受人为添加字符的影响。

    由于PyTorch没有自带全局的最大池化层,所以类似5.8节我们可以通过普通的池化来实现全局池化。

    class GlobalMaxPool1d(nn.Module):
        def __init__(self):
            super(GlobalMaxPool1d, self).__init__()
        def forward(self, x):
             # x shape: (batch_size, channel, seq_len)
             # return shape: (batch_size, channel, 1)
            return F.max_pool1d(x, kernel_size=x.shape[2])
    

    1.3 读取和预处理IMDb数据集

    我们依然使用和上一节中相同的IMDb数据集做情感分析。以下读取和预处理数据集的步骤与上一节中的相同。

    batch_size = 64
    train_data = d2l.read_imdb('train', data_root=os.path.join(DATA_ROOT, "aclImdb"))
    test_data = d2l.read_imdb('test', data_root=os.path.join(DATA_ROOT, "aclImdb"))
    vocab = d2l.get_vocab_imdb(train_data)
    train_set = Data.TensorDataset(*d2l.preprocess_imdb(train_data, vocab))
    test_set = Data.TensorDataset(*d2l.preprocess_imdb(test_data, vocab))
    train_iter = Data.DataLoader(train_set, batch_size, shuffle=True)
    test_iter = Data.DataLoader(test_set, batch_size)
    

    1.4 textCNN模型

    textCNN模型主要使用了一维卷积层和时序最大池化层。假设输入的文本序列由nn个词组成,每个词用dd维的词向量表示。那么输入样本的宽为nn,高为1,输入通道数为dd。textCNN的计算主要分为以下几步。

    1. 定义多个一维卷积核,并使用这些卷积核对输入分别做卷积计算。宽度不同的卷积核可能会捕捉到不同个数的相邻词的相关性。
    2. 对输出的所有通道分别做时序最大池化,再将这些通道的池化输出值连结为向量。
    3. 通过全连接层将连结后的向量变换为有关各类别的输出。这一步可以使用丢弃层应对过拟合。

    图10.7用一个例子解释了textCNN的设计。这里的输入是一个有11个词的句子,每个词用6维词向量表示。因此输入序列的宽为11,输入通道数为6。给定2个一维卷积核,核宽分别为2和4,输出通道数分别设为4和5。因此,一维卷积计算后,4个输出通道的宽为112+1=1011-2+1=10,而其他5个通道的宽为114+1=811-4+1=8。尽管每个通道的宽不同,我们依然可以对各个通道做时序最大池化,并将9个通道的池化输出连结成一个9维向量。最终,使用全连接将9维向量变换为2维输出,即正面情感和负面情感的预测。

    图10.7 textCNN的设计

    下面我们来实现textCNN模型。与上一节相比,除了用一维卷积层替换循环神经网络外,这里我们还使用了两个嵌入层,一个的权重固定,另一个则参与训练。

    class TextCNN(nn.Module):
        def __init__(self, vocab, embed_size, kernel_sizes, num_channels):
            super(TextCNN, self).__init__()
            self.embedding = nn.Embedding(len(vocab), embed_size)
            # 不参与训练的嵌入层
            self.constant_embedding = nn.Embedding(len(vocab), embed_size)
            self.dropout = nn.Dropout(0.5)
            self.decoder = nn.Linear(sum(num_channels), 2)
            # 时序最大池化层没有权重,所以可以共用一个实例
            self.pool = GlobalMaxPool1d()
            self.convs = nn.ModuleList()  # 创建多个一维卷积层
            for c, k in zip(num_channels, kernel_sizes):
                self.convs.append(nn.Conv1d(in_channels = 2*embed_size, 
                                            out_channels = c, 
                                            kernel_size = k))
    
        def forward(self, inputs):
            # 将两个形状是(批量大小, 词数, 词向量维度)的嵌入层的输出按词向量连结
            embeddings = torch.cat((
                self.embedding(inputs), 
                self.constant_embedding(inputs)), dim=2) # (batch, seq_len, 2*embed_size)
            # 根据Conv1D要求的输入格式,将词向量维,即一维卷积层的通道维(即词向量那一维),变换到前一维
            embeddings = embeddings.permute(0, 2, 1)
            # 对于每个一维卷积层,在时序最大池化后会得到一个形状为(批量大小, 通道大小, 1)的
            # Tensor。使用flatten函数去掉最后一维,然后在通道维上连结
            encoding = torch.cat([self.pool(F.relu(conv(embeddings))).squeeze(-1) for conv in self.convs], dim=1)
            # 应用丢弃法后使用全连接层得到输出
            outputs = self.decoder(self.dropout(encoding))
            return outputs
    

    创建一个TextCNN实例。它有3个卷积层,它们的核宽分别为3、4和5,输出通道数均为100。

    embed_size, kernel_sizes, nums_channels = 100, [3, 4, 5], [100, 100, 100]
    net = TextCNN(vocab, embed_size, kernel_sizes, nums_channels)
    

    1.4.1 加载预训练的词向量

    同上一节一样,加载预训练的100维GloVe词向量,并分别初始化嵌入层embeddingconstant_embedding,前者参与训练,而后者权重固定。

    glove_vocab = Vocab.GloVe(name='6B', dim=100,
                            cache=os.path.join(DATA_ROOT, "glove"))
    net.embedding.weight.data.copy_(
        d2l.load_pretrained_embedding(vocab.itos, glove_vocab))
    net.constant_embedding.weight.data.copy_(
        d2l.load_pretrained_embedding(vocab.itos, glove_vocab))
    net.constant_embedding.weight.requires_grad = False
    

    1.4.2 训练并评价模型

    现在就可以训练模型了。

    lr, num_epochs = 0.001, 5
    optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, net.parameters()), lr=lr)
    loss = nn.CrossEntropyLoss()
    d2l.train(train_iter, test_iter, net, loss, optimizer, device, num_epochs)
    

    输出:

    training on  cuda
    epoch 1, loss 0.4858, train acc 0.758, test acc 0.832, time 42.8 sec
    epoch 2, loss 0.1598, train acc 0.863, test acc 0.868, time 42.3 sec
    epoch 3, loss 0.0694, train acc 0.917, test acc 0.876, time 42.3 sec
    epoch 4, loss 0.0301, train acc 0.956, test acc 0.871, time 42.4 sec
    epoch 5, loss 0.0131, train acc 0.979, test acc 0.865, time 42.3 sec
    

    下面,我们使用训练好的模型对两个简单句子的情感进行分类。

    d2l.predict_sentiment(net, vocab, ['this', 'movie', 'is', 'so', 'great']) # positive
    
    d2l.predict_sentiment(net, vocab, ['this', 'movie', 'is', 'so', 'bad']) # negative
    

    小结

    • 可以使用一维卷积来表征时序数据。
    • 多输入通道的一维互相关运算可以看作单输入通道的二维互相关运算。
    • 时序最大池化层的输入在各个通道上的时间步数可以不同。
    • textCNN主要使用了一维卷积层和时序最大池化层。
    展开全文
  • 文本情感分类 读取数据 import collections import os import random import time from tqdm import tqdm import torch from torch import nn import torchtext.vocab as Vocab import torch.utils.data as Data ...

    文本情感分类

    读取数据

    import collections
    import os
    import random
    import time
    from tqdm import tqdm
    import torch
    from torch import nn
    import torchtext.vocab as Vocab
    import torch.utils.data as Data
    import torch.nn.functional as F
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    
    def read_imdb(folder='train', data_root="/home/kesci/input/IMDB2578/aclImdb_v1/aclImdb"):
        data = []
        for label in ['pos', 'neg']:
            folder_name = os.path.join(data_root, folder, label)
            for file in tqdm(os.listdir(folder_name)):
                with open(os.path.join(folder_name, file), 'rb') as f:
                    review = f.read().decode('utf-8').replace('\n', '').lower()
                    data.append([review, 1 if label == 'pos' else 0])
        random.shuffle(data)
        return data
    
    DATA_ROOT = "/home/kesci/input/IMDB2578/aclImdb_v1/"
    data_root = os.path.join(DATA_ROOT, "aclImdb")
    train_data, test_data = read_imdb('train', data_root), read_imdb('test', data_root)
    
    # 打印训练数据中的前五个sample
    for sample in train_data[:5]:
        print(sample[1], '\t', sample[0][:50])
    

    数据预处理

    def get_tokenized_imdb(data):
        '''
        @params:
            data: 数据的列表,列表中的每个元素为 [文本字符串,0/1标签] 二元组
        @return: 切分词后的文本的列表,列表中的每个元素为切分后的词序列
        '''
        def tokenizer(text):
            return [tok.lower() for tok in text.split(' ')]
        
        return [tokenizer(review) for review, _ in data]
    
    def get_vocab_imdb(data):
        '''
        @params:
            data: 同上
        @return: 数据集上的词典,Vocab 的实例(freqs, stoi, itos)
        '''
        tokenized_data = get_tokenized_imdb(data)
        counter = collections.Counter([tk for st in tokenized_data for tk in st])
        return Vocab.Vocab(counter, min_freq=5)
    
    vocab = get_vocab_imdb(train_data)
    print('# words in vocab:', len(vocab))
    
    def preprocess_imdb(data, vocab):
        '''
        @params:
            data: 同上,原始的读入数据
            vocab: 训练集上生成的词典
        @return:
            features: 单词下标序列,形状为 (n, max_l) 的整数张量
            labels: 情感标签,形状为 (n,) 的0/1整数张量
        '''
        max_l = 500  # 将每条评论通过截断或者补0,使得长度变成500
    
        def pad(x):
            return x[:max_l] if len(x) > max_l else x + [0] * (max_l - len(x))
    
        tokenized_data = get_tokenized_imdb(data)
        features = torch.tensor([pad([vocab.stoi[word] for word in words]) for words in tokenized_data])
        labels = torch.tensor([score for _, score in data])
        return features, labels
    

    创建数据迭代器

    train_set = Data.TensorDataset(*preprocess_imdb(train_data, vocab))
    test_set = Data.TensorDataset(*preprocess_imdb(test_data, vocab))
    
    # 上面的代码等价于下面的注释代码
    # train_features, train_labels = preprocess_imdb(train_data, vocab)
    # test_features, test_labels = preprocess_imdb(test_data, vocab)
    # train_set = Data.TensorDataset(train_features, train_labels)
    # test_set = Data.TensorDataset(test_features, test_labels)
    
    # len(train_set) = features.shape[0] or labels.shape[0]
    # train_set[index] = (features[index], labels[index])
    
    batch_size = 64
    train_iter = Data.DataLoader(train_set, batch_size, shuffle=True)
    test_iter = Data.DataLoader(test_set, batch_size)
    
    for X, y in train_iter:
        print('X', X.shape, 'y', y.shape)
        break
    print('#batches:', len(train_iter))
    

    循环神经网络

    在这里插入图片描述

    class BiRNN(nn.Module):
        def __init__(self, vocab, embed_size, num_hiddens, num_layers):
            '''
            @params:
                vocab: 在数据集上创建的词典,用于获取词典大小
                embed_size: 嵌入维度大小
                num_hiddens: 隐藏状态维度大小
                num_layers: 隐藏层个数
            '''
            super(BiRNN, self).__init__()
            self.embedding = nn.Embedding(len(vocab), embed_size)
            
            # encoder-decoder framework
            # bidirectional设为True即得到双向循环神经网络
            self.encoder = nn.LSTM(input_size=embed_size, 
                                    hidden_size=num_hiddens, 
                                    num_layers=num_layers,
                                    bidirectional=True)
            self.decoder = nn.Linear(4*num_hiddens, 2) # 初始时间步和最终时间步的隐藏状态作为全连接层输入
            
        def forward(self, inputs):
            '''
            @params:
                inputs: 词语下标序列,形状为 (batch_size, seq_len) 的整数张量
            @return:
                outs: 对文本情感的预测,形状为 (batch_size, 2) 的张量
            '''
            # 因为LSTM需要将序列长度(seq_len)作为第一维,所以需要将输入转置
            embeddings = self.embedding(inputs.permute(1, 0)) # (seq_len, batch_size, d)
            # rnn.LSTM 返回输出、隐藏状态和记忆单元,格式如 outputs, (h, c)
            outputs, _ = self.encoder(embeddings) # (seq_len, batch_size, 2*h)
            encoding = torch.cat((outputs[0], outputs[-1]), -1) # (batch_size, 4*h)
            outs = self.decoder(encoding) # (batch_size, 2)
            return outs
    
    embed_size, num_hiddens, num_layers = 100, 100, 2
    net = BiRNN(vocab, embed_size, num_hiddens, num_layers)
    

    加载预训练词向量

    cache_dir = "/home/kesci/input/GloVe6B5429"
    glove_vocab = Vocab.GloVe(name='6B', dim=100, cache=cache_dir)
    
    def load_pretrained_embedding(words, pretrained_vocab):
        '''
        @params:
            words: 需要加载词向量的词语列表,以 itos (index to string) 的词典形式给出
            pretrained_vocab: 预训练词向量
        @return:
            embed: 加载到的词向量
        '''
        embed = torch.zeros(len(words), pretrained_vocab.vectors[0].shape[0]) # 初始化为0
        oov_count = 0 # out of vocabulary
        for i, word in enumerate(words):
            try:
                idx = pretrained_vocab.stoi[word]
                embed[i, :] = pretrained_vocab.vectors[idx]
            except KeyError:
                oov_count += 1
        if oov_count > 0:
            print("There are %d oov words." % oov_count)
        return embed
    
    net.embedding.weight.data.copy_(load_pretrained_embedding(vocab.itos, glove_vocab))
    net.embedding.weight.requires_grad = False # 直接加载预训练好的, 所以不需要更新它
    

    训练模型

    def evaluate_accuracy(data_iter, net, device=None):
        if device is None and isinstance(net, torch.nn.Module):
            device = list(net.parameters())[0].device 
        acc_sum, n = 0.0, 0
        with torch.no_grad():
            for X, y in data_iter:
                if isinstance(net, torch.nn.Module):
                    net.eval()
                    acc_sum += (net(X.to(device)).argmax(dim=1) == y.to(device)).float().sum().cpu().item()
                    net.train()
                else:
                    if('is_training' in net.__code__.co_varnames):
                        acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item() 
                    else:
                        acc_sum += (net(X).argmax(dim=1) == y).float().sum().item() 
                n += y.shape[0]
        return acc_sum / n
    
    def train(train_iter, test_iter, net, loss, optimizer, device, num_epochs):
        net = net.to(device)
        print("training on ", device)
        batch_count = 0
        for epoch in range(num_epochs):
            train_l_sum, train_acc_sum, n, start = 0.0, 0.0, 0, time.time()
            for X, y in train_iter:
                X = X.to(device)
                y = y.to(device)
                y_hat = net(X)
                l = loss(y_hat, y) 
                optimizer.zero_grad()
                l.backward()
                optimizer.step()
                train_l_sum += l.cpu().item()
                train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()
                n += y.shape[0]
                batch_count += 1
            test_acc = evaluate_accuracy(test_iter, net)
            print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, time %.1f sec'
                  % (epoch + 1, train_l_sum / batch_count, train_acc_sum / n, test_acc, time.time() - start))
    
    lr, num_epochs = 0.01, 5
    optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, net.parameters()), lr=lr)
    loss = nn.CrossEntropyLoss()
    
    train(train_iter, test_iter, net, loss, optimizer, device, num_epochs)
    

    评价模型

    def predict_sentiment(net, vocab, sentence):
        '''
        @params:
            net: 训练好的模型
            vocab: 在该数据集上创建的词典,用于将给定的单词序转换为单词下标的序列,从而输入模型
            sentence: 需要分析情感的文本,以单词序列的形式给出
        @return: 预测的结果,positive 为正面情绪文本,negative 为负面情绪文本
        '''
        device = list(net.parameters())[0].device # 读取模型所在的环境
        sentence = torch.tensor([vocab.stoi[word] for word in sentence], device=device)
        label = torch.argmax(net(sentence.view((1, -1))), dim=1)
        return 'positive' if label.item() == 1 else 'negative'
    
    predict_sentiment(net, vocab, ['this', 'movie', 'is', 'so', 'great'])
    

    卷积神经网络

    def corr1d(X, K):
        '''
        @params:
            X: 输入,形状为 (seq_len,) 的张量
            K: 卷积核,形状为 (w,) 的张量
        @return:
            Y: 输出,形状为 (seq_len - w + 1,) 的张量
        '''
        w = K.shape[0] # 卷积窗口宽度
        Y = torch.zeros((X.shape[0] - w + 1))
        for i in range(Y.shape[0]): # 滑动窗口
            Y[i] = (X[i: i + w] * K).sum()
        return Y
    
    X, K = torch.tensor([0, 1, 2, 3, 4, 5, 6]), torch.tensor([1, 2])
    print(corr1d(X, K))
    
    def corr1d_multi_in(X, K):
        # 首先沿着X和K的通道维遍历并计算一维互相关结果。然后将所有结果堆叠起来沿第0维累加
        return torch.stack([corr1d(x, k) for x, k in zip(X, K)]).sum(dim=0)
        # [corr1d(X[i], K[i]) for i in range(X.shape[0])]
    
    X = torch.tensor([[0, 1, 2, 3, 4, 5, 6],
                  [1, 2, 3, 4, 5, 6, 7],
                  [2, 3, 4, 5, 6, 7, 8]])
    K = torch.tensor([[1, 2], [3, 4], [-1, -3]])
    print(corr1d_multi_in(X, K))
    

    时序最大池化层

    class GlobalMaxPool1d(nn.Module):
        def __init__(self):
            super(GlobalMaxPool1d, self).__init__()
        def forward(self, x):
            '''
            @params:
                x: 输入,形状为 (batch_size, n_channels, seq_len) 的张量
            @return: 时序最大池化后的结果,形状为 (batch_size, n_channels, 1) 的张量
            '''
            return F.max_pool1d(x, kernel_size=x.shape[2]) # kenerl_size=seq_len
    

    TextCNN

    在这里插入图片描述

    class TextCNN(nn.Module):
        def __init__(self, vocab, embed_size, kernel_sizes, num_channels):
            '''
            @params:
                vocab: 在数据集上创建的词典,用于获取词典大小
                embed_size: 嵌入维度大小
                kernel_sizes: 卷积核大小列表
                num_channels: 卷积通道数列表
            '''
            super(TextCNN, self).__init__()
            self.embedding = nn.Embedding(len(vocab), embed_size) # 参与训练的嵌入层
            self.constant_embedding = nn.Embedding(len(vocab), embed_size) # 不参与训练的嵌入层
            
            self.pool = GlobalMaxPool1d() # 时序最大池化层没有权重,所以可以共用一个实例
            self.convs = nn.ModuleList()  # 创建多个一维卷积层
            for c, k in zip(num_channels, kernel_sizes):
                self.convs.append(nn.Conv1d(in_channels = 2*embed_size, 
                                            out_channels = c, 
                                            kernel_size = k))
                
            self.decoder = nn.Linear(sum(num_channels), 2)
            self.dropout = nn.Dropout(0.5) # 丢弃层用于防止过拟合
    
        def forward(self, inputs):
            '''
            @params:
                inputs: 词语下标序列,形状为 (batch_size, seq_len) 的整数张量
            @return:
                outputs: 对文本情感的预测,形状为 (batch_size, 2) 的张量
            '''
            embeddings = torch.cat((
                self.embedding(inputs), 
                self.constant_embedding(inputs)), dim=2) # (batch_size, seq_len, 2*embed_size)
            # 根据一维卷积层要求的输入格式,需要将张量进行转置
            embeddings = embeddings.permute(0, 2, 1) # (batch_size, 2*embed_size, seq_len)
            
            encoding = torch.cat([
                self.pool(F.relu(conv(embeddings))).squeeze(-1) for conv in self.convs], dim=1)
            # encoding = []
            # for conv in self.convs:
            #     out = conv(embeddings) # (batch_size, out_channels, seq_len-kernel_size+1)
            #     out = self.pool(F.relu(out)) # (batch_size, out_channels, 1)
            #     encoding.append(out.squeeze(-1)) # (batch_size, out_channels)
            # encoding = torch.cat(encoding) # (batch_size, out_channels_sum)
            
            # 应用丢弃法后使用全连接层得到输出
            outputs = self.decoder(self.dropout(encoding))
            return outputs
    
    embed_size, kernel_sizes, nums_channels = 100, [3, 4, 5], [100, 100, 100]
    net = TextCNN(vocab, embed_size, kernel_sizes, nums_channels)
    

    训练并评价模型

    lr, num_epochs = 0.001, 5
    optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, net.parameters()), lr=lr)
    loss = nn.CrossEntropyLoss()
    train(train_iter, test_iter, net, loss, optimizer, device, num_epochs)
    
    展开全文
  • 现在我们基于LSTM(Long-Short Term Memory,长短期记忆人工神经网络)搭建一个文本情感分类深度学习模型,其结构图如下: 模型结构很简单,没什么复杂的,实现也很容易,用的就是Keras,它都为我们...
  • 文本情感分类深度学习模型

    千次阅读 2017-10-11 21:53:25
    # -*- coding:utf-8 -*-''' word embedding测试 在GTX960上,18s一轮 经过30轮迭代,训练集准确率为98.41%,测试集准确率为89.03% Dropout不能用太多,否则信息损失太严重 '''import numpy as np ...
  • 在《文本情感分类:传统模型(1)》一文中,简单介绍了进行文本情感分类的传统思路。 传统的思路简单易懂,而且稳定性也比较强,然而存在着两个难以克服的局限性:一、精度问题,传统思路差强人意,当然一般的应用...
  • 俗话说得好,万事开头难,...当然,更主要是因为小J一时脑热,选了一门计系的人工智能基础,大作业是用深度学习做金融文本情感分类,语言、工具等自选(人生苦短,我用python)。本想抱计系大佬大腿,然鹅阴差阳错...
  • 向AI转型的程序员都关注了这个号????????????大数据挖掘DT机器学习 公众号:datayx基于情感词典的文本情感分类传统的基于情感词典的文本情感分类,是对人的记忆和判断思维的...
  • 如果能够完成这一步,句子的分类就不成问题了。显然,一个最初等的思路是:给每个词语赋予唯一的编号1,2,3,4...,然后把句子看成是编号的集合,比如假设1,2,3,4分别代表“我”、“你”、“爱”、“恨”,那么...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 412
精华内容 164
关键字:

深度学习文本情感分类