mldeeptool 1.0.0__tar.gz

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.
@@ -0,0 +1 @@
1
+ recursive-include mldeeptool *
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: mldeeptool
3
+ Version: 1.0.0
4
+ Summary: 神经网络相关工具库
5
+ Home-page: https://www.python.org
6
+ Author: mengling
7
+ Author-email: 1321443305@qq.com
8
+ Requires-Python: >=3.8
9
+ Requires-Dist: numpy
10
+ Requires-Dist: torch
11
+ Dynamic: author
12
+ Dynamic: author-email
13
+ Dynamic: home-page
14
+ Dynamic: requires-dist
15
+ Dynamic: requires-python
16
+ Dynamic: summary
@@ -0,0 +1,176 @@
1
+ from typing import Literal
2
+ import numpy as np
3
+ import torch
4
+ from torch import Tensor, nn, tensor, float32, bfloat16, float16
5
+ from torch.utils.data import DataLoader, TensorDataset
6
+
7
+
8
+ class RegressionTool:
9
+ """回归模型工具
10
+ bfloat16容易梯度消失
11
+ """
12
+ def __init__(self, model_name, model: nn.Module,
13
+ device:Literal['cpu', 'cuda']='cpu',
14
+ dtype:Literal['float32', 'bfloat16', 'float16']='float32',
15
+ loss_fn=None):
16
+ self.model_name = model_name
17
+ self.device=device
18
+ match dtype:
19
+ case 'float32':
20
+ self.dtype = float32
21
+ case 'bfloat16':
22
+ self.dtype = bfloat16
23
+ case 'float16':
24
+ self.dtype = float16
25
+ self.model = model.to(dtype=self.dtype, device=self.device)
26
+ self.loss_fn = loss_fn or nn.MSELoss()
27
+
28
+ def save(self, filepath):
29
+ torch.save(self.model.state_dict(), filepath)
30
+
31
+ def load(self, filepath):
32
+ self.model.load_state_dict(torch.load(filepath, map_location=self.device))
33
+
34
+ def get_verify_loss(self, X:Tensor, Y:Tensor)-> float:
35
+ pY:Tensor = self.predict(X, False)
36
+ loss = self.loss_fn(pY, Y)
37
+ return loss.item()
38
+
39
+ def to_tensor(self, X:np.ndarray|list|Tensor):
40
+ if isinstance(X, Tensor):
41
+ return X.to(dtype=self.dtype, device=self.device)
42
+ else:
43
+ return tensor(X, dtype=self.dtype, device=self.device)
44
+
45
+ def epoch_event(self, epoch:int, loss:float, verify_loss:float, current_lr:float):
46
+ """每批次结束额外调用的事件函数
47
+ """
48
+ pass
49
+
50
+ def train(self, X, Y, verify_X, verify_Y, path:str, verify_num:int=50, loss_print=True,
51
+ learn_step=1e-2, train_num:int=500, max_norm:float=1.0,
52
+ batch_size: int=100_000, num_workers:int=4):
53
+ # 如果 X 是 NumPy 数组,as_tensor方法会尽量共享内存(不拷贝)
54
+ # 更高效,适合大批量数据
55
+ X, Y = tensor(X, dtype=self.dtype, device='cpu'), tensor(Y, dtype=self.dtype, device='cpu')
56
+ verify_X, verify_Y = self.to_tensor(verify_X), self.to_tensor(verify_Y)
57
+ dataloader = DataLoader(TensorDataset(X, Y), batch_size=batch_size,
58
+ shuffle=True, # 每个 epoch 都打乱顺序
59
+ num_workers=num_workers,# 多进程加载, 数据已在GPU中使用会报错
60
+ pin_memory=True) # 锁页内存, 加速 CPU->GPU 传输, 但是初始数据已经在gpu上开启报错
61
+ # 记录验证正确率和对应轮数
62
+ best_verify_loss = 1e+12
63
+ min_loss, loss_best_index = 1e+12, 0
64
+ # 进入训练状态
65
+ self.model.train()
66
+ optimizer = torch.optim.AdamW(self.model.parameters(), lr=learn_step)
67
+ scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min', patience=10)
68
+ total_samples = X.shape[0]
69
+
70
+ for epoch in range(train_num):
71
+ total_loss = 0.0
72
+ for X_batch, Y_batch in dataloader:
73
+ X_batch, Y_batch = X_batch.to(self.device), Y_batch.to(self.device)
74
+ Y0 = self.model(X_batch)
75
+ # 更新参数
76
+ loss = self.loss_fn(Y0, Y_batch)
77
+ optimizer.zero_grad()
78
+ loss.backward()
79
+ # 防止梯度爆炸
80
+ nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=max_norm)
81
+ optimizer.step()
82
+ total_loss+=loss.item()* X_batch.shape[0]
83
+ loss_value = total_loss/total_samples
84
+ scheduler.step(loss_value)
85
+
86
+ if min_loss<=loss_value:
87
+ loss_best_index+=1
88
+ else:
89
+ loss_best_index=0
90
+ min_loss = min(loss_value, min_loss)
91
+ # 收敛提前跳出
92
+ if epoch>100 and loss_best_index>50:
93
+ print(f'模型已收敛, 提前结束训练 min_loss: {min_loss}')
94
+ break
95
+ current_lr = optimizer.param_groups[0]["lr"]
96
+ if loss_print and epoch % 10 == 0: print(f'loss-{epoch}: {loss_value} current_lr: {current_lr}')
97
+ if epoch % verify_num==0:
98
+ verify_loss = self.get_verify_loss(verify_X, verify_Y)
99
+ print(f'verify_loss-{epoch}: {verify_loss}')
100
+ best_verify_loss = min(verify_loss, best_verify_loss)
101
+ if best_verify_loss==verify_loss: self.save(f'{path}/{self.model_name}_best.pth')
102
+ self.model.train()
103
+ # 调用额外事件
104
+ self.epoch_event(epoch, loss_value, verify_loss, current_lr)
105
+
106
+ self.save(f'{path}/{self.model_name}_last.pth')
107
+ verify_loss = self.get_verify_loss(verify_X, verify_Y)
108
+ print(f'verify_loss-last: {verify_loss}')
109
+ best_verify_loss = min(verify_loss, best_verify_loss)
110
+ if best_verify_loss==verify_loss: self.save(f'{path}/{self.model_name}_best.pth')
111
+
112
+ def predict(self, X, return_index=True)->Tensor|list:
113
+ X = self.to_tensor(X)
114
+ # 进入验证状态
115
+ self.model.eval()
116
+ # 仅验证不更新模型
117
+ with torch.no_grad():
118
+ # 转换为Tensor
119
+ Y:Tensor = self.model(X)
120
+ if return_index:
121
+ return Y.argmax(dim=1).tolist()
122
+ else:
123
+ return Y
124
+
125
+ def print_parameters(self):
126
+ """打印梯度数据
127
+ """
128
+ for name, param in self.model.named_parameters():
129
+ if param.grad is not None:
130
+ print(f"{name} grad mean: {param.grad.abs().mean().item()}")
131
+
132
+ class ClassificationTool(RegressionTool):
133
+ """分类模型工具
134
+ 最后的激活函数不需要添加softmax层
135
+ CrossEntropyLoss损失函数input会被softmax处理
136
+ 训练入参-Y为概率分布
137
+ """
138
+ def __init__(self, model_name, model: nn.Module, device:Literal['cpu', 'cuda']='cpu', dtype:Literal['float32', 'bfloat16']='float32', loss_fn=None):
139
+ super().__init__(model_name, model, device, dtype, loss_fn=loss_fn or nn.CrossEntropyLoss())
140
+ # assert isinstance(self.model[-1], nn.Linear), '分类模型最后一层只能为nn.Linear, 不用加激活层'
141
+
142
+ def get_verify_loss(self, X:Tensor, Y:Tensor)-> float:
143
+ pY:Tensor = super().predict(X, False)
144
+ loss = self.loss_fn(pY, Y)
145
+ return loss.item()
146
+
147
+ def predict(self, X, return_index=True)->Tensor|list:
148
+ Y = super().predict(X,return_index=return_index)
149
+ if return_index:
150
+ return Y
151
+ else:
152
+ return Y.softmax(dim=1, dtype=self.dtype)
153
+
154
+ def accuracy(self, X, Y, is_absolute=True)-> float:
155
+ """计算正确率
156
+ (默认)is_absolute=True: 最大概率序列与Y值校验, 仅适用于单分类情况
157
+ is_absolute=False: loss的值(v)=-log(p) ==> p=e**-v ; p为正确率
158
+ """
159
+ X, Y = self.to_tensor(X), self.to_tensor(Y)
160
+ if is_absolute:
161
+ pys = self.predict(X)
162
+ ys = Y.argmax(dim=1).tolist()
163
+ return len([1 for y,py in zip(ys,pys) if y==py])/len(ys)
164
+ else:
165
+ lossv = self.get_verify_loss(X, Y)
166
+ return np.e**-lossv
167
+
168
+ def y_to_index(self, Y:np.ndarray|Tensor)->np.ndarray[int]:
169
+ Y:np.ndarray = Y.numpy() if isinstance(Y, Tensor) else Y
170
+ return Y.argmax(Y, axis=1)
171
+
172
+ def index_to_arr(out_dim, indexs:list[int])->np.ndarray[int]:
173
+ arr = np.zeros((len(indexs), out_dim))
174
+ for index, row in zip(indexs, arr):
175
+ row[index] = 1
176
+ return arr
@@ -0,0 +1,14 @@
1
+ try:
2
+ from sklearn.model_selection import train_test_split
3
+ import numpy as np
4
+ except ModuleNotFoundError:
5
+ raise ModuleNotFoundError('pip install numpy scikit-learn')
6
+
7
+
8
+ def get_train_test_sample(X:np.ndarray, Y:np.ndarray, test_rate: float=0.2, is_stratify=False, **sklearn_kwargs)-> tuple[np.ndarray,np.ndarray,np.ndarray,np.ndarray]:
9
+ """X_train, X_test, y_train, y_test"""
10
+ return train_test_split(X, Y,
11
+ test_size=test_rate,# 测试集占比
12
+ stratify=Y if is_stratify else None, # 分层采样保持类别比例, 仅适用于分类数据集
13
+ **sklearn_kwargs
14
+ )
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: mldeeptool
3
+ Version: 1.0.0
4
+ Summary: 神经网络相关工具库
5
+ Home-page: https://www.python.org
6
+ Author: mengling
7
+ Author-email: 1321443305@qq.com
8
+ Requires-Python: >=3.8
9
+ Requires-Dist: numpy
10
+ Requires-Dist: torch
11
+ Dynamic: author
12
+ Dynamic: author-email
13
+ Dynamic: home-page
14
+ Dynamic: requires-dist
15
+ Dynamic: requires-python
16
+ Dynamic: summary
@@ -0,0 +1,9 @@
1
+ MANIFEST.in
2
+ setup.py
3
+ mldeeptool/__init__.py
4
+ mldeeptool/public.py
5
+ mldeeptool.egg-info/PKG-INFO
6
+ mldeeptool.egg-info/SOURCES.txt
7
+ mldeeptool.egg-info/dependency_links.txt
8
+ mldeeptool.egg-info/requires.txt
9
+ mldeeptool.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ numpy
2
+ torch
@@ -0,0 +1 @@
1
+ mldeeptool
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,24 @@
1
+ import setuptools
2
+ import json
3
+
4
+ # 参考:https://www.jb51.net/article/202841.htm
5
+ # 打包需将此文件和MANIFEST.in文件置于mengling_tool包同目录
6
+ # 包中必须有__init__.py文件存在才能在pip时正常导入
7
+ # pip install --upgrade setuptools wheel -i https://pypi.douban.com/simple
8
+ # python setup.py sdist bdist_wheel
9
+ # pip install twine
10
+ # twine upload dist/*
11
+ '''
12
+ python setup.py sdist bdist_wheel
13
+ twine upload -u user -p password dist/*
14
+ '''
15
+
16
+ name = 'mldeeptool'
17
+
18
+ with open('../config.json', encoding='utf-8') as file:
19
+ definfo, opmap = json.loads(file.read())
20
+ setuptools.setup(
21
+ name=name,
22
+ packages=setuptools.find_packages(),
23
+ **{**definfo, **opmap[name]}
24
+ )