1. 文本分类概述


1. 什么是文本分类?

文本分类技术是指在预先定义好的一些主题(如体育、教育、科技、时政、时尚等)下,根据文本的属性、含义或内容对其进行主题抽取,将大量的文本数据归类到相应的主题中。

分类是信息检索领域多年来一直研究的课题,一方面以搜索的应用为目的来提高有效性和某些情况下的效率;另一方面,分类也是经典的机器学习技术。在机器学习领域,分类是在有标注的预定义类别体系下进行,因此属于有监督的学习问题;相反聚类则是一种无监督的学习问题。

文本分类方向: 主要有二分类,多分类,多标签分类
文本分类方法: 传统机器学习方法(贝叶斯,svm等),深度学习方法(fastText,TextCNN等)

2. 文本分类流程

一般情况下,文本分类的主要流程如下:
在这里插入图片描述

2.1 文本预处理

预处理模块的主要任务是:对组成训练集和预测集的语料库进行预先处理。训练集的语料是分类整理好的语料库,用来训练模型。测试集的语料是用来检测训练好的模型的实际效果,也是已经分类好的语料库。对这些语料库进行预处理包括以下几个方面:

(1)文档切分:文档切分这个步骤的有无主要取决于爬取的数据源的形式。如果得到的文档集合本身就是按照文章分开的,那么这个步骤就是可省的。反之,如果文档集合是一个单一的文件没那么就需要对这个单一文件进行文档切分,是每篇文章单独地存在于一个文件之中,以便于后来的操作。

(2)文本分词:文本分词这个步骤的有无取决于源文档的语言类型。如果源文档是英文文档,则不需要进行分词;若源文档是中文文本,则需要进行分词。经验表明,词级文本分类比字级文本分类具有更好的效果,因此,文本分词在中文文本分类中是必不可少的一步。在本文中,实验采用的是结巴分词算法。结巴分词用python开发,使用起来方便快捷,分词效果也十分良好。结巴分词的分词原理主要分为三个步骤:第一、使用正则表达式将中文文章中的段落粗略地分成一个个句子。第二、将分好的每个句子构成有向无环图,然后再寻找最佳切分方案。第三、对于生成的连续单字,采用HMM模型对其进行再次切分。

(3)去停用词:对于中文文本分类来说去停用词是预处理模块当中必不可少的一部分。一些词语比如“你我他”、“这个”、“的”等是对文本分类结果没有影响的冗余单词,他们不能正确的表征句子实质含义,那么就应该从文本中清除掉。在这里,可采用哈工大停用词表。去停用词可以有效地帮助我们提高关键词密度。建立停用词字典,目前停用词字典有2000个左右,停用词主要包括一些副词、形容词及其一些连接词。通过维护一个停用词表,实际上是一个特征提取的过程,本质 上是特征选择的一部分。

2.2 文本特征表示

在向量空间模型中,文本可以选择字、词组、短语、甚至“概念”等多种元素表示。这些元素用来表征文本的性质,区别文本的属性,因此这些元素可以被称为文本的特征。在文本数据集上一般含有数万甚至数十万个不同的词组,如此庞大的词组构成的向量规模惊人,计算机运算非常困难。进行特征选择,对文本分类具有重要的意义。特征选择就是要选择那些最能表征文本含义的词组元素。特征选择不仅可以降低问题的规模,还有助于分类性能的改善。选取不同的特征对文本分类系统的性能有不同程度的影响。已提出的文本分类特征选择方法比较多,常用的方法有:文档频率(Document Frequency,DF)、信息增益(Information Gain,IG)、 校验(CHI)和互信息(Mutual Information,MI)等方法。

扫描二维码关注公众号,回复: 10455693 查看本文章

常见的文本表征的模型有:

传统的: one-hot(问题:维度高)、tf-idf(词频统计)

基于词向量的: Word2vec、doc2vec、glove、festext(基于特定任务、结构简单)

基于语言模型: ELMO、GPT、BERT (基于文本特征的,需要大量数据集)

2.2.1 One Hot(独热)编码

One-Hot表示是把语料库中的所有文本进行分词,把所有单词(词汇)收集起来,并对单词进行编号,构建一个词汇表(vocabulary),词汇表是一个字典结构,key是单词,value是单词的索引。如果词汇表有n个单词构成,那么单词的索引从0开始,到n-1结束。

