0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

チュートリアルStep8

Last updated at Posted at 2024-10-04

スクリーンショット 2024-10-09 22.05.09.png

puyopuyo.gif
 ぷよぷよプログラミング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フレームワーク編を読んでください。本にも書かれていますが、自分で、ゼロから打ち込みながら勉強すると理解は進みます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?