| 班级 |


| ---- | ---- | ---- |
| 实验要求|
| 实验目标|掌握K近邻树实现算法 |
| 学号 |3180701331|

一、实验目的

1.理解K-近邻算法原理,能实现算法K近邻算法;
2.掌握常见的距离度量方法;
3.掌握K近邻树实现算法;
4.针对特定应用场景及数据,能应用K近邻解决实际问题。

二、实验内容

1.实现曼哈顿距离、欧氏距离、闵式距离算法,并测试算法正确性。
2.实现K近邻树算法;
3.针对iris数据集,应用sklearn的K近邻算法进行类别预测。
4.针对iris数据集,编制程序使用K近邻树进行类别预测。

三、实验报告要求

1.对照实验内容,撰写实验过程、算法及测试结果;
2.代码规范化:命名规则、注释;
3.分析核心算法的复杂度;
4.查阅文献,讨论K近邻的优缺点;
5.举例说明K近邻的应用场景。

四、实验内容以及结果

In [1]:

import math
#导入数学运算函数
from itertools import combinations

In [2]:

#计算欧式距离
def L(x,y,p=2):
   # x1 = [1, 1], x2 = [5,1]  在这里,实例是两个二维特征 x1 = [1, 1], x2 = [5,1]
    if len(x)==len(y) and len(x)>1:
# 当两个特征的维数相等时,并且维度大于1时。
        sum=0 # 目前总的损失函数值为0
        for i in range(len(x)):
            sum+=math.pow(abs(x[i] - y[i]), p)
            # math.pow( x, y )函数是计算x的y次方。
            return math.pow(sum,1/p)# 距离公式。
        else:
            return 0

In [3]:

# 输入样例,该列来源于课本
x1 = [1, 1]
x2 = [5, 1]
x3 = [4, 4]

In [4]:

# 计算x1与x2和x3之间的距离
for i in range(1,5):# i从1到4
    r={ '1-{}'.format(c):L(x1, c, p=i) for c in [x2, x3]}
    print(min(zip(r.values(), r.keys())))# 当p=i时选出x2和我x3中距离x1最近的点

结果:
(3.0, '1-[4, 4]')
(3.0, '1-[4, 4]')
(3.0, '1-[4, 4]')
(3.0, '1-[4, 4]')

编写K-近邻算法
python实现,遍历所有数据点,找出n个距离最近的点的分类情况,少数服从多数

In [5]:

# 导包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter

In [6]:

# data
iris = load_iris()# 获取python中鸢尾花Iris数据集
df = pd.DataFrame(iris.data, columns=iris.feature_names)# 将数据集使用DataFrame建表
df['label'] = iris.target# 将表的最后一列作为目标列
df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
# data = np.array(df.iloc[:100, [0, 1, -1]])

In [7]:

df# 将建好的表显示在屏幕上查看

Out[7]:

