6-机器学习之KNN(K-近临算法)

tags: python,机器学习,KNN,matplotlib,pyplot,pandas,numpy,Series,DataFrame

一、 k-近邻算法原理

存在一个样本数据集合,也称作训练样本集(一般在代码中用X_trainy_train表示),并且样本集中每个数据都存在标签(y_train存放标签数据),即我们知道样本集中每一数据(X_train中存放)与所属分类(y_train中存放)的对应关系。
输人没有标签的新数据(X_test中存放)后,将新数据的每个特征与样本集中数据对应的 特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。
一般来说,我们 只选择样本数据集中前K个最相似的数据,这就是K-近邻算法中K的出处,通常K是不大于20的整数。 最后 ,选择K个最相似数据中出现次数最多的分类,作为新数据的分类。

机器学习分类

简单地说,K-近邻算法采用测量不同特征值之间的距离方法进行分类。
k-近邻算法属于有监督学习算法,即训练值中有对应的结果。机器学习的分类见上图。

二、k-近邻算法案例

2.1. 使用步骤

  1. 调包
  2. 准备数据
  3. 建立训练模型
  4. 训练
  5. 预测
  6. 计算训练数据得分和预测数据得分

2.2. 预测电影类型

通过统计每个电影中,接吻和打斗的次数,来区分电影是动作片还是爱情片。
目前有电影数据movie数据如下:

movie数据
获取训练数据X_train和对应的标记y_train

X_train = movie[['武打镜头', '接吻镜头']]
y_train = movie['分类情况']

将上面获取到的训练数据使用knn算法进行计算,但是在计算前,需要先导入使用到的模块:

import pandas as pd
import numpy as np
from pandas import Series, DataFrame
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsClassifier

knn = KNeighborsClassifier()
# 训练数据必须是2维, 标记没有要求是几维的,一般是1维, 而且也没有要求必须是数字,
knn.fit(X_train, y_train)

训练数据必须是2维,
标记没有要求是几维的,一般是1维,而且也没有要求必须是数字。

现在有待预测的新数据:

# 预测数据
# 哪吒, 海王, 红海行动, 前任2, 
X_test = np.array([[50, 0], [40, 2], [65, 0], [1, 20]])

使用上面的待预测数据X_test,调用训练好的模型,来预测其对应的分类结果:

knn.predict(X_test)

输出结果为array(['动作片', '动作片', '动作片', '爱情片'], dtype=object)
最后可以测试一下我们的模型预测的准确率:

y_test = ['动作片', '动作片', '动作片', '爱情片']
knn.score(X_test, y_test)

输出结果为1.0,即预测准确率为100%。

2.3. 通过身高、体重、鞋子尺码数据预测性别

# 调包
from sklearn.neighbors import KNeighborsClassifier

# 准备数据
X_train = [[180, 180, 43], [170, 150, 39], [173, 128, 43], [170, 140, 39], [163, 90, 39], [164, 85, 36.5], [155, 75, 35], [172, 110, 41], [165, 114, 40], [175, 130, 43], [171, 135, 43], [160, 90, 36],[160, 90, 36], [158, 85, 36]]
y_train = ['男', '男', '男','男', '男', '女', '女', '女', '男', '男', '男', '女', '女', '女']

# 建立训练模型并训练
knn = KNeighborsClassifier()
knn.fit(X_train, y_train)

# 对新数据进行预测
# 对应的实际结果为y_test = ['男', '男', '女', '女', '男', '女']
X_test = [[173, 118, 42], [173, 145, 41], [173, 120, 40], [165, 95, 40], [160, 120, 41], [158, 100, 36]]
knn.predict(X_test)

上面的代码的输出结果为:['男', '男', '男', '女', '男', '女']

使用上面训练好的模型对训练数据进行计算得分:

knn.score(X_train,  y_train)

输出结果为:0.8571428571428571

对测试数据计算得分:

