Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
Test_Accuracy.cpp 7.58 KiB
/********************************************************************************
 * Copyright (c) 2024 CEA-List
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 ********************************************************************************/

#include <catch2/catch_test_macros.hpp>
#include <cmath>       //
#include <cstddef>     // std::size_t
#include <functional>  // std::multiplies, std::plus
#include <memory>      // std::make_unique
#include <numeric>     // std::accumulate
#include <random>      // std::random_device, std::mt19937,
                       // std::uniform_int_distribution
#include <vector>

#include "aidge/backend/cpu/operator/ArgMaxImpl.hpp"
#include "aidge/backend/cpu/operator/AndImpl.hpp"
#include "aidge/backend/cpu/operator/ReduceSumImpl.hpp"
#include "aidge/data/Tensor.hpp"
#include "aidge/learning/metrics/Accuracy.hpp"
#include "aidge/utils/TensorUtils.hpp"

#if USE_AIDGE_BACKEND_CUDA
#include "aidge/backend/cuda/operator/ArgMaxImpl.hpp"
#include "aidge/backend/cuda/operator/AndImpl.hpp"
#include "aidge/backend/cuda/operator/ReduceSumImpl.hpp"
#endif

namespace Aidge {
TEST_CASE("[metrics] Accuracy", "[metrics][Accuracy]") {

    constexpr std::uint16_t NBTRIALS = 10;

    // set random variables
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<std::size_t> dimsDist(1, 5);
    std::uniform_int_distribution<std::size_t> nbDimsDist(1, 2);
    std::uniform_real_distribution<float> valueDist(0.0f, 1.0f);

    SECTION("CPU") {
        for (std::uint16_t trial = 0; trial < NBTRIALS; ++trial) {
            const std::size_t nb_dims = 2; // For Accuracy test, nb_dims is fixed as 2: NbBatch, NbClass
            std::vector<std::size_t> dims(2);
            std::int32_t classAxis =  1; 

            for (std::size_t i = 0; i < nb_dims; ++i) { dims[i] = dimsDist(gen); }
            
            std::size_t numClasses = dims[1];
            std::size_t numBatchess = dims[0];
            const std::size_t nb_elements = std::accumulate(dims.cbegin(), dims.cend(), std::size_t(1), std::multiplies<std::size_t>());

            // create random predictions
            std::unique_ptr<float[]> pred = std::make_unique<float[]>(nb_elements);
            for (std::size_t i = 0; i < nb_elements; ++i) {
                pred[i] = valueDist(gen);
            }

            // create random targets
            std::unique_ptr<float[]> targ = std::make_unique<float[]>(nb_elements);
            for (std::size_t i = 0; i < nb_elements; ++i) {
                targ[i] = valueDist(gen);
            }
            // Calculate accuracy
            int correct_predictions = 0;
            for (std::size_t batch = 0; batch < dims[0]; ++batch) {
                // Find the index of the maximum value in the current batch (for both pred and targ)
                auto pred_start = pred.get() + batch * numClasses;
                auto targ_start = targ.get() + batch * numClasses;

                std::size_t pred_max_idx = std::distance(pred_start, std::max_element(pred_start, pred_start + numClasses));
                std::size_t targ_max_idx = std::distance(targ_start, std::max_element(targ_start, targ_start + numClasses));

                // If the indices match, it's a correct prediction
                if (pred_max_idx == targ_max_idx) {
                    correct_predictions++;
                }
            }

            // Calculate accuracy as the proportion of correct predictions
            float accuracy = static_cast<float>(correct_predictions);

            // compute the Accuracy using Aidge::metric::Accuracy function
            std::shared_ptr<Tensor> pred_tensor = std::make_shared<Tensor>(dims);
            pred_tensor->setBackend("cpu");
            pred_tensor->getImpl()->setRawPtr(pred.get(), nb_elements);

            std::shared_ptr<Tensor> targ_tensor = std::make_shared<Tensor>(dims);
            targ_tensor->setBackend("cpu");
            targ_tensor->getImpl()->setRawPtr(targ.get(), nb_elements);

            const Tensor res_function = metrics::Accuracy(pred_tensor, targ_tensor, classAxis);

            // compare results
            Tensor res_manual_tensor = Tensor(accuracy);
            REQUIRE(approxEq<float>(res_manual_tensor, res_function));
        }
    }
#if USE_AIDGE_BACKEND_CUDA
    SECTION("CUDA") {
        for (std::uint16_t trial = 0; trial < NBTRIALS; ++trial) {
            const std::size_t nb_dims = 2; // For Accuracy test, nb_dims is fixed as 2: NbBatch, NbClass
            std::vector<std::size_t> dims(2);
            std::int32_t classAxis =  1; 

            for (std::size_t i = 0; i < nb_dims; ++i) { dims[i] = dimsDist(gen); }
            
            std::size_t numClasses = dims[1];
            std::size_t numBatchess = dims[0];
            const std::size_t nb_elements = std::accumulate(dims.cbegin(), dims.cend(), std::size_t(1), std::multiplies<std::size_t>());

            // create random predictions
            std::unique_ptr<float[]> pred = std::make_unique<float[]>(nb_elements);
            for (std::size_t i = 0; i < nb_elements; ++i) {
                pred[i] = valueDist(gen);
            }
            float * d_pred;
            cudaMalloc(&d_pred, nb_elements * sizeof(float));
            cudaMemcpy(d_pred, pred.get(), nb_elements * sizeof(float), cudaMemcpyHostToDevice);

            // create random targets
            std::unique_ptr<float[]> targ = std::make_unique<float[]>(nb_elements);
            for (std::size_t i = 0; i < nb_elements; ++i) {
                targ[i] = valueDist(gen);
            }
            float * d_targ;
            cudaMalloc(&d_targ, nb_elements * sizeof(float));
            cudaMemcpy(d_targ, targ.get(), nb_elements * sizeof(float), cudaMemcpyHostToDevice);


            // Calculate accuracy
            int correct_predictions = 0;
            for (std::size_t batch = 0; batch < dims[0]; ++batch) {
                // Find the index of the maximum value in the current batch (for both pred and targ)
                auto pred_start = pred.get() + batch * numClasses;
                auto targ_start = targ.get() + batch * numClasses;

                std::size_t pred_max_idx = std::distance(pred_start, std::max_element(pred_start, pred_start + numClasses));
                std::size_t targ_max_idx = std::distance(targ_start, std::max_element(targ_start, targ_start + numClasses));

                // If the indices match, it's a correct prediction
                if (pred_max_idx == targ_max_idx) {
                    correct_predictions++;
                }
            }

            // Calculate accuracy as the proportion of correct predictions
            float accuracy = static_cast<float>(correct_predictions);

            // compute the MSE using Aidge::loss::MSE function
            std::shared_ptr<Tensor> pred_tensor = std::make_shared<Tensor>(dims);
            pred_tensor->setBackend("cuda");
            pred_tensor->getImpl()->setRawPtr(d_pred, nb_elements);


            std::shared_ptr<Tensor> targ_tensor = std::make_shared<Tensor>(dims);
            targ_tensor->setBackend("cuda");
            targ_tensor->getImpl()->setRawPtr(d_targ, nb_elements);

            const Tensor res_function = metrics::Accuracy(pred_tensor, targ_tensor, classAxis);

            // compare results
            Tensor res_manual_tensor = Tensor(accuracy);
            REQUIRE(approxEq<float>(res_manual_tensor, res_function));

            cudaFree(d_pred);
            cudaFree(d_targ);
        }
    }
#endif
}
}  // namespace Aidge