机器学习实验作业一----knn算法

目录

knn算法概述:

课本中的算法思路:

实现过程需要用到的库:

归一化处理: 

 训练集样式:

测试集样式:

编码运行问题及其我编写代码前不了解的函数:

 部分代码解释:

1.画出训练集散点图:

 2.欧式距离计算:

 3.归一化:

4. K 近邻分类器:(也就是上面说的课本中的算法思路)

5.得到最合适的k值

 6.计算准确率,精确值,召回率,f1值

 新增:画出不同k值下的ROC曲线

1.了解ROC曲线:

1.FPR

2.TPR

3. AUC

4.阈值

2.绘制ROC曲线

1.函数解释

2.绘制的ROC图像

3.ROC函数代码 

 代码展示:

代码运行结果展示:

优缺点分析(参照课本): 

优点:

缺点:


机器学习课程的第一个算法knn算法,全称K-Nearest Neighbor,k最邻近算法,为机器学习中最常用,也是最简单的算法。KNN通过测量不同特征值之间的距离来进行分类。本文实现的是较为简单的knn算法,包括测试集,训练集,文本记录转化为Numpy的解析,使用matplotlib创建散点图,归一化数值。

本文的训练集,测试集是来源于网上查询的鸢尾花(iris)数据集。

knn算法概述:

knn算法是最简单的分类算法,主要用于将一个还未分类的点进行分类,得到该点的标签。

K近邻,就是K个最近的邻居的意思,说的是每个样本都可以用它最接近的K个邻近值来代表,也就是该点的标签是用他k个最近邻居中占比最大的标签(每个邻居都带有标签,统计标签出现的个数,取最多的那个标签),把该点分类到其中。

课本中的算法思路:

        对未知类别属性的数据集中的每个点依次执行以下操作:

  1.  计算已知类别数据集中点与当前点之间的距离。
  2. 按照距离递增次序排序。
  3. 选取与当前点距离最小的k个点。
  4. 确定前k个点所在类别出现的频率。
  5. 返回前k个点出现频率最高的类别作为当前点的预测分类。
实现过程需要用到的库:

Pandas 用于数据处理

NumPy 用于数值计算

Matplotlib 用于绘图

归一化处理: 

由于数据的样式不一定统一,当数据的两个数值之间的差距过大的时候,例如(1,1000)(2,3000)这两个数据,得到欧氏距离根号(1*1+2000*2000),我们可以很清楚的看到,由于x比y小很多,所以该数据的受y的影响比较大,这不是我们想看到的,因此我们采用归一化处理,也就是说我们要让所有特征的取值在0到1中分布,这样就可以使得特征影响的相同。

我这边采用的归一化公式为:

                        newvalue=(oldvalue-min)/(max-min)

max和min是数据集中的最小特征值和最大特征值。我们把所有的值都转化为0~1中的值。

当然,归一化公式并不唯一,具体情况具体分析了。

 训练集样式:

测试集样式:

 


编码运行问题及其我编写代码前不了解的函数:

  1. 读取训练集数据文件 train_data = pd.read_csv("训练集路径", sep='\s+'),函数默认情况下是用逗号分隔的,这边由于我的格式是以空格分隔的,所以加上sep='\s+'来表示是以空格区分的,当然如果你的格式是以回车分隔的,也可以改为sep='\t'。
  2. plt.figure()创建一个新的图形窗口或画布。 
  3. plt.scatter(横坐标, 纵坐标, color=颜色, label=标签)用来绘制图上的一个点。
  4. 由于我未正确设置中文字体,matplotlib 画出的散点图无法显示中文,并且会抛出错误。因此我使用以下代码来表示我们采用的是微软雅黑字体,就可以正常显示中文。

    plt.rcParams['font.sans-serif'] = ['SimHei']

      5.X_train.max(axis=0)这里的X_train是变量名,axis=0表示沿着数组行的方向,axis=1就是表示沿着数组列的方向进行。max也就是是取这一行(列)的最大值。

       6.np.mean(a == b)这个函数的作用就是a与测试集标签b相等的比例,当然我们也可以用for循环遍历所有的节点得到准确率。

        7.求多特征值的TP 、TN 、 FP 、 FN,网上查资料看到有直接的函数accuracy_score, precision_score, recall_score, f1_score这些都在sklearn.metrics库中,可以直接得到,对于该实验,我学习了其中计算加权的那部分,他的具体实现就是把其中的一个判断为正例,其他的判断为负例,然后就把问题划分为了求二特征值的简单模型。

 部分代码解释:

