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

[SYSTEMDS-3426] Python NN Builtin components #1848

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 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
79 changes: 79 additions & 0 deletions src/main/python/systemds/operator/nn_nodes/affine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# -------------------------------------------------------------
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# -------------------------------------------------------------
import os

from systemds.context import SystemDSContext
from systemds.operator import Matrix, Source, MultiReturn
from systemds.utils.helpers import get_path_to_script_layers


class Affine:
_sds_context: SystemDSContext
_source: Source
_X: Matrix
weight: Matrix
bias: Matrix

def __init__(self, d, m, seed=-1):
"""
d: number of features
m: number of neuron
"""
self._sds_context = SystemDSContext()
path = get_path_to_script_layers()
path = os.path.join(path, "affine.dml")
self._source = self._sds_context.source(path, "affine")
self.weight = Matrix(self._sds_context, '')
self.bias = Matrix(self._sds_context, '')

params_dict = {'D': d, 'M': m, 'seed': seed}
out = [self.weight, self.bias]
op = MultiReturn(self._sds_context, "affine::init", output_nodes=out, named_input_nodes=params_dict)
self.weight._unnamed_input_nodes = [op]
self.bias._unnamed_input_nodes = [op]
op._source_node = self._source


def forward(self, X):
"""
X: input matrix
return out: output matrix
"""
self._X = X
out = self._source.forward(X, self.weight, self.bias)
return out

def backward(self, dout):
"""
dout: gradient of output, passed from the upstream
return dX, dW,db: gradient of input, weights and bias, respectively
"""
params_dict = {'dout': dout, 'X': self._X, 'W': self.weight, 'b': self.bias}
dX = Matrix(self._sds_context, '')
dW = Matrix(self._sds_context, '')
db = Matrix(self._sds_context, '')
out = [dX, dW, db]
op = MultiReturn(self._sds_context, "affine::backward", output_nodes=out, named_input_nodes=params_dict)
dX._unnamed_input_nodes = [op]
dW._unnamed_input_nodes = [op]
db._unnamed_input_nodes = [op]
op._source_node = self._source
return dX, dW, db
54 changes: 54 additions & 0 deletions src/main/python/systemds/operator/nn_nodes/relu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# -------------------------------------------------------------
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# -------------------------------------------------------------
import os.path

from systemds.context import SystemDSContext
from systemds.operator import Matrix, Source
from systemds.utils.helpers import get_path_to_script_layers


class ReLU:
_sds_context: SystemDSContext
_source: Source
_X: Matrix

def __init__(self):
self._sds_context = SystemDSContext()
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not allowed since it starts up a new JVM.
just remove it and the variable for SDS.

we can on first call extract the context from the variable X on a forward or on a Backward pass.

path = get_path_to_script_layers()
path = os.path.join(path, "relu.dml")
self._source = self._sds_context.source(path, "relu")

def forward(self, X):
"""
X: input matrix
return out: output matrix
"""
self._X = X
out = self._source.forward(X)
return out

def backward(self, dout):
"""
dout: gradient of output, passed from the upstream
return dX: gradient of input
"""
dX = self._source.backward(dout, self._X)
return dX
8 changes: 8 additions & 0 deletions src/main/python/systemds/operator/nodes/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,18 @@ def __parse_lines_with_filter(self, path: str) -> Iterable[str]:
lines = []
with open(path) as file:
insideBracket = 0
insideComment = False
Baunsgaard marked this conversation as resolved.
Show resolved Hide resolved
for l in file.readlines():
ls = l.strip()
if len(ls) == 0 or ls[0] == '#':
continue
elif insideComment:
if ls.endswith("*/"):
insideComment = False
continue
elif ls.startswith("/*"):
insideComment = True
continue
elif insideBracket > 0:
for c in ls:
if c == '{':
Expand Down
13 changes: 10 additions & 3 deletions src/main/python/systemds/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ def get_slice_string(i):
raise NotImplementedError("Not Implemented slice with dynamic end")
else:
# + 1 since R and systemDS is 1 indexed.
return f'{i.start+1}:{i.stop}'
return f'{i.start + 1}:{i.stop}'
else:
# + 1 since R and systemDS is 1 indexed.
sliceIns = i+1
sliceIns = i + 1
return sliceIns


