38从传统算法到深度学习:目标检测入门实战 --深度学习和人工神经网络

人工神经网络

通过前面的 6 节实验,我们学习了基于传统特征提取算法构建一个目标检测项目,但是传统的算法需要花费大量时间人为的设计特征,在实际应用中面对复杂的背景和目标时往往表现得并不理想。但是随着深度学习的崛起目标检测的性能和表现得到了大幅度的提升,深度学习的发展推动了目标检测的迅猛发展。在接下来的几节实验我们将向大家逐步介绍基于深度学习的目标检测算法。
深度学习是人工智能的一个分支,其受人脑的结构和功能的启发,通过人工神经网络(Artificial Neural Network)模仿人脑处理数据和决策的方式从数据中学习内在规律和特征表示。随着近年的计算机性能的发展和海量数据的增长使得深度学习成为机器学习中的热门研究方向。如今深度学习已经广泛应用于我们的日常生活中,如在线翻译、人脸识别、语音转换等。
人工神经网络(Artificial Neural Network)是一类可以从提供的数据中学习的机器学习算法,是一种模仿人脑神经系统处理信息的运算模型,其由大量的节点或称为神经元相互连接构成。一个节点由输入(Input)、权重(Weight)、偏差(Bias)、激活函数(Activation Function)、输出(Output)组成。下图左边的图片是一个简单的三层神经网络,其中每一个圆形表示一个节点且每一个节点都与下一层中的每个节点相连接。黄色节点所在的层被称为输入层(Input Layer),绿色节点所在的层被称为输出层(Output Layer),在神经网络中除去输入层和输出层的部分被称为隐藏层(Hidden Layer)。

image.png

上图右边的图片是一个节点或称为神经元,x1 ,x2 ,x3...xn表神经元的输入(这些x可以是像素值、语音、文字等),每一个x都有一个与之对应的权重w。这些权重值在训练过程中会被不断地更新,拥有较高权重值的x会被认为是比较重要的信息, 反之拥有较低权重值的信息会被认为不太重要。b表示偏差。我们将x和w相乘再求和,将求和结果加上偏差后输入一个激活函数得出最后的输出。这就是一个感知器的典型结构。将计算过程用数学方式来表达就是下面的公式。
image.png

激活函数(Activation Function)
激活函数为人工神经网络处理非线性问题提供了重要作用,其引入了非线性因素并且为节点建立一个输出边界,激活函数增加了神经网络的复杂性和网络学习复杂事物的能力。常见的激活函数有 Step、Sigmoid、Tanh、ReLU、Leaky ReLU、ELU,这里我们将向大家介绍 Sigmoid 函数。
Sigmoid 函数是神经网络中一个常见的激活函数。下面是 sigmoid 公式,其中x是上面求和的结果y。
image.png

下图是 Sigmoid 函数图,它的值在 0 到 1 之间。可以看到x越小y越接近 0,反之x越大y越接近 1。Sigmoid 函数曾被大量使用,但是由于其会导致梯度反向传递时,梯度爆炸和梯度消失,近年来使用越来越少。
image.png

前馈神经网络(Feed Forward Network)
前馈神经网络或前馈网络是深度学习中常见的一种单向网络,每一层的神经元只接收前一层神经元的输出并将自身的输出传递给下一层的神经元。信息从输入层逐层传递最后从输出层输出结果,整个网络没有反馈和中间跳转层。
image.png

上图是一个简单的前馈网络,为了方便描述一个前馈网络,我们可以使用一组整数来表示网络每层的节点数。例如上图的网络我们可以表示为 2-3-2,表示第一层有 2 个节点,第二层有 3 个节点,第三层有 2 个节点。我们使用整数 0 表示输入层,1 表示隐藏层,2 表示输出层。通常输出层会有多个节点,例如我们使用神经网络对人肤色分类时,输出层会有三个节点分别表示黄色、白色、黑色。
反向传播(Back Propagation)
在使用神经网络解决问题时,信息经过神经网络的前向传播最后得出的结果会与期望的结果有偏差,这时我们需要计算实际输出结果和期望结果之间的误差,并将该误差反向地从输出层向输入层传播,以此来更新权重以达到优化神经网络的目的,这个过程就是神经网络训练的过程。
image.png

使用 Python 构建一个神经网络