词汇向量有n列,但是只有一列的值为1,把值为1的列的索引带入到词汇表(vocabulary)中,就可以查找到该词向量表示的词汇,也就是说,对于某个单词 term,如果它出现在词汇序列中的位置为 k,那么它的向量表示就是“第 k 位为1,其他位置都为0 ”,这就是One-Hot(独热)名称的由来。

举个例子来说:假设现有分词后的语料库如下

我 爱 学习 学习 爱 我
学习 很 重要
我 努力 学习

把上述语料中的词汇整理出来并进行排序(排序原则另说,可以有很多),假设我们的词汇表排序结果如下:

{“我”:3,“爱”:2,“学习”:4,“很”:1,“重要”:1,“努力”:1}

(1)单词的词向量

得出如下词向量表示:

“我”:[1,0,0,0,0,0]
“爱”:[0,1,0,0,0,0]

(2)文档向量
文档向量的表示方法是直接把各词的词向量表示加和,即文档向量中,列的值表示词在文档中出现的次数。
那么原来的三句话的向量表示如下:

[2,2,2,0,0,0]
[0,0,1,1,1,0]
[1,0,1,0,0,1]

One-Hot方法很简单,但是它的问题也很明显:

  • 没有考虑单词之间的相对位置(文本中词的顺序信息也是很重要的);
  • 任意两个词之间都是孤立的(在大多数情况下,词与词是相互影响的);
  • 如果文档中有很多词,词向量会有很多列,但是只有一个列的值是1(它得到的特征是离散稀疏的);

编程实现:

# 自定义方法:文本转onehot编码
"""
说明:
文档格式txt,每行表示一个训练样本(一句话,一段文字都可以)
文档如果是不分行的一段话,可以用nltk.tokenize.sent_tokenize(text)将文档分句,然后把每句话当作一个样本进行编码
"""
import numpy as np
import pandas as pd
import jieba

def doc2onthot_matrix():
    # 读取待编码的文件
    file_path=input("请输入待编码文件路径及文件名:")
    with open(file_path,encoding="utf-8") as f:
        docs=f.readlines()
    
    # 将文件每行分词,分词后的词语放入words中
    words=[]
    for i in range(len(docs)):
        docs[i]=jieba.lcut(docs[i].strip("\n"))
        words+=docs[i]
    
    # 找出分词后不重复的词语,作为词袋,是后续onehot编码的维度
    vocab=sorted(set(words),key=words.index)
    
    # 建立一个M行V列的全0矩阵,M问文档样本数,这里是行数,V为不重复词语数,即编码维度
    V=len(vocab)
    M=len(docs)
    onehot=np.zeros((M,V))
    
    for i,doc in enumerate(docs):
        for word in doc:
            if word in vocab:
                pos=vocab.index(word)
                onehot[i][pos]=1
    onehot=pd.DataFrame(onehot,columns=vocab)
    return onehot

2.2.2 TF-IDF模型

IF-IDF是信息检索(IR)中最常用的一种文本表示法。算法的思想也很简单,就是统计每个词出现的词频(TF),然后再为其附上一个权值参数(IDF)。TF 称为词频, 用于计算该词描述文档内容的能力; IDF 称为逆文档频率, 用于计算该词区分文档的能力。TF*IDF 的指导思想建立在这样一条基本假设之上::在一个文本中出现很多次的单词, 在另一个同类文本中出现次数也会很多, 反之亦然。

**词语的重要性程度是根据其在文章中出现的次数,成正比增加;另外,随着其在语料库中所有文本中出现的次数,成反比下降。**更通俗地说,一个词语在一篇文章中出现的次数越多,表示该词语对文章的影响就越大;同时该词语在所有文章中出现的次数越少,说明这个词语属于这个文章中的独特词条,即对改文章的作用就越强。

假设要统计一篇文档中的前10个关键词.首先想到的是统计一下文档中每个词出现的频率(TF),词频越高,这个词就越重要。但是统计完你可能会发现你得到的关键词基本都是“的”、“是”、“为”这样没有实际意义的词(停用词),这个问题怎么解决呢?你可能会想到为每个词都加一个权重,像这种”停用词“就加一个很小的权重(甚至是置为0),这个权重就是IDF。公式如下:

