/// <summary>
/// The example shows
/// - how to load a pretrained model and evaluate several nodes by combining their outputs
/// Note: The example uses the model trained by <CNTK>/Examples/Image/Classification/ResNet/Python/TrainResNet_CIFAR10.py
/// Please see README.md in <CNTK>/Examples/Image/Classification/ResNet about how to train the model.
/// The parameter 'modelFilePath' specifies the path to the model.
/// </summary>
void EvaluateCombinedOutputs(const wchar_t* modelFilePath, const DeviceDescriptor& device)
{
    printf("\n===== Evaluate combined outputs =====\n");

    // Load the model.
    FunctionPtr modelFunc = Function::Load(modelFilePath, device);

    // Get node of interest
    std::wstring intermediateLayerName = L"final_avg_pooling";
    FunctionPtr interLayerPrimitiveFunc = modelFunc->FindByName(intermediateLayerName);

    Variable poolingOutput = interLayerPrimitiveFunc->Output();

    // Create a function which combine outputs from the node "final_avg_polling" and the final layer of the model.
    FunctionPtr evalFunc = Combine( { modelFunc->Output(), poolingOutput });
    Variable inputVar = evalFunc->Arguments()[0];

    // Prepare input data.
    // For evaluating an image, you first need to perform some image preprocessing to make sure that the input image has the correct size and layout
    // that match the model inputs.
    // Please note that the model used by this example expects the CHW image layout.
    // inputVar.Shape[0] is image width, inputVar.Shape[1] is image height, and inputVar.Shape[2] is channels.
    // For simplicity and avoiding external dependencies, we skip the preprocessing step here, and just use some artificially created data as input.
    std::vector<float> inputData(inputVar.Shape().TotalSize());
    for (size_t i = 0; i < inputData.size(); ++i)
    {
        inputData[i] = static_cast<float>(i % 255);
    }

    // Create input value and input data map
    ValuePtr inputVal = Value::CreateBatch(inputVar.Shape(), inputData, device);
    std::unordered_map<Variable, ValuePtr> inputDataMap = { { inputVar, inputVal } };

    // Create output data map. Using null as Value to indicate using system allocated memory.
    // Alternatively, create a Value object and add it to the data map.
    Variable modelOutput = evalFunc->Outputs()[0];
    Variable interLayerOutput = evalFunc->Outputs()[1];

    std::unordered_map<Variable, ValuePtr> outputDataMap = { { modelOutput, nullptr }, { interLayerOutput, nullptr } };

    // Start evaluation on the device
    evalFunc->Evaluate(inputDataMap, outputDataMap, device);

    // Get evaluate result as dense outputs
    for(auto & outputVariableValuePair : outputDataMap)
    {
        auto variable = outputVariableValuePair.first;
        auto value = outputVariableValuePair.second;
        std::vector<std::vector<float>> outputData;
        value->CopyVariableValueTo(variable, outputData);
        PrintOutput<float>(variable.Shape().TotalSize(), outputData);
    }
}
/// <summary>
/// The example shows
/// - how to load a pretrained model and evaluate an intermediate layer of its network.
/// Note: The example uses the model trained by <CNTK>/Examples/Image/Classification/ResNet/Python/TrainResNet_CIFAR10.py
/// Please see README.md in <CNTK>/Examples/Image/Classification/ResNet about how to train the model.
/// The parameter 'modelFilePath' specifies the path to the model.
/// </summary>
void EvaluateIntermediateLayer(const wchar_t* modelFilePath, const DeviceDescriptor& device)
{
    printf("\n===== Evaluate intermediate layer =====\n");

    // Load the model.
    FunctionPtr rootFunc = Function::Load(modelFilePath, device);

    std::wstring intermediateLayerName = L"final_avg_pooling";
    FunctionPtr interLayerPrimitiveFunc = rootFunc->FindByName(intermediateLayerName);

    // The Function returned by FindByName is a primitive function.
    // For evaluation, it is required to create a composite function from the primitive function.
    FunctionPtr modelFunc = AsComposite(interLayerPrimitiveFunc);

    Variable outputVar = modelFunc->Output();
    Variable inputVar = modelFunc->Arguments()[0];

    // Prepare input data.
    // For evaluating an image, you first need to perform some image preprocessing to make sure that the input image has the correct size and layout
    // that match the model inputs.
    // Please note that the model used by this example expects the CHW image layout.
    // inputVar.Shape[0] is image width, inputVar.Shape[1] is image height, and inputVar.Shape[2] is channels.
    // For simplicity and avoiding external dependencies, we skip the preprocessing step here, and just use some artificially created data as input.
    std::vector<float> inputData(inputVar.Shape().TotalSize());
    for (size_t i = 0; i < inputData.size(); ++i)
    {
        inputData[i] = static_cast<float>(i % 255);
    }

    // Create input value and input data map
    ValuePtr inputVal = Value::CreateBatch(inputVar.Shape(), inputData, device);
    std::unordered_map<Variable, ValuePtr> inputDataMap = { { inputVar, inputVal } };

    // Create output data map. Using null as Value to indicate using system allocated memory.
    // Alternatively, create a Value object and add it to the data map.
    std::unordered_map<Variable, ValuePtr> outputDataMap = { { outputVar, nullptr } };

    // Start evaluation on the device
    modelFunc->Evaluate(inputDataMap, outputDataMap, device);

    // Get evaluate result as dense output
    ValuePtr outputVal = outputDataMap[outputVar];
    std::vector<std::vector<float>> outputData;
    outputVal->CopyVariableValueTo(outputVar, outputData);

    PrintOutput<float>(outputVar.Shape().TotalSize(), outputData);
}