基于ResNetRS的宝可梦图像识别

简介: ResNetRS是在ResNet-D架构上面的改进

1、ResNet-D架构

  ResNetRS是在ResNet-D架构上面的改进,ResNet-D架构的结构如下:

image-20220907220047082

  注意,残差边上多了个池化操作。

2、ResNetRS架构

  我们提供了有关 ResNet-RS 架构更改的更多详细信息。我们重申 ResNet-RS 是:改进的缩放策略、改进的训练方法、ResNet-D 修改(He 等人,2018 年)和 SqueezeExcitation 模块(Hu 等人,2018 年)的组合。

  表 11 显示了我们工作中使用的所有 ResNet 深度的块布局。 ResNet-50 到 ResNet-200 使用 He 等人的标准块配置。 (2015 年)。 ResNet-270 及更高版本主要扩展 c3 和 c4 中的块数,我们尝试保持它们的比例大致恒定。我们凭经验发现,在较低阶段添加块会限制过度拟合,因为较低层中的块具有显着较少的参数,即使所有块具有相同数量的 FLOP。图 6 显示了我们的 ResNet-RS 模型中使用的 ResNet-D 架构更改。

image-20220907221339109

   图 6. ResNet-RS 架构图。

    输出大小假定输入图像分辨率为 224×224。

    在卷积布局中,x2 是指第一个 3×3 卷积,步长为 2。

    ResNet-RS 架构是 Squeeze-and-Excitation 和 ResNet-D 的简单组合。

    × 符号表示块在 ResNet-101 架构中重复的次数。这些值根据表 11 中的块布局随深度变化。

3、手动搭建模型(Tensorflow)

   这里只是介绍网络的搭建方法,训练的时候我们直接使用迁移学习去做,用这个从头训练太慢了。
import tensorflow as tf
from tensorflow.keras import layers
from tensorflow.keras.models import Model
from typing import Callable, Dict, List, Union

3.1 模型配置项

DEPTH_TO_WEIGHT_VARIANTS = {
    50: [160],
    101: [160, 192],
    152: [192, 224, 256],
    200: [256],
    270: [256],
    350: [256, 320],
    420: [320],
}
BLOCK_ARGS = {
    50: [
        {
            "input_filters": 64,
            "num_repeats": 3
        },
        {
            "input_filters": 128,
            "num_repeats": 4
        },
        {
            "input_filters": 256,
            "num_repeats": 6
        },
        {
            "input_filters": 512,
            "num_repeats": 3
        },
    ],
    101: [
        {
            "input_filters": 64,
            "num_repeats": 3
        },
        {
            "input_filters": 128,
            "num_repeats": 4
        },
        {
            "input_filters": 256,
            "num_repeats": 23
        },
        {
            "input_filters": 512,
            "num_repeats": 3
        },
    ],
    152: [
        {
            "input_filters": 64,
            "num_repeats": 3
        },
        {
            "input_filters": 128,
            "num_repeats": 8
        },
        {
            "input_filters": 256,
            "num_repeats": 36
        },
        {
            "input_filters": 512,
            "num_repeats": 3
        },
    ],
    200: [
        {
            "input_filters": 64,
            "num_repeats": 3
        },
        {
            "input_filters": 128,
            "num_repeats": 24
        },
        {
            "input_filters": 256,
            "num_repeats": 36
        },
        {
            "input_filters": 512,
            "num_repeats": 3
        },
    ],
    270: [
        {
            "input_filters": 64,
            "num_repeats": 4
        },
        {
            "input_filters": 128,
            "num_repeats": 29
        },
        {
            "input_filters": 256,
            "num_repeats": 53
        },
        {
            "input_filters": 512,
            "num_repeats": 4
        },
    ],
    350: [
        {
            "input_filters": 64,
            "num_repeats": 4
        },
        {
            "input_filters": 128,
            "num_repeats": 36
        },
        {
            "input_filters": 256,
            "num_repeats": 72
        },
        {
            "input_filters": 512,
            "num_repeats": 4
        },
    ],
    420: [
        {
            "input_filters": 64,
            "num_repeats": 4
        },
        {
            "input_filters": 128,
            "num_repeats": 44
        },
        {
            "input_filters": 256,
            "num_repeats": 87
        },
        {
            "input_filters": 512,
            "num_repeats": 4
        },
    ],
}
CONV_KERNEL_INITIALIZER = {
    "class_name": "VarianceScaling",
    "config": {
        "scale": 2.0,
        "mode": "fan_out",
        "distribution": "truncated_normal"
    },
}
这里只搭建ResNet-RS101架构

