20210213のPythonに関する記事は30件です。

ラズパイ4BでSSH接続(Mac、最初からディスプレイなし、無線LANの場合)<後編>

5.起動する(電源に繋ぐ)

Raspberry Pi4にMicroSDカードを挿して、USB-Cで給電し起動を行ってください。

緑のランプが点滅すればSDCardの読み込みが行われています。

6.SSH接続を行う

前回の記事でmicroSDCardにwpa_supplicant.confファイルが書いてあるので(ちなみにwpaはWi-Fi Protected Accessの略です)、ラズパイの電源を起動をしただけでラズパイとWiFiが繋がっている状態になります。その状態でMacからリモートでSSH接続を行います。

リモート接続するためにはラズパイのIPアドレスが必要になります。

調べるためにはターミナルで以下のコマンドを打ってください。

arp -a

そうするとこのような画面が出てきます。

スクリーンショット 2021-02-11 19.50.58.png

ここでは(192.168.3.7)でした。

注意してほしいのはここではraspberrypiと表示されていますが、必ずしもraspberrypiと表示されているとは限りません。表示されてなくてもWiFi接続しているラズパイの場合もあります。それでも次のSSH接続で確認すれば問題ないので、これかな?と思うものがあればSSH接続を試してみてください。

ちなみに192.168.3.1とか192.168.3.255はルータかその辺です。
    
  
  

それでは大変長らくお待たせしました。いよいよSSH接続を行います。

接続のコマンドはターミナルで

ssh pi@各々のIPアドレス

になるので今回の場合は以下のようになります。

ssh pi@192.168.0.7

このように各自で調べたIPアドレスを書いてください。

そうするとパスワードを求められるのでraspberryと入力してください。(初期設定がユーザ名pi、パスワードraspberryになっています。)

接続できれば一番下に

pi@raspberrypi:~ $

と表示されます。

余談ですが、僕が初めて接続できたときは嬉しくて雄叫びを上げたっけなぁ。SDCard買い替えたりしたりして苦労しましたもの。。

  
 

それでは最後にVNCに移ります。

 

7.VNC viewerでGUI接続

VNCとは?
Virtual Network Computingの略
ネットワーク上の離れたコンピュータを遠隔操作ができるソフトウェア。デスクトップ画面を操作できる。
  
 
VNC viewerはこちらから無料ダウンロードできます。
 
 

ラズパイをSSH接続した状態で、

sudo raspi-config

と打ってください。そしたら下のような画面が出ます

スクリーンショット 2021-02-12 11.21.45.png

設定によって背景の色は違うかもしれませんが、書かれている単語が同じであれば問題ないです。

まずはVNCの設定をONにします。

3P3Yes

VNC接続をデフォルトに設定します(これをしないとエラーがでます。)

1S5B4

ラズパイのデスクトップのサイズです。

2D1好きなサイズ

(ちなみに僕はMac Book Proの13.3インチでDMT Mode 35 1280x1024 60Hz 5:4 にしています。ちょうどいい具合に使えています。) 
 

このツールをアップデートします。

8

このツールを終了します。

Finish

ここで一度ラズパイを再起動をします。「~ like to reboot now?」と聞かれたらyesを押します。

もし聞かれなかったら下のコマンドを入力してください。一度SSH接続からログアウトします。

sudo reboot

その後、reboot(再起動)するとssh接続が切れるのでまたssh pi@各々のIPアドレスでSSH接続を行い、VNC Viewer(こちらからインストールしてください)
を開き、上の方にある入力欄に先ほど入れた確認したIPアドレスを入力してください。

VNC Server has not recognizedという画面が出たらcontinueを押します。

以前違うラズパイを、同じIPアドレスに接続していたら場合「VNC Server identity check failed 」という警告が出ますが、continueで大丈夫です。

Authenticate VNC Server という画面が出るのでusernameにpi,passwordにraspberryと入力してOKを押してください。

接続に成功すればデスクトップ画面が表れます。
 
 

以上で終了です!お疲れ様でした。(その後の初期設定やセキュリティ等も重要ですが、それは後日気が向いたら書きます。)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Lambdaでメッセージを編集してSNS Publishする一例

Lambdaのコード

 ヒアドキュメントと変数、json.dumpsを利用した一例です。
 SecurityHubの通知を例にして作成しています。

lambda_function
from __future__ import print_function
import json
import boto3
import os

sns = boto3.client("sns")
def lambda_handler(event, context):
    message = event["detail"]["findings"][0]
    msg = """
    Security Hubにより検知した違反内容
    セキュリティ基準コントロール:{standards_control_arn}
    タイトル:{title}
    説明:{description}
    セキュリティステータス:{status}
    重要度:{severity}
    修復手順:{remediation_text}({remedeation_url})

    対象リソース
    {resources}

    詳細
    --
    {detail}
    """.format(standards_control_arn=message["ProductFields"]["StandardsControlArn"], \
    title=message["Title"],\
    description=message["Description"], \
    status=message["Compliance"]["Status"], \
    severity=message["Severity"]["Label"],\
    remediation_text=message["Remediation"]["Recommendation"]["Text"], \
    remedeation_url=message["Remediation"]["Recommendation"]["Url"],\
    resources=json.dumps(message["Resources"], indent=4), \
    detail=json.dumps(event, indent=4))

    response = sns.publish(
        TopicArn=os.environ.get("sns_arn"),
        Subject="テスト",
        Message=msg
    )
    return response
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VS Code Remote ContainersとPylanceで快適Python環境を構築する方法

最近、チーム用のPython開発環境を構築する機会があり、VS Codeを使った環境構築がかなり便利だったので記事にしました。

はじめに

本記事では、VS CodeのRemote Containersを利用し、再現性が高く快適なPython環境を構築する方法を紹介します。

すでにあるRemote Containersの記事との違いは、Pythonを使ったチームでの開発を想定し、リンターや便利な拡張機能も環境構築に含めていることです。

今回紹介する内容を使えば、コードの自動フォーマットやコードの補完などが有効になる快適なPython開発が可能です。

Pythonの実装のコツやノウハウについては、同じチームの@sugulu_Ogawa_ISIDさんの記事が参考になります。

便利なこと

本記事で紹介する方法の便利なところは以下の2点です

  1. Pythonのバージョンや使用packageをチームで一元管理
  2. VS codeで使用するリンターや拡張機能をチームで統一

一つ目は、ほぼDockerのメリットになりますが、anacondaやpyenvやvenvのようなバージョン管理やpackage管理で悩まなくてすみます。そして、新しくチーム用の開発環境を作る際に、各々のローカル環境に左右されずDockerが使えさえすればOKとなるので、開発初期の環境構築作業が非常に楽になります。

二つ目は、コード規約や拡張機能の知見を共有できることです。私の周囲では、開発用エディタはVS Codeを利用していることが多く、エディタは統一されています。しかし、VS Codeの設定にはかなり個人差があります。VS Codeで提供されている機能を使いこなしているか否かで作業効率がかなり変わるので、できればVS Codeの環境自体も共有したいです。

今回紹介するRemote Containersでは、設定ファイルさえ渡せば上記2点が簡単にチームで共有できます。

Remote Containersを使った環境構築方法

必要なものはざっくり以下の5つです。

  • Visual Studio Code
  • Docker
  • Remote Containers拡張機能
  • devcontainer.json
  • Dockerfile

VS CodeとDockerのインストール方法はここでは紹介しません。Remote ContainersはVS Codeの拡張機能からインストールできます。

Remote Containers機能を使うには作業フォルダ内に.devcontainer/devcontainer.jsonが必要です。このdevcontainer.jsonがRemote Containers機能の設定ファイルに該当します。

今回はテスト用に以下のようなプロジェクトを使います。
公式のPythonテンプレートを参考にしています)

.
├── .devcontainer
│   ├── Dockerfile
│   └── devcontainer.json
├── .gitattributes
├── .gitignore
├── requirements.txt
└── test.py

Dockerfileの中身は以下になります。ほぼ公式のDockerfileのままです。

ARG VARIANT="3"
FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}

ENV PIP_TARGET=/usr/local/pip-global
ENV PYTHONPATH=${PIP_TARGET}:${PYTHONPATH}
ENV PATH=${PIP_TARGET}/bin:${PATH}
RUN if ! cat /etc/group | grep -e "^pip-global:" > /dev/null 2>&1; then groupadd -r pip-global; fi \
    && usermod -a -G pip-global vscode \
    && umask 0002 && mkdir -p ${PIP_TARGET} \
    && chown :pip-global ${PIP_TARGET} \
    && ( [ ! -f "/etc/profile.d/00-restore-env.sh" ] || sed -i -e "s/export PATH=/export PATH=\/usr\/local\/pip-global:/" /etc/profile.d/00-restore-env.sh )

COPY requirements.txt /tmp/pip-tmp/
RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
    && rm -rf /tmp/pip-tmp

次は肝心のdevcontainer.jsonの中身です。こちらも基本的に公式のPythonテンプレートに倣ってます。

{
    "name": "Python 3",
    "build": {
        "dockerfile": "Dockerfile",
        "context": "..",
        // Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8 
        "args": {
            "VARIANT": "3"
        }
    },
    "workspaceMount": "source=${localWorkspaceFolder},target=/workspaces,type=bind,consistency=delegated",
    "workspaceFolder": "/workspaces",   
    "settings": {
        "terminal.integrated.shell.linux": "/bin/bash",
        "python.pythonPath": "/usr/local/bin/python",
        "python.linting.enabled": true,
        "python.linting.pylintEnabled": true,
        "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
        "python.formatting.blackPath": "/usr/local/py-utils/bin/black",
        "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
        "python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
        "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
        "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
        "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
        "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
        "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint",
        "python.analysis.typeCheckingMode": "basic",
        "python.analysis.completeFunctionParens": true
    },
    "extensions": [
        "ms-python.python",
        "ms-python.vscode-pylance",
        "ms-vsliveshare.vsliveshare",
        "njpwerner.autodocstring"
    ],
    "remoteUser": "vscode"
}

workspaceMountでは、マウントしたいローカルフォルダとコンテナ内のマウント先を指定しています。${localWorkspaceFolder}.devcontainer/devcontainer.jsonを含むフォルダのパスを指定できます。

settingsの内容はRemote Containersで開いた時のVS Codeの設定です。

そして、extensionsに拡張機能を追加することで、Remote Containers内で指定した拡張機能がインストールされます。これにより、「2. リンターや拡張機能を簡単にチームで共有できる」を実現できます。

VS codeでPythonを開発するにあたりぜひ入れて欲しい拡張機能はms-python.pythonms-python.vscode-pylancenjpwerner.autodocstringです。(ms-python.vscode-pylanceを使うにはms-python.pythonが必須です)

ms-python.vscode-pylancePylance)は後述しますが、VS CodeでのPython開発を強力にサポートしてくれる拡張機能です。njpwerner.autodocstringPython Docstring Generator)はDocstringを生成してくれる拡張機能です。

Remote Containersの使い方

ここまでの設定が完了すればあとは起動するだけです。
起動するには、VS Codeの左下の緑のアイコンをクリックします。

スクリーンショット 2021-02-13 21.30.45.png

アイコンをクリックすると以下のような選択肢が出るので、Remote-Containers: Open Folder in Containerを選択し、.devcontainerがあるフォルダを選択します。

スクリーンショット 2021-02-13 21.31.55.png

これで完了です。あとはローカルで開発するのと同じように作業できます。

Pylanceの使い方

続いて、拡張機能としてインストールしたPylanceについて紹介します。

Pylanceの提供する主な機能は以下になります。

  • Docstrings
  • Signature help, with type information
  • Parameter suggestions
  • Code completion
  • Auto-imports (as well as add and remove import code actions)
  • As-you-type reporting of code errors and warnings (diagnostics)
  • Code outline
  • Code navigation
  • Type checking mode
  • Native multi-root workspace support
  • IntelliCode compatibility
  • Jupyter Notebooks compatibility
  • Semantic highlighting

いろいろ便利な機能が提供されていますが、特に便利なのはコードの補完、Docstrings表示と型チェックです。
本記事ではチーム開発で役立つDocstrings表示と型チェックを紹介します。

まずはDocstringsについてです。
これはDocstringsで書かれた内容を該当コードにホバーすると以下のように表示される機能です。
(Docstringsとは、関数やクラスの説明を記述したものです。)

スクリーンショット 2021-02-13 1.11.23.png

この機能があると引数や返り値などについて確認する時間を短縮することが可能です。
Docstringsを書くのはちょっと面倒だったりするのですが、先ほどインストールしたPython Docstring Generatorを使えばDocstringsを書く手間を短縮できます。

Macであれば、Docstringを挿入する場所でcmd + shift + 2とすると、以下のようにテンプレートを作成してくれます。
スクリーンショット 2021-02-13 1.16.26.png

Docstringのテンプレートは以下から選択できます。
Python Docstring Generator

Google (default)
docBlockr
Numpy
Sphinx
PEP0257 (coming soon)

次に型チェックです。

Pythonでは、変数の型が一致しなくても実行できてしまいます。そのため、本来期待していた型と異なる型で実装してしまうことがあります。型指定があるとこのような間違った型の実装を防ぐことができ、特に複数人かつ複雑な構成で開発する場合は便利です。

PylanceのType checking modeを設定すると、以下のようにコードを書いている途中に型の間違いに気づくことができます。
スクリーンショット 2021-02-13 12.10.45.png

Type checking modeではoffbasicstrictと型チェックの厳しさを選択できます。
strictにすると型の指定がないだけでもエラー判定されます。

開発チーム次第ではありますが、基本的にはbasicで問題ないと思います。
この設定は、devcontainer.json"python.analysis.typeCheckingMode": "basic"で変更できます。

終わりに

本記事では、VS CodeのRemote Containers機能を用いたPythonの開発環境構築方法を紹介しました。
ちなみに、本記事では紹介しませんでしたがJupyter Notebookもインストールするだけで今回の設定で使えます。
他にもsort importやimport文の自動追加や自動削除といった紹介しなかった便利機能もあります。

また、拡張機能についても今回は必要最低限しか記載していませんが、他にも便利なものがたくさんありました。
こちらも今後まとまったら記事にしようと思います。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonを使ってconnpassイベントをキーワード検索

Pythonを使ってconnpassイベントをキーワード検索

公式ドキュメント ... https://connpass.com/about/api/

リクエストURL

パラメーター
keyword_or ... キーワード (,)区切りで複数選択
ym ... 取得年月(YYYYMM)(,)区切りで複数選択
order ... 表示順(1: 更新日時順, 2: 開催日時順, 3: 新着順)
count ... 取得数

例:
https://connpass.com/api/v1/event/?keyword_or=php,laravel,wordpress&ym=202102,202103&order=1&count=100

Python側

キーワード、取得年月、取得数で検索できるようにした
結果は加工しやすいようにDataFrameに入れた

def get_search(keyword: str, ym: str, count: int = 100):

    df = pd.DataFrame(
        columns=[
            "id",
            "title",
            "catch",
            "description",
            "event_url",
            "hash_tag",
            "started_at",
            "ended_at",
            "limit",
            "address",
            "place",
            "owner_id",
            "owner_nickname",
            "owner_display_name",
            "accepted",
            "waiting",
            "updated_at",
        ],
    )

    url = (
        f"https://connpass.com/api/v1/event/?keyword_or={keyword}"
        f"&ym={ym}&count={count}"
    )
    response = requests.get(url)
    resources = response.json()

    for event in resources["events"]:

        df = df.append(
            {
                "id": event["event_id"],
                "title": event["title"],
                "catch": event["catch"],
                "description": event["description"],
                "event_url": event["event_url"],
                "hash_tag": event["hash_tag"],
                "started_at": event["started_at"],
                "ended_at": event["ended_at"],
                "limit": event["limit"],
                "address": event["address"],
                "place": event["place"],
                "owner_id": event["owner_id"],
                "owner_nickname": event["owner_nickname"],
                "owner_display_name": event["owner_display_name"],
                "accepted": event["accepted"],
                "waiting": event["waiting"],
                "updated_at": event["updated_at"],
            },
            ignore_index=True,
        )

    return df

今回はphp,laravel,wordpressを対象に202102,202103のイベントを取得

search = connpass.get_search("php,laravel,wordpress", "202102,202103")

結果

        id                            title  ... waiting                 updated_at
0   204563                 ヘッドレスWordPress入門  ...       0  2021-02-13T20:30:41+09:00
1   204610    (若手?)エンジニアもくもく会 vol.34 @Slack  ...       0  2021-02-13T19:59:55+09:00
2   204076           もくもく会in神戸 #2【三宮&ウェブ開催】  ...       0  2021-02-13T18:15:58+09:00
3   200508       TwilioQuest - 2021 新春チャレンジ  ...       0  2021-02-13T17:19:22+09:00
4   204362             【超入門】Webサイトをデプロイしよう!  ...       0  2021-02-13T16:54:57+09:00
..     ...                              ...  ...     ...                        ...
72  202607             名古屋駅前 もくもく会 2021.2.7  ...       0  2021-01-25T21:56:44+09:00
73  202576           サーバー構築ハンズオン@オンライン #129  ...       0  2021-01-25T15:14:45+09:00
74  201718    第73回【オンライン開催】PORTもくもく会【学生歓迎!】  ...       0  2021-01-16T14:19:06+09:00
75  201342                  錦糸町 朝活・もくもく会 #3  ...       0  2021-01-13T07:26:46+09:00
76  198286  [新宿御苑前]がやがやと勉強する会(テーマ自由、初心者歓迎!)  ...       0  2021-01-12T13:50:49+09:00

connpassイベントを取得できました
いいね!と思ったら LGTM お願いします :clap::clap::clap:

【PR】週末ハッカソンというイベントやってます! → https://weekend-hackathon.toyscreation.jp/about/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

共変ベクトル、反変ベクトル生成器を作ってみた(MathJaxがFirebaseで動かなくて中途半端なでき)

共変ベクトルと反変ベクトルの計算を楽にしたい

一般相対性理論を勉強する場合に最大の難関が、「共変ベクトルや反変ベクトルのイメージがつかない」ことだと思います。
共変ベクトルと反変ベクトルを掛け合わせると綺麗にスカラーになる部分が一番面白いと個人的には思うのですが、その手前でイメージがつかないと挫折しそうでした。
そんなとき、「自動でクリストッフェルや共変や反変を計算してくれるサイトがあればなあ」と思っていました。
思っていたので、作りたくなり、作りました。

反変ベクトルと共変ベクトルの計算機

自分のコンピューターでみた場合 vs Firebaseにホスティングした場合

自分のコンピューターでみると、うん!ばっちり!なできです 1次〜100次元まで計算できます!
スクリーンショット 2021-02-13 午後9.17.54.png
 
しかし、Firebaseにホスティングすると、LaTex表示になってしまいます。
スクリーンショット 2021-02-13 午後9.18.26.png

なぜこうなったのか

webに数式を表示するには、「MathJax」というjavascriptの手法を使うのが楽で、
MathJaxを使えば、Latex形式で貼り付ければ数式が表示されます。
しかし、FirebaseはMathJaxと相性が悪いらしく、なんらかのおまじないを打たなければ厳しいみたいです。

中の仕組み

今回の作品は、全然ダメではありますが、
「LaTex形式では掃き出せている」ので、個人的には満足です(wordとかに貼り付けて変換するなりしていただければ使えるでしょうし)

