十大基础机器学习算法详解与实践
本文档详细介绍十大经典机器学习算法(非深度学习),包含算法原理、数学公式、Python实现和可运行的训练代码。所有代码基于Python 3.11,可直接运行。
目录
- 引言
- 算法1:线性回归 (Linear Regression)
- 算法2:逻辑回归 (Logistic Regression)
- 算法3:支持向量机 (SVM)
- 算法4:朴素贝叶斯 (Naive Bayes)
- 算法5:决策树 (Decision Tree)
- 算法6:随机森林 (Random Forest)
- 算法7:K近邻 (KNN)
- 算法8:K均值聚类 (K-Means)
- 算法9:主成分分析 (PCA)
- 算法10:梯度提升树 (Gradient Boosting)
- 算法对比与选择指南
- 总结
引言
为什么学习这些算法?
基础机器学习算法是理解机器学习的基石,它们具有以下特点:
- 易于理解:算法原理清晰,数学基础扎实
- 实用性强:在实际项目中广泛应用
- 可解释性好:模型决策过程可解释
- 计算效率高:训练和推理速度快
- 数据要求低:不需要大量数据即可工作
环境准备
Python版本:Python 3.11
依赖安装:
bash
pip install -r docs/requirements_visualization.txt
核心依赖:
- numpy >= 1.24.0
- scikit-learn >= 1.3.0
- matplotlib >= 3.7.0
- pandas >= 2.0.0
- scipy >= 1.10.0
算法1:线性回归 (Linear Regression)
算法原理
线性回归是最基础的回归算法,通过拟合一条直线(或超平面)来预测连续值。它是统计学和机器学习中最基础、最常用的算法之一。
核心思想:找到一条直线(或超平面),使得所有数据点到直线的距离(误差)最小。
算法流程:
1. 数据准备 → 2. 特征选择 → 3. 模型训练 → 4. 预测 → 5. 评估
↓ ↓ ↓ ↓ ↓
清洗数据 相关性分析 最小二乘法 新数据 MSE/R²
处理缺失值 特征工程 或梯度下降 预测值 残差分析
异常值检测 特征缩放
工作原理示意图:
一维情况(简单线性回归):
y
| ●
| ● ●
| ● ●
| ●
|_____________x
拟合直线 y = wx + b
多维情况(多元线性回归):
y
| ●
| ● ●
| ● ●
| ●
|_____________x₁
超平面 y = w₁x₁ + w₂x₂ + ... + b
数学公式详解
1. 模型公式
简单线性回归(一维):
y = w₀ + w₁x + ε
多元线性回归(多维):
y = w₀ + w₁x₁ + w₂x₂ + ... + wₙxₙ + ε
矩阵形式:
Y = XW + ε
其中:
y:目标变量(预测值),形状为 (n_samples,)X:特征矩阵,形状为 (n_samples, n_features)W:权重向量 [w₀, w₁, ..., wₙ]ᵀ,形状为 (n_features+1,)ε:误差项,假设服从正态分布 N(0, σ²)
2. 损失函数
均方误差(MSE):
MSE = (1/n) × Σ(yᵢ - ŷᵢ)²
均方根误差(RMSE):
RMSE = √MSE = √[(1/n) × Σ(yᵢ - ŷᵢ)²]
平均绝对误差(MAE):
MAE = (1/n) × Σ|yᵢ - ŷᵢ|
3. 求解方法
方法1:最小二乘法(解析解)
通过求导并令导数为零,得到解析解:
W = (XᵀX)⁻¹XᵀY
优点 :一步到位,无需迭代
缺点:需要计算矩阵逆,当特征很多时计算量大
方法2:梯度下降(迭代优化)
梯度下降通过迭代更新参数:
wⱼ = wⱼ - α × (∂MSE/∂wⱼ)
其中:
α:学习率(learning rate)∂MSE/∂wⱼ:损失函数对权重wⱼ的偏导数
梯度计算公式:
∂MSE/∂wⱼ = (2/n) × Σ xᵢⱼ(yᵢ - ŷᵢ)
∂MSE/∂b = (2/n) × Σ(yᵢ - ŷᵢ)
优点 :适用于大规模数据,可以处理特征很多的情况
缺点:需要调学习率,可能需要多次迭代
4. 评估指标
R²决定系数:
R² = 1 - (SS_res / SS_tot)
其中:
SS_res = Σ(yᵢ - ŷᵢ)²:残差平方和SS_tot = Σ(yᵢ - ȳ)²:总平方和
R²的取值范围:[0, 1],越接近1表示模型拟合越好
调整R²(Adjusted R²):
R²_adj = 1 - [(1-R²)(n-1)/(n-p-1)]
其中p是特征数量,用于惩罚过多的特征
数据预处理
1. 数据清洗
缺失值处理:
- 删除缺失值(如果缺失比例很小)
- 用均值/中位数/众数填充
- 用回归预测填充
异常值检测与处理:
- IQR方法:Q1 - 1.5×IQR < 数据 < Q3 + 1.5×IQR
- Z-score方法:|z| > 3 视为异常值
- 可视化方法:箱线图、散点图识别异常值
处理策略:
- 删除异常值(如果确认是错误数据)
- 用截断值替换(Winsorization)
- 保留但标记(如果可能是真实值)
2. 特征工程
特征缩放 :
虽然线性回归理论上不需要特征缩放,但实际应用中建议进行:
- 标准化(Standardization) :
z = (x - μ) / σ - 归一化(Normalization) :
x_norm = (x - min) / (max - min)
特征变换:
- 多项式特征:x², x³(处理非线性关系)
- 对数变换:log(x)(处理指数关系)
- 交互特征:x₁ × x₂(捕捉特征交互)
特征选择:
- 相关性分析:选择与目标变量相关性高的特征
- 方差分析:删除方差很小的特征(几乎常数)
- 正则化:L1正则化(Lasso)可以自动进行特征选择
3. 数据划分
python
# 训练集:用于训练模型(通常70-80%)
# 验证集:用于调参(通常10-15%)
# 测试集:用于最终评估(通常10-15%)
模型训练与优化
1. 正则化
Ridge回归(L2正则化):
Loss = MSE + λ × Σwᵢ²
Lasso回归(L1正则化):
Loss = MSE + λ × Σ|wᵢ|
Elastic Net(L1+L2):
Loss = MSE + λ₁ × Σ|wᵢ| + λ₂ × Σwᵢ²
正则化可以:
- 防止过拟合
- 处理多重共线性
- Lasso可以进行特征选择
2. 超参数调优
学习率(梯度下降):
- 太小:收敛慢
- 太大:可能不收敛或震荡
- 建议:0.001 - 0.1,使用学习率衰减
正则化系数λ:
- 使用交叉验证选择最优值
- 网格搜索或随机搜索
3. 模型诊断
残差分析:
- 残差应该随机分布,无模式
- 残差应该服从正态分布
- 残差应该同方差(方差恒定)
多重共线性检测:
- VIF(方差膨胀因子)> 10 表示存在多重共线性
- 相关系数矩阵检查特征间相关性
线性假设检验:
- 检查残差图是否有非线性模式
- 如果存在非线性,考虑多项式回归或非线性模型
Python实现
1. 使用scikit-learn实现
python
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
import warnings
warnings.filterwarnings('ignore')
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
def linear_regression_demo():
"""
线性回归完整示例
"""
print("=" * 60)
print("线性回归 (Linear Regression) 示例")
print("=" * 60)
# 1. 生成示例数据
np.random.seed(42)
n_samples = 100
X = np.random.randn(n_samples, 1) * 10
# y = 2x + 3 + 噪声
y = 2 * X.flatten() + 3 + np.random.randn(n_samples) * 2
# 2. 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 3. 创建并训练模型
model = LinearRegression()
model.fit(X_train, y_train)
# 4. 预测
y_train_pred = model.predict(X_train)
y_test_pred = model.predict(X_test)
# 5. 评估
train_mse = mean_squared_error(y_train, y_train_pred)
test_mse = mean_squared_error(y_test, y_test_pred)
train_r2 = r2_score(y_train, y_train_pred)
test_r2 = r2_score(y_test, y_test_pred)
print(f"\n模型参数:")
print(f" 权重 (w): {model.coef_[0]:.4f}")
print(f" 截距 (b): {model.intercept_:.4f}")
print(f"\n训练集评估:")
print(f" MSE: {train_mse:.4f}")
print(f" R²: {train_r2:.4f}")
print(f"\n测试集评估:")
print(f" MSE: {test_mse:.4f}")
print(f" R²: {test_r2:.4f}")
# 6. 可视化
plt.figure(figsize=(12, 5))
# 训练集
plt.subplot(1, 2, 1)
plt.scatter(X_train, y_train, alpha=0.6, label='训练数据', color='blue')
plt.plot(X_train, y_train_pred, 'r-', linewidth=2, label='拟合直线')
plt.xlabel('特征 X', fontsize=12)
plt.ylabel('目标 y', fontsize=12)
plt.title(f'训练集 (R² = {train_r2:.3f})', fontsize=14, fontweight='bold')
plt.legend()
plt.grid(True, alpha=0.3)
# 测试集
plt.subplot(1, 2, 2)
plt.scatter(X_test, y_test, alpha=0.6, label='测试数据', color='green')
plt.plot(X_test, y_test_pred, 'r-', linewidth=2, label='拟合直线')
plt.xlabel('特征 X', fontsize=12)
plt.ylabel('目标 y', fontsize=12)
plt.title(f'测试集 (R² = {test_r2:.3f})', fontsize=14, fontweight='bold')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('docs/images/linear_regression_demo.png', dpi=300, bbox_inches='tight')
plt.show()
return model, X_test, y_test, y_test_pred
# 运行示例
if __name__ == '__main__':
model, X_test, y_test, y_pred = linear_regression_demo()
2. 从零实现线性回归
python
class LinearRegressionFromScratch:
"""
从零实现线性回归(使用梯度下降)
"""
def __init__(self, learning_rate=0.01, n_iterations=1000):
self.learning_rate = learning_rate
self.n_iterations = n_iterations
self.weights = None
self.bias = None
self.cost_history = []
def fit(self, X, y):
"""
训练模型
"""
n_samples, n_features = X.shape
# 初始化参数
self.weights = np.zeros(n_features)
self.bias = 0
# 梯度下降
for i in range(self.n_iterations):
# 预测
y_pred = np.dot(X, self.weights) + self.bias
# 计算损失
cost = (1 / n_samples) * np.sum((y_pred - y) ** 2)
self.cost_history.append(cost)
# 计算梯度
dw = (1 / n_samples) * np.dot(X.T, (y_pred - y))
db = (1 / n_samples) * np.sum(y_pred - y)
# 更新参数
self.weights -= self.learning_rate * dw
self.bias -= self.learning_rate * db
def predict(self, X):
"""
预测
"""
return np.dot(X, self.weights) + self.bias
# 使用示例
def linear_regression_from_scratch_demo():
"""
从零实现线性回归示例
"""
print("\n" + "=" * 60)
print("从零实现线性回归示例")
print("=" * 60)
# 生成数据
np.random.seed(42)
X = np.random.randn(100, 1) * 10
y = 2 * X.flatten() + 3 + np.random.randn(100) * 2
# 训练模型
model = LinearRegressionFromScratch(learning_rate=0.01, n_iterations=1000)
model.fit(X, y)
# 预测
y_pred = model.predict(X)
# 评估
mse = mean_squared_error(y, y_pred)
r2 = r2_score(y, y_pred)
print(f"\n模型参数:")
print(f" 权重 (w): {model.weights[0]:.4f}")
print(f" 截距 (b): {model.bias:.4f}")
print(f"\n评估指标:")
print(f" MSE: {mse:.4f}")
print(f" R²: {r2:.4f}")
# 可视化损失函数
plt.figure(figsize=(10, 6))
plt.plot(model.cost_history, linewidth=2)
plt.xlabel('迭代次数', fontsize=12)
plt.ylabel('损失 (MSE)', fontsize=12)
plt.title('损失函数收敛曲线', fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3)
plt.savefig('docs/images/linear_regression_loss.png', dpi=300, bbox_inches='tight')
plt.show()
# 运行
if __name__ == '__main__':
linear_regression_from_scratch_demo()
模型后处理
1. 预测结果解释
系数解释:
- 系数wᵢ表示:当特征xᵢ增加1个单位,目标变量y平均增加wᵢ个单位(其他特征不变)
- 截距w₀表示:当所有特征为0时的预测值
置信区间:
python
# 预测值的95%置信区间
from scipy import stats
std_err = np.sqrt(mse)
confidence_interval = stats.t.interval(0.95, len(y)-2,
loc=y_pred,
scale=std_err)
2. 模型解释性
特征重要性:
- 标准化系数:
w_std = w × (std_x / std_y) - 标准化系数越大,特征对目标变量的影响越大
SHAP值(如果需要更详细的解释):
python
import shap
explainer = shap.LinearExplainer(model, X_train)
shap_values = explainer.shap_values(X_test)
3. 模型部署
保存模型:
python
import joblib
joblib.dump(model, 'linear_regression_model.pkl')
模型服务化:
- REST API封装
- 批量预测接口
- 实时预测接口
实际应用案例
案例1:房价预测
问题描述:根据房屋面积、房间数、位置等特征预测房价
数据预处理:
python
# 1. 处理缺失值
df['bedrooms'].fillna(df['bedrooms'].median(), inplace=True)
# 2. 特征工程
df['price_per_sqft'] = df['price'] / df['sqft_living']
df['age'] = 2024 - df['yr_built']
# 3. 特征选择
features = ['sqft_living', 'bedrooms', 'bathrooms', 'age', 'zipcode']
X = df[features]
y = df['price']
# 4. 处理分类特征(zipcode)
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
X['zipcode'] = le.fit_transform(X['zipcode'])
模型训练:
python
from sklearn.linear_model import Ridge
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import GridSearchCV
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
model = Ridge()
param_grid = {'alpha': [0.1, 1.0, 10.0, 100.0]}
grid_search = GridSearchCV(model, param_grid, cv=5, scoring='neg_mean_squared_error')
grid_search.fit(X_scaled, y)
best_model = grid_search.best_estimator_
结果解释:
- 房屋面积每增加100平方英尺,房价平均增加约$20,000
- 每增加一个卧室,房价平均增加约$15,000
案例2:销售额预测
问题描述:根据广告投入、促销活动等预测销售额
特征工程:
python
# 创建交互特征
df['tv_radio_interaction'] = df['TV'] * df['Radio']
df['tv_newspaper_interaction'] = df['TV'] * df['Newspaper']
# 对数变换(处理非线性关系)
df['TV_log'] = np.log1p(df['TV'])
模型选择:
- 简单线性回归:基线模型
- Ridge回归:处理多重共线性
- 多项式回归:捕捉非线性关系
优缺点分析
优点:
- 简单易懂:算法原理直观,易于理解和实现
- 训练速度快:最小二乘法有解析解,计算效率高
- 可解释性强:系数有明确的物理意义
- 不需要特征缩放:在某些情况下(最小二乘法)不需要
- 理论基础扎实:有完整的统计理论支撑
- 不易过拟合:当使用正则化时
缺点:
- 线性假设:假设特征和目标变量是线性关系,无法捕捉非线性模式
- 对异常值敏感:MSE损失函数对异常值敏感
- 容易过拟合:当特征很多时,容易过拟合(需要用正则化)
- 多重共线性问题:特征间高度相关时,系数不稳定
- 需要特征工程:需要手动创建非线性特征
常见问题与解决方案
Q1: 模型预测不准确怎么办?
可能原因及解决方案:
-
非线性关系:
- 解决方案:使用多项式特征、对数变换
- 或使用非线性模型(决策树、随机森林)
-
特征不足:
- 解决方案:特征工程,创建更多有意义的特征
- 特征选择,删除无关特征
-
数据质量问题:
- 解决方案:检查异常值、缺失值
- 数据清洗和预处理
Q2: 如何判断模型是否过拟合?
判断方法:
- 训练集R²很高,但测试集R²很低
- 训练集MSE很小,但测试集MSE很大
解决方案:
- 使用正则化(Ridge、Lasso)
- 减少特征数量
- 增加训练数据
- 使用交叉验证
Q3: 如何处理多重共线性?
检测方法:
python
from statsmodels.stats.outliers_influence import variance_inflation_factor
vif_data = pd.DataFrame()
vif_data["feature"] = X.columns
vif_data["VIF"] = [variance_inflation_factor(X.values, i)
for i in range(len(X.columns))]
# VIF > 10 表示存在多重共线性
解决方案:
- 删除高度相关的特征之一
- 使用Ridge回归(L2正则化)
- 使用主成分分析(PCA)降维
Q4: 如何处理异方差性?
检测方法:
- 绘制残差图,检查残差是否随预测值变化
解决方案:
- 使用加权最小二乘法(WLS)
- 对目标变量进行对数变换
- 使用稳健回归方法
Q5: 如何选择正则化参数?
方法:
python
from sklearn.linear_model import RidgeCV
# 使用交叉验证自动选择最优alpha
ridge_cv = RidgeCV(alphas=[0.1, 1.0, 10.0, 100.0], cv=5)
ridge_cv.fit(X_train, y_train)
print(f"最优alpha: {ridge_cv.alpha_}")
算法流程图
开始
↓
数据加载与探索
↓
数据清洗(缺失值、异常值)
↓
特征工程(缩放、变换、选择)
↓
数据划分(训练集/验证集/测试集)
↓
模型选择(线性回归/Ridge/Lasso)
↓
超参数调优(交叉验证)
↓
模型训练
↓
模型评估(MSE、R²、残差分析)
↓
模型诊断(多重共线性、异方差性)
↓
模型优化(正则化、特征选择)
↓
最终预测
↓
模型解释与部署
↓
结束
算法2:逻辑回归 (Logistic Regression)
算法原理
逻辑回归虽然名字叫"回归",但实际上是分类算法,主要用于二分类问题(也可以扩展到多分类)。它是线性回归的扩展,通过Sigmoid函数将线性组合映射到概率空间。
核心思想:使用Sigmoid函数将线性回归的输出映射到[0,1]区间,表示样本属于正类的概率。
算法流程:
1. 数据准备 → 2. 特征工程 → 3. 模型训练 → 4. 概率预测 → 5. 分类决策
↓ ↓ ↓ ↓ ↓
清洗数据 特征缩放 梯度下降 输出概率 阈值选择
处理不平衡 特征选择 或牛顿法 [0,1]区间 默认0.5
编码类别 处理共线性
工作原理示意图:
线性回归输出 z = w₀ + w₁x₁ + ... + wₙxₙ
↓
Sigmoid函数
↓
概率 P(y=1|x) = σ(z) = 1/(1+e^(-z))
↓
决策规则:如果 P > 0.5,预测为类别1,否则为类别0
Sigmoid函数图像:
P
1 | ╱──────
| ╱
| ╱
0.5 |───
| ╱
|╱
0 |─────────────── z
-∞ 0 +∞
数学公式详解
1. Sigmoid函数
定义:
σ(z) = 1 / (1 + e^(-z))
性质:
- 值域:[0, 1]
- 单调递增
- 关于点(0, 0.5)中心对称
- 导数:σ'(z) = σ(z)(1 - σ(z))
为什么使用Sigmoid:
- 将任意实数映射到[0,1],符合概率定义
- 单调性保证特征越大,概率越大
- 导数形式简单,便于梯度计算
2. 模型公式
二分类逻辑回归:
P(y=1|x) = σ(w₀ + w₁x₁ + ... + wₙxₙ)
= 1 / (1 + e^(-(w₀ + w₁x₁ + ... + wₙxₙ)))
P(y=0|x) = 1 - P(y=1|x)
对数几率(Logit):
logit(P) = ln(P / (1-P)) = w₀ + w₁x₁ + ... + wₙxₙ
多分类逻辑回归(Softmax):
P(y=k|x) = e^(z_k) / Σᵢ e^(z_i)
其中 z_k = w₀ᵏ + w₁ᵏx₁ + ... + wₙᵏxₙ
3. 损失函数
对数损失(Log Loss / Cross Entropy):
Loss = -[y·log(ŷ) + (1-y)·log(1-ŷ)]
批量损失:
J(W) = -(1/n) × Σ[yᵢ·log(ŷᵢ) + (1-yᵢ)·log(1-ŷᵢ)]
为什么使用对数损失:
- 当预测错误时,损失会很大(惩罚严重错误)
- 当预测正确时,损失接近0
- 梯度形式简单,便于优化
4. 优化方法
梯度下降:
wⱼ = wⱼ - α × (∂J/∂wⱼ)
梯度计算:
∂J/∂wⱼ = (1/n) × Σ xᵢⱼ(ŷᵢ - yᵢ)
牛顿法(二阶优化):
- 收敛速度更快
- 但计算Hessian矩阵成本高
数据预处理
1. 数据清洗
缺失值处理:
- 分类特征:用众数填充
- 数值特征:用均值/中位数填充
- 或使用模型预测填充
异常值处理:
- 逻辑回归对异常值相对稳健(因为Sigmoid函数有界)
- 但仍需检查和处理极端值
2. 特征工程
特征缩放:
- 必须进行特征缩放(与线性回归不同)
- 标准化:
z = (x - μ) / σ - 归一化:
x_norm = (x - min) / (max - min)
原因:梯度下降需要特征尺度相近才能快速收敛
特征变换:
- 多项式特征:捕捉非线性关系
- 交互特征:x₁ × x₂
- 分箱(Binning):将连续特征离散化
类别特征编码:
- One-Hot编码:每个类别一个二进制特征
- Label编码:仅适用于有序类别
- Target编码:用目标变量的均值编码
3. 处理类别不平衡
问题:正负样本比例悬殊(如1:99)
解决方案:
- 调整类别权重:
python
model = LogisticRegression(class_weight='balanced')
# 或
model = LogisticRegression(class_weight={0: 1, 1: 10})
- 过采样(SMOTE):
python
from imblearn.over_sampling import SMOTE
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)
- 欠采样:
python
from imblearn.under_sampling import RandomUnderSampler
undersample = RandomUnderSampler(random_state=42)
X_resampled, y_resampled = undersample.fit_resample(X_train, y_train)
- 调整决策阈值:
python
# 默认阈值0.5,可以调整
y_pred = (y_proba > 0.3).astype(int) # 降低阈值,增加召回率
模型训练与优化
1. 正则化
L2正则化(Ridge):
python
model = LogisticRegression(penalty='l2', C=1.0)
L1正则化(Lasso):
python
model = LogisticRegression(penalty='l1', C=1.0, solver='liblinear')
Elastic Net:
python
model = LogisticRegression(penalty='elasticnet', l1_ratio=0.5, solver='saga')
参数C:
- C = 1/λ(正则化强度的倒数)
- C越大,正则化越弱(容易过拟合)
- C越小,正则化越强(容易欠拟合)
2. 超参数调优
主要超参数:
C:正则化强度(通常0.001 - 100)penalty:正则化类型('l1', 'l2', 'elasticnet')solver:优化算法('lbfgs', 'liblinear', 'saga'等)max_iter:最大迭代次数
调优方法:
python
from sklearn.model_selection import GridSearchCV
param_grid = {
'C': [0.001, 0.01, 0.1, 1.0, 10.0, 100.0],
'penalty': ['l1', 'l2'],
'solver': ['liblinear', 'lbfgs']
}
grid_search = GridSearchCV(LogisticRegression(), param_grid, cv=5, scoring='roc_auc')
grid_search.fit(X_train, y_train)
3. 多分类问题
方法1:One-vs-Rest (OvR):
- 训练K个二分类器(K为类别数)
- 每个分类器区分一个类别和其他所有类别
方法2:One-vs-One (OvO):
- 训练K(K-1)/2个二分类器
- 每对类别训练一个分类器
方法3:Softmax(多分类逻辑回归):
python
model = LogisticRegression(multi_class='multinomial', solver='lbfgs')
Python实现
python
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report, roc_curve, auc
from sklearn.datasets import make_classification
def logistic_regression_demo():
"""
逻辑回归完整示例
"""
print("=" * 60)
print("逻辑回归 (Logistic Regression) 示例")
print("=" * 60)
# 1. 生成二分类数据
X, y = make_classification(
n_samples=1000,
n_features=2,
n_redundant=0,
n_informative=2,
n_clusters_per_class=1,
random_state=42
)
# 2. 划分数据集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 3. 训练模型
model = LogisticRegression(max_iter=1000, random_state=42)
model.fit(X_train, y_train)
# 4. 预测
y_train_pred = model.predict(X_train)
y_test_pred = model.predict(X_test)
y_test_proba = model.predict_proba(X_test)[:, 1]
# 5. 评估
train_acc = accuracy_score(y_train, y_train_pred)
test_acc = accuracy_score(y_test, y_test_pred)
print(f"\n模型参数:")
print(f" 权重: {model.coef_}")
print(f" 截距: {model.intercept_}")
print(f"\n训练集准确率: {train_acc:.4f}")
print(f"测试集准确率: {test_acc:.4f}")
print(f"\n分类报告:")
print(classification_report(y_test, y_test_pred))
# 6. 可视化
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
# 决策边界
ax1 = axes[0]
h = 0.02
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
ax1.contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.RdYlBu)
scatter = ax1.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.RdYlBu, edgecolors='black')
ax1.set_xlabel('特征 1', fontsize=12)
ax1.set_ylabel('特征 2', fontsize=12)
ax1.set_title('决策边界', fontsize=14, fontweight='bold')
plt.colorbar(scatter, ax=ax1)
# 混淆矩阵
ax2 = axes[1]
cm = confusion_matrix(y_test, y_test_pred)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', ax=ax2)
ax2.set_xlabel('预测标签', fontsize=12)
ax2.set_ylabel('真实标签', fontsize=12)
ax2.set_title('混淆矩阵', fontsize=14, fontweight='bold')
# ROC曲线
ax3 = axes[2]
fpr, tpr, _ = roc_curve(y_test, y_test_proba)
roc_auc = auc(fpr, tpr)
ax3.plot(fpr, tpr, linewidth=2, label=f'ROC曲线 (AUC = {roc_auc:.3f})')
ax3.plot([0, 1], [0, 1], 'k--', label='随机分类器')
ax3.set_xlabel('假正率 (FPR)', fontsize=12)
ax3.set_ylabel('真正率 (TPR)', fontsize=12)
ax3.set_title('ROC曲线', fontsize=14, fontweight='bold')
ax3.legend()
ax3.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('docs/images/logistic_regression_demo.png', dpi=300, bbox_inches='tight')
plt.show()
return model
# 运行示例
if __name__ == '__main__':
model = logistic_regression_demo()
模型后处理
1. 概率校准
问题:逻辑回归输出的概率可能不够准确
校准方法:
python
from sklearn.calibration import CalibratedClassifierCV
# Platt Scaling
calibrated_model = CalibratedClassifierCV(model, method='sigmoid', cv=3)
calibrated_model.fit(X_train, y_train)
y_proba_calibrated = calibrated_model.predict_proba(X_test)[:, 1]
2. 阈值优化
默认阈值:0.5(可能不是最优)
优化方法:
python
from sklearn.metrics import roc_curve
fpr, tpr, thresholds = roc_curve(y_test, y_proba)
# 找到最优阈值(最大化F1分数或Youden指数)
optimal_idx = np.argmax(tpr - fpr)
optimal_threshold = thresholds[optimal_idx]
y_pred_optimal = (y_proba > optimal_threshold).astype(int)
3. 模型解释
系数解释:
- 系数wᵢ表示:特征xᵢ增加1个单位,对数几率增加wᵢ
- 优势比(Odds Ratio):
OR = e^wᵢ- OR > 1:特征增加会提高正类概率
- OR < 1:特征增加会降低正类概率
特征重要性:
python
# 标准化系数
feature_importance = np.abs(model.coef_[0]) * np.std(X_train, axis=0)
实际应用案例
案例1:垃圾邮件分类
问题描述:根据邮件内容判断是否为垃圾邮件
数据预处理:
python
from sklearn.feature_extraction.text import TfidfVectorizer
# 文本特征提取
vectorizer = TfidfVectorizer(max_features=5000, ngram_range=(1, 2))
X = vectorizer.fit_transform(emails)
# 处理不平衡(假设垃圾邮件占10%)
from imblearn.over_sampling import SMOTE
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X, y)
模型训练:
python
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(
C=1.0,
penalty='l2',
class_weight='balanced',
max_iter=1000
)
model.fit(X_resampled, y_resampled)
结果评估:
- 准确率:95%
- 精确率:92%(垃圾邮件中真正是垃圾邮件的比例)
- 召回率:88%(所有垃圾邮件中被正确识别的比例)
- F1分数:90%
案例2:疾病诊断
问题描述:根据患者症状预测是否患病
特征工程:
python
# 处理类别特征
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(drop='first', sparse=False)
X_categorical = encoder.fit_transform(df[['性别', '血型']])
# 数值特征标准化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_numerical = scaler.fit_transform(df[['年龄', '血压', '血糖']])
# 合并特征
X = np.hstack([X_numerical, X_categorical])
模型解释:
- 年龄每增加1岁,患病概率的对数几率增加0.05
- 血压每增加1个单位,患病概率的对数几率增加0.15
- 优势比:e^0.15 ≈ 1.16,表示血压每增加1个单位,患病概率增加16%
优缺点分析
优点:
- 输出概率:不仅给出分类结果,还给出概率值
- 可解释性强:系数有明确的统计意义
- 训练速度快:优化算法成熟,收敛快
- 不需要特征缩放:虽然建议缩放,但不是必须的
- 不易过拟合:使用正则化可以有效防止过拟合
- 支持多分类:可以扩展到多分类问题
缺点:
- 线性决策边界:只能学习线性决策边界(除非使用核技巧)
- 需要特征工程:需要手动创建非线性特征
- 对异常值敏感:虽然比线性回归好,但仍可能受影响
- 假设特征独立:虽然不像朴素贝叶斯那样严格,但仍可能受影响
- 需要大量样本:在小样本上可能表现不佳
常见问题与解决方案
Q1: 模型预测概率总是接近0.5怎么办?
可能原因:
- 特征不够区分性强
- 数据不平衡但未处理
- 正则化过强
解决方案:
- 特征工程,创建更有区分性的特征
- 处理类别不平衡
- 调整正则化参数C
Q2: 如何选择最优阈值?
方法:
python
from sklearn.metrics import precision_recall_curve, f1_score
precision, recall, thresholds = precision_recall_curve(y_test, y_proba)
f1_scores = 2 * (precision * recall) / (precision + recall)
optimal_idx = np.argmax(f1_scores)
optimal_threshold = thresholds[optimal_idx]
考虑因素:
- 如果假阳性成本高:提高阈值(更保守)
- 如果假阴性成本高:降低阈值(更激进)
Q3: 如何处理多分类问题?
方法1:One-vs-Rest:
python
model = LogisticRegression(multi_class='ovr')
方法2:Multinomial(Softmax):
python
model = LogisticRegression(multi_class='multinomial', solver='lbfgs')
选择建议:
- 类别数少(<10):使用Multinomial
- 类别数多(>10):使用OvR
Q4: 模型过拟合怎么办?
解决方案:
- 增加正则化强度(减小C值)
- 减少特征数量
- 增加训练数据
- 使用交叉验证选择最优参数
Q5: 如何处理特征间的交互?
方法:
python
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=2, interaction_only=True)
X_poly = poly.fit_transform(X)
注意:会增加特征数量,可能导致过拟合
算法流程图
开始
↓
数据加载与探索
↓
数据清洗(缺失值、异常值)
↓
特征工程(文本向量化/数值标准化/类别编码)
↓
处理类别不平衡(SMOTE/欠采样/权重调整)
↓
数据划分(训练集/验证集/测试集)
↓
模型选择(二分类/多分类)
↓
超参数调优(C、penalty、solver)
↓
模型训练(梯度下降/牛顿法)
↓
模型评估(准确率/精确率/召回率/F1/ROC-AUC)
↓
阈值优化(根据业务需求)
↓
概率校准(如果需要)
↓
模型解释(系数、优势比)
↓
模型部署
↓
结束
算法3:支持向量机 (SVM)
算法原理
支持向量机(Support Vector Machine, SVM)是一种强大的分类算法,通过寻找最优分离超平面来分类数据。SVM的核心思想是最大化两类数据之间的间隔(margin),从而获得更好的泛化能力。
核心思想:找到能够最大化两类数据间隔的超平面,使得分类边界距离最近的样本点(支持向量)最远。
算法流程:
1. 数据准备 → 2. 特征缩放 → 3. 选择核函数 → 4. 训练SVM → 5. 预测
↓ ↓ ↓ ↓ ↓
清洗数据 标准化/归一化 线性/多项式/RBF 求解优化问题 决策函数
处理缺失值 (必须!) 高斯/ Sigmoid 二次规划 sign(w·x+b)
异常值检测
工作原理示意图:
硬间隔SVM(线性可分):
●
● ●
● ●
●
─────────── 超平面 w·x + b = 0
●
● ●
● ●
●
软间隔SVM(线性不可分):
●
● ● ξ
● ξ ● 允许一些样本在间隔内
●
─────────── 超平面(允许错误)
●
● ●
● ●
●
支持向量:距离超平面最近的样本点
间隔:两个平行超平面之间的距离
数学公式详解
1. 硬间隔SVM(线性可分)
优化目标:
minimize: (1/2)||w||²
subject to: yᵢ(w·xᵢ + b) ≥ 1, ∀i
几何意义:
- 最大化间隔 = 最小化 ||w||
- 约束条件确保所有样本正确分类且距离超平面至少为1
对偶问题(更易求解):
maximize: Σαᵢ - (1/2)ΣΣαᵢαⱼyᵢyⱼ(xᵢ·xⱼ)
subject to: Σαᵢyᵢ = 0, αᵢ ≥ 0
2. 软间隔SVM(线性不可分)
优化目标:
minimize: (1/2)||w||² + CΣξᵢ
subject to: yᵢ(w·xᵢ + b) ≥ 1 - ξᵢ, ξᵢ ≥ 0
参数说明:
C:惩罚参数,控制对误分类的惩罚程度- C大:严格分类,可能过拟合
- C小:允许更多错误,泛化能力更强
ξᵢ:松弛变量,允许样本在间隔内或错误分类
3. 核技巧(非线性SVM)
问题:数据线性不可分时,需要非线性决策边界
解决方案:通过核函数将数据映射到高维空间,在高维空间中线性可分
常用核函数:
-
线性核 :
K(xᵢ, xⱼ) = xᵢ·xⱼ- 适用于线性可分数据
-
多项式核 :
K(xᵢ, xⱼ) = (γxᵢ·xⱼ + r)^d- 参数:
d(次数)、γ(缩放)、r(常数项)
- 参数:
-
RBF(高斯)核 :
K(xᵢ, xⱼ) = exp(-γ||xᵢ - xⱼ||²)- 参数:
γ(gamma),控制决策边界的复杂度 - γ大:决策边界复杂,可能过拟合
- γ小:决策边界平滑,可能欠拟合
- 参数:
-
Sigmoid核 :
K(xᵢ, xⱼ) = tanh(γxᵢ·xⱼ + r)
决策函数:
f(x) = sign(ΣαᵢyᵢK(xᵢ, x) + b)
4. 支持向量
定义 :满足 αᵢ > 0 的样本点
特点:
- 支持向量决定了决策边界
- 只有支持向量对模型有影响
- 删除非支持向量不影响模型
数据预处理
1. 特征缩放(必须!)
为什么必须缩放:
- SVM使用距离度量,对特征尺度敏感
- 不同尺度的特征会导致某些特征主导模型
方法:
python
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
标准化 vs 归一化:
- 标准化 :
z = (x - μ) / σ(推荐,保持分布形状) - 归一化 :
x_norm = (x - min) / (max - min)(映射到[0,1])
2. 处理类别不平衡
方法:
python
from sklearn.svm import SVC
# 方法1:调整类别权重
model = SVC(class_weight='balanced')
# 方法2:自定义权重
model = SVC(class_weight={0: 1, 1: 10})
3. 特征选择
原因:SVM在高维空间中计算成本高
方法:
- 相关性分析
- 主成分分析(PCA)
- 递归特征消除(RFE)
模型训练与优化
1. 核函数选择
选择指南:
- 线性核:特征数 >> 样本数,或数据线性可分
- RBF核:数据非线性,样本数适中(默认选择)
- 多项式核:数据有明显的多项式关系
- Sigmoid核:很少使用
经验法则:
- 先尝试RBF核
- 如果效果不好,再尝试其他核
2. 超参数调优
主要超参数:
C:惩罚参数(通常0.001 - 1000)gamma:RBF核参数(通常0.0001 - 10)kernel:核函数类型degree:多项式核的次数(通常2-5)
调优方法:
python
from sklearn.model_selection import GridSearchCV
param_grid = {
'C': [0.1, 1, 10, 100],
'gamma': [0.001, 0.01, 0.1, 1],
'kernel': ['rbf', 'poly', 'sigmoid']
}
grid_search = GridSearchCV(SVC(), param_grid, cv=5, scoring='accuracy')
grid_search.fit(X_train, y_train)
3. 处理大规模数据
问题:SVM训练时间复杂度为O(n²)或O(n³),不适合大规模数据
解决方案:
- 使用
LinearSVC(线性SVM,更快) - 采样数据
- 使用增量学习
- 使用近似方法(如Nystroem方法)
Python实现
python
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
def svm_demo():
"""
支持向量机完整示例
"""
print("=" * 60)
print("支持向量机 (SVM) 示例")
print("=" * 60)
# 1. 生成数据
X, y = make_classification(
n_samples=200,
n_features=2,
n_redundant=0,
n_informative=2,
n_clusters_per_class=1,
random_state=42
)
# 2. 数据标准化(SVM对尺度敏感)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y, test_size=0.2, random_state=42
)
# 3. 训练不同核函数的SVM
kernels = ['linear', 'poly', 'rbf', 'sigmoid']
models = {}
for kernel in kernels:
model = SVC(kernel=kernel, random_state=42, probability=True)
model.fit(X_train, y_train)
models[kernel] = model
test_acc = accuracy_score(y_test, model.predict(X_test))
print(f"{kernel} 核函数 - 测试准确率: {test_acc:.4f}")
# 4. 可视化不同核函数的效果
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
axes = axes.flatten()
h = 0.02
x_min, x_max = X_scaled[:, 0].min() - 1, X_scaled[:, 0].max() + 1
y_min, y_max = X_scaled[:, 1].min() - 1, X_scaled[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
for idx, (kernel, model) in enumerate(models.items()):
ax = axes[idx]
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
ax.contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.RdYlBu)
scatter = ax.scatter(X_scaled[:, 0], X_scaled[:, 1], c=y,
cmap=plt.cm.RdYlBu, edgecolors='black')
ax.set_title(f'{kernel} 核函数', fontsize=14, fontweight='bold')
ax.set_xlabel('特征 1', fontsize=12)
ax.set_ylabel('特征 2', fontsize=12)
plt.tight_layout()
plt.savefig('docs/images/svm_demo.png', dpi=300, bbox_inches='tight')
plt.show()
return models
# 运行示例
if __name__ == '__main__':
models = svm_demo()
模型后处理
1. 支持向量分析
查看支持向量:
python
# 支持向量的数量
n_support = model.n_support_
print(f"支持向量数量: {n_support}")
# 支持向量的索引
support_vectors = model.support_
print(f"支持向量索引: {support_vectors}")
# 支持向量的系数
support_coef = model.dual_coef_
支持向量比例:
- 比例高:模型可能过拟合
- 比例低:模型泛化能力好
2. 决策边界可视化
2D可视化:
python
def plot_decision_boundary(model, X, y):
h = 0.02
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.RdYlBu)
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.RdYlBu, edgecolors='black')
# 标记支持向量
support_vectors = model.support_vectors_
plt.scatter(support_vectors[:, 0], support_vectors[:, 1],
s=200, facecolors='none', edgecolors='red', linewidths=2)
3. 概率预测校准
SVM概率输出:
python
model = SVC(probability=True) # 需要设置probability=True
model.fit(X_train, y_train)
y_proba = model.predict_proba(X_test)
注意:SVM的概率输出使用Platt Scaling,可能不够准确
实际应用案例
案例1:文本分类
问题描述:新闻文章分类(政治、体育、科技等)
数据预处理:
python
from sklearn.feature_extraction.text import TfidfVectorizer
# TF-IDF特征提取
vectorizer = TfidfVectorizer(max_features=10000, ngram_range=(1, 2))
X = vectorizer.fit_transform(texts)
# 特征已经标准化(TF-IDF本身就是标准化的)
模型训练:
python
from sklearn.svm import LinearSVC # 线性SVM,更快
model = LinearSVC(C=1.0, class_weight='balanced')
model.fit(X_train, y_train)
结果:
- 准确率:92%
- 训练速度快(线性SVM)
- 适合高维稀疏数据
案例2:图像分类
问题描述:手写数字识别(MNIST)
数据预处理:
python
from sklearn.preprocessing import StandardScaler
# 图像数据展平
X = images.reshape(len(images), -1) # (n_samples, 784)
# 标准化像素值
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
模型训练:
python
from sklearn.svm import SVC
# 使用RBF核处理非线性
model = SVC(kernel='rbf', C=10, gamma=0.001)
model.fit(X_train_scaled, y_train)
优化:
- 使用PCA降维(减少特征数)
- 使用网格搜索调参
- 考虑使用线性SVM + 特征工程
优缺点分析
优点:
- 泛化能力强:最大化间隔,泛化性能好
- 支持非线性:通过核技巧处理非线性问题
- 内存效率高:只需要存储支持向量
- 对异常值相对稳健:软间隔SVM可以处理异常值
- 适合高维数据:在高维空间中表现好
缺点:
- 训练时间长:时间复杂度O(n²)或O(n³)
- 不适合大规模数据:样本数>10万时训练很慢
- 对参数敏感:C和gamma需要仔细调优
- 可解释性差:难以解释决策过程
- 需要特征缩放:必须进行特征标准化
- 概率输出不准确:概率预测使用近似方法
常见问题与解决方案
Q1: SVM训练太慢怎么办?
解决方案:
- 使用LinearSVC(线性SVM):
python
from sklearn.svm import LinearSVC
model = LinearSVC() # 比SVC快很多
- 减少样本数:
python
from sklearn.model_selection import train_test_split
X_small, _, y_small, _ = train_test_split(X, y, train_size=10000, random_state=42)
- 特征降维:
python
from sklearn.decomposition import PCA
pca = PCA(n_components=100)
X_reduced = pca.fit_transform(X)
Q2: 如何选择C和gamma?
方法:
python
from sklearn.model_selection import GridSearchCV
param_grid = {
'C': [0.1, 1, 10, 100, 1000],
'gamma': [0.0001, 0.001, 0.01, 0.1, 1]
}
grid_search = GridSearchCV(SVC(kernel='rbf'), param_grid, cv=5)
grid_search.fit(X_train, y_train)
经验值:
- C:通常0.1 - 100
- gamma:通常0.001 - 1
Q3: 如何选择核函数?
选择指南:
- 线性核:特征数 >> 样本数,或数据线性可分
- RBF核:默认选择,适合大多数情况
- 多项式核:数据有明显的多项式关系
测试方法:
python
kernels = ['linear', 'rbf', 'poly']
for kernel in kernels:
model = SVC(kernel=kernel)
scores = cross_val_score(model, X_train, y_train, cv=5)
print(f"{kernel}: {scores.mean():.4f}")
Q4: 支持向量太多怎么办?
可能原因:
- C太大(过拟合)
- gamma太大(决策边界太复杂)
解决方案:
- 减小C值
- 减小gamma值
- 增加训练数据
- 特征选择,减少噪声特征
Q5: 如何处理多分类问题?
方法:
- One-vs-Rest:默认方法,训练K个二分类器
- One-vs-One:训练K(K-1)/2个二分类器
python
# One-vs-Rest(默认)
model = SVC(decision_function_shape='ovr')
# One-vs-One
model = SVC(decision_function_shape='ovo')
算法流程图
开始
↓
数据加载与探索
↓
数据清洗(缺失值、异常值)
↓
特征缩放(标准化/归一化,必须!)
↓
特征选择(如果特征太多)
↓
数据划分(训练集/验证集/测试集)
↓
选择核函数(线性/RBF/多项式)
↓
超参数调优(C、gamma、degree)
↓
模型训练(求解二次规划问题)
↓
模型评估(准确率/精确率/召回率)
↓
支持向量分析
↓
决策边界可视化
↓
模型部署
↓
结束
算法4:朴素贝叶斯 (Naive Bayes)
算法原理
朴素贝叶斯基于贝叶斯定理,假设特征之间相互独立。
核心思想:根据先验概率和条件概率计算后验概率。
数学公式
贝叶斯定理:
P(y|x) = P(x|y) · P(y) / P(x)
朴素假设:
P(x₁, x₂, ..., xₙ|y) = P(x₁|y) · P(x₂|y) · ... · P(xₙ|y)
Python实现
python
from sklearn.naive_bayes import GaussianNB, MultinomialNB, BernoulliNB
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.datasets import fetch_20newsgroups
def naive_bayes_demo():
"""
朴素贝叶斯完整示例
"""
print("=" * 60)
print("朴素贝叶斯 (Naive Bayes) 示例")
print("=" * 60)
# 1. 生成数据
X, y = make_classification(
n_samples=1000,
n_features=10,
n_classes=3,
n_informative=5,
random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 2. 训练高斯朴素贝叶斯
model = GaussianNB()
model.fit(X_train, y_train)
# 3. 预测
y_pred = model.predict(X_test)
y_proba = model.predict_proba(X_test)
# 4. 评估
acc = accuracy_score(y_test, y_pred)
print(f"\n测试集准确率: {acc:.4f}")
print(f"\n分类报告:")
print(classification_report(y_test, y_pred))
# 5. 可视化
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
# 混淆矩阵
ax1 = axes[0]
cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', ax=ax1)
ax1.set_xlabel('预测标签', fontsize=12)
ax1.set_ylabel('真实标签', fontsize=12)
ax1.set_title('混淆矩阵', fontsize=14, fontweight='bold')
# 概率分布
ax2 = axes[1]
for i in range(len(np.unique(y_test))):
ax2.hist(y_proba[y_test == i, i], bins=20, alpha=0.6,
label=f'类别 {i}')
ax2.set_xlabel('预测概率', fontsize=12)
ax2.set_ylabel('频数', fontsize=12)
ax2.set_title('各类别预测概率分布', fontsize=14, fontweight='bold')
ax2.legend()
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('docs/images/naive_bayes_demo.png', dpi=300, bbox_inches='tight')
plt.show()
return model
# 运行示例
if __name__ == '__main__':
model = naive_bayes_demo()
应用场景
- 文本分类(垃圾邮件检测)
- 情感分析
- 推荐系统
- 医疗诊断
算法5:决策树 (Decision Tree)
算法原理
决策树是一种树形结构的分类和回归算法,通过一系列if-else规则对数据进行分类或回归。决策树具有很好的可解释性,是理解数据模式的重要工具。
核心思想:递归地将数据分割成更纯的子集,直到满足停止条件。
算法流程:
1. 选择最优特征 → 2. 分割数据 → 3. 递归构建子树 → 4. 剪枝优化
↓ ↓ ↓ ↓
信息增益/基尼 按阈值分割 左子树/右子树 防止过拟合
不纯度最小化 连续/离散特征 重复步骤1-2 后剪枝/预剪枝
工作原理示意图:
决策树结构:
[根节点]
/ \
[特征1 < 阈值] [特征1 ≥ 阈值]
/ \
[特征2 < 阈值] [特征2 ≥ 阈值]
/ \ / \
[类别A] [类别B] [类别C] [类别D]
每个节点代表一个决策规则
每个叶子节点代表一个类别(或回归值)
数学公式详解
1. 分割准则
信息熵(Entropy):
H(S) = -Σ pᵢ log₂(pᵢ)
- 衡量数据集的混乱程度
- 熵越大,数据越混乱
- 熵为0表示数据完全纯净(只有一个类别)
基尼不纯度(Gini Impurity):
Gini(S) = 1 - Σ pᵢ²
- 另一种衡量混乱程度的方法
- 计算速度比熵快
- 结果通常与熵相似
信息增益(Information Gain):
IG(S, A) = H(S) - Σ (|Sᵥ|/|S|) · H(Sᵥ)
- 选择信息增益最大的特征进行分割
- 信息增益越大,分割效果越好
信息增益比(Gain Ratio):
GainRatio(S, A) = IG(S, A) / H_A(S)
- 解决信息增益偏向多值特征的问题
- H_A(S)是特征A的固有值
2. 分割方法
连续特征:
- 选择最优分割点(阈值)
- 遍历所有可能的分割点,选择信息增益最大的
离散特征:
- 二分类:直接分割
- 多分类:可以多路分割或二值化
3. 停止条件
预剪枝(Pre-pruning):
max_depth:最大深度min_samples_split:节点最小样本数min_samples_leaf:叶子节点最小样本数min_impurity_decrease:最小不纯度减少量
后剪枝(Post-pruning):
- 先构建完整树,再删除不重要的分支
- 使用验证集评估剪枝效果
数据预处理
1. 数据清洗
缺失值处理:
- 决策树可以处理缺失值(sklearn不支持)
- 通常需要填充:用众数/中位数/均值
- 或创建"缺失"类别
异常值处理:
- 决策树对异常值相对稳健
- 但仍需检查和处理极端值
2. 特征工程
特征缩放:
- 不需要特征缩放(决策树的优势)
- 因为决策树基于阈值分割,不受特征尺度影响
类别特征编码:
- Label编码:适用于有序类别
- One-Hot编码:适用于无序类别
特征变换:
- 分箱(Binning):将连续特征离散化
- 可以创建更有意义的特征
3. 处理类别不平衡
方法:
python
model = DecisionTreeClassifier(class_weight='balanced')
# 或
model = DecisionTreeClassifier(class_weight={0: 1, 1: 10})
模型训练与优化
1. 超参数调优
关键超参数:
max_depth:最大深度(防止过拟合)min_samples_split:节点最小样本数(默认2)min_samples_leaf:叶子节点最小样本数(默认1)max_features:每次分割考虑的特征数criterion:分割准则('gini'或'entropy')min_impurity_decrease:最小不纯度减少量
调优方法:
python
from sklearn.model_selection import GridSearchCV
param_grid = {
'max_depth': [3, 5, 7, 10, None],
'min_samples_split': [2, 5, 10],
'min_samples_leaf': [1, 2, 4],
'criterion': ['gini', 'entropy']
}
grid_search = GridSearchCV(DecisionTreeClassifier(), param_grid, cv=5)
grid_search.fit(X_train, y_train)
2. 防止过拟合
方法:
- 限制树深度 :
max_depth - 增加最小样本数 :
min_samples_split,min_samples_leaf - 限制特征数 :
max_features - 后剪枝 :使用
ccp_alpha参数
后剪枝示例:
python
from sklearn.tree import DecisionTreeClassifier
# 计算ccp_alpha路径
model = DecisionTreeClassifier(random_state=42)
path = model.cost_complexity_pruning_path(X_train, y_train)
ccp_alphas = path.ccp_alphas
# 使用交叉验证选择最优alpha
from sklearn.model_selection import cross_val_score
scores = []
for ccp_alpha in ccp_alphas:
model = DecisionTreeClassifier(ccp_alpha=ccp_alpha, random_state=42)
score = cross_val_score(model, X_train, y_train, cv=5).mean()
scores.append(score)
best_alpha = ccp_alphas[np.argmax(scores)]
3. 特征重要性
计算:
python
feature_importance = model.feature_importances_
解释:
- 值越大,特征越重要
- 所有特征重要性之和为1
- 可以用于特征选择
Python实现
python
from sklearn.tree import DecisionTreeClassifier, plot_tree, export_text
from sklearn.datasets import load_iris
def decision_tree_demo():
"""
决策树完整示例
"""
print("=" * 60)
print("决策树 (Decision Tree) 示例")
print("=" * 60)
# 1. 加载Iris数据集
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 2. 训练决策树
model = DecisionTreeClassifier(max_depth=3, random_state=42)
model.fit(X_train, y_train)
# 3. 预测
y_pred = model.predict(X_test)
acc = accuracy_score(y_test, y_pred)
print(f"\n测试集准确率: {acc:.4f}")
print(f"\n决策树结构:")
print(export_text(model, feature_names=iris.feature_names))
# 4. 可视化决策树
plt.figure(figsize=(20, 10))
plot_tree(model, feature_names=iris.feature_names,
class_names=iris.target_names, filled=True, fontsize=10)
plt.title('决策树可视化', fontsize=16, fontweight='bold')
plt.savefig('docs/images/decision_tree_demo.png', dpi=300, bbox_inches='tight')
plt.show()
# 5. 特征重要性
feature_importance = model.feature_importances_
plt.figure(figsize=(10, 6))
plt.barh(iris.feature_names, feature_importance)
plt.xlabel('重要性', fontsize=12)
plt.title('特征重要性', fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3, axis='x')
plt.savefig('docs/images/decision_tree_feature_importance.png', dpi=300, bbox_inches='tight')
plt.show()
return model
# 运行示例
if __name__ == '__main__':
model = decision_tree_demo()
模型后处理
1. 决策树可视化
文本表示:
python
from sklearn.tree import export_text
tree_rules = export_text(model, feature_names=feature_names)
print(tree_rules)
图形可视化:
python
from sklearn.tree import plot_tree
import matplotlib.pyplot as plt
plt.figure(figsize=(20, 10))
plot_tree(model, feature_names=feature_names,
class_names=class_names, filled=True, fontsize=10)
plt.show()
Graphviz可视化(更美观):
python
from sklearn.tree import export_graphviz
import graphviz
dot_data = export_graphviz(model, out_file=None,
feature_names=feature_names,
class_names=class_names,
filled=True, rounded=True)
graph = graphviz.Source(dot_data)
graph.render("decision_tree")
2. 规则提取
提取决策路径:
python
def get_decision_path(model, X_sample):
"""获取样本的决策路径"""
path = model.decision_path(X_sample)
node_indicator = path.toarray()
leaf_id = model.apply(X_sample)
# 获取路径上的节点
node_index = np.where(node_indicator[0] == 1)[0]
return node_index
3. 特征重要性分析
可视化特征重要性:
python
feature_importance = model.feature_importances_
indices = np.argsort(feature_importance)[::-1]
plt.figure(figsize=(10, 6))
plt.barh(range(len(feature_importance)), feature_importance[indices])
plt.yticks(range(len(feature_importance)),
[feature_names[i] for i in indices])
plt.xlabel('重要性', fontsize=12)
plt.title('特征重要性', fontsize=14, fontweight='bold')
plt.show()
实际应用案例
案例1:医疗诊断
问题描述:根据患者症状判断疾病类型
数据预处理:
python
# 处理类别特征
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df['症状'] = le.fit_transform(df['症状'])
# 处理数值特征(不需要缩放)
X = df[['年龄', '体温', '血压', '症状']]
y = df['疾病类型']
模型训练:
python
model = DecisionTreeClassifier(
max_depth=5,
min_samples_split=10,
min_samples_leaf=5,
criterion='gini',
random_state=42
)
model.fit(X_train, y_train)
模型解释:
- 可视化决策树,医生可以理解诊断规则
- 特征重要性显示哪些症状最关键
- 可以提取if-else规则用于医疗系统
案例2:信用评估
问题描述:根据客户信息评估信用等级
特征工程:
python
# 创建特征
df['收入负债比'] = df['月收入'] / (df['月负债'] + 1)
df['信用历史长度'] = 2024 - df['首次信用记录年份']
# 分箱处理
df['年龄组'] = pd.cut(df['年龄'], bins=[0, 30, 40, 50, 100],
labels=['青年', '中年', '中老年', '老年'])
模型训练:
python
model = DecisionTreeClassifier(
max_depth=6,
min_samples_split=20,
min_samples_leaf=10,
class_weight='balanced',
random_state=42
)
结果解释:
- 决策规则清晰,符合业务逻辑
- 可以用于解释拒绝贷款的原因
- 特征重要性指导特征工程
优缺点分析
优点:
- 可解释性强:决策过程清晰,易于理解
- 不需要特征缩放:不受特征尺度影响
- 可以处理混合数据类型:数值和类别特征
- 可以处理非线性关系:通过树结构捕捉复杂模式
- 特征重要性:自动提供特征重要性
- 对异常值稳健:基于阈值分割,对异常值不敏感
缺点:
- 容易过拟合:需要仔细调参和剪枝
- 不稳定:数据微小变化可能导致树结构大变化
- 偏向多值特征:信息增益偏向取值多的特征
- 难以处理连续输出:回归任务效果不如分类
- 忽略特征间相关性:每次只考虑一个特征
- 可能产生偏向:如果数据不平衡
常见问题与解决方案
Q1: 决策树过拟合怎么办?
解决方案:
- 限制树深度 :
max_depth=5(根据数据调整) - 增加最小样本数 :
min_samples_split=20,min_samples_leaf=10 - 后剪枝 :使用
ccp_alpha参数 - 使用集成方法:随机森林、梯度提升
Q2: 如何选择最优的max_depth?
方法:
python
depths = range(1, 21)
train_scores = []
test_scores = []
for depth in depths:
model = DecisionTreeClassifier(max_depth=depth, random_state=42)
model.fit(X_train, y_train)
train_scores.append(model.score(X_train, y_train))
test_scores.append(model.score(X_test, y_test))
# 选择测试集得分最高的深度
optimal_depth = depths[np.argmax(test_scores)]
Q3: 如何解释决策树?
方法:
- 可视化树结构 :使用
plot_tree或export_graphviz - 提取规则 :使用
export_text - 分析特征重要性 :查看
feature_importances_ - 追踪样本路径 :使用
decision_path
Q4: 决策树不稳定怎么办?
问题:数据微小变化导致树结构大变化
解决方案:
- 使用随机森林(多个树的平均)
- 增加
min_samples_split和min_samples_leaf - 使用集成方法
Q5: 如何处理类别不平衡?
方法:
python
# 方法1:调整类别权重
model = DecisionTreeClassifier(class_weight='balanced')
# 方法2:自定义权重
model = DecisionTreeClassifier(class_weight={0: 1, 1: 5})
# 方法3:使用SMOTE过采样
from imblearn.over_sampling import SMOTE
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)
算法流程图
开始
↓
数据加载与探索
↓
数据清洗(缺失值、异常值)
↓
特征工程(编码类别特征、创建新特征)
↓
数据划分(训练集/验证集/测试集)
↓
选择分割准则(Gini/Entropy)
↓
构建决策树(递归分割)
↓
├─ 选择最优特征和阈值
↓
├─ 分割数据
↓
├─ 递归构建子树
↓
└─ 直到满足停止条件
↓
剪枝优化(预剪枝/后剪枝)
↓
模型评估(准确率/精确率/召回率)
↓
可视化决策树
↓
提取决策规则
↓
特征重要性分析
↓
模型部署
↓
结束
算法6:随机森林 (Random Forest)
算法原理
随机森林是多个决策树的集成,通过投票或平均来做出最终预测。
核心思想:多个弱学习器组合成强学习器。
Python实现
python
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
def random_forest_demo():
"""
随机森林完整示例
"""
print("=" * 60)
print("随机森林 (Random Forest) 示例")
print("=" * 60)
# 1. 生成数据
X, y = make_classification(
n_samples=1000,
n_features=20,
n_informative=10,
n_classes=3,
random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 2. 训练不同树数量的随机森林
n_trees = [10, 50, 100, 200]
accuracies = []
for n_tree in n_trees:
model = RandomForestClassifier(n_estimators=n_tree, random_state=42)
model.fit(X_train, y_train)
acc = accuracy_score(y_test, model.predict(X_test))
accuracies.append(acc)
print(f"树数量: {n_tree:3d} - 准确率: {acc:.4f}")
# 3. 可视化树数量对性能的影响
plt.figure(figsize=(10, 6))
plt.plot(n_trees, accuracies, 'o-', linewidth=2, markersize=8)
plt.xlabel('树数量', fontsize=12)
plt.ylabel('准确率', fontsize=12)
plt.title('随机森林性能 vs 树数量', fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3)
plt.savefig('docs/images/random_forest_demo.png', dpi=300, bbox_inches='tight')
plt.show()
# 4. 特征重要性
best_model = RandomForestClassifier(n_estimators=100, random_state=42)
best_model.fit(X_train, y_train)
feature_importance = best_model.feature_importances_
top_features = np.argsort(feature_importance)[-10:][::-1]
plt.figure(figsize=(10, 6))
plt.barh(range(len(top_features)), feature_importance[top_features])
plt.yticks(range(len(top_features)), [f'特征 {i}' for i in top_features])
plt.xlabel('重要性', fontsize=12)
plt.title('Top 10 特征重要性', fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3, axis='x')
plt.savefig('docs/images/random_forest_feature_importance.png', dpi=300, bbox_inches='tight')
plt.show()
return best_model
# 运行示例
if __name__ == '__main__':
model = random_forest_demo()
应用场景
- 特征选择
- 异常检测
- 任何需要高准确率的分类/回归问题
算法7:K近邻 (KNN)
算法原理
K近邻通过查找最近的K个样本,根据它们的标签进行预测。
核心思想:相似的样本应该有相似的标签。
Python实现
python
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
def knn_demo():
"""
K近邻完整示例
"""
print("=" * 60)
print("K近邻 (KNN) 示例")
print("=" * 60)
# 1. 生成数据
X, y = make_classification(
n_samples=300,
n_features=2,
n_redundant=0,
n_informative=2,
n_clusters_per_class=1,
random_state=42
)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y, test_size=0.2, random_state=42
)
# 2. 测试不同的K值
k_values = range(1, 21)
train_accuracies = []
test_accuracies = []
for k in k_values:
model = KNeighborsClassifier(n_neighbors=k)
model.fit(X_train, y_train)
train_acc = accuracy_score(y_train, model.predict(X_train))
test_acc = accuracy_score(y_test, model.predict(X_test))
train_accuracies.append(train_acc)
test_accuracies.append(test_acc)
# 3. 可视化K值对性能的影响
plt.figure(figsize=(12, 6))
plt.plot(k_values, train_accuracies, 'o-', label='训练集', linewidth=2)
plt.plot(k_values, test_accuracies, 's-', label='测试集', linewidth=2)
plt.xlabel('K值', fontsize=12)
plt.ylabel('准确率', fontsize=12)
plt.title('K值对KNN性能的影响', fontsize=14, fontweight='bold')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(k_values[::2])
plt.savefig('docs/images/knn_demo.png', dpi=300, bbox_inches='tight')
plt.show()
# 4. 可视化决策边界(最佳K值)
best_k = k_values[np.argmax(test_accuracies)]
print(f"\n最佳K值: {best_k}")
model = KNeighborsClassifier(n_neighbors=best_k)
model.fit(X_train, y_train)
h = 0.02
x_min, x_max = X_scaled[:, 0].min() - 1, X_scaled[:, 0].max() + 1
y_min, y_max = X_scaled[:, 1].min() - 1, X_scaled[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.figure(figsize=(10, 8))
plt.contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.RdYlBu)
scatter = plt.scatter(X_scaled[:, 0], X_scaled[:, 1], c=y,
cmap=plt.cm.RdYlBu, edgecolors='black', s=50)
plt.xlabel('特征 1', fontsize=12)
plt.ylabel('特征 2', fontsize=12)
plt.title(f'KNN决策边界 (K={best_k})', fontsize=14, fontweight='bold')
plt.colorbar(scatter)
plt.savefig('docs/images/knn_decision_boundary.png', dpi=300, bbox_inches='tight')
plt.show()
return model
# 运行示例
if __name__ == '__main__':
model = knn_demo()
应用场景
- 推荐系统
- 图像识别
- 模式识别
- 任何需要局部相似性的问题
算法8:K均值聚类 (K-Means)
算法原理
K均值聚类是一种无监督学习算法,将数据分成K个簇,使得簇内距离最小,簇间距离最大。它是应用最广泛的聚类算法之一。
核心思想:迭代优化簇中心位置,使得每个数据点到其最近簇中心的距离平方和最小。
算法流程:
1. 初始化K个簇中心 → 2. 分配数据点到最近簇 → 3. 更新簇中心 → 4. 重复2-3直到收敛
↓ ↓ ↓
随机选择K个点 计算距离,分配 计算新中心(均值)
或K-means++初始化 到最近簇 更新中心位置
工作原理示意图:
初始状态:
● ● ●
● ● ●
● ● ●
● ● ●
[随机选择K个中心]
迭代过程:
● ● ●
● ● ●
● ● ●
● ● ●
[分配点到最近中心]
[更新中心位置]
[重复直到收敛]
最终结果:
●●●
●●●
●●●
[K个簇,每个簇内数据相似]
数学公式详解
1. 目标函数
簇内平方和(Within-Cluster Sum of Squares, WCSS):
J = Σᵢ₌₁ⁿ minⱼ ||xᵢ - μⱼ||²
其中:
xᵢ:第i个数据点μⱼ:第j个簇中心n:数据点数量K:簇数量
目标:最小化J,即最小化簇内距离
2. 算法步骤
步骤1:初始化簇中心
- 随机选择K个数据点作为初始中心
- 或使用K-means++(更智能的初始化)
步骤2:分配数据点
cᵢ = argminⱼ ||xᵢ - μⱼ||²
将每个数据点分配到最近的簇中心
步骤3:更新簇中心
μⱼ = (1/|Cⱼ|) × Σxᵢ∈Cⱼ xᵢ
计算每个簇中所有数据点的均值作为新中心
步骤4:重复步骤2-3
直到簇中心不再变化或达到最大迭代次数
3. K-means++初始化
目的:选择更好的初始中心,避免局部最优
方法:
- 随机选择第一个中心
- 选择下一个中心时,优先选择距离已有中心较远的点
- 重复直到选择K个中心
数据预处理
1. 特征缩放(必须!)
为什么必须缩放:
- K-means基于欧氏距离,对特征尺度敏感
- 不同尺度的特征会导致某些特征主导聚类结果
方法:
python
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
2. 处理缺失值
方法:
- 删除缺失值
- 用均值/中位数填充
- 用模型预测填充
3. 处理异常值
问题:异常值会影响簇中心位置
方法:
- 使用Z-score方法检测异常值
- 删除或截断异常值
- 使用鲁棒聚类方法(如K-medoids)
4. 特征选择
目的:选择相关特征,提高聚类质量
方法:
- 相关性分析
- 主成分分析(PCA)
- 领域知识
模型训练与优化
1. 选择K值
方法1:肘部法则(Elbow Method)
python
inertias = []
K_range = range(1, 11)
for k in K_range:
kmeans = KMeans(n_clusters=k, random_state=42)
kmeans.fit(X)
inertias.append(kmeans.inertia_)
# 绘制肘部曲线,选择"肘部"对应的K值
plt.plot(K_range, inertias, 'o-')
plt.xlabel('K值')
plt.ylabel('Inertia')
plt.title('肘部法则')
plt.show()
方法2:轮廓系数(Silhouette Score)
python
from sklearn.metrics import silhouette_score
silhouette_scores = []
K_range = range(2, 11)
for k in K_range:
kmeans = KMeans(n_clusters=k, random_state=42)
labels = kmeans.fit_predict(X)
score = silhouette_score(X, labels)
silhouette_scores.append(score)
# 选择轮廓系数最大的K值
optimal_k = K_range[np.argmax(silhouette_scores)]
方法3:Gap统计量
- 比较实际数据与随机数据的聚类质量
- 选择Gap最大的K值
2. 初始化方法
随机初始化:
python
kmeans = KMeans(n_clusters=k, init='random', n_init=10)
K-means++(推荐):
python
kmeans = KMeans(n_clusters=k, init='k-means++', n_init=10)
n_init参数:
- 运行多次,选择最好的结果
- 默认10次,可以增加以提高稳定性
3. 处理局部最优
方法:
- 增加
n_init参数(多次运行) - 使用K-means++初始化
- 使用不同的随机种子
模型后处理
1. 评估聚类质量
轮廓系数:
python
from sklearn.metrics import silhouette_score, silhouette_samples
# 整体轮廓系数
score = silhouette_score(X, labels)
# 每个样本的轮廓系数
samples_silhouette = silhouette_samples(X, labels)
Davies-Bouldin指数:
python
from sklearn.metrics import davies_bouldin_score
db_score = davies_bouldin_score(X, labels)
# 值越小越好
Calinski-Harabasz指数:
python
from sklearn.metrics import calinski_harabasz_score
ch_score = calinski_harabasz_score(X, labels)
# 值越大越好
2. 簇分析
簇大小:
python
unique, counts = np.unique(labels, return_counts=True)
for cluster_id, count in zip(unique, counts):
print(f"簇 {cluster_id}: {count} 个样本")
簇中心特征:
python
centers = kmeans.cluster_centers_
for i, center in enumerate(centers):
print(f"簇 {i} 中心: {center}")
簇内距离:
python
for i in range(k):
cluster_points = X[labels == i]
distances = np.linalg.norm(cluster_points - centers[i], axis=1)
print(f"簇 {i} 平均距离: {np.mean(distances):.4f}")
3. 可视化
2D可视化:
python
plt.scatter(X[:, 0], X[:, 1], c=labels, cmap='viridis', alpha=0.6)
plt.scatter(centers[:, 0], centers[:, 1], c='red', marker='x', s=200, linewidths=3)
plt.title('K-Means聚类结果')
plt.show()
轮廓图:
python
from sklearn.metrics import silhouette_samples
import matplotlib.cm as cm
fig, ax = plt.subplots(figsize=(10, 6))
y_lower = 10
for i in range(k):
ith_cluster_silhouette_values = samples_silhouette[labels == i]
ith_cluster_silhouette_values.sort()
size_cluster_i = ith_cluster_silhouette_values.shape[0]
y_upper = y_lower + size_cluster_i
color = cm.nipy_spectral(float(i) / k)
ax.fill_betweenx(np.arange(y_lower, y_upper),
0, ith_cluster_silhouette_values,
facecolor=color, edgecolor=color, alpha=0.7)
y_lower = y_upper + 10
ax.set_title('轮廓图')
ax.set_xlabel('轮廓系数')
ax.set_ylabel('簇标签')
plt.show()
Python实现
python
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score, silhouette_samples
def kmeans_demo():
"""
K均值聚类完整示例
"""
print("=" * 60)
print("K均值聚类 (K-Means) 示例")
print("=" * 60)
# 1. 生成数据
X, y_true = make_blobs(
n_samples=300,
centers=4,
n_features=2,
random_state=42
)
# 2. 测试不同的K值
k_range = range(2, 11)
inertias = []
silhouette_scores = []
for k in k_range:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
labels = kmeans.fit_predict(X)
inertias.append(kmeans.inertia_)
silhouette_scores.append(silhouette_score(X, labels))
# 3. 可视化肘部法则和轮廓系数
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
# 肘部法则
ax1 = axes[0]
ax1.plot(k_range, inertias, 'o-', linewidth=2, markersize=8)
ax1.set_xlabel('K值', fontsize=12)
ax1.set_ylabel('Inertia (簇内平方和)', fontsize=12)
ax1.set_title('肘部法则 (Elbow Method)', fontsize=14, fontweight='bold')
ax1.grid(True, alpha=0.3)
ax1.set_xticks(k_range)
# 轮廓系数
ax2 = axes[1]
ax2.plot(k_range, silhouette_scores, 'o-', linewidth=2, markersize=8, color='green')
ax2.set_xlabel('K值', fontsize=12)
ax2.set_ylabel('轮廓系数', fontsize=12)
ax2.set_title('轮廓系数 vs K值', fontsize=14, fontweight='bold')
ax2.grid(True, alpha=0.3)
ax2.set_xticks(k_range)
plt.tight_layout()
plt.savefig('docs/images/kmeans_evaluation.png', dpi=300, bbox_inches='tight')
plt.show()
# 4. 可视化聚类结果(最佳K值)
best_k = k_range[np.argmax(silhouette_scores)]
print(f"\n最佳K值: {best_k}")
kmeans = KMeans(n_clusters=best_k, random_state=42, n_init=10)
labels = kmeans.fit_predict(X)
centers = kmeans.cluster_centers_
plt.figure(figsize=(12, 5))
# 真实标签
plt.subplot(1, 2, 1)
scatter1 = plt.scatter(X[:, 0], X[:, 1], c=y_true, cmap='viridis', s=50, alpha=0.6)
plt.scatter(centers[:, 0], centers[:, 1], c='red', marker='x', s=200, linewidths=3, label='聚类中心')
plt.xlabel('特征 1', fontsize=12)
plt.ylabel('特征 2', fontsize=12)
plt.title('真实标签', fontsize=14, fontweight='bold')
plt.legend()
plt.colorbar(scatter1)
# 预测标签
plt.subplot(1, 2, 2)
scatter2 = plt.scatter(X[:, 0], X[:, 1], c=labels, cmap='viridis', s=50, alpha=0.6)
plt.scatter(centers[:, 0], centers[:, 1], c='red', marker='x', s=200, linewidths=3, label='聚类中心')
plt.xlabel('特征 1', fontsize=12)
plt.ylabel('特征 2', fontsize=12)
plt.title(f'K-Means聚类结果 (K={best_k})', fontsize=14, fontweight='bold')
plt.legend()
plt.colorbar(scatter2)
plt.tight_layout()
plt.savefig('docs/images/kmeans_demo.png', dpi=300, bbox_inches='tight')
plt.show()
return kmeans
# 运行示例
if __name__ == '__main__':
model = kmeans_demo()
实际应用案例
案例1:客户细分
问题描述:根据客户行为数据将客户分成不同群体
数据预处理:
python
# 特征工程
df['消费频率'] = df['订单数'] / df['注册天数']
df['平均订单金额'] = df['总消费'] / df['订单数']
df['最近购买天数'] = (pd.Timestamp.now() - df['最后购买日期']).dt.days
# 特征选择
features = ['消费频率', '平均订单金额', '最近购买天数', '浏览时长']
X = df[features]
# 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
选择K值:
python
# 使用肘部法则和轮廓系数
k_range = range(2, 11)
inertias = []
silhouette_scores = []
for k in k_range:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
labels = kmeans.fit_predict(X_scaled)
inertias.append(kmeans.inertia_)
silhouette_scores.append(silhouette_score(X_scaled, labels))
# 选择最优K(假设K=5)
optimal_k = 5
结果分析:
- 簇1:高价值客户(高消费、高频)
- 簇2:潜在流失客户(长时间未购买)
- 簇3:新客户(低消费、低频)
- 簇4:稳定客户(中等消费、中等频率)
- 簇5:低价值客户(低消费、低频)
案例2:图像分割
问题描述:将图像分成K个颜色区域
数据预处理:
python
# 将图像转换为像素点
image = plt.imread('image.jpg')
pixels = image.reshape(-1, 3) # (height*width, RGB)
# 标准化(K-means需要)
scaler = StandardScaler()
pixels_scaled = scaler.fit_transform(pixels)
聚类:
python
# 选择K=8(8种主要颜色)
kmeans = KMeans(n_clusters=8, random_state=42)
labels = kmeans.fit_predict(pixels_scaled)
# 用簇中心替换像素值
segmented_pixels = kmeans.cluster_centers_[labels]
segmented_image = segmented_pixels.reshape(image.shape)
优缺点分析
优点:
- 简单高效:算法简单,易于实现和理解
- 计算速度快:时间复杂度O(n×K×I×d),其中I是迭代次数
- 可扩展性好:可以处理大规模数据
- 结果可解释:每个簇有明确的中心
- 适用于球形簇:适合数据呈球形分布的情况
缺点:
- 需要指定K值:K值选择困难
- 对初始值敏感:可能陷入局部最优
- 假设球形簇:不适合非球形簇(如月牙形)
- 对异常值敏感:异常值会影响簇中心
- 需要特征缩放:对特征尺度敏感
- 不适合类别特征:只适用于数值特征
常见问题与解决方案
Q1: 如何选择最优K值?
方法:
- 肘部法则:寻找inertia下降的"肘部"
- 轮廓系数:选择轮廓系数最大的K
- 领域知识:根据业务需求确定K
- Gap统计量:比较实际数据与随机数据
代码示例:
python
# 综合方法
def find_optimal_k(X, max_k=10):
inertias = []
silhouette_scores = []
K_range = range(2, max_k + 1)
for k in K_range:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
labels = kmeans.fit_predict(X)
inertias.append(kmeans.inertia_)
silhouette_scores.append(silhouette_score(X, labels))
# 肘部法则
# 计算二阶导数,找到变化最大的点
# 或直接观察图形
# 轮廓系数
optimal_k_silhouette = K_range[np.argmax(silhouette_scores)]
return optimal_k_silhouette
Q2: 如何避免局部最优?
解决方案:
- 多次运行 :增加
n_init参数
python
kmeans = KMeans(n_clusters=k, n_init=50, random_state=42)
- K-means++初始化:
python
kmeans = KMeans(n_clusters=k, init='k-means++', n_init=10)
- 不同随机种子:
python
best_score = -1
best_labels = None
for seed in range(10):
kmeans = KMeans(n_clusters=k, random_state=seed, n_init=1)
labels = kmeans.fit_predict(X)
score = silhouette_score(X, labels)
if score > best_score:
best_score = score
best_labels = labels
Q3: 如何处理非球形簇?
解决方案:
-
使用其他聚类算法:
- DBSCAN(基于密度)
- 谱聚类(Spectral Clustering)
- 高斯混合模型(GMM)
-
特征变换:
- 使用核方法将数据映射到高维空间
- 使用PCA降维后再聚类
Q4: 如何处理类别特征?
方法:
- One-Hot编码:将类别特征转换为数值
- 使用K-modes:专门处理类别数据的算法
- 混合距离:结合数值和类别距离
Q5: 如何评估聚类质量?
方法:
python
# 1. 轮廓系数(-1到1,越大越好)
silhouette_score(X, labels)
# 2. Davies-Bouldin指数(越小越好)
davies_bouldin_score(X, labels)
# 3. Calinski-Harabasz指数(越大越好)
calinski_harabasz_score(X, labels)
# 4. 如果有真实标签,使用调整兰德指数
from sklearn.metrics import adjusted_rand_score
adjusted_rand_score(y_true, labels)
算法流程图
开始
↓
数据加载与探索
↓
数据清洗(缺失值、异常值)
↓
特征工程(特征选择、创建新特征)
↓
特征缩放(标准化/归一化,必须!)
↓
选择K值(肘部法则/轮廓系数/Gap统计量)
↓
初始化簇中心(随机/K-means++)
↓
分配数据点到最近簇中心
↓
更新簇中心(计算均值)
↓
├─ 中心是否变化?
↓ 否
是 ↓
↓ 继续迭代
↓
达到最大迭代次数?
↓
评估聚类质量(轮廓系数/DB指数)
↓
可视化聚类结果
↓
分析每个簇的特征
↓
应用聚类结果
↓
结束
算法9:主成分分析 (PCA)
算法原理
PCA通过线性变换将高维数据投影到低维空间,保留最重要的信息。
核心思想:找到数据方差最大的方向(主成分)。
数学公式
协方差矩阵:
C = (1/n) X^T X
特征值分解:
C = V Λ V^T
Python实现
python
from sklearn.decomposition import PCA
from sklearn.datasets import load_iris
def pca_demo():
"""
主成分分析完整示例
"""
print("=" * 60)
print("主成分分析 (PCA) 示例")
print("=" * 60)
# 1. 加载Iris数据集
iris = load_iris()
X, y = iris.data, iris.target
# 2. 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 3. PCA降维
pca = PCA()
X_pca = pca.fit_transform(X_scaled)
# 4. 可视化解释方差比例
explained_variance_ratio = pca.explained_variance_ratio_
cumulative_variance = np.cumsum(explained_variance_ratio)
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
# 解释方差比例
ax1 = axes[0, 0]
ax1.bar(range(1, len(explained_variance_ratio) + 1),
explained_variance_ratio, alpha=0.7)
ax1.set_xlabel('主成分', fontsize=12)
ax1.set_ylabel('解释方差比例', fontsize=12)
ax1.set_title('各主成分解释方差比例', fontsize=14, fontweight='bold')
ax1.grid(True, alpha=0.3, axis='y')
# 累积解释方差
ax2 = axes[0, 1]
ax2.plot(range(1, len(cumulative_variance) + 1), cumulative_variance,
'o-', linewidth=2, markersize=8)
ax2.axhline(y=0.95, color='r', linestyle='--', label='95%阈值')
ax2.set_xlabel('主成分数量', fontsize=12)
ax2.set_ylabel('累积解释方差比例', fontsize=12)
ax2.set_title('累积解释方差', fontsize=14, fontweight='bold')
ax2.legend()
ax2.grid(True, alpha=0.3)
# 2D投影
ax3 = axes[1, 0]
scatter = ax3.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='viridis', s=50, alpha=0.6)
ax3.set_xlabel(f'第一主成分 (解释方差: {explained_variance_ratio[0]:.2%})', fontsize=12)
ax3.set_ylabel(f'第二主成分 (解释方差: {explained_variance_ratio[1]:.2%})', fontsize=12)
ax3.set_title('PCA 2D投影', fontsize=14, fontweight='bold')
plt.colorbar(scatter, ax=ax3)
# 3D投影(如果可能)
if X_pca.shape[1] >= 3:
ax4 = axes[1, 1]
scatter = ax4.scatter(X_pca[:, 0], X_pca[:, 1], X_pca[:, 2],
c=y, cmap='viridis', s=50, alpha=0.6)
ax4.set_xlabel('PC1', fontsize=12)
ax4.set_ylabel('PC2', fontsize=12)
ax4.set_zlabel('PC3', fontsize=12)
ax4.set_title('PCA 3D投影', fontsize=14, fontweight='bold')
else:
ax4.axis('off')
plt.tight_layout()
plt.savefig('docs/images/pca_demo.png', dpi=300, bbox_inches='tight')
plt.show()
print(f"\n各主成分解释方差比例:")
for i, ratio in enumerate(explained_variance_ratio):
print(f" PC{i+1}: {ratio:.4f} ({ratio*100:.2f}%)")
print(f"\n前2个主成分累积解释方差: {cumulative_variance[1]:.4f} ({cumulative_variance[1]*100:.2f}%)")
return pca
# 运行示例
if __name__ == '__main__':
pca = pca_demo()
应用场景
- 数据可视化
- 特征降维
- 噪声过滤
- 数据压缩
算法10:梯度提升树 (Gradient Boosting)
算法原理
梯度提升通过串行训练多个弱学习器,每个学习器纠正前一个的错误。
核心思想:逐步减少残差。
Python实现
python
from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor
def gradient_boosting_demo():
"""
梯度提升树完整示例
"""
print("=" * 60)
print("梯度提升树 (Gradient Boosting) 示例")
print("=" * 60)
# 1. 生成数据
X, y = make_classification(
n_samples=1000,
n_features=20,
n_informative=10,
n_classes=2,
random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 2. 训练梯度提升树
model = GradientBoostingClassifier(
n_estimators=100,
learning_rate=0.1,
max_depth=3,
random_state=42
)
model.fit(X_train, y_train)
# 3. 预测
y_pred = model.predict(X_test)
y_proba = model.predict_proba(X_test)[:, 1]
acc = accuracy_score(y_test, y_pred)
print(f"\n测试集准确率: {acc:.4f}")
# 4. 可视化训练过程
train_scores = np.zeros((model.n_estimators,), dtype=np.float64)
test_scores = np.zeros((model.n_estimators,), dtype=np.float64)
for i, y_pred_train in enumerate(model.staged_predict(X_train)):
train_scores[i] = accuracy_score(y_train, y_pred_train)
for i, y_pred_test in enumerate(model.staged_predict(X_test)):
test_scores[i] = accuracy_score(y_test, y_pred_test)
plt.figure(figsize=(12, 5))
# 训练过程
plt.subplot(1, 2, 1)
plt.plot(np.arange(model.n_estimators), train_scores,
label='训练集', linewidth=2)
plt.plot(np.arange(model.n_estimators), test_scores,
label='测试集', linewidth=2)
plt.xlabel('迭代次数', fontsize=12)
plt.ylabel('准确率', fontsize=12)
plt.title('梯度提升训练过程', fontsize=14, fontweight='bold')
plt.legend()
plt.grid(True, alpha=0.3)
# 特征重要性
plt.subplot(1, 2, 2)
feature_importance = model.feature_importances_
top_features = np.argsort(feature_importance)[-10:][::-1]
plt.barh(range(len(top_features)), feature_importance[top_features])
plt.yticks(range(len(top_features)), [f'特征 {i}' for i in top_features])
plt.xlabel('重要性', fontsize=12)
plt.title('Top 10 特征重要性', fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3, axis='x')
plt.tight_layout()
plt.savefig('docs/images/gradient_boosting_demo.png', dpi=300, bbox_inches='tight')
plt.show()
return model
# 运行示例
if __name__ == '__main__':
model = gradient_boosting_demo()
应用场景
- 竞赛(Kaggle等)
- 高精度预测
- 特征重要性分析
- 任何需要最高准确率的场景
算法对比与选择指南
算法对比表
| 算法 | 类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 线性回归 | 回归 | 简单、快速、可解释 | 假设线性关系 | 连续值预测 |
| 逻辑回归 | 分类 | 简单、快速、概率输出 | 假设线性关系 | 二分类问题 |
| SVM | 分类 | 高准确率、支持非线性 | 对参数敏感 | 小样本、非线性 |
| 朴素贝叶斯 | 分类 | 快速、对小样本有效 | 特征独立假设 | 文本分类 |
| 决策树 | 分类/回归 | 可解释、无需特征缩放 | 容易过拟合 | 需要可解释性 |
| 随机森林 | 分类/回归 | 高准确率、特征重要性 | 黑盒模型 | 高准确率需求 |
| KNN | 分类/回归 | 简单、无需训练 | 计算慢、对K敏感 | 局部模式识别 |
| K-Means | 聚类 | 简单、快速 | 需要指定K | 无监督聚类 |
| PCA | 降维 | 降维、可视化 | 线性变换 | 数据降维 |
| 梯度提升 | 分类/回归 | 最高准确率 | 训练慢、易过拟合 | 竞赛、高精度 |
选择指南
根据问题类型选择:
- 回归问题:线性回归 → 随机森林 → 梯度提升
- 二分类问题:逻辑回归 → SVM → 随机森林
- 多分类问题:决策树 → 随机森林 → 梯度提升
- 聚类问题:K-Means
- 降维问题:PCA
根据数据规模选择:
- 小样本(<1000):SVM、朴素贝叶斯
- 中等样本(1000-10000):大部分算法都适用
- 大样本(>10000):随机森林、梯度提升
根据可解释性需求选择:
- 高可解释性:线性回归、逻辑回归、决策树
- 中等可解释性:随机森林(特征重要性)
- 低可解释性:SVM、梯度提升
总结
核心要点
- 十大基础算法覆盖了机器学习的主要任务类型
- 每个算法都有其适用场景和优缺点
- 实践为主:通过代码实现加深理解
- 可视化:通过图表直观理解算法行为
学习建议
- 理解原理:掌握每个算法的数学原理
- 动手实践:运行代码,观察结果
- 参数调优:尝试不同的超参数
- 对比分析:在相同数据集上对比不同算法
- 实际应用:在真实项目中应用这些算法
下一步学习
- 深度学习:神经网络、CNN、RNN等
- 强化学习:Q-learning、Policy Gradient等
- 高级技术:AutoML、模型解释、模型部署
相关资源:
完整代码 :所有代码都可以在 docs/ml_algorithms_fundamentals.py 中找到并直接运行。
"""
十大基础机器学习算法完整实现
Python 3.11 兼容版本
所有代码可直接运行
"""
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.linear_model import LinearRegression, LogisticRegression
from sklearn.svm import SVC
from sklearn.naive_bayes import GaussianNB
from sklearn.tree import DecisionTreeClassifier, plot_tree, export_text
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split
from sklearn.metrics import (accuracy_score, mean_squared_error, r2_score,
confusion_matrix, classification_report, roc_curve, auc,
silhouette_score)
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_classification, make_blobs, load_iris
import warnings
warnings.filterwarnings('ignore')
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
sns.set_style("whitegrid")
# 创建输出目录
import os
os.makedirs('docs/images', exist_ok=True)
# ==================== 算法1:线性回归 ====================
def linear_regression_demo():
"""线性回归完整示例"""
print("=" * 60)
print("算法1:线性回归 (Linear Regression)")
print("=" * 60)
# 生成数据
np.random.seed(42)
n_samples = 100
X = np.random.randn(n_samples, 1) * 10
y = 2 * X.flatten() + 3 + np.random.randn(n_samples) * 2
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 训练模型
model = LinearRegression()
model.fit(X_train, y_train)
# 预测
y_train_pred = model.predict(X_train)
y_test_pred = model.predict(X_test)
# 评估
train_mse = mean_squared_error(y_train, y_train_pred)
test_mse = mean_squared_error(y_test, y_test_pred)
train_r2 = r2_score(y_train, y_train_pred)
test_r2 = r2_score(y_test, y_test_pred)
print(f"\n模型参数:")
print(f" 权重 (w): {model.coef_[0]:.4f}")
print(f" 截距 (b): {model.intercept_:.4f}")
print(f"\n训练集 - MSE: {train_mse:.4f}, R²: {train_r2:.4f}")
print(f"测试集 - MSE: {test_mse:.4f}, R²: {test_r2:.4f}")
# 可视化
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.scatter(X_train, y_train, alpha=0.6, label='训练数据', color='blue')
plt.plot(X_train, y_train_pred, 'r-', linewidth=2, label='拟合直线')
plt.xlabel('特征 X', fontsize=12)
plt.ylabel('目标 y', fontsize=12)
plt.title(f'训练集 (R² = {train_r2:.3f})', fontsize=14, fontweight='bold')
plt.legend()
plt.grid(True, alpha=0.3)
plt.subplot(1, 2, 2)
plt.scatter(X_test, y_test, alpha=0.6, label='测试数据', color='green')
plt.plot(X_test, y_test_pred, 'r-', linewidth=2, label='拟合直线')
plt.xlabel('特征 X', fontsize=12)
plt.ylabel('目标 y', fontsize=12)
plt.title(f'测试集 (R² = {test_r2:.3f})', fontsize=14, fontweight='bold')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('docs/images/linear_regression_demo.png', dpi=300, bbox_inches='tight')
plt.close()
print("✓ 图表已保存: docs/images/linear_regression_demo.png")
# ==================== 算法2:逻辑回归 ====================
def logistic_regression_demo():
"""逻辑回归完整示例"""
print("\n" + "=" * 60)
print("算法2:逻辑回归 (Logistic Regression)")
print("=" * 60)
# 生成二分类数据
X, y = make_classification(
n_samples=1000, n_features=2, n_redundant=0,
n_informative=2, n_clusters_per_class=1, random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 训练模型
model = LogisticRegression(max_iter=1000, random_state=42)
model.fit(X_train, y_train)
# 预测
y_test_pred = model.predict(X_test)
y_test_proba = model.predict_proba(X_test)[:, 1]
# 评估
test_acc = accuracy_score(y_test, y_test_pred)
print(f"\n测试集准确率: {test_acc:.4f}")
# 可视化
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
# 决策边界
h = 0.02
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
axes[0].contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.RdYlBu)
scatter = axes[0].scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.RdYlBu, edgecolors='black')
axes[0].set_xlabel('特征 1', fontsize=12)
axes[0].set_ylabel('特征 2', fontsize=12)
axes[0].set_title('决策边界', fontsize=14, fontweight='bold')
plt.colorbar(scatter, ax=axes[0])
# 混淆矩阵
cm = confusion_matrix(y_test, y_test_pred)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', ax=axes[1])
axes[1].set_xlabel('预测标签', fontsize=12)
axes[1].set_ylabel('真实标签', fontsize=12)
axes[1].set_title('混淆矩阵', fontsize=14, fontweight='bold')
# ROC曲线
fpr, tpr, _ = roc_curve(y_test, y_test_proba)
roc_auc = auc(fpr, tpr)
axes[2].plot(fpr, tpr, linewidth=2, label=f'ROC曲线 (AUC = {roc_auc:.3f})')
axes[2].plot([0, 1], [0, 1], 'k--', label='随机分类器')
axes[2].set_xlabel('假正率 (FPR)', fontsize=12)
axes[2].set_ylabel('真正率 (TPR)', fontsize=12)
axes[2].set_title('ROC曲线', fontsize=14, fontweight='bold')
axes[2].legend()
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('docs/images/logistic_regression_demo.png', dpi=300, bbox_inches='tight')
plt.close()
print("✓ 图表已保存: docs/images/logistic_regression_demo.png")
# ==================== 算法3:支持向量机 ====================
def svm_demo():
"""支持向量机完整示例"""
print("\n" + "=" * 60)
print("算法3:支持向量机 (SVM)")
print("=" * 60)
# 生成数据
X, y = make_classification(
n_samples=200, n_features=2, n_redundant=0,
n_informative=2, n_clusters_per_class=1, random_state=42
)
# 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y, test_size=0.2, random_state=42
)
# 训练不同核函数
kernels = ['linear', 'poly', 'rbf', 'sigmoid']
models = {}
for kernel in kernels:
model = SVC(kernel=kernel, random_state=42, probability=True)
model.fit(X_train, y_train)
models[kernel] = model
test_acc = accuracy_score(y_test, model.predict(X_test))
print(f"{kernel:8s} 核函数 - 测试准确率: {test_acc:.4f}")
# 可视化
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
axes = axes.flatten()
h = 0.02
x_min, x_max = X_scaled[:, 0].min() - 1, X_scaled[:, 0].max() + 1
y_min, y_max = X_scaled[:, 1].min() - 1, X_scaled[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
for idx, (kernel, model) in enumerate(models.items()):
ax = axes[idx]
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
ax.contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.RdYlBu)
scatter = ax.scatter(X_scaled[:, 0], X_scaled[:, 1], c=y,
cmap=plt.cm.RdYlBu, edgecolors='black')
ax.set_title(f'{kernel} 核函数', fontsize=14, fontweight='bold')
ax.set_xlabel('特征 1', fontsize=12)
ax.set_ylabel('特征 2', fontsize=12)
plt.tight_layout()
plt.savefig('docs/images/svm_demo.png', dpi=300, bbox_inches='tight')
plt.close()
print("✓ 图表已保存: docs/images/svm_demo.png")
# ==================== 算法4:朴素贝叶斯 ====================
def naive_bayes_demo():
"""朴素贝叶斯完整示例"""
print("\n" + "=" * 60)
print("算法4:朴素贝叶斯 (Naive Bayes)")
print("=" * 60)
# 生成数据
X, y = make_classification(
n_samples=1000, n_features=10, n_classes=3,
n_informative=5, random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 训练模型
model = GaussianNB()
model.fit(X_train, y_train)
# 预测
y_pred = model.predict(X_test)
y_proba = model.predict_proba(X_test)
# 评估
acc = accuracy_score(y_test, y_pred)
print(f"\n测试集准确率: {acc:.4f}")
# 可视化
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
# 混淆矩阵
cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', ax=axes[0])
axes[0].set_xlabel('预测标签', fontsize=12)
axes[0].set_ylabel('真实标签', fontsize=12)
axes[0].set_title('混淆矩阵', fontsize=14, fontweight='bold')
# 概率分布
for i in range(len(np.unique(y_test))):
axes[1].hist(y_proba[y_test == i, i], bins=20, alpha=0.6,
label=f'类别 {i}')
axes[1].set_xlabel('预测概率', fontsize=12)
axes[1].set_ylabel('频数', fontsize=12)
axes[1].set_title('各类别预测概率分布', fontsize=14, fontweight='bold')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('docs/images/naive_bayes_demo.png', dpi=300, bbox_inches='tight')
plt.close()
print("✓ 图表已保存: docs/images/naive_bayes_demo.png")
# ==================== 算法5:决策树 ====================
def decision_tree_demo():
"""决策树完整示例"""
print("\n" + "=" * 60)
print("算法5:决策树 (Decision Tree)")
print("=" * 60)
# 加载Iris数据集
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 训练决策树
model = DecisionTreeClassifier(max_depth=3, random_state=42)
model.fit(X_train, y_train)
# 预测
y_pred = model.predict(X_test)
acc = accuracy_score(y_test, y_pred)
print(f"\n测试集准确率: {acc:.4f}")
print(f"\n决策树结构:")
print(export_text(model, feature_names=iris.feature_names))
# 可视化决策树
plt.figure(figsize=(20, 10))
plot_tree(model, feature_names=iris.feature_names,
class_names=iris.target_names, filled=True, fontsize=10)
plt.title('决策树可视化', fontsize=16, fontweight='bold')
plt.savefig('docs/images/decision_tree_demo.png', dpi=300, bbox_inches='tight')
plt.close()
# 特征重要性
feature_importance = model.feature_importances_
plt.figure(figsize=(10, 6))
plt.barh(iris.feature_names, feature_importance)
plt.xlabel('重要性', fontsize=12)
plt.title('特征重要性', fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3, axis='x')
plt.savefig('docs/images/decision_tree_feature_importance.png', dpi=300, bbox_inches='tight')
plt.close()
print("✓ 图表已保存: docs/images/decision_tree_demo.png")
# ==================== 算法6:随机森林 ====================
def random_forest_demo():
"""随机森林完整示例"""
print("\n" + "=" * 60)
print("算法6:随机森林 (Random Forest)")
print("=" * 60)
# 生成数据
X, y = make_classification(
n_samples=1000, n_features=20, n_informative=10,
n_classes=3, random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 测试不同树数量
n_trees = [10, 50, 100, 200]
accuracies = []
for n_tree in n_trees:
model = RandomForestClassifier(n_estimators=n_tree, random_state=42)
model.fit(X_train, y_train)
acc = accuracy_score(y_test, model.predict(X_test))
accuracies.append(acc)
print(f"树数量: {n_tree:3d} - 准确率: {acc:.4f}")
# 可视化
plt.figure(figsize=(10, 6))
plt.plot(n_trees, accuracies, 'o-', linewidth=2, markersize=8)
plt.xlabel('树数量', fontsize=12)
plt.ylabel('准确率', fontsize=12)
plt.title('随机森林性能 vs 树数量', fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3)
plt.savefig('docs/images/random_forest_demo.png', dpi=300, bbox_inches='tight')
plt.close()
print("✓ 图表已保存: docs/images/random_forest_demo.png")
# ==================== 算法7:K近邻 ====================
def knn_demo():
"""K近邻完整示例"""
print("\n" + "=" * 60)
print("算法7:K近邻 (KNN)")
print("=" * 60)
# 生成数据
X, y = make_classification(
n_samples=300, n_features=2, n_redundant=0,
n_informative=2, n_clusters_per_class=1, random_state=42
)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y, test_size=0.2, random_state=42
)
# 测试不同的K值
k_values = range(1, 21)
train_accuracies = []
test_accuracies = []
for k in k_values:
model = KNeighborsClassifier(n_neighbors=k)
model.fit(X_train, y_train)
train_acc = accuracy_score(y_train, model.predict(X_train))
test_acc = accuracy_score(y_test, model.predict(X_test))
train_accuracies.append(train_acc)
test_accuracies.append(test_acc)
# 可视化K值影响
plt.figure(figsize=(12, 6))
plt.plot(k_values, train_accuracies, 'o-', label='训练集', linewidth=2)
plt.plot(k_values, test_accuracies, 's-', label='测试集', linewidth=2)
plt.xlabel('K值', fontsize=12)
plt.ylabel('准确率', fontsize=12)
plt.title('K值对KNN性能的影响', fontsize=14, fontweight='bold')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(k_values[::2])
plt.savefig('docs/images/knn_demo.png', dpi=300, bbox_inches='tight')
plt.close()
print("✓ 图表已保存: docs/images/knn_demo.png")
# ==================== 算法8:K均值聚类 ====================
def kmeans_demo():
"""K均值聚类完整示例"""
print("\n" + "=" * 60)
print("算法8:K均值聚类 (K-Means)")
print("=" * 60)
# 生成数据
X, y_true = make_blobs(
n_samples=300, centers=4, n_features=2, random_state=42
)
# 测试不同的K值
k_range = range(2, 11)
inertias = []
silhouette_scores = []
for k in k_range:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
labels = kmeans.fit_predict(X)
inertias.append(kmeans.inertia_)
silhouette_scores.append(silhouette_score(X, labels))
# 可视化
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
axes[0].plot(k_range, inertias, 'o-', linewidth=2, markersize=8)
axes[0].set_xlabel('K值', fontsize=12)
axes[0].set_ylabel('Inertia (簇内平方和)', fontsize=12)
axes[0].set_title('肘部法则 (Elbow Method)', fontsize=14, fontweight='bold')
axes[0].grid(True, alpha=0.3)
axes[0].set_xticks(k_range)
axes[1].plot(k_range, silhouette_scores, 'o-', linewidth=2, markersize=8, color='green')
axes[1].set_xlabel('K值', fontsize=12)
axes[1].set_ylabel('轮廓系数', fontsize=12)
axes[1].set_title('轮廓系数 vs K值', fontsize=14, fontweight='bold')
axes[1].grid(True, alpha=0.3)
axes[1].set_xticks(k_range)
plt.tight_layout()
plt.savefig('docs/images/kmeans_evaluation.png', dpi=300, bbox_inches='tight')
plt.close()
best_k = k_range[np.argmax(silhouette_scores)]
print(f"\n最佳K值: {best_k}")
print("✓ 图表已保存: docs/images/kmeans_evaluation.png")
# ==================== 算法9:主成分分析 ====================
def pca_demo():
"""主成分分析完整示例"""
print("\n" + "=" * 60)
print("算法9:主成分分析 (PCA)")
print("=" * 60)
# 加载Iris数据集
iris = load_iris()
X, y = iris.data, iris.target
# 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# PCA降维
pca = PCA()
X_pca = pca.fit_transform(X_scaled)
# 可视化
explained_variance_ratio = pca.explained_variance_ratio_
cumulative_variance = np.cumsum(explained_variance_ratio)
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
axes[0, 0].bar(range(1, len(explained_variance_ratio) + 1),
explained_variance_ratio, alpha=0.7)
axes[0, 0].set_xlabel('主成分', fontsize=12)
axes[0, 0].set_ylabel('解释方差比例', fontsize=12)
axes[0, 0].set_title('各主成分解释方差比例', fontsize=14, fontweight='bold')
axes[0, 0].grid(True, alpha=0.3, axis='y')
axes[0, 1].plot(range(1, len(cumulative_variance) + 1), cumulative_variance,
'o-', linewidth=2, markersize=8)
axes[0, 1].axhline(y=0.95, color='r', linestyle='--', label='95%阈值')
axes[0, 1].set_xlabel('主成分数量', fontsize=12)
axes[0, 1].set_ylabel('累积解释方差比例', fontsize=12)
axes[0, 1].set_title('累积解释方差', fontsize=14, fontweight='bold')
axes[0, 1].legend()
axes[0, 1].grid(True, alpha=0.3)
scatter = axes[1, 0].scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='viridis', s=50, alpha=0.6)
axes[1, 0].set_xlabel(f'第一主成分 (解释方差: {explained_variance_ratio[0]:.2%})', fontsize=12)
axes[1, 0].set_ylabel(f'第二主成分 (解释方差: {explained_variance_ratio[1]:.2%})', fontsize=12)
axes[1, 0].set_title('PCA 2D投影', fontsize=14, fontweight='bold')
plt.colorbar(scatter, ax=axes[1, 0])
axes[1, 1].axis('off')
plt.tight_layout()
plt.savefig('docs/images/pca_demo.png', dpi=300, bbox_inches='tight')
plt.close()
print(f"\n各主成分解释方差比例:")
for i, ratio in enumerate(explained_variance_ratio):
print(f" PC{i+1}: {ratio:.4f} ({ratio*100:.2f}%)")
print(f"\n前2个主成分累积解释方差: {cumulative_variance[1]:.4f} ({cumulative_variance[1]*100:.2f}%)")
print("✓ 图表已保存: docs/images/pca_demo.png")
# ==================== 算法10:梯度提升树 ====================
def gradient_boosting_demo():
"""梯度提升树完整示例"""
print("\n" + "=" * 60)
print("算法10:梯度提升树 (Gradient Boosting)")
print("=" * 60)
# 生成数据
X, y = make_classification(
n_samples=1000, n_features=20, n_informative=10,
n_classes=2, random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 训练梯度提升树
model = GradientBoostingClassifier(
n_estimators=100, learning_rate=0.1,
max_depth=3, random_state=42
)
model.fit(X_train, y_train)
# 预测
y_pred = model.predict(X_test)
acc = accuracy_score(y_test, y_pred)
print(f"\n测试集准确率: {acc:.4f}")
# 可视化训练过程
train_scores = np.zeros((model.n_estimators,), dtype=np.float64)
test_scores = np.zeros((model.n_estimators,), dtype=np.float64)
for i, y_pred_train in enumerate(model.staged_predict(X_train)):
train_scores[i] = accuracy_score(y_train, y_pred_train)
for i, y_pred_test in enumerate(model.staged_predict(X_test)):
test_scores[i] = accuracy_score(y_test, y_pred_test)
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(np.arange(model.n_estimators), train_scores,
label='训练集', linewidth=2)
plt.plot(np.arange(model.n_estimators), test_scores,
label='测试集', linewidth=2)
plt.xlabel('迭代次数', fontsize=12)
plt.ylabel('准确率', fontsize=12)
plt.title('梯度提升训练过程', fontsize=14, fontweight='bold')
plt.legend()
plt.grid(True, alpha=0.3)
plt.subplot(1, 2, 2)
feature_importance = model.feature_importances_
top_features = np.argsort(feature_importance)[-10:][::-1]
plt.barh(range(len(top_features)), feature_importance[top_features])
plt.yticks(range(len(top_features)), [f'特征 {i}' for i in top_features])
plt.xlabel('重要性', fontsize=12)
plt.title('Top 10 特征重要性', fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3, axis='x')
plt.tight_layout()
plt.savefig('docs/images/gradient_boosting_demo.png', dpi=300, bbox_inches='tight')
plt.close()
print("✓ 图表已保存: docs/images/gradient_boosting_demo.png")
# ==================== 主函数 ====================
def main():
"""运行所有算法示例"""
print("\n" + "=" * 60)
print("十大基础机器学习算法完整演示")
print("Python 3.11 兼容版本")
print("=" * 60)
# 运行所有算法
linear_regression_demo()
logistic_regression_demo()
svm_demo()
naive_bayes_demo()
decision_tree_demo()
random_forest_demo()
knn_demo()
kmeans_demo()
pca_demo()
gradient_boosting_demo()
print("\n" + "=" * 60)
print("✓ 所有算法演示完成!")
print("=" * 60)
print("\n所有图表已保存到 docs/images/ 目录")
print("详细文档请参考: docs/ml_algorithms_fundamentals.md")
if __name__ == '__main__':
main()