← 返回首页
🤖

分类算法实践详解

📂 ai ⏱ 4 min 682 words

分类算法实践详解

分类是机器学习中最常见的任务之一,通过学习数据特征来预测类别标签。

垃圾邮件分类案例

数据准备

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.svm import SVC
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import (accuracy_score, precision_score, recall_score, 
                            f1_score, confusion_matrix, classification_report, 
                            roc_curve, auc)
import seaborn as sns

# 创建模拟邮件数据
np.random.seed(42)
n_samples = 1000

# 生成文本特征(模拟TF-IDF特征)
X_text = np.random.rand(n_samples, 100)  # 模拟100个词的TF-IDF特征

# 创建标签(0: 正常邮件, 1: 垃圾邮件)
y = np.random.choice([0, 1], size=n_samples, p=[0.7, 0.3])

# 添加一些模式使数据更真实
# 垃圾邮件通常包含更多某些特定词
spam_indices = np.where(y == 1)[0]
X_text[spam_indices, :20] *= 2  # 垃圾邮件前20个特征值更大

print(f"数据集大小: {X_text.shape}")
print(f"类别分布: {np.bincount(y)}")
print(f"垃圾邮件比例: {y.mean():.2%}")

数据探索

# 类别分布可视化
plt.figure(figsize=(8, 5))
class_counts = pd.Series(y).value_counts()
plt.bar(['正常邮件', '垃圾邮件'], class_counts.values, 
        color=['lightblue', 'lightcoral'], edgecolor='black')
plt.xlabel('邮件类型')
plt.ylabel('数量')
plt.title('邮件类别分布')
plt.grid(True, alpha=0.3)

# 添加数据标签
for i, v in enumerate(class_counts.values):
    plt.text(i, v + 5, str(v), ha='center', fontweight='bold')

plt.tight_layout()
plt.show()

# 特征分布比较
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

# 正常邮件特征分布
normal_features = X_text[y == 0].mean(axis=0)
axes[0].bar(range(len(normal_features)), normal_features, color='lightblue')
axes[0].set_xlabel('特征索引')
axes[0].set_ylabel('平均TF-IDF值')
axes[0].set_title('正常邮件特征分布')
axes[0].grid(True, alpha=0.3)

# 垃圾邮件特征分布
spam_features = X_text[y == 1].mean(axis=0)
axes[1].bar(range(len(spam_features)), spam_features, color='lightcoral')
axes[1].set_xlabel('特征索引')
axes[1].set_ylabel('平均TF-IDF值')
axes[1].set_title('垃圾邮件特征分布')
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

模型训练与评估

数据预处理

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
    X_text, y, test_size=0.2, random_state=42, stratify=y
)

print(f"训练集大小: {X_train.shape[0]}")
print(f"测试集大小: {X_test.shape[0]}")
print(f"训练集类别分布: {np.bincount(y_train)}")
print(f"测试集类别分布: {np.bincount(y_test)}")

# 特征缩放
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

多种分类模型比较

# 定义模型
models = {
    '逻辑回归': LogisticRegression(max_iter=1000, random_state=42),
    '随机森林': RandomForestClassifier(n_estimators=100, random_state=42),
    '梯度提升': GradientBoostingClassifier(n_estimators=100, random_state=42),
    'SVM': SVC(probability=True, random_state=42),
    '朴素贝叶斯': MultinomialNB()
}

# 训练和评估模型
results = {}
for name, model in models.items():
    # 对于朴素贝叶斯,不能使用负值
    if name == '朴素贝叶斯':
        # 使用非负特征
        model.fit(np.abs(X_train), y_train)
        y_pred = model.predict(np.abs(X_test))
        y_prob = model.predict_proba(np.abs(X_test))[:, 1]
    else:
        model.fit(X_train_scaled, y_train)
        y_pred = model.predict(X_test_scaled)
        y_prob = model.predict_proba(X_test_scaled)[:, 1]
    
    # 计算指标
    accuracy = accuracy_score(y_test, y_pred)
    precision = precision_score(y_test, y_pred)
    recall = recall_score(y_test, y_pred)
    f1 = f1_score(y_test, y_pred)
    roc_auc = auc(*roc_curve(y_test, y_prob)[:2])
    
    # 交叉验证
    if name == '朴素贝叶斯':
        cv_scores = cross_val_score(model, np.abs(X_text), y, cv=5, scoring='f1')
    else:
        cv_scores = cross_val_score(model, X_text, y, cv=5, scoring='f1')
    
    results[name] = {
        'accuracy': accuracy,
        'precision': precision,
        'recall': recall,
        'f1': f1,
        'roc_auc': roc_auc,
        'cv_f1': cv_scores.mean(),
        'cv_std': cv_scores.std()
    }
    
    print(f"\n{name}:")
    print(f"  准确率: {accuracy:.4f}")
    print(f"  精确率: {precision:.4f}")
    print(f"  召回率: {recall:.4f}")
    print(f"  F1分数: {f1:.4f}")
    print(f"  AUC-ROC: {roc_auc:.4f}")
    print(f"  交叉验证F1: {cv_scores.mean():.4f} (+/- {cv_scores.std()*2:.4f})")

模型性能可视化

# 可视化模型性能
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# 准确率比较
model_names = list(results.keys())
accuracy_scores = [results[name]['accuracy'] for name in model_names]
axes[0, 0].bar(model_names, accuracy_scores, color='skyblue', edgecolor='black')
axes[0, 0].set_ylabel('准确率')
axes[0, 0].set_title('模型准确率比较')
axes[0, 0].tick_params(axis='x', rotation=45)
axes[0, 0].grid(True, alpha=0.3)
axes[0, 0].set_ylim(0.5, 1.0)

