彩笔运维勇闯机器学习--KNN算法
<h2 id="前言">前言</h2><p>彩笔运维勇闯机器学习:KNN算法,它也是分类中的一种</p>
<h2 id="开始探索">开始探索</h2>
<h4 id="scikit-learn">scikit-learn</h4>
<pre><code>import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report, confusion_matrix
np.random.seed(0)
x0 = np.random.randn(60, 2) * 0.6 + np.array()
x1 = np.random.randn(30, 2) * 0.6 + np.array()
x2 = np.random.randn(10, 2) * 0.6 + np.array()
X = np.vstack((x0, x1, x2))
y = np.array(*60 + *30 + *10)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
scaler = StandardScaler()
X_train_std = scaler.fit_transform(X_train)
X_test_std = scaler.transform(X_test)
k = 5
knn = KNeighborsClassifier(n_neighbors=k)
knn.fit(X_train_std, y_train)
y_pred = knn.predict(X_test_std)
def plot_knn_decision(X, y, model):
h = 0.02
x_min, x_max = X[:, 0].min()-1, X[:, 0].max()+1
y_min, y_max = X[:, 1].min()-1, X[:, 1].max()+1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
Z = model.predict(np.c_)
Z = Z.reshape(xx.shape)
plt.figure(figsize=(8,6))
plt.contourf(xx, yy, Z, cmap=plt.cm.Pastel2)
plt.scatter(X[:, 0], X[:, 1], c=y, edgecolors='k', cmap=plt.cm.Set1)
plt.grid(True)
plt.show()
plot_knn_decision(X_train_std, y_train, knn)
</code></pre>
<p>这是一个三分类的数据,分类<code>0</code>有60个,分类<code>1</code>有30个,分类<code>2</code>有10个</p>
<p>脚本!启动:</p>
<p><img alt="watermarked-knn_1_1" loading="lazy" src="https://img2024.cnblogs.com/blog/1416773/202510/1416773-20251013103923165-1077143280.png" class="lazyload"></p>
<h2 id="深入理解knn">深入理解KNN</h2>
<p>KNN算法属于惰性学习,没有所谓的数据训练的过程。它把训练数据暂时保存,当有新的数据需要进行分类时,再使用训练数据进行对应的计算,而这个计算算法常见的是欧氏距离</p>
<p></p><div class="math display">\[d(A, B) = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2}
\]</div><p></p><p>下面用一个例子来加深一下算法的过程</p>
<h4 id="举例说明">举例说明</h4>
<p>假设有以下训练数据</p>
<table>
<thead>
<tr>
<th></th>
<th>x1</th>
<th>x2</th>
<th>类别</th>
</tr>
</thead>
<tbody>
<tr>
<td>A</td>
<td>1</td>
<td>2</td>
<td>0</td>
</tr>
<tr>
<td>B</td>
<td>2</td>
<td>3</td>
<td>0</td>
</tr>
<tr>
<td>C</td>
<td>3</td>
<td>3</td>
<td>1</td>
</tr>
<tr>
<td>D</td>
<td>6</td>
<td>5</td>
<td>1</td>
</tr>
<tr>
<td>E</td>
<td>7</td>
<td>2</td>
<td>1</td>
</tr>
</tbody>
</table>
<p>1)由于是惰性学习,训练数据就先放着备用</p>
<p>2)假设有一个测试数据,T(3,4),需要对他进行分类</p>
<p>对每一个点分别计算:</p>
<ul>
<li>A点,(1,2),<span class="math inline">\(D_A=\sqrt{(3-1)^2+(4-2)^2} \approx 2.83\)</span></li>
<li>B点,(2,3),<span class="math inline">\(D_A=\sqrt{(3-2)^2+(4-3)^2} \approx 1.41\)</span></li>
<li>C点,(3,3),<span class="math inline">\(D_A=\sqrt{(3-3)^2+(4-3)^2} = 1\)</span></li>
<li>D点,(6,5),<span class="math inline">\(D_A=\sqrt{(3-6)^2+(4-5)^2} \approx 3.16\)</span></li>
<li>E点,(7,8),<span class="math inline">\(D_A=\sqrt{(3-7)^2+(4-8)^2} \approx 5.66\)</span></li>
</ul>
<p>3)设置超参数K=3,选择3个距离最小作为邻居</p>
<table>
<thead>
<tr>
<th>邻居</th>
<th>距离</th>
<th>类别</th>
</tr>
</thead>
<tbody>
<tr>
<td>C</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>B</td>
<td>1.41</td>
<td>0</td>
</tr>
<tr>
<td>A</td>
<td>2.83</td>
<td>0</td>
</tr>
</tbody>
</table>
<p>4)投票,少数服从多数,T(3,4)的类别是0</p>
<h4 id="小结">小结</h4>
<p>KNN算法的优点是简单直接,非常容易理解。缺点也很明显,由于是惰性计算,面对高维的、数据量非常大的数据,往往需要大量的计算才能进行分类,并且对于每一个测试数据都需要“遍历所有训练数据”来计算距离,这在大规模 数据集上会变得非常慢。</p>
<h2 id="异常检测">异常检测</h2>
<p>在之前讨论分类问题的时候,遇到了所谓的“类别不平衡”问题,就是多数类占据样本的大量,而少数类只占用非常少的样本,导致分类算法对于少数类不能正确分类,需要做额外的处理</p>
<p>在实际工作中,“类别不平衡”问题有着非常重要的实践,比如有100w的日志,怎么精准识别出10条异常日志,除了10条日常,其余999990条日志都属于正常日志。对于这种问题又叫做“异常检测”,对于“异常检测”问题,有一些算法是比较擅长处理的,比如KNN算法</p>
<h4 id="举例说明-1">举例说明</h4>
<p>在下列数据中,找出异常点</p>
<table>
<thead>
<tr>
<th></th>
<th>x1</th>
<th>x2</th>
</tr>
</thead>
<tbody>
<tr>
<td>A</td>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>B</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>C</td>
<td>3</td>
<td>3</td>
</tr>
<tr>
<td>D</td>
<td>6</td>
<td>5</td>
</tr>
<tr>
<td>E</td>
<td>7</td>
<td>2</td>
</tr>
</tbody>
</table>
<p>1)算法没变,还是使用欧式距离公式</p>
<table>
<thead>
<tr>
<th></th>
<th>A(1,2)</th>
<th>B(2,3)</th>
<th>C(3,3)</th>
<th>D(6,5)</th>
<th>E(7,8)</th>
</tr>
</thead>
<tbody>
<tr>
<td>A(1,2)</td>
<td>-</td>
<td>1.41</td>
<td>2.83</td>
<td>5.83</td>
<td>8.49</td>
</tr>
<tr>
<td>B(2,3)</td>
<td>1.41</td>
<td>-</td>
<td>1</td>
<td>4.24</td>
<td>6.71</td>
</tr>
<tr>
<td>C(3,3)</td>
<td>2.83</td>
<td>1</td>
<td>-</td>
<td>3.61</td>
<td>5.83</td>
</tr>
<tr>
<td>D(6,5)</td>
<td>5.83</td>
<td>4.24</td>
<td>3.61</td>
<td>-</td>
<td>3.16</td>
</tr>
<tr>
<td>E(7,8)</td>
<td>8.49</td>
<td>6.71</td>
<td>5.83</td>
<td>3.16</td>
<td>-</td>
</tr>
</tbody>
</table>
<p>2)设置超参数K=2,找到最近的2个邻居计算平均距离</p>
<ul>
<li>A最近的邻居:(1.41 2.83),<span class="math inline">\(D_A=2.12\)</span></li>
<li>B最近的邻居:(1 1.41),<span class="math inline">\(D_B=1.21\)</span></li>
<li>C最近的邻居:(1 2.83),<span class="math inline">\(D_C=1.91\)</span></li>
<li>D最近的邻居:(3.16 3.61),<span class="math inline">\(D_D=3.39\)</span></li>
<li>E最近的邻居:(3.16 5.83),<span class="math inline">\(D_E=4.5\)</span></li>
</ul>
<p>3)找出异常点</p>
<ul>
<li>如果要找出最异常的,那就是E点</li>
<li>如果要找出2个的异常点,那就是D与E</li>
</ul>
<h4 id="scikit-learn-1">scikit-learn</h4>
<pre><code>import numpy as np
from sklearn.neighbors import NearestNeighbors
np.random.seed(42)
X_normal = np.random.randn(100, 2)
X_outliers = np.array([, [-5, -5], ])
X = np.vstack((X_normal, X_outliers))
k = 3
nbrs = NearestNeighbors(n_neighbors=k)
nbrs.fit(X)
distances, _ = nbrs.kneighbors(X)
k_dist = distances[:, -1]
n_outliers = 3
threshold = np.partition(k_dist, -n_outliers)[-n_outliers]
outlier_mask = k_dist >= threshold
outliers = X
print("异常点坐标:")
print(outliers)
</code></pre>
<p>脚本!启动:</p>
<p><img alt="watermarked-knn_1_2" loading="lazy" src="https://img2024.cnblogs.com/blog/1416773/202510/1416773-20251013103938672-1662717543.png" class="lazyload"></p>
<p>画图分析</p>
<pre><code>import matplotlib.pyplot as plt
plt.figure(figsize=(8,6))
plt.scatter(X[:, 0], X[:, 1], c='blue')
plt.scatter(outliers[:, 0], outliers[:, 1], c='red', edgecolors='black', s=100)
plt.legend()
plt.grid(True)
plt.show()
</code></pre>
<p><img alt="watermarked-knn_1_3" loading="lazy" src="https://img2024.cnblogs.com/blog/1416773/202510/1416773-20251013103948204-453833562.png" class="lazyload"></p>
<h4 id="knn增强版本lof">KNN增强版本LOF</h4>
<p>局部离群因子(LOF)算法,专门用于异常检测</p>
<pre><code>import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import LocalOutlierFactor
X = np.array([
,
,
,
,
,
,
])
k = 2
lof = LocalOutlierFactor(n_neighbors=k, contamination=0.3)
y_pred = lof.fit_predict(X)
anomaly_scores = lof.negative_outlier_factor_
for i, (point, label, score) in enumerate(zip(X, y_pred, anomaly_scores)):
status = "异常" if label == -1 else "正常"
print(f"点 {i}: 坐标={point}, 状态={status}, LOF分数={score:.3f}")
</code></pre>
<ul>
<li><code>n_neighbors=2</code>,就是超参数k,用来选择邻居数</li>
<li><code>contamination=0.3</code>,表示有30%的数据为异常</li>
</ul>
<p>脚本!启动:</p>
<p><img alt="watermarked-knn_1_4" loading="lazy" src="https://img2024.cnblogs.com/blog/1416773/202510/1416773-20251013103957956-1291262519.png" class="lazyload"></p>
<h4 id="两种算法的对比">两种算法的对比</h4>
<table>
<thead>
<tr>
<th></th>
<th>KNN</th>
<th>LOF</th>
</tr>
</thead>
<tbody>
<tr>
<td>功能</td>
<td>查找最近邻居</td>
<td>检测局部异常</td>
</tr>
<tr>
<td>输出</td>
<td>每个点最近的 k 个邻居及其距离</td>
<td>每个点的异常标签(1 或 -1)和 LOF 分数</td>
</tr>
<tr>
<td>适用任务</td>
<td>查找最近的用户/商品/样本</td>
<td>检测数据中的异常点</td>
</tr>
<tr>
<td>是否计算异常</td>
<td>否</td>
<td>是(negative_outlier_factor_)</td>
</tr>
<tr>
<td>参数</td>
<td>n_neighbors 只是最近邻个数</td>
<td>n_neighbors, contamination 控制邻居数和异常比例</td>
</tr>
</tbody>
</table>
<h2 id="小结-1">小结</h2>
<ul>
<li>联系我,做深入的交流<br>
<img alt="" width="500" height="200" loading="lazy" src="https://img2024.cnblogs.com/blog/1416773/202411/1416773-20241121135740959-1907948957.png#" class="lazyload"></li>
</ul>
<hr>
<p>至此,本文结束<br>
在下才疏学浅,有撒汤漏水的,请各位不吝赐教...</p>
</div>
<div id="MySignature" role="contentinfo">
<p>本文来自博客园,作者:it排球君,转载请注明原文链接:https://www.cnblogs.com/MrVolleyball/p/19137972</p>
<div>本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。 </div><br><br>
来源:https://www.cnblogs.com/MrVolleyball/p/19137972
頁:
[1]