下面我们构建一个神经网络,首先导入 NumPy 模块。然后我们设定一个学习率 alpha 用于控制网络的学习进度,这里我们设定一个值为 0.1。

import numpy as np
alpha = 0.1

然后我们创建一个 set_w 函数对神经网络进行一些初始化处理。该函数有一个输入值 layers,这个输入值是一个列表,表示网络的结构,例如我们给函数输入一个列表 [3, 2, 1] 表示这个网络的输入层有 3 个节点,隐藏层有两个节点,输出层有 1 个节点。

def set_w(layers):
    W = [np.random.randn(x + 1, y + 1) / np.sqrt(x)
        for x, y in zip(layers[:-1], layers[1:-1])]
    
    w = np.random.randn(layers[-2] + 1, layers[-1])
    W.append(w / np.sqrt(layers[-2]))
    
    print("Network Layers: {}".format("-".join(str(n) for n in layers)))
    return W
image.png

下面我们构建一个 sigmoid 激活函数,该函数需要一个输入值 x。在函数内我们根据前面提到的激活函数公式计算激活值并返回计算结果。

def sigmoid(x):
    return 1.0 / (1 + np.exp(-x))

接下来我们构建一个 sigmoid_deriv 函数,该函数同样需要一个输入值 x。在函数内我们计算 sigmoid 函数的导数并返回计算结果。

def sigmoid_deriv(x):
    return x * (1 - x)

下面我们构建一个 feedforward 函数,该函数需要一个输入值 data, 这个 data 表示输入网络的数据集。我们使用这个函数实现前向传播,当训练完神经网络,我们将使用这个函数进行结果预测。在函数内首先使用 np.atleast_2d 确保 data 至少是 2 维数组。因为我们将偏置添加进了权重矩阵,所以在第三行代码中我们使用 np.c_ 在数组的每一行的末尾添加一个 1。
代码的第 5、6 行表示我们用 for 获取 W 中每个权重矩阵,然后分别使用矩阵乘法和 sigmoid 函数对数据进行预测。最后返回预测值。

def feedforward(data):
    p = np.atleast_2d(data)
    p = np.c_[p, np.ones(p.shape[0])]
    
    for layer in np.arange(0, len(W)):
        p = sigmoid(np.dot(p, W[layer]))
        
    return p

下面我们创建一个 loss 函数用于计算实际输出结果和期望结果之间的误差。该函数有两个输入值,data 数据集和数据集中每个数据对应的标签。在函数内我们首先使用 np.atleast_2d 确保 data 至少是 2 维数组,然后使用 feedforward 函数计算数据集的预测结果 predictions。最后我们计算预测结果与真实标签的误差 loss。

def loss(data, y):
    y = np.atleast_2d(y)
    predictions = feedforward(data)
    loss = 0.5 * np.sum((predictions - y) ** 2)

    return loss

接下来我们将创建一个 backprop 函数, 这个函数将用于计算反向传播。该函数需要两个输入值 x 和 y 分别表示数据集中的每个数据和其对应的标签。下面的第 2 至 7 行代码与 feedforward 函数类似这里就不多赘述了,第 8 行代码先将网络的每层矩阵相乘结果作为 sigmoid 函数的输入,然后将函数计算结果添加到列表 A 中。
第 10 行代码开始就是反向传播的过程,首先计算网络的输出值与标签值的差,这一步其实是loss 函数的导数。第 11 行代码我们创建一个列表 D 用于存储梯度变化的量,根据链式法则计算 error 与 sigmoid 函数的导数的乘积。列表中的值将用于更新权重矩阵。

def backprop(x, y):
    a = np.atleast_2d(x)
    A = [np.c_[a, np.ones((a.shape[0]))]]

    for layer in np.arange(0, len(W)):
        
        out = sigmoid(A[layer].dot(W[layer]))
        A.append(out) 

    error = A[-1] - y
    D = [error * sigmoid_deriv(A[-1])]

    for layer in np.arange(len(A) - 2, 0, -1):
        delta = D[-1].dot(W[layer].T)
        delta = delta * sigmoid_deriv(A[layer])
        D.append(delta)
 
    D = D[::-1]

    for layer in np.arange(0, len(W)):
        W[layer] += -alpha * A[layer].T.dot(D[layer])

