2021年1月30日土曜日

実践!Chainerとロボットで学ぶディープラーニング(1)

環境構築

今回Mindstorms EVを買うきっかけになった1つが、Mindstormsを使ってChainerによる強化学習を勉強できるとの記事を目にしたことです。それでアフレルからディープラーニングセットを購入しましたが、RasberryPiを使ったりハードルは高そうです・・・。

とにかくテキスト「実践!Chainerとロボットで学ぶディープラーニング」を参考にまず環境構築を行っていきます。

1.raspberry PiにLinuxOSをインストール

テキストp214からのLinuxOSのインストールは、2021年1月現在もっと簡単にできるようです。

PaspberryPiのホームページからRaspberry Pi Imagerをダウンロードします。


インストール後、Raspberry Pi Imagerを開きます。
CHOOSE OSでPaspberry Pi OS(32bit)を選びます。これで良いのかな?
次にCHOOSE SD CARDでPCに接続したSDカードを選択し、WRITEで書込みを実施します。
これでテキストp222の「1.6イメージファイルをSDカードに書き込む」まで終了です。
簡単になりました。

2.SSH有効化

SDカードにOSのインストールが完了したら次は、RaspberryPiにアクセスするために、通信環境(SSH通信)を整備します。

テキストもわかり易いですが、ラズパイ(raspberry pi)の初期設定をするよを参考にしました。「SSH有効化」のところからです。

ここから設定に使うファイル(raspberry-pi-setup-master.zip)をダウンロードします。
このzipファイルを解凍すると下記のファイルがあるので、sshファイルをOSを書き込んだSDカードに入れます。これでSSHの準備完了です。

3.WiFiの有効化

次にWiFiが使えるように設定します。
上の解凍したraspberry-pi-setup-masterの中にあるwpa_supplicant.confをテキストエディタか何かで開いてください。

ここで、

ssid="ssid"の""で囲まれた部分を使っているWiFiのSSID
psk="pwd"のpwd部分を使っているWiFiのパスワード

に書き換えて、sshファイルと同じくOSを書き込んだSDカードに入れます。これでWiFiの準備も完了です。

これでテキストのp225まで終了です。それではRaspberryPiを起動します。私はMacを使っていますのでテキストではp240のRaspberry Piへのssh接続になります。

4.SSH接続

作成したSDカードをPaspberry Piに挿入してください。バッテリーを繋げるとRaspberry Pi OSが起動します。

a)LANケーブルでのSSH接続
テキストではPCとLANケーブルで接続とありますが、同一ネットワーク上であれば何処でもOKです。
ターミナルを開きssh pi@respberrypi.localを入力します。
パスワードを要求されるのでraspberryと入力します。

下記のように表示されればssh接続でRaspberry Piにログインできました。

b)無線でのSSH接続
先程のLANケーブルを抜いておきます。
はじめにPアドレスを特定する必要があります。ターミナルを開きping raspberrypi.localを入力します。
画面にraspberrypiのIPアドレスが表示されます。今回は192.168.1.8でした。このIPアドレスは接続の度に変わりますので注意が必要です。なこのIPアドレスは、接続の度に変わると厄介なので固定することもできます。
先程のIPアドレスを使って、ターミナルからssh pi@192.168.1.8と入力します。

yes/noを聞いてくることがあるのでyes
下の画面が出てくればRaspberryPiとの接続完了です。
これでテキストp245まで終了です。

あとは、テキストに沿って

RaspberryPiの環境構築
RaspberryPi Cameraの導入
EV3の実行環境構築
JupyterLabの起動確認

を行っていけば環境構築は終了です。
と言いたいところですが、最後のJupyter Labの起動が上手くいきません。
セッティングで何処かにミスがあったのかも知れません。

そこで、VNC接続でRaspberryPiとに接続してJupyter Labを開くことにしました。
VNC接続も先のラズパイ(raspberry pi)の初期設定をするよに解りやすく載っていて簡単にできます。