その場で計算式を解いているわけではなく、pythonでn=1~100まで計算させてからその結果を予めhtmlに忍ばせており、
vue.jsでイベントハンドリング(数値が入力されたら、showされるものを変える)しています。

結果と考察

・webに数式を表示する技術に技術的限界が現状あり、これは誰かが解決してもいい問題のように感じた
 ⇨そのため、今回はwebブラウザ上での計算を諦め、予めpythonで計算したものを表示することにした。

・クリストッフェルをそのまま計算したりする手法は、pythonやC++では存在するが、web上でだれでも計算できるようには現状なっていない。ここは誰かが解決してもいいような問題に感じた
 ⇨大学生のレポートが楽になるし、相対性理論で苦しめられる学生が減るのでは?

・LaTexにおいて、「\」が多様されるが、pythonはこの記号がエスケープに使われるため、かなりプログラム自体に苦戦した。
 ⇨pythonにおいては、「\」によって、「\」一個分を表示する(エスケープでエスケープする)という文法であることを学んだ。これに気づくまでがまた長かった。

今後

Firebaseがダメなら、HerokuとAWSは試したいところ。
できれば、共変ベクトル、反変ベクトルの経験をいかして、リッチテンソル、クリストッフェルも計算したいところ!(これもブラウザ上計算ではなく、結果を予め出力したものが妥当に思います。)

HTMLでLaTex形式で出力するpythonのソースコード

for n in range(1,101):
    First_1 = "          <jigen"
    First_2 = " v-show="
    First_3 = "\"jigen == "
    First_4 = "\">"
    First = First_1 + str(n) + First_2+ First_3 + str(n) + First_4
    print(First)

    Second_1 = "            ~~~~~~~~~~~~~~"
    print(Second_1)

    Third_1 = "            <br>共変ベクトル \[q_i^\prime=\]"
    print(Third_1)
    Forth_1 = "            <p class="+ "\"sample1\"" + ">"
    print(Forth_1)
    Fifth_1 = "              <!-- 共変ベクトル -->"
    print(Fifth_1)

    #共変
    res_start = "\["
    res1 = "\\"+ " " + "\\"+ " " +"\\"+ "frac{\partial x_"
    res2 = "}{\partial x_i\ }\ q_"
    res_end = "\]"

    res_ans1 = "" 

    for s in reversed(range(1,n+1)):
      res = res1 + str(s) + res2 + str(s)
      #print(res)
      res_ans1 = res + "+" + " " + res_ans1
      #print(res_ans1)

    res_ans1 = res_ans1.rstrip(" +")
    res_ans2 = "              " +res_start + res_ans1 + res_end
    print(res_ans2)

    Seventh_1 = "            </p>"
    print(Seventh_1)

    Eighth_1 = "            ~~~~~~~~~~~~~~"
    print(Eighth_1)

    Ninth_1 = "            <br>反変ベクトル \[q^{\prime i}=\]"
    print(Ninth_1)
    Tenth_1 = "            <p class="+ "\"sample1\"" + ">"
    print(Tenth_1)
    Eleventh_1 = "              <!-- 反変ベクトル -->"
    print(Eleventh_1)




    #反変
    res_start = "\["
    res1 = "\\"+ " " + "\\" + "frac{\partial x^{\prime i}}{\partial x^"
    res2 = "\ }\ q^"
    res_end = "\]"

    res_ans1 = "" 

    for s in reversed(range(1,n+1)):
      res = res1 + str(s) + res2 + str(s)
      #print(res)
      res_ans1 = res + "+" + " " + res_ans1
      #print(res_ans1)

    res_ans1 = res_ans1.rstrip(" +")
    res_ans2 = "              " + res_start + res_ans1 + res_end
    print(res_ans2)

    Thirteenth_1 = "            </p>"
    print(Thirteenth_1)

    Forteenth_1 = "          </jigen"+str(n)+">"
    print(Forteenth_1)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonによる四則演算コンパイラの作成

はじめに

こんにちは,keygoroと申します.よろしくお願いします.
今回,Pythonを使用して四則演算に対応するアセンブリ言語プログラムを生成するプログラムを作成しました.完成形ではありませんが,ここにまとめたいと思います.作成するにあたり,植山類さんの低レイヤを知りたい人のためのCコンパイラ作成入門(https://www.sigbus.info/compilerbook) を参考にさせていただきました.

目的

今回の目的は,四則演算に対応するアセンブリ言語プログラムを生成するコンパイラをPythonで作成することである.例えば,30 + (4 − 2) × 5などの式がコンパイルできるようにする.

コンパイラで計算を行う際,計算の優先順位を処理する必要がある.入力として与えられる数式はただの文字列であって,構造化されたデータではない.式を正しく評価するためには,文字の並びを解析して,そこに隠れた構造をうまく導き出す必要がある.そのために,今回は構文解析の最も一般的なアルゴリズムの一つである「再帰下降構文解析法」を用いてコンパイラを作成する.以下で,コンパイラを作成するために重要なトークナイザ,構文解析,スタックマシンについて解説する.

トークナイザ

四則演算の式の最小単位のことをトークンと呼ぶ.例えば,5 + 20 − 4は,5, + ,20, − ,4という5つのトークンで構成されている.四則演算の式(文字列)をトークンの列に分解することを「トークナイズする」という.トークナイズする際には,四則演算に関係ない空白文字は無視する必要がある. 例えば,”5 + 20 − 4”という式も"5, + ,20, − ,4"という5つのトークンに分解されるようにプログラムする必要がある.
今回作成したPythonのプログラムでは,プログラム1で示すクラスにトークンの情報を格納している.kindはトークンの種類,valはトークンが数字だった場合の値,strはそのときのトークン文字列を表している.

例として, 1 * (2 + 3)のとき先頭のトークンは,kind=num,val=1,str=1*(2+3)となる.

#プログラム1 トークンのクラス
class Token:
   def __init__(self, kind, val, str):
     self.kind = kind 
     self.str = str

構文解析

四則演算の演算子の優先順位をうまく処理するためには,トークン列に対して構文解析を行う必要がある.コンパイルの行程で構文解析をする機能のことをパーサーと呼ぶ.パーサーはフラットなトークン列を演算子の優先順位を表現可能な木構造で表すことができる. 例えば,1 * (2 + 3)という式は,図1のような木構造で表すことができる.
1*(2+3)の構文木.png
                 図1 1*(2+3)を表す抽象構文木

トークン列から構文木を生成するには,四則演算の法則を記述する必要がある.法則を記述するためにここではEBNF(Extended Backus-Naur Form)を用いることにする.
EBNFでは,一つ一つの生成規則を$A=\alpha_1,\alpha_2,\dots$という形式で表す.これは記号$A$を$\alpha_1,\alpha_2,\dots$に展開できるという意味である.
$\alpha_1,\alpha_2,\dots$は0個以上の記号の列で,それ以上展開できない記号と,さらに展開される(いずれかの生成規則で左辺に来ている)記号の両方を含むことができる.以下は,EBNFを使って加減算の式の文法を表したものである.

expr = num ("+" num | "-" num)*  

ここで,numは数値を表す記号を表している.この式では,まずnumが1つあって,その後に0個以上 の「+num,あるいは-num」があるものをexprとして定めている.

演算子の優先規則もEBNFによって,以下のように記述することができる.

expr = mul ("+" mul | "-" mul)*   
mul = num ("*" num | "/" num)*

この規則では,exprが直接num(数字)に展開されず,mulを介してnumに展開されている.mulは乗除算の生成規則を表しているが,図2からもわかるように,この規則によって乗除算が先に実行されるという規則を構文木の中で自然に表現できる.
1*2+3の構文木.jpg
図2 1*2+3を表す具象構文木
ここで,図1のようなシンプルな構文木を抽象構文木,図2のような文法に1対1対応の構文木を具象構文木という.
また,以下のように再帰的に文法記述することにより,カッコの中を優先するという四則演算の規則を記述することができるようになる.

expr = mul ("+" mul | "-" mul)*
mul = primary ("*" primary | "/" primary)* 
primary = num | "(" expr ")"

以下のプログラム3は,この文法にしたがって,トークン列から構文木を生成するプログラムの主要部分である.
Expr(), Mul(), Primary()という関数が,ENBFにおけるexpr, mul, primaryに対応している.NewNnode()は,構文木の新しいノードを生成する関数である.Consume()は今参照しているトークンが+, -, *, /などの演算子であるかを判定する関数である.

#プログラム2 構文木を生成するプログラム

def Expr():
  node = Mul()#nodeに関数Mul()が入る
  while True:#Trueの間繰り返す.
     if Consume("+"):#Consumeでstr_opr="+"だったとき
      node = New_Node(ND.ADD,node,Mul())#このような新しいノードを作る
     elif Consume("-"):#Consumeでstr_opr="-"だったとき
      node = New_Node(ND.SUB,node,Mul())#このような新しいノードを作る
     else:
       return node

def Mul():
   node = Primary()#nodeに関数Primary()が入る
   while True:#Trueの間繰り返す
     if Consume("*"):#Consumeでstr_opr="*"だったとき
       node = New_Node(ND.MUL,node,Primary())#このような新しいノードを作る
     elif Consume("/"):#Consumeでstr_opr="/"だったとき
       node = New_Node(ND.DIV,node,Primary())#このような新しいノードを作る
     else:
       return node 

def Primary():
       if Consume("("):#Consumeでstr_opr="("だったとき
         node = Expr()#nodeにExpr()が入る
         Expect(")")#関数Expectでstr_opr=)
         return node
       return New_Node_Num(Expect_Number())

スタックマシン

この章では,パーサーが生成した構文木をアセンブリ言語に変換するプログラムについて説明する.説明を簡単にするために,コンピュータとしては,スタックマシンを想定する.スタックマシンに対して変換できれば,それをx86-64用に拡張するのは容易である.スタックマシンは,スタックをデータ保存領域として持っているコンピュータのことである.したがってスタックマシンでは「スタックにプッシュする」と「スタックからポップする」という2つの操作が基本操作となる.プッシュでは,スタックの一番上に新しい要素が積まれ,ポップでは,スタックの一番上から要素が取り除かれる.スタックマシンにおける演算命令は,スタックトップの要素に作用する.例えばスタックマシンのADD命令は,スタックトップから2つ要素をポップしてきて,それらを加算し,その結果をスタックにプッシュする.SUB,MUL,DIV命令は, ADDと同じように,スタックトップの2つの要素を,それらを減算・乗算・除算した1つの要素で置き換える命令ということになる.このように定義したスタックマシンを使うと,次のようなコードで2*3+4*5を計算することができる.

// 2*3を計算
PUSH 2 
PUSH 3 
MUL

// 4*5を計算

PUSH 4
PUSH 5
MUL

// 2*3 + 4*5を計算

ADD

構文木をスタックマシンのコードに変換する方法について説明するために,図3のような加算を表す構文木を考える.
加算を表す抽象構文木.png
図3 加算を表す構文木

AやBというのは部分木を抽象化して表したもので,実際にはなんらかの型のノードを意味している.この木をコンパイルするときは次のようにすればよい.

  1. 左の部分木をコンパイルする
  2. 右の部分木をコンパイルする
  3. スタックの2つの値を,それらを加算した結果で 置き換えるコードを出力

すなわち,構文木をスタックマシンにコンパイルするときは,再帰的に考えて,木を下りながらアセンブリを出力していくことになる.プログラム3はこの処理を行うPythonのプログラムの主要部である. gen()という関数は再帰処理を行っている.

#プログラム3 スタックマシンへコンパイルするプログラム
def gen(node):
      if node.kind == ND.NUM:#nodeの種類が数字のとき
         print("   push  ",node.val)#nodeの値をpushする
         return 

      gen(node.lhs)#左辺
      gen(node.rhs)#右辺
      print("   pop  rdi")
      print("   pop  rax")

      if node.kind == ND.ADD:#nodeの種類がADDだった場合
        print("   add rax,  rdi")
      elif node.kind == ND.SUB:#nodeの種類がSUBだった場合
        print("   sub rax,  rdi")
      elif node.kind == ND.MUL:#nodeの種類がMULだった場合
        print("   imul rax,  rdi")
      elif node.kind == ND.DIV:#nodeの種類がDIVだった場合
        print("   cqo")
        print("   idiv  rdi")
      else:
        print("error")#どれにも当てはまらなかったらerror

      print("   push rax")

プログラム

以下に,作成した四則演算コンパイラのプログラムをまとめて示す.

新しいトークンを作る関数,入力した数式をトークナイズする関数を定義

#新しいトークンを作る関数,入力した数式をトークナイズする関数を定義
from enum import Enum
class TK(Enum): #列挙型
  RESERVED = 0#記号なら0
  NUM = 1#整数なら1
  EOF = 2#入力の終わりなら2を出力

class Token:
   def __init__(self, kind, val, str):#トークンの種類、数値、トークン文字列の場所
     self.kind = kind #アトリビュートを追加 (kind,val,str)に対応
     self.val = val
     self.str = str

token = []#apendで追加していくリスト
oprlist=["+","-","*","/","(",")"]#演算子のリスト

def StrtoL(str):
  str2=""
  flg=0
  for i in range(len(str)):#strの長さ分繰り返す
    if oprlist.count(str[i]):#oprlistにある演算子に当てはまったらflg1としてbreak
       flg=1
       break
    else:
       str2+=str[i] #演算子ではないとき数字がstr2に加わる
  if flg==1:
     return int(str2),str[i:]#flg==1だったらこれを出力.int(str2)でstr2の文字列を数値に変換.str[i+1:]はstr[i]以降の数字を表示する
  else:
     return int(str2),0
def NewToken(kind,str):#tokenをつくってtoken[]に追加する
    a=Token(kind,0,str)#numは「低レイヤ〜」で追加していないので0としている。
    token.append(a)#tokenに追加
    return a#aを返す

def Tokenize(p_str):#入力をトークナイズする関数
    while True:#Trueの間繰り返す
      if p_str[0].isspace():#先頭が空白なのかチェック
        p_str=p_str[1:]#p_strの1文字目以降としてcontinue
        continue
      if p_str[0] in oprlist:#p_strの1文字目がoprlistと一致したら(p_oprの1文字目が記号だったら)
        NewToken(TK.RESERVED,p_str)#kindをTK.RESERVED、strをp_strとして新しいトークンを作成
        if len(p_str) >1:#p_strの長さが1以上として終端を決めている
          p_str=p_str[1:]#p_strの1文字目以降としてcontinue
          continue
        else:
           break#p_strの長さが1より小さければbreak
      if p_str[0].isdigit():#p_strの1文字目が数字かどうかチェック
          b = NewToken(TK.NUM,p_str)#kindをTK.NUM、strをp_strとして新しいトークンを作成
          num,p_str=StrtoL(p_str)
          b.val=num#valにnumを入れている
          #p_str=n_str
          if p_str==0:#p_str=0かチェック
            break#p_str=0だったらbreak
          else:#p_str=0でなかったら「トークナイズできません」と表示
            continue
      print("トークナイズできません")
    NewToken(TK.EOF,0)

str1=input("数式:")
print(str1)
#str1 = "1*(2+2)"
Tokenize(str1)#入力str1についてトークナイズ

for i in token:
  print("kind:",i.kind,"val:",i.val,"str:",i.str)

#1*(2+2)と入力した場合の出力
数式:1+(2*2)
1+(2*2)
kind: TK.NUM val: 1 str: 1+(2*2)
kind: TK.RESERVED val: 0 str: +(2*2)
kind: TK.RESERVED val: 0 str: (2*2)
kind: TK.NUM val: 2 str: 2*2)
kind: TK.RESERVED val: 0 str: *2)
kind: TK.NUM val: 2 str: 2)
kind: TK.RESERVED val: 0 str: )
kind: TK.EOF val: 0 str: 0

EBNFによる四則演算の記述に対応した構文木を生成するための関数

from enum import Enum
cur_pos=0

def Consume(str_opr):#C言語でコンパイラを作成した場合のconsumeに対応
  global token
  global cur_pos
  if token[cur_pos].kind!=TK.RESERVED or token[cur_pos].str[0]!=str_opr:#トークンの種類が記号ではない、または文字列の1文字目がstr_oprではないとき、falseを返す
    return False
  cur_pos+=1
  return True#それ以外のときはtrue

def Expect(str_opr):#C言語でコンパイラを作成した場合のexpectに対応
   global token
   global cur_pos
   if token[cur_pos].kind!=TK.RESERVED or token[cur_pos].str[0]!=str_opr:#トークンの種類が記号ではない、または文字列の1文字目が+,-ではないとき
     print(str_opr,"ではありません")#+ではありません、-ではありません をプリント
   cur_pos+=1

def Expect_Number():#C言語でコンパイラを作成した場合のexpect_numberに対応
  global token
  global cur_pos
  if token[cur_pos].kind!=TK.NUM:#トークンの種類が数字ではない時、
    print("数ではありません")#数ではありません。をプリント
  val = token[cur_pos].val
  cur_pos+=1
  return val#数字を出力

def At_EOF():#C言語でコンパイラを作成した場合のat_eofに対応
   global token
   global cur_pos
   return token[cur_pos].kind==TK.EOF#トークンの種類がEOF


class ND(Enum): #列挙型.抽象構文木でのノードの種類
  ADD = 0#+
  SUB = 1#-
  MUL = 2#*
  DIV = 3#/
  NUM = 4#整数

class Node:#抽象構文木のノードの型
   def __init__(self):
     self.kind = None#ノードの型.何も入っていない場合はNone
     self.val = None#ノードの値.kindがND_NUMの場合そのときの値を表示
     self.lhs = None#左辺
     self.rhs = None#右辺

def New_Node(kind,lhs,rhs):#新しいノードを作る関数
  node = Node()#classNodeが入っている
  node.kind = kind#ノードの型
  node.lhs = lhs#左辺
  node.rhs = rhs#右辺
  return node 

def New_Node_Num(val):
  node = Node()
  node.kind = ND.NUM#nodeの種類が数字
  node.val = val#そのときの値
  return node

def Expr():
  node = Mul()#nodeに関数Mul()が入る
  while True:#Trueの間繰り返す.
     if Consume("+"):#Consumeでstr_opr="+"だったとき
      node = New_Node(ND.ADD,node,Mul())#このような新しいノードを作る
     elif Consume("-"):#Consumeでstr_opr="-"だったとき
      node = New_Node(ND.SUB,node,Mul())#このような新しいノードを作る
     else:
       return node

def Mul():
   node = Primary()#nodeに関数Primary()が入る
   while True:#Trueの間繰り返す
     if Consume("*"):#Consumeでstr_opr="*"だったとき
       node = New_Node(ND.MUL,node,Primary())#このような新しいノードを作る
     elif Consume("/"):#Consumeでstr_opr="/"だったとき
       node = New_Node(ND.DIV,node,Primary())#このような新しいノードを作る
     else:
       return node 

def Primary():
       if Consume("("):#Consumeでstr_opr="("だったとき
         node = Expr()#nodeにExpr()が入る
         Expect(")")#関数Expectでstr_opr=)
         return node
       return New_Node_Num(Expect_Number())

node = Expr()

スタックマシンへコンパイルするプログラム

