- 投稿日:2021-10-26T23:54:06+09:00
Pythonのdatetimeはawareとnaiveだよ(nativeじゃないよ)
Python datetime pythonの標準ライブラリdatetimneのdatetimeオブジェクトは時刻を扱う。 datetimeにはaware(タイムゾーン情報有)とnaive(タイムゾーン情報無)がある。 from datetime import datetime from dateutil import tz print(datetime.today()) # 2021-10-26 23:46:50.725275 print(datetime.today().tzinfo) # None つまりnaive print(datetime(2021, 12, 31, 19, 55, 40, 0, tz.gettz('Asia/Tokyo'))) # 2021-12-31 19:55:40+09:00 print(datetime(2021, 12, 31, 19, 55, 40, 0, tz.gettz('Asia/Tokyo')).tzinfo) # tzfile('Japan') つまりaware WEBで色んな記事を見ているとnaiveのことをnativeと誤記しているものが散見される。 aware(気づいている)の対義語だからnaive(ものを知らない)。 公式ドキュメント にも書いてある。 Aware オブジェクトと Naive オブジェクト¶ 日時のオブジェクトは、それらがタイムゾーンの情報を含んでいるかどうかによって "aware" あるいは "naive" に分類されます。 間違えないようにしましょう。 P.S. 古い公式ドキュメント だと公式がnaiveと書くべきところをnativeと間違えている。
- 投稿日:2021-10-26T23:52:32+09:00
pythonでOSコマンドを実行する(subprocess.run利用)
PyhonでOSコマンドを実行する方法はバージョンによって 色々あるのですが、今回はsubprocess.runの利用方法をまとめてみます。 Pythonは3.8を前提としています。 基本 subprocess.runは引数のコマンドを同期処理で実行します。 コマンドをそのまま文字列として実行したい場合は、「shell=True」を指定します。 可読性は高くなりますが脆弱性にもつながるので利用には要注意です。 import subprocess subprocess.run(['ls', '-al']) command = 'ls -al' ret = subprocess.run(command, shell=True) print(ret) 上記の戻り値の出力は以下のような簡単なものになります。 CompletedProcess(args='ls -al', returncode=0) 戻り値の確認と標準出力、エラー出力のキャプチャ 「capture_output=True」のオプションを利用すると標準出力、エラー出力を キャプチャすることが出来ます。(Python3.7以降) 戻り値の「ret.stdout」「ret.stderr」で参照に可能になりますが この戻り値はバイトデータなのでdecodeが必要です。 「text=True」のオプションをつけるとdocodeした結果が格納されます。 戻り値は「ret.returncode」で参照が可能です。 command = 'ls -al' ret = subprocess.run(command, shell=True, capture_output=True, text=True) print(ret.returncode) print('stdout:' + ret.stdout) print('stderr:' + ret.stderr) 例外処理 コマンドの実行結果を1つ1つチェックする代わりに例外処理を利用することも出来ます。 「check=True」のオプションを指定すると実行結果が0以外の場合に 「subprocess.CalledProcessError」例外がスローされるようになります。 例外オブジェクトの「stderr」でエラー出力の参照、 「cmd」で実行コマンドを参照することが可能です。 import subprocess try: command = 'mkdir @/@' ret = subprocess.run(command, shell=True, capture_output=True, text=True, check=True) print(ret.returncode) print('stdout:' + ret.stdout) except subprocess.CalledProcessError as cpe: print('returncode:' + str(cpe.returncode)) print('stderr:' + cpe.stderr) print('cmd:' + cpe.cmd) raise cpe まとめ 今までの内容を本番実装に近い形でまとめてみます。 subprocess.runは関数化して実行コマンドをログで残すようにしました。 import subprocess import traceback import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def process_run(_command): logger.info('subprocess.run: ' + _command) ret = subprocess.run(_command, shell=True, capture_output=True, text=True, check=True) return ret def main_func(): try: process_run('ls -al') except subprocess.CalledProcessError as cpe: logger.error('subprocess err. ' + cpe.stderr + '\n' + 'returncode: ' + str(cpe.returncode) + '\n' + 'cmd: ' + cpe.cmd) raise cpe except Exception as e: traceback.format_exc() raise e if __name__ == '__main__': main_func() 以上
- 投稿日:2021-10-26T23:19:49+09:00
PFRLでスーパーマリオ1-1をクリアするまで
以前はStableBaselinesを使っていましたが、ニューラルネットワークの構造をいじりにくいことやアルゴリズムに手を加えにくいと思っていました。ネットワークにAttentionを加えるだけでも一苦労でした。 そもそもTensorFlow向けであった点も使いづらかったので、PyTorch向けの深層強化学習ライブラリを探していたところPFRLというライブラリを見つけました。 Hello World代わりにスーパーマリオブラザーズ1-1をクリアしてみようと思います。 まずネットワークのを定義します。 class Network(nn.Module): def __init__(self, hidden_dim, space_shape, n_actions): super(Network, self).__init__() h, w, c = space_shape self.hidden_dim = hidden_dim self.feature_extract = nn.Sequential( nn.Conv2d(c, hidden_dim, 3, 1, 1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(hidden_dim, hidden_dim, 3, 1, 1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(hidden_dim, hidden_dim, 3, 1, 1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(hidden_dim, hidden_dim, 3, 1, 1), nn.ReLU() ) flatten_dim = hidden_dim * (h // 8) * (w // 8) self.flatten_dim = flatten_dim self.head = pfrl.nn.Branched( nn.Sequential( nn.Linear(flatten_dim, n_actions), SoftmaxCategoricalHead() ), nn.Linear(flatten_dim, 1) ) def forward(self, image): b, _, _, _ = image.shape feature = self.feature_extract(image) feature = feature.view(b, -1) actions, values = self.head(feature) return actions, values 出力用のNNとOptimizerをagents.PPOに渡します。 agentsには様々な強化学習のアルゴリズムが用意されています。 ここではPPO(Proximal Policy Optimization)を使います。 opt = torch.optim.Adam(network.parameters(), lr=3e-4, eps=1e-8) agent = agents.PPO( network, opt, gamma=0.9, gpu=0, update_interval=update_interval, minibatch_size=32, epochs=10, clip_eps=0.2, clip_eps_vf=None, standardize_advantages=True, entropy_coef=0.01, max_grad_norm=0.5, ) experimentsにはagentとenvを渡します。 envについてはStable Baselinesのときと同じなので省略します experiments.train_agent_batch_with_evaluation( agent=agent, env=env, eval_env=env_eval, steps=TOTAL_STEPS, eval_n_steps=None, eval_n_episodes=10, eval_interval=100000, outdir=output_dir, save_best_so_far_agent=True, ) 10000ステップごとに16エピソードをプレイした平均報酬和を以下に示します。 右に進むことができた距離の最大値を更新できた場合に報酬を与えており、報酬和の最大値は316となるようにしています。 2000000ステップでかなり学習できています。 プレイ動画はこんな感じ。
- 投稿日:2021-10-26T22:04:02+09:00
PythonからMecab (少しだけ使いやすく)
はじめに Mecabとは形態素解析エンジンで、要するに文章を解析するソフトである。(僕が言うまでもないか。) googleやyahooにも使われてるらしく、昨日は素晴らしいの一言である。そして今回は、mecabをpythonから使いやすくしたい。python-mecab というライブラリがあって調べると重要なもののようだが、素人にはよくわからない。使いやすくなってるようには見えないので一応書いてみる。 Mecabのホームページ コード mecab.py import subprocess as sp import os import sys import random if "nt" in sys.builtin_module_names: ENCODING="sjis" TYPE_COMMAND="type" else: ENCODING="utf8" TYPE_COMMAND="cat" def tmpf(): fname="__mecab_tmp{0}.txt".format(random.randint(4545,46494)) return fname if os.path.exists(fname) else tmp() class Mecab: def __init__(self,text): self.text=text def __call__(self): return Mecab.__analize__(self.text) @staticmethod def __analize__(text): tmp=tmpf() with open(tmp,"wb") as f: f.write(text.encode(ENCODING,"ignore")) try: for line in sp.check_output("type {0} {1} | mecab".format(TYPE_COMMAND,tmp),shell=True).decode(ENCODING,"ignore").split("\n"): data=line.rstrip().split("\t",1) if not data[0]: pass elif data[0]=="EOS\r": yield ("EOS",None) else: data__=data[1].split(",") yield (data[0],data__) except: pass os.remove(tmp) @staticmethod def __getDataAsSentence__(text): #res list include data sentence=list() for word,data in Mecab.__analize__(text): if word=="EOS" or (data and "\\xe3\\x80\\x82" in str(word.encode())):#str(data[1].encode())=="b\'\\xe5\\x8f\\xa5\\xe7\\x82\\xb9\'"): #KUTEN yield sentence sentence=list() else: sentence.append((word,data)) @staticmethod def getDataAsSentence(text): for sentence in Mecab.__getDataAsSentence__(text): yield "".join(map(lambda a:a[0],sentence)) @staticmethod def getWords(text): for word,data in Mecab.__analize__(text): if word=="EOS" or (data and "\\xe3\\x80\\x82" in str(word.encode())): continue yield word 少し複雑にしてしまったが、ご愛敬。 まず if "nt" in sys.builtin_module_names: ENCODING="sjis" TYPE_COMMAND="type" else: ENCODING="utf8" TYPE_COMMAND="cat" は、osに応じて、ファイルのエンコードと type コマンドを決めている。その他は linux 系しかそうていしない。(笑) def tmpf(): fname="__mecab_tmp{0}.txt".format(random.randint(4545,46494)) return fname if not os.path.exists(fname) else tmpf() は一時ファイルを作る。もっとうまく書けそうだが、まあ何でもいい。(笑) Mecab クラスが肝。以下、関数の意味 init 引数 意味 text 文字列 call Mecab.__analize__(self.text) と同じ。 __analize__ 返り値 意味 data 検索結果を一行ごとに返す。 形式は (単語,それ以外のデータのリスト) __getDataAsSentence__ EOSを区切りに、データを文単位で返す。 __analize__ の返り値のリスト。 getDataAsSentence EOSを区切りにデータを、文にして返す。__getDataAsSentence__の文単位のデータの単語をつなげて返す。 getWords 単語を返す。 __analize__ の中で、わざわざ一時ファイルを作って書き込んでいるのは、大きいデータになると、 for line in sp.check_output("type {0} | mecab".format(tmp),shell=True).decode(ENCODING,"ignore").split("\n"): の type ... のところで、メモリーエラーになったことがあるから。もっとうまい方法があるのだろうか。。。 使ってみる main.py import mecab import sys text=sys.argv[1] for word in mecab.Mecab.__analize__(text): print(word) for word in mecab.Mecab.getWords(text): print(word) 実行結果 $ python main.py 隣の柿はよく客食う柿だ。 ('隣', ['名詞', '一般', '*', '*', '*', '*', '隣', 'トナリ', 'トナリ']) ('の', ['助詞', '連体化', '*', '*', '*', '*', 'の', 'ノ', 'ノ']) ('柿', ['名詞', '一般', '*', '*', '*', '*', '柿', 'カキ', 'カキ']) ('は', ['助詞', '係助詞', '*', '*', '*', '*', 'は', 'ハ', 'ワ']) ('よく', ['副詞', '一般', '*', '*', '*', '*', 'よく', 'ヨク', 'ヨク']) ('客', ['名詞', '一般', '*', '*', '*', '*', '客', 'キャク', 'キャク']) ('食う', ['動詞', '自立', '*', '*', '五段・ワ行促音便', '基本形', '食う', 'クウ', 'クウ']) ('柿', ['名詞', '一般', '*', '*', '*', '*', '柿', 'カキ', 'カキ']) ('だ', ['助動詞', '*', '*', '*', '特殊・ダ', '基本形', 'だ', 'ダ', 'ダ']) ('。', ['記号', '句点', '*', '*', '*', '*', '。', '。', '。']) ('EOS', None) 隣 の 柿 は よく 客 食う 柿 だ おわりに データを返すときに、辞書型にすればもっと使いやすいだろうが面倒やし、個人的な需要がないのでつくらず。必要は発明の母とはよく言ったものだ。(笑) コード
- 投稿日:2021-10-26T21:38:01+09:00
USBカメラでストリーミングしてみた。
Windows+USBカメラでストリーミング 配達完了通知がくると置配された荷物が不安で仕事にならないため、ラズパイで監視することにした。 ついでにライブビューも見たかったので、Windows上で画像取得と表示を作ってみた。 環境 Windows 10 Python 3.8 Opencv 4.0.1 USBカメラはPC付属 サーバ TCPを利用してlocalhost ポート50000 で待ち受けます。 終了はCtr+Break。 ImgStreamService.py # -*- coding: utf-8 -*- import cv2 import socket import ImgByteConvert class GetImageServer(): def __init__(self, PORT=8080): self.PORT = PORT self.capture = cv2.VideoCapture() self.ImageByteConverter = ImgByteConvert.ImgByteConvert() #フレームの取得 def get_frames(self): _, frame = self.capture.read() #print(len(frame)) return self.ImageByteConverter.FrameEncod(frame) #キャプチャー開始 def StartCapture(self): self.capture = cv2.VideoCapture(0,cv2.CAP_DSHOW) WIDTH = 320 HEIGHT = 240 FPS = 24 self.capture.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH) self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT, HEIGHT) self.capture.set(cv2.CAP_PROP_FPS, FPS) return #画像処理サービススタート def start(self): server = self.socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.bind(("",self.PORT)) server.listen() while True: client, adress = server.accept() print("接続要求あり") self.StartCapture() try: while True: textimg = self.get_frames() client.send(textimg) except: print("接続終了") client.close() self.capture.release() PORT=50000 if __name__== '__main__': Stream= GetImageServer(PORT) Stream.start() クライアント 終了は画面上でq。 ImgStreamService.py # -*- coding: utf-8 -*- import cv2 import socket import sys import ImgByteConvert HOST = 'localhost' PORT = 50000 if __name__ == '__main__': ImgConvert = ImgByteConvert.ImgByteConvert() client =socket.socket(socket.AF_INET,socket.SOCK_STREAM) #サーバとの接続 try: client.connect((HOST,PORT)) except: print("接続できません") sys.exit() while True: client.send("".encode('utf-8')) textframe = client.recv(65536) frame = ImgConvert.FrameDecode(textframe) if frame is not None: cv2.imshow('frame',frame) #フレーム上で'q'を入力. if cv2.waitKey(1) & 0xFF == ord('q'): break cv2.destroyAllWindows() client.close() イメージ⇔バイナリ変換 ImageByteConvert.py # -*- coding: utf-8 -*- import cv2 import base64 import numpy as np class ImgByteConvert(): def __init__(self,VIDEOFORMAT ='.jpg'): self.videoformat = VIDEOFORMAT #イメージを圧縮してバイナリコードに変換. def FrameEncod(self, frame): #メモリ上データをjpgに圧縮. img = cv2.imencode(self.videoformat,frame)[1] #伝送のためにバイナリ変換. return base64.b64encode(img) #バイナリコードをイメージ配列に変換. def FrameDecode(self, ByteFrame): #バイナリをデコード. img_binary = base64.b64decode(ByteFrame) #np.arrayに変換. img_array = np.frombuffer(img_binary, dtype=np.uint8) #画像配列に変換. return cv2.imdecode(img_array, cv2.IMREAD_COLOR) 最後に Djangoでアプリを作成するためにWindowsでのテスト用に作ってみました。 ラズパイでは UNIXソケット(AF_UNIX)をお勧め。
- 投稿日:2021-10-26T21:23:58+09:00
Cythonで作ったプログラムを、PyinstallerかPy2exeでexe化するには
概要 CythonスクリプトをPyinstallerやpy2exeでexe化する方法について説明します。 Cythonとは Pythonはインタプリタ言語ですので、「処理が遅い!」とイライラすることも少なくありません。 そんな問題を解決してくれるのがCythonです。 Cythonを用いるとPythonの処理が速くなります。 (処理速度を速くするには、PythonスクリプトをCython用スクリプトに書き直す必要があります。) 実際にこれを使うと、処理速度が2分の1になったこともありますが、 効果が出ない時も少なくありません。 詳細は公式サイトを確認してください。 Cython公式サイト https://cython.org Cythonスクリプトをexe化するには? Cythonを使うとPythonの処理が速くなりますが、exe化するのが難しいです。 (exe化しないと配布した時、CythonとPythonを実機にインストールしている人しか利用できませんね) そのため、今回はCythonスクリプトをexe化する方法について、備忘録として残します。(もちろんですが、Windowsです。) まずはCythonのインストール方法やスクリプトの書き方、型宣言について説明しますが、すぐ本題に早く入りたいという人は読み飛ばして下さい。 Cythonの基礎知識 Cythonのインストール方法や、型宣言などについて説明しています。 そもそもCythonというものが何か分からないという人は見て下さい。 インストール方法 Cythonのinstall方法について説明しています。 ◆手順1 もちろん、Pythonをインストールしていなかったら、しておきましょう。 Python公式サイト → https://www.python.org Python自体のインストール方法については割愛します。 ◆手順2 MinGWをインストールしましょう。(おそらく他のCコンパイラでもいけるはず...) http://www.mingw.org/ あるいは https://www.mingw-w64.org (一応、どちらも32bit,64bitで使えます。) ◆手順3 Microsoft社のVisualStudioをインストールしましょう。 https://visualstudio.microsoft.com/ja/downloads/ ◆手順4 pipでCythonをinstallしましょう。 $pip install Cython 以上でインストールは完了です。 PyPI Cython https://pypi.org/project/Cython/ pyxファイルについて pyxファイルについて説明しています CythonとPythonでは、スクリプトの書き方が少し変わるところがあるので注意しましょう。 Cythonスクリプトはpyxファイルに書きます。 pyxファイルとはCython専用のファイルのことです。 ◆pyxファイルの作り方 拡張子はなんでも良いのでファイルを作ります。 エクスプローラーの 表示 > ファイル名拡張 をONにすると拡張子が編集できるようになるので、pyxに変更して下さい。 ◆pyxファイルの編集 ファイルをクリックすると、どのプログラムから開くか聞かれると思うので、 メモ帳などのテキストエディタを選択して下さい。 Cythonスクリプトの書き方 Cythonスクリプトの書き方について説明しています。 test.pyxを作ってみました。 とりあえず、フィボナッチ数列を書きました。 def fib(n): cdef a,b,i a, b = 0, 1 for i in range(n): a, b = a + b, a return a ◆Pythonスクリプトとの相違点 cdefを使っている。 → 変数の固定 型宣言をすることもできます。 Cythonスクリプトをpydファイルにする。 Cythonスクリプトをpydファイルにする方法について説明しています。 以下のコマンドを実行しましょう。 $cythonize -i test.pyx そうすると、test.cp36-win_amd64.pydというファイルができると思います。 Microsoft社のVisualStudioがインストールされていなかったら「cythonizeコマンドがない」とエラーが出ると思います。また、Cコンパイラがインストールされていなかったら、別のエラーが出ると思います。 Cythonスクリプトの実行 Cythonスクリプトの実行について説明しています。 Cythonのfib関数をPythonから呼び出しましょう。 #test.py import test #test.pydをimport import time start = time.time() test.fib(50000) #Cythonでfibを実行 finish = time.time() print("Cython:",finish - start) def fib(n): a, b = 0, 1 for i in range(n): a, b = a + b, a return a start = time.time() fib(50000) #CPythonでfibを実行 finish = time.time() print("CPython:",finish - start) これを実行します。 $python test.py 実行結果 Cython: 0.031176090240478516 CPython: 0.03124523162841797 Cythonの方が処理速度が速いですが、期待していたより効果が薄いですね... 型宣言を使うとより速くなる 型宣言について説明しています。 思ったよりCythonの速度が速くなかったので、型宣言をしてみました。 実はCythonは、型宣言をすることも出来るのです。 test.pyxを以下のように変えました。 def fib(int n): cdef int a,b,i a, b = 0, 1 for i in range(n): a, b = a + b, a return a test.pyの内容は変えず、もう一度実行すると... Cython: 0.0 Cythonの速度が0になってしまいました。 timeがうまく機能しなかったみたいなので、test.pyのfib()の繰り返し回数を大きくします。 #test.py import test #test.pydをimport import time start = time.time() test.fib(12000000) #Cythonでfibを実行 finish = time.time() print("Cython:",finish - start) def fib(n): a, b = 0, 1 for i in range(n): a, b = a + b, a return a start = time.time() fib(12000000) #CPythonでfibを実行 finish = time.time() print("CPython:",finish - start) すると... Cython: 0.01522377395629883 !? いくらなんでも高速化しすぎではないか...? ここまでくると、処理速度の増加には何か別の理由があるのか...と疑ってしまいました。 CPythonの場合、どのくらいかかったの?と思うかもしれませんが、30分以上経っても具体的な数値がprintされなかったので、プログラムを終了させて諦めました。 Cythonスクリプトのexe化 さて、本題に入ります。 Cythonスクリプトをexe化する方法について、でしたね。 通常Pythonをexe化するにはPyinstallerやPy2exeを使います。 (Pyinstallerでコンパイルした実行可能ファイルは、起動までの処理が遅いため、py2exeを使う人が増えています。) なので、今回は前者、後者それぞれでexe化する方法を紹介します。 Pyinstallerでexe化する場合 まずは、pyinstallerをインストールしましょう。 以下のコードを実行して下さい。 $pip install pyinstaller これでインストールは完了です。 手順が全て済んでいるのであれば、Cythonでビルドしたpydファイルと、Cythonを呼び出すためのpyファイルが、同じファイルに保存されていると思います。 そのディレクトリでコマンドプロンプトかWindowsPowerShellを開き、以下のコードを入力して下さい。 $pyinstaller -r ○○○.pyd -F ○○○.py 私の場合は「test.pyd」と、「test.py」を作ったので、 $pyinstaller -r test.pyd test.py と入力しました。 すると、buildとdistというファイルができますので、distファイルの方を開くと、作った実行可能ファイルがあると思います。 py2exeでexe化する場合 まずは、py2exeを入手しましょう。 $pip install py2exe でインストールできます。 続いて、コンパイル用のsetup.pyを書きます。 以下のコードを書いて下さい。 #setup.py from distutils.core import setup import py2exe option = { 'compressed': 1, 'optimize': 2, 'bundle_files': 2, } setup( options = { 'py2exe': {"includes":["○○○"]}#pydファイルの名前 #拡張子の"pyd"は書かない }, console = [ {'script': '□□□.py'} #Cythonを呼び出すpyのファイル名 ], zipfile = None, ) ○○○の部分にはpydファイルの名前を入れます。 私の場合は、test.pydを作ったので、"includes":["test"]と書きました。("test.pyd"とは書かない) 一方で□□□の部分には、Cythonを呼び出すpythonスクリプトのファイル名を入れて下さい。私の場合は、'script': 'test.py'と書きました。 もちろんですが、test.pydとtest.pyは、setup.pyと同ディレクトリで保存して下さい。 次に、そのディレクトリで以下のコードを実行して下さい。 $python setup.py py2exe すると、distというファイルができるので、その中に必要なライブラリ関連のファイルと、実行可能ファイル(exe)があります。 test.exeが正常に動作するか確認しましょう。 補足 CythonはPyinstallerでコンパイルすると、エラーが起きる可能性が高いので、できればpy2exeでコンパイルした方が良い。 ◆Pyinstallerでexe化する場合 <メリット> ・py2exeよりもバージョンが新しい ・アイコン設定が簡単 <デメリット> ・型宣言によってエラーが起こる可能性がある。 ・意味のわからないValueErrorが起きる可能性がある。 (プログラムを変えると案外できたりして...) ◆py2exeでexe化する場合 <メリット> ・型宣言が自由に使える。 ・実行可能ファイルと、ライブラリ関連のファイルを分ければ、exe自体の容量が下がる。 ・コンパイル時間が速い。 ・exeが起動するまでの時間も短い。 <デメリット> ・pyinstallerよりもバージョンが古い。(でも有能) ・コンパイル用setup.pyを書くのが面倒 結論 ・Cythonスクリプトをexe化することは可能である。 ・Cythonの場合はPyinstallerよりpy2exeでコンパイルした方が良い。(個人的感想) 参考文献 CythonをPyinstallerでコンパイルするには? https://stackoverflow.com/questions/24525861/building-cython-compiled-python-code-with-pyinstaller 2021年10月26日閲覧 py2exeでpydとpyを合わせてコンパイル https://stackoverflow.com/questions/9579990/include-pyd-module-files-in-py2exe-compilation 2021年10月26日閲覧
- 投稿日:2021-10-26T21:02:52+09:00
bookmeterのAPIをつくってみた。 #2 (簡単なユーザーデータの取得)
はじめに 前回はデータを取得したので、次はユーザー名から「読みたい本」や「読んだ本」を取れるようにしたい。 名前からユーザーIDの取得 「読みたい本」や「読んだ本」の URL は、 読みたい本:https://bookmeter.com/users/<userid>/books/wish 読んだ本 : https://bookmeter.com/users/<userid>/books/read のようになっている。(<userid>=ユーザーID) ユーザーIDはユーザー名とは別のものなので、まずはユーザー名からユーザーIDを取得する。 流れ 名前で検索してユーザーの一覧を取得。 そこから名前が完全一致のデータを取得。(同名別人の可能性はあるが、名前の付け方が悪いせいにしておく。笑) IDを取得。 ユーザーの検索 ユーザーを検索するには https://bookmeter.com/users/search を使えば良い。ブラウザからは、普通のページが返ってくるが、User-Agent だけ付けたリクエストをすると JSON 形式のデータが返ってくる。以下コード。前回の続き。 コード bookmeter.py def searchUsers(name): params={ "name":name } res=requests.get(SEARCH_USERS_URL,headers=HEADERS,params=params) #soup=bs(res.text,"html.parser") data=json.loads(res.text) return data["resources"] 試す main.py import bookmeter import json import sys name=sys.argv[1] for data in bookmeter.searchUsers(name): print(json.dumps(data,indent=4,ensure_ascii=False)) 実行結果 $ python main.py 田中 { "id": 756064, "path": "/users/756064", "name": "田中田中", "image": "https://img.bookmeter.com/profile_image/normals/757/1490774229046470.jpg", "sign_up_date": "2017年" } { "id": 747469, "path": "/users/747469", "name": "田中田中", "image": "https://img.bookmeter.com/profile_image/normals/748/1487672432957391.png", "sign_up_date": "2017年" } { "id": 817445, "path": "/users/817445", "name": "田中田中", "image": "https://bookmeter.com/images/common/profile_image.png", "sign_up_date": "2017年" ~~以下略~~ } 完全一致のデータを取得 さっきのデータから、名前が完全一致したものを返すだけ。 bookmeter.py import re def getUser(name): for user in searchUsers(name): if re.fullmatch(name,user["name"]): return user return None main.py import bookmeter import sys import json name=sys.argv[1] data=bookmeter.getUser(name) if data: print(json.dumps(data,indent=4,ensure_ascii=False)) 実行結果 $ python main.py 田中田中 { "id": 756064, "path": "/users/756064", "name": "田中田中", "image": "https://img.bookmeter.com/profile_image/normals/757/1490774229046470.jpg", "sign_up_date": "2017年" } $ python main.py 田中田中田中 $ python main.py vectorcc { "id": 1035737, "path": "/users/1035737", "name": "vectorcc", "image": "https://bookmeter.com/images/common/profile_image.png", "sign_up_date": "2019年" } IDの取得は data["id"] とすれば良い。 まとめる 以上のコードをまとめる。 bookmeter.py class _User: URL=URL+"/users/{0}" class Type: WANT="wish" READ="read" READING="reading" def __init__(self,name): self.name=name self.id=self.getid(name) if not self.id: raise Exception("Don't find id of {0}.".format(self.name)) @property def wanturl(self): return self.url+"/books/wish" @property def readurl(self): return self.url+"/books/read" @property def url(self): return self.URL.format(self.id) @staticmethod def getid(name): user=getUser(name) return user["id"] if user else None あるユーザーの読んだ本のデータを取得 前回のものと、上の関数を組み合わせて、あるユーザーの読んだ本のデータを取ってみる。 main.py import bookmeter import json import sys name=sys.argv[1] user=bookmeter._User(name) for data in bookmeter.getBooks(user.readurl,n=1000): print(json.dumps(data,indent=4,ensure_ascii=False)) 実行結果 $ python main.py 田中 { "imgUrl": "https://m.media-amazon.com/images/I/61fkTy7nTFL._SL500_.jpg", "title": "ソードアート・オンライン (6) ファントム…", "href": "https://bookmeter.com/books/1651321", "date": "2013/01/09", "author": "川原 礫", "page": "445" } { "imgUrl": "https://m.media-amazon.com/images/I/51ilIORClBL._SL500_.jpg", "title": "ソードアート・オンライン〈5〉ファントム・バ…", "href": "https://bookmeter.com/books/625788", "date": "2013/01/09", "author": "川原 礫", "page": "297" } { "imgUrl": "https://m.media-amazon.com/images/I/613gLJ4BcEL._SL500_.jpg", "title": "バッカーノ! 1935-B Dr. Feel…", "href": "https://bookmeter.com/books/5699809", "date": "2012/12/30", "author": "成田良悟", "page": "328" } ~~以下略~~ 以上! おわりに 次回こそはデータベースを作りたい。一々調べれば良いようだが、手元にあった方が都合が良いこともあると思う。 コードはこちら
- 投稿日:2021-10-26T21:00:51+09:00
【初心者】matplotlibの日本語表示対応【Google Colab】
matplotlibの日本語文字化け matplotlibは、標準で日本語に対応しておらず、日本語を使用すると文字化けが発生し、正しく表示されません。 例えばGoogle Colabで実行すると、□□と表示されてしまいます。 import matplotlib.pyplot as plt plt.plot([1, 2, 3, 4]) plt.xlabel('縦軸') plt.ylabel('横軸') plt.show() japanize-matplotlibによる日本語表示対応 japanize-matplotlibを利用することで、日本語対応できます。pipコマンドでインストールできるため、非常に簡単に利用できます。 !pip install japanize_matplotlib import matplotlib.pyplot as plt import japanize_matplotlib plt.plot([1, 2, 3, 4]) plt.xlabel('縦軸') plt.ylabel('横軸') plt.show() 正しく、日本語が表示されていることが確認できました。
- 投稿日:2021-10-26T21:00:31+09:00
pandasが遅い? Polarsを使いましょ
はじめに? pandas の DataFrame が遅い!高速化したい!と思っているそこのあなた! Polars の DataFrame を試してみてはいかがでしょうか?? GitHub: https://github.com/pola-rs/polars User Guide: https://pola-rs.github.io/polars-book/user-guide/index.html API reference: https://pola-rs.github.io/polars/py-polars/html/reference/index.html この記事の目的 Polars の使い方をざっくり紹介。適当に例を並べていくので、雰囲気だけでもつかんでいただければ(系統立った説明はしてません。。) Polars のメリデメ ?速い?? ベンチマーク: https://pola-rs.github.io/polars-book/user-guide/introduction.html#performance- https://h2oai.github.io/db-benchmark/ IOも速い 下記「どのくらい速いの?」も参照 ?メモリエラーが起こりにくい MemoryError: Unable to allocate の悪夢から逃れられる ?使いやすい pandas の使い方と近いので、すぐ慣れる ?機能も十分 pandas でできることは大体できる(はず) SQL でいうところの PARTITION BY ができる int の列に NULL を入れられる NaN と NULL は区別されている ?ドキュメントが充実してない ?型によっては apply ができなかったりする インストール pip で入れるだけ pip install polars ※私が試したバージョン: Python==3.9.7 polars==0.10.7 pyarrow==5.0.0 使い方 まずは import import polars as pl 基本的な使い方 pd を pl に変えるだけでも、ある程度の操作ができる DataFrame の作成 df = pl.DataFrame({ 'col_str': ['a', 'b', 'c'], 'col_int': [1, None, 3], 'col_float': [0.1, 0.2, 0.3], 'col_bool': [True, True, False], }) print(df) # shape: (3, 3) # ┌─────────┬─────────┬──────────┐ # │ col_str ┆ col_int ┆ col_bool │ # │ --- ┆ --- ┆ --- │ # │ str ┆ i64 ┆ bool │ # ╞═════════╪═════════╪══════════╡ # │ a ┆ 1 ┆ true │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤ # │ b ┆ null ┆ true │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤ # │ c ┆ 3 ┆ false │ # └─────────┴─────────┴──────────┘ ※このように、print しても読みやすい。jupyter notebook などでは、pandas と同様の表示も可能。 pandas の index のようなものはない。 read_csv や to_csv なども可能。 pl.DataFrame() には pandas の DataFrame を渡すこともできる。 列の追加 df['col_bool'] = [True, True, False] # Series を渡すこともできる: # df = df.with_column( # pl.Series('col_bool', [True, True, False]) # ) 列名の変更 df = df.rename({'col_float': 'col_flt'}) 型のcast df['col_flt'] = df['col_flt'].cast(pl.Float32) 使える型 → https://pola-rs.github.io/polars-book/user-guide/datatypes.html 列の削除 df = df.drop('col_flt') NULL を埋める df = df.fill_null(0) NaN を埋める場合は fill_nan を使う。 null_count や drop_nulls などのメソッドもある。 head なども使える print(df.head(2)) # shape: (2, 3) # ┌─────────┬─────────┬──────────┐ # │ col_str ┆ col_int ┆ col_bool │ # │ --- ┆ --- ┆ --- │ # │ str ┆ i64 ┆ bool │ # ╞═════════╪═════════╪══════════╡ # │ a ┆ 1 ┆ true │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤ # │ b ┆ 0 ┆ true │ # └─────────┴─────────┴──────────┘ print(df[1:]) # shape: (2, 3) # ┌─────────┬─────────┬──────────┐ # │ col_str ┆ col_int ┆ col_bool │ # │ --- ┆ --- ┆ --- │ # │ str ┆ i64 ┆ bool │ # ╞═════════╪═════════╪══════════╡ # │ b ┆ 0 ┆ true │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤ # │ c ┆ 3 ┆ false │ # └─────────┴─────────┴──────────┘ print(df[[2], [1]]) # shape: (1, 1) # ┌─────────┐ # │ col_int │ # │ --- │ # │ i64 │ # ╞═════════╡ # │ 3 │ # └─────────┘ print(df[-1, 'col_bool']) # False pandas や numpy への変換 print(df.to_pandas()) # col_str col_int col_bool # 0 a 1 True # 1 b 0 True # 2 c 3 False print(df.to_numpy()) # [['a' 1 True] # ['b' 0 True] # ['c' 3 False]] Config print で表示される行数や列数を調整したいときに pl.Config.set_tbl_rows(20) pl.Config.set_tbl_cols(10) map や apply 的なやつ df['col_int_div_2'] = df['col_int'].apply(lambda x: x / 2) df = df.hstack( df[pl.when(pl.col('col_int_div_2') >= 1) .then(1) .otherwise(pl.Series([7, 8, 9])) .alias('wto')] ) df = df.hstack( df.select([ pl.col('col_int').is_in([1, 2]).is_not().alias('col_int_not_in_1_2') ]) ) print(df) # shape: (3, 6) # ┌─────────┬─────────┬──────────┬───────────────┬─────┬────────────────────┐ # │ col_str ┆ col_int ┆ col_bool ┆ col_int_div_2 ┆ wto ┆ col_int_not_in_1_2 │ # │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ # │ str ┆ i64 ┆ bool ┆ f64 ┆ i64 ┆ bool │ # ╞═════════╪═════════╪══════════╪═══════════════╪═════╪════════════════════╡ # │ a ┆ 1 ┆ true ┆ 0.5 ┆ 7 ┆ false │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ b ┆ 0 ┆ true ┆ 0.0 ┆ 8 ┆ true │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ c ┆ 3 ┆ false ┆ 1.5 ┆ 1 ┆ true │ # └─────────┴─────────┴──────────┴───────────────┴─────┴────────────────────┘ 結合 df_2 = pl.DataFrame({ 'col_str': ['a', 'c', 'd'], 'col_datetime': [ datetime.strptime( f'2021-10-{i} 10:20:30 +0900', '%Y-%m-%d %H:%M:%S %z' ) for i in [12, 15, 17] ], }) df_join = df[['col_str', 'col_int']].join( df_2, on='col_str', how='left') print(df_join) # shape: (3, 3) # ┌─────────┬─────────┬─────────────────────┐ # │ col_str ┆ col_int ┆ col_datetime │ # │ --- ┆ --- ┆ --- │ # │ str ┆ i64 ┆ datetime │ # ╞═════════╪═════════╪═════════════════════╡ # │ a ┆ 1 ┆ 2021-10-12 01:20:30 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ b ┆ 0 ┆ null │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ c ┆ 3 ┆ 2021-10-15 01:20:30 │ # └─────────┴─────────┴─────────────────────┘ ※Datetimeは内部的にUNIX時刻で表現されているため、timezoneを適切に扱わないとこのようにずれてしまう pandas の concat のように、単純に積むこともできる df = df[['col_str', 'col_int']].vstack( pl.DataFrame({ 'col_str': ['x', 'y', 'z'], 'col_int': [7, 8, 9], }) ) print(df) # shape: (6, 2) # ┌─────────┬─────────┐ # │ col_str ┆ col_int │ # │ --- ┆ --- │ # │ str ┆ i64 │ # ╞═════════╪═════════╡ # │ a ┆ 1 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ # │ b ┆ 0 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ # │ c ┆ 3 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ # │ x ┆ 7 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ # │ y ┆ 8 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ # │ z ┆ 9 │ # └─────────┴─────────┘ フィルターとソート df = df.filter((pl.col('col_int') >= 1) & (pl.col('col_int') <= 7)) df = df.sort('col_int', reverse=True) print(df) # shape: (3, 2) # ┌─────────┬─────────┐ # │ col_str ┆ col_int │ # │ --- ┆ --- │ # │ str ┆ i64 │ # ╞═════════╪═════════╡ # │ x ┆ 7 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ # │ c ┆ 3 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ # │ a ┆ 1 │ # └─────────┴─────────┘ シフト df['col_int_shifted'] = df['col_int'].shift(1) print(df) # shape: (3, 3) # ┌─────────┬─────────┬─────────────────┐ # │ col_str ┆ col_int ┆ col_int_shifted │ # │ --- ┆ --- ┆ --- │ # │ str ┆ i64 ┆ i64 │ # ╞═════════╪═════════╪═════════════════╡ # │ x ┆ 7 ┆ null │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ c ┆ 3 ┆ 7 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ a ┆ 1 ┆ 3 │ # └─────────┴─────────┴─────────────────┘ 集約 改めて df を定義 df = pl.DataFrame({ 'col_str': ['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'a', 'c'], 'col_int': [1, 3, 2, 6, 5, 3, 1, 4, 2, 1], 'col_float': [.2, .4, .1, .5, .6, .8, .9, .1, .5, .2], }) 以下のような感じのテーブル col_str col_int col_float a 1 0.2 b 3 0.4 c 2 0.1 a 6 0.5 b 5 0.6 c 3 0.8 a 1 0.9 b 4 0.1 a 2 0.5 c 1 0.2 describe print(df.describe()) # shape: (5, 4) # ┌──────────┬─────────┬────────────────────┬─────────────────────┐ # │ describe ┆ col_str ┆ col_int ┆ col_float │ # │ --- ┆ --- ┆ --- ┆ --- │ # │ str ┆ str ┆ f64 ┆ f64 │ # ╞══════════╪═════════╪════════════════════╪═════════════════════╡ # │ mean ┆ null ┆ 2.8 ┆ 0.43000000000000005 │ # ├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ std ┆ null ┆ 1.7511900715418263 ┆ 0.28303906287138375 │ # ├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ min ┆ null ┆ 1 ┆ 0.1 │ # ├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ max ┆ null ┆ 6 ┆ 0.9 │ # ├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ median ┆ null ┆ 2.5 ┆ 0.45 │ # └──────────┴─────────┴────────────────────┴─────────────────────┘ groupby print(df.groupby('col_str').max()) # shape: (3, 3) # ┌─────────┬─────────────┬───────────────┐ # │ col_str ┆ col_int_max ┆ col_float_max │ # │ --- ┆ --- ┆ --- │ # │ str ┆ i64 ┆ f64 │ # ╞═════════╪═════════════╪═══════════════╡ # │ b ┆ 5 ┆ 0.6 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ a ┆ 6 ┆ 0.9 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ c ┆ 3 ┆ 0.8 │ # └─────────┴─────────────┴───────────────┘ print(df.groupby('col_str').agg({'col_int': 'min'})) # shape: (3, 2) # ┌─────────┬─────────────┐ # │ col_str ┆ col_int_min │ # │ --- ┆ --- │ # │ str ┆ i64 │ # ╞═════════╪═════════════╡ # │ c ┆ 1 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ b ┆ 3 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ a ┆ 1 │ # └─────────┴─────────────┘ より複雑なもの print( df.groupby('col_str') .agg([ pl.col('col_float').sum(), pl.sum('col_int'), # 短く書ける pl.sum('col_int').alias('int_sum'), # 列名を自分でつけられる pl.col('col_int').list(), # list にもできる pl.col('col_int').first(), # 他にも count, mean, などなど (pl.col('col_int') > 2).sum().alias( 'col_int_gt_2_count'), # 条件を満たすものをカウント ]) ) # shape: (3, 7) # ┌─────────┬────────────────┬─────────────┬─────────┬───────────────┬───────────────┬───────────────┐ # │ col_str ┆ col_float_sum ┆ col_int_sum ┆ int_sum ┆ col_int_agg_l ┆ col_int_first ┆ col_int_gt_2_ │ # │ --- ┆ --- ┆ --- ┆ --- ┆ ist ┆ --- ┆ count │ # │ str ┆ f64 ┆ i64 ┆ i64 ┆ --- ┆ i64 ┆ --- │ # │ ┆ ┆ ┆ ┆ list [i64] ┆ ┆ u32 │ # ╞═════════╪════════════════╪═════════════╪═════════╪═══════════════╪═══════════════╪═══════════════╡ # │ b ┆ 1.1 ┆ 12 ┆ 12 ┆ [3, 5, 4] ┆ 3 ┆ 3 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ c ┆ 1.1 ┆ 6 ┆ 6 ┆ [2, 3, 1] ┆ 2 ┆ 1 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ a ┆ 2.1 ┆ 10 ┆ 10 ┆ [1, 6, ... 2] ┆ 1 ┆ 1 │ # └─────────┴────────────────┴─────────────┴─────────┴───────────────┴───────────────┴───────────────┘ Window 関数 各 window での最大や平均 print(df.select([ 'col_str', 'col_int', 'col_float', pl.col('col_int') .max() .over('col_str') .alias('max_int_by_str'), pl.col('col_float') .mean() .over('col_str') .alias('avg_float_by_str'), ])) # shape: (10, 5) # ┌─────────┬─────────┬───────────┬────────────────┬────────────────────┐ # │ col_str ┆ col_int ┆ col_float ┆ max_int_by_str ┆ avg_float_by_str │ # │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ # │ str ┆ i64 ┆ f64 ┆ i64 ┆ f64 │ # ╞═════════╪═════════╪═══════════╪════════════════╪════════════════════╡ # │ a ┆ 1 ┆ 0.2 ┆ 6 ┆ 0.525 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ b ┆ 3 ┆ 0.4 ┆ 5 ┆ 0.3666666666666667 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ c ┆ 2 ┆ 0.1 ┆ 3 ┆ 0.3666666666666667 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ a ┆ 6 ┆ 0.5 ┆ 6 ┆ 0.525 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ b ┆ 5 ┆ 0.6 ┆ 5 ┆ 0.3666666666666667 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ c ┆ 3 ┆ 0.8 ┆ 3 ┆ 0.3666666666666667 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ a ┆ 1 ┆ 0.9 ┆ 6 ┆ 0.525 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ b ┆ 4 ┆ 0.1 ┆ 5 ┆ 0.3666666666666667 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ a ┆ 2 ┆ 0.5 ┆ 6 ┆ 0.525 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ c ┆ 1 ┆ 0.2 ┆ 3 ┆ 0.3666666666666667 │ # └─────────┴─────────┴───────────┴────────────────┴────────────────────┘ 各 window でのランク # overの中身でsortしておかないと上手くいかない print(df.sort('col_str').select([ 'col_str', 'col_int', 'col_float', pl.col('col_int') .rank('min') .over('col_str') .flatten() .alias('rank_int_by_str'), ])) # shape: (10, 5) # ┌─────────┬─────────┬───────────┬───────────────────────┬──────────────────┐ # │ col_str ┆ col_int ┆ col_float ┆ rank_int_list_by_str? ┆ rank_int_by_str? │ # │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ # │ str ┆ i64 ┆ f64 ┆ list [u32] ┆ u32 │ # ╞═════════╪═════════╪═══════════╪═══════════════════════╪══════════════════╡ # │ a ┆ 1 ┆ 0.2 ┆ [1, 4, ... 3] ┆ 1 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ a ┆ 6 ┆ 0.5 ┆ [1, 4, ... 3] ┆ 4 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ a ┆ 1 ┆ 0.9 ┆ [1, 4, ... 3] ┆ 1 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ a ┆ 2 ┆ 0.5 ┆ [1, 4, ... 3] ┆ 3 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ b ┆ 3 ┆ 0.4 ┆ [1, 3, 2] ┆ 1 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ b ┆ 5 ┆ 0.6 ┆ [1, 3, 2] ┆ 3 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ b ┆ 4 ┆ 0.1 ┆ [1, 3, 2] ┆ 2 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ c ┆ 2 ┆ 0.1 ┆ [2, 3, 1] ┆ 2 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ c ┆ 3 ┆ 0.8 ┆ [2, 3, 1] ┆ 3 │ # ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ # │ c ┆ 1 ┆ 0.2 ┆ [2, 3, 1] ┆ 1 │ # └─────────┴─────────┴───────────┴───────────────────────┴──────────────────┘ cumcount はないっぽいので、中身が 1 のカラムを作って cumsum するのがよさげ どのくらい速いの? groupby や join が pandas に比べてどのくらい速いのか、試してみました。 1回しか計測してないですが、高速感は伝わると思います。 from contextlib import contextmanager import time import numpy as np import pandas as pd import polars as pl @contextmanager def timer(name: str): t0 = time.time() yield print(f'{name}: {time.time() - t0:.1f} s') np.random.seed(42) N = 10**8 M = 10**4 df_dict = { 'col_int': np.random.randint(0, M, N), 'col_float': np.random.rand(N), } df_dict_2 = { 'col_int': np.random.randint(0, 10**5, M), 'col_float': np.random.rand(M), } df_pd = pd.DataFrame(df_dict) df_pl = pl.DataFrame(df_dict) df_pd_2 = pd.DataFrame(df_dict_2) df_pl_2 = pl.DataFrame(df_dict_2) with timer('pandas groupby'): df_pd.groupby('col_int').agg({'col_float': 'mean'}) with timer('polars groupby'): df_pl.groupby('col_int').agg({'col_float': 'mean'}) with timer('pandas join'): pd.merge( df_pd, df_pd_2, on='col_int', how='left', suffixes=['', '_2'] ) with timer('polars join'): df_pl.join( df_pl_2, on='col_int', how='left', suffix='_2' ) # pandas groupby: 3.8 s # polars groupby: 1.5 s # pandas join: 31.0 s # polars join: 2.6 s 終わりに 以上です。最後までお読みいただきありがとうございました。 快適なPythonライフをエンジョイしましょう? GitHub: https://github.com/pola-rs/polars User Guide: https://pola-rs.github.io/polars-book/user-guide/index.html API reference: https://pola-rs.github.io/polars/py-polars/html/reference/index.html
- 投稿日:2021-10-26T20:38:00+09:00
Python 正規表現で個人情報マスキング
個人情報をマスキングする ダッシュボードでデータを掲載したいものの、個人情報は情報セキュリティ的に載せられない。 CRM(顧客情報管理システム)などで手入力されるデータは表記が揺れるため、 SQLやPowerQueryには限界を感じ、自分の扱う個人情報で特に多い電話番号のマスキングに 正規表現を使用してみた。 SQLでもある程度できるが、使用しているDBで一般的なreplace関数の引数が使えず、 何度も電話番号が出現した場合は対応できないため、諦めた。 テストデータを用意する 携帯電話と固定電話を含んだリストを作成する。 メモなどに入力され、電話番号以外にも情報があり、それは残したい想定。 data = {'ID':[1, 2, 3, 4, 5], 'Memo':['2021/12/12_テスト000-0123-4567テスト', '2021/12/12_テスト(000)0123-4567テスト', '2021/12/12_テスト00001234567テスト', '2021/12/12_テスト(00)0123-4567テスト', '2021/12/12_テスト(000-123-4567テスト'] } df = pd.DataFrame(data) どの正規表現がいいか試す ハイフンや()を取り除いて10桁の数字を指定してみる ハイフンや()を取り除くために、英数字以外を空白に置き換えて、 10桁の数字の場合、「***」に置き換えしてみた。 英数字以外は\Wや[^a-zA-Z0-9]で表現されるらしい。 結果は以下で、電話番号自体はマスクされた。 df['masked'] = df['Memo'].map(lambda _: re.sub(r'\d{10}', r'***', re.sub(r'\W', r'', str(_)))) #out put # ID Memo masked #0 1 2021/12/12_テスト000-0123-4567テスト 20211212_テスト***7テスト #1 2 2021/12/12_テスト(000)0123-4567テスト 20211212_テスト***7テスト #2 3 2021/12/12_テスト00001234567テスト 20211212_テスト***7テスト #3 4 2021/12/12_テスト(00)0123-4567テスト 20211212_テスト***テスト #4 5 2021/12/12_テスト(000-123-4567テスト 20211212_テスト***テスト しかし、日付の記号も消えるという、やや残念な様子である。 3桁-4桁-3桁の正規表現を指定してみる 正確には、 『0-1文字の数字以外+2-3桁の数字+0-1文字の数字以外 +3-4桁の数字+0-1文字の数字以外+4桁』の数字を指定してみた。 0~9の数字は\dか[0-9]で表現される。 {}は、直前のものの繰り返し回数を指定できる。 \d{3}は3桁、\d{2,3}は2~3桁の、0~9の数字のどれがということになる。 ?は0-1回という意味で、[^0-9]か\Dは数字以外ということで、 ^はそれ以外という意味らしい。 [^0-9]?とすることで、0-1回の数字以外が入るという意味になる。 結果は以下で、日付は残り、電話番号自体もマスクされた。 df['masked'] = df['Memo'].map(lambda _: re.sub(r'\D?\d{2,3}\D?\d{3,4}\D?\d{4}', r'***', str(_))) #out put # ID Memo masked #0 1 2021/12/12_テスト000-0123-4567テスト 2021/12/12_テスト***テスト #1 2 2021/12/12_テスト(000)0123-4567テスト 2021/12/12_テスト***テスト #2 3 2021/12/12_テスト00001234567テスト 2021/12/12_テスト***テスト #3 4 2021/12/12_テスト(00)0123-4567テスト 2021/12/12_テスト***テスト #4 5 2021/12/12_テスト(000-123-4567テスト 2021/12/12_テスト***テスト 結果 この正規表現に当てはまる電話番号以外データがない限り、 基本的には上記でカバーできそうだった。 正規表現は情報を流し読みするだけでは理解できず、 実際にいろいろ試してみて勉強になった。 正規表現について詳しくは、書籍や他記事を参照してほしい。
- 投稿日:2021-10-26T20:16:27+09:00
デバッグ用:pretty_errors
インストール pip install pretty_errors 使用 import pretty_errors pretty_errors.configure( display_locals = True, filename_display = pretty_errors.FILENAME_EXTENDED, ) display_localsは、局部変数を設定する、 filename_display は、ファイルの名を設定する、 他のこと(色など)も設定できる。 全局セット python -m pretty_errors
- 投稿日:2021-10-26T19:28:26+09:00
Pythonで2進数likeな文字列をnumpyにEncodeする
題名通りです. import numpy as np np.fromiter('0101', dtype=np.int8) >>> array([0, 1, 0, 1], dtype=int8)
- 投稿日:2021-10-26T18:24:45+09:00
python + OpenCVで画像を加工④ 画像の顔を検出してモザイクをかける
前回の記事と前々回の記事でそれぞれ画像の中から顔を検出し、モザイクをかけることができるようになりました。 今回は画像の中から顔を検出し、検出した顔の周囲にのみモザイクをかける処理をやってみようと思います。 最終的な完成コードはこちらになります。 #写真の中の顔を検出して自動でモザイクをかける処理 import cv2 cascade_file= "haarcascade_frontalface_default.xml" clas = cv2.CascadeClassifier(cascade_file) img = cv2.imread("photo.jpg") #写真の中の顔をすべて検出して周囲の四角形の座標を取得 #検出できた顔のすべてについて[四角の左上のx座標、四角の左上のy座標、幅、高さ]の数値を取得 face_list = clas.detectMultiScale(img, scaleFactor = 1.1, minSize=(30,30)) #モザイク処理 #[四角の左上のx座標、四角の左上のy座標、幅、高さ]をそれぞれ変数x, y, w, hに代入 for x, y, w, h in face_list: face= img[y:y+h, x:x+w] small_pic = cv2.resize(face, (8,8)) mosaic = cv2.resize(small_pic,(w,h)) img[y:y+h, x:x+w]=mosaic cv2.imshow("photo_processed.jpg",img) cv2.waitKey() cv2.destroyAllWindows() 顔の部分だけモザイク処理 前回の記事までとかぶる内容は割愛するとしまして、今回の肝は下記の部分です。 for x, y, w, h in face_list: face= img[y:y+h, x:x+w] small_pic = cv2.resize(face, (8,8)) mosaic = cv2.resize(small_pic ,(w,h)) img[y:y+h, x:x+w]=mosaic for x, y, w, h in face_list: 検出した顔について①四角の左上のx座標、②四角の左上のy座標、③幅、④高さをそれぞれ変数x, y, w, hに代入しています。 face= img[y:y+h, x:x+w] imread()で取り込んだ画像=imgの中から、座標[y:y+h, x:x+w]の部分を切り取ってface変数に代入しています。座標というと[x, y]の順番に書いてしまいそうですが、OpenCVの場合yを先に記述することも多いので注意が必要です。ついでに言いますと、y座標は数値が増えるにつれて位置的に下に下がっていくので慣れるまで混乱しました。。 座標を図式化すると、四角の左上は単純に[x, y]ですが、右下はそれぞれ幅と高さを足し合わせているので[x+w, y+h]になります。代入するときにはx座標とy座標が入れ替わります。 small_pic = cv2.resize(face, (8,8)) resize()を使って切り取った顔をいったんものすごく小さく加工してsmall_picに代入しています。resize()についてはpython + OpenCVで画像を加工①かOpenCVのチュートリアルを参照していただけたらよいかと思います。 mosaic = cv2.resize(small_pic ,(w,h)) 今度は切り取った写真の顔部分を元の大きさに戻しています。画像を小さくして画素数を減らし、画素数の少なくなった画像を無理やり大きいサイズに戻すのでいわゆるモザイクの状態になるということですね。(w, h)で切り取った画像の大きさを指定しているので、これで大きさは元に戻ります。 img[y:y+h, x:x+w]=mosaic img[y:y+h, x:x+w]、つまり写真を切り取った場所にmosaic変数=モザイクのかかった顔を当てはめています。 face= img[y:y+h, x:x+w] small_pic = cv2.resize(face, (8,8)) mosaic = cv2.resize(small_pic ,(w,h)) img[y:y+h, x:x+w]=mosaic これで1人分の顔を切り取る→顔の周囲だけ縮小→元の大きさに戻す→切り取った場所に戻す、という作業をやっています。whileのループ処理で検出された顔の数だけこれを繰り返すことで、検出した顔のすべてにモザイク処理が施されるという理屈です。 cv2.imshow("photo_processed.jpg",img) cv2.waitKey() cv2.destroyAllWindows() 加工した写真を表示して完了。 #写真の中の顔を検出して自動でモザイクをかける処理 import cv2 cascade_file= "haarcascade_frontalface_default.xml" clas = cv2.CascadeClassifier(cascade_file) img = cv2.imread("photo.jpg") #写真の中の顔をすべて検出して周囲の四角形の座標を取得 #検出できた顔のすべてについて[四角の左上のx座標、四角の左上のy座標、幅、高さ]の数値を取得 face_list = clas.detectMultiScale(img, scaleFactor = 1.1, minSize=(30,30)) #モザイク処理 #[四角の左上のx座標、四角の左上のy座標、幅、高さ]をそれぞれ変数x, y, w, hに代入 for x, y, w, h in face_list: face= img[y:y+h, x:x+w] small_pic = cv2.resize(face, (8,8)) mosaic = cv2.resize(small_pic,(w,h)) img[y:y+h, x:x+w]=mosaic cv2.imshow("photo_processed.jpg",img) cv2.waitKey() cv2.destroyAllWindows() 「顔を検出」「モザイク処理を施す」という作業を理解できていたら思ったほど大変ではないなという印象でした。この後は動画の顔を検出してモザイク処理をやってみたいなと思います。
- 投稿日:2021-10-26T17:52:58+09:00
[Python] Seleniumの記述を簡単にするクラスを試作してみた
はじめに seleniumって書くのめんどくさいくないですか? 入力やクリックみたいな簡単な操作でも要素取って入力・クリックみたいに2手間かかったり、サイトの表示速度に応じてtime.sleep()に入れるパラメータの値を調整したり、ActionChainsを使うと記述量が多くなって可読性が下がったりで自分は結構面倒に感じてしまいます。 こういった面倒を解消したかったので、各操作を1つのメソッドで実行できるクラスを試作してみました。 クラス wait_driver.py from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions from selenium.webdriver.common.by import By from selenium.webdriver.common.action_chains import ActionChains import time class WaitDriver: TIMEOUT = 20 SLEEP = 1 def __init__(self, driver): if not isinstance(driver, webdriver.Chrome): raise("Invalid driver type, expected for Chrome") self.__driver = driver def get_driver(self): return self.__driver def set_driver(self, driver): if not isinstance(driver, webdriver.Chrome): raise("Invalid driver type, expected for Chrome") self.__driver = driver def get(self, url): self.__driver.get(url) def quit(self): self.__driver.quit() def find(self, xpath, timeout=TIMEOUT): wait = WebDriverWait(self.__driver, timeout) element = wait.until( expected_conditions.visibility_of_element_located( (By.XPATH, xpath) ) ) return element def send_keys(self, xpath, text, timeout=TIMEOUT, sleep=SLEEP): element = self.find(xpath, timeout) time.sleep(sleep) element.send_keys(text) return element def click(self, xpath, timeout=TIMEOUT, sleep=SLEEP): wait = WebDriverWait(self.__driver, timeout) element = wait.until( expected_conditions.element_to_be_clickable( (By.XPATH, xpath) ) ) time.sleep(sleep) element.click() return element def move_to_element(self, xpath, timeout=TIMEOUT, sleep=SLEEP): element = self.find(xpath, timeout) actions = ActionChains(self.__driver) actions.move_to_element(element) time.sleep(sleep) actions.perform() return element def drug_and_drop(self, draggable_xpath, droppable_xpath, timeout=TIMEOUT, sleep=SLEEP): source = self.find(draggable_xpath, timeout) target = self.find(droppable_xpath, timeout) actions = ActionChains(self.__driver) actions.click_and_hold(source) actions.move_to_element(target) time.sleep(sleep) actions.perform() return source, target 使い方 windowsでのselenium環境構築方法はこちらを参考にしてください。 1. WaitDriver(試作のやつ) test_selenium.py from selenium import webdriver import chromedriver-binary from wait_driver import WaitDriver driver = Webdriver.Chrome() # 作ったやつ wd = WaitDriver(driver) 2. メソッド test_selenium.py # 指定のURLにアクセス wd.get(url) #(指定の要素が表示されるまで待機して)取得、要素返却 element = wd.find(xpath) #(指定の要素がクリックできるまで待機して)クリック、要素返却 wd.click(xpath) #(指定の要素が表示されるまで待機して)入力、要素返却 wd.send_keys(xpath, text) #(画面外の指定した要素が表示されるまで待機して)スクロール、要素返却 element = wd.move_to_element(xpath) #(指定したドラッグ要素とドロップ要素が表示されるまで待機して)ドラッグ&ドロップ、2つの要素返却 wd.drug_and_drop(druggable_xpath, droppable_xpath) # Chromeドライバー返却(通常のChromeドライバーに切り戻したいときに使える) driver = wd.get_driver() # Chromeドライバーをセット wd.set_driver(driver) # Chromeを閉じる wd.quit() 各メソッドにはtimeoutとsleepの値を渡すこともできます。 timeoutは要素取得するまでに許容する時間です。時間内に要素が取得できなければタイムアウトになります。デフォルトは20秒。 sleepは要素取得してから次のアクションを起こすまでの時間です。これを設定しないと操作が早すぎてクローリングしていることがばれる可能性があるので1ないしは2秒以上を推奨します。デフォルトは1秒。 また、デフォルトの値自体を変更したい場合はWaitDriverのクラス変数(以下部分)を直接いじります。 wait_driver.py class WaitDriver: TIMEOUT = 20 SLEEP = 1 .... まとめ まだ十分には使ってないですが、ちょっと試した感じだとなかなかよさげなのでよかったら使ってみてください。まだメソッドが少ないですし、xpathでしか要素を取得できないのでそこは今後ブラッシュアップしていければと思います。
- 投稿日:2021-10-26T17:19:55+09:00
pytest の PytestUnknownMarkWarning 出力を抑制する
結論 以下の方針が良さそう。 marker の数が少なければ marker を登録する marker が多ければ filterwarnings を設定する テスト環境 $ python --version Python 3.8.5 $ pytest --version pytest 6.2.5 $ echo $OSTYPE darwin20.0 PytestUnknownMarkWarning pytest では marker を付けることができる。例えば以下のようなテストファイルがあったとして test_example.py import pytest @pytest.mark.testmarker1 def test_example1(): assert True @pytest.mark.testmarker2 def test_example2(): assert True 例えば次のように -m オプションで marker を指定すればその marker が付いたテストだけが実行される。この場合は test_example2。(-m でもう少し複雑な条件も指定できるがここでは割愛。) $ pytest -m testmarker2 しかし @pytest.mark.<marker> でデコレーションしただけでは以下のような warning が出力されてしまう。何も対策しないと鬱陶しいし、確認すべき warning や error が見づらくなるので出力を抑制したい。 =========================================== warnings summary =========================================== test_example.py:3 /Users/guest/test_example.py:3: PytestUnknownMarkWarning: Unknown pytest.mark.testmarker1 - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html @pytest.mark.testmarker1 test_example.py:7 /Users/guest/test_example.py:7: PytestUnknownMarkWarning: Unknown pytest.mark.testmarker2 - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html @pytest.mark.testmarker2 -- Docs: https://docs.pytest.org/en/stable/warnings.html ==================================== 2 passed, 2 warnings in 0.01s ===================================== 対策 1. markers を設定する warning メッセージに表示されている通り pytest のドキュメントページ を見るとこの方法が書かれている。pytest.ini もしくは pyproject.toml に自分が利用する marker の設定を記載するというもの。記述例は以下。 pytest.ini [pytest] markers = testmarker1 testmarker2 pyproject.toml [tool.pytest.ini_options] markers = [ "testmarker1", "testmarker2", ] 2. filterwarnings を設定する ドキュメントに記載されている通り、基本的には 1. がよいはずである。が、marker の数が多くなってくると面倒だし、test のメンテナンス(追加・削除)に応じて ini や toml を都度更新しないといけないのも何だかうれしくない。 検索して stackoverflow とか見てもイマイチで、ピンポイントで PytestUnknownMarkWarning だけ抑止するような方法が出てこない。ふと pytest 自体のソースコードを確認していると、pytest の pyproject.toml に以下のような設定が記載されていた(抜粋)。 pyproject.toml [tool.pytest.ini_options] filterwarnings = [ "ignore::_pytest.warning_types.PytestUnknownMarkWarning", ] コメントで # ignore use of unregistered marks, because we use many to test the implementation とまで書いてくれているので目的にも合致していそう。これを設定するとピンポイントで PytestUnknownMarkWarning が無視される。 pytest.ini であれば以下のように設定すればよい。 pytest.ini [pytest] filterwarnings = ignore::_pytest.warning_types.PytestUnknownMarkWarning
- 投稿日:2021-10-26T17:16:28+09:00
TwilioでCTI、IVRを作る
電話番号の取得 電話番号は各国の規制で、本人確認などが必要なため、対応する書類の提出が必要。 約3日の審査あり。 お試し版で最初に現れるUSの電話番号はすぐに使える(EmergencyCallのためUSの住所などを登録しないといけないが、使える。) 着信時のアクション設定 Phone Numbers - Manage - Active Numbers のなかの使用したい電話番号をクリック。 A CALL COMES INの部分に、着信した時点でHTTP POSTを飛ばすアドレスを記入する。 PRIMARY HANDLER FAILSは、A CALL COMES INが終わった段階で、実行されるようだ。音声案内のTwiML Binを作って設定してみた。 で、Save. ちなみに、TwiML Binの中身は、以下から作成した。 <?xml version="1.0" encoding="UTF-8"?> <Response> <Say language="ja-JP">お電話ありがとうございます。本日は営業を終了しています。またのお電話お待ちしております。</Say> </Response> Skypeなどで電話をかけてみると、電話がかかった時に、A CALL COMES INで指定したURLに情報が飛んでくる。 POSTだと、中身のParameterを取り出してみると、(XXXXで内容を変更しております。) [POST]|となっております。 2021-10-26 06:20:52,418 [views.py:1135] DEBUG ------------------post twilio--------------------- 2021-10-26 06:20:52,418 [views.py:1139] DEBUG [POST]AccountSid|AC27bcxxxxxxxxxxxxxxxxxxxxxxxxxxxx 2021-10-26 06:20:52,419 [views.py:1139] DEBUG [POST]ApiVersion|2010-04-01 2021-10-26 06:20:52,419 [views.py:1139] DEBUG [POST]CallSid|CA59xxxxxxxxxxxxxxxxxxxxxxxxxxxx 2021-10-26 06:20:52,419 [views.py:1139] DEBUG [POST]CallStatus|ringing 2021-10-26 06:20:52,419 [views.py:1139] DEBUG [POST]Called|+1540573XXX <=xxxxは私が伏せました。 2021-10-26 06:20:52,419 [views.py:1139] DEBUG [POST]CalledCity| 2021-10-26 06:20:52,419 [views.py:1139] DEBUG [POST]CalledCountry|US 2021-10-26 06:20:52,419 [views.py:1139] DEBUG [POST]CalledState|VA 2021-10-26 06:20:52,419 [views.py:1139] DEBUG [POST]CalledZip| 2021-10-26 06:20:52,419 [views.py:1139] DEBUG [POST]Caller|+26669XXXX <=Skypeから xxxxは私が伏せました。 2021-10-26 06:20:52,419 [views.py:1139] DEBUG [POST]CallerCity| 2021-10-26 06:20:52,419 [views.py:1139] DEBUG [POST]CallerCountry|LS 2021-10-26 06:20:52,419 [views.py:1139] DEBUG [POST]CallerState| 2021-10-26 06:20:52,419 [views.py:1139] DEBUG [POST]CallerZip| 2021-10-26 06:20:52,420 [views.py:1139] DEBUG [POST]Direction|inbound 2021-10-26 06:20:52,420 [views.py:1139] DEBUG [POST]From|+26669XXXX <=Skypeから xxxxは私が伏せました。 2021-10-26 06:20:52,420 [views.py:1139] DEBUG [POST]FromCity| 2021-10-26 06:20:52,420 [views.py:1139] DEBUG [POST]FromCountry|LS 2021-10-26 06:20:52,420 [views.py:1139] DEBUG [POST]FromState| 2021-10-26 06:20:52,420 [views.py:1139] DEBUG [POST]FromZip| 2021-10-26 06:20:52,420 [views.py:1139] DEBUG [POST]To|+15405732101 2021-10-26 06:20:52,420 [views.py:1139] DEBUG [POST]ToCity| 2021-10-26 06:20:52,420 [views.py:1139] DEBUG [POST]ToCountry|US 2021-10-26 06:20:52,420 [views.py:1139] DEBUG [POST]ToState|VA 2021-10-26 06:20:52,421 [views.py:1139] DEBUG [POST]ToZip| のようなデータを取得することができた。 さあ、これで着信した瞬間に、データベースから発信者情報を取り出して、ユーザーに情報を表示したりすることができそうだ。
- 投稿日:2021-10-26T17:09:18+09:00
分散分析を一つ一つみながら理解する
統計のなかの検定で一番ハードなのは分散分析かなと思います(あくまで統計検定2級とかの話) お作法としては淡々としてたり、統計検定では1から全て求めるみたいなことはありません。 しかし、その影響か「なんとなく」の理解で終わってしまっている方(=私)も多いのかなと思います。 そこで、理解の深化も兼ねて分散分析を(個人的主観で)丁寧に扱っていこうと思います。 いろんな知識が必要であるため一度に理解するのは大変ですが、頑張っていきましょう。 前提として何を言いたいのか? まずは分散分析をする目的を知りましょう。 分散が絡んでくることには間違いないのですが、目的は 「3つ以上の母集団のうち、それぞれの平均に差があるのかどうか?」 ということを知りたいときに分散分析が用いられます。 (※分散分析においては母集団を「級」と呼びます。のちに出てきます) 例えば - A, B, C高校の数学のテストの平均点に差があるのか? - 自動車のA, B, C社が作る燃料効率には差がありますか? というときは分散分析を使います。 大抵の検定は1つの統計量が本当に起こりうるのか?ということを重視し、等分散性でも2つの母集団のみで比べましたが、分散分析は3つ以上を対象とします。 実際に見ていく では、順番に流れを追っていきます 前提のデータ まずm個の母集団があり、各母集団から$n _1, n_2, ・・・, n_m$個の標本をとってきます。 (第i母集団: $n_i$) そして、これらのm個の母集団の要素はそれぞれ正規分布N($μ_i$, σ)に従うものとしています 注意点としてそれぞれの母集団の分散は全てσであり、共通しているという前提があります (これを事前に確かめるには、以前書いた等分散性をつかって検定しておきます。 今回はすでに分散には差がないことは確認済みであるとして進めます) 以下はそれぞれに得られる統計量を書き表したもの。 総標本数はNとしています。 帰無仮説 今回は平均の差がないことを言いたいので、 $H_0$: それぞれの母集団において平均$μ_i$には差がない とします。 少し言い換えると、 $$μ = \frac{\sum n_i μ_i}{N}$$ という全体平均を考えると、それぞれの母平均$μ_i$は $$μ_i = μ + α$$ と表現できるため、帰無仮説を言い換えて 任意のiでα=0である を示すことにもなります。 対立仮説 対立仮説としては - 少なくとも一つのiで差がある - 少なくとも一つのiでα ≠ 1 となる と設定します 検定 では、順に分散分析の流れを体験していきます。 まず全てのサンプルを使って平均$\bar{\bar{x}}$を求めると、 $$\bar{\bar{x}} = \frac{1}{N} \sum_{i=1}^{m} \sum_{j=1}^{n_i} x_{ij}$$ それぞれの母集団iに対しての標本平均を$\bar{x_i}$とすると $$\bar{x_i} = \frac{1}{n_i} \sum_{j=1}^{n_i}x_{ij}$$ ここで平均に対しての差を知りたいため、全体に対する偏差平方和Vを考えます \begin{align} V &= \sum_{i=1}^{m} \sum_{j=1}^{n_i} (x_{ij} - \bar{\bar{x}})^2 \\ &= \sum_{i=1}^{m} \sum_{j=1}^{n_i} (x_{ij} - \bar{x_i} + \bar{x_i} - \bar{\bar{x}})^2 \\ &= \sum_{i=1}^{m} \sum_{j=1}^{n_i} (x_{ij} - \bar{x_i})^2 + \sum_{i=1}^{m} \sum_{j=1}^{n_i} (\bar{x_i} - \bar{\bar{x}})^2 + 2\sum_{i=1}^{m} \sum_{j=1}^{n_i} (x_{ij} - \bar{x_i})(\bar{x_i} - \bar{\bar{x}}) \\ &= \sum_{i=1}^{m} \sum_{j=1}^{n_i} (x_{ij} - \bar{x_i})^2 + \sum_{i=1}^{m} \sum_{j=1}^{n_i} (\bar{x_i} - \bar{\bar{x}})^2 + 2\sum_{i=1}^{m} (\bar{x_i} - \bar{\bar{x}})\sum_{j=1}^{n_i} (x_{ij} - \bar{x_i}) \\ &= \sum_{i=1}^{m} \sum_{j=1}^{n_i} (x_{ij} - \bar{x_i})^2 + \sum_{i=1}^{m} \sum_{j=1}^{n_i} (\bar{x_i} - \bar{\bar{x}})^2 + 2\sum_{i=1}^{m} (\bar{x_i} - \bar{\bar{x}})(n_i * \bar{x_i} - n_i * \bar{x_i}) \\ &= \sum_{i=1}^{m} \sum_{j=1}^{n_i} (x_{ij} - \bar{x_i})^2 + \sum_{i=1}^{m} \sum_{j=1}^{n_i} (\bar{x_i} - \bar{\bar{x}})^2 \\ &= \sum_{i=1}^{m} \sum_{j=1}^{n_i} (x_{ij} - \bar{x_i})^2 + \sum_{i=1}^{m} n_i (\bar{x_i} - \bar{\bar{x}})^2 \\ &= V_1 + V_2 \end{align} 導出の過程自体は非常にシンプルな計算で導出できます これからわかるように、この全ての標本との総平均の差の偏差平方和は [(各要素(データ)-それぞれの要素が属する母集団の平均)の平方和] + [(各母集団の平均 - 全ての平均)の平方和] に帰着することになります。 今、すべての母集団の分散はσであることから、 各標本と標本平均の偏差平方和と分散の比である$χ^2$を考えると良さそう!ってなります 文章で書くと周りくどいですが、数式としては以下。 $χ^2 = \frac{(n-1)\hat{σ^2}}{σ^2}$ このことから、 $$\sum_{i=1}^{m} \sum_{j=1}^{n_i} (\frac{x_{ij} - \bar{\bar{x}}}{σ})^2 = \frac{V}{σ^2}$$ は自由度N - 1の$χ^2$分布に従います。 そして、$χ^2$の加法性からV1 ~ $χ^2$(N-m), V2 ~ $χ^2$(m-1)に従うことがわかります。 かなり、複合的なアプローチをしているため、十分に理解していないといけない感ありますね。。 ※各標本はN(μ, $\frac{σ^2}{n_i^2}$)に従うので、 $$\sum_{i=1}^{m} n_i (\frac{\bar{x_i} - \bar{\bar{x}}}{σ})^2 = \sum_{i=1}^{m} (\frac{\bar{x_i} - \bar{\bar{x}}}{\frac{σ}{\sqrt{n_i}}})^2 = \frac{V_2}{σ^2}$$ 今、$V_1, V_2$それぞれの$χ^2$分布の自由度がわかったのでF値を計算できるようになりました $$F_0 = \frac{\frac{V_2}{m-1}}{\frac{V_1}{N-m}}$$ つまり、このF値はF(m-1, N-m)の分布に従うため、あとは付表などで参照し、有意水準に達するかどうかを見ていきます。 そもそも帰無仮説は平均に差がないということなので、$F_0$は1に近いと想定されるはずですが、差があるとそれだけ数値は大きくなります。 用語の確認 なんの気無しに使ってきた$V_1, V_2$ですが、ちゃんと用語があります。 もう一度$V_1, V_2$がどういうものかおさらいしておくと \begin{align} V_1 &= \sum_{i=1}^{m} \sum_{j=1}^{n_i} (x_{ij} - \bar{x_i})^2 \\ V_2 &= \sum_{i=1}^{m} n_i (\bar{x_i} - \bar{\bar{x}})^2 \end{align} でした。 ちょっと最初に準備した標本の欄を見て確認しましょう だいたいこれがイメージであり、これが全てといった感じです。 各要素に対して全体を見ているVをブロックに切り分けていく、と思えば十分と思います。 それに上記の図からもそれぞれの自由度もわかると思います(級間変動は要素mなので、m-1となるとか) それぞれの普遍性 $χ^2$の性質に普遍性として$E[χ^2] = n-1(自由度)$となるため、以下が成り立ちます \begin{align} E[\frac{V_2}{σ^2}] &= m- 1 ⇨ E[\frac{V_2}{m- 1}] = σ^2 \\ \\ E[\frac{V_1}{σ^2}] &= N - m ⇨ E[\frac{V_1}{N - m}] = σ^2 \end{align} 分散分析表(よく見る「あの表」) 大抵のテキストに載っている分散分析表を見て理論の部分は締めくくりたいと思います。 といっても、上記の流れを簡略して書いているだけのため、さらっと見て終わりにしようと思います。 おそらく参考書によっては「分散分析はこの表でドン!」みたいに片付けていると思うのですが、なぜこんなことをしているのか?というと「分散の比が$χ^2$分布に従う」という性質を利用しているから自由度で割ったりしているわけです。 実際にこれらの表の全てを手計算するということは現実問題ほぼないと思いますが、統計検定では部分的に穴埋めすることもあるので、何しているのか?という理解はしておくと便利だと思います。 Pythonで分散分析してみる では、ラストは実際にデータを用意しながら分散分析をしてみます いつもながらコードの説明はコメントアウトなどのみで行っていきます。 手動でゴリゴリ import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from scipy import stats # 各母集団のデータを準備 rng = np.random.default_rng(seed=123) data1 = rng.integers(4, 10, size=10) data2 = rng.integers(4, 14, size=8) data3 = rng.integers(2, 9, size=13) # それぞれの平均を求め、級内変動・級間変動をみる mean_1 = np.mean(data1) mean_2 = np.mean(data2) mean_3 = np.mean(data3) mean_all = np.mean([mean_1, mean_2, mean_3]) # 級内変動 V_1_1 = sum((data1 - mean_1)**2) V_1_2 = sum((data2 - mean_2)**2) V_1_3 = sum((data3 - mean_3)**2) V1 = V_1_1 + V_1_2 + V_1_3 # 級間変動 V_2_1 = len(data1) * (mean_1 - mean_all)**2 V_2_2 = len(data2) * (mean_2 - mean_all)**2 V_2_3 = len(data3) * (mean_3 - mean_all)**2 V2 = V_2_1 + V_2_2 + V_2_3 print(f'V1:{V1:.2f}\nV2: {V2:.2f}') # 自由度 dof_all = len(data1) + len(data2) + len(data3) - 1 dof_V2 = 3 - 1 dof_V1 = dof_all - dof_V2 print(f'級内の自由度: {dof_V2}') print(f'級間の自由度: {dof_V1}') # 平均平方 V2_dof = V2 / dof_V2 V1_dof = V1 / dof_V1 # F値 F = V2_dof / V1_dof print(f'F値: {F:.3f}') # 分散分析(scipy) p_value = 1 - stats.f.cdf(F, dfn=dof_V2, dfd=dof_V1) print(f'分散分析(scipy): {p_value}') scipyなら一行 stats.f_oneway(data1, data2, data3) ちなみに、statsmodels.formula.apiのolsでも同様のことが可能ですが、今回意図的にサンプル数を変えた状態なので、もしDataFrameでも試してみたい!って場合はどぞ! 終わり 他のサイトよりはやや深めなアプローチができたかなと思いますが、かなりいろんな知識が必要だったりするので、難しいですね! ま、全部覚える必要もなく、わからないときに調べて思い出すくらいでちょうど良きかと思います! 今回参考にさせていただいた記事 scipy.stats.f pythonで統計学基礎:03 検定・分散分析 基本統計学
- 投稿日:2021-10-26T16:32:36+09:00
VScodeでdockerの開発環境(python)を使う。
Ubuntu20.04.3LTS Docker version 20.10.7 docker-compose version 1.29.1 VScode 1.61.1 RTX 3060 Driver Version: 470.74 python機械学習の開発環境をどうするか。 Anaconda:事業展開するときに有償ライセンスが必要かも知れない。 Docker Desktop:事業展開するときに有償ライセンスが必要かも知れない。 という背景から、ライセンスが緩いdockerとdocker-compose を使った方がいい?という動機。 最新の開発環境は、dockerで提供されるケースが増えているという助言も。 CUDAやcuDNN、tensorflowのバージョン合わせに疲れたこともある。 できれば、VScodeを使ってGUIで操作したという動機。 ここでは、VScodeの実行ボタンを押して、コンテナ内のpython上で実行させることを記述。 VScodeの拡張機能 VScodeのプロジェクトはフォルダ単位で管理される。ローカル(ホスト)とリモート(コンテナ)を行き来するので、どちらにいるのか気をつけておく。(左下の緑のリモートマークに注目) 以下の拡張機能をホスト側(通常インストールして開いた画面)でインストールする。 dockerの拡張機能を入れるとタブからコンテナ一覧を見たり、起動・削除ができるようになる。また、ファイルメニューからdockerfileを右クリックするとビルドできるが、今回の流れでは操作しない。コンテナのターミナルを起動することもできるが、混乱するので慣れるまでは使わない。 プロジェクトフォルダを開く dockerfileやdocker-compose.ymlがあるフォルダを開く。するとホストのファイルが表示される。dockerfileとdocker-compose.ymlの記載例は下の方にあります。その他、pythonのライブラリを一括でインストールするのに便利なrequirementsファイル、pythonのテスト用のファイルや(今回の流れでは無くても良いですが)学習管理サイトWeights&Biases(wandb)の設定ファイルなどが有ります。 コンテナのなかに入る 左下の「リモートウィンドウを開きます」 を押す。 ここから、Reopen in Containerを選ぶ。 すると、VScodeがdocker用の設定ファイルを作るベースを聞いてくる。Dockerfileのみの場合は、Dockerfile。docker-compose.ymlがあるならFrom docker-compose.ymlを選ぶ。 すると、左下のマークが替わり、コンテナに接続される。コンテナ内(リモート)の状態だ。 ホスト側のプロジェクトフォルダが、コンテナ内の/workspaceフォルダにデフォルトでマウントされる。このフォルダ内のファイルに対する変更や新規作成はホスト側に反映される。 同時に隠しフォルダ.devcontainerが作成されその下にdevcontainer.jsonとdocker-compose.ymlが作成させる。docker-compose.ymlは既存のと重複するが、隠しファイル内に作成されたものは、VScodeに必要な記述だけ記載するのが良いと思う。 コンテナ内でpythonファイルの実行 エクスプローラーからpythonのファイルを選ぶと実行ボタンがない。 メニューの実行からデバッグの開始やデバッグなしで実行を選ぶと、拡張機能がないと表示されるので、pythonの拡張をインストールする。ホストに拡張をインストールしていてもリモートのコンテナにはインストールしていないので必要になる。 拡張がインストールされると、右上に実行ボタンが表示されるようになった。 ちなみに最近、実行ボタンの色が緑から白に変わったようだ。 実行ボタンがうまく機能しない場合はpythonインタプリターの指定を確認する。 左下のを押すと、pythonのパスが複数表示される。これらは、ホスト側ではなく、リモート(コンテナ)側のパスである。 端末からwhich pythonを実行して一致するものを選ぶ。(今回の場合pythonとpython3はどちらも一緒) ホスト側に戻る 左下のボタンを押し、「リモート接続を終了する」を選ぶ。 VScodeが作業開始待ちの初期状態に戻る。 docker拡張のタブを見てみると、先ほどのコンテナが休止しているのが分かる。pruneボタンを押すと休止中のコンテナの削除ができる。 コンテナ用の拡張がビルド時にインストールされるように設定する dockerfileなどを変更して再ビルドを繰り返すとき、拡張が自動で入る方が楽です。この場合、隠しフォルダ内のdevcontainer.jsonに拡張機能の識別子を記述します。 識別子は拡張をクリックした画面の詳細情報のなかに記載がある。 コンテナ内で一般ユーザーとして実行する コンテナ内でマウントしたファイルを操作すると、所有者がrootに変わってしまい面倒だが、自分のdockerの使い方が違うのか?下に示すdockerfileの記述で一般ユーザーとしてファイル作成・上書きができる。コンテナ内のユーザー名はホスト側のユーザー名と異なっても大丈夫。コンテナ内で作成したファイルは、ホスト側から見るとホストの所有になる。ls -laで確認する。 今回使用したdockerfile # tensorflowがGPU上で使えるイメージ FROM nvcr.io/nvidia/tensorflow:21.09-tf2-py3 # ユーザーを作成 ARG UID=1000 RUN useradd -m -u ${UID} user # 作成したユーザーに切り替える # このユーザーはRUN, CMD, ENTRYPOINT, docker run, exec の実行ユーザ。 USER ${UID} # プロジェクトフォルダを/codeに追加する。ユーザー権限で扱えるようchownオプションを使う。 # ADDの実行権者はrootなのでオプションが必要。 ADD --chown=user:user . /code # 作成したフォルダに移動し、パッケージリストをインストールする。 WORKDIR /code RUN pip install -r requirements.txt # 作成時にホームディレクトリに置きたいファイルがあれば RUN cp .netrc ~ ここで呼び出しているrequirements.txt。利用したいパッケージを記載。うまく行っている環境があればpip freezeで取得。 requirements.txt matplotlib jupyter wandb あらかじめ作成しておいた、docker-compose.ymlの記述。 dockerfileで大枠を記載して、docker-compose.ymlで細かな部分を記述する感じ。docker-compose.ymlだけでも書けるらしい。 docker-compose.yml version: '3'# 3.2とかも記載できる。2,3系では命令が少し異なるらしい services: gpu_test: build: . # ドットはdockerfileの記述を使う意味 deploy: resources: reservations: devices: # GPUを利用するための記述。ホスト側はドライバだけ入れればいい。cudaとか不要。 - driver: nvidia count: 1 capabilities: [gpu] ports: - "8888:8888" #ジュピターノートブック用のポート hostname to 0.0.0.0 volumes: - /media/DATA_drive/DATA:/data:cached # データフォルダをマウント - .:/code:cached # ソースコードがあるフォルダをマウント tty: true 隠しフォルダに作成されたdocker-compose.yml 抜粋 docker-compose.yml #volumes: # Update this to wherever you want VS Code to mount the folder of your project #- .:/workspace:cached 上述のdocker-compose.ymlで記述したためコメントアウト # Overrides default command so things don't shut down after the process ends. # コンテナが自動終了しないようにするらしい command: /bin/sh -c "while sleep 1000; do :; done" devcontainer.json抜粋 devcontainer.json // The optional 'workspaceFolder' property is the path VS Code should open by default when // connected. This is typically a file mount in .devcontainer/docker-compose.yml "workspaceFolder": "/code",# ソースファイルをマウントしたフォルダを指定 // Add the IDs of extensions you want installed when the container is created. "extensions": ["ms-python.python"] # ビルド時に入れたい拡張機能を記述 jupyter notebookの起動 コンテナ内のターミナルから、jupyter notebook で起動。 表示されたURLをホストのブラウザで開く。hostnameの部分は0.0.0.0に変える必要があった。
- 投稿日:2021-10-26T15:28:17+09:00
PythonのMunkresを用いて割り当て問題を解く
【例題】以下の行列の各行から1つの数を選んで足した最小値を求めよ。ただし各列からは1つしか選べない。 A B C D E a 765 530 183 439 863 b 497 383 563 790 973 c 287 630 343 169 583 d 627 343 773 959 943 e 767 473 103 699 303 これをまともに解くとO(n!)となりますが、以下の記事を参考にしてPython のMunkresを使ってみました。 割当問題のハンガリアン法をpythonで実装してみた まずmunkresをインストールします。 pip install munkres あとは行列をMunkresに渡すだけという簡単なものです。注意点はarrayをMunkresに渡すと中の値が変更されてしまうのでcopyしてから渡したほうが良いですね。 from munkres import Munkres import numpy as np import copy mtx = np.array([[765, 530, 183, 439, 863], [497, 383, 563, 790, 973], [287, 630, 343, 169, 583], [627, 343, 773, 959, 943], [767, 473, 103, 699, 303]]) ansMtx = Munkres().compute(copy.copy(mtx)) asum = sum([mtx[idx] for idx in ansMtx]) print(ansMtx) print(f"Minmum sum = {asum}") #[(0, 2), (1, 0), (2, 3), (3, 1), (4, 4)] #Minmum sum = 1495 これはProject Euler P345を解くのに役に立ちました。
- 投稿日:2021-10-26T15:14:27+09:00
python初心者のはじめの一歩
環境 windows10 python3.9 エディタ VSCode pythonライブラリ [pandas] pythonにはライブラリが複数ありますが、今回はcsvデータを読み込んで 色々と加工するときに便利なpandasライブラリを使用します。 ライブラリを使用する時には、importを使用。 これは、どの言語でもだいたい同じですね。 import pandas as pd csvデータを読み込んで表示してみよう! csv読み込みは、pandasライブラリの中の「read_csv」関数を使用します。 csvのパスですが、バックスラッシュと¥記号で、躓きました。 データを読み込んだら、表示するだけです。 表示は、様々な方法があるので、以下をコピペして実行してみてください。 import pandas as pd #msg = "Hello World" #print(msg) # \\や/に注意!! # ↓↓ココにハマった df_population_data = pd.read_csv('C:\\Users\\xxx\\py_work\\data\\data.csv',encoding='shift-jis') df_population_data = pd.read_csv('C:/Users/xxx/py_work/data/data.csv',encoding='shift-jis') pd.set_option('display.max_columns', 10) # 最大表示列数 pd.set_option('display.max_rows', 10) # 最大表示行数 # 全データ表示 print(df_population_data) # 先頭10行表示 print(df_population_data.head(10)) #デフォルトは、先頭・末尾5行 print(df_population_data.head()) print(df_population_data.tail()) # 50~55行目を表示 print(df_population_data[50:55]) # 先頭行の要素を表示 print(df_population_data.iloc[0]) # 都道府県コードの表示 要素1つ目 print(df_population_data.index.values) お試しで、全データを表示してみます。 > & C:/Users/xxx/AppData/Local/Programs/Python/Python39/python.exe c:/Users/xxx/py_work/test2.py 都道府県コード 都道府県名 元号 和暦(年) 西暦(年) 人口(総数) 人口(男) 人口(女) 0 1 北海道 大正 9.0 1920.0 2359183 1244322 1114861 1 2 青森県 大正 9.0 1920.0 756454 381293 375161 2 3 岩手県 大正 9.0 1920.0 845540 421069 424471 3 4 宮城県 大正 9.0 1920.0 961768 485309 476459 4 5 秋田県 大正 9.0 1920.0 898537 453682 444855 .. ... ... .. ... ... ... ... ... 934 43 熊本県 平成 27.0 2015.0 1786170 841046 945124 935 44 大分県 平成 27.0 2015.0 1166338 551932 614406 936 45 宮崎県 平成 27.0 2015.0 1104069 519242 584827 937 46 鹿児島県 平成 27.0 2015.0 1648177 773061 875116 938 47 沖縄県 平成 27.0 2015.0 1433566 704619 728947 参考 こちらを参考にしました。 感想 今まで、C,C++,Javaなどで開発をしてきましたが、csv読み込みを1行で行えるなんて! しかもfor文を使用しなくていいなんて!!!と感動しっぱなしでした。 他のライブラリも使用してみたいと思います。
- 投稿日:2021-10-26T15:13:24+09:00
Switchbot Meterから温度と湿度を取得しMuninで可視化するメモ
概要 Switchbot Meterから温度と湿度をLinuxサーバで取得して、Muninでグラフにしたメモです。 Switchbot Meterとは? SwitchBot社が販売している温湿度計です。見た目は単なるデジタル温湿度計ですが、同社のスマートリモコン製品と連携できたり、BluetoothやWebAPIで温度・湿度の値を取得することができたりと、アイデア次第で様々なことに利用できる製品です。 価格も定価で1980円と、この手の製品としては格安です。また、しょっちゅう10%引きや20%引きのセールをやっているので、安いときには1500円くらいから手に入ります。私はついつい4台購入してしまいました。 ちょっと余談ですが、私は以前から自宅でPCsensor社のUSB接続の温度計を使って自宅の温度をモニタリングしていました。 参考: USB温度計・湿度計で温度・湿度を可視化していた記事 それはそれでよかったのですが、USB接続なのでケーブル長に限界があり設置場所が限られました。また、自分で校正する必要があったので、管理が面倒でした。 Switchbot Meterはそれらの課題をクリアすることができ、自宅の温度をモニタリングしたい人にはぴったりの製品だと言えます。両者の違いをまとめると以下のようになります。欲を言えばUSB給電にも対応してほしかったですが、この価格でこの機能を実現しているのは驚異的だと思います。 Switchbot Meter PCsensor TEMPer 価格 1980円 1000~2000円 API Bluetooth, WebAPI(仕様公開) USB(公式には仕様非公開) 校正 済 ユーザにて実施する必要あり 電源 単4電池2本 USB Muninとは? サーバやネットワーク機器の様々なリソースをグラフ化できるRRDtoolのフロントエンドです。2003年頃から開発され、2021年時点でもメンテナンスが続いている歴史のあるソフトウェアです。 柔軟なプラグインアーキテクチャを採用しているのが特徴で、サードパーティのプラグインも豊富にあります。今回私もSwitchbot Meter用のプラグインを作成しました。 Bluetooth編 必要なもの Bluetooth経由でデータを取得するには以下のものが必要です。 Switchbot Meter(Hubは必須ではありません) Switchbotアプリ(iOS/Android) Bluetooth(BLE) レシーバー BluetoothレシーバーはBLEに対応した↓のような製品が使用できます。 https://www.amazon.co.jp/dp/B07NQ5YGDW/ref=cm_sw_em_r_mt_dp_dl_W3TBCK5S1F2RYN5Z172Q Bluetoothは直線距離だと10~20mくらいは電波が届くことになっていますが、宅内では壁やドアの影響をそれなりに受けます。 なるべくレシーバーからSwitchbot Meterまでの見通しがよい場所に設置するなどの工夫が必要かもしれません。 事前準備 Muninのインストール munin, munin-node についてはすでに動作しているものとして進めます。 Redhat系やDebian系のLinuxなら、パッケージを使ってインストールするのが楽だと思います。 Bluetoothレシーバーの確認 Bluetoothを扱うのでなんらかのBluetoothレシーバーが必要になります。hciconfigコマンドで以下のようにHCIデバイスが見えていれば動作する可能性が高いと思います。 $ hciconfig hci0: Type: Primary Bus: USB BD Address: 00:1A:xx:xx:xx:xx ACL MTU: 310:10 SCO MTU: 64:8 UP RUNNING RX bytes:10233604 acl:0 sco:0 events:313292 errors:0 TX bytes:25868 acl:0 sco:0 commands:3310 errors:0 ... Pythonモジュールのインストール PythonでBluetoothを扱うのでbluepyモジュールをインストールしてください。 たとえばUbuntuなら以下のようなコマンドでインストールできます。 $ sudo apt install python3-pip $ sudo pip3 install bluepy Muninの設定例 Muninのプラグインを作成して、本家のリポジトリにマージしてもらいました。 このプラグインをmunin-nodeが動作しているサーバの /etc/munin/plugins 配下などにコピーしてください。 プラグインの設定ファイルを /etc/munin/plugin-conf.d 配下などに作成してください。 userとenv.macaddrは必須です。MACアドレスは、SwitchBotアプリの温湿度計の設定画面の「デバイス情報」から確認することができます。 [switchbotmeterbt] user root env.macaddr aa:aa:aa:aa:aa:aa bb:bb:bb:bb:bb:bb cc:cc:cc:cc:cc:cc 値が取得できていれば、以下のようなグラフが作成されます。 温度はこんな感じです。部屋によって傾向が違って面白いです。青色のグラフの部屋は西日が入るので、晴れた日の夕方には温度が高くなっています。 湿度はこんな感じです。こちらも部屋によって傾向が違いますね。黄色のグラフはカメラの防湿庫の中に置いた機器です。やはり湿度が安定してますね。 ついでに信号強度(RSSI)もグラフにしました。青色のグラフは少し離れた部屋に置いている機器なので時折電波が途切れています。-95dBを下回ると受信するのが難しくなるようです。 バッテリー残量も取得できるのでこちらもついでにグラフにしました。乾電池なのでどのくらい持つのか気になるところですが、単4電池2本で1年以上は余裕で持つらしいです。1ヶ月ほど使用していますが、まだ100%から減っていません。 参考情報 こちらのサンプルコードを流用させていただき、Munin Pluginを作成しました。 こちらは公式のAPIドキュメントです。この手の製品で仕様が公開されているのはとてもありがたいです。 WebAPI編 必要なもの Bluetoothが使えない場合は、WebAPI経由で温度・湿度を取得することも可能です。こちらはクラウドにデータをアップロードする必要があるので、Switchbot Hubが必要になります。 Switchbot Meter Switchbot Hub(クラウド連携のため) Switchbotアプリ(iOS/Android) 事前準備 トークンの取得 Bluetoothを使う方法とは異なり、Switchbot OpenAPIというクラウドサービスを利用します。APIを利用するには、アカウントの登録とトークンの取得が必要になります。トークンは以下の手順で取得できます。たとえが古いですが、トークン取得のやり方が昔のファミコンの裏技みたいだと思いました。 iOS/AndoroidでSwitchbot Appをインストールする。 アカウントを登録する。 Switchbot Appから、OpenAPI用のTokenを生成する。 「プロフィール」画面を開く。 「設定」画面を開く。 「アプリバージョン」を10回タップする。 「開発者向けオプション」が表示されるので、その画面から「トークンを取得」ボタンを押してトークンを生成する。 参考: Switchbot OpenAPIの利用手順・技術仕様 デバイスIDの取得 これでAPIにアクセスできるようになっているので、curlコマンド等でAPIを利用することができます。以下のURLにアクセスすると、デバイスの一覧を取得できるので、目的のSwitchbot Meterの"deviceId"をメモしておきます。devicdIdはMACアドレスから「:」を除いたものなので、Switchbotアプリから確認してもいいです。 $ curl -s -H 'Authorization:xxxxxx' https://api.switch-bot.com/v1.0/devices | jq . { "statusCode": 100, "body": { "deviceList": [ { "deviceId": "ECA9xxxxxxxx", # ←これ "deviceName": "温湿度計 xx", "deviceType": "Meter", "enableCloudService": true, "hubDeviceId": "F944xxxxxxxx" }, ... 今回は温度と湿度の取得しかしていませんが、OpenAPIを使うと、curlコマンド等でSwitchbot Hubに接続された製品を操作できるので楽しいです。REST APIでエアコンのON/OFFなどができて夢が広がります。詳しくは参考情報のURLをご参照ください。 Muninの設定例 Muninのプラグインを作成して、こちらも本家のリポジトリにマージしてもらいました。 このプラグインをmunin-nodeが動作しているサーバの /etc/munin/plugins 配下などにコピーしてください。 プラグインの設定ファイルを /etc/munin/plugin-conf.d 配下などに作成してください。env.tokenとenv.deviceidは必須です。 [switchbotmeter] env.token xxxxxxxx env.deviceid xxxxxxxx env.interval 900 1点注意点があります。Switchbot OpenAPIは、呼び出し回数を1日1000回に制限しています。munin-nodeはデフォルトだと5分に1回APIにアクセスするので、1日288回アクセスすることになります。アクセス回数が気になる場合は、env.intervalを指定することでアクセス頻度を下げることができます。上記の例だと900秒(15分)間は結果をキャッシュするようになるので、アクセス回数は1日あたり96回になります。 値が取得できていれば、以下のようなグラフが作成されます。Bluetoothとは異なり、取得できる値は温度と湿度のみで、受信強度やバッテリー残量は取得できませんでした。 参考情報 Switchbot OpenAPIの利用手順・技術仕様 まとめ Switchbot Meterから温度・湿度を取得して、Muninで可視化する方法をご紹介しました。Pythonやシェルスクリプトでハードウェアを操作できるのは楽しかったです。 自宅のリモコンがある家電製品はほとんど全てSwitchbot Hubで操作できるようにしたので、私の自宅はスマートホーム的なものに近づいたように思います。ちょっと未来を感じました。
- 投稿日:2021-10-26T15:13:24+09:00
Switchbot Meterから温度・湿度を取得して、Muninで可視化するメモ
概要 Switchbot Meterから温度と湿度をLinuxサーバで取得して、Muninでグラフにしたメモです。 Switchbot Meterとは? SwitchBot社が販売している温湿度計です。見た目は単なるデジタル温湿度計ですが、同社のスマートリモコン製品と連携できたり、BluetoothやWebAPIで温度・湿度の値を取得することができたりと、アイデア次第で様々なことに利用できる製品です。 価格も定価で1980円と、この手の製品としては格安です。また、しょっちゅう10%引きや20%引きのセールをやっているので、安いときには1500円くらいから手に入ります。私はついつい4台購入してしまいました。 ちょっと余談ですが、私は以前から自宅でPCsensor社のUSB接続の温度計を使って自宅の温度をモニタリングしていました。 参考: USB温度計・湿度計で温度・湿度を可視化していた記事 それはそれでよかったのですが、USB接続なのでケーブル長に限界があり設置場所が限られました。また、自分で校正する必要があったので、管理が面倒でした。 Switchbot Meterはそれらの課題をクリアすることができ、自宅の温度をモニタリングしたい人にはぴったりの製品だと言えます。両者の違いをまとめると以下のようになります。欲を言えばUSB給電にも対応してほしかったですが、この価格でこの機能を実現しているのは驚異的だと思います。 Switchbot Meter PCsensor TEMPer 価格 1980円 1000~2000円 API Bluetooth, WebAPI(仕様公開) USB(公式には仕様非公開) 校正 済 ユーザにて実施する必要あり 電源 単4電池2本 USB Muninとは? サーバやネットワーク機器の様々なリソースをグラフ化できるRRDtoolのフロントエンドです。2003年頃から開発され、2021年時点でもメンテナンスが続いている歴史のあるソフトウェアです。 柔軟なプラグインアーキテクチャを採用しているのが特徴で、サードパーティのプラグインも豊富にあります。今回私もSwitchbot Meter用のプラグインを作成しました。 Bluetooth編 必要なもの Bluetooth経由でデータを取得するには以下のものが必要です。 Switchbot Meter(Hubは必須ではありません) Switchbotアプリ(iOS/Android) Bluetooth(BLE) レシーバー BluetoothレシーバーはBLEに対応した↓のような製品が使用できます。 Bluetoothは直線距離だと10~20mくらいは電波が届くことになっていますが、宅内では壁やドアの影響をそれなりに受けます。 なるべくレシーバーからSwitchbot Meterまでの見通しがよい場所に設置するなどの工夫が必要かもしれません。 事前準備 Muninのインストール munin, munin-node についてはすでに動作しているものとして進めます。 Redhat系やDebian系のLinuxなら、パッケージを使ってインストールするのが楽だと思います。 Bluetoothレシーバーの確認 Bluetoothを扱うのでなんらかのBluetoothレシーバーが必要になります。hciconfigコマンドで以下のようにHCIデバイスが見えていれば動作する可能性が高いと思います。 $ hciconfig hci0: Type: Primary Bus: USB BD Address: 00:1A:xx:xx:xx:xx ACL MTU: 310:10 SCO MTU: 64:8 UP RUNNING RX bytes:10233604 acl:0 sco:0 events:313292 errors:0 TX bytes:25868 acl:0 sco:0 commands:3310 errors:0 ... Pythonモジュールのインストール PythonでBluetoothを扱うのでbluepyモジュールをインストールしてください。 たとえばUbuntuなら以下のようなコマンドでインストールできます。 $ sudo apt install python3-pip $ sudo pip3 install bluepy Muninの設定例 Muninのプラグインを作成して、本家のリポジトリにマージしてもらいました。 このプラグインをmunin-nodeが動作しているサーバの /etc/munin/plugins 配下などにコピーしてください。 プラグインの設定ファイルを /etc/munin/plugin-conf.d 配下などに作成してください。 userとenv.macaddrは必須です。MACアドレスは、SwitchBotアプリの温湿度計の設定画面の「デバイス情報」から確認することができます。 [switchbotmeterbt] user root env.macaddr aa:aa:aa:aa:aa:aa bb:bb:bb:bb:bb:bb cc:cc:cc:cc:cc:cc 値が取得できていれば、以下のようなグラフが作成されます。 温度はこんな感じです。部屋によって傾向が違って面白いです。青色のグラフの部屋は西日が入るので、晴れた日の夕方には温度が高くなっています。 湿度はこんな感じです。こちらも部屋によって傾向が違いますね。黄色のグラフはカメラの防湿庫の中に置いた機器です。やはり湿度が安定してますね。 ついでに信号強度(RSSI)もグラフにしました。青色のグラフは少し離れた部屋に置いている機器なので時折電波が途切れています。-95dBを下回ると受信するのが難しくなるようです。 バッテリー残量も取得できるのでこちらもついでにグラフにしました。乾電池なのでどのくらい持つのか気になるところですが、単4電池2本で1年以上は余裕で持つらしいです。1ヶ月ほど使用していますが、まだ100%から減っていません。 参考情報 こちらのサンプルコードを流用させていただき、Munin Pluginを作成しました。 こちらは公式のAPIドキュメントです。この手の製品で仕様が公開されているのはとてもありがたいです。 WebAPI編 必要なもの Bluetoothが使えない場合は、WebAPI経由で温度・湿度を取得することも可能です。こちらはクラウドにデータをアップロードする必要があるので、Switchbot Hubが必要になります。 Switchbot Meter Switchbot Hub(クラウド連携のため) Switchbotアプリ(iOS/Android) 事前準備 トークンの取得 Bluetoothを使う方法とは異なり、Switchbot OpenAPIというクラウドサービスを利用します。APIを利用するには、アカウントの登録とトークンの取得が必要になります。トークンは以下の手順で取得できます。たとえが古いですが、トークン取得のやり方が昔のファミコンの裏技みたいだと思いました。 iOS/AndoroidでSwitchbot Appをインストールする。 アカウントを登録する。 Switchbot Appから、OpenAPI用のTokenを生成する。 「プロフィール」画面を開く。 「設定」画面を開く。 「アプリバージョン」を10回タップする。 「開発者向けオプション」が表示されるので、その画面から「トークンを取得」ボタンを押してトークンを生成する。 参考: Switchbot OpenAPIの利用手順・技術仕様 デバイスIDの取得 これでAPIにアクセスできるようになっているので、curlコマンド等でAPIを利用することができます。以下のURLにアクセスすると、デバイスの一覧を取得できるので、目的のSwitchbot Meterの"deviceId"をメモしておきます。devicdIdはMACアドレスから「:」を除いたものなので、Switchbotアプリから確認してもいいです。 $ curl -s -H 'Authorization:xxxxxx' https://api.switch-bot.com/v1.0/devices | jq . { "statusCode": 100, "body": { "deviceList": [ { "deviceId": "ECA9xxxxxxxx", # ←これ "deviceName": "温湿度計 xx", "deviceType": "Meter", "enableCloudService": true, "hubDeviceId": "F944xxxxxxxx" }, ... 今回は温度と湿度の取得しかしていませんが、OpenAPIを使うと、curlコマンド等でSwitchbot Hubに接続された製品を操作できるので楽しいです。REST APIでエアコンのON/OFFなどができて夢が広がります。詳しくは参考情報のURLをご参照ください。 Muninの設定例 Muninのプラグインを作成して、こちらも本家のリポジトリにマージしてもらいました。 このプラグインをmunin-nodeが動作しているサーバの /etc/munin/plugins 配下などにコピーしてください。 プラグインの設定ファイルを /etc/munin/plugin-conf.d 配下などに作成してください。env.tokenとenv.deviceidは必須です。 [switchbotmeter] env.token xxxxxxxx env.deviceid xxxxxxxx env.interval 900 1点注意点があります。Switchbot OpenAPIは、呼び出し回数を1日1000回に制限しています。munin-nodeはデフォルトだと5分に1回APIにアクセスするので、1日288回アクセスすることになります。アクセス回数が気になる場合は、env.intervalを指定することでアクセス頻度を下げることができます。上記の例だと900秒(15分)間は結果をキャッシュするようになるので、アクセス回数は1日あたり96回になります。 値が取得できていれば、以下のようなグラフが作成されます。Bluetoothとは異なり、取得できる値は温度と湿度のみで、受信強度やバッテリー残量は取得できませんでした。 参考情報 Switchbot OpenAPIの利用手順・技術仕様 まとめ Switchbot Meterから温度・湿度を取得して、Muninで可視化する方法をご紹介しました。Pythonやシェルスクリプトでハードウェアを操作できるのは楽しかったです。 自宅のリモコンがある家電製品はほとんど全てSwitchbot Hubで操作できるようにしたので、私の自宅はスマートホーム的なものに近づいたように思います。ちょっと未来を感じました。
- 投稿日:2021-10-26T14:55:45+09:00
【2021年度10月版】Django REST frameworkをDockerで動かす(Pipenv、MySQL、pre-commit導入)
皆さん一度は聞いたことがあるであろうPythonの代表的なフレームワークDjango。 先週ぐらい前に初めて触れたのですが、開発効率がとんでもなく上がりますね。 なんといってもREST APIの構築がかなり簡単に行なえますし、今までAPIの構築をAPI GatewayやLambdaで構築していたのが時間の無駄だったのではないかと思うようになりました。 MySQL、Oracle、PosgreSQLなどのデータベースも豊富用意されていますし、今後はサーバーサイドの開発はDjangoで開発することが増えてきそうですね。 そんなDjangoをDockerで動かしてみようという記事になります。 私自身、Djangoに触れてからわずか1週間しか経っておらず、普段はフロントエンドを中心に開発しておりますので、記事の内容に不備がある可能性がございますのでその点はご了承下さい。 注意事項 Python2系はどうやらサポートが終了してるそうなので。、Python3系を使用します。 データベースはMySQLを使用しますが、他のデータベースを使用する場合はデータベースの設定の部分だけ適時変えて下さい。(インストールするライブラリとデータベースの設定ファイルを変えるだけなので、そこを変えたからと言ってエラーが出るということはほぼ無いと思います。) Macでの環境を前提に話を進めていきます。 APIやDBの設定の説明などは、既に記事を出している方も多いので省きます。 また、筆者の環境は以下になります。 項目 バージョン OS Big Sur v11.6(M1チップ) python3 3.9.7 pip 21.3 dokcer 20.10.8 それでは、多少長めの記事となっておりますが頑張っていきましょう! 事前準備 DjangoをDockerで動かす前に、まずは、必要なものをインストールしておきましょう。 Python・Pipコマンド確認 バージョン確認などでPython3コマンドとPipコマンドが使えるかの確認。 $ python3 --version $ pip --version Docker Desktop Docker専用のデスクトップアプリをダウンロードして下さい。 ただし、プロセッサーがIntelの場合とM1の場合でダウンロードするファイルが違うので、下記のアドレスを参考に今一度ご自身のプロセッサーをご確認下さい。 https://helpx.adobe.com/jp/download-install/kb/apple-silicon-m1-chip.html 下記のリンクから自分の環境に合ったDokcerをインストール。 プロセッサー バージョン Intel Mac with Intel chip M1 Mac with Apple chip https://docs.docker.com/desktop/mac/install/ VSCode拡張機能 拡張機能もインストール。 ・https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker Dockerはコンテナにアクセスするときに、長いコマンドを打つ羽目になるので上記の拡張機能をインストールしておくことをオススメします。 MySQL DjangoでMySQLを使用する場合mysqlclientというライブラリを使用するのですが、MySQLライブラリをインストールしていないとエラーが出るためインストール。 $ brew install mysql Pipenv プロジェクトごとにライブラリを管理するためにPipenvをインストール。 Nodeでいうnpmやyarnのことです。 $ brew install pipenv $ pipenv --version Django環境構築 それでは本題のDjangoをDockerで動かしていきましょう。 Docker Desktopを立ち上げた上で行って下さい。(dockerコマンドが打てないため) 必要なライブラリのインストール $ pipenv install django $ pipenv install djangorestframework $ pipenv install django-filter Docker設定ファイル $ touch Dockerfile docker-compose.yml Dockerfile FROM python:3.9 ENV PYTHONUNBUFFERED 1 WORKDIR /code ADD Pipfile* /code/ RUN cd /code && \ pip install -U pip && \ pip install pipenv && \ pipenv install --system --ignore-pipfile --deploy COPY . /code/ docker-compose.yml docker-compose.yml version: '3' services: db: platform: linux/x86_64 image: mysql:8.0 container_name: example_db environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: example-db MYSQL_USER: example MYSQL_PASSWORD: example-password TZ: 'Asia/Tokyo' command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci web: build: . container_name: example_web_app command: python3 manage.py runserver 0.0.0.0:8000 volumes: - .:/code ports: - "8000:8000" depends_on: - db Djangoプロジェクト作成 $ docker-compose run web django-admin.py startproject example . この場合、exampleディレクトリが作成され、このディレクトリを起点に設定などを行っていきます。 MySQL設定 DjangoはデフォルトではSQLiteが使われているので、これをMySQLに変更。 example/settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'example-db', 'USER': 'example', 'PASSWORD': 'example-password', 'HOST': 'db', 'PORT': '3306' } } docker-compose.ymlと値を一致させて下さい。 docker-compose.yml settings.py MYSQL_ROOT_PASSWORD なし MYSQL_DATABASE NAME MYSQL_USER USER MYSQL_PASSWORD PASSWORD ローカルサーバー立ち上げ ビルドして立ち上げます。(Pipenvで動かしているため少し時間がかかります) $ docker-compose up -d --build そして、http://localhost:8000へアクセス。 もしローカルサーバーが立ち上がらない場合は、VSCodeの左側にDockerのマークがあると思うのでそこをクリック。(拡張機能をインストールしないと表示されない) そこで、mysqlじゃない方を右クリックし「Restart」を選択。 するとローカルサーバーが起動します。 MySQLにアクセス VSCodeの左側のDockerのマークをクリック。 そのマークをクリックし、「mysql:8.0」で右クリック → 「Attch Shell」を選択。 $ mysql -u root -p > Enter password: docker-compose.ymlのMYSQL_ROOT_PASSWORDの値を入力 MySQLにアクセスできればOK。 試しにデータベースの一覧を表示してみましょう。 mysql > show databases; > docker-compose.ymlのMYSQL_ROOT_PASSWORDの値を入力 +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | example-db | | sys | +--------------------+ 5 rows in set (0.02 sec) これでDjangoをDockerで動かすことが出来ましたね。 独自スクリプト・pre-commit導入 最後に快適な環境を手に入れるために独自スクリプトの追加とpre-commitを導入していきます。 独自スクリプト VueやReactで開発するときってローカルサーバーを立ち上げる場合、npm run startなどのコマンドを打ちますよね。 それらはpackage.jsonで定義されているのですが、それと同様にPipenvを使えば同じようにローカルサーバーを立ち上げたり、ビルドなども行えます。 現にビルドコマンドを追加してみます。 ディレクトリ直下のPipfileに[scripts]を追加。 [scripts] build = "docker-compose up -d --build" そしたら、pipenv runでこのコマンドを打ってみます。 $ pipenv run build すると、先程と同じようにビルドが開始されます。 あとは自分の好きなようにカスタマイズしてみて下さい! pre-commit pre-commitを導入する前に、まずはflake8とautopep8を導入します。 flake8 falke8とはPython版のESLintです。 とりあえずインストール。 $ pipenv install --dev flake8 flake8をスクリプトで走らせてみましょう。 Pipfileのscriptsに追加。 [scripts] build = "docker-compose up -d --build" lint = "flake8 ." $ pipenv run lint autopep8 お次はautopep8(Python版のPrettier)を導入しましょう。 $ pipenv install --dev autopep8 同じようにスクリプトを追加し、走らせてみる。 [scripts] build = "docker-compose up -d --build" lint = "flake8 ." format = "autopep8 -ivr ." $ pipenv run format また、PrettierやESLintと同様に自分好みの設定を施したい方は以下の記事が参考になります。 Pipenv で flake8 / autopep8 を上手く使う pre-commit導入 さて、最後にflake8とautopep8をGitでCommitする前に適用させるようにしていきましょう。 ちなみにpre-commitはPython版のHuskyみたいなものです。 $ brew install pre-commit $ pre-commit --version そしたら、設定ファイルの作成。 $ pre-commit sample-config > .pre-commit-config.yaml プロジェクト直下に.pre-commit-config.yamlが作成され、中身を以下に変更。 pre-commit-config.yaml # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://gitlab.com/pycqa/flake8 rev: 3.8.1 hooks: - id: flake8 args: [--ignore=E402, --max-complexity, "10", --max-expression-complexity=7, --max-cognitive-complexity=7] additional_dependencies: [flake8-bugbear, flake8-builtins, flake8-eradicate, pep8-naming, flake8-expression-complexity, flake8-cognitive-complexity] - repo: https://github.com/pre-commit/mirrors-autopep8 rev: v1.5.7 hooks: - id: autopep8 args: [--in-place, -r, --aggressive, --exclude, "manage.py,./*/migrations,./*/snapshots,./*,./**/__init__.py,hirameki/asgi.py", --max-line-length, "120"] 以下のコマンドでgitにhooksを追加。 $ pre-commit install これでコミットする前にflake8とautopep8が走ります。 実際にコミットし、プッシュしてみましょう。 $ git add . $ git commit -m 'pre-commit動作確認' $ git push いかがだったでしょうか? PipenvでPythonの環境構築を行ったのは今回が初めてなので、多少至らない部分もありますがその点はご了承下さい。 また、何か気になる点や間違っているところがあればコメント欄にて教えてくださると助かります。 エラーが出た場合もご気軽にご相談下さい! 以上、「【2021年度10月版】Django REST frameworkをDockerで動かす(Pipenv、MySQL、pre-commit導入)」でした! Thank you for reading 参考記事 以下、この記事を書くにあたって参考にさせていただいた記事となります。 ・2018年のPythonプロジェクトのはじめかた ・Django REST Frameworkを使って爆速でAPIを実装する ・Django+MySQLの開発環境をdocker-composeで構築する ・【Python】Pipfile.lockを活用したDockerとpipenvでの安全な環境構築 ・pre-commitでコミット時にコードの整形やチェックを行う
- 投稿日:2021-10-26T14:46:36+09:00
【Python】APIから日出、日没、薄明の時間を取得してみた
初めに ダッシュボード作成のために、各種情報をAPIにて取得する Sunset and sunrise times APIを使用すると取得できることが判明 ⇒Pythonを用い、HP等で表示させる用途を想定 ※PHPバージョン 環境 -Windows10 -Python 3.8.12 パラメータ lat (float):必須、取得したい場所の緯度、 lng (float): 必須、取得したい場所の経度 date (string):オプション、YYYY-MM-DDフォーマットにて入力/相対・絶対日時の指定も可能。入力しない場合は現在の時刻を取得する。 callback (string):オプション,Callback function name for JSONP response. formatted (integer): オプション、0 か 1 (1 がデフォルト). 0の場合はISO 8601にて出力される。 本コードではsunset_url.formatにてパラメータをセットしてください。 コード sunset_time_api.php # -*- coding:utf-8 -*- #各種libraryのインポート import requests import json import datetime from datetime import datetime, timezone, timedelta # sunset_apiより天気を取得する sunset_url = "https://api.sunrise-sunset.org/json?lat={lat}&lng={lon}&date={date}&formatted=0" #各種パラメータをセット sunset_url = sunset_url.format(lat="35.00000", lon="135.000000", date="today") sunset_json = requests.get(sunset_url).json() # ISO8601フォーマット => Datetime_awere(timezoneあり) utc_sunrise = datetime.fromisoformat(sunset_json["results"]["sunrise"]) utc_sunset = datetime.fromisoformat(sunset_json["results"]["sunset"]) utc_civil_twilight_end = datetime.fromisoformat( sunset_json["results"]["civil_twilight_end"]) utc_nautical_twilight_end = datetime.fromisoformat( sunset_json["results"]["nautical_twilight_end"]) utc_astronomical_twilight_end = datetime.fromisoformat( (sunset_json["results"]["astronomical_twilight_end"])) # timezone変更(UTC => JST)、表示フォーマットの変更 # 時刻フォーマット例;'%Y年%-m月%-d日 %-H時%-M分%-S秒' JST = timezone(timedelta(hours=+9), "JST") sunrise = utc_sunrise.astimezone(JST).strftime('%m/%d %H:%M') sunset = utc_sunset.astimezone(JST).strftime('%m/%d %H:%M') civil_twilight_end = utc_civil_twilight_end.astimezone(JST).strftime('%m/%d %H:%M') nautical_twilight_end = utc_nautical_twilight_end.astimezone(JST).strftime('%m/%d %H:%M') astronomical_twilight_end = utc_astronomical_twilight_end.astimezone(JST).strftime('%m/%d %H:%M') print("日の出 : " + sunrise) print("日の入り:"+sunset) print("市民薄明の終わり:" + civil_twilight_end) print("航海薄明の終わり:" + nautical_twilight_end) print("天文薄明の終わり:" + astronomical_twilight_end) 参考サイト Sunset and sunrise times API Pythonで日付操作を完全マスター!! Python日付型
- 投稿日:2021-10-26T14:05:32+09:00
Pythonで簡単なc言語のファイルを作成
たくさんのバイナリファイルを作るにあたりc言語のプログラムファイルが必要だったのでPythonでチャチャチャと作った。その回顧録です。 c言語のプログラムファイルを作成 数値計算を含まない 文字列をprintfするだけのC言語ファイルを100個作るものです。 文字列は、英字の大文字・小文字を5~100文字をランダムに生成したものです。 strmakefile.py from random import randint x_lay = "abcdefghijklmopqrstuvwxyz" X_lay = str.upper(x_lay) lay = x_lay + X_lay laylist = list(lay) #print(laylist) #['a', 'b', 'c', 'd'.... for i in range(100): str = "" strlen = randint(5, 100) for k in range(strlen): t = randint(0, 45) str += laylist[t] txt = "#include <stdio.h> \nint main(void){\nprintf(\"" txt = txt+str+"\");\n}" filename = f"hoge/hoge{i}.c" with open(filename, mode="w") as f: s = f.write(txt) これを変数strの生成を行っているところでinputを使うとキーボードで入力したものが表示されるプログラムファイルが作成できる。 数値計算を含む これは数値計算をするC言語のプログラムファイルを100個作るものです。 変数txにそれぞれ[a,b,c,d,e]に数値を入れています。 for文でランダムな数字を生成してその長さの式が生成されます。 nummakefile.py import random txt = "#include <stdio.h> \nint main(void){\n" tx = "int a = 2;\nint b = 3;\nfloat c = 2.34;\nfloat d = 5.98;\nint e = 100;\nfloat x = " entxt = "\n}" numlen = ["+","-","*","/"] var = ["a", "b", "c", "d", "e"] eof = ";" for i in range(100): m = random.randint(1, 10) oppo = txt + tx for k in range(m): oppo += random.choice(var) oppo += random.choice(numlen) oppo += random.choice(var) oppo += eof + entxt filename = f"hoge/hoge{i}.c" with open(filename, mode="w") as f: f.write(oppo) コンパイル まとめてコンパイルする時は、for文を使うといいかも‼ lsの-1オプションでファイル名だけを1行ずつ表示してくれる。 呼び出されたファイル名を変数iに入れてコンパイルする。この時に-oオプションがないと同じバイナリファイルを上書きしていくだけなで、変数iを使ってそれぞれ違う名前のファイルを作成していく。 for i in `ls -1 *.c` do gcc $i -o con$i done
- 投稿日:2021-10-26T13:24:20+09:00
subprocess + pyinstaller --noconsole
PyInstallerを利用した際、subprocess関連が入っているときにうまく動作しないことがあります。 エラーメッセージすら出ないので、なかなかの困りものです。 これを回避するための策を提示します。 以下の関数を定義 def subprocess_args(include_stdout=True): # The following is true only on Windows. if hasattr(subprocess, 'STARTUPINFO'): info = subprocess.STARTUPINFO() info.dwFlags |= subprocess.STARTF_USESHOWWINDOW env = os.environ else: info = None env = None if include_stdout: return {'stdout': subprocess.PIPE, 'stdin': subprocess.PIPE, 'stderr': subprocess.PIPE, 'startupinfo': info, 'env': env } else: return {'stdin': subprocess.PIPE, 'stderr': subprocess.PIPE, 'startupinfo': info, 'env': env} 利用の仕方 def print_pdf(pdf_path): ''' PDFの印刷ダイアログを表示させる ''' acrobat_path = r"C:\Program Files (x86)\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe" command = f'"{acrobat_path}" /P "{pdf_path}"' proc = subprocess.run(command, **subprocess_args(True)) return proc.stdout, proc.stderr 参考
- 投稿日:2021-10-26T12:27:50+09:00
【物体検出】YOLOv5のオプションまとめ
2021/10/26 (最終更新日: 2021/10/26) はじめに YOLOv5のtrain.pyとdetect.pyのオプションをまとめてみました。 ※【引用】YOLOv5のコードはこちら。 一つの表中でオプションは五十音順にソートしております。 引数がなしとなっているオプションはそのオプションを記述することでflagが立つ(Trueとなる)オプションです。 いくつかは私の理解不足で説明を省いていたりするものもありますがご容赦ください。 (順次アップデートしていければと考えております。) train.pyのオプション よく使うオプション option 引数 説明 default batch int バッチサイズ指定 16 data str 学習データの情報を記載したyamlファイルのpath サンプルデータのcoco128が読み込まれる cfg str 使用するYOLOのバージョン指定 ' ' epochs int エポック数指定 300 exist-ok なし 学習結果を上書き保存する False img(imgsz, img-sizeでもよい) int 学習画像のサイズを指定 640 name str 学習結果を保存するディレクトリ名を指定 'exp' (注*2) patience int EarlyStopの設定指定したepoch数中でモデル精度が向上しなければ学習を打ち切る 100 project str 学習結果を保存するディレクトリを指定 ROOT / 'runs/train' (注*1) single-cls なし 多クラスにラベル付けされていても単一クラスとして扱う(物体の検出のみ行う) False weights str 学習済み重みを指定(用意された重みを使用する場合は、例えば'yolov5x.pt'のようにファイルのパスを指定しなくてもOK) ROOT / 'yolov5s.pt' (注*1) その他オプション option 引数 説明 default adam なし Adam optimizerを使用する Fasle(SGDが指定される) bucket str - ' ' cache str cashの保存先指定(ram or disk) 'ram' device 使用プロセッサの指定(0 or 0,1,2,3 or cpu) ' ' (cudaが指定される) evolve int ハイパーパラメータのバージョン指定いじったことない None freeze int 学習しない層を指定 0 hyp str ハイパーパラメータの指定いじったことない image-weights なし - False label-smoothing float - 0.0 linear-lr なし - False multi-scale なし 画像サイズを±50%変化させる False noautoanchor なし 自動アンカーチェックの無効化 False nosave なし 最終checkpointのみ保存 False quad なし - False rect なし - - resume なし - False sync-bn なし SyncBatchNormを使用するDDPモードでのみ使用可能 False save_period int 指定epoch毎にチェックポイントを保存する -1 (無効)\ workers int データローダーワーカーの最大数 8 detect.pyのオプション よく使うオプション option 引数 説明 default conf float クラス判定の閾値指定 0.25 exist-ok なし 推論結果を上書き保存する False img(imgsz, img-sizeでもよい) int 推論対象の画像のサイズを指定 640 name str 推論結果を保存するディレクトリ名を指定 'exp' (注*2) project str 推論結果を保存するディレクトリを指定 ROOT / 'runs/train' (注*1) save-txt なし 推論結果(検出座標と予測クラス)をtxtファイルに出力する False save-conf なし 推論結果(クラスの確率)をtxtファイルに出力する False sorce str 推論対象の画像が格納されたディレクトリまでのパス ROOT / 'data/images' (注*1) weights str 学習済み重みを指定 ROOT / 'yolov5s.pt' その他オプション option 引数 説明 default agnostic-nms なし False augment なし 拡張推論 False classes int クラスフィルタ device 使用プロセッサの指定(0 or 0,1,2,3 or cpu) ' ' (cudaが指定される) iou-thres folat IoU値の閾値設定 0.45 max-det int 1枚の画像で検出する物体数の最大 1000 nosave なし 画像を保存しない False save-crop なし 予測画像を検出座標でトリミングしたものを保存 False update なし モデルをアップデートする False view-img なし 結果を表示 False visualize なし 可視化 False 注釈 *1: ROOTはtrain.py、detect.pyが存在する階層 *2: exsit_okがFalseの場合、学習を行う度にexpの後に連番が振られる
- 投稿日:2021-10-26T11:43:43+09:00
【厳選】最低限のpandasテクニックを習得できるpandas64本ノック
2021/10/26 (最終更新日: 2021/10/26) はじめに 機械学習を勉強しようと思ったらまずpandasの基礎を固めるのが機械学習エンジニアへの近道! そこでpandasを勉強しようと思い調べるとpandas100本ノックなるものがたくさん出てきますね。 私もいくつか利用してpandasを学んできましたが、問題数を100本に満たすために同じような問題やpandasではないライブラリの問題が混ざったりしていたので今回は純粋なpandasノックを作成してみました。 全部で64問。 問題はipynbファイルにて作成しております。 各問いには見出しがついているので辞書のようにpandasでわからないことを調べることもできるように作りました。 問題はgithub上(以下URL)に公開しています。 URL: https://github.com/gotty21/basic_pandas-64-knocks 自分の学習環境上にcolneして利用していただければと思います。 使い方 cloneしてきたフォルダ内には以下が入っています。 名前 説明 question.ipynb 問題のみが記載されているファイル answer.ipynb question.ipynbに追記する形で答えが記載されているファイル data 問題で使用するテーブルデータが格納されているフォルダ 問題を解く際にはquestion.ipynbを使用してください。 どこから解いてもエラーがでないように設問の下にすでにコードが書かれていることがあります。 消さずに回答のコードと一緒に実行してください。 答え合わせや辞書として使う場合はanswer.ipynbを使用してください。 Jupyter NotebookやGoogle Colaboratoryの左に目次タブを開いて、見たいセルに飛ぶことができます。
- 投稿日:2021-10-26T09:46:10+09:00
Aiohttpで作るSimple Web Server
前回、Python Asyncio 入門という記事を書きました。それはAsyncioの互換ライブラリ Aiohttpのhttpクライアント機能を使ったサンプルでの説明でした。 AiohttpにはFlaskに似たサーバ機能もあります。Flaskは同期的プログラミングですが、Aiohttpは非同期的プログラミングで速度の点で優れています。 Asyncio公式サイト aiohttp公式サイト 以下のサンプルはaiohttp公式サイトのものを少し修正しして、2つのroutes、2つのHandlersにしてみました。プログラムの流れは説明不要でしょう。厳密な理解をしたいときは公式サイトをご確認ください。 simple.py from aiohttp import web async def hello(request): return web.Response(text="Hello, world") async def handle_greeting(request): name = request.match_info.get('name', "Anonymous") txt = "Hello, {}".format(name) return web.Response(text=txt) app = web.Application() app.add_routes([web.get('/', hello), web.get('/greet/{name}', handle_greeting)]) web.run_app(app) 以下のようにコマンドラインで起動できます。 python simple.py ======== Running on http://0.0.0.0:8080 ======== (Press CTRL+C to quit) ブラウザから http-://localhost:8080/ でアクセスすると「Hello, world」と表示されます。 http-://localhost:8080/greet/taro でアクセスすると「Hello, taro」と表示されます。 また、Flaskのように Route decorators を使って以下のように書くこともできます。こちらの方がスッキリしているような気がします。 simple2.py from aiohttp import web routes = web.RouteTableDef() @routes.get('/') async def hello(request): return web.Response(text="Hello, world") @routes.get('/greet/{name}') async def handle_greeting(request): name = request.match_info.get('name', "Anonymous") txt = "Hello, {}".format(name) return web.Response(text=txt) app = web.Application() app.add_routes(routes) web.run_app(app) aiohttpのサーバ機能は今回のさわり程度で終えたいと思います。次回以降、非同期のサーバ機能についてはASGI(Starlette)を見ていきたいと思います。
- 投稿日:2021-10-26T08:27:05+09:00
Kerasを使って学べるレシピの紹介
はじめに Axrossを運営している松田です。 Axrossは、エンジニアの"教育"と"実務"のギャップに着目し、「学んだが活用できない人を減らしたい」という想いで、ソフトバンク社内起業制度にて立ち上げたサービスです。 現役エンジニアによる実践ノウハウが"レシピ"として教材化されており、Pythonプログラミングを活用して実業務に近いテーマで、動くものを作りながら学ぶことができます。 Axross:https://axross-recipe.com 公式Twitter:https://twitter.com/Axross_SBiv 今回は、深層学習に適したPythonライブラリKerasの特徴を紹介し、Axrossのサービスで学べるKerasを活用した実践的なレシピを一部ご紹介します。 Kerasとは Keras(ケラス)とは、Pythonで書かれた深層学習のオープンソースライブラリの1つです。 Kerasは、Googleのエンジニアによって作り出されました。読みやすく簡潔なアーキテクチャで迅速に実装できることと、ユーザーフレンドリーで拡張性があることを重視して設計・開発されました。 Kerasを使うことで、ディープラーニングのベースとなっている数学的理論の部分や、深層学習のアルゴリズムを、比較的短く簡単なPythonコードで多くのモデルを素早く実装することができます。 Kerasは、構造がシンプルで使いやすく、ディープラーニングの初心者が実装を学び始めたり、ディープラーニングの迅速な実験をするのに最適なライブラリです。一方で、TensorFlowやPyTorchと比べると処理速度が遅く、学習に時間がかかります。そのため、小規模なデータセットにしか適さない点がデメリットです。 Keras公式サイト Googleのオープンソース深層学習フレームワークであるTensorFlowの開発チームが、ディープラーニング分野で画像やテキストデータをより容易に扱うために、TensorFlowのコアライブラリにKerasをサポートすることを2017年に発表しました。最近ではKerasは、TensorFlowの機能と統合されていることから、Keras単体で独立して動作させるよりも、KerasのバックエンドとしてTensorFlowを用いることが主流となっています。 そして、TensorFlowとともに学術研究と企業の事業の両方で多く採用され、利用されています。 TensorFlowについてはこちらのQiita記事内容もご確認ください。 Kerasを活用したレシピの紹介 AxrossのKerasを使って学べる実践レシピを12個ご紹介します。 画像認識 画像認識による乗り物を分類するレシピ(モデル作成編) 投稿者:@uehara7 さん 外部APIを用いてWEBサービスから画像データを収集し、データの前処理、VGG16モデルの構築・評価まで、機械学習(AI)を活用して画像に映る物体を認識して画像分類する、一連のAI開発手法を学ぶことができます。 また、続編の乗り物画像分類モデルの精度向上レシピ(転移学習編)では、転移学習によって、画像分類モデルの精度を向上させる手法を学ぶことができます。 YOLOv2とVGG16による料理認識レシピ 投稿者:@belltree さん 深層学習モデル YOLOv2とVGG16による物体検出の学習方法を応用し、料理の画像データを用いて、料理メニューの推定を行います。 labelImgによるアノテーション作成、darkflowでYOLOv2の重みを利用、darkflowからの検出物体データの取り出し方、VGG16のFine TuningとOpenCV/PILの利用法、OpenCVで日本語テキストを表示する方法、交差検証といった、物体検出を行う機械学習モデルを構築するための一連の手法を学ぶことができます。 TensorFlow(Keras)を使った洋服の種類分類を体験するレシピ 投稿者:@st4trafalgar さん Fashion MNISTの洋服の画像データセットを利用し、TensorFlow(Keras)を使った深層学習による画像分類を行います。 Kerasによるモデルの定義、学習、精度評価といった一連のディープラーニングの開発の流れを学ぶことができます。ECサイト等でのファッションコーディネート画像を自動分類するタスクで応用ができます。 生産ラインを想定した画像異常検知の実践レシピ 投稿者:@apple さん オートエンコーダ、畳み込みオートエンコーダ、サーポートベクトルマシン(SVM)を構築し、一様なパターンを持つ画像の異常を検知し、異常部分をマーキングする手法について、画像データの前処理からモデル構築、評価まで機械学習の画像処理タスクの一連の流れを体験できます。 実務では、工場の生産ラインなどで画像をもとに正常の範囲を逸脱するもの(不良品や不純物等)の検出を自動化するときに活用されています。 画像編集 Kerasで作る画像ノイズ除去モデル AR-CNNレシピ 投稿者:@kz_onkさん 高解像度の画像ノイズ除去分野において有名なAR-CNN(Artifacts Reduction Convolutional Neural Network)モデルについて、モデル構造の解説から、Kerasによるモデルの実装、ノイズ除去の画質比較テストの流れでハンズオン形式で学ぶことができます。 画像からノイズを除去する手法は、ぼやけた画像を拡大しつつ、欠落している情報を補完して鮮明な画質にする等、動画や画像の編集処理に応用することができます。 顔交換(FaceSwap)技術を活用し、人物画像の顔を入れ替えるレシピ 投稿者:@katkazzzzzさん FaceSwapを活用し、動画や画像から人物の顔の入れ替えを行い、機械学習で自然な合成を実現する手法を学びながら、画像処理におけるオートエンコーダーの仕組みとその効果を体験することができます。 AIを利用して人の顔を差し替えるディープフェイクと呼ばれる、フェイク動画の悪用やいたずらが近年問題視されていますが、その技術そのものは非常に優れた技術であり、使い方によっては私たちの社会に貢献できるものと思います。倫理規定に賛同した上でお試しください。 物体検出 MediaPipeを利用して簡単なジェスチャーを推定するレシピ 投稿者:@KzhtTkhsさん MediaPipeを用いて手のランドマークを検出し、ランドマークを元に簡単なジェスチャー認識を行う方法をPythonを使って学べるレシピです。 非接触操作システムでの活用例を参考にして、人差指の指先の軌跡から「静止」「時計回り」「反時計回り」の3種類のジェスチャーを認識するプログラムを作ります。ジェスチャー学習用データの取得から、ジェスチャー分類モデルの定義、作成、動作確認までの一連の開発を行います。 MoveNetのキーポイントからボディランゲージを読み取るレシピ 投稿者:@高橋かずひとさん TensorFlow Hub上で高速な姿勢推定モデルMoveNet を動かし、17個のキーポイントを取得し、TensorFlow Similarityモデルを用いてキーポイントからのボディランゲージの認識手法について学ぶことができます。 高速な姿勢推定の技術は、防犯カメラ画像の不審行動の検出などに応用できます。 セキュリティ 【AIセキュリティ入門】Adversarial Examplesを理解しAIモデルを頑健にするレシピ 投稿者:@ChillStackさん ディープラーニングをはじめとする様々な機械学習を活用したAI発展に伴い、顔認証システムや防犯システム、自動運転技術など、様々な分野でAIの社会実装が進んでいます。一方で、AIに対する攻撃手法も数多く生まれており、「AIを防御する技術」の確立が急務となっています。 このレシピは、映像や音声などAIへの入力データに対して細工をすることによって、意図的にAIを誤認識させる攻撃手法とAIをより頑健にするセキュリティの仕組みを実践しながら学ぶことができます。 時系列分析 POSデータから商品の売り上げを予測するレシピ 投稿者:@su2umaruさん 時系列データ(POSデータ)を使用して、Recurrent Neural Network (RNN) をベースとした機械学習モデルによる商品の売上予測を行います。 人工的に整っていないリアルなデータを用いることで、現場で求めらえれる機械学習モデルの精度を上げるたために重要な、データの収集や前処理の手法を試しながら学ぶことができます。 TensorFlow Kerasで時系列データの異常検知を行うレシピ 投稿者:@高橋かずひとさん TensorFlow Kerasを用いて再構成畳み込みオートエンコーダモデルを構築し、心電図の時系列データに対する波形の異常検知を行う手法と、Optunaを用いたモデル構造探索について学べます。 実運用では、センサーから取得した時系列データを取得・分析し、その特徴を観察することで、モーターの故障を未然に防ぐための異常振動検知など、製造業を中心とした機械のメンテナス業務に応用可能です。 最後に AIを使いこなす人材になるためのコツは、 ● 常に最新AI技術(モデルやライブラリ)やトレンド、ユースケースをキャッチアップすること ● 座学で学んだ後は、AIを使って何かアウトプットを出す疑似体験を繰り返すこと ● 様々なデータセットやAI技術を動かしてみてその手触り感を把握すること だと思います。 ぜひAxrossのレシピを通して、プログラムの意味を考えながら写経(コードを実際に書き写す行為)し、実際に動くものをつくりながら学ぶことで、AIを活用できる人材が少しでも多く増やすことができたらと思います。