机器学习,KNN 算法

机器学习概述

人工智能三大概念

  • AL:人工智能,机器模拟人类
  • ML:机器学习,能像人一样思考
  • DL:深度学习,像人一样活动

机器学习的概念

让机器自动学习,而不是基于规则的编程

深度学习

也叫深度神经网络,大脑仿生,设计一层一层的神经元模拟万事万物

三者之间的关系

  • 机器学习是实现人工智能的一种途径
  • 深度学习是机器学习的一种方法

学习方式

基于规则,这没什么好解释的,就是自己指定规则,严格按照规则。

基于模型的学习

让机器自己学习从历史数据中获得经验、训练模型:

发展史

1956年夏季,以麦卡赛、明斯基、罗切斯特和申农等为首的一批有远见卓识的年轻科学家在一起聚会,共同研究和探讨用机器模拟智能的一系列有关问题,并首次提出了"人工智能"这一术语,它标志着"人工智能"这门新兴学科的正式诞生。

1956 年被认为是人工智能元年

1950-1970 符号主义流派:专家系统占主导地位

1950:图灵设计国际象棋程序

1962:IBM Arthur Samuel 的跳棋程序战胜人类高手(人工智能第一次浪潮)

1980-2000

统计主义流派:主要用统计模型解决问题

1993:Vapnik提出SVM

1997:IBM 深蓝战胜卡斯帕罗夫(人工智能第二次浪潮)

2010-2017

神经网络、深度学习流派

2012:AlexNet深度学习的开山之作

2016:Google AlphaGO 战胜李世石(人工智能第三次浪潮)

2017-至今

大规模预训练模型

2017年,自然语言处理NLP的Transformer框架出现

2018年,Bert和GPT的出现

2022年,chatGPT的出现,进入到大规模模型AIGC发展的阶段

机器学习发展三要素

数据、算法、算力三要素相互作用,是AI发展的基石

  1. CPU:负责调度任务、计算任务等;主要适合I\O密集型的任务
  2. GPU:更加适合矩阵运算;主要适合计算密集型任务
  3. TPU:Tensor,专门针对神经网络训练设计一款处理器

常用术语

样本,特征,标签/目标值

假设就业薪资是计算出来的。那么就业薪资就是标签

对于整个结果叫做数据集

还有一种叫,我们针对于整个训练集,分为,正式和测试,正式数据叫train ,测试数据叫test 。比如对于标签 的测试数据,我们可以说:y_test ,对于样本 的测试数据,可以说:x_test

算法分类

有监督学习是:有特征、有标签

即输入的数据是由特征值目标值组成的。如下图

又可以分为:连续或者不连续

  • 连续的采用:回归
  • 不连续的采用:分类

不连续 = 分类

为什么叫分类呢,没有特定关系。比如要预测的结果是,好评/中评/差评,没有关系,不能排序。又分为了二分类和多分类

  • 二分类:比如 猫/非猫
  • 多分类:好评/中评/差评等等

这个值又叫离散值。

连续 = 回归

根据一些特征,可以推断出一个具体的数值。

比如房子的面积、楼层、位置,可以推断出价格。

无监督学习是:有特征、无标签

即输入的数据,没有被标记,未知的类型,没有标签。

会产生聚类问题

根据样本间的相似性对样本进行聚类,发现事物内部结构及相互关系。

半监督学习

工作流程

  1. 让专家标注少量数据,利用已经标注的数据(也就是带有标签的数据)训练出一个模型
  2. 再利用该模型去套用未标记的数据,结果并不完全正确
  3. 通过询问领域专家分类结果与模型做对比,我们只需要对结果进行整改,从而对模型做进一步改善和提高。

意义是降低专家数据标注的次数,用于提高效率。

强化学习

  1. 强化学习(Reinforcement Learning):机器学习的一个重要分支
  2. 应用场景:里程碑AlphaGo围棋、各类游戏、对抗比赛、无人驾驶场景

是针对深度学习使用的。

建模流程

在整个建模流程中,数据基本处理、特征工程一般是耗时、耗精力最多的。

有监督学习模型训练和模型预测

机器学习建模的一般步骤

  • 获取数据:搜集与完成机器学习任务相关的数据集
  • 数据基本处理:数据集中异常值,缺失值的处理等
  • 特征工程:对数据特征进行提取、转成向量,让模型达到最好的效果
  • 机器学习(模型训练):选择合适的算法对模型进行训练
    • 根据不同的任务来选中不同的算法;有监督学习,无监督学习,半监督学习,强化学习
  • 模型评估:评估效果好上线服务,评估效果不好则重复上述步骤

特征工程

