K-近邻算法(KNN)

一、引言。

        KNN(K- Nearest Neighbor)法即K最邻近法,最初由 Cover和Hart于1968年提出,是一个理论上比较成熟的方法,也是最简单的机器学习算法之一。该方法的思路非常简单直观:如果一个样本在特征空间中的K个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。该方法在定类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别 。

二、KNN算法原理。

        在分类任务中,我们的目标是判断样本x的类别y。KNN会先观察与该样本点距离最近的K个样本,统计这些样本所属的类别。然后将当前样本归到出现次数最多的类中。下面我们用数学语言来描述KNN算法。设已分类样本的集合为\AE \chi _{0}。对于一个待分类的样本x,定义其邻居N_{k}(x)X_{0}中与x距离最近的K个样本x_{1},x_{2},...,x_{K}组成的集合,这些样本对应的类别分别是y_{1},y_{2},...,y_{K}。我们统计集合N_{K}(x)中类别为j的样本的数量,记为G_{j}(x)

                                               G_{_{j}}(x)=\sum_{x_{i}\in N_{K}(x)}\prod (y_{i}=j)

其中,\prod (p)是示性函数,其自变量p是一个命题,当p为真时,\prod (p)=1,反之,当p为假时,\prod (p)=0,最后,我们将x的类别y\hat{}(x)判断为使G_{j}(x)最大的类别:

                                                y\hat{}(x)=arg _{}max\underset{j}{}G_{j}^{}(x)

        与分类任务类似,我们还可以将KNN应用于回归任务。对于样本x,我们需要预测其对应的实数值y。同样,KNN考虑K个相邻的样本点x_{i}\epsilon N_{K}(x),将这些的样本,点对于实数值y_{i}进行加权平均,就得到样本x的预测结果y\hat{}(x)

                                                 y\hat{}(x)=\sum_{x_{i}\epsilon N_{K}(x)}w_{i}y_{i},其中\sum_{i=1}^{K}w_{i}=1

在这里,权重w_{i}代表不同邻居对当前样本的重要程度,权重越大,该邻居的值y_{i}对最后的预测影响也越大。我们即可以预先定义好权重,也可以根据数据集的特性设置权重与距离的关系,还可以将权重作为模型的参数,通过学习得到。

注意:样本之间的距离用欧氏距离。对于n维空间中的两个点x=(x_{1},x_{2},...,x_{n})y=(y_{1},y_{2},...,y_{n}),其欧氏距离为:

                                               d_{Euc}(x,y)=\sqrt{\sum_{i=1}^{n}(x_{i}-y_{i})^{2}}

三、KNN的应用场景。
  1.数据预处理:用于缺失值估算。
  2.推荐系统:根据用户的行为数据,推荐相似的物品或内容。
  3.金融:用于信用评估、股票市场预测等。
  4.医疗保健:用于疾病诊断、病情预测等。
  5.模式识别:用于手写数字识别、物体分类等。
四、KNN的优缺点。
  1.优点:简单易懂,无需复杂的模型训练和参数估计;适合多分类问题;对异常值不敏感;适合类域交叉样本;适用大样本自动分类。
  2.缺点:计算量较大,尤其在处理大数据集时;对特征值敏感;需要选择合适的K值和距离度量方法;空间复杂度较高,需要存储所有训练数据;对于大规模数据集和高维数据,效果可能会下降;可解释性差,无法提供决策规则或变量重要性信息。

五、KNN算法实现。

        使用KNN算法实现了计算机视觉中的图像分类和色彩风格迁移技术,具体过程如下:

        先实现用KNN算法实现分类任务。

