利用Word2vec生成句向量(一)

首先为什么是Word2vec?不是Glove也不是什么Fasttext等其他的词向量?
Glove词向量的训练与部署可以看我之前的文章:https://www.jianshu.com/p/5b60e5b27cf1
很多文章也说过在很多中文文本任务中,Word2vec的表现比Glove好(刚好在我的任务中也是)
本身我用的语料比较大,训练又比较喜欢循环次数多一点,每次分词再训练时间很久,就直接用最稳的词向量了。

本文将会介绍两种比较简单效率又较高的句向量生成方式,一切为了能部署至实际生产环境,本文会着重讲代码实践的部分。想要看偏理论偏学术的可以左转去https://www.cnblogs.com/llhthinker/p/10335164.html


分词
首先是分词方面,依旧采用jieba分词并去除停用词,但是有一些小trick。
*根据新闻长度选择了正文中的前800字和标题进行拼接作为分词的输入。
*分词结果中去掉了所有的单字(长度为1),因为单字的词向量在不同语境中意义不同,容易造成影响,干脆不要。
*分词结果中去掉了所有的数字以及带百分号的数字,这个is_number函数的代码参考自别人,真的是写的大道至简。

    def stop_words_list(self):
        stopwords_dir = os.path.join(self.base_dir, 'stopwords.txt')
        stopwords = [line.strip() for line in open(stopwords_dir, encoding='UTF-8').readlines()]
        return stopwords

    def is_number(self, string):
        # 判断百分号数字
        if string.endswith('%'):
            string = string.replace('%', '')
        try:
            if string == 'NaN':
                return False
            float(string)
            return True
        except ValueError:
            return False

    def fenci(self, title, content):
        # 标题加内容前800字作为句向量的标准
        seg_list = jieba.cut(title.strip() + '。' + content[:800],
                             cut_all=False)  # seg_list是生成器generator类型
        words = ''
        for word in seg_list:
            if word not in self.stop_words_list:
                if word != '\t'  and word != '\n' and not self.is_number(word) and len(
                        word) > 1 and word != 'nbsp':
                    words += word
                    words += " "
        return words

Word2vec词向量
关于word2vec词向量的训练本文不再赘述,有几个参数在训练时要注意一下:
*size一般在50-300之间
*根据语料大小选择iter,一般在10-25之间
*根据语料大小选择min_count,默认为5,语料大的大一点,语料小的小一点

参数实在不会调就用默认值就好。接下来介绍第一种也是最简单的句向量生成方式:平均词向量


平均词向量

直接对分词结果中的每个词,取Word2vec中的对应词向量,并分别对每一位相加,最后取平均,简单粗暴。

但是要注意判断一下当前词是在Word2vec模型中,否则会访问不到而报错。

    # 根据word2vec词向量均值
    def get_sentence_matrix(self, splited_words):
        sentences_matrix = []
        index = 0
        # 平均特征矩阵
        while index < len(splited_words):
            words_matrix = []
            words = splited_words[index].split(" ")
            # 得出各个词的特征向量  并形成一个矩阵  然后计算平均值  就得到该句子的特征向量
            for word in words:
                # 当前词是在Word2vec模型中,self.model为词向量模型
                if word in self.model:
                    words_matrix.append(np.array(self.model[word]))
            # 将words_matrix求均值
            feature = averageVector(many_vectors=words_matrix,
                                    column_num=self.model.vector_size)
            sentences_matrix.append(feature)
            index += 1
        return sentences_matrix
def averageVector(many_vectors, column_num):
    """
    求多个向量的权值向量
    :param many_vector:
    :column_num:向量列数
    """
    average_vector = []
    for i in range(0, column_num, 1):
        average_vector.append(0)
    row_num = len(many_vectors)
    # 先求出各个列权重之和  后面再求平均值
    row_index = 0
    for weight_index, vector in enumerate(many_vectors):
        for i in range(0, column_num, 1):
            average_vector[i] += float(vector[i]) 
        row_index += 1
    for i in range(0, column_num, 1):
        average_vector[i] = average_vector[i] / row_num
    return average_vector

平均词向量的方法比较简单,在很多情况下效果不错,如果进行粗略的相似度计算与匹配可以满足要求。

但是有一个比较严重的问题,就是在平均词向量中,认为所有的词的重要程度是一样的,所以直接进行了相加平均。但是现实中,并不是每一个词对于当前文本的重要程度都相同,所以需要一种方法来细化每一个词对于当前文本的重要程度或贡献度。

IDF字典训练
自然就想到了TFIDF方法,TFIDF的理论较为简单,在此也不赘述。