第 13 到 16 行计算每层的梯度变化量,我们使用 for 循环反向遍历网络的每一层(不包括最后一层,因为最后一层网络的梯度变化我们已经在第 11 行计算了)。第 14、15 行计算当前层的梯度变化 delta, delta 就等于前一层的梯度变化 D[-1] 与当前层的权重的转置矩阵相乘,然后再与当前层的 sigmoid 函数的导数相乘。第 16 行将计算后得到的 delta 添加到列表 D 中。
上面第 18 到 21 行将更新权重矩阵。第 18 行将颠倒 D 中 delta 的顺序,因为 delta 是通过反向传播从输出层向输入层计算的,所以在更新权重矩阵时要将其顺序颠倒。第 20、21 行使用 for 循环遍历网络的每一层,在每一层我们将当前层的激活函数的转置矩阵和 D 中的 delta 相乘,再乘以负的学习率 alpha,最后我将计算得到的值与当前层的权重相加即可完成权重的更新。
下面是创建一个 train 函数用于训练网络。该函数需要 3 个输入值,data 表示输入的数据集,y 是每个数据的标签,epochs 表示训练的次数,这里我们设置一个默认值 500。

def train(data, y, epochs = 500):
    for epoch in np.arange(0, epochs):
        for (x, label) in zip(data, y):
            backprop(x, label)

        print("epoch: {}, loss: {}".format(epoch + 1, loss(data, y)))

在函数内首先使用一个 for 循环用于执行 epochs 次训练。接下来使用一个 for 循环获取 data 和 y 中的每个数据 x 和其对应的标签 label,在循环内我们执行 backprop 函数,将获取的数据和标签作为函数的输入值。最后输出每次训练后的误差。
至此我们的神经网络已经构建完成了,下面我们使用一个手写数字的数据集来训练搭建的网络并对数字进行分类识别。我们将从 Scikit-learn 中导入这个数据集(见下图),该数据集包含 1797 张数字,每张数字图片的尺寸是8×8,每一数字图像在 Scikit-learn 中被处理为一维向量。


image.png

首先我们将导入需要用到的模块

from sklearn import datasets
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

然后我们使用从 sklearn 导入的 dataset 模块中的 load_digits 方法加载数据集,并且将数据集中的数据类型转换为浮点型。

digits = datasets.load_digits()
data = digits.data.astype("float")

接下来使用从 sklearn 的 model_selection 模块中导入 train_test_split 方法将数据集分为训练集和测试集,这里我们将数据集中的 25% 数据分为测试集(关于 train_test_split 方法的详细讲解可以参考前面的线性分类实验)。

(trainX, testX, trainY, testY) = train_test_split(data, digits.target, test_size=0.25)

然后我们使用从 sklearn 的 preprocessing 模块中导入的 LabelBinarizer 对数据集的标签进行编码,具体操作是使用 LabelBinarizer 中的 fit_trainsform 方法分别将训练集和测试集的标签二值化。

trainY = LabelBinarizer().fit_transform(trainY)
testY = LabelBinarizer().fit_transform(testY)

下面我们可以使用神经网络训练模型了,首先我们调用 set_w 函数初始化我们的权重矩阵,trainX.shape[1] 表示获取每个数字图片的像素个数,前面我们提到每张数字图片的尺寸是8×8,所以输入层的节点数是 64,而由于数字从 0 到 9 共有 10 种,所以输出层的节点个数是 10。

W = set_w([trainX.shape[1], 32,16,10])

训练神经网络的代码很简单,只需要调用 train 函数即可,这里我们将训练集 trainX 和其对应的标签 trainY 作为函数的输入。

train(trainX, trainY)

为了评估模型的性能,在模型训练完后我们需要对模型进行评估。首先我们调用 feedforward 函数对测试集进行预测,然后使用 argmax 方法找到概率最大的标签值的下标。最后使用从 sklearn 的 metrics 模块中导入的 classification_report 方法显示评估的结果。

print("Evaluating model...")
predictions = feedforward(testX)
predictions = predictions.argmax(axis=1)
print(classification_report(testY.argmax(axis=1), predictions))

如果运行正常的,大家应该可以看到类似下图的结果。可以看到从 0 到 9 每个数字预测的精准度以及整个测试集预测的精准率。


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容