毕业设计项目 深度学习昆虫识别系统(源码+论文)
文章目录
-
- 项目背景
-
- 项目运行成效
-
- 设计思路
-
- 数据采集与处理流程
-
- 卷积神经网络架构
-
4.1 卷积模块
-
4.2 下采样模块
-
4.3 激活函数的选择:
-
4.4 全连接模块
-
4.5 利用TensorFlow中的Keras模块构建卷积神经网络架构
-
5 MobileNetV2网络
-
6 损失函数softmax 交叉熵
-
- 6.1 softmax函数
- 6.2 交叉熵损失函数
-
7 优化器SGD
-
8 学习率衰减策略
-
9 最后
0 前言
近段时间以来,在毕业设计与毕业答辩方面的要求逐步提高。传统的毕设题目往往显得缺乏新意与特色,并且难以满足毕业答辩的标准。然而,在这两段时间里每年都有不少同学反映其开发的系统无法满足老师的期望。
为了让大家顺利并节省精力通过毕设 学长将向大家介绍优质的毕业设计项目 今天就要开始分享
🚩 毕业设计 深度学习昆虫识别系统(源码+论文)
🥇学长这里给一个题目综合评分(每项满分5分)
难度系数:3分
工作量:3分
创新点:4分
🧿 项目分享:见文末!
1 项目运行效果



视频效果:
毕业设计 深度学习昆虫识别系统
2 设计原理
以一个demo作为样例讲解大致原理,实际工程要更为细致复杂。
中国是农业大国,在传统农业生产中常常会遇到病虫害的问题。针对病虫害问题的解决而言,在传统的昆虫识别方法中昆虫专家依据专业知识观察昆虫外部特征并参照相关的昆虫图鉴来进行识别这一过程耗费大量时间和精力已逐渐被昆虫图像识别技术所取代目前广泛采用的昆虫识别技术包括图像识别法微波雷达检测法生物光子检测法取样检测法近红外及高光谱检测法声测法等近年来人工智能技术迅速发展深度学习技术在自然语言处理机器视觉等领域取得了显著成果随着深度学习技术的进步已有研究者开始尝试将深度学习技术应用于昆虫图像识别领域本文旨在利用基于深度学习的图像识别技术来解决昆虫识别问题希望能为现实生活中病虫害识别等问题提供新的解决方案




3 数据收集和处理
数据被视为深度学习的基础要素。
数据的主要来源包括百度图片、必应图片、新浪微博、百度贴吧、新浪博客以及一些专业昆虫相关的网站等。
由于爬虫获取的图像质量参差不齐、标签可能存在错误且存在重复文件等问题,则必须进行清洗。
自动化清洗的方法主要包括:
自动化清洗包括:
- 去除尺寸过小的图像。
- 剔除比例失真明显的图片。
- 去掉无色或灰度的照片。
- 图像去重:通过视觉感知算法生成哈希值。
半自动化清洗包括:
- 图像级别的清洗: 通过训练好的昆虫与非昆虫图像分类器给文件打分, 其中非昆虫图片通常会获得较低的分数. 使用上一阶段训练完成的昆虫分类器来预测文件内容, 并将预判类别的概率作为得分依据. 设定阈值筛选低分文件, 并根据得分对图片进行重命名, 在资源管理器中选择按名称排序便于后续人工筛选出不符合标准的图片.
- 类别的清洗: 通过计算每个类别中的样本数量确定类别不平衡程度. 将样本划分为均匀分布的子集.
手工清洗是指通过人工识别文件夹内图像所属物种的过程, 这一过程涉及应用昆虫学专业知识来完成分类任务, 并因其复杂性而耗时费力。
4 卷积神经网络
卷积神经网络(Convolutional Neural Network, CNN)是一种前馈型人工 neural network,在其中每个 artificial neuron能够实现对邻近区域的 local perception。其基本结构包括 convolutional layers, activation layers, pooling layers以及 fully connected layers等主要组件。

4.1卷积层
卷积核相当于一个滑动窗口,示意图中3x3大小的卷积核依次划过6x6大小的输入数据中的对应区域,并与卷积核滑过区域做矩阵点乘,将所得结果依次填入对应位置即可得到右侧4x4尺寸的卷积特征图,例如划到右上角3x3所圈区域时,将进行0x0+1x1+2x1+1x1+0x0+1x1+1x0+2x0x1x1=6的计算操作,并将得到的数值填充到卷积特征的右上角。

