[1DCNN] 简单使用自制音频数据集进行模型训练

本文打算使用自制的西瓜数据集进行深度学习的模型训练.
通过对手部敲击西瓜的音频进行分析,进行快速傅里叶变换提取频域特征,使用一维卷积神经网络模型进行模型训练,构建西瓜成熟度检测模型.

一. 数据集预处理

1.数据采集

   使用进行模型训练的音频数据集,是自己采买西瓜进行录制的.
   在进行采集音频的时候,根据敲击西瓜的方式分为了,拍 ( P ),弹 ( T ),敲 ( Q ).

关于西瓜信息:
采集的西瓜音频总共有939条.

数据集一共包含158个西瓜样本,每个西瓜有若干条音频数据.
其中选取86个西瓜样本作为训练集,36个测试集,36个验证集,
基本上数据比例为6:2:2

西瓜 八分熟 成熟 过熟 偏生 总和
西瓜个数 25 76 31 26 158
音频数 145 440 192 162 939
标签 0 1 2 3

2.数据预处理

   因为自行采集的数据集,每个音频时长在6~30S之间,里面含有多个敲击信号.要对数据进行预处理.

2.1端点检测

端点检测详解链接: 使用matlab进行双门限法的端点检测

端点检测:是指从包含语音的一段信号中确定出语音的起始点和结束点位置.
意义:不仅可以增加样本数量,而且能够减少网络训练过程中不必要的计算,提升模型训练的准确率.

端点检测效果图


双参数双门限端点检测效果图

 

   从图中可以看出,经过端点检测处理后,样本个数变多了,信号长度大幅度缩减.

值得注意的是:
1.音频时间不要过短.
   假设采样频率为16kHZ,每一个数据样本要提取频域信号前1000个数据代表该信号,则要保证至少截取的每一个音频时长为0.0625s.(当然每个截取的音频里至少包含一个激励信号)

采样频率=每单位时间采样点数=采样点数/采样时间

2.阈值的设置要合理
   因为音频数量过多,需要进行批量处理.在进行批量处理之前,对于阈值的设置要合理.
   假设仅使用单参数的双门限法,也就是根据短时能量法来进行端点检测,根据音频的短时平均能量(或其它参数)设置阈值(amp1和amp2),使激励信号可以被正常找到.

在这里插入图片描述


合理设置阈值找到有效激励信号

在这里插入图片描述

合理设置阈值找到有效激励信号

2.2数据增强

   如果在进行了对音频进行了端点检测之后,数据量不够,还可以进行其他的数据增强.(端点检测也算数据增强了)

   在进行数据增强时,最好只做一些小改动,使得增强数据和源数据存在较小差异即可,切记不能改变原有数据的结构,不然将产生“脏数据”,通过对音频数据进行数据增强,能有助于模型避免过度拟合并变得更加通用.

对音频进行的改变如下:加噪,波形拉伸,高音修正.

加噪
添加的噪声为均值为0,标准差为1的高斯白噪声.

#####增加噪声#####
def add_noise(data):
    # 0.02为噪声因子
    wn = np.random.normal(0, 1, len(data))
    return np.where(data != 0.0, data.astype('float64') + 0.02 * wn, 0.0).astype(np.float32)

波形拉伸
在不影响音高的情况下改变声音的速度/持续时间.

#####波形拉伸#####
def time_stretch(x, rate):
    # rate:拉伸的尺寸,
    # rate > 1 加快速度
    # rate < 1 放慢速度
    return librosa.effects.time_stretch(x, rate)

高音修正
音高修正只改变音高而不影响音速.

#####音高修正#####
def pitch_shifting(x, sr, n_steps, bins_per_octave=12):
    # sr: 音频采样率
    # n_steps: 要移动多少步
    # bins_per_octave: 每个八度音阶(半音)多少步
    return librosa.effects.pitch_shift(x, sr, n_steps, bins_per_octave=bins_per_octave)

实例

import librosa
import numpy as np
import matplotlib.pyplot as plt
import soundfile as sf
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示符号
fs = 16000
wav_data, y = librosa.load(r"C:\Users\Administrator\Desktop\test\10_28_1190001.wav", sr=fs, mono=True)

