机器学习入门实战:使用Scikit-learn完成鸢尾花分类

在前面的文章中,我们已经掌握了使用Pandas进行数据分析的核心技巧。今天,我们将正式迈入机器学习的核心领域,通过一个完整的实战项目,带你从零到一掌握机器学习项目的标准流程——使用Scikit-learn库对经典的鸢尾花数据集进行分类。无论你是刚接触机器学习的新手,还是想巩固基础的学习者,这个项目都能帮你建立完整的技术认知。

项目概述

鸢尾花数据集是机器学习领域的“Hello World”,也是最经典的分类数据集之一。该数据集包含3种鸢尾花品种(Setosa山鸢尾、Versicolour变色鸢尾、Virginica维吉尼亚鸢尾)的各50个样本,总计150个样本。每个样本包含4个核心特征(萼片长度、萼片宽度、花瓣长度、花瓣宽度),我们的核心任务就是基于这4个特征,构建一个能够准确识别鸢尾花品种的机器学习模型。

完整实现步骤

机器学习项目的标准流程为:环境准备与数据加载→数据探索与可视化→数据预处理与划分→模型训练与评估→模型比较与选择→深入分析最佳模型→模型部署与预测→项目总结与扩展,我们将严格遵循这个流程逐步实现。

1. 环境准备与数据加载

首先确保本地环境已安装必要的库(若未安装,可通过pip install numpy pandas matplotlib seaborn scikit-learn命令安装),随后加载鸢尾花数据集并进行基础处理。

python
# 导入必要的库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import datasets
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score

# 解决matplotlib中文显示问题,设置图表样式
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示异常问题
sns.set_style("whitegrid")  # 设置图表网格样式
plt.rcParams['figure.figsize'] = (10, 6)  # 设置默认图表大小

# 加载鸢尾花数据集(Scikit-learn内置数据集,可直接调用)
iris = datasets.load_iris()
X = iris.data  # 特征矩阵:shape=(150,4),对应150个样本、4个特征
y = iris.target  # 目标变量(标签):shape=(150,),0/1/2分别对应三种鸢尾花品种
feature_names = iris.feature_names  # 特征名称:['萼片长度(cm)', '萼片宽度(cm)', '花瓣长度(cm)', '花瓣宽度(cm)']
target_names = iris.target_names  # 品种名称:['setosa', 'versicolor', 'virginica']

# 转换为DataFrame格式(便于后续数据分析和可视化)
df = pd.DataFrame(X, columns=feature_names)
df['species'] = y  # 添加数字标签列
# 映射数字标签为具体品种名称,提升可读性
df['species_name'] = df['species'].map({0: target_names[0], 1: target_names[1], 2: target_names[2]})

# 查看数据集基础信息
print("数据集形状:", X.shape)  # 输出:(150, 4)
print("特征名称:", feature_names)
print("目标类别:", target_names)
print("\n前5行数据:")
print(df.head())

2. 数据探索与可视化

在构建模型前,必须先深入理解数据——包括特征的分布情况、特征间的相关性、不同品种的特征差异等。数据探索是机器学习的基础,直接影响后续模型的选择和效果。

python
# 1. 数据基本统计分析(查看特征的均值、标准差、最值等)
print("数据基本统计信息:")
print(df.describe())  # 输出每个特征的统计指标

# 查看各类别样本数量(确认数据集是否平衡)
print("\n各类别样本数量:")
print(df['species_name'].value_counts())  # 输出:每种品种各50个样本,数据集平衡

# 2. 特征分布可视化(直方图:查看单个特征在不同品种中的分布)
fig, axes = plt.subplots(2, 2, figsize=(14, 10))  # 创建2行2列的子图
features = feature_names

