python学习_day16---KNN算法

一、算法介绍

K最近邻(k-Nearest Neighbors,KNN)算法是一种基本的分类和回归算法,也是最简单易懂的机器学习算法。
应用场景有字符识别、文本分类、图像识别等领域

二、算法原理

存在一个训练样本集合A,在给定测试样本b时,基于某种距离度量,找出训练集A中与测试样本b最靠近的k个训练样本(通常k<=20且为整数),之后,基于这k个训练样本的结果信息来预测种类或值。
在这里插入图片描述上图,绿色点为测试样本

三、算法流程

在这里插入图片描述

1、计算测试样本和所有训练样本特征的相似度

如何计算相似度?使用距离衡量相似度:一般使用欧式距离
在这里插入图片描述点1(x1,y1)
点2(x2,y2)
在这里插入图片描述点1(x1,y1,z1)
点2(x2,y2,z2)

2、按照相似度从大到小排序(按照距离从小到大排序)
距离越小,相似度越大
距离越大,相似度越小

3、选择K个最相似的样本
K由自己设定:K=5

4、根据K个最近的邻居 的结果:预测测试样本的结果
取众数 作为预测结果

四、KNN电影分类的实现

在这里插入图片描述

import pandas as pd
import numpy as np

df = pd.read_excel("D:\pycharm\pythonProject\KNN算法./电影分类数据.xlsx")
# print(df)

# 1. 准备训练数据集
train_dataset = df.loc[:, "电影名称":"电影类型"]
# print("训练数据集\n", train_dataset)

# 2. 准备 测试样本
test_sample = df.columns[-3:].values  # 测试样本特征值

# 特征一般使用X表示,标签使用y表示
# 拆分训练集为训练集的特征和标签
train_X = train_dataset.loc[:, "搞笑镜头":"打斗镜头"]  # 训练集的特征
train_y = train_dataset["电影类型"] # 训练集的标签

# 算法阶段
# (1)计算测试样本和训练集中所有样本之间的相似度(使用距离表征相似度)
# print(train_X)
# print(test_sample)
dis = np.sqrt(np.sum((train_X-test_sample)**2,  axis=1))

# (2) 按照距离递增排序
# df_final= pd.DataFrame({"类型":train_y, "距离":dis})
# print("df_final\n", df_final)
train_dataset["距离"] = dis
train_dataset.sort_values(by="距离", inplace=True)
# print(train_dataset)

#(3)选择与测试样本距离最近的k个训练样本
k = 5
result = train_dataset.head(k)
# print(result)

# (4) 根据前K个训练样本的标签,进行投票或者求平均
# 电影类型的众数  mode返回的结果是series
print("唐人街探案的类型", result["电影类型"].mode()[0])

在这里插入图片描述

五、KNN算法特点

1、自定义K值 (K<=20)
2、惰性学习算法 (边测试边训练)
3、计算复杂度高 (每个测试样本 需要和所有训练样本 进行距离计算 排序)
4、优势:简单

六、KNN电影类型预测-sklearn实现

sklearn:机器学习算法工具
安装sklearn
安装命令: pip install scikit-learn

from sklearn.neighbors import KNeighborsClassifier  # KNN分类
import pandas as pd

df = pd.read_excel("D:\pycharm\pythonProject\KNN算法./电影分类数据.xlsx")

# 1. 准备训练数据集
train_dataset = df.loc[:, "电影名称":"电影类型"]
# print("训练数据集\n", train_dataset)

# 特征一般使用X表示,标签使用y表示
# 拆分训练集为训练集的特征和标签
train_X = train_dataset.loc[:, "搞笑镜头":"打斗镜头"]  # 训练集的特征
train_y = train_dataset["电影类型"]  # 训练集的标签

# 2. 准备 测试样本
test_sample = df.columns[-3:].values  # 测试样本特征值

# 1. 实例化算法对象
# n_neighbors K值,默认是5
#metric 距离的计算方式,默认是闵可夫斯基距离,但是当闵可夫斯基距离中p=2时,就是欧式距离
knn = KNeighborsClassifier(n_neighbors=3)

