From 8853c1f6a802fe7549b89b74ed850aec455b923a Mon Sep 17 00:00:00 2001 From: Sadik Armagan Date: Mon, 22 Oct 2018 09:04:18 +0100 Subject: IVGCVSW-2026 + IVGCVSW-2027 Add FullyConnected Support to TfLiteParser Change-Id: Id48f97ee33e2fd650a1ee3365ef66bdfc514a586 --- CMakeLists.txt | 1 + src/armnnTfLiteParser/TfLiteParser.cpp | 70 ++++++++++++ src/armnnTfLiteParser/TfLiteParser.hpp | 1 + src/armnnTfLiteParser/test/FullyConnected.cpp | 154 ++++++++++++++++++++++++++ 4 files changed, 226 insertions(+) create mode 100644 src/armnnTfLiteParser/test/FullyConnected.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fab3d9d8b3..5cdc07da35 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -414,6 +414,7 @@ if(BUILD_UNIT_TESTS) src/armnnTfLiteParser/test/Concatenation.cpp src/armnnTfLiteParser/test/Conv2D.cpp src/armnnTfLiteParser/test/DepthwiseConvolution2D.cpp + src/armnnTfLiteParser/test/FullyConnected.cpp src/armnnTfLiteParser/test/MaxPool2D.cpp src/armnnTfLiteParser/test/Reshape.cpp src/armnnTfLiteParser/test/Softmax.cpp diff --git a/src/armnnTfLiteParser/TfLiteParser.cpp b/src/armnnTfLiteParser/TfLiteParser.cpp index 216c09014c..8b1d3e6bc4 100644 --- a/src/armnnTfLiteParser/TfLiteParser.cpp +++ b/src/armnnTfLiteParser/TfLiteParser.cpp @@ -456,6 +456,7 @@ TfLiteParser::TfLiteParser() m_ParserFunctions[tflite::BuiltinOperator_CONCATENATION] = &TfLiteParser::ParseConcatenation; m_ParserFunctions[tflite::BuiltinOperator_CONV_2D] = &TfLiteParser::ParseConv2D; m_ParserFunctions[tflite::BuiltinOperator_DEPTHWISE_CONV_2D] = &TfLiteParser::ParseDepthwiseConv2D; + m_ParserFunctions[tflite::BuiltinOperator_FULLY_CONNECTED] = &TfLiteParser::ParseFullyConnected; m_ParserFunctions[tflite::BuiltinOperator_MAX_POOL_2D] = &TfLiteParser::ParseMaxPool2D; m_ParserFunctions[tflite::BuiltinOperator_RELU] = &TfLiteParser::ParseRelu; m_ParserFunctions[tflite::BuiltinOperator_RELU6] = &TfLiteParser::ParseRelu6; @@ -1219,6 +1220,75 @@ void TfLiteParser::ParseConcatenation(size_t subgraphIndex, size_t operatorIndex RegisterOutputSlots(subgraphIndex, operatorIndex, layer, {outputTensorIndexes[0]}); } +void TfLiteParser::ParseFullyConnected(size_t subgraphIndex, size_t operatorIndex) +{ + CHECK_MODEL(m_Model, subgraphIndex, operatorIndex); + + const auto & operatorRfr = m_Model->subgraphs[subgraphIndex]->operators[operatorIndex]; + const auto options = operatorRfr->builtin_options.AsFullyConnectedOptions(); + + CHECK_SUPPORTED_FUSED_ACTIVATION(options, subgraphIndex, operatorIndex); + + FullyConnectedDescriptor desc; + desc.m_BiasEnabled = false; + + auto inputs = GetInputs(m_Model, subgraphIndex, operatorIndex); + auto outputs = GetOutputs(m_Model, subgraphIndex, operatorIndex); + CHECK_VALID_SIZE(outputs.size(), 1); + + armnn::TensorInfo filterTensorInfo = ToTensorInfo(inputs[1]); + + // Fully Connected Layer accepts two dimensional weights input + int32_t weightsDimension = static_cast(filterTensorInfo.GetNumDimensions()); + if (weightsDimension != 2) + { + throw ParseException( + boost::str( + boost::format( + "Dimension %1% for Fully Connected weights is not supported by Armnn. " + "Node %2%") + % weightsDimension + % CHECK_LOCATION().AsString())); + } + + auto filterTensorAndData = CreateConstTensor(inputs[1], filterTensorInfo, false); + armnn::IConnectableLayer* layer; + auto layerName = boost::str(boost::format("FullyConnected:%1%:%2%") % subgraphIndex % operatorIndex); + + if (inputs.size() == 3) + { + desc.m_BiasEnabled = true; + TensorInfo biasTensorInfo = ToTensorInfo(inputs[2]); + auto biasTensorAndData = CreateConstTensor(inputs[2], biasTensorInfo, false); + layer = m_Network->AddFullyConnectedLayer(desc, + filterTensorAndData.first, + biasTensorAndData.first, + layerName.c_str()); + } + else + { + layer = m_Network->AddFullyConnectedLayer(desc, + filterTensorAndData.first, + layerName.c_str()); + } + BOOST_ASSERT(layer != nullptr); + + armnn::TensorInfo outputTensorInfo = ToTensorInfo(outputs[0]); + layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo); + + // register the input connection slot for the layer + // only the tensors for the inputs are relevant, exclude the const tensors + auto inputTensorIndexes = AsUnsignedVector(GetInputTensorIds(m_Model, subgraphIndex, operatorIndex)); + RegisterInputSlots(subgraphIndex, operatorIndex, layer, {inputTensorIndexes[0]}); + + // we need to add the activation layer and fortunately we don't need to care about the data layout + armnn::IConnectableLayer* fusedActivationLayer = AddFusedActivationLayer(layer, 0, + options->fused_activation_function); + // register the output connection slots for the layer, connections are made after all layers have been created + auto outputTensorIndexes = AsUnsignedVector(GetOutputTensorIds(m_Model, subgraphIndex, operatorIndex)); + RegisterOutputSlots(subgraphIndex, operatorIndex, fusedActivationLayer, {outputTensorIndexes[0]}); +} + armnn::IConnectableLayer* TfLiteParser::AddFusedActivationLayer(armnn::IConnectableLayer* prevLayer, unsigned int outputSlot, tflite::ActivationFunctionType activationType) diff --git a/src/armnnTfLiteParser/TfLiteParser.hpp b/src/armnnTfLiteParser/TfLiteParser.hpp index 35f0b64419..76e539acaf 100644 --- a/src/armnnTfLiteParser/TfLiteParser.hpp +++ b/src/armnnTfLiteParser/TfLiteParser.hpp @@ -94,6 +94,7 @@ private: void ParseConcatenation(size_t subgraphIndex, size_t operatorIndex); void ParseConv2D(size_t subgraphIndex, size_t operatorIndex); void ParseDepthwiseConv2D(size_t subgraphIndex, size_t operatorIndex); + void ParseFullyConnected(size_t subgraphIndex, size_t operatorIndex); void ParseMaxPool2D(size_t subgraphIndex, size_t operatorIndex); void ParseRelu(size_t subgraphIndex, size_t operatorIndex); void ParseRelu6(size_t subgraphIndex, size_t operatorIndex); diff --git a/src/armnnTfLiteParser/test/FullyConnected.cpp b/src/armnnTfLiteParser/test/FullyConnected.cpp new file mode 100644 index 0000000000..2853fe96ab --- /dev/null +++ b/src/armnnTfLiteParser/test/FullyConnected.cpp @@ -0,0 +1,154 @@ +// +// Copyright © 2017 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include +#include "ParserFlatbuffersFixture.hpp" +#include "../TfLiteParser.hpp" + +#include +#include + +BOOST_AUTO_TEST_SUITE(TensorflowLiteParser) + +struct FullyConnectedFixture : public ParserFlatbuffersFixture +{ + explicit FullyConnectedFixture(const std::string& inputShape, + const std::string& outputShape, + const std::string& filterShape, + const std::string& filterData, + const std::string biasShape = "", + const std::string biasData = "") + { + std::string inputTensors = "[ 0, 2 ]"; + std::string biasTensor = ""; + std::string biasBuffer = ""; + if (biasShape.size() > 0 && biasData.size() > 0) + { + inputTensors = "[ 0, 2, 3 ]"; + biasTensor = R"( + { + "shape": )" + biasShape + R"( , + "type": "INT32", + "buffer": 3, + "name": "biasTensor", + "quantization": { + "min": [ 0.0 ], + "max": [ 255.0 ], + "scale": [ 1.0 ], + "zero_point": [ 0 ], + } + } )"; + biasBuffer = R"( + { "data": )" + biasData + R"(, }, )"; + } + m_JsonString = R"( + { + "version": 3, + "operator_codes": [ { "builtin_code": "FULLY_CONNECTED" } ], + "subgraphs": [ { + "tensors": [ + { + "shape": )" + inputShape + R"(, + "type": "UINT8", + "buffer": 0, + "name": "inputTensor", + "quantization": { + "min": [ 0.0 ], + "max": [ 255.0 ], + "scale": [ 1.0 ], + "zero_point": [ 0 ], + } + }, + { + "shape": )" + outputShape + R"(, + "type": "UINT8", + "buffer": 1, + "name": "outputTensor", + "quantization": { + "min": [ 0.0 ], + "max": [ 511.0 ], + "scale": [ 2.0 ], + "zero_point": [ 0 ], + } + }, + { + "shape": )" + filterShape + R"(, + "type": "UINT8", + "buffer": 2, + "name": "filterTensor", + "quantization": { + "min": [ 0.0 ], + "max": [ 255.0 ], + "scale": [ 1.0 ], + "zero_point": [ 0 ], + } + }, )" + biasTensor + R"( + ], + "inputs": [ 0 ], + "outputs": [ 1 ], + "operators": [ + { + "opcode_index": 0, + "inputs": )" + inputTensors + R"(, + "outputs": [ 1 ], + "builtin_options_type": "FullyConnectedOptions", + "builtin_options": { + "fused_activation_function": "NONE" + }, + "custom_options_format": "FLEXBUFFERS" + } + ], + } ], + "buffers" : [ + { }, + { }, + { "data": )" + filterData + R"(, }, )" + + biasBuffer + R"( + ] + } + )"; + SetupSingleInputSingleOutput("inputTensor", "outputTensor"); + } +}; + +struct FullyConnectedWithNoBiasFixture : FullyConnectedFixture +{ + FullyConnectedWithNoBiasFixture() + : FullyConnectedFixture("[ 1, 4, 1, 1 ]", // inputShape + "[ 1, 1 ]", // outputShape + "[ 4, 1 ]", // filterShape + "[ 2, 3, 4, 5 ]") // filterData + {} +}; + +BOOST_FIXTURE_TEST_CASE(FullyConnectedWithNoBias, FullyConnectedWithNoBiasFixture) +{ + RunTest<2, uint8_t>( + 0, + { 10, 20, 30, 40 }, + { 400/2 }); +} + +struct FullyConnectedWithBiasFixture : FullyConnectedFixture +{ + FullyConnectedWithBiasFixture() + : FullyConnectedFixture("[ 1, 4, 1, 1 ]", // inputShape + "[ 1, 1 ]", // outputShape + "[ 4, 1 ]", // filterShape + "[ 2, 3, 4, 5 ]", // filterData + "[ 1 ]", // biasShape + "[ 10, 0, 0, 0 ]" ) // biasData + {} +}; + +BOOST_FIXTURE_TEST_CASE(ParseFullyConnectedWithBias, FullyConnectedWithBiasFixture) +{ + RunTest<2, uint8_t>( + 0, + { 10, 20, 30, 40 }, + { (400+10)/2 }); +} + +BOOST_AUTO_TEST_SUITE_END() -- cgit v1.2.1