4.2 池化层
池化操作又称为降采样,提取网络主要特征可以在达到空间不变性的效果同时,有效地减少网络参数,因而简化网络计算复杂度,防止过拟合现象的出现。在实际操作中经常使用最大池化或平均池化两种方式,如下图所示。虽然池化操作可以有效的降低参数数量,但过度池化也会导致一些图片细节的丢失,因此在搭建网络时要根据实际情况来调整池化操作。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UTsB7AhE-1658995487680)
4.3 激活函数:
基本可以将激活函数分为两大类,在卷积神经网络发展的初期阶段多采用饱和型激活功能,在深度学习演进的过程中则发展出了更多替代方案;其中一部分替代方案被称为非饱和型激活功能,在这一过程中研究者发现这类激活功能存在一些局限性,并对其实现原理进行了深入分析
4.4 全连接层
在设计神经网络架构时,“分类器”通常被定义为一个关键模块,在整个网络架构中的重要性不言而喻。经过连续运用卷积操作、池化操作以及激活函数处理后,在经历多轮特征提取后(...),神经网络能够有效地从原始输入数据中提取出具有判别性的特征并将其编码到潜在特征空间中。随后通过构建全连接层(fully connected layer),深度学习模型通过构建一系列线性变换与非线性激活函数的组合来实现对潜在特征与样本类别之间的复杂映射关系建立。这一过程不仅能够捕获图像的空间信息与纹理细节信息(...),而且能够有效地将高维的潜在表示映射回低维类别标签空间以实现精准的分类目标。此外,在解码器模块(decoder module)的作用下,“隐藏信息”的具象化过程也被纳入到了整体架构体系之中,并在整个图像处理任务流程中发挥着不可或缺的作用
4.5 使用tensorflow中keras模块实现卷积神经网络
class CNN(tf.keras.Model):
def __init__(self):
super().__init__()
self.conv1 = tf.keras.layers.Conv2D(
filters=32, # 卷积层神经元(卷积核)数目
kernel_size=[5, 5], # 感受野大小
padding='same', # padding策略(vaild 或 same)
activation=tf.nn.relu # 激活函数
)
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
5 MobileNetV2网络
简介
移动网系是谷歌近期推出的轻量化且高效率的卷积神经网络模型,在准确率与延迟方面进行了权衡。
主要改进点
相对于MobileNetV1,MobileNetV2 主要改进点:
- 本研究采用倒置残差模块(Inverted Residuals),通过先升维再降维的方式增强梯度传播路径的同时
- 移除低维度或浅层结构后的ReLU激活函数以保持特征多样性
- 基于全卷积设计的模型架构使得其能够适应不同尺寸图像
- 在MobileNetV2框架中采用倒置残差模块设计的单元结构如图所示:若需要实现下采样功能则应在深度可分离卷积操作中选择步长为2的卷积核
- 对于小规模模型推荐采用较小的扩张因子值(expansion factor),而较大的模型则更适合采用较大的扩张因子值(expansion factor)。具体而言建议选择5至10之间的数值范围论文中提到t=6t=6t=6时采用了该参数设置
倒残差结构(Inverted residual block )
ResNet的Bottleneck结构是降维->卷积->升维,是两边细中间粗
而MobileNetV2是先升维(6倍)-> 卷积 -> 降维,是沙漏形。

区别于MobileNetV1, MobileNetV2的卷积结构如下:

由于DW卷积保持通道数量不变,在上一层通道数较低的情况下,该方法在低维空间内的特征提取能力较为有限,并且表现欠佳。因此,在V2版本中,在DW操作之前增加了一层PW层以提升维度。
在V2版本中取消了第二个PW的激活函数,并采用了线性激活函数作为替代方案。这是因为,在高维空间中能够有效提升非线性能力;而在低维空间中可能会引入不必要的复杂度。考虑到第二个PW的核心作用是进行降维操作,在此情况下不宜再添加ReLU6层。

tensorflow相关实现代码
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)
6 损失函数softmax 交叉熵
6.1 softmax函数
Softmax函数由下列公式定义

softmax 的作用是把 一个序列,变成概率。