for idx, feature in enumerate(features):
    row, col = idx // 2, idx % 2  # 计算当前特征对应的子图位置
    # 遍历每个品种,绘制直方图
    for species in target_names:
        species_data = df[df['species_name'] == species][feature]
        axes[row, col].hist(species_data, alpha=0.5, label=species, bins=15)
    
    axes[row, col].set_xlabel(feature)
    axes[row, col].set_ylabel('频数')
    axes[row, col].set_title(f'{feature}分布(按品种划分)')
    axes[row, col].legend()  # 显示图例
    axes[row, col].grid(alpha=0.3)  # 添加网格,提升可读性

plt.tight_layout()  # 自动调整子图间距,避免重叠
plt.show()

# 3. 特征间关系散点图矩阵(查看特征两两之间的相关性和品种聚类情况)
plt.figure(figsize=(12, 10))
# sns.pairplot:自动绘制所有特征两两之间的散点图,对角线为特征分布直方图
sns.pairplot(df, hue='species_name', diag_kind='hist', palette='husl')
plt.suptitle('鸢尾花特征散点图矩阵', y=1.02)  # 设置总标题,y=1.02避免与子图重叠
plt.show()

# 4. 特征相关性热图(量化特征间的线性相关程度)
plt.figure(figsize=(10, 8))
correlation_matrix = df[features].corr()  # 计算特征间的相关系数矩阵
# 绘制热图,annot=True显示相关系数数值,cmap='coolwarm'用冷暖色区分正负相关
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', center=0,
            square=True, linewidths=0.5, cbar_kws={"shrink": 0.8})
plt.title('特征相关性热图')
plt.tight_layout()
plt.show()

**探索结论参考**:从可视化结果可看出,Setosa品种的花瓣特征明显小于另外两个品种,容易区分;花瓣长度与花瓣宽度、萼片长度呈较强正相关,这为后续特征选择提供了参考。

3. 数据预处理与划分

数据预处理是提升模型效果的关键步骤,主要包括数据集划分(避免过拟合)和特征标准化(消除特征尺度差异的影响)。

python
# 1. 划分训练集和测试集(核心原则:训练集用于建模,测试集用于评估模型泛化能力)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)
# 参数说明:
# test_size=0.2:测试集占比20%,训练集占比80%
# random_state=42:固定随机种子,保证结果可复现
# stratify=y:按原数据集的类别比例划分,避免某类别在测试集中占比异常

# 查看划分后的数据规模和类别分布
print(f"训练集大小: {X_train.shape[0]} 个样本")  # 输出:120个样本
print(f"测试集大小: {X_test.shape[0]} 个样本")  # 输出:30个样本
print(f"训练集中各类别分布: {np.bincount(y_train)}")  # 输出:[40,40,40](按0/1/2顺序)
print(f"测试集中各类别分布: {np.bincount(y_test)}")  # 输出:[10,10,10]

# 2. 数据标准化(对逻辑回归、SVM等对特征尺度敏感的模型至关重要)
scaler = StandardScaler()  # 初始化标准化器(将特征转换为均值=0、方差=1)
X_train_scaled = scaler.fit_transform(X_train)  # 训练集:先拟合(计算均值和方差)再转换
X_test_scaled = scaler.transform(X_test)  # 测试集:仅用训练集的均值和方差转换,避免数据泄露

# 查看标准化后的结果
print("\n标准化后的训练集前5个样本:")
print(pd.DataFrame(X_train_scaled, columns=feature_names).head())

**关键提醒**:测试集必须完全独立于训练集,标准化时仅能使用训练集的统计信息(均值、方差),否则会导致数据泄露,高估模型泛化能力。

4. 模型训练与评估

我们将同时训练4种经典的分类模型(逻辑回归、支持向量机、决策树、随机森林),通过多个评估指标对比它们的性能。评估指标包括测试集准确率、5折交叉验证得分、分类报告(精确率、召回率、F1值)。

python
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier

# 1. 初始化4种分类模型
models = {
    '逻辑回归': LogisticRegression(random_state=42, max_iter=1000),  # max_iter=1000确保收敛
    '支持向量机': SVC(kernel='rbf', random_state=42, probability=True),  # probability=True启用概率预测
    '决策树': DecisionTreeClassifier(random_state=42),
    '随机森林': RandomForestClassifier(n_estimators=100, random_state=42)  # n_estimators=100:100棵决策树
}

