Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mylstm elichika (WIP) #210

Merged
merged 8 commits into from
May 13, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions elichika/elichika/chainer2onnx.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ def compile_model(model, inputs) -> 'ONNXModel':
oc.chainer_f_converter[F.concat] = fb.convert_concat
oc.chainer_f_converter[F.max_pooling_2d] = fb.convert_max_pooling_2d
oc.chainer_f_converter[F.resize_images] = fb.convert_resize_images
oc.chainer_f_converter[F.tanh] = fb.convert_tanh
oc.chainer_f_converter[F.sigmoid] = fb.convert_sigmoid
oc.chainer_f_converter[F.broadcast_to] = fb.convert_broadcast_to
oc.chainer_f_converter[F.expand_dims] = fb.convert_expand_dims

if int(chainer.__version__[0]) >= 6:
oc.chainer_f_converter[F.roi_max_pooling_2d] = fb.convert_roi_max_pooling_2d
Expand Down
43 changes: 40 additions & 3 deletions elichika/elichika/functions_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,21 @@ def convert_relu(onnx_graph, node):
[node.inputs[0]],
[node.outputs[0]],
name=str(node.lineprop))
return

def convert_tanh(onnx_graph, node):
onnx_graph.add_node('Tanh',
[node.inputs[0]],
[node.outputs[0]],
name=str(node.lineprop))
return

def convert_sigmoid(onnx_graph, node):
onnx_graph.add_node("Sigmoid",
[node.inputs[0]],
[node.outputs[0]],
name=str(node.lineprop))
return

