【非参】python实现KNN回归估计

本文目录如下:

一、KNN 回归原理

方式一

方式二

二、python 代码实现

KNN 回归算法

算法测试

参考资料

一、KNN 回归原理

方式一

在非参数模型中,核回归估计可以看做是响应变量 周围固定范围内的一个带权均值估计,这个固定范围由带宽(bandwith)参数 控制。KNN 估计则是与待估计的 最近的 个 的带权均值,公式如下:

其中,权重 定义为:

k 最近邻集合 定义为:

是的个最近邻观测点

如何定义与 最近的 个 呢?每个响应变量 都有其对应的坐标 ,待估计的 的值我们可能不知道,但我们可以知道他的坐标 ,然后根据距离公式计算出与 最近的几个 ,进而得知与 最近的 个 ,然后计算出 的 k 近邻估计值 。

如果数据分布较为稀疏,k 个最近邻会离估计点 非常远,得到的估计值偏差较大,此时一般不用 k 近邻估计。

观察权重计算公式,k 近邻估计在 内的权值是固定的,可以提取出来,即:

此时可以看做是k 个最邻近的 的均值,相当于是不带权重的

方式二

换个角度,k 最近邻集合 是在变化的,故我们可以看成是 k 近邻估计有一个均匀核函数,他的带宽在变,但是带宽内的值固定不变,即带宽内的每个邻居 的权重相同且不变。于是,我们可以换个思路,用距离替代核函数,离估计点 越近的 的权重就越大,对应离 越近的点 的权重就越大,这也是核回归的思想。改写后的 k 近邻估计计算公式为:

其中 即为核函数(实际上是距离计算公式的变形),计算各个 到中心点 的距离; 为一个小数,如 0.00001,防止除数为 0。距离计算公式有非常多种,这里只列出简单的两种:

欧式距离:曼哈顿距离:

计算距离时要注意不同特征的量纲差异(比如年龄和薪资),若不同特征之间量纲差异较大,则最好先对特征进行标准化或归一化。

方式一(权重固定,近似看成不带权重)为参考资料[1]上的版本,经过理论验证,算法较为稳健;方式二(带不同权重)是根据参考资料[1]的理论延伸,还未经理论推导验证,在低维数据上表现尚可,高维数据上表现欠佳。

二、python 代码实现

KNN 回归算法

class KNNRegressor:

    

    K近邻回归估计算法。

    

    def __init__(self, k:int, eps=1e-5):

        self.k = k  # k为最相邻的邻居个数        self.eps = eps  # 一个小数,用来防止除数为0.

    def fit(self, X, Y):

        

        传入训练数据集。

        

        assert len(X) == len(Y)

        self.X = np.asarray(X)

        self.Y = np.asarray(Y)

    def Euclidean_distance(self, x, x_all):

        

        一个点x与所有点x_all的欧几里得距离。$d = \sqrt{(x_2 - x_1)^2 + (y_2-y_1)^2}$

        

        x, x_all = np.asarray(x), np.asarray(x_all)

        if x_all.ndim < 2: #低维            return

 np.sqrt((x - x_all)**2)

        else#高维            return

 np.sqrt(np.sum((x - x_all)**2, axis=1))

    def predict(self, x, type

=2):

        

        预测函数。

        Args:

        x: 待估计的点。

        type: 预测类型。1:不带权重;2:带权重。

        Return:

        result: x对应的y的估计结果。

        

        x = np.array(x)

        result = []

        for sample in

 x:

            distance = self.Euclidean_distance(sample, self.X)

            index = distance.argsort()[:self.k]

            if type

 == 1:

                result.append(np.mean(self.Y[index]))

            elif type

 == 2:

                weight = (1 / (distance[index] + self.eps)) / np.sum(1 / (distance[index] + self.eps))

                result.append(np.sum(weight * self.Y[index]))

            else

:

                print(type输入错误!1:不带权重;2:带权重。

)

                break        return

 np.array(result)

算法测试

import numpy as np

import matplotlib.pyplot as plt

# 生成随机数据

n = 100

x = np.linspace(0, 1, n)

print(x.shape = 

, x.shape)

np.random.seed(0)

e = np.random.normal(0, 0.6, n)

# e = e[:, np.newaxis]print(e.shape = 

, e.shape)

y = 6 * np.sin(np.pi*x) + e

y_true = 6 * np.sin(np.pi*x)

print(y.shape = 

, y.shape)

# 一维数据

k = 5

knn = KNNRegressor(k)

knn.fit(x, y)

y_knn_1 = knn.predict(x, type

=1)

y_knn_2 = knn.predict(x, type

=2)

# 画图

plt.figure(figsize=(12, 16), dpi=60)

plt.rc(font, family=Times New Roman, weight = medium, size=17)  #设置英文字体# plt.rcParams[font.sans-serif] = [SimHei] # 黑体# plt.rcParams[axes.unicode_minus] = False   # 解决无法显示符号的问题

ax = plt.subplot(2, 1, 1)

ax.scatter(x, y, c=k, label=$Simulation$

)

ax.plot(x, y_true, lw=2, label=$Truth$

)

ax.plot(x, y_knn_1, lw=3, label=$\hat{m}_k (x)$

)

ax.set_xlabel(x

)

ax.set_ylabel(y

)

ax.set_title(fKNN regression without weights, k = {k}

)

ax.legend()

ax2 = plt.subplot(2, 1, 2)

ax2.scatter(x, y, c=k, label=$Simulation$

)

ax2.plot(x, y_true, lw=2, label=$Truth$

)

ax2.plot(x, y_knn_2, lw=3, label=$\hat{m}_k (x)$

)

ax2.set_xlabel(x

)

ax2.set_ylabel(y

)

ax2.set_title(fKNN regression with weights, k = {k}

)

ax2.legend()

plt.show()

# plt.savefig(KNN回归.png)

参考资料

[1] Härdle W, Müller M, Sperlich S, et al. Nonparametric and semiparametric models[M]. Berlin: Springer, 2004.

>[2]几种常见的距离计算公式

>[3]【知乎】knn算法中k是 怎么决定的?

>[4]python实现knn回归算法