KNN分类算法实践

KNN分类算法

KNN是机器学习种最简单的分类算法,而图像分类也是图像识别种最简单的问题,所以这里使用KNN来做图像分类,帮忙大家初步了解图像识别算法。

KNN(K-NearestNeighbor),即K-最近邻算法,顾名思义,找到最近的k个邻居,在前k个最近样本(k近邻)中选择最近的占比最高的类别作为预测类别。

KNN算法的计算逻辑:

  1. 给定测试对象,计算它与训练集中每个对象的距离;
  2. 圈定距离最近的k个训练对象,作为测试对象的邻居;
  3. 根据这k个近邻对象所属的类别,找到占比最高的那个类别作为测试对象的预测类别。

KNN中,有两个方面的因素会影响KNN算法的准确度:一个是距离计算,另一个是k的选择。

一般使用两种比较常见的距离公式计算距离:

  1. 曼哈顿距离:在这里插入图片描述

  2. 欧式距离:
    在这里插入图片描述

KNN算法实现

import numpy as np
import matplotlib.pyplot as plt


class KNNClassify:

    @staticmethod
    def create_data_set():
        _group = np.array([[1.0, 2.0], [1.2, 0.1], [0.1, 1.4], [0.3, 3.5], [1.1, 1.0], [0.5, 1.5]])
        _labels = np.array(['A', 'A', 'B', 'B', 'A', 'B'])
        return _group, _labels
    

if __name__ == "__main__":
    group, labels = KNNClassify.create_data_set()
    plt.scatter(group[labels == 'A', 0], group[labels == 'A', 1], color='r', marker='*')
    plt.scatter(group[labels == 'B', 0], group[labels == 'B', 1], color='g', marker='+')
    plt.show()

我们先创建一个简单的数据集,然后使用Matplotlib绘制图形,可以直观看到地查看数据分布情况。

在这里插入图片描述

接下来实现KNN算法

import operator


class KNNClassify:
    
    # KNN 分类
    @staticmethod
    def knn_classify(k, dis, x_train, y_train, x_test):
        assert dis == 'E' or dis == 'M', 'dis must E or M, E代表欧式距离, M代表曼哈顿距离'
        num_test = x_test.shape[0]
        label_list = []

        for i in range(num_test):
            # 将测试数据复制为多份,方便直接利用矩阵进行快速计算
            x_test_temp = np.tile(x_test[i], (x_train.shape[0], 1))
            # 矩阵减法 相同位置分别相减
            if dis == 'E':   # 使用欧式距离公式作为距离度量
                result_temp = (x_train - x_test_temp) ** 2
                # axis=1 计算的是行的和,结果以列展示
                result_sum_temp = result_temp.sum(axis=1)
                # 最后再求平方根
                distances = np.sqrt(result_sum_temp)
            else:  # 使用曼哈顿公式作为距离度量
                result_temp = np.abs(x_train - x_test_temp)
                # axis=1 计算的是行的和,结果以列展示
                distances = result_temp.sum(axis=1)
            # 距离由小到大进行排序,并返回index值
            nearest_k = distances.argsort()
            top_k = nearest_k[:k]
            class_count = {}
            for index in top_k:
                class_count[y_train[index]] = class_count.get(y_train[index], 0) + 1
            sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
            label_list.append(sorted_class_count[0][0])
        return np.array(label_list)

最后测试下KNN算法的效果:

# 使用曼哈顿距离和欧式距离得到的预测结果不一样 E: ['A', 'B']  M: ['A', 'A']
y_test_pred = KNNClassify.knn_classify(1, 'E', group, labels, np.array([[1.0, 2.1], [0.4, 2.0]]))
print(y_test_pred)

注意,输入测试集的时候,需要将其转换为Numpy矩阵,否则系统会提示传入的参数是list类型,没有shape的方法。

KNN实现MNIST数据分类

下载和准备数据集

MNIST数据集是手写数字的图片数据集,MNIST可以直接通过pytorch进行下载与读取(也可以自行下载,然后放到相关目录,使用pytorch解析)