在这里插入图片描述
TF应该很容易理解就是计算词频,IDF衡量词的常见程度。为了计算IDF我们需要事先准备一个语料库用来模拟语言的使用环境,如果一个词越是常见,那么式子中分母就越大,逆文档频率就越小越接近于0。这里的分母+1是为了避免分母为0的情况出现。TF-IDF的计算公式如下:
在这里插入图片描述
根据公式很容易看出,TF-IDF的值与该词在文章中出现的频率成正比,与该词在整个语料库中出现的频率成反比,因此可以很好的实现提取文章中关键词的目的。

其实,TF-IDF方法是将One-hot中为1的列改成了权值TF-IDF

编程实现

import numpy as np
import pandas as pd
import math
import jieba

def doc2tfidf_matrix():
    # 读取待编码的文件
    file_path=input("请输入待编码文件路径及文件名:")
    with open(file_path,encoding="utf-8") as f:
        docs=f.readlines()
    
    # 将文件每行分词,分词后的词语放入words中
    words=[]
    for i in range(len(docs)):
        docs[i]=jieba.lcut(docs[i].strip("\n"))
        words+=docs[i]
    
    # 找出分词后不重复的词语,作为词袋
    vocab=sorted(set(words),key=words.index)
    
    # 建立一个M行V列的全0矩阵,M问文档样本数,这里是行数,V为不重复词语数,即编码维度
    V=len(vocab)
    M=len(docs)
    onehot=np.zeros((M,V)) # 二维矩阵要使用双括号
    tf=np.zeros((M,V))
    
    for i,doc in enumerate(docs):
        for word in doc:
            if word in vocab:
                pos=vocab.index(word)
                onehot[i][pos]=1
                tf[i][pos]+=1 # tf,统计某词语在一条样本中出现的次数

    row_sum=tf.sum(axis=1) # 行相加,得到每个样本出现的词语数
    # 计算TF(t,d)
    tf=tf/row_sum[:,np.newaxis] #分母表示各样本出现的词语数,tf为单词在样本中出现的次数,[:,np.newaxis]作用类似于行列转置
    # 计算DF(t,D),IDF
    df=onehot.sum(axis=0) # 列相加,表示有多少样本包含词袋某词
    idf=list(map(lambda x:math.log10((M+1)/(x+1)),df))
    
    # 计算TFIDF
    tfidf=tf*np.array(idf)
    tfidf=pd.DataFrame(tfidf,columns=vocab)
    return tfidf

2.2.3 文本深度表示模型Word2Vec

传统的(基于计数的)文本数据特征工程策略包括了一大类的模型,这些模型通常称为词袋模型:包括词频、TF-IDF(词频逆文档频率)、N-grams等等。虽然它们是从文本中提取特征的有效方法,但是由于模型本身就是一袋非结构化的单词,我们丢失了额外的信息,比如每个文本文档中围绕邻近单词的语义、结构、序列和上下文。

因此,这就需要一种能够解决这种问题的办法。为了克服词袋模型没有语义以及特征稀疏的缺点,我们需要利用向量空间模型(VMS),这样,我们就可以把单词嵌入到基于语义和上下文的连续向量空间中。事实上,语义分布领域的分布假设告诉我们,在相同上下文中出现的单词语义上是相似的,具有相似的意义。简单地说,“一个词的特征取决于和它一起出现的词”。详见论文《Don’t count, predict! A systematic comparison of context-counting vs. context-predicting semantic vectors》。简而言之,上下文词向量有两种主要的方法。基于计数的方法,比如Latent Semantic Analysis (LSA)可以用于计算某些词和它的相邻的词一起出现频率的统计度量,然后基于这些度量构建dense的向量。基于神经网络的语言模型的预测方法试着从相邻的单词中预测单词,观察语料库中的单词序列,在这个过程中,它学习分布表示,给我们dense的词嵌入。

下面,我们来讨论一下预测方法Word2Vec

该模型由谷歌于2013年创建,是一种基于预测的深度学习模型,用于计算和生成高质量的、连续的dense的单词向量表示,并捕捉上下文和语义相似性。本质上,这些是无监督的模型,可以接收大量的文本语料库,创建可能的单词的词汇表,并为表示该词汇表的向量空间中的每个单词生成dense的单词嵌入。通常可以指定单词的嵌入向量的大小,向量的总数本质上就是词汇表的大小。这使得该向量空间的维度大大低于传统的词袋模型构建出的高维稀疏的向量空间。

