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

幅優先探索(BPF)多分理解した(python)

参考記事

Pythonで誰でも書けるBFS(幅優先探索)

この記事は、pythonでBFSを検索すると上位にでてきます。
しかし、私にとって一部わかりにくい部分があり、あと、多分ですが、説明に一部間違いがあったと思われる。
ですので、私の理解したことを記事にしようと思いました。

書き換えた

BPF完全に理解した状況で、それを記録しました。

from collections import deque

n, m = map(int, input().split())

graph = [[] for _ in range(n+1)]

for i in range(m):
 a, b = map(int, input().split())
 graph[a].append(b)
 graph[b].append(a)

#print(graph) ⇦①

dist = [-1] * (n+1)
dist[0] = 0
dist[1] = 0

#print(dist) ←②

d = deque()
d.append(1)

#print(d)⇦③

while d:
 #print(d)⇦④
 v = d.popleft() #末尾から要素を1つ「返して」、「dからはその要素を消す」しょっぱなのv=1⇦⑤
 #print(v)
 for i in graph[v]: #しょっぱなは、graph[1]=[2]なので、v=1,i=2です。⇦⑥
   #print(i)⇦⑦
   #print(graph[i])←
   if dist[i] != -1:
     #print(i)⇦⑧
     continue
   else: #!!!
     #print(i)⇦⑨
     #print(v) #しょっぱなのv=1,i=2  ⇦⑩
     dist[i] = dist[v] + 1 #elseなのか? #◯!!!

     d.append(i) #i=2で探索開始 #◯!!!

ans = dist[1:]
#print(*ans, sep="\n")

 関門

一番きついのは、 #!!!で記載した部分の、[else]の部分が参考文献では記述されていないことです。
多分elseだと思います。。。これがない書き方もあるんでしょうが、if elseになれた目では理解が難しかったです。
①、②から、
graph
[[], [2], [1, 3, 4], [2, 4], [3, 2]]
dist
[0, 0, -1, -1, -1]

⑤のpopleft()は「dから消す」と思い込んでましたが、「dから消して、vに入れる」です。

⑥ですが
⑤でv=1になったので、graph[1]=[2]がiに代入されます。つまり、i=2です。

if~elseですが、 ifが実行される時は⇨、elseが実行されるときは←として、iとgraph内の配列を記述すると
2 ←
[1, 3, 4]
1 ⇨
[2]
3 ←
[2, 4]
4 ←
[3, 2]
2 ⇨
[1, 3, 4]
4 ⇨
[3, 2]
3 ⇨
[2, 4]
2 ⇨
[1, 3, 4]

◯!!!ですが、しょっぱなの場合は
dist[2]=dist[1]+1 つまり、[1]から1つだけ離れているので、距離が記録されます。
d.append(i)で次のvの探索が進みます。

参考文献の間違いだと思われる箇所