sepal length sepal width petal length petal width label
0 5.1 3.5 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 1.3 0.2
3 4.6 3.1 1.5 0.2
4 5.0 3.6 1.4 0.2
5 5.4 3.9 1.7 0.4
6 4.6 3.4 1.4 0.3
7 5.0 3.4 1.5 0.2
8 4.4 2.9 1.4 0.2
9 4.9 3.1 1.5 0.1
10 5.4 3.7 1.5 0.2
11 4.8 3.4 1.6 0.2
12 4.8 3.0 1.4 0.1
13 4.3 3.0 1.1 0.1
14 5.8 4.0 1.2 0.2
15 5.7 4.4 1.5 0.4
16 5.4 3.9 1.3 0.4
17 5.1 3.5 1.4 0.3
18 5.7 3.8 1.7 0.3
19 5.1 3.8 1.5 0.3
20 5.4 3.4 1.7 0.2
21 5.1 3.7 1.5 0.4
22 4.6 3.6 1.0 0.2
23 5.1 3.3 1.7 0.5
24 4.8 3.4 1.9 0.2
25 5.0 3.0 1.6 0.2
26 5.0 3.4 1.6 0.4
27 5.2 3.5 1.5 0.2
28 5.2 3.4 1.4 0.2
29 4.7 3.2 1.6 0.2
... ... ... ... ...
120 6.9 3.2 5.7 2.3
121 5.6 2.8 4.9 2.0
122 7.7 2.8 6.7 2.0
123 6.3 2.7 4.9 1.8
124 6.7 3.3 5.7 2.1
125 7.2 3.2 6.0 1.8
126 6.2 2.8 4.8 1.8
127 6.1 3.0 4.9 1.8
128 6.4 2.8 5.6 2.1
129 7.2 3.0 5.8 1.6
130 7.4 2.8 6.1 1.9
131 7.9 3.8 6.4 2.0
132 6.4 2.8 5.6 2.2
133 6.3 2.8 5.1 1.5
134 6.1 2.6 5.6 1.4
135 7.7 3.0 6.1 2.3
136 6.3 3.4 5.6 2.4
137 6.4 3.1 5.5 1.8
138 6.0 3.0 4.8 1.8
139 6.9 3.1 5.4 2.1
140 6.7 3.1 5.6 2.4
141 6.9 3.1 5.1 2.3
142 5.8 2.7 5.1 1.9
143 6.8 3.2 5.9 2.3
144 6.7 3.3 5.7 2.5
145 6.7 3.0 5.2 2.3
146 6.3 2.5 5.0 1.9
147 6.5 3.0 5.2 2.0
148 6.2 3.4 5.4 2.3
149 5.9 3.0 5.1 1.8
150 rows × 5 columns

In [8]:

#数据进行可视化
#将标签为0、1的两种花,根据特征为长度和宽度打点表示
plt.scatter(df[:50]['sepal length'], df[:50]['sepal width'], label='0')
plt.scatter(df[50:100]['sepal length'], df[50:100]['sepal width'], label='1')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()

Out[8]:
<matplotlib.legend.Legend at 0xb3015c0>

In [9]:

#取数据,并且分成训练和测试集合
data = np.array(df.iloc[:100, [0, 1, -1]])
#按行索引,取出第0列第1列和最后一列,即取出sepal长度、宽度和标签
X, y = data[:,:-1], data[:,-1]#X为sepal length,sepal width y为标签 
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# train_test_split函数用于将矩阵随机划分为训练子集和测试子集

In [10]:

class KNN:
    def __init__(self, X_train, y_train, n_neighbors=3, p=2):
        """
        parameter: n_neighbors 临近点个数
        parameter: p 距离度量
        """
        self.n = n_neighbors#临*点个数
        self.p = p#距离度量
        self.X_train = X_train
        self.y_train = y_train
    def predict(self, X):
        # 取出n个点,放入空的列表,列表中存放预测点与训练集点的距离及其对应标签
        # 取距离最小的k个点:先取前k个,然后遍历替换
        # knn_list存“距离”和“label”
        knn_list = []
        for i in range(self.n):
            #np.linalg.norm 求范数
            dist = np.linalg.norm(X - self.X_train[i], ord=self.p)
            knn_list.append((dist, self.y_train[i]))
        #再取出训练集剩下的点,然后与n_neighbor个点比较大叫,将距离大的点更新
        #保证knn_list列表中的点是距离最小的点
        for i in range(self.n, len(self.X_train)):
            max_index = knn_list.index(max(knn_list, key=lambda x: x[0]))
            dist = np.linalg.norm(X - self.X_train[i], ord=self.p)
            if knn_list[max_index][0] > dist:
                knn_list[max_index] = (dist, self.y_train[i])
        # 统计
        # 统计分类最多的点,确定预测数据的分类
        knn = [k[-1] for k in knn_list]
        #counter为计数器,按照标签计数
        count_pairs = Counter(knn)
        #排序
        max_count = sorted(count_pairs, key=lambda x:x)[-1]
        return max_count

    #预测的正确率    
    def score(self, X_test, y_test):
        right_count = 0
        n = 10
        for X, y in zip(X_test, y_test):
            label = self.predict(X)
            if label == y:
                right_count += 1
        return right_count / len(X_test)

In [11]:

