KNN算法 0基础小白也能懂(附代码)

KNN算法 0基础小白也能懂(附代码)

原文链接

1.K近邻是啥

1968年,Cover 和 Hart 提出了最初的近邻法,思路是——未知的豆离哪种豆最近,就认为未知豆和该豆是同一种类。

近邻算法的定义:为了判定未知样本的类别,以全部训练样本作为代表点计算未知样本与所有训练样本的距离,并以最近邻者的类别作为决策未知样本类别的唯一依据。

说人话就是,看这玩意离哪个东西距离最近,越近越像。

最近邻算法的缺陷是对噪声数据过于敏感。从图中可以得到,一个圈起来的蓝点和两个圈起来的红点到绿点的距离是相等的,根据最近邻算法,该点的形状无法判断。其实也就是东西太多太杂的话判断不清

为了解决这个问题,我们可以把位置样本周边的多个最近样本计算在内,扩大参与决策的样本量,以避免个别数据直接决定决策结果。也就是增大数据量减少误差。

引进K近邻算法——选择未知样本一定范围内确定个数的K个样本,该K个样本大多数属于某一类型,则未知样本判定为该类型。K近邻算法是最近邻算法的一个延伸。根据K近邻算法,离绿点最近的三个点中有两个是红点,一个是蓝点,红点的样本数量多于蓝点的样本数量,因此绿点的类别被判定为红点。

2.KNN算法步骤

一般来说,只选择样本数据集中前N个最相似的数据.K一般不大于20,最后,选择K个中出现次数最多的分类,作为新数据的分类。

那K值到底如何选择呢?

  1. 数据特性
    噪声与非相关特征:如果数据中存在较多噪声或非相关特征,较大的K值可以平滑分类结果,减小噪声的影响。然而,这也可能导致类别之间的界限变得模糊,特别是在数据分布复杂或类别间存在明显界限的情况下。
    特征选择与缩放:为减小噪声和非相关特征的影响,可以通过特征选择和特征缩放来优化输入数据。例如,利用进化算法或互信息进行特征选择,从而提高KNN算法的性能。

  2. 奇数K值
    避免平票:在二元分类问题中,选择奇数的K值有助于避免分类器投票时出现平票的情况,从而提高分类器的确定性。这一点在K值较小、数据分布相对均匀时尤为重要。

  3. 超参数优化
    启发式方法:K值的选择可以通过各种启发式技术来优化。交叉验证(Cross-validation)是一种常用的方法,它通过将数据集划分为训练集和验证集,尝试不同的K值,并选择在验证集上表现最佳的K值。
    自助法(Bootstrap):在二元分类问题中,自助法可以用于评估不同K值的性能,并帮助选择最佳的K值。自助法通过多次重复采样训练集,计算每次采样的分类准确率,从而估计K值的期望性能。(从原始数据集中随机有放回地抽样,生成多个新的子数据集。每个子数据集都与原始数据集大小相同,但是由于是有放回抽样,因此某些样本可能会在一个子数据集中出现多次,而另一些样本可能根本没有出现(OOB)。)

3.实战实现KNN算法

3.1 背景

假如一套房子打算出租,但不知道市场价格,可以根据房子的规格(面积、房间数量、厕所数量、容纳人数等),在已有数据集中查找相似(K近邻)规格的房子价格,看别人的相同或相似户型租了多少钱。

数据集在这,CardioGoodFitness 数据集 提取码:show

3.2数据分类

已知的数据集中,每个已出租住房都有房间数量、厕所数量、容纳人数等字段,并有对应出租价格。将预计出租房子数据与数据集中每条记录比较计算欧式距离(坐标系里的距离),取出距离最小的5条记录,将其价格取平均值,可以将其看做预计出租房子的市场平均价格。

import pandas as pd
import numpy as np
from scipy.spatial import distance#用于计算欧式距离
from sklearn.preprocessing import StandardScaler#用于对数据进行标准化操作
from sklearn.neighbors import KNeighborsRegressor#KNN算法
from sklearn.metrics import mean_squared_error#用于计算均方根误差

上面导入包的作用原理会在后面一一指出,先导入数据并提取目标字段

#导入数据并提取目标字段
path = r'rent_price.csv'
file = open(path, encoding = 'gb18030', errors = 'ignore')
dc_listings = pd.read_csv(file)
features = ['accommodates','bedrooms','bathrooms','beds','price','minimum_nights','maximum_nights','number_of_reviews']
dc_listings = dc_listings[features]

dc_listings长下面这样

3.3 初步数据清洗

数据集中非数值类型的字段需要转换,替换掉美元$符号和千分位逗号

