K近邻算法概述
这是机器学习中的第一个算法,也是机器学习中的入门级别算法,它既可以做分类任务,也可以做回归任务。用官方的话来说,所谓K近邻算法,即是给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的K个实例(也就是上面所说的K个邻居), 这K个实例的多数属于某个类,就把该输入实例分类到这个类中。
K的含义:通俗地讲,就是对于一条新的数据,在训练集中去找K个距离最相近的数据进行预测
K 值的选择会对算法的结果产生重大影响。K值较小意味着只有与输入实例较近的训练实例才会对预测结果起作用,但容易发生过拟合;如果 K 值较大,优点是可以减少学习的估计误差,但缺点是学习的近似误差增大,这时与输入实例较远的训练实例也会对预测起作用,使预测发生错误。在实际应用中,K 值一般选择一个较小的数值,通常采用交叉验证的方法来选择最优的 K 值。随着训练实例数目趋向于无穷和 K=1 时,误差率不会超过贝叶斯误差率的2倍,如果K也趋向于无穷,则误差率趋向于贝叶斯误差率。
实战练习
本文将以房价预测任务作为实战练习
#导入数据
import pandas as pd
#指定features,截取整个数据包的部分特征
features = ['accommodates','bedrooms','bathrooms','beds','price','minimum_nights','maximum_nights','number_of_reviews']
#读取数据
dc_listings = pd.read_csv('listings.csv')
#筛选数据
dc_listings = dc_listings[features]
#打印数据的shape值
print(dc_listings.shape)
#观察数据是否准确
dc_listings.head()

