近朱者赤,近墨者黑。在机器学习中,有一个算法将这句古训发挥得淋漓尽致。

一、引言:从生活中的物以类聚说起

1.新同学应该坐哪里?

想象一下,班里新转来一位同学小张,我们对他的喜好一无所知。现在教室有两个空位:一个在学霸区(被小红、小明、小刚包围),一个在游戏区(被小强、小华、小磊包围)。

如果你是小张,并且希望学习进步,你会选择坐在哪里?

大多数人会直觉地选择学霸区。为什么?因为我们相信近朱者赤——一个人的特点和习惯,往往会受到周围人的影响。

2.二手房价格预测的智慧

现在换个场景:你想买一套二手房,但不知道它的合理价格是多少。

专业的方法是请评估师综合考虑地段、户型、朝向等复杂因素。但有一个更简单的方法:看看这个房子最近的几个邻居卖多少钱,取个平均值

这种“以邻居为标准”的思考方式,正是我们今天要介绍的k-最近邻(k-NN)算法的核心思想。

二、k-NN算法三步曲:简单却强大

1.第一步:找邻居——如何定义“远近”?

在k-NN中,“邻居”不是指地理位置上的接近,而是指在特征空间中的相似度。

距离度量是关键

  • 欧氏距离(直线距离):就像在平面上直接测量两点间的直线距离

  • 曼哈顿距离(网格距离):只能沿着垂直线行走,就像曼哈顿的街道布局

举个例子,假设我们用两个特征来描述同学:

  • X轴:每周学习时间(0-50小时)

  • Y轴:成绩分数(0-100分)

在二维平面上,每个同学就是一个点。两个同学的“距离”越近,说明他们的学习习惯和成绩越相似。

2.第二步:选k值——应该听几个邻居的意见?

这是k-NN中最关键的选择之一:

  • k=1:只问最近的一个邻居

    • 风险:万一这个邻居是个“怪人”呢?(容易过拟合)

  • k=3:问最近的三个邻居,投票决定

    • 稳健:减少了单个异常值的影响

  • k=全班:问所有人

    • 问题:失去个性化,变成“随大流”(可能欠拟合)

k值的选择体现了个性与共识的平衡

# k值选择的经验法则
1. 通常取奇数:避免投票平局(如3、5、7)
2. 常见做法:取样本数的平方根
3. 最佳实践:通过交叉验证寻找最优k

3.第三步:做决策——邻居的意见如何整合?

根据问题类型,有两种决策方式:

  • 分类问题(如判断水果种类):邻居们投票,少数服从多数

  • 回归问题(如预测房价):邻居们的取值取平均值

三、可视化实验:k值如何影响决策?

想象一个场景:红球和蓝球分布在桌面上,我们需要画出分界线。

  • k=1:边界极其复杂,每个红球周围都是红色区域

    • 现象:对噪声极度敏感(过拟合)

  • k=5:边界变得平滑,能够忽略一些异常点

    • 现象:平衡了敏感性与稳定性

  • k=20:边界非常平缓,但可能过度简化

    • 现象:忽略了局部特征(欠拟合)

关键洞察:k值太小,模型太“敏感”;k值太大,模型太“迟钝”。需要找到“恰到好处”的k值。

四、动手体验:教室里的k-NN实验室

1.互动游戏:预测新同学的兴趣

假设我们知道部分同学的每周运动时间和看书时间,以及他们的主要兴趣(篮球、游戏、读书、音乐)。

  1. 在教室地面画出坐标系(X轴:运动时间,Y轴:看书时间)

  2. 同学们根据自己特征值站到对应位置

  3. 新同学(兴趣未知)站到某个位置

实验观察

  • k=1时:预测结果可能被单个“怪邻居”带偏

  • k=3时:预测更加稳定可靠

  • 改变距离度量:邻居人选可能发生变化

2.代码实战:水果分类器

下面用Python实现一个简单的k-NN水果分类器:

import numpy as np
from sklearn.neighbors import KNeighborsClassifier
import matplotlib.pyplot as plt
import matplotlib

# 配置中文字体(根据您的系统选择合适的方法)

# 方法1:使用系统自带的中文字体(Windows系统)
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

# 方法2:使用其他中文字体(如果SimHei不可用,可以尝试以下字体之一)
# plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']  # 微软雅黑
# plt.rcParams['font.sans-serif'] = ['KaiTi']  # 楷体
# plt.rcParams['font.sans-serif'] = ['FangSong']  # 仿宋
# plt.rcParams['font.sans-serif'] = ['STSong']  # 华文宋体(macOS)

# 方法3:指定字体文件路径(如果以上方法都不行)
# font_path = '/path/to/your/chinese/font.ttf'  # 替换为您的字体文件路径
# matplotlib.font_manager.FontProperties(fname=font_path)

# 1. 准备数据:水果的特征 [甜度, 酸度]
# 标签:0=苹果,1=橙子,2=香蕉
X = np.array([[3, 2],  # 苹果
              [2, 3],  # 苹果
              [1, 1],  # 苹果
              [8, 7],  # 橙子
              [9, 8],  # 橙子
              [7, 9],  # 橙子
              [5, 8],  # 香蕉
              [6, 7],  # 香蕉
              [4, 9]]) # 香蕉
y = np.array([0, 0, 0, 1, 1, 1, 2, 2, 2])

# 2. 创建k-NN分类器(选择k=3)
k = 3
knn = KNeighborsClassifier(n_neighbors=k)
knn.fit(X, y)  # 训练(其实就是记住这些数据)

# 3. 预测一个新水果
new_fruit = np.array([[4, 6]])  # 甜度4,酸度6
prediction = knn.predict(new_fruit)
fruit_names = ['苹果', '橙子', '香蕉']
print(f"这个水果很可能是:{fruit_names[prediction[0]]}")

# 4. 查看它的邻居
distances, indices = knn.kneighbors(new_fruit)
print(f"它的{k}个邻居是:{[fruit_names[i] for i in y[indices[0]]]}")

# 5. 可视化展示
plt.figure(figsize=(8, 6))
colors = ['red', 'orange', 'yellow']
for i in range(3):
    plt.scatter(X[y==i, 0], X[y==i, 1],
                c=colors[i], label=fruit_names[i], s=100)
plt.scatter(new_fruit[0, 0], new_fruit[0, 1],
           c='green', marker='*', s=300, label='新水果')
plt.xlabel('甜度')
plt.ylabel('酸度')
plt.legend()
plt.title(f'k-NN水果分类(k={k})')
plt.grid(True)
plt.show()

这段代码使用机器学习中的k-最近邻(k-NN)算法实现了一个水果分类器。首先配置了matplotlib的中文字体支持,确保图表中能够正确显示中文标签。

然后创建了一个包含三种水果(苹果、橙子、香蕉)的数据集,每个水果用两个特征表示:甜度和酸度。

接着初始化一个k值为3的k-NN分类器,并用数据集训练模型(实际上只是存储数据)。代码对一个新的未知水果(甜度4,酸度6)进行预测,通过查找其最近的3个邻居来确定该水果的类别,并输出预测结果。

最后,将所有水果数据点和新水果可视化展示在一个二维散点图中,用不同颜色区分各类水果,直观演示了k-NN算法的工作原理和分类效果。

动手尝试

  1. 改变k值(1, 5, 7),观察预测结果变化

  2. 修改新水果的甜度酸度,看分类如何变化

  3. 添加新的训练数据,观察决策边界如何调整

五、k-NN的优缺点:何时使用?

1.优点:k-NN的“高光时刻”

  1. 直观易懂:没有复杂的数学公式,适合入门

  2. 无需训练:属于“懒惰学习”,只需存储数据

  3. 对异常值不敏感:当k足够大时,个别异常点影响有限

  4. 自然处理多分类:不像有些算法只能处理二分类问题

适用场景

  • 推荐系统:“喜欢这部电影的人也喜欢...”

  • 手写数字识别:这个字和哪个标准字最像?

  • 医疗诊断:症状与哪些病例最相似?

2.缺点:k-NN的“软肋”

  1. 计算成本高:每次预测都需要计算与所有训练样本的距离

  2. 维度灾难:特征太多时,所有点都“差不多远”

  3. 需要特征缩放:不同特征尺度差异会影响距离计算

  4. 对不平衡数据敏感:多数类会主导预测结果

六、k-NN与其他算法的对比

算法思维方式特点
k-NN局部思维,案例比较简单直观,无需训练
逻辑回归全局思维,概率判断寻找全局最优决策边界
决策树规则思维,分层判断像“20个问题”游戏

如果没有学习过逻辑回归和决策树的读者,欢迎阅读我之前写过的文章:

(1)决策树算法
https://blog.csdn.net/2303_77568009/article/details/155527635?spm=1001.2014.3001.5501

(2)逻辑回归算法

https://blog.csdn.net/2303_77568009/article/details/155580110?spm=1001.2014.3001.5501

七、总结:k-NN的哲学智慧

一句话概括:看看你周围都是什么人,你就是什么人。

算法三要素

  1. k值:平衡个性与共识的参考范围

  2. 距离度量:定义“相似性”的标准

  3. 决策规则:整合邻居意见的方式(投票或平均)

八、思考与延伸

  1. 思考题:如果让你用k-NN做音乐推荐,你会选择哪些特征来衡量两首歌的“距离”?

  2. 实际应用:k-NN在信用卡欺诈检测中的应用——新交易与哪些欺诈交易最相似?

九、项目实战:k-NN在信用卡欺诈检测中的应用——新交易与哪些欺诈交易最相似?

1.项目概述

本项目将使用k-最近邻(k-NN)算法来检测信用卡欺诈交易。通过分析交易的特征,找出与已知欺诈交易最相似的新交易,从而识别潜在的欺诈行为。