import torch
import numpy as np
from torch.utils.data import DataLoader
from torchvision import datasets


class MNIST:

    def __init__(self):
        self._batch_size = 100
        self._train_data_set = None
        self._test_data_set = None
        self._train_loader = None
        self._test_loader = None

    def init_data(self):
        # MNIST data_set
        self._train_data_set = datasets.MNIST(
            root='/ml_dataset/pymnist',  # 选择数据保存路径
            train=True,          # 选择训练集
            transform=None,      # 不考虑使用任何数据预处理
            download=True        # 从网络上下载图片
        )

        self._test_data_set = datasets.MNIST(
            root='/ml_dataset/pymnist',
            train=False,           # 选择测试集
            transform=None,
            download=True
        )

        # 加载数据
        self._train_loader = torch.utils.data.DataLoader(
            dataset=self._train_data_set,
            batch_size=self._batch_size,
            shuffle=True      # 将数据打乱
        )

        self._test_loader = torch.utils.data.DataLoader(
            dataset=self._test_data_set,
            batch_size=self._batch_size,
            shuffle=True
        )

        print("train_data:", self._train_data_set.data.size())
        print("train_labels:", self._train_data_set.targets.size())
        print("test_data:", self._test_data_set.data.size())
        print("test_labels:", self._test_data_set.targets.size())

if __name__ == "__main__":
    mnist = MNIST()
    mnist.init_data()

首次运行代码会自动下载数据到目录/ml_dataset/pymnist,如果运行时速度过慢,可以上网查询MNIST下载方式,将下载好的数据包,放置该目录,程序会自动解析。

train_dataset与test_dataset可以返回训练集数据、训练集标签、测试集数据以及测试集标签,训练集数据以及测试集数据都是n×m维的矩阵,这里的n是样本数(行数),m是特征数(列数)。训练数据集包含60 000个样本,测试数据集包含10 000个样本。

MNIST数据集中,每张图片均由28×28个像素点构成,每个像素点使用一个灰度值表示。在这里,我们将28×28的像素展开为一个一维的行向量,这些行向量就是图片数组里的行(每行784个值,或者说每行就代表了一张图片)。训练集标签以及测试标签包含了相应的目标变量,也就是手写数字的类标签(整数0~9)。

上面代码打印的结果为:

train_data: torch.Size([60000, 28, 28])
train_labels: torch.Size([60000])
test_data: torch.Size([10000, 28, 28])
test_labels: torch.Size([10000])

如果大家对这个数据集不是很了解,可以尝试多去显示几张图片看下,并查看相应的标签。

例如:

# 查看mnist数据集的图像是啥样的
def show_data_example(self):
    digit = self._train_loader.dataset.data[0]
    plt.imshow(digit, cmap='gray')
    plt.show()
    print(self._train_loader.dataset.targets[0])

数字5:

在这里插入图片描述

原理剖析

在真正使用Python实现KNN算法之前,我们先来剖析一下思想,这里我们以MNIST的60 000张图片作为训练集,我们希望对测试数据集的10 000张图片全部打上标签。KNN算法将会比较测试图片与训练集中每一张图片,然后将它认为最相似的那个训练集图片的标签赋给这张测试图片。

那么,具体应该如何比较这两张图片呢?在本例中,比较图片就是比较28×28的像素块。最简单的方法就是逐个像素进行比较,最后将差异值全部加起来

在这里插入图片描述

两张图片使用L1距离(曼哈顿距离,相应的L2距离是欧式距离)来进行比较。逐个像素求差值,然后将所有差值加起来得到一个数值。如果两张图片一模一样,那么L1距离为0,但是如果两张图片差别很大,那么,L1的值将会非常大。

验证KNNMNIST上的效果

我们直接利用上面实现的KNN算法来测试:

from KNN import KNNClassify

