AI学习[随堂笔记1117]_深度神经网络_模块化(内附代码)_数据集类/神经网络类/训练(测试)类

深度神经网络_模块化

建立“根据图片进行多分类”的感知机模型,并将组成此模型的三大重要部分进行分门别类,方便后续其他网络使用
同时预留足够的接口(如可调节的输入图像尺寸)和调试信息,提升泛用性

首先,将整个模型分为三部分:
①本地数据集获取
②感知机神经网络建立
③进行模型的训练/测试

下为三部分具体的代码实现和逻辑梳理
————————————————————————

第一部分,获取本地数据集

逻辑梳理
首先继承torch中的Dataset类,此类包含了很多数据集的操作,直接继承方便后续使用
接着在初始化函数中初始化此父类
再对本地数据集的路径(储存了图片、文件夹名就是标签)进行遍历
最后,将路径中的图片(路径形式)和标签(文件夹名)放入"列表[元组()]"结构中,防止被更改

def __init__(self, root, is_train):
        super(Datasets_V1, self).__init__()
        # 定义数据集
        self.dataset = []
        # 读取本地数据集的路径(图片+标签)
        self.path = root + "/" + ("TRAIN" if is_train else "TEST")
        # 抓取本地数据集的文件列表
        for i in os.listdir(self.path):
            for j in os.listdir(self.path + "/" + i):
                # 以(路径,标签)形式,填充数据集(以元祖填充,避免被修改)
                self.dataset.append((self.path + "/" + i + "/" + j, i))

–为外部访问数据留下接口(迭代)–
以元祖和类的形式存放的数据不便于外部直接调用
所以,我们对魔术方法__getitem__进行设置,使其能够**“返回归一化的图片数据+独热(one_hot)编码化的标签”**

注意点1:

img.reshape(-1)

参数只写-1,会只保留第一维度(0维)的形状、余下维度全部整合为1个维度
最终的效果即为“转换NCWH”
注意点2:

return np.float32(img), np.float32(one_hot)

返回数据时,要把两个数据都改为np的float32(32位浮点型),才能被后续的网络正常计算

 # 为外部(迭代)时留下接口
    def __getitem__(self, item):
        # 左侧小圆圈指:重写了父类的方法
        # 创建可被迭代的对象
        data = self.dataset[item]
        # 对数据集的数据进行归一化
        # 获取“数据”的路径,存放到L中
        # imread(路径,几通道?[0:单、1:三])
        img = cv2.imread(data[0], 0)
        # 改为单通道
        img = img.reshape(-1)  # 只写-1,会只保留第一维度(0维)的形状、余下维度全部整合为1个维度
        # 归一化
        img = img / 255

        # 对数据集的标签进行一位有效编码(one_hot)
        # 创建全为0的数组
        one_hot = np.zeros(10)
        # 使对应位置变为1
        one_hot[int(data[1])] = np.float(1)
        # 返回真正需要的对象(注意:需要为np.float32)
        return np.float32(img), np.float32(one_hot)

代码总览:

from torch.utils.data import Dataset
import os  # 引用os,操作本地数据集
import cv2  # 引用cv2操作图像
import numpy as np

class Datasets_V1(Dataset):
    def __init__(self, root, is_train):
        super(Datasets_V1, self).__init__()
        # 定义数据集
        self.dataset = []
        # 读取本地数据集的路径(图片+标签)
        self.path = root + "/" + ("TRAIN" if is_train else "TEST")
        # 抓取本地数据集的文件列表
        for i in os.listdir(self.path):
            for j in os.listdir(self.path + "/" + i):
                # 以(路径,标签)形式,填充数据集(以元祖填充,避免被修改)
                self.dataset.append((self.path + "/" + i + "/" + j, i))

    # 为外部获取(数据集长度)时留下接口
    def __len__(self):
        return len(self.dataset)

    # 为外部(迭代)时留下接口
    def __getitem__(self, item):
        # 左侧小圆圈指:重写了父类的方法
        # 创建可被迭代的对象
        data = self.dataset[item]
        # 对数据集的数据进行归一化
        # 获取“数据”的路径,存放到L中
        # imread(路径,几通道?[0:单、1:三])
        img = cv2.imread(data[0], 0)
        # 改为单通道
        img = img.reshape(-1)  # 只写-1,会只保留第一维度(0维)的形状、余下维度全部整合为1个维度
        # 归一化
        img = img / 255

        # 对数据集的标签进行一位有效编码(one_hot)
        # 创建全为0的数组
        one_hot = np.zeros(10)
        # 使对应位置变为1
        one_hot[int(data[1])] = np.float(1)
        # 返回真正需要的对象(注意:需要为np.float32)
        return np.float32(img), np.float32(one_hot)