2.环境配置

  • IDE: PyCharm

  • Python版本: 3.10

  • 主要库: scikit-learn, pandas, numpy, matplotlib, seaborn

3.实现步骤

3.1导入必要的库

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
from sklearn.model_selection import GridSearchCV
from collections import Counter
import warnings
warnings.filterwarnings('ignore')

# 设置中文字体(可选)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

这段代码导入了项目所需的所有Python库:

  1. numpy用于数值计算
  2. pandas用于数据处理
  3. matplotlib和seaborn用于数据可视化
  4. scikit-learn中的各种模块用于机器学习任务(包括k-NN算法、数据预处理、模型评估等)
  5. collections和warnings用于辅助功能

最后还配置了中文字体以确保图表能正确显示中文。

3.2 加载和探索信用卡交易数据集

# 由于真实的信用卡欺诈数据集通常很大,这里我们使用一个样本数据集
# 在实际应用中,可以使用Kaggle上的信用卡欺诈检测数据集
# https://www.kaggle.com/mlg-ulb/creditcardfraud

def load_credit_card_data():
    """
    加载信用卡交易数据集
    这里我们创建一个模拟数据集,实际应用中应使用真实数据
    """
    # 设置随机种子以确保可重复性
    np.random.seed(42)
    
    # 创建模拟数据
    n_samples = 10000
    n_fraud = int(n_samples * 0.01)  # 假设欺诈交易占1%
    
    # 创建特征:交易金额、时间、V1-V28(匿名特征,实际数据中由PCA处理得到)
    n_features = 30
    X = np.random.randn(n_samples, n_features)
    
    # 欺诈交易通常有异常特征值,我们模拟这一点
    # 将欺诈交易的特征值调整得更极端
    fraud_indices = np.random.choice(n_samples, n_fraud, replace=False)
    for idx in fraud_indices:
        # 随机选择几个特征,给它们赋予极端的值
        extreme_features = np.random.choice(n_features, 5, replace=False)
        X[idx, extreme_features] += np.random.choice([-3, 3], 5) * np.random.rand(5)
    
    # 创建标签:0=正常交易,1=欺诈交易
    y = np.zeros(n_samples)
    y[fraud_indices] = 1
    
    # 添加一些更有意义的特征名称
    feature_names = ['Amount', 'Time'] + [f'V{i}' for i in range(1, 29)]
    
    # 创建DataFrame
    df = pd.DataFrame(X, columns=feature_names)
    df['Class'] = y
    
    return df

# 加载数据
df = load_credit_card_data()
print("数据集形状:", df.shape)
print("\n数据前5行:")
print(df.head())
print("\n类别分布:")
print(df['Class'].value_counts())
print(f"欺诈交易比例: {df['Class'].value_counts()[1]/len(df)*100:.2f}%")

这里定义了一个函数来创建模拟的信用卡交易数据集。

由于真实信用卡欺诈数据不易获取且通常很大,代码生成了包含10000条记录的模拟数据,其中欺诈交易约占1%。每条交易有30个特征(包括交易金额、时间和28个匿名特征),并通过给欺诈交易的某些特征添加极端值来模拟异常行为。

最后展示了数据集的基本信息和类别分布。

3.3数据预处理

def preprocess_data(df):
    """
    数据预处理:处理不平衡数据、特征缩放、划分数据集
    """
    # 分离特征和标签
    X = df.drop('Class', axis=1)
    y = df['Class']
    
    # 处理数据不平衡问题 - 使用过采样方法
    from imblearn.over_sampling import SMOTE
    
    # 查看原始数据分布
    print("原始数据类别分布:", Counter(y))
    
    # 使用SMOTE过采样
    smote = SMOTE(random_state=42)
    X_resampled, y_resampled = smote.fit_resample(X, y)
    print("过采样后类别分布:", Counter(y_resampled))
    
    # 划分训练集和测试集
    X_train, X_test, y_train, y_test = train_test_split(
        X_resampled, y_resampled, test_size=0.3, random_state=42, stratify=y_resampled
    )
    
    # 特征标准化(对k-NN非常重要)
    scaler = StandardScaler()
    X_train_scaled = scaler.fit_transform(X_train)
    X_test_scaled = scaler.transform(X_test)
    
    return X_train_scaled, X_test_scaled, y_train, y_test, scaler

# 数据预处理
X_train, X_test, y_train, y_test, scaler = preprocess_data(df)
print(f"\n训练集大小: {X_train.shape}")
print(f"测试集大小: {X_test.shape}")

这段代码处理数据不平衡问题并进行数据准备。

首先分离特征和标签,然后使用SMOTE过采样技术增加欺诈交易的样本数量,解决类别不平衡问题。接着将数据划分为训练集和测试集(70%训练,30%测试),并对特征进行标准化处理,这对k-NN算法非常重要,因为距离计算对特征尺度敏感。

3.4训练k-NN模型并进行预测

def train_knn_model(X_train, y_train, X_test, y_test):
    """
    训练k-NN模型并评估
    """
    # 创建k-NN分类器
    knn = KNeighborsClassifier(n_neighbors=5, weights='distance', metric='euclidean')
    
    # 训练模型
    knn.fit(X_train, y_train)
    
    # 预测
    y_pred = knn.predict(X_test)
    
    # 评估模型
    print("模型评估:")
    print("准确率:", accuracy_score(y_test, y_pred))
    print("\n分类报告:")
    print(classification_report(y_test, y_pred))
    
    # 混淆矩阵
    cm = confusion_matrix(y_test, y_pred)
    plt.figure(figsize=(8, 6))
    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', 
                xticklabels=['正常', '欺诈'], 
                yticklabels=['正常', '欺诈'])
    plt.title('混淆矩阵')
    plt.ylabel('真实标签')
    plt.xlabel('预测标签')
    plt.show()
    
    return knn, y_pred

# 训练模型
knn_model, y_pred = train_knn_model(X_train, y_train, X_test, y_test)

这里训练了一个k-NN分类器模型并进行评估。

创建一个k值为5的k-NN分类器,使用距离加权和欧氏距离度量。用训练数据训练模型后,在测试集上进行预测,然后计算准确率、生成分类报告和混淆矩阵。

混淆矩阵用热图可视化,直观展示模型对正常交易和欺诈交易的分类情况。

3.5寻找最佳k值

def find_best_k(X_train, y_train, X_test, y_test):
    """
    通过网格搜索寻找最佳k值
    """
    # 定义参数网格
    param_grid = {
        'n_neighbors': [3, 5, 7, 9, 11, 13],
        'weights': ['uniform', 'distance'],
        'metric': ['euclidean', 'manhattan', 'minkowski']
    }
    
    # 创建k-NN分类器
    knn = KNeighborsClassifier()
    
    # 网格搜索
    grid_search = GridSearchCV(knn, param_grid, cv=5, scoring='f1', n_jobs=-1, verbose=1)
    grid_search.fit(X_train, y_train)
    
    # 输出最佳参数
    print("最佳参数:", grid_search.best_params_)
    print("最佳交叉验证分数 (F1):", grid_search.best_score_)
    
    # 使用最佳参数重新训练模型
    best_knn = grid_search.best_estimator_
    y_pred_best = best_knn.predict(X_test)
    
    print("\n最佳模型在测试集上的表现:")
    print("分类报告:")
    print(classification_report(y_test, y_pred_best))
    
    # 可视化不同k值的表现
    k_values = param_grid['n_neighbors']
    mean_scores = []
    
    for k in k_values:
        knn_temp = KNeighborsClassifier(n_neighbors=k, weights='distance', metric='euclidean')
        knn_temp.fit(X_train, y_train)
        y_pred_temp = knn_temp.predict(X_test)
        f1 = classification_report(y_test, y_pred_temp, output_dict=True)['weighted avg']['f1-score']
        mean_scores.append(f1)
    
    plt.figure(figsize=(10, 6))
    plt.plot(k_values, mean_scores, marker='o', linestyle='-', linewidth=2)
    plt.title('不同k值的F1分数')
    plt.xlabel('k值')
    plt.ylabel('F1分数')
    plt.grid(True)
    plt.show()
    
    return best_knn

# 寻找最佳k值
best_knn_model = find_best_k(X_train, y_train, X_test, y_test)

通过网格搜索寻找k-NN的最佳超参数。

定义了包含不同k值、权重函数和距离度量的参数网格,使用5折交叉验证和F1分数作为评估指标进行网格搜索。

找到最佳参数后重新训练模型,并可视化不同k值对应的模型表现(F1分数曲线),帮助理解k值选择对模型性能的影响。

3.6欺诈检测核心功能:查找相似交易

