[4807fa]: / dl / utils / gen_conv_params.py

Download this file

189 lines (179 with data), 7.6 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
__all__ = ['squaredims', 'cal_padding_size', 'get_iter', 'gen_conv_params', 'get_itemset',
'join_dict', 'reduce_projections']
import functools
def assert_int_or_list(n):
assert isinstance(n, int) or (isinstance(n, (list, tuple)) and len(n) > 0), (
'n should be an int or a non-empty list or tuple, but is {0}'.format(n))
def squaredims(n):
"""Given an integer, calculate the factorization n = a * b, where |a - b| <= 2, a <= b
>>> squaredims(100)
(10, 10)
"""
import math
a = math.floor(math.sqrt(n))
b = math.ceil(n / a)
return a, b
def cal_padding_size(a, kernel_size=3, stride=2):
"""Calculate padding size (equal on both sides)
Args:
a: int or tuple; input size ()
>>> cal_padding_size((10), 4)
0
>>> cal_padding_size((10,), 4)
(0,)
>>> cal_padding_size(10, 4)
0
>>> cal_padding_size((10, 8))
(1, 1)
"""
assert_int_or_list(a)
if not isinstance(a, int):
p = ()
for i in a:
p = p + (cal_padding_size(i, kernel_size, stride),)
return p
# do not pad if a < kernel_size (in this case change kernel size)
if a < kernel_size:
return 0
if (a - kernel_size + stride) % stride == 0:
return 0
else:
p = stride - (a - kernel_size + stride) % stride
return (p + 1) // 2
def get_iter(k, length=None):
"""Return an iterator for two kinds of input: int, or tuple/list
Args:
k: int or tuple/list
length: int or None; default None. If not None, return the iterator of a list of size length
>>> list(get_iter([1, 2], 4))
[1, 2, 2, 2]
>>> list(get_iter([1], 1))
[1]
>>> list(get_iter(1, 3))
[1, 1, 1]
>>> list(get_iter(2))
[2]
>>> list(get_iter((3, 4, 5)))
[3, 4, 5]
"""
assert_int_or_list(k)
assert length is None or (isinstance(length, int) and length > 0)
if isinstance(k, (tuple, list)):
if length is None:
return iter(k)
else:
if len(k) < length:
k = list(k) + [k[len(k)-1]] * (length - len(k)) # elongate k by repeating its last value
return iter(k[:length])
else:
if length is None:
return iter([k])
else:
return iter([k] * length)
def gen_conv_params(n, kernel_size=3, stride=2, threshold=40):
"""Calculate conv_params;
Args:
n: int or tuple/list; e.g., the dimentionality of 1d or 2d features
kernel_size: int or tuple/list, default 3
stride: int or tuple/list, default 2
threshold: int or tuple/list, default 40; if the number of features n is less than threshold,
then set kernel_size = n
Return:
a list of [kernel_size, stride, padding]
>>> gen_conv_params(10, threshold=1)
[[3, 2, 1], [3, 2, 0], [2, 1, 0]]
>>> gen_conv_params([10, 5], threshold=5)
[[(3, 3), (2, 2), (1, 0)], [(3, 2), (2, 1), (0, 0)], [(2, 1), (1, 1), (0, 0)]]
>>> gen_conv_params((10,1), threshold=5)
[[(3, 1), (2, 1), (1, 0)], [(3, 1), (2, 1), (0, 0)], [(2, 1), (1, 1), (0, 0)]]
"""
[assert_int_or_list(e) for e in [n, kernel_size, stride, threshold]]
if isinstance(n, (tuple, list)):
iter_threshold = get_iter(threshold, len(n))
res = [gen_conv_params(i, kernel_size, stride, next(iter_threshold)) for i in n]
max_depth = max([len(e) for e in res])
return functools.reduce(lambda a, b: [[a[i][0]+(b[i][0],), a[i][1]+(b[i][1],), a[i][2]+(b[i][2],)]
if i<len(b) else [a[i][0]+(1,), a[i][1]+(1,), a[i][2]+(0,)]
for i in range(max_depth)],
res, [[(), (), ()]]*max_depth)
else:
if n < threshold:
return [[n, 1, 0]]
iter_kernel_size = get_iter(kernel_size)
kernel_size = next(iter_kernel_size)
iter_stride = get_iter(stride)
stride = next(iter_stride)
conv_params = []
while n > kernel_size:
p = cal_padding_size(n, kernel_size, stride)
k = n if n < kernel_size else kernel_size
conv_params.append([k, stride, p])
n = (n + 2 * p - k) // stride + 1
try:
kernel_size = next(iter_kernel_size)
except StopIteration:
pass
try:
stride = next(iter_stride)
except StopIteration:
pass
conv_params.append([n, 1, 0])
return conv_params
def get_itemset(keys, dic):
"""Merge elements of dic given keys
The item values of dic should be comparable
>>> get_itemset([1], {1: ['1', 'a']})
['1', 'a']
"""
return sorted(functools.reduce(lambda x, y: set(x).union(y),
[dic[k] for k in keys]))
def join_dict(dict0, dict1):
"""Join two dictionaries. values (type: list) of dict1 is keys of dict0
>>> join_dict({1: [2], 2: [3], 3:[5]}, {1:[1], 2:[6]})
{1: [2]}
>>> join_dict({}, {})
{}
>>> join_dict({'a': ['b', '1']}, {'test': ['a']})
{'test': ['1', 'b']}
"""
return {k: get_itemset(set(v).intersection(dict0.keys()), dict0) for k, v in dict1.items()
if set(v).intersection(dict0.keys())}
def reduce_projections(projections, del_unused_input=True):
"""Given a ordered list of projections, return reduced version that only includes paticipating nodes
Args:
(ordered) list of dictionaries (keys are int, values are list of int)
Return:
projections: a list of lists of int. Since now keys are consecutive int starting from 0,
representing it using a list instead of dict
idx_to_var: list of ints, mapping 0-len(proj) to their original index
"""
import copy
import collections
assert len(projections) > 0, 'projections should be a non-empty list of dictionaries'
assert all([isinstance(proj, (dict, collections.defaultdict)) for proj in projections])
projections = [copy.deepcopy(proj) for proj in projections]
# remove possible empty entries in projections[0]
projections[0] = {k: v for k, v in projections[0].items() if v}
for i in range(1, len(projections)):
projections[i] = {k: set(v).intersection(projections[i-1].keys())
for k, v in projections[i].items()
if set(v).intersection(projections[i-1].keys())}
projections = [proj for proj in projections if proj]
# Delete unused input so that any node in a lower layer
# will "flow" (connect) to the last layer
if del_unused_input:
for i in range(1, len(projections)):
allkeys = get_itemset(projections[i].keys(), projections[i])
projections[i-1] = {k: v for k, v in projections[i-1].items()
if k in allkeys}
input_to_idx = {k: i for i, k in enumerate(get_itemset(projections[0].keys(), projections[0]))}
var_to_idx = [{k: i for i, k in enumerate(sorted(proj.keys()))} for proj in projections]
projections = [{var_to_idx[i][k]: sorted([input_to_idx[v] for v in proj[k]]) if i == 0
else sorted([var_to_idx[i-1][v] for v in proj[k]])
for k in sorted(proj.keys())}
for i, proj in enumerate(projections)]
idx_to_var = [sorted(m.keys()) for m in [input_to_idx] + var_to_idx]
return projections, idx_to_var
if __name__ == '__main__':
import doctest
doctest.testmod()