参考笔记:掘金-NLP预处理技术
笔者根据其框架并根据自身学习扩充了对应的特征提取的Feature Extraction内容

1.特征提取

为了能够更好的训练模型,我们需要将文本的原始特征转化成具体特征,转化的方式主要有两种:统计和Embedding。

原始特征:需要人类或者机器进行转化,如:文本、图像。
具体特征:已经被人类进行整理和分析,可以直接使用,如:物体的重要、大小。

1.1 统计

  • 词频,是指某一个给定的词在该文件中出现的频率,需要进行归一化,避免偏向长文本
  • 逆向文件频率,是一个词普遍重要性的度量,由总文件数目除以包含该词的文件数目

那么,每个词都会得到一个TF-IDF值,用来衡量它的重要程度,计算公式如下:

[14]NLP预处理教程 - 图1

其中[14]NLP预处理教程 - 图2的式子中[14]NLP预处理教程 - 图3是该词在文件[14]NLP预处理教程 - 图4中的出现次数,而分母则是在文件[14]NLP预处理教程 - 图5中所有词的出现次数之和。

[14]NLP预处理教程 - 图6的式子中[14]NLP预处理教程 - 图7表示语料库中文件总数,[14]NLP预处理教程 - 图8表示包含词[14]NLP预处理教程 - 图9的文件数目,而[14]NLP预处理教程 - 图10是对结果做平滑处理。

TF_IDF可以使用sklearn机器学习库进行快速搭建,也可以使用jieba等自然语言工具调用。

可参考资料:sklearn-TfidfVectorizer彻底说清楚
1)下面的代码使用了sklearn的TFIDF算法进行特征提取

  1. # 使用 sklearn的TFIDF算法进行特征提取
  2. import jieba
  3. from sklearn.feature_extraction.text import TfidfTransformer,TfidfVectorizer,CountVectorizer
  4. corpus = ["词频,是指某一个给定的词在该文件中出现的频率,需要进行归一化,避免偏向长文本",
  5. "逆向文件频率,是一个词普遍重要性的度量,由总文件数目除以包含该词的文件数目"]
  6. corpus_list = []
  7. for corpu in corpus:
  8. corpus_list.append(" ".join(jieba.cut_for_search(corpu)))
  9. print("\n语料大小:{}\n{}".format(len(corpus_list),corpus_list))
  10. vectorizer = TfidfVectorizer(use_idf=True, smooth_idf=True, norm=None)
  11. tfidf = vectorizer.fit_transform(corpus_list)
  12. weight = tfidf.toarray()
  13. vocab = vectorizer.get_feature_names()
  14. print("\n词汇表大小:{}\n{}".format(len(vocab),vocab))
  15. print("\n权重形状:{}\n{}".format(weight.shape,weight))

image.png

2)使用jieba分词中的TFIDF算法进行关键词提取

  1. # jieba分词中 基于TFIDF的关键词提取
  2. import jieba
  3. import jieba.analyse
  4. sentences = ['中华蜜蜂原产于中国,是中国的土著蜂,适应中国各地的气候和蜜源条件,适于定地饲养且稳产,尤其是在南方山区,有着其他蜂种不可替代的地位。',
  5. '东方蜜蜂原产地在东方、简称东蜂,是蜜蜂属中型体中等的一个品种,分布于亚洲的中国、伊朗、日本、朝鲜等多个国家以及俄罗斯远东地区。该品种个体耐寒性强,适应利用南方冬季蜜源。']
  6. seg_list = []
  7. for sentence in sentences:
  8. seg_list.append(", ".join(jieba.cut(sentence, cut_all=True)))
  9. print("\n语料大小:{}\n{}".format(len(seg_list),seg_list))
  10. keywords = jieba.analyse.extract_tags(sentences[0], topK=20, withWeight=True, allowPOS=('n','nr','ns'))
  11. print("\n关键词大小:{}\n{}".format(len(keywords),keywords))
  12. keywords = jieba.analyse.extract_tags(sentences[1], topK=20, withWeight=True, allowPOS=('n','nr','ns'))
  13. print("\n关键词大小:{}\n{}".format(len(keywords),keywords))

image.png

1.2 Embedding - Word2vec 实践

Embedding是将词嵌入到一个由神经网络的隐藏层权重构成的空间中,让语义相近的词在这个空间中距离也是相近的。Word2vec就是这个领域具有表达性的方法,大体的网络结构如下:
image.png

输入层是经过One-Hot编码的词,隐藏层是我们想要得到的Embedding维度,而输出层是我们基于语料的预测结果。不断迭代这个网络,使得预测结果与真实结果越来越接近,直到收敛,我们就得到了词的Embedding编码,一个稠密的且包含语义信息的词向量,可以作为后续模型的输入。