#数据初步清洗
our_acc_value = 3
dc_listings['distance'] = np.abs(dc_listings.accommodates - our_acc_value)
dc_listings = dc_listings.sample(frac=1, random_state=0) #抽取 100% 的样本重排
dc_listings = dc_listings.sort_values('distance')
dc_listings['price'] = dc_listings['price'].str.replace(r'[\$,]', '', regex=True).astype(float)
dc_listings = dc_listings.dropna() #删除包含空值(NaN)的行

理想情况下,数据集中每个字段取值范围都相同,但实际上这是几乎不可能的,如果计算时直接用原数数据计算,则会造成较大训练误差,所以需要对各列数据进行标准化或归一化操作,尽量减少不必要的训练误差。归一化的目的就是使得预处理的数据被限定在一定的范围内(比如[0,1]或者[-1,1]),从而消除奇异样本数据导致的不良影响。

#数据标准化
dc_listings[features] = StandardScaler().fit_transform(dc_listings[features]) #都变成标准正态分布
normalized_listings = dc_listings

最好不要将所有数据全部拿来测试,需要分出训练集和测试集具体划分比例按数据集确定。

#取得训练集和测试集
norm_train_df = normalized_listings[:2792]
norm_test_df = normalized_listings[2792:]

3.4 计算欧式距离并预测房屋价格

#scipy包distance模块计算欧式距离
first_listings = normalized_listings.iloc[0][['accommodates', 'bathrooms']]
fifth_listings = normalized_listings.iloc[20][['accommodates', 'bathrooms']]
#用python方法做多变量KNN模型
def predict_price_multivariate(new_listing_value, feature_columns):
    temp_df = norm_train_df
    #distance.cdist计算两个集合的距离
    temp_df['distance'] = distance.cdist(temp_df[feature_columns], [new_listing_value[feature_columns]])
    temp_df = temp_df.sort_values('distance')#temp_df按distance排序
    knn_5 = temp_df.price.iloc[:5] #选择距离最近的前5个样本
    predicted_price = knn_5.mean()
    return predicted_price
cols = ['accommodates', 'bathrooms']
norm_test_df['predicted_price'] = norm_test_df[cols].apply(predict_price_multivariate, feature_columns=cols, axis=1)
norm_test_df['squared_error'] = (norm_test_df['predicted_price'] - norm_test_df['price']) ** 2
mse = norm_test_df['squared_error'].mean()
rmse = mse ** (1/2)
print(rmse)

#利用sklearn完成KNN
col = ['accommodates', 'bedrooms']
knn = KNeighborsRegressor()
#将自变量和因变量放入模型训练,并用测试数据测试
knn.fit(norm_train_df[cols], norm_train_df['price'])
two_features_predictions = knn.predict(norm_test_df[cols])
#计算预测值与实际值的均方根误差
two_features_mse = mean_squared_error(norm_test_df['price'], two_features_predictions)
two_features_rmse = two_features_mse ** (1/2)
print(two_features_rmse)

输出为

1.4667825805653032
......(一堆报错,表示你正在对一个可能是原 DataFrame 的切片的数据进行修改,不过不影响结果)
1.5356457412450537

总结:K近邻算法的核心要素

K的大小

在实际的应用中,一般采用一个比较小的K值。并采用交叉验证的方法,选取一个最优的K值。比如在之前的代码中,手动实现的K值选的就是5,同时sklearn包里K值默认也是5.

距离度量准则

  1. 欧氏距离(Euclidean Distance)
    欧氏距离是最常用的距离度量准则之一,适用于连续型变量。它表示两个点之间的“直线”距离。
    \(d(p,q)=\sqrt {\sum_{i=1}^{n}(p_i-q_i)^2}\)
    优点:简单易懂,计算方便。
    缺点:对特征尺度敏感,需要进行特征缩放(如标准化)。

  2. 曼哈顿距离(Manhattan Distance)
    曼哈顿距离,也称为“城市街区距离”或“L1距离”,表示两个点在各维度上的绝对差值的和。
    \(d(p,q)=\sum_{i=1}^{n}|p_i-q_i|\)
    优点:对特征缩放不太敏感,适用于高维空间。
    缺点:不能反映“直线”距离,可能导致某些情况下误差较大。

  3. 切比雪夫距离(Chebyshev Distance)
    切比雪夫距离,也称为L∞距离,表示两个点之间在所有坐标轴上最大差值的距离。
    \(d(p,q)=max_i|p_i-q_i|\)
    优点:适用于棋盘格状的网格空间,特别适合某些特殊情况下的度量。
    缺点:在某些应用中可能不够精确。

posted @ 2024-08-26 17:38  Mephostopheles  阅读(3)  评论(0编辑  收藏  举报