3.2 get_survival_probability

  根据区块数和初始速率获取生存概率

def get_survival_probability(init_rate, block_num, total_blocks):
    return init_rate * float(block_num) / total_blocks

3.3 fixed_padding

def fixed_padding(inputs, kernel_size):
    """沿空间维度填充输入,与输入大小无关"""
    pad_total = kernel_size - 1
    pad_beg = pad_total // 2
    pad_end = pad_total - pad_beg

    # 使用 ZeroPadding 来避免 TFOpLambda 层
    padded_inputs = layers.ZeroPadding2D(
        padding=((pad_beg, pad_end), (pad_beg, pad_end)))(inputs)

    return padded_inputs

3.4 Conv2DFixedPadding

Conv2D block with fixed padding

def Conv2DFixedPadding(filters, kernel_size, strides, name=None):

def apply(inputs):
    if strides > 1:
        inputs = fixed_padding(inputs, kernel_size)
    return layers.Conv2D(filters=filters,
                         kernel_size=kernel_size,
                         strides=strides,
                         padding='same' if strides == 1 else 'valid',
                         use_bias=False,
                         kernel_initializer=CONV_KERNEL_INITIALIZER,
                         name=name)(inputs)

return apply

3.5 STEM块

# ResNet-D型STEM块
def STEM(inputs,
         bn_momentum: float = 0.0,
         bn_epsilon: float = 1e-5,
         activation: str = 'relu',
         name=None):
    # first stem block
    x = Conv2DFixedPadding(filters=32,
                           kernel_size=3,
                           strides=2,
                           name=name + '_stem_conv_1')(inputs)
    x = layers.BatchNormalization(momentum=bn_momentum,
                                  epsilon=bn_epsilon,
                                  name=name + '_stem_batch_norm_1')(x)
    x = layers.Activation(activation, name=name + '_stem_act_1')(x)

    # second stem block
    x = Conv2DFixedPadding(filters=32,
                           kernel_size=3,
                           strides=1,
                           name=name + '_stem_conv_2')(x)
    x = layers.BatchNormalization(momentum=bn_momentum,
                                  epsilon=bn_epsilon,
                                  name=name + '_stem_batch_norm_2')(x)
    x = layers.Activation(activation, name=name + '_stem_act_2')(x)

    # final stem block
    x = Conv2DFixedPadding(filters=64,
                           kernel_size=3,
                           strides=1,
                           name=name + '_stem_conv_3')(x)
    x = layers.BatchNormalization(momentum=bn_momentum,
                                  epsilon=bn_epsilon,
                                  name=name + '_stem_batch_norm_3')(x)
    x = layers.Activation(activation, name=name + '_stem_act_3')(x)

    # Replace stem max pool:
    x = Conv2DFixedPadding(filters=64,
                           kernel_size=3,
                           strides=2,
                           name=name + '_stem_conv_4')(x)
    x = layers.BatchNormalization(momentum=bn_momentum,
                                  epsilon=bn_epsilon,
                                  name=name + 'stem_batch_norm_4')(x)
    x = layers.Activation(activation, name=name + '_stem_act_4')(x)

    return x

3.6 SE注意力机制模块

