汎用性
「学習は以上のようにして行われます。次は汎用性です。この簡単なプログラムは足し算だけではなく、引き算も学習できます。プログラムの学習する部分には変更がありません。正解の計算の仕方をかえるだけです。
# 正解
t = x1 + x2
を
# 正解
t = x1 - x2
に変更するだけです」
「その上の行は」
if x2 > x1:
x2, x1 = x1, x2 # x1, x2 の大きい方をx1とし、x1-x2 が正の範囲とします。
と書かれているところをミアが指さす。
「これは、正解を正の値にしておきたいので、x1がx2より小さいときに両者を入れ替えているだけで、学習の仕組みそのものには関係ありません」
「学習の核心部分は変更していません」そういって、アレックスがプログラムの核心部分をマウスカーソルで示す。
# プログラムによる解答
y = w1 * x1 + w2 * x2 # 積和計算でプログラムが予測する解答 y を計算
# 誤差に基づく差分
dw1 = (t - y) * x1 # # 正解と解答との差 t-y に x1 を掛ける
dw2 = (t - y) * x2 # 線形回帰:二乗誤差E = (t-y)^2の最小化
# ∂E/∂w1 = ∂E/∂y・∂y・∂w1= 2*(t-y)*x1
# ウェイト更新
w1 += eta * dw1 # ウェイトを微調整する
w2 += eta * dw2
「では、これを実行してみましょう」アレックスが実行コマンドを入力する。
「どうです、先ほどより少し収束は遅いですが、それでも学習しています」
「そういうことか」
「汎用的、ということね」
「掛け算もできるの」エレナが尋ねる。
「残念ながら、この『記憶セル』は単純すぎて、一般的な掛け算はできません。水をさすようですが、ちょっと種明かしのようなことをしましょう」アレックスが答えた。
「このグラフで緑は正解との差ですが、青と赤のグラフを見てください」
「青は ウェイトw1 の、赤は w2の推移ということでしょう」
「そうです。学習と共に、青が1に、赤がマイナス1に近づいています」
「そうね」
「先ほどの『記憶セル』
y = w1 * x1 + w2 * x2 # 積和計算でプログラムが予測する解答 y を計算
に、w1=1, w2=-1 を入れてみると、こうなります」
y = (1)*x1 + (-1)*x2 = x1 - x2
「なんだ、あらかじめ『記憶セル』の中に組み込まれていた、というわけか。足し算の時には両方が1になっていたな」マルコが言った。
y = (1)*x1 + (1)*x2 = x1 + x2
「この場合にはそうです。先ほど種明かしのようなこと、といったのはこのことです。ただし、w1=1 、w2=-1という値は学習によって得られたものです」
「また、この『記憶セル』は、汎用性を保ったまま、複雑にすることができます。現在は要素が2つだけのベクトル同士の積和ですが、ベクトルの要素を増やしたり、ウェイトを行列やテンソルにすることができます。2060年の年初時点で、代表的な最高性能の『生成AI』は数百兆ものパラメーターを持っています」
「拡張はできるでしょうね」ミアが言う。
「本質的に重要なのは、我々が足し算を、プログラミングなどで指示したのではないということです」
「問題と正解のセットだけから、『記憶セル』が『足し算』の関係を学習したということね」
「そうです。そして、限界はありますが、別の種類の問題でも同一の『記憶セル』を使って学習できる、ということです。汎用性です」
「たとえば、正解をつぎのように変更してみましょう」
# 正解
t = (x1 + x2)/2
「二つの数字の平均を求めるプログラムです。これも学習できます。w1,w2それぞれが 0.5
に近づきます」
「なるほど、汎用的だな。たった2つのパラメーターにしては優秀だ」
「『生成AI』って、一番ベーシックな部分では、このような事をしているのね」エレナが言った。
「そうです、そして核心部分は正解との差によるパラメタの修正です。実際のシステムでは、誤差からパラメーターをどちらにどれだけ修正すべきかを、微分を使って計算します。このように誤差を逆向きに伝えて勾配を計算する仕組みを『バックプロパゲーション』といいます」
「以降は、『記憶セル』を数式ないしは模式図であらわしていきますが、裏ではこの『バックプロパゲーション』が動いていて、セル内のウェイトないしはパラメーターを修正している、ということを覚えておいてください」
「積和計算の部分ですが、ここでは簡単な形式で書きましたが、一般的には以下のようにバイアスを加えています」
y = w1 * x1 + w2 * x2 + b
です。そして、これは数学で言う所の『アフィン変換』の形になっています。、なので、このように略記されます」
w1 * x1 + w2 * x2 + b = affine(w,x)
「今回は w はベクトルでしたが、一般的なニューラルネットワークでは、ウェイトは行列として扱われます」
「少し早いですが、みなさん、昼食にしませんか」アレックスが言った。
アレックスが実行した『引き算』のプログラムは、以下の通りです。
import random
import matplotlib.pyplot as plt
# 初期ウェイト
w1 = 0.0
w2 = 0.0
eta = 0.05 # 学習率
epochs = 100
# グラフ用の記録リスト
w1_list = []
w2_list = []
err_list = []
for i in range(epochs):
# 入力(0〜3)
x1 = random.randint(0, 3) # 0から3の整数を乱数として発生→x1
x2 = random.randint(0, 3) # 0から3の整数を乱数として発生→x2
if x2 > x1:
x2, x1 = x1, x2 # x1, x2 の大きい方をx1とし、yが正の範囲とします。
# 正解
t = x1 - x2
# プログラムによる解答
y = w1 * x1 + w2 * x2 # 積和計算でプログラムが予測する解答 y を計算
print(f'{i+1}回目:解答は {y} かな。違うかな')
print(f'違うよ、正解は {t} だよ、正解との差は {t-y } だね。\n')
# 記録
w1_list.append(w1)
w2_list.append(w2)
err_list.append(t-y)
# 誤差に基づく差分
dw1 = (t - y) * x1 # 正解と回答との差 t-y を計算する
dw2 = (t - y) * x2 # 線形回帰:二乗誤差E = (t-y)^2の最小化
# ∂E/∂w1 = ∂E/∂y・∂y・∂w1= 2*(t-y)*x1
# ウェイト更新
w1 += eta * dw1 # ウェイトを微調整する
w2 += eta * dw2
# テスト
print("\nテスト:")
for x1 in range(4):
for x2 in range(4):
if x2 > x1:
x2, x1 = x1, x2 # x1, x2 の大きい方をx1とし、yが正の範囲とします。
y = w1 * x1 + w2 * x2
print(f"{x1} - {x2} = {y:.2f}(正解 {x1-x2})")
# グラフ描画
plt.figure(figsize=(10, 6))
plt.plot(w1_list, label="w1")
plt.plot(w2_list, label="w2")
plt.plot(err_list, label="t - y (error)")
plt.xlabel("epochs")
plt.ylabel("value")
plt.title("Learning curves of w1, w2, and error")
plt.legend()
plt.grid(True)
plt.show()