# F1分数比较
f1_scores = [results[name]['f1'] for name in model_names]
axes[0, 1].bar(model_names, f1_scores, color='lightcoral', edgecolor='black')
axes[0, 1].set_ylabel('F1分数')
axes[0, 1].set_title('模型F1分数比较')
axes[0, 1].tick_params(axis='x', rotation=45)
axes[0, 1].grid(True, alpha=0.3)
axes[0, 1].set_ylim(0.5, 1.0)

# ROC曲线
for name in model_names:
    if name == '朴素贝叶斯':
        y_prob = models[name].predict_proba(np.abs(X_test))[:, 1]
    else:
        y_prob = models[name].predict_proba(X_test_scaled)[:, 1]
    
    fpr, tpr, _ = roc_curve(y_test, y_prob)
    roc_auc = auc(fpr, tpr)
    axes[1, 0].plot(fpr, tpr, label=f'{name} (AUC = {roc_auc:.2f})')

axes[1, 0].plot([0, 1], [0, 1], 'k--', label='随机分类器')
axes[1, 0].set_xlabel('假正率 (FPR)')
axes[1, 0].set_ylabel('真正率 (TPR)')
axes[1, 0].set_title('ROC曲线')
axes[1, 0].legend(loc='lower right')
axes[1, 0].grid(True, alpha=0.3)

# 混淆矩阵(以随机森林为例)
rf_model = models['随机森林']
y_pred_rf = rf_model.predict(X_test_scaled)
cm = confusion_matrix(y_test, y_pred_rf)

sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', ax=axes[1, 1],
            xticklabels=['正常邮件', '垃圾邮件'],
            yticklabels=['正常邮件', '垃圾邮件'])
axes[1, 1].set_xlabel('预测标签')
axes[1, 1].set_ylabel('真实标签')
axes[1, 1].set_title('随机森林混淆矩阵')

plt.tight_layout()
plt.show()

超参数调优

网格搜索

# 选择最佳模型(假设是随机森林)
best_model_name = max(results, key=lambda x: results[x]['f1'])
print(f"最佳模型: {best_model_name}")

# 超参数调优
param_grid = {
    'n_estimators': [50, 100, 200],
    'max_depth': [3, 5, 10, None],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4]
}

grid_search = GridSearchCV(
    RandomForestClassifier(random_state=42),
    param_grid,
    cv=5,
    scoring='f1',
    n_jobs=-1,
    verbose=1
)

grid_search.fit(X_train_scaled, y_train)

print(f"最佳参数: {grid_search.best_params_}")
print(f"最佳交叉验证F1: {grid_search.best_score_:.4f}")

# 使用最佳模型
best_model = grid_search.best_estimator_
y_pred_best = best_model.predict(X_test_scaled)
y_prob_best = best_model.predict_proba(X_test_scaled)[:, 1]

# 评估最佳模型
print(f"\n最佳模型性能:")
print(f"准确率: {accuracy_score(y_test, y_pred_best):.4f}")
print(f"F1分数: {f1_score(y_test, y_pred_best):.4f}")
print(f"AUC-ROC: {auc(*roc_curve(y_test, y_prob_best)[:2]):.4f}")

模型解释

特征重要性

# 特征重要性
feature_importance = best_model.feature_importances_
feature_names = [f'特征{i}' for i in range(len(feature_importance))]

# 可视化
plt.figure(figsize=(12, 6))
indices = np.argsort(feature_importance)[::-1][:20]  # 前20个特征

plt.bar(range(20), feature_importance[indices], align='center',
        color='lightblue', edgecolor='black')
plt.xticks(range(20), [feature_names[i] for i in indices], rotation=45)
plt.xlabel('特征')
plt.ylabel('重要性')
plt.title('特征重要性排序(前20个)')
plt.tight_layout()
plt.show()

# 打印重要特征
print("\n最重要的10个特征:")
for i in indices[:10]:
    print(f"{feature_names[i]}: {feature_importance[i]:.4f}")

实际应用

预测新数据

# 预测新邮件
def predict_email(email_features):
    """预测邮件是否为垃圾邮件"""
    # 预处理
    email_scaled = scaler.transform(email_features.reshape(1, -1))
    
    # 预测
    prediction = best_model.predict(email_scaled)[0]
    probability = best_model.predict_proba(email_scaled)[0]
    
    return {
        'prediction': '垃圾邮件' if prediction == 1 else '正常邮件',
        'confidence': max(probability),
        'probability_normal': probability[0],
        'probability_spam': probability[1]
    }

# 测试预测
sample_email = np.random.rand(100)  # 模拟一封邮件的特征
result = predict_email(sample_email)
print("\n邮件预测结果:")
print(f"预测类别: {result['prediction']}")
print(f"置信度: {result['confidence']:.2%}")
print(f"正常邮件概率: {result['probability_normal']:.2%}")
print(f"垃圾邮件概率: {result['probability_spam']:.2%}")

总结

通过这个完整的分类案例,我们掌握了:

  1. 分类问题的数据探索和可视化
  2. 多种分类模型的比较和评估
  3. 模型性能指标的选择(准确率、精确率、召回率、F1、AUC-ROC)
  4. 超参数调优
  5. 模型解释和特征重要性分析
  6. 实际预测应用

分类是机器学习的核心任务,掌握分类算法对于构建智能系统至关重要。