Raw File
LinearAlgebraNodes.cpp
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//

#include "LinearAlgebraNodes.h"

using namespace Microsoft::MSR::CNTK;

// -----------------------------------------------------------------------
// EpochAccumulatorNode calculates mean values of all samples used in forward pass.
// -----------------------------------------------------------------------

// TODO: can this be static?
template <class ElemType>
void Microsoft::MSR::CNTK::UpdateRunningAverage(ComputationNode<ElemType>& newInput,
                                                TensorView<ElemType>& runningAverage, size_t& runningCount)
{
    FrameRange fr(newInput.GetMBLayout());
    // Set gaps to zero, since we are reducing in time.
    newInput.MaskMissingValueColumnsToZero(fr);

    size_t newSamplesCount = newInput.GetMBLayout()->GetActualNumSamples();
    size_t totalSamplesCount = runningCount + newSamplesCount;
    if (totalSamplesCount == 0)
        totalSamplesCount = 1;
    ElemType alpha =                   1.0f / totalSamplesCount;
    ElemType beta = (ElemType) runningCount / totalSamplesCount;

    size_t rank = runningAverage.GetShape().GetRank();
    auto input = newInput.ValueTensorFor(rank, fr);

    // runningMean = beta * accumulator + alpha * input.
    runningAverage.DoCopyOf(beta, input, alpha);
    runningCount += newSamplesCount;
}

template void Microsoft::MSR::CNTK::UpdateRunningAverage<float>(ComputationNode<float>& newInput,
                                                                TensorView<float>& runningAverage,
                                                                size_t& runningCount);
template void Microsoft::MSR::CNTK::UpdateRunningAverage<double>(ComputationNode<double>& newInput,
                                                                 TensorView<double>& runningAverage,
                                                                 size_t& runningCount);
template void Microsoft::MSR::CNTK::UpdateRunningAverage<half>(ComputationNode<half>& newInput,
                                                                 TensorView<half>& runningAverage,
                                                                 size_t& runningCount);

template <class ElemType>
EpochAccumulatorNode<ElemType>::EpochAccumulatorNode(DEVICEID_TYPE deviceId, const wstring& name)
    : Base(deviceId, name), m_numSamples(0)
{
    m_accumulator = make_shared<Matrix<ElemType>>(deviceId);
}

template <class ElemType>
EpochAccumulatorNode<ElemType>::EpochAccumulatorNode(const Microsoft::MSR::ScriptableObjects::IConfigRecordPtr configp)
    : EpochAccumulatorNode(configp->Get(L"deviceId"), L"<placeholder>")
{
    AttachInputsFromConfig(configp, this->GetExpectedNumInputs());
}

template <class ElemType>
void EpochAccumulatorNode<ElemType>::BackpropToNonLooping(size_t /*inputIndex*/)
{
    LogicError("%ls operation is used for forward only.", OperationName().c_str());
}

template <class ElemType>
void EpochAccumulatorNode<ElemType>::OnEpochStart()
{
    Reset();
}

template <class ElemType>
void EpochAccumulatorNode<ElemType>::ForwardPropNonLooping()
{
    TensorView<ElemType> accumulator = EnsureAccumlator();
    UpdateRunningAverage(InputRef(0), accumulator, m_numSamples);
    CopyAccumulatorToValue();
}

// Copies internal accumulator to the output.
template <class ElemType>
void EpochAccumulatorNode<ElemType>::CopyAccumulatorToValue()
{
    // Value gets resized in UpdateFunctionValuesSize that is called in BeforeForwardProp. Resize fills matrix with NaN
    // values, so m_value matrix cannot be used as persistent storage between ForwardProp calls.
    Value().SetValue(*m_accumulator);
}

template <class ElemType>
void EpochAccumulatorNode<ElemType>::CopyTo(ComputationNodeBasePtr nodeP, const std::wstring& newName,
                                            const CopyNodeFlags flags) const
{
    Base::CopyTo(nodeP, newName, flags);
    if (flags & CopyNodeFlags::copyNodeValue)
    {
        auto node = nodeP->As<EpochAccumulatorNode<ElemType>>();
        node->m_numSamples = m_numSamples;
        node->m_accumulator->SetValue(*m_accumulator);
    }
}

template <class ElemType>
void EpochAccumulatorNode<ElemType>::Validate(bool isFinalValidationPass)
{
    Base::Validate(isFinalValidationPass);
    SetDims(Input(0)->GetSampleLayout(), HasMBLayout());
}

template <class ElemType>
TensorView<ElemType> EpochAccumulatorNode<ElemType>::EnsureAccumlator()
{
    if (m_accumulator->HasNoElements())
    {
        // Accumulator has not been resized yet, allocate with necessary size.
        const size_t sampleSize = GetSampleLayout().GetNumElements();
        m_accumulator->Resize(sampleSize, 1);
        Reset();
    }
    size_t rank = DetermineElementwiseTensorRank();
    return DataTensorFor(m_accumulator, rank, FrameRange());
}

template <class ElemType>
void EpochAccumulatorNode<ElemType>::Reset()
{
    m_accumulator->SetValue(0);
    m_numSamples = 0;
}

template class EpochAccumulatorNode<float>;
template class EpochAccumulatorNode<double>;
template class EpochAccumulatorNode<half>;
back to top