1.画出训练集散点图:

 创建画布,并利用训练集中的数据绘制散点图。for循环中每次取到训练集中的一个数据,plt.scatter()函数根据这个鸢尾花的长度为x,宽度为y来在散点图中绘制这个点。其中xlabel,ylabel,title分别表示x轴,y轴,还有散点图的名称。然后显示散点图。以下是该部分代码和得到的散点图。

plt.figure()//创建画布
colors = {'setosa': 'red', 'versicolor': 'blue', 'virginica': 'green'}
for i in range(len(train_data)):
    row = train_data.iloc[i]
    plt.scatter(row["Sepal.Length"], row["Sepal.Width"], color=colors[row["Species"]], label=row["Species"])
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.xlabel('鸢尾花长度')
plt.ylabel('鸢尾花宽度')
plt.title('训练集--鸢尾花长度与宽度的关系')
plt.show()

 2.欧式距离计算:

欧式距离也算是空间中两点间的直线距离,也就是传统的空间距离公式:

                        d=\sqrt{(x1-x2)^{2}+(y1-y2)^{2}+(z1-z2)^{2}}

那么对应函数也就是:

  1. (x1 - x2) 计算了两点在每个维度上的差值。
  2. (x1 - x2) ** 2 对每个维度上的差值进行平方。
  3. np.sum(...) 对所有维度上的平方差值求和。
  4. np.sqrt(...) 对上一步得到的和进行平方根运算,得到最终的欧式距离。
def euclidean_distance(x1, x2):
    return np.sqrt(np.sum((x1 - x2) ** 2))

 3.归一化:

在代码中我们用max_vals来存储x这个特征值中的最大值,x_train.max(axis=0)(这里的axis是变量名,可以自己取)表示的是在这个训练集中x_train这一列的最大值。后面的x_train_scaled也就是把数据归一化结论得到的训练集表示。

max_vals = X_train.max(axis=0)
min_vals = X_train.min(axis=0)
X_train_scaled = (X_train - min_vals) / (max_vals - min_vals)

4. K 近邻分类器:(也就是上面说的课本中的算法思路)

对于每个测试集中的点,求出该点与训练集中每个点的距离存储在distances中。np.argsort(distances)[:k]是用二分排序得到距离该点的最近k个存在nearest_indices中

在把那k个点的标签都加入到 nearest_laels中,得到最大标签数的那个加入测试集标签中。

def predict_knn(X_train, y_train, X_test, k):
    y_pred = []
    for test_point in X_test:
        distances = [euclidean_distance(test_point, train_point) for train_point in X_train]
        nearest_indices = np.argsort(distances)[:k]
        nearest_labels = [y_train[i] for i in nearest_indices]
        pred_label = max(set(nearest_labels), key=nearest_labels.count)
        y_pred.append(pred_label)
    return y_pred

5.得到最合适的k值

遍历k的取值,我这边k的取值为2~20。

将每一个k的值用临近分类器分类,然后用np.mean()计算准确率。

用best_k, best_accuracy,y_pred_test_best来记录准确率最高的k,和最高的准确率,还有这个最高的k对应的测试集中各个点的分类标签。

for j in range(2, 21):
    y_pred_test_custom = predict_knn(X_train_scaled, y_train, X_test_scaled, j)
    accuracy = np.mean(y_pred_test_custom == y_test)
    accuracies.append(round(accuracy, 3))
    print(f"准确率k={j}: {accuracy:.3f}")
    if best_accuracy < accuracy:
        best_k = j
        best_accuracy = accuracy
        y_pred_test_best = y_pred_test_custom
print(f"k取值为{best_k}的准确率最高,准确率为{best_accuracy:.3f} ")

 6.计算准确率,精确值,召回率,f1值