下面将用房子举例说明:

什么是特征

描述了房子的多个参数,比如面积、位置、楼层等等。

利用专业背景知识和技巧处理数据,让机器学习算法效果最好。这个过程就是特征工程。

数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。

特征提取

特征提取从无到有的做行列向量数据

这一步要做一些处理:如归一化,标准化

特征降维

将数据从3个特征 变成2个特征。一般会对原始数据产生影响。

比如:数据库里的有三张表,合并成一张表叫宽表(记录了三个表的核心字段)

特征选择

特征提取后,有很多特征,需要从众多特征中选取较为重要的特征,叫做特征选择。

特征组合

总结

拟合

  • 拟合:用在机器学习领域,用来表示模型对样本点的拟合情况
  • 欠拟合:模型在训练集上表现很差、在测试集表现也很差
  • 过拟合:模型在训练集上表现很好、在测试集表现很差

欠拟合例子

过拟合

  • 欠拟合产生的原因:模型过于简单
  • 过拟合产的原因:模型太过于复杂、数据不纯、训练数据太少

泛化:模型在新数据集(非训练数据)上的表现好坏的能力。

奥卡姆剃刀原则:给定两个具有相同泛化误差的模型,较简单的模型比较复杂的模型更可取

下面是一张表达了,拟合关系的图

开发环境搭建 scikit-learn

  • 简单高效的数据挖掘和数据分析工具
  • 可供大家使用,可在各种环境中重复使用
  • 建立在NumPy,SciPy和matplotlib上
  • 开源,可商业使用-获取BSD许可证
bash 复制代码
pip install scikit-learn

机器学习的流程

  1. 数据准备
  2. 数据预处理
  3. 特征工程
    1. 特征提取
    2. 特征预处理
    3. 特征降维
    4. 特征选取
    5. 特征组合
  4. 模型训练
  5. 模型评估
  6. 模型预测

官网 scikit-learn.org/stable/inde...

算法选择路线

knn 算法

knn算法也叫k近邻算法,是属于分类的一种。

knn思想:如果一个样本在特征空间中的 k 个最相似的样本中的大多数属于某一个类别,则该样本也属于这个类别

它既可以做分类也可以做回归,那是怎么做的呢,就是找到离你最近,最相似的样本。

假设我们有五个样本,这个时候就有两个问题,第一个叫分类,第二个叫回归。

  • 分类是离你最近的五个样本投票,那个多,你就是谁。
  • 回归是根据票数进行排序,找到最小值(具体算法不一定)

分类 K 近邻算法

样本相似性:样本都是属于一个任务数据集的。样本距离越近则越相似。

欧式距离:对应坐标轴差值平方和开根号

二维平面上点a(x₁,y₁)与b(x₂,y₂)间的欧氏距离:

<math xmlns="http://www.w3.org/1998/Math/MathML"> d 12 = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 d_{12} = \sqrt{(x_1 - x_2 )^2 + (y_1 - y_2)^2} </math>d12=(x1−x2)2+(y1−y2)2

三维空间点 <math xmlns="http://www.w3.org/1998/Math/MathML"> a ( x 1 , y 1 , z 1 ) a(x_1,y_1,z_1) </math>a(x1,y1,z1)与 <math xmlns="http://www.w3.org/1998/Math/MathML"> b ( x 2 , y 2 , z 2 ) b(x_2,y_2,z_2) </math>b(x2,y2,z2)间的欧氏距离为

<math xmlns="http://www.w3.org/1998/Math/MathML"> d 12 = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 + ( z 1 − z 2 ) 2 d_{12} = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2 + (z_1 - z_2)^2} </math>d12=(x1−x2)2+(y1−y2)2+(z1−z2)2

n维空间点 <math xmlns="http://www.w3.org/1998/Math/MathML"> a ( x 11 , x 12 , ... , x 1 n ) a(x_{11},x_{12},\dots,x_{1n}) </math>a(x11,x12,...,x1n)与 <math xmlns="http://www.w3.org/1998/Math/MathML"> b ( x 21 , x 22 , ... , x 2 n ) b(x_{21},x_{22},\dots,x_{2n}) </math>b(x21,x22,...,x2n)(两个n维向量)间的欧氏距离为

<math xmlns="http://www.w3.org/1998/Math/MathML"> d 12 = ∑ k = 1 n ( x 1 k − x 2 k ) 2 d_{12} = \sqrt{\sum_{k=1}^{n} (x_{1k} - x_{2k})^2} </math>d12=∑k=1n(x1k−x2k)2 。

案例,计算过程

求,唐人街探案的欧氏距离,对比功夫熊猫。

