k近邻思想是我认为最纯粹最清晰的思想。k近邻算法(KNN)正是这种思想在数据领域的应用。你的薪水是由你周围的人决定的。你的等级取决于离你最近的人的等级。你看到的世界是由你周围的人决定的。想法归想法,无法编码,无法应用于数据科学领域。我们通过提出问题然后应用该方法解决问题来加深对该方法的理解。问题:假设你是airbnb平台的房东,你的房子租金是怎么定的?分析:房客根据airbnb平台上的租房信息选择自己满意的房子,主要包括价格、卧室数量、户型、地段等。房屋租金的设定与市场动态密切相关。同样的房子,如果我们收费太高,租客肯定不会租。如果收费太低,它就不会盈利。答:收集一些和我们房子相似的房子信息,确定离我们房子最近的那些,然后计算它们的平均价格作为我们房子的租金。这就是K-NearestNeighbors(KNN),第k近邻算法。KNN的核心思想是未标记样本的类别,由最近的k个邻居的投票决定。本文梳理了基于租金定价问题的算法应用的全过程,包括以下几个部分。读入数据数据处理手写算法代码预测使用sklearn进行模型预测超参数优化交叉验证提前总结声明,这个数据集是公开的,相关主题的资料你可以在网上找到很多,本文试图解释一下完整准确,如果能找到更详细的学习资料再好不过了。1、读入数据首先读入数据,理解数据,发现目标变量price、cleaning_fee和security_deposit的格式有点问题,另外一些变量是字符类型,需要处理。我转置了数据框以便于查看。2.数据处理我们先只处理价格,尽量把注意力放在算法本身。#对目标变量price进行处理,转化为数值stripped_commas=dc_listings['price'].str.replace(',','')stripped_dollars=stripped_commas.str.replace('$','')dc_listings['price']=stripped_dollars.astype('float')#k近邻算法也是一个模型,需要划分训练集和测试集sample_num=len(dc_listings)#这里我们先将数据随机打散保证数据集划分随机有效dc_listings=dc_listings.loc[np.random.permutation(len(sample_num))]train_df=dc_listings.iloc[0:int(0.7*sample_num)]test_df=dc_listings.iloc[int(0.7*sample_num):]3.手写算法代码预测直接按照k近邻算法的定义写代码。考虑到简单性和效率,我们只对单个变量进行预测。居住人数应该是与租金高度相关的信息,面积也应该如此。我们这里使用前者。我们的目标是理解算法逻辑。在实践中,通常不仅仅考虑单个变量。#注意,这里是train_dfdefpredict_price(new_listing):temp_df=train_df.copy()temp_df['distance']=temp_df['accommodates'].apply(lambdax:np.abs(x-new_listing))temp_df=temp_df.sort_values('distance')nearest_neighbor_prices=temp_df.iloc[0:5]['price']predicted_price=nearest_neighbor_prices.mean()return(predicted_price)#这里是test_dftest_df['predicted_price']=test_df['accommodates'].apply(predict_price)#MAE(meanabsoluteerror),MSE(meansquarederror),RMSE(rootmeansquarederror)test_df['squared_error']=(test_df['predicted_price']-test_df['price'])**(2)mse=test_df['squared_error'.mean()rmse=mse**(1/2)值得强调的是,模型算法的构建是基于训练集,而预测评估是基于测试集。严格来说还有另一种类型的应用程序评估样本,oot:跨时间样本。从结果来看,即使我们只使用住宿数量这个变量来进行邻居选择,预测结果也是非常有效的。4、使用sklearn进行模型预测这次我们会用到更多的变量,只去掉字符串和无法解释的变量,剩下能用的变量就用上。当使用多个变量时,这些不变量是不同的,我们需要对它们进行标准化。保证了各个变量的分布差异性,同时变量可以叠加。#去除非数字变量和不合适的变量dc_listings.drop(drop_columns,axis=1)#删除缺失率高的列(变量)dc_listings=dc_listings.drop(['cleaning_fee','security_deposit'],axis=1)#removingmissingValueline(sample)dc_listings=dc_listings.dropna(axis=0)#多个变量的维度不同,需要标准化']#于是我们得到了一个可以用来建模的数据集,7:3拆分训练集测试集train_df=normalized_listings.iloc[0:int(0.7*len(normalized_listings))]test_df=normalized_listings.iloc[int(0.7*len(normalized_listings)):]#price为y,其余变量为Xfeatures=train_df.columns.tolist()features.remove('price')处理后的数据集i如下,其中price为我们要预测的目标,其余为可用变量。fromsklearn.neighborsimportKNeighborsRegressorfromsklearn.metricsimportmean_squared_errorknn=KNeighborsRegressor(n_neighbors=5,algorithm='brute')knn.fit(train_df[features],train_df['price'])预测=knn.predict(test_df[features])mse=mean_squared_error(test_df['price'],predictions)rmse=mse**(1/2)最终的rmse=111.9比单变量knn的117.4小,优化了结果。严格来说,这种比较并不完全公平,因为我们丢失了少量缺失的特征样本。5.超参优化在第三和第四部分,我们预设了k=5,但这是通过拍脑袋来确定的。这个值是否合理或最优还需要进一步确定。其中,这个k是一个超参数。对于任何数据集,只要用到knn,就需要确定这个k值。k值不是模型根据数据学习得到的,而是根据结果通过预设和反向选择来确定的。任何超参数都是这样确定的,其他算法也是如此。importmatplotlib.pyplotasplt%matplotlibinlinehyper_params=[xforxinrange(1,21)]rmse_values=[]features=train_df.columns.tolist()features.remove('price')forhpinhyper_params:knn=KNeighborsRegressor(n_neighbors=hp,algorithm='brute')knn.fit(train_df[features],train_df['price'])predictions=knn.predict(test_df[features])mse=mean_squared_error(test_df['price'],predictions)rmse=mse**(1/2)rmse_values.append(rmse)plt.plot(hyper_params,rmse_values,c='r',linestyle='-',marker='+')我们发现k越大,预测价格和真实价格的偏差从走势上可以看出价格会比较准确。但需要注意的是,k越大,计算量越大。我们在确定k值的时候,可以用albow法,也就是看上图中的拐点,也就是图像中肘部的拐点。k=7或10可能是比k=5更好的结果。6.Cross-validation以上计算结果完全依赖于训练集和测试集,虽然我们已经考虑了它们划分的随机性。但是一次性结果还是偶尔有,尤其是样本量不够大的时候。交叉验证就是为了解决这个问题而设计的。我们可以将同一个样本集分成不同的训练集和测试集。每次划分之后,重新训练和预测,然后综合看这些结果。最广泛使用的是n-fold交叉验证。其过程是将数据集随机分成n份,用n-1个子集作为训练集,剩下的1个子集作为测试集。这样一共可以进行n次训练和预测。我们可以直接手写逻辑,如下。sample_num=len(normalized_listings)normalized_listings.loc[normalized_listings.index[0:int(0.2*sample_num)],"fold"]=1normalized_listings.loc[normalized_listings.index[int(0.2*sample_num):int(0.4*sample_num)],"fold"]=2normalized_listings.loc[normalized_listings.index[int(0.4*sample_num):int(0.6*sample_num)],"fold"]=3normalized_listings.loc[normalized_listings.index[int(0.6*sample_num)]:int(0.8*sample_num)],"fold"]=4normalized_listings.loc[normalized_listings.index[int(0.8*sample_num):],"fold"]=5fold_ids=[1,2,3,4,5]deftrain_and_validate(df,folds):fold_rmses=[]forfoldinfolds:#Trainmodel=KNeighborsRegressor()train=df[df["fold"]!=fold]test=df[df["fold"]==fold].copy()模型.fit(train[features],train["price"])#Predictlabels=model.predict(test[features])test["predicted_price"]=labelsmse=mean_squared_error(test["price"],test["predicted_price"])rmse=mse**(1/2)fold_rmses.append(rmse)return(fold_rmses)rmses=train_and_validate(normalized_listings,fold_ids)avg_rmse=np.mean(rmses)在项目中,我们需要充分利用工具和资源。sklearn库包含了我们常用的机器学习算法实现,可以直接用于验证。fromsklearn.model_selectionimportcross_val_score,KFoldkf=KFold(5,shuffle=True,random_state=1)model=KNeighborsRegressor()mses=cross_val_score(模型,normalized_listings[特征],normalized_listings["pricef"],scoring="neg_mean_squared",c_error)rmses=np.sqrt(np.absolute(mses))avg_rmse=np.mean(rmses)交叉验证结果的置信度会更高,尤其是在小数据集上。因为它可以在一定程度上缓解意外错误。结合交叉验证和超参数优化,我们一般会得到该数据集下knn算法预测的最优结果。#超参数优化num_folds=[xforxinrange(2,50,2)]rmse_values=[]forfoldinnum_folds:kf=KFold(fold,shuffle=True,random_state=1)model=KNeighborsRegressor()mses=cross_val_score(model,normalized_listings[features],normalized_listings["价格"],scoring="neg_mean_squared_error",cv=kf)rmses=np.sqrt(np.absolute(mses))avg_rmse=np.mean(rmses)std_rmse=np.std(rmses)rmse_values。append(avg_rmse)plt.plot(num_folds,rmse_values,c='r',linestyle='-',marker='+')我们得到相同的趋势,k越大,效果趋势越好。同时,由于交叉验证在一定程度上解决了过拟合问题,理想的k值越大,模型可以越复杂。7.总结从k近邻算法的核心思想和上面的编码过程可以看出,该算法是一种基于实例的学习方法,因为它完全依赖于训练集中的实例。该算法不需要任何数学方法,简单易懂。但是非常不适合大数据集,因为k近邻算法每次预测都需要计算整个训练集中的数据与待预测数据的距离,然后按升序排列,导致在大量的计算中。如果可以用一个数学函数来描述数据集的特征变量与目标变量之间的关系,那么一旦用训练集得到函数表示,预测就是一个简单的数学计算问题。计算复杂度大大降低。其他经典的机器学习算法基本上都是函数表达式问题。我们稍后会看到。本文转载自微信公众号「thunderbang」,可通过以下二维码关注。转载本文请联系迅雷公众号。
