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

time-windowを使ってみた

はじめに

みなさま,はじめまして.
株式会社オプティマインドにて,GISエンジニアをしている@tkmbnです.
Qiitaへの初投稿です.

概要

時間枠(日時の組み合わせ,開始日時〜終了日時)のあれやこれをしてくれるライブラリtime-windowの使い方を解説します.
Screen Shot 2020-03-02 at 11.29.07.png

複雑な時間枠をスッキリまとめてくれたりします!←このライブラリを使って一番やりたいこと.

なぜ書いたか

Screen Shot 2020-03-01 at 23.09.29.png
DBから時間枠つきのデータを取得する際に,時間枠に重なりがあればまとめてほしいという依頼がありました.
どうにかこうにか自力で実装しようとしたんですが,混乱しだして,「なんか良いツールないかなぁ...」と探していたところ上記のライブラリに出会いました.
readmeとか読んでみたんですが,あまり詳細に書かれていないため,自分への覚書も含め,記事にしました.

まるまる解説!というわけではないので,あしからず...

本題

インストール方法

pip install time-window

具体的な使い方

最初の方は,readmeの日本語訳的なものと思ってください笑

TimeWindow オブジェクト

作り方

2020年03月01日12:00:00 から 2020年03月01日15:00:00 のTimeWindowを作りたいときは...

from datetime import datetime
from time_window import TimeWindow
t1 = datetime(2020, 3, 1, 12, 00, 00)
t2 = datetime(2020, 3, 1, 15, 00, 00)
tw1 = TimewWindow(t1, t2)
tw1

-> TimeWindow(datetime.datetime(2020, 3, 1, 12, 0), datetime.datetime(2020, 3, 1, 15, 0))

このとき,時間枠は,[t1, t2) になることに注意!

また,timedeltaでも作成可能です.

from datetime import datetime, timedelta
from time_window import TimeWindow
t1 = datetime(2020, 3, 1, 12, 00, 00)
delta = timedelta(hours=3)
tw2 = TimeWindow.from_timedelta(t1, delta)
tw2

-> TimeWindow(datetime.datetime(2020, 3, 1, 12, 0), datetime.datetime(2020, 3, 1, 15, 0))

同じ結果が得られました.

delta, middle

時間枠の差分を知りたいときは,

tw1.delta

-> datetime.timedelta(0, 10800)

時間枠の中間の時点を知りたいときは,

tw1.middle

-> datetime.datetime(2020, 3, 1, 13, 30)
overlaps
tw = TimeWindow(datetime(2020, 3, 1, 12, 0), datetime(2020, 3, 1, 15, 0))
tw2 = TimeWindow(datetime(2020, 3, 1, 13, 0), datetime(2020, 3, 1, 18, 0))
tw.overlaps(tw2)

-> True

重なっていれば,True, そうでなければ,False.

contains(readme未掲載)
tw = TimeWindow(datetime(2020, 3, 1, 12, 0), datetime(2020, 3, 1, 15, 0))
tw4 = TimeWindow(datetime(2020, 3, 1, 13, 0), datetime(2020, 3, 1, 14, 0))
tw.contains(tw4)

-> True

含まれていれば,True, そうでなければ,False.

contiguous
tw = TimeWindow(datetime(2020, 3, 1, 12, 0), datetime(2020, 3, 1, 15, 0))
tw3 = TimeWindow(datetime(2020, 3, 1, 15, 0), datetime(2020, 3, 1, 18, 0))
tw.contiguous(tw3)

-> [TimeWindow(datetime.datetime(2020, 3, 1, 12, 0), datetime.datetime(2020, 3, 1, 15, 0)), 
TimeWindow(datetime.datetime(2020, 3, 1, 15, 0), datetime.datetime(2020, 3, 1, 18, 0))]

接している場合は,TimeWindowのリストが,接していない場合は,Falseが返ってくる.

set型みたいな事もできる
積集合
tw = TimeWindow(datetime(2020, 3, 1, 12, 0), datetime(2020, 3, 1, 15, 0))
tw2 = TimeWindow(datetime(2020, 3, 1, 13, 0), datetime(2020, 3, 1, 18, 0))
tw.intersection(tw2)

-> TimeWindow(datetime.datetime(2020, 3, 1, 13, 0), datetime.datetime(2020, 3, 1, 15, 0))
和集合
tw.union(tw2)

-> TimeWindow(datetime.datetime(2020, 3, 1, 12, 0), datetime.datetime(2020, 3, 1, 18, 0))

TimeWindowsCollection オブジェクト (readme未掲載)

作り方
from datetime import datetime, timedelta
from time_window import TimeWindow, TimeWindowsCollection
tw = TimeWindow(datetime(2020, 3, 1, 13, 0), datetime(2020, 3, 1, 18, 0))
tw2 = TimeWindow(datetime(2020, 3, 1, 12, 0), datetime(2020, 3, 1, 15, 0))
twc = TimeWindowsCollection([tw, tw2])
twc

-> [TimeWindow(datetime.datetime(2020, 3, 1, 13, 0), datetime.datetime(2020, 3, 1, 18, 0)), 
TimeWindow(datetime.datetime(2020, 3, 1, 12, 0), datetime.datetime(2020, 3, 1, 15, 0))]

##開始時刻で並べ替えたい!
twc.time_windows_sorted_by_since

-> [TimeWindow(datetime.datetime(2020, 3, 1, 12, 0), datetime.datetime(2020, 3, 1, 15, 0)), 
TimeWindow(datetime.datetime(2020, 3, 1, 13, 0), datetime.datetime(2020, 3, 1, 18, 0))]
複数の時間枠をまとめたい

これで自分のやりたいことは,完璧にできました.

tw = TimeWindow(datetime(2020, 3, 1, 13, 0), datetime(2020, 3, 1, 18, 0))
tw2 = TimeWindow(datetime(2020, 3, 1, 12, 0), datetime(2020, 3, 1, 15, 0))
tw3 = TimeWindow(datetime(2020, 3, 1, 19, 0), datetime(2020, 3, 1, 21, 0))
twc = TimeWindowsCollection([tw, tw2, tw3])
twc

-> [TimeWindow(datetime.datetime(2020, 3, 1, 13, 0), datetime.datetime(2020, 3, 1, 18, 0)), 
TimeWindow(datetime.datetime(2020, 3, 1, 12, 0), datetime.datetime(2020, 3, 1, 15, 0)), 
TimeWindow(datetime.datetime(2020, 3, 1, 19, 0), datetime.datetime(2020, 3, 1, 21, 0))]


twc.compressed()

-> [TimeWindow(datetime.datetime(2020, 3, 1, 12, 0), datetime.datetime(2020, 3, 1, 18, 0)), 
TimeWindow(datetime.datetime(2020, 3, 1, 19, 0), datetime.datetime(2020, 3, 1, 21, 0))]

重なり合っている時間枠はまとまり,重なっていないものは別の時間枠となっています.

Screen Shot 2020-03-01 at 23.41.07.png

しかし,落とし穴がありました.
普通にインデックス指定して,TimeWindowを取り出そうとしたところ,

twc.compressed()[0]   

-> Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'TimeWindowsCollection' object does not support indexing

返ってくる形見ると,普通のリストなんですが,どうやら違うみたいです...
TimeWindowsCollectionオブジェクトをTimeWindowのリストに戻してやる必要があるので,

twc.compressed().time_windows

-> [TimeWindow(datetime.datetime(2020, 3, 1, 12, 0), datetime.datetime(2020, 3, 1, 18, 0)), 
TimeWindow(datetime.datetime(2020, 3, 1, 19, 0), datetime.datetime(2020, 3, 1, 21, 0))]

twc.compressed().time_windows[0]  
TimeWindow(datetime.datetime(2020, 3, 1, 12, 0), datetime.datetime(2020, 3, 1, 18, 0))

無事取り出せることができました!!

参考文献

最後に

最後まで読んでいただきありがとうございます!
なにか疑問点,間違っている点,等ございましたら,コメントお願いいたします.

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

プログラミングスクール卒業後3ヶ月たった所感

2019年10月に最近youtubeでよく広告を見る某プログラミングスクールに通い、11月より機械学習エンジニア・データサイエンティストとして現職につきました。
卒業後3ヶ月経った今、スクールで学んだことで役立ったこと、今大変なことなどをつらつらと書いていきたいと思います。

経歴

2015年4月 食品会社の研究・開発職として4年間務める
2019年7月 プログラミングスクール学習開始
2019年10月 プログラミングスクール卒業
2019年11月 機械学習エンジニア・データサイエンティストとして転職

スキルセット

言語の使用状況

スクール 転職後
HTML
CSS
JavaScript
SQL
Linux
Ruby -
Python -

フレームワークの使用状況

スクール 転職後
SCSS
jQuery
Vue -
Ruby on Rails -
Flask -
Django -

インフラ等

スクール 転職後
Git/Github
Nginx -
EC2(AWS)
S3(AWS)

スクールについて

役立ったこと

  • HTML/CSS/JavaScript/Linuxについて基礎が学べ、今でもシステムを作成する際に役立っている
  • Ruby→Pythonに言語が変わったがどちらも動的型付けなため、基礎的な文法を学ぶことへの学習コストはかからなかった
  • EC2およびS3の作成・運用の基礎が学べ、現職ではシステムのデプロイは全てAWSを用いているため、すんなり業務に入ることができた
  • 現職では頻繁には使用しないが、Gitを利用した複数人の開発の経験は業務の随所で役立つ
  • メンターへの質問シートの書き方を守ることで、先輩エンジニアなどに質問する際に、何が問題で・どこで詰まっているのかが明確に伝えられ、回答を頂きやすい

役立たなかったこと

基本的には学んだことはある程度役立つと思いますが、下記点は仕方ないと考えていました。

  • スクールでRubyを学んだが、現職では全く使用しないこと(機械学習案件を志望していたため、仕方ないと割り切ってはいました)

転職前にやるべきだったこと

  1. 簡単でもいいので、新しい職場で使用するであろう言語で簡易的なシステムやアプリをデプロイまで持っていく
理由

コードの書き方や、フレームワークを予め触っておいて、素早く職場に馴染めるようにもっと努力をすべきと思いました。

  1. AWSのサービスをより理解しておく
理由

最近のサービスのデプロイのほとんどはAWSを利用していると思います。無料枠内で学ぶのもいいですが、実際には有料で数万円数十万円をかけてシステムを運用します。そのため少しの金額でも身銭を切って、ドメインの取り方を学んだり、アクセスの分散方法を学ぶなど、AWSというサービスを少しでも多く理解しておいた方がよかったと思いました。

  1. 綺麗なコードを書く意識をつける・練習する
理由

シンプルにレビュアーが大変なのと、あとで改修する際に自分でもわからなくなってしまうので、綺麗なコードを意識することは非常に大事です。(作者はめっちゃ冗長に書きすぎて、最初はレビューの時間が非常にかかってしまいました)

今大変なこと

  1. 想像以上にJavaScriptを多用するため、様々な機能を覚える必要がある
  2. 基本的に少人数でシステムを作成したり、データ分析を行うため分からない部分が多く、仕事が止まりやすい

他にもいろいろ大変なこともあるのですが、結論としては悩んだらわかりそうな人に聞く!!これが仕事をうまく進める方法だと思います。私自身人に聞くということが苦手で、どうしても1人でやろうとしがちだったのでした。しかし、先輩エンジニアに聞いたら数分で解決してくれたり、アドバイスしてくれるため、1時間もかけて調べるよりも、わかる人に聞いた方が100%早いです!!

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

[整理用]Python開発環境

整理がてら自分のPython開発環境のメモ

用途

  • Deep learning / 画像処理の開発
  • リモートセンシング

方針

  • なるべく楽に導入
  • 管理は最低限出来れば良い
  • コードを楽に書ける
    • 補完やフォーマットなど

OS: Ubuntu 18.04

  • ライブラリなどの導入がWindowsに比べて楽
  • 最近はそうでもないけど、Ubuntuじゃないと動かないものもあったので

環境管理: Anaconda

  • 機械学習で必要なのが一通りインストールされる + 最低限の環境管理が出来るため
  • 主なライブラリは以下
    • numpyとかAnacondaについてくるのは省く
    • geopandasはないとやってられない
# Deep learning用
pytorch
torchvision
tensorboard

# 画像処理用
opencv

# リモートセンシング用
gdal
qgis
geopandas

エディタ: VSCode

  • Extensionの充実
    • Remote Development
      • リモートサーバ上(GPUサーバ)で開発しているため
      • Extensionがリモート先でも使えるのがよい
    • ms-python
      • Microsoft公式Extension
      • イライラしない程度に補完してくれる
    • Bracket Pair Colorizer
      • 対応する括弧の色付け
  • LinterやFormatterの設定が楽
  • notebookファイル編集が出来る

Linter: Flake8

  • 厳しすぎず緩すぎずといった感じ
  • max-line-length = 120, max-complexity = 10で使用

Formatter: yapf

  • Format後の感じが一番しっくり来たので
  • Flake8と同じくcolumn_limit = 120で使用

おわりに

  • albumentationsとか便利系も今後は入れていきたい
  • 他に良さげなものがあれば教えていただけるとありがたいです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

elasticsearch_dsl 覚書

環境

Python3.8.2
elasticsearch-dsl 7.1

Elasticsearch側は面倒だったので、Python上でクエリのみ確認。

A, Q

test = A("terms", field="hoge")
print(test.to_dict())
print(type(test))

test2 = Q("terms", field="huge")
print(test2.to_dict())
print(type(test2))
{'terms': {'field': 'hoge'}}
<class 'elasticsearch_dsl.aggs.Terms'>
{'terms': {'field': 'huge'}}
<class 'elasticsearch_dsl.query.Terms'>

Agg Array

array_test = [A("terms", field="key_" +str(i)) for i in range(5)]

search = Search()
for i in range(5):
    search.aggs.bucket("array_" + str(i), array_test[i])

print(search.to_dict())
{'aggs': {
  'array_0': {'terms': {'field': 'key_0'}}, 
  'array_1': {'terms': {'field': 'key_1'}}, 
  'array_2': {'terms': {'field': 'key_2'}}, 
  'array_3': {'terms': {'field': 'key_3'}}, 
  'array_4': {'terms': {'field': 'key_4'}}
}}

Aggs Array ネストをつけたい

array_test = [A("terms", field="key_" +str(i)) for i in range(5)]

search = Search()
X = search.aggs.bucket("array_0", array_test[0])
for i in range(1, 5):
    X = X.bucket("array_" + str(i), array_test[i])

print(search.to_dict())
{'aggs': {'array_0': {
  'terms': {'field': 'key_0'},
  'aggs': {'array_1': {
    'terms': {'field': 'key_1'},
    'aggs': {'array_2': {
      'terms': {'field': 'key_2'},
      'aggs': {'array_3': {
        'terms': {'field': 'key_3'},
        'aggs': {'array_4': {
          'terms': {'field': 'key_4'}
        }}
      }}
    }}
  }}
}}}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python Windows実行ファイル実行時に陥った罠

PythonのWindows実行ファイル作成時に陥った罠

Pythonを用いてWindowsの実行ファイルを作成した際に、コマンドライン引数を用いて処理を行たかったが、その際に陥った罠について備忘として記録する。

コマンドライン引数

コマンドライン引数を用いたhelloworld

hello.py
import sys

str1 = sys.argv[1]

print(str1)

python test.py helloworld
>>> helloworld

pyinstallerを使って実行ファイル化

pyinstaller hello.py --onefile

hello.exeの実行

IndexError: list index out of range
原因は、コマンドラインを使用しないため、リストが作成できなかった。

解決方法

Input関数を使用

hello.py
str1 = input("please put something")

print(str1)

hello.pyの実行

>>>please put something
helloworld

 結論

試行錯誤した結果、sysを用いてコマンドライン引数を取得することができなかったため、Input関数を使用することとした結果、うまく行った。
もし、Windowsの実行ファイルにて、コマンドライン引数を使用する方法をご存知の方がいらっしゃいましたら、是非ご教示いただければと存じます。

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

pythonでのcsvファイル読み書きの際の文字コード ~windows環境ver~

本稿の投稿経緯

python初心者の私が、csvファイルの読み書きの際のエンコードにたまにエラーに躓くので、その内容のまとめをメモしたもの。同じく初心者向けの記事になります。なお、環境はwindows環境になります。

エラー

csvファイルの読み書きの際に、よく引っかかるエラーについて

書き込み時のエラー

エラー内容
UnicodeEncodeError: 'shift_jis' codec can't encode character '\u9ad9' in position 14: illegal multibyte sequence