4行目は、distの初期化(#5)でdist[0]とdist[1]以外は-1と定義したので、「dist[i]==1であれば未>訪問(このwhile文の中でまだ頂点iについて調べていない)」ということです。逆にdist[i]!=1なら訪問済であることを表します。

これは、
「dist[i]==-1であれば未>訪問(このwhile文の中でまだ頂点iについて調べていない)」ということです。逆にdist[i]!=-1なら訪問済であることを表します。
だと思われます。。。。

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

機械学習についてのメモ(随時更新)

学習の流れ

  1. 実施内容の決定

  2. データ入手

  3. データ前処理

  4. 手法選択

  5. ハイパーパラメータ選択

  6. モデルの学習

  7. モデルの評価 → 3、4、5へ

訓練データとテストデータを分ける理由

・システムリリース時
持っている全データを訓練データとしても良い

・精度評価を行いたい場合
訓練データとテストデータを分け、訓練データのみから学習したモデルでテストデータを評価する

教師あり学習の目的が、未知のデータに対する予測であるため

適合不足と過学習

適合不足

・訓練データに対しても予測精度が低い状態

過学習

・訓練データによくフィットしているが、テストデータ(未知のデータ)に対する予測精度が低い状態

適合不足と過学習のバランスをいかにうまく取るかが機械学習の一番難しいところ

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

[Python] クラスをiterableにする方法

Contents

Pythonのクラスで反復処理1を行う方法の解説記事です。
各コードはGitHub repositoryからダウンロードできます。

iter_minimum.py
iteration = IterClass(["a", "ab", "abc", "bb", "cc"])
print([v for v in iteration if "a" in v])
# ['a', 'ab', 'abc']
実行環境
OS Windows Subsystem for Linux
Python version 3.8.5

解決方法

クラスの特殊メソッド__iter__2yield from構文3によって実装できます。リストまたはタプルを記憶し、繰り返し処理として呼び出された際に先頭から順番に返します。

iter_minimum.py
class IterClass(object):
    """
    Iterable class.

    Args:
        values (list[object] or tuple(object)): list of values

    Raises:
        TypeError: @values is not a list/tuple
    """

    def __init__(self, values):
        if not isinstance(values, (list, tuple)):
            raise TypeError("@values must be a list or tuple.")
        self._values = values

    def __iter__(self):
        yield from self._values

応用(__bool__との組み合わせ)

上記の例では新しいクラスを作成する意義が感じられないので、応用バージョンを作りました。

最小単位の作成

反復処理で返される最小単位Unitクラスをまず作成します。

iter_advanced.py
class Unit(object):
    """
    The smallest unit.
    """

    def __init__(self, value):
        if not isinstance(value, (float, int)):
            raise TypeError(
                f"@value must be integer or float value, but {value} was applied.")
        self._value = value
        self._enabled = True

    def __bool__(self):
        return self._enabled

    @property
    def value(self):
        """
        float: value of the unit
        """
        return self._value

    def enable(self):
        """
        Enable the unit.
        """
        self._enabled = True

    def disable(self):
        """
        Disable the unit.
        """
        self._enabled = False

Unitは固有の値と状態(__bool__: True/False)を持っています。

Unitの作成:

iter_advanced.py
unit1 = Unit(value=1.0)
# Unitの状態がTrueであれば値を表示
if unit1:
    print(unit1.value)
# 1.0

状態をFalseに:

iter_advanced.py
# Disable
unit1.disable()
# Unitの状態がTrueであれば値を表示
if unit1:
    print(unit1.value)
# 出力なし

状態をTrueに:

iter_advanced.py
# Disable
unit1.enable()
# Unitの状態がTrueであれば値を表示
if unit1:
    print(unit1.value)
# 1.0

反復処理を行うクラス

複数のUnitの情報を保管して反復処理を行うクラスSeriesを作成します。

iter_advanced.py
class Series(object):
    """
    A series of units.
    """

    def __init__(self):
        self._units = []

    def __iter__(self):
        yield from self._units

    def add(self, unit):
        """
        Append a unit.

        Args:
            unit (Unit]): the smallest unit

        Raises:
            TypeError: unit is not an instance of Unit
        """
        if not isinstance(unit, Unit):
            raise TypeError("@unit must be a instance of Unit")
        self._units.append(unit)

    def _validate_index(self, num):
        """
        Validate the index number.

        Args:
            num (int): index number of a unit

        Raises:
            TypeError: @num is not an integer
            IndexError: @num is not a valid index number
        """
        if not isinstance(num, int):
            raise TypeError(
                f"@num must be integer, but {num} was applied.")
        try:
            self._units[num]
        except IndexError:
            raise IndexError(f"@num must be under {len(self._units)}")

    def enable(self, num):
        """
        Enable a unit.

        Args:
            num (int): index of the unit to be enabled

        Raises:
            TypeError: @num is not an integer
            IndexError: @num is not a valid index number
        """
        self._validate_index(num)
        self._units[num].enable()

    def disable(self, num):
        """
        Disable a unit.

        Args:
            num (int): index of the unit to be disabled

        Raises:
            TypeError: @num is not an integer
            IndexError: @num is not a valid index number
        """
        self._validate_index(num)
        self._units[num].disable()

状態がTrueとなっているUnitの値を返す関数

Seriesクラスの挙動を確認するため、状態がTrueとなっているUnitの値を返す関数を作ります。

iter_advanced.py
def show_enabled(series):
    """
    Show the values of enabled units.
    """
    if not isinstance(series, Series):
        raise TypeError("@unit must be a instance of Series")
    print([unit.value for unit in series if unit])

挙動確認

UnitSeriesに登録し、すべてのUnitの値を表示します。

iter_advanced.py
# Create a series of units
series = Series()
[series.add(Unit(i)) for i in range(6)]
show_enabled(series)
# [0, 1, 2, 3, 4, 5]

5, 6番目のUnit(値は4, 5)の状態をFalseにすると...

iter_advanced.py
# Disable two units
series.disable(4)
series.disable(5)
show_enabled(series)
# [0, 1, 2, 3]

5番目のUnit(値は4)の状態をTrueに戻すと...

iter_advanced.py
# Enable one disabled unit
series.enable(4)
show_enabled(series)
# [0, 1, 2, 3, 4]

あとがき

閲覧ありがとうございます。お疲れ様でした。

この記事を作成したきっかけ:
自作のPython package CovsirPhy: COVID-19 analysis with phase-dependent SIRsでこの仕組みを使いました。
- PhaseUnit: ODEモデルのパラメータ値が一定となる期間
- PhaseSeries: 感染者数のシナリオ

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

asyncpg でよくやる操作まとめ

psycopg2 でよくやる操作まとめ - Qiita がじわじわ LGTM が増える人気記事になっているが、最近は aiohttp Server との組み合わせで asyncpg の方をよく使うようになってきたので、こちらについてもまとめる。

しかし公式ドキュメントが普通にわかりやすいし分量も多くないので全部読んでしまうのが早いかもしれない。
asyncpg — asyncpg Documentation

asyncpg 概要

  • Python から PostgreSQL にアクセスするためのモジュール
  • asyncio を活用した非同期処理を行うため Python 3.5 以上でないと動かない
  • DB-API の仕様 には準拠していない (そもそも DB-API に規定されている仕様は同期的な API のため)

基本的な使い方

インストールする

$ pip install asyncpg

接続する

import asyncpg

dsn = "postgresql://username:password@hostname:5432/database"
conn = await asyncpg.connect(dsn)

# ...

await conn.close()

connasyncpg.connection.Connection オブジェクトとして得られる。

コネクションプールを作成する

import asyncpg

dsn = "postgresql://username:password@hostname:5432/database"
async with asyncpg.create_pool(dsn) as pool:
    await pool.execute("...")

poolasyncpg.pool.Pool オブジェクトとして得られる。

プールからコネクションを取り出して使うこともできるが、プールに対して直接 execute などのメソッドを実行することもできる。

クエリを実行する

asyncpg では cursor を作成せずとも直接 conn.execute でクエリを実行できる。

await conn.execute("INSERT INTO users(name) VALUES($1)", "foo")

クエリ内に $数字 と書いておいてクエリ実行時の第2引数以降に値を指定すると、クエリに値を埋め込むことができる。 datetime 型など、一部の型のオブジェクトはそのまま指定してもよしなに内部で変換してクエリに埋め込んでくれる。どの型をどう変換するかは公式ドキュメントに載っており、自分でカスタマイズすることもできる。

データを取得する

execute の代わりに fetch などのメソッドでクエリを実行すると結果を取得できる。

await conn.fetch("SELECT * FROM users")  #=> [<Record id='1' name='foo'>, <Record id='2' name='bar'>]

結果は asyncpg.Record オブジェクトのリストとして得られる。

最初の1行目だけを取得する fetchrow や、1行1列目の値だけを取得する fetchval も便利。

await conn.fetchrow("SELECT * FROM users WHERE id = $1", "1")  #=> <Record id='1' name='foo'>
await conn.fetchval("SELECT COUNT(1) FROM users")  #=> 2

Record オブジェクトについて

Record オブジェクトは tuple のようにも dict のようにも振る舞うので、わざわざ他のデータ型に変換する必要はあまりなさそう。

record = await conn.fetchrow("SELECT * FROM users WHERE id = $1", "1")

record[1]  #=> インデックスでアクセス
record["name"]  #=> キーでアクセス

トランザクションを使う

トランザクションを使う場合は非同期コンテキストマネージャ (async with) ブロック内に処理を書く。

async with connection.transaction():
    await conn.execute("...")

async with を使ってトランザクションを開始した場合は、ブロックが終了した時点で自動で commit されるため、明示的に commit する必要はない。

タイムアウトを設定する

コネクション作成時に command_timeout に秒数を指定すると、クエリのデフォルトタイムアウトを設定できる。

conn = await asyncpg.connect(dsn, command_timeout=60)

クエリ実行のたびに個別にタイムアウトを設定する場合は executefetch の引数で timeout を指定する。

await conn.execute("...", timeout=60)

便利 Tips

取得したデータを Pandas DataFrame にする

fetch の結果は Record オブジェクトのリストなので、それをそのまま pd.DataFrame() に入れてしまってもなんとかなる。

import pandas as pd

records = await conn.fetch("SELECT * FROM users")
if len(records) > 0:
    df = pd.DataFrame(records, columns=list(records[0].keys()))
else:
    # 結果が0件だった場合の処理

Pandas DataFrame の内容を INSERT する

asyncpg は COPY 系のメソッドが豊富で、タプルのリストを COPY コマンドで Bulk Insert してくれる copy_records_to_table を使えば Pandas DataFrame も簡単に INSERT できる。

await conn.copy_records_to_table("users", records=df.itertuples(), columns=df.columns)

CSV ファイルを INSERT する

同様に copy_to_table を使えば CSV フォーマットなどのファイルから簡単に INSERT できる。

await conn.copy_to_table("users", source="users.csv", format="csv")

指定したオプションは多くが COPY クエリにそのまま流れるので、COPY クエリの仕様を理解すれば使いこなせる。(format, null, header など)
PostgreSQL: Documentation: COPY

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

Raspberry pi - Arduino Uno間のシリアル通信(Python)

Raspberry pi - Arduino間などでデータのやり取りをしたい、と思って調べてたらでできたものをオブジェクト指向化したやつ。

Arduino側のソース

ソース

Serial.ino
int n = 12;

void setup(){
  Serial.begin(9600);
}

void loop(){
  Serial.println(n);
  Serial.write("Hello World!\n");
  delay(1000);
}
  • 一応シリアルモニタで通信ができているか確認する。
  • シリアルモニタで通信を確認出来たらシリアルモニタを閉じる。(閉じないとSerialExceptionエラーが出るみたい。)
  • 通信速度は9600に設定
  • 数値の送信はSerial.print関数、文字列の送信はSerial.write関数で行う。(一つにできないのかな?)

Raspberry pi側(Python)のソース

pyserialのpipインストール

Pythonでシリアル通信をするために必要なライブラリをインストールする。

pip install pyserial

ソース

Arduino_USB.py
import serial
import time

class Arduino_USB:
    data = ""

    # コンストラクタ
    def __init__(self, dev, bps):
        # 通信の設定 デバイス名 通信速度
        self.ser = serial.Serial(dev, bps)
        time.sleep(2)

    # シリアル通信開始
    def startUSB(self):
        # スタートコマンド。バイト文字"a"の送信
        self.ser.write(b"a")

    # シリアル通信切断
    def closeUSB(self):
        self.ser.close()

    # Arduinoからデータを読み出す
    def getUSB(self):
        self.data = self.ser.readline()
        return str(self.data, encoding = "utf-8")

a = Arduino_USB("/dev/ttyACM0", 9600)
a.startUSB()
print(a.getUSB())
print(a.getUSB())
a.closeUSB()
  • Raspberry piにArduinoをUSB接続する。
  • デバイス名は基本/dev/ttyACM0で認識されている。
  • Arduino側と同じ通信速度に設定する。
  • time.sleepでArduino側のラグを待機。
  • 多分改行コードまでを読み込んでる?

実行結果

12

こんにちは

まとめ

  • データの読み込みのタイミングの同期と読み込む範囲がよくわからん。(1Byteとか書いてあったけど日本語が読み込めるのはなぜ?)
  • 数値の送信はSerial.print関数、文字列の送信はSerial.write関数で行うところを一つにできないのかな?
  • いろいろと謎だけど個人的な遊びなので動けばヨシ!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GPyを使ってガウス過程回帰

ガウス過程回帰とは

すでにサンプリング済みの入力xに対応する出力yをもとにして、
新しい入力x'に対する出力の予測値y'の期待値と分散を返す回帰モデルを作成する。
限られたサンプル点から関数の真の形を予測するときに用いられる。
https://jp.mathworks.com/help/stats/gaussian-process-regression-models.html

ガウス過程回帰モデルを扱うには、GPyというpythonのライブラリを使う。
https://gpy.readthedocs.io/en/deploy/#

真の関数の描画

入力を2次元とし、真の関数はそれらを余弦関数に通した値の足し合わせであるとする。

temp.py
import numpy as np

# 関数の定義
def func(x):
    fx = np.sum(np.cos(2 * np.pi * x))
    return fx

xa = np.linspace(-1, 1, 101)
ya = np.linspace(-1, 1, 101)
Xa, Ya = np.meshgrid(xa, ya)

Za = np.zeros([101, 101])
for i in range(len(Xa)):
    for j in range(len(Ya)):
        x = np.array([Xa[i,j], Ya[i,j]])
        Za[i,j] = func(x)

# 描画
import matplotlib.pyplot as plt
fig1 = plt.figure(figsize=(8,8))
ax1 = fig1.add_subplot(111)
ax1.contour(Xa, Ya, Za, cmap="jet", levels=10, alpha=1)
plt.xlim(-1,1)
plt.ylim(-1,1)

image.png

サンプリング

無駄なくサンプリングする方法として、Sobol sequenceやLatin hypercube sampling等がある。
それらは使わず、ここでは単純にランダムにサンプル点を決定する。
サンプル点数は最初は少なめに、20点とする。

temp.py
import random
random.seed(1)

# ランダムにサンプリング
n_sample = 20
Xa_rand = [random.random()* 2 - 1 for i in range(n_sample)]
Ya_rand = [random.random()* 2 - 1 for i in range(n_sample)]

xlist = np.stack([Xa_rand, Ya_rand], axis=1)
Za_rand = []
for x in xlist:
    Za_rand = np.append(Za_rand, func(x))

# 描画
ax1.scatter(Xa_rand, Ya_rand)

先程の図にサンプル点をプロットする。
下半分はまだマシだが、上半分はサンプルが少なく、スカスカである。
image.png

ガウス過程回帰

ガウス過程回帰モデルを構築する。
GPy.kernでカーネル関数を選択する。ここでは2次元のRBFカーネルとした。
GPy.models.GPRegressionで回帰モデルを構築し、model.optimizeでモデルのパラメータをチューニングさせる。

temp.py
import GPy

# 学習用データ
Input = np.stack([Xa_rand, Ya_rand], axis=1)
Output = Za_rand[:,None]

# ガウス過程回帰モデルを構築
kernel = GPy.kern.RBF(2)
model = GPy.models.GPRegression(Input, Output, kernel)
model.optimize(messages=True, max_iters=1e5)

# 描画
model.plot(levels=10)
plt.gcf().set_size_inches(8, 8, forward=True)
plt.xlim(-1,1)
plt.ylim(-1,1)
plt.xlabel("x1")
plt.ylabel("x2")

応答曲面をプロットする。
20点とかなり点数が少なかったが、意外と大まかな山谷は再現できている。
上半分の誤差は大きい。
image.png

信頼区間の描画

2次元の入力のうち、どちらか一方を0に固定して、応答曲面の断面を見てみる。

temp.py
# x2=0断面
model.plot(fixed_inputs=[(1, 0)])
plt.xlim(-1,1)
plt.ylim(-4,4)
plt.xlabel("x1")

# x1=0断面
model.plot(fixed_inputs=[(0, 0)])
plt.xlim(-1,1)
plt.ylim(-4,4)
plt.xlabel("x2")

x2=0断面
image.png

x1=0断面
image.png

水色の帯は2.5~97.5%の信頼区間を示す。
信頼区間の幅が大きいほど、回帰結果にばらつきが大きい。
x2の大きいところはやはり自信がないらしい。

サンプリング数を増やした場合

n_sample = 40
image.png
image.png
image.png

n_sample = 100
image.png
image.png
image.png

サンプリングの数が多くなるほど、信頼区間の幅は狭くなり、回帰結果のばらつきが小さくなる。

まとめ

GPyを用いて、ガウス過程回帰モデルを構築した。

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

将棋棋士の高見七段と増田六段をCNNで分類してみた【CNN初心者向け】

この記事でやったこと

  • CNNによる画像分類をkerasで実装
  • 将棋界でも似ていると言われている高見七段と増田六段を分類できるかを試した
  • 高見七段と増田六段は学習後の分類結果が悪かった。
  • 代わりに「高見七段とガルリ・ガスパロフ」や「高見七段と藤井二冠」を分類してみたところうまく分類できていた。
  • やはり高見七段と増田六段は似ている...?

はじめに

突然ですが、将棋界で似ていると言われている高見七段と増田六段をご存知でしょうか。(上が高見七段、下が増田六段)
高見七段
images (9).jfif
切れ長い目や黒縁のメガネなど似ている要素が多い気がします。実際自分も将棋好きですが、数年前はまじで混乱していました。

kerasでCNNを学んだのでこの二人を分類できるかを試してみました。

実装概要

使用しているライブラリ一覧は下記になります。実装はgoogle colabで行っています。

import cv2
import os
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import random
import pandas as pd

from PIL import Image
from sklearn.model_selection import train_test_split
from google.colab.patches import cv2_imshow

顔部分の切り抜き

まずはgoogle画像検索で画像をダウンロードしました。だいたい各人40枚程度集まりました。
枚数としては少ないですが、なかなか写真の数も多くなくこれが限界でした...

次にダウンロードした画像から顔部分のみを切り抜きます。
顔検出に必要な学習済みモデルは下記からダウンロードできます。
https://github.com/opencv/opencv/tree/master/data/haarcascades
使い方については下記サイトを参考にしました。

#githubよりダウンロード
HAAR_FILE = "haarcascade_frontalface_alt2.xml"
cascade = cv2.CascadeClassifier(HAAR_FILE)

m_list = os.listdir("masuda_orig")

#増田六段の顔切り取り
for m_num,m in enumerate(m_list):
  image = cv2.imread("masuda_orig/" + m)
  face_list = cascade.detectMultiScale(image, minSize=(10, 10))
  for i, (x, y, w, h) in enumerate(face_list):
      trim = image[y: y+h, x:x+w]
      trim = cv2.resize(trim,(size_im,size_im))
      cv2.imwrite(DATA_DIR + 'masuda_tmp/masuda'+str(m_num) +"_" + str(i+1) + '.jpg', trim)

"masuda_orig"フォルダにあるファイルから顔画像のみを切りとったデータを"masuda_tmp"フォルダに保存していきます。
これを高見七段にも同様に実施しました。一例として上の高見七段の画像は下記のように切り取られます。
takami_40_1.jpg

学習データとテストデータの作成

次に顔部分が切り抜かれた画像から、手動で誤って切り抜かれた画像や、一緒に写っている別人の画像を除きます。
この部分は手動で行いました。

そうして得られた画像を学習データとテストデータに分類します。

m_list = os.listdir("masuda")
t_list = os.listdir("takami")

X = []
y = []

for m in m_list:
  image = Image.open("masuda/" + m)
  image = image.convert("RGB")
  image = np.asarray(image)
  X.append(image)
  y.append([1])

for t in t_list:
  image = Image.open("takami/" + t)
  image = image.convert("RGB")
  image = np.asarray(image)
  X.append(image)
  y.append([0])

X=np.asarray(X)
y=np.asarray(y)

X_train,X_test, y_train,y_test = train_test_split(X,y,shuffle=True,test_size=0.3)
print(X_train.shape,X_test.shape, y_train.shape, y_test.shape)

X_train = X_train.astype("float") / 255
X_test  = X_test.astype("float")  / 255

学習モデルの作成と学習の実行

学習モデルはCNNを通したあとに全結合層を通して二値分類しています。
モデルは下記サイトを参考にしました。

from keras.models import Sequential
from keras.layers import Conv2D
from keras.layers import MaxPool2D
from keras.optimizers import Adam

from keras.layers import Dense, Dropout, Activation, Flatten


model = Sequential()
model.add(Conv2D(32,(3,3),activation="relu",input_shape=(size_im,size_im,3)))
model.add(MaxPool2D((2,2)))
model.add(Dropout(0.1))
model.add(Conv2D(64,(3,3),activation="relu"))
model.add(MaxPool2D((2,2)))
model.add(Dropout(0.1))
model.add(Conv2D(128,(3,3),activation="relu"))
model.add(MaxPool2D((2,2)))
model.add(Dropout(0.1))
model.add(Conv2D(128,(3,3),activation="relu"))
model.add(MaxPool2D((2,2)))
model.add(Dropout(0.1))
model.add(Flatten())
model.add(Dense(512,activation="relu"))
model.add(Dense(1,activation="sigmoid"))

model.summary()

学習後の分類結果

学習後のモデルの精度、テスト結果の正誤を確認して、分類を誤ったデータがどれだったかを確認していきます。

#モデルの学習
optim=Adam()
model.compile(loss="binary_crossentropy",
              optimizer=optim,
              metrics="acc")

model.fit(X_train,y_train,
          epochs=20,
          batch_size=2,
          validation_data=(X_test,y_test))
#モデルの分類を誤った画像を表示する
df = pd.DataFrame()
df["pred"] =  model.predict(X_test).flatten()
df["test"] = y_test.flatten()
df["pred"] = df["pred"].apply(lambda x: 0 if x < 0.5 else 1)
df["acc"] = df["pred"] == df["test"]

fig, ax = plt.subplots(1,len(mistake_list),figsize=(20,5))
mistake_list = df[df["acc"] == 0].index
for i,test_i in enumerate(mistake_list):
  ax[i].imshow(X_test[i,...])

高見七段と増田六段

エポック数を20として実行した結果、正解率は
学習データ90%、テストデータ67%となる結果が得られました。学習データが40枚、テストデータが19枚となります。
学習データに対して過学習している傾向がわかります。また、二値分類の結果で67%は低い結果と言えそうです。
分類を誤った画像は下記になります。左の2つとかは確かに分類が難しそう。

ダウンロード (3).png

高見七段と藤井二冠

高見七段と増田六段が似ているため、学習結果が低かった可能性を検証するべく、
「高見七段と藤井二冠」の分類も行ってみました。学習データが40枚、テストデータが19枚となります。

その結果は学習データ92%、テストデータ88%。
増田六段との分類と比べると若干精度が高くなっています。
やはり、高見七段と増田六段は分類が難しい...?
分類が間違った画像は下記。特に目立った特徴はないですね...
ダウンロード (2).png

高見七段とガルリがスロフ

試しにもっと違う人ということで、元チェス世界王者のガルリガスパロフとの分類も試してみました。
images (19).jfif
学習データとテストデータは48枚、21枚です。

その結果は学習データ100%、テストデータ100%。
100%はちょっと怪しいですが、増田六段や藤井二冠との分類と比較すると高い精度になっています。

まとめ

分類結果は下記となりました。
- 「高見七段と増田六段」:67%
- 「高見七段と藤井二冠」:88%
- 「高見七段とガルリガスパロフ」:100%

機械からみてもこの二人は似ているということになりそうです。
ただし今回は学習データが40枚ほどと非常に少なかったのが課題です。そのため、得られた結果も正確性が低く、どれをテストデータにするかで変動してしまいます。
もっとデータを多くすれば精度はより高くなりそうです。

使用したコード

実際に使用したコードはこちら。
https://colab.research.google.com/drive/14Dg2-uQWSf4NT2OnxTWGVSEDST3O68d8?usp=sharing

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

将棋棋士の高見七段と増田六段をCNNで分類してみた

この記事でやったこと

  • CNNによる画像分類をkerasで実装
  • 将棋界でも似ていると言われている高見七段と増田六段を分類できるかを試した
  • 高見七段と増田六段は学習後の分類結果が悪かった。
  • 代わりに「高見七段とガルリ・ガスパロフ」や「高見七段と藤井二冠」を分類してみたところうまく分類できていた。
  • やはり高見七段と増田六段は似ている...?

はじめに

突然ですが、将棋界で似ていると言われている高見七段と増田六段をご存知でしょうか。(上が高見七段、下が増田六段)
高見七段
images (9).jfif
切れ長い目や黒縁のメガネなど似ている要素が多い気がします。実際自分も将棋好きですが、数年前はまじで混乱していました。

kerasでCNNを学んだのでこの二人を分類できるかを試してみました。

実装概要

使用しているライブラリ一覧は下記になります。実装はgoogle colabで行っています。

import cv2
import os
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import random
import pandas as pd

from PIL import Image
from sklearn.model_selection import train_test_split
from google.colab.patches import cv2_imshow

顔部分の切り抜き

まずはgoogle画像検索で画像をダウンロードしました。だいたい各人40枚程度集まりました。
枚数としては少ないですが、なかなか写真の数も多くなくこれが限界でした...

次にダウンロードした画像から顔部分のみを切り抜きます。
顔検出に必要な学習済みモデルは下記からダウンロードできます。
https://github.com/opencv/opencv/tree/master/data/haarcascades
使い方については下記サイトを参考にしました。

#githubよりダウンロード
HAAR_FILE = "haarcascade_frontalface_alt2.xml"
cascade = cv2.CascadeClassifier(HAAR_FILE)

m_list = os.listdir("masuda_orig")

#増田六段の顔切り取り
for m_num,m in enumerate(m_list):
  image = cv2.imread("masuda_orig/" + m)
  face_list = cascade.detectMultiScale(image, minSize=(10, 10))
  for i, (x, y, w, h) in enumerate(face_list):
      trim = image[y: y+h, x:x+w]
      trim = cv2.resize(trim,(size_im,size_im))
      cv2.imwrite(DATA_DIR + 'masuda_tmp/masuda'+str(m_num) +"_" + str(i+1) + '.jpg', trim)

"masuda_orig"フォルダにあるファイルから顔画像のみを切りとったデータを"masuda_tmp"フォルダに保存していきます。
これを高見七段にも同様に実施しました。一例として上の高見七段の画像は下記のように切り取られます。
takami_40_1.jpg

学習データとテストデータの作成

次に顔部分が切り抜かれた画像から、手動で誤って切り抜かれた画像や、一緒に写っている別人の画像を除きます。
この部分は手動で行いました。

そうして得られた画像を学習データとテストデータに分類します。

m_list = os.listdir("masuda")
t_list = os.listdir("takami")

X = []
y = []

for m in m_list:
  image = Image.open("masuda/" + m)
  image = image.convert("RGB")
  image = np.asarray(image)
  X.append(image)
  y.append([1])

for t in t_list:
  image = Image.open("takami/" + t)
  image = image.convert("RGB")
  image = np.asarray(image)
  X.append(image)
  y.append([0])

X=np.asarray(X)
y=np.asarray(y)

X_train,X_test, y_train,y_test = train_test_split(X,y,shuffle=True,test_size=0.3)
print(X_train.shape,X_test.shape, y_train.shape, y_test.shape)

X_train = X_train.astype("float") / 255
X_test  = X_test.astype("float")  / 255

学習モデルの作成と学習の実行

学習モデルはCNNを通したあとに全結合層を通して二値分類しています。
モデルは下記サイトを参考にしました。

from keras.models import Sequential
from keras.layers import Conv2D
from keras.layers import MaxPool2D
from keras.optimizers import Adam

from keras.layers import Dense, Dropout, Activation, Flatten


model = Sequential()
model.add(Conv2D(32,(3,3),activation="relu",input_shape=(size_im,size_im,3)))
model.add(MaxPool2D((2,2)))
model.add(Dropout(0.1))
model.add(Conv2D(64,(3,3),activation="relu"))
model.add(MaxPool2D((2,2)))
model.add(Dropout(0.1))
model.add(Conv2D(128,(3,3),activation="relu"))
model.add(MaxPool2D((2,2)))
model.add(Dropout(0.1))
model.add(Conv2D(128,(3,3),activation="relu"))
model.add(MaxPool2D((2,2)))
model.add(Dropout(0.1))
model.add(Flatten())
model.add(Dense(512,activation="relu"))
model.add(Dense(1,activation="sigmoid"))

model.summary()

学習後の分類結果

学習後のモデルの精度、テスト結果の正誤を確認して、分類を誤ったデータがどれだったかを確認していきます。

#モデルの学習
optim=Adam()
model.compile(loss="binary_crossentropy",
              optimizer=optim,
              metrics="acc")

model.fit(X_train,y_train,
          epochs=20,
          batch_size=2,
          validation_data=(X_test,y_test))
#モデルの分類を誤った画像を表示する
df = pd.DataFrame()
df["pred"] =  model.predict(X_test).flatten()
df["test"] = y_test.flatten()
df["pred"] = df["pred"].apply(lambda x: 0 if x < 0.5 else 1)
df["acc"] = df["pred"] == df["test"]

fig, ax = plt.subplots(1,len(mistake_list),figsize=(20,5))
mistake_list = df[df["acc"] == 0].index
for i,test_i in enumerate(mistake_list):
  ax[i].imshow(X_test[i,...])

高見七段と増田六段

エポック数を20として実行した結果、正解率は
学習データ90%、テストデータ67%となる結果が得られました。学習データが40枚、テストデータが19枚となります。
学習データに対して過学習している傾向がわかります。また、二値分類の結果で67%は低い結果と言えそうです。
分類を誤った画像は下記になります。左の2つとかは確かに分類が難しそう。

ダウンロード (3).png

高見七段と藤井二冠

高見七段と増田六段が似ているため、学習結果が低かった可能性を検証するべく、
「高見七段と藤井二冠」の分類も行ってみました。学習データが40枚、テストデータが19枚となります。

その結果は学習データ92%、テストデータ88%。
増田六段との分類と比べると若干精度が高くなっています。
やはり、高見七段と増田六段は分類が難しい...?
分類が間違った画像は下記。特に目立った特徴はないですね...
ダウンロード (2).png

高見七段とガルリがスロフ

試しにもっと違う人ということで、元チェス世界王者のガルリガスパロフとの分類も試してみました。
images (19).jfif
学習データとテストデータは48枚、21枚です。

その結果は学習データ100%、テストデータ100%。
100%はちょっと怪しいですが、増田六段や藤井二冠との分類と比較すると高い精度になっています。

まとめ

分類結果は下記となりました。
- 「高見七段と増田六段」:67%
- 「高見七段と藤井二冠」:88%
- 「高見七段とガルリガスパロフ」:100%

機械からみてもこの二人は似ているということになりそうです。
ただし今回は学習データが40枚ほどと非常に少なかったのが課題です。そのため、得られた結果も正確性が低く、どれをテストデータにするかで変動してしまいます。
もっとデータを多くすれば精度はより高くなりそうです。

使用したコード

実際に使用したコードはこちら。
https://colab.research.google.com/drive/14Dg2-uQWSf4NT2OnxTWGVSEDST3O68d8?usp=sharing

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

jupyter lab上で画像のラベリングをする

やりたいこと

AIに使いたい画像のラベリングをしたいが、GUIを作るのが面倒。。。と思い、
JupyterLab上でできないかーと考え、色々調べてたらできたのでそのときのメモ。
matplotで表示した画像を更新しながらinputでラベリングを入力するようなイメージ。

実行環境

  • Python 3.8.5
  • jupyter lab 2.2.2
  • mac(windows10でも実行済み)

コード

sample_imgフォルダにある画像をラベリングする。

準備

import cv2
import matplotlib.pyplot as plt
import os
import glob
import IPython
# 画像のpathを取得
img_list = glob.glob(os.path.join(r"sample_img","*.jpg"))
img_list
>>> ['sample_img/img3.jpg', 'sample_img/img2.jpg', 'sample_img/img1.jpg']

画像を表示するコード

name_list = []
for img_path in img_list:
    # 画像の読み込み
    img = cv2.imread(img_path)
    # 画像を表示するフレーム
    fig = plt.figure(figsize=(5,5))
    ax = fig.add_subplot(1,1,1)
    ax.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
    plt.title(img_path)
    plt.pause(.01)

    # ラベル付けするためのテキストボックス
    comment = input()

    if comment == "break":
        break
    else:
        # 表示している画像をクリア
        IPython.display.clear_output()
        # Inputに入力した文字列をリストに追加
        name_list.append(comment)

動作画面はこんな感じ。
passlabeling.gif

確認

# name_listの確認
name_list
>>> ['dog', 'cat', 'cat']

最後に

Input部分をラジオボタンみたいにできたらいいな〜.

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

[Python] UnionFind ABC177D

ABC177D

UnionFindにはいろいろな実装があるが, 本問ではparents配列にノード数を保持する実装だと非常に簡単に解ける. 以下のようにしてノード数を保持する.

・自身が子のとき, 親ノード番号を格納する。自身が根のとき, ノード数を負の数で格納する。
・負の数のときは自身が根であり, その絶対値がその木のノード数を表す。

サンプルコード
import sys

#UnionFindTreeクラスの定義
class UnionFind():
    #クラスコンストラクタ
    #selfはインスタンス自身
    def __init__(self, n):
        #親ノードを-1に初期化する
        self.parents = [-1] * n

    #根を探す
    def find(self, x):
        if self.parents[x] < 0:
            return x
        else:
            self.parents[x] = self.find(self.parents[x])
            return self.parents[x]

    #xとyの木を併合
    def union(self, x, y):
        #根探し
        x = self.find(x)
        y = self.find(y)

        if x == y:
            return

        if self.parents[x] > self.parents[y]:
            x, y = y, x

        self.parents[x] += self.parents[y]
        self.parents[y] = x


N, M = map(int, input().split())
info = [tuple(map(int, s.split())) for s in sys.stdin.readlines()]

#UnionFindインスタンスの生成
uf = UnionFind(N)
for a, b in info:
    #インデックスを調整し、a,bの木を結合
    a -= 1; b -= 1
    uf.union(a, b)

ans = min(uf.parents)

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

OpenCVでスプライトを回転させる

はじめに

OpenCVで透過画像を扱う ~スプライトを舞わせる~の続きです。

スプライトを回転させるって、ワクワクしますな。ナムコのSYSTEM II基板を思い出します。アサルトとか、オーダインとか。プレイステーション本体を購入する前にナムコミュージアム VOL.4を買ったものだ。

OpenCVで画像を回転させる

OpenCVではcv2.rotate()で画像を90度単位で回転できることができるが、任意の角度で回転させる関数はない。より高度な関数があるので、それを使う。

アフィン変換のお勉強

回転や拡大縮小などの一次変換と平行移動を合わせた写像をアフィン変換という。要は

\begin{pmatrix}
x' \\
y'
\end{pmatrix}
=
\begin{pmatrix}
a & b \\
d & e
\end{pmatrix}
\begin{pmatrix}
x \\
y
\end{pmatrix}
+
\begin{pmatrix}
c \\
f
\end{pmatrix}

という変換だ。この形ではプログラムしづらいので

\begin{pmatrix}
x' \\
y' \\
1
\end{pmatrix}
=
\begin{pmatrix}
a & b & c \\
d & e & f \\
0 & 0 & 1
\end{pmatrix}
\begin{pmatrix}
x \\
y \\
1
\end{pmatrix}

と表現することにする。一番下がなんとなく気持ち悪いが、1=1という恒等式があるだけだ。

この

\begin{pmatrix}
a & b & c \\
d & e & f \\
\end{pmatrix}

を定義して、cv2.warpAffine()という関数にかける。
ただし、画像を回転させるにあたり、わざわざ

\begin{pmatrix}
cosθ & -sinθ & 0 \\
sinθ & cosθ & 0 \\
\end{pmatrix}

という計算をする必要はない。cv2.getRotationMatrix2D()で得られる行列を使えばよいのだ。

回転行列を作る cv.getRotationMatrix2D(center, angle, scale)

  • center 回転中心。
  • angle 回転角度。反時計回りで、単位はラジアンではなく度
  • scale 拡大縮小の倍率。

平行移動分は自分で追加しよう。

画像をアフィン変換する cv.warpAffine(src, M, dsize)

  • src 元画像。
  • M 2*3の変換行列。
  • dsize 出力画像サイズを(width, height)のタプルで指定する。
  • これら以外にも必須でない引数があるが省略。

実践

変換行列は具体的にどうなっているのかなっと。

ソース1
import cv2
angle = 30  # degrees
M = cv2.getRotationMatrix2D((0,0), angle, 1)
print (M)
print (M.shape)
print (M.dtype)
結果1
[[ 0.8660254  0.5        0.       ]
 [-0.5        0.8660254  0.       ]]
(2, 3)
float64

cos30度 = √3 /2 = 0.8660254…だからまさしく回転行列の…って、あれ? マイナスの位置が違ってるぞ?
どうやら数学で使うxy平面とは違い下が正になっている座標系に対応した計算になっているらしい。

で、これを使って実際に画像変換してみる。

ソース2
import cv2

filename = "hoge.png"
img = cv2.imread(filename)
h, w = img.shape[:2]
center = (140,60)
angle = 0

while True:
    angle = (angle + 10) % 360
    M = cv2.getRotationMatrix2D(center, angle, 1)
    img_rot = cv2.warpAffine(img, M, (w, h))
    cv2.imshow(filename, img_rot)
    key = cv2.waitKey(100) & 0xFF
    if key == ord("q"):
        break

cv2.destroyAllWindows()

結果はこう。

元画像 結果
uchuhikoushi_point.png uchuhikoushi_anim_0.gif

回転角度と回転中心については理解したが、「ちょっと待てよ、そもそも画像がはみ出してるじゃん」と言いたくなってくる。

はみ出しを避ける

はみ出しを避けるにはどうするか。どうするかって、計算するんだよ計算。

rot_cad_2.png

2辺の長さが w と h である長方形(赤)が角度aだけ傾いたとき、外接する四角形(青)のサイズは

rot_w = w*cos(a) + h*sin(a)
rot_h = w*sin(a) + h*cos(a)

となる。0度~90度 を外れるとサインやコサインがマイナスになるので、正確には各項は絶対値とする必要がある。
ああ、ようやく理解したぞ。後述の先人たちの記事にこの式が出てきて回転行列とも違うし何だろうと思っていたのよね。

で、この青の長方形に収まるように位置を平行移動すればいいわけだ。(0,0)を回転中心としているので、画像中心を基準で考える、と。

ソース3
import cv2
import numpy as np

filename = "hoge.png"
img = cv2.imread(filename)
h, w = img.shape[:2]
angle = 0

while True:
    angle = (angle + 10) % 360
    a = np.radians(angle)
    w_rot = int(np.round(w*abs(np.cos(a)) + h*abs(np.sin(a))))
    h_rot = int(np.round(w*abs(np.sin(a)) + h*abs(np.cos(a))))

    M = cv2.getRotationMatrix2D((w/2,h/2), angle, 1)
    M[0][2] += -w/2 + w_rot/2
    M[1][2] += -h/2 + h_rot/2

    img_rot  = cv2.warpAffine(img, M, (w_rot,h_rot))
    cv2.imshow(filename, img_rot)
    key = cv2.waitKey(100) & 0xFF
    if key == ord("q"):
        break

cv2.destroyAllWindows()

結果はこう。画像がウィンドウからはみ出ることはなくなったが、ウィンドウの位置(画像の左上の位置)が共通という制約の上でのアニメーションなので暴れているのは仕方がない。

uchuhikoushi_anim_1.gif

回転中心を指定する

中心を指定して回転させ、さらに作られた画像が暴れないようにするには、あらかじめ回転後の画像が描かれるキャンバスのサイズを決めておけばよい。
キャンパスのサイズを求めるのは難しくない。回転中心と四隅の距離のうち、最大のやつが半径に相当する。キャンバスのサイズはその2倍。
rot_cad_3.png

調子が良かったのだろう、以下のソースでは半径を求める式をわずか1行で表現している。
今となっては自分でもどういう計算をしているのか読み解くのが大変なほどだ。あまり技巧に走るのもよくないな。

ソース4
import cv2
import numpy as np

filename = "hoge.png"
img = cv2.imread(filename)
h, w = img.shape[:2]
xc, yc = 140, 60 # 回転中心
angle = 0

# 回転中心と四隅の距離の最大値を求める
pts = np.array([(0,0), (w,0), (w,h), (0,h)])
ctr = np.array([(xc,yc)])
r = np.sqrt(max(np.sum((pts-ctr)**2, axis=1)))
winH, winW = int(2*r), int(2*r)

while True:
    angle = (angle + 10) % 360
    M = cv2.getRotationMatrix2D((xc,yc), angle, 1)
    M[0][2] += r - xc
    M[1][2] += r - yc

    imgRot = cv2.warpAffine(img, M, (winW,winH))
    cv2.imshow("", imgRot)
    key = cv2.waitKey(100) & 0xFF
    if key == ord("q"):
        break

cv2.destroyAllWindows()

結果はこう。
uchuhikoushi_anim_2.gif

回転画像を背景画像に合成する

以上の処理を、前回のスプライト関数に追加する。
int()でなくmath.ceil()で切り上げしたほうが良かったかもしれない。

ソース5
import cv2
import numpy as np

def putSprite_mask2(back, front4, pos, angle=0, center=(0,0)):
    x, y = pos
    xc, yc = center
    fh, fw = front4.shape[:2]
    bh, bw = back.shape[:2]

    # 回転中心と四隅の距離の最大値を求める
    pts = np.array([(0,0), (fw,0), (fw,fh), (0,fh)])
    ctr = np.array([(xc,yc)])
    r = int(np.sqrt(max(np.sum((pts-ctr)**2, axis=1))))

    # 回転する
    M = cv2.getRotationMatrix2D((xc,yc), angle, 1)
    M[0][2] += r - xc
    M[1][2] += r - yc
    imgRot = cv2.warpAffine(front4, M, (2*r,2*r))  # 回転画像を含む外接四角形

    # 外接四角形の全体が背景画像外なら何もしない
    x0, y0 = x+xc-r, y+yc-r
    if not ((-2*r < x0 < bw) and (-2*r < y0 < bh)) :
        return back    

    # 外接四角形のうち、背景画像内のみを取得する
    x1, y1 = max(x0,  0), max(y0,  0)
    x2, y2 = min(x0+2*r, bw), min(y0+2*r, bh)
    imgRot = imgRot[y1-y0:y2-y0, x1-x0:x2-x0]

    # マスク手法で外接四角形と背景を合成する
    front_roi = imgRot[:, :, :3]
    mask1 = imgRot[:, :, 3]
    mask_roi = 255 - cv2.merge((mask1, mask1, mask1))
    roi = back[y1:y2, x1:x2]
    tmp = cv2.bitwise_and(roi, mask_roi)
    tmp = cv2.bitwise_or(tmp, front_roi)
    back[y1:y2, x1:x2] = tmp

    return back


if __name__ == "__main__":
    filename_back = "space.jpg"
    filename_front = "uchuhikoushi.png"
    img_back = cv2.imread(filename_back)
    img_front = cv2.imread(filename_front, -1)

    pos = [(0, 50), (300,200), (400,400), (500,-50), (-100,1000)] # 画像を置く左上座標        
    xc, yc = 140, 60  # 前景画像の回転中心
    angle = 0

    while True:
        back = img_back.copy()
        for x,y in pos:
            img = putSprite_mask2(back, img_front, (x,y), angle, (xc,yc))

            # 正しく描写されていることを確認する(必須ではない)
            cv2.circle(img, (x,y), 5, (0,255,0), -1)       # 前景画像の左上にマーク
            cv2.circle(img, (x+xc,y+yc), 5, (0,0,255), -1) # 回転中心にマーク
            cv2.putText(img, f"angle={angle}", (10,440), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)

        cv2.imshow("putSprite_mask2", img)

        key = cv2.waitKey(0) & 0xFF
        if key == ord("q"):
            break
        angle = (angle + 30) % 360

    cv2.destroyAllWindows()

できたできた。
uchuhikoushi_anim_3.gif

終わりに

本当はこの先、外接四角形を最小限にして左上座標を細かく制御することで同等の挙動を実現させたかったのだが、図形と行列と、つまりは高校数学レベルの問題がうまく解けずにそこまではできなかった。
えー? 今、高校で行列やらないの?!

参考記事

指定した角度づつ元画像の中心を軸に回転させた画像を作成して保存する。
opencvの画像回転で、はみ出した部分が切り取られないようにする方法
完全に理解するアフィン変換

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

犬ですが何か?Django--カスタムユーザーモデルを作成する

ユーザー登録/ログイン/ログアウトの機能追加

こんにちワン!柴犬のぽん太です。今日は暑いのでガリガリ君ソーダ味を食べました。めちゃくちゃ美味しかったです!ガリガリ君って埼玉県の会社が作っているんですね。ぽん太はまだ埼玉県に行ったことがないです。

さて、今日はユーザー管理機能に挑戦します。

accountアプリの作成

まずはaccountアプリケーションを作ります。

terminal
(venv_dog) Ponta@shiba_app # python manage.py startapp accounts

アプリケーションを作成したらそれをshiba_app/settings.pyのINSTALLED_APPSに登録します。ぽん太のINSTALLED_APPSは次のようになっています。

shiba_app/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'top.apps.TopConfig',
    'wan.apps.WanConfig',
    'accounts.apps.AccountsConfig',
]

