介绍

本文是上一篇文章,Karaage 还是 Toy Poodle?这是我试图用机器学习来区分的报复文章!

我很高兴很多人看到我上次在第一篇文章中写的内容作为试用,但另一方面,我很尴尬,因为有很多粗糙的部分。 (顺便说一句,谢谢你的10,000次观看!!)

这一次,我们使用卷积神经网络 (CNN) 创建了炸鸡和贵宾犬的分类器。
之后,我使用 GradCAM 来可视化神经网络正在查看的内容并做出决策,所以如果你能看到它,我会很高兴。

目标使用机器学习创建一个分类器,可以正确将此图像识别为贵宾犬! !

唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類

一、什么是卷积神经网络

与一般的前向传播神经网络不同,CNN 是一个神经网络,不仅包含全连接层,还包含卷积层和池化层。其特点是能够灵活地交换和改进图像像素,参数个数比全连接层少。

唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類

1.1 关于卷积层

在卷积层中,使用多个过滤器执行二维卷积操作,为过滤器的数量创建特征图。

唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類

1.2 关于池化层

池化层是一种防止识别结果因物体位置的微小变化而发生变化的方法。我举了一个具体的例子。在这里使用最大池。

唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類

2. 用tensorflow实现卷积神经网络

2.1 导入所需模块

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import cv2
import os
import glob
import pathlib
from tensorflow.keras.callbacks import EarlyStopping 
import japanize_matplotlib

2.2 数据集的创建

首先创建一个从图像路径加载图像的函数。

def load_and_preprocess_image(path):
    """画像パスから画像を読み込み返す関数"""
    image = tf.io.read_file(path)
    image = tf.image.decode_jpeg(image, channels=3)
    image = tf.cast(image,tf.float32) / 255.0 #ピクセルに0〜255にデータがあるので0〜1の範囲にする
    return image


def preprocess(example_i,example_l,size=(64,64),mode='train'):
    """訓練時:画像をランダムに変形、テスト時:画像を整形して返す関数"""
    image = example_i
    label = example_l

    if mode == 'train':
        image_cropped = tf.image.random_crop(image,size=(150,150,3))
        image_resized = tf.image.resize(image_cropped,size = size)
        image_flip = tf.image.random_flip_left_right(image_resized)
        return (image_flip, tf.cast(label,tf.int32))
    else:
        image_cropped = tf.image.crop_to_bounding_box(
            image,offset_height=34,offset_width=14,
            target_height=150,target_width=150)
        image_resized = tf.image.resize(image_cropped,size=size)
        return (image_resized, tf.cast(label,tf.int32))

接下来,我们将实际创建训练数据集。标注很困难,所以我为炸鸡创建了一个文件夹,为贵宾犬创建了一个文件夹,创建了单独的数据集,并将它们组合在一起。

#データセットの作成 唐揚げ
kara_imgdir_path = pathlib.Path('../../karapu/karaage/')
kara_file_list = sorted([str(path) for path in kara_imgdir_path.glob('*.jpg')])

kara_ds_path = tf.data.Dataset.from_tensor_slices(kara_file_list)

kara_image_ds = kara_ds_path.map(load_and_preprocess_image)

kara_image_labels = [1]*377 #唐揚げはラベル1
kara_label_ds = tf.data.Dataset.from_tensor_slices(tf.cast(kara_image_labels, tf.int64))
#ラベルと画像のデータの結合
kara_image_label_ds = tf.data.Dataset.zip((image_ds, label_ds))

#データセットの作成プードル
pu_imgdir_path = pathlib.Path('../../karapu/poodle/')
pu_file_list = sorted([str(path) for path in pu_imgdir_path.glob('*.jpg')])

pu_ds_path = tf.data.Dataset.from_tensor_slices(pu_file_list)

pu_image_ds = pu_ds_path.map(load_and_preprocess_image)

pu_image_labels = [0]*402 #プードルはラベル0

pu_label_ds = tf.data.Dataset.from_tensor_slices(tf.cast(pu_image_labels, tf.int64))
pu_image_label_ds = tf.data.Dataset.zip((pu_image_ds, pu_label_ds))

#唐揚げとプードルの結合
ds = kara_image_label_ds.concatenate(pu_image_label_ds)
#シャッフル
ds_images_labels = ds.shuffle(779)

