在说完了神经元和神经网路後,接下来要介绍深度学习(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及影像辨识的起手式。
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。
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 - 如何写出优秀的测试?
元件介绍 Dropdown 是一个下拉选单元件,当页面上的选项过多时,可以用这个元件来收纳选项,透过...
聊了 HTTP 的基本概念(网站内容是怎麽被下载到电脑里的?、Method、Status Code)...
这边实做一个函数,让他能够一次对好几只策略做最佳化,输入的strategylist就是把好几个策略包...
还记得我们在之前做过变化模式吗? 没错,就是滑鼠悬停之後会变色的那个。 我们今天呢,就是要来帮它们...
首先我们介绍一下这个APP的功能。 介绍 这个APP主要会有的功能如下: 计算今天吃的东西类型 计算...