1.PaspberryPiの設定
  • ターミナル(Macの場合)からping raspberrypi.localと入力しRaspberryPiのIPアドレスを確認します。


    この例では、192.168.1.9でした。

  • 同じくターミナルからssh pi@192.168.1.9と入力し、ssh接続をします。
    IPアドレスはそれぞれの環境で異なります。
    もし、WARNINGが出たら過去に接続した記録が残っているそうです。設定を一度削除します。


    ターミナルからssh-keygen -R 192.168.1.9と入力し、設定を一度消去後に再度ssh pi@192.168.1.9と入力してssh接続を確立します。

  • ターミナルからsudo raspi-configと入力し、RaspberryPiの設定画面を開きます。

  • 3のInternet Optionを選択し、P3のVNC選択、yesと進みます。
  • 1のSystem Optionを選択し、S5のBoot/Auto Login選択、B4のDesktop Autologin選択。この設定をしないとVNC接続ができないとの事。

  • 2のDisplay Optionを選択し、D1のResolution選択、リモート接続した際のRaspberryPiの画面設定を行います。

  • 8のUpdateを選択し、Finishで終了です。

  • ターミナルからsudo rebootと入力しRaspberryPiを再起動します。


2.PCにVNCアプリ「VNC Viewer」をインストール

VNC CONNECT画面で、RaspberryPiのIPアドレス192.168.1.9を入力すれば接続完了です。

Username:pi
Password:raspberry

です。


EV3でライントレース(P制御)

 前回EV3を買って直ぐにライントレースをしてみました。部品は本体以外はNXTです。検知部分はEV3のカラーセンサーではありませんが考え方は同じでしょう。

検知センサーで白色の時と黒色の時の中間のデータ(この場合45)を境にそれより大きい場合、小さい場合とで、タイヤを右に回したり左に回したりしますので、必然的にジグザグ走行となります。

プログラムはシンプルに


です。カーブはともかく直線では真っ直ぐに進みたいですね。そこでP制御(と言っていいのかな?、素人なので分かりません)を組み込んでみました。

P制御を含めPID制御についてはここに解りやすく解説があります。
ロボット制御 – PID制御について

プログラムは


white・・・白地のセンサーの値

black・・・黒地のセンサーの値

mean・・・白地と黒地の中間の値

actual・・・走行中のセンサーの値

steer・・・(actual-mean)でmeanからかけ離れているほど大きく回転させるようにする。係数の-4.5はセンサーやコースの状況で試行錯誤で決定するパラメーター(いわゆるハイパーパラメーター)
今回NXTのセンサーを使っていますが、EV3のカラーセンサーを使うと全く異なった数値になると思います。

結果の動画をアップしておきます。




まずは目的達成ですか。



2021年1月25日月曜日

Mindstorms EV3買っちゃいました

 Mindstormsこれで3台目です。RCXからNXT、そしてEV3。EV3も2013年に発売されてそろそろ後継機が?と思いますが、発売されて年数が経っているEV3なので、皆さんが色々な遊び方を紹介してくれているデータの豊富さを考えると、逆に買い時かなと思いました。

それと強化学習の本を読んでいて、迷路、CartPoleなどやっているとライントレース、それもMindstormsを使った事例が紹介されていたのでつい手が出てしまいました。

Mindstormsが届いた日に作りました。

ドライビングベース

本体以外はNXTの部品です

RCXもNTXも本体は壊れて動かなくなってしまいましたが、部品は殆ど修正することなく使えますね。


お決まりのシンプルなプログラムでシンプルなコースでのライントレースです。

自分でコースを作ってみました。


ジグザグでなんとか動いています。これが強化学習で滑らかな動きになるのでしょうか。

そのためにはRaspberryPiを動かしたりハードルはめちゃくちゃ高そうです。

2021年1月16日土曜日

Chainer Cupyのインストールで躓いたところのメモ

本当にメモ書きです。すみません。m(_ _)m

★cupyのインストール方法