shift-jisでエンコードできない文字があるよってことですね。ファイル書き込みの際にファイルの文字コードと書き込み文字の文字コードの不一致で発生します。

ちなみにコードの指定箇所はここなど。

コード例
with open(filepath, 'w', newline='', encoding='shift-jis') as f

読み込み時のエラー

エラー内容
UnicodeDecodeError: 'shift_jis' codec can't decode byte 0xee in position 0

shift-jisででコードできない文字があるよってことですね。ファイル読み込みの際にファイルの文字コードとファイルの読み込みで指定した文字コードの不一致で発生します。(もしくは、ファイルの読み込み時に指定した文字コードで読めない文字がファイルに書き込まれている。)

ちなみにコードの指定箇所はここなど。

コード例
data = pd.read_csv(filepath, encoding = 'shift-jis')

正しい文字コードの指定は?

ファイル作成、書き込み、読み込みの一連の動作をpython上でする場合は、以下の横軸通りに指定しておけば、エラーは発生しないはずです。
(ファイルの文字コードの意味は、書き込み時に指定した文字コードによって作成されるcsvの文字コードを表しています)

書き込み時の文字コード     ファイルの文字コード     読み込み時の文字コード     
UTF-8 UTF-8 UTF-8
cp932 ansi cp932
shift-jis ansi shift-jis

cp932もshift-jisもファイルだとansiだけどどっちつかうの?
cp932とshift-jisの違いは、例えば髙(はしごだか)﨑(たてさき)といった、環境依存文字が取り扱いできるかどうかの違いが一番の差かと思います。できるのがcp932になります。なので、例えば、他システムからansiのcsvファイルが連携されてくる時は、shift-jisではなく、cp392で取り込む想定にしておくほうが吉ということです。

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

AtCoder Beginner Contest 157 参戦記

AtCoder Beginner Contest 157 参戦記

ABC157A - Duplex Printing

1分で突破. 書くだけ.

N = int(input())

print((N + 1) // 2)

ABC157B - Bingo

7分半で突破. 書くだけ……ではあるけど、エレガントに書こうと思えばどう書けばいいんですかね……. 特にビンゴ判定のところ.

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

for _ in range(N):
    b = int(input())
    for y in range(3):
        for x in range(3):
            if A[y][x] == b:
                A[y][x] = -1

for y in range(3):
    f = True
    for x in range(3):
        if A[y][x] != -1:
            f = False
    if f:
        print('Yes')
        exit()

for x in range(3):
    f = True
    for y in range(3):
        if A[y][x] != -1:
            f = False
    if f:
        print('Yes')
        exit()

f = True
for x in range(3):
    if A[x][x] != -1:
        f = False
if f:
    print('Yes')
    exit()

f = True
for x in range(3):
    if A[2 - x][x] != -1:
        f = False
if f:
    print('Yes')
    exit()

print('No')

ABC157C - Guess The Number

20分で突破. WA4. やー、これは酷い. N = 1 のときは先頭桁が0を取れることを見落としてボッコボコになった. それを除けば解くのは難しくなく、配列で決まった桁を管理し、決まらなかった桁には最小になる数字を割り付ければいいだけである.

N, M = map(int, input().split())

t = [-1] * N
for _ in range(M):
    s, c = map(int, input().split())
    s -= 1
    if t[s] != -1 and t[s] != c:
        print(-1)
        exit()
    t[s] = c

if N != 1:
    if t[0] == 0:
        print(-1)
        exit()

    if t[0] == -1:
        t[0] = 1

    for i in range(1, N):
        if t[i] == -1:
            t[i] = 0
else:
    if t[0] == -1:
        t[0] = 0

print(''.join(map(str, t)))

ABC157D - Friend Suggestions

25分くらいで突破? Eに寄り道したので正確には分からず. 友達候補は友達の友達クラスタの大きさから以下を引いたもの.

  • 自分の数(= 1)
  • 友達の数
  • 友達の友達クラスタにいるブロックした人の数

友達の友達クラスタの大きさはサイズ付き UnionFind で簡単に求まる.

from sys import setrecursionlimit


def find(parent, i):
    t = parent[i]
    if t < 0:
        return i
    t = find(parent, t)
    parent[i] = t
    return t


def unite(parent, i, j):
    i = find(parent, i)
    j = find(parent, j)
    if i == j:
        return
    parent[j] += parent[i]
    parent[i] = j


setrecursionlimit(10 ** 5)


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

parent = [-1] * N
friends = [[] for _ in range(N)]
for A, B in AB:
    unite(parent, A - 1, B - 1)
    friends[A-1].append(B-1)
    friends[B-1].append(A-1)
blocks = [[] for _ in range(N)]
for C, D in CD:
    blocks[C-1].append(D-1)
    blocks[D-1].append(C-1)

result = []
for i in range(N):
    p = find(parent, i)
    t = -parent[p] - 1
    t -= len(friends[i])
    for b in blocks[i]:
        if p == find(parent, b):
            t -= 1
    result.append(t)
print(*result)

ABC157E - Simple String Queries

敗退. セグ木とsetで行けるかなあと思ったけど TLE. 遅延セグ木で行けるかなあと思って、ちゃんと調べてる暇がなかったので Dirty 管理すればいいのかなと適当に書いたけど、性能改善しても TLE 消えず. PyPy は更に性能改善してくれたけどやっぱり足りず.

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

【Udemy Python3入門+応用】 19.リストのコピー

※この記事はUdemyの
現役シリコンバレーエンジニアが教えるPython3入門+応用+アメリカのシリコンバレー流コードスタイル
の講座を受講した上での、自分用の授業ノートです。
講師の酒井潤さんから許可をいただいた上で公開しています。

■リストを代入した場合の挙動

list_copy
x = [1, 2, 3, 4, 5]
y = x
y[0] = 100
print('x = ', x)
print('y = ', y)
result
x =  [100, 2, 3, 4, 5]
y =  [100, 2, 3, 4, 5]

xをいじっただけなのに、yにまで変更が及んでしまう。

list_copy
x = [1, 2, 3, 4, 5]
y = x.copy()
y[0] = 100
print('x = ', x)
print('y = ', y)
result
x =  [1, 2, 3, 4, 5]
y =  [100, 2, 3, 4, 5]

xの代わりにx.copy()とすることで、
xそのもの」ではなく「xのコピー」をyに代入することができる。


■なぜ影響が及んだのか

◆別のid
id_different
X = 20
Y = X
Y = 5
print('X = ', X)
print('Y = ', Y)
print('id of X =', id(X))
print('id of Y =', id(Y))
result
X =  20
Y =  5
id of X = 4364961520
id of Y = 4364961040

リストではなく、数値で同様にしてみると、Yを書き換えてもXには影響が出ない。
id()を使って、XYのidをみてみると、それぞれ別のidとなっていることが確認できる。

◆同じid
id_same
X = ['a', 'b']
Y = X
Y[0] = 'p'
print('X = ', X)
print('Y = ', Y)
print('id of X =', id(X))
print('id of Y =', id(Y))
result
X =  ['p', 'b']
Y =  ['p', 'b']
id of X = 4450177504
id of Y = 4450177504

リストではYの書き換えにより、Xにも影響が出ている。
この場合idをみてみると、XYも同じidをさしていることがわかる。

id_same
X = ['a', 'b']
Y = X.copy()
Y[0] = 'p'
print('X = ', X)
print('Y = ', Y)
print('id of X =', id(X))
print('id of Y =', id(Y))
result
X =  ['a', 'b']
Y =  ['p', 'b']
id of X = 4359291360
id of Y = 4359293920

.copy()により回避した場合は、
XYのidが異なっていることがわかる。

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

【初心者】Python、FlaskとHerokuで、ニャンコつぶやきフォームを作った。

FlaskとHerokuを使って、簡単な投稿フォームを作った。

(1)アウトプットイメージ

以下のような簡単な投稿フォームを作成することをゴールとした。
スクリーンショット 2020-03-01 21.23.24.png
つぶやく内容に”にゃー にゃー”、つぶやいたネコに”たま”を入力し、送信ボタンを押すと、
スクリーンショット 2020-03-01 21.25.01.png
こんな感じで投稿される。

(2)仮想環境を設定

desktopに、my_formというディレクトリを作成、ディレクトリに移動して仮想環境を設定。仮想環境を起動する。

python3 -m venv .
source bin/activate

(3)必要なフレームワークと、WEBサーバーをインストール

flaskとgunicornをインストール。

pip install flask
pip install gnicorn

(4)必要なディレクトリとファイル(.py .html)を準備

my_formディレクトリ内にform.pyを作成、
また、my_formディレクトリ内にtemplatesディレクトリと(templatesディレクトリ内に)index.html、layout.htmlを作成する。

(4)form.pyを記述

以下を記述する。

form.py
from flask import Flask,request,render_template
app = Flask(__name__)


@app.route("/")
def show():
    return render_template("index.html")


@app.route("/result",methods=["POST"])
def result():
    article = request.form["article"]
    name = request.form["name"]
    return render_template("index.html",article=article,name=name)

render_templateをインポートすることで、Jinja2を使う環境を整えた。Jinja2テンプレートエンジンにより、htmlのテンプレートを呼び出す(ここでは、index.html)。

まず、@app.route("/")の処理であるが、Jinja2でindex.htmlを呼び出す。
具体的には、

return render_template("index.html" 〜)

とする。

次に、@app.route("/result",method=["POST"])の処理であるが、reuestをインポートすることで、呼び出したhtml側でフォームに入力したデータを取り出すことできる。
例えば、articleについては、article = request.form["article"]とすることで、index.html側でフォームに入力した内容を(index.html側のフォームname属性は、"article"を設定したものと想定)取り出すことができる。
なお、methodsは、index.html側のフォームをPOSTメソッドで今回は送信するので、[POST]とした。GETメソッドによる送信は後述する。

article = request.form["article"]

取り出したarticleと、nameの内容を再度return render_templateとして、index.htmlに返す。その際に、引数articleと、nameのそれぞれに値を代入。

return render_template("index.html",article=article,name=name)

methodsはPOSTとする

@app.route("/result",methods=["POST"])

(5)layout.htmlを記述

テンプレート部分が増えても共通部分のメンテナンスが容易になるよう共通のhtmlを作成しておく。

layout.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>Nyanco Form</title>
        <style>body {padding: 10px;}</style>
    </head>
    <body>
        {% block content %}
        {% endblock %}
    </body>
</html>

以下の部分に個別の内容(index.html)をはめ込むことができる。

{% block content %}

(6)index.htmlを記述

index.html
{% extends "layout.html" %}
{% block content %}
<h1> ニャンコつぶやきフォーム </h1>

<form action="/result" method="post">
      <label for="article">つぶやく内容</label>
      <input type="text" name="article">

      <p></p>

      <label for="name">つぶやいたネコ</label>
      <input type="text" name="name">
      <button type="submit">送信</button>
</form>

<p></p>

<p>つぶやき内容/{{ article }}</p>
<p>つぶやいたネコ/{{ name }}</p>
{% endblock %}

layout.htmlの部分位に{% block content %}から、{% endblock %}までの内容をはめ込む処理をしている。
inputのname属性は"article"とした。

 <input type="text" name="article">

送信先アドレスは、/result、フォームに入力した内容はPOSTメソッドで送信する。

<form action="/result" method="post">

(7)ローカル環境で起動してみる

以下のようにflaskを起動してみる。

 FLASK_APP=form.py flask run
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

と出るので、ブラウザに http://〜の部分を貼り付けると、

スクリーンショット 2020-03-01 22.14.45.png
と出ていれば成功。

(8)POSTメソッド、GETメソッドの違いについて

なお、今回はPOSTメソッドを使用したが、GETメソッドの場合も備忘しておく。
POSTメソッドの場合、フォームを送信すると、
スクリーンショット 2020-03-01 22.18.21.png
となり、ブラウザのアドレスを見ると、
スクリーンショット 2020-03-01 22.18.10.png
となっている。
一方で、GETメソッドによる方法をとるのであれば、form.pyと、index.htmlを以下のように修正し、

form.py
from flask import Flask,request,render_template
app = Flask(__name__)


@app.route("/")
def show():
    return render_template("index.html")


@app.route("/result",methods=["GET"])
def result():
    article = request.args.get("article")
    name = request.args.get("name")
    return render_template("index.html",article=article,name=name)
index.html
{% extends "layout.html" %}
{% block content %}
<h1> ニャンコつぶやきフォーム </h1>

<form action="/result" method="get">
      <label for="article">つぶやく内容</label>
      <input type="text" name="article">

      <p></p>

      <label for="name">つぶやいたネコ</label>
      <input type="text" name="name">
      <button type="submit">送信</button>
</form>

<p></p>

<p>つぶやき内容/{{ article }}</p>
<p>つぶやいたネコ/{{ name }}</p>
{% endblock %}

送信してブラウザのアドレスを見ると、
スクリーンショット 2020-03-01 22.24.51.png
GETメソッドでは、送信するデータをアドレスの末尾に付け加える仕様となっている。したがって、どんなデータか他の人に見られてしまうので、例えば検索等のアプリに使われる。パスワード等を入力するようなフォームには使わないようにする必要がある。

(9)Herokuにデプロイ

Herokuへのデプロイ詳細は以下の記事に書いた通りなので、エッセンスのみとし、詳細説明を省く。
https://qiita.com/nooonchi/items/a7ea7c7a12c56ab8e901

Herokuにログインし、Heroku上にアプリを作成

heroku login

アプリ名はcat-formとした。

Heroku create cat-form

ディレクトリmy_formを初期化して、

git init

Herokuとローカル環境を紐つけて、

heroku git:remote -a cat-form

ディレクトリmy_formにrequirements.txtを作成して、

pip freeze > requirements.txt

ディレクトリmy_formにProckfileを作成し、以下を入力。
この時、gの前はブランク一つ必要、また、:appの前のformは、form.pyのformという意味なので注意が必要。

web: gunicorn form:app --log-file -

addして、

git add .

今回は、the-firstという名前でcommitして、

git commit -m'the-first'

Herokuにpushする。

git push heroku master

最後に、

heroku open

すれば完成。
スクリーンショット 2020-03-01 21.23.24.png

(10)おわりに

今後は、これを発展させ、掲示板にチャレンジしたい。

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

【初心者】Python、FlaskとHerokuで、ニャンコつぶやきフォームを作った

FlaskとHerokuを使って、簡単な投稿フォームを作った。

(1)アウトプットイメージ

以下のような簡単な投稿フォームを作成することをゴールとした。
スクリーンショット 2020-03-01 21.23.24.png
つぶやく内容に”にゃー にゃー”、つぶやいたネコに”たま”を入力し、送信ボタンを押すと、
スクリーンショット 2020-03-01 21.25.01.png
こんな感じで投稿される。

(2)仮想環境を設定

desktopに、my_formというディレクトリを作成、ディレクトリに移動して仮想環境を設定。仮想環境を起動する。

python3 -m venv .
source bin/activate

(3)必要なフレームワークと、WEBサーバーをインストール

flaskとgunicornをインストール。

pip install flask
pip install gnicorn

(4)必要なディレクトリとファイル(.py .html)を準備

my_formディレクトリ内にform.pyを作成、
また、my_formディレクトリ内にtemplatesディレクトリと(templatesディレクトリ内に)index.html、layout.htmlを作成する。

(4)form.pyを記述

以下を記述する。

form.py
from flask import Flask,request,render_template
app = Flask(__name__)


@app.route("/")
def show():
    return render_template("index.html")


@app.route("/result",methods=["POST"])
def result():
    article = request.form["article"]
    name = request.form["name"]
    return render_template("index.html",article=article,name=name)

render_templateをインポートすることで、Jinja2を使う環境を整えた。Jinja2テンプレートエンジンにより、htmlのテンプレートを呼び出す(ここでは、index.html)。

まず、@app.route("/")の処理であるが、Jinja2でindex.htmlを呼び出す。
具体的には、

return render_template("index.html" 〜)

とする。

次に、@app.route("/result",method=["POST"])の処理であるが、reuestをインポートすることで、呼び出したhtml側でフォームに入力したデータを取り出すことできる。
例えば、articleについては、article = request.form["article"]とすることで、index.html側でフォームに入力した内容を(index.html側のフォームname属性は、"article"を設定したものと想定)取り出すことができる。
なお、methodsは、index.html側のフォームをPOSTメソッドで今回は送信するので、[POST]とした。GETメソッドによる送信は後述する。

article = request.form["article"]

取り出したarticleと、nameの内容を再度return render_templateとして、index.htmlに返す。その際に、引数articleと、nameのそれぞれに値を代入。

return render_template("index.html",article=article,name=name)

methodsはPOSTとする

@app.route("/result",methods=["POST"])

(5)layout.htmlを記述

テンプレート部分が増えても共通部分のメンテナンスが容易になるよう共通のhtmlを作成しておく。

layout.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>Nyanco Form</title>
        <style>body {padding: 10px;}</style>
    </head>
    <body>
        {% block content %}
        {% endblock %}
    </body>
</html>

以下の部分に個別の内容(index.html)をはめ込むことができる。

{% block content %}

(6)index.htmlを記述

index.html
{% extends "layout.html" %}
{% block content %}
<h1> ニャンコつぶやきフォーム </h1>

<form action="/result" method="post">
      <label for="article">つぶやく内容</label>
      <input type="text" name="article">

      <p></p>

      <label for="name">つぶやいたネコ</label>
      <input type="text" name="name">
      <button type="submit">送信</button>
</form>

<p></p>

<p>つぶやき内容/{{ article }}</p>
<p>つぶやいたネコ/{{ name }}</p>
{% endblock %}

layout.htmlの部分位に{% block content %}から、{% endblock %}までの内容をはめ込む処理をしている。
inputのname属性は"article"とした。

 <input type="text" name="article">

送信先アドレスは、/result、フォームに入力した内容はPOSTメソッドで送信する。

<form action="/result" method="post">

(7)ローカル環境で起動してみる

以下のようにflaskを起動してみる。

 FLASK_APP=form.py flask run
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

と出るので、ブラウザに http://〜の部分を貼り付けると、

スクリーンショット 2020-03-01 22.14.45.png
と出ていれば成功。

(8)POSTメソッド、GETメソッドの違いについて

なお、今回はPOSTメソッドを使用したが、GETメソッドの場合も備忘しておく。
POSTメソッドの場合、フォームを送信すると、
スクリーンショット 2020-03-01 22.18.21.png
となり、ブラウザのアドレスを見ると、
スクリーンショット 2020-03-01 22.18.10.png
となっている。
一方で、GETメソッドによる方法をとるのであれば、form.pyと、index.htmlを以下のように修正し、

form.py
from flask import Flask,request,render_template
app = Flask(__name__)


@app.route("/")
def show():
    return render_template("index.html")


@app.route("/result",methods=["GET"])
def result():
    article = request.args.get("article")
    name = request.args.get("name")
    return render_template("index.html",article=article,name=name)
index.html
{% extends "layout.html" %}
{% block content %}
<h1> ニャンコつぶやきフォーム </h1>

<form action="/result" method="get">
      <label for="article">つぶやく内容</label>
      <input type="text" name="article">

      <p></p>

      <label for="name">つぶやいたネコ</label>
      <input type="text" name="name">
      <button type="submit">送信</button>
</form>

<p></p>

<p>つぶやき内容/{{ article }}</p>
<p>つぶやいたネコ/{{ name }}</p>
{% endblock %}

送信してブラウザのアドレスを見ると、
スクリーンショット 2020-03-01 22.24.51.png
GETメソッドでは、送信するデータをアドレスの末尾に付け加える仕様となっている。したがって、どんなデータか他の人に見られてしまうので、例えば検索等のアプリに使われる。パスワード等を入力するようなフォームには使わないようにする必要がある。

(9)Herokuにデプロイ

Herokuへのデプロイ詳細は以下の記事に書いた通りなので、エッセンスのみとし、詳細説明を省く。
https://qiita.com/nooonchi/items/a7ea7c7a12c56ab8e901

Herokuにログインし、Heroku上にアプリを作成

heroku login

アプリ名はcat-formとした。

Heroku create cat-form

ディレクトリmy_formを初期化して、

git init

Herokuとローカル環境を紐つけて、

heroku git:remote -a cat-form

ディレクトリmy_formにrequirements.txtを作成して、

pip freeze > requirements.txt

ディレクトリmy_formにProckfileを作成し、以下を入力。
この時、gの前はブランク一つ必要、また、:appの前のformは、form.pyのformという意味なので注意が必要。

web: gunicorn form:app --log-file -

addして、

git add .

今回は、the-firstという名前でcommitして、

git commit -m'the-first'

Herokuにpushする。

git push heroku master

最後に、

heroku open

すれば完成。
スクリーンショット 2020-03-01 21.23.24.png

(10)おわりに

今後は、これを発展させ、掲示板にチャレンジしたい。

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

ハイパーパラメータチューニングってなに。

はじめに

ハイパーパラメータチューニングはモデルの精度を向上させるために用いられる手法です.
scikit-learn でモデルを作り、パラメータを設定しないと適当な複雑さで設定されます.

ハイパーパラメータってなに。

学習の前に指定し、学習の方法や速度、モデルの複雑さを定めるパラメータのことです.

手法(種類)

  • 手動
  • グリッドサーチ
  • ランダムサーチ

bergstra12a-04.jpg
出典:http://www.jmlr.org/papers/volume13/bergstra12a/bergstra12a.pdf

グリッドサーチ

各パラメータに対して候補を決め、それらの組み合わせを全て試す方法です. 全て試すのでパラメータ候補を多くはできません.

ランダムサーチ

各パラメータに対して候補を決め、ランダムなパラメータの組み合わせをn回繰り返す方法です. 全て試さないのでよりよいパラメータの組み合わせを探索できないかもしれません.

パラメータの組み合わせ

import numpy as np

params_list01 = [1, 3, 5, 7]
params_list02 = [1, 2, 3, 4, 5]

# グリッドサーチ
grid_search_params = []
for p1 in params_list01:
    for p2 in params_list02:
        grid_search_params.append(p1, p2)
# append(): リストの末尾に要素を追加する

# ランダムサーチ
random_search_params = []
count = 10
for i in range(count):
    p1 = np.random.choice(params_list01)  # random.choice(): 配列の中身をランダムに取得する
    p2 = np.random.choice(params_list02)
    random_search_params.append(p1, p2)

scikit-learn

scikit-learnのリファレンスはこちら

from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
params = {
    "max_depth": [2, 4, 6, 8, None],
    "n_estimators": [50,100,200,300,400,500],
    "max_features": range(1, 11),
    "min_samples_split": range(2, 11),
    "min_samples_leaf": range(1, 11)
}

# グリッドサーチ
gscv = GridSearchCV(RandomForestRegressor(), params, cv=3, n_jobs=-1, verbose=1)
gscv.fit(X_train_valid, y_train_valid)

print("Best score: {}".format(gscv.best_score_))
print("Best parameters: {}".format(gscv.best_params_))

# ランダムサーチ
rscv = RandomizedSearchCV(RandomForestRegressor(), params, cv=3, n_iter=10, n_jobs=-1, verbose=1)
rscv.fit(X_train_valid, y_train_valid)

print("Best score: {}".format(rscv.best_score_))
print("Best parameters: {}".format(rscv.best_params_))

おわりに

いったいどれを採用したら良いのー??というときはランダムサーチをします. 効率よく良いパラメータの組み合わせがみつかるようです. 

参考

書籍: Kaggleで勝つデータ分析の技術(技術評論社)

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

類似画像解析のための近似最近傍探索(初心者向け)(1)

類似画像について書き残したいが、まずはK-NN法の説明から。

K-NN法

あるデータから近いデータk個のデータから識別していく。
数が多いものデータのクラスと同じクラスと判断していく。
ex)
k=3の場合、四角1個と三角2個のため、三角のグループとみなす。
k=5の場合、四角3個と三角2個のため、四角のグループとみなす。
スクリーンショット 2020-03-01 22.05.27.png