y_test = ['男', '男', '女', '女', '男', '女']
knn.score(X_test, y_test)

输出结果为:0.8333333333333334

2.4. 预测鸢尾花类型

2.4.1. 常规机器学习步骤

先从sklearn数据库中获取鸢尾花的数据,代码如下:

from sklearn.datasets import load_iris
iris = load_iris()

把获取到的鸢尾花的数据保存到iris中,数据为字典的形式,其中key值为'data'的数据对应的值为鸢尾花的4个特征值,是一个二维数据。行为每一个花对应的数据,列为每一个特征对应的值。另外其中key值为'target'对应的值为'data'对应的值中每一行(代表一朵花)值代表的花的类型,也即是标记。

但是我们不能自己创造新的测试数据来对训练好的模型进行测试,所以为了验证模型的准确性,我们需要从iris中随机抽调训练值和测试值。

对此我们使用train_test_split模块,从已有的数据和其对应的标签中,随机切分出训练数据X_train、测试数据X_test、训练数据对应的标签y_train、测试数据对应的标签y_test
模块导入代码:from sklearn.model_selection import train_test_split

准备数据:

data = iris['data']
target = iris['target']
X_train, X_test, y_train, y_test = train_test_split(data, target, test_size=0.2)

建立模型,并进行训练:

knn = KNeighborsClassifier()
knn.fit(X_train, y_train)

使用训练数据测试模型的得分:

knn.score(X_train,  y_train)

得分为:0.975

由于上面使用train_test_split切分是随机的,所以每次取到的数据都是不一样的,所以这里的得分一般不相同)

使用测试数据计算模型的得分:

knn.score(X_test, y_test)

2.4.2. 机器学习结果可视化(获取knn分类的边界)

由于上面的步骤最后只给了一个模型得分的结果,可视化不是很好,所以可以使用别的方式,将预测结果做的比较直观一些。
首先我们将所有鸢尾花的特征值画到一个线图中,以通过观察来选择用于可视化的特征参数。

df = DataFrame(data=data)
df.plot()

特征值
上面的图片中,每一个系列就是数据中的一列,也即是代表了鸢尾花的4个特征值中的一个。图片中所有的数据,每一列从开始到最后,代表了3种花,所以在图中可以看到曲线有跳跃的地方,有的曲线被很明显的分成了3段。
我们可以通过散点图的形式,选取4个特征中的两个,来观察三种花在这两种特征值中的分布情况。由于我们要区分不同的花,所以特征值中数据最好不要有重叠,我们可以将上面的4个特征值之间所有的散点图画出来,见下代码:

plt.figure(figsize=(3*5,2*4))
c = 1
for i in range(3):
    for j in range(i+1,4):
        plt.subplot(2,3,c,title='{}-{}'.format(i,j), facecolor='c')
        plt.scatter(data[:,i],data[:,j],c=target)
        c += 1

数据分布
通过上面的图片,可以看出使用特征值0和1、1和3画散点图时会有重叠,其余的数据分布还可以,所以这里我们使用0和3进行画图。

接下来我们在特征0和特征3所组成的平面中,将3种不同的鸢尾花在该平面上的分布画出来。

具体步骤为:取两组特征值组成的平面的x、y轴最小值和最大值,然后在该范围内进行划分,如果每个轴划分1000份,则可以形成1000*1000个点。接着使用特征0和特征3中的值进行训练。将划分后的点代入模型中进行预测,然后获取所有点对应的花的种类,最后将种类作为颜色,画在平面上。代码如下:

# 先生成两个线段上的范围
x, y = np.linspace(data[:,0].min(), data[:,0].max(), 1000), np.linspace(data[:,3].min(), data[:,3].max(), 1000)
# 范围拉起来形成2维平面
X, Y = np.meshgrid(x,y)
# 两个平面上相交的点
XY = np.c_[X.ravel(), Y.ravel()]