Word2vec 使用的词向量不是我们上述提到的One-hot Representation那种词向量,而是 Distributed representation 的词向量表示方式。其基本思想是 通过训练将每个词映射成 K 维实数向量(K 一般为模型中的超参数),通过词之间的距离(比如 cosine 相似度、欧氏距离等)来判断它们之间的语义相似度。

Word2Vec可以利用两种不同的模型结构来创建这些单词嵌入表示。

(1). Continuous Bag of Words(CBOW)模型

CBOW模型体系结构试图基于上下文单词(周围单词)预测当前目标单词(中心单词)。

考虑一个简单的句子,“the quick brown fox jumps over the lazy dog”,这可以是(context_window, target_word)对,如果我们考虑一个大小为2的上下文窗口,我们有([quick, fox], brown)([the, brown], quick)([the, dog], lazy)等例子。因此,该模型试图基于context_window单词预测target_word。
在这里插入图片描述
我们将在没有任何辅助信息的情况下,从语料库本身来做。可以将CBOW结构建模为一个深度学习分类模型,这样我们就可以将上下文单词作为输入X,并尝试预测目标单词Y。

虽然使用像gensim这样具有Word2Vec模型的框架非常好用,但是现在让我们从头开始实现它,以了解幕后的工作原理。

实现将集中于以下几个部分

  1. 构建语料库词汇表
  2. 建立一个CBOW(上下文,目标)生成器
  3. 构建CBOW模型架构
  4. 训练模型
  5. 获取Word Embeddings

接下来将展开详细的实现细节,我们将利用norm_bible变量中包含的Bible语料库来训练模型。

1. 构建语料库词汇表

首先,我们将构建语料库词汇表,从词汇表中提取每个惟一的单词,并将一个惟一的数字标识符映射到它。

from keras.preprocessing import text

from keras.utils import np_utils

from keras.preprocessing import sequence

tokenizer = text.Tokenizer()

tokenizer.fit_on_texts(norm_bible)

word2id = tokenizer.word_index

# build vocabulary of unique words

word2id['PAD'] = 0

id2word = {v:k for k, v in word2id.items()}

wids = [[word2id[w] for w in text.text_to_word_sequence(doc)] for doc in norm_bible]

vocab_size = len(word2id)

embed_size = 100

window_size = 2 # context window size

print('Vocabulary Size:', vocab_size)

print('Vocabulary Sample:', list(word2id.items())[:10])

Output

------

Vocabulary Size: 12425

Vocabulary Sample: [('perceived', 1460), ('flagon', 7287), ('gardener',11641), ('named', 973), ('remain', 732), ('sticketh', 10622), ('abstinence', 11848), ('rufus', 8190), ('adversary', 2018), ('jehoiachin', 3189)]

可以看到,我们已经在语料库中创建了一个包含惟一单词的词汇表,以及将单词映射到其惟一标识符的方法,反之亦然。“PAD”通常用于在需要时将上下文单词填充为固定长度。

2. 建立一个CBOW(上下文,目标)生成器

我们需要由目标中心词和及其上下文词组成的对。在我们的实现中,目标单词的长度为1,周围的上下文的长度为2 * window_size,其中我们在语料库中的目标单词前后分别取window_size个单词。

def generate_context_word_pairs(corpus, window_size, vocab_size):

context_length = window_size*2

for words in corpus:

sentence_length = len(words)

for index, word in enumerate(words):

context_words = []

label_word = []

start = index - window_size

end = index + window_size + 1

context_words.append([words[i]

for i in range(start, end)

if 0 <= i < sentence_length

and i != index])

label_word.append(word)

x =

sequence.pad_sequences(context_words, maxlen=context_length)

y = np_utils.to_categorical(label_word, vocab_size)

yield (x, y)

# Test this out for some samples

i = 0

for x, y in generate_context_word_pairs(corpus=wids, window_size=window_size, vocab_size=vocab_size):

if 0 not in x[0]:

print('Context (X):', [id2word[w] for w in x[0]], '-> Target (Y):', id2word[np.argwhere(y[0])[0][0]])

if i == 10:

break

i += 1