どのkをとるかによって、解はバラバラなので適切なkを見つける必要がある。
crossvalidationを使い、k毎の汎化誤差を求めて、最小のものを最適なkとする。
汎化誤差(=テストデータによる実績との誤差)
これらを全てk毎の計算をするのは、画像によるベクトル変換をした時膨大な処理時間となる。
そこで、近似最近傍検索。

近似最近傍検索

最近傍が遠くても、許容して採用する。
d(q,x) <= (1+ε)d(q,x*)
d(q,x) が近似解までの距離
d(q,x*)が最近傍までの距離

近似解は、最良優良探索で決める
最良優良探索は、何らかの規則に従って次に探索する最も望ましいノードを選択するようにした探索アルゴリズム。

スクリーンショット 2020-03-01 22.28.23.png

次回、近似最近傍探索を実際に使ってみたいと思う。

参考

機械学習k近傍法理論編

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

【Udemy Python3入門+応用】 18.リストのメソッド

※この記事はUdemyの
現役シリコンバレーエンジニアが教えるPython3入門+応用+アメリカのシリコンバレー流コードスタイル
の講座を受講した上での、自分用の授業ノートです。
講師の酒井潤さんから許可をいただいた上で公開しています。

■リストのメソッド

.index()
.index
r = [1, 2, 3, 4, 5, 1, 2, 3]
print(r.index(3))
result
2

.index()を使うことで、指定した要素のインデックスを調べることができる。
この場合、先頭から探してくれるので、インデックスが2と返ってきている(1番目の「3」を検出している)。

.index
r = [1, 2, 3, 4, 5, 1, 2, 3]
print(r.index(3, 3))
result
7

.index(3, 3)
最初の3は「『3』を探してくれ」というように、検索する要素を指定しており、
後ろの3は「インデックス『3』以降から探してくれ」というように、検索範囲を指定している。

.count()
.count
r = [1, 2, 3, 4, 5, 1, 2, 3]
print(r.count(3))
result
2

「『3』が何個あるか数えておくれ」という意味。

◆if文
if
r = [1, 2, 3, 4, 5, 1, 2, 3]

if 5 in r:
    print('exist')
result
exist

If '5' exists in r, print "exist."
という意味。
if文については後の授業で扱う。

◆数字順に並べる
sort_and_reverse
r = [1, 2, 3, 4, 5, 1, 2, 3]

r.sort()
print(r)

r.sort(reverse=True)
print(r)

r.reverse()
print(r)
result
[1, 1, 2, 2, 3, 3, 4, 5]
[5, 4, 3, 3, 2, 2, 1, 1]
[1, 1, 2, 2, 3, 3, 4, 5]

.sort()を用いることで、数字の小さい順にリストの要素を並べ替えできる。
.sort(reverse=True)とすると、逆順にできる。
.reverse()というメソッドも用意されているので、それでもOK。

.split().join()
split_and_join
s = 'My name is Tony.'

to_split = s.split(' ')
print(to_split)

x = ' '.join(to_split)
print(x)
result
['My', 'name', 'is', 'Tony.']
My name is Tony.

.split(' ')によって、
(スペース)で文字列をバラバラにして、それを要素にしてリストを作ってくれ」
ということができる。
逆に、' '.join()を使うことで、
(スペース)を要素間に挟んで、要素を結合して文字列にしてくれ」
ということができる。

◆help
help
help(list)
result
class list(object)
 |  list(iterable=(), /)
 |  
 |  Built-in mutable sequence.
 |  
 |  If no argument is given, the constructor creates a new empty list.
 |  The argument must be an iterable if specified.
 |  
 |  Methods defined here:
 |  
 |  __add__(self, value, /)
 |      Return self+value.
 |  
 |  __contains__(self, key, /)
 |      Return key in self.
 |  
 |  __delitem__(self, key, /)
 |      Delete self[key].
 |  
 |  __eq__(self, value, /)
 |      Return self==value.
 |  
 |  __ge__(self, value, /)
 |      Return self>=value.
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __getitem__(...)
 |      x.__getitem__(y) <==> x[y]
 |  
 |  __gt__(self, value, /)
 |      Return self>value.
 |  
 |  __iadd__(self, value, /)
 |      Implement self+=value.
 |  
 |  __imul__(self, value, /)
 |      Implement self*=value.
 |  
 |  __init__(self, /, *args, **kwargs)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __le__(self, value, /)
 |      Return self<=value.
 |  
 |  __len__(self, /)
 |      Return len(self).
 |  
 |  __lt__(self, value, /)
 |      Return self<value.
 |  
 |  __mul__(self, value, /)
 |      Return self*value.
 |  
 |  __ne__(self, value, /)
 |      Return self!=value.
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  __reversed__(self, /)
 |      Return a reverse iterator over the list.
 |  
 |  __rmul__(self, value, /)
 |      Return value*self.
 |  
 |  __setitem__(self, key, value, /)
 |      Set self[key] to value.
 |  
 |  __sizeof__(self, /)
 |      Return the size of the list in memory, in bytes.
 |  
 |  append(self, object, /)
 |      Append object to the end of the list.
 |  
 |  clear(self, /)
 |      Remove all items from list.
 |  
 |  copy(self, /)
 |      Return a shallow copy of the list.
 |  
 |  count(self, value, /)
 |      Return number of occurrences of value.
 |  
 |  extend(self, iterable, /)
 |      Extend list by appending elements from the iterable.
 |  
 |  index(self, value, start=0, stop=9223372036854775807, /)
 |      Return first index of value.
 |      
 |      Raises ValueError if the value is not present.
 |  
 |  insert(self, index, object, /)
 |      Insert object before index.
 |  
 |  pop(self, index=-1, /)
 |      Remove and return item at index (default last).
 |      
 |      Raises IndexError if list is empty or index is out of range.
 |  
 |  remove(self, value, /)
 |      Remove first occurrence of value.
 |      
 |      Raises ValueError if the value is not present.
 |  
 |  reverse(self, /)
 |      Reverse *IN PLACE*.
 |  
 |  sort(self, /, *, key=None, reverse=False)
 |      Stable sort *IN PLACE*.
 |  
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  __hash__ = None

helpでメソッドの使い方を呼び出すこともできる。

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

pythonのpikaで色々なrabbimqの機能を使用する

はじめに

前回のpythonでrabbimqを扱うではRabbitMqで単純なメッセージのやり取りを行いましたが、RabbitMqは色々な機能があるため今回はその色々な機能と使用方法をまとめます。

環境

  • python:3.6.5
  • イメージ:rabbitmq:3-management

キューの確認設定

標準のpikaの設定ではRabbitMq内に送受信したいキューが存在しないときは自動的にキューを作成しますが、自動的にキューを作成せずにエラーしたい場合があります。その場合は、チャンネルのqueue_declare()関数のpassive引数にTrueを与えるとキューがないときはエラーします。

シチュエーション例

プロデューサー側で全てのキューを管理して、コンシューマー側では純粋に接続のみ許したいときなど。

passiveをtrueにしたプロデューサー

前回の例として作成したプロデューサーのchannel.queue_declare()passive=Trueを追加しているだけです。キューが存在すればそのまま接続できますが、キューが存在しなければpika.exceptions.ChannelClosedByBrokerがエクセプションとして上がってきます。

client_main.py
import pika

pika_param = pika.ConnectionParameters('localhost')
connection = pika.BlockingConnection(pika_param)
channel = connection.channel()

try:
    channel.queue_declare(queue='hello', passive=True)
except pika.exceptions.ChannelClosedByBroker as ex:
    print(ex)
    exit(1)

channel.basic_publish(exchange='', routing_key='hello', body='Hello World!')

connection.close()

キューチェックの実行

ソースができたため、実行してみます。エクセプションが発生しました。発生したエクセプションを表示するとhelloのキューがないということがわかります。

PS C:\Users\xxxx\program\python\pika> python .\client_main.py
(404, "NOT_FOUND - no queue 'hello' in vhost '/'")

コネクションの排他設定

標準のpikaの設定では無条件にコネクションを受け付けますが、他のコネクションを受け付けないように排他制御をかけることができます。その場合は、チャンネルのqueue_declare()関数のexclusive引数にTrueを与えると他にコネクションが接続しているときはエラーするため、排他のチェックに利用できます。コネクションをクローズすればまた別のコネクションを接続することができます。

シチュエーション例

コンシューマー側でメッセージを用意してRabbitMqに送るまでは、他のコンシューマからメッセージの受け付けをしたくないときなど。

コネクションの排他設定を有効にしたプロデューサー

channel.queue_declare()exclusive=Trueを追加しているだけです。今回は、一度接続してからコネクションをクローズせずに新しくコネクションを作って接続しています。後から来たコネクションに対してはpika.exceptions.ChannelClosedByBrokerがエクセプションとして上がってきます。

client_main.py
import pika

pika_param = pika.ConnectionParameters('localhost')
connection = pika.BlockingConnection(pika_param)
channel = connection.channel()
channel.queue_declare(queue='hello', exclusive=True)
channel.basic_publish(exchange='', routing_key='hello', body='Hello World!')
channel.close()