# 2. 训练模型并评估性能,用字典保存结果
results = {}

for model_name, model in models.items():
    print(f"\n{'='*60}")
    print(f"开始训练 {model_name}...")
    
    # 训练模型(使用标准化后的训练集)
    model.fit(X_train_scaled, y_train)
    
    # 模型预测(使用标准化后的测试集)
    y_pred = model.predict(X_test_scaled)  # 类别预测
    # 若模型支持概率预测,则获取预测概率(用于后续更细致的分析)
    y_pred_proba = model.predict_proba(X_test_scaled) if hasattr(model, "predict_proba") else None
    
    # 3. 模型评估
    # 准确率:预测正确的样本数/总样本数
    accuracy = accuracy_score(y_test, y_pred)
    
    # 5折交叉验证:避免单次划分的偶然性,更稳定地评估模型泛化能力
    cv_scores = cross_val_score(model, X_train_scaled, y_train, cv=5)
    
    # 输出评估结果
    print(f"{model_name} - 测试集准确率: {accuracy:.4f}")
    print(f"{model_name} - 5折交叉验证平均得分: {cv_scores.mean():.4f} (+/- {cv_scores.std()*2:.4f})")
    # 注:cv_scores.std()*2表示95%置信区间,反映得分的波动程度
    
    # 保存模型和评估结果到字典
    results[model_name] = {
        'model': model,
        'accuracy': accuracy,
        'cv_mean': cv_scores.mean(),
        'cv_std': cv_scores.std(),
        'predictions': y_pred,
        'predictions_proba': y_pred_proba
    }
    
    # 输出分类报告(包含精确率、召回率、F1值,更全面评估模型性能)
    print(f"\n{model_name} 分类报告:")
    print(classification_report(y_test, y_pred, target_names=target_names))

**分类报告解读**:精确率(Precision)表示预测为某类的样本中实际为该类的比例;召回率(Recall)表示实际为某类的样本中被正确预测的比例;F1值是精确率和召回率的调和平均数,综合反映模型性能。对于平衡数据集,准确率可作为基础指标,但分类报告能更细致地发现模型在特定类别上的表现。

5. 模型比较与选择

通过整理各模型的评估结果,我们可以直观对比它们的性能,选择最优模型。选择原则:优先考虑交叉验证平均得分(更稳定),其次考虑测试集准确率,同时兼顾模型复杂度(简单模型泛化能力更强、易解释)。

python
# 1. 整理模型性能数据,转换为DataFrame便于查看
models_comparison = pd.DataFrame({
    '模型': list(results.keys()),
    '测试准确率': [results[m]['accuracy'] for m in results.keys()],
    '交叉验证平均得分': [results[m]['cv_mean'] for m in results.keys()],
    '交叉验证标准差': [results[m]['cv_std'] for m in results.keys()]
}).sort_values('测试准确率', ascending=False)  # 按测试准确率降序排序

# 输出模型性能对比表
print("模型性能比较表:")
print(models_comparison)

# 2. 可视化模型性能(更直观对比)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))  # 1行2列子图:准确率对比+交叉验证得分对比

# 子图1:测试集准确率对比(柱状图)
x_pos = np.arange(len(models_comparison))
ax1.bar(x_pos, models_comparison['测试准确率'], color=sns.color_palette("husl", len(models_comparison)))
ax1.set_xticks(x_pos)
ax1.set_xticklabels(models_comparison['模型'], rotation=45, ha='right')  # 旋转标签避免重叠
ax1.set_ylabel('准确率')
ax1.set_title('不同模型在测试集上的准确率')
ax1.set_ylim([0.8, 1.0])  # 限定y轴范围,突出差异

# 在柱状图上添加准确率数值标签
for i, v in enumerate(models_comparison['测试准确率']):
    ax1.text(i, v + 0.005, f'{v:.3f}', ha='center')

