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

Pythonからテスト用コンテナを手軽に作成する方法(testcontainers-python)

概要

pythonコード内でMySQLやPostgres、Seleniumなどのテストコンテナを作成することができる、testcontainersというライブラリがあるらしいというので試してみた結果です。
最初はDocker環境もいらないとかまさかないよね、と思いつつちょっと期待して試しましたが、予想通り、単にどこかにある既存のDockerホストに対して命令するだけだったのですが、それでも手動で上げ下げするよりは楽だと思われます。

対応範囲も広く、以下に対応しています。
- MySQL
- MariaDB
- PostgreSQL
- ElasticSearch
- Oracle DB
- MongoDB

MongoはなぜかReadmeに記載ないですが、以下のように対応しています。
https://testcontainers-python.readthedocs.io/en/latest/database.html#testcontainers.mongodb.MongoDbContainer

前提

自分のPCにDocker Toolboxを使ってコンテナ実行環境を作成しています。WindowsなのでDocker Desktop for Windowsを使いたいところですが、Hyper-Vを有効化できないため、インストールできません。またWindowsのバージョンも縛りがあり、WSL2も不可。
そこで少し古い方法ですが、Docker Toolboxというツールを使用しています。
https://docs.docker.com/toolbox/toolbox_install_windows/

Oracle Virtualboxという仮想化ソフトをHypervisorとして使用し、そのうえでLinuxを動かし、その上でDockerを起動する形になる。そのため、前提としてVirtualBoxをインストールする必要がある。(Toolboxをインストールする際に一緒にインストールすることもできる)

Host: Windows10(1803)
VirtualBox:6.0.12
Docker: 19.03.1

またDocker-Toolbox自体は起動された状態にしておく必要がある。(testcontainersからDocker-Toolboxの起動はできない。たぶん)

ちなみにコンテナ起動環境は、Windowsの場合、DOCKER_HOSTという環境変数を元に探しているとのこと。

echo %DOCKER_HOST%
tcp://192.168.99.100:2376

インストール

必要モジュールのインストールを実施。今回つかう範囲で必要だった上2つは追加で入れています。

pip install PyMySQL
pip install sqlalchemy
pip install testcontainers

公式にあるrequirementsファイルをpythonのバージョンに合わせて使用してもよいのですが、分量が多すぎたので個別にいれています。
https://github.com/testcontainers/testcontainers-python/tree/master/requirements

テスト

公式のテストコードそのままですが、以下のコードを記載したファイルを作成し、実行します。

app.py
import sqlalchemy
from testcontainers.mysql import MySqlContainer

with MySqlContainer('mysql:5.7.17') as mysql:
    engine = sqlalchemy.create_engine(mysql.get_connection_url())
    version, = engine.execute("select version()").fetchone()
    print(version)  # 5.7.17

イメージがない場合はpullしてきて起動してくれます。新規にコンテナを起動した場合は、コード実行後にコンテナも削除するようです。

テスト(MongoDBバージョン)

前提としてPyMongoをインストールしておきます。

pip install pymongo

以下のコードを実行。MySQL同様にバージョンを返しています。

app.py
from testcontainers.mongodb import MongoDbContainer

with MongoDbContainer("mongo:4.4.4") as mongo:
    db = mongo.get_connection_client()
    version = db.server_info()['version']
    print(version)  # 4.4.4

感想

今までは開発環境の操作とコーディング、テストは別々に行っていましたが、これをうまく使えば統合できそうだなと。ただまだそこまで至っていないので、まずはテストをしっかり書くところから取組みたいと思います。

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

pyppeteer製のクローラをplaywright-pythonに移行したときのTips

SPA向けのクローラをpyppeteerで実装していたのですが、playwright-pythonに移行してみました。

基本的な使い心地はそのままに、細かいところで便利になっている印象だったので、pyppeteerを単純なクローラとして利用しているのでしたら、試してみてはいかがでしょうか。

■導入

普通にpip, pipenv, poetry等でplaywright-pythonを入れたあと、playwright installでバイナリを取得します。

$ poetry add playwright
$ poetry run playwright install

ただし、これだとchromium/firefox/webkitの全バイナリが入ってしまうので、クローラのように複数ブラウザで動かす必要がない場合は、明示的に指定するのがおすすめです。

$ poetry run playwright install webkit

■スクリプトの書き換え

pyppeteerとほとんど変わりませんが、asyncで書かなくても良い(もちろん書いても良い)ので、よりとっつきやすくなっています。

pyppeteer.py
import asyncio
from pyppeteer import launch

async def main():
    browser = await launch({    
        "args": ["--no-sandbox"],   
        "slowMo": 5,    
        "headless": True,
    })
    page = await browser.newPage()
    await page.goto('https://example.com')
    await page.screenshot({'path': 'example.png'})
    await browser.close()

asyncio.get_event_loop().run_until_complete(main())
playwright.py
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    iphone_11 = p.devices["iPhone 11 Pro"]
    browser = p.webkit.launch(headless=True)
    context = browser.new_context(**iphone_11)
    page = context.new_page()
    page.goto("https://example.com")
    page.screenshot(path="example.png")
    browser.close()

page.waitFor()slowMoが不要に

Auto-waitingが優秀で、画面描画に合わせてpage.waitFor()slowMoで適宜waitを入れてやらなくても、いい感じに処理してくれます。

余計なwaitがないと実行時間も短くなるので、とりあえず消して試してみるのがおすすめです。

device

viewportとかその辺の設定がdeviceとしてビルトインで提供されていて楽ちんです。対応デバイス一覧は以下を参照。

任意のデバイスを定義して使う場合はこんな感じ。

browser = playwright.webkit.launch(headless=True)
device = {
    'user_agent': 'foobar',
    'viewport': {
        'width': 1200,
        'height': 2048
    },
    'device_scale_factor': 1,
    'is_mobile': True,
    'has_touch': True,
    'default_browser_type': 'webkit'
}
context = browser.new_context(**device)

クローラとしてUAを明示したいだけなら、以下でOKです。

browser = playwright.webkit.launch(headless=True)
context = browser.new_context(user_agent="foobar")

HTML文字列取得

pyppeteerと同じなので、クローラがhtml文字列をlxmlやbs4に食わせるタイプとして実装されていれば、特に変更不要です。

python
page = context.new_page()
page.goto(url)

html = page.content()

■Dockernize

公式の https://github.com/microsoft/playwright-python/blob/master/Dockerfile を参考にしてください。

ライブラリが足りない場合、実行時に丁寧に表示されるので、とりあえず流してみて、エラーがあれば追加してあげるのが良さそうです。

shell
playwright._impl._api_types.Error: Host system is missing dependencies!

  Install missing packages with:
      sudo apt-get install libnss3\
          libnspr4\
          libasound2

参考に、以下に動作確認(2021/02/28: webkit用)したものを示します。

Dockerfile
FROM ubuntu:focal

# 1. Install latest Python
RUN apt-get update && apt-get install -y python3 python3-pip && \
    update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1 && \
    update-alternatives --install /usr/bin/python python /usr/bin/python3 1

# 2. Install WebKit dependencies
RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
    libwoff1 \
    libopus0 \
    libwebp6 \
    libwebpdemux2 \
    libenchant1c2a \
    libgudev-1.0-0 \
    libsecret-1-0 \
    libhyphen0 \
    libgdk-pixbuf2.0-0 \
    libegl1 \
    libnotify4 \
    libxslt1.1 \
    libevent-2.1-7 \
    libgles2 \
    libxcomposite1 \
    libatk1.0-0 \
    libatk-bridge2.0-0 \
    libepoxy0 \
    libgtk-3-0 \
    libharfbuzz-icu0

# 3. Install gstreamer and plugins to support video playback in WebKit.
RUN apt-get update && apt-get install -y --no-install-recommends \
    libgstreamer-gl1.0-0 \
    libgstreamer-plugins-bad1.0-0 \
    gstreamer1.0-plugins-good \
    gstreamer1.0-libav \
    # add
    libvpx6\
    libgstreamer-plugins-base1.0-0\
    libgstreamer1.0-0\
    libopenjp2-7

RUN apt-get update \
    && pip install --upgrade pip \
    && pip install --no-cache-dir poetry && poetry config virtualenvs.create false && poetry install --no-dev \
    && playwright install webkit \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

なお、pyppeteerでDockerコンテナ経由の実行時に必要だった"args": ["--no-sandbox"]は不要になります。

(備考) tz選択が出て止まってしまう場合

Configuring tzdata
------------------

Please select the geographic area in which you live. Subsequent configuration
questions will narrow this down by presenting a list of cities, representing
the time zones in which they are located.

  1. Africa      4. Australia  7. Atlantic  10. Pacific  13. Etc
  2. America     5. Arctic     8. Europe    11. SystemV
  3. Antarctica  6. Asia       9. Indian    12. US
Geographic area:

DEBIAN_FRONTEND="noninteractive"を突っ込んでください。環境変数に設定してあげてもよいかと思います。

■移行で注意すべき点

pyppeteerで使われているChromiumのバージョンはかなり古い

pyppeteerで使われているChromiumのバージョンはかなり古い(REV:588429)です。
playwright-python(2021/02/28時点)だとREV:854489なので、そのあたりに起因する挙動の違いがないとも限りません。注意してください。

ちなみにMacOSXでplaywrightを動かした場合、バイナリは以下に配置されているようです。

shell
$ ls ~/Library/Caches/ms-playwright
chromium-854489 ffmpeg-1005     firefox-1234    webkit-1438

playwright-python公式のベースイメージはUbuntu Focal Fossa(20.04)

DockerHubのpythonイメージとかconda系を使ってる場合、playwrightに必要なライブラリがなくて困ったりするかもしれません。

私はpython:3.8.6-slimを使ってたんですがlibevent-2.1-7が見当たらなくてうーん…ってなりました。

頑張る気力がなかったので、playwrightに寄せる形でクローラのベースイメージをubuntu:focalにしてしまいましたが、クローラで使っている他のライブラリの依存関係が厳しめな場合、注意が必要です。

ちなみにubuntu:focalのpython3は3.8.5だったので、その辺も現在お使いのライブラリによっては、調整が必要になるかもしれません。

■最後に

「pyppeteerのスペルに気を使わなくて良くなる」、この一点だけでもplaywright-python、おすすめです!

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

pytestでFizzBuzz問題をテスト駆動開発(TDD)

分かること

・テスト駆動開発(TDD)はどの順序で開発すべきか?
・テスト駆動開発(TDD)はなぜ失敗をわざと行うのか?
・pytestはどう使うべきか?

目次

1. テスト駆動開発(TDD)について
2. Python, Pytestでテスト駆動開発FizzBuzz問題

1. テスト駆動開発(TDD)について

テスト駆動開発は以下3種のサイクルである。
(ToDoリストを作成し、簡単な処理から手を付けると良い)

1. RED(テスト失敗)
テストコードを作成してテストする。(実装コードを作成してないので失敗する)
2. GREEN(テスト成功)
成功するように必要最小限の実装コードを書く。(正しさや綺麗さは考えない)
3. リファクタリング
成功を維持したままコードを綺麗にする
image.png
(画像:50 分でわかるテスト駆動開発 より)

なぜ失敗を工程に入れるのか?

テストコードの正しさを確認(テストコードのテスト)する為。
誤りのある実装コードのテストが予想通りのエラーならば、正しい実装コードでもテストが正しく機能されると期待できる。

