用 c++ 版本徒手写一个神经网络 Tensor 和 Tensor 运算(1)

916 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情

概述

首先这是一个入门级的分享,所有其中给出了一些详细解释,如果您对神经网络有了一定了解可以跳过。

动机

看过安德烈、卡帕奇分享,其中他就推荐大家在学习神经网络时,最好自己动手去实现一个简单网络,而不是上来就用 pytorch 或者 tf 来实现一些复杂网络。因此自己冒出自己写一个网络想法。

开发环境

系统 Ubuntu 基于 cpu ,语言这里选择了 c++ ,处于个人现在正在学习 c++ 所以选择了 c++ 开发网络,随便练一练手。

cmake_minimum_required(VERSION 3.1)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

project(hello VERSION 1.0)

file(GLOB_RECURSE SRC_FILES src/*.cpp)

add_executable(hello main.cpp ${SRC_FILES})

target_include_directories(hello PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)

有关 cmake 可以简单参照一下 简单介绍一下 cmake 使用方法(1)

这里还是简单说一下,项目源文件放在 src 目录下,头文件放置了 include 文件夹下,然后我们在 include 文件夹下创建一个 tensor.h 头文件。

#pragma once

#include <vector>
#include <cmath>
#include <iostream>

#ifndef TENSOR_H_FILE
#define TENSOR_H_FILE


class Tensor
{
public:
    __uint32_t _cols;
    __uint32_t _rows;
    std::vector<float> _vals;


public:
    Tensor(__uint32_t cols,__uint32_t rows):_cols(cols),_rows(rows),_vals({})
    {
        _vals.resize(cols*rows,0.0f);
    }

};

#endif //TENSOR_H_FILE

tinynn_cpp_001.png

这里定义一个类,暂时 tensor 可以看做一个 2D 的矩阵,只有行和列,并且在存在这个 tensor 时我们用到 vector 这个动态数组,将其展平存在为一个 1D 矩阵,也可以理解为一个向量或者更直就可以将其看做为一个数组,长度为 cols x rows

tinynn_cpp_002.png

at 函数实现

函数 at 可以通过 col 和 row 可以定位到矩阵某一个元素。

tinynn_cpp_003.png

float at(__uint32_t col, __uint32_t row)
{
    return _vals[row*_cols + col];
}
float &at(__uint32_t col, __uint32_t row)
{
    return _vals[row*_cols + col];
}

Tensor multiply(Tensor& target)
{
    assert(_cols == target._rows);
    Tensor output(target._cols,_rows);

    for(__uint32_t y = 0; y < output._rows; y++){
        for(__uint32_t x = 0; x < output._cols; x++)
        {
            float result = 0.0f;
            for(__uint32_t k = 0; k < _cols; k++)
                result += at(k,y) * target.at(x,k);
                output.at(x,y) = result;
        }
    }

    return output;
};

tinynn_cpp_005.png

有关矩阵相乘,我们可以翻一翻线性代数的矩阵

float &at(__uint32_t col, __uint32_t row)
{
    return _vals[row*_cols + col];
}


Tensor multiply(Tensor& target)
{
    assert(_cols == target._rows);
    Tensor output(target._cols,_rows);
    // iterate output

    for(__uint32_t y = 0; y < output._rows; y++){
        for(__uint32_t x = 0; x < output._cols; x++)
        {
            float result = 0.0f;
            for(__uint32_t k = 0; k < _cols; k++)
                result += at(k,y) * target.at(x,k);
                output.at(x,y) = result;
        }
    }
    return output;
};

add 函数

矩阵加法

Tensor add(Tensor& target)
{
    assert(_rows == target._rows && _cols == target._cols);
    Tensor output(_cols,_rows);

    for(__uint32_t y = 0; y < output._rows; y++){
        for(__uint32_t x = 0; x < output._cols; x++)
        {
            output.at(x,y) = at(x,y) + target.at(x,y);
        }
    }
    return output;
}

multiplyScaler

Tensor multiplyScaler(float s)
{
    Tensor output(_cols,_rows);

    // iterate output
    for(__uint32_t y = 0; y < output._rows; y++){
        for(__uint32_t x = 0; x < output._cols; x++)
        {
            output.at(x,y) = at(x,y) * s;
        }
    }
    return output;
}

addScaler

Tensor addScaler(float s)
{

    Tensor output(_cols,_rows);
    // iterate output
    for(__uint32_t y = 0; y < output._rows; y++){
        for(__uint32_t x = 0; x < output._cols; x++)
        {
            output.at(x,y) = at(x,y) + s;
        }
    }
    return output;
}

negative

对于矩阵的元素求负,这样做好处就是我们可以将减法通过 negative 来实现

Tensor negative()
{
    Tensor output(_cols,_rows);
    for(__uint32_t y = 0; y < output._rows; y++){
        for(__uint32_t x = 0; x < output._cols; x++)
        {
            output.at(x,y) = -at(x,y);
        }
    }
    return output;
}

transpose

矩阵转置,也就是行列互换。

tinynn_cpp_006.png

Tensor transpose()
{
    Tensor output(_cols,_rows);
    for(__uint32_t y = 0; y < _rows; y++){
        for(__uint32_t x = 0; x < _cols; x++)
        {
            output.at(y,x) = at(x,y);
        }

    }
    return output;
}