connection = pika.BlockingConnection(pika_param)
channel = connection.channel()
try:
    channel.queue_declare(queue='hello')
except pika.exceptions.ChannelClosedByBroker as ex:
    print('other connection access fail')
    exit(1)
channel.basic_publish(exchange='', routing_key='hello', body='Hello World!')
channel.close()

channel.close()の下にconnection.close()を入れると正常に終了します。

コネクションの排他の実行

ソースができたため、実行してみます。2つめのキュー接続時にエクセプションが発生しました。発生したエクセプションを表示するとhelloにアクセスロックがかかっていることがわかります。

PS C:\Users\xxxx\program\python\pika> python .\client_main.py
other connection access fail
(405, "RESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'hello' in vhost '/'. It could be originally declared on 
another connection or the exclusive property value does not match that of the original declaration.")

チャンネルの上限設定

標準のpikaの設定では無条件にチャンネルを作成できますが、チェンネルの上限を設定することができます。その場合は、pikaのpika.ConnectionParameters()関数のchannel_max引数に上限値を与えると上限を設定することができます。上限を超えるチャンネルを作成しようとするとエラーを発生させます。

シチュエーション例

RabbitMqのサーバが潤沢ではないため、チャンネル生成数を加減するときなど

チャンネルの上限設定を有効にしたプロデューサー

pika.ConnectionParameters()channel_max=2を追加しているだけです。今回は、上限値を2にしているので無意味に3つのチャンネルを作成しています。その結果、3つめのチェンネルに対しては作成できずにpika.exceptions.ConnectionClosedByBrokerがエクセプションとして上がってきます。

client_main.py
import pika

pika_param = pika.ConnectionParameters('localhost', channel_max=2)
connection = pika.BlockingConnection(pika_param)

channel = connection.channel(1)
channel = connection.channel(2)
try:
    channel = connection.channel(3)
except pika.exceptions.ConnectionClosedByBroker as ex:
    print('channel crate error')
    print(ex)

チャンネルの上限の実行

ソースができたため、実行してみます。3つめのチャンネル作成時にエクセプションが発生しました。発生したエクセプションを表示するとチャンネル上限が2であることがわかります。

PS C:\Users\xxxx\program\python\pika> python .\client_main.py
channel crate error
(530, 'NOT_ALLOWED - number of channels opened (2) has reached the negotiated channel_max (2)')

リトライの設定

pikaの接続に失敗したときにリトライする回数を設定することができます。その場合は、pikaのpika.ConnectionParameters()関数のconnection_attempts引数にリトライ回数を与えると回数を設定することができます。

シチュエーション例

RabbitMqのサーバへのネットワークが不安定の場合など

リトライの設定を有効にしたプロデューサー

pika.ConnectionParameters()connection_attempts=2を追加しているだけです。今回は、リトライをしているのがわかるようにpikaのログを出すようにして、RabbitMqを落としています。上のlogger.xxx系はpika内のログを出すための設定です。

client_main.py
import pika
import datetime
import logging 

logger = logging.getLogger('pika')
logger.setLevel(logging.ERROR)
logger.addHandler(logging.StreamHandler())
pika_param = pika.ConnectionParameters('localhost', connection_attempts=2)

try:
    print('start connect {}'.format(datetime.datetime.now()))
    connection = pika.BlockingConnection(pika_param)
except pika.exceptions.AMQPConnectionError as ex:
    print('connect error {}'.format(datetime.datetime.now()))
    print(ex)

リトライの実行

ソースができたため、実行してみます。ログが大量に出て分かり難いですが同じようなエラーが4回(2回エラー×設定値リトライ(2回))出ています。

PS C:\Users\xxxx\program\python\pika> python .\client_main.py
start connect 2020-03-01 21:46:18.549268
Socket failed to connect: <socket.socket fd=936, family=AddressFamily.AF_INET6, type=SocketKind.SOCK_STREAM, proto=6, laddr=('::', 38520, 0, 0)>; error=10061 (Unknown error)
TCP Connection attempt failed: ConnectionRefusedError(10061, 'Unknown error'); dest=(<AddressFamily.AF_INET6: 23>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('::1', 5672, 0, 0))
AMQPConnector - reporting failure: AMQPConnectorSocketConnectError: ConnectionRefusedError(10061, 'Unknown error')
Socket failed to connect: <socket.socket fd=936, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('0.0.0.0', 38524)>; error=10061 (Unknown error)
TCP Connection attempt failed: ConnectionRefusedError(10061, 'Unknown error'); dest=(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('127.0.0.1', 5672))
AMQPConnector - reporting failure: AMQPConnectorSocketConnectError: ConnectionRefusedError(10061, 'Unknown error')
Socket failed to connect: <socket.socket fd=812, family=AddressFamily.AF_INET6, type=SocketKind.SOCK_STREAM, proto=6, laddr=('::', 38533, 0, 0)>; error=10061 (Unknown error)
TCP Connection attempt failed: ConnectionRefusedError(10061, 'Unknown error'); dest=(<AddressFamily.AF_INET6: 23>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('::1', 5672, 0, 0))
AMQPConnector - reporting failure: AMQPConnectorSocketConnectError: ConnectionRefusedError(10061, 'Unknown error')
Socket failed to connect: <socket.socket fd=812, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('0.0.0.0', 38535)>; error=10061 (Unknown error)
TCP Connection attempt failed: ConnectionRefusedError(10061, 'Unknown error'); dest=(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('127.0.0.1', 5672))
AMQPConnector - reporting failure: AMQPConnectorSocketConnectError: ConnectionRefusedError(10061, 'Unknown error')
AMQP connection workflow failed: AMQPConnectionWorkflowFailed: 4 exceptions in all; last exception - AMQPConnectorSocketConnectError: ConnectionRefusedError(10061, 'Unknown error'); first exception - AMQPConnectorSocketConnectError: ConnectionRefusedError(10061, 'Unknown error').
AMQPConnectionWorkflow - reporting failure: AMQPConnectionWorkflowFailed: 4 exceptions in all; last exception - AMQPConnectorSocketConnectError: ConnectionRefusedError(10061, 'Unknown error'); first exception - AMQPConnectorSocketConnectError: ConnectionRefusedError(10061, 'Unknown error')
Connection workflow failed: AMQPConnectionWorkflowFailed: 4 exceptions in all; last exception - AMQPConnectorSocketConnectError: 
ConnectionRefusedError(10061, 'Unknown error'); first exception - AMQPConnectorSocketConnectError: ConnectionRefusedError(10061, 
'Unknown error')
Error in _create_connection().
Traceback (most recent call last):
  File "C:\Users\minkl\AppData\Local\Programs\Python\Python36-32\lib\site-packages\pika\adapters\blocking_connection.py", line 450, in _create_connection
    raise self._reap_last_connection_workflow_error(error)
pika.exceptions.AMQPConnectionError
connect error 2020-03-01 21:46:28.665843

おわりに

良く使いそうなRabbitMqの機能を使用してみました。必要な機能は一通りあると思いますが、どの設定がキューなのかコネクションなのかチャンネルなのかを理解して使用しないと混乱しそうに感じました。

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

確率積分(Ito integral)を算出してみた

■ 確率積分として以下のような方式があります

Riemann sum
式)
$ \sum_{j=0}^{N-1} h(t_j) (t_{j+1}-t_j) $

Ito integral.
伊藤清(Kiyoshi Ito)によって拡張された確率過程の方式
式)
$ \sum_{j=0}^{N-1}h(t_j) (W(t_{j+1})-W(t_j)) $.
<=> $ \int_0^{T} h(t)dW(t) $.

Stratonovich integrals
式)
$\sum_{j=0}^{N-1}h\big(\frac{t_j+t_{j+1}}{2}\big)(W(t_{j+1})-W(t_j))$

 Ito integralの計算

$h(t)\equiv W(t)$の時、Ito integralは、

\begin{align*}
\sum_{j=0}^{N-1} W(t_j) (W(t_{j+1})-W(t_j)) \\
&=\sum_{j=0}^{N-1} ({W(t_{j+1})}^2 - {W(t_{j+1})}^2 + 2 W(t_j) W(t_{j+1}) - {W(t_j)}^2  - {W(t_j)}^2) \\
&= \frac{1}{2} \sum_{j=0}^{N-1} ({W(t_{j+1})}^2 - {W(t_j)}^2 - (W(t_{j+1}) - W(t_j))^2 )\\
&= \frac{1}{2} (W(T)^2 - W(0)^2)  -  \frac{1}{2} \sum_{j=0}^{N-1} (W(t_{j+1}) - W(t_j))^2
\end{align*}

$\sum_{j=0}^{N-1} (W(t_{j+1}) - W(t_j))^2$はWiener Processの分散に等しく、$T$とおけるため、上記の式は、
$$\sum_{j=0}^{N-1} (W(t_{j+1}) - W(t_j))^2=\frac{1}{2}(W(T)^2-T)$$と表現できる。

Ito integralを算出してみる。

N=10000;
M=1;
T = 1.0
dt = T / N;

t = np.arange(0.0,1.0, dt);
dW = np.sqrt(dt)*randn(N,M); # (N, M)行列
W = np.cumsum(dW,axis=0);
e = np.array([[0]])
W_=np.concatenate((e,W[:-1]), axis=0)  # 初期値に0をおく  W(0)として。

ito = np.dot(W_.T,dW) # (10000, 1).T * (10000, 1)  => (1, 1)
np.abs(ito - 0.5*(W[-1]**2 - T) )[0][0]

## output
##  0.00414652405737

参考記事
- Desmond J. Higham "An Algorithmic Introduction to Numerical Simulation of Stochastic Differential Equations"

https://www.semanticscholar.org/paper/An-Algorithmic-Introduction-to-Numerical-Simulation-Higham/1c4126f96df7690dd40dab5f34ee4be5a5f95fbb

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

【Udemy Python3入門+応用】 17.リストの操作

※この記事はUdemyの
現役シリコンバレーエンジニアが教えるPython3入門+応用+アメリカのシリコンバレー流コードスタイル
の講座を受講した上での、自分用の授業ノートです。
講師の酒井潤さんから許可をいただいた上で公開しています。

■リストの操作

◆リストの要素を置換する
>>> s = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> s[0] = 'A'
>>> s
['A', 'b', 'c', 'd', 'e', 'f', 'g']
>>> s[2:5] = ['C', 'D', 'E']
>>> s
['A', 'b', 'C', 'D', 'E', 'f', 'g']

リスト内のインデックスやスライスを指定して、直接文字列を代入することができる。

.append().insert()
>>> n = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> n
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> n.append(100)
>>> n
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100]
>>> n.insert(0, 200)
>>> n
[200, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100]

.appendにより、リスト内の最後に文字列が付け加えられる。
.insertを用いると、任意のインデックスを指定し、その場所に文字列を挿入できる。

.pop()
>>> n = [200, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 300]
>>> n
[200, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 300]
>>> n.pop(0)
200
>>> n
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 300]
>>> n.pop()
300
>>> n
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

.pop()でインデックスを指定すると、その要素が抽出される。
抽出された要素はリストからなくなる。
インデックスを指定しない場合は、最後の要素が抽出される。

◆リストの要素を消去する(空のリストで置換する)
>>> s = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> s[2:5] = []
>>> s
['a', 'b', 'f', 'g']

指定した部分を空のリストに置換してやることで、要素を消去できる。

◆リストの要素を消去する(delを用いる)
>>> n = [1, 2, 3, 4, 5]
>>> n
[1, 2, 3, 4, 5]
>>> del n[0]
>>> n
[2, 3, 4, 5]
>>> del n
>>> n
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'n' is not defined

インデックスを指定したものをdelで消去できる。
うっかりインデックスを指定せずにndelをかけた場合、n自体が抹消されるため注意。

◆リストの要素を消去する(.remove()を用いる)
>>> n = [1, 2, 3, 4, 5]
>>> n
[1, 2, 3, 4, 5]
>>> n.remove(2)
>>> n
[1, 3, 4, 5]
>>> n.remove(4)
>>> n
[1, 3, 5]
>>> n.remove(6)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list

.remove()を用いると、要素を直接指定して消去することができる。
このときリスト内に存在しない要素を指定するとエラーとなる。

◆リストに別のリストの要素を追加する
>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> x = a + b
>>> x
[1, 2, 3, 4, 5, 6]

これは前にやった通り。

>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> a += b
>>> a
[1, 2, 3, 4, 5, 6]

+=により、bのリスト内の要素がaのリストに付け加えられる。

>>> x = [1, 2, 3]
>>> y = [4, 5, 6]
>>> x.extend(y)
>>> x
[1, 2, 3, 4, 5, 6]

.extend()というメソッドを使う方法もある。

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

時系列予測のためのLSTM(1)(初心者向け)

今回は、LSTMについて。
時系列などの規則性がある現象についての予測問題はアプローチとして有効かもしれない。
自分の理解のためのアウトプットなので、一旦は参考サイトをほぼトレースしている。

LSTM(Long Short-Term Memory)

ひとことでいうと、短期記憶で長期期間で活用して学習を進める。特徴がある。
LSTMの部分を図式化すると、以下になる。
スクリーンショット 2020-03-01 19.57.58.png
htでの計算で、前の出力結果ht-1とXtを使い求める。htはht+1を求めるために使われるイメージ。
それぞれ、
・OutputGate
・Forget Gate
・Input Gate
・活性化関数部分
・Memory Cell
で成り立っている

OutputGate

以下の矢印赤い部分。
Xtに対しての線形変換Wo、htに対しての線形変換Ro、バイアスBoを使い
Ot=σ(WoXt+Roht−1+Bo)
という計算が行われる。
ニューラルネットワークと同様の計算式。
スクリーンショット 2020-03-01 20.22.56.png

Forget Gate

OutputGateと同様、
Wf、Rf、Bfのパラメータがあり
ft=σ(WfXt+Rfht−1+Bf)
という計算が行われる。
スクリーンショット 2020-03-01 20.30.57.png

Input Gate

同様に、
it=σ(WiXt+Riht−1+Bi)
という計算が行われる。
スクリーンショット 2020-03-01 20.33.26.png

活性化関数部分

Zt=tanh(WzXt+Rzht−1+Bz)
活性化関数部分の計算式。
スクリーンショット 2020-03-01 20.39.47.png

Memory Cell付近の計算

①Forget Gate側

ft=σ(WfXt+Rfht−1+Bf)とセル点線からのアウトプットCt-1により
Ct−1 ⊗ ft というアウトプット。
⊗ は要素ごとの積
スクリーンショット 2020-03-01 20.43.05.png

②Input Gate側

it=σ(WiXt+Riht−1+Bi)とZt=tanh(WzXt+Rzht−1+Bz)により、
it ⊗ Zt というアウトプット。
スクリーンショット 2020-03-01 20.44.28.png

③Cellの手前

①でのCt−1 ⊗ ft と②での it ⊗ Zt により
Ct = Ct−1 ⊗ ft + it ⊗ Zt
という計算が行われる。
スクリーンショット 2020-03-01 20.45.04.png

④出力付近

Memory Cell部分 Ct = it ⊗ Zt + Ct−1 ⊗ ft
OutputGate部分 Ot = σ(WoXt+Roht−1+Bo)
を使い
ht = Ot ⊗ tanh(Ct)
が行われる。
スクリーンショット 2020-03-01 20.45.28.png

LSTMのポイント

Ct = Ct−1 ⊗ ft + it ⊗ Zt
Ct−1 ⊗ ft Forget Gate部分で、Ct-1は過去の情報のパラメータをどれくらい反映するかを調整している。
it ⊗ Zt Input部分で、得られた入力値 it をどれだけ反映するか、を Zt 活性化関数により調整している。

参考

今更聞けないLSTMの基本

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

【Udemy Python3入門+応用】 16.リスト型

※この記事はUdemyの
現役シリコンバレーエンジニアが教えるPython3入門+応用+アメリカのシリコンバレー流コードスタイル
の講座を受講した上での、自分用の授業ノートです。
講師の酒井潤さんから許可をいただいた上で公開しています。

■リスト型

◆リストの使い方
>>> l = [1, 20, 4, 50, 2, 17, 80]

>>> type(l)
<class 'list'>

このように、リストを使うことができる。

>>> l
[1, 20, 4, 50, 2, 17, 80]
>>> l[0]
1
>>> l[1]
20
>>> l[-1]
80
>>> l[0:2]
[1, 20]
>>> l[:2]
[1, 20]
>>> l[2:]
[4, 50, 2, 17, 80]
>>> l[:]
[1, 20, 4, 50, 2, 17, 80]

リストに対しては、インデックスやスライスも使える。


■リストにlen()を使う

