由于最近想找一个机器学习相关的实习,所以就想着把李航博士的那本《统计学习方法》中的算法用Python实现一遍,上次实现了HMM,这次试着实现KNN,代码参考了网上许多别人优秀的成果,在此表示感谢!由于看的太多,未能把连接逐个粘出来,表示抱歉,但是可以保证,我的代码是自己动手写的,不存在抄袭~
由于自己是一个Python的入门小白,对于python这种面向对象的程序语言,编程思维还是没有很好的转变过来,所以在高手看来,我的代码可能十分蹩脚,甚至存在诸多不合理,但是作为一个初学者,我虚心接受大家的一切建议和批评。
下面就贴出我的代码,参考的仍然是李航博士的《统计学习方法》,KNN只是实现了书中例题那种规模的分类效果,确切的说应该是只实现了NN,也就是最近邻算法,没有实现KNN,不过这个思想应该好理解,以后有机会再把他完善~
# -*- coding: utf-8 -*-
"""
Created on Sun Apr 9 13:11:43 2017
2017-4-12
写完kd数的最近邻叶节点的查找
2017-4-13
写完最后一个回溯算法,思路是只回溯了上层的根节点,没有比较所有结点
因为那样就相当于线性扫描,算是最近邻算法。
k近邻的思路是求得该点与结点距离第k近的点。
@author: sgp
"""
class KD_tree:
def __init__(self,split,point=None,left=None,right=None):
self.split = split
self.point = []
self.left = []
self.right = []
def findroot(data):
'''
找到方差最大的维度上的根节点
'''
L = len(data)#计算数列长度
N = len(data[0])#计算数列维度
max_var = 0
split = 0
point = []
for i in range(N):
ll = []
for j in data:
ll.append(j[i])
var = variance(ll)
if var > max_var:
max_var = var
split = i
data.sort(key=lambda x: x[split])
#print('序列:\n %r' % data)
#print(data[L//2])
point.append(data[L//2]) #原格式是= data[L//2],后改为现在格式,以便于列表维度保持一致单括号和双括号的区别
return split,point
def kd_tree_1(data):
'''
依据维度方差,得到一列数据的根节点和左右子
'''
L = len(data)#计算数列长度
split,point = findroot(data)
root = KD_tree(split,point)
for i in range(L//2):
root.left.append(data[i])
for i in range(L//2 + 1,L):
root.right.append(data[i])
a = root.left[:]#如果写出a = root.left那么相当于C语言中的指针,当执行完del root.left[:]后,a也清空了
b = root.right[:]
del root.left[:]#将其清空是为了避免函数调用的时候反复向该列表增加值导致系统死机
del root.right[:]
return point,a,b
def kd_tree_2(data):
'''
递归实现KD树
'''
L = len(data)
point,left,right = kd_tree_1(data)
print('根结点:\n %r' % point)
print('左子:\n %r' % left)
print('右子:\n %r' % right)
if L//2 > 1:
left = kd_tree_2(left)
right = kd_tree_2(right)
def searchNN(dot,data):
'''
待测点dot跟所有根节点在划分域上进行比较
若当前节点没有子节点,则返回该节点
若当前节点有子节点,则返回其左右子
'''
split,root = findroot(data)
point_1,left_1,right_1 = kd_tree_1(data)
if len(left_1) != 0:
if dot[split] < root[0][split]:
if left_1 == []:#避免寻到的最近点为空
return root
else:
return left_1
else:
if right_1 == []:
return root
else:
return right_1
else:
return point_1
def searchNN_2(dot,data):
'''
根据李航《统计学习方法》P43方法找到最近邻点
'''
next_list = searchNN(dot,data)
if len(next_list) != 1:
searchNN_2(dot,next_list)
else:
print(next_list)
def searchNN_3(dot,data):
'''
找到最近邻的子节点,然后回溯各个查找层次的根节点,求个距离最近的结点并计算距离
'''
split,point = findroot(data)
dist = []
dist.append(point)
def searchNN_4(dot,data):
next_list = searchNN(dot,data)
if len(next_list) != 1:
root_i,left_i,right_i = kd_tree_1(next_list)
dist.append(root_i)
searchNN_4(dot,next_list)
else:
dist.append(next_list)
for i in range(len(dist)-1):
#print(dist)
if L_pDistance(dot,dist[i][0],2) < L_pDistance(dot,dist[i+1][0],2):
a = L_pDistance(dot,dist[i][0],2)
b = dist[i]
else:
a = L_pDistance(dot,dist[i+1][0],2)
b = dist[i+1]
#return a,b
print(a)
print(b)
#return searchNN_4
searchNN_4(dot,data)
def variance(data):
'''
计算方差
'''
l = len(data)
E_2_data = (sum(data) / l) ** 2
data_2 = []
for i in range(l):
data_2.append(data[i] * data[i])
E_data_2 = sum(data_2) / l
variance = E_data_2 - E_2_data
#print(variance)
return variance
def L_pDistance(x_1,x_2,p):
'''
计算距离
'''
#n = x_1.shape[1]
b = 0
for i in range(len(x_1)):
a = abs(x_1[i] - x_2[i])
b = a ** p + b
L_p = b ** (1/p)
return L_p