谁减去谁不重要,因为开根号可以避免,这里就使用大的减去小的。

  1. <math xmlns="http://www.w3.org/1998/Math/MathML"> ( 39 − 23 ) 2 = 256 + ( 3 − 0 ) 2 = 9 + ( 31 − 17 ) 2 = 196 = 461 = 21.47 \sqrt{(39-23)^2=256 + (3-0)^2=9 + (31-17)^2 = 196} = 461 = 21.47 </math>(39−23)2=256+(3−0)2=9+(31−17)2=196 =461=21.47
  2. 如果 k = 5,将要计算5个最小值排序的情况。将这个五个值,相加平方开根号的过程。

回归 K 值选择

K值过小:用较小邻域中的训练实例进行预测

  • 容易受到异常点的影响
  • K值的减小就意味着整体模型变得复杂,容易发生过拟合

样本越多,KNN 越稳定,K 越大,越能抵抗噪声,样本越少 + K 越小,最容易被误导。

K值过大:用较大邻域中的训练实例进行预测

  • 受到样本均衡的问题
  • 且K值的增大就意味着整体的模型变得简单,欠拟合

样本少时,K 一大,预测永远是最多类别。

超参与交叉验证

超惨是指用户传递过来的参数

统计k中样本最多的那个类别,将未知样本归属于该类别。

  1. 计算未知样本到每一个训练样本的距离
  2. 将训练样本根据距离大小升序排列
  3. 取出距离最近的 K 个训练样本
  4. 进行多数表决,统计 K 个样本中哪个类别的样本个数最多
  5. 将未知的样本归属到出现次数最多的类别

回归问题的处理流程:

  1. 计算未知样本到每一个训练样本的距离
  2. 将训练样本根据距离大小升序排列
  3. 取出距离最近的 K 个训练样本
  4. 把这个 K 个样本的目标值计算其平均值
  5. 作为将未知的样本预测的值

注意类别型的,字符串形式没有办法求平均值。

实际工作中经常使用交叉验证的方式,并且大部分k值较小。

knn 是寻找与未知样本最近距离的k个样本,采用的是欧式距离

KNN 分类算法实现

概述:找距离测试集最近的K个样本,进行投票,标签最多,就用它作为测试集的结果。

  • 思路1:分类思路,投票选最多的
  • 思路2:回归思路,求均值

分类思路

  1. 基于欧式距离计算每个训练集,距离测试集的距离
    • 欧氏距离:对应维度差值的平方和,开平方根
  2. 按照距离值,进行升序排列,找到距离最小的那K个样本
  3. 分类思路:投票选举,票数最多的标签值 -> 作为测试集的标签。
    • 如果标签票数一样,参考最简单的模型,就是距离最近的那个结果。奥卡姆剃刀

为什么预测结果是1呢,首先,看最近的3个邻居,那么最后是,0,1,1。于是1大于0,结果是1。

那如果看最近的2个邻居,一个 0 , 1,取那个呢,不要看顺序,它会取0,为什么,因为 0 小,它取最小值。

py 复制代码
# 导入 KNN 分类对象
from sklearn.neighbors import KNeighborsClassifier

# 参考3个最近的K
estimator = KNeighborsClassifier(n_neighbors=3)

# 准备数据集
x_train = [[1], [2], [3], [4]]
y_train = [0, 0, 1, 1]  # 分类法:2分法

# 准备测试集
x_test = [[5]]

# 模型训练
estimator.fit(x_train, y_train)

# 模型预测,获取测试集的预测标签
y_test = estimator.predict(x_test)

print(y_test)

KNN 回归算法实现

为什么结果是 0.15 呢。

找到距离最小的样本是,0.1 和 0.2

那相加 / 2 = 0.15

py 复制代码
# 导入 KNN 回归算法
from sklearn.neighbors import KNeighborsRegressor

# 参考2个最近的K
estimator = KNeighborsRegressor(n_neighbors=2)

# 准备数据集

# 验证
# 差值:     (9,17,8)      (10,5,1)     (20, 6, 2)   (18, 12, 5)
# 平方和       434           126           440          493
# 开平方根     20.83         11.22         20.98        22.20
x_train = [[12, 27, 3], [13, 15, 10], [23, 16, 9], [21, 22, 6]]
y_train = [0.1, 0.2, 0.3, 0.4]

# 准备测试集
x_test = [[3, 10, 11]]

# 模型训练
estimator.fit(x_train, y_train)

# 模型预测,获取测试集的预测结果
y_test = estimator.predict(x_test)

print(y_test)

距离计算方法

曼哈顿距离

也称为城市街区距离,曼哈顿城市特点:横平竖直