Context (X): ['old','testament','james','bible'] -> Target (Y): king

Context (X): ['first','book','called','genesis'] -> Target(Y): moses

Context (X):['beginning','god','heaven','earth'] -> Target(Y):created

Context (X):['earth','without','void','darkness'] -> Target(Y): form

Context (X): ['without','form','darkness','upon'] -> Target(Y): void

Context (X): ['form', 'void', 'upon', 'face'] -> Target(Y): darkness

Context (X): ['void', 'darkness', 'face', 'deep'] -> Target(Y): upon

Context (X): ['spirit', 'god', 'upon', 'face'] -> Target (Y): moved

Context (X): ['god', 'moved', 'face', 'waters'] -> Target (Y): upon

Context (X): ['god', 'said', 'light', 'light'] -> Target (Y): let

Context (X): ['god', 'saw', 'good', 'god'] -> Target (Y): light

前面的输出应该让你对X如何形成我们的上下文单词有更多的了解,我们正试图根据这个上下文预测目标中心单词Y。例如,如果原始文本是“in the beginning god created heaven and earth”,经过预处理和删除停止词后,就变成了‘beginning god created heaven earth’ ,而对于我们来说,我们正在努力实现的就是这个目标。给定[beginning, god, heaven, earth] 作为上下文,预测目标中心词是什么,在本例中是’ created ’

3. 构建CBOW模型架构

我们现在利用keras构建CBOW模型的深度学习架构。为此,我们的输入是传递给嵌入层(用随机权重初始化)的上下文单词。词嵌入被传播到λ层,做词嵌入的平均 (叫CBOW是因为在做平均的时候我们真的不考虑上下文词的顺序),我们把平均后的向量送到一个dense的softmax层去预测我们的目标词。我们将其与实际的目标字匹配,通过利用categorical_crossentropy损失计算损失,并对每个epoch执行反向传播来更新嵌入层。下面的代码向我们展示了我们的模型架构。

import keras.backend as K

from keras.models import Sequential

from keras.layers import Dense, Embedding, Lambda

# build CBOW architecture

cbow = Sequential()

cbow.add(Embedding(input_dim=vocab_size, output_dim=embed_size, input_length=window_size*2))

cbow.add(Lambda(lambda x: K.mean(x, axis=1), output_shape=(embed_size,)))

cbow.add(Dense(vocab_size, activation='softmax'))

cbow.compile(loss='categorical_crossentropy', optimizer='rmsprop')

# view model summary

print(cbow.summary())

# visualize model structure

from IPython.display import SVG

from keras.utils.vis_utils import model_to_dot

SVG(model_to_dot(cbow, show_shapes=True, show_layer_names=False,

rankdir='TB').create(prog='dot', format='svg'))

在这里插入图片描述
如果你对上述深度学习模型的形象化仍有困难。可以看看下图:

在这里插入图片描述
4.训练模型

for epoch in range(1, 6):

loss = 0.

i = 0

for x, y in generate_context_word_pairs(corpus=wids, window_size=window_size, vocab_size=vocab_size):

i += 1

loss += cbow.train_on_batch(x, y)

if i % 100000 == 0:

print('Processed {} (context, word) pairs'.format(i))

print('Epoch:', epoch, '\tLoss:', loss)

print()

Epoch: 1 Loss: 4257900.60084

Epoch: 2 Loss: 4256209.59646

Epoch: 3 Loss: 4247990.90456

Epoch: 4 Loss: 4225663.18927

Epoch: 5 Loss: 4104501.48929

一旦这个模型被训练好,相似的单词应该就有相似的基于嵌入的权值,我们可以测试一下相似性。

获取词嵌入

要为整个词汇表获取词嵌入,可以利用下面的代码从嵌入层提取。我们不接受位置为0的嵌入,因为它属于 (PAD) ,这并不是一个真正的单词。

weights = cbow.get_weights()[0]

weights = weights[1:]

print(weights.shape)

pd.DataFrame(weights, index=list(id2word.values())[1:]).head()

在这里插入图片描述
可以清楚地看到,正如前面的输出所描述的,每个单词都有一个dense的大小为“(1x100)”的嵌入。让我们尝试根据这些嵌入为感兴趣的特定单词找到一些上下文相似的单词。为此,我们基于dense的嵌入向量,在我们的词汇表中建立一个成对的距离矩阵,然后根据最短的欧氏距离找出感兴趣的每个单词的n个最近邻。