def gen(node):
      if node.kind == ND.NUM:#nodeの種類が数字のとき
         print("   push  ",node.val)#nodeの値をpushする
         return 

      gen(node.lhs)#左辺
      gen(node.rhs)#右辺
      print("   pop  rdi")
      print("   pop  rax")

      if node.kind == ND.ADD:#nodeの種類がADDだった場合
        print("   add rax,  rdi")
      elif node.kind == ND.SUB:#nodeの種類がSUBだった場合
        print("   sub rax,  rdi")
      elif node.kind == ND.MUL:#nodeの種類がMULだった場合
        print("   imul rax,  rdi")
      elif node.kind == ND.DIV:#nodeの種類がDIVだった場合
        print("   cqo")
        print("   idiv  rdi")
      else:
        print("error")#どれにも当てはまらなかったらerror

      print("   push rax")

アセンブリプログラムを出力

print(".intel_syntax noprefix")
print(".globl main")
print("main:");
gen(node)
print("  pop rax")
print("  ret")

#出力

.intel_syntax noprefix
.globl main
main:
   push   1
   push   2
   push   2
   pop  rdi
   pop  rax
   imul rax,  rdi
   push rax
   pop  rdi
   pop  rax
   add rax,  rdi
   push rax
  pop rax
  ret

まとめ

四則演算に対応するアセンブリ言語プログラムを生成するコンパイラをPythonで作成した.今後としては,コンパイラを改良し現在扱うことのできない−3などの単項式の演算や,比較演算子を扱えるようにすることを考えている.

参考文献

1] 植山 類:低レイヤを知りたい人のためのCコンパイラ作成入門,https://www.sigbus.info/ compilerbook#,2020.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TensorflowのPreProcessでTensorflow以外の関数を使う

はじめに

tensorflow.data.Datasetを使っていて、preprocess内でnumpyのFFTやopenCVを使ったらエラーが出て困った人向けです。いや自分自身が困ったので、そのTipです。

時間がない人向け

tf.py_functionを使用すると出来る。

細かい説明

tf.data.Datasetの簡単な動き

こちらはtensorflowの公式のドキュメントで書いてある通りに、datasetのloaderを作ります。pytorchでいえばdataloaderですね。

def preprocess_image(image):
    image = tf.image.decode_jpeg(image, channels=3)
    image = tf.image.resize(image, [192, 192])
    return image


def load_and_preprocess_image(path):
    image = tf.io.read_file(path)
    return preprocess_image(image)

path_ds = tf.data.Dataset.from_tensor_slices(path_list)
image_ds = path_ds.map(load_and_preprocess_image, num_parallel_calls=tf.data.experimental.AUTOTUNE)

label_ds = tf.data.Dataset.from_tensor_slices(label)
image_label_ds = tf.data.Dataset.zip((image_ds, label_ds))

やっていることは簡単で、pathを渡すと、preprocessを終えたデータが読み込まれるというよくあるdataloaderを作っています。事前に画像のPathのリストと、紐づくラベルのリストを作っておけば簡単です。
これを実行してみると、下記のようなサンプルとなります。ちなみに画像はCifar10です。

fig = plt.figure(figsize=(10, 10))
for i, (im, l) in enumerate(image_label_ds.take(9)):
    fig.add_subplot(3, 3, i + 1)
    plt.imshow(im.numpy().astype(np.uint8))
    plt.title(annot[l.numpy()])  # annotはラベル名のdictionary
plt.tight_layout()

sample1.png

凝ったPreProcessをしたい

このpreprocess内で少し凝った処理を走らせましょう。今回は例として、openCVを使って、色空間を変換(RGB->YCrCb)しています。別にNumpyのFFT関数でもいいし、まぁtensorflow純正以外の関数を使うなら何でも良いです。これで実行してみます。

def preprocess_image(image):
    image = tf.image.decode_jpeg(image, channels=3)
    image = tf.image.resize(image, [192, 192])
    image = cv2.cvtColor(image.numpy(), cv2.COLOR_RGB2YCrCb)
    image = tf.convert_to_tensor(image)
    return image

なんかものすごいエラーが出ます。エラー内容が見たい人は左の三角をクリック。
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~/workspace/project/cifar10/load_cifar10.py in 
      58 AUTOTUNE = tf.data.experimental.AUTOTUNE
      59 path_ds = tf.data.Dataset.from_tensor_slices(path_list)
----> 60 image_ds = path_ds.map(load_and_preprocess_image, num_parallel_calls=AUTOTUNE)
      61 
      62 label_ds = tf.data.Dataset.from_tensor_slices(label)

~/.pyenv/versions/py37_emopy/lib/python3.7/site-packages/tensorflow/python/data/ops/dataset_ops.py in map(self, map_func, num_parallel_calls, deterministic)
   1700           num_parallel_calls,
   1701           deterministic,
-> 1702           preserve_cardinality=True)
   1703 
   1704   def flat_map(self, map_func):

~/.pyenv/versions/py37_emopy/lib/python3.7/site-packages/tensorflow/python/data/ops/dataset_ops.py in __init__(self, input_dataset, map_func, num_parallel_calls, deterministic, use_inter_op_parallelism, preserve_cardinality, use_legacy_function)
   4082         self._transformation_name(),
   4083         dataset=input_dataset,
-> 4084         use_legacy_function=use_legacy_function)
   4085     if deterministic is None:
   4086       self._deterministic = "default"

~/.pyenv/versions/py37_emopy/lib/python3.7/site-packages/tensorflow/python/data/ops/dataset_ops.py in __init__(self, func, transformation_name, dataset, input_classes, input_shapes, input_types, input_structure, add_to_graph, use_legacy_function, defun_kwargs)
   3369       with tracking.resource_tracker_scope(resource_tracker):
   3370         # TODO(b/141462134): Switch to using garbage collection.
-> 3371         self._function = wrapper_fn.get_concrete_function()
   3372         if add_to_graph:
   3373           self._function.add_to_graph(ops.get_default_graph())