<math xmlns="http://www.w3.org/1998/Math/MathML"> d 12 = ∑ k = 1 n ∣ x 1 k − x 2 k ∣ d_{12} = \sum_{k=1}^{n} \left| x_{1k} - x_{2k} \right| </math>d12=∑k=1n∣x1k−x2k∣

举个例子:ABCD四点 X=[ [1,1], [2,2], [3,3], [4,4] ]

计算AB AC AD BC BD曼哈顿距离

经计算得: AB =|2−1|+ ⌈2−1⌉= 2

d = 2 4 6 2 4 2

为什么是这样呢,如下图

  1. 从 0 到 6的位置,走完x是六步,走完y是6步。
  2. 用 x和y 的 6 - 0,相加刚好是12。

切比雪夫距离

和曼哈顿距离有点像,曼哈顿不会斜着走,而切比雪夫是可以的,先斜着走,再直线距离。

其中 i 表示多个,可以用多组绝对值计算,i 表示的是维度索引,是一组而不是一个

<math xmlns="http://www.w3.org/1998/Math/MathML"> d 12 = m a x ( ∣ x 1 i − x 2 i ∣ ) d_{12} = max(\left| x_{1i} - x_{2i} \right|) </math>d12=max(∣x1i−x2i∣)

闵可夫斯基距离

不是一种新的距离的度量方式。

是对多个距离度量公式的概括性的表述

<math xmlns="http://www.w3.org/1998/Math/MathML"> d 12 = ∑ k = 1 n ∣ x 1 k − x 2 k ∣ p p d_{12} = \sqrt[p]{\sum_{k=1}^n \left| x_{1k} - x_{2k} \right| ^p} </math>d12=p∑k=1n∣x1k−x2k∣p

其中p是一个变参数:

  • 当 p=1 时,就是曼哈顿距离
  • 当 p=2 时,就是欧氏距离
  • 当 p→∞ 时,就是切比雪夫距离

根据 p 的不同,闵氏距离可表示某一类种的距离

特征预处理

归一化

特征的单位或者大小相差较大,或者某特征的方差相比其他的特征要大出几个数量级容易影响(支配)目标结果,使得一些模型(算法)无法学习到其它的特征。

其实也就是脏数据,比如没有人的身高3米多,也没有人的体重800斤。要对这些脏数据进行处理。

公式: <math xmlns="http://www.w3.org/1998/Math/MathML"> x ′ = x − m i n m a x − m i n x' = \frac{x - min}{max - min} </math>x′=max−minx−min

归一化后的值(范围在 [0,1])

上面能计算出归一化的值,当然也能反归一化,也就是逆向还原。

Min-Max标准化的区间映射公式:

<math xmlns="http://www.w3.org/1998/Math/MathML"> x ′ ′ = x ′ ∗ ( m x − m i ) + m i x'' = x' * (mx - mi) + mi </math>x′′=x′∗(mx−mi)+mi

这里有个特别注意的点:mx 和 mi,是区间最小值和最大值,是自己指定的,希望压到多大和多小。

解释: 公式解释

  • x:特征列中,某个具体要计算的值。
  • min:该特征列的最小值。
  • max:该特征列的最大值。
  • mx:区间的最大值,默认的区间是 [0,1],即:mx=1
  • mi:区间的最小值,默认的区间是 [0,1],即:mi=0

归一化的弊端:

即使该特征列的数据再多,也只会受到该列的最大值,最小值的影响,可能导致:数据不均衡。

如果最大值或者最小值又恰巧是异常值,就会导致计算结果偏差,又叫鲁棒性较差

案例,假设区间是,5,3

py 复制代码
# 导入归一化的包
from sklearn.preprocessing import MinMaxScaler

# 创建数据集
x_train = [[90, 2, 10, 40], [60, 4, 15, 45], [75, 3, 13, 46]]

# 创建归一化对象
# 解释:feature_range 表示区间,这里设置为3,5,默认是 0,1
transfer = MinMaxScaler(feature_range=(3, 5))

# 执行归一化操作
# fit_transform 针对于训练集的,即:训练 + 转换(归一化)
# fit 针对测试集,只有转换,归一化,返回归一化对象
x_train_new = transfer.fit_transform(x_train)
# transfer.fit(x_train)
print(x_train_new)

跑完答案

适用于小数据集的运算。

标准化

计算公式

<math xmlns="http://www.w3.org/1998/Math/MathML"> x ′ = ( x − m e a n ) σ x' = \frac{(x - mean)}{σ} </math>x′=σ(x−mean)

它适合于大数据集的应用场景。一般用标准化处理。