def convert_softmax(onnx_graph, node):
onnx_graph.add_node(
Expand Down Expand Up @@ -125,7 +139,7 @@ def convert_softmax_cross_entropy(onnx_graph, node):
ignore_label = oc.try_get_attribute(node.args.keywords['ignore_label'])
reduce = oc.try_get_attribute(node.args.keywords['reduce'])
enable_double_backprop = oc.try_get_attribute(node.args.keywords['enable_double_backprop'])

assert normalize # TODO(hamaji): Not supported yet.
assert cache_score # TODO(hamaji): Not supported yet.
assert class_weight is None # TODO(hamaji): Not supported yet.
Expand Down Expand Up @@ -208,12 +222,12 @@ def convert_unpooling_2d(onnx_graph, node : 'nodes.NodeCall'):
pad = oc.try_get_attribute(node.args.keywords['pad'])
outsize = oc.try_get_attribute(node.args.keywords['outsize'])
cover_all = oc.try_get_attribute(node.args.keywords['cover_all'])

assert(stride is None) # TODO(hamaji): Not supported yet.
assert(pad == 0) # TODO(hamaji): Not supported yet.
assert(outsize is None) # TODO(hamaji): Not supported yet.
assert(cover_all is False) # TODO(hamaji): Not supported yet.

scales = np.array([1, 1] + list(_pair(ksize)), dtype=np.float32)
scales_ = oc.ONNXValue(onnx_graph, scales, [node, '/Scale'], is_constant = True)
onnx_graph.add_node(
Expand Down Expand Up @@ -361,3 +375,26 @@ def _pair(x):
spatial_scale=oc.try_get_attribute(spatial_scale.value),
sampling_ratio=_pair(oc.try_get_attribute(sampling_ratio.value)))
return

def convert_broadcast_to(onnx_graph, node):
node_ = node

shape = oc.ONNXValue(onnx_graph, node_.args.keywords['shape'])
onnx_graph.add_node(
"Expand",
[node_.inputs[0], shape.create_tensor()],
node_.outputs,
str(node.lineprop))
return


def convert_expand_dims(onnx_graph, node):
node_ = node
axis = oc.try_get_attribute(node_.args.keywords['axis'])
onnx_graph.add_node(
'Unsqueeze',
[node_.inputs[0]],
node_.outputs,
str(node.lineprop),
axes=[int(axis)])
return
32 changes: 32 additions & 0 deletions elichika/elichika/onnx_converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,38 @@ def generate_tensors(values_):

onnx_graph.nodes.append(onnx_node)

if isinstance(node, nodes.NodeLen):
node_ = node # type: nodes.NodeLen

onnx_node = oh.make_node(
'ChainerGenericLen',
[value2onnx_parameter[node_.iter_value].onnx_name],
[value2onnx_parameter[node_.outputs[0]].onnx_name],
str(node.lineprop)
)

onnx_graph.nodes.append(onnx_node)

if isinstance(node, nodes.NodeTensorAttribute):
node_ = node # type: nodes.NodeShape

if node_.type == 'shape':
onnx_node = oh.make_node(
'ChainerSequenceSeparate',
[value2onnx_parameter[node_.value].onnx_name],
[value2onnx_parameter[node_.outputs[0]].onnx_name],
str(node.lineprop)
)
elif node_.type == 'size':
onnx_node = oh.make_node(
'Size',
[value2onnx_parameter[node_.value].onnx_name],
[value2onnx_parameter[node_.outputs[0]].onnx_name],
str(node.lineprop)
)

onnx_graph.nodes.append(onnx_node)

if isinstance(node, nodes.NodeFor):
node_ = node # type: nodes.NodeFor

Expand Down
9 changes: 8 additions & 1 deletion elichika/elichika/parser/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,16 @@ def ret_tuple():
add_chainer_funtion('matmul', F.matmul)
add_chainer_funtion('max_pooling_2d', F.max_pooling_2d)
add_chainer_funtion('resize_images', F.resize_images)
add_chainer_funtion('tanh', F.tanh)
add_chainer_funtion('sigmoid', F.sigmoid)
add_chainer_funtion('broadcast_to', F.broadcast_to)
add_chainer_funtion('expand_dims', F.expand_dims)

if int(chainer.__version__[0]) >= 6:
add_chainer_funtion('roi_max_pooling_2d', F.roi_max_pooling_2d)
add_chainer_funtion('roi_average_pooling_2d', F.roi_average_pooling_2d)
add_chainer_funtion('roi_max_align_2d', F.roi_max_align_2d)

add_chainer_funtion('roi_average_align_2d', F.roi_average_align_2d)

default_module.set_default_value(chainer_functions_module_name, f_dict)
Expand Down Expand Up @@ -134,6 +138,9 @@ def ret_tuple():
m_list = values.FuncValue(functions_builtin.ListFunction(), None)
default_module.set_default_value('list', values.ValueRef(m_list))

m_len = values.FuncValue(functions_builtin.LenFunction(), None)
default_module.set_default_value('len', values.ValueRef(m_len))

model_inst = values.parse_instance(default_module, '', model)
forward_func = model_inst.try_get_and_store_obj('forward')

Expand Down
17 changes: 17 additions & 0 deletions elichika/elichika/parser/functions_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,23 @@ def vcall(self, module: 'Field', graph: 'Graph', inst: 'values.ValueRef', args:
return values.ValueRef(value)


class LenFunction(functions.FunctionBase):
def __init__(self):
super().__init__()
self.name = 'len'

def vcall(self, module: 'Field', graph: 'Graph', inst: 'values.ValueRef', args: 'functions.FunctionArgInput', line=-1):
node = nodes.NodeLen(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add assert len(args.inputs) == 1 or something just in case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary I think.

args.inputs[0].get_value(), # TODO: Check this.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate, please? It'll be great if you can give info about what should be done to remove this TODO comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was a non-issue. I was concerned if a new node was necessary here. As @durswd pointed out, this can be done through NodeCall itself.

line
)
graph.add_node(node)
value = values.NumberValue(None)
value.name = '@F.{}.{}'.format(line, self.name)
node.set_outputs([value])
return values.ValueRef(value)


class ListFunction(functions.FunctionBase):
def __init__(self):
super().__init__()
Expand Down
27 changes: 25 additions & 2 deletions elichika/elichika/parser/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def remove_ref(value):
converted = {}

ret = functions.FunctionArgValueInput()

for v in value.inputs:
converted_v = remove_ref(v)
ret.inputs.append(converted_v)
Expand All @@ -58,7 +58,7 @@ def remove_ref(value):
for k,v in value.keywords.items():
if v in converted.keys():
keywords_[k] = converted[v]
else:
else:
keywords_[k] = remove_ref(v)
ret.keywords = keywords_
return ret
Expand Down Expand Up @@ -401,3 +401,26 @@ def __init__(self, classtype, value, line=-1):

def __str__(self):
return 'Convert({},{})'.format(self.classtype, self.lineprop)

class NodeLen(Node):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that it can be replaced into NodeCall.

def __init__(self, iter_value, line=-1):
super().__init__(line)
iter_value = remove_ref(iter_value)

self.iter_value = iter_value
self.append_inputs(self.iter_value)

def __str__(self):
return 'Len({})'.format(self.lineprop)

class NodeTensorAttribute(Node):
def __init__(self, type, value, line=-1):
super().__init__(line)
value = remove_ref(value)

self.value = value
self.type = type
self.append_inputs(self.value)

def __str__(self):
return 'TensorAttribute({},{})'.format(self.type, self.lineprop)
9 changes: 5 additions & 4 deletions elichika/elichika/parser/values.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,8 @@ def get_outputs(self) -> 'List[FieldOutput]':
ret = []

for key, att in self.attributes.items():

if att.name in ['shape', 'size']:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sawada-san, could you double-check this is the right approach to handle these attribtues here? If this is right, I guess we need at least a comment to explain these will be handled in other code (vevaluator.py:114?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this implementation causes error as follows (it is very rare case).

class A:
def init(self):
self.size = 'Size'

size, shape is regarded as bultin property.
I implemented shape in NDArray

https://github.com/pfnet-research/chainer-compiler/blob/master/elichika/elichika/parser/functions_ndarray.py#L126

it is redundant. But it can support many cases.
Do you change it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't aware that there was an implementation to handle NDArrayFunctions. In that case, there is a bug in the implementation then. It doesn't handle the following case.

xs = np.random.rand(3,4,5)
[x.shape for x in xs]

I'm sure your implementation can be extended to fix this though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

continue
# instance or func
if isinstance(att.get_ref().get_value(), Instance) or isinstance(att.get_ref().get_value(), FuncValue) or isinstance(att.get_ref().get_value(), ModuleValue):
continue
Expand Down Expand Up @@ -512,7 +513,7 @@ def __init__(self):

def has_constant_value(self) -> 'bool':
return self.internal_value is not None

def is_all_constant_values(self, is_ref_enabled = False) -> 'bool':
return self.internal_value is not None

Expand All @@ -539,7 +540,7 @@ def __init__(self):

def has_constant_value(self) -> 'bool':
return True

def is_all_constant_values(self, is_ref_enabled = False) -> 'bool':
return True

Expand Down Expand Up @@ -623,7 +624,7 @@ def is_all_constant_values(self, is_ref_enabled = False) -> 'bool':
else:
if not v.is_all_constant_values(is_ref_enabled):
return False
return True
return True

def __str__(self):
return self.name + '(Tp{})'
Expand Down
25 changes: 16 additions & 9 deletions elichika/elichika/parser/vevaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,16 @@ def veval_ast_attribute(astc : 'AstContext', local_field : 'values.Field', graph
if gotten_obj is not None:
return value_ref.get_field().get_attribute(astc.nast.attr, from_module)

if option.eval_as_written_target:
if option is not None and option.eval_as_written_target:
return attr


if attr.name in ['shape', 'size']:
node = nodes.NodeTensorAttribute(attr.name, value_ref, value_ref.get_value())
value = values.TupleValue()
node.set_outputs([value])
graph.add_node(node)
return values.ValueRef(value)

# value is unknown
if config.show_warnings:
print('Assigning value is not found in L.{}'.format(astc.lineno))
Expand Down Expand Up @@ -155,7 +162,7 @@ def return_value_or_ref(obj : 'value.Object'):
if not isinstance(value_obj.get_value(), values.TupleValue):
# TODO fix it
assert(False) # not supported

for i in range(len(targets)):
node_assign = nodes.NodeAssign(targets[i], value_obj.get_value().get_constant_value()[i], astc.lineno)
targets[i].revise(try_get_ref(value_obj.get_value().get_constant_value()[i],'assign', lineprop))
Expand Down Expand Up @@ -320,7 +327,7 @@ def veval_ast_if(astc : 'AstContext', local_field : 'values.Field', graph : 'Gra
input_value = None
true_input_body_value = None
false_input_body_value = None

if 'true_input_value' in v:
input_value = v['true_input_value']
elif 'false_input_value' in v:
Expand Down Expand Up @@ -366,7 +373,7 @@ def veval_ast_if(astc : 'AstContext', local_field : 'values.Field', graph : 'Gra
outputs.append(output_value)
true_graph.add_output_value(true_output_body_value)
false_graph.add_output_value(false_output_body_value)

if field.get_attribute(name).has_obj():
field.get_attribute(name).get_ref().revise(output_value)
else:
Expand Down Expand Up @@ -544,7 +551,7 @@ def veval_ast_listcomp(astc : 'AstContext', local_field : 'values.Field', graph
target_value = values.Value()
target_ref = values.ValueRef(target_value)
node_forgen.set_outputs([target_value])

local_field.get_attribute(target_name).revise(target_ref)

body_graph.add_node(node_forgen)
Expand Down Expand Up @@ -577,7 +584,7 @@ def veval_ast_listcomp(astc : 'AstContext', local_field : 'values.Field', graph

# default output
outputs.append(functions.generate_value_with_same_type(iter_value))

# generate pairs
value_pairs = {}
for v in value_inputs:
Expand Down Expand Up @@ -810,7 +817,7 @@ def veval_ast_tuple(astc : 'AstContext', local_field : 'values.Field', graph : '
node = nodes.NodeGenerate('Tuple', vs.copy(), line=lineprop)
node.set_outputs([tuple_value])
graph.add_node(node)

return values.ValueRef(tuple_value)

def veval_ast_list(astc : 'AstContext', local_field : 'values.Field', graph : 'Graph'):
Expand Down Expand Up @@ -914,7 +921,7 @@ def veval_ast_for(astc : 'AstContext', local_field : 'values.Field', graph : 'Gr

# default output
outputs.append(functions.generate_value_with_same_type(iter_value))

# generate pairs
value_pairs = {}
for v in value_inputs:
Expand Down
13 changes: 12 additions & 1 deletion elichika/tests/model/MyLSTM.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ def forward(self, xs, h, c, mask):
#h = self.initial_h
#c = self.initial_c
inputs = F.pad_sequence(xs)
x = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a TODO comment to explain this is a workaround for the bug and these assignments should be removed?

input = None
gate = None
i = None
o = None
f = None
nc = None
nh = None
m = None
pmask = None
nmask = None
for time in range(max_len):
x = inputs[:, time]
input = F.concat((x, h), axis=1)
Expand Down Expand Up @@ -83,7 +94,7 @@ def main():
num_vocabs = 10
num_hidden = 5

model_fn = lambda: MyLSTM(num_hidden, batch_size, sequence_length)
model_fn = MyLSTM(num_hidden, batch_size, sequence_length)

labels, lengths = sequence_utils.gen_random_sequence(
batch_size, sequence_length, num_vocabs)
Expand Down
Loading