# 子图2:5折交叉验证得分对比(误差棒图,展示得分均值和波动)
ax2.errorbar(x_pos, models_comparison['交叉验证平均得分'],
             yerr=models_comparison['交叉验证标准差'],
             fmt='o', markersize=10, capsize=5, linewidth=2)
ax2.set_xticks(x_pos)
ax2.set_xticklabels(models_comparison['模型'], rotation=45, ha='right')
ax2.set_ylabel('交叉验证得分')
ax2.set_title('5折交叉验证得分(均值±标准差)')
ax2.set_ylim([0.8, 1.0])

plt.tight_layout()
plt.show()

# 3. 选择最佳模型(取测试准确率最高且交叉验证得分稳定的模型)
best_model_name = models_comparison.iloc[0]['模型']
best_model = results[best_model_name]['model']
print(f"\n本次实验最佳模型: {best_model_name}")
print(f"最佳模型测试准确率: {results[best_model_name]['accuracy']:.4f}")

**常见结果参考**:在鸢尾花数据集上,4种模型的性能通常都较好(准确率≥0.93),其中随机森林和支持向量机的表现可能略优,但逻辑回归作为简单模型,性能也接近,适合场景简单的部署。

6. 深入分析最佳模型

选定最佳模型后,我们需要进一步分析其性能细节:通过混淆矩阵查看模型在各类别上的错误分布,通过特征重要性(或特征系数)理解模型的决策依据,这有助于提升模型的可解释性。

python
# 1. 混淆矩阵分析(直观展示模型在各类别上的预测正确/错误情况)
print(f"\n{'='*60}")
print(f"对最佳模型 ({best_model_name}) 的深入分析")

best_predictions = results[best_model_name]['predictions']
cm = confusion_matrix(y_test, best_predictions)  # 计算混淆矩阵

# 绘制混淆矩阵热图
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
            xticklabels=target_names, yticklabels=target_names)
plt.xlabel('预测标签')
plt.ylabel('真实标签')
plt.title(f'{best_model_name} 混淆矩阵')
plt.show()

# 2. 特征重要性/系数分析(理解模型基于哪些特征做决策)
# 情况1:模型支持feature_importances_(如随机森林、决策树)
if hasattr(best_model, 'feature_importances_'):
    importances = best_model.feature_importances_  # 特征重要性得分(总和为1)
    indices = np.argsort(importances)[::-1]  # 对重要性得分降序排序,获取索引

    # 绘制特征重要性柱状图
    plt.figure(figsize=(10, 6))
    plt.bar(range(X.shape[1]), importances[indices], align='center')
    plt.xticks(range(X.shape[1]), [feature_names[i] for i in indices], rotation=45)
    plt.xlabel('特征')
    plt.ylabel('重要性得分')
    plt.title(f'{best_model_name} 特征重要性排序')
    plt.tight_layout()
    plt.show()
    
    # 输出特征重要性排序
    print("\n特征重要性排序:")
    for i, idx in enumerate(indices):
        print(f"{i+1}. {feature_names[idx]}: {importances[idx]:.4f}")

# 情况2:模型支持coef_(如逻辑回归等线性模型,系数正负表示特征对类别的影响方向)
elif hasattr(best_model, 'coef_'):
    # 处理多分类问题的系数(逻辑回归多分类时coef_ shape为(类别数, 特征数))
    if len(best_model.coef_.shape) > 1:
        coefficients = best_model.coef_.mean(axis=0)  # 取各类别系数的均值
    else:
        coefficients = best_model.coef_
    
    # 绘制特征系数柱状图
    plt.figure(figsize=(10, 6))
    plt.bar(range(len(coefficients)), coefficients)
    plt.xticks(range(len(coefficients)), feature_names, rotation=45)
    plt.xlabel('特征')
    plt.ylabel('系数值')
    plt.title(f'{best_model_name} 特征系数(正系数促进分类,负系数抑制分类)')
    plt.tight_layout()
    plt.show()