按照课本上给出的定义和公式:

TP 表示真正例,TN 表示真负例,FP 表示假正例,FN 表示假负例

  1. 准确率: 准确率是分类器正确预测的样本数占总样本数的比例。 公式:准确率 = (TP + TN) / (TP + TN + FP + FN)

  2. 精确率: 精确率衡量的是被分类器预测为正类的样本中有多少是真正的正类样本。 公式:精确率 = TP / (TP + FP)

  3. 召回率: 召回率衡量的是真正的正类样本中有多少被分类器预测为正类。 公式:召回率 = TP / (TP + FN)

  4. F1 值: F1 值是精确率和召回率的调和平均值,综合考虑了精确率和召回率。 公式:F1 值 = 2 * (精确率 * 召回率) / (精确率 + 召回率)

def calculate(y_test, y_pred_test_best):
    accuracy = best_accuracy
    precision = precision_score(y_test, y_pred_test_best, average='weighted')
    recall = recall_score(y_test, y_pred_test_best, average='weighted')
    f1=(accuracy*recall)*2/(accuracy+recall)

    print("准确率: {:.2f}".format(accuracy))
    print("精确率: {:.2f}".format(precision))
    print("召回率: {:.2f}".format(recall))
    print("F1值: {:.2f}".format(f1))

calculate(y_test, y_pred_test_best)

学习别人的代码之后才发现对于这四个值都有对应的计算函数,例如accuracy_score, precision_score, recall_score, f1_score这些都在sklearn.metrics库中。

并且classification_report(y_test, y_pred_test_best)这个代码可以直接生成如图的表格,确实功能强大。

 新增:画出不同k值下的ROC曲线

1.了解ROC曲线:

ROC曲线就有四个关键的数据

与准确率一样需要用到的 TP 表示真正例,TN 表示真负例,FP 表示假正例,FN 表示假负例

1.FPR

曲线的x轴,表示假阳性率,计算公式为 :FPR = FP / (FP + TN)

2.TPR

曲线的纵坐标,表示真阳性率,也称为召回率,计算公式为 :TPR = TP / (TP + FN)

3. AUC

ROC曲线下方的面积被称为AUC,也就是ROC曲线与x轴围成的面积。AUC的取值范围在0到1之间,AUC越大,越接近1,表示模型性能越好,也就是是相同假阳性下对应的真阳性率标记高,预测的就更准确。那ROC曲线通常会越靠近左上角,AUC值就越大。

4.阈值

阈值代表了分类器将样本判定为正例的概率阈值。阈值决定了分类器如何将样本判定为正例或负例。当样本的预测概率高于阈值时,分类器将其判定为正例,否则判定为负例。

阈值的取值在[0, 1]之间,每个阈值代表的是ROC上的一个点,也就我们在绘制ROC曲线的时候需要选取多个阈值来描绘多个点,用平滑的曲线将这些点连接起来就得到了ROC曲线。

我们拿鸢尾花来举例一下,假设我们的阈值取值为0.3,我们拿本实验的knn算法来解释一下,鸢尾花有三种类别setosa,versicolor和virginica,我们选取测试集中的一个点,用knn算法求得训练集中与该点最近的k个点,这k个点都属于这三种类别,每种类别的预测概率就为该类别在这k个中的占比,这边假设setosa为0.2,versicolor为0.4和virginica为0.4,那么

  • TP是被正确分类为正例的versicolor和virginica样本数。
  • FN是实际为正例但被错误分类为负例的versicolor和virginica样本数。
  • FP是实际为负例但被错误分类为正例的setosa样本数。
  • TN是被正确分类为负例的setosa样本数。

 通过这个我们就可以得到FPR和TPR,然后我们就可以得到ROC上的一个点,把这些点平滑的连接起来就是ROC曲线,不同的k值得到的曲线也不相同。

2.绘制ROC曲线

1.函数解释

