KNN算法
概述
k-近邻算法概述,简单地说,根据测量不同特征值之间的距离进行分类。
基本思想与算法复杂度
思想:
训练样本集中每个数据都存在标签,输入没有标签的新数据,将新数据的每个特征与样本数据的对应的特征作比较,然后算法提取样本集中特征最相似数据的标签,即是认定为新数据的标签。k就是选择n个相似的样本数据,然后统计k个数据中最多的分类即称为新数据的分类标签。
算法复杂度:
KNN属于lazy-learning算法,分类器不用使用训练集进行训练,因此训练时间复杂度为0;分类时间复杂度为O(n);最终的时间复杂度为O(n)。
优点与缺点
优点:精度高、对异常值不敏感,无数据输入假定
缺点:计算复杂度高,空间复杂度高
适用数据范围:数值型和标称型
注释:
- 数据标称型:标称型目标变量的结果只在有限目标集中取值,如真与假(标称型目标变量主要用于分类)
- 计算量大的原因:因为对每一个待分类的文本都要计算它到全体已知样本的距离,才能求得它的K个最近邻点
KNN算法一般流程
- 收集数据
- 准备数据:距离计算所需要的数值,最好是结构化的数据
- 分析数据
- 训练算法:不需要
- 测试算法:计算错误率
- 使用算法:首先需要输入样本数据和机构化的输出结果,然后运行KNN算法,判定输入的新数据属于哪个分类
KNN伪代码
- 计算已知类别数据集中点与当前点之间的距离
- 赞好距离递增次序排列
- 选取与当前点距离最小的k个点
- 确定前k个点所在类别的出现频率
- 返回前k个点出现频率最高的类别作为当前点的预测分类
注释:
距离公式(欧氏距离):
d
=
(
x
A
0
−
x
B
0
)
2
+
(
x
A
1
−
x
B
1
)
2
d = \sqrt{(xA_0-xB_0)^2 + (xA_1-xB_1)^2}
d=(xA0−xB0)2+(xA1−xB1)2
# 准备数据集
import numpy as np
import operator # 运算符模块
def createDataset():
"""创建数据集"""
list1 = [[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]] # 4*2矩阵
group = np.array(list1)
labels = ['A', 'A', 'B', 'B']
return group, labels
# knn算法
def classify0(X, dataSet, labels, k):
"""
K近邻算法
X -> 需要测试的数据的向量
dataSet -> 样本集
label -> 标签
k -> 最近的k个值
"""
dataSetSize = dataSet.shape[0] #样本的个数
diffMat = np.tile(X, (dataSetSize, 1)) - dataSet
sqDiffMat = diffMat ** 2
sqDistances = sqDiffMat.sum(axis = 1) # 每行的平方和
distances = sqDistances ** 0.5
# 为了直接检索标签,直接返回索引值,也可以方便排序
sortedDistIndicies = distances.argsort() # 返回的值是从小到大的索引
classCount = {}
for i in range(k):
votelabel = labels[sortedDistIndicies[i]]
# k个数据内,统计标签的数量
classCount[votelabel] = classCount.get(votelabel, 0) + 1
# 找出标签数量最多的那个分类,升序
sortedClassCount = sorted(classCount.items(), key=lambda x:x[1])
return sortedClassCount[-1][0]
if __name__ == "__main__":
X = [0,0]
k = 3
group, labels = createDataset()
result = classify0(X, group, labels, k)
print("label = %s" %result)
# print("label = ", result)
# print("label = {}".format(result))
示例1:改进约会网站的配对效果
工作步骤:
- 收集数据:提供文本文件
- 准备数据: 使用Python解析文本文件
- 分析数据:使用Matplotlib画二维扩散图
- 训练算法:此步骤不适用于knn算法
- 测试算法:使用海伦提供的部分数据作为测试样本
- 使用算法:产生简单的命令行程序,然后海伦可以输入一些特征数据以判断对方是否为自己喜欢的类型
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
class KnnAlgo():
def __init__(self, filename):
self.filename = filename
# 准备数据
def file2martix(self):
"""
将文本文件转化为需要的数据
filename -> 文件名
"""
path = os.getcwd()
with open(path + '/'+ filename) as f:
arrayOLines = f.readlines()
numberOfLines = len(arrayOLines)
returnMat = np.zeros((numberOfLines, 3))
classLabelVector = []
index = 0
for line in arrayOLines:
#strip参数默认,则删除空白符(\n,\t,\r,'')
#此处删除末尾的\n
line = line.strip()
listFromLine = line.split('\t')
# 样本集的特征值array矩阵
returnMat[index, :] = listFromLine[:3]
# 标签列表
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat, classLabelVector
# 分析数据
def drawfigure(self):
"""
分析数据,数据可视化
x -> 样本数据的特征值
y -> 样本数据的标签
"""
#分析玩视频游戏所耗时间百分比与每周消费的冰淇淋公升数的关系
#创建一个新的图
# 中文显示
x, y = self.file2martix()
font_set = FontProperties(fname='/System/Library/Fonts/Supplemental/Songti.ttc')
fig = plt.figure(figsize = (10,15))
a1 = plt.subplot(311)
#c->color s->size 这里是根据label不同的值,决定散点不同的大小和不同的颜色
scatter = a1.scatter(x[:,0], x[:,1], c=np.array(y), s=20*np.array(y))
# 自动生成lable的操作
handles, labels = scatter.legend_elements()
legend1 = a1.legend(handles, labels, loc='best', title='Classes')
a1.add_artist(legend1)
a1.set_xlabel(u'每年获得的飞行常客里程数', FontProperties=font_set)
a1.set_ylabel(u'玩视频游戏所耗时间百分比', FontProperties=font_set)
a2 = plt.subplot(312)
scatter = a2.scatter(x[:,0], x[:,2], c=np.array(y), s=20*np.array(y))
handles, labels = scatter.legend_elements()
legend2 = a2.legend(handles, labels, loc='best', title='Classes')
a2.add_artist(legend2)
a2.set_xlabel(u'每年获得的飞行常客里程数', FontProperties=font_set)
a2.set_ylabel(u'每周消费的冰淇淋公升数', FontProperties=font_set)
a3 = plt.subplot(313)
scatter = a3.scatter(x[:,1], x[:,2], c=np.array(y), s=20*np.array(y))
handles, labels = scatter.legend_elements()
legend3 = a3.legend(handles, labels, loc='best', title='Classes')
a3.add_artist(legend3)
a3.set_xlabel(u'玩视频游戏所耗时间百分比', FontProperties=font_set)
a3.set_ylabel(u'每周消费的冰淇淋公升数', FontProperties=font_set)
fig.show()
# 准备数据:归一化数值
# 因为飞行常客里程数值远大于其他两个特征---会影响结果,因此将数据归一化处理
# 取值范围0~1或-1~1之间
# 公式:newValue = (oldValue-min)/(max-min)
def autoNorm(self, dataset):
"""数据的归一化处理"""
# 向量化思维!!取出所有列中最小的值组成array
minVals = dataset.min(axis=0)
maxVals = dataset.max(axis=0)
ranges = maxVals - minVals
normDataSet = np.zeros(shape=dataset.shape)
row = dataset.shape[0]
normDataSet = dataset - np.tile(minVals, reps=(row, 1))
normDataSet = normDataSet / np.tile(ranges, reps=(row, 1))
return normDataSet, ranges, minVals
def classify0(self, X, dataSet, labels, k):
"""
K近邻算法
X -> 需要测试的数据的向量
dataSet -> 样本集
label -> 标签
k -> 最近的k个值
"""
dataSetSize = dataSet.shape[0] #样本的个数
# tile 将矩阵复制
diffMat = np.tile(X, (dataSetSize, 1)) - dataSet
sqDiffMat = diffMat ** 2
sqDistances = sqDiffMat.sum(axis = 1) # 每行的平方和
distances = sqDistances ** 0.5
# 为了直接检索标签,直接返回索引值,也可以方便排序
sortedDistIndicies = distances.argsort() # 返回的值是从小到大的索引
classCount = {}
for i in range(k):
votelabel = labels[sortedDistIndicies[i]]
# k个数据内,统计标签的数量
classCount[votelabel] = classCount.get(votelabel, 0) + 1
# 找出标签数量最多的那个分类,升序
sortedClassCount = sorted(classCount.items(), key=lambda x:x[1])
return sortedClassCount[-1][0]
# 分类器针对约会网站的测试代码
def datingTest(self):
"""测试并计算出错误率"""
randseed = 0.1
datingDataMat, datingLabels = self.file2martix()
normat, _, _ = self.autoNorm(datingDataMat)
row = normat.shape[0]
numTestVecs = int(row * randseed)
err = 0
for i in range(numTestVecs):
classifierResult = self.classify0(normat[i, :], normat[numTestVecs: ], datingLabels[numTestVecs: ], k=5)
print("预测的分类标签为: {},真实的标签为: {}".format(classifierResult, datingLabels[i]))
if (classifierResult != datingLabels[i]): err += 1.0
print('错误率为:{}%'.format(err/numTestVecs))
def classifyPerson(self):
"""根据用户输入的特征预测类别"""
resultlist = ['not at all', 'in small doses', 'in large doses']
game_percent = float(input("玩视频游戏所耗时间百分比:"))
ffmiles = float(input("每年获得的飞行常客里程数"))
ice_cream = float(input("每周消费的冰淇淋公升数:"))
datingDataMat, datingLabels = self.file2martix()
normat, _, _ = self.autoNorm(datingDataMat)
input_arr = np.array([ffmiles, game_percent, ice_cream])
classifierResult = self.classify0(input_arr, normat, datingLabels, k=5)
print('你可能对这个人属于:{}'.format(resultlist[classifierResult]))
if __name__ == "__main__":
filename = 'datingTestSet2.txt'
Knn_algo = KnnAlgo(filename)
Knn_algo.datingTest()
Knn_algo.drawfigure()
Knn_algo.classifyPerson()
结果
预测的分类标签为: 3,真实的标签为: 3
预测的分类标签为: 2,真实的标签为: 2
预测的分类标签为: 1,真实的标签为: 1
预测的分类标签为: 1,真实的标签为: 1
预测的分类标签为: 1,真实的标签为: 1
预测的分类标签为: 1,真实的标签为: 1
预测的分类标签为: 3,真实的标签为: 3
预测的分类标签为: 3,真实的标签为: 3
…
预测的分类标签为: 1,真实的标签为: 1
错误率为:0.04%
玩视频游戏所耗时间百分比:20
每年获得的飞行常客里程数3000
每周消费的冰淇淋公升数:12
你可能对这个人属于:in small doses
使用sklearn
class sklearn.neighbors.KNeighborsClassifier(n_neighbors=5, weights='uniform', algorithm='auto', leaf_size=30, p=2, metric='minkowski', metric_params=None, n_jobs=None, **kwargs)
参数注释:
- n_neighbor:临近节点数量,默认值是5
- weights:权重,默认值是uniform
uniform:表示每个数据点的权重相同;
distance:离一个簇中心越近,权重越高;
callable:用户定义函数,用于表示每个数据点的权重- algorithm
auto:根据值选择最合适的算法
ball_tree:使用BallTree
kd_tree:使用KDTree
brute:使用Brute-Force查找- leaf_size:leaf_size传递给BallTree或KDTree,表示构造书的大小,用于影响模型构建的的速度和树需要的内存数量,最佳值是根据数据来确定的,默认值是30
- p:用于设置Minkowski距离的Power参数,当p=1时等价于Manhattan距离;当p=2等价于euclidean距离;当p>2时,是Minkowski距离(闵可夫斯基距离),默认值是2
- metric:树使用的距离度量,默认是Minkowsi距离
- metric_params:度量函数的其他关键字参数
- n_jobs:并发执行的job的数量,默认值是1。-1则使用所有的cpu。
示例2:手写识别系统
# 手写是示例,使用sklearn
import os
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
import matplotlib.pyplot as plt
class HdKnn():
def img2vector(self, doc_name, filename):
"""
将文件的图转换为需要的向量
32*32 ---> 1*1024
"""
path = os.getcwd()
with open(path + '/digits/' + doc_name + '/' + filename) as f:
returnVector = np.zeros(shape=(1, 1024)) #[[0,0,0,0,...0]] 1*1024
for i in range(32):
linestr = f.readline()
for j in range(32):
returnVector[0, 32*i+j] = int(linestr[j])
return returnVector
def generation_data(self, doc_name):
"""
将文件夹内所有的文件生成需要的数据样本集
"""
path = os.getcwd()
hwlabels = []
dataFileList = os.listdir(path + '/digits/' + doc_name)
row = len(dataFileList)
dataMat = np.zeros(shape=(row, 1024))
for i in range(row):
# 特征值
file_name = dataFileList[i]
dataMat[i,:] = self.img2vector(doc_name, file_name)
# 标签
class_name = int(file_name.split('_')[0])
hwlabels.append(class_name)
return dataMat, hwlabels
def classifer(self, k_list, x_train, y_train, x_test, y_test):
"""
训练模型以及模型评估
"""
training_accuracy = []
test_accuracy = []
for k in k_list:
knn = KNeighborsClassifier(n_neighbors=k)
knn.fit(x_train, y_train)
training_accuracy.append(knn.score(x_train, y_train))
test_accuracy.append(knn.score(x_test, y_test))
plt.plot(k_list, training_accuracy, label="Training Accuracy")
plt.plot(k_list, test_accuracy, label="Test Accuracy")
plt.xlabel('k_values')
plt.ylabel('Accuracy')
plt.legend()
if __name__ == "__main__":
train_doc_name = 'trainingDigits'
test_doc_name = 'testDigits'
k_list = range(1, 11)
hdknn = HdKnn()
trainingdata, trainlabel = hdknn.generation_data(train_doc_name)
testdata, testlabel = hdknn.generation_data(test_doc_name)
hdknn.classifer(k_list, x_train=trainingdata, y_train=trainlabel, x_test=testdata, y_test=testlabel)
总结
KNN算法是分类数据最简单最有效的算法。KNN算法必须保存全部数据集,如果训练数据集的很大,必须使用大量的存储空间。此外,由于必须对数据集中的每个数据计算距离值,实际使用时可能非常耗时。另一个缺点:无法给出任何数据的基础结构信息,因此我们也无法知晓平均实力样本和典型实例样本具有什么特征。