from sklearn.metrics.pairwise import euclidean_distances

# compute pairwise distance matrix

distance_matrix = euclidean_distances(weights)

print(distance_matrix.shape)

# view contextually similar words

similar_words = {search_term: [id2word[idx] for idx in distance_matrix[word2id[search_term]-1].argsort()[1:6]+1]

for search_term in ['god', 'jesus', 'noah', 'egypt', 'john', 'gospel', 'moses','famine']}

similar_words

(12424, 12424)

{'egypt': ['destroy', 'none', 'whole', 'jacob', 'sea'],

'famine': ['wickedness', 'sore', 'countries', 'cease', 'portion'],

'god': ['therefore', 'heard', 'may', 'behold', 'heaven'],

'gospel': ['church', 'fowls', 'churches', 'preached', 'doctrine'],

'jesus': ['law', 'heard', 'world', 'many', 'dead'],

'john': ['dream', 'bones', 'held', 'present', 'alive'],

'moses': ['pharaoh', 'gate', 'jews', 'departed', 'lifted'],

'noah': ['abram', 'plagues', 'hananiah', 'korah', 'sarah']}

您可以清楚地看到,其中一些在上下文上是有意义的 (god, heaven), (gospel, church) 等等,而有些可能没有意义。训练时间越长,效果往往越好。现在,我们将探讨与CBOW相比通常给出更好结果的skip-gram架构。

(2). Skip-gram模型

Skip-gram模型体系结构实现与CBOW模型相反的功能。它预测给定目标单词(中心单词)的源上下文单词(周围单词)。考虑到我们前面简单的句子,“the quick brown fox jumps over the lazy dog”。如果我们使用CBOW模型,就会得到一对(context_window, target_word),其中如果我们考虑一个大小为2的上下文窗口,就会得到([quick, fox], brown), ([the, brown], quick), ([the, dog], lazy),等等。

现在考虑到Skip-gram模型的目标是根据目标单词预测上下文,该模型通常将上下文和目标颠倒过来,并尝试根据目标单词预测每个上下文单词。因此,任务变成了给定目标单词'quick',预测上下文[the, brown],以此类推。因此,该模型是基于target_word预测context_window单词。
在这里插入图片描述
正如我们在CBOW模型中所讨论的,我们现在需要将这个Skip-gram架构建模为一个深度学习分类模型,这样我们就可以将目标单词作为输入并预测上下文单词。这变得有点复杂,因为我们在上下文中有多个单词。比如句子:“the quick brown fox jumps over the lazy dog”
我们将每个(target, context_words)对分解为多个(target, context)对,这样每个上下文只包含一个单词,从而进一步简化了这个过程。因此,我们前面的数据集被转换成成对的,比如(brown,quick),(brown,fox), (quick, the), (quick, brown)等等。但是,如何监督或训练模型,使其知道什么是上下文相关的,什么不是?

为此,我们对Skip-gram模型输入(X, Y),其中X是我们的输入,Y是我们的标签。我们使用[(target, context), 1]对作为输入正样本,其中target是我们感兴趣的单词,context是发生在目标单词附近的上下文单词,正样本标签1表示这是上下文相关的一对。我们还输入[(target, random), 0]对作为输入负样本,其中target仍然是我们感兴趣的单词,但是random只是从我们的词汇表中随机选择的一个单词,它与我们的目标单词没有上下文关系。因此,负样本标签0表示这是上下文无关的一对。我们这样做是为了让模型能够了解哪些词对与上下文相关,哪些不相关,并为语义相似的词生成类似的嵌入。

实现Skip-gram模型

现在让我们从头开始实现这个模型,以了解幕后的工作原理,并将其与CBOW模型的实现进行比较。我们将像往常一样利用包含在 norm_bible 变量中的圣经语料库来训练我们的模型。工作将集中于五个部分:

  1. 构建语料库词汇表
  2. 构建skip-gram[(target, context), relevancy]生成器
  3. 构建skip-gram模型结构
  4. 训练模型
  5. 得到词嵌入

1.构建语料库词汇表
这个和CBOW是一样的。

