Skip to content

Commit b085293

Browse files
authored
Release 0.1.0/resize layers should be more clear (#7)
* Use more utilities, new abstraction * Add layer inspectors * Add errors and utils * Calculate a new layer's size Rather than having to reinstatiate layers, or some sub-optimal traversal of a neural architectures' nn.Modules, just do the math for 'in-size' and 'out-size' for a given layer to make the math easier. * Clarify demo.py * Trim down resize_layers in nn.widen module * Complete resize_layers for #2 Need a 'fit_layers', for when shrinking/pruning clips out too many neuron connections
1 parent 22e5358 commit b085293

File tree

7 files changed

+107
-44
lines changed

7 files changed

+107
-44
lines changed

demo.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@
99
from morph._models import EasyMnist
1010

1111

12+
def random_dataset():
13+
return TensorDataset(torch.randn(2, 28, 28))
14+
1215
def main():
1316
my_model = EasyMnist()
1417
# do one pass through the algorithm
1518
modified = morph.once(my_model)
1619

1720
print(modified) # take a peek at the new layers. You take it from here
1821

19-
my_dataloader = DataLoader(TensorDataset(torch.randn(2, 28, 28)))
22+
my_dataloader = DataLoader(random_dataset)
2023

2124
# get back the class that will do work
2225
morphed = net.Morph(my_model, epochs=5, dataloader=my_dataloader)

morph/_error.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class ValidationError(Exception):
2+
"""Custom error that represents a validation issue, according to internal
3+
system rules
4+
"""
5+
def __init__(self, msg):
6+
super(ValidationError, self).__init__(msg)

morph/_utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from ._error import ValidationError
2+
3+
4+
def check(pred: bool, message='Validation failed'):
5+
if not pred: raise ValidationError(message)
6+
7+
8+
def round(value: float) -> int:
9+
"""Rounds a `value` up to the next integer if possible.
10+
Performs differently from the standard Python `round`
11+
"""
12+
return int(value + .5)

morph/layers/widen.py

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
import torch
44
import torch.nn as nn
55

6-
from ..nn.utils import layer_has_bias
6+
from ..nn.utils import layer_has_bias, redo_layer
7+
from .._utils import check, round
78

89

910
# NOTE: should factor be {smaller, default at all}?
10-
# TODO: Research - is there a better type for layer than nn.Module?
1111
def widen(layer: nn.Module, factor=1.4, in_place=False) -> nn.Module:
1212
"""
1313
Args:
@@ -23,23 +23,18 @@ def widen(layer: nn.Module, factor=1.4, in_place=False) -> nn.Module:
2323
Returns:
2424
A new layer of the base type (e.g. nn.Linear) or `None` if in_place=True
2525
"""
26-
if factor < 1.0:
27-
raise ValueError('Cannot shrink with the widen() function')
28-
if factor == 1.0:
29-
raise ValueError("You shouldn't waste compute time if you're not changing anything")
26+
check(factor > 1.0, "Your call to widen() should be increasing the size of your layers")
3027
# we know that layer.weight.size()[0] is the __output__ dimension in the linear case
3128
output_dim = 0
3229
if isinstance(layer, nn.Linear):
3330
output_dim = layer.weight.size()[0] # FIXME: switch to layer.out_features?
3431
input_dim = layer.weight.size()[1] # FIXME: switch to layer.in_features?
35-
# TODO: other classes, for robustness?
36-
# TODO: Use dictionary look-ups instead, because they're faster?
3732
else:
3833
raise ValueError('unsupported layer type:', type(layer))
3934

4035
logging.debug(f"current dimensions: {(output_dim, input_dim)}")
4136

42-
new_size = round(factor * output_dim + .5) # round up, not down, if we can
37+
new_size = round(factor * output_dim) # round up, not down, if we can
4338

4439
# We're increasing layer width from output_dim to new_size, so let's save that for later
4540
size_diff = new_size - output_dim
@@ -56,20 +51,26 @@ def widen(layer: nn.Module, factor=1.4, in_place=False) -> nn.Module:
5651

5752
# TODO: cleanup duplication? Missing properties that will effect usability?
5853
if in_place:
59-
layer.out_features = new_size
60-
layer.weight = p_weights
61-
layer.bias = p_bias
62-
logging.warning(
63-
'Using experimental "in-place" version. May have unexpected affects on activation.'
64-
)
54+
write_layer_properties(layer, new_size, p_weights, p_bias)
6555
return layer
6656
else:
67-
print(f"New shape = {expanded_weights.shape}")
68-
l = nn.Linear(*expanded_weights.shape[::-1], bias=utils.layer_has_bias(layer))
69-
l.weight = p_weights
70-
l.bias = p_bias
57+
logging.debug(f"New shape = {expanded_weights.shape}")
58+
new_input, new_output = expanded_weights[1], expanded_weights[0]
59+
l = redo_layer(layer, new_in=new_input, new_out=new_output)
60+
write_layer_properties(layer, new_size=None, new_weights=p_weights, new_bias=p_bias)
61+
7162
return l
7263

64+
def write_layer_properties(layer, new_size, new_weights, new_bias):
65+
"""Assigns properties to this `layer`, making the changes on a model in-line
66+
"""
67+
if new_size: layer.out_features = new_size
68+
if new_weights: layer.weight = new_weights
69+
if new_bias: layer.bias = new_bias
70+
logging.warning(
71+
'Using experimental "in-place" version. May have unexpected affects on activation.'
72+
)
73+
7374

7475
def _expand_bias_or_weight(t: nn.Parameter, increase: int) -> torch.Tensor:
7576
"""Returns a tensor of shape `t`, with padding values drawn from a Guassian distribution

morph/nn/shrink.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from morph.layers.sparse import percent_waste
2+
from morph._utils import check, round
3+
from morph.nn.utils import in_dim, out_dim
4+
5+
import torch.nn as nn
6+
7+
8+
def calc_reduced_size(layer: nn.Module) -> (int, int):
9+
"""Calculates the reduced size of the layer, post training (initial or morphed re-training)
10+
so the layers can be resized.
11+
"""
12+
# TODO: remove this guard when properly we protect access to this function
13+
check(
14+
type(layer) == nn.Conv2d or type(layer) == nn.Linear,
15+
'Invalid layer type: ' + type(layer))
16+
17+
percent_keep = 1 - percent_waste(layer)
18+
shrunk_in, shrunk_out = percent_keep * in_dim(layer), percent_keep * out_dim(layer)
19+
20+
return round(shrunk_in), round(shrunk_out)

morph/nn/utils.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import torch.nn as nn
22

33
from morph.nn._types import type_name, type_supported
4+
from morph._utils import check
45

56
from typing import List, Tuple, TypeVar
67

@@ -12,15 +13,14 @@
1213
def group_layers_by_algo(children_list: CL) -> ML:
1314
"""Group the layers into how they will be acted upon by my implementation of the algorithm:
1415
1. First child in the list (the "input" layer)
15-
2. Slice of all the child, those that are not first nor last
16+
2. Slice of all the children, those that are not first nor last
1617
3. Last child in the list (the "output" layer)
1718
"""
1819

1920
list_len = len(children_list)
2021

2122
# validate input in case I slip up
22-
if list_len < 1:
23-
raise ValueError('Invalid argument:', children_list)
23+
check(list_len > 1, 'Your children_list must be more than a singleton')
2424

2525
if list_len <= 2:
2626
return children_list # interface?
@@ -43,6 +43,31 @@ def make_children_list(children_or_named_children):
4343
return [c for c in children_or_named_children]
4444

4545

46+
#################### LAYER INSPECTION ####################
47+
48+
49+
def in_dim(layer: nn.Module) -> int:
50+
check(type_supported(layer))
51+
52+
if layer_is_linear(layer):
53+
return layer.in_features
54+
elif layer_is_conv2d(layer):
55+
return layer.in_channels
56+
else:
57+
raise RuntimeError('Inspecting on unsupported layer')
58+
59+
60+
def out_dim(layer: nn.Module) -> int:
61+
check(type_supported(layer))
62+
63+
if layer_is_linear(layer):
64+
return layer.out_features
65+
elif layer_is_conv2d(layer):
66+
return layer.out_channels
67+
else:
68+
raise RuntimeError('Inspecting on unsupported layer')
69+
70+
4671
#################### NEW LAYERS ####################
4772

4873

morph/nn/widen.py

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,42 @@
11
import torch.nn as nn
22

3-
# TODO: nope. This is really long
4-
from morph.nn.utils import group_layers_by_algo, layer_is_conv2d, make_children_list, new_input_layer, new_output_layer, redo_layer, type_name, type_supported
3+
import logging
54

5+
from morph.nn.utils import group_layers_by_algo, make_children_list, out_dim, redo_layer
6+
from morph._utils import round
7+
from morph.nn._types import type_name, type_supported
68

7-
# TODO: refactor out width_factor
8-
def resize_layers(net: nn.Module):
9+
10+
def resize_layers(net: nn.Module, width_factor: float = 1.4) -> nn.Module:
11+
"""Perform a uniform layer widening, which increases the output dimension for
12+
fully-connected layers and the number of filters for convolutional layers.
13+
"""
914

1015
old_layers = make_children_list(net.named_children())
1116
(first_name, first_layer), middle, last = group_layers_by_algo(old_layers)
1217

1318
first_layer_output_size = first_layer.out_channels # count of the last layer's out features
1419

15-
new_out_next_in = int(first_layer_output_size * 1.4)
20+
new_out_next_in = round(first_layer_output_size * width_factor)
1621

1722
# NOTE: is there a better way to do this part? Maybe nn.Sequential?
1823
network = nn.Module() # new network
1924

20-
network.add_module(
21-
first_name,
22-
new_input_layer(first_layer, type_name(first_layer), out_dim=new_out_next_in))
25+
network.add_module(first_name, redo_layer(first_layer, new_out=new_out_next_in))
2326

24-
# TODO: format and utilize the functions in utils for making layers
2527
for name, child in middle:
26-
# otherwise, we want to
27-
_t = type_name(child)
28-
if type_supported(_t):
28+
if type_supported(type_name(child)):
2929

30-
new_out = 0
31-
# TODO: look up performance on type name access. Maybe this could just be layer_is_conv2d(child)
32-
if layer_is_conv2d(_t):
33-
new_out = int(child.out_channels * 1.4)
34-
else: # type_name == 'Linear'
35-
new_out = int(child.out_features * 1.4)
30+
new_out = round(out_dim(child) * width_factor)
3631

3732
new_layer = redo_layer(child, new_in=new_out_next_in, new_out=new_out)
3833
new_out_next_in = new_out
3934
network.add_module(name, new_layer)
35+
else:
36+
logging.warning(f"Encountered a non-resizable layer: {type(child)}")
37+
network.add_module(name, child)
4038

4139
last_name, last_layer = last
42-
network.add_module(
43-
last_name,
44-
new_output_layer(last_layer, type_name(last_layer), in_dim=new_out_next_in))
40+
network.add_module(last_name, redo_layer(last_layer, new_in=new_out_next_in))
4541

4642
return network

0 commit comments

Comments
 (0)