Day 15:KMeans聚类与股票风格分类
📋 目录
- 聚类分析概述
- KMeans算法原理
- K值选择方法(肘部法则、轮廓系数)
- 初始化问题与KMeans++
- 数据预处理与标准化
- 聚类的评估与解释
第一部分:聚类分析概述
1.1 什么是聚类?
聚类(Clustering) 是一种无监督学习 方法,将数据分成若干个组(簇),使得组内相似度高,组间相似度低。
与分类的区别:
| 对比项 | 分类 | 聚类 |
|---|---|---|
| 学习类型 | 监督学习 | 无监督学习 |
| 标签 | 有标签 | 无标签 |
| 目标 | 预测新样本类别 | 发现数据内在结构 |
| 评估 | 准确率、混淆矩阵 | 轮廓系数、内部指标 |
1.2 量化交易中的应用场景
| 应用场景 | 说明 |
|---|---|
| 股票风格分类 | 根据基本面因子划分价值/成长/动量等风格 |
| 市场状态识别 | 识别牛/熊/震荡市 |
| 风险分群 | 识别相似风险特征的股票 |
| 行业轮动 | 聚类发现行业轮动规律 |
| 异常检测 | 识别偏离正常模式的异常行为 |
1.3 常用聚类算法对比
| 算法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| KMeans | 基于质心 | 快速、简单 | 需要指定K,对异常值敏感 |
| 层次聚类 | 树状结构 | 不需要K,可解释 | 计算量大 |
| DBSCAN | 基于密度 | 发现任意形状,处理异常值 | 参数敏感 |
| GMM | 概率分布 | 软聚类,可处理椭圆形状 | 计算复杂 |
第二部分:KMeans算法原理
2.1 算法核心思想
目标 :将 nnn 个数据点分成 KKK 个簇,使得簇内平方和(Inertia)最小。
簇内平方和公式 :
Inertia=∑i=1nminμj∈C∥xi−μj∥2=∑j=1K∑xi∈Cj∥xi−μj∥2 \text{Inertia} = \sum_{i=1}^n \min_{\mu_j \in C} \|x_i - \mu_j \|^2 = \sum_{j=1}^K \sum_{x_i \in C_j} \|x_i - \mu_j \|^2 Inertia=i=1∑nμj∈Cmin∥xi−μj∥2=j=1∑Kxi∈Cj∑∥xi−μj∥2
其中 μj\mu_jμj 是第 jjj 个簇的质心(中心点)。
2.2 算法步骤
输入 :数据集 XXX,簇数 KKK,最大迭代次数 max_iters
输出:簇分配结果
-
随机初始化 KKK 个质心 μ1,μ2,⋯ ,μK\mu_1, \mu_2, \cdots, \mu_Kμ1,μ2,⋯,μK
-
重复直到收敛或达到最大迭代次数:
2.1 分配步骤:将每个样本分配到最近的质心
c(i)=argminj∥xi−μj∥2c(i) = \arg\min_j \|x_i - \mu_j\|^2c(i)=argminj∥xi−μj∥22.2 更新步骤:重新计算每个簇的质心
μj=1∣Cj∣∑i∈Cjxi\mu_j = \cfrac{1}{|C_j|}\sum_{i \in C_j} x_iμj=∣Cj∣1∑i∈Cjxi -
返回簇分配结果
2.3 收敛性
KMeans算法保证在有限步内收敛,但可能收敛到局部最优解而非全局最优。
影响因素:
- 初始质心选择
- 数据分布
- K值选择
2.4 距离度量
KMeans通常使用欧氏距离 :
d(x,y)=∑i=1p(xi−yi)2 d(x,y) = \sqrt{\sum_{i=1}^p(x_i-y_i)^2} d(x,y)=i=1∑p(xi−yi)2
重要 :使用欧氏距离前必须对特征进行标准化,否则量纲大的特征会主导距离计算。
第三部分:K值选择方法
3.1 肘部法则(Elbow Method)
原理:随着 K 增加,Inertia(簇内平方和)会下降。找到 Inertia 下降速度变缓的"肘点"。
python
# 肘部法则示例
inertias = []
for k in range(1, 11):
kmeans = KMeans(n_clusters=k, random_state=42)
kmeans.fit(X_scaled)
inertias.append(kmeans.inertia_)
plt.plot(range(1, 11), inertias, 'bo-')
plt.xlabel('K值')
plt.ylabel('Inertia')
plt.title('肘部法则选择K值')
特点:
- 简单直观
- 肘点有时不明显(需要主观判断)
3.2 轮廓系数(Silhouette Coefficient)
原理 :结合簇内凝聚度和簇间分离度,取值范围 [−1,1][-1, 1][−1,1]。
单个样本的轮廓系数 :
s(i)=b(i)−a(i)max(a(i),b(i)) s(i) = \cfrac{b(i) - a(i)}{\max(a(i), b(i))} s(i)=max(a(i),b(i))b(i)−a(i)
其中:
- a(i)a(i)a(i):样本到同簇其他样本的平均距离(簇内不相似度)
- b(i)b(i)b(i):样本到其他簇的最小平均距离(簇间不相似度)
整体轮廓系数:所有样本轮廓系数的平均值
解读:
| 轮廓系数 | 含义 |
|---|---|
| s>0.5s > 0.5s>0.5 | 聚类效果好 |
| 0.2<s<0.50.2 < s < 0.50.2<s<0.5 | 聚类效果一般 |
| s<0.2s < 0.2s<0.2 | 聚类效果差 |
| s≈0s \approx 0s≈0 | 簇之间重叠 |
| s<0s < 0s<0 | 样本可能被错误分配 |
python
from sklearn.metrics import silhouette_score
silhouette_scores = []
for k in range(2, 11):
kmeans = KMeans(n_clusters=k, random_state=42)
labels = kmeans.fit_predict(X_scaled)
score = silhouette_score(X_scaled, labels)
silhouette_scores.append(score)
3.3 轮廓系数可视化
python
from sklearn.metrics import silhouette_samples
# 绘制单个K值的轮廓图
silhouette_vals = silhouette_samples(X_scaled, labels)
y_lower = 10
for i in range(k):
cluster_vals = silhouette_vals[labels == i]
cluster_vals.sort()
y_upper = y_lower + len(cluster_vals)
plt.fill_betweenx(np.arange(y_lower, y_upper), 0, cluster_vals)
y_lower = y_upper + 10
3.4 K值选择方法对比
| 方法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 肘部法则 | Inertia下降拐点 | 简单直观 | 肘点主观 |
| 轮廓系数 | 聚合/分离度 | 客观数值 | 计算量大 |
| Gap统计量 | 与随机数据对比 | 统计严谨 | 计算复杂 |
| Calinski-Harabasz | 簇间/簇内方差比 | 快速 | 偏向凸簇 |
第四部分:初始化问题与KMeans++
4.1 随机初始化的陷阱
问题:不同的初始质心可能导致不同的聚类结果(局部最优)。
python
# 运行多次,结果可能不同
kmeans1 = KMeans(n_clusters=3, random_state=0)
kmeans2 = KMeans(n_clusters=3, random_state=42)
# 可能得到不同的簇分配
4.2 KMeans++ 初始化
KMeans++ 是一种智能初始化方法,能有效提高收敛到全局最优的概率。
算法步骤:
- 随机选择第一个质心
- 对于每个未选点,计算到最近已有质心的距离 D(x)D(x)D(x)
- 以概率 D(x)2∑D(x)2\frac{D(x)^2}{\sum D(x)^2}∑D(x)2D(x)2 选择下一个质心
- 重复直到选满 KKK 个质心
sklearn 中的实现:
python
from sklearn.cluster import KMeans
# KMeans++ 是默认初始化方法
kmeans = KMeans(n_clusters=5, init='k-means++', random_state=42)
# 也可以使用随机初始化
kmeans = KMeans(n_clusters=5, init='random', random_state=42)
4.3 多次运行取最优
python
best_inertia = float('inf')
best_kmeans = None
for i in range(10):
kmeans = KMeans(n_clusters=5, random_state=i)
kmeans.fit(X_scaled)
if kmeans.inertia_ < best_inertia:
best_inertia = kmeans.inertia_
best_kmeans = kmeans
第五部分:数据预处理与标准化
5.1 为什么需要标准化?
问题:不同特征的量纲差异会影响距离计算。
示例:
- 市盈率(PE):5-50
- 换手率:1%-20%
- ROE:-30% - 30%
若不标准化,PE 的数值会主导距离计算。
5.2 标准化方法
| 方法 | 公式 | 适用场景 |
|---|---|---|
| StandardScaler | (x−μ)/σ(x - \mu)/\sigma(x−μ)/σ | 特征分布近似正态 |
| MinMaxScaler | (x−min)/(max−min)(x - \min)/(\max - \min)(x−min)/(max−min) | 需要落在[0,1] |
| RobustScaler | (x−median)/IQR(x - \text{median})/IQR(x−median)/IQR | 有异常值 |
python
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
5.3 处理异常值
python
# 使用分位数去除极端值
lower = df['pe'].quantile(0.01)
upper = df['pe'].quantile(0.99)
df = df[(df['pe'] > lower) & (df['pe'] < upper)]
第六部分:聚类的评估与解释
6.1 内部评估指标
| 指标 | 公式 | 说明 |
|---|---|---|
| Inertia | $\sum | x - \mu |
| 轮廓系数 | (b−a)/max(a,b)(b-a)/\max(a,b)(b−a)/max(a,b) | 越接近1越好 |
| Davies-Bouldin | 簇间/簇内距离比 | 越小越好 |
| Calinski-Harabasz | 簇间方差/簇内方差 | 越大越好 |
6.2 聚类结果的可视化
PCA降维可视化:
python
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=labels, cmap='viridis')
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.title('聚类结果可视化(PCA降维)')
6.3 簇特征分析
python
# 计算每个簇的特征均值
cluster_means = pd.DataFrame(X_scaled, columns=feature_names)
cluster_means['cluster'] = labels
cluster_profile = cluster_means.groupby('cluster').mean()
# 找出每个簇的最显著特征
for cluster in cluster_profile.index:
top_features = cluster_profile.loc[cluster].abs().sort_values(ascending=False).head(5)
print(f"簇 {cluster} 的特征: {top_features.index.tolist()}")