#####1.增加噪声#####
def add_noise(data):
    # 0.02为噪声因子
    wn = np.random.normal(0, 1, len(data))
    return np.where(data != 0.0, data.astype('float64') + 0.02 * wn, 0.0).astype(np.float32)

#####3.波形拉伸#####
def time_stretch(x, rate):
    # rate:拉伸的尺寸,
    # rate > 1 加快速度
    # rate < 1 放慢速度
    return librosa.effects.time_stretch(x, rate)
#####4.音高修正#####
def pitch_shifting(x, sr, n_steps, bins_per_octave=12):
    # sr: 音频采样率
    # n_steps: 要移动多少步
    # bins_per_octave: 每个八度音阶(半音)多少步
    return librosa.effects.pitch_shift(x, sr, n_steps, bins_per_octave=bins_per_octave)

data_noise = add_noise(wav_data)
data_stretch = time_stretch(wav_data, rate=2)
data_pitch2 = pitch_shifting(wav_data, fs, n_steps=-6, bins_per_octave=12)   # 向下移三音(如果bins_per_octave为12,则六步)

# 绘图
plt.subplot(2, 2, 1)
plt.title("波形图", fontsize=15)
time = np.arange(0, len(wav_data)) * (1.0 / fs)
plt.plot(time, wav_data)
plt.xlabel('秒/s', fontsize=15)
plt.ylabel('振幅', fontsize=15)

plt.subplot(2, 2, 2)
plt.title("加噪", fontsize=15)
plt.plot(time, data_noise)
plt.xlabel('秒/s', fontsize=15)
plt.ylabel('振幅/Hz', fontsize=15)

plt.subplot(2, 2, 4)
plt.title("高音修正", fontsize=15)
plt.plot(time, data_pitch2)
plt.xlabel('秒/s', fontsize=15)
plt.ylabel('振幅/Hz', fontsize=15)

plt.subplot(2, 2, 3)
plt.title("波形拉伸", fontsize=15)
time = np.arange(0, len(data_stretch)) * (1.0 / fs)
plt.plot(time, data_stretch)
plt.xlabel('秒/s', fontsize=15)
plt.ylabel('振幅/Hz', fontsize=15)

plt.tight_layout()
plt.show()

在这里插入图片描述

音频数据增强效果图

2.3快速傅里叶变换(FFT)

 
   对端点检测(或者数据增强)后得到的信号进行快速傅里叶变换,得到其幅频特性.提取频域信号前1000个数据(或者更多)代表该信号.
 
   从物理的角度去看待傅立叶变换,它其实是帮助我们改变传统的时间域分析信号的方法转到从频率域分析问题的思维,下面的一幅立体图形可以帮助我们更好得理解这种角度的转换:
在这里插入图片描述   最前面的时域信号在经过傅立叶变换的分解之后,变为了不同正弦波信号的叠加,我们再去分析这些正弦波的频率,可以将一个信号变换到频域.有些信号在时域上是很难看出什么特征的,但是如果变换到频域之后,就很容易看出特征了.这就是很多信号分析采用FFT变换的原因.另外,FFT可以将一个信号的频谱提取出来,这在频谱分析方面也是经常用的.

   对于计算机来说只有离散和有限长度的数据才能被处理,对于其它的变换类型只有在数学演算中才能用到,在计算机面前我们只能用DFT方法,而FFT也只不过是DFT的一种快速的算法.

关于如何实现fft,numpy中有一个fft的库.
具体程序实例如下:

import numpy.fft as nf
import numpy as np
from sklearn import preprocessing
import matplotlib.pyplot as plt
import os
import re
from scipy.io import wavfile
# coding=utf-8
import os
import shutil
import pandas as pd
import numpy as np
import scipy.io as sio