cupyを単純にインストールしようとすると(pip install cupy-cuda)バージョンエラーが帰ってくる。

pip install cupy-cuda102==7.8

とバージョン指定が必要


win10では下記2項目の書き換えで強化学習プログラムが動くように

★gymで

AttributeError: 'EnvSpec' object has no attribute 'timestep_limit'

pip instll gym==0.12.1

にダウングレードで解決


★pyglet

'ImageData' object has no attribute 'data'

pip install pyglet==1.2.4

で解決




2021年1月11日月曜日

つくりながら学ぶ!深層強化学習 迷路(1)

先日「つくりながら学ぶ!深層強化学習」を読んで、本の例題が3✕3の迷路だったで自由な大きさの迷路を自動で作成するプログラムを作りました。と言っても

子供のために迷路を自動作成する

を使わせていただきました。これを使って第2章の迷路課題をおさらいしてみます。

まずは2.2のエージェントをランダムに動かしてゴールに向かう方策で実行してみる。

▼使用するパッケージの宣言

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

▼迷路の自動作成の為の関数の定義

def direction():
  '''
  進行方向を選択。
  戻り値は順に「現在地の破壊壁方向」「進行部屋の壁破壊方向」「進行部屋の部屋番号」
  '''
  rand = np.random.randint(0, high=4)
  if rand == 0: return 0, 2, -1 * MAZE_SIZE  # 上
  if rand == 1: return 1, -1, 1  # 右
  if rand == 2: return 2, 0, MAZE_SIZE  # 下
  return -1, 1, -1  # 左
  
  def check_cell(cells, c_cell, d_cell, dire):
  ''' 進めるかチェック '''
  # 進んだ部屋が迷路外かどうか
  # 上下へのはみ出し
  if d_cell < 0 or MAZE_CELL <= d_cell: return False
  # 部屋番号+1が迷路サイズで割り切れる場合、右端の部屋。右には進めない。
  if (c_cell+1) % MAZE_SIZE == 0 and dire == 1: return False
  # 部屋番号+1が迷路サイズで割り、余り1の場合、左端の部屋。左には進めない。
  if (c_cell+1) % MAZE_SIZE == 1 and dire == -1: return False
  # 現在地部屋番号 = 進んだ部屋番号。同じ部屋の場合は処理しない
  if cells[c_cell] == cells[d_cell]: return False
  # それ以外は進める
  return True
  
  def choice_cell_no(cells, c_cell, d_cell):
  ''' 小さい方の部屋番号を取得 '''
  if cells[c_cell] < cells[d_cell]:
    return cells[c_cell], cells[d_cell]
  return cells[d_cell], cells[c_cell]
  
  def create_maze():
  ''' 迷路を生成 '''
  # 初期状態を生成
  # [上, 右, 下, 左] = 1:壁 / 0:通路
  lst_maze = np.ones([MAZE_CELL, 4])
  # 部屋のナンバリング、最終的に全部0にする。
  lst_cells = np.array([i for i in range(0, MAZE_CELL)])

  while True:
    # すべての部屋が番号0の時は処理終了
    if np.sum(lst_cells) == 0: break

    # 壁を破る元のセルを選択(現在地)
    int_choice_cell = np.random.randint(0, high=MAZE_CELL)
    # 既にスタート地点とつながっている(0)時は処理しない。
    if lst_cells[int_choice_cell] == 0: continue

    # 進行方向を選択
    int_now_wall, int_direction_wall, int_direction = direction()
    # 進んだときのセル
    int_direction_cell = int_choice_cell + int_direction

    # 値のチェック
    if not check_cell(lst_cells, int_choice_cell, int_direction_cell, int_direction):
      continue

    # 部屋番号を確保
    int_min_value, int_max_value = choice_cell_no(lst_cells, int_choice_cell, int_direction_cell)

    # 小さい方の部屋番号に統一する
    lst_cells[lst_cells == int_max_value] = int_min_value

    # 壁も更新する
    lst_maze[int_choice_cell][int_now_wall] = 0
    lst_maze[int_direction_cell][int_direction_wall] = 0

  return lst_maze 

