ai 1.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.
- ai/__init__.py +1 -0
- ai/config.py +122 -0
- ai/data/__init__.py +0 -0
- ai/data/augment.py +450 -0
- ai/llm/__init__.py +0 -0
- ai/llm/postprocess.py +125 -0
- ai/optim/__init__.py +472 -0
- ai/tensor/__init__.py +25 -0
- ai/trainer/__init__.py +18 -0
- ai/utils/__init__.py +26 -0
- ai-1.1.1.dist-info/METADATA +37 -0
- ai-1.1.1.dist-info/RECORD +15 -0
- ai-1.1.1.dist-info/WHEEL +5 -0
- ai-1.1.1.dist-info/top_level.txt +2 -0
- tests/__init__.py +3 -0
ai/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.1.1'
|
ai/config.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import yaml
|
|
3
|
+
from configparser import ConfigParser
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AttrDict(dict):
|
|
7
|
+
def __init__(self, *args, **kwargs):
|
|
8
|
+
super(AttrDict, self).__init__(*args, **kwargs)
|
|
9
|
+
self.__dict__ = self
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_yaml_config(filepath):
|
|
13
|
+
"""使用 .yaml 文件配置
|
|
14
|
+
"""
|
|
15
|
+
return AttrDict(yaml.load(open(filepath, 'r'), Loader=yaml.FullLoader))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_conf_config(filepath):
|
|
19
|
+
"""使用 .conf 文件配置
|
|
20
|
+
"""
|
|
21
|
+
conf = ConfigParser()
|
|
22
|
+
conf.read(filepath, encoding="UTF-8")
|
|
23
|
+
return conf
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Dict(dict):
|
|
27
|
+
"""重写 dict, 支持 “.” 方式的属性调用
|
|
28
|
+
"""
|
|
29
|
+
def __init__(self, *args, **kwargs):
|
|
30
|
+
super(Dict, self).__init__(*args, **kwargs)
|
|
31
|
+
|
|
32
|
+
def __getattr__(self, key):
|
|
33
|
+
try:
|
|
34
|
+
value = self[key]
|
|
35
|
+
if isinstance(value, dict):
|
|
36
|
+
value = Dict(value)
|
|
37
|
+
return value
|
|
38
|
+
except KeyError as k:
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
def __setattr__(self, key, value):
|
|
42
|
+
if isinstance(value, dict):
|
|
43
|
+
value = Dict(value)
|
|
44
|
+
self[key] = value
|
|
45
|
+
|
|
46
|
+
def __delattr__(self, key):
|
|
47
|
+
try:
|
|
48
|
+
del self[key]
|
|
49
|
+
except KeyError as k:
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
def __call__(self, key):
|
|
53
|
+
try:
|
|
54
|
+
return self[key]
|
|
55
|
+
except KeyError as k:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Config(object):
|
|
60
|
+
"""支持链式调用的 Config
|
|
61
|
+
"""
|
|
62
|
+
def __init__(self, filepath=None):
|
|
63
|
+
if filepath:
|
|
64
|
+
self.filepath = filepath
|
|
65
|
+
else:
|
|
66
|
+
cur_dir = os.path.split(os.path.realpath(__file__))[0]
|
|
67
|
+
self.filepath = os.path.join(cur_dir, "self.conf")
|
|
68
|
+
self.config = get_conf_config(self.filepath)
|
|
69
|
+
self.d = Dict()
|
|
70
|
+
for s in self.config.sections():
|
|
71
|
+
value = Dict()
|
|
72
|
+
for k in self.config.options(s):
|
|
73
|
+
value[k] = self.config.get(s, k)
|
|
74
|
+
self.d[s] = value
|
|
75
|
+
|
|
76
|
+
def add(self, section):
|
|
77
|
+
self.config.add_section(section)
|
|
78
|
+
self.d[section] = Dict()
|
|
79
|
+
with open(self.filepath, 'w', encoding="UTF-8") as f:
|
|
80
|
+
self.config.write(f)
|
|
81
|
+
|
|
82
|
+
def set(self, section, key, value):
|
|
83
|
+
self.config.set(section, key, value)
|
|
84
|
+
self.d[section][key] = value
|
|
85
|
+
with open(self.filepath, 'w', encoding="UTF-8") as f:
|
|
86
|
+
self.config.write(f)
|
|
87
|
+
|
|
88
|
+
def get(self, section, key):
|
|
89
|
+
return self.config.get(section, key, default=None)
|
|
90
|
+
|
|
91
|
+
def remove_section(self, section):
|
|
92
|
+
self.config.remove_section(section)
|
|
93
|
+
del self.d[section]
|
|
94
|
+
with open(self.filepath, 'w', encoding="UTF-8") as f:
|
|
95
|
+
self.config.write(f)
|
|
96
|
+
|
|
97
|
+
def remove_option(self, section, key):
|
|
98
|
+
self.config.remove_option(section, key)
|
|
99
|
+
del self.d[section][key]
|
|
100
|
+
with open(self.filepath, 'w', encoding="UTF-8") as f:
|
|
101
|
+
self.config.write(f)
|
|
102
|
+
|
|
103
|
+
def save(self):
|
|
104
|
+
for s in self.d:
|
|
105
|
+
if s not in self.config.sections():
|
|
106
|
+
self.add(s)
|
|
107
|
+
for k in self.d[s]:
|
|
108
|
+
try:
|
|
109
|
+
v = self.get(s, k)
|
|
110
|
+
except:
|
|
111
|
+
v = None
|
|
112
|
+
if self.d[s][k] != v:
|
|
113
|
+
self.set(s, k, self.d[s][k])
|
|
114
|
+
|
|
115
|
+
def __getattr__(self, name):
|
|
116
|
+
if name not in self.__dict__:
|
|
117
|
+
try:
|
|
118
|
+
return self.d[name]
|
|
119
|
+
except KeyError as k:
|
|
120
|
+
self.d[name] = Dict()
|
|
121
|
+
return self.d[name]
|
|
122
|
+
return self.__dict__[name]
|
ai/data/__init__.py
ADDED
|
File without changes
|
ai/data/augment.py
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import cv2
|
|
2
|
+
import math
|
|
3
|
+
import random
|
|
4
|
+
import numpy as np
|
|
5
|
+
from PIL import Image, ImageFilter
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class WarpMLS:
|
|
9
|
+
def __init__(self, src, src_pts, dst_pts, dst_w, dst_h, trans_ratio=1.):
|
|
10
|
+
self.src = src
|
|
11
|
+
self.src_pts = src_pts
|
|
12
|
+
self.dst_pts = dst_pts
|
|
13
|
+
self.pt_count = len(self.dst_pts)
|
|
14
|
+
self.dst_w = dst_w
|
|
15
|
+
self.dst_h = dst_h
|
|
16
|
+
self.trans_ratio = trans_ratio
|
|
17
|
+
self.grid_size = 100
|
|
18
|
+
self.rdx = np.zeros((self.dst_h, self.dst_w))
|
|
19
|
+
self.rdy = np.zeros((self.dst_h, self.dst_w))
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def _bilinear_interp(x, y, v11, v12, v21, v22):
|
|
23
|
+
return (v11 * (1 - y) + v12 * y) * (1 - x) + (v21 * (1 - y) + v22 * y) * x
|
|
24
|
+
|
|
25
|
+
def generate(self):
|
|
26
|
+
self.calc_delta()
|
|
27
|
+
return self.gen_img()
|
|
28
|
+
|
|
29
|
+
def calc_delta(self):
|
|
30
|
+
w = np.zeros(self.pt_count, dtype=np.float32)
|
|
31
|
+
|
|
32
|
+
if self.pt_count < 2:
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
i = 0
|
|
36
|
+
while 1:
|
|
37
|
+
if self.dst_w <= i < self.dst_w + self.grid_size - 1:
|
|
38
|
+
i = self.dst_w - 1
|
|
39
|
+
elif i >= self.dst_w:
|
|
40
|
+
break
|
|
41
|
+
|
|
42
|
+
j = 0
|
|
43
|
+
while 1:
|
|
44
|
+
if self.dst_h <= j < self.dst_h + self.grid_size - 1:
|
|
45
|
+
j = self.dst_h - 1
|
|
46
|
+
elif j >= self.dst_h:
|
|
47
|
+
break
|
|
48
|
+
|
|
49
|
+
sw = 0
|
|
50
|
+
swp = np.zeros(2, dtype=np.float32)
|
|
51
|
+
swq = np.zeros(2, dtype=np.float32)
|
|
52
|
+
new_pt = np.zeros(2, dtype=np.float32)
|
|
53
|
+
cur_pt = np.array([i, j], dtype=np.float32)
|
|
54
|
+
|
|
55
|
+
k = 0
|
|
56
|
+
for k in range(self.pt_count):
|
|
57
|
+
if i == self.dst_pts[k][0] and j == self.dst_pts[k][1]:
|
|
58
|
+
break
|
|
59
|
+
|
|
60
|
+
w[k] = 1. / ((i - self.dst_pts[k][0]) * (i - self.dst_pts[k][0]) +
|
|
61
|
+
(j - self.dst_pts[k][1]) * (j - self.dst_pts[k][1]))
|
|
62
|
+
|
|
63
|
+
sw += w[k]
|
|
64
|
+
swp = swp + w[k] * np.array(self.dst_pts[k])
|
|
65
|
+
swq = swq + w[k] * np.array(self.src_pts[k])
|
|
66
|
+
|
|
67
|
+
if k == self.pt_count - 1:
|
|
68
|
+
pstar = 1 / sw * swp
|
|
69
|
+
qstar = 1 / sw * swq
|
|
70
|
+
|
|
71
|
+
miu_s = 0
|
|
72
|
+
for k in range(self.pt_count):
|
|
73
|
+
if i == self.dst_pts[k][0] and j == self.dst_pts[k][1]:
|
|
74
|
+
continue
|
|
75
|
+
pt_i = self.dst_pts[k] - pstar
|
|
76
|
+
miu_s += w[k] * np.sum(pt_i * pt_i)
|
|
77
|
+
|
|
78
|
+
cur_pt -= pstar
|
|
79
|
+
cur_pt_j = np.array([-cur_pt[1], cur_pt[0]])
|
|
80
|
+
|
|
81
|
+
for k in range(self.pt_count):
|
|
82
|
+
if i == self.dst_pts[k][0] and j == self.dst_pts[k][1]:
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
pt_i = self.dst_pts[k] - pstar
|
|
86
|
+
pt_j = np.array([-pt_i[1], pt_i[0]])
|
|
87
|
+
|
|
88
|
+
tmp_pt = np.zeros(2, dtype=np.float32)
|
|
89
|
+
tmp_pt[0] = np.sum(pt_i * cur_pt) * self.src_pts[k][0] - \
|
|
90
|
+
np.sum(pt_j * cur_pt) * self.src_pts[k][1]
|
|
91
|
+
tmp_pt[1] = -np.sum(pt_i * cur_pt_j) * self.src_pts[k][0] + \
|
|
92
|
+
np.sum(pt_j * cur_pt_j) * self.src_pts[k][1]
|
|
93
|
+
tmp_pt *= (w[k] / miu_s)
|
|
94
|
+
new_pt += tmp_pt
|
|
95
|
+
|
|
96
|
+
new_pt += qstar
|
|
97
|
+
else:
|
|
98
|
+
new_pt = self.src_pts[k]
|
|
99
|
+
|
|
100
|
+
self.rdx[j, i] = new_pt[0] - i
|
|
101
|
+
self.rdy[j, i] = new_pt[1] - j
|
|
102
|
+
|
|
103
|
+
j += self.grid_size
|
|
104
|
+
i += self.grid_size
|
|
105
|
+
|
|
106
|
+
def gen_img(self):
|
|
107
|
+
src_h, src_w = self.src.shape[:2]
|
|
108
|
+
dst = np.zeros_like(self.src, dtype=np.float32)
|
|
109
|
+
|
|
110
|
+
for i in np.arange(0, self.dst_h, self.grid_size):
|
|
111
|
+
for j in np.arange(0, self.dst_w, self.grid_size):
|
|
112
|
+
ni = i + self.grid_size
|
|
113
|
+
nj = j + self.grid_size
|
|
114
|
+
w = h = self.grid_size
|
|
115
|
+
if ni >= self.dst_h:
|
|
116
|
+
ni = self.dst_h - 1
|
|
117
|
+
h = ni - i + 1
|
|
118
|
+
if nj >= self.dst_w:
|
|
119
|
+
nj = self.dst_w - 1
|
|
120
|
+
w = nj - j + 1
|
|
121
|
+
|
|
122
|
+
di = np.reshape(np.arange(h), (-1, 1))
|
|
123
|
+
dj = np.reshape(np.arange(w), (1, -1))
|
|
124
|
+
delta_x = self._bilinear_interp(di / h, dj / w,
|
|
125
|
+
self.rdx[i, j], self.rdx[i, nj],
|
|
126
|
+
self.rdx[ni, j], self.rdx[ni, nj])
|
|
127
|
+
delta_y = self._bilinear_interp(di / h, dj / w,
|
|
128
|
+
self.rdy[i, j], self.rdy[i, nj],
|
|
129
|
+
self.rdy[ni, j], self.rdy[ni, nj])
|
|
130
|
+
nx = j + dj + delta_x * self.trans_ratio
|
|
131
|
+
ny = i + di + delta_y * self.trans_ratio
|
|
132
|
+
nx = np.clip(nx, 0, src_w - 1)
|
|
133
|
+
ny = np.clip(ny, 0, src_h - 1)
|
|
134
|
+
nxi = np.array(np.floor(nx), dtype=np.int32)
|
|
135
|
+
nyi = np.array(np.floor(ny), dtype=np.int32)
|
|
136
|
+
nxi1 = np.array(np.ceil(nx), dtype=np.int32)
|
|
137
|
+
nyi1 = np.array(np.ceil(ny), dtype=np.int32)
|
|
138
|
+
|
|
139
|
+
if len(self.src.shape) == 3:
|
|
140
|
+
x = np.tile(np.expand_dims(ny - nyi, axis=-1), (1, 1, 3))
|
|
141
|
+
y = np.tile(np.expand_dims(nx - nxi, axis=-1), (1, 1, 3))
|
|
142
|
+
else:
|
|
143
|
+
x = ny - nyi
|
|
144
|
+
y = nx - nxi
|
|
145
|
+
dst[i:i + h, j:j + w] = self._bilinear_interp(
|
|
146
|
+
x,
|
|
147
|
+
y,
|
|
148
|
+
self.src[nyi, nxi],
|
|
149
|
+
self.src[nyi, nxi1],
|
|
150
|
+
self.src[nyi1, nxi],
|
|
151
|
+
self.src[nyi1, nxi1]
|
|
152
|
+
)
|
|
153
|
+
dst = np.clip(dst, 0, 255)
|
|
154
|
+
dst = np.array(dst, dtype=np.uint8)
|
|
155
|
+
return dst
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def distort(src, segment=4):
|
|
159
|
+
img_h, img_w = src.shape[:2]
|
|
160
|
+
cut = img_w // segment
|
|
161
|
+
thresh = cut // 3
|
|
162
|
+
if thresh == 0:
|
|
163
|
+
return src
|
|
164
|
+
|
|
165
|
+
src_pts = list()
|
|
166
|
+
dst_pts = list()
|
|
167
|
+
|
|
168
|
+
src_pts.append([0, 0])
|
|
169
|
+
src_pts.append([img_w, 0])
|
|
170
|
+
src_pts.append([img_w, img_h])
|
|
171
|
+
src_pts.append([0, img_h])
|
|
172
|
+
|
|
173
|
+
dst_pts.append([np.random.randint(thresh), np.random.randint(thresh)])
|
|
174
|
+
dst_pts.append([img_w - np.random.randint(thresh), np.random.randint(thresh)])
|
|
175
|
+
dst_pts.append([img_w - np.random.randint(thresh), img_h - np.random.randint(thresh)])
|
|
176
|
+
dst_pts.append([np.random.randint(thresh), img_h - np.random.randint(thresh)])
|
|
177
|
+
|
|
178
|
+
half_thresh = thresh * 0.5
|
|
179
|
+
|
|
180
|
+
for cut_idx in np.arange(1, segment, 1):
|
|
181
|
+
src_pts.append([cut * cut_idx, 0])
|
|
182
|
+
src_pts.append([cut * cut_idx, img_h])
|
|
183
|
+
dst_pts.append([cut * cut_idx + np.random.randint(thresh) - half_thresh,
|
|
184
|
+
np.random.randint(thresh) - half_thresh])
|
|
185
|
+
dst_pts.append([cut * cut_idx + np.random.randint(thresh) - half_thresh,
|
|
186
|
+
img_h + np.random.randint(thresh) - half_thresh])
|
|
187
|
+
|
|
188
|
+
trans = WarpMLS(src, src_pts, dst_pts, img_w, img_h)
|
|
189
|
+
dst = trans.generate()
|
|
190
|
+
|
|
191
|
+
return dst
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def stretch(src, segment=4):
|
|
195
|
+
img_h, img_w = src.shape[:2]
|
|
196
|
+
|
|
197
|
+
cut = img_w // segment
|
|
198
|
+
thresh = cut * 4 // 5
|
|
199
|
+
if thresh == 0:
|
|
200
|
+
return src
|
|
201
|
+
|
|
202
|
+
src_pts = list()
|
|
203
|
+
dst_pts = list()
|
|
204
|
+
|
|
205
|
+
src_pts.append([0, 0])
|
|
206
|
+
src_pts.append([img_w, 0])
|
|
207
|
+
src_pts.append([img_w, img_h])
|
|
208
|
+
src_pts.append([0, img_h])
|
|
209
|
+
|
|
210
|
+
dst_pts.append([0, 0])
|
|
211
|
+
dst_pts.append([img_w, 0])
|
|
212
|
+
dst_pts.append([img_w, img_h])
|
|
213
|
+
dst_pts.append([0, img_h])
|
|
214
|
+
|
|
215
|
+
half_thresh = thresh * 0.5
|
|
216
|
+
|
|
217
|
+
for cut_idx in np.arange(1, segment, 1):
|
|
218
|
+
move = np.random.randint(thresh) - half_thresh
|
|
219
|
+
src_pts.append([cut * cut_idx, 0])
|
|
220
|
+
src_pts.append([cut * cut_idx, img_h])
|
|
221
|
+
dst_pts.append([cut * cut_idx + move, 0])
|
|
222
|
+
dst_pts.append([cut * cut_idx + move, img_h])
|
|
223
|
+
|
|
224
|
+
trans = WarpMLS(src, src_pts, dst_pts, img_w, img_h)
|
|
225
|
+
dst = trans.generate()
|
|
226
|
+
|
|
227
|
+
return dst
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def perspective(src):
|
|
231
|
+
img_h, img_w = src.shape[:2]
|
|
232
|
+
|
|
233
|
+
thresh = img_h // 2
|
|
234
|
+
if thresh == 0:
|
|
235
|
+
return src
|
|
236
|
+
|
|
237
|
+
src_pts = list()
|
|
238
|
+
dst_pts = list()
|
|
239
|
+
|
|
240
|
+
src_pts.append([0, 0])
|
|
241
|
+
src_pts.append([img_w, 0])
|
|
242
|
+
src_pts.append([img_w, img_h])
|
|
243
|
+
src_pts.append([0, img_h])
|
|
244
|
+
|
|
245
|
+
dst_pts.append([0, np.random.randint(thresh)])
|
|
246
|
+
dst_pts.append([img_w, np.random.randint(thresh)])
|
|
247
|
+
dst_pts.append([img_w, img_h - np.random.randint(thresh)])
|
|
248
|
+
dst_pts.append([0, img_h - np.random.randint(thresh)])
|
|
249
|
+
|
|
250
|
+
trans = WarpMLS(src, src_pts, dst_pts, img_w, img_h)
|
|
251
|
+
dst = trans.generate()
|
|
252
|
+
|
|
253
|
+
return dst
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def gridmask(src, d1=4, d2=16, rotate=1, ratio=0.5, mode=0):
|
|
257
|
+
h, w, c = src.shape
|
|
258
|
+
hh = math.ceil((math.sqrt(h*h + w*w)))
|
|
259
|
+
d = np.random.randint(d1, d2)
|
|
260
|
+
l = math.ceil(d * ratio)
|
|
261
|
+
|
|
262
|
+
mask = np.ones((hh, hh), np.float32)
|
|
263
|
+
st_h = np.random.randint(d)
|
|
264
|
+
st_w = np.random.randint(d)
|
|
265
|
+
for i in range(-1, hh//d+1):
|
|
266
|
+
s = d*i + st_h
|
|
267
|
+
t = s + l
|
|
268
|
+
s = max(min(s, hh), 0)
|
|
269
|
+
t = max(min(t, hh), 0)
|
|
270
|
+
mask[s:t,:] *= 0
|
|
271
|
+
for i in range(-1, hh//d+1):
|
|
272
|
+
s = d*i + st_w
|
|
273
|
+
t = s + l
|
|
274
|
+
s = max(min(s, hh), 0)
|
|
275
|
+
t = max(min(t, hh), 0)
|
|
276
|
+
mask[:,s:t] *= 0
|
|
277
|
+
r = np.random.randint(rotate)
|
|
278
|
+
mask = Image.fromarray(np.uint8(mask))
|
|
279
|
+
mask = mask.rotate(r)
|
|
280
|
+
mask = np.asarray(mask)
|
|
281
|
+
mask = mask[(hh-h)//2:(hh-h)//2+h, (hh-w)//2:(hh-w)//2+w]
|
|
282
|
+
|
|
283
|
+
if mode == 1:
|
|
284
|
+
mask = 1 - mask
|
|
285
|
+
|
|
286
|
+
if c == 3:
|
|
287
|
+
mask = cv2.merge([mask, mask, mask])
|
|
288
|
+
|
|
289
|
+
dst = src * mask
|
|
290
|
+
return np.uint8(dst)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class RandomDistort(object):
|
|
294
|
+
def __init__(self, p=0.1):
|
|
295
|
+
self.p = p
|
|
296
|
+
|
|
297
|
+
def __call__(self, img):
|
|
298
|
+
if random.random() > self.p:
|
|
299
|
+
return img
|
|
300
|
+
img = np.array(img)
|
|
301
|
+
img = distort(img)
|
|
302
|
+
return Image.fromarray(np.uint8(img))
|
|
303
|
+
|
|
304
|
+
def __repr__(self):
|
|
305
|
+
return self.__class__.__name__ + '()'
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class RandomStretch(object):
|
|
309
|
+
def __init__(self, p=0.1):
|
|
310
|
+
self.p = p
|
|
311
|
+
|
|
312
|
+
def __call__(self, img):
|
|
313
|
+
if random.random() > self.p:
|
|
314
|
+
return img
|
|
315
|
+
img = np.array(img)
|
|
316
|
+
img = stretch(img)
|
|
317
|
+
return Image.fromarray(np.uint8(img))
|
|
318
|
+
|
|
319
|
+
def __repr__(self):
|
|
320
|
+
return self.__class__.__name__ + '()'
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class RandomPerspective(object):
|
|
324
|
+
def __init__(self, p=0.1):
|
|
325
|
+
self.p = p
|
|
326
|
+
|
|
327
|
+
def __call__(self, img):
|
|
328
|
+
if random.random() > self.p:
|
|
329
|
+
return img
|
|
330
|
+
img = np.array(img)
|
|
331
|
+
img = perspective(img)
|
|
332
|
+
return Image.fromarray(np.uint8(img))
|
|
333
|
+
|
|
334
|
+
def __repr__(self):
|
|
335
|
+
return self.__class__.__name__ + '()'
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class RandomGridMask(object):
|
|
339
|
+
def __init__(self, p=0.5):
|
|
340
|
+
self.p = p
|
|
341
|
+
|
|
342
|
+
def __call__(self, img):
|
|
343
|
+
if random.random() > self.p:
|
|
344
|
+
return img
|
|
345
|
+
img = np.array(img)
|
|
346
|
+
img = gridmask(img)
|
|
347
|
+
return Image.fromarray(np.uint8(img))
|
|
348
|
+
|
|
349
|
+
def __repr__(self):
|
|
350
|
+
return self.__class__.__name__ + '()'
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class AddPepperNoise(object):
|
|
354
|
+
"""增加椒盐噪声
|
|
355
|
+
Args:
|
|
356
|
+
snr (float): Signal Noise Rate
|
|
357
|
+
p (float): 概率值,依概率执行该操作
|
|
358
|
+
"""
|
|
359
|
+
def __init__(self, snr, p=0.5):
|
|
360
|
+
assert isinstance(snr, float) or (isinstance(p, float))
|
|
361
|
+
self.snr = snr
|
|
362
|
+
self.p = p
|
|
363
|
+
|
|
364
|
+
def __call__(self, img):
|
|
365
|
+
if random.random() > self.p:
|
|
366
|
+
return img
|
|
367
|
+
img = np.array(img)
|
|
368
|
+
h, w, c = img.shape
|
|
369
|
+
signal_pct = self.snr
|
|
370
|
+
noise_pct = (1 - self.snr)
|
|
371
|
+
mask = np.random.choice((0, 1, 2), size=(h, w, 1), p=[signal_pct, noise_pct/2., noise_pct/2.])
|
|
372
|
+
mask = np.repeat(mask, c, axis=2)
|
|
373
|
+
img[mask == 1] = 255 # 盐噪声
|
|
374
|
+
img[mask == 2] = 0 # 椒噪声
|
|
375
|
+
return Image.fromarray(np.uint8(img))
|
|
376
|
+
|
|
377
|
+
def __repr__(self):
|
|
378
|
+
return self.__class__.__name__ + '()'
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class GaussianBlur(object):
|
|
382
|
+
"""Gaussian blur augmentation in SimCLR https://arxiv.org/abs/2002.05709"""
|
|
383
|
+
|
|
384
|
+
def __init__(self, sigma=[.1, 2.], p=0.5):
|
|
385
|
+
self.sigma = sigma
|
|
386
|
+
self.p = p
|
|
387
|
+
|
|
388
|
+
def __call__(self, img):
|
|
389
|
+
if random.random() > self.p:
|
|
390
|
+
return img
|
|
391
|
+
sigma = random.uniform(self.sigma[0], self.sigma[1])
|
|
392
|
+
img = img.filter(ImageFilter.GaussianBlur(radius=sigma))
|
|
393
|
+
return img
|
|
394
|
+
|
|
395
|
+
def __repr__(self):
|
|
396
|
+
return self.__class__.__name__ + '()'
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
class RandomErasing(object):
|
|
400
|
+
"""
|
|
401
|
+
https://github.com/zhunzhong07/Random-Erasing
|
|
402
|
+
Args:
|
|
403
|
+
p: The prob that the operation will be performed.
|
|
404
|
+
sl: min erasing area
|
|
405
|
+
sh: max erasing area
|
|
406
|
+
r1: min aspect ratio
|
|
407
|
+
mean: erasing value
|
|
408
|
+
"""
|
|
409
|
+
def __init__(self, p=0.5, sl=0.01, sh=0.1, r1=0.2, max_attempts=10, aspect_ratio=None, mean=[0.4914, 0.4822, 0.4465]):
|
|
410
|
+
self.p = p
|
|
411
|
+
self.sl = sl
|
|
412
|
+
self.sh = sh
|
|
413
|
+
self.r1 = r1
|
|
414
|
+
self.mean = mean
|
|
415
|
+
self.max_attempts = max_attempts
|
|
416
|
+
self.aspect_ratio = aspect_ratio
|
|
417
|
+
|
|
418
|
+
def __call__(self, img):
|
|
419
|
+
if random.random() > self.p:
|
|
420
|
+
return img
|
|
421
|
+
img = np.array(img)
|
|
422
|
+
h, w, c = img.shape
|
|
423
|
+
|
|
424
|
+
for attempt in range(self.max_attempts):
|
|
425
|
+
area = h * w
|
|
426
|
+
|
|
427
|
+
target_area = random.uniform(self.sl, self.sh) * area
|
|
428
|
+
if self.aspect_ratio is None:
|
|
429
|
+
aspect_ratio = random.uniform(self.r1, 1/self.r1)
|
|
430
|
+
else:
|
|
431
|
+
aspect_ratio = self.aspect_ratio
|
|
432
|
+
|
|
433
|
+
eh = int(round(math.sqrt(target_area * aspect_ratio)))
|
|
434
|
+
ew = int(round(math.sqrt(target_area / aspect_ratio)))
|
|
435
|
+
|
|
436
|
+
if ew < w and eh < h:
|
|
437
|
+
x1 = random.randint(0, h - eh)
|
|
438
|
+
y1 = random.randint(0, w - ew)
|
|
439
|
+
if c == 3:
|
|
440
|
+
img[x1:x1+eh, y1:y1+ew, 0] = self.mean[0]
|
|
441
|
+
img[x1:x1+eh, y1:y1+ew, 1] = self.mean[1]
|
|
442
|
+
img[x1:x1+eh, y1:y1+ew, 2] = self.mean[2]
|
|
443
|
+
else:
|
|
444
|
+
img[x1:x1+eh, y1:y1+ew, 0] = self.mean[0]
|
|
445
|
+
return Image.fromarray(np.uint8(img))
|
|
446
|
+
|
|
447
|
+
return Image.fromarray(np.uint8(img))
|
|
448
|
+
|
|
449
|
+
def __repr__(self):
|
|
450
|
+
return self.__class__.__name__ + '()'
|
ai/llm/__init__.py
ADDED
|
File without changes
|
ai/llm/postprocess.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
def get_csv_from_llm(res):
|
|
2
|
+
if '<answer>' in res:
|
|
3
|
+
start = res.find('<answer>')
|
|
4
|
+
if start != -1:
|
|
5
|
+
res = res[start+len('<answer>'):]
|
|
6
|
+
end = res.find('</answer>')
|
|
7
|
+
if end != -1:
|
|
8
|
+
res = res[:end]
|
|
9
|
+
|
|
10
|
+
res = res.strip()
|
|
11
|
+
res = res.replace('\n\n', '\n')
|
|
12
|
+
start_tag, end_tag = '```csv', '```'
|
|
13
|
+
left = res.rfind(start_tag)
|
|
14
|
+
if left == -1:
|
|
15
|
+
start_tag = '```'
|
|
16
|
+
left = res.rfind('```')
|
|
17
|
+
right = res.rfind(end_tag)
|
|
18
|
+
if left != -1 and right != -1 and right > left:
|
|
19
|
+
res = res[left+len(start_tag):right].strip()
|
|
20
|
+
else:
|
|
21
|
+
res = res.strip('`')
|
|
22
|
+
|
|
23
|
+
return res
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_list_from_llm(res):
|
|
27
|
+
if '<answer>' in res:
|
|
28
|
+
start = res.find('<answer>')
|
|
29
|
+
if start != -1:
|
|
30
|
+
res = res[start+len('<answer>'):]
|
|
31
|
+
end = res.find('</answer>')
|
|
32
|
+
if end != -1:
|
|
33
|
+
res = res[:end]
|
|
34
|
+
|
|
35
|
+
res = res.strip()
|
|
36
|
+
res = res.replace('\n\n', '\n')
|
|
37
|
+
res = res.replace('",', '",')
|
|
38
|
+
|
|
39
|
+
if '"' not in res and "'" not in res:
|
|
40
|
+
res = res.strip('[]')
|
|
41
|
+
res = res.replace(';', ';')
|
|
42
|
+
tmp = res.split(';')
|
|
43
|
+
res = '[' + ','.join([f'"{t}"' for t in tmp]) + ']'
|
|
44
|
+
|
|
45
|
+
start_tag, end_tag = '```python', '```'
|
|
46
|
+
left = res.find(start_tag)
|
|
47
|
+
if left == -1:
|
|
48
|
+
start_tag = '```'
|
|
49
|
+
left = res.find('```')
|
|
50
|
+
right = res.rfind(end_tag)
|
|
51
|
+
if left != -1 and right != -1 and right > left:
|
|
52
|
+
res = res[left+len(start_tag):right].strip()
|
|
53
|
+
elif left != -1:
|
|
54
|
+
res = res[left+len(start_tag):].strip()
|
|
55
|
+
else:
|
|
56
|
+
res = res.strip('`')
|
|
57
|
+
|
|
58
|
+
left = res.find('[')
|
|
59
|
+
if left != -1:
|
|
60
|
+
res = res[left:]
|
|
61
|
+
else:
|
|
62
|
+
res = '[' + res
|
|
63
|
+
|
|
64
|
+
if not res.startswith('['):
|
|
65
|
+
res = '[' + res
|
|
66
|
+
if not res.endswith(']'):
|
|
67
|
+
if res.endswith(','):
|
|
68
|
+
res = res.strip(',')
|
|
69
|
+
if res.endswith(',"'):
|
|
70
|
+
res = res[:-2]
|
|
71
|
+
if not res.endswith('"'):
|
|
72
|
+
res += '"'
|
|
73
|
+
res += ']'
|
|
74
|
+
try:
|
|
75
|
+
data = eval(res)
|
|
76
|
+
except:
|
|
77
|
+
print('Error: get_list_from_llm')
|
|
78
|
+
print('res:', res)
|
|
79
|
+
data = []
|
|
80
|
+
|
|
81
|
+
return data
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_json_from_llm(res):
|
|
85
|
+
if '<answer>' in res:
|
|
86
|
+
start = res.find('<answer>')
|
|
87
|
+
if start != -1:
|
|
88
|
+
res = res[start+len('<answer>'):]
|
|
89
|
+
end = res.find('</answer>')
|
|
90
|
+
if end != -1:
|
|
91
|
+
res = res[:end]
|
|
92
|
+
|
|
93
|
+
res = res.strip()
|
|
94
|
+
res = res.replace('\\\"', '"').replace('\\n', '\n')
|
|
95
|
+
res = res.replace('\n\n', '\n').replace('\n', '').replace(' ', '')
|
|
96
|
+
res = res.replace('",', '",').replace('""', '","')
|
|
97
|
+
res = res.replace('}{', '},{')
|
|
98
|
+
|
|
99
|
+
start_tag, end_tag = '```json', '```'
|
|
100
|
+
left = res.find(start_tag)
|
|
101
|
+
if left == -1:
|
|
102
|
+
start_tag = '```'
|
|
103
|
+
left = res.find(start_tag)
|
|
104
|
+
right = res.rfind(end_tag)
|
|
105
|
+
if left != -1 and right != -1 and right > left:
|
|
106
|
+
res = res[left+len(start_tag):right].strip()
|
|
107
|
+
elif left != -1:
|
|
108
|
+
res = res[left+len(start_tag):].strip()
|
|
109
|
+
else:
|
|
110
|
+
res = res.strip('`')
|
|
111
|
+
|
|
112
|
+
if '``````json' in res:
|
|
113
|
+
tmp = res.split('``````json')
|
|
114
|
+
res = '[' + ','.join(tmp) + ']'
|
|
115
|
+
|
|
116
|
+
res = res.replace('"""', '"')
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
data = eval(res)
|
|
120
|
+
except:
|
|
121
|
+
print('Error: get_json_from_llm')
|
|
122
|
+
print('res:', res)
|
|
123
|
+
data = {}
|
|
124
|
+
|
|
125
|
+
return data
|
ai/optim/__init__.py
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import numpy as np
|
|
3
|
+
import torch
|
|
4
|
+
from torch.optim import Optimizer
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AdamGC(Optimizer):
|
|
9
|
+
r"""Implements AdamGC algorithm with Gradient Centralization.
|
|
10
|
+
|
|
11
|
+
It has been proposed in `Adam: A Method for Stochastic Optimization`_.
|
|
12
|
+
|
|
13
|
+
Arguments:
|
|
14
|
+
params (iterable): iterable of parameters to optimize or dicts defining
|
|
15
|
+
parameter groups
|
|
16
|
+
lr (float, optional): learning rate (default: 1e-3)
|
|
17
|
+
betas (Tuple[float, float], optional): coefficients used for computing
|
|
18
|
+
running averages of gradient and its square (default: (0.9, 0.999))
|
|
19
|
+
eps (float, optional): term added to the denominator to improve
|
|
20
|
+
numerical stability (default: 1e-8)
|
|
21
|
+
weight_decay (float, optional): weight decay (L2 penalty) (default: 0)
|
|
22
|
+
amsgrad (boolean, optional): whether to use the AMSGrad variant of this
|
|
23
|
+
algorithm from the paper `On the Convergence of Adam and Beyond`_
|
|
24
|
+
(default: False)
|
|
25
|
+
|
|
26
|
+
.. _Adam\: A Method for Stochastic Optimization:
|
|
27
|
+
https://arxiv.org/abs/1412.6980
|
|
28
|
+
.. _On the Convergence of Adam and Beyond:
|
|
29
|
+
https://openreview.net/forum?id=ryQu7f-RZ
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8,
|
|
33
|
+
weight_decay=0, amsgrad=False, correct_bias=False):
|
|
34
|
+
if not 0.0 <= lr:
|
|
35
|
+
raise ValueError("Invalid learning rate: {}".format(lr))
|
|
36
|
+
if not 0.0 <= eps:
|
|
37
|
+
raise ValueError("Invalid epsilon value: {}".format(eps))
|
|
38
|
+
if not 0.0 <= betas[0] < 1.0:
|
|
39
|
+
raise ValueError("Invalid beta parameter at index 0: {}".format(betas[0]))
|
|
40
|
+
if not 0.0 <= betas[1] < 1.0:
|
|
41
|
+
raise ValueError("Invalid beta parameter at index 1: {}".format(betas[1]))
|
|
42
|
+
defaults = dict(lr=lr, betas=betas, eps=eps, correct_bias=correct_bias,
|
|
43
|
+
weight_decay=weight_decay, amsgrad=amsgrad)
|
|
44
|
+
super(AdamGC, self).__init__(params, defaults)
|
|
45
|
+
|
|
46
|
+
def __setstate__(self, state):
|
|
47
|
+
super(AdamGC, self).__setstate__(state)
|
|
48
|
+
for group in self.param_groups:
|
|
49
|
+
group.setdefault('amsgrad', False)
|
|
50
|
+
|
|
51
|
+
def step(self, closure=None):
|
|
52
|
+
"""Performs a single optimization step.
|
|
53
|
+
|
|
54
|
+
Arguments:
|
|
55
|
+
closure (callable, optional): A closure that reevaluates the model
|
|
56
|
+
and returns the loss.
|
|
57
|
+
"""
|
|
58
|
+
loss = None
|
|
59
|
+
if closure is not None:
|
|
60
|
+
loss = closure()
|
|
61
|
+
|
|
62
|
+
for group in self.param_groups:
|
|
63
|
+
for p in group['params']:
|
|
64
|
+
if p.grad is None:
|
|
65
|
+
continue
|
|
66
|
+
grad = p.grad.data
|
|
67
|
+
if grad.is_sparse:
|
|
68
|
+
raise RuntimeError('Adam does not support sparse gradients, please consider SparseAdam instead')
|
|
69
|
+
amsgrad = group['amsgrad']
|
|
70
|
+
|
|
71
|
+
state = self.state[p]
|
|
72
|
+
|
|
73
|
+
# State initialization
|
|
74
|
+
if len(state) == 0:
|
|
75
|
+
state['step'] = 0
|
|
76
|
+
# Exponential moving average of gradient values
|
|
77
|
+
state['exp_avg'] = torch.zeros_like(p.data)
|
|
78
|
+
# Exponential moving average of squared gradient values
|
|
79
|
+
state['exp_avg_sq'] = torch.zeros_like(p.data)
|
|
80
|
+
if amsgrad:
|
|
81
|
+
# Maintains max of all exp. moving avg. of sq. grad. values
|
|
82
|
+
state['max_exp_avg_sq'] = torch.zeros_like(p.data)
|
|
83
|
+
|
|
84
|
+
exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']
|
|
85
|
+
if amsgrad:
|
|
86
|
+
max_exp_avg_sq = state['max_exp_avg_sq']
|
|
87
|
+
beta1, beta2 = group['betas']
|
|
88
|
+
|
|
89
|
+
state['step'] += 1
|
|
90
|
+
|
|
91
|
+
if group['weight_decay'] > 0:
|
|
92
|
+
grad.add_(group['weight_decay'], p.data)
|
|
93
|
+
|
|
94
|
+
# GC operation for Conv layers and FC layers
|
|
95
|
+
length = len(list(p.data.size()))
|
|
96
|
+
if length > 1:
|
|
97
|
+
grad.add_(-grad.mean(dim=tuple(range(1, length)), keepdim=True))
|
|
98
|
+
|
|
99
|
+
# Decay the first and second moment running average coefficient
|
|
100
|
+
exp_avg.mul_(beta1).add_(1 - beta1, grad)
|
|
101
|
+
exp_avg_sq.mul_(beta2).addcmul_(1 - beta2, grad, grad)
|
|
102
|
+
|
|
103
|
+
step_size = group["lr"]
|
|
104
|
+
if group["correct_bias"]: # No bias correction for Bert
|
|
105
|
+
bias_correction1 = 1.0 - beta1 ** state["step"]
|
|
106
|
+
bias_correction2 = 1.0 - beta2 ** state["step"]
|
|
107
|
+
step_size = step_size * math.sqrt(bias_correction2) / bias_correction1
|
|
108
|
+
|
|
109
|
+
if amsgrad:
|
|
110
|
+
# Maintains the maximum of all 2nd moment running avg. till now
|
|
111
|
+
torch.max(max_exp_avg_sq, exp_avg_sq, out=max_exp_avg_sq)
|
|
112
|
+
# Use the max. for normalizing running avg. of gradient
|
|
113
|
+
denom = max_exp_avg_sq.sqrt().add_(group['eps'])
|
|
114
|
+
else:
|
|
115
|
+
denom = exp_avg_sq.sqrt().add_(group['eps'])
|
|
116
|
+
|
|
117
|
+
p.data.addcdiv_(-step_size, exp_avg, denom)
|
|
118
|
+
|
|
119
|
+
return loss
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class AdamW(Optimizer):
|
|
123
|
+
r"""Implements AdamW algorithm.
|
|
124
|
+
It has been proposed in `Adam: A Method for Stochastic Optimization`_.
|
|
125
|
+
Arguments:
|
|
126
|
+
params (iterable): iterable of parameters to optimize or dicts defining
|
|
127
|
+
parameter groups
|
|
128
|
+
lr (float, optional): learning rate (default: 1e-3)
|
|
129
|
+
betas (Tuple[float, float], optional): coefficients used for computing
|
|
130
|
+
running averages of gradient and its square (default: (0.9, 0.999))
|
|
131
|
+
eps (float, optional): term added to the denominator to improve
|
|
132
|
+
numerical stability (default: 1e-8)
|
|
133
|
+
weight_decay (float, optional): weight decay (L2 penalty) (default: 0)
|
|
134
|
+
amsgrad (boolean, optional): whether to use the AMSGrad variant of this
|
|
135
|
+
algorithm from the paper `On the Convergence of Adam and Beyond`_
|
|
136
|
+
.. _Adam\: A Method for Stochastic Optimization:
|
|
137
|
+
https://arxiv.org/abs/1412.6980
|
|
138
|
+
.. _On the Convergence of Adam and Beyond:
|
|
139
|
+
https://openreview.net/forum?id=ryQu7f-RZ
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8,
|
|
143
|
+
weight_decay=0, amsgrad=False, correct_bias=False):
|
|
144
|
+
if not 0.0 <= lr:
|
|
145
|
+
raise ValueError("Invalid learning rate: {}".format(lr))
|
|
146
|
+
if not 0.0 <= eps:
|
|
147
|
+
raise ValueError("Invalid epsilon value: {}".format(eps))
|
|
148
|
+
if not 0.0 <= betas[0] < 1.0:
|
|
149
|
+
raise ValueError("Invalid beta parameter at index 0: {}".format(betas[0]))
|
|
150
|
+
if not 0.0 <= betas[1] < 1.0:
|
|
151
|
+
raise ValueError("Invalid beta parameter at index 1: {}".format(betas[1]))
|
|
152
|
+
defaults = dict(lr=lr, betas=betas, eps=eps, correct_bias=correct_bias,
|
|
153
|
+
weight_decay=weight_decay, amsgrad=amsgrad)
|
|
154
|
+
super(AdamW, self).__init__(params, defaults)
|
|
155
|
+
|
|
156
|
+
def __setstate__(self, state):
|
|
157
|
+
super(AdamW, self).__setstate__(state)
|
|
158
|
+
for group in self.param_groups:
|
|
159
|
+
group.setdefault('amsgrad', False)
|
|
160
|
+
|
|
161
|
+
def step(self, closure=None):
|
|
162
|
+
"""Performs a single optimization step.
|
|
163
|
+
Arguments:
|
|
164
|
+
closure (callable, optional): A closure that reevaluates the model
|
|
165
|
+
and returns the loss.
|
|
166
|
+
"""
|
|
167
|
+
loss = None
|
|
168
|
+
if closure is not None:
|
|
169
|
+
loss = closure()
|
|
170
|
+
|
|
171
|
+
for group in self.param_groups:
|
|
172
|
+
for p in group['params']:
|
|
173
|
+
if p.grad is None:
|
|
174
|
+
continue
|
|
175
|
+
grad = p.grad.data
|
|
176
|
+
if grad.is_sparse:
|
|
177
|
+
raise RuntimeError('Adam does not support sparse gradients, please consider SparseAdam instead')
|
|
178
|
+
amsgrad = group['amsgrad']
|
|
179
|
+
|
|
180
|
+
state = self.state[p]
|
|
181
|
+
|
|
182
|
+
# State initialization
|
|
183
|
+
if len(state) == 0:
|
|
184
|
+
state['step'] = 0
|
|
185
|
+
# Exponential moving average of gradient values
|
|
186
|
+
state['exp_avg'] = torch.zeros_like(p.data)
|
|
187
|
+
# Exponential moving average of squared gradient values
|
|
188
|
+
state['exp_avg_sq'] = torch.zeros_like(p.data)
|
|
189
|
+
if amsgrad:
|
|
190
|
+
# Maintains max of all exp. moving avg. of sq. grad. values
|
|
191
|
+
state['max_exp_avg_sq'] = torch.zeros_like(p.data)
|
|
192
|
+
|
|
193
|
+
exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']
|
|
194
|
+
if amsgrad:
|
|
195
|
+
max_exp_avg_sq = state['max_exp_avg_sq']
|
|
196
|
+
beta1, beta2 = group['betas']
|
|
197
|
+
|
|
198
|
+
state['step'] += 1
|
|
199
|
+
|
|
200
|
+
if group['weight_decay'] > 0:
|
|
201
|
+
grad.add_(group['weight_decay'], p.data)
|
|
202
|
+
|
|
203
|
+
# Decay the first and second moment running average coefficient
|
|
204
|
+
exp_avg.mul_(beta1).add_(1 - beta1, grad)
|
|
205
|
+
exp_avg_sq.mul_(beta2).addcmul_(1 - beta2, grad, grad)
|
|
206
|
+
|
|
207
|
+
step_size = group["lr"]
|
|
208
|
+
if group["correct_bias"]: # No bias correction for Bert
|
|
209
|
+
bias_correction1 = 1.0 - beta1 ** state["step"]
|
|
210
|
+
bias_correction2 = 1.0 - beta2 ** state["step"]
|
|
211
|
+
step_size = step_size * math.sqrt(bias_correction2) / bias_correction1
|
|
212
|
+
|
|
213
|
+
if amsgrad:
|
|
214
|
+
# Maintains the maximum of all 2nd moment running avg. till now
|
|
215
|
+
torch.max(max_exp_avg_sq, exp_avg_sq, out=max_exp_avg_sq)
|
|
216
|
+
# Use the max. for normalizing running avg. of gradient
|
|
217
|
+
denom = max_exp_avg_sq.sqrt().add_(group['eps'])
|
|
218
|
+
else:
|
|
219
|
+
denom = exp_avg_sq.sqrt().add_(group['eps'])
|
|
220
|
+
|
|
221
|
+
# p.data.addcdiv_(-step_size, exp_avg, denom)
|
|
222
|
+
p.data.add_(-step_size, torch.mul(p.data, group['weight_decay']).addcdiv_(1, exp_avg, denom) )
|
|
223
|
+
|
|
224
|
+
return loss
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class AdamWGC(Optimizer):
|
|
228
|
+
r"""Implements AdamWGC algorithm with Gradient Centralization.
|
|
229
|
+
It has been proposed in `Adam: A Method for Stochastic Optimization`_.
|
|
230
|
+
Arguments:
|
|
231
|
+
params (iterable): iterable of parameters to optimize or dicts defining
|
|
232
|
+
parameter groups
|
|
233
|
+
lr (float, optional): learning rate (default: 1e-3)
|
|
234
|
+
betas (Tuple[float, float], optional): coefficients used for computing
|
|
235
|
+
running averages of gradient and its square (default: (0.9, 0.999))
|
|
236
|
+
eps (float, optional): term added to the denominator to improve
|
|
237
|
+
numerical stability (default: 1e-8)
|
|
238
|
+
weight_decay (float, optional): weight decay (L2 penalty) (default: 0)
|
|
239
|
+
amsgrad (boolean, optional): whether to use the AMSGrad variant of this
|
|
240
|
+
algorithm from the paper `On the Convergence of Adam and Beyond`_
|
|
241
|
+
.. _Adam\: A Method for Stochastic Optimization:
|
|
242
|
+
https://arxiv.org/abs/1412.6980
|
|
243
|
+
.. _On the Convergence of Adam and Beyond:
|
|
244
|
+
https://openreview.net/forum?id=ryQu7f-RZ
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8,
|
|
248
|
+
weight_decay=0, amsgrad=False, correct_bias=False):
|
|
249
|
+
if not 0.0 <= lr:
|
|
250
|
+
raise ValueError("Invalid learning rate: {}".format(lr))
|
|
251
|
+
if not 0.0 <= eps:
|
|
252
|
+
raise ValueError("Invalid epsilon value: {}".format(eps))
|
|
253
|
+
if not 0.0 <= betas[0] < 1.0:
|
|
254
|
+
raise ValueError("Invalid beta parameter at index 0: {}".format(betas[0]))
|
|
255
|
+
if not 0.0 <= betas[1] < 1.0:
|
|
256
|
+
raise ValueError("Invalid beta parameter at index 1: {}".format(betas[1]))
|
|
257
|
+
defaults = dict(lr=lr, betas=betas, eps=eps, correct_bias=correct_bias,
|
|
258
|
+
weight_decay=weight_decay, amsgrad=amsgrad)
|
|
259
|
+
super(AdamWGC, self).__init__(params, defaults)
|
|
260
|
+
|
|
261
|
+
def __setstate__(self, state):
|
|
262
|
+
super(AdamWGC, self).__setstate__(state)
|
|
263
|
+
for group in self.param_groups:
|
|
264
|
+
group.setdefault('amsgrad', False)
|
|
265
|
+
|
|
266
|
+
def step(self, closure=None):
|
|
267
|
+
"""Performs a single optimization step.
|
|
268
|
+
Arguments:
|
|
269
|
+
closure (callable, optional): A closure that reevaluates the model
|
|
270
|
+
and returns the loss.
|
|
271
|
+
"""
|
|
272
|
+
loss = None
|
|
273
|
+
if closure is not None:
|
|
274
|
+
loss = closure()
|
|
275
|
+
|
|
276
|
+
for group in self.param_groups:
|
|
277
|
+
for p in group['params']:
|
|
278
|
+
if p.grad is None:
|
|
279
|
+
continue
|
|
280
|
+
grad = p.grad.data
|
|
281
|
+
if grad.is_sparse:
|
|
282
|
+
raise RuntimeError('Adam does not support sparse gradients, please consider SparseAdam instead')
|
|
283
|
+
amsgrad = group['amsgrad']
|
|
284
|
+
|
|
285
|
+
state = self.state[p]
|
|
286
|
+
|
|
287
|
+
# State initialization
|
|
288
|
+
if len(state) == 0:
|
|
289
|
+
state['step'] = 0
|
|
290
|
+
# Exponential moving average of gradient values
|
|
291
|
+
state['exp_avg'] = torch.zeros_like(p.data)
|
|
292
|
+
# Exponential moving average of squared gradient values
|
|
293
|
+
state['exp_avg_sq'] = torch.zeros_like(p.data)
|
|
294
|
+
if amsgrad:
|
|
295
|
+
# Maintains max of all exp. moving avg. of sq. grad. values
|
|
296
|
+
state['max_exp_avg_sq'] = torch.zeros_like(p.data)
|
|
297
|
+
|
|
298
|
+
exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']
|
|
299
|
+
if amsgrad:
|
|
300
|
+
max_exp_avg_sq = state['max_exp_avg_sq']
|
|
301
|
+
beta1, beta2 = group['betas']
|
|
302
|
+
|
|
303
|
+
# GC operation for Conv layers and FC layers
|
|
304
|
+
length = len(list(p.data.size()))
|
|
305
|
+
if length > 1:
|
|
306
|
+
grad.add_(-grad.mean(dim=tuple(range(1, length)), keepdim=True))
|
|
307
|
+
|
|
308
|
+
state['step'] += 1
|
|
309
|
+
|
|
310
|
+
if group['weight_decay'] > 0:
|
|
311
|
+
grad.add_(group['weight_decay'], p.data)
|
|
312
|
+
|
|
313
|
+
# Decay the first and second moment running average coefficient
|
|
314
|
+
exp_avg.mul_(beta1).add_(1 - beta1, grad)
|
|
315
|
+
exp_avg_sq.mul_(beta2).addcmul_(1 - beta2, grad, grad)
|
|
316
|
+
if amsgrad:
|
|
317
|
+
# Maintains the maximum of all 2nd moment running avg. till now
|
|
318
|
+
torch.max(max_exp_avg_sq, exp_avg_sq, out=max_exp_avg_sq)
|
|
319
|
+
# Use the max. for normalizing running avg. of gradient
|
|
320
|
+
denom = max_exp_avg_sq.sqrt().add_(group['eps'])
|
|
321
|
+
else:
|
|
322
|
+
denom = exp_avg_sq.sqrt().add_(group['eps'])
|
|
323
|
+
|
|
324
|
+
step_size = group["lr"]
|
|
325
|
+
if group["correct_bias"]: # No bias correction for Bert
|
|
326
|
+
bias_correction1 = 1.0 - beta1 ** state["step"]
|
|
327
|
+
bias_correction2 = 1.0 - beta2 ** state["step"]
|
|
328
|
+
step_size = step_size * math.sqrt(bias_correction2) / bias_correction1
|
|
329
|
+
|
|
330
|
+
# p.data.addcdiv_(-step_size, exp_avg, denom)
|
|
331
|
+
p.data.add_(-step_size, torch.mul(p.data, group['weight_decay']).addcdiv_(1, exp_avg, denom) )
|
|
332
|
+
|
|
333
|
+
return loss
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class RangerGC(Optimizer):
|
|
337
|
+
|
|
338
|
+
def __init__(self, params, lr=1e-3, k=5, alpha=0.5, n_sma_threshhold=5, betas=(0.95,0.999),
|
|
339
|
+
eps=1e-8, weight_decay=0, amsgrad=True, transformer='softplus', smooth=50,
|
|
340
|
+
grad_transformer='square', correct_bias=False):
|
|
341
|
+
if not 0.0 <= alpha <= 1.0:
|
|
342
|
+
raise ValueError(f'Invalid slow update rate: {alpha}')
|
|
343
|
+
if not 1 <= k:
|
|
344
|
+
raise ValueError(f'Invalid lookahead steps: {k}')
|
|
345
|
+
if not lr > 0:
|
|
346
|
+
raise ValueError(f'Invalid Learning Rate: {lr}')
|
|
347
|
+
if not eps > 0:
|
|
348
|
+
raise ValueError(f'Invalid eps: {eps}')
|
|
349
|
+
|
|
350
|
+
defaults = dict(lr=lr, k=k, alpha=alpha, step_counter=0, betas=betas,
|
|
351
|
+
n_sma_threshhold=n_sma_threshhold, eps=eps, weight_decay=weight_decay,
|
|
352
|
+
smooth=smooth, transformer=transformer, grad_transformer=grad_transformer,
|
|
353
|
+
amsgrad=amsgrad, correct_bias=correct_bias)
|
|
354
|
+
super().__init__(params, defaults)
|
|
355
|
+
|
|
356
|
+
# adjustable threshold
|
|
357
|
+
self.n_sma_threshhold = n_sma_threshhold
|
|
358
|
+
|
|
359
|
+
# lookahead params
|
|
360
|
+
self.alpha = alpha
|
|
361
|
+
self.k = k
|
|
362
|
+
|
|
363
|
+
# radam buffer for state
|
|
364
|
+
self.radam_buffer = [[None,None,None] for ind in range(10)]
|
|
365
|
+
|
|
366
|
+
def __setstate__(self, state):
|
|
367
|
+
super(RangerGC, self).__setstate__(state)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def step(self, closure=None):
|
|
371
|
+
loss = None
|
|
372
|
+
if closure is not None:
|
|
373
|
+
loss = closure()
|
|
374
|
+
|
|
375
|
+
# Evaluate averages and grad, update param tensors
|
|
376
|
+
for group in self.param_groups:
|
|
377
|
+
|
|
378
|
+
for p in group['params']:
|
|
379
|
+
if p.grad is None:
|
|
380
|
+
continue
|
|
381
|
+
grad = p.grad.data.float()
|
|
382
|
+
if grad.is_sparse:
|
|
383
|
+
raise RuntimeError('Ranger optimizer does not support sparse gradients')
|
|
384
|
+
|
|
385
|
+
amsgrad = group['amsgrad']
|
|
386
|
+
smooth = group['smooth']
|
|
387
|
+
grad_transformer = group['grad_transformer']
|
|
388
|
+
|
|
389
|
+
p_data_fp32 = p.data.float()
|
|
390
|
+
|
|
391
|
+
state = self.state[p]
|
|
392
|
+
|
|
393
|
+
if len(state) == 0:
|
|
394
|
+
state['step'] = 0
|
|
395
|
+
state['exp_avg'] = torch.zeros_like(p_data_fp32)
|
|
396
|
+
state['exp_avg_sq'] = torch.zeros_like(p_data_fp32)
|
|
397
|
+
if amsgrad:
|
|
398
|
+
# Maintains max of all exp. moving avg. of sq. grad. values
|
|
399
|
+
state['max_exp_avg_sq'] = torch.zeros_like(p.data)
|
|
400
|
+
|
|
401
|
+
# lookahead weight storage now in state dict
|
|
402
|
+
state['slow_buffer'] = torch.empty_like(p.data)
|
|
403
|
+
state['slow_buffer'].copy_(p.data)
|
|
404
|
+
|
|
405
|
+
else:
|
|
406
|
+
state['exp_avg'] = state['exp_avg'].type_as(p_data_fp32)
|
|
407
|
+
state['exp_avg_sq'] = state['exp_avg_sq'].type_as(p_data_fp32)
|
|
408
|
+
|
|
409
|
+
exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']
|
|
410
|
+
beta1, beta2 = group['betas']
|
|
411
|
+
if amsgrad:
|
|
412
|
+
max_exp_avg_sq = state['max_exp_avg_sq']
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
# compute variance mov avg
|
|
416
|
+
exp_avg_sq.mul_(beta2).addcmul_(1 - beta2, grad, grad)
|
|
417
|
+
# compute mean moving avg
|
|
418
|
+
exp_avg.mul_(beta1).add_(1 - beta1, grad)
|
|
419
|
+
|
|
420
|
+
# transformer
|
|
421
|
+
if grad_transformer == 'square':
|
|
422
|
+
grad_tmp = grad**2
|
|
423
|
+
elif grad_transformer == 'abs':
|
|
424
|
+
grad_tmp = grad.abs()
|
|
425
|
+
|
|
426
|
+
exp_avg_sq.mul_(beta2).add_((1 - beta2)*grad_tmp)
|
|
427
|
+
|
|
428
|
+
if amsgrad:
|
|
429
|
+
# Maintains the maximum of all 2nd moment running avg. till now
|
|
430
|
+
torch.max(max_exp_avg_sq, exp_avg_sq, out=max_exp_avg_sq)
|
|
431
|
+
# Use the max. for normalizing running avg. of gradient
|
|
432
|
+
denomc = max_exp_avg_sq.clone()
|
|
433
|
+
else:
|
|
434
|
+
denomc = exp_avg_sq.clone()
|
|
435
|
+
|
|
436
|
+
if grad_transformer == 'square':
|
|
437
|
+
denomc.sqrt_()
|
|
438
|
+
|
|
439
|
+
# GC operation for Conv layers and FC layers
|
|
440
|
+
length = len(list(p.data.size()))
|
|
441
|
+
if length > 1:
|
|
442
|
+
grad.add_(-grad.mean(dim=tuple(range(1, length)), keepdim=True))
|
|
443
|
+
|
|
444
|
+
state['step'] += 1
|
|
445
|
+
|
|
446
|
+
if group['weight_decay'] > 0:
|
|
447
|
+
p_data_fp32.add_(-group['weight_decay'] * group['lr'], p_data_fp32)
|
|
448
|
+
|
|
449
|
+
step_size = group["lr"]
|
|
450
|
+
if group["correct_bias"]: # No bias correction for Bert
|
|
451
|
+
bias_correction1 = 1.0 - beta1 ** state["step"]
|
|
452
|
+
bias_correction2 = 1.0 - beta2 ** state["step"]
|
|
453
|
+
step_size = step_size * math.sqrt(bias_correction2) / bias_correction1
|
|
454
|
+
|
|
455
|
+
if group['transformer'] =='softplus':
|
|
456
|
+
sp = torch.nn.Softplus(smooth)
|
|
457
|
+
denomf = sp(denomc)
|
|
458
|
+
p_data_fp32.addcdiv_(-step_size, exp_avg, denomf)
|
|
459
|
+
else:
|
|
460
|
+
denom = exp_avg_sq.sqrt().add_(group['eps'])
|
|
461
|
+
p_data_fp32.addcdiv_(-step_size * group['lr'], exp_avg, denom)
|
|
462
|
+
|
|
463
|
+
p.data.copy_(p_data_fp32)
|
|
464
|
+
|
|
465
|
+
# Integrated lookahead...
|
|
466
|
+
# We do it at the param level instead of group level
|
|
467
|
+
if state['step'] % group['k'] == 0:
|
|
468
|
+
slow_p = state['slow_buffer'] # get access to slow param tensor
|
|
469
|
+
slow_p.add_(self.alpha, p.data - slow_p) # (fast weights - slow weights) * alpha
|
|
470
|
+
p.data.copy_(slow_p) # copy interpolated weights to RAdam param tensor
|
|
471
|
+
|
|
472
|
+
return loss
|
ai/tensor/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def tensor2list(tensor):
|
|
5
|
+
'''将张量转换为列表
|
|
6
|
+
'''
|
|
7
|
+
return tensor.detach().cpu().tolist()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def cat(tensors, dim=0):
|
|
11
|
+
"""更高效的 torch.cat 版本,避免在只有一个元素时复制原张量
|
|
12
|
+
"""
|
|
13
|
+
assert isinstance(tensors, (list, tuple))
|
|
14
|
+
if len(tensors) == 1:
|
|
15
|
+
return tensors[0]
|
|
16
|
+
return torch.cat(tensors, dim)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def contiguous(tensor):
|
|
20
|
+
'''获取连续存储的张量
|
|
21
|
+
'''
|
|
22
|
+
if tensor.is_contiguous():
|
|
23
|
+
return tensor
|
|
24
|
+
else:
|
|
25
|
+
return tensor.contiguous()
|
ai/trainer/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
import random
|
|
4
|
+
import torch
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def set_seed(seed, n_gpu=1):
|
|
8
|
+
'''设置随机种子
|
|
9
|
+
'''
|
|
10
|
+
os.environ['PYTHONHASHSEED'] = str(seed)
|
|
11
|
+
random.seed(seed)
|
|
12
|
+
np.random.seed(seed)
|
|
13
|
+
torch.manual_seed(seed)
|
|
14
|
+
if n_gpu > 0:
|
|
15
|
+
torch.cuda.manual_seed(seed)
|
|
16
|
+
torch.cuda.manual_seed_all(seed)
|
|
17
|
+
torch.backends.cudnn.deterministic = True
|
|
18
|
+
torch.backends.cudnn.benchmark = True
|
ai/utils/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import bz2
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def ensure_dir(dir_path):
|
|
6
|
+
'''确保单个目录存在
|
|
7
|
+
'''
|
|
8
|
+
if not os.path.exists(dir_path):
|
|
9
|
+
os.makedirs(dir_path)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def ensure_dirs(dir_list):
|
|
13
|
+
'''确保多个目录存在
|
|
14
|
+
'''
|
|
15
|
+
for dir_path in dir_list:
|
|
16
|
+
ensure_dir(dir_path)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def unpack_bz2(src_path):
|
|
20
|
+
'''解压 bz2 文件
|
|
21
|
+
'''
|
|
22
|
+
data = bz2.BZ2File(src_path).read()
|
|
23
|
+
dst_path = src_path[:-4]
|
|
24
|
+
with open(dst_path, 'wb') as fp:
|
|
25
|
+
fp.write(data)
|
|
26
|
+
return dst_path
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ai
|
|
3
|
+
Version: 1.1.1
|
|
4
|
+
Summary: Ai lib.
|
|
5
|
+
Home-page: https://github.com/Decalogue/ai
|
|
6
|
+
Author: Decalogue
|
|
7
|
+
Author-email: 1044908508@qq.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Information Technology
|
|
13
|
+
Classifier: Natural Language :: English
|
|
14
|
+
Classifier: Natural Language :: Chinese (Simplified)
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
19
|
+
Classifier: Operating System :: Other OS
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Description-Content-Type: text/x-rst
|
|
27
|
+
Dynamic: author
|
|
28
|
+
Dynamic: author-email
|
|
29
|
+
Dynamic: classifier
|
|
30
|
+
Dynamic: description
|
|
31
|
+
Dynamic: description-content-type
|
|
32
|
+
Dynamic: home-page
|
|
33
|
+
Dynamic: license
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
36
|
+
|
|
37
|
+
Ai lib.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ai/__init__.py,sha256=E-6wAZjiLRVInABFxrWoqdYlO_t-zVhK8XNfNnaDyzI,22
|
|
2
|
+
ai/config.py,sha256=rlgDA990_SdbNNtoPnB48Gg0gGkEyvxzTdMo6dplpmg,3571
|
|
3
|
+
ai/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
ai/data/augment.py,sha256=05zxXdwiXG1L-eoQd8Uz8f7wyUZhdf-a0w2jDldtrcA,14188
|
|
5
|
+
ai/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
ai/llm/postprocess.py,sha256=37XykIrCleMJ674dqfBovab0_bLy5BMF0lFHuBnSNRQ,3314
|
|
7
|
+
ai/optim/__init__.py,sha256=TJuUat9jw_6bkeQhQ5e5rXMvedV8fUpJj2N6rxwbeFw,21346
|
|
8
|
+
ai/tensor/__init__.py,sha256=gXpXpnD1RLiaVSgKI9KEU4K0Hx6NJ6XZY8GGurqNXF0,542
|
|
9
|
+
ai/trainer/__init__.py,sha256=LuN8w4K-Wu7KbhjuoSr0oki6Iap8YbQWNzLGJjGT3Do,425
|
|
10
|
+
ai/utils/__init__.py,sha256=xGOyk8BzlHyDCNrZlakPfDewRPIXVJOOJd41MYhIDmo,493
|
|
11
|
+
tests/__init__.py,sha256=83Ndli5J42Tcuq58g7r-XLkeebJaKlazC_Gi2fBezE4,108
|
|
12
|
+
ai-1.1.1.dist-info/METADATA,sha256=mo3nRkCsvILXulrRcZwckI2EL1gX_gnqvwuK1gOPJjA,1194
|
|
13
|
+
ai-1.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
ai-1.1.1.dist-info/top_level.txt,sha256=8u8HzGvcQsdPD992eODUIMuDjQ_EdzUnYySgx4Tfw8o,9
|
|
15
|
+
ai-1.1.1.dist-info/RECORD,,
|
ai-1.1.1.dist-info/WHEEL
ADDED
tests/__init__.py
ADDED