# 使用特征0和3中的数据进行训练
knn = KNeighborsClassifier()
knn.fit(data[:,(0,3)], target)

# 将划分好的所有点代入模型中进行种类预测
# y_中为每个点对应的种类
y_ = knn.predict(XY)

# 将数据展示出来
plt.pcolormesh(X, Y, y_.reshape(1000,1000))
plt.scatter(data[:,0],data[:,3],c=target,cmap='rainbow')

平面根据花种类进行划分
步骤总结:

  • 绘制图形
  • 定义KNN分类器
  • 第一步,训练数据
  • 第二步预测数据:,所预测的数据,自己创造,就是上面所显示图片的背景点
  • 生成预测数据
  • 对数据进行预测
  • 显示数据

2.5. 使用交叉表对预测结果进行可视化展示

当前有癌症数据,其有32列,第0列和第1列分别为患者的id和癌症的最终检测结果(分为良性B和恶性的M),剩下的30列为判断癌症良性或恶性的特征数据,我们分别取出从第二列到最后列所有的数据作为特征数据,第一列为对应的标签。由于我们不能自己生成测试数据和标签,所以需要对原始数据使用train_test_split进行随机切分。

from sklearn.model_selection import train_test_split
data = cancer.iloc[:,  2:]
target = cancer.Diagnosis
# 数据切分
X_train, X_test, y_train, y_test = train_test_split(data, target, test_size=69)
# 模型建立与训练
knn = KNeighborsClassifier()
knn.fit(X_train, y_train)
# 数据预测
y_ = knn.predict(X_test)
# 模型得分
knn.score(X_train, y_train)
knn.score(X_test, y_test)

由于上面的模型中,特征值过多,如果只选取其中两个画散点图进行可视化展示,效果不好。这里可以使用交叉表的形式,直接在表中展示预测结果和对应真实结果,这样比直接给模型的得分更加直观。

# 对于这种不太好进行可视化展示的数据,我们可以使用交叉表.
pd.crosstab(index=y_test, columns=y_, rownames=['真实值'], colnames=['预测值'], margins=True)

预测结果交叉表
上面的交叉表中,我们行index使用了测试数据的真实标签,所以交叉表的每一行都是真实数据;而列columns我们使用了预测值,最后margins=True表示在交叉表中显示汇总数据。
具体看法,先看行,B有44个真实结果,M有25个真实结果。然后看列,模型预测了47个B和22个M。最后看对角线,其为预测正确的数据,即B预测正确44个,M预测正确22个,也即是B都预测正确了,但是M有3个预测成了B。

2.6. 对训练值、训练值标签、预测标签进行可视化展示

数据情况:
当前有手写数字测试数据,数字从0到9,每个数字有标号为1到500的图片,总共500张,每个图片都是黑白图片。我们把图片读取出来,其读取的数据为二维数据。

训练思路:
对与此数据集,每张图片都是一个二维数据,但是这个二维数据只能看做一个特征值,由于在knn中,单个训练数据只能是一维的,所以需要把这个数据转化为一维数据。同时记录该数据对应的实际数字值,此即该测试数据的测试标签。将所有的数据都读出来,并以列表的形式存储,则总共有500*10=5000个数据,每个数据对应一个数字,也即是训练数据标签,由于图片是二维的,所以最后存储的数据是3维的,而knn对与总训练数据集要求是2维的,所以要把最后的两维合并为一维,最代入进行训练。
同样由于不能自己创造测试数据和对应的标签,所以需要使用train_test_split进行切分。

data = []
target = []
for i in range(10):
    for j in range(1,501):
        bmp_path = './data/data/{}/{}_{}.bmp'.format(i,i,j)
        temp = plt.imread(bmp_path)
        data.append(temp)
        target.append(i)
		
# 使用数组方便读取数据,转化为ndarray方便对维度进行转换
data = np.array(data)
data = data.reshape(5000,-1)
target = np.array(target)

