[Day 05] tinyML与卷积神经网路(CNN)

在说完了神经元和神经网路後,接下来要介绍深度学习(Deep Learning, DL)了。在上篇Fig. 4-1图中,神经网路只有三层,当中间隐藏层(Hidden Layer)增加後,人工神经网路(Artificial Neural Network, ANN)就变成深度神经网路(Deep Neural Network, DNN)了,隐藏层每一层的神经元数量代表网路的宽度,而隐藏层的层数则代表深度。当层数越多时,就越能达到深度学习的目标。

一般来说ANN或DNN在输入层资料都是一维的,二维或多维资料也都是展开成一维资料再输入,没有空间概念。而层与层之间皆采用全连结方式串接神经元,故当网路宽度和深度都增加时,其权重值(或称参数量)就会呈非线性爆增。若在一般电脑上运作时所需记忆体容量问题可能还勉强可以控制,但对於tinyML应用来说,以MCU非常有限的记忆体空间要存放网路结构、权重值(程序码区)和计算缓冲区(随机记忆体区)就会带来很大的麻烦(俗称模型塞不进去)。

为了解决这项问题,於是有学者提出采用卷积(Convolution, 或称回旋积)方式来共用权重值(参数量),大幅降低记忆体使用量,同时导入二(多)维空间提取特徵(滤波)概念,让神经网路更有利於影像(灰阶二维、彩色三维)类型资料的计算。其中1998年由知名学者Yann LeCun提出应用於手写数字辨识的LeNet-5(如Fig. 5-1)就是最为知名的**卷积神经网路(Convolutional Neural Network, CNN)**代表,几乎是每个学习DL, CNN及影像辨识的起手式。

卷积神经网路─LeNet-5
Fig. 5-1 卷积神经网路─LeNet-5。(OmniXR整理绘制, 2021/9/18)

如上图可得知LeNet-5的完整结构,其输入影像为一张32x32(共1024个)像素的灰阶影像,首先会经过一个5x5的卷积核以步移1步的方式进行卷积,然後得到一张新的28x28像素的特徵图(Feature Map),这里共使用了6个卷积核(Kernel)进行运算,所以会到6张特徵图。换句话说只需(5x5+1)x6个参数(那个+1指的是偏置量)就够了,但其计算量则需5x5x28x28x6(117,600)次乘加(Multiply-Accumulate, MAC)运算则已明显暴增。为了让运算减少,接下来使用池化(Pooling)技术将影像长宽缩小一半变成14x14像素的影像。再使用一次5x5的卷积核产生16张特徵图并使用池化让影像缩小到5x5。再来在进入全连结运算前,有两种展平特徵图的做法,一是再用一个5x5的卷积核产生120点的特徵点,另一种方式则是把16张5x5的特徵图展平为400(5x5x16)个特徵点,再和後面120个神经元进行全连结。为更稳定输出,这里再加人一层有84个神经元的隐藏层。最後则是全连结到十个输出(数字0~9),而为了让输出能更明确以机率表示,通常还会加上一些正归化函式,如Softmax等。

由於LeNet-5包含了许多DL及CNN基础知识,接下来就一一为大家说明几个重要元素,详见Fig. 5-2。

  • 卷积(Convolution):要设定一个卷积核,不管是原始资料或是卷积核其内容可以是整数或是浮点数。而卷积核的大小、移步(Stride)距离会严重影响计算量,所以通常都会使用3x3或5x5等较小尺寸的核,而移步距离则设1或2。所谓卷积就是把原始资料乘上卷积核後所有位置加总得到的数值,这相当於在提取特定数值排列的特徵。
  • 池化(Pooling):主为目的为缩小影像,通常以长宽各缩小一半来处理,常见有最大池化和平均池化,最大池化是挑出4个点中最大值保留,而平均池化则是将4个点的值加总後再平均。前者亦有把重点特徵保留下的用意。
  • 展平(Flatten):为了能产生全连结所需的特徵点,可将较小尺寸(如3x3, 5x5)的特徵图直接展开。由於这个动作把n维资料全部打回一维资料,因此得名。
  • 全连结(Full Connected):和[Day 04]提及的传统神经网路相同,这里就不多做赘述。
  • 正规化Softmax函式:由於输出有多个,但正确答案只有一个,为了强化该输出的机率表现,所以通常还会搭配一个机率正规化函式。其中Softmax主要概念就是把所有输出的机率加总当成分母,再以原输出机率当成分子,求出新的机率值,这样会更接近现实状况。

