基于jieba、gensim.word2vec、Logistic

作者: 潇洒坤 | 来源:发表于2018-09-01 11:06 被阅读292次

    学习资源来源:容大教育,致以诚挚的谢意。
    重新编辑:潇洒坤

    jieba中文叫做结巴,是一款中文分词工具,官方文档链接:https://github.com/fxsjy/jieba
    gensim.word2vec中文叫做词向量模型,是是用来文章内容向量化的工具,官方文档链接:https://radimrehurek.com/gensim/models/word2vec.html
    LogisticRegression中文叫做逻辑回归模型,是一种基础、常用的分类方法。

    建议读者安装anaconda,这个集成开发环境自带了很多包。
    到2018年8月30日仍为最新版本的anaconda下载链接: https://pan.baidu.com/s/1pbzVbr1ZJ-iQqJzy1wKs0A 密码: g6ex
    官网下载地址:https://repo.anaconda.com/archive/Anaconda3-5.2.0-Windows-x86_64.exe
    下面代码的开发环境为jupyter notebook,使用在jupyter notebook中的截图表示运行结果。

    0.打开jupyter

    在桌面新建文件夹命名为基于word2vec的文档分类,如下图所示:

    image.png
    打开基于word2vec的文档分类文件夹,在按住Shift键的情况下,点击鼠标右键,出现如下图所示。
    选择在此处打开PowerShell窗口,之后会在此路径下打开PowerShell。
    image.png
    在PowerShell中输入命令并运行:jupyter notebook
    image.png
    PowerShell运行命令后,会自动打开网页,点击如下图所示网页中的按钮:
    image.png
    代码文件重命名为word2vecTest,重命名按钮位置如下图所示:
    image.png

    1.数据准备

    训练集共有24000条样本,12个分类,每个分类2000条样本。
    测试集共有12000条样本,12个分类,每个分类1000条样本。
    数据集下载链接: https://pan.baidu.com/s/1PY3u-WtfBdZQ8FsKgWo_KA 密码: hq5v
    下载完成后,将压缩文件包放到基于word2vec的文档分类文件夹中,并将其解压到当前文件夹,如下图所示:

    image.png
    加载训练集到变量train_df中,并打印训练集前5行,代码如下。
    read_csv方法中有3个参数,第1个参数是加载文本文件的路径,第2个关键字参数sep是分隔符,第3个关键字参数header是文本文件的第1行是否为字段名。
    import pandas as pd
    
    train_df = pd.read_csv('sohu_train.txt', sep='\t', header=None)
    train_df.head()
    

    上面一段代码的运行结果如下图所示:


    image.png

    查看训练集每个分类的名字以及样本数量,代码如下:

    for name, group in train_df.groupby(0):
        print(name,len(group))
    

    上面一段代码的运行结果如下图所示:


    image.png

    加载测试集并查看每个分类的名字以及样本数量,代码如下:

    test_df = pd.read_csv('sohu_test.txt', sep='\t', header=None)
    for name, group in test_df.groupby(0):
        print(name, len(group))
    

    上面一段代码的运行结果如下图所示:


    image.png

    2.分词

    需要安装jieba库,cmd中安装命令:pip install jieba
    对训练集的24000条样本循环遍历,使用jieba库的cut方法获得分词列表赋值给变量cutWords。
    判断分词是否为停顿词,如果不为停顿词,则添加进变量cutWords中。
    代码如下:

    import jieba
    import time
    
    train_df.columns = ['分类', '文章']
    stopword_list = [k.strip() for k in open('stopwords.txt', encoding='utf8').readlines() if k.strip() != '']
    cutWords_list = []
    i = 0
    startTime = time.time()
    for article in train_df['文章']:
        cutWords = [k for k in jieba.cut(article) if k not in stopword_list]
        i += 1
        if i % 1000 == 0:
            print('前%d篇文章分词共花费%.2f秒' %(i, time.time()-startTime))
        cutWords_list.append(cutWords)
    

    上面一段代码的运行结果如下:

    前1000篇文章分词共花费67.62秒
    前2000篇文章分词共花费133.32秒
    前3000篇文章分词共花费272.28秒
    前4000篇文章分词共花费405.01秒
    前5000篇文章分词共花费529.79秒
    前6000篇文章分词共花费660.60秒
    前7000篇文章分词共花费696.51秒
    前8000篇文章分词共花费732.88秒
    前9000篇文章分词共花费788.51秒
    前10000篇文章分词共花费841.61秒
    前11000篇文章分词共花费903.35秒
    前12000篇文章分词共花费970.47秒
    前13000篇文章分词共花费1010.61秒
    前14000篇文章分词共花费1048.76秒
    前15000篇文章分词共花费1100.81秒
    前16000篇文章分词共花费1154.80秒
    前17000篇文章分词共花费1207.07秒
    前18000篇文章分词共花费1256.73秒
    前19000篇文章分词共花费1374.76秒
    前20000篇文章分词共花费1493.85秒
    前21000篇文章分词共花费1523.02秒
    前22000篇文章分词共花费1552.69秒
    前23000篇文章分词共花费1598.88秒
    前24000篇文章分词共花费1644.56秒

    从上面的运行结果可以看出,对24000篇文章进行分词共使用1644秒,即27分24秒。
    时间充裕的读者可以自己运行试试,将分词结果保存为本地文件cutWords_list.txt,代码如下:

    with open('cutWords_list.txt', 'w') as file: 
        for cutWords in cutWords_list:
            file.write(' '.join(cutWords) + '\n')
    

    上面一段代码大概5秒左右运行完成,本文作者提供已经分词完成的文本文件。
    读者节省时间可以下载,链接: https://pan.baidu.com/s/1vCBeHNR6DEGSQQDvA7yQOw 密码: j49q
    下载文件是单个文本文件压缩的zip文件,文件大小为50M。
    压缩的zip文件解压后的文本文件大小为118M。
    载入分词文件的代码如下:

    with open('cutWords_list.txt') as file:
        cutWords_list = [k.split() for k in file.readlines()]
    

    3.word2vec模型

    完成此步骤需要先安装gensim库,安装命令:pip install gensim
    调用gensim.models.word2vec库中的LineSentence方法实例化行模型对象,代码如下:

    from gensim.models import Word2Vec
    
    word2vec_model = Word2Vec(cutWords_list, size=100, iter=10, min_count=20)
    

    调用模型对象的方法时,一直提示警告信息,避免出现烦人的警告信息,代码如下:

    import warnings
    
    warnings.filterwarnings('ignore')
    

    调用Word2Vec模型对象的wv.most_similar方法查看与摄影含义最相近的词。
    wv.most_similar方法有2个参数,第1个参数是要搜索的词,第2个关键字参数topn数据类型为正整数,是指需要列出多少个最相关的词汇,默认为10,即列出10个最相关的词汇。
    wv.most_similar方法返回值的数据类型为列表,列表中的每个元素的数据类型为元组,元组有2个元素,第1个元素为相关词汇,第2个元素为相关程度,数据类型为浮点型。

    word2vec_model.wv.most_similar('摄影')
    

    上面一段代码的运行结果,如下图所示:


    image.png

    wv.most_similar方法使用positive和negative这2个关键字参数的简单示例。
    查看女人+先生-男人的结果,代码如下:

    word2vec_model.most_similar(positive=['女人', '先生'], negative=['男人'], topn=1)
    

    上面一段代码的运行结果,如下图所示:

    image.png
    查看两个词的相关性,如下图所示:
    image.png
    保存Word2Vec模型为word2vec_model.w2v文件,代码如下:
    word2vec_model.save('word2vec_model.w2v')
    

    4.特征工程

    对于每一篇文章,获取文章的每一个分词在word2vec模型的相关性向量。
    然后把一篇文章的所有分词在word2vec模型中的相关性向量求和取平均数,即此篇文章在word2vec模型中的相关性向量。
    实例化Word2Vec对象时,关键字参数size定义为100,则相关性矩阵都为100维。
    定义getVector函数获取每个文章的词向量,传入2个参数,第1个参数是文章分词的结果,第2个参数是word2vec模型对象。
    变量vector_list是通过列表推导式得出单篇文章所有分词的词向量,通过np.array方法转成ndarray对象再对每一列求平均值。
    代码经过作者实验,使用numpy库计算速度最快,读者如果发现运行更快的代码写法可以留言
    每当完成1000篇文章词向量转换的时候,打印花费时间
    最终将24000篇文章的词向量赋值给变量X,即X为特征矩阵。

    对比文章转换为相关性向量的4种方法花费时间。
    为了节省时间,只对比前5000篇文章转换为相关性向量的花费时间。

    4.1 第1种方法,用for循环常规计算

    代码如下:

    import numpy as np 
    import time 
    
    def getVector_v1(cutWords, word2vec_model):
        count = 0
        article_vector = np.zeros(word2vec_model.layer1_size)
        for cutWord in cutWords:
            if cutWord in word2vec_model:
                article_vector += word2vec_model[cutWord]
                count += 1
        return article_vector / count
    
    startTime = time.time()
    vector_list = []
    i = 0
    for cutWords in cutWords_list[:5000]:
        i += 1
        if i % 1000 ==0:
            print('前%d篇文章形成词向量花费%.2f秒' %(i, time.time()-startTime))
        vector_list.append(getVector_v1(cutWords, word2vec_model))
    X = np.array(vector_list)
    

    上面一段代码的运行结果如下:

    前1000篇文章形成词向量花费21.05秒
    前2000篇文章形成词向量花费41.51秒
    前3000篇文章形成词向量花费92.37秒
    前4000篇文章形成词向量花费140.78秒
    前5000篇文章形成词向量花费181.00秒

    4.2 第2种方法,用pandas的mean方法计算

    代码如下:

    import time 
    import pandas as pd 
    
    def getVector_v2(cutWords, word2vec_model):
        vector_list = [word2vec_model[k] for k in cutWords if k in word2vec_model]
        vector_df = pd.DataFrame(vector_list)
        cutWord_vector = vector_df.mean(axis=0).values
        return cutWord_vector
    
    startTime = time.time()
    vector_list = []
    i = 0
    for cutWords in cutWords_list[:5000]:
        i += 1
        if i % 1000 ==0:
            print('前%d篇文章形成词向量花费%.2f秒' %(i, time.time()-startTime))
        vector_list.append(getVector_v2(cutWords, word2vec_model))
    X = np.array(vector_list)
    

    上面一段代码的运行结果如下:

    前1000篇文章形成词向量花费42.44秒
    前2000篇文章形成词向量花费83.17秒
    前3000篇文章形成词向量花费180.70秒
    前4000篇文章形成词向量花费272.86秒
    前5000篇文章形成词向量花费349.57秒

    4.3 第3种方法,用numpy的mean方法计算

    代码如下:

    import time
    import numpy as np
    
    def getVector_v3(cutWords, word2vec_model):
        vector_list = [word2vec_model[k] for k in cutWords if k in word2vec_model]
        cutWord_vector = np.array(vector_list).mean(axis=0)
        return cutWord_vector
    
    startTime = time.time()
    vector_list = []
    i = 0
    for cutWords in cutWords_list[:5000]:
        i += 1
        if i % 1000 ==0:
            print('前%d篇文章形成词向量花费%.2f秒' %(i, time.time()-startTime))
        vector_list.append(getVector_v3(cutWords, word2vec_model))
    X = np.array(vector_list)
    

    上面一段代码的运行时间如下:

    前1000篇文章形成词向量花费15.50秒
    前2000篇文章形成词向量花费30.63秒
    前3000篇文章形成词向量花费68.17秒
    前4000篇文章形成词向量花费103.79秒
    前5000篇文章形成词向量花费133.46秒

    4.4 第4种方法,用numpy的add、divide方法计算

    import time
    import numpy as np
    
    def getVector_v4(cutWords, word2vec_model):
        i = 0
        index2word_set = set(word2vec_model.wv.index2word)
        article_vector = np.zeros((word2vec_model.layer1_size))
        for cutWord in cutWords:
            if cutWord in index2word_set:
                article_vector = np.add(article_vector, word2vec_model.wv[cutWord])
                i += 1
        cutWord_vector = np.divide(article_vector, i)
        return cutWord_vector
    
    startTime = time.time()
    vector_list = []
    i = 0
    for cutWords in cutWords_list[:5000]:
        i += 1
        if i % 1000 ==0:
            print('前%d篇文章形成词向量花费%.2f秒' %(i, time.time()-startTime))
        vector_list.append(getVector_v4(cutWords, word2vec_model))
    X = np.array(vector_list)
    

    上面一段代码的运行时间如下:

    前1000篇文章形成词向量花费13.47秒
    前2000篇文章形成词向量花费26.45秒
    前3000篇文章形成词向量花费50.70秒
    前4000篇文章形成词向量花费74.40秒
    前5000篇文章形成词向量花费95.19秒

    因为形成特征矩阵的花费时间较长,为了避免以后重复花费时间,把特征矩阵保存为文件。
    使用ndarray对象的dump方法,需要1个参数,数据类型为字符串,为保存文件的文件名,代码如下:

    X.dump('articles_vector.txt')
    

    文章向量文件,即特征矩阵文件下载链接: https://pan.baidu.com/s/1tYwZHIdu4GfU3xoMbjYYPQ 密码: y66i
    加载此文件中的内容赋值给变量X,代码如下:

    X = np.load('articles_vector.txt')
    

    5.模型训练、模型评估

    5.1 标签编码

    调用sklearn.preprocessing库的LabelEncoder方法对文章分类标签编码

    from sklearn.preprocessing import LabelEncoder
    import pandas as pd
    
    train_df = pd.read_csv('sohu_train.txt', sep='\t', header=None)
    train_df.columns = ['分类', '文章']
    labelEncoder = LabelEncoder()
    y = labelEncoder.fit_transform(train_df['分类'])
    

    5.2 逻辑回归模型

    调用sklearn.linear_model库的LogisticRegression方法实例化模型对象。
    调用sklearn.model_selection库的train_test_split方法划分训练集和测试集。

    from sklearn.linear_model import LogisticRegression
    from sklearn.model_selection import train_test_split
    
    train_X, test_X, train_y, test_y = train_test_split(X, y, test_size=0.2)
    logistic_model = LogisticRegression()
    logistic_model.fit(train_X, train_y)
    logistic_model.score(test_X, test_y)
    

    上面一段代码的运行结果如下:

    0.7825

    5.3 保存模型

    调用sklearn.externals库中的joblib方法保存模型为logistic.model文件。
    模型持久化官方文档示例:http://sklearn.apachecn.org/cn/0.19.0/modules/model_persistence.html

    from sklearn.externals import joblib
    
    joblib.dump(logistic_model, 'logistic.model')
    

    模型文件logistic.model下载链接: https://pan.baidu.com/s/1N6oowCjjYPyQ7Z0fg9O4dQ 密码: r2xx
    加载模型代码如下:

    from sklearn.externals import joblib
    
    logistic_model = joblib.load('logistic.model')
    

    5.4 交叉验证

    交叉验证的结果更具有说服力。
    调用sklearn.model_selection库的ShuffleSplit方法实例化交叉验证对象。
    调用sklearn.model_selection库的cross_val_score方法获得交叉验证每一次的得分。

    from sklearn.linear_model import LogisticRegression
    from sklearn.model_selection import ShuffleSplit
    from sklearn.model_selection import cross_val_score
    
    cv_split = ShuffleSplit(n_splits=5, train_size=0.7, test_size=0.2)
    logistic_model = LogisticRegression()
    score_ndarray = cross_val_score(logistic_model, X, y, cv=cv_split)
    print(score_ndarray)
    print(score_ndarray.mean())
    

    上面一段代码的运行结果如下:

    [0.78604167 0.78895833 0.78 0.78375 0.785625 ]
    0.784875

    6.模型测试

    调用sklearn.externals库的joblib对象的load方法加载模型赋值给变量logistic_model
    调用pandas库read_csv方法读取测试集数据。
    调用DataFrame对象的groupby方法对每个分类分组,从而每种文章类别的分类准确性。
    调用自定义的getVector方法将文章转换为相关性向量。
    自定义getVectorMatrix方法获得测试集的特征矩阵。
    调用StandardScaler对象的transform方法将预测标签做标签编码,从而获得预测目标值。

    import pandas as pd
    import numpy as np
    from sklearn.externals import joblib
    import jieba 
    
    def getVectorMatrix(article_series):
        return np.array([getVector_v3(jieba.cut(k), word2vec_model) for k in article_series])
        
    logistic_model = joblib.load('logistic.model')
    test_df = pd.read_csv('sohu_test.txt', sep='\t', header=None)
    test_df.columns = ['分类', '文章']
    for name, group in test_df.groupby('分类'):
        featureMatrix = getVectorMatrix(group['文章'])
        target = labelEncoder.transform(group['分类'])
        print(name, logistic_model.score(featureMatrix, target))
    

    上面一段代码的运行结果如下:

    体育 0.963
    健康 0.82
    女人 0.775
    娱乐 0.788
    房地产 0.896
    教育 0.881
    文化 0.576
    新闻 0.581
    旅游 0.811
    汽车 0.93
    科技 0.793
    财经 0.697

    7.结论

    word2vec模型应用的第1个小型项目,训练集数据共有24000条,测试集数据共有12000条。
    经过交叉验证,模型平均得分为0.78左右。
    测试集的验证效果中,体育、教育、健康、文化、旅游、汽车、娱乐这7个分类得分较高,即容易被正确分类。
    女人、娱乐、新闻、科技、财经这5个分类得分较低,即难以被正确分类。
    想要学习如何提高文档分类的准确率,请查看我的另外一篇文章《基于jieba、TfidfVectorizer、LogisticRegression的文档分类》。
    文章链接:https://www.jianshu.com/p/ec053e920a3b

    相关文章

      网友评论

      • 62e7d228de51:你好,最后一段代码里,函数getVectorMatrix的定义中,getVector_v3(k, word2vec_model)是不是应该修改为getVector_v3(jieba.cut(k), word2vec_model)?前面说过getVector的第一个参数是文章分词的结果,所以它应该是可迭代的对象,而不是字符串
        潇洒坤:谢谢,我已经修改了。热心的读者啊:smiley:
      • 62e7d228de51:谢谢楼主分享,帮助很大~
        潇洒坤:@单秋果 https://www.jianshu.com/p/179471b709ae
        62e7d228de51:@潇洒坤 嗯嗯,代码能成功运行,可以给我CNN的链接吗,我想学习一下,谢谢!
        潇洒坤:@单秋果 代码都能够成功运行吗?我最新的博客使用CNN模型解决文本分类问题,准确率有96%,应该对你有用。

      本文标题:基于jieba、gensim.word2vec、Logistic

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