From fb352802859beed89d104fcefdc1152acc020ed1 Mon Sep 17 00:00:00 2001 From: Carlos Yeverino Date: Wed, 13 Jun 2018 21:40:25 +0200 Subject: [PATCH] Structure of CNNArch2Caffe2 created based on CNNArch2MXNet --- .circleci/config.yml | 69 ++ .gitignore | 10 + .gitlab-ci.yml | 26 + .travis.yml | 5 + pom.xml | 221 ++++++ settings.xml | 57 ++ src/license/se/license.txt | 18 + .../ArchitectureElementDataCaffe2.java | 195 +++++ .../generator/CNNArchGeneratorCaffe2.java | 147 ++++ .../generator/CNNArchGeneratorCliCaffe2.java | 93 +++ .../CNNArchTemplateControllerCaffe2.java | 293 ++++++++ .../generator/LayerNameCreatorCaffe2.java | 153 ++++ .../cnnarchcaffe2/generator/TargetCaffe2.java | 56 ++ .../TemplateConfigurationCaffe2.java | 51 ++ .../resources/templates/CNNBufferFile.ftl | 51 ++ src/main/resources/templates/CNNCreator.ftl | 180 +++++ src/main/resources/templates/CNNPredictor.ftl | 115 +++ src/main/resources/templates/elements/Add.ftl | 2 + .../templates/elements/BatchNorm.ftl | 3 + .../templates/elements/Concatenate.ftl | 4 + .../templates/elements/Convolution.ftl | 15 + .../resources/templates/elements/Dropout.ftl | 3 + .../resources/templates/elements/Flatten.ftl | 2 + .../templates/elements/FullyConnected.ftl | 10 + src/main/resources/templates/elements/Get.ftl | 1 + .../templates/elements/GlobalPooling.ftl | 6 + .../resources/templates/elements/Input.ftl | 28 + src/main/resources/templates/elements/Lrn.ftl | 6 + .../resources/templates/elements/Output.ftl | 11 + .../templates/elements/OutputShape.ftl | 2 + .../resources/templates/elements/Pooling.ftl | 14 + .../resources/templates/elements/Relu.ftl | 4 + .../resources/templates/elements/Sigmoid.ftl | 3 + .../resources/templates/elements/Softmax.ftl | 4 + .../resources/templates/elements/Split.ftl | 5 + .../resources/templates/elements/Tanh.ftl | 3 + src/main/resources/templates/execute.ftl | 21 + .../monticar/cnnarch/AbstractSymtabTest.java | 142 ++++ .../lang/monticar/cnnarch/GenerationTest.java | 117 +++ .../lang/monticar/cnnarch/SymtabTest.java | 85 +++ src/test/resources/architectures/Alexnet.cnna | 43 ++ .../resources/architectures/ResNeXt50.cnna | 44 ++ .../resources/architectures/ResNet152.cnna | 37 + .../resources/architectures/ResNet34.cnna | 35 + .../architectures/SequentialAlexnet.cnna | 29 + .../architectures/ThreeInputCNN_M14.cnna | 32 + src/test/resources/architectures/VGG16.cnna | 31 + .../ArgumentConstraintTest1.cnna | 34 + .../ArgumentConstraintTest2.cnna | 34 + .../ArgumentConstraintTest3.cnna | 34 + .../ArgumentConstraintTest4.cnna | 34 + .../ArgumentConstraintTest5.cnna | 34 + .../ArgumentConstraintTest6.cnna | 34 + .../invalid_tests/DuplicatedArgument.cnna | 11 + .../invalid_tests/DuplicatedIONames.cnna | 16 + .../invalid_tests/DuplicatedNames.cnna | 20 + .../invalid_tests/IllegalIOName.cnna | 11 + .../resources/invalid_tests/IllegalName.cnna | 15 + .../InvalidArrayAccessValue.cnna | 27 + .../invalid_tests/InvalidIOShape1.cnna | 11 + .../invalid_tests/InvalidIOShape2.cnna | 11 + .../invalid_tests/InvalidInputShape.cnna | 11 + .../invalid_tests/InvalidRecursion.cnna | 40 ++ .../invalid_tests/MissingArgument.cnna | 34 + .../resources/invalid_tests/MissingIO2.cnna | 11 + .../invalid_tests/MissingLayerOperator.cnna | 11 + .../resources/invalid_tests/MissingMerge.cnna | 24 + .../MissingParallelBrackets.cnna | 45 ++ .../resources/invalid_tests/NotIOArray.cnna | 11 + .../invalid_tests/UnfinishedArchitecture.cnna | 15 + .../resources/invalid_tests/UnknownIO.cnna | 8 + .../invalid_tests/UnknownMethod.cnna | 11 + .../invalid_tests/UnknownVariableName.cnna | 11 + .../invalid_tests/WrongArgument.cnna | 11 + .../resources/invalid_tests/WrongIOType.cnna | 11 + .../invalid_tests/WrongRangeOperator.cnna | 11 + .../resources/target_code/CNNBufferFile.h | 51 ++ .../target_code/CNNCreator_Alexnet.py | 426 +++++++++++ .../CNNCreator_CifarClassifierNetwork.py | 664 ++++++++++++++++++ .../resources/target_code/CNNCreator_VGG16.py | 462 ++++++++++++ .../target_code/CNNPredictor_Alexnet.h | 109 +++ .../CNNPredictor_CifarClassifierNetwork.h | 109 +++ .../target_code/CNNPredictor_VGG16.h | 109 +++ .../resources/target_code/execute_Alexnet | 6 + .../execute_CifarClassifierNetwork | 6 + src/test/resources/target_code/execute_VGG16 | 6 + .../resources/valid_tests/Alexnet_alt.cnna | 53 ++ .../resources/valid_tests/Alexnet_alt2.cnna | 39 + .../valid_tests/ArgumentSequenceTest.cnna | 13 + .../valid_tests/CifarClassifierNetwork.cnna | 35 + .../resources/valid_tests/Fixed_Alexnet.cnna | 43 ++ .../valid_tests/Fixed_ThreeInputCNN_M14.cnna | 27 + .../valid_tests/MultipleOutputs.cnna | 21 + .../valid_tests/ResNeXt50_InstanceTest.cnna | 44 ++ .../resources/valid_tests/ResNeXt50_alt.cnna | 46 ++ .../resources/valid_tests/ResNet152_alt.cnna | 40 ++ .../valid_tests/SimpleNetworkLinear.cnna | 10 + .../valid_tests/SimpleNetworkRelu.cnna | 11 + .../valid_tests/SimpleNetworkSigmoid.cnna | 11 + .../valid_tests/SimpleNetworkSoftmax.cnna | 11 + .../valid_tests/SimpleNetworkTanh.cnna | 11 + .../ThreeInputCNN_M14_alternative.cnna | 32 + 102 files changed, 5468 insertions(+) create mode 100644 .circleci/config.yml create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .travis.yml create mode 100644 pom.xml create mode 100644 settings.xml create mode 100644 src/license/se/license.txt create mode 100644 src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/ArchitectureElementDataCaffe2.java create mode 100644 src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/CNNArchGeneratorCaffe2.java create mode 100644 src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/CNNArchGeneratorCliCaffe2.java create mode 100644 src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/CNNArchTemplateControllerCaffe2.java create mode 100644 src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/LayerNameCreatorCaffe2.java create mode 100644 src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/TargetCaffe2.java create mode 100644 src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/TemplateConfigurationCaffe2.java create mode 100644 src/main/resources/templates/CNNBufferFile.ftl create mode 100644 src/main/resources/templates/CNNCreator.ftl create mode 100644 src/main/resources/templates/CNNPredictor.ftl create mode 100644 src/main/resources/templates/elements/Add.ftl create mode 100644 src/main/resources/templates/elements/BatchNorm.ftl create mode 100644 src/main/resources/templates/elements/Concatenate.ftl create mode 100644 src/main/resources/templates/elements/Convolution.ftl create mode 100644 src/main/resources/templates/elements/Dropout.ftl create mode 100644 src/main/resources/templates/elements/Flatten.ftl create mode 100644 src/main/resources/templates/elements/FullyConnected.ftl create mode 100644 src/main/resources/templates/elements/Get.ftl create mode 100644 src/main/resources/templates/elements/GlobalPooling.ftl create mode 100644 src/main/resources/templates/elements/Input.ftl create mode 100644 src/main/resources/templates/elements/Lrn.ftl create mode 100644 src/main/resources/templates/elements/Output.ftl create mode 100644 src/main/resources/templates/elements/OutputShape.ftl create mode 100644 src/main/resources/templates/elements/Pooling.ftl create mode 100644 src/main/resources/templates/elements/Relu.ftl create mode 100644 src/main/resources/templates/elements/Sigmoid.ftl create mode 100644 src/main/resources/templates/elements/Softmax.ftl create mode 100644 src/main/resources/templates/elements/Split.ftl create mode 100644 src/main/resources/templates/elements/Tanh.ftl create mode 100644 src/main/resources/templates/execute.ftl create mode 100644 src/test/java/de/monticore/lang/monticar/cnnarch/AbstractSymtabTest.java create mode 100644 src/test/java/de/monticore/lang/monticar/cnnarch/GenerationTest.java create mode 100644 src/test/java/de/monticore/lang/monticar/cnnarch/SymtabTest.java create mode 100644 src/test/resources/architectures/Alexnet.cnna create mode 100644 src/test/resources/architectures/ResNeXt50.cnna create mode 100644 src/test/resources/architectures/ResNet152.cnna create mode 100644 src/test/resources/architectures/ResNet34.cnna create mode 100644 src/test/resources/architectures/SequentialAlexnet.cnna create mode 100644 src/test/resources/architectures/ThreeInputCNN_M14.cnna create mode 100644 src/test/resources/architectures/VGG16.cnna create mode 100644 src/test/resources/invalid_tests/ArgumentConstraintTest1.cnna create mode 100644 src/test/resources/invalid_tests/ArgumentConstraintTest2.cnna create mode 100644 src/test/resources/invalid_tests/ArgumentConstraintTest3.cnna create mode 100644 src/test/resources/invalid_tests/ArgumentConstraintTest4.cnna create mode 100644 src/test/resources/invalid_tests/ArgumentConstraintTest5.cnna create mode 100644 src/test/resources/invalid_tests/ArgumentConstraintTest6.cnna create mode 100644 src/test/resources/invalid_tests/DuplicatedArgument.cnna create mode 100644 src/test/resources/invalid_tests/DuplicatedIONames.cnna create mode 100644 src/test/resources/invalid_tests/DuplicatedNames.cnna create mode 100644 src/test/resources/invalid_tests/IllegalIOName.cnna create mode 100644 src/test/resources/invalid_tests/IllegalName.cnna create mode 100644 src/test/resources/invalid_tests/InvalidArrayAccessValue.cnna create mode 100644 src/test/resources/invalid_tests/InvalidIOShape1.cnna create mode 100644 src/test/resources/invalid_tests/InvalidIOShape2.cnna create mode 100644 src/test/resources/invalid_tests/InvalidInputShape.cnna create mode 100644 src/test/resources/invalid_tests/InvalidRecursion.cnna create mode 100644 src/test/resources/invalid_tests/MissingArgument.cnna create mode 100644 src/test/resources/invalid_tests/MissingIO2.cnna create mode 100644 src/test/resources/invalid_tests/MissingLayerOperator.cnna create mode 100644 src/test/resources/invalid_tests/MissingMerge.cnna create mode 100644 src/test/resources/invalid_tests/MissingParallelBrackets.cnna create mode 100644 src/test/resources/invalid_tests/NotIOArray.cnna create mode 100644 src/test/resources/invalid_tests/UnfinishedArchitecture.cnna create mode 100644 src/test/resources/invalid_tests/UnknownIO.cnna create mode 100644 src/test/resources/invalid_tests/UnknownMethod.cnna create mode 100644 src/test/resources/invalid_tests/UnknownVariableName.cnna create mode 100644 src/test/resources/invalid_tests/WrongArgument.cnna create mode 100644 src/test/resources/invalid_tests/WrongIOType.cnna create mode 100644 src/test/resources/invalid_tests/WrongRangeOperator.cnna create mode 100644 src/test/resources/target_code/CNNBufferFile.h create mode 100644 src/test/resources/target_code/CNNCreator_Alexnet.py create mode 100644 src/test/resources/target_code/CNNCreator_CifarClassifierNetwork.py create mode 100644 src/test/resources/target_code/CNNCreator_VGG16.py create mode 100644 src/test/resources/target_code/CNNPredictor_Alexnet.h create mode 100644 src/test/resources/target_code/CNNPredictor_CifarClassifierNetwork.h create mode 100644 src/test/resources/target_code/CNNPredictor_VGG16.h create mode 100644 src/test/resources/target_code/execute_Alexnet create mode 100644 src/test/resources/target_code/execute_CifarClassifierNetwork create mode 100644 src/test/resources/target_code/execute_VGG16 create mode 100644 src/test/resources/valid_tests/Alexnet_alt.cnna create mode 100644 src/test/resources/valid_tests/Alexnet_alt2.cnna create mode 100644 src/test/resources/valid_tests/ArgumentSequenceTest.cnna create mode 100644 src/test/resources/valid_tests/CifarClassifierNetwork.cnna create mode 100644 src/test/resources/valid_tests/Fixed_Alexnet.cnna create mode 100644 src/test/resources/valid_tests/Fixed_ThreeInputCNN_M14.cnna create mode 100644 src/test/resources/valid_tests/MultipleOutputs.cnna create mode 100644 src/test/resources/valid_tests/ResNeXt50_InstanceTest.cnna create mode 100644 src/test/resources/valid_tests/ResNeXt50_alt.cnna create mode 100644 src/test/resources/valid_tests/ResNet152_alt.cnna create mode 100644 src/test/resources/valid_tests/SimpleNetworkLinear.cnna create mode 100644 src/test/resources/valid_tests/SimpleNetworkRelu.cnna create mode 100644 src/test/resources/valid_tests/SimpleNetworkSigmoid.cnna create mode 100644 src/test/resources/valid_tests/SimpleNetworkSoftmax.cnna create mode 100644 src/test/resources/valid_tests/SimpleNetworkTanh.cnna create mode 100644 src/test/resources/valid_tests/ThreeInputCNN_M14_alternative.cnna diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..9ec02ad --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,69 @@ +# +# +# ****************************************************************************** +# MontiCAR Modeling Family, www.se-rwth.de +# Copyright (c) 2017, Software Engineering Group at RWTH Aachen, +# All rights reserved. +# +# This project is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this project. If not, see . +# ******************************************************************************* +# + +# Java Maven CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-java/ for more details +# +version: 2 +general: + branches: + ignore: + - gh-pages + +jobs: + build: + docker: + # specify the version you desire here + - image: circleci/openjdk:8-jdk + + # Specify service dependencies here if necessary + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + # - image: circleci/postgres:9.4 + + working_directory: ~/repo + + environment: + # Customize the JVM maximum heap limit + MAVEN_OPTS: -Xmx3200m + + steps: + - checkout + + # run tests! + - run: mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -B clean install --settings "settings.xml" +workflows: + version: 2 + commit-workflow: + jobs: + - build + scheduled-workflow: + triggers: + - schedule: + cron: "30 1 * * *" + filters: + branches: + only: master + + jobs: + - build + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1178002 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +target +nppBackup +.project +.settings +.classpath +.idea +.git + +*.iml + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..e0e8057 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,26 @@ +stages: +- windows +- linux + +masterJobLinux: + stage: linux + image: maven:3-jdk-8 + script: + - mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -B clean deploy --settings settings.xml + only: + - master + +masterJobWindows: + stage: windows + script: + - mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -B clean install --settings settings.xml + tags: + - Windows10 + +BranchJobLinux: + stage: linux + image: maven:3-jdk-8 + script: + - mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -B clean install --settings settings.xml + except: + - master diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5b54b0c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +script: +- git checkout ${TRAVIS_BRANCH} +- mvn clean install cobertura:cobertura org.eluder.coveralls:coveralls-maven-plugin:report --settings "settings.xml" +after_success: +- if [ "${TRAVIS_BRANCH}" == "master" ]; then mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -B deploy --debug --settings "./settings.xml"; fi diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a1b6458 --- /dev/null +++ b/pom.xml @@ -0,0 +1,221 @@ + + 4.0.0 + + + + + de.monticore.lang.monticar + cnnarch-caffe2-generator + 0.2.1-SNAPSHOT + + + + + + + 0.2.1-SNAPSHOT + + + 18.0 + 4.12 + 1.1.2 + 4.3.1 + + + 4.5.3-SNAPSHOT + 2.5.4 + 3.3 + 2.4 + 2.4.3 + + + grammars + cli + + + 1.8 + + UTF-8 + UTF-8 + + + + + + org.antlr + antlr4-runtime + 4.7.1 + + + + com.google.guava + guava + ${guava.version} + + + + + + de.monticore.lang.monticar + cnn-arch + ${CNNArch.version} + + + + de.monticore.lang.monticar + cnn-arch + ${CNNArch.version} + ${grammars.classifier} + provided + + + + + + junit + junit + ${junit.version} + test + + + + ch.qos.logback + logback-classic + ${logback.version} + + + + org.jscience + jscience + ${jscience.version} + + + + + + + + + + + maven-deploy-plugin + 2.8.1 + + internal.repo::default::file://${project.build.directory}/external-dependencies + + + + + + de.monticore.mojo + monticore-maven-plugin + ${monticore.plugin} + + + + generate + + + + + + + + maven-compiler-plugin + ${compiler.plugin} + + true + ${java.version} + ${java.version} + + + + + maven-assembly-plugin + 3.1.0 + + + jar-with-dependencies + package + + single + + + + + de.monticore.lang.monticar.cnnarchcaffe2.generator.CNNArchGeneratorCliCaffe2 + + + + jar-with-dependencies + + + + + + + + + org.apache.maven.plugins + maven-source-plugin + ${source.plugin} + + + create source jar + package + + jar-no-fork + + + false + + **/*.java + **/*.ftl + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + + + + + org.eluder.coveralls + coveralls-maven-plugin + 4.3.0 + + + + + org.codehaus.mojo + cobertura-maven-plugin + 2.7 + + xml + 256m + + true + + + + + + + + + se-nexus + https://nexus.se.rwth-aachen.de/content/repositories/embeddedmontiarc-releases/ + + + se-nexus + https://nexus.se.rwth-aachen.de/content/repositories/embeddedmontiarc-snapshots/ + + + + diff --git a/settings.xml b/settings.xml new file mode 100644 index 0000000..9fbcd28 --- /dev/null +++ b/settings.xml @@ -0,0 +1,57 @@ + + + + + + org.mortbay.jetty + de.topobyte + + + + + + + + se-nexus + cibuild + ${env.cibuild} + + + + + + se-nexus + external:* + https://nexus.se.rwth-aachen.de/content/groups/public + + + + + + + se-nexus + + + central + http://central + + + + + + + central + http://central + + + + + + + + + se-nexus + + \ No newline at end of file diff --git a/src/license/se/license.txt b/src/license/se/license.txt new file mode 100644 index 0000000..27ba085 --- /dev/null +++ b/src/license/se/license.txt @@ -0,0 +1,18 @@ + + ****************************************************************************** + MontiCAR Modeling Family, www.se-rwth.de + Copyright (c) 2017, Software Engineering Group at RWTH Aachen, + All rights reserved. + + This project is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3.0 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this project. If not, see . +******************************************************************************* diff --git a/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/ArchitectureElementDataCaffe2.java b/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/ArchitectureElementDataCaffe2.java new file mode 100644 index 0000000..2e9df2a --- /dev/null +++ b/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/ArchitectureElementDataCaffe2.java @@ -0,0 +1,195 @@ +/** + * + * ****************************************************************************** + * MontiCAR Modeling Family, www.se-rwth.de + * Copyright (c) 2017, Software Engineering Group at RWTH Aachen, + * All rights reserved. + * + * This project is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this project. If not, see . + * ******************************************************************************* + */ +package de.monticore.lang.monticar.cnnarchcaffe2.generator; + +import de.monticore.lang.monticar.cnnarch._symboltable.ArchTypeSymbol; +import de.monticore.lang.monticar.cnnarch._symboltable.ArchitectureElementSymbol; +import de.monticore.lang.monticar.cnnarch._symboltable.LayerSymbol; +import de.monticore.lang.monticar.cnnarch.predefined.AllPredefinedLayers; +import de.se_rwth.commons.logging.Log; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.List; + +public class ArchitectureElementDataCaffe2 { + + private String name; + private ArchitectureElementSymbol element; + private CNNArchTemplateControllerCaffe2 templateController; + + public ArchitectureElementDataCaffe2(String name, ArchitectureElementSymbol element, CNNArchTemplateControllerCaffe2 templateController) { + this.name = name; + this.element = element; + this.templateController = templateController; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ArchitectureElementSymbol getElement() { + return element; + } + + public void setElement(ArchitectureElementSymbol element) { + this.element = element; + } + + public CNNArchTemplateControllerCaffe2 getTemplateController() { + return templateController; + } + + public void setTemplateController(CNNArchTemplateControllerCaffe2 templateController) { + this.templateController = templateController; + } + + public List getInputs(){ + return getTemplateController().getLayerInputs(getElement()); + } + + public boolean isLogisticRegressionOutput(){ + return getTemplateController().isLogisticRegressionOutput(getElement()); + } + + + public boolean isLinearRegressionOutput(){ + boolean result = getTemplateController().isLinearRegressionOutput(getElement()); + if (result){ + Log.warn("The Output '" + getElement().getName() + "' is a linear regression output (squared loss) during training" + + " because the previous architecture element is not a softmax (cross-entropy loss) or sigmoid (logistic regression loss) activation. " + + "Other loss functions are currently not supported. " + , getElement().getSourcePosition()); + } + return result; + } + + public boolean isSoftmaxOutput(){ + return getTemplateController().isSoftmaxOutput(getElement()); + } + + + + + public List getKernel(){ + return ((LayerSymbol) getElement()) + .getIntTupleValue(AllPredefinedLayers.KERNEL_NAME).get(); + } + + public int getChannels(){ + return ((LayerSymbol) getElement()) + .getIntValue(AllPredefinedLayers.CHANNELS_NAME).get(); + } + + public List getStride(){ + return ((LayerSymbol) getElement()) + .getIntTupleValue(AllPredefinedLayers.STRIDE_NAME).get(); + } + + public int getUnits(){ + return ((LayerSymbol) getElement()) + .getIntValue(AllPredefinedLayers.UNITS_NAME).get(); + } + + public boolean getNoBias(){ + return ((LayerSymbol) getElement()) + .getBooleanValue(AllPredefinedLayers.NOBIAS_NAME).get(); + } + + public double getP(){ + return ((LayerSymbol) getElement()) + .getDoubleValue(AllPredefinedLayers.P_NAME).get(); + } + + public int getIndex(){ + return ((LayerSymbol) getElement()) + .getIntValue(AllPredefinedLayers.INDEX_NAME).get(); + } + + public int getNumOutputs(){ + return ((LayerSymbol) getElement()) + .getIntValue(AllPredefinedLayers.NUM_SPLITS_NAME).get(); + } + + public boolean getFixGamma(){ + return ((LayerSymbol) getElement()) + .getBooleanValue(AllPredefinedLayers.FIX_GAMMA_NAME).get(); + } + + public int getNsize(){ + return ((LayerSymbol) getElement()) + .getIntValue(AllPredefinedLayers.NSIZE_NAME).get(); + } + + public double getKnorm(){ + return ((LayerSymbol) getElement()) + .getDoubleValue(AllPredefinedLayers.KNORM_NAME).get(); + } + + public double getAlpha(){ + return ((LayerSymbol) getElement()) + .getDoubleValue(AllPredefinedLayers.ALPHA_NAME).get(); + } + + public double getBeta(){ + return ((LayerSymbol) getElement()) + .getDoubleValue(AllPredefinedLayers.BETA_NAME).get(); + } + + @Nullable + public String getPoolType(){ + return ((LayerSymbol) getElement()) + .getStringValue(AllPredefinedLayers.POOL_TYPE_NAME).get(); + } + + @Nullable + public List getPadding(){ + return getPadding((LayerSymbol) getElement()); + } + + @Nullable + public List getPadding(LayerSymbol layer){ + List kernel = layer.getIntTupleValue(AllPredefinedLayers.KERNEL_NAME).get(); + List stride = layer.getIntTupleValue(AllPredefinedLayers.STRIDE_NAME).get(); + ArchTypeSymbol inputType = layer.getInputTypes().get(0); + ArchTypeSymbol outputType = layer.getOutputTypes().get(0); + + int heightWithPad = kernel.get(0) + stride.get(0)*(outputType.getHeight() - 1); + int widthWithPad = kernel.get(1) + stride.get(1)*(outputType.getWidth() - 1); + int heightPad = Math.max(0, heightWithPad - inputType.getHeight()); + int widthPad = Math.max(0, widthWithPad - inputType.getWidth()); + + int topPad = (int)Math.ceil(heightPad / 2.0); + int bottomPad = (int)Math.floor(heightPad / 2.0); + int leftPad = (int)Math.ceil(widthPad / 2.0); + int rightPad = (int)Math.floor(widthPad / 2.0); + + if (topPad == 0 && bottomPad == 0 && leftPad == 0 && rightPad == 0){ + return null; + } + + return Arrays.asList(0,0,0,0,topPad,bottomPad,leftPad,rightPad); + } +} diff --git a/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/CNNArchGeneratorCaffe2.java b/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/CNNArchGeneratorCaffe2.java new file mode 100644 index 0000000..f80e536 --- /dev/null +++ b/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/CNNArchGeneratorCaffe2.java @@ -0,0 +1,147 @@ +/** + * + * ****************************************************************************** + * MontiCAR Modeling Family, www.se-rwth.de + * Copyright (c) 2017, Software Engineering Group at RWTH Aachen, + * All rights reserved. + * + * This project is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this project. If not, see . + * ******************************************************************************* + */ +package de.monticore.lang.monticar.cnnarchcaffe2.generator; + +import de.monticore.io.paths.ModelPath; +import de.monticore.lang.monticar.cnnarch._cocos.CNNArchCocos; +import de.monticore.lang.monticar.cnnarch._symboltable.ArchitectureSymbol; +import de.monticore.lang.monticar.cnnarch._symboltable.CNNArchCompilationUnitSymbol; +import de.monticore.lang.monticar.cnnarch._symboltable.CNNArchLanguage; +import de.monticore.symboltable.GlobalScope; +import de.monticore.symboltable.Scope; +import de.se_rwth.commons.logging.Log; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class CNNArchGeneratorCaffe2 { + + private String generationTargetPath; + + public CNNArchGeneratorCaffe2() { + setGenerationTargetPath("./target/generated-sources-cnnarch/"); + } + + public String getGenerationTargetPath() { + if (generationTargetPath.charAt(generationTargetPath.length() - 1) != '/') { + this.generationTargetPath = generationTargetPath + "/"; + } + return generationTargetPath; + } + + public void setGenerationTargetPath(String generationTargetPath) { + this.generationTargetPath = generationTargetPath; + } + + public void generate(Path modelsDirPath, String rootModelName){ + final ModelPath mp = new ModelPath(modelsDirPath); + GlobalScope scope = new GlobalScope(mp, new CNNArchLanguage()); + generate(scope, rootModelName); + } + + public void generate(Scope scope, String rootModelName){ + Optional compilationUnit = scope.resolve(rootModelName, CNNArchCompilationUnitSymbol.KIND); + if (!compilationUnit.isPresent()){ + Log.error("could not resolve architecture " + rootModelName); + System.exit(1); + } + + CNNArchCocos.checkAll(compilationUnit.get()); + + try{ + generateFiles(compilationUnit.get().getArchitecture()); + } + catch (IOException e){ + Log.error(e.toString()); + } + } + + //check cocos with CNNArchCocos.checkAll(architecture) before calling this method. + public Map generateStrings(ArchitectureSymbol architecture){ + Map fileContentMap = new HashMap<>(); + CNNArchTemplateControllerCaffe2 archTc = new CNNArchTemplateControllerCaffe2(architecture); + Map.Entry temp; + + temp = archTc.process("CNNPredictor", TargetCaffe2.CPP); + fileContentMap.put(temp.getKey(), temp.getValue()); + + temp = archTc.process("CNNCreator", TargetCaffe2.PYTHON); + fileContentMap.put(temp.getKey(), temp.getValue()); + + temp = archTc.process("execute", TargetCaffe2.CPP); + fileContentMap.put(temp.getKey().replace(".h", ""), temp.getValue()); + + temp = archTc.process("CNNBufferFile", TargetCaffe2.CPP); + fileContentMap.put("CNNBufferFile.h", temp.getValue()); + + checkValidGeneration(architecture); + + return fileContentMap; + } + + private void checkValidGeneration(ArchitectureSymbol architecture){ + if (architecture.getInputs().size() > 1){ + Log.warn("This cnn architecture has multiple inputs, " + + "which is currently not supported by the generator. " + + "The generated code will not work correctly." + , architecture.getSourcePosition()); + } + if (architecture.getOutputs().size() > 1){ + Log.warn("This cnn architecture has multiple outputs, " + + "which is currently not supported by the generator. " + + "The generated code will not work correctly." + , architecture.getSourcePosition()); + } + if (architecture.getOutputs().get(0).getDefinition().getType().getWidth() != 1 || + architecture.getOutputs().get(0).getDefinition().getType().getHeight() != 1){ + Log.error("This cnn architecture has a multi-dimensional output, " + + "which is currently not supported by the generator." + , architecture.getSourcePosition()); + } + } + + //check cocos with CNNArchCocos.checkAll(architecture) before calling this method. + public void generateFiles(ArchitectureSymbol architecture) throws IOException{ + CNNArchTemplateControllerCaffe2 archTc = new CNNArchTemplateControllerCaffe2(architecture); + Map fileContentMap = generateStrings(architecture); + + for (String fileName : fileContentMap.keySet()){ + File f = new File(getGenerationTargetPath() + fileName); + Log.info(f.getName(), "FileCreation:"); + if (!f.exists()) { + f.getParentFile().mkdirs(); + if (!f.createNewFile()) { + Log.error("File could not be created"); + } + } + + FileWriter writer = new FileWriter(f); + writer.write(fileContentMap.get(fileName)); + writer.close(); + } + } + +} diff --git a/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/CNNArchGeneratorCliCaffe2.java b/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/CNNArchGeneratorCliCaffe2.java new file mode 100644 index 0000000..0c397ba --- /dev/null +++ b/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/CNNArchGeneratorCliCaffe2.java @@ -0,0 +1,93 @@ +/** + * + * ****************************************************************************** + * MontiCAR Modeling Family, www.se-rwth.de + * Copyright (c) 2017, Software Engineering Group at RWTH Aachen, + * All rights reserved. + * + * This project is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this project. If not, see . + * ******************************************************************************* + */ +package de.monticore.lang.monticar.cnnarchcaffe2.generator; + +import org.apache.commons.cli.*; + +import java.nio.file.Path; +import java.nio.file.Paths; + +public class CNNArchGeneratorCliCaffe2 { + + public static final Option OPTION_MODELS_PATH = Option.builder("m") + .longOpt("models-dir") + .desc("full path to the directory with the CNNArch model") + .hasArg(true) + .required(true) + .build(); + + public static final Option OPTION_ROOT_MODEL = Option.builder("r") + .longOpt("root-model") + .desc("name of the architecture") + .hasArg(true) + .required(true) + .build(); + + public static final Option OPTION_OUTPUT_PATH = Option.builder("o") + .longOpt("output-dir") + .desc("full path to output directory for tests") + .hasArg(true) + .required(false) + .build(); + + private CNNArchGeneratorCliCaffe2() { + } + + public static void main(String[] args) { + Options options = getOptions(); + CommandLineParser parser = new DefaultParser(); + CommandLine cliArgs = parseArgs(options, parser, args); + if (cliArgs != null) { + runGenerator(cliArgs); + } + } + + private static Options getOptions() { + Options options = new Options(); + options.addOption(OPTION_MODELS_PATH); + options.addOption(OPTION_ROOT_MODEL); + options.addOption(OPTION_OUTPUT_PATH); + return options; + } + + private static CommandLine parseArgs(Options options, CommandLineParser parser, String[] args) { + CommandLine cliArgs; + try { + cliArgs = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("argument parsing exception: " + e.getMessage()); + System.exit(1); + return null; + } + return cliArgs; + } + + private static void runGenerator(CommandLine cliArgs) { + Path modelsDirPath = Paths.get(cliArgs.getOptionValue(OPTION_MODELS_PATH.getOpt())); + String rootModelName = cliArgs.getOptionValue(OPTION_ROOT_MODEL.getOpt()); + String outputPath = cliArgs.getOptionValue(OPTION_OUTPUT_PATH.getOpt()); + CNNArchGeneratorCaffe2 generator = new CNNArchGeneratorCaffe2(); + if (outputPath != null){ + generator.setGenerationTargetPath(outputPath); + } + generator.generate(modelsDirPath, rootModelName); + } +} diff --git a/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/CNNArchTemplateControllerCaffe2.java b/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/CNNArchTemplateControllerCaffe2.java new file mode 100644 index 0000000..80219ad --- /dev/null +++ b/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/CNNArchTemplateControllerCaffe2.java @@ -0,0 +1,293 @@ +/** + * + * ****************************************************************************** + * MontiCAR Modeling Family, www.se-rwth.de + * Copyright (c) 2017, Software Engineering Group at RWTH Aachen, + * All rights reserved. + * + * This project is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this project. If not, see . + * ******************************************************************************* + */ +package de.monticore.lang.monticar.cnnarchcaffe2.generator; + +import de.monticore.lang.monticar.cnnarch._symboltable.*; +import de.monticore.lang.monticar.cnnarch.predefined.Sigmoid; +import de.monticore.lang.monticar.cnnarch.predefined.Softmax; +import de.se_rwth.commons.logging.Log; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.*; + +public class CNNArchTemplateControllerCaffe2 { + + public static final String FTL_FILE_ENDING = ".ftl"; + public static final String TEMPLATE_ELEMENTS_DIR_PATH = "elements/"; + public static final String TEMPLATE_CONTROLLER_KEY = "tc"; + public static final String ELEMENT_DATA_KEY = "element"; + + private LayerNameCreatorCaffe2 nameManager; + private Configuration freemarkerConfig = TemplateConfigurationCaffe2.get(); + private ArchitectureSymbol architecture; + + private Writer writer; + private String mainTemplateNameWithoutEnding; + private TargetCaffe2 targetCaffe2Language; + private ArchitectureElementDataCaffe2 dataElement; + + public CNNArchTemplateControllerCaffe2(ArchitectureSymbol architecture) { + setArchitecture(architecture); + } + + public String getFileNameWithoutEnding() { + return mainTemplateNameWithoutEnding + "_" + getFullArchitectureName(); + } + + public TargetCaffe2 getTargetCaffe2Language(){ + return targetCaffe2Language; + } + + public void setTargetCaffe2Language(TargetCaffe2 targetCaffe2Language) { + this.targetCaffe2Language = targetCaffe2Language; + } + + public ArchitectureElementDataCaffe2 getCurrentElement() { + return dataElement; + } + + public void setCurrentElement(ArchitectureElementSymbol layer) { + this.dataElement = new ArchitectureElementDataCaffe2(getName(layer), layer, this); + } + + public void setCurrentElement(ArchitectureElementDataCaffe2 dataElement) { + this.dataElement = dataElement; + } + + public ArchitectureSymbol getArchitecture() { + return architecture; + } + + public void setArchitecture(ArchitectureSymbol architecture) { + this.architecture = architecture; + this.nameManager = new LayerNameCreatorCaffe2(architecture); + } + + public String getName(ArchitectureElementSymbol layer){ + return nameManager.getName(layer); + } + + public String getArchitectureName(){ + return getArchitecture().getEnclosingScope().getSpanningSymbol().get().getName().replaceAll("\\.","_"); + } + + public String getFullArchitectureName(){ + return getArchitecture().getEnclosingScope().getSpanningSymbol().get().getFullName().replaceAll("\\.","_"); + } + + public List getLayerInputs(ArchitectureElementSymbol layer){ + List inputNames = new ArrayList<>(); + + if (isSoftmaxOutput(layer) || isLogisticRegressionOutput(layer)){ + inputNames = getLayerInputs(layer.getInputElement().get()); + } + else { + for (ArchitectureElementSymbol input : layer.getPrevious()) { + if (input.getOutputTypes().size() == 1) { + inputNames.add(getName(input)); + } else { + for (int i = 0; i < input.getOutputTypes().size(); i++) { + inputNames.add(getName(input) + "[" + i + "]"); + } + } + } + } + return inputNames; + + } + + public List getArchitectureInputs(){ + List list = new ArrayList<>(); + for (IOSymbol ioElement : getArchitecture().getInputs()){ + list.add(nameManager.getName(ioElement)); + } + return list; + } + + public List getArchitectureOutputs(){ + List list = new ArrayList<>(); + for (IOSymbol ioElement : getArchitecture().getOutputs()){ + list.add(nameManager.getName(ioElement)); + } + return list; + } + + public void include(String relativePath, String templateWithoutFileEnding, Writer writer){ + String templatePath = relativePath + templateWithoutFileEnding + FTL_FILE_ENDING; + + try { + Template template = freemarkerConfig.getTemplate(templatePath); + Map ftlContext = new HashMap<>(); + ftlContext.put(TEMPLATE_CONTROLLER_KEY, this); + ftlContext.put(ELEMENT_DATA_KEY, getCurrentElement()); + + this.writer = writer; + template.process(ftlContext, writer); + this.writer = null; + } + catch (IOException e) { + Log.error("Freemarker could not find template " + templatePath + " :\n" + e.getMessage()); + System.exit(1); + } + catch (TemplateException e){ + Log.error("An exception occured in template " + templatePath + " :\n" + e.getMessage()); + System.exit(1); + } + } + + public void include(IOSymbol ioElement, Writer writer){ + ArchitectureElementDataCaffe2 previousElement = getCurrentElement(); + setCurrentElement(ioElement); + + if (ioElement.isAtomic()){ + if (ioElement.isInput()){ + include(TEMPLATE_ELEMENTS_DIR_PATH, "Input", writer); + } + else { + include(TEMPLATE_ELEMENTS_DIR_PATH, "Output", writer); + } + } + else { + include(ioElement.getResolvedThis().get(), writer); + } + + setCurrentElement(previousElement); + } + + public void include(LayerSymbol layer, Writer writer){ + ArchitectureElementDataCaffe2 previousElement = getCurrentElement(); + setCurrentElement(layer); + + if (layer.isAtomic()){ + ArchitectureElementSymbol nextElement = layer.getOutputElement().get(); + if (!isSoftmaxOutput(nextElement) && !isLogisticRegressionOutput(nextElement)){ + String templateName = layer.getDeclaration().getName(); + include(TEMPLATE_ELEMENTS_DIR_PATH, templateName, writer); + } + } + else { + include(layer.getResolvedThis().get(), writer); + } + + setCurrentElement(previousElement); + } + + public void include(CompositeElementSymbol compositeElement, Writer writer){ + ArchitectureElementDataCaffe2 previousElement = getCurrentElement(); + setCurrentElement(compositeElement); + + for (ArchitectureElementSymbol element : compositeElement.getElements()){ + include(element, writer); + } + + setCurrentElement(previousElement); + } + + public void include(ArchitectureElementSymbol architectureElement, Writer writer){ + if (architectureElement instanceof CompositeElementSymbol){ + include((CompositeElementSymbol) architectureElement, writer); + } + else if (architectureElement instanceof LayerSymbol){ + include((LayerSymbol) architectureElement, writer); + } + else { + include((IOSymbol) architectureElement, writer); + } + } + + public void include(ArchitectureElementSymbol architectureElement){ + if (writer == null){ + throw new IllegalStateException("missing writer"); + } + include(architectureElement, writer); + } + + public Map.Entry process(String templateNameWithoutEnding, TargetCaffe2 targetCaffe2Language){ + StringWriter writer = new StringWriter(); + this.mainTemplateNameWithoutEnding = templateNameWithoutEnding; + this.targetCaffe2Language = targetCaffe2Language; + include("", templateNameWithoutEnding, writer); + + String fileEnding = targetCaffe2Language.toString(); + if (targetCaffe2Language == TargetCaffe2.CPP){ + fileEnding = ".h"; + } + String fileName = getFileNameWithoutEnding() + fileEnding; + + Map.Entry fileContent = new AbstractMap.SimpleEntry<>(fileName, writer.toString()); + + this.mainTemplateNameWithoutEnding = null; + this.targetCaffe2Language = null; + return fileContent; + } + + public String join(Iterable iterable, String separator){ + return join(iterable, separator, "", ""); + } + + public String join(Iterable iterable, String separator, String elementPrefix, String elementPostfix){ + StringBuilder stringBuilder = new StringBuilder(); + boolean isFirst = true; + for (Object element : iterable){ + if (!isFirst){ + stringBuilder.append(separator); + } + stringBuilder.append(elementPrefix); + stringBuilder.append(element.toString()); + stringBuilder.append(elementPostfix); + isFirst = false; + } + return stringBuilder.toString(); + } + + + public boolean isLogisticRegressionOutput(ArchitectureElementSymbol architectureElement){ + return isTOutput(Sigmoid.class, architectureElement); + } + + public boolean isLinearRegressionOutput(ArchitectureElementSymbol architectureElement){ + return architectureElement.isOutput() + && !isLogisticRegressionOutput(architectureElement) + && !isSoftmaxOutput(architectureElement); + } + + + public boolean isSoftmaxOutput(ArchitectureElementSymbol architectureElement){ + return isTOutput(Softmax.class, architectureElement); + } + + private boolean isTOutput(Class inputPredefinedLayerClass, ArchitectureElementSymbol architectureElement){ + if (architectureElement.isOutput()){ + if (architectureElement.getInputElement().isPresent() && architectureElement.getInputElement().get() instanceof LayerSymbol){ + LayerSymbol inputLayer = (LayerSymbol) architectureElement.getInputElement().get(); + if (inputPredefinedLayerClass.isInstance(inputLayer.getDeclaration())){ + return true; + } + } + } + return false; + } +} diff --git a/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/LayerNameCreatorCaffe2.java b/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/LayerNameCreatorCaffe2.java new file mode 100644 index 0000000..10f3937 --- /dev/null +++ b/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/LayerNameCreatorCaffe2.java @@ -0,0 +1,153 @@ +/** + * + * ****************************************************************************** + * MontiCAR Modeling Family, www.se-rwth.de + * Copyright (c) 2017, Software Engineering Group at RWTH Aachen, + * All rights reserved. + * + * This project is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this project. If not, see . + * ******************************************************************************* + */ +package de.monticore.lang.monticar.cnnarchcaffe2.generator; + +import de.monticore.lang.monticar.cnnarch._symboltable.*; +import de.monticore.lang.monticar.cnnarch.predefined.Convolution; +import de.monticore.lang.monticar.cnnarch.predefined.FullyConnected; +import de.monticore.lang.monticar.cnnarch.predefined.Pooling; + +import java.util.*; + +public class LayerNameCreatorCaffe2 { + + private Map elementToName = new HashMap<>(); + private Map nameToElement = new HashMap<>(); + + public LayerNameCreatorCaffe2(ArchitectureSymbol architecture) { + name(architecture.getBody(), 1, new ArrayList<>()); + } + + public ArchitectureElementSymbol getArchitectureElement(String name){ + return nameToElement.get(name); + } + + public String getName(ArchitectureElementSymbol architectureElement){ + return elementToName.get(architectureElement); + } + + protected int name(ArchitectureElementSymbol architectureElement, int stage, List streamIndices){ + if (architectureElement instanceof CompositeElementSymbol){ + return nameComposite((CompositeElementSymbol) architectureElement, stage, streamIndices); + } + else{ + if (architectureElement.isAtomic()){ + if (architectureElement.getMaxSerialLength().get() > 0){ + return add(architectureElement, stage, streamIndices); + } + else { + return stage; + } + } + else { + ArchitectureElementSymbol resolvedElement = architectureElement.getResolvedThis().get(); + return name(resolvedElement, stage, streamIndices); + } + } + } + + protected int nameComposite(CompositeElementSymbol compositeElement, int stage, List streamIndices){ + if (compositeElement.isParallel()){ + int startStage = stage + 1; + streamIndices.add(1); + int lastIndex = streamIndices.size() - 1; + + List endStages = new ArrayList<>(); + for (ArchitectureElementSymbol subElement : compositeElement.getElements()){ + endStages.add(name(subElement, startStage, streamIndices)); + streamIndices.set(lastIndex, streamIndices.get(lastIndex) + 1); + } + + streamIndices.remove(lastIndex); + return Collections.max(endStages) + 1; + } + else { + int endStage = stage; + for (ArchitectureElementSymbol subElement : compositeElement.getElements()){ + endStage = name(subElement, endStage, streamIndices); + } + return endStage; + } + } + + protected int add(ArchitectureElementSymbol architectureElement, int stage, List streamIndices){ + int endStage = stage; + if (!elementToName.containsKey(architectureElement)) { + String name = createName(architectureElement, endStage, streamIndices); + + while (nameToElement.containsKey(name)) { + endStage++; + name = createName(architectureElement, endStage, streamIndices); + } + + elementToName.put(architectureElement, name); + nameToElement.put(name, architectureElement); + } + return endStage; + } + + protected String createName(ArchitectureElementSymbol architectureElement, int stage, List streamIndices){ + if (architectureElement instanceof IOSymbol){ + String name = createBaseName(architectureElement); + IOSymbol ioElement = (IOSymbol) architectureElement; + if (ioElement.getArrayAccess().isPresent()){ + int arrayAccess = ioElement.getArrayAccess().get().getIntValue().get(); + name = name + "_" + arrayAccess + "_"; + } + return name; + } + else { + return createBaseName(architectureElement) + stage + createStreamPostfix(streamIndices) + "_"; + } + } + + + protected String createBaseName(ArchitectureElementSymbol architectureElement){ + if (architectureElement instanceof LayerSymbol) { + LayerDeclarationSymbol layerDeclaration = ((LayerSymbol) architectureElement).getDeclaration(); + if (layerDeclaration instanceof Convolution) { + return "conv"; + } else if (layerDeclaration instanceof FullyConnected) { + return "fc"; + } else if (layerDeclaration instanceof Pooling) { + return "pool"; + } else { + return layerDeclaration.getName().toLowerCase(); + } + } + else if (architectureElement instanceof CompositeElementSymbol){ + return "group"; + } + else { + return architectureElement.getName(); + } + } + + protected String createStreamPostfix(List streamIndices){ + StringBuilder stringBuilder = new StringBuilder(); + for (int streamIndex : streamIndices){ + stringBuilder.append("_"); + stringBuilder.append(streamIndex); + } + return stringBuilder.toString(); + } +} + diff --git a/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/TargetCaffe2.java b/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/TargetCaffe2.java new file mode 100644 index 0000000..bf1d328 --- /dev/null +++ b/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/TargetCaffe2.java @@ -0,0 +1,56 @@ +/** + * + * ****************************************************************************** + * MontiCAR Modeling Family, www.se-rwth.de + * Copyright (c) 2017, Software Engineering Group at RWTH Aachen, + * All rights reserved. + * + * This project is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this project. If not, see . + * ******************************************************************************* + */ +package de.monticore.lang.monticar.cnnarchcaffe2.generator; + +//can be removed +public enum TargetCaffe2 { + PYTHON{ + @Override + public String toString() { + return ".py"; + } + }, + CPP{ + @Override + public String toString() { + return ".cpp"; + } + }; + + public static TargetCaffe2 fromString(String target){ + switch (target.toLowerCase()){ + case "python": + return PYTHON; + + case "py": + return PYTHON; + + case "cpp": + return CPP; + + case "c++": + return CPP; + + default: + throw new IllegalArgumentException(); + } + } +} diff --git a/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/TemplateConfigurationCaffe2.java b/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/TemplateConfigurationCaffe2.java new file mode 100644 index 0000000..b8ede8d --- /dev/null +++ b/src/main/java/de/monticore/lang/monticar/cnnarchcaffe2/generator/TemplateConfigurationCaffe2.java @@ -0,0 +1,51 @@ +/** + * + * ****************************************************************************** + * MontiCAR Modeling Family, www.se-rwth.de + * Copyright (c) 2017, Software Engineering Group at RWTH Aachen, + * All rights reserved. + * + * This project is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this project. If not, see . + * ******************************************************************************* + */ +package de.monticore.lang.monticar.cnnarchcaffe2.generator; + +import freemarker.template.Configuration; +import freemarker.template.TemplateExceptionHandler; + +public class TemplateConfigurationCaffe2 { + + private static TemplateConfigurationCaffe2 instance; + private Configuration configuration; + + private TemplateConfigurationCaffe2() { + configuration = new Configuration(Configuration.VERSION_2_3_23); + configuration.setClassForTemplateLoading(TemplateConfigurationCaffe2.class, "/templates"); //YEVERINO + configuration.setDefaultEncoding("UTF-8"); + configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + configuration.setLocalizedLookup(false); //YEVERINO + configuration.clearTemplateCache(); //YEVERINO + } + + public Configuration getConfiguration() { + return configuration; + } + + public static Configuration get(){ + if (instance == null){ + instance = new TemplateConfigurationCaffe2(); + } + return instance.getConfiguration(); + } + +} diff --git a/src/main/resources/templates/CNNBufferFile.ftl b/src/main/resources/templates/CNNBufferFile.ftl new file mode 100644 index 0000000..c0d8dd9 --- /dev/null +++ b/src/main/resources/templates/CNNBufferFile.ftl @@ -0,0 +1,51 @@ +#ifndef CNNBUFFERFILE_H +#define CNNBUFFERFILE_H + +#include +#include +#include + +// Read file to buffer +class BufferFile { + public : + std::string file_path_; + int length_; + char* buffer_; + + explicit BufferFile(std::string file_path) + :file_path_(file_path) { + + std::ifstream ifs(file_path.c_str(), std::ios::in | std::ios::binary); + if (!ifs) { + std::cerr << "Can't open the file. Please check " << file_path << ". \n"; + length_ = 0; + buffer_ = NULL; + return; + } + + ifs.seekg(0, std::ios::end); + length_ = ifs.tellg(); + ifs.seekg(0, std::ios::beg); + std::cout << file_path.c_str() << " ... "<< length_ << " bytes\n"; + + buffer_ = new char[sizeof(char) * length_]; + ifs.read(buffer_, length_); + ifs.close(); + } + + int GetLength() { + return length_; + } + char* GetBuffer() { + return buffer_; + } + + ~BufferFile() { + if (buffer_) { + delete[] buffer_; + buffer_ = NULL; + } + } +}; + +#endif // CNNBUFFERFILE_H diff --git a/src/main/resources/templates/CNNCreator.ftl b/src/main/resources/templates/CNNCreator.ftl new file mode 100644 index 0000000..79fede9 --- /dev/null +++ b/src/main/resources/templates/CNNCreator.ftl @@ -0,0 +1,180 @@ +<#-- CAFFE2 --> +CAFFE2 + +import mxnet as mx +import logging +import os +import errno +import shutil +import h5py +import sys +import numpy as np + +@mx.init.register +class MyConstant(mx.init.Initializer): + def __init__(self, value): + super(MyConstant, self).__init__(value=value) + self.value = value + def _init_weight(self, _, arr): + arr[:] = mx.nd.array(self.value) + +class ${tc.fileNameWithoutEnding}: + + module = None + _data_dir_ = "data/${tc.fullArchitectureName}/" + _model_dir_ = "model/${tc.fullArchitectureName}/" + _model_prefix_ = "${tc.architectureName}" + _input_names_ = [${tc.join(tc.architectureInputs, ",", "'", "'")}] + _input_shapes_ = [<#list tc.architecture.inputs as input>(${tc.join(input.definition.type.dimensions, ",")})] + _output_names_ = [${tc.join(tc.architectureOutputs, ",", "'", "_label'")}] + + + def load(self, context): + lastEpoch = 0 + param_file = None + + try: + os.remove(self._model_dir_ + self._model_prefix_ + "_newest-0000.params") + except OSError: + pass + try: + os.remove(self._model_dir_ + self._model_prefix_ + "_newest-symbol.json") + except OSError: + pass + + if os.path.isdir(self._model_dir_): + for file in os.listdir(self._model_dir_): + if ".params" in file and self._model_prefix_ in file: + epochStr = file.replace(".params","").replace(self._model_prefix_ + "-","") + epoch = int(epochStr) + if epoch > lastEpoch: + lastEpoch = epoch + param_file = file + if param_file is None: + return 0 + else: + logging.info("Loading checkpoint: " + param_file) + self.module.load(prefix=self._model_dir_ + self._model_prefix_, + epoch=lastEpoch, + data_names=self._input_names_, + label_names=self._output_names_, + context=context) + return lastEpoch + + + def load_data(self, batch_size): + train_h5, test_h5 = self.load_h5_files() + + data_mean = train_h5[self._input_names_[0]][:].mean(axis=0) + data_std = train_h5[self._input_names_[0]][:].std(axis=0) + 1e-5 + + train_iter = mx.io.NDArrayIter(train_h5[self._input_names_[0]], + train_h5[self._output_names_[0]], + batch_size=batch_size, + data_name=self._input_names_[0], + label_name=self._output_names_[0]) + test_iter = None + if test_h5 != None: + test_iter = mx.io.NDArrayIter(test_h5[self._input_names_[0]], + test_h5[self._output_names_[0]], + batch_size=batch_size, + data_name=self._input_names_[0], + label_name=self._output_names_[0]) + return train_iter, test_iter, data_mean, data_std + + def load_h5_files(self): + train_h5 = None + test_h5 = None + train_path = self._data_dir_ + "train.h5" + test_path = self._data_dir_ + "test.h5" + if os.path.isfile(train_path): + train_h5 = h5py.File(train_path, 'r') + if not (self._input_names_[0] in train_h5 and self._output_names_[0] in train_h5): + logging.error("The HDF5 file '" + os.path.abspath(train_path) + "' has to contain the datasets: " + + "'" + self._input_names_[0] + "', '" + self._output_names_[0] + "'") + sys.exit(1) + test_iter = None + if os.path.isfile(test_path): + test_h5 = h5py.File(test_path, 'r') + if not (self._input_names_[0] in test_h5 and self._output_names_[0] in test_h5): + logging.error("The HDF5 file '" + os.path.abspath(test_path) + "' has to contain the datasets: " + + "'" + self._input_names_[0] + "', '" + self._output_names_[0] + "'") + sys.exit(1) + else: + logging.warning("Couldn't load test set. File '" + os.path.abspath(test_path) + "' does not exist.") + return train_h5, test_h5 + else: + logging.error("Data loading failure. File '" + os.path.abspath(train_path) + "' does not exist.") + sys.exit(1) + + + def train(self, batch_size, + num_epoch=10, + optimizer='adam', + optimizer_params=(('learning_rate', 0.001),), + load_checkpoint=True, + context='gpu', + checkpoint_period=5, + normalize=True): + if context == 'gpu': + mx_context = mx.gpu() + elif context == 'cpu': + mx_context = mx.cpu() + else: + logging.error("Context argument is '" + context + "'. Only 'cpu' and 'gpu are valid arguments'.") + + if 'weight_decay' in optimizer_params: + optimizer_params['wd'] = optimizer_params['weight_decay'] + del optimizer_params['weight_decay'] + if 'learning_rate_decay' in optimizer_params: + min_learning_rate = 1e-08 + if 'learning_rate_minimum' in optimizer_params: + min_learning_rate = optimizer_params['learning_rate_minimum'] + del optimizer_params['learning_rate_minimum'] + optimizer_params['lr_scheduler'] = mx.lr_scheduler.FactorScheduler( + optimizer_params['step_size'], + factor=optimizer_params['learning_rate_decay'], + stop_factor_lr=min_learning_rate) + del optimizer_params['step_size'] + del optimizer_params['learning_rate_decay'] + + + train_iter, test_iter, data_mean, data_std = self.load_data(batch_size) + if self.module == None: + if normalize: + self.construct(mx_context, data_mean, data_std) + else: + self.construct(mx_context) + + begin_epoch = 0 + if load_checkpoint: + begin_epoch = self.load(mx_context) + else: + if os.path.isdir(self._model_dir_): + shutil.rmtree(self._model_dir_) + + try: + os.makedirs(self._model_dir_) + except OSError: + if not os.path.isdir(self._model_dir_): + raise + + self.module.fit( + train_data=train_iter, + eval_data=test_iter, + optimizer=optimizer, + optimizer_params=optimizer_params, + batch_end_callback=mx.callback.Speedometer(batch_size), + epoch_end_callback=mx.callback.do_checkpoint(prefix=self._model_dir_ + self._model_prefix_, period=checkpoint_period), + begin_epoch=begin_epoch, + num_epoch=num_epoch + begin_epoch) + self.module.save_checkpoint(self._model_dir_ + self._model_prefix_, num_epoch + begin_epoch) + self.module.save_checkpoint(self._model_dir_ + self._model_prefix_ + '_newest', 0) + + + def construct(self, context, data_mean=None, data_std=None): +${tc.include(tc.architecture.body)} + self.module = mx.mod.Module(symbol=mx.symbol.Group([${tc.join(tc.architectureOutputs, ",")}]), + data_names=self._input_names_, + label_names=self._output_names_, + context=context) diff --git a/src/main/resources/templates/CNNPredictor.ftl b/src/main/resources/templates/CNNPredictor.ftl new file mode 100644 index 0000000..65a734b --- /dev/null +++ b/src/main/resources/templates/CNNPredictor.ftl @@ -0,0 +1,115 @@ +CAFFE2 + +#ifndef ${tc.fileNameWithoutEnding?upper_case} +#define ${tc.fileNameWithoutEnding?upper_case} + +#include + +#include +#include +#include + +#include + +class ${tc.fileNameWithoutEnding}{ +public: + const std::string json_file = "model/${tc.fullArchitectureName}/${tc.architectureName}_newest-symbol.json"; + const std::string param_file = "model/${tc.fullArchitectureName}/${tc.architectureName}_newest-0000.params"; + const std::vector input_keys = {"data"}; + //const std::vector input_keys = {${tc.join(tc.architectureInputs, ",", "\"", "\"")}}; + const std::vector> input_shapes = {<#list tc.architecture.inputs as input>{1,${tc.join(input.definition.type.dimensions, ",")}}<#if input?has_next>,}; + const bool use_gpu = false; + + PredictorHandle handle; + + explicit ${tc.fileNameWithoutEnding}(){ + init(json_file, param_file, input_keys, input_shapes, use_gpu); + } + + ~${tc.fileNameWithoutEnding}(){ + if(handle) MXPredFree(handle); + } + + void predict(${tc.join(tc.architectureInputs, ", ", "const vector &", "")}, + ${tc.join(tc.architectureOutputs, ", ", "vector &", "")}){ +<#list tc.architectureInputs as inputName> + MXPredSetInput(handle, "data", ${inputName}.data(), ${inputName}.size()); + //MXPredSetInput(handle, "${inputName}", ${inputName}.data(), ${inputName}.size()); + + + MXPredForward(handle); + + mx_uint output_index; + mx_uint *shape = 0; + mx_uint shape_len; + size_t size; + +<#list tc.architectureOutputs as outputName> + output_index = ${outputName?index?c}; + MXPredGetOutputShape(handle, output_index, &shape, &shape_len); + size = 1; + for (mx_uint i = 0; i < shape_len; ++i) size *= shape[i]; + assert(size == ${outputName}.size()); + MXPredGetOutput(handle, ${outputName?index?c}, &(${outputName}[0]), ${outputName}.size()); + + + } + + void init(const std::string &json_file, + const std::string ¶m_file, + const std::vector &input_keys, + const std::vector> &input_shapes, + const bool &use_gpu){ + + BufferFile json_data(json_file); + BufferFile param_data(param_file); + + int dev_type = use_gpu ? 2 : 1; + int dev_id = 0; + + handle = 0; + + if (json_data.GetLength() == 0 || + param_data.GetLength() == 0) { + std::exit(-1); + } + + const mx_uint num_input_nodes = input_keys.size(); + + const char* input_keys_ptr[num_input_nodes]; + for(mx_uint i = 0; i < num_input_nodes; i++){ + input_keys_ptr[i] = input_keys[i].c_str(); + } + + mx_uint shape_data_size = 0; + mx_uint input_shape_indptr[input_shapes.size() + 1]; + input_shape_indptr[0] = 0; + for(mx_uint i = 0; i < input_shapes.size(); i++){ + input_shape_indptr[i+1] = input_shapes[i].size(); + shape_data_size += input_shapes[i].size(); + } + + mx_uint input_shape_data[shape_data_size]; + mx_uint index = 0; + for(mx_uint i = 0; i < input_shapes.size(); i++){ + for(mx_uint j = 0; j < input_shapes[i].size(); j++){ + input_shape_data[index] = input_shapes[i][j]; + index++; + } + } + + MXPredCreate((const char*)json_data.GetBuffer(), + (const char*)param_data.GetBuffer(), + static_cast(param_data.GetLength()), + dev_type, + dev_id, + num_input_nodes, + input_keys_ptr, + input_shape_indptr, + input_shape_data, + &handle); + assert(handle); + } +}; + +#endif // ${tc.fileNameWithoutEnding?upper_case} diff --git a/src/main/resources/templates/elements/Add.ftl b/src/main/resources/templates/elements/Add.ftl new file mode 100644 index 0000000..da609cb --- /dev/null +++ b/src/main/resources/templates/elements/Add.ftl @@ -0,0 +1,2 @@ + ${element.name} = ${tc.join(element.inputs, " + ")} +<#include "OutputShape.ftl"> \ No newline at end of file diff --git a/src/main/resources/templates/elements/BatchNorm.ftl b/src/main/resources/templates/elements/BatchNorm.ftl new file mode 100644 index 0000000..d73d3b5 --- /dev/null +++ b/src/main/resources/templates/elements/BatchNorm.ftl @@ -0,0 +1,3 @@ + ${element.name} = mx.symbol.BatchNorm(data=${element.inputs[0]}, + fix_gamma=${element.fixGamma?string("True","False")}, + name="${element.name}") diff --git a/src/main/resources/templates/elements/Concatenate.ftl b/src/main/resources/templates/elements/Concatenate.ftl new file mode 100644 index 0000000..6c41ea1 --- /dev/null +++ b/src/main/resources/templates/elements/Concatenate.ftl @@ -0,0 +1,4 @@ + ${element.name} = mx.symbol.concat(${tc.join(element.inputs, ", ")}, + dim=1, + name="${element.name}") +<#include "OutputShape.ftl"> \ No newline at end of file diff --git a/src/main/resources/templates/elements/Convolution.ftl b/src/main/resources/templates/elements/Convolution.ftl new file mode 100644 index 0000000..57d2a2e --- /dev/null +++ b/src/main/resources/templates/elements/Convolution.ftl @@ -0,0 +1,15 @@ +<#assign input = element.inputs[0]> +<#if element.padding??> +<#assign input = element.name> + ${element.name} = mx.symbol.pad(data=${element.inputs[0]}, + mode='constant', + pad_width=(${tc.join(element.padding, ",")}), + constant_value=0) + + ${element.name} = mx.symbol.Convolution(data=${input}, + kernel=(${tc.join(element.kernel, ",")}), + stride=(${tc.join(element.stride, ",")}), + num_filter=${element.channels?c}, + no_bias=${element.noBias?string("True","False")}, + name="${element.name}") +<#include "OutputShape.ftl"> \ No newline at end of file diff --git a/src/main/resources/templates/elements/Dropout.ftl b/src/main/resources/templates/elements/Dropout.ftl new file mode 100644 index 0000000..5fa111f --- /dev/null +++ b/src/main/resources/templates/elements/Dropout.ftl @@ -0,0 +1,3 @@ + ${element.name} = mx.symbol.Dropout(data=${element.inputs[0]}, + p=${element.p?c}, + name="${element.name}") diff --git a/src/main/resources/templates/elements/Flatten.ftl b/src/main/resources/templates/elements/Flatten.ftl new file mode 100644 index 0000000..85e9859 --- /dev/null +++ b/src/main/resources/templates/elements/Flatten.ftl @@ -0,0 +1,2 @@ + ${element.name} = mx.symbol.Flatten(data=${element.inputs[0]}, + name="${element.name}") \ No newline at end of file diff --git a/src/main/resources/templates/elements/FullyConnected.ftl b/src/main/resources/templates/elements/FullyConnected.ftl new file mode 100644 index 0000000..d9d42f0 --- /dev/null +++ b/src/main/resources/templates/elements/FullyConnected.ftl @@ -0,0 +1,10 @@ +<#assign flatten = element.element.inputTypes[0].height != 1 || element.element.inputTypes[0].width != 1> +<#assign input = element.inputs[0]> +<#if flatten> + ${element.name} = mx.symbol.flatten(data=${input}) +<#assign input = element.name> + + ${element.name} = mx.symbol.FullyConnected(data=${input}, + num_hidden=${element.units?c}, + no_bias=${element.noBias?string("True","False")}, + name="${element.name}") diff --git a/src/main/resources/templates/elements/Get.ftl b/src/main/resources/templates/elements/Get.ftl new file mode 100644 index 0000000..02e3d3d --- /dev/null +++ b/src/main/resources/templates/elements/Get.ftl @@ -0,0 +1 @@ + ${element.name} = ${element.inputs[element.index]} diff --git a/src/main/resources/templates/elements/GlobalPooling.ftl b/src/main/resources/templates/elements/GlobalPooling.ftl new file mode 100644 index 0000000..3d21270 --- /dev/null +++ b/src/main/resources/templates/elements/GlobalPooling.ftl @@ -0,0 +1,6 @@ + ${element.name} = mx.symbol.Pooling(data=${element.inputs[0]}, + global_pool=True, + kernel=(1,1), + pool_type=${element.poolType}, + name="${element.name}") +<#include "OutputShape.ftl"> \ No newline at end of file diff --git a/src/main/resources/templates/elements/Input.ftl b/src/main/resources/templates/elements/Input.ftl new file mode 100644 index 0000000..fac5a6b --- /dev/null +++ b/src/main/resources/templates/elements/Input.ftl @@ -0,0 +1,28 @@ +<#assign channelIndex = element.element.outputTypes[0].channelIndex + 1> +<#assign heightIndex = element.element.outputTypes[0].heightIndex + 1> +<#assign widthIndex = element.element.outputTypes[0].widthIndex + 1> +<#assign indexList = []> +<#if channelIndex != 0><#assign indexList = indexList + [channelIndex]> +<#if heightIndex != 0><#assign indexList = indexList + [heightIndex]> +<#if widthIndex != 0><#assign indexList = indexList + [widthIndex]> +<#assign dimensions = element.element.outputTypes[0].dimensions> + ${element.name} = mx.sym.var("${element.name}", + shape=(0,${tc.join(dimensions, ",")})) +<#include "OutputShape.ftl"> +<#if heightIndex != channelIndex + 1 || widthIndex != heightIndex + 1> + ${element.name} = mx.symbol.transpose(data=${element.name}, + axes=(0,${tc.join(indexList, ",")})) + + +<#if indexList?size != 3> + ${element.name} = mx.symbol.reshape(data=${element.name}, + shape=(0,${element.element.outputTypes[0].channels?c},${element.element.outputTypes[0].height?c},${element.element.outputTypes[0].width?c})) + + if not data_mean is None: + assert(not data_std is None) + _data_mean_ = mx.sym.Variable("_data_mean_", shape=(${tc.join(dimensions, ",")}), init=MyConstant(value=data_mean.tolist())) + _data_mean_ = mx.sym.BlockGrad(_data_mean_) + _data_std_ = mx.sym.Variable("_data_std_", shape=(${tc.join(dimensions, ",")}), init=MyConstant(value=data_mean.tolist())) + _data_std_ = mx.sym.BlockGrad(_data_std_) + ${element.name} = mx.symbol.broadcast_sub(${element.name}, _data_mean_) + ${element.name} = mx.symbol.broadcast_div(${element.name}, _data_std_) diff --git a/src/main/resources/templates/elements/Lrn.ftl b/src/main/resources/templates/elements/Lrn.ftl new file mode 100644 index 0000000..b1ecff6 --- /dev/null +++ b/src/main/resources/templates/elements/Lrn.ftl @@ -0,0 +1,6 @@ + ${element.name} = mx.symbol.LRN(data=${element.inputs[0]}, + alpha=${element.alpha?c}, + beta=${element.beta?c}, + knorm=${element.knorm?c}, + nsize=${element.nsize?c}, + name="${element.name}") diff --git a/src/main/resources/templates/elements/Output.ftl b/src/main/resources/templates/elements/Output.ftl new file mode 100644 index 0000000..cd80d10 --- /dev/null +++ b/src/main/resources/templates/elements/Output.ftl @@ -0,0 +1,11 @@ + +<#if element.softmaxOutput> + ${element.name} = mx.symbol.SoftmaxOutput(data=${element.inputs[0]}, + name="${element.name}") +<#elseif element.logisticRegressionOutput> + ${element.name} = mx.symbol.LogisticRegressionOutput(data=${element.inputs[0]}, + name="${element.name}") +<#elseif element.linearRegressionOutput> + ${element.name} = mx.symbol.LinearRegressionOutput(data=${element.inputs[0]}, + name="${element.name}") + \ No newline at end of file diff --git a/src/main/resources/templates/elements/OutputShape.ftl b/src/main/resources/templates/elements/OutputShape.ftl new file mode 100644 index 0000000..d41a3d4 --- /dev/null +++ b/src/main/resources/templates/elements/OutputShape.ftl @@ -0,0 +1,2 @@ + # ${element.name}, output shape: {<#list element.element.outputTypes as type>[${tc.join(type.dimensions, ",")}]} + diff --git a/src/main/resources/templates/elements/Pooling.ftl b/src/main/resources/templates/elements/Pooling.ftl new file mode 100644 index 0000000..e4dba2b --- /dev/null +++ b/src/main/resources/templates/elements/Pooling.ftl @@ -0,0 +1,14 @@ +<#assign input = element.inputs[0]> +<#if element.padding??> +<#assign input = element.name> + ${element.name} = mx.symbol.pad(data=${element.inputs[0]}, + mode='constant', + pad_width=(${tc.join(element.padding, ",")}), + constant_value=0) + + ${element.name} = mx.symbol.Pooling(data=${input}, + kernel=(${tc.join(element.kernel, ",")}), + pool_type=${element.poolType}, + stride=(${tc.join(element.stride, ",")}), + name="${element.name}") +<#include "OutputShape.ftl"> \ No newline at end of file diff --git a/src/main/resources/templates/elements/Relu.ftl b/src/main/resources/templates/elements/Relu.ftl new file mode 100644 index 0000000..4af183b --- /dev/null +++ b/src/main/resources/templates/elements/Relu.ftl @@ -0,0 +1,4 @@ + ${element.name} = mx.symbol.Activation(data=${element.inputs[0]}, + act_type='relu', + name="${element.name}") + diff --git a/src/main/resources/templates/elements/Sigmoid.ftl b/src/main/resources/templates/elements/Sigmoid.ftl new file mode 100644 index 0000000..e5a4033 --- /dev/null +++ b/src/main/resources/templates/elements/Sigmoid.ftl @@ -0,0 +1,3 @@ + ${element.name} = mx.symbol.Activation(data=${element.inputs[0]}, + act_type='sigmoid', + name="${element.name}") diff --git a/src/main/resources/templates/elements/Softmax.ftl b/src/main/resources/templates/elements/Softmax.ftl new file mode 100644 index 0000000..b1af2ef --- /dev/null +++ b/src/main/resources/templates/elements/Softmax.ftl @@ -0,0 +1,4 @@ +<#-- This template is not used if the followiing architecture element is an output. See Output.ftl --> + ${element.name} = mx.symbol.softmax(data=${element.inputs[0]}, + axis=1, + name="${element.name}") diff --git a/src/main/resources/templates/elements/Split.ftl b/src/main/resources/templates/elements/Split.ftl new file mode 100644 index 0000000..919c87a --- /dev/null +++ b/src/main/resources/templates/elements/Split.ftl @@ -0,0 +1,5 @@ + ${element.name} = mx.symbol.split(data=${element.inputs[0]}, + num_outputs=${element.numOutputs?c}, + axis=1, + name="${element.name}") +<#include "OutputShape.ftl"> \ No newline at end of file diff --git a/src/main/resources/templates/elements/Tanh.ftl b/src/main/resources/templates/elements/Tanh.ftl new file mode 100644 index 0000000..6295a95 --- /dev/null +++ b/src/main/resources/templates/elements/Tanh.ftl @@ -0,0 +1,3 @@ + ${element.name} = mx.symbol.Activation(data=${element.inputs[0]}, + act_type='tanh', + name="${element.name}") diff --git a/src/main/resources/templates/execute.ftl b/src/main/resources/templates/execute.ftl new file mode 100644 index 0000000..0a72561 --- /dev/null +++ b/src/main/resources/templates/execute.ftl @@ -0,0 +1,21 @@ +<#list tc.architecture.outputs as output> + <#assign shape = output.definition.type.dimensions> + vector CNN_${tc.getName(output)}(<#list shape as dim>${dim?c}<#if dim?has_next>*); + + + _cnn_.predict(<#list tc.architecture.inputs as input>CNNTranslator::translate(${input.name}<#if input.arrayAccess.isPresent()>[${input.arrayAccess.get().intValue.get()?c}]), + <#list tc.architecture.outputs as output>CNN_${tc.getName(output)}<#if output?has_next>, + ); + +<#list tc.architecture.outputs as output> +<#assign shape = output.definition.type.dimensions> +<#if shape?size == 1> + ${output.name}<#if output.arrayAccess.isPresent()>[${output.arrayAccess.get().intValue.get()?c}] = CNNTranslator::translateToCol(CNN_${tc.getName(output)}, std::vector {${shape[0]?c}}); + +<#if shape?size == 2> + ${output.name}<#if output.arrayAccess.isPresent()>[${output.arrayAccess.get().intValue.get()?c}] = CNNTranslator::translateToMat(CNN_${tc.getName(output)}, std::vector {${shape[0]?c}, ${shape[1]?c}}); + +<#if shape?size == 3> + ${output.name}<#if output.arrayAccess.isPresent()>[${output.arrayAccess.get().intValue.get()?c}] = CNNTranslator::translateToCube(CNN_${tc.getName(output)}, std::vector {${shape[0]?c}, ${shape[1]?c}, ${shape[2]?c}}); + + diff --git a/src/test/java/de/monticore/lang/monticar/cnnarch/AbstractSymtabTest.java b/src/test/java/de/monticore/lang/monticar/cnnarch/AbstractSymtabTest.java new file mode 100644 index 0000000..bab3623 --- /dev/null +++ b/src/test/java/de/monticore/lang/monticar/cnnarch/AbstractSymtabTest.java @@ -0,0 +1,142 @@ +/** + * + * ****************************************************************************** + * MontiCAR Modeling Family, www.se-rwth.de + * Copyright (c) 2017, Software Engineering Group at RWTH Aachen, + * All rights reserved. + * + * This project is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this project. If not, see . + * ******************************************************************************* + */ +package de.monticore.lang.monticar.cnnarch; + +import de.monticore.ModelingLanguageFamily; +import de.monticore.io.paths.ModelPath; +import de.monticore.lang.monticar.cnnarch._symboltable.CNNArchCompilationUnitSymbol; +import de.monticore.lang.monticar.cnnarch._symboltable.CNNArchLanguage; +import de.monticore.symboltable.GlobalScope; +import de.monticore.symboltable.Scope; +import org.junit.Assert; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Common methods for symboltable tests + */ +public class AbstractSymtabTest { + + private static final String MODEL_PATH = "src/test/resources/"; + + protected static Scope createSymTab(String... modelPath) { + ModelingLanguageFamily fam = new ModelingLanguageFamily(); + + fam.addModelingLanguage(new CNNArchLanguage()); + + final ModelPath mp = new ModelPath(); + for (String m : modelPath) { + mp.addEntry(Paths.get(m)); + } + GlobalScope scope = new GlobalScope(mp, fam); + + return scope; + } + +/* protected static ASTCNNArchCompilationUnit getAstNode(String modelPath, String model) { + Scope symTab = createSymTab(MODEL_PATH + modelPath); + CNNArchCompilationUnitSymbol comp = symTab. resolve( + model, CNNArchCompilationUnitSymbol.KIND).orElse(null); + assertNotNull("Could not resolve model " + model, comp); + + return (ASTCNNArchCompilationUnit) comp.getAstNode().get(); + }*/ + + protected static CNNArchCompilationUnitSymbol getCompilationUnitSymbol(String modelPath, String model) { + Scope symTab = createSymTab(MODEL_PATH + modelPath); + CNNArchCompilationUnitSymbol comp = symTab. resolve( + model, CNNArchCompilationUnitSymbol.KIND).orElse(null); + assertNotNull("Could not resolve model " + model, comp); + + return comp; + } + + + + public static void checkFilesAreEqual(Path generationPath, Path resultsPath, List fileNames) { + for (String fileName : fileNames){ + File genFile = new File(generationPath.toString() + "/" + fileName); + File fileTarget = new File(resultsPath.toString() + "/" + fileName); + assertTrue(areBothFilesEqual(genFile, fileTarget)); + } + } + + public static boolean areBothFilesEqual(File file1, File file2) { + if (!file1.exists()) { + Assert.fail("file does not exist: " + file1.getAbsolutePath()); + return false; + } + if (!file2.exists()) { + Assert.fail("file does not exist: " + file2.getAbsolutePath()); + return false; + } + List lines1; + List lines2; + try { + lines1 = Files.readAllLines(file1.toPath()); + lines2 = Files.readAllLines(file2.toPath()); + } catch (IOException e) { + e.printStackTrace(); + Assert.fail("IO error: " + e.getMessage()); + return false; + } + lines1 = discardEmptyLines(lines1); + lines2 = discardEmptyLines(lines2); + if (lines1.size() != lines2.size()) { + Assert.fail( + "files have different number of lines: " + + file1.getAbsolutePath() + + " has " + lines1 + + " lines and " + file2.getAbsolutePath() + " has " + lines2 + " lines" + ); + return false; + } + int len = lines1.size(); + for (int i = 0; i < len; i++) { + String l1 = lines1.get(i); + String l2 = lines2.get(i); + Assert.assertEquals("files differ in " + i + " line: " + + file1.getAbsolutePath() + + " has " + l1 + + " and " + file2.getAbsolutePath() + " has " + l2, + l1, + l2 + ); + } + return true; + } + + private static List discardEmptyLines(List lines) { + return lines.stream() + .map(String::trim) + .filter(l -> !l.isEmpty()) + .collect(Collectors.toList()); + } +} diff --git a/src/test/java/de/monticore/lang/monticar/cnnarch/GenerationTest.java b/src/test/java/de/monticore/lang/monticar/cnnarch/GenerationTest.java new file mode 100644 index 0000000..8015b25 --- /dev/null +++ b/src/test/java/de/monticore/lang/monticar/cnnarch/GenerationTest.java @@ -0,0 +1,117 @@ +/** + * + * ****************************************************************************** + * MontiCAR Modeling Family, www.se-rwth.de + * Copyright (c) 2017, Software Engineering Group at RWTH Aachen, + * All rights reserved. + * + * This project is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this project. If not, see . + * ******************************************************************************* + */ +package de.monticore.lang.monticar.cnnarch; + +import de.monticore.lang.monticar.cnnarchcaffe2.generator.CNNArchGeneratorCliCaffe2; +import de.se_rwth.commons.logging.Log; +import freemarker.template.TemplateException; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Arrays; + +import static junit.framework.TestCase.assertTrue; + +public class GenerationTest extends AbstractSymtabTest{ + + @Before + public void setUp() { + // ensure an empty log + Log.getFindings().clear(); + Log.enableFailQuick(false); + } + + @Test + public void testCifar10Classifier() throws IOException, TemplateException { + Log.getFindings().clear(); + String[] args = {"-m", "src/test/resources/valid_tests", "-r", "CifarClassifierNetwork", "-o", "./target/generated-sources-cnnarch/"}; + CNNArchGeneratorCliCaffe2.main(args); + assertTrue(Log.getFindings().isEmpty()); + + checkFilesAreEqual( + Paths.get("./target/generated-sources-cnnarch"), + Paths.get("./src/test/resources/target_code"), + Arrays.asList( + "CNNCreator_CifarClassifierNetwork.py", + "CNNPredictor_CifarClassifierNetwork.h", + "execute_CifarClassifierNetwork", + "CNNBufferFile.h")); + } + + @Test + public void testAlexnetGeneration() throws IOException, TemplateException { + Log.getFindings().clear(); + String[] args = {"-m", "src/test/resources/architectures", "-r", "Alexnet", "-o", "./target/generated-sources-cnnarch/"}; + CNNArchGeneratorCliCaffe2.main(args); + assertTrue(Log.getFindings().isEmpty()); + + checkFilesAreEqual( + Paths.get("./target/generated-sources-cnnarch"), + Paths.get("./src/test/resources/target_code"), + Arrays.asList( + "CNNCreator_Alexnet.py", + "CNNPredictor_Alexnet.h", + "execute_Alexnet")); + } + + @Test + public void testGeneratorVGG16() throws IOException, TemplateException { + Log.getFindings().clear(); + String[] args = {"-m", "src/test/resources/architectures", "-r", "VGG16", "-o", "./target/generated-sources-cnnarch/"}; + CNNArchGeneratorCliCaffe2.main(args); + assertTrue(Log.getFindings().isEmpty()); + + checkFilesAreEqual( + Paths.get("./target/generated-sources-cnnarch"), + Paths.get("./src/test/resources/target_code"), + Arrays.asList( + "CNNCreator_VGG16.py", + "CNNPredictor_VGG16.h", + "execute_VGG16")); + } + + + @Test + public void testThreeInputCNNGeneration() throws IOException, TemplateException { + Log.getFindings().clear(); + String[] args = {"-m", "src/test/resources/architectures", "-r", "ThreeInputCNN_M14"}; + CNNArchGeneratorCliCaffe2.main(args); + assertTrue(Log.getFindings().size() == 1); + } + + @Test + public void testResNeXtGeneration() throws IOException, TemplateException { + Log.getFindings().clear();; + String[] args = {"-m", "src/test/resources/architectures", "-r", "ResNeXt50"}; + CNNArchGeneratorCliCaffe2.main(args); + assertTrue(Log.getFindings().isEmpty()); + } + + @Test + public void testMultipleOutputs() throws IOException, TemplateException { + Log.getFindings().clear(); + String[] args = {"-m", "src/test/resources/valid_tests", "-r", "MultipleOutputs"}; + CNNArchGeneratorCliCaffe2.main(args); + assertTrue(Log.getFindings().size() == 3); + } +} diff --git a/src/test/java/de/monticore/lang/monticar/cnnarch/SymtabTest.java b/src/test/java/de/monticore/lang/monticar/cnnarch/SymtabTest.java new file mode 100644 index 0000000..d30f376 --- /dev/null +++ b/src/test/java/de/monticore/lang/monticar/cnnarch/SymtabTest.java @@ -0,0 +1,85 @@ +/** + * + * ****************************************************************************** + * MontiCAR Modeling Family, www.se-rwth.de + * Copyright (c) 2017, Software Engineering Group at RWTH Aachen, + * All rights reserved. + * + * This project is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this project. If not, see . + * ******************************************************************************* + */ +package de.monticore.lang.monticar.cnnarch; + +import de.monticore.lang.monticar.cnnarch._parser.CNNArchParser; +import de.monticore.lang.monticar.cnnarch._symboltable.CNNArchCompilationUnitSymbol; +import de.monticore.symboltable.Scope; +import de.se_rwth.commons.logging.Log; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class SymtabTest extends AbstractSymtabTest { + + @Before + public void setUp() { + // ensure an empty log + Log.getFindings().clear(); + Log.enableFailQuick(false); + } + + @Test + public void testParsing() throws Exception { + CNNArchParser parser = new CNNArchParser(); + assertTrue(parser.parse("src/test/resources/architectures/Alexnet.cnna").isPresent()); + } + + @Ignore + @Test + public void testAlexnet(){ + Scope symTab = createSymTab("src/test/resources/architectures"); + CNNArchCompilationUnitSymbol a = symTab.resolve( + "Alexnet", + CNNArchCompilationUnitSymbol.KIND).orElse(null); + assertNotNull(a); + a.resolve(); + a.getArchitecture().getBody().getOutputTypes(); + } + + @Ignore + @Test + public void testResNeXt(){ + Scope symTab = createSymTab("src/test/resources/architectures"); + CNNArchCompilationUnitSymbol a = symTab.resolve( + "ResNeXt50", + CNNArchCompilationUnitSymbol.KIND).orElse(null); + assertNotNull(a); + a.resolve(); + a.getArchitecture().getBody().getOutputTypes(); + } + + @Ignore + @Test + public void test3(){ + Scope symTab = createSymTab("src/test/resources/valid_tests"); + CNNArchCompilationUnitSymbol a = symTab.resolve( + "MultipleOutputs", + CNNArchCompilationUnitSymbol.KIND).orElse(null); + assertNotNull(a); + a.resolve(); + a.getArchitecture().getBody().getOutputTypes(); + } + +} diff --git a/src/test/resources/architectures/Alexnet.cnna b/src/test/resources/architectures/Alexnet.cnna new file mode 100644 index 0000000..d42958d --- /dev/null +++ b/src/test/resources/architectures/Alexnet.cnna @@ -0,0 +1,43 @@ +architecture Alexnet(img_height=224, img_width=224, img_channels=3, classes=10){ + def input Z(0:255)^{img_channels, img_height, img_width} data + def output Q(0:1)^{classes} predictions + + def split1(i){ + [i] -> + Convolution(kernel=(5,5), channels=128) -> + Lrn(nsize=5, alpha=0.0001, beta=0.75) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), padding="no_loss") -> + Relu() + } + def split2(i){ + [i] -> + Convolution(kernel=(3,3), channels=192) -> + Relu() -> + Convolution(kernel=(3,3), channels=128) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), padding="no_loss") -> + Relu() + } + def fc(){ + FullyConnected(units=4096) -> + Relu() -> + Dropout() + } + + data -> + Convolution(kernel=(11,11), channels=96, stride=(4,4), padding="no_loss") -> + Lrn(nsize=5, alpha=0.0001, beta=0.75) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), padding="no_loss") -> + Relu() -> + Split(n=2) -> + split1(i=[0|1]) -> + Concatenate() -> + Convolution(kernel=(3,3), channels=384) -> + Relu() -> + Split(n=2) -> + split2(i=[0|1]) -> + Concatenate() -> + fc(->=2) -> + FullyConnected(units=10) -> + Softmax() -> + predictions +} \ No newline at end of file diff --git a/src/test/resources/architectures/ResNeXt50.cnna b/src/test/resources/architectures/ResNeXt50.cnna new file mode 100644 index 0000000..78cf41e --- /dev/null +++ b/src/test/resources/architectures/ResNeXt50.cnna @@ -0,0 +1,44 @@ +architecture ResNeXt50(img_height=224, img_width=224, img_channels=3, classes=1000){ + def input Z(0:255)^{img_channels, img_height, img_width} data + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels, stride=1, act=true){ + Convolution(kernel=(kernel,kernel), channels=channels, stride=(stride,stride)) -> + BatchNorm() -> + Relu(?=act) + } + def resGroup(innerChannels, outChannels, stride=1){ + conv(kernel=1, channels=innerChannels) -> + conv(kernel=3, channels=innerChannels, stride=stride) -> + conv(kernel=1, channels=outChannels, act=false) + } + def resLayer(innerChannels, outChannels, stride=1, addSkipConv=false){ + ( + resGroup(innerChannels=innerChannels, + outChannels=outChannels, + stride=stride, + | = 32) -> + Add() + | + conv(kernel=1, channels=outChannels, stride=stride, act=false, ? = addSkipConv) + ) -> + Add() -> + Relu() + } + + data -> + conv(kernel=7, channels=64, stride=2) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2)) -> + resLayer(innerChannels=4, outChannels=256, addSkipConv=true) -> + resLayer(innerChannels=4, outChannels=256, -> = 2) -> + resLayer(innerChannels=8, outChannels=512, stride=2, addSkipConv=true) -> + resLayer(innerChannels=8, outChannels=512, -> = 3) -> + resLayer(innerChannels=16, outChannels=1024, stride=2, addSkipConv=true) -> + resLayer(innerChannels=16, outChannels=1024, -> = 5) -> + resLayer(innerChannels=32, outChannels=2048, stride=2, addSkipConv=true) -> + resLayer(innerChannels=32, outChannels=2048, -> = 2) -> + GlobalPooling(pool_type="avg") -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} \ No newline at end of file diff --git a/src/test/resources/architectures/ResNet152.cnna b/src/test/resources/architectures/ResNet152.cnna new file mode 100644 index 0000000..60031f2 --- /dev/null +++ b/src/test/resources/architectures/ResNet152.cnna @@ -0,0 +1,37 @@ +architecture ResNet152(img_height=224, img_width=224, img_channels=3, classes=1000){ + def input Z(0:255)^{img_channels, img_height, img_width} data + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels, stride=1, act=true){ + Convolution(kernel=(kernel,kernel), channels=channels, stride=(stride,stride)) -> + BatchNorm() -> + Relu(?=act) + } + def resLayer(channels, stride=1, addSkipConv=false){ + ( + conv(kernel=1, channels=channels, stride=stride) -> + conv(kernel=3, channels=channels) -> + conv(kernel=1, channels=4*channels, act=false) + | + conv(kernel=1, channels=4*channels, stride=stride, act=false, ? = addSkipConv) + ) -> + Add() -> + Relu() + } + + data -> + conv(kernel=7, channels=64, stride=2) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2)) -> + resLayer(channels=64, addSkipConv=true) -> + resLayer(channels=64, ->=2) -> + resLayer(channels=128, stride=2, addSkipConv=true) -> + resLayer(channels=128, ->=7) -> + resLayer(channels=256, stride=2, addSkipConv=true) -> + resLayer(channels=256, ->=35) -> + resLayer(channels=512, stride=2, addSkipConv=true) -> + resLayer(channels=512, ->=2) -> + GlobalPooling(pool_type="avg") -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} \ No newline at end of file diff --git a/src/test/resources/architectures/ResNet34.cnna b/src/test/resources/architectures/ResNet34.cnna new file mode 100644 index 0000000..bf9173b --- /dev/null +++ b/src/test/resources/architectures/ResNet34.cnna @@ -0,0 +1,35 @@ +architecture ResNet34(img_height=224, img_width=224, img_channels=3, classes=1000){ + def input Z(0:255)^{img_channels, img_height, img_width} data + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels, stride=1, act=true){ + Convolution(kernel=(kernel,kernel), channels=channels, stride=(stride,stride)) -> + BatchNorm() -> + Relu(?=act) + } + def resLayer(channels, stride=1){ + ( + conv(kernel=3, channels=channels, stride=stride) -> + conv(kernel=3, channels=channels, act=false) + | + conv(kernel=1, channels=channels, stride=stride, act=false, ? = (stride != 1)) + ) -> + Add() -> + Relu() + } + + data -> + conv(kernel=7, channels=64, stride=2) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2)) -> + resLayer(channels=64, ->=3) -> + resLayer(channels=128, stride=2) -> + resLayer(channels=128, ->=3) -> + resLayer(channels=256, stride=2) -> + resLayer(channels=256, ->=5) -> + resLayer(channels=512, stride=2) -> + resLayer(channels=512, ->=2) -> + GlobalPooling(pool_type="avg") -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} diff --git a/src/test/resources/architectures/SequentialAlexnet.cnna b/src/test/resources/architectures/SequentialAlexnet.cnna new file mode 100644 index 0000000..66ad2a7 --- /dev/null +++ b/src/test/resources/architectures/SequentialAlexnet.cnna @@ -0,0 +1,29 @@ +architecture SequentialAlexnet(img_height=224, img_width=224, img_channels=3, classes=10){ + def input Z(0:255)^{img_channels, img_height, img_width} data + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels, hasPool=true, convStride=(1,1)){ + Convolution(kernel=kernel, channels=channels, stride=convStride) -> + Relu() -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), ?=hasPool) + } + def fc(){ + FullyConnected(units=4096) -> + Relu() -> + Dropout() + } + + data -> + conv(kernel=(11,11), channels=96, convStride=(4,4)) -> + Lrn(nsize=5, alpha=0.0001, beta=0.75) -> + conv(kernel=(5,5), channels=256, convStride=(4,4)) -> + Lrn(nsize=5, alpha=0.0001, beta=0.75) -> + conv(kernel=(3,3), channels=384, hasPool=false) -> + conv(kernel=(3,3), channels=384, hasPool=false) -> + conv(kernel=(3,3), channels=256) -> + fc() -> + fc() -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} diff --git a/src/test/resources/architectures/ThreeInputCNN_M14.cnna b/src/test/resources/architectures/ThreeInputCNN_M14.cnna new file mode 100644 index 0000000..95f50f4 --- /dev/null +++ b/src/test/resources/architectures/ThreeInputCNN_M14.cnna @@ -0,0 +1,32 @@ +architecture ThreeInputCNN_M14(img_height=200, img_width=300, img_channels=3, classes=3){ + /*CNN used for flower grading. Model 14 of the paper. + *Title: Multi-Input Convolutional Neural Network for Flower Grading + *Authors: Yu Sun, Lin Zhu, Guan Wang, and Fang Zhao. + *Year: 2017*/ + + def input Z(0:255)^{img_channels, img_height, img_width} data[3] + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels){ + Convolution(kernel=kernel, channels=channels) -> + Relu() + } + + def inputGroup(index){ + [index] -> + conv(kernel=(3,3), channels=32, ->=3) -> + Pooling(pool_type="max", kernel=(2,2), stride=(2,2)) + } + + data -> + inputGroup(index=[0|..|2]) -> + Concatenate() -> + conv(kernel=(3,3), channels=64) -> + Pooling(pool_type="max", kernel=(2,2), stride=(2,2)) -> + + FullyConnected(units=32) -> + Relu() -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} \ No newline at end of file diff --git a/src/test/resources/architectures/VGG16.cnna b/src/test/resources/architectures/VGG16.cnna new file mode 100644 index 0000000..dec4a1d --- /dev/null +++ b/src/test/resources/architectures/VGG16.cnna @@ -0,0 +1,31 @@ +architecture VGG16(img_height=224, img_width=224, img_channels=3, classes=1000){ + def input Z(0:255)^{img_channels, img_height, img_width} data + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels){ + Convolution(kernel=(kernel,kernel), channels=channels) -> + Relu() + } + def fc(){ + FullyConnected(units=4096) -> + Relu() -> + Dropout(p=0.5) + } + + data -> + conv(kernel=3, channels=64, ->=2) -> + Pooling(pool_type="max", kernel=(2,2), stride=(2,2)) -> + conv(kernel=3, channels=128, ->=2) -> + Pooling(pool_type="max", kernel=(2,2), stride=(2,2)) -> + conv(kernel=3, channels=256, ->=3) -> + Pooling(pool_type="max", kernel=(2,2), stride=(2,2)) -> + conv(kernel=3, channels=512, ->=3) -> + Pooling(pool_type="max", kernel=(2,2), stride=(2,2)) -> + conv(kernel=3, channels=512, ->=3) -> + Pooling(pool_type="max", kernel=(2,2), stride=(2,2)) -> + fc() -> + fc() -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} diff --git a/src/test/resources/invalid_tests/ArgumentConstraintTest1.cnna b/src/test/resources/invalid_tests/ArgumentConstraintTest1.cnna new file mode 100644 index 0000000..8436e3e --- /dev/null +++ b/src/test/resources/invalid_tests/ArgumentConstraintTest1.cnna @@ -0,0 +1,34 @@ +architecture ArgumentConstraintTest1(img_height=224, img_width=224, img_channels=3, classes=1000){ + def input Z(0:255)^{img_channels, img_height, img_width} image + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels, stride=1, act=true){ + Convolution(kernel=(kernel,kernel), channels=channels, stride=(stride,stride)) -> + BatchNorm() -> + Relu(?=act) + } + def skip(channels, stride){ + Convolution(kernel=(1,1), channels=75, stride=(stride,stride)) -> + BatchNorm() + } + def resLayer(channels, stride=1){ + ( + conv(kernel=3, channels=channels, stride=stride) -> + conv(kernel=3, channels=channels, stride=stride, act=false) + | + skip(channels=channels, stride=stride, ?=(stride!=1)) + ) -> + Add() -> + Relu() + } + + image -> + conv(kernel=7, channels=64, stride=2) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2)) -> + resLayer(channels=64, ->=3) -> + resLayer(channels=128, stride=2) -> + GlobalPooling(pool_type="avg") -> + FullyConnected(units=classes, ->=true) -> + Softmax() -> + predictions +} diff --git a/src/test/resources/invalid_tests/ArgumentConstraintTest2.cnna b/src/test/resources/invalid_tests/ArgumentConstraintTest2.cnna new file mode 100644 index 0000000..59b8611 --- /dev/null +++ b/src/test/resources/invalid_tests/ArgumentConstraintTest2.cnna @@ -0,0 +1,34 @@ +architecture ArgumentConstraintTest2(img_height=224, img_width=224, img_channels=3, classes=1000){ + def input Z(0:255)^{img_channels, img_height, img_width} image + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels, stride=1, act=true){ + Convolution(kernel=(kernel,kernel), channels=channels, stride=(stride,stride)) -> + BatchNorm() -> + Relu(?=act) + } + def skip(channels, stride){ + Convolution(kernel=(1,1), channels=96, stride=(stride,-stride)) -> + BatchNorm() + } + def resLayer(channels, stride=1){ + ( + conv(kernel=3, channels=channels, stride=stride) -> + conv(kernel=3, channels=channels, stride=stride, act=false) + | + skip(channels=channels, stride=stride, ?=(stride!=1)) + ) -> + Add() -> + Relu() + } + + image -> + conv(kernel=7, channels=64, stride=2) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2)) -> + resLayer(channels=64, ->=3) -> + resLayer(channels=128, stride=2) -> + GlobalPooling(pool_type="avg") -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} diff --git a/src/test/resources/invalid_tests/ArgumentConstraintTest3.cnna b/src/test/resources/invalid_tests/ArgumentConstraintTest3.cnna new file mode 100644 index 0000000..ee333ab --- /dev/null +++ b/src/test/resources/invalid_tests/ArgumentConstraintTest3.cnna @@ -0,0 +1,34 @@ +architecture ArgumentConstraintTest3(img_height=224, img_width=224, img_channels=3, classes=1000){ + def input Z(0:255)^{img_channels, img_height, img_width} image + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels, stride=1, act=true){ + Convolution(kernel=(kernel,kernel), channels=channels, stride=(stride,stride)) -> + BatchNorm() -> + Relu(?=act) + } + def skip(channels, stride){ + Convolution(kernel=(1,1), channels=64, stride=(stride,stride)) -> + BatchNorm() + } + def resLayer(channels, stride=1){ + ( + conv(kernel=3, channels=channels, stride=stride) -> + conv(kernel=3, channels=channels, stride=stride, act=false) + | + skip(channels=channels, stride=stride, ?=(stride!=1)) + ) -> + Add() -> + Relu() + } + + image -> + conv(kernel=7, channels=64, stride=2) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), padding="valid") -> + resLayer(channels=64, ->=3) -> + resLayer(channels=128, stride=2) -> + GlobalPooling(pool_type="avg", ?=1) -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} diff --git a/src/test/resources/invalid_tests/ArgumentConstraintTest4.cnna b/src/test/resources/invalid_tests/ArgumentConstraintTest4.cnna new file mode 100644 index 0000000..f06b6a8 --- /dev/null +++ b/src/test/resources/invalid_tests/ArgumentConstraintTest4.cnna @@ -0,0 +1,34 @@ +architecture ArgumentConstraintTest4(img_height=224, img_width=224, img_channels=3, classes=1000){ + def input Z(0:255)^{img_channels, img_height, img_width} image + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels, stride=1, act=true){ + Convolution(kernel=(kernel,kernel), channels=channels, stride=(stride,stride)) -> + BatchNorm() -> + Relu(?=act) + } + def skip(channels, stride){ + Convolution(kernel=(1,1), channels=96, stride=(stride,stride)) -> + BatchNorm() + } + def resLayer(channels, stride=1){ + ( + conv(kernel=3, channels=channels, stride=stride) -> + conv(kernel=3, channels=channels, stride=stride, act=false) + | + skip(channels=channels, stride=stride, ?=(stride!=1)) + ) -> + Add() -> + Relu() + } + + image -> + conv(kernel=7, channels=64, stride=2) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), padding=1) -> + resLayer(channels=64, ->=3) -> + resLayer(channels=128, stride=2) -> + GlobalPooling(pool_type="avg") -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} diff --git a/src/test/resources/invalid_tests/ArgumentConstraintTest5.cnna b/src/test/resources/invalid_tests/ArgumentConstraintTest5.cnna new file mode 100644 index 0000000..42446f0 --- /dev/null +++ b/src/test/resources/invalid_tests/ArgumentConstraintTest5.cnna @@ -0,0 +1,34 @@ +architecture ArgumentConstraintTest5(img_height=224, img_width=224, img_channels=3, classes=1000){ + def input Z(0:255)^{img_channels, img_height, img_width} image + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels, stride=1, act=true){ + Convolution(kernel=(kernel,kernel), channels=channels, stride=(stride,stride)) -> + BatchNorm() -> + Relu(?=act) + } + def skip(channels, stride){ + Convolution(kernel=(1,-1), channels=96, stride=(stride,stride)) -> + BatchNorm() + } + def resLayer(channels, stride=1){ + ( + conv(kernel=3, channels=channels, stride=stride) -> + conv(kernel=3, channels=channels, stride=stride, act=false) + | + skip(channels=channels, stride=stride, ?=(stride!=1)) + ) -> + Add() -> + Relu() + } + + image -> + conv(kernel=7, channels=64, stride=2) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), padding="same") -> + resLayer(channels=64, ->=3) -> + resLayer(channels=128, stride=2) -> + GlobalPooling(pool_type="avg") -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} diff --git a/src/test/resources/invalid_tests/ArgumentConstraintTest6.cnna b/src/test/resources/invalid_tests/ArgumentConstraintTest6.cnna new file mode 100644 index 0000000..e046ce4 --- /dev/null +++ b/src/test/resources/invalid_tests/ArgumentConstraintTest6.cnna @@ -0,0 +1,34 @@ +architecture ArgumentConstraintTest6(img_height=224, img_width=224, img_channels=3, classes=1000){ + def input Z(0:255)^{img_channels, img_height, img_width} image + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels, stride=1, act=true){ + Convolution(kernel=(kernel,kernel), channels=channels, stride=(stride,stride)) -> + BatchNorm() -> + Relu(?=act) + } + def skip(channels, stride){ + Convolution(kernel=(1,1), channels=false, stride=(stride,stride)) -> + BatchNorm() + } + def resLayer(channels, stride=1){ + ( + conv(kernel=3, channels=channels, stride=stride) -> + conv(kernel=3, channels=channels, stride=stride, act=false) + | + skip(channels=channels, stride=stride, ?=(stride!=1)) + ) -> + Add() -> + Relu() + } + + image -> + conv(kernel=7, channels=64, stride=2) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), padding="valid") -> + resLayer(channels=64, ->=3) -> + resLayer(channels=128, stride=2) -> + GlobalPooling(pool_type="avg") -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} diff --git a/src/test/resources/invalid_tests/DuplicatedArgument.cnna b/src/test/resources/invalid_tests/DuplicatedArgument.cnna new file mode 100644 index 0000000..6f885ac --- /dev/null +++ b/src/test/resources/invalid_tests/DuplicatedArgument.cnna @@ -0,0 +1,11 @@ +architecture DuplicatedArgument(){ + def input Q(-oo:+oo)^{10} in1 + def output Q(0:1)^{2} out1 + + in1 -> + FullyConnected(units=64, units=32) -> + Tanh() -> + FullyConnected(units=2) -> + Softmax() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/DuplicatedIONames.cnna b/src/test/resources/invalid_tests/DuplicatedIONames.cnna new file mode 100644 index 0000000..2940f40 --- /dev/null +++ b/src/test/resources/invalid_tests/DuplicatedIONames.cnna @@ -0,0 +1,16 @@ +architecture DuplicatedIONames(inputs=10, classes=2){ + def input Q(-oo:+oo)^{inputs} in1 + def input Q(-oo:+oo)^{inputs} in1 + def output Q(0:1)^{classes} out1 + + def fc(){ + FullyConnected(units=64) -> + Tanh() + } + + in1 -> + fc() -> + FullyConnected(units=classes) -> + Softmax() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/DuplicatedNames.cnna b/src/test/resources/invalid_tests/DuplicatedNames.cnna new file mode 100644 index 0000000..1fbf822 --- /dev/null +++ b/src/test/resources/invalid_tests/DuplicatedNames.cnna @@ -0,0 +1,20 @@ +architecture DuplicatedNames(inputs=10, inputs=10, classes=2){ + def input Q(-oo:+oo)^{inputs} in1 + def output Q(0:1)^{classes} out1 + + def fc(){ + FullyConnected(units=64) -> + Tanh() + } + + def fc(){ + FullyConnected(units=64) -> + Tanh() + } + + in1 -> + fc() -> + FullyConnected(units=classes) -> + Softmax() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/IllegalIOName.cnna b/src/test/resources/invalid_tests/IllegalIOName.cnna new file mode 100644 index 0000000..cd2d66c --- /dev/null +++ b/src/test/resources/invalid_tests/IllegalIOName.cnna @@ -0,0 +1,11 @@ +architecture IllegalIOName{ + def input Q(-oo:+oo)^{10} data_ + def output Q(0:1)^{2} predictions_ + + data_ -> + FullyConnected(units=64, no_bias=true) -> + Tanh() -> + FullyConnected(units=2, no_bias=true) -> + Softmax() -> + predictions_ +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/IllegalName.cnna b/src/test/resources/invalid_tests/IllegalName.cnna new file mode 100644 index 0000000..ed80721 --- /dev/null +++ b/src/test/resources/invalid_tests/IllegalName.cnna @@ -0,0 +1,15 @@ +architecture IllegalName(inputs=10, classes=2, Tg = 1){ + def input Q(-oo:+oo)^{inputs} in1 + def output Q(0:1)^{classes} out1 + + def Fc(){ + FullyConnected(units=10) + } + + in1 -> + FullyConnected(units=64) -> + Tanh() -> + FullyConnected(units=classes) -> + Softmax() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/InvalidArrayAccessValue.cnna b/src/test/resources/invalid_tests/InvalidArrayAccessValue.cnna new file mode 100644 index 0000000..1901cd8 --- /dev/null +++ b/src/test/resources/invalid_tests/InvalidArrayAccessValue.cnna @@ -0,0 +1,27 @@ +architecture InvalidArrayAccessValue(img_height=200, img_width=300, img_channels=3, classes=3){ + def input Z(0:255)^{img_channels, img_height, img_width} image[3] + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels){ + Convolution(kernel=kernel, channels=channels) -> + Relu() + } + + def inputGroup(index){ + [index] -> + conv(kernel=(3,3), channels=32, ->=3) -> + Pooling(pool_type="max", kernel=(2,2), stride=(2,2)) + } + + (image[0] | image[1] | image[2] | image[3]) -> + inputGroup(index=[0|..|2]) -> + Concatenate() -> + conv(kernel=(3,3), channels=64) -> + Pooling(pool_type="max", kernel=(2,2), stride=(2,2)) -> + + FullyConnected(units=32) -> + Relu() -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} diff --git a/src/test/resources/invalid_tests/InvalidIOShape1.cnna b/src/test/resources/invalid_tests/InvalidIOShape1.cnna new file mode 100644 index 0000000..3f03ce0 --- /dev/null +++ b/src/test/resources/invalid_tests/InvalidIOShape1.cnna @@ -0,0 +1,11 @@ +architecture InvalidIOShape1(){ + def input Q(-oo:+oo)^{10, 2} in1 + def output Q(0:1)^{10, 2, 2, 2} out1 + + in1 -> + FullyConnected(units=64) -> + Tanh() -> + FullyConnected(units=10) -> + Softmax() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/InvalidIOShape2.cnna b/src/test/resources/invalid_tests/InvalidIOShape2.cnna new file mode 100644 index 0000000..3e746f6 --- /dev/null +++ b/src/test/resources/invalid_tests/InvalidIOShape2.cnna @@ -0,0 +1,11 @@ +architecture InvalidIOShape2(){ + def input Q(-oo:+oo)^{10.5} in1 + def output Q(0:1)^{-10} out1 + + in1 -> + FullyConnected(units=64) -> + Tanh() -> + FullyConnected(units=10) -> + Softmax() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/InvalidInputShape.cnna b/src/test/resources/invalid_tests/InvalidInputShape.cnna new file mode 100644 index 0000000..9fd7acb --- /dev/null +++ b/src/test/resources/invalid_tests/InvalidInputShape.cnna @@ -0,0 +1,11 @@ +architecture InvalidInputShape(inputs=10, classes=2){ + def input Q(-oo:+oo)^{inputs} in1[2] + def output Q(0:1)^{classes} out1[2] + + in1 -> + FullyConnected(units=64) -> + Tanh() -> + FullyConnected(units=classes) -> + Softmax() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/InvalidRecursion.cnna b/src/test/resources/invalid_tests/InvalidRecursion.cnna new file mode 100644 index 0000000..188eaa9 --- /dev/null +++ b/src/test/resources/invalid_tests/InvalidRecursion.cnna @@ -0,0 +1,40 @@ +architecture InvalidRecursion(img_height=224, img_width=224, img_channels=3, classes=1000){ + def input Z(0:255)^{img_channels, img_height, img_width} image + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels, stride=1, act=true){ + Convolution(kernel=(kernel,kernel), channels=channels, stride=(stride,stride)) -> + BatchNorm() -> + resLayer(channels = 8) -> + Relu(?=act) + } + def skip(channels, stride){ + Convolution(kernel=(1,1), channels=channels, stride=(stride,stride)) -> + BatchNorm() + } + def resLayer(channels, stride=1){ + ( + conv(kernel=3, channels=channels, stride=stride) -> + conv(kernel=3, channels=channels, stride=stride, act=false) + | + skip(channels=channels, stride=stride, ?=(stride!=1)) + ) -> + Add() -> + Relu() + } + + image -> + conv(kernel=7, channels=64, stride=2) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2)) -> + resLayer(channels=64, ->=3) -> + resLayer(channels=128, stride=2) -> + resLayer(channels=128, ->=3) -> + resLayer(channels=256, stride=2) -> + resLayer(channels=256, ->=5) -> + resLayer(channels=512, stride=2) -> + resLayer(channels=512, ->=2) -> + GlobalPooling(pool_type="avg") -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} diff --git a/src/test/resources/invalid_tests/MissingArgument.cnna b/src/test/resources/invalid_tests/MissingArgument.cnna new file mode 100644 index 0000000..88441c4 --- /dev/null +++ b/src/test/resources/invalid_tests/MissingArgument.cnna @@ -0,0 +1,34 @@ +architecture MissingArgument(img_height=224, img_width=224, img_channels=3, classes=1000){ + def input Z(0:255)^{img_channels, img_height, img_width} image + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels, stride=1, act=true){ + Convolution(kernel=(kernel,kernel), channels=channels, stride=(stride,stride)) -> + BatchNorm() -> + Relu(?=act) + } + def skip(channels, stride){ + Convolution(channels=96, stride=(stride,stride)) -> + BatchNorm() + } + def resLayer(channels, stride=1){ + ( + conv(kernel=3, channels=channels, stride=stride) -> + conv(kernel=3, channels=channels, stride=stride, act=false) + | + skip(channels=channels, stride=stride, ?=(stride!=1)) + ) -> + Add() -> + Relu() + } + + image -> + conv(kernel=7, stride=2) -> + Pooling(pool_type="max") -> + resLayer(channels=64, ->=3) -> + resLayer(channels=128, stride=2) -> + GlobalPooling(pool_type="avg") -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/MissingIO2.cnna b/src/test/resources/invalid_tests/MissingIO2.cnna new file mode 100644 index 0000000..c82202c --- /dev/null +++ b/src/test/resources/invalid_tests/MissingIO2.cnna @@ -0,0 +1,11 @@ +architecture MissingIO2(inputs=10, classes=2){ + def input Q(-oo:+oo)^{inputs} in1[2] + def output Q(0:1)^{classes} out1[2] + + in1[0] -> + FullyConnected(units=64, no_bias=true) -> + Tanh() -> + FullyConnected(units=classes, no_bias=true) -> + Softmax() -> + out1[0] +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/MissingLayerOperator.cnna b/src/test/resources/invalid_tests/MissingLayerOperator.cnna new file mode 100644 index 0000000..6cea4a6 --- /dev/null +++ b/src/test/resources/invalid_tests/MissingLayerOperator.cnna @@ -0,0 +1,11 @@ +architecture MissingLayerOperator(){ + def input Q(-oo:+oo)^{10} in1 + def output Q(0:1)^{2} out1 + + in1 -> + FullyConnected(units=64, no_bias=true) -> + Tanh() + FullyConnected(units=2, no_bias=true) + Softmax() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/MissingMerge.cnna b/src/test/resources/invalid_tests/MissingMerge.cnna new file mode 100644 index 0000000..d2fb2e2 --- /dev/null +++ b/src/test/resources/invalid_tests/MissingMerge.cnna @@ -0,0 +1,24 @@ +architecture MissingMerge(inputs=10, classes=2){ + def input Q(-oo:+oo)^{inputs} in1 + def output Q(0:1)^{classes} out1 + + in1 -> + ( + ( + FullyConnected(units=16) + | + FullyConnected(units=16) + ) + | + ( + FullyConnected(units=16) + | + FullyConnected(units=16) + ) + ) -> + Add() -> + Tanh() -> + FullyConnected(units=classes) -> + Softmax() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/MissingParallelBrackets.cnna b/src/test/resources/invalid_tests/MissingParallelBrackets.cnna new file mode 100644 index 0000000..e8cacb5 --- /dev/null +++ b/src/test/resources/invalid_tests/MissingParallelBrackets.cnna @@ -0,0 +1,45 @@ +architecture MissingParallelBrackets(img_height=224, img_width=224, img_channels=3, classes=10){ + def input Z(0:255)^{img_channels, img_height, img_width} image + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels, hasPool=true, convStride=(1,1)){ + Convolution(kernel=kernel, channels=channels, stride=convStride) -> + Relu() -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), ?=hasPool) + } + def fc(){ + FullyConnected(units=4096) -> + Relu() -> + Dropout() + } + + image -> + conv(kernel=(11,11), channels=96, convStride=(4,4)) -> + Lrn(nsize=5, alpha=0.0001, beta=0.75) -> + + SplitData(index=0, n=2) -> + conv(kernel=(5,5), channels=128) -> + Lrn(nsize=5, alpha=0.0001, beta=0.75) + | + SplitData(index=1, n=2) -> + conv(kernel=(5,5), channels=128) -> + Lrn(nsize=5, alpha=0.0001, beta=0.75) + -> + + conv(kernel=(3,3), channels=384 ,hasPool=false) -> + + SplitData(index=0, n=2) -> + conv(kernel=(3,3), channels=192, hasPool=false) -> + conv(kernel=(3,3), channels=128) + | + SplitData(index=1, n=2) -> + conv(kernel=(3,3), channels=192, hasPool=false) -> + conv(kernel=(3,3), channels=128) + -> + + fc() -> + fc() -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/NotIOArray.cnna b/src/test/resources/invalid_tests/NotIOArray.cnna new file mode 100644 index 0000000..a1f16d8 --- /dev/null +++ b/src/test/resources/invalid_tests/NotIOArray.cnna @@ -0,0 +1,11 @@ +architecture NotIOArray(inputs=10, classes=2){ + def input Q(-oo:+oo)^{inputs} in1 + def output Q(0:1)^{classes} out1 + + in1[1] -> + FullyConnected(units=64, no_bias=true) -> + Tanh() -> + FullyConnected(units=classes, no_bias=true) -> + Softmax() -> + out1[0] +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/UnfinishedArchitecture.cnna b/src/test/resources/invalid_tests/UnfinishedArchitecture.cnna new file mode 100644 index 0000000..2f806bb --- /dev/null +++ b/src/test/resources/invalid_tests/UnfinishedArchitecture.cnna @@ -0,0 +1,15 @@ +architecture UnfinishedArchitecture(inputs=10, classes=2){ + def input Q(-oo:+oo)^{inputs} in1 + def output Q(0:1)^{classes} out1 + + in1 -> + FullyConnected(units=64, no_bias=true) -> + Tanh() -> + ( + FullyConnected(units=classes, no_bias=true) -> + Softmax() -> + out1 + | + + ) +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/UnknownIO.cnna b/src/test/resources/invalid_tests/UnknownIO.cnna new file mode 100644 index 0000000..4098708 --- /dev/null +++ b/src/test/resources/invalid_tests/UnknownIO.cnna @@ -0,0 +1,8 @@ +architecture UnknownIO(){ + in1 -> + FullyConnected(units=64) -> + Tanh() -> + FullyConnected(units=10) -> + Softmax() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/UnknownMethod.cnna b/src/test/resources/invalid_tests/UnknownMethod.cnna new file mode 100644 index 0000000..ec87f5e --- /dev/null +++ b/src/test/resources/invalid_tests/UnknownMethod.cnna @@ -0,0 +1,11 @@ +architecture UnknownMethod(inputs=10, classes=2){ + def input Q(-oo:+oo)^{inputs} in1 + def output Q(0:1)^{classes} out1 + + in1 -> + FllyConnected(units=64, no_bias=true) -> + Tanh() -> + FullyConnected(units=classes, no_bias=true) -> + Softmax() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/UnknownVariableName.cnna b/src/test/resources/invalid_tests/UnknownVariableName.cnna new file mode 100644 index 0000000..9f79070 --- /dev/null +++ b/src/test/resources/invalid_tests/UnknownVariableName.cnna @@ -0,0 +1,11 @@ +architecture UnknownVariableName(inputs=10){ + def input Q(-oo:+oo)^{inputs} in1 + def output Q(0:1)^{2} out1 + + in1 -> + FullyConnected(units=64) -> + Tanh() -> + FullyConnected(units=classes) -> + Softmax() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/WrongArgument.cnna b/src/test/resources/invalid_tests/WrongArgument.cnna new file mode 100644 index 0000000..893b4ce --- /dev/null +++ b/src/test/resources/invalid_tests/WrongArgument.cnna @@ -0,0 +1,11 @@ +architecture WrongArgument(inputs=10, classes=2){ + def input Q(-oo:+oo)^{inputs} in1 + def output Q(0:1)^{classes} out1 + + in1 -> + FullyConnected(units=64, bias=true) -> + Tanh(asd=1) -> + FullyConnected(unit=classes) -> + Softmax() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/WrongIOType.cnna b/src/test/resources/invalid_tests/WrongIOType.cnna new file mode 100644 index 0000000..11dae44 --- /dev/null +++ b/src/test/resources/invalid_tests/WrongIOType.cnna @@ -0,0 +1,11 @@ +architecture WrongIOType(inputs=10, classes=2){ + def input Q(-oo:+oo)^{inputs} in1 + def output Q(0:1)^{classes} out1 + + in1 -> + FullyConnected(units=64, no_bias=true) -> + Tanh() -> + FullyConnected(units=classes, no_bias=true) -> + Relu() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/invalid_tests/WrongRangeOperator.cnna b/src/test/resources/invalid_tests/WrongRangeOperator.cnna new file mode 100644 index 0000000..d6ddac7 --- /dev/null +++ b/src/test/resources/invalid_tests/WrongRangeOperator.cnna @@ -0,0 +1,11 @@ +architecture WrongRangeOperator(inputs=10, classes=2){ + def input Q(-oo:+oo)^{inputs} in1 + def output Q(0:1)^{classes} out1 + + in1 -> + FullyConnected(units=[64->..|65]) -> + Tanh() -> + FullyConnected(units=[classes |..-> classes + 1] ) -> + Softmax() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/target_code/CNNBufferFile.h b/src/test/resources/target_code/CNNBufferFile.h new file mode 100644 index 0000000..879264d --- /dev/null +++ b/src/test/resources/target_code/CNNBufferFile.h @@ -0,0 +1,51 @@ +#ifndef CNNBUFFERFILE_H +#define CNNBUFFERFILE_H + +#include +#include +#include + +// Read file to buffer +class BufferFile { + public : + std::string file_path_; + int length_; + char* buffer_; + + explicit BufferFile(std::string file_path) + :file_path_(file_path) { + + std::ifstream ifs(file_path.c_str(), std::ios::in | std::ios::binary); + if (!ifs) { + std::cerr << "Can't open the file. Please check " << file_path << ". \n"; + length_ = 0; + buffer_ = NULL; + return; + } + + ifs.seekg(0, std::ios::end); + length_ = ifs.tellg(); + ifs.seekg(0, std::ios::beg); + std::cout << file_path.c_str() << " ... "<< length_ << " bytes\n"; + + buffer_ = new char[sizeof(char) * length_]; + ifs.read(buffer_, length_); + ifs.close(); + } + + int GetLength() { + return length_; + } + char* GetBuffer() { + return buffer_; + } + + ~BufferFile() { + if (buffer_) { + delete[] buffer_; + buffer_ = NULL; + } + } +}; + +#endif // CNNBUFFERFILE_H \ No newline at end of file diff --git a/src/test/resources/target_code/CNNCreator_Alexnet.py b/src/test/resources/target_code/CNNCreator_Alexnet.py new file mode 100644 index 0000000..678b44c --- /dev/null +++ b/src/test/resources/target_code/CNNCreator_Alexnet.py @@ -0,0 +1,426 @@ +import mxnet as mx +import logging +import os +import errno +import shutil +import h5py +import sys +import numpy as np + +@mx.init.register +class MyConstant(mx.init.Initializer): + def __init__(self, value): + super(MyConstant, self).__init__(value=value) + self.value = value + def _init_weight(self, _, arr): + arr[:] = mx.nd.array(self.value) + +class CNNCreator_Alexnet: + + module = None + _data_dir_ = "data/Alexnet/" + _model_dir_ = "model/Alexnet/" + _model_prefix_ = "Alexnet" + _input_names_ = ['data'] + _input_shapes_ = [(3,224,224)] + _output_names_ = ['predictions_label'] + + + def load(self, context): + lastEpoch = 0 + param_file = None + + try: + os.remove(self._model_dir_ + self._model_prefix_ + "_newest-0000.params") + except OSError: + pass + try: + os.remove(self._model_dir_ + self._model_prefix_ + "_newest-symbol.json") + except OSError: + pass + + if os.path.isdir(self._model_dir_): + for file in os.listdir(self._model_dir_): + if ".params" in file and self._model_prefix_ in file: + epochStr = file.replace(".params","").replace(self._model_prefix_ + "-","") + epoch = int(epochStr) + if epoch > lastEpoch: + lastEpoch = epoch + param_file = file + if param_file is None: + return 0 + else: + logging.info("Loading checkpoint: " + param_file) + self.module.load(prefix=self._model_dir_ + self._model_prefix_, + epoch=lastEpoch, + data_names=self._input_names_, + label_names=self._output_names_, + context=context) + return lastEpoch + + + def load_data(self, batch_size): + train_h5, test_h5 = self.load_h5_files() + + data_mean = train_h5[self._input_names_[0]][:].mean(axis=0) + data_std = train_h5[self._input_names_[0]][:].std(axis=0) + 1e-5 + + train_iter = mx.io.NDArrayIter(train_h5[self._input_names_[0]], + train_h5[self._output_names_[0]], + batch_size=batch_size, + data_name=self._input_names_[0], + label_name=self._output_names_[0]) + test_iter = None + if test_h5 != None: + test_iter = mx.io.NDArrayIter(test_h5[self._input_names_[0]], + test_h5[self._output_names_[0]], + batch_size=batch_size, + data_name=self._input_names_[0], + label_name=self._output_names_[0]) + return train_iter, test_iter, data_mean, data_std + + def load_h5_files(self): + train_h5 = None + test_h5 = None + train_path = self._data_dir_ + "train.h5" + test_path = self._data_dir_ + "test.h5" + if os.path.isfile(train_path): + train_h5 = h5py.File(train_path, 'r') + if not (self._input_names_[0] in train_h5 and self._output_names_[0] in train_h5): + logging.error("The HDF5 file '" + os.path.abspath(train_path) + "' has to contain the datasets: " + + "'" + self._input_names_[0] + "', '" + self._output_names_[0] + "'") + sys.exit(1) + test_iter = None + if os.path.isfile(test_path): + test_h5 = h5py.File(test_path, 'r') + if not (self._input_names_[0] in test_h5 and self._output_names_[0] in test_h5): + logging.error("The HDF5 file '" + os.path.abspath(test_path) + "' has to contain the datasets: " + + "'" + self._input_names_[0] + "', '" + self._output_names_[0] + "'") + sys.exit(1) + else: + logging.warning("Couldn't load test set. File '" + os.path.abspath(test_path) + "' does not exist.") + return train_h5, test_h5 + else: + logging.error("Data loading failure. File '" + os.path.abspath(train_path) + "' does not exist.") + sys.exit(1) + + + def train(self, batch_size, + num_epoch=10, + optimizer='adam', + optimizer_params=(('learning_rate', 0.001),), + load_checkpoint=True, + context='gpu', + checkpoint_period=5, + normalize=True): + if context == 'gpu': + mx_context = mx.gpu() + elif context == 'cpu': + mx_context = mx.cpu() + else: + logging.error("Context argument is '" + context + "'. Only 'cpu' and 'gpu are valid arguments'.") + + if 'weight_decay' in optimizer_params: + optimizer_params['wd'] = optimizer_params['weight_decay'] + del optimizer_params['weight_decay'] + if 'learning_rate_decay' in optimizer_params: + min_learning_rate = 1e-08 + if 'learning_rate_minimum' in optimizer_params: + min_learning_rate = optimizer_params['learning_rate_minimum'] + del optimizer_params['learning_rate_minimum'] + optimizer_params['lr_scheduler'] = mx.lr_scheduler.FactorScheduler( + optimizer_params['step_size'], + factor=optimizer_params['learning_rate_decay'], + stop_factor_lr=min_learning_rate) + del optimizer_params['step_size'] + del optimizer_params['learning_rate_decay'] + + + train_iter, test_iter, data_mean, data_std = self.load_data(batch_size) + if self.module == None: + if normalize: + self.construct(mx_context, data_mean, data_std) + else: + self.construct(mx_context) + + begin_epoch = 0 + if load_checkpoint: + begin_epoch = self.load(mx_context) + else: + if os.path.isdir(self._model_dir_): + shutil.rmtree(self._model_dir_) + + try: + os.makedirs(self._model_dir_) + except OSError: + if not os.path.isdir(self._model_dir_): + raise + + self.module.fit( + train_data=train_iter, + eval_data=test_iter, + optimizer=optimizer, + optimizer_params=optimizer_params, + batch_end_callback=mx.callback.Speedometer(batch_size), + epoch_end_callback=mx.callback.do_checkpoint(prefix=self._model_dir_ + self._model_prefix_, period=checkpoint_period), + begin_epoch=begin_epoch, + num_epoch=num_epoch + begin_epoch) + self.module.save_checkpoint(self._model_dir_ + self._model_prefix_, num_epoch + begin_epoch) + self.module.save_checkpoint(self._model_dir_ + self._model_prefix_ + '_newest', 0) + + + def construct(self, context, data_mean=None, data_std=None): + data = mx.sym.var("data", + shape=(0,3,224,224)) + # data, output shape: {[3,224,224]} + + if not data_mean is None: + assert(not data_std is None) + _data_mean_ = mx.sym.Variable("_data_mean_", shape=(3,224,224), init=MyConstant(value=data_mean.tolist())) + _data_mean_ = mx.sym.BlockGrad(_data_mean_) + _data_std_ = mx.sym.Variable("_data_std_", shape=(3,224,224), init=MyConstant(value=data_mean.tolist())) + _data_std_ = mx.sym.BlockGrad(_data_std_) + data = mx.symbol.broadcast_sub(data, _data_mean_) + data = mx.symbol.broadcast_div(data, _data_std_) + conv1_ = mx.symbol.pad(data=data, + mode='constant', + pad_width=(0,0,0,0,2,1,2,1), + constant_value=0) + conv1_ = mx.symbol.Convolution(data=conv1_, + kernel=(11,11), + stride=(4,4), + num_filter=96, + no_bias=False, + name="conv1_") + # conv1_, output shape: {[96,55,55]} + + lrn1_ = mx.symbol.LRN(data=conv1_, + alpha=0.0001, + beta=0.75, + knorm=2, + nsize=5, + name="lrn1_") + pool1_ = mx.symbol.Pooling(data=lrn1_, + kernel=(3,3), + pool_type="max", + stride=(2,2), + name="pool1_") + # pool1_, output shape: {[96,27,27]} + + relu1_ = mx.symbol.Activation(data=pool1_, + act_type='relu', + name="relu1_") + + split1_ = mx.symbol.split(data=relu1_, + num_outputs=2, + axis=1, + name="split1_") + # split1_, output shape: {[48,27,27][48,27,27]} + + get2_1_ = split1_[0] + conv2_1_ = mx.symbol.pad(data=get2_1_, + mode='constant', + pad_width=(0,0,0,0,2,2,2,2), + constant_value=0) + conv2_1_ = mx.symbol.Convolution(data=conv2_1_, + kernel=(5,5), + stride=(1,1), + num_filter=128, + no_bias=False, + name="conv2_1_") + # conv2_1_, output shape: {[128,27,27]} + + lrn2_1_ = mx.symbol.LRN(data=conv2_1_, + alpha=0.0001, + beta=0.75, + knorm=2, + nsize=5, + name="lrn2_1_") + pool2_1_ = mx.symbol.Pooling(data=lrn2_1_, + kernel=(3,3), + pool_type="max", + stride=(2,2), + name="pool2_1_") + # pool2_1_, output shape: {[128,13,13]} + + relu2_1_ = mx.symbol.Activation(data=pool2_1_, + act_type='relu', + name="relu2_1_") + + get2_2_ = split1_[1] + conv2_2_ = mx.symbol.pad(data=get2_2_, + mode='constant', + pad_width=(0,0,0,0,2,2,2,2), + constant_value=0) + conv2_2_ = mx.symbol.Convolution(data=conv2_2_, + kernel=(5,5), + stride=(1,1), + num_filter=128, + no_bias=False, + name="conv2_2_") + # conv2_2_, output shape: {[128,27,27]} + + lrn2_2_ = mx.symbol.LRN(data=conv2_2_, + alpha=0.0001, + beta=0.75, + knorm=2, + nsize=5, + name="lrn2_2_") + pool2_2_ = mx.symbol.Pooling(data=lrn2_2_, + kernel=(3,3), + pool_type="max", + stride=(2,2), + name="pool2_2_") + # pool2_2_, output shape: {[128,13,13]} + + relu2_2_ = mx.symbol.Activation(data=pool2_2_, + act_type='relu', + name="relu2_2_") + + concatenate3_ = mx.symbol.concat(relu2_1_, relu2_2_, + dim=1, + name="concatenate3_") + # concatenate3_, output shape: {[256,13,13]} + + conv3_ = mx.symbol.pad(data=concatenate3_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv3_ = mx.symbol.Convolution(data=conv3_, + kernel=(3,3), + stride=(1,1), + num_filter=384, + no_bias=False, + name="conv3_") + # conv3_, output shape: {[384,13,13]} + + relu3_ = mx.symbol.Activation(data=conv3_, + act_type='relu', + name="relu3_") + + split3_ = mx.symbol.split(data=relu3_, + num_outputs=2, + axis=1, + name="split3_") + # split3_, output shape: {[192,13,13][192,13,13]} + + get4_1_ = split3_[0] + conv4_1_ = mx.symbol.pad(data=get4_1_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv4_1_ = mx.symbol.Convolution(data=conv4_1_, + kernel=(3,3), + stride=(1,1), + num_filter=192, + no_bias=False, + name="conv4_1_") + # conv4_1_, output shape: {[192,13,13]} + + relu4_1_ = mx.symbol.Activation(data=conv4_1_, + act_type='relu', + name="relu4_1_") + + conv5_1_ = mx.symbol.pad(data=relu4_1_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv5_1_ = mx.symbol.Convolution(data=conv5_1_, + kernel=(3,3), + stride=(1,1), + num_filter=128, + no_bias=False, + name="conv5_1_") + # conv5_1_, output shape: {[128,13,13]} + + pool5_1_ = mx.symbol.Pooling(data=conv5_1_, + kernel=(3,3), + pool_type="max", + stride=(2,2), + name="pool5_1_") + # pool5_1_, output shape: {[128,6,6]} + + relu5_1_ = mx.symbol.Activation(data=pool5_1_, + act_type='relu', + name="relu5_1_") + + get4_2_ = split3_[1] + conv4_2_ = mx.symbol.pad(data=get4_2_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv4_2_ = mx.symbol.Convolution(data=conv4_2_, + kernel=(3,3), + stride=(1,1), + num_filter=192, + no_bias=False, + name="conv4_2_") + # conv4_2_, output shape: {[192,13,13]} + + relu4_2_ = mx.symbol.Activation(data=conv4_2_, + act_type='relu', + name="relu4_2_") + + conv5_2_ = mx.symbol.pad(data=relu4_2_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv5_2_ = mx.symbol.Convolution(data=conv5_2_, + kernel=(3,3), + stride=(1,1), + num_filter=128, + no_bias=False, + name="conv5_2_") + # conv5_2_, output shape: {[128,13,13]} + + pool5_2_ = mx.symbol.Pooling(data=conv5_2_, + kernel=(3,3), + pool_type="max", + stride=(2,2), + name="pool5_2_") + # pool5_2_, output shape: {[128,6,6]} + + relu5_2_ = mx.symbol.Activation(data=pool5_2_, + act_type='relu', + name="relu5_2_") + + concatenate6_ = mx.symbol.concat(relu5_1_, relu5_2_, + dim=1, + name="concatenate6_") + # concatenate6_, output shape: {[256,6,6]} + + fc6_ = mx.symbol.flatten(data=concatenate6_) + fc6_ = mx.symbol.FullyConnected(data=fc6_, + num_hidden=4096, + no_bias=False, + name="fc6_") + relu6_ = mx.symbol.Activation(data=fc6_, + act_type='relu', + name="relu6_") + + dropout6_ = mx.symbol.Dropout(data=relu6_, + p=0.5, + name="dropout6_") + fc7_ = mx.symbol.FullyConnected(data=dropout6_, + num_hidden=4096, + no_bias=False, + name="fc7_") + relu7_ = mx.symbol.Activation(data=fc7_, + act_type='relu', + name="relu7_") + + dropout7_ = mx.symbol.Dropout(data=relu7_, + p=0.5, + name="dropout7_") + fc8_ = mx.symbol.FullyConnected(data=dropout7_, + num_hidden=10, + no_bias=False, + name="fc8_") + + predictions = mx.symbol.SoftmaxOutput(data=fc8_, + name="predictions") + + self.module = mx.mod.Module(symbol=mx.symbol.Group([predictions]), + data_names=self._input_names_, + label_names=self._output_names_, + context=context) diff --git a/src/test/resources/target_code/CNNCreator_CifarClassifierNetwork.py b/src/test/resources/target_code/CNNCreator_CifarClassifierNetwork.py new file mode 100644 index 0000000..6dd81fe --- /dev/null +++ b/src/test/resources/target_code/CNNCreator_CifarClassifierNetwork.py @@ -0,0 +1,664 @@ +import mxnet as mx +import logging +import os +import errno +import shutil +import h5py +import sys +import numpy as np + +@mx.init.register +class MyConstant(mx.init.Initializer): + def __init__(self, value): + super(MyConstant, self).__init__(value=value) + self.value = value + def _init_weight(self, _, arr): + arr[:] = mx.nd.array(self.value) + +class CNNCreator_CifarClassifierNetwork: + + module = None + _data_dir_ = "data/CifarClassifierNetwork/" + _model_dir_ = "model/CifarClassifierNetwork/" + _model_prefix_ = "CifarClassifierNetwork" + _input_names_ = ['data'] + _input_shapes_ = [(3,32,32)] + _output_names_ = ['softmax_label'] + + + def load(self, context): + lastEpoch = 0 + param_file = None + + try: + os.remove(self._model_dir_ + self._model_prefix_ + "_newest-0000.params") + except OSError: + pass + try: + os.remove(self._model_dir_ + self._model_prefix_ + "_newest-symbol.json") + except OSError: + pass + + if os.path.isdir(self._model_dir_): + for file in os.listdir(self._model_dir_): + if ".params" in file and self._model_prefix_ in file: + epochStr = file.replace(".params","").replace(self._model_prefix_ + "-","") + epoch = int(epochStr) + if epoch > lastEpoch: + lastEpoch = epoch + param_file = file + if param_file is None: + return 0 + else: + logging.info("Loading checkpoint: " + param_file) + self.module.load(prefix=self._model_dir_ + self._model_prefix_, + epoch=lastEpoch, + data_names=self._input_names_, + label_names=self._output_names_, + context=context) + return lastEpoch + + + def load_data(self, batch_size): + train_h5, test_h5 = self.load_h5_files() + + data_mean = train_h5[self._input_names_[0]][:].mean(axis=0) + data_std = train_h5[self._input_names_[0]][:].std(axis=0) + 1e-5 + + train_iter = mx.io.NDArrayIter(train_h5[self._input_names_[0]], + train_h5[self._output_names_[0]], + batch_size=batch_size, + data_name=self._input_names_[0], + label_name=self._output_names_[0]) + test_iter = None + if test_h5 != None: + test_iter = mx.io.NDArrayIter(test_h5[self._input_names_[0]], + test_h5[self._output_names_[0]], + batch_size=batch_size, + data_name=self._input_names_[0], + label_name=self._output_names_[0]) + return train_iter, test_iter, data_mean, data_std + + def load_h5_files(self): + train_h5 = None + test_h5 = None + train_path = self._data_dir_ + "train.h5" + test_path = self._data_dir_ + "test.h5" + if os.path.isfile(train_path): + train_h5 = h5py.File(train_path, 'r') + if not (self._input_names_[0] in train_h5 and self._output_names_[0] in train_h5): + logging.error("The HDF5 file '" + os.path.abspath(train_path) + "' has to contain the datasets: " + + "'" + self._input_names_[0] + "', '" + self._output_names_[0] + "'") + sys.exit(1) + test_iter = None + if os.path.isfile(test_path): + test_h5 = h5py.File(test_path, 'r') + if not (self._input_names_[0] in test_h5 and self._output_names_[0] in test_h5): + logging.error("The HDF5 file '" + os.path.abspath(test_path) + "' has to contain the datasets: " + + "'" + self._input_names_[0] + "', '" + self._output_names_[0] + "'") + sys.exit(1) + else: + logging.warning("Couldn't load test set. File '" + os.path.abspath(test_path) + "' does not exist.") + return train_h5, test_h5 + else: + logging.error("Data loading failure. File '" + os.path.abspath(train_path) + "' does not exist.") + sys.exit(1) + + + def train(self, batch_size, + num_epoch=10, + optimizer='adam', + optimizer_params=(('learning_rate', 0.001),), + load_checkpoint=True, + context='gpu', + checkpoint_period=5, + normalize=True): + if context == 'gpu': + mx_context = mx.gpu() + elif context == 'cpu': + mx_context = mx.cpu() + else: + logging.error("Context argument is '" + context + "'. Only 'cpu' and 'gpu are valid arguments'.") + + if 'weight_decay' in optimizer_params: + optimizer_params['wd'] = optimizer_params['weight_decay'] + del optimizer_params['weight_decay'] + if 'learning_rate_decay' in optimizer_params: + min_learning_rate = 1e-08 + if 'learning_rate_minimum' in optimizer_params: + min_learning_rate = optimizer_params['learning_rate_minimum'] + del optimizer_params['learning_rate_minimum'] + optimizer_params['lr_scheduler'] = mx.lr_scheduler.FactorScheduler( + optimizer_params['step_size'], + factor=optimizer_params['learning_rate_decay'], + stop_factor_lr=min_learning_rate) + del optimizer_params['step_size'] + del optimizer_params['learning_rate_decay'] + + + train_iter, test_iter, data_mean, data_std = self.load_data(batch_size) + if self.module == None: + if normalize: + self.construct(mx_context, data_mean, data_std) + else: + self.construct(mx_context) + + begin_epoch = 0 + if load_checkpoint: + begin_epoch = self.load(mx_context) + else: + if os.path.isdir(self._model_dir_): + shutil.rmtree(self._model_dir_) + + try: + os.makedirs(self._model_dir_) + except OSError: + if not os.path.isdir(self._model_dir_): + raise + + self.module.fit( + train_data=train_iter, + eval_data=test_iter, + optimizer=optimizer, + optimizer_params=optimizer_params, + batch_end_callback=mx.callback.Speedometer(batch_size), + epoch_end_callback=mx.callback.do_checkpoint(prefix=self._model_dir_ + self._model_prefix_, period=checkpoint_period), + begin_epoch=begin_epoch, + num_epoch=num_epoch + begin_epoch) + self.module.save_checkpoint(self._model_dir_ + self._model_prefix_, num_epoch + begin_epoch) + self.module.save_checkpoint(self._model_dir_ + self._model_prefix_ + '_newest', 0) + + + def construct(self, context, data_mean=None, data_std=None): + data = mx.sym.var("data", + shape=(0,3,32,32)) + # data, output shape: {[3,32,32]} + + if not data_mean is None: + assert(not data_std is None) + _data_mean_ = mx.sym.Variable("_data_mean_", shape=(3,32,32), init=MyConstant(value=data_mean.tolist())) + _data_mean_ = mx.sym.BlockGrad(_data_mean_) + _data_std_ = mx.sym.Variable("_data_std_", shape=(3,32,32), init=MyConstant(value=data_mean.tolist())) + _data_std_ = mx.sym.BlockGrad(_data_std_) + data = mx.symbol.broadcast_sub(data, _data_mean_) + data = mx.symbol.broadcast_div(data, _data_std_) + conv2_1_ = mx.symbol.pad(data=data, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv2_1_ = mx.symbol.Convolution(data=conv2_1_, + kernel=(3,3), + stride=(1,1), + num_filter=8, + no_bias=False, + name="conv2_1_") + # conv2_1_, output shape: {[8,32,32]} + + batchnorm2_1_ = mx.symbol.BatchNorm(data=conv2_1_, + fix_gamma=True, + name="batchnorm2_1_") + relu2_1_ = mx.symbol.Activation(data=batchnorm2_1_, + act_type='relu', + name="relu2_1_") + + conv3_1_ = mx.symbol.pad(data=relu2_1_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv3_1_ = mx.symbol.Convolution(data=conv3_1_, + kernel=(3,3), + stride=(1,1), + num_filter=8, + no_bias=False, + name="conv3_1_") + # conv3_1_, output shape: {[8,32,32]} + + batchnorm3_1_ = mx.symbol.BatchNorm(data=conv3_1_, + fix_gamma=True, + name="batchnorm3_1_") + conv2_2_ = mx.symbol.Convolution(data=data, + kernel=(1,1), + stride=(1,1), + num_filter=8, + no_bias=False, + name="conv2_2_") + # conv2_2_, output shape: {[8,32,32]} + + batchnorm2_2_ = mx.symbol.BatchNorm(data=conv2_2_, + fix_gamma=True, + name="batchnorm2_2_") + add4_ = batchnorm3_1_ + batchnorm2_2_ + # add4_, output shape: {[8,32,32]} + + relu4_ = mx.symbol.Activation(data=add4_, + act_type='relu', + name="relu4_") + + conv5_1_ = mx.symbol.pad(data=relu4_, + mode='constant', + pad_width=(0,0,0,0,1,0,1,0), + constant_value=0) + conv5_1_ = mx.symbol.Convolution(data=conv5_1_, + kernel=(3,3), + stride=(2,2), + num_filter=16, + no_bias=False, + name="conv5_1_") + # conv5_1_, output shape: {[16,16,16]} + + batchnorm5_1_ = mx.symbol.BatchNorm(data=conv5_1_, + fix_gamma=True, + name="batchnorm5_1_") + relu5_1_ = mx.symbol.Activation(data=batchnorm5_1_, + act_type='relu', + name="relu5_1_") + + conv6_1_ = mx.symbol.pad(data=relu5_1_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv6_1_ = mx.symbol.Convolution(data=conv6_1_, + kernel=(3,3), + stride=(1,1), + num_filter=16, + no_bias=False, + name="conv6_1_") + # conv6_1_, output shape: {[16,16,16]} + + batchnorm6_1_ = mx.symbol.BatchNorm(data=conv6_1_, + fix_gamma=True, + name="batchnorm6_1_") + conv5_2_ = mx.symbol.Convolution(data=relu4_, + kernel=(1,1), + stride=(2,2), + num_filter=16, + no_bias=False, + name="conv5_2_") + # conv5_2_, output shape: {[16,16,16]} + + batchnorm5_2_ = mx.symbol.BatchNorm(data=conv5_2_, + fix_gamma=True, + name="batchnorm5_2_") + add7_ = batchnorm6_1_ + batchnorm5_2_ + # add7_, output shape: {[16,16,16]} + + relu7_ = mx.symbol.Activation(data=add7_, + act_type='relu', + name="relu7_") + + conv8_1_ = mx.symbol.pad(data=relu7_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv8_1_ = mx.symbol.Convolution(data=conv8_1_, + kernel=(3,3), + stride=(1,1), + num_filter=16, + no_bias=False, + name="conv8_1_") + # conv8_1_, output shape: {[16,16,16]} + + batchnorm8_1_ = mx.symbol.BatchNorm(data=conv8_1_, + fix_gamma=True, + name="batchnorm8_1_") + relu8_1_ = mx.symbol.Activation(data=batchnorm8_1_, + act_type='relu', + name="relu8_1_") + + conv9_1_ = mx.symbol.pad(data=relu8_1_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv9_1_ = mx.symbol.Convolution(data=conv9_1_, + kernel=(3,3), + stride=(1,1), + num_filter=16, + no_bias=False, + name="conv9_1_") + # conv9_1_, output shape: {[16,16,16]} + + batchnorm9_1_ = mx.symbol.BatchNorm(data=conv9_1_, + fix_gamma=True, + name="batchnorm9_1_") + add10_ = batchnorm9_1_ + relu7_ + # add10_, output shape: {[16,16,16]} + + relu10_ = mx.symbol.Activation(data=add10_, + act_type='relu', + name="relu10_") + + conv11_1_ = mx.symbol.pad(data=relu10_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv11_1_ = mx.symbol.Convolution(data=conv11_1_, + kernel=(3,3), + stride=(1,1), + num_filter=16, + no_bias=False, + name="conv11_1_") + # conv11_1_, output shape: {[16,16,16]} + + batchnorm11_1_ = mx.symbol.BatchNorm(data=conv11_1_, + fix_gamma=True, + name="batchnorm11_1_") + relu11_1_ = mx.symbol.Activation(data=batchnorm11_1_, + act_type='relu', + name="relu11_1_") + + conv12_1_ = mx.symbol.pad(data=relu11_1_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv12_1_ = mx.symbol.Convolution(data=conv12_1_, + kernel=(3,3), + stride=(1,1), + num_filter=16, + no_bias=False, + name="conv12_1_") + # conv12_1_, output shape: {[16,16,16]} + + batchnorm12_1_ = mx.symbol.BatchNorm(data=conv12_1_, + fix_gamma=True, + name="batchnorm12_1_") + add13_ = batchnorm12_1_ + relu10_ + # add13_, output shape: {[16,16,16]} + + relu13_ = mx.symbol.Activation(data=add13_, + act_type='relu', + name="relu13_") + + conv14_1_ = mx.symbol.pad(data=relu13_, + mode='constant', + pad_width=(0,0,0,0,1,0,1,0), + constant_value=0) + conv14_1_ = mx.symbol.Convolution(data=conv14_1_, + kernel=(3,3), + stride=(2,2), + num_filter=32, + no_bias=False, + name="conv14_1_") + # conv14_1_, output shape: {[32,8,8]} + + batchnorm14_1_ = mx.symbol.BatchNorm(data=conv14_1_, + fix_gamma=True, + name="batchnorm14_1_") + relu14_1_ = mx.symbol.Activation(data=batchnorm14_1_, + act_type='relu', + name="relu14_1_") + + conv15_1_ = mx.symbol.pad(data=relu14_1_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv15_1_ = mx.symbol.Convolution(data=conv15_1_, + kernel=(3,3), + stride=(1,1), + num_filter=32, + no_bias=False, + name="conv15_1_") + # conv15_1_, output shape: {[32,8,8]} + + batchnorm15_1_ = mx.symbol.BatchNorm(data=conv15_1_, + fix_gamma=True, + name="batchnorm15_1_") + conv14_2_ = mx.symbol.Convolution(data=relu13_, + kernel=(1,1), + stride=(2,2), + num_filter=32, + no_bias=False, + name="conv14_2_") + # conv14_2_, output shape: {[32,8,8]} + + batchnorm14_2_ = mx.symbol.BatchNorm(data=conv14_2_, + fix_gamma=True, + name="batchnorm14_2_") + add16_ = batchnorm15_1_ + batchnorm14_2_ + # add16_, output shape: {[32,8,8]} + + relu16_ = mx.symbol.Activation(data=add16_, + act_type='relu', + name="relu16_") + + conv17_1_ = mx.symbol.pad(data=relu16_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv17_1_ = mx.symbol.Convolution(data=conv17_1_, + kernel=(3,3), + stride=(1,1), + num_filter=32, + no_bias=False, + name="conv17_1_") + # conv17_1_, output shape: {[32,8,8]} + + batchnorm17_1_ = mx.symbol.BatchNorm(data=conv17_1_, + fix_gamma=True, + name="batchnorm17_1_") + relu17_1_ = mx.symbol.Activation(data=batchnorm17_1_, + act_type='relu', + name="relu17_1_") + + conv18_1_ = mx.symbol.pad(data=relu17_1_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv18_1_ = mx.symbol.Convolution(data=conv18_1_, + kernel=(3,3), + stride=(1,1), + num_filter=32, + no_bias=False, + name="conv18_1_") + # conv18_1_, output shape: {[32,8,8]} + + batchnorm18_1_ = mx.symbol.BatchNorm(data=conv18_1_, + fix_gamma=True, + name="batchnorm18_1_") + add19_ = batchnorm18_1_ + relu16_ + # add19_, output shape: {[32,8,8]} + + relu19_ = mx.symbol.Activation(data=add19_, + act_type='relu', + name="relu19_") + + conv20_1_ = mx.symbol.pad(data=relu19_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv20_1_ = mx.symbol.Convolution(data=conv20_1_, + kernel=(3,3), + stride=(1,1), + num_filter=32, + no_bias=False, + name="conv20_1_") + # conv20_1_, output shape: {[32,8,8]} + + batchnorm20_1_ = mx.symbol.BatchNorm(data=conv20_1_, + fix_gamma=True, + name="batchnorm20_1_") + relu20_1_ = mx.symbol.Activation(data=batchnorm20_1_, + act_type='relu', + name="relu20_1_") + + conv21_1_ = mx.symbol.pad(data=relu20_1_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv21_1_ = mx.symbol.Convolution(data=conv21_1_, + kernel=(3,3), + stride=(1,1), + num_filter=32, + no_bias=False, + name="conv21_1_") + # conv21_1_, output shape: {[32,8,8]} + + batchnorm21_1_ = mx.symbol.BatchNorm(data=conv21_1_, + fix_gamma=True, + name="batchnorm21_1_") + add22_ = batchnorm21_1_ + relu19_ + # add22_, output shape: {[32,8,8]} + + relu22_ = mx.symbol.Activation(data=add22_, + act_type='relu', + name="relu22_") + + conv23_1_ = mx.symbol.pad(data=relu22_, + mode='constant', + pad_width=(0,0,0,0,1,0,1,0), + constant_value=0) + conv23_1_ = mx.symbol.Convolution(data=conv23_1_, + kernel=(3,3), + stride=(2,2), + num_filter=64, + no_bias=False, + name="conv23_1_") + # conv23_1_, output shape: {[64,4,4]} + + batchnorm23_1_ = mx.symbol.BatchNorm(data=conv23_1_, + fix_gamma=True, + name="batchnorm23_1_") + relu23_1_ = mx.symbol.Activation(data=batchnorm23_1_, + act_type='relu', + name="relu23_1_") + + conv24_1_ = mx.symbol.pad(data=relu23_1_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv24_1_ = mx.symbol.Convolution(data=conv24_1_, + kernel=(3,3), + stride=(1,1), + num_filter=64, + no_bias=False, + name="conv24_1_") + # conv24_1_, output shape: {[64,4,4]} + + batchnorm24_1_ = mx.symbol.BatchNorm(data=conv24_1_, + fix_gamma=True, + name="batchnorm24_1_") + conv23_2_ = mx.symbol.Convolution(data=relu22_, + kernel=(1,1), + stride=(2,2), + num_filter=64, + no_bias=False, + name="conv23_2_") + # conv23_2_, output shape: {[64,4,4]} + + batchnorm23_2_ = mx.symbol.BatchNorm(data=conv23_2_, + fix_gamma=True, + name="batchnorm23_2_") + add25_ = batchnorm24_1_ + batchnorm23_2_ + # add25_, output shape: {[64,4,4]} + + relu25_ = mx.symbol.Activation(data=add25_, + act_type='relu', + name="relu25_") + + conv26_1_ = mx.symbol.pad(data=relu25_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv26_1_ = mx.symbol.Convolution(data=conv26_1_, + kernel=(3,3), + stride=(1,1), + num_filter=64, + no_bias=False, + name="conv26_1_") + # conv26_1_, output shape: {[64,4,4]} + + batchnorm26_1_ = mx.symbol.BatchNorm(data=conv26_1_, + fix_gamma=True, + name="batchnorm26_1_") + relu26_1_ = mx.symbol.Activation(data=batchnorm26_1_, + act_type='relu', + name="relu26_1_") + + conv27_1_ = mx.symbol.pad(data=relu26_1_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv27_1_ = mx.symbol.Convolution(data=conv27_1_, + kernel=(3,3), + stride=(1,1), + num_filter=64, + no_bias=False, + name="conv27_1_") + # conv27_1_, output shape: {[64,4,4]} + + batchnorm27_1_ = mx.symbol.BatchNorm(data=conv27_1_, + fix_gamma=True, + name="batchnorm27_1_") + add28_ = batchnorm27_1_ + relu25_ + # add28_, output shape: {[64,4,4]} + + relu28_ = mx.symbol.Activation(data=add28_, + act_type='relu', + name="relu28_") + + conv29_1_ = mx.symbol.pad(data=relu28_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv29_1_ = mx.symbol.Convolution(data=conv29_1_, + kernel=(3,3), + stride=(1,1), + num_filter=64, + no_bias=False, + name="conv29_1_") + # conv29_1_, output shape: {[64,4,4]} + + batchnorm29_1_ = mx.symbol.BatchNorm(data=conv29_1_, + fix_gamma=True, + name="batchnorm29_1_") + relu29_1_ = mx.symbol.Activation(data=batchnorm29_1_, + act_type='relu', + name="relu29_1_") + + conv30_1_ = mx.symbol.pad(data=relu29_1_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv30_1_ = mx.symbol.Convolution(data=conv30_1_, + kernel=(3,3), + stride=(1,1), + num_filter=64, + no_bias=False, + name="conv30_1_") + # conv30_1_, output shape: {[64,4,4]} + + batchnorm30_1_ = mx.symbol.BatchNorm(data=conv30_1_, + fix_gamma=True, + name="batchnorm30_1_") + add31_ = batchnorm30_1_ + relu28_ + # add31_, output shape: {[64,4,4]} + + relu31_ = mx.symbol.Activation(data=add31_, + act_type='relu', + name="relu31_") + + globalpooling31_ = mx.symbol.Pooling(data=relu31_, + global_pool=True, + kernel=(1,1), + pool_type="avg", + name="globalpooling31_") + # globalpooling31_, output shape: {[64,1,1]} + + fc31_ = mx.symbol.FullyConnected(data=globalpooling31_, + num_hidden=128, + no_bias=False, + name="fc31_") + dropout31_ = mx.symbol.Dropout(data=fc31_, + p=0.5, + name="dropout31_") + fc32_ = mx.symbol.FullyConnected(data=dropout31_, + num_hidden=10, + no_bias=False, + name="fc32_") + + softmax = mx.symbol.SoftmaxOutput(data=fc32_, + name="softmax") + + self.module = mx.mod.Module(symbol=mx.symbol.Group([softmax]), + data_names=self._input_names_, + label_names=self._output_names_, + context=context) diff --git a/src/test/resources/target_code/CNNCreator_VGG16.py b/src/test/resources/target_code/CNNCreator_VGG16.py new file mode 100644 index 0000000..ba39f3a --- /dev/null +++ b/src/test/resources/target_code/CNNCreator_VGG16.py @@ -0,0 +1,462 @@ +import mxnet as mx +import logging +import os +import errno +import shutil +import h5py +import sys +import numpy as np + +@mx.init.register +class MyConstant(mx.init.Initializer): + def __init__(self, value): + super(MyConstant, self).__init__(value=value) + self.value = value + def _init_weight(self, _, arr): + arr[:] = mx.nd.array(self.value) + +class CNNCreator_VGG16: + + module = None + _data_dir_ = "data/VGG16/" + _model_dir_ = "model/VGG16/" + _model_prefix_ = "VGG16" + _input_names_ = ['data'] + _input_shapes_ = [(3,224,224)] + _output_names_ = ['predictions_label'] + + + def load(self, context): + lastEpoch = 0 + param_file = None + + try: + os.remove(self._model_dir_ + self._model_prefix_ + "_newest-0000.params") + except OSError: + pass + try: + os.remove(self._model_dir_ + self._model_prefix_ + "_newest-symbol.json") + except OSError: + pass + + if os.path.isdir(self._model_dir_): + for file in os.listdir(self._model_dir_): + if ".params" in file and self._model_prefix_ in file: + epochStr = file.replace(".params","").replace(self._model_prefix_ + "-","") + epoch = int(epochStr) + if epoch > lastEpoch: + lastEpoch = epoch + param_file = file + if param_file is None: + return 0 + else: + logging.info("Loading checkpoint: " + param_file) + self.module.load(prefix=self._model_dir_ + self._model_prefix_, + epoch=lastEpoch, + data_names=self._input_names_, + label_names=self._output_names_, + context=context) + return lastEpoch + + + def load_data(self, batch_size): + train_h5, test_h5 = self.load_h5_files() + + data_mean = train_h5[self._input_names_[0]][:].mean(axis=0) + data_std = train_h5[self._input_names_[0]][:].std(axis=0) + 1e-5 + + train_iter = mx.io.NDArrayIter(train_h5[self._input_names_[0]], + train_h5[self._output_names_[0]], + batch_size=batch_size, + data_name=self._input_names_[0], + label_name=self._output_names_[0]) + test_iter = None + if test_h5 != None: + test_iter = mx.io.NDArrayIter(test_h5[self._input_names_[0]], + test_h5[self._output_names_[0]], + batch_size=batch_size, + data_name=self._input_names_[0], + label_name=self._output_names_[0]) + return train_iter, test_iter, data_mean, data_std + + def load_h5_files(self): + train_h5 = None + test_h5 = None + train_path = self._data_dir_ + "train.h5" + test_path = self._data_dir_ + "test.h5" + if os.path.isfile(train_path): + train_h5 = h5py.File(train_path, 'r') + if not (self._input_names_[0] in train_h5 and self._output_names_[0] in train_h5): + logging.error("The HDF5 file '" + os.path.abspath(train_path) + "' has to contain the datasets: " + + "'" + self._input_names_[0] + "', '" + self._output_names_[0] + "'") + sys.exit(1) + test_iter = None + if os.path.isfile(test_path): + test_h5 = h5py.File(test_path, 'r') + if not (self._input_names_[0] in test_h5 and self._output_names_[0] in test_h5): + logging.error("The HDF5 file '" + os.path.abspath(test_path) + "' has to contain the datasets: " + + "'" + self._input_names_[0] + "', '" + self._output_names_[0] + "'") + sys.exit(1) + else: + logging.warning("Couldn't load test set. File '" + os.path.abspath(test_path) + "' does not exist.") + return train_h5, test_h5 + else: + logging.error("Data loading failure. File '" + os.path.abspath(train_path) + "' does not exist.") + sys.exit(1) + + + def train(self, batch_size, + num_epoch=10, + optimizer='adam', + optimizer_params=(('learning_rate', 0.001),), + load_checkpoint=True, + context='gpu', + checkpoint_period=5, + normalize=True): + if context == 'gpu': + mx_context = mx.gpu() + elif context == 'cpu': + mx_context = mx.cpu() + else: + logging.error("Context argument is '" + context + "'. Only 'cpu' and 'gpu are valid arguments'.") + + if 'weight_decay' in optimizer_params: + optimizer_params['wd'] = optimizer_params['weight_decay'] + del optimizer_params['weight_decay'] + if 'learning_rate_decay' in optimizer_params: + min_learning_rate = 1e-08 + if 'learning_rate_minimum' in optimizer_params: + min_learning_rate = optimizer_params['learning_rate_minimum'] + del optimizer_params['learning_rate_minimum'] + optimizer_params['lr_scheduler'] = mx.lr_scheduler.FactorScheduler( + optimizer_params['step_size'], + factor=optimizer_params['learning_rate_decay'], + stop_factor_lr=min_learning_rate) + del optimizer_params['step_size'] + del optimizer_params['learning_rate_decay'] + + + train_iter, test_iter, data_mean, data_std = self.load_data(batch_size) + if self.module == None: + if normalize: + self.construct(mx_context, data_mean, data_std) + else: + self.construct(mx_context) + + begin_epoch = 0 + if load_checkpoint: + begin_epoch = self.load(mx_context) + else: + if os.path.isdir(self._model_dir_): + shutil.rmtree(self._model_dir_) + + try: + os.makedirs(self._model_dir_) + except OSError: + if not os.path.isdir(self._model_dir_): + raise + + self.module.fit( + train_data=train_iter, + eval_data=test_iter, + optimizer=optimizer, + optimizer_params=optimizer_params, + batch_end_callback=mx.callback.Speedometer(batch_size), + epoch_end_callback=mx.callback.do_checkpoint(prefix=self._model_dir_ + self._model_prefix_, period=checkpoint_period), + begin_epoch=begin_epoch, + num_epoch=num_epoch + begin_epoch) + self.module.save_checkpoint(self._model_dir_ + self._model_prefix_, num_epoch + begin_epoch) + self.module.save_checkpoint(self._model_dir_ + self._model_prefix_ + '_newest', 0) + + + def construct(self, context, data_mean=None, data_std=None): + data = mx.sym.var("data", + shape=(0,3,224,224)) + # data, output shape: {[3,224,224]} + + if not data_mean is None: + assert(not data_std is None) + _data_mean_ = mx.sym.Variable("_data_mean_", shape=(3,224,224), init=MyConstant(value=data_mean.tolist())) + _data_mean_ = mx.sym.BlockGrad(_data_mean_) + _data_std_ = mx.sym.Variable("_data_std_", shape=(3,224,224), init=MyConstant(value=data_mean.tolist())) + _data_std_ = mx.sym.BlockGrad(_data_std_) + data = mx.symbol.broadcast_sub(data, _data_mean_) + data = mx.symbol.broadcast_div(data, _data_std_) + conv1_ = mx.symbol.pad(data=data, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv1_ = mx.symbol.Convolution(data=conv1_, + kernel=(3,3), + stride=(1,1), + num_filter=64, + no_bias=False, + name="conv1_") + # conv1_, output shape: {[64,224,224]} + + relu1_ = mx.symbol.Activation(data=conv1_, + act_type='relu', + name="relu1_") + + conv2_ = mx.symbol.pad(data=relu1_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv2_ = mx.symbol.Convolution(data=conv2_, + kernel=(3,3), + stride=(1,1), + num_filter=64, + no_bias=False, + name="conv2_") + # conv2_, output shape: {[64,224,224]} + + relu2_ = mx.symbol.Activation(data=conv2_, + act_type='relu', + name="relu2_") + + pool2_ = mx.symbol.Pooling(data=relu2_, + kernel=(2,2), + pool_type="max", + stride=(2,2), + name="pool2_") + # pool2_, output shape: {[64,112,112]} + + conv3_ = mx.symbol.pad(data=pool2_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv3_ = mx.symbol.Convolution(data=conv3_, + kernel=(3,3), + stride=(1,1), + num_filter=128, + no_bias=False, + name="conv3_") + # conv3_, output shape: {[128,112,112]} + + relu3_ = mx.symbol.Activation(data=conv3_, + act_type='relu', + name="relu3_") + + conv4_ = mx.symbol.pad(data=relu3_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv4_ = mx.symbol.Convolution(data=conv4_, + kernel=(3,3), + stride=(1,1), + num_filter=128, + no_bias=False, + name="conv4_") + # conv4_, output shape: {[128,112,112]} + + relu4_ = mx.symbol.Activation(data=conv4_, + act_type='relu', + name="relu4_") + + pool4_ = mx.symbol.Pooling(data=relu4_, + kernel=(2,2), + pool_type="max", + stride=(2,2), + name="pool4_") + # pool4_, output shape: {[128,56,56]} + + conv5_ = mx.symbol.pad(data=pool4_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv5_ = mx.symbol.Convolution(data=conv5_, + kernel=(3,3), + stride=(1,1), + num_filter=256, + no_bias=False, + name="conv5_") + # conv5_, output shape: {[256,56,56]} + + relu5_ = mx.symbol.Activation(data=conv5_, + act_type='relu', + name="relu5_") + + conv6_ = mx.symbol.pad(data=relu5_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv6_ = mx.symbol.Convolution(data=conv6_, + kernel=(3,3), + stride=(1,1), + num_filter=256, + no_bias=False, + name="conv6_") + # conv6_, output shape: {[256,56,56]} + + relu6_ = mx.symbol.Activation(data=conv6_, + act_type='relu', + name="relu6_") + + conv7_ = mx.symbol.pad(data=relu6_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv7_ = mx.symbol.Convolution(data=conv7_, + kernel=(3,3), + stride=(1,1), + num_filter=256, + no_bias=False, + name="conv7_") + # conv7_, output shape: {[256,56,56]} + + relu7_ = mx.symbol.Activation(data=conv7_, + act_type='relu', + name="relu7_") + + pool7_ = mx.symbol.Pooling(data=relu7_, + kernel=(2,2), + pool_type="max", + stride=(2,2), + name="pool7_") + # pool7_, output shape: {[256,28,28]} + + conv8_ = mx.symbol.pad(data=pool7_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv8_ = mx.symbol.Convolution(data=conv8_, + kernel=(3,3), + stride=(1,1), + num_filter=512, + no_bias=False, + name="conv8_") + # conv8_, output shape: {[512,28,28]} + + relu8_ = mx.symbol.Activation(data=conv8_, + act_type='relu', + name="relu8_") + + conv9_ = mx.symbol.pad(data=relu8_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv9_ = mx.symbol.Convolution(data=conv9_, + kernel=(3,3), + stride=(1,1), + num_filter=512, + no_bias=False, + name="conv9_") + # conv9_, output shape: {[512,28,28]} + + relu9_ = mx.symbol.Activation(data=conv9_, + act_type='relu', + name="relu9_") + + conv10_ = mx.symbol.pad(data=relu9_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv10_ = mx.symbol.Convolution(data=conv10_, + kernel=(3,3), + stride=(1,1), + num_filter=512, + no_bias=False, + name="conv10_") + # conv10_, output shape: {[512,28,28]} + + relu10_ = mx.symbol.Activation(data=conv10_, + act_type='relu', + name="relu10_") + + pool10_ = mx.symbol.Pooling(data=relu10_, + kernel=(2,2), + pool_type="max", + stride=(2,2), + name="pool10_") + # pool10_, output shape: {[512,14,14]} + + conv11_ = mx.symbol.pad(data=pool10_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv11_ = mx.symbol.Convolution(data=conv11_, + kernel=(3,3), + stride=(1,1), + num_filter=512, + no_bias=False, + name="conv11_") + # conv11_, output shape: {[512,14,14]} + + relu11_ = mx.symbol.Activation(data=conv11_, + act_type='relu', + name="relu11_") + + conv12_ = mx.symbol.pad(data=relu11_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv12_ = mx.symbol.Convolution(data=conv12_, + kernel=(3,3), + stride=(1,1), + num_filter=512, + no_bias=False, + name="conv12_") + # conv12_, output shape: {[512,14,14]} + + relu12_ = mx.symbol.Activation(data=conv12_, + act_type='relu', + name="relu12_") + + conv13_ = mx.symbol.pad(data=relu12_, + mode='constant', + pad_width=(0,0,0,0,1,1,1,1), + constant_value=0) + conv13_ = mx.symbol.Convolution(data=conv13_, + kernel=(3,3), + stride=(1,1), + num_filter=512, + no_bias=False, + name="conv13_") + # conv13_, output shape: {[512,14,14]} + + relu13_ = mx.symbol.Activation(data=conv13_, + act_type='relu', + name="relu13_") + + pool13_ = mx.symbol.Pooling(data=relu13_, + kernel=(2,2), + pool_type="max", + stride=(2,2), + name="pool13_") + # pool13_, output shape: {[512,7,7]} + + fc13_ = mx.symbol.flatten(data=pool13_) + fc13_ = mx.symbol.FullyConnected(data=fc13_, + num_hidden=4096, + no_bias=False, + name="fc13_") + relu14_ = mx.symbol.Activation(data=fc13_, + act_type='relu', + name="relu14_") + + dropout14_ = mx.symbol.Dropout(data=relu14_, + p=0.5, + name="dropout14_") + fc14_ = mx.symbol.FullyConnected(data=dropout14_, + num_hidden=4096, + no_bias=False, + name="fc14_") + relu15_ = mx.symbol.Activation(data=fc14_, + act_type='relu', + name="relu15_") + + dropout15_ = mx.symbol.Dropout(data=relu15_, + p=0.5, + name="dropout15_") + fc15_ = mx.symbol.FullyConnected(data=dropout15_, + num_hidden=1000, + no_bias=False, + name="fc15_") + + predictions = mx.symbol.SoftmaxOutput(data=fc15_, + name="predictions") + + self.module = mx.mod.Module(symbol=mx.symbol.Group([predictions]), + data_names=self._input_names_, + label_names=self._output_names_, + context=context) diff --git a/src/test/resources/target_code/CNNPredictor_Alexnet.h b/src/test/resources/target_code/CNNPredictor_Alexnet.h new file mode 100644 index 0000000..a4e198d --- /dev/null +++ b/src/test/resources/target_code/CNNPredictor_Alexnet.h @@ -0,0 +1,109 @@ +#ifndef CNNPREDICTOR_ALEXNET +#define CNNPREDICTOR_ALEXNET + +#include + +#include +#include +#include + +#include + +class CNNPredictor_Alexnet{ +public: + const std::string json_file = "model/Alexnet/Alexnet_newest-symbol.json"; + const std::string param_file = "model/Alexnet/Alexnet_newest-0000.params"; + const std::vector input_keys = {"data"}; + //const std::vector input_keys = {"data"}; + const std::vector> input_shapes = {{1,3,224,224}}; + const bool use_gpu = false; + + PredictorHandle handle; + + explicit CNNPredictor_Alexnet(){ + init(json_file, param_file, input_keys, input_shapes, use_gpu); + } + + ~CNNPredictor_Alexnet(){ + if(handle) MXPredFree(handle); + } + + void predict(const vector &data, + vector &predictions){ + MXPredSetInput(handle, "data", data.data(), data.size()); + //MXPredSetInput(handle, "data", data.data(), data.size()); + + MXPredForward(handle); + + mx_uint output_index; + mx_uint *shape = 0; + mx_uint shape_len; + size_t size; + + output_index = 0; + MXPredGetOutputShape(handle, output_index, &shape, &shape_len); + size = 1; + for (mx_uint i = 0; i < shape_len; ++i) size *= shape[i]; + assert(size == predictions.size()); + MXPredGetOutput(handle, 0, &(predictions[0]), predictions.size()); + + } + + void init(const std::string &json_file, + const std::string ¶m_file, + const std::vector &input_keys, + const std::vector> &input_shapes, + const bool &use_gpu){ + + BufferFile json_data(json_file); + BufferFile param_data(param_file); + + int dev_type = use_gpu ? 2 : 1; + int dev_id = 0; + + handle = 0; + + if (json_data.GetLength() == 0 || + param_data.GetLength() == 0) { + std::exit(-1); + } + + const mx_uint num_input_nodes = input_keys.size(); + + const char* input_keys_ptr[num_input_nodes]; + for(mx_uint i = 0; i < num_input_nodes; i++){ + input_keys_ptr[i] = input_keys[i].c_str(); + } + + mx_uint shape_data_size = 0; + mx_uint input_shape_indptr[input_shapes.size() + 1]; + input_shape_indptr[0] = 0; + for(mx_uint i = 0; i < input_shapes.size(); i++){ + input_shape_indptr[i+1] = input_shapes[i].size(); + shape_data_size += input_shapes[i].size(); + } + + mx_uint input_shape_data[shape_data_size]; + mx_uint index = 0; + for(mx_uint i = 0; i < input_shapes.size(); i++){ + for(mx_uint j = 0; j < input_shapes[i].size(); j++){ + input_shape_data[index] = input_shapes[i][j]; + index++; + } + } + + MXPredCreate((const char*)json_data.GetBuffer(), + (const char*)param_data.GetBuffer(), + static_cast(param_data.GetLength()), + dev_type, + dev_id, + num_input_nodes, + input_keys_ptr, + input_shape_indptr, + input_shape_data, + &handle); + assert(handle); + } +}; + +#endif // CNNPREDICTOR_ALEXNET \ No newline at end of file diff --git a/src/test/resources/target_code/CNNPredictor_CifarClassifierNetwork.h b/src/test/resources/target_code/CNNPredictor_CifarClassifierNetwork.h new file mode 100644 index 0000000..32676e7 --- /dev/null +++ b/src/test/resources/target_code/CNNPredictor_CifarClassifierNetwork.h @@ -0,0 +1,109 @@ +#ifndef CNNPREDICTOR_CIFARCLASSIFIERNETWORK +#define CNNPREDICTOR_CIFARCLASSIFIERNETWORK + +#include + +#include +#include +#include + +#include + +class CNNPredictor_CifarClassifierNetwork{ +public: + const std::string json_file = "model/CifarClassifierNetwork/CifarClassifierNetwork_newest-symbol.json"; + const std::string param_file = "model/CifarClassifierNetwork/CifarClassifierNetwork_newest-0000.params"; + const std::vector input_keys = {"data"}; + //const std::vector input_keys = {"data"}; + const std::vector> input_shapes = {{1,3,32,32}}; + const bool use_gpu = false; + + PredictorHandle handle; + + explicit CNNPredictor_CifarClassifierNetwork(){ + init(json_file, param_file, input_keys, input_shapes, use_gpu); + } + + ~CNNPredictor_CifarClassifierNetwork(){ + if(handle) MXPredFree(handle); + } + + void predict(const vector &data, + vector &softmax){ + MXPredSetInput(handle, "data", data.data(), data.size()); + //MXPredSetInput(handle, "data", data.data(), data.size()); + + MXPredForward(handle); + + mx_uint output_index; + mx_uint *shape = 0; + mx_uint shape_len; + size_t size; + + output_index = 0; + MXPredGetOutputShape(handle, output_index, &shape, &shape_len); + size = 1; + for (mx_uint i = 0; i < shape_len; ++i) size *= shape[i]; + assert(size == softmax.size()); + MXPredGetOutput(handle, 0, &(softmax[0]), softmax.size()); + + } + + void init(const std::string &json_file, + const std::string ¶m_file, + const std::vector &input_keys, + const std::vector> &input_shapes, + const bool &use_gpu){ + + BufferFile json_data(json_file); + BufferFile param_data(param_file); + + int dev_type = use_gpu ? 2 : 1; + int dev_id = 0; + + handle = 0; + + if (json_data.GetLength() == 0 || + param_data.GetLength() == 0) { + std::exit(-1); + } + + const mx_uint num_input_nodes = input_keys.size(); + + const char* input_keys_ptr[num_input_nodes]; + for(mx_uint i = 0; i < num_input_nodes; i++){ + input_keys_ptr[i] = input_keys[i].c_str(); + } + + mx_uint shape_data_size = 0; + mx_uint input_shape_indptr[input_shapes.size() + 1]; + input_shape_indptr[0] = 0; + for(mx_uint i = 0; i < input_shapes.size(); i++){ + input_shape_indptr[i+1] = input_shapes[i].size(); + shape_data_size += input_shapes[i].size(); + } + + mx_uint input_shape_data[shape_data_size]; + mx_uint index = 0; + for(mx_uint i = 0; i < input_shapes.size(); i++){ + for(mx_uint j = 0; j < input_shapes[i].size(); j++){ + input_shape_data[index] = input_shapes[i][j]; + index++; + } + } + + MXPredCreate((const char*)json_data.GetBuffer(), + (const char*)param_data.GetBuffer(), + static_cast(param_data.GetLength()), + dev_type, + dev_id, + num_input_nodes, + input_keys_ptr, + input_shape_indptr, + input_shape_data, + &handle); + assert(handle); + } +}; + +#endif // CNNPREDICTOR_CIFARCLASSIFIERNETWORK \ No newline at end of file diff --git a/src/test/resources/target_code/CNNPredictor_VGG16.h b/src/test/resources/target_code/CNNPredictor_VGG16.h new file mode 100644 index 0000000..2bdc19c --- /dev/null +++ b/src/test/resources/target_code/CNNPredictor_VGG16.h @@ -0,0 +1,109 @@ +#ifndef CNNPREDICTOR_VGG16 +#define CNNPREDICTOR_VGG16 + +#include + +#include +#include +#include + +#include + +class CNNPredictor_VGG16{ +public: + const std::string json_file = "model/VGG16/VGG16_newest-symbol.json"; + const std::string param_file = "model/VGG16/VGG16_newest-0000.params"; + const std::vector input_keys = {"data"}; + //const std::vector input_keys = {"data"}; + const std::vector> input_shapes = {{1,3,224,224}}; + const bool use_gpu = false; + + PredictorHandle handle; + + explicit CNNPredictor_VGG16(){ + init(json_file, param_file, input_keys, input_shapes, use_gpu); + } + + ~CNNPredictor_VGG16(){ + if(handle) MXPredFree(handle); + } + + void predict(const vector &data, + vector &predictions){ + MXPredSetInput(handle, "data", data.data(), data.size()); + //MXPredSetInput(handle, "data", data.data(), data.size()); + + MXPredForward(handle); + + mx_uint output_index; + mx_uint *shape = 0; + mx_uint shape_len; + size_t size; + + output_index = 0; + MXPredGetOutputShape(handle, output_index, &shape, &shape_len); + size = 1; + for (mx_uint i = 0; i < shape_len; ++i) size *= shape[i]; + assert(size == predictions.size()); + MXPredGetOutput(handle, 0, &(predictions[0]), predictions.size()); + + } + + void init(const std::string &json_file, + const std::string ¶m_file, + const std::vector &input_keys, + const std::vector> &input_shapes, + const bool &use_gpu){ + + BufferFile json_data(json_file); + BufferFile param_data(param_file); + + int dev_type = use_gpu ? 2 : 1; + int dev_id = 0; + + handle = 0; + + if (json_data.GetLength() == 0 || + param_data.GetLength() == 0) { + std::exit(-1); + } + + const mx_uint num_input_nodes = input_keys.size(); + + const char* input_keys_ptr[num_input_nodes]; + for(mx_uint i = 0; i < num_input_nodes; i++){ + input_keys_ptr[i] = input_keys[i].c_str(); + } + + mx_uint shape_data_size = 0; + mx_uint input_shape_indptr[input_shapes.size() + 1]; + input_shape_indptr[0] = 0; + for(mx_uint i = 0; i < input_shapes.size(); i++){ + input_shape_indptr[i+1] = input_shapes[i].size(); + shape_data_size += input_shapes[i].size(); + } + + mx_uint input_shape_data[shape_data_size]; + mx_uint index = 0; + for(mx_uint i = 0; i < input_shapes.size(); i++){ + for(mx_uint j = 0; j < input_shapes[i].size(); j++){ + input_shape_data[index] = input_shapes[i][j]; + index++; + } + } + + MXPredCreate((const char*)json_data.GetBuffer(), + (const char*)param_data.GetBuffer(), + static_cast(param_data.GetLength()), + dev_type, + dev_id, + num_input_nodes, + input_keys_ptr, + input_shape_indptr, + input_shape_data, + &handle); + assert(handle); + } +}; + +#endif // CNNPREDICTOR_VGG16 \ No newline at end of file diff --git a/src/test/resources/target_code/execute_Alexnet b/src/test/resources/target_code/execute_Alexnet new file mode 100644 index 0000000..7f1bc1b --- /dev/null +++ b/src/test/resources/target_code/execute_Alexnet @@ -0,0 +1,6 @@ + vector CNN_predictions(10); + + _cnn_.predict(CNNTranslator::translate(data), + CNN_predictions); + + predictions = CNNTranslator::translateToCol(CNN_predictions, std::vector {10}); \ No newline at end of file diff --git a/src/test/resources/target_code/execute_CifarClassifierNetwork b/src/test/resources/target_code/execute_CifarClassifierNetwork new file mode 100644 index 0000000..1611f85 --- /dev/null +++ b/src/test/resources/target_code/execute_CifarClassifierNetwork @@ -0,0 +1,6 @@ + vector CNN_softmax(10); + + _cnn_.predict(CNNTranslator::translate(data), + CNN_softmax); + + softmax = CNNTranslator::translateToCol(CNN_softmax, std::vector {10}); \ No newline at end of file diff --git a/src/test/resources/target_code/execute_VGG16 b/src/test/resources/target_code/execute_VGG16 new file mode 100644 index 0000000..dcad53e --- /dev/null +++ b/src/test/resources/target_code/execute_VGG16 @@ -0,0 +1,6 @@ + vector CNN_predictions(1000); + + _cnn_.predict(CNNTranslator::translate(data), + CNN_predictions); + + predictions = CNNTranslator::translateToCol(CNN_predictions, std::vector {1000}); \ No newline at end of file diff --git a/src/test/resources/valid_tests/Alexnet_alt.cnna b/src/test/resources/valid_tests/Alexnet_alt.cnna new file mode 100644 index 0000000..9a27ff0 --- /dev/null +++ b/src/test/resources/valid_tests/Alexnet_alt.cnna @@ -0,0 +1,53 @@ +architecture Alexnet_alt(img_height=224, img_width=224, img_channels=3, classes=10){ + def input Z(0:255)^{img_channels, img_height, img_width} image + def output Q(0:1)^{classes} predictions + + image -> + Convolution(kernel=(11,11), channels=96, stride=(4,4), padding="no_loss") -> + Lrn(nsize=5, alpha=0.0001, beta=0.75) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), padding="no_loss") -> + Relu() -> + Split(n=2) -> + ( + [0] -> + Convolution(kernel=(5,5), channels=128) -> + Lrn(nsize=5, alpha=0.0001, beta=0.75) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), padding="no_loss") -> + Relu() + | + [1] -> + Convolution(kernel=(5,5), channels=128) -> + Lrn(nsize=5, alpha=0.0001, beta=0.75) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), padding="no_loss") -> + Relu() + ) -> + Concatenate() -> + Convolution(kernel=(3,3), channels=384) -> + Relu() -> + Split(n=2) -> + ( + [0] -> + Convolution(kernel=(3,3), channels=192) -> + Relu() -> + Convolution(kernel=(3,3), channels=128) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), padding="no_loss") -> + Relu() + | + [1] -> + Convolution(kernel=(3,3), channels=192) -> + Relu() -> + Convolution(kernel=(3,3), channels=128) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), padding="no_loss") -> + Relu() + ) -> + Concatenate() -> + FullyConnected(units=4096) -> + Relu() -> + Dropout() -> + FullyConnected(units=4096) -> + Relu() -> + Dropout() -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} \ No newline at end of file diff --git a/src/test/resources/valid_tests/Alexnet_alt2.cnna b/src/test/resources/valid_tests/Alexnet_alt2.cnna new file mode 100644 index 0000000..fab6f88 --- /dev/null +++ b/src/test/resources/valid_tests/Alexnet_alt2.cnna @@ -0,0 +1,39 @@ +architecture Alexnet_alt2(img_height=224, img_width=224, img_channels=3, classes=10){ + def input Z(0:255)^{img_channels, img_height, img_width} image + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels, convStride=1, poolStride=1, hasLrn=false, convPadding="same"){ + Convolution(kernel=(kernel,kernel), channels=channels, stride=(convStride,convStride), padding=convPadding) -> + Lrn(nsize=5, alpha=0.0001, beta=0.75, ?=hasLrn) -> + Pooling(pool_type="max", kernel=(3,3), stride=(poolStride,poolStride), padding="no_loss", ?=(poolStride != 1)) -> + Relu() + } + def split1(i){ + [i] -> + conv(kernel=5, channels=128, poolStride=2, hasLrn=true) + } + def split2(i){ + [i] -> + conv(kernel=3, channels=192) -> + conv(kernel=3, channels=128, poolStride=2) + } + def fc(){ + FullyConnected(units=4096) -> + Relu() -> + Dropout() + } + + image -> + conv(kernel=11, channels=96, convStride=4, poolStride=2, hasLrn=true, convPadding="no_loss") -> + Split(n=2) -> + split1(i=[0|1]) -> + Concatenate() -> + conv(kernel=3, channels=384) -> + Split(n=2) -> + split2(i=[0|1]) -> + Concatenate() -> + fc(-> = 2) -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} \ No newline at end of file diff --git a/src/test/resources/valid_tests/ArgumentSequenceTest.cnna b/src/test/resources/valid_tests/ArgumentSequenceTest.cnna new file mode 100644 index 0000000..28e95e1 --- /dev/null +++ b/src/test/resources/valid_tests/ArgumentSequenceTest.cnna @@ -0,0 +1,13 @@ +architecture ArgumentSequenceTest { + def input Z(0:255)^{3, 224, 224} image + def output Q(0:1)^{10} predictions + + image -> + Convolution(kernel=(5,5), channels=128, stride=(2,2)) -> + FullyConnected(units=[]) -> + Convolution(kernel=[ |(3,3)| (5,5)->(3,3) ], channels=[128|256|512]) -> + Concatenate() -> + FullyConnected(units=10) -> + Softmax() -> + predictions +} \ No newline at end of file diff --git a/src/test/resources/valid_tests/CifarClassifierNetwork.cnna b/src/test/resources/valid_tests/CifarClassifierNetwork.cnna new file mode 100644 index 0000000..5eba014 --- /dev/null +++ b/src/test/resources/valid_tests/CifarClassifierNetwork.cnna @@ -0,0 +1,35 @@ +architecture CifarClassifierNetwork(classes=10){ + def input Z(0:255)^{3, 32, 32} data + def output Q(0:1)^{classes} softmax + + def conv(kernel, channels, stride=1, act=true){ + Convolution(kernel=(kernel,kernel), channels=channels, stride=(stride,stride)) -> + BatchNorm() -> + Relu(?=act) + } + def resLayer(channels, stride=1, addSkipConv=false){ + ( + conv(kernel=3, channels=channels, stride=stride) -> + conv(kernel=3, channels=channels, act=false) + | + conv(kernel=1, channels=channels, stride=stride, act=false, ? = addSkipConv) + ) -> + Add() -> + Relu() + } + + data -> + resLayer(channels=8, addSkipConv=true) -> + resLayer(channels=16, stride=2, addSkipConv=true) -> + resLayer(channels=16, ->=2) -> + resLayer(channels=32, stride=2, addSkipConv=true) -> + resLayer(channels=32, ->=2) -> + resLayer(channels=64, stride=2, addSkipConv=true) -> + resLayer(channels=64, ->=2) -> + GlobalPooling(pool_type="avg") -> + FullyConnected(units=128) -> + Dropout()-> + FullyConnected(units=classes) -> + Softmax() -> + softmax +} \ No newline at end of file diff --git a/src/test/resources/valid_tests/Fixed_Alexnet.cnna b/src/test/resources/valid_tests/Fixed_Alexnet.cnna new file mode 100644 index 0000000..656e618 --- /dev/null +++ b/src/test/resources/valid_tests/Fixed_Alexnet.cnna @@ -0,0 +1,43 @@ +architecture Fixed_Alexnet(){ + def input Z(0:255)^{3, 224, 224} image + def output Q(0:1)^{10} predictions + + def group1(i){ + [i] -> + Convolution(kernel=(5,5), channels=128) -> + Lrn(nsize=5, alpha=0.0001, beta=0.75) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), padding="no_loss") -> + Relu() + } + def group2(i){ + [i] -> + Convolution(kernel=(3,3), channels=192) -> + Relu() -> + Convolution(kernel=(3,3), channels=128) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), padding="no_loss") -> + Relu() + } + def fc(){ + FullyConnected(units=4096) -> + Relu() -> + Dropout() + } + + image -> + Convolution(kernel=(11,11), channels=96, stride=(4,4), padding="no_loss") -> + Lrn(nsize=5, alpha=0.0001, beta=0.75) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2), padding="no_loss") -> + Relu() -> + Split(n=2) -> + group1(i=[0|1]) -> + Concatenate() -> + Convolution(kernel=(3,3), channels=384) -> + Relu() -> + Split(n=2) -> + group2(i=[0|1]) -> + Concatenate() -> + fc(->=2) -> + FullyConnected(units=10) -> + Softmax() -> + predictions +} \ No newline at end of file diff --git a/src/test/resources/valid_tests/Fixed_ThreeInputCNN_M14.cnna b/src/test/resources/valid_tests/Fixed_ThreeInputCNN_M14.cnna new file mode 100644 index 0000000..8fe709e --- /dev/null +++ b/src/test/resources/valid_tests/Fixed_ThreeInputCNN_M14.cnna @@ -0,0 +1,27 @@ +architecture Fixed_ThreeInputCNN_M14(){ + def input Z(0:255)^{3, 200, 300} image[3] + def output Q(0:1)^{3} predictions + + def conv(kernel, channels){ + Convolution(kernel=kernel, channels=channels) -> + Relu() + } + + def inputGroup(index){ + [index] -> + conv(kernel=(3,3), channels=32, ->=3) -> + Pooling(pool_type="max", kernel=(2,2), stride=(2,2)) + } + + image -> + inputGroup(index=[0|..|2]) -> + Concatenate() -> + conv(kernel=(3,3), channels=64) -> + Pooling(pool_type="max", kernel=(2,2), stride=(2,2)) -> + + FullyConnected(units=32) -> + Relu() -> + FullyConnected(units=3) -> + Softmax() -> + predictions +} \ No newline at end of file diff --git a/src/test/resources/valid_tests/MultipleOutputs.cnna b/src/test/resources/valid_tests/MultipleOutputs.cnna new file mode 100644 index 0000000..2682693 --- /dev/null +++ b/src/test/resources/valid_tests/MultipleOutputs.cnna @@ -0,0 +1,21 @@ +architecture MultipleOutputs{ + def input Q(-oo:+oo)^{10} data + def output Q(0:1)^{4} pred[2] + + data -> + FullyConnected(units=128, no_bias=true) -> + Tanh() -> + ( + FullyConnected(units=16, no_bias=true) -> + Tanh() -> + FullyConnected(units=4, no_bias=true) -> + Softmax() + | + FullyConnected(units=16, no_bias=true) -> + Tanh() -> + FullyConnected(units=4, no_bias=true) -> + Softmax() + ) -> + pred + +} \ No newline at end of file diff --git a/src/test/resources/valid_tests/ResNeXt50_InstanceTest.cnna b/src/test/resources/valid_tests/ResNeXt50_InstanceTest.cnna new file mode 100644 index 0000000..ad939bf --- /dev/null +++ b/src/test/resources/valid_tests/ResNeXt50_InstanceTest.cnna @@ -0,0 +1,44 @@ +architecture ResNeXt50_InstanceTest(img_height=224, img_width=224, img_channels=3, classes=1000, cardinality=32){ + def input Z(0:255)^{img_channels, img_height, img_width} data + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels, stride=1, act=true){ + Convolution(kernel=(kernel,kernel), channels=channels, stride=(stride,stride)) -> + BatchNorm() -> + Relu(?=act) + } + def resGroup(innerChannels, outChannels, stride=1){ + conv(kernel=1, channels=innerChannels) -> + conv(kernel=3, channels=innerChannels, stride=stride) -> + conv(kernel=1, channels=outChannels, act=false) + } + def resLayer(innerChannels, outChannels, stride=1, addSkipConv=false){ + ( + resGroup(innerChannels=innerChannels, + outChannels=outChannels, + stride=stride, + | = cardinality) -> + Add() + | + conv(kernel=1, channels=outChannels, stride=stride, act=false, ? = addSkipConv) + ) -> + Add() -> + Relu() + } + + data -> + conv(kernel=7, channels=64, stride=2) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2)) -> + resLayer(innerChannels=4, outChannels=256, addSkipConv=true) -> + resLayer(innerChannels=4, outChannels=256, -> = 2) -> + resLayer(innerChannels=8, outChannels=512, stride=2, addSkipConv=true) -> + resLayer(innerChannels=8, outChannels=512, -> = 3) -> + resLayer(innerChannels=16, outChannels=1024, stride=2, addSkipConv=true) -> + resLayer(innerChannels=16, outChannels=1024, -> = 5) -> + resLayer(innerChannels=32, outChannels=2048, stride=2, addSkipConv=true) -> + resLayer(innerChannels=32, outChannels=2048, -> = 2) -> + GlobalPooling(pool_type="avg") -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} \ No newline at end of file diff --git a/src/test/resources/valid_tests/ResNeXt50_alt.cnna b/src/test/resources/valid_tests/ResNeXt50_alt.cnna new file mode 100644 index 0000000..6fa681b --- /dev/null +++ b/src/test/resources/valid_tests/ResNeXt50_alt.cnna @@ -0,0 +1,46 @@ +architecture ResNeXt50_alt(img_height=224, img_width=224, img_channels=3, classes=1000){ + def input Z(0:255)^{img_channels, img_height, img_width} image + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels, stride=1, act=true){ + Convolution(kernel=kernel, channels=channels, stride=(stride,stride)) -> + BatchNorm() -> + Relu(?=act) + } + def resGroup(innerChannels, outChannels, stride=1){ + conv(kernel=(1,1), channels=innerChannels) -> + conv(kernel=(3,3), channels=innerChannels, stride=stride) -> + conv(kernel=(1,1), channels=outChannels, act=false) + } + def skip(outChannels, stride){ + Convolution(kernel=(1,1), channels=outChannels, stride=(stride,stride)) -> + BatchNorm() + } + def resLayer(innerChannels, outChannels, stride=1, changedChannels=false){ + ( + resGroup(innerChannels=innerChannels, + outChannels=outChannels, + stride=stride, + | = 32) -> + Add() + | + skip(outChannels=outChannels, stride=stride, ? = (stride!=1 || changedChannels)) + ) -> + Add() -> + Relu() + } + def resStructure(innerChannels, outChannels, resLayers){ + resLayer(innerChannels=innerChannels, outChannels=outChannels, stride=2) -> + resLayer(innerChannels=innerChannels, outChannels=outChannels, -> = resLayers - 1) + } + + image -> + conv(kernel=(7,7), channels=64, stride=2) -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2)) -> + resLayer(innerChannels=4, outChannels=256, changedChannels=true, -> = 3) -> + resStructure(innerChannels=[8->16->32], outChannels=[512->1024->2048], resLayers=[4->6->3]) -> + GlobalPooling(pool_type="avg") -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} \ No newline at end of file diff --git a/src/test/resources/valid_tests/ResNet152_alt.cnna b/src/test/resources/valid_tests/ResNet152_alt.cnna new file mode 100644 index 0000000..dfba32d --- /dev/null +++ b/src/test/resources/valid_tests/ResNet152_alt.cnna @@ -0,0 +1,40 @@ +architecture ResNet152_alt(img_height=224, img_width=224, img_channels=3, classes=1000){ + def input Z(0:255)^{img_channels, img_height, img_width} data + def output Q(0:1)^{classes} predictions + + def resLayer(channels, stride=1, addSkipConv=false){ + ( + Convolution(kernel=(1,1), channels=channels, stride=(stride,stride)) -> + BatchNorm() -> + Relu() -> + Convolution(kernel=(3,3), channels=channels) -> + BatchNorm() -> + Relu() -> + Convolution(kernel=(1,1), channels=4*channels) -> + BatchNorm() + | + Convolution(kernel=(1,1), channels=4*channels, stride=(stride,stride), ? = addSkipConv) -> + BatchNorm(? = addSkipConv) + ) -> + Add() -> + Relu() + } + + data -> + Convolution(kernel=(7,7), channels=64, stride=(2,2)) -> + BatchNorm() -> + Relu() -> + Pooling(pool_type="max", kernel=(3,3), stride=(2,2)) -> + resLayer(channels=64, addSkipConv=true) -> + resLayer(channels=64, ->=2) -> + resLayer(channels=128, stride=2, addSkipConv=true) -> + resLayer(channels=128, ->=7) -> + resLayer(channels=256, stride=2, addSkipConv=true) -> + resLayer(channels=256, ->=35) -> + resLayer(channels=512, stride=2, addSkipConv=true) -> + resLayer(channels=512, ->=2) -> + GlobalPooling(pool_type="avg") -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} \ No newline at end of file diff --git a/src/test/resources/valid_tests/SimpleNetworkLinear.cnna b/src/test/resources/valid_tests/SimpleNetworkLinear.cnna new file mode 100644 index 0000000..cbb8a38 --- /dev/null +++ b/src/test/resources/valid_tests/SimpleNetworkLinear.cnna @@ -0,0 +1,10 @@ +architecture SimpleNetworkLinear(inputs=10, classes=2){ + def input Q(-oo:+oo)^{inputs} in1 + def output Q(-oo:oo)^{classes} out1 + + in1 -> + FullyConnected(units=64, no_bias=true) -> + Tanh() -> + FullyConnected(units=classes, no_bias=true) -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/valid_tests/SimpleNetworkRelu.cnna b/src/test/resources/valid_tests/SimpleNetworkRelu.cnna new file mode 100644 index 0000000..9d8354c --- /dev/null +++ b/src/test/resources/valid_tests/SimpleNetworkRelu.cnna @@ -0,0 +1,11 @@ +architecture SimpleNetworkRelu(inputs=10, classes=2){ + def input Q(-oo:+oo)^{inputs} in1 + def output Q(0:oo)^{classes} out1 + + in1 -> + FullyConnected(units=64, no_bias=true) -> + Tanh() -> + FullyConnected(units=classes, no_bias=true) -> + Relu() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/valid_tests/SimpleNetworkSigmoid.cnna b/src/test/resources/valid_tests/SimpleNetworkSigmoid.cnna new file mode 100644 index 0000000..08098a6 --- /dev/null +++ b/src/test/resources/valid_tests/SimpleNetworkSigmoid.cnna @@ -0,0 +1,11 @@ +architecture SimpleNetworkSigmoid(inputs=10, classes=2){ + def input Q(-oo:+oo)^{inputs} in1 + def output Q(0:1)^{classes} out1 + + in1 -> + FullyConnected(units=64, no_bias=true) -> + Tanh() -> + FullyConnected(units=classes, no_bias=true) -> + Sigmoid() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/valid_tests/SimpleNetworkSoftmax.cnna b/src/test/resources/valid_tests/SimpleNetworkSoftmax.cnna new file mode 100644 index 0000000..5c0643c --- /dev/null +++ b/src/test/resources/valid_tests/SimpleNetworkSoftmax.cnna @@ -0,0 +1,11 @@ +architecture SimpleNetworkSoftmax(inputs=10, classes=2){ + def input Q(-oo:+oo)^{inputs} in1 + def output Q(0:1)^{classes} out1 + + in1 -> + FullyConnected(units=64, no_bias=true) -> + Tanh() -> + FullyConnected(units=classes, no_bias=true) -> + Softmax() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/valid_tests/SimpleNetworkTanh.cnna b/src/test/resources/valid_tests/SimpleNetworkTanh.cnna new file mode 100644 index 0000000..bd1ffec --- /dev/null +++ b/src/test/resources/valid_tests/SimpleNetworkTanh.cnna @@ -0,0 +1,11 @@ +architecture SimpleNetworkTanh(inputs=10, classes=2){ + def input Q(-oo:+oo)^{inputs} in1 + def output Q(-1:1)^{classes} out1 + + in1 -> + FullyConnected(units=64, no_bias=true) -> + Tanh() -> + FullyConnected(units=classes, no_bias=true) -> + Tanh() -> + out1 +} \ No newline at end of file diff --git a/src/test/resources/valid_tests/ThreeInputCNN_M14_alternative.cnna b/src/test/resources/valid_tests/ThreeInputCNN_M14_alternative.cnna new file mode 100644 index 0000000..5b73e1d --- /dev/null +++ b/src/test/resources/valid_tests/ThreeInputCNN_M14_alternative.cnna @@ -0,0 +1,32 @@ +architecture ThreeInputCNN_M14_alternative(img_height=200, img_width=300, img_channels=3, classes=3){ + /*CNN used for flower grading. Model 14 of the paper. + *Title: Multi-Input Convolutional Neural Network for Flower Grading + *Authors: Yu Sun, Lin Zhu, Guan Wang, and Fang Zhao. + *Year: 2017*/ + + def input Z(0:255)^{img_channels, img_height, img_width} image[3] + def output Q(0:1)^{classes} predictions + + def conv(kernel, channels){ + Convolution(kernel=kernel, channels=channels) -> + Relu() + } + + def inputGroup(index){ + [index] -> + conv(kernel=(3,3), channels=32, ->=3) -> + Pooling(pool_type="max", kernel=(2,2), stride=(2,2)) + } + + (image[0] | image[1] | image[2]) -> + inputGroup(index=[0|..|2]) -> + Concatenate() -> + conv(kernel=(3,3), channels=64) -> + Pooling(pool_type="max", kernel=(2,2), stride=(2,2)) -> + + FullyConnected(units=32) -> + Relu() -> + FullyConnected(units=classes) -> + Softmax() -> + predictions +} -- GitLab