▼作成する迷路の大きさ(1辺のセルの数)を入力

MAZE_SIZE = int(input('maze size->'))  # 迷路の1辺の長さ
MAZE_CELL = MAZE_SIZE * MAZE_SIZE  # 迷路のセル数

ここでは例として5✕5の迷路をつくりました。

迷路を生成
MAZE_OUT = create_maze()
MAZE_OUTの中身は 

[[1. 0. 1. 1.]
 [1. 0. 0. 0.]
 [1. 0. 1. 0.]
 [1. 0. 1. 0.]
 [1. 1. 1. 0.]
 [1. 1. 0. 1.]
 [0. 0. 0. 1.]
 [1. 0. 0. 0.]
 [1. 0. 1. 0.]
 [1. 1. 0. 0.]
 [0. 0. 0. 1.]
 [0. 1. 1. 0.]
 [0. 0. 0. 1.]
 [1. 1. 1. 0.]
 [0. 1. 0. 1.]
 [0. 1. 0. 1.]
 [1. 0. 1. 1.]
 [0. 0. 0. 0.]
 [1. 1. 1. 0.]
 [0. 1. 1. 1.]
 [0. 1. 1. 1.]
 [1. 0. 1. 1.]
 [0. 0. 1. 0.]
 [1. 0. 1. 0.]
 [1. 1. 1. 0.]]

と5✕5=25個のセルの状態を表しています。上からs0→s24です。 各々のセルの4つの0または1の意味は、順番にセルの上、右、下、左の様子を表していて、 0は通路(進行可能)、1は壁(進行不可)を示しています。
例えば一番上のs0[1. 0. 1. 1.]は、セルの上、下、左が壁で、右にしか進めないことを示しています。 

 ▼初期位置での迷路の様子
# 図を描く大きさと、図の変数名を宣言
fig = plt.figure(figsize=(MAZE_SIZE, MAZE_SIZE))
ax = plt.gca()

# 状態を示す文字S0~S※を描く
# 赤い壁を描く
s_num =0
for gyo in range(MAZE_SIZE):
#for gyo in range(2):
  for retu in range(MAZE_SIZE):
  #for retu in range(1):
   
    plt.text(retu+0.5, MAZE_SIZE-gyo-0.5, 'S'+str(s_num), size=10, ha='center')

    if MAZE_OUT[s_num,0] == 1:
      #print
      #print(gyo,retu,MAZE_OUT[s_num])
      plt.plot([retu, retu+1], [MAZE_SIZE-gyo, MAZE_SIZE-gyo], color='red', linewidth=2)

    if MAZE_OUT[s_num,1] == 1:
      #print
      #print(gyo,retu,MAZE_OUT[s_num])
      plt.plot([retu+1, retu+1], [MAZE_SIZE-gyo-1, MAZE_SIZE-gyo], color='red', linewidth=2)

    if MAZE_OUT[s_num,2] == 1:
      #print
      #print(gyo,retu,MAZE_OUT[s_num])
      plt.plot([retu, retu+1], [MAZE_SIZE-gyo-1, MAZE_SIZE-gyo-1], color='red', linewidth=2)

    if MAZE_OUT[s_num,3] == 1:
      #print
      #print(gyo,retu,MAZE_OUT[s_num])
      plt.plot([retu, retu], [MAZE_SIZE-gyo-1, MAZE_SIZE-gyo], color='red', linewidth=2)

    s_num += 1

plt.plot([0, MAZE_SIZE], [0, 0], color='black', linewidth=3)
plt.plot([MAZE_SIZE, MAZE_SIZE], [0, MAZE_SIZE], color='black', linewidth=3)
plt.plot([0, MAZE_SIZE], [MAZE_SIZE, MAZE_SIZE], color='black', linewidth=3)
plt.plot([0, 0], [MAZE_SIZE, 0], color='black', linewidth=3)

