NLP入门——天池新闻文本分类(5)基于深度学习文本分类2

本文介绍了使用Word2Vec生成词向量并应用于新闻文本分类的任务,探讨了Skip-grams方法和TextCNN模型的实现细节,包括Skip-grams的负采样策略与Hierarchical Softmax在Word2Vec训练中的应用。
摘要由CSDN通过智能技术生成

NLP入门——天池新闻文本分类(5)基于深度学习的文本分类2

深度模型

前面提到新闻文本分类任务可以拆分成两步来进行,第一步先将文本表示成词向量,第二步则使用机器学习或深度学习模型来对模型输入(词向量)进行分类处理。因为模型的提升也可以从这两个方面来着手。第一种思路是选择更为合适的词向量方法,比如从one-hot词向量转变成Word2vec词向量;而第二种思路则是选择更为有效的预测模型,比如从多元线性回归模型转成集成树模型(GBDT, Xgboost, lightgbm)。在第4节提到的Fasttext方法在这个任务中可以看成是将两个步骤融合起来,同时进行。

word2vec

在本节中,我们尝试使用Word2Vec方法来生成词向量,再将这些词向量作为模型的输入,用于预测。这里重点参考了DataWhale公布的Word2Vec代码,语料选择的是本任务的语料。在DataWhale提供的参考代码中,模型似乎想要考虑不同类别新闻数量不一致可能带来的影响,使得各个类别的数据分布相对“均匀”,但不太确定这样做是否真的有效。另外模型似乎想要使用十折交叉验证法来进行训练和验证,因此制作了10个均等的数据集,但后续的代码又没真的进行十折交叉验证。感觉代码写的很乱。

以下描述一下使用Word2Vec方法得到词向量的思路,以及得到词向量后使用TextCNN来进行新闻文本分类的思路。Word2Vec 利用大量的语料信息将单词表示成词向量,在词向量的生成过程充分利用了语句中的上下文信息,从而使得词向量能够反映出语义信息。Word2Vec可以将one-hot编码的稀疏词向量表示成稠密编码的低维词向量,并使得词向量具有语义信息。有两种处理方式:CBOW (continuous bag of words ) 方法和Skipgrams方法。CBOW通过建立全连接神经网络,使用一段语句中的n-1个词预测剩下的一个词,从而获得该单词对应的隐向量,并将该向量作为词向量。CBOW通过建立全连接神经网络,使用一段语句中的n-1个词预测剩下的一个词,从而获得该单词对应的隐向量,并将该向量作为词向量。通过这样的处理后,就可以重复表示单词之间的关联关系,语义相近的单词,词向量也相近,从而可以很好地表示单词的语义信息。

通过Word2Vec得到词向量后,可以将文本各个词向量直接相加,从而抽象表示文本,再使用机器学习模型进行处理。这和第3小节中基于词袋模型的处理方法思路是类似的,但这样势必会遗漏文本中的大量信息。另一种思路则采取深度学习方法来进行分类处理。第一种方法可以使用循环神经网络(比如LSTM)来进行处理,这里可以通过单词补充的方式使得模型的输入是定长的。第二种方法可以采用更为复杂的特定模型来进行处理,比如TextCNN方法。还可以考虑引入注意力机制。这里重点介绍一下TextCNN方法。

Skip-grams(SG)过程

神经网络基于训练数据,将会输出一个概率分布,这些概率代表着词典中每个词作为input word的output word的可能性
模型的输出概率代表着我们词典中的每个词有多大可能性和input word同时出现

input word和out word都会进行one-hot编码,形成一个稀疏向量(实际上仅有一个位置是1)
为了节约计算资源,它会仅仅选择矩阵对应向量中维度值为1的索引行计算

Skip-grams训练

Word2Vec模型是一个超级大的神经网络(权重矩阵规模非常大)。
百万数量级的权重矩阵和亿万数量级的训练样本意味着训练灾难。