>>> len(l)
7

リストに対してlen()を用いると、リストに含まれるデータの個数が返る。


■文字列をリストに変換する

>>> letters = list('abcdefg')
>>> letters
['a', 'b', 'c', 'd', 'e', 'f', 'g']

あまり使いみちはないらしいが…


■リストからの抽出

◆1つとばしで抽出する
>>> n = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> n[::2]
[1, 3, 5, 7, 9]

[::2]とすることで、1つとばしに抽出できる。

◆反対順にする
>>> n[::-1]
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

[::-1]とすることで、並び順を逆にして出力することができる。


■リストの中にリストを入れる

◆リストの中にリストを入れる
>>> a = ['a', 'b', 'c']
>>> n = [1, 2, 3]
>>> x = [a, n]
>>> x
[['a', 'b', 'c'], [1, 2, 3]]
◆インデックスはどうなる?
>>> x[0]
['a', 'b', 'c']
>>> x[1]
[1, 2, 3]

この状態では、含まれているリストにインデックスがふられる。

◆要素になっているリストの中でのインデックス
>>> x[0][1]
'b'

[0][1]とすることにより、[0]の中での[1]を指定できる。

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

にじさんじで学ぶPythonプログラミング入門【カルロスピノ氏リスペクト】

こんにちは。タケシです。
インフラエンジニアとして働きながら、スキルの幅を広げるためにプログラミングを学んでいます。

Progateなどの教材で学習するだけでは興味がそそられず、いかにもお勉強って感じでやる気が出なかったのですが、初学者でも楽しく無料でプログラミングが学べそうな動画を先日見つけました。

IMAGE ALT TEXT HERE
ホロライブで学ぶPython入門~15分でYoutubeの情報取得までやる編~

この動画では投稿主のカルロスピノ氏が、プログラミング初心者向けにPythonでYoutubeの動画情報取得をする方法を解説しています。

ブラウザ(Chrome)でPythonのコードを実行するので面倒な環境構築も不要ですし、お金もかかりません。

この記事ではカルロスピノ氏の動画を見た視聴者(私)が、動画内で紹介されている内容を実行した結果を交えつつPythonを使ってできることを紹介していきます。

この記事はこのような人向けに書いています。
・プログラミングをこれから学びたい初心者
・Pythonを学びたいけど既存の教材はとっつきにくい人
・Pythonを学びたいけどスクールに通うお金がない人
・Youtubeで情報収集するアプリを開発したい人

この記事を読むとできるようになること
・Pythonのコードを無料で実行できる環境が用意できる
・Pythonの簡単なプログラミングができるようになる
・推しのYoutuber/VTuberの情報収集ができるようになる

ブラウザ(Chrome)でPythonプログラムを実行するための環境構築

詳しい手順はカルロスピノ氏の動画内で解説されていますが、補足も兼ねてこの記事でも手順を紹介していきます。

Google Colaboratoryにアクセスする

Googleがクラウド上でPythonのプログラムを実行できる環境を無料で提供しています。
それがGoogle Colaboratoryです。以下のURLからアクセスしましょう。

https://colab.research.google.com/notebooks/welcome.ipynb

Python_環境構築_1.PNG

Google Colaboratoryにアクセスするとこのような画面が表示されるので左上の「ファイル」タブをクリックします。

Python_環境構築_2.PNG.png

「ノートブックを新規作成」をクリックしましょう。

※カルロスピノ氏の動画では、ノートブックの新規作成をする項目が「Python3の新しいノートブック」「Python2の新しいノートブック」になっていましたが、私がGoogle Colaboratoryにアクセスしたときには項目が「ノートブックを新規作成」に変わっていました。

Python_環境構築_3.PNG

ノートブックが新規作成されると、コードが実行できる箱(セル)が表示されます。
箱(セル)内にコードを書いたのち、左端の●ボタンをクリックすると実行されます。

※セルは「+コード」タブをクリックすると追加可能。

必要な外部ライブラリの用意

カルロスピノ氏の当該動画内では、Youtubeの動画情報収集を手軽に行えるRDFを使用しています。

そのRDFで動画情報を取得する上でfeedparserという外部ライブラリを使用しているので、そのインストールとインポートを事前に行っておきましょう。

feedparserのインストール

新しいセルでpip install feedparserと入力し、実行するとGoogle Colaboratoryの環境にfeedparserがインストールされます。

Collecting feedparser
  Downloading https://files.pythonhosted.org/packages/91/d8/7d37fec71ff7c9dbcdd80d2b48bcdd86d6af502156fc93846fb0102cb2c4/feedparser-5.2.1.tar.bz2 (192kB)
     |████████████████████████████████| 194kB 2.8MB/s 
Building wheels for collected packages: feedparser
  Building wheel for feedparser (setup.py) ... done
  Created wheel for feedparser: filename=feedparser-5.2.1-cp36-none-any.whl size=44940 sha256=e16ce8a47aa3d1c3dd0fe4b042ecf27cdc731a2f20e05d2885cd6de37f4a268f
  Stored in directory: /root/.cache/pip/wheels/8c/69/b7/f52763c41c5471df57703a0ef718a32a5e81ee35dcf6d4f97f
Successfully built feedparser
Installing collected packages: feedparser
Successfully installed feedparser-5.2.1

このような実行結果が表示されていれば成功です。

feedparserのインポート

続いて新しいセルでimport feedparserと入力し実行しましょう。

何も表示されなければ成功です。

Python_環境構築_6.PNG

これで任意のYoutubeチャンネルで公開されている動画の情報が収集できます。

RDFで収集できる情報

RDFで収集できる情報の一覧は、以下のコードを実行することで確認することができます。

チャンネルIDの部分は任意のYoutuber/VTuberのチャンネルIDに置き換えてください。

rdf_url = "https://www.youtube.com/feeds/videos.xml?channel_id=チャンネルID"
document = feedparser.parse(rdf_url)
for entry in document.entries:
  print(entry)

ちなみに、RDFによって私がいくつかのYoutubeチャンネルで情報収集した結果、以下のことがわかりました。

  • 情報収集できるのは最新の動画15件分程度
  • 公開後に非公開とした動画の情報は収集不可
  • 動画の評価、コメント、スパチャの金額、同時接続数等は収集できない

Youtube RDFでにじさんじライバーの動画情報収集をしてみよう

カルロスピノ氏がほぼ日刊ホロライブというチャンネルでホロライブの切り抜き動画をUPされていますので、私はにじさんじライバーの動画情報を収集してみます。

Pythonで書いたプログラムによる基本的な情報収集方法は、カルロスピノ氏が動画内で解説されているので、ここではカルロスピノ氏のプログラムにアレンジを加えるやり方を紹介しましょう。

各ライバーのArk配信のタイトル・再生数を収集

にじさんじライバーの配信(アーカイブ)の中でも、任意のゲームタイトルの実況のみを表示する方法を紹介します。

ここでは例としてArkというゲームの実況をしていた配信(アーカイブ)のタイトル・再生数を表示してみます。

rdf_url = "https://www.youtube.com/feeds/videos.xml?channel_id=チャンネルID"
document = feedparser.parse(rdf_url)
for entry in document.entries:
  if "ARK" in entry["title"] or "Ark" in entry["title"] or "アーク" in entry["title"]:
    print(entry["title"]+" 再生数:"+entry["media_statistics"]["views"])
print(entry["author"])

上記のコード内のチャンネルIDの部分を、任意のライバーのチャンネルIDに変更してください。

チャンネルIDは、各ライバーのチャンネルにアクセスしたときに表示されるURLの末尾になります。

なお、ゲームタイトルは各ライバーによって表記ゆれがある(ARK,Ark,アークなど)ので、orで表記ゆれに対応しています。

本間ひまわり氏のチャンネルURL
https://www.youtube.com/channel/UC0g1AE0DOjBYnLhkgoRWN1w

一例ですが、本間ひまわり氏のチャンネルIDはUC0g1AE0DOjBYnLhkgoRWN1wになります。

#8【ARK】?捕獲よ~ん?【本間ひまわり/にじさんじ】 再生数:82420
#7【ARK】?おそらのおうさまテイムする?【本間ひまわり/叶/にじさんじ】 再生数:111207
#6【ARK】海中生物捕獲大作戦~モサほし~【本間ひまわり】 再生数:100238
#5【ARK】どうくつたんけん!!!【本間ひまわり】 再生数:111546
#4【ARK】筋肉痛バキバキザウルス【本間ひまわり】 再生数:156699
#3【ARK】38万人おめでとう!38の何かしちゃうよ~ん^^【本間ひまわり】 再生数:185975
本間ひまわり - Himawari Honma -

このようにARKの配信(アーカイブ)のタイトル・再生数が表示できます。

夜王国のスプラ配信のタイトル・再生数を収集

先ほどのコードではライバー1人1人の配信(アーカイブ)情報しか収集できません。

そこで、ライバーのユニット単位で特定の配信に関する情報収集をしたいときのコードを紹介します。

例として、夜王国それぞれのスプラトゥーン配信のタイトル・再生数を収集してみましょう。

night_kingdom = ["UC6wvdADTJ88OfIbJYIpAaDA", "UCuvk5PilcvDECU7dDZhQiEw", "UC1QgXt46-GEvtNjEC1paHnw"]
for channel_id in night_kingdom:
  rdf_url = "https://www.youtube.com/feeds/videos.xml?channel_id=" + channel_id
  document = feedparser.parse(rdf_url)
  for entry in document.entries:
    if "スプラ" in entry["title"]:
      print(entry["title"]+" 再生数:"+entry["media_statistics"]["views"])
  print(entry["author"]+"\n")

night_kingdomという配列に要素として不破氏、白雪氏、グウェル氏のチャンネルIDが入っています。

【スプラトゥーン2】ありがとうございました【にじさんじ】 再生数:64893
【#にじさんじスプラ杯】スプラが大好きです。今度は嘘じゃないっす【にじさんじ】 再生数:101978
【スプラトゥーン2】最後の練習...見てるか谷沢...【にじさんじ】 再生数:56815
【スプラトゥーン2】イカ杯メンツで前夜祭だあああああああああ【にじさんじ】 再生数:49194
【スプラトゥーン2】何故だが涙がこぼれる...今日は最後の練習だ【にじさんじ】 再生数:117751
【スプラトゥーン2】深夜1時!?いいから練習だぁ!!!!!!【にじさんじ】 再生数:56281
【スプラトゥーン2】夜王国で戦争!?いいから脳死だぁ!!!!!!【にじさんじ】 再生数:52810
【スプラトゥーン2】大会までもう時間ないってマジ??【にじさんじ】 再生数:64277
【スプラトゥーン2】俺の回線なんか重いよ【にじさんじ】 再生数:54743
【スプラトゥーン2】プラべでイカ杯の全ステ全ルールで遊ぶぞ!!【にじさんじ】 再生数:52664
【スプラトゥーン2】エスコート??俺が姫になるんだよ!!!!!【にじさんじ】 再生数:71476
【スプラトゥーン2】後輩ちゃんと地獄のリグマが始まります【にじさんじ】 再生数:64756
【スプラトゥーン2】ぜっっっっったいに脳死しないガチマッチ【にじさんじ】 再生数:55901
不破 湊 / Fuwa Minato【にじさんじ】

【#にじさんじスプラ杯】大会本番!!意外に強いらCチーム【白雪 巴/にじさんじ】 再生数:22140
【スプラトゥーン2】試合目前!!白雪ボム強化意識【白雪 巴/にじさんじ】 再生数:12360
【スプラトゥーン2】意外に強いらしいチームCの本番前日練習【白雪 巴/にじさんじ】 再生数:9868
【スプラトゥーン2】夜王国で銃撃訓練【白雪 巴/にじさんじ】 再生数:13229
【スプラトゥーン2】深夜の͡͡コソ練。ランク20いくまで耐久【白雪 巴/にじさんじ】 再生数:21462
【スプラトゥーン2】お姉さんとイカプレイしない?【白雪 巴/にじさんじ】 再生数:13949
白雪 巴/Shirayuki Tomoe【にじさんじ】

【Splatoon2】  #にじさんじスプラ杯 本戦【黛灰/夜見れな/フミ/グウェル・オス・ガール】 再生数:14572
【Splatoon2】ガチ練習試合 GチームとIチーム  #にじさんじスプラ杯【黛灰/夜見れな/フミ/グウェル・オス・ガール/イブラヒム/フレン・E・ルスタリオ/鷹宮リオン/天宮こころ】 再生数:17937
【Splatoon 2】夜王国スプラ【不破湊/白雪巴/グウェル・オス・ガール/にじさんじ】 再生数:16813
【Splatoon 2】#にじさんじスプラ杯 Gチーム練習会【フミ/黛灰/夜見れな/グウェル・オス・ガール/にじさんじ】 再生数:71870
グウェル・オス・ガール / Gwelu Os Gar 【にじさんじ】

コードを実行した結果、夜王国のチャンネルでそれぞれ行われたスプラトゥーン配信のタイトル・再生数が収集できました。

グウェル氏のアーカイブのサムネイルを収集しダウンロード

カルロスピノ氏の動画内では、アーカイブのサムネイルを一括保存する方法が紹介されています。

そこで解説されているコードに対するコメントで

for文でインデックスを取りたいならenumerate関数を使うといいですよ。

とありましたので、enumerate関数を使った一括保存のコードを書いてみました。

※下記コードを実行する前にimport requestsを実行する必要があります。

for index, entry in enumerate(document.entries):
  url = entry["media_thumbnail"][0]["url"]
  response = requests.get(url)
  image = response.content

  file_name = "Gwelu"+str(index)+".jpg"

  with open(file_name, "wb") as image_file:
    image_file.write(image)

Python_検索結果_グウェルサムネ_DL.PNG

グウェル氏の直近のアーカイブ15件分のサムネイルをクラウド上に保存できました。

Pythonでできるようになること

Google Colaboratory + RDFでは出来ることも限られていますが、Pythonでのプログラミングによってカルロス氏作成のopipi.netで公開されているグラフやTwitter Botの作成ができるようになるとのこと。

今後、Youtube APIの使い方講座なども動画として投稿されるそうなので、気になった方はチャンネル登録しておくといいでしょう。

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

【Udemy Python3入門+応用】 14.文字の代入 15.f-strings

※この記事はUdemyの
現役シリコンバレーエンジニアが教えるPython3入門+応用+アメリカのシリコンバレー流コードスタイル
の講座を受講した上での、自分用の授業ノートです。
講師の酒井潤さんから許可をいただいた上で公開しています。

■文字の代入

{}.format()
>>> 'A is {}.' .format('a')
'A is a.'

>>> 'A is {}.' .format('test')
'A is test.'

{}の部分に.format()で指定した文字列が代入される。

◆どこに代入するかを指定する
>>> 'My name is {} {}.' .format('Tony', 'Stark')
'My name is Tony Stark.'

>>> 'My name is {0} {1}.' .format('Tony', 'Stark')
'My name is Tony Stark.'

>>> 'My name is {0} {1}. Watashi ha {1} {0} desu.' .format('Tony', 'Stark')
'My name is Tony Stark. Watashi ha Stark Tony desu.'

{}が複数ある場合、前から順に代入される。
インデックスを指定してやると、そのインデックスに相当する文字列が代入される。

◆変数を用いて指定する
>>>Watashi ha {family} {name} desu.' .format(name = 'Tony', family = 'Stark')
Watashi ha Stark Tony desu.'
◆他の型のものはstr型に変換されて代入される
>>> type('1')
<class 'str'>

>>> type(1)
<class 'int'>
>>> '{}, {}, {}, Go!' .format('1', '2', '3')
'1, 2, 3, Go!'

>>> '{}, {}, {}, Go!' .format(1, 2, 3)
'1, 2, 3, Go!'

1はint型であるが、str型に変換されて代入される。


■f-strings

Python 3.6から、f-stringsが以下のように使えるようになった。

f-strings
a = 'a'
print(f'a is {a}')

x, y, z = 1, 2, 3
print(f'a is {x}, {y}, {z}')
print(f'a is {z}, {y}, {x}')

name = 'Tony'
family = 'Stark'
print(f'My name is {name} {family}. Watashi ha {family} {name} desu.')
result
a is a
a is 1, 2, 3
a is 3, 2, 1
My name is Tony Stark. Watashi ha Stark Tony desu.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Udemy Python3入門+応用】 13.文字のメソッド

※この記事はUdemyの
現役シリコンバレーエンジニアが教えるPython3入門+応用+アメリカのシリコンバレー流コードスタイル
の講座を受講した上での、自分用の授業ノートです。
講師の酒井潤さんから許可をいただいた上で公開しています。