2. Pytestでテスト駆動開発(FizzBuzz問題

FizzBuzz問題をテスト駆動開発で実装し、テスト駆動開発の工程を確認する。

FizzBuzz問題
1から100までの数をプリントするプログラムを書け。
ただし
3の倍数のときは数の代わりに「Fizz」とプリントし、
5の倍数のときは「Buzz」とプリントし、
3と5両方の倍数の場合には「FizzBuzz」とプリントすること。

0. [準備] ToDoリストを作成する

簡単な処理から優先順位を高くすると良い。

- [ ] 数を文字列にして返す

- [ ] 3の倍数のときは数の代わりに文字列'Fizz文字列'と返す
- [ ] 5の倍数のときは文字列'Buzz'と返す
- [ ] 3と5の倍数の場合には文字列'FizzBuzz'と返す

- [ ] 1から100までの数
- [ ] プリントする

1. ToDoリストから1つをピックアップしてサイクルを回す

- [ ] 数を文字列にして返す
    - [ ] 1を渡したら文字列"1"を返す  ← next
    - [ ] 2を渡したら文字列"2"を返す

必要ならToDoの深堀りをする。

1-1. RED(テスト失敗)

テストコードを作成してテストでわざと失敗する

1-1-1. テストコードを作成(test_fizz_buzz.py)

「1を渡したら文字列"1"を返す」テストを作成する

test_fizz_buzz.py
import pytest

class TestStringConvert:
    # 前準備
    @pytest.fixture
    def target(self):
        from fizz_buzz import string_convert        
        return string_convert

    # 検証
    def test_return_value(self, target):
        assert target(1) == '1'

この時点で既に、
・実装ファイルとテストコードのファイル名(fizz_buzz.py, test_fizz_buzz.py)
・数を文字列にして返す関数名
・数を文字列にして返す処理をテストするクラスと関数(TestStringConvert, test_return_value)
と考えることが多い為、ToDoは簡単な処理から始めるのがおすすめ。

"@pytest.fixture"はテストの前処理(実装コードで使う関数の呼び出し)を行うpytestの機能。

1-1-2. テストコードをテスト(test_fizz_buzz.py)

実装コードを作成してない(実装モジュールを作成してない)ので失敗する

pytest test_fizz_buzz.py
============================= test session starts =============================
-
collected 1 item

test_fizz_buzz.py E                                                      [100%]

=================================== ERRORS ====================================
____________ ERROR at setup of TestStringConvert.test_return_value ____________

self = <test_fizz_buzz.TestStringConvert object at 0x000001EF5648A190>

    @pytest.fixture
    def target(self):
>       from fizz_buzz import string_convert
E       ModuleNotFoundError: No module named 'fizz_buzz'

test_fizz_buzz.py:7: ModuleNotFoundError
=========================== short test summary info ===========================
ERROR test_fizz_buzz.py::TestStringConvert::test_return_value - ModuleNotFoun...
========================= 1 error in 0.18s =========================

予想(狙い)通り、fizz_buzzモジュールがないエラーなら成功。

E       ModuleNotFoundError: No module named 'fizz_buzz'

このエラーが出なければ、テストコードのどこかに誤りがあると分かる。

1-2. GREEN(テスト成功)

テストコードが成功することだけを優先して必要最小限の実装コードを書く。
(この時点で正しさや綺麗さは考えなくて良い)

1-2-1. 必要最小限の実装コードを書く(fizz_buzz.py)

fizz_buzz.py
def string_convert(x):
    return '1'

1-2-2. テストコードをテスト(test_fizz_buzz.py)

pytest test_fizz_buzz.py
============================= test session starts =============================
-
collected 1 item

test_fizz_buzz.py .                                                      [100%]
======================== 1 passed in 0.02s =========================

成功。

(1-3. リファクタリング

今回はそのまま次のサイクルへ向かう。

2. 次のサイクルを回す

- [ ] 数を文字列にして返す         
    - [x] 1を渡したら文字列"1"を返す    
    - [ ] 2を渡したら文字列"2"を返す   ← next

2-1. RED(テスト失敗)

2-1-1. テストコードを拡張(test_fizz_buzz.py)

「2を渡したら文字列"2"を返す」テストを追加する

test_fizz_buzz.py
 import pytest

 class TestStringConvert:
     # 前準備
     @pytest.fixture
     def target(self):
         from fizz_buzz import string_convert        
         return string_convert

+    check_value_data = [
+        (1, '1'),  # 1を渡したら文字列"1"を返す
+        (2, '2')   # 2を渡したら文字列"2"を返す
+    ]

     # 検証
-    def test_return_value(self, target):      
-       assert target(x) == '1'
+    @pytest.mark.parametrize('x, expected_return_value', check_value_data)
+    def test_return_value(self, target, x, expected_return_value):      
+        assert target(x) == expected_return_value

@pytest.mark.parametrizeで以下2パターン(計2回)のテストを定義する。

1.x = 1, expected_return_value= '1'
2.x = 2, expected_return_value= '2'

→1つのテストに複数のassert文を記述するのは避ける。

以下のような記述は避ける。
(assert1でエラーとなったとき、assert2のテスト結果が分からない為。)

    # 検証(避けたい記述方法)
    def test_return_value(self, target):      
        assert target(1) == '1'  # assert1 
        assert target(2) == '2'  # assert2(assert1がエラーだと実行されない)

2-1-2. テストコードをテスト(test_fizz_buzz.py)

現在の実装コード(fizz_buzz.py)はどんな値を入れても文字列1を返す為、エラーが出る。

pytest test_fizz_buzz.py
============================= test session starts =============================
-
collected 2 items

test_fizz_buzz.py .F                                                     [100%]

================================== FAILURES ===================================
__________________ TestStringConvert.test_return_value[2-2] ___________________

self = <test_fizz_buzz.TestStringConvert object at 0x0000013B7BC5C4F0>
target = <function string_convert at 0x0000013B7BC32DC0>, x = 2
expected_return_value = '2'

    @pytest.mark.parametrize('num, expected_return_value', check_value_data)
    def test_return_value(self, target, x, expected_return_value):
>       assert target(x) == expected_return_value
E       AssertionError: assert '1' == '2'
E         - 2
E         + 1

test_fizz_buzz.py:18: AssertionError
=========================== short test summary info ===========================
FAILED test_fizz_buzz.py::TestStringConvert::test_return_value[2-2] - Asserti...
=================== 1 failed, 1 passed in 0.19s ====================

2-2. GREEN(テスト成功)

テストの(狙い通りの)失敗を確認後、テストが成功するように実装コードを訂正する。

fizz_buzz.py
  def string_convert(x):
-     return '1'
+     return str(x)

PS C:\Users\spell\Documents\GIT\fizzbuzz> pytest test_fizz_buzz.py
============================= test session starts =============================
platform win32 -- Python 3.8.5, pytest-6.1.1, py-1.9.0, pluggy-0.13.1
rootdir: C:\Users\spell\Documents\GIT\fizzbuzz
collected 2 items

test_fizz_buzz.py ..                                                     [100%]
======================== 2 passed in 0.02s =========================

2つのテストが実行されたことも分かる。

collected 2 items

2-3. リファクタリング

成功を維持したままコードを綺麗にする

test_fizz_buzz.py
 import pytest

 class TestStringConvert:
     @pytest.fixture
     def target(self):
         from fizz_buzz import string_convert        
         return string_convert

     check_value_data = [
         (1, '1'),
         (2, '2')
     ]

-    @pytest.mark.parametrize('x, expected_return_value', check_value_data)
-    def test_return_value(self, target, x, expected_return_value):      
-        assert target(x) == expected_return_value
+    @pytest.mark.parametrize('num, expected_return_value', check_value_data)
+    def test_return_value(self, target, num, expected_return_value):      
+        assert target(num) == expected_return_value
fizz_buzz.py
-  def string_convert(x):
-      return str(x)
+  def string_convert(num):
+      return str(num)

3. 次のサイクルを回す

- [X] 数を文字列にして返す                          ← finish
    - [X] 1を渡したら文字列'1'を返す
    - [X] 2を渡したら文字列'2'を返す

- [ ] 3の倍数のときは数の代わりに文字列'Fizz'と返す   
    - [ ] 3を渡したら文字列'Fizz'を返す             ← next
- [ ] 5の倍数のときは文字列'Buzz'と返す
- [ ] 3と5の倍数の場合には文字列'FizzBuzz'と返す

- [ ] 1から100までの数
- [ ] プリントする

3-1. RED(テスト失敗)

3-1-1. テストコードを拡張(test_fizz_buzz.py)

「3を渡したら文字列"Fizz"を返す」テストを追加する

test_fizz_buzz.py
import pytest

class TestStringConvert:
    @pytest.fixture
    def target(self):
        from fizz_buzz import string_convert        
        return string_convert

    check_value_data = [
        (1, '1'),
        (2, '2'),
        (3, 'Fizz')
    ]

    @pytest.mark.parametrize('num, expected_return_value', check_value_data)
    def test_return_value(self, target, num, expected_return_value):      
        assert target(num) == expected_return_value

3-2. GREEN(テスト成功)

3-2-1. 成功するように必要最小限のコードを書く(倍数関係なく3ならFizzを返す)

fizz_buzz.py
 def string_convert(num):
+    if num == 3:
+        return 'Fizz'

     return str(num)

3-2-2. 成功したら正しい処理(3の倍数ならFizzを返す)をするよう訂正

fizz_buzz.py
 def string_convert(num):
-    if num == 3:
+    if num % 3 == 0:
         return 'Fizz'

     return str(num)

3-3. リファクタリング

・テストコードから仕様が読み取れない(個別の具体的な動きしかない)為、テストコードだけで仕様を読み取れるようにする。
・また、テストのパターン数に対称性を持たせる。
(メンテナンスを減らす為にも場合分けは減らして対称性を持たせる)

test_fizz_buzz.py
 import pytest

 class TestStringConvert:
     @pytest.fixture
     def target(self):
         from fizz_buzz import string_convert        
         return string_convert

     check_value_data = [
-        (1, '1'),
-        (2, '2'),
+        # 3の倍数のとき
         (3, 'Fizz'),
+        # その他の数のとき
+        (1, '1')
     ]

     @pytest.mark.parametrize('num, expected_return_value', check_value_data)
     def test_return_value(self, target, num, expected_return_value):      
         assert target(num) == expected_return_value

今回はコメントアウトで説明したが、以下の手法でも効果的。

・テスト関数名を長くして仕様を説明する。
・パターンごとにテストクラス分けを行う。

以上を繰り返すのがテスト駆動開発(TDD)である。

参考動画:50 分でわかるテスト駆動開発@t_wadaさんによるライブコーディング)

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

C - Modulo Summation, D - Flipping Signs

C - Modulo Summation

O(N)
最小公倍数を計算。
その後、最小公倍数-1の値で余りを取得し加算していきます。

python
import math
import heapq
import itertools
from functools import reduce

def my_lcm_base(x, y):
    return (x * y) // math.gcd(x, y)

# main
def main():
    N = int(input())
    A = list(map(int, input().split()))

    g = reduce(my_lcm_base, A, 1)
    g = g - 1
    res=0
    for i in range(0, len(A)):
        res += g % A[i]

    print(res)

# エントリポイント
if __name__ == '__main__':
    main()

D - Flipping Signs

O(N)
i, i+1に-1をかけていくのを繰り返す。
最終的に奇数なら−符号が一つ存在、偶数なら全て+符号になります。

偶数なら全てを正整数に変更して全ての整数の合計を出力。

奇数なら全てを正整数に変更して最小値を取得。
全ての整数-(最小値*2)を計算。
正整数にした際の最小値を負数に変更して、全ての整数の合計を出力。

python
import math
import heapq
import itertools
from functools import reduce

def my_lcm_base(x, y):
    return (x * y) // math.gcd(x, y)

# main
def main():
    N = int(input())
    A = list(map(int, input().split()))

    cnt=0
    for i in range(0, len(A)):
        if A[i]<0:
            cnt+=1

    min_v=10000000000
    for i in range(0, len(A)):
        if A[i]<0:
            A[i] = A[i] * -1
        min_v = min(min_v, A[i])

    if cnt%2==0:
        print(sum(A))
    else:
        print(sum(A)-(min_v*2))

# エントリポイント
if __name__ == '__main__':
    main()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gaussian-Bernoulli RBMの分散学習をちゃんと行う

Gaussian-Bernouili RBMって

RBMと言えば可視層と隠れ層がBernouili分布に従いますよね。
本題のGaussian-Bernouili RBM(GBRBM)は可視層がGaussian分布で隠れ層がBernouili分布に従うRBMなわけです。
GBRBM学習って分散の学習が上手くいかない〜、とか、分散を1に固定して学習しない記事や実装コードばかり目にするので、
GBRBMを使おうとしてた時に相当困りました・・・(4年前)。
ということでコードを混ぜてGBRBMについて!!!

分散の学習について

悩んでる人は当時の僕だけじゃないはずですが、
一応前置きすると、可視特徴量の分散をきちんと再現できるわけでは無いです。
あくまで「途中でパラメータが発散してしまう!」などの、学習が破綻するようなレベルの問題をクリアするためのお話です。

結論から言うと、”学習時にCD法を行う際、きちんと隠れ層をサンプリングしてから可視層の分布を推定する”ってことです。
多くの記事やコードでは
$v_0$ ⇒ $p(h_0)$ ⇒ $p(v_1)$ ⇒ $p(h_1)$
のように、CD法を行ってます。
これを
$v_0$ ⇒ $p(h_0)$ ⇒ $h_0$ ⇒ $p(v_1)$ ⇒ $v_1$ ⇒ $p(h_1)$
のように、ちゃんと$h_0$をサンプリングしてから$p(v_1)$を算出すれば学習もちゃんと進みます。

「RBMの隠れ層の値は 1 or 0 程度の情報量しか持ってない」という前提で可視層が予測されるので
学習時はちゃんとサンプリングした方が良いそうです。

ただし、学習が終わってしまえば、デコード時は隠れ層の期待値からデコードして大丈夫(というかそうすべき?)です。

初期化

import numpy as np

class GBRBM(object):
  def __init__(self, I, J):
    self.W = np.random.randn(I, J)
    self.b = np.zeros(I)
    self.c = np.zeros(J)
    self.z = np.zeros(I)

Wは接続重み、bとcはそれぞれ可視層のバイアスと隠れ層のバイアスです。
分散$\sigma^2_i$の学習ですが、非負制約を満たさなくてはいけないので
$$
z_i = \log\sigma^2_i
$$
として、$\sigma^2_i$の代わりに$z_i$を学習します。

ネットワーク定義

  def propup(self, v):
    v_ = v / np.exp(self.z)
    tmp = np.dot(v_, self.W) + self.c
    ph = self.sigmoid(tmp)
    return ph

  def propdown(self, h):
    v_mean = np.dot(h, self.W.T) + self.b
    return v_mean

  def contrastive_divergence(self, v, k=1):
    sigma = np.sqrt(np.exp(self.z))
    ph0 = self.propup(v) # p(h|v)
    phk = ph0
    for step in range(k):
      hk = np.random.binomial(size=phk.shape, n=1, p=phk) # sampling h 
      pvk = self.propdown(hk) # p(v|h)
      vk = np.random.randn(pvk.shape[0], pvk.shape[1])*sigma + pvk 
      phk = self.propup(vk) # p(h|v)    

    return ph0, vk, phk

propupは$p(h|v)$を算出するブロックで、propdownは$p(v|h)$を算出するブロックです。
contrastive_divergenceはCD法によるサンプリングブロックで、
さっきも書きましたが$p(v|h)$を算出する際にphk($p(h|v)$のこと)を使うのではなく
ちゃんとサンプリングしたhkを使います。
ただ、勾配計算にはどうせ期待値を使うのでサンプリングした値ではなく$p(h|v)$を返します。

勾配計算

  def get_grad(self, v):
    N = v.shape[0]

    #--- CD_1 ---#
    v0 = v
    h0, v1, h1 = self.contrastive_divergence(v0)

    var = np.exp(self.params['z'])
    v0_, v1_ = v0/var, v1/var

    W, b = self.W, self.b
    #--- Calculate gradient params ---#
    vw0 = np.dot(v0_.T, h0)
    vw1 = np.dot(v1_.T, h1)
    dW = (vw0 - vw1) / N
    db = np.mean(v0_ - v1_, axis=0) 

    vz1 = 0.5*((v0 - b)**2) - np.dot(h0, W.T)*v0
    vz2 = 0.5*((v1 - b)**2) - np.dot(h1, W.T)*v1
    dz = np.mean((vz1 - vz2)/var, axis=0)

    return dW, db, dz

ここはどの記事でもコードでもほぼ一緒だと思います。

全体コード

import numpy as np

class GBRBM(object):
  def __init__(self, I, J):
    self.W = np.random.randn(I, J)
    self.b = np.zeros(I)
    self.c = np.zeros(J)
    self.z = np.zeros(I)

  def propup(self, v):
    v_ = v / np.exp(self.z)
    tmp = np.dot(v_, self.W) + self.c
    ph = self.sigmoid(tmp)
    return ph

  def propdown(self, h):
    v_mean = np.dot(h, self.W.T) + self.b
    return v_mean

  def contrastive_divergence(self, v, k=1):
    sigma = np.sqrt(np.exp(self.z))
    ph0 = self.propup(v) # p(h|v)
    phk = ph0
    for step in range(k):
      hk = np.random.binomial(size=phk.shape, n=1, p=phk) # sampling h 
      pvk = self.propdown(hk) # p(v|h)
      vk = np.random.randn(pvk.shape[0], pvk.shape[1])*sigma + pvk 
      phk = self.propup(vk) # p(h|v)    

    return ph0, vk, phk

  def get_grad(self, v):
    N = v.shape[0]

    #--- CD_1 ---#
    v0 = v
    h0, v1, h1 = self.contrastive_divergence(v0)

    var = np.exp(self.params['z'])
    v0_, v1_ = v0/var, v1/var

    W, b = self.W, self.b
    #--- Calculate gradient params ---#
    vw0 = np.dot(v0_.T, h0)
    vw1 = np.dot(v1_.T, h1)
    dW = (vw0 - vw1) / N
    db = np.mean(v0_ - v1_, axis=0) 

    vz1 = 0.5*((v0 - b)**2) - np.dot(h0, W.T)*v0
    vz2 = 0.5*((v1 - b)**2) - np.dot(h1, W.T)*v1
    dz = np.mean((vz1 - vz2)/var, axis=0)

    return dW, db, dz

  def train(self, v, nbatch=256, nepoch=100, lr=0.01): 
    N = v.shape[0]

    for epoch in range(n_epoch):
      perm = np.random.permutation(N)

      for i in range(0, N-nbatch, nbatch):
        v_batch = np.asarray(v[perm[i:i+n_batch]])
        dW, db, dc, dz = self.get_grads(v_batch)

        self.W -= lr * dW
        self.b -= lr * db
        self.c -= lr * dc
        self.z -= lr * dz

  def sigmoid(self, x):
    return 1./(1. + np.exp(x))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ディープラーニングのデータセットづくりのためにpythonのpixivpyを使ってタグに一致した画像をダウンロードする。(その3)

初めに

ganやらcnnやらで二次元の画像のデータセットを作りたくなったので
タグを指定してpixivから画像をダウンロードするスクリプトを作った。

pythonのpixivpyを使ってpixivから特定のユーザーの作品をすべて一括でダウンロードする(うごイラ含む)
これをベースに作りました。

注意

pixivpyはおそらく中国の有志が作ったライブラリであり、公式のものではありません。
そのためログイン時のユーザーid、passwordは流出してもいいように捨て垢を使うなどの方法を使うことをお勧めします。
また乱用厳禁です。
スクレイピングのルールとして1秒当たりリクエリは1回までという暗黙のルールがあります。
これがないとdos攻撃となってしまい、法的にアウトです。
sleepをコメントアウトする際は自己責任でお願いします。
また何か問題が起きても自己責任でお願いします。

ソースコード

tag_downloader.py
from pixivpy3 import *
import json
import os
from time import sleep



#タグによるフィルター 書き方 target_tag = ["Fate/GrandOrder","FGO","FateGO","Fate/staynight"]
target_tag = [] #target_tag内に複数書くと、そのうち少なくとも一つあればダウンロード
target_tag2 = []#さらにtarget_tag2に書くとtarget_tagを満たし、かつtarget_tag2を満たすものだけダウンロード
extag = ["R-18"]#extag内のタグが一つでも入っていればダウンロードしない
#ブックマーク数によるフィルター、最小値を設定、0ならすべて
score=0
#閲覧数によるフィルター、最小値を設定、0ならすべて
view=1000

#画像を保存するディレクトリ
saving_direcory_path = "./img/"
#検索タグ
serch_tag = "初音ミク"
#何ページ分取得するか(1ページ30作品)
max_page = 10



#def search_illust(self, word, search_target='partial_match_for_tags', sort='date_desc', duration=None):
# 搜索 (Search) 
# search_target - 検索タイプ
#   partial_match_for_tags  - 部分一致
#   exact_match_for_tags    - 完全一致
#   title_and_caption       - 説明文?
# sort: [date_desc, date_asc, popular_desc] - popular_desc:プレミアム会員のみ
# duration: [within_last_day, within_last_week, within_last_month]
# start_date, end_date: '2020-07-01'


#2021/2/21方法変更
api = AppPixivAPI()
api.auth(refresh_token=REFRESH_TOKEN)
aapi = AppPixivAPI()
aapi.auth(refresh_token=REFRESH_TOKEN)

if not os.path.exists(saving_direcory_path):
    os.mkdir(saving_direcory_path)
separator = "------------------------------------------------------------"

def making_illust_list(serch_tag):
    ids = []
    urls = []
    for p in range(1, max_page+1):
        sleep(1)
        try:
            print("p" + str(p))
            if p == 1:
                a = aapi.search_illust(serch_tag, search_target='partial_match_for_tags', sort='popular_desc')
            else:
                a = aapi.search_illust(**nxt)
            illusts = a.illusts
            for illust in illusts:
                ids.append(illust.id)
            nxt = aapi.parse_qs(a.next_url)
            if nxt == None:
                break
        except:
            print("erro")
    return ids




def download(ids):
    sleep(1)         
    for idnum in ids:
        illust =  api.works(idnum).response[0]

        #フィルター

        #タグによるフィルター
        if len(list(set(target_tag)&set(illust.tags))) == 0 and target_tag != []:
            continue
        if len(list(set(target_tag2)&set(illust.tags))) == 0 and target_tag2 != []:
            continue
        #exタグが一つでも入っていたらスキップ
        if len(list(set(extag)&set(illust.tags))) > 0 :
            continue
        #score以上の作品だけダウンロード
        if illust.stats.favorited_count.private + illust.stats.favorited_count.public < score :
            continue
        #view以上の作品だけダウンロード
        if illust.stats.views_count < view :
            continue

        print("Title: {}".format(illust.title))
        print("score: {}".format(illust.stats.favorited_count.private + illust.stats.favorited_count.public))
        print("view: {}".format(illust.stats.views_count))

        #if the illustration has already downloaded, skip downloading it
        if os.path.exists(saving_direcory_path+str(illust.id)+"_p0.png") or os.path.exists(saving_direcory_path+str(illust.id)+"_p0.jpg") or os.path.exists(saving_direcory_path+str(illust.id)+'_ugoira') or os.path.exists(saving_direcory_path+str(illust.id)+"_p0.gif"):
            print("Title:"+str(illust.title)+" has already downloaded.")
            print(separator)
            continue

        sleep(1)
        # illustrations with only one picture
        #複数の絵を投稿しているといろいろなタグが混ざるので一枚絵のみダウンロードすることにした。
        if illust.type == "illustration":
            aapi.download(illust.image_urls.large, saving_direcory_path)
            #print("Download complete! Thanks to {0}{1}!!".format(illust.user.id, illust.user.name))
        print(separator)



ids = making_illust_list(serch_tag)
download(ids)


使い方

pixivpyでログインできなくなったので、新しいログイン方法の紹介
これでログインしてください。

firt.py
#タグによるフィルター 書き方 target_tag = ["Fate/GrandOrder","FGO","FateGO","Fate/staynight"]
target_tag = [] #target_tag内に複数書くと、そのうち少なくとも一つあればダウンロード
target_tag2 = []#さらにtarget_tag2に書くとtarget_tagを満たし、かつtarget_tag2を満たすものだけダウンロード
extag = ["R-18"]#extag内のタグが一つでも入っていればダウンロードしない
#ブックマーク数によるフィルター、最小値を設定、0ならすべて
score=0
#閲覧数によるフィルター、最小値を設定、0ならすべて
view=1000

#画像を保存するディレクトリ
saving_direcory_path = "./img/"
#検索タグ
serch_tag = "初音ミク"
#何ページ分取得するか(1ページ30作品)
max_page = 10



ここの数値をいじって実行してください。
target_tagあたりは今回はあまり関係ないですが、元のコードを流用したため残っています。
詳しくは
pythonのpixivpyを使ってpixivから特定のユーザーの作品をすべて一括でダウンロードする(うごイラ含む)
を見てください。

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

pytesseract 概要と使い方 メモ

  • pytesseractの概要と使用方法についてメモする。

pytesseract 概要

  • OCRツールTesseractのPythonラッパー。

  • PillowNumPyなどの形式で解析対象データを受け取ることが可能。

  • コマンド呼び出しで実行。

インストール

  • pipコマンドを使用し、インストールする。
pip install pytesseract

※必要に応じて解析データの読み込み用途でPillowなどもインストールする。

tesseract本体も必要。Linux環境などではapt-get -y install tesseract-ocr tesseract-ocr-jpnでインストールしておく。

使用方法

  • Pythonコード
from pytesseract import pytesseract
from PIL import Image

# 読み込み対象ファイルの指定
img = Image.open("./test.png, "r")
# tesseractコマンドのインストールパス
pytesseract.tesseract_cmd = "/usr/bin/tesseract"
# 文字列として出力できる。
ocr_result = pytesseract.image_to_string(img, lang="eng+jpn")

参考情報

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

Numeraiに上位20%以上のスコアがでる予測値を提出する方法

はじめに

はじめまして。
tit_BTCQASH と申します。

この記事では上位20%以上の性能が出る予測値をNumeraiにサブミットする方法を解説します。

Numeraiにステークしたいけど、毎週プログラミングなんてめんどくさい・プログラミングなんてできない、という人向けです。

Numeraiって何?という方は、私の過去記事( 日本語版 /英語版)をご参照ください。

データのダウンロードから予測の提出まで

本項ではNumeraiに予測値を提出する方法を説明します。
使用する計算モデルは公式 がGithubで頒布しているexample_model.pyです。
公式が配布しているサンプルモデルですが、下手な独自モデルよりも性能が良いです。また、Numeraiに提出する予測値を格納したCSVファイルも公式が毎週配布しています。

それでは、提出用ファイルのダウンロードと提出方法について説明いたします。

image.png
公式サイトの左側にあるDownload Dataを押し、Zipファイルを解凍すると、example_predictions.csvというファイルがあります。
image.png

example_predictions.csvを公式サイト左側にあるUpload Predictions(Download Dataの下)からアップロードすれば完了です。
(ね?簡単でしょう?)
本ファイルは毎週土曜日18:00(UTC)にダウンロードできるようになります。本ファイルを月曜日の14:30(UTC)までに提出すれば、その週から開催されるNumeraiトーナメントに参加できます。

example_model.pyの性能

example_model.pyから計算した予測値は毎週公式が提出してくれています。本モデルの性能について下図に示します。
image.png
本モデルの性能で一番見るべきところはCorr Reptationです。Corr Reptationとは20ラウンドのCorr平均値を表しており、Corr*100の値が週当たりの平均リターン[%]を表します。example_model.pyは週当たり2.83%の平均リターンがあり、年利換算で約150%とかなり高いです。
一方mmcはマイナスの値なので、他のモデルに対する優位性はありません。そのため、ステーキングをする場合はCorrのみがよいでしょう。

image.png
また、ランキングを見ると過去20ラウンドでは200-700位の間に位置することが分かります。現在4861モデルがNumeraiに存在するので、example_model.pyは上位4~15%程度の性能を持つことがわかります。
下手に独自モデルを作って予測値を提出するより、example_model.pyを出した方が高いリターンを得られるかもしれません。

さいごに

NMRのトークン価値は変動しますので、NMRトーナメントで優秀な成績を修め、NMRの枚数を増やしても、JPY建てでは損失が発生する可能性があります。

また、NMRを一度ステークすると、トーナメント期間中は引き出せないことも注意が必要です。

個人的には、下手に年利が高いDefiよりもリスクが低いと感じておりますが、リスク感度は個人個人で異なると思いますので、ご自身の判断でご参加ください。

チップ用
NMR:0x0000000000000000000000000000000000021d96

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

Github Actionsで始めるプログラム定期実行

はじめに

よくプログラムの定期実行の方法として、Windowsではタスクの登録、Linuxではcronでの定期実行が紹介されるが、ローカル環境でこれらを行う場合は、もちろんPCを起動しておく必要がある。しかし、たかが1プログラムのために電気代を消費するのも勿体ないし、サーバーを借りて実行するほどでもない、といった需要に一番マッチするのがGithub Actionsである。
※この記事はGitに関しての記事ではないため、コミットやプッシュなどは理解しているものとして話を進める。

GitHub Actionsとは?

Github Actionsはその名の通りGitHubから提供されているワークフローを自動化するためのツールである。
具体的には、リポジトリの.github/workflowsの配下にYAML形式の実行制御ファイルを配置することによって利用することができる。Linuxで利用されるcronによる定期実行やレポジトリへのプッシュを検知して、ワークフローを実行することもできる。

公式ドキュメント -> https://docs.github.com/ja/actions/reference/workflow-syntax-for-github-actions

実行するトリガーとなるイベントを検知

リポジトリのmainブランチにプッシュされたときに実行

on:
  push:
    branches:
      - main

時間を設定して定期実行

on:
  schedule:
    - cron: '0 3 * * *'

※時間はUTCであることに注意が必要。上記の例では、1日に1回日本時間の12:00に実行。
cronの時間設定については↓の記事を参照
クーロン(cron)をさわってみるお

実行する環境の設定

jobs:
  build:
    runs-on: ubuntu-latest

他にもWindows ServerやMacOSなども使用できる。利用する場合は公式ドキュメントを参照されたい。

ワークフローの実行

ここからは主にLinuxで実行したいコマンドを書いていくことになる。(Linuxのコマンドの内容については解説しない)
今回は例としてPythonの環境を構築して、レポジトリ内のmain.pyを実行し、実行した結果をレポジトリに自動でプッシュするように設定する。

.github/workflows/actionstest.yaml
name: ActionsTest # GitHub Actionsにつける名前。任意で良い。

on:
  schedule:
    - cron: '0 3 * * *' # cronで定期実行

jobs:
  build:
    runs-on: ubuntu-latest # 最新のubuntu環境で下記のジョブを実行
    steps:
      - uses: actions/checkout@v2 # おまじない

      - name: Setup python # ワークフローのセクションごとに設定する名前。特に設定する必要はないが、どこでエラーが起きているかを把握する為にも設定しておいた方が良い。
        uses: actions/setup-python@v2 # Pythonのセットアップ
        with:
          python-version: "3.x" # Pythonのバージョン指定

      - name: Install dependencies # Pythonの依存環境のインストール
        run: | #このような書き方で複数行を一気に実行することができる。
          python -m pip install --upgrade pip
          pip install pandas
          pip install pytz
          pip install matplotlib
          pip install numpy

      - name: Run main.py # Pythonファイルの実行
        run: |
          python main.py

      - name: Commit and Push # 実行した結果をプッシュして変更をレポジトリに反映
        run: |
          git config user.name "YOUR NAME"
          git config user.email "YOUR MAIL"
          git add .
          git commit -m "Commit Message"
          git pull
          git push origin master

※最後のGitにコミットする前の名前とEmailは必須だそうだ。(この行を省いた状態でテストしていない...)

これだけで簡単にスクリプトを実行し、レポジトリに反映するスクリプトが出来る。ただし、月当たりの利用時間の制限があるので、サーバーとして運用するのは厳しいかと思われる。(無料枠では2000分/月

その他の機能

上記で紹介したもののほかにも、環境変数を利用したり、特定のシェルを利用することもできる。これらの機能の詳細については、下記のドキュメントを参考にされたい。

公式ドキュメント -> https://docs.github.com/ja/actions/reference/workflow-syntax-for-github-actions

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

ラズパイでラジコン作るぞ

書いてること

ラズパイでラジコンを作ったので、その方法をここに残します。
専用アプリ不要で手持ちのスマホがコントローラーになります。

完成品はこちら ↓

構成図

全体の構成図です。

image.png

  • ラズパイで4つのサーボモーターを制御
    • server.pyから制御信号を出す
    • ※Python3スクリプト
  • index.htmlserver.pyはSocket通信
  • http://{ラズパイ}:8000/index.htmlを開いたスマホがコントローラーになる
  • <制御の流れ概要>
    • index.htmlから5パターンの信号(前進/後進/左回り/右回り/停止)を発信
    • server.pyindex.htmlからの信号をもとにサーボモーターを制御

サーボモーターの配線はこちら

image.png

Raspberry Piの情報

$ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 10 (buster)
Release:        10
Codename:       buster

$ python3 -V
Python 3.7.3

スクリプト

スクリプトは全てラズパイの/home/pi/data/web配下に格納します。
ディレクトリ構成は以下のようになります。
ソケット通信については こちらのサイト を参考にさせて頂きました。

pi@raspi:~/data/web $ tree
.
├── images
│   ├── back.png
│   ├── go.png
│   ├── left.png
│   ├── right.png
│   └── stop.png
├── index.html
└── server.py

コントローラーになるindex.htmlのソースです。
index.htmlはSocket通信ではクライアント側になり、Socket通信のサーバー側となるserver.pyに対し5種類の信号(GO/BACK/LEFT/RIGHT/STOP)を送ります。

index.html
<!DOCTYPE html>
<html>/home/pi/data/web
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>コントローラー</title>
  </head>
<body>
  <img onclick="sendMessage('GO')"    src="images/go.png" />
  <img onclick="sendMessage('BACK')"  src="images/back.png" />
  <img onclick="sendMessage('LEFT')"  src="images/left.png" />
  <img onclick="sendMessage('RIGHT')" src="images/right.png" />
  <img onclick="sendMessage('STOP')"  src="images/stop.png" />
  <br />
  <!-- 出力 area -->
  <textarea id="messageTextArea" rows="10" cols="50"></textarea>
  <script type="text/javascript">
    var webSocket = new WebSocket("ws://192.168.3.19:9998"); // ウェブサーバを接続
    var messageTextArea = document.getElementById("messageTextArea"); // ウェブサーバから受信したデータを出力するオブジェクトを取得
    // ソケット接続すれば呼び出す関数。
    webSocket.onopen = function(message){
      messageTextArea.value += "Server connect...\n";
    };
    // ソケット接続が切ると呼び出す関数。
    webSocket.onclose = function(message){
      messageTextArea.value += "Server Disconnect...\n";
    };
    // ソケット通信中でエラーが発生すれば呼び出す関数。
    webSocket.onerror = function(message){
      messageTextArea.value += "error...\n";
    };
    // ソケットサーバからメッセージが受信すれば呼び出す関数。
    webSocket.onmessage = function(message){
      // 出力areaにメッセージを表示する。
      messageTextArea.value += "Recieve From Server => "+message.data+"\n";
    };

    function sendMessage(argVal){
      messageTextArea.value += "Send to Server => "+ argVal +"\n";
      webSocket.send(argVal); // WebSocketでtextMessageのオブジェクトの値をサーバに送信
    }

    // 通信を切断する。
    function disconnect(){
      webSocket.close();
    }
  </script>
</body>
</html>

サーバー側でサーボモーターを制御するserver.pyのソースです。
server.pyindex.htmlから受信した信号(GO/BACK/LEFT/RIGHT/STOP)に応じモーターを制御します。

server.py
import asyncio
import websockets
import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)

wheelPin = {
    "FrontRight": "2",
    "FrontLeft" : "3",
    "RearRight" : "17",
    "RearLeft"  : "4"
  }

wheelPwm = {
    "FrontRight": "",
    "FrontLeft" : "",
    "RearRight" : "",
    "RearLeft"  : ""
}

## 角度をラジアンに変換
def exchDegreeToRadian(argDegree):
    return 2.5 + (12.0 - 2.5) / 180 * (argDegree + 90)

## サーボモータ制御
def wheelControl(argFlg, argTerm):
    global wheelPwm
    act01 = exchDegreeToRadian( -90 )
    act02 = exchDegreeToRadian(  90 )
    act03 = exchDegreeToRadian(   0 )
    if(argFlg == "GO"):
        wheelPwm["FrontRight"].ChangeDutyCycle( act01 )
        wheelPwm["FrontLeft" ].ChangeDutyCycle( act01 )
        wheelPwm["RearRight" ].ChangeDutyCycle( act01 )
        wheelPwm["RearLeft"  ].ChangeDutyCycle( act01 )
    elif(argFlg == "BACK"):
        wheelPwm["FrontRight"].ChangeDutyCycle( act02 )
        wheelPwm["FrontLeft" ].ChangeDutyCycle( act02 )
        wheelPwm["RearRight" ].ChangeDutyCycle( act02 )
        wheelPwm["RearLeft"  ].ChangeDutyCycle( act02 )
    elif(argFlg == "STOP"):
        wheelPwm["FrontRight"].ChangeDutyCycle( act03 )
        wheelPwm["FrontLeft" ].ChangeDutyCycle( act03 )
        wheelPwm["RearRight" ].ChangeDutyCycle( act03 )
        wheelPwm["RearLeft"  ].ChangeDutyCycle( act03 )
    elif(argFlg == "LEFT"):
        wheelPwm["FrontRight"].ChangeDutyCycle( act01 )
        wheelPwm["FrontLeft" ].ChangeDutyCycle( act02 )
        wheelPwm["RearRight" ].ChangeDutyCycle( act01 )
        wheelPwm["RearLeft"  ].ChangeDutyCycle( act02 )
    elif(argFlg == "RIGHT"):
        wheelPwm["FrontRight"].ChangeDutyCycle( act02 )
        wheelPwm["FrontLeft" ].ChangeDutyCycle( act01 )
        wheelPwm["RearRight" ].ChangeDutyCycle( act02 )
        wheelPwm["RearLeft"  ].ChangeDutyCycle( act01 )

    time.sleep(argTerm)

#####################################################
# ここから処理を開始

## サーボモータの初期設定
for wheel in wheelPin:
    print("wheel={0} , pin={1}".format( wheel, wheelPin[wheel] ) )
    PinNo = int(wheelPin[wheel]) # GPIO
    GPIO.setup(PinNo, GPIO.OUT)
    wheelPwm[wheel] = GPIO.PWM(PinNo, 50)
    wheelPwm[wheel].start(0.0)

##----------------------------------------##

# クライアント接続すると呼び出す
async def accept(websocket, path):
  while True:
    # クライアントからメッセージを待機
    data = await websocket.recv()
    print("receive : " + data)
    # クライアントでechoを付けて再送信
    #await websocket.send("echo : " + data)
    if data == "GO":
      wheelControl("GO", 0.1)
    elif data == "BACK":
      wheelControl("BACK", 0.1)
    elif data == "STOP":
      wheelControl("STOP", 0.1)
    elif data == "LEFT":
      wheelControl("LEFT", 0.1)
    elif data == "RIGHT":
      wheelControl("RIGHT", 0.1)

# WebSocketサーバー生成
start_server = websockets.serve(accept, "raspi203.local", 9998)

# 非同期でサーバを待機する。
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

以上で全ての準備が完了しました。

実行方法

2つコンソールを起動し、コンソール①ではWebサーバーを起動し、コンソール②ではserver.pyを実行します。

コンソール①
$ cd /home/pi/data/web
$ python3 -m http.server 8000
コンソール②
$ cd /home/pi/data/web
$ python3 server.py

実際の実行画面はこんな感じ

image.png

これでサービスが起動状態になりました。スマホなどでhttp://{ラズパイIP}:8000/index.htmlを開くとそのデバイスがコントローラーになります。

おわりに

今回は、とりあえずラズパイをラジコンにするまでを紹介しました。今後は、コントローラのUIをもっとリッチにするとか、ラジコンにカメラを搭載しコントローラ側に表示するとか、機能を盛り込んでいきます。

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

AtCoder参加したったー(ABC193編)

こんにちは!バイオインフォマティクス系オタク修士学生のroadricefieldです!AtCoder ABC193参加しました!

A問題

何円安くなっているかを引き算で計算してそれが定価の何%に当たるか計算して出力すればいいですね.「割合」は小学5年生の算数で習います.

(私の解答)

A, B = map(int, input().split())
print(((A-B)/A)*100)

B問題

まず問題設定より,AiXi以上である店iでは,高橋くんがその店にたどり着いたときにはスヌケマシンは売り切れてしまっています.このことに気をつけてすべての店についてスヌケマシンを買えるのか,買えるのであれば価格はいくらなのかを調べて最安値を出力すればいいです.

(私の解答)

N = int(input())

ans = 10000000000

for _ in range(N):
  A, P, X = map(int, input().split())
  if A >= X: continue
  if P < ans: ans = P

if ans != 10000000000: print(ans)
else: print(-1)

私の解答ではどこの店でもスヌケマシンが買えない場合はansが最後に10000000000のままとなっていますのでそのようなときは-1を出力するように書いています.

C問題

変数ansNとして,abと表すことのできる数を列挙していってNよりも小さいかどうかを判定していき,小さいならば順次ansから1を引いていって,abNよりも大きくなってしまったところで処理を終了してその時のansを出力するという方針で解きました.

問題は「abと表すことのできる数を列挙」をどのように実現するかです.a, bそれぞれ2からスタートして1ずつ増やしながら列挙していけばよいでしょう.しかし,そのままやると途中で重複が起きてしまいます.たとえばa = 2, b = 4 のときab = 16ですがa = 4, b = 2のときもab = 16です.これに気をつけなければなりません.と,いうわけで一度出現した数を記録しておいて一度現れた数は飛ばしながら処理をすすめることにしましょう.aに代入する数がすでに出現している場合はbがどんな数でもabはすでに出現していることを利用するなどして以下のように書きました.

(私の解答)

N = int(input())

ans = N
done = set()

for i in range(2,10**5+5):
  if i**2 > N: break
  if i in done: continue
  j = 2
  while 1:
    if i**j > N: break
    else:
      done.add(i**j)
      ans -= 1
      j += 1

print(ans)

abで表せる数がさほど多くないので普通に間に合います.

D問題

これは高校の数学Aの確率ですね完全に.高橋くんと青木くんの裏向きのカードの組み合わせとしてあり得るすべての場合の数,(9*K*-8)(9*K*-9)で高橋くんが勝つ組み合わせの数を割ればいいです.ここで数字が同じ2枚のカードは異なるものとして扱わなければなりませんね.

まず最初に1~9のカードがそれぞれ何枚が表向きに場に出ていないカードとして残っているかを計算しておいて,それに気をつけながら高橋くん,青木くんの裏側のカードの数字のすべての組み合わせ(9 × 9 = 81通り)についてそれぞれ検討していきます.気をつけることとしては例えば高橋くんと青木くんの裏向きになっているカードの数が同じである場合を検討するとき,そもそもその数字のカードが表向きでないものとして2枚以上残っていなければそのような場合は存在しないことになります.高橋くんが勝つ数字の組み合わせとなる場合の数を計算していってその総和を(9*K*-8)(9*K*-9)で割ったものが答えになりますね.

(私の解答)

K = int(input())
S = input()
S = list(map(int, list(S[:4])))
T = input()
T = list(map(int, list(T[:4])))
ST = S + T

left_card = [K]*9

for i in range(1,10): left_card[i-1] -= ST.count(i)

bumbo = (9*K - 8)*(9*K - 9)
bunshi = 0

for i in range(1,10):
  if left_card[i-1] == 0: continue
  for j in range(1,10):
    if left_card[j-1] == 0: continue
    S.append(i)
    T.append(j)
    Takahashi = 0
    Aoki = 0
    for k in range(1,10):
      Takahashi += k*(10**S.count(k))
      Aoki += k*(10**T.count(k))
    if Takahashi > Aoki:
      if i == j and left_card[i-1] == 1:
        pass
      elif i == j and left_card[i-1] != 1:
        bunshi += left_card[i-1]*(left_card[i-1]-1)
      else:
        bunshi += left_card[i-1]*left_card[j-1]
    S = S[:4]
    T = T[:4]

print(bunshi/bumbo)

今回はアルゴリズムの力というか思い通りの処理を実装する力が問われるような問題でしたね(D問題までは).て,いうかAtCoderってすごく難しくてやっとD問題まで安定して解けるようになってきた~と思ったら次に待ち受けるのはDよりはるかに難しいE問題ですからね!?時間足りひん!!Eを解けるようになるにはA~Dを解き切る時間を超大幅に短縮する鍛錬を積むか,解けるはずのC,D問題を後回しにしてEに果敢に立ち向かうかしかないですね......

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

①NiceHashマイニング収益をAWS Lambda×SNSでメール通知する

目次

1. 背景

 日々メインPCとは別にマイニングリグを稼働させて、NiceHashでマイニングをしているのですが、残高確認する際にNiceHashのダッシュボードを都度確認するのは面倒なので日次ジョブとして通知したいと思ってました。
 業務ではコーディングや、AWSに触れる機会が一切ないので、勉強がてらAWS上でスマホへメール通知するシステムを構築してみました。
 最重要なマイニングリグのヘルスチェックエラー通知はMySettingsから設定できる 1 仕様なので、今回は収益情報のみをメールの通知対象としました。

2. 構成/構築手順

システム構成は、AWS Lambdaを中心とした基本的なサーバレスアーキテクチャです。

処理の流れ
 1. EventBridge(CroudWatch Event)の日次実行cronがトリガーとなり、Lambda関数をキック
 2. Lambdaでは、外部APIからマイニング収益情報を取得
 3. S3バケットへ残高情報を書き込み、前日の残高情報を取得、残高の増減を算出
 4. Amazon SNSへ値をpublishしてスマホへメールを通知
Qiita 掲載図①.png

2-1.Lambdaの構築

2-1-1.IAMロールの作成

AWSサービス間を連携するために新規IAMロールを作成し必要なポリシーをアタッチする
・IAMを起動し、ユースケースLambdaを選択し「次のステップ」をクリック
image.png
・S3バケットへ残高情報を読み書きするためにAmazonS3FullAccess、収益情報をメール通知するためにAmazonSNSFullAccessポリシーをロールにアタッチし「次のステップ」をクリック
image.png
・タグの追加は不要なので何も記入せず「次のステップ」をクリック
・ロール名は適当にNiceHash-Nortificationとして「ロールの作成」をクリック
image.png

2-1-2.Lambda関数の作成

呼び出されるLambda関数本体を作成する
・サービスからLambdaを起動し、以下のように入力し「関数の作成」をクリック

 関数名:「NiceHash-Nortification-Mail」
 ランタイム:「Python 3.6」#Python3系ならたぶんOK
 アクセス権限:「NiceHash-Nortification」#作成したIAMロール

image.png

2-1-3.ソースコードのデプロイ

Lambdaで実行するプログラムをデプロイする
・以下4つのpythonファイルを新規に作成してコードをデプロイ

NiceHash-Nortification-Mail
NiceHash-Nortification-Mail/
├ lambda_function.py
├ nicehash.py
├ marketrate.py
└ s3inout.py

Lambdaで呼び出されるメインプログラム

lambda_function.py
import json
import datetime
import boto3

import nicehash
import marketrate
import s3inout
#Function kicked by AWS Lambda
def lambda_handler(event, context):
    client = boto3.client('sns')
    #Amazon SNS
    TOPIC_ARN = 'arn:aws:sns:xx-xxxx-x:xxxxxxxxxx:NiceHashSNS' # SNSのARNを指定
    msg = create_message()
    subject = "NiceHash-Mining 日次収益通知"
    #Send a notification message to Amazon SNS
    response = client.publish(
        TopicArn = TOPIC_ARN,
        Message = msg,
        Subject = subject
    )
#Function to get a nortification message
def create_message():
    #NiceHash API    
    host = 'https://api2.nicehash.com'
    organisation_id = 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx' # hogehoge
    key = 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx' # API Key Code
    secret = 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx' # API Secret Key Code
    market='BTC'

    #S3 bucket
    bucket_name = '[bucket_name]'#hogehoge
    key_name = '[balance_filename]'#hogehoge

    #Get mining information from NiceHash API
    PrivateApi = nicehash.private_api(host, organisation_id, key, secret)
    accounts_info = PrivateApi.get_accounts_for_currency(market)
    balance_row = float(accounts_info['totalBalance'])

    #Get currency_to_JPY_rate from CoinGecko API
    TradeTable = marketrate.trade_table(market)
    rate = TradeTable.get_rate()
    balance_jpy = int(balance_row*rate)

    #S3 dealer
    S3dealer = s3inout.s3_dealer(bucket = bucket_name, key = key_name)
    pre_balance = int(S3dealer.read_from_s3_bucket())
    diff = balance_jpy - pre_balance
    S3dealer.write_to_s3_bucket(str(balance_jpy))

    #Nortification message
    time_text = "時刻: " + str(datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=9))))[:19]
    market_text = "仮想通貨: " + market
    rate_text = "単位仮想通貨価値: " + str(rate) + "円"
    balance_text = "現在の残高: " + str(balance_jpy) + "円"
    pre_balance_text = "昨日の残高: " + str(pre_balance) + "円"
    symbol = "+" if diff > 0 else ""
    diff_txt = "【日次収益: " + str(symbol) + str(diff) + "円】"
    mon_revenue = "推定月次収益: " + str(diff*30) + "円"
    ann_revenue = "推定年次収益: " + str(diff*365) + "円"
    msg = '\n'.join([time_text,market_text,rate_text,balance_text,pre_balance_text,diff_txt,mon_revenue,ann_revenue])
    return msg