class FraudDetector:
    """
    欺诈检测器类
    """
    def __init__(self, model, scaler, fraud_indices=None):
        self.model = model
        self.scaler = scaler
        self.fraud_indices = fraud_indices if fraud_indices is not None else []
    
    def find_similar_transactions(self, new_transaction, k=5):
        """
        查找与新交易最相似的k个交易
        """
        # 标准化新交易
        new_transaction_scaled = self.scaler.transform(new_transaction.reshape(1, -1))
        
        # 查找k个最近邻
        distances, indices = self.model.kneighbors(new_transaction_scaled, n_neighbors=k)
        
        # 获取邻居的标签和距离
        neighbor_labels = self.model.classes_[self.model._y[indices[0]]]
        neighbor_distances = distances[0]
        
        # 计算相似度(距离的倒数)
        similarities = 1 / (1 + neighbor_distances)
        
        return indices[0], neighbor_labels, neighbor_distances, similarities
    
    def predict_with_explanation(self, new_transaction):
        """
        预测新交易是否为欺诈,并提供解释
        """
        # 预测
        new_transaction_scaled = self.scaler.transform(new_transaction.reshape(1, -1))
        prediction = self.model.predict(new_transaction_scaled)[0]
        prediction_proba = self.model.predict_proba(new_transaction_scaled)[0]
        
        # 查找相似交易
        indices, labels, distances, similarities = self.find_similar_transactions(new_transaction)
        
        # 计算相似交易中欺诈交易的比例
        fraud_count = np.sum(labels == 1)
        fraud_ratio = fraud_count / len(labels)
        
        # 生成解释
        explanation = self._generate_explanation(prediction, prediction_proba, 
                                                fraud_count, fraud_ratio, distances, labels)
        
        return prediction, prediction_proba, explanation, indices, labels, distances, similarities
    
    def _generate_explanation(self, prediction, proba, fraud_count, fraud_ratio, distances, labels):
        """
        生成解释文本
        """
        if prediction == 1:
            result = "欺诈交易"
        else:
            result = "正常交易"
        
        explanation = f"""
        检测结果: {result}
        
        详细信息:
        - 欺诈概率: {proba[1]*100:.2f}%
        - 正常概率: {proba[0]*100:.2f}%
        - 最近5个邻居中欺诈交易数量: {fraud_count}
        - 最近邻居中欺诈交易比例: {fraud_ratio*100:.2f}%
        
        决策依据:
        该交易与{len(labels)}个历史交易最相似。
        """
        
        # 添加邻居的详细信息
        explanation += "\n最相似的交易详情:\n"
        for i, (label, distance) in enumerate(zip(labels, distances)):
            label_text = "欺诈" if label == 1 else "正常"
            explanation += f"  邻居{i+1}: {label_text}交易, 距离: {distance:.4f}\n"
        
        # 添加判断逻辑
        if fraud_ratio > 0.5:
            explanation += "\n判断逻辑: 大多数相似交易都是欺诈交易,因此被标记为欺诈。"
        else:
            explanation += "\n判断逻辑: 大多数相似交易都是正常交易,因此被标记为正常。"
        
        return explanation

实现了一个FraudDetector类,封装了欺诈检测的核心功能。主要方法包括:find_similar_transactions查找与新交易最相似的k个历史交易,返回邻居的索引、标签、距离和相似度;predict_with_explanation不仅预测新交易是否为欺诈,还提供详细的解释,包括预测概率、相似交易中欺诈交易的比例以及具体的判断依据。

3.7模拟新交易并检测欺诈

def simulate_new_transactions(df, n_transactions=5):
    """
    模拟新交易进行测试
    """
    # 获取特征名称
    feature_names = df.columns.tolist()[:-1]  # 排除'Class'列
    
    # 创建欺诈检测器
    # 首先需要获取训练数据中的欺诈交易索引
    fraud_indices_train = np.where(y_train == 1)[0]
    fraud_detector = FraudDetector(knn_model, scaler, fraud_indices_train)
    
    # 模拟新交易
    print("模拟信用卡交易欺诈检测...")
    print("="*60)
    
    for i in range(n_transactions):
        print(f"\n交易 #{i+1}:")
        print("-"*40)
        
        # 随机生成新交易(可以调整参数使其更像欺诈交易)
        if i < 2:
            # 前两个模拟为正常交易
            new_transaction = np.random.randn(len(feature_names))
        else:
            # 后三个模拟为欺诈交易(特征值更极端)
            new_transaction = np.random.randn(len(feature_names))
            # 随机选择一些特征赋予极端值
            extreme_features = np.random.choice(len(feature_names), 8, replace=False)
            new_transaction[extreme_features] += np.random.choice([-4, 4], 8)
        
        # 检测欺诈
        prediction, proba, explanation, indices, labels, distances, similarities = \
            fraud_detector.predict_with_explanation(new_transaction)
        
        # 显示交易特征(只显示前5个)
        print("交易特征 (前5个):")
        for j in range(min(5, len(feature_names))):
            print(f"  {feature_names[j]}: {new_transaction[j]:.4f}")
        
        # 显示检测结果
        print(f"\n检测结果: {' 欺诈交易' if prediction == 1 else ' 正常交易'}")
        print(f"置信度: {max(proba)*100:.2f}%")
        
        # 显示解释
        print("\n" + explanation)
        print("="*60)
    
    return fraud_detector

# 模拟交易检测
fraud_detector = simulate_new_transactions(df, n_transactions=5)

模拟5笔新的信用卡交易来测试欺诈检测系统。

前两笔模拟正常交易,后三笔通过添加极端特征值模拟欺诈交易。

对每笔交易调用FraudDetector进行检测,展示交易特征、检测结果(包括是否欺诈和置信度)以及详细的解释说明,直观演示系统如何工作并提供可解释的检测结果。

3.8可视化分析

def visualize_fraud_detection(df, fraud_detector):
    """
    可视化欺诈检测结果
    """
    # 1. 特征重要性分析(对于k-NN,我们可以通过查看欺诈交易的特征分布来了解)
    fraud_data = df[df['Class'] == 1]
    normal_data = df[df['Class'] == 0]
    
    # 选择几个重要特征进行可视化
    features_to_plot = ['Amount', 'V1', 'V2', 'V3', 'V4', 'V7']
    
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    axes = axes.ravel()
    
    for i, feature in enumerate(features_to_plot):
        axes[i].hist(normal_data[feature].values, bins=50, alpha=0.5, label='正常', color='blue')
        axes[i].hist(fraud_data[feature].values, bins=50, alpha=0.5, label='欺诈', color='red')
        axes[i].set_title(f'{feature} 分布')
        axes[i].set_xlabel(feature)
        axes[i].set_ylabel('频率')
        axes[i].legend()
        axes[i].grid(True, alpha=0.3)
    
    plt.suptitle('欺诈交易与正常交易特征分布对比')
    plt.tight_layout()
    plt.show()
    
    # 2. 距离分布可视化
    # 模拟一些正常和欺诈交易,查看它们的最近邻距离
    print("\n分析正常交易和欺诈交易的距离特征...")
    
    # 从测试集中选取样本
    normal_samples = X_test[y_test == 0][:10]
    fraud_samples = X_test[y_test == 1][:10]
    
    normal_distances = []
    fraud_distances = []
    
    for sample in normal_samples:
        distances, _ = fraud_detector.model.kneighbors(sample.reshape(1, -1), n_neighbors=5)
        normal_distances.extend(distances[0])
    
    for sample in fraud_samples:
        distances, _ = fraud_detector.model.kneighbors(sample.reshape(1, -1), n_neighbors=5)
        fraud_distances.extend(distances[0])
    
    # 绘制距离分布
    plt.figure(figsize=(10, 6))
    plt.hist(normal_distances, bins=30, alpha=0.5, label='正常交易距离', color='blue')
    plt.hist(fraud_distances, bins=30, alpha=0.5, label='欺诈交易距离', color='red')
    plt.title('正常交易与欺诈交易的最近邻距离分布')
    plt.xlabel('与最近邻居的距离')
    plt.ylabel('频率')
    plt.legend()
    plt.grid(True, alpha=0.3)
    plt.show()
    
    # 3. 欺诈交易聚集可视化(使用前两个主成分)
    from sklearn.decomposition import PCA
    
    pca = PCA(n_components=2)
    X_pca = pca.fit_transform(X_test)
    
    plt.figure(figsize=(10, 8))
    scatter = plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y_test, cmap='coolwarm', alpha=0.6)
    plt.colorbar(scatter, label='类别 (0=正常, 1=欺诈)')
    plt.title('信用卡交易PCA降维可视化 (前两个主成分)')
    plt.xlabel('第一主成分')
    plt.ylabel('第二主成分')
    plt.grid(True, alpha=0.3)
    plt.show()

# 运行可视化
visualize_fraud_detection(df, fraud_detector)

提供多种可视化来深入理解数据和模型。

包括:欺诈交易与正常交易在关键特征上的分布对比直方图;正常交易与欺诈交易的最近邻距离分布对比;使用PCA降维将高维交易数据投影到二维平面进行可视化,展示欺诈交易和正常交易在特征空间中的分布模式。

这些可视化帮助理解欺诈交易的特征模式和模型的工作原理。

3.9项目源代码

# ============================
# 1. 环境配置与库导入
# ============================

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score, roc_curve, auc, \
    precision_recall_curve
from sklearn.decomposition import PCA
from imblearn.over_sampling import SMOTE
from collections import Counter
import warnings
import joblib
import os
from datetime import datetime
import time

# 设置中文字体支持(针对Windows系统)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 忽略警告信息
warnings.filterwarnings('ignore')

# 设置随机种子确保结果可重现
np.random.seed(42)


# ============================
# 2. 数据生成与加载模块
# ============================