最後の
'accounts.apps.AccountsConfig',
を追加しました。

shiba_app/setting.pyのその他の設定

ユーザー登録・認証機能のためにsettings.pyに幾つかの設定が必要です。
まず、メールアドレス認証のためのメール送受信ですが、これは開発環境ではコンソールへ表示させます。このための設定が、
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
です。

つぎに、Djangoがカスタムユーザーモデルを参照するように、
AUTH_USER_MODEL = 'accounts.CustomUser'
を設定します。

さらに、後述のdjango-allauthのために、
SITE_ID = 1
を設定します。

また、認証バックエンドの設定が、
AUTHENTICATION_BACKENDS = (
'allauth.account.auth_backends.AuthenticationBackend',
'django.contrib.auth.backends.ModelBackend',
)
です。
allauth.account.auth_backends.AuthenticationBackend
が一般ユーザーがメールアドレス認証でログインするための認証バックエンド、
django.contrib.auth.backends.ModelBackend
が管理サイトにスーパーユーザーがユーザー名でログインするための認証バックエンドです。

ACCOUNT_AUTHENTICATION_METHOD = 'email'
で、メールアドレス認証に設定し、
ACCOUNT_USERNAME_REQUIRED = True
でユーザー名を必須としています。

ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
ACCOUNT_EMAIL_REQUIRED = True
でメールアドレス確認を必須としています。

