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

目录

  • 一、KNN回归原理
  • 二、python代码实现
      • KNN回归算法
      • 算法测试
  • 参考资料

一、KNN回归原理

在非参数模型中,核回归估计可以看做是响应变量 Y Y Y周围固定范围内的一个带权均值估计,这个固定范围由带宽(bandwith)参数 h h h控制。KNN估计则是与待估计的 Y i Y_i Yi最近的 k k k Y Y Y的带权均值,公式如下:
m ^ k ( x ) = 1 n ∑ i = 1 n W k i ( x ) Y i \hat{m}_k (x) = \frac{1}{n} \sum_{i=1}^{n}W_{ki}(x)Y_i m^k(x)=n1i=1nWki(x)Yi
其中,权重 W k i ( x ) W_{ki}(x) Wki(x)定义为:
W k i ( x ) = { n k i f i ∈ J x 0 o t h e r w i s e W_{ki}(x) = \begin{cases} \frac{n}{k} & if \quad i\in J_x \\ 0 & otherwise \end{cases} Wki(x)={kn0ifiJxotherwise
k最近邻集合 J x J_x Jx定义为:
J x = i : X i 是 x 的 k 个 最 近 邻 观 测 点 J_x={i:X_i 是x的k个最近邻观测点} Jx=i:Xixk

如何定义与 Y i Y_i Yi最近的 k k k Y Y Y呢?每个响应变量 Y Y Y都有其对应的坐标 x x x,待估计的 Y i Y_i Yi的值我们可能不知道,但我们可以知道他的坐标 x i x_i xi,然后根据距离公式计算出与 x i x_i xi最近的几个 x x x,进而得知与 Y i Y_i Yi最近的 k k k Y Y Y,然后计算出 Y i Y_i Yi的k近邻估计值$\hat{m}_k (x) $。

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

观察权重计算公式,k近邻估计在 J x J_x Jx内的权值是固定的,可以提取出来,即:
m ^ k ( x ) = 1 n × n k ∑ i = 1 n Y i = 1 k ∑ i = 1 n Y i \hat{m}_k (x) = \frac{1}{n} \times \frac{n}{k} \sum_{i=1}^{n}Y_i = \frac{1}{k} \sum_{i=1}^{n}Y_i m^k(x)=n1×kni=1nYi=k1i=1nYi
此时可以看做是k个最邻近的 Y Y Y的均值,相当于是不带权重的

换个角度,k最近邻集合 J x J_x Jx是在变化的,故我们可以看成是k近邻估计有一个均匀核函数,他的带宽在变,但是带宽内的值固定不变,即带宽内的每个邻居 Y Y Y的权重相同且不变。于是,我们可以换个思路,用距离替代核函数,离估计点 x i x_i xi越近的 x x x的权重就越大,对应离 Y i Y_i Yi越近的点 Y Y Y的权重就越大,这也是核回归的思想。改写后的k近邻估计计算公式为:
m ^ k ( x ) = ∑ i = 1 n K R ( x − X i ) Y i ∑ i = 1 n K R ( x − X i ) = ∑ i = 1 k 1 d i s t a n c e ( X i , X ) + e p s ∑ i = 1 n 1 d i s t a n c e ( X i , X ) + e p s Y i \hat{m}_k (x) = \frac{\sum_{i=1}^{n}K_R (x-X_i)Y_i}{\sum_{i=1}^{n}K_R (x-X_i)} = \sum_{i=1}^{k} \frac{\frac{1}{distance(X_i, X) + eps}} {\sum_{i=1}^n \frac{1}{distance(X_i, X)+eps}}Y_i m^k(x)=i=1nKR(xXi)i=1nKR(xXi)Yi=i=1ki=1ndistance(Xi,X)+eps1distance(Xi,X)+eps1Yi

其中 K R ( x − X i ) K_R (x-X_i) KR(xXi)即为核函数(实际上是距离计算公式的变形),计算各个 x x x到中心点 X i X_i Xi的距离; e p s eps eps为一个小数,如0.00001,防止除数为0。距离计算公式有非常多种,这里只列出简单的两种:

  • 欧式距离: d = ( x 2 − x 1 ) 2 + ( y 2 − y 1 ) 2 d = \sqrt{(x_2 - x_1)^2 + (y_2-y_1)^2} d=(x2x1)2+(y2y1)2
  • 曼哈顿距离: d = ∣ x 2 − x 1 ∣ + ∣ y 2 − y 1 ∣ d = |x_2 - x_1| + |y_2 - y_1| d=x2x1+y2y1

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

二、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(f'KNN 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(f'KNN regression with weights, k = {k}')
ax2.legend()

plt.show()
# plt.savefig('KNN回归.png')

【非参】python实现KNN回归估计_第1张图片

参考资料

[1] Härdle W, Müller M, Sperlich S, et al. Nonparametric and semiparametric models[M]. Berlin: Springer, 2004.
[2] 几种常见的距离计算公式
[3] 【知乎】KNN算法中K是 怎么决定的?

你可能感兴趣的:(非参数模型,python)