实战--假钞识别

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,点击查看活动详情

基于DNN的假钞识别

项目目录

一个项目需要一个清晰的目录来保证各个模块功能的正确放置,下面是经过学习得到的一个目录结构:

data目录 存放原始数据与预处理后切分为训练集、验证机、测试集的数据
log目录 训练过程中使用tensorboardX保存的指标数值,如损失、精确度等
model_save目录 存放不同训练阶段的模型,最后找出个最优的用于测试集
config.py 保存超参数
dataset_banknote.py Banknote数据类,用于训练时获取数据
inference.py 挑选模型在测试集上运行
model.py 算法模型
preprocess.py 对原始数据进行预处理,划分为训练集、验证集、测试集
trainer.py 模型训练代码
data目录 存放数据集
utils.py 工具类文件

项目配置

项目参数配置分三种:

  • 数据配置(Data):配置相关的路径,维度,随机种子等。
  • 模型配置(Model):配置不同层的模型大小,或者相关模型参数。
  • 实验配置(Experiment):配置一些训练过程中的超参数。

配置文件可以选择 yaml 格式,这里会推荐选用 python 脚本,后续使用方便简洁,不需要折腾yaml文件的读取等过程。

# banknote classification config

# 超参配置
# yaml
class Hyperparameter:
    # ################################################################
    #                             Data
    # ################################################################
    device = 'cpu'  # cuda
    data_dir = './data/'  # 目录
    data_path = './data/data_banknote_authentication.txt'  # 原始数据集的路径
    trainset_path = './data/train.txt'
    devset_path = './data/dev.txt'
    testset_path = './data/test.txt'

    in_features = 4  # input feature dim 输入维度
    out_dim = 2  # output feature dim (classes number) 输出维度
    seed = 1234  # random seed

    # ################################################################
    #                             Model Structure
    # ################################################################
    layer_list = [in_features, 64, 128, 64, out_dim]
    # ################################################################
    #                             Experiment
    # ################################################################
    batch_size = 64  # 一次取64条数据
    init_lr = 1e-3  # 学习率
    epochs = 100  # 训练轮数
    verbose_step = 10 #每隔的步数,进行损失值记录。(开发数据集)
    save_step = 200 # 每隔的步数,对模型进行记录


HP = Hyperparameter()

复制代码

数据处理

数据处理这里涉及到数据清洗,数据校验等过程,这次实战使用的数据集是 Banknote Dataset 数据集。

Banknote Dataset 数据集:从纸币鉴别过程中的图像里提取的数据,用来预测钞票的真伪的数据集。每个样本由5个数值型变量构成,4个输入变量和1个输出变量。部分样本如下:

image.png

每一行的5个(列)变量含义如下:

第一列:图像经小波变换后的方差(variance)(连续值);

第二列:图像经小波变换后的偏态(skewness)(连续值);

第三列:图像经小波变换后的峰度(curtosis)(连续值);

第四列:图像的熵(entropy)(连续值);

第五列:钞票所属的类别(整数,0或1)。

下载地址:archive.ics.uci.edu/ml/datasets…

因为该数据集已经处理完成了,并且不需要进行数据清洗过程,所以这里直接开始其他流程。

我们拿到了假钞的数据集,会使用交叉验证方法,该数据集分成根据 0.7,0.2,0.1 比例分成三种数据集:训练数据集,开发数据集,测试数据集。在这个过程中需要对数据集进行打乱,保证取数据随机。将三种数据集分成三个文件:train.txtdev.txttest.txt

import numpy as np
from config import HP
import os

# 训练集
trainset_ratio = 0.7
# 测试集
devset_ratio = 0.2
# 验证集
testset_ratio = 0.1

# 随机种子
np.random.seed(HP.seed)
# 读取文件内容
dataset = np.loadtxt(HP.data_path, delimiter=',')
print(dataset)
# 对文件内容进行洗牌
np.random.shuffle(dataset)

# 获取数据行数
n_items = dataset.shape[0]

# 获取训练集行数
trainset_num = int(trainset_ratio * n_items)
devset_num = int(devset_ratio * n_items)
testset_num = n_items - trainset_num - devset_num

# 将数据保存在不同的文件里面
np.savetxt(os.path.join(HP.data_dir, 'train.txt'),
           dataset[:trainset_num], delimiter=',')
np.savetxt(os.path.join(HP.data_dir, 'dev.txt'),
           dataset[trainset_num:devset_num + trainset_num], delimiter=',')
np.savetxt(os.path.join(HP.data_dir, 'test.txt'),
           dataset[devset_num + trainset_num:], delimiter=',')

复制代码

数据读取

构建数据集类,方便训练模型进行使用,一般需要重写的方法有

  • __init__:从文件中加载数据集。
  • __getitem__:设置数据读取的格式以及获取格式。
  • __len__:获取数据长度

一般常用的是这三种,其余根据具体要求而定。

import torch
from config import HP
import numpy as np


class BanknoteDataset(torch.utils.data.Dataset):
    def __init__(self, data_path):
        # 从文件读取数据
        self.dataset = np.loadtxt(data_path, delimiter=',')

    def __getitem__(self, idx):
        # 获取其中一个样本
        item = self.dataset[idx]
        # 将样本分开:参数与结果
        x, y = item[:HP.in_features], item[HP.in_features:]
        return torch.Tensor(x).float().to(HP.device), torch.Tensor(y).squeeze().long().to(HP.device)

    def __len__(self):
        return self.dataset.shape[0]
