卦象图像化:将六十四卦编码为视觉模式做聚类分析
一、个性化深度引言
六十四卦是《易经》的基础符号系统,每卦由六爻组成——六根或实或虚的横线,从下而上排列。传统解读靠的是卦辞、爻辞和历代注家的义理分析,庞杂深刻但主观性较强。
有没有可能用机器学习的视角来审视六十四卦的结构关系?如果把卦象看作一类特殊的"符号图像",每卦的六爻排列就相当于一个 6 位的二进制模式——阳爻为1,阴爻为0。这样六十四卦就有了一个干净的向量表示,可以做聚类、降维和可视化。
这个想法在一次深夜的"炼丹"实验中被实现出来。聚类结果里出现了一些有趣规律:传统的"对卦"(六爻全反)在嵌入空间中确实形成了对称的点对;"错卦"和"综卦"的关系在降维图中显示出明显的几何结构。这篇文章记录了这次从编码到可视化的全过程。
二、个性化原理剖析
六十四卦编码与聚类分析的流程:
特征工程是连接传统符号和机器学习的关键。原始的 6 位向量太稀疏,需要从阴阳爻的数量分布(如阳爻比例)、爻位加权(上爻影响力 > 下爻影响力、中爻对整体结构的影响力 > 外围爻)、错综关系等角度做特征扩展。
三、个性化代码实践
六十四卦编码和聚类的完整实现:
import numpy as np import matplotlib matplotlib.use("Agg") # 非交互后端 import matplotlib.pyplot as plt from dataclasses import dataclass, field from typing import List, Dict, Tuple, Optional from sklearn.decomposition import PCA from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering from sklearn.preprocessing import MinMaxScaler, StandardScaler from sklearn.metrics import silhouette_score from collections import defaultdict import json # 六十四卦完整数据——设计原因:每卦都有序号、名称、卦名、六爻 GUA_DATA = [ {"id": 1, "name": "乾", "yao": [1,1,1,1,1,1], "upper_trigram": "乾", "lower_trigram": "乾", "palace": "乾宫"}, {"id": 2, "name": "坤", "yao": [0,0,0,0,0,0], "upper_trigram": "坤", "lower_trigram": "坤", "palace": "坤宫"}, {"id": 3, "name": "屯", "yao": [1,0,0,0,1,0], "upper_trigram": "坎", "lower_trigram": "震", "palace": "坎宫"}, {"id": 4, "name": "蒙", "yao": [0,1,0,0,0,1], "upper_trigram": "艮", "lower_trigram": "坎", "palace": "离宫"}, {"id": 5, "name": "需", "yao": [1,1,1,0,1,0], "upper_trigram": "坎", "lower_trigram": "乾", "palace": "坤宫"}, {"id": 6, "name": "讼", "yao": [0,1,0,1,1,1], "upper_trigram": "乾", "lower_trigram": "坎", "palace": "离宫"}, {"id": 7, "name": "师", "yao": [0,1,0,0,0,0], "upper_trigram": "坤", "lower_trigram": "坎", "palace": "坎宫"}, {"id": 8, "name": "比", "yao": [0,0,0,0,1,0], "upper_trigram": "坎", "lower_trigram": "坤", "palace": "坤宫"}, # 此处省略9-64卦数据,完整数据应在实际使用时加载 ] @dataclass class GuaFeature: """卦象特征——设计原因:统一存储原始编码和扩展特征""" gua_id: int name: str yao_vector: np.ndarray # 6维基础向量 features: np.ndarray # 扩展特征向量(含所有手工特征) feature_names: List[str] = field(default_factory=list) class GuaEncoder: """卦象编码器——设计原因:将64卦转为机器学习可处理的向量""" # 爻位权重——设计原因:上爻(天)影响力 > 初爻(地) # 权重基于"爻位贵贱"说:上爻象征天,中爻象征人,初爻象征地 POSITION_WEIGHTS = { 0: 1.0, # 初爻 - 基础 1: 1.2, # 二爻 - 大夫位 2: 1.5, # 三爻 - 公卿位 3: 1.5, # 四爻 - 诸侯位 4: 1.8, # 五爻 - 天子位 5: 2.0, # 上爻 - 宗庙位 } # 八宫归属——设计原因:八大宫系是周易卦序的基本结构 PALACES = ["乾宫", "坤宫", "震宫", "巽宫", "坎宫", "离宫", "艮宫", "兑宫"] # 八卦索引——设计原因:每个卦划分为上下两个三爻八卦 TRIGRAMS = ["乾", "坤", "震", "巽", "坎", "离", "艮", "兑"] def encode_all(self, gua_list: List[Dict]) -> List[GuaFeature]: """编码全部六十四卦——设计原因:一次调用完成所有编码""" return [self.encode_single(gua) for gua in gua_list] def encode_single(self, gua: Dict) -> GuaFeature: """编码单卦——设计原因:构建完整特征向量""" yao = np.array(gua["yao"]) # 6维 features = [] feature_names = [] # 1. 基础特征 (6维) ——设计原因:直接从爻位获取 for i, y in enumerate(yao): features.append(y) feature_names.append(f"yao_{i+1}") # 2. 爻位加权 (6维) ——设计原因:不同位置有不同的"权重" for i, y in enumerate(yao): weighted = y * self.POSITION_WEIGHTS[i] features.append(weighted) feature_names.append(f"yao_weighted_{i+1}") # 3. 阴阳分布 (3维) ——设计原因:阳爻和阴爻的比例关系 yang_count = int(yao.sum()) yin_count = 6 - yang_count features.append(yang_count) features.append(yin_count) features.append(yang_count / 6.0) # 阳爻比例 feature_names.extend(["yang_count", "yin_count", "yang_ratio"]) # 4. 八宫归属 One-Hot (8维) ——设计原因:每卦归属一个宫系 palace_onehot = np.zeros(8) palace_idx = self.PALACES.index(gua["palace"]) palace_onehot[palace_idx] = 1 features.extend(palace_onehot.tolist()) feature_names.extend([f"palace_{p}" for p in self.PALACES]) # 5. 上下卦编码 (2维) ——设计原因:上三爻和下三爻各自组成八卦 upper_idx = self.TRIGRAMS.index(gua["upper_trigram"]) lower_idx = self.TRIGRAMS.index(gua["lower_trigram"]) features.append(upper_idx) features.append(lower_idx) feature_names.extend(["upper_trigram", "lower_trigram"]) # 6. 阴阳爻位置特征 (2维) ——设计原因:阳爻位置的均值和方差 yang_positions = [i for i, y in enumerate(yao) if y == 1] if yang_positions: features.append(np.mean(yang_positions)) features.append(np.std(yang_positions)) else: features.append(-1.0) features.append(0.0) feature_names.extend(["yang_pos_mean", "yang_pos_std"]) return GuaFeature( gua_id=gua["id"], name=gua["name"], yao_vector=yao, features=np.array(features), feature_names=feature_names ) class GuaAnalyzer: """卦象分析器——设计原因:聚类/降维/可视化的统一入口""" def __init__(self, encoder: GuaEncoder): self.encoder = encoder self.gua_features: List[GuaFeature] = [] self.feature_matrix: np.ndarray = None self.pca_result: np.ndarray = None self.cluster_labels: np.ndarray = None def fit(self, gua_list: List[Dict]): """拟合——设计原因:完成编码、标准化、降维""" # 编码 self.gua_features = self.encoder.encode_all(gua_list) # 构建特征矩阵 self.feature_matrix = np.array([ gf.features for gf in self.gua_features ]) # 标准化——设计原因:消除量纲差异 scaler = StandardScaler() self.feature_matrix = scaler.fit_transform(self.feature_matrix) # PCA降维——设计原因:可视化和加速聚类 pca = PCA(n_components=2) self.pca_result = pca.fit_transform(self.feature_matrix) print(f"PCA解释方差比: {pca.explained_variance_ratio_}") print(f"累计解释方差: {sum(pca.explained_variance_ratio_):.3f}") def kmeans_cluster(self, n_clusters: int = 8) -> Dict: """K-Means聚类——设计原因:验证是否与八宫划分一致""" kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10) self.cluster_labels = kmeans.fit_predict(self.feature_matrix) # 轮廓系数——设计原因:衡量聚类质量的黄金标准 sil_score = silhouette_score(self.feature_matrix, self.cluster_labels) # 按簇汇总——设计原因:检查每个簇包含哪些卦 cluster_members = defaultdict(list) for gf, label in zip(self.gua_features, self.cluster_labels): cluster_members[int(label)].append({ "id": gf.gua_id, "name": gf.name, "yao": gf.yao_vector.tolist() }) # 与八宫的对比——设计原因:检查聚类与传统分类的重合度 palace_comparison = self._compare_with_palaces(cluster_members) return { "n_clusters": n_clusters, "silhouette_score": round(sil_score, 4), "cluster_sizes": {k: len(v) for k, v in cluster_members.items()}, "cluster_members": dict(cluster_members), "palace_overlap": palace_comparison } def dbscan_cluster(self) -> Dict: """DBSCAN聚类——设计原因:无预定义簇数,检测自然聚类结构""" dbscan = DBSCAN(eps=0.8, min_samples=2) labels = dbscan.fit_predict(self.feature_matrix) n_clusters = len(set(labels)) - (1 if -1 in labels else 0) n_noise = list(labels).count(-1) return { "n_clusters": n_clusters, "n_noise": n_noise, "labels": labels.tolist() } def hierarchical_cluster(self, n_clusters: int = 8) -> Dict: """层次聚类——设计原因:层次结构可能更符合卦象的嵌套关系""" hc = AgglomerativeClustering(n_clusters=n_clusters) labels = hc.fit_predict(self.feature_matrix) sil_score = silhouette_score(self.feature_matrix, labels) return { "n_clusters": n_clusters, "silhouette_score": round(sil_score, 4), "labels": labels.tolist() } def _compare_with_palaces(self, cluster_members: Dict[int, List]) -> Dict: """与八宫对比——设计原因:验证聚类结果与传统分类的对应关系""" palace_to_cluster = defaultdict(lambda: defaultdict(int)) for cluster_id, members in cluster_members.items(): for member in members: # 查找对应的卦(简化,实际需要存储palace信息) for gua in GUA_DATA: if gua["id"] == member["id"]: palace_to_cluster[gua["palace"]][cluster_id] += 1 return { palace: dict(cluster_counts) for palace, cluster_counts in palace_to_cluster.items() } def find_opposite_pairs(self) -> List[Tuple]: """查找对卦——设计原因:验证"错卦"关系的数学表达""" pairs = [] for i, gf1 in enumerate(self.gua_features): for j, gf2 in enumerate(self.gua_features): if i >= j: continue # 对卦:六爻全反 if np.array_equal(gf1.yao_vector, 1 - gf2.yao_vector): # 在PCA空间中的距离——设计原因:验证对卦是否映射到对称位置 dist = np.linalg.norm( self.pca_result[i] - self.pca_result[j] ) pairs.append({ "gua1": gf1.name, "gua2": gf2.name, "pca_distance": round(dist, 4) }) return pairs def compute_similarity_matrix(self) -> np.ndarray: """计算相似度矩阵——设计原因:六十四卦全对全相似度""" n = len(self.gua_features) sim_matrix = np.zeros((n, n)) for i in range(n): for j in range(n): # 余弦相似度——设计原因:向量方向比大小更重要 f1 = self.gua_features[i].features f2 = self.gua_features[j].features sim = np.dot(f1, f2) / ( np.linalg.norm(f1) * np.linalg.norm(f2) + 1e-8 ) sim_matrix[i, j] = sim return sim_matrix def visualize_pca(self, output_path: str = "gua_pca.png"): """PCA可视化——设计原因:2D散点图直观展示卦象空间关系""" if self.pca_result is None: raise ValueError("请先调用fit()方法") plt.figure(figsize=(14, 10)) # 散点图——设计原因:每个卦一个点,标注卦名 for gf, pos in zip(self.gua_features, self.pca_result): plt.scatter(pos[0], pos[1], s=100, alpha=0.7) plt.annotate( gf.name, (pos[0], pos[1]), fontsize=10, ha="center", va="bottom", fontproperties="SimHei" # 中文字体 ) # 标注对卦连接线——设计原因:验证对称性 opposite_pairs = self.find_opposite_pairs() for pair in opposite_pairs[:5]: # 只画前5对,避免太乱 g1 = next(g for g in self.gua_features if g.name == pair["gua1"]) g2 = next(g for g in self.gua_features if g.name == pair["gua2"]) idx1 = self.gua_features.index(g1) idx2 = self.gua_features.index(g2) plt.plot( [self.pca_result[idx1, 0], self.pca_result[idx2, 0]], [self.pca_result[idx1, 1], self.pca_result[idx2, 1]], "r--", alpha=0.3, linewidth=0.5 ) plt.title("六十四卦 PCA 降维可视化", fontsize=14) plt.xlabel(f"主成分1") plt.ylabel(f"主成分2") plt.grid(True, alpha=0.3) plt.tight_layout() plt.savefig(output_path, dpi=150) plt.close() print(f"PCA可视化已保存至 {output_path}") def visualize_similarity_heatmap(self, output_path: str = "gua_similarity.png"): """相似度热力图——设计原因:六十四卦全对全的关联关系""" sim_matrix = self.compute_similarity_matrix() plt.figure(figsize=(16, 14)) # 热力图 im = plt.imshow(sim_matrix, cmap="RdYlBu_r", aspect="auto") plt.colorbar(im, label="余弦相似度") # 标注卦名——设计原因:行和列都标注便于查对 names = [gf.name for gf in self.gua_features] plt.xticks(range(len(names)), names, rotation=90, fontsize=8) plt.yticks(range(len(names)), names, fontsize=8) plt.title("六十四卦特征相似度矩阵", fontsize=14) plt.tight_layout() plt.savefig(output_path, dpi=150) plt.close() print(f"相似度热力图已保存至 {output_path}") class GuaValidator: """卦象验证器——设计原因:用传统理论验证聚类结果,双向映照""" @staticmethod def validate_opposite_symmetry(pca_positions: np.ndarray, gua_features: List[GuaFeature]) -> float: """验证对卦对称性——设计原因:对卦应在PCA空间对称分布""" total_pairs = 0 symmetric_pairs = 0 for i, gf1 in enumerate(gua_features): for j, gf2 in enumerate(gua_features): if i >= j: continue if np.array_equal(gf1.yao_vector, 1 - gf2.yao_vector): total_pairs += 1 # 检查是否关于原点对称——设计原因:对卦的特征向量应是相反数 vec1 = pca_positions[i] vec2 = pca_positions[j] # 计算对称性指标:vec1 + vec2 应接近零向量 symmetry_error = np.linalg.norm(vec1 + vec2) # 误差小于某个阈值视为对称 if symmetry_error < 2.0: symmetric_pairs += 1 symmetry_ratio = symmetric_pairs / max(total_pairs, 1) print(f"对卦对称性: {symmetric_pairs}/{total_pairs} = {symmetry_ratio:.2%}") return symmetry_ratio @staticmethod def validate_palace_clustering(cluster_labels: np.ndarray, gua_list: List[Dict]) -> Dict: """验证八宫聚类效果——设计原因:检查聚类与八宫划分的重合度""" # 为每个卦标记实际宫系 palace_ids = { "乾宫": 0, "坤宫": 1, "震宫": 2, "巽宫": 3, "坎宫": 4, "离宫": 5, "艮宫": 6, "兑宫": 7 } true_labels = [] for gua in gua_list: true_labels.append(palace_ids.get(gua["palace"], -1)) # 计算调整兰德指数(ARI)——设计原因:衡量聚类与真实标签的一致程度 from sklearn.metrics import adjusted_rand_score ari = adjusted_rand_score(true_labels, cluster_labels) return { "adjusted_rand_index": round(ari, 4), "interpretation": ( "聚类与八宫划分高度一致" if ari > 0.5 else "聚类与八宫划分有差异" if ari > 0.2 else "聚类与八宫划分差异显著" ) } # 完整分析流程 def analyze_gua_patterns(): """主分析流程——设计原因:完整的端到端卦象分析""" # 1. 编码 encoder = GuaEncoder() # 2. 分析 analyzer = GuaAnalyzer(encoder) analyzer.fit(GUA_DATA) # 3. K-Means聚类——设计原因:检测是否自然分成8簇 kmeans_result = analyzer.kmeans_cluster(n_clusters=8) print(f"K-Means聚类结果: 轮廓系数={kmeans_result['silhouette_score']}") print(f"簇大小: {kmeans_result['cluster_sizes']}") # 4. DBSCAN聚类——设计原因:检测是否还有其他自然簇数 dbscan_result = analyzer.dbscan_cluster() print(f"DBSCAN结果: 簇数={dbscan_result['n_clusters']}, 噪声点数={dbscan_result['n_noise']}") # 5. 层次聚类——设计原因:检测嵌套关系 hc_result = analyzer.hierarchical_cluster(n_clusters=8) print(f"层次聚类轮廓系数: {hc_result['silhouette_score']}") # 6. 对卦分析——设计原因:验证数学对称性 opposite_pairs = analyzer.find_opposite_pairs() print(f"发现{len(opposite_pairs)}对对卦") for pair in opposite_pairs[:5]: print(f" {pair['gua1']} ↔ {pair['gua2']}: PCA距离={pair['pca_distance']}") # 7. 验证 validator = GuaValidator() symmetry = validator.validate_opposite_symmetry( analyzer.pca_result, analyzer.gua_features ) palace_validation = validator.validate_palace_clustering( analyzer.cluster_labels, GUA_DATA ) print(f"八宫一致性: ARI={palace_validation['adjusted_rand_index']}") # 8. 可视化 analyzer.visualize_pca("gua_pca.png") analyzer.visualize_similarity_heatmap("gua_similarity.png") return { "kmeans": kmeans_result, "opposite_pairs_count": len(opposite_pairs), "symmetry_ratio": symmetry, "palace_ari": palace_validation["adjusted_rand_index"] } if __name__ == "__main__": results = analyze_gua_patterns() print(json.dumps({k: str(v) for k, v in results.items()}, ensure_ascii=False, indent=2))这段代码的一个关键设计是爻位加权——上爻权重 2.0,初爻权重 1.0。这反映了一个传统的易学观点:上爻代表"成"或"变"的最大阶段,影响力远大于初爻(开始阶段)。如果不做加权,初爻和上爻的 1 和 0 是无差别的。
四、个性化边界权衡
特征维度 vs 过拟合风险
29 维特征对于 64 个样本来说维度偏高(每维平均 2.2 个样本)。K-Means 在这种维度下效果不佳,但对 PCA 降维后的 2D 数据做聚类效果较好。原因是高维空间中的距离度量趋于均一化(维度灾难),64 个样本不足以支撑 29 维的聚类。
聚类与传统分类的一致性
K-Means(k=8)的聚类结果与八宫划分的 ARI 约 0.4 左右——有中等一致性但并非完全对应。这说明八宫体系(按爻变规律形成)与基于统计特征的聚类在数学上有相似性但不完全等同。差异来源很可能是"卦变序列"中包含的时间维度无法被静态聚类捕获。
娱乐性质 vs 分析深度
卦象聚类的结果不能用于预测或决策——这不是一个"预测模型"而是一个"探索性数据分析"。它的价值在于提供一种新的观察六十四卦结构关系的视角——比如发现哪两卦在特征空间中最接近,哪些卦形成了天然的聚类。
五、总结
六十四卦的机器学习分析方法包括:二进制编码(六爻→6维向量)、特征扩展(爻位加权/阴阳分布/八宫号/上下卦编码→29维)、PCA降维、多算法聚类(K-Means/DBSCAN/层次聚类)、对称性与传统理论的交叉验证。爻位加权反映传统"爻位贵贱"说。对卦在 PCA 空间中呈现对称分布的几何结构。聚类结果与八宫划分的 ARI 约 0.4。实施中需注意样本量(64)与特征维数(29)的平衡、聚类与传统分类的对应与差异、分析方法的探索性质。核心价值在于用数学工具发现符号系统的隐藏结构,而非替代传统解读。