参考资料:
部分资料版本老旧代码失效,gensim请以教程版本为准,保准代码可以run通
[1] : getting-started-with-word2vec-and-glove-in-python
[2] : python︱gensim训练word2vec及相关函数与功能理解
[3] : gensim中word2vec使用
[4] : gensim中word2vec使用

5.2.1.1 自建数据集创建和训练Word2vec

  1. import gensim
  2. print("gensim 版本:",gensim.__version__)
  3. # gensim 版本: 3.8.3

gensim是一款强大的自然语言处理工具,里面包括N多常见模型:
基本的语料处理工具、LSI、LDA、HDP、DTM、DIM、TF-IDF、word2vec、paragraph2vec

第一种方法:最简单的训练方法(快速)

  1. # 最简单的训练方式 - 一键训练
  2. # 引入 word2vec
  3. from gensim.models import word2vec
  4. # 引入数据集
  5. sentences = ['中华蜜蜂原产于中国,是中国的土著蜂,适应中国各地的气候和蜜源条件,适于定地饲养且稳产,尤其是在南方山区,有着其他蜂种不可替代的地位。',
  6. '东方蜜蜂原产地在东方、简称东蜂,是蜜蜂属中型体中等的一个品种,分布于亚洲的中国、伊朗、日本、朝鲜等多个国家以及俄罗斯远东地区。该品种个体耐寒性强,适应利用南方冬季蜜源。']
  7. seg_list = []
  8. for sentence in sentences:
  9. seg_list.append(" ".join(jieba.cut(sentence, cut_all=True)))
  10. # 切分词汇
  11. sentences = [s.split() for s in seg_list]
  12. # 构建模型
  13. model = word2vec.Word2Vec(sentences, min_count=1,size=100)
  14. """Word2Vec的参数
  15. min_count:在不同大小的语料集中,我们对于基准词频的需求也是不一样的。譬如在较大的语料集中,我们希望忽略那些只出现过一两次的单词,
  16. 这里我们就可以通过设置min_count参数进行控制。一般而言,合理的参数值会设置在 0~100 之间。
  17. size:参数主要是用来设置词向量的维度,Word2Vec 中的默认值是设置为 100 层。更大的层次设置意味着更多的输入数据,不过也能提升整体的准确度,合理的设置范围为 10~数百。
  18. workers:参数用于设置并发训练时候的线程数,不过仅当Cython安装的情况下才会起作用。
  19. """
  20. # 进行相关性比较
  21. model.wv.similarity('东方','中国')

第二种方法:分阶段式的训练方法(灵活)

  1. # 引入数据集
  2. sentences = ['中华蜜蜂原产于中国,是中国的土著蜂,适应中国各地的气候和蜜源条件,适于定地饲养且稳产,尤其是在南方山区,有着其他蜂种不可替代的地位。',
  3. '东方蜜蜂原产地在东方、简称东蜂,是蜜蜂属中型体中等的一个品种,分布于亚洲的中国、伊朗、日本、朝鲜等多个国家以及俄罗斯远东地区。该品种个体耐寒性强,适应利用南方冬季蜜源。']
  4. seg_list = []
  5. for sentence in sentences:
  6. seg_list.append(" ".join(jieba.cut(sentence, cut_all=True)))
  7. # 切分词汇
  8. sentences = [s.split() for s in seg_list]
  9. # 先启动一个空模型 an empty model
  10. new_model = gensim.models.Word2Vec(min_count=1)
  11. # 建立词汇表
  12. new_model.build_vocab(sentences)
  13. # 训练word2vec模型
  14. new_model.train(sentences, total_examples=new_model.corpus_count, epochs=new_model.epochs)
  15. # 进行相关性比较
  16. new_model.wv.similarity('东方','中国')

分阶段训练的另一个作用:增量训练

  1. # 增量训练
  2. old_model = gensim.models.Word2Vec.load(temp_path)
  3. # old_model = new_model
  4. more_sentences = [['东北','黑蜂','分布','在','中国','黑龙江省','饶河县',','
  5. ,'它','是','在','闭锁','优越','的','自然环境','里',',','通过','自然选择','与','人工','进行','所','培育','的','中国','唯一','的','地方','优良','蜂种','。']]
  6. old_model.build_vocab(more_sentences, update=True)
  7. old_model.train(more_sentences, total_examples=model.corpus_count, epochs=model.epochs)
  8. # 进行相关性比较
  9. new_model.wv.similarity('东方','中国')

5.2.1.2 外部语料库导入得到的word2vec

