diff --git a/ae_wavenet.ipynb b/ae_wavenet.ipynb index 1335252..6cde6e9 100644 --- a/ae_wavenet.ipynb +++ b/ae_wavenet.ipynb @@ -9,6 +9,13 @@ "import wavenet as wn" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": 3, @@ -54,6 +61,13 @@ "outputs": [], "source": [] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": null, diff --git a/rfield.py b/rfield.py index 57267b5..e42f2f2 100644 --- a/rfield.py +++ b/rfield.py @@ -1,6 +1,23 @@ # An instance of this class represents the coordinate relationship between an # output element and its input receptive field. +import fractions +import numpy as np + +class _Stats(object): + '''Describes a 1D tensor of positioned elements''' + def __init__(self, l_pad, r_pad, size, spc, vspc, l_pos, r_pos): + self.l_pad = l_pad + self.r_pad = r_pad + self.size = size + self.spc = spc + self.vspc = vspc + self.l_pos = l_pos + self.r_pos = r_pos + def __repr__(self): + return 'l_pad: {}, r_pad: {}, size: {}, spc: {}, vspc: {}, l_pos:' \ + ' {}, r_pos: {}'.format(self.l_pad, self.r_pad, self.size, self.spc, + self.vspc, self.l_pos, self.r_pos) class FieldOffset(object): ''' @@ -15,17 +32,15 @@ def __init__(self, padding=(0, 0), wing_sizes=None, filter_sz=None, stride=1, is_downsample=True, parent=None): '''Converts a padding strategy into actual padding values. ''' - self.stride = stride self.parent = parent - self.is_downsample = is_downsample - self.left_pad = padding[0] - self.right_pad = padding[1] + self.l_pad = padding[0] + self.r_pad = padding[1] # stride_ratio is ratio of output spacing to input spacing - if self.is_downsample: - self.stride_ratio = self.stride + if is_downsample: + self.stride_ratio = fractions.Fraction(stride, 1) else: - self.stride_ratio = 1.0 / self.stride + self.stride_ratio = fractions.Fraction(1, stride) if isinstance(wing_sizes, tuple): self.left_wing_sz = wing_sizes[0] @@ -39,22 +54,31 @@ def __init__(self, padding=(0, 0), wing_sizes=None, filter_sz=None, 'wing_sizes tuple or filter_sz (integer)') def __repr__(self): - return 'left_wing_sz: {}, right_wing_sz: {}, stride: {}, is_downsample: {}'.format( - self.left_wing_sz, self.right_wing_sz, self.stride, self.is_downsample) + return 'left_wing_sz: {}, right_wing_sz: {}, stride_ratio: {}'.format( + self.left_wing_sz, self.right_wing_sz, self.stride_ratio) def _input_size(self, output_size): '''calculate the input_size needed to produce the desired output_size''' - if self.is_downsample: - input_size = (output_size - 1) * self.stride + 1 \ - + self.left_wing_sz + self.right_wing_sz \ - - self.left_pad - self.right_pad + const = self.left_wing_sz + self.right_wing_sz - self.l_pad - self.r_pad + if self.stride_ratio.denominator == 1: + input_size = (output_size - 1) * self.stride_ratio.numerator + 1 + const else: - input_size = (self.stride - 1 - self.left_pad \ - - self.right_pad + output_size \ - + self.left_wing_sz + self.right_wing_sz) \ - // self.stride + input_size = (self.stride_ratio.denominator + output_size - 1 + const) \ + // self.stride_ratio.denominator return input_size + def _input_stride(self, output_stride_r): + return output_stride_r / self.stride_ratio + + def _input_spacing(self, out_spc, out_vspc): + if self.stride_ratio > 1: + in_spc = out_vspc / self.stride_ratio + in_vspc = in_spc + else: + in_spc = out_vspc + in_vspc = in_spc / self.stride_ratio + return in_spc, in_vspc + def input_size(self, output_size): '''calculate input_size size needed to produce the desired output_size. recurses up to parents until the end.''' @@ -65,60 +89,85 @@ def input_size(self, output_size): return self.parent.input_size(this_input_size) def _local_bounds(self): + '''For this transformation, calculates the offset between the first elements + of the input and the output, assuming output element spacing of 1. + Return values must be adjusted by the actual output element spacing. ''' - ''' - padded_stride = 1 if self.is_downsample else self.stride_ratio - l_ind = self.left_wing_sz - self.left_pad - r_ind = self.right_wing_sz - self.right_pad - l_off = l_ind * padded_stride - r_off = r_ind * padded_stride + # spacing is the distance between any two consecutive elements in this + # tensor, including padding elements + #input_spacing = max(fractions.Fraction(1, 1), self.stride_ratio) + input_spacing = 1 + l_ind = self.left_wing_sz - self.l_pad + r_ind = self.right_wing_sz - self.r_pad + l_off = l_ind * input_spacing + r_off = r_ind * input_spacing return l_off, r_off - def _bounds(self): - if self.parent is None: - l_in_pos, r_in_pos, in_stride = 0, 0, 1 - else: - l_in_pos, r_in_pos, in_stride = self.parent._bounds() - + def _geometry(self, out_size, out_spc, out_vspc, l_out_pos, r_out_pos, accu=None): l_off, r_off = self._local_bounds() - - # Accumulate offsets - l_out_pos = l_in_pos + l_off * in_stride - r_out_pos = r_in_pos + r_off * in_stride - out_stride = self.stride_ratio * in_stride - - return l_out_pos, r_out_pos, out_stride - - def bounds(self): - '''Considering the full chain of transformations, calculate the number - of positions the output boundaries are inset from the input boundaries. - ''' - l_pos, r_pos, stride = self._bounds() - return l_pos, r_pos - - def _min_stride(self, pre_stride, min_stride): - '''Traverse the chain of transformations, recording the minimal stride - seen''' - cur_stride = pre_stride / self.stride_ratio - min_stride = min(cur_stride, min_stride) + in_size = self._input_size(out_size) + in_spc, in_vspc = self._input_spacing(out_spc, out_vspc) + l_in_pos = l_out_pos + l_off * in_spc + r_in_pos = r_out_pos + r_off * in_spc + + if accu is not None: + assert isinstance(accu, list) + if len(accu) == 0: + first_stats = _Stats(0, 0, out_size, out_spc, out_vspc, + l_out_pos, r_out_pos) + accu.append(first_stats) + + stats = _Stats(self.l_pad, self.r_pad, in_size, in_spc, + in_vspc, l_in_pos, r_in_pos) + accu.append(stats) if self.parent is None: - return min_stride + if accu is None: + return in_size, l_in_pos, r_in_pos + else: + return accu else: - return self.parent._min_stride(cur_stride, min_stride) + return self.parent._geometry(in_size, in_spc, in_vspc, l_in_pos, r_in_pos, accu) + def geometry(self, out_size): + '''calculate in_size, left_pos, right_pos needed for the chain of transformations + to produce the desired out_size''' + return self._geometry(out_size, 1, 1, 0, 0, None) def print(self, output_size, lpad_rpad_inpad_data_sym='<>-*'): - '''print a symbolic stack of units with the given output_size''' - lpad, rpad, ipad, data = list(lpad_rpad_inpad_data_sym) - span = self.input_size(output_size) - min_stride = self._min_stride(1, 1) - - - - - - - + '''pretty print a symbolic stack of units with the given output_size''' + lpad, rpad, ipad, data = list(lpad_rpad_inpad_data_sym) + stats = self._geometry(output_size, 1, 1, 0, 0, []) + + lcm_denom = fractions.Fraction(np.lcm.reduce(tuple(s.spc.denominator for s in stats))) + for s in stats: + s.spc *= lcm_denom + s.vspc *= lcm_denom + s.l_pos *= lcm_denom + s.r_pos *= lcm_denom + + + def _dilate_str(string, space_sym, spacing): + space_str = space_sym * (spacing - 1) + return string[0] + ''.join(space_str + s for s in string[1:]) + + max_l_pos = max(s.l_pos for s in stats) + max_r_pos = max(s.r_pos for s in stats) + + for st in stats: + indent = max_l_pos - st.l_pos + dedent = max_r_pos - st.r_pos + + assert indent == round(indent) + assert st.spc == round(st.spc) + assert st.vspc == round(st.vspc) + assert st.r_pos == round(st.r_pos) + core = _dilate_str(data * st.size, ipad, round(st.vspc / st.spc)) + body = lpad * st.l_pad + core + rpad * st.r_pad + body_dil = _dilate_str(body, ' ', round(st.spc)) + + s = ' ' * round(indent) + body_dil + ' ' * round(dedent) + '|' + print(s) + return stats diff --git a/rfield_notes.txt b/rfield_notes.txt index 5625924..f0c7ec5 100644 --- a/rfield_notes.txt +++ b/rfield_notes.txt @@ -45,13 +45,41 @@ the input, which in general is both padded (self.left_pad) and dilated (self.str But, we don't want to confuse the stride of the output with the coordinate system that - left_ind stride pad_stride sr pos pos_formula -C * * * 1 3/2x 1/2 pos(B) + left_ind(C) * -B * - * - * 1 3x 3 2 pos(A) + left_ind(B) * pad_stride(A) -A * * * * * * * * * * 0 x 1 0 0 + left_ind spacing pad_stride sr pos pos_formula +C * * * 1 1 1/2 pos(B) + left_ind(C) * +B * - * - * 1 1 3 2 pos(A) + left_ind(B) * pad_stride(A) +A * * * * * * * * * * 0 2/3 1 0 0 | 0 + +Central Idea: + +An input tensor of elements is transformed in a series of steps, each producing +a new tensor. At each step, all of the tensor elements have associated with +them a physical coordinate (such as 'x'), and are regularly spaced. + +The difference between consecutive elements of a given tensor is called its +'spacing'. But, tensors may have two types of elements: 'value elements' and +'padding elements'. The physical distance between a pair of consecutive value +elements (which, there may be intervening padding elements between this pair of +value elements) is called 'value_spacing'. The physical distance between a +pair of any two consecutive elements, ignoring their type, is called just +'spacing'. + +A transformation is characterized by its stride_ratio, which equals +out.value_spacing / in.value_spacing. + + + +Here, there are a few important concepts. First is the spacing between elements in +spacing_ratio. This equals +output_spacing / input_spacing. Note, though that there are two kinds of spacing +for each tensor: unpadded_spacing, and padded_spacing. + + + + The initial input spacing should be such that the output stride is a whole number. So, this reduces to the problem of finding the multiple for the overall stride that brings the output to a whole number. Actually, not only that, we need every intermediate @@ -85,17 +113,26 @@ So, the print function will not print on the way down. It will maintain a densi There are several tasks needed. But, mainly, we need to accumulate the following data for every layer: -left_pad, right_pad, stride, size, left, right +left_pad, right_pad, stride, input_size, local_bounds -all of these are available in one form or another. +left_pad and right_pad are easy +And, maintain the min_stride along with that. -Another difficulty is, the recursion logic is intermixed with the +At the end, start printing. Simple enough. + +all of these are available in one form or another. - +Another difficulty is, the recursion logic is intermixed with the measurement +logic. But, for the print function we need to use the measurement logic +in different ways. +So, what is the overall blueprint for print? +As we recurse, collect +Another issue is that of keeping track of strides. Each operation imposes a stride ratio factor +to the existing stride. Some intermediate calculations involve fractional