2.构建skip-gram[(target, context), relevancy]生成器
现在开始构建我们的skip-gram生成器了,它将像我们前面讨论的那样给我们一对单词和它们的相关性。幸运的是,keras有一个漂亮的skipgrams函数,我们不需要像在CBOW中那样手动实现这个生成器。
注意:函数skipgrams(…)出现在keras.preprocessing.sequence中。
该函数将一个单词索引序列(整数列表)转换为以下形式的单词元组:

  • (word, 在同一个窗口中的word),标签1(正样本)。

  • (word,词汇表中的random word),标签0(负样本)。

from keras.preprocessing.sequence import skipgrams

# generate skip-grams

skip_grams = [skipgrams(wid, vocabulary_size=vocab_size, window_size=10) for wid in wids]

# view sample skip-grams

pairs, labels = skip_grams[0][0], skip_grams[0][1]

for i in range(10):

print("({:s} ({:d}), {:s} ({:d})) -> {:d}".format(

id2word[pairs[i][0]], pairs[i][0],

id2word[pairs[i][1]], pairs[i][1],

labels[i]))

(james (1154), king (13)) -> 1

(king (13), james (1154)) -> 1

(james (1154), perform (1249)) -> 0

(bible (5766), dismissed (6274)) -> 0

(king (13), alter (5275)) -> 0

(james (1154), bible (5766)) -> 1

(king (13), bible (5766)) -> 1

(bible (5766), king (13)) -> 1

(king (13), compassion (1279)) -> 0

(james (1154), foreskins (4844)) -> 0

可以看到我们已经成功地生成了所需的skip-grams,还可以根据标签(0或1)清楚地看到哪些是相关的,哪些是不相关的。

3.构建skip-gram模型结构

现在,我们在利用keras来为skip-gram模型构建我们的深度学习架构。为此,我们的输入将是我们的目标单词和上下文或随机单词对。然后传递到一个嵌入层(用随机权重初始化)。一旦我们获得了目标词和上下文词的词嵌入,我们将它传递到一个合并层,在那里我们计算这两个向量的点积。然后我们将这个点积值传递给一个dense的sigmoid层,该层根据这对单词是上下文相关的还是随机的单词(Y)来预测是1还是0。我们将其与实际的关联标签(Y)匹配,通过mean_squared_error计算损失,并对每个epoch反向传播来更新嵌入层。下面的代码向我们展示了我们的模型架构。

from keras.layers import Merge

from keras.layers.core import Dense, Reshape

from keras.layers.embeddings import Embedding

from keras.models import Sequential

# build skip-gram architecture

word_model = Sequential()

word_model.add(Embedding(vocab_size, embed_size,

embeddings_initializer="glorot_uniform",

input_length=1))

word_model.add(Reshape((embed_size, )))

context_model = Sequential()

context_model.add(Embedding(vocab_size, embed_size,

embeddings_initializer="glorot_uniform",

input_length=1))

context_model.add(Reshape((embed_size,)))

model = Sequential()

model.add(Merge([word_model, context_model], mode="dot"))

model.add(Dense(1, kernel_initializer="glorot_uniform", activation="sigmoid"))

model.compile(loss="mean_squared_error", optimizer="rmsprop")

# view model summary

print(model.summary())

# visualize model structure

from IPython.display import SVG

from keras.utils.vis_utils import model_to_dot

SVG(model_to_dot(model, show_shapes=True, show_layer_names=False,

rankdir='TB').create(prog='dot', format='svg'))

在这里插入图片描述
skip-gram模型的可视化:
在这里插入图片描述
4.训练模型

在我们的完整语料库上运行模型需要相当多的时间,但比CBOW模型要少。所以我只运行了5个epochs。你可以利用以下代码,并在必要时训练更长的时间。

for epoch in range(1, 6):

loss = 0

for i, elem in enumerate(skip_grams):

pair_first_elem = np.array(list(zip(*elem[0]))[0], dtype='int32')

pair_second_elem = np.array(list(zip(*elem[0]))[1], dtype='int32')

labels = np.array(elem[1], dtype='int32')

X = [pair_first_elem, pair_second_elem]

Y = labels

if i % 10000 == 0:

print('Processed {} (skip_first, skip_second, relevance) pairs'.format(i))

loss += model.train_on_batch(X,Y)

print('Epoch:', epoch, 'Loss:', loss)

Epoch: 1 Loss: 4529.63803683