注意:数据计算是按列算的,比如:90,60,75

py 复制代码
# 导入标准化的包
from sklearn.preprocessing import StandardScaler

# 创建数据集
x_train = [[90, 2, 10, 40], [60, 4, 15, 45], [75, 3, 13, 46]]

# 创建标准化对象
transfer = StandardScaler()

# 执行标准化操作
# fit_transform 针对于训练集的,即:训练 + 转换(标准化)
# fit 针对测试集,只有转换,标准化,返回标准化对象
x_train_new = transfer.fit_transform(x_train)
# transfer.fit(x_train)
# print(x_train_new)

# 查看方差
print(transfer.var_)
# 查看平均值
print(transfer.mean_)
# 查看标准差
print(transfer.scale_)

总结

数据归一化

  • 如果出现异常点,影响了最大值和最小值,那么结果显然会发生改变
  • 应用场景:最大值与最小值非常容易受异常点影响,鲁棒性较差,只适合传统精确小数据场景

数据标准化

  • 如果出现异常点,由于具有一定数据量,少量的异常点对于平均值的影响并不大
  • 应用场景:适合现代嘈杂大数据场景。(以后就是用你了)

利用KNN算法对鸢尾花分类

实现流程:

  1. 获取数据集
  2. 数据基本处理
  3. 数据集预处理-数据标准化
  4. 机器学习(模型训练)
  5. 模型评估
  6. 模型预测

查看数据

  • x_train 是数据集,对应 data
  • y_train 是标签集,对应 target
  • target_names 是标签的语义
py 复制代码
from sklearn.datasets import load_iris

def test_load_iris():
    iris_data = load_iris()

    # iris_data,结果是字典。
    # print(iris_data)

    # 查看数据集,即:特征列,分别是:sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
    # print(iris_data.data[:5])

    # 查看数据集,即标签列
    # print(iris_data.target[:5])

    # 查特征列,标签列,即数据集的条数
    # print(len(iris_data.data), len(iris_data.target))

    # 查看数据集的列名
    # print(iris_data.feature_names)

    # 具体标签名 ['setosa' 'versicolor' 'virginica']
    print(iris_data.target_names)

    # 查看所有 key 的名字
    print(iris_data.keys())

if __name__ == '__main__':
    # 加载数据集
    test_load_iris()

转为 DataFrame 对象 / 绘制散点图

py 复制代码
if __name__ == '__main__':

    # 中文显示
    plt.rcParams['font.sans-serif'] = ['SimHei']  # 黑体
    plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题


    # 加载数据集
    iris_data = load_iris()
    # 封装成对象
    frame = pd.DataFrame(data=iris_data.data, columns=iris_data.feature_names, )
    frame['label'] = iris_data.target

    # 绘制成散点图
    # fit_reg 去除拟合回归线
    sns.lmplot(data=frame, y='petal width (cm)', x='petal length (cm)',
               hue='label', fit_reg=False)

    plt.title('鸢尾花数据集展示')
    plt.show()

区分数据集和测试集

py 复制代码
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

if __name__ == '__main__':
    # 加载数据集
    iris_data = load_iris()
    # 特征是 data,标签是 target

    # 切分训练集和测试集
    # 参数1:要被切分的特征
    # 参数2:要被切分的标签
    # 参数3:测试集的比例,这里是20
    # 参数4:随机种子,种子一致
    x_train, x_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target, test_size=0.2, random_state=70)

    # 打印测试结果
    print(f'训练集的特征: {x_train}')
    print(f'训练集的标签: {y_train}')
    print(f'训练集的条数: {len(x_train)}, {len(y_train)}')

    print(f'测试集的特征: {x_test}')
    print(f'测试集的标签: {y_test}')
    print(f'测试集的条数: {len(x_test)}, {len(y_test)}')

模型预测和评估

py 复制代码
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler

