tensorplay 0.1.1__py3-none-any.whl
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.
- TensorPlay/__init__.py +38 -0
- TensorPlay/core.py +420 -0
- TensorPlay/data.py +72 -0
- TensorPlay/func.py +81 -0
- TensorPlay/initializer.py +26 -0
- TensorPlay/layer.py +324 -0
- TensorPlay/module.py +237 -0
- TensorPlay/operator.py +1053 -0
- TensorPlay/optimizer.py +157 -0
- TensorPlay/scheduler.py +184 -0
- TensorPlay/utils.py +162 -0
- tensorplay-0.1.1.dist-info/METADATA +182 -0
- tensorplay-0.1.1.dist-info/RECORD +16 -0
- tensorplay-0.1.1.dist-info/WHEEL +5 -0
- tensorplay-0.1.1.dist-info/licenses/LICENSE +21 -0
- tensorplay-0.1.1.dist-info/top_level.txt +1 -0
TensorPlay/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TensorPlay - 一个用于深度学习验证的工具包
|
|
3
|
+
|
|
4
|
+
版本: 0.1.1
|
|
5
|
+
作者: Welog
|
|
6
|
+
日期: 2025年9月3日
|
|
7
|
+
|
|
8
|
+
功能特点:
|
|
9
|
+
- 提供多阶自动微分处理能力
|
|
10
|
+
- 提供计算图可视化功能
|
|
11
|
+
- 支持多维度的模型组件管理
|
|
12
|
+
- 支持JSON格式保存和加载
|
|
13
|
+
- 支持模型结构打印
|
|
14
|
+
- 支持钩子调试
|
|
15
|
+
"""
|
|
16
|
+
__version__ = "0.1.1"
|
|
17
|
+
__author__ = "Welog"
|
|
18
|
+
__email__ = "2095774200@shu.edu.cn"
|
|
19
|
+
__description__ = "一个用于深度学习验证的工具包"
|
|
20
|
+
__url__ = "https://github.com/bluemoon-o2/TensorPlay"
|
|
21
|
+
__license__ = "MIT"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# =============================================================================
|
|
25
|
+
# 全局接口
|
|
26
|
+
# =============================================================================
|
|
27
|
+
from .core import (config, no_grad, to_data, Tensor, Layer, Operator, Optimizer)
|
|
28
|
+
from .layer import (Dense, BatchNorm, LayerNorm, Conv2D)
|
|
29
|
+
from .module import (Module, Sequential)
|
|
30
|
+
from .optimizer import (SGD, Adam, Momentum, AdamW, Nadam, Lookahead, RMSprop)
|
|
31
|
+
from .operator import (concatenate, load_operator)
|
|
32
|
+
from .func import (mse, sse, nll, cross_entropy, sphere)
|
|
33
|
+
from .initializer import (he_init, xavier_init, uniform_init, my_init)
|
|
34
|
+
from .utils import (plot_dot_graph, accuracy)
|
|
35
|
+
from .data import (DataLoader)
|
|
36
|
+
from .scheduler import (StepLR, MultiStepLR, ExponentialLR, EarlyStopping)
|
|
37
|
+
|
|
38
|
+
load_operator()
|
TensorPlay/core.py
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
import contextlib
|
|
3
|
+
import weakref
|
|
4
|
+
import numpy as np
|
|
5
|
+
import TensorPlay as tp
|
|
6
|
+
from typing import List, Union, Tuple, Optional, Any, Callable, Generator
|
|
7
|
+
warnings.filterwarnings("default", category=UserWarning)
|
|
8
|
+
|
|
9
|
+
# =============================================================================
|
|
10
|
+
# Config
|
|
11
|
+
# =============================================================================
|
|
12
|
+
class Config:
|
|
13
|
+
precision = np.float32
|
|
14
|
+
enable_grad = True
|
|
15
|
+
training = True
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@contextlib.contextmanager
|
|
19
|
+
def config(name: str, value: bool) -> Generator:
|
|
20
|
+
"""配置是否开启梯度计算的上下文管理器"""
|
|
21
|
+
prev_mode = getattr(Config, name)
|
|
22
|
+
setattr(Config, name, value)
|
|
23
|
+
try:
|
|
24
|
+
yield
|
|
25
|
+
finally:
|
|
26
|
+
setattr(Config, name, prev_mode)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def no_grad() -> contextlib._GeneratorContextManager:
|
|
30
|
+
"""上下文管理器,禁用梯度计算"""
|
|
31
|
+
return config("enable_grad", False)
|
|
32
|
+
|
|
33
|
+
# =============================================================================
|
|
34
|
+
# Tensor
|
|
35
|
+
# =============================================================================
|
|
36
|
+
def to_data(data: Union[np.ndarray, tuple, list, int, float]) -> np.ndarray:
|
|
37
|
+
"""将数据转换为张量要求格式"""
|
|
38
|
+
if isinstance(data, (tuple, list, int, float, Config.precision)):
|
|
39
|
+
return np.array(data, dtype=Config.precision)
|
|
40
|
+
if isinstance(data, np.ndarray):
|
|
41
|
+
return data.astype(Config.precision)
|
|
42
|
+
else:
|
|
43
|
+
raise TypeError(f"Data must be a numpy array, tuple, list, int, or float (not {type(data).__name__})")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Tensor:
|
|
47
|
+
"""张量:(B, H, W, C)"""
|
|
48
|
+
|
|
49
|
+
__add__: Callable[..., 'Tensor']
|
|
50
|
+
__radd__: Callable[..., 'Tensor']
|
|
51
|
+
__sub__: Callable[..., 'Tensor']
|
|
52
|
+
__rsub__: Callable[..., 'Tensor']
|
|
53
|
+
__mul__: Callable[..., 'Tensor']
|
|
54
|
+
__rmul__: Callable[..., 'Tensor']
|
|
55
|
+
__truediv__: Callable[..., 'Tensor']
|
|
56
|
+
__rtruediv__: Callable[..., 'Tensor']
|
|
57
|
+
__pow__: Callable[..., 'Tensor']
|
|
58
|
+
__rpow__: Callable[..., 'Tensor']
|
|
59
|
+
__matmul__: Callable[..., 'Tensor']
|
|
60
|
+
__getitem__: Callable[..., 'Tensor']
|
|
61
|
+
reslice: Callable[..., 'Tensor']
|
|
62
|
+
sum: Callable[..., 'Tensor']
|
|
63
|
+
max: Callable[..., 'Tensor']
|
|
64
|
+
min: Callable[..., 'Tensor']
|
|
65
|
+
exp: Callable[..., 'Tensor']
|
|
66
|
+
log: Callable[..., 'Tensor']
|
|
67
|
+
mean: Callable[..., 'Tensor']
|
|
68
|
+
relu: Callable[..., 'Tensor']
|
|
69
|
+
leaky_relu: Callable[..., 'Tensor']
|
|
70
|
+
gelu: Callable[..., 'Tensor']
|
|
71
|
+
tanh: Callable[..., 'Tensor']
|
|
72
|
+
softmax: Callable[..., 'Tensor']
|
|
73
|
+
log_softmax: Callable[..., 'Tensor']
|
|
74
|
+
sigmoid: Callable[..., 'Tensor']
|
|
75
|
+
reshape: Callable[..., 'Tensor']
|
|
76
|
+
clip: Callable[..., 'Tensor']
|
|
77
|
+
dropout: Callable[..., 'Tensor']
|
|
78
|
+
expand: Callable[..., 'Tensor']
|
|
79
|
+
flatten: Callable[..., 'Tensor']
|
|
80
|
+
transpose: Callable[..., 'Tensor']
|
|
81
|
+
broadcast: Callable[..., 'Tensor']
|
|
82
|
+
rebroadcast: Callable[..., 'Tensor']
|
|
83
|
+
|
|
84
|
+
T: 'Tensor'
|
|
85
|
+
|
|
86
|
+
def __init__(self, data: Union[np.ndarray, list, int, float], op: 'Operator' = None, name: str = None):
|
|
87
|
+
self.data = to_data(data)
|
|
88
|
+
self.grad = None
|
|
89
|
+
self.op = op
|
|
90
|
+
self.name = name
|
|
91
|
+
self.rank = 0
|
|
92
|
+
self.source_module = None # 用于钩子机制
|
|
93
|
+
|
|
94
|
+
def __repr__(self) -> str:
|
|
95
|
+
return f"Tensor({self.data})"
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def shape(self) -> Tuple[int, ...]:
|
|
99
|
+
return self.data.shape
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def ndim(self) -> int:
|
|
103
|
+
return self.data.ndim
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def size(self) -> int:
|
|
107
|
+
return self.data.size
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def dtype(self) -> np.dtype:
|
|
111
|
+
return self.data.dtype
|
|
112
|
+
|
|
113
|
+
def clone(self) -> 'Tensor':
|
|
114
|
+
"""返回当前张量的副本,确保梯度独立"""
|
|
115
|
+
cloned_tensor = Tensor(self.data, op=self.op)
|
|
116
|
+
if self.grad is not None:
|
|
117
|
+
cloned_tensor.grad = Tensor(self.grad.data.copy())
|
|
118
|
+
return cloned_tensor
|
|
119
|
+
|
|
120
|
+
def detach(self) -> 'Tensor':
|
|
121
|
+
"""返回一个不追踪梯度的张量副本"""
|
|
122
|
+
detached = Tensor(self.data)
|
|
123
|
+
return detached
|
|
124
|
+
|
|
125
|
+
def zero_grad(self) -> None:
|
|
126
|
+
"""清空梯度"""
|
|
127
|
+
self.grad = None
|
|
128
|
+
|
|
129
|
+
def one_grad(self) -> None:
|
|
130
|
+
"""将梯度设为1"""
|
|
131
|
+
self.grad = Tensor(np.ones(self.shape))
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def zeros(cls, shape: Union[int, Tuple[int, ...]]) -> 'Tensor':
|
|
135
|
+
"""创建一个指定形状的全0张量"""
|
|
136
|
+
return Tensor(np.zeros(shape))
|
|
137
|
+
|
|
138
|
+
def backward(self, clean: bool = True, retain_grad: bool = True, higher_grad: bool = False) -> None:
|
|
139
|
+
"""
|
|
140
|
+
计算子图的反向传播
|
|
141
|
+
:param clean: bool 是否清理计算图
|
|
142
|
+
:param retain_grad: bool 是否保留下游梯度
|
|
143
|
+
:param higher_grad: bool 是否支持多阶梯度
|
|
144
|
+
"""
|
|
145
|
+
Operator.state = False
|
|
146
|
+
self.one_grad()
|
|
147
|
+
# 使用集合避免重复处理同一运算符
|
|
148
|
+
op_set = set()
|
|
149
|
+
queue = []
|
|
150
|
+
# 从输出张量开始,收集所有相关运算符
|
|
151
|
+
if self.op is not None:
|
|
152
|
+
queue.append(self.op)
|
|
153
|
+
op_set.add(self.op)
|
|
154
|
+
# 广度优先搜索收集所有相关运算符
|
|
155
|
+
while queue:
|
|
156
|
+
current_op = queue.pop(0) # 取出队首元素
|
|
157
|
+
if current_op.inp is None:
|
|
158
|
+
continue
|
|
159
|
+
# 处理输入为列表或单个张量的情况
|
|
160
|
+
inputs = current_op.inp if isinstance(current_op.inp, list) else [current_op.inp]
|
|
161
|
+
for inp_tensor in inputs:
|
|
162
|
+
# 确保输入是张量且有运算符,排除起始张量
|
|
163
|
+
if isinstance(inp_tensor, Tensor) and inp_tensor.op is not None and inp_tensor.op not in op_set:
|
|
164
|
+
op_set.add(inp_tensor.op)
|
|
165
|
+
queue.append(inp_tensor.op)
|
|
166
|
+
# 按算符深度和计算顺序逆序处理
|
|
167
|
+
op_list = sorted(op_set, key=lambda x: (x.rank, Operator.compute_list.index(x)), reverse=True)
|
|
168
|
+
for op in op_list:
|
|
169
|
+
if op.inp is None:
|
|
170
|
+
continue
|
|
171
|
+
with config('enable_grad', higher_grad):
|
|
172
|
+
grads = op.propagate_grad()
|
|
173
|
+
for i, grad in enumerate(grads):
|
|
174
|
+
if op.inp[i].grad is None:
|
|
175
|
+
op.inp[i].grad = grad
|
|
176
|
+
else:
|
|
177
|
+
op.inp[i].grad = op.inp[i].grad + grad
|
|
178
|
+
if not retain_grad:
|
|
179
|
+
op.out().grad = None
|
|
180
|
+
Operator.state = True
|
|
181
|
+
if clean:
|
|
182
|
+
if higher_grad:
|
|
183
|
+
# 保留反向计算图
|
|
184
|
+
Operator.clean(specific_ops=op_list)
|
|
185
|
+
else:
|
|
186
|
+
Operator.clean()
|
|
187
|
+
|
|
188
|
+
# =============================================================================
|
|
189
|
+
# Operator
|
|
190
|
+
# =============================================================================
|
|
191
|
+
class Operator:
|
|
192
|
+
"""算子基类"""
|
|
193
|
+
compute_list: List['Operator'] = [] # 记录计算顺序
|
|
194
|
+
state: bool = True # 是否在前向状态
|
|
195
|
+
|
|
196
|
+
def __init__(self):
|
|
197
|
+
"""子类根据需要重写,没有额外参数不写"""
|
|
198
|
+
if Config.enable_grad:
|
|
199
|
+
self.compute_list.append(self)
|
|
200
|
+
self.inp = None # 输入张量
|
|
201
|
+
self.out = None # 输出张量
|
|
202
|
+
self.rank = 0
|
|
203
|
+
|
|
204
|
+
def __repr__(self) -> str:
|
|
205
|
+
return f"Operator.{self.__class__.__name__}"
|
|
206
|
+
|
|
207
|
+
def __call__(self, *args):
|
|
208
|
+
"""前向调用接口"""
|
|
209
|
+
inputs = [inp if isinstance(inp, (Tensor, type(None))) else Tensor(inp) for inp in args]
|
|
210
|
+
datas = [inp.data if inp is not None else None for inp in inputs]
|
|
211
|
+
out = self._forward(*datas)
|
|
212
|
+
if Config.enable_grad:
|
|
213
|
+
self.inp = inputs
|
|
214
|
+
self.out = weakref.ref(out)
|
|
215
|
+
self.rank = max([inp.rank for inp in inputs])
|
|
216
|
+
out.op = self
|
|
217
|
+
out.rank = self.rank + 1
|
|
218
|
+
return out
|
|
219
|
+
|
|
220
|
+
def propagate_grad(self) -> Union[Tensor, List[Tensor]]:
|
|
221
|
+
"""后向调用接口,集成反向钩子调用"""
|
|
222
|
+
if not Config.enable_grad and self.state:
|
|
223
|
+
warnings.warn('Attention: forward() run with no grad...\n'
|
|
224
|
+
'If you are not computing higher-gradients, '
|
|
225
|
+
'please examine your code.', UserWarning, stacklevel=2)
|
|
226
|
+
g = self._backward()
|
|
227
|
+
# 调用反向钩子
|
|
228
|
+
if self.out().source_module is not None:
|
|
229
|
+
module = self.out().source_module
|
|
230
|
+
module._call_backward_hooks(self.out(), self.inp)
|
|
231
|
+
return g
|
|
232
|
+
|
|
233
|
+
def _forward(self, *args: Any) -> Tensor:
|
|
234
|
+
"""前向具体运算"""
|
|
235
|
+
raise NotImplementedError
|
|
236
|
+
|
|
237
|
+
def _backward(self) -> Any:
|
|
238
|
+
"""后向具体计算"""
|
|
239
|
+
raise NotImplementedError
|
|
240
|
+
|
|
241
|
+
@classmethod
|
|
242
|
+
def clean(cls, specific_ops: Optional[List['Operator']] = None) -> None:
|
|
243
|
+
"""清理计算图数据"""
|
|
244
|
+
if specific_ops is not None:
|
|
245
|
+
while specific_ops:
|
|
246
|
+
ops = specific_ops.pop()
|
|
247
|
+
ops.out().op = None
|
|
248
|
+
cls.compute_list.remove(ops)
|
|
249
|
+
else:
|
|
250
|
+
while cls.compute_list:
|
|
251
|
+
ops = cls.compute_list.pop()
|
|
252
|
+
if ops.out() is not None:
|
|
253
|
+
ops.out().op = None
|
|
254
|
+
cls.compute_list.clear()
|
|
255
|
+
|
|
256
|
+
# =============================================================================
|
|
257
|
+
# Layer
|
|
258
|
+
# =============================================================================
|
|
259
|
+
class Layer:
|
|
260
|
+
"""
|
|
261
|
+
基础参数层,实现钩子功能,所有参数层都需要继承此类
|
|
262
|
+
save和load方法自定义格式,必须互认
|
|
263
|
+
"""
|
|
264
|
+
layer_list: List['Layer'] = [] # 基础参数层全局记录,兼容最底层实现
|
|
265
|
+
|
|
266
|
+
def __init__(self, *args):
|
|
267
|
+
self._forward_pre_hooks = {}
|
|
268
|
+
self._forward_hooks = {}
|
|
269
|
+
self._backward_hooks = {}
|
|
270
|
+
# 基础参数层只记录Layer类,Module以上不记录
|
|
271
|
+
if isinstance(self, tp.Module):
|
|
272
|
+
return
|
|
273
|
+
Layer.layer_list.append(self)
|
|
274
|
+
|
|
275
|
+
def __repr__(self) -> str:
|
|
276
|
+
prefix = '' if self.__class__.__name__ == 'Layer' else 'Layer.'
|
|
277
|
+
return f"{prefix}{self.__class__.__name__}"
|
|
278
|
+
|
|
279
|
+
def save(self, *args) -> str:
|
|
280
|
+
"""
|
|
281
|
+
保存接口,所有继承了Layer的类需重写此方法
|
|
282
|
+
:return: 自定义格式,与load方法互认
|
|
283
|
+
"""
|
|
284
|
+
raise NotImplementedError
|
|
285
|
+
|
|
286
|
+
def load(self, *args) -> None:
|
|
287
|
+
"""
|
|
288
|
+
读取接口,所有继承了Layer的类需重写此方法
|
|
289
|
+
:param args: str 自定义格式,与save方法互认
|
|
290
|
+
"""
|
|
291
|
+
raise NotImplementedError
|
|
292
|
+
|
|
293
|
+
def param(self) -> List[Tensor]:
|
|
294
|
+
"""
|
|
295
|
+
参数接口,所有继承了Layer的类需重写此方法
|
|
296
|
+
:return: list[Tensor]
|
|
297
|
+
"""
|
|
298
|
+
raise NotImplementedError
|
|
299
|
+
|
|
300
|
+
@classmethod
|
|
301
|
+
def get_params(cls) -> List[Tensor]:
|
|
302
|
+
"""
|
|
303
|
+
返回所有基础参数层参数,兼容优化器的默认设置
|
|
304
|
+
:return: list[Tensor]
|
|
305
|
+
"""
|
|
306
|
+
params = []
|
|
307
|
+
for i in Layer.layer_list:
|
|
308
|
+
# Param返回列表
|
|
309
|
+
if i.param() is not None:
|
|
310
|
+
params += i.param()
|
|
311
|
+
return params
|
|
312
|
+
|
|
313
|
+
def register_forward_pre_hook(self, hook: Callable) -> int:
|
|
314
|
+
"""注册前向传播前的钩子"""
|
|
315
|
+
handle = id(hook)
|
|
316
|
+
self._forward_pre_hooks[handle] = hook
|
|
317
|
+
return handle
|
|
318
|
+
|
|
319
|
+
def register_forward_hook(self, hook: Callable) -> int:
|
|
320
|
+
"""注册前向传播后的钩子"""
|
|
321
|
+
handle = id(hook)
|
|
322
|
+
self._forward_hooks[handle] = hook
|
|
323
|
+
return handle
|
|
324
|
+
|
|
325
|
+
def register_backward_hook(self, hook: Callable) -> int:
|
|
326
|
+
"""注册反向传播的钩子"""
|
|
327
|
+
handle = id(hook)
|
|
328
|
+
self._backward_hooks[handle] = hook
|
|
329
|
+
return handle
|
|
330
|
+
|
|
331
|
+
def remove_hook(self, handle: int) -> None:
|
|
332
|
+
"""移除指定钩子"""
|
|
333
|
+
for hooks in [self._forward_pre_hooks, self._forward_hooks, self._backward_hooks]:
|
|
334
|
+
if handle in hooks:
|
|
335
|
+
del hooks[handle]
|
|
336
|
+
return
|
|
337
|
+
|
|
338
|
+
def _call_forward_pre_hooks(self, *args: Tensor, **kwargs) -> None:
|
|
339
|
+
"""调用前向传播前的钩子"""
|
|
340
|
+
for hook in self._forward_pre_hooks.values():
|
|
341
|
+
hook(self, args, kwargs)
|
|
342
|
+
|
|
343
|
+
def _call_forward_hooks(self, *args: Tensor, **kwargs) -> None:
|
|
344
|
+
"""调用前向传播后的钩子"""
|
|
345
|
+
for hook in self._forward_hooks.values():
|
|
346
|
+
hook(self, args, kwargs, self._forward_result)
|
|
347
|
+
|
|
348
|
+
def _call_backward_hooks(self, grad_outputs: Tensor, inputs: Tensor) -> None:
|
|
349
|
+
"""调用反向传播的钩子"""
|
|
350
|
+
for hook in self._backward_hooks.values():
|
|
351
|
+
if isinstance(inputs, Tensor):
|
|
352
|
+
hook(self, grad_outputs, inputs)
|
|
353
|
+
elif isinstance(inputs, list):
|
|
354
|
+
for item in inputs:
|
|
355
|
+
if isinstance(item, Tensor):
|
|
356
|
+
hook(self, grad_outputs, [item for item in inputs])
|
|
357
|
+
else:
|
|
358
|
+
raise TypeError(f"input must be a Tensor or list of Tensors, got {type(inputs).__name__}")
|
|
359
|
+
|
|
360
|
+
def __call__(self, *args: Tensor, **kwargs) -> Tensor:
|
|
361
|
+
"""调用方法,集成钩子和张量-模块关联"""
|
|
362
|
+
self._call_forward_pre_hooks(*args, **kwargs)
|
|
363
|
+
self._forward_result = self.forward(*args, **kwargs)
|
|
364
|
+
# 记录输出张量的来源模块(用于反向传播时触发钩子)
|
|
365
|
+
if self._backward_hooks:
|
|
366
|
+
if isinstance(self._forward_result, Tensor):
|
|
367
|
+
self._forward_result._source_module = self
|
|
368
|
+
elif isinstance(self._forward_result, list):
|
|
369
|
+
for item in self._forward_result:
|
|
370
|
+
if isinstance(item, Tensor):
|
|
371
|
+
item._source_module = self
|
|
372
|
+
else:
|
|
373
|
+
raise TypeError(f"forward_result must be a Tensor or list, got {type(self._forward_result).__name__}")
|
|
374
|
+
self._call_forward_hooks(*args, **kwargs)
|
|
375
|
+
return self._forward_result
|
|
376
|
+
|
|
377
|
+
def forward(self, *args: Tensor, **kwargs) -> Tensor:
|
|
378
|
+
"""前向传播方法,需要子类实现"""
|
|
379
|
+
raise NotImplementedError(f"Module {self.__class__.__name__} has no forward method implemented")
|
|
380
|
+
|
|
381
|
+
# =============================================================================
|
|
382
|
+
# Optimizer
|
|
383
|
+
# =============================================================================
|
|
384
|
+
class Optimizer:
|
|
385
|
+
"""优化器类,储存参数需要重写接口函数save()和load()"""
|
|
386
|
+
hooks = [] # 用于存储钩子函数的列表
|
|
387
|
+
|
|
388
|
+
def __init__(self, params=None):
|
|
389
|
+
"""
|
|
390
|
+
:param params:list[Tensor,Tensor...] 需要优化的参数的列表,为None时优化所有Layer中的参数
|
|
391
|
+
"""
|
|
392
|
+
if params is None:
|
|
393
|
+
self.params = Layer.get_params()
|
|
394
|
+
else:
|
|
395
|
+
self.params = params
|
|
396
|
+
|
|
397
|
+
def step(self):
|
|
398
|
+
"""具体优化方法,必须重写"""
|
|
399
|
+
for hook in self.hooks:
|
|
400
|
+
self.params = hook(self.params)
|
|
401
|
+
self._step()
|
|
402
|
+
|
|
403
|
+
def _step(self):
|
|
404
|
+
"""具体优化方法,必须重写"""
|
|
405
|
+
raise NotImplementedError
|
|
406
|
+
|
|
407
|
+
def zero_grad(self):
|
|
408
|
+
"""使参数的梯度归零"""
|
|
409
|
+
for i in self.params:
|
|
410
|
+
i.zero_grad()
|
|
411
|
+
|
|
412
|
+
def register_hook(self, hook: Callable) -> int:
|
|
413
|
+
"""注册钩子函数"""
|
|
414
|
+
self.hooks.append(hook)
|
|
415
|
+
return len(self.hooks) - 1
|
|
416
|
+
|
|
417
|
+
def remove_hook(self, handle: int):
|
|
418
|
+
"""移除指定的钩子函数"""
|
|
419
|
+
if handle in self.hooks:
|
|
420
|
+
del self.hooks[handle]
|
TensorPlay/data.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from .core import Tensor
|
|
2
|
+
from .operator import concatenate
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
# =============================================================================
|
|
6
|
+
# Datasets
|
|
7
|
+
# =============================================================================
|
|
8
|
+
def load_iris():
|
|
9
|
+
"""
|
|
10
|
+
加载鸢尾花数据集
|
|
11
|
+
================= ==============
|
|
12
|
+
Classes 3
|
|
13
|
+
Samples per class 50
|
|
14
|
+
Samples total 150
|
|
15
|
+
Dimensionality 4
|
|
16
|
+
Features real, positive
|
|
17
|
+
================= ==============
|
|
18
|
+
"""
|
|
19
|
+
from sklearn.datasets import load_iris
|
|
20
|
+
return load_iris()
|
|
21
|
+
|
|
22
|
+
# =============================================================================
|
|
23
|
+
# DataLoader
|
|
24
|
+
# =============================================================================
|
|
25
|
+
class DataLoader:
|
|
26
|
+
"""数据加载器,支持批处理、打乱和自定义转换"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, data, batch_size: int = 64, shuffle: bool = True, transform=None):
|
|
29
|
+
"""
|
|
30
|
+
:param data: 数据集,格式为[(输入特征, 标签), ...]
|
|
31
|
+
:param batch_size: 批次大小
|
|
32
|
+
:param shuffle: 是否打乱数据集
|
|
33
|
+
:param transform: 数据转换函数,格式为func(input, label) -> (transformed_input, transformed_label)
|
|
34
|
+
"""
|
|
35
|
+
self.data = data
|
|
36
|
+
self.batch_size = batch_size
|
|
37
|
+
self.shuffle = shuffle
|
|
38
|
+
self.transform = transform
|
|
39
|
+
self.indices = list(range(len(data)))
|
|
40
|
+
self.cursor = 0 # 当前批次指针
|
|
41
|
+
if shuffle:
|
|
42
|
+
np.random.shuffle(self.indices)
|
|
43
|
+
|
|
44
|
+
def __iter__(self):
|
|
45
|
+
"""迭代器初始化"""
|
|
46
|
+
self.cursor = 0
|
|
47
|
+
if self.shuffle:
|
|
48
|
+
np.random.shuffle(self.indices)
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def __next__(self):
|
|
52
|
+
"""获取下一个批次"""
|
|
53
|
+
if self.cursor >= len(self.data):
|
|
54
|
+
raise StopIteration
|
|
55
|
+
# 计算当前批次索引范围
|
|
56
|
+
end = min(self.cursor + self.batch_size, len(self.data))
|
|
57
|
+
batch_indices = self.indices[self.cursor:end]
|
|
58
|
+
self.cursor = end
|
|
59
|
+
|
|
60
|
+
batch_inputs = []
|
|
61
|
+
batch_labels = []
|
|
62
|
+
for idx in batch_indices:
|
|
63
|
+
x, y = self.data[idx]
|
|
64
|
+
if self.transform:
|
|
65
|
+
x, y = self.transform(x, y)
|
|
66
|
+
batch_inputs.append(Tensor([x]))
|
|
67
|
+
batch_labels.append(Tensor([y]))
|
|
68
|
+
return concatenate(*batch_inputs, axis=0), concatenate(*batch_labels, axis=0)
|
|
69
|
+
|
|
70
|
+
def __len__(self):
|
|
71
|
+
"""返回批次数量"""
|
|
72
|
+
return (len(self.data) + self.batch_size - 1) // self.batch_size
|
TensorPlay/func.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from .core import Tensor
|
|
2
|
+
from .operator import MeanSquaredError, CrossEntropy
|
|
3
|
+
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# 损失函数
|
|
6
|
+
# =============================================================================
|
|
7
|
+
def mse(out: Tensor, target: Tensor) -> Tensor:
|
|
8
|
+
"""均方误差(Mean Squared Error):MSE = (1/n) * sum((a - b)²)"""
|
|
9
|
+
return MeanSquaredError()(out, target)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def cross_entropy(out: Tensor, target: Tensor, axis: int = 1, activation: str = 'softmax') -> Tensor:
|
|
13
|
+
"""交叉熵损失函数"""
|
|
14
|
+
return CrossEntropy(axis=axis, activation=activation)(out, target)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def sse(out: Tensor, target: Tensor) -> Tensor:
|
|
18
|
+
"""平方误差(Sum of Squared Error):SSE = sum((a - b)²)"""
|
|
19
|
+
if out.shape != target.shape:
|
|
20
|
+
raise ValueError("SSE can only be calculated between tensors of the same shape")
|
|
21
|
+
return ((out - target) ** 2).sum()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def nll(out: Tensor, target: Tensor) -> Tensor:
|
|
25
|
+
"""交叉熵误差(Negative Log Likelihood):NLL = -sum(target * log(output))"""
|
|
26
|
+
if out.shape != target.shape:
|
|
27
|
+
raise ValueError("NLL can only be calculated between tensors of the same shape")
|
|
28
|
+
return -(target * out.log()).sum()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# =============================================================================
|
|
33
|
+
# 优化函数
|
|
34
|
+
# =============================================================================
|
|
35
|
+
def sphere(x, y):
|
|
36
|
+
return x ** 2 + y ** 2
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def matyas(x, y):
|
|
40
|
+
return 0.26 * (x ** 2 + y ** 2) - 0.48 * x * y
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def goldstein(x, y):
|
|
44
|
+
return (1 + (x + y + 1) ** 2 * (19 - 14 * x + 3 * x ** 2 - 14 * y + 6 * x * y + 3 * y ** 2)) * (
|
|
45
|
+
30 + (2 * x - 3 * y) ** 2 * (18 - 32 * x + 12 * x ** 2 + 48 * y - 36 * x * y + 27 * y ** 2))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def higher_optimizer(x, epoch, func, verbose=False):
|
|
49
|
+
"""
|
|
50
|
+
基于牛顿法的二阶单变量函数优化器
|
|
51
|
+
:param x: 初始值
|
|
52
|
+
:param epoch: 迭代次数
|
|
53
|
+
:param func: 目标函数
|
|
54
|
+
:param verbose: 是否打印每次迭代的结果
|
|
55
|
+
:return: 优化后的变量和函数值
|
|
56
|
+
"""
|
|
57
|
+
for i in range(epoch):
|
|
58
|
+
y = func(x)
|
|
59
|
+
y.name = 'y'
|
|
60
|
+
if verbose:
|
|
61
|
+
print(f"第{i + 1}次迭代: x={x.data}, y={y.data}")
|
|
62
|
+
y.backward(higher_grad=True)
|
|
63
|
+
gx = x.grad
|
|
64
|
+
gx.name = 'gx'
|
|
65
|
+
x.zero_grad()
|
|
66
|
+
gx.backward()
|
|
67
|
+
gx2 = x.grad
|
|
68
|
+
x.zero_grad()
|
|
69
|
+
x.data -= gx.data / gx2.data
|
|
70
|
+
return x, y
|
|
71
|
+
|
|
72
|
+
# =============================================================================
|
|
73
|
+
# 测试函数
|
|
74
|
+
# =============================================================================
|
|
75
|
+
def deriv(func, x: Tensor, eps=1e-4):
|
|
76
|
+
"""函数的数值微分计算,测试用"""
|
|
77
|
+
x1 = Tensor(x.data.data + eps)
|
|
78
|
+
x2 = Tensor(x.data.data - eps)
|
|
79
|
+
y1 = func(x1)
|
|
80
|
+
y2 = func(x2)
|
|
81
|
+
return (y1.data.data - y2.data.data) / (2 * eps)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
from .core import Tensor, Config
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
def my_init(size) -> Tensor:
|
|
6
|
+
"""对单个张量初始化权重"""
|
|
7
|
+
std = np.sqrt(2.0 / size)
|
|
8
|
+
return Tensor(np.random.normal(0, std, size))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def xavier_init(inp_size: int, out_size: int) -> Tensor:
|
|
12
|
+
"""Xavier初始化 - 适用于tanh/sigmoid等激活函数"""
|
|
13
|
+
std = np.sqrt(2.0 / (inp_size + out_size))
|
|
14
|
+
return Tensor(np.random.normal(0, std, (out_size, inp_size)))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def he_init(shape: Tuple[int, ...]) -> Tensor:
|
|
18
|
+
"""He初始化 - 适用于ReLU及其变体激活函数"""
|
|
19
|
+
fan_in = np.prod(shape[:-1])
|
|
20
|
+
std = np.sqrt(2.0 / fan_in)
|
|
21
|
+
return Tensor(np.random.uniform(-std * np.sqrt(3), std * np.sqrt(3), shape).astype(Config.precision))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def uniform_init(size, a=-0.05, b=0.05) -> Tensor:
|
|
25
|
+
"""均匀分布初始化 - 适用于线性层"""
|
|
26
|
+
return Tensor(np.random.uniform(a, b, size))
|