NiceHash API (2-2で説明)

nicehash.py
from datetime import datetime
from time import mktime
import uuid
import hmac
import requests
import json
from hashlib import sha256
import optparse
import sys
class private_api:
  def __init__(self, host, organisation_id, key, secret, verbose=False):
      self.key = key
      self.secret = secret
      self.organisation_id = organisation_id
      self.host = host
      self.verbose = verbose
  def request(self, method, path, query, body):
      xtime = self.get_epoch_ms_from_now()
      xnonce = str(uuid.uuid4())
      message = bytearray(self.key, 'utf-8')
      message += bytearray('\x00', 'utf-8')
      message += bytearray(str(xtime), 'utf-8')
      message += bytearray('\x00', 'utf-8')
      message += bytearray(xnonce, 'utf-8')
      message += bytearray('\x00', 'utf-8')
      message += bytearray('\x00', 'utf-8')
      message += bytearray(self.organisation_id, 'utf-8')
      message += bytearray('\x00', 'utf-8')
      message += bytearray('\x00', 'utf-8')
      message += bytearray(method, 'utf-8')
      message += bytearray('\x00', 'utf-8')
      message += bytearray(path, 'utf-8')
      message += bytearray('\x00', 'utf-8')
      message += bytearray(query, 'utf-8')
      if body:
          body_json = json.dumps(body)
          message += bytearray('\x00', 'utf-8')
          message += bytearray(body_json, 'utf-8')
      digest = hmac.new(bytearray(self.secret, 'utf-8'), message, sha256).hexdigest()
      xauth = self.key + ":" + digest
      headers = {
          'X-Time': str(xtime),
          'X-Nonce': xnonce,
          'X-Auth': xauth,
          'Content-Type': 'application/json',
          'X-Organization-Id': self.organisation_id,
          'X-Request-Id': str(uuid.uuid4())
      }
      s = requests.Session()
      s.headers = headers
      url = self.host + path
      if query:
          url += '?' + query
      if self.verbose:
          print(method, url)
      if body:
          response = s.request(method, url, data=body_json)
      else:
          response = s.request(method, url)
      if response.status_code == 200:
          return response.json()
      elif response.content:
          raise Exception(str(response.status_code) + ": " + response.reason + ": " + str(response.content))
      else:
          raise Exception(str(response.status_code) + ": " + response.reason)

  def get_epoch_ms_from_now(self):
      now = datetime.now()
      now_ec_since_epoch = mktime(now.timetuple()) + now.microsecond / 1000000.0
      return int(now_ec_since_epoch * 1000)

  def algo_settings_from_response(self, algorithm, algo_response):
      algo_setting = None

      for item in algo_response['miningAlgorithms']:
          if item['algorithm'] == algorithm:
              algo_setting = item
      if algo_setting is None:
          raise Exception('Settings for algorithm not found in algo_response parameter')
      return algo_setting

  def get_accounts(self):
      return self.request('GET', '/main/api/v2/accounting/accounts2/', '', None)

  def get_accounts_for_currency(self, currency):
      return self.request('GET', '/main/api/v2/accounting/account2/' + currency, '', None)

  def get_withdrawal_addresses(self, currency, size, page):
      params = "currency={}&size={}&page={}".format(currency, size, page)
      return self.request('GET', '/main/api/v2/accounting/withdrawalAddresses/', params, None)

  def get_withdrawal_types(self):
      return self.request('GET', '/main/api/v2/accounting/withdrawalAddresses/types/', '', None)

  def withdraw_request(self, address_id, amount, currency):
      withdraw_data = {
          "withdrawalAddressId": address_id,
          "amount": amount,
          "currency": currency
      }
      return self.request('POST', '/main/api/v2/accounting/withdrawal/', '', withdraw_data)

  def get_my_active_orders(self, algorithm, market, limit):
      ts = self.get_epoch_ms_from_now()
      params = "algorithm={}&market={}&ts={}&limit={}&op=LT".format(algorithm, market, ts, limit)
      return self.request('GET', '/main/api/v2/hashpower/myOrders', params, None)

  def create_pool(self, name, algorithm, pool_host, pool_port, username, password):
      pool_data = {
          "name": name,
          "algorithm": algorithm,
          "stratumHostname": pool_host,
          "stratumPort": pool_port,
          "username": username,
          "password": password
      }
      return self.request('POST', '/main/api/v2/pool/', '', pool_data)

  def delete_pool(self, pool_id):
      return self.request('DELETE', '/main/api/v2/pool/' + pool_id, '', None)

  def get_my_pools(self, page, size):
      return self.request('GET', '/main/api/v2/pools/', '', None)

  def get_hashpower_orderbook(self, algorithm):
      return self.request('GET', '/main/api/v2/hashpower/orderBook/', 'algorithm=' + algorithm, None )

  def create_hashpower_order(self, market, type, algorithm, price, limit, amount, pool_id, algo_response):
      algo_setting = self.algo_settings_from_response(algorithm, algo_response)
      order_data = {
          "market": market,
          "algorithm": algorithm,
          "amount": amount,
          "price": price,
          "limit": limit,
          "poolId": pool_id,
          "type": type,
          "marketFactor": algo_setting['marketFactor'],
          "displayMarketFactor": algo_setting['displayMarketFactor']
      }
      return self.request('POST', '/main/api/v2/hashpower/order/', '', order_data)

  def cancel_hashpower_order(self, order_id):
      return self.request('DELETE', '/main/api/v2/hashpower/order/' + order_id, '', None)

  def refill_hashpower_order(self, order_id, amount):
      refill_data = {
          "amount": amount
      }
      return self.request('POST', '/main/api/v2/hashpower/order/' + order_id + '/refill/', '', refill_data)

  def set_price_hashpower_order(self, order_id, price, algorithm, algo_response):
      algo_setting = self.algo_settings_from_response(algorithm, algo_response)
      price_data = {
          "price": price,
          "marketFactor": algo_setting['marketFactor'],
          "displayMarketFactor": algo_setting['displayMarketFactor']
      }
      return self.request('POST', '/main/api/v2/hashpower/order/' + order_id + '/updatePriceAndLimit/', '',
                          price_data)

  def set_limit_hashpower_order(self, order_id, limit, algorithm, algo_response):
      algo_setting = self.algo_settings_from_response(algorithm, algo_response)
      limit_data = {
          "limit": limit,
          "marketFactor": algo_setting['marketFactor'],
          "displayMarketFactor": algo_setting['displayMarketFactor']
      }
      return self.request('POST', '/main/api/v2/hashpower/order/' + order_id + '/updatePriceAndLimit/', '',
                          limit_data)

  def set_price_and_limit_hashpower_order(self, order_id, price, limit, algorithm, algo_response):
      algo_setting = self.algo_settings_from_response(algorithm, algo_response)
      price_data = {
          "price": price,
          "limit": limit,
          "marketFactor": algo_setting['marketFactor'],
          "displayMarketFactor": algo_setting['displayMarketFactor']
      }
      return self.request('POST', '/main/api/v2/hashpower/order/' + order_id + '/updatePriceAndLimit/', '',
                          price_data)

  def get_my_exchange_orders(self, market):
      return self.request('GET', '/exchange/api/v2/myOrders', 'market=' + market, None)

  def get_my_exchange_trades(self, market):
      return self.request('GET','/exchange/api/v2/myTrades', 'market=' + market, None)

  def create_exchange_limit_order(self, market, side, quantity, price):
      query = "market={}&side={}&type=limit&quantity={}&price={}".format(market, side, quantity, price)
      return self.request('POST', '/exchange/api/v2/order', query, None)

  def create_exchange_buy_market_order(self, market, quantity):
      query = "market={}&side=buy&type=market&secQuantity={}".format(market, quantity)
      return self.request('POST', '/exchange/api/v2/order', query, None)

  def create_exchange_sell_market_order(self, market, quantity):
      query = "market={}&side=sell&type=market&quantity={}".format(market, quantity)
      return self.request('POST', '/exchange/api/v2/order', query, None)

  def cancel_exchange_order(self, market, order_id):
      query = "market={}&orderId={}".format(market, order_id)
      return self.request('DELETE', '/exchange/api/v2/order', query, None)

if __name__ == "__main__":
 parser = optparse.OptionParser()
 parser.add_option('-b', '--base_url', dest="base", help="Api base url", default="https://api2.nicehash.com")
 parser.add_option('-o', '--organization_id', dest="org", help="Organization id")
 parser.add_option('-k', '--key', dest="key", help="Api key")
 parser.add_option('-s', '--secret', dest="secret", help="Secret for api key")
 parser.add_option('-m', '--method', dest="method", help="Method for request", default="GET")
 parser.add_option('-p', '--path', dest="path", help="Path for request", default="/")
 parser.add_option('-q', '--params', dest="params", help="Parameters for request")
 parser.add_option('-d', '--body', dest="body", help="Body for request")
 options, args = parser.parse_args()
 private_api = private_api(options.base, options.org, options.key, options.secret)
 params = ''
 if options.params is not None:
     params = options.params
 try:
     response = private_api.request(options.method, options.path, params, options.body)
 except Exception as ex:
     print("Unexpected error:", ex)
     exit(1)
 print(response)
 exit(0)

CoinGecko API (2-2で説明)

marketrate.py
import requests
import json
class trade_table:
    def __init__(self, market="BTC"):
        #currency-name conversion table
        self.currency_rename_table = {'BTC':'Bitcoin','ETH':'Ethereum','LTC':'Litecoin',
                                      'XRP':'XRP','RVN':'Ravencoin','MATIC':'Polygon',
                                      'BCH':'Bitcoin Cash','XLM':'Stellar','XMR':'Monero','DASH':'Dash'}
        self.market = self.currency_rename_table[market]

    def get_rate(self):
        body = requests.get('https://api.coingecko.com/api/v3/coins/markets?vs_currency=jpy')
        coingecko = json.loads(body.text)
        idx = 0
        while coingecko[idx]['name'] != self.market:
            idx += 1
            #Escape of illegal market_currency name
            if idx > 100:
                return "trade_table_err"
        #market-currency_to_JPY_rate
        else:
            return int(coingecko[idx]['current_price'])

S3バケットへの収益情報の読み込み・書き出し(2-3で説明)

s3inout.py
import boto3
class s3_dealer:
  def __init__(self, bucket = 'nice-hash-balance', key = 'balance_latest.txt'):
    self.bucket = bucket
    self.key = key

  #Get balance of the previous day
  def read_from_s3_bucket(self):
    S3 = boto3.client('s3')
    res = S3.get_object(Bucket=self.bucket, Key=self.key)
    body = res['Body'].read()
    return body.decode('utf-8')

  #Export balance
  def write_to_s3_bucket(self, balance):
    S3 = boto3.resource('s3')
    obj = S3.Object(self.bucket, self.key)
    obj.put(Body=balance)

2-1-4.レイヤー作成

必要なモジュールをLambdaのレイヤーに取り込む
2-1-3 記載のソースをデプロイしただけで実行するとrequestsモジュールが読み込めず以下エラーが発生してしまうため、外部モジュールをLayersへ定義する

{
  "errorMessage": "Unable to import module 'lambda_function': No module named 'requests'",
  "errorType": "Runtime.ImportModuleError"
}

・レイヤーファイルを作成するために、EC2でAmazon Linux AMIから新規インスタンスを作成する
2-1-1の手順で、EC2のロールに対してS3のアクセスポリシーをアタッチ
 ※インターネット環境に接続されたWSLやUbuntu等のUNIXマシンであれば何でもOK
・EC2インスタンスへコンソール接続し、以下CLIコマンドを打鍵してレイヤーファイルを作成する

ec2-user
[ec2-user@ip-xxx-xx-xx-xxx ~]$ su -
[root@ip-xxx-xx-xx-xxx ~]# mkdir layer/
[root@ip-xxx-xx-xx-xxx ~]# cd layer
[root@ip-xxx-xx-xx-xxx ~]# yum -y install gcc gcc-c++ kernel-devel python-devel libxslt-devel libffi-devel openssl-devel
[root@ip-xxx-xx-xx-xxx ~]# yum -y install python-pip
[root@ip-xxx-xx-xx-xxx ~]# pip install -t ./ requests
[root@ip-xxx-xx-xx-xxx ~]# cd ../
[root@ip-xxx-xx-xx-xxx ~]# zip -r Layer.zip layer/

・レイヤーファイルを、S3バケットへアップロード

ec2-user
[root@ip-xxx-xx-xx-xxx ~]# chmod 777 Layer.zip
[root@ip-xxx-xx-xx-xxx ~]# aws s3 cp Layer.zip s3://layerzip-s3

・S3でEC2からアップロードしたレイヤーファイルのオブジェクトURLを取得
image.png

・LambdaでS3のオブジェクトURLから名前を適当にImportRequestsとしてレイヤーを作成
image.png

・Lambdaで「レイヤーの追加」をクリック
image.png

・カスタムレイヤーから作成したImportRequestsを読み込む
image.png

2-1-5.タイムアウト値の延長

タイムアウトエラーを回避するためにタイムアウト値を変更する
・Lambdaはデフォルトだと、メモリ:128MB、タイムアウト:3秒になっているため、タイムアウトのみ「3秒5秒」へ変更する
image.png

2-2.APIによる収益情報取得

外部APIからマイニング収益情報を取得する
・LambdaとNiceHash APIを連携するために、NiceHashへログインしてMySettingsからAPI Keysを発行する
image.png
NiceHash API(nicehash.py)で収益情報を取得するために、lambda_function.pyの対象箇所に発行したAPI Keys、組織IDを入力する

lambda_function.py
#NiceHash API    
host = 'https://api2.nicehash.com'
organisation_id = 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx' # hogehoge
key = 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx' # API Key Code
secret = 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx' # API Secret Key Code

・NiceHash APIのみでは、円相場の情報は取得できないため、別の外部API CoinGecko API(marketrate.py)を呼び出して、各仮想通貨市場の日本円相場を取得する。

2-3.EventBridgeによるトリガー定義

日次ジョブとしてLambdaをキックするためのトリガーを定義する
・Lambdaから「トリガーの追加」をクリック
・EventBridgeを選択し、以下のように入力し「追加」をクリック
 ※AWSでcronを定義する際は、crontabとの違いや時差を考慮する必要があるため注意する 2

ルール:「新規ルールの作成」
ルール名:DailyTrigger
ルールタイプ:スケジュール式
スケジュール式:cron(0 15 * * ? *) # 毎日0:00に実行するcron

image.png

2-4.S3バケットへの書き出し・読み込み

前日の収益との比較を行うため、S3バケットのファイルに対して書き出し・読み込みを行う
・S3バケットbucket_nameを作成して、前日の残高(円)を整数で記載したダミーファイルbalance_filename.csvを予め格納しておく

bucket_name/balance_filename.csv
28583

・LambdaとS3のサービス間で連携するために、lambda_function.pyの対象箇所を編集する

lambda_function.py
#NiceHash API    
#S3 bucket
bucket_name = '[bucket_name]' # S3バケット名
key_name = '[balance_filename.csv]' # 残高情報が記載されたファイル名

2-5.Amazon SNSによるメール通知

取得した収益情報をメール通知するために、「SNSとメール」「LambdaとSNS」の連携を行う
・Amazon SNSを起動し、適当にNiceHashSNSとしてトピックを作成する
・作成したトピックNiceHashSNSのARNをコピーする
image.png
・「SNSとメール」を連携するために、作成したトピックNiceHashSNSを開き、「サブスクリプションの作成」をクリック
image.png
・プロトコル:Eメール、エンドポイント:通知したいメールアドレスを指定し、「サブスクリプションの作成」をクリック
・サブスクリプションを作成すると、指定したメールアドレスに通知メールが届くので、挿入されたリンクへアクセスしサブスクリプションのアクティベーションを行う
・「LambdaとSNS」を連携するために、lambda_function.pyの対象箇所を編集する

lambda_function.py
#Amazon SNS
TOPIC_ARN = 'arn:aws:sns:xx-xxxx-x:xxxxxxxxxx:NiceHashSNS' # コピーしたSNSのARNを記載

3. 実行結果

・毎日0:00になるとEventBridgeがLambdaをキックして、日次の通知メールが来るようになりました。
IMG_1134.PNG

4. 終わりに

・APIで情報取得している割に、通知はメールというのは構成的にビミョかったので、この後にLINE APIを活用してLINE通知する構成に変更しました。次回は、LINE通知する構成について投稿予定です。

5. 更新履歴

ver. 1.0 初版投稿 2021/02/28

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

組合せ最適化問題

https://www.msi.co.jp/nuopt/docs/v19/examples/html/02-05-00.html
をPythonを使って解いてみた。
A,B,C,D,Eはそれぞれ缶コーヒー、水入りペットボトル、バナナ、りんご、おにぎり、パン

!pip install pulp
import pulp

数理モデルの設定(価値を最大化)

m = pulp.LpProblem(sense=pulp.LpMaximize)

変数の設定

