词袋法
使用sklearn提供的CountVectorizer类,样例如下:
def corpus4sklearn():
return ["i am chinese", "i am in Hubei"]
def bow_extracter(corpus):
# ngram_range是说可以把几个词连起来视作一个特征,bow一般就是一个词一个特征,否则特征数会爆炸
vectorizer = CountVectorizer(stop_words=None, ngram_range=(1, 1), analyzer="word")
return do_extracter(vectorizer, corpus)
def do_extracter(vectorizer, corpus):
mat = vectorizer.fit_transform(corpus)
print("feature num:%d" % mat.shape[1])
print("vocab:%s" % vectorizer.vocabulary_)
return vectorizer, mat
提取的结果是语料中的每个词(去掉stop_words)作为一个特征,语料中的每篇文档形成
[1,0,2,0...]
这样的向量,向量元素是某个单词在文档里出现的次数。这里的特征数目可能会非常巨大(取决于词典vocabulary_里词的总数)。
CountVectorizer具体说明参看:
tf-idf特征提取
也可使用tf-idf方法完成文本向量化,该方法是在词袋法的基础上考虑了idf,即“逆向文档频率”,削弱了那些在多个文档中频繁出现的词的影响(这些词往往不那么重要,因为它们无法区分文档)。样例代码如下:
def corpus4sklearn():
return ["i am chinese", "i am in Hubei"]
def tfidf_extracter(corpus):
# 做l2正则化,即均方。l1则为误差绝对值的和
vectorizer = TfidfVectorizer(
min_df=1, norm="l2", smooth_idf=True, use_idf=True, ngram_range=(1, 1)
)
return do_extracter(vectorizer, corpus)
def do_extracter(vectorizer, corpus):
mat = vectorizer.fit_transform(corpus)
print("feature num:%d" % mat.shape[1])
print("vocab:%s" % vectorizer.vocabulary_)
return vectorizer, mat
tf-idf公式
tf-idf(d, t) = tf(t) * idf(d, t)
其中:
t为词,tf(t)为词频;
d为文档,idf(d, t)为逆文档频率。
idf(d, t)=log [ N / df(d, t) ] + 1
N为文档总数,df(d, t)为包含词t的文档数。可见,词t在文档里出现的越少,idf值越大,该词的影响力就越大。+1主要是确保那些在所有文档里出现的词(显然,此时df(d, t)=N,所以log [ N / df(d, t) ] = 0)不会完全被忽略。
为避免除数为0,sklearn的tf-idf向量化默认会做idf平滑
,上述公式演变成:
idf(d, t) = log [ (1 + N) / (1 + df(d, t)) ] + 1
word2vec/doc2vec向量化
不管是词袋还是tf-idf,都没有考虑单词的顺序和语义。要考虑顺序和语义,可以使用word2vec及其改进版doc2vec来生成文档向量。相比于词袋法和tf-idf,Word2vec/doc2vec的特征数更可控。核心代码为:
def corpus4word2vec():
origin = ["i am chinese", "i am in Hubei"]
return [x.split(" ") for x in origin]
def corpus4doc2vec():
origin = ["i am chinese", "i am in Hubei"]
# doc2vec的一条语料记录用标记文档(TaggedDocument)表示,第二个参数是用户打的标记
return [TaggedDocument(x.split(" "), [i]) for i, x in enumerate(origin)]
def gen_model_by_word2vec(corpus):
model = gensim.models.Word2Vec(corpus, vector_size=100, window=5, min_count=1, sg=0)
print(model.corpus_total_words)
return model
def gen_model_by_doc2vec(corpus):
model = gensim.models.Doc2Vec(
documents=corpus, vector_size=50, dm=0, window=5, min_count=1,epochs=20
)
print(model.corpus_total_words)
return model
...
# 获得某个单词的向量都是用wv[]
vec = model.wv["chinese"]
# doc2vec可推断文档的向量,word2vec不行
model.infer_vector(["i", "chinese"])
特别说一下doc2vec里TaggedDocument的tag,因为最终的model.dv里是用tag来作为key的,所以每个文档必须要有一个唯一tag(比如使用自增序列号)。除此之外,还可以增加额外的tag,但额外tag的值要与唯一tag区分开。比如,我有10篇doc,唯一tag从0~9,额外tag就不能再用0~9的数字了,可用Y和N这样的字符串。
word2vec和doc2vec的差异:
word2vec得到的是词的向量,doc2vec不仅能得到词向量,还可以推断出文档向量。
得到词或文档的向量后,就可以用这些向量进行后续的文本分类、预测等工作了。
训练参数选择
特别讲一下Doc2Vec的训练参数选择:
vector_size,向量大小,默认是100,数据集不大时(数百到数千条文本),可设为50。数据集大时,300~400也很常见。
epochs,迭代次数,默认10,用于处理较大数据集(数万到数百万)。如果数据集较小(数百到数千),需要加大epochs到20,可以加强向量生成的准确性和稳定性。
总的来说,对于小数据集,可以适当加大epochs并减小vector_size,以获得更准确、更稳定的训练效果。
还有一种更容易量化的方法来判断模型的准确性和稳定性:
1、对同一份文本多次使用infer_vector来推测其向量,对这些向量计算余弦相似度,相似度达到0.98~0.99的,说明模型比较稳定;
2、对同一份文本,计算model.dv里的训练向量和infer_vector推测向量的余弦相似度,相似度达到0.95以上的,说明模型基本是准确的。不过,该量化方法,训练的dm参数应设置为0,即使用DBOW而非DM方法来训练。