clf = KNN(X_train, y_train)# 调用KNN算法进行计算

In [12]:

clf.score(X_test, y_test)# 计算正确率

Out[12]:1.0
In [13]:

#预测点
test_point = [6.0, 3.0]
#预测结果
print('Test Point: {}'.format(clf.predict(test_point)))

结果:Test Point: 1.0

In [14]:

plt.scatter(df[:50]['sepal length'], df[:50]['sepal width'], label='0')
plt.scatter(df[50:100]['sepal length'], df[50:100]['sepal width'], label='1')
#打印预测点
plt.plot(test_point[0], test_point[1], 'bo', label='test_point')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()

Out[14]:
<matplotlib.legend.Legend at 0xb6fa240>

scikitlearn
In [15]:

from sklearn.neighbors import KNeighborsClassifier

In [16]:

clf_sk = KNeighborsClassifier()
clf_sk.fit(X_train, y_train)

Out[16]:
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
metric_params=None, n_jobs=1, n_neighbors=5, p=2,
weights='uniform')
In [17]:

clf_sk.score(X_test, y_test)

Out[17]:1.0

sklearn.neighbors.KNeighborsClassifier

n_neighbors: 临近点个数
p: 距离度量
algorithm: 近邻算法,可选{'auto', 'ball_tree', 'kd_tree', 'brute'}
weights: 确定近邻的权重
kd树
In [18]:

# kd-tree每个结点中主要包含的数据结构如下
class KdNode(object):
    def __init__(self, dom_elt, split, left, right):
        self.dom_elt = dom_elt # k维向量节点(k维空间中的一个样本点)
        self.split = split # 整数(进行分割维度的序号)
        self.left = left # 该结点分割超平面左子空间构成的kd-tree
        self.right = right # 该结点分割超平面右子空间构成的kd-tree
        
class KdTree(object):
    def __init__(self, data):
        k = len(data[0]) # 数据维度
        
def CreateNode(split, data_set): # 按第split维划分数据集exset创建KdNode
        if not data_set: # 数据集为空
            return None
        # key参数的值为一个函数,此函数只有一个参数且返回一个值用来进行比较
        # operator模块提供的itemgetter函数用于获取对象的哪些维的数据,参数为需要获取的数据在对象
        #data_set.sort(key=itemgetter(split)) # 按要进行分割的那一维数据排序
        data_set.sort(key=lambda x: x[split])
        split_pos = len(data_set) // 2 # //为Python中的整数除法
        median = data_set[split_pos] # 中位数分割点
        split_next = (split + 1) % k # cycle coordinates
        
        # 递归的创建kd树
        return KdNode(median, split,
            CreateNode(split_next, data_set[:split_pos]), # 创建左子树
            CreateNode(split_next, data_set[split_pos + 1:])) # 创建右子树
    
        self.root = CreateNode(0, data) # 从第0维分量开始构建kd树,返回根节点
    
# KDTree的前序遍历
def preorder(root):
    print (root.dom_elt)
    if root.left: # 节点不为空
        preorder(root.left)
    if root.right:
        preorder(root.right)

In [19]:

# 对构建好的kd树进行搜索,寻找与目标点最近的样本点:
from math import sqrt
from collections import namedtuple

# 定义一个namedtuple,分别存放最近坐标点、最近距离和访问过的节点数
result = namedtuple("Result_tuple", "nearest_point nearest_dist nodes_visited")

