### 文章目录

 这两年开始毕业设计和毕业答辩的要求和难度不断提升，传统的毕设题目缺少创新和亮点，往往达不到毕业答辩的要求，这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。

 **基于深度学习的昆虫识别算法研究与实现 **

磊学长这里给一个题目综合评分(每项满分5分)

## 2.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],     # 感受野大小
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],
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网络

#### 主要改进点

MobileNetV2 Inverted residual block 如下所示，若需要下采样，可在 DW 时采用步长为 2 的卷积

#### 倒残差结构（Inverted residual block）

ResNet的Bottleneck结构是降维->卷积->升维，是两边细中间粗

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,
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.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.1 softmax函数

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())```

## 5.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实现

```# 二分类 损失函数
loss = torch.nn.BCELoss()
l = loss(pred，real)```

```# 多分类损失函数
loss = torch.nn.CrossEntropyLoss()```

## 6 优化器SGD

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']:
continue
if weight_decay != 0: # 进行正则化
# add_表示原处改变，d_p = d_p + 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
if nesterov: # 使用neterov动量
# d_p= d_p + momentum*buf
else:
d_p = buf
# p = p - lr*d_p
return loss```

## 7 学习率衰减策略

```# ----------------------------------------------------------------------- #
# 多周期余弦退火衰减
# ----------------------------------------------------------------------- #
# 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```