我们的函数需要四个变量,y_test 是测试集的真实标签,y_pred_probs 是预测的概率值,k 值,classes 是类别的列表。

  • 我们用y_test_bin记录真实标签进行二值化处理的结果,将其转换为二进制形式。也就是说我们把我们的标签都标号了,便于遍历。
  • roc_curve(y_test_bin[:, i], y_pred_probs[:, i])中第一个变量是真实标签,第二个是预测概率,得到的是图上的一个点。
  • for循环中对于每个类别,计算对应的假正例率、真正例率以及ROC曲线下面积。我们利用roc_curve函数来得到该类别在不同阈值下的多个点。用roc_auc[i] = auc(fpr[i], tpr[i])计算该类别的AUC值,再把这些点连接起来得到该类别下的ROC曲线。
  •  使用了 plt.plot 函数来绘制 ROC 曲线上的点,并为每条线条指定了不同的类别标签和颜色。我们没有显式地为每个类别指定颜色,因此 Matplotlib 会自动选择合适颜色。

2.绘制的ROC图像

可以看到我的曲线都不够平滑,那是因为我的数据集太小,预测的结果比较单一,因此得到的点的分布就比较简单。

我们还可以看到当我们的k值取19的时候,我们绘制的ROC曲线就是TPR=1这一条直线。那是因为在鸢尾花数据集中,如果 k 值足够大,KNN 算法会考虑到所有的训练样本,因此最终分类结果将会非常准确。这会导致 ROC 曲线上的所有点都位于左上角,形成一条直线,使得 AUC 的值等于 1。

3.ROC函数代码 

def plot_roc_curve(y_test, y_pred_probs, k,classes):
    # Binarize the output
    y_test_bin = label_binarize(y_test, classes=classes)
    n_classes = y_test_bin.shape[1]

    plt.figure()
    fpr = dict()
    tpr = dict()
    roc_auc = dict()

    # Compute ROC curve and ROC area for each class
    for i in range(n_classes):
        fpr[i], tpr[i], _ = roc_curve(y_test_bin[:, i], y_pred_probs[:, i])
        roc_auc[i] = auc(fpr[i], tpr[i])

        plt.plot(fpr[i], tpr[i], label=f'类别Class {i} (AUC = {roc_auc[i]:.2f})')

    plt.plot([0, 1], [0, 1], 'k--')
    plt.xlim([0.0, 1.0])
    plt.ylim([0.0, 1.05])
    plt.xlabel('FPR')
    plt.ylabel('TPR')
    plt.title(f'k值为(k={k})的ROC曲线 ')
    plt.legend(loc="lower right")
    plt.show()

 代码展示:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import classification_report, auc
from sklearn.metrics import roc_curve
from sklearn.preprocessing import LabelBinarizer, MultiLabelBinarizer, label_binarize

train_data = pd.read_csv("C:\\Users\\李烨\\Desktop\\新建文件夹\\6\\iris.txt", sep='\s+')

X_train = train_data[["Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width"]].values
y_train = train_data["Species"].values

plt.figure()
colors = {'setosa': 'red', 'versicolor': 'blue', 'virginica': 'green'}
for i in range(len(train_data)):
    row = train_data.iloc[i]
    plt.scatter(row["Sepal.Length"], row["Sepal.Width"], color=colors[row["Species"]], label=row["Species"])

plt.rcParams['font.sans-serif'] = ['SimHei']
# plt.rcParams['axes.unicode_minus'] = False
plt.xlabel('鸢尾花长度')
plt.ylabel('鸢尾花宽度')
plt.title('训练集--鸢尾花长度与宽度的关系')
plt.show()

max_vals = X_train.max(axis=0)
min_vals = X_train.min(axis=0)
X_train_scaled = (X_train - min_vals) / (max_vals - min_vals)

def plot_roc_curve(y_test, y_pred_probs, k,classes):
    # Binarize the output
    y_test_bin = label_binarize(y_test, classes=classes)
    n_classes = y_test_bin.shape[1]

    plt.figure()
    fpr = dict()
    tpr = dict()
    roc_auc = dict()

    # Compute ROC curve and ROC area for each class
    for i in range(n_classes):
        fpr[i], tpr[i], _ = roc_curve(y_test_bin[:, i], y_pred_probs[:, i])
        roc_auc[i] = auc(fpr[i], tpr[i])

        plt.plot(fpr[i], tpr[i], label=f'类别Class {i} (AUC = {roc_auc[i]:.2f})')

    plt.plot([0, 1], [0, 1], 'k--')
    plt.xlim([0.0, 1.0])
    plt.ylim([0.0, 1.05])
    plt.xlabel('FPR')
    plt.ylabel('TPR')
    plt.title(f'k值为(k={k})的ROC曲线 ')
    plt.legend(loc="lower right")
    plt.show()


