「ネットワークを深くすれば精度が上がるはず」——実際に試すと、ある深さを超えたあたりから精度が頭打ちになったり、むしろ下がったりした経験はありませんか?
その原因の一つが勾配消失問題です。ResNetが2015年に提案したResidual接続(スキップ接続)は、その問題を解決し、100層を超えるネットワークの学習を可能にした革命的なアイデアです。
今回はその仕組みをKerasで自前実装し、CIFAR-10を使ってResidual接続あり vs なしを直接比較します。
関連記事:Global Average Pooling vs Flatten|CNNの最終層、どっちが精度・速度で有利か?【Keras実験】
- Residual接続(スキップ接続)の仕組みと役割
- なぜ深いネットワークで勾配消失が起きるのか
- Kerasでスキップ接続を実装する具体的なコード
- Residual接続あり・なしで精度と学習安定性がどう変わるか
Residual接続とは何か
通常のCNNでは、入力 x が複数の層を通り変換されて出力 F(x) になります。Residual接続では、この変換に加えて入力 x を直接出力に足すという操作を行います。
\[ \text{出力} = F(x) + x \]
この「+ x」の部分がスキップ接続(ショートカット接続)です。
なぜこれが効くのか
深いネットワークで学習がうまくいかない理由の一つは、誤差逆伝播の際に勾配が層を遡るたびに小さくなっていく勾配消失です。スキップ接続があると、勾配が + x のパスを通ってそのまま浅い層まで届くため、勾配消失が起きにくくなります。
また、もしブロック内の変換 F(x) がゼロに近くなっても、スキップ接続によって x がそのまま伝わるため、「恒等写像(identity mapping)」を簡単に学習できるという利点もあります。ブロックは差分 F(x) = 出力 - x を学習すればよいので、残差学習(Residual learning)とも呼ばれます。
チャンネル数が異なる場合の1×1 Conv
スキップ接続で x を足すには、x と F(x) の形(チャンネル数・空間サイズ)が一致している必要があります。チャンネル数が変わる場合は 1×1 Conv でチャンネル数を揃えます。
スキップ接続で
x + F(x) の加算を行う際、チャンネル数や空間サイズが一致していないと ValueError が発生します。チャンネル数が変わるときは 1×1 Conv(
kernel_size=(1,1))でショートカット側を変換してください。padding='same' の指定も忘れずに。
実験コード
使用環境はGoogle Colab(GPU:T4)、データセットはCIFAR-10です。Residual接続の有無以外の条件は全て同一にします。
環境準備(最初に一度だけ実行)
# ── 環境準備(最初に一度だけ実行)──────────────────────
!apt-get -y install fonts-ipafont-gothic
!rm -rf /root/.cache/matplotlib
!pip install -q japanize_matplotlib
print("環境準備完了")
実行結果をクリックして内容を開く
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
fonts-ipafont-mincho
The following NEW packages will be installed:
fonts-ipafont-gothic fonts-ipafont-mincho
0 upgraded, 2 newly installed, 0 to remove and 51 not upgraded.
Need to get 8,237 kB of archives.
After this operation, 28.7 MB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu jammy/universe amd64 fonts-ipafont-gothic all 00303-21ubuntu1 [3,513 kB]
Get:2 http://archive.ubuntu.com/ubuntu jammy/universe amd64 fonts-ipafont-mincho all 00303-21ubuntu1 [4,724 kB]
Fetched 8,237 kB in 0s (18.3 MB/s)
Selecting previously unselected package fonts-ipafont-gothic.
(Reading database ... 122412 files and directories currently installed.)
Preparing to unpack .../fonts-ipafont-gothic_00303-21ubuntu1_all.deb ...
Unpacking fonts-ipafont-gothic (00303-21ubuntu1) ...
Selecting previously unselected package fonts-ipafont-mincho.
Preparing to unpack .../fonts-ipafont-mincho_00303-21ubuntu1_all.deb ...
Unpacking fonts-ipafont-mincho (00303-21ubuntu1) ...
Setting up fonts-ipafont-mincho (00303-21ubuntu1) ...
update-alternatives: using /usr/share/fonts/opentype/ipafont-mincho/ipam.ttf to provide /usr/share/fonts/truetype/fonts-japanese-mincho.ttf (fonts-japanese-mincho.ttf) in auto mode
Setting up fonts-ipafont-gothic (00303-21ubuntu1) ...
update-alternatives: using /usr/share/fonts/opentype/ipafont-gothic/ipag.ttf to provide /usr/share/fonts/truetype/fonts-japanese-gothic.ttf (fonts-japanese-gothic.ttf) in auto mode
Processing triggers for fontconfig (2.13.1-4.2ubuntu5) ...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.1/4.1 MB 51.5 MB/s eta 0:00:00
Preparing metadata (setup.py) ... done
Building wheel for japanize_matplotlib (setup.py) ... done
環境準備完了
import・データ準備
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import matplotlib.pyplot as plt
import japanize_matplotlib
import time
# CIFAR-10 読み込み・正規化
(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0
print("データ準備完了")
実行結果をクリックして内容を開く
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz 170498071/170498071 ━━━━━━━━━━━━━━━━━━━━ 4s 0us/step データ準備完了
Residual Blockの実装
まず今回の実験のキーとなる、Residual BlockとPlain Blockの関数を定義します。
def residual_block(x, filters, use_skip=True):
"""
Residual Block(use_skip=True: スキップ接続あり, False: なし)
チャンネル数が変わる場合は 1x1 Conv でショートカット側を変換する。
"""
shortcut = x
# メインパス:Conv → BN → ReLU → Conv → BN
h = layers.Conv2D(filters, (3, 3), padding='same')(x)
h = layers.BatchNormalization()(h)
h = layers.Activation('relu')(h)
h = layers.Conv2D(filters, (3, 3), padding='same')(h)
h = layers.BatchNormalization()(h)
if use_skip:
# チャンネル数が異なる場合は 1x1 Conv で揃える
if shortcut.shape[-1] != filters:
shortcut = layers.Conv2D(filters, (1, 1), padding='same')(shortcut)
shortcut = layers.BatchNormalization()(shortcut)
h = layers.Add()([h, shortcut]) # ← スキップ接続
h = layers.Activation('relu')(h)
return h
モデル構築関数
def build_model(use_skip, name):
"""
Residual接続あり(use_skip=True) / なし(use_skip=False) のCNNを構築
ネットワーク深さ・チャンネル数はどちらも同一
"""
inputs = keras.Input(shape=(32, 32, 3))
# ── 入力Conv ───────────────────────────────────────
x = layers.Conv2D(64, (3, 3), padding='same')(inputs)
x = layers.BatchNormalization()(x)
x = layers.Activation('relu')(x)
# ── ブロック群(深さ6ブロック) ─────────────────────
# Stage 1: 64ch × 2ブロック
x = residual_block(x, 64, use_skip=use_skip)
x = residual_block(x, 64, use_skip=use_skip)
x = layers.MaxPooling2D((2, 2))(x) # 32→16
# Stage 2: 128ch × 2ブロック
x = residual_block(x, 128, use_skip=use_skip)
x = residual_block(x, 128, use_skip=use_skip)
x = layers.MaxPooling2D((2, 2))(x) # 16→8
# Stage 3: 256ch × 2ブロック
x = residual_block(x, 256, use_skip=use_skip)
x = residual_block(x, 256, use_skip=use_skip)
# ── 出力 ───────────────────────────────────────────
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dense(128, activation='relu')(x)
x = layers.Dropout(0.3)(x)
outputs = layers.Dense(10, activation='softmax')(x)
return keras.Model(inputs, outputs, name=name)
def compile_and_fit(model):
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
start = time.time()
history = model.fit(x_train, y_train,
epochs=30, batch_size=64,
validation_split=0.2,
verbose=1)
return history, time.time() - start
2パターンの学習実行
configs = [
(True, 'A_with_skip'), # Residual接続あり
(False, 'B_without_skip'), # Residual接続なし
]
histories, times, scores, params = {}, {}, {}, {}
for use_skip, name in configs:
print(f"\n=== {name} ===")
model = build_model(use_skip, name)
model.summary()
h, t = compile_and_fit(model)
s = model.evaluate(x_test, y_test, verbose=0)
label = name.split('_', 1)[1] # 'with_skip' / 'without_skip'
histories[label] = h
times[label] = t
scores[label] = s
params[label] = model.count_params()
print(f"学習時間:{t:.1f}秒 パラメータ数:{model.count_params():,} test_accuracy:{s[1]:.4f}")
実行結果をクリックして内容を開く
=== A_with_skip === Model: "A_with_skip" ┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃ Connected to ┃ ┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩ │ input_layer │ (None, 32, 32, 3) │ 0 │ - │ │ (InputLayer) │ │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d (Conv2D) │ (None, 32, 32, │ 1,792 │ input_layer[0][0] │ │ │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ batch_normalization │ (None, 32, 32, │ 256 │ conv2d[0][0] │ │ (BatchNormalizatio… │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ activation │ (None, 32, 32, │ 0 │ batch_normalizat… │ │ (Activation) │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d_1 (Conv2D) │ (None, 32, 32, │ 36,928 │ activation[0][0] │ │ │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ batch_normalizatio… │ (None, 32, 32, │ 256 │ conv2d_1[0][0] │ │ (BatchNormalizatio… │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ activation_1 │ (None, 32, 32, │ 0 │ batch_normalizat… │ │ (Activation) │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d_2 (Conv2D) │ (None, 32, 32, │ 36,928 │ activation_1[0][… │ │ │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ batch_normalizatio… │ (None, 32, 32, │ 256 │ conv2d_2[0][0] │ │ (BatchNormalizatio… │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ add (Add) │ (None, 32, 32, │ 0 │ batch_normalizat… │ │ │ 64) │ │ activation[0][0] │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ activation_2 │ (None, 32, 32, │ 0 │ add[0][0] │ │ (Activation) │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d_3 (Conv2D) │ (None, 32, 32, │ 36,928 │ activation_2[0][… │ │ │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ batch_normalizatio… │ (None, 32, 32, │ 256 │ conv2d_3[0][0] │ │ (BatchNormalizatio… │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ activation_3 │ (None, 32, 32, │ 0 │ batch_normalizat… │ │ (Activation) │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d_4 (Conv2D) │ (None, 32, 32, │ 36,928 │ activation_3[0][… │ │ │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ batch_normalizatio… │ (None, 32, 32, │ 256 │ conv2d_4[0][0] │ │ (BatchNormalizatio… │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ add_1 (Add) │ (None, 32, 32, │ 0 │ batch_normalizat… │ │ │ 64) │ │ activation_2[0][… │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ activation_4 │ (None, 32, 32, │ 0 │ add_1[0][0] │ │ (Activation) │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ max_pooling2d │ (None, 16, 16, │ 0 │ activation_4[0][… │ │ (MaxPooling2D) │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d_5 (Conv2D) │ (None, 16, 16, │ 73,856 │ max_pooling2d[0]… │ │ │ 128) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ batch_normalizatio… │ (None, 16, 16, │ 512 │ conv2d_5[0][0] │ │ (BatchNormalizatio… │ 128) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ activation_5 │ (None, 16, 16, │ 0 │ batch_normalizat… │ │ (Activation) │ 128) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d_6 (Conv2D) │ (None, 16, 16, │ 147,584 │ activation_5[0][… │ │ │ 128) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d_7 (Conv2D) │ (None, 16, 16, │ 8,320 │ max_pooling2d[0]… │ │ │ 128) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ batch_normalizatio… │ (None, 16, 16, │ 512 │ conv2d_6[0][0] │ │ (BatchNormalizatio… │ 128) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ batch_normalizatio… │ (None, 16, 16, │ 512 │ conv2d_7[0][0] │ │ (BatchNormalizatio… │ 128) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ add_2 (Add) │ (None, 16, 16, │ 0 │ batch_normalizat… │ │ │ 128) │ │ batch_normalizat… │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ activation_6 │ (None, 16, 16, │ 0 │ add_2[0][0] │ │ (Activation) │ 128) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d_8 (Conv2D) │ (None, 16, 16, │ 147,584 │ activation_6[0][… │ │ │ 128) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ batch_normalizatio… │ (None, 16, 16, │ 512 │ conv2d_8[0][0] │ │ (BatchNormalizatio… │ 128) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ activation_7 │ (None, 16, 16, │ 0 │ batch_normalizat… │ │ (Activation) │ 128) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d_9 (Conv2D) │ (None, 16, 16, │ 147,584 │ activation_7[0][… │ │ │ 128) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ batch_normalizatio… │ (None, 16, 16, │ 512 │ conv2d_9[0][0] │ │ (BatchNormalizatio… │ 128) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ add_3 (Add) │ (None, 16, 16, │ 0 │ batch_normalizat… │ │ │ 128) │ │ activation_6[0][… │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ activation_8 │ (None, 16, 16, │ 0 │ add_3[0][0] │ │ (Activation) │ 128) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ max_pooling2d_1 │ (None, 8, 8, 128) │ 0 │ activation_8[0][… │ │ (MaxPooling2D) │ │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d_10 (Conv2D) │ (None, 8, 8, 256) │ 295,168 │ max_pooling2d_1[… │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ batch_normalizatio… │ (None, 8, 8, 256) │ 1,024 │ conv2d_10[0][0] │ │ (BatchNormalizatio… │ │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ activation_9 │ (None, 8, 8, 256) │ 0 │ batch_normalizat… │ │ (Activation) │ │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d_11 (Conv2D) │ (None, 8, 8, 256) │ 590,080 │ activation_9[0][… │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d_12 (Conv2D) │ (None, 8, 8, 256) │ 33,024 │ max_pooling2d_1[… │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ batch_normalizatio… │ (None, 8, 8, 256) │ 1,024 │ conv2d_11[0][0] │ │ (BatchNormalizatio… │ │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ batch_normalizatio… │ (None, 8, 8, 256) │ 1,024 │ conv2d_12[0][0] │ │ (BatchNormalizatio… │ │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ add_4 (Add) │ (None, 8, 8, 256) │ 0 │ batch_normalizat… │ │ │ │ │ batch_normalizat… │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ activation_10 │ (None, 8, 8, 256) │ 0 │ add_4[0][0] │ │ (Activation) │ │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d_13 (Conv2D) │ (None, 8, 8, 256) │ 590,080 │ activation_10[0]… │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ batch_normalizatio… │ (None, 8, 8, 256) │ 1,024 │ conv2d_13[0][0] │ │ (BatchNormalizatio… │ │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ activation_11 │ (None, 8, 8, 256) │ 0 │ batch_normalizat… │ │ (Activation) │ │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d_14 (Conv2D) │ (None, 8, 8, 256) │ 590,080 │ activation_11[0]… │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ batch_normalizatio… │ (None, 8, 8, 256) │ 1,024 │ conv2d_14[0][0] │ │ (BatchNormalizatio… │ │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ add_5 (Add) │ (None, 8, 8, 256) │ 0 │ batch_normalizat… │ │ │ │ │ activation_10[0]… │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ activation_12 │ (None, 8, 8, 256) │ 0 │ add_5[0][0] │ │ (Activation) │ │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ global_average_poo… │ (None, 256) │ 0 │ activation_12[0]… │ │ (GlobalAveragePool… │ │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ dense (Dense) │ (None, 128) │ 32,896 │ global_average_p… │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ dropout (Dropout) │ (None, 128) │ 0 │ dense[0][0] │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ dense_1 (Dense) │ (None, 10) │ 1,290 │ dropout[0][0] │ └─────────────────────┴───────────────────┴────────────┴───────────────────┘ Total params: 2,816,010 (10.74 MB) Trainable params: 2,811,530 (10.73 MB) Non-trainable params: 4,480 (17.50 KB) Epoch 1/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 49s 50ms/step - accuracy: 0.5160 - loss: 1.3457 - val_accuracy: 0.5150 - val_loss: 1.5214 Epoch 2/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.6890 - loss: 0.8981 - val_accuracy: 0.6007 - val_loss: 1.1967 Epoch 3/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 29s 47ms/step - accuracy: 0.7606 - loss: 0.7040 - val_accuracy: 0.7005 - val_loss: 0.8658 Epoch 4/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 31s 49ms/step - accuracy: 0.8031 - loss: 0.5905 - val_accuracy: 0.6942 - val_loss: 0.9124 Epoch 5/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 31s 49ms/step - accuracy: 0.8293 - loss: 0.5000 - val_accuracy: 0.7288 - val_loss: 0.7699 Epoch 6/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.8596 - loss: 0.4218 - val_accuracy: 0.7234 - val_loss: 0.8523 Epoch 7/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.8796 - loss: 0.3555 - val_accuracy: 0.7862 - val_loss: 0.7377 Epoch 8/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 49ms/step - accuracy: 0.8990 - loss: 0.3006 - val_accuracy: 0.7825 - val_loss: 0.7059 Epoch 9/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9155 - loss: 0.2504 - val_accuracy: 0.8262 - val_loss: 0.5775 Epoch 10/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9284 - loss: 0.2121 - val_accuracy: 0.7382 - val_loss: 1.0013 Epoch 11/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9421 - loss: 0.1740 - val_accuracy: 0.8003 - val_loss: 0.7194 Epoch 12/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9484 - loss: 0.1537 - val_accuracy: 0.8023 - val_loss: 0.6419 Epoch 13/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9582 - loss: 0.1225 - val_accuracy: 0.8135 - val_loss: 0.8706 Epoch 14/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 31s 49ms/step - accuracy: 0.9635 - loss: 0.1106 - val_accuracy: 0.6922 - val_loss: 1.8044 Epoch 15/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 31s 50ms/step - accuracy: 0.9685 - loss: 0.0953 - val_accuracy: 0.8439 - val_loss: 0.5770 Epoch 16/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9713 - loss: 0.0848 - val_accuracy: 0.8024 - val_loss: 0.8008 Epoch 17/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9742 - loss: 0.0770 - val_accuracy: 0.8184 - val_loss: 0.8351 Epoch 18/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9758 - loss: 0.0722 - val_accuracy: 0.8097 - val_loss: 0.8360 Epoch 19/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9792 - loss: 0.0638 - val_accuracy: 0.8382 - val_loss: 0.6745 Epoch 20/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9789 - loss: 0.0634 - val_accuracy: 0.8113 - val_loss: 0.9820 Epoch 21/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9815 - loss: 0.0556 - val_accuracy: 0.7907 - val_loss: 1.0682 Epoch 22/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9827 - loss: 0.0540 - val_accuracy: 0.8081 - val_loss: 1.0396 Epoch 23/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9836 - loss: 0.0517 - val_accuracy: 0.8305 - val_loss: 0.8231 Epoch 24/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9861 - loss: 0.0437 - val_accuracy: 0.8401 - val_loss: 0.8829 Epoch 25/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9838 - loss: 0.0498 - val_accuracy: 0.8092 - val_loss: 0.9178 Epoch 26/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 49ms/step - accuracy: 0.9851 - loss: 0.0461 - val_accuracy: 0.8286 - val_loss: 0.8215 Epoch 27/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9877 - loss: 0.0366 - val_accuracy: 0.8336 - val_loss: 0.7747 Epoch 28/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9881 - loss: 0.0382 - val_accuracy: 0.8447 - val_loss: 0.8085 Epoch 29/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9874 - loss: 0.0390 - val_accuracy: 0.8266 - val_loss: 0.9674 Epoch 30/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9875 - loss: 0.0392 - val_accuracy: 0.8075 - val_loss: 1.1337 学習時間:926.0秒 パラメータ数:2,816,010 test_accuracy:0.7963 === B_without_skip === Model: "B_without_skip" ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ │ input_layer_1 (InputLayer) │ (None, 32, 32, 3) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_15 (Conv2D) │ (None, 32, 32, 64) │ 1,792 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ batch_normalization_15 │ (None, 32, 32, 64) │ 256 │ │ (BatchNormalization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ activation_13 (Activation) │ (None, 32, 32, 64) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_16 (Conv2D) │ (None, 32, 32, 64) │ 36,928 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ batch_normalization_16 │ (None, 32, 32, 64) │ 256 │ │ (BatchNormalization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ activation_14 (Activation) │ (None, 32, 32, 64) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_17 (Conv2D) │ (None, 32, 32, 64) │ 36,928 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ batch_normalization_17 │ (None, 32, 32, 64) │ 256 │ │ (BatchNormalization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ activation_15 (Activation) │ (None, 32, 32, 64) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_18 (Conv2D) │ (None, 32, 32, 64) │ 36,928 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ batch_normalization_18 │ (None, 32, 32, 64) │ 256 │ │ (BatchNormalization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ activation_16 (Activation) │ (None, 32, 32, 64) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_19 (Conv2D) │ (None, 32, 32, 64) │ 36,928 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ batch_normalization_19 │ (None, 32, 32, 64) │ 256 │ │ (BatchNormalization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ activation_17 (Activation) │ (None, 32, 32, 64) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d_2 (MaxPooling2D) │ (None, 16, 16, 64) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_20 (Conv2D) │ (None, 16, 16, 128) │ 73,856 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ batch_normalization_20 │ (None, 16, 16, 128) │ 512 │ │ (BatchNormalization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ activation_18 (Activation) │ (None, 16, 16, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_21 (Conv2D) │ (None, 16, 16, 128) │ 147,584 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ batch_normalization_21 │ (None, 16, 16, 128) │ 512 │ │ (BatchNormalization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ activation_19 (Activation) │ (None, 16, 16, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_22 (Conv2D) │ (None, 16, 16, 128) │ 147,584 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ batch_normalization_22 │ (None, 16, 16, 128) │ 512 │ │ (BatchNormalization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ activation_20 (Activation) │ (None, 16, 16, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_23 (Conv2D) │ (None, 16, 16, 128) │ 147,584 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ batch_normalization_23 │ (None, 16, 16, 128) │ 512 │ │ (BatchNormalization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ activation_21 (Activation) │ (None, 16, 16, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d_3 (MaxPooling2D) │ (None, 8, 8, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_24 (Conv2D) │ (None, 8, 8, 256) │ 295,168 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ batch_normalization_24 │ (None, 8, 8, 256) │ 1,024 │ │ (BatchNormalization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ activation_22 (Activation) │ (None, 8, 8, 256) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_25 (Conv2D) │ (None, 8, 8, 256) │ 590,080 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ batch_normalization_25 │ (None, 8, 8, 256) │ 1,024 │ │ (BatchNormalization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ activation_23 (Activation) │ (None, 8, 8, 256) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_26 (Conv2D) │ (None, 8, 8, 256) │ 590,080 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ batch_normalization_26 │ (None, 8, 8, 256) │ 1,024 │ │ (BatchNormalization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ activation_24 (Activation) │ (None, 8, 8, 256) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_27 (Conv2D) │ (None, 8, 8, 256) │ 590,080 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ batch_normalization_27 │ (None, 8, 8, 256) │ 1,024 │ │ (BatchNormalization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ activation_25 (Activation) │ (None, 8, 8, 256) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ global_average_pooling2d_1 │ (None, 256) │ 0 │ │ (GlobalAveragePooling2D) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense_2 (Dense) │ (None, 128) │ 32,896 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dropout_1 (Dropout) │ (None, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense_3 (Dense) │ (None, 10) │ 1,290 │ └─────────────────────────────────┴────────────────────────┴───────────────┘ Total params: 2,773,130 (10.58 MB) Trainable params: 2,769,418 (10.56 MB) Non-trainable params: 3,712 (14.50 KB) Epoch 1/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 46s 52ms/step - accuracy: 0.4753 - loss: 1.4362 - val_accuracy: 0.4110 - val_loss: 2.3687 Epoch 2/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 29s 46ms/step - accuracy: 0.6430 - loss: 1.0066 - val_accuracy: 0.3818 - val_loss: 2.7285 Epoch 3/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.7151 - loss: 0.8230 - val_accuracy: 0.6055 - val_loss: 1.3240 Epoch 4/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 46ms/step - accuracy: 0.7627 - loss: 0.6940 - val_accuracy: 0.7251 - val_loss: 0.8020 Epoch 5/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 29s 46ms/step - accuracy: 0.7966 - loss: 0.5976 - val_accuracy: 0.7088 - val_loss: 0.8812 Epoch 6/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.8232 - loss: 0.5229 - val_accuracy: 0.7462 - val_loss: 0.8431 Epoch 7/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.8439 - loss: 0.4621 - val_accuracy: 0.7405 - val_loss: 0.8004 Epoch 8/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.8636 - loss: 0.4040 - val_accuracy: 0.7942 - val_loss: 0.6351 Epoch 9/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.8832 - loss: 0.3500 - val_accuracy: 0.8115 - val_loss: 0.5778 Epoch 10/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.8949 - loss: 0.3104 - val_accuracy: 0.7932 - val_loss: 0.6827 Epoch 11/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 29s 46ms/step - accuracy: 0.9091 - loss: 0.2691 - val_accuracy: 0.8004 - val_loss: 0.6670 Epoch 12/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.9184 - loss: 0.2388 - val_accuracy: 0.8074 - val_loss: 0.6502 Epoch 13/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.9301 - loss: 0.2064 - val_accuracy: 0.8020 - val_loss: 0.7048 Epoch 14/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 41s 46ms/step - accuracy: 0.9400 - loss: 0.1822 - val_accuracy: 0.7523 - val_loss: 0.9711 Epoch 15/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 29s 46ms/step - accuracy: 0.9470 - loss: 0.1552 - val_accuracy: 0.8081 - val_loss: 0.7556 Epoch 16/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.9524 - loss: 0.1409 - val_accuracy: 0.7672 - val_loss: 1.0236 Epoch 17/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 29s 46ms/step - accuracy: 0.9579 - loss: 0.1229 - val_accuracy: 0.8438 - val_loss: 0.5993 Epoch 18/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 29s 46ms/step - accuracy: 0.9622 - loss: 0.1121 - val_accuracy: 0.7967 - val_loss: 0.8734 Epoch 19/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.9667 - loss: 0.0989 - val_accuracy: 0.7816 - val_loss: 1.0158 Epoch 20/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.9680 - loss: 0.0954 - val_accuracy: 0.7685 - val_loss: 1.1077 Epoch 21/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.9732 - loss: 0.0822 - val_accuracy: 0.8008 - val_loss: 0.9681 Epoch 22/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.9749 - loss: 0.0755 - val_accuracy: 0.8264 - val_loss: 0.7816 Epoch 23/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.9748 - loss: 0.0735 - val_accuracy: 0.7982 - val_loss: 0.9215 Epoch 24/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.9776 - loss: 0.0661 - val_accuracy: 0.8000 - val_loss: 0.9783 Epoch 25/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.9785 - loss: 0.0644 - val_accuracy: 0.8077 - val_loss: 1.0973 Epoch 26/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.9785 - loss: 0.0651 - val_accuracy: 0.8391 - val_loss: 0.7431 Epoch 27/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.9807 - loss: 0.0592 - val_accuracy: 0.7971 - val_loss: 1.1145 Epoch 28/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 30s 48ms/step - accuracy: 0.9832 - loss: 0.0510 - val_accuracy: 0.8310 - val_loss: 0.8199 Epoch 29/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 29s 46ms/step - accuracy: 0.9830 - loss: 0.0536 - val_accuracy: 0.8454 - val_loss: 0.7514 Epoch 30/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 28s 45ms/step - accuracy: 0.9842 - loss: 0.0469 - val_accuracy: 0.8354 - val_loss: 0.8374 学習時間:885.8秒 パラメータ数:2,773,130 test_accuracy:0.8304
グラフ+サマリー
# ── val_accuracy / val_loss 比較グラフ ────────────────────
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
for label, h in histories.items():
axes[0].plot(h.history['val_accuracy'], label=label)
axes[1].plot(h.history['val_loss'], label=label)
axes[0].set_title('val_accuracy の比較(全30エポック)')
axes[1].set_title('val_loss の比較(全30エポック)')
for ax in axes:
ax.set_xlabel('Epoch'); ax.legend(); ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('residual_comparison.png', dpi=150)
plt.show()
# ── train_loss vs val_loss(過学習・収束の安定性)────────
fig2, axes2 = plt.subplots(2, 1, figsize=(7, 10))
for i, (label, h) in enumerate(histories.items()):
axes2[i].plot(h.history['loss'], label='train_loss')
axes2[i].plot(h.history['val_loss'], label='val_loss')
axes2[i].set_title(label)
axes2[i].set_xlabel('Epoch'); axes2[i].legend(); axes2[i].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('residual_overfit.png', dpi=150)
plt.show()
print("\n===== 最終結果サマリー =====")
print(f"{'Pattern':>14} | {'Val Acc':>8} | {'Test Acc':>9} | {'Time(s)':>8} | {'Params':>12}")
print("-" * 62)
for label in ['with_skip', 'without_skip']:
val_acc = histories[label].history['val_accuracy'][-1]
test_acc = scores[label][1]
t = times[label]
p = params[label]
print(f"{label:>14} | {val_acc:>8.4f} | {test_acc:>9.4f} | {t:>8.1f} | {p:>12,}")
print("-" * 62)
最終結果サマリー
===== 最終結果サマリー =====
Pattern | Val Acc | Test Acc | Time(s) | Params
--------------------------------------------------------------
with_skip | 0.8075 | 0.7963 | 926.0 | 2,816,010
without_skip | 0.8354 | 0.8304 | 885.8 | 2,773,130
--------------------------------------------------------------
実験結果
精度グラフ
損失グラフ
with_skip
without_skip
| パターン | 最終 val_accuracy | 最終 test_accuracy | パラメータ数 | 学習時間 |
|---|---|---|---|---|
| A:Residual接続あり | 80.75% | 79.63% | 2,816,010 | 926.0秒 |
| B:Residual接続なし | 83.54% | 83.04% | 2,773,130 | 885.8秒 |
考察
① 「スキップ接続あり」が負けた——なぜか?
今回の実験では、直感に反してResidual接続なし(B)の方がtest_accuracyで約3.4pt高いという結果になりました。「スキップ接続=精度が上がる」という単純な話ではないことが、この実験からよく分かります。
最大の原因は過学習の深刻さです。スキップありモデル(A)のtrain_accuracyは終盤で98.75%に達しているのに対し、val_accuracyは80.75%止まりで、乖離幅が約18ptあります。一方、スキップなしモデル(B)の乖離幅は約15ptで、Aより小さく抑えられています。
スキップ接続は勾配の流れを改善し、学習を速める効果があります。ところがそれが今回は「訓練データへの素早い過剰適合」として裏目に出ました。Aはearly epochから急激にtrain_lossを下げますが、val_lossは10エポック前後から上昇傾向に転じ、30エポック終盤では1.1を超えています。
② スキップなしのval_lossが序盤に暴れた理由
スキップなし(B)はEpoch 1〜2でval_lossが2.37→2.73と一時的に大きく跳ね上がっています。これはスキップ接続がない分、深い層への勾配伝達が不安定で学習の立ち上がりが遅いことを示しています。ただしEpoch 3以降は安定して収束しており、最終的にはAを上回る汎化性能を発揮しました。
③ 今回の設定が「スキップありに不利」だった
Residual接続が本来の効果を発揮するのは、BatchNorm・Data Augmentation・適切な正則化が整った条件下です。今回の実験はData Augmentationなし・Dropout 0.3のみという比較的シンプルな設定です。スキップ接続で学習が速くなったぶん、過学習の抑制が追いつかなかったと考えられます。
実際のResNetはData Augmentation(ランダムクロップ・水平反転など)を前提とした設計になっており、それが過学習を防ぐ重要な役割を担っています。
④ スキップ接続の効果を引き出すには
今回の結果を踏まえると、Residual接続を活かすためには以下のような追加の工夫が必要です。
- Data Augmentation(ランダムクロップ・水平反転)で訓練データの多様性を上げる
- EarlyStoppingでval_lossが上昇し始めたタイミングで学習を止める
- 学習率スケジューリング(ReduceLROnPlateau など)で後半の過学習を抑える
- Dropout率の引き上げ(0.3 → 0.5 など)
⑤ ResNetとの実装上の違い
本家のResNet-50はBottleneck Block(1×1→3×3→1×1の3層構成)を使っており、Data Augmentationも標準で組み込まれています。今回は2層構成のBasic Blockのみで実験したため、本来の性能を引き出せていない部分があります。Bottleneck Blockとの比較はまた別の記事で検証予定です。
関連記事もあわせてどうぞ:
- BatchNormalization vs Dropout → BatchNormalization と Dropout を組み合わせると精度はどう変わるか【Keras実験】
- GAPとFlattenの比較 → Global Average Pooling vs Flatten|CNNの最終層、どっちが精度・速度で有利か?【Keras実験】
- Conv2Dフィルター数の比較 → Conv2Dのフィルター数を変えると精度はどう変わる?【Keras実験】
- Residual接続(スキップ接続)は、入力
xをブロック出力に直接足すF(x) + xという構造で、勾配を浅い層まで届けやすくする - 今回の実験では、スキップ接続ありの方が学習は速かったが過学習も深刻で、test_accuracyはスキップなしに負けた(79.63% vs 83.04%)
- スキップ接続は「学習を速くする道具」であり、過学習への対策(Data Augmentation・EarlyStopping・学習率調整)をセットで使わないと逆効果になることがある
- Kerasでは
layers.Add()でスキップ接続を実装し、チャンネル数が変わる箇所は 1×1 Conv でショートカット側を変換する - ResNetが強いのはスキップ接続単体の力ではなく、Data Augmentationや学習率スケジューリングとの組み合わせによるもの





0 件のコメント:
コメントを投稿