zoukankan      html  css  js  c++  java
  • 机器学习 实验二 K-近邻算法及应用

    机器学习实验-计算机18级 https://edu.cnblogs.com/campus/ahgc/machinelearning
    作业要求 https://edu.cnblogs.com/campus/ahgc/machinelearning/homework/11950
    学 号 3180701236

    一、实验目的
    1.理解K-近邻算法原理,能实现算法K近邻算法;

    2.掌握常见的距离度量方法;

    3.掌握K近邻树实现算法;

    4.针对特定应用场景及数据,能应用K近邻解决实际问题。

    二、实验内容
    1.实现曼哈顿距离、欧氏距离、闵式距离算法,并测试算法正确性。

    2.实现K近邻树算法;

    3.针对iris数据集,应用sklearn的K近邻算法进行类别预测。

    4.针对iris数据集,编制程序使用K近邻树进行类别预测。

    三、实验步骤

    测量距离

    # 导入包
    import math
    from itertools import combinations
    
    # 当p=1时,就是曼哈顿距离;
    # 当p=2时,就是欧氏距离;
    # 当p=inf时,就是闵式距离。
    # 函数主要用于距离测算
    def L(x, y, p=2):
        # x1 = [1, 1], x2 = [5,1]
        if len(x) == len(y) and len(x) > 1:
            sum = 0
            for i in range(len(x)):
                sum += math.pow(abs(x[i] - y[i]), p)
            return math.pow(sum, 1/p)
        else:
            return 0
    
    # 输入样例,该列来源于课本
    x1 = [1, 1]
    x2 = [5, 1]
    x3 = [4, 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最近的点
    

    结果:
    2 50
    1 50
    0 50
    Name: label, dtype: int64

    #画数据的散点图
    plt.scatter(df[:50]['sepal length'], df[:50]['sepal width'], label='0')#将数据的前50个数据绘制散点图
    plt.scatter(df[50:100]['sepal length'], df[50:100]['sepal width'], label='1')#将数据的50-100之间的数据绘制成散点图
    plt.xlabel('sepal length')#给x坐标命名
    plt.ylabel('sepal width')#给y坐标命名
    plt.legend()
    

    结果:

    编写K-近邻算法

    # 导包
    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
    
    # 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]])
    
    df # 将建好的表显示在屏幕上查看
    

    结果:

    # 绘制数据散点图
    plt.scatter(df[:50]['sepal length'], df[:50]['sepal width'], label='0') # 绘制前50个数据的散点图
    plt.scatter(df[50:100]['sepal length'], df[50:100]['sepal width'], label='1') # 绘制50-100个数据的散点图
    plt.xlabel('sepal length')
    plt.ylabel('sepal width') # 设置x,y轴坐标名
    plt.legend() # 绘图
    

    结果:

    data = np.array(df.iloc[:100, [0, 1, -1]]) # iloc函数:通过行号来取行数据,读取数据前100行的第0,1列和最后一列
    # X为data数据集中去除最后一列所形成的新数据集
    # y为data数据集中最后一列数据所形成的新数据集
    X, y = data[:,:-1], data[:,-1] 
    # 选取训练集,和测试集
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
    
    # 建立一个类KNN,用于k-近邻的计算
    class KNN:
        #初始化
        def __init__(self, X_train, y_train, n_neighbors=3, p=2): # 初始化数据,neighbor表示邻近点,p为欧氏距离
            self.n = n_neighbors
            self.p = p
            self.X_train = X_train
            self.y_train = y_train
        
        def predict(self, X):
            # X为测试集
            knn_list = []
            for i in range(self.n): # 遍历邻近点
                dist = np.linalg.norm(X - self.X_train[i], ord=self.p) # 计算训练集和测试集之间的距离
                knn_list.append((dist, self.y_train[i])) # 在列表末尾添加一个元素
                
            for i in range(self.n, len(self.X_train)): # 3-20
                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]
            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)
    
    clf = KNN(X_train, y_train) # 调用KNN算法进行计算
    
    clf.score(X_test, y_test) # 计算正确率
    

    结果:

    test_point = [6.0, 3.0] # 用于算法测试的数据
    print('Test Point: {}'.format(clf.predict(test_point))) # 结果
    

    结果:

    plt.scatter(df[:50]['sepal length'], df[:50]['sepal width'], label='0') # 将数据的前50个数据绘制散点图
    plt.scatter(df[50:100]['sepal length'], df[50:100]['sepal width'], label='1') # 将数据的50-100个数据绘制散点图
    plt.plot(test_point[0], test_point[1], 'bo', label='test_point') # 将测试数据点绘制在图中
    plt.xlabel('sepal length')  
    plt.ylabel('sepal width') # x,y轴命名
    plt.legend()  # 绘图
    

    结果:

    使用scikitlearn中编好的包直接调用实现K-近邻算法
    sklearn.neighbors.KNeighborsClassifier
    n_neighbors: 临近点个数
    p: 距离度量
    algorithm: 近邻算法,可选{'auto', 'ball_tree', 'kd_tree', 'brute'}
    weights: 确定近邻的权重

    # 导包
    from sklearn.neighbors import KNeighborsClassifier
    
    # 调用
    clf_sk = KNeighborsClassifier()
    clf_sk.fit(X_train, y_train)
    

    结果:

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

    结果:

    kd树

    # 建造kd树
    # kd-tree 每个结点中主要包含的数据如下:
    class KdNode(object):
        def __init__(self, dom_elt, split, left, right):
            self.dom_elt = dom_elt#结点的父结点
            self.split = split#划分结点
            self.left = left#做结点
            self.right = right#右结点
    
    class KdTree(object):
        def __init__(self, data):
            k = len(data[0])#数据维度
            #print("创建结点")
            #print("开始执行创建结点函数!!!")
            def CreateNode(split, data_set):
                #print(split,data_set)
                if not data_set:#数据集为空
                    return None
                #print("进入函数!!!")
                data_set.sort(key=lambda x:x[split])#开始找切分平面的维度
                #print("data_set:",data_set)
                split_pos = len(data_set)//2 #取得中位数点的坐标位置(求整)
                median = data_set[split_pos]
                split_next = (split+1) % k #(取余数)取得下一个节点的分离维数
                return KdNode(
                    median,
                    split,
                    CreateNode(split_next, data_set[:split_pos]),#创建左结点
                    CreateNode(split_next, data_set[split_pos+1:]))#创建右结点
            #print("结束创建结点函数!!!")
            self.root = CreateNode(0, data)#创建根结点
                
    #KDTree的前序遍历
    def preorder(root):
        print(root.dom_elt)
        if root.left:
            preorder(root.left)
        if root.right:
            preorder(root.right)
    
    # 遍历kd树
    #KDTree的前序遍历
    def preorder(root):
        print(root.dom_elt)
        if root.left:
            preorder(root.left)
        if root.right:
            preorder(root.right)
                   
    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)#表示数据的无
            
            nodes_visited = 1
            s = kd_node.split #数据维度分隔
            pivot = kd_node.dom_elt #切分根节点
            
            if target[s] <= pivot[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# 得到叶子结点,此时为nearest
            dist = temp1.nearest_dist #update distance
            
            nodes_visited += temp1.nodes_visited
            print("nodes_visited:", nodes_visited)
            if dist < max_dist:
                max_dist = dist
            
            temp_dist = abs(pivot[s]-target[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)))#计算目标点到邻近节点的Distance
            
            if temp_dist < dist:
                
                nearest = pivot #更新最近点
                dist = temp_dist #更新最近距离
                max_dist = dist #更新超球体的半径
                print("输出数据:" , nearest, dist, max_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"))  # 从根节点开始递归
    
    # 数据测试
    data= [[2,3],[5,4],[9,6],[4,7],[8,1],[7,2]]
    kd=KdTree(data)
    preorder(kd.root)
    

    结果:

    # 导包
    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)]
    
    # 输入数据进行测试
    ret = find_nearest(kd, [3,4.5])
    print (ret)
    

    结果:

    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)
    

    结果:

    四、个人小结

    1 算法原理
    knn算法的核心思想是未标记样本的类别,由距离其最近的k个邻居投票来决定。
    具体的,假设我们有一个已标记好的数据集。此时有一个未标记的数据样本,我们的任务是预测出这个数据样本所属的类别。knn的原理是,计算待标记样本和数据集中每个样本的距离,取距离最近的k个样本。待标记的样本所属类别就由这k个距离最近的样本投票产生。
    假设X_test为待标记的样本,X_train为已标记的数据集,算法原理的伪代码如下:

    遍历X_train中的所有样本,计算每个样本与X_test的距离,并把距离保存在Distance数组中。
    对Distance数组进行排序,取距离最近的k个点,记为X_knn。
    在X_knn中统计每个类别的个数,即class0在X_knn中有几个样本,class1在X_knn中有几个样本等。
    待标记样本的类别,就是在X_knn中样本个数最多的那个类别。

    2 算法优缺点
    优点:准确性高,对异常值和噪声有较高的容忍度。
    缺点:计算量较大,对内存的需求也较大。

    3 算法参数
    其算法参数是k,参数选择需要根据数据来决定。
    k值越大,模型的偏差越大,对噪声数据越不敏感,当k值很大时,可能造成欠拟合;
    k值越小,模型的方差就会越大,当k值太小,就会造成过拟合。

  • 相关阅读:
    【基础算法】- 全排列
    【基础算法】- 2分查找
    区块链培训
    Static Binding (Early Binding) vs Dynamic Binding (Late Binding)
    test
    No data is deployed on the contract address!
    "throw" is deprecated in favour of "revert()", "require()" and "assert()".
    Variable is declared as a storage pointer. Use an explicit "storage" keyword to silence this warning.
    京都行
    Failed to write genesis block: database already contains an incompatible
  • 原文地址:https://www.cnblogs.com/cydestiny/p/14780685.html
Copyright © 2011-2022 走看看