K最近邻(K-Nearest Neighbor,KNN)算法核心思想是一个样本与数据集中的 k k k个样本最相似, 如果这 k k k个样本中的大多数属于某一个类别, 则该样本也属于这个类别。也就是说,该方法在确定分类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。KNN方法在类别决策时,只与极少量的相邻样本有关。 k k k通常是不大于20的整数。
我们以下图为例,当要判断绿色实例的类别的时候,我们可以看看它的附近有红色三角形和蓝色矩形,然后在第一个最近邻实线圈内采取多数表决的决策规则,发现绿球周围红色三角形多于蓝色矩形,于是把绿色实例也分类为红色那一类。
在许多实际应用中数据是不充足的。为了选择好的模型,可以采用交叉验证方法。交叉验证的基本想法是重复地使用数据,把给定的数据进行切分,将切分的数据组合为训练集与测试集,在此基础上反复进行训练测试以及模型的选择。在实现过程中将采用 sklearn.model_selection.cross_val_score() 实现交叉验证选取 k k k值。
距离度量方式有三种:欧式距离、曼哈顿距离、闵可夫斯基距离。
欧式距离:
d ( x , y ) = ∑ k = 1 n ( x k − y k ) 2 d(x, y)=\sqrt{\sum_{k=1}^{n}\left(x_{k}-y_{k}\right)^{2}} d(x,y)=k=1∑n(xk−yk)2
最常用的就是欧式距离。
曼哈顿距离:
d ( x , y ) = ∑ k = 1 n ∣ x k − y k ∣ d(x,y)=\sum\limits_{k=1}^{n}{\left| {{x}_{k}}-{{y}_{k}} \right|} d(x,y)=k=1∑n∣xk−yk∣
闵可夫斯基距离:
d ( x , y ) = ∑ k = 1 n ( x k − y k ) 2 D ( x , y ) = ( ∣ x 1 − y 1 ∣ ) p + ( ∣ x 2 − y 2 ∣ ) p + … + ( ∣ x n − y n ∣ ) p p = ∑ i = 1 n ( ∣ x i − y i ∣ ) p p d(x, y)=\sqrt{\sum_{k=1}^{n}\left(x_{k}-y_{k}\right)^{2}}D(x, y)=\sqrt[p]{\left(\left|x_{1}-y_{1}\right|\right)^{p}+\left(\left|x_{2}-y_{2}\right|\right)^{p}+\ldots+\left(\left|x_{n}-y_{n}\right|\right)^{p}}=\sqrt[p]{\sum_{i=1}^{n}\left(\left|x_{i}-y_{i}\right|\right)^{p}} d(x,y)=k=1∑n(xk−yk)2D(x,y)=p(∣x1−y1∣)p+(∣x2−y2∣)p+…+(∣xn−yn∣)p=pi=1∑n(∣xi−yi∣)p
我们可以发现,欧式距离是闵可夫斯基距离距离在 p = 2 p=2 p=2时的特例,而曼哈顿距离是 p = 1 p=1 p=1时的特例。
分类决策规则一般使用多数表决法。即如果一个样本在特征空间中的 k k k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。
KNN算法的计算过程:
步骤一:输入训练集数据和标签,输入测试数据;
步骤二:计算测试数据与各个训练数据之间的距离;
步骤三:按照距离的递增关系进行排序,选取距离最小的 K K K个点;
步骤四:确定前K个点所在类别的出现频率,返回前 K K K个点中出现频率最高的类别作为测试数据的预测分类。
KNN算法的优点:
KNN算法的缺点:
数据推测《唐人街探案》属于那种电影类型。
import math
# 使用字典构建数据集
movie_data = {"宝贝当家": [45, 2, 9, "喜剧片"],
"美人鱼": [21, 17, 5, "喜剧片"],
"澳门风云3": [54, 9, 11, "喜剧片"],
"功夫熊猫3": [39, 0, 31, "喜剧片"],
"谍影重重": [5, 2, 57, "动作片"],
"叶问3": [3, 2, 65, "动作片"],
"伦敦陷落": [2, 3, 55, "动作片"],
"我的特工爷爷": [6, 4, 21, "动作片"],
"奔爱": [7, 46, 4, "爱情片"],
"夜孔雀": [9, 39, 8, "爱情片"],
"代理情人": [9, 38, 2, "爱情片"],
"新步步惊心": [8, 34, 17, "爱情片"]}
# 测试样本 唐人街探案": [23, 3, 17, "?片"]
#下面为求与数据集中所有数据的距离代码:
x = [23, 3, 17]
KNN = []
# 计算唐人街探案到每个点的距离,此处使用的欧氏距离,保留两位小数
for key, v in movie_data.items():
d = math.sqrt((x[0] - v[0]) ** 2 + (x[1] - v[1]) ** 2 + (x[2] - v[2]) ** 2)
KNN.append([key, round(d, 2)])
# 输出所用电影到 唐人街探案的距离
print(KNN)
#按照距离大小进行递增排序
KNN.sort(key=lambda dis: dis[1])
#选取距离最小的k个样本,这里取k=5;
KNN=KNN[:5]
print(KNN)
#确定前k个样本所在类别出现的频率,并输出出现频率最高的类别
labels = {"喜剧片":0,"动作片":0,"爱情片":0}
for s in KNN:
label = movie_data[s[0]]
labels[label[3]] += 1
labels =sorted(labels.items(),key=lambda l: l[1],reverse=True)
print(labels,labels[0][0],sep='\n')
输出结果:
[('喜剧片', 4), ('动作片', 1), ('爱情片', 0)]
测试代码来源https://blog.csdn.net/saltriver/article/details/52502253
链接: https://www.bilibili.com/video/BV1Nt411i7oD?vd_source=46c7da561c57eb4be6a24d9f87da11b1
import csv
import random
# 读取数据
with open("Prostate_Cancer.csv", "r") as file:
reader = csv.DictReader(file) # 借用字典读取文件
# for row in reader:
# print(row)
datas = [row for row in reader] # 将数据全部读出,放到列表中
# print(datas)
# 分组:分成两组,一组学习,一组出结果
random.shuffle(datas) # 打乱列表顺序,便于切割,不产生特殊结果
n = len(datas) // 3
test_set = datas[0: n] # 测试集
train_set = datas[n:] # 训练集
# KNN开始
# 计算距离
def distance(d1, d2):
sums = 0
for key in ("radius", "texture", "perimeter", "area", "smoothness", "compactness", "symmetry", "fractal_dimension"):
sums += (float(d1[key]) - float(d2[key])) ** 2
return sums ** 0.5
K = 5
def knn(data):
# 1、距离
res = [
{"result": train["diagnosis_result"], "distance": distance(data, train)} # 返回结果与调用distance方法来你计算data和train之间的距离
for train in train_set # 遍历训练集
]
# print(res)
# 2、排序--升序
res = sorted(res, key=lambda item: item["distance"])
# print(res)
# 3、提取前K个
res1 = res[0:K]
# print(res1)
# 4、加权平均
result = {"B": 0, "M": 0} # 最终的返回结果
# 总距离
sum = 0
for r in res1:
sum += r["distance"]
for r in res1:
result[r["result"]] += 1 - r["distance"]/sum
if result["B"] > result["M"]:
return"B"
else :
return"M"
# 测试阶段
correct = 0
for test in test_set:
result = test["diagnosis_result"]
result1 = knn(test)
if result == result1:
correct += 1
print("准确率为:{:.2f}%".format(100 * correct / len(test_set)))
参考来源链接: https://blog.csdn.net/codedz/article/details/108862498
1. sklearn中的KNN方法
sklearn.neighbors.KNeighborsClassifier(n_neighbors = 5,
weights='uniform',
algorithm = '',
leaf_size = '30',
p = 2,
metric = 'minkowski',
metric_params = None,
n_jobs = None
)
参数如下:
n_neighbors:这个值就是指 KNN 中的 “K”了。前面说到过,通过调整 K 值,算法会有不同的效果。
weights(权重):最普遍的 KNN 算法无论距离如何,权重都一样,但有时候我们想搞点特殊化,比如距离更近的点让它更加重要。这时候就需要 weight 这个参数了,这个参数有三个可选参数的值,决定了如何分配权重。参数选项如下:
‘uniform’:不管远近权重都一样,就是最普通的 KNN 算法的形式。
‘distance’:权重和距离成反比,距离预测目标越近具有越高的权重。
自定义函数:自定义一个函数,根据输入的坐标值返回对应的权重,达到自定义权重的目的。algorithm:在 sklearn 中,要构建 KNN 模型有三种构建方式
- 暴力法,就是直接计算距离存储比较的那种放松。
- 使用 kd 树构建 KNN 模型
- 使用球树构建。
其中暴力法适合数据较小的方式,否则效率会比较低。
如果数据量比较大一般会选择用 KD 树构建 KNN 模型,而当 KD 树也比较慢的时候,则可以试试球树来构建 KNN。参数选项如下:
- ‘brute’ :蛮力实现
- ‘kd_tree’:KD 树实现 KNN
- ‘ball_tree’:球树实现 KNN
- ‘auto’: 默认参数,自动选择合适的方法构建模型
不过当数据较小或比较稀疏时,无论选择哪个最后都会使用 ‘brute’
- leaf_size:如果是选择蛮力实现,那么这个值是可以忽略的,当使用KD树或球树,它就是是停止建子树的叶子节点数量的阈值。默认30,但如果数据量增多这个参数需要增大,否则速度过慢不说,还容易过拟合。
p:和metric结合使用的,当metric参数是"minkowski"的时候,p=1为曼哈顿距离, p=2为欧式距离。默认为p=2。- metric:指定距离度量方法,一般都是使用欧式距离。
‘euclidean’ :欧式距离
‘manhattan’:曼哈顿距离
‘chebyshev’:切比雪夫距离
‘minkowski’: 闵可夫斯基距离,默认参数
n_jobs:指定多少个CPU进行运算,默认是-1,也就是全部都算。
属性如下:
classes_ : 分类器已知的类别标签,返回ndarray标签数组。
effective_metric_: 距离度量,和上述参数中metric参数设定的距离度量一致。
effective_metric_params_:指标函数附加的关键字参数,对于大多数距离指标,将会和metric参数相同,但如果effective_metric_params_属性设置为‘minkowski’,那么也可能包含p参数的值。返回的形式是字典。
outputs_2d_:训练时当y的形状为(n,)或(n,1),则返回False,否则返回True。
方法如下:
fit(X, y):使用X作为训练数据,y作为标签目标数据进行数据拟合训练。
get_params([deep]):获取参数组成的字典。
kneighbors([X, n_neighbors, return_distance]):找寻一个点的k个邻居。
predict(X):根据提供的数据去预测它的类别标签。
predict_proba(X):返回测试数据X的概率估计值。
score(X, y[, sample_weight]):返回给定数据和标签的平均准确度。
set_params(params):设置估值器的参数。
2. 交叉验证法选取最佳k值
from sklearn.datasets import load_iris
from sklearn.model_selection import cross_val_score
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsClassifier
#加载鸢尾花数据集
iris = load_iris()
x = iris.data
y = iris.target
k_range = range(1, 31) # 设置循环次数
k_error = []
#循环,取k从1~30,查看误差效果
for k in k_range:
knn = KNeighborsClassifier(n_neighbors=k)
#cv参数决定数据集划分比例,这里是按照5:1划分训练集和测试集
scores = cross_val_score(knn, x, y, cv=6, scoring='accuracy')
k_error.append(1 - scores.mean())
#画图,x轴为k值,y值为误差值
plt.plot(k_range, k_error)
plt.xlabel('Value of K in KNN')
plt.ylabel('Error')
plt.show()
输出结果:
由这个图我们可以看出大致在k=11的时候,损失值是最小的,所以我们选择k = 11进行训练。
3. KNN测试
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn import neighbors, datasets
n_neighbors = 11
# 加载鸢尾花数据集
iris = datasets.load_iris()
# 选择其中的两个特征
X = iris.data[:, :2]
y = iris.target
h = .02 # 网格步长
# 创建色彩图
cmap_light = ListedColormap(['orange', 'cyan', 'cornflowerblue'])
cmap_bold = ListedColormap(['darkorange', 'c', 'darkblue'])
# 在两种权重下绘制图像
for weights in ['uniform', 'distance']:
# 创建knn分类器实例, 并进行训练拟合
clf = neighbors.KNeighborsClassifier(n_neighbors, weights = weights)
clf.fit(X,y)
# 绘制决策边界
x_min, x_max = X[:,0].min() - 1, X[:,0].max() + 1
y_min, y_max = X[:,1].min() - 1, X[:,1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.figure()
plt.pcolormesh(xx, yy, Z, cmap = cmap_light)
# 绘制训练点
plt.scatter(X[:, 0], X[:, 1], c = y, cmap = cmap_bold, edgecolor='k', s=20)
plt.xlim(xx.min(), xx.max())
plt.ylim(yy.min(), yy.max())
plt.title('3-Class classification (k = %i, weights = "%s")' % (n_neighbors, weights))
plt.show()