@icyfenix-dmla/cli 2026.5.13-2349 → 2026.5.13-2356

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icyfenix-dmla/cli",
3
- "version": "2026.5.13-2349",
3
+ "version": "2026.5.13-2356",
4
4
  "description": "DMLA 沙箱服务命令行工具",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/version.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "buildTime": "2026-05-13T15:50:26.143Z",
3
- "cliVersion": "2026.5.13-2349"
2
+ "buildTime": "2026-05-13T15:56:58.097Z",
3
+ "cliVersion": "2026.5.13-2356"
4
4
  }
@@ -1,141 +0,0 @@
1
- # GaussianMixtureModel 类定义
2
- # 从文档自动提取生成
3
-
4
- import numpy as np
5
-
6
- class GaussianMixtureModel:
7
- """
8
- 高斯混合模型实现
9
- 使用EM算法求解
10
- """
11
- def __init__(self, n_components=3, max_iter=100, tol=1e-4):
12
- self.n_components = n_components
13
- self.max_iter = max_iter
14
- self.tol = tol # 收敛阈值
15
-
16
- self.weights_ = None # 混合系数 (K,)
17
- self.means_ = None # 均值 (K, n_features)
18
- self.covariances_ = None # 协方差矩阵 (K, n_features, n_features)
19
- self.log_likelihood_history_ = []
20
-
21
- def _initialize(self, X):
22
- """初始化参数"""
23
- n_samples, n_features = X.shape
24
- K = self.n_components
25
-
26
- # 随机初始化均值(从数据中随机选择K个点)
27
- indices = np.random.choice(n_samples, K, replace=False)
28
- self.means_ = X[indices].copy()
29
-
30
- # 初始化协方差为数据协方差的对角线
31
- data_cov = np.cov(X.T)
32
- self.covariances_ = np.array([np.diag(np.diag(data_cov)) + 1e-6 * np.eye(n_features)
33
- for _ in range(K)])
34
-
35
- # 初始化混合系数为均匀分布
36
- self.weights_ = np.ones(K) / K
37
-
38
- def _gaussian_pdf(self, X, mean, cov):
39
- """计算多元高斯概率密度"""
40
- n_features = X.shape[1]
41
- diff = X - mean
42
-
43
- # 加小值保证数值稳定
44
- cov_reg = cov + 1e-6 * np.eye(n_features)
45
-
46
- # 使用Cholesky分解计算行列式和逆
47
- try:
48
- L = np.linalg.cholesky(cov_reg)
49
- log_det = 2 * np.sum(np.log(np.diag(L)))
50
- diff_L = np.linalg.solve(L, diff.T).T
51
- mahalanobis = np.sum(diff_L ** 2, axis=1)
52
- except np.linalg.LinAlgError:
53
- # 如果Cholesky失败,使用标准方法
54
- sign, log_det = np.linalg.slogdet(cov_reg)
55
- cov_inv = np.linalg.inv(cov_reg)
56
- mahalanobis = np.sum(diff @ cov_inv * diff, axis=1)
57
-
58
- log_prob = -0.5 * (n_features * np.log(2 * np.pi) + log_det + mahalanobis)
59
- return log_prob
60
-
61
- def _e_step(self, X):
62
- """E步:计算责任度"""
63
- n_samples = X.shape[0]
64
- K = self.n_components
65
-
66
- # 计算每个成分的对数概率
67
- log_probs = np.zeros((n_samples, K))
68
- for k in range(K):
69
- log_probs[:, k] = (np.log(self.weights_[k] + 1e-10) +
70
- self._gaussian_pdf(X, self.means_[k], self.covariances_[k]))
71
-
72
- # 计算对数似然
73
- log_likelihood = np.sum(np.log(np.sum(np.exp(log_probs), axis=1)))
74
-
75
- # 计算责任度(使用log-sum-exp trick避免数值下溢)
76
- log_max = log_probs.max(axis=1, keepdims=True)
77
- log_sum = np.log(np.sum(np.exp(log_probs - log_max), axis=1, keepdims=True)) + log_max
78
- responsibilities = np.exp(log_probs - log_sum)
79
-
80
- return responsibilities, log_likelihood
81
-
82
- def _m_step(self, X, responsibilities):
83
- """M步:更新参数"""
84
- n_samples, n_features = X.shape
85
- K = self.n_components
86
-
87
- # 计算每个成分的有效样本数
88
- N_k = responsibilities.sum(axis=0) + 1e-10
89
-
90
- # 更新混合系数
91
- self.weights_ = N_k / n_samples
92
-
93
- # 更新均值
94
- self.means_ = (responsibilities.T @ X) / N_k[:, np.newaxis]
95
-
96
- # 更新协方差
97
- for k in range(K):
98
- diff = X - self.means_[k]
99
- weighted_diff = responsibilities[:, k:k+1] * diff
100
- self.covariances_[k] = (weighted_diff.T @ diff) / N_k[k]
101
- # 添加正则化
102
- self.covariances_[k] += 1e-6 * np.eye(n_features)
103
-
104
- def fit(self, X):
105
- """训练模型"""
106
- self._initialize(X)
107
- self.log_likelihood_history_ = []
108
-
109
- prev_log_likelihood = -np.inf
110
-
111
- for iteration in range(self.max_iter):
112
- # E步
113
- responsibilities, log_likelihood = self._e_step(X)
114
- self.log_likelihood_history_.append(log_likelihood)
115
-
116
- # 检查收敛
117
- if abs(log_likelihood - prev_log_likelihood) < self.tol:
118
- print(f"EM收敛于第{iteration}次迭代")
119
- break
120
-
121
- # M步
122
- self._m_step(X, responsibilities)
123
-
124
- prev_log_likelihood = log_likelihood
125
-
126
- return self
127
-
128
- def predict(self, X):
129
- """预测聚类标签"""
130
- responsibilities, _ = self._e_step(X)
131
- return np.argmax(responsibilities, axis=1)
132
-
133
- def predict_proba(self, X):
134
- """预测属于各成分的概率"""
135
- responsibilities, _ = self._e_step(X)
136
- return responsibilities
137
-
138
- def score(self, X):
139
- """计算对数似然"""
140
- _, log_likelihood = self._e_step(X)
141
- return log_likelihood
@@ -1,99 +0,0 @@
1
- # SimpleBayesianNetwork 类定义
2
- # 从文档自动提取生成
3
-
4
- class SimpleBayesianNetwork:
5
- """
6
- 简单贝叶斯网络实现
7
- 支持离散变量和精确推断(枚举法)
8
- """
9
- def __init__(self):
10
- self.nodes = {} # 节点信息:{name: {'parents': [], 'values': []}}
11
- self.cpts = {} # 条件概率表:{name: {parent_values: {value: prob}}}
12
- self.topo_order = [] # 拓扑排序
13
-
14
- def add_node(self, name, values, parents=None):
15
- """添加节点"""
16
- if parents is None:
17
- parents = []
18
- self.nodes[name] = {'parents': parents, 'values': values}
19
- self._update_topo_order()
20
-
21
- def set_cpt(self, name, cpt):
22
- """
23
- 设置条件概率表
24
-
25
- cpt格式:{parent_value_tuple: {value: prob}}
26
- 对于无父节点的变量:{(): {value: prob}}
27
- """
28
- self.cpts[name] = cpt
29
-
30
- def _update_topo_order(self):
31
- """计算拓扑排序"""
32
- visited = set()
33
- order = []
34
-
35
- def visit(node):
36
- if node in visited:
37
- return
38
- visited.add(node)
39
- for parent in self.nodes[node]['parents']:
40
- visit(parent)
41
- order.append(node)
42
-
43
- for node in self.nodes:
44
- visit(node)
45
-
46
- self.topo_order = order
47
-
48
- def get_prob(self, name, value, parent_values):
49
- """获取条件概率 P(name=value | parent_values)"""
50
- parent_key = tuple(parent_values) if parent_values else ()
51
- return self.cpts[name].get(parent_key, {}).get(value, 0)
52
-
53
- def joint_prob(self, assignment):
54
- """计算联合概率 P(X1, X2, ...)"""
55
- prob = 1.0
56
- for node in self.topo_order:
57
- parents = self.nodes[node]['parents']
58
- parent_values = [assignment[p] for p in parents]
59
- value = assignment[node]
60
- prob *= self.get_prob(node, value, parent_values)
61
- return prob
62
-
63
- def enumerate_inference(self, query, evidence):
64
- """
65
- 枚举推断:计算 P(query | evidence)
66
-
67
- query: {node: '?'} 返回分布
68
- evidence: {node: value}
69
- """
70
- query_nodes = list(query.keys())
71
- hidden = [n for n in self.nodes if n not in query_nodes and n not in evidence]
72
-
73
- def enumerate_assignments(variables, current):
74
- if not variables:
75
- yield current.copy()
76
- return
77
- var = variables[0]
78
- for value in self.nodes[var]['values']:
79
- current[var] = value
80
- yield from enumerate_assignments(variables[1:], current)
81
- del current[var]
82
-
83
- query_values = {}
84
- total = 0.0
85
-
86
- query_node = query_nodes[0]
87
- for qv in self.nodes[query_node]['values']:
88
- prob_sum = 0.0
89
- for assignment in enumerate_assignments(hidden, {}):
90
- assignment.update(evidence)
91
- assignment[query_node] = qv
92
- prob_sum += self.joint_prob(assignment)
93
- query_values[qv] = prob_sum
94
- total += prob_sum
95
-
96
- # 归一化
97
- for k in query_values:
98
- query_values[k] /= total
99
- return query_values
@@ -1,65 +0,0 @@
1
- # AlexNet 类定义
2
- # 从文档自动提取生成
3
-
4
- import torch
5
- import torch.nn as nn
6
- from PIL import Image
7
-
8
- class AlexNet(nn.Module):
9
- """
10
- AlexNet 网络结构
11
- 适配 Tiny ImageNet 200 类分类任务
12
-
13
- 原始 AlexNet 为 1000 类,这里修改最后一层为 200 类
14
- 使用 AdaptiveAvgPool2d 确保输出尺寸固定为 6x6
15
- """
16
- def __init__(self, num_classes=200):
17
- super(AlexNet, self).__init__()
18
-
19
- # 特征提取层 (5 个卷积层)
20
- self.features = nn.Sequential(
21
- # Conv1: 11x11 卷积,步长 4,输出 96 通道
22
- nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=2),
23
- nn.ReLU(inplace=True),
24
- nn.MaxPool2d(kernel_size=3, stride=2),
25
-
26
- # Conv2: 5x5 卷积,输出 256 通道
27
- nn.Conv2d(96, 256, kernel_size=5, stride=1, padding=2),
28
- nn.ReLU(inplace=True),
29
- nn.MaxPool2d(kernel_size=3, stride=2),
30
-
31
- # Conv3: 3x3 卷积,输出 384 通道
32
- nn.Conv2d(256, 384, kernel_size=3, stride=1, padding=1),
33
- nn.ReLU(inplace=True),
34
-
35
- # Conv4: 3x3 卷积,输出 384 通道
36
- nn.Conv2d(384, 384, kernel_size=3, stride=1, padding=1),
37
- nn.ReLU(inplace=True),
38
-
39
- # Conv5: 3x3 卷积,输出 256 通道
40
- nn.Conv2d(384, 256, kernel_size=3, stride=1, padding=1),
41
- nn.ReLU(inplace=True),
42
- nn.MaxPool2d(kernel_size=3, stride=2),
43
-
44
- # 自适应池化,确保输出固定为 6x6
45
- nn.AdaptiveAvgPool2d((6, 6))
46
- )
47
-
48
- # 分类层 (3 个全连接层)
49
- self.classifier = nn.Sequential(
50
- nn.Dropout(p=0.5),
51
- nn.Linear(256 * 6 * 6, 4096),
52
- nn.ReLU(inplace=True),
53
-
54
- nn.Dropout(p=0.5),
55
- nn.Linear(4096, 4096),
56
- nn.ReLU(inplace=True),
57
-
58
- nn.Linear(4096, num_classes)
59
- )
60
-
61
- def forward(self, x):
62
- x = self.features(x)
63
- x = torch.flatten(x, 1)
64
- x = self.classifier(x)
65
- return x
@@ -1,65 +0,0 @@
1
- # AlexNet 类定义
2
- # 从文档自动提取生成
3
-
4
- import torch
5
- import torch.nn as nn
6
- from PIL import Image
7
-
8
- class AlexNet(nn.Module):
9
- """
10
- AlexNet 网络结构
11
- 适配 Tiny ImageNet 200 类分类任务
12
-
13
- 原始 AlexNet 为 1000 类,这里修改最后一层为 200 类
14
- 使用 AdaptiveAvgPool2d 确保输出尺寸固定为 6x6
15
- """
16
- def __init__(self, num_classes=200):
17
- super(AlexNet, self).__init__()
18
-
19
- # 特征提取层 (5 个卷积层)
20
- self.features = nn.Sequential(
21
- # Conv1: 11x11 卷积,步长 4,输出 96 通道
22
- nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=2),
23
- nn.ReLU(inplace=True),
24
- nn.MaxPool2d(kernel_size=3, stride=2),
25
-
26
- # Conv2: 5x5 卷积,输出 256 通道
27
- nn.Conv2d(96, 256, kernel_size=5, stride=1, padding=2),
28
- nn.ReLU(inplace=True),
29
- nn.MaxPool2d(kernel_size=3, stride=2),
30
-
31
- # Conv3: 3x3 卷积,输出 384 通道
32
- nn.Conv2d(256, 384, kernel_size=3, stride=1, padding=1),
33
- nn.ReLU(inplace=True),
34
-
35
- # Conv4: 3x3 卷积,输出 384 通道
36
- nn.Conv2d(384, 384, kernel_size=3, stride=1, padding=1),
37
- nn.ReLU(inplace=True),
38
-
39
- # Conv5: 3x3 卷积,输出 256 通道
40
- nn.Conv2d(384, 256, kernel_size=3, stride=1, padding=1),
41
- nn.ReLU(inplace=True),
42
- nn.MaxPool2d(kernel_size=3, stride=2),
43
-
44
- # 自适应池化,确保输出固定为 6x6
45
- nn.AdaptiveAvgPool2d((6, 6))
46
- )
47
-
48
- # 分类层 (3 个全连接层)
49
- self.classifier = nn.Sequential(
50
- nn.Dropout(p=0.5),
51
- nn.Linear(256 * 6 * 6, 4096),
52
- nn.ReLU(inplace=True),
53
-
54
- nn.Dropout(p=0.5),
55
- nn.Linear(4096, 4096),
56
- nn.ReLU(inplace=True),
57
-
58
- nn.Linear(4096, num_classes)
59
- )
60
-
61
- def forward(self, x):
62
- x = self.features(x)
63
- x = torch.flatten(x, 1)
64
- x = self.classifier(x)
65
- return x
@@ -1,67 +0,0 @@
1
- # TinyImageNetDataset 类定义
2
- # 从文档自动提取生成
3
-
4
- import os
5
- from PIL import Image
6
- from torch.utils.data import Dataset, DataLoader
7
-
8
- class TinyImageNetDataset(Dataset):
9
- """
10
- Tiny ImageNet 200 数据集加载器
11
-
12
- 训练集按类别子目录读取,验证集从标注文件解析标签。
13
- 支持自定义预处理变换,适配 AlexNet 训练需求。
14
- """
15
- def __init__(self, root_dir, transform=None, is_train=True):
16
- self.root_dir = root_dir
17
- self.transform = transform
18
- self.is_train = is_train
19
-
20
- self.samples = []
21
- self.classes = []
22
-
23
- if is_train:
24
- train_dir = os.path.join(root_dir, 'train')
25
- self.classes = sorted(os.listdir(train_dir))
26
- self.class_to_idx = {cls: idx for idx, cls in enumerate(self.classes)}
27
-
28
- for cls in self.classes:
29
- cls_dir = os.path.join(train_dir, cls)
30
- images_dir = os.path.join(cls_dir, 'images')
31
- if os.path.exists(images_dir):
32
- for img_name in os.listdir(images_dir):
33
- if img_name.endswith('.JPEG'):
34
- self.samples.append((
35
- os.path.join(images_dir, img_name),
36
- self.class_to_idx[cls]
37
- ))
38
- else:
39
- val_dir = os.path.join(root_dir, 'val')
40
- val_images_dir = os.path.join(val_dir, 'images')
41
- val_annotations = os.path.join(val_dir, 'val_annotations.txt')
42
-
43
- if os.path.exists(val_annotations):
44
- with open(val_annotations, 'r') as f:
45
- for line in f:
46
- parts = line.strip().split('\t')
47
- if len(parts) >= 2:
48
- img_name = parts[0]
49
- cls = parts[1]
50
- if cls not in self.classes:
51
- self.classes.append(cls)
52
- self.samples.append((
53
- os.path.join(val_images_dir, img_name),
54
- self.classes.index(cls)
55
- ))
56
-
57
- def __len__(self):
58
- return len(self.samples)
59
-
60
- def __getitem__(self, idx):
61
- img_path, label = self.samples[idx]
62
- image = Image.open(img_path).convert('RGB')
63
-
64
- if self.transform:
65
- image = self.transform(image)
66
-
67
- return image, label
@@ -1,67 +0,0 @@
1
- # TinyImageNetDataset 类定义
2
- # 从文档自动提取生成
3
-
4
- import os
5
- from PIL import Image
6
- from torch.utils.data import Dataset, DataLoader
7
-
8
- class TinyImageNetDataset(Dataset):
9
- """
10
- Tiny ImageNet 200 数据集加载器
11
-
12
- 训练集按类别子目录读取,验证集从标注文件解析标签。
13
- 支持自定义预处理变换,适配 AlexNet 训练需求。
14
- """
15
- def __init__(self, root_dir, transform=None, is_train=True):
16
- self.root_dir = root_dir
17
- self.transform = transform
18
- self.is_train = is_train
19
-
20
- self.samples = []
21
- self.classes = []
22
-
23
- if is_train:
24
- train_dir = os.path.join(root_dir, 'train')
25
- self.classes = sorted(os.listdir(train_dir))
26
- self.class_to_idx = {cls: idx for idx, cls in enumerate(self.classes)}
27
-
28
- for cls in self.classes:
29
- cls_dir = os.path.join(train_dir, cls)
30
- images_dir = os.path.join(cls_dir, 'images')
31
- if os.path.exists(images_dir):
32
- for img_name in os.listdir(images_dir):
33
- if img_name.endswith('.JPEG'):
34
- self.samples.append((
35
- os.path.join(images_dir, img_name),
36
- self.class_to_idx[cls]
37
- ))
38
- else:
39
- val_dir = os.path.join(root_dir, 'val')
40
- val_images_dir = os.path.join(val_dir, 'images')
41
- val_annotations = os.path.join(val_dir, 'val_annotations.txt')
42
-
43
- if os.path.exists(val_annotations):
44
- with open(val_annotations, 'r') as f:
45
- for line in f:
46
- parts = line.strip().split('\t')
47
- if len(parts) >= 2:
48
- img_name = parts[0]
49
- cls = parts[1]
50
- if cls not in self.classes:
51
- self.classes.append(cls)
52
- self.samples.append((
53
- os.path.join(val_images_dir, img_name),
54
- self.classes.index(cls)
55
- ))
56
-
57
- def __len__(self):
58
- return len(self.samples)
59
-
60
- def __getitem__(self, idx):
61
- img_path, label = self.samples[idx]
62
- image = Image.open(img_path).convert('RGB')
63
-
64
- if self.transform:
65
- image = self.transform(image)
66
-
67
- return image, label
@@ -1,67 +0,0 @@
1
- # TinyImageNetDataset 类定义
2
- # 从文档自动提取生成
3
-
4
- import os
5
- from PIL import Image
6
- from torch.utils.data import Dataset, DataLoader
7
-
8
- class TinyImageNetDataset(Dataset):
9
- """
10
- Tiny ImageNet 200 数据集加载器
11
-
12
- 训练集按类别子目录读取,验证集从标注文件解析标签。
13
- 支持自定义预处理变换,适配 AlexNet 训练需求。
14
- """
15
- def __init__(self, root_dir, transform=None, is_train=True):
16
- self.root_dir = root_dir
17
- self.transform = transform
18
- self.is_train = is_train
19
-
20
- self.samples = []
21
- self.classes = []
22
-
23
- if is_train:
24
- train_dir = os.path.join(root_dir, 'train')
25
- self.classes = sorted(os.listdir(train_dir))
26
- self.class_to_idx = {cls: idx for idx, cls in enumerate(self.classes)}
27
-
28
- for cls in self.classes:
29
- cls_dir = os.path.join(train_dir, cls)
30
- images_dir = os.path.join(cls_dir, 'images')
31
- if os.path.exists(images_dir):
32
- for img_name in os.listdir(images_dir):
33
- if img_name.endswith('.JPEG'):
34
- self.samples.append((
35
- os.path.join(images_dir, img_name),
36
- self.class_to_idx[cls]
37
- ))
38
- else:
39
- val_dir = os.path.join(root_dir, 'val')
40
- val_images_dir = os.path.join(val_dir, 'images')
41
- val_annotations = os.path.join(val_dir, 'val_annotations.txt')
42
-
43
- if os.path.exists(val_annotations):
44
- with open(val_annotations, 'r') as f:
45
- for line in f:
46
- parts = line.strip().split('\t')
47
- if len(parts) >= 2:
48
- img_name = parts[0]
49
- cls = parts[1]
50
- if cls not in self.classes:
51
- self.classes.append(cls)
52
- self.samples.append((
53
- os.path.join(val_images_dir, img_name),
54
- self.classes.index(cls)
55
- ))
56
-
57
- def __len__(self):
58
- return len(self.samples)
59
-
60
- def __getitem__(self, idx):
61
- img_path, label = self.samples[idx]
62
- image = Image.open(img_path).convert('RGB')
63
-
64
- if self.transform:
65
- image = self.transform(image)
66
-
67
- return image, label
@@ -1,98 +0,0 @@
1
- # KernelSVM 类定义
2
- # 从文档自动提取生成
3
-
4
- import numpy as np
5
-
6
- class KernelSVM:
7
- """
8
- 核SVM实现
9
- 支持线性核、多项式核、RBF核
10
- """
11
- def __init__(self, kernel='rbf', C=1.0, gamma=1.0, degree=3, coef0=1):
12
- self.kernel = kernel
13
- self.C = C
14
- self.gamma = gamma
15
- self.degree = degree
16
- self.coef0 = coef0 # 多项式核的常数项
17
-
18
- self.alpha = None
19
- self.b = None
20
- self.X_train = None
21
- self.y_train = None
22
- self.support_vectors_ = None
23
- self.support_vector_labels_ = None
24
- self.alpha_sv = None
25
-
26
- def _kernel(self, X1, X2):
27
- """计算核矩阵"""
28
- if self.kernel == 'linear':
29
- return X1 @ X2.T
30
-
31
- elif self.kernel == 'poly':
32
- return (X1 @ X2.T + self.coef0) ** self.degree
33
-
34
- elif self.kernel == 'rbf':
35
- # ||x - x'||^2 = ||x||^2 + ||x'||^2 - 2*x^T*x'
36
- X1_norm = np.sum(X1 ** 2, axis=1).reshape(-1, 1)
37
- X2_norm = np.sum(X2 ** 2, axis=1).reshape(1, -1)
38
- distances = X1_norm + X2_norm - 2 * X1 @ X2.T
39
- return np.exp(-self.gamma * distances)
40
-
41
- else:
42
- raise ValueError(f"未知核函数: {self.kernel}")
43
-
44
- def fit(self, X, y, lr=0.01, n_iterations=500):
45
- """训练模型(简化版SMO思想)"""
46
- n_samples = X.shape[0]
47
- self.X_train = X
48
- self.y_train = y
49
-
50
- # 计算核矩阵
51
- K = self._kernel(X, X)
52
-
53
- # 初始化
54
- self.alpha = np.zeros(n_samples)
55
-
56
- # 梯度上升优化
57
- for _ in range(n_iterations):
58
- for i in range(n_samples):
59
- # 梯度
60
- gradient = 1 - y[i] * np.sum(self.alpha * y * K[:, i])
61
- self.alpha[i] += lr * gradient
62
- self.alpha[i] = np.clip(self.alpha[i], 0, self.C)
63
-
64
- # 约束修正:满足等式约束 sum(alpha * y) = 0
65
- # 减去均值偏差后,需再次投影到边界约束 [0, C]
66
- self.alpha = self.alpha - np.mean(self.alpha * y) * y
67
- self.alpha = np.clip(self.alpha, 0, self.C)
68
- # 注意:投影后等式约束可能不再精确满足,但迭代过程中误差会累积抵消
69
-
70
- # 支持向量
71
- sv_mask = self.alpha > 1e-5
72
- self.support_vectors_ = X[sv_mask]
73
- self.support_vector_labels_ = y[sv_mask]
74
- self.alpha_sv = self.alpha[sv_mask]
75
-
76
- # 计算b
77
- if len(self.support_vectors_) > 0:
78
- K_sv = self._kernel(self.support_vectors_, self.support_vectors_)
79
- margins = np.sum(self.alpha_sv * self.support_vector_labels_ * K_sv, axis=1)
80
- self.b = np.mean(self.support_vector_labels_ - margins)
81
- else:
82
- self.b = 0
83
-
84
- return self
85
-
86
- def decision_function(self, X):
87
- """决策函数"""
88
- K = self._kernel(X, self.support_vectors_)
89
- return K @ (self.alpha_sv * self.support_vector_labels_) + self.b
90
-
91
- def predict(self, X):
92
- """预测类别"""
93
- return np.sign(self.decision_function(X)).astype(int)
94
-
95
- def score(self, X, y):
96
- """计算准确率"""
97
- y_pred = self.predict(X)
98
- return np.mean(y_pred == y)
@@ -1,111 +0,0 @@
1
- # SimpleSVM 类定义
2
- # 从文档自动提取生成
3
-
4
- import numpy as np
5
-
6
- class SimpleSVM:
7
- """
8
- 简化版软间隔SVM实现
9
-
10
- 使用梯度上升优化对偶问题,支持软间隔(通过参数C控制)
11
-
12
- 核心步骤:
13
- 1. 预计算核矩阵 K = X @ X.T(线性核)
14
- 2. 迭代更新拉格朗日乘子 alpha
15
- 3. 根据alpha找出支持向量
16
- 4. 计算超平面参数 w 和 b
17
- """
18
-
19
- def __init__(self, learning_rate=0.01, n_iterations=1000, C=1.0):
20
- self.lr = learning_rate # 梯度上升的学习率
21
- self.n_iterations = n_iterations # 迭代次数
22
- self.C = C # 软间隔惩罚系数
23
- self.alpha = None # 拉格朗日乘子(训练后获得)
24
- self.w = None # 超平面法向量
25
- self.b = None # 超平面截距
26
- self.support_vectors_ = None # 支持向量集合
27
-
28
- def fit(self, X, y):
29
- """
30
- 训练SVM模型
31
-
32
- 对偶问题的目标函数:
33
- max sum(alpha_i) - 0.5 * sum(alpha_i * alpha_j * y_i * y_j * x_i^T x_j)
34
- 约束:0 <= alpha_i <= C, sum(alpha_i * y_i) = 0
35
-
36
- 使用梯度上升迭代优化,每次更新一个alpha_i
37
- """
38
- n_samples, n_features = X.shape
39
-
40
- # 初始化拉格朗日乘子(全零)
41
- self.alpha = np.zeros(n_samples)
42
-
43
- # 预计算核矩阵(线性核:样本内积)
44
- # K[i,j] = x_i^T x_j,用于加速目标函数计算
45
- K = X @ X.T
46
-
47
- # 梯度上升优化对偶问题
48
- for iteration in range(self.n_iterations):
49
- for i in range(n_samples):
50
- # 计算alpha_i的梯度
51
- # 目标函数对alpha_i的偏导:1 - y_i * sum_j(alpha_j * y_j * K[j,i])
52
- gradient = 1 - y[i] * np.sum(self.alpha * y * K[:, i])
53
-
54
- # 梯度上升更新
55
- self.alpha[i] += self.lr * gradient
56
-
57
- # 投影到约束区间 [0, C]
58
- # 对应软间隔的约束:0 <= alpha_i <= C
59
- self.alpha[i] = np.clip(self.alpha[i], 0, self.C)
60
-
61
- # 约束修正:确保 sum(alpha * y) = 0
62
- # 通过减去均值偏差来近似满足线性约束
63
- bias = np.mean(self.alpha * y)
64
- self.alpha = self.alpha - bias * y
65
- self.alpha = np.clip(self.alpha, 0, self.C)
66
-
67
- # 找出支持向量(alpha > 阈值的样本)
68
- sv_threshold = 1e-5
69
- sv_indices = self.alpha > sv_threshold
70
- self.support_vectors_ = X[sv_indices]
71
- sv_labels = y[sv_indices]
72
- sv_alpha = self.alpha[sv_indices]
73
-
74
- # 计算超平面参数 w = sum(alpha_i * y_i * x_i)
75
- # 只有支持向量参与计算(其他样本alpha=0)
76
- self.w = np.zeros(n_features)
77
- for i, (sv, label, a) in enumerate(zip(self.support_vectors_, sv_labels, sv_alpha)):
78
- self.w += a * label * sv
79
-
80
- # 计算截距 b
81
- # 使用支持向量计算:对于支持向量,y_i(w^T x_i + b) = 1(硬间隔)
82
- # 或 y_i(w^T x_i + b) = 1 - xi_i(软间隔)
83
- # 这里取所有支持向量的平均值
84
- if len(self.support_vectors_) > 0:
85
- self.b = np.mean(sv_labels - self.support_vectors_ @ self.w)
86
- else:
87
- self.b = 0
88
-
89
- return self
90
-
91
- def decision_function(self, X):
92
- """
93
- 决策函数值:w^T x + b
94
-
95
- 正值表示预测为正类,负值表示预测为负类
96
- 绝对值大小反映样本到超平面的距离
97
- """
98
- return X @ self.w + self.b
99
-
100
- def predict(self, X):
101
- """
102
- 预测类别标签
103
-
104
- sign(w^T x + b): +1 表示正类,-1 表示负类
105
- """
106
- return np.sign(self.decision_function(X)).astype(int)
107
-
108
- def score(self, X, y):
109
- """计算分类准确率"""
110
- predictions = self.predict(X)
111
- return np.mean(predictions == y)
@@ -1,235 +0,0 @@
1
- # DecisionTreeClassifier 类定义
2
- # 从文档自动提取生成
3
-
4
- import numpy as np
5
-
6
- class DecisionTreeClassifier:
7
- """
8
- CART 决策树分类器
9
-
10
- 使用 Gini 指数作为分裂准则,构建二叉决策树。
11
- 支持预剪枝策略:最大深度限制和叶节点最小样本数限制。
12
-
13
- 参数:
14
- max_depth : int, 默认值 10
15
- 树的最大深度,防止过拟合
16
- min_samples_split : int, 默认值 2
17
- 分裂所需的最小样本数,防止学习孤例
18
- """
19
-
20
- def __init__(self, max_depth=10, min_samples_split=2, min_gain_threshold=0.0):
21
- self.max_depth = max_depth
22
- self.min_samples_split = min_samples_split
23
- self.min_gain_threshold = min_gain_threshold
24
- self.tree = None
25
-
26
- def _gini(self, y):
27
- """
28
- 计算数据集的 Gini 指数
29
-
30
- Gini 指数衡量数据的不纯度,值越小越纯净。
31
-
32
- 参数:
33
- y : ndarray
34
- 目标变量数组
35
-
36
- 返回:
37
- float : Gini 指数值
38
- """
39
- if len(y) == 0:
40
- return 0
41
- _, counts = np.unique(y, return_counts=True)
42
- probs = counts / len(y)
43
- return 1 - np.sum(probs ** 2)
44
-
45
- def _gini_split(self, y_left, y_right):
46
- """
47
- 计算分裂后的加权 Gini 指数
48
-
49
- 加权平均两个子集的 Gini 指数,权重为样本数比例。
50
-
51
- 参数:
52
- y_left : ndarray
53
- 左分支的目标变量
54
- y_right : ndarray
55
- 右分支的目标变量
56
-
57
- 返回:
58
- float : 分裂后的加权 Gini 指数
59
- """
60
- n = len(y_left) + len(y_right)
61
- return (len(y_left) / n) * self._gini(y_left) + \
62
- (len(y_right) / n) * self._gini(y_right)
63
-
64
- def _best_split(self, X, y):
65
- """
66
- 寻找最佳分裂特征和分割点
67
-
68
- 遍历所有特征的所有候选分割点,选择 Gini 指数最小的分裂方案。
69
- 候选分割点是特征的唯一值(CART 的标准策略)。
70
-
71
- 参数:
72
- X : ndarray, shape (n_samples, n_features)
73
- 特征矩阵
74
- y : ndarray, shape (n_samples,)
75
- 目标变量
76
-
77
- 返回:
78
- tuple : (最佳特征索引, 最佳分割点, 对应的 Gini 指数)
79
- """
80
- best_gini = float('inf')
81
- best_feature = None
82
- best_threshold = None
83
-
84
- n_features = X.shape[1]
85
-
86
- for feature in range(n_features):
87
- # 获取该特征的所有唯一值作为候选分割点
88
- # 使用相邻唯一值的中点作为候选阈值(标准 CART 算法策略)
89
- thresholds = np.unique(X[:, feature])
90
- thresholds = (thresholds[:-1] + thresholds[1:]) / 2
91
-
92
- for threshold in thresholds:
93
- # 按阈值分裂数据
94
- left_mask = X[:, feature] <= threshold
95
- right_mask = ~left_mask
96
-
97
- y_left = y[left_mask]
98
- y_right = y[right_mask]
99
-
100
- # 忽略无效分裂(某分支为空)
101
- if len(y_left) == 0 or len(y_right) == 0:
102
- continue
103
-
104
- gini = self._gini_split(y_left, y_right)
105
-
106
- # 更新最优分裂
107
- if gini < best_gini:
108
- best_gini = gini
109
- best_feature = feature
110
- best_threshold = threshold
111
-
112
- return best_feature, best_threshold, best_gini
113
-
114
- def _build_tree(self, X, y, depth):
115
- """
116
- 递归构建决策树
117
-
118
- 核心步骤:
119
- 1. 检查终止条件(深度限制、样本数限制、纯净度)
120
- 2. 若满足终止条件,返回叶节点(多数类)
121
- 3. 否则寻找最优分裂,创建内部节点
122
- 4. 递归构建左右子树
123
-
124
- 参数:
125
- X : ndarray
126
- 特征矩阵
127
- y : ndarray
128
- 目标变量
129
- depth : int
130
- 当前深度
131
-
132
- 返回:
133
- dict : 树节点(字典表示)
134
- """
135
- n_samples = len(y)
136
-
137
- # 检查预剪枝终止条件
138
- if (depth >= self.max_depth or
139
- n_samples < self.min_samples_split or
140
- len(np.unique(y)) == 1):
141
- # 返回叶节点,预测值为多数类
142
- values, counts = np.unique(y, return_counts=True)
143
- return {'leaf': True, 'class': values[np.argmax(counts)]}
144
-
145
- # 寻找最优分裂
146
- feature, threshold, gini = self._best_split(X, y)
147
-
148
- # 若无法分裂或分裂增益不足,返回叶节点
149
- if feature is None or gini > self._gini(y) - self.min_gain_threshold:
150
- values, counts = np.unique(y, return_counts=True)
151
- return {'leaf': True, 'class': values[np.argmax(counts)]}
152
-
153
- # 分裂数据
154
- left_mask = X[:, feature] <= threshold
155
- right_mask = ~left_mask
156
-
157
- # 递归构建子树
158
- left_tree = self._build_tree(X[left_mask], y[left_mask], depth + 1)
159
- right_tree = self._build_tree(X[right_mask], y[right_mask], depth + 1)
160
-
161
- return {
162
- 'leaf': False,
163
- 'feature': feature,
164
- 'threshold': threshold,
165
- 'left': left_tree,
166
- 'right': right_tree
167
- }
168
-
169
- def fit(self, X, y):
170
- """
171
- 训练决策树
172
-
173
- 参数:
174
- X : ndarray, shape (n_samples, n_features)
175
- 特征矩阵
176
- y : ndarray, shape (n_samples,)
177
- 目标变量
178
-
179
- 返回:
180
- self : 训练后的模型实例
181
- """
182
- self.tree = self._build_tree(X, y, depth=0)
183
- return self
184
-
185
- def _predict_one(self, x, node):
186
- """
187
- 预测单个样本
188
-
189
- 从根节点开始,根据分裂条件选择分支,直到到达叶节点。
190
-
191
- 参数:
192
- x : ndarray
193
- 单个样本的特征向量
194
- node : dict
195
- 当前树节点
196
-
197
- 返回:
198
- int : 预测类别
199
- """
200
- if node['leaf']:
201
- return node['class']
202
-
203
- if x[node['feature']] <= node['threshold']:
204
- return self._predict_one(x, node['left'])
205
- else:
206
- return self._predict_one(x, node['right'])
207
-
208
- def predict(self, X):
209
- """
210
- 批量预测
211
-
212
- 参数:
213
- X : ndarray, shape (n_samples, n_features)
214
- 特征矩阵
215
-
216
- 返回:
217
- ndarray : 预测类别数组
218
- """
219
- return np.array([self._predict_one(x, self.tree) for x in X])
220
-
221
- def score(self, X, y):
222
- """
223
- 计算准确率
224
-
225
- 参数:
226
- X : ndarray
227
- 特征矩阵
228
- y : ndarray
229
- 真实类别
230
-
231
- 返回:
232
- float : 准确率
233
- """
234
- y_pred = self.predict(X)
235
- return np.mean(y_pred == y)
@@ -1,88 +0,0 @@
1
- # RandomForestClassifier 类定义
2
- # 从文档自动提取生成
3
-
4
- import numpy as np
5
-
6
- class RandomForestClassifier:
7
- """
8
- 随机森林分类器
9
-
10
- 实现:
11
- 1. Bootstrap采样(对应理论:样本随机)
12
- 2. 多棵决策树训练(每棵树使用不同的Bootstrap样本和特征子集)
13
- 3. 多数投票预测(对应理论:投票机制)
14
-
15
- 参数:
16
- n_estimators : int, 默认值 100
17
- 树的数量(对应理论中的B)
18
- max_depth : int, 默认值 10
19
- 每棵树的最大深度
20
- max_features : str or int, 默认值 'sqrt'
21
- 每次分裂时考虑的特征数量(对应理论中的m)
22
- """
23
-
24
- def __init__(self, n_estimators=100, max_depth=10, max_features='sqrt'):
25
- self.n_estimators = n_estimators
26
- self.max_depth = max_depth
27
- self.max_features = max_features
28
- self.trees = []
29
-
30
- def _bootstrap_sample(self, X, y):
31
- """
32
- Bootstrap采样(对应理论:有放回重采样)
33
-
34
- 从原始数据集中有放回地抽取n个样本
35
- """
36
- n_samples = X.shape[0]
37
- indices = np.random.choice(n_samples, n_samples, replace=True)
38
- return X[indices], y[indices]
39
-
40
- def fit(self, X, y):
41
- """
42
- 训练随机森林
43
-
44
- 核心步骤:
45
- 1. 确定特征子集大小m
46
- 2. 对每棵树:Bootstrap采样 → 训练决策树
47
- """
48
- n_features = X.shape[1]
49
-
50
- # 确定特征子集大小m(对应理论:分类用sqrt(d),回归用d/3)
51
- if self.max_features == 'sqrt':
52
- max_features = int(np.sqrt(n_features))
53
- elif self.max_features == 'log2':
54
- max_features = int(np.log2(n_features))
55
- else:
56
- max_features = n_features
57
-
58
- self.trees = []
59
- for _ in range(self.n_estimators):
60
- # Bootstrap采样
61
- X_sample, y_sample = self._bootstrap_sample(X, y)
62
-
63
- # 训练决策树(带特征随机)
64
- tree = DecisionTreeForRF(
65
- max_depth=self.max_depth,
66
- max_features=max_features
67
- )
68
- tree.fit(X_sample, y_sample)
69
- self.trees.append(tree)
70
-
71
- return self
72
-
73
- def predict(self, X):
74
- """
75
- 多数投票预测(对应理论:硬投票)
76
-
77
- 每棵树预测一个类别,选择得票最多的类别
78
- """
79
- predictions = np.array([tree.predict(X) for tree in self.trees])
80
- result = []
81
- for i in range(X.shape[0]):
82
- values, counts = np.unique(predictions[:, i], return_counts=True)
83
- result.append(values[np.argmax(counts)])
84
- return np.array(result)
85
-
86
- def score(self, X, y):
87
- """计算准确率"""
88
- return np.mean(self.predict(X) == y)
@@ -1,127 +0,0 @@
1
- # KMeans 类定义
2
- # 从文档自动提取生成
3
-
4
- import numpy as np
5
-
6
- class KMeans:
7
- """
8
- K-means聚类算法实现
9
-
10
- 参数:
11
- n_clusters : int, 簇的数量K
12
- max_iter : int, 最大迭代次数
13
- tol : float, 收敛阈值(中心变化小于此值时停止)
14
- n_init : int, 随机初始化的次数(取最优结果)
15
- """
16
-
17
- def __init__(self, n_clusters=3, max_iter=300, tol=1e-4, n_init=10):
18
- self.n_clusters = n_clusters
19
- self.max_iter = max_iter
20
- self.tol = tol
21
- self.n_init = n_init
22
-
23
- self.cluster_centers_ = None # 簇中心
24
- self.labels_ = None # 每个样本的簇分配
25
- self.inertia_ = None # 目标函数值(距离平方和)
26
-
27
- def _init_centers(self, X):
28
- """
29
- 随机初始化簇中心
30
-
31
- 从数据中随机选择K个样本作为初始中心
32
- """
33
- indices = np.random.choice(len(X), self.n_clusters, replace=False)
34
- return X[indices].copy()
35
-
36
- def _assign_clusters(self, X, centers):
37
- """
38
- 分配步骤:将每个样本分配到最近的簇中心
39
-
40
- 计算每个样本到所有中心的距离平方,返回最近的簇编号
41
- """
42
- distances = np.zeros((len(X), self.n_clusters))
43
- for k in range(self.n_clusters):
44
- # 计算样本到第k个中心的距离平方(对应目标函数中的||x - μ||²)
45
- distances[:, k] = np.sum((X - centers[k]) ** 2, axis=1)
46
- return np.argmin(distances, axis=1)
47
-
48
- def _update_centers(self, X, labels):
49
- """
50
- 更新步骤:重新计算每个簇的中心
51
-
52
- 簇中心 = 簇内样本的均值(这就是"means"的含义)
53
- """
54
- centers = np.zeros((self.n_clusters, X.shape[1]))
55
- for k in range(self.n_clusters):
56
- mask = labels == k
57
- if np.sum(mask) > 0:
58
- # 取簇内样本的均值作为新中心
59
- centers[k] = X[mask].mean(axis=0)
60
- else:
61
- # 空簇的罕见情况:随机重新初始化
62
- centers[k] = X[np.random.randint(len(X))]
63
- return centers
64
-
65
- def _compute_inertia(self, X, labels, centers):
66
- """
67
- 计算目标函数值J
68
-
69
- J = 所有样本到其所属簇中心的距离平方和
70
- """
71
- inertia = 0
72
- for k in range(self.n_clusters):
73
- mask = labels == k
74
- inertia += np.sum((X[mask] - centers[k]) ** 2)
75
- return inertia
76
-
77
- def fit(self, X):
78
- """
79
- 训练K-means模型
80
-
81
- 执行多次随机初始化,取目标函数最小的结果
82
- """
83
- best_inertia = float('inf')
84
- best_centers = None
85
- best_labels = None
86
-
87
- for init in range(self.n_init):
88
- # 初始化簇中心
89
- centers = self._init_centers(X)
90
-
91
- # 迭代直到收敛
92
- for i in range(self.max_iter):
93
- # 步骤2:分配样本到最近的簇
94
- labels = self._assign_clusters(X, centers)
95
-
96
- # 步骤3:更新簇中心
97
- new_centers = self._update_centers(X, labels)
98
-
99
- # 检查收敛:中心变化是否小于阈值
100
- if np.max(np.abs(new_centers - centers)) < self.tol:
101
- break
102
-
103
- centers = new_centers
104
-
105
- # 计算本次初始化的目标函数值
106
- inertia = self._compute_inertia(X, labels, centers)
107
-
108
- # 保留最优结果
109
- if inertia < best_inertia:
110
- best_inertia = inertia
111
- best_centers = centers.copy()
112
- best_labels = labels.copy()
113
-
114
- # 存储最优结果
115
- self.cluster_centers_ = best_centers
116
- self.labels_ = best_labels
117
- self.inertia_ = best_inertia
118
-
119
- return self
120
-
121
- def predict(self, X):
122
- """
123
- 预测新样本所属的簇
124
-
125
- 根据训练得到的簇中心,将新样本分配到最近的簇
126
- """
127
- return self._assign_clusters(X, self.cluster_centers_)
@@ -1,111 +0,0 @@
1
- # PCA 类定义
2
- # 从文档自动提取生成
3
-
4
- import numpy as np
5
-
6
- class PCA:
7
- """
8
- 主成分分析(Principal Component Analysis)实现
9
-
10
- 核心步骤(对应理论推导):
11
- 1. 数据中心化(减去均值)
12
- 2. 计算协方差矩阵 S = X^T X / (n-1)
13
- 3. 特征分解 S = V Λ V^T
14
- 4. 选择前 k 个特征值对应的特征向量作为主成分
15
- 5. 投影到主成分空间
16
-
17
- 参数说明:
18
- n_components : int, 可选
19
- 要保留的主成分数量。若为 None,保留所有成分
20
- """
21
-
22
- def __init__(self, n_components=None):
23
- self.n_components = n_components
24
-
25
- # 存储 PCA 结果
26
- self.components_ = None # 主成分(特征向量矩阵)
27
- self.explained_variance_ = None # 特征值(各主成分的方差)
28
- self.explained_variance_ratio_ = None # 方差解释比例
29
- self.mean_ = None # 数据均值向量
30
-
31
- def fit(self, X):
32
- """
33
- 训练 PCA 模型
34
-
35
- 参数说明:
36
- X : ndarray, shape (n_samples, n_features)
37
- 输入数据矩阵
38
-
39
- 返回:
40
- self : PCA 对象实例
41
- """
42
- n_samples, n_features = X.shape
43
-
44
- # 步骤1:数据中心化(对应理论中的 x_i - x̄)
45
- self.mean_ = X.mean(axis=0)
46
- X_centered = X - self.mean_
47
-
48
- # 步骤2:计算协方差矩阵(对应理论中的 S = 1/n Σ(x_i - x̄)(x_i - x̄)^T)
49
- # 使用 n-1 而非 n,得到无偏估计(与 sklearn 一致)
50
- cov_matrix = X_centered.T @ X_centered / (n_samples - 1)
51
-
52
- # 步骤3:特征分解(对应理论中的 S = VΛV^T)
53
- # np.linalg.eigh 专门用于对称矩阵,返回实数特征值
54
- eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix)
55
-
56
- # 特征值和特征向量按降序排列(PCA 选择方差最大的方向)
57
- indices = np.argsort(eigenvalues)[::-1]
58
- eigenvalues = eigenvalues[indices]
59
- eigenvectors = eigenvectors[:, indices]
60
-
61
- # 存储特征值(对应理论中的 λ_j)
62
- self.explained_variance_ = eigenvalues
63
-
64
- # 步骤4:计算方差解释比例(对应理论中的 Σλ_j / Σλ_total)
65
- total_variance = eigenvalues.sum()
66
- self.explained_variance_ratio_ = eigenvalues / total_variance
67
-
68
- # 确定主成分数量
69
- if self.n_components is None:
70
- self.n_components = n_features
71
-
72
- # 步骤5:选择前 k 个主成分(对应理论中的 V_k)
73
- self.components_ = eigenvectors[:, :self.n_components].T
74
-
75
- return self
76
-
77
- def transform(self, X):
78
- """
79
- 将数据投影到主成分空间
80
-
81
- 参数说明:
82
- X : ndarray, shape (n_samples, n_features)
83
- 输入数据
84
-
85
- 返回:
86
- Z : ndarray, shape (n_samples, n_components)
87
- 投影后的低维数据
88
- """
89
- # 中心化后投影(对应理论中的 Z = X̃ V_k)
90
- X_centered = X - self.mean_
91
- return X_centered @ self.components_.T
92
-
93
- def fit_transform(self, X):
94
- """训练并转换(一步完成)"""
95
- self.fit(X)
96
- return self.transform(X)
97
-
98
- def inverse_transform(self, Z):
99
- """
100
- 从低维空间重构原始数据
101
-
102
- 参数说明:
103
- Z : ndarray, shape (n_samples, n_components)
104
- 低维表示
105
-
106
- 返回:
107
- X_reconstructed : ndarray, shape (n_samples, n_features)
108
- 重构的高维数据(加回均值)
109
- """
110
- # 重构公式(对应理论中的 X̂ = Z V_k^T + x̄)
111
- return Z @ self.components_ + self.mean_