A = pulp.LpVariable('A', lowBound = 0,upBound=None, cat='Integer')
B = pulp.LpVariable('B', lowBound = 0,upBound=None, cat='Integer')
C = pulp.LpVariable('C', lowBound = 0,upBound=None, cat='Integer')
D = pulp.LpVariable('D', lowBound = 0,upBound=None, cat='Integer')
E = pulp.LpVariable('E', lowBound = 0,upBound=None, cat='Integer')
F = pulp.LpVariable('F', lowBound = 0,upBound=None, cat='Integer')

目的関数の設定

m += (120 * A) + (130 * B) + (80 * C) + (100 * D) + (250 * E) + (185 * F)

制約条件

m += (10 * A) + (12 * B) + (7 * C) + (9 * D) + (21 * E) + (16 * F) <= 65

問題を解く

status = m.solve()
print("Status", pulp.LpStatus[status])

print("缶コーヒー", A.value())
print("水入りペットボトル", B.value())
print("バナナ", C.value())
print("りんご", D.value())
print("おにぎり", E.value())
print("パン", F.value())

print("合計", m.objective.value())

缶コーヒー 3.0
水入りペットボトル 0.0
バナナ 2.0
りんご 0.0
おにぎり 1.0
パン 0.0
合計 770.0

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

TensorFlow2 + Keras で画像分類に挑戦 CNN編2 ~畳み込み処理を視覚的に理解する~

はじめに

TensorFlow2 + Keras による画像分類の勉強メモ(CNN編の第2弾)です。MLP編(多層パーセプトロンモデル編)については、こちら をご覧ください。

前回は「とりあえず動かす」ということで、MNIST を対象に TF/Keras でシンプルなCNN(畳み込みニューラルネットワークモデル)を定義し、学習と評価を行ないました。

CNNモデルの定義(TF/Keras)
model = tf.keras.models.Sequential()
model.add( tf.keras.layers.Reshape((28, 28, 1), input_shape=(28, 28)) )
model.add( tf.keras.layers.Conv2D(32, kernel_size=(3,3), activation='relu') ) # 畳み込み層
model.add( tf.keras.layers.MaxPooling2D(pool_size=(2,2)) )
model.add( tf.keras.layers.Flatten() )
model.add( tf.keras.layers.Dense(128, activation='relu') )
model.add( tf.keras.layers.Dropout(0.2) )
model.add( tf.keras.layers.Dense(10, activation='softmax') )

今回は、畳み込み処理(Convolution)とは、何なのか?どんなことをしているのか?について、図とコードと使ってできるだけ分かりやすく解説していきたいと思います。

畳み込み処理(Convolution)

CNNの最大の特徴は、畳み込み層が存在することです。TF/Keras で定義するモデル的には tf.keras.layers.Conv2D(32, kernel_size=(3,3), activation='relu') のような「畳み込み層」が含まれていると、CNNモデルと言えます

この畳み込み層では「畳み込みフィルタの適用(フィルタの畳み込み処理)」が行なわれています。この「フィルタの適用」とは gimp や フォトショップ などの画像処理ソフトでのフィルタ処理(ぼかし処理、モザイク処理、シャープ化など)と同じことです。

(やや正確性を欠きますが)言葉で表現すれば畳み込みフィルタの適用とは「入力画像を構成している各ピクセルについて、適当な重みを付けて周囲のピクセル情報を取り込んで出力画像を作成すること」なります。

具体的には、Conv2D 層の通過によって、次のようにデータが変化します。

フィルタ.png

上記の例では、フィルタの要素値(=重み)を「すべて $0.11$ 」のように、こちら側で用意して与えています。しかし、CNNではフィルタの要素値(=重み)は学習を通じて最適化・決定していきます。

Conv2D(...) で任意フィルタで畳み込む

単にフィルタを畳み込むだけなら OpenCV でやったほうが早いのですが、ここでは TF/Keras で CNNモデルを作成するときに使用する tf.keras.layers.Conv2D(...) で実行してみたいと思います。

例として、ここでは MNIST の手書き文字画像に、3種のフィルタを畳み込んでみたいと思います。tf.keras.layers.Conv2D(...) の単層で構成されるモデルを作成し、model.set_weights(...) でフィルタ(=重み)をセットして、y=model(x) で結果を取得します。

任意のフィルタを畳み込む
%tensorflow_version 2.x
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt

# (0)準備:画像表示関数
def show_image(img,title=None):
  plt.figure(figsize=(2,2),dpi=120)
  plt.gcf().patch.set_facecolor('white')
  plt.xticks([])
  plt.yticks([])
  plt.title(title,fontsize=15)
  plt.imshow(img,cmap='Greys')
  plt.show()
  plt.close()

# (1) 入力データ x の準備
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train/255.0, x_test/255.0
x = x_train[0]  # 訓練用データの0番目の画像を使用
show_image(x,title='Input x')

# (2) モデルに入力できる形に整形
x_size = x.shape
x = x.reshape([1,x_size[0],x_size[1],1]) # shape:(28, 28)=>(1,28,28,1)
x = x.astype(np.float32)

# (3) サイズ 3x3 のフィルタ w を作成
a = np.array([[1,1,1],[1,1,1],[1,1,1]])/9 # 移動平均フィルタ
#a = np.array([[1,2,1],[0,0,0],[-1,-2,-1]]) # ソーベルフィルタ(横方向検出)
#a = np.array([[1,0,-1],[2,0,-2],[1,0,-1]]) # ソーベルフィルタ(縦方向検出)

a = a.astype(np.float32)
a = a.reshape([3,3,1,1]) # shape:(3, 3) => (3,3,1,1)
w = [a]

# (4) フィルタを畳み込むモデルを構築
model = tf.keras.models.Sequential()
model.add( tf.keras.layers.Conv2D(filters=1,
                                  kernel_size=(3,3),
                                  use_bias=False,                                  
                                  padding='same',
                                  activation='relu',
                                  input_shape=(28,28,1)))

# (5) フィルタ w をモデルにセット 
model.set_weights(w)

# (6) 畳み込み処理の実行
y = model(x)
y = y.numpy().reshape(y.numpy().shape[1:3]) # shape:(1,28,28,1)=>(28,28)
show_image(y,title='Output y')

フィルタに関する部分のコメントアウトを切り替えながら実行すると、次のような結果が得られます。

平均化フィルタ
f1.png
ソーベルフィルタ(横方向検出)
f2.png
ソーベルフィルタ(縦方向検出)
f3.png

カーネルサイズ

フィルタが、どのぐらい周辺のピクセル情報までを取り込むかという縦横の範囲をカーネルサイズ(フィルタサイズ)といいます。一般には「$3\times 3$」や「$5\times 5$」のような奇数値が使われます。tf.keras.layers.Conv2D(32, kernel_size=(3,3), activation='relu')(3, 3) がカーネルサイズの指定になります。

カーネルサイズが $3\times 3$ ということは、自身を含め周囲 $9$ マスの情報の重み付き和をフィルタ適用後の値とすることを意味します。カーネルサイズ $5\times 5$ では自身を含めて周囲 $25$ マスの情報の重み付き和で適用後の値が決まります。

kernel_size.png

カーネル値

このカーネル(行列)の各要素には、畳み込み処理のための「重み」が格納されます。どのような値を入れるかによって、そのフィルタが、ぼかし効果を与えるものになったり、エッジ強調をするものになったり変化します。

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

今日出会ったとても大きな自然数 ー 最小のペラン擬素数とそれに対するペラン数を求める ー

1.経緯

数学のお勉強中にとても大きな自然数に出会ったので、これを算出するコードと結果をメモしておきます。

ペラン数の定義と性質

以下、Wikipedia ペラン数より引用

ペラン数(英: Perrin number)とは、以下の漸化式で定義される数である。

$P(0) = 3, P(1) = 0, P(2) = 2$

$P(n) = P(n-2) + P(n-3)  n > 2$
  

(・・・中略・・・)

素数、整除性

$P(n)$ が $n$ で割り切れるような $n$ を列挙すると以下のようになる。

$n = 1, 2, 3, 5, 7, 11, 13, ...$

$1$の後にはしばらく素数が続いている。全ての素数 $p$ に対して、 $P(p)$ が $p$ で割り切れることが証明されている。しかし、その逆は成り立たない。すなわち、$P(n)$ が $n$ で割り切れるような合成数 $n$ が存在する。このような $n$ をペラン擬素数(Perrin pseudoprime)と呼ぶ。「ペラン擬素数は存在するか」という疑問はPerrin自身も考察しており、後にAdamsとShanksが最小のペラン擬素数 $271441 = 521^2$ を発見し肯定的に解決された。

上記にある、最小のペラン擬素数が $271441$ であることを確認し、かつ、$P(271441)$ を求めるコードをPythonで書いてみたら、$P(271441)$ が思いのほか大きな数だったので、これをメモしておくことにした。

2.ペラン数列を作る関数

Pythonでざっくり書いてみた。

def perrin_numbers(n=100):
    """与えられた整数 n に対して、n より小さいペラン数を漸化式による生成順のリストで返す。

    - n のデフォルト値は100とする。
    - はじめの3項は、3, 0, 2 である。
    - n が整数ではない場合、TypeError例外が発生する。
    - n が3以下の場合、ValueError例外が発生する。
    """
    if not type(n) is int :
        raise TypeError('引数は整数でなければなりません。')
    if n <= 3:
        raise ValueError('引数は4以上でなければなりません。')

    # 初期設定
    a, b, c = 3, 0, 2  # はじめの3項    
    result = []  # 数列を格納するリスト

    # result にペラン数を詰めていくループ
    while a < n:
        result.append(a)
        a, b, c = b, c, a + b

    # 作成したリストを返す
    return result

