美文网首页文本分析
用Py做文本分析4:文本向量化

用Py做文本分析4:文本向量化

作者: 凡有言说 | 来源:发表于2020-02-07 23:35 被阅读0次

    文本向量化即将信息数值化,方便后续的建模分析。

    1.词袋模型

    词袋模型将文本直接简化为一系列词的集合,然后对此编号,形成字典,最终将文本转化为特征向量。这里以“大鱼吃小鱼也吃虾米,小于吃虾米”为例。

    • 首先对语料进行清理并完成分词
    • 然后对每个词进行编号,形成字典(顺序无关即可)
      • {“大鱼”:1, "吃":2, "小鱼":3, "也":4, "虾米":5}
    • 最后用0/1表示词是否在文本中出现,从而将文本转换成特征向量
      • 大鱼吃小鱼也吃虾米→[大鱼, 吃, 小鱼, 也, 虾米]→[1,2,1,1,1]
      • 小鱼吃虾米→[小鱼, 吃, 虾米]→[0,1,1,0,1]

    词袋模型中,词和文本的关系相当于文本是一个袋子,然后把词直接放在袋子里。该模型的缺点在于:

    • 不考虑词与词之间的顺序
    • 假设词与词相互独立(大多数情况下,词和词之间是有关联的)
    • 得到的特征向量都是离散稀疏的(维度的灾难)

    下面我们用genism包来实现词袋模型,关于genism包的安装:
    python3安装及加载gensim
    windows安装gensim

    首先是将文档进行分词,然后创建一个字典。在字典中,建立word和id的映射关系(第一个词是xx,第二个词是xx...)。最后把所有的单词取一个集合。

    from gensim.corpora import Dictionary
    
    text = [['human', 'interface', 'computer']]
    dct = Dictionary(text)
    
    #映射关系
    dct.token2id
    
    {'computer': 0, 'human': 1, 'interface': 2}
    
    #文档数
    dct.num_docs
    
    1
    
    #词条数
    dct.num_pos
    
    3
    
    #向词典中增加词条
    dct.add_documents([['cat', 'say', 'meow'], ['dog']])
    dct.token2id
    
    {'computer': 0,
     'human': 1,
     'interface': 2,
     'cat': 3,
     'meow': 4,
     'say': 5,
     'dog': 6}
    
    dct.num_docs
    
    3
    

    下面我们来看一看另一种格式:BOW稀疏向量
    比如一个字典里有100万个词,此时句子里只包含了10个词,如果生成一个长度为100万的向量,则大部分都是零,此时可以用BOW稀疏向量来解决。
    list of (token_id, token_count) 第几个词出现几次
    这样10个词的句子只需使用10个向量,大大降低了维度。

    dct.doc2bow(['this', 'is', 'cat', 'not', 'a', 'dog'])
    
    [(3, 1), (6, 1)]
    
    #返回新出现的(不在字典中)的词
    dct.doc2bow(['this', 'is', 'cat', 'not', 'a', 'dog'], return_missing = True)
    
    ([(3, 1), (6, 1)], {'a': 1, 'is': 1, 'not': 1, 'this': 1})
    
    dct.doc2idx(['this', 'is', 'cat', 'not', 'a', 'dog'])
    
    [-1, -1, 3, -1, -1, 6]
    

    有时我们希望看到完整的,此时要使用"BOW长向量"。这里有两个转换思路:

    • 从稀疏格式自行转换
    • 直接生成文档-词条矩阵
      我们采用“生成文档-词条矩阵”的做法。

    2.文档-词条矩阵

    2.1用Pandas库实现

    基本的程序框架:

    • 对原始文档分词并清理
    • 拼接为一个df
    • 汇总并转换为文档-词条矩阵格式
    • 剔除低频词
    #分词及清理停用词函数
    stoplist_path = 'D:/Files/program data/nlp/PythonData/停用词.txt'
    stoplist = list(pd.read_csv(stoplist_path, names = ['w'], sep = 'aaa',
                               encoding = 'utf-8', engine = 'python').w)
    
    import jieba
    def m_cut(intext):
        return [w for w in jieba.cut(intext)
               if w not in stoplist and len(w) >1]
    
    #数据框转换函数
    def m_appdf(chapnum):
        tmpdf = pd.DataFrame(m_cut(chapter.txt[chapnum + 1]), columns = ['word'])
        tmpdf['chap'] = chapter.index[chapnum]
        return tmpdf
    
    #全部读入并转换为数据框
    df0 = pd.DataFrame(columns = ['word', 'chap']) #初始化结果数据框
    
    for i in range(len(chapter)):
        df0 = df0.append(m_appdf(i))
    df0.tail()
    
    #输出为序列格式
    df0.groupby(['word', 'chap']).agg('size').tail(10)
    
    #直接输出为数据框
    t2d = pd.crosstab(df0.word, df0.chap)
    len(t2d)
    t2d.head()
    
    #计算各词条的总出现频数,并对低频词进行删减
    totnum = t2d.agg(func = 'sum', axis = 1)
    totnum
    t2dclean = t2d.iloc[list(totnum >= 10)]
    t2dclean.T
    

    2.2用sklearn库实现

    文本信息在向量化之前很难直接进行建模分析,而skleam库提供了一个从文本信息到数据挖掘模型间的桥梁,即CountVectorizer类。通过该类中的功能,可以较便捷地实现文档信息的向量化。

    class sklearn.feature_extraction.text.CountVectorizer(
            input = 'content' : {'filename', 'file', 'content'}
            encoding = 'utf-8'
            stop_words = None
            min_df / max_dif : float in range[0.0, 1.0] or int, default = 1/1.0
                  词频绝对值/比例的阈值,在此范围之外的将被剔除
                  小数格式说明提供的是百分比,如0.05指的是5%的阈值
    )
    
    from sklearn.feature_extraction.text import CountVectorizer
    countvec = CountVectorizer()
    
    analyze = countvec.build_analyzer()
    analyze('郭靖 和 哀牢山 三十六 剑 。')
    
    ['郭靖', '哀牢山', '三十六']
    

    CountVectorizer.fit_transform(raw_documents)

    • 对文档进行学习(处理),返回term-document matrix
    • 等价于先调用fit函数,然后再返回transform函数,但是更高效
    x = countvec.fit_transform(['郭靖 和 哀牢山 三十六 剑 。','黄蓉 和 郭靖 郭靖'])
    type(x)
    
    #将稀疏矩阵转换为标准格式矩阵
    x.todense()
    
    matrix([[1, 1, 1, 0],
            [0, 0, 2, 1]], dtype=int64)
    
    #词汇列表,获取每个列对应的词条
    countvec.get_feature_names()
    
    ['三十六', '哀牢山', '郭靖', '黄蓉']
    
    #词条字典
    countvec.vocabulary_
    
    {'郭靖': 2, '哀牢山': 1, '三十六': 0, '黄蓉': 3}
    

    接下来我们用sklearn生成射雕的章节d2m矩阵

    • 将章节文档数据框处理为空格分隔词条的文本格式
    • 使用fit_transform函数生产bow稀疏矩阵
    • 转换为标准格式的d2m矩阵
    #将文档数据框处理为空格分隔词条的文本格式
    rawchap = [' '.join(m_cut(w)) for w in chapter.txt]
    
    #生产bow稀疏矩阵
    from sklearn.feature_extraction.text import CountVectorizer
    countvec = CountVectorizer(min_df = 10) #出现10次以上的才纳入分析
    res = countvec.fit_transform(rawchap)
    res
    
    #转换为标准格式的d2m矩阵
    res.todense()
    
    #词汇列表,获取每个列对应的词条
    countvec.get_feature_names()
    

    3.N-gram

    已介绍的词袋模型有一定的缺陷,完全无法利用语序信息

    Bi-gram:进一步保留顺序信息,两个两个词条地看

    • P(我帮你) = P(我) * P(帮|我) * P(你|帮)
    • {"我帮":1, "帮你":2, "你帮":3, "帮我":4}
    • 我帮你→[1,1,0,0]
    • 你帮我→[0,0,1,1]

    N-gram:考虑更多的前后词

    • 考虑了词的顺序,信息量更充分,长度达到5后,效果明显提升
    • 词表迅速膨胀,数据出现大量的稀疏化问题

    离散表示方式面临的问题

    • 无法衡量词向量间的关系
      • 各种度量都不合适,只能靠字典补充
    • 词表维度随着语料库增长膨胀
      • N-gram词序列随语料库膨胀得更快
    • 数据稀疏问题(对设备的性能要求高)

    4.分布式表示

    分布式的思想是通过文本的上下文信息去发现目标文本的含义。具体来说:

    • 不直接考虑词与词在原文中的相对位置、距离、语法结构等,先把词看做一个单独的向量。
    • 根据一个词在上下文中的临近词的含义,应当可以归纳出词本身的含义。
    • 单个词的词向量不足以表示整个文本,能表示的仅仅是这个词本身。

    在操作的过程中

    • 事先要决定用多少维度的向量来表示目标词条
      • 维度以50维和100维比较常见
      • 向量中每个维度的取值由模型训练来决定,且不再是唯一的
    • 所有词都在同一个高维空间中构成不同的向量
      • 因此词与词之间的关系就可以用空间中的距离加以表述
    • 词向量并不是事先得到,然后再去建模,而是在训练语言模型时顺便得到的
      • 语言模型就是看一句话是不是正常人说出来的,具体表现为词条后出现的顺序和距离所对应的概率是否最大化

    一个简单模型在大数据量上的表现会比复杂模型在小数据量上的表现更好,数据中包含的信息量决定一切。

    4.1共线矩阵

    共线矩阵想表示的是一句话中词A和哪些词共同出现了。


    image.png

    窗口长度越长,则信息量越丰富,但是数据量也会越大。一般设为5-10。共线矩阵的一大特点在于其行/列数值自然就表示出各个词汇的相似度。但直接将其作为词向量会出现如下问题:

    • 向量维数随着词典大小线性增长
    • 存储整个词典的空间消耗巨大
    • 一些模型如文本分类模型会面临稀疏性问题
    • 高度的稀疏性导致模型会欠稳定

    解决的思路是构造低维稠密向量作为词的分布式表示(25-1000)

    • 提取主成分,用SVD做降维

    但用SVD也面临一些问题:

    • 计算量随语料库和词典增长膨胀太快
    • 难以向词典中新加入的词分配词向量
    • 与其他深度学习模型框架差异大

    4.2NNLM

    NNLM是一个前馈的神经网络模型,只考虑了前文出现的内容

    • 使用非对称的前向窗函数,窗的长度为n-1

    在具体计算的时候

    • 滑动窗口遍历整个语料库求和,计算量正比于语料库大小
    image.png

    整个网络由输入层、投射层、隐含层和输出层构成。

    • 输入层:N-1个前向词,one-hot方式表示,只有对应的位置为1
      比如词典里有10万个词,有10-1=9个前向词,那么这9个词,每个都是长度为10万的向量,其中只有一个位置为1,这会是一个高度稀疏的向量。
    • 投射层:会对来自输入层的超大维度向量做降维处理

    经过投射层的处理后,输入的向量就成了(N-1)*M维。

    • 输出层:预测某个单词在上下文中是否出现

    虽然该模型已经对数据做了降维处理,但是计算量依旧很大,需要使用GPU来计算。

    4.3word2vec模型

    word2vec模型的出发点是降低神经网络的计算量,但相比较NNLM也做了其他有益的改进。

    • 改为用上下文的词汇来同时预测中间词
      • 滑动时使用双向上下文窗口
    • 输入层:仍然直接使用BOW(词袋模型)方式来表示
    • 投射层:对向量直接求和,比如9个500维向量求和还是500维,而NNLM模型则是对向量进行拼接。这样比起来,word2vec显著降低了向量维度。实质上是直接去掉了投射层。
    • 隐含层:直接去除。

    本质上,word2vec只是一个线性分类器。背后支撑word2vec是大量的语料,虽然模型粗糙,但是语料多了后保留的信息就多。显然,短语料是不适合用word2vec来分析。

    为了降低运算量,word2vec使用了softmax

    • 结果向量太大,数十万维度:在几十万个词条中预测哪一个会出现
      • 考虑使用Huffman Tree来编码输出层的词典
        • 哈夫曼树,又称最优二叉树,是一类带权路径长度最短的树。
      • 将结果输出转化为树形结构,改用分层softmax
        • 在每一个分叉时,将问题转换为二分类预测
        • 在每次预测都会有概率结果提供
        • 将概率连乘,就构成了似然函数

    还是用负例采样

    • 1个正样本,V-1个负样本(没有出现过目标词的样本),显然负样本比例太高
    • 因此考虑对负样本做抽样
      • 基于词出现的频率进行加权采样,但这样会造成低频词很难被抽中,因此一般取词频的0.75次方用于加权。

    当然,word2vec仍然存在一些问题:

    • 只是利用了每个局部上下文窗口信息进行训练,没有利用包含在全局矩阵中的统计信息
    • 对多义词无法很好的表示和处理,因为使用了唯一的词向量

    word2vec就是用一个一层的神经网络(CBOW的本质)把one-hot形式的词向量映射为分布式形式的词向量,为了加快训练速度,用了Hierarchical softmax,negative sampling 等trick(MaybeSheWill)。

    参考资料:
    Python数据分析--玩转文本挖掘
    了解N-Gram模型
    语言模型:从n元模型到NNLM
    神经网络语言模型(NNLM)
    理解 NNLM
    世上最通俗的理解word2vec
    无痛理解word2vec

    相关文章

      网友评论

        本文标题:用Py做文本分析4:文本向量化

        本文链接:https://www.haomeiwen.com/subject/hkrpxhtx.html