文本分类任务的基础实现(五)——机器学习部分——特征提取_Doc2vec特征+hash特征原理介绍

  • 本文介绍前文用到的特征提取方法的原理介绍。
    【写的不好,理解的不透彻,理解深刻了回来再补充,去吃公司下午茶了,嘻嘻嘻】

Doc2vec特征 & hash特征

1. Doc2Vec

  • 将原始数据数字化为doc2vec特征
from gensim.models.doc2vec import Doc2Vec, TaggedDocument 

"""=====================================================================================================================
0 辅助函数 
"""
def sentence2list(sentence):
    s_list = sentence.strip().split()
    return s_list

"""====================================================================
1 读取原始数据,并进行简单处理
"""
df_train = pd.read_csv('../data/train_set.csv')
df_train.drop(columns='article', inplace=True)
df_test = pd.read_csv('../data/test_set.csv')
df_test.drop(columns='article', inplace=True)
df_all = pd.concat(objs=[df_train, df_test], axis=0, sort=True)
y_train = (df_train['class'] - 1).values

df_all['word_list'] = df_all['word_seg'].apply(sentence2list)
texts = df_all['word_list'].tolist()

"""====================================================================
2 doc2vec
"""
"""
语料库的准备,就是将准备好的文章库,转换为一个语料库。
文章一般会被保存为TaggedDocument,也就是带有标签的文档。
一篇文章对应着一个TaggedDocument对象。
TaggedDocument里面存放的是Token列表和Tag:
其中Token列表就是将文章通过分词软件分成的词语的列表,Tag这里保存着原来文章的编号。
这里的Tag也可以是文档的标题,或者任何可以代表文档的Paragraph Id。
下面这个代码中 documents 变量表示一个TaggedDocument数组。
"""
#documents是训练文档,训练文档必须是一行一个文本,并且进行过分词。
documents = [TaggedDocument(doc, [i]) for i, doc in enumerate(texts)]
model = Doc2Vec(documents, vector_size=200, window=5, min_count=3, workers=4, epochs=25)
docvecs = model.docvecs

x_train = []
for i in range(0, 102277):
    x_train.append(docvecs[i])
x_train = np.array(x_train)

x_test = []
for j in range(102277, 204554):
    x_test.append(docvecs[j])
x_test = np.array(x_test)

算法介绍:

Word2vec是一种非监督式算法,是一个Estimator,它采用一系列代表文档的词语来训练word2vec model。该模型将每个词语映射到一个固定大小的向量。word2vec model使用文档中每个词语的平均数来将文档转换为向量,然后这个向量可以作为预测的特征,来计算文档相似度计算等等。

另一类最近比较流行的模型是把每一个单词表示成一个向量。这些模型一般是基于某种文本中与单词共现相关的统计量来构造。一旦向量表示算出,就可以像使用TF-IDF向量一样使用这些模型(例如使用它们作为机器学习的特征)。一个比较通用的例子是使用单词的向量表示基于单词的含义计算两个单词的相似度。Word2Vec就是这些模型中的一个具体实现,常称作分布向量表示。

学出来的向量可以通过计算距离来找 sentences/paragraphs/documents 之间的相似性,可以用于文本聚类,对于有标签的数据,还可以用监督学习的方法进行文本分类,例如经典的情感分析问题。

Gensim中 Word2Vec 模型的期望输入是进过分词的句子列表,每个句子是一个单词列表,即是某个二维数组。这里我们暂时使用 Python 内置的数组,不过其在输入数据集较大的情况下会占用大量的 RAM。Gensim 本身只是要求能够迭代的有序句子列表,因此在工程实践中我们可以使用自定义的生成器,只在内存中保存单条语句。

>>> sentences = [['first', 'sentence'], ['second', 'sentence']]
>>> # train word2vec on the two sentences
>>> model = gensim.models.Word2Vec(sentences, min_count=1)

将输入视为Python的内置列表很简单, 但是在输入很大时会占用大量的内存. 所以Gensim只要求输入按顺序提供句子, 并不将这些句子存储在内存, 然后Gensim可以加载一个句子, 处理该句子, 然后加载下一个句子。