class CreditCardDataGenerator:
    """
    生成模拟信用卡交易数据
    特征包括:交易金额、时间、V1-V28(匿名特征,由PCA处理得到)
    """

    def __init__(self, n_samples=10000, fraud_ratio=0.01):
        """
        初始化数据生成器

        参数:
        n_samples: 总样本数
        fraud_ratio: 欺诈交易比例
        """
        self.n_samples = n_samples
        self.fraud_ratio = fraud_ratio
        self.n_features = 30  # 30个特征
        self.feature_names = ['Amount', 'Time'] + [f'V{i}' for i in range(1, 29)]

    def generate_data(self):
        """
        生成模拟数据

        返回:
        DataFrame: 包含特征和标签的数据
        """
        print("正在生成模拟信用卡交易数据...")

        # 生成基础数据(正态分布)
        X = np.random.randn(self.n_samples, self.n_features)

        # 计算欺诈交易数量
        n_fraud = int(self.n_samples * self.fraud_ratio)
        fraud_indices = np.random.choice(self.n_samples, n_fraud, replace=False)

        # 为欺诈交易添加异常特征
        print(f"正在为 {n_fraud} 条欺诈交易添加异常特征...")
        for idx in fraud_indices:
            # 随机选择5个特征并赋予极端值
            extreme_features = np.random.choice(self.n_features, 5, replace=False)
            for feature in extreme_features:
                # 随机选择增加或减少极端值
                direction = np.random.choice([-1, 1])
                extreme_value = 3 + np.random.rand() * 2  # 3-5倍标准差
                X[idx, feature] += direction * extreme_value

        # 生成标签
        y = np.zeros(self.n_samples)
        y[fraud_indices] = 1

        # 创建DataFrame
        df = pd.DataFrame(X, columns=self.feature_names)
        df['Class'] = y

        # 添加一些更有意义的特征值
        # 交易金额应该为正数
        df['Amount'] = np.abs(df['Amount']) * 100  # 金额在0-300左右

        # 时间特征(0-24小时)
        df['Time'] = np.abs(df['Time']) * 12 + 6  # 6-18小时

        print(f"数据生成完成,共 {self.n_samples} 条记录,其中欺诈交易 {n_fraud} 条")
        return df

    def save_data(self, df, filepath='data/credit_card_transactions.csv'):
        """
        保存数据到CSV文件

        参数:
        df: 数据DataFrame
        filepath: 文件保存路径
        """
        # 创建目录(如果不存在)
        os.makedirs(os.path.dirname(filepath), exist_ok=True)

        # 保存数据
        df.to_csv(filepath, index=False)
        print(f"\n数据已保存到: {filepath}")
        print(f"数据形状: {df.shape}")
        print(f"欺诈交易比例: {df['Class'].mean() * 100:.2f}%")

    def load_data(self, filepath='data/credit_card_transactions.csv'):
        """
        从CSV文件加载数据

        参数:
        filepath: 文件路径

        返回:
        DataFrame: 加载的数据
        """
        if os.path.exists(filepath):
            print(f"从 {filepath} 加载数据...")
            df = pd.read_csv(filepath)
            print(f"数据加载成功: {df.shape}")
            return df
        else:
            print(f"文件 {filepath} 不存在,正在生成新数据...")
            df = self.generate_data()
            self.save_data(df, filepath)
            return df


def explore_data(df):
    """
    探索性数据分析

    参数:
    df: 包含交易数据的DataFrame
    """
    print("\n" + "=" * 60)
    print("数据探索分析")
    print("=" * 60)

    # 基本信息
    print("\n1. 数据基本信息:")
    print(f"数据集形状: {df.shape}")
    print(f"特征数量: {len(df.columns) - 1}")  # 减去标签列

    # 数据类型
    print("\n2. 数据类型:")
    print(df.dtypes)

    # 缺失值检查
    print("\n3. 缺失值统计:")
    missing_values = df.isnull().sum()
    missing_percent = (missing_values / len(df)) * 100
    missing_df = pd.DataFrame({
        '缺失值数量': missing_values,
        '缺失值比例(%)': missing_percent
    })
    missing_data = missing_df[missing_df['缺失值数量'] > 0]
    if len(missing_data) > 0:
        print(missing_data)
    else:
        print("无缺失值")

    # 类别分布
    print("\n4. 交易类别分布:")
    class_counts = df['Class'].value_counts()
    class_percent = df['Class'].value_counts(normalize=True) * 100
    class_df = pd.DataFrame({
        '数量': class_counts,
        '比例(%)': class_percent
    })
    class_df.index = class_df.index.map({0: '正常交易', 1: '欺诈交易'})
    print(class_df)

    # 特征统计
    print("\n5. 前5个特征的统计信息:")
    print(df[['Amount', 'Time', 'V1', 'V2', 'V3']].describe().T.round(3))

    # 可视化类别分布
    plt.figure(figsize=(12, 5))

    plt.subplot(1, 2, 1)
    colors = ['lightblue', 'lightcoral']
    plt.pie(class_counts.values, labels=class_df.index, autopct='%1.1f%%',
            colors=colors, startangle=90, explode=(0, 0.1))
    plt.title('交易类别分布')

    plt.subplot(1, 2, 2)
    bars = plt.bar(class_df.index, class_counts.values, color=colors)
    plt.title('交易类别数量')
    plt.ylabel('交易数量')

    # 在柱状图上显示数量
    for bar, count in zip(bars, class_counts.values):
        plt.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.1,
                 str(count), ha='center', va='bottom')

    plt.tight_layout()
    plt.show()

    return df


# ============================
# 3. 数据预处理模块
# ============================

class DataPreprocessor:
    """
    数据预处理类
    处理数据不平衡、特征标准化、数据集划分
    """

    def __init__(self):
        """初始化预处理器"""
        self.scaler = StandardScaler()
        self.smote = SMOTE(random_state=42)

    def preprocess(self, df):
        """
        完整的数据预处理流程

        参数:
        df: 原始数据DataFrame

        返回:
        tuple: 处理后的训练和测试数据
        """
        print("\n" + "=" * 60)
        print("数据预处理")
        print("=" * 60)

        # 分离特征和标签
        X = df.drop('Class', axis=1)
        y = df['Class']

        # 1. 检查数据不平衡
        print(f"\n1. 原始数据类别分布:")
        original_dist = Counter(y)
        print(f"   正常交易: {original_dist[0]}")
        print(f"   欺诈交易: {original_dist[1]}")
        print(f"   欺诈比例: {original_dist[1] / len(y) * 100:.2f}%")

        # 2. 处理数据不平衡(使用SMOTE)
        print("\n2. 处理数据不平衡(使用SMOTE过采样)...")
        X_resampled, y_resampled = self.smote.fit_resample(X, y)
        resampled_dist = Counter(y_resampled)
        print(f"   过采样后正常交易: {resampled_dist[0]}")
        print(f"   过采样后欺诈交易: {resampled_dist[1]}")
        print(f"   过采样后欺诈比例: {resampled_dist[1] / len(y_resampled) * 100:.2f}%")

        # 3. 划分训练集和测试集
        print("\n3. 划分训练集和测试集...")
        X_train, X_test, y_train, y_test = train_test_split(
            X_resampled, y_resampled, test_size=0.3, random_state=42, stratify=y_resampled
        )
        print(f"   训练集大小: {X_train.shape}")
        print(f"   测试集大小: {X_test.shape}")

        # 4. 特征标准化
        print("\n4. 特征标准化(对k-NN算法非常重要)...")
        X_train_scaled = self.scaler.fit_transform(X_train)
        X_test_scaled = self.scaler.transform(X_test)
        print("   特征标准化完成")

        # 5. 特征选择(使用PCA降维,可选)
        print("\n5. 特征降维(PCA)...")
        pca = PCA(n_components=0.95)  # 保留95%的方差
        X_train_pca = pca.fit_transform(X_train_scaled)
        X_test_pca = pca.transform(X_test_scaled)
        print(f"   原始特征维度: {X_train_scaled.shape[1]}")
        print(f"   PCA降维后维度: {X_train_pca.shape[1]}")
        print(f"   保留方差比例: {pca.explained_variance_ratio_.sum():.4f}")

        return X_train_pca, X_test_pca, y_train, y_test, self.scaler, pca

    def save_preprocessor(self, filepath='models/preprocessor.pkl'):
        """
        保存预处理对象

        参数:
        filepath: 保存路径
        """
        os.makedirs(os.path.dirname(filepath), exist_ok=True)
        joblib.dump({
            'scaler': self.scaler,
            'smote': self.smote
        }, filepath)
        print(f"预处理器已保存到: {filepath}")


# ============================
# 4. k-NN模型训练模块
# ============================