Epoch: 2 Loss: 3750.71884749

Epoch: 3 Loss: 3752.47489296

Epoch: 4 Loss: 3793.9177565

Epoch: 5 Loss: 3716.07605051

模型训练好之后,相似的单词应该有相似的基于嵌入的权重。

得到词嵌入

要为整个词汇表获取单词嵌入,可以利用下面的代码从嵌入层提取相同的单词。请注意,我们只对目标单词嵌入感兴趣,因此我们将从 word_model嵌入层提取嵌入。我们没有在位置0处进行嵌入,因为词汇表中没有一个单词的数字标识符为0,我们忽略了它。

merge_layer = model.layers[0]

word_model = merge_layer.layers[0]

word_embed_layer = word_model.layers[0]

weights = word_embed_layer.get_weights()[0][1:]

print(weights.shape)

pd.DataFrame(weights, index=id2word.values()).head()

在这里插入图片描述
可以清楚地看到,正如前面的输出所描述的,每个单词都有一个dense的大小为(1x100)的嵌入,类似于我们从CBOW模型中得到的结果。现在让我们对这些dense的嵌入向量使用欧氏距离度量来为词汇表中的每个单词生成成对的距离度量。然后,我们可以根据最短的欧氏距离找到感兴趣的每个单词的n个最近邻,这与我们在CBOW模型的嵌入中所做的类似。

from sklearn.metrics.pairwise import euclidean_distances

distance_matrix = euclidean_distances(weights)

print(distance_matrix.shape)

similar_words = {search_term: [id2word[idx] for idx in distance_matrix[word2id[search_term]-1].argsort()[1:6]+1]

for search_term in ['god', 'jesus', 'noah', 'egypt', 'john', 'gospel', 'moses','famine']}

similar_words

(12424, 12424)

{'egypt': ['pharaoh', 'mighty', 'houses', 'kept', 'possess'],

'famine': ['rivers', 'foot', 'pestilence', 'wash', 'sabbaths'],

'god': ['evil', 'iniquity', 'none', 'mighty', 'mercy'],

'gospel': ['grace', 'shame', 'believed', 'verily', 'everlasting'],

'jesus': ['christ', 'faith', 'disciples', 'dead', 'say'],

'john': ['ghost', 'knew', 'peter', 'alone', 'master'],

'moses': ['commanded', 'offerings', 'kept', 'presence', 'lamb'],

'noah': ['flood', 'shem', 'peleg', 'abram', 'chose']}

从结果中可以清楚地看到,对于感兴趣的每个单词,许多相似的单词都是有意义的,并且与我们的CBOW模型相比,我们获得了更好的结果。现在我们用t-SNE来可视化一下。

from sklearn.manifold import TSNE

words = sum([[k] + v for k, v in similar_words.items()], [])

words_ids = [word2id[w] for w in words]

word_vectors = np.array([weights[idx] for idx in words_ids])

print('Total words:', len(words), '\tWord Embedding shapes:', word_vectors.shape)

tsne = TSNE(n_components=2, random_state=0, n_iter=10000, perplexity=3)

np.set_printoptions(suppress=True)

T = tsne.fit_transform(word_vectors)

labels = words

plt.figure(figsize=(14, 8))

plt.scatter(T[:, 0], T[:, 1], c='steelblue', edgecolors='k')

for label, x, y in zip(labels, T[:, 0], T[:, 1]):

plt.annotate(label, xy=(x+1, y+1), xytext=(0, 0), textcoords='offset points')

在这里插入图片描述

2.3 分类器模块【详见后面博客】

分类器模块详见后面的博客。

2.3.1 fastText模型

2.3.2 textCNN模型

2.3.3 charCNN 模型

2.3.4 Bi-LSTM 模型

2.3.5 Bi-LSTM + Attention 模型

2.3.6 RCNN 模型

2.3.7 Adversarial LSTM 模型

2.3.8 Transformer 模型

2.3.9 ELMo 预训练模型

2.3.10 BERT 预训练模型

参考

  1. https://www.sohu.com/a/343589173_100099320
  2. https://www.cnblogs.com/jiangxinyang/p/10207273.html
发布了21 篇原创文章 · 获赞 3 · 访问量 620

猜你喜欢

转载自blog.csdn.net/qq_38293297/article/details/104850025