class MNIST:
    
    def predict(self):
        # 需要转为numpy矩阵
        x_train = self._train_loader.dataset.data.numpy()
        # 需要reshape之后才能放入knn分类器
        x_train = x_train.reshape(x_train.shape[0], 28*28)
        y_train = self._train_loader.dataset.targets.numpy()
        x_test = self._test_loader.dataset.data[:1000].numpy()
        x_test = x_test.reshape(x_test.shape[0], 28*28)
        y_test = self._test_loader.dataset.targets[:1000].numpy()

        num_test = y_test.shape[0]
        y_test_pred = KNNClassify.knn_classify(5, 'M', x_train, y_train, x_test)
        correct_arrays = np.array(y_test == y_test_pred)
        num_correct = correct_arrays.sum()
        accuracy = float(num_correct) / num_test
        print(f'Got {num_correct}/{num_test} => accuracy: {accuracy}')

这里需要多说点,上面的KNN算法实现,我是当作一个单独的脚本,这里的MNIST数据分类,是另一个脚本,且位于同一文件夹下:

在这里插入图片描述

直接在MNIST.py脚本中使用from KNN import KNNClassify可能会出错,需要在__init__.py中添加一行代码:from .KNN import KNNClassify

最后运行代码,Got 368 / 1000 correct => accuracy: 0.368000!这说明1000张图片中有368张图片预测类别的结果是准确的。

先别气馁,我们之前不是刚说过可以使用数据预处理的技术吗?下面我们试一下如果在进行数据加载的时候尝试使用归一化,那么分类准确度是否会提高呢?

我们稍微修改下代码,主要是在将X_train和X_test放入KNN分类器之前先调用centralized,进行归一化处理。

class MNIST:
    
    @staticmethod
    def get_x_mean(x_train):
        # 我只关心我需要转换成x_train.shape[0]行,列数自己计算(转换成60000*784)
        x_train = x_train.reshape(x_train.shape[0], -1)  # Turn the image to 1-D
        # 每行是一张图片 求每一列均值。即求所有图片每一个像素上的平均值
        mean_image = x_train.mean(axis=0)
        return mean_image

    @staticmethod
    def centralized(x_test, mean_image):
        x_test = x_test.reshape(x_test.shape[0], -1)
        x_test = x_test.astype(np.float64)
        x_test -= mean_image  # Subtract the mean from the graph, and you get zero mean graph
        return x_test

    # 归一化后预测
    def predict_centralized(self):
        x_train = self._train_loader.dataset.data.numpy()
        mean_image = self.get_x_mean(x_train)
        x_train = self.centralized(x_train, mean_image=mean_image)
        y_train = self._train_loader.dataset.targets.numpy()
        x_test = self._test_loader.dataset.data[:1000].numpy()
        x_test = self.centralized(x_test, mean_image)
        y_test = self._test_loader.dataset.targets[:1000].numpy()
        num_test = y_test.shape[0]
        y_test_pred = KNNClassify.knn_classify(5, 'M', x_train, y_train, x_test)
        correct_arrays = np.array(y_test == y_test_pred)
        num_correct = correct_arrays.sum()
        accuracy = float(num_correct) / num_test
        print(f'Got {num_correct}/{num_test} => accuracy: {accuracy}')

下面再来看下输出结果的准确率:Got 951 / 1000 correct => accuracy: 0.951000,95%算是不错的结果。

现在我们来看一看归一化后的图像是什么样子的

class MNIST:
    
    def show_data_centralized(self):
        x_train = self._train_loader.dataset.data.numpy()
        mean_image = self.get_x_mean(x_train)
        x_test = self._test_loader.dataset.data[:1000].numpy()
        c_data = self.centralized(x_test, mean_image)
        c_data = c_data.reshape(c_data.shape[0], 28, 28)
        plt.imshow(c_data[0], cmap='gray')
        plt.show()
        print(self._test_loader.dataset.targets[0])  # 输出的标签为7
        

if __name__ == "__main__":
    mnist = MNIST()
    mnist.init_data()
    mnist.show_data_example()
    # 无任何数据预处理,M: 36.8% E: 27%
    # mnist.predict()

    # 归一化后,准确率大大提升 M:95.1% E: 96.3%
    # mnist.predict_centralized()
    # 查看归一化后的图像与原图像有啥区别
    mnist.show_data_centralized()

