EE103(4):文本线性处理

EE103课程所有应用代码在这里

预处理

实验原理

词向量

维护一个包含了n个不同单词的词向量

每个词在文件中的出现次数用词向量\vec{a}来储存,a_i表示词i在文本中出现的次数

\vec{h}=\frac{\vec{a}}{\vec{1}^T\vec{a}}词频向量,或者直方图向量

语料库矩阵

收集N个文本,每个文本计算其直方图词向量,以(h_1^T,h_2^T,...,h_N^T)的方式排列,其中H_{ij}表示第j个词在第i个文件中出现的次数,H的第j列b_j表示第j个单词在每个文件中出现的次数

预处理

  1. 保留词干
  2. 停用词处理:去除很短的词(the、is、at)、意义较小的词(what、this、how)、极端不常见的词
  3. 去除标点符号
  4. TF-IDFbi-grams
TF-IDF

计算TF=\frac{某个词在文本中出现的次数}{该文本中包含的总词数}IDF=log\frac{总文本数}{包含该词的文本数},再计算其乘积。乘积越大,代表词汇包含的信息量越大,则直方图向量中的权重越大

实验方法

使用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||

两个文本的角度:\angle (h_1,h_2)

两个文本越相似,以上两值应该越小

实验方法

使用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值表示要得到的簇的个数,质心表示每个簇的均值向量(即向量各维取平均),距离量度常用欧几里得距离和余弦相似度

  1. 首先确定一个k值,即我们希望将数据集经过聚类得到k个集合

  2. 从数据集中随机选择k个数据点作为质心

  3. 对数据集中每一个点,计算其与每一个质心的距离(如欧式距离),离哪个质心近,就划分到那个质心所属的集合

  4. 把所有数据归好集合后,一共有k个集合。然后重新计算每个集合的质心

  5. 如果新计算出来的质心和上一次计算的质心之间的距离小于某一个设置的阈值(表示重新计算的质心的位置变化不大,趋于稳定,或者说收敛),我们可以认为聚类已经达到期望的结果,算法终止,否则回到3

最小二乘二分类

利用词向量对文本进行二分类,并用测试集上的错误率来评价分类效果

为每个文本添加标签,若属于一个分类则标签为1,属于另一个分类则标签为-1,得到分类向量\vec{y}

训练回归模型
在这里插入图片描述

对于新输入的词向量得出的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后解线性方程组的方法进行

  1. 对所有的评论数据,通过评论自带的星级,以3为界分为好评和差评

  2. 将数据分为训练集和测试集,使用训练集列线性方程组解出\omega每个位上的值

  3. 在测试集上测试分类效果,计算预测准确与不准确的概率

  4. 改变λ取值,重复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_{ij}表示第j个词在第i个文件中出现的次数,H的第j列b_j表示第j个单词在每个文件中出现的次数

提取b_j,计算\vec{g}=\frac{\vec{b}}{\vec{1}^T\vec{b}},具有相近出现方式的词的\vec{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时,混淆矩阵如图:


在这里插入图片描述
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容