• 【毕业设计】基于深度学习的植物识别算法 - cnn opencv python



    0 前言

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

    为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天要分享的是

    🚩 **基于深度学习的植物识别算法 **

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

    • 难度系数:3分
    • 工作量:4分
    • 创新点:4分

    🧿 选题指导, 项目分享:

    https://gitee.com/dancheng-senior/project-sharing-1/blob/master/%E6%AF%95%E8%AE%BE%E6%8C%87%E5%AF%BC/README.md

    1 课题背景

    植物在地球上是一种非常广泛的生命形式,直接关系到人类的生活环境,目前,植物识别主要依靠相关行业从业人员及有经验专家实践经验,工作量大、效率低。近年来,随着社会科技及经济发展越来越快,计算机硬件进一步更新,性能也日渐提高,数字图像采集设备应用广泛,设备存储空间不断增大,这样大量植物信息可被数字化。同时,基于视频的目标检测在模式识别、机器学习等领域得到快速发展,进而基于图像集分类方法研究得到发展。
    本项目基于深度学习实现图像植物识别。

    2 具体实现

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    3 数据收集和处理

    数据是深度学习的基石
    数据的主要来源有: 百度图片, 必应图片, 新浪微博, 百度贴吧, 新浪博客和一些专业的植物网站等
    爬虫爬取的图像的质量参差不齐, 标签可能有误, 且存在重复文件, 因此必须清洗。清洗方法包括自动化清洗, 半自动化清洗和手工清洗。
    自动化清洗包括:

    • 滤除小尺寸图像.
    • 滤除宽高比很大或很小的图像.
    • 滤除灰度图像.
    • 图像去重: 根据图像感知哈希.

    半自动化清洗包括:

    • 图像级别的清洗: 利用预先训练的植物/非植物图像分类器对图像文件进行打分, 非植物图像应该有较低的得分; 利用前一阶段的植物分类器对图像文件 (每个文件都有一个预标类别) 进行预测, 取预标类别的概率值为得分, 不属于原预标类别的图像应该有较低的得分. 可以设置阈值, 滤除很低得分的文件; 另外利用得分对图像文件进行重命名, 并在资源管理器选择按文件名排序, 以便于后续手工清洗掉非植物图像和不是预标类别的图像.
    • 类级别的清洗

    手工清洗: 人工判断文件夹下图像是否属于文件夹名所标称的物种, 这需要相关的植物学专业知识, 是最耗时且枯燥的环节, 但也凭此认识了不少的植物.

    3 MobileNetV2网络

    简介

    MobileNet网络是Google最近提出的一种小巧而高效的CNN模型,其在accuracy和latency之间做了折中。

    主要改进点

    相对于MobileNetV1,MobileNetV2 主要改进点:

    • 引入倒残差结构,先升维再降维,增强梯度的传播,显著减少推理期间所需的内存占用(Inverted Residuals)
    • 去掉 Narrow layer(low dimension or depth) 后的 ReLU,保留特征多样性,增强网络的表达能力(Linear Bottlenecks)
    • 网络为全卷积,使得模型可以适应不同尺寸的图像;使用 RELU6(最高输出为 6)激活函数,使得模型在低精度计算下具有更强的鲁棒性
    • MobileNetV2 Inverted residual block 如下所示,若需要下采样,可在 DW 时采用步长为 2 的卷积
    • 小网络使用小的扩张系数(expansion factor),大网络使用大一点的扩张系数(expansion factor),推荐是5~10,论文中 t = 6 t = 6t=6

    倒残差结构(Inverted residual block

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

    而MobileNetV2是先升维(6倍)-> 卷积 -> 降维,是沙漏形。
    在这里插入图片描述区别于MobileNetV1, MobileNetV2的卷积结构如下:
    在这里插入图片描述
    因为DW卷积不改变通道数,所以如果上一层的通道数很低时,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)  
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    4 损失函数softmax 交叉熵

    4.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)
    
    • 1
    • 2
    • 3
    • 4

    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())
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4.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))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    PyTorch实现
    交叉熵函数分为二分类(torch.nn.BCELoss())和多分类函数(torch.nn.CrossEntropyLoss()

    # 二分类 损失函数
    loss = torch.nn.BCELoss()
    l = loss(pred,real)
    
    • 1
    • 2
    • 3
    # 多分类损失函数
    loss = torch.nn.CrossEntropyLoss()
    
    
    • 1
    • 2
    • 3

    5 优化器SGD

    简介
    SGD全称Stochastic Gradient Descent,随机梯度下降,1847年提出。每次选择一个mini-batch,而不是全部样本,使用梯度下降来更新模型参数。它解决了随机小批量样本的问题,但仍然有自适应学习率、容易卡在梯度较小点等问题。
    在这里插入图片描述
    pytorch调用方法:

    torch.optim.SGD(params, lr=<required parameter>, momentum=0, dampening=0, weight_decay=0, nesterov=False)
    
    • 1

    相关代码:

        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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    6 最后

  • 相关阅读:
    Ranger (五) --------- 使用 Ranger 对 Hive 进行权限管理
    华为程序员,985本科36岁,被公司解约:中年人路在何方?
    彻底理解零拷贝技术
    LightGBM高级教程:深度集成与迁移学习
    根据E-R图设计数据库表
    电脑访问不到在同网络的手机设备
    toB应用私有化交付发展历程、技术对比和选型
    全球第六大IT服务提供商富士通遭黑客攻击:多个系统被感染、客户敏感数据泄露
    Python基础语法
    Hbase相关总结
  • 原文地址:https://blog.csdn.net/HUXINY/article/details/126297941