# 对原始数据进行fft快速傅里叶变换之后,
# 每个类别整体对数据进行归一化并写入csv文件里存储数据
def myfft(sourceDir, targetDir):
    # 列出源目录文件和文件夹
    for file in os.listdir(sourceDir):
        # 拼接完整路径
        sourceFile = os.path.join(sourceDir, file)
        for files in os.listdir(sourceFile):
            sourceFile1 = os.path.join(sourceFile, files)
            data = []
            for files1 in os.listdir(sourceFile1):
                # 对每一个音频数据进行fft快速傅里叶变换
                try:
                    rate, data1 = wavfile.read(f'{
      
      sourceDir}/{
      
      file}/{
      
      files}/{
      
      files1}')
                    xf = np.fft.fft(data1)  # 快速傅里叶变换
                    xff = np.abs(xf)  # 取复数的绝对值,即复数的模(双边频谱)
                    n = 2 ** 15  # n>2*rate 即>32000 ,取了2^15 = 32768
                    y = xff / n  # 归一化处理
                    y1 = y[0:(int(n / 2))]  # 由于对称性,只取一半区间
                    data.append(y1[0:1000])  #取前一千个数据点
                except Exception as e:
                    continue
            data = np.array(data)
            print(data.shape)
            #对所有的数据进行标准化处理
            zscore_scaler = preprocessing.StandardScaler() 
            data2 = zscore_scaler.fit_transform(data)
            test = pd.DataFrame(data=data2)  
            test.to_csv(f'{
      
      targetDir}/{
      
      file}{
      
      files}.csv', encoding='utf-8', header=False,index=False)  # 保存为csv格式文件

if __name__ == "__main__":
		# 每个类别分别进行一次,完成之后得到4个.csv文件
    myfft("F:/文件/文件/watermelon/watermelon_data/split_data",
              "F:/文件/文件/watermelon/make_DataSet1")

在这里插入图片描述

原文件夹分布与目标文件夹得到四分类的.csv文件示例
 

在这里插入图片描述


单个音频FFT效果图

2.4数据集制作

   原有音频总共九百多条,经过端点检测数据增强后扩充到了六万多条.

西瓜成熟度及其对应标签

成熟度 标签
八分熟 0
成熟 1
过熟 2
偏生 3

数据集划分

为什么要划分数据集?
   深度学习是通过大量的线性分类器或非线性分类器,可导或不可导的激励函数,以及池化层等功能对观测对象的特征进行自动化的提取.

拥有如此好的分类能力会带来两个问题:
   在复杂的网络中,如此多的W已经早就没有统计学中的权重权值的意义,无法得到清晰的物理解释,也无法有效地进行逆向研究.
   网络能够学到很多东西,包括样本中所包含的噪声信息或者特例信息.

因此过拟合的原因和预防方法如下:
原因:样本过少,不足以归纳其共性.参数过多,能够拟合极为复杂的特征内容.
改善原因:增加样本数量,理论上越多越好.
检查手段:拿一些样本进行验证.通常会把拿到的所有样本数据分为三个集合.

训练集 :用来学习的样本集,通过这些向量来确定网络中的各个特定系数. 是用来进行训练模型的

验证集 : 是用来调整分类器的参数的样本集,在训练的过程中,网络模型会立刻在验证集进行验证.我们会同步观察到这个验证集数据上模型的表现如何,损失函数值是否会下降,准确率是否在提高.是用来调整超参数的.

测试集 : 是在训练后为测试模型的能力主要是分类能力而设置的一部分数据集合.是用来测试模型的准确性评估泛化能力.

数据划分比例
   因为是小规模数据集数据,则将数据集根据 7 : 3 : 1 的比例划分为训练集,测试集,验证集.就是将训练集和测试集按照70%,30%的比例划分得到,然后从训练集中取10%作为验证集.

处理小结
   将原始的音频进行端点检测和数据增强后,再进行快速傅里叶变换,取每一个音频信号的前1000点数据值,作为其特征值.
   汇总每一种成熟度的音频数据特征值,存储为csv格式,才能进行后续的模型训练.

 
读取4个类别的csv数据,并打上对应标签.
程序代码如下:

