1. 算法原理
在本设计中有六项数值需要进行回归预测,anger, disgust, fear, joy, sad, surprise的数值作为样本的结果。同样,首先将训练样本进行TF-IDF编码后进行预测。并依据上一题中详细解释过的KNN算法和Lp算法得到K个与测试样本距离最近的训练样本。但是与分类任务不同的是,回归任务不能通过投票预测结果,而是需要另外的算法得到回归结果:
假设通过KNN算法得出了K个与测试样本最相近的训练样本,分别记为 t r a i n 1 , t r a i n 2 , . . . , t r i a n K train_1,train_2,...,trian_K train1,train2,...,trianK,用 d ( t r a i n 1 , t e s t ) d(train_1,test) d(train1,test)表示 t r a i n 1 train_1 train1和测试样本之间的Lp距离, p r o b prob prob表示某个回归值,则有:
p r o b ( t e s t ) = ∑ k = 1 K p r o b ( t r a i n k ) d ( t r a i n k , t e s t ) \displaystyle prob(test)=\sum_{k=1}^K\frac{prob(train_k)}{d(train_k,test)} prob(test)=k=1∑Kd(traink,test)prob(traink)
即测试样本的某项回归值等于K
个训练样本的回归值除以该训练样本与测试样本之间的Lp距离之商的和。
这样得出来的结果之和可能不为1,而依据题目要求,六种概率之和必须为1,因此可以对六种数据做以下处理:
p o r b i = p r o b i ∑ j = 1 6 p r o b j \displaystyle porb_i=\frac{prob_i}{\sum^6_{j=1}prob_j} porbi=∑j=16probjprobi
也就是每个概率都除以六个概率之和,本质上是对六种概率进行相同的线性变化,使得六者的和相加为1。
与分类问题不同的是,回归问题的预测结果不可能和实际结果完全准确,为了使得结果更加精确,引入下面的指标相关系数作为判断预测结果和实际结果差距的依据。
C O R ( X , Y ) = c o v ( X , Y ) σ X σ Y = ∑ i = 1 n ( X i − X ‾ ) ( Y i − Y ‾ ) ∑ i = 1 n ( X i − X ‾ ) 2 ∑ i = 1 n ( Y i − Y ‾ ) 2 COR(X,Y)=\frac{cov(X,Y)}{\sigma_X\sigma_Y}=\frac{\sum^n_{i=1}(X_i-\overline X)(Y_i-\overline Y)}{\sqrt{\sum^n_{i=1}(X_i-\overline X)^2\sum^n_{i=1}(Y_i-\overline Y)^2}} COR(X,Y)=σXσYcov(X,Y)=∑i=1n(Xi−X)2∑i=1n(Yi−Y)2∑i=1n(Xi−X)(Yi−Y)
本题中的结果有六个概率值。先分别计算六个维度上的真实概率值和预测概率值的相关系数,然后对六个维度取平均,计算得到最终相关系数作为判断依据。相关系数的绝对值越大,预测值和真实值的线性相关程度就越好。
2. 伪代码
首先创建TF-IDF矩阵,并且通过KNN算法和Lp距离找出和测试样本最近的K
个训练样本。和之前的一样,不再重复说明。
找到K
个训练样本后,根据这些样本的标签对测试样本的回归值进行预测:
for 每种要预测的概率 i
i = 0
for eachSentence in KNN最近邻
i = i + eachSentence的i值 / eachSentence的Lp距离
end
end
得到了每个要预测的回归值后,还需要将这些值归一化,即使得这些概率的和为1。
每种概率值 = 每种概率值 / 所有概率值之和
只要将每种概率值除以所有概率之和,这时所有概率相加为:原来的所有概率之和/原来的所有概率之和=1。
3. 代码展示
3.1 测试流程
生成TF-IDF的矩阵的代码和上述内容相同,不再重复展示。
之后开始测试,以验证集为例。声明下面的函数并进行相关数据的初始化:
def valid(tf_idf, idf, emt, sentenceCount):
#整个验证集预测的6个概率
predictAnger = []
predictDisgust = []
predictFear = []
predictJoy = []
predictSad = []
predictSurprise = []
#整个验证集实际的6个概率
trueAnger = []
trueDisgust = []
trueFear = []
trueJoy = []
trueSad = []
trueSurprise = []
接下来打开验证集文件。
with open("validation_set.csv","r")as validSet:
for eachLine in validSet:
s = eachLine.split(',') # 依据逗号拆分每个验证样本
sentence = s[0].split(' ') # 验证样本的文档
s[-1] = s[-1][0:-1] # 将最后的回车去掉
emotion = s[1:7] # 文档对应的6种概率
if emotion[0] == 'anger': # 排除测试集的第一行
continue
for i in range(0, 6):
emotion[i] = float(emotion[i]) # 将概率大小由字符串转为浮点数
得到了文档对应的实际的六种概率则可以加入上述列表中:
trueAnger.append(emotion[0])
trueDisgust.append(emotion[1])
trueFear.append(emotion[2])
trueJoy.append(emotion[3])
trueSad.append((emotion[4]))
trueSurprise.append(emotion[5])
接着是对K
个最近邻的查找,通过findSimSentence
函数完成。得到后调用predict
函数得到六种预测的概率,再调用standard
函数将六个概率标准化,即使得六种概率之和为1。这几个函数的详细代码会在之后讨论。其中findSimSentence
和第二题完全相同,不再展示代码。需要注意的是,为了防止之后出现计算概率时除以0的情况,距离diff
需要设置最小值。
simSentence = {
}
findSimSentence(sentenceCount, tf_idf, idf, sentence ,simSentence, K) #查找最近邻
predictEmt = predict(simSentence, emt) # 依据最近邻预测概率
standard(predictEmt) # 将概率标准化
得到了预测的六种概率之后,也分别加入对应的列表中。
predictAnger.append(predictEmt[0])
predictDisgust.append(predictEmt[1])
predictFear.append(predictEmt[2])
predictJoy.append((predictEmt[3]))
predictSad.append(predictEmt[4])
predictSurprise.append(predictEmt[5])
有了所有验证样本的实际结果和预测结果组成的六个向量(列表)后,调用cor
函数计算六个相关系数,并求平均值输出。
print((cor(predictAnger,trueAnger)+cor(predictDisgust,trueDisgust)+cor(predictFear,trueFear)+cor(predictJoy,trueJoy)+cor(predictSad,trueSad)+cor(predictSurprise,trueSurprise))/6)
3.2 概率值的计算
计算出K个最近邻后,调用以下函数计算六种概率的预测值:
def predict(simSentence, emt):
predictEmt = [0 for i in range(0,6)] # 大小为6的向量,分别对应六种情绪的预测概率值
for i in range(0,6):
for eachSentence in simSentence.keys():
# 概率预测值 = 所有最近邻的概率/Lp距离之和
predictEmt[i] = predictEmt[i] + emt[eachSentence][i]/simSentence[eachSentence]
return predictEmt
得到六种概率的预测值后,调用下面的函数将其标准化:
def standard(predictEmt):
total = sum(predictEmt)
for i in range(0,6):
predictEmt[i] /= total
求得所有概率值之和,并将所有概率除以该值即可。
3.3 相关系数的计算
def cor(x, y):
avgX = sum(x)/len(x) # x的平均值
avgY = sum(y)/len(y) # y的平均值
tmp1 = 0.0 # tmp1用于计算相关系数的分母
tmp2 = 0.0 # tmp2用于计算x的标准差
tmp3 = 0.0 # tmp3用于计算y的标准差
for i in range(0,len(x)):
# 依据公式计算变量值
tmp1 = tmp1 + (x[i]-avgX)*(y[i]-avgY)
tmp2 = tmp2 + (x[i]-avgX)*(x[i]-avgX)
tmp3 = tmp3 + (y[i]-avgY)*(y[i]-avgY)
tmp1 = abs(tmp1)
return tmp1/math.sqrt(tmp2*tmp3)
4. 实验结果及分析
同样对K
和p
进行调整,具体结果如下:
当p=2
时,对K调优:
K值 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
相关系数 | 0.3070 | 0.3445 | 0.3544 | 0.3562 | 0.3540 | 0.3709 | 0.3621 | 0.3535 | 0.3472 | 0.3516 |
K值 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
---|---|---|---|---|---|---|---|---|---|---|
相关系数 | 0.3402 | 0.3316 | 0.3214 | 0.3228 | 0.3156 | 0.3030 | 0.3004 | 0.2919 | 0.2950 | 0.2996 |
可以看出,在K
取6的时候相关系数达到最大值。
当K=6
时,对p
进行调优:
p值 | 1 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | ∞ |
---|---|---|---|---|---|---|---|---|---|---|
相关系数 | 0.3100 | 0.3709 | 0.3739 | 0.3827 | 0.3866 | 0.3883 | 0.3912 | 0.3907 | 0.3856 | 0.1916 |
p
取12的时候相关系数最大。
输出的结果的部分如下:
可以看到,数据都在0到1之间且6个概率之和为1。
5. 思考题
-
为什么计算回归值时使用距离的倒数?
因为距离越大,则当前训练样本与测试样本的相似性越小,需要相应地减小当前训练样本对预测结果的影响。取倒数的话,距离越大,倒数值越小,从而计算时对结果的影响越小。
-
如果要求得到的每一种标签的概率的和等于1,应该怎么处理?
将每个标签值除以所有标签值的总和。