class KNNModelTrainer:
    """
    k-NN模型训练器
    """

    def __init__(self, n_neighbors=5, weights='distance', metric='euclidean'):
        """
        初始化模型训练器

        参数:
        n_neighbors: k值
        weights: 权重函数
        metric: 距离度量
        """
        self.n_neighbors = n_neighbors
        self.weights = weights
        self.metric = metric
        self.model = KNeighborsClassifier(
            n_neighbors=n_neighbors,
            weights=weights,
            metric=metric
        )
        self.best_model = None
        self.grid_search = None

    def train(self, X_train, y_train):
        """
        训练模型

        参数:
        X_train: 训练特征
        y_train: 训练标签

        返回:
        训练好的模型
        """
        print("\n" + "=" * 60)
        print("训练k-NN模型")
        print("=" * 60)

        print(f"模型参数: k={self.n_neighbors}, weights={self.weights}, metric={self.metric}")

        start_time = time.time()
        self.model.fit(X_train, y_train)
        training_time = time.time() - start_time

        print(f"模型训练完成,耗时: {training_time:.2f}秒")

        return self.model

    def evaluate(self, model, X_test, y_test):
        """
        评估模型

        参数:
        model: 训练好的模型
        X_test: 测试特征
        y_test: 测试标签
        """
        print("\n模型评估:")

        # 预测
        y_pred = model.predict(X_test)
        y_pred_proba = model.predict_proba(X_test)[:, 1]

        # 计算评估指标
        accuracy = accuracy_score(y_test, y_pred)
        report = classification_report(y_test, y_pred, target_names=['正常', '欺诈'])

        print(f"准确率: {accuracy:.4f}")
        print("\n分类报告:")
        print(report)

        # 混淆矩阵
        cm = confusion_matrix(y_test, y_pred)

        # 可视化评估结果
        self._visualize_evaluation(y_test, y_pred, y_pred_proba, cm)

        return y_pred, y_pred_proba

    def _visualize_evaluation(self, y_test, y_pred, y_pred_proba, cm):
        """
        可视化评估结果

        参数:
        y_test: 真实标签
        y_pred: 预测标签
        y_pred_proba: 预测概率
        cm: 混淆矩阵
        """
        fig, axes = plt.subplots(2, 2, figsize=(14, 12))

        # 1. 混淆矩阵
        ax1 = axes[0, 0]
        sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
                    xticklabels=['正常', '欺诈'],
                    yticklabels=['正常', '欺诈'],
                    ax=ax1)
        ax1.set_title('混淆矩阵')
        ax1.set_ylabel('真实标签')
        ax1.set_xlabel('预测标签')

        # 2. ROC曲线
        ax2 = axes[0, 1]
        fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba)
        roc_auc = auc(fpr, tpr)

        ax2.plot(fpr, tpr, color='darkorange', lw=2,
                 label=f'ROC曲线 (AUC = {roc_auc:.2f})')
        ax2.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--', label='随机分类器')
        ax2.set_xlim([0.0, 1.0])
        ax2.set_ylim([0.0, 1.05])
        ax2.set_xlabel('假正率')
        ax2.set_ylabel('真正率')
        ax2.set_title('ROC曲线')
        ax2.legend(loc="lower right")
        ax2.grid(True, alpha=0.3)

        # 3. 精确率-召回率曲线
        ax3 = axes[1, 0]
        precision, recall, _ = precision_recall_curve(y_test, y_pred_proba)

        ax3.plot(recall, precision, color='darkgreen', lw=2)
        ax3.set_xlabel('召回率')
        ax3.set_ylabel('精确率')
        ax3.set_title('精确率-召回率曲线')
        ax3.set_xlim([0.0, 1.0])
        ax3.set_ylim([0.0, 1.05])
        ax3.grid(True, alpha=0.3)

        # 4. 预测概率分布
        ax4 = axes[1, 1]
        fraud_probs = y_pred_proba[y_test == 1]
        normal_probs = y_pred_proba[y_test == 0]

        ax4.hist(normal_probs, bins=30, alpha=0.7, label='正常交易', color='blue')
        ax4.hist(fraud_probs, bins=30, alpha=0.7, label='欺诈交易', color='red')
        ax4.set_xlabel('欺诈预测概率')
        ax4.set_ylabel('频率')
        ax4.set_title('预测概率分布')
        ax4.legend()
        ax4.grid(True, alpha=0.3)

        plt.tight_layout()
        plt.show()

        print(f"\n模型性能总结:")
        print(f"ROC曲线下面积 (AUC): {roc_auc:.4f}")
        print(f"混淆矩阵:\n{cm}")

    def find_best_k(self, X_train, y_train, X_test, y_test):
        """
        通过网格搜索寻找最佳k值

        参数:
        X_train: 训练特征
        y_train: 训练标签
        X_test: 测试特征
        y_test: 测试标签

        返回:
        最佳模型
        """
        print("\n" + "=" * 60)
        print("寻找最佳k值(网格搜索)")
        print("=" * 60)

        # 定义参数网格
        param_grid = {
            'n_neighbors': [3, 5, 7, 9, 11, 13, 15],
            'weights': ['uniform', 'distance'],
            'metric': ['euclidean', 'manhattan', 'minkowski']
        }

        print("参数网格:")
        for param, values in param_grid.items():
            print(f"  {param}: {values}")

        # 创建k-NN分类器
        knn = KNeighborsClassifier()

        # 网格搜索
        print("\n开始网格搜索(这可能需要一些时间)...")
        start_time = time.time()

        self.grid_search = GridSearchCV(
            knn, param_grid, cv=5, scoring='f1', n_jobs=-1, verbose=1
        )
        self.grid_search.fit(X_train, y_train)

        search_time = time.time() - start_time
        print(f"网格搜索完成,耗时: {search_time:.2f}秒")

        # 输出最佳参数
        print(f"\n最佳参数: {self.grid_search.best_params_}")
        print(f"最佳交叉验证分数 (F1): {self.grid_search.best_score_:.4f}")

        # 使用最佳参数重新训练模型
        self.best_model = self.grid_search.best_estimator_

        # 评估最佳模型
        print("\n最佳模型在测试集上的表现:")
        y_pred_best, y_pred_proba_best = self.evaluate(self.best_model, X_test, y_test)

        # 可视化不同k值的表现
        self._visualize_k_performance(X_train, y_train, X_test, y_test)

        return self.best_model

    def _visualize_k_performance(self, X_train, y_train, X_test, y_test):
        """
        可视化不同k值的表现

        参数:
        X_train: 训练特征
        y_train: 训练标签
        X_test: 测试特征
        y_test: 测试标签
        """
        # 测试不同的k值
        k_values = [3, 5, 7, 9, 11, 13, 15]
        train_scores = []
        test_scores = []

        print("\n测试不同k值的表现...")
        for k in k_values:
            knn_temp = KNeighborsClassifier(n_neighbors=k, weights='distance', metric='euclidean')
            knn_temp.fit(X_train, y_train)

            # 训练集分数
            train_score = knn_temp.score(X_train, y_train)
            train_scores.append(train_score)

            # 测试集分数
            test_score = knn_temp.score(X_test, y_test)
            test_scores.append(test_score)

            print(f"  k={k}: 训练集准确率={train_score:.4f}, 测试集准确率={test_score:.4f}")

        # 可视化
        plt.figure(figsize=(10, 6))
        plt.plot(k_values, train_scores, 'o-', linewidth=2, markersize=8, label='训练集准确率')
        plt.plot(k_values, test_scores, 's-', linewidth=2, markersize=8, label='测试集准确率')
        plt.title('不同k值对模型性能的影响')
        plt.xlabel('k值')
        plt.ylabel('准确率')
        plt.xticks(k_values)
        plt.legend()
        plt.grid(True, alpha=0.3)
        plt.show()

        # 找到最佳k值
        best_k_index = np.argmax(test_scores)
        best_k = k_values[best_k_index]
        best_score = test_scores[best_k_index]
        print(f"\n根据测试集表现,最佳k值为: {best_k} (准确率: {best_score:.4f})")

    def save_model(self, model, filepath='models/knn_model.pkl'):
        """
        保存训练好的模型

        参数:
        model: 训练好的模型
        filepath: 保存路径
        """
        os.makedirs(os.path.dirname(filepath), exist_ok=True)
        joblib.dump(model, filepath)
        print(f"模型已保存到: {filepath}")


# ============================
# 5. 欺诈检测器模块
# ============================

class FraudDetector:
    """
    欺诈检测器类
    封装欺诈检测的核心功能
    """

    def __init__(self, model, scaler, pca=None):
        """
        初始化欺诈检测器

        参数:
        model: 训练好的k-NN模型
        scaler: 标准化器
        pca: PCA降维器(可选)
        """
        self.model = model
        self.scaler = scaler
        self.pca = pca

    def preprocess_new_transaction(self, transaction):
        """
        预处理新交易

        参数:
        transaction: 新交易特征

        返回:
        预处理后的特征
        """
        # 确保是二维数组
        if len(transaction.shape) == 1:
            transaction = transaction.reshape(1, -1)

        # 标准化
        transaction_scaled = self.scaler.transform(transaction)

        # PCA降维(如果使用了PCA)
        if self.pca is not None:
            transaction_scaled = self.pca.transform(transaction_scaled)

        return transaction_scaled

    def find_similar_transactions(self, new_transaction, k=5):
        """
        查找与新交易最相似的k个交易

        参数:
        new_transaction: 新交易特征
        k: 邻居数量

        返回:
        tuple: (邻居索引, 邻居标签, 邻居距离, 相似度)
        """
        # 预处理新交易
        new_transaction_scaled = self.preprocess_new_transaction(new_transaction)

        # 查找k个最近邻
        distances, indices = self.model.kneighbors(new_transaction_scaled, n_neighbors=k)

        # 获取邻居的标签和距离
        # 注意:这里我们需要从训练数据中获取邻居的标签
        # 由于模型已经存储了训练数据,我们可以直接使用
        neighbor_labels = self.model.predict(new_transaction_scaled)

        # 实际上,我们需要获取每个邻居的具体标签
        # 这里我们使用模型的内部数据结构
        if hasattr(self.model, '_y'):
            neighbor_labels = self.model._y[indices[0]]
        else:
            # 如果无法直接获取,使用模型预测每个邻居的标签
            neighbor_labels = []
            for idx in indices[0]:
                # 这里需要原始训练数据,实际情况中可能无法直接获取
                # 作为演示,我们假设可以获取
                neighbor_labels.append(0)  # 默认值

        # 计算相似度(距离的倒数)
        similarities = 1 / (1 + distances[0])

        return indices[0], neighbor_labels, distances[0], similarities

    def detect_fraud(self, new_transaction, threshold=0.5):
        """
        检测交易是否为欺诈

        参数:
        new_transaction: 新交易特征
        threshold: 欺诈阈值

        返回:
        tuple: (是否为欺诈, 欺诈概率, 解释信息)
        """
        # 预处理新交易
        new_transaction_scaled = self.preprocess_new_transaction(new_transaction)

        # 预测
        prediction = self.model.predict(new_transaction_scaled)[0]
        prediction_proba = self.model.predict_proba(new_transaction_scaled)[0]

        # 查找相似交易
        indices, labels, distances, similarities = self.find_similar_transactions(new_transaction)

        # 生成解释信息
        explanation = self._generate_explanation(
            prediction, prediction_proba, labels, distances, threshold
        )

        return prediction, prediction_proba, explanation, indices, labels, distances

    def _generate_explanation(self, prediction, proba, labels, distances, threshold):
        """
        生成解释信息

        参数:
        prediction: 预测结果
        proba: 预测概率
        labels: 邻居标签
        distances: 邻居距离
        threshold: 欺诈阈值

        返回:
        解释信息字符串
        """
        fraud_count = np.sum(labels == 1)
        total_neighbors = len(labels)
        fraud_ratio = fraud_count / total_neighbors

        # 平均距离
        avg_distance = np.mean(distances)

        # 生成解释
        explanation = f"""
        ===== 欺诈检测结果 =====

        检测结果: {' 欺诈交易' if prediction == 1 else ' 正常交易'}

        详细统计:
        - 欺诈概率: {proba[1] * 100:.2f}%
        - 正常概率: {proba[0] * 100:.2f}%
        - 欺诈阈值: {threshold * 100:.2f}%

        邻居分析:
        - 最近 {total_neighbors} 个邻居中,{fraud_count} 个是欺诈交易
        - 欺诈交易比例: {fraud_ratio * 100:.2f}%
        - 平均邻居距离: {avg_distance:.4f}

        决策依据:
        """

        if prediction == 1:
            if fraud_ratio > 0.5:
                explanation += "大多数相似交易都是欺诈交易,因此被标记为欺诈。"
            elif proba[1] > threshold:
                explanation += f"欺诈概率 ({proba[1] * 100:.2f}%) 超过阈值 ({threshold * 100:.2f}%),因此被标记为欺诈。"
            else:
                explanation += "基于k-NN投票结果被标记为欺诈。"
        else:
            if fraud_ratio < 0.3:
                explanation += "大多数相似交易都是正常交易,因此被标记为正常。"
            elif proba[1] < threshold:
                explanation += f"欺诈概率 ({proba[1] * 100:.2f}%) 低于阈值 ({threshold * 100:.2f}%),因此被标记为正常。"
            else:
                explanation += "基于k-NN投票结果被标记为正常。"

        # 添加邻居详细信息
        explanation += f"\n\n最近邻居详情 (共{total_neighbors}个):\n"
        for i, (label, distance) in enumerate(zip(labels, distances)):
            label_text = "欺诈" if label == 1 else "正常"
            similarity = 1 / (1 + distance)  # 相似度
            explanation += f"  邻居{i + 1}: {label_text}交易, 距离: {distance:.4f}, 相似度: {similarity:.4f}\n"

        return explanation

    def batch_detect(self, transactions):
        """
        批量检测交易

        参数:
        transactions: 交易特征数组

        返回:
        DataFrame: 检测结果
        """
        print(f"批量检测 {len(transactions)} 笔交易...")

        results = []
        for i, transaction in enumerate(transactions):
            is_fraud, proba, _, _, _, _ = self.detect_fraud(transaction)
            results.append({
                '交易ID': i + 1,
                '欺诈概率': proba[1],
                '预测结果': '欺诈' if is_fraud == 1 else '正常',
                '建议': '需要人工审核' if is_fraud == 1 else '可自动通过'
            })

            # 显示进度
            if (i + 1) % 10 == 0:
                print(f"  已处理 {i + 1}/{len(transactions)} 笔交易")

        # 创建结果DataFrame
        results_df = pd.DataFrame(results)

        # 统计结果
        fraud_count = results_df[results_df['预测结果'] == '欺诈'].shape[0]
        print(f"\n批量检测完成:")
        print(f"  总交易数: {len(transactions)}")
        print(f"  疑似欺诈交易: {fraud_count} ({fraud_count / len(transactions) * 100:.2f}%)")

        return results_df