# 汇总所有的数据,并绘制标签
def makeall(file0, file1, file2, file3, name, savefile):
    eight_medium = pd.read_csv(file0, header=None)  # 8分熟
    mature = pd.read_csv(file1, header=None)  # 成熟
    overrripe = pd.read_csv(file2, header=None)  # 过熟
    ripe_yet = pd.read_csv(file3, header=None)  # 偏生

    bfs_data = np.asarray(eight_medium)
    cs_data = np.asarray(mature)
    gs_data = np.asarray(overrripe)
    ps_data = np.asarray(ripe_yet)

    data = np.concatenate((bfs_data, cs_data, gs_data, ps_data))
    bfs_label = np.zeros((bfs_data.shape[0], 1))
    cs_label = np.ones((cs_data.shape[0], 1))
    gs_label = 2 * np.ones((gs_data.shape[0], 1))
    ps_label = 3 * np.ones((ps_data.shape[0], 1))

    label = np.concatenate(((bfs_label, cs_label, gs_label, ps_label)))

    variable = pd.DataFrame(label)  # 将变量转化为dataframe数据结构
    variable.to_csv(f'{
      
      savefile}/{
      
      name}_label.csv', header=None, index=None)
    variable = pd.DataFrame(data)  # 将变量转化为dataframe数据结构
    variable.to_csv(f'{
      
      savefile}/{
      
      name}_data.csv', header=None, index=None)  # 存储为没有表头和索引的csv文件


if __name__ == "__main__":
    savefile = './makelabel/z-score_fft_alldata_0'
    name = 'train'
    savename = 'train_all'
    t0_train = f'{
      
      savefile}/{
      
      name}0.csv'
    t1_train = f'{
      
      savefile}/{
      
      name}1.csv'
    t2_train = f'{
      
      savefile}/{
      
      name}2.csv'
    t3_train = f'{
      
      savefile}/{
      
      name}3.csv'
    makeall(t0_train, t1_train, t2_train, t3_train, savename, savefile)

在这里插入图片描述

经过处理得到两个.csv文件,如图
 

二. 模型训练

1.模型设计

提出的模型设计是以简单卷积神经网络做基础,在其结构上进行增加与改进.

Tensorflow
   深度学习Tensorflow框架.不管是在服务器,边缘设备还是网络上,TensorFlow 都可以轻松地训练和部署模型;构建和训练先进的模型,并且不会降低速度或性能.

Keras
   tf.keras是TensorFlow 2.0的高阶API接口,为TensorFlow的代码提供了新的风格和设计模式,大大提升了TF代码的简洁性和复用性,官方也推荐使用tf.keras来进行模型设计和开发.
   用Keras中的深度学习模型—通用模型(Model函数式模型)来定义模型.通用模型可以设计非常复杂,任意拓扑结构的神经网络.相比于序列模型(Sequential)只能依次线性逐层添加,通用模型能够比较灵活地构造网络结构,设定各层级的关系.
   函数式模型接口是用户定义多输出模型,非循环有向模型或具有共享层的模型等复杂模型的途径.换言之,只要你的模型不是类似 VGG 一条路走到黑的模型,或者你的模型需要多于一个的输出,那么你总应该选择函数式模型.函数式模型是最广泛的一类模型,序贯模型(Sequential)只是它的一种特殊情况.

CNN
   卷积神经网络(Convolutional Neural Network,CNN)是一种前馈神经网络,通常用于处理具有类似网格结构的数据,例如图像或声音.CNN 在图像和语音识别方面表现出色,因为它们可以自动提取出特征,而无需人工干预.

   CNN 的核心是卷积层(Convolutional Layer),它通过滑动一个小的窗口(称为卷积核)在输入数据上进行卷积操作,从而提取出局部特征。卷积层通常会跟随一个池化层(Pooling Layer),用于降低特征图的维度,减少计算量。
 

卷积网络有两个比较大的特点:
①有至少一个卷积层,用来提取特征.
②卷积网络的卷积层通过权值共享方式进行工作,大大减少权值W的数量,使得在训练中在达到同样识别率的情况下收敛速度明显快于全连接BP网络.

以下是以简单简单卷积神经网络做基础,设计一维卷积神经网络(1DCNN).
模型设计如下

模型设计每层具体参数

Layer (type) Kernel_size Filter number Output Shape
conv1d 3×1 8 ( 1000, 8)
max_pooling1d ( 1000, 8)
batch_normalization ( 1000, 8)
conv1d_1 3×1 16 ( 1000, 16)
max_pooling1d_1 ( 1000, 16)
batch_normalization_1 ( 1000, 16)
conv1d_2 3×1 32 ( 1000, 32)
max_pooling1d_2 ( 500, 32)
batch_normalization_2 ( 500, 32)
flatten 16000
dropout 16000
dense 32 32
dense_2 4 4

程序如下