问题解决:

  1. 将常见的组合单词或词组作为单个’words’来处理
    2.对高频词抽样来减少样本个数
  2. 对优化目标采用’negative sampling’方法,这样每个训练样本的训练只会更新一小部分模型权重,从而降低计算负担
    3.1 负采样时,随机选择一小部分negative words来更新对应权重,同时对positive words更新权重
    3.2 使用’一元模型分布’来选择’negative words’,个单词被选作negative sample的概率和它出现频次有关,频次越高越容易被选中
    3.3 负采样代码中,有一个包含了一亿个元素的数组’unigram table’,数组由词汇表中每个单词的索引号填充。单次负采样的概率*1亿=单次在表中出现的次数;也就是说,进行负采样时,只需要在0-1亿范围内生成一个随机数,然后选择表中索引号为这个随机数的单次作为negative word即可;一个单词负采样概率越大,它在表中出现的次数越多,被选择的概率就越大
  3. 霍夫曼树:输入权值为(w1,w2…wn)的n个节点;输出对应的霍夫曼树,一般得到霍夫曼树后会对叶子节点进行霍夫曼编码,由于权重高的叶子节点靠近根节点,而权重低的叶子节点会远离根节点。 所以高权重节点编码值较短,而低权重值编码值较长,这保证了树的带权路径最短,也符合信息论:常用词拥有更短的编码
    4.1.将(w1,w2…wn)看做是有n棵树的森林,每个数仅有一个节点
    4.2.在森林中选择根节点权值最小的两个数合并,得到一棵新树,这两棵树分布作为新树的左右子树,新树根节点权重为左右子树根节点权重和
    4.3.删除森林中权值最小的两棵树,并把合并后的新树加入森林
    4.4.重复4.2与4.3,直到森林中只剩一棵树
    4.5.在Word2Vec中,约定左子树编码为1,右子树编码为0,同时约定左子树的权重不小于右子树的权重
  4. Hierarchical Softmax过程:为了避免计算所有词的softmax概率,Word2Vec采用了霍夫曼树代替从隐藏层到输出softmax层的映射。霍夫曼树的建立:
    5.1.根据标签(label)和频率建立霍夫曼树(label出现的频率越高,Huffman树的路径越短)
    5.2.Huffman树中每一叶子节点代表一个label
    5.2.1. p - 从根节点出发到达w对应叶子节点的路径
    5.2.2. l - 路径p中包含节点的个数
    5.2.3. p1,p2,…pl - 路径p中的l个节点,其中p1表示根节点,p2表示词w对应的第二个节点
    5.2.4. d2,d3,…dl∈{0,1} - 词w的Huffman编码,它有l-1位编码构成,dl表示路径p中第l个节点对应的编码(根节点无)
    5.2.5. θ1,θ2,…θ(l-1)∈R - 路径p中非叶子节点对应的向量,θj表示路径p中第j个非叶子节点对应的向量
    5.3.一棵Huffman树,是一个二分类树(二叉树)。再Word2Vec中,1表示负类,0表示正类,通过Sigmoid函数分类

word2Vec训练词向量

'''
model = Word2Vec(sentences, workers=num_workers, size=num_features)
参数详解:
    sentences - 语料集,可以是一个list,对于大语料集,建议使用BrownCorpus,Text8Corpus,lineSentence构建
    sg - 用于设置训练算法,默认为0,即CBOW算法;sg=1则采用skip-gram算法
    size - 指定特征向量的维度,默认为100。大的size需要更多的训练数据,但是效果会更好
    window - 指当前词与预测词在一个句子中的最大距离
    alpha - 学习速率
    seed - 随机种子
    min_count - 可以对字典做截断,词频数少于min_count则被丢弃,默认为5
    max_vocab_size - 设置词向量构建期间的RAM限制。
                        如果所有独立单词个数超过限制,则丢弃其中最不频繁的一个。每一千万个单词大约需要1GB的RAM
    sample - 高频词汇的随机降采样的配置阈值,默认1乘e的-3次方,范围是0到1乘e的-5次方
    workers - 参加控制训练的并行数
    hs - hs=1采用Hierarchica_softmax技巧,hs=0采用negative_sampling(下采样)
    iter - 迭代次数,默认5次
'''

TextCNN

TextCNN是2014年提出的模型。在对词向量输入进行处理时,使用了CNN。模型的结果如下:
在这里插入图片描述

textCNN Datawhale实现

import logging
import random

import numpy as np
import torch

logging.basicConfig(level=logging.INFO, format='%(asctime)-15s %(levelname)s: %(message)s')

