跳转至

Lecture 6

1. 回归分析进阶

1.1. Influential Data Points

影响点(Influential Points) 是对回归模型有不成比例影响的数据点,需要特别关注。

Cook's Distance(库克距离)

库克距离衡量删除某个数据点后,回归模型预测值的变化程度。它综合考虑了 杠杆值(leverage)残差(residual)

\[ D_i = \frac{\sum_{j=1}^{n}(\hat{y}_j - \hat{y}_{j(i)})^2}{p \cdot MSE} \]

其中 \(\hat{y}_{j(i)}\) 是删除第 \(i\) 个数据点后的预测值,\(p\) 是特征数量。

库克距离的经验阈值

  • \(D_i > 1\):通常认为是影响点
  • \(D_i > \frac{4}{n}\):需要关注的数据点(\(n\) 为样本量)
Python
import statsmodels.api as sm
import numpy as np

X = sm.add_constant(X)  # 添加截距项
model = sm.OLS(y, X).fit()

# 获取 Cook's Distance
influence = model.get_influence()
cooks_d = influence.cooks_distance[0]

# 标记影响点
influential = np.where(cooks_d > 4 / len(X))[0]
print(f"影响点索引: {influential}")
print(f"对应 Cook's D: {cooks_d[influential]}")

Dummy Variable(虚拟变量)

虚拟变量将分类变量转换为数值变量,使回归模型能够处理分类特征。对于 \(k\) 个类别,需要 \(k-1\) 个虚拟变量(避免虚拟变量陷阱,即完全多重共线性)。

Python
1
2
3
4
5
6
import pandas as pd

# 假设 color 列有 red, green, blue 三个类别
df_encoded = pd.get_dummies(df, columns=['color'], drop_first=True)
# drop_first=True 删除第一个类别,避免完全共线性
# 结果:color_green, color_blue(red 作为基准类别)

1.2. Non-Parametric Linear Regression

非参数线性回归 不假设数据的函数形式,通过局部拟合来捕捉数据中的非线性关系。

LOWESS / LOESS

LOWESS(Locally Weighted Scatterplot Smoothing)LOESS(Locally Estimated Scatterplot Smoothing) 是常用的非参数回归方法,核心思想是:对每个预测点,只用其邻近的数据点进行加权最小二乘拟合。

Python
import statsmodels.api as sm
import matplotlib.pyplot as plt

# LOWESS 平滑
lowess_result = sm.nonparametric.lowess(y, x, frac=0.3)  # frac 控制平滑程度

plt.scatter(x, y, alpha=0.5, label='原始数据')
plt.plot(lowess_result[:, 0], lowess_result[:, 1],
         color='red', linewidth=2, label='LOWESS')
plt.legend()
plt.show()

LOWESS 的关键参数

frac(span)参数控制局部邻域的大小:值越大越平滑(偏差增大、方差减小),值越小越贴近数据(偏差减小、方差增大)。这是典型的 偏差-方差权衡

1746772367678

2. Machine Learning

2.1 KNN — K 近邻算法

KNN(K-Nearest Neighbors) 是最简单的机器学习算法之一,可用于分类和回归。其核心思想是:一个样本的类别/值由其 K 个最近邻居的多数投票/均值 决定。

算法流程

  1. 选择超参数 \(K\) 和距离度量
  2. 对于新样本,计算它与所有训练样本的距离
  3. 找到距离最近的 \(K\) 个邻居
  4. 分类:多数投票;回归:取均值
\[ \text{距离度量(欧氏距离):} \quad d(\mathbf{x}, \mathbf{y}) = \sqrt{\sum_{i=1}^{n}(x_i - y_i)^2} \]
Python
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

# KNN 需要特征缩放
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('knn', KNeighborsClassifier(n_neighbors=5))
])
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)

\(K\) 值的选择对模型性能影响很大:

