k近邻算法的特点
- 思想极度简单
- 应用数学知识少(近乎为零)
- 效果好(缺点?)
- 可以解释机器学习算法使用过程中的很多细节问题
- 更完整的刻画机器学习应用的流程
k近邻算法
k近邻算法整体是这样的一个算法,我们已经知道的这些数据点其实是分布在一个特征空间中的,通常呢,我们使用一个二维的平面来演示。比如说现在这个例子就是一个肿瘤病人相关的数据,横轴代表一个特征,是发现了这个肿瘤病人他肿块的大小,而纵轴呢,是发现这个肿块的时间。对于每一个病人,他的肿块大小和发现这个肿瘤的时间就构成了在这个特征平面中的一个点。这些点中有恶性的肿瘤用蓝色表示,良性的肿瘤用红色表示,图中一共有八个数据点。
如果现在新来的一个病人,他在特征空间中对应的位置在这里,我们怎么判断这个新来的绿色点,它最有可能是良性肿瘤患者还是恶性肿瘤患者呢?K近邻算法就是这样的一个算法,首先我们必须取一个K值,假设K等于三。对于每一个新的数据点,K近邻算法做的事情就是在所有的这些点中寻找离这个新的点最近的三个点。然后这些最近的点以他们自己的label,自己的结果进行投票。在这个例子中,离这个新的点最近的三个点都是代表恶性肿瘤的蓝色的点,所以蓝色对红色是3:0。因此K近邻算法就说这个新的点有很高的概率,它其实也是一个蓝色的点,很有可能是一个恶性肿瘤的患者。
K近邻算法的本质是认为两个样本如果他们足够的相似的话,那么它就有更高的概率属于同一个类别。当然可能只看离它最近的那一个样本是不靠谱的,所以我们多看几个样本,一共看K个样本,看哪个类别最多,我们就认为这个新的样本最有可能属于哪个类别, 可以理解为少数服从多数。
在这里,我们描述两个样本是否相似,这个相似性是通过两个样本在特征空间中的距离来进行描述的。
我们再举一个例子, 比如现在又新来了一个病人
我们来看一下离它最近的三个点, 那么对于这个新来的点, 红色和蓝色的比率时候2:1, 所以红色胜出, 我们来看一下离它最近的三个点, 那么对于这个新来的点, 红色和蓝色的比率时候2:1, 所以红色胜出, 那么K近邻算法就告诉我们,对于这个新病人来说, 他更可能是一个良性肿瘤患者
k近邻算法首先可以解决的是我们之前介绍的监督学习分类的问题, 不过k近邻算法也可以解决回归问题, 后续我们将进行介绍
计算距离我们使用欧拉距离公式:
欧拉距离
(
x
(
a
)
−
x
(
b
)
)
2
+
(
y
(
a
)
−
y
(
b
)
)
2
(
x
(
a
)
−
x
(
b
)
)
2
+
(
y
(
a
)
−
y
(
b
)
)
2
+
(
z
(
a
)
−
z
(
b
)
)
2
(
X
1
(
a
)
−
X
1
(
b
)
)
2
+
(
X
2
(
a
)
−
X
2
(
b
)
)
2
+
…
+
(
X
n
(
a
)
−
X
n
(
b
)
)
2
\begin{array}{c}\sqrt{\left(x^{(a)}-x^{(b)}\right)^{2}+\left(y^{(a)}-y^{(b)}\right)^{2}} \\\sqrt{\left(x^{(a)}-x^{(b)}\right)^{2}+\left(y^{(a)}-y^{(b)}\right)^{2}+\left(z^{(a)}-z^{(b)}\right)^{2}} \\\sqrt{\left(X_{1}^{(a)}-X_{1}^{(b)}\right)^{2}+\left(X_{2}^{(a)}-X_{2}^{(b)}\right)^{2}+\ldots+\left(X_{n}^{(a)}-X_{n}^{(b)}\right)^{2}}\end{array}
(x(a)−x(b))2+(y(a)−y(b))2(x(a)−x(b))2+(y(a)−y(b))2+(z(a)−z(b))2(X1(a)−X1(b))2+(X2(a)−X2(b))2+…+(Xn(a)−Xn(b))2
第二个公式是欧拉距离公式在立体几何中的拓展
不过对于我们要处理的这个特征向量很有可能要比三维要高,那么此时我们使用这种XYZ一个一个的字母是不方便的所以在这里我们又添加了一个角标,X就是代表我们整个的数据, x n ( a ) {x_{n}}^{(a)} xn(a)就代表a样本的第n个维度, x n ( b ) {x_{n}}^{(b)} xn(b)就代表b样本的第n个维度, 那么这个式子其实就是在计算ab两个样本之间的距离, 这个计算方式就是A样本的每个维度的特征减去B样本中相应的每个维度的特征然后平方然后进行相加最后再开个根号, 在公式中我们假设我们的特征向量一共有N个维度, 也就是说一共有n个特征。这就是高维的欧拉距离公式的计算
但是这种公式看起来比较不方便, 所以通常我们使用连加的形式来进行表示
∑
i
=
1
n
(
X
i
(
a
)
−
X
i
(
b
)
)
2
\sqrt{\sum_{i=1}^{n}\left(X_{i}^{(a)}-X_{i}^{(b)}\right)^{2}}
i=1∑n(Xi(a)−Xi(b))2
它表示从i=1开始, 直到i=n结束, 每次都将A样本的第i个特征和B样本的第i个特征相减再平方, 最后所有的项加起来开个根号
接下来我们先采用最原始的方式实现一下KNN分类算法, 先大致的了解一下KNN算法的实现思路(以下代码均在jupyter 中实现)
在此之前我们先使用matplotlib库可视化一下数据集中的数据
import numpy as np
import matplotlib.pyplot as plt
raw_data_X = [[3.393533211, 2.331273381],
[3.110073483, 1.781539638],
[1.343808831, 3.368360954],
[3.582294042, 4.679179110],
[2.280362439, 2.866990263],
[7.423436942, 4.696522875],
[5.745051997, 3.533989803],
[9.172168622, 2.511101045],
[7.792783481, 3.424088941],
[7.939820817, 0.791637231]
]
# 每一个样本的类别, 0代表良性肿瘤, 1代表恶性肿瘤
raw_data_y = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
# 将所有的数据都作为我们的训练集
# 在这里我们进行命名训练集, 也就是对应的是训练使用的这些样本的label
X_train = np.array(raw_data_X)
y_train = np.array(raw_data_y)
X_train
array([[3.39353321, 2.33127338],
[3.11007348, 1.78153964],
[1.34380883, 3.36836095],
[3.58229404, 4.67917911],
[2.28036244, 2.86699026],
[7.42343694, 4.69652288],
[5.745052 , 3.5339898 ],
[9.17216862, 2.51110105],
[7.79278348, 3.42408894],
[7.93982082, 0.79163723]])
y_train
array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1])
# 进行可视化我们训练的样本
plt.scatter(X_train[y_train==0, 0], X_train[y_train==0, 1], color="g")
plt.scatter(X_train[y_train==1, 0], X_train[y_train==1, 1], color="r")
plt.show()
# 假设新输入一个数据
x = np.array([8.093607318, 3.365731514])
# 进行可视化我们训练的样本
plt.scatter(X_train[y_train==0, 0], X_train[y_train==0, 1], color="g")
plt.scatter(X_train[y_train==1, 0], X_train[y_train==1, 1], color="r")
plt.scatter(x[0], x[1], color="b")
plt.show()
KNN的过程
from math import sqrt
# 原始方法(笨方法)
# 该列表是用于存储它们之间的距离
distances = []
for x_train in X_train:
d = sqrt(np.sum((x_train - x)**2))# 让两个样本进行对应相减得到两点之间的距离
distances.append(d)
distances
[4.812566907609877,
5.229270827235305,
6.749798999160064,
4.6986266144110695,
5.83460014556857,
1.4900114024329525,
2.354574897431513,
1.3761132675144652,
0.3064319992975,
2.5786840957478887]
nearest = np.argsort(distance)
# 查看训练数据集中离新输入的患者最近的几个点
nearest
array([8, 7, 5, 6, 9, 3, 0, 1, 4, 2], dtype=int64)
在 k-最近邻(k-Nearest Neighbors,简称 k-NN)算法中,“k” 指的是一个超参数,表示用于确定新样本所属类别的邻居数量。具体来说,k-NN 算法通过以下步骤来进行分类:
- 计算未知样本与训练集中所有已知样本的距离。
- 选择距离最近的 “k” 个已知样本(最近的 k 个邻居)。
- 根据这 k 个邻居中所属类别的多数投票来确定未知样本的类别。换句话说,未知样本将被分类为与其距离最近的 k 个邻居中最常见的类别。
因此,k-NN 算法中的 “k” 控制了决策过程中邻居的数量,它是一个重要的超参数,需要根据具体问题的特点来选择。选择不同的 “k” 值可能会导致不同的分类结果,因此通常需要进行交叉验证等方法来确定最佳的 “k” 值。较小的 “k” 值可能会导致模型对噪声敏感,而较大的 “k” 值可能会导致模型过于平滑。因此,在实际应用中,需要根据数据集的特点和问题的需求来选择合适的 “k” 值。
# k 指的是要查找的最近样本的数量
k=6
# 查找最近的点相应的y坐标
topk_y = [y_train[i] for i in nearest[:k]]
topk_y
[1, 1, 1, 1, 1, 0]
接下来我们看下collections中的Counter方法
collections
模块是 Python 标准库中的一个模块,它提供了一些额外的数据类型和数据结构,用于扩展内置数据类型(如列表、元组、字典等)的功能。Counter
是 collections
模块中的一个类,用于创建计数器对象,用于计算可迭代对象中元素的出现次数。
以下是关于 Counter
类的一些详细信息和用法示例:
-
创建计数器对象:可以使用
Counter()
构造函数来创建一个计数器对象。计数器对象可以接受一个可迭代对象作为参数,例如列表、字符串、元组等。from collections import Counter # 创建计数器对象 my_list = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4] counter = Counter(my_list) print(counter)
输出:
Counter({4: 4, 3: 3, 2: 2, 1: 1})
上面的计数器对象表示在
my_list
中,元素 4 出现了 4 次,元素 3 出现了 3 次,以此类推。 -
元素计数:可以使用计数器对象的键(元素)来查询元素出现的次数。
print(counter[3]) # 查询元素 3 出现的次数
输出:
3
-
元素计数的方法:计数器对象还提供了一些方法来获取元素的计数。
print(counter.get(2)) # 获取元素 2 出现的次数 print(counter.keys()) # 获取所有不重复的元素 print(counter.values()) # 获取所有元素的计数值 print(counter.most_common()) # 获取按计数值降序排列的元素列表
-
更新计数器:可以使用计数器对象的
update()
方法来更新计数器的内容。counter.update([3, 4, 5]) # 更新计数器,增加元素 5 的计数
更新后的计数器会反映新的计数值。
Counter
类在数据分析、文本处理和统计分析等领域非常有用,它可以帮助你快速统计和分析数据中元素的出现频率。
from collections import Counter
# 我们可以把这个统计的过程理解成投票vote的过程
Counter(topk_y) # 它会自动统计距离这个新输入样本最近的7个样本中不同类别所占的个数, 返回值是个字典类型的数据
Counter({1: 5, 0: 1})
votes = Counter(topk_y)
votes.most_common(1)[0][0] # 括弧中的数字代表票数最多的几个元素, 返回值是一个元素为元组的列表
1
predict_y = votes.most_common(1)[0][0] # 这就是整个预测出来的值