LOGIN_REDIRECT_URL = 'wan:index'
でログイン後、wanアプリケーションのindexにリダイレクトし、
ACCOUNT_LOGOUT_REDIRECT_URL = 'top:index'
でログアウト後、topアプリケーションのindexにリダイレクトします。

ACCOUNT_LOGOUT_ON_GET = True
は、ログアウトボタンのクリック後ただちにログアウトさせる設定です。

shiba_app/settings.py
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

AUTH_USER_MODEL = 'accounts.CustomUser'

SITE_ID = 1

AUTHENTICATION_BACKENDS = (
    'allauth.account.auth_backends.AuthenticationBackend',
    'django.contrib.auth.backends.ModelBackend',
)

ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_USERNAME_REQUIRED = True

ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
ACCOUNT_EMAIL_REQUIRED = True

LOGIN_REDIRECT_URL = 'wan:index'
ACCOUNT_LOGOUT_REDIRECT_URL = 'top:index'

ACCOUNT_LOGOUT_ON_GET = True

カスタムユーザーモデルの定義

Djangoにはデフォルトのユーザーモデルが定義されていますが、それをオーバーライドしてカスタムユーザーモデルを定義します。

account/models.py
rom django.contrib.auth.models import AbstractUser
from django.db import models


class CustomUser(AbstractUser):

    class Meta:
        verbose_name_plural = 'CustomUser'

    dogname = models.CharField(verbose_name='Dog Name', blank=False, max_length=40)
    introduction = models.TextField(verbose_name='Introduction', blank=True)

dogname, introductionを追加してみました。
あとで機会があればこれらの入力画面を作ってみたいと思います。

マイグレーション

terminal
(venv_dog) Ponta@shiba_app # python manage.py makemigrations
Migrations for 'accounts':
  accounts/migrations/0001_initial.py
    - Create model CustomUser

Process finished with exit code 0

(venv_dog) Ponta@shiba_app # python manage.py migrate
Operations to perform:
  Apply all migrations: accounts, admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0001_initial... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying accounts.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying sessions.0001_initial... OK
(venv_dog) Ponta@shiba_app # 

django-allauthのインストール

ユーザー認証機能の作り込みは面倒なので、django-allauthというパッケージを利用します。

terminal
(venv_dog) Ponta@shiba_app # pip install django-allauth

Django-allauthをアプリケーションで使えるようにするためにsettings.pyのINSTALLED_APPSに
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
の4行を追加します。

shiba_app/settings.py
INSTALLED_APPS = [
(中略)
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
]

もう一度マイグレーション

django-allauthが使うテーブルを作成するため、もう一度migrateコマンドを実行します。

terminal
(venv_dog) Ponta@shiba_app # python manage.py migrate       
Operations to perform:
  Apply all migrations: account, accounts, admin, auth, contenttypes, sessions, sites, socialaccount
Running migrations:
  Applying account.0001_initial... OK
  Applying account.0002_email_max_length... OK
  Applying sites.0001_initial... OK
  Applying sites.0002_alter_domain_unique... OK
  Applying socialaccount.0001_initial... OK
  Applying socialaccount.0002_token_max_lengths... OK
  Applying socialaccount.0003_extra_data_default_dict... OK

スーパーユーザーの作成

Djangoの管理画面にアクセスするため、スーパーユーザーを作成します。

terminal
(venv_dog) Ponta@shiba_app # python manage.py createsuperuser 
Username: ponta
Email address: ponta@example.com
Password: 
Password (again): 
This password is too short. It must contain at least 8 characters.
This password is too common.
Bypass password validation and create user anyway? [y/N]: N
Password: 
Password (again): 
Superuser created successfully.

おっと、パスワードが短すぎたり、簡単すぎるとpassword validationに引っかかるようです。
2回目には真面目に設定しました。

管理画面にログイン

スーパーユーザーを作成しましたので、一旦Djangoの管理画面にログインしてみます。
http://127.0.0.1:8000/admin/
にアクセスします。すると、
http://127.0.0.1:8000/admin/login/?next=/admin/
にリダイレクトされ、ログイン画面が現れました。

スクリーンショット 2020-08-30 18.22.33.png

ここにスーパーユーザーを作成したときのUsernameとPasswordを入力し、ログインします。すると、下記のように無事ログインできて、管理画面が現れました。

スクリーンショット 2020-08-30 18.22.55.png

ルーティング設定 shiba_app/urls.py

いま、shiba_appには3つのアプリケーション(top, wan, accounts)が作成されています。

shiba_app/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('top.urls')),
    path('wan/', include('wan.urls')),
    path('accounts/', include('allauth.urls')),
]

ルーティング設定 top/urls.py

top/urls.py
from django.urls import path
from . import views

app_name = 'top'

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
]

ルーティング設定 wan/urls.py

wan/urls.py
from django.urls import path
from . import views

app_name = 'wan'

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
]

画面設定top/views.py

top/views.py
from django.views import generic


class IndexView(generic.TemplateView):
    template_name = "top/index.html"

画面設定wan/views.py

wan/views.py
from django.views import generic


class IndexView(generic.TemplateView):
    template_name = "wan/index.html"

画面テンプレートtop/index.html

ログイン中はユーザー名とログアウトボタンが表示され、ログアウト中はユーザー登録ボタンとログインボタンが表示される仕組みです。

top/templates/top/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>柴犬ぽん太の「犬ですが何か?」</title>
</head>
<body>
    <h1>柴犬ぽん太の「犬ですが何か?」</h1>
    <p>柴犬のぽん太です。よろしくワン!</p>
    {% if user.is_authenticated %}
        <p>ユーザー名: {{ user }}</p>
        <p><a href="{% url 'account_logout' %}">ログアウト</a> </p>
    {% else %}
        <p><a href="{% url 'account_signup' %}">ユーザー登録</a></p>
        <p><a href="{% url 'account_login' %}">ログイン</a></p>
    {% endif %}
</body>
</html>

画面テンプレートwan/index.html

ログイン中はユーザー名とログアウトボタンが表示され、ログアウト中はユーザー登録ボタンとログインボタンが表示される仕組みです。(えっ!さっきと同じ??)

wan/templates/wan/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>柴犬ぽん太の「犬ですが何か?」</title>
</head>
<body>
    <h1>柴犬ぽん太の「犬ですが何か?」</h1>
    {% if user.is_authenticated %}
        <p>ユーザー名: {{ user }}</p>
        <p><a href="{% url 'account_logout' %}">ログアウト</a> </p>
    {% else %}
        <p><a href="{% url 'account_signup' %}">ユーザー登録</a></p>
        <p><a href="{% url 'account_login' %}">ログイン</a></p>
    {% endif %}
</body>
</html>

ユーザー登録テスト

では、トップページを確認してみます。

スクリーンショット 2020-08-30 21.59.52.png

ユーザー登録をクリックします。
スクリーンショット 2020-08-30 20.46.29.png

テスト用のユーザーを登録します。
スクリーンショット 2020-08-30 20.46.38.png

メールが送信されました。
今はテストなのでコンソール(ターミナル画面)にメール文が流れます。

terminal
[30/Aug/2020 11:45:57] "GET /accounts/signup/ HTTP/1.1" 200 1330
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [example.com] Please Confirm Your E-mail Address
From: webmaster@localhost
To: lisa@example.com
Date: Sun, 30 Aug 2020 11:46:30 -0000
Message-ID: (省略)

Hello from example.com!

You're receiving this e-mail because user Lisa has given yours as an e-mail address to connect their account.

To confirm this is correct, go to http://127.0.0.1:8000/accounts/confirm-email/NA:1kCLn4:g2Po76yr88dimfzO641FunQcGbYPexyYkTE4j0JLZ4Q/

Thank you from example.com!
example.com
-------------------------------------------------------------------------------
[30/Aug/2020 11:46:30] "POST /accounts/signup/ HTTP/1.1" 302 0

この表示の途中にある、
"http://127.0.0.1:8000/accounts/confirm-email/NA:1kCLn4:g2Po76yr88dimfzO641FunQcGbYPexyYkTE4j0JLZ4Q/"
にアクセスするとemail認証が完了します。

スクリーンショット 2020-08-30 20.55.21.png

email認証が完了したらログインしてみましょう。

スクリーンショット 2020-08-30 20.56.59.png

ログインすると次の画面になります。

スクリーンショット 2020-08-30 20.57.53.png

ログアウトすると最初の画面に戻ります。

スクリーンショット 2020-08-30 21.59.52.png

おしまい。じゃあまたね!ワン!

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

python3の地味なトリビア

他の人の記事眺めてて、どうもご存じないらしい、という点を幾つか。

コロンの後ろには1文だけ書ける

なので

exec("for i in range(10):\n\tprint(i)")

これは

for i in range(10):print(i)

もともと1行で書ける。execとか要らない。

これはコロンを使う全ての構文であてはまる。def,class,while,try/catch,if....他なにかあったっけ。

他所様のフレームワークのソースを探検してたら、こういうのよく見かけない?

class OreOreException(Exception): pass

式の継続が明らかな場合は、任意箇所で改行できる

例えば内包表記

xx = ["even" if i % 2 == 0 else "odd" \
    for i in range(10)]

これは

xx = [
  "even" 
      if i % 2 == 0 
      else "odd"
  for i in range(10)
]

こう書けます。

同じ理由で、通常の式を括弧で囲んで、無理くり複数行にも書ける。

一瞬でもカンマを使うと、その1部分だけがタプル表記に化けるので要注意。

タプル...内包表記...?

内包表記関連の記事を眺めててギョッとしたのだが、たまーにタプル内包表記という日本語を散見する。

Pythonのtuple内包表記の落とし穴 - done is better than perfect

Python タプルの内包表記とジェネレータ式 - Qiita

python2.5から、括弧内包表記はジェネレータとして機能するようだ。

関数型プログラミング HOWTO — Python 2.7.18 ドキュメント

2.4までなら確かに「タプル内包表記」だった可能性があるが。

タプルはそもそも、(括弧と)カンマで成立する構文。

5. データ構造 — Python 3.8.5 ドキュメント

タプルはコンマで区切られたいくつかの値からなります。例えば以下のように書きます:

括弧要らないらしい.... だがカンマは必須。

逆に、要素1個のときはどう書くの?という問いが出てくる。

python - How to create a tuple with only one element - Stack Overflow

>>> type( ('a') )
<type 'str'>

>>> type( ('a',) )
<type 'tuple'>

ポイントは末尾のカンマね。

ちなみにtuple()に突っ込むとタプルが出来上がるのは仕様です。
list()やらtuple()やらはdict()やらは、ジェネレータ/イテレータを直接受け付けるため。

組み込み型 — Python 3.8.5 ドキュメント

tuple([iterable])って書いてあるでしょ?

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

AtCoder Beginner Contest 177 問題C 間違った理由を調べてみた

はじめに

2020/8/29に行われたContest177の問題Cで、絶望するほどハマったので、調べてみました。

問題

https://atcoder.jp/contests/abc177/tasks/abc177_c

考えたこと

まともに計算すると、多分TLEになると思った。
でもちょっと工夫すればなんとかなりそう。

しばし熟考。

求める和が、{(A1+A2+…+An)^2-(A1^2+A2^2+…+An^2)}÷2 だという結論に達し、コード化。
およそ10分後、完成。

c.py
n = int(input())
l = input()
a = l.split(' ')

tmp = 0
tmp2 = 0

for i in range(n):
    tmp += int(a[i])

for i in range(n):
    tmp2 += int(a[i])*int(a[i])

print(int((tmp*tmp-tmp2)/2)%1000000007)

よっしゃ、今日は調子ええで!4問いけるかもしれんな!
と思った矢先、無常の判定が。
image.png
ここでフリーズしてしまい、この日は2問で終了。

なんで

コンテスト終了後、解説を見る。わからない。なんで?累積和でもこの計算方法でも、数学的には同じ値になるはずやん。
土曜日はとにかく納得いかなかったので、寝て、心を落ち着かせ、
日曜日に累積和バージョンを書いて、提出してみた。

c_.py
n = int(input())
l = input()
a = l.split(' ')

tmp = 0
total2 = 0

for i in range(n):
    tmp += int(a[i])

for i in range(n):
    total2 += int(a[i])*(tmp-int(a[i]))
    tmp -= int(a[i])

print(total2%1000000007)

image.png

えええええええええええええ

なんで?なんで同じことをしているのに、片方はWAで片方はACなん???

色々試してみた

まずpythonでint型の数字が、どれぐらい大きな数字を扱えるか確認した。
結果、pythonのver3では、メモリが許す限りいくらでも大きな値が使えるとのことだった。
とすると、おかしいのは÷2のところ?
きっと大きな数字の時に、変なことが起きているに違いない。

適当な大きさの数字を10個入力して、2で割る前の数字と2で割った時の数字を表示させてみた。

input_data1
10
1234567 2345678 3456789 4567890 5678901 6789012 7890123 8901234 9012345 1111111

2257615544087530
1128807772043765

これは大丈夫そう。もうちょっと大きくしてみる。

input_data2
10
12345678 23456789 34567890 45678901 56789012 67890123 78901234 89012345 90123456 11111111

225761590208477104
112880795104238560

1の位を見ると、2で割る前が4だから、2で割ったものは2か7になるはず。
なのに0。
ということで、大きな数字を2で割ると、どうもおかしな事が起こるらしい。

どうして2で割れないの

「python 割り算 おかしい」でググったら、以下の記事を見つけました。

Python3で巨大な浮動小数計算の結果が変だったので理由を調べてみた
https://paiza.hatenablog.com/entry/2017/08/01/Python3%E3%81%A7%E5%B7%A8%E5%A4%A7%E3%81%AA%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E8%A8%88%E7%AE%97%E3%81%AE%E7%B5%90%E6%9E%9C%E3%81%8C%E5%A4%89%E3%81%A0%E3%81%A3%E3%81%9F%E3%81%AE%E3%81%A7%E7%90%86

うーん、分かったような・・・でもやっぱり分からない。。。
ただ大きい数字は、割り算するとおかしな事が起こりうる、ということなんですね。
一つ賢くなりました。ありがとうございました。

・・・ああああ、悔しい。

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

VScode+RemoteWSLでWindowsのPython実行環境を作る

この記事ではVScodeとWSLを用いてWindows上で動くPythonの実行環境を作成する方法を解説します。

メリット

具体的な構築方法に入る前にまず、多くあるPython実行環境の中でこれを選ぶメリットを解説したいと思います。
1. VSCode上でステップ実行等の高度な機能が利用できる
2. インストールするパッケージの管理が容易
3. 必要な機能を選んで使用することができる。

