703 строки
36 KiB
C++
703 строки
36 KiB
C++
//
|
|
// Copyright (c) Microsoft. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
|
//
|
|
// Note: Despite its name, this file is really about parsing NDL into an actual ComputationNetwork.
|
|
//
|
|
|
|
#define _CRT_SECURE_NO_WARNINGS // "secure" CRT not available on all platforms --add this at the top of all CPP files that give "function or variable may be unsafe" warnings
|
|
|
|
#include "Basics.h"
|
|
#include "NDLNetworkBuilder.h"
|
|
#include "LinearAlgebraNodes.h"
|
|
#include "RecurrentNodes.h"
|
|
#include "ConvolutionalNodes.h"
|
|
#include "RNNNodes.h"
|
|
#include "NonlinearityNodes.h"
|
|
#include "ReshapingNodes.h"
|
|
#include "InputAndParamNodes.h"
|
|
#include "TensorShape.h"
|
|
|
|
namespace Microsoft { namespace MSR { namespace CNTK {
|
|
|
|
using namespace std;
|
|
|
|
template <class ElemType>
|
|
void NDLNodeEvaluatorImpl<ElemType>::Evaluate(NDLNode<ElemType>* node, const wstring& baseName, const NDLPass pass)
|
|
{
|
|
ComputationNetworkBuilder<ElemType> builder(*m_net);
|
|
|
|
// constants don't need to be evaluated, they just translate into numbers...
|
|
if (node->GetType() == ndlTypeConstant || node->GetType() == ndlTypeArray)
|
|
return;
|
|
|
|
// setup the node parameters, where they start in the parameter list, and how many there are
|
|
// this is needed for the ndlPassResolve step to hookup all the inputs
|
|
int nodeParamStart = 0;
|
|
int nodeParamCount = 0;
|
|
|
|
// get the parameters
|
|
std::vector<NDLNode<ElemType>*> parameter = node->GetParameters();
|
|
|
|
// get the name for the symbol to be used by CN nodes
|
|
std::wstring name = msra::strfun::utf16(node->GetName());
|
|
if (!baseName.empty())
|
|
{
|
|
name = baseName + L"." + name;
|
|
}
|
|
|
|
std::wstring cnNodeType = msra::strfun::utf16(node->GetValue());
|
|
|
|
ComputationNodePtr nodePtr;
|
|
|
|
// get the node pointer for the node, should be stored in the EvalValue;
|
|
if (pass > ndlPassInitial)
|
|
{
|
|
nodePtr = ComputationNode<ElemType>::FromVoidPtr(node->GetEvalValue());
|
|
if (!nodePtr)
|
|
{
|
|
nodePtr = dynamic_pointer_cast<ComputationNode<ElemType>>(m_net->GetNodeFromName(name));
|
|
node->SetEvalValue(nodePtr.get());
|
|
}
|
|
}
|
|
|
|
if (OperationNameOf(InputValue) == cnNodeType || OperationNameOf(SparseInputValue) == cnNodeType)
|
|
{
|
|
bool isSparse = (OperationNameOf(SparseInputValue) == cnNodeType);
|
|
if (parameter.size() < 1)
|
|
RuntimeError("%ls should have 1 or more parameters (tensor dimensions, e.g. [vecdim] or [rows, cols]).", cnNodeType.c_str());
|
|
|
|
if (pass == ndlPassInitial)
|
|
{
|
|
// evaluate only scalar parameters
|
|
vector<void*> params = EvaluateParameters(node, baseName, 0, parameter.size(), pass);
|
|
size_t i = 0;
|
|
auto tensorShape = ProcessTensorShapeParameters(node, params, i, /*isImage=*/false, cnNodeType);
|
|
|
|
wstring dynamicAxis = node->GetOptionalParameter("dynamicAxis", "");
|
|
// TODO: Map dynamicAxis from name to node at this point, where that node is memoized inside NDL.
|
|
// first look for this node already existing in the network
|
|
// BUGBUG: How does this set the dimensions then?
|
|
if (m_net->NodeNameExists(name))
|
|
nodePtr = dynamic_pointer_cast<ComputationNode<ElemType>>(m_net->GetNodeFromName(name));
|
|
else if (isSparse)
|
|
nodePtr = builder.CreateSparseInputNode(name, tensorShape, dynamicAxis);
|
|
else
|
|
nodePtr = builder.CreateInputNode(name, tensorShape, dynamicAxis);
|
|
}
|
|
}
|
|
else if (cnNodeType == L"ImageInput" || cnNodeType == L"SparseImageInput")
|
|
{
|
|
bool isSparse = (cnNodeType == L"SparseImageInput");
|
|
if (parameter.size() < 3 || parameter.size() > 4) // we allow 4 for legacy (numImages, was ignored)
|
|
RuntimeError("%ls should have 3 parameters[imageWidth, imageHeight, imageChannels].", cnNodeType.c_str());
|
|
|
|
if (pass == ndlPassInitial)
|
|
{
|
|
// evaluate only scalar parameters
|
|
vector<void*> params = EvaluateParameters(node, baseName, 0, parameter.size(), pass);
|
|
size_t imageWidth = ((NDLNode<ElemType>*) params[0])->GetScalar();
|
|
size_t imageHeight = ((NDLNode<ElemType>*) params[1])->GetScalar();
|
|
size_t imageChannels = ((NDLNode<ElemType>*) params[2])->GetScalar();
|
|
ImageLayoutKind imageLayoutKind = ImageLayoutKindFrom(node->GetOptionalParameter("imageLayout", "HWC"));
|
|
wstring dynamicAxis = node->GetOptionalParameter("dynamicAxis", "");
|
|
|
|
if (isSparse)
|
|
nodePtr = builder.CreateSparseInputNode(name, ImageDimensions::AsTensorShape(imageWidth, imageHeight, imageChannels, imageLayoutKind), dynamicAxis);
|
|
else
|
|
nodePtr = builder.CreateInputNode(name, ImageDimensions::AsTensorShape(imageWidth, imageHeight, imageChannels, imageLayoutKind), dynamicAxis);
|
|
}
|
|
}
|
|
else if (OperationNameOf(LearnableParameter) == cnNodeType || cnNodeType == L"ImageParameter")
|
|
{
|
|
bool isImage = (cnNodeType == L"ImageParameter");
|
|
if (!isImage)
|
|
{
|
|
if (parameter.size() < 1)
|
|
RuntimeError("%ls should have 1 or more parameters (tensor dimensions, e.g. [vecdim] or [rows, cols]) plus other optional parameters (learningRateMultiplier=[1|0|float], init=[uniform|gaussian|fixedvalue|fromFile|heNormal|bilinear], initValueScale=[1|float], value=[0|float]).", cnNodeType.c_str());
|
|
}
|
|
else
|
|
{
|
|
if (parameter.size() < 3)
|
|
RuntimeError("%ls should have 3 or more parameters [imageWidth, imageHeight, imageChannels] plus other optional parameters (learningRateMultiplier=[1|0|float], init=[uniform|gaussian|fixedvalue|fromFile|heNormal|bilinear], initValueScale=[1|float], value=[0|float]).", cnNodeType.c_str());
|
|
}
|
|
|
|
if (pass == ndlPassInitial)
|
|
{
|
|
// evaluate only scalar parameters
|
|
vector<void*> params = EvaluateParameters(node, baseName, 0, parameter.size(), pass);
|
|
size_t i = 0;
|
|
auto tensorShape = ProcessTensorShapeParameters(node, params, i, isImage, cnNodeType);
|
|
|
|
// for backward compatibility needsGradient is now subsumed by learningRateMultiplier
|
|
bool gradientUpdateNeeded = node->GetOptionalParameter("needGradient", "true")
|
|
&& node->GetOptionalParameter("needsGradient", "true")
|
|
&& node->GetOptionalParameter("computeGradient", "true");
|
|
float learningRateMultiplier = node->GetOptionalParameter("learningRateMultiplier", "1");
|
|
if (!gradientUpdateNeeded) // if user has specified needsGradient flag to false
|
|
learningRateMultiplier = 0.0;
|
|
|
|
nodePtr = builder.CreateLearnableParameter(name, tensorShape);
|
|
nodePtr->SetLearningRateMultiplier(learningRateMultiplier);
|
|
}
|
|
else if (pass == ndlPassFinal)
|
|
{
|
|
static int randomSeed = 1;
|
|
wstring initString = node->GetOptionalParameter("init", "uniform");
|
|
ElemType initValueScale = node->GetOptionalParameter("initValueScale", "1");
|
|
ElemType value = node->GetOptionalParameter("value", "0");
|
|
bool initOnCPUOnly = node->GetOptionalParameter("initOnCPUOnly", "false");
|
|
int forcedRandomSeed = node->GetOptionalParameter("randomSeed", "-1" /*disabled*/);
|
|
|
|
if (EqualCI(initString, L"fixedValue"))
|
|
m_net->InitLearnableParameters(nodePtr, L"fixedValue", value);
|
|
else if (EqualCI(initString, L"uniform"))
|
|
m_net->InitLearnableParameters(nodePtr, L"uniform", initValueScale, forcedRandomSeed < 0 ? randomSeed++ : (unsigned long)forcedRandomSeed, initOnCPUOnly);
|
|
else if (EqualCI(initString, L"gaussian"))
|
|
m_net->InitLearnableParameters(nodePtr, L"gaussian", initValueScale, forcedRandomSeed < 0 ? randomSeed++ : (unsigned long)forcedRandomSeed, initOnCPUOnly);
|
|
else if (EqualCI(initString, L"bilinear"))
|
|
{
|
|
const size_t kernelWidth = node->GetOptionalParameter("kernelWidth", "0");
|
|
const size_t kernelHeight = node->GetOptionalParameter("kernelHeight", "0");
|
|
assert(kernelWidth > 0 && kernelHeight > 0);
|
|
m_net->InitLearnableParametersWithBilinearFill<ElemType>(nodePtr, kernelWidth, kernelHeight);
|
|
}
|
|
else if (EqualCI(initString, L"fromFile"))
|
|
{
|
|
std::string initFromFilePath = node->GetOptionalParameter("initFromFilePath", "");
|
|
if (initFromFilePath == "")
|
|
RuntimeError("initFromFilePath must be set when using \"fromFile\" initialization method");
|
|
if (initFromFilePath[0] == '\"' && initFromFilePath[initFromFilePath.size() - 1] == '\"')
|
|
// remove the opening and closing double quotes
|
|
initFromFilePath = initFromFilePath.substr(1, initFromFilePath.size() - 2);
|
|
if (!fexists(initFromFilePath))
|
|
RuntimeError("File pointed to by initFromFilePath does not exist: %s", initFromFilePath.c_str());
|
|
dynamic_pointer_cast<LearnableParameter<ElemType>>(nodePtr)->InitFromFile(msra::strfun::utf16(initFromFilePath));
|
|
}
|
|
else if (EqualCI(initString, L"heNormal"))
|
|
m_net->InitLearnableParameters(nodePtr, L"heNormal", initValueScale, forcedRandomSeed < 0 ? randomSeed++ : (unsigned long)forcedRandomSeed, initOnCPUOnly);
|
|
else
|
|
RuntimeError("'init' must be one of the values of [ uniform | gaussian | fixedValue | fromFile | heNormal | bilinear]");
|
|
}
|
|
}
|
|
else if (cnNodeType == L"Constant")
|
|
{
|
|
if (parameter.size() != 1)
|
|
RuntimeError("Constant should have 1 fixed parameter [val] and two optional parameters [rows=[1|yourvalue], cols=[1|yourvalue]].");
|
|
|
|
if (pass == ndlPassInitial)
|
|
{
|
|
size_t rows = node->GetOptionalParameter("rows", "1");
|
|
size_t cols = node->GetOptionalParameter("cols", "1");
|
|
|
|
nodePtr = builder.CreateLearnableParameter(name, rows, cols);
|
|
nodePtr->SetLearningRateMultiplier(0);
|
|
}
|
|
else if (pass == ndlPassFinal || nodePtr->Value().GetNumElements() != 0)
|
|
{
|
|
ElemType val = parameter[0]->GetScalar();
|
|
m_net->InitLearnableParameters(nodePtr, L"fixedValue", val);
|
|
}
|
|
}
|
|
else if (cnNodeType == L"RowSlice") // Note: This now maps onto SliceNode which specifies the end differently.
|
|
{
|
|
if (parameter.size() != 3)
|
|
RuntimeError("RowSlice should have three parameters. Usage: RowSlice(startRowIndex, numRows, origNodeName.");
|
|
|
|
nodeParamCount = 1;
|
|
nodeParamStart = 2;
|
|
|
|
if (pass == ndlPassInitial)
|
|
{
|
|
// evaluate only scalar parameters
|
|
vector<void*> params = EvaluateParameters(node, baseName, 0, parameter.size(), pass);
|
|
size_t start_index = ((NDLNode<ElemType>*) params[0])->GetScalar();
|
|
size_t num_rows = ((NDLNode<ElemType>*) params[1])->GetScalar();
|
|
|
|
nodePtr = builder.RowSlice(NULL, start_index, num_rows, name);
|
|
}
|
|
}
|
|
else if (cnNodeType == OperationNameOf(RowRepeatNode))
|
|
{
|
|
if (parameter.size() != 2)
|
|
RuntimeError("RowRepeat should have two parameters. Usage: RowRepeat(origNodeName, numRepeats).");
|
|
|
|
nodeParamCount = 1;
|
|
nodeParamStart = 0;
|
|
|
|
if (pass == ndlPassInitial)
|
|
{
|
|
// evaluate only scalar parameters
|
|
vector<void*> params = EvaluateParameters(node, baseName, 0, parameter.size(), pass);
|
|
size_t num_repeat = ((NDLNode<ElemType>*) params[1])->GetScalar();
|
|
|
|
nodePtr = builder.RowRepeat(NULL, num_repeat, name);
|
|
}
|
|
}
|
|
else if (cnNodeType == OperationNameOf(DiagonalNode))
|
|
{
|
|
if (parameter.size() != 1)
|
|
RuntimeError("Diagonal should have one parameter. Usage: Diagonal(origNodeName).");
|
|
|
|
nodeParamCount = 1;
|
|
nodeParamStart = 0;
|
|
|
|
if (pass == ndlPassInitial)
|
|
{
|
|
// evaluate only scalar parameters
|
|
vector<void*> params = EvaluateParameters(node, baseName, 0, parameter.size(), pass);
|
|
|
|
nodePtr = builder.Diagonal(NULL, name);
|
|
}
|
|
}
|
|
else if (cnNodeType == L"Reshape" /*OperationNameOf(ReshapeNode)*/)
|
|
{
|
|
if (parameter.size() != 2)
|
|
RuntimeError("Reshape should have two parameters. Usage: Reshape(origNodeName, numRows, [imageWidth=], [imageHeight=], [imageChannels=].");
|
|
|
|
nodeParamCount = 1;
|
|
nodeParamStart = 0;
|
|
|
|
if (pass == ndlPassInitial)
|
|
{
|
|
// evaluate only scalar parameters
|
|
vector<void*> params = EvaluateParameters(node, baseName, 0, parameter.size(), pass);
|
|
size_t num_rows = ((NDLNode<ElemType>*) params[1])->GetScalar();
|
|
size_t img_width = node->GetOptionalParameter("imageWidth", "0");
|
|
size_t img_height = node->GetOptionalParameter("imageHeight", "0");
|
|
size_t img_channels = node->GetOptionalParameter("imageChannels", "0");
|
|
ImageLayoutKind imageLayoutKind = ImageLayoutKindFrom(node->GetOptionalParameter("imageLayout", "HWC"));
|
|
|
|
nodePtr = builder.LegacyReshape(NULL, num_rows, ImageDimensions::AsTensorShape(img_width, img_height, img_channels, imageLayoutKind), name);
|
|
}
|
|
}
|
|
else if (cnNodeType == OperationNameOf(ReconcileDynamicAxisNode))
|
|
{
|
|
nodeParamCount = 2;
|
|
nodeParamStart = 0;
|
|
|
|
if (pass == ndlPassInitial)
|
|
{
|
|
nodePtr = builder.ReconcileDynamicAxis(NULL, NULL, name);
|
|
}
|
|
}
|
|
else if (cnNodeType == OperationNameOf(PastValueNode) ||
|
|
cnNodeType == OperationNameOf(FutureValueNode))
|
|
{
|
|
if (parameter.size() < 2 || parameter.size() > 3) // we allow 3 for legacy (cols parameter which is now unused)
|
|
RuntimeError("PastValue or FutureValue should have two to three fixed parameters. Usage: PastValue(rows, input, [timeStep=1, defaultHiddenActivity=0.1]).");
|
|
// TODO: allow a tensor descriptor. Or allow 0 (inference). Maybe already supported--check this.
|
|
|
|
nodeParamCount = 1; // number of inputs
|
|
nodeParamStart = parameter.size() > 2 ? 2 : 1; // index of input
|
|
|
|
if (pass == ndlPassInitial)
|
|
{
|
|
// evaluate only scalar parameters
|
|
vector<void*> params = EvaluateParameters(node, baseName, 0, parameter.size(), pass);
|
|
size_t rows = ((NDLNode<ElemType>*) params[0])->GetScalar();
|
|
// if we have three parameters the second is columns
|
|
// ignore legacy size_t cols = parameter.size() > 2 ? ((NDLNode<ElemType>*)params[1])->GetScalar() : 1;
|
|
|
|
float defaultHiddenActivity = node->GetOptionalParameter("defaultHiddenActivity", "0.1"); // TODO: parameter should be called 'defaultHiddenActivation'
|
|
|
|
// for backward compatibility we check 'timeStep' first
|
|
size_t timeStep = node->GetOptionalParameter("timeStep", "1");
|
|
if (timeStep == 1)
|
|
timeStep = node->GetOptionalParameter("delayTime", "1");
|
|
|
|
if (cnNodeType == OperationNameOf(PastValueNode))
|
|
nodePtr = builder.PastValue(NULL, defaultHiddenActivity, rows, timeStep, name);
|
|
else
|
|
nodePtr = builder.FutureValue(NULL, defaultHiddenActivity, rows, timeStep, name);
|
|
}
|
|
}
|
|
else if (cnNodeType == OperationNameOf(ConvolutionNode) ||
|
|
cnNodeType == OperationNameOf(PoolingNode) ||
|
|
cnNodeType == OperationNameOf(MaxUnpoolingNode))
|
|
{
|
|
if (parameter.size() != 2 && parameter.size() != 3 && parameter.size() != 7)
|
|
{
|
|
if (cnNodeType == OperationNameOf(ConvolutionNode))
|
|
{
|
|
RuntimeError("%ls: unexpected parameter count. %ls supports 2 modes: \n"
|
|
"1. 2D convolution which takes 7 fixed parameters [weightNodeName, inputValueNodeName, kernelWidth, kernelHeight, outputChannels, horizontalSubsample, verticalSubsample] \n"
|
|
"and two optional parameters [zeroPadding = [false|yourvalue], maxTempMemSizeInSamples = [0|yourvalue], imageLayout = \"HWC\"|\"cudnn\"]. \n"
|
|
"2. ND convolution which takes 3 fixed parameters [weightNodeName, inputValueNodeName, kernelShape] and \n"
|
|
"10 optional parameters [mapCount = [0|yourvalue], stride = [1|yourvalue], sharing = [true|yourvalue], autoPadding = [true|yourvalue], lowerPad = [0|yourvalue], upperPad = [0|yourvalue], bool transpose = [false|yourvalue], maxTempMemSizeInSamples = [0|yourvalue], imageLayout = \"cudnn\"|\"HWC\"]. \n"
|
|
"For ND convolution, parameters kernelShape, mapCount, stride, sharing, autoPadding, lowerPad, upperPad can be arrays, e.g. kernelShape={5, 5, 3}",
|
|
cnNodeType.c_str(), cnNodeType.c_str());
|
|
}
|
|
else if (cnNodeType == OperationNameOf(PoolingNode))
|
|
{
|
|
RuntimeError("%ls: unexpected parameter count. %ls 3 fixed parameters [inputValueNodeName, poolKind, kernelShape] and \n"
|
|
"5 optional parameters stride = [1|yourvalue], autoPadding = [true|yourvalue], lowerPad = [0|yourvalue], upperPad = [0|yourvalue], imageLayout = \"cudnn\"]. \n"
|
|
"Parameters kernelShape, stride, autoPadding, lowerPad, upperPad can be arrays, e.g. kernelShape={5, 5, 3}",
|
|
cnNodeType.c_str(), cnNodeType.c_str());
|
|
}
|
|
else if (cnNodeType == OperationNameOf(MaxUnpoolingNode))
|
|
{
|
|
RuntimeError("%ls: unexpected parameter count. %ls 3 fixed parameters [inputValueNodeName, mask, kernelShape] and \n"
|
|
"5 optional parameters stride = [1|yourvalue], autoPadding = [true|yourvalue], lowerPad = [0|yourvalue], upperPad = [0|yourvalue], imageLayout = \"cudnn\"]. \n"
|
|
"Parameters kernelShape, stride, autoPadding, lowerPad, upperPad can be arrays, e.g. kernelShape={5, 5, 3}",
|
|
cnNodeType.c_str(), cnNodeType.c_str());
|
|
}
|
|
}
|
|
|
|
// setup the parameter position of children so we can hook them up later
|
|
nodeParamStart = 0;
|
|
nodeParamCount = (cnNodeType == OperationNameOf(ConvolutionNode) || cnNodeType == OperationNameOf(MaxUnpoolingNode))
|
|
? 2
|
|
: 1;
|
|
|
|
if (pass == ndlPassInitial)
|
|
{
|
|
if (parameter.size() == 2 || parameter.size() == 3)
|
|
{
|
|
auto reqParams = node->GetParameters(false);
|
|
auto optParams = node->GetParameters(true);
|
|
auto paramGetter = [reqParams, node](size_t index) -> TensorShape
|
|
{
|
|
assert(index < reqParams.size());
|
|
auto parm = reqParams[index];
|
|
if (parm->GetType() != ndlTypeArray)
|
|
return TensorShape((size_t)parm->GetScalar());
|
|
auto parms = node->GetParentScript()->ParseVariable(parm->GetValue(), false)->GetParameters();
|
|
vector<size_t> dims(parms.size());
|
|
for (size_t i = 0; i < dims.size(); i++)
|
|
dims[i] = parms[i]->GetValue();
|
|
return TensorShape(dims);
|
|
};
|
|
auto paramResolver = [optParams, node](const char* name, size_t defaultVal) -> TensorShape
|
|
{
|
|
auto res = std::find_if(begin(optParams), end(optParams), [name](const NDLNode<ElemType>* n) { return EqualCI(n->GetName(), name); });
|
|
if (res == end(optParams))
|
|
return TensorShape(defaultVal);
|
|
auto parm = node->GetParentScript()->ParseVariable((*res)->GetValue(), false);
|
|
if (parm->GetType() == ndlTypeConstant)
|
|
return TensorShape((size_t)parm->GetValue());
|
|
auto parms = parm->GetParameters();
|
|
vector<size_t> dims(parms.size());
|
|
for (size_t i = 0; i < dims.size(); i++)
|
|
dims[i] = parms[i]->GetValue();
|
|
return TensorShape(dims);
|
|
};
|
|
auto boolParamResolver = [&optParams, node](const char* name, bool defaultVal) -> vector<bool>
|
|
{
|
|
auto res = std::find_if(begin(optParams), end(optParams), [name](const NDLNode<ElemType>* n) { return EqualCI(n->GetName(), name); });
|
|
if (res == end(optParams))
|
|
return vector<bool>{defaultVal};
|
|
auto parm = node->GetParentScript()->ParseVariable((*res)->GetValue(), false);
|
|
if (parm == nullptr)
|
|
return vector<bool>{(*res)->GetValue()};
|
|
if (parm->GetType() != ndlTypeArray)
|
|
return vector<bool>{parm->GetValue()};
|
|
auto parms = parm->GetParameters();
|
|
vector<bool> dims(parms.size());
|
|
for (size_t i = 0; i < dims.size(); i++)
|
|
dims[i] = parms[i]->GetValue();
|
|
return dims;
|
|
};
|
|
|
|
auto kernelShape = paramGetter(reqParams.size() - 1);
|
|
auto mapCount = paramResolver("mapCount", 0);
|
|
auto stride = paramResolver("stride", 1);
|
|
auto sharing = boolParamResolver("sharing", true);
|
|
auto autoPad = boolParamResolver("autoPadding", true);
|
|
auto lowerPad = paramResolver("lowerPad", 0);
|
|
auto upperPad = paramResolver("upperPad", 0);
|
|
ImageLayoutKind imageLayout = ImageLayoutKindFrom(node->GetOptionalParameter("imageLayout", "CHW"));
|
|
size_t maxTempMemSizeInSamples = node->GetOptionalParameter("maxTempMemSizeInSamples", "0");
|
|
|
|
if (cnNodeType == OperationNameOf(MaxUnpoolingNode))
|
|
nodePtr = builder.MaxUnpooling(NULL, NULL, kernelShape, stride, autoPad, lowerPad, upperPad, imageLayout, name);
|
|
else if (cnNodeType == OperationNameOf(PoolingNode))
|
|
{
|
|
auto parm = node->GetParentScript()->ParseVariable(reqParams[1]->GetValue(), false);
|
|
auto pool = PoolKindFrom(wstring(parm->GetValue()));
|
|
nodePtr = builder.Pooling(NULL, pool, kernelShape, stride, autoPad, lowerPad, upperPad, imageLayout, name);
|
|
}
|
|
else
|
|
{
|
|
bool transpose = node->GetOptionalParameter("transpose", "false");
|
|
nodePtr = builder.Convolution(NULL, NULL, kernelShape, mapCount, stride, sharing,
|
|
autoPad, lowerPad, upperPad, transpose, imageLayout, maxTempMemSizeInSamples, name);
|
|
}
|
|
|
|
}
|
|
else if (parameter.size() == 7)
|
|
{
|
|
int id = 2; // skip weightNode and inputValueNode
|
|
|
|
// evaluate only scalar parameters
|
|
vector<void*> params = EvaluateParameters(node, baseName, id, parameter.size() - id, pass);
|
|
id = 0; // reset counter because the params array starts at zero
|
|
size_t kernelWidth = ((NDLNode<ElemType>*) params[id++])->GetScalar();
|
|
size_t kernelHeight = ((NDLNode<ElemType>*) params[id++])->GetScalar();
|
|
size_t outputChannels = ((NDLNode<ElemType>*) params[id++])->GetScalar();
|
|
size_t horizontalSubsample = ((NDLNode<ElemType>*) params[id++])->GetScalar();
|
|
size_t verticalSubsample = ((NDLNode<ElemType>*) params[id++])->GetScalar();
|
|
assert(id == 5);
|
|
|
|
// optional
|
|
ImageLayoutKind imageLayoutKind = ImageLayoutKindFrom(node->GetOptionalParameter("imageLayout", "HWC"));
|
|
bool zeroPadding = node->GetOptionalParameter("zeroPadding", "false");
|
|
size_t maxTempMemSizeInSamples = node->GetOptionalParameter("maxTempMemSizeInSamples", "0");
|
|
|
|
nodePtr = builder.Convolution(NULL, NULL, kernelWidth, kernelHeight, outputChannels,
|
|
horizontalSubsample, verticalSubsample, imageLayoutKind, zeroPadding,
|
|
maxTempMemSizeInSamples, name);
|
|
}
|
|
else
|
|
assert(false);
|
|
}
|
|
}
|
|
else if (cnNodeType == OperationNameOf(MaxPoolingNode))
|
|
{
|
|
if (parameter.size() != 5)
|
|
RuntimeError("%ls should have 5 parameters[inputValueNodeName, windowWidth, windowHeight, horizontalSubsample, verticalSubsample, imageLayout = \"HWC\"|\"cudnn\"].", cnNodeType.c_str());
|
|
|
|
// setup the parameter position of children so we can hook them up later
|
|
nodeParamCount = 1;
|
|
nodeParamStart = 0;
|
|
|
|
if (pass == ndlPassInitial)
|
|
{
|
|
int id = 1; // skip inputValueNode
|
|
|
|
// evaluate only scalar parameters
|
|
vector<void*> params = EvaluateParameters(node, baseName, id, parameter.size() - id, pass);
|
|
id = 0; // reset counter because the params array starts at zero
|
|
size_t windowWidth = ((NDLNode<ElemType>*) params[id++])->GetScalar();
|
|
size_t windowHeight = ((NDLNode<ElemType>*) params[id++])->GetScalar();
|
|
size_t horizontalSubsample = ((NDLNode<ElemType>*) params[id++])->GetScalar();
|
|
size_t verticalSubsample = ((NDLNode<ElemType>*) params[id++])->GetScalar();
|
|
assert(id == 4);
|
|
|
|
ImageLayoutKind imageLayoutKind = ImageLayoutKindFrom(node->GetOptionalParameter("imageLayout", "HWC"));
|
|
|
|
nodePtr = builder.MaxPooling(NULL, /*inputWidth,inputHeight, channels,*/ windowWidth, windowHeight,
|
|
horizontalSubsample, verticalSubsample, imageLayoutKind, name);
|
|
}
|
|
}
|
|
else if (cnNodeType == OperationNameOf(AveragePoolingNode))
|
|
{
|
|
if (parameter.size() != 5)
|
|
RuntimeError("%ls should have 5 parameters[inputValueNodeName, windowWidth, windowHeight, horizontalSubsample, verticalSubsample, imageLayout = \"HWC\"|\"cudnn\"].", cnNodeType.c_str());
|
|
|
|
// setup the parameter position of children so we can hook them up later
|
|
nodeParamCount = 1;
|
|
nodeParamStart = 0;
|
|
|
|
if (pass == ndlPassInitial)
|
|
{
|
|
int id = 1; // skip inputValueNode
|
|
|
|
// evaluate only scalar parameters
|
|
vector<void*> params = EvaluateParameters(node, baseName, id, parameter.size() - id, pass);
|
|
id = 0; // reset counter because the params array starts at zero
|
|
size_t windowWidth = ((NDLNode<ElemType>*) params[id++])->GetScalar();
|
|
size_t windowHeight = ((NDLNode<ElemType>*) params[id++])->GetScalar();
|
|
size_t horizontalSubsample = ((NDLNode<ElemType>*) params[id++])->GetScalar();
|
|
size_t verticalSubsample = ((NDLNode<ElemType>*) params[id++])->GetScalar();
|
|
assert(id == 4);
|
|
|
|
ImageLayoutKind imageLayoutKind = ImageLayoutKindFrom(node->GetOptionalParameter("imageLayout", "HWC"));
|
|
|
|
nodePtr = builder.AveragePooling(NULL, /*inputWidth,inputHeight, channels,*/ windowWidth, windowHeight,
|
|
horizontalSubsample, verticalSubsample, imageLayoutKind, name);
|
|
}
|
|
}
|
|
else if (cnNodeType == OperationNameOf(BatchNormalizationNode))
|
|
{
|
|
if (parameter.size() != 5)
|
|
RuntimeError("%ls should have 5 fixed parameters[inputValueNodeName, scale, bias, runMean, runVariance].", cnNodeType.c_str());
|
|
|
|
// setup the parameter position of children so we can hook them up later
|
|
nodeParamCount = 5;
|
|
nodeParamStart = 0;
|
|
|
|
if (pass == ndlPassInitial)
|
|
{
|
|
int id = 5; // skip inputValueNode, scale and bias, runMean, runVariance.
|
|
// evaluate only scalar parameters
|
|
vector<void*> params = EvaluateParameters(node, baseName, id, parameter.size() - id, pass);
|
|
|
|
// Optional parameters
|
|
bool spatial = node->GetOptionalParameter("spatial", "false");
|
|
double normTimeConst = node->GetOptionalParameter("normalizationTimeConstant", "0");
|
|
double blendTimeConst = node->GetOptionalParameter("blendTimeConstant", "0");
|
|
double epsilon = node->GetOptionalParameter("epsilon", "0.00001");
|
|
std::wstring bnEngineS = node->GetOptionalParameter("engine", "cntk");
|
|
bool useCntkEngine;
|
|
if (EqualCI(bnEngineS, L"cntk"))
|
|
useCntkEngine = true;
|
|
else if (EqualCI(bnEngineS, L"cudnn"))
|
|
useCntkEngine = false;
|
|
else
|
|
InvalidArgument("Unsupported batch normalization engine, choose either \"cntk\"(default) or \"cudnn\".");
|
|
ImageLayoutKind imageLayoutKind = ImageLayoutKindFrom(node->GetOptionalParameter("imageLayout", "CHW"));
|
|
|
|
nodePtr = builder.BatchNormalization(nullptr, nullptr, nullptr, nullptr, nullptr, spatial, normTimeConst, blendTimeConst, epsilon, useCntkEngine, imageLayoutKind, name);
|
|
}
|
|
}
|
|
else if (cnNodeType == OperationNameOf(CropNode))
|
|
{
|
|
// We expect 2 or 4 inputs.
|
|
if (parameter.size() != 2 && parameter.size() != 4)
|
|
{
|
|
RuntimeError("%ls accepts inputs: [input1, input2, offsetX, offsetY] or \
|
|
[input1, input2] or \
|
|
[input1, input2, eqNode1, eqNode2].", cnNodeType.c_str());
|
|
}
|
|
|
|
if (pass == ndlPassInitial)
|
|
{
|
|
// In initial phase we just need to create node.
|
|
if (parameter.size() == 4)
|
|
{
|
|
// Here we need to determine if 3rd and 4th parameters are offsets or equivalence nodes.
|
|
vector<void*> params = EvaluateParameters(node, baseName, 0, parameter.size(), pass);
|
|
// TODO: Is there a better way to discriminate?
|
|
if (((NDLNode<ElemType>*) params[2])->GetType() == NDLType::ndlTypeConstant)
|
|
{
|
|
// We have offsets given, take offsets from evaluated parameters.
|
|
size_t offsetX = ((NDLNode<ElemType>*) params[2])->GetScalar();
|
|
size_t offsetY = ((NDLNode<ElemType>*) params[3])->GetScalar();
|
|
|
|
// Create crop node with offsets but without inputs (will be attached later in resolve phase).
|
|
nodePtr = builder.Crop(nullptr, nullptr, offsetX, offsetY, name);
|
|
}
|
|
else
|
|
{
|
|
// We have 4 node inputs (2 crop inputs and 2 equivalence node inputs).
|
|
nodePtr = builder.Crop(nullptr, nullptr, nullptr, nullptr, name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Just two inputs, must be node inputs which will be attached in the resolve phase below.
|
|
nodePtr = builder.Crop(nullptr, nullptr, name);
|
|
}
|
|
// Done processing in this phase.
|
|
nodeParamStart = 0;
|
|
nodeParamCount = 0;
|
|
}
|
|
else
|
|
{
|
|
// In non-initial phase we just process node inputs below, here we just set inputs of interest.
|
|
nodeParamStart = 0;
|
|
nodeParamCount = nodePtr->GetNumInputs();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// setup the variables for node parameter processing
|
|
nodeParamCount = parameter.size(); // all parameters are nodes in standard nodes
|
|
nodeParamStart = 0;
|
|
|
|
if (pass == ndlPassInitial)
|
|
{
|
|
nodePtr = builder.CreateComputationNode(node->GetValue(), name);
|
|
}
|
|
}
|
|
|
|
switch (pass)
|
|
{
|
|
case ndlPassInitial:
|
|
node->SetEvalValue(nodePtr.get());
|
|
// evaluate parameters
|
|
EvaluateParameters(node, baseName, nodeParamStart, nodeParamCount, pass);
|
|
break;
|
|
case ndlPassResolve:
|
|
{
|
|
std::vector<void*> inputs = EvaluateParameters(node, baseName, nodeParamStart, nodeParamCount, pass);
|
|
|
|
if (cnNodeType == OperationNameOf(RowStackNode)) // support variable length inputs
|
|
{
|
|
std::vector<ComputationNodeBasePtr> inputNodes;
|
|
inputNodes.resize(inputs.size());
|
|
for (int i = 0; i < inputs.size(); i++)
|
|
inputNodes[i] = ComputationNode<ElemType>::FromVoidPtr(inputs[i]);
|
|
|
|
nodePtr->AttachInputs(inputNodes);
|
|
}
|
|
else
|
|
{
|
|
#if 1
|
|
vector<ComputationNodeBasePtr> inputNodes;
|
|
for (let& in : inputs)
|
|
inputNodes.push_back(ComputationNode<ElemType>::FromVoidPtr(in));
|
|
nodePtr->AttachInputs(inputNodes);
|
|
#else // TODO: delete this
|
|
switch (inputs.size())
|
|
{
|
|
// TODO: just use a vector attach
|
|
case 1:
|
|
nodePtr->AttachInputs(ComputationNode<ElemType>::FromVoidPtr(inputs[0]));
|
|
break;
|
|
case 2:
|
|
nodePtr->AttachInputs(ComputationNode<ElemType>::FromVoidPtr(inputs[0]), ComputationNode<ElemType>::FromVoidPtr(inputs[1]));
|
|
break;
|
|
case 3:
|
|
nodePtr->AttachInputs(ComputationNode<ElemType>::FromVoidPtr(inputs[0]), ComputationNode<ElemType>::FromVoidPtr(inputs[1]), ComputationNode<ElemType>::FromVoidPtr(inputs[2]));
|
|
break;
|
|
case 4:
|
|
nodePtr->AttachInputs(ComputationNode<ElemType>::FromVoidPtr(inputs[0]), ComputationNode<ElemType>::FromVoidPtr(inputs[1]), ComputationNode<ElemType>::FromVoidPtr(inputs[2]), ComputationNode<ElemType>::FromVoidPtr(inputs[3]));
|
|
break;
|
|
case 5:
|
|
nodePtr->AttachInputs(ComputationNode<ElemType>::FromVoidPtr(inputs[0]), ComputationNode<ElemType>::FromVoidPtr(inputs[1]), ComputationNode<ElemType>::FromVoidPtr(inputs[2]), ComputationNode<ElemType>::FromVoidPtr(inputs[3]), ComputationNode<ElemType>::FromVoidPtr(inputs[4]));
|
|
break;
|
|
case 6:
|
|
nodePtr->AttachInputs(ComputationNode<ElemType>::FromVoidPtr(inputs[0]), ComputationNode<ElemType>::FromVoidPtr(inputs[1]), ComputationNode<ElemType>::FromVoidPtr(inputs[2]), ComputationNode<ElemType>::FromVoidPtr(inputs[3]), ComputationNode<ElemType>::FromVoidPtr(inputs[4]), ComputationNode<ElemType>::FromVoidPtr(inputs[5]));
|
|
break;
|
|
default:
|
|
if (nodeParamCount > 0)
|
|
RuntimeError("Invalid number of parameters name = '%s' call = '%s'\n", node->GetName().c_str(), node->GetValue().c_str());
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
// process common optional parameters (currently only "tag");
|
|
ProcessOptionalParameters(node);
|
|
break;
|
|
}
|
|
case ndlPassFinal:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ProcessTensorShapeParameters - assume positional parameters starting from position i are tensor dimensions--parse those.
|
|
// Is isImage then must be a 3D tensor, which is interpreted as (W,H,C), and optional parameter 'imageLayout' says how.
|
|
template <class ElemType>
|
|
TensorShape NDLNodeEvaluatorImpl<ElemType>::ProcessTensorShapeParameters(const NDLNode<ElemType>* node, const vector<void*>& params, size_t& i, bool isImage, const wstring& cnNodeType /*for error messages only*/)
|
|
{
|
|
// gather dims
|
|
vector<size_t> dims;
|
|
dims.push_back(((NDLNode<ElemType>*) params[i])->GetScalar()); // first is mandatory
|
|
for (i++; i < params.size(); i++)
|
|
dims.push_back(((NDLNode<ElemType>*) params[i])->GetScalar());
|
|
|
|
// if image then interpret as W, H, C with layout according to optional imageLayout parameter
|
|
// If more than 3 parameters are given, then we assume that this is for a Times operation and interpret the last 3 dimensions according to imageLayout.
|
|
if (isImage)
|
|
{
|
|
if (dims.size() < 3)
|
|
RuntimeError("%ls should have 3 or more parameters [width, height, numChannels].", cnNodeType.c_str());
|
|
ImageLayoutKind imageLayoutKind = ImageLayoutKindFrom(node->GetOptionalParameter("imageLayout", "HWC"));
|
|
size_t k0 = dims.size() - 3; // last 3 need to be arranged
|
|
SmallVector<size_t> imageDims = ImageDimensions::AsTensorShape(dims[k0 + 0], dims[k0 + 1], dims[k0 + 2], imageLayoutKind).GetDims();
|
|
for (size_t k = 0; k < 3; k++)
|
|
dims[k0 + k] = imageDims[k];
|
|
}
|
|
|
|
// turn into tensor
|
|
return TensorShape(dims);
|
|
}
|
|
|
|
template class NDLBuilderImpl<float>;
|
|
template class NDLBuilderImpl<double>;
|
|
|
|
}}}
|