def mymodel():
    inputs = keras.Input(shape=(1000, 0))  
    h1 = layers.Conv1D(filters=8, kernel_size=3, strides=1, padding='same', activation='relu')(inputs)
    h1 = layers.MaxPool1D(pool_size=2, strides=1, padding='same')(h1)
    h1 = layers.BatchNormalization()(h1)

    h2 = layers.Conv1D(filters=16, kernel_size=3, strides=1, padding='same', activation='relu' )(h1)
    h2 = layers.MaxPool1D(pool_size=2, strides=1, padding='same')(h2)
    h2 = layers.BatchNormalization()(h2)

    h2 = layers.Conv1D(filters=32, kernel_size=3, strides=1, padding='same', activation='relu')(h2)
    h2 = layers.MaxPool1D(pool_size=2, strides=2, padding='same')(h2)
    h2 = layers.BatchNormalization()(h2)

    h3 = layers.Flatten()(h2)  # 扁平层,方便全连接层传入数据
    h4 = layers.Dropout(0.2)(h3)  # Droupt层舍弃百分之20的神经元

    h4 = layers.GaussianNoise(0.005)(h4)
    h4 = layers.Dense(32, activation='relu')(h4)  # 全连接层,输出为32
  
    outputs = layers.Dense(4, activation='softmax')(h4)  # 再来个全连接层,分类结果为4种
    deep_model = keras.Model(inputs, outputs, name='1DCNN')  # 整合每个层,搭建1DCNN模型成功
    return deep_model

2.模型训练超参数设置

   超参数通常指的是在机器学习算法训练的步骤开始之前设定的一些参数值,这些参数值通常是没办法通过算法本身来学会的------与其相对的就是在算法中可以学会或者学到的那些参数,例如权值w和偏置b.

批训练参数
   在训练神经网络时,需要对2个参数进行设置,即batch,epoch.
   首先是(batch)尺寸,一个批中的样本总数,即训练一次网络所用的样本数.训练网络时一次把所有的数据输入网络中计算量过大,我们一般把数据分成若干个批,按批传递给网络,并每一批传输后更新参数.这样的做有两个方面的优点,一方面是一批中的所有数据共同决定了本次梯度下降的方向,下降起来就不容易跑偏,减少了随机性;另一方面因为一批中的样本数与整个数据集相比小了很多,计算量也不是很大.
   而每一次读入的训练集数目称作批大小(Batch Size),在卷积神经网络中,大批次通常可使网络更快收敛,但由于内存资源的限制,批次过大可能会导致内存不够用或程序内核崩溃.bath_size通常取值为[16,32,64,128].
    其次,将所有样本数据投入神经网络模型进行一次训练称为1个Epoch.
   假设所有样本数为1000个,我们设置Batch Size为10个,即一次读入10个数据进行训练,则训练一轮数据需要读入100次才能训练完成.
   本次设计中Epoch设置为100,Batch Size设置为128.

损失函数
   损失函数是用来衡量模型预测结果与真实结果之间差异的函数.在机器学习中,我们通常使用损失函数来优化模型的参数,使得模型能够更好地拟合数据.常见的损失函数包括均方误差,交叉熵等.本次设计选择的是"sparse_categorical_accuracy".

学习率
   学习率(learning rate或作lr)是指在优化算法中更新网络权重的幅度大小.学习率是最影响性能的超参数之一,相比于其它超参数,学习率调整是一种更加有效控制着模型的有效容量的方式.因此,为了训练神经网络,其中一个需要设置的关键超参数是学习率.选择最优学习率是很重要的.
   学习率可以是恒定的,逐渐降低的,基于动量的或者是自适应的.不同的优化算法决定不同的学习率.当学习率过大则可能导致模型不收敛,损失loss不断上下震荡;学习率过小则导致模型收敛速度偏慢,需要更长的时间训练.通常 lr 取值为[0.01,0.001,0.0001].
   本次设计的学习率设置为0.001.

