NLP自然语言处理实战(三):词频背后的语义--5.距离和相似度&反馈及改进

目录

  • 1.距离和相似度
  • 2.反馈及改进
      • 线性判别分析

1.距离和相似度

我们可以使用相似度评分(或距离),根据两篇文档的表达向量间的相似度(或距离)来判断文档间有多相似。

LSA能够保持较大的距离,但它并不能总保持较小的距离(文档之间关系的精细结构)。LSA底层的SVD算法的重点是使新主题向量空间中所有文档之间的方差最大化。

特征向量(词向量、主题向量、文档上下文向量等)之间的距离驱动着NLP流水线或任何机器学习流水线的性能。那么,对一个具体的NLP问题来说,该选择哪一个距离呢?

  • 欧几里得距离或笛卡尔距离,或均方根误差(RMSE):2范数或L2
  • 平方欧几里得距离,距离平方和(SSD):L2^2
  • 余弦、夹角或投影距离:归一化点积
  • 闵可夫斯基距离:p范数或LP
  • 分级距离,分级范数:p范数或LP为0
  • 城市街区距离、曼哈顿距离或出租车距离,绝对距离之和(SAD):1范数或L1

距离通常由相似度(分数)计算,反之亦然,因此距离与相似度得分成反比。相似度得分设计为0到1之间。典型的距离与相似度之间的换算公式如下:
similarity = 1 / (1 + distance)
distance = (1 / similarity) - 1

但是,对于0到1之间(像概率一样)的距离和相似度得分,更常用的公式如下:
similarity = 1 - distance
distance = 1 - similarity

术语“距离”(distance)和“长度”(length)经常与术语“度量指标”(metric)混淆,因为许多距离和长度都是有效和有用的度量指标。但不幸的是,并非所有的距离都可以称为度量指标。
一个真正的度量指标必须具有下列4个数学性质,而距离或得分可能不具有这些性质。

  • 非负性:度量指标永远不可能是负的,metric(A, B)>= 0
  • 不可分辨性:如果两个对象之间的度量指标为0,那么它们是相同的。如果metric(A, B) == 0,assert(A==B)
  • 对称性:度量指标不关心方向,metric(A, B) = metric(B, A)
  • 三角不等式:无法通过A和C之间的B更快地从A到C,即metric(A, C)<= metric(A, B) = metric(B, C)

2.反馈及改进

前面所有的LSA方法都没有考虑文档之间的相似度信息。我们创建的主题对一组通用规则来说是最优的。在这些特征(主题)提取模型的无监督学习中,没有任何关于向量主题之间应该多么接近的数据。我们也不允许任何关于主题向量在哪里结束或者它们之间相关性如何的反馈。引导(steering)或“习得型距离指标”(learned distance metrics)是在降维和特征提取方面的最新进展。通过调整向聚类和嵌入算法报告的距离分数,我们可以控制自己的向量,从而让它们使一些代价函数最小化。通过这种方式,可以“强制”向量专注于我们感兴趣的信息内容的某个方面。

在Talentpair公司,我们使用每篇文档主题向量之间的余弦距离,对简历和职位描述进行匹配。
一种方法是计算两个质心之间的平均差(就像在LDA中做的那样),并在所有的简历或职位描述向量中添加部分这样的“偏差”。这样做应该去掉简历和职位描述之间的平均主题向量差异。午餐啤酒之类的主题可能会出现在职位描述中,但绝不会出现在简历中。类似的,一些简历中可能会出现一些奇怪的爱好,如水下雕塑,但从来不会出现在职位描述中。引导主题向量可以帮助我们集中在感兴趣的建模主题上。

线性判别分析

