参考笔记:掘金-NLP预处理技术
笔者根据其框架并根据自身学习扩充了对应的特征提取的Feature Extraction内容
1.特征提取
为了能够更好的训练模型,我们需要将文本的原始特征转化成具体特征,转化的方式主要有两种:统计和Embedding。
原始特征:需要人类或者机器进行转化,如:文本、图像。
具体特征:已经被人类进行整理和分析,可以直接使用,如:物体的重要、大小。
1.1 统计
- 词频,是指某一个给定的词在该文件中出现的频率,需要进行归一化,避免偏向长文本
- 逆向文件频率,是一个词普遍重要性的度量,由总文件数目除以包含该词的文件数目
那么,每个词都会得到一个TF-IDF值,用来衡量它的重要程度,计算公式如下:
其中的式子中是该词在文件中的出现次数,而分母则是在文件中所有词的出现次数之和。
而的式子中表示语料库中文件总数,表示包含词的文件数目,而是对结果做平滑处理。
TF_IDF可以使用sklearn机器学习库进行快速搭建,也可以使用jieba等自然语言工具调用。
可参考资料:sklearn-TfidfVectorizer彻底说清楚
1)下面的代码使用了sklearn的TFIDF算法进行特征提取
# 使用 sklearn的TFIDF算法进行特征提取
import jieba
from sklearn.feature_extraction.text import TfidfTransformer,TfidfVectorizer,CountVectorizer
corpus = ["词频,是指某一个给定的词在该文件中出现的频率,需要进行归一化,避免偏向长文本",
"逆向文件频率,是一个词普遍重要性的度量,由总文件数目除以包含该词的文件数目"]
corpus_list = []
for corpu in corpus:
corpus_list.append(" ".join(jieba.cut_for_search(corpu)))
print("\n语料大小:{}\n{}".format(len(corpus_list),corpus_list))
vectorizer = TfidfVectorizer(use_idf=True, smooth_idf=True, norm=None)
tfidf = vectorizer.fit_transform(corpus_list)
weight = tfidf.toarray()
vocab = vectorizer.get_feature_names()
print("\n词汇表大小:{}\n{}".format(len(vocab),vocab))
print("\n权重形状:{}\n{}".format(weight.shape,weight))
2)使用jieba分词中的TFIDF算法进行关键词提取
# jieba分词中 基于TFIDF的关键词提取
import jieba
import jieba.analyse
sentences = ['中华蜜蜂原产于中国,是中国的土著蜂,适应中国各地的气候和蜜源条件,适于定地饲养且稳产,尤其是在南方山区,有着其他蜂种不可替代的地位。',
'东方蜜蜂原产地在东方、简称东蜂,是蜜蜂属中型体中等的一个品种,分布于亚洲的中国、伊朗、日本、朝鲜等多个国家以及俄罗斯远东地区。该品种个体耐寒性强,适应利用南方冬季蜜源。']
seg_list = []
for sentence in sentences:
seg_list.append(", ".join(jieba.cut(sentence, cut_all=True)))
print("\n语料大小:{}\n{}".format(len(seg_list),seg_list))
keywords = jieba.analyse.extract_tags(sentences[0], topK=20, withWeight=True, allowPOS=('n','nr','ns'))
print("\n关键词大小:{}\n{}".format(len(keywords),keywords))
keywords = jieba.analyse.extract_tags(sentences[1], topK=20, withWeight=True, allowPOS=('n','nr','ns'))
print("\n关键词大小:{}\n{}".format(len(keywords),keywords))
1.2 Embedding - Word2vec 实践
Embedding是将词嵌入到一个由神经网络的隐藏层权重构成的空间中,让语义相近的词在这个空间中距离也是相近的。Word2vec就是这个领域具有表达性的方法,大体的网络结构如下:
输入层是经过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
import gensim
print("gensim 版本:",gensim.__version__)
# gensim 版本: 3.8.3
gensim是一款强大的自然语言处理工具,里面包括N多常见模型:
基本的语料处理工具、LSI、LDA、HDP、DTM、DIM、TF-IDF、word2vec、paragraph2vec
第一种方法:最简单的训练方法(快速)
# 最简单的训练方式 - 一键训练
# 引入 word2vec
from gensim.models import word2vec
# 引入数据集
sentences = ['中华蜜蜂原产于中国,是中国的土著蜂,适应中国各地的气候和蜜源条件,适于定地饲养且稳产,尤其是在南方山区,有着其他蜂种不可替代的地位。',
'东方蜜蜂原产地在东方、简称东蜂,是蜜蜂属中型体中等的一个品种,分布于亚洲的中国、伊朗、日本、朝鲜等多个国家以及俄罗斯远东地区。该品种个体耐寒性强,适应利用南方冬季蜜源。']
seg_list = []
for sentence in sentences:
seg_list.append(" ".join(jieba.cut(sentence, cut_all=True)))
# 切分词汇
sentences = [s.split() for s in seg_list]
# 构建模型
model = word2vec.Word2Vec(sentences, min_count=1,size=100)
"""Word2Vec的参数
min_count:在不同大小的语料集中,我们对于基准词频的需求也是不一样的。譬如在较大的语料集中,我们希望忽略那些只出现过一两次的单词,
这里我们就可以通过设置min_count参数进行控制。一般而言,合理的参数值会设置在 0~100 之间。
size:参数主要是用来设置词向量的维度,Word2Vec 中的默认值是设置为 100 层。更大的层次设置意味着更多的输入数据,不过也能提升整体的准确度,合理的设置范围为 10~数百。
workers:参数用于设置并发训练时候的线程数,不过仅当Cython安装的情况下才会起作用。
"""
# 进行相关性比较
model.wv.similarity('东方','中国')
第二种方法:分阶段式的训练方法(灵活)
# 引入数据集
sentences = ['中华蜜蜂原产于中国,是中国的土著蜂,适应中国各地的气候和蜜源条件,适于定地饲养且稳产,尤其是在南方山区,有着其他蜂种不可替代的地位。',
'东方蜜蜂原产地在东方、简称东蜂,是蜜蜂属中型体中等的一个品种,分布于亚洲的中国、伊朗、日本、朝鲜等多个国家以及俄罗斯远东地区。该品种个体耐寒性强,适应利用南方冬季蜜源。']
seg_list = []
for sentence in sentences:
seg_list.append(" ".join(jieba.cut(sentence, cut_all=True)))
# 切分词汇
sentences = [s.split() for s in seg_list]
# 先启动一个空模型 an empty model
new_model = gensim.models.Word2Vec(min_count=1)
# 建立词汇表
new_model.build_vocab(sentences)
# 训练word2vec模型
new_model.train(sentences, total_examples=new_model.corpus_count, epochs=new_model.epochs)
# 进行相关性比较
new_model.wv.similarity('东方','中国')
分阶段训练的另一个作用:增量训练
# 增量训练
old_model = gensim.models.Word2Vec.load(temp_path)
# old_model = new_model
more_sentences = [['东北','黑蜂','分布','在','中国','黑龙江省','饶河县',','
,'它','是','在','闭锁','优越','的','自然环境','里',',','通过','自然选择','与','人工','进行','所','培育','的','中国','唯一','的','地方','优良','蜂种','。']]
old_model.build_vocab(more_sentences, update=True)
old_model.train(more_sentences, total_examples=model.corpus_count, epochs=model.epochs)
# 进行相关性比较
new_model.wv.similarity('东方','中国')
5.2.1.2 外部语料库导入得到的word2vec
text8下载地址
第一种方式:载入语料法
# 外部语料引入 【text8】:http://mattmahoney.net/dc/text8.zip
sentences = word2vec.Text8Corpus('./text8')
model = word2vec.Word2Vec(sentences, size=200)
flag = False
if flag:
class Text8Corpus(object):
"""Iterate over sentences from the "text8" corpus, unzipped from http://mattmahoney.net/dc/text8.zip ."""
def __init__(self, fname, max_sentence_length=MAX_WORDS_IN_BATCH):
self.fname = fname
self.max_sentence_length = max_sentence_length
def __iter__(self):
# the entire corpus is one gigantic line -- there are no sentence marks at all
# so just split the sequence of tokens arbitrarily: 1 sentence = 1000 tokens
sentence, rest = [], b''
with utils.smart_open(self.fname) as fin:
while True:
text = rest + fin.read(8192) # avoid loading the entire file (=1 line) into RAM
if text == rest: # EOF
words = utils.to_unicode(text).split()
sentence.extend(words) # return the last chunk of words, too (may be shorter/longer)
if sentence:
yield sentence
break
last_token = text.rfind(b' ') # last token may have been split in two... keep for next iteration
words, rest = (utils.to_unicode(text[:last_token]).split(),
text[last_token:].strip()) if last_token >= 0 else ([], text)
sentence.extend(words)
while len(sentence) >= self.max_sentence_length:
yield sentence[:self.max_sentence_length]
sentence = sentence[self.max_sentence_length:]
第二种方法:载入模型文件法
# 此种方法需保证有vectors.npy
model_normal = gensim.models.KeyedVectors.load('text.model')
model_binary = gensim.models.KeyedVectors.load_word2vec_format('text.model.bin', binary=True)
5.2.1.3 word2vec的两种格式的存储和读取方法
# 普通保存
model.wv.save('text.model')
# model = Word2Vec.load('text8.model')
model_normal = gensim.models.KeyedVectors.load('text.model')
# 二进制保存
model.wv.save_word2vec_format('text.model.bin', binary=True)
# model = word2vec.Word2Vec.load_word2vec_format('text.model.bin', binary=True)
model_binary = gensim.models.KeyedVectors.load_word2vec_format('text.model.bin', binary=True)
5.2.1.4 训练好的word2vec怎么用? 养兵千日用兵一时
# 相似度 比较
model.similarity('肖战', '王一博'),model.similarity('肖战', '张艺兴'),model.similarity('王一博', '张艺兴')
# 相近词 排列
model.most_similar(positive=['腾讯'], topn=10)
# 不相关词 识别
model.doesnt_match("早饭 中饭 晚饭 土豆 夜宵 加餐".split())
# 比较两个列表的相似度
model.n_similarity(['皇帝','国王',"朕","天子"],['陛下'])
# 得到词向量
model["重庆"]
# 获取词汇表
model.vocab.keys()
vocab = model.index2word[:100]
5.2.1.5 word2vec 和 深度学习框架
word2vec 如何与神经网络相结合呢?
- tensorflow 版本
- torch 版本
参考资料: https://zhuanlan.zhihu.com/p/210808209
import numpy as np
import tensorflow as tf
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.utils import to_categorical
#导入word2vec模型并进行预处理
def w2v_model_preprocessing(content,w2v_model,embedding_dim,max_len=32):
# 初始化 `[word : index]` 字典
word2idx = {"_PAD": 0}
# 训练数据 词汇表构建
tokenizer = Tokenizer()
tokenizer.fit_on_texts(sentences)
vocab_size = len(tokenizer.word_index) # 词库大小
print(tokenizer.word_index)
error_count = 0
# 存储所有 word2vec 中所有向量的数组,其中多一位,词向量全为 0, 用于 padding
embedding_matrix = np.zeros((vocab_size + 1, w2v_model.vector_size))
print(embedding_matrix.shape)
for word, i in tokenizer.word_index.items():
if word in w2v_model:
embedding_matrix[i] = w2v_model.wv[word]
else:
error_count += 1
# 训练数据 词向量截断补全(padding)
seq = tokenizer.texts_to_sequences(sentences)
trainseq = pad_sequences(seq, maxlen=max_len,padding='post')
return embedding_matrix,trainseq
# 从文本 到 tf可用的word2vec
sentences = ['中华蜜蜂原产于中国,是中国的土著蜂,适应中国各地的气候和蜜源条件,适于定地饲养且稳产,尤其是在南方山区,有着其他蜂种不可替代的地位。',
'东方蜜蜂原产地在东方、简称东蜂,是蜜蜂属中型体中等的一个品种,分布于亚洲的中国、伊朗、日本、朝鲜等多个国家以及俄罗斯远东地区。该品种个体耐寒性强,适应利用南方冬季蜜源。']
seg_list = []
for sentence in sentences:
seg_list.append(" ".join(jieba.cut(sentence, cut_all=True)))
sentences = [s.split() for s in seg_list]
# 一些超参数
max_len = 64
embedding_dim = model.vector_size
embedding_matrix,train_data = w2v_model_preprocessing(sentences,model,embedding_dim,max_len)
embedding_matrix.shape,train_data.shape
from tensorflow.keras.models import Sequential,Model
from tensorflow.keras.models import load_model
from tensorflow.keras.layers import Dense,Dropout,Activation,Input, Lambda, Reshape,concatenate
from tensorflow.keras.layers import Embedding,Conv1D,MaxPooling1D,GlobalMaxPooling1D,Flatten,BatchNormalization
from tensorflow.keras.losses import categorical_crossentropy
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.regularizers import l2
def build_textcnn(max_len,embeddings_dim,embeddings_matrix):
#构建textCNN模型
main_input = Input(shape=(max_len,), dtype='float64')
# 词嵌入(使用预训练的词向量)
embedder = Embedding(
len(embeddings_matrix), #表示文本数据中词汇的取值可能数,从语料库之中保留多少个单词
embeddings_dim, # 嵌入单词的向量空间的大小
input_length=max_len, #规定长度
weights=[embeddings_matrix],# 输入序列的长度,也就是一次输入带有的词汇个数
trainable=False # 设置词向量不作为参数进行更新
)
embed = embedder(main_input)
flat = Flatten()(embed)
dense01 = Dense(5096, activation='relu')(flat)
dense02 = Dense(1024, activation='relu')(dense01)
main_output = Dense(2, activation='softmax')(dense02)
model = Model(inputs=main_input, outputs=main_output)
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.summary()
return model
TextCNN = build_textcnn(64,embedding_dim,embedding_matrix)
# 数据集加载
X_train, y_train = train_data,to_categorical([0,1], num_classes=2)
# 粗糙的模型训练
history = TextCNN.fit(X_train, y_train,
batch_size=2,
epochs=3,
verbose=1)
5.2.1.6 word2vec的可视化方法
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
def wv_visualizer(model,word):
# 寻找出最相似的十个词
words = [wp[0] for wp in model.wv.most_similar(word,topn=10)]
# 提取出词对应的词向量
wordsInVector = [model.wv[word] for word in words]
wordsInVector
# 进行 PCA 降维
pca = PCA(n_components=2)
pca.fit(wordsInVector)
X = pca.transform(wordsInVector)
# 绘制图形
xs = X[:, 0]
ys = X[:, 1]
# draw
plt.figure(figsize=(12,8))
plt.scatter(xs, ys, marker = 'o')
for i, w in enumerate(words):
plt.annotate(
w,
xy = (xs[i], ys[i]), xytext = (6, 6),
textcoords = 'offset points', ha = 'left', va = 'top',
**dict(fontsize=10)
)
plt.show()
# 调用时传入目标词组即可
wv_visualizer(model,["man","king"])