优化器
   当数据,模型和损失函数确定,任务的数学模型就已经确定,接着就要选择一个合适的优化器(Optimizer)对该模型进行优化.
   优化器基类提供了计算梯度loss的方法,并可以将梯度应用于变量.使用比较多的优化器是SGDM和Adam.(SGD)虽然收敛偏慢,但是加入动量Momentum可加快收敛,同时带动量的随机梯度下降算法有更好的最优解,即模型收敛后会有更高的准确性,SGDM在CV里面应用较多,而Adam则基本横扫NLP,RL,GAN,语音合成等领域.目前Adam是快速收敛且常被使用的优化器,比如NLP领域,Transformer,BERT这些经典模型均使用的Adam,及其变种AdamW.
   因此本次设计对学习率进行自适应学习的算法为Adam学习率优化算法.

3.模型性能评估指标

   衡量模型的优劣是深度学习中关键性的问题,模型的评估就是判断神经网络拟合出模型是否优秀.很多情况下,难以一眼判断出模型的好坏,所以出现了诸多模型评价指标,混淆矩阵是评判模型结果的指标之一.混淆矩阵衡量分类型模型准确度中最基本,最直观,计算最简单的方法.混淆矩阵分别统计分类模型归错类,归对类的观测值个数,然后将结果放在一个表里展示出来.以二分类模型为例,其混淆矩阵形式如图.

在这里插入图片描述


混淆矩阵图

 

4.模型训练

 
在确定了模型设计,与模型训练超参数设置后,开始进行模型训练.

实现代码如下

"""
本段本代码是进行模型训练

"""
import  os
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"] = "1"
#os.environ['TF_CPP_MIN_LOG_LEVEL']='2'
import tensorflow as tf
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
from tensorflow.keras import layers, models, Model, Sequential
import  tensorflow as tf
from    tensorflow import keras
from    tensorflow.keras import layers, Sequential
from numpy import random

#固定随机种子,在调用seed_tensorflow后还需设置model.fit中shuffle=False、worker=1.
#保证每次训练结果一致
def seed_tensorflow(seed=42):
    os.environ['PYTHONHASHSEED'] = str(seed)
    random.seed(seed)
    np.random.seed(seed)
    tf.random.set_seed(seed)
    os.environ['TF_DETERMINISTIC_OPS'] = '1' 
seed_tensorflow(42)




def read_csv_file(train_data_file_path, train_label_file_path):
    """
    读取csv文件并将文件进行拼接
    :param train_data_file_path: 训练数据路径
    :param train_label_file_path: 训练标签路径
    :return: 返回拼接完成后的路径
    """
    # 从csv中读取数据
    train_data = pd.read_csv(train_data_file_path, header=None)
    train_label = pd.read_csv(train_label_file_path, header=None)

    # 将数据集拼接起来
    # 数据与标签拼接
    dataset_train = pd.concat([train_data, train_label], axis=1)
    dataset = pd.concat([dataset_train], axis=0).sample(frac=1, random_state=0).reset_index(drop=True)
    return dataset


def get_train_test(dataset, data_ndim=1):
    # 获得训练数据和标签
    X_train = dataset.iloc[:, :-1]
    y_train = dataset.iloc[:, -1]

    # 为了增加模型的泛化能力,需要打乱数据集
    index = [i for i in range(len(X_train))]
    random.seed(42)
    random.shuffle(index)
    X_train = np.array(X_train)[index]
    y_train = np.array(y_train)[index]

    # 改变数据维度让他符合(数量,长度,维度)的要求
    X_train = np.array(X_train).reshape(X_train.shape[0], X_train.shape[1], data_ndim)

    print("X shape: ", X_train.shape)

    return X_train, y_train

# 保存最佳模型
class CustomModelCheckpoint(keras.callbacks.Callback):  # 使用回调函数来观察训练过程中网络内部的状态和统计信息r然后选取最佳的进行保存
    def __init__(self, model, path):  # (自定义初始化)
        self.model = model
        self.path = path
        self.best_loss = np.inf  # np.inf 表示+∞,是没有确切的数值的,类型为浮点型  自定义最佳损失数值

    def on_epoch_end(self, epoch, logs=None):  # on_epoch_end(self, epoch, logs=None)在每次迭代训练结束时调用。在不同的方法中这个logs有不同的键值
        val_loss = logs['val_loss']  # logs是一个字典对象directory;
        if val_loss < self.best_loss:
            print("\nValidation loss decreased from {} to {}, saving model".format(self.best_loss, val_loss))
            self.model.save_weights(self.path, overwrite=True)  # overwrite=True覆盖原有文件  # 此处为保存权重没有保存整个模型
            self.best_loss = val_loss