plt.text(0.5, MAZE_SIZE-0.7, 'START', size=8,ha='center')
plt.text(MAZE_SIZE-0.5, 0.2, 'GOAL', size=8,ha='center')

# 描画範囲の設定と目盛りを消す設定
ax.set_xlim(0, MAZE_SIZE)
ax.set_ylim(0, MAZE_SIZE)
plt.tick_params(axis='both', which='both', bottom='off', top='off',labelbottom='off', right='off', left='off', labelleft='off')

# 現在地S0に緑丸を描画する
line, = ax.plot([0.5], [MAZE_SIZE-0.5], marker="o", color='g', markersize=30)


▼パラメーターθの初期値θ0の実装
MAZE_OUTを使ってθの初期値を作るわけですが、MAZE_OUTの最終行はゴールので次にどうするかという方策がありませんので削除します。

MAZE_OUT = np.delete(MAZE_OUT, (MAZE_SIZE*MAZE_SIZE-1), axis=0)
つぎにMAZE_OUTの壁で進めない方向の"1"を"np.non"に進める方向は"1"に変換してパラメーターθとします。
theta_0 = np.where(MAZE_OUT==1,np.nan,1)
# 方策パラメータthetaを行動方策piに変換する関数の定義
def simple_convert_into_pi_from_theta(theta):
    '''単純に割合を計算する'''

    [m, n] = theta.shape  # thetaの行列サイズを取得
    pi = np.zeros((m, n))
    for i in range(0, m):
        pi[i, :] = theta[i, :] / np.nansum(theta[i, :])  # 割合の計算

    pi = np.nan_to_num(pi)  # nanを0に変換

    return pi
    
# 初期の方策pi_0を求める
pi_0 = simple_convert_into_pi_from_theta(theta_0)

# 1step移動後の状態sを求める関数を定義


def get_next_s(pi, s):
    direction = ["up", "right", "down", "left"]

    next_direction = np.random.choice(direction, p=pi[s, :])
    # pi[s,:]の確率に従って、directionが選択される

    if next_direction == "up":
        s_next = s - MAZE_SIZE  # 上に移動するときは状態の数字がMAZE_SIZE分小さくなる
    elif next_direction == "right":
        s_next = s + 1  # 右に移動するときは状態の数字が1大きくなる
    elif next_direction == "down":
        s_next = s + MAZE_SIZE  # 下に移動するときは状態の数字がMAZE_SIZE分大きくなる
    elif next_direction == "left":
        s_next = s - 1  # 左に移動するときは状態の数字が1小さくなる

    return s_next

# 迷路内をエージェントがゴールするまで移動させる関数の定義


def goal_maze(pi):
    s = 0  # スタート地点
    state_history = [0]  # エージェントの移動を記録するリスト

    while (1):  # ゴールするまでループ
        next_s = get_next_s(pi, s)
        state_history.append(next_s)  # 記録リストに次の状態(エージェントの位置)を追加

        if next_s == MAZE_SIZE*MAZE_SIZE-1:  # ゴール地点なら終了
            break
        else:
            s = next_s

    return state_history

▼定義したgoal_maze関数の実行

# 迷路内をゴールを目指して、移動
state_history = goal_maze(pi_0)

#迷路を解くのにかかったステップ数の表示
print(state_history)
print("迷路を解くのにかかったステップ数は" + str(len(state_history) - 1) + "です")
これで移動の軌跡がstate_historyに格納される。 


 ▼エージェントの移動の様子を動画に

from matplotlib import animation
from IPython.display import HTML


def init():
    '''背景画像の初期化'''
    line.set_data([], [])
    return (line,)


def animate(i):
    '''フレームごとの描画内容'''
    state = state_history[i]  # 現在の場所を描く
    #x = (state % 3) + 0.5  # 状態のx座標は、3で割った余り+0.5
    x = (state % MAZE_SIZE) + 0.5  # 状態のx座標は、3で割った余り+0.5
    #y = 2.5 - int(state / 3)  # y座標は3で割った商を2.5から引く
    y = MAZE_SIZE-0.5 - int(state / MAZE_SIZE)  # y座標は3で割った商を2.5から引く
    line.set_data(x, y)
    return (line,)