# 2. 训练算法
# fit 拟合;拟合可以称为学习、训练
# 拟合(学习/训练)特征和标签之间的关系;
# 传入的参数  训练集特征和标签
knn.fit(train_X, train_y)  # fit过程,简单存储所有训练集特征和标签

# 3. 预测
# 传入新数据特征,输出对应的标签
# 传入的必须是数据集
# y_pred = knn.predict( [[23,3,17]] )
y_pred = knn.predict(test_sample.reshape(1, -1))
# print(test_sample)

print("预测结果", y_pred) # 预测结果 ['喜剧片']

七、KNN手写数字识别

1、sklearn手写数字集认识

from sklearn.datasets import load_digits  # 手写数字数据集
import matplotlib.pyplot as plt

# 加载手写数字数据集
x, y = load_digits(return_X_y=True)

# 打开动画开关
plt.ion()

for sample, label in zip(x, y):
    plt.cla()  # 清理画布
    # sample 每行的数据,即每个样本
    img = sample.reshape(8,8)
    # print(sample)
    plt.title(label)
    plt.imshow(img)
    plt.pause(2)  # 每张图像之间间隔4秒

plt.ioff()  # 关闭动画开关
plt.show()

2、实现

1>KNN-sklearn实现

from sklearn.datasets import load_digits  # 手写数字数据集
# import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split  # 数据拆分
from sklearn.neighbors import KNeighborsClassifier


# 加载手写数字数据集
x, y = load_digits(return_X_y=True)

# print("特征的shape", x.shape)  # (1797, 64)
# 1797个样本 64个特征值

# print("标签", y.shape, y)

# 拆分数据集
# 训练集 和 测试集; 拆分比例通常是7:3  8:2
X_train, X_test, y_train, y_test = train_test_split(
    x,  # 特征
    y,  # 标签
    test_size=0.2,  # 给测试集分多少比例的样本
    random_state=1,  # 随机种子,设置随机种子后,可以保证多次运行结果是固定的
)

"""

import numpy as np
X, y = np.arange(10).reshape((5, 2)), range(5)
print(X)
print(y)

X_train, X_test, y_train, y_test = train_test_split(
    X, # 特征
    y, # 标签
    test_size=0.2,  # 给测试集分多少比例的样本
    random_state=1  # 随机种子,设置随机种子后,可以保证多次运行结果是固定的
)

print("拆分后训练集\n", X_train)
print("拆分后测试集\n", X_test)
"""

print("拆分后", X_train.shape, X_test.shape)

# 算法阶段
# 实例化算法对象
knn = KNeighborsClassifier()

# 传入的参数  训练集特征和标签
knn.fit(X_train, y_train)

# 预测
"""
y_pred = knn.predict(X_test)
print("预测结果", y_pred)
print("真实结果", y_test)

print("正确识别的数目", (y_pred == y_test).sum())
print("在测试集上准确率", round(100*(y_pred == y_test).sum() / len(y_test), 2))
"""

# 得分
# 输入:测试集X_test  测试集标签y_test
# 根据X_test 获得预测结果
# 根据预测结果和真实结果y_test 计算准确率

acc = knn.score(X_test, y_test)
print("acc", round(acc*100, 2))

2>自实现

from sklearn.datasets import load_digits  # 手写数字数据集
from sklearn.model_selection import train_test_split  # 数据拆分
import numpy as np
import pandas as pd

# 加载手写数字数据集
x, y = load_digits(return_X_y=True)

# 拆分数据集
# 训练集 和 测试集; 拆分比例通常是7:3  8:2
X_train, X_test, y_train, y_test = train_test_split(
    x,  # 特征
    y,  # 标签
    test_size=0.2,  # 给测试集分多少比例的样本
    random_state=1,  # 随机种子,设置随机种子后,可以保证多次运行结果是固定的
)
print("拆分后", X_train.shape, X_test.shape)  # (1437, 64)    (360, 64)

