【机器学习实战】KNN

KNN是比较简单且好用的算法。
读本篇博客之前,需要先对numpy、scikit-learn有个基本的了解,因为它们都是python做科学计算、机器学习必备的好工具!

还有就是需要了解cross validation(交叉验证),可参考我的另外一片博客:交叉验证

A. 算法原理

KNN是个怎样的算法呢?其实顾名思义就能猜个七八成了,k-nearest-neighbors,邻居?首先假设我们有一个计算两个sample之间的距离的函数distance,然后我们可以用它来计算测试的sample与所有train_samples之间的距离,在这些距离中,我们选最小的k个,投票决定(每个选出来的距离当然是投给它相应的那个类别啦),那个投票最多的类别(如果有多个,则可以进一步细化或直接随机选一个即可)作为最终分类的类别。
原理十分简单,实现起来也很简单。

推荐好习惯:

对于数组、矩阵的操作,尽量使用numpy的各种运算来加速,不然用python处理规模稍微大点的数据集会坑害自己的!!!除非你自己手写各种优化、并行、多线程之类的,不过numpy已经做得很好了。

B. 实现

首先是实现了一个叫做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:]))

C. 结果和复杂度分析

如果运行多遍的话,会发现结果有点不稳定,最低到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,每个都要做一遍)。

你可能感兴趣的:(算法,python,机器学习)