手把手使用 C++ 构建多层神经网络(深度神经网络)

· · 科技·工程

第一步:实现矩阵和相关运算(也可以使用 Eigen 库)

以下实现较为轻量,且可读性高、满足神经网络基本操作,定义了一些示例函数。后期可使用 CPU 指令集或 GPU、TPU 加速或使用 NPU 加速替代。

#include <iostream>
#include <vector>

using VECTOR = std::vector<double>;

class MATRIX : public std::vector<VECTOR> {
public:
    MATRIX() { }
    MATRIX(int n, int m) { resize(n, VECTOR(m, 0)); }

    inline std::size_t rows() const noexcept {
        return size();
    }

    inline std::size_t cols() const noexcept {
        if (empty()) return 0;
        return (*this)[0].size();
    }

    MATRIX operator + (double other) const noexcept {
        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] += other;
            }
        }

        return res;
    }

    MATRIX operator + (MATRIX other) const {
        if (rows() != other.rows() || cols() != other.cols()) {
            throw std::runtime_error("(MATRIX) operator + Error: rows() != other.rows() || cols() != other.cols()");
        }

        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] += other[i][j];
            }
        }

        return res;
    }

    MATRIX operator - (double other) const noexcept {
        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] -= other;
            }
        }

        return res;
    }

    MATRIX operator - (MATRIX other) const {
        if (rows() != other.rows() || cols() != other.cols()) {
            throw std::runtime_error("(MATRIX) operator - Error: rows() != other.rows() || cols() != other.cols()");
        }

        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] -= other[i][j];
            }
        }

        return res;
    }

    MATRIX operator * (double other) const noexcept {
        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] *= other;
            }
        }

        return res;
    }

    MATRIX operator * (MATRIX other) const {
        if (cols() != other.rows()) {
            throw std::runtime_error("(MATRIX) operator * Error: cols() != other.rows()");
        }

        MATRIX res(rows(), other.cols());

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                for (int k = 0; k < cols(); ++k) {
                    res[i][j] += (*this)[i][k] * other[k][j];
                }
            }
        }

        return res;
    }

    MATRIX operator / (double other) const noexcept {
        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] /= other;
            }
        }

        return res;
    }

    static MATRIX Hadamard_Static(MATRIX a, MATRIX b) {
        if (a.rows() != b.rows() || a.cols() != b.cols()) {
            throw std::runtime_error("(MATRIX) Hadamard Error: a.rows() != b.rows() || a.cols() != b.cols()");
        }

        MATRIX res = a;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] *= b[i][j];
            }
        }

        return res;
    }

    static void EXAMPLE_ADD1() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "Value b: ";
        double b;
        std::cin >> b;
        std::cout << std::endl;

        auto multi = a + b;

        std::cout << "(a + b)'s size: " << multi.rows() << " " << multi.cols() << std::endl;

        for (int i = 0; i < multi.rows(); ++i) {
            for (int j = 0; j < multi.cols(); ++j) {
                std::cout << multi[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_ADD2() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX b's size: ";
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX b(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "MATRIX b:" << std::endl;
        for (int i = 0; i < b.rows(); ++i) {
            for (int j = 0; j < b.cols(); ++j) {
                std::cin >> b[i][j];
            }
        }

        auto add = a + b;

        std::cout << "(a + b)'s size: " << add.rows() << " " << add.cols() << std::endl;

        for (int i = 0; i < add.rows(); ++i) {
            for (int j = 0; j < add.cols(); ++j) {
                std::cout << add[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_SUB1() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "Value b: ";
        double b;
        std::cin >> b;
        std::cout << std::endl;

        auto multi = a + b;

        std::cout << "(a - b)'s size: " << multi.rows() << " " << multi.cols() << std::endl;

        for (int i = 0; i < multi.rows(); ++i) {
            for (int j = 0; j < multi.cols(); ++j) {
                std::cout << multi[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_SUB2() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX b's size: ";
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX b(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "MATRIX b:" << std::endl;
        for (int i = 0; i < b.rows(); ++i) {
            for (int j = 0; j < b.cols(); ++j) {
                std::cin >> b[i][j];
            }
        }

        auto add = a - b;

        std::cout << "(a - b)'s size: " << add.rows() << " " << add.cols() << std::endl;

        for (int i = 0; i < add.rows(); ++i) {
            for (int j = 0; j < add.cols(); ++j) {
                std::cout << add[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_MULTI1() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "Value b: ";
        double b;
        std::cin >> b;
        std::cout << std::endl;

        auto multi = a * b;

        std::cout << "(a * b)'s size: " << multi.rows() << " " << multi.cols() << std::endl;

        for (int i = 0; i < multi.rows(); ++i) {
            for (int j = 0; j < multi.cols(); ++j) {
                std::cout << multi[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_MULTI2() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX b's size: ";
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX b(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "MATRIX b:" << std::endl;
        for (int i = 0; i < b.rows(); ++i) {
            for (int j = 0; j < b.cols(); ++j) {
                std::cin >> b[i][j];
            }
        }

        auto multi = a * b;

        std::cout << "(ab)'s size: " << multi.rows() << " " << multi.cols() << std::endl;

        for (int i = 0; i < multi.rows(); ++i) {
            for (int j = 0; j < multi.cols(); ++j) {
                std::cout << multi[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_DEVIDE1() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "Value b: ";
        double b;
        std::cin >> b;
        std::cout << std::endl;

        auto multi = a / b;

        std::cout << "(a / b)'s size: " << multi.rows() << " " << multi.cols() << std::endl;

        for (int i = 0; i < multi.rows(); ++i) {
            for (int j = 0; j < multi.cols(); ++j) {
                std::cout << multi[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_HADAMARD1() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX b's size: ";
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX b(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "MATRIX b:" << std::endl;
        for (int i = 0; i < b.rows(); ++i) {
            for (int j = 0; j < b.cols(); ++j) {
                std::cin >> b[i][j];
            }
        }

        auto add = Hadamard_Static(a, b);

        std::cout << "Hadamard(a, b)'s size: " << add.rows() << " " << add.cols() << std::endl;

        for (int i = 0; i < add.rows(); ++i) {
            for (int j = 0; j < add.cols(); ++j) {
                std::cout << add[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }
};

constexpr auto Hadamard = MATRIX::Hadamard_Static;

int main() {
    MATRIX::EXAMPLE_HADAMARD1();
    return 0;
}

第二步:激活函数的定义

以下实现补充了常见、流行的激活函数(2024 年)的定义,并允许用户自行定义激活函数。自定义方法:std::pair<std::function<double(double)>, std::function<double(double)>>

#include <iostream>
#include <vector>
#include <cmath>
#include <functional>
#include <utility>

using VECTOR = std::vector<double>;

class MATRIX : public std::vector<VECTOR> {
public:
    MATRIX() { }
    MATRIX(int n, int m) { resize(n, VECTOR(m, 0)); }

    inline std::size_t rows() const noexcept {
        return size();
    }

    inline std::size_t cols() const noexcept {
        if (empty()) return 0;
        return (*this)[0].size();
    }

    MATRIX operator + (double other) const noexcept {
        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] += other;
            }
        }

        return res;
    }

    MATRIX operator + (MATRIX other) const {
        if (rows() != other.rows() || cols() != other.cols()) {
            throw std::runtime_error("(MATRIX) operator + Error: rows() != other.rows() || cols() != other.cols()");
        }

        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] += other[i][j];
            }
        }

        return res;
    }

    MATRIX operator - (double other) const noexcept {
        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] -= other;
            }
        }

        return res;
    }

    MATRIX operator - (MATRIX other) const {
        if (rows() != other.rows() || cols() != other.cols()) {
            throw std::runtime_error("(MATRIX) operator - Error: rows() != other.rows() || cols() != other.cols()");
        }

        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] -= other[i][j];
            }
        }

        return res;
    }

    MATRIX operator * (double other) const noexcept {
        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] *= other;
            }
        }

        return res;
    }

    MATRIX operator * (MATRIX other) const {
        if (cols() != other.rows()) {
            throw std::runtime_error("(MATRIX) operator * Error: cols() != other.rows()");
        }

        MATRIX res(rows(), other.cols());

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                for (int k = 0; k < cols(); ++k) {
                    res[i][j] += (*this)[i][k] * other[k][j];
                }
            }
        }

        return res;
    }

    MATRIX operator / (double other) const noexcept {
        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] /= other;
            }
        }

        return res;
    }

    static MATRIX Hadamard_Static(MATRIX a, MATRIX b) {
        if (a.rows() != b.rows() || a.cols() != b.cols()) {
            throw std::runtime_error("(MATRIX) Hadamard Error: a.rows() != b.rows() || a.cols() != b.cols()");
        }

        MATRIX res = a;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] *= b[i][j];
            }
        }

        return res;
    }

    static void EXAMPLE_ADD1() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "Value b: ";
        double b;
        std::cin >> b;
        std::cout << std::endl;

        auto multi = a + b;

        std::cout << "(a + b)'s size: " << multi.rows() << " " << multi.cols() << std::endl;

        for (int i = 0; i < multi.rows(); ++i) {
            for (int j = 0; j < multi.cols(); ++j) {
                std::cout << multi[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_ADD2() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX b's size: ";
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX b(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "MATRIX b:" << std::endl;
        for (int i = 0; i < b.rows(); ++i) {
            for (int j = 0; j < b.cols(); ++j) {
                std::cin >> b[i][j];
            }
        }

        auto add = a + b;

        std::cout << "(a + b)'s size: " << add.rows() << " " << add.cols() << std::endl;

        for (int i = 0; i < add.rows(); ++i) {
            for (int j = 0; j < add.cols(); ++j) {
                std::cout << add[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_SUB1() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "Value b: ";
        double b;
        std::cin >> b;
        std::cout << std::endl;

        auto multi = a + b;

        std::cout << "(a - b)'s size: " << multi.rows() << " " << multi.cols() << std::endl;

        for (int i = 0; i < multi.rows(); ++i) {
            for (int j = 0; j < multi.cols(); ++j) {
                std::cout << multi[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_SUB2() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX b's size: ";
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX b(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "MATRIX b:" << std::endl;
        for (int i = 0; i < b.rows(); ++i) {
            for (int j = 0; j < b.cols(); ++j) {
                std::cin >> b[i][j];
            }
        }

        auto add = a - b;

        std::cout << "(a - b)'s size: " << add.rows() << " " << add.cols() << std::endl;

        for (int i = 0; i < add.rows(); ++i) {
            for (int j = 0; j < add.cols(); ++j) {
                std::cout << add[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_MULTI1() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "Value b: ";
        double b;
        std::cin >> b;
        std::cout << std::endl;

        auto multi = a * b;

        std::cout << "(a * b)'s size: " << multi.rows() << " " << multi.cols() << std::endl;

        for (int i = 0; i < multi.rows(); ++i) {
            for (int j = 0; j < multi.cols(); ++j) {
                std::cout << multi[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_MULTI2() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX b's size: ";
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX b(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "MATRIX b:" << std::endl;
        for (int i = 0; i < b.rows(); ++i) {
            for (int j = 0; j < b.cols(); ++j) {
                std::cin >> b[i][j];
            }
        }

        auto multi = a * b;

        std::cout << "(ab)'s size: " << multi.rows() << " " << multi.cols() << std::endl;

        for (int i = 0; i < multi.rows(); ++i) {
            for (int j = 0; j < multi.cols(); ++j) {
                std::cout << multi[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_DEVIDE1() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "Value b: ";
        double b;
        std::cin >> b;
        std::cout << std::endl;

        auto multi = a / b;

        std::cout << "(a / b)'s size: " << multi.rows() << " " << multi.cols() << std::endl;

        for (int i = 0; i < multi.rows(); ++i) {
            for (int j = 0; j < multi.cols(); ++j) {
                std::cout << multi[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_HADAMARD1() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX b's size: ";
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX b(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "MATRIX b:" << std::endl;
        for (int i = 0; i < b.rows(); ++i) {
            for (int j = 0; j < b.cols(); ++j) {
                std::cin >> b[i][j];
            }
        }

        auto add = Hadamard_Static(a, b);

        std::cout << "Hadamard(a, b)'s size: " << add.rows() << " " << add.cols() << std::endl;

        for (int i = 0; i < add.rows(); ++i) {
            for (int j = 0; j < add.cols(); ++j) {
                std::cout << add[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }
};

constexpr auto Hadamard = MATRIX::Hadamard_Static;

using ActivationFunction = std::pair<std::function<double(double)>, std::function<double(double)>>;

ActivationFunction Sigmoid = {(std::function<double(double)>)[](double x) -> double {
    return 1 / (1 + exp(-x));
}, (std::function<double(double)>)[](double x) -> double {
    double SigmoidValue = 1 / (1 + exp(-x));
    return SigmoidValue * (1 - SigmoidValue);
}};

ActivationFunction ReLU = {(std::function<double(double)>)[](double x) -> double {
    return std::max((double)0.0, x);
}, (std::function<double(double)>)[](double x) -> double {
    return x > 0 ? (double)1.0 : (double)0.0;
}};

int main() {
    double x;
    std::cin >> x;

    std::cout << "Sigmoid(" << x << ") = " << Sigmoid.first(x) << std::endl;
    std::cout << "Sigmoid'(" << x << ") = " << Sigmoid.second(x) << std::endl;

    std::cout << "ReLU(" << x << ") = " << ReLU.first(x) << std::endl;
    std::cout << "ReLU'(" << x << ") = " << ReLU.second(x) << std::endl;

    return 0;
}

第三步:神经网络类的定义

#include <iostream>
#include <vector>
#include <cmath>
#include <functional>
#include <utility>
#include <random>
#include <ctime>

using VECTOR = std::vector<double>;

class MATRIX : public std::vector<VECTOR> {
public:
    MATRIX() { }
    MATRIX(int n, int m) { resize(n, VECTOR(m, 0)); }

    inline std::size_t rows() const noexcept {
        return size();
    }

    inline std::size_t cols() const noexcept {
        if (empty()) return 0;
        return (*this)[0].size();
    }

    MATRIX operator + (double other) const noexcept {
        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] += other;
            }
        }

        return res;
    }

    MATRIX operator + (MATRIX other) const {
        if (rows() != other.rows() || cols() != other.cols()) {
            throw std::runtime_error("(MATRIX) operator + Error: rows() != other.rows() || cols() != other.cols()");
        }

        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] += other[i][j];
            }
        }

        return res;
    }

    MATRIX operator - (double other) const noexcept {
        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] -= other;
            }
        }

        return res;
    }

    MATRIX operator - (MATRIX other) const {
        if (rows() != other.rows() || cols() != other.cols()) {
            throw std::runtime_error("(MATRIX) operator - Error: rows() != other.rows() || cols() != other.cols()");
        }

        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] -= other[i][j];
            }
        }

        return res;
    }

    MATRIX operator * (double other) const noexcept {
        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] *= other;
            }
        }

        return res;
    }

    MATRIX operator * (MATRIX other) const {
        if (cols() != other.rows()) {
            throw std::runtime_error("(MATRIX) operator * Error: cols() != other.rows()");
        }

        MATRIX res(rows(), other.cols());

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                for (int k = 0; k < cols(); ++k) {
                    res[i][j] += (*this)[i][k] * other[k][j];
                }
            }
        }

        return res;
    }

    MATRIX operator / (double other) const noexcept {
        MATRIX res = *this;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] /= other;
            }
        }

        return res;
    }

    static MATRIX Hadamard_Static(MATRIX a, MATRIX b) {
        if (a.rows() != b.rows() || a.cols() != b.cols()) {
            throw std::runtime_error("(MATRIX) Hadamard Error: a.rows() != b.rows() || a.cols() != b.cols()");
        }

        MATRIX res = a;

        for (int i = 0; i < res.rows(); ++i) {
            for (int j = 0; j < res.cols(); ++j) {
                res[i][j] *= b[i][j];
            }
        }

        return res;
    }

    static void EXAMPLE_ADD1() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "Value b: ";
        double b;
        std::cin >> b;
        std::cout << std::endl;

        auto multi = a + b;

        std::cout << "(a + b)'s size: " << multi.rows() << " " << multi.cols() << std::endl;

        for (int i = 0; i < multi.rows(); ++i) {
            for (int j = 0; j < multi.cols(); ++j) {
                std::cout << multi[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_ADD2() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX b's size: ";
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX b(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "MATRIX b:" << std::endl;
        for (int i = 0; i < b.rows(); ++i) {
            for (int j = 0; j < b.cols(); ++j) {
                std::cin >> b[i][j];
            }
        }

        auto add = a + b;

        std::cout << "(a + b)'s size: " << add.rows() << " " << add.cols() << std::endl;

        for (int i = 0; i < add.rows(); ++i) {
            for (int j = 0; j < add.cols(); ++j) {
                std::cout << add[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_SUB1() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "Value b: ";
        double b;
        std::cin >> b;
        std::cout << std::endl;

        auto multi = a + b;

        std::cout << "(a - b)'s size: " << multi.rows() << " " << multi.cols() << std::endl;

        for (int i = 0; i < multi.rows(); ++i) {
            for (int j = 0; j < multi.cols(); ++j) {
                std::cout << multi[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_SUB2() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX b's size: ";
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX b(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "MATRIX b:" << std::endl;
        for (int i = 0; i < b.rows(); ++i) {
            for (int j = 0; j < b.cols(); ++j) {
                std::cin >> b[i][j];
            }
        }

        auto add = a - b;

        std::cout << "(a - b)'s size: " << add.rows() << " " << add.cols() << std::endl;

        for (int i = 0; i < add.rows(); ++i) {
            for (int j = 0; j < add.cols(); ++j) {
                std::cout << add[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_MULTI1() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "Value b: ";
        double b;
        std::cin >> b;
        std::cout << std::endl;

        auto multi = a * b;

        std::cout << "(a * b)'s size: " << multi.rows() << " " << multi.cols() << std::endl;

        for (int i = 0; i < multi.rows(); ++i) {
            for (int j = 0; j < multi.cols(); ++j) {
                std::cout << multi[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_MULTI2() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX b's size: ";
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX b(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "MATRIX b:" << std::endl;
        for (int i = 0; i < b.rows(); ++i) {
            for (int j = 0; j < b.cols(); ++j) {
                std::cin >> b[i][j];
            }
        }

        auto multi = a * b;

        std::cout << "(ab)'s size: " << multi.rows() << " " << multi.cols() << std::endl;

        for (int i = 0; i < multi.rows(); ++i) {
            for (int j = 0; j < multi.cols(); ++j) {
                std::cout << multi[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_DEVIDE1() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "Value b: ";
        double b;
        std::cin >> b;
        std::cout << std::endl;

        auto multi = a / b;

        std::cout << "(a / b)'s size: " << multi.rows() << " " << multi.cols() << std::endl;

        for (int i = 0; i < multi.rows(); ++i) {
            for (int j = 0; j < multi.cols(); ++j) {
                std::cout << multi[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }

    static void EXAMPLE_HADAMARD1() {
        std::cout << "MATRIX a's size: ";

        int n, m;
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX a(n, m);

        std::cout << "MATRIX b's size: ";
        std::cin >> n >> m;
        std::cout << std::endl;
        MATRIX b(n, m);

        std::cout << "MATRIX a:" << std::endl;
        for (int i = 0; i < a.rows(); ++i) {
            for (int j = 0; j < a.cols(); ++j) {
                std::cin >> a[i][j];
            }
        }

        std::cout << "MATRIX b:" << std::endl;
        for (int i = 0; i < b.rows(); ++i) {
            for (int j = 0; j < b.cols(); ++j) {
                std::cin >> b[i][j];
            }
        }

        auto add = Hadamard_Static(a, b);

        std::cout << "Hadamard(a, b)'s size: " << add.rows() << " " << add.cols() << std::endl;

        for (int i = 0; i < add.rows(); ++i) {
            for (int j = 0; j < add.cols(); ++j) {
                std::cout << add[i][j] << " ";
            }
            std::cout << std::endl;
        }

        std::cout << std::endl;
    }
};

constexpr auto Hadamard = MATRIX::Hadamard_Static;

using ActivationFunction = std::pair<std::function<double(double)>, std::function<double(double)>>;

ActivationFunction Sigmoid = {(std::function<double(double)>)[](double x) -> double {
    return 1 / (1 + exp(-x));
}, (std::function<double(double)>)[](double x) -> double {
    double SigmoidValue = 1 / (1 + exp(-x));
    return SigmoidValue * (1 - SigmoidValue);
}};

ActivationFunction ReLU = {(std::function<double(double)>)[](double x) -> double {
    return std::max((double)0.0, x);
}, (std::function<double(double)>)[](double x) -> double {
    return x > 0 ? (double)1.0 : (double)0.0;
}};

class NeuralNetwork {
public:
    int layersN;
    std::vector<std::size_t> layersNodes;
    std::vector<ActivationFunction> layersActivationFunctions;
    std::vector<MATRIX> weights, biases;

    NeuralNetwork() { }
    NeuralNetwork(std::vector<std::size_t> _layersNodes, std::vector<ActivationFunction> _layersActivationFunctions = {}) {
        layersN = _layersNodes.size();

        layersNodes = _layersNodes;
        layersNodes.shrink_to_fit();

        layersActivationFunctions.resize(layersN);
        layersActivationFunctions.shrink_to_fit();
        if (_layersActivationFunctions.empty()) [[unlikely]] {
            for (int i = 1; i < layersN; ++i) {
                layersActivationFunctions[i] = ReLU;
            }
        } else [[likely]] {
            for (int i = 1; i < layersN; ++i) {
                layersActivationFunctions[i] = _layersActivationFunctions[i - 1];
            }
        }

        weights.resize(layersN);
        for (int i = 1; i < weights.size(); ++i) {
            auto& weight = weights[i];
            weight = MATRIX(layersNodes[i], layersNodes[i - 1]);
        }

        biases.resize(layersN);
        for (int i = 1; i < weights.size(); ++i) {
            auto& biase = biases[i];
            biase = MATRIX(layersNodes[i], 1);
        }
    }

    std::vector<MATRIX> FeedForward(std::vector<MATRIX> inputs) const {
        std::vector<MATRIX> outputs;
        for (auto& input : inputs) {
            MATRIX output = input;
            for (int i = 1; i < layersN; ++i) {
                output = weights[i] * output + biases[i];
                for (int j = 0; j < output.rows(); ++j) {
                    output[j][0] = layersActivationFunctions[i].first(output[j][0]);
                }
            }
            outputs.push_back(output);
        }
        return outputs;
    }

    std::vector<MATRIX> operator()(std::vector<MATRIX> input) const {
        return FeedForward(input);
    }
};

int main() {
    NeuralNetwork NN({1, 100, 50, 2}, {ReLU, ReLU, Sigmoid});

    srand(time(0));
    for (int i = 1; i < 4; ++i) {
        for (int j = 0; j < NN.weights[i].rows(); ++j) {
            for (int k = 0; k < NN.weights[i].cols(); ++k) {
                NN.weights[i][j][k] = rand() % 10 - 5;
            }
        }

        for (int j = 0; j < NN.biases[i].rows(); ++j) {
            NN.biases[i][j][0] = rand() % 10 - 5;
        }
    }

    std::vector<MATRIX> inputs;

    std::cout << "Enter the number of inputs: ";
    int t;
    std::cin >> t;

    for (int i = 1; i <= t; ++i) {
        MATRIX input(1, 1);
        std::cout << std::endl << "input " << i << ": " << std::endl;
        std::cin >> input[0][0];
        inputs.push_back(input);
    }

    std::cout << std::endl << "------" << std::endl;
    std::vector<MATRIX> outputs = NN(inputs);
    for (auto& output : outputs) {
        for (int i = 0; i < output.rows(); ++i) {
            std::cout << output[i][0] << std::endl;
        }
        std::cout << std::endl;
    }

    return 0;
}