Skip to content

Commit

Permalink
Integrate gaizna/eval_unittests into master
Browse files Browse the repository at this point in the history
  • Loading branch information
Project Philly committed May 25, 2016
2 parents 1a6dce7 + 596e11a commit b519e5d
Show file tree
Hide file tree
Showing 10 changed files with 689 additions and 10 deletions.
16 changes: 16 additions & 0 deletions CNTK.sln
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FullUtterance", "FullUttera
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Truncated", "Truncated", "{1141DC61-E014-4DEC-9157-F6B1FC055C7A}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EvalTests", "Tests\UnitTests\EvalTests\EvalTests.vcxproj", "{82125DA1-1CD7-45B5-9281-E6AE7C287CB7}"
ProjectSection(ProjectDependencies) = postProject
{60BDB847-D0C4-4FD3-A947-0C15C08BCDB5} = {60BDB847-D0C4-4FD3-A947-0C15C08BCDB5}
{86883653-8A61-4038-81A0-2379FAE4200A} = {86883653-8A61-4038-81A0-2379FAE4200A}
{482999D1-B7E2-466E-9F8D-2119F93EAFD9} = {482999D1-B7E2-466E-9F8D-2119F93EAFD9}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug_CpuOnly|x64 = Debug_CpuOnly|x64
Expand Down Expand Up @@ -1516,6 +1523,14 @@ Global
{578D52A0-3928-4405-A016-F016E8B49031}.Release_CpuOnly|x64.Build.0 = Release_CpuOnly|x64
{578D52A0-3928-4405-A016-F016E8B49031}.Release|x64.ActiveCfg = Release|x64
{578D52A0-3928-4405-A016-F016E8B49031}.Release|x64.Build.0 = Release|x64
{82125DA1-1CD7-45B5-9281-E6AE7C287CB7}.Debug_CpuOnly|x64.ActiveCfg = Debug_CpuOnly|x64
{82125DA1-1CD7-45B5-9281-E6AE7C287CB7}.Debug_CpuOnly|x64.Build.0 = Debug_CpuOnly|x64
{82125DA1-1CD7-45B5-9281-E6AE7C287CB7}.Debug|x64.ActiveCfg = Debug|x64
{82125DA1-1CD7-45B5-9281-E6AE7C287CB7}.Debug|x64.Build.0 = Debug|x64
{82125DA1-1CD7-45B5-9281-E6AE7C287CB7}.Release_CpuOnly|x64.ActiveCfg = Release_CpuOnly|x64
{82125DA1-1CD7-45B5-9281-E6AE7C287CB7}.Release_CpuOnly|x64.Build.0 = Release_CpuOnly|x64
{82125DA1-1CD7-45B5-9281-E6AE7C287CB7}.Release|x64.ActiveCfg = Release|x64
{82125DA1-1CD7-45B5-9281-E6AE7C287CB7}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1695,5 +1710,6 @@ Global
{BA6A65C5-92A2-4040-ADC3-0727A45694F6} = {977ECCB7-598D-4548-B95B-BACA9CC7D98B}
{3BDF52CD-7F3C-42BC-AB78-CF5BBC5F4AB4} = {772A0DB3-4710-4281-8AA9-A9F1F7C543D3}
{1141DC61-E014-4DEC-9157-F6B1FC055C7A} = {772A0DB3-4710-4281-8AA9-A9F1F7C543D3}
{82125DA1-1CD7-45B5-9281-E6AE7C287CB7} = {6F19321A-65E7-4829-B00C-3886CD6C6EDE}
EndGlobalSection
EndGlobal
6 changes: 4 additions & 2 deletions Source/Common/Include/Eval.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,12 @@ class Eval : public IEvaluateModel<ElemType>, protected Plugin
template<typename ElemType>
struct VariableBuffer
{
size_t m_numberOfSamples = 0;

//
// All elements of a sequence, concatenated.
// For dense inputs, the number of samples is given by the the length of
// this vector / product of tensor dimensions. E.g. for a tensor of dimension
// [2,2] and 12 elements in the buffer, the number of samples is 3.
// For sparse inputs, the number of samples is indicated by the m_colIndices field.
//
std::vector<ElemType> m_buffer;

Expand Down
44 changes: 36 additions & 8 deletions Source/EvalDll/CNTKEval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,14 +238,14 @@ VariableLayout CNTKEvalExtended<ElemType>::ToVariableLayout(const ComputationNod
auto matrix = dynamic_pointer_cast<Matrix<ElemType>>(n->ValuePtr());
return VariableLayout
{
/* name */ n->GetName(),
/* type */ sizeof(ElemType) == sizeof(float) ? VariableLayout::Float32 : VariableLayout::Float64,
/* storage */ matrix ? matrix->GetMatrixType() == MatrixType::DENSE ? VariableLayout::Dense :
/* name */ n->GetName(),
/* type */ sizeof(ElemType) == sizeof(float) ? VariableLayout::Float32 : VariableLayout::Float64,
/* storage */ matrix ? matrix->GetMatrixType() == MatrixType::DENSE ? VariableLayout::Dense :
matrix->GetMatrixType() == MatrixType::SPARSE ? VariableLayout::Sparse :
VariableLayout::Undetermined :
VariableLayout::Undetermined,
/* dimension */ n->GetSampleLayout().GetNumElements(),
/* dynamic axis */ wstring(n->GetMBLayout()->GetAxisName())
/* dimension */ n->GetSampleLayout().GetNumElements(),
/* dynamic axis */ wstring(n->GetMBLayout() ? n->GetMBLayout()->GetAxisName() : L"*")
};
}

Expand Down Expand Up @@ -304,11 +304,39 @@ void CNTKEvalExtended<ElemType>::ForwardPass(const Variables<ElemType>& inputs,
for (auto& input : m_inputMatrices)
{
VariableBuffer<ElemType> buffer = inputs[i];
int numRows = input.second.sampleLayout.GetNumElements();
int numCols = buffer.m_numberOfSamples;
shared_ptr<Matrix<ElemType>> matrix = dynamic_pointer_cast<Matrix<ElemType>>(input.second.matrix);
auto type = matrix->GetMatrixType();
auto type = matrix->GetMatrixType();
int numRows = input.second.sampleLayout.GetNumElements();

if (type == MatrixType::DENSE)
{
if (buffer.m_buffer.size() % numRows != 0)
{
RuntimeError("Input %ls: Expected input data to be a multiple of %ld, but it is %ld", m_inputNodes[i]->GetName().c_str(), numRows, buffer.m_buffer.size());
}
if (buffer.m_buffer.size() == 0)
{
RuntimeError("Input %ls: Expected at least one element.", m_inputNodes[i]->GetName().c_str());
}
}
else if (type == MatrixType::SPARSE)
{
if (buffer.m_colIndices.size() < 2)
{
RuntimeError("Input %ls: Expected at least one element.", m_inputNodes[i]->GetName().c_str());
}
if (buffer.m_colIndices[0] != 0)
{
RuntimeError("Input %ls: First element of column indices must be 0", m_inputNodes[i]->GetName().c_str());
}
if (buffer.m_colIndices[buffer.m_colIndices.size()-1] != buffer.m_indices.size())
{
RuntimeError("Input %ls: Last element of column indices must be equal to the size of indices (%ld), but was %d", m_inputNodes[i]->GetName().c_str(), buffer.m_indices.size(), buffer.m_colIndices[buffer.m_colIndices.size() - 1]);
}
}

int numCols = type == MatrixType::DENSE ? buffer.m_buffer.size() / numRows : buffer.m_colIndices.size() - 1;
assert(numCols >= 1);
input.second.pMBLayout->Init(1, numCols);
input.second.pMBLayout->AddSequence(0, 0, 0, numCols);

Expand Down
269 changes: 269 additions & 0 deletions Tests/UnitTests/EvalTests/EvalExtendedTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//

#include "stdafx.h"
#include "EvalTestHelper.h"

using namespace Microsoft::MSR::CNTK;

namespace Microsoft { namespace MSR { namespace CNTK { namespace Test {

// Used for retrieving the model appropriate for the element type (float / double)
template<typename ElemType>
using GetEvalProc = void(*)(IEvaluateModelExtended<ElemType>**);

typedef std::pair<std::wstring, std::vector<float>*> Variable;
typedef std::map<std::wstring, std::vector<float>*> Variables;

BOOST_FIXTURE_TEST_SUITE(EvalTestSuite, EvalFixture)

IEvaluateModelExtended<float>* SetupNetworkAndGetLayouts(std::string modelDefinition, VariableSchema& inputLayouts, VariableSchema& outputLayouts)
{
// Load the eval library
auto hModule = LoadLibrary(L"evaldll.dll");
if (hModule == nullptr)
{
auto err = GetLastError();
throw std::exception((boost::format("Cannot load evaldll.dll: 0x%08lx") % err).str().c_str());
}

// Get the factory method to the evaluation engine
std::string func = "GetEvalExtendedF";
auto procAddress = GetProcAddress(hModule, func.c_str());
auto getEvalProc = (GetEvalProc<float>)procAddress;

// Native model evaluation instance
IEvaluateModelExtended<float> *eval;
getEvalProc(&eval);

try
{
eval->CreateNetwork(modelDefinition);
}
catch (std::exception& ex)
{
fprintf(stderr, ex.what());
throw;
}
fflush(stderr);

// Get the model's layers dimensions
outputLayouts = eval->GetOutputSchema();

for (auto vl : outputLayouts)
{
fprintf(stderr, "Output dimension: %d\n", vl.m_numElements);
fprintf(stderr, "Output name: %ls\n", vl.m_name.c_str());
}

eval->StartForwardEvaluation({outputLayouts[0].m_name});
inputLayouts = eval->GetInputSchema();

return eval;
}

BOOST_AUTO_TEST_CASE(EvalConstantPlusTest)
{
// Setup model for adding two constants (1 + 2)
std::string modelDefinition =
"deviceId = -1 \n"
"precision = \"float\" \n"
"traceLevel = 1 \n"
"run=NDLNetworkBuilder \n"
"NDLNetworkBuilder=[ \n"
"v1 = Constant(1) \n"
"v2 = Constant(2) \n"
"ol = Plus(v1, v2, tag=\"output\") \n"
"FeatureNodes = (v1) \n"
"] \n";

VariableSchema inputLayouts;
VariableSchema outputLayouts;
IEvaluateModelExtended<float> *eval;
eval = SetupNetworkAndGetLayouts(modelDefinition, inputLayouts, outputLayouts);

// Allocate the output values layer
std::vector<VariableBuffer<float>> outputBuffer(1);

// Allocate the input values layer (empty)

std::vector<VariableBuffer<float>> inputBuffer;

// We can call the evaluate method and get back the results...
eval->ForwardPass(inputBuffer, outputBuffer);

std::vector<float> expected{ 3 /* 1 + 2 */ };
auto buf = outputBuffer[0].m_buffer;
BOOST_CHECK_EQUAL_COLLECTIONS(buf.begin(), buf.end(), expected.begin(), expected.end());

eval->Destroy();
}

BOOST_AUTO_TEST_CASE(EvalScalarTimesTest)
{
std::string modelDefinition =
"deviceId = -1 \n"
"precision = \"float\" \n"
"traceLevel = 1 \n"
"run=NDLNetworkBuilder \n"
"NDLNetworkBuilder=[ \n"
"i1 = Input(1) \n"
"o1 = Times(Constant(3), i1, tag=\"output\") \n"
"FeatureNodes = (i1) \n"
"] \n";

VariableSchema inputLayouts;
VariableSchema outputLayouts;
IEvaluateModelExtended<float> *eval;
eval = SetupNetworkAndGetLayouts(modelDefinition, inputLayouts, outputLayouts);

// Allocate the output values layer
std::vector<VariableBuffer<float>> outputBuffer(1);

// Allocate the input values layer
std::vector<VariableBuffer<float>> inputBuffer(1);
inputBuffer[0].m_buffer = { 2 };

// We can call the evaluate method and get back the results...
eval->ForwardPass(inputBuffer, outputBuffer);

std::vector<float> expected{ 6 };
auto buf = outputBuffer[0].m_buffer;
BOOST_CHECK_EQUAL_COLLECTIONS(buf.begin(), buf.end(), expected.begin(), expected.end());

eval->Destroy();
}

BOOST_AUTO_TEST_CASE(EvalScalarTimesDualOutputTest)
{
std::string modelDefinition =
"deviceId = -1 \n"
"precision = \"float\" \n"
"traceLevel = 1 \n"
"run=NDLNetworkBuilder \n"
"NDLNetworkBuilder=[ \n"
"i1 = Input(1) \n"
"i2 = Input(1) \n"
"o1 = Times(Constant(3), i1, tag=\"output\") \n"
"o2 = Times(Constant(5), i1, tag=\"output\") \n"
"FeatureNodes = (i1) \n"
"] \n";

VariableSchema inputLayouts;
VariableSchema outputLayouts;
IEvaluateModelExtended<float> *eval;
eval = SetupNetworkAndGetLayouts(modelDefinition, inputLayouts, outputLayouts);

// Allocate the output values layer
std::vector<VariableBuffer<float>> outputBuffer(1);

// Allocate the input values layer
std::vector<VariableBuffer<float>> inputBuffer(1);
inputBuffer[0].m_buffer = { 2 };

// We can call the evaluate method and get back the results...
// TODO: Indicate to ForwardPass that we want output o2 only
eval->ForwardPass(inputBuffer, outputBuffer);

std::vector<float> expected{ 6 };
auto buf = outputBuffer[0].m_buffer;
BOOST_CHECK_EQUAL_COLLECTIONS(buf.begin(), buf.end(), expected.begin(), expected.end());

eval->Destroy();
}

BOOST_AUTO_TEST_CASE(EvalDenseTimesTest)
{
std::string modelDefinition =
"deviceId = -1 \n"
"precision = \"float\" \n"
"traceLevel = 1 \n"
"run=NDLNetworkBuilder \n"
"NDLNetworkBuilder=[ \n"
"i1 = Input(4) \n"
"o1 = Times(Constant(2, rows=1, cols=4), i1, tag=\"output\") \n"
"FeatureNodes = (i1) \n"
"] \n";

VariableSchema inputLayouts;
VariableSchema outputLayouts;
IEvaluateModelExtended<float> *eval;
eval = SetupNetworkAndGetLayouts(modelDefinition, inputLayouts, outputLayouts);

// Allocate the output values layer
std::vector<VariableBuffer<float>> outputBuffer(1);

// Number of inputs must adhere to the schema
std::vector<VariableBuffer<float>> inputBuffer1(0);
BOOST_REQUIRE_THROW(eval->ForwardPass(inputBuffer1, outputBuffer), std::exception); // Not enough inputs

// Number of elements in the input must adhere to the schema
std::vector<VariableBuffer<float>> inputBuffer(1);
inputBuffer[0].m_buffer = { 1, 2, 3 };
BOOST_REQUIRE_THROW(eval->ForwardPass(inputBuffer, outputBuffer), std::exception); // Not enough elements in the sample

// Output values and shape must be correct.
inputBuffer[0].m_buffer = { 1, 2, 3, 4 };
eval->ForwardPass(inputBuffer, outputBuffer);

std::vector<float> expected{ 20 };
auto buf = outputBuffer[0].m_buffer;
BOOST_CHECK_EQUAL_COLLECTIONS(buf.begin(), buf.end(), expected.begin(), expected.end());

eval->Destroy();
}

BOOST_AUTO_TEST_CASE(EvalSparseTimesTest)
{
std::string modelDefinition =
"deviceId = -1 \n"
"precision = \"float\" \n"
"traceLevel = 1 \n"
"run=NDLNetworkBuilder \n"
"NDLNetworkBuilder=[ \n"
"i1 = SparseInput(3) \n"
"o1 = Times(Constant(2, rows=1, cols=3), i1, tag=\"output\") \n"
"FeatureNodes = (i1) \n"
"] \n";

VariableSchema inputLayouts;
VariableSchema outputLayouts;
IEvaluateModelExtended<float> *eval;
eval = SetupNetworkAndGetLayouts(modelDefinition, inputLayouts, outputLayouts);

// Allocate the output values layer
std::vector<VariableBuffer<float>> outputBuffer(1);

// Allocate the input values layer
std::vector<VariableBuffer<float>> inputBuffer(1);
inputBuffer[0].m_buffer = {1, 2, 3, 5, 6};
inputBuffer[0].m_indices = {0, 2, 2, 1, 2};

inputBuffer[0].m_colIndices = {};
BOOST_REQUIRE_THROW(eval->ForwardPass(inputBuffer, outputBuffer), std::exception); // Empty input

inputBuffer[0].m_colIndices = { 0 };
BOOST_REQUIRE_THROW(eval->ForwardPass(inputBuffer, outputBuffer), std::exception); // Empty input

inputBuffer[0].m_colIndices = { 1, 0 };
BOOST_REQUIRE_THROW(eval->ForwardPass(inputBuffer, outputBuffer), std::exception); // Illegal: First entry must be 0

inputBuffer[0].m_colIndices = { 0, 2, 2, 4 };
BOOST_REQUIRE_THROW(eval->ForwardPass(inputBuffer, outputBuffer), std::exception); // Illegal: Last entry must be indices.size()

inputBuffer[0].m_colIndices = { 0, 2, 2, 5 };

// We can call the evaluate method and get back the results...
eval->ForwardPass(inputBuffer, outputBuffer);

// [2,2,2] * [1,2,3]^T etc.
std::vector<float> expected{ 6, 0, 28 };
auto buf = outputBuffer[0].m_buffer;
BOOST_CHECK_EQUAL_COLLECTIONS(buf.begin(), buf.end(), expected.begin(), expected.end());
eval->Destroy();
}

BOOST_AUTO_TEST_SUITE_END()
}}}}
Loading

0 comments on commit b519e5d

Please sign in to comment.