上記のコードの中で、はじめの3項を入れておくa, b, c = 3, 0, 2 のところは、フィボナッチ数列だと、2項でよく a, b = 0, 1 で、 a, b, c = b, c, a + b のところは a, b = b, a + b だった。(ちなみに、フィボナッチ数列を作るときのa, b = b, a + b はPythonチュートリアルにも載っている。

上記を引数なしで実行してみると、100未満のペラン数のリストが返される。

In [100]: perrin_numbers()
Out[100]: [3, 0, 2, 3, 2, 5, 5, 7, 10, 12, 17, 22, 29, 39, 51, 68, 90]

先のWikipediaの説明に記載されていた、オンライン整数列大辞典の数列A001608 にいくと、漸化式による生成順にペラン数のいくつかが並べられており、上記のプログラムの出力が正しいことを確認できる。

3.素数の判定プログラム

素数の判定プログラムは随所でサンプルを入手できるので、とり急ぎ以下のようなもので間に合わせる。

def is_prime(n):
    """与えられた整数nが素数ならばTrue を返す。

    n が整数ではない: TypeError
    n が負: ValueError
    n が0または1の場合: False
    n が2以上の場合: n が素数ならばTrue 、それ以外ではFalse
    """

    if not type(n) is int:
         raise TypeError('引数は非負の整数でなければなりません。')
    if n < 0:
        raise ValueError('引数は非負の整数でなければなりません。')

    if n <= 1:
        return False

    for m in range(2, n):
        if n % m == 0:
            return False

    return True

確認として

for n in range(100):
    if is_prime(n):
        print(n, end=', ')

で試すと、 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, と出力された。

4.最小のペラン擬素数を求めるコード

さて、本題。最小のペラン擬素数を求めるコード:

N = 10 ** 35000

for n, p in enumerate(perrin_numbers(N)):        
    if n <= 1:
        continue

    if (not is_prime(n)) and p % n == 0:
        print('P({}) = {}'.format(n, p))
        break

上記のコード中、 N = 10 ** 35000 というのは、上述のperrin_numbers() に与える引数で、これぐらいにしておくと、生成されるペラン数列の長さが30万弱になり、$P(271441)$を得ることができる。

実行すると、以下が出力された。なお、結果が出るまでに、体感5分ぐらい待たされた。(iMac Core i5 3,4GHz メモリ24GB を使用)

P(271441) = 201457073802614865627084800584613290061498293215532795108637680542039636796914828821643988874649933788778325016035694088943830527563059976814857098785526005838224453673408107150271385574892254428798457737927479633840816839617860087365802432258566514570408418254960463248818324755529371025575468741262166875462013650030842844060246410092716180254852666548433552507712800186963818835006644823850064977752394853016254014341776772499516478297332919332970838857704265391168090363868351777924109704351927355388729565633766598690787886714990928837199314570480130473063930790646073066885473741392552943198198279806335289710679049948629385324504945676758656726271787313145908280161956464399207139083896430852486949362648455666007040470715407225585610541063599234495540373565140428919333123532602175059038230906355901868390758986124141368821926492211066985381552959964859652310220695672323827850815455445463128743236848512199922170531419225569149762447676716133808485920309126050212553154429309503264230429132377432757226314122037525735626414460174163657853293725940305892638571101066366363438719304473954631895083375943436272604459481183504070431731243351364604420644585511431610594632106850503483141563243432208426754493159053527726911610783689656443628469145657362183952332750258269379436770776810819821781203929234511131018520031599337976629373855144396413897909819257709009359807694799332817773985737039458752125486507359012164940026779663068239401304431788449657341397097107110510746079071397434161903361596497655252550684984128051433244252270792128208155269718495863321998152487927727932363102707698707368154480875582347697065389247583964289632524054671441957402828735444951782054600399689216535232246568763304878838441427036456761435549014109197120308537632479049577261515817032635760743416099032969644210751286305815805632816059492089661543045620720667567663466151932748239937059283057247928900635890029694148729329728900031337080942454081944233528911206122215465205485863813916275143559390968236782183169665536330465139830093378021220146481314455903733552711378098573355615853310544130698819429108466858327140456536632634400063987562763236273629080261151572808860478681697100857037379790331945776857718509025630901345989008693554336696442572474779569594877707961394518975488307517005744702936429610870231267983079227238482213516762674511880576307102946001639532034916792601958402132812660449144431626255251783245840935840578125294755781982611947500558528956805664597001392867116350528600811559971581245482423816900226347905697712520262940917036486094843084208414554001302974184802648159934660328810907114760982787555462310129115645985455560853250207673704394458635057943670259591159755635010790200050874486447705089124399203021755760724634998299470992175919418286629079520576475470515788687279409267222311779449259358289220205826586078628842623835013215821473683813143590697906309317305954483891497787220673121064731237993085364771987083190589194422044153648678285699194710655511045589385974461792190857460839782959703787263163733463959765582923265171121802136092930957251108064452151404040236532160463257618126503482199628987737074746324055835605696135758658055678138805898198434504924164530845523810468243850109378398401098476739805871823493797548873472971563983327732114060882337952969109235158094800665097356307418557467243268504927762756932059684427801056724819208589024577675187033870296153207432933015791001332565772796098578921202651190483247586553420672206270087970719080187305070010743909176303167692537823846972783278014467446486696864528051425485529119306226508511018196767947275674053463679282419237275540488983226115159046323648221586988142012781225555942504229374951967343026446309508898271352871652932954661725996811623229287737161751590469604184617021143175713265997439955569964989349727476126023906691339346025951796476280181365591186908386384485109355507353940034957057574802404797755116500006701024628072879291780102380935566167504406591493278614192170849627210511952403178246183077014468930853521025833538922675559433679218512791280337994345140482846204425035740520094999804249508914638513255911134768288053497477934083419806634658207481073099812680881134115303388089609836492705444142292710028166849282884248481901463108496407885913920443277854395758974307407274991138630626725480473122162936968204383319816058481349189644449586546070989949160276726841034318590241447007204604680766988618405526930040540876377326888835538842515490394340478922751365557842390248352542841549144201988389372698002238655588323681874366351611849548833239986194432881020067592228573031717757846727703133845662631372674608802847688604127125409021398234804480381079673005464722990387429932155777030202516079664830182179516976666572392523165854516214715004368850991371901042497753736991267528236674155216662593277054553964612125631468188454994068049552380887559280516204791552713851216937857827543386637981944163425102008243072344380863977671488281693864589070450063008153048394216373228772346290283305905659153599399228507997041896614414136975415914057188697434602430008676829507816634618216149130572730713455565010215146262952458965117531407849870341566617793463546182971894642810317345878219098955990056585703983144266506971893619246939081624150091688666073550932507510578433180599454695570034769566674636224492997592867727033037865858640172642706057761099838505731848183158335160719354826180779752018363555629600912461502269706194485041549376460513286671613029707287443567415053678932879482228995712571714384433705634232732880185329236925956547311709036952937282531610823210331229732881184532479141923207878384876628052976375897508848153287268005014332063670818443775714538535518674813001638224300681992850155386432758677824577484903455058486345808667231329595492372001618109739740726666333801332614438724019615732304946188413463880287667662920466897333387850498838727990458153535790066152166491727676433371011661625342104452066305975492301838120240501211007021981155827267266372976068331209398795541971982832414959689584009117353649871993771650895518827602443711425163130430047750664209091706667105648738358175314850884940193036090622503031408258366770596894606824791423126387125251421534845317993396858459457383052805838806544771162988010315902264132608886569175535047524324702847533290256205626579490242267970297425722400778746016670696130996873911278531396227099923473295249418315766559361928180613299004611988248655458162617748887662999465785605664082614555038863721851360801596959085657585125772229554320682598723177097347905785230861321181701525564702559479006354996775153631613725249359977091943955037668113225050540001132245084838828921365355395528861831881890728304955262955524520572866627984300553604451602741814801163887204299642755918885350282132524028939527612192932502574695873192851258922183599811377576047975492047493896435033413062622521536888824839533333793219933330550554936933934483146869880411298234125385760844027590867516719977219112775878154214786199694385818304288803614487803293617595943929583942322966798408194543651280618888351993142972648983138635967975663765013137286226310228259062424453545181264448103808158191107579567681913814925349004388616732361985475086230273960891014085711705806055451999931686477062769448928962294483162587157170405226539069346227141744514604625869506980865251292429469664881299116034511465909480620244600086044053410843457475069671814836382426517618494434469087328777075910932276906418644967115634886059963429713470081273465728356211869375057604077755541623434788781398998416122580775700451239752120470330002916720886395829821827065746993509811088193144755122654879460043187668794957114660385080276307782523807300423864064591681282857217409961106447011428282775775526075932417133265382880487242117012908870867106628960186127846348904231454574943268604219728826467266252925233146892760482762284712446849626218313947633946391999638291045934856071474145377455833438200746455748108112384479511407124934458665663812633383281334611748510421862815988811358405361278041859055607086114558043631850181791696207055798530243304838835921503000759476753033701367068403565920477944111978642224496393205738190170075525787956678580182581642554226758219063732451125323844833101678158731512706818405574277400746020140447398211537936534862875571350259038147639113040042465405758453769298022866238519855147937663328639601416089363722117572343567848792235692621419706712844297128557442089398126785221366964743818926407391971391354840898864251496047807256019810631777194873698054296236135538487712778286423649499375347165531462798898272568068126526565172911262805686020596072307120355092509382555611380634295975355244410206790080267723227338971784019353177407116612312937543330575660017579214501437646356166265071459224084343008915575908529424450851541816367914742643286298819435643659231844807036293818613420377068331543766290550645869715290122160776347985917562439571220030847931460853948833579723760565373767747364804534010342839662058142566841060259962188384824702745705586785809430556825179489011555008634890618578689744837244121402104302992172656367778681855390049998553953303307072941248802050498900787183313314845003670718897369982170701992577635554726667419131130138547223594536462076316519830821485000330261315592048685649412534633746353693797124166767719198763847125729787548784983513555503883968819510655017689272824743424111710104930488508033703580491452903772277513210862135592388171764401419068732892877763850233239227370057798716895603547628055117300500736700516107604270701944143040816390708837657381378450338091293328429869619240734564031949840283262855171714444946535476240183602832938511787601264884788570238133828146663318201502614647289615399057936417781005163951260448761050313320321503401956211546572410532188463210563055645045038678894414970584128469935702081686923057071655146722137982275734743445118591181580231140946028371173553225865116171479528048745781417145015089712275977851163966235782480673233019551914816262522314260529687147833311678750751878450202770267150936635580837463795123043298356205361558596778652597506362909919679426050488383791002181465189950888898437494124536248717424394554295168266258547453595648488246273133510884130660082307390321539611538885521743035594835575047927680209721090428408204020976411154690398538097470665139302262932277896760350292705603686741986269156191958619791891187538377129894817894055084534637296242063114967478206236150378487142604730385706439340625582289130727258736456108905138027014964914189354724485145242699644408413090345325622607421365666982045080364900764632928906659692663695256766325198059900497163578818585010649230427507187754132317902447202019261705644936414486179853192492939040545125560617620383920500083745686207176871479209771272058307641148743039899020731546223799910013403242556437800449268854086235417070118222553078581649480572543554873279954734433706886626618842288851603912607730877657076015234633962206236042716613275426815393457167310946124021383168257535692725654625296692529922080707741730198424240711627444088225982741332263147539296853097930949253421617843282151805545596092292008253884438035971368586251283095647310862417459981018912396618954178415743282450140201803518253779170167712796775252055400088319409516888919285411280091335985233633869359711912349900217654145992997804916563262822940344971393442546996118162295707239194427484129922298611572912742809662563820128055153867894328509224685804070987376606986995790708041997869695366449375507569639673254844839398809515520277378464434792295850650771390171218407933515731318775286472056013452907899793532194790414246771780367005596203973745864172768191707087720986559987504958518374311283337503688482244252534298726121949687997393254106827650675170275340701506465013706677792160303818141330598803972933394078969469246700666758145449565901379004666596316828077922213396419387962274243359138990574858922428436287013814356464159522687839616309826262155176927832182527442424408338533202629062933809202826918826184315731891132639025955265949273265126692551893980623401863247433382286906860961145133832008112321061239456117368531060636047731290591460848793601566379149292469477910986676374653560370522947481183482521721663791665324118314094917900050876290950702395166182938513978956264012722742146951967558371186759162546362234882884233441042818739009110573266818731909374447721552377444916140040640156700604967759581543165335989966639867602793098834774035539768533313919828459913257602158171629572492538799068573896720997307398399276343825342923922237519716752169864086614389238849041151180457988955412649948274441455815889835744912669490243601617764090250145402035835578781883042222988754174423257484600230067351014489350675898437909737905332745454353621879931762812484563985419762323329713531906641781046215446164639801516221299889255997572117946748953638942188914531241108643081080066667519356662367257593632817706768889454227618813968740719600907925470032241490959805972777688964861915598070088814998783008053426475541100720408404194148279990961933172782840181496441790138942663765140264226673569813386198615017347652460607026762608567707159318331263306109200656909928629351079777933935037101394339182567894888565493945934838223840080610012214007314536180163880918237938594919830742445477687206213144697960224389005903533813528441239450908300533180025572795513947823423301856709895528904371729273377191295238939209790015441966663343270893200735132901066225095495302796451347221849811693964468452258161885197556674403852934267749554060590179957648521230031621316922578990153956094840309905119088468636521924211289718001402340701993277839587151748236196972181761285913775372865375345455520546034942969117119888701271413502812025714494008347254072089800344838286637028266135281896546434629957123479944732732875606944150458034614023015723092835048208400693956853174435753461846305906217154281186445560779353592557862785764318215171330589434736569934867374994845366471193478901567862586996480771394069057066469631994693010706156988282913985363381347816630052704915437453420867796879684666004850393119850562889838522218160375446127956496416487799205382557834235448762120031202479576168406080379019050455537652564526402690830088030570778678168873842054135448174693459884472015547057443157996491330272338849633962693934984316796912281243456162834793583054757250740620641455246548167555011945501147322826544835732135517607974549047727015778961513575483485574862414711387076615100705283195987424631404323836132796244906626286728960365867920080007131456744745893030125576351207015190547034642731653744036639437241929969103840321155354294141575389343843313270245636960659160343967545802110918118167363301187985124773586584225464886241967539974608830221633386606812777078907045161987977996109252267858536327140960258901824878576642649945166112535279378481171737589211403829060412506226617704888351884558258107756541693485824784723540273443683417342278420687373277325387797269546884199571122581083962412941240174242125095898941837451852943638469642425828257221736392622079356023623748795991911426982307191192170423279344141673214597379410372983084316062194816040509861028558022121015717997577780275698275606455242665438087736052567664208115295581863646632627560128481425906981273155114357152219363008082266100701975489175306423587352330692042513729860760955482442696062344245293665510805295902429158935632497862915476236716535868421393315318944228832112339031805445794429209822411354093350838307102985321218519963699922473674239837393353570344714182491161644679167987271471132588047152466938694305930221972292909900699853267220200899534651264428624238934164336951246433594060564620865300143506892349888345069672901103441961098769141413313297202166824609907827431856956280849722558521107494583498363248517992052710584606364572355697164614446237900581851443619113248389663050766713343393522610438622882274668868686227661953548981672378062582435736340417635247786753649240202952761449299476460695731696184731671133348838094119770798528567001141817702199958696663929439478862335477963229788891268554471249196395257010701348784708357324867395950580194354930867930722686107634039813045189558321987820794017770490340332452926539684912153534603867461546428215852084394858660228607341522175337230518489884676207418698108705365037294510341623098158866764415564282194859217700196141142426824466829450511358229232138502940469282814912821692304224256506324953250715292204767376029316528316548242159333592118230565471990328296094562646876096399834528364280724791117562388683503333892693956573789345702033108285246963221242084195452037965562359442874594420913980290859853673709870079122702286064341403605685895733626215502184956595751054527234104500239545507311372824334191255580436782933443994557839755041340610907988560890602804301262396268665097311334089204787927321361449675955669785943508868324667869230874012806055361469643176810697740592512586872754546562477281860422068848502270584615041355045648255765117866517679243388647791534117352907030993969773498135586594923902429207926056497451571277787101464596987362803424251795361119164510210898047748769679526926623648132123274336546978796121264323423585391444902498788788218039266356797224752018794027601702023861090149012531069784934427987965402606024254254868733539400903721333813207266514232636113654334005636810095244076718544697629148468001099225997903103151555406794472693237824887747540827233537779001022163519285952169917195833044291141243180118841743499110086598337583991310161503066107236288653077682195475930351984255780291697008433843073433074601889515577836624970796740587397592460130213371218328386231264557513990977768970374188663028891407913278664560320782421269228972276425165664214339199842313246256713838189508942456994912962432552811401183901965103673296419202275000301225114435476721030380997547435864952354248059596899055760756034885926441024088437341295723231840985772140582862127012476602847301280111335538717512240777231806958701100803177954483254789540327331885072038398470482543527616181908366304736353621003016160927217289584014346512557306343472629474328700901108630015682997058185035681265393950991469139560186102049093032348651350437156119780322449547281451304874966230421776236166164310393564949321048105802871165753280456861101967294475380045480141088618879689428970796985166844859316436959517001339410684596644746882151165428823431346617226276979398836168951905251043627836419987645395754583137752391812157923613641822812256916836935788735543852224141118088976283090206170239430144393204604967358679554143078770740751046550355373586838184313367249930706971843162111425117122488408146835378154617332323408201757936693854237404793525052049738896775674090875320991301556058474779893842309277903543692452022563584757065215292945265033614447511323758932811358927423319243865076908787290247658459302352240776781820315613246705852124438522759953531531919501858200233118618980909522409074411111357865492738620078576257406160491318145953676853380127611877268859197026329983875448565469639030274308464089778048663883789597069874503458977803595993550219428038008238874622009784761333246458270194802905091824225279515144180650101703829462247699661042621220963957171482129137268810198958586824948563268269639260156898806748251833184320221886656734967899610901890216734691376507089533073165356996131308780800109241432161858733017946085091560452360874601925519920161534103161515898314875226556878202927548215375384192631270687147040439159818037592863893922340052025682245549407307931063867954171747897701150924273403922366585837624827903333782711690754985802721579316721292837304169555258055322767586889475462901941938543350498355686228398937707905153963062661578743525221827301361893596024700178586399254380106436833321889352926374347262945297822004906389882657615697369580248140471439671157692721743860106393030437272590708194328178900674401242535034675333366425555139849391217599080003338214592107604900061363164067989020474389086192077095584653382324183899321297501416237974926033731393584702848185370012780136649525894298886792799325181246683448991686756027753725617838750996397103468225168417780416539903367661617767239948082332836539936074263688619876922934371072395768401180074799860874308657463508535160724331846365355319819312729583447779080192442714464447751537825555815094737152244971084976233190683409271839454337629856414097314061720526074015952450161078911344693751755130039349680373199599254747132213019844660346010579611469760246840727588479479073602120366659751848892419246209783003723272299111313691745928406091455393370774545055893451970708877848965544161987794235016232067950581909864177442096429323060750469034458202561108454148220246641752715826490286543813955365519564416871042819017269848991228087992400576104821430532344907909110173648615424590448989719927313846595112150605407750486773134125235196754532193000221265567499346675669167727711600682566736809455832645964800782777517361764287975892080279001194328380628100577509737189015566615825195966609575813646966602703084100988334691430682146737698187648774452840946652757332051522676109161464708644154627089669278268510283013409561222076212941966565953044804499599369135886260191165441690238744776455607919753735587157492970984920386180888105206036257223941787106267935809297702955583161313113672220699072903258058017483367462775926882335055915241616439293147550098566829545668131037270639495826638932777289494339505882895536416601674214778536092737984952063623617540489136478305996634279253402096550738347511592487821455009576727326763572367989216883620228621398179722223873738227069943520999295291415579759832641071497123001160378662540012207001156631511537112473290977599260246646332912323982254391460524161807153590309695261723700699659072953645296252832438379204246287869552724034186639158448995655206745776459276757569566934475424540082019261495979460829255170832330594119902850158209146739063208620089830605187118024138501455837836489369088648727587834419074093548077696376052551255341049296736424758727409573641001506173318655571732432343430026670566730420750809653814355762213913064054360053560297945122907184988722790107614575240330121845680480569323427969236302937872822576678462498401625732788475462876421212566787542725401738496527711681877317937789194804521574407573340688666352562165600206642301081450663182179018497520733521549668536476140795950771199906430398315548451876243289406594469844982464701329945546427126995147448836479239004325067516891303070354633114384098440257425477731011258534164709890651001983894117414417996618918484443361597215989525362344546227283762965133400604123922498581550706562884293675351506793508331391097089638702916277596771252777776478068745697890604386743076133049847572728979703193384585712674508529236194093026960701862734929523543509398978885445518617685958768268083273933173936520986869239495684432842089643246834212505653382136543219805142209601871611378148671735001588173032105740382336030520246394139573848514176936858961335792643371787873043941982905138882721190322779321918324765390241531225413449460368083893523202140633081418048798581168274053729258015932373069637059432915587535253049845513526581075728967263280597655163717432283495115724645141710986383124789053282457927639884990273511048016463017460849339019085527493655422546445801380119798171897360018269410609804792308069254779027168466505224707266652627580145523029467870600790476676289800901781561669319844411757563248982283272840812646836175182414761382764388145203821895191151595218696518691872406981168318241201332370692222060383936237636117201165526156957863155583701860616561949305437320873833910675170670306821228507615798177817646859595896320706822428427509801872378290764184516963833483596480719951109537612113060273147929452814441182360175967957993798297641864703073587083021887402757452878814759235255878828829450967469161159167235383240170513187056226418719545829065734457474838495121459385173935677788888049943533786181918892017885638146796172878789590289145517968689525343868885772954306788971222382036524640670285569505128348695009978414590463867073370236958681727384035361211362353637349573713717960694874357490694075959311524426361340521893957162462287039988035147548019025061129651869574366752008620573813230160191202295951795588142997319284085438208430336949701304898220726192882601484990791653703871277923102807815226586002707394906101616844429481327828900505182139251350188930443688915639968259408181947131267308957632581016217764258424928165300520237945697816317732927385799663862565497326983790175021251319429888726750155829559307487271998656173203764085185766234966218383583837144367946653000278404227312931449229410045710362825681100924490018064872684952777634459696479632214081852088847854566687585314795724655262903411835507248675299956949223079832960928989026912141995586090753958799648603558019365017842272654905607626189067184669032603846854101285467298742973407972217296328084747890042362433635164165528980198938784122239624857284082414157100988469101505445436826679080557877885246463468869853851930055722577154170691028639024419404620231756218856935483007347695202939433183696177701812155576531940276674696067546658587672958123128853937735334964276459060914064349842335664313043613695315820400286549360101379318106821244480331817104676839042826022335089627853190630584761668797810219281643576420737271577420163885762392627281731376727000653085470637597037174626219879898385374786636070847654177980140421796087418258214840017018164650781645619528696473771084032141305148775584605475575261632968622758261223104974227712510442008923565643718604523491642166663803304815161606323379791838677709303305930948670123385888617159760038871536655293883755808622965690102918868022824215469117097977034150817344201199669402397100346905334152323726923550456857747626327625280725572918369714000517373628225335739095953676003316374425859910261765441525364723553533723434952162189667220973669771730817756908834252753540741407935757257659089417805853792692946463031264266020032997879343797349421400722863175730512452785383794356363786125028263257569800223672542311502481830094008337359077047657168445144324456700902139215411530892298582441208032029102675467042765437531044576254375344389985940660067557310940404532862802463545039771311804387573950950861548139630077619029000145295184008164628375420210717227057819208339526646801876374545823351663693479587691354815065914399742320995312243152226777839933172055774175981589070808884040913064717750251917736122249860274226342590623771895518656523695198764989682144211646752772845837794677341321552867864293206231444106547751081893958487119434752575897002594823298248655825418203964192785845144898215597279696392000616672902208845834059798009529974969661248182900295965195771120222525454887248863754524774558292159015438066579914654927964373165125981971570616307473171261362836347627181769023280220543280695205051356334292389942519481591088561577994948770903383610318430423023643125881354813015714891805646251942932744689254090056228504820061548285993994012182460381770919949960964688896258124101646242653067871382392788795960515252952503759863004126975272291995231268932158585812619813044502137905249325693784131120515470993707393107770082154690356505338258267202247049110218765273510080203443409787211388420478897553499612737686134746591828454148776740710349384507302173940415369000365436372958477595545133031065480438971825010437106603013659298856622987369305434336458274681442880906359241328188519678953865350028732315930348834076133205727964386536523631602544016699793101803505057780688865434708695556103485171273087403358988894530678371316758209901929683888396224568717872463916352742032707895177958370323471052617784122789381070986347215980579931745496763972704394555646902088454566377548731502219648587373656630174916895864874279703102718301630729979898285867225153481149538823191061988074671152352088191500570535987347518561577508946974921956627069400031286073404236655019235041371431175733134797832753881964049042345925154652083029050796289119683783612018112560311429641609919775388784525469357985150569482248288638512543978328868863589258504970500537810176914334882333021705900262712319680593640372125765788580476170975746013402039022542338454052739470595506339261331984614720879127455851901941198902408093620626172442406295371804639666823531179292085279612764019500434408215411437540133215610931189871252574578354485232403236540275526597297713562615259956647333395279119915621210701784382389249127730572072423411204755994395522103428446272456657529365783752148525616871909054243710655518335541765173623979230356464861015746518415822844961365703040651605418003505505780656717122426786267703316701166755383501238851140044073449072622222263131886893460347069768393617041097174859813640000542210004850977984435886960421095793602946466439770701514991684731490074523852359636312250424556870729424992686901920327203764258486288760365144947826981462904512002127757750950170389796054075626077254586655428043404052599670263264035105964128091940463054001295327011464297479132682829030952800257101813907945703635881570585179593772684196438383415168094644233886146238905998637191882832442510342098588967226369880575288027585476823792355959127720162549533681545782505920973028209980729902859315659396644582824591178805210500670712113854403437316008452567496653280311804153842800552015952469361461961325714783559559346622889490068035832697264146016087145107885066376051428267873658497374459089793781292208419319132057533931784102158442245604469597906935165539674593047257918423450699362871657543977802313180118196458587000390629593628917089388505361062189604909864782021012331677118564447717154294383816699813822027118939962585557314021079766015336064718627146652351945399087873443653643589332726764507097875810973885934021605878459502805205881833111392650907669072886252092773094801229096596210228677079453091484636627185129497164980826869064824379781130095829308239185284995925882955920916323651420985913815583364415301076016745508503097429276591986169852653780884632976837286297751484913613116332050162061717702250105297989859753165538382713065318237528205270255637455446705423418694479792846505904117277262975067979834042567430231348867520479610033041650664417274223704632303900575123466454263528756789971847440247038888717783162086959739361219977772305598639264893899827715051989037751730358085483095649031658936088098084890401644569092713576759691612454685034656003133390087542438570185853699101852778839027426821362509905708582763626296362697818305985013797260210366628782369250950453880457967581189394341150495524929365683304815285212914628041548683804957446458106847335799198272909069824062232644324413379981396565182884410990078451908121489729520959948554114607057798069520710271747073681932535191791082019344376269551779704651453094725561532240969136806909590183293482204297916675039384080088382527876941258002456477086893123896028991251735233427056691210346182844110310949236017941251318013931483512048257937587866107180038511569490666944153067925096952409119103491203475523979550473606108251630270063009889340799527059278100211508381579120150825703769896545422209364044764962526303644007408711898334974708945765937948445688880592697362568400273982423283733387474851406393210255352970085804396635775343304335214193072287036896693497641072056997309042423883093422808685399151588037766494982383059113350000283936236629246214883925747928146609434649171569560589280904056767116587674501065180556058547255929534985766825011700978579410020115111379758917366620289922909813067257445888941616051468063236795491641940888034868774693998721229688914756338996038067133748173096685477512027236071404148763517999690286293618571492489480996107380176172289767379701813155946421478569839858777349629120369749191390757318192622703102622144999315366565083000796294691220091919488967536938943777282096796898377706558205115152690878300630666554968973628115325823595214074099810590318876238242786448779629174619032885368144019730801195773111264788011904776041924136686980407024993769069438515411073027144343500314451571675280514472983395199993713037760231407362946498277210924838678113234937345660237481511497751288337896346807828650412800290324498586308025110003008428983732206576613914428123219240753565400586410540401079858061721430596606688693293576476315948217855801859726791338863161111842163482125837441550366830584085940884475313608350547201052981933061967614046645172622216643719552324004459816448204798774276204651813529810297099328507558871717363880456469149866927815750536694898305538143063223956078001197146131539017590443828690475221863875858283693472302217830423391280527350252815670104431784548806559534283121510781010476396651417858920470224967113851171600493216279327730301239055048547347755966096317461147728163862748564450489376040190560037415174508521647171818153808571222598992178878690933196382424866269579220026510152553873787223850341412244845380117396852955931566150524419247222914194430412573480060255408386732226249253933110937296002342984100910995076248522877490557896377860457558169268959661867254735060532948852504945572052817351792375219036300​

コードの実行結果から、Wikipedia に記載のとおり最小のペラン擬素数が $271441$ であることが確かめられた。また、$P(271441)$ は、上記右辺の$2014$で始まり$6300$で終わる数で、これの桁数は、33,150桁ある。

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

ROSの勉強 第16弾:速度の増減

#プログラミング ROS< 速度の増減 >

はじめに

1つの参考書に沿って,ROS(Robot Operating System)を難なく扱えるようになることが目的である.その第16弾として,速度の増減を扱う.

環境

仮想環境
ソフト VMware Workstation 15
実装RAM 2 GB
OS Ubuntu 64 ビット
isoファイル ubuntu-mate-20.04.1-desktop-amd64.iso
コンピュータ
デバイス MSI
プロセッサ Intel(R) Core(TM) i5-7300HQ CPU @ 2.50GHz 2.50GHz
実装RAM 8.00 GB (7.89 GB 使用可能)
OS Windows (Windows 10 Home, バージョン:1909)
ROS
Distribution noetic
プログラミング言語 Python 3.8.5

物理的な問題

前回で,ロボットの最大速度を指定することができるようにはなったが,有限の加速度という物理的な問題を扱う必要が出てくる.

質量を持った多くの物体と同様に,ロボットも瞬時にスタートしたり,ストップしたりはできない.ロボットの移動を物理学でとらえると,時間とともにだんだん加速していく物体である.急に止めたりしようとすると,モータに急激な負荷をかけることになる.例えば,横滑り,スリップ,振動,メカニカルな駆動系で何か壊れるといった問題に発展し得る.これを避けるために,ある一定の時間内で移動コマンドを増減させる

実装

先ほどのことを踏まえて,要求しても瞬間的に加速するのを防ぐプログラムを学ぶ.以下にソースコードとそのときの実行様子を示す.

ソースコード
keys_to_twist_with_ramps.py
#! /usr/bin/env python3

"""瞬間的に加速するのを防ぐために速度を一定時間で増減させる"""

import rospy
import math
from std_msgs.msg import String
from geometry_msgs.msg import Twist

#キーの割当:[angular.z, linear.x]
key_mapping = {'w':[ 0, 1], 'x':[0, -1],
               'a':[-1, 0], 'd':[1,  0],
               's':[ 0, 0]}

twist_pub      = None       #twist配信用の変数
target_twist   = None       #目標の速度インスタンス用変数
last_twist     = None       #何も押されなかった場合,直前のtwistを配信するための記録用変数
last_send_time = None       #配信した時刻を記録する変数
vel_scales     = [0.1, 0.1] #デフォルトの速度(非常に遅い)
vel_ramps      = [1, 1]     #単位はm/s 増減の度合い 

def ramped_vel(vel_prev, vel_target, time_prev, time_now, ramp_rate):
    """ 最大の速度ステップを計算する """

    step = ramp_rate * (time_now - time_prev).to_sec()

    if vel_target > vel_prev:   #目標値に達していなければsignを1.0に
        sign = 1.0
    else:                       #目標値に達しているならばsignを-1.0に
        sign = -1.0

    error = math.fabs(vel_target - vel_prev)    #絶対値計算 abs()と違って,引数がintでもfloatで返す

    if error < step:    #この時間ステップ内にそこに到達できる --> 到達した
        return vel_target
    else:
        return vel_prev + sign * step   #ターゲットに向けてステップを進める

def ramped_twist(prev, target, time_prev, time_now, ramps):
    """ 計算した速度ステップをtwistに適用 """
    twist = Twist()
    twist.angular.z = ramped_vel(prev.angular.z, target.angular.z, time_prev, time_now, ramps[0])
    twist.linear.x  = ramped_vel(prev.linear.x, target.linear.x, time_prev, time_now, ramps[1])

    return twist

def send_twist():
    """ twistを送る """
    global last_send_time, target_twist, last_twist, vel_scales, vel_ramps, twist_pub

    time_now = rospy.Time.now()

    last_twist = ramped_twist(last_twist, target_twist, last_send_time, time_now, vel_ramps)

    last_send_time = time_now目標角速度に
    twist_pub.publish(last_twist)       #twist配信

def keys_callback(msg):
    global target_twist, last_twist, vel_scales

    if len(msg.data) == 0 or msg.data[0] not in key_mapping.keys():
        return  #データがないもしくはキーマッピングにないデータの場合,何もせずに終了

    velocity = key_mapping[msg.data[0]] #キーマッピングからキーに合わせて抽出
    target_twist = Twist()  #Twistインスタンス生成(0に初期化)
    target_twist.angular.z = velocity[0] * vel_scales[0]    #配列の要素に速度スケール値をかけて目標角速度に代入
    target_twist.linear.x = velocity[1] * vel_scales[1]     #配列の要素に速度スケール値をかけて目標速度に代入

def fetch_param(name, default):
    """ コマンドラインでの入力の受付"""
    if rospy.has_param(name):   #コマンドラインでパラlast_send_timeメータnameが指定されているかチェック
        return rospy.get_param(name)    #指定された値を取得
    else:
        #パラメータが指定されておらず,デフォルト値を使うことを警告する
        rospy.logwarn(f"parameter {name} not defined. Defaulting to {default:.1f}")
        return default


if __name__ == '__main__':
    rospy.init_node('keys_to_twist')    #ノードの初期化
    last_send_time = rospy.Time.now()
    twist_pub = rospy.Publisher('cmd_vel', Twist, queue_size = 1)   #cmd_vel配信準備
    rospy.Subscriber('keys', String, keys_callback) #keysを購読し,コールバック関数を呼び出す.引数はさらに後ろで指定する

    target_twist = Twist()  #0に初期化
    last_twist = Twist()    #0に初期化

    vel_scales[0] = fetch_param('~angular_scale', 0.1)
    vel_scales[1] = fetch_param('~linear_scale', 0.1)
    vel_ramps[0]  = fetch_param('~angular_accel', 1.0)
    vel_ramps[1]  = fetch_param('~angular_accel', 1.0)


    rate = rospy.Rate(20)   #10Hz(100ミリ秒ごと)で出力するため
    while not rospy.is_shutdown():
        send_twist()
        rate.sleep()

実行様子

ezgif-7-a30741504dfe.gif

結果

グラフから確かに,ステップ状に変化するのではなく,だんだん増えたり減ったりしていることがわかる.加速と減速に一定の時間をかけているため,物理的に実行可能であるということも確認できた.

感想

今回は少しソースコードも関数が増えて複雑になったが,前回に危惧されたステップコマンドによる物理的問題を解消する方法を学んだ.特にこれからロボットを扱っていく中で,非常に重要で意識しておくべきことだと思う.

次回はシミュレーション上で,実際にturtlebotを動かしてみる.

参考文献

プログラミングROS Pythonによるロボットアプリケーション開発
        Morgan Quigley, Brian Gerkey, William D.Smart 著
                       河田 卓志 監訳
            松田 晃一,福地 正樹,由谷 哲夫 訳
                  オイラリー・ジャパン 発行

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

全角の恐ろしさを駆け出しエンジニアに伝えたい

こんにちは
森林浴が趣味になりつつあるノブです。
この記事は私のQiita初投稿の記事になります。
今回の記事は私が体験した恐怖のエラーを駆け出しエンジニアに伝えたいと思います。
駆け出しエンジニアの方は私を反面教師にして同じ過つを繰り返さないでください!

<目次>
・エラー内容
・対策
・まとめ

エラー内容

今回私は体験したエラーはこのような物です。
スクリーンショット 2021-02-28 17.10.02.png
スクリーンショット 2021-02-28 17.10.13.png

フレーズリストの表に物が入るとフレーズリストと表の間に隙間が空いていることがわかるでしょうか?
私はこれがどうしても気に入りませんでした。
しかし、いくら探しても原因がわかりませなんでした。
そこで、ゼミの先生に相談したところ全角スペースで書いてるんじゃないと言われました。
私は、血眼になって探しました。でも見つかりません。
そこで社会人エンジニアの方にも相談してかれこれ3時間三人で悩み続けました。
これはもう解決は無理だとその場の全員が思い始めたところに社会人エンジニアがもう一度全角スペースがあるのじゃないかと疑いました。
私は何度もチェックしたので絶対ないと思ったのですが、一度全角スペースを検索してみることを勧められました。
やってみたら見事に全角スペースが発見されたのです。
私は今でもあの時の二人の顔を忘れません。
恐らく生涯全角スペースをみると思い出すでしょう。

対策

このエラーの対策はたくさんあるとおもいます。
1つは、半角全角を見えるようにするエディタを使用する方法です。こうすることによって未然に全角スペースに気が付くことができます。
もう一つは、設定で全角を打てなくする方法です。私の先輩の社会人エンジニアはその設定をしているみたいです。この設定をしておくとそもそも全角を打てないのでエラーを出す心配がありません。

まとめ

エンジニアは毎日たくさんのエラーをみることと思います。
エラーを解決していき経験を積むことが成長につながることはもちろんだとは思うのですが、エラーを事前になくしておくことはさらに大切だと思います。
少なくとも全角のエラーはもうすることがないと思います!
皆さんは今回の対策を使って私のように全角スペースに3時間も取られないようにしてください。

では、良いエンジニアライフを!

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

kivyMDチュートリアル其の伍 Themes - Icon Definitions篇

さぁ、今週もやってまいりました、kivyMDのお時間です。
少しサンプルコードのkivyの難易度が上がってきて、あたふたしておりますがそんなことは
お構いなくで、元気にやって参りましょう。慌てて、今は再勉強中です。
本日はIconのことをやっていきます。

Icon Definitions

冒頭は「大将、いつものやつで!」と言わんばかりのリンクからになります。

See also

Material Design Icons
https://materialdesignicons.com/

リンクの[View the Contributors]を辿ると、こんなにあるの?と思えるくらいアイコンが
羅列されています。内訳はというと、Googleとマテリアルデザインのコミュニティ?とで構成
され何千にものぼるほどです。コミュニティは赤枠で囲まれていますね(先週とかあったっけ?赤枠
はコミュニティ発ですみたいなもの)。マニュアルの続きではこういった説明もあります。

List of icons from materialdesignicons.com. These expanded material design icons
are maintained by Austin Andrews (Templarian on Github).

materialdesignicons.comからのアイコンのリスト。 これらの拡張マテリアルデザインアイコンは、
Austin Andrews(GithubのTemplarian)によって管理されています。

何食わぬ顔で依頼していますが、上記の通りとなっています。Templarianってなんだろう。。
まぁ、なんにせよAustin Andrewsさんによってこれらのアイコンが使えるので感謝を捧げましょう。
Thank you Austin Andrews!

とイキってお礼を伝えたところで、今日の本題に入りたいと思います。
安心してください、コード動いてますよ。

To preview the icons and their names, you can use the following application:

とまぁ、サンプルコードが載っけてあるのでさっそくこちらを見ていきましょう。

iv/icon.py
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.screenmanager import Screen

from kivymd.icon_definitions import md_icons
from kivymd.app import MDApp
from kivymd.uix.list import OneLineIconListItem


Builder.load_string(
    '''
#:import images_path kivymd.images_path


<CustomOneLineIconListItem>:

    IconLeftWidget:
        icon: root.icon


<PreviousMDIcons>:

    MDBoxLayout:
        orientation: 'vertical'
        spacing: dp(10)
        padding: dp(20)

        MDBoxLayout:
            adaptive_height: True

            MDIconButton:
                icon: 'magnify'

            MDTextField:
                id: search_field
                hint_text: 'Search icon'
                on_text: root.set_list_md_icons(self.text, True)

        RecycleView:
            id: rv
            key_viewclass: 'viewclass'
            key_size: 'height'

            RecycleBoxLayout:
                padding: dp(10)
                default_size: None, dp(48)
                default_size_hint: 1, None
                size_hint_y: None
                height: self.minimum_height
                orientation: 'vertical'
'''
)


class CustomOneLineIconListItem(OneLineIconListItem):
    icon = StringProperty()


class PreviousMDIcons(Screen):

    def set_list_md_icons(self, text="", search=False):
        '''Builds a list of icons for the screen MDIcons.'''

        def add_icon_item(name_icon):
            self.ids.rv.data.append(
                {
                    "viewclass": "CustomOneLineIconListItem",
                    "icon": name_icon,
                    "text": name_icon,
                    "callback": lambda x: x,
                }
            )

        self.ids.rv.data = []
        # print(len(md_icons)) #-> 4996
        #print(type(md_icons)) #-> <class 'dict'>
        for name_icon in md_icons.keys():
            if search:
                if text in name_icon:
                    add_icon_item(name_icon)
            else:
                add_icon_item(name_icon)


class MainApp(MDApp):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.screen = PreviousMDIcons()

    def build(self):
        return self.screen

    def on_start(self):
        self.screen.set_list_md_icons()


MainApp().run()

少々コードが難しくなってきましたが、わかる範囲だけ触っていこうと思います。

モジュールのインポート

from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.screenmanager import Screen

from kivymd.icon_definitions import md_icons
from kivymd.app import MDApp
from kivymd.uix.list import OneLineIconListItem

あまりこれまではインポート文を触ってきませんでしたが、これからは注意していこうと思います。
サンプルコードを動かすには上記が必要となります。今回のiconをいじるには下記の1文が必要に
なりますかね。
from kivy.uix.screenmanager import Screen

kv側

Builder.load_stringでkvを読み込んでいるところをピックアップします。

Builder.load_string(
    '''
#:import images_path kivymd.images_path


<CustomOneLineIconListItem>:

    IconLeftWidget:
        icon: root.icon


<PreviousMDIcons>:

    MDBoxLayout:

        (略)
        MDBoxLayout:
        (略)

        RecycleView:
        (略)
'''
)

少し長いので略せるところは略します。冒頭のimport文はなんなんでしょうね。取り除いても普通に動いて
いたしで。。まぁ、この辺は一旦放っておきましょう。大まかに捉えると、2つのウィジェットが定義されて
います。CustomOneLineIconListItemとPreviousMDIconsになります。CustomOneLineIconListItem
に関してはIconLeftWidgetを内部に持っていて、またその中にはiconを保持しています。このアイコンはroot
とあるので、ここではpython側で宣言されているのだろうと推測しておきます。

もう一方のPreviousMDIconsウイジェットは子ウィジェットのMDBoxLayoutを持ちます。さらにその子ウィ
ジェットは子MDBoxLayoutウィジェットと子RecycleViewウィジェットを持ちます。PreviousMDIconsから
すると孫にあたるのでしょうか。

    MDBoxLayout:
        orientation: 'vertical'
        spacing: dp(10)
        padding: dp(20)

さらにPreviousMDIconsのトップにあるMDBoxLayoutですが、この辺りは説明するまでもと
思いましたがどうなっているか自分も知りたいと思ったのでさらっと触ります。まずorientationはマニュアル
参照ですが、アプリでも表示されているように縦並び配置ということですよね。で、あとはspacingとpaddingは
デザインのこと知っている人であれば常識みたいなところですが、自分みたいなドがつく素人は次の試した結果を見た
方が早そうです。

17.png

それぞれspacingとpaddingを50ほど増やした結果が上キャプチャになります。見てわかりやすいのは
spacingですかね。トップに定義したMDBoxLayoutの2つの子ウィジェットの空白を決めますよということ
になるのですね。んで、paddingはというとそれぞれ子ウィジェットにあるパーツの周辺の長さを決めれますよ
ということになります。何いってるか分からないとかあればデザインの本を買ってみてください。

        padding: dp(20)

        MDBoxLayout:
            adaptive_height: True

            MDIconButton:
                icon: 'magnify'

            MDTextField:
                id: search_field
                hint_text: 'Search icon'
                on_text: root.set_list_md_icons(self.text, True)

続けて2つのウィジェットの1つにあたるMDBoxLayoutに触れます。これらが意味するところは検索マークと実際に
検索するときのテキストフィールドのエリアにあたりますね。んで先にadaptive_heightってなんぞよと思いましたが、
こればっかりは先のcomponentsにあるし特に説明がないしでよく分かりませんでした。
# おそらくコードもないしでcomponents - BoxLayoutは触れないと思います

まぁ、これだけで終わるとやるせないので試しにFalseで動かしてみたところ以下のようになりました。

18.png

んー、画面上部に配置しなくなるよーということなのか。奥が深すぎるぞ、kivyMD。。。
後のMDIconButtonとMDTextFieldは見たまんまになりますかね。MDTextFieldでは、またrootと
あるので伏線だなと勘ぐっておきます。あと、id: search_fieldとありますがこの後でこれが出てくる
ことはありません。うん、こればっかりはよくわからないな。

        RecycleView:
            id: rv
            key_viewclass: 'viewclass'
            key_size: 'height'

            RecycleBoxLayout:
                padding: dp(10)
                default_size: None, dp(48)
                default_size_hint: 1, None
                size_hint_y: None
                height: self.minimum_height
                orientation: 'vertical'

んで、kvの最後にラスボスとなるRecycleViewがやってきます。すみません、こればっかりは力不足で
自分からは触れることのできない内容となっています。ですが、先行で分かりやすすぎて目から鱗状態に
なるページがあったのでそちらを参照ください。むずい、RecycleView...

私文 vs kivy(5-1) RecycleViewの基本
https://labor.hatenablog.jp/entry/2019/09/25/%E7%A7%81%E6%96%87_vs_kivy%285-1%29_RecycleView%E3%81%AE%E5%9F%BA%E6%9C%AC_1

ぽきたさんによるRecycleViewの説明になります。ありがたや、ありがたや。
改めてここで謝辞を述べさせていただきます。後、勝手な引用をお許しください。

なるほど、もともとListViewというものがあったのですね。んで後継でこいつが生まれたと。
あとは、概念としてMVCがあるというのは面白いなぁと思いました。全部UI側なのにMVCを採用
したとは実に面白い・・・と思うのは私だけでしょうか。
# 少し憶測入ってるかもだけど

1つ1つ説明もありだけど、レイアウトで被っている内容もあるしでここではkivyに近い内容なので
割愛します。ご了承のほどを。あ、あとid: rvだとかkey_viewclass: 'viewclass'だとかは後で
出てくるのでお楽しみをー!

python側

class CustomOneLineIconListItem(OneLineIconListItem):
    icon = StringProperty()

んで、ここからはpython側に入っていきますがまずはCustomOneLineIconListItemクラスに
になります。このクラスはOneLineIconListItemを継承していて、1行のリストアイテムを表示
させたいときにこれを継承するのかな。知らないけど。こんなこと言ってますが、components -
listで触りますのでご心配なくと乞うご期待ということで!

あとは、kv側で定義してあったroot.iconとリンクするところで変数化をしていますね。
伏線回収っと。

class PreviousMDIcons(Screen):

    def set_list_md_icons(self, text="", search=False):
        '''Builds a list of icons for the screen MDIcons.'''

        def add_icon_item(name_icon):
            self.ids.rv.data.append(
                {
                    "viewclass": "CustomOneLineIconListItem",
                    "icon": name_icon,
                    "text": name_icon,
                    "callback": lambda x: x,
                }
            )

        self.ids.rv.data = []
        # print(len(md_icons)) -> 4996
        for name_icon in md_icons.keys():
            if search:
                if text in name_icon:
                    add_icon_item(name_icon)
            else:
                add_icon_item(name_icon)

それで少し長いですが、こちらはPreviousMDIconsになります。Previousと謳っている割には、
検索してからの表示も兼ねていて混乱しそうになります。

まずは、後でも出てきますがアプリが初期化され起動されたときにはこのset_list_md_iconsが
呼び出されます。すぐ下の内部メソッドadd_icon_itemはさっそくkv側でidをrvと決めていた
ところ、すなわちrecycleviewにあたりますね。引数にはname_iconを受け取っていて、rvに
データを流し込んでいます。方法はというとrv.data.appendでjson形式のようにデータを入れ
込んでいますね。あとはkv側で決めていたviewclassもここで登場しています。recycleviewは
わりかし、このような形をしているので自分みたいにわけわからんとなっている方は形を覚えて
おきましょう。ときにおまじないは便利なものです。あと、伏線回収っと。

後出しで実はというと、このadd_icon_itemはクラスを初期化したとき(rv.dataを初期化してそのあと
イテレータを回している寸前)は即座に呼び出されません。じゃ、いつ呼ばれんのよと言われそうですが、
もう見えているかもしれませんので説明するまでもないかもですが、このイテレータを回しているときに
初めてadd_icon_itemが呼び出されます。# 見りゃわかるだろーがと言われそう

んで、一見何をしているか分からないfor文ですが、大まかに言って検索したときとそうでないとき(
検索フィールドに何も文字列を入れていないとき)とでこのif文で処理を分けています。その条件とは
と言われそうですが(被害妄想)、それがsearchになります。このsearchはset_list_md_iconsの
第2引数ですね。デフォルトではfalseになります。もう察しの良い方はわかるかもというか当たり前
かもですが、trueになるときは検索フィールドに値が入ったときですね。このことがいわゆるkv側での
以下の1文になります。
on_text: root.set_list_md_icons(self.text, True)

あとは、さらなるその中にあるifですがここまでくるとお手のまえ、そのtextがname_iconに含まれて
いるとそのアイコンが絞り出されるといった挙動になっています。もう、ここまで実装されているとお見事
としか言いようがありません。

class MainApp(MDApp):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.screen = PreviousMDIcons()

    def build(self):
        return self.screen

    def on_start(self):
        self.screen.set_list_md_icons()


MainApp().run()

今更かい、と言われそうですがアプリの起動方法もついでに見ておきましょう。
初期化(init)メソッドでは、screen変数にPreviousMDIconsクラスを生成して入れ込んでいます。
んで、その次のbuildメソッドではscreenを返して、最後にon_startメソッドでscreen経由で
set_list_md_iconsメソッドを呼び出しています。runメソッドは言わずもがな。

触れ込みは以上になりますが、ここでアプリを動かしてみたいと思います。というかすでに動かして
いるけど。無駄にスマホのように形を変えて、キャプチャしたものが以下になります。

15.png

16.png

というように、動かしてみるとわかりますがと!に!か!く!量が多い。。コードの方の触れ込みで
言い忘れていましたが、なんとその総数4996。。日常生活で使うものは大抵入ってる?と思える
くらいの量があります。

API - kivymd.icon_definitions

kivymd.icon_definitions.md_icons

コードの方で触れているので、言わずもがなかもですが使い方を一応おさらいしておきます。
importでモジュールを取り込んで、それを使う、以上!です。
あと、コードでは触れてませんが辞書型のオブジェクトになります。

まとめ

さぁ、今週も無事に終わりましたがいかがだったでしょうか。
自分のTODOというか振り返りとしてはRecycleViewをなんとかしたいなぁと思いました。
他のkotlinとか触っても出てきたりするので、またわかり次第解説とかページ作ろうかな。
日本語のページという括りでは、あまり検索しても出てこないしで。一応以下にRecycleViewの
リンクを備忘録&共有として貼っておきます。

RecycleView
https://kivy.org/doc/stable/api-kivy.uix.recycleview.html

あとはもっともっとkivyというかkv側に詳しくならないと。。課題は山積みだぁ。
とまぁ、どうでも良いまとめをしたところで今日はこの辺りで。

それでは、ごきげんよう。

参照

Themes - Icon Definitions
https://kivymd.readthedocs.io/en/latest/themes/icon-definitions/

github

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

GPSが位置信号を受信できていない時、周囲のWiFi情報を使って位置情報を特定できたらいいなと思ったら、機械学習でできたという話

はじめに

Raspberry PiとGPSモジュールを使って現在位置を取得するプログラムを作成しているのですが、電源を入れてからGPSが有効になるまでにはどうしても時間がかかってしまう。
時には10分近く位置を取得できない場合もあり、ちょっと使い物にならない。

そんな時、思いついたのが周囲のWi-Fiアクセスポイントの情報を使ってGPSの代わりに位置をある程特定できるんじゃないのかなということ。

ということで、試してみたらこれがなかなかの精度でできたので、嬉しくなってメモしてみることにした。

利用環境

  • Raspberry Pi 3B+
  • GPSモジュール
  • SORACOM Air
  • Python 3.x
  • SQLite3
  • scikit-learn
  • micropyGPS

データ収集

データベースの作成

ここではSQLite3を使います。

sqlite3 data.db

以下のSQLを実行。

create table gps (
    dat text,
    lon real,
    lat real,
    wlan text
);

Wi-Fiデータ取得

iwlistを使って、Wi-Fiの情報を表示するスクリプトを作成。

get_iwlist.sh
#!/bin/sh
sudo iwlist wlan0 scan

GPSデータ取得

USB接続のGPSモジュールを接続して測位情報を取得するために以下のスクリプトを作成。

gps.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from time import sleep
import serial
import micropyGPS
from datetime import datetime, timedelta
import subprocess
import sqlite3

DEVICE_NAME = "/dev/ttyACM"
RATE = 119200

DBNAME = "data.db"

gps = micropyGPS.MicropyGPS(9, 'dd')

lon = 0.0
lat = 0.0
dat = ""

def init_device():
    for i in range(5):
        try:
            device_name = "{}{}".format(DEVICE_NAME, i)
            s = serial.Serial(device_name, RATE, timeout=10)
            s.readline().decode('utf-8')
            print("device find : " + device_name)
            return s
        except:
            print("no device : " + device_name)

try:
    s = serial.Serial(DEVICE_NAME + "0", RATE, timeout=10)
except:
    s = init_device()

i = 0
t = datetime.now()
flg = True

while flg:
    try:
        sentence = s.readline().decode('utf-8')
        print("read data : {}".format(len(sentence)))
        if sentence[0] != '$':
            continue
        for x in sentence:
            gps.update(x)

        dat = "{:04}-{:02}-{:02} {:02}:{:02}:{:02}".format(
            gps.date[2]+2000,
            gps.date[1],
            gps.date[0],
            gps.timestamp[0] if gps.timestamp[0] < 24 else gps.timestamp[0] - 24,
            gps.timestamp[1],
            int(gps.timestamp[2])
        )
        lon = gps.longitude[0]
        lat = gps.latitude[0]
    except:
        print("GPS data error.")
        init_device()
        continue

    try:
        if (datetime.now() - t).seconds > 0:
            print(lon, lat, dat)

            completedProcess = subprocess.run(['/home/pi/get_iwlist.sh'], check=True, shell=True, stdout=subprocess.PIPE)
            txt = completedProcess.stdout.decode('utf-8')

            sql = """
INSERT INTO gps VALUES (
    "{}",
    {},
    {},
    '{}'
);
""".format(dat, lon, lat, wlan)

            if lon > 0:
                con = sqlite3.connect(DBNAME)
                cur = con.cursor()
                cur.execute(sql)
                con.commit()
                cur.close()
                con.close()

            t = datetime.now()
    except:
        print("Data error.")

実行

スクリプトを実行し、データを収集。

python3 gps.py

実行後、機器を持って周辺をうろうろします。

データ学習

import pandas as pd
import sqlite3

DB_FILE = "data.db"
conn = sqlite3.connect(DB_FILE)

df = pd.read_sql_query('SELECT * FROM gps', conn)

addr = []

for txt in df.wlan:
    for t in txt.split("\n"):
        if t.find("Address") > 0:
            mac = t.split("Address: ")[-1]
            if mac in addr:
                pass
            else:
                addr.append(mac)

for a in addr:
    df[a] = 0

for i in df.index:
    print("\r{}".format(i), end="")
    row = df.loc[i]
    txt = row.wlan
    for t in txt.split("\n"):
        if t.find("Address") > 0:
            df[t.split("Address: ")[-1]].loc[i] = 1

k = 1000000

X = df[df.columns[4:]]
y_lon = df.lon * k
y_lat = df.lat * k

from sklearn import model_selection
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestRegressor

model = {}

model["lon"] = RandomForestRegressor()
model["lat"] = RandomForestRegressor()

y = {}
y["lon"] = y_lon.astype("int")
y["lat"] = y_lat.astype("int")

y_act = {}

for m in ["lon", "lat"]:

    print(m)

    X_train, X_test, y_train, y_test = model_selection.train_test_split(X, y[m], test_size=.1, random_state=42)
    y_act[m] = y_test

    scaler = StandardScaler()
    scaler.fit(X_train)

    X_train = scaler.transform(X_train)
    X_test = scaler.transform(X_test)

    model[m].fit(X_train, y_train)
    print(model[m].score(X_test, y_test))

実行すると...

lon
0.9944890346537838
lat
0.9922297709277479

結構精度高く予測できてる!

位置予測

...後で追記します。

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

KerasでGANの実装 (サンプルコード有り)

はじめに

以前投稿した記事「kerasでGAN(mnist)動かしてみた」で扱っていたGANのコードを今更ながらGithubで公開しました。
これからGANを勉強したい方などの参考になればと思っています。
データは全てMnist、ライブライはKerasを使っています。

Github: https://github.com/ozora-ogino/keras-gan-example

コードの説明

GithubリポジトリにはGAN, DCGAN, CGANが実装されています。
それぞれ以下のようなクラスで実装しています。

gan.py
class GAN():
    def __init__(self):
        self.img_rows = 28
        self.img_cols = 28
        self.channels = 1
        self.img_shape = (self.img_rows, self.img_cols, self.channels)
        self.latent_dim = 100

        optimizer = Adam(0.0002, 0.5)

        # 識別モデルのビルドとコンパイル
        self.discriminator = self.build_discriminator()
        self.discriminator.compile(loss='binary_crossentropy',
            optimizer=optimizer,
            metrics=['accuracy'])

        # 生成モデルのビルド
        self.generator = self.build_generator()

        # ノイズ
        z = Input(shape=(self.latent_dim,))
        img = self.generator(z)

        # 画像生成時の識別モデルの学習をOFF
        self.discriminator.trainable = False

        # 識別モデルは画像(Fake or Real)を入力として、それがFakeかどうかを判定します。
        validity = self.discriminator(img)

        # モデルを結合
        self.combined = Model(z, validity)
        self.combined.compile(loss='binary_crossentropy', optimizer=optimizer)


    def build_generator(self):

        model = Sequential()

        model.add(Dense(256, input_dim=self.latent_dim))
        model.add(LeakyReLU(alpha=0.2))
        model.add(BatchNormalization(momentum=0.8))
        model.add(Dense(512))
        model.add(LeakyReLU(alpha=0.2))
        model.add(BatchNormalization(momentum=0.8))
        model.add(Dense(1024))
        model.add(LeakyReLU(alpha=0.2))
        model.add(BatchNormalization(momentum=0.8))
        model.add(Dense(np.prod(self.img_shape), activation='tanh'))
        model.add(Reshape(self.img_shape))

        model.summary()

        noise = Input(shape=(self.latent_dim,))
        img = model(noise)

        return Model(noise, img)

    def build_discriminator(self):

        model = Sequential()

        model.add(Flatten(input_shape=self.img_shape))
        model.add(Dense(512))
        model.add(LeakyReLU(alpha=0.2))
        model.add(Dense(256))
        model.add(LeakyReLU(alpha=0.2))
        model.add(Dense(1, activation='sigmoid'))
        model.summary()

        img = Input(shape=self.img_shape)
        validity = model(img)

        return Model(img, validity)

    def train(self, epochs, batch_size=128, sample_interval=50):

        (X_train, _), (_, _) = mnist.load_data()

        # 前処理のスケーリング
        X_train = X_train / 127.5 - 1.
        X_train = np.expand_dims(X_train, axis=3)

        # Adversarial ground truths
        valid = np.ones((batch_size, 1))
        fake = np.zeros((batch_size, 1))

        for epoch in range(epochs):

            #  *** 識別モデルの学習 ***

            # ランダムにバッチを抽出させる
            idx = np.random.randint(0, X_train.shape[0], batch_size)
            imgs = X_train[idx]

            noise = np.random.normal(0, 1, (batch_size, self.latent_dim))

            # 生成画像のバッチ
            gen_imgs = self.generator.predict(noise)

            # 学習
            d_loss_real = self.discriminator.train_on_batch(imgs, valid)
            d_loss_fake = self.discriminator.train_on_batch(gen_imgs, fake)
            d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

            #  *** 生成モデルの学習 ***

            noise = np.random.normal(0, 1, (batch_size, self.latent_dim))

            # ノイズと画像を入力として学習
            g_loss = self.combined.train_on_batch(noise, valid)

            # 進捗
            print ("%d [D loss: %f, acc.: %.2f%%] [G loss: %f]" % (epoch, d_loss[0], 100*d_loss[1], g_loss))

            # 画像の保存
            if epoch % sample_interval == 0:
                self.sample_images(epoch)

    def sample_images(self, epoch):
        r, c = 5, 5
        noise = np.random.normal(0, 1, (r * c, self.latent_dim))
        gen_imgs = self.generator.predict(noise)

        # スケーリング
        gen_imgs = 0.5 * gen_imgs + 0.5

        fig, axs = plt.subplots(r, c)
        cnt = 0
        for i in range(r):
            for j in range(c):
                axs[i,j].imshow(gen_imgs[cnt, :,:,0], cmap='gray')
                axs[i,j].axis('off')
                cnt += 1
        fig.savefig("images/%d.png" % epoch)
        plt.close()

どなたかこれから学ぶ方のためになれば幸いです!

参考

実践GAN 敵対的生成ネットワークによる深層学習
Keras-GAN: https://github.com/eriklindernoren/Keras-GAN

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

負荷試験でlocustを使ってみる

locustについて

試してみる

前提

  • Docker、docker-composeがインストール済み

環境構築

locustのコンテナと、試しに負荷をかけてみるコンテナを作ります。

docker-compose.yml
version: '3.7'

services:
  master:
    image: locustio/locust
    ports:
      - "8089:8089"
    volumes:
      - ./:/mnt/locust
    # 負荷をかける環境のドメインを指定する
    command: -f /mnt/locust/main.py -H http://test_web:80

  # お試し用の負荷をかけられるコンテナ
  test_web:
    image: httpd
    tty: true

テストシナリオ

テストシナリオを作ります。

main.py
from locust import HttpUser, task, constant_pacing

class StressTestUser(HttpUser):
    wait_time = constant_pacing(1)

    @task
    def top_page_access(self):
        # 負荷をかける対象のパスを指定する
        self.client.get("/")

ユーザクラスを作ります。ユーザクラスは1人のユーザーが行う行動をwait_timeやtaskなどで定義していきます。
https://docs.locust.io/en/stable/writing-a-locustfile.html#wait-time-attribute

起動

コンテナを起動します。

docker-compose up

実行

  1. http://localhost:8089 をブラウザで開く
  2. Number of total users to simulate に同時アクセスを行う最大ユーザ数を入力する
  3. Spawn rate にユーザ数の増加速度を入力する
  4. Start swarming をクリックする 1.png

テスト状況

テストを実行すると状況が確認できます。
2.png
3.png

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

【5年分データ分析】ボリンジャーバンドを利用した逆張り戦略は効率的に稼げない?

株式相場を予測・分析するテクニカル分析の様々な手法を検証しています.
過去には,TOPIX500銘柄の過去5年分のデータを使って,移動平均線のゴールデンクロス,MACD,RSIを検証しました.

【5年分データ分析】ゴールデンクロスの数日後に株価は上がっているのか
【5年分データ分析】MACDの買いサインから上がる確率を検証しました
投資タイミングを判断するときに必ず見てほしい指標RSIをデータ分析

今後も様々な手法を検証していきます.

今回はTOPIX500銘柄の過去5年分のデータを使って,
ボリンジャーバンドの買いサインの数日後に,株価が上がっているのか
売りサインの数日後に,株価が下がっているのか
を検証します.

この記事はYoutubeにもアップロードしました.是非ご覧ください!SofTalkで記事を読み上げています.
チャンネル登録していただけますと励みになります!

ボリンジャーバンド

有名なテクニカル分析の手法の1つです.

ボリンジャーバンドは株価の移動平均に加えて,ばらつき具合(標準偏差)を取り入れた分析手法です.

ミドルバンド,アッパーバンド,ローバンドがあり,ミドルバンドは20日の移動平均,アッパーバンドは20日の移動平均に20日の標準偏差の2倍を加えたもの,ローバンドは20日の移動平均から20日の標準偏差の2倍を引いたものです.

アッパーバンド,ローバンドが示す範囲と実際の価格の関係を見て分析を行います.

図6.png

武田薬品工業(TYO: 4502)を例に見ていきましょう.

2020年8月から2021年2月の株価時系列データです.

図7.png

基本的な使い方として,アッパーバンド,ローバンドで囲まれた領域から株価が外れた時が売りや買いのタイミングです.

上に抜けたときには高値になっていると考えられるため売り,下に抜けたときには安値になっていると考えられるため買いの判断をします.

この平均値への回帰を前提とした逆張り戦略は,価格が2σ内で変動する確率が約95%となることから,理論的に価格はそのほとんどが2σの範囲に収まるはずという考え方に基づいた投資戦略です(正規分布において,1σ(標準偏差)内に事象が存在する確率は約68%,2σ内に事象が存在する確率は約95%となる).

武田薬品工業の例では,緑点で示したように買いタイミングが8回あります.その後の株価に注目すると,8回のうち6回は数日中に株価上昇しています.また赤点で示したように売りタイミングは2回あります.その後の株価に注目すると,2回のうち1回は数日中に株価下落しています.

この例だけを見ると,ボリンジャーバンドが短期トレードにおいて,投資タイミングを判断するときに有効だろうと推測されます.特に,この例では買いタイミングがうまく捉えることができています.

検証内容

武田薬品工業の例ではボリンジャーバンドを使って売りタイミングや買タイミングを判断する際の指標として有効でした.

今回の検証では,TOPIX500銘柄を対象に5年分のデータからボリンジャーバンドの有効性を検証します.大まかな流れとして,5年分のすべてのデータに対してボリンジャーバンドから外れたタイミングを見つけ,その数日後の株価上昇率を調査します.

また,検証プログラムは記事の最後に記載します.

【ボリンジャーバンドの設定期間】
20日(最も使われる)

【基準値】
・ローバンド($2\sigma$)を下回るとき
・アッパーバンド($-2\sigma$)を上回るとき

【検証期間】
2015年1月~2020年2月の約5年間

【対象銘柄】
TOPIX500銘柄

【株価の上昇を確認する日】
1. 1日後
2. 3日後
3. 5日後
4. 10日後

※ その他のパラメータでの検証結果は記事の一番下に載せてあるので興味ある方はご覧ください.

▼ 買いサイン
ローバンドから外れたとき,数日後に株価が上昇しているかを確認します.

図3.png

▼ 売りサイン
アッパーバンドから外れたとき,数日後に株価が下落しているかを確認します.

図4.png

検証結果

ここでは1,3,5,10日後に上昇している確率と,分布図を記載します.
その他のパラメータで検証した結果は記事の最後にまとめて記載します.
使用するデータは前述の通りTOPIX500銘柄の5年間です.

▼ 買いサイン
ローバンドを下回った数日後の株価の変化率(買いサインから何%株価が変動したか)の分布を示します.
グレーが上昇,赤が下落を表します.

図4.png

ローバンドを下回った次の日(1日後)は,株価が下落する方が上昇するよりも若干多い結果となりました.3,5,10日後は上昇確率が50%を上回る結果となりました.これらの結果から,ローバンドを下回った数日後の株価は少しではありますが上昇傾向にあると言えます.

▼ 売りサイン
アッパーバンドを上回った数日後の株価上昇率(売りサインから何%株価が変動したか)の分布を示します.

図5.png

アッパーバンドを上回った1日後は,株価が下落した確率が51.73%となり,株価が下落しやすいことがわかります.しかし,3,5,10日後は下落確率が50%を下回るので,むしろ株価が上昇する方が多かったことがわかります.したがって,3,5,10日の期間で株を売買する場合は,アッパーバンドを上回ったタイミングを売りサインとすることは適切でないと言えます.

まとめ

今回は,株価移動平均にばらつき具合(標準偏差)を取り入れた分析手法であるボリンジャーバンドを利用し,価格がアッパーバンド(+2σ)を上回ったときに売り,ローバンド(−2σ)を下回ったときに買いとする,平均値への回帰を前提とした逆張り戦略を検証しました.

今回の検証結果から,買いサインのタイミングから株価上昇傾向が見られましたが,その上昇確率は決して高いものではなく,効率的に稼げるものではないということがわかりました.また,売りサインのタイミングから1日後は株価の下落傾向が見られましたが,3,5,10日後の株価はむしろ上昇する確率の方が高いということがわかりました.したがって,今回の検証からはボリンジャーバンドを使った逆張り戦略は効率的に稼ぐための指標だとは言えません.

しかし,最初の武田薬品工業の例にあるように,ボリンジャーバンドを使った逆張り戦略は,売りタイミングや買いタイミングをうまくとらえられることもあります.今後の課題としては,ボリンジャーバンドを使った逆張り戦略が生かせるタイミングやその他のテクニカル指標(MACDなど)と組み合わせて,ボリンジャーバンドが生かせないかを検証していきたいと思います.


<Youtube>
分析動画を投稿しています.
検証結果を多くの方に見てもらえると嬉しいです.
https://www.youtube.com/channel/UCKM_EhOxMfXkcLFOwAdEKcQ
↑チャンネル登録していただけますと励みになります。


<過去記事>
日経225全銘柄の投資効率を検証
【5年分データ分析】ゴールデンクロスの数日後に株価は上がっているのか
【5年分データ分析】MACDの買いサインから上がる確率を検証しました
コロナ・ショック後から株価上昇し続けている15銘柄

付録(分析プログラム)

プログラム内で使用している自作の関数や株価データの取得方法は以下の記事をご参照ください。(執筆中)
株分析ツールの使い方(備忘録)

import trade_package.get as get
import trade_package.tech as tech
import pandas as pd
import matplotlib.pyplot as plt

# 銘柄コードの読み込み
stocks = get.topix500()
# 結果保存用データフレーム
df_roc1_u =pd.DataFrame()
df_roc3_u =pd.DataFrame()
df_roc5_u =pd.DataFrame()
df_roc10_u =pd.DataFrame()
df_roc1_l =pd.DataFrame()
df_roc3_l =pd.DataFrame()
df_roc5_l =pd.DataFrame()
df_roc10_l =pd.DataFrame()
# 株価変化率計算
def roc(df, day):
    return (df.shift(-day)["Close"]-df["Close"])/df["Close"]*100
# ヒストグラム作成
def hist(df_roc, later, filename):
    # x軸の幅
    r_max = 40
    r_min = -40
    fig, ax = plt.subplots(figsize=(16, 12))
    # ヒストグラム作成
    n, bins, patches = ax.hist(df_roc, range=(r_min, r_max), bins=40, color="lightgray", align='left', density=True)
    # 負の変化率のbinを赤色に変更
    for i in range(20):
        patches[i].set_facecolor('#ffb6b9')
    # グラフの設定
    ax.tick_params(labelsize=20)
    ax.set_xlabel('Rate of Change[%]', fontsize=24)
    ax.set_ylabel('Frequency', fontsize=24)
    ax.set_ylim(0,0.21)
    ax.set_xticks([i for i in range(-42,42,2)])
    ax.set_xticklabels(['{}~{}'.format(i, i+2) for i in range(-42,42,2)], rotation=90)
    ax.set_title('Bollinger Bands (period=20)', fontsize=24)
    prob = len(df_roc[df_roc[0]>0])/len(df_roc[0])*100
    prob_bar = 100-prob
    ax.text(-31, 0.115,'下落確率', fontsize=40, color="#ffb6b9",fontname="MS Gothic")
    ax.text(-30, 0.1,f'{round(prob_bar,2)}%', fontsize=40, color="#ffb6b9")
    ax.text(19, 0.115,'上昇確率', fontsize=40, color="lightgray",fontname="MS Gothic")
    ax.text(20, 0.1,f'{round(prob,2)}%', fontsize=40, color="lightgray")
    ax.text(-40, 0.175,f'{filename[5:]}日後', fontsize=40, fontname="MS Gothic")
    print(filename,round(prob,2),round(prob_bar,2))
    # グラフ保存
    plt.savefig('{}.png'.format(filename), bbox_inches="tight")

for code in stocks.code:
    print(code)
    # 価格データ取得
    price_data = get.price(code)
    data = price_data.copy()
    # dataフレームにボリンジャーバンドのデータを追加
    tech.bb(data, period=20)
    # ローーバンドを下回るか計算
    data["sign_l"] = data["Close"]-data["bb_l"]
    # アッパーバンドを上回るか計算
    data["sign_u"] = data["bb_u"]-data["Close"]
    # 数日後の株価変動率計算
    data["roc1"]   = roc(data,1)
    data["roc3"]   = roc(data,3)
    data["roc5"]   = roc(data,5)
    data["roc10"]  = roc(data,10)
    # 結果データフレームにボリンジャーバンドをから出たデータフレームのみ追加
    df_roc1_u = pd.concat([df_roc1_u, data[data["sign_u"]<0].roc1])
    df_roc3_u = pd.concat([df_roc3_u, data[data["sign_u"]<0].roc3])
    df_roc5_u = pd.concat([df_roc5_u, data[data["sign_u"]<0].roc5])
    df_roc10_u = pd.concat([df_roc10_u, data[data["sign_u"]<0].roc10])

    df_roc1_l = pd.concat([df_roc1_l, data[data["sign_l"]<0].roc1])
    df_roc3_l = pd.concat([df_roc3_l, data[data["sign_l"]<0].roc3])
    df_roc5_l = pd.concat([df_roc5_l, data[data["sign_l"]<0].roc5])
    df_roc10_l = pd.concat([df_roc10_l, data[data["sign_l"]<0].roc10])

# ヒストグラム作成
hist(df_roc1_l,1,'l_roc1')
hist(df_roc3_l,3,'l_roc3')
hist(df_roc5_l,5,'l_roc5')
hist(df_roc10_l,10,'l_roc10')

hist(df_roc1_u,1,'u_roc1')
hist(df_roc3_u,3,'u_roc3')
hist(df_roc5_u,5,'u_roc5')
hist(df_roc10_u,10,'u_roc10')

追加検証(その他のパラメータ)

▼ 買いサイン
ローバンドを下回った数日後の株価の変化率(買いサインから何%株価が変動したか)の分布を示します.
グレーが上昇,赤が下落を表します.

図2.png

▼ 売りサイン
アッパーバンドを上回った数日後の株価上昇率(売りサインから何%株価が変動したか)の分布を示します.

図2.png

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

djangoでKeyError: ('app_label', 'self.model_name_lower')が出た時の対処方

発生タイミング

モデルの一部変更を行い、makemigration実行した際に発生

エラーの意味

調べたところKeyErrorとはpythonの辞書型のエラーというものらしい。
実際のエラー文を一部抜粋すると以下のような形になっていた。

エラー文、terminal
 model_state = state.models[app_label, self.model_name_lower]
KeyError: ('アプリ名', '項目名')

上段の文章から考えるとアプリの中の、項目名がおかしいよとのこと。

最初はモデル設計が違うのかと該当箇所を調べ、項目名等を修正したが直らず。
そもそもmodels.pyの中に該当の項目名すら無いのに発生してしまいました。

調べていくとmigrationファイルが悪さをしている可能性があるということがわかったので、一旦マイグレーションファイルの履歴を確認することにしました。

マイグレーションの履歴を確認

% python manage.py showmigrations

すると以下のような結果に

app_name
 [ ] 0001_initial
 [ ] 0002_auto_20201221_1907
 [ ] 0003_auto_20201231_0005
 [ ] 0004_auto_20210125_1430
 [ ] 0005_auto_20210126_0951
 [ ] 0006_auto_20210213_2037
 [ ] 0007_auto_20210223_1819
 [ ] 0008_auto_20210228_1527

[ ]が空白になっているところは実行されていないということらしいので、再度migrateを実行すると案の定エラーが発生。

  Applying app_name.0002_auto_20201221_1907... OK
  Applying app_name.0003_auto_20201231_0005... OK
  Applying app_name.0004_auto_20210125_1430... OK
  Applying app_name.0005_auto_20210126_0951... OK
  Applying app_name.0006_auto_20210213_2037... OK
  Applying app_name.0007_auto_20210223_1819... OK
  Applying app_name.0008_auto_20210228_1527...Traceback (most recent call last):
  File "manage.py", line 22, in <module>
    main()  以下エラー文

これでエラーを起こしているマイグレーションファイルが特定できました!

該当のマイグレーションファイルを見ると

operations = [
        migrations.RenameField(
            model_name='クラス名',
            old_name='古い項目名',
            new_name='新しい項目名',
        ),

どうやら以前modelの項目名を変更を行い、makemigrationを行った際に既存のモデル名からリネームする処理のファイルが出来てしまっていたようです。

その後、モデルのクラス名そのものを変更してしまった為、起こったエラーでした。

該当のマイグレーションファイルを削除し再度実行した結果無事に解決出来ました!

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

【全13サイト】各金融機関サイトにPythonとSeleniumを使ってログインするサンプル一覧と注意点

「各金融機関の明細データを自動で取得できるようにしたい」と思い立ち、手始めにログイン部分のコードを書いてみました。

最終的に13サイト分できあがったので一覧化しました。参考になれば幸いです。

全体的な処理の流れ

各サイトとも、全体的な処理の流れは同じです。異なる部分はIDやパスワードを入力する要素を取得する部分です。

全体的な処理の流れを下記記事で解説しているので、こちらもあわせて確認してみてください。

楽天銀行サイトにPythonとSeleniumを使ってログインする

サンプルコード紹介記事一覧

2021/2/28時点のサイト数:全13サイト

銀行

証券会社

ロボアド

iDeco

クレジットカード会社

注意点

コーディング時における注意点や、実際に試していて気づいた点などを紹介します。

その1. 設定ファイルの管理は厳重に

今回のサンプルコードでは、ログイン情報を"login_info.json"というJSON形式のテキストファイルに保存しています。

万が一このファイルが外部に漏れたりすると一大事なのでくれぐれも管理方法には注意しましょう!

できれば暗号化して、毎回パスワード入力して復号化してから使う、くらいしてもいいかもしれません。

その2. 要素を取得する時のメソッド名に注意

ログインボタンのように1つの要素を取得したい時、
find_element_by_***
ではなく、
find_elements_by_***
("s"があるほうのメソッド)
を使用すると以下のようなエラーが発生します。

AttributeError: 'list' object has no attribute 'click'

"s"は複数形の場合に使用しますよね。
そのため、
find_elements_by_***
はリスト型のオブジェクトを返却します。

ダメな例

# "s"付きのメソッドを呼び出す
button = browser.find_elements_by_name("ボタン名")
button.click() # click()はリスト型オブジェクトには存在しないメソッドなのでエラー発生

正しい例

button = browser.find_elements_by_name("ボタン名")
button.click()

IDEの自動補完でメソッド名選択する時に間違いやすい

このコーディング誤りは、PyCharmやVSCodeのようなIDE環境で、コードの自動補完機能を使っている場合に間違いやすいです。
しかもビルド時にはエラーにならないため気が付きにくいです。

その3. Ajax要素のロード完了に注意

画面遷移後、ページロードが完了するまでWebDriverWait()を使って待機させます。

# ページロード完了まで待機
WebDriverWait(browser, 10).until(
    ec.presence_of_element_located((By.ID, "任意の要素のID"))
)

ここで指定する要素を気をつけないと、思うような待機ができず後続処理が失敗するケースがあります。

画面によっては、下記例のように非同期通信で画面要素を取得・描画している場合があります。

home_rakuten.png

WebDriverWait()で指定する要素は、極力後続処理で参照する要素を指定するようにしましょう。

普段と違う画面が挟まることがある

ログインしたあと、普段と違う画面が挟まることがあります。

例. キャンペーンのポップアップ

home_wn.png

この他にもパスワード変更や重要なお知らせなどがある場合、通常と違う画面が挟まることがあります。

ログインしたあと、色々操作する予定の場合はこういった画面遷移時の挙動も考慮に入れておく必要があります。

さいごに

以上、各金融機関サイトにPythonとSeleniumを使ってログインするサンプル一覧と注意点でした!

ログイン部分については一通りできあがったので、次は明細データを取得する部分を作っていく予定です。できあがったらまた記事アップしていきますね。

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

【初学者向け】Python基礎を総まとめ~アプリも紹介~

この記事はこれからPythonを学んでいこうという人のための記事になります。
AIや解析などで注目が上がるpythonの基本をまとめてみました。
言葉の説明は細かくはしていませんので、わからないことは都度都度でググればより理解が深まると思います。
内容に合わせて「寄り道編」を用意しています。
読み進めると文字当てゲームや電卓、パスワード解析などのアプリ紹介もしているので手を動かしてやってみたい人は参考にしてみてください。

この記事を読むことで学習できる内容は以下の通り。
① 5つの基本操作 [ 文字 / 描画 / 数字 / 計算 / インプット ]
② 基本その2:[リスト型 / タプル型 / 辞書型 / 関数 / 条件分岐 / 応用 ]
③ 基本その3:[ while / for / ネスト処理 / 変換 ]
④ 基本その4:[ バリデーション / ファイル操作 ]
⑤ 基本その5:[ クラスとオブジェクト ]

実行環境

・python 3.9
・VSCode
・MacOS 10.15.7

5つの基本操作:[ 文字 / 描画 / 数字 / 計算 / インプット ]

### 1 文字出力
print("Hello World") # Hello World



### 2 線を描画 (例)鳥居
print("_ _ _ _ _ _")
print("_|_ _|_ _|_")
print(" |       | ")
print(" |       | ")
print(" |       | ")
# _ _ _ _ _ _
# _|_ _|_ _|_
#  |       | 
#  |       | 
#  |       | 



### 3 変数:文字の代入
char_name = "SU-NE-O"
char_age = "11"
print("His name is " + char_name) # His name is SU-NE-O
print("He is " + char_age + " years old") # He is 11 years old



### 4 String[文字]の挙動
print("This is\nPython")
# This is 
# Python ←\nで改行
print("This is\"Python") # This is"Python ←\以下の"が文字認識

text = "Sample Text"
print(text.lower())  # sample text ←小文字変換
print(text.upper())  # SAMPLE TEXT ←大文字変換
print(text.isupper())  # False ←大文字をTrue or Falseで返す
print(text.upper().isupper())  # True ←先にupper()で変換されるのでTrue
print(text[0])  # S ←文字の一番最初を返す。2番目は1で、最後は-1
print(text.replace("Sample", "Python"))  # Python Text に置き換わる



### 5 Number[数字]の挙動
print(2)  # 2
print(3.4)  # 3.4
print(8 + 3)  # 11.3 足し算
print(8 - 3)  # 5 引き算
print(8 * 3)  # 24 掛け算
print(8 / 3)  # 2.6666666666666665 割り算
print(8 % 3)  # 2 あまり
print(8 * (3 + 2))  # 40 四則演算



# 6 数字のいろんな変換
num = -5
print(str(num) + " is my number")  # -5 is my number ←number型はstringに変えないとエラー
print(abs(num))  # 5 絶対値
print(pow(5, 3))  # 125 累乗の計算 5の3乗
print(max(6, 4))  # 6 数が多い方を出力
print(max(6, 4))  # 4 数が少ない方を出力
print(round(3.3))  # 3 四捨五入。 3.7の場合4
  # 以下はmathが必要
  from math import *
  print(floor(3.7))  # 3 引数の整数
  print(ceil(3.7))  # 4 引数の数の繰り上げ整数
  print(sqrt(36))  # 6.0 ルート(平方根)計算



# 7 ユーザーからの入力 ←commandでの操作を反映させるinput()
name = input("Enter your name: ")  # Enter your name: [任意の名前]
age = input("Enter your age: ")  # Enter your name: [任意の年齢]
print("Hello " + name + "! You are " + age)
# Hello [任意の名前]! You are [任意の年齢]

ちょっと寄り道 ~ 足し算の電卓をcommandで

### 8 ユーザーが入力した値で計算
num1 = input("Enter a number: ")
num2 = input("Enter another number: ")
result = float(num1) + float(num2) # ユーザーから入力された値はデフォルトで文字なので、数字をfloat()で囲む
print("足し算の結果は " + str(result) + " だよ") 

# Enter a number: 2.5
# Enter another number: 3.7
# 足し算の結果は 6.2 だよ

基本その2:[リスト型 / タプル型 / 辞書型 / 関数 / 条件分岐 / 応用 ]

リスト型:

基本形は[ ]。設定されたリスト内の値は操作可能。

### 9 リストの基本操作
friends = ["Yuta", "Kana", "Tatsuya", "Jim", "Maria"]

print(friends)  # ['Yuta', 'Kana', 'Tatsuya', 'Jim', 'Maria']
print(friends[0])  # Yuta
print(friends.index("Kana"))  # 1 値のインデックス番号取得
print(friends[1:])  # ['Kana', 'Tatsuya', 'Jim', 'Maria'] インデックス番号が 1以上のものを出力
print(friends[1:3])  # ['Kana', 'Tatsuya'] インデックス番号が 1以上3未満を出力
friends[1] = "Abe" # インデックス1をAbeで代入 ↓
print(friends) # ['Yuta', 'Abe', 'Tatsuya', 'Jim', 'Maria']




### 10 そのほかのリスト操作
numbers = [4, 5, 10, 23, 36, 42]
friends = ["Yuta", "Kana", "Tatsuya", "Jim", "Maria"]

friends.extend(numbers)  # リストをつなげる
print(friends)
# ['Yuta', 'Kana', 'Tatsuya', 'Jim', 'Maria', 4, 5, 10, 23, 36, 42]

friends.append("Baiden")  # リストの最後に追加する
print(friends) #['Yuta', 'Kana', 'Tatsuya', 'Jim', 'Maria', 'Baiden']

friends.insert(1, "Trump") # insert(index番号,値)で特定の場所に挿入
print(friends) # ['Yuta', 'Trump', 'Kana', 'Tatsuya', 'Jim', 'Maria']

friends.remove("Jim")  # remove(値)で値をリストから削除
print(friends)  # ['Yuta', 'Kana', 'Tatsuya', 'Maria']

friends.clear()  # clear()でリスト内を全削除
print(friends)  # []

friends.pop()  # リスト最後の値を削除
print(friends)  # ['Yuta', 'Kana', 'Tatsuya', 'Jim']

print(friends.count("Tatsuya")) # 1 count(値)でリスト内の値の数を出力

friends.sort() # アルファベット順(数字なら小さい順)に並び替える
print(friends) # ['Jim', 'Kana', 'Maria', 'Tatsuya', 'Yuta']

friends.reverse() # 元のリストの逆で並び替え
print(friends) # ['Maria', 'Jim', 'Tatsuya', 'Kana', 'Yuta']

friends2 = friends.copy()  # リストをコピー
print(friends2)  # ['Yuta', 'Kana', 'Tatsuya', 'Jim', 'Maria']

タプル型:

基本形は( )。タプルは変更や削除ができないなどリストと比較して限定的。
使うとコードの保守性が高くなる。定数リストとも言われている。

### 11 タプルは操作が限定的。

color = ("red", "blue")
print(color) # ('red', 'blue') アクセスはできる。

color = ("red", "blue")
print(color)  # ('red', 'blue')

color[0] = "black"
print(color) # TypeError: 'tuple' object does not support item assignment

print(color.index("red")) # 0 
print(color.count("blue")) # 1 
# タプルでできる他のメソッドは以下の記事を参考に。
# https://blog.codecamp.jp/python-tuple

辞書型:

基本形は{ key:value }。keyとvalueの組み合わせが含まれているデータ型。
特定の要素を検索、追加、削除でき、リストと違って順番がないので要素を取り出す場合はkeyを指定する。

### 12 辞書型の基本 "key": "value"

months = {
    "Jan": "January",
    "Feb": "February",
    "Mar": "March"
}
print(months["Jan"])  # January  valueが出力される
print(months.get("Mar"))  # March  存在するkeyを取得。なければNoneを返す
print(months.get("Lux", "Not a valid key")) # Not a valid key  keyがない時の定型文を指定できる



### 13 keyが数字の場合と検索 / 追加 / 削除

weights = {
    0: "big",
    1: "small",
    2: "normal"
}

print(weights[1])  # small
print(0 in weights.keys())  # True  .keys()でkeyの存在判定ができる
print("small" in weights.values())  # True  .values()でvalueの存在判定ができる

weights[3] = "can not judge"  # 要素の追加
print(weights)  # {0: 'big', 1: 'small', 2: 'normal', 3: 'can not judge'}

weights.setdefault(5, "so big")  # setdefault()でも追加できる
print(weights) # {0: 'big', 1: 'small', 2: 'normal', 3: 'can not judge', 5: 'so big'}

weights.pop(1)  # 要素の削除(keyを指定)
print(weights)  # {0: 'big', 2: 'normal', 3: 'can not judge', 5: 'so big'}

weights.clear()  # 要素の全削除
print(weights)  # {}

関数:

基本形は def 関数名 ( ):

### 14 関数の基本
def say_hello():
    return "Hello User" # say_halloを呼び出すと Hello User を返す

print(say_hello()) # Hello User



### 15 引数指定
def say_hello(name, age):
    return "Hello " + name + " ! your age is " + str(age) + " !"

print(say_hello("Mike", 22))  # Hello Mike ! your age is 22 !



### 16 関数で計算 (例)立方体の体積
def cube(num):
    return num*num*num

print(cube(4))  # 64 

条件分岐:

### 17 if文で条件分岐
is_male = True

if (is_male):
    print("you are a male")
else:
    print("you are not a male")

# you are a male ←is_maleがTrue
# you are not a male ←is_maleがFalse



### 18 条件が2つ以上
is_male = True
is_tall = True

# AまたはBの場合
if is_male or is_tall:
    print("you are a male or tall or both")
else:
    print("you neither male nor tall")
# you are a male or tall or both ←is_maleがTrue または is_tallがTrue
# you are not a male ←is_maleがFalse ←is_male と is_tallどちらもFalse


# AかつBの場合
if is_male and is_tall:
    print("you are a male and tall")
else:
    print("you are not a male or tall or both")
# you are a male and tall ←is_maleとis_tallがTrue
# you are not a male or tall or both ←is_maleまたはis_tallまたはどちらもFalse


# さらに条件分岐したいときは elif
if is_male and is_tall:
    print("you are a male and tall")
elif is_male and not(is_tall):
    print("you are a male but not tall")
elif is_tall and not(is_male):
    print("you are not a male but tall")
else:
    print("you are not a male and tall")

# you are a male and tall ←is_maleとis_tallがTrue
# you are a male but not tall ←is_maleがTrueでis_tallがFalse
# you are not a male but tall ←is_maleがFalseでis_tallがTrue
# you are not a male and tall  ←is_maleとis_tallがFalse

応用: 条件式で遊んでみる

### 19 最大値の出力
def max_num(num1, num2, num3):
    if num1 >= num2 and num1 >= num3:
        return num1
    elif num2 >= num1 and num2 >= num3:
        return num2
    else:
        return num3

print(max_num(3, 40, 8)) # 40
print(max_num(300, 40, 8)) # 300




### 20 文字の判断
def judge(kinds, name):
    if kinds == 'dog' and name == 'Hachi':
        return 'The dog is Hachi'
    elif kinds != 'dog' and name == 'Hachi':
        return 'Hachi. but not dog'
    else:
        return 'can not judge'


print(judge("cat", "Hachi")) # Hachi. but not dog

寄り道 ~ 計算機を関数で作ってみよう

### 21 2つの数字をcommand上で操作

num1 = float(input("Enter first number: "))
op = input("Enter operator( +, -, /, *, % ): ")
num2 = float(input("Enter second number: "))

if op == "+":
    print(num1 + num2)
elif op == "-":
    print(num1 - num2)
elif op == "/":
    print(num1 / num2)
elif op == "*":
    print(num1 * num2)
elif op == "%":
    print(num1 % num2)
else:
    print("Invalid operator")
# 実行後、オペレーションに応じて計算可

基本その3:[ while / for / ネスト処理 / 変換 ]

While文:

ループ処理の鉄板。

### 22 While文のループ

i = 1
while i <= 5:
    print(i)
    i += 1
print("Done with Loop")

# 1
# 2
# 3
# 4
# 5
# Done with Loop
## Whileが完了されるまでは次に行かないのでこれを応用すると推理ゲームが作れる




### 23 whileを使った簡易推理ゲーム

secret_word = "otakara"  # 正解の言葉
guess = ""  # ユーザーが入力した言葉
guess_count = 0 #ユーザーが推理した数
guess_limit = 5 # 推理の上限
out_of_guesses = False # 上限以上ならTrueでWhileから外れる

while guess != secret_word and not(out_of_guesses):
    if guess_count < guess_limit:
        guess = input("Enter guess: ")
        guess_count += 1
    else:
        out_of_guesses = True

if out_of_guesses:
    print("You LOSE!")
else:
    print("You Win!")

# Enter guess: msvps
# Enter guess: skmsp
# Enter guess:  spv
# Enter guess: dmvpsd
# Enter guess: otakara
# You Win!

For文:

ループ処理の鉄板。rangeを使って範囲を指定することもできる。

### 24 言葉を分解するforのループ
for letter in "soccer":  # soccerのアルファベットが分解される
    print(letter)
# s
# o
# c
# c
# e
# r




### 25 配列を使ったforのループ
frinds = ["jim", "kevin", "mike", "karen"]
for friend in frinds:
    print(friend)
# jim
# kevin
# mike
# karen




### 26 range()でより応用が効くループに
for index in range(len(frinds)):
    print(frinds[index])

# jim
# kevin
# mike
# karen




### 27 rangeで決められた範囲だけで処理する場合
for index in range(5):  # 0 ~ 4が範囲。1 ~ 3にしたい場合はrange(1, 4)
    if index == 0:
        print("index is first")
    else:
        print("index is not first")

# index is first
# index is not first
# index is not first
# index is not first
# index is not first

寄り道 ~ パスワード解析をfor文で ~

forを複数回すことで簡単に総当たりのパスワード解析プログラムを作ることができる。

### 28 forを使って任意の4ケタ(1~9の数字)の数字を当てる。

import time
from getpass import getpass # 入力文字が非表示
password = getpass("Enter your password: ")

start_guess = input("Do you start ? (yes / no): ")
start = time.time()
if (start_guess == "yes"):
    for a in range(0, 10):
        for b in range(0, 10):
            for c in range(0, 10):
                for d in range(0, 10):
                    guess_password = str(a) + str(b) + str(c) + str(d)
                    print(guess_password)
                    if(password == guess_password):
                        print("you are password is ----> " + guess_password)
                        break
                    else:
                        continue
                    break
                else:
                    continue
                break
            else:
                continue
            break
        else:
            continue
        break
    finish = time.time() - start
    print(finish, ' sec')
else:
    print("stopped...")


# Enter your password: 
# Do you start ? (yes / no):  

# 0000
# 0001
# 0002
#  ↓
# 1465
# 1466
# 1467
# you are password is ----> 1467
# 0.019086122512817383  sec


# 範囲指定を工夫すればabcやABC、記号なども設定できる。

ネスト処理と変換

### 29 for文を使ってネストリストへのアクセス
number_grid = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
    [0]
]

print(number_grid[0][1]) # index番号は0から始まる
# 2

for row in number_grid:
    for col in row:
        print(col)

# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9
# 0



### 30 文字の変換(多数のデータを判断→自動入力、などに役立つ)
def translate(phrase):
    translation = ""
    for letter in phrase:
        if letter in "AIUEOaiueo":
            translation = translation + "g"
        else:
            translation = translation + letter
    return translation

print(translate(input("Enter your phrase: ")))

# Enter your phrase: dog cat letter tomato
# dgg cgt lgttgr tgmgtg

基本その4:[ バリデーション / ファイル操作 ]

バリデーション

入力などのユーザー処理をコントロールして予期しないエラーハンドリングする。

### 32 例外処理 try except

try:
    number = int(input("Enter your number: "))
    print(number)
except ZeroDivisionError: # 0による徐算エラーハンドリング
    print("divided by zero")
except ValueError: # 不適切な値のエラーハンドリング
    print("Invalid Input")

# そのほかのエラーハンドリング方法は下記URL記事が有益
# https://note.nkmk.me/python-try-except-else-finally/

ファイル操作

open()を使って、ファイルの読み込みができる。

### 33 ファイル操作
open("./data/sample.txt", "r")
# 第1引数にファイルのパス、第2引数に実行関数のモードを記入
# r ... 読み込み
# w ... 書き込み。 同じファイル名のファイルがあった場合は、そのファイルに上書きされる
# a ... 追記。 同じファイル名のファイルがあった場合は、既存のファイルに追記していく
# r+ ... 既存ファイルの読み込み + 書き込み
# そのほかは以下のURLを参照
# https://chappy88.hatenablog.com/entry/2019/08/27/230934


# 34 ファイルの読み込み
select_file = open("./data/sample.txt", "r")
print(select_file.read())
# 1 - this is 1 sample
# 2 - this is 2 sample
# 3 - this is 3 sample
# 4 - this is 4 sample
# 5 - this is 5 sample


### 35 ファイル読み込みの判定
print(select_file.readable()) # True


### 36 ファイルから1行出力
print(select_file.readline()) # 1 - this is 1 sample


### 37 ファイルから行をリスト化して出力
print(select_file.readlines()) # ['1 - this is 1 sample\n', '2 - this is 2 sample\n', '3 - this is 3 sample\n', '4 - this is 4 sample\n', '5 - this is 5 sample']



### 38 追記のa
select_file = open("./data/sample.txt", "a")
select_file.write("\n6 - this is 6 sample")  # 改行するときは\nをいれる
select_file = open("./data/sample.txt", "r")
print(select_file.read())
# 1 - this is 1 sample
# 2 - this is 2 sample
# 3 - this is 3 sample
# 4 - this is 4 sample
# 5 - this is 5 sample
# 6 - this is 6 sample


### 39 上書きのw
select_file = open("./data/sample.txt", "w")
select_file.write("6 - this is 6 sample")
select_file = open("./data/sample.txt", "r")
print(select_file.read())
# 6 - this is 6 sample


### 40 新規作成
select_file = open("./data/index.html", "w")
select_file.write("<p>Hello World</p>")
select_file = open("./data/index.html", "r")
print(select_file.read())
# ./data/index.htmlがない場合新規作成される

基本その5:[ クラスとオブジェクト ]

クラスとオブジェクト

クラスとは簡単に言うととある処理をまとめたもの(抽象化されたもの)で、オブジェクトはそれぞれの要素。例えば「ヒト」と言うクラスを作った時、「ヒト」の情報には、名前・血液・身長など、おおよその人間に当てはまる情報が抽象化できる。これをクラスとして定義する。pythonはオブジェクトサポート言語なので、今まで登場してきた文字列/数値/リスト/タプルなど、これらは全てクラスから作られたオブジェクトになる。

# 他のファイルで定義されたクラスを呼び出すときは、from ファイル名 import class名 で呼び出す。
### 42 クラスとオブジェクト
from Student import Studentinfo

student1 = Studentinfo("Jim", "Business", 3.1, False)

print(student1.name)
# Jim


# ************************ #
# Student.py  
class Studentinfo:

    def __init__(self, name, major, gpa, is_on_probation):
        self.name = name
        self.major = major
        self.gpa = gpa
        self.is_on_probation = is_on_probation

# __init__は、クラスを「インスタンス化」する際に必要なメソッドで、「インスタンス化」とは、抽象的なクラスという概念に個性を与えて具体化することこと。上の例では、Studentクラスを具体的にするために、引数にnameやmajorを定義することで、情報を与えた側が同じ書式で呼び出せば、一定の情報を返すことができる。

寄り道:クラスを使ってクイズを作ってみる

### 43 クイズゲーム

from Questions import Question
question_prompts = [
    "(第1問)猿も? \n(a) 木から落ちる\n(b) 棒にあたる\n(c) 川流し \n\n",
    "(第2問)2021年2月時点の内閣総理大臣の名前は? \n(a) 安倍 晋三\n(b) 麻生 太郎\n(c) 菅 義偉 \n\n",
    "(第3問)2020年のアメリカ大統領選挙で勝った人は? \n(a) バルデン\n(b) バイデン\n(c) バイトル \n\n",
]
questions = [
    Question(question_prompts[0], "a"),
    Question(question_prompts[1], "c"),
    Question(question_prompts[2], "b"),
]


def run_quiz(questions):
    score = 0
    for question in questions:
        answer = input(question.prompt)
        if answer == question.answer:
            score += 1
    print("あなたは" + str(score) + "/" + str(len(questions)) + " 正解しました。")


run_quiz(questions)

# (第1問)猿も? 
# (a) 木から落ちる
# (b) 棒にあたる
# (c) 川流し 

# (第2問)2021年2月時点の内閣総理大臣の名前は? 
# (a) 安倍 晋三
# (b) 麻生 太郎
# (c) 菅 義偉 

# (第3問)2020年のアメリカ大統領選挙で勝った人は? 
# (a) バルデン
# (b) バイデン
# (c) バイトル 

# あなたは / 3 正解しました。

# ****************************************** #
#Questions.py 

class Question:
    def __init__(self, prompt, answer):
        self.prompt = prompt
        self.answer = answer

まとめ

以上、pythonの基本的なことを紹介しました。
外部ライブラリを活用することでさらにできることが増えていくので、次回はライブラリ別に紹介できればと思います。

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

続・マクドナルドで一日分の栄養を取れる組み合わせを計算したらさらに衝撃の結果に

サイドサラダ禁止!

前回は見事にサイドサラダ王になってしまいした。敗因(勝因)は明確で、サイドサラダは1gあたりのカロリーが全96商品中で最も少ないというとてもチートな商品だったのです。
1.jpg

ですので、マイスーパーコンピューターのソルバーがサイドサラダに全振りしてきたというわけです。そもそも、"サイド"サラダのくせにメインディッシュとするなんて邪道中の邪道です。そこで今回はサイドサラダ禁止にして、改めてマクドナルドで最も健康的なメニューを調べました。

変更点は2か所

サイドサラダ禁止という条件に加え、炭水化物の基準量が高すぎるとのご指摘を頂いたので基準量を675gから438.75gに変更しました。体重60kgの30歳男子という条件は前回と同じです。

栄養素 基準量
たんぱく質 (g) 39
脂質 (g) 75
炭水化物 (g) 438.75
ナトリウム (mg) 5,000
カリウム (mg) 3,000
カルシウム (mg) 738
リン (mg) 600
鉄 (mg) 6.3
ビタミンA (μg) 625
ビタミンB1 (mg) 1.4
ビタミンB2 (mg) 1.6
ナイアシン (mg) 15
ビタミンC (mg) 100
コレステロール (mg) 0
食物繊維 (g) 21
食塩相当量 (g) 5.0

コードは以下2か所以外は前回と同じです。

mac2.py
# Constraints
problem +=  47.7*AA+ 55.8*AB+ 66.6*AC+ 48.4*AD+ 52.4*AE+ 36.4*AF+ 31*AG+ 27.1*AH+ 57.5*AI+ 38.9*AJ+ 36.8*AK+ 35.5*AL+ 57.5*AM+ 9.5*AN+ 38.1*AO+ 27.3*AP+ 27.2*AQ+ 31.4*AR+ 30.8*AS+ 39.2*AT+ 37.7*AU+ 37*AV+ 47.3*AW+ 30.3*AX+ 0*AY+ 92.9*AZ+ 40.2*BA+ 41.8*BB+ 35.8*BC+ 26.4*BD+ 29.1*BE+ 52.7*BF+ 30.2*BG+ 34.3*BH+ 41.9*BI+ 42.4*BJ+ 42.4*BK+ 31.2*BL+ 0.1*BM+ 62.8*BN+ 76*BO+ 44.1*BP+ 31*BQ+ 38.9*BR+ 36.8*BS+ 35.5*BT+ 48*BU+ 31.4*BV+ 30.8*BW+ 49.1*BX+ 47.6*BY+ 64.4*BZ+ 30.3*CA+ 41.8*CB+ 45*CC+ 29.1*CD+ 35.5*CE+ 36.3*CF+ 38.2*CG+ 37.6*CH+ 32.7*CI+ 9.6*CJ+ 4.3*CK+ 28.3*CL+ 5.6*CM+ 2.3*CN+ 128.5*CO+ 18.2*CP+ 1.1*CQ+ 18.3*CR+ 1.2*CS+ 17.2*CT+ 21.1*CU+ 24.7*CV+ 39.2*CW+ 13.1*CX+ 1.6*CY+ 13.3*CZ+ 7.7*DA+ 26.4*DB+ 31.5*DC+ 26.8*DD+ 90.4*DE+ 167.7*DF+ 5.2*DG+ 64.3*DH+ 51*DI+ 28*DJ+ 37.7*DK+ 53.4*DL+ 9.7*DM+ 46.8*DN+ 44.1*DO+ 40.6*DP+ 2*DQ+ 1.2*DR >= 438.75 #111行目変更

problem += CN <= 0 #125行目追加

そして目的も同じく一日必要な栄養素を満たす最もカロリーの低い商品の組み合わせとします。金に糸目はつけません!

結果

さあ行きますよ。科学の粋を集めたソルバーが火を吹きます。
2.jpg

3.jpg

最適な組み合わせは存在するようです!しかもわずか0.28秒で計算完了!速い!世界最速のスーパーコンピューター富岳がそろばんに見えるぜ!

算出された商品の種類は8種類。偏りなく栄養を摂取するにはやはりこのぐらいの数は必要ですよね。以下がその組み合わせです!

ソフトツイスト(1個)
次世代の食事はデザートから入ります。
4.jpg

シャカチキ チェダーチーズ味シーズニング(1個)
調味料も直に摂取します。
shaka.jpg

りんご&クリーム(2個)
ちょっと健康的になってきました。
ringo.jpg

ストロベリージャム(2個)
隠し味も重要です。
スト.jpg

マックフルーリー® オレオ®クッキー(3個)
メインディッシュの前にデザートを食べるのは次世代の食事では鉄板です。
5.png

シャカチキ レッドペッパー味シーズニング(3個)
口の中が甘くなったので辛みで回復します。
6.jpg

バターパット(7個)
ミルクのコクと風味を楽しみます。
butter.jpg

そしていよいよメインディッシュ…!
ケチャップ(48個)
7.png

おわかりでしょうか。メニューにある調味料群は、実はメインディシュだったんです!これが次世代の健康メニューです!この組み合わせによって以下の通り、一日必要な栄養素を満たす極めて健康的な栄養を摂取することができます。

栄養素 摂取量
エネルギー(kcal) 2,541
たんぱく質 (g) 42.2
脂質 (g) 75.0
炭水化物 (g) 439
ナトリウム (mg) 11,317
カリウム (mg) 6,020
カルシウム (mg) 881
リン (mg) 1,121
鉄 (mg) 6.3
ビタミンA (μg) 1,373
ビタミンB1 (mg) 1.9
ビタミンB2 (mg) 1.6
ナイアシン (mg) 15.6
ビタミンC (mg) 922
コレステロール (mg) 190
食物繊維 (g) 22.3
食塩相当量 (g) 27.1
kekka2.txt抜粋
ストロベリージャム: 2.0
バターパット: 7.0
りんご&クリーム: 2.0
ケチャップ: 48.0
シャカチキ チェダーチーズ味シーズニング: 1.0
シャカチキ レッドペッパー味シーズニング: 3.0
ソフトツイスト: 1.0
マックフルーリー® オレオ®クッキー: 3.0

これが大人のハッピーセット!

いかがでしたか?ビッグマックもポテトもナゲットも食べずサイドサラダさえも食べず、ひたすらケチャップをペロペロ舐める。これが次世代の健康メニューです!伊能忠敬ならこれだけで日本一周するでしょう。これこそ真の完全食です。最高!塩分が多いので、真夏に溶鉱炉で働くといった汗だくの環境にぴったりですね。

ちなみにケチャップは無料とのことなので、店員さんにいくつもらえるのか確認しました。
私「ケチャップはいくつまで貰えるんですか?」
店員「常識の範囲内まで
・・・「常識」はコンピュータじゃ解けないじゃん!

元ブログ(プログラミングを主題とするように著者自身が加筆)
https://www.transrecog.com/diary/2021/02/27/post-1426/

(前回の反応)

「トレーニーのPFCでも是非計算してもらいたい」「線形計画法、これを探してたんだ。アセットマネジメントに使えそう」といった手法に興味を持ってくれた方がたくさんいらっしゃいました。笑ってくれて、そんなに難しくなく、数理最適化は役立ちそう、と思っていただければ私の目的は達成です。

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

平均点を求める

仕様
・while を使って無限ループを作成します。
・その中で「点数を入力してください:」と表示して、テストの点数(整数値)を入力してもらいます。
・平均点は「点数の合計値 / 人数」で求められるので、いったん、入力された点数を合計値に加算します。また、人数はカウンタ変数を用います。
・-1 と入力されたら、そこで繰り返し処理を終了します。
最後に「○○人のテストの平均点は△△点です」と表示してください。

score=0
count=0
while True:
    t=int(input("点数を入力してください:"))

    if t==-1:
        break
    else:
        score+=t
        count+=1
average=score/count
print(count,"人のテストの平均値は",average,"点です。")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

送料込み価格の計算

仕様
・「税抜価格を入力してください:」と表示し、キーボードから税抜価格の入力を受け付けます。
・税抜価格が入力されたら、税込価格を計算します。小数点以下は切り捨てにします。
・入力された税込価格が2000円以上の場合「送料は無料です」と表示しますが、2000円未満の場合は「送料として450円かかります」と表示して送料450円分を価格にプラスします。
・「送料込みの価格は○○○円です。」というように、画面に送料込みの価格を画面に表示します。

price=int(input("税抜き価格を入力してください:"))
tax_price=int(price*1.1)

if tax_price>=2000:
    print("送料は無料です")
    total_price=tax_price
else :
    print("送料として450円かかります")
    total_price=tax_price+450

print("送料込みの価格は",total_price,"円です。")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

植物の水やりをIoTする その3:Raspberry piを用いての給水タイミング制御

前回:植物の水やりをIoTする その2:モーター制御とトランジスタ

誰が水やりを制御するべきか

前回と前々回で、水やりをするべきタイミングと、実際に水やりをする手段を獲得した。
これらを組み合わせることによって、ようやく適切なタイミングでの給水をすることができるようになる。
単純にArduinoのなかにそのプログラムを組み込んでしまえば、5Vの電源に繋ぐだけでスタンドアロン稼働させることができる。
それはそれで有用ではあるだろうが、今回はそれを少し拡張させてみたい。

命令通りに従わせる

Arduino君には手足になってもらうこととします。
水分量が何%であるかを獲得できても、何%になったら給水を行うかの判断を同時にやらなければならないわけではない。
値の獲得とその値を元に何を判断するかは、全く別問題である。
なので、Arduinoには「センサー値を送れと言われたら送り、給水しろと言われたら給水をする」という仕事のみを担当してもらうことにする。
前回、シリアル通信を通してArduinoとPCで文字列のやりとりを行った。
シリアル通信での文字のやり取りは一文字ずつであり、しかも今時のjsonなどといった形式の解析機能などArduinoは持っていないため、受け取る値は固定長形式とする。

Arduinoへ送る文字列のうち1文字目を命令とし、給水の場合にその後何秒間給水するかの数値を送る。

autoWatering.ino
int SENSOR_PIN = A0;    // select the input pin for the potentiometer
int MOTER_PIN = 2;      // select the pin for the LED

int CMD_SENSOERING = 0;
int CMD_WATERING = 1;

void setup() {
  pinMode(MOTER_PIN, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  // シリアル値が来たら実行する
  int Vib = Serial.available();
  if (Vib > 0) {
    String VibON;

    // 固定長で一文字目は命令とする
    int command = Serial.read() - 48;

    // 残りの文字があればモーター稼働時間
    for (int i = 0; i < Vib - 2; i++) {
      VibON += String(Serial.read() - 48);
    }
    Serial.read(); // 最後は改行文字

    if (command == CMD_SENSOERING) {
      int value = analogRead(SENSOR_PIN);
      int per = 100 - (value - 200) / 3;
      Serial.println(per);
      Serial.flush();

    } else if (command == CMD_WATERING) {
      int v = VibON.toInt();
      if (0 < v) {
        digitalWrite(MOTER_PIN, HIGH);
        delay(v);
        digitalWrite(MOTER_PIN, LOW);
      }
    }
  }
}

これで、Arduinoはただ命令を受け取ってその通りに処理を実行するだけの機械と化した。

Raspberry piで制御する

以下のようにラズパイとArduinoを接続する。
IMG_0069-1.jpg
具体的にラズパイの環境をどう構築したかに関しては、今回は割愛する。
ラズパイとはつまりはとても小さなコンピューターなので、最近のやつだとWifiやBluetoothを内蔵していたりと普通に使う分にはパソコンとなんら変わることがないためだ。
植物は室内栽培に適した観葉植物のスパティフィラム。
すでに我が家に来てから二週間が経過したが、順調に葉の数を増やしている。
一度も外に出したことはないが、たった7WのLEDでも十分な光量を確保できることが判明した。

さて、ラズパイから以下のpythonのコードを実行する。
こちらは給水用のコード。
一回実行すると3秒間の給水を行う。

temporaryWatering.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import serial
#ser = serial.Serial('/dev/cu.usbmodem141101', 115200) # Mac用
ser = serial.Serial('/dev/ttyACM0', 115200) # ラズパイ用
ser.write(b"13000")
ser.close()

こちらはセンサー値取得。
一回実行すると、センサー値を返す。
センサー値は実行のたびに多少の上下幅があったため、10回の平均値とした。

receiveMoisture.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import serial
import time
#ser = serial.Serial('/dev/cu.usbmodem141101', 115200) # Mac用
ser = serial.Serial('/dev/ttyACM0', 115200) # ラズパイ用
sensor_value = 0
for i in range(10):
    ser.write(b"0")
    a = ser.readline()
    while ser.in_waiting:
        a = a + ser.readline()
    sensor_value += int(a.split(b'\r')[0])
    time.sleep(1)
sensor_value //= 10
print(sensor_value)
ser.close()

これで、ラズパイからの制御をする準備が整った。
これらを組み合わせて、以下の通り完成となる。

autoWatering.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import serial
import pymysql.cursors

while True:
    # 通信は都度開閉する
    #ser = serial.Serial('/dev/cu.usbmodem141101', 115200) # Mac用
    ser = serial.Serial('/dev/ttyACM0', 115200) # ラズパイ用

    # センサー値は割と上下するので、10回の平均をとる
    sensor_value = 0
    for i in range(10):
        ser.write(b"0")
        a = ser.readline()
        while ser.in_waiting:
            a = a + ser.readline()
        sensor_value += int(a.split(b'\r')[0])
    sensor_value //= 10
    print(sensor_value)

    # 水分量が90%を切ったら3秒間給水する
    if (sensor_value < 90):
        ser.write(b"13000")
    ser.close()

    # 1時間に1回
    time.sleep(3600)

appendix

これで、ようやく自動給水システムが完成したことになる。
このままでもいいが、どうせラズパイにまで制御を拡張させたので、次はいよいよIoTの「I」の部分への拡張を試みたい。

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

pythonでLINEのAPI簡単に使ってみた

はじめに

いつも使ってるLINEのAPIを使って何か動かしてみたい。
LINEのAPIを使うと、他のアプリのように特定のアプリを入れなくても使ってもらえるのがいいところ。
しかも、pythonで簡単にかけるので他のライブラリとの併用がし易い。
中にどんな処理を入れるかは動いてから考えよう。
まずはハンズオン。

開発環境

M1チップのMacBook Air
heroku 7.47.13
Python 3.9.1
Flask 1.1.2
line-bot-sdk 1.18.0

herokuは対応するpythonのバージョンが限られているので要注意。

全体の流れ

まずLINE Developersに登録し、アクセストークンを発行。
そのあとflaskとline-bot-sdkでサンプルを実装してみる。
無料で利用できるPaasとして有名なherokuでデプロイ。
最後にWebhookの設定をして完了。

LINE Developersに登録

LINE Developersにアクセス。
今すぐ始めようからLINEログインをして、アカウント作成。
プロバイダー名を決める。
作成したプロバイダーを選んで、新規チャネル作成をクリック。
スクリーンショット 2021-02-28 10.50.21.png
チャンネルの種類からMessaging APIを選択。
スクリーンショット 2021-02-28 10.54.34.png

開いたトップで必要な情報を入力。

アプリ名   自分で決めるアプリ名
アプリ説明  アプリ説明
プラン    Developer Trial
大業種    個人
小業種    個人(その他)
メールアドレス 自分のメールアドレス

利用規約に同意して、作成ボタンをポチる。
基本設定ページからアクセストークンの再発行。

flaskでサンプル実装

pipでflaskとline-bot-sdkをインストール。

$ pip install flask
$ pip install line-bot-sdk

githubからechoのサンプルをもらってきて、少し修正。

main.py
# ======================================================================
# Project Name    : Linebot 
# File Name       : main.py
# Encoding        : utf-8
# Creation Date   : 2021/02/18
# ======================================================================

from flask import Flask, request, abort

from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,
)
import os

app = Flask(__name__)

YOUR_CHANNEL_ACCESS_TOKEN = "ここにアクセストークンを入れる"
YOUR_CHANNEL_SECRET = "ここにチャンネルシークレットを入れる"

line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(YOUR_CHANNEL_SECRET)

@app.route("/callback", methods=['POST'])
def callback():
    # get X-Line-Signature header value
    signature = request.headers['X-Line-Signature']

    # get request body as text
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    # handle webhook body
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text))


