【深度学习】从现代C++中的开始:卷积

一、说明

        在上一个故事中,我们介绍了机器学习的一些最相关的编码方面,例如 functional 规划、矢量化线性代数规划

        本文,让我们通过使用 2D 卷积实现实际编码深度学习模型来开始我们的道路。让我们开始吧。

二、关于本系列

        我们将学习如何仅使用普通和现代C++对必须知道的深度学习算法进行编码,例如卷积、反向传播、激活函数、优化器、深度神经网络等。

这个故事是:在C++中编码 2D 卷积

查看其他故事:

0 — 现代C++深度学习编程基础

2 — 使用 Lambda 的成本函数

3 — 实现梯度下降

4 — 激活函数

...更多内容即将推出。

三、卷 积

        卷积是信号处理领域的老朋友。最初,它的定义如下:

        在机器学习术语中:

  • 我(...通常称为输入
  • K(...作为内核,以及
  • F(...)作为给定 K 的 I(x) 的特征映射

考虑一个多维离散域,我们可以将积分转换为以下求和:

最后,对于2D数字图像,我们可以将其重写为:

理解卷积的一种更简单的方法是下图:

有效卷积 — 作者图片

        我们可以很容易地看到内核在输入矩阵上滑动,生成另一个矩阵作为输出。这是卷积的简单情况,称为有效卷积。在这种情况下,矩阵的维度由下式给出:Output

dim(Output) = (m-k+1, n-k+1)

        这里:

  • m分别是输入矩阵中的行数和列数,以及n
  • k是平方核的大小。

        现在,让我们对第一个 2D 卷积进行编码。

四、使用循环对 2D 卷积进行编码

        实现卷积的最直观方法是使用循环:

auto Convolution2D = [](const Matrix &input, const Matrix &kernel)
{
    const int kernel_rows = kernel.rows();
    const int kernel_cols = kernel.cols();
    const int rows = (input.rows() - kernel_rows) + 1;
    const int cols = (input.cols() - kernel_cols) + 1;

    Matrix result = Matrix::Zero(rows, cols);

    for (int i = 0; i < rows; ++i) 
    {
        for (int j = 0; j < cols; ++j) 
        {
            double sum = input.block(i, j, kernel_rows, kernel_cols).cwiseProduct(kernel).sum();
            result(i, j) = sum;
        }
    }

    return result;
};

        这里没有秘密。我们将内核滑过列和行,为每个步骤应用内积。现在,我们可以像以下那样简单地使用它:

#include <iostream>
#include <Eigen/Core>

using Matrix = Eigen::MatrixXd;

auto Convolution2D = ...;

int main(int, char **) 
{
    Matrix kernel(3, 3);
    kernel << 
        -1, 0, 1,
        -1, 0, 1,
        -1, 0, 1;

    std::cout << "Kernel:\n" << kernel << "\n\n";

    Matrix input(6, 6);
    input << 3, 1, 0, 2, 5, 6,
        4, 2, 1, 1, 4, 7,
        5, 4, 0, 0, 1, 2,
        1, 2, 2, 1, 3, 4,
        6, 3, 1, 0, 5, 2,
        3, 1, 0, 1, 3, 3;

    std::cout << "Input:\n" << input << "\n\n";

    auto output = Convolution2D(input, kernel);
    std::cout << "Convolution:\n" << output << "\n";

    return 0;
}

        这是我们第一次实现卷积 2D,设计为易于理解。有一段时间,我们不关心性能或输入验证。让我们继续前进以获得更多见解。

在接下来的故事中,我们将学习如何使用快速傅立叶变换托普利兹矩阵来实现卷积。

五、填充

        在前面的示例中,我们注意到输出矩阵始终小于输入矩阵。有时,这种减少是好的,有时是坏的。我们可以通过在输入矩阵周围添加填充来避免这种减少:

        填充为 1 的输入图像

        卷积中填充的结果如下所示:

        填充卷积 — 作者图片

        实现填充卷积的一种简单(和蛮力)方法如下:

auto Convolution2D = [](const Matrix &input, const Matrix &kernel, int padding)
{
    int kernel_rows = kernel.rows();
    int kernel_cols = kernel.cols();
    int rows = input.rows() - kernel_rows + 2*padding + 1;
    int cols = input.cols() - kernel_cols + 2*padding + 1;

    Matrix padded = Matrix::Zero(input.rows() + 2*padding, input.cols() + 2*padding);
    padded.block(padding, padding, input.rows(), input.cols()) = input;

    Matrix result = Matrix::Zero(rows, cols);

    for(int i = 0; i < rows; ++i) 
    {
        for(int j = 0; j < cols; ++j) 
        {
            double sum = padded.block(i, j, kernel_rows, kernel_cols).cwiseProduct(kernel).sum();
            result(i, j) = sum;
        }
    }

    return result;
};

此代码很简单,但在内存使用方面非常昂贵。请注意,我们正在制作输入矩阵的完整副本以创建填充版本:

Matrix padded = Matrix::Zero(input.rows() + 2*padding, input.cols() + 2*padding);
padded.block(padding, padding, input.rows(), input.cols()) = input;

更好的解决方案可以使用指针来控制切片和内核边界:

auto Convolution2D_v2 = [](const Matrix &input, const Matrix &kernel, int padding)
{
    const int input_rows = input.rows();
    const int input_cols = input.cols();
    const int kernel_rows = kernel.rows();
    const int kernel_cols = kernel.cols();

    if (input_rows < kernel_rows) throw std::invalid_argument("The input has less rows than the kernel");
    if (input_cols < kernel_cols) throw std::invalid_argument("The input has less columns than the kernel");
    
    const int rows = input_rows - kernel_rows + 2*padding + 1;
    const int cols = input_cols - kernel_cols + 2*padding + 1;

    Matrix result = Matrix::Zero(rows, cols);

    auto fit_dims = [&padding](int pos, int k, int length) 
    {
        int input = pos - padding;
        int kernel = 0;
        int size = k;
        if (input < 0) 
        {
            kernel = -input;
            size += input;
            input = 0;
        }
        if (input + size > length) 
        {
            size = length - input;
        }
        return std::make_tuple(input, kernel, size);
    };

    for(int i = 0; i < rows; ++i) 
    {
        const auto [input_i, kernel_i, size_i] = fit_dims(i, kernel_rows, input_rows);
        for(int j = 0; size_i > 0 && j < cols; ++j) 
        {
            const auto [input_j, kernel_j, size_j] = fit_dims(j, kernel_cols, input_cols);
            if (size_j > 0) 
            {
                auto input_tile = input.block(input_i, input_j, size_i, size_j);
                auto input_kernel = kernel.block(kernel_i, kernel_j, size_i, size_j);
                result(i, j) = input_tile.cwiseProduct(input_kernel).sum();
            }
        }
    }
    return result;
};

        这个新代码要好得多,因为这里我们没有分配一个临时内存来保存填充的输入。但是,它仍然可以改进。调用和内存成本也很高。input.block(…)kernel.block(…)

调用的一种解决方案是使用 CwiseNullaryOp 替换它们。block(…)

        我们可以通过以下方式运行填充卷积:

#include <iostream>

#include <Eigen/Core>
using Matrix = Eigen::MatrixXd;
auto Convolution2D = ...; // or Convolution2D_v2

int main(int, char **) 
{
    Matrix kernel(3, 3);
    kernel << 
        -1, 0, 1,
        -1, 0, 1,
        -1, 0, 1;
    std::cout << "Kernel:\n" << kernel << "\n\n";

    Matrix input(6, 6);
    input << 
        3, 1, 0, 2, 5, 6,
        4, 2, 1, 1, 4, 7,
        5, 4, 0, 0, 1, 2,
        1, 2, 2, 1, 3, 4,
        6, 3, 1, 0, 5, 2,
        3, 1, 0, 1, 3, 3;
    std::cout << "Input:\n" << input << "\n\n";

    const int padding = 1;
    auto output = Convolution2D(input, kernel, padding);
    std::cout << "Convolution:\n" << output << "\n";

    return 0;
}

        请注意,现在,输入和输出矩阵具有相同的维度。因此,它被称为填充。默认填充模式,即无填充,通常称为填充。我们的代码允许 ,或任何非负填充。samevalidsamevalid

六、内核

        在深度学习模型中,核通常是奇次矩阵,如、等。有些内核非常有名,比如 Sobel 的过滤器:3x35x511x11

索贝尔过滤器 Gx 和 Gy

        更容易看到每个 Sobel 滤镜对图像的影响:

应用 Sobel 滤镜  

使用 Sobel 过滤器的代码在这里

        Gy 突出显示水平边缘,Gx 突出显示垂直边缘。因此,Sobel 内核 Gx 和 Gy 通常被称为“边缘检测器”。

        边缘是图像的原始特征,例如纹理、亮度、颜色等。现代计算机视觉的关键点是使用算法直接从数据中自动查找内核,例如Sobel过滤器。或者,使用更好的术语,通过迭代训练过程拟合内核。

        事实证明,训练过程教会计算机程序实现如何执行复杂的任务,例如识别和检测物体、理解自然语言等......内核的训练将在下一个故事中介绍。

七、结论和下一步

        在这个故事中,我们编写了第一个2D卷积,并使用Sobel滤波器作为将此卷积应用于图像的说明性案例。卷积在深度学习中起着核心作用。它们被大量用于当今每个现实世界的机器学习模型中。我们将重新审视卷积,以学习如何改进我们的实现,并涵盖一些功能,如步幅。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/47988.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Unity实用插件篇】| A* Pathfinding Project - A*寻路插件 的使用教程

前言【Unity实用插件篇】| A*寻路插件学习使用一、A*算法 简述二、A* Pathfinding Project 介绍2.1 A* Pathfinding Project 功能2.2 相关链接2.3 标准版和Pro版区别2.4 A* Pathfinding Project Free与Navigation的对比三、快速搭建一个自己的场景测试寻路3.1 寻路场景搭建3.2 …

python绘制3D条形图

文章目录 数据导入三维条形图bar3d 数据导入 尽管在matplotlib支持在一个坐标系中绘制多组条形图&#xff0c;效果如下 其中&#xff0c;蓝色表示中国&#xff0c;橘色表示美国&#xff0c;绿色表示欧盟。从这个图就可以非常直观地看出&#xff0c;三者自2018到2022年的GDP变化…

python使用CGI编程,网页写个标题

需要有个 Linux虚拟机&#xff0c;安装 apache&#xff0c; 本次使用 deepin v23&#xff0c;参考&#xff1a; sudo apt install apache2 #安装 apache2 systemctl start apache2 # 启动 apache2 sudo a2enmod cgi # 启用CGI模块 sudo mkdir /usr/lib/cgi-bin #创…

初识Mybatis,并创建第一个Mybatis项目(详细图文教程)

目录 前言 一、Mybatis是什么&#xff1f; 二、Mybatis的优点 三、创建第一个Mybatis项目 配置Mybatis开发环境 创建数据库 添加框架 配置连接字符串和Mybatis 使用Mybatis操作数据库 测试 前言 Spring 集成了 Mybatis 框架&#xff0c;方便我们更加便捷的使用&#…

关于自签名证书授权后在哪儿看

目录 firefox可以看到 chrome and edge firefox可以看到 chrome and edge 只能从打开的网站左上角进入

【数据结构】--八大排序算法【完整版】

匠心制作&#xff0c;后续有问题会加以修改的 &#xff0c;全文均是自己写的&#xff0c;几张图有参考网络 ———————————————— 目录 一、直接插入排序 二、希尔排序(直接插入排序的改良版) 三、选择排序&#xff08;直接选择排序&#xff09; 四、堆排序 …

前端实现导出excel表格(单行表头)

需求&#xff1a;实现勾选行导出为表格 一、安装插件 npm install --save file-saver xlsx运行项目报如下警告的话 运行npm install xlsx0.16.0 --save 来降低版本号&#xff08;最初我安装的版本号是0.18.16的版本&#xff09;再次运行项目就不会报如下警告了 二、新建一个ex…

SQL与NoSQL数据库选型及实际业务场景探讨

在企业系统架构设计中&#xff0c;选择合适的数据库类型是一项关键决策。本文将对比SQL和NoSQL数据库的特点&#xff0c;分析它们在数据模型、可扩展性、一致性与事务、查询复杂性与频率&#xff0c;以及性能与延迟等方面的优势和劣势。同时&#xff0c;结合轻易云数据集成平台…

系统学习Linux-MySQL用户权限管理(三)

一、用户权限管理概述 数据库用户权限管理是数据库系统中非常重要的一个方面&#xff0c;它用于控制不同用户访问和操作数据库的权限范围。数据库用户权限管理可以保护敏感数据和数据库结构&#xff0c;确保只有被授权的用户才可以操作和使用数据库&#xff0c;防止数据被修改…

Unity游戏源码分享-街机捕鱼2017版本

Unity游戏源码分享-街机捕鱼2017版本 完整的单机游戏 功能玩法&#xff0c;积分系统都已经齐全的了。 项目源码地址&#xff1a;https://download.csdn.net/download/Highning0007/88105459

个人博客系统 -- 登录页面添加图片验证码

目录 1. 功能展示 2. 前段代码 3. 后端代码 1. 功能展示 在登录页面添加验证码登录 1. 检测到没有输入验证码或者输入的验证码错误时,进行弹窗提示.并且刷新当前验证码图片 2. 点击验证码进行刷新 2. 前段代码 1. 添加验证码标签,在密码的下面,在login.html进行修改 主要…

1200*A. Cheap Travel

#include<bits/stdc.h> using namespace std; typedef long long ll; int n,m,a,b,res; int main(){cin>>n>>m>>a>>b;if(a*m<b) resa*n;else{if(n%m0) resn/m*b;else{resn/m*b;resmin(n%m*a,b);}}cout<<res;return 0; }

基于JavaSE的手机库存管理系统

1、项目背景 基于JavaSE完成如下需求&#xff1a; 功能需求&#xff1a; 1、查询库存量 2、可以修改库存中不同品牌手机的个数 3、退出系统 实现步骤&#xff1a; 1、把List当做库房 2、把手机存放在库房中 3、使用封装的方法区操作仓库中的手机 2、项目知识点 面向对象 集合…

从iOS App启动速度看如何为基础性能保驾护航 | 京东物流技术团队

1 前言 启动是App给用户的第一印象&#xff0c;一款App的启动速度&#xff0c;不单单是用户体验的事情&#xff0c;往往还决定了它能否获取更多的用户。所以到了一定阶段App的启动优化是必须要做的事情。App启动基本分为以下两种 1.1 冷启动 App 点击启动前&#xff0c;它的…

vue3 - 报错 Cannot use ‘in‘ operator to search for ‘path‘ in undefined (完美解决)

问题描述 在vue3项目中,浏览器控制台报错 Cannot use ‘in’ operator to search for ‘path’ in undefined 详细解决方案。 解决方案 在创建vue3的路由时,报了这样的错:Cannot use ‘in’ operator to search for ‘path’ in undefined,

六边形架构

六边形架构 微服务系统架构微服务定义微服务系统设计 传统分层架构六边形架构参考资料 微服务系统架构 需求描述做什么的问题&#xff0c;架构描述怎么做的问题(描述组成系统的各部件及其之间的关系) 微服务定义 下面的定义来自周志明老师的 凤凰架构 微服务是一种通过多个小型…

Linux6.17 Docker 安全及日志管理

文章目录 计算机系统5G云计算第四章 LINUX Docker 安全及日志管理一、Docker 容器与虚拟机的区别1.隔离与共享2.性能与损耗 二、Docker 存在的安全问题1.Docker 自身漏洞2.Docker 源码问题 三、Docker 架构缺陷与安全机制1.容器之间的局域网攻击2.DDoS 攻击耗尽资源3.有漏洞的系…

Redis 高可用之持久化

一、Redis 高可用 1.1 什么是高可用 在web服务器中&#xff0c;高可用是指服务器可以正常访问的时间&#xff0c;衡量的标准是在多长时间内可以提供正常服务(99.9%、99.99%、99.999%等等)。 但是在Redis语境中&#xff0c;高可用的含义似乎要宽泛一些&#xff0c;除了保证提…

postgresql|数据库|启动数据库时报错:FATAL: could not map anonymous shared memory的解决

前言&#xff1a; 一个很偶然的出现的问题&#xff0c;因为我需要验证备份文件是否正确&#xff0c;因此&#xff0c;我在一台已启动了一个数据库实例的服务器上&#xff0c;依据全备的数据库文件在启动一个实例&#xff0c;当然&#xff0c;在此之前&#xff0c;已经修改了备…

LeakCanary内存泄漏检测框架分析。

一、什么叫内存泄漏、内存溢出&#xff1f; 内存溢出(out of memory)&#xff1a;是指程序在申请内存时&#xff0c;没有足够的内存空间供其使用&#xff0c;出现out of memory&#xff1b;比如申请了一个integer,但给它存了long才能存下的数&#xff0c;那就是内存溢出。 内…