buto > /dev/null

だいたい急に挑戦してゴールにたどり着かずに飽きる日々です

Python DB登録

SQLAlchemyを使ってMySQLにデータ登録

画面の入力フォームの値をDBに登録する(レイアウトは後回し)

f:id:butorisa:20201020172623p:plain

データを登録するテーブル定義

# カラム名 データ型 制約
1 no int primary_key, auto_increment
2 title varchar(100) not null
3 content varchar(1000) -

入力フォームを作成

入力フォームはいつもの書き方で <input name="xxx">で項目名を設定

        <form action="/post" method="post">
            <div class="input-group mb-3">
              <div class="input-group-prepend">
                <span class="input-group-text" id="basic-addon1">Title</span>
              </div>
              <input type="text" class="form-control" name="title" aria-label="Title" aria-describedby="basic-addon1">
            </div>
            <div class="input-group">
              <div class="input-group-prepend">
                <span class="input-group-text">content</span>
              </div>
              <textarea class="form-control" name="content" aria-label="With textarea"></textarea>
            </div>
            <button type="submit" class="btn btn-outline-info">submit</button>
        </form>

DB登録処理を作成

引数に登録するオブジェクトを取りsession.add()で登録する

    def register_item(self, item=None):
        """ itemテーブル登録 """
        session = db.create_session()
        session.add(item)
        session.commit()

Controllerを定義

POSTでリクエストが来たらDB登録処理を呼んで、画面表示処理を実行 request.form['xxx'](フォームのname属性)で入力値が受け取れる auto_incrementのカラムは値を設定しなくてOK

@app.route('/post', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        # DB登録するオブジェクトを生成
        target = Item(title=request.form['title'], content=request.form['content'])
        Item.register_item(app, item=target)

        # 画面再表示
        return show_mindmap()

ソースコード一式はgithubに置きました

SQL弱者としてはSELECTとINSERTができればかなり満足

Python DBデータを取得する

Flask+jinja2+MySQLでちょっとした画面を作る

お手軽WebフレームワークFlaskでMySQLに登録したデータを表示する画面を作ってみる!

環境構築

  1. MySQLをインストール
    • brew install mysql
    • brew services start mysql
    • mysql -uroot
    • クライアントツールはMySQL Workbenchを利用
    • クライアントツールからスキーマ、テーブル、データを作成
  2. Pythonライブラリをインストール ※PyPI最新版を利用
    • Flask
    • jinja2
    • pymysql データ取得だけでは使わなかった
    • sqlalchemy

MySQLに接続する

ソースコードGitHub

ファイル構成 - config.py:DB接続情報を記述 - database.py:セッションを取得 - models/item.py:itemテーブルのモデルクラス データ取得メソッドを記述 - main.py:item.pyのデータ取得メソッドを呼び、HTMLを返す

f:id:butorisa:20201020172428p:plain

database.py SQLAlchemyを使ってMySQLの接続セッションを確立

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from config import SystemConfig


def create_session():
    # create_engine()の引数はconfig.pyに記述したDB接続情報
    engine = create_engine(SystemConfig.SQLALCHEMY_DATABASE_URI)
    session = sessionmaker(bind=engine)
    return session()

item.py database.pyからセッションを取得し、データを取得

import database as db
from sqlalchemy import (Column, Integer, String)
from sqlalchemy.ext.declarative import declarative_base


base = declarative_base()


class Item(base):
    # テーブル情報
    __tablename__ = 'item'
    no = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(100), nullable=False)
    content = Column(String(1000), nullable=False)

    def get_item(self):
        """ itemテーブルの全データを取得 """
        session = db.create_session()
        item_list = session.query(Item).all()
        if item_list is None:
            return []
        return item_list

session.query(モデルクラス)でデータ取得できる!!

自作ゲームで強化学習 #2

前回の記事からgymにもっとゲームをあそんでもらうべく、報酬の条件を調整してみる!

スコアに応じた報酬を与えてみる

  • スコア0:-10
  • それ以上:スコア÷100(スコア100だと報酬+1)

結果:カップケーキを1つ取ったら動かなくなった コードはこちら

f:id:butorisa:20201020172252p:plain

5秒以内にカップケーキを取ったらボーナスを与える

上記のスコア+(前回ケーキを取ってから)5秒以内だったら更に10加算

結果:カップケーキを1つ取ったら動かなくなった

f:id:butorisa:20201020172307p:plain

5秒以内にプレイヤーが移動したらボーナスを与える

上記2つに加えて移動したら報酬を増やすようにした

結果:移動は増えたが、ケーキを取りに行く感じではなかった

ここまでのコードはこちら

わかったこと

  • 「5秒以内に動かなかったら報酬をマイナス」という減点法は効果なかった
    • プレイヤーが動かないままになってしまった
  • 「プレイヤー移動で報酬プラス」は効果あり
    • 移動アクションは増えた
    • ケーキにたどり着かない場所を行ったり来たりする移動が多かった

自作ゲームで強化学習 #1

AIにもゲームをあそんでもらった

pygameで作ったゲーム強化学習して上達するのか試してみた!!