————————————————————————

第二部分,建立感知机神经网络

逻辑梳理
引用nn类来快捷进行感知机神经网络的建立
为了后续兼容复杂的网络,此网络的深度较深

代码总览:

from torch import nn

class Deep_Net_V1(nn.Module):
	# 在初始化时,设置两个传入参数,分别定义网络的传入值尺寸和输出值尺寸
    def __init__(self, input_size, output_size):
    	# 调用父类的初始化,真正初始化一个神经网络对象
        super(Deep_Net_V1, self).__init__()
        self.layer = nn.Sequential(
            # 要求:十几层
            nn.Linear(input_size, 1024),
            # 注意:实际上,每一个数据层都可以加上一个激活函数(效果是影响下一层的输入),也确实可以提高效率
            # 在层级之间加上”合适“的激活函数可以让降低层级的线性度,降低取值范围
            nn.ReLU(),
            nn.Linear(1024, 2048),
            nn.ReLU(),
            nn.Linear(2048, 1024),
            nn.ReLU(),
            nn.Linear(1024, 2048),
            nn.ReLU(),
            nn.Linear(2048, 1024),
            nn.Linear(1024, 2048),  # 注意:Linear的参数必须是(上层的感知机数量,这层感知机数)。一定要对应!
            nn.Linear(2048, 1024),
            nn.Linear(1024, 2048),
            nn.Linear(2048, 1024),
            nn.Linear(1024, 2048),
            nn.ReLU(),
            nn.Linear(2048, 1024),
            nn.ReLU(),
            nn.Linear(1024, 2048),
            nn.ReLU(),
            nn.Linear(2048, 1024),
            nn.ReLU(),
            nn.Linear(1024, 2048),
            nn.Linear(2048, 1024),
            nn.Linear(1024, 512),
            nn.Linear(512, 128),
            nn.Linear(128, 64),
            nn.Linear(64, 32),
            nn.Linear(32, 16),
            nn.Linear(16, output_size),
            # 传入的是NV结构(NHWC结构),使用Softmax激活其中的V(体积),也就是第1维度
            nn.Softmax(dim=1)  # dim=1代表”操作的维度为1“,即会输出第一维度的概率结果
            # 与之对应的是第0维度,这时操作的数值是“批次”(N)。无实际意义
            # nn.Sigmoid()  # 也可以使用sigmoid激活
        )
	# 定义前向传输的过程
    def forward(self, x):
    	# 直接调用layer(实际上调用的是Sequential),使它处理传入的X图像数据
        return self.layer(x)

————————————————————————

第三部分,进行模型的训练/测试

逻辑梳理
加载之前写好的两部分模组、以及"数据加载器类DataLoader"——解决数据集太大加载缓慢的问题

注意点1:
建立神经网络时,可以直接通过加载器中,数据的shape(形状)来确定输入值/输出值的大小

		# 获取输入数据(图像)
        # 获取训练集_加载器中,第1号数据的第1个数据。再分析他的形状,即为输入(图片)大小
        img_size = self.train_loader.dataset[0][0].shape[0]
        # 获取训练集_加载器中,第1号数据的第2个数据。再分析他的形状,即为输出(标签)大小
        label_size = self.train_loader.dataset[0][1].shape[0]

注意点2:
选择驱动时,根据当前环境CUDA的安装情况来决定使用的驱动类型

# 确定使用的驱动(是CUDA还是CPU)
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        # 选择使用的驱动
        self.net.to(self.device)

注意点3:
开始训练/测试时,一次如下的循环的具体执行次数为:
一个数据池中的数据数 / 总数据数
如:一个由10000张图片组成的数据集,一次选取200张放入数据池
那么他在一次for……enumerate……中的循环次数为:200 / 10000 = 50(次)