text8下载地址
第一种方式:载入语料法

  1. # 外部语料引入 【text8】:http://mattmahoney.net/dc/text8.zip
  2. sentences = word2vec.Text8Corpus('./text8')
  3. model = word2vec.Word2Vec(sentences, size=200)
  1. flag = False
  2. if flag:
  3. class Text8Corpus(object):
  4. """Iterate over sentences from the "text8" corpus, unzipped from http://mattmahoney.net/dc/text8.zip ."""
  5. def __init__(self, fname, max_sentence_length=MAX_WORDS_IN_BATCH):
  6. self.fname = fname
  7. self.max_sentence_length = max_sentence_length
  8. def __iter__(self):
  9. # the entire corpus is one gigantic line -- there are no sentence marks at all
  10. # so just split the sequence of tokens arbitrarily: 1 sentence = 1000 tokens
  11. sentence, rest = [], b''
  12. with utils.smart_open(self.fname) as fin:
  13. while True:
  14. text = rest + fin.read(8192) # avoid loading the entire file (=1 line) into RAM
  15. if text == rest: # EOF
  16. words = utils.to_unicode(text).split()
  17. sentence.extend(words) # return the last chunk of words, too (may be shorter/longer)
  18. if sentence:
  19. yield sentence
  20. break
  21. last_token = text.rfind(b' ') # last token may have been split in two... keep for next iteration
  22. words, rest = (utils.to_unicode(text[:last_token]).split(),
  23. text[last_token:].strip()) if last_token >= 0 else ([], text)
  24. sentence.extend(words)
  25. while len(sentence) >= self.max_sentence_length:
  26. yield sentence[:self.max_sentence_length]
  27. sentence = sentence[self.max_sentence_length:]

第二种方法:载入模型文件法

  1. # 此种方法需保证有vectors.npy
  2. model_normal = gensim.models.KeyedVectors.load('text.model')
  3. model_binary = gensim.models.KeyedVectors.load_word2vec_format('text.model.bin', binary=True)

5.2.1.3 word2vec的两种格式的存储和读取方法

  1. # 普通保存
  2. model.wv.save('text.model')
  3. # model = Word2Vec.load('text8.model')
  4. model_normal = gensim.models.KeyedVectors.load('text.model')
  5. # 二进制保存
  6. model.wv.save_word2vec_format('text.model.bin', binary=True)
  7. # model = word2vec.Word2Vec.load_word2vec_format('text.model.bin', binary=True)
  8. model_binary = gensim.models.KeyedVectors.load_word2vec_format('text.model.bin', binary=True)

5.2.1.4 训练好的word2vec怎么用? 养兵千日用兵一时

  1. # 相似度 比较
  2. model.similarity('肖战', '王一博'),model.similarity('肖战', '张艺兴'),model.similarity('王一博', '张艺兴')

image.png

  1. # 相近词 排列
  2. model.most_similar(positive=['腾讯'], topn=10)

image.png

  1. # 不相关词 识别
  2. model.doesnt_match("早饭 中饭 晚饭 土豆 夜宵 加餐".split())

image.png

  1. # 比较两个列表的相似度
  2. model.n_similarity(['皇帝','国王',"朕","天子"],['陛下'])

image.png

  1. # 得到词向量
  2. model["重庆"]

image.png

  1. # 获取词汇表
  2. model.vocab.keys()
  3. vocab = model.index2word[:100]

5.2.1.5 word2vec 和 深度学习框架

word2vec 如何与神经网络相结合呢?

  • tensorflow 版本
  • torch 版本