**分析结论参考**:混淆矩阵可看出模型是否在某一特定品种上易出错(如Versicolour和Virginica可能存在少量混淆);特征重要性通常显示花瓣长度和花瓣宽度是区分鸢尾花品种的核心特征,这与我们之前的探索结论一致。

7. 模型部署与预测

训练好的模型需要保存下来,以便后续在实际场景中调用。我们使用Scikit-learn推荐的joblib库保存模型(比pickle更适合保存机器学习模型,支持大型数组高效序列化),同时保存标准化器(新样本预测时需用相同的标准化规则)。

python
# 导入joblib库(用于保存/加载模型)
import joblib
import os

# 1. 保存模型和标准化器
# 创建模型保存目录(若不存在则创建)
model_dir = 'saved_models'
os.makedirs(model_dir, exist_ok=True)  # exist_ok=True避免目录已存在时报错

# 定义保存路径
model_path = os.path.join(model_dir, 'best_iris_model.joblib')
scaler_path = os.path.join(model_dir, 'iris_scaler.joblib')

# 保存最佳模型和标准化器
joblib.dump(best_model, model_path)
joblib.dump(scaler, scaler_path)

print(f"最佳模型已保存到: {model_path}")
print(f"标准化器已保存到: {scaler_path}")

# 2. 加载模型并进行新样本预测(模拟实际部署场景)
# 加载保存的模型和标准化器
loaded_model = joblib.load(model_path)
loaded_scaler = joblib.load(scaler_path)

# 构造3个新样本(模拟实际场景中输入的特征数据)
new_samples = np.array([
    [5.1, 3.5, 1.4, 0.2],  # 特征类似Setosa品种
    [6.7, 3.0, 5.2, 2.3],  # 特征类似Virginica品种
    [5.9, 2.8, 4.1, 1.3]   # 特征类似Versicolour品种
])

# 新样本必须使用与训练集相同的标准化规则(使用加载的标准化器)
new_samples_scaled = loaded_scaler.transform(new_samples)

# 模型预测
predictions = loaded_model.predict(new_samples_scaled)  # 类别预测
prediction_probas = loaded_model.predict_proba(new_samples_scaled)  # 各类别预测概率

# 输出预测结果
print("\n新样本预测结果:")
for i, (sample, pred, proba) in enumerate(zip(new_samples, predictions, prediction_probas)):
    print(f"\n样本 {i+1}: 特征值 = {sample}")
    print(f"预测品种: {target_names[pred]}")
    print(f"各类别预测概率: {dict(zip(target_names, proba.round(4)))}")  # 保留4位小数

**部署关键提醒**:实际部署时,需确保新样本的特征顺序、单位与训练集一致,且必须经过相同的标准化(或其他预处理)操作,否则会导致预测结果异常。

8. 项目总结与扩展建议

python
# 项目核心总结
print("=" * 60)
print("鸢尾花分类项目总结")
print("=" * 60)

print(f"1. 数据集信息: 鸢尾花数据集包含 {X.shape[0]} 个样本、{X.shape[1]} 个特征,共 {len(target_names)} 个品种")
print(f"2. 最佳模型: {best_model_name}")
print(f"3. 模型性能: 测试准确率 {results[best_model_name]['accuracy']:.4f},5折交叉验证平均得分 {results[best_model_name]['cv_mean']:.4f}")
print(f"4. 核心特征: 基于模型分析,{feature_names[indices[0]]} 和 {feature_names[indices[1]]} 是区分鸢尾花品种的关键特征")
print(f"5. 工程化成果: 模型和标准化器已保存至本地,可直接用于新样本预测")