在这里插入图片描述

所以是否了解到数据预处理的重要性了?!未进行数据预处理的准确率只有36.8%,进行归一化数据预处理后,准确率提升至95%!

在开始使用算法进行图像识别之前,良好的数据预处理能够很快达到事半功倍的效果。图像预处理不仅可以使得原始图像符合某种既定规则以便于进行后续的处理,而且可以帮助去除图像中的噪声。

在后续讲解神经网络的时候我们还会了解到,数据预处理还可以帮助减少后续的运算量以及加速收敛。常用的图像预处理操作包括归一化、灰度变换、滤波变换以及各种形态学变换等。

归一化可用于保证所有维度上的数据都在一个变化幅度上。比如,在预测房价的例子中,假设房价由面积s和卧室数b决定,面积s在0~200之间,卧室数b在0~5之间,进行归一化的一个实例就是s=s/200,b=b/5。

KNN实现Cifar10数据分类

Cifar10是一个由彩色图像组成的分类的数据集(MNIST是黑白数据集),其中包含了飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船、卡车10个类别,且每个类中包含了1000张图片。整个数据集中包含了60 000张32×32的彩***片。该数据集被分成50 000和10 000两部分,50 000是training set,用来做训练;10 000是test set,用来做验证。

下载和准备数据集

同样的你可以通过Pytorch直接下载该数据集,或者自己从数据集官网下载,然后放置指定目录,程序会自动解析

import torch
import numpy as np
from torch.utils.data import DataLoader
import torchvision.datasets as datasets
import matplotlib.pyplot as plt

from KNN import KNNClassify


class Cifar10:

    def __init__(self):

        self._batch_size = 100
        self._train_data_set = None
        self._test_data_set = None
        self._train_loader = None
        self._test_loader = None

    def init_data(self):
        self._train_data_set = datasets.CIFAR10(
            root='/ml_dataset/pycifar',
            train=True,
            download=True
        )
        self._test_data_set = datasets.CIFAR10(
            root='/ml_dataset/pycifar',
            train=False,
            download=True
        )

        self._train_loader = torch.utils.data.DataLoader(
            dataset=self._train_data_set,
            batch_size=self._batch_size,
            shuffle=True
        )
        self._test_loader = torch.utils.data.DataLoader(
            dataset=self._test_data_set,
            batch_size=self._batch_size,
            shuffle=True
        )


如果对数据集不了解,可以查看数据集的图片内容