■文字のメソッド

.startswith
.startswith
s = 'My name is Mike. Hi, Mike.'
print(s)

is_start = s.startswith('My')
print(is_start)

is_start = s.startswith('You')
print(is_start)
result
My name is Mike. Hi, Mike.
True
False

.startswithでは「指定した文字(列)で始まっているかどうか?」を調べることができる。

.find.rfind
.find_and_.rfind
s = 'My name is Mike. Hi, Mike.'
print(s)
print(s.find('Mike'))
print(s.rfind('Mike'))
result
My name is Mike. Hi, Mike.
11
21

.findで、「指定した文字(列)が何番目か?」を調べることができる。
今回 'Mike' は文中に2回登場しているが、.findでは最初に登場した 'Mike' の位置を調べている。
.rfindでは後ろから探すことなる。
今回では2回目に登場した 'Mike' の位置を調べている。

M y   n a m e   i s    M  i  k  e  .     H  i  ,     M  i  k  e  . 
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      

インデックスを書いてみるとこんな感じ。

.count
.count
s = 'My name is Mike. Hi, Mike.'
print(s)
print(s.count('Mike'))
result
My name is Mike. Hi, Mike.
2

.countで、文中に 'Mike' が登場した回数を調べている。

.capitalize.title
.capitalize
s = 'My name is Mike. Hi, Mike.'
print(s)
print(s.capitalize())
print(s.title())
print(s.upper())
print(s.lower())
result
My name is Mike. Hi, Mike.
My name is mike. hi, mike.
My Name Is Mike. Hi, Mike.
MY NAME IS MIKE. HI, MIKE.
my name is mike. hi, mike.

.capitalizeを使うと、文の先頭の文字だけ大文字となり、残りはすべて小文字となる。
.titleを使うと、各単語の頭文字が大文字になる。
.upperでは全ての文字が大文字になる。
.lowerでは全ての文字が小文字になる。

.replace
.replace
s = 'My name is Mike. Hi, Mike.'
print(s)
print(s.replace('Mike', 'Nancy'))
result
My name is Mike. Hi, Mike.
My name is Nancy. Hi, Nancy.

.replaceで、指定した文字列を任意の文字列に入れ替えることができる。

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

FlaskでPostgreSQLと連携した読書記録簿を作った

はじめに

土日で勉強がてら、ちょっとした読書記録簿を作りました。
画面はこちら。
画面.png

機能としては、読んだ本を登録することと、登録された本のリストを見ることくらいです。

ソースコード

ソースコードは以下の通りです。htmlは見たままなので割愛します。

app.py
from flask import Flask, render_template,url_for,request,redirect
from sqlalchemy import create_engine
import pandas
import psycopg2

'''
参考にしたサイト
https://tanuhack.com/pandas-postgres-readto/

'''

# PostgreSQLの接続情報
connection_config = {
    'user': 'user',
    'password': 'password',
    'host': 'localhost',
    'port': '5432',
    'database': 'mydb'
}

# psycopg2を使ったDB接続
connection = psycopg2.connect(**connection_config)

# dfの作成
df = pandas.read_sql(sql='SELECT * FROM books;', con=connection) 
header = ['id','書籍名','著者','読了日','評価']
record = df.values.tolist() # DataFrameのインデックスを含まない全レコードの2次元配列のリスト

app = Flask(__name__)

@app.route('/')
def index():
    #indexを読み込むたびにDBからのSELECT文更新
    df = pandas.read_sql(sql='SELECT * FROM books;', con=connection) 
    record = df.values.tolist()
    return render_template('index.html',  header=header, record=record)

@app.route('/result', methods=['GET','POST'])
def addition():
    if request.method == "POST":
        # レコード数再取得が必要
        df = pandas.read_sql(sql='SELECT * FROM books;', con=connection) 
        record = df.values.tolist() 

        # POSTの内容を元にINSERT文の値取得、idは現在のレコード数+1
        book_id = len(record)+1
        res1 = request.form['書籍名']
        res2 = request.form['著者']
        res3 = request.form['読了日']
        res4 = request.form['評価']
        dict1={'id':[book_id],'name':[res1],'writer':[res2],'read_day':[res3],'rank':[res4]}

        # DBに飛ばすにはSQLAlchemy必要
        engine = create_engine('postgresql://{user}:{password}@{host}:{port}/{database}'.format(**connection_config))
        df = pandas.DataFrame(data=dict1)
        df.to_sql('books', con=engine, if_exists='append', index=False)
        return redirect(url_for('index'))

## おまじない
if __name__ == "__main__":
    app.run(debug=True)

概要としては、PostgreSQLに読んだ本のテーブルがあり、SELECT文でDataFrameとして取得した内容をリスト化し、render_templateでindex.htmlに送って「あなたが読んだ本」として表示させます。
そして、読んだ本の登録は入力フォームに情報を入力し、登録ボタンを押すことでPOSTメソッドとして処理されます。idは最新の番号を自動で割り振り、他は入力フォームから取得し、1行の辞書型のデータを作ります。
最後に、辞書型のデータをDataFrameに変換し、df.to_sqlでDBにINSERT文を飛ばせばデータが追加されます。

課題

・デプロイできなかった
 herokuを使おうとしたんですが、デプロイしたものがうまく起動せずに断念しました。
 原因ははっきりしていないのですが、生の環境でやったのが影響していそうだったので、virtualenvを使うべきだったと思います。

・機能不足
 ご覧の通り、データを追加する機能しかありません。登録情報を修正したり、消したかったらコマンドプロンプトで直接DBを見に行かなければいけません。

今後に向けて

とりあえず、最低限やりたかったDBとウェブページの連携(DBからの取得とDBへの追記)はできたので及第点にしちゃいます。
次は読書記録簿をリメイクするのか別のものを作るか決めていませんが、virtualenvをつかい、少しずつherokuにデプロイする、という作成スタイルでやりたいと思います。

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

回帰と二値分類の評価指標についてのメモランダム

タスクごとの評価指標についてあまりに忘れるので、最低限覚えておきたいことをまとめる。
この記事では、回帰と二値分類の主な評価指標を扱う。
参考文献: Kaggle本

1. 準備

1-1. 環境

記事中のコードは、Windows-10, Python 3.7.3で動作を確認した。

import platform

print(platform.platform())
print(platform.python_version())

1-2. データセット

回帰、二値分類用データセットを、sklearn.datasetsから読み込む。

from sklearn import datasets
import numpy as np
import pandas as pd

# 回帰用データセット
boston = datasets.load_boston()
boston_X = pd.DataFrame(boston.data, columns=boston.feature_names)
boston_y = pd.Series(boston.target)

# 二値分類用データセット
cancer = datasets.load_breast_cancer()
cancer_X = pd.DataFrame(cancer.data, columns=cancer.feature_names)
cancer_y = pd.Series(cancer.target)

1-3. モデリング

評価指標を得るため、テキトウにモデリングし、予測値を出力する。
本当に評価指標を得るためだけなので、EDAも特徴量作成もバリデーションすらしていない。罪悪感が凄い。

from sklearn.linear_model import LinearRegression, LogisticRegression

# 回帰
slr = LinearRegression()
slr.fit(boston_X, boston_y)
boston_y_pred = slr.predict(boston_X)

# 二値分類
lr = LogisticRegression(solver='liblinear')
lr.fit(cancer_X, cancer_y)
cancer_y_pred = lr.predict(cancer_X)
cancer_y_pred_prob = lr.predict_proba(cancer_X)[:, 1]

2. 回帰タスクの評価指標

RMSE (Root Mean Squared Error: 平均平方二乗誤差)

$$
\mathrm{RMSE}=\displaystyle\sqrt{\dfrac{1}{N}\sum_{i=1}^N(y_i-\hat{y}_i)^2}
$$
予測値がどれだけ真の値から外れているのか直感的に把握できるので、実務でもよく使っている。
メジャーな評価指標なのに、sklearnはMSEしかサポートしていない。np.sqrtしてから返してくれればいいのに。

from sklearn.metrics import mean_squared_error

rmse = np.sqrt(mean_squared_error(boston_y, boston_y_pred))
print(rmse)

4.679191295697281

MAE (Mean Absolute Error)

$$
\mathrm{MAE}=\dfrac{1}{N}\displaystyle\sum_{i=1}^N|y_i-\hat{y}_i|
$$
隣のチームがこれを評価指標にしていた。私は使ったことがない。
RMSEと比べて、外れ値の影響が少ない。RMSEが平均なら、MAEは中央値というイメージ。

from sklearn.metrics import mean_absolute_error

mae = mean_absolute_error(boston_y, boston_y_pred)
print(mae)

3.2708628109003115

決定係数

    R^2=1-\dfrac{\sum_{i=1}^N(y_i-\hat{y}_i)^2}{\sum_{i=1}^N(y_i-\bar{y})^2}

1に近づくほど精度が高いという、まさに文系のためにあるような指標。
そのため昔は大好きだったが、一度実務で痛い目を見てからあまり信用しなくなった。
決定係数は分母が分散なので、データのバラツキが大きければテキトウなモデルでも割と高く出る。
参考程度に見て、実際の当たり具合はRMSEで判断した方が無難。

from sklearn.metrics import r2_score

r2 = r2_score(boston_y, boston_y_pred)
print(r2)

0.7406426641094095

3. 二値分類タスクの評価指標

二値分類タスクは、正例か負例かの分類結果を予測値とする場合と、正例である確率を予測値とする場合があるので、分けて扱う。

3-1. 分類結果を予測値とする場合

混同行列 (confusion matrix)

  • TP (True Positive, 真陽性): 予測値を正例として、その予測が正しい場合
  • TN (True Negative, 真陰性): 予測値を負例として、その予測が正しい場合
  • FP (False Positive, 偽陽性): 予測値を正例として、その予測が誤りの場合
  • FN (False Negative, 偽陰性): 予測値を負例として、その予測が誤りの場合

ただの集計だが、なんだかんだで一番大切ではないだろうか。
例えば、100人に1人しか感染しないウイルスが流行したので、検査を行ったとする。
このとき、とにかく全員を陰性としてしまえば、その精度は一見99%に見えてしまう……みたいなトリックに惑わされないよう、混同行列はしっかり覚えておく必要がある。
なのに、いっつもTPだのFNだのワケが分からなくなる。

from sklearn.metrics import confusion_matrix

cm = confusion_matrix(cancer_y, cancer_y_pred)
tn, fp, fn, tp = cm.flatten()
print(cm)

[[198 14]
[ 9 348]]

accuracy (正答率)

$$
accuracy = \frac{TP+TN}{TP+TN+FP+FN}
$$

error rate (誤答率)

$$
error \; rate = 1-accuracy
$$
先程の例で言えば、全員を陰性とした場合の正答率は0.99となってしまう。
二値分類タスクの場合、まずデータが不均衡かどうかを見なければ何も始まらないことがよく分かる。

from sklearn.metrics import accuracy_score

accuracy = accuracy_score(cancer_y, cancer_y_pred)
print(accuracy)

0.9595782073813708

precision (適合率)

$$
precision = \frac{TP}{TP+FP}
$$

recall (再現率)

$$
recall = \frac{TP}{TP+FN}
$$
precisionは正例と予測したもののうち真の値も正例の割合、recallは真の値が正例のもののうちどの程度を正例と予測したかの割合。
両者はトレードオフの関係にある。precisionは誤検知の割合で、recallは見逃しの割合なので、目的に応じてどちらを重視するか決める。
先程のウイルス検査の例などはrecallを重視するだろうし、マーケティングの文脈ならprecisionが重要になってくるだろう。

from sklearn.metrics import precision_score, recall_score

precision = precision_score(cancer_y, cancer_y_pred)
recall = recall_score(cancer_y, cancer_y_pred)
print(precision)
print(recall)

0.9613259668508287
0.9747899159663865

F1-score

$$
F_1 = \dfrac{2}{\dfrac{1}{recall}+\dfrac{1}{precision}}
$$

Fβ-score

$$
F_\beta = \dfrac{(1+\beta^2)}{\dfrac{\beta^2}{recall}+\dfrac{1}{precision}}
$$
F1-scoreはprecisionとrecallの調和平均、Fβ-scoreはそれをrecallをどれだけ重視するかを表す係数βで調整した指標。
バランスを取っているので使い勝手が良さそうだが、なにぶん実務で使ったことがないのでイメージが湧かない。

from sklearn.metrics import f1_score, fbeta_score

f1 = f1_score(cancer_y, cancer_y_pred)
fbeta = fbeta_score(cancer_y, cancer_y_pred, beta=0.5)
print(f1)
print(fbeta)

0.968011126564673
0.96398891966759

MCC (Matthews Correlation Coefficient)

$$
MCC = \dfrac{TP \times TN - FP \times FN}{\sqrt{(TP+FP)(TP+FN)(TN+FP)(TN+FN)}}
$$
寡聞にして聞いたことがない。
-1から1の値をとり、1のときに完璧な予測を、0のときにランダムな予測を、-1のときは完全に反対の予測を行っていると解釈するらしい。
不均衡なデータでも適切に評価しやすいとのことで、是非使えるようになりたい。

from sklearn.metrics import matthews_corrcoef

mcc = matthews_corrcoef(cancer_y, cancer_y_pred)
print(mcc)

0.9132886202215396

3-2. 確率を予測値とする場合

AUC (Area Under the ROC Curve)

横軸に偽陽性率、縦軸に真陽性率をプロットしたROC曲線の、下部の面積。
左から順に予測値が高いレコードを並べ、実際に正例なら上に、負例なら横に進むプロットと考えればいい。
そのため、完全な予測のときROC直線は左上の天井に跳ね上がり、AUCは1となる。ランダムな予測なら対角線上をなぞる。
有名なGini係数は Gini = 2AUC - 1 と表されるため、AUCと線形。

import japanize_matplotlib
import matplotlib.pyplot as plt
from sklearn.metrics import roc_auc_score, roc_curve
%matplotlib inline

auc = roc_auc_score(cancer_y, cancer_y_pred_prob)
print(auc)

fpr, tpr, thresholds = roc_curve(cancer_y, cancer_y_pred_prob)
plt.plot(fpr, tpr, label='AUC={:.2f}'.format(auc))
plt.legend()
plt.xlabel('偽陽性率')
plt.ylabel('真陽性率')
plt.title('ROC曲線')
plt.show()

0.9946488029173934
hoge.png

logloss

$$
logloss = -\frac{1}{N} \sum_{i=1}^N(y_i {\log} p_i+(1-y_i){\log}(1-p_i))
$$
これもAUCと並んで有名。cross entropyとも呼ばれる。
真の値を予測している確率の対数をとり、符号を反転させているので低い方がいい(らしい)
正例である確率を低く見積もったのに正例である場合や、高く見積もったのに負例だった場合にペナルティを与える発想になっている。

from sklearn.metrics import log_loss

logloss = log_loss(cancer_y, cancer_y_pred_prob)
print(logloss)

0.09214591499092101

多クラス分類やレコメンデーションは実務経験がなく、ただ写経するだけになりそうだったので次の機会に譲る。
いつか書けるようになりたい。

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

【Udemy Python3入門+応用】 12.文字列のインデックスとスライス

※この記事はUdemyの
現役シリコンバレーエンジニアが教えるPython3入門+応用+アメリカのシリコンバレー流コードスタイル
の講座を受講した上での、自分用の授業ノートです。
講師の酒井潤さんから許可をいただいた上で公開しています。

■インデックス

◆インデックスの使い方
index
word = 'python'

print(word[0])
print(word[1])
print(word[2])
print(word[3])
print(word[4])
print(word[5])
result
p
y
t
h
o
n

変数に文字列を代入したとき、その文字列の任意の文字を指定できる。
Pythonにおいて、「1番目の文字」のインデックスは「0」であることに注意。

◆インデックスのエラー
index
word = 'python'

print(word[100])
result
    print(word[100])
IndexError: string index out of range

このとき、文字列の範囲を超えたインデックスを指定するとエラーとなる。

◆インデックスの「ー」
index
word = 'python'

print(word[-1])
print(word[-2])
print(word[-3])
result
n
o
h

[-1]などは先頭の文字([0])から更に左にいき、最後の文字に回るようにカウントされる。
よって、[-1]は最後の文字を表すインデックスとなる。


■スライス

◆スライスの使い方
slice
word = 'python'

print(word[0:2])
result
py

[:]により、始点と終点を指定し、その間の数を指定することができる。

|p|y|t|h|o|n|
0 1 2 3 4 5 6

