k近邻分类(KNN)

    近来学习了最近邻分类KNN(k Nearest Neighbors),写下心得,以为记录。

    K近邻的原理很简单,对于数据集,可以分为训练集和测试集,KNN使用训练集全部数据作为分类计算的依据。输入一个待分类样本,计算该样本与训练集所有样本的距离,挑选出距离最小的k个样本,统计这k个样本的类别标签,出现最多的那个类别就被认为是待分类样本的类别。

    《机器学习实战》中使用欧式距离作为特征向量距离的度量,其他距离也可以用,不说。

    跟其他很多分类器一样,由于特征向量每个维度数据范围不一样,某个维度过大,会严重影响距离计算,因此要进行归一化。

    代码本身很简单,只是对Python和Python相关一系列的库还不是很熟悉,因此花了点时间摸索。用KNN来做数字识别,训练样本2000个,测试样本946,k取3,最终准确率为98.8%,还可以,就是计算时间很长,计算时间与训练样本的数量是线性关系,这或许就是KNN的一个缺点,SVM就只需要留下支撑向量。计算时间还与k有关系,准确率也和k有关,k取10准确率就只有97.7了,这也许能说明什么。

    代码中还有约会网站分类问题的代码。数据集可以网上下载到,不传了。

from pandas import Series, DataFrame
import numpy as np
import seaborn as sns
from matplotlib import pylab as plt
from os import listdir


# _dataset的shape[0]和labels的数量一样
# DataFrame的index默认是range(n),后面用这一特性来查询排序后标签的位置
def create_dataset():
    _dataset = DataFrame([[1., 1.1],
                         [1., 1.],
                         [0., 0.],
                         [0.1, 0.1]])
    _label = Series(["A", "A", "B", "B"])
    return _dataset, _label


def create_dataset1():
    fr = open("datingTestSet.txt", "r")
    d = fr.readlines()
    li = []
    for line in d:
        li.append(line.strip().split())
    _ds = DataFrame(li, columns=["flight", "gamepercent", "icecream", "labels"])
    _label = _ds["labels"]
    del _ds["labels"]
    _ds = _ds.astype(float)  #

    # print(_ds)
    # print(set(_label))
    # print(_ds.shape)
    return _ds, _label


# 读取文本形式的图像,转换为一维向量并返回
def img2vector(filepathname):
    fr = open(filepathname, "r")
    vector = np.zeros((1, 1024))
    for i in range(32):
        line = fr.readline()
        for j in range(32):
            vector[0, i*32 + j] = int(line[j])
    return vector


# 读取数字识别的数据集
def create_dataset2(filepath):
    dir_list = listdir(filepath)
    img_n = len(dir_list)
    _labels = Series(index=range(img_n))
    _dataset_list = np.zeros((img_n, 1024))

    for i in range(img_n):
        line = dir_list[i]
        _name = line.split(".")[0]
        _digit_lab = _name.split("_")[0]
        _labels[i] = int(_digit_lab)
        _dataset_list[i, :] = img2vector(filepath + "\\" + line)
    _dataset = DataFrame(_dataset_list, columns=range(1024))
    print(_labels.shape)
    print(_dataset.shape)

    return _dataset, _labels


# 将ratio的数据划分为训练数据,其余作为测试数据
def split_dataset(_org_dataset, _org_labels, train_ratio):
    _train_n = int(_org_dataset.shape[0] * train_ratio)
    train_ds = _org_dataset[:][0:_train_n]
    train_lb = _org_labels[0:_train_n]
    test_ds = _org_dataset[:][_org_dataset.index > _train_n]
    test_lb = _org_labels[_org_labels.index > _train_n]
    return train_ds, train_lb, test_ds, test_lb


def knn_classify(dataset, labels, test_feature, k, is_normalize=False):
    if k > dataset.shape[0]:
        print("k should not large than the number of sample")
        return None
    # 计算欧式距离
    norm_para = []
    test_feature.index = dataset.columns
    norm_ds = DataFrame(columns=dataset.columns, index=dataset.index)
    norm_ts_f = Series(index=dataset.columns)
    # 归一化很浪费时间,本应该把dataset的归一化放在外面,这里作为测试,懒得改了
    if is_normalize:
        for c in dataset:
            norm_para.append([dataset[c].min(), dataset[c].max()])
        # print(norm_para)
        for c in range(dataset.shape[1]):
            norm_ds[norm_ds.columns[c]] = (dataset[dataset.columns[c]] - norm_para[c][0])/(norm_para[c][1] - norm_para[c][0] + 0.000001)
        for c in range(test_feature.shape[0]):
            norm_ts_f[dataset.columns[c]] = (test_feature.values[c] - norm_para[c][0])/(norm_para[c][1] - norm_para[c][0] + 0.000001)
    else:
        norm_ds = dataset.copy()
        norm_ts_f = test_feature.copy()

    d = norm_ds - norm_ts_f
    # print(d[0:2])
    d = d**2
    # print(d[0:2])
    d = d.sum(axis=1)
    d = d**0.5
    # print(d[0:2])
    # 对欧式距离进行降序排序,注意这里用的是sort_values
    # 数据的index也会跟随数据的位置发生改变,也就是用键值对访问d,d没有发生任何变化
    d = d.sort_values(ascending=True)
    # combine 仅仅是为了调试显示使用
    # combine = np.array([[d.index[i], d.values[i], labels[d.index[i]]] for i in range(len(d))])
    # print("k nearest neighbor is:")
    # for i in range(k):
    #     print(combine[i])
    # print(d)
    # 用set来保证类别标签的唯一性,利用为唯一标签构建Series,统计前k个特征类别出现的次数
    # 然后对k个特征的类别出现次数进行排序,返回出现最多的那一个类别标签
    unique_labels = set(labels)
    labels_number = unique_labels.__len__()
    knn_set = Series(np.zeros(labels_number), unique_labels)
    # print("set for knn:")
    for i in range(k):
        # d.index是以列表访问d的index,与现实的顺序一样
        # labels[d.index[i]]其实就是获取对应特征的类别标签而已
        knn_set[labels[d.index[i]]] += 1

    knn_set = knn_set.sort_values(ascending=False)
    # print(knn_set)
    # print(knn_set)
    return knn_set.index[0]


def classify_dataset(dataset, labels, test_ds, test_lb, k, is_normalize):
    res = []
    n = test_ds.shape[0]

    # show_ds = dataset.copy()
    # show_ds["labels"] = labels

    # sns.pairplot(show_ds, hue="labels")
    # plt.show()

    for i in range(n):
        print("classify " + str(i) + ":")
        fe = Series(test_ds.values[i], index=dataset.columns)
        ans = knn_classify(dataset, labels, fe, k, is_normalize)
        res.append(ans)
        if ans != test_lb.values[i]:
            print("error classify:" + str(test_ds.index[i]))
    right_n = 0
    # print("res:")
    for i in range(n):
        # print(str(test_lb.values[i]) + " " + str(res[i]))
        if res[i] == test_lb.values[i]:
            right_n += 1
    acc = right_n * 1.0 / n
    print("accurracy=" + str(acc))


# 测试约会网站分类
# org_dataset, org_label = create_dataset1()
# train_dataset, train_label, test_dataset, test_label = split_dataset(org_dataset, org_label, 0.8)
# classify_dataset(train_dataset, train_label, test_dataset, test_label, 10, True)


# 测试数字识别
train_dataset, train_label = create_dataset2("digits\\trainingDigits")
test_dataset, test_label = create_dataset2("digits\\testDigits")
classify_dataset(train_dataset, train_label, test_dataset, test_label, 3, False)



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值