1.概念與目的
k近鄰法(k-Nearest Neighbor,簡稱kNN)學習是一種常用的監督式學習方法。
給定測試樣本,基於某種距離度量找出訓練集中與其最靠近的k個訓練樣本,然後基於這k個“鄰居”的信息來進行預測。通常,在分類任務中可使用“投票法”,即選擇這k個樣本中出現最多的類別標記作爲預測結果;在迴歸任務中可以使用“平均法”,即將這k個樣本的實值輸出標記的平均值作爲預測結果;還可以基於距離遠近進行加權平均或加權投票,距離越近的樣本權重越大。
**k近鄰有個明顯的不同之處:**它似乎沒有顯示的訓練過程。事實上,它是“懶惰學習”(lazy learning)的著名代表,此類學習技術在訓練階段僅僅是把樣本保存起來,訓練時間開銷爲0,待收到測試樣本後再進行處理,這種稱爲“急切學習”。
****KNN的三要素:****k值的選擇,距離度量及分類決策規則.當k=1時稱爲最近鄰算法.主要核心思想是“物以類聚”,看訓練集中離該輸入最近的實例多數屬於什麼類別。
2.模型
當訓練集,距離度量,k值以及分類決策規則確定後,特徵空間已經根據這些要素被劃分爲一些子空間,且子空間裏每個點所屬的類也已被確定.
3.策略
(1)距離:
特徵空間中兩個實例點的距離是相似程度的反映,k近鄰算法一般使用歐氏距離,也可以使用更一般的Lp距離或Minkowski距離.設特徵空間X是n維實數向量空間Rn,x(i),x(j)∈X,x=(x0,x1,x2,…,xn)T,x(i),x(j)的Lp距離定義爲:
這裏p⩾1。當p=2時,稱爲歐式距離(Euclidean distance),即
當p=1時,稱爲曼哈頓距離(Manhanttan distance),即
當p=∞時,它是各個座標距離的最大值,即:
(2)k值:
k值較小時,整體模型變得複雜,容易發生過擬合.k值較大時,整體模型變得簡單.在應用中k一般取較小的值,通過交叉驗證法選取最優的k.
(3)分類決策規則
k近鄰中的分類決策規則往往是多數表決,多數表決規則等價於經驗風險最小化.
4.算法
目標:根據給定的距離度量,在訓練集中找出與x最鄰近的k個點,根據分類規則決定x的類別ykd樹算法
(1)描述:
kd樹就是一種對k維空間中的實例點進行存儲以便對其進行快速檢索的樹形數據結構,它是一個二叉樹,表示對k維空間的一個劃分.構造kd樹相當於不斷用垂直於座標軸的超平面將k維空間劃分,構造一列的k維超矩形區域,而kd樹的每一個節點對應於一個k維超矩形區域。kd樹更適用於訓練實例數遠大於空間維數時的k近鄰搜索.
(2)構造平衡kd樹算法:
可以通過如下遞歸實現:在超矩形區域上選擇一個座標軸和此座標軸上的一個切分點,確定一個超平面,該超平面將當前超矩形區域切分爲兩個子區域.在子區域上重複切分直到子區域內沒有實例時終止.通常依次選擇座標軸和選定座標軸上的中位數點爲切分點,這樣可以得到平衡kd樹.
(3)kd樹的最近鄰搜索:
從根節點出發,若目標點x當前維的座標小於切分點的座標則移動到左子結點,否則移動到右子結點,直到子結點爲葉結點爲止.以此葉結點爲"當前最近點",遞歸地向上回退,在每個結點:(a)如果該結點比當前最近點距離目標點更近,則以該結點爲"當前最近點"(b)"當前最近點"一定存在於該結點一個子結點對應的區域,檢查該結點的另一子結點對應的區域是否與以目標點爲球心,以目標點與"當前最近點"間的距離爲半徑的超球體相交.如果相交,移動到另一個子結點,如果不相交,向上回退.持續這個過程直到回退到根結點,最後的"當前最近點"即爲最近鄰點.
5.kd樹的代碼實現
(1)首先在構造kd樹的時候需要尋找中位數,因此用快速排序來獲取一個list中的中位數。
import matplotlib.pyplot as plt
import numpy as np
class QuickSort(object):
"Quick Sort to get medium number"
def __init__(self, low, high, array):
self._array = array
self._low = low
self._high = high
self._medium = (low+high+1)//2 # python3中的整除
def get_medium_num(self):
return self.quick_sort_for_medium(self._low, self._high,
self._medium, self._array)
def quick_sort_for_medium(self, low, high, medium, array): #用快速排序來獲取中位數
if high == low:
return array[low] # find medium
if high > low:
index, partition = self.sort_partition(low, high, array);
#print array[low:index], partition, array[index+1:high+1]
if index == medium:
return partition
if index > medium:
return self.quick_sort_for_medium(low, index-1, medium, array)
else:
return self.quick_sort_for_medium(index+1, high, medium, array)
def quick_sort(self, low, high, array): #正常的快排
if high > low:
index, partition = self.sort_partition(low, high, array);
#print array[low:index], partition, array[index+1:high+1]
self.quick_sort(low, index-1, array)
self.quick_sort(index+1, high, array)
def sort_partition(self, low, high, array): # 用第一個數將數組裏面的數分成兩部分
index_i = low
index_j = high
partition = array[low]
while index_i < index_j:
while (index_i < index_j) and (array[index_j] >= partition):
index_j -= 1
if index_i < index_j:
array[index_i] = array[index_j]
index_i += 1
while (index_i < index_j) and (array[index_i] < partition):
index_i += 1
if index_i < index_j:
array[index_j] = array[index_i]
index_j -= 1
array[index_i] = partition
return index_i, partition
(2)構造kd樹
class KDTree(object):
def __init__(self, input_x, input_y):
self._input_x = np.array(input_x)
self._input_y = np.array(input_y)
(data_num, axes_num) = np.shape(self._input_x)
self._data_num = data_num
self._axes_num = axes_num
self._nearest = None #用來存儲最近的節點
return
def construct_kd_tree(self):
return self._construct_kd_tree(0, 0, self._input_x)
def _construct_kd_tree(self, depth, axes, data):
if not data.any():
return None
axes_data = data[:, axes].copy()
qs = QuickSort(0, axes_data.shape[0]-1, axes_data)
medium = qs.get_medium_num() #找到軸的中位數
data_list = []
left_data = []
right_data = []
data_range = range(np.shape(data)[0])
for i in data_range: # 跟中位數相比較
if data[i][axes] == medium: #相等
data_list.append(data[i])
elif data[i][axes] < medium:
left_data.append(data[i])
else:
right_data.append(data[i])
left_data = np.array(left_data)
right_data = np.array(right_data)
left = self._construct_kd_tree(depth+1, (axes+1)% self._axes_num, left_data)
right = self._construct_kd_tree(depth+1, (axes+1)% self._axes_num, right_data)
#[樹的深度,軸,中位數,該節點的數據,左子樹,右子樹]
root = [depth, axes, medium, data_list, left, right]
return root
def print_kd_tree(self, root): #打印kd樹
if root:
[depth, axes, medium, data_list, left, right] = root
print('{} {}'.format(' '*depth, data_list[0]))
if root[4]:
self.print_kd_tree(root[4])
if root[5]:
self.print_kd_tree(root[5])
//測試代碼
input_x = [[2,3], [6,4], [9,6], [4,7], [8,1], [7,2]]
input_y = [1, 1, 1, 1, 1, 1]
kd = KDTree(input_x, input_y)
tree = kd.construct_kd_tree()
kd.print_kd_tree(tree)
#得到結果:
[7 2]
[6 4]
[2 3]
[4 7]
[9 6]
[8 1]
(3)搜索kd樹
在類中繼續添加如下函數,基本的思路是將路徑上的節點依次入棧,再逐個出棧。
def _get_distance(self, x1, x2): #計算兩個向量之間的距離
x = x1-x2
return np.sqrt(np.inner(x, x))
def _search_leaf(self, stack, tree, target): #以tree爲根節點,一直搜索到葉節點,並添加到stack中
travel_tree = tree
while travel_tree:
[depth, axes, medium, data_list, left, right] = travel_tree
if target[axes] >= medium:
next_node = right
next_direction = 'right' # 記錄被訪問過的子樹的方向
elif target[axes] < medium:
next_node = left
next_direction = 'left' # 記錄被訪問過的子樹的方向
stack.append([travel_tree, next_direction]) #保存方向,用來記錄哪個子樹被訪問過
travel_tree = next_node
def _check_nearest(self, current, target): # 判斷當前節點跟目標的距離
d = self._get_distance(current, target)
if self._nearest:
[node, distance] = self._nearest
if d < distance:
self._nearest = [current, d]
else:
self._nearest = [current, d]
def search_kd_tree(self, tree, target): #搜索kd樹
stack = []
self._search_leaf(stack, tree, target) # 一直搜索到葉節點,並將路徑入棧
self._nearest = []
while stack:
[[depth, axes, medium, data_list, left, right], next_direction] = stack.pop() #出棧
[data] = data_list
self._check_nearest(data, target) #檢查當前節點的距離
if left is None and right is None: #如果當前節點爲葉節點,繼續下一個循環
continue
[node, distance] = self._nearest
if abs(data[axes] - node[axes]) < distance: #<*> 當前節點的軸經過圓
if next_direction == 'right': # 判斷哪個方向被訪問過,轉向相反方向
try_node = left
else:
try_node = right
self._search_leaf(stack, try_node, target) #往相反的方向搜索葉節點
print(self._nearest)
//測試代碼
kd.search_kd_tree(tree, [7.1, 4.1])
> [array([6, 4]), 1.1045361017187258]
kd.search_kd_tree(tree, [9, 2])
> [array([8, 1]), 1.4142135623730951]
kd.search_kd_tree(tree, [6, 2])
> [array([7, 2]), 1.0]
(4)尋找k個最近節點
如果要尋找k個最近節點,則需要保存k個元素的數組,並在函數_check_nearest中與k個元素做比較,然後在標記<*>的地方跟k個元素的最大值比較。其他代碼略。
def _check_nearest(self, current, target, k):
d = self._get_distance(current, target)
#print current, d
l = len(self._nearest)
if l < k:
self._nearest.append([current, d])
else:
farthest = self._get_farthest()[1]
if farthest > d:
# 將最遠的節點移除
new_nearest = [i for i in self._nearest if i[1] [[array([7, 2]), 2.1023796041628633], [array([6, 4]), 1.1045361017187258]]
kd.search_kd_tree(tree, [9, 2], k=2)
> [[array([8, 1]), 1.4142135623730951], [array([7, 2]), 2.0]]
kd.search_kd_tree(tree, [6, 2], k=2)
> [[array([6, 4]), 2.0], [array([7, 2]), 1.0]]
參考博客解釋:
https://blog.csdn.net/baimafujinji/article/details/52928203