# ============================
# 6. 模拟交易测试模块
# ============================

def simulate_transactions(n_transactions=10, feature_dim=30):
    """
    模拟新交易进行测试

    参数:
    n_transactions: 交易数量
    feature_dim: 特征维度

    返回:
    模拟交易数组
    """
    print(f"\n模拟 {n_transactions} 笔新交易...")

    transactions = []
    transaction_types = []

    for i in range(n_transactions):
        # 随机生成基础特征
        transaction = np.random.randn(feature_dim)

        # 根据交易类型调整特征
        if i < n_transactions * 0.3:  # 30%为欺诈交易
            # 欺诈交易:添加极端特征值
            extreme_features = np.random.choice(feature_dim, 8, replace=False)
            for feature in extreme_features:
                direction = np.random.choice([-1, 1])
                extreme_value = 4 + np.random.rand() * 3  # 4-7倍标准差
                transaction[feature] += direction * extreme_value
            transaction_types.append('欺诈')
        else:
            # 正常交易
            transaction_types.append('正常')

        transactions.append(transaction)

    print(f"模拟完成: {transaction_types.count('欺诈')} 笔欺诈交易, "
          f"{transaction_types.count('正常')} 笔正常交易")

    return np.array(transactions), transaction_types


def test_fraud_detector(fraud_detector, n_transactions=10):
    """
    测试欺诈检测器

    参数:
    fraud_detector: 欺诈检测器实例
    n_transactions: 测试交易数量
    """
    print("\n" + "=" * 60)
    print("测试欺诈检测器")
    print("=" * 60)

    # 模拟新交易
    transactions, true_types = simulate_transactions(n_transactions)

    # 逐笔检测
    print("\n逐笔检测结果:")
    print("-" * 50)

    correct_predictions = 0
    for i, (transaction, true_type) in enumerate(zip(transactions, true_types)):
        print(f"\n交易 #{i + 1} (真实类型: {true_type}):")

        # 显示前5个特征
        print(f"  特征示例: Amount={transaction[0]:.2f}, Time={transaction[1]:.2f}, "
              f"V1={transaction[2]:.2f}, V2={transaction[3]:.2f}, V3={transaction[4]:.2f}")

        # 检测欺诈
        is_fraud, proba, explanation, _, _, _ = fraud_detector.detect_fraud(transaction)
        predicted_type = '欺诈' if is_fraud == 1 else '正常'

        print(f"  预测结果: {predicted_type} (欺诈概率: {proba[1] * 100:.2f}%)")

        # 检查预测是否正确
        is_correct = (true_type == '欺诈' and is_fraud == 1) or (true_type == '正常' and is_fraud == 0)
        if is_correct:
            correct_predictions += 1
            print(f"  结果:  正确")
        else:
            print(f"  结果:  错误")

        # 显示简要解释
        lines = explanation.split('\n')
        for line in lines[:10]:  # 只显示前10行
            if line.strip():
                print(f"  {line}")

    # 计算准确率
    accuracy = correct_predictions / n_transactions
    print(f"\n" + "=" * 50)
    print(f"测试总结: {correct_predictions}/{n_transactions} 正确, 准确率: {accuracy * 100:.2f}%")
    print("=" * 50)

    # 批量检测
    print("\n批量检测演示:")
    batch_results = fraud_detector.batch_detect(transactions)

    # 显示批量检测结果
    print("\n批量检测结果摘要:")
    print(batch_results.head())

    # 可视化批量检测结果
    plt.figure(figsize=(10, 6))

    # 欺诈概率分布
    plt.subplot(1, 2, 1)
    fraud_probs = batch_results[batch_results['预测结果'] == '欺诈']['欺诈概率']
    normal_probs = batch_results[batch_results['预测结果'] == '正常']['欺诈概率']

    if len(fraud_probs) > 0:
        plt.hist(fraud_probs, bins=10, alpha=0.7, label='预测为欺诈', color='red')
    if len(normal_probs) > 0:
        plt.hist(normal_probs, bins=10, alpha=0.7, label='预测为正常', color='green')

    plt.xlabel('欺诈概率')
    plt.ylabel('交易数量')
    plt.title('欺诈概率分布')
    plt.legend()
    plt.grid(True, alpha=0.3)

    # 预测结果分布
    plt.subplot(1, 2, 2)
    result_counts = batch_results['预测结果'].value_counts()
    colors = ['green', 'red'] if '正常' in result_counts.index else ['red', 'green']
    plt.pie(result_counts.values, labels=result_counts.index, autopct='%1.1f%%',
            colors=colors, startangle=90, explode=[0.1 if i == 0 else 0 for i in range(len(result_counts))])
    plt.title('预测结果分布')

    plt.tight_layout()
    plt.show()

    return batch_results


# ============================
# 7. 可视化分析模块(修复版)
# ============================