#trainとvalidにデータを分ける(train,valid)=(679, 100)
tf.random.set_seed(1)
ds_images_labels = ds_images_labels.shuffle(1000,reshuffle_each_iteration=False)
train = ds_images_labels.take(679)
valid = ds_images_labels.skip(679)

BATCH_SIZE = 32
BUFFER_SIZE = 779
IMAGE_SIZE = (128,128)
steps_per_epoch = np.ceil(779/BATCH_SIZE) #画像数/バッチ数
#dsが(img, label)のタプルのため、lambdaに二つ引数を渡す。
ds_train = ds_images_labels.map(lambda x,i:preprocess(x,i,size=IMAGE_SIZE,mode='train'))
ds_train = ds_train.shuffle(buffer_size=BUFFER_SIZE).repeat()
ds_train = ds_train.batch(BATCH_SIZE)

ds_valid = valid.map(lambda x,i:preprocess(x,i,size=IMAGE_SIZE,mode='train'))
ds_valid = ds_valid.batch(BATCH_SIZE)

数据现已准备就绪。我们将继续研究。

2.3 模型的创建

使用 from tensorflow.keras.callbacks import EarlyStopping) 决定 EarlyStopping。监视器应该是有效的损失。

from tensorflow.keras.callbacks import EarlyStopping
es_cb = EarlyStopping(
                      monitor = 'val_loss', 
                      patience = 10, 
                      restore_best_weights = True)

model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(32,(3,3),padding='same',activation='relu'),
    tf.keras.layers.MaxPooling2D((2,2)),
    tf.keras.layers.Dropout(rate=0.5),
    tf.keras.layers.Conv2D(64,(3,3),padding='same',activation='relu'),
    tf.keras.layers.MaxPooling2D((2,2)),
    tf.keras.layers.Dropout(rate=0.5),

    tf.keras.layers.Conv2D(128,(3,3),padding='same',activation='relu'),
    tf.keras.layers.MaxPooling2D((2,2)),

    tf.keras.layers.Conv2D(256,(3,3),padding='same',activation='relu'),

    tf.keras.layers.GlobalAveragePooling2D(),

    tf.keras.layers.Dense(1,activation=None)    
    ])

model.compile(optimizer = tf.keras.optimizers.Adam(),
                loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
                metrics=['accuracy'])
history = model.fit(ds_train,validation_data=ds_valid,
                    epochs=20,steps_per_epoch=steps_per_epoch,callbacks = es_cb,)

`model.summary()` 查看模型中的内容。
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類

嗯,参数很多,不过既然是卷积的结果,要是全是全连接层就好了……

如果最终输出接近 1,则归类为炸鸡,如果接近 0,则归类为贵宾犬。

2.4 模型评价

让我们看一下损失、准确率和历元图。

唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類

听起来很棒!验证与 Train 紧密相连。这是可以预料的。

测试数据(27张)准确率也是96.30%!

hist = history.history
x_arr = np.arange(len(hist['loss'])) + 1
fig = plt.figure(figsize=(12,4))
ax = fig.add_subplot(1,2,1)
ax.plot(x_arr, hist['loss'], '-o', label='Train loss')
ax.plot(x_arr, hist['val_loss'],'--<', label='Validation loss')
ax.legend(fontsize=15)
ax.set_xlabel('Epoch', size=15)
ax.set_ylabel('Loss', size=15)
ax = fig.add_subplot(1,2,2)
ax.plot(x_arr,hist['accuracy'],'-o',label='Train acc.')
ax.plot(x_arr,hist['val_accuracy'],'--<',label='Validation acc.')
ax.legend(fontsize=15)
ax.set_xlabel('Epoch',size=15)
ax.set_ylabel('accuracy',size=15)
plt.show()

#正解率の表示
ds_test = ds_test.map(lambda x,i:preprocess(x,i,size=IMAGE_SIZE,mode='eval')).batch(1)
test_results = model.evaluate(ds_test,verbose=1)
print('Test Acc: {:.2f}%'.format(test_results[1]*100))

现在让我们看看测试数据的预测输出! !
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類

和目标形象! ?

唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類

是的,它是油炸的……
不知道是不是类似的训练图不够多,所以想多收集一些。这只是令人沮丧,所以最后我将使用 GradCAM 来可视化 CNN 正在查看的内容并将这些孩子判断为炸鸡!