class Cifar10
    
    def show_data_example(self):
        classes = ['plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
        digit = self._train_loader.dataset.data[0]
        plt.imshow(digit)
        plt.show()
        print(classes[self._train_loader.dataset.targets[0]])  # 打印出是frog

在这里插入图片描述

classes是我们定义的类别,其对应的是Cifar中的10个类别。使用PyTorch读取的类别是index,所以我们还需要额外定义一个classes来指向具体的类别。由于只有32×32个像素,因此图像比较模糊。

验证KNNCifar10上的效果

现在我们主要观察下KNN对于Cifar10数据集的分类效果,与之前MNIST数据集不同的是,X_train = train_loader.dataset.train_dataX_traindtypeuint8而不是torch.uint8,所以不需要使用numpy()这个方法进行转换。

class Cifar10:
    
    def predict(self):
        # 这个数据集无需使用numpy()转换
        x_train = self._train_loader.dataset.data
        mean_image = self.get_x_mean(x_train)
        x_train = self.centralized(x_train, mean_image=mean_image)
        y_train = self._train_loader.dataset.targets
        x_test = self._test_loader.dataset.data[:100]
        x_test = self.centralized(x_test, mean_image)
        y_test = self._test_loader.dataset.targets[:100]
        num_test = len(y_test)
        # 这里设置了k=6,可以更换其他值试一下准确率是否有改变
        # k=6 acc:0.33  k=8 acc:0.3
        y_test_pred = KNNClassify.knn_classify(8, 'E', x_train, y_train, x_test)
        correct_arrays = np.array(y_test == y_test_pred)
        num_correct = correct_arrays.sum()
        accuracy = float(num_correct) / num_test
        print(f'Got {num_correct}/{num_test} => accuracy: {accuracy}')

经过验证,KNN算法在Cifar10数据集上的准确率不高,大概只有30%的准确率,而且是在归一化处理的基础上。

总结

前面我们讲了影响KNN算法的两大因素分别为距离度量算法和K的取值,也就是算法的两个超参数,到底如何选取这两个值,就是一个模型调参的问题,这个过程一般就是需要你自己去测试,选取一个效果比较好的取值。

虽然KNNMNIST数据集中的表现还算可以(主要原因可能是MNIST是灰度图),但是其在Cifar10数据集上的分类准确度就差强人意了。另外,虽然KNN算法的训练不需要花费时间(训练过程只是将训练集数据存储起来),但由于每个测试图像需要与所存储的全部训练图像进行比较,因此测试需要花费大量时间,这显然是一个很大的缺点,因为在实际应用中,我们对测试效率的关注要远远高于训练效率。

在实际的图像分类中基本上是不会使用KNN算法的。因为图像都是高维度数据(它们通常包含很多像素),这些高维数据想要表达的主要是语义信息,而不是某个具体像素间的距离差值(在图像中,具体某个像素的值和差值基本上并不会包含有用的信息),所以这就是我们为什么需要用深度学习和神经网络来训练模型提高准确率和检测速度。

我的gtihub博客地址:https://forchenxi.github.io/

另外,如果对投资理财感兴趣的同学,可以关注我的微信公众号:运气与实力。

<think>好的,我现在需要帮助用户解决同时使用内网外网的问题。用户的情况是笔记本电脑通过网线连接内网同时使用Wi-Fi连接外网。当前的问题是,当Wi-Fi开启时,只能使用外网,无法访问内网系统,必须关闭Wi-Fi才能使用内网。用户希望同时访问内外网。 首先,我需要回忆一下网络路由的基本知识。当一台计算机有多个网络接口(比如有线网卡和无线网卡),每个接口可能有不同的网关。默认情况下,操作系统会根据路由表的优先级来选择使用哪个网关。通常,系统会有一个默认网关,而其他网络的路由需要手动添加。 用户的内网网关是192.168.18.254,而外网的网关是192.168.1.1。可能的情况是,当Wi-Fi连接时,外网的网关被设为默认网关,导致所有流量都走外网,而内网的路由没有被正确配置,因此内网请求可能被错误地路由到外网网关,导致无法访问。 解决方法应该是添加一条静态路由,告诉系统访问内网网段(192.168.18.0/24)时通过内网网关192.168.18.254,而其他流量仍然使用外网的默认网关。这样,当访问内网系统时,数据包会通过有线网卡,而其他互联网流量则通过Wi-Fi。 接下来,我需要考虑用户可能已经尝试过添加路由,但可能存在问题,比如路由没有正确添加或者没有持久化。另外,子网掩码是否正确也是一个关键点。用户提到的内网地址是192.168.18.33,子网掩码可能是255.255.255.0,即/24,但需要确认是否正确。 此外,路由的优先级问题也可能存在。如果两个接口都有默认网关,系统可能会根据跃点数(metric)来决定优先使用哪个接口。调整跃点数,让外网接口(Wi-Fi)的跃点数更低,可以确保默认流量走外网,而内网流量走有线。 另外,防火墙设置也可能阻止了内网通信,需要检查是否被防火墙拦截。还有可能用户的路由命令有误,比如没有使用正确的子网掩码或网关地址。 回顾用户提供的步骤,之前的回答已经涵盖了添加路由、检查网关可达性、子网掩码匹配、防火墙设置、接口跃点数调整等方法。用户现在的情况是已经手动设置了内网网关,但可能因为默认网关被外网占据,导致内网路由失效。 因此,正确的步骤应该是:删除内网接口的默认网关,仅保留外网的默认网关,然后为内网网段添加静态路由。这样,系统在访问内网时会使用指定的路由,而其他所有流量则走外网网关。 需要确保用户的内网接口(以太网3)没有设置默认网关,否则当两个接口都有默认网关时,系统可能会出现不可预测的路由行为。用户之前可能在内网接口配置中手动设置了网关,这会导致路由冲突。 总结步骤应该是: 1. 移除内网接口的默认网关设置。 2. 添加永久静态路由指向内网网段,使用内网网关。 3. 调整接口跃点数,确保外网接口优先级更高。 4. 测试路由是否生效,并检查防火墙设置。 同时需要指导用户如何通过命令行添加路由,并确保路由持久化(使用-p参数)。此外,检查路由表确认路由是否正确添加,以及通过ping和tracert测试连通性。 可能的问题点包括:用户是否正确移除了内网接口的默认网关,是否正确输入了路由命令,子网掩码是否正确,以及是否有其他冲突的路由规则。 最后,需要以清晰的中文步骤解释给用户,确保用户能够逐步操作,并在每个步骤后验证结果,以便及时发现问题。</think>### 解决方案:双网卡同时访问内外网 通过**路由优先级控制**和**静态路由配置**可实现外网并行访问,以下是详细操作步骤: --- #### 步骤1:移除内网接口的默认网关(关键操作) 1. 打开 **控制面板 → 网络和共享中心 → 更改适配器设置** 2. 右键 **以太网3**(内网网卡) → 属性 → **Internet协议版本4 (TCP/IPv4)** → 高级 3. 在 **默认网关** 栏位中 **删除所有条目**(保留外网WiFi的默认网关) > 这一步确保系统默认流量只通过WiFi外网出口 --- #### 步骤2:添加永久静态路由(管理员权限运行CMD) ```cmd route add -p 192.168.18.0 mask 255.255.255.0 192.168.18.254 ``` **命令解释**: - `-p` 参数:永久路由(重启后保留) - `192.168.18.0/24`:需要访问的内网网段 - `192.168.18.254`:内网专用网关 --- #### 步骤3:验证路由配置 执行命令查看路由表: ```cmd route print -4 ``` 确认输出中包含: ``` IPv4 路由表 =========================================================================== 永久路由: 网络地址 网络掩码 网关地址 跃点数 192.168.18.0 255.255.255.0 192.168.18.254 1 ``` --- #### 步骤4:调整接口跃点数(强制外网优先) 1. 打开 **网络连接** 设置 2. **WiFi连接** → 属性 → IPv4 → 高级 → 取消"自动跃点" → 手动设置为 **25** 3. **以太网3** → 属性 → IPv4 → 高级 → 手动设置为 **50** > 数值越小优先级越高,确保外网流量默认走WiFi --- #### 步骤5:最终连通性测试 1. 保持WiFi网线同时连接 2. 测试外网访问: ```cmd ping 8.8.8.8 ``` 3. 测试内网访问: ```cmd ping 192.168.18.254 ``` 4. 测试跨网段访问(例如访问内网服务器): ```cmd tracert -d 192.168.18.100 ``` --- ### 原理示意图 ``` [笔记本电脑] ├─ WiFi (外网) → 默认网关 192.168.1.1 (跃点25) → 互联网 └─ 以太网3 (内网) → 专用路由 192.168.18.0/24 → 网关192.168.18.254 (跃点50) ``` --- ### 常见问题处理 1. **路由添加失败**: - 检查是否以管理员身份运行CMD - 确认内网网关可达:`ping 192.168.18.254` 2. **访问内网依然失败**: ```cmd route delete 192.168.18.0 route add -p 192.168.18.0 mask 255.255.255.0 192.168.18.254 if [以太网3的接口编号] ``` (接口编号通过`route print`查看) 3. **网页访问异常**: ```cmd netsh winsock reset netsh int ip reset ipconfig /flushdns ``` 重启后生效 --- 通过以上配置,您可保持WiFi连接互联网的同时,通过有线网卡访问内网系统。若仍有异常,请提供具体错误提示或tracert截图,我将协助进一步诊断。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值