# set seed 
seed = 666
random.seed(seed)
np.random.seed(seed)
torch.cuda.manual_seed(seed)
torch.manual_seed(seed)

# set cuda
gpu = 0
use_cuda = gpu >= 0 and torch.cuda.is_available()
if use_cuda:
    torch.cuda.set_device(gpu)
    device = torch.device("cuda", gpu)
else:
    device = torch.device("cpu")
logging.info("Use cuda: %s, gpu id: %d.", use_cuda, gpu)
2020-07-17 11:37:20,835 INFO: Use cuda: True, gpu id: 0.
# split data to 10 fold
fold_num = 10
data_file = '../data/train_set.csv'
import pandas as pd



def all_data2fold(fold_num, num=10000):
    fold_data = []
    f = pd.read_csv(data_file, sep='\t', encoding='UTF-8')
    texts = f['text'].tolist()[:num]
    labels = f['label'].tolist()[:num]

    total = len(labels)

    index = list(range(total))
    np.random.shuffle(index)

    all_texts = []
    all_labels = []
    for i in index:
        all_texts.append(texts[i])
        all_labels.append(labels[i])

    label2id = {
   }
    for i in range(total):
        label = str(all_labels[i])
        if label not in label2id:
            label2id[label] = [i]
        else:
            label2id[label].append(i)

    all_index = [[] for _ in range(fold_num)]
    for label, data in label2id.items():
        # print(label, len(data))
        batch_size = int(len(data) / fold_num)
        other = len(data) - batch_size * fold_num
        for i in range(fold_num):
            cur_batch_size = batch_size + 1 if i < other else batch_size
            # print(cur_batch_size)
            batch_data = [data[i * batch_size + b] for b in range(cur_batch_size)]
            all_index[i].extend(batch_data)

    batch_size = int(total / fold_num)
    other_texts = []
    other_labels = []
    other_num = 0
    start = 0
    for fold in range(fold_num):
        num = len(all_index[fold])
        texts = [all_texts[i] for i in all_index[fold]]
        labels = [all_labels[i] for i in all_index[fold]]

        if num > batch_size:
            fold_texts = texts[:batch_size]
            other_texts.extend(texts[batch_size:])
            fold_labels = labels[:batch_size]
            other_labels.extend(labels[batch_size:])
            other_num += num - batch_size
        elif num < batch_size:
            end = start + batch_size - num
            fold_texts = texts + other_texts[start: end]
            fold_labels = labels + other_labels[start: end]
            start = end
        else:
            fold_texts = texts
            fold_labels = labels

        assert batch_size == len(fold_labels)

        # shuffle
        index = list(range(batch_size))
        np.random.shuffle(index)

        shuffle_fold_texts = []
        shuffle_fold_labels = []
        for i in index:
            shuffle_fold_texts.append(fold_texts[i])
            shuffle_fold_labels.append(fold_labels[i])

        data = {
   'label': shuffle_fold_labels, 'text': shuffle_fold_texts}
        fold_data.append(data)

    logging.info("Fold lens %s", str([len(data['label']) for data in fold_data]))

    return fold_data


fold_data = all_data2fold(10)
2020-07-17 11:37:25,526 INFO: Fold lens [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000]
# build train, dev, test data
fold_id = 9

# dev
dev_data = fold_data[fold_id]

# train
train_texts = []
train_labels = []
for i in range(0, fold_id):
    data = fold_data[i]
    train_texts.extend(data['text'])
    train_labels.extend(data['label'])

train_data = {
   'label': train_labels, 'text': train_texts}

# test
test_data_file = '../data/test_a.csv'
f = pd.read_csv(test_data_file, sep='\t', encoding='UTF-8')
texts = f['text'].tolist()
test_data = {
   'label': [0] * len(texts), 'text': texts}
# build vocab
from collections import Counter
from transformers import BasicTokenizer

basic_tokenizer = BasicTokenizer()


class Vocab():
    def __init__(self, train_data):
        self.min_count = 5
        self.pad = 0
        self.unk = 1
        self._id2word = ['[PAD]', '[UNK]']
        self._id2extword = ['[PAD]', '[UNK]']

        self._id2label = []
        self.target_names = []

        self.build_vocab(train_data)

        reverse 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值