for i, (img, label) in enumerate(self.train_loader):

注意点3:
反向传播三定式:清空梯度/反向传播/步进更新参数

# 反向传播三定式:清空梯度/反向传播/步进更新参数
                self.opt.zero_grad()
                loss.backward()
                self.opt.step()

代码总览:

# 训练器
import torch.cuda
from torch.utils.data import DataLoader  # 引用数据集加载器类(避免数据集太大加载缓慢的问题)
from torch import optim  # 优化参数、反向传播模块
import os

from nn_class import Deep_Net_V1  # 加载自定义神经网络类
from datasets_class import Datasets_V1  # 加载自定义数据集类


class Train_MNIST_Local:
    def __init__(self, sample_img_url="MNIST_IMG"):
        # 先定义数据集
        self.train_dataset = Datasets_V1(sample_img_url, is_train=True)
        self.train_loader = DataLoader(self.train_dataset, batch_size=256, shuffle=True)
        self.test_dataset = Datasets_V1(sample_img_url, is_train=False)
        self.test_loader = DataLoader(self.test_dataset, batch_size=256, shuffle=True)

        # 获取输入数据(图像)
        # 获取训练集_加载器中,第1号数据的第1个数据。再分析他的形状,即为输入(图片)大小
        img_size = self.train_loader.dataset[0][0].shape[0]
        # 获取训练集_加载器中,第1号数据的第2个数据。再分析他的形状,即为输出(标签)大小
        label_size = self.train_loader.dataset[0][1].shape[0]

        # 再定义网络
        self.net = Deep_Net_V1(img_size, label_size)
        # 确定使用的驱动(是CUDA还是CPU)
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        # 选择使用的驱动
        self.net.to(self.device)

        # 优化器
        # 优化器不止Adam,还有SGD等等
        self.opt = optim.Adam(self.net.parameters())

    # 魔法方法,开始训练
    def __call__(self, *args, **kwargs):
        # 创建存放参数表文件的文件夹
        os.makedirs("param", exist_ok=True)  # 创建存放参数的文件夹

        # 进训练循环100 * 60000
        for epoch in range(100):
            print("第" + str(epoch + 1) + "次训练")
            sum_loss = 0
            for i, (img, label) in enumerate(self.train_loader):
                # 进训练模式(可省略
                self.net.train()
                # 把数据放入驱动中(cuda、cpu)
                img, label = img.to(self.device), label.to(self.device)
                # 进行一次前向传播、得到结果out
                out = self.net(img)
                # 计算损失(使用自定义的均方差)
                loss = torch.mean((out - label) ** 2)
                sum_loss += loss.item()  # !!!!item()

                # 反向传播三定式:清空梯度/反向传播/步进更新参数
                self.opt.zero_grad()
                loss.backward()
                self.opt.step()

            # 保存网络参数表(后缀无所谓)
            torch.save(self.net.state_dict(), "param/" + str(epoch + 1) + ".pt")
            # 计算平均损失
            avg_loss = sum_loss / len(self.train_loader)
            print("第" + str(epoch + 1) + "次训练的平均损失:" + str(avg_loss))

    def test_net(self):
        for epoch in range(10):
            print("第" + str(epoch + 1) + "次测试")
            sum_loss = 0
            for i, (img, label) in enumerate(self.test_loader):
                # 进测试模式
                self.net.eval()
                img, label = img.to(self.device), label.to(self.device)

                # 读取参数文件,读取最后一个
                file_list = os.listdir("param")
                file_list.sort(key=lambda x: int(x[:-3]))  # 倒着数第三位开始(.pt往后),按数字的从小到大排序
                last_param = file_list[len(file_list) - 1]  # 最后的参数文件名(含后缀)
                self.net.load_state_dict(torch.load("param/" + last_param))

                out = self.net(img)
                loss = torch.mean((out - label) ** 2)

                sum_loss += loss.item()
            avg_loss = sum_loss / len(self.test_loader)
            print("第" + str(epoch + 1) + "次测试的平均损失:" + str(avg_loss))


if __name__ == '__main__':
    train = Train_MNIST_Local()
    train()
    train.test_net()

你可能感兴趣的:(深度学习,笔记,python,人工智能,神经网络,学习)