近来学习了最近邻分类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)