使用LSI(潜在语义索引)进行维度归约
对于之前训练的TF-IDF向量空间表示,我们重新跑一遍代码,看一下它有些什么特点。
import pandas as pd
import pickle
from sklearn.feature_extraction.text import TfidfVectorizer
pkl_file_rb = open(r'./save_file', 'rb')
train =pickle.load(pkl_file_rb)
corpus = pd.concat([train . title1_tokenized, train . title2_tokenized])
corpus = [c for c in corpus]
tfidf_model = TfidfVectorizer().fit(corpus)
matrix1= tfidf_model.transform(train['title1_tokenized'])
matrix2= tfidf_model.transform(train['title2_tokenized'])
matrix1
<320552x67243 sparse matrix of type ''
with 2503463 stored elements in Compressed Sparse Row format>
有没有注意到——它被存成了一个 sparse matrix 的数据类型。
为什么会这样?
我们看到词库有67243个,这也是向量空间的维数,而一个新闻标题充其量也只由十几个词构成,因此绝大多数的维上取值都是0!
事实上,从内存供给能力看,也只能用这种稀疏矩阵的方法存储,否则就会OOM了……
对于这种高维稀疏的数据,使用机器学习(Machine Learning)直接用来训练模型是不合适的。
一方面,如此高的维度将导致低效的计算;另一方面,高维稀疏数据会增加从文本挖掘潜在模式的难度。
维度归约,也称降维,是指使用数据编码或变换,以便得到原数据的归约或“压缩”表示。
如果原数据可以由压缩数据重新构造而不丢失任何信息,则该数据归约是无损的;如果我们只能重新构造原数据的近似表示,则该数据归约是有损的。
潜在语义索引(LSI)是一种常用的文档维度归约算法。LSI基于奇异值分解(Singular Value Decomposition,SVD)的方法实现,SVD是线性代数中一种重要的矩阵分解,是矩阵分析中正规矩阵对角化的推广。
LSI基本思想:提取最具代表性的特征,同时最小化同构错误。
奇异值分解(SVD):X=(Aij)=UΣV`,
其中,Xm*n, Um*r, Σr*r(对角阵), Vn*r, r<=MIN(m,n),
取Σ对角上的前k个元素,得Σk,
Xk= UkΣkVk`, Uk由U的前k列组成,Vk由V的前k列组成,
文档d在LSI对应的向量d’=dTUkΣ-1 ,由r维降至k维。
我们可以使用 sklearn 包的 TruncatedSVD 函数实现LSI方法。
from sklearn.decomposition import TruncatedSVD
svd_model = TruncatedSVD(n_components=64, algorithm='randomized', n_iter=100, random_state=122) #参数n_components即降维的目标维数r
svd_model.fit(tfidf_model.transform(corpus))
TruncatedSVD(n_components=64, n_iter=100, random_state=122)
这里我们指定了降维的目标维数r为64,也就是说相对于原始的TF-IDF模型,我们把数据有损压缩了1000倍!
# 使用这个模型,对TF-IDF结果进行压缩。
matrix1_sub = svd_model.transform(matrix1)
matrix2_sub = svd_model.transform(matrix2)
matrix1_sub.shape
(320552, 64)
接下来,我们使用这个压缩的“稠密”数据重复一下上一回的余弦相似度分布计算。
import numpy as np
def cos_sim(a, b):
a_norm = np.linalg.norm(a)
b_norm = np.linalg.norm(b)
cos = np.dot(a,b)/(a_norm * b_norm)
return cos
cosin_measure = []
for i in range(matrix1_sub.shape[0]):
cosin_measure.append(cos_sim(np.squeeze(matrix1_sub[i]),np.squeeze(matrix2_sub[i])))
c:\users\hp\appdata\local\programs\python\python37\lib\site-packages\ipykernel_launcher.py:6: RuntimeWarning: invalid value encountered in double_scalars
c:\users\hp\appdata\local\programs\python\python37\lib\site-packages\ipykernel_launcher.py:6: RuntimeWarning: divide by zero encountered in double_scalars
train['cosin_measure'] = cosin_measure
print('Train columns with null values:\n', train.isnull().sum()) #检查各列的空值记录数
train.loc[:,'cosin_measure'] = train.loc[:,'cosin_measure'].fillna(0) #余弦相似度空值填充
Train columns with null values:
title1_zh 0
title2_zh 7
label 0
title1_tokenized 0
title2_tokenized 0
cosin_measure 18
dtype: int64
简便起见,我们跳过4分位箱形图的展示,直接看一下不同label在百分位数分布点位[1,5,10,25,50,75,90,95,99]的余弦相似度取值。
for level in ['unrelated','agreed','disagreed']:
data = train[train.label == level].cosin_measure
print(level,np.percentile(data, [1,5,10,25,50,75,90,95,99], axis=0))
unrelated [-0.06352342 -0.01780777 -0.00228008 0.04328785 0.25206004 0.66588372
0.88882218 0.96387027 0.99633213]
agreed [0.03268588 0.15247196 0.26748694 0.54153524 0.80297221 0.96769122
0.99764079 0.99989671 1. ]
disagreed [0.00746681 0.04532879 0.08774524 0.21708 0.43988834 0.70170525
0.85775077 0.91423598 0.97046834]
我们看到,不同label类别的新闻标题A和B余弦相似度的取值分布,仍然保留了一定的差异。
对于新版的余弦相似度,我们选用0.888这个吉祥的数字作为分界阈值,即:新闻标题A和B余弦相似度小于0.888的判为’unrelated’,余弦相似度大于0.888的判为’agreed’。
重新计算一下准确率。
train_currect = train[(train['label'] == 'unrelated') & (train['cosin_measure'] < 0.888) \
| (train['label'] == 'agreed') & (train['cosin_measure'] >= 0.888)]
accuracy = len(train_currect) / len(train)
print('accuracy: {:.1%}'.format(accuracy))
accuracy: 72.9%
结果是72.9%,比之前的77.4%有所下降,但与基线模型的68%相比,依然有所提升!
这里仅是通过简单的余弦距离方法利用LSI向量数据,下一次我们将尝试使用这个64维的压缩数据建立一个机器学习(Machine Learning)模型用来识别新闻标题A和B的关系。
好了,就到这儿吧。
本系列共12期,将分6天发布。相关代码已全部分享在我的github项目(moronism189/chinese-nlp-stepbystep)下,急的可以先去那里下载。