まず1に関してVSCodeを用いることで特別な設定をほぼ行うことなくプログラムのステップ実行(一行ずつ挙動を確認しながら実行すること)やオートフォーマット(スペーシングなどのプログラムの見た目の調節を自動で行う機能)を利用することができます。
次に2に関して。この環境ではPythonにデフォルトで備わっている仮想環境の機能を使用します。そのため、pipによるコマンドベースでのパッケージの管理を行うことができます。また、VSCodeから使用することでactiveな仮想環境の切り替えを容易に行うことが可能です。
最後に3つ目に関してです。今回構築する環境でインストールするのはVSCode関連では拡張機能のRemote-WSLとPython、WSL関連ではPythonのみです。ミニマムな構築からVSCodeの拡張機能を用いた便利機能満載の構築まで自分で選択して行うことができます。

構築手順

それでは実際の構築手順を確認していきます。すでに多くの記事が存在しているため、VSCodeのインストールおよびWSLのインストールに関しては省略させていただきます。

WSL側のセットアップ

Pythonのインストール。お好みでバージョン指定なんかを入れてください。
sudo apt install -y python

プロジェクトのディレクトリを作り、その配下に仮想環境フォルダを作成します。
mkdir sample_project
cd sample_project
python3 -m venv venv

VS Code側のセットアップ

拡張機能の「Remote-WSL」をインストールしてください。
image.png
その後、一度Remote-WSLを開き(左下、緑色のアイコン)、Remote-WSL側に拡張機能の「Python」をインストールしておいてください。(Windows側とRemote-WSL側で拡張機能が分けて管理されています。すでにPythonの拡張をインストールしている場合でも追加の設定が必要です)

コード実行方法

Remote-WSLの機能を用いてVS Code上からWSL上に作成したsample_projectフォルダを開きます(メニューのファイル→フォルダを開くより)。
この状態でVS Codeがフォルダに配置されている仮想環境を認識し、自動で読み込みを行ってくれます。実行したい.pyファイルを開き、F5キーを押すことでフォルダ内の仮想環境がactivateされた状態でコード実行を行うことができます。ブレイクポイントの設定等も可能です。

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

[CovsirPhy] COVID-19データ解析用Pythonパッケージ: SIR model

Introduction

COVID-19のデータ(PCR陽性者数など)のデータを簡単にダウンロードして解析できるPythonパッケージ CovsirPhyを作成しています。パッケージを使用した解析例、作成にあたって得られた知識(Python, GitHub, Sphinx,...)に関する記事を今後公開する予定です。

英語版のドキュメントはCovsirPhy: COVID-19 analysis with phase-dependent SIRs, Kaggle: COVID-19 data with SIR modelにて公開しています。

今回は基本モデルSIR modelについて紹介します。実データは出てきません。
英語版:Usage (details: theoretical datasets)

1. 実行環境

CovsirPhyは下記方法でインストールできます!

Python 3.7以上, もしくはGoogle Colaboratoryをご使用ください。

  • 安定版:pip install covsirphy --upgrade
  • 開発版:pip install "git+https://github.com/lisphilar/covid19-sir.git#egg=covsirphy"
# データ表示用
from pprint import pprint
# CovsirPhy
import covsirphy as cs
cs.__version__
# '2.8.0'
実行環境
OS Windows Subsystem for Linux
Python version 3.8.5

2. SIR modelとは

Susceptible(感受性保持者)がInfected(感染者)と接触したとき、感染する確率をEffective contact rate $\beta$ [1/min]と定義します。$\gamma$ [1/min]はInfectedからRecovered(回復者)に移行する確率です12

\begin{align*}
\mathrm{S} \overset{\beta I}{\longrightarrow} \mathrm{I} \overset{\gamma}{\longrightarrow} \mathrm{R}  \\
\end{align*}

3. 連立常微分方程式

総人口$N = S + I + R$として、

\begin{align*}
& \frac{\mathrm{d}S}{\mathrm{d}T}= - N^{-1}\beta S I  \\
& \frac{\mathrm{d}I}{\mathrm{d}T}= N^{-1}\beta S I - \gamma I  \\
& \frac{\mathrm{d}R}{\mathrm{d}T}= \gamma I  \\
\end{align*}

4. 無次元パラメータ

このまま扱っても良いですが、パラメータの範囲を$(0, 1)$に限定するため無次元化します。今回の記事では出てきませんが、実データからパラメータを計算する際に効果を発揮します。

$(S, I, R) = N \times (x, y, z)$, $(T, \beta, \gamma) = (\tau t, \tau^{-1}\rho, \tau^{-1}\sigma)$, $1 \leq \tau \leq 1440$ [min]として、

\begin{align*}
& \frac{\mathrm{d}x}{\mathrm{d}t}= - \rho x y  \\
& \frac{\mathrm{d}y}{\mathrm{d}t}= \rho x y - \sigma y  \\
& \frac{\mathrm{d}z}{\mathrm{d}t}= \sigma y  \\
\end{align*}

このとき、

\begin{align*}
& 0 \leq (x, y, z, \rho, \sigma) \leq 1  \\
\end{align*}

5.(基本/実効)再生産数

(基本/実効)再生産数 Reproduction numberは次の通り定義されます3

\begin{align*}
R_t = \rho \sigma^{-1} = \beta \gamma^{-1}
\end{align*}

6. データ例

パラメータ$(\rho, \sigma) = (0.2, 0.075)$及び初期値を設定してグラフ化します。

# Parameters
pprint(cs.SIR.EXAMPLE, compact=True)
# {'param_dict': {'rho': 0.2, 'sigma': 0.075},
# 'population': 1000000,
# 'step_n': 180,
# 'y0_dict': {'Fatal or Recovered': 0, 'Infected': 1000, 'Susceptible': 999000}}

(基本/実効)再生産数:

# Reproduction number
eg_dict = cs.SIR.EXAMPLE.copy()
model_ins = cs.SIR(
    population=eg_dict["population"],
    **eg_dict["param_dict"]
)
model_ins.calc_r0()
# 2.67

グラフ表示:

# Set tau value and start date of records
example_data = cs.ExampleData(tau=1440, start_date="01Jan2020")
# Add records with SIR model
model = cs.SIR
area = {"country": "Full", "province": model.NAME}
example_data.add(model, **area)
# Change parameter values if needed
# example_data.add(model, param_dict={"rho": 0.4, "sigma": 0.0150}, **area)
# Records with model variables
df = example_data.specialized(model, **area)
# Plotting
cs.line_plot(
    df.set_index("Date"),
    title=f"Example data of {model.NAME} model",
    y_integer=True,
    filename="sir.png"
)

sir.png

7. 次回

基本モデルSIR modelをCOVID-19用に改変したモデルSIR-F modelについて紹介します。

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

環境構築

初めに

pythonの環境を構築しました。経緯をまとめます。

VSCODEのインストール

Visual Studio Code インストール手順

VSCODEのダウンロード

https://code.visualstudio.com/
を開いたらダウンロードのボタンを押す。
image.png
ダウンロードが完了したら指示に従いインストールする。
指示に従い「次へ」
OSを再起動する。

pythonのインストール

image.png

任意の場所に「mypythonproject」というフォルダを作成する。
ターミナル(右下のエリア)に記載のある
「PS C:\Users\simps\OneDrive\ドキュメント\MyPythonProject>」←の後に「python」と入力
入力後、「ENTER」を押す。

WINDOWSがインストール画面(python3.8)を開く。
インストールする。

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

Python3のリスト・辞書型の書き方

型について

# List型
  sampleA = [[name,"Sato"],[age,1]], [[name,"Mori"],[age,20]]

# Dictionary型
  sampleB = {"apple":1, "orange":2}

# 型を調べる
  type(sampleA)
  type(sampleB)

#List型からKey/Valueを取り出す
  for index, value in enumerate(sampleA)

#辞書型からKey/Valueを取り出す
  for index, value in items(sampleA)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Colaboratoryで競馬データスクレイピング

Colaboratoryで競馬データのスクレイピング

競馬データのスクレイピングをしたい、かつ機械学習となったら、Colaboratory便利なので、
Colaboratoryで競馬のスクレイピングをしたコードをメモします。

(htmlの変更でスクレイピングできなくなるかもしれませんので注意してください。2020.8/30動作確認済み)

以下コード

sample.ipynb
#Chromiumとseleniumをインストール
#「!」印ごとColaboratoryのコードセルに貼り付けます。
!apt-get update
!apt install chromium-chromedriver
!cp /usr/lib/chromium-browser/chromedriver /usr/bin
!pip install selenium
#BeautifulSoupのライブラリをインポート
from bs4 import BeautifulSoup
import requests
import re
import pandas as pd
race_date ="2020"
race_course_num="06"
race_info ="03"
race_count ="05"
race_no="01"
url = "https://race.netkeiba.com/race/result.html?race_id="+race_date+race_course_num+race_info+race_count+race_no+"&rf=race_list"

# 該当URLのデータをHTML形式で取得
race_html=requests.get(url)
race_html.encoding = race_html.apparent_encoding  
race_soup=BeautifulSoup(race_html.text,'html.parser')
# 無駄な文字列を取り除いてリストへ格納
def make_data(data):
    data = re.sub(r"\n","",str(data))
    data = re.sub(r" ","",str(data))
    data = re.sub(r"</td>","'",str(data))
    data = re.sub(r"<[^>]*?>","",str(data))
    data = re.sub(r"\[","",str(data))
    return data
# レース表だけを取得して保存
HorseList = race_soup.find_all("tr",class_="HorseList")

# レース表の整形
# 表の横列の数=15("着順,枠,馬番,馬名,性齢,斤量,騎手,タイム,着差,人気,単勝オッズ,後3F,コーナー通過順,厩舎,馬体重(増減))
col = ["着順","枠","馬番","馬名","性齢","斤量","騎手","タイム","着差","人気","単勝オッズ","後3F","コーナー通過順","厩舎","馬体重(増減)","出馬数"]

# 出馬数をカウント
uma_num = len(HorseList)

df_temp = pd.DataFrame(map(make_data,HorseList),columns=["temp"])
df = df_temp["temp"].str.split("'", expand=True)
df.columns= col
df["出馬数"] = uma_num 
df

スクリーンショット 2020-08-30 19.24.04.png

最後に

あとは日付などを変えていけばたくさんスクレイピングできます。
環境構築もいらないColaboratoryはやっぱり便利ですね。

参考

https://qiita.com/Mokutan/items/89c871eac16b8142b5b2
https://qiita.com/ftoyoda/items/fe3e2fe9e962e01ac421

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

TestData 作成ライブラリ Mimesisの紹介

はじめに

大量の(それっぽい)企業データがあったら良いなぁという状況があり、Mimesisというライブラリが便利だったので紹介します

対象

フロントエンジニア、テストデータを作成したい人、Pythonが好きな人
python3.6以上がインストールされていること

ゴール

インストール〜簡易的なJSONテストデータを出力できるまで

Mimesisとは

テストデータ作成ライブラリ
Fakerが有名のようだが、法人データ作成がないと思われた
Mimesisでは法人データが作成でき、かつちょっと変わったHomePageだとか
IPアドレスのようなデータも作成できるようなので、このライブラリを使用してデータを作成してみました
Mimesis公式

インストール

Mimesisのインストールはpipのみ

$ pip install Mimesis

pipってとても優秀なやつだと、毎回思います(コピペ)

Getting Started

簡単な使い方

main.py
import mimesis

# テストデータを日本語に設定
g = mimesis.Generic('ja')

# 会社名と法人タイプ 
print("{0} {1}".format(g.business.company_type(), g.business.company()))
# homePage
print(g.internet.home_page())

起動&閲覧

ターミナルで

$ python main.py 
株式会社 デンカ生研
https://bantay.moscow

なんとなくありそうな会社名じゃないでしょうか

JSONデータとして出力

せっかくなのでJSONのテストデータを作成してみます
(SwaggerにしろImportするにしろそのほうが便利なので)

ソース

先程のソースを

main.py
import json
from datetime import date, datetime
import mimesis

# 日付と数値のフォーマットを揃える(JSON出力時にはこれを用意しておくと便利)
def format_default(obj):
    if isinstance(obj, datetime) or isinstance(obj, date):
        return obj.isoformat()
    if isinstance(obj, decimal.Decimal):
        #小数部分が無ければIntで返す
        if float(obj).is_integer():
            return int(obj)
        else:
            return float(obj)
    raise TypeError

# 複数件セットできるようにDictを準備
data = []
g = mimesis.Generic('ja')

# 複数件生成してDictにセット(必要件数分ここを変更
for idx, x in enumerate(range(0, 2)):
    ins_data = {}
    ins_data['company_id'] = idx  # Id
    ins_data['company_name'] = "株式会社 {0}".format(g.business.company())  # 会社名
    ins_data['foundation_date'] = g.datetime.formatted_date("%Y/%m")  # 設立年月日
    ins_data['postal_code'] = g.address.postal_code()  # 郵便番号
    ins_data['state'] = g.address.state()  # 都道府県
    ins_data['city'] = g.address.city()  # 〜市
    ins_data['street'] = g.address.street_name()  # 番地
    ins_data['home_page'] = g.internet.home_page()  # homepage
    data.append(ins_data)  # Dictに追加

# DictをJSONとしていい感じに整形して出力
print(json.dumps(data, default=format_default, indent=2, ensure_ascii=False))
$ python main.py 
[
  {
    "company_id": 0,
    "company_name": "株式会社 三菱UFJフィナンシャルグループ",
    "foundation_date": "2003/08",
    "postal_code": "559-9285",
    "state": "秋田県",
    "city": "松山市",
    "street": "目黒",
    "home_page": "https://trachytes.frl"
  },
  {
    "company_id": 1,
    "company_name": "株式会社 大丸",
    "foundation_date": "2004/02",
    "postal_code": "564-0918",
    "state": "福島県",
    "city": "太田市",
    "street": "要町",
    "home_page": "https://arvin.tui"
  }
]

どうでしょう、見てるだけでも楽しい感じがしますね

あとはこんな感じで

$ ptyhon main.py > testData.json

テストデータ(JSON)を作成して、SwaggerやFrontのMock用データに利用しています

おまけ