# 初期化関数とフレームごとの描画関数を用いて動画を作成する
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=len(
    state_history), interval=200, repeat=False)

HTML(anim.to_jshtml())

# 動画を保存
anim.save('movie_maze-random.mp4')

エージェントの移動の様子

エージェントがランダムに動いてゴールを目指すため、無駄な動きも多くゴールに辿り着くまで時間がかかります。
このランダムな方策では、迷路の大きさが5より大きくなると時間がかかり過ぎ、ゴールに辿り着くのが難しくなります。

プログラムの全体はここにおいています。

2021年1月3日日曜日

強化学習で遊べる迷路を自動作成

先日「つくりながら学ぶ! 深層強化学習」を読んでいました。
ハードルが高くてよく分かりません。

第2章の迷路課題のところで、3✕3の固定された迷路を使ってデモがありましたが、こんなのを見ていると5✕5とか10✕10とか色々な迷路で試してみたいと思うのは私だけでしょうか?

ということで、強化学習で遊べる迷路を自動作成するプログラムを作りました。
迷路を作るためのアルゴリズムは幾つかあるようですが、今から勉強して1から作るのは骨が折れるので、子供のために迷路を自動作成するを使わせてもらいました。
自分で作ったのは出来た迷路を描画するルーチンのみです。(*^^*)
# 使用するパッケージの宣言
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
作成する迷路の1辺のブロック数を入力します。
#作成する迷路の大きさ
MAZE_SIZE = int(input('maze size->'))  # 迷路の1辺の長さ
MAZE_CELL = MAZE_SIZE * MAZE_SIZE  # 迷路のセル数
def direction():
  '''
  進行方向を選択。
  戻り値は順に「現在地の破壊壁方向」「進行部屋の壁破壊方向」「進行部屋の部屋番号」
  '''
  rand = np.random.randint(0, high=4)
  if rand == 0: return 0, 2, -1 * MAZE_SIZE  # 上
  if rand == 1: return 1, -1, 1  # 右
  if rand == 2: return 2, 0, MAZE_SIZE  # 下
  return -1, 1, -1  # 左

def check_cell(cells, c_cell, d_cell, dire):
  ''' 進めるかチェック '''
  # 進んだ部屋が迷路外かどうか
  # 上下へのはみ出し
  if d_cell < 0 or MAZE_CELL <= d_cell: return False
  # 部屋番号+1が迷路サイズで割り切れる場合、右端の部屋。右には進めない。
  if (c_cell+1) % MAZE_SIZE == 0 and dire == 1: return False
  # 部屋番号+1が迷路サイズで割り、余り1の場合、左端の部屋。左には進めない。
  if (c_cell+1) % MAZE_SIZE == 1 and dire == -1: return False
  # 現在地部屋番号 = 進んだ部屋番号。同じ部屋の場合は処理しない
  if cells[c_cell] == cells[d_cell]: return False
  # それ以外は進める
  return True

def choice_cell_no(cells, c_cell, d_cell):
  ''' 小さい方の部屋番号を取得 '''
  if cells[c_cell] < cells[d_cell]:
    return cells[c_cell], cells[d_cell]
  return cells[d_cell], cells[c_cell]