下面我们在标注好的短消息数据集上训练一个线性判别分析(LDA)模型。LDA的工作原理和LSA类似,但是它需要分类标签或其他分数才能找到高维空间中维度(词袋或TF-IDF向量中的词项)的最佳线性组合。LDA没有最大化新空间中所有向量之间的分离程度(方差),而是最大化了每个类质心向量之间的距离。
但是,这意味着必须通过给出样例(标注好的向量)来告诉LDA算法想对哪些主题建模。只有这样,算法才能计算出从高维空间到低维空间的最优转换。得到的低维向量的维数不能超过所能提供的类标签或分数的数量。因此只需要训练一个“垃圾性”主题,下面我们看看一维主题模型在垃圾短消息分类方面能达到多高的精确率。

lda = LDA(n_components=1)
lda = lda.fit(tfidf_docs, sms.spam)
sms['lda_spaminess'] = lda.predict(tfidf_docs)
print(((sms.spam - sms.lda_spaminess) ** 2).sum() ** 0.5)
print((sms.spam == sms.lda_spaminess).sum())
print(len(sms))

Out[1]:0.0
Out[2]:4837
Out[3]:4837
上面每一个都答对了!在TF-IDF向量中有10000个词项,它可以“记住”答案,这一点儿也不奇怪。下面我们来做一些交叉验证:

from sklearn.model_selection import cross_val_score
lda = LDA(n_components=1)
scores = cross_val_score(lda, tfidf_docs, sms.spam, cv=5)
print("Accuracy:{:.2f}(+/-{:.2f})".format(scores.mean(), scores.std() * 2))

Out[1]:Accuracy:0.77(+/-0.02)
显然这个模型并不好。这里再次给我们的提醒使,永远不要对模型在训练集上的效果感到兴奋。
为了确保77%的精确率数值是正确的,下面我们保留三分之一的数据集用于测试:

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(tfidf_docs, sms.spam, test_size=0.33, random_state=271828)
lda = LDA(n_components=1)
lda.fit(X_train, y_train)
LDA(n_components=1, priors=None, shrinkage=None, solver='svd', store_covariance=False, tol=0.0001)
print(lda.score(X_test, y_test).round(3))

Out[1]:0.764
同样,测试集的精确率也较低。因此,看起来并不像是数据抽样做的不好,这是一个糟糕的过拟合模型。
下面我们看看LSA和LDA结合起来是否有助于创建一个精确的、泛化能力强的模型,这样面对新的短消息时就不会出错:

from sklearn.decomposition import PCA
pca = PCA(n_components=16)
pca = pca.fit(tfidf_docs)
pca_topic_vectors = pca.transform(tfidf_docs)
columns = ['topic{}'.format(i) for i in range(pca.n_components)]
pca_topic_vectors = pd.DataFrame(pca_topic_vectors, columns=columns, index=index)
X_train, X_test, y_train, y_test = train_test_split(pca_topic_vectors.values, sms.spam, test_size=0.3, random_state=271828)
lda = LDA(n_components=1)
lda.fit(X_train, y_train)
LDA(n_components=1, priors=None, shrinkage=None, solver='svd', store_covariance=False, tol=0.0001)
print(lda.score(X_test, y_test).round(3))
lda = LDA(n_components=1)
scores = cross_val_score(lda, pca_topic_vectors, sms.spam, cv=10)
print("Accuracy: {:.3f}(+/-){:.3f}".format(scores.mean(), scores.std() * 2))

Out[1]:0.964
Out[2]:Accuracy: 0.957(+/-)0.022
因此,通过使用LSA,我们可以刻画一个只有16个维度的短消息,并且仍然有足够的信息将它们分类为垃圾信息(或非垃圾信息)。我们的低维模型不太可能过拟合,它应该有很好的泛化能力,并且能够看到尚未看到的短消息或聊天信息进行分类。

现在我们已经回到了本章开始时的简单模型。在尝试所有这些语义分析之前,使用简单的LDA模型可以获得更高的精确率。但是这个新模型的优点是,现在可以在多于一个的维度上创建表示语句语义的向量。

你可能感兴趣的:(NLP自然语言处理实战学习,自然语言处理,机器学习,人工智能)