KNN是比较简单且好用的算法。
读本篇博客之前,需要先对numpy、scikit-learn有个基本的了解,因为它们都是python做科学计算、机器学习必备的好工具!
还有就是需要了解cross validation(交叉验证),可参考我的另外一片博客:交叉验证
KNN是个怎样的算法呢?其实顾名思义就能猜个七八成了,k-nearest-neighbors,邻居?首先假设我们有一个计算两个sample之间的距离的函数distance,然后我们可以用它来计算测试的sample与所有train_samples之间的距离,在这些距离中,我们选最小的k个,投票决定(每个选出来的距离当然是投给它相应的那个类别啦),那个投票最多的类别(如果有多个,则可以进一步细化或直接随机选一个即可)作为最终分类的类别。
原理十分简单,实现起来也很简单。
推荐好习惯:
对于数组、矩阵的操作,尽量使用numpy的各种运算来加速,不然用python处理规模稍微大点的数据集会坑害自己的!!!除非你自己手写各种优化、并行、多线程之类的,不过numpy已经做得很好了。
首先是实现了一个叫做KNN的模块,封装了如何获取数据集,如何分类一个sample,以及如何分类一个测试集,以及计算准确率的函数。使用的是著名的Iris数据集,其描述可以参考:Iris数据集的介绍
# !/usr/bin/env python2
# -*- coding: utf-8 -*-
__author__ = 'jacket'
# standard module
import sys
# third-party module
import numpy as np
from sklearn.datasets import load_iris
from sklearn.cross_validation import train_test_split
def getDataSet():
# X = np.array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
# Y = ['A', 'A', 'B', 'B']
# return (X, Y)
Iris = load_iris()
return train_test_split(Iris.data, Iris.target, test_size=0.3, random_state=np.random)
def insertSort(array, data, len_upper_bound=None):
""" insertion sort hold on a complexity: O(NK), N is the total data size, while K is the len_upper_bound """
tail = len(array) - 1
pos = tail
key = data[0]
if (not len_upper_bound) or (len(array) < len_upper_bound):
array.append(data)
tail += 1
while pos >= 0 and key < array[pos][0]:
if pos < tail:
array[pos+1] = array[pos]
pos -= 1
if pos < tail:
array[pos+1] = data
def classifyOne(train_x, train_y, test_x, K):
# first calculate the distance between test_x with each train_x
# Euclid distance = sqrt(sum([(x1-x2)**2 for (x1, x2) in zip(X1, X2)])) / 2
# use numpy's ufunc to speed up the calculation!
distances = np.sqrt(np.sum((train_x - test_x)**2, axis=1)) / 2
k_shortest = []
# use insertion sort to select the k_shortest distances
for i in range(distances.shape[0]):
insertSort(k_shortest, (distances[i], train_y[i]), K)
# vote for each label corresponding to the k_shortest distances
votes = dict.fromkeys(train_y, 0)
for (_, y) in k_shortest:
votes[y] += 1
# select the most frequent label to be the predict category
[most_label, max_votes] = [None, 0]
for label in votes:
if votes[label] > max_votes:
max_votes = votes[label]
most_label = label
return most_label
def predict(train_x, train_y, test_x, K):
results = np.zeros(test_x.shape[0], dtype=train_y.dtype)
for (i, x) in enumerate(test_x):
results[i] = classifyOne(train_x, train_y, x, K)
return results
def measure(true_labels, predict_labels):
correct = np.sum([true_labels == predict_labels])
return correct * 1.0 / len(true_labels)
然后下面的代码说明了如何使用上述的模块:
# !/usr/bin/env python2
# -*- coding: utf-8 -*-
__author__ = 'jacket'
# standard module
import sys
# self-define module
import KNN
def main(args):
[train_x, test_x, train_y, test_y] = KNN.getDataSet()
results = KNN.predict(train_x, train_y, test_x, 2)
for (predict, y) in zip(results, test_y):
print('Actual: {0} | Predict: {1}'.format(y, predict))
correct_rate = KNN.measure(test_y, results)
print('correct_rate = {0}'.format(correct_rate))
if __name__ == '__main__':
exit(main(sys.argv[1:]))
如果运行多遍的话,会发现结果有点不稳定,最低到89.9%,最高有100%的,这是因为划分训练集和测试集的时候是随机分的有关,不过一个好的模型,应该是“恰恰好拟合”的,即不论给的数据分布是怎样的,只要是有训练过类似的,都能给出比较好的结果,所以这个问题的根本在于——目前knn的模型是不好的,调整一下k的值试试看。我发现把k值调整到4或5的时候,结果就会比较稳定了,基本都在93.3%到97.7%之间。
复杂度的话,首先knn不需要训练,测试的话,设训练集大小为M,测试集大小为N。对于每个test_sample,它需要遍历一遍训练集来计算距离,O(M),然后对这M个距离进行排序,可以用python或numpy的sort函数,它们都是快排实现的,平均复杂度是O(Mlog M),而我自己实现时用的是变形的插入排序,复杂度为O(KM),其中K就是KNN中的K了,所以我上面实现的KNN测试每个test_sample的复杂度为:O((K+1)M),故而总的算法复杂度为O((K+1)MN)(因为有N个test_sample,每个都要做一遍)。