def find_nearest(tree, point):
    k = len(point) # 数据维度
    def travel(kd_node, target, max_dist):
        if kd_node is None:
            return result([0] * k, float("inf"), 0) # python中用float("inf")和float("-inf")表示正负

        nodes_visited = 1
    
        s = kd_node.split # 进行分割的维度
        pivot = kd_node.dom_elt # 进行分割的“轴”
    
        if target[s] <= pivot[s]: # 如果目标点第s维小于分割轴的对应值(目标离左子树更近)
            nearer_node = kd_node.left # 下一个访问节点为左子树根节点
            further_node = kd_node.right # 同时记录下右子树
        else: # 目标离右子树更近
            nearer_node = kd_node.right # 下一个访问节点为右子树根节点
            further_node = kd_node.left# 同时记录下右子树
        
        temp1 = travel(nearer_node, target, max_dist) # 进行遍历找到包含目标点的区域

        nearest = temp1.nearest_point # 以此叶结点作为“当前最近点”
        dist = temp1.nearest_dist # 更新最近距离

        nodes_visited += temp1.nodes_visited

        if dist < max_dist:
            max_dist = dist # 最近点将在以目标点为球心,max_dist为半径的超球体内

        temp_dist = abs(pivot[s] - target[s]) # 第s维上目标点与分割超平面的距离
        if max_dist < temp_dist: # 判断超球体是否与超平面相交
            return result(nearest, dist, nodes_visited) # 不相交则可以直接返回,不用继续判断
    
        #----------------------------------------------------------------------
        # 计算目标点与分割点的欧氏距离
        temp_dist = sqrt(sum((p1 - p2) ** 2 for p1, p2 in zip(pivot, target)))

        if temp_dist < dist: # 如果“更近”
            nearest = pivot # 更新最近点
            dist = temp_dist # 更新最近距离
            max_dist = dist # 更新超球体半径
        
        # 检查另一个子结点对应的区域是否有更近的点
        temp2 = travel(further_node, target, max_dist)

        nodes_visited += temp2.nodes_visited
        if temp2.nearest_dist < dist: # 如果另一个子结点内存在更近距离
            nearest = temp2.nearest_point # 更新最近点
            dist = temp2.nearest_dist # 更新最近距离

        return result(nearest, dist, nodes_visited)
    
    return travel(tree.root, point, float("inf")) # 从根节点开始递归

In [20]:

data = [[2,3],[5,4],[9,6],[4,7],[8,1],[7,2]]
kd = KdTree(data)
preorder(kd.root)

结果:
[7, 2]
[5, 4]
[2, 3]
[4, 7]
[9, 6]
[8, 1]

In [21]:

from time import clock
from random import random

# 产生一个k维随机向量,每维分量值在0~1之间
def random_point(k):
    return [random() for _ in range(k)]

# 产生n个k维随机向量
def random_points(k, n):
    return [random_point(k) for _ in range(n)]

In [22]:

ret = find_nearest(kd, [3,4.5])
print (ret)

结果:
Result_tuple(nearest_point=[2, 3], nearest_dist=1.8027756377319946, nodes_visited=4)

In [23]:

N = 400000
t0 = clock()
kd2 = KdTree(random_points(3, N)) # 构建包含四十万个3维空间样本点的kd树
ret2 = find_nearest(kd2, [0.1,0.5,0.8]) # 四十万个样本点中寻找离目标最近的点
t1 = clock()
print ("time: ",t1-t0, "s")
print (ret2)

结果:
7.299844505209247 s
Result_tuple(nearest_point=[0.10505669630674175, 0.49542598718931097, 0.803316691954
3026], nearest_dist=0.007582362181450973, nodes_visited=53)

五、实验小结

本次实验,用python实现了曼哈顿距离、欧氏距离、闵式距离算法,并测试了算法正确性。理解了K-近邻算法使用的模型实际上对应于对特征空间的划分,模型由三个基本要素决定——距离度量、k值选择(k值的选取,既不能太大,也不能太小,何值为最好,需要实验调整参数确定!)、分类决策规则。其准确性高,对异常值和噪声有较高的容忍度。但是用这个算法计算量较大,对内存的需求也较大。适用数据范围是:数值型和标称型。
K-近邻算法应用可以针对约会网站的数据分类,用于改进约会网站的配对效果。也可以运用在手写数字识别中。
比起其他机器学习方法,k-近邻算法是最简单最有效的分类数据算法,使用算法时必须有接近实际数据的训练样本数据。事实上k决策树是k-近邻算法的优化版本,比起前者,决策树有效减少了储存空间和计算空间的开销,老师也刚为我们讲解了k决策树也是刚了解想要运用,后期需继续深入学习!

posted on 2021-05-18 20:31  阳宝冲冲冲  阅读(82)  评论(0编辑  收藏  举报