NLP预处理技术-特征提取和word2vec

2,004 阅读9分钟

「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

参考笔记:掘金-NLP预处理技术

笔者根据其框架以及自身学习积累扩充了特征提取(Feature Extraction)部分的内容

1.特征提取

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

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

1.1 统计

  • 词频,是指某一个给定的词在该文件中出现的频率,需要进行归一化,避免偏向长文本
  • 逆向文件频率,是一个词普遍重要性的度量,由总文件数目除以包含该词的文件数目 那么,每个词都会得到一个TF-IDF值,用来衡量它的重要程度,计算公式如下:

image.png

可参考资料: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使用

1.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('东方','中国')

1.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)

1.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)

1.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]

1.1.5 word2vec 和 深度学习框架

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

  • tensorflow 版本
  • torch 版本

参考资料: 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)

1.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"])

NLP萌新,才疏学浅,有错误或者不完善的地方,请批评指正!!