インデックスは実際は文字と文字の間を表しており、このように考えるとわかりやすい。
(0と2の間は「py」となる。)

◆省略
slice
word = 'python'

print(word[:2])
print(word[2:])
print(word[:])
result
py
thon
python

始点を省略すると「最初から」になり、
終点を省略すると「最後まで」になる。
始点も終点も省略した場合は、文字列全体を指定することになる。


■文字列の任意の文字だけを入れ替える

◆「python」→「jython」
change_letter
word = 'python'
word = 'j' + word[1:]
print(word)
result
jython


■文字数をカウントする

count_letters
word = 'python'

n = len(word)
print(n)
result
6

文字列の"length"をlen()を用いてカウントすることができる。

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

お絵かきで Python を学ぶ(タートルグラフィックス)

タートルグラフィックスとは

教育用プログラミング言語の「LOGO」を元に画面上の亀をプログラミングで操作して図として描画するといったものです。
1960 年代に開発され、プログラムの結果を視覚ですぐに確認できることから、子供向けのプログラミング学習に用いられることもあります。

環境

  • PyCharm
  • Python3

タートルグラフィックスに触れる(対話型シェル)

まずは対話型シェルを用いて簡単にタートルグラフィックスに触れてみようと思います。

PyCharm のターミナル画面を開いて、Pythonと入力します。

$ Python

これで対話型シェルになったので、turtleを import します。

>>> import turtle

つぎにturtle.forward(100)と入力します。

>>> turtle.forward(100)

すると以下のようにグラフィックが立ち上がります。

image.png

これは矢印が前に 100 回進んだということを表しています。

矢印の向きを下に向けたい場合にはturtle.right(90)として 右に 90 度曲げます。

>>> turtle.right(90)

image.png

この状態で再度turtle.forward(100)入力すると下に進みます。

>>> turtle.forward(100)

image.png

先頭の矢印の形が味気ないと感じる場合にはshape.()で変更できます。
今回は亀に変更させてみたいと思います。

>>> turtle.shape('turtle')

image.png

color()でこの亀の色を変更させることも可能です。

>>> turtle.color('red')

image.png

この亀を隠したい場合にはhideturtle()を使用します。

>>> turtle.hideturtle()

image.png

隠した亀を再び表示させたい場合にはshowturtle()と入力します。

>>> turtle.showturtle()

image.png

直線以外にも円を描くこともできます。

>>> turtle.circle(100)

image.png

今、赤い線で描かれていますがこの線の色を変更させたい場合には
pencolor()を使用します。

同時に角度を少し変更させて進ませてみます。

>>> turtle.pencolor('blue')
>>> turtle.right(45)
>>> turtle.forward(100)

image.png

後ろに進むことも可能であり、backward()を使用します。

>>> turtle.backward(200)

image.png

一番最初の位置に戻りたい場合にはhome()を使用します。

>>> turtle.home()

image.png

すべてクリアにしたい場合にはclear()を使用します。

>>> turtle.clear()

image.png

終了させるときには最後にdone()と入力します。

>>> turtle.done()

ファイルを作成して実行させる

上記ではターミナルから対話型シェルで実行させたが、ここからは
適当な名前の Python ファイルを作成してファイルから実行させてみます。

ファイル上に以下の前進させるだけの簡単な処理を記述して実行させてみます。

import turtle

turtle.forward(100)

すると一瞬だけ表示されてすぐに消えてしまうことが確認できると思います。
ずっと表示するには最後の行にすべての処理が完了しましたと表す、done()
記述してあげます。

import turtle

turtle.forward(100)
turtle.done()

image.png

これでしっかりと表示されることが確認できると思います。

四角形

Python の for ループ使用して四角形を出力させてみます。
考え方としてはturtle.forward(100)の前進を 4 回繰り返し、
一度前進が完了したら 90 度に曲がる処理を加えるといったものである。

import turtle

turtle.color('red', 'yellow')

turtle.begin_fill()
for _ in range(4):
    turtle.forward(100)
    turtle.right(90)
turtle.end_fill()

turtle.done()

ファイル形式では処理の内容をturtle.begin_fill()turtle.end_fill()
括ってあげます。

実行してあげると線が赤で中が黄色の四角形が表示されます。

image.png

星形

星形のプログラムですが、四角形のプログラムと構成がほとんど一緒になります。
変更箇所としては前進する回数が 5 回になるのと角度が360 / 5 * 2になります。

import turtle

turtle.color('red', 'yellow')

turtle.begin_fill()
for _ in range(5):
    turtle.forward(100)
    turtle.right(360 / 5 * 2)
turtle.end_fill()

turtle.done()

image.png

星を三回作成させ、一度進むたびに前進する距離を少しずつ増やしてみる。

import turtle

turtle.color('red', 'yellow')

turtle.begin_fill()
for i in range(5 * 3):
    turtle.forward(100 + i * 10)
    turtle.right(360 / 5 * 2)
turtle.end_fill()

turtle.done()

すると以下のように綺麗な図形ができる。
このようにループの処理を色々としてあげると違った形になり楽しむことができる。

image.png

三角形

三角形も同様に三回前進して、一度前進したあとに角度を変えるといった処理になります。

import turtle

turtle.color('red', 'yellow')

turtle.begin_fill()
for i in range(3):
    turtle.forward(100)
    turtle.left(360 / 3)
turtle.end_fill()

turtle.done()

実行してみると三角形が表示されます。

image.png

星形でも行ったように一度前進したあとの角度を少しずらしてあげるとどうなるか見てみます。

import turtle

turtle.begin_fill()
for i in range(200):
    turtle.forward(200)
    turtle.left(360 / 3 + 10)
turtle.end_fill()

turtle.done()

このように星型の時と同様に綺麗な図形になります。

image.png

おわり

ざっくりでしたが以上が扱い方の説明になります。

このタートルグラフィックスを使用することで自分のプログラミングが
簡単に可視化できるため子供はもちろん大人も楽しんでプログラミングを学習することができると思います。

最後に以下のような木を作成することも可能です。

import turtle as t
foo = t.Turtle()
foo.left(90)
foo.speed(10)

def draw(l):
    if(l<10):
        return
    else:
        foo.forward(l)
        foo.left(30)
        draw(3*l/4)
        foo.right(60)
        draw(3*l/4)
        foo.left(30)
        foo.backward(l)

draw(100)

image.png

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

はじめてのDjango起動

■環境

 windows10
 Pycharm Community 2019.3
 Python3.7

■前提条件

 Djangoプロジェクト、アプリケーション作成していることを前提。以下参照
 ターミナルでDjangoアプリケーション作成まで

■Djangoの起動

プロジェクトのディレクトリに移動して、runserver

C:\python_project\venv_python_dev\Private_Diary_project>python manage.py runserver

ブラウザで http://127.0.0.1:8000/
つながりました!

image.png

ターミナルで、「 CTL+C 」 でWebサーバ停止

以上

 

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

気象データをもとに「天気図っぽい前線」を機械学習で描いてみる(5)

気象データをもとに「天気図っぽい前線」を機械学習で描いてみる(5)機械学習編 Automatic Front Detection in Weather Data

1. 「天気図っぽい前線」の学習

1.1 「天気図っぽい前線」とは?

天気図の前線は、防災的な観点からか、日本に影響のあるような部分を中心に解析されているようです。
他の国の天気図を見ると、日本の天気図では描かれないような前線が登場してたりします。
例えば下記のコラムではヨーロッパの気象図に登場する前線についての記載があります。
衛星でも見えない、「隠れた」前線の話

これらから言えるのは、前線解析は必ずしも一意に決まるものでなく、機関ごとの流儀や意図がありそうということです。ということは、私が試みている機械学習による前線描画は、気象データから日本の天気図っぽい前線を描画させるもの、と言えるでしょう。
学習精度をどんどん高めて気象庁のような日本の天気図っぽい前線解析を完璧に描画できるようになると、気象実況解析のエキスパートシステムとなるものかもしれません。

永澤義嗣氏が著された気象予報と防災ー予報官の道(中公新書)には、「天気図を三千枚書いて一人前」という記載があります。今回学習に使用しているデータはおよそ2000枚です。Deep Learning予報官として、一人前というにはまだ努力不足です。

ところで、この機械学習は天気図を生成しているものの、天気予報をしているものではありません。
天気予報は現在から将来を予測するもので、前線描画は現在から現在のデータへの変換です。

2. 機械学習の流れ

2.1 何をやるのか?

CNNによって画像から画像を生成するものです。生成画像のピクセルごとに赤・青・白どの色になるべきか、確率を計算させて確率の高かった色をピクセルにセットするという手法です。

この手法は下記を参考にしています。
Exascale Deep Learning for Climate Analytics
Thorsten Kurth, Sean Treichler, Joshua Romero, Mayur Mudigonda, Nathan Luehr, Everett Phillips, Ankur Mahesh, Michael Matheson, Jack Deslippe, Massimiliano Fatica, Prabhat, Michael Houston
arXiv:1810.01993 [cs.DC]

2018年にテキサス州ダラスで開催されたSC18というスパコン分野の国際学会において、ACM Gordon Bell Prizeを受賞した論文です。私は幸いにも、SC18へ出張していて受賞講演を聴講する機会に恵まれ、公私混同しながらとても感慨深かった思い出があります。

2.2 機械学習の流れ