def predict_knn(X_train, y_train, X_test, y_test, k, classes):
    y_pred = []
    y_pred_probs = np.zeros((len(X_test), len(classes)))

    for idx, test_point in enumerate(X_test):
        distances = [euclidean_distance(test_point, train_point) for train_point in X_train]
        nearest_indices = np.argsort(distances)[:k]

        nearest_labels = [y_train[i] for i in nearest_indices]
        pred_label = max(set(nearest_labels), key=nearest_labels.count)
        y_pred.append(pred_label)

        # 计算概率
        unique_labels = np.unique(y_train)
        label_to_index = {label: index for index, label in enumerate(unique_labels)}

        # 在预测时将类别标签转换为整数索引
        for i in classes:
            label_index = label_to_index[i]
            y_pred_probs[idx, label_index] = nearest_labels.count(i) / k

    # 绘制 ROC 曲线
    plot_roc_curve(y_test, y_pred_probs, k, classes=np.unique(y_train))

    return y_pred

def euclidean_distance(x1, x2):
    return np.sqrt(np.sum((x1 - x2) ** 2))



test_data = pd.read_csv("C:\\Users\\李烨\\Desktop\\新建文件夹\\6\\iristest.txt", sep='\s+')

#print(test_data.columns)
X_test = test_data[["Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width"]].values
y_test = test_data["Species"].values

X_test_scaled = (X_test - min_vals) / (max_vals - min_vals)

accuracies = []

best_k = 2
best_accuracy = 0
for j in range(2, 21):
    y_pred_test_custom = predict_knn(X_train_scaled, y_train, X_test_scaled,y_test, j,classes=np.unique(y_train))

    accuracy = np.mean(y_pred_test_custom == y_test)
    accuracies.append(round(accuracy, 3))
    print(f"准确率k={j}: {accuracy:.3f}")
    if best_accuracy < accuracy:
        best_k = j
        best_accuracy = accuracy
        y_pred_test_best = y_pred_test_custom
print(f"k取值为{best_k}的准确率最高,准确率为{best_accuracy:.3f} ")
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score


def calculate(y_test, y_pred_test_best):
    accuracy = best_accuracy
    precision = precision_score(y_test, y_pred_test_best, average='weighted')
    recall = recall_score(y_test, y_pred_test_best, average='weighted')
    f1=(accuracy*recall)*2/(accuracy+recall)

    print("准确率: {:.2f}".format(accuracy))
    print("精确率: {:.2f}".format(precision))
    print("召回率: {:.2f}".format(recall))
    print("F1值: {:.2f}".format(f1))

calculate(y_test, y_pred_test_best)

print(classification_report(y_test, y_pred_test_best))

代码运行结果展示:

优缺点分析(参照课本): 

优点:

  1. 简单易理解:KNN 是一种直观且易于理解的算法,适合入门级别的学习和实践。

  2. 适用于多分类问题:KNN 可以处理多分类问题,并且对特征空间的结构没有严格的假设。

  3. 对异常值不敏感:KNN 算法对异常值不敏感,因为它是基于邻居间的距离计算结果。

缺点:

  1. 计算复杂度高:在预测阶段,KNN 需要计算新数据点与所有训练集数据点的距离。

  2. 预测速度较慢:由于需要计算新数据点与所有训练数据点的距离,导致在预测阶段速度较慢。

  3. 对数据不平衡和高维数据敏感:当数据集存在类别不平衡或者维度较高时,KNN 的表现可能会受到影响。

  4. 参数选择困难:KNN 中的 K 值选择对算法的性能影响较大,选择不当可能导致过拟合或欠拟合。

                        

  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值