def mymodel():
    inputs = keras.Input(shape=(1000, 1))
    h1 = layers.Conv1D(filters=8, kernel_size=3, strides=1, padding='same', activation='relu')(inputs)
    h1 = layers.MaxPool1D(pool_size=2, strides=1, padding='same')(h1)
    h1 = layers.BatchNormalization()(h1)

    h2 = layers.Conv1D(filters=16, kernel_size=3, strides=1, padding='same', activation='relu')(h1)
    h2 = layers.MaxPool1D(pool_size=2, strides=1, padding='same')(h2)
    h2 = layers.BatchNormalization()(h2)

    h2 = layers.Conv1D(filters=32, kernel_size=3, strides=1, padding='same', activation='relu')(h2)
    h2 = layers.MaxPool1D(pool_size=2, strides=2, padding='same')(h2)
    h2 = layers.BatchNormalization()(h2)

    h3 = layers.Flatten()(h2)  # 扁平层,方便全连接层传入数据
    h4 = layers.Dropout(0.2)(h3)  # Droupt层舍弃百分之20的神经元

    h4 = layers.GaussianNoise(0.005)(h4)
    h4 = layers.Dense(32, activation='relu' )(h4)  # 全连接层,输出为32

    outputs = layers.Dense(4, activation='softmax')(h4)  # 再来个全连接层,分类结果为4种

    deep_model = keras.Model(inputs, outputs, name='1DCNN')  # 整合每个层,搭建1DCNN模型成功

    return deep_model



def bulid(X_train, y_train, X_test, y_test,X_val,y_val, batch_size=128, epochs=100):
    """
    搭建网络结构完成训练
    :param X_train: 训练集数据
    :param y_train: 训练集标签
    :param X_test: 测试集数据
    :param y_test: 测试集标签
    :param X_val: 验证集数据
    :param y_val: 验证集标签
    :param batch_size: 批次大小
    :param epochs: 循环轮数
    :return: acc和loss曲线
    """

    model = mymodel()
    model.compile(optimizer=tf.keras.optimizers.Adam(lr = 0.001,decay=1e-3),
                  loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
                  metrics=['sparse_categorical_accuracy'])

    history = model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size,
                        validation_data=(X_val,y_val),
                        workers =1,
                        callbacks=[CustomModelCheckpoint(model, r'mybestcnn_minmax_fft_4.h5')])
    keras.models.save_model(model,'./mycnn.h5')
    model.summary()
    # 获得训练集和测试集的acc和loss曲线
    acc = history.history['sparse_categorical_accuracy']
    val_acc = history.history['val_sparse_categorical_accuracy']
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    # 评估模型
    scores = model.evaluate(X_test, y_test, verbose=1)
    print('%s: %.2f%%' % (model.metrics_names[1], scores[1] * 100))
    y_predict = model.predict(X_test)
    y_pred_int = np.argmax(y_predict, axis=1)
    print(classification_report(y_test, y_pred_int, digits=4))

    # 绘制acc曲线
    plt.subplot(1, 2, 1)
    plt.plot(acc, label='Training Accuracy')
    plt.plot(val_acc, label='Validation Accuracy')
    plt.title('Training and Validation Accuracy')
    plt.legend()

    # 绘制loss曲线
    plt.subplot(1, 2, 2)
    plt.plot(loss, label='Training Loss')
    plt.plot(val_loss, label='Validation Loss')
    plt.title('Training and Validation Loss')
    plt.legend()
    plt.show()


    #  绘制混淆矩阵
    y_pred_gailv = model.predict(X_test, verbose=1)
    y_pred_int = np.argmax(y_pred_gailv, axis=1)
    con_mat = confusion_matrix(y_test.astype(str), y_pred_int.astype(str))
    con_mat = np.delete(con_mat, [0, 2, 4, 6], axis=0)
    con_mat = np.delete(con_mat, [1, 3, 5, 7], axis=1)
    classes = list(set(y_train))
    classes.sort()
    # plt.imshow(con_mat, cmap=plt.cm.Blues)
    plt.imshow(con_mat, cmap='Blues')
    indices = range(len(con_mat))
    plt.xticks(indices, classes)
    plt.yticks(indices, classes)
    plt.colorbar()
    plt.title('Confusion Matrix')
    plt.xlabel('guess')
    plt.ylabel('true')
    for first_index in range(len(con_mat)):
        for second_index in range(len(con_mat[first_index])):
            plt.text(first_index, second_index, con_mat[second_index][first_index], va='center', ha='center')
    plt.show()



