Python是实现的人工智能实验之KNN回归任务

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=1Kd(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(XiX)2i=1n(YiY)2 i=1n(XiX)(YiY)
本题中的结果有六个概率值。先分别计算六个维度上的真实概率值和预测概率值的相关系数,然后对六个维度取平均,计算得到最终相关系数作为判断依据。相关系数的绝对值越大,预测值和真实值的线性相关程度就越好。


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的矩阵的代码和上述内容相同,不再重复展示。

之后开始测试,以验证集为例。声明下面的函数并进行相关数据的初始化:

扫描二维码关注公众号,回复: 14214175 查看本文章
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. 实验结果及分析

同样对Kp进行调整,具体结果如下:

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. 为什么计算回归值时使用距离的倒数?

    因为距离越大,则当前训练样本与测试样本的相似性越小,需要相应地减小当前训练样本对预测结果的影响。取倒数的话,距离越大,倒数值越小,从而计算时对结果的影响越小。

  2. 如果要求得到的每一种标签的概率的和等于1,应该怎么处理?

    将每个标签值除以所有标签值的总和。

猜你喜欢

转载自blog.csdn.net/newlw/article/details/125068357
今日推荐