2. hash特征

  • 用哈希技巧向量化大文本向量
from sklearn.feature_extraction.text import HashingVectorizer

vectorizer = HashingVectorizer(ngram_range=(1, 2), n_features=200)
d_all = vectorizer.fit_transform(df_all['word_seg'])
x_train = d_all[:len(y_train)]
x_test = d_all[len(y_train):]

算法介绍

词袋模型向量化情景简单,但是,事实上这种方式从字符标记到整型特征的目录(vocabulary_属性)的映射都是在内存中进行,在处理大数据集时会出现一些问题:- 语料库越大,词表就会越大,因此使用的内存也越大,- 拟合(fitting)需要根据原始数据集的大小等比例分配中间数据结构的大小。- 构建词映射需要完整的传递数据集,因此不可能以严格在线的方式拟合文本分类器。- pickling和un-pickling vocabulary很大的向量器会非常慢(通常比pickling/un-pickling单纯数据的结构,比如同等大小的Numpy数组),- 将向量化任务分隔成并行的子任务很不容易实现,因为vocabulary属性要共享状态有一个细颗粒度的同步障碍:从标记字符串中映射特征索引与每个标记的首次出现顺序是独立的,因此应该被共享,在这点上并行worker的性能收到了损害,使他们比串行更慢。

通过同时使用由sklearn.feature_extraction.FeatureHasher类实施的“哈希技巧”(特征哈希)、文本预处理和CountVectorizer的标记特征有可能克服这些限制。
这个组合在HashingVectorizer实现,这个转换器类是无状态的,其大部分API与CountVectorizer.HashingVectorizer兼容,这意味着你不需要在上面调用fit:

from sklearn.feature_extraction.text import HashingVectorizer
hv = HashingVectorizer(n_features=10)
hv.transform(corpus)
...                                
<4x10 sparse matrix of type '<... 'numpy.float64'>'
    with 16 stored elements in Compressed Sparse Row format>

你可以看到从向量输出中抽取了16个非0特征标记:与之前由CountVectorizer在同一个样本语料库抽取的19个非0特征要少。差异来自哈希方法的冲突,因为较低的n_features参数的值。

在真实世界的环境下,n_features参数可以使用默认值2 * 20(将近100万可能的特征)。如果内存或者下游模型的大小是一个问题,那么选择一个较小的值比如2 * 18可能有一些帮助,而不需要为典型的文本分类任务引入太多额外的冲突。

注意维度并不影响CPU的算法训练时间,这部分是在操作CSR指标(LinearSVC(dual=True), Perceptron, SGDClassifier, PassiveAggressive),但是,它对CSC matrices (LinearSVC(dual=False), Lasso(), etc)算法有效。

让我们用默认设置再试一下:

hv = HashingVectorizer()
hv.transform(corpus)
...                               
<4x1048576 sparse matrix of type '<... 'numpy.float64'>'
    with 19 stored elements in Compressed Sparse Row format>

冲突没有再出现,但是,代价是输出空间的维度值非常大。当然,这里使用的19词以外的其他词之前仍会有冲突。

HashingVectorizer也有以下的局限:

  • 不能反转模型(没有inverse_transform方法),也无法访问原始的字符串表征,因为,进行mapping的哈希方法是单向本性。
  • 没有提供了IDF权重,因为这需要在模型中引入状态。如果需要的话,可以在管道中添加TfidfTransformer。

进行HashingVectorizer的核外扩展

使用HashingVectorizer的一个有趣发展是进行核外扩展的能力。这意味着我们可以从无法放入电脑主内存的数据中进行学习。

实现核外扩展的一个策略是将数据以流的方式以一小批提交给评估器。每批的向量化都是用HashingVectorizer这样来保证评估器的输入空间的维度是相等的。因此任何时间使用的内存数都限定在小频次的大小。尽管用这种方法可以处理的数据没有限制,但是从实用角度学习时间受到想要在这个任务上花费的CPU时间的限制。

猜你喜欢

转载自blog.csdn.net/weixin_38278334/article/details/82706789