if __name__ == "__main__":

    """
    频域数据集
    """
    #训练集
    x_train_csv_path = f'./makelabel/train_all_data.csv'
    y_train_csv_path = f'./makelabel/train_all_label.csv'
    dataset1 = read_csv_file(x_train_csv_path, y_train_csv_path)
    X_train,  y_train = get_train_test(dataset=dataset1, data_ndim=1)
    #测试集
    x_test_csv_path = f'./makelabel/test_all_data.csv'
    y_test_csv_path = f'./makelabel/test_all_label.csv'
    dataset2 = read_csv_file(x_test_csv_path, y_test_csv_path)
    X_test, y_test = get_train_test(dataset=dataset2, data_ndim=1)
    #验证集
    x_val_csv_path = f'./makelabel/val_all_data.csv'
    y_val_csv_path = f'./makelabel/val_all_label.csv'
    dataset3 = read_csv_file(x_val_csv_path, y_val_csv_path)
    X_val, y_val = get_train_test(dataset=dataset3, data_ndim=1)
    # 模型训练
    bulid(X_train, y_train, X_test, y_test,X_val,y_val)

5.模型训练结果与分析

5.1三类数据集训练结果

在这里插入图片描述

模型训练效果
 

从图中可以看出,三种数据验证集的准确率都能达到99%,
其中Q数据集的验证集准确率最高.
三种数据集在训练20个epoch后均达到了98%以上.

通过每种数据集中测试集测试后得到的混淆矩阵,分析模型对每种成熟度的检测效果,如表所示.
三种数据集的单个识别率都达到了99%,平均准确率也达到了99%,
以上数据,都说明了该分类模型很稳健,能较好的实现西瓜成熟度的检测.

数据集训练效果
F1-score
数据集
八分熟 成熟 过熟 偏生 平均
Q 0.9958 0.9972 0.9975 0.9954 0.9965
P 0.9950 0.9942 0.9948 0.9926 0.9941
T 0.9924 0.9955 0.9945 0.9925 0.9937
ALL 0.9935 0.9983 0.9973 0.9948 0.9960

 
为了测试不同的敲击方法混合使用时,模型的识别效果,将所有的数据融合使用,再进行训练.
训练曲线如图所示
经过100个epoch后模型的验证集准确率达到99.66%,loss收敛至0.02.

在这里插入图片描述下图为所有数据中测试集测试后得到的混淆矩阵.
该混淆矩阵表明,模型还是有些误判.
通过混淆矩阵可以得到模型在测试集上的平均准确率为99.78%
在这里插入图片描述

5.2对比模型性能

   为了对比模型的性能,选取一些经典卷积神经网络模型进行对比实验.
   所选取的卷积神经网络模型均为二维型,对其进行一维改编.
   本文所选取的卷积模型为ResNet18,MobileNet,AlexNet,VGGNet,GoogLeNet进行对比实验;在实验之前采用相同的训练集,学习率,批训练参数(epoch,Batch Size),优化器,损失函数,进行相同的训练过程.

各模型的识别结果如表所示

对比模型训练效果
识别模型 模型参数量 单个类别 平均
八分熟 成熟 过熟 偏生
1DCNN 514,516 0.9935 0.9983 0.9973 0.9948 0.9960
VGG16 41,619,780 0.9855 0.9967 0.9972 0.9855 0.9912
AlexNet 12,360,900 0.9757 0.9928 0.9913 0.9797 0.9849
GoogelNet 3,527,476 0.9792 0.9945 0.9938 0.9803 0.9870
ResNet18 3,856,772 0.9403 0.9741 0.9730 0.9587 0.9615
MobileNet 487,711 0.8765 0.9610 0.9607 0.9344 0.9332

最后
如文中有错误,欢迎评论区或私信指出.
初学者新手还在学习中,还望海涵~

猜你喜欢

转载自blog.csdn.net/tenju/article/details/131980298
今日推荐