数据特征:
- accommodates: 可以容纳的旅客
- bedrooms: 卧室的数量
- bathrooms: 厕所的数量
- beds: 床的数量
- price: 每晚的费用
- minimum_nights: 客人最少租了几天
- maximum_nights: 客人最多租了几天
- number_of_reviews: 评论的数量
任务需求
如果房东有一套3个房间的房子想要出租,能够租到多少钱呢?
import numpy as np
#定义新的变量
acc_value = 3
#计算此变量与所有数据中对应数据值的差值绝对值
dc_listings['distance'] = np.abs(dc_listings.bedrooms - acc_value)
#统计并排序
dc_listings.distance.value_counts().sort_index()
0.0 190
1.0 555
2.0 2554
3.0 400
5.0 2
7.0 1
Name: distance, dtype: int64
根据输出结果显示,和我们距离为0的(同样数量的房间)有190个
#打乱数据
dc_listings = dc_listings.sample(frac=1,random_state=0)
#对所有数据进行排序
dc_listings = dc_listings.sort_values('distance')
#打印前五条数据
dc_listings.price.head()
2732 $200.00
3683 $350.00
2010 $195.00
3355 $150.00
872 $600.00
Name: price, dtype: object
但是,上一步输出的数据是字符串格式,需要转换成float格式
#去掉"$",转换为float格式
dc_listings['price'] = dc_listings.price.str.replace("\$|,",'').astype(float)
#取前五行求均值
mean_price = dc_listings.price.iloc[:5].mean()
print(mean_price)
299.0
最终得到了平均价格 mean_price = 299.0,也就是我们的房子大致的价格了
模型的评估
首先需要切分数据集 — 将整个数据集切分为训练集、测试集
使用均方根误差公式 — Root Mean Squared Error (RMSE) 进行模型的评估
R
M
S
E
=
(
a
c
t
u
a
l
1
−
p
r
e
d
i
c
t
e
d
1
)
2
+
(
a
c
t
u
a
l
2
−
p
r
e
d
i
c
t
e
d
2
)
2
+
.
.
.
+
(
a
c
t
u
a
l
n
−
p
r
e
d
i
c
t
e
d
n
)
2
n
RMSE = \frac{\sqrt{(actual_1-predicted_1)^2+(actual_2-predicted_2)^2+...+(actual_n-predicted_n)^2}}{n}
RMSE=n(actual1−predicted1)2+(actual2−predicted2)2+...+(actualn−predictedn)2
#数据集划分
train_df = dc_listings.copy().iloc[:2792]
test_df = dc_listings.copy().iloc[2792:]
基于单变量预测价格函数:predict_price()
根据某一变量算出相对的差的绝对值并取5个最相近的数据,对这5个数据所对应的房价求平均值,得到测试集中的房价的预测值。
def predict_price(new_listing_value,feature_column):
temp_df = train_df
#新建distance 算绝对值
temp_df['distance'] = np.abs(dc_listings[feature_column] - new_listing_value)
#排序
temp_df = temp_df.sort_values('distance')
#取前五个数据
knn_5 = temp_df.price.iloc[:5]
#求均值
predicted_price = knn_5.mean()
return(predicted_price)
对测试集内的每条样本都进行同样的操作
test_df['predicted_price'] = test_df.bedrooms.apply(predict_price,feature_column='accommodates')
使用均方根误差公式进行结果衡量:
test_df['squared_error'] = (test_df['predicted_price'] - test_df['price'])**(2)
mse = test_df['squared_error'].mean()
rmse = mse ** (1/2)
print(rmse)
107.53357430392573
至此我们得到了对于一个变量的模型评估得分:107.53357430392573
那么,对于不同的变量,效果会不会不一样呢?
下面是对 accommodates,bedrooms,bathrooms,number_of_reviews 四种变量进行的评估
for feature in ['accommodates','bedrooms','bathrooms','number_of_reviews']:
test_df['predicted_price'] = test_df[feature].apply(predict_price,feature_column=feature)
test_df['squared_error'] = (test_df['predicted_price'] - test_df['price'])**(2)
mse = test_df['squared_error'].mean()
rmse = mse ** (1/2)
print("RMSE for the {} column: {}".format(feature,rmse))
RMSE for the accommodates column: 101.06532370839962
RMSE for the bedrooms column: 107.53357430392573
RMSE for the bathrooms column: 95.1642038415889
RMSE for the number_of_reviews column: 148.0541181862992
就这个结果而言,不同的变量,RMSE值的差距还是蛮大的,因此我们需要做数据预处理
数据预处理
对原始数据进行标准化的处理,standardization (或者叫做 Z-score normalization)
要求 均值 μ = 0 \mu = 0 μ=0 和标准差 σ = 1 \sigma = 1 σ=1
转换公式如下:
z
=
x
−
μ
σ
z = \frac{x - \mu}{\sigma}
z=σx−μ
这个意义是十分重大的,想象一下,我们经常通过梯度下降来进行优化求解,公式一般如下,如果特征之间的数值差异太大,那么更新的结果肯定也会产生较大的差异,这是我们所不希望的。在最开始的时候,我们认为特征之间的重要程度的是一样,并不想偏袒哪个特征,所以这部预处理工作必做!
Δ
w
j
=
−
η
∂
J
∂
w
j
=
η
∑
i
(
t
(
i
)
−
o
(
i
)
)
x
j
(
i
)
\Delta w_j = - \eta \frac{\partial J}{\partial w_j} = \eta \sum_i (t^{(i)} - o^{(i)})x^{(i)}_{j}
Δwj=−η∂wj∂J=ηi∑(t(i)−o(i))xj(i)
参数更新:
w
j
=
w
j
+
Δ
w
j
w_j = w_j + \Delta w_j
wj=wj+Δwj
使用Sklearn库的StandardScaler类进行数据预处理
import pandas as pd
from sklearn.preprocessing import StandardScaler
features = ['accommodates','bedrooms','bathrooms','beds','price','minimum_nights','maximum_nights','number_of_reviews']
dc_listings = pd.read_csv('listings.csv')
dc_listings = dc_listings[features]
#对 "price" 进行操作:去掉 $,并转换为float类型
dc_listings['price'] = dc_listings.price.str.replace("\$|,",'').astype(float)
#滤除缺失数据
dc_listings = dc_listings.dropna()
#使用sklearn库执行数据预处理,StandardScaler类是一个用来进行数据进行归一化和标准化的类
dc_listings[features] = StandardScaler().fit_transform(dc_listings[features])
normalized_listings = dc_listings
normalized_listings.head()

