以下代码全部基于python3
工作原理:存在一个样本数据集合(也称作训练样本集),并且样本集中每个数据都存在标签(即我们知道样本中每一数据与所属分类的对应关系)。输入没有标签的新数据后,将新数据的特征值与样本中数据对应的特征值进行比较,然后算法提取样本集中特征最相似数据(最邻近)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,通常k是不大于20的整数。最后选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
kNN算法伪代码:
对未知类别属性的数据集中的每个点依次执行以下操作:
(1)计算已知类别数据集中的点与当前点之间的距离;
(2)按照距离递增次序排序;
(3)选取与当前点距离最小的k个点;
(4)确定前k个点所在类别的出现频率;
(5)返回前k个点出现频率最高的类别作为当前点的预测分类。
python3 下kNN算法实现:
from numpy import *
import operator
def createDataSet():
group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
labels = ['A','A','B','B']
return group,labels
def classify_KNN(test_X, train_set, labels, K):
rows = train_set.shape[0]
diff = tile(test_X,(rows,1)) - train_set
# 这一行利用tile函数将输入样本实例转化为与训练集同尺寸的矩阵
# 便之后的矩阵减法运算
# .tile若输入一个二元祖,第一个数表示复制的行数,第二个数表示对inx的重复的次数
# .shape[0]代表行数,.shape[1]代表列数
sqDistance = (diff ** 2).sum(axis=1)
Distance = sqDistance ** 0.5
sorted_Distance = Distance.argsort()
# 对每个训练样本与输入的测试样本求欧几里得距离,即点之间的范数
# 随后按距离由小到大进行排序
# 当axis为0时,是压缩行,即将每一列的元素相加,将矩阵压缩为一行
# 当axis为1时,是压缩列,即将每一行的元素相加,将矩阵压缩为一列
# 如果axis为整数元组(x,y),则是求出axis=x和axis=y情况下得到的和
classCount = {}
for i in range(K):
vote_label = labels[sorted_Distance[i]]
classCount[vote_label] = classCount.get(vote_label,0) +1
sortedClassCount = sorted(classCount.items() , key = operator.itemgetter(1),reverse=True)
return sortedClassCount[0][0]
运行:
>import kNN
>group,labels = kNN.createDataSet()
>kNN.classify_KNN([0,0],group,labels,3)
'B'
def file_parse_matrix(filename):
with open(filename) as fp:
Arr_lines = fp.readlines()
number = len(Arr_lines)
# 初始化数据为m行3列(飞行里程,游戏时间,冰淇淋数)
# 标签单独创建一个向量保存
return_mat = zeros((number, 3))
label_vec = []
index = 0
for line in Arr_lines:
line = line.strip()
listFromLine = line.split('\t') # 按换行符分割数据
# 将文本数据前三行存入数据矩阵,第四行存入标签向量
return_mat[index, :] = listFromLine[0:3]
label_vec.append(listFromLine[-1])
index += 1
return return_mat, label_vec
运行:
>datingDataMat,datingLabels=kNN.file_parse_matrix('datingTestSet.txt')
>datingDataMat
>>>datingLabels
>import matplotlib
>import matplotlib.pyplot as plt
>fig = plt.figure()
>ax = fig.add_subplot(111)
>ax.scatter(datingDataMat[:,1], datingDataMat[:,2])
>plt.show()
//这段代码运行的时候有一点问题,等待后续更正
数值归一化:处理不同取值范围的特征值,如将取值范围处理为0到1或者-1到1之间。
公式:newValue = (oldValue-min)/(max-min)
其中min和max分别是数据集中的最小特征值和最大特征值。
函数Norm_feature(),可以自动将数字特征值转化为0到1的区间:
def Norm_feature(data_set):
minVal = data_set.min(0)
maxVal = data_set.max(0)
ranges = maxVal - minVal # 计算极差
# 下一步将初始化一个与原始数据矩阵同尺寸的矩阵
# 利用tile函数实现扩充向量,并进行元素间的对位运算
norm_set = zeros(shape(data_set))
rows = data_set.shape[0]
norm_set = (data_set - tile(minVal, (rows, 1))) / tile(ranges, (rows,1))
return norm_set, ranges, minVal
运行:
>data_normed, ranges, minV = kNN.Norm_feature(datingDataMat)
‘’'书上的测试函数没有参数,是自适应函数
此处传入分割参数以及测试集,可以修改测试数值(使用书上的0.1作为分割率)
def Test_accuray(split_ratio, test_set, test_label):
norm_test, ranges, Min = Norm_feature(test_set)
rows = norm_test.shape[0]
rows_test = int(rows * split_ratio)
error = 0
for i in range(rows_test):
Result = classify_KNN(norm_test[i,:], norm_test[rows_test:rows], \
test_label[rows_test:rows], 3)
# 参数1表示从测试集(此处约会数据是随机的因此抽取前10%即可)中抽取一个实例
# 参数2,3,4使用后90%作为训练数据,为输入的实例进行投票并分类,K=3
print("the classifier came with: %s, the real answer is :%s " \
% (Result, test_label[i]))
if(Result != test_label[i]) : error += 1
# print(type(error)) #for test
print("the accuracy is %f | the error_rate is %f " % \
(1- (float(error) /float(rows_test)),(float(error) /float(rows_test))))
运行:
>kNN.Test_accuray(0.1,datingDataMat,datingLabels)
实际图像存储在源代码的两个子目录内:目录trainingDigits中包含了大约2000个例子,每个数字大约有200个样本;目录testDigits中包含了大约900个测试数据。我们使用目录trainingDigits中的数据训练分类器,使用目录testDigits中的数据测试分类器的效果。两组数据没有重叠。
为了使用前面两个例子的分类器,我们必须将图像格式化处理为一个向量。
from os import listdir #从os模块中导入函数listdir,它可以列出给定目录的文件名。
def img2vec(filename):
'''this is to...将32X32的图像转化为1X1024的行向量'''
returnvec = zeros((1, 1024))
with open(filename) as fp:
for i in range(32):
line = fp.readline()
for j in range(32):
returnvec[0, 32 * i + j] = int(line[j])
# returnVEC按32进位,j代表每位的32个元素
return returnvec
运行:
>testVector = kNN.img2vec('digits/testDigits/0_13.txt')
>testVector[0,0:31]
>testVector[0,32:36]
def HandWritingTest(train_dir, test_dir):
labels = []
File_list = listdir(train_dir)
# 将目录内的文件按名字放入列表,使用函数解析为数字
m = len(File_list)
train_mat = zeros((m, 1024))
for i in range(m):
fname = File_list[i]
fstr = fname.split('.')[0]
fnumber = int(fstr.split('_')[0])
# 比如'digits/testDigits/0_13.txt',被拆分为0,13,txt
# 此处0即为标签数字
labels.append(fnumber)
train_mat[i, :] = img2vec('%s/%s' % (train_dir, fname))
# labels is label_vec,同之前的KNN代码相同,存储标签
test_File_list = listdir(test_dir)
error = 0.0
test_m = len(test_File_list)
for i in range(test_m):
fname = test_File_list[i]
fstr = fname.split('.')[0]
fnumber = int(fstr.split('_')[0])
vec_test = img2vec('digits/testDigits/%s' % fname)
Result = classify_KNN(vec_test, train_mat, labels, 3)
print("the classifier came with: %d, the real answer is :%d " \
% (Result, fnumber))
if (Result != fnumber): error += 1
# 这部分和Test模块相同,直接copy过来就好
print("the accuracy is %f | the error_rate is %f " % \
(1 - (float(error) / float(test_m)), (float(error) / float(test_m))))
运行:
>kNN.HandWritingTest('digits/trainingDigits', 'digits/testDigits/')