~/.pyenv/versions/py37_emopy/lib/python3.7/site-packages/tensorflow/python/eager/function.py in get_concrete_function(self, *args, **kwargs)
   2937     """
   2938     graph_function = self._get_concrete_function_garbage_collected(
-> 2939         *args, **kwargs)
   2940     graph_function._garbage_collector.release()  # pylint: disable=protected-access
   2941     return graph_function

~/.pyenv/versions/py37_emopy/lib/python3.7/site-packages/tensorflow/python/eager/function.py in _get_concrete_function_garbage_collected(self, *args, **kwargs)
   2904       args, kwargs = None, None
   2905     with self._lock:
-> 2906       graph_function, args, kwargs = self._maybe_define_function(args, kwargs)
   2907       seen_names = set()
   2908       captured = object_identity.ObjectIdentitySet(

~/.pyenv/versions/py37_emopy/lib/python3.7/site-packages/tensorflow/python/eager/function.py in _maybe_define_function(self, args, kwargs)
   3211 
   3212       self._function_cache.missed.add(call_context_key)
-> 3213       graph_function = self._create_graph_function(args, kwargs)
   3214       self._function_cache.primary[cache_key] = graph_function
   3215       return graph_function, args, kwargs

~/.pyenv/versions/py37_emopy/lib/python3.7/site-packages/tensorflow/python/eager/function.py in _create_graph_function(self, args, kwargs, override_flat_arg_shapes)
   3073             arg_names=arg_names,
   3074             override_flat_arg_shapes=override_flat_arg_shapes,
-> 3075             capture_by_value=self._capture_by_value),
   3076         self._function_attributes,
   3077         function_spec=self.function_spec,

~/.pyenv/versions/py37_emopy/lib/python3.7/site-packages/tensorflow/python/framework/func_graph.py in func_graph_from_py_func(name, python_func, args, kwargs, signature, func_graph, autograph, autograph_options, add_control_dependencies, arg_names, op_return_value, collections, capture_by_value, override_flat_arg_shapes)
    984         _, original_func = tf_decorator.unwrap(python_func)
    985 
--> 986       func_outputs = python_func(*func_args, **func_kwargs)
    987 
    988       # invariant: `func_outputs` contains only Tensors, CompositeTensors,

~/.pyenv/versions/py37_emopy/lib/python3.7/site-packages/tensorflow/python/data/ops/dataset_ops.py in wrapper_fn(*args)
   3362           attributes=defun_kwargs)
   3363       def wrapper_fn(*args):  # pylint: disable=missing-docstring
-> 3364         ret = _wrapper_helper(*args)
   3365         ret = structure.to_tensor_list(self._output_structure, ret)
   3366         return [ops.convert_to_tensor(t) for t in ret]

~/.pyenv/versions/py37_emopy/lib/python3.7/site-packages/tensorflow/python/data/ops/dataset_ops.py in _wrapper_helper(*args)
   3297         nested_args = (nested_args,)
   3298 
-> 3299       ret = autograph.tf_convert(func, ag_ctx)(*nested_args)
   3300       # If `func` returns a list of tensors, `nest.flatten()` and
   3301       # `ops.convert_to_tensor()` would conspire to attempt to stack

~/.pyenv/versions/py37_emopy/lib/python3.7/site-packages/tensorflow/python/autograph/impl/api.py in wrapper(*args, **kwargs)
    256       except Exception as e:  # pylint:disable=broad-except
    257         if hasattr(e, 'ag_error_metadata'):
--> 258           raise e.ag_error_metadata.to_exception(e)
    259         else:
    260           raise

AttributeError: in user code:

    <ipython-input-87-550493cbb714>:12 load_and_preprocess_image  *
        return preprocess_image(image)
    <ipython-input-93-6034e2f0f0fd>:4 preprocess_image  *
        image = cv2.cvtColor(image.numpy(), cv2.COLOR_RGB2YCrCb)

    AttributeError: 'Tensor' object has no attribute 'numpy'

ちなみにこの色変換の関数そのものには異常はありません。TensorflowのDatasetのようなパイプラインを介さずに利用すると、ちゃんと画像が表示できます。

image = tf.io.read_file(image_path)
image = preprocess_image(image)

fig = plt.figure(figsize=(8, 4))

fig.add_subplot(1, 2, 1)
plt.imshow(image.numpy().astype(np.uint8))
plt.title("YCC")

fig.add_subplot(1, 2, 2)
# 表示用にもう一度YCC→RGBに戻している
image = cv2.cvtColor(image.numpy(), cv2.COLOR_YCrCb2RGB)
plt.imshow(image.astype(np.uint8))
plt.title("RGB")

sample2.png

tf.py-function降臨

こういう事態を避ける方法があって、tf.py_functionを使用します。下記の例のように、tensorflow純正の関数ではない変換を別の関数にしてやり、それをラッピングする形で使用します。簡単ですね。

def trans_color(image):
    image = cv2.cvtColor(image.numpy(), cv2.COLOR_RGB2YCrCb)
    return tf.convert_to_tensor(image)

def preprocess_image(image):
    image = tf.image.decode_jpeg(image, channels=3)
    image = tf.image.resize(image, [192, 192])
    image = tf.py_function(trans_color, [image], tf.float32)
    return image

これで実際に動かしてみると、下記画像が得られました。
右上の画像と前項での画像が同じになっているのでうまく動いていそうです。

sample3.png

まとめ

tensorflowの前処理で、自身がよく知っているnumpyのFFTを使いたいとか、画像処理の定番のopenCVを使いたいとか、そういうことのTipsでした。py_functionはもっと奥深い機能だと思いますが、こんなふうにも使えるんですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jupyter Notebook で Ruby が使えるってま!?

概要

会社でRailsを使って開発をしている関係でRubyのコードを試したいことが多々ありました。
Paizaとかブラウザで実行できるものもあるがPythonみたくJupyter notebookでできないかなと思っていました。

そこで調べるとRubyをJupyter notebookに入れることが出来ることが判明(゚∀゚) 実際に導入する手順がわかったので今後PCが壊れたときのためにインストール方法をメモします。

条件

言語 ver
Ruby 2.10 以上
Jupyter Notebook --

※Ruby と jupyter notebookの道入がまだの人は以下の記事よりそれぞれのインストールを済ませて下さい。

・Rubyのインストール
【完全版】MacでRails環境構築する手順の全て

・Jupyter Noteboookのインストール

① anacondaのインストール
Anaconda(Python3)インストール手順<macOS用>

② anacondaのPATHの通し方
AnacondaのPATHの設定

③ Jupyter notebook のインストール
Installing the Jupyter Software

方法

iRubyのgithubをもとにインストールを進めていきます。

まず、依存関係にあるライブラリのインストールをします。

cmzp の versionによっては動かない可能性あり↓
* Jupyter NotebookにRuby Kernelを追加する方法

terminal
brew install automake gmp libtool wget
brew install zeromq --HEAD
# 1.30以上で動かないとの記事あり
brew install czmq --HEAD
gem install ffi-rzmq
gem install rbczmq

irubyのインストールとjupyterへの登録をします。

terminal
# irubyのインストール
gem install iruby

# irubyの登録
iruby register --force

最後にjupyter notebookの起動をします。

terminal
jupyter notebook

以下のように Ruby が New の中に入っているのを確認して下さい。

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Streamlit による売上データの分析例

目的

本項では Kaggle に登録されている売上原価の集計・可視化に取り組む。このデータセットにはある会社の支店 A、B、C の売上原価(三ヶ月分)が含まれている。需要予測等のデータ分析実務に近づけるため、支店間の売上原価を比較するダッシュボードの作成を目的とする。今回は streamlit を用いて、集計時のパラメータを GUI で操作できる interactive な図を作成する。

データの形式

以下のように請求書単位で売上原価(cost of goods sold)の履歴が記録されている。
image.png
ちなみに売上原価は次のように計算される。
(cogs)=(Unit Price)×(Quantity)

データの前処理

売上原価の時系列変化を調べるために購入時期を表す列 week を追加し、売上の周期性を調べるために曜日列 day 時間帯列 time を追加する。

Visualization.py
import re
import streamlit as st
import pandas as pd
import altair as alt

df = pd.read_csv('supermarket_sales - Sheet1.csv')

date = []
week = []
for c in df['Date']:
    c = re.findall(r'\d+', c)
    m,d = int(c[0]),int(c[1])

    if m == 3:
        d += (31+28) # 三月某日には一月と二月の日数を加える。
    elif m == 2:
        d += 31 # 二月某日には一月の日数を加える。

    d += -1

    # 2019年1月1日は火曜日
    if d%7 == 6: tmp = 'Mon'
    elif d%7 == 0: tmp = 'Tue'
    elif d%7 == 1: tmp = 'Wed'
    elif d%7 == 2: tmp = 'Thr'
    elif d%7 == 3: tmp = 'Fri'
    elif d%7 == 4: tmp = 'Sat'
    elif d%7 == 5: tmp = 'Sun'
    date.append(tmp)

    if d//14 == 0: tmp = '1/1~1/14'
    elif d//14 == 1: tmp = '1/15~1/28'
    elif d//14 == 2: tmp = '1/29~2/11'
    elif d//14 == 3: tmp = '2/12~2/25'
    elif d//14 == 4: tmp = '2/26~3/11'
    elif d//14 == 5: tmp = '3/12~3/25'
    elif d//14 == 6: tmp = '3/26~3/31' # 最終期は他より短いことに注意
    week.append(tmp)

df['day'] = date
df['week'] = week

time = []
for c in df['Time']:
    c = re.findall(r'\d+', c)
    t = c[0] + ':00~' + c[0] +':59'
    time.append(t)

df['time'] = time

st.table(df.head(10))

スクリーンショット 2021-02-13 17.34.02.png

支店間推移

まずは直近の動向を調べてみる。

Visualization.py
st.markdown('# 支店別売上推移')

stacked_bar = alt.Chart(df).mark_bar(size=35).encode(
        x=alt.X('sum(cogs)',axis=alt.Axis(labelFontSize=20, ticks=True, titleFontSize=20, labelAngle=0)),
        y=alt.Y('week', axis=alt.Axis(labelFontSize=20, ticks=True, titleFontSize=20, labelAngle=0),sort=['1/1~1/14','1/15~1/28','1/29~2/11','2/12~2/25','2/26~3/11','3/12~3/25','3/26~3/31']),
        color='Product line',
        row = alt.Row('Branch', header=alt.Header(labelFontSize=20, titleFontSize=20)),
        tooltip=['week','Total']
        ).properties(
        width=800,
        height=420,
        )

st.write(stacked_bar)

visualization-5.png

売上原価分布の比較

品目別に支店間で売上原価分布を比較する。どのような層に高額商品の需要があるのかを調べたい場合、属性条件をドロップダウンから1つ選択できるようにする。

Visualization.py!
st.markdown('# 売上原価分布の比較')

cond = st.selectbox(
    '層別条件を選ぶ',
     ('Gender', 'Customer type', 'Payment'))

boxplot = alt.Chart(df).mark_boxplot(size=50,ticks=alt.MarkConfig(width=20), median=alt.MarkConfig(color='black',size=50)).encode(
        x = alt.X('Branch',sort = alt.Sort(['A','B','C']), axis=alt.Axis(labelFontSize=15, ticks=True, titleFontSize=18)),
        y = alt.Y('cogs', axis=alt.Axis(labelFontSize=15, ticks=True, titleFontSize=18, grid=False,domain=True, title='Firmicutes')),
        column = alt.Column('Product line', header=alt.Header(labelFontSize=20, titleFontSize=20)),
        row = alt.Row(cond, header=alt.Header(labelFontSize=20, titleFontSize=20)),
    ).properties(
        width=300,
        height=300,
    )

st.write(boxplot)

ドロップダウンで Gender を選んだ場合は以下のとおり。
visualization-6.png
ドロップダウンで Payment を選んだ場合は以下のとおり。
visualization-7.png

売上原価の詳細な比較

上記の内容を踏まえて支店間で詳細に比較する。多角的に比較できるように次のパラメータを設ける。

  • 製造ラインを選択する。特定の製造ラインに着目したい場合は他を除外できる。
  • 集計する cogs の最小値と最大値を決める。スライドバーの範囲外の売上原価は集計しない。外れ値を除外できる。
  • 標準化するかどうかを決める。
  • 集計方法を選ぶ。weekは二週間毎、dayは曜日毎、timeは時間帯毎に集計する。
Visualization.py
st.markdown('# 詳細な売上原価の比較')

options = st.multiselect(
    '製造ラインを選択してください',
    list(set(df['Product line'])),
    list(set(df['Product line'])))

df = df[df['Product line'].isin(options)]

values = st.slider(
    '集計する cogs の最小値と最大値を決める',
   min(df['cogs']), max(df['cogs']), (min(df['cogs']), max(df['cogs'])))

df = df[(values[0] <= df['cogs']) & (values[1] >= df['cogs'])]

norm = st.radio("標準化するか?",
     ('Yes','No'))

axis = st.radio("集計方法を選ぶ",
     ('week','day','time'))

if axis == 'week':
    sort =['1/1~1/14','1/15~1/28','1/29~2/11','2/12~2/25','2/26~3/11','3/12~3/25','3/26~3/31']
elif axis == 'day':
    sort =['Mon','Tue','Wed','Thr','Fri','Sat','Sun']
elif axis == 'time':
    sort =['10:00~10:59','11:00~11:59','12:00~12:59','13:00~13:59','14:00~14:59','14:00~14:59','15:00~15:59','16:00~16:59','17:00~17:59','18:00~18:59','19:00~19:59','20:00~20:59']

st.markdown('## 組成の可視化')

if norm == 'Yes':  
    stacked_bar = alt.Chart(df).mark_bar(size=35).encode(
        x=alt.X('sum(cogs)',axis=alt.Axis(labelFontSize=20, ticks=True, titleFontSize=20, labelAngle=0),stack="normalize"),
        y=alt.Y(axis, axis=alt.Axis(labelFontSize=20, ticks=True, titleFontSize=20, labelAngle=0),sort=sort),
        color='Product line',
        column = alt.Column(cond, header=alt.Header(labelFontSize=20, titleFontSize=20)),
        row = alt.Row('Branch', header=alt.Header(labelFontSize=20, titleFontSize=20)),
        tooltip=[axis,'cogs']
        ).properties(
        width=800,
        height=420,
        )
else:
    stacked_bar = alt.Chart(df).mark_bar(size=35).encode(
        x=alt.X('sum(cogs)',axis=alt.Axis(labelFontSize=20, ticks=True, titleFontSize=20, labelAngle=0)),
        y=alt.Y(axis, axis=alt.Axis(labelFontSize=20, ticks=True, titleFontSize=20, labelAngle=0),sort=sort),
        color='Product line',
        column = alt.Column(cond, header=alt.Header(labelFontSize=20, titleFontSize=20)),
        row = alt.Row('Branch', header=alt.Header(labelFontSize=20, titleFontSize=20)),
        tooltip=[axis,'cogs']
        ).properties(
        width=800,
        height=420,
        )

st.write(stacked_bar)

st.markdown('## 分布の可視化')

point = alt.Chart().mark_point().encode(
    x=alt.X(axis, axis=alt.Axis(labelFontSize=20, ticks=True, titleFontSize=20, grid=False),sort=sort),
    y=alt.Y('cogs:Q', aggregate='mean', axis=alt.Axis(labelFontSize=20, ticks=True, titleFontSize=20, grid=False,domain=True)),
    color=alt.Color('Product line'),
    ).properties(
    width=400,
    height=400
    )

bar = alt.Chart().mark_errorbar(extent='stderr',ticks=True,orient='vertical').encode(
    x=alt.X(axis, axis=alt.Axis(labelFontSize=20, ticks=True, titleFontSize=20, grid=False),sort=sort),
    y=alt.Y('cogs', type='quantitative', axis=alt.Axis(labelFontSize=20, ticks=True, titleFontSize=20, grid=False,domain=True)),
    color=alt.Color('Product line'),
    ).properties(
    width=400,
    height=400
    )

chart = alt.layer(point, bar, data=df
    ).facet(
    column=alt.Column(cond, header=alt.Header(labelFontSize=20, titleFontSize=20)),
    row = alt.Row('Branch', header=alt.Header(labelFontSize=20, titleFontSize=20))
    )

st.write(chart)

今回は次のパラメータで可視化してみる。
スクリーンショット 2021-02-13 19.31.32.png

組成の可視化

visualization-10.png

分布の可視化

点は平均値、バーは標準誤差を表す。
visualization-11.png

Streamlit の利点

平均値、中央値、四分位数、標準誤差といった単純な統計量を用いたデータの吟味は、課題の設定や検証に非常に有効である。Streamlit ではこのような統計量を GUI で操作しながら可視化することができる。

Visualization.py のダウンロード

Githubからダウンロードできる。

参考

本稿は NTT Communications 第七回 TechWorkshop 「データサイエンティストによるデータ分析Workshop」で取り組んだワークの内容を参考に作成しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQLdbで文字コードエラー

こんなエラー

Traceback (most recent call last):
....
    cursor.execute(sql)
  File "/home/user/.local/lib/python3.6/site-packages/MySQLdb/cursors.py", line 188, in execute
    query = query.encode(db.encoding)
  File "/usr/lib/python3.6/encodings/cp1252.py", line 12, in encode
    return codecs.charmap_encode(input,errors,encoding_table)
UnicodeEncodeError: 'charmap' codec can't encode characters in position 486-487: character maps to <undefined>

対策
デフォルトでlatin-1を使うらしいので、接続時に明示的にエンコードコード方法を指定

conn = MySQLdb.connect(
    host=host,
    db=dbname,
    port=port,
    user=user,
    passwd=password,
    use_unicode=True,
★これ  charset="utf8mb4"
)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで学ぶ制御工学 第2弾:制御モデルの例

#Pythonで学ぶ制御工学< 制御モデルの例 >

はじめに

基本的な制御工学をPythonで実装し,復習も兼ねて制御工学への理解をより深めることが目的である.
その第2弾として制御モデルの例を扱う.

システム

システムには大きく分けて2つある.

動的システム
    過去の状態に影響を受けるようなシステム

静的システム
    過去の状態に依存しないようなシステム

制御対象は多くの場合,動的システムであり,ここで扱うのも動的システムである.

制御モデル

制御を考えるためには,制御モデルを構築できなければならない.以下では,4つの例を示して,制御モデルとはどのようにして求められるのかということへの理解を深める.なお,図において,緑の背景で示したものが制御モデルである.

制御モデル①:台車のモデル

次に台車と条件を示した図を示す.
image.png
以下にモデルの導出過程を示す.
image.png
image.png
image.png

制御モデル②:垂直駆動アームのモデル

次に垂直駆動アームと条件を示した図を示す.
image.png
以下にモデルの導出過程を示す.
image.png

image.png
image.png

制御モデル③:RLC回路のモデル

次にRLC回路と条件を示した図を示す.
image.png
以下にモデルの導出過程を示す.
image.png
image.png
image.png

制御モデル④:増幅回路のモデル

次に増幅回路と条件を示した図を示す.
image.png
以下にモデルの導出過程を示す.
image.png
image.png
image.png

感想

久しぶりに制御モデルを求める手順をおさらいできた.次回からはようやく,これらの制御モデルに対して伝達関数などを扱うことができる.これについても学んできてはいるが,今回同様,良い復習になり,また新たな鬼月が得られるかもしれない.楽しみである.

参考文献

Pyhtonによる制御工学入門  南 祐樹 著  オーム社

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

A - Repeat ACL, B - Balance, C - Exam and Wizard

2021/2/12
くじかつ精進

A - Repeat ACL

O(1)

python
N = int(input())

S = ""
for i in range(0, N):
  S += "ACL"

print(S)

B - Balance

O(N)

python
import math
import itertools

N = int(input())
W = list(map(int, input().split()))

S1 = 0
S2 = sum(W)
res = 10000
for i in range(0, N):
    S1 += W[i]
    S2 -= W[i]
    res = min(res, abs(S2-S1))

print(res)

C - Exam and Wizard

O(N)

python
N = int(input())
A = list(map(int, input().split()))
B = list(map(int, input().split()))

total = 0
total_BtoA = 0
total_AtoB = 0
total_AtoB_0 = 0
C = []
for i in range(0, N):
    if B[i] > A[i]:
        total_BtoA+=1
        total += B[i] - A[i]
    elif B[i] < A[i]:
        C.append(A[i] - B[i])
    else:
        total_AtoB_0 += 1

C.sort(reverse=True)

for i in range(0, len(C)):
    if total >=0:
        total -= C[i]
        total_AtoB+=1

if total>0:
    print(-1)
elif (len(C) + total_AtoB_0 == N):
    print(0)
else:
    print(total_AtoB + total_BtoA)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】comment属性でセルのコメントを取得する

pythonを使用してExcelファイルの操作を勉強しています。
本日の気づき(復習)は、セルのコメントに関してです。
pythonでExcelを操作するため、openpyxlというパッケージを使用しています。

image.png

上記のような各コメントを一覧で残しておきたいとします。

セルのコメントを取得する方法

先ずはセルにコメントがあるかどうかの判定をします

# セルにコメントがあるかどうかを判定
cell.comment
# コメントがない場合
--> None

コメントがあったら、下記のように取得します。

# コメントのテキストを取得
cell.comment.text

# コメントの作成者を取得
cell.comment.aouther

コメントを挿入することもできます。

# コメントのテキストを取得
cell.comment = Comment(コメントのテキスト,コメントの作成者)

コメントが挿入されている番地を取得する事も出来ます。

# コメントが挿入されているセル番地を取得
cell.coordinate

Worksheet.iter_rowsメソッド

今回はセルの1行目から順にコメントの有無を調べたいので、
Worksheet.iter_rowsメソッドを使用しました。

ws.iter_rows(min_row=最小の行番号,max_row=最大の行番号,
             min_col=最小の列番号,max_col=最大の列番号)

max_rowなどの引数を省略するとデータが入っている最大の位置まで取得してくれます。

ちなみに、rowsがあったら、colsもやっぱりあって

ws.iter_cols(min_col=最小の列番号,max_col=最大の列番号,
             min_row=最小の行番号,max_row=最大の行番号)

こちらで、列単位で順に取得してくれます。
これらをふまえて

read_comment.py
from openpyxl import load_workbook, Workbook
from openpyxl.comments import Comment

wb_new = Workbook()
ws_new = wb_new.active
ws_new.title = '説明一覧'

wb = load_workbook('日報.xlsx')
ws = wb.active

ws_new['B2'] = '説明内容'
ws_new['C2'] = '記入者'
ws_new['D2'] = 'セル番地'
ws_new.column_dimensions['B'].width = 40
row_count = ws_new.max_row
print(f'新規作成したシートの行数は{row_count}です。')

for row in ws.iter_rows(min_row=1):
    for cell in row:
        if cell.comment is None:
            continue
        row_count = row_count + 1
        ws_new[f'B{row_count}'] = cell.comment.text
        ws_new[f'C{row_count}'] = cell.comment.author
        ws_new[f'D{row_count}'] = cell.coordinate

ws_new['D2'].comment = Comment('説明があったセル番号', 'hogehoge')
wb_new.save('説明一覧.xlsx')

こんな感じで記述してみました。
個人的にはコメント取得よりも、Worksheet.iter_rowsメソッドの方が勉強になりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】Google SpreadSheetsから手元のCSVファイルをインポートする

概要

タイトルそのまま。
Google SpreadSheetsから手元のCSVファイルの内容を取り込みます。

gsp.gif

環境

  • Docker
  • Python3系
  • Google Cloud Platform

下準備

まず、外部からGoogle SpreadSeetsにアクセスするために「Google Cloud Platform」の設定を行います。

https://console.cloud.google.com/
gcp_censored.jpg

まだGoogle Cloud Platformのアカウントを持っていない方は下記記事などを参考に作成しておいてください。

参照記事: これから始めるGCP(GCE) 安全に無料枠を使い倒せ

Google Sheets APIを有効化

スクリーンショット 2021-02-13 17.22.00.png

左サイドバーから「APIとサービス→ライブラリ」を選択。各種Google APIが出てくるので、その中から「Google Sheets API」を見つけて有効化します。

スクリーンショット 2021-02-13 17.22.36.png

サービスアカウントを作成

次に左サイドバーから「APIとサービス→認証情報」を選択。「サービスアカウント」の作成を行います。
参照記事: GCP Service Accountを理解する

スクリーンショット 2021-02-13 17.23.48_censored.jpg

  • サービスアカウント名
    • 適当
  • サービスアカウントの説明
    • わかりやすければ何でもOK

サービスアカウント名以外は基本的に任意入力項目なので、特にこだわりが無ければ空欄で大丈夫です。

スクリーンショット 2021-02-13 17.24.53_censored.jpg

秘密鍵を取得

サービスアカウント詳細ページの下部にいくと「鍵を追加」という項目があるので、JSON形式で秘密鍵を作成。すると手元のデバイスにJSONファイルがダウンロードされるので、「client_secret.json」にリネームして保管しておいてください。(後ほど使用します。)

Google SpreadSheetsを作成

スクリーンショット 2021-02-13 17.18.49.png

今回操作するための適当なGoogle SpreadSheetsを作成しておきます。

スクリーンショット 2021-02-13 17.26.30_censored.jpg

あと、右の方にある「共有」ボタンから先ほど作成したサービスアカウントのメールアドレスを「編集者」として追加しておくのも忘れずに。

コード

API経由でGoogle SpreadSheetsにアクセスするための下準備が済んだので、いよいよコードを書いていきます。

環境構築

まず、Pythonを実行できる環境をDockerで準備します。

ディレクトリを作成

$ mkdir csv-to-google-spread-sheets
$ cd csv-to-google-spread-sheets
$ mkdir opt

各種ファイルを作成

touch Dockerfile
touch docker-compose.yml
touch requirements.txt
./Dockerfile
FROM python:3
USER root

RUN apt-get update
RUN apt-get -y install locales && \
    localedef -f UTF-8 -i ja_JP ja_JP.UTF-8

ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
ENV TZ JST-9
ENV TERM xterm

ADD . /code
WORKDIR /code

RUN apt-get install -y vim less
RUN pip install --upgrade pip
RUN pip install --upgrade setuptools
RUN pip install -r requirements.txt
./docker-compose.yml
version: '3'
services:
  python3:
    restart: always
    build: .
    container_name: 'python3'
    working_dir: '/root/'
    tty: true
    volumes:
      - ./opt:/root/opt
./requirements.txt
gspread
oauth2client

client_secret.jsonを配置

サービスアカウント作成の際にダウンロードした秘密鍵「client_secret.json」をoptフォルダ以下に配置します。

最終的に次のような構成になっていればOK。

csv-to-google-spread-sheets
├─ opt
  ├─ client_secret.json
├─ docker-compose.yml
├─ Dockerfile
├─ requirements.txt

コンテナを起動

$ docker-compose up  -d
$ docker exec -it python3 /bin/bash
$ cd opt

root@*********:~/opt# 

テスト実行

touch test.py
./opt/test.py
print('test')
$ python test.py

test

ちゃんと「test」と出力されていたら成功です。

実装

環境構築ができたので、本格的なコードを書いていきます。

$ touch gs.py
./opt/gs.py
import gspread
from oauth2client.service_account import ServiceAccountCredentials
import csv

SPREADSHEET_KEY = 'それぞれの値を入力' # 「docs.google.com/spreadsheets/d/xxxxxxxxxxxxx/edit#gid=0」の「xxxxxxxxxxxxx」部分

scope = 'https://spreadsheets.google.com/feeds'
credentials_file_path = './client_secret.json'

credentials = ServiceAccountCredentials.from_json_keyfile_name(credentials_file_path, scope)

gc = gspread.authorize(credentials)
workbook = gc.open_by_key(SPREADSHEET_KEY)

workbook.values_update(
    'シート1', # どのシートに書き込むのかを指定
    params={'valueInputOption': 'USER_ENTERED'},
    body={'values': list(csv.reader(open('./test.csv', encoding='utf_8_sig')))}
)

最後に、Google Spread Sheetsに取り込むようのCSVファイルを準備します。

$ touch test.csv
/opt/test.csv
Name,Age,Country
Taro,20,Japan

次のコマンドを実行し、該当のGoogle SpreadSheetsに↑の内容が書き込まれていれば成功です。

$ python gs.py

gsp.gif

あとがき

お疲れ様でした。基本的な流れは以上です。あとは各自の目的に合わせてカスタマイズしてみてください。Lambdaとかでも上手く使えそうな気がします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

tensorflow-gpuをRTX3070で動かすのは大変だった

概略

TensorFlow公式ページ(https://www.tensorflow.org/install/gpu) に従ってGPU版を導入するもうまく動作しなかった。

ファイル名の書き換えで動作することを確認。

RTX30シリーズで頻発している様子。
今回の対応はかなり応急処置的なので改善待ちですね・・・。

環境

OS
Windows 10 Home(64bit)
バージョン : 20H2

CPU
Intel(R) Core(TM) i7-10700 CPU @ 2.90GHz 2.90 GHz

GPU
GeForce RTX 3070
Driver version : 461.40
CUDA Version : 11.2

言語とライブラリ
conda : 4.9.2
Python : 3.8.5
tensorflow-gpu : 2.4.1

解決までの経緯

指定の要件で動かない

公式のソフトウェア要件(https://www.tensorflow.org/install/gpu#software_requirements) の通りにセットアップして学習を始めると以下のメッセージが

2021-02-13 17:10:23.436049: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library cudart64_110.dll
2021-02-13 17:10:24.817916: I tensorflow/compiler/jit/xla_cpu_device.cc:41] Not creating XLA devices, tf_xla_enable_xla_devices not set
2021-02-13 17:10:24.818847: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library nvcuda.dll
2021-02-13 17:10:24.850803: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1720] Found device 0 with properties:
pciBusID: 0000:01:00.0 name: GeForce RTX 3070 computeCapability: 8.6
coreClock: 1.725GHz coreCount: 46 deviceMemorySize: 8.00GiB deviceMemoryBandwidth: 417.29GiB/s

~~ 中略 ~~

2021-02-13 17:10:24.892311: W tensorflow/stream_executor/platform/default/dso_loader.cc:60] Could not load dynamic library 'cusolver64_10.dll'; dlerror: cusolver64_10.dll not found
2021-02-13 17:10:24.895383: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library cusparse64_11.dll
2021-02-13 17:10:24.895962: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library cudnn64_8.dll
2021-02-13 17:10:24.908415: W tensorflow/core/common_runtime/gpu/gpu_device.cc:1757] Cannot dlopen some GPU libraries. Please make sure the missing libraries mentioned above are installed properly if you would like to use GPU. Follow the guide at https://www.tensorflow.org/install/gpu for how to download and setup the required libraries for your platform.
Skipping registering GPU devices...
2021-02-13 17:10:24.909790: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2021-02-13 17:10:24.910559: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1261] Device interconnect StreamExecutor with strength 1 edge matrix:
2021-02-13 17:10:24.910934: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1267]    

2021-02-13 17:10:24.911055: I tensorflow/compiler/jit/xla_gpu_device.cc:99] Not creating XLA devices, tf_xla_enable_xla_devices not set
2021-02-13 17:10:25.019189: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:116] None of the MLIR optimization passes are enabled (registered 2)
Epoch 1/100
15/15 [==============================] - 2s 80ms/step - loss: 1.1186 - accuracy: 0.3416
Epoch 2/100
15/15 [==============================] - 1s 80ms/step - loss: 1.0781 - accuracy: 0.3804

~~ 以下略 ~~

最終的に学習は始まってるし一見できている風なんですが、

ここ

Skipping registering GPU devices...

「GPUでやるの飛ばすね。。。」とおっしゃっています。
なので、CPUで動いています。なぜ?

'cusolver64_10.dll'が読み込めない

さっきのメッセージをよく読むと

Could not load dynamic library 'cusolver64_10.dll'; dlerror: cusolver64_10.dll not found

「'cusolver64_10.dll'がないよ」とのこと。
実際CUDAの入っているフォルダを確認してもそんなものはない。

調べてみると公式のGitHubリポジトリでもissueが上がっていました。
(https://github.com/tensorflow/tensorflow/issues/44291)
そこで見つけた解決策がこちら

訳「'cusolver64_11.dll'を'cusolver64_10.dll'に名前変更したら解決しましたよ」

それありなの?(笑)

ありでした

2021-02-13 18:02:35.671492: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library cusolver64_10.dll

はい、読み込めてますね。
(かなり胡散臭い対応ですが)これでとりあえずは動くみたいです。

ちなみにGPUだとさっきのCPUで動かした場合の約10倍の速度で学習しています。早い!

# GPU
Epoch 1/100
15/15 [==============================] - 0s 10ms/step - loss: 1.1017 - accuracy: 0.3842
Epoch 2/100
15/15 [==============================] - 0s 8ms/step - loss: 1.0474 - accuracy: 0.4595  

#CPU
Epoch 1/100
15/15 [==============================] - 2s 80ms/step - loss: 1.1186 - accuracy: 0.3416
Epoch 2/100
15/15 [==============================] - 1s 80ms/step - loss: 1.0781 - accuracy: 0.3804

参考

GPU サポート  |  TensorFlow
https://www.tensorflow.org/install/gpu

cuda - tensorflow-gpu の導入がうまくいきません [RTX 3070] - スタック・オーバーフロー
https://ja.stackoverflow.com/questions/72013/tensorflow-gpu-%E3%81%AE%E5%B0%8E%E5%85%A5%E3%81%8C%E3%81%86%E3%81%BE%E3%81%8F%E3%81%84%E3%81%8D%E3%81%BE%E3%81%9B%E3%82%93-rtx-3070

nvidia cudaセットアップ - Qiita
https://qiita.com/mailstop/items/a84ac6b4eac8d12ba488

tensorflow-nightly-gpu looking for cusolver64_10.dll on a cuDNN 11.1 installation · Issue #44291 · tensorflow/tensorflow
https://github.com/tensorflow/tensorflow/issues/44291

RTX 3090 and Tensorflow for Windows 10 - step by step : tensorflow
https://www.reddit.com/r/tensorflow/comments/jsalkw/rtx_3090_and_tensorflow_for_windows_10_step_by/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

張り出しばりの断面力を python と API を用いてサクッと算出

構造力学 の教科書に記載されている問題を python で解いてみました

構造力学は人によっては難解だと感じる学問、python の勉強は楽しいけど 構造力学は苦手という人向けに
python を用いて構造力学の問題を解いてみたいと思います。

今回取り上げる 例題は、

構造力学[第2版]上-静定編

image.png

から
4章 演習問題4.3 より

図 4.20 に示す張り出しばりのQ図, M図を描け

image.png

を python と api でサクッと解いてみたいと思います。

全ソースコードはこちら
https://colab.research.google.com/drive/11cMEEUSFgu3hVw0vLOo1PZhvwSiM-5Ys?usp=sharing

解説

今回用いた環境 Google colaboratory

Google colaboratory という Google が無償で提供する python をインストールしなくても使える環境で
問題を解きます。

計算の流れ

  1. 入力データを生成
  2. API に断面力の計算をさせる
  3. 計算結果を表示する

1.入力データを生成する

この変数body に 入力データを記録していきます。

body = dict()

格点 を定義する

トラスなどの骨組構造における部材と部材の交点の座標と番号を定義する

image.png

n1 = {"x":0, "y":0, "z":0}
n2 = {"x":2, "y":0, "z":0}
n3 = {"x":6, "y":0, "z":0}

body["node"] = {
    "1": n1,
    "2": n2,
    "3": n3 
    }

部材 を定義する

骨組構造における部材の番号と下記の情報を定義する

  • ni: 部材の左端の点
  • nj: 部材の右端の点
  • e: 後述する材料番号

image.png

body["member"] = {
    "1": {"ni":"1", "nj":"2", "e":"1"},
    "2": {"ni":"2", "nj":"3", "e":"1"}
        }

材料の番号と下記の情報を定義する

今回の問題では、材料は一様で、特に指定がないので、適当な数値を入力している

  • E: 弾性係数
  • G: せん断弾性係数
  • Xp: 膨張係数
  • A: 断面積
  • J: ねじり定数
  • Iy: y軸周りの断面二次モーメント
  • Iz: z軸周りの断面二次モーメント
body["element"] = {
    "1":{
       "1":{ "E":20000000, "G":770000, "Xp":0.00001, "A":1.0, "J":1.0, "Iy":1.0, "Iz":1.0 }
      }
    }

支点の情報を定義する

  • n: 固定する格点番号
  • tx: x方向の変位を固定する場合は1, 固定しない場合は0
  • ty: y方向の変位を固定する場合は1, 固定しない場合は0
  • tz: z方向の変位を固定する場合は1, 固定しない場合は0
  • rx: x軸周りの回転を固定する場合は1, 固定しない場合は0
  • ry: y軸周りの回転を固定する場合は1, 固定しない場合は0
  • rz: z軸周りの回転を固定する場合は1, 固定しない場合は0

image.png

body["fix_node"] = { 
    "1":[
       {"n":"2","tx":1,"ty":1,"tz":1,"rx":0,"ry":0,"rz":0},
       {"n":"3","tx":0,"ty":1,"tz":1,"rx":0,"ry":0,"rz":0}
      ]
    }

荷重の情報を定義する

  • fix_node: 支点の情報の番号
  • element: 材料情報の番号
  • load_node
    • n: 荷重を載荷する格点番号
    • tx: x方向に作用する荷重
    • ty: y方向に作用する荷重
    • tz: z方向に作用する荷重
    • rx: x軸周りに作用するモーメント
    • ry: y軸周りに作用するモーメント
    • rz: z軸周りに作用するモーメント

image.png

body["load"] = {
      "1":{
         "fix_node": "1",
         "element": "1",
         "load_node":[
                      { "n":"1","tx":0,"ty":0,"tz":2,"rx":0,"ry":0,"rz":0 }
                      ]
        }
    }

2. API に断面力の計算をさせる

断面力の計算は、StructuralEngineという API を使います。
上記のデータを ここに POST すると断面力を計算してくれます

response = requests.post(
    'https://asia-northeast1-the-structural-engine.cloudfunctions.net/frameWeb-2',
     json.dumps(body),
     headers={'Content-Type': 'application/json'})
result = response.json()   # レスポンスのHTMLを文字列で取得
print('解析結果', result)   
解析結果
{'1': {'disg': {'1': {'dx': 0.0, 'dy': 0.0, 'dz': 8e-07, 'rx': 0.0, 'ry': 4.666666666666666e-07, 'rz': 0.0}, '2': {'dx': 0.0, 'dy': 0.0, 'dz': 0.0, 'rx': 0.0, 'ry': 2.6666666666666667e-07, 'rz': 0.0}, '3': {'dx': 0.0, 'dy': 0.0, 'dz': 0.0, 'rx': 0.0, 'ry': -1.333333333333333e-07, 'rz': 0.0}}, 'reac': {'2': {'tx': 0.0, 'ty': 0.0, 'tz': -1.0000000000000016, 'mx': 0.0, 'my': 0.0, 'mz': 0.0}, '3': {'tx': 0.0, 'ty': 0.0, 'tz': 1.0000000000000002, 'mx': 0.0, 'my': 0.0, 'mz': 0.0}}, 'fsec': {'1': {'P1': {'fxi': -0.0, 'fyi': -0.0, 'fzi': -2.0000000000000018, 'mxi': 0.0, 'myi': -3.552713678800501e-15, 'mzi': 0.0, 'fxj': 0.0, 'fyj': 0.0, 'fzj': -2.0000000000000018, 'mxj': -0.0, 'myj': 4.000000000000002, 'mzj': -0.0, 'L': 2.0}}, '2': {'P1': {'fxi': -0.0, 'fyi': -0.0, 'fzi': 1.0000000000000002, 'mxi': 0.0, 'myi': 4.0, 'mzi': 0.0, 'fxj': 0.0, 'fyj': 0.0, 'fzj': 1.0000000000000002, 'mxj': -0.0, 'myj': -4.440892098500626e-16, 'mzj': -0.0, 'L': 4.0}}}}}

3. 計算結果を表示する

API で得られた解析結果から Q図と M図を作図します。

断面力の情報を取り出す

情報は、以下の階層で格納されているため 取り出して作図します。

  • "1": ケース番号
    • "1","2": 部材番号
      • fsec: 断面力
        • P1:着目点
          • fzi:z軸方向 左側のせん断力
          • fzj:z軸方向 右側のせん断力
          • myi:y軸周り 左側の曲げモーメント
          • myj:y軸周り 右側の曲げモーメント
# ケース1の情報を取り出す
case1 = result['1']
# 断面力情報を取り出す
fsec = case1['fsec']
# 部材番号1の情報を取り出す
member1 = fsec['1']['P1']
# 部材番号2の情報を取り出す
member2 = fsec['2']['P1']

Q図、M図を作図する

# 部材のx座標
x = [ n1['x'], n2['x'], n2['x'], n3['x'] ]
# 部材のy座標
y = [ n1['y'], n2['y'], n2['y'], n3['y'] ]
# せん断力
yQ = [ member1['fzi'], member1['fzj'], member2['fzi'], member2['fzj']]
# 曲げモーメント
yM = [ member1['myi'], member1['myj'], member2['myi'], member2['myj']]

以上の情報から図を描きます。

fig = plt.figure()

ax1 = fig.add_subplot(1, 2, 1)
ax1.set_ylim([2, -3])
ax1.set_xlim([-1, 7])
ax1.plot(x, y, marker ='o', color = "darkblue")
ax1.plot(x, yQ, color = "lightblue")
ax1.fill_between( x, yQ, color="lightblue", alpha=0.2)
ax1.set_title("Q")


ax2 = fig.add_subplot(1, 2, 2)
ax2.set_ylim([-1, 5])
ax2.set_xlim([-1, 7])
ax2.plot(x, y, marker ='o', color = "darkblue")
ax2.plot(x, yM, color = "lightblue")
ax2.fill_between( x, yM, color="lightblue", alpha=0.2)
ax2.set_title("M")

image.png

答えが一致しました

まとめ

構造力学 の教科書に記載されている問題を python で解いてみました

構造力学が苦手でも、python があれば理解せずに先に進めます
いろいろ遊んでるうちに 構造力学の理解が深まれば幸いです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

scikit-learnを使ってみる

scikit-learnを使ってみたので、そのメモです。
scikit-learnはPythonから使える機械学習ライブラリです。
https://scikit-learn.org/stable/index.html

線形回帰

一次関数

一次関数 y = 2x + 3 を求めます。
xの値を0〜10の範囲で50個準備します。
最初の75%をトレーニングデータ、残り25%をテストデータとして使用します。

import numpy as np
from sklearn import linear_model
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

# Y = 2X + 3を求める関数とする。
X = np.linspace(0, 10, num=50)
Y = 2 * X + 3

train_X, test_X, train_Y, test_Y = train_test_split(X, Y, shuffle=False)
train_X2 = train_X.reshape(-1, 1)
test_X2 = test_X.reshape(-1, 1)

clf = linear_model.LinearRegression()
train_X2 = train_X.reshape(-1, 1)
clf.fit(train_X2, train_Y)

print(f'回帰係数={clf.coef_}')
print(f'切片={clf.intercept_}')
print(f'決定係数={clf.score(train_X2, train_Y)}')

plt.scatter(X, Y, label='data')
plt.plot(train_X, clf.predict(train_X2), color='green', label='train')
plt.plot(test_X, clf.predict(test_X2), color='red', label='test')
plt.legend()
plt.savefig('linear_regression1.png')
plt.show()

結果をグラフにしてみます。

linear_regression1.png

完璧です!

二次関数

同じような感じで二次関数を求めます。
求める式は、y = x^2 + 2x + 3 です。

import numpy as np
from sklearn import linear_model
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

# Y = X^2 + 2X + 3を求める関数とする。
X = np.linspace(0, 10, num=50)
Y = X**2 + 2 * X + 3

train_X, test_X, train_Y, test_Y = train_test_split(X, Y, shuffle=False)
train_X2 = train_X.reshape(-1, 1)
test_X2 = test_X.reshape(-1, 1)

clf = linear_model.LinearRegression()
train_X2 = train_X.reshape(-1, 1)
clf.fit(train_X2, train_Y)

print(f'回帰係数={clf.coef_}')
print(f'切片={clf.intercept_}')
print(f'決定係数={clf.score(train_X2, train_Y)}')

plt.scatter(X, Y, label='data')
plt.plot(train_X, clf.predict(train_X2), color='green', label='train')
plt.plot(test_X, clf.predict(test_X2), color='red', label='test')
plt.legend()
plt.savefig('linear_regression2.png')
plt.show()

結果をグラフにしてみます。

linear_regression2.png

一次関数を求めようとしているので、結果は予想通りフィットしていません。

二次関数 - 多項式を使う

前の例をPolynomialFeaturesを使って二次関数として求めてみます。

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
import matplotlib.pyplot as plt

# Y = X^2 + 2X + 3を求める関数とする。
X = np.linspace(0, 10, num=50)
Y = X**2 + 2 * X + 3

train_X, test_X, train_Y, test_Y = train_test_split(X, Y, shuffle=False)
train_X2 = train_X.reshape(-1, 1)
test_X2 = test_X.reshape(-1, 1)

pf = PolynomialFeatures(degree=2, include_bias=False)
train_X2 = pf.fit_transform(train_X2)

lr = LinearRegression()
lr.fit(train_X2, train_Y)

test_X2 = pf.fit_transform(test_X2)

print(f'回帰係数={lr.coef_}')
print(f'切片={lr.intercept_}')
print(f'決定係数={lr.score(train_X2, train_Y)}')

plt.scatter(X, Y, label='data')
plt.plot(train_X, lr.predict(train_X2), color='green', label='train')
plt.plot(test_X, lr.predict(test_X2), color='red', label='test')
plt.legend()
plt.savefig('linear_regression3.png')
plt.show()

結果をグラフにしてみます。
linear_regression3.png

フィットしました!

三角関数

次に三角関数で同じことをやってみます。
求める関数はsin関数です。
やる前から結果は見えているような気もしますが...

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
import matplotlib.pyplot as plt

# 三角関数 Y = sin(X)を求める。
X = np.linspace(0, 20, num=200)
Y = np.sin(X)

train_X, test_X, train_Y, test_Y = train_test_split(X, Y, shuffle=False)
train_X2 = train_X.reshape(-1, 1)
test_X2 = test_X.reshape(-1, 1)

pf = PolynomialFeatures(degree=2, include_bias=False)
train_X2 = pf.fit_transform(train_X2)

lr = LinearRegression()
lr.fit(train_X2, train_Y)

test_X2 = pf.fit_transform(test_X2)

print(f'回帰係数={lr.coef_}')
print(f'切片={lr.intercept_}')
print(f'決定係数={lr.score(train_X2, train_Y)}')

plt.scatter(X, Y, label='data')
plt.plot(train_X, lr.predict(train_X2), color='green', label='train')
plt.plot(test_X, lr.predict(test_X2), color='red', label='test')
plt.legend()
plt.savefig('linear_regression4.png')
plt.show()

結果をグラフにしてみます。

linear_regression4.png

三角関数を二次関数として求めているので予想通りの結果です。
では何次関数ならフィットするのでしょうか?
2〜5次で試してみます。

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
import matplotlib.pyplot as plt

# 三角関数 Y = sin(X)を求める。
X = np.linspace(0, 20, num=200)
Y = np.sin(X)

train_X, test_X, train_Y, test_Y = train_test_split(X, Y, shuffle=False)

for degree in np.arange(2, 6):
    train_X2 = train_X.reshape(-1, 1)
    test_X2 = test_X.reshape(-1, 1)

    pf = PolynomialFeatures(degree=degree, include_bias=False)
    train_X2 = pf.fit_transform(train_X2)

    lr = LinearRegression()
    lr.fit(train_X2, train_Y)

    test_X2 = pf.fit_transform(test_X2)

    print(f'degree={degree}')
    print(f'  回帰係数={lr.coef_}')
    print(f'  切片={lr.intercept_}')
    print(f'  決定係数={lr.score(train_X2, train_Y)}')

    plt.plot(train_X, lr.predict(train_X2))
    plt.plot(test_X, lr.predict(test_X2), label=f'degree={degree}')

plt.scatter(X, Y, label='data', marker='.')
plt.legend()
plt.savefig('linear_regression5.png')
plt.show()

結果をグラフにしてみます。

linear_regression5.png

次数が多くなると発散していく感じがします。

サポートベクターマシン

サポートベクターマシンを使って学習させてみます。

import numpy as np
from sklearn import svm
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

# 三角関数 Y = sin(X)を求める。
X = np.linspace(0, 20, num=200)
Y = np.sin(X)

train_X, test_X, train_Y, test_Y = train_test_split(X, Y, shuffle=False)
train_X2 = train_X.reshape(-1, 1)
test_X2 = test_X.reshape(-1, 1)

regr = svm.SVR()
regr.fit(train_X2, train_Y)

plt.scatter(X, Y, label='data', marker='.')
plt.plot(train_X, regr.predict(train_X2), color='green', label='train')
plt.plot(test_X, regr.predict(test_X2), color='red', label='test')
plt.legend()
plt.savefig('svm_regression1.png')
plt.show()

結果をグラフにします。
svm_regression1.png

n次関数の予想よりはだいぶ良くなりました。
トレーニングデータはいい感じですが、テストデータに関しては今ひとつです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TWELITE OPEN-CLOSE SENSE PALを使って窓の鍵開閉を検知してみた

前から出かけるときに窓の鍵が閉まってるか確認するのが面倒だなと思ってて、IoT機器で簡単に確認できるようにしたいと思っていて、製品が出てるのは知ってるのですが、結構値段が高いので自分で作れないかと思っていました。
そんなある日、たまたま郵便がポストに届いたらLINEで通知がくるようにしたという記事を見てTWELITEを知って、これ使えばやりたいことができると思いました。

これ誰かやってるんじゃないか?と思ったら、案の定すでにやってる人がいました。
TWELITE PALを使って窓の施錠確認システムを電子工作なしで作る

基本はこれの通りにやったのですが、最初にセットアップする部分がTWELITEのバージョン?によって違ったりWindows向けの情報はすぐ見つかるけどMac向けが見当たらなかったり、親機子機の設定方法がもっと簡単な方法があったので備忘録として残しておきます。ここではPythonでセンサーの変化を読み取るところをゴールとします。

結果

こんな感じで動きます。
twelite_open_close-min.gif
手に磁石を持っていて、近づけるとセンサーが感知します。
見づらいですがターミナル上で出力が変わっていて、手を近づけたときにOpen、離すとCloseと出てます。磁石を窓の鍵に付ければ開いてるか閉まってるかわかりますね。

動作環境

macOS Catalina 10.15.7
MacBook Air
Python 3.8.6

セットアップ

親機をMacにUSB接続して認識されているかlsコマンドで確認する。
ls -l /dev/tty.*
正常に認識されてると
/dev/tty.usbserial-XXXXXX
みたいな結果があるはずです。あとでPythonのスクリプトからセンサー読み取るときに使います。

必要なライブラリをインストールする。(公式を参考に)
brew install libusb
pip install pyserial
pip install pyftdi

TWELITE STAGE APPのバイナリで親機の設定を行う。
githubに販売元が公開しているバイナリを使う。(ここがソースコードからインストールしている説明がたくさんあって迷ってしまったところ。バイナリを使うのが一番簡単)
https://github.com/monowireless/TWELITE_Stage_BIN_macOS
ダウンロードして解凍したら、解凍したディレクトリ直下にある「TWELITE_Stage.command」がアプリです。これに実行権限をつけます。ターミナルで以下を実行。

chmod +x TWELITE_Stage.command

あとは、公式ページにある通りに以下の設定していく。
https://www.mono-wireless.com/jp/products/twelite-pal/sense/pal-usage.html
- 親機・中継機アプリ(App_Wings)に書換える。
- アプリケーションIDを子機と同じにする。
- 周波数チャネルを子機と同じにする。
- 親機か中継機を選択する(親機モード0に設定する)
- [ビューア]>[PALビューア]で動作確認する。

最後に、Pythonからセンサーの状態を読み取るプログラムについて。
公式ページにある下記のスクリプトでできます。

TWELITE PALシリーズのデータを受信したときに出力されたシリアルデータを解釈するためのPythonのサンプルスクリプトです。
https://mono-wireless.com/jp/products/TWE-APPS/App_pal/palscript.html

引数にUSBが認識されて出力されたIDを指定して実行
python PAL_Script.py -t /dev/tty.usbserial-XXXXXX

これで最初に紹介した動画のようにセンサーの変化を検知して出力できます。
TWELITEは電子工作なしでもセンサーでいろいろ遊べそうです。

参考にした記事たち

郵便がポストに届いたら LINE で通知がくるようにした
https://qiita.com/task_jp/items/85ccc04a00982569f6c5

TWELITEの無線タグアプリを試す
https://qiita.com/miminashi/items/453f7f3e31c8c009e486

無線通信機能付き磁力センサ「OPEN-CLOSE SENSE PAL」を使ってみた
https://qiita.com/RyoOkaya/items/660ceb91d2b4e0378a8d

TWELITE STAGE を試す
https://qiita.com/nanbuwks/items/bb07970d5339177aa7a0

TWELITE PALを使って窓の施錠確認システムを電子工作なしで作る
https://zlog.hateblo.jp/entry/2019/08/18/Twelite-pal-window-monitor

TWELITE STAGE APP macOS向けのバイナリ
https://github.com/monowireless/TWELITE_Stage_BIN_macOS

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windowsでvenv

はじめに

Windowsにおける、venvのメモ
venv単位で仮想環境が作れるので便利
1週間で忘れてしまっていたので、メモ残し(特にポリシーのやつ)

構築&実行プロセス

venvの環境を構築

python -m venv envsample

※envsampleは好きな名前を
※上記で、envsampleのフォルダができる

ポリシーの設定

Set-ExecutionPolicy RemoteSigned -Scope Process

※このコマンドは毎回実行する必要がある(その代わり管理権限が不要)
※永続設定もある

環境下に入る

.\envsample\Scripts\activate

※Script以下のactivateを実行すればよい(確かbatファイル)
※docker exec ~ bash みたいなやつ

(envsample) PS C:\sample\python

※上みたいに先頭にカッコ書きになったら、仮想環境下にいる

環境外に出る

deactivate

※多分カッコが外れる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Level別 Pythonリスト内包表記 8選

リストの内包表記はとても便利な書き方ですが、他の言語からやってくる人にとって
少し理解しづらいものだと感じました。

なのでこれさえ押さえとけば、簡単な内包表記は書けちゃうよって物をまとめました。

環境

Python 3.7.8

そもそもリストの内包表記とは

リストを生成する簡潔な手段です。【公式:リストの内包表記

具体的にはこんな感じ。

sample = [1, 2, 3, 4]
new_sample = [ x * 2 for x in sample]
print(new_sample)

# [2, 4, 6, 8]

level 1 : 基本をおさえよう

リスト内包表記の基本はこれだ。

[ expression for item in iterable (if condition) ]

実にシンプル。

  • iterableオブジェクトをfor文でループ
  • expressionでitemデータを加工
  • オプションでifによる条件の絞り込み

level 2 : まずはfor文を置き替えて

full_name = "Yamada Taro"
name_list = []
for c in full_name:
    name_list.append(c)

print(full_name)
print(name_list)

# Yamada Taro
# ['Y', 'a', 'm', 'a', 'd', 'a', ' ', 'T', 'a', 'r', 'o']

上のfor文を内包表記で書き換えてみましょう。

full_name = "Yamada Taro"
characters = [char for char in full_name]

print(full_name)
print(characters)

# Yamada Taro
# ['Y', 'a', 'm', 'a', 'd', 'a', ' ', 'T', 'a', 'r', 'o']

2次元リストもitelableとして利用可能。

Matrix = [[7, 1, 5],
          [22, 99, 0],
          [33, 2, 57]]
row_min = [min(row) for row in Matrix]

# [1, 0, 2]

level 3 : optionのifでデータを絞ろう

if文を使えば、データを絞る事ができます。
SQLのWHERE句に該当するものです。

4文字より少なく、小文字のものだけに絞ります。

People = ["Taro", "Jiro", 'Wan', "Elepahnt", "Tomas", "ken", "sae"]
target = [name for name in People if len(name) < 4 and name.islower()]
print(target)

# ['ken', 'sae']

level 4 : expression でデータを加工しよう

expressionの所は、pythonの構文であれば何を書いても構いません。
nameを加工する例。

Genius = ["taro", "jiro", "yamada", "ito"]
target = [name.capitalize() for name in Genius]
print(target)

# ['Taro', 'Jiro', 'Yamada', 'Ito']

もちろん、expressionに if..else.. をいれる事も可能です。

People = ["Taro", "Hiroyuki", "ken", "youu"]
>>> answer = [name if name.startswith('y') else 'Not Genius' for name in People]
>>> print(answer)

# ['Not Genius', 'Not Genius', 'Not Genius', 'youu']

ここでの注意は、if...else... は三項演算子であり、Level 1 で紹介した式の最後のif とは異なるという事です。

[ expression for item in iterable (if condition) ]

この式でのifは抽出条件で、SQLのwhere句と同じものです。
else は入れられません。

もしexpressionに if を入れるなら、else を必ずいれないといけません。
それは、三項演算子だからです。

a = 1
b = 2 if a >0
# SyntaxError: invalid syntax

b = 2 if a > 0 else -1
print(b)
# 2

level 5 : ネストされたfor文もリストに入れよう

ネストされたループはどうでしょう。

Genius = ["taro", "jiro", "yamada"]
L = []
for name in Genius:
    for char in name:
        L.append(char)

print(L)
# ['t', 'a', 'r', 'o', 'j', 'i', 'r', 'o', 'y', 'a', 'm', 'a', 'd', 'a']

内包表記にすると、こんな感じになります。

Genius = ["taro", "jiro", "yamada"]
L = [char for name in Genius for char in name]
print(L)

# ['t', 'a', 'r', 'o', 'j', 'i', 'r', 'o', 'y', 'a', 'm', 'a', 'd', 'a']

ループは2つより多くなると分かりづらくなるので、2つまでが良いと思います。

level 6 : map, filterを置き換えよう

mapやfilterはfunctionを引数にする事ができますが、
内包表記で書いたほうが分かりやすくなります。

mapは次のように置き換えられます。

map
L = list(map(func, iterable))
# 置き換え可能
L = [func(a) for a in iterable]

filterは次のように置き換えられます。

filter
L = list(filter(condition_func, iterable))
# 置き換え可能
L = [a for a in iterable if condition]

実際に例を見てみましょう。

mapの例
People = ["Kenny", "Tomas", "tom", "young"]
L = map(lambda a: a.lower(), People)
print(list(L))
# ['kenny', 'tomas', 'tom', 'young']

L = [a.lower() for a in People]
print(L)
# ['kenny', 'tomas', 'tom', 'young']
filterの例
People = ["Kenny", "Tomas", "tom", "young"]
L1 = filter(lambda a: len(a) < 4, People)
print(list(L1))
# ['tom']

L2 = [a for a in People if len(a) < 4]
print(L2)
# ['tom']

level 7 : サイズが大きくなる場合は、generator式を使おう

大量の要素を含むものはジェネレーター式を検討しよう。
ジェネレーター式は実際必要になった時に生成されるので、メモリのコストを大幅に削減できます。
(でも、任意の要素にはアクセスできない。先頭から順番のアクセスのみ。)

# 100万の要素を生成
>>> large_list = [x for x in range(1_000_000)]
>>> large_list_g = (x for x in range(1_000_000))
>>> print(large_list.__sizeof__())
8697440
>>> print(large_list_g.__sizeof__())
96
>>>

level 8 : 複雑と感じるなら無理して使わない。

下記2つは同じリストを生成しますが、どちらが直感的に分かりやすいでしょうか。

内包表記
list_2d = [
     [0, 0, 0],
     [1, 1, 1],
     [2, 2, 2],
]
flat = [num ** 2 if num % 2 == 0 else num for row in list_2d for num in row]
print(flat)

# [0, 0, 0, 1, 1, 1, 4, 4, 4]
forループ
list_2d = [
     [0, 0, 0],
     [1, 1, 1],
     [2, 2, 2],
]
flat = []
for row in list_2d:
    for num in row:
        flat.append(num ** 2 if num % 2 == 0 else num)

print(flat)

# [0, 0, 0, 1, 1, 1, 4, 4, 4]

私はforループの方が直感的で分かりやすいと思いますが、人によって違うと思います。
この例では単純な2重ループでしたが、条件文がついたり、expressionでデータを加工したり、forループが増えたりして、複雑だと感じたら内包表記を使わない方が良いのではないでしょうか。

さいごに

内包表記は簡潔にリストの記述をできますが、他者が逆に理解しづらくなってしまっては本末転倒なので、チーム内の様子を見ながら利用するのが良いのではないでしょうか。

level1 の式を押さえておくのはお勧めです。

以上:tangerine:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

flaskにwebアプリをデプロイして躓いたこと

概要

いくつものエラーを解決して、ようやくflaskフレームワークによるWebアプリが完成しました。

内容はかなりシンプルかつジュニアレベルなもので
言ってしまえば単なるメモ帳のようなものです。
或いは自分しか存在しないtwitterのタイムラインといえば伝わりやすいでしょうか。
https://young-mesa-87008.herokuapp.com/

できること

・ユーザ登録
・メモの記録
・過去の記録を閲覧

実装してみた主な機能

パスワードはsalt・sha256関数によるパスワードのハッシュ化&認証
ログイン・ログアウトはsessionライブラリによるセッション管理を実装
herokuのDBアドオンによるClearDBサーバに対し、PyMysqlをドライバとして使用

開発で躓いたところ:cry:

その1:ログイン後、逐一セッションが切れる

ログインに成功して、ひとまず機能動作確認の為
ぐるぐるページ遷移したりガチャガチャ操作していると
突然 500 error が返されることがありました。

原因の究明

まず、heroku logs --tailでErrorの内容を見ると
デコレータによってsessionを確認した際、KeyErrorを起こしているようでした。

ERROR in app: Exception on /user/home [GET]
return super(SecureCookieSession, self).__getitem__(key)
KeyError: 'login'


そもそもの処理内容としましては
アプリ内で登録したユーザでログインを行った後、
メモを記載するページや過去の記録を閲覧するページに遷移できるのですが
その際、login_requiredデコレータで現在ログインしているユーザの情報をsessionから取得させるようにしていました。

app.py
# ログイン時に呼ばれる関数
@app.route('/login/try', methods=['POST'])
def login_try():
    name = request.form.get('name')
    pwd = request.form.get('pwd')
    result = check_user(name, str(pwd))
    if result:
        # ↓ ココで辞書型配列であるsessionに
        #   'login'をKey値としてnameを格納
        session['login'] = name

def login_required(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # ↓ ココで問い合わせ
        if not is_login():
            return redirect('/login')
        return func(*args, **kwargs)
    return wrapper

# セッション管理
def is_login():
    # 辞書型配列のsessionから loginをキーに持つ値を返す。 
    return 'login' in session

調査として、flaskのsession絡みのコード記事
app.secret_keyに関する質問記事を読むと、
sessionの確立にはapp.secret_keyをsetする必要があり
またkey値はアプリケーション内で同一のものを使用する必要がありました。

そこで自身のコードを見直してみると..

app.py
# ランダム文字列生成
def randomname(n):
    randlst = [random.choice(string.ascii_letters + string.digits) for i in range(n)]
    return ''.join(randlst)

# Flaskインスタンスと暗号化キーの指定
app = Flask(__name__)
app.secret_key = randomname(16)

上記のようにapp.secret_keyの値を関数によって算出し、
使用していることが原因のようでした。
アプリケーションを再起動するたびに、新しいキーが与えられるため、以前のキーは無効になります。
つまり、最初に定義したキーが無効になり、新しいsecret key の下でsessionが定義されるので、当然参照したsessionのKeyは空となり、KeyErrorが発生していたようです。

修正対応としては、
①iniファイルに機密情報(16文字のstring)を記載しておく
②iniファイルを読み込むインタラクティブなモジュール(ini.py)を用意
③ini.pyをメインモジュール(app.py)にimportして流用する
といった対応にしました。

app.py
import secret

app.secret_key = secret.ini_key
ini.py
import configparser

ini = configparser.ConfigParser()
ini.read('config.ini', encoding='UTF-8')
ini_key = ini['secret_key']['sec_key']
config.ini
[secret_key]
sec_key = XXXXXXXXXXXXXXXX

その2:複数のクエリを実行したい時、Pymysqlのconnentionが確立しない

これはselectやinsertのクエリをウォーターフォールで実行するテストを行った時に出会いました。以下のpyを呼び出すと、ins_queryの実行タイミングで
raise err.Error("Already closed")が返されてしまいます。

mysql.py
    # select
    def query(stmt, *args):
        try:
            with conn.cursor() as cursor:
                cursor.execute(stmt, (args))
                data = cursor.fetchall()
        finally:
            conn.close()
            cursor.close()
            return data

    # insert
    def ins_query(stmt, *args):
        try:
            with conn.cursor() as cursor:
                cursor.execute(stmt, (args))
                data = cursor.fetchall()
        finally:
            conn.commit()
            conn.close()
            cursor.close()
            return True

原因の究明

こちらの記事を参考にしました。
1つ目のquery()実行後、conn.close()を行い、
2つ目のins_query()実行時には、cursorを定義して接続しようとしていますが
一度閉じた接続を再接続するにはself.connnection.ping() を投げてやる必要があるようです。
以下のように、cursorによるDB接続試行前に、conn.ping()を書くことで
修正に至りました。

class MySQL:

    # select
    def query(stmt, *args):
        try:
       # ↓ここ
            conn.ping()
            with conn.cursor() as cursor:
                cursor.execute(stmt, (args))
                data = cursor.fetchall()
        finally:
            conn.close()
            cursor.close()
            return data

    # insert
    def ins_query(stmt, *args):
        try:
       # ↓ここ
            conn.ping()
            with conn.cursor() as cursor:
                cursor.execute(stmt, (args))
                data = cursor.fetchall()
        finally:
            conn.commit()
            conn.close()
            cursor.close()
            return True

その3:記録を投稿した時刻と、閲覧した時に返される時刻が9時間ずれている

例えば
投稿した時間→ 2/13 0:50の場合
表示される時間→2/12 15:50
9時間前に投稿したことになっていました。

現在時刻は以下のように取得しています。

app.py
# 今日日付取得
def get_today():
    d = datetime.datetime.now()
    today = (d.strftime('%Y-%m-%d'))
    return today

# 現在時刻取得
def get_time():
    t = datetime.datetime.now()
    time = (t.strftime('%H:%M:%S'))
    return time

原因の究明

恐らくタイムゾーンの設定ではないかと、ClearDBの設定値を疑いましたが
DBの設定値を直接変えることはできず、heroku上で変更が可能だそうです。
herokuのタイムゾーン設定変更

heroku config:add TZ=Asia/Tokyo --app [app-name]

終わりに

現在の業務が忙しく(言い訳)、なかなか勉強する時間が取れず(言い訳)
昨年6月頃から始めた学習を滞らせていましたが
ようやくサーバに自作アプリをアップロードさせることが出来、
一つ達成感を得ることが出来ました。

ただ、実装した機能は初歩中の初歩ですし、
まだまだ改善の余地があると思います。
それに今後商用アプリを作成するならばもっとセキュアにする必要もありますね。
今後もpythonやフロントサイドのoutputとして開発を積み上げていきたいです。

次回はAPI連携や機械学習を取り入れたサービスを創ろうと思います。
また、PHPやvue.jsといった言語も取り入れて、より動的なアプリを作ってみたいです。
それと、他のRuby on Rails, Django, Lavarelといったフレームワークも挑戦してみたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EmoPy Challenge

machine

  • 64bit ubuntu 18.04LTS
  • VMWare Workstation 15 Player

installing pyenv(2021/2/11)

sudo apt update
sudo apt upgrade
  • installing required libs and then you must reboot your machine.
sudo apt install -y build-essential libffi-dev libssl-dev zlib1g-dev liblzma-dev libbz2-dev libreadline-dev libsqlite3-dev git
  • install package of pyenv
git clone https://github.com/pyenv/pyenv.git ~/.pyenv`
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc`
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc`
echo 'eval "$(pyenv init -)"' >> ~/.bashrc`

source ~/.bashrc`
  • check the version of installed pyenv.
pyenv -v
  • install python into virtual environment.
pyenv install 3.6.10

set up for EmoPy

  • install required lib.
sudo apt-get install graphviz`
sudo pip3 install virtualenv`
sudo apt-get install python3-venv`
  • Create and activate the virtual environment. Run:
pyenv exec python3.6 -m venv venv
  • activate virtual environment.
source venv/bin/activate

installing EmoPy

  • install via pip or from git
# in case of pip

sudo pip3 install -U pip
sudo pip3 install EmoPy

# in case of git

git clone https://github.com/thoughtworksarts/EmoPy.git
cd EmoPy/
sudo pip3 install -r requirements.txt

run programs

  • run run_all.py as a test
sudo python3 EmoPy/tests/run_all.py

prog for webcam.

cd EmoPy/EmoPy/examples/
sudo python3 fermodel_example_webcam.py
  • you may need to unintall h5py and install other-version
sudo pip3 uninstall h5py
sudo pip3 install h5py==2.10.0
  • output is shown.
Capturing image ...
Image written to:  image_data/image.jpg
Initializing FER model parameters for target emotions: ['calm', 'anger', 'happiness']
WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/op_def_library.py:263: colocate_with (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.
Instructions for updating:
Colocations handled automatically by placer.
2021-02-12 20:40:55.266811: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2021-02-12 20:40:55.330783: I tensorflow/core/platform/profile_utils/cpu_utils.cc:94] CPU Frequency: 2111995000 Hz
2021-02-12 20:40:55.335114: I tensorflow/compiler/xla/service/service.cc:150] XLA service 0x1f22ed0 executing computations on platform Host. Devices:
2021-02-12 20:40:55.335376: I tensorflow/compiler/xla/service/service.cc:158]   StreamExecutor device (0): <undefined>, <undefined>
WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/python/ops/math_ops.py:3066: to_int32 (from tensorflow.python.ops.math_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.cast instead.
anger: 62.6%
calm: 28.7%
happiness: 8.8%
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

畳み込みニューラルネットワーク(CNN)の実装

はじめに

今回は、「畳み込みニューラルネットワーク」を実装していきます。

ディープラーニングや畳み込みニューラルネットワークの基本については、下記の記事を参照してください。

?基本となるディープラーニングについて
https://qiita.com/hara_tatsu/items/c0e59b388823769f9704

?ディープラーニングの実装及び過学習対策
https://qiita.com/hara_tatsu/items/b7423e90574cf7730978

?「畳み込みニューラルネットワーク(CNN)まとめ」
https://qiita.com/hara_tatsu/items/8dcd0a339ad2f67932e7

畳み込みニューラルネットワークを実装する

今回は題材として、「【SIGNATE】画像ラベリング(10種類)」を使います。
https://signate.jp/competitions/133

画像データに映っているものを10種類のラベルから分類するものです。

データの前処理

必要なライブラリーをインポート

python.py
import numpy as np
import pandas as pd
from PIL import Image
import glob

目的変数と説明変数の処理

目的変数を読み込んでカテゴリー変数へ変換

python.py
train_Y = pd.read_csv('train_master.tsv', delimiter='\t')
train_Y = train_Y.drop('file_name', axis=1)
# カテゴリー変数へ変換
from tensorflow.keras.utils import to_categorical
Y = to_categorical(train_Y)

print(Y.shape)
print(Y[:5])

(5000, 10)
array([[0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
       [0., 0., 0., 1., 0., 0., 0., 0., 0., 0.]], dtype=float32)

説明変数(画像データ)は、「glob」を使って読み込めるが画像データの並びがバラバラに読み込まれてしまう。そのため、読み込み後に小さい数字の順番に並び替える。

python.py
train_file = glob.glob('train_images/t*')

# 0埋めでない数値を小さい順に並び替える関数
import re
from collections import OrderedDict

def sortedStringList(array=[]):
    sortDict=OrderedDict()
    for splitList in array:
        sortDict.update({splitList:[int(x) for x in re.split("(\d+)",splitList)if bool(re.match("\d*",x).group())]})
    return [sortObjKey for sortObjKey,sortObjValue in sorted(sortDict.items(), key=lambda x:x[1])]

# 小さい順に並び替え
sort_file = sortedStringList(train_file)
print(sort_file[:5])

['train_images/train_0.jpg',
 'train_images/train_1.jpg',
 'train_images/train_2.jpg',
 'train_images/train_3.jpg',
 'train_images/train_4.jpg']

説明変数(画像データ)の前処理

現在は、「glob」で画像データのファイル名を取得している状態。
PIL形式で読み込み後にnumpy化及び正規化をする。

※画像データは「0〜255」までの数値の集まり。そのため、全体を「255.0」で割ることで、「0〜1」までの数値の集まりに変換する。
(✅ディープラーニングは、データの数値の幅が大きいと学習がうまくいかない)

python.py
from tensorflow.keras.preprocessing.image import load_img, img_to_array, array_to_img

# まずは、1枚で試してみる

train = sort_file[0]
# 画像ファイルからPIL形式で読み込み
train = load_img(train)
# PIL形式からnumpy形式へ + 正規化
train = img_to_array(train) / 255.0
print(train.shape)

(96, 96, 3)

# 画像表示
plt.imshow(train)

スクリーンショット 2021-02-07 9.25.24.png

全ての画像を一括で処理。

※※CPU環境では学習に時間がかかる場合があるため、「image = load_img(image, target_size = (32, 32)) 」とすれば画像サイズを小さくできるので計算時間が減る。(予測精度は悪くなる可能性あり)

python.py
X = []

for image in sort_file:
    # 画像ファイルのPIL形式で読み込み
    image = load_img(image) 
    #image = load_img(image, target_size = (32, 32)) 学習時間を短縮したい場合
    # PIL形式からnumpy形式へ + 正規化
    image = img_to_array(image) / 255.0
    # データをリストへ格納
    X.append(image)

#Xはリスト型のためnumpy型へ変換
X_np = np.array(X)
print(X_np.shape)
print(X_np.dtype)
print(X_np[0])

(5000, 96, 96, 3)
float32
[[[0.57254905 0.5529412  0.42745098]
  [0.5764706  0.5568628  0.43137255]
  [0.5803922  0.56078434 0.43529412]
  [0.6509804  0.627451   0.49411765]
  [0.64705884 0.62352943 0.49019608]
 ...
  [0.6431373  0.61960787 0.4862745 ]]

 [[0.57254905 0.5529412  0.42745098]
  [0.57254905 0.5529412  0.42745098]
  [0.5764706  0.5568628  0.43137255]
 ...
  [0.5568628  0.4862745  0.43137255]]]

学習用データ、検証用データ、テストデータへ分割

python.py
# データの分割
from sklearn.model_selection import train_test_split

X_train, X_test, Y_train, Y_test = train_test_split(X_np, Y, test_size=0.2, random_state=0)
X_train, X_valid, Y_train, Y_valid = train_test_split(X_train, Y_train, test_size=0.2, random_state=0)

# 形状を確認
print("Y_train=", Y_train.shape, ", X_train=", X_train.shape)
print("Y_valid=", Y_valid.shape, ", X_valid=", X_valid.shape)
print("Y_test=", Y_test.shape, ", X_test=", X_test.shape)

Y_train= (3200, 10) , X_train= (3200, 96, 96, 3)
Y_valid= (800, 10) , X_valid= (800, 96, 96, 3)
Y_test= (1000, 10) , X_test= (1000, 96, 96, 3)

畳み込みニューラルネットワークモデルの構築

最初は単純なモデルで精度を確認する。

python.py
from tensorflow import keras
from tensorflow.keras.layers import Dense, Activation, Conv2D, Flatten, MaxPooling2D

# モデルの初期化
model = keras.Sequential()

# 入力層and畳み込み層
model.add(Conv2D(32, # フィルター数
                 kernel_size= 3, # フィルターのサイズ(3×3)
                 input_shape=(96, 96, 3,), # 入力データ(画像)のサイズ
                 padding="same", #ゼロパディング(画像サイズを小さくしない)
                 activation="relu" #活性化関数
                ))

# プーリング層
model.add(MaxPooling2D())

# 1次元に変換
model.add(Flatten())

# 全結合層
model.add(Dense(128, activation="relu")) 

# 出力層
model.add(Dense(10, activation='softmax'))

# モデルの構築
model.compile(optimizer = "rmsprop", #誤差逆伝播
              loss='categorical_crossentropy', #損失関数
              metrics=['accuracy'] #訓練時に監視する指標
             )

# モデルの表示
model.summary()

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_12 (Conv2D)           (None, 96, 96, 32)        896       
_________________________________________________________________
max_pooling2d_12 (MaxPooling (None, 48, 48, 32)        0         
_________________________________________________________________
flatten_8 (Flatten)          (None, 73728)             0         
_________________________________________________________________
dense_17 (Dense)             (None, 128)               9437312   
_________________________________________________________________
dense_18 (Dense)             (None, 10)                1290      
=================================================================
Total params: 9,439,498
Trainable params: 9,439,498
Non-trainable params: 0
_________________________________________________________________
python.py
# EarlyStopping用
from tensorflow.keras import regularizers

%%time
# 学習の実施
log = model.fit(X_train, Y_train, #学習用データ
                epochs=5000, #繰り返し計算する回数
                batch_size=64, #ミニバッチ勾配降下法で分割するデータのかたまり
                verbose=True, #計算過程を表示
                #過学習していると判断したら学習を止める
                callbacks=[keras.callbacks.EarlyStopping(monitor='val_loss',#監視する値
                                                         min_delta=0, #改善とみなされる最小の量
                                                         patience=30, #設定したエポック数改善がないと終了
                                                         verbose=1,
                                                         mode='auto' #監視する値が増減どうなったら終了か自動で推定
                                                        )],
                validation_data=(X_valid, Y_valid) #検証用データ
               )

結果の確認①

python.py
# テスト用データ(Y_test)をカテゴリー変数から通常の数値へ復元
Y_test_ = np.argmax(Y_test, axis=1)

# モデルでの予測
Y_pred = model.predict_classes(X_test)

# モデルの評価
from sklearn.metrics import classification_report

print(classification_report(Y_test_, Y_pred))

precision    recall  f1-score   support

           0       0.63      0.80      0.70        93
           1       0.47      0.44      0.45       100
           2       0.72      0.66      0.69        99
           3       0.32      0.34      0.33       103
           4       0.46      0.50      0.48       117
           5       0.23      0.20      0.22        93
           6       0.46      0.52      0.49       101
           7       0.56      0.36      0.44       110
           8       0.62      0.68      0.65        88
           9       0.55      0.54      0.55        96

    accuracy                           0.50      1000
   macro avg       0.50      0.50      0.50      1000
weighted avg       0.50      0.50      0.50      1000

正解率50%!!

python.py
acc = log.history['accuracy']
val_acc = log.history['val_accuracy']

loss = log.history['loss']
val_loss = log.history['val_loss']

epochs_range = range(37)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Loss')
plt.show()

スクリーンショット 2021-02-07 9.51.50.png

予測精度は低く、早い段階で過学習をしている。

原因として考えられるのは、

✅学習用データが少ないこと。

そこで学習用データの拡張と水増しをしてみる。

学習用データの拡張と水増し

✅学習用データの拡張と水増しが必要な理由

・対象の物体が逆さまや斜めになったデータも学習させることで、応用力をつけさせるため(データの拡張)
・必要な量の学習データが準備できていない(データの水増し)

python.py
from tensorflow.keras.preprocessing.image import ImageDataGenerator

# 学習用データの拡張設定
image_gen = ImageDataGenerator(rotation_range=45, #45°回転
                               horizontal_flip = True, #左右反転
                              )  

# 拡張データの生成
train_data_gen = image_gen.flow(X_train, Y_train, batch_size = 32, shuffle = False)

# 拡張データを表示する関数
def plotImages(images_arr):
    fig, axes = plt.subplots(1, 5, figsize=(20,20))
    axes = axes.flatten()
    for img, ax in zip( images_arr, axes):
        ax.imshow(img)
        ax.axis('off')
    plt.tight_layout()
    plt.show()

augmented_images = [train_data_gen[0][0][0] for i in range(5)]
plotImages(augmented_images)                   

スクリーンショット 2021-02-07 13.54.22.png

python.py
#学習用データの状態確認
#3200 / 32
print(len(train_data_gen))

100

print(type(train_data_gen))

tensorflow.python.keras.preprocessing.image.NumpyArrayIterator

検証用データを学習用データの状態に合わせる

python.py
valid_gen = ImageDataGenerator()
valid_data_gen = valid_gen.flow(X_valid, Y_valid, batch_size = 32)
#800 / 32
print(len(valid_data_gen))

25

print(type(valid_data_gen))

tensorflow.python.keras.preprocessing.image.NumpyArrayIterator

学習用データを拡張して学習

python.py
%%time
# 学習の実施
log = model.fit_generator(train_data_gen, #学習用データ
                          steps_per_epoch = 100,
                          epochs = 5000, #繰り返し計算する回数
                          callbacks = [keras.callbacks.EarlyStopping(monitor='val_loss',#監視する値
                                       min_delta=0, #改善とみなされる最小の量
                                       patience=30, #設定したエポック数改善がないと終了
                                       verbose=1,
                                       mode='auto' #監視する値が増減どうなったら終了か自動で推定
                                       )],
                          validation_data = valid_data_gen, #検証用データ
                          validation_steps = 25)

結果の確認②

python.py
# 予測
Y_pred = model.predict_classes(X_test)

# モデルの評価
print(classification_report(Y_test_, Y_pred))

precision    recall  f1-score   support

           0       0.62      0.86      0.72        93
           1       0.38      0.70      0.49       100
           2       0.70      0.79      0.74        99
           3       0.47      0.19      0.27       103
           4       0.74      0.37      0.49       117
           5       0.39      0.39      0.39        93
           6       0.51      0.56      0.54       101
           7       0.49      0.54      0.51       110
           8       0.72      0.66      0.69        88
           9       0.61      0.43      0.50        96

    accuracy                           0.54      1000
   macro avg       0.56      0.55      0.53      1000
weighted avg       0.56      0.54      0.53      1000

正解率54%!!

python.py
acc = log.history['accuracy']
val_acc = log.history['val_accuracy']

loss = log.history['loss']
val_loss = log.history['val_loss']

epochs_range = range(69)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Loss')
plt.show()

スクリーンショット 2021-02-07 13.35.27.png

正解率は上昇したもの、まだまだ過学習しています。
最後に、過学習対策を施したモデルを構築します。

過学習対策をしたモデルを構築

python.py
from tensorflow.keras.layers import Dropout

# モデルの初期化
model = keras.Sequential()

# 入力層
model.add(Conv2D(
    32, kernel_size=3,
    input_shape=(96, 96, 3,),
    padding="same",
    activation="relu",
    ))
# プーリング層
model.add(MaxPooling2D())
model.add(Dropout(0.25))

# 層のユニットの繰り返し
model.add(Conv2D(64, kernel_size=3, padding="same", activation="relu"))
model.add(MaxPooling2D())
model.add(Dropout(0.25))

# 1次元に変換
model.add(Flatten())
model.add(Dense(128, activation="relu")) 
model.add(Dense(64, activation="relu")) 
model.add(Dropout(0.5))

# 出力層
model.add(Dense(10, activation='softmax'))

# モデルの構築
model.compile(optimizer = "rmsprop", loss='categorical_crossentropy', metrics=['accuracy'])

model.summary()

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_4 (Conv2D)            (None, 96, 96, 32)        896       
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 48, 48, 32)        0         
_________________________________________________________________
dropout_3 (Dropout)          (None, 48, 48, 32)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 48, 48, 64)        18496     
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 24, 24, 64)        0         
_________________________________________________________________
dropout_4 (Dropout)          (None, 24, 24, 64)        0         
_________________________________________________________________
flatten_3 (Flatten)          (None, 36864)             0         
_________________________________________________________________
dense_7 (Dense)              (None, 128)               4718720   
_________________________________________________________________
dense_8 (Dense)              (None, 64)                8256      
_________________________________________________________________
dropout_5 (Dropout)          (None, 64)                0         
_________________________________________________________________
dense_9 (Dense)              (None, 10)                650       
=================================================================
Total params: 4,747,018
Trainable params: 4,747,018
Non-trainable params: 0
_________________________________________________________________

python.py
%%time
# 学習の実施
log = model.fit_generator(train_data_gen, #学習用データ
                          steps_per_epoch = 100,
                          epochs = 5000, #繰り返し計算する回数
                          callbacks = [keras.callbacks.EarlyStopping(monitor='val_loss',#監視する値
                                       min_delta=0, #改善とみなされる最小の量
                                       patience=30, #設定したエポック数改善がないと終了
                                       verbose=1,
                                       mode='auto' #監視する値が増減どうなったら終了か自動で推定
                                       )],
                          validation_data = valid_data_gen, #検証用データ
                          validation_steps = 25)

結果の確認③

python.py
# 予測
Y_pred = model.predict_classes(X_test)


# モデルの評価
print(classification_report(Y_test_, Y_pred))

precision    recall  f1-score   support

           0       0.73      0.77      0.75        93
           1       0.45      0.59      0.51       100
           2       0.87      0.63      0.73        99
           3       0.49      0.42      0.45       103
           4       0.57      0.59      0.58       117
           5       0.38      0.23      0.28        93
           6       0.54      0.75      0.63       101
           7       0.64      0.42      0.51       110
           8       0.69      0.76      0.72        88
           9       0.56      0.73      0.63        96

    accuracy                           0.58      1000
   macro avg       0.59      0.59      0.58      1000
weighted avg       0.59      0.58      0.58      1000

正解率58%!!

python.py
acc = log.history['accuracy']
val_acc = log.history['val_accuracy']

loss = log.history['loss']
val_loss = log.history['val_loss']

epochs_range = range(69)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Loss')
plt.show()

スクリーンショット 2021-02-07 13.42.27.png

おわりに

正解率はあまり向上しなかったが、いい感じに学習が進んでいるグラフになっている。

さらなる精度向上を目指すなら、
・モデルの構築をより複雑にする
・転移学習
・学習用データをもっと集める

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

三菱UFJ銀行サイトにPythonとSeleniumを使ってログインする

三菱UFJ銀行のサイトにPythonとSelenium使ってログインするサンプルコードを書いてみました。

※2021/02/13時点の情報です。

ポイントは以下の通りです。

  • ログイン情報を、設定ファイルから取得
  • SeleniumuでID、パスワードを入力
  • 三菱UFJ銀行の場合、formはsubmitではないのでbutton.click()でログインする

環境

  • Windows 10
  • Python 3.8
  • Selenium 3.141.0

サンプルコード

bank_ufj.py
#
# 三菱UFJ銀行サイトへログイン
#

import json
from selenium.webdriver import Firefox, FirefoxOptions
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait

# 設定ファイルからログイン情報を取得
login_info = json.load(open("login_info.json", "r", encoding="utf-8"))

# ログインサイト名
site_name = "bank_ufj"

# ログイン画面URL
url_login = login_info[site_name]["url"]

# ユーザー名とパスワードの指定
USER = login_info[site_name]["id"]
PASS = login_info[site_name]["pass"]

# Firefoxのヘッドレスモードを有効にする
options = FirefoxOptions()
options.add_argument('--headless')

# Firefoxを起動する
browser = Firefox(options=options)

# ログイン画面取得
browser.get(url_login)

# 入力
e = browser.find_element_by_id("tx-contract-number")
e.clear()
e.send_keys(USER)
e = browser.find_element_by_id("tx-ib-password")
e.clear()
e.send_keys(PASS)

# ログイン実行
button = browser.find_element_by_class_name("gonext")
button.click()

# ページロード完了まで待機
WebDriverWait(browser, 10).until(
    EC.presence_of_element_located((By.ID, "md-allmenu"))
)

# ログインできたか確認(画面キャプチャ取る)
browser.save_screenshot("home.png")

browser.quit()

ログイン情報設定ファイル

サイトURLやログイン情報(IDやパスワード)は、JSON形式のファイルに保持するようにしています。以下のサンプルでは三菱UFJ銀行と楽天証券の2サイトの情報と設定しています。

"id"と"pass"の値を書き換えればそのまま使えるはずです。

なお、このファイルだけは絶対に漏洩しないよう、管理方法に注意しましょう!

login_info.json
{
  "bank_ufj": {
    "id": "1234567890",
    "pass": "password",
    "url": "https://entry11.bk.mufg.jp/ibg/dfw/APLIN/loginib/login?_TRANID=AA000_001"
  },
  "sec_rakuten": {
    "id": "1234567890",
    "pass": "password",
    "url": "https://www.rakuten-sec.co.jp/ITS/V_ACT_Login.html"
  }
}

実行後

Seleniumで動かしていると実際にログインできているのか、画面遷移できているのかが視覚的にわかりにくいですよね。

そこで動作確認のために画面キャプチャを保存しておくとよいでしょう。

# ログインできたか確認(画面キャプチャ取る)
browser.save_screenshot("home.png")

一部ぼかしを入れていますが実際に保存された画像がこちらです。

biz_home.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS覚書 - boto3を使ってIoT coreのエンドポイントを取得する -

IoT coreを使用するのに、クライアントからカスタムエンドポイントを取得する必要があるので、取得できるようになるまでの手順を覚書しておく。

必要なもののインストール

pythonへboto3と、AWSクライアント

boto3

pip3 install boto3

AWSクライアント

https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv2-mac.html

キーセットの取得

まずはアクセスキーとシークレットーキーが必要

アカウントの IAM → アクセス管理 → ユーザー → アクセスキー と進む。
スクリーンショット 2021-02-13 12.21.02.png
アクセスキーを作成してシークレットキーを取得する(一度しか取得できないので紛失に注意する)。

AWSクライアントへのキーセットの登録

> aws configure --profile <プロファイル名(自由な名前でOK)>
AWS Access Key ID [None]: <取得したアクセスキー>
AWS Secret Access Key [None]: <取得したシークレットキー>
Default region name [None]: <デフォルトで使用したいリージョン、東京ならば「ap-northeast-1」>
Default output format [None]: json

プロファイルを作成したらプロファイルをデフォルト設定する

> set AWS_DEFAULT_PROFILE= <先ほど作成したプロファイル名>

ちゃんとキーセットが設定されているか確認する。

> aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************3LHQ shared-credentials-file    
secret_key     ****************vexf shared-credentials-file    
    region                us-east-2      config-file    ~/.aws/config

以下のファイルに、それっぽいのがあるか確認できる。

> cat ~/.aws/credentials

IoT coreのエンドポイントを確認

Iot core → 設定 と進むとみれる。
スクリーンショット 2021-02-13 12.40.04.png

クライアントからエンドポイントの取得

pythonから取得する。

> python
Python 3.8.2 (default, Apr 27 2020, 13:03:04) 
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import boto3
>>> client = boto3.client('iot', region_name='ap-northeast-1')
>>> endpoint_response = client.describe_endpoint(endpointType='iot:Data-ATS')
>>> print(endpoint_response)
{'ResponseMetadata': {'RequestId': '**************', 'HTTPStatusCode': 200, 'HTTPHeaders': {'date': 'Sat, 13 Feb 2021 03:42:30 GMT', 'content-type': 'application/json', 'content-length': '74', 'connection': 'keep-alive', 'x-amzn-requestid': '****************', 'access-control-allow-origin': '*', 'x-amz-apigw-id': '********', 'x-amzn-trace-id': 'Root=****************'}, 'RetryAttempts': 0}, 'endpointAddress': '<ここが実際に取得したいエンドポイントになる>'}

整形

{
    "ResponseMetadata": {
        "RequestId": "**************",
        "HTTPStatusCode": 200,
        "HTTPHeaders": {
            "date": "Sat, 13 Feb 2021 03:42:30 GMT",
            "content-type": "application/json",
            "content-length": "74",
            "connection": "keep-alive",
            "x-amzn-requestid": "****************",
            "access-control-allow-origin": "*",
            "x-amz-apigw-id": "********",
            "x-amzn-trace-id": "Root=****************"
        },
        "RetryAttempts": 0
    },
    "endpointAddress": "<ここが実際に取得したいエンドポイントになる>"
}

画面でみたエンドポイントと同じであるか確認する。

boto3.client の部分は、キーセット直接指定でも問題ない。

>>> client = boto3.client('iot', region_name='ap-northeast-1', aws_access_key_id='***', aws_secret_access_key='***')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonを複数バージョン管理してみる

概要

Pythonを使ったプログラミングをする際、Pythonの何バージョンを使うかで揉めることがある。毎回使用するバージョンに合わせて環境を入れ替えるのが面倒なので、Python仮想環境を使用して必要がバージョンに切り替えられる快活環境を作る。

Python_VE.png

実行環境

  • OS: Ubuntu 18.04 LTS
  • Python: 3.6.9

Python仮想環境ツール

conda Anacondaに同封されているツール
pyenv オープンソースで開発しているバージョン管理ツール

condaセットアップ

condaはAnacondaに同封されたツールなのでAnacondaをインストールします
参考:Anaconda Installing on Linux

Anacondaインストーラをダウンロード

Anaconda Individual Editionからインストーラをダウンロードする

downlaod_link.jpg

インストーラを実行する

# ダウンロードしたインストーラに実行権限を与える
$ chmod +x Anaconda3-2020.11-Linux-x86_64.sh
# インストールを実行する
$ ./Anaconda3-2020.11-Linux-x86_64.sh

インストーラ実行時の操作は以下

Welcome to Anaconda3 2020.11

In order to continue the installation process, please review the license
agreement.
Please, press ENTER to continue
>>> Enterを入力
.
.
Do you accept the license terms? [yes|no]
[no] >>> 
Please answer 'yes' or 'no':
>>> yes
.
.
Anaconda3 will now be installed into this location:
/home/$USER/anaconda3

  - Press ENTER to confirm the location
  - Press CTRL-C to abort the installation
  - Or specify a different location below

[/home/$USER/anaconda3] >>> Enterを入力
.
.
Do you wish the installer to initialize Anaconda3
by running conda init? [yes|no]
[no] >>> yes

condaチートシート

# 名前とPythonバージョンを指定して仮想環境を作る
# ex) conda create --name py36 python=3.6
$ conda create --name <name> python=<verson>

# 準備されている仮想環境リストを表示する
$ conda info --envs
# conda environments:
#
base                  *  /home/dai_guard/anaconda3
py36                     /home/dai_guard/anaconda3/envs/py36

# 仮想環境を有効にする ※デフォルトのbase環境になる
$ conda activate

# 名前を指定して仮想環境を有効にする
$ conda activate <name>

# 仮想環境を無効にする
$ conda diactivate


pyenvセットアップ

GitHubで公開されているオープンソースなツール

pyenvをクローン&ビルドする

# リポジトリからクローンする
$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv
# ビルドする
$ cd ~/.pyenv
$ src/configure
$ make -C src

環境変数を設定する

# pyenvのフォルダを設定する
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
# PATHにpyenvフォルダを追加する
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
# 自動でpyenvの仮想環境を起動する設定
echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n  eval "$(pyenv init -)"\nfi' >> ~/.bashrc

pyenvチートシート

# 現在インストールされているPythonバージョン
$ pyenv versions
* system (set by PYENV_VERSION environment variable)
  3.8.7
# イントールできるPythonバージョン
$ pyenv install --list
# バージョンを指定してPythonをインストールする
# ex) pyenv install 3.8.7
$ pyenv install <version>
# Pythonバージョンを切り替える
# ex) pyenv shell 3.8.7
$ pyenv shell <version>

使ってみて

conda

  • メリット

    • インストールが簡単
    • Anacondaでメンテナンスしてるので信頼できる
  • デメリット

    • Anacondaに同封なのでライセンス対応のため会社などでは使えない可能性がある
    • 力補完が効かないのでコマンドを覚えないといけない

pyenv

  • メリット

    • 入力補完が使えるので簡単
    • venvと連動してフォルダごとに個別の環境を作れる
  • デメリット

    • デフォルトだと現在使用中の仮想環境が表示されない
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EmoPy Challenge

machine

  • 64bit Ubuntu18.04 LTS
  • VMWare Workstation 15 Player

installing pyenv(2021/2/11)

    sudo apt update
    sudo apt upgrade
  • installing required libs. then you must reboot your machine.
    sudo apt install -y build-essential libffi-dev libssl-dev zlib1g-dev liblzma-dev libbz2-dev libreadline-dev libsqlite3-dev git
  • install package of the pyenv
    git clone https://github.com/pyenv/pyenv.git ~/.pyenv
    echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
    echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
    echo 'eval "$(pyenv init -)"' >> ~/.bashrc
    source ~/.bashrc
  • check the version of the installed pyenv.

    pyenv -v
    
  • install python into virtual environment.

    pyenv install <version>
    (example) pyenv install 3.6.10
    

set up for EmoPy

  • install required lib.

    sudo apt-get install graphviz
    sudo pip3 install virtualenv
    sudo apt-get install python3-venv
    
  • Create and activate the virtual environment. Run:

    pyenv exec python3.6 -m venv venv
    
  • activate virtual environment.

    source venv/bin/activate
    

installing EmoPy

  • update pip and install EmoPy from PyPi or git.
  • it takes a long time...
    sudo pip3 install -U pip
    # PyPi
    sudo pip3 install EmoPy
    # git
    git clone https://github.com/thoughtworksarts/EmoPy.git
    cd EmoPy
    sudo pip3 install -r requirements.txt

run programs.

  • run run_all.py as a test

    python3 EmoPy/tests/run_all.py
    
  • prog for webcam(under constructing).

cd examples/
python3 fermodel_example_webcam.py
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonを組み込み関数でデバッグする方法(超個人的メモ)

pythonには、組み込みのデバッガがある。

breakpoint()と呼び出すだけ。

foo = "foo"
breakpoint()

お手軽すぎる。
python最高

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

量子コンピュータによる機械学習に載っている機械学習手法を実装してみました

目次

  1. はじめに
  2. ベイジアンネットワーク
  3. 隠れマルコフモデル
  4. カーネル密度推定
  5. k近傍法
  6. サポートベクターマシーン
  7. まとめ

はじめに

機械学習の勉強を始めようと参考書を開くと様々な手法が次々と紹介されていて,少々困惑してしまいました.そこでそれぞれの手法を実際に実装しながら調べたりすることで理解を深めようと思いました.正直pythonのライブラリを使った実装なので細かい理論までは追えていませんがそこは追々ということにしておきます.

ベイジアンネットワーク

ベイジアンネットワークとは条件つき独立性を仮定した確率モデルであり,$p(s)$の扱いを平易にする.

細かい理論については置いておいて,この記事では実装することを目標にpgmpyを用いて実装していきます.

問題設定

上記のような問題を考えます.
緑の表での$R=0$は雨が降らない確率を,$R=1$は雨が降る確率を表しています.
青の表ではスプリンクラーが作動しない確率$S=0$と作動する確率$S=1$を$R=0$の時,$R=1$の時で別々で与えたいます.黄色の表に関しても同様です.

ベイジアンネットワークの実装

では実際に上記の図をベイジアンネットワークで作成していきます.

Bayesian.py
# ライブラリのimport
from pgmpy.factors.discrete import TabularCPD
from pgmpy.models import BayesianModel
from pgmpy.inference import VariableElimination


## ベイジアンネットワークの構造
model = BayesianModel([
    # ('親ノード', '子ノード')
    ('rain', 'sprinkler'),
    ('rain', 'grass'),
    ('sprinkler', 'grass'),
])

rain_cpd = TabularCPD(
    variable='rain',
    variable_card=2,
    values=[[.8], [.2]])

sprinkler_cpd = TabularCPD(
    variable='sprinkler',
    variable_card=2,
    values=[[.6, 0.99], [.4, 0.01]],
    evidence=['rain'],
    evidence_card=[2])

grass_cpd = TabularCPD(
    variable='grass',
    variable_card=2,
    values=[[1.0, 0.1, 0.2, 0.01], [0.0, 0.9, 0.8, 0.99]],
    evidence=['rain', 'sprinkler'],
    evidence_card=[2, 2])

model.add_cpds(rain_cpd, sprinkler_cpd, grass_cpd)

ベイジアンネットワークで推論

実装したベイジアンネットワークを使って推論を行ってみます.
ここでは芝生が濡れていないときの雨が降っていない確率と降っている確率を求めます.

model_infer = VariableElimination(model)
rain0_given_grass1 = model_infer.query(variables=['rain'], evidence={'grass': 0})
print(rain0_sprinkler1_given_grass1)

結果は

+---------+-------------+
| rain    |   phi(rain) |
+=========+=============+
| rain(0) |      0.9282 |
+---------+-------------+
| rain(1) |      0.0718 |
+---------+-------------+

となりました.

ここではベイジアンネットワークを作成し,推論を行いましたが,

実際にベイジアンネットワークを推定することは,一般的に難しい問題とされている.

隠れマルコフモデル

隠れマルコフモデルは,「連鎖したイベント」のシーケンスを表すという点でベイジアンネットワークと性質が多少異なるグラフィカルモデルである.

こちらも細かい理論については置いておいて,この記事では実装することを目標にhmmlearnを用いて実装していきます.

モデルの構築

隠れマルコフモデルには三つのパラメータがあります.

  1. 初期状態確率$\pi$
  2. 遷移確率$A$
  3. 出力確率$B$

これらのパラメータは学習で推定することができるが,明示的に指定してモデルを構築することもできる.

hmm1.py
from hmmlearn import hmm
import numpy as np


model = hmm.GaussianHMM(n_components=5, covariance_type='full')

# 初期状態確率π(q_iが初期状態である確率)
model.startprob_ = np.array([0.1, 0.1, 0.7, 0.0, 0.1])

# 状態遷移確率
model.transmat_ = np.array([[0.9, 0.1, 0.0, 0.0, 0.0],
                           [0.4, 0.3, 0.3, 0.0, 0.0],
                           [0.1, 0.2, 0.4, 0.3, 0.0],
                           [0.0, 0.1, 0.3, 0.3, 0.3],
                           [0.0, 0.0, 0.3, 0.1, 0.6]])

# 出力確率の平均と共分散(正規分布に従うと仮定している)
model.means_ = np.array([[1.0, 1.0],
                         [2.0, 2.0],
                         [3.0, 3.0],
                         [4.0, 4.0],
                         [5.0, 5.0]])

model.covars_ = 0.1 * np.tile(np.identity(2), (5, 1, 1))

観測系列のサンプルを得ます.

X, Z = model.sample(10)

# (観測系列,状態系列)
print(X)
print(Z)
[[2.71618377 2.78799886]
 [0.68544061 0.98482839]
 [1.00413618 0.67011749]
 [1.48194136 0.40049633]
 [0.99204692 0.56744503]
 [1.22996837 1.54221408]
 [0.96063165 0.7236326 ]
 [1.09050931 1.02022522]
 [0.62496655 1.79905732]
 [2.16068752 2.01486085]]
[2 0 0 0 0 0 0 0 0 1]

モデルの推定

未知のモデルから観測結果$X$が得られたときのモデルのパラメータ推定を行います.
今回は先ほど得られたXを用いてモデルを推定するので先ほど設定したパラメータと近い結果が出ればよしとなります.

# 未知のモデルから観測結果xが得られたときのモデルのパラメータ推定
remodel = hmm.GaussianHMM(n_components=5, covariance_type='full', n_iter=100)

# 10個じゃ足りないので
X, Z = model.sample(100000)
remodel.fit(X)
print(remodel.transmat_)
print(remodel.means_)
[[8.62112104e-001 1.40414272e-043 4.53692486e-002 9.25186472e-002 4.32192981e-210]
 [9.47989464e-002 3.86113456e-001 1.21180890e-002 2.01738550e-001 3.05230958e-001]
 [7.35799070e-001 1.74156909e-023 6.10442576e-002 2.03156672e-001 5.34486884e-141]
 [3.78574551e-001 2.95087807e-001 2.57058086e-002 3.00631833e-001 3.48102550e-037]
 [4.78719535e-072 2.59776988e-001 6.40756512e-040 5.38660770e-002 6.86356935e-001]]
[[0.99491185 0.99964309]
 [2.98617434 2.99500088]
 [1.08849405 1.04527434]
 [2.00120936 2.00125902]
 [4.37461666 4.36637911]]

正解に近いパラメータが推定できていることが確認できる.パラメータ内のベクトルの並び順はランダムなので注意.

カーネル密度推定

カーネル密度推定(kernel density estimation)は,統計学において,確率変数の確率密度関数を推定する.

データの作成

今回は使用するデータとして2020年横浜市の最高気温のデータを使用します.

kde.py
import numpy as np
from scipy.stats import gaussian_kde
import matplotlib.pyplot as plt

# データの読み込み(今回は2020年横浜市の最高気温のデータを使用)
f = open('data/Yokohama_tmp.txt', 'r')
lines = f.readlines()
data = []
for line in lines:
    data.append(float(line))

# ヒストグラムで表してみます.
plt.hist(data, bins=30, range=(6, 37), color='orange')
plt.xlabel('temperature')
plt.ylabel('days')
plt.show()

kde_hist.png

推定

このデータを用いて推定を行っていきます.

# gaussian_kdeのインスタンスを作成
kde = gaussian_kde(data)

# 6〜37で密度推定
estimate = kde(np.linspace(6, 37, num=32), bw_method=0.2![kde.png]
# グラフで表示
plt.plot([x for x in range(6, 38)], estimate)
plt.xlabel('temperature')
plt.ylabel('kde')
plt.show()

kde.png

k近傍法

k近傍法(K-Nearest Neighbor)は,k個の近傍点を囲むある半径を持った一定値のカーネルによるカーネル密度推定として捉えることができる.

つまりは特徴空間において近くにあるk個のオブジェクトのうち最も一般的なクラスに分類します.
kの値が小さいとノイズの影響を受けやすくなり,kの値が大きいと精度が落ちる.

実装

分類するデータとしてアヤメの花を使用します.
kを変えながら精度を確認していきます.

knn.py
import matplotlib.pyplot as plt

from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier

# アヤメデータセット読み込み
from sklearn.datasets import load_iris
iris = load_iris()

# 特徴量
X = iris.data
# 目的変数
Y = iris.target

# データ分割
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, random_state=0)

list_knn = []
scores = []
for k in range(1, 31):
    # K-NN
    knc = KNeighborsClassifier(n_neighbors=k)
    knc.fit(X_train, Y_train)
    # 予測 
    Y_pred = knc.predict(X_test)
    score = knc.score(X_test, Y_test)
    print("[%d] score: {:.2f}".format(score) % k)

    list_knn.append(k)
    scores.append(score)

# プロット
plt.ylim(0.9, 1.0)
plt.xlabel("n_neighbors")
plt.ylabel("score")
plt.plot(list_knn, scores)
plt.show()

knn.png

kが20を超えたあたりから精度が落ちているのがわかります.

サポートベクターマシーン

サポートベクターマシンは,教師あり学習を用いるパターン認識モデルの一つであり,分類や回帰へ適用できる.現在知られている手法の中でも認識性能が優れた学習モデルの一つである.

ここでもsklearnを使用して実装を行っていきます.

実装

今回使用するデータもアヤメの花です.

svm.py
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC

iris = load_iris()

# 説明変数
X = iris.data
# 目的変数
Y = iris.target

# データの分割
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.5, random_state=0)

# モデルを構築
model = SVC(gamma='scale')
model.fit(X_train, Y_train)

# 予測
print(model.score(X_test, Y_test))
"0.9466666666666667"

sklearnを用いて実装するとさっぱりしすぎな気もしますね...

まとめ

それぞれのアルゴリズムが何に用いられるのか.何が難点なのかをまとめてみました.

  • ベイジアンネットワーク

    • 複数の事象間の因果関係をグラフ構造で表現することができる.
    • 既知の情報を元に未知の情報を確率的に知ることができる.
    • ベイジアンネットワークの推定は一般的に難しい.
  • 隠れマルコフモデル

    • 時系列のパターン認識に用いられる.(株価変動の予測,音声認識など)
    • 観測された記号系列の背後に存在する状態の遷移系列を推測する.
    • ニューラルネットワークに隠れマルコフモデルの役割が奪われつつあるが,出力結果の導出過程がわかるという利点もある.
  • カーネル密度推定

    • いくつかの標本から全体の分布を推定することができる.
  • k近傍法

    • クラス判別用の手法.
    • パラメータkに結果が左右される.
  • サポートベクターマシーン

    • パターン認識に用いられる.
    • 分類や回帰に用いられる.
    • 2つのグループに分けるのには優れているが3つ以上のグループに分けるものにはそのまま適用できず計算量が多くなってしまう.

軽くまとめてみました.それぞれの手法がどのような手法であるかに加えてどのような場面に適用できて,どのような利点があるのかを知っておくのは良いことだと思います.

コードはこちらに掲載しています.Github

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【VSCode】black : pythonのfomatterを導入

背景

fomatterで有名なのがC++ならc-lang, pythonであればblack
いつも使ってるblackのインストール方法をメモ

install

blackをインストール

pip install black

pythonのextensionをinstall
image.png

gedit ~/.config/Code/User/settings.jsonで以下の2行を追加する

"python.formatting.provider": "black",
"editor.formatOnSave": true,

結論

・formatterあると便利!

参考文献

VSCode: Using Black to automatically format Python
https://dev.to/adamlombard/how-to-use-the-black-python-code-formatter-in-vscode-3lo0

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む