Expand All @@ -77,5 +77,12 @@ def check_is_empty_slice(i):

def check_no_less_than_zero(i: list):
for x in i:
if(x < 0):
if (x < 0):
raise ValueError("Negative index not supported in systemds")


def get_path_to_script_layers() -> str:
root = os.environ.get("SYSTEMDS_ROOT")
if root is None:
root = get_module_dir()
return os.path.join(root, "scripts", "nn", "layers")
20 changes: 20 additions & 0 deletions src/main/python/tests/nn_nodes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -------------------------------------------------------------
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# -------------------------------------------------------------
Copy link
Contributor

Choose a reason for hiding this comment

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

Add line in end of file

67 changes: 67 additions & 0 deletions src/main/python/tests/nn_nodes/test_affine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# -------------------------------------------------------------
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# -------------------------------------------------------------

import unittest

import numpy as np

from systemds.context import SystemDSContext

from systemds.operator.nn_nodes.affine import Affine

dim = 6
n = 10
m = 5
np.random.seed(11)
X = np.random.rand(n, dim)

np.random.seed(10)
W = np.random.rand(dim, m)
b = np.random.rand(m)
thaivd1309 marked this conversation as resolved.
Show resolved Hide resolved


class TestAffine(unittest.TestCase):
sds: SystemDSContext = None

@classmethod
def setUpClass(cls):
cls.sds = SystemDSContext()

@classmethod
def tearDownClass(cls):
cls.sds.close()

def test_affine(self):
Copy link
Contributor

@Baunsgaard Baunsgaard Jun 26, 2023

Choose a reason for hiding this comment

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

add dedicated tests for forward for backward.
Ideally you add multiple such tests.
Then write in what you expect to get out from the operation based on a explicit input.

Xm = self.sds.from_numpy(X)
Wm = self.sds.from_numpy(W)
bm = self.sds.from_numpy(b)

affine = Affine(dim, m, 10)
out = affine.forward(Xm)
print(out.compute())
print(out.script_str)
dout = self.sds.from_numpy(np.random.rand(n, m))
dX, dW, db = affine.backward(dout)
assert True


if __name__ == '__main__':
unittest.main()
65 changes: 65 additions & 0 deletions src/main/python/tests/nn_nodes/test_relu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# -------------------------------------------------------------
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# -------------------------------------------------------------

import unittest

import numpy as np

from systemds.context import SystemDSContext

from systemds.operator.nn_nodes.relu import ReLU

X = np.array([0, -1, -2, 2, 3, -5])
dout = np.array([0, 1, 2, 3, 4, 5])


class TestRelu(unittest.TestCase):
sds: SystemDSContext = None

@classmethod
def setUpClass(cls):
cls.sds = SystemDSContext()

@classmethod
def tearDownClass(cls):
cls.sds.close()

def test_forward(self):
relu = ReLU()
#forward
Xm = self.sds.from_numpy(X)
out = relu.forward(Xm).compute().flatten()
expected = np.array([0, 0, 0, 2, 3, 0])
self.assertTrue(np.allclose(out, expected))

def test_backward(self):
relu = ReLU()
# forward
Xm = self.sds.from_numpy(X)
out = relu.forward(Xm)
# backward
doutm = self.sds.from_numpy(dout)
dx = relu.backward(doutm).compute().flatten()
expected = np.array([0, 0, 0, 3, 4, 0], dtype=np.double)
self.assertTrue(np.allclose(dx, expected))

if __name__ == '__main__':
unittest.main()