def visualize_analysis(df, fraud_detector, X_test, y_test):
    """
    可视化分析

    参数:
    df: 原始数据
    fraud_detector: 欺诈检测器
    X_test: 测试特征(PCA降维后的)
    y_test: 测试标签
    """
    print("\n" + "=" * 60)
    print("可视化分析")
    print("=" * 60)

    # 1. 特征分布对比(使用原始数据)
    print("\n1. 欺诈交易与正常交易特征分布对比...")
    fraud_data = df[df['Class'] == 1]
    normal_data = df[df['Class'] == 0]

    # 选择重要特征
    important_features = ['Amount', 'Time', 'V1', 'V2', 'V3', 'V4', 'V7', 'V10', 'V14', 'V17']

    fig, axes = plt.subplots(5, 2, figsize=(15, 20))
    axes = axes.ravel()

    for i, feature in enumerate(important_features[:10]):
        axes[i].hist(normal_data[feature].values, bins=50, alpha=0.5, label='正常交易', color='blue', density=True)
        axes[i].hist(fraud_data[feature].values, bins=50, alpha=0.5, label='欺诈交易', color='red', density=True)
        axes[i].set_title(f'{feature} 分布')
        axes[i].set_xlabel(feature)
        axes[i].set_ylabel('密度')
        axes[i].legend()
        axes[i].grid(True, alpha=0.3)

    plt.suptitle('欺诈交易与正常交易特征分布对比', fontsize=16)
    plt.tight_layout()
    plt.show()

    # 2. 距离分析(使用PCA降维后的数据)
    print("\n2. 正常交易与欺诈交易的最近邻距离分析...")

    # 从测试集中选取样本(已经是PCA降维后的数据)
    normal_samples = X_test[y_test == 0][:20]
    fraud_samples = X_test[y_test == 1][:20]

    normal_distances = []
    fraud_distances = []

    # 使用模型直接计算距离,不需要通过欺诈检测器
    model = fraud_detector.model

    for sample in normal_samples:
        # 直接使用模型的kneighbors方法,因为X_test已经是预处理后的数据
        distances, _ = model.kneighbors(sample.reshape(1, -1), n_neighbors=5)
        normal_distances.extend(distances[0])

    for sample in fraud_samples:
        distances, _ = model.kneighbors(sample.reshape(1, -1), n_neighbors=5)
        fraud_distances.extend(distances[0])

    # 绘制距离分布
    plt.figure(figsize=(12, 5))

    plt.subplot(1, 2, 1)
    plt.hist(normal_distances, bins=30, alpha=0.5, label='正常交易', color='blue', density=True)
    plt.hist(fraud_distances, bins=30, alpha=0.5, label='欺诈交易', color='red', density=True)
    plt.title('最近邻距离分布')
    plt.xlabel('与最近邻居的距离')
    plt.ylabel('密度')
    plt.legend()
    plt.grid(True, alpha=0.3)

    plt.subplot(1, 2, 2)
    box_data = [normal_distances, fraud_distances]
    plt.boxplot(box_data, labels=['正常交易', '欺诈交易'])
    plt.title('最近邻距离箱线图')
    plt.ylabel('距离')
    plt.grid(True, alpha=0.3)

    plt.tight_layout()
    plt.show()

    # 3. PCA降维可视化(使用PCA降维后的数据)
    print("\n3. PCA降维可视化...")

    # 使用PCA将数据降维到2维
    pca_2d = PCA(n_components=2)
    X_test_2d = pca_2d.fit_transform(X_test)

    plt.figure(figsize=(10, 8))
    scatter = plt.scatter(X_test_2d[:, 0], X_test_2d[:, 1], c=y_test,
                          cmap='coolwarm', alpha=0.6, s=30)
    plt.colorbar(scatter, label='类别 (0=正常, 1=欺诈)')
    plt.title('信用卡交易PCA降维可视化 (前两个主成分)')
    plt.xlabel('第一主成分')
    plt.ylabel('第二主成分')
    plt.grid(True, alpha=0.3)

    # 添加一些新交易的投影
    new_transactions, _ = simulate_transactions(5, df.shape[1] - 1)  # 减去标签列

    for i, transaction in enumerate(new_transactions):
        # 使用欺诈检测器检测
        is_fraud, _, _, _, _, _ = fraud_detector.detect_fraud(transaction)

        # 对transaction进行预处理
        transaction_scaled = fraud_detector.preprocess_new_transaction(transaction)

        # PCA降维到2维
        transaction_2d = pca_2d.transform(transaction_scaled)

        # 绘制
        color = 'red' if is_fraud == 1 else 'green'
        marker = 'X' if is_fraud == 1 else 'o'
        plt.scatter(transaction_2d[:, 0], transaction_2d[:, 1],
                    color=color, marker=marker, s=200,
                    label=f'新交易{i + 1} ({"欺诈" if is_fraud == 1 else "正常"})')

    plt.legend(loc='upper right')
    plt.show()

    # 4. 特征重要性分析(基于原始特征)
    print("\n4. 特征重要性分析...")

    # 从测试集中选取一个样本,需要重新创建原始特征
    # 我们需要原始数据的一个测试样本
    from sklearn.model_selection import train_test_split

    # 分离特征和标签
    X_original = df.drop('Class', axis=1).values
    y_original = df['Class'].values

    # 划分训练集和测试集(为了获取原始特征)
    _, X_test_original, _, _ = train_test_split(
        X_original, y_original, test_size=0.3, random_state=42
    )

    # 取第一个样本
    sample_transaction = X_test_original[0]

    # 使用欺诈检测器
    _, _, _, _, _, distances = fraud_detector.detect_fraud(sample_transaction)

    # 为了演示,我们创建一个简单的特征重要性分析
    # 在实际应用中,可以使用更复杂的方法
    feature_importance = np.abs(sample_transaction)
    feature_importance = feature_importance / feature_importance.sum()

    # 取前10个最重要的特征
    top_indices = np.argsort(feature_importance)[-10:][::-1]
    top_features = [df.columns[i] for i in top_indices if i < len(df.columns) - 1]
    top_importance = feature_importance[top_indices[:len(top_features)]]

    plt.figure(figsize=(10, 6))
    bars = plt.barh(range(len(top_features)), top_importance)
    plt.yticks(range(len(top_features)), top_features, fontsize=9)
    plt.xlabel('特征重要性')
    plt.title('特征重要性分析(基于当前样本)')
    plt.gca().invert_yaxis()  # 最重要的特征在最上面

    # 在条形上显示数值
    for bar, importance in zip(bars, top_importance):
        plt.text(bar.get_width() + 0.01, bar.get_y() + bar.get_height() / 2,
                 f'{importance:.4f}', va='center')

    plt.grid(True, alpha=0.3, axis='x')
    plt.tight_layout()
    plt.show()

    print("可视化分析完成!")


# ============================
# 8. 性能优化与部署模块
# ============================

def performance_tips():
    """
    提供性能优化建议
    """
    print("\n" + "=" * 60)
    print("性能优化与部署建议")
    print("=" * 60)

    tips = """
    k-NN在信用卡欺诈检测中的性能优化建议:

    1. 数据预处理优化:
       - 特征选择: 使用相关性分析或主成分分析(PCA)减少特征维度
       - 特征缩放: 必须进行标准化,欧氏距离对尺度敏感
       - 处理不平衡数据: 使用SMOTE、ADASYN或调整类别权重

    2. 算法优化:
       - 使用KD树或Ball树加速最近邻搜索
       - 调整k值: 通过交叉验证找到最佳k值,通常5-15之间
       - 使用加权投票: 距离越近的邻居权重越大
       - 选择合适的距离度量: 根据特征类型选择欧氏、曼哈顿或余弦距离

    3. 实时性能优化:
       - 使用近似最近邻(ANN)算法如Annoy、FAISS
       - 对交易进行聚类预处理,减少距离计算
       - 实现增量学习,定期更新模型

    4. 实际部署考虑:
       - 设置合适的欺诈阈值,平衡误报和漏报
       - 实现模型监控,跟踪模型性能随时间变化
       - 建立人工审核流程,处理模型不确定的交易
       - 定期重新训练模型,适应新的欺诈模式

    5. 与其他算法结合:
       - 使用集成学习结合k-NN和其他算法
       - 第一层用规则引擎过滤明显正常交易
       - 第二层用k-NN进行细粒度检测
       - 第三层用深度学习模型处理复杂模式
    """

    print(tips)

    # 模型保存示例
    print("\n模型保存与加载示例:")
    print("""
    # 保存模型
    import joblib

    # 保存完整检测器
    detector_data = {
        'model': knn_model,
        'scaler': scaler,
        'pca': pca
    }
    joblib.dump(detector_data, 'models/fraud_detector.pkl')

    # 加载模型
    loaded_data = joblib.load('models/fraud_detector.pkl')
    loaded_detector = FraudDetector(
        loaded_data['model'],
        loaded_data['scaler'],
        loaded_data['pca']
    )

    # 使用检测器
    new_transaction = np.random.randn(30)
    result = loaded_detector.detect_fraud(new_transaction)
    """)


# ============================
# 9. 主程序入口
# ============================

def main():
    """
    主函数:运行完整的信用卡欺诈检测流程
    """
    print("=" * 70)
    print("信用卡欺诈检测系统 - 基于k-NN算法")
    print("=" * 70)

    start_time = time.time()

    # 步骤1: 生成/加载数据
    print("\n步骤1: 数据准备")
    data_generator = CreditCardDataGenerator(n_samples=10000, fraud_ratio=0.01)
    df = data_generator.load_data()

    # 步骤2: 数据探索
    explore_data(df)

    # 步骤3: 数据预处理
    preprocessor = DataPreprocessor()
    X_train, X_test, y_train, y_test, scaler, pca = preprocessor.preprocess(df)

    # 步骤4: 训练基础模型
    trainer = KNNModelTrainer(n_neighbors=5, weights='distance', metric='euclidean')
    knn_model = trainer.train(X_train, y_train)

    # 步骤5: 评估基础模型
    print("\n步骤5: 评估基础模型")
    y_pred, y_pred_proba = trainer.evaluate(knn_model, X_test, y_test)

    # 步骤6: 寻找最佳k值
    print("\n步骤6: 优化模型参数")
    best_model = trainer.find_best_k(X_train, y_train, X_test, y_test)

    # 步骤7: 创建欺诈检测器
    print("\n步骤7: 创建欺诈检测器")
    fraud_detector = FraudDetector(best_model, scaler, pca)

    # 步骤8: 测试欺诈检测器
    batch_results = test_fraud_detector(fraud_detector, n_transactions=15)

    # 步骤9: 可视化分析
    visualize_analysis(df, fraud_detector, X_test, y_test)

    # 步骤10: 性能优化建议
    performance_tips()

    # 保存模型
    trainer.save_model(best_model, 'models/knn_fraud_detector.pkl')
    preprocessor.save_preprocessor('models/preprocessor.pkl')

    # 保存完整检测器
    detector_data = {
        'model': best_model,
        'scaler': scaler,
        'pca': pca
    }
    os.makedirs('models', exist_ok=True)
    joblib.dump(detector_data, 'models/fraud_detector_full.pkl')

    # 统计信息
    end_time = time.time()
    total_time = end_time - start_time

    print("\n" + "=" * 70)
    print("项目总结")
    print("=" * 70)
    print(f"总运行时间: {total_time:.2f}秒")
    print(f"数据集大小: {df.shape}")
    print(f"欺诈交易比例: {df['Class'].mean() * 100:.2f}%")
    print(f"训练集大小: {X_train.shape}")
    print(f"测试集大小: {X_test.shape}")
    print(f"最佳模型参数: {best_model.get_params()}")
    print(f"模型已保存到: models/knn_fraud_detector.pkl")
    print("=" * 70)

    return fraud_detector, df, batch_results


# ============================
# 10. 运行主程序
# ============================