def create_maze():
  ''' 迷路を生成 '''
  # 初期状態を生成
  # [上, 右, 下, 左] = 1:壁 / 0:通路
  lst_maze = np.ones([MAZE_CELL, 4])
  # 部屋のナンバリング、最終的に全部0にする。
  lst_cells = np.array([i for i in range(0, MAZE_CELL)])

  while True:
    # すべての部屋が番号0の時は処理終了
    if np.sum(lst_cells) == 0: break

    # 壁を破る元のセルを選択(現在地)
    int_choice_cell = np.random.randint(0, high=MAZE_CELL)
    # 既にスタート地点とつながっている(0)時は処理しない。
    if lst_cells[int_choice_cell] == 0: continue

    # 進行方向を選択
    int_now_wall, int_direction_wall, int_direction = direction()
    # 進んだときのセル
    int_direction_cell = int_choice_cell + int_direction

    # 値のチェック
    if not check_cell(lst_cells, int_choice_cell, int_direction_cell, int_direction):
      continue

    # 部屋番号を確保
    int_min_value, int_max_value = choice_cell_no(lst_cells, int_choice_cell, int_direction_cell)

    # 小さい方の部屋番号に統一する
    lst_cells[lst_cells == int_max_value] = int_min_value

    # 壁も更新する
    lst_maze[int_choice_cell][int_now_wall] = 0
    lst_maze[int_direction_cell][int_direction_wall] = 0

  return lst_maze 

# 迷路を生成
MAZE_OUT = create_maze()

# 図を描く大きさと、図の変数名を宣言
fig = plt.figure(figsize=(MAZE_SIZE, MAZE_SIZE))
ax = plt.gca()

# 状態を示す文字S0~S※を描く
# 赤い壁を描く
s_num =0
for gyo in range(MAZE_SIZE):
#for gyo in range(2):
  for retu in range(MAZE_SIZE):
  #for retu in range(1):
   
    plt.text(retu+0.5, MAZE_SIZE-gyo-0.5, 'S'+str(s_num), size=10, ha='center')

    if MAZE_OUT[s_num,0] == 1:
      #print
      #print(gyo,retu,MAZE_OUT[s_num])
      plt.plot([retu, retu+1], [MAZE_SIZE-gyo, MAZE_SIZE-gyo], color='red', linewidth=2)

    if MAZE_OUT[s_num,1] == 1:
      #print
      #print(gyo,retu,MAZE_OUT[s_num])
      plt.plot([retu+1, retu+1], [MAZE_SIZE-gyo-1, MAZE_SIZE-gyo], color='red', linewidth=2)

    if MAZE_OUT[s_num,2] == 1:
      #print
      #print(gyo,retu,MAZE_OUT[s_num])
      plt.plot([retu, retu+1], [MAZE_SIZE-gyo-1, MAZE_SIZE-gyo-1], color='red', linewidth=2)

    if MAZE_OUT[s_num,3] == 1:
      #print
      #print(gyo,retu,MAZE_OUT[s_num])
      plt.plot([retu, retu], [MAZE_SIZE-gyo-1, MAZE_SIZE-gyo], color='red', linewidth=2)

    s_num += 1

plt.plot([0, MAZE_SIZE], [0, 0], color='black', linewidth=3)
plt.plot([MAZE_SIZE, MAZE_SIZE], [0, MAZE_SIZE], color='black', linewidth=3)
plt.plot([0, MAZE_SIZE], [MAZE_SIZE, MAZE_SIZE], color='black', linewidth=3)
plt.plot([0, 0], [MAZE_SIZE, 0], color='black', linewidth=3)

plt.text(0.5, MAZE_SIZE-0.7, 'START', size=8,ha='center')
plt.text(MAZE_SIZE-0.5, 0.2, 'GOAL', size=8,ha='center')

# 描画範囲の設定と目盛りを消す設定
ax.set_xlim(0, MAZE_SIZE)
ax.set_ylim(0, MAZE_SIZE)
plt.tick_params(axis='both', which='both', bottom='off', top='off',labelbottom='off', right='off', left='off', labelleft='off')

# 現在地S0に緑丸を描画する
line, = ax.plot([0.5], [MAZE_SIZE-0.5], marker="o", color='g', markersize=30)


【自動作成された迷路の例】
3✕3の迷路

5✕5の迷路
10✕10の迷路

たかが迷路を表示するだけのサブルーチンですが、昔のように頭が働かないので、試行錯誤の果です。
どこか間違っているかも知れませんが取り敢えず動いているので良しとします。m(__)m