if __name__ == "__main__":
    port = int(os.getenv("PORT", 5000))
    app.run(host="0.0.0.0", port=port)

チャンネルシークレットをLINE Developerのチャネル基本設定から、アクセストークンをMessaging API設定からコピーしてペタリ。

YOUR_CHANNEL_ACCESS_TOKEN = "ここにアクセストークンを入れる"
YOUR_CHANNEL_SECRET = "ここにチャンネルシークレットを入れる"

herokuでデプロイ

herokuのページでアカウント作成。

作成後、指示に従ってターミナルでログイン。

$ heroku login
Enter your Heroku credentials:
Email: 
Password: 

Webhookで使うURLを好きな名前でCreate。
https://<好きなURL>.herokuapp.comのようになります。

$ heroku create <好きなURL>

次に設定ファイルruntime.txtとrequirements.txtを作成。

runtime.txt
python-3.9.1
requirements.txt
Flask==1.1.2
line-bot-sdk==1.18.0

Procfileに実行方法を定義。

web: python main.py

heroku gitにプログラムと設定ファイルをデプロイ。

$ git init
$ git add .
$ git commit -m "new commit"
$ git push heroku master

pushしたらherokuの設定は完了。
尚プログラムはpushした段階で実行されてる模様。

Webhookの設定

最後にLINE Developerに戻り、Webhookの設定を行う。
Messaging API設定で以下を設定。

Webhook送信:利用する
Webhook URL:https://<好きなURL>.herokuapp.com/callback

試してみる

同ページのQRコードから自分のスマホで友達追加。
送信した文字がechoされる動作を確認して終了。

やはり、いつも使ってるアプリで自分の作ったプログラムの動作が見れると感動する。
以前qiitaにも書いた、pythonで作った簡易顔認証と組み合わせて何かできないかなあ。

参考 : https://cppx.hatenablog.com/entry/2017/10/31/165128

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