但是如果每次分词都对所有文本算一次TFIDF,如果数据过大,会比较浪费时间。有的人直接用jieba中的TFIDF工具,虽然我没有细究jieba的IDF值是怎么算的,但还是觉得自己的相关领域的语料训练出来的效果会好一些。一个小trick就是获取大量相关领域的语料,并提前训练好IDF字典进行存储,这样对于每一个文本就只需要计算TF就可以得到每一个词的TFIDF值。

采用平滑IDF处理,并对于未出现在字典中的词语给出默认的IDF值,通过pickle进行存储,代码如下:

import math
import os
import pickle

# idf值统计方法
def train_idf(doc_list):
    idf_dic = {}
    # 总文档数
    tt_count = len(doc_list)

    # 每个词出现的文档数
    for doc in doc_list:
        for word in set(doc):
            idf_dic[word] = idf_dic.get(word, 0.0) + 1.0

    # 按公式转换为idf值,分母加1进行平滑处理
    for k, v in idf_dic.items():
        idf_dic[k] = math.log(tt_count / (1.0 + v))

    # 对于没有在字典中的词,默认其仅在一个文档出现,得到默认idf值
    print("tt_count" + str(tt_count))
    default_idf = math.log(tt_count / (1.0))
    return idf_dic, default_idf

def load_fenci_data():
    # 调用上面方式对数据集进行处理,处理后的每条数据仅保留非干扰词

    doc_list = []
    for line in open(os.path.join('/home/brx/Documents/Projects/Similarity/qa_similarity/com', 'history_fenci.txt'), encoding='UTF-8'): # 分词结果文件
        doc_list.append(line.strip().split())
    return doc_list

def save_obj(obj, name ):
    with open('./'+ name + '.pkl', 'wb') as f:
        pickle.dump(obj, f, pickle.HIGHEST_PROTOCOL)

def load_obj(name ):
    with open('./' + name + '.pkl', 'rb') as f:
        return pickle.load(f)

doc_list = load_fenci_data()
idf_dic, default_idf = train_idf(doc_list)
save_obj(idf_dic, 'idf_dic')
# idf_dic = load_obj('idf_dic')
print(default_idf)

这样即可根据语料获得IDF字典idf_dic.pkl和默认IDF值,字典可以直接通过load_obj方法进行读取。


TFIDF加权平均词向量

代码如下,和之前的平均词向量相比,多了idf_dic(从pickle文件load而来)vector_weight(从idf_dic中读取而来)。但是也要注意判断当前词是否在idf_dic中,如果平均词向量的代码看懂了,这个代码也非常好理解。

    # 根据word2vec和tfidf获取句子矩阵
    def get_sentence_matrix(self, splited_words):
        sentences_matrix = []
        index = 0
        # 平均特征矩阵
        while index < len(splited_words):
            words_matrix = []
            word_weight = []
            words = splited_words[index].split(" ")
            # 得出各个词的特征向量  并形成一个矩阵  然后计算平均值  就得到该句子的特征向量
            for word in words:
                if word in self.model:
                    words_matrix.append(np.array(self.model[word]))
                    if word in self.idf_dic:
                        word_weight.append(self.idf_dic[word])
                    else:
                        word_weight.append(self.default_idf)

            # 将words_matrix求均值
            feature = averageVector(many_vectors=words_matrix, vector_weight=word_weight,
                                    column_num=self.model.vector_size)
            sentences_matrix.append(feature)
            index += 1
        return sentences_matrix

def averageVector(many_vectors, vector_weight, column_num):
    """
    求多个向量的权值向量
    :param many_vector:
    :column_num:向量列数
    :vector_weight:IDF权重
    """
    average_vector = []
    # print(vector_weight)
    for i in range(0, column_num, 1):
        average_vector.append(0)
    row_num = len(many_vectors)
    # 先求出各个列权重之和  后面再求平均值
    row_index = 0
    for weight_index, vector in enumerate(many_vectors):
        for i in range(0, column_num, 1):
            average_vector[i] += float(vector[i]) * vector_weight[weight_index]
        row_index += 1
    for i in range(0, column_num, 1):
        average_vector[i] = average_vector[i] / row_num
    return average_vector

稍有基础的小伙伴应该都可以根据以上代码实现自己的功能,计算欧式或余弦距离,进行相似度匹配和聚类分类等任务。下一篇文章将会介绍一个号称更为有效的句向量生成方法,同样可以基于Word2vec实现。
\color{red}{(纯原创,转载请注明来源)}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容