def SE(inputs,
       in_filters: int,
       se_ratio: float = 0.25,
       expand_ratio: int = 1,
       name=None):
    x = layers.GlobalAveragePooling2D(name=name + '_se_squeeze')(inputs)

    se_shape = (1, 1, x.shape[-1])
    x = layers.Reshape(se_shape, name=name + '_se_reshape')(x)

    num_reduced_filters = max(1, int(in_filters * 4 * se_ratio))

    x = layers.Conv2D(filters=num_reduced_filters,
                      kernel_size=(1, 1),
                      strides=[1, 1],
                      kernel_initializer=CONV_KERNEL_INITIALIZER,
                      padding='same',
                      use_bias=False,
                      activation='relu',
                      name=name + '_se_reduce')(x)
    x = layers.Conv2D(filters=4 * in_filters * expand_ratio,  # Expand ratio is 1 by default
                      kernel_size=[1, 1],
                      strides=[1, 1],
                      kernel_initializer=CONV_KERNEL_INITIALIZER,
                      padding='same',
                      use_bias=False,
                      activation='sigmoid',
                      name=name + '_se_expand')(x)
    out = layers.multiply([inputs, x], name=name + '_se_excite')
    return out

3.7 Bottleneck Block

def BottleneckBlock(filters: int,
                    strides: int,
                    use_projection: bool,
                    bn_momentum: float = 0.0,
                    bn_epsilon: float = 1e-5,
                    activation: str = 'relu',
                    se_ratio: float = 0.25,
                    survival_probability: float = 0.8,
                    name=None):
    # 带有BN的残差网络的bottle block变体
    def apply(inputs):

        shortcut = inputs
        # 是否需要projection shortcut
        if use_projection:
            filters_out = filters * 4
            if strides == 2:
                shortcut = layers.AveragePooling2D(pool_size=(2, 2),
                                                   strides=(2, 2),
                                                   padding='same',
                                                   name=name + '_projection_pooling')(inputs)
                shortcut = Conv2DFixedPadding(filters=filters_out,
                                              kernel_size=1,
                                              strides=1,
                                              name=name + '_projection_conv')(shortcut)
            else:
                shortcut = Conv2DFixedPadding(filters=filters_out,
                                              kernel_size=1,
                                              strides=strides,
                                              name=name + '_projection_conv')(inputs)
                shortcut = layers.BatchNormalization(momentum=bn_momentum,
                                                     epsilon=bn_epsilon,
                                                     name=name + '_projection_batch_norm')(shortcut)

        # first conv layer:1x1 conv
        x = Conv2DFixedPadding(filters=filters,
                               kernel_size=1,
                               strides=1,
                               name=name + '_conv_1')(inputs)
        x = layers.BatchNormalization(momentum=bn_momentum,
                                      epsilon=bn_epsilon,
                                      name=name + 'batch_norm_1')(x)
        x = layers.Activation(activation, name=name + '_act_1')(x)

        # second conv layer:3x3 conv
        x = Conv2DFixedPadding(filters=filters,
                               kernel_size=3,
                               strides=strides,
                               name=name + '_conv_2')(x)
        x = layers.BatchNormalization(momentum=bn_momentum,
                                      epsilon=bn_epsilon,
                                      name=name + '_batch_norm_2')(x)
        x = layers.Activation(activation, name=name + '_act_2')(x)

        # third conv layer:1x1 conv
        x = Conv2DFixedPadding(filters=filters * 4,
                               kernel_size=1,
                               strides=1,
                               name=name + '_conv_3')(x)
        x = layers.BatchNormalization(momentum=bn_momentum,
                                      epsilon=bn_epsilon,
                                      name=name + '_batch_norm_3')(x)

        if 0 < se_ratio < 1:
            x = SE(x, filters, se_ratio=se_ratio, name=name + '_se')

        # Drop connect
        if survival_probability:
            x = layers.Dropout(survival_probability,
                               noise_shape=(None, 1, 1, 1),
                               name=name + '_drop')(x)
        x = layers.Add()([x, shortcut])

        return layers.Activation(activation, name=name + '_output_act')(x)

    return apply

3.8 Block Group