# 切分训练数据及标签和测试数据及标签
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(data, target, test_size=0.2)

knn = KNeighborsClassifier()
knn.fit(X_train, y_train)
y_ = knn.predict(X_test)

对上面的数据进行机器学习之后,可以抽取部分数据进行可视化。由于切分时选择的大小为test_size=0.2,而总样本个数为5000,所以测试数据个数为5000*0.2=1000。可以从这1000个测试数据中,每隔10个选择一个测试数据,画图其对应的图片,并在图片上标示对应的实际数字和预测数字。

plt.figure(figsize=(10*2, 10*3))
for i in range(100):
    axes = plt.subplot(10,10,i+1)
	# 测试数据X_test是2维的,需要将其中的每一个数据转为2维的。
    axes.imshow(X_test[i*10].reshape(28,28), cmap='gray')
    axes.axis('off')
    if y_test[i*10] != y_[i*10]:
        axes.set_title('True:{}\nPredict:{}'.format(y_test[i*10], y_[i*10]),fontdict=dict(color='red'))    
    axes.set_title('True:{}\nPredict:{}'.format(y_test[i*10], y_[i*10]))

数据-对应真实标签-预测标签

2.7. k-近临算法用于回归对趋势进行预测

首先生成用于训练的数据:

x = np.random.rand(100) * 10 
y = np.sin(x)
plt.scatter(x,y)

散点图
使用rand生成[0-1)之间的随机数,然后放大10倍作为训练数据X_train。然后生成对应的标签,使用sin生成。
由于上面的趋势过于明显,所以需要增加一些噪点:

# 加点噪声
y[::4] += np.random.randn(25) * 0.3 
plt.scatter(x,y)

添加噪声后的测试数据
从训练数据中每4个获取一个来增加噪点,使用randn生成[-3,3]之间的标准正态分布,由于测试数据在[-1,1]之间,所以将噪点进行缩小

由于需要对趋势进行预测,这种机器学习模式区别于之前的情况,之前的训练数据的标签是不连续的,比如标签为男或者女,或标签为3种花的一种,但是现在的请是预测趋势,是连续的,所以这里不能使用之前的模型。

这里使用k-近临回归模型,使用的模块为KNeighborsRegressor

# 导入模块
from sklearn.neighbors import KNeighborsRegressor

# 建立模型并训练
knn = KNeighborsRegressor()
knn.fit(x.reshape(-1,1), y)

# 建立测试数据
X_test = np.linspace(0, 10, 1000).reshape(-1,1)

# 对测试数据进行预测
y_ = knn.predict(X_test)

# 将原始数据和测试数据与预测结果画出来
plt.scatter(x,y)
plt.plot(X_test, y_, c='r')

趋势预测
步骤总结:

  • 导包
  • 生成样本数据
  • 生成测试数据的结果
  • 第一步:生成模型,并训练数据
  • 第二步:使用模型,预测数据
  • 绘图显示数据

三、其他知识补充

3.1. 随机数种子

在上面的章节中,使用到了模块train_test_split,该模块会根据指定的test_size参数来选择不同比例的测试数据大小和训练数据大小,而且每次都是随机选取。
但是有些时候,可能需要对整个模型进行优化,如果每次选取的训练数据不相同时,会造成优化前后的模型无法对比优劣。所以可以在其中指定随机种子。
对与随机数而言,如果不主动指定随机种子,则系统会把计算机当前的时间戳作为随机种子,而如果我们指定了随机种子,则每次生成随机数时,系统会根据该种子计算随机数。如果随机种子一样,则每次生成的随机数也是一样的。
如代码:

np.random.seed(1)
np.random.rand(1)
# 指定了随机种子之后每次随机结果都一样

输出结果为:array([0.417022])

所以为了即满足随机获取样本片段的寻求且片段一致,可以在模块train_test_split中设置随机种子参数random_state,这样每次获取的片段都是一样的。

