EE103课程所有应用代码在这里
预处理
实验原理
词向量
维护一个包含了n个不同单词的词向量
每个词在文件中的出现次数用词向量 a ⃗ \vec{a} a来储存, a i a_i ai表示词i在文本中出现的次数
h ⃗ = a ⃗ 1 ⃗ T a ⃗ \vec{h}=\frac{\vec{a}}{\vec{1}^T\vec{a}} h=1Taa是词频向量,或者直方图向量
语料库矩阵
收集N个文本,每个文本计算其直方图词向量,以 ( h 1 T , h 2 T , . . . , h N T ) (h_1^T,h_2^T,...,h_N^T) (h1T,h2T,...,hNT)的方式排列,其中 H i j H_{ij} Hij表示第j个词在第i个文件中出现的次数, H H H的第j列 b j b_j bj表示第j个单词在每个文件中出现的次数
预处理
- 保留词干
- 停用词处理:去除很短的词(the、is、at)、意义较小的词(what、this、how)、极端不常见的词
- 去除标点符号
- TF-IDF和bi-grams
TF-IDF
计算 T F = 某 个 词 在 文 本 中 出 现 的 次 数 该 文 本 中 包 含 的 总 词 数 TF=\frac{某个词在文本中出现的次数}{该文本中包含的总词数} TF=该文本中包含的总词数某个词在文本中出现的次数, I D F = l o g 总 文 本 数 包 含 该 词 的 文 本 数 IDF=log\frac{总文本数}{包含该词的文本数} IDF=log包含该词的文本数总文本数,再计算其乘积。乘积越大,代表词汇包含的信息量越大,则直方图向量中的权重越大
实验方法
使用python的nltk库可以保留词干并去除标点符号、sklearn库可以过滤停用词、提取直方图词向量。我们使用10条Amazon上婴儿摇篮pacifier的销售评论数据作为数据集进行词向量提取。
处理
import nltk
tokenizer = nltk.RegexpTokenizer(r'\w+') #去除标点符号的正则过滤器
corpus2=["" for i in range(0,corpus.shape[0])]
for i in range(0,corpus.shape[0]):
lis=tokenizer.tokenize(corpus[i])
for word in lis:
corpus2[i]+= nltk.PorterStemmer().stem(word)+" "
#将文本中所有单词只保留词干
生成直方图向量
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer(min_df=1)
corpus = [ 'This is the first document.',
'This is the second second document.',
'And the third one.',
'Is this the first document?',
] #假设的语料库,实际中我们使用Amazon上婴儿摇篮pacifier的销售评论数据作为数据集进行词向量提取
X = vectorizer.fit_transform(corpus).toarray() #提取直方图词向量矩阵
feature_name = vectorizer.get_feature_names() #得到列标签,即每个关键词
生成TF-IDF直方图向量
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = [ 'This is the first document.',
'This is the second second document.',
'And the third one.',
'Is this the first document?',
] #假设的语料库,实际中我们使用Amazon上婴儿摇篮pacifier的销售评论数据作为数据集进行词向量提取
tfidf_vectorizer = TfidfVectorizer()
tfidf = tfidf_vectorizer.fit_transform(corpus).toarray() #提取直方图词向量矩阵
feature_name = tfidf_vectorizer.get_feature_names() #得到列标签,即每个关键词
结果分析
10条评论共出现178个关键词,直方图向量数值分布如图
文本相似性度量
实验原理
两个文本的距离: ∣ ∣ h 1 − h 2 ∣ ∣ ||h_1-h_2|| ∣∣h1−h2∣∣
两个文本的角度: ∠ ( h 1 , h 2 ) \angle (h_1,h_2) ∠(h1,h2)
两个文本越相似,以上两值应该越小
实验方法
使用numpy的ndarray的线性运算功能计算向量距离,并利用范数计算弧度
def norm(arr):
return np.sqrt(np.sum((arr)**2,axis=0)) #范数计算
def radian(arr1,arr2):
return np.arccos(np.sum(arr1*arr2,axis=0)/(norm(arr1)*norm(arr2))) #弧度计算
def distance(arr1,arr2):
return np.sqrt(np.sum((np.abs(arr1-arr2))**2,axis=0)) #距离计算
sm_dis=np.zeros((tfidf.shape[0],tfidf.shape[0])) #储存距离的矩阵
sm_rad=np.zeros((tfidf.shape[0],tfidf.shape[0])) #储存弧度的矩阵
for j in range(0,tfidf.shape[0]):
for i in range(j,tfidf.shape[0]):
sm_dis[j][i]=distance(tfidf[j],tfidf[i])
sm_dis[i][j]=sm_dis[j][i]
sm_rad[j][i]=radian(tfidf[j],tfidf[i])
sm_rad[i][j]=sm_rad[j][i]
#循环打表
结果分析
我们使用10条Amazon上婴儿摇篮pacifier的销售评论数据作为数据集进行距离度量。
10条评论之间距离度量
10条评论之间角度(弧度制)度量
文章分类
实验原理
主题聚类
使用k均值聚类将语料库中的直方图词向量划分为k组,每组通常都具有相同的话题。k个重心同样也是直方图向量
可以找出每个词向量中与重心关系最大的关键词
对于新文章,也可以利用重心进行分类
k均值聚类
k均值聚类中,K值表示要得到的簇的个数,质心表示每个簇的均值向量(即向量各维取平均),距离量度常用欧几里得距离和余弦相似度
-
首先确定一个k值,即我们希望将数据集经过聚类得到k个集合
-
从数据集中随机选择k个数据点作为质心
-
对数据集中每一个点,计算其与每一个质心的距离(如欧式距离),离哪个质心近,就划分到那个质心所属的集合
-
把所有数据归好集合后,一共有k个集合。然后重新计算每个集合的质心
-
如果新计算出来的质心和上一次计算的质心之间的距离小于某一个设置的阈值(表示重新计算的质心的位置变化不大,趋于稳定,或者说收敛),我们可以认为聚类已经达到期望的结果,算法终止,否则回到3
最小二乘二分类
利用词向量对文本进行二分类,并用测试集上的错误率来评价分类效果
为每个文本添加标签,若属于一个分类则标签为1,属于另一个分类则标签为-1,得到分类向量 y ⃗ \vec{y} y
训练回归模型
对于新输入的词向量得出的y值,使用 s g n ( y ) sgn(y) sgn(y)获取其分类
目标函数:
λ \lambda λ为人为规定的正则化系数,可以观察 λ \lambda λ与测试集上的错误率的关系来选取
实验方法
python的scipy库提供了聚类工具
from scipy.cluster.vq import kmeans,vq,whiten
data = whiten(data) #白化数据(数据降维)
centroids,_ = kmeans(data,num) #计算num个群集的K均值
clx,_ = vq(data,centroids) #将每个值分配给一个聚类
先使用10条Amazon上婴儿摇篮pacifier的销售评论数据,进行主题聚类查看结果
任意残差回归,可以使用向 ω \omega ω的每个位求偏导令偏导为0后解线性方程组的方法进行
-
对所有的评论数据,通过评论自带的星级,以3为界分为好评和差评
-
将数据分为训练集和测试集,使用训练集列线性方程组解出 ω \omega ω每个位上的值
-
在测试集上测试分类效果,计算预测准确与不准确的概率
-
改变λ取值,重复2、3
解线性方程组可以使用python的SciPy库
from scipy import linalg
import numpy as np
# x1 + x2 + 7*x3 = 2
# 2*x1 + 3*x2 + 5*x3 = 3
# 4*x1 + 2*x2 + 6*x3 = 4
A = np.array([[1, 1, 7], [2, 3, 5], [4, 2, 6]]) # A代表系数矩阵
b = np.array([2, 3, 4]) # b代表常数列
x = linalg.solve(A, b)
print(x)
key_num=len(feature_name)
train_length=200
y_train=sr[:train_length]
h_train=tfidf[:train_length]
y_test=sr[train_length:]
h_test=tfidf[train_length:]
#划分训练集和测试集
A=np.zeros((key_num,key_num))
b=np.zeros((key_num,))
lamb=np.linspace(0.1,10,50) #生成λ
rating=[]
for l in np.nditer(lamb):
for j in range(0,key_num):
for i in range(0,key_num):
if i==j:
A[j][i]+=np.sum(h_train[...,i]*h_train[...,i])+l
else:
A[j][i]+=np.sum(h_train[...,i]*h_train[...,j])
b[j]+=np.sum(h_train[...,j]*y_train)
#生成每个λ对应的系数矩阵和常数项向量
omega = linalg.solve(A, b) #解线性方程组
r_test=np.sum(h_test*omega,axis=1)
r_test=np.where(r_test>=0,1,-1)
rate=(r_test==y_test)
rating.append(np.sum(rate)/rate.shape[0])
#计算正确率
plt.plot(lamb,rating)
plt.show()
结果分析
我们使用10条Amazon上婴儿摇篮pacifier的销售评论数据作为数据集进行分类。
试图对10条评论按主题聚类进行好评/中等/差评三分类的分类结果为:
可见使用聚类的方式进行分类,并不能明确地针对“情感色彩”这一属性进行很好的分类
试图对200条评论按最小二乘二分类进行好评/差评二分类,在不同的λ下在100条评论的测试集上预测正确率:
可见对于给定的数据集,当λ在1.71以上时,正确率将接近90%,可以明确地对评论按情感色彩进行分类。
词聚类
实验原理
收集N个文本,每个文本计算其直方图词向量,以 ( h 1 T , h 2 T , . . . , h N T ) (h_1^T,h_2^T,...,h_N^T) (h1T,h2T,...,hNT)的方式排列,其中 H i j H_{ij} Hij表示第j个词在第i个文件中出现的次数, H H H的第j列 b j b_j bj表示第j个单词在每个文件中出现的次数
提取 b j b_j bj,计算 g ⃗ = b ⃗ 1 ⃗ T b ⃗ \vec{g}=\frac{\vec{b}}{\vec{1}^T\vec{b}} g=1Tbb,具有相近出现方式的词的 g ⃗ \vec{g} g应该相似。对它们使用k均值聚类划分为k组,在同一组中的单词倾向于在同一个文本中出现
实验方法
同样使用python的scipy库提供的聚类工具进行聚类。但是在聚类时需要将词向量矩阵转置。
之后遍历每个关键词的分组结果,将每个关键词放入对应分类序号的数组
结果分析
我们使用10条Amazon上婴儿摇篮pacifier的销售评论数据作为数据集进行k=10的词聚类。
图为部分结果
量化评估
实验原理
利用每个文本的词向量预测一个数值(比如通过评论关键词预测打分)。使用多元回归方式进行并计算RMS。对于预测准确度的评估,使用混淆矩阵来进行
目标函数:
RMS计算式:
λ \lambda λ为人为规定的正则化系数
实验方法
同样使用任意残差回归的原理进行,只是不再根据星级对评论进行二分类
在不同的λ下计算RMS
y_train=sr[:train_length]
h_train=tfidf[:train_length]
y_test=sr[train_length:]
h_test=tfidf[train_length:]
#划分训练集和测试集
A=np.zeros((key_num,key_num))
b=np.zeros((key_num,))
lamb=np.linspace(0.1,10,50) #生成λ
rmss=[]
count=0
for l in np.nditer(lamb):
for j in range(0,key_num):
for i in range(0,key_num):
if i==j:
A[j][i]+=np.sum(h_train[...,i]*h_train[...,i])+l
else:
A[j][i]+=np.sum(h_train[...,i]*h_train[...,j])
b[j]+=np.sum(h_train[...,j]*y_train)
#生成每个λ对应的系数矩阵和常数项向量
omega = linalg.solve(A, b) #解线性方程组
r_test=np.sum(h_test*omega,axis=1)
rms=np.sqrt(np.sum((y_test-r_test)**2)/y_test.shape[0])
rmss.append(rms)
#计算RMS
plt.plot(lamb,rmss)
plt.show()
列出RMS最小时的λ对应的混淆矩阵
key_num=len(feature_name)
train_length=200
y_train=sr[:train_length]
h_train=tfidf[:train_length]
y_test=sr[train_length:]
h_test=tfidf[train_length:]
A=np.zeros((key_num,key_num))
b=np.zeros((key_num,))
mat=np.zeros((5,5))
for j in range(0,key_num):
for i in range(0,key_num):
if i==j:
A[j][i]+=np.sum(h_train[...,i]*h_train[...,i])+l
else:
A[j][i]+=np.sum(h_train[...,i]*h_train[...,j])
b[j]+=np.sum(h_train[...,j]*y_train)
omega = linalg.solve(A, b)
r_test=np.sum(h_test*omega,axis=1)
y_test+=2
r_test+=2
r_test=np.rint(r_test)
r_test=np.where(r_test>4,4,r_test)
r_test=np.where(r_test<0,0,r_test)
for i in range(0,y_test.shape[0]):
mat[y_test[i]][int(r_test[i])]+=1
结果分析
横轴为λ取值,纵轴为RMS。可见,对于给定数据集,当λ=2时,RMS最小。
λ=2时,混淆矩阵如图: