机器学习环境部署

使用软件anaconda.navigator
anaconda官网下载
在这里插入图片描述
配置环境后使用Jupyter notebook进行算法完成

knn算法

一、问题引入

海伦一直使用在线约会网站寻找适合自己的约会对象。她曾交往过三种类型的人:

  • 不喜欢的人
  • 一般喜欢的人
  • 非常喜欢的人

这些人包含以下三种特征

  1. 每年获得的飞行常客里程数

  2. 玩视频游戏所耗时间百分比

  3. 每周消费的冰淇淋公升数

该网站现在需要尽可能向海伦推荐她喜欢的人,需要我们设计一个分类器,根据用户的以上三种特征,识别出是否该向海伦推荐。

二、需求概要分析

根据问题,我们可知,样本特征个数为3,样本标签为三类。现需要实现将一个待分类样本的三个特征值输入程序后,能够识别该样本的类别,并且将该类别输出。

四、K近邻算法的一般流程

  1. **数据准备:**这包括收集、清洗和预处理数据。预处理可能包括归一化或标准化特征,以确保所有特征在计算距离时具有相等的权重。
玩视频游戏所耗时间百分比每年获得的飞行常客里程数每周消费的冰淇淋的公升数样本分类
10.84000.51
2121340000.93
30200001.12
467320000.12

我们很容易发现,当计算样本之间的距离时数字差值最大的属性对计算结果的影响最大,也就是说,每年获取的飞行常客里程数对于计算结果的影响将远远大于上表中其他两个特征-玩视频游戏所耗时间占比和每周消费冰淇淋公斤数的影响。而产生这种现象的唯一原因,仅仅是因为飞行常客里程数远大于其他特征值。但海伦认为这三种特征是同等重要的,因此作为三个等权重的特征之一,飞行常客里程数并不应该如此严重地影响到计算结果。

**在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。**下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:
v n e w = ( v o l d − v m i n ) ( v m a x − v m i n ) v_{new}=(v_{old}-v_{min})(v_{max}-v_{min}) vnew=(voldvmin)(vmaxvmin)

**在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。**下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:
v n e w = ( v o l d − v m i n ) ( v m a x − v m i n ) v_{new}=(v_{old}-v_{min})(v_{max}-v_{min}) vnew=(voldvmin)(vmaxvmin)

  1. 选择距离度量方法:确定用于比较样本之间相似性的度量方法,常见的如欧几里得距离、曼哈顿距离等。

  2. 确定K值:选择一个K值,即在分类或回归时应考虑的邻居数量。这是一个超参数,可以通过交叉验证等方法来选择最优的K值。

  3. 找到K个最近邻居:对于每一个需要预测的未标记的样本:

  • 计算该样本与训练集中所有样本的距离。
  • 根据距离对它们进行排序。
  • 选择距离最近的K个样本
  1. 预测

    • 对于分类任务:查看K个最近邻居中最常见的类别,作为预测结果。例如,如果K=3,并且三个最近邻居的类别是[1, 2, 1],那么预测结果就是类别1。
    • 对于回归任务:预测结果可以是K个最近邻居的平均值或加权平均值。
  2. **评估:**使用适当的评价指标(如准确率、均方误差等)评估模型的性能。

  3. **优化:**基于性能评估结果,可能需要返回并调整某些参数,如K值、距离度量方法等,以获得更好的性能。

算法完成

#模块导入
import numpy as np
from collections import Counter
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
#数据导入
def load_dataset(filename):
    """
    从文本文件中导入数据(Mac / Windows 通用版本)
    """
    with open(filename, 'r', encoding='utf-8') as f:
        lines = f.readlines()

    X, y = [], []
    for line in lines:
        line = line.strip()
        if not line:  # 跳过空行
            continue
        parts = line.split('\t')
        if len(parts) < 4:
            continue
        X.append(list(map(float, parts[:3])))
        y.append(parts[-1])

    print(f" 成功加载样本数量: {len(X)} 条")
    return np.array(X), np.array(y)
#归一化
def min_max_normalize(X):
    X_min = X.min(axis=0)
    X_max = X.max(axis=0)
    X_norm = (X - X_min) / (X_max - X_min + 1e-8)
    return X_norm
#欧式距离
def ojld_distance(x1, x2):
    return np.sqrt(np.sum((x1 - x2) ** 2))
#曼哈顿距离
def mhd_distance(x1,x2):
    return np.sum(np.abs(x1 - x2))

#knn分类器
class KNNClassifier:
    def __init__(self, k=3, distance='euclidean'):
        self.k = k
        self.distance = distance
    def fit(self, X_train, y_train):
        """存储训练数据"""
        self.X_train = X_train
        self.y_train = y_train
    def _compute_distance(self, x):
        """计算单个样本到所有训练样本的距离"""
        if self.distance == 'euclidean':
            return np.sqrt(np.sum((self.X_train - x) ** 2, axis=1))
        elif self.distance == 'manhattan':
            return np.sum(np.abs(self.X_train - x), axis=1)
        else:
            raise ValueError("未知的距离类型")
    def predict(self, X_test):
        """对测试集进行预测"""
        predictions = []
        for x in X_test:
            # 计算距离
            distances = self._compute_distance(x)
            # 取最近的k个索引
            k_indices = np.argsort(distances)[:self.k]
            # 取对应的类别
            k_labels = self.y_train[k_indices]
            # 多数表决法
            most_common = Counter(k_labels).most_common(1)[0][0]
            predictions.append(most_common)
        return np.array(predictions)
#主程序
def evaluate_model(y_true, y_pred):
        """计算并打印分类准确率"""
        acc = accuracy_score(y_true, y_pred)
        print(f"\n 模型分类准确率: {acc:.3f}")
        return acc
if __name__ == "__main__":
    print(" 正在加载数据集...")
    X, y = load_dataset("/Users/mac/datingTestSet.txt")  # 这里替换成你的文件路径

    print(" 数据归一化中...")
    X_norm = min_max_normalize(X)

    print(" 划分训练集与测试集...")
    X_train, X_test, y_train, y_test = train_test_split(X_norm, y, test_size=0.2, random_state=42)

    print(" 训练 KNN 模型中...")
    knn = KNNClassifier(k=5, distance='euclidean')
    knn.fit(X_train, y_train)

    print(" 开始预测...")
    y_pred = knn.predict(X_test)

    print(" 模型评估中...")
    evaluate_model(y_test, y_pred)

    print("\n 分类任务完成!")
 正在加载数据集...
 成功加载样本数量: 1000 条
 数据归一化中...
 划分训练集与测试集...
 训练 KNN 模型中...
 开始预测...
 模型评估中...

模型分类准确率: 0.935

 分类任务完成!

模型评估优化

一、常见的分类模型评估指标

  1. 准确率 (Accuracy)

    • 描述:所有分类正确的样本数占总样本数的比例。
    • 公式: A c c u r a c y = N u m b e r   o f   c o r r e c t   p r e d i c t i o n s T o t a l   n u m b e r   o f   p r e d i c t i o n s Accuracy=\frac{Number\ of\ correct\ predictions}{Total\ number\ of\ predictions} Accuracy=Total number of predictionsNumber of correct predictions
  2. 混淆矩阵 (Confusion Matrix)

    • 描述:一个表格,显示模型对每个类别的预测数量和实际数量。

    • 主要组件:真正例 (True Positives, TP),假正例 (False Positives, FP),真反例 (True Negatives, TN),假反例 (False Negatives, FN)。

      True PositiveTrue Negative
      Predicted PositiveTPFP
      Predicted NegativeFNTN
  3. 精确率(Precision)

    • 描述:正确预测为正的样本数 (True Positives, TP) 与所有预测为正的样本数 (即True Positives + False Positives) 之比。
    • 公式: P r e c i s i o n = T r u e   P o s i t i v e s T r u e   P o s i t i v e s + F a l s e   P o s i t i v e s Precision=\frac{True\ Positives}{True\ Positives + False\ Positives} Precision=True Positives+False PositivesTrue Positives
  4. **召回率 (Recall) **

    • 描述:正确预测为正的样本数 (True Positives, TP) 与所有实际为正的样本数 (即True Positives + False Negatives) 之比。
    • 公式: R e c a l l = T r u e   P o s i t i v e s T r u e   P o s i t i v e s + F a l s e   N e g a t i v e s Recall=\frac{True\ Positives}{True\ Positives + False\ Negatives} Recall=True Positives+False NegativesTrue Positives
  5. F1分数 (F1 Score)

    • 描述:精确率和召回率的调和平均值。F1的大小反映了模型的稳定性,数值越大说明越稳定。
    • 公式: F 1 = 2 × P r e c i s i o n × R e c a l l P r e c i s i o n + R e c a l l F1=2×\frac{Precision×Recall}{Precision+Recall} F1=2×Precision+RecallPrecision×Recall
  6. ROC曲线 (Receiver Operating Characteristic Curve)

    • 描述:在各种阈值设置下,真正例率 (TPR) 对假正例率 (FPR) 的曲线。通过描绘两个指标之间的关系来展示分类器性能
  7. PR曲线 (Precision-Recall Curve)

    • 描述:在各种阈值设置下,精确率(Precision)和召回率(Recall)的曲线。也是通过描绘两个指标之间的关系来展示分类器性能
  8. AUC (Area Under the ROC Curve)

    • 描述:ROC曲线下的面积,是用于评估分类器在不考虑决策边界的情况下的整体性能的指标。
    • 特点:AUC的值介于0和1之间。一个完美的分类器的AUC值为1,而一个随机猜测的分类器的AUC值为0.5。
    • 优点:AUC考虑了所有可能的决策边界,因此它为比较不同模型的性能提供了一种不依赖于特定阈值的方法。
  9. AP (Average Precision)

    • 描述:PR曲线下的面积,用于评估分类器的整体精确率。
    • 特点:AP的值介于0和1之间。完美分类器的AP值为1,随机分类器的AP值等于数据中正类的比例。
    • 优点:与AUC类似,AP为分类器提供了一个整体的性能度量,不依赖于特定的阈值。但是,与AUC不同,AP特别适用于正负样本不平衡的情况。

二、ROC曲线与PR曲线的差异

  1. 坐标轴
    • ROC曲线的x轴是FPR,y轴是TPR。
    • PR曲线的x轴是Recall(或TPR),y轴是Precision。
  2. 应用场景
    • 在正负样本均衡的情况下,ROC曲线是一个很好的性能度量。
    • 在正负样本不均衡的情况下,PR曲线往往更有信息量。
  3. 整体性能指标
    • ROC曲线使用AUC(曲线下的面积)。
    • PR曲线使用AP(Average Precision,曲线下的面积)。

总之,AUC是ROC曲线下的面积,它考虑了分类器在所有可能的决策边界下的性能。AP是PR曲线下的面积,它特别适用于正负样本不平衡的情况,提供了对分类器整体精确率的评估。选择AUC还是AP取决于具体问题和数据集的性质。在正负样本严重不平衡的情况下,AP可能会提供更真实、更有信息量的评估。

三、画ROC曲线和PR曲线

具体实现:

#模型评估优化
from sklearn.metrics import (
    accuracy_score,
    precision_score,
    recall_score,
    f1_score,
    roc_curve,
    auc,
    precision_recall_curve,
    average_precision_score,
    confusion_matrix,
    ConfusionMatrixDisplay
)
import matplotlib.pyplot as plt
def evaluate_knn_model(y_true, y_pred, y_proba=None, labels=None):
    print("\n📊 ===== 模型评估指标 =====")
    acc = accuracy_score(y_true, y_pred)
    pre = precision_score(y_true, y_pred, average='macro')
    rec = recall_score(y_true, y_pred, average='macro')
    f1 = f1_score(y_true, y_pred, average='macro')

    print(f"✅ 准确率 (Accuracy): {acc:.3f}")
    print(f"🎯 精确率 (Precision): {pre:.3f}")
    print(f"📈 召回率 (Recall): {rec:.3f}")
    print(f"💡 F1分数: {f1:.3f}")

    # 绘制混淆矩阵
    cm = confusion_matrix(y_true, y_pred, labels=labels)
    disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=labels)
    disp.plot(cmap=plt.cm.Blues)
    plt.title("Confusion Matrix")
    plt.show()

    # 如果有预测概率(y_proba),绘制 ROC 和 PR 曲线
    if y_proba is not None and len(set(y_true)) == 2:
        print("\n📈 绘制 ROC 曲线与 PR 曲线...")
        # ROC 曲线
        fpr, tpr, _ = roc_curve(y_true, y_proba)
        roc_auc = auc(fpr, tpr)

        plt.figure(figsize=(6, 5))
        plt.plot(fpr, tpr, color='blue', lw=2, label=f"ROC curve (AUC = {roc_auc:.2f})")
        plt.plot([0, 1], [0, 1], color='gray', linestyle='--')
        plt.xlabel("False Positive Rate (FPR)")
        plt.ylabel("True Positive Rate (TPR)")
        plt.title("ROC Curve")
        plt.legend()
        plt.show()

        # PR 曲线
        precision, recall, _ = precision_recall_curve(y_true, y_proba)
        ap = average_precision_score(y_true, y_proba)

        plt.figure(figsize=(6, 5))
        plt.plot(recall, precision, color='green', lw=2, label=f"PR curve (AP = {ap:.2f})")
        plt.xlabel("Recall")
        plt.ylabel("Precision")
        plt.title("Precision-Recall Curve")
        plt.legend()
        plt.show()

        print(f"🌟 AUC: {roc_auc:.3f}")
        print(f"🌟 AP: {ap:.3f}")

在这里插入图片描述

再进一步增加ROC曲线与PR曲线:
在类中增加函数predict_proba

def predict_proba(self, X_test):
        """
        返回每个样本属于各类别的概率估计。
        用反距离加权法计算:距离越近,权重越大。
        """
        classes = np.unique(self.y_train)
        proba = []

        for x in X_test:
            # 计算距离
            distances = self._compute_distance(x)
            k_indices = np.argsort(distances)[:self.k]
            k_labels = self.y_train[k_indices]
            k_distances = distances[k_indices]

            # 防止除零
            weights = 1 / (k_distances + 1e-5)
            score = {c: 0.0 for c in classes}

            for label, w in zip(k_labels, weights):
                score[label] += w

            total = sum(score.values())
            proba.append([score[c] / total for c in classes])

        return np.array(proba)

最后调用结果展示

在这里插入图片描述

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