チュートリアルStep8

![スクリーンショット 2024-10-09 22.05.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900427/a43f17a4-08dc-e581-77fd-c22118b3d575.png)


![puyopuyo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900427/1fdf8221-96c5-fcfa-3a52-383422a6fdc3.gif)
　ぷよぷよプログラミングAI学習システムは、中高生の自己調整学習に向けて開発されました。
　どうやって、Chromebookで、１０分で、素敵な人工知能を作れるのでしょうか？
　秘密は、３つ。
　１　jupyter liteで、webクライアントがpythonコードを実行
　２　超軽量な機械学習フレームワークdezeroを採用
　３　ぷよぷよのstageとactionがコンパクト

### Github&X

https://github.com/Lunanaedu/jupyterlite-puyopuyo

https://github.com/Lunanaedu/puyopuyo-kai

https://x.com/educationai_ai

### ぷよぷよプログラミングAI学習用まとめ

https://qiita.com/educational-ai/items/50c0fa689c01794d145e

### チュートリアルまとめ

https://qiita.com/educational-ai/items/88feaf91bdda0b5e0d25



# Step8 basic DQN　& brain

# 8 DQNの基礎

深層強化学習の基本である、DQNの基礎から始めます。

## 8.1 人工知能と機械学習の基本

　人工知能は、ある状態から、何かを推測します。例えば、写真から、映っているのが犬だと推測します。この推測をevaluationあるいは、predictionと英語で書くことが多く、プログラムにもよく見られます。evaluationは、value（価値）をつけるという言葉なので、ある状態から推測するということは、何かの価値をつけるということでもあります。

　人工知能の推測できる正確性は、良くても８０％くらいです。複数の可能性を同時に出すことが多くて、例えば、写真に写っているものの可能性が、イヌ８０％、ネコ５０％、ウマ１０％のように推測します。そこで、一番高い可能性のものだけを表示していることがよくあります。

　人工知能の一部には、推測ではなくて決定だけをするものがあります。それは、あらかじめ決められた計算式で答えを出すものです。例えば、２つの数値が与えられた場合に、必ず合計を出すものです。これが人工知能と呼べるかどうかは、疑問ではありますが、強化学習においてはheuristic、経験則などと呼ばれています。

　heuristicが人工知能と呼べるか疑問なのは、学習が存在しないからです。経験値であるデータが積み上がり、それを統計処理して経験則を算出して、計算式を更新することはできますが、これを学習というかは微妙です。

　機械学習は、人工知能が学習することです。大量のデータを統計処理して、経験則を出すことは、極めて高度な数学を要するかもしれません。それを、「だいたい正しい」方法にするのが、人工知能の役割です。この「だいたい正しい」という部分が、とても大切です。

　例えば、優勝チームを推測することを考えてみます。まだ試合をしていないのだから、何が起きるかは分かりません。しかし、それまでの経験から、守備力が強いチームが勝つとか、攻撃力の強いチームが勝つとか、いろいろな特徴から推測はできそうです。この特徴を数値化して、過去の環境を再現して、勝敗をつけることができれば、良さそうです。

　このように、機械学習の基本は、過去のデータから、特徴量を抽出して、その環境を再現して、結果を出して、訓練することです。画像系の機械学習では、環境再現部分がなくて、過去のデータと結果だけで訓練することが多く、ゲームなどの強化学習では、過去のデータがほとんどありません。自然言語系は、その中間です。

　自然言語系が、環境の再現をしているかについては、chatGPTなどの現在の商用AIは、内容を公開していないので分かりません。過去のtransformerは学習に使用していました。文を構成する単語間の結びつきを「人が文を理解する環境」として再現して、学習を進めています。

　機械学習は英語で、machine learning　でありMLと略されます。学習はlearningですが、trainが良く使われます。

## 8.2 brain model

　人工知能の脳モデルを、brainあるいはbrain model、もっと略してmodelと呼びます。
まずは、modelの例を見てみます。


```python
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から出します。


```python
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

行列計算になっています。入力ｘ　に、重みwを掛けて、バイアスbを足しています。

## 8.4 Relu　&　Sigmoid

functionで実装していますが、Relu層とSigmoid層して実装されることの多いレイヤーです。活性化関数ともいわれます。
Reluは、正の数しかとさないフィルターで、Sigmoidは、シグモイド曲線に合わせて０から１までの範囲で出力します。

linear層は行列計算なので、数値の掛け算の和になるため、オーバーフローしないようにするために使います。

## 8.5 conv2d & deconv2d

畳み込みニューラルネットワークで有名なconv2dとdeconv2dです。dezeroはどちらもあります。


```python
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を使用します。


```python
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　Learning３フレームワーク編を読んでください。本にも書かれていますが、自分で、ゼロから打ち込みながら勉強すると理解は進みます。