ニューラルネットワークによる機械学習の流れをまとめます。
(1) 入力画像を作る
気象データをダウンロードして可視化する。
6種類のカラー画像を用意しました(第2回

(2) 教師画像を作る
「速報天気図」から色をもとに前線要素だけを抜き出した画像を作る(第3回
教師画像を増やすために白黒天気図のカラー化なんてのもやりました(第4回)

(3) CNNのあれこれ
・入力データ:並べ替え
CNNの入力は、6種類の入力画像(channel数は3)をconcatenateした18channelのテンソルになります。
CNNのミニバッチの中に、近接した時間のデータばかりになると学習時に偏ってしまうので、入力データと教師画像の組を時間的にランダムに並べ替えておきます。

・入力データ:one-hotベクトル
CNNは各ピクセルが赤、青、白のそれぞれに該当する確率を計算するように作成するので、RGBである教師画像については、正解が1、その他が0となった配列に変換しておきます。つまり、白なら(1,0,0)、赤なら(0,1,0)、青なら(0,0,1)というデータを作ります。

・出力データ
例えばCNNはあるピクセルの値としては、例えば(0.1, 0.7, 0.2)を出力します。この場合はこのピクセルは赤だ、として絵を作ります。

・ニューラルネットワーク
CNNはU-Netを意識した分岐経路を有するものを作成します。

・ロス関数
categorical_cross_entropyを用います。これにより赤、青、白の確率を計算します。

(4) 学習させる!
いよいよCNNを学習させます。
今回、2017年1月から2019年10月まで、各日から2枚(6時UTCと18時UTCのデータ)を使用して学習させています。教師データは赤、青、白のいずれかが1になっているone-hotベクトルですから学習が進むにつれて前線要素のカラーを再現できるようになっていきます。

私のMac miniは数日の間、触れないくらいの高温になります。
Mac miniのうち、最もCPUを酷使されている個体のひとつではないかと思っており、CPU冥利に尽きるというものでしょう。

(5) 予測させる!(初見データに前線を描かせる)
学習が収束したところで、学習済ネットワークを使って、初見データの前線を描画します。今回は初見データとして2019年11月から2020年1月までのデータに前線を描画させました。

3. 何はともあれ結果

3.1 学習データ

学習データの一例です。
速報天気図の前線ととほぼ同じような位置に該当する前線要素が生成できるまで収束しているように見えます。

左上から右へ、
出力結果:生成した前線要素(地上気圧の可視化画像へ重ね合わせたもの)
教師画像:速報天気図
入力:相当温位850hPa
入力:気温・気圧・風(地上)

左下から右へ、
入力:気温・高度・風(850hPa)
入力:湿数(700hPa)
入力:鉛直流(700hPa)
入力:気温・高度・風(500hPa)

tst_chart_3cmprd2019_041606.v8.256.png

3.2 生成データ(初見データ)

3.1で、2017年1月から2019年10月のデータによって学習させたネットワークを使って、前線生成させました。

うまく生成できている例

日本の東の海上に低気圧があり、寒冷前線が南西に延びています。
ニューラルネットワークでもこの寒冷前線を生成できています。また、温暖前線も途切れながらも生成されています。

tst_chart_3cmprd2020_010818.v8.256.png

かなりダメな例

速報天気図では、千島列島近海の低気圧から南西に寒冷前線が延びています。また、北緯30度線にほぼ沿って停滞前線が解析されています。
これに対して、ニューラルネットワークではどちらの前線もほぼ生成できていません。

tst_chart_3cmprd2020_012412.v8.256.png

どのデータが寄与しているのか?

パッとデータを眺めていると、入力データのうち、850hPaの相当温位で、等値線が混み合っている領域に前線が引かれているようです。ダメな例では混み合い具合が少し弱いようにも見えますがどうでしょう?

今回のCNNのフィルタサイズは3x3です。もう少し大きなフィルタを使ってみるか、画像を縮小してみるということも手かもしれません。

さて、2020年1月分の前線生成結果です。こんな感じのデータが生成されるんです、というだけのアニメです。

ww_qiita_chart202001DDHH.v8.256.gif

3.3 学習の進行状況

LossおよびAccuracyの遷移です。このデータを取り直すために再度学習させた800エポックまでの状況です。

200エポックごとに学習を止めて再スタートさせているのですが、その際に入力画像のランダム化(後述)を行うことで学習対象のデータが一部入れ替わったりするので、一度LossやAccuracyが悪くなります。

lossAndAccu.png

先ほどのうまく生成できた例についての生成画像の変化です。
左から、200、400、600、800エポック学習時点のパラメタでの生成画像、最終的に1500エポック以上
実行したときの生成画像。
前線を描くべき場所に前線を引くこと自体は早期にできるようになり、前線記号(山型のマークなど)の精度が上がってきているように見えます。

ofile_qiita_2020010818.v8.256.png

ダメな例の方です。
ん?学習が浅い方のパラメタで生成した方がまだマシだったかもしれない?

ofile_qiita_2020012412.v8.256.png

学習時間ですが、私のMac miniで1エポックで580秒前後でした。これでマル4日くらい実行し続けでした。
GPUを持っていたりするともっと先まで実行する気になるのかなあ。

3.4 ところで・・・

まだ前線がくっきり間違えずにできるところまで来ていませんが、データ例を増やして計算リソースを増やせば、かなり良いところまでいけるのではないか、という感触です。
それではこれは何かの役に立つのか?
前線解析は気象庁で実施している結果を入手できることができますので、プロにお任せした方がお得です。

意味があるとすると、数値予報結果や気候計算結果に自動的に前線を描く、ということでしょうか?

ww.gif

例えば上記は2020年2月18日12時UTCを初期値としたGSMの予報計算結果に対して前線を描いたものです。予想天気図は24時間後、48時間後という間隔での発表ですので、GSMの6時間ごとの結果に対して前線を描く、ということを行っています。

4. TL;DR CNNのあれこれ

以下はCNN本体の説明と、多少ハマった部分のTipsです。

4.1 入力データ

4.1.1 入力画像・教師画像の組をランダムに並べ替える

入力画像と教師画像は、読み込んだ直後には日付順に並んでいます。
このまま学習させると、ミニバッチの各回は時間的に近接した気象状況となってしまうことが想定されるので、異なる気象状況が混じったバッチとするために、リストをランダムにソートします。

randomize.py
# i_datalist_train 年月日時順に並んだn_datalist個の入力用気象画像データ
# t_datalist_train 年月日時順に並んだn_datalist個の教師画像データ

# 0からn_datalist-1までの数列からランダムにn_datalistのインデックスを生成して
# ループ処理する

for c_list in random.sample(range(n_datalist), n_datalist ):

  t_datalist_train.append(t_datalist[c_list])
  # 並び替え済教師画像リスト

  i_datalist_train.append(i_datalist[c_list])
  # 並び替え済入力画像リスト

4.1.2 前線画像のone-hot化

前回までで作成した教師画像用の前線要素画像は、各ピクセルが赤、青、白のいずれかとなっています。
ほとんどの面積を占めるのバックグラウンドが白色、寒冷前線と高気圧記号が青色、温暖前線と閉塞前線および低気圧記号が赤色です。
CNNにより画像を生成して教師画像との間でcategorical_cross_entropyによる誤差関数を計算しますので、この前線要素画像データをone-hotベクトル化しておきます。to_categoricalというnumpyのメソッドを利用するために、RGB配列をいったん0,1,2のいずれかをとる配列に変換します。その後にto_categoricalを用いてone-hotベクトルに変換します。そのあたりを実行しているのが下記のソースです。

one-hot.py
    # t_img        読み込んだ前線画像ファイル
    # img_size     t_imgの画像サイズ

    t_data = np.empty((img_size[1],img_size[0]))
    # t_data one-hot化するために0,1,2の3値化するための配列を用意

    # ピクセル毎に赤は1, 青は2, それ以外は0 とセットする
    for x in range(img_size[1]):
      for y in range(img_size[0]):
        r,g,b = t_img.getpixel((y,x))
        # r g bにそれぞれRGB値を格納する
        if(r>g+20):
          if(r>b+20):
            t_data[x,y]=1 # 赤色ピクセル
          else:
            if(b>g+20):
              t_data[x,y]=2 # 青色ピクセル
            else:
              t_data[x,y]=0 # 白色ピクセル
        else:
          if(b>r+20):
            if(b>g+20):
              t_data[x,y]=2 # 青色ピクセル
            else:
              t_data[x,y]=0 # 白色ピクセル
          else:
            t_data[x,y]=0 # 白色ピクセル

    # t_data から、3要素のone-hot vector配列 T_data に変換
    T_data = np_utils.to_categorical(t_data[:,:],3)

4.2 出力データ

予測させた出力データから、最も確率が大きいものを採用して3色画像に変換します。
データを出力すると下記のように、ひとつの値が大きくなっています。その値に該当する色をピクセルに配置します。

w_array
(256, 256, 3)
[[[9.99969959e-01 1.26371087e-05 1.73822737e-05]
  [1.00000000e+00 8.79307649e-09 8.33461922e-09]
  [1.00000000e+00 1.22459204e-12 8.95228910e-16]
  ...
  [9.99985695e-01 6.48013793e-06 7.86928376e-06]
  [9.99960303e-01 8.51386540e-06 3.12020056e-05]
  [9.99833941e-01 2.61777150e-05 1.39806682e-04]]

 [[9.99999881e-01 8.55169304e-08 1.83308195e-08]
  [1.00000000e+00 9.66997732e-11 1.11044485e-12]
  [1.00000000e+00 4.26908814e-16 1.04265986e-22]
  ...

ピクセルへの色配置のためのソースです。w_arrayは、(R,G,B)の確率値ベクトルが緯度x経度に並んでいます。これをもとに、画像データmask1を作成します。

rasterize.py
  # w_array 予測させた出力データのNd_array配列の1画面分が格納されている

  s_img = array_to_img(w_array[:,:,:].reshape(i_dmlat,i_dmlon,3))
  # s_img   画像化したデータ

  new_img_size = [w_array.shape[1], w_array.shape[0]]
  mask1 = Image.new('RGB', new_img_size)
  # mask1   出力するための配列

  for x in range(new_img_size[1]):
    for y in range(new_img_size[0]):

      # w_arrayの第3要素をw1/r1/b1に格納する
      w1 = w_array[x,y,0]
      r1 = w_array[x,y,1]
      b1 = w_array[x,y,2]

      if(r1>w1):
        if(r1>b1):  # r1>w1, r1>b1
          r,g,b=255,0,0
          # r1が最大の場合はRGBとして赤色をセット
        else: # r1=<w1
          if(b1>w1):  # r1=<w1<b1
            r,g,b=0,0,255
            # b1 が最大の場合はRGBとして青色をセット
          else: # r1=<w1, b1=<w1
            r,g,b=255,255,255
            # w1 が最大の場合はRGBとして白色をセット
      else: # w1>=r1
        if(b1>r1):
          if(b1>w1): # b1>w1>r1
            r,g,b=0,0,255
            # b1 が最大の場合はRGBとして青色をセット
          else: # w1>=b1>r1
            r,g,b=255,255,255
            # w1 が最大の場合はRGBとして白色をセット
        else: # w1>=r1 w>=b1
          r,g,b=255,255,255
          # w1 が最大の場合はRGBとして白色をセット

      mask1.putpixel((y,x),(r,g,b))
      # RBGの値をmask1にセット

4.3 ニューラルネットワーク(Convolutional Neural Network)

4.3.1 ネットワーク構造

CNNの構造は、U-Netのような分岐を持たせているもので自作です。
入力が18x256x256のテンソル、出力が3x256x256のテンソルです。
画像サイズはConv2Dのstride=(2,2)パラメタの指定により半分になっていき、最終的に120x16x16になります。
通常のU-Netでは、画像サイズを半分にする際にチャネル数を倍にするのですが、今回はメモリ量の都合で、きれいに倍にはしていません。

GSMtoWChart045.png

4.3.2 Kerasによるネットワーク定義

ネットワーク定義部分のPython-Kerasのソースは下記です。
Kerasのネットワークの定義のやり方としては、Functional APIを用いて各レイヤの出力を次レイヤまたは先の方の結合部分に繋いでいくものです。

cnv2dtra = concatenate([cnv2dtra , cnv2d14 ], axis=1)
などの部分が,cnv2dtraに分岐しきたcnv2d14を第1要素(チャンネル)で結合させている部分です。

cnn.py
#- Neural Network Definition

num_hidden1 = 18
num_hidden2 = 32
num_hidden3 = 48
num_hidden3a = 72
num_hidden3b = 120
num_hidden4 = 128
num_hidden5 = 16
num_hidden6 = 16
num_itter = int(_num_itter) # 学習回数

#--- seting Neural Network using Keras

in_ch = 18 # 入力画像 RGB x 6種類 のチャンネル数

cnn_input = Input(shape=(in_ch, i_dmlat, i_dmlon))
# i_dmlat, i_dmlon 画像の緯度方向、経度方向の画素数

# 1st hidden layer

cnv2d1 = Conv2D(num_hidden1, data_format='channels_first', kernel_size=(3,3), dilation_rate=(3,3), activation='relu', padding='same')(cnn_input)
cnv2d2 = Conv2D(num_hidden1, data_format='channels_first', kernel_size=(3,3), dilation_rate=(3,3), activation='relu', padding='same')(cnv2d1)
cnv2d4 = Conv2D(num_hidden1, data_format='channels_first', strides=(2,2) , kernel_size=(3,3), activation='relu', padding='same')(cnv2d2)

# 2nd hidden layer

cnv2d5 = Conv2D(num_hidden2, data_format='channels_first', kernel_size=(3,3), dilation_rate=(3,3), activation='relu', padding='same')(cnv2d4)
cnv2d6 = Conv2D(num_hidden2, data_format='channels_first', kernel_size=(3,3), dilation_rate=(3,3), activation='relu', padding='same')(cnv2d5)
cnv2d8 = Conv2D(num_hidden2, data_format='channels_first', strides=(2,2) , kernel_size=(3,3), activation='relu', padding='same')(cnv2d6)

# 3rd hidden layer

cnv2d9 = Conv2D(num_hidden3, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same')(cnv2d8)
cnv2d10 = Conv2D(num_hidden3, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same')(cnv2d9)
cnv2d12 = Conv2D(num_hidden3, data_format='channels_first', strides=(2,2) , kernel_size=(3,3), activation='relu', padding='same')(cnv2d10)

# 4th hidden layer

cnv2d13 = Conv2D(num_hidden3a, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same')(cnv2d12)
cnv2d14 = Conv2D(num_hidden3a, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same')(cnv2d13)
cnv2d16 = Conv2D(num_hidden3a, data_format='channels_first', strides=(2,2) , kernel_size=(3,3), activation='relu', padding='same')(cnv2d14)

cnv2d17 = Conv2D(num_hidden3b, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same')(cnv2d16)
cnv2d19 = Conv2D(num_hidden3b, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same')(cnv2d17)

#--- decode start

cnv2dtra = Conv2DTranspose(num_hidden3a, data_format='channels_first', kernel_size=(3,3), strides=(2,2), activation='relu', padding='same')(cnv2d19)

cnv2dtra = concatenate([cnv2dtra , cnv2d14 ], axis=1)
# 分岐結合

cnv2dtrb = Conv2D(num_hidden3a, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same')(cnv2dtra)
cnv2dtr1 = Conv2DTranspose(num_hidden3, data_format='channels_first', kernel_size=(3,3), strides=(2,2), activation='relu', padding='same')(cnv2dtrb)

cnv2dtr1 = concatenate([cnv2dtr1 , cnv2d10 ], axis=1)
# 分岐結合

cnv2dtr2 = Conv2D(num_hidden3, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same')(cnv2dtr1)
cnv2dtr4 = Conv2DTranspose(num_hidden2, data_format='channels_first', kernel_size=(3,3), strides=(2,2), activation='relu', padding='same')(cnv2dtr2)

cnv2dtr4 = concatenate([cnv2dtr4, cnv2d6], axis=1)
# 分岐結合

cnv2dtr5 = Conv2D(num_hidden2, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same')(cnv2dtr4)
cnv2dtr7 = Conv2DTranspose(num_hidden1, data_format='channels_first', kernel_size=(3,3), strides=(2,2), activation='relu', padding='same')(cnv2dtr5)

cnv2dtr7 = concatenate([cnv2dtr7, cnv2d2], axis=1)
# 分岐結合

cnv2dtr8 = Conv2D(num_hidden1, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same')(cnv2dtr7)
cnv2dtr9 = Conv2D(num_hidden1, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same')(cnv2dtr8)

cnv2dtr10 = Conv2D(3, data_format='channels_first', kernel_size=(1,1), activation=softMaxAxis , padding='same')(cnv2dtr8)
# 最終的に1枚の3値の画像にする


NN_1 = Model( input=cnn_input , output=cnv2dtr10 )
# 入力はcnn_inputで最後のcnv2dtr10を出力としてネットワークを構築

NN_1.compile(optimizer='adam', loss='categorical_crossentropy' , metrics=['accuracy'])
# ロス関数 categorical_crossentropy

4.4 最終層のsoftmax

最終層では、各ピクセルについて赤青白をsoftmaxで計算します。
標準のsoftmaxでは配列の一部を指定することができないので、softMaxAxisという関数を定義しています。
この方法は
Stack Over flow:"How to specify the axis when using the softmax activation in a Keras layer?"
を参考にしています。

softMaxAxis.py
def softMaxAxis(x):
        return softmax(x,axis=1)

このとき、モデルを保存して再読み込みさせるときに、カスタムオブジェクトの名前を教えてあげる必要があるようです。

load_model
NN_1 = load_model( paramfile , custom_objects={'softMaxAxis': softMaxAxis })

4.5 生成画像と等圧線図の重ね合わせ

ImageMagickを利用します。
convert -compose multiply が重ね合わせを行うコマンドです。
-gravity center -geometry +0+0 により2つの画像の中心を縦横のオフセット無しで合わせることを指定します。

Image_Overlay.sh
# m_file 生成した前線画像(PNG)
# s_file 等圧線図(PNG)
composite -gravity center -geometry +0+0 -compose multiply  ${m_file} ${s_file} out.png

5.まとめ

気象データ可視化画像を認識して、「日本気象庁の解析するような前線画像」を生成するニューラルネットワークを作成し、前線の自動描画をやってみました。

この手法は前線描画に限らず、地図上にプロットされる何らかのデータで気象と関連するものであれば有効なのではないかと思っています。
一方で、当たりハズレの評価が結構難しい気がします。機械的にピクセル値のズレを見ても違う気がしますし、前線という形を捉えていることの評価をどうするか。気象予報士試験のように、人が採点するのが案外正しいかもしれません。

現在、MSMの気象データから合成レーダの画像を生成するということに取り組んでいて、それについては正解データとの評価も検討しております。
こちらも近日公開しようかと思っています。

今回Qiita初投稿でしたが、5回にわたって長文を投稿してしまいした。
読んで頂いた皆様どうもありがとうございました。

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

POJ 2386 python DFS

POJ2386 LakeCounting をpythonで

DFS(深さ優先探索)を学習中。
蟻本だけでは分からなかったので備忘録として。
.replace('.','0').replace('W','1')はしなくてもよい。

2386.py
n,m=map(int,input().split())
a=[list(map(int,list(input().replace('.','0').replace('W','1')))) for i in range(n)]

def dfs(x,y):
  a[x][y]=0
  for dx in [-1,0,1]:
    for dy in [-1,0,1]:
      nx=x+dx
      ny=y+dy
      if 0<=nx<n and 0<=ny<m and a[nx][ny]==1:
          dfs(nx,ny)

cnt=0
for i in range(n):
  for j in range(m):
    if a[i][j]==1:
      dfs(i,j)
      cnt+=1
print(cnt)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

POJ 2386 をpythonで解く

POJ2386 LakeCounting をpythonで解く

DFS(深さ優先探索)を学習中。
蟻本だけでは分からなかったので備忘録として。
.replace('.','0').replace('W','1')はしなくてもよい。

2386.py
n,m=map(int,input().split())
a=[list(map(int,list(input().replace('.','0').replace('W','1')))) for i in range(n)]

def dfs(x,y):
  a[x][y]=0
  for dx in [-1,0,1]:
    for dy in [-1,0,1]:
      nx=x+dx
      ny=y+dy
      if 0<=nx<n and 0<=ny<m and a[nx][ny]==1:
          dfs(nx,ny)

cnt=0
for i in range(n):
  for j in range(m):
    if a[i][j]==1:
      dfs(i,j)
      cnt+=1
print(cnt)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Udemy Python3入門+応用】 11.文字列

※この記事はUdemyの
現役シリコンバレーエンジニアが教えるPython3入門+応用+アメリカのシリコンバレー流コードスタイル
の講座を受講した上での、自分用の授業ノートです。
講師の酒井潤さんから許可をいただいた上で公開しています。

■基本の文字列

print_standard
print('hello')
print("hello")
result
hello
hello

print()内で文字列を指定する場合は、''でも""でもよい。


■文字列内に「'」がある場合

""で括る
print_'
print("I don't know.")
result
I don't know.

""で括った場合は、きちんと出力される。

''で括るとエラーになる
print_'
print('I don't know.')
result
    print('I don't know.')
                 ^
SyntaxError: invalid syntax

''で括った場合、文字列中の「'」が括りの終わりと判定されてしまい、エラーとなってしまう。

\を使ってエラーを回避する
print_'
print('I don\'t know.')
result
I don't know.

文字列中の'の前に\を入れてやることで、括りとしての判定を回避できる。

print_'
print('say "I don\'t know."')
print("say \"I don't know.\"")
result
say "I don't know."
say "I don't know."

応用。


■文字列内に「\n」がある場合

◆改行の\n
print_\n
print('Hello. \nHow are you?')
result
Hello.
How are you?

\nが「改行」となるのは以前扱った。

◆誤って\nと判定される
print_\n
print('C:\name\name')
result
C:
ame
ame

「C:\name\name」を表示させたかったのに、\nの部分が改行判定されてしまった。

rを用いた判定回避
print_\n
print(r'C:\name\name')
result
C:\name\name

その際は、"raw"の頭文字であるrを文字列の先頭につけることで、文字列をそのままprintすることができる。


■改行

new_line
print("""
line1
line2
line3
""")
result
line1
line2
line3

上のように""" """を用いることで、\nを使って一行で記述せずとも、見やすいコードが書ける。

new_line
print('#######')
print("""
line1
line2
line3
""")
print('#######')
result
#######

line1
line2
line3

#######

ただし、この書き方だと文字列の上下に空白行が挿入されることになる。

new_line
print('#######')
print("""\
line1
line2
line3\
""")
print('#######')
result
#######
line1
line2
line3
#######

\をこのように入れてあげることで、「次に書くコードは次の行から始めてね」という意味になる。


■演算子を用いる

◆文字列と演算子
operator
print('Hi.' * 3 + 'Mike.')
print('Py' + 'thon')
print('Py''thon')
result
Hi.Hi.Hi.Mike.
Python
Python

文字列に対し、演算子を組み合わせることもできる。
単純に文字列同士を接続する場合、+を記述しなくても接続してprintされる。

◆変数と文字列の並列
operator
prefix = ('Py')

print(prefix'thon')
result
    print(prefix'thon')
                     ^
SyntaxError: invalid syntax

ただし、変数に文字列を代入した場合は+を省略することはできない。

operator
prefix = ('Py')

print(prefix + 'thon')
result
Python

+できちんと接続することができた。

+の省略が好まれる場合
operator
print('aaaaaaaaaaaaaaaaaaaaaaaaaaa'
      'bbbbbbbbbbbbbbbbbbbbbbbbbbb')
result
aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb

長い文字列をつなげて表示させる場合は、このように記述するとコードが見やすくなる。
(1行で書くと画面サイズに収まりきらない場合があったりして見にくい。)

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