# 下一步扩展建议(提升技能的关键方向)
print("\n进阶扩展建议:")
print("1. 尝试更多算法: 如K近邻(KNN)、朴素贝叶斯、XGBoost、LightGBM等,对比不同算法的性能")
print("2. 超参数调优: 使用GridSearchCV或RandomizedSearchCV对最佳模型进行超参数调优,进一步提升准确率")
print("3. 特征工程: 尝试创建新特征(如花瓣面积=花瓣长度×花瓣宽度、萼片长宽比等),或使用特征选择工具(如SelectKBest)筛选最优特征子集")
print("4. Web部署: 将模型封装为Web接口(使用Flask或Streamlit),实现浏览器输入特征、在线查看预测结果的功能")
print("5. 处理不平衡数据: 本数据集为平衡数据集,可尝试模拟不平衡场景(如删除某品种部分样本),学习SMOTE等不平衡数据处理方法")

# 生成并保存详细分析报告(便于后续回顾或分享)
analysis_report = f"""
鸢尾花分类项目分析报告
生成时间: {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')}

一、数据集基本信息
- 样本总数: {X.shape[0]} 个
- 特征数量: {X.shape[1]} 个({', '.join(feature_names)})
- 品种数量: {len(target_names)} 个({', '.join(target_names)})
- 数据集特点: 样本平衡,无缺失值

二、模型训练与评估结果
模型性能对比表:
{models_comparison.to_string()}

三、最佳模型详情
- 最佳模型名称: {best_model_name}
- 测试集准确率: {results[best_model_name]['accuracy']:.4f}
- 5折交叉验证平均得分: {results[best_model_name]['cv_mean']:.4f}
- 交叉验证标准差: {results[best_model_name]['cv_std']:.4f}

四、项目文件说明
- 最佳模型文件路径: {model_path}
- 标准化器文件路径: {scaler_path}
- 本分析报告文件路径: iris_analysis_report.txt

五、扩展建议
1. 尝试KNN、XGBoost等更多算法;2. 使用GridSearchCV进行超参数调优;3. 进行特征工程;4. 封装为Web应用;5. 学习不平衡数据处理方法。
"""

# 保存分析报告(编码设为utf-8,避免中文乱码)
with open('iris_analysis_report.txt', 'w', encoding='utf-8') as f:
    f.write(analysis_report)

print(f"\n详细分析报告已保存到: iris_analysis_report.txt")

关键知识点回顾

通过本项目,我们系统实践了机器学习项目的全流程,核心知识点包括:

  • 数据加载与探索:使用Pandas和Scikit-learn加载数据,通过describe()、value_counts()分析数据基本特征
  • 数据可视化:使用matplotlib和seaborn绘制直方图、散点图矩阵、相关性热图,直观理解数据
  • 数据预处理:训练/测试集划分(stratify参数保证类别平衡)、特征标准化(StandardScaler),避免数据泄露
  • 模型训练:掌握逻辑回归、SVM、决策树、随机森林的基本使用方法
  • 模型评估:准确率、交叉验证、分类报告(精确率/召回率/F1值)、混淆矩阵的解读与使用
  • 模型选择:基于多指标对比选择最优模型,兼顾性能和复杂度
  • 模型部署:使用joblib保存/加载模型和预处理工具,实现新样本预测

实践建议(提升技能的关键)

  1. 修改随机种子:尝试将random_state改为不同值(如0、10、20),观察模型性能是否波动,理解数据划分的随机性影响
  1. 调整测试集比例:将test_size改为0.1或0.3,对比模型性能变化,思考训练集/测试集比例的选择原则
  1. 添加新特征:尝试创建“花瓣面积”“萼片长宽比”等新特征,重新训练模型,观察是否提升准确率
  1. 超参数调优:以随机森林为例,使用GridSearchCV调整n_estimators、max_depth等参数,学习超参数调优的流程
  1. 模拟不平衡数据:删除Setosa品种的30个样本,使数据集不平衡,尝试使用SMOTE方法处理后再训练模型,对比处理前后的性能

本项目是机器学习入门的经典实战,掌握其流程和核心知识点后,你可以将相同的方法应用到其他分类问题(如手写数字识别、垃圾邮件分类等)中,逐步提升自己的机器学习实战能力。祝你学习顺利!

|(注:文档部分内容可能由 AI 生成)

更多推荐