复制代码

模型设置

对模型进行设置,根据题目进行分析这是一个二分类问题,我们选择的模型是MLP模型(多层感知机),层数不多一共5层,三层隐藏层分别是64,128,64。激活函数使用 relu 函数。

在这里插入图片描述

from torch import nn
from torch.nn import functional as F
from config import HP


# 设置模型
class BanknoteClassificationModel(nn.Module):
    def __init__(self):
        super(BanknoteClassificationModel, self).__init__()
        # 定义模型 列表
        self.linear_layer = nn.ModuleList(
            [nn.Linear(in_features=in_dim, out_features=out_dim) for in_dim, out_dim in zip(HP.layer_list[:-1], HP.layer_list[1:])])

    def forward(self, input_x):
        """前向计算"""
        for layer in self.linear_layer:
            # 先进行线性计算,然后增加激活函数
            input_x = layer(input_x)
            input_x = F.relu(input_x)
        return input_x

复制代码

模型训练

模型训练过程:

  • 准备:获取模型,获取损失函数,加载数据集,设置损失函数,定义优化器
    # 定义模型
    model = BanknoteClassificationModel()
    model = model.to(HP.device)

    # 定义损失函数:交叉熵
    criterion = nn.CrossEntropyLoss()
    # 定义优化器
    opt = optim.Adam(model.parameters(), lr=HP.init_lr)
    # train dataloader数据获取
    trainset = BanknoteDataset(HP.trainset_path)
    # 设置数据加载器:数据集 设置每次取数据行数,是否打乱,多余部分丢弃
    train_loader = DataLoader(
        trainset, batch_size=HP.batch_size, shuffle=True, drop_last=True)
    # dev dataloader数据获取
    devset = BanknoteDataset(HP.devset_path)
    # 设置数据加载器:数据集 设置每次取数据行数,是否打乱,(不涉及训练选择不丢弃)
    dev_loader = DataLoader(
        devset, batch_size=HP.batch_size, shuffle=True, drop_last=False)
复制代码
  • 开始训练:取每一批次的数据,训练流程如下:
    • 梯度置0。
    • 获取预测值与实际值
    • 计算损失值
    • 损失反向求导
    • 更新模型
for epoch in range(start_epoch, HP.epochs):
        print("Start Epoch:%d,Steps: %d" %
              (epoch, len(train_loader) / HP.batch_size))
        # 取每一批次的数据
        for batch in train_loader:
            x, y = batch
            opt.zero_grad()  # graninit clean
            pred = model(x)  # forward process
            loss = criterion(pred, y)  # loss calc
            loss.backward()  # 反向求导
            opt.step()  # 更新模型
            # 记录每次训练的损失值
            logger.add_scalar('loss/Train', loss, step)
复制代码
  • 额外配置:在特定的点记录模型,记录训练损失值和开发损失值。
 # 测试时候,每verbose_step次记录损失值
            if not step % HP.verbose_step:
                eval_loss = evaluate(model, dev_loader, criterion)
                logger.add_scalar('Loss/Dev', eval_loss, step)

            # 在 save_step 次进行存档
            if not step % HP.save_step:
                model_path = 'model_%d_%d.pth' % (epoch, step)
                save_checkpoint(model, epoch, opt, os.path.join(
                    'model_save', model_path))

            step += 1
            logger.flush()
            print('Epoch:[%d/%d],step:%d Train Loss:%.5f.Dev Loss:%0.5f' %
                  (epoch, HP.epochs, step, loss.item(), eval_loss))
复制代码

模型评估和选择

在相关文件下命令行下输入 tensorboard --logdir=./ 就可以得到一个网站,可以查看相关的模型生成。

image.png

由上图可以得知:在600步时候,模型已经是比较好的模型了,200步处于欠拟合,1.4K步会存在过拟合,所以后面测试时候,应该选用600步存储的模型。

测试

根据选择训练好的模型,进行对模型加载相关参数。加载测试数据集,开始进行测试记录正确数据个数,得到预测准确率。

步骤:

  • 加载合适的模型。
  • 获取测试集
  • 开始进行测试与验证,得出结果。
import torch
from config import HP
from dataset_banknote import BanknoteDataset
from model import BanknoteClassificationModel
from torch.utils.data import DataLoader
# 加载模型
model = BanknoteClassificationModel()

# 选择优化后的模型开始进行测试
checkpoint = torch.load('./model_save/model_40_600.pth')
model.load_state_dict(checkpoint['model_state_dict'])

# 获取测试集
testset = BanknoteDataset(HP.testset_path)
test_loader = DataLoader(
    testset, batch_size=HP.batch_size, shuffle=True, drop_last=False)

model.eval()


total_cnt = 0  # 总共数据个数
correct_cnt = 0  # 正确数据个数
with torch.no_grad():
    for batch in test_loader:
        x, y = batch
        pred = model(x)  # 开始测试
        total_cnt += pred.size(0)
        correct_cnt += (torch.argmax(pred, 1) == y).sum()

print('Acc:%.3f' % (correct_cnt / total_cnt))
# Acc:1.000
复制代码

猜你喜欢

转载自juejin.im/post/7128046201462603790