def BlockGroup(filters,
               strides,
               num_repeats,  # Block重复次数
               se_ratio: float = 0.25,
               bn_epsilon: float = 1e-5,
               bn_momentum: float = 0.0,
               activation: str = "relu",
               survival_probability: float = 0.8,
               name=None):
    """Create one group of blocks for the ResNet model."""

    def apply(inputs):
        # 只有每个block_group的第一个block块使用projection shortcut和strides
        x = BottleneckBlock(
            filters=filters,
            strides=strides,
            use_projection=True,
            se_ratio=se_ratio,
            bn_epsilon=bn_epsilon,
            bn_momentum=bn_momentum,
            activation=activation,
            survival_probability=survival_probability,
            name=name + "_block_0_",
        )(inputs)

        for i in range(1, num_repeats):
            x = BottleneckBlock(
                filters=filters,
                strides=1,
                use_projection=False,
                se_ratio=se_ratio,
                activation=activation,
                bn_epsilon=bn_epsilon,
                bn_momentum=bn_momentum,
                survival_probability=survival_probability,
                name=name + f"_block_{i}_",
            )(x)
        return x

    return apply

3.9 ResNetRS

# 构建ResNet-RS模型:这里复现ResNet-RS101
def ResNetRS(depth: int,  # ResNet网络的深度,101:[160,192]
             input_shape=None,
             bn_momentum=0.0,  # BN层的动量参数
             bn_epsilon=1e-5,  # BN层的Epsilon参数
             activation: str = 'relu',  # 激活函数
             se_ratio=0.25,  # 挤压和激发曾的比例
             dropout_rate=0.25,  # 最终分类曾之前的dropout
             drop_connect_rate=0.2,  # skip connection的丢失率
             include_top=True,  # 是否在网络顶部包含全连接层
             block_args: List[Dict[str, int]] = None,  # 字典列表,构造块模块的参数
             model_name='resnet-rs',  # 模型的名称
             pooling=None,  # 可选的池化模式
             weights='imagenet',
             input_tensor=None,
             classes=1000,  # 分类数
             classifier_activation: Union[str, Callable] = 'softmax',  # 分类器激活
             include_preprocessing=True):  # 是否包含预处理层(对输入图像通过ImageNet均值和标准差进行归一化):

    img_input = layers.Input(shape=input_shape)
    x = img_input
    inputs = img_input

    # 这里本来有个预处理判断,tensorflow版本太低。
    # if include_preprocessing:
    #     num_channels=input_shape[-1]
    #     if num_channels==3:
    #         # 预处理

    # Build stem
    x = STEM(x, bn_momentum=bn_momentum, bn_epsilon=bn_epsilon, activation=activation, name='stem')

    # Build blocks
    if block_args is None:
        block_args = BLOCK_ARGS[depth]

    for i, args in enumerate(block_args):
        # print(i,args)
        survival_probability = get_survival_probability(init_rate=drop_connect_rate,
                                                        block_num=i + 2,
                                                        total_blocks=len(block_args) + 1)
        # args['input_filters']=[64,128,256,512]
        # 只有第一个BlockGroup的stride=1,后面三个都是stride=2
        x = BlockGroup(filters=args['input_filters'],
                       activation=activation,
                       strides=(1 if i == 0 else 2),
                       num_repeats=args['num_repeats'],
                       se_ratio=se_ratio,
                       bn_momentum=bn_momentum,
                       bn_epsilon=bn_epsilon,
                       survival_probability=survival_probability,
                       name=f"BlockGroup{i + 2}_")(x)
    # Build head:
    if include_top:
        x = layers.GlobalAveragePooling2D(name='avg_pool')(x)
        if dropout_rate > 0:
            x = layers.Dropout(dropout_rate, name='top_dropout')(x)
        x = layers.Dense(classes, activation=classifier_activation, name='predictions')(x)
    else:
        if pooling == 'avg':
            x = layers.GlobalAveragePooling2D(name='avg_pool')(x)
        elif pooling == 'max':
            x = layers.GlobalMaxPooling2D(name='max_pool')(x)

    # Create model
    model = Model(inputs, x, name=model_name)

    return model