在多分类任务中,在处理多个神经元生成的结果时(即)将其映射至区间(0,1),使得所有概率值总和为1
python实现
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()函数
dim参数:
- 当dim设为0时,在全部数据上应用 softmax 运算
- 当dim设为1时,在某一特定维度下的列执行 softmax 计算
- 当dim设置为-1或2时,在某一特定维度下的行执行 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())
6.2 交叉熵损失函数
定义如下:

python实现
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的交叉熵函数实现方式分为二分类问题中的BCE损失(torch.nn.BCELoss())以及多分类问题中的Cross Entropy损失(torch.nn.CrossEntropyLoss())。
# 二分类 损失函数
loss = torch.nn.BCELoss()
l = loss(pred,real)
# 多分类损失函数
loss = torch.nn.CrossEntropyLoss()
7 优化器SGD
简介
SGD即Stochastic Gradient Descent(SGD),随机梯度下降算法,在1847年首次提出。该方法通过从数据集中随机选取一个小批量样本,并利用这些样本计算损失函数的梯度来进行模型更新。尽管该方法有效解决了随机选取小批量样本带来的计算效率问题,并且能够较好地处理大规模数据集的学习任务;但同时也面临自适应学习率难以精确调节的挑战。

pytorch调用方法:
torch.optim.SGD(params, lr=<required parameter>, momentum=0, dampening=0, weight_decay=0, nesterov=False)
相关代码:
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'] # 权重衰减系数
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: # 进行正则化
# add_表示原处改变,d_p = d_p + weight_decay*p.data
d_p.add_(weight_decay, p.data)
if momentum != 0:
param_state = self.state[p] # 之前的累计的数据,v(t-1)
# 进行动量累计计算
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
8 学习率衰减策略
余弦退火衰减
它本质上是一种带重启机制的随机梯度下降方法。在模型更新过程中,在模型可能会陷入多个局部最优解的情况下(即优化函数可能包含多个峰值),算法需要具备跳出当前极小值的能力,并继续探索其他潜在的极小值点直至找到全局极小值点。实现跳出的方法是在模型陷入局部最小值区域时突然提高学习率(即重启学习率)。
多周期的余弦退火衰减示意图如下:

相关代码实现
# ----------------------------------------------------------------------- #
# 多周期余弦退火衰减
# ----------------------------------------------------------------------- #
# eager模式防止graph报错
tf.config.experimental_run_functions_eagerly(True)
# ------------------------------------------------ #
import math
# 继承自定义学习率的类
class CosineWarmupDecay(keras.optimizers.schedules.LearningRateSchedule):
'''
initial_lr: 初始的学习率
min_lr: 学习率的最小值
max_lr: 学习率的最大值
warmup_step: 线性上升部分需要的step
total_step: 第一个余弦退火周期需要对总step
multi: 下个周期相比于上个周期调整的倍率
print_step: 多少个step并打印一次学习率
'''
# 初始化
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的学习率
self.learning_rate_list = []
# 当前步长
self.step = 0
# 前向传播, 训练时传入当前step,但是上面已经定义了一个,这个step用不上
def __call__(self, step):
# 如果当前step达到了当前周期末端就调整
if self.step>=self.total_step:
# 乘上倍率因子后会有小数,这里要注意
# 调整一个周期中线性部分的step长度
self.warmup_step = self.warmup_step * (1 + self.multi)
# 调整一个周期的总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)))
# 计算线性上升部分的增长系数k
k = (self.initial_lr - self.min_lr) / self.warmup_step
# 线性增长线段 y=kx+b
warmup = k * self.step + self.min_lr
# 以学习率峰值点横坐标为界,左侧是线性上升,右侧是余弦下降
decayed_learning_rate = tf.where(self.step<self.warmup_step, warmup, decayed_learning_rate)
# 每个epoch打印一次学习率
if step % self.print_step == 0:
# 打印当前step的学习率
print('learning_rate has changed to: ', decayed_learning_rate.numpy().item())
# 每个step保存一次学习率
self.learning_rate_list.append(decayed_learning_rate.numpy().item())
# 计算完当前学习率后step加一用于下一次
self.step = self.step + 1
# 返回调整后的学习率
return decayed_learning_rate
篇幅有限,更多详细设计见设计论文
9 最后
项目包含内容

近3万字 完整详细设计论文


🧿 项目分享:见文末!
