机器学习-k近邻算法
k近邻算法
简介
如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。
近朱者朱,近墨者墨。
流程:
- 计算距离
- 排序
- 找k近邻
- 统计频次
- 结果:
- 分类:输出频次最高的类别作为预测结果
- 回归:求近邻样本的目标值的平均值
初步使用
机器学习流程
1.获取数据集、2.数据基本处理、3.特征工程、4.机器学习、5.模型评估
Scikit-learn工具安装
pip install scikit-learn
- 分类 回归 聚类 降维 模型调优 预处理
- 强调:sklearn接受的x都必须是2维
knn中的api
sklearn.neighbors.KNeighborsClassifier(n_neighbors=5)
n_neighbors:int,可选(默认= 5),k_neighbors查询默认使用的邻居数
- 训练: knn.fit(x,y)
- 预测:knn.predict(x)
from sklearn.neighbors import KNeighborsClassifier
#构造数据
x = [[0], [1], [2], [3]] #在sklearn中 x必须是二维的 y必须是一维
y = [0, 0, 1, 1]
#开始进行模型训练
knn=KNeighborsClassifier(n_neighbors=2)
#调用fit方法进行训练
knn.fit(x,y) #给了y就是监督学习
#利用训练好的模型对未知样本进行预测
knn.predict([[-1]])
knn.predict([[4]])
距离度量
- 欧氏距离(Euclidean Distance)
俩点空间距离 - 曼哈顿距离(Manhattan Distance)
一个点到另一个点,直角折线步数。 - 切比雪夫距离 (Chebyshev Distance)
一个点可以直行、横行、斜行(只能附近的)。一个点到另一个点最少步数。 - 闵可夫斯基距离(Minkowski Distance)
闵氏距离是一组距离的定义,是对多个距离度量公式的概括性的表述。
当p=1时,就是曼哈顿距离;
当p=2时,就是欧氏距离;
当p→∞时,就是切比雪夫距离。
闵氏距离的缺点:
(1)将各个分量的量纲(scale),也就是“单位”相同的看待了
(2)未考虑各个分量的分布(期望,方差等)可能是不同的
- 原特征的量纲不一致,导致小量纲特征不起作用
- 标准化欧式距离, 先做标准化(量纲归一)-再求欧氏距离
- 特征之间有相关性,呈现斜态的椭圆分布
- 马氏距离
- 1.旋转坐标轴(考虑数据的分布),得到真正的长轴和短轴
- 2.对新轴进行压缩(结果是一个圆形分布)
- 3.计算欧式距离
- 马氏距离
- 标准化欧氏距离 (Standardized EuclideanDistance)
Sk 表示各个维度的标准差
- 余弦距离(Cosine Distance)
余弦距离[-1,1]—常用于文本相似度计算,推荐算法 1-cosθ
角越大,距离越远。 - 汉明距离(Hamming Distance)
- 不同字母的比例
两个等长字符串s1与s2的汉明距离为:将其中一个变为另外一个所需要作的最小字符替换次数。
- 杰卡德距离(Jaccard Distance)
iou 交并比 交集/并集
文本相似度
- 马氏距离(Mahalanobis Distance)
欧式距离&马氏距离:
- 标准化欧氏距离: 先做标准化—再求欧氏距离
- 马氏距离: 先做pca轴旋转—再做标准化—最后求欧氏距离
k值选择
-
K值的减小就意味着整体模型变得复杂,容易发生过拟合;
-
K值的增大就意味着整体的模型变得简单,就会欠拟合
-
实际应用中,K值一般取一个比较小的数值
-
一般都是采样交叉验证和网格搜索技术来搜索一个最优的k值
-
K值过小:
- 容易受到异常点的影响
- 容易过拟合
-
k值过大:
- 受到样本均衡的问题
- 容易欠拟合
-
knn能不能在训练集上做到零误差?
-
可以,k=1,模型过拟合。
kd树
-
knn算法在训练的时候什么都不干,预测的时候要计算预测样本和所有训练样本的距离
-
kdtree目的是减少距离计算次数
kd树:为了避免每次都重新计算一遍距离,算法会把距离信息保存在一棵树里,这样在计算之前从树里查询距离信息,尽量避免重新计算。其基本原理是,如果A和B距离很远,B和C距离很近,那么A和C的距离也很远。 -
kd树包含构建和搜索两个过程
- 构建kd树:模型训练
- 搜索kd树:模型预测的时候
构造过程
- 两个问题:
- 如何选择划分维度(第一次的选择)
- 首次选择0轴划分,后续交替轮询
- 首次选择方差最大的那个轴
- 怎么划分
- 选中位数 为了保证树是一颗平衡二叉树(左子树和右子树差不多,减少搜索时间)
- 如何选择划分维度(第一次的选择)
- 过程
- 首选选择一个轴进行划分
- 提取出该轴的中位数,将该轴坐标小于中位数的样本点划到左子树,大于的划到右子树
- 分别对左子树和右子树重改动做(划分轴依次交替轮询),直到子树样本点只有一个为止
kd树的构建过程【知道】
1.构造根节点
2.通过递归的方法,不断地对k维空间进行切分,生成子节点
3.重复第二步骤,直到子区域中没有示例时终止
需要关注细节:a.选择向量的哪一维进行划分;b.如何划分数据
kd树的搜索过程【知道】
1.二叉树搜索比较待查询节点和分裂节点的分裂维的值,(小于等于就进入左子树分支,大于就进入右子树分支直到叶子结点)
2.顺着“搜索路径”找到最近邻的近似点
3.回溯搜索路径,并判断搜索路径上的结点的其他子结点空间中是否可能有距离查询点更近的数据点,如果有可能,则需要跳到其他子结点空间中去搜索
4.重复这个过程直到搜索路径为空
数据集介绍
- scikit-learn数据集API介绍
sklearn.datasets
加载获取流行数据集
datasets.load_()
获取小规模数据集,数据包含在datasets里
datasets.fetch_(data_home=None)
获取大规模数据集,需要从网络上下载,函数的第一个参数是data_home,表示数据集下载的目录,默认是 ~/scikit_learn_data/ - load和fetch返回的数据类型datasets.base.Bunch(字典格式)
data:特征数据数组,是 [n_samples * n_features] 的二维 numpy.ndarray 数组
target:标签数组,是 n_samples 的一维 numpy.ndarray 数组
DESCR:数据描述
feature_names:特征名,新闻数据,手写数字、回归数据集没有
target_names:标签名 - seaborn.lmplot() 会在绘制二维散点图时,自动完成回归拟合。
sns.lmplot() 里的 x, y 分别代表横纵坐标的列名,
data= 是关联到数据集,
hue=*代表按照 species即花的类别分类显示,
fit_reg=是否进行线性拟合。
数据集分割
sklearn.model_selection.train_test_split(arrays, *options)
x 数据集的特征值
y 数据集的标签值
test_size 测试集的大小,一般为float
random_state 随机数种子,不同的种子会造成不同的随机采样结果。相同的种子采样结果相同。
return:x_train, x_test, y_train, y_test
鸢尾花种类预测
from sklearn.datasets import load_iris
iris=load_iris()
iris
iris.target
iris.feature_names
iris.target_names
iris.DESCR
# 查看数据分布
%matplotlib inline
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
#将数据封装成DataFrame
iris_data=pd.DataFrame(iris.data,columns=['Sepal_Length', 'Sepal_Width', 'Petal_Length', 'Petal_Width'])
iris_data.loc[:,'target']=iris.target
iris_data.head()
iris_data['target'].value_counts()
# 定义绘图函数
def plot(data,col1,col2):
#利用sns画图
sns.lmplot(x=col1,y=col2,data=data,hue='target',fit_reg=False)
plt.xlabel(col1)
plt.ylabel(col2)
plt.title('鸢尾花数据分布')
plt.show()
plot(iris_data,'Petal_Width', 'Sepal_Length')
# 数据集分割
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
#获取鸢尾花数据集
iris_data=load_iris()
# 获取x,y
x=iris.data
y=iris.target
#进行分割
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.3)
x_train.shape
###### random_state的作用
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.3)#不指定random_state
x_train[:2]#每次执行的结果不一样 因为每次都会选择不同的随机数种子
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.3,random_state=0)#指定random_state
x_train[:2]#每次执行分割的结果固定
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.3,random_state=12)#指定random_state
x_train[:2]#结果跟上面不一样,说明种子不一样 结果不一样
小结
scikit-learn中数据集介绍
sklearn.datasets
- 加载获取流行数据集
- datasets.load_*()
- 获取小规模数据集,数据包含在datasets里
- datasets.fetch_*(data_home=None)
- 获取大规模数据集,需要从网络上下载,函数的第一个参数是data_home,表示数据集下载的目录,默认是 ~/scikit_learn_data/
load和fetch返回的数据类型datasets.base.Bunch(字典格式)
- data:特征数据数组,是 [n_samples * n_features] 的二维 numpy.ndarray 数组
- target:标签数组,是 n_samples 的一维 numpy.ndarray 数组
- DESCR:数据描述
- feature_names:特征名,新闻数据,手写数字、回归数据集没有
- target_names:标签名
查看分布
- pip install seaborn
- sns.lmplot(x=col1,y=col2,data=data,hue=‘target’,fit_reg=False)
数据分割
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.3,random_state=0) - x,y需要分割的数据
- test_size指定测试集所占比例
- random_state指定随机数种子 固定分割结果,目的是为了保证数据分割结果固定,这样评估成绩才有可比性
- 不指定:每次执行种子会变化,结果就会变化
- 指定种子:结果固定
- 不同种子结果不一样
特征工程-特征预处理
通过一些转换函数将特征数据转换成更加适合算法模型的特征数据过程
归一化
通过对原始数据进行变换把数据映射到(默认为[0,1])之间–只适合传统精确小数据场景
sklearn.preprocessing.MinMaxScaler (feature_range=(0,1)… )
MinMaxScalar.fit_transform(X)
X:numpy array格式的数据[n_samples,n_features]
返回值:转换后的形状相同的array
标准化
通过对原始数据进行变换把数据变换到均值为0,标准差为1范围内
适合现代嘈杂大数据场景
sklearn.preprocessing.StandardScaler( )
处理之后每列来说所有数据都聚集在均值0附近标准差差为1
StandardScaler.fit_transform(X)
X:numpy array格式的数据[n_samples,n_features]
返回值:转换后的形状相同的array
from sklearn.preprocessing import MinMaxScaler,StandardScaler
import pandas as pd
#归一化
#加载数据
data=pd.read_csv('data/dating.txt')
data.head()
x=data.iloc[:,:3] #只对x特征进行归一化 目标是不能做处理
x.head()
# 归一化
# 实例化归一化对象
mm=MinMaxScaler()
# 训练 训练的时候 就是计算出每一列的max和min 并保存到对象的属性中 为将来做变换使用
mm.fit(x) #x必须是二维数据
# 进行变换
result=mm.transform(x) ##利用fit中计算的max和min进行变换
result.shape
result[:2]
#看一下最大值和最小值
result.max(axis=0),result.min(axis=0)
#查看fit过程中计算出来的maxhemin
mm.data_max_,mm.data_min_
### 归一化存在问题
import numpy as np
arr1=np.arange(20)
arr1[19]=1000
arr1
arr2=np.arange(20)*10
arr2
data=np.vstack([arr1,arr2]).T
data
#归一化
mm=MinMaxScaler()
res=mm.fit_transform(data)
res
##### 标准化
x.head()
#实例化标准化对象
sc=StandardScaler()
# 训练并转换
res2=sc.fit_transform(x) #训练的时候就式计算每一列的均值和标准差并保存到属性中
res2[:5]
#看一下标准化之后的均值和标准差
res2.mean(axis=0),res2.std(axis=0)
sc.mean_
sc.scale_
- 特征工程【知道】
- 定义: 通过一些转换函数将特征数据转换成更加适合算法模型的特征数据过程
- 包含内容:归一化、 标准化
- 归一化【知道】
- 定义: 对原始数据进行变换把数据映射到(默认为[0,1])之间
- api: sklearn.preprocessing.MinMaxScaler (feature_range=(0,1)… )
- 参数:feature_range – 自己指定范围,默认0-1
- 总结: 鲁棒性比较差(容易受到异常点的影响)、只适合传统精确小数据场景
- 标准化【掌握】
- 定义: 对原始数据进行变换把数据变换到均值为0,标准差为1范围内
- api: sklearn.preprocessing.StandardScaler( )
- 总结: 异常值对我影响小、适合现代嘈杂大数据场景(用这个)
knn实现-鸢尾花种类预测
sklearn.neighbors.KNeighborsClassifier(n_neighbors=5,algorithm=‘auto’)
n_neighbors:
int,可选(默认= 5),k_neighbors查询默认使用的邻居数
algorithm:{‘auto’,‘ball_tree’,‘kd_tree’,‘brute’}
快速k近邻搜索算法,默认参数为auto
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
# 获取数据集
iris=load_iris()
#得到x和y
x=iris.data
y=iris.target
# 分割数据集
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.3,random_state=12)
# 特征工程-标准化
#实例化对象 对训练集做了什么,也要对测试集做同样的什么
sc=StandardScaler()
#利用训练集进行训练转换
new_x_train=sc.fit_transform(x_train) #求解出训练集的均值和标准差
#利用训练好的标准化模型对测试集进行转换
new_x_test=sc.transform(x_test) #此时不要再进行fit 用训练集的均值和标准差来对测试机进行变换 而不能再去求解测试集的均值和标准差
# 模型训练
# 实例化knn
knn=KNeighborsClassifier()
#训练
knn.fit(new_x_train,y_train)
# 评估
#输出准确率
print("训练集准确率:",knn.score(new_x_train,y_train))
print("测试集准确率:",knn.score(new_x_test,y_test))
y_pred=knn.predict(new_x_test)
y_pred
y_test
小结
-
sklearn.neighbors.KNeighborsClassifier(n_neighbors=5,algorithm=‘auto’)
- n_neighbors:
- int,可选(默认= 5),k_neighbors查询默认使用的邻居数
- algorithm:{‘auto’,‘ball_tree’,‘kd_tree’,‘brute’}
- 快速k近邻搜索算法,默认参数为auto,可以理解为算法自己决定合适的搜索算法。除此之外,用户也可以自己指定搜索算法ball_tree、kd_tree、brute方法进行搜索,
- brute是蛮力搜索,也就是线性扫描,当训练集很大时,计算非常耗时。
- kd_tree,构造kd树存储数据以便对其进行快速检索的树形数据结构,kd树也就是数据结构中的二叉树。以中值切分构造的树,每个结点是一个超矩形,在维数小于20时效率高。
- ball tree是为了克服kd树高维失效而发明的,其构造过程是以质心C和半径r分割样本空间,每个节点是一个超球体。
- 快速k近邻搜索算法,默认参数为auto,可以理解为算法自己决定合适的搜索算法。除此之外,用户也可以自己指定搜索算法ball_tree、kd_tree、brute方法进行搜索,
- n_neighbors:
-
1.获取数据集 laod_iris
-
2.数据基本处理 train_test-split
-
3.特征工程 —口诀:对训练集干了啥,测试集也要干啥
- 标准化 测试集要用训练集得到的参数进行转换
-
#利用训练集进行训练转换 new_x_train=sc.fit_transform(x_train) #求解出训练集的均值和标准差 #利用训练好的标准化模型对测试集进行转换 new_x_test=sc.transform(x_test) #此时不要再进行fit 用训练集的均值和标准差来对测试机进行变换 而不能再去求解测试集的均值和标准差
-
4.机器学习(模型训练): knn.fit(x,y)
-
5.模型评估: knn.score() 输出准确率
knn算法总结
- 优点:
- 简单有效 原理简单 效果好(非线性分类器)
- 重新训练代价低 —训练的时候不干事
- 适合类域交叉样本分类(非线性分类器)
- 适合大样本自动分类(样本越多,越稳定)
- 缺点
- 预测耗时 训练的时候最多构建kdtree 预测的时候需需要计算距离 找出最近邻 计算量大
- 概率是基于统计频率得来
- 可解释性不强 抛弃了原特征,无法解释原特征 基于实例的算法
- knn 近邻法做升维 原特征—近邻法—>高维空间
- svm 核方法做升维
- 近朱者朱
- 计算量大
交叉验证,网格搜索
交叉验证:将拿到的训练数据,分为训练和验证集。
交叉验证目的:为了让被评估的模型更加准确可信。
网格搜索:有很多参数是需要手动指定的(如k-近邻算法中的K值),这种叫超参数。
每组超参数都采用交叉验证来进行评估。最后选出最优参数组合建立模型。
- sklearn.model_selection.GridSearchCV(estimator, param_grid=None,cv=None)
对估计器的指定参数值进行详尽搜索
estimator:估计器对象
param_grid:估计器参数(dict){“n_neighbors”:[1,3,5]}
cv:指定几折交叉验证
fit:输入训练数据
score:准确率
结果分析:
bestscore__:在交叉验证中验证的最好结果
bestestimator:最好的参数模型
cvresults:每次交叉验证后的验证集准确率结果和训练集准确率结果
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
iris=load_iris()
#得到x和y
x=iris.data
y=iris.targetirist=load_iris()
# 得到x和y
x=iris.data
y=iris.target
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.3,random_state=1234)
#实例化对象 对训练集做了什么,也要对测试集做同样的什么
sc=StandardScaler()
#利用训练集进行训练转换
new_x_train=sc.fit_transform(x_train) #求解出训练集的均值和标准差
#利用训练好的标准化模型对测试集进行转换
new_x_test=sc.transform(x_test) #此时不要再进行fit
model=KNeighborsClassifier() #实例化模型
#指定搜索的参数字典
param_dict={'n_neighbors':[3,5,7,9,11]}
#实例化Gridsearchcv对象
gs=GridSearchCV(model,param_grid=param_dict,cv=3)
#训练搜索
gs.fit(new_x_train,y_train)
# 输出找到最优参数
gs.best_params_
knn=KNeighborsClassifier(n_neighbors=5)
knn.fit(new_x_train,y_train)
knn.score(new_x_train,y_train)
knn.score(new_x_test,y_test)
交叉验证【知道】
- 分割方式:
- 训练集:训练集+验证集 将训练集等分成k份
- 测试集:测试集
- 为什么需要交叉验证
- 为了更可靠的评估模型的好坏/让模型评估的结果更可靠
- 注意:交叉验证不能提高模型的准确率
- 网格搜索【知道】
- 超参数:
- sklearn中,需要手动指定的参数,叫做超参数
- 网格搜索就是把这些超参数的值,通过字典的形式传递进去,然后进行选择最优值
- 超参数:
- api【知道】
- sklearn.model_selection.GridSearchCV(estimator, param_grid=None,cv=None)
- estimator – 选择了哪个训练模型
- param_grid – 需要传递的超参数
- cv – 几折交叉验证
- sklearn.model_selection.GridSearchCV(estimator, param_grid=None,cv=None)
facebook位置预测
具体步骤:
# 1.获取数据集
# 2.基本数据处理
# 2.1 缩小数据范围
# 2.2 选择时间特征
time=pd.to_datetime(fb_data['time'],unit='s')#注意 这里时间戳的单位是秒 不是毫秒
# 2.3 去掉签到较少的地方
# 2.4 确定特征值和目标值
# 2.5 分割数据集
# 3.特征工程 -- 特征预处理(标准化)
# 4.机器学习 -- knn+cv
# 5.模型评估
from sklearn.model_selection import train_test_split,GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
import pandas as pd
# 加载数据
fb_data=pd.read_csv('data/FBlocation/train2.csv')
fb_data.head()
fb_data.info()
#数据基本处理
# 2.1 缩小数据范围
# fb_data=data.query("x>2.0 & x<2.5 & y>2.0 & y<2.5")
# fb_data.shape
# 2.2 选择时间特征
time=pd.to_datetime(fb_data['time'],unit='s')#注意 这里时间戳的单位是秒 不是毫秒
fb_data.loc[:,'day']=time.dt.day
fb_data.loc[:,'hour']=time.dt.hour
fb_data.loc[:,'weekday']=time.dt.weekday
fb_data.head()
# 去掉签到较少的地方
#分组
place=fb_data.groupby('place_id')['row_id'].count()
#逻辑筛选 保留大于20条的数据
place=place[place>20] #得到大于20条数据的place
#对数据进行筛选
fb_data=fb_data[fb_data['place_id'].isin(place.index)]
fb_data.shape
# 2.4 确定特征值和目标值
x=fb_data[['x','y','accuracy','day','hour','weekday']]
y=fb_data['place_id']
#2.5 分割数据集
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.3,random_state=12)
# 特征工程
sc=StandardScaler()
new_x_train=sc.fit_transform(x_train)
new_x_test=sc.transform(x_test)
# 机器学习
#做网格搜索
model=KNeighborsClassifier()
#指定搜索的参数字典
param_dict={'n_neighbors':[3,5,7,9,11,13,15]}
#实例化Gridsearchcv对象
gs=GridSearchCV(model,param_grid=param_dict,cv=5)
#训练搜索
gs.fit(new_x_train,y_train)
#输出最优参数
gs.best_params_
#根据最优参数重新训练模型
knn=KNeighborsClassifier(n_neighbors=5)
knn.fit(new_x_train,y_train)
# 模型评估
print("训练集准确率:",knn.score(new_x_train,y_train))
print("测试集准确率:",knn.score(new_x_test,y_test))
#查看分类数
fb_data['place_id'].unique().shape