if __name__ == '__main__':
    # 1. 加载数据集
    iris_data = load_iris()
    # 2. 数据预处理
    x_train, x_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target, test_size=0.2, random_state=70)

    # 3. 特征工程
    # 此处无需手动实现,数据已经转换完毕,分别是:花萼的长度,花萼的宽度,花瓣的长度,花瓣的宽度
    # 字段名如下:sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)

    # 4. 特征预处理(归一化、标准化),我们发现特征共 4 列,差值都不大。可以不做,这里学习一下。
    # 标准化
    transfer = StandardScaler()
    # 对于训练集数据,做训练+转换
    x_train = transfer.fit_transform(x_train)
    # 对于测试机数据 做转换
    x_test = transfer.transform(x_test)

    # 5. 特征降维、特征选取、特征组合、这里不需要做,因为数据比较简单

    # 6. 模型训练,选分类
    estimator = KNeighborsClassifier(n_neighbors=5)
    # 具体训练代码
    estimator.fit(x_test, y_test)

    # 7. 模型预测
    # 对测试集进行数据预测
    y_predict = estimator.predict(x_test)
    print(f'模型预测的结果为{y_predict}')
    print(f'测试集真实数据为{y_test}')

    # 对新的数据集做测试
    # 准备新的数据集
    x_test_new = [[3.6, 1.5, 1.5, 0.3]]
    # 对新数据重新标准化处理
    x_test_new = transfer.transform(x_test_new)
    # 模型预测
    y_predict_new = estimator.predict(x_test_new)
    print(f'模型预测结果为:{y_predict_new}')
    # 查看上述的特征 -> 各结果标签的 概率值,结果为 0 1 2,查看每个值的概率
    y_predict_proba_new = estimator.predict_proba(x_test_new)
    print(f'模型预测概率结果为:{y_predict_proba_new}') # 模型预测概率结果为:[[0.2 0.8 0. ]],根据顺序,表示百分百80是 1 分类

    # 8. 模型评估
    # KNN 算法评估,主要参考 准确率,即:预测正确的数量 / 总数量
    # 方式一:针对于测试集的特征 和 测试集的标签,进行预测,一般不太准确,不建议使用
    # 这种方式可以预测前和预测后进行评估,因为没有用到 predict 的值
    print(f'准确率:{estimator.score(x_test, y_test)}') # 准确率:0.9666666666666667

    # 方式二:针对预测值(y_predict)和测试集的真实标签(y_test)进行评估
    # 主要是做模型预测后的评估

    print(f'模型准确率:{accuracy_score(y_test, y_predict)}') # 模型准确率:0.9666666666666667

fit_transform 和 transform 的区别

fit_transform 是先基于训练集的特征做,拟合,例如获取均值,标准差,方差等信息,再基于内置好的模型(标准化)对象,调整参数,进行转换。

比如:

  • StandardScaler → 计算每列的均值和标准差
  • MinMaxScaler → 计算每列的最小值和最大值
  • PCA → 计算协方差矩阵和特征向量

上面这些,只会学习一次,后面直接使用这些参数。

而 transform

作用:使用 fit 学到的参数对数据进行转换

比如:

  • 标准化:(X - mean) / std
  • 归一化:(X - min) / (max - min)

不会重新计算均值/标准差,直接用已有参数。

总结

  • fit_transform 再第一次使用

交叉验证

什么是交叉验证?

它是一种更加完善的,可信度更高的模型预估方式,思路是:把数据集分成 N 份,每次都取 1 份当作测试集,其他的当作训练集,然后计算模型的评分,接下来,再用下一份作为测试集,其他作为训练集,计算模型评分,分成几份,就进行几次计算,最后计算所有评分的均值,当做模型的最终评分。

交叉验证法原理:将数据集划分为 cv=4 份

  1. 第一次:把第一份数据做验证集,其他数据做训练
  2. 第二次:把第二份数据做验证集,其他数据做训练
  3. ... 以此类推,总共训练4次,评估4次。
  4. 使用训练集+验证集多次评估模型,取平均值做交叉验证为模型得分
  5. 若k=5模型得分最好,再使用全部训练集(训练集+验证集) 对k=5模型再训练一遍,再使用测试集对k=5模型做评估

好处:交叉验证的结果,比单一切分训练集和测试集,获取的评分结果,可信度更高

细节:

  1. 把数据集分成 N 份,就叫 N折交叉验证。
  2. 交叉验证一般和网格搜索一起使用。

交叉验证法,是划分数据集的一种方法,目的就是为了得到更加准确可信的模型评分。

网格搜索

为什么需要网格搜索?

  • 模型有很多超参数,其能力也存在很大的差异。需要手动产生很多超参数组合,来训练模型
  • 每组超参数都采用交叉验证评估,最后选出最优参数组合建立模型。

网格搜索是模型调参的有力工具。寻找最优超参数的工具!

只需要将若干参数 传递给网格搜索对象 ,它自动帮我们完成不同超参数的组合、模型训练、模型评估,最终返回一组最优的超参数

网络搜索就说寻找最优的超惨。

网格搜索 + 交叉验证的强力组合 (模型选择和调优)

  • 交叉验证解决模型的数据输入问题(数据集划分)得到更可靠的模型
  • 网格搜索解决超参数的组合
  • 两个组合再一起形成一个模型参数调优的解决方案

总而言之,网络搜索 + 交叉验证目的就说为了寻找模型的最优解决方案,追求较高的效率。