3. 使用 GradCAM 进行可视化

Grad-CAM(梯度加权类激活映射)
是一种对输入及其对基于 CNN 的图像识别模型的预测进行局部解释的方法。
您可以通过如下所示的热图来可视化 CNN 强调的位置。
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類

在解释 GradCAM 之前,我将解释 Global Average Pooling 和 CAM。

3.1 全球平均池化(GAP)

Global Average Pooling 的特点是与普通的扁平化相比,它可以显着减少参数的数量。
当执行 GAP 时,特征图的每个分量的平均值成为单位值。
在以下示例中,与 40 个展平参数相比,10 个 GAP 显着减少。

唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類

3.2 类激活图(CAM)

唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類

3.3 GradCAM

GradCAM 是 Gradient-weighted Class Activation Mapping 的缩写,是 CAM 的推广。
具体来说,我们将通过从特征图的梯度给出热图来可视化它。
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類

3.4 GradCAM 实现与可视化

def show_cam(i):
    grad_model = tf.keras.models.Model([model.inputs], [model.get_layer('conv2d_15').output, model.output])

    with tf.GradientTape() as tape:
        conv_outputs, predictions = grad_model(tensor_list[i][0])
        class_idx = np.argmax(predictions[0])
        loss = predictions[:, class_idx]

    output = conv_outputs[0]
    grads = tape.gradient(loss, conv_outputs)[0]

    gate_f = tf.cast(output > 0, 'float32')
    gate_r = tf.cast(grads > 0, 'float32')

    guided_grads = gate_f * gate_r * grads

    # 重みを平均化して、レイヤーの出力に乗じる
    weights = np.mean(guided_grads, axis=(0, 1))
    cam = np.dot(output, weights)


    # 画像を元画像と同じ大きさにスケーリング
    cam = cv2.resize(cam, (500, 500), cv2.INTER_LINEAR)
    image = cv2.resize(image_list[i][0],(500, 500),cv2.INTER_LINEAR)

    # ReLUの代わり
    cam  = np.maximum(cam, 0)
    # ヒートマップを計算
    heatmap = cam / cam.max()

    # モノクロ画像に疑似的に色をつける
    jet_cam = cv2.applyColorMap(np.uint8(255.0*heatmap), cv2.COLORMAP_JET)
    jet_cam = 0.7 * jet_cam / np.max(jet_cam)
    jet_cam = np.float32(jet_cam) + np.float32(image)


    # RGBに変換
    rgb_cam = np.float32(cv2.cvtColor(jet_cam, cv2.COLOR_BGR2RGB))

    # もとの画像に合成
    output_arr = cv2.addWeighted(src1=image, alpha=1, src2=rgb_cam, beta=0.3, gamma=0)

    fig = plt.figure(figsize=(6,10))
    ax = fig.add_subplot(1,2,1)
    ax.set_title('Grad CAM')
    ax.imshow(rgb_cam)

    ax = fig.add_subplot(1,2,2)
    if preds[i] == 1:
        ax.set_title('予測結果→唐揚げ')
    else:
        ax.set_title('予測結果→プードル')
    ax.imshow(image)

炸鸡的可视化
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類

贵宾犬可视化

唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類

使用 GraadCAM 进行可视化的结果是,炸鸡关注的是眼睛和鼻子的整体! !
有问题的图像是
唐揚げとプードルを絶対に見分けたい!畳み込みニューラルネットワークで画像分類
可以看到重点是整体,这就是炸鸡的特点。

4. 最后

在学习了 CNN 和 GradCAM 之后,我对机器学习进行图像识别产生了浓厚的兴趣! !通过 GradCAM 可视化 CNN 的值,我可以看到如何改进我的炒贵宾犬数据集。将来,我想使用训练数据来学习面部不可见的贵宾犬和多只贵宾犬的图像。

永远不要喂狮子狗!!!

5. 参考文献

研究生 CAM 纸
Grad-CAM:通过基于梯度的本地化来自深度网络的视觉解释


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308626072.html

相关文章:

  • 2021-05-03
  • 2021-04-25
  • 2022-01-08
  • 2022-01-20
  • 2021-10-30
  • 2021-11-13
  • 2021-05-27
  • 2021-12-19
猜你喜欢
  • 2021-09-20
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-07-04
  • 2021-05-16
  • 2021-09-29
相关资源
相似解决方案