参考资料: https://zhuanlan.zhihu.com/p/210808209

  1. import numpy as np
  2. import tensorflow as tf
  3. from tensorflow.keras.preprocessing.sequence import pad_sequences
  4. from tensorflow.keras.preprocessing.text import Tokenizer
  5. from tensorflow.keras.utils import to_categorical
  6. #导入word2vec模型并进行预处理
  7. def w2v_model_preprocessing(content,w2v_model,embedding_dim,max_len=32):
  8. # 初始化 `[word : index]` 字典
  9. word2idx = {"_PAD": 0}
  10. # 训练数据 词汇表构建
  11. tokenizer = Tokenizer()
  12. tokenizer.fit_on_texts(sentences)
  13. vocab_size = len(tokenizer.word_index) # 词库大小
  14. print(tokenizer.word_index)
  15. error_count = 0
  16. # 存储所有 word2vec 中所有向量的数组,其中多一位,词向量全为 0, 用于 padding
  17. embedding_matrix = np.zeros((vocab_size + 1, w2v_model.vector_size))
  18. print(embedding_matrix.shape)
  19. for word, i in tokenizer.word_index.items():
  20. if word in w2v_model:
  21. embedding_matrix[i] = w2v_model.wv[word]
  22. else:
  23. error_count += 1
  24. # 训练数据 词向量截断补全(padding)
  25. seq = tokenizer.texts_to_sequences(sentences)
  26. trainseq = pad_sequences(seq, maxlen=max_len,padding='post')
  27. return embedding_matrix,trainseq
  28. # 从文本 到 tf可用的word2vec
  29. sentences = ['中华蜜蜂原产于中国,是中国的土著蜂,适应中国各地的气候和蜜源条件,适于定地饲养且稳产,尤其是在南方山区,有着其他蜂种不可替代的地位。',
  30. '东方蜜蜂原产地在东方、简称东蜂,是蜜蜂属中型体中等的一个品种,分布于亚洲的中国、伊朗、日本、朝鲜等多个国家以及俄罗斯远东地区。该品种个体耐寒性强,适应利用南方冬季蜜源。']
  31. seg_list = []
  32. for sentence in sentences:
  33. seg_list.append(" ".join(jieba.cut(sentence, cut_all=True)))
  34. sentences = [s.split() for s in seg_list]
  35. # 一些超参数
  36. max_len = 64
  37. embedding_dim = model.vector_size
  38. embedding_matrix,train_data = w2v_model_preprocessing(sentences,model,embedding_dim,max_len)
  39. embedding_matrix.shape,train_data.shape
  1. from tensorflow.keras.models import Sequential,Model
  2. from tensorflow.keras.models import load_model
  3. from tensorflow.keras.layers import Dense,Dropout,Activation,Input, Lambda, Reshape,concatenate
  4. from tensorflow.keras.layers import Embedding,Conv1D,MaxPooling1D,GlobalMaxPooling1D,Flatten,BatchNormalization
  5. from tensorflow.keras.losses import categorical_crossentropy
  6. from tensorflow.keras.optimizers import Adam
  7. from tensorflow.keras.regularizers import l2
  8. def build_textcnn(max_len,embeddings_dim,embeddings_matrix):
  9. #构建textCNN模型
  10. main_input = Input(shape=(max_len,), dtype='float64')
  11. # 词嵌入(使用预训练的词向量)
  12. embedder = Embedding(
  13. len(embeddings_matrix), #表示文本数据中词汇的取值可能数,从语料库之中保留多少个单词
  14. embeddings_dim, # 嵌入单词的向量空间的大小
  15. input_length=max_len, #规定长度
  16. weights=[embeddings_matrix],# 输入序列的长度,也就是一次输入带有的词汇个数
  17. trainable=False # 设置词向量不作为参数进行更新
  18. )
  19. embed = embedder(main_input)
  20. flat = Flatten()(embed)
  21. dense01 = Dense(5096, activation='relu')(flat)
  22. dense02 = Dense(1024, activation='relu')(dense01)
  23. main_output = Dense(2, activation='softmax')(dense02)
  24. model = Model(inputs=main_input, outputs=main_output)
  25. model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
  26. model.summary()
  27. return model
  28. TextCNN = build_textcnn(64,embedding_dim,embedding_matrix)
  29. # 数据集加载
  30. X_train, y_train = train_data,to_categorical([0,1], num_classes=2)
  31. # 粗糙的模型训练
  32. history = TextCNN.fit(X_train, y_train,
  33. batch_size=2,
  34. epochs=3,
  35. verbose=1)

5.2.1.6 word2vec的可视化方法

  1. from sklearn.decomposition import PCA
  2. import matplotlib.pyplot as plt
  3. def wv_visualizer(model,word):
  4. # 寻找出最相似的十个词
  5. words = [wp[0] for wp in model.wv.most_similar(word,topn=10)]
  6. # 提取出词对应的词向量
  7. wordsInVector = [model.wv[word] for word in words]
  8. wordsInVector
  9. # 进行 PCA 降维
  10. pca = PCA(n_components=2)
  11. pca.fit(wordsInVector)
  12. X = pca.transform(wordsInVector)
  13. # 绘制图形
  14. xs = X[:, 0]
  15. ys = X[:, 1]
  16. # draw
  17. plt.figure(figsize=(12,8))
  18. plt.scatter(xs, ys, marker = 'o')
  19. for i, w in enumerate(words):
  20. plt.annotate(
  21. w,
  22. xy = (xs[i], ys[i]), xytext = (6, 6),
  23. textcoords = 'offset points', ha = 'left', va = 'top',
  24. **dict(fontsize=10)
  25. )
  26. plt.show()
  27. # 调用时传入目标词组即可
  28. wv_visualizer(model,["man","king"])

image.png