• 统计学习方法第三章


    一、KD树

    https://zhuanlan.zhihu.com/p/45346117

    二、代码理解

    2.1 首先定义KD树中的节点类

    class Node(object):
        def __init__(self):
            """Node class to build tree leaves.
            """
            self.father = None
            self.left = None
            self.right = None
            self.feature = None
            self.split = None
    
        def __str__(self):
            return "feature: %s, split: %s" % (str(self.feature), str(self.split))
    
        @property
        def brother(self):
            """Find the node's brother.
            Returns:
                node -- Brother node.
            """
            if not self.father:
                ret = None
            else:
                if self.father.left is self:
                    ret = self.father.right
                else:
                    ret = self.father.left
            return ret
    

    1、每个节点,有如下属性:
    父节点,左子树,右子树,用于切分的特征,切分点。
    2、str,打印树节点的时候返回特征和切分点。
    3、节点的兄弟节点

    2.2 KD树类

    class KDTree(object):
        def __init__(self):
            """KD Tree class to improve search efficiency in KNN.
            Attributes:
                root: the root node of KDTree.
            """
            self.root = Node()
    
        def __str__(self):
            """Show the relationship of each node in the KD Tree.
            Returns:
                str -- KDTree Nodes information.
            """
    
            ret = []
            i = 0
            que = [(self.root, -1)]
            while que:
                nd, idx_father = que.pop(0)
                ret.append("%d -> %d: %s" % (idx_father, i, str(nd)))
                if nd.left:
                    que.append((nd.left, i))
                if nd.right:
                    que.append((nd.right, i))
                i += 1
            return "
    ".join(ret)
    

    打印KDtree的时候的展示样式。

    2.3 生成KDTree

        def build_tree(self, X, y):
            """Build a KD Tree. The data should be scaled so as to calculate variances.
            Arguments:
                X {list} -- 2d list object with int or float.
                y {list} -- 1d list object with int or float.
            """
    
            # 初始化根节点,所有样本的索引,一个que作为临时变量保存当前需要处理的节点。
            nd = self.root
            idxs = range(len(X))
            que = [(nd, idxs)] 
    
            while que:
    
                nd, idxs = que.pop(0) # 为当前节点的各个属性赋值,其属性值通过后续计算得到。
    
                n = len(idxs) # 此次循环要处理的样本总数。当只有一个样本的时候,就不用再分了。
                # Stop split if there is only one element in this node
                if n == 1:
                    nd.split = (X[idxs[0]], y[idxs[0]])
                    continue
                # Split
                feature = self._choose_feature(X, idxs) # 输入样本X和其索引idxs,返回0或者1,因为是二维特征,表示对哪个维度进行分割。
                median_idx = self._get_median_idx(X, idxs, feature) # 返回需要切分的列中的中值的索引。
                idxs_left, idxs_right = self._split_feature(
                    X, idxs, feature, median_idx) # 根据切分维度和索引把x分为两组,小于阈值的为左子树,大于等于阈值的为右子树。[[左子树索引组成的列表], [右子树索引组成的列表]]。
                # 所有属性都get之后,进行赋值更新当前节点。
                nd.feature = feature 
                nd.split = (X[median_idx], y[median_idx]) # 根据哪个值进行切分。
                # Put children of current node in que 确定其左右子节点。即把一个空节点的父节点设为该节点。
                if idxs_left != []:
                    nd.left = Node()
                    nd.left.father = nd
                    que.append((nd.left, idxs_left)) # 下次循环左节点,把该节点,和左子树索引列表保存到临时列表que中。相当于深度优先遍历。
                if idxs_right != []:
                    nd.right = Node()
                    nd.right.father = nd
                    que.append((nd.right, idxs_right))
    

    最好KDTree保存了所有节点的信息。

    2.4搜索函数

    搜索KD Tree中与目标元素距离最近的节点,使用广度优先搜索来实现。

    1. 从根节点开始,根据目标在分割特征中是否小于或大于当前节点,向左或向右移动。
    2. 一旦算法到达叶节点,它就将该叶子节点保存为“当前最佳”。
    3. 回溯,即从叶节点再返回到根节点
    4. 如果当前节点比当前最佳节点更接近,那么它就成为当前最好的。
    5. 如果目标距离当前节点的父节点所在的将数据集分割为两份的超平面的距离更接近,说明当前节点的兄弟节点所在的子树有可能包含更近的点。因此需要对这个兄弟节点递归执行1-4步。
        def nearest_neighbour_search(self, Xi):
            """Nearest neighbour search and backtracking.
            Arguments:
                Xi {list} -- 1d list with int or float. 需要查找的目标值。
            Returns:
                node -- The nearest node to Xi.
            """
    
            # The leaf node after searching Xi.
            dist_best = float("inf") # 首先定义一个无穷大的距离。
            nd_best = self._search(Xi, self.root) # 从根节点开始,找到“当前最佳”叶子节点。即1,2。
            que = [(self.root, nd_best)] # 同样一个临时列表变量保存信息
            while que:
                nd_root, nd_cur = que.pop(0)
                # 计算目标值xi和根节点的距离
                dist = self._get_eu_dist(Xi, nd_root)
                # Update best node and distance.
                if dist < dist_best:
                    dist_best, nd_best = dist, nd_root # 更新当前最小距离,根节点为最合适节点。
                while nd_cur is not nd_root: # 如果当前节点不是根节点
                    # Calculate distance between Xi and current node
                    dist = self._get_eu_dist(Xi, nd_cur) # 计算目标值xi和当前节点的距离。
                    # Update best node, distance and visit flag.
                    if dist < dist_best:
                        dist_best, nd_best = dist, nd_cur
                    # If it's necessary to visit brother node.
                    if nd_cur.brother and dist_best > self._get_hyper_plane_dist(Xi, nd_cur.father): # 计算目标值和当前节点的父节点所在的将数据集分割为两份的超平面的距离
                        _nd_best = self._search(Xi, nd_cur.brother) # 从其兄弟节点找到最小距离节点。
                        que.append((nd_cur.brother, _nd_best))
                    # Back track.
                    nd_cur = nd_cur.father # 找到当钱节点的父节点,继续计算距离。
    
            return nd_best # 最终返回和目标值xi距离最小的节点
    

    三、第三章习题

    3.1 参照图3.1,在二维空间中给出实例点,画出kk为1和2时的k近邻法构成的空间划分,并对其进行比较,体会k值选择与模型复杂度及预测准确率的关系。

    import numpy as np
    from sklearn.neighbors import KNeighborsClassifier
    import matplotlib.pyplot as plt
    from matplotlib.colors import ListedColormap 
    
    data = np.array([[5, 12, 1],
                    [6, 21, 0],
                    [14, 5, 0],
                    [16, 10, 0],
                    [13, 19, 0],
                    [13, 32, 1],
                    [17, 27, 1],
                    [18, 24, 1],
                    [20, 20, 0],
                    [23, 14, 1],
                    [23, 25, 1],
                    [23, 31, 1],
                    [26, 8, 0],
                    [30, 17, 1],
                    [30, 26, 1],
                    [34, 8, 0],
                    [34, 19, 1],
                    [37, 28, 1]])
    X_train = data[:, 0:2]
    y_train = data[:, 2]
    
    models = (KNeighborsClassifier(n_neighbors=1, n_jobs=-1),
              KNeighborsClassifier(n_neighbors=2, n_jobs=-1)) # 在训练集中找到与该实例最邻近的k个实例
    models = (clf.fit(X_train, y_train) for clf in models)
    
    titles = ('K Neighbors with k=1',
              'K Neighbors with k=2')
    
    fig = plt.figure(figsize=(15, 5))
    plt.subplots_adjust(wspace=0.4, hspace=0.4) # 为子图之间的空间保留的宽度和高度。
    
    X0, X1 = X_train[:, 0], X_train[:, 1] 
    
    x_min, x_max = X0.min() - 1, X0.max() + 1
    y_min, y_max = X1.min() - 1, X1.max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.2),
                             np.arange(y_min, y_max, 0.2)) # 生成网格采样点
    
    for clf, title, ax in zip(models, titles, fig.subplots(1, 2).flatten()):    
        Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
        Z = Z.reshape(xx.shape) 
        colors = ('red', 'green', 'lightgreen', 'gray', 'cyan')  
        cmap = ListedColormap(colors[:len(np.unique(Z))])  # 选择颜色
        ax.contourf(xx, yy, Z, cmap=cmap, alpha=0.5) # 轮廓填充
    
        ax.scatter(X0, X1, c=y_train, s=50, edgecolors='k', cmap=cmap, alpha=0.5) # 散点图
        ax.set_title(title)
    
    plt.show()
    

    3.2 利用例题3.2构造的kd树求点(x=(3,4.5)^T) 的最近邻点。

    import numpy as np
    from sklearn.neighbors import KDTree
    
    train_data = np.array([(2, 3), (5, 4), (9, 6), (4, 7), (8, 1), (7, 2)])
    tree = KDTree(train_data, leaf_size=2)
    dist, ind = tree.query(np.array([(3, 4.5)]), k=1)
    x1 = train_data[ind[0]][0][0]
    x2 = train_data[ind[0]][0][1]
    
    print("x点的最近邻点是({0}, {1})".format(x1, x2))
    

    3.3 参照算法3.3,写出输出为(x)(k)近邻的算法

    解答:
    算法:用kd树的kk近邻搜索
    输入:已构造的kd树;目标点(x)
    输出:(x)的最近邻

    1. 在kdkd树中找出包含目标点xx的叶结点:从根结点出发,递归地向下访问树。若目标点xx当前维的坐标小于切分点的坐标,则移动到左子结点,否则移动到右子结点,直到子结点为叶结点为止;
    2. 如果“当前kk近邻点集”元素数量小于kk或者叶节点距离小于“当前kk近邻点集”中最远点距离,那么将叶节点插入“当前k近邻点集”;
    3. 递归地向上回退,在每个结点进行以下操作:
      (a)如果“当前kk近邻点集”元素数量小于kk或者当前节点距离小于“当前kk近邻点集”中最远点距离,那么将该节点插入“当前kk近邻点集”。
      (b)检查另一子结点对应的区域是否与以目标点为球心、以目标点与于“当前kk近邻点集”中最远点间的距离为半径的超球体相交。如果相交,可能在另一个子结点对应的区域内存在距目标点更近的点,移动到另一个子结点,接着,递归地进行最近邻搜索;如果不相交,向上回退;
    4. 当回退到根结点时,搜索结束,最后的“当前kk近邻点集”即为xx的最近邻点。
    # 构建kd树,搜索待预测点所属区域
    from collections import namedtuple
    import numpy as np
    
    
    # 建立节点类
    class Node(namedtuple("Node", "location left_child right_child")):
        def __repr__(self):
            return str(tuple(self))
    
    
    # kd tree类
    class KdTree():
        def __init__(self, k=1):
            self.k = k
            self.kdtree = None
    
        # 构建kd tree
        def _fit(self, X, depth=0):
            try:
                k = self.k
            except IndexError as e:
                return None
            # 这里可以展开,通过方差选择axis
            axis = depth % k
            X = X[X[:, axis].argsort()]
            median = X.shape[0] // 2
            try:
                X[median]
            except IndexError:
                return None
            return Node(
                location=X[median],
                left_child=self._fit(X[:median], depth + 1),
                right_child=self._fit(X[median + 1:], depth + 1)
            )
    
        def _search(self, point, tree=None, depth=0, best=None):
            if tree is None:
                return best
            k = self.k
            # 更新 branch
            if point[0][depth % k] < tree.location[depth % k]:
                next_branch = tree.left_child
            else:
                next_branch = tree.right_child
            if not next_branch is None:
                best = next_branch.location
            return self._search(point, tree=next_branch, depth=depth + 1, best=best)
    
        def fit(self, X):
            self.kdtree = self._fit(X)
            return self.kdtree
    
        def predict(self, X):
            res = self._search(X, self.kdtree)
            return res 
    
    KNN = KdTree()
    X_train = np.array([[2, 3],
                        [5, 4],
                        [9, 6],
                        [4, 7],
                        [8, 1],
                        [7, 2]])
    KNN.fit(X_train)
    X_new = np.array([[3, 4.5]])
    res = KNN.predict(X_new)
    
    x1 = res[0]
    x2 = res[1]
    
    print("x点的最近邻点是({0}, {1})".format(x1, x2))
    
  • 相关阅读:
    Pycharm创建Django项目示例
    Window下MyCat的下载与安装
    Python中使用xlrd、xlwt、xlutils读写Excel文件
    循环队列(Java实现)
    oracle 创建表
    win10 删除文件卡在99%
    python xx005文件操作
    python xx004集合
    python xx003字典
    不理解
  • 原文地址:https://www.cnblogs.com/leimu/p/13666937.html
Copyright © 2020-2023  润新知