3.2. 机器学习数据标准化

在一些样本数据中,如果数据的特征值分布过于分散,则可能会导致训练好的模型预测数据时不准确。
此时可以将其中所有的特征值进行标准化,将所有的值更改为0-1之间的数据,这样会使得数据分布更加集中一些。

标准化的方法是,取出该列的所有数据,获取其中的最大值c_max和最小值c_min,然后将该列所有的数据v进行以下计算:(v - c_min) / (c_max - c_min),并将计算好的值赋值给原数据位置。
为此有两种实现思路:

  1. 第一层循环:取出数据的每一列,计算最大最小。第二层循环:在取出的该列中,取出所有的数据,分别进行计算,然后将计算好的结果赋值给原数据的位置。
  2. 定义好数据标准化的子函数,在其中返回标准化之后的结果。第一层循环:取出数据的每一列,对该列数据使用.transform()函数,函数中传入定义好的子函数,然后将计算好的结果赋值给该列。

.transform()函数一般为对数据某一列进行操作,其会将调用该函数的数据全部传入进来。与之相似的.map()函数,他是将调用该函数的数据逐个传入进来。
所以在.transform()函数中传入的自定义函数,可以是对调用.transform()函数的所有数据进行操作,比如求最值、平均等,而.map()函数则不可以,因为其是逐个传入数据,其没有一下子获取所有数据,所以不能对调用自己的数据求最值、平均等操作。

思路一实现代码(data_new_change中存有所有要标准化的数据,数据大小为32561 rows × 9 columns):

for col in data_new_change.columns:
    c_max = data_new_change[col].max()
    c_min = data_new_change[col].min()
    for i in range(len(data_new_change[col])):
        v = data_new_change[col][i]
        data_new_change[col][i] = (v-c_min)/(c_max - c_min)

思路二实现代码 #E91E63(data_new_change2中存有所有要标准化的数据,数据大小为32561 rows × 9 columns):

def normalized(x):
    return (x - x.min()) / (x.max() - x.min())
for col in data_new_change2.columns:
    data_new_change2[col] = data_new_change2[col].transform(normalized)

上面两个代码数据是一样的。

对两种代码的执行时间进行测试,结果如下:

耗时结果
从上面的计算结果可以看到,两种代码耗时差距巨大,所以以后这种情况,建议使用思路2对应的代码。

四、K近临回归算法Lasso回归在线性数据预测中的表现

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsRegressor
from sklearn.linear_model import Lasso

np.random.seed(1)
X = np.random.rand(30) * 10
y = 5 * X

index = np.arange(30)
np.random.shuffle(index,)
y[index[:15]] += np.random.randn(15) * 8 
plt.scatter(X,y)

上面的代码生成的测试数据散点图如下:

测试数据散点图
对上面的数据分别使用knn中的回归算法和lasso线性算法进行训练:

x_ = np.linspace(0, 10,100).reshape(-1,1)

knn = KNeighborsRegressor()
knn.fit(X.reshape(-1,1),y)
score_knn = knn.score(X.reshape(-1,1),y)
y_knn = knn.predict(x_)

lasso = Lasso()
lasso.fit(X.reshape(-1,1),y)
score_lasso = lasso.score(X.reshape(-1,1),y)
# 计算斜率
coef_lasso = lasso.coef_
y_lasso = lasso.predict(x_)

使用两种训练好的模型对测试值进行预测,并画出预测值的图像:

plt.figure(figsize=(8,6))
plt.scatter(X,y,label='train data')
plt.plot(x_,y_lasso,c='r', label='Lasso predict(score: {:.4f}, coef: {:.2f})'.format(score_lasso,coef_lasso[0]))
plt.plot(x_,y_knn,c='g',label='Knr predict(score: {:.4f})'.format(score_knn))
plt.legend()

预测结果

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值