if __name__ == "__main__":
    print("正在启动信用卡欺诈检测系统...")
    fraud_detector, df, results = main()

    print("\n系统已准备就绪,可以进行以下操作:")
    print("1. 使用 fraud_detector.detect_fraud(transaction) 检测单笔交易")
    print("2. 使用 fraud_detector.batch_detect(transactions) 批量检测")
    print("3. 查看结果: results 包含批量检测结果")
    print("4. 查看数据: df 包含原始数据")

    # 示例:检测一笔随机交易
    print("\n示例:检测一笔随机交易")
    random_transaction = np.random.randn(30)
    result = fraud_detector.detect_fraud(random_transaction)
    print(f"随机交易检测结果: {'欺诈' if result[0] == 1 else '正常'} "
          f"(概率: {result[1][1] * 100:.2f}%)")

4.运行结果

4.1图片1:交易类别分布图

生成时间:在数据探索分析阶段(explore_data函数中)
图片位置plt.figure(figsize=(12, 5)) 创建的第一个图形

功能介绍
这张图片采用左右并排的两个子图展示信用卡交易数据的类别分布情况:

  • 左侧饼图:直观显示正常交易和欺诈交易在总数据集中的比例。欺诈交易通常只占极小的部分(约1%),体现了信用卡欺诈检测的核心挑战——数据不平衡问题。

  • 右侧柱状图:用柱状形式展示两类交易的具体数量,并在柱顶标注精确数值。颜色区分明显(浅蓝代表正常交易,浅红代表欺诈交易)。

分析价值:快速理解数据集的基本特征,认识到处理不平衡数据的必要性。

4.2图片2:模型综合评估图

生成时间:在模型评估阶段(_visualize_evaluation方法中)
图片位置fig, axes = plt.subplots(2, 2, figsize=(14, 12)) 创建的复合图形

功能介绍
这张综合评估图包含4个子图,全方位评估k-NN模型的性能:

  1. 左上子图 - 混淆矩阵

    • 展示模型预测结果与真实标签的对比

    • 四个象限分别代表:真正例、假正例、真负例、假负例

    • 热图颜色深浅反映数量多少,帮助快速识别分类错误模式

  2. 右上子图 - ROC曲线

    • 展示模型在不同阈值下的真正率与假正率关系

    • 包含对角线作为随机分类器的基准

    • 计算AUC(曲线下面积)值,量化模型整体性能

    • AUC越接近1,模型区分能力越强

  3. 左下子图 - 精确率-召回率曲线

    • 展示在不同阈值下精确率与召回率的权衡关系

    • 特别适用于评估不平衡数据集上的模型性能

    • 帮助确定最佳阈值平衡点

  4. 右下子图 - 预测概率分布

    • 对比正常交易和欺诈交易的预测概率分布

    • 理想情况下,两类概率分布应该有明显分离

    • 重叠区域表示模型难以区分的交易

分析价值:这是模型性能诊断的核心工具,帮助全面理解模型的分类能力和局限性。

4.3图片3:k值选择分析图

生成时间:在寻找最佳k值阶段(_visualize_k_performance方法中)
图片位置plt.figure(figsize=(10, 6)) 创建的折线图

功能介绍
这张折线图展示不同k值对k-NN模型性能的影响:

  • X轴:不同的k值(3, 5, 7, 9, 11, 13, 15)

  • Y轴:模型准确率

  • 两条曲线

    • 训练集准确率(通常随k增大而下降)

    • 测试集准确率(通常先上升后下降,存在最优值)

分析价值

  • 直观展示k值与模型泛化能力的关系

  • 帮助确定避免过拟合和欠拟合的最佳k值

  • 训练集与测试集曲线分离程度反映模型过拟合情况

  • 通常选择测试集准确率最高且不过度复杂的k值

4.4图片4:批量检测结果分析图

生成时间:在测试欺诈检测器阶段(test_fraud_detector函数末尾)
图片位置plt.figure(figsize=(10, 6)) 创建的复合图形

功能介绍
这张图展示对新模拟交易批量检测的结果:

  1. 左侧子图 - 欺诈概率分布直方图

    • 按预测结果分类显示交易的欺诈概率分布

    • 预测为欺诈的交易(红色)通常具有较高的欺诈概率

    • 预测为正常的交易(绿色)通常具有较低的欺诈概率

    • 分布分离程度反映模型置信度

  2. 右侧子图 - 预测结果分布饼图

    • 展示被分类为正常和欺诈的交易比例

    • 使用不同颜色区分(绿色=正常,红色=欺诈)

    • 显示具体百分比,快速了解检测结果分布

分析价值

  • 评估模型在实际应用中的表现

  • 检查预测结果的分布是否合理

  • 验证模型对新数据的泛化能力

4.5图片5:特征分布对比图

生成时间:在可视化分析阶段(visualize_analysis函数开始部分)
图片位置fig, axes = plt.subplots(5, 2, figsize=(15, 20)) 创建的复合图形

功能介绍
这张大型复合图对比正常交易与欺诈交易在10个关键特征上的分布差异:

  • 包含特征:Amount, Time, V1, V2, V3, V4, V7, V10, V14, V17

  • 每张子图:显示一个特征的分布直方图,蓝色代表正常交易,红色代表欺诈交易

  • 使用密度图:消除样本数量差异的影响,专注于分布形状

分析价值

  • 识别哪些特征在欺诈交易中表现出异常分布

  • 帮助理解欺诈行为的特征模式

  • 为特征工程提供指导,可能发现区分两类交易的关键特征

  • V14, V17等匿名特征通常在实际欺诈数据中区分度较高

4.6图片6:最近邻距离分析图

生成时间:在可视化分析阶段(距离分析部分)
图片位置plt.figure(figsize=(12, 5)) 创建的复合图形

功能介绍
这张图分析正常交易与欺诈交易在特征空间中的聚集程度:

  1. 左侧子图 - 距离分布直方图

    • 对比正常交易和欺诈交易到其最近邻居的距离分布

    • 欺诈交易通常更"异常",距离可能更大

    • 正常交易可能更"普通",距离可能更小

  2. 右侧子图 - 距离箱线图

    • 用箱线图形式对比两类交易的距离分布

    • 显示中位数、四分位数和异常值

    • 更直观地比较分布的中心趋势和离散程度

分析价值

  • 验证k-NN算法的基本假设:相似样本在特征空间中距离相近

  • 了解欺诈交易的异常程度

  • 为距离阈值设置提供参考

4.7图片7:PCA降维可视化图

生成时间:在可视化分析阶段(PCA降维部分)
图片位置plt.figure(figsize=(10, 8)) 创建的散点图

功能介绍
这张图将高维交易数据投影到二维平面进行可视化:

  • 数据点:测试集中的交易,按真实类别着色(冷色=正常,暖色=欺诈)

  • 新交易:模拟的新交易用特殊标记(X=预测为欺诈,o=预测为正常)投影到同一空间

  • 颜色条:明确颜色与类别的对应关系

分析价值

  • 直观展示高维数据在主要方向上的分布

  • 观察正常交易和欺诈交易在降维空间中的分离程度

  • 查看新交易在特征空间中的位置,理解预测结果的几何意义

  • 验证PCA降维是否保留了区分两类交易的关键信息

4.8图片8:特征重要性分析图

生成时间:在可视化分析阶段(特征重要性分析部分)
图片位置plt.figure(figsize=(10, 6)) 创建的水平条形图

功能介绍
这张图展示基于一个样本的特征重要性排序:

  • Y轴:前10个最重要的特征名称

  • X轴:特征重要性值(基于特征绝对值的归一化)

  • 条形方向:水平排列,最重要的特征在最上方

  • 数值标注:在每个条形右侧显示精确的重要性值

分析价值

  • 识别对当前预测影响最大的特征

  • 理解k-NN决策的依据(哪些特征贡献了最大的距离)

  • 为特征选择和工程提供方向

  • 虽然这只是基于单个样本的分析,但可以反映特征的一般重要性模式

4.9小结

这8张图片构成了一个完整的机器学习项目可视化分析体系:

  1. 数据理解(图片1, 5) - 理解数据分布和特征

  2. 模型评估(图片2, 3) - 评估模型性能和参数选择

  3. 应用测试(图片4) - 验证模型在新数据上的表现

  4. 深入分析(图片6, 7, 8) - 探索模型工作原理和决策依据

每张图片都有明确的业务和机器学习分析目的,共同帮助读者理解信用卡欺诈检测系统的性能、局限性和工作原理。

5.项目总结

本项目演示了如何使用k-NN算法进行信用卡欺诈检测。主要步骤包括:

  1. 数据加载和探索:了解数据分布和特征

  2. 数据预处理:处理不平衡数据,标准化特征

  3. 模型训练:训练k-NN分类器,寻找最佳参数

  4. 欺诈检测:实现查找相似交易的功能,提供可解释的检测结果

  5. 可视化和分析:理解欺诈交易的特征和模式

  6. 性能优化:提供实际部署的建议

k-NN在欺诈检测中的优势在于其直观性和解释性——我们可以明确指出一个交易为什么被标记为欺诈(因为它与已知的欺诈交易相似)。然而,k-NN也有计算量大、对高维数据效果差等缺点,在实际应用中需要考虑这些因素并进行优化。

在实际项目中,建议结合其他算法(如孤立森林、随机森林或深度学习模型)来提高检测性能,并建立实时监控系统来跟踪模型表现。

十、总结

k-NN算法以其直观性和简单性,成为机器学习入门的最佳起点。它告诉我们:在数据科学中,有时最简单的思路反而最有效。正如生活中的智慧——“观其友,知其人”,通过观察一个对象的邻居,我们往往能对这个对象有深入的了解。

希望这篇博客能帮助你直观理解k-NN算法的核心思想。记住,最好的学习方法就是动手实践——尝试运行文中的代码,调整参数,观察变化,你会发现机器学习的世界比你想象的更有趣!

更多推荐