实现步骤
  数据准备:
  1.读取图像数据集,将图像转换为合适的特征表示形式,例如可以将图像的像素值作为特征向量,或者提取图像的更高级特征,如纹理特征、形状特征等。
  2.将数据集划分为训练集和测试集,按照一定的比例8:2将数据集分为训练集和测试集。我们先在训练集上应用KNN算法,再在测试集上测试算法的表现。
  构建KNN模型:
  1.构建KNN模型,设置合适的K值,例如K=5。
  2.将训练集的特征向量作为训练数据,对应的类别标签作为目标数据,对KNN模型进行训练。
  图像分类:
  1.对于测试集中的每个图像,提取其特征向量。
  2.使用训练好的KNN模型预测该图像的类别标签。

        然后再将KNN算法应用到回归任务——色彩分隔迁移上。通过KNN算法,可以将目标图像中的每个像素分类到不同的色彩类别中。然后,可以将这些类别映射到源图像的对应色彩上,从而实现色彩风格的迁移。这一步需要确保源图像和目标图像在色彩上有良好的对应关系‌。最后,将处理后的图像显示出来,可以看到色彩风格迁移的效果。
实现步骤
  数据准备:
  1.读取源图像和目标图像,将它们转换为Lab颜色空间,因为Lab颜色空间更接近人类视觉感知,能够更好地分离亮度和颜色信息。
  2.提取源图像的颜色特征作为训练数据,例如可以将源图像的每个像素的ab通道值作为特征向量。
  构建KNN模型:
  1.构建KNN模型,设置合适的K值,例如K=4。
  2.将源图像的颜色特征作为训练数据,对应的颜色值作为目标数据,对KNN模型进行训练。
  色彩风格迁移:
  1.对于目标图像中的每个像素,提取其ab通道值作为特征向量。
  2.使用训练好的KNN模型预测该像素的颜色值,得到新的ab通道值。
  3.将新的ab通道值与目标图像的L通道值合并,转换回RGB颜色空间,得到迁移后的图像。
注意事项:
  1.K值的选择:K值的选择会影响图像分类和色彩风格迁移的效果,需要根据具体的图像和需求以及分类任务进行调整。
  2.特征提取和距离度量:选择合适的特征提取方法和距离度量方式对于KNN算法的性能至关重要。
  3.计算效率:对于较大的图像,KNN算法的计算量可能较大,需要考虑如何提高计算效率,例如可以采用近似最近邻算法等。
 

完整的Python代码如下:

print('随机生成mnist_x和mnist_y的样本文件')
import numpy as np

# 假设MNIST有10类,这里随机生成一些标签示例(0 - 9)
# 如果MNIST图像数量为n
n = 1000  # 假设的图像数量,需根据实际的mnist_x数量调整
mnist_y = np.random.randint(0, 10, n)

# 将生成的标签保存为文件
np.savetxt('mnist_y', mnist_y)

# 假设与之前的mnist_y样本数一致
n = 1000  # 必须与生成mnist_y时的n相同
height, width = 28, 28  # MNIST图像尺寸

# 生成随机像素值(0-255的整数)
mnist_x = np.random.randint(0, 256, (n, height * width))

# 保存为空格分隔的文本文件
np.savetxt("mnist_x", mnist_x, fmt="%d", delimiter=" ")
print('mnist_x和mnist_y的样本文件生成成功')

print('随机生成gauss.csv的文件')
import numpy as np
import csv

# 生成随机高斯分布数据
num_samples = 200  # 定义样本数量,可以根据需求修改
data = np.random.randn(num_samples, 2)
labels = np.random.randint(0, 2, num_samples)  # 随机生成0或1的标签

# 将数据和标签合并
combined_data = np.hstack((data, labels.reshape(-1, 1)))

# 写入到csv文件
with open('gauss.csv', 'w', newline='') as csvfile:
    writer = csv.writer(csvfile)
    for row in combined_data:
        writer.writerow(row)

print('gauss.csv的文件生成成功')
print('输出input.jpg的图像')
import cv2
import numpy as np
import os