最後に公式に書いてあることですがどんなデータを作成できるか紹介しておきます
(他にも使い所がわからないような面白いものもあるので

Random

idやamountなど

Address

countryやcityなど

Business

companyやcompany_typeなど

Datetime

daysやhoursなど

Food

drinkやvegetableなど (これ面白いですよね

Person

first_nameやemail, blood_typeなど

Text

alphabetやanswerなど

Development

osやversionなど

File

file_nameやmime_typeなど

Hardware

cpuやscreen_sizeなど

Internet

home_pageやhttp_method, ip_v4など

Numbers

complex_numberやintegers(start=0, end=10, n=10)など

Path

homeやrootなど

Path

homeやrootなど

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

[Python × AWS × Serverless] PyCon JP 2020 の登壇で言い残したこと

Python × AWS × Serverless 初学者が次の一歩を踏み出すためのテクニック

というタイトルで登壇してきました。社外の登壇は超久々でとても緊張しました。

当初考えてた内容から路線変更したりとか、人はなぜ締め切りをめいっぱい使ってしまうのか、とか、色々考えたりしながらなんとか形にはなりましたが、構成や個別のトピックとか思い返せばまだ言い足りないことがあったなと思いましたのでここで補足しようと思います。

スライドは SpeakerDeck で公開しています。

スライドタイトルについて

内容的に Tips の紹介という趣のスライドなので、「これで初学者を抜け出せるのか?」って疑問にはハッキリ回答できてないかもなぁ、、、と感じました。

もちろんタイトル詐欺の意図はありません。自分なりにこのタイトルと内容を選んだ背景が(一応は)ありますので記しておきます。

私のバックグラウンドですが、AWS方面は割と知見があるものの、アプリ開発のがっつりした経験は持っていない、といったスキルセットです。このような背景があって、基本的なディレクトリ構成とか、どうやって設計していくかとか、そのへんの慣れがなく戸惑っていた側面が大きかったんです。で、わからないがゆえに「サーバーレスに固有な何かがあるのでは?」みたいな不安も出てきて、どうするのが良いのかわからなかったです(自覚的ではなかったのでなかなか解消できませんでしたが)。

その後、(本発表でも喋ったように)サーバーレスといえどアプリ開発部分の考え方は普通に言語のプラクティスに従えば良いとわかりました。これからサーバーレスに挑戦される皆さまには、必要以上に恐れることないよということが伝わればいいなと。

私自身も、以前はサーバーレスについて「次何したらいいのかわからなくてモヤモヤする」状態を経験していました。そのときに知りたかった情報というのが今回のネタだった、ということで発表に至っております。

このような前置きの共有って地味に大事な部分ですよね。共感を得られるストーリーとか Why が提起されないプレゼンって訴求力がないので。ここを省いてしまったことで、視聴者のみなさまに訴求するメッセージがわかりにくくなってしまったかもしれないなと、大いに反省しました。

AWS サービスのリソース名を明示的に与える

「ステージ」云々のセクションで説明を入れそびれた部分です。

Serverless Framework は、デプロイするAWSリソースの名前をステージ間で衝突しないようによしなに命名してくれます。よって、 Serverless Framework を使っていれば多くの場合 Name 属性は必須ではありません。

しかし、あえて「リソース名は明示的に命名した方がいいんじゃないかな?」というのがここで述べたいことです。スライドにも実はちょろっと出てきますが、命名規則というのは例えばこういうものです↓

# template.yml
# SQS Queue name
Name: ${self:service}-my-queue-${self:provider.stage}

こういう命名にすることでサービス間/ステージ間で名前が被らない・予測しやすいリソース名管理ができます。

反対意見として、「インフラは全部コードで管理していくんだから、人間が見るための名前に気を遣う必要なくない?」とする考え方もあります。私もどっちかと言えばそっちを支持したい派です。

リソース名の衝突や、文字数制限 (例: lambda の関数名は64文字まで) などの問題を気にしなくて済む分、自分で変に命名しない方が楽だったりします。命名規則の管理も、(スタックの)ソースの記述が増えてうるさくなっちゃいいますし。できるなら名前はおまかせの方が嬉しいんです。

では、何故明示的な命名をした方がよいのでしょうか。個人的にうれしいポイントは2つあって、それぞれ以下の通りです。このへんは自分が受け持ってる案件の状況がバイアスに入ってると思います。たぶん。

Lambda のログを検索しやすい

Step Functions をよく利用するので、複数の Lambda にまたがってログを検索したいシーンがちょいちょい出てきます。

ログの検索にはいまのところ CloudWatch Logs Insights を使うケースがほとんどなのですが、その時のロググループの名前が(明示的に命名規則を付けていると)スッキリしますので、検索がちょっとだけやりやすいです。

おそらく、Datadogを利用しつつログ検索はタグで絞るようにするとか、(命名規則に頼らずとも)色々やりようはあるんだろうと思うのですが、今自分が採れる選択の中ではこのやり方が運用シーンにも即しておりそこそこ妥当性のあるやり方かなと思っています。

※このへんは周辺ツール (CLI や Datadog など)をもうちょっと充実させれば景色も見解も変わってくると思います

リソース名を参照する時に見通しがよく、保守しやすい

スタックの記述とアプリ側の記述、どっちもある程度スッキリ書くためには命名規則はあった方がメリットが大きいと思います(より主張したいメリットはこっちです)。

シンプルな構成図なら CloudFormation の GetAtt やら Ref やらで値を引いてやればいいので、さほど問題じゃありません。命名規則が役立つのは、構成図が膨れてきた頃合いです。

例えば、SQS のキューを複数作っている構成を想像してみます。Python のコード上ではキューのエンドポイント、すなわち Queue URL (AccountId, Region, QueueName の3つによって特定可能) が必要です。

作成したキューの URL を Ref で参照して lambda 環境変数に渡してあげてもよいのですが、個人的にはアカウントID、リージョン、キューの Name 属性をそれぞれ渡してあげれば十分かな、と思います。

アカウントIDやリージョンは他のAWSサービスを利用する場合にも出番がありそうです。あって困るものではなさそうですし、Queue URL を組み立てるユーティリティ関数の実装もごく簡単ですので、小回りが効きそうな方を選択するのが良いだろうと考えています。

Python のコードベースとしての管理は上記の通りとして、今度はスタック側の管理を考えてみます(どちらかと言えば、命名規則のメリットが大きいのはこちらだと考えます)。

さっきの SQS のキュー名の定義を再掲します。キューの名前は、 servicestage を join して作るイメージでした。

# 【再掲】 template.yml
# SQS Queue name
Name: ${self:service}-my-queue-${self:provider.stage}

実際のスタック定義としては、上記の Name の文字列全体(あるいは my-queue の部分)をテンプレートの環境変数 or custom セクションで宣言してあげる感じになります。

このやり方をすることで、他所のリソース(例えば IAM ポリシーの定義)からの参照が比較的スッと書けます。名前の部分は変数として参照可能な宣言を行っているので、 ARN などは単に join して組み立てるだけです。

ここで「いやいや、 Ref とか GetAtt とかあるじゃん」という指摘があると思いますが、それらを迂闊に使うのは個人的にあまりおすすめできません。

特にポリシー周りにおいて、これらを濫用すると CloudFormation の循環参照が起こりやすい気がします。このへんは経験則によるところが大きく明確な文章で根拠を示せませんが、私としては軽々しく AWS リソースを直接参照する関数を使いたくないんですよね...。代えて、依存先として害が少ない環境変数や、 custom セクションの変数に依存させる戦略を採っています。意識的に(特にIAMポリシー周りでの) Ref, GetAtt は使用を控えめにしています。

ただでさえ CloudFormation のデバッグは苦行なのに、構成が大きくなってから後で CloudFormation の循環参照問題に頭抱えるハメになるの、イヤですよね...? あれは人類がデバッグするものじゃないです...

このような理由があって、私はリソース名を(多少冗長で面倒であったとしても)明示的に宣言するのが好みです。この節で書いたことはあくまで副次的な効果ではありますが、私としては結構重く見ています。

おわり

サーバーレスのネタって、概要をさらう資料やチュートリアルをこなす程度の情報なら割とそのへんに転がってるのですが、結局それだけでは実際の開発シーンにおいて痒い部分を解消してくれない実感があったので、そのへんを埋めたい思いで応募してみました。

今の私が伝えたいことはおおよそ喋れたと思いますが、AWS周りの基礎知識はもっと深堀りできる部分だったので45分セッションで応募してみても良かったかもしれません(Python 成分が薄くなっちゃうので TPO 的にどうなの、とは思います)

一方で、残念ながら思ったより反応が少なめだったので、今回の話自体に需要があったのかどうか、今回の発表からはいまひとつ感触がつかめないところではありました(裏番組のセッションが強すぎた点、私の発表自体も改善余地があった点、PyCon の主要な参加層にはミートしづらいネタだった、など考えうる要因は複数あります)。需要皆無ということはおそらくないはずなので、場を変えて喋ってみるなどして探っていこうと思います。ベースの資料はできたことですし。

実践寄りな知識をもうちょっと体系立てて書けたら良いなとも思います。次回の技術書典で頑張ってみるというのも良いかもしれませんね。

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

【Python】いろんな型について調べてみた!(typing)

はじめに

python3.5 より python にも型という概念が組み込まれました。
使用するには、 import typing を指定してあげる必要があり、型のチェックには mypy を使用します。

mypy について

インストール

まず、 mypy を使用するためにパッケージをインストールします。

$ pip install mypy

設定ファイル

mypy.ini を以下のように作成し、そこに設定情報を記入します。

mypy.ini
[mypy]
python_version = 3.8
disallow_untyped_calls = True
disallow_untyped_defs = True
disallow_untyped_decorators = True
disallow_incomplete_defs = True
check_untyped_defs = True
pretty = True

[mypy-django.*]
ignore_missing_imports = True

設定できるものについては、こちら を参照してください。

いろんな使用方法

組み込み型

組み込み型は、そのまま使用することができます。
ただ、list には、 typing.Listというのもあり、こちらの場合、配列に入れる変数の型まで指定できるので、 typing.List を使うほうが良いかと思います。

num: int = 1
float_num: float = 1.11
is_something: bool = True
text: str = 'hoge'
byte_str: bytes = b'fuga'
arr: list = [0, 1, 2, 3]

Any 型

何の型が変数に入るか分からない場合は、 Any を使用します。
この型が指定されている場合は、型検査は行われません。

something: Any = Something()

全ての型の基底クラスである object も同じように使用できますが、object は、ある値が型安全な方法で任意の型として使えることを示すために使用し、Any はある値が動的に型付けられることを示すために使用します。

Dict 型

dictionary 型を使用する場合は、 typing.Dict を使用します。

dictionary: Dict[str, int] = {'hoge': 1, 'fuga': 2}

Tuple 型

タプル型を使いたい場合は、 typing.Tuple を使用します。

something: Tuple[str, int] = ('hoge', 1)

Set 型

set 型を使用したい場合は、typing.Set を使用します。
set 型は、重複しないようをのコレクションです。

something: Set[int] = {6, 7}

Sequence 型

シーケンス型を使用したい場合は、typing.Sequence を使用します。
シーケンス型は、順番(シーケンシャル)に処理するためのデータ構造です。

something: Sequence = [0, 1, 2]

Iterator 型

イテレータ型を使用したい場合は、typing.Iterator を使用します。
イテレータ型はイテラブルなクラスで __iter__() を持っている必要があります。

something: Iterator = IterableClass()

Mapping 型

マッピング型を使用したい場合は、typing.Mapping を使用します。
マッピング型は任意のキー探索を行うためのデータ構造で、Immutable な型です。

something: Mapping[str, int] = {'hoge': 1, 'fuga': 2}

Union 型

ユニオン型を使用したい場合は、typing.Union を使用します。
ユニオン型では、複数の型を指定することができます。

something_mapping: Mapping[str, int] = {'hoge': 1, 'fuga': 2}
something_union: Union[int, None] = something_mapping.get('hoge') # 1
something_union: Union[int, None] = something_mapping.get('piko') # None

Optional 型

typing.Optional は、typing.Union 型の None が必ず選択されているようなイメージです。

something_mapping: Mapping[str, int] = {'hoge': 1, 'fuga': 2}
something_optional: Optional[int] = something_mapping.get('hoge') # 1
something_optional: Optional[int] = something_mapping.get('piko') # None

Callable 型

関数を使用したい場合は、typing.Callable を使用します。

def something() -> bool:
    return True

something_callable: Callable = something

Literal 型

決まった値しか入らないことを保証したいときは、typing.Literal を使用します。

mode: Literal['r', 'rb', 'w', 'wb'] = 'r' # OK
mode: Literal['r', 'rb', 'w', 'wb'] = 'a' # NG

AnyStr 型

他の種類の文字列を混ぜることなく、任意の種類の文字列を許す関数によって使われるようにしたいときは、typing.AnyStr を使用します。

def concat(a: AnyStr, b: AnyStr) -> AnyStr:
    return a + b

concat(u"foo", u"bar")  # OK unicodeが出力されます
concat(b"foo", b"bar")  # OK bytesが出力されます
concat(u"foo", b"bar")  # NG unicodeとbytesが混ざっているためエラーになります

型のエイリアス

これまでの型を任意の型名で定義することができます。

Vector = List[float]
ConnectionOptions = Dict[str, str]
Address = Tuple[str, int]
Server = Tuple[Address, ConnectionOptions]

NewType

異なる型を作るためには typing.NewType を使用します。

UserId = NewType('UserId', int)
some_id = UserId(524313)

ジェネリックス

Generic 型を使用するには、以下のようにします。

T = TypeVar('T')

# これが Generic 関数
# l にはどんな型でも良い Sequence 型が使える
def first(l: Sequence[T]) -> T:
    return l[0]

ユーザ定義のジェネリック型は以下のように使用できます。

T = TypeVar('T')

class LoggedVar(Generic[T]):
    def __init__(self, value: T, name: str, logger: Logger) -> None:
        self.name = name
        self.logger = logger
        self.value = value

    def set(self, new: T) -> None:
        self.log('Set ' + repr(self.value))
        self.value = new

    def get(self) -> T:
        self.log('Get ' + repr(self.value))
        return self.value

    def log(self, message: str) -> None:
        self.logger.info('%s: %s', self.name, message)

logger: LoggedVar[str] = LoggedVar('hoge', 'Test', Logger())
logger.get() # T は、str 型として扱われる

キャスト(typing.cast)

一度、定義した型を変更したいときは、typing.cast を使ってキャストします。
処理をできる限り速くするため、実行時には意図的に何も検査しません。

a = [4]
b = cast(List[int], a)
c = cast(List[str], a) # [4] が文字列になることはないので、変換したい場合は、自分で変換処理を追加する

定数(typing.Final)

定数を使用する場合は、typing.Final を使用します。

SOMETHING: Final[str] = 'Something'

おわりに

いかがだったでしょうか?
型を駆使することでより強固なアプリケーションの作成が行えるかと思います。
ただ、型を指定しているからといって、特別処理が早くなることはないので、そこはご注意ください。
実際に動作を見ていないものもあるので、間違っているところなど見つけましたら、お知らせいただけると助かります。

参考

https://docs.python.org/ja/3/library/typing.html

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

DiscordのSimplePollっぽいやつをスクラッチ実装しようとしたら手間取った

https://top.gg/bot/simplepoll

↑こういうのを導入しようという話がありましたが、敢えて他にbotを入れるよりも自前のPython botで作った方が早そう&メンテしやすそうだったのでしました

手間取ったポイント

  • 「自分がしたばかりの発言」にリアクションをつける方法
  • リアクション絵文字の指定

コード

#poll
@bot.command()
async def poll(ctx, *args):
    text = "**{}**".format(args[0])
    n = len(args)-1
    if(n == 0):
        await ctx.send("ないようがないよう")
        return
    if(n > 20):
        await ctx.send("ないようがありすぎるよう")
    return
    chars = "abcdefghijklmnopqrstuvwxyz"
    disc = ""
    for i in range(n):
        disc += ":regional_indicator_{}:{}\n".format(chars[i],args[i+1])
    embed = discord.Embed(description=disc)
    await ctx.send(text,embed=embed)
    for i in range(n):
        emoji = "??????????????????????????"
        if emoji:
            lm =  ctx.channel.last_message
            await lm.add_reaction(emoji[i])

手間取ったポイント1

  • 「自分がしたばかりの発言」にリアクションをつける方法

 ctx.messageでコマンドのきっかけとなったメッセージオブジェクトは拾えますが、crx.sendで送ったbot自身の発言のメッセージにリアクションで投票ボタンをつける必要があるので、それは別に取得する必要があります。

 最初は、https://discordpy.readthedocs.io/ja/latest/api.html#discord.TextChannel.last_message_id last_message_idでIDを取得し、https://discordpy.readthedocs.io/ja/latest/api.html#discord.TextChannel.fetch_message fetch_messageでなんとかなるかと思いましたが、これで得られたmessageオブジェクトがなんか違うものだったらしく、add_reactionの属性なしと言われて失敗。

 結論的にはhttps://discordpy.readthedocs.io/ja/latest/api.html#discord.TextChannel.last_message last_messageという属性でメッセージオブジェクトがそのまま取得できたのでそれで敢行。これだと任意のメッセージIDから取得することはできないですが、今回はそこまで必要なかったのであきらめました。ちょっと時間差があるらしく、add_reactionの直前にしないと一つ前のメッセージにリアクションをつけてしまうことがありました。

手間取ったポイント2

 公式リファレンス → https://discordpy.readthedocs.io/ja/latest/faq.html

 テキストであれば、普段discordで打ってるように:hogehoge:と打てばそのまま絵文字になってくれますが、リアクションだとそうはいかないようです。Unicode絵文字をそのまま打ったり、{THUMBS UP SIGN}等のキーワード(Discord内のそれとは別)が必要なようです。
 
 また、今回わかったこととして、サーバー独自の絵文字と、デフォルトの絵文字では方法が異なるということです。

サーバー独自の絵文字の場合

 サーバーのIDを指定して、絵文字の名前で検索する必要があります。

guild = bot.get_guild('ここにサーバーのID')
emoji = discord.utils.get(guild.emojis, name='ここに絵文字のDiscordでの呼び方')
if emoji:
    await message.add_reaction(emoji)

 また、Discordのチャットで\:hogehoge:などと発言するとIDを表示してくれるので、ID直打ちも可です。そんな手間は変わらないですが。サーバーIDが分からなければ、開発者モードを有効にしてサーバーアイコンを右クリックすると得られます。

 上のコードのbotは、参考にしたコードによってはclientになっているかもしれません。いずれにせよ、最初に指定した変数名と一致させる必要があります。

Unicodeにデフォルトで入っている絵文字の場合

 こっちの方がめんどくさかったです。この場合はコード内でそのままUnicode絵文字が使えるので、emoji='?'というような形で指定することができます。しかし、これのA,B,C……はどうやって打てばよいでしょうか? Discord内ではregional_indicator_a:等で出力できますが、これはあくまでDiscord内の呼び名であり、例えば上のコードでemoji = discord.utils.get(guild.emojis, name='regional_indicator_a')とやってもAの絵文字は出てきません。コピペしても:regional_code_a:というテキストがコピーされて絵文字にはなりません。なんだこの制御。

 結論的にいうと、:regional_indicator_a:\でリテラル表示してそれをコピーして文字列を作りました。26文字……。それが??????????????????????????という文字列です。これがUnicode上でどういう位置づけになって、皆様のOS上でどのように表示されているかは分かりませんが、めんどくさかった……。

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

Windows10でGoogle Assistantを利用する方法

概要

WindowsでGoogle Assistantを利用したいと考えた時に情報が少なかったので、調べたことをまとめておきます。
ただし、2020年8月19日現在では、利用できる機能が限られているようです。詳しくは以下でまとめておきます。

実行環境

・python == 3.6
・windows10

利用できる機能

こちらから引用しました。
https://developers.google.com/assistant/sdk/overview

Supported architectures All gRPC platforms
Supported languages All gRPC languages
Hands-free activation No
Audio capture and playback Reference code is provided
Conversation state management Reference code is provided
Timers and alarms No
Playback of podcasts and news No
Broadcast voice messages No
Visual output (HTML5) of Assistant responses Yes

最新情報は、こちらのURLから確認できます。
https://developers.google.com/assistant/sdk/release-notes

環境構築

  1. Pythonの環境構築はここでは省きます。
  2. Google Cloud Platformを開いて、左上の"Create Project"をクリック。https://console.cloud.google.com/cloud-resource-manager
    001.png

  3. Project nameに好きな名前を入れます。(プロジェクトIDは変更も可能ですが、すべて小文字で半角数字で終わっていなければいけません。)
    002.png

  4. Google Assistant APIsを別タブで開きます。https://console.developers.google.com/apis/api/embeddedassistant.googleapis.com/overview
    003.png自分の作ったプロジェクトを選択し、Openをクリック。
    004.png

  5. 右上側の"CREATEC REDENTIALS"をクリック。
    005.png

  6. 画像の通りに選択欄を埋めます。そして、"What credentials do I need?"をクリック。その後、"SET UP CONCENT SCREAN"をクリック。
    006.png

  7. そして"External"を選択し"CREATE"
    007.png

  8. Projectnameを決めます。ただし、Googleなど有名な企業名が入っていると認められない場合があります。その後、一番下の"SAVE"をクリック。
    008.png

  9. 右側のカギマークの付いた"Credentials"に戻ります。上にある"+CREATE CREDENTIALS"から"Help me choose"を選択。
    004.png

  10. もう一度画像の通りに選択欄を埋めます。
    005.png

  11. そして"client id"の名前を決めます。そして"Create OAuth client ID"をクリック。
    006.png

  12. "Create ID"を入力し、"Download"を押します。(ここでダウンロードしたファイルは使いません。)
    010.png

  13. "Done"を押した後、自分のプロジェクト欄の一番右側にある矢印を押してjsonファイルをダウンロードします。
    011.png

  14. ここでダウンロードしたjsonファイルはCドライブ直下にフォルダーを作ってそこに入れます。
    007.png

  15. コマンドプロンプトから以下を入力し、Google Assistant SDKをインストール。

py -m pip install google-assistant-sdk[samples]

16 次に以下を入力し、インストール。

py -m pip install google-auth-oauthlib[tool]

17 以下のようにコマンドプロンプトに入力してください。

google-oauthlib-tool --client-secrets C:\先程のjsonファイルの場所\ --scope https://www.googleapis.com/auth/assistant-sdk-prototype --save --headless

18 表示されたURLをコピーして、ブラウザで検索。アカウントとの連携を認めるためにAllowを二回クリック。サインインに必よなコードが表示されるのでコピーします。
008.png
009.png

19 コマンドプロンプトに戻りコピーしたコードを貼り付けてEnterを入力。jsonファイルの情報がアカウントに保存されます。

20 以上の操作が完了していると、以下のコードで録音と再生のテストができます。

py -m googlesamples.assistant.grpc.audio_helpers

010.png

21 jsonファイルをがあるフォルダにコマンドプロンプト上で移動します。

22 そのフォルダ上で以下のコマンドを入力します。

googlesamples-assistant-devicetool --project-id 自分のプロジェクトID register-model --manufacturer “製造者名を自分で決めてください” --product-name “製品名を自分で決めてください” --type LIGHT --model “なんでもいいです。”

23 最後に以下のコマンドを入力します。

py -m googlesamples.assistant.grpc.pushtotalk --device-model-id “自分のモデルID” --project-id 自分のプロジェクトID

24 成功すれば、"Please Enter to send a new request..."と表示されます。Enter入力後、話しかけれると答えてくれます。(ただし、現時点では英語です。)

参考

https://www.lifewire.com/google-assistant-on-windows-4628292

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

Pythonで解く【初中級者が解くべき過去問精選 100 問】(001 - 004 全探索:全列挙)

1. 目的

初中級者が解くべき過去問精選 100 問をPythonで解きます。
すべて解き終わるころに水色になっていることが目標です。

本記事は「001 - 004 全探索:全列挙」になります。

2. 総括

Pythonでは組み合わせの列挙等はitertoolsでも行えるが、練習のためfor文で書いた。
特につまずくことはなかった。

3. 本編

001 - 004 全探索:全列挙

001. ITP1_7_B - How Many Ways?

image.png

回答

answer_list = []
while True:
    n, x = map(int, input().split())
    if n == 0 and x == 0:
        break

    count = 0
    for first in range(1, n + 1):
        for second in range(first + 1, n + 1):
            for third in range(second + 1, n + 1):
                if first + second + third == x:
                    count += 1

    answer_list.append(count)

for answer in answer_list:
    print(answer)

問題文は重複無しとあるので、for文のfirstsecondthirdの範囲が重複しないように気を付けます。
具体的にはsecondのスタートをfirst+1thirdのスタートをsecond+1にします。


002. AtCoder Beginner Contest 106 B - 105

image.png

回答

def is_target(num):
    count = 0
    for i in range(1, num+1):
        if num % i == 0:
            count += 1
    if count == 8:
        return True
    else:
        return False

if __name__ == "__main__":
    N = int(input())
    count = 0
    for num in range(1, N+1, 2):
        count += is_target(num)

    print(count)

約数が8であるか否かをTrueFalseで返すis_targetという関数を作成します。
そのあと1~Nまでの奇数についてis_targetでチェックして足し合わせます(Trueは1なのでそのまま足せる)。


003. AtCoder Beginner Contest 122 B - ATCoder

image.png

回答

target = 'ACGT'
S = input()

answer = 0
for start in range(len(S)):
    if S[start] not in target:
        continue
    count = 0
    for end in range(start, len(S)):
        if S[end] not in target:
            break
        count += 1

    answer = max(answer, count)

print(answer)

文字列Sから取り出す部分文字列は添え字startendを使ってS[start : end]と書けますので、startendそれぞれについて、ACGTであるか否かをチェックしていきます。

チェックの際は、startのfor文ではACGTじゃない場合はcontinueendのfor文ではACGTじゃない場合はbreakであることに注意します。


004. パ研杯2019 C - カラオケ

image.png

回答

N, M = map(int, input().split())
A = [list(map(int, input().split())) for _ in range(N)]

answer = 0
for song1 in range(M):
    for song2 in range(song1+1, M):
        score = 0
        for i in range(N):
            score += max(A[i][song1], A[i][song2])

        answer = max(answer, score)

print(answer)

song1song2についてfor文を回します。
そしてその内側で各生徒(添え字i)についてsong1song2の大きいほうの点数を採用します。

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

リスト処理関数(cons,car,cdr,atom,list)実装例まとめ

拙作記事『7行インタプリタ実装まとめ』について,そろそろSchemeとPython以外にも対応しないとなあと思っていろいろ整理した結果,『S式入力の実装部分がほとんどじゃないこれ?』→『あと,リスト処理内容に基準を設けてないと言語ごとに実装方針がバラバラになりそう』となり,とりあえず『cons car cdr atom listが使える』ようにする記述例を先にまとめていくことにした次第.ターゲット言語上での純LISP機能実装に近いとでもいいますか.

※Python版については,これを用いたS式入出力記述例も掲載しています(他の言語版も揃ったら別記事に移すかも).

仕様

  • ドット対(cons cells)を定義
  • アトムは全て文字列,空リストはNULL
  • cons car cdrを実装
  • アトムか否かを判定するatomを実装
  • 文字列の羅列から単方向リストを生成するlistを実装

Python(CPython 3.7.3)

ドット対はペア要素のタプルで定義(不変としたいため).空リストNULLはNoneを使用.

#### Cons cells are created by using tuple.
#### All of atoms are string and the null value is None.
def cons(x, y): return (x, y)
def car(s): return s[0]
def cdr(s): return s[1]
def atom(s): return isinstance(s, str) or s == None
def list(ts):
    if not ts:
        return None
    else:
        return cons(ts[0], list(ts[1:]))

利用例は次の通り.

def mkassoc(a, b):
    if a == None or b == None:
        return None
    else:
        return cons(cons(car(a), car(b)), mkassoc(cdr(a), cdr(b)))

def assoc(k, vs):
    if vs == None:
        return None
    else:
        if car(car(vs)) == k:
            return car(vs)
        else:
            return assoc(k, cdr(vs))
>>> vs = mkassoc(list(("hoge", "hage", "hige")), list(("10", "20", "30")))
>>> assoc("hage", vs)
('hage', '20')
>>> car(assoc("hage", vs))
'hage'
>>> cdr(assoc("hage", vs))
'20'

(上記定義を用いたS式入出力記述例)

#### Cons cells are created by using tuple.
#### All of atoms are string and the null value is None.
def cons(x, y): return (x, y)
def car(s): return s[0]
def cdr(s): return s[1]
def atom(s): return isinstance(s, str) or s == None
def list(ts):
    if not ts:
        return None
    else:
        return cons(ts[0], list(ts[1:]))

#### s_read

# '(a b c)'
# => ['(', 'a', 'b', 'c', ')']
def s_lex(s):
    return s.replace('(', ' ( ').replace(')', ' ) ').split()

# ['(', 'a', 'b', 'c', ')']
# => ('a', 'b', 'c')
def s_syn(s):
    t = s.pop(0)
    if t == '(':
        r = []
        while s[0] != ')':
            r.append(s_syn(s))
        s.pop(0)
        return tuple(r)
    else:
        if t == 'None':
            return None
        else:
            return t

# ('a', 'b', 'c')
# => ('a', ('b', ('c', None)))
def s_sem(s):
    if atom(s):
        return s
    elif len(s) == 0:
        return None
    elif s[0] == '.':
        return s_sem(s[1])
    else:
        return cons(s_sem(s[0]), s_sem(s[1:]))

def s_read(ss): return s_sem(s_syn(s_lex(ss)))

#### s_print

def s_prcons(s):
    sa_r = s_print(car(s))
    sd = cdr(s)
    if sd == None:
        return sa_r
    elif atom(sd):
        return sa_r + ' . ' + sd
    else:
        return sa_r + ' ' + s_prcons(sd)

def s_print(s):
    if atom(s):
        return s
    elif s == None:
        return None
    else:
        return '(' + s_prcons(s) + ')'
>>> s_print(s_read('((Apple . 100) (Orange . 120) (Lemmon . 250))'))
'((Apple . 100) (Orange . 120) (Lemmon . 250))'
>>> x = s_read('((Apple . 100) (Orange . 120) (Lemmon . 250))')
>>> car(x)
('Apple', '100')
>>> car(car(cdr(x)))
'Orange'
>>> s_print(cdr(x))
'((Orange . 120) (Lemmon . 250))'

C言語(gcc 8.3.0)

atom実装のため,まず,文字列とドット対ポインタの両方を扱うことができるnode_t構造体を定義,それを用いてドット対cons_t構造体を定義.空リストNULLはNULLポインタを使用.

#include <stdio.h>
#include <stdlib.h>

/* Cons cells are created by using typedef struct. */
/* All of atoms are char* and the null value is NULL. */

typedef unsigned int value_t;
enum NODE_TAG { NODE_STRG, NODE_CONS };

typedef struct _node_t_ {
  value_t value;
  enum NODE_TAG tag;
} _node_t, *node_t;

node_t node(value_t value, enum NODE_TAG tag)
{
  node_t n = (node_t)malloc(sizeof(_node_t));
  n->value = value; n->tag = tag;
  return (n);
}

typedef struct _cons_t_ {
  node_t x;
  node_t y;
} _cons_t, *cons_t;

node_t cons(node_t x, node_t y)
{
  cons_t c = (cons_t)malloc(sizeof(_cons_t));
  c->x = x; c->y = y;
  node_t n = node((value_t)c, NODE_CONS);
  return (n);
}

#define str_to_node(s)  (node((value_t)(s), NODE_STRG))
#define node_to_str(s)  ((char *)(s->value))

#define car(s)  (((cons_t)(s->value))->x)
#define cdr(s)  (((cons_t)(s->value))->y)
#define atom(s) (s->tag == NODE_STRG)

#define MAXSTR 64

node_t list(const char s[][MAXSTR], const int n)
{
  node_t r = str_to_node(NULL);
  for (int i = n - 1; i >= 0; i--) {
    r = cons(str_to_node(s[i]), r);
  }
  return (r);
}

利用例は次の通り.

#include <string.h>

node_t mkassoc(node_t a, node_t b)
{
  if (node_to_str(a) == NULL || node_to_str(b) == NULL) {
    return NULL;
  } else {
    return cons(cons(car(a), car(b)), mkassoc(cdr(a), cdr(b)));
  }
}

node_t assoc(node_t k, node_t vs)
{
  if (node_to_str(vs) == NULL) {
    return NULL;
  } else {
    if (strcmp(node_to_str(car(car(vs))), node_to_str(k)) == 0) {
      return car(vs);
    } else {
      return assoc(k, cdr(vs));
    }
  }
}

int main(void)
{
  const char s1[][MAXSTR] = { "hoge", "hage", "hige" };
  const char s2[][MAXSTR] = { "10", "20", "30" };
  node_t vs = mkassoc(list(s1, 3), list(s2, 3));

  node_t k = str_to_node("hage");
  node_t r = assoc(k, vs);
  printf("car(assoc(\"hage\", vs)) = %s\n", node_to_str(car(r)));
  printf("cdr(assoc(\"hage\", vs)) = %s\n", node_to_str(cdr(r)));

  free(vs);
  free(k);
  free(r);

  return (0);
}
car(assoc("hage", vs)) = hage
cdr(assoc("hage", vs)) = 20

Common Lisp(SBCL 1.4.16)

あくまで参考.ドット対はクロージャで実現.オリジナルと区別するため,s_cons s_car s_cdr s_atom s_listの名前で定義.空リストNULLはNILを使用.

;;;; Cons cells are created by using lambda closure.
;;;; All of atoms are string and the null value is NIL.
(defun s_cons (x y) (lambda (f) (funcall f x y)))
(defun s_car (c) (funcall c (lambda (x y) x)))
(defun s_cdr (c) (funcall c (lambda (x y) y)))
(defun s_atom (s) (and (not (functionp s)) (not (equal s NIL))))
(defun s_list (s) (if (null s) NIL (s_cons (car s) (s_list (cdr s)))))

利用例は次の通り.

(defun s_mkassoc (a b)
  (if (or (equal a NIL) (equal b NIL)) NIL
      (s_cons (s_cons  (s_car a) (s_car b))
              (s_mkassoc (s_cdr a) (s_cdr b)))))

(defun s_assoc (k vs)
  (if (equal vs NIL) NIL
      (if (equal (s_car (s_car vs)) k)
          (s_car vs)
          (s_assoc k (s_cdr vs)))))
* (defparameter vs
    (s_mkassoc (s_list '("hoge" "hage" "hige")) (s_list '("10" "20" "30"))))
VS
* (s_assoc "hage" vs)
#<CLOSURE (LAMBDA (F) :IN S_CONS) {50F3E645}>
* (s_car (s_assoc "hage" vs))
"hage"
* (s_cdr (s_assoc "hage" vs))
"20"

Ruby(CRuby 2.5.5)

ドット対は二要素の配列で定義.空リストNULLはnilを使用.

#### Cons cells are created by using Array.
#### All of atoms are string and the null value is nil.
def cons(x, y) [x, y] end
def car(s) s[0] end
def cdr(s) s[1] end
def atom(s) s.is_a?(String) || s == nil end
def list(s) s.size == 0 ? nil : cons(s[0], list(s[1..-1])) end

利用例は次の通り.

def mkassoc(a, b)
  if a == nil || b == nil then
    return nil
  else
    return cons(cons(car(a), car(b)), mkassoc(cdr(a), cdr(b)))
  end
end

def assoc(k, vs)
  if vs == nil then
    return nil
  else
    if car(car(vs)) == k then
      return car(vs)
    else
      return assoc(k, cdr(vs))
    end
  end
end
>> vs = mkassoc(list(["hoge", "hage", "hige"]), list(["10", "20", "30"]))
=> [["hoge", "10"], [["hage", "20"], [["hige", "30"], nil]]]
>> assoc("hage", vs)
=> ["hage", "20"]
>> car(assoc("hage", vs))
=> "hage"
>> cdr(assoc("hage", vs))
=> "20"

備考

記事に関する補足

  • 参照用を想定していることもあり,エラーチェックもモジュール化もガーベジコレクションもなにそれおいしいの状態.実用のS式パーサとかは既にたくさんあるしなあ.
  • 現バージョンのlistだと,Common Lisp版を含めて『リストのリスト』が作れない…cons使えばいっか(いいかげん).

変更履歴

  • 2020-08-30:利用例を連想リスト実装に統一
  • 2020-08-30:Rubyの実装例を追加
  • 2020-08-30:初版公開(Python,C,Common Lisp)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

最適化の前に3次元のグラフの書き方

DEAPにある最適化アルゴリズムのグラフの書き方です。

最適化の前に3次元のグラフの書き方です。

まず、どんな3次元のグラフが描きたいかと言うと、DEAPという遺伝的アルゴリズムのライブラリーにある評価関数です。
多目的最適化を検討したいので、どうしても3次元のグラフ描いた方がわかりやすそうなので、まずは3次元のグラフの書き方です。

最適化の評価関数はこの辺を参照してください。
DEAP 1.3.1ドキュメント
だれでも分かる多目的最適化問題超入門

三次元のグラフを描くためには、まず、X1、x2の2つの軸で、メッシュ状に点をつくる。
その点に、z軸の値を計算する。
x1、x2、Zの値をAxes3Dで3次元のグラフを描きます。

1)メッシュ状の点を作る。

1:numpyarange関数で、等間隔の数値を作る。
np.arange(-30,30,1) a-30から30まで、1ずつ間を空けた数値ができます。
2:meshgrid(a,b)で一次元配列のa,bの交点に格子点が得られ、それをX1,X2に入れます。

#メッシュの作り方
import numpy as np
a = np.arange(-30,30,1)
b = np.arange(-30,30,1)
print(a)
#[-30 -29 -28 -27 -26 -25 -24 -23 -22 -21 -20 -19 -18 -17 -16 -15 -14 -13
# -12 -11 -10  -9  -8  -7  -6  -5  -4  -3  -2  -1   0   1   2   3   4   5
#   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23
#  24  25  26  27  28  29]

print(b)
#[-30 -29 -28 -27 -26 -25 -24 -23 -22 -21 -20 -19 -18 -17 -16 -15 -14 -13
# -12 -11 -10  -9  -8  -7  -6  -5  -4  -3  -2  -1   0   1   2   3   4   5
#   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23
#  24  25  26  27  28  29]

X1, x2 = np.meshgrid(a, b)

print(X1)
#[[-30 -29 -28 ...  27  28  29]
# [-30 -29 -28 ...  27  28  29]
# [-30 -29 -28 ...  27  28  29]
# ...
# [-30 -29 -28 ...  27  28  29]
# [-30 -29 -28 ...  27  28  29]
# [-30 -29 -28 ...  27  28  29]]

print(X2)
#[[-30 -29 -28 ...  27  28  29]
# [-30 -29 -28 ...  27  28  29]
# [-30 -29 -28 ...  27  28  29]
# ...
# [-30 -29 -28 ...  27  28  29]
# [-30 -29 -28 ...  27  28  29]
# [-30 -29 -28 ...  27  28  29]]

import matplotlib.pyplot as plt
plt.scatter(X1,X2,s=1)
plt.show()

確認のために、X1,X2の2次元の散布図を描いてみると、次のようになりました。
scatter_Figure 2020-08-30 161126.png

ちゃんとメッシュになっています。
次にZ軸の値を計算して、3次元のグラフを作ります。
3:Zは、Ackley関数なるものを計算しました。
数式

{f(x_{1}, x_{2})=20-20\exp \biggl( -0.2\sqrt{\frac{1}{n}\sum_{i=1}^{n}x_{i}^2} \biggr) +e-\exp \biggl(\frac{1}{n}\sum_{i=1}^{n}\cos(2\pi x_{i}) \biggr)
}

とりあず、X1,Zでプロットすると、次のようなグラフになります。
x1_z_Figure 2020-08-30 162742.png

4:3次元のグラフを描きます。
from mpl_toolkits.mplot3d import Axes3Dで3次元のグラフを描くライブラリーをインポートします。
ax.plot_wireframe(X1, X2, Z)で、3次元のグラフになります。

plot_surface:面で表示されます。
surfaceFigure 2020-08-30 163457.png

plot_wireframe:ワイヤーフレームで表示されます。
wireflameFigure 2020-08-30 163108.png

Z =20 - 20 * np.exp(-0.2 * np.sqrt((X1 ** 2 + X2 ** 2) / 2)) + np.exp(1) - np.exp((np.cos(2 * np.pi * X1) + np.cos(2 * np.pi * X2)) / 2)

plt.scatter(X1,Z,s=1)
plt.show()


from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure()
ax = Axes3D(fig)

#ワイヤーフレームで表示
ax.plot_wireframe(X1, X2, Z)
#面で表示
#ax.plot_surface(X1, X2, Z, rstride=1, cstride=1, cmap='hsv')

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

AtCoderBeginnerContest177復習&まとめ(後半)

AtCoder ABC177

2020-08-29(土)に行われたAtCoderBeginnerContest177の問題をA問題から順に考察も踏まえてまとめたものとなります.
後半ではDEの問題を扱います.前半はこちら
問題は引用して記載していますが,詳しくはコンテストページの方で確認してください.
コンテストページはこちら
公式解説PDF

D問題 Friends

問題文
人$1$から人$N$までの$N$人の人がいます。
「人$A_i$と人$B_i$は友達である」という情報が$M$個与えられます。同じ情報が複数回与えられることもあります。
$X$と$Y$が友達、かつ、$Y$と$Z$が友達ならば、$X$と$Z$も友達です。また、$M$個の情報から導くことができない友達関係は存在しません。
悪の高橋君は、この$N$人をいくつかのグループに分け、全ての人について「同じグループの中に友達がいない」という状況を作ろうとしています。
最小でいくつのグループに分ければ良いでしょうか?

友達の繋がりをたどって,何人と繋がりがあるのか(=公式解説の友達集合の要素数)を,各まとまりごとに調べていく.
友達同士を同じグループに入れないようにするためには,少なくとも一番大きい友達集合の要素数のグループが必要となり,それが出力すべき答えとなる.
実装は,人$1$から人$N$がどこかのグループに属しているかどうか確認するlistを作成して管理することで,計算の重複をしないようにしている.

abc177d.py
n, m = map(int, input().split())
set_dict = {}
chech_list = [0] * (n + 1)
for i in range(m):
    a, b = map(int, input().split())
    if a in set_dict:
        set_dict[a].add(b)
    else:
        set_dict[a] = {b}
    if b in set_dict:
        set_dict[b].add(a)
    else:
        set_dict[b] = {a}
    chech_list[a] = 1
    chech_list[b] = 1
ans = 1
for i in range(1, n + 1):
    if chech_list[i] == 0:
        continue
    count = 0
    temp_set = set_dict[i]
    while len(temp_set) > 0:
        x = temp_set.pop()
        count += 1
        chech_list[x] = 0
        for y in set_dict[x]:
            if chech_list[y] == 1:
                temp_set.add(y)
    ans = max(count, ans)
print(ans)

E問題 Coprime

問題文
$N$個の整数があります。$i$番目の数は$A_i$です。
「全ての$1 \leq i < j \leq N$について、$GCD(A_i,A_j)=1$」が成り立つとき、{$A_i$}は pairwise coprime であるといいます。
{$A_i$}が pairwise coprime ではなく、かつ、$GCD(A_1,…,A_N)=1$であるとき、{$A_i$}は setwise coprime であるといいます。
{$A_i$}が pairwise coprime、setwise coprime、そのどちらでもない、のいずれであるか判定してください。
ただし$GCD(…)$は最大公約数を表します。

素因数分解を高速でする方法が思いつかなかった.
公式解説のエラトステネスの篩みて納得.

abc177e.py
def gcd(a,b):
    if b == 0:
        return a
    else:
        return gcd(b,a%b)
n = int(input())
a_list = list(map(int, input().split()))
a_list.sort()
ans2 = a_list[0]
for i in range(1, n):
    ans2 = gcd(ans2, a_list[i])
    if ans2 == 1:
        break
if ans2 != 1:
    print("not coprime")
else:
    flag = 1
    max_a = a_list[n - 1] + 1
    num_flag_list = [True] * max_a
    d_list = list(range(0, max_a))
    d_list[0] = 1
    num_flag_list[0] = num_flag_list[1] = False
    for i in range(2, int(max_a**0.5) + 1):
        if num_flag_list[i]:
            for j in range(i**2, max_a, i):
                if num_flag_list[j] == True:
                    num_flag_list[j] = False
                    d_list[j] = i
    p_set = set()
    for a in a_list:
        if a == 1:
            continue
        temp_p_set = set()
        while True:
            p = d_list[a]
            if p not in temp_p_set:
                if p in p_set:
                    flag = 0
                    break
            temp_p_set.add(p)
            p_set.add(p)
            a = a // p
            if a == 1:
                break
        if flag == 0:
            break
    if flag == 1:
        print("pairwise coprime")
    else:
        print("setwise coprime")

後半も最後まで読んでいただきありがとうございました.

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

AtCoderBeginnerContest177復習&まとめ(前半)

AtCoder ABC177

2020-08-29(土)に行われたAtCoderBeginnerContest177の問題をA問題から順に考察も踏まえてまとめたものとなります.
前半ではABCまでの問題を扱います.
問題は引用して記載していますが,詳しくはコンテストページの方で確認してください.
コンテストページはこちら
公式解説PDF

A問題 Don't be late

問題文
高橋君は青木君と待ち合わせをしています。
待ち合わせ場所は高橋君の家から$D$メートル離れた地点であり、待ち合わせの時刻は$T$分後です。
高橋君は今から家を出発し、分速$S$メートルで待ち合わせ場所までまっすぐ移動します。
待ち合わせに間に合うでしょうか?

abc177a.py
d, t, s  = map(int, input().split())
if d <= t * s:
    print("Yes")
else:
    print("No")

B問題 Substring

問題文
$2$つの文字列$S, T$が与えられます。
$T$が$S$の部分文字列となるように、$S$のいくつかの文字を書き換えます。
少なくとも何文字書き換える必要がありますか?
ただし、部分文字列とは連続する部分列のことを指します。例えば、'xxx' は 'yxxxy' の部分文字列ですが、'xxyxx' の部分文字列ではありません。

$S,T$は$1$文字以上$1000$文字以下なので,全部の可能性を時間内に計算することができます.
例えば,7文字の'abcdefg'と3文字の'efh'を考えると,'abc','bcd','cde','def','efg'の各々に対して,何文字書き換える必要があるか計算し,最小のものが答えとなります.

abc177b.py
s = input()
t = input()
min_count = 1000
for i in range(len(s) - len(t) + 1):
    count = 0
    for j in range(len(t)):
        if s[i+j] != t[j]:
            count += 1
    min_count = min(count, min_count)
    if min_count == 0:
        break
print(min_count)

C問題 Substring

問題文
$N$個の整数$A_1,…,A_N$が与えられます。
$1 \leq i < j \leq N$を満たす全ての組$(i,j)$についての$A_i×A_j$の和を$mod(10^9+7)$で求めてください。

求める和は,$A_1×(A_2+...+A_N) + A_2×(A_3+...+A_N) + ... + A_{N-1}×(A_N)$となるので,個別に全て計算するよりも,計算量を抑えることができる.

abc177c.py
n = int(input())
a_list = list(map(int, input().split()))
ans = 0
total = 0
mod = 10 ** 9 + 7
for i in range(n):
    total += a_list[i]
    if total >= mod:
        total = total % mod
for i in range(n - 1):
    total -= a_list[i]
    if total < 0:
        total += mod
    ans += a_list[i] * total
    if ans >= mod:
        ans = ans % mod
print(ans)

前半はここまでとなります.
最近は公式の解説がとても丁寧に記述してあったので,詳しい解法はそちらを参考にしてもらえたらと思います.
前半の最後まで読んでいただきありがとうございました.

後半はDE問題の解説となります.
後半に続く

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

グループごとの移動平均

テスト用データの作成

import pandas as pd
dt1={
    "label": ["A"  for x in range(1,100,1)]        ,
    "seq": [x for x in range(1,100,1)]   ,
    "val": [x for x in range(10,1000,10)]
    }
dt1=pd.DataFrame(dt1)


dt2={
    "label": ["B"  for x in range(1,100,1)]        ,
    "seq": [x for x in range(99,0,-1)]   ,
    "val": [x for x in range(10,1000,10)]
    }
dt2=pd.DataFrame(dt2)

dt3={
    "label": ["C"  for x in range(1,100,1)]        ,
    "seq": [x for x in range(1,100,1)]   ,
    "val": [x for x in range(10,1000,10)]
    }
dt3=pd.DataFrame(dt3)


dt=pd.concat([dt1 , dt2 , dt3 ])

#内容の表示
print("元データ")
print(dt[dt.label=="A"].head(10))
print(dt[dt.label=="B"].head(10))
print(dt[dt.label=="C"].head(10))
元データ
  label  seq  val
0     A    1   10
1     A    2   20
2     A    3   30
3     A    4   40
4     A    5   50
5     A    6   60
6     A    7   70
7     A    8   80
8     A    9   90
9     A   10  100
  label  seq  val
0     B   99   10
1     B   98   20
2     B   97   30
3     B   96   40
4     B   95   50
5     B   94   60
6     B   93   70
7     B   92   80
8     B   91   90
9     B   90  100
  label  seq  val
0     C    1   10
1     C    2   20
2     C    3   30
3     C    4   40
4     C    5   50
5     C    6   60
6     C    7   70
7     C    8   80
8     C    9   90
9     C   10  100

label別でseq順にソートされているときのvalの5回分の移動平均を求める

以下の命令が主要部分
groupby("label").rolling(5 ,on="seq")

dt2=dt.sort_values("seq").copy()
dt3=dt2.groupby("label").rolling(5 ,on="seq").mean().reset_index()


print("結果データ")
print(dt3[dt3.label=="A"].head(10))
print(dt3[dt3.label=="B"].head(10))
print(dt3[dt3.label=="C"].head(10))
結果データ
  label  level_1  seq   val
0     A        0    1   NaN
1     A        1    2   NaN
2     A        2    3   NaN
3     A        3    4   NaN
4     A        4    5  30.0
5     A        5    6  40.0
6     A        6    7  50.0
7     A        7    8  60.0
8     A        8    9  70.0
9     A        9   10  80.0
    label  level_1  seq    val
99      B       98    1    NaN
100     B       97    2    NaN
101     B       96    3    NaN
102     B       95    4    NaN
103     B       94    5  970.0
104     B       93    6  960.0
105     B       92    7  950.0
106     B       91    8  940.0
107     B       90    9  930.0
108     B       89   10  920.0
    label  level_1  seq   val
198     C        0    1   NaN
199     C        1    2   NaN
200     C        2    3   NaN
201     C        3    4   NaN
202     C        4    5  30.0
203     C        5    6  40.0
204     C        6    7  50.0
205     C        7    8  60.0
206     C        8    9  70.0
207     C        9   10  80.0

参考 pandasで窓関数を適用するrollingを使って移動平均などを算出

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