输出结果显示,我们所有的数据都已经进行了标准化的处理操作,各个属性数值之间的差异缩小
多变量KNN模型
在这里,我们从新建模
#数据集划分
norm_train_df = normalized_listings.copy().iloc[0:2792]
norm_test_df = normalized_listings.copy().iloc[2792:]
多变量的距离计算:
- 根据欧几里德距离公式计算
d = ( q 1 − p 1 ) 2 + ( q 2 − p 2 ) 2 + . . . + ( q n − p n ) 2 d = \sqrt{(q_1-p_1)^2+(q_2-p_2)^2+...+(q_n-p_n)^2} d=(q1−p1)2+(q2−p2)2+...+(qn−pn)2 - 使用scipy中现成的距离的计算工具:
from scipy.spatial import distance
first_listing = normalized_listings.iloc[0][['accommodates', 'bathrooms']]
fifth_listing = normalized_listings.iloc[20][['accommodates', 'bathrooms']]
#调用distance.euclidean()函数计算欧几里德距离
first_fifth_distance = distance.euclidean(first_listing, fifth_listing)
print("The first_listing :{}".format(first_listing))
print("The fifth_listing :{}".format(fifth_listing))
print("The euclidean distance :{}".format(first_fifth_distance))
The first_listing :accommodates 0.401420
bathrooms -0.439211
Name: 0, dtype: float64
The fifth_listing :accommodates 1.898489
bathrooms 2.969551
Name: 22, dtype: float64
The euclidean distance :3.723019604017032
多变量KNN模型
from scipy.spatial import distance
def predict_price_multivariate(new_listing_value,feature_columns):
temp_df = norm_train_df
temp_df['distance'] = distance.cdist(temp_df[feature_columns],[new_listing_value[feature_columns]])
temp_df = temp_df.sort_values('distance')
knn_5 = temp_df.price.iloc[: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("The root mean squared error :{}".format(rmse))
The root mean squared error :0.7894063922577537
使用Sklearn来完成KNN
from sklearn.neighbors import KNeighborsRegressor
from sklearn.metrics import mean_squared_error
#选择指定的变量
cols = ['accommodates','bedrooms']
#将KNN模型实例化
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("The two_features_rmse :{}".format(two_features_rmse))
The two_features_rmse :0.8426824704818202
模型也可以加入更多的特征变量
from sklearn.neighbors import KNeighborsRegressor
from sklearn.metrics import mean_squared_error
#选择指定的变量
cols = ['accommodates','bedrooms','bathrooms','beds','minimum_nights','maximum_nights','number_of_reviews']
#将KNN模型实例化
knn = KNeighborsRegressor()
#进行模型的训练
knn.fit(norm_train_df[cols], norm_train_df['price'])
#对测试集进行预测
seven_features_predictions = knn.predict(norm_test_df[cols])
#模型评估
seven_features_mse = mean_squared_error(norm_test_df['price'], seven_features_predictions)
seven_features_rmse = seven_features_mse ** (1/2)
print("The seven_features_rmse :{}".format(seven_features_rmse))
The seven_features_rmse :0.8243838530880285
根据2类特征变量做预测和7类特征变量做预测所得到的结果显示,在特征变量类别增加时,会对结果的预测起促进作用。
从整体上来看,KNN在理论上是不需要建模训练的,数据集做好了,就可以直接使用,是非常简单的。但是KNN缺点也比较明显,当有大量训练数据的基础上,进来一条新的数据需要预测时,需要根据这条新数据对所有现有数据进行运算、对比,这样会大大增加预测时间,致使得到预测会比较慢。

73

被折叠的 条评论
为什么被折叠?