交叉验证和网格搜索实战

一般会对,下面代码的 CV 循环,比如循环20次,它会告诉20次那次好,然后由我们逐个的去判断。

原因:

  1. 因为折数不一样,超参也不一样。
  2. 只跑一次是判断不出来最终结果的
py 复制代码
estimator = GridSearchCV(estimator=estimator, param_grid=param_dict, cv=5)

完整代码

py 复制代码
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler

if __name__ == '__main__':
    # 1. 加载数据集
    iris_data = load_iris()
    # 2. 数据预处理
    x_train, x_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target, test_size=0.2,
                                                        random_state=22)
    # 3. 特征预处理
    transfer = StandardScaler()
    transfer.fit_transform(x_train)
    transfer.transform(x_test)

    # 4. 模型训练,选分类,注意此时不需要传递 K 的值
    estimator = KNeighborsClassifier()
    # 4.1 定义字典,记录超参可能出现的值
    # 注意这些值不能写死,
    param_dict = {'n_neighbors': list(range(1, 21))}
    # 4.2 创建网格搜索对象
    # 参数1:模型对象。参数2:超参字典,参数3:交叉验证的折数
    # 返回一个更加强大的模型对象
    estimator = GridSearchCV(estimator=estimator, param_grid=param_dict, cv=5)

    # 模型训练
    estimator.fit(x_test, y_test)

    # 5. 模型预测
    y_predict = estimator.predict(x_test)
    print(f'模型预测的结果为{y_predict}')

    # 6. 打印网格搜索 和 交叉验证的 结果
    print(estimator.best_score_)  # 最优组合的平均分 0.9666666666666668
    print(estimator.best_estimator_)  # 最优组合的模型对象 KNeighborsClassifier(n_neighbors=6)
    print(estimator.best_params_)  # 最优组合的超参(仅供参考) {'n_neighbors': 6}
    print(estimator.cv_results_)  # 所有组合的 评分结果(过程)
    # {'mean_fit_time': array([0.00020003, 0.00019999, 0.00019984, 0.00040126, 0.00019999]), 'std_fit_time': array([0.00040007, 0.00039997, 0.00039968, 0.00049144, 0.00039997]), 'mean_score_time': array([0.00110083, 0.00140481, 0.00130205, 0.00100107, 0.00140252]), 'std_score_time': array([0.00020204, 0.00037565, 0.00039944, 0.00063234, 0.00049298]), 'param_n_neighbors': masked_array(data=[1, 2, 3, 5, 7],
    #              mask=[False, False, False, False, False],
    #        fill_value=999999), 'params': [{'n_neighbors': 1}, {'n_neighbors': 2}, {'n_neighbors': 3}, {'n_neighbors': 5},
    #        {'n_neighbors': 7}], 'split0_test_score': array([0.83333333, 0.83333333, 0.83333333, 0.83333333, 0.83333333]),
    #        'split1_test_score': array([1.        , 0.83333333, 1.        , 0.66666667, 0.66666667]), 'split2_test_score':
    #        array([1., 1., 1., 1., 1.]), 'split3_test_score': array([1., 1., 1., 1., 1.]), 'split4_test_score': array([1.
    #        , 1.        , 1.        , 1.        , 0.83333333]), 'mean_test_score': array([0.96666667, 0.93333333, 0.96666667,
    #        0.9       , 0.86666667]), 'std_test_score': array([0.06666667, 0.08164966, 0.06666667, 0.13333333, 0.12472191]),
    #        'rank_test_score': array([1, 3, 1, 4, 5], dtype=int32)}

    # 7. 结合上述的结果,对模型再次评估
    estimator = KNeighborsClassifier(n_neighbors=6)
    estimator.fit(x_train, y_train)

    y_predict = estimator.predict(x_test)
    print(f'准确率:{accuracy_score(y_test, y_predict)}') # 准确率:0.9333333333333333

数字识别

根据像素点生成图片

已知数据

  • MNIST手写数字识别
  • 1999年发布,成为分类算法基准测试的基础
  • MNIST仍然是研究人员和学习者的可靠资源

从数万个手写图像的数据集中正确识别数字

数据介绍

  1. 数据文件 train.csv 和 test.csv 包含从 0 到 9 的手绘数字的灰度图像
  2. 每个图像高 28 像素,宽28 像素,共784个像素
  3. 每个像素取值范围[0,255],取值越大意味着该像素颜色越深
  4. 训练数据集(train.csv)共785列。
    • 第一列为标签,为该图片对应的手写数字。其余784列为该图像的像素值
  5. 训练集中的特征名称均有pixel前缀,后面的数字([0,783])代表了像素的序号。