カップケーキ取ったらスコア+100、20秒後にスコアを表示

f:id:butorisa:20201020171924g:plain

強化学習実行までの道のり

こちらの記事を写経しました 【AI + pygame】pygameで作るインベーダー風ゲーム 1. ゲームコードを Open AI Gym が実行できるよう修正 1. 強化学習モデル+学習実行コードを記事からぱくる 1. 実行コードを動かすとエラーが出るので対応 - モデルへの入力データ次元が違うとか - tensorflowはこのメソッドを実装してないとか(tensorflowのバージョン不適) - こんな変数はないとか(ただのコードミス)

強化学習スタート

  • 環境(Anaconda) AnacondaとPyPIがごちゃ混ぜになっております…
    • Python:3.7.6
    • pygame:1.9.6 ※pip
    • gym:0.17.1
    • keras:2.3.1 ※pip
    • keras-rl:0.4.2 ※pip
    • pillow:7.1.1
    • tensorflow:1.13.1 ※pip
  • ソースコード
  • パラメータ
    • 学習ステップ数:20,000 1エピソードで行ったキー操作の回数(多分)
    • テストエピソード数:10 ゲーム開始~終了で1エピソード
  • 報酬の設定 ゲーム終了時のスコアで報酬を決める
    •  0 ~ 100 : -10
    • 101 ~ 500 : +5
    • 501 ~ 1000: +10
    • 1001 ~   : +15

10,000ステップくらいから↓のような感じ スコアは100~300で全然上がらない

f:id:butorisa:20201020172001g:plain

ゲーム開始時は右下にカップケーキがあって、開始直後に右下のケーキを取るようになった (ここは400ステップくらいで学習してくれた)

報酬=スコアにすればよかった!

テスト結果 reward(報酬)はずっと5のまま

Testing for 10 episodes ...

Episode 1: reward: 5.000, steps: 173
Episode 2: reward: 5.000, steps: 173
Episode 3: reward: 5.000, steps: 174
Episode 4: reward: 5.000, steps: 173
Episode 5: reward: 5.000, steps: 173
Episode 6: reward: 5.000, steps: 173
Episode 7: reward: 5.000, steps: 172
Episode 8: reward: 5.000, steps: 172
Episode 9: reward: 5.000, steps: 173
Episode 10: reward: 5.000, steps: 173

上記の報酬設定だと、ケーキを1~5回取った時は同じ報酬だから プレイヤーがたくさん動いてケーキを取りまくるスタイルにならなかった

ケーキを取った回数に比例して報酬をプラスすればもっと動いてくれると期待

pygameでお手軽ゲーム開発

矢印キーでケーキを取るゲームを作った

初めてゲームを作ってみた! プレイヤーを矢印キーで操作してカップケーキを取りに行くゲームです

カップケーキのマスに行くと左上のスコアが加算、カップケーキは他のマスに移動 ゲーム開始から20秒後にスコアを表示して終了

f:id:butorisa:20201020171652g:plain

背景画像はこちら

f:id:butorisa:20201020171716p:plain

プレイヤー、カップケーキは「いらすとや」からダウンロードしました

import pygame
from pygame.locals import *
import sys
from collections import namedtuple
import time

def main():
    # 画面サイズ
    screen_size = namedtuple('screen_size', 'width height')
    screen_size = screen_size(width=300, height=400)

    # スコアの位置
    pos_score = [10, 10]
    
    # プレイヤーの位置
    pos_player = [150, 345]
    
    # ケーキの位置
    pos_cake = [250, 50]
    
    pygame.init()
    
    # 既定のウィンドウ、32bit
    pygame.display.set_mode(screen_size, 0, 32)
    pygame.display.set_caption('cupcake')
    screen = pygame.display.get_surface()
    
    # 背景画像
    bg = pygame.image.load('board.png').convert_alpha()
    # 透過度
    rect_bg = bg.get_rect()
    
    # スコア
    score = 0
    
    # プレイヤー
    player = pygame.image.load('valentinesday_heart_girl.png').convert_alpha()
    rect_player = player.get_rect()
    rect_player.center = pos_player
    
    # カップケーキ
    cake = pygame.image.load('sweets_cupcake.png').convert_alpha()
    rect_cake = cake.get_rect()
    rect_cake.center = pos_cake# 
    
    # タイマースタート
    start_time = time.time()
    
    while(1):
        # プレイヤーの移動
        pressed_key = pygame.key.get_pressed()
        # ←
        if pressed_key[K_LEFT]:
            pos_player[0] -= 100
        # →
        if pressed_key[K_RIGHT]:
            pos_player[0] += 100
            
        # ↑
        if pressed_key[K_UP]:
            pos_player[1] -= 100
            
        # ↓
        if pressed_key[K_DOWN]:
            pos_player[1] += 100
            
        # 画面の端だったら動かない
        if pos_player[0] < 10:
            pos_player[0] += 100
            
        if pos_player[0] > 250:
            pos_player[0] -= 100
            
        if pos_player[1] < 10:
            pos_player[1] += 100
            
        if pos_player[1] > 350:
            pos_player[1] -= 100
            
        rect_player.center = pos_player
        
        # プレイヤーがケーキにたどり着いたらケーキは移動
        if rect_player.colliderect(rect_cake):
            
            # スコア加点
            score += 100
            pos_cake[0] += 100
            pos_cake[1] -= 100
            
            # 画面の端だったら反対側に移動
            pos_cake[0] = pos_cake[0] % 300
            pos_cake[1] = pos_cake[1] % 400
            rect_cake.center = pos_cake
        
        # 画面更新間隔
        pygame.time.wait(100)
        
        screen.fill((0, 0, 0, 0))
        # 背景描画
        screen.blit(bg, rect_bg)
        # スコア描画
        sysfont = pygame.font.SysFont(None, 20)
        rect_score = sysfont.render('SCORE:' + str(score), True, (160,100,65))
        screen.blit(rect_score, pos_score)
        # ケーキ描画
        screen.blit(cake, rect_cake)
        # プレイヤー描画
        screen.blit(player, rect_player)
        
        # タイムアウト
        current_time = time.time()
        if (current_time - start_time) >= 20:
            start_flg = False
            pos_msg = [50, 150]
            font_msg = pygame.font.SysFont(None, 50)
            rect_msg = font_msg.render('SCORE:' + str(score), True, (160,100,65))
            
            screen.fill((255, 250, 165, 0))
            screen.blit(rect_msg, pos_msg)
            
        pygame.display.update()
        
        for event in pygame.event.get():
            # 閉じるボタン押下
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
                
