KNN算法是k-Nearest Neighbor Classification的简称,也就是k近邻分类算法。基本思路是在特征空间中查找k个最相似或者距离最近的样本,然后根据k个最相似的样本对未知样本进行分类。基本步骤为:

(1)计算已知样本空间中所有点与未知样本的距离;

(2)对所有距离按升序排列;

(3)确定并选取与未知样本距离最小的k个样本或点;

(4)统计选取的k个点所属类别的出现频率;

(5)把出现频率最高的类别作为预测结果,即未知样本所属类别。

下面的代码模拟了上面的算法思路和步骤,以身高+体重对肥胖程度进行分类为例,采用欧几里得距离。

from collections import Counter
import numpy as np# 已知样本数据
# 每行数据分别为性别,身高,体重
knownData = ((1, 180, 85),
             (1, 180, 86),
             (1, 180, 90),
             (1, 180, 100),
 
             (1, 185, 120),
             (1, 175, 80),
             (1, 175, 60),
             (1, 170, 60),             (1, 175, 90),
             (1, 175, 100),
             (1, 185, 90),
             (1, 185, 80))knownTarget = ('稍胖', '稍胖', '稍胖', '过胖',
               '太胖', '正常', '偏瘦', '正常',
               '过胖', '太胖', '正常', '偏瘦')def KNNPredict(current, knownData=knownData, knownTarget=knownTarget, k=3):
    # current为未知样本,格式为(性别,身高,体重)
    data = dict(zip(knownData, knownTarget))
    # 如果未知样本与某个已知样本精确匹配,直接返回结果
    if current in data.keys():
        return data[current]
    
    # 按性别过滤,只考虑current性别一样的样本数据
    g = lambda item:item[0][0]==current[0]
    samples = list(filter(g, data.items()))
    g = lambda item:((item[0][1]-current[1])**2+\
                     (item[0][2]-current[2])**2)**0.5
    distances = sorted(samples, key=g)
    # 选取距离最小的前k个
    distances = (item[1] for item in distances[:k])
    # 计算选取的k个样本所属类别的出现频率
    # 选择频率最高的类别作为结果
    return Counter(distances).most_common(1)[0][0]unKnownData = [(1, 180, 70), (1, 160, 90), (1, 170, 85)]
for current in unKnownData:
    print(current, ':', KNNPredict(current))

运行结果为:

(1, 180, 70) : 偏瘦
(1, 160, 90) : 过胖
(1, 170, 85) : 正常

下面的代码使用扩展库sklearn中的k近邻分类算法处理了同样的问题:

# 使用sklearn库的k近邻分类模型
from sklearn.neighbors import KNeighborsClassifier# 创建并训练模型
clf = KNeighborsClassifier(n_neighbors=3, weights='distance')
clf.fit(knownData, knownTarget)# 分类
for current in unKnownData:
    print(current, end=' : ')
    current = np.array(current).reshape(1,-1)
    print(clf.predict(current)[0])

运行结果为:

(1, 180, 70) : 偏瘦
(1, 160, 90) : 过胖
(1, 170, 85) : 正常