3.10 ResNetRS101架构

# build ResNet-RS101 model
def ResNetRS101(include_top=True,
                weights='imagenet',
                classes=1000,
                input_shape=None,
                input_tensor=None,
                pooling=None,
                classifier_activation='softmax',
                include_preprocessing=True):
    return ResNetRS(depth=101,
                    include_top=include_top,
                    drop_connect_rate=0.0,
                    dropout_rate=0.25,
                    weights=weights,
                    classes=classes,
                    input_shape=input_shape,
                    input_tensor=input_tensor,
                    pooling=pooling,
                    classifier_activation=classifier_activation,
                    model_name='resnet-rs-101',
                    include_preprocessing=include_preprocessing)
if __name__ == '__main__':
    model = ResNetRS101(input_shape=(224, 224, 3), classes=1000)
    model.summary()

image-20220907222127622

4、宝可梦数据集

  部分数据展示
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  都按照文件夹分好了,那么我们对数据集进行切分就可以了。
  按照8:2切分数据集。
import os
import random
import shutil
import numpy as np

# 数据集路径
DATASET_DIR = "pokeman"
# DATASET_DIR = "../第13章-验证码识别项目/captcha"
# 数据切分后存放路径
NEW_DIR = "data"
# 测试集占比
num_test = 0.2

# 打乱所有种类数据,并分割训练集和测试集
def shuffle_all_files(dataset_dir, new_dir, num_test):
    # 先删除已有new_dir文件夹
    if not os.path.exists(new_dir):
        pass
    else:
        # 递归删除文件夹
        shutil.rmtree(new_dir)
    # 重新创建new_dir文件夹
    os.makedirs(new_dir)
    # 在new_dir文件夹目录下创建train文件夹
    train_dir = os.path.join(new_dir, 'train')
    os.makedirs(train_dir)
    # 在new_dir文件夹目录下创建test文件夹
    test_dir = os.path.join(new_dir, 'test')
    os.makedirs(test_dir)
    # 原始数据类别列表
    directories = []
    # 新训练集类别列表
    train_directories = []
    # 新测试集类别列表
    test_directories = []
    # 类别名称列表
    class_names = []
    # 循环所有类别
    for filename in os.listdir(dataset_dir):
        # 原始数据类别路径
        path = os.path.join(dataset_dir, filename)
        # 新训练集类别路径
        train_path = os.path.join(train_dir, filename)
        # 新测试集类别路径
        test_path = os.path.join(test_dir, filename)
        # 判断该路径是否为文件夹
        if os.path.isdir(path):
            # 加入原始数据类别列表
            directories.append(path)
            # 加入新训练集类别列表
            train_directories.append(train_path)
            # 新建类别文件夹
            os.makedirs(train_path)
            # 加入新测试集类别列表
            test_directories.append(test_path)
            # 新建类别文件夹
            os.makedirs(test_path)
            # 加入类别名称列表
            class_names.append(filename)
    print('类别列表:', class_names)

    # 循环每个分类的文件夹
    for i in range(len(directories)):
        # 保存原始图片路径
        photo_filenames = []
        # 保存新训练集图片路径
        train_photo_filenames = []
        # 保存新测试集图片路径
        test_photo_filenames = []
        # 得到所有图片的路径
        for filename in os.listdir(directories[i]):
            # 原始图片路径
            path = os.path.join(directories[i], filename)
            # 训练图片路径
            train_path = os.path.join(train_directories[i], filename)
            # 测试集图片路径
            test_path = os.path.join(test_directories[i], filename)
            # 保存图片路径
            photo_filenames.append(path)
            train_photo_filenames.append(train_path)
            test_photo_filenames.append(test_path)
        # list转array
        photo_filenames = np.array(photo_filenames)
        train_photo_filenames = np.array(train_photo_filenames)
        test_photo_filenames = np.array(test_photo_filenames)
        # 打乱索引
        index = [i for i in range(len(photo_filenames))]
        random.shuffle(index)
        # 对3个list进行相同的打乱,保证在3个list中索引一致
        photo_filenames = photo_filenames[index]
        train_photo_filenames = train_photo_filenames[index]
        test_photo_filenames = test_photo_filenames[index]
        # 计算测试集数据个数
        test_sample_index = int((1 - num_test) * float(len(photo_filenames)))
        # 复制测试集图片
        for j in range(test_sample_index, len(photo_filenames)):
            # 复制图片
            shutil.copyfile(photo_filenames[j], test_photo_filenames[j])
        # 复制训练集图片
        for j in range(0, test_sample_index):
            # 复制图片
            shutil.copyfile(photo_filenames[j], train_photo_filenames[j])

