0 前言
?? 優(yōu)質(zhì)競賽項目系列,今天要分享的是
?? **基于深度學(xué)習(xí)的昆蟲識別算法研究與實現(xiàn) **
該項目較為新穎,適合作為競賽課題方向,學(xué)長非常推薦!
??學(xué)長這里給一個題目綜合評分(每項滿分5分)
- 難度系數(shù):3分
- 工作量:4分
- 創(chuàng)新點:4分
?? 更多資料, 項目分享:
https://gitee.com/dancheng-senior/postgraduate文章來源地址http://www.zghlxwxcb.cn/news/detail-724932.html
1 課題背景
中國是農(nóng)業(yè)大國,在傳統(tǒng)的農(nóng)業(yè)生產(chǎn)中,經(jīng)常會受到病蟲害問題的困擾。在解決病蟲害問題時,第一步是識別昆蟲。在傳統(tǒng)的昆蟲識別方法中,昆蟲專家根據(jù)專業(yè)知識觀察昆蟲的外部特征,并對照相關(guān)的昆蟲圖鑒進(jìn)行識別,費時費力。如今,傳統(tǒng)的昆蟲識別方法逐漸被昆蟲圖像識別技術(shù)代替。目前常用的昆蟲識別技術(shù)有圖像識別法、微波雷達(dá)檢測法、生物光子檢測法、取樣檢測法、近紅外及高光譜法、聲測法等。近年來,隨著人工智能的迅速發(fā)展,深度學(xué)習(xí)技術(shù)在處理自然語言、機器視覺等方面取得了很多成果,隨著深度學(xué)習(xí)的發(fā)展,已經(jīng)有研究人員開始將深度學(xué)習(xí)技術(shù)應(yīng)用于昆蟲的圖像識別。文章旨在利用基于深度學(xué)習(xí)的圖像識別技術(shù)解決昆蟲識別問題,希望能給現(xiàn)實生活中的病蟲害識別問題提供新的解決問題的思路。
2 具體實現(xiàn)
3 數(shù)據(jù)收集和處理
數(shù)據(jù)是深度學(xué)習(xí)的基石
數(shù)據(jù)的主要來源有: 百度圖片, 必應(yīng)圖片, 新浪微博, 百度貼吧, 新浪博客和一些專業(yè)的昆蟲網(wǎng)站等
爬蟲爬取的圖像的質(zhì)量參差不齊, 標(biāo)簽可能有誤, 且存在重復(fù)文件, 因此必須清洗。清洗方法包括自動化清洗, 半自動化清洗和手工清洗。
自動化清洗包括:
- 濾除小尺寸圖像.
- 濾除寬高比很大或很小的圖像.
- 濾除灰度圖像.
- 圖像去重: 根據(jù)圖像感知哈希.
半自動化清洗包括:
- 圖像級別的清洗: 利用預(yù)先訓(xùn)練的昆蟲/非昆蟲圖像分類器對圖像文件進(jìn)行打分, 非昆蟲圖像應(yīng)該有較低的得分; 利用前一階段的昆蟲分類器對圖像文件 (每個文件都有一個預(yù)標(biāo)類別) 進(jìn)行預(yù)測, 取預(yù)標(biāo)類別的概率值為得分, 不屬于原預(yù)標(biāo)類別的圖像應(yīng)該有較低的得分. 可以設(shè)置閾值, 濾除很低得分的文件; 另外利用得分對圖像文件進(jìn)行重命名, 并在資源管理器選擇按文件名排序, 以便于后續(xù)手工清洗掉非昆蟲圖像和不是預(yù)標(biāo)類別的圖像.
- 類級別的清洗
手工清洗: 人工判斷文件夾下圖像是否屬于文件夾名所標(biāo)稱的物種, 這需要相關(guān)的昆蟲學(xué)專業(yè)知識, 是最耗時且枯燥的環(huán)節(jié)。
3 卷積神經(jīng)網(wǎng)絡(luò)
卷積神經(jīng)網(wǎng)絡(luò)(Convolutional Neural
Netwoek,CNN)是一種前饋神經(jīng)網(wǎng)絡(luò),它的人工神經(jīng)元可以局部響應(yīng)周圍的神經(jīng)元,每個神經(jīng)元都接收一些輸入,并做一些點積計算。它通常包含卷積層、激活層、池化層、全連接層。
2.1卷積層
卷積核相當(dāng)于一個滑動窗口,示意圖中3x3大小的卷積核依次劃過6x6大小的輸入數(shù)據(jù)中的對應(yīng)區(qū)域,并與卷積核滑過區(qū)域做矩陣點乘,將所得結(jié)果依次填入對應(yīng)位置即可得到右側(cè)4x4尺寸的卷積特征圖,例如劃到右上角3x3所圈區(qū)域時,將進(jìn)行0x0+1x1+2x1+1x1+0x0+1x1+1x0+2x0x1x1=6的計算操作,并將得到的數(shù)值填充到卷積特征的右上角。
2.2 池化層
池化操作又稱為降采樣,提取網(wǎng)絡(luò)主要特征可以在達(dá)到空間不變性的效果同時,有效地減少網(wǎng)絡(luò)參數(shù),因而簡化網(wǎng)絡(luò)計算復(fù)雜度,防止過擬合現(xiàn)象的出現(xiàn)。在實際操作中經(jīng)常使用最大池化或平均池化兩種方式,如下圖所示。雖然池化操作可以有效的降低參數(shù)數(shù)量,但過度池化也會導(dǎo)致一些圖片細(xì)節(jié)的丟失,因此在搭建網(wǎng)絡(luò)時要根據(jù)實際情況來調(diào)整池化操作。[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-
UTsB7AhE-1658995487680)
2.3 激活函數(shù):
激活函數(shù)大致分為兩種,在卷積神經(jīng)網(wǎng)絡(luò)的發(fā)展前期,使用較為傳統(tǒng)的飽和激活函數(shù),主要包括sigmoid函數(shù)、tanh函數(shù)等;隨著神經(jīng)網(wǎng)絡(luò)的發(fā)展,研宄者們發(fā)現(xiàn)了飽和激活函數(shù)的弱點,并針對其存在的潛在問題,研宄了非飽和激活函數(shù),其主要含有ReLU函數(shù)及其函數(shù)變體
2.4 全連接層
在整個網(wǎng)絡(luò)結(jié)構(gòu)中起到“分類器”的作用,經(jīng)過前面卷積層、池化層、激活函數(shù)層之后,網(wǎng)絡(luò)己經(jīng)對輸入圖片的原始數(shù)據(jù)進(jìn)行特征提取,并將其映射到隱藏特征空間,全連接層將負(fù)責(zé)將學(xué)習(xí)到的特征從隱藏特征空間映射到樣本標(biāo)記空間,一般包括提取到的特征在圖片上的位置信息以及特征所屬類別概率等。將隱藏特征空間的信息具象化,也是圖像處理當(dāng)中的重要一環(huán)。
2.5 使用tensorflow中keras模塊實現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)
?
class CNN(tf.keras.Model):
def __init__(self):
super().__init__()
self.conv1 = tf.keras.layers.Conv2D(
filters=32, # 卷積層神經(jīng)元(卷積核)數(shù)目
kernel_size=[5, 5], # 感受野大小
padding='same', # padding策略(vaild 或 same)
activation=tf.nn.relu # 激活函數(shù)
)
self.pool1 = tf.keras.layers.MaxPool2D(pool_size=[2, 2], strides=2)
self.conv2 = tf.keras.layers.Conv2D(
filters=64,
kernel_size=[5, 5],
padding='same',
activation=tf.nn.relu
)
self.pool2 = tf.keras.layers.MaxPool2D(pool_size=[2, 2], strides=2)
self.flatten = tf.keras.layers.Reshape(target_shape=(7 * 7 * 64,))
self.dense1 = tf.keras.layers.Dense(units=1024, activation=tf.nn.relu)
self.dense2 = tf.keras.layers.Dense(units=10)
def call(self, inputs):
x = self.conv1(inputs) # [batch_size, 28, 28, 32]
x = self.pool1(x) # [batch_size, 14, 14, 32]
x = self.conv2(x) # [batch_size, 14, 14, 64]
x = self.pool2(x) # [batch_size, 7, 7, 64]
x = self.flatten(x) # [batch_size, 7 * 7 * 64]
x = self.dense1(x) # [batch_size, 1024]
x = self.dense2(x) # [batch_size, 10]
output = tf.nn.softmax(x)
return output
4 MobileNetV2網(wǎng)絡(luò)
簡介
MobileNet網(wǎng)絡(luò)是Google最近提出的一種小巧而高效的CNN模型,其在accuracy和latency之間做了折中。
主要改進(jìn)點
相對于MobileNetV1,MobileNetV2 主要改進(jìn)點:
- 引入倒殘差結(jié)構(gòu),先升維再降維,增強梯度的傳播,顯著減少推理期間所需的內(nèi)存占用(Inverted Residuals)
- 去掉 Narrow layer(low dimension or depth) 后的 ReLU,保留特征多樣性,增強網(wǎng)絡(luò)的表達(dá)能力(Linear Bottlenecks)
- 網(wǎng)絡(luò)為全卷積,使得模型可以適應(yīng)不同尺寸的圖像;使用 RELU6(最高輸出為 6)激活函數(shù),使得模型在低精度計算下具有更強的魯棒性
- MobileNetV2 Inverted residual block 如下所示,若需要下采樣,可在 DW 時采用步長為 2 的卷積
- 小網(wǎng)絡(luò)使用小的擴張系數(shù)(expansion factor),大網(wǎng)絡(luò)使用大一點的擴張系數(shù)(expansion factor),推薦是5~10,論文中 t = 6 t = 6t=6
倒殘差結(jié)構(gòu)(Inverted residual block )
ResNet的Bottleneck結(jié)構(gòu)是降維->卷積->升維,是兩邊細(xì)中間粗
而MobileNetV2是先升維(6倍)-> 卷積 -> 降維,是沙漏形。區(qū)別于MobileNetV1,
MobileNetV2的卷積結(jié)構(gòu)如下:
因為DW卷積不改變通道數(shù),所以如果上一層的通道數(shù)很低時,DW只能在低維空間提取特征,效果不好。所以V2版本在DW前面加了一層PW用來升維。
同時V2去除了第二個PW的激活函數(shù)改用線性激活,因為激活函數(shù)在高維空間能夠有效地增加非線性,但在低維空間時會破壞特征。由于第二個PW主要的功能是降維,所以不宜再加ReLU6。
tensorflow相關(guān)實現(xiàn)代碼
?
import tensorflow as tf
import numpy as np
from tensorflow.keras import layers, Sequential, Model
class ConvBNReLU(layers.Layer):
def __init__(self, out_channel, kernel_size=3, strides=1, **kwargs):
super(ConvBNReLU, self).__init__(**kwargs)
self.conv = layers.Conv2D(filters=out_channel,
kernel_size=kernel_size,
strides=strides,
padding='SAME',
use_bias=False,
name='Conv2d')
self.bn = layers.BatchNormalization(momentum=0.9, epsilon=1e-5, name='BatchNorm')
self.activation = layers.ReLU(max_value=6.0) # ReLU6
def call(self, inputs, training=False, **kargs):
x = self.conv(inputs)
x = self.bn(x, training=training)
x = self.activation(x)
return x
class InvertedResidualBlock(layers.Layer):
def __init__(self, in_channel, out_channel, strides, expand_ratio, **kwargs):
super(InvertedResidualBlock, self).__init__(**kwargs)
self.hidden_channel = in_channel * expand_ratio
self.use_shortcut = (strides == 1) and (in_channel == out_channel)
layer_list = []
# first bottleneck does not need 1*1 conv
if expand_ratio != 1:
# 1x1 pointwise conv
layer_list.append(ConvBNReLU(out_channel=self.hidden_channel, kernel_size=1, name='expand'))
layer_list.extend([
# 3x3 depthwise conv
layers.DepthwiseConv2D(kernel_size=3, padding='SAME', strides=strides, use_bias=False, name='depthwise'),
layers.BatchNormalization(momentum=0.9, epsilon=1e-5, name='depthwise/BatchNorm'),
layers.ReLU(max_value=6.0),
#1x1 pointwise conv(linear)
# linear activation y = x -> no activation function
layers.Conv2D(filters=out_channel, kernel_size=1, strides=1, padding='SAME', use_bias=False, name='project'),
layers.BatchNormalization(momentum=0.9, epsilon=1e-5, name='project/BatchNorm')
])
self.main_branch = Sequential(layer_list, name='expanded_conv')
def call(self, inputs, **kargs):
if self.use_shortcut:
return inputs + self.main_branch(inputs)
else:
return self.main_branch(inputs)
?
?
5 損失函數(shù)softmax 交叉熵
5.1 softmax函數(shù)
Softmax函數(shù)由下列公式定義
softmax 的作用是把 一個序列,變成概率。
softmax用于多分類過程中,它將多個神經(jīng)元的輸出,映射到(0,1)區(qū)間內(nèi),所有概率的和將等于1。
python實現(xiàn)
?
def softmax(x):
shift_x = x - np.max(x) # 防止輸入增大時輸出為nan
exp_x = np.exp(shift_x)
return exp_x / np.sum(exp_x)
PyTorch封裝的Softmax()函數(shù)
dim參數(shù):
-
dim為0時,對所有數(shù)據(jù)進(jìn)行softmax計算
-
dim為1時,對某一個維度的列進(jìn)行softmax計算
-
dim為-1 或者2 時,對某一個維度的行進(jìn)行softmax計算
import torch x = torch.tensor([2.0,1.0,0.1]) x.cuda() outputs = torch.softmax(x,dim=0) print("輸入:",x) print("輸出:",outputs) print("輸出之和:",outputs.sum())
5.2 交叉熵?fù)p失函數(shù)
定義如下:
python實現(xiàn)
?
def cross_entropy(a, y):
return np.sum(np.nan_to_num(-y*np.log(a)-(1-y)*np.log(1-a)))
# tensorflow version
loss = tf.reduce_mean(-tf.reduce_sum(y_*tf.log(y), reduction_indices=[1]))
# numpy version
loss = np.mean(-np.sum(y_*np.log(y), axis=1))
PyTorch實現(xiàn)
交叉熵函數(shù)分為二分類(torch.nn.BCELoss())和多分類函數(shù)(torch.nn.CrossEntropyLoss()
?
# 二分類 損失函數(shù)
loss = torch.nn.BCELoss()
l = loss(pred,real)
# 多分類損失函數(shù)
loss = torch.nn.CrossEntropyLoss()
?
6 優(yōu)化器SGD
簡介
SGD全稱Stochastic Gradient Descent,隨機梯度下降,1847年提出。每次選擇一個mini-
batch,而不是全部樣本,使用梯度下降來更新模型參數(shù)。它解決了隨機小批量樣本的問題,但仍然有自適應(yīng)學(xué)習(xí)率、容易卡在梯度較小點等問題。
pytorch調(diào)用方法:
?
torch.optim.SGD(params, lr=<required parameter>, momentum=0, dampening=0, weight_decay=0, nesterov=False)
相關(guān)代碼:
?
def step(self, closure=None):
"""Performs a single optimization step.
Arguments:
closure (callable, optional): A closure that reevaluates the model
and returns the loss.
"""
loss = None
if closure is not None:
loss = closure()
for group in self.param_groups:
weight_decay = group['weight_decay'] # 權(quán)重衰減系數(shù)
momentum = group['momentum'] # 動量因子,0.9或0.8
dampening = group['dampening'] # 梯度抑制因子
nesterov = group['nesterov'] # 是否使用nesterov動量
for p in group['params']:
if p.grad is None:
continue
d_p = p.grad.data
if weight_decay != 0: # 進(jìn)行正則化
# add_表示原處改變,d_p = d_p + weight_decay*p.data
d_p.add_(weight_decay, p.data)
if momentum != 0:
param_state = self.state[p] # 之前的累計的數(shù)據(jù),v(t-1)
# 進(jìn)行動量累計計算
if 'momentum_buffer' not in param_state:
buf = param_state['momentum_buffer'] = torch.clone(d_p).detach()
else:
# 之前的動量
buf = param_state['momentum_buffer']
# buf= buf*momentum + (1-dampening)*d_p
buf.mul_(momentum).add_(1 - dampening, d_p)
if nesterov: # 使用neterov動量
# d_p= d_p + momentum*buf
d_p = d_p.add(momentum, buf)
else:
d_p = buf
# p = p - lr*d_p
p.data.add_(-group['lr'], d_p)
return loss
?
7 學(xué)習(xí)率衰減策略
余弦退火衰減
這可以理解為是一種帶重啟的隨機梯度下降算法。在網(wǎng)絡(luò)模型更新時,由于存在很多局部最優(yōu)解,這就導(dǎo)致模型會陷入局部最優(yōu)解,即優(yōu)化函數(shù)存在多個峰值。這就要求,當(dāng)模型陷入局部最優(yōu)解時,能夠跳出去,并且繼續(xù)尋找下一個最優(yōu)解,直到找到全局最優(yōu)解。要使得模型跳出局部最優(yōu)解,就需
多周期的余弦退火衰減示意圖如下:
相關(guān)代碼實現(xiàn)
?
# ----------------------------------------------------------------------- #
# 多周期余弦退火衰減
# ----------------------------------------------------------------------- #
# eager模式防止graph報錯
tf.config.experimental_run_functions_eagerly(True)
# ------------------------------------------------ #
import math
# 繼承自定義學(xué)習(xí)率的類
class CosineWarmupDecay(keras.optimizers.schedules.LearningRateSchedule):
'''
initial_lr: 初始的學(xué)習(xí)率
min_lr: 學(xué)習(xí)率的最小值
max_lr: 學(xué)習(xí)率的最大值
warmup_step: 線性上升部分需要的step
total_step: 第一個余弦退火周期需要對總step
multi: 下個周期相比于上個周期調(diào)整的倍率
print_step: 多少個step并打印一次學(xué)習(xí)率
'''
# 初始化
def __init__(self, initial_lr, min_lr, warmup_step, total_step, multi, print_step):
# 繼承父類的初始化方法
super(CosineWarmupDecay, self).__init__()
# 屬性分配
self.initial_lr = tf.cast(initial_lr, dtype=tf.float32)
self.min_lr = tf.cast(min_lr, dtype=tf.float32)
self.warmup_step = warmup_step # 初始為第一個周期的線性段的step
self.total_step = total_step # 初始為第一個周期的總step
self.multi = multi
self.print_step = print_step
# 保存每一個step的學(xué)習(xí)率
self.learning_rate_list = []
# 當(dāng)前步長
self.step = 0
# 前向傳播, 訓(xùn)練時傳入當(dāng)前step,但是上面已經(jīng)定義了一個,這個step用不上
def __call__(self, step):
# 如果當(dāng)前step達(dá)到了當(dāng)前周期末端就調(diào)整
if self.step>=self.total_step:
# 乘上倍率因子后會有小數(shù),這里要注意
# 調(diào)整一個周期中線性部分的step長度
self.warmup_step = self.warmup_step * (1 + self.multi)
# 調(diào)整一個周期的總step長度
self.total_step = self.total_step * (1 + self.multi)
# 重置step,從線性部分重新開始
self.step = 0
# 余弦部分的計算公式
decayed_learning_rate = self.min_lr + 0.5 * (self.initial_lr - self.min_lr) * \
(1 + tf.math.cos(math.pi * (self.step-self.warmup_step) / \
(self.total_step-self.warmup_step)))
# 計算線性上升部分的增長系數(shù)k
k = (self.initial_lr - self.min_lr) / self.warmup_step
# 線性增長線段 y=kx+b
warmup = k * self.step + self.min_lr
# 以學(xué)習(xí)率峰值點橫坐標(biāo)為界,左側(cè)是線性上升,右側(cè)是余弦下降
decayed_learning_rate = tf.where(self.step<self.warmup_step, warmup, decayed_learning_rate)
# 每個epoch打印一次學(xué)習(xí)率
if step % self.print_step == 0:
# 打印當(dāng)前step的學(xué)習(xí)率
print('learning_rate has changed to: ', decayed_learning_rate.numpy().item())
# 每個step保存一次學(xué)習(xí)率
self.learning_rate_list.append(decayed_learning_rate.numpy().item())
# 計算完當(dāng)前學(xué)習(xí)率后step加一用于下一次
self.step = self.step + 1
# 返回調(diào)整后的學(xué)習(xí)率
return decayed_learning_rate
6 最后
?? 更多資料, 項目分享:文章來源:http://www.zghlxwxcb.cn/news/detail-724932.html
https://gitee.com/dancheng-senior/postgraduate
到了這里,關(guān)于競賽 深度學(xué)習(xí)+opencv+python實現(xiàn)昆蟲識別 -圖像識別 昆蟲識別的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!