<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>第 5 章 K近邻 on 《从零学AI指南手册》</title>
		<link>https://mlwithme.github.io/ml/chapter05/</link>
		<description>Recent content in 第 5 章 K近邻 on 《从零学AI指南手册》</description>
		<generator>Hugo</generator>
		<language>zh_CN</language>
		
		
		
		
			<atom:link href="https://mlwithme.github.io/ml/chapter05/index.xml" rel="self" type="application/rss+xml" />
			<item>
				<title>5.1 K近邻思想</title>
				<link>https://mlwithme.github.io/ml/chapter05/7c610e923e754259/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://mlwithme.github.io/ml/chapter05/7c610e923e754259/</guid>
				<description>&lt;p&gt;在前几章中，我们分别介绍了线性回归、逻辑回归及模型的改善与泛化。从这章开始，我们将继续学习下一个新的算法模型——K近邻(K-Nearest Neighbor, KNN)。整个K近邻算法的学习路线如图5-1所示，整体来看掌握阶段一的内容相对简单并且这里还加入了sklearn中的网格搜索模块的内容介绍，而后面两个阶段的内容则具有一定的难度，主要集中在kd树的构建和搜索的原理及实现过程，各位读者可以按需进行学习。&lt;/p&gt;&#xA;&lt;div align=center&gt;&lt;img width=&#34;400&#34; src=&#34;https://mlwithme.github.io/images/ml/240427133548.jpg&#34;/&gt; &lt;/div&gt;&lt;center&gt;图 5-1 K近邻学习路线图&lt;/center&gt;&#xA;&lt;h1 id=&#34;51-k近邻思想&#34;&gt;5.1 K近邻思想&lt;a class=&#34;anchor&#34; href=&#34;#51-k%e8%bf%91%e9%82%bb%e6%80%9d%e6%83%b3&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;从K近邻的这一名字可以看出，这一算法的核心在于数量“K”和状态“近邻”。某一天，你和几位朋友准备去外面聚餐，但是就晚上吃什么菜一直各持己见。最后，无奈的你提出用少数人服从多数人的原则进行选择。于是你们每个人都将自己想要吃的东西写在了纸条上，最后的统计情况是： 3个人赞成吃火锅、2个人赞成吃炒菜、1个人赞成吃自助。当然，最后你们一致同意按照多数人的意见去吃了火锅。那这个吃火锅和K近邻有什么关系呢？吃火锅确实跟K近邻没关系，但是整个决策的过程却完全体现了K近邻算法的决策过程。&lt;/p&gt;</description>
			</item>
			<item>
				<title>5.2 K近邻原理</title>
				<link>https://mlwithme.github.io/ml/chapter05/1a97571e0a6447ef/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://mlwithme.github.io/ml/chapter05/1a97571e0a6447ef/</guid>
				<description>&lt;h1 id=&#34;52-k近邻原理&#34;&gt;5.2 K近邻原理&lt;a class=&#34;anchor&#34; href=&#34;#52-k%e8%bf%91%e9%82%bb%e5%8e%9f%e7%90%86&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;521-算法原理&#34;&gt;5.2.1 算法原理&lt;a class=&#34;anchor&#34; href=&#34;#521-%e7%ae%97%e6%b3%95%e5%8e%9f%e7%90%86&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;如图5-2所示，黑色样本点为原始的训练数据，并且包含了0、1、2这3个类别（分别为图中不同形状的样本点）。现在得到一个新的样本点（图中黑色倒三角），需要对其所属类别进行分类，那K近邻是如何对其进行分类的呢？&lt;/p&gt;&#xA;&lt;div align=center&gt;&lt;img width=&#34;300&#34; src=&#34;https://mlwithme.github.io/images/ml/p5-1.png&#34;/&gt;&lt;/div&gt;&lt;center&gt;图 5-2 K近邻原理示意图&lt;/center&gt;&#xA;&lt;p&gt;首先K近邻会确定一个K值，然后选择离自己（黑色倒三角样本点）最近的K个样本点，最后根据投票的规则（Majority Voting Rule）确定新样本所属的类别。如图5-2所示，示例中选择了离三角形样本点最近的14个样本点（正方形4个、圆形7个、星形3个），并且在图中离三角形样本点最近的14个样本中最多的为圆形样本，所以K近邻算法将把新样本归类为类别1。因此，对于K近邻算法的原理可以总结为如下3个步骤：&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;1. 确定一个K值&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;用于选择离自己（三角形样本点）最近的样本数。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;2. 选择一种度量距离&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;用来计算并得到离自己最近的K个样本，示例中采用了应用最为广泛的欧氏距离。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;3. 确定一种决策规则&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;用来判定新样本所属类别，示例中采用了基于投票的分类规则。&lt;/p&gt;&#xA;&lt;p&gt;可以看出，K近邻分类的3个步骤其实对应了3个超参数的选择，但是，通常来讲，对于决策规则的选择基本上都采用了基于投票的决策规则，因此下面对于这个问题也就不再进行额外讨论。&lt;/p&gt;&#xA;&lt;h2 id=&#34;522-k值选择&#34;&gt;5.2.2 K值选择&lt;a class=&#34;anchor&#34; href=&#34;#522-k%e5%80%bc%e9%80%89%e6%8b%a9&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;K值的选择会极大程度上影响K近邻的分类结果。如图5-3所示，可以想象，如果在分类过程中选择较小的K值，则会使模型的训练误差减小从而使模型的泛化误差增大，也就是模型过于复杂而产生了过拟合现象。当选择较大的K值时，将使模型趋于简单，容易发生欠拟合的情况。极端情况下，如果直接将K值设置为样本总数，则无论新输入的样本点位于什么地方，模型都会简单地将它预测为训练样本最多的类别，并且恒定不变，因此，对于K值的选择，依然建议使用第4章所介绍的交叉验证方法进行选择。&lt;/p&gt;&#xA;&lt;div align=center&gt;&lt;img width=&#34;500&#34; src=&#34;https://mlwithme.github.io/images/ml/p5-2.png&#34;/&gt;&lt;/div&gt;&lt;center&gt;图 5-3 K值选择图形&lt;/center&gt;&#xA;&lt;h2 id=&#34;523-距离度量&#34;&gt;5.2.3 距离度量&lt;a class=&#34;anchor&#34; href=&#34;#523-%e8%b7%9d%e7%a6%bb%e5%ba%a6%e9%87%8f&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;在样本空间中，任意两个样本点之间的距离都可以看作两个样本点之间的相似性度量。两个样本点之间的距离越近，也就意味着这两个样本点越相似。在第11章介绍聚类算法时，同样也会用到样本点间相似性的度量。同时，不同的距离度量方式将会产生不同的距离，进而最后产生不同的分类结果。虽然一般情况下K近邻使用的是欧氏距离，但也可以是其他距离，例如更一般的$L_P$距离或者Minkowski距离[1]。&lt;/p&gt;&#xA;&lt;p&gt;设训练样本$X=\{{{x}^{(1)}},{{x}^{(2)}},...,{{x}^{(n)}}\}$其中${{x}^{(i)}}=\{x_{1}^{(i)},x_{2}^{(i)},...,x_{m}^{(i)}\}\in {{R}^{m}}$，即每个样本包含$m$个特征维度，则$L_p$距离定义为&lt;/p&gt;&#xA;$$&#xA;{{L}_{p}}({{x}^{(i)}},{{x}^{(j)}})={{\left( \sum\limits_{k=1}^{m}{|}x_{k}^{(i)}-x_{k}^{(j)}{{|}^{p}} \right)}^{\frac{1}{p}}};\quad p\ge 1\tag{5-1}&#xA;$$&lt;p&gt;&#xA;(1) 当$p=1$时称为曼哈顿（Manhattan Distance），即&#xA;&lt;/p&gt;&#xA;$$&#xA;{{L}_{1}}({{x}^{(i)}},{{x}^{(j)}})=\sum\limits_{k=1}^{m}{|}x_{k}^{(i)}-x_{k}^{(j)}|\tag{5-2}&#xA;$$&lt;p&gt;&#xA;从式(5-2)可以看出，曼哈顿距离计算的是各个维度之间距离的绝对值累加和。&lt;/p&gt;&#xA;&lt;p&gt;(2) 当$p=2$时称为欧氏距离（Euclidean Distance），即&#xA;&lt;/p&gt;&#xA;$$&#xA;{{L}_{2}}({{x}^{(i)}},{{x}^{(j)}})={{\left( \sum\limits_{k=1}^{m}{|}x_{k}^{(i)}-x_{k}^{(j)}{{|}^{2}} \right)}^{\frac{1}{2}}}\tag{5-3}&#xA;$$&lt;p&gt;&#xA;(3) 当$p=\infty$时，它是各个坐标距离中的最大值，即&lt;/p&gt;&#xA;$$&#xA;  {{L}_{\infty }}({{x}^{(i)}},{{x}^{(j)}})=\underset{k}{\mathop{\max }}\,|x_{k}^{(i)}-x_{k}^{(j)}|\tag{5-4}&#xA;$$&lt;p&gt;&#xA;当然，$p$同样能取其他任意的正整数，然后按照式(5-1)进行计算即可。&lt;/p&gt;&#xA;&lt;p&gt;例如现有二维空间的3个样本点${{x}^{(1)}}=(0,0),{{x}^{(2)}}=(4,0),{{x}^{(3)}}=(3,3)$，则其在不同取值$p$下，距离样本点${{x}^{(1)}}$最近邻的点为&#xA;&lt;/p&gt;&#xA;$$&#xA;\begin{aligned}&#xA;  &amp; {{L}_{1}}({{x}^{(1)}},{{x}^{(2)}})=|0-4|+|0-0|=4 \\[1ex] &#xA; &amp; {{L}_{1}}({{x}^{(1)}},{{x}^{(3)}})=|0-3|+|0-3|=6 \\[1ex]  &#xA; &amp; {{L}_{2}}({{x}^{(1)}},{{x}^{(2)}})=\sqrt{{{(0-4)}^{2}}+{{(0-0)}^{2}}}=4 \\[1ex]  &#xA; &amp; {{L}_{2}}({{x}^{(1)}},{{x}^{(3)}})=\sqrt{{{(0-3)}^{2}}+{{(0-3)}^{2}}}\approx 4.2 \\[1ex]  &#xA; &amp; {{L}_{\infty }}({{x}^{(1)}},{{x}^{(2)}})=\max \{|0-4|,|0-0|\}=4 \\[1ex]  &#xA; &amp; {{L}_{\infty }}({{x}^{(1)}},{{x}^{(3)}})=\max \{|0-3|,|0-3|\}=3 \\ &#xA;\end{aligned}\tag{5-5}&#xA;$$&lt;p&gt;&#xA;由式(5-5)可知，当$p=1,2,\infty$时，离样本点${{x}^{(1)}}$最近的样本点分别是 ${{x}^{(2)}},{{x}^{(2)}},{{x}^{(3)}}$。&lt;/p&gt;</description>
			</item>
			<item>
				<title>5.3 sklearn接口与示例代码</title>
				<link>https://mlwithme.github.io/ml/chapter05/d245bda107034e65/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://mlwithme.github.io/ml/chapter05/d245bda107034e65/</guid>
				<description>&lt;h1 id=&#34;53-sklearn接口与示例代码&#34;&gt;5.3 sklearn接口与示例代码&lt;a class=&#34;anchor&#34; href=&#34;#53-sklearn%e6%8e%a5%e5%8f%a3%e4%b8%8e%e7%a4%ba%e4%be%8b%e4%bb%a3%e7%a0%81&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;在正式介绍如何使用sklearn库完成K近邻的建模任务前，我们先来总结一下使用sklearn来进行机器学习建模的流程，这样更加有利于对后续内容的学习。&lt;/p&gt;&#xA;&lt;h2 id=&#34;531-sklearn接口介绍&#34;&gt;5.3.1 sklearn接口介绍&lt;a class=&#34;anchor&#34; href=&#34;#531-sklearn%e6%8e%a5%e5%8f%a3%e4%bb%8b%e7%bb%8d&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;根据第2、3、4章中的示例代码可以发现，sklearn在实现各类算法模型时基本上遵循了统一的接口风格，这使我们在刚开始学习的时候很容易入门。总结起来，在sklearn中对于各类模型的使用，基本上遵循着以下3个步骤。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;1. 建立模型&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;这一步通常来讲在对应的路径下导入我们需要用到的模型类，例如可以通过代码来导入一个基于梯度下降算法优化的分类器，示例代码如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;sklearn.linear_model&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; SGDClassifier&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在导入模型类以后，需要通过传入模型对应的参数来实例化这个模型，例如可以通过相应参数实例化一个逻辑回归模型，示例代码如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;model &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; SGDClassifier(loss&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;log_loss&amp;#39;&lt;/span&gt;,penalty&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;l2&amp;#39;&lt;/span&gt;, alpha&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;0.5&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;同时，由于sklearn在迭代更新中可能会更改一些接口的名称或者位置，所以具体的路径信息可以查看官方的API说明文档 [2]。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;2. 训练模型&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;在sklearn中，所有模型的训练（或者计算）过程都是通过&lt;code&gt;model.fit()&lt;/code&gt;方法来完成的，并且一般情况下需要按实际情况在调用&lt;code&gt;model.fit()&lt;/code&gt;时传入相应参数。如果是有监督模型，则一般是&lt;code&gt;model.fit(x,y)&lt;/code&gt;，如果是无监督模型，则一般是&lt;code&gt;mode.fit(x)&lt;/code&gt;。同时，还可以调用&lt;code&gt;model.score(x,y)&lt;/code&gt;来对模型的结果进行评估。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;3. 模型预测&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;在训练好一个模型后，通常要对测试集或者新输入的数据进行预测。在sklearn中一般通过模型类对应的&lt;code&gt;model.predict(x)&lt;/code&gt;方法实现，但这也不是绝对的，例如在对数据进行预处理时，调用&lt;code&gt;model.fit()&lt;/code&gt;方法在训练集上计算并得到相应的参数后，往往通过&lt;code&gt;model.transform()&lt;/code&gt;方法来对测试集（或新数据）进行变换。&lt;/p&gt;&#xA;&lt;p&gt;总体上来讲，在sklearn中基本上算法模型可以通过上面这3个步骤来完成对模型的建立、训练与预测。&lt;/p&gt;&#xA;&lt;h2 id=&#34;532-k近邻示例代码&#34;&gt;5.3.2 K近邻示例代码&lt;a class=&#34;anchor&#34; href=&#34;#532-k%e8%bf%91%e9%82%bb%e7%a4%ba%e4%be%8b%e4%bb%a3%e7%a0%81&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;从5.2节的内容分析可知，其实K近邻在分类过程中同之前算法模型不一样，即没有一个训练求解参数的过程。这是因为K近邻算法根本就没有可训练的参数，只有3个超参数，而K近邻算法的核心在于如何快速地找到距离任意一个样本最近的K个样本点。当然，最直接的办法就是遍历样本点进行距离计算，但是当样本点达到一定数量级后这种做法显然是行不通的，所以此时可以通过建立KD树（KD Tree）或者Ball树（Ball Tree）来解决这一问题。不过这里暂时不做介绍，先直接通过开源库sklearn来完成建模过程。&lt;/p&gt;&#xA;&lt;p&gt;本次示例的数据集仍旧采用第4章中所介绍的手写体分类数据集。同时，在下面示例中我们将介绍如何通过网格搜索（Grid Search）来快速完成模型的选择，完整代码见&lt;code&gt;AllBooKCode/Chapter05/C03_knn_train.py&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;1. 模型选择&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;由于在4.6.1节中已经详细介绍了该数据集的载入和预处理方法，所以这里就不再赘述了。从上面的分析可以得知，K近邻算法有两个（另外一个暂不考虑）超参数，即K值和度量方式P值。假设两者的取值分别为&lt;code&gt;n_neighbors = [5, 6, 7, 8, 9, 10]&lt;/code&gt;、&lt;code&gt;p=[1, 2]&lt;/code&gt;，则此时一共有12个备选模型。同时，如果采用5折交叉验证，则一共需要进行60次模型拟合，并且需要3个循环实现。不过好在sklearn中提供了网格搜索的功能可以帮助我们通过4行代码实现上述功能，示例代码如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;sklearn.neighbors&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; KNeighborsClassifier&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;sklearn.model_selection&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; GridSearchCV&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;3&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06287e&#34;&gt;model_selection&lt;/span&gt;(x_train, y_train):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;4&lt;/span&gt;     paras &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;n_neighbors&amp;#39;&lt;/span&gt;: [&lt;span style=&#34;color:#40a070&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#40a070&#34;&gt;6&lt;/span&gt;, &lt;span style=&#34;color:#40a070&#34;&gt;7&lt;/span&gt;, &lt;span style=&#34;color:#40a070&#34;&gt;8&lt;/span&gt;, &lt;span style=&#34;color:#40a070&#34;&gt;9&lt;/span&gt;, &lt;span style=&#34;color:#40a070&#34;&gt;10&lt;/span&gt;], &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;p&amp;#39;&lt;/span&gt;: [&lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;]}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;5&lt;/span&gt;     model &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; KNeighborsClassifier()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;6&lt;/span&gt;     gs &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; GridSearchCV(model, paras, verbose&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;, cv&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;5&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;7&lt;/span&gt;     gs&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;fit(x_train, y_train)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;8&lt;/span&gt;     &lt;span style=&#34;color:#007020&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;最佳模型:&amp;#39;&lt;/span&gt;, gs&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;best_params_, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;准确率:&amp;#39;&lt;/span&gt;, gs&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;best_score_)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在上述代码中，第4行以字典的形式定义了超参数的取值情况，并且需要注意的是，字典的key必须是类&lt;code&gt;KNeighborsClassifier()&lt;/code&gt;中参数的名字，并且类&lt;code&gt;KNeighborsClassifier()&lt;/code&gt;就是sklearn中所实现的K近邻算法模型，因此，该类也包含了K近邻中最基本的两个参数K值和P值。第5行定义了一个K近邻模型，但值得注意的是此时并没有在定义模型的时候就传入相应的参数，即以&lt;code&gt;KNeighborsClassifier(n_neighbors=2, p=2)&lt;/code&gt;这样的形式（其中&lt;code&gt;n_neighbors&lt;/code&gt;代表K值）来实例化这个类。因为在使用网格搜索时，需要将模型作为一个参数传入&lt;code&gt;GridSearchCV()&lt;/code&gt;类中，同时也需要将模型对应的超参数以字典的形式传入。第6行是实例化&lt;code&gt;GridSearchCV()&lt;/code&gt;并同时传入了定义的K近邻模型及参数字典，其中&lt;code&gt;verbose&lt;/code&gt;用来控制训练过程中输出提示信息的详细程度，&lt;code&gt;cv=5&lt;/code&gt;表示在训练过程中使用5折交叉验证。最后，根据传入的训练集，便可以对模型进行训练。&lt;/p&gt;&#xA;&lt;p&gt;在模型训练完成后，便可以输出最佳模型（超参数组合），以及此时对应的模型得分，结果如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt; Fitting &lt;span style=&#34;color:#40a070&#34;&gt;5&lt;/span&gt; folds &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;for&lt;/span&gt; each of &lt;span style=&#34;color:#40a070&#34;&gt;12&lt;/span&gt; candidates, totalling &lt;span style=&#34;color:#40a070&#34;&gt;60&lt;/span&gt; fits&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt; [CV] END &lt;span style=&#34;color:#666&#34;&gt;...................&lt;/span&gt;n_neighbors&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;5&lt;/span&gt;, p&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;; total time&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;   &lt;span style=&#34;color:#40a070&#34;&gt;0.0&lt;/span&gt;s&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;3&lt;/span&gt; [CV] END &lt;span style=&#34;color:#666&#34;&gt;...................&lt;/span&gt;n_neighbors&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;5&lt;/span&gt;, p&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;; total time&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;   &lt;span style=&#34;color:#40a070&#34;&gt;0.0&lt;/span&gt;s&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;4&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.....&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;5&lt;/span&gt; 最佳模型: {&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;n_neighbors&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;p&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt; 准确率&lt;span style=&#34;&#34;&gt;：&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;0.971&lt;/span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;从最终的输出结果可知，当K取值为5且P取值为1时所对应的模型最佳。同时，由于模型在进行交叉验证时各个拟合过程之间是相互独立的，为了提高整个过程的训练速度，&lt;code&gt;GridSearchCV()&lt;/code&gt;还支持以并行的方式进行参数搜索。当在实例化类&lt;code&gt;GridSearchCV()&lt;/code&gt;时，只需同时指定&lt;code&gt;n_jobs=2&lt;/code&gt;即可实现参数的并行搜索，它表示同时使用CPU的两个核进行计算。当然也可以指定为&lt;code&gt;-1&lt;/code&gt;，即使用所有的核同时进行计算。&lt;/p&gt;</description>
			</item>
			<item>
				<title>5.4 kd树构建与搜索</title>
				<link>https://mlwithme.github.io/ml/chapter05/24378571ce404d9c/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://mlwithme.github.io/ml/chapter05/24378571ce404d9c/</guid>
				<description>&lt;h1 id=&#34;54-kd树构建与搜索&#34;&gt;5.4 kd树构建与搜索&lt;a class=&#34;anchor&#34; href=&#34;#54-kd%e6%a0%91%e6%9e%84%e5%bb%ba%e4%b8%8e%e6%90%9c%e7%b4%a2&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;在前面几节内容中，我们分别介绍了K近邻分类器的基本原理及其如何通过开源的sklearn框架实现K近邻的建模。不过到目前为止，还有一个问题没有解决，也就是如何快速地找到当前样本点周围最近的K个样本点。通常来讲，这一问题可以通过kd树来解决，下面开始介绍其具体原理。&lt;/p&gt;&#xA;&lt;h2 id=&#34;541-构造kd树&#34;&gt;5.4.1 构造kd树&lt;a class=&#34;anchor&#34; href=&#34;#541-%e6%9e%84%e9%80%a0kd%e6%a0%91&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;kd树（k-Dimensional Tree）是一种基于特征维度划分的数据结构，用于组织k维空间中的样本点，其本质上等同于二叉搜索树。不同的是，在kd树中每个节点存储的并不是一个值，而是一个k维的样本点[3]。在二叉搜索树中，任意节点中的值都大于其左子树中的所有值，小于或等于其右子树中的所有值，如图5-4所示。这里需要注意的一点是，K近邻中的K指的是样本个数，kd树中的k指的是样本特征维度，各位读者注意不要混淆。&lt;/p&gt;&#xA;&lt;div align=center&gt;&lt;img width=&#34;300&#34; src=&#34;https://mlwithme.github.io/images/ml/p5-3.png&#34;/&gt;&lt;/div&gt;&lt;center&gt;图 5-4 二叉搜索树&lt;/center&gt;&#xA;&lt;p&gt;在kd树中，所有节点也都满足类似二叉搜索树的特性，即左子树中所有的样本点均“小于”其父节点中的样本点，而右子树中所有的样本点均“大于”或“等于”其父节点中的样本点，因此可以看出，构造kd树的关键就在于如何定义任意两个k维样本点之间的大小关系。&lt;/p&gt;&#xA;&lt;p&gt;具体地，在构造kd树时，每一层的节点在划分左右子树时只会循环地选择k个维度中的一个维度进行比较。例如样本点中一共有$x$和$y$两个维度，那么在对根节点进行划分时可以以维度$x$对左右子树进行划分，接着以维度$y$对根节点的孩子进行左右子树划分，进一步，根节点的孩子的孩子则再以维度$x$进行左右子树划分，以此循环[4]。同时，为了使最后得到的是一棵平衡的kd树，所以在kd的构建过程中每次都会选择当前子树中对应维度的中位数作为切分点。&lt;/p&gt;&#xA;&lt;p&gt;例如现在有样本点[5,7]、[3,8]、[6,3]、[8,5]、[15,6]、[10,4]、[12,13]、[9,10]、[11,14]，那么根据上述规则便可以构造得到如图5-4所示的kd树。&lt;/p&gt;&#xA;&lt;div align=center&gt;&lt;img width=&#34;350&#34; src=&#34;https://mlwithme.github.io/images/ml/240429203450.jpg&#34;/&gt; &lt;/div&gt;&lt;center&gt;图 5-5 kd树示例&lt;/center&gt;&#xA;&lt;p&gt;从图5-5可以看出，对于根节点来讲其左子树中所有样本点的$x$维度均小于9，其右子树的$x$维度均大于或等于9。对于根节点的左孩子来讲其左子树中所有样本点的$y$维度均小于7，其右子树中所有样本点的$y$​维度均大于或等于7。当然，对于其他节点也满足类似的特性。同时，根据kd树交替选择特征维度对样本空间进行划分的特性，以图5-5中的划分方式还可以得到如图5-6所示的特征空间。&lt;/p&gt;&#xA;&lt;div align=center&gt;&lt;img width=&#34;280&#34; src=&#34;https://mlwithme.github.io/images/ml/240429205146.jpg&#34;/&gt; &lt;/div&gt;&lt;center&gt;图 5-6 kd树特征空间&lt;/center&gt;&#xA;&lt;p&gt;从图5-6中可以看出，整个二维平面首先被样本点[9,10]划分成左右两部分（对应图5-5中的左右子树），接着左右两部分又各自分别被[5,7]和[12,13]划分成上下两部分，直到划分结束。至此，对于kd树的构造就介绍完了，接下来讲解如何通过kd树来完成搜索任务。&lt;/p&gt;&#xA;&lt;h2 id=&#34;542-最近邻kd树搜索&#34;&gt;5.4.2 最近邻kd树搜索&lt;a class=&#34;anchor&#34; href=&#34;#542-%e6%9c%80%e8%bf%91%e9%82%bbkd%e6%a0%91%e6%90%9c%e7%b4%a2&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;在正式介绍K近邻（最近的K个样本点）搜索前，我们先来介绍如何利用kd树进行最近邻搜索，即找到距离当前样本点最近的样本点。总体来讲，在已知kd树中搜索离给定样本点$q$最近的样本点时，首先设定一个当前全局最佳点和一个当前全局最短距离，分别用来保存当前离$q$最近的样本点及对应的距离，然后从根节点开始以类似生成kd树的规则来递归地遍历kd树中的节点。如果当前节点离$q$的距离小于全局最短距离，则更新全局最佳点和全局最短距离； 如果被搜索点到当前节点划分维度的距离小于全局最短距离，则再递归遍历当前节点另外一个子树，直至整个递归过程结束。具体步骤可以总结为[4] ：&lt;/p&gt;&#xA;&lt;p&gt;(1) 设定一个当前全局最佳点和全局最短距离，分别用来保存当前离搜索点$q$最近的样本点和最短距离，初始值分别为空和无穷大。&lt;/p&gt;&#xA;&lt;p&gt;(2) 从根节点开始，并设其为当前节点。&lt;/p&gt;&#xA;&lt;p&gt;(3) 如果当前节点为空，则结束。&lt;/p&gt;&#xA;&lt;p&gt;(4) 如果当前节点到被搜索点$q$的距离小于当前全局最短距离，则更新全局最佳点和最短距离。&lt;/p&gt;&#xA;&lt;p&gt;(5) 如果被搜索点$q$的划分维度小于当前节点的划分维度，则设当前节点的左孩子为新的当前节点并执行步骤(3)(4)(5)(6)。反之设当前节点的右孩子为新的当前节点并执行步骤(3)(4)(5)(6)。&lt;/p&gt;&#xA;&lt;p&gt;(6) 如果被搜索点$q$的划分维度到当前节点划分维度的距离小于全局最短距离，则说明全局最佳点可能存在于当前节点的另外一个子树中，所以设当前节点的另外一个孩子为当前节点并执行步骤(3)(4)(5)(6)。&lt;/p&gt;&#xA;&lt;p&gt;递归完成后，此时的全局最佳点就是在kd树中离被搜索点$q$最近的样本点。&lt;/p&gt;&#xA;&lt;p&gt;这里需要明白的一点是，利用步骤(6)中的规则来判断另外一个子树中是否可能存在全局最佳点的原理如图5-7所示。&lt;/p&gt;&#xA;&lt;div align=center&gt;&lt;img width=&#34;280&#34; src=&#34;https://mlwithme.github.io/images/ml/p5-6.png&#34;/&gt;&lt;/div&gt;&lt;center&gt;图 5-7 子空间排除原理示意图&lt;/center&gt;&#xA;&lt;p&gt;在图5-7中，右上角为当前全局最佳点，五角星为被搜索点。可以看到，此时的整个空间被下方的样本点划分成了左右两部分（子树），并且五角星离左子树中样本点的最短距离为五角星到当前划分维度的距离$d$。显然，如果被搜索点到当前全局最佳点的距离$r$小于距离$d$，则此时左子树中就不可能存在更优的全局最佳点。在下面的搜索示例中我们将会详细进行介绍。&lt;/p&gt;&#xA;&lt;p&gt;当然，上述步骤还可以通过一个更清晰的伪代码进行表达，代码如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt; bestNode, bestDist &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;None&lt;/span&gt;, inf&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06287e&#34;&gt;NearestNodeSearch&lt;/span&gt;(curr_node):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;3&lt;/span&gt;     &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; curr_node &lt;span style=&#34;color:#666&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;4&lt;/span&gt;         &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;5&lt;/span&gt;     &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; distance(curr_node, q) &lt;span style=&#34;color:#666&#34;&gt;&amp;lt;&lt;/span&gt; bestDist:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;6&lt;/span&gt;         bestDist &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; distance(curr_node, q)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7&lt;/span&gt;         bestNode &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; curr_node&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8&lt;/span&gt;     &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; q_i &lt;span style=&#34;color:#666&#34;&gt;&amp;lt;&lt;/span&gt; curr_node_i:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;9&lt;/span&gt;         NearestNodeSearch(curr_node&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;left)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;10&lt;/span&gt;     &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;else&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;11&lt;/span&gt;         NearestNodeSearch(curr_node&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;right)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;12&lt;/span&gt;     &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;|&lt;/span&gt;curr_node_i &lt;span style=&#34;color:#666&#34;&gt;-&lt;/span&gt; q_i&lt;span style=&#34;color:#666&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;lt;&lt;/span&gt; bestDist:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;13&lt;/span&gt;         NearestNodeSearch(curr_node&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;other)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在上述代码中，&lt;code&gt;q_i&lt;/code&gt;和&lt;code&gt;curr_node_i&lt;/code&gt;分别表示被搜索点和当前节点的划分维度，&lt;code&gt;curr_node.other&lt;/code&gt;表示&lt;code&gt;curr_node.left&lt;/code&gt;和&lt;code&gt;curr_node.right&lt;/code&gt;中先前未被访问过的子树。&lt;/p&gt;</description>
			</item>
			<item>
				<title>5.5 从零实现K近邻</title>
				<link>https://mlwithme.github.io/ml/chapter05/40f253b916f24038/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://mlwithme.github.io/ml/chapter05/40f253b916f24038/</guid>
				<description>&lt;h1 id=&#34;55-从零实现k近邻&#34;&gt;5.5 从零实现K近邻&lt;a class=&#34;anchor&#34; href=&#34;#55-%e4%bb%8e%e9%9b%b6%e5%ae%9e%e7%8e%b0k%e8%bf%91%e9%82%bb&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;在前面几节内容中，我们已经详细地介绍了KNN的基本思想与原理，以及kd树的构建过程和搜索原理等。但是对于KNN和kd树具体的实现细节并没有做过多的介绍。下面我们就开始正式介绍如何从零实现kd树以及完成整个KNN的代码实现。以下完整示例代码可参见&lt;code&gt;AllBooKCode/Chapter05/C05_knn_imp_from_scratch.py&lt;/code&gt; 文件。&lt;/p&gt;&#xA;&lt;h2 id=&#34;551-kd树节点定义&#34;&gt;5.5.1 kd树节点定义&lt;a class=&#34;anchor&#34; href=&#34;#551-kd%e6%a0%91%e8%8a%82%e7%82%b9%e5%ae%9a%e4%b9%89&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;根据第5.4.1节内容介绍，kd树本质上也就等同于二叉搜索树，因此，首先我们需要定义kd树中的节点信息，以及kd树的构建与查询等。同时，由于在KNN的预测结果中需要根据训练样本给出每个预测样本的标签值，因此就需要知道每个训练样本的原始标签值，故需要在节点中保存每个样本索引。最终，kd树的节点信息定义如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;Node&lt;/span&gt;(&lt;span style=&#34;color:#007020&#34;&gt;object&lt;/span&gt;):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;     &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06287e&#34;&gt;__init__&lt;/span&gt;(&lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;, data&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;None&lt;/span&gt;, index&lt;span style=&#34;color:#666&#34;&gt;=-&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;3&lt;/span&gt;         &lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;data &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; data&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;4&lt;/span&gt;         &lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;left_child &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;5&lt;/span&gt;         &lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;right_child &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;6&lt;/span&gt;         &lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;index &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; index&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;7&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;8&lt;/span&gt;     &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06287e&#34;&gt;__str__&lt;/span&gt;(&lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;9&lt;/span&gt;         &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;data(&lt;/span&gt;&lt;span style=&#34;color:#70a0d0&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;data&lt;span style=&#34;color:#70a0d0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;),index(&lt;/span&gt;&lt;span style=&#34;color:#70a0d0&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;int&lt;/span&gt;(&lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;index)&lt;span style=&#34;color:#70a0d0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;)&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在上述代码中，第2~6行定义了节点&lt;code&gt;Node&lt;/code&gt;中保存的具体信息，包括样本点、左右子树以及在原始样本中的索引；第8~9行定义了&lt;code&gt;__str__()&lt;/code&gt;方法，其作用是在使用&lt;code&gt;print()&lt;/code&gt;函数时可以直接打印出节点的信息，而不必用&lt;code&gt;node.data&lt;/code&gt;这样的形式来访问节点中的样本。&lt;/p&gt;&#xA;&lt;h2 id=&#34;552-kd树构建&#34;&gt;5.5.2 kd树构建&lt;a class=&#34;anchor&#34; href=&#34;#552-kd%e6%a0%91%e6%9e%84%e5%bb%ba&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;在完成kd树节点的定义之后，下一步就可以开始定义构建kd树的整个过程。首先，我们需要定义类的初始化函数，示例代码如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;MyKDTree&lt;/span&gt;(&lt;span style=&#34;color:#007020&#34;&gt;object&lt;/span&gt;):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;     &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06287e&#34;&gt;__init__&lt;/span&gt;(&lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;, points):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;3&lt;/span&gt;         &lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;root &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;4&lt;/span&gt;         &lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;dim &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; points&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;shape[&lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;5&lt;/span&gt;         points &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;hstack(([points, np&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;arange(&lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#007020&#34;&gt;len&lt;/span&gt;(points))&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;reshape(&lt;span style=&#34;color:#666&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;)]))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;6&lt;/span&gt;         &lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;insert(points, order&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt;)  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# 递归构建KD树&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;7&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;8&lt;/span&gt;     &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06287e&#34;&gt;is_empty&lt;/span&gt;(&lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;9&lt;/span&gt;         &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;root&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在上述代码中，第3行定义了kd树的根节点。第4行定义了原始样本的维度。第5行用于在样本点的最后一列附加上每个样本点的索引值。第6行则是调用&lt;code&gt;insert()&lt;/code&gt;方法递归完成kd树的构建；第8~9行定义了一个方法来判断当前kd是否为空。&lt;/p&gt;&#xA;&lt;p&gt;接下来便是完成&lt;code&gt;insert()&lt;/code&gt;方法的实现过程，示例代码如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;     &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06287e&#34;&gt;insert&lt;/span&gt;(&lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;, data, order&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt;):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;         &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;len&lt;/span&gt;(data) &lt;span style=&#34;color:#666&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;3&lt;/span&gt;             &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;4&lt;/span&gt;         data &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;sorted&lt;/span&gt;(data, key&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;lambda&lt;/span&gt; x: x[order &lt;span style=&#34;color:#666&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;dim])  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# 按某个维度进行排序&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;5&lt;/span&gt;         idx &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;len&lt;/span&gt;(data) &lt;span style=&#34;color:#666&#34;&gt;//&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;6&lt;/span&gt;         node &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; Node(data[idx][:&lt;span style=&#34;color:#666&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;], data[idx][&lt;span style=&#34;color:#666&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;])&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7&lt;/span&gt;         left_data &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; data[:idx]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8&lt;/span&gt;         right_data &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; data[idx &lt;span style=&#34;color:#666&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;:]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;9&lt;/span&gt;         &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;is_empty():&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;10&lt;/span&gt;             &lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;root &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; node  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# 整个kd树的根节点&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;11&lt;/span&gt;         node&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;left_child &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;insert(left_data, order &lt;span style=&#34;color:#666&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;)  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# 递归构建左子树&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;12&lt;/span&gt;         node&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;right_child &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;insert(right_data, order &lt;span style=&#34;color:#666&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;)  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# 递归构建右子树&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;13&lt;/span&gt;         &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; node&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在上述代码中，第2~3行用来判断当前传入的样本点是否为空，如果为空则结束当前递归。第4行用于将当前样本按照某个维度的大小顺序进行排序，其中样本点维度的比较顺序为从左到右依次轮询，&lt;code&gt;order&lt;/code&gt;在每次进行递归时都会累加；第5~6行用来获取并保存当前样本点排序后中间位置的样本并保存到一个新初始化的节点中；第7~8行则是分别取当前排序后样本的左边部分和右边部分，以此来分别作为当前节点的左右子树。第11~12行则是分别递归构建左右子树。&lt;/p&gt;</description>
			</item>
			<item>
				<title>引用</title>
				<link>https://mlwithme.github.io/ml/chapter05/76818d9e48944f8c/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://mlwithme.github.io/ml/chapter05/76818d9e48944f8c/</guid>
				<description>&lt;h1 id=&#34;引用&#34;&gt;引用&lt;a class=&#34;anchor&#34; href=&#34;#%e5%bc%95%e7%94%a8&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;[1] 李航.统计学习方法［M］.2版.北京： 清华大学出版社,2019.&lt;/p&gt;&#xA;&lt;p&gt;[2] PEDREGOSA.scikitlearn: Machine Learning in Python［J］.JMLR 12,2011: 28252830.&lt;/p&gt;&#xA;&lt;p&gt;[3] &lt;a href=&#34;https://en.wikipedia.org/wiki/Kd_tree&#34;&gt;https://en.wikipedia.org/wiki/Kd_tree&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;[4] &lt;a href=&#34;http://web.stanford.edu/class/cs106l/&#34;&gt;http://web.stanford.edu/class/cs106l/&lt;/a&gt;&lt;/p&gt;</description>
			</item>
	</channel>
</rss>