def show_image():
    input_path = 'style_transfer\input.jpg'
    img = cv2.imread(input_path)  # 以灰度模式读取图像
    cv2.imshow('Output Image', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

show_image()
print('用KNN算法完成分类任务')

import matplotlib.pyplot as plt
import numpy as np
import os

# 读入MNIST数据集
m_x = np.loadtxt('mnist_x', delimiter=' ')
m_y = np.loadtxt('mnist_y')

# 数据集可视化
plt.figure(figsize=(10, 3))
for i in range(5):  # 显示前5个样本
    plt.subplot(1, 5, i+1)
    data = np.reshape(np.array(m_x[i], dtype=int), [28, 28])
    plt.imshow(data, cmap='gray')
    plt.title(f"Label: {int(m_y[i])}")
    plt.axis('off')
plt.tight_layout()
plt.show()

# 将数据集分为测试集和训练集
ratio = 0.8
split = int(len(m_x) * ratio)

# 打乱数据
np.random.seed(0)
idx = np.random.permutation(np.arange(len(m_x)))
m_x = m_x[idx]
m_y = m_y[idx]
x_train, x_test = m_x[:split], m_x[split:]
y_train, y_test = m_y[:split], m_y[split:]

def distance(a, b):
    return np.sqrt(np.sum(np.square(a - b)))

class KNN:
    def __init__(self, k, label_num):
        self.k = k
        self.label_num = label_num  # 类别的数量

    def fit(self, x_train, y_train):
        self.x_train = x_train
        self.y_train = y_train

    def get_knn_indices(self, x):  # 修正方法名!!!
        # 获得距离目标样本点最近的K个样本的下标
        dis = list(map(lambda a: distance(a, x), self.x_train))
        knn_indices = np.argsort(dis)[:self.k]
        return knn_indices

    def get_label(self, x):
        knn_indices = self.get_knn_indices(x)  # 调用修正后的方法
        label_statistic = np.zeros(self.label_num)
        for index in knn_indices:
            label = int(self.y_train[index])
            label_statistic[label] += 1
        return np.argmax(label_statistic)

    def predict(self, x_test):
        predicted_labels = np.zeros(len(x_test), dtype=int)
        for i, x in enumerate(x_test):
            predicted_labels[i] = self.get_label(x)
        return predicted_labels

# 测试不同K值的准确率
accuracies = []
k_values = range(1, 10)
for k in k_values:
    knn = KNN(k, label_num=10)
    knn.fit(x_train, y_train)
    predicted_labels = knn.predict(x_test)
    accuracy = np.mean(predicted_labels == y_test)
    accuracies.append(accuracy)
    print(f'K的取值为 {k},预测准确率为 {accuracy * 100:.1f}%')

# 生成准确率变化图
plt.figure()
plt.plot(k_values, accuracies, 'bo-')
plt.xlabel('K-value')
plt.ylabel('accuracy')
plt.title('Classification accuracy for different K values')
plt.grid(True)
plt.show()

# 可视化测试结果(随机选5个样本)
plt.figure(figsize=(10, 3))
for i in np.random.choice(len(x_test), 5):  # 随机选择5个测试样本
    plt.subplot(1, 5, (i % 5)+1)
    data = np.reshape(x_test[i], [28, 28])
    plt.imshow(data, cmap='gray')
    true_label = int(y_test[i])
    pred_label = predicted_labels[i]
    title_color = 'green' if true_label == pred_label else 'red'
    plt.title(f"True: {true_label}\nPred: {pred_label}", color=title_color)
    plt.axis('off')
plt.tight_layout()
plt.show()

print('使用scikit-learn实现KNN算法')

from sklearn.neighbors import KNeighborsClassifier    #sklearn中的KNN分类器
from matplotlib.colors import ListedColormap

#读入高斯数据集
data = np.loadtxt('gauss.csv', delimiter=',')
x_train = data[:, :2]
y_train = data[:, 2]
print('数据集大小:', len(x_train))

#可视化
plt.figure()
plt.scatter(x_train[y_train==0, 0], x_train[y_train==0, 1], c='blue', marker='o')
plt.scatter(x_train[y_train==1, 0], x_train[y_train==1, 1], c='red', marker='x')
plt.xlabel('X axis')
plt.ylabel('Y axis')
plt.show()

#设置步长
step = 0.02
#设置网络边界
x_min, x_max = np.min(x_train[:, 0]) - 1, np.max(x_train[:, 0]) + 1
y_min, y_max = np.min(x_train[:, 1]) - 1, np.max(x_train[:, 1]) + 1
#构造网络
xx, yy = np.meshgrid(np.arange(x_min, x_max, step), np.arange(y_min, y_max, step))
grid_data = np.concatenate([xx.reshape(-1, 1), yy.reshape(-1, 1)], axis=1)

fig = plt.figure(figsize=(16, 4.5))
#观察分类结果变化
ks = [1, 3, 10]
cmap_light = ListedColormap(['royalblue', 'lightcoral'])

for i, k in enumerate(ks):
    #定义KNN分类器
    knn = KNeighborsClassifier(n_neighbors=k)
    knn.fit(x_train, y_train)
    z = knn.predict(grid_data)

    #画出分类结果
    ax = fig.add_subplot(1, 3, i + 1)
    ax.pcolormesh(xx, yy, z.reshape(xx.shape), cmap=cmap_light, alpha=0.7)
    ax.scatter(x_train[y_train==0, 0], x_train[y_train==0, 1], c='blue', marker='o')
    ax.scatter(x_train[y_train==1, 0], x_train[y_train==1, 1], c='red', marker='x')

    ax.set_xlabel('X axis')
    ax.set_ylabel('Y axis')
    ax.set_title(f'K = {k}')

plt.show()

print('用KNN算法完成回归任务——色彩风格迁移')

import cv2
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.image import imread
from skimage.color import rgb2lab, lab2rgb
from sklearn.neighbors import KNeighborsRegressor
import os
import win32api
# 检查并提示是否安装Pillow库
try:
    from PIL import Image
except ImportError:
    print("错误: 需要安装Pillow库,请执行: pip install Pillow")
    exit(1)

# 检查并提示是否安装pywin32库,若未安装则中文路径支持受限
try:
    import win32api
except ImportError:
    print("警告: 未找到pywin32,中文路径支持受限")
def get_short_path(long_path):
    """
    获取长路径对应的短路径(8.3格式)。
    如果转换成功,会在控制台打印转换信息;如果转换失败,会打印失败信息并返回原路径。
    :param long_path: 原始的长路径
    :return: 转换后的短路径或者原路径(如果转换失败)
    """
    try:
        short_path = win32api.GetShortPathName(long_path)
        print(f"路径转换: {long_path} -> {short_path}")  # 调试输出
        return short_path
    except Exception as e:
        print(f"短路径转换失败({long_path}): {str(e)}")
        return long_path
# 获取当前脚本的绝对路径
script_dir = os.path.dirname(os.path.abspath(__file__))

# 创建目标目录
path = os.path.join(script_dir, 'style_transfer')
data_dir = os.path.join(path, 'vangogh')
os.makedirs(data_dir, exist_ok=True)

# 处理风格图像路径
style_path = os.path.join(path, 'style.jpg')

# --- 增强的文件创建验证 ---
if not os.path.exists(style_path):
    try:
        # 生成测试图像
        test_img = np.zeros((300, 300, 3), dtype=np.uint8)
        test_img[100:200, 100:200] = (0, 0, 255)  # BGR红色方块

        # 获取短路径并创建父目录
        short_style_path = get_short_path(style_path)
        os.makedirs(os.path.dirname(short_style_path), exist_ok=True)

        # 保存并验证
        print(f"尝试保存到: {short_style_path}")  # 调试输出
        if cv2.imwrite(short_style_path, test_img, [int(cv2.IMWRITE_JPEG_QUALITY), 95]):
            # 二次验证:尝试读取保存的文件
            validation_img = cv2.imread(short_style_path)
            if validation_img is None:
                raise RuntimeError("文件保存成功但无法读取,可能已损坏")
            print("文件创建验证通过")
        else:
            raise RuntimeError("cv2.imwrite返回失败")

    except Exception as e:
        print(f"创建style.jpg失败: {str(e)}")
        # 尝试备用路径
        backup_path = os.path.join(script_dir, 'backup_style.jpg')
        print(f"尝试使用备用路径: {backup_path}")
        cv2.imwrite(backup_path, test_img)
        style_path = backup_path  # 更新后续使用的路径
else:
    print(f"使用已存在的style.jpg,路径: {style_path}")
def validate_image_read(path):
    """
    尝试读取图像文件。
    首先尝试使用短路径读取,如果失败则尝试原始路径读取,如果仍然失败则使用Pillow库作为备用方案读取。
    在每个读取尝试阶段都会在控制台打印调试信息。
    如果读取失败,会打印详细的错误信息,包括路径信息、短路径尝试情况、错误原因、文件是否存在以及文件大小(如果存在)。
    :param path: 要读取的图像文件的路径
    :return: 读取到的图像数据(以OpenCV格式)
    :raises RuntimeError: 如果所有读取尝试都失败,则抛出运行时错误
    """
    print(f"尝试读取路径: {path}")  # 调试输出

    # 优先尝试短路径
    short_path = get_short_path(path)
    img = cv2.imread(short_path)
    if img is not None:
        print(f"成功通过OpenCV读取短路径: {short_path}")
        return img

    # 备用方案1:尝试原始路径
    img = cv2.imread(path)
    if img is not None:
        print(f"成功通过OpenCV读取原始路径: {path}")
        return img

    # 备用方案2:使用Pillow
    try:
        from PIL import Image
        print(f"尝试通过Pillow读取: {path}")
        pil_img = Image.open(path).convert('RGB')
        return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
    except Exception as e:
        # 收集错误信息
        err_msg = [
            f"图像读取失败: {path}",
            f"短路径尝试: {short_path}",
            f"错误信息: {str(e)}",
            f"文件存在: {os.path.exists(path)}",
            f"文件大小: {os.path.getsize(path) if os.path.exists(path) else 'N/A'} bytes"
        ]
        raise RuntimeError("\n".join(err_msg))
block_size = 1
def read_style_image(file_name, size=block_size):
    file_name = os.path.abspath(file_name)
    if not os.path.isfile(file_name):
        raise FileNotFoundError(f"路径不是文件: {file_name}")

    # 使用增强的读取函数
    img = validate_image_read(file_name)

    # 转换颜色空间
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

    plt.figure()
    plt.imshow(img)
    plt.xlabel('X axis')
    plt.ylabel('Y axis')
    plt.title("Style Image")
    plt.show()

    img = rgb2lab(img)
    w, h = img.shape[:2]
    if w <= 2 * size or h <= 2 * size:
        raise ValueError(f"图像尺寸{w}x{h}过小,至少需要{(2 * size + 1)}x{(2 * size + 1)}")

    X, Y = [], []
    for x in range(size, w - size):
        for y in range(size, h - size):
            X.append(img[x - size:x + size + 1, y - size:y + size + 1, 0].flatten())
            Y.append(img[x, y, 1:])
    return X, Y
try:
    X, Y = read_style_image(style_path)
except Exception as e:
    print(f"读取风格图像失败: {str(e)}")
    exit(1)
# 训练KNN模型
knn = KNeighborsRegressor(n_neighbors=4, weights='distance')
knn.fit(X, Y)
def rebuild(img, size=block_size):
    if img.ndim!= 3 or img.shape[2]!= 3:
        raise ValueError("输入图像必须是RGB格式")

    plt.figure()
    plt.imshow(img)
    plt.xlabel('X axis')
    plt.ylabel('Y axis')
    plt.title("Input Image")
    plt.show()

    img = rgb2lab(img)
    w, h = img.shape[:2]

    photo = np.zeros((w, h, 3))
    print('Constructing window...')
    windows = []
    for x in range(size, w - size):
        for y in range(size, h - size):
            window = img[x - size:x + size + 1, y - size:y + size + 1, 0].flatten()
            windows.append(window)

    print('Predicting...')
    pred_ab = knn.predict(np.array(windows)).reshape(w - 2 * size, h - 2 * size, -1)

    photo[:, :, 0] = img[:, :, 0]
    photo[size:w - size, size:h - size, 1:] = pred_ab

    result = photo[size:w - size, size:h - size, :]
    return lab2rgb(result)
# 处理输入图像
input_path = os.path.join(path, 'input.jpg')
if not os.path.exists(input_path):
    try:
        input_img = np.zeros((300, 300, 3), dtype=np.uint8)
        input_img = cv2.circle(input_img, (150, 150), 100, (0, 255, 0), -1)
        cv2.imwrite(input_path, input_img)
    except Exception as e:
        print(f"创建input.jpg失败: {str(e)}")
        exit(1)
try:
    content = imread(input_path)
    new_photo = rebuild(content)

    plt.figure(figsize=(10, 5))
    plt.subplot(1, 2, 1)
    plt.imshow(content)
    plt.title("Original")
    plt.subplot(1, 2, 2)
    plt.imshow(new_photo)
    plt.title("Stylized")
    plt.show()
except Exception as e:
    print(f"处理失败: {str(e)}")
# 在rebuild函数后添加路径调试
if __name__ == "__main__":
    print("=" * 40)
    print("最终使用路径:")
    print(f"Style路径: {style_path}")
    print(f"Input路径: {os.path.join(path, 'input.jpg')}")
    print("=" * 40)

Python程序运行结果如下:

随机生成mnist_x和mnist_y的样本文件
mnist_x和mnist_y的样本文件生成成功
随机生成gauss.csv的文件
gauss.csv的文件生成成功
输出input.jpg的图像

用KNN算法完成分类任务

K的取值为 1,预测准确率为 10.0%
K的取值为 2,预测准确率为 10.0%
K的取值为 3,预测准确率为 12.5%
K的取值为 4,预测准确率为 9.5%
K的取值为 5,预测准确率为 8.5%
K的取值为 6,预测准确率为 10.5%
K的取值为 7,预测准确率为 12.0%
K的取值为 8,预测准确率为 11.0%
K的取值为 9,预测准确率为 9.5%

使用scikit-learn实现KNN算法
数据集大小: 200

用KNN算法完成回归任务——色彩风格迁移

使用已存在的style.jpg,路径:C:\Users\ABC\PycharmProjects\pythonProject18\style_transfer\style.jpg
尝试读取路径: C:\Users\ABC\PycharmProjects\pythonProject18\style_transfer\style.jpg
路径转换: C:\Users\ABC\PycharmProjects\pythonProject18\style_transfer\style.jpg -> C:\Users\ABC\PYCHAR~1\PYB613~1\STYLE_~1\style.jpg
尝试通过Pillow读取: C:\Users\ABC\PycharmProjects\pythonProject18\style_transfer\style.jpg

Constructing window...
Predicting...

========================================
最终使用路径:
Style路径: C:\Users\ABC\PycharmProjects\pythonProject18\style_transfer\style.jpg
Input路径: C:\Users\ABC\PycharmProjects\pythonProject18\style_transfer\input.jpg
========================================

六、总结。

1.KNN算法虽因计算效率问题在现代大规模数据中应用受限,但其简洁性使其仍为机器学习入门基础算法之一。

2.计算机视觉包括一系列重要任务,如图像分类、定位、图像分割和目标检测。其中,图像分类可以被认为是最基本的内容。它构成了其他计算机视觉任务的基础。图像分类应用在许多领域,如医学成像、卫星图像中的目标识别、交通控制系统、刹车灯检测、机器视觉等。

3.色彩迁移技术是计算机视觉领域的一个分支,它可以将一张图像的色彩特征转移到另一张图像上,从而改变目标图像的色彩外观。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值