卷积神经网路主要构成元素
Fig. 5-2 卷积神经网路主要构成元素。(OmniXR整理绘制, 2021/9/18)

以下就用一个简单的C语言程序来表达如何完成Fig. 5-2的卷积动作,这样的程序可轻易在Arm Cortex-M上实现,不用CMSIS也不用Mbed,且先不考虑运行效能,也不使用平行运算指令集加速运算,只是让大家更容易了解卷积的运作方式。

// 定义影像、卷积核大小及移步距离
#define image_w 4  // 影像宽度
#define image_h 4  // 影像高度
#define kernel_w 3 // 卷积核宽度
#define kernel_h 3 // 卷积核高度
#define stride 1   // 移步距离

void main()
{
  // 初始化影像内容
  int image[] = { 1,2,3,4,
                  5,6,7,8,
                  9,10,11,12,
                  13,14,15,16 };
  // 初始化卷积核内容
  int kernel[] = { 0,1,0,
                   1,0,1,
                   1,0,1 };    
  // 初始化卷积结果内容
  int result[] = { 0,0,
                   0,0 };
                   
  int result_w = image_w-kernel_w+1; // 卷积结果宽度为2
  int result_h = image_h-kernel_h+1; // 卷积结果高度为2 
  int result_pos; // 卷积结果储存位置
  int kernel_pos; // 卷积核取值位置
  int image_pos;  // 影像取值位置
  
  // 计算完整卷积结果
  for(int h=0; h<result_h; h++){
    for(int w=0; w<result_w; w+=stride){
      result_pos = h*result_w+w; // 取得卷积结果储存位置
      result[result_pos] = 0;    // 清除结果值
      
      // 计算单一卷积结果
      for(int i=0; i<kernel_h; i++){
        image_pos = (h+i)*image_w+w; // 取得影像取用位置
        kernel_pos = i*kernel_w;     // 取得卷积核取用位置
     
        for(int j=0; j<kernel_w; j++,image_pos++,kernel_pos++){
          // 将目前卷积相乘结果加总
          result[result_pos] += (image[image_pos]*kernel[kernel_pos]); 
        }
      }
      
    }
  }
}

<<:  Angular 深入浅出三十天:表单与测试 Day05 - 如何写出优秀的测试?

>>:  Day5 中秋就是要继续烤肉阿-日式盐葱酱烧肉

【Day19】导航元件 - Dropdown

元件介绍 Dropdown 是一个下拉选单元件,当页面上的选项过多时,可以用这个元件来收纳选项,透过...

理解 HTTP(三):透过 HTTP 上网安全吗?浅谈网路安全、HTTPS、中间人攻击

聊了 HTTP 的基本概念(网站内容是怎麽被下载到电脑里的?、Method、Status Code)...

[Day28] 一次跑n支策略最佳化

这边实做一个函数,让他能够一次对好几只策略做最佳化,输入的strategylist就是把好几个策略包...

Day 11:「动起来!动起来!」- 用 Tailwind 简单做出过渡和动画效果

还记得我们在之前做过变化模式吗? 没错,就是滑鼠悬停之後会变色的那个。 我们今天呢,就是要来帮它们...

Day 28 - ios 开发实作 (今天还要继续吃吗APP-2)

首先我们介绍一下这个APP的功能。 介绍 这个APP主要会有的功能如下: 计算今天吃的东西类型 计算...