py 复制代码
import sys
from collections import Counter
import matplotlib.pyplot as plt
import pandas as pd

if __name__ == '__main__':

    idx = 20

    # 获取数据
    data = pd.read_csv(r'E:\BaiduNetdiskDownload\手写数字识别.csv')
    # 判断用户传入的索引是否合法
    if idx < 0 or idx >= len(data):
        print('索引不合法')
        sys.exit(1)

    # 获取所有的行,从第一列开始,不包含第 0 列
    x = data.iloc[:, 1:]
    y = data.iloc[:, 0]

    # 查看下每个数字一共有多少个,注意是 collections 下的

    # 数字的种类Counter({1: 4684, 7: 4401, 3: 4351, 9: 4188, 2: 4177, 6: 4137, 0: 4132, 4: 4072, 8: 4063, 5: 3795})
    print(f'数字的种类{Counter(y)}')
    print(f'像素的形状{x.shape}')

    print(f'索引对应的数字是:{y[idx]}')

    # 绘制图片
    # x.iloc[idx] 返回的是列名+数据
    # x.iloc[idx].values 整合成一个数组
    # reshape(28, 28) 转换为 28*28 的像素点,因为图片就说 28 * 28
    # 结果就是 一个 28*28 的矩阵,,每28个是一个数组
    digit = x.iloc[idx].values.reshape(28, 28)

    # 绘制图片
    plt.imshow(digit, cmap='gray')  # 灰度图
    # 不显示坐标轴
    plt.axis('off')
    plt.show()

模型保存

py 复制代码
import joblib
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler

if __name__ == '__main__':
    # 1. 获取数据
    data = pd.read_csv('./手写数字识别.csv')

    # 2. 数据预处理
    # 像素点,特征
    x = data.iloc[:, 1:]
    # 标签
    y = data.iloc[:, 0]

    # stratify 表示参考 y 轴数据分布划分数据集和测试集
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=7, stratify=y)

    # 3. 特征工程
    transfer = StandardScaler()
    transfer.fit_transform(x_train)
    transfer.transform(x_test)

    # 4. 模型训练
    estimator = KNeighborsClassifier(n_neighbors=5)
    estimator.fit(x_train, y_train)

    # 5. 模型评估
    print(f'准确率:{estimator.score(x_test, y_test)}')  # 准确率:0.9653571428571428

    # 6. 模型评估
    # pkl 是 pandas 独有的文件格式
    # pth 也是可以的
    joblib.dump(estimator, '/knn.pkl')

图片数字识别

因为,加载的图片,是一个0~1的数值,需要 * 255

py 复制代码
import joblib
import matplotlib.pyplot as plt
import pandas as pd

if __name__ == '__main__':
    # 1. 加载图片
    # 打印出来是 28 * 28 的矩阵
    img = plt.imread('./demo.png')

    # 值如果是2,表示灰度图
    print(img.ndim)

    # 转 0~255
    img = img * 255

    # 显示图片
    plt.imshow(img, cmap='gray')
    plt.show()

    # 转换成 一行 矩阵,最多是 28 * 28 = 784列,但是还需要计算,可以直接写 -1,表示能转多少转多少
    img = img.reshape(1, -1)

    # 读取模型
    knn = joblib.load('/knn.pkl')

    # 用训练时的特征名构造
    feature_names = knn.feature_names_in_
    img_df = pd.DataFrame(img, columns=feature_names)

    # 模型预测
    y_predict = knn.predict(img_df)
    print(y_predict)
相关推荐
interception2 小时前
爬虫逆向,瑞数6,补环境,国家专利
javascript·爬虫·python·网络爬虫
初次攀爬者2 小时前
知识库-向量化功能-文本文件向量化
后端
laocooon5238578862 小时前
相对名次算法的处理python
开发语言·python·算法
星火开发设计2 小时前
Python冒泡排序详解:从原理到代码实现与优化
开发语言·笔记·python·开源·排序算法·课程设计
Java水解2 小时前
MySQL索引分析以及相关面试题
后端·mysql·面试
小智RE0-走在路上2 小时前
Python学习笔记(9) --文件操作
笔记·python·学习
万俟淋曦2 小时前
【论文速递】2025年第38周(Sep-14-20)(Robotics/Embodied AI/LLM)
人工智能·深度学习·机器学习·机器人·大模型·论文·具身智能
总是学不会.2 小时前
[特殊字符] 自动分区管理系统实践:让大型表维护更轻松
java·后端·数据库开发·开发
愈努力俞幸运2 小时前
Python heapq (堆/优先队列)
python