\(K\) 效果
太小(如 \(K=1\) 对噪声敏感,容易 过拟合
太大(如 \(K=n\) 决策边界过于平滑,容易 欠拟合

最优 \(K\)通常通过交叉验证确定:

Python
from sklearn.model_selection import cross_val_score
import numpy as np

k_range = range(1, 31)
cv_scores = []

for k in k_range:
    knn = KNeighborsClassifier(n_neighbors=k)
    scores = cross_val_score(knn, X_scaled, y, cv=5, scoring='accuracy')
    cv_scores.append(scores.mean())

best_k = k_range[np.argmax(cv_scores)]
print(f"最优 K = {best_k}, 准确率 = {max(cv_scores):.4f}")
度量 公式 适用场景
欧氏距离 \(\sqrt{\sum(x_i - y_i)^2}\) 连续特征
曼哈顿距离 $\sum x_i - y_i
闵可夫斯基距离 $(\sum x_i - y_i
余弦相似度 $\frac{\mathbf{x} \cdot \mathbf{y}}{ \mathbf{x}

KNN 的局限性

  • 维度灾难:高维空间中距离变得无意义,所有点距离相近
  • 计算成本:预测时需要计算与所有训练样本的距离,时间复杂度 \(O(n \cdot d)\)
  • 特征缩放:KNN 基于距离,必须对特征进行标准化
  • 不平衡数据:多数类会主导投票结果

2.2 决策树(Decision Tree)

决策树 通过递归地将特征空间划分为矩形区域来进行预测。每个内部节点表示一个特征上的判断,每个叶节点表示一个预测结果。

graph TD
    A[收入 > 50K?] -->|是| B[年龄 > 30?]
    A -->|否| C[拒绝]
    B -->|是| D[批准]
    B -->|否| E[信用分 > 700?]
    E -->|是| D
    E -->|否| C

分裂准则

决策树通过最大化信息增益来选择最佳分裂特征:

信息熵(Entropy)

\[ H(S) = -\sum_{i=1}^{c} p_i \log_2 p_i \]

基尼不纯度(Gini Impurity)

\[ G(S) = 1 - \sum_{i=1}^{c} p_i^2 \]

信息增益(Information Gain)

\[ IG(S, A) = H(S) - \sum_{v \in Values(A)} \frac{|S_v|}{|S|} H(S_v) \]
Python
1
2
3
4
5
6
7
8
from sklearn.tree import DecisionTreeClassifier, export_text

# 训练决策树
dt = DecisionTreeClassifier(max_depth=3, min_samples_split=10, random_state=42)
dt.fit(X_train, y_train)

# 查看决策规则
print(export_text(dt, feature_names=list(X.columns)))

优点

  • 可解释性强,决策过程透明
  • 不需要特征缩放
  • 能处理数值型和分类型特征
  • 能捕捉非线性关系和特征交互

缺点

  • 容易过拟合(特别是深度不受限时)
  • 对数据的微小变化敏感(不稳定性)
  • 贪心算法,不保证全局最优
  • 倾向于选择具有更多取值的特征

预剪枝(Pre-pruning):在构建树时提前停止

Python
1
2
3
4
5
6
dt = DecisionTreeClassifier(
    max_depth=5,           # 最大深度
    min_samples_split=20,  # 节点最少样本数
    min_samples_leaf=10,   # 叶节点最少样本数
    max_features=0.8       # 每次分裂考虑的最大特征比例
)

后剪枝(Post-pruning):先生成完整树,再通过 代价复杂度剪枝(CCP) 移除不必要的分支

Python
# 使用 cost-complexity pruning
path = dt.cost_complexity_pruning_path(X_train, y_train)
ccp_alphas = path.ccp_alphas

# 通过交叉验证选择最优 alpha
from sklearn.model_selection import cross_val_score
scores = []
for alpha in ccp_alphas:
    dt = DecisionTreeClassifier(ccp_alpha=alpha, random_state=42)
    score = cross_val_score(dt, X_train, y_train, cv=5).mean()
    scores.append(score)
best_alpha = ccp_alphas[np.argmax(scores)]

2.3 无监督学习(Unsupervised Learning)

无监督学习 使用没有标签的数据进行训练,在没有显性指导的情况下发现数据的结构和模式。主要任务包括聚类、降维和关联规则挖掘。

K-Means 聚类

K-Means 是最经典的聚类算法,目标是将 \(n\) 个样本划分为 \(K\) 个簇,使得每个样本到其所属簇中心的距离之和最小。

算法流程

  1. 随机初始化 \(K\) 个簇中心 \(\mu_1, \mu_2, \ldots, \mu_K\)
  2. 分配步骤:将每个样本分配到最近的簇中心
  3. 更新步骤:重新计算每个簇的中心(簇内样本的均值)
  4. 重复步骤 2-3 直到收敛

目标函数(SSE / Inertia)

\[ J = \sum_{k=1}^{K}\sum_{x_i \in C_k} ||x_i - \mu_k||^2 \]
Python
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler

# 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 训练 K-Means
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
kmeans.fit(X_scaled)

# 结果
print(f"簇中心:\n{kmeans.cluster_centers_}")
print(f"Inertia (SSE): {kmeans.inertia_:.2f}")
print(f"标签: {kmeans.labels_}")

肘部法则(Elbow Method):绘制不同 \(K\) 值的 SSE 曲线,找到拐点。

Python
import matplotlib.pyplot as plt

sse = []
k_range = range(1, 11)
for k in k_range:
    km = KMeans(n_clusters=k, random_state=42, n_init=10)
    km.fit(X_scaled)
    sse.append(km.inertia_)

plt.plot(k_range, sse, 'bo-')
plt.xlabel('K')
plt.ylabel('SSE')
plt.title('Elbow Method')
plt.show()

轮廓系数(Silhouette Score):衡量样本与自身簇的相似度 vs 与其他簇的相似度。

\[s(i) = \frac{b(i) - a(i)}{\max(a(i), b(i))}\]

其中 \(a(i)\) 是样本 \(i\) 到同簇其他点的平均距离,\(b(i)\) 是样本 \(i\) 到最近其他簇的平均距离。\(s(i) \in [-1, 1]\),越接近 1 越好。

Python
from sklearn.metrics import silhouette_score

scores = []
for k in range(2, 11):
    km = KMeans(n_clusters=k, random_state=42, n_init=10)
    labels = km.fit_predict(X_scaled)
    score = silhouette_score(X_scaled, labels)
    scores.append(score)

best_k = range(2, 11)[np.argmax(scores)]
print(f"最优 K = {best_k}, 轮廓系数 = {max(scores):.4f}")

K-Means 的局限性

  • 假设簇是 凸形且大小相近 的球形分布
  • 对初始中心敏感(使用 n_init 多次初始化缓解)
  • 需要预先指定 \(K\)
  • 对异常值和噪声敏感
  • 只能发现线性可分的簇结构

层次聚类(Hierarchical Clustering)

层次聚类不需要预设 \(K\) 值,通过逐步合并(自底向上)或逐步分裂(自顶向下)构建树状结构(树状图 Dendrogram)。

Python
from scipy.cluster.hierarchy import dendrogram, linkage
import matplotlib.pyplot as plt

Z = linkage(X_scaled, method='ward')  # Ward 方差最小化
plt.figure(figsize=(12, 6))
dendrogram(Z, truncate_mode='level', p=5)
plt.title('Hierarchical Clustering Dendrogram')
plt.xlabel('Sample Index')
plt.ylabel('Distance')
plt.show()

DBSCAN(基于密度的聚类)

DBSCAN 将高密度区域划分为簇,能够发现任意形状的簇并自动识别噪声点。

参数 含义
\(\epsilon\) (eps) 邻域半径
min_samples 成为核心点所需的最小邻居数
Python
1
2
3
4
5
6
7
8
from sklearn.cluster import DBSCAN

dbscan = DBSCAN(eps=0.5, min_samples=5)
labels = dbscan.fit_predict(X_scaled)

n_clusters = len(set(labels)) - (1 if -1 in labels else 0)
n_noise = list(labels).count(-1)
print(f"簇数: {n_clusters}, 噪声点: {n_noise}")

2.4 降维方法

PCA — 主成分分析

PCA(Principal Component Analysis) 是最常用的线性降维方法,通过找到数据方差最大的方向(主成分)来降低维度。

数学原理:对数据协方差矩阵进行特征分解,特征值最大的特征向量就是第一主成分方向。

\[ \mathbf{C} = \frac{1}{n-1}\mathbf{X}^T\mathbf{X}, \quad \mathbf{C}\mathbf{v}_i = \lambda_i \mathbf{v}_i \]
Python
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt

# 降维到 2 维
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)

print(f"各主成分解释方差比: {pca.explained_variance_ratio_}")
print(f"累计解释方差比: {pca.explained_variance_ratio_.cumsum()}")

plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='viridis', alpha=0.6)
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.title('PCA Projection')
plt.colorbar()
plt.show()

累积方差解释比:选择解释足够大方差(通常 >= 95%)的主成分数。

Python
1
2
3
4
pca_full = PCA().fit(X_scaled)
cumsum = np.cumsum(pca_full.explained_variance_ratio_)
n_components = np.argmax(cumsum >= 0.95) + 1
print(f"解释 95% 方差需要 {n_components} 个主成分")

碎石图(Scree Plot):绘制各主成分的特征值,找到"肘部"。

Python
1
2
3
4
5
6
plt.plot(range(1, len(pca_full.explained_variance_ratio_) + 1),
         pca_full.explained_variance_ratio_, 'bo-')
plt.xlabel('主成分编号')
plt.ylabel('解释方差比')
plt.title('Scree Plot')
plt.show()

PCA 的适用场景与局限

  • 适用:高维数据可视化、去除特征间相关性、加速模型训练
  • 局限:只能捕捉 线性 结构;主成分的可解释性较差
  • 替代方案:t-SNE(非线性降维,适合可视化)、UMAP(更快的非线性降维)

2.5 算法对比总结

算法 类型 需要标签 需要特征缩放 可解释性 计算复杂度
KNN 监督 ✅ ✅
决策树 监督 ✅ ❌
K-Means 无监督 ❌ ✅
层次聚类 无监督 ❌ ✅
DBSCAN 无监督 ❌ ✅
PCA 无监督 ❌ ✅