• sknearl-7处理文本数据


    本章代码大部分没跑,只供学习

    第四节特征工程里提到,有连续特征和离散特征,对于文本数据,文本特征可以看作第三种特征

    1 用字符串表示的数据类型

    2 例子 电影评论情感分析

    给定一个影评(输入),输出影评是正面还是负面

    sklearn无法处理文本数据,需要将文本数据转换为数值表示,然后再用机器学习算法处理

    3 将文本数据表示为词袋

    词袋 即统计每个单词出现的频率

    词袋构造步骤

    1 划分原始字符串:将原始字符串用空格或标点负号分隔,获取单词拼写

    2 构建词表,可进行编号

    3 统计单词频率

    3.1 词袋应用于玩具数据集

    通过sklearn.feature_extraction.text.CountVectorizer构造词袋。构造完了可访问.vocabulary_访问词表,然后调用transform获取词袋,看下词表和词袋

    1. def test_workds_bag(self):
    2. bards_words = ['the fool doth think he is wise,', 'but the wise man knows himself to be a fool']
    3. vect = CountVectorizer().fit(bards_words)
    4. print(f'vocabulary type: {type(vect.vocabulary_)}, vocabulary: {vect.vocabulary_}')
    5. bag_words_trans = vect.transform(bards_words)
    6. print(f'words bag transform type:{type(bag_words_trans)},\n'
    7. f'words bag transfrom shape:{bag_words_trans.shape},\n'
    8. f'words bag transform repr: {repr(bag_words_trans)}\n'
    9. f'words bag transform:\n {bag_words_trans.toarray()}')

    注意,vocabulary只是单词排序,字典的值不是单词出现次数,只是在句子里的下标,注意区分词表和词袋的概念。

    词袋用稀疏矩阵表示(sparse matrix)

    CountVectorier默认使用的正则是"\b\w\w+\b",含义是提起至少两个字符以上的字母数字且被单词边界分开。所以不会提取长度为1的作为单词,所以上述句子提取的词袋也没提取到a

    3.2 词袋应用于电影数据集

    先构造词袋,然后用LogisticRegression交叉验证,然后网格搜索最优的C

    1. def test_movies_bag(self):
    2. movie_train, movie_test = load_files('train_path'), load_files('test_path')
    3. text_tr, ytr, text_te, yte = movie_train.data, movie_train.target, movie_test.data, movie_test.target
    4. text_tr, test_te = [doc.replace(b"
      "
      , b"") for doc in text_tr], [doc.replace(b"
      "
      , b"") for doc in text_te]
    5. vect = CountVectorizer().fit(text_tr)
    6. xtr = vect.transform(text_tr)
    7. print(f'vect transform features :{vect.get_feature_names()}') # shape: (25000, n) feature num is n, sort by alphabet
    8. # cross validation
    9. print(f'mean logistic regression cross score: {cross_val_score(LogisticRegression(), xtr, ytr, cv=5)}')
    10. # grid search
    11. params_grid = {'C': [0.001, 0.01, 0.1, 1, 10]}
    12. grid = GridSearchCV(LogisticRegression(), params_grid, cv=5).fit(xtr, ytr)
    13. print(f'grid best score: {grid.best_score_:.3f}')
    14. print(f'grid best params: {grid.best_params_}') # C:0.1
    15. print(f'grid test score: {grid.score(text_te, yte)}')

    词袋数据存在稀疏矩阵,对于高维稀疏矩阵,线性模型的LogisticRegression性能最好,约为88%

    其实仅靠词袋还有很多问题,动词有进行时,过去时,单三等形式,还可能有很多写错的字符,这些需要考虑影响程度。可考虑在词袋基础做单词改进(不识别大小写,即大小写不同的单词会被识别为同一单词)

    考虑CountVectorizer提取单词原理,使用"\b\w\w+\b",对于doesn't,bilibili.txt这类单词,会拆开识别

    方案1 仅考虑在两个以上的文档中出现的相同单词

    通过CountVectorizer的min_df参数实现(仅出现一次的单词可能没什么用,先这么试试) 

    1. def get_movie_data_test(self):
    2. movie_train, movie_test = load_files('train_path'), load_files('test_path')
    3. text_tr, ytr, text_te, yte = movie_train.data, movie_train.target, movie_test.data, movie_test.target
    4. text_tr, test_te = [doc.replace(b"
      "
      , b"") for doc in text_tr], [doc.replace(b"
      "
      , b"") for doc in
    5. text_te]
    6. return text_tr, test_te, ytr, yte
    7. def test_movies_bag_5_appear(self):
    8. xtr, xte, ytr, yte = self.get_movie_data_test()
    9. vect = CountVectorizer(min_df=5).fit(xtr)
    10. xtr_trans = vect.transform(xtr)
    11. grid = GridSearchCV(LogisticRegression(), {'C': [0.001, 0.01, 0.1, 1, 10]}, cv=5).fit(xtr, ytr)
    12. print(f'logistic regression best score: {grid.score(xte, yte)}')

    结论 精度大概为89%,发现处理单词出现频率后,精度没明显的提升,但减少了约三分之二的特征,可提升处理速度

    4 停用词

    删除没有意义的词语还有一种方法:删除出现频率过高的词语。有两种方法:1使用特定的语言停用词词表(sklearn.feature_extraction.text.ENGLISH_STOP_WORDS提供了停用词词表) 2指定特定频率,舍弃频率在该频率以上的词语。比如说above, into, well, anyone等词

    可以从数据集里删除停用词。虽然减少不了多少特征,但可能会提升性能,因为停用词出现频率可能高一些

    5 tf-idf放缩数据

    tf-idf概念 也叫词频-逆向文档频率(term frequency - inverse document frequency, tf-idf)。给词语赋予权重,对于语料库中经常出现的词语,不会赋予很高权重;在某个文档出现频率次数较高的词被识别为术语,赋予较高的权重。最后通过一个量化指标 tf-idf分数来反映单词权重。sklearn里有两个类实现了tf-idf:TfidfTransformer和TfidfVectorizer,前者接受稀疏矩阵并转换,后者接受文本数据完成词袋特征提取和tfidf变换,计算公式如下

    N是文档总数量,Nw是出现某个单词的文档数量,tf是单词在查询文档中出现的次数

    可以看下tfidf得分最高和最低的单词

    tfidf得分较小时,说明单词要么出现频率很低,要么就是在很多文档里都有使用

    tfidf得分较大时,说明词汇较高频率出现在某些文档中。但这类词语有的对影评情感分类并没有显著的作用,比如电影标题

    看下idf得分最低的单词(出现频率最高,只按频率排序,idf和tfidf不一样

    这些单词主要是停用词

    1. def test_tfidf_show_features(self):
    2. xtr, xte, ytr, yte = self.get_movie_data_test()
    3. pipe = make_pipeline(TfidfVectorizer(min_df=5), LogisticRegression())
    4. grid = GridSearchCV(pipe, {'logisticregression__C': [0.001, 0.01, 0.1, 1, 10]}, cv=5).fit(xtr, ytr)
    5. print(f'best cross score: {grid.best_score_}')
    6. # show tfidf words
    7. vectorizer = grid.best_estimator_.named_steps["tfidfvectorizer"]
    8. xtr_trans = vectorizer.transform(xtr)
    9. max_val = xtr_trans.max(axis=0).toarray().ravel()
    10. sorted_by_tfidf = max_val.argsort()
    11. feature_name = np.array(vectorizer.get_feature_names())
    12. # show tf-idf score
    13. print(f'tfidf score lowest 20 ea: {feature_name[:20]}')
    14. print(f'tfidf score highest 20 ea: {feature_name[-20:]}')
    15. # show idf score
    16. print(f' idf score lowest 20 ea: {vectorizer.idf_[:20]}')

    6 研究模型系数

    看下训练的logistic模型系数的最大最小值

    1. mglearn.tools.visualize_coefficients(grid.best_estimator_.named_steps["logisticregression"].coef_,
    2. feature_names=feature_names, n_top_features=40)

    看x轴发现最小得分的单词大多是负面情绪的单词,比如worst,waste等,得分高的单词大部分也是正面单词:great, excellent等

    7 多个单词词袋

    词袋缺点 舍弃了单词顺序

    词袋解决方案 有一种词袋考虑上下文中单词的计数,即某个单词相邻某几个单词的计数

    二元分词 两个词例,以此类推三元等,词例范围可通过vector类的ngram_range参数传入来指定词例个数。ngram_range是一个元组,包括了词例的最小长度和最大长度。CountVectorizer默认是(1,1)的ngram_range

    1. def test_word_bag_ngram(self):
    2. bards_words = ['the fool doth think he is wise,', 'but the wise man knows himself to be a fool']
    3. vector = CountVectorizer(ngram_range=(1, 1)).fit(bards_words)
    4. print(f'length of feature: {len(vector.vocabulary_)},\nvector feature names: {vector.get_feature_names_out()}')

    仅查看二元分词

    1. def test_word_bag_ngram(self):
    2. bards_words = ['the fool doth think he is wise,', 'but the wise man knows himself to be a fool']
    3. vector = CountVectorizer(ngram_range=(1, 1)).fit(bards_words)
    4. print(f'length of feature: {len(vector.vocabulary_)},\nvector feature names: {vector.get_feature_names_out()}')
    5. # show 2 dimension words
    6. vector = CountVectorizer(ngram_range=(2, 2)).fit(bards_words)
    7. print(f'feature len: {len(vector.vocabulary_)},\n2 di vector feature names: {vector.get_feature_names_out()}')

    优缺点 多元分词可能导致过拟合,也会增加计算量,n元分词计算量是一元分词的n倍

    可以同时使用一元,二元,三元分词

    1. vector = CountVectorizer(ngram_range=(1, 3)).fit(bards_words)
    2. print(f'feature len: {len(vector.vocabulary_)},\n vector feature names: {vector.get_feature_names_out()}')

    7.1 对影评数据应用3元词袋

    对影评数据应用1-3元词袋,然后网格搜索出最佳参数,然后热图可视化(没跑,用的教材的图)

    二元的精度提升了约一个百分点,发现一元到二元精度提升很多,二元到三元没提升多少,表明三元可能没太大作用,

    看下特征系数,绘制bar图

    发现三元特征的特征系数普遍较低,也验证了三元分词没起太多作用

    8 高级分词、词干提取、词形还原

    目的 很多单词有不同分词形式,将分词形式作为单独特征可能会导致过拟合,将词干提取或合并可减少次问题导致的误差

    词干提取(stemming) 删除单词不同分词形式的通用分词后缀,然后合并词干

    词形还原(lemmatization) 将单词不同分词形式按照已有分词字典进行合并还原

    标准化 词干提取和词形还原都叫标准化,即将一个单词还原成标准形式

    先看下词干提取

    1. def test_word_stem(self):
    2. en_nlp = spacy.load('en_core_web_sm')
    3. stemmer = nltk.stem.PorterStemmer()
    4. def compare_normalization(doc):
    5. doc_spacy = en_nlp(doc)
    6. print(f'show word split result: {[token.lemma_ for token in doc_spacy]}')
    7. print(f'show word stem found result: {[stemmer.stem(token.norm_.lower()) for token in doc_spacy]}')
    8. test_text = "our meeting today was worse than yesterday, I'm scared of meeting the clients tomorrow"
    9. compare_normalization(test_text)

    was词干提取后变成wa,因为词干提取原理是删分词后缀

    worse变成wors,meeting变成meet

    sklearn里没支持词干提取和词形还原,单CountVectorizer可以使用tokenizer指定分词器将文档转换为词例列表

    看下词形还原

    1. def test_lemmatization(self):
    2. regexp = re.compile('(?u)\\b\\w\\w+\\b')
    3. en_nlp = spacy.load('en_core_web_sm')
    4. old_tokenizer = en_nlp.tokenizer
    5. en_nlp.tokenizer = lambda string: old_tokenizer.tokens_from_list(regexp.findall(string))
    6. def custom_tokenizer(doc):
    7. doc_spacy = en_nlp(doc, entity=False, parse=False)
    8. return [token.lemma_ for token in doc_spacy]
    9. lemma_vect = CountVectorizer(tokenizer=custom_tokenizer, min_df=5)
    10. xtr, xte, ytr, yte = self.get_movie_data_test()
    11. xtr_lemma = lemma_vect.fit_transform(xtr)
    12. print(f'words lemmatization shape: {xtr_lemma.shape}')

    词形还原可以合并特征,可以看作正则化,因为选的特征变少了。数据集比较小时,词形还原可以有较大的性能提升。

    9 主题建模与文档归类

    另一种常用的文本建模方法是主题建模,比如每个新闻都涉及一些主题,比如财经,体育,科技等。给一个新闻预测是哪个主题,是主题建模要考虑的问题。一般主题建模指隐含狄利克雷分布(Latent Dirichlet Allocation,LDA)的分解方法

    机器学习学习到的主题,和我们日常提到的主题可能不太一样。机器学习可能按词频学到词频较高的词语作为主题,类似于PCA的主成分,没有什么让人直观理解的含义,只是个计算量。

    预处理 应用LDA前应删掉常见的频率很高的非主题词,可以在CountVctorizer构造传入参数min_df=.3,表示删除至少在30%文档出现的词语

    任务 现在设置学习目标是10个主题。主题类似于NMF中的分量,没有内在的顺序,但改变主题数量会改变所有主题(其实LDA和NMF有一定相似性,也可试着用NMF提取主题)。此处用batch学习方法,比online方法稍慢,但结果可能会更好,然后增大max_iter,可以得到更好的模型

    9.1 模型1

    1. def test_topic_modeling(self):
    2. vect = CountVectorizer(max_features=10000, max_df=.15)
    3. x = vect.fit_transform('test train')
    4. lda = LatentDirichletAllocation(n_topics=10, learning_method='batch', max_iter=25, random_state=0)
    5. topics = lda.fit_transform(x)
    6. print(f'lda component shape: {lda.components_.shape}') # (10, 10000)

    查看每个主题最重要的词语

    从词汇重要程度看,topic1可能和战争有关,主题2可能和喜剧有关,主题3可能和电视连续剧有关

    9.2 模型2

    1. lda100 = LatentDirichletAllocation(n_topics=100, learning_method='batch', max_iter=25, random_state=0)
    2. topics_100 = lda100.fit_transform(x)
    3. # randomly select several topics
    4. topics_sample = np.array([11, 21, 31, 41, 51])
    5. sorting_100 = np.argsort(lda100.components_, axis=1)[:, ::-1]
    6. feature_names_100 = np.array(vect.get_feature_names_out())
    7. mglearn.tools.print_topics(topics=topics_sample, feature_names=feature_names_100,
    8. sorting=sorting_100, topic_per_chunk=7, n_words=20)

    9.3 汇总每个文档主题重要性

    还有一种量化指标是将所有文档的主题重要性汇总,然后按会总量从大到小可视化,或者按topic可视化

    9.4 优缺点

    主题建模是无监督学习,最好有已知标签进行进一步验证。学习结果和random_state挂钩

    10 小结

    讨论了CountVectorizer和TfidfVectorizer,是相对简单的方法,其他高级的方法可使用py的包spacy(相对较新,较高效,设计良好),nltk(很完整的库,有些过时),gensim(着重于主题建模的nlp包)

    研究方向

    1 使用连续向量表示。

    2 递归神经网络(RNN)。很适合自动翻译和摘要,

  • 相关阅读:
    【信号隐藏-数字水印】基于小波变换算法DWT结合离散余弦变换DCT实现音频数字水印嵌入提取附matlab代码
    每日一题 1222. 可以攻击国王的皇后
    修改、添加和删除列表元素
    从源码看vue(v2.7.10)中的v-bind的原理
    volatile关键字在并发中有哪些作用?
    CMU/MIT/清华/Umass提出生成式机器人智能体RoboGen
    Linux网络命令
    Java版企业电子招标采购系统源码—企业战略布局下的采购寻源
    一次线上OOM问题的个人复盘
    单片机选型
  • 原文地址:https://blog.csdn.net/peter6768/article/details/133973553