# 每个测试样本 和 所有训练样本进行距离计算
acc_num = 0 # 记录正确识别的数目
for test_sample, label in zip(X_test, y_test):
    # test_sample是测试样本
    dis = np.sqrt(np.sum((X_train-test_sample)**2,  axis=1))

    df = pd.DataFrame({
    
    "标签":y_train, "距离":dis})

    y_pred = df.sort_values(by="距离").head(5)["标签"].mode()[0]

    # label是真实结果

    if y_pred == label:
        acc_num += 1
    else:
        print("预测结果是{} 真实结果{}".format(y_pred, label))

print("正确的数目{} 准确率{}".format(acc_num,  acc_num*100/len(y_test)))

3、手写数字文件读取

import os
import numpy as np
from sklearn.neighbors import KNeighborsClassifier

"""
# 训练集路径下包含1934个文件
train_path = "../KNN算法/digit_data/trainingDigits"  # 训练数据集路径

all_files = os.listdir(train_path)
# 建立一个空二维数组 用来存放训练数据集
train_data = np.zeros((len(all_files), 1025), dtype=int)

for ind, i in enumerate(all_files):
    # 路径拼接
    file_path = os.path.join(train_path, i)
    # print(file_path)

    # 使用numpy的方法读文件
    arr = np.loadtxt(file_path, dtype=str)  # arr包含32个元素,每个元素是一个字符串
    arr = "".join(arr)  # 拼接为一个大字符串
    arr = [int(num) for num in arr]  # 调整为包含1024个元素的的列表
    # print(arr, type(arr))

    train_data[ind,:-1] = arr

    # print("文件名", i.split("_")[0])
    label = int(i.split("_")[0])  # 获取对应的标签

    train_data[ind, -1] = label
"""


def build_dataset(data_path=""):
    """
    输入参数:训练集 或 测试集的路径
    输出参数:训练集或测试集二维数据
    :return:
    """
    all_files = os.listdir(data_path)
    # 建立一个空二维数组 用来存放数据集
    dataset = np.zeros((len(all_files), 1025), dtype=int)

    for ind, i in enumerate(all_files):
        # 路径拼接
        file_path = os.path.join(train_path, i)

        # 使用numpy的方法读文件
        arr = np.loadtxt(file_path, dtype=str)  # arr包含32个元素,每个元素是一个字符串
        arr = "".join(arr)  # 拼接为一个大字符串
        arr = [int(num) for num in arr]  # 调整为包含1024个元素的的列表
        # print(arr, type(arr))

        dataset[ind, :-1] = arr

        # print("文件名", i.split("_")[0])
        label = int(i.split("_")[0])  # 获取对应的标签

        dataset[ind, -1] = label

    return dataset


train_path = "D:\pycharm\pythonProject\KNN算法\digit_data./trainingDigits"  # 训练数据集路径
test_path = "D:\pycharm\pythonProject\KNN算法\digit_data./testDigits"  # 测试数据集路径

train_dataset = build_dataset(train_path)
test_dataset = build_dataset(test_path)

k_list = [3, 4, 5, 6, 7, 8]
score_list = []

for k in k_list:
    # 实例化
    knn = KNeighborsClassifier(n_neighbors=k)

    # 传入的参数  训练集特征和标签
    knn.fit(train_dataset[:, :-1], train_dataset[:, -1])

    # 输入:测试集特征  测试集标签
    acc = knn.score(test_dataset[:, :-1], test_dataset[:, -1])
    print("K={} acc是{}".format(k, round(acc * 100, 2)))
    score_list.append(acc)

import matplotlib.pyplot as plt

plt.plot(k_list, score_list, marker='o')
plt.show()

# k=5    97.99
# k=3    98.52

八、KNN算法API

def init(self, n_neighbors=5, *,
weights=‘uniform’, algorithm=‘auto’, leaf_size=30,
p=2, metric=‘minkowski’, metric_params=None, n_jobs=None,
**kwargs):

