背景
専門家からすれば私の考えに、それはねえ……と言いたいこともあるかもしれないが、自分としては、今よく知られている活性化関数に次のような不満があった。
特定の値を漸近線として持っている。(sigmoid,tanhなど)(嫌な理由:学習停止してしまうのでは?)
学習しない領域がある。(ReLU(以下relu))(嫌な理由:学習が止まってしまう)
マイナスがない。(relu, sigmoid)(例えばマイナス×マイナスはプラスと符合が反転する分表現力高まるはずでは?)
目的
自分が求める活性化関数を作り、既存の要旨られた活性化関数と比較することを目標にする。
求める活性化関数の要件は、
1,プラスマイナス両方の値を持っている。
2,漸近線を持たず緩やかに増減し続ける。
検討
二つの要件を満たす式としてグラフの形状から平方根を候補にした。
候補の問題
平方根はマイナスで虚数となってしまうし、0で傾きが無限大となってしまう。
問題の解決方法
虚数を解決する方法は、Tensorflowなら
tf.signとtf.absを使うことで解決できる。
0の時の傾き無限大の問題は、0の位置をシフトすれば解決できる。
そこで、できた関数のコードは
g_a = tf.constant(0.1)
g_aa = tf.constant(g_a * g_a)
def sqrt_180_degree_symmetry(x):
return tf.sign(x) * (tf.sqrt(tf.abs(x) + g_aa) - tf.sign(x) * g_a)
absでxの絶対値を取ることで虚数になることを防止。さらにsingで符号を反映させることでマイナスにも対応。そして少しだけxをシフト(定数マイナスし、その二乗をxに加える)することで0の時傾きが無限大にならないようにした。
環境
これを評価するためのサンプルコードをChatGPTに出力させた結果が以下のコードである。
import tensorflow as tf
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout
from tensorflow.keras.utils import to_categorical
import time
# CIFAR-10 データの読み込み
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
# 正規化
x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255
# ラベルを one-hot エンコーディング
y_train = to_categorical(y_train, 10)
y_test = to_categorical(y_test, 10)
# モデルの構築
model = Sequential([
Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=(32, 32, 3)),
Conv2D(32, (3, 3), activation='relu'),
MaxPooling2D(2, 2),
Dropout(0.25),
Conv2D(64, (3, 3), activation='relu', padding='same'),
Conv2D(64, (3, 3), activation='relu'),
MaxPooling2D(2, 2),
Dropout(0.25),
Flatten(),
Dense(512, activation='relu'),
Dropout(0.5),
Dense(10, activation='softmax')
])
# モデルのコンパイル
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
# モデルの訓練
history = model.fit(x_train, y_train, batch_size=64, epochs=50, validation_data=(x_test, y_test), verbose=2)
# モデルの評価
score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
print("time", time.time() - start_t)
loss = history.history['loss']
accuracy = history.history['accuracy']
val_loss = history.history['val_loss']
val_accuracy = history.history['val_accuracy']
import pandas as pd
df = pd.DataFrame({'loss': loss, 'accuracy': accuracy, 'val_loss': val_loss, 'val_accuracy': val_accuracy})
df.to_csv('fime_name.csv', index=False)
(*はじめMNIST用のサンプルコードを出してもらったが、正答率が高すぎで評価ができなかったので、cifar100用のコードを出してもらった。ただ今度はcifar100だと計算に時間がかかるのと、正答率もあまり高くなかったのでcifar10で落ち着いた。
また、精度を上げるためにデータを加工して増やす方法もあるが、今回は活性化関数の性質を見るのが目的なので、データを加工して増やす処理は意図的に入れていない)
評価は基本的にこのコードの活性化関数
activation=’relu’
の部分を置き換えることで行った。
tanhなら
activation=’tanh’
def custam_function: なら
activation=custam_function
という感じである。
結果
まずreluとtanhの結果を示す。この検証をしたときはsigmoidの値は正常に出力されなかったので、検証から外すことにした。(後でその上手くいかなかったデータを示すためにデータ出力用のコードを追加したら正常に出力されたが今回は、省く)
まずは良く知られているreluとtanhの結果を示す。accuracy(正答率)とloss(損失)の試行回数による変位を示す。(val_…はテストデータによる結果)。CSVの出力データも置いておく。
relu | tanh | |
function | ||
accracy | ||
loss | ||
csv | reluダウンロード | tanhダウンロード |
reluの方が正確さは優秀であるが、テスト用データval_accurancyが0.8程度で停滞しているなか、学習用データので正答率accurancyは上昇し続け、過学習に陥っているのが分かる、
tanhは学習にかかる試行数が少なく済んでいるだけかもしれないが、それでもtanhはaccracyとval_accuracy、lossとval_lossの差の広がりは小さい。
次に、平方根の活性化関数の結果を次に示す。
g_aの値がいくつがいいのかわからないので以下3つの値で調べてみた。結果は以下のようになった。
それぞれの値を選んだ理由は、
g_a = 0.1: x = 0付近の傾きの値が1以上になる.初期の学習の進みが早くなることを期待。ただし、勾配爆発の可能性はある。
g_a = 0.5: x = 0での傾きが1になる。他の活性化関数からも察するに傾き1が好まれているようなので。
g_a = 1 : x= 0 で今回勾配は1より小さくなる。どのような指数、累乗根でも1となる。特別な値。
g_a=0.1 | g_a=0.5 | g_a=1 | |
function | |||
accracy | |||
loss | |||
csv | sqrt_01ダウンロード | sqrt_05ダウンロード | sqrt_1ダウンロード |
特徴としては、全体としてテストデータの振れ幅が大きいこと。g_aの値が小さいほどわずかであるが、学習速度が速いのが見て取れる。また、reluよりは過学習に陥いっていない。
どのデータも、テストデータ学習データを先行している。
g_aの違いによる 正答率 は 1 < 0.5 < 0.1
g_aによる 過学習に陥りにくさ どれもほぼ同じ
このことからg_aは今回の実験の範囲では0.1がよさそうである。g_aが小さいほうがよさそうではあるが、もっと小さくした方がいいかどうかは、0に近づくと傾きが極めて大きくなるので、どのくらいがいいかは不明。
活性化関数ごとの結果は、
正答率は 平方根 < tanh < relu
過学習に陥りにくさ relu < tanh < 平方根
という結果になった。
今回作った平方根の活性化関数は、relu,tanhと比べて少しは個性を持つものになった。ただし、正答率より過学習が問題になる場面がどの程度あるかといわれると、微妙な結果となった。その過学習の優位性もtanhと比べてどのくらい優位にあるかはの具体的指標は不明である。
結論
1,プラスマイナス両方の値を持っている。
2,漸近線を持たず緩やかに増減し続ける。
の二つの要件を満たす平方根を主体とした活性化関数
g_a = tf.constant(0.1)
g_aa = tf.constant(g_a * g_a)
def sqrt_180_degree_symmetry(x):
return tf.sign(x) * (tf.sqrt(tf.abs(x) + g_aa) - tf.sign(x) * g_a)
を作成し、実際のネットワークに組み込んで機能していることを確認できた。
reluの正答率の考察
reluは学習データでは9割近いところまで正答率が上がっているが、tanhや平方根を180度対象にした関数では8割にも満たない。テストデータ(val_accurancy)でも、reluが一番いい成績を残している。
reluの何が正答率を上げる要因となっているのかを考えると、
reluと他の二つの違いで思いつくのは、他の2つがマイナスを取ることである。
マイナスを取ると、重みの値をマイナスの状態でマイナス方向に変位を追加した後で、入力値がプラスからマイナスになると値が反転してしまうのが良くないのかもしれない。出力を小さくしたいのに、結果は逆に大きくなってしまうという事が起こる可能性がある。
そこで次はプラスしかとらない関数で正答率がreluと同程度になるか試してみることにする。