チュートリアルStep10

![スクリーンショット 2024-10-09 22.05.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900427/ca46858b-10e5-0334-1a41-8a2f921cb43b.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


# Step10 Agent

# 10 Agentは特徴量抽出が大切。

 Agentとbrain(model)の使い分けてです。

 modelには、できる限り処理された情報のみを入れたほうが、効率よく学習が進みます。また、actionが存在するかどうかの処理や、actionが有効かどうかの処理などは、やめたほうがいい。また、データを管理して学習を効率よく進める部分なども、やめたほうがいい。そのような、入力と出力のルール化みたいところを、ラップでくるんで行うのがAgentです。

action = agent(state)

主に3つの機能を有するべきです。
* 入力データから特徴量を抽出する機能
* 出力データの形式を整える機能
* 学習を効率よく進める機能

## 10.1 コードを見ていきます。


```python
import copy
import numpy as np
import dezero_emb as dezero
class DQNAgent:
  def __init__(self):
    self.epsilon = CFG_ML.initial_epsilon
    self.action_size = 2

    self.replay_buffer = ReplayBuffer(CFG_ML.buffer_size, CFG_ML.batch_size)
    self.qnet = DQNet()
    self.qnet_target = DQNet()
    self.optimizer = dezero.optimizers.Adam(CFG_ML.lr)
    self.optimizer.setup(self.qnet)

  def __call__(self, board, puyo):
    action_list = utils.create_action_list(board)

    next_boards = []
    next_reward =[]
    action =(2, 1)
    if len(action_list):
      for action in action_list:
        next_board, reward, done = utils.next_board(board, puyo, action)
        if not done:
          next_boards.append(next_board)
          next_reward.append(reward)
      
      next_boards = np.stack(next_boards)
      predictions = self.eval2(next_boards)
      
      next_reward =np.array(next_reward)[:, np.newaxis]
      predictions += dezero.Variable(next_reward)
      index = predictions.data.argmax()
      action = action_list[index]
    return action


  def boardtostate(self, board):
    cont_b = 2 ** np.arange(CFG.Width,dtype=np.int32)
    b1 = np.zeros(CFG.Height * CFG.Width,dtype = np.int32).reshape(CFG.Height , CFG.Width)
    b1[board == 1] = 1
    b2 = np.zeros(CFG.Height * CFG.Width,dtype = np.int32).reshape(CFG.Height , CFG.Width)
    b2[board == 2] = 1
    b3 = np.zeros(CFG.Height * CFG.Width,dtype = np.int32).reshape(CFG.Height , CFG.Width)
    b3[board == 3] = 1
    b4 = np.zeros(CFG.Height * CFG.Width,dtype = np.int32).reshape(CFG.Height , CFG.Width)
    b4[board == 4] = 1
    board_list = np.concatenate([b1,b2,b3,b4])
    state =  board_list.dot(cont_b)      
    return state

  def eval(self, board):
    state = self.boardtostate(board)      
    return self.qnet_target(state)

  def eval2(self, boards):
    states = []
    for i in range(boards.shape[0]):
      state = self.boardtostate(boards[i])
      states.append(state)
    states = np.stack(states)      
    return self.qnet_target(states)


  def update(self, board, action, reward, next_board, done):
    state =  self.boardtostate(board) 
    next_state =  self.boardtostate(next_board)      
    
    self.replay_buffer.add(state, action, reward, next_state, done)
    if not done:
      return
    if len(self.replay_buffer) < CFG_ML.batch_size:
      return
    state, action, reward, next_state, done = self.replay_buffer.get_batch()

    qs = self.qnet(state)
    next_qs = self.qnet_target(next_state)
    reward =reward[:,np.newaxis]
    done =done[:,np.newaxis]
    target = reward + (1 - done) * CFG_ML.gamma * next_qs

    self.qnet.cleargrads()
    loss = dezero.F.mean_squared_error(qs, target)
    loss.backward()
    self.optimizer.update()


  def sync_qnet(self):
    self.qnet_target = copy.deepcopy(self.qnet)

  def save_model(self,filename):
    self.qnet.save_weights(filename)

  def load_model(self,filename):
    self.qnet.load_weights(filename)
    self.qnet_target.load_weights(filename)

```

## 10.2 init

初期化には、agentが持つデータが定義されます。４つあります。
* replay_buffer
* DQNet : 訓練対象
* target_DQNet : 評価用
* optimizer : lossは関数を使うので、optimizerだけです。
２つのmodelの使い分けですが、評価用のmodelで計算したpredictionsを使って、訓練用のmodelを学習させます。訓練後に、sync_qnetで同期します。

## 10.3 board to state

* cont_bは、boardの横１行のデータを２進法でビット化するために使います。これで、12行6列の72個のデータを、12個に圧縮して特徴量にします。4色あるので、288個のデータが、48個になります。
* np.concatenate([b1,b2,b3,b4]) : 4色の2次元データを連結しています。
* board_list.dot(cont_b) : board_list と cont_b の行列積を計算します。


## 10.3  call
agent()のように呼び出されたときに、この関数が動きます。
* action_list　から　next_board　を計算して　next_statesを作ります。fallblockは、blockを落とす場所をすれば、必ず決まった挙動をするゲームなので、計算可能です。計算ができないゲームでは、この手法は使えません。
* next_statesから、Qを推測します(predictions)。predictionsにrewardを加えて、一番大きいものを選択します。

## 10.4 update

* データをreplay bufferに入れていきます。
* done（終了）で、バッチデータから学習をします。
* qnet.cleargrads : 累積しているgradを０にします。
* loss.backward : 逆伝播させます。
* optimizer.update : パラメータに反映させます。