ぷよぷよプログラミングAI学習システムは、中高生の自己調整学習に向けて開発されました。
どうやって、Chromebookで、10分で、素敵な人工知能を作れるのでしょうか?
秘密は、3つ。
1 jupyter liteで、webクライアントがpythonコードを実行
2 超軽量な機械学習フレームワークdezeroを採用
3 ぷよぷよのstageとactionがコンパクト
Github&X
ぷよぷよプログラミングAI学習用まとめ
チュートリアルまとめ
Step8 basic DQN & brain
8 DQNの基礎
深層強化学習の基本である、DQNの基礎から始めます。
8.1 人工知能と機械学習の基本
人工知能は、ある状態から、何かを推測します。例えば、写真から、映っているのが犬だと推測します。この推測をevaluationあるいは、predictionと英語で書くことが多く、プログラムにもよく見られます。evaluationは、value(価値)をつけるという言葉なので、ある状態から推測するということは、何かの価値をつけるということでもあります。
人工知能の推測できる正確性は、良くても80%くらいです。複数の可能性を同時に出すことが多くて、例えば、写真に写っているものの可能性が、イヌ80%、ネコ50%、ウマ10%のように推測します。そこで、一番高い可能性のものだけを表示していることがよくあります。
人工知能の一部には、推測ではなくて決定だけをするものがあります。それは、あらかじめ決められた計算式で答えを出すものです。例えば、2つの数値が与えられた場合に、必ず合計を出すものです。これが人工知能と呼べるかどうかは、疑問ではありますが、強化学習においてはheuristic、経験則などと呼ばれています。
heuristicが人工知能と呼べるか疑問なのは、学習が存在しないからです。経験値であるデータが積み上がり、それを統計処理して経験則を算出して、計算式を更新することはできますが、これを学習というかは微妙です。
機械学習は、人工知能が学習することです。大量のデータを統計処理して、経験則を出すことは、極めて高度な数学を要するかもしれません。それを、「だいたい正しい」方法にするのが、人工知能の役割です。この「だいたい正しい」という部分が、とても大切です。
例えば、優勝チームを推測することを考えてみます。まだ試合をしていないのだから、何が起きるかは分かりません。しかし、それまでの経験から、守備力が強いチームが勝つとか、攻撃力の強いチームが勝つとか、いろいろな特徴から推測はできそうです。この特徴を数値化して、過去の環境を再現して、勝敗をつけることができれば、良さそうです。
このように、機械学習の基本は、過去のデータから、特徴量を抽出して、その環境を再現して、結果を出して、訓練することです。画像系の機械学習では、環境再現部分がなくて、過去のデータと結果だけで訓練することが多く、ゲームなどの強化学習では、過去のデータがほとんどありません。自然言語系は、その中間です。
自然言語系が、環境の再現をしているかについては、chatGPTなどの現在の商用AIは、内容を公開していないので分かりません。過去のtransformerは学習に使用していました。文を構成する単語間の結びつきを「人が文を理解する環境」として再現して、学習を進めています。
機械学習は英語で、machine learning でありMLと略されます。学習はlearningですが、trainが良く使われます。
8.2 brain model
人工知能の脳モデルを、brainあるいはbrain model、もっと略してmodelと呼びます。
まずは、modelの例を見てみます。
import dezero_emb as dezero
class DQNet(dezero.Models.Model):
def __init__(self):
super().__init__()
self.l1 = dezero.L.Linear(128)
self.l2 = dezero.L.Linear(128)
self.l3 = dezero.L.Linear(1)
def forward(self, x):
x = dezero.F.relu(self.l1(x))
x = dezero.F.relu(self.l2(x))
x = self.l3(x)
return x
dezeroは、ゼロから作るDeep Learning 3 フレームワーク編で紹介されているニューラルネットワークのフレームワークです。著者も中で書いていますが、基本はchainerです。chainerの開発はすでに終了しています。dezero_embは、jupyterliteのpyodideで動かすために、まとめて、一部変更したものです。
Modelクラスの親クラスはLayerクラスで、Layerクラスの親クラスはVariableクラスです。
DQNet <- Model <- Layer <- Variable
Variableは、tensorと呼ばれているものに近い。
Variableは、Variable間のつながりを重視しており、順伝播(forward)と逆伝播(backward)を実装しています。そして、つながりをつけることで、自動的に順伝播だけでなく逆伝播がつながります。逆伝播が自動的につながることを自動微分とも言います。微分は、曲線の傾きであることから、傾きの英語であるgradient、略してgradが使われます。
forward関数に、x = F.relu(self.l1(x))のように、書かれているのが、つながりを示すものです。このように書くだけで、自動的に逆伝播も定義されています。
8.3 Linear
L.Lenearは、Lenear層です。コードをdezero_embから出します。
class Linear(L.Layer):
def __init__(self, out_size, nobias=False, dtype=np.float32, in_size=None):
super().__init__()
self.in_size = in_size
self.out_size = out_size
self.dtype = dtype
self.W = Parameter(None, name='W')
if self.in_size is not None:
self._init_W()
if nobias:
self.b = None
else:
self.b = Parameter(np.zeros(out_size, dtype=dtype), name='b')
def _init_W(self, xp=np):
I, O = self.in_size, self.out_size
W_data = xp.random.randn(I, O).astype(self.dtype) * np.sqrt(1 / I)
self.W.data = W_data
def forward(self, x):
if self.W.data is None:
self.in_size = x.shape[1]
xp = cuda.get_array_module(x)
self._init_W(xp)
y = F.linear(x, self.W, self.b)
return y
y = w * x + b
行列計算になっています。入力x に、重みwを掛けて、バイアスbを足しています。
8.4 Relu & Sigmoid
functionで実装していますが、Relu層とSigmoid層して実装されることの多いレイヤーです。活性化関数ともいわれます。
Reluは、正の数しかとさないフィルターで、Sigmoidは、シグモイド曲線に合わせて0から1までの範囲で出力します。
linear層は行列計算なので、数値の掛け算の和になるため、オーバーフローしないようにするために使います。
8.5 conv2d & deconv2d
畳み込みニューラルネットワークで有名なconv2dとdeconv2dです。dezeroはどちらもあります。
class Conv2d(L.Layer):
def __init__(self, out_channels, kernel_size, stride=1,
pad=0, nobias=False, dtype=np.float32, in_channels=None):
super().__init__()
self.in_channels = in_channels
self.out_channels = out_channels
self.kernel_size = kernel_size
self.stride = stride
self.pad = pad
self.dtype = dtype
self.W = Parameter(None, name='W')
if in_channels is not None:
self._init_W()
if nobias:
self.b = None
else:
self.b = Parameter(np.zeros(out_channels, dtype=dtype), name='b')
def _init_W(self, xp=np):
C, OC = self.in_channels, self.out_channels
KH, KW = utils.pair(self.kernel_size)
scale = np.sqrt(1 / (C * KH * KW))
W_data = xp.random.randn(OC, C, KH, KW).astype(self.dtype) * scale
self.W.data = W_data
def forward(self, x):
if self.W.data is None:
self.in_channels = x.shape[1]
xp = cuda.get_array_module(x)
self._init_W(xp)
y = F.conv2d(x, self.W, self.b, self.stride, self.pad)
return y
class Deconv2d(L.Layer):
def __init__(self, out_channels, kernel_size, stride=1,
pad=0, nobias=False, dtype=np.float32, in_channels=None):
super().__init__()
self.in_channels = in_channels
self.out_channels = out_channels
self.kernel_size = kernel_size
self.stride = stride
self.pad = pad
self.dtype = dtype
self.W = Parameter(None, name='W')
if in_channels is not None:
self._init_W()
if nobias:
self.b = None
else:
self.b = Parameter(np.zeros(out_channels, dtype=dtype), name='b')
def _init_W(self, xp=np):
C, OC = self.in_channels, self.out_channels
KH, KW = utils.pair(self.kernel_size)
scale = np.sqrt(1 / (C * KH * KW))
W_data = xp.random.randn(C, OC, KH, KW).astype(self.dtype) * scale
self.W.data = W_data
def forward(self, x):
if self.W.data is None:
self.in_channels = x.shape[1]
xp = cuda.get_array_module(x)
self._init_W(xp)
y = F.deconv2d(x, self.W, self.b, self.stride, self.pad)
return y
8.6 モデルの保存と読み込み
dezeroのモデルは、モデルを保存するsave_weightsと読み込むload_weightsを持っています。どちらも中身は、numpyの保存と読み込みです。
8.7 loss
学習をするときに、モデルが出した推測値と学習用の期待値を比較します。例えば、モデルは70点と出したけれど、80点と出してほしかったとします。そこで、モデルに80点を出すように変更を加えるのですが、その差である10点に着目します。
この10点を損失lossとよびます。
この10点分だけ補正をモデルにかけるべきでしょうか?
そもそも、モデルは「だいたい正解」であればいいので、どんな状態の入力であっても、「だいたい正解」に近い推測値が出てくれればいい。それじゃあ、「だいたい正解」をどうすればわかるのでしょうか?
そこで、ある程度の数をまとめて処理して、「だいたい正解」を出します。線形回帰あるいは、平均2乗誤差mean_squeared_errorと呼ばれています。ある程度の数は、バッチ数と呼ばれます。
最適な学習用数値を出すために、backwardを使用します。
class MeanSquaredError(Function):
def forward(self, x0, x1):
diff = x0 - x1
y = (diff ** 2).sum() / len(diff)
return y
def backward(self, gy):
x0, x1 = self.inputs
diff = x0 - x1
gx0 = gy * diff * (2. / len(diff))
gx1 = -gx0
return gx0, gx1
8.8 optimizer
lossで計算された、最適な学習用数値は逆伝搬されて、各モデル内の層に差分として残ります。この差分からウエイト、バイアスの数値を変更するのが、optimizerです。optimizerの違いが、学習効率にどの程度の影響があるかは、よく分かりません。
8.9 詳細は
dezeroの詳細は、ゼロから作るDeep Learning3フレームワーク編を読んでください。本にも書かれていますが、自分で、ゼロから打ち込みながら勉強すると理解は進みます。