n_neighbors:表示K值,K个近邻
weights:表示权重的计算方式{‘uniform’, ‘distance’} 默认是uniform
uniform:近邻的点的权重都一样,所以主要看点的个数
distance:距离的倒数表示权重
algorithm:算法{‘auto’, ‘ball_tree’, ‘kd_tree’, ‘brute’}
auto:自动选择最优算法
ball_tree:建立球树
kd_tree:建立k维的平衡二叉树
brute:穷举法,暴力搜索
brute:在测试样本寻找最近的k个邻居时,需要较长时间
ball_tree和kd_tree:可以在搜索k近邻时,降低搜索时间;
是以空间换时间(提前需要构建、存储一个树)
leaf_size:叶子节点,针对ball_tree和kd_tree
p和metric:决定了距离的计算方式
metric_params
n_jobs:并行

九、寻找最好的参数

1、交叉验证

在这里插入图片描述

"""
寻找最好的参数:交叉验证
"""
from sklearn.datasets import load_digits  # 手写数字数据集
from sklearn.model_selection import train_test_split  # 数据拆分
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score  # 交叉验证

# 加载手写数字数据集
x, y = load_digits(return_X_y=True)

# 拆分数据集
# 训练集 和 测试集; 拆分比例通常是7:3  8:2
X_train, X_test, y_train, y_test = train_test_split(
    x,  # 特征
    y,  # 标签
    test_size=0.2,  # 给测试集分多少比例的样本
    random_state=1,  # 随机种子,设置随机种子后,可以保证多次运行结果是固定的
)

"""
best_acc = 0
best_k = 0
best_w = ""
k_list = [2, 3, 4, 5, 6, 7]
weights = ["uniform", "distance"]

for k in k_list:
    for w in weights:
        knn = KNeighborsClassifier(n_neighbors=k, weights=w)

        knn.fit(X_train, y_train)

        acc = knn.score(X_test, y_test)
        if acc > best_acc:
            best_k = k
            best_w = w
            best_acc = acc

print("最好的参数k={}, w={} 准确率{}".format(best_k, best_w, best_acc))

"""

# knn = KNeighborsClassifier(n_neighbors=7, weights='uniform')
# acc = cross_val_score(knn, x, y, cv=6).mean()
# print(acc)


best_acc = 0
best_k = 0
best_w = ""
k_list = [2, 3, 4, 5, 6, 7]
weights = ["uniform", "distance"]

for k in k_list:
    for w in weights:
        # 使用特定参数的算法对象
        knn = KNeighborsClassifier(n_neighbors=k, weights=w)

        # 训练集(X)拆分为cv(5)份
        # 使用knn每次选择一份作为测试集
        acc = cross_val_score(knn, x, y, cv=5).mean()
        if acc > best_acc:
            best_k = k
            best_w = w
            best_acc = acc

print("最好的参数k={}, w={} 准确率{}".format(best_k, best_w, best_acc))

2、网格搜索

from sklearn.datasets import load_digits  # 手写数字数据集
from sklearn.model_selection import train_test_split  # 数据拆分
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score  # 交叉验证
from sklearn.model_selection import GridSearchCV  # 网格搜索

# 加载手写数字数据集
x, y = load_digits(return_X_y=True)

# 拆分数据集
# 训练集 和 测试集; 拆分比例通常是7:3  8:2
X_train, X_test, y_train, y_test = train_test_split(
    x,  # 特征
    y,  # 标签
    test_size=0.2,  # 给测试集分多少比例的样本
    random_state=1,  # 随机种子,设置随机种子后,可以保证多次运行结果是固定的
)

knn = KNeighborsClassifier()

# estimator 算法对象
# param_grid 参数
grid = GridSearchCV(knn, param_grid={
    
    "n_neighbors": [2, 3, 4, 5, 6, 7],
                                     "weights": ["uniform", "distance"]
                                     },
                    cv=5)

# grid.fit(X_train, y_train)
grid.fit(x, y)

print("最好的得分", grid.best_score_)
print("最好的参数", grid.best_params_)

3、总结

网格搜索 是 交叉验证的 封装

交叉验证和网格搜索本质上:都是在选择参数
选择好参数后,该文件没有意义,后续不再使用

Guess you like

Origin blog.csdn.net/weixin_45800653/article/details/121729103