if __name__ == '__main__':
    main()

Django テンプレート継承

base.htmlを継承してコード量を減らす

やっとテンプレート継承の恩恵を受けられた!

base.htmlには普通にHTMLタグを書く bodyタグの中は空っぽ

<!DOCTYPE html>
{% load static %}
{% load bootstrap4 %}
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>TITLE</title>
    <link rel="stylesheet" href="../../static/redo/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
</head>
<body>
  <div class="container">
    {% block content %}{% endblock %}
  </div>
</body>
</html>

継承したテンプレートはHTMLタグを省略できる (base.htmlの{% block content %}~{% endblock %}に挿入する部分だけ記述でOK)

{% extends "redo/base.html" %}
{% load bootstrap4 %}
{% block title %}類語検索{% endblock %}

{% block content %}
    <p>メッセージを入力してください</p>
    <form action="{% url 'redo:register_message' %}" method="post">{% csrf_token %}
        {% bootstrap_form form layout='horizontal' %}
        <div class="form-group row">
            <div class="offset-md-3 col-md-9">
                <button type="submit" class="btn btn-outline-primary">submit</button>
            </div>
        </div>
    </form>
    {{ message }}
{% endblock %}

継承したテンプレートに記述した入力フォームが表示された!

f:id:butorisa:20201020171507p:plain

画面に何も表示されない時は

継承元テンプレート(base.html)と継承するテンプレートで使っているタグ名が違っているはず! (継承元:{% block content %} 継承先:{% block contents %}など)

これに気づかず30分くらいハマった。。。

AWS Elastic Beanstalk起動時にエラー

Elastic Beanstalk

作成したアプリをささっとデプロイできるサービス Elastic Beanstalkを利用すると自動でサーバ(EC2)構築、ネットワーク設定もしてくれる

チュートリアルをやってみる

Amazon Elastic Beanstalk を使用したアプリケーションの起動 数分でアプリの実行環境ができるらしいけど、10分以上待っても状態が「Pending」だ…

The EC2 instances failed to communicate with AWS Elastic Beanstalk, either because of configuration problems with the VPC or a failed EC2 instance. Check your VPC configuration and try launching the environment again.

なんかエラー出た。。チュートリアル通り進んだはずなのに

インターネット接続が原因ぽい

エラーメッセージで検索をするとAWS公式ドキュメントに対処法があった Amazon EC2 インスタンスが Elastic Beanstalk との通信に失敗したときに表示されるエラーを解決する方法を教えてください。

作成したElastic Beanstalkインスタンスがインターネット接続できてないのがエラーの原因かな

解決方法 1. Elastic Beanstalkに紐づくネットワークACLにアウトバウンド接続が許可されているか? OK 1. Elastic Beanstalkを作成したVPCにはサブネットが紐づいているか? OK 1. EC2がパブリックの場合は、ルートテーブルにインターネットへのルートがあるか? NG

ルーティングの設定し忘れでした InternetGateway(IGW)を作って満足していたのですが、IGWに向かってね~の設定がないので データ送受信ができず、Elastic Beanstalkの起動時にエラーになっていた (そういえば、No data っていうメッセージも出ていたな)

IGWへ向かうルートを追加してあげて解決

  1. VPCコンソールから[ルートテーブル]をクリック
  2. Elastic BeanstalkがあるVPCに紐づくルートテーブルを選択
  3. 送信先「0.0.0.0/0」ターゲット「対象のIGW」のルートを追加
  4. Elastic Beanstalkメニューで[環境再構築]
  5. 環境が作成された!!