# 打乱并切分数据集
shuffle_all_files(DATASET_DIR, NEW_DIR, num_test)

  切分之后:
在这里插入图片描述

5、ResNetRS50架构实现宝可梦图像识别

import tensorflow as tf
import numpy as np
from tensorflow.keras import layers
from tensorflow.keras.models import Model
from tensorflow.keras.applications import ResNetRS50
import math
import os
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.models import Sequential
from tensorflow.keras.optimizers import Adam
import matplotlib.pyplot as plt
from tensorflow.keras.callbacks import LearningRateScheduler
from tensorflow.keras.models import Model

5.1 ResNetRS50架构

  这里去掉顶部的全连接层。
model=ResNetRS50(include_top=False,input_shape=(224,224,3))
model.summary()

image-20220917091212916

5.2 超参数与模型结构

# 类别数
num_classes = 5
# 批次大小
batch_size = 32
# 周期数
epochs=100
# 图片大小
image_size = 224

model=tf.keras.Sequential([
    model,
    layers.GlobalAveragePooling2D(),
    layers.Dense(num_classes,activation='softmax')
])
model.summary()

image-20220917091310191

5.3 数据增强

# 训练集数据进行数据增强
train_datagen = ImageDataGenerator(
    rotation_range=20,  # 随机旋转度数
    width_shift_range=0.1,  # 随机水平平移
    height_shift_range=0.1,  # 随机竖直平移
    rescale=1 / 255,  # 数据归一化
    shear_range=10,  # 随机错切变换
    zoom_range=0.1,  # 随机放大
    horizontal_flip=True,  # 水平翻转
    brightness_range=(0.7, 1.3),  # 亮度变化
    fill_mode='nearest',  # 填充方式
)
# 测试集数据只需要归一化就可以
test_datagen = ImageDataGenerator(
    rescale=1 / 255,  # 数据归一化
)

5.4 数据生成器

# 训练集数据生成器,可以在训练时自动产生数据进行训练
# 从'data/train'获得训练集数据
# 获得数据后会把图片resize为image_size×image_size的大小
# generator每次会产生batch_size个数据
train_generator = train_datagen.flow_from_directory(
    '../data/pokeman-dataset/train',
    target_size=(image_size, image_size), # 调整图像尺寸
    batch_size=batch_size,
)

# 测试集数据生成器
test_generator = test_datagen.flow_from_directory(
    '../data/pokeman-dataset/test',
    target_size=(image_size, image_size),
    batch_size=batch_size,
)
# 字典的键为17个文件夹的名字,值为对应的分类编号
print(train_generator.class_indices)

image-20220917091401404

5.5 callbacks

# 学习率调节函数,逐渐减小学习率
def adjust_learning_rate(epoch):
    # 前40周期
    if epoch<=30:
        lr = 1e-4
    # 前40到80周期
    elif 30 < epoch <= 80:
        lr = 1e-5
    # 80到100周期
    else:
        lr = 1e-6
    return lr

# 定义优化器
adam = Adam(learning_rate=1e-4)

# 读取模型
checkpoint_save_path = "./checkpoint/ResNetRS50-pokeman.ckpt"
if os.path.exists(checkpoint_save_path + '.index'):
    print('-------------load the model-----------------')
    model.load_weights(checkpoint_save_path)
# 保存模型
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
                                                 save_weights_only=True,
                                                 save_best_only=True)
# 早停
early_stop=tf.keras.callbacks.EarlyStopping(monitor='val_loss',patience=3,verbose=0)

# 定义学习率衰减策略
callbacks = []
callbacks.append(LearningRateScheduler(adjust_learning_rate))
# callbacks.append(early_stop)
callbacks.append(cp_callback)

5.6 compile&fit

# 定义优化器,loss function,训练过程中计算准确率
model.compile(optimizer=adam,loss='categorical_crossentropy',metrics=['accuracy'])

# Tensorflow2.1版本(包括2.1)之后可以直接使用fit训练模型
history = model.fit(x=train_generator,epochs=epochs,validation_data=test_generator,callbacks=callbacks)

image-20220917091514686

5.7 保存模型

# 保存模型
model.save('model/ResNetRS50-pokeman.h5')

5.8 acc和loss可视化

# 画出训练集准确率曲线图
plt.plot(np.arange(epochs),history.history['accuracy'],c='b',label='train_accuracy')
# 画出验证集准确率曲线图
plt.plot(np.arange(epochs),history.history['val_accuracy'],c='y',label='val_accuracy')
# 图例
plt.legend()
# x坐标描述
plt.xlabel('epochs')
# y坐标描述
plt.ylabel('accuracy')
# 显示图像
plt.show()

image-20220917091559190

# 画出训练集loss曲线图
plt.plot(np.arange(epochs),history.history['loss'],c='b',label='train_loss')
# 画出验证集loss曲线图
plt.plot(np.arange(epochs),history.history['val_loss'],c='y',label='val_loss')
# 图例
plt.legend()
# x坐标描述
plt.xlabel('epochs')
# y坐标描述
plt.ylabel('loss')
# 显示图像
plt.show()

image-20220917091617981

5.9 test

  这里随便用一些图片测试下这个模型

import tensorflow as tf
import numpy as np
import os
import matplotlib.pyplot as plt
from tensorflow.keras import layers
from tensorflow.keras.models import load_model

# from PIL import Image

model = load_model('model/ResNetRS50-pokeman.h5')
model.summary()

# 类别总数
dataset_dir = 'data/train'
classes = []
for filename in os.listdir(dataset_dir):
    classes.append(filename)
# print('classes:',classes)

# 预测单张图片
def predict_single_image(img_path):
    # string类型的tensor
    img = tf.io.read_file(img_path)
    # 将jpg格式转换为tensor
    img = tf.image.decode_jpeg(img, channels=3)
    # 数据归一化
    img = tf.image.convert_image_dtype(img, dtype=tf.float32)
    # resize
    img = tf.image.resize(img, size=[224, 224])
    # 扩充一个维度
    img = np.expand_dims(img, axis=0)

    # 预测:结果是二维的
    test_result = model.predict(img)
    # print('test_result:', test_result)
    # 转化为一维
    result = np.squeeze(test_result)
    # print('转化后result:', result)

    # 找到概率值最大的索引
    predict_class = np.argmax(result)
    # print('概率值最大的索引:', predict_class)

    # 返回类别和所属类别的概率
    return classes[int(predict_class)], result[predict_class]

# 对整个文件夹的图片进行预测
def predict_directory(file_path):
    classes_pred=[]
    classes_true=[]
    probs=[]
    for file in os.listdir(file_path):
        # 测试图片完整路径
        file_dir=os.path.join(file_path,file)
        # 打印文件路径
        print(file_dir)
        # 传入文件路径进行预测
        preds,prob=predict_single_image(file_dir)

        # 取出图片的真实标签(这里直接将文件夹名称作为真实标签值了)
        # label_true=file.split('_')[0].title()
        label_true = file_dir.split('\\')[0].split('/')[-1]
        # 保存真实值和预测值结果
        classes_true.append(label_true)
        classes_pred.append(preds)
        probs.append(prob)
    return classes_pred,classes_true,probs

# img_path = 'Gemstones/train/Almandine/almandine_0.jpg'
# classes, prob = predict_single_image(img_path)
# print(classes, prob)

file_path= 'data/test/bulbasaur'
classes_pred,classes_true,probs=predict_directory(file_path)
print(classes_pred)
print(classes_true)
print(probs)

image-20220917102034556

  效果还是不错的。
目录
相关文章
|
10月前
|
弹性计算 缓存 小程序
阿里云服务器包年包月一口价:价格透明、告别隐形消费及成本更低的计费模式
阿里云ECS包年包月为预付费模式,价格透明、一口价无隐形消费,长期使用享低至2.3折优惠,成本比按量付费节省超60%,适合稳定业务场景,推荐个人开发者及企业选用。
|
人工智能 自然语言处理 数据库
RAG:打破大模型的知识壁垒
RAG:打破大模型的知识壁垒
615 113
|
运维 监控 关系型数据库
AI 时代的 MySQL 数据库运维解决方案
本文探讨了大模型与MySQL数据库运维结合所带来的变革,介绍了构建结构化运维知识库、选择合适的大模型、设计Prompt调用策略、开发MCP Server以及建立监控优化闭环等关键步骤。通过将自然语言处理能力与数据库运维相结合,实现了故障智能诊断、SQL自动优化等功能,显著提升了MySQL运维效率和准确性。
1163 18
|
人工智能 运维 数据可视化
阿里云百炼 MCP服务使用教程合集
阿里云百炼推出首个全生命周期MCP服务,支持一键部署、无需运维,具备高可用与低成本特点。该服务提供多类型供给、低成本托管及全链路工具兼容,帮助企业快速构建专属智能体。MCP(模型上下文协议)作为标准化开源协议,助力大模型与外部工具高效交互。教程涵盖简单部署、GitHub运营、数据分析可视化及文档自动化等场景,助您快速上手。欢迎加入阿里云百炼生态,共同推动AI技术发展!
10480 60
|
人工智能 Ubuntu API
从零开始:在Ubuntu上快速部署Docker和Dify
本文介绍了如何在 Ubuntu 环境下通过阿里云镜像快速安装 Docker 与 Docker Compose,并部署 Dify 智能应用平台。结合蓝耘 MaaS 平台 API,实现大模型高效接入,帮助开发者快速构建 AI 应用,提升开发效率与使用体验。
3748 8
|
存储 负载均衡 应用服务中间件
LVS负载均衡群集——NAT模式实操
LVS负载均衡群集——NAT模式实操
1933 0
|
机器学习/深度学习 JSON 监控
国内最大的MCP中文社区来了,4000多个服务等你体验
国内最大的MCP中文社区MCPServers来了!平台汇聚4000多个服务资源,涵盖娱乐、监控、云平台等多个领域,为开发者提供一站式技术支持。不仅有丰富的中文学习资料,还有详细的实战教程,如一键接入MCP天气服务等。MCPServers专注模块稳定性和实用性,经过99.99% SLA认证,是高效开发的理想选择。立即访问mcpservers.cn,开启你的开发之旅!
15935 16
|
监控 Linux iOS开发
告别数据丢失!跨平台同步工具FreeFileSync 14.2下载教程|手把手配置多设备备份
FreeFileSync 14.2 是一款开源跨平台文件同步工具,支持 Windows、macOS 和 Linux 系统。新增功能包括实时同步监控、云存储集成(Google Drive 和 Dropbox)、智能冲突解决及性能优化,适用于数据备份、服务器文件同步等场景。本文详细介绍其下载、安装、配置及高级使用技巧,并提供常见问题解答和延伸学习资源。
|
机器学习/深度学习 缓存 负载均衡
Qwen MoE关键细节:通过全局负载均衡提升模型性能和专家的特异化程度
Qwen MoE关键细节:通过全局负载均衡提升模型性能和专家的特异化程度

热门文章

最新文章