今天是参加昇思学习打卡营的第17天,学习任务是K近邻算法实现红酒聚类。
以下是内容关键点概要:
- 了解KNN的基本概念;
- 了解如何使用MindSpore进行KNN实验。
- K近邻算法原理介绍
- 数据处理
- 模型构建-计算距离
- 模型预测
K近邻算法原理介绍
K近邻算法(K-Nearest-Neighbor, KNN)是一种用于分类和回归的非参数统计方法,最初由 Cover和Hart于1968年提出(Cover等人,1967),是机器学习最基础的算法之一。它正是基于以上思想:要确定一个样本的类别,可以计算它与所有训练样本的距离,然后找出和该样本最接近的k个样本,统计出这些样本的类别并进行投票,票数最多的那个类就是分类的结果。KNN的三个基本要素:
-
K值,一个样本的分类是由K个邻居的“多数表决”确定的。K值越小,容易受噪声影响,反之,会使类别之间的界限变得模糊。
-
距离度量,反映了特征空间中两个样本间的相似度,距离越小,越相似。常用的有Lp距离(p=2时,即为欧式距离)、曼哈顿距离、海明距离等。
-
分类决策规则,通常是多数表决,或者基于距离加权的多数表决(权值与距离成反比)。
-
分类问题
-
预测算法(分类)的流程如下:
(1)在训练样本集中找出距离待测样本x_test最近的k个样本,并保存至集合N中;
(2)统计集合N中每一类样本的个数𝐶𝑖,𝑖=1,2,3,...,𝑐𝐶𝑖,𝑖=1,2,3,...,𝑐;
(3)最终的分类结果为argmax𝐶𝑖𝐶𝑖 (最大的对应的𝐶𝑖𝐶𝑖)那个类。
在上述实现过程中,k的取值尤为重要。它可以根据问题和数据特点来确定。在具体实现时,可以考虑样本的权重,即每个样本有不同的投票权重,这种方法称为带权重的k近邻算法,它是一种变种的k近邻算法。
-
回归问题
假设离测试样本最近的k个训练样本的标签值为𝑦𝑖𝑦𝑖,则对样本的回归预测输出值为:
-
即为所有邻居的标签均值。
带样本权重的回归预测函数为:
其中𝑤𝑖𝑤𝑖为第个𝑖𝑖样本的权重。
-
距离的定义
KNN算法的实现依赖于样本之间的距离,其中最常用的距离函数就是欧氏距离(欧几里得距离)。ℝ𝑛𝑅𝑛空间中的两点𝑥𝑥和𝑦𝑦,它们之间的欧氏距离定义为:
需要特别注意的是,使用欧氏距离时,应将特征向量的每个分量归一化,以减少因为特征值的尺度范围不同所带来的干扰,否则数值小的特征分量会被数值大的特征分量淹没。
其它的距离计算方式还有Mahalanobis距离、Bhattacharyya距离等。
实验环境
预备知识:
- 熟练使用Python。
- 具备一定的机器学习理论知识,如KNN、无监督学习、 欧式距离等。
实验环境:
- MindSpore 2.0(MindSpore版本会定期更新,本指导也会定期刷新,与版本配套);
- 本案例支持win_x86和Linux系统,CPU/GPU/Ascend均可运行。
- 如果在本地运行此实验,请参考《MindSpore环境搭建实验手册》在本地安装MindSpore。
数据处理
数据准备
Wine数据集是模式识别最著名的数据集之一,Wine数据集的官网:Wine Data Set。这些数据是对来自意大利同一地区但来自三个不同品种的葡萄酒进行化学分析的结果。数据集分析了三种葡萄酒中每种所含13种成分的量。这些13种属性是
- Alcohol,酒精
- Malic acid,苹果酸
- Ash,灰
- Alcalinity of ash,灰的碱度
- Magnesium,镁
- Total phenols,总酚
- Flavanoids,类黄酮
- Nonflavanoid phenols,非黄酮酚
- Proanthocyanins,原花青素
- Color intensity,色彩强度
- Hue,色调
- OD280/OD315 of diluted wines,稀释酒的OD280/OD315
- Proline,脯氨酸
- 方式一,从Wine数据集官网下载wine.data文件。
- 方式二,从华为云OBS中下载wine.data文件。
-
Key Value Key Value Data Set Characteristics: Multivariate Number of Instances: 178 Attribute Characteristics: Integer, Real Number of Attributes: 13 Associated Tasks: Classification Missing Values? No
from download import download
# 下载红酒数据集
url = "https://ascend-professional-construction-dataset.obs.cn-north-4.myhuaweicloud.com:443/MachineLearning/wine.zip"
path = download(url, "./", kind="zip", replace=True)
数据读取与处理
导入MindSpore模块和辅助模块
在生成数据之前,导入需要的Python库。
目前使用到os库,为方便理解,其他需要的库,我们在具体使用到时再说明。
详细的MindSpore的模块说明,可以在MindSpore API页面中搜索查询。
可以通过context.set_context来配置运行需要的信息,譬如运行模式、后端信息、硬件等信息。
导入context模块,配置运行需要的信息。
# 使得matplotlib生成的图形能够在Jupyter Notebook中内联显示
%matplotlib inline
# 导入操作系统接口的库,用于文件和目录操作
import os
# 导入csv库,用于读写CSV文件
import csv
# 导入NumPy库,一个用于科学计算的库,提供大量的数学函数操作
import numpy as np
# 导入matplotlib的pyplot模块,用于数据可视化和绘图
import matplotlib.pyplot as plt
# 导入MindSpore库,并设置别名为ms,MindSpore是一个深度学习框架
import mindspore as ms
# 从MindSpore库中导入nn模块,包含构建神经网络所需的类
from mindspore import nn
# 从MindSpore库中导入ops模块,包含执行基础操作的函数
from mindspore import ops
# 设置MindSpore的上下文,指定计算将在CPU上执行
ms.set_context(device_target="CPU")
读取Wine数据集wine.data
,并查看部分数据。
# 使用with语句打开名为'wine.data'的CSV文件,确保文件使用后正确关闭
with open('wine.data') as csv_file:
# 使用csv.reader函数读取CSV文件,并将分隔符设置为逗号 ","
# 将读取的所有行作为列表存储到变量data中,其中每个元素是一个行列表
data = list(csv.reader(csv_file, delimiter=','))
# 打印data列表中索引61到66的行(Python中列表索引从0开始,所以这是第57到62行)
# 然后连接打印索引129到131的行(即第130到133行)
print(data[56:62]+data[130:133])
取三类样本(共178条),将数据集的13个属性作为自变量𝑋𝑋。将数据集的3个类别作为因变量𝑌𝑌。
# 导入NumPy库,用于数学运算和处理数据结构
import numpy as np
# 使用列表推导式和条件列表推导式结合,创建一个新的列表,其中每个子列表
# 包含从data列表的每个子列表(s)中索引1开始的元素(即第二列及之后的列)的浮点数转换
# 外层列表推导式遍历data列表的前178个元素(第0行至第177行,因为索引从0开始)
X = np.array(
[[float(x) for x in s[1:]] for s in data[:178]],
np.float32 # 指定数组的数据类型为32位浮点数
)
# 使用列表推导式创建一个新的列表,包含data列表的每个子列表(s)中的第一个元素
# 这些元素是原始数据集中的分类标签,转换成整数类型
Y = np.array(
[s[0] for s in data[:178]], # 取每个子列表的第一个元素,即分类标签
np.int32 # 指定数组的数据类型为32位整数
)
取样本的某两个属性进行2维可视化,可以看到在某两个属性上样本的分布情况以及可分性。
# 定义葡萄酒数据集中的特征名称列表
attrs = ['Alcohol', 'Malic acid', 'Ash', 'Alcalinity of ash', 'Magnesium', 'Total phenols',
'Flavanoids', 'Nonflavanoid phenols', 'Proanthocyanins', 'Color intensity', 'Hue',
'OD280/OD315 of diluted wines', 'Proline']
# 设置matplotlib图形的尺寸
plt.figure(figsize=(10, 8))
# 循环四次以创建子图
for i in range(0, 4):
# 创建一个2x2的子图网格,并选择第i+1个子图
plt.subplot(2, 2, i+1)
# 计算属性索引,每次循环计算两个属性的索引
a1, a2 = 2 * i, 2 * i + 1
# 在子图上绘制三个类别的散点图
# 假设X数组的前59行是类别1,59到130行是类别2,剩余的是类别3
plt.scatter(X[:59, a1], X[:59, a2], label='1') # 绘制类别1的散点
plt.scatter(X[59:130, a1], X[59:130, a2], label='2') # 绘制类别2的散点
plt.scatter(X[130:, a1], X[130:, a2], label='3') # 绘制类别3的散点
# 设置子图的X轴和Y轴标签,使用属性名列表attrs
plt.xlabel(attrs[a1])
plt.ylabel(attrs[a2])
# 显示图例
plt.legend()
# 显示所有子图
plt.show()
将数据集按128:50划分为训练集(已知类别样本)和验证集(待验证样本):
# 导入NumPy库,用于数学运算和处理数据结构
import numpy as np
# 使用np.random.choice函数从178个样本中随机选择128个作为训练索引
# 参数replace=False表示选择是不放回的,即每个样本只能被选一次
train_idx = np.random.choice(178, 128, replace=False)
# 创建一个包含所有样本索引的集合,然后从这个集合中减去训练索引的集合
# 结果是一个包含测试样本索引的集合
# 最后将测试索引的集合转换回NumPy数组
test_idx = np.array(list(set(range(178)) - set(train_idx)))
# 根据训练索引从X和Y中选择行,创建训练集的特征和标签数组
X_train, Y_train = X[train_idx], Y[train_idx]
# 根据测试索引从X和Y中选择行,创建测试集的特征和标签数组
X_test, Y_test = X[test_idx], Y[test_idx]
模型构建--计算距离
利用MindSpore提供的tile, square, ReduceSum, sqrt, TopK
等算子,通过矩阵运算的方式同时计算输入样本x和已明确分类的其他样本X_train的距离,并计算出top k近邻
# 导入MindSpore的nn模块和ops模块
import mindspore as ms
from mindspore import nn, ops
import numpy as np
class KnnNet(nn.Cell):
def __init__(self, k):
super(KnnNet, self).__init__() # 调用基类的构造函数
self.k = k # 保存分类时考虑的最近邻居数量
def construct(self, x, X_train):
# 将输入x平铺,以匹配训练集中样本的数量
x_tile = ops.tile(x, (128, 1))
# 计算输入x与训练集中每个样本的平方差
square_diff = ops.square(x_tile - X_train)
# 对平方差求和,得到平方距离
square_dist = ops.sum(square_diff, 1)
# 开方得到实际距离
dist = ops.sqrt(square_dist)
# 取距离最小的k个邻居,-dist表示值越大,样本就越接近
values, indices = ops.topk(-dist, self.k)
# 返回最近邻的索引
return indices
def knn(knn_net, x, X_train, Y_train):
# 将x和X_train转换为MindSpore的Tensor
x, X_train = ms.Tensor(x), ms.Tensor(X_train)
# 使用KnnNet模型获取最近邻的索引
indices = knn_net(x, X_train)
# 初始化一个列表,用于统计每个类别在最近邻中的出现次数
topk_cls = [0] * len(indices.asnumpy())
# 遍历所有最近邻的索引
for idx in indices.asnumpy():
# 根据索引获取对应的类别,并增加该类别的计数
topk_cls[Y_train[idx]] += 1
# 返回出现次数最多的类别,即预测的类别
cls = np.argmax(topk_cls)
return cls
模型预测
在验证集上验证KNN算法的有效性,取𝑘=5𝑘=5,验证精度接近80%,说明KNN算法在该3分类任务上有效,能根据酒的13种属性判断出酒的品种。
# 初始化准确率计数器为0
acc = 0
# 创建KNN网络实例,这里k=5,即选择最近的5个邻居进行投票
knn_net = KnnNet(5)
# 遍历测试集
for x, y in zip(X_test, Y_test):
# 对每个测试样本进行预测
pred = knn(knn_net, x, X_train, Y_train)
# 如果预测结果与真实标签相同,则准确率计数器增加
acc += (pred == y)
# 打印每个测试样本的真实标签和预测结果
print('label: %d, prediction: %s' % (y, pred))
# 计算整体的验证准确率,即正确预测的数量除以测试集的大小
print('Validation accuracy is %f' % (acc/len(Y_test)))
Validation accuracy is 0.680000
学习心得:
-
通过实践,我对KNN算法的原理和工作流程有了更深刻的理解。KNN算法通过测量不同指标之间的距离来进行分类,这是一种直观且易于理解的方法。
-
学习过程中,我意识到距离度量在KNN算法中的重要性。欧几里得距离是最常见的度量方式,但根据数据的特性,可能需要考虑曼哈顿距离或闵可夫斯基距离等其他度量。
-
K值的选择对KNN算法的性能有显著影响。太小的K值可能会使模型对噪声敏感,而太大的K值则可能导致模型过于平滑,无法捕捉数据的局部特性。
-
我认识到KNN算法的优点,如简单易懂、无需训练过程、可用于非线性数据。同时,也了解到其缺点,比如计算成本高、对不平衡数据集表现不佳、需要大量的内存存储。
-
在应用KNN算法之前,对数据进行适当的预处理,如标准化或归一化,对提高算法性能至关重要。
-
通过KNN算法在红酒数据集上的应用,我进一步理解了聚类和分类的区别。聚类是无监督学习,而分类是有监督学习。
加油!!!