- 投稿日:2022-02-23T23:34:57+09:00
Raspberry Piでインターホンの音を検知してLINEに通知する (2)PyAudio録音時の警告・エラーに対処する
「インターホンの音を検知してLINE通知することで、どこにいてもイヤホンをしていても来客に気づきたい」と考えて作成したものをまとめています。 前回「(1)インターホンの音を録音する」では、Raspberry PiとPyAudioモジュールを使ってインターホンの音を録音しました。 しかし、その際に警告やエラーが出ることを説明しました。 今回は、これらへの対処方法をまとめます。特にエラー対処は実際に運用する際に重要です。 なかなか解決方法が見つからなかったので需要があるかなあと思う反面、 調べきれていないとも感じている箇所です(実際、どれも根本的な解決ではない)。 装置:Raspberry Pi 4 マイク:共立プロダクツ MI-305 [USBマイク] プログラム言語:Python3 (PyAudioモジュールを使用。 PyAudio Documentation) 下記のプログラムを念頭に話を進めます。(今回は.wavに保存するところは省略) overflow_and_ALSAerror.py import pyaudio import wave input_device_index = 2 # 前回確認したデバイス番号 CHUNK = 1024 FORMAT = pyaudio.paInt16 CHANNELS = 1 # モノラル入力 # 前回確認したmaxInputChannelsが上限 RATE = 44100 RECORD_SECONDS = 5 p = pyaudio.PyAudio() stream = p.open(format=FORMAT, channels=CHANNELS, input_device_index = input_device_index, rate=RATE, input=True, frames_per_buffer=CHUNK) print("Recording...") frames = [] for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)): data = stream.read(CHUNK) frames.append(data) print("Done!") stream.stop_stream() stream.close() p.terminate() OSError: [Errno -9981] Input overflowed 上記を実行した際、 OSError: [Errno -9981] Input overflowed が表示されることがあり、その場合は CHUNK = 1024 * 4 # または、*8, *16, ... のようにCHUNKを増やせば防げることは前回の通り。 しかしCHUNKを大きくしても、何度も繰り返すとたまに出現し、録音が止まる。 今回のように24時間動作していてほしい場合には致命的。 そこで、PyAudioのdocumentationを参考に、下記のように書き換える。 wo_overflow.py import pyaudio import wave input_device_index = 2 # 前回確認したデバイス番号 CHUNK = 1024 FORMAT = pyaudio.paInt16 CHANNELS = 1 # モノラル入力 # 前回確認したmaxInputChannelsが上限 RATE = 44100 RECORD_SECONDS = 5 p = pyaudio.PyAudio() stream = p.open(format=FORMAT, channels=CHANNELS, input_device_index = input_device_index, rate=RATE, input=True, frames_per_buffer=CHUNK) print("Recording...") frames = [] for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)): # ↓↓↓ここを変更 data = stream.read(CHUNK, exception_on_overflow=False) # ↑↑↑ frames.append(data) print("Done!") stream.stop_stream() stream.close() p.terminate() stream.readの引数にexception_on_overflow=Falseを加えることで、overflow時のOS Errorを出さないようにする(デフォルトではTrue)。 これでOSError: [Errno -9981] Input overflowedは出ないはず。 ただ、overflowしていることには変わりがないので、その瞬間は切り取られたような音声データになっている(と思う)。 まずはCHUNKを大きくして、ある程度は安定に録音できるようにしてからこの対処をすべきか。 ALSA関連の警告 冒頭のコードを実行した際、 ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.front.0:CARD=0' で始まる警告がたたみかけてくることは前回の通り(最後にほぼ全文を載せておく)。 録音に問題はないのだが、1画面に収まらない量の警告なのでどうにかしたい。 調べたら、下記のページが役に立ちそう。 「PyAudio working, but spits out error messages each time」 質問に対していくつか解決策が寄せられているが、 「All of the above is true and a good solution. I just came here to suggest a nicer way of re-using the error handler code:」 で始まる回答を使用してみた。configファイル系をいじらなくて良さそうなのと、nicer wayということなので。 error_hider.py from ctypes import * from contextlib import contextmanager import pyaudio ERROR_HANDLER_FUNC = CFUNCTYPE(None, c_char_p, c_int, c_char_p, c_int, c_char_p) def py_error_handler(filename, line, function, err, fmt): pass c_error_handler = ERROR_HANDLER_FUNC(py_error_handler) @contextmanager def noalsaerr(): asound = cdll.LoadLibrary('libasound.so') asound.snd_lib_error_set_handler(c_error_handler) yield asound.snd_lib_error_set_handler(None) wo_ALSAerror.py import pyaudio import wave from error_hider import noalsaerr # ↑のコードをimport input_device_index = 2 # 前回確認したデバイス番号 CHUNK = 1024 FORMAT = pyaudio.paInt16 CHANNELS = 1 # モノラル入力 # 前回確認したmaxInputChannelsが上限 RATE = 44100 RECORD_SECONDS = 5 # ↓↓↓ここを変更 with noalsaerr(): p = pyaudio.PyAudio() # ↑↑↑ stream = p.open(format=FORMAT, channels=CHANNELS, input_device_index = input_device_index, rate=RATE, input=True, frames_per_buffer=CHUNK) print("Recording...") frames = [] for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)): data = stream.read(CHUNK, exception_on_overflow=False) frames.append(data) print("Done!") stream.stop_stream() stream.close() p.terminate() 上記を実行すると、ALSA関連の警告は消える。 "with noalsaerr():"を加えるだけなので非常に簡便。 しかし、下記のjack server関連の警告は引き続き現れる・・・。 これ以上はわからなかったのと、対処前と比べると少ない量なので、ここで妥協することにした。 Cannot connect to server socket err = No such file or directory Cannot connect to server request channel jack server is not running or cannot be started JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock Cannot connect to server socket err = No such file or directory Cannot connect to server request channel jack server is not running or cannot be started JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock Cannot connect to server socket err = No such file or directory Cannot connect to server request channel jack server is not running or cannot be started JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock まとめ 今回は、Raspberry PiとPyAudioを使って録音する際に現れる、警告・エラーへの対処方法をまとめました。 特にOverflowへの対処は運用を始めてから重要さに気づきました。 どれも対症療法であるだけでなく、警告が消えきっていないところもあるので心苦しいですが、 改善が見られたので良かったです。 次回は、前回録音したデータを解析して、インターホンの検知基準を作成します。 その他の記事: Raspberry Piでインターホンの音を検知してLINEに通知する (1)インターホンの音を録音する 参考 PyAudio documentation PyAudio working, but spits out error messages each time ALSA関連の警告のほぼ全文 ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.front.0:CARD=0' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM front ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.surround51.0:CARD=0' ...(中略)... ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM surround71 ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.iec958.0:CARD=0,AES0=4,AES1=130,AES2=0,AES3=2' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM iec958 ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.iec958.0:CARD=0,AES0=4,AES1=130,AES2=0,AES3=2' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM spdif ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.iec958.0:CARD=0,AES0=4,AES1=130,AES2=0,AES3=2' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM spdif ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'defaults.bluealsa.device' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5036:(snd_config_expand) Args evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM bluealsa ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'defaults.bluealsa.device' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5036:(snd_config_expand) Args evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM bluealsa Cannot connect to server socket err = No such file or directory Cannot connect to server request channel jack server is not running or cannot be started JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock Cannot connect to server socket err = No such file or directory Cannot connect to server request channel jack server is not running or cannot be started JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock ALSA lib pcm_oss.c:377:(_snd_pcm_oss_open) Unknown field port ALSA lib pcm_oss.c:377:(_snd_pcm_oss_open) Unknown field port ALSA lib pcm_a52.c:823:(_snd_pcm_a52_open) a52 is only for playback ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.iec958.0:CARD=0,AES0=6,AES1=130,AES2=0,AES3=2' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM iec958:{AES0 0x6 AES1 0x82 AES2 0x0 AES3 0x2 CARD 0} ALSA lib pcm_usb_stream.c:486:(_snd_pcm_usb_stream_open) Invalid type for card ALSA lib pcm_usb_stream.c:486:(_snd_pcm_usb_stream_open) Invalid type for card Cannot connect to server socket err = No such file or directory Cannot connect to server request channel jack server is not running or cannot be started JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
- 投稿日:2022-02-23T23:26:51+09:00
事業企画1年目のpython学習3日目
本記事について とある事業会社で事業企画をやっています。 元々は、新規営業1年→エンプラ営業3年やっていました。 事業企画への異動に伴って、ルーチンワークの自動化やスクレイピングに興味を持ちpython学習をスタートしました。 目的達成までは記事を執筆予定です。どうぞよろしくお願いいたします。 開発環境 jupyterlab 使っている本 すっきりわかるpython入門 https://sukkiri.jp/books/sukkiri_python 勉強計画 週5日間ミニマム1h勉強する。 最初の1weekは1冊終わらせることをgoalに。 start@ 2022/2/21 学習時間 2.0h 学び 今日は条件分岐を学びました〜 条件分岐 順次、分岐、繰り返しがある。 今までの2日間の記事では順次はのみ利用。 この3つを使えば、ありとあらゆるプログラムが作成可能とのこと。 構造化定理。という。 1行=1つの文にしない場合は、セミコロン;でつなぐ。 name = 'kamon';print('my name is {}'.format(name)) #my name is kamon if文 今からこの下のコードをifで条件分岐させていく。 name = input ('what is your name? <<') print('{}hello'.format(name)) food = input('what is {} favorite food? <<').format(name) print('I like {} too' .format(food)) #what is your name? << kamon #kamonhello #what is {} favorite food? << egg #I like egg too if food == 'curry': print('i love curry,you are nice!') else:print('I like {} too'.format(food)) #I like egg too ifの後は、:をつけるのを忘れない。 score = int(input('what is your score <<')) if score >= 60: print('you are grest') print('go to next clss') else: print('take a test again') #what is your score << 64 #you are grest #go to next clss なお、elseになる場合しっかりとブロックをしないと行けない。しないと、常にインデントしないprintも出力されてしまう。 インデントだいじ。気をつけよう。 インデントは半角スペースの個数を揃えないと行けない。 2個か4個か8個が使われることが多いが、4個おすすめ。 score == 100 #scoreが100なら name == 'kamon' #nameがkamonなら password != '123456 ' #passewordが12345でないなら tempreature <0 #tempreatureが0以下なら 指定した要素が含まれていたらifの返答をしたい場合はin演算を使う if 'egg' in food: print('I like egg too ') else: print('I like {} too'.format(food)) #I like egg too ディクショナリの中でkeyが使用されているかを確認することができる。 キー in ディクショナリでキーの存在を調べられる。 key = input('add new object >>') if key in scores: print('already registered') else: data= int(input('add ur point >>')) scores[key] = data print(scores) #add new object >> python #add ur point >> 80 #{'database': 60, 'network': 100, 'security': 50, 'python': 80} 真偽値 trueとfalseは、文字列ではない。 if文は条件式の結果がtrueなら、ifブロックをfalseならばelseブロックを実行する。 if score >= 60 #if true 60以上 if score <= 60 #if false60以下なら score = int(input('what is your point >>')) print(score >=60) #what is your point >> 65 #True 論理演算子 and #かつ or #または not #出なければ if score < 60 or score >100 : #60点未満、100点より大きかったら if not 'egg' in food: #foodに'egg'が含まれていなければ 分岐構文のバリエーション if-else (基本形) ifのみの構文 elseをつけず、ifがtrueでない場合は何も出力しない。 if-elif構文 条件がelseだった際にさらに別の条件式で判別したい場合 #if-elseでifのみを表現するならelseの中をpassと表現すればOK if score >= 60: print(score) else: pass #65 #if-elif score = int(input('what is ur score >>')) if score < 0 or > 100: print('error point') print('please input again') elif score >= 60: print('you passed test') else: print('you failed test') if文のネスト print('すべての質問にyまたはnで答えてください') okane_aruka = input('お金に余裕あるか?? >>') if okane_aruka =='y': onaka_suiteruka = input('お腹かなり空いている? >>') nomitai_kibunka = input('お酒飲みたい? >>' ) if onaka_suiteruka == 'y' and nomitai_kibunka == 'y': print('焼肉はいかが?') elif onaka_suiteruka =='y': print('カレーはいかが?') elif nomitai_kibunka =='y': print('焼き鳥はいかが?') else: print('パスタはどう?') yashoku_iruka == input('夜食は欲しい?') if yashoku_iruka == 'y': print('コンビニのチキンはどう?') else: print('家で食べましょう') #すべての質問にyまたはnで答えてください #お金に余裕あるか?? >> y #お腹かなり空いている? >> n #お酒飲みたい? >> y #焼き鳥はいかが? #夜食は欲しい? n #コンビニのチキンはどう? 今日ミスったところ if文の=を1つにしてしまったり、elseの:を忘れてしまったり・・・。 感想 (3日目から書いていこうと思います) 昨日は、リストやディクショナリーなどを学習しましたがすごく分量が多く実は3hほど時間をかけました。 まだ頭に定着するまでは時間がかかりますが高校時代ぶりの学習を考えると、とても楽しく思っています。 目標としておいているのはスクレイピングによる情報収集の効率化と定常業務の自動化ですが、事業企画としての定常業務を覚えていませんw今年中にはどちらもできるようになりたいと思います。 今回の本は中高生でもわかるようにと思考して書かれた本とのことでとてもわかりやすいです。 バリバリの事業企画マン✖︎パイソニスタを目指して引き続き頑張っていきたいとおもいます。 P.s. 編集リクエストをいただける皆様、とても感謝しております。 マークダウンを使うのは実に5年ほどぶりでその際も扱えていたといえないレベルなのでそういったところの指摘から含め色々と今後もご指導いただけたら幸いです。
- 投稿日:2022-02-23T23:22:16+09:00
sklearnテキスト分類で、vectorizerの種類とオプション指定が精度に与える影響を検証する
概要 from sklearn.feature_extraction.textのvectorizerの種類やオプションの指定によって、カテゴリ分類の精度がどのように変わるかを実験しました。具体的には、以下の要因について比較しました。 要因 水準数 水準 vectorizer 3 CountVectorizer, TfidfVectorizer, HashingVectorizer tokenizer 2 char, word ngram_range 3 (1,1), (1,3), (3,3) binary 2 True,False 動機 sklearnでテキスト分類を使うときにvectorizerやオプションの指定をどうしたらいいかよくわからなかったので、感覚を掴むために実験しました。 結果 精度 教師データとしてlivedoorニュースコーパスから抽出したタイトルとカテゴリのペア7367件(カテゴリ数9)を用いました。半数のデータを使ってナイーブベイズで学習した分類モデルの精度を残りのデータで評価する、という試行を各学習条件に対して100回ずつ行いました。結果を以下に示します。 精度の平均値が最も高かったのは、(vectorizer, tokenizer, ngram_range, binary) = (CountVectorizer , word, (1,3), True)の条件で、平均値は0.812でした。精度の平均値が最も低かったのは、(vectorizer, tokenizer, ngram_range, binary) = (HashingVectorizer, word, (3,3), False)の条件で、平均値は0.812でした。 要因別で見ると、vectorizerについては、HashingVectorizerがCountVectorizer、TfidfVectorizerよりも全体的に小さいです。tokenizerとngram_rangeは強めの交互作用がありそうで、(tokenizer, ngram_range)=(char, (1,1))または(word, (3,3))のときに精度が低くなっていました。binaryはTrueのほうが全体的に精度が高いですが、影響は相対的に小さめにみえます。 vectorizer, tokenizer, ngram_range, binaryの4要因について対応ありの分散分析の結果を示します。対応は、試行ごとに用いた学習データの対応です。 Anova ========================================================================= F Value Num DF Den DF Pr > F ------------------------------------------------------------------------- vectorizer 53443.2497 2.0000 198.0000 0.0000 tokenizer 1898.8320 1.0000 99.0000 0.0000 ngram_range 29846.5163 2.0000 198.0000 0.0000 binary 3226.5334 1.0000 99.0000 0.0000 vectorizer:tokenizer 538.8225 2.0000 198.0000 0.0000 vectorizer:ngram_range 2122.0846 4.0000 396.0000 0.0000 tokenizer:ngram_range 78421.4171 2.0000 198.0000 0.0000 vectorizer:binary 133.0737 2.0000 198.0000 0.0000 tokenizer:binary 1403.4966 1.0000 99.0000 0.0000 ngram_range:binary 1179.3318 2.0000 198.0000 0.0000 vectorizer:tokenizer:ngram_range 1968.6917 4.0000 396.0000 0.0000 vectorizer:tokenizer:binary 576.0826 2.0000 198.0000 0.0000 vectorizer:ngram_range:binary 688.8808 4.0000 396.0000 0.0000 tokenizer:ngram_range:binary 955.2296 2.0000 198.0000 0.0000 vectorizer:tokenizer:ngram_range:binary 930.2602 4.0000 396.0000 0.0000 ========================================================================= すべての要因の主効果および交互作用が有意でした。 F値に注目すると、tokenizerとngram_rangeの交互作用、vectorizerの主効果が目立って大きく、ngram_rangeの主効果は交互作用がより強く出ているのでとばすと、binaryの主効果が続いています。ただしbinaryの主効果はそれより上位の効果に比べればかなり小さいです。 グラフからうける印象に沿った結果となっていることがわかります。 下位検定については、グラフを見れば雰囲気はなんとなくわかるので、今回は省略します。 時間 精度評価の際に、分類機の学習にかかる時間も計測していましたので、その結果を以下に示します。 時間はまず、外れ値はありますが、すべての条件における殆どの試行が(1回あたり)0.2秒以下となっており、試行を数百回、数千回と繰り返すケースでなければ、あまり意識する必要はなさそうです。 一応、学習時間を比較してみると、ngram_rangeの影響が強く、特にngram_rangeが(1,3)や(3,3)のときに学習時間が長くなる傾向がありそうです。またbinaryもTrueのときに学習時間が長くなる傾向がありそうです。 精度のときと同様に、vectorizer, tokenizer, ngram_range, binaryの4要因対応あり分散分析の結果を示します。 Anova ======================================================================== F Value Num DF Den DF Pr > F ------------------------------------------------------------------------ vectorizer 27.3369 2.0000 198.0000 0.0000 tokenizer 63.1572 1.0000 99.0000 0.0000 ngram_range 1095.7163 2.0000 198.0000 0.0000 binary 661.9470 1.0000 99.0000 0.0000 vectorizer:tokenizer 21.8070 2.0000 198.0000 0.0000 vectorizer:ngram_range 105.7113 4.0000 396.0000 0.0000 tokenizer:ngram_range 46.4642 2.0000 198.0000 0.0000 vectorizer:binary 11.3311 2.0000 198.0000 0.0000 tokenizer:binary 9.2247 1.0000 99.0000 0.0031 ngram_range:binary 56.8929 2.0000 198.0000 0.0000 vectorizer:tokenizer:ngram_range 21.8805 4.0000 396.0000 0.0000 vectorizer:tokenizer:binary 19.7996 2.0000 198.0000 0.0000 vectorizer:ngram_range:binary 14.5234 4.0000 396.0000 0.0000 tokenizer:ngram_range:binary 1.4395 2.0000 198.0000 0.2395 vectorizer:tokenizer:ngram_range:binary 9.6528 4.0000 396.0000 0.0000 ======================================================================== tokenizer, ngram_range, binaryの三重交互作用を除くすべての交互作用と主効果が有意でした。 F値が大きいのはngram_rangeの主効果、binaryの主効果の2つで、グラフの印象と一致する結果となっています。 考察 tokenizerとngram_range 精度に関して最も影響が強かったのは、tokenizerとngram_rangeの交互作用でした。具体的には、(tokenizer, ngram_range)=(char, (1,1)) or (word, (3,3))のときに精度が低いという結果でした。(char, (1,1))は文字単位のユニグラム、(word, (3,3))は単語単位のトリグラムです。それぞれトークン化の単位として細かすぎ、荒すぎたため、精度が悪くなったと考えられます。 上記以外のtokenizerとngram_rangeの組み合わせに関しては、(word, (1,3))が少しだけ高いといえば高いですが、概ね似たような精度でした。 個人的にはそこまで精度が変わらないのであれば、形態素解析辞書を使わずに済む、(char, (3,3))が一番手軽で便利だと思いました。 なお、荒い方に関しては、より多くの学習データがある状況だったり、分類対象が長文(今回は記事タイトルで比較的短め)のケースでは、高精度が出ていた可能性もあるため、あくまで、今回用いた学習データ、評価データにおいてはの話となります。学習データの量や一文の長さと、トークン化の粗さの関係については機会があれば、別途検証してみたいです。 vectorizer vectorizerはtokenizerとngram_rangeの交互作用の次に影響が強かったです。 水準の差をみると、CountVectorizerとTfidfVectorizerは前者のほうが少し精度がいいですが、ほぼ同程度、HashingVectorizerが相対的に少し低めという結果でした。 HashingVectorizerについては、算出のロジックをよく理解できていないうえ、ナイーブベイズ分類器似入力するために負値がでないようにするパラメータ設定を行ったりしているので、他2つと正当な比較ができているのかはよくわかりません。 CountVectorizerがTfidfVectorizerと同等以上の精度になっているのは少し意外でした。Tfidfは単語の登場頻度などを考慮した「気の利いた」ベクトル化になっているため、精度にいい影響があるかと思っていました。結果から考えれば、ナイーブベイズの学習自体が単語の登場頻度等を考慮した計算であるため、Tfidfとある意味で役割がかぶっており、入力値はシンプルなカウントベクトルのほうが適していたということなのかもしれません。ナイーブベイズ以外で学習したときにどういう傾向がでるのか気になります。 binary 影響は小さいものの、binary=Trueのときには、全体的に精度が高くなる傾向がありました。binaryオプションがTrueのとき、一つの文に単語が複数回登場していても1回のみの登場として扱われるようになります(HashingVectorizerの場合、意味合いが少し違うかもしれないのですが深く理解できていません)。分類タスクにおいては単語が何文書に登場したかが重要であり、各文書に何回登場したかという情報を含めること(つまりbinary=False)は、ノイズになるということなのかもしれません。 制限事項 今回用いたデータはライブドアニュースコーパスの記事タイトルとカテゴリです。また、学習モデルはナイーブベイズに固定しています。ベクトル化においては、注目したパラメータ以外(例えばトークンとして採用する登場回数の最小、最大しきい値やstop_wordsなどの設定)はデフォルト値を使用しています。ただしHashingVectorizerはn_featuresは学習時間短縮のため、デフォルト値(2^20)ではなく2^16としており、ナイーブベイズ学習の妨げとなる負値を登場させないため、alternative_signをTrueにしています。tokenizer=wordの条件では、分かち書きのためmecabおよび辞書としてmecab-ipa-neologdを使用しています。これらの状況以外では、結果が変わる可能性があります。 次に実装するとしたら 最も精度が高かったのは単語で分かち書きする条件でしたが、分かち書きは辞書の影響もうけるので、思考停止である程度精度高く実装したい場合は、(vectorizer, tokenizer, ngram_range, binary) = (CountVectorizer, char, (3,3), True)の条件が、良さそうな気がします。 データ数が多かったり入力が長文であることが期待できるなら単語分割にしたり、ngram_rangeを大きくすることを試し見ても良いかもしれません。 方法 再現用に実験、分析に使ったコードを記載します。ノートブック(一部ターミナル)での実行を想定しています。 環境 ターミナルで実行 % sw_vers ProductName: macOS ProductVersion: 11.6.1 BuildVersion: 20G224 % python -V Python 3.8.6 インストール pip install scikit-learn pip install mecab-python3 pip install pingouin pip install scipy pip install statsmodels pip install matplotlib データの用意 ターミナルで実行 curl -O "https://www.rondhuit.com/download/ldcc-20140209.tar.gz" #macの場合 tar zxvf ldcc-20140209.tar.gz 以下はpythonで実行 import os import pandas as pd path = os.path.join("text") categories, titles, contents = [], [], [] dirs = [ v for v in os.listdir(path) if os.path.isdir(os.path.join(path, v)) ] for dir in dirs: files = [os.path.join(path,dir, v) for v in os.listdir(os.path.join(path,dir)) if v.startswith(dir)] for file in files: with open(file, "r", encoding="utf-8") as f: lines = f.read().splitlines() categories.append(dir) titles.append(lines[2]) contents.append(" ".join(lines[3:])) df = pd.DataFrame({ "category": categories, "title": titles, "content": contents }) df.to_csv("livedoor_news.tsv", sep="\t", index=False,header=None) データの読み込み import pandas as pd df = pd.read_csv("livedoor_news.tsv", sep="\t", names=["label", "title", "text"]) df.head() label title text 0 movie-enter 【DVDエンター!】誘拐犯に育てられた女が目にした真実は、孤独か幸福か 2005年11月から翌2006年7月まで読売新聞にて連載された、直木賞作家・角田光代による... 1 movie-enter 藤原竜也、中学生とともにロケット打ち上げに成功 「アンテナを張りながら生活をしていけばいい」 2月28日、映画『おかえり、はやぶさ』(... 2 movie-enter 『戦火の馬』ロイヤル・プレミアにウィリアム王子&キャサリン妃が出席 3月2日より全国ロードショーとなる、スティーブン・スピルバーグの待望の監督最新作『戦火の馬... 3 movie-enter 香里奈、女子高生100人のガチンコ質問に回答「ラーメンも食べる」 女優の香里奈が18日、都内で行われた映画『ガール』(5月26日公開)の女子高生限定試写会に... 4 movie-enter ユージの前に立ちはだかったJOY「僕はAKBの高橋みなみを守る」 5日、東京・千代田区の内幸町ホールにて、映画『キャプテン・アメリカ/ザ・ファースト・アベン... 学習用の関数の定義 以下の関数は、与えられたvectorizer, modelを用いて、100回学習試行をし、試行ごとの精度と所要時間のリストをします。 #vectorizerをimport from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer, HashingVectorizer #multinomialnbをimport from sklearn.naive_bayes import MultinomialNB # accuracy_scoreをimport from sklearn.metrics import accuracy_score # train_test_splitをimport from sklearn.model_selection import train_test_split # timeをimport import time # 100回学習して、試行ごとの精度と所要時間のリストを返す def calc_accuracy(texts, labels, model, vectorizer): X = vectorizer.fit_transform(texts) scores, times = [], [] for random_state in range(100): X_train, X_test, y_train, y_test = train_test_split(X, labels, test_size=0.5, shuffle=True, random_state=random_state, stratify=labels) start = time.time() model.fit(X_train, y_train) end = time.time() times.append(end - start) y_pred = model.predict(X_test) accuracy = accuracy_score(y_test, y_pred) scores.append(accuracy) return scores, times 以下の関数は日本語テキストをmecabで分かち書きしたリストにして返します。 mecabの辞書はデフォルトのものを使用しており、筆者の環境ではmecab-ipadic-neologdです。 import MeCab mecab = MeCab.Tagger("-Owakati") def parse_text(text): return mecab.parse(text).strip().split() 条件ごとの学習結果の取得 #vectorizerをimport from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer, HashingVectorizer #multinomialnbをimport from sklearn.naive_bayes import MultinomialNB scores, times = [],[] models, vectorizers = [], [] tokenizers, ngram_ranges, binaries = [],[],[] random_states = [] # ngram_rangeとbinaryだけはループ変数として用意し、その他の条件はループの中で直接書く。 for ngram_range in [(1,1),(1,3),(3,3)]: # ngram_rangeの水準パターン for binary in [True, False]: # binaryの水準パターン #ループの進捗確認用の出力 print("ngram_range: {}, binary: {}".format(ngram_range, binary)) # ナイーブベイズモデルを宣言 model = MultinomialNB() i=-1 # 進捗確認用の出力 i+=1; print(i) # CountVectorizer、tokenizerはmecab vectorizer = CountVectorizer(tokenizer=parse_text, ngram_range=ngram_range, binary=binary) s, t = calc_accuracy(df.title, df.label, model, vectorizer) scores += s times += t models += ["MultinomialNB"] * len(s) vectorizers += ["CountVectorizer"] * len(s) tokenizers += ["mecab-neologd"] * len(s) ngram_ranges += ["({},{})".format(ngram_range[0], ngram_range[1])] * len(s) binaries += [binary] * len(s) random_states += list(range(100)) i+=1; print(i) # CountVectorizer、tokenizerはchar vectorizer = CountVectorizer(analyzer="char", ngram_range=ngram_range, binary=binary) s, t = calc_accuracy(df.title, df.label, model, vectorizer) scores += s times += t models += ["MultinomialNB"] * len(s) vectorizers += ["CountVectorizer"] * len(s) tokenizers += ["char"] * len(s) ngram_ranges += ["({},{})".format(ngram_range[0], ngram_range[1])] * len(s) binaries += [binary] * len(s) random_states += list(range(100)) i+=1; print(i) # TfidfVectorizer、tokenizerはmecab vectorizer = TfidfVectorizer(tokenizer=parse_text, ngram_range=ngram_range, binary=binary) s, t = calc_accuracy(df.title, df.label, model, vectorizer) scores += s times += t models += ["MultinomialNB"] * len(s) vectorizers += ["TfidfVectorizer"] * len(s) tokenizers += ["mecab-neologd"] * len(s) ngram_ranges += ["({},{})".format(ngram_range[0], ngram_range[1])] * len(s) binaries += [binary] * len(s) random_states += list(range(100)) i+=1; print(i) # TfidfVectorizer、tokenizerはchar vectorizer = TfidfVectorizer(analyzer="char", ngram_range=ngram_range,binary=binary) s, t = calc_accuracy(df.title, df.label, model, vectorizer) scores += s times += t models += ["MultinomialNB"] * len(s) vectorizers += ["TfidfVectorizer"] * len(s) tokenizers += ["char"] * len(s) ngram_ranges += ["({},{})".format(ngram_range[0], ngram_range[1])] * len(s) binaries += [binary] * len(s) random_states += list(range(100)) i+=1; print(i) # HashingVectorizer、tokenizerはmecab vectorizer = HashingVectorizer(tokenizer=parse_text, ngram_range=ngram_range, alternate_sign=False, n_features=2**16, binary=binary) s, t = calc_accuracy(df.title, df.label, model, vectorizer) scores += s times += t models += ["MultinomialNB"] * len(s) vectorizers += ["HashingVectorizer"] * len(s) tokenizers += ["mecab-neologd"] * len(s) ngram_ranges += ["({},{})".format(ngram_range[0], ngram_range[1])] * len(s) binaries += [binary] * len(s) random_states += list(range(100)) i+=1; print(i) # HashingVectorizer、tokenizerはchar vectorizer = HashingVectorizer(analyzer="char", ngram_range=ngram_range, alternate_sign=False,n_features=2**16,binary=binary) s, t = calc_accuracy(df.title, df.label, model, vectorizer) scores += s times += t models += ["MultinomialNB"] * len(s) vectorizers += ["HashingVectorizer"] * len(s) tokenizers += ["char"] * len(s) binaries += [binary] * len(s) ngram_ranges += ["({},{})".format(ngram_range[0], ngram_range[1])] * len(s) random_states += list(range(100)) 結果の保存 #結果のデータフレーム化 df_accuracy = pd.DataFrame({ "model": models, "vectorizer": vectorizers, "tokenizer": tokenizers, "ngram_range": ngram_ranges, "binary": binaries, "random_state": random_states, "accuracy": scores, "time": times }) #結果の保存 df_accuracy.to_csv("df_accuracy.tsv", index=False, sep="\t") 結果の読み込み、確認 #結果の読み込み df_accuracy = pd.read_csv("df_accuracy.tsv", sep="\t") #結果の出力 df_accuracy.head() 要約情報の取得、保存 分析には直接関係ないですが、各条件の平均値、標準偏差を数値で確認しておきます。 # 結果の概要の表示 # accuracyのmeanを取得 df_describe = df_accuracy.groupby(["model", "vectorizer", "tokenizer", "ngram_range","binary"],as_index=False)["accuracy"].mean() # accuracyの列名をaccuracy_meanに変更 df_describe.columns = ["model", "vectorizer", "tokenizer", "ngram_range", "binary","accuracy_mean"] # accuracyのstdを取得 df_describe["accuracy_std"] = df_accuracy.groupby(["model", "vectorizer", "tokenizer", "ngram_range","binary"],as_index=False)["accuracy"].std()["accuracy"] # timeのmeanを取得 df_describe["time_mean"] = df_accuracy.groupby(["model", "vectorizer", "tokenizer", "ngram_range","binary"],as_index=False)["time"].mean()["time"] # timeのstdを取得 df_describe["time_std"] = df_accuracy.groupby(["model", "vectorizer", "tokenizer", "ngram_range","binary"],as_index=False)["time"].std()["time"] # 結果の保存 df_describe.to_csv("df_describe.tsv",sep="\t",index=False) # 結果の表示 df_describe.head() model vectorizer tokenizer ngram_range binary accuracy_mean accuracy_std time_mean time_std 0 MultinomialNB CountVectorizer char (1,1) False 0.709704 0.006653 0.013404 0.003324 1 MultinomialNB CountVectorizer char (1,1) True 0.723350 0.006072 0.020566 0.013112 2 MultinomialNB CountVectorizer char (1,3) False 0.797581 0.005588 0.059319 0.007868 3 MultinomialNB CountVectorizer char (1,3) True 0.807527 0.005507 0.090673 0.020877 4 MultinomialNB CountVectorizer char (3,3) False 0.798833 0.005596 0.041194 0.005625 グラフの表示保存 accuracyに関する箱ひげ図を作成します。 #accuracyの全結果 import matplotlib.pyplot as plt fig = df_accuracy.boxplot(column="accuracy", by=["vectorizer", "tokenizer", "ngram_range","binary"],rot=90, figsize=(10,5)) fig.set_ylabel("accuracy") fig.set_ylim(0,1) plt.savefig("./accuracy_boxplot.png", bbox_inches="tight", pad_inches=0.1) plt.show() timeに関する箱ひげ図を作成します。 fig = df_accuracy.boxplot(column="time", by=["vectorizer", "tokenizer", "ngram_range","binary"],rot=90, figsize=(10,5)) fig.set_ylabel("time (sec) per single train") fig.set_ylim(0,1) plt.savefig("./time_boxplot.png", bbox_inches="tight", pad_inches=0.1) plt.show() 分散分析 必要ライブラリを読み込みます。 import statsmodels.api as sm from statsmodels.formula.api import ols import pandas as pd import numpy as np import statsmodels.stats.anova as anova accuracyについての分散分析を実行します。 Python aov = anova.AnovaRM(df_accuracy, "accuracy", "random_state",within=["vectorizer", "tokenizer", "ngram_range","binary"]) result = aov.fit() print(result) timeについての分散分析を実行します。 aov = anova.AnovaRM(df_accuracy, "time", "random_state",within=["vectorizer", "tokenizer", "ngram_range","binary"]) result = aov.fit() print(result) 下位検定 結果では触れていませんが、下位検定をする場合は以下で可能です。 モジュールをimportします。 #対応ありt検定のモジュールをimport from scipy import stats from statsmodels.stats.multicomp import pairwise_tukeyhsd import pingouin as pg 主効果に関する下位検定(t検定)をします。 #vectorizerのt検定 posthocs = pg.pairwise_ttests(dv='accuracy', within='vectorizer', subject='random_state', padjust="bonf",data=df_accuracy) print(posthocs.round(3)) #tokenizerのt検定 posthocs = pg.pairwise_ttests(dv='accuracy', within='tokenizer', subject='random_state', padjust="bonf",data=df_accuracy) print(posthocs.round(3)) #ngram_rangeのt検定 posthocs = pg.pairwise_ttests(dv='accuracy', within='ngram_range', subject='random_state', padjust="bonf",data=df_accuracy) print(posthocs.round(3)) #binaryのt検定 posthocs = pg.pairwise_ttests(dv='accuracy', within='binary', subject='random_state', padjust="bonf",data=df_accuracy) print(posthocs.round(3)) Contrast A B Paired Parametric \ 0 vectorizer CountVectorizer HashingVectorizer True True 1 vectorizer CountVectorizer TfidfVectorizer True True 2 vectorizer HashingVectorizer TfidfVectorizer True True T dof alternative p-unc p-corr p-adjust BF10 hedges 0 300.048 99.0 two-sided 0.0 0.0 bonf 5.785e+143 20.653 1 132.300 99.0 two-sided 0.0 0.0 bonf 1.019e+109 7.593 2 -206.300 99.0 two-sided 0.0 0.0 bonf 6.997e+127 -12.589 Contrast A B Paired Parametric T dof \ 0 tokenizer char mecab-neologd True True 43.576 99.0 alternative p-unc BF10 hedges 0 two-sided 0.0 5.158e+62 3.985 Contrast A B Paired Parametric T dof alternative \ 0 ngram_range (1,1) (1,3) True True -216.416 99.0 two-sided 1 ngram_range (1,1) (3,3) True True 48.683 99.0 two-sided 2 ngram_range (1,3) (3,3) True True 247.933 99.0 two-sided p-unc p-corr p-adjust BF10 hedges 0 0.0 0.0 bonf 7.548e+129 -15.472 1 0.0 0.0 bonf 1.649e+67 5.106 2 0.0 0.0 bonf 4.502e+135 20.772 Contrast A B Paired Parametric T dof alternative p-unc \ 0 binary False True True True -56.803 99.0 two-sided 0.0 BF10 hedges 0 3.563e+73 -1.222 ngram_rangeとtokenizerの単純主効果の分析をします。 #ngram_rangeとtokenizerの単純主効果分析 posthocs = pg.pairwise_ttests(dv='accuracy', within=['tokenizer', 'ngram_range'], subject='random_state', padjust="bonf",data=df_accuracy) print(posthocs.round(3)) Contrast tokenizer A B Paired \ 0 tokenizer - char mecab-neologd True 1 ngram_range - (1,1) (1,3) True 2 ngram_range - (1,1) (3,3) True 3 ngram_range - (1,3) (3,3) True 4 tokenizer * ngram_range char (1,1) (1,3) True 5 tokenizer * ngram_range char (1,1) (3,3) True 6 tokenizer * ngram_range char (1,3) (3,3) True 7 tokenizer * ngram_range mecab-neologd (1,1) (1,3) True 8 tokenizer * ngram_range mecab-neologd (1,1) (3,3) True 9 tokenizer * ngram_range mecab-neologd (1,3) (3,3) True Parametric T dof alternative p-unc p-corr p-adjust BF10 \ 0 True 43.576 99.0 two-sided 0.0 NaN NaN 5.158e+62 1 True -216.416 99.0 two-sided 0.0 0.0 bonf 7.548e+129 2 True 48.683 99.0 two-sided 0.0 0.0 bonf 1.649e+67 3 True 247.933 99.0 two-sided 0.0 0.0 bonf 4.502e+135 4 True -229.806 99.0 two-sided 0.0 0.0 bonf 2.677e+132 5 True -173.003 99.0 two-sided 0.0 0.0 bonf 2.368e+120 6 True -7.464 99.0 two-sided 0.0 0.0 bonf 2.666e+08 7 True -69.816 99.0 two-sided 0.0 0.0 bonf 1.301e+82 8 True 257.836 99.0 two-sided 0.0 0.0 bonf 2.078e+137 9 True 356.651 99.0 two-sided 0.0 0.0 bonf 1.29e+151 hedges 5 -20.525 6 -0.607 7 -4.692 8 32.934 9 37.888 おわりに scikit-learnでのテキスト分類を最近勉強し始めたのですが、パラメータが多く、何をどう設定すればよいかあまりわからない状態でした。 必ずしも一般性のある結果ではありませんが、各種パラメータが精度にどう影響を与えるのかの感覚がつかめた気がしたので良かったです。 異なる学習モデルやコーパスで学習する検証も機会があればやってみたいです。 参考 scikit-learnのドキュメント。特に、CountVectorizer, TfidfVectorizer, HashingVectorizerに関する部分。 https://scikit-learn.org/stable/modules/classes.html#module-sklearn.feature_extraction.text 対応あり分散分析のパッケージ https://www.statsmodels.org/dev/generated/statsmodels.stats.anova.AnovaRM.html pairwise_ttest用のパッケージ https://pingouin-stats.org/generated/pingouin.ttest.html
- 投稿日:2022-02-23T23:15:07+09:00
PythonのGUIアプリをファイルひとつで実行する
Pythonで書いたGUIアプリを実行ファイルにすると便利なのではないだろうか? 何がしたいのか この文章は、Pythonで作ったアプリを広くみんなに使ってもらいたい、そんな思いを実現するための記事です。 ここではWindowsを対象とします。MacやLinuxでも同様にできるようですが、試していません。 Pythonは、コードを書いて実行するのはお手軽なのですが、他の人にPythonのアプリを使ってもらうのはなかなか大変です。 まず、Pythonを入れてもらわなければいけません。この段階でハードルが高すぎるのですが、その次にコマンドラインでPythonのアプリを実行してもらわなければいけません。 環境さえ作れれば良いのですが、その環境を作成するのに手間がかかり、恐らく使うところまでは行きつけそうにありません。 ならば普通のWindowsアプリのように、GUIがあり、ファイルひとつで実行できれば作ったアプリを活用してもらうのが容易になりそうです。 必要なもの 今回使用するのは以下です。 - Python - tkinter - Pyinstaller また、必須ではありませんが開発環境としてVisual Studio Codeがあると便利です。 PythonとVScodeのインストール方法は下記リンクを参照してください。 PythonでGUIアプリ PythonでGUIアプリを作る手段はいくつかありますが、ここではtkinter(ティーケーインター)を使用します。 tkinterは、Pythonの標準ライブラリに入っており追加インストールが必要ありません。 tkinterのドキュメントは下記リンクを参照してください。 Tk を用いたグラフィカルユーザインターフェイス ウインドウを表示するプログラムは以下のようになります。 window.py from logging import root import tkinter as tk root = tk.Tk() root.title('ウインドウ表示') root.geometry('320x240') root.mainloop() Pythonのコードを実行ファイル化 Pythonで作ったプログラムを実行ファイルにするにはPyinstallerを使います。 PyinstallerはPythonアプリとそのすべての依存ファイルを1つのファイルにしてくれます。 Pyinstallerのマニュアルは下記リンクを参照して下さい。英語ですが・・・(汗) PyInstaller Manual Pyinstallerを使用するにはまずインストールします。ここでは仮想環境を作成してインストールします。 PS D:\project\desktop> python -m venv venv PS D:\project\desktop> .\venv\Scripts\activate (venv) PS D:\project\desktop> pip install pyinstaller 先ほどのプログラムを実行ファイルにするには以下のコマンドを実行します。最後にsuccessfullyが表示されれば出来上がりです。 (venv) PS D:\project\desktop> pyinstaller window.py --onefile --noconsole ・ ・ ・ 59127 INFO: Building EXE from EXE-00.toc completed successfully. distフォルダが作成され、その中に実行ファイルができています。 出来上がった実行ファイルはPythonがインストールされていないPCでも実行することができます。 Pyinstallerはクロスコンパイラではないため、Windowsで作成するとWindowsでのみ使用できる実行ファイルとなります。 ともあれ、これでPythonでGUIアプリを作ってファイルひとつで実行することができるようになりました。 おまけ GUIアプリの例としてサイコロアプリを作ってみました。 dice.py from logging import root from os import system import random from struct import pack import tkinter as tk root = tk.Tk() root.title('サイコロ') root.geometry('320x240') lbl = tk.Label(root, text=' ', padx=5, pady=5, font=('system', 20) ) def roll_dice(): lbl['text'] = text=str(random.randint(1, 6)) return btn = tk.Button(root, text='サイコロを振る', command=roll_dice) lbl.pack(pady=50) btn.pack() root.mainloop()
- 投稿日:2022-02-23T21:36:03+09:00
Streamlit上でPyCaretを動かす方法
こんにちは。Qiita初投稿です。 はじめに PythonコードだけでWebアプリを作れるStreamlitと 数行のコードで機械学習の前処理から推定までできるPyCaretというものがあります。 Streamlit上でPyCaretを動かす方法を調べると、ライブラリをイジる方法しかヒットせず、少しハードルが高かったのでライブラリをイジらずに動かす方法を試していこうと思います。 環境 Python 3.7 Streamlit 1.5.1 PyCaret 2.3.6 手順 クラス分類や回帰など問題ごとにライブラリが分かれているので、使用するものをimportします。 今回は回帰で試していきます。 from pycaret.regression import * StreamlitとDataFrameも扱うので、そちらもimportしておきます。 import pandas as pd import streamlit as st データの取得 今回はボストンの住宅価格データセットを使います。 from pycaret.datasets import get_data data = get_data("boston") 前処理 前処理はsetup関数を使います。 デフォルトではデータの型推定が正しいか入力を求められますが、Streamlitでは入力を返せないので無効にします(html=False, silent=True)。 pipe = setup(data, target="medv", html=False, silent=True) モデル比較 PyCaretでは学習に使うモデルを選択することができます。 それぞれのモデルの比較はcompare_models関数で実行できます。 実行結果をpandasのDataFrameで取り出し、表示させます。 best = compare_models() # モデル比較 best_model_results = pull() # 比較結果の取得 st.write(best_model_results) # 比較結果の表示 デフォルトでは決定係数R2のスコアが良い順にソートされて出力されます。 モデル作成 モデル作成はcreate_model関数を使用します。 モデル比較で表示されるモデル名(name)と、関数の引数で指定するモデル名(ID)が異なります。 (いつからからモデル比較結果のIndexにモデルIDが表示されるようになりました) model = create_model("et") # Extra Trees Regressorを使用 複数のモデルでブレンドモデルやアンサンブルモデルを作成することもできます。 ブレンドモデル et = create_model("et") # Extra Trees Regressorを使用 lightgbm = create_model("lightgbm") # Light Gradient Boosting Machine model = blend_models(estimator_list=[et, lightgbm]) アンサンブルモデル ensembled_model = ensemble_model(model) モデル作成は特にStreamlit向けに調整する事項はありません。 モデルの可視化 モデルの可視化はplot_model関数を使用します。 可視化の内容は学習曲線や残差などいくつか用意されていますが、 リファレンスにもある通り、すべての可視化内容がStreamlitで表示できるようにはなっていません。 (対応していても表示に時間がかかるものもありました) https://pycaret.readthedocs.io/en/latest/api/regression.html#pycaret.regression.plot_model plot_model(model, plot="cooks", display_format="streamlit") クックの距離を描写するとこんな感じです。 予測 予測はpredict_model関数を使います。 特にデータを指定しなければ、setup実行時にテスト用に分割してあったデータセットの一部で予測を行います。 predictions = predict_model(model) st.write(predictions) 予測結果はLabelというカラムに出力されます。 medv(住宅価格)を予測しましたが、何も考えずにモデルを作ったわりには、それなりの精度がでています。 st.cache Streamlitの性質上、ユーザがUIを操作するたびにPyCaretの関数が再実行されることがあります。 @st.cacheを使って対策を取っておくと良いでしょう。 create_modelの例 @st.cache(allow_output_mutation=True) def create_model_cache(estimator): return create_model(estimator) さいごに setupとplot_modelでそれぞれオプションを指定し、また必要に応じてst.cacheを活用すれば Streamlit上でPyCaretを動かすことができます。 サンプルプログラムをGitHubに公開しているので、よければ試してみてください。 参考 PyCaret 最速でPyCaretを使ってみた PyCaretとStreamlitでAutoMLのGUIツールをさくっと作ってみる Pycaretを使って複数モデルをマージして予測してみました
- 投稿日:2022-02-23T19:16:33+09:00
django-stdimageで元の画像を削除する
はじめに django-stdimageは画像サイズを変更して保存してくれます. 元の画像はいらないので削除しましょう。 こちらにまとめさせていただきました。
- 投稿日:2022-02-23T19:05:30+09:00
heapq.nlargest(n, iterable)でリストの上位n番目までをスッキリ抽出
はじめに heapq.nlargest(n, iterable, key=None) はリストなどの上位n番目までの要素をリストで返します。。調べても記事がなかったので書いておきます。 説明 heapq.nlargest(n, iterable, key=None) ではiterableで定義されるデータセットのうち、最大値から降順にn個の値のリストを返します。 つまり、 sorted(iterable,key=None,reverse=True)[:n] と同義です。 実行 sample=[5,7,3,12,7,3,1] を例に取って実行します。 コード sample=[5,7,3,12,7,3,1] print(heapq.nlargest(5,sample)) print(sorted(sample,reverse=True)[:5]) 結果 [12, 7, 7, 5, 3] [12, 7, 7, 5, 3] 次に、 sample=[("c",5),("b",7),("e",3),("f",12),("a",7),("d",3),("g",1)] を例にとって実行します。 コード sample=[("c",5),("b",7),("e",3),("f",12),("a",7),("d",3),("g",1)] # 普通にソート print(heapq.nlargest(5,sample)) print(sorted(sample,reverse=True)[:5]) # 第二引数に関してソート print(heapq.nlargest(5,sample,key=lambda t:t[1])) print(sorted(sample,reverse=True,key=lambda t:t[1])[:5]) 結果 [('g', 1), ('f', 12), ('e', 3), ('d', 3), ('c', 5)] [('g', 1), ('f', 12), ('e', 3), ('d', 3), ('c', 5)] [('f', 12), ('b', 7), ('a', 7), ('c', 5), ('e', 3)] [('f', 12), ('b', 7), ('a', 7), ('c', 5), ('e', 3)] まとめ sortedを使うより多少コード量が減るし、見栄え的にもわかりやすいので、heapqをインポートしているような場面では積極的に使っていこうと思っています。
- 投稿日:2022-02-23T17:08:33+09:00
エンジニアが学ぶべき言語は?
Q:エンジニアが学ぶべき言語は? A:日本語 まあ日本で仕事するならそうだよね。状況を説明できないエンジニアが結構いるよね。
- 投稿日:2022-02-23T17:05:22+09:00
【備忘録】バイオインフォマティクス環境設定
【備忘録】バイオインフォマティクス環境設定(macOS Monterey 12.2.1) 「バイオインフォマティクス 〜Pythonによる実践レシピ〜」を購入して、環境構築したときのメモです。 大まかな流れは、以下の通り。 - Xcodeインストール(GUI) - wgetインストール(コマンドライン) - Anacondaインストール(GUI) - condaによるインストール(コマンドライン) condaによるインストール Xcode、 wget、 Anaconda のインストールは省略して、condaのところだけ詳細に記載します。 まずは、conda自身の確認。 $conda install conda -y チャネル(リポジトリ)の追加。 $conda config --add channels bioconda $conda config --add channels conda-forge 環境構築。(biopythonは最新にして、pythonは3.8に設定) $conda create -n bio python=3.8 biopython=1.79 環境のアクティベーション。 $source activate bio 環境上で諸々をインストール。(細かく分けているのは、どこで失敗したかを確認するため) $conda install scipy matplotlib pip pandas cython numba -y $conda install scikit-learn seaborn pysam pyvcf -y $conda install rpy2 -y $conda install r-essentials r-gridextra -y $conda install simuPOP dendropy -y これで設定完了です。 Anacondaを立ち上げて、bioでJupyter-Labを起動します。
- 投稿日:2022-02-23T16:08:40+09:00
Android端末で新と旧アプリをpython+Appiumで自動実行させて得たエビデンス画像をExcelファイルに貼り付けて新旧画像の比較結果を示した
(゜д゜)ノやぁーどもです。 今回は、割と結構実用的なプログラムを作ってみましたよ! 1年くらい前に↓の記事で、Android端末でアプリケーションをpython+Appiumで自動実行させることをしましたが、今回、これを使って作った自動実行プログラムで、新アプリと旧アプリそれぞれで貯めたエビデンス画像をExcelファイルに新と旧で並べて貼っていって、「新と旧で差異がないよね」ってことの確認を支援するプログラムを作ってみました。 背景 私が担当したエンハンスプロジェクトで、現在のアプリ(以降、旧アプリ)で使っていたAPIの全てを別のAPI(そのとき開発中のもの)に置き換えるという案件を実施しました。新APIでは、単純に置きなおしすればよいというものではなく、その1つ1つの動作仕様が違うものやパラメーターも違うものもあり、こちら側のプログラムロジックもだいぶ修正することになったのです。そして、旧アプリと新アプリでまったく同じ動作を保証しなければなりませんでした。 まず、この連結試験で、以前使ったpython+Appiumで自動実行プログラムを開発して、旧アプリと新アプリで実行してみて、そのエビデンス画像の比較をすればテスト工数がだいぶ省けると考えたのですが、さらに、そこで、そのエビデンス画像を手作業でExcelファイルに貼って、その結果を人の目で確認していくという作業に工数が掛かっているという所に目を付けたのです。 人が判断する箇所も必要でしょうが、新と旧の画像で同一の名前のものを確認してExcelファイルに貼っていくというのは、人でなくてもできることです。なので、そこをpythonにさせようと思いました。さらに、人の目で判断する上で、2画像の差分を示してあげることでより確認作業が楽になるのではないかと思い、比較した類似度と、比較した差異画像を生成するということも同時に行ったのです。 以下、pythonにさせた事と人がやることにした作業の切り分けです。 今回作ったプログラムの概要 【前作業】 python+Appiumで作った自動実行プログラムを新アプリと旧アプリで動作させ、それぞれのエビデンス画像を別フォルダで貯め込んでおく。 【今回の範囲】 1,pythonで、新アプリ、旧アプリの結果フォルダを参照して、同一名称の画像を並べてExcelファイルに貼りつける。 2,pythonで、その2画像の類似度を算出して、Excelファイルに記録 3,pythonで、その2画像の差異を示す画像を作成して、Excelファイルに貼り付ける (※この時点でExcelファイル完成) 4,人(開発メンバー)がExcelファイルを開いて、新と旧の差異を見て確認していく 結果となるExcelファイルを開くと、↓このような感じで差異を示すことができます。 上部にファイル名と算出した類似度を示すとともに、 新と旧のエビデンス画像を左右に貼り付け、真ん中に差異を示す画像を貼り付けてます。 技術解説 類似度の算出 ↓類似度の算出は、「02_2画像の画素値を比較し類似度を算出」の方で実装しました。 理由は、今回は新アプリと旧アプリで同じ端末を利用するつもりなため、同じ動きであったならば画素的なズレは全く無いはずだからです。もし、新アプリと旧アプリで別端末を利用するのであればヒストグラムでの算出の方が得たい結果に近くなるのではないかと思います。 差異画像の生成 ↓の「02_画像比較して異なる箇所が分かる画像を生成するプログラム」の「03_diff_center.png」のやり方の方で、実装しました。 Excelファイル作成 pythonでExcelファイルを作成するに当たっては、「openpyxl」というライブラリを利用しました。 ググったら色々サンプル見つかります。 プログラム ↓Gitに格納しておきました。 ダウンロードして頂いて実行環境がWindowsの方は「install_library.bat」を実行することで、必要なライブラリがインストールされるはずです。LinuxやMacの方は「install_library.bat」の中身を実行してみてください。 そのあと、「main.py」を実行していただければ、動作するはずです。 「tmp01」と「tmp02」に新アプリと旧アプリを想定したサンプル画像が入ってます。 まとめ 今回、新アプリと旧アプリの動作比較という割とありがちな案件で使えるプログラムを作ってみました。 実は、まだ実運用で利用していなくって、この記事を書いている1~2カ月後くらいに実行する予定なのです。 実案件で利用後にまたレポート書いてみます。 この記事を読んでて、新アプリと旧アプリの比較という用件のある方は是非使ってみて頂きたいです。
- 投稿日:2022-02-23T16:08:06+09:00
Raspberry Piでインターホンの音を検知してLINEに通知する (1)インターホンの音を録音する
住居に必ず備わっているインターホン。 ・聞こえづらい部屋がある ・イヤホンをしていると聞こえない ・外出中の来客を把握したい という課題・要望に対応すべく、Raspberry Piを使ってLINEに通知するシステムを作りました。 LINEであれば、スマホがブーブブッと振動して気づきやすいですよね。 少し調べたところ、インターホンのLEDや画面の点灯で検出するものが多かったですが、 自分のアパートのインターホンだと難しそうだったので、音で検知するようにしました。 FFTを使って、インターホンに対応する周波数(音の高さ)を検出します。 いろんなサイトから組み合わせる形になったので、まとめます。 今回は、Raspberry Piを使ってインターホンの音を録音するところまで。 このデータを使って、音の検知基準を作成します。 ※準備では.wavファイルに保存していますが、運用時には保存せずに処理します。 装置:Raspberry Pi 4 マイク:共立プロダクツ MI-305 [USBマイク] https://www.yodobashi.com/product/100000001004508009/ ※Raspberry Piにはジャックマイクは使用できないため、USBマイクが必須。ジャックはOutputのみ。 プログラム言語:Python3 (PyAudioモジュールを使用。 PyAudio Documentation) Terminalで接続の確認 参考:Raspberry Pi オーディオまとめ Raspberry PiのTerminalを開き、デバイスをチェック。 pi@raspberrypi:~ $ cat /proc/asound/devices 0: [ 0] : control 16: [ 0- 0]: digital audio playback 32: [ 1] : control 33: : timer 48: [ 1- 0]: digital audio playback 64: [ 2] : control 88: [ 2- 0]: digital audio capture 88の"~ capture"がUSBマイクの抜き差しに応じて現れたり消えたりしたら認識されているはず。 64の"control"も同様にマイクに反応して出現するみたい。 Terminalのままマイクテストを行いたい場合は、上記リンクの「マイクチェック」を参照。 PyAudioのインストール 参考:raspberry Piにpyaudioをインストール PyAudioを使って録音していきます。pipによるインストールが必要。 上記リンクを参考に、下記のようにsudoをすればOK。 sudo apt-get install python-dev portaudio19-dev sudo pip3 install pyaudio PyAudioで使用するデバイス番号を確認 参考:https://forums.raspberrypi.com/viewtopic.php?t=71062 上記Terminalで表示されるデバイス番号と、PyAudioで使用するデバイス番号は関係無い。 (参考リンクにも、"Device indexes in pyaudio are pretty arbitrary."とある。) 下記を使って確認。 CheckDevID.py # https://forums.raspberrypi.com/viewtopic.php?t=71062 import pyaudio p = pyaudio.PyAudio() for i in range(p.get_device_count()): dev = p.get_device_info_by_index(i) print((i,dev['name'],dev['maxInputChannels'])) # (0, 'bcm2835 HDMI 1: - (hw:0,0)', 0) # (1, 'bcm2835 Headphones: - (hw:1,0)', 0) # (2, 'USB PnP Sound Device: Audio (hw:2,0)', 1) # (3, 'sysdefault', 0) # (4, 'lavrate', 0) # (5, 'samplerate', 0) # (6, 'speexrate', 0) # (7, 'pulse', 32) # (8, 'upmix', 0) # (9, 'vdownmix', 0) # (10, 'dmix', 0) # (11, 'default', 32) 自分の場合、USBマイクは2番。'maxInputChannels'が1なのはモノラルであることを示している。 PyAudioでインターホンの音を録音する 参考:RaspberryPi + Python3でPyaudioとdocomo音声認識APIを使ってみる record.py import pyaudio import wave input_device_index = 2 # 先ほど確認したデバイス番号 CHUNK = 1024 FORMAT = pyaudio.paInt16 CHANNELS = 1 # モノラル入力 # 先ほど確認したmaxInputChannelsが上限 RATE = 44100 RECORD_SECONDS = 5 WAVE_OUTPUT_FILENAME = "output.wav" p = pyaudio.PyAudio() stream = p.open(format=FORMAT, channels=CHANNELS, input_device_index = input_device_index, rate=RATE, input=True, frames_per_buffer=CHUNK) print("Recording...") frames = [] for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)): data = stream.read(CHUNK) frames.append(data) print("Done!") stream.stop_stream() stream.close() p.terminate() wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb') wf.setnchannels(CHANNELS) wf.setsampwidth(p.get_sample_size(FORMAT)) wf.setframerate(RATE) wf.writeframes(b''.join(frames)) wf.close() これで"output.wav"に音が録音されているはず。 ジャックにイヤホンを接続して聞いてみる。 問題無さそうなら、プログラム開始と同時にインターホンを鳴らして録音する。 (スマホなどにも録音しておくとチェックの時に便利。) なお、ここで怒涛の警告メッセージが出た(長いので最後に載せる)。 エラーではなく警告メッセージなので、これが現れても、 "Recording..."~"Done!"の部分は表示され、.wavは正常に保存されるはず。 しかしこの量の警告メッセージは煩わしいので、ALSAに関わる部分を表示させなくする方法を見つけたので次回まとめる。 (ALSAとは、Raspberry Pi オーディオまとめにあるように、ALSA(Advanced Linux Sound Architecture)のこと) 一方で、 OSError: [Errno -9981] Input overflowed が表示される場合は、エラーなので、.wavは保存されない。 これは、 CHUNK = 1024 * 4 # または、*8, *16, ... と、CHUNKを大きくすることで回避できる。 CHUNKとは、音源から1回読み込むときのデータサイズのこと(PyAudioの基本メモ1)。 CHUNKはdata = stream.read(CHUNK)の部分で使っているが、おそらく、読み込み量・頻度が小さく、読み込まれないまま消えていったデータがあるときに上記エラーが出ると思われる。したがって、CHUNKを大きくすればOK。 しかし、運用するためにずっと回していると、CHUNKを大きくしても突発的に上記エラーで止まることがある。 overflowを無理矢理回避する方法も次回にまとめる。 今回の録音程度では、CHUNKを大きくしていれば問題無いはず。 まとめ Raspberry Pi + PyAudioを使ってインターホンの音を録音することができました。 次回は、この音を解析する・・・前に、ALSAの警告メッセージとoverflowエラーの対処についてまとめます。(ここが調べるのに一番苦労したところ・・・) その他の記事: Raspberry Piでインターホンの音を検知してLINEに通知する (2)PyAudio録音時の警告・エラーに対処する 参考 PyAudio Documentation Raspberry Pi オーディオまとめ raspberry Piにpyaudioをインストール https://forums.raspberrypi.com/viewtopic.php?t=71062 RaspberryPi + Python3でPyaudioとdocomo音声認識APIを使ってみる PyAudioの基本メモ1 文中で述べた警告メッセージ ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.front.0:CARD=0' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM front ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.surround51.0:CARD=0' ...(中略)... ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM surround71 ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.iec958.0:CARD=0,AES0=4,AES1=130,AES2=0,AES3=2' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM iec958 ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.iec958.0:CARD=0,AES0=4,AES1=130,AES2=0,AES3=2' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM spdif ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.iec958.0:CARD=0,AES0=4,AES1=130,AES2=0,AES3=2' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM spdif ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'defaults.bluealsa.device' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5036:(snd_config_expand) Args evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM bluealsa ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'defaults.bluealsa.device' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5036:(snd_config_expand) Args evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM bluealsa Cannot connect to server socket err = No such file or directory Cannot connect to server request channel jack server is not running or cannot be started JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock Cannot connect to server socket err = No such file or directory Cannot connect to server request channel jack server is not running or cannot be started JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock ALSA lib pcm_oss.c:377:(_snd_pcm_oss_open) Unknown field port ALSA lib pcm_oss.c:377:(_snd_pcm_oss_open) Unknown field port ALSA lib pcm_a52.c:823:(_snd_pcm_a52_open) a52 is only for playback ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.iec958.0:CARD=0,AES0=6,AES1=130,AES2=0,AES3=2' ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM iec958:{AES0 0x6 AES1 0x82 AES2 0x0 AES3 0x2 CARD 0} ALSA lib pcm_usb_stream.c:486:(_snd_pcm_usb_stream_open) Invalid type for card ALSA lib pcm_usb_stream.c:486:(_snd_pcm_usb_stream_open) Invalid type for card Cannot connect to server socket err = No such file or directory Cannot connect to server request channel jack server is not running or cannot be started JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
- 投稿日:2022-02-23T15:19:57+09:00
二次元データの並び替え python
やりたいこと frame_grabber.exeを用いてRPLiDARから取得したデータ(x,y,z)をyに注目して並び替えたい #RPLIDAR SCAN DATA #COUNT=457 #Angule Distance Quality 358.3411 742.0 188 0.9668 735.0 188 1.7468 736.0 188 2.4060 734.0 188 3.0597 733.0 188 3.7134 732.0 188 4.2462 733.0 188 4.9054 731.0 188 5.5591 730.0 188 6.3391 727.0 188 6.8719 728.0 188 7.6520 726.0 188 8.3112 725.0 188 8.9648 725.0 188 9.6240 728.0 188 こんな感じにRPLiDARからデータを取得できる! これをzを消してyを並び替える 想定環境 ・Jupyter-notebook 実際にやってみる f = open("./a.pts",'r') data = f.read() datas_list = data.split('\n') datas_float = [] for data_list in datas_list: datas_float.append(data_list.split(' ')) まず初めにptsファイルを読み込む ※これは,txtファイルと同様に読み込めるみたい 次に改行ごとに分けて,スペースごとに区切って,一個ずつのデータとして見る datas_floatの中身:list datas_float[ i ]:list datas_float[ i ][ j ]:str x座標 y座標 z座標 '352.1228' '2101.0' '188' '352.7765' '2165.0' '188' '353.5620' '1787.0' '188' ・ ・ ・ ・ ・ ・ ・ ・ ・ z座標はいらなくて,x座標,y座標だけをfloat型で取り出せるようにしたい データの数は1397 そしてこの時はまだstr型 これをfloat型に変換する x_list = [] y_list = [] x座標,y座標それぞれのリストを作成する for i in range (len(datas_float)-1): for ii in range(2): if(ii % 2 == 1): x_list.append(datas_float[i][0]) # [i][0]はx座標 else: y_list.append(datas_float[i][1]) # [i][1]はy座標 # [1][2]はz座標 この時点ではstr x_list y_list '352.1228' '2101.0' '352.7765' '2165.0' ・ ・ ・ ・ ・ ・ '351.0132' '1583.0' list_float = [] for i in range(len(x_list)): num = [] num.append(float(x_list[i])) num.append(float(y_list[i])) list_float.append(num) x座標とy座標を結合させる list_float_sorted = sorted(list_float, key=lambda x: x[1]) list_float_sorted[:5] 要素1に着目てして並び替える list_float 要素0 要素1 201.9979 156.0 211.6515 159.0 ・ ・ ・ ・ ・ ・ 9.1241 2749.0 これで昇順の並び替え完了しました!! おまけ list_float_sorted = sorted(list_float, key=lambda x: x[1],reverse=True) list_float_sorted[:5] keyの後に"reverse=True"を指定すると,昇順から降順になる!!
- 投稿日:2022-02-23T14:57:03+09:00
薬物の呼称でWORDLE作った (薬物WORDLE) 【Next.js】
はじめに どうもyoshiiです。 今回は新しく作ったアプリの紹介です。 あんま技術的な話とかはないです。 薬物WORDLE 薬物の呼称でWORLDEができます。 ぜひ遊んでみてね。 薬物WORDLE作りました俺が薬物の呼称に詳しくないので90個しか単語なくてめちゃくちゃ難しいですこの後0時から毎日解答が更新されますぜひ遊んでみてねhttps://t.co/2lrJxIuZaU#薬物WORDLE pic.twitter.com/6VaNTYLXL9— yoshii? (@ganja_tuber) February 22, 2022 制作期間 3日 使った技術 Python: 薬物の呼称を収集 Next.js(TypeScript): フロントエンドとAPIルートでバックエンドもちょっと Firestore: その日の解答情報を保存 実装方法を簡単に説明 単語の収集方法 薬物をまとめているサイトから、pythonでhtml解析して収集しました。 TypeScriptでもurllibとbeautifulsoupみたいなのほしいなあ… 単語の入力方法 ボタンではなく、キーボードで入力できるようにしています。 これはPOKÉDLEさんを参考にしました。 実装方法は、divを6個(今回は6文字なので)flex-boxで並べて、そこにinputを透過して重ねています。 詳しく説明するのはかなり面倒なので、気になったらTwitterとかで聞いてください。 モーダルビュー モーダルビューはreact-modalっていうクソ便利なライブラリがあったのでお借りしました。 作ってみて 最近Next.jsのドキュメントを1から全部読んで、なんか作りたいな〜と思ったのがきっかけでした。 APIルートを使えば、サーバー側との簡単なやりとりくらいはNext.jsだけで完結できて便利でした。 Next.jsは個人開発で真価を発揮すると思いました。自分ルールでガンガン書いてもそれなりに綺麗な感じになって、後から修正するのも簡単そうです。 逆に言えば、チーム開発でも、事前にしっかりルールをチーム内で決めておけば、いい速度で開発が進みそうだなと思いました。 最後に 普段からなんのありがたみもないアプリを作ったり、飲酒ツイートしかしてないですが、友達になってくれると嬉しいです。
- 投稿日:2022-02-23T14:37:23+09:00
Python マルチスレッドで完全にデッドロックなしでSQLite3を使うためのメモ
はじめに トランザクションやらコミットのこと無知な状態でマルチスレッドでsqliteやろうとしたらデッドロックしたのでメモ threading.Event()でトランザクション前にスレッドを待機させる import threading import random import time # event3の中でevent1→event2の順でイベントが実行される # event1はランダム秒後に開始する、event3の終了を待つ,終了したらevent3_endをclear # event2はevent1が開始するまで待機状態 # event3はevent1が開始したらevent1をclearにしevent2を待つ # event3でevent2が開始したらevent2をclearにしevent3の終了を告げる # 上記繰り返し test_event1_begin = threading.Event() test_event2_begin = threading.Event() test_event3_end = threading.Event() def test_sql1(): global test_event3_end while True: interval = random.uniform(1, 3) print(f'evet1 {interval}sec 後に開始') time.sleep(interval) # event1を開始 test_event1_begin.set() # event3が終了するまで待機 test_event3_end.wait() test_event3_end.clear() def test_sql2(): global test_event1_end, test_event2_end while True: time.sleep(0.001) # event1を待機 test_event1_begin.wait() interval = random.uniform(1, 3) print(f'evet2 {interval}sec 後に開始', end='\n') time.sleep(interval) # event2を開始 test_event2_begin.set() def test_sql3(): global test_event1_end, test_event2_end, test_event3_end while True: time.sleep(0.001) test_event1_begin.wait() test_event1_begin.clear() test_event2_begin.wait() test_event2_begin.clear() print('event3終了') time.sleep(1) test_event3_end.set() def main(): thread_list = [] tasks = [test_sql1, test_sql2, test_sql3] for task in tasks: th = threading.Thread(target=task, args=()) th.start() thread_list.append(th) for th in thread_list: th.join() if __name__ == '__main__': main() evet1 1.198280231761349sec 後に開始 evet2 1.606435444539516sec 後に開始 event3終了 evet1 2.7041704830942663sec 後に開始 evet2 1.2936549765944632sec 後に開始 event3終了 evet1 1.3772512376025186sec 後に開始 evet2 1.7498730389596326sec 後に開始 event3終了 結論 上記のように別スレッドからEventフラグを使ってスレッド管理するのもありですが、トランザクション処理は単一のスレッドで行ったほうが管理しやすいかなと思いました。パフォーマンスもこっちのほうがいいのでは? # 以下、DBに反映させたいデータ q1 = [] q2 = [] q3 = [] def th1(): while True: # q1キューにデータを入れる処理 q1.append('any') def th2(): while True: # q2キューにデータを入れる処理 q2.append('any') def th3(): while True: # q3キューにデータを入れる処理 q3.append('any') def any_transaction(): global q1,q2,q3 while True: time.sleep(0.001) # キューにデータがあれば順次する if q1: # q1をdbに入れる処理 pass if q1: # q1をdbに入れる処理 pass if q1: # q1をdbに入れる処理 pass
- 投稿日:2022-02-23T14:18:41+09:00
Blenderで移動できる窓を作る
Blenderで移動できる窓を作る Blenderの標準アドオンのArchimeshで窓を作成すると、壁に沿って移動できます。 手動で行う場合は、壁のオブジェクトにブーリアンモディファイアーを使って実現できます。 穴用のオブジェクトをブーリアンの差分に指定することで、壁に穴を開けられます。 ここでは、Pythonを使った「移動できる窓の作成方法」を紹介します。 パネルの表示 パネルを表示するために、scriptingワークスペースで新規を押し、下記をコピペしてスクリプト実行をしてください。なお、このコードをファイルに保存すれば、アドオンとしてインストール可能です。 """ Window frame setting - Make a hole in the wall. - Make a hole in the frame. - Make the hole the parent of the frame. - Make the glass the parent of the frame. - Hide the hole. - Set materials. """ import bpy bl_info = { "name": "Window frame setting", # プラグイン名 "author": "tsutomu", # 制作者名 "version": (1, 0), # バージョン "blender": (3, 0, 0), # 動作可能なBlenderバージョン "support": "TESTING", # サポートレベル "category": "3D View", # カテゴリ名 "description": "Window frame setting", # 説明文 "location": "", # 機能の位置付け "warning": "", # 注意点やバグ情報 "doc_url": "", # ドキュメントURL } class CWS_OT_make_sample(bpy.types.Operator): bl_idname = "object.make_sample_operator" bl_label = "Make sample" def execute(self, context): dc = { "frame": {"scale": (0.6, 0.1, 0.6)}, "glass": {"scale": (0.5, 0.02, 0.5)}, "wall": {"scale": (1, 0.05, 1)}, "hole": {"scale": (0.5, 0.2, 0.5)}, } for name, prop in dc.items(): bpy.ops.mesh.primitive_cube_add(**prop) bpy.context.selected_objects[0].name = name return {"FINISHED"} class CWS_OT_set_frame(bpy.types.Operator): """What to do when the 'Set to frame' button is pressed""" bl_idname = "object.set_frame_operator" bl_label = "Set to frame" frame: bpy.props.StringProperty(name="frame") # type: ignore glass: bpy.props.StringProperty(name="glass") # type: ignore wall: bpy.props.StringProperty(name="wall") # type: ignore hole: bpy.props.StringProperty(name="hole") # type: ignore doset: bpy.props.BoolProperty(name="doset") # type: ignore def execute(self, context): try: frame = bpy.data.objects[self.frame] glass = bpy.data.objects[self.glass] wall = bpy.data.objects[self.wall] hole = bpy.data.objects[self.hole] except KeyError as e: self.report({"ERROR"}, f"Not found {e.args[0].split()[2]}") return {"CANCELLED"} for obj in [wall, frame]: if "Boolean" not in obj.modifiers: bpy.context.view_layer.objects.active = obj bpy.ops.object.modifier_add(type="BOOLEAN") obj.modifiers[-1].object = hole if not glass.parent and not hole.parent: bpy.ops.object.select_all(action="DESELECT") glass.select_set(state=True) hole.select_set(state=True) bpy.context.view_layer.objects.active = frame bpy.ops.object.parent_set() hole.hide_set(state=True) hole.hide_render = True matprop = { wall: {"Base Color": (0.5, 0.5, 0.5, 1)}, frame: {}, glass: {"Roughness": 0, "Transmission": 1}, } for obj, prop in matprop.items(): if not obj.material_slots: obj.active_material = bpy.data.materials.new(name=obj.name) obj.active_material.use_nodes = True node = obj.active_material.node_tree.nodes.get("Principled BSDF") if self.doset and node: for key, value in prop.items(): node.inputs[key].default_value = value return {"FINISHED"} class CWS_PT_set_frame(bpy.types.Panel): bl_label = "Window frame" bl_idname = "CWS_PT_set_frame" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Edit" def draw(self, context): self.layout.operator(CWS_OT_make_sample.bl_idname, text=CWS_OT_make_sample.bl_label) for name in ["frame", "glass", "wall", "hole"]: self.layout.prop(context.scene, name, text=name.capitalize() + " ") self.layout.prop(context.scene, "doset", text="Set material properties") prop = self.layout.operator(CWS_OT_set_frame.bl_idname, text=CWS_OT_set_frame.bl_label) prop.frame = context.scene.frame prop.glass = context.scene.glass prop.wall = context.scene.wall prop.hole = context.scene.hole prop.doset = context.scene.doset regist_classes = ( CWS_OT_make_sample, CWS_OT_set_frame, CWS_PT_set_frame, ) def register(): for regist_class in regist_classes: bpy.utils.register_class(regist_class) bpy.types.Scene.frame = bpy.props.StringProperty(default="frame") bpy.types.Scene.glass = bpy.props.StringProperty(default="glass") bpy.types.Scene.wall = bpy.props.StringProperty(default="wall") bpy.types.Scene.hole = bpy.props.StringProperty(default="hole") bpy.types.Scene.doset = bpy.props.BoolProperty(default=True) def unregister(): for regist_class in regist_classes: bpy.utils.unregister_class(regist_class) del bpy.types.Scene.frame del bpy.types.Scene.glass del bpy.types.Scene.wall del bpy.types.Scene.hole del bpy.types.Scene.doset if __name__ == "__main__": register() 試してみる layoutワークスペースに戻り、Nでサイドバーを出し、編集タブを表示してください。下記のように、パネルが表示されています。 実行するには、下記の4つのオブジェクトが必要ですが、Make sampleボタンを押すと自動で作成できます。自分でオブジェクトを用意している場合は、Make sampleを押す必要はありません。 窓枠(frame) ガラス(glass) 壁(wall) 穴(hole) 4つのオブジェクトを用意したら、それらのオブジェクトの名前をパネルに入力してください。Make sampleで用意した場合は、すでに入力済みです。 Set to frameボタンを押すと、移動できる窓の設定が完了です。 このボタンでは、下記の処理をします。 壁に穴を開けます。 窓枠に穴を開けます。 窓枠を、穴とガラスの親にします。これにより、窓枠を移動すると穴とガラスも一緒に移動します。 穴を非表示にします。 マテリアルを設定します。(Set material propertiesチェック時) 適当に背景を設定すると、下記のようになります。 窓枠を動かすと、穴も動きます。 以上
- 投稿日:2022-02-23T13:58:58+09:00
大学の研究を思い返して、Pythonで非線形系を解析(Duffing-vandel Pol方程式)
まえがき 25年以上前になるが、情報処理系の大学に通っていた。大学の研究室では、非線形系の解析をコンピューターを使って行っていた。当時は、FORTRANでホストコンピューターにバッチ処理を投入して、タイムシェアリング処理で計算を行ってた。あの頃は、指導されるままに行なっていたが、最近になって、内容を思い返してみて、面白い事を、やらせてもらってたのではないかと興味が湧いてきた。コンピュータ技術も発達したので、手持ちのPCでも、あの頃の研究が行えるのではないかと思い立ち、Pythonで非線形系の解析を行なってみることにした。 非線形系 Duffing-vander Pol方程式 非線形系としてDuffing-vandel Polの方程式で知られる下記の式で表される系について解析する。これは、大学時代に研究していたの時の同じ式になる。 {{d^2x}\over{dt^2}}-μ(1-γx^2){{dx}\over{dt}}+{C_1}x+x^3=Bcosνt これを、分解して(x, y)の系に置き換える。 y={{dx}\over{dt}} {{dy}\over{dt}}=μ(1-γx^2)y-{C_1}x-x^3+Bcosνt 更に、定数が多いので、μ=0.2, ν=1, C1=-1.0と固定した系について解析する。 非線形系の解析 上記の微分方程式をPythonを使って解く。ソースは下記になった。 import numpy as np import matplotlib.pyplot as plt # coding: UTF-8 from scipy.integrate import odeint, simps def duffing(var, t, B, nu): """ var = [x, p] dx/dt = p dp/dt = mu(1-gamma*x**2)*p -C1*x -x**3 + B*cos(nu*t) """ mu=0.2 gamma=1 C1=-1.0 x_dot = var[1] p_dot = mu*(1-gamma*var[0]**2)*var[1]-C1*var[0]-var[0]**3+B*np.cos(nu*t) return np.array([x_dot, p_dot]) def cfsev(var,B,nu): t = np.arange(0,5000,2*np.pi/nu) var = odeint(duffing, var, t, args=(B,nu)) return var.T[0][100:], var.T[1][100:] def plot_point(var,B,nu): x,p=cfsev(var,B,nu) plt.plot(x, p, ".", markersize=4) plt.show() plot_point([1, 1],1,4) 最後の行のplot_point( [初期値], B, ν)を与えて、計算する。 一番オーソドックスなのは、安定点に落ち着くケースである。例では2点がプロットされており、νの2倍周期で安定する事を示している。 2. この系ではリミットサイクルが観測できます。非線形系に特異な現象で、サイクル上で安定している状態である。 3. カオス状態も存在する。カオスは、次の動作が予想できないにも関わらず、全体としては何らかの規則性を持っている特徴がある。軌道はグラフのようにカオス特有の綺麗な図形で観測できる。 このように非線形系の特徴を示す現象が観測できる興味深い系になっている。 分岐現象の観測 闇雲に変数を指定して、系の現象を確認していくのは、非効率である。そこで変数の一定範囲で変化させて現象の分布について調べてみる。加えて、系の振る舞いが変化する推移についても観測する。ソースは下記になる。 import numpy as np import matplotlib.pyplot as plt # coding: UTF-8 from scipy.integrate import odeint, simps def duffing(var, t, B, nu): """ var = [x, p] dx/dt = p dp/dt = mu(1-gamma*x**2)*p -C1*x -x**3 + B*cos(nu*t) """ mu=0.2 gamma=1 C1=-1.0 x_dot = var[1] p_dot = mu*(1-gamma*var[0]**2)*var[1]-C1*var[0]-var[0]**3+B*np.cos(nu*t) return np.array([x_dot, p_dot]) def cfsev(var,B,nu): t = np.arange(0,5000,2*np.pi/nu) var = odeint(duffing, var, t, args=(B,nu)) return var.T[0][100:], var.T[1][100:] def orb_diagm(B,nu_min,nu_max): var=[0,1] lx=list() lnu=list() for nu in np.arange(nu_max,nu_min,-1*(nu_max-nu_min)/500): x,p=cfsev(var,B,nu) if len(x)>300: lx.extend(x[-300:]) lnu.extend([nu]*300) var=[x[-1],p[-1]] print(nu); plt.plot(lnu, lx, ".", markersize=4) plt.show() orb_diagm(1.0,0.3,1) 関数duffing(var, t, B, nu)とcfsev(var,B,nu)は、前回と同じである。orb_diagm(B,nu_min,nu_max)を追加している。これは、Bを固定してνをnu_minとnu_maxの間で変化させた場合の状態の推移をプロットする。最後の行のorb_diagm(1.0,0.3,1)で、Bとνを指定して実行する。 ここで、ν=2.25はカオス状態に当たる。νを2.5→2.25で見ていくと、周期4で安定していた状態から2倍に分かれて周期8になり、その後さらに分岐を繰り返してカオス状態に推移しているように見受けられる。 まとめ Pythonを使ってDuffing-vander Pol方程式の非線形系の解析を行なってみた。安定点、リミットサイクル、カオス現象など非線形系に特有な現象が観測できた。 あとがき 大学時代は、FORTRANで大型コンピュータを使って解析していたのが、20年が経って手持ちのPCで解析できるようになった。FORTRANで何千行も書いていたコードが、Pythonのライブラリを利用する事で、20行程度で、しかも手持ちのPCで行えるようになっている。 指導を頂いていた教授からは、数年前から年賀状が来なくなった。もう80才を過ぎている御歳しになる。元気にしておられればいいが。先生、凄い世の中になりました。
- 投稿日:2022-02-23T13:29:33+09:00
PynamoDB + FastAPIを用いたAPI開発方法 メモ
PynamoDBとFastAPIを用いて簡単なCRUD APIを作成したのでメモとして残しておく。 PynamoDB DynamoDB用Pythonインターフェイス DynamoDB APIを抽象化しており、比較的簡単にDynamoDB操作ができる。 事前準備 こちらの手順で動作確認用のDynamoDBコンテナを起動する。 テーブル名などはtest_userとして修正する。 依存ライブラリインストール pynamodb fastapi uvicorn コード app.py from pynamodb.models import Model from pynamodb.attributes import UnicodeAttribute,NumberAttribute from fastapi import FastAPI, HTTPException from fastapi.responses import JSONResponse from fastapi.responses import PlainTextResponse from starlette.exceptions import HTTPException as StarletteHTTPException from pydantic import BaseModel from typing import Optional import uuid import logging # PynamoDB用データモデル class UserModel(Model): # 接続先情報(クレデンシャルやテーブル情報)を定義 class Meta: host = "http://localhost:8000" aws_access_key_id = 'dummy' aws_secret_access_key = 'dummy' region = 'ap-northeast-1' table_name = "test_user" # テーブル属性定義 id = UnicodeAttribute(hash_key=True) email = UnicodeAttribute(null=True) # FastAPIリクエストボディ用データモデル class User(BaseModel): id: Optional[str] = None email: str app = FastAPI() # ユーザー登録API @app.post("/users") def regist_user(user: User): # ユーザーID生成 user_id = str(uuid.uuid4()).replace("-","") regist_user = UserModel(user_id) regist_user.email = user.email # ユーザー登録 regist_user.save() user.id = user_id return user # ユーザー取得API @app.get("/users/{user_id}") def get_user(user_id: str): try: # ユーザー取得 user = UserModel.get(user_id) logging.info(user) return user.attribute_values except UserModel.DoesNotExist: logging.error("User does not exist.") raise HTTPException(status_code=404, detail="user_not_found") # ユーザー更新API @app.put("/users/{user_id}") def update_user(user_id:str, user: User): try: # 更新対象ユーザー取得 target_user = UserModel.get(user_id) # ユーザー更新 target_user.update(actions=[ UserModel.email.set(user.email) ]) return target_user.attribute_values except UserModel.DoesNotExist: logging.error("User does not exist.") raise HTTPException(status_code=404, detail="user_not_found") # ユーザー削除API @app.delete("/users/{user_id}") def update_user(user_id:str): try: # 削除対象ユーザー取得 target_user = UserModel.get(user_id) # ユーザー削除 target_user.delete() return {} except UserModel.DoesNotExist: logging.error("User does not exist.") raise HTTPException(status_code=404, detail="user_not_found") 動作確認 API起動 uvicorn app:app --reload --port 5000 ユーザー登録API リクエスト POST /users HTTP/1.1 Host: 127.0.0.1:5000 Content-Type: application/json Content-Length: 37 { "email":"test0@example.com" } レスポンス { "id": "87527962cbd14293943d323548f403da", "email": "test0@example.com" } ユーザー取得API リクエスト GET /users/87527962cbd14293943d323548f403da HTTP/1.1 Host: 127.0.0.1:5000 レスポンス { "email": "test0@example.com", "id": "87527962cbd14293943d323548f403da" } ユーザー更新API リクエスト PUT /users/87527962cbd14293943d323548f403da HTTP/1.1 Host: 127.0.0.1:5000 Content-Type: application/json Content-Length: 44 { "email":"test0_update@example.com" } レスポンス { "email": "test0_update@example.com", "id": "87527962cbd14293943d323548f403da" } ユーザー削除API リクエスト DELETE /users/87527962cbd14293943d323548f403da HTTP/1.1 Host: 127.0.0.1:5000 レスポンス {} 参考情報 PynamoDB
- 投稿日:2022-02-23T12:52:05+09:00
DjangoではじめてのWebアプリ ~0.環境構築~
自己紹介 大学で情報や数学を学んでいる学部一年の雨傘といいます。 これでもかというほど暇を持て余している春休み、何か力をつけてみたいと思いWebアプリを作ってみることにしました。 せっかくなので記事に残しておけば何かの役に立つかな(漠然)といった気持ちで、のんびり書いていこうと思います。 私のプログラミングの知識に関して簡単に。 大学の授業ではpythonを使用しています。基本的な文法は習得済み(のはず)。 中学生時代、HTMLとCSSを使って簡単なホームページ作りは勉強しました。しかしページ自体は完成したものの公開に至らず。今回はきちんと公開までできたらいいなあと思います。 趣味程度に、C++を中心として競プロの過去問を解きながらアルゴリズムの勉強をしています。 この程度なので、初心者の目線から記事を書くことが出来るかなーと思っています。 私と同じように、初めてWebアプリをつくろうと思い立った人の参考になれば幸いです。 (私は長い説明書きを読むのが苦手なので、本記事も画像やコード部分をつまみながら読む程度で理解できるような記事を目指しています。そのため、もしも重要な説明が欠けているなど、問題があれば教えていただきたいです) 今回の内容 本記事では、PythonおよびDjangoのインストールをした後、プロジェクトの作成、Webサーバの動作確認までを解説します。 実際の制作に入るのは次回からとなります。 環境構築 今回使用する環境は以下の通りです。 - Windows 10 Home - Python 3.10.2 - Django 3.2.10 - Visual Studio Code 1.64.2 Pythonのインストール Microsoft Storeからお手軽にインストールすることが出来ます。 検索窓に「python」と入力し、今回使用する「Python 3.10」(画像内赤枠)をインスト―ルしました。 コマンドプロンプトにてpython -Vを実行。(すでにコマンドプロンプトを起動していた場合はいったん閉じてから改めて起動しましょう) cmd C:\Users\[ユーザ名]>python -V Python 3.10.2 無事にインストールされていることが確認できました。 仮想環境のセットアップ 今回は仮想環境(Virtual Environment)というものをつくり、その中で制作を行っていきます。 仮想環境についてはイマイチ分かっていませんが、PythonやDjangoの設定をプロジェクトごとに隔離することが出来るので管理がしやすくなるなど、仮想環境の中で作業をした方が何かと便利なようです。 cmd C:\Users\[ユーザ名]>D: D:\>mkdir myapp D:\>cd myapp D:\myapp>python3 -m venv myvenv D:\myapp>myvenv\Scripts\activate それぞれの行では以下のことをやっています。 Dドライブ直下へ移動(今回は個人的にDドライブ内で作業がしたいため) Dドライブ直下にmyappというディレクトリ(フォルダ)を作成 myappへ移動 仮想環境の作成(myapp内にmyvenvというディレクトリが作られます) 仮想環境の起動 その後、コマンドプロンプトがこんな感じになれば成功です。 cmd (myvenv) D:\myapp> Djangoのインストール 無事に仮想環境を起動することが出来たので、Djangoをインストールしてきます。 その前に、インストールに使うpipというソフトウェアを更新しておきます コマンドプロンプトで、 cmd (myvenv) D:\myapp>python -m pip install --upgrade pip ~~ いろいろ難しそうな文 ~~ Successfully installed pip-[バージョン名] のようにすれば更新が行われます。 すでに最新のpipがインストールされていれば「Requirement already satisfied: pip in d:\myapp\myvenv\lib\site-packages ([バージョン名])」と出ます。(仮想環境をつくってすぐの作業なのでこうはならないかも?) いよいよDjangoのインストールに進みます。 まず、pip installでインストールものをまとめたrequirements.txtを作成します。 そこに以下のように記述します。 requirements.txt Django~=3.2.10 そして、コマンドプロンプトにてpip install -r requirements.txtを実行してDjangoのインストールをします。 cmd (myvenv) D:\myapp>pip install -r requirements.txt ~~ いろいろ難しそうな文 ~~ Successfully installed Django-3.2.12 asgiref-3.5.0 pytz-2021.3 sqlparse-0.4.2 無事にDjangoのインストールも完了しました。 これで環境構築は終了となります! プロジェクトの作成 環境構築が終われば、後やるべきことはプロジェクトの作成のみです。もう一息頑張りましょう。 コマンドプロンプトでdjango-admin.exe startproject mysite .を実行します。 cmd (myvenv) D:\myapp>django-admin.exe startproject mysite . 最後のピリオド.までしっかり入力しましょう。 (ディレクトリの作成場所の指定を意味します。.は現在のディレクトリ、すなわちmyappの省略) この操作により、myapp内にmysiteという名前のディレクトリおよびその中身が作られます。 これでプロジェクトの作成自体は出来ました。ここから設定を少しいじっていきます。 myapp\mysite\settings.pyをテキストエディタ等で開いておきましょう。 言語、タイムゾーンの設定 settings.pyの106行目あたりで、下のようなコードを探してください。 settings.py(一部) LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' この部分で言語とタイムゾーンの設定をすることが出来ます。 日本語、日本のタイムゾーンにしたいので、それぞれにjaとAsia/Tokyoを指定します。 settings.py(一部)(変更後) LANGUAGE_CODE = 'ja' TIME_ZONE = 'Asia/Tokyo' 静的ファイルのパスの追加 「静的ファイル」とは、HTMLやCSS、JavaScript、画像ファイルなどのことを指すようです。 制作を続けていくうちに理解できると思うので、とりあえず今は手順通りに進めてみます。 settings.pyの120行目あたりで、下のようなコードを探してください settings.py(一部) STATIC_URL = '/static/' この下に、STATIC_ROOT = os.path.join(BASE_DIR, 'static')と追記します。 settings.py(一部)(変更後) STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'static') ここで追加したコードのos.path.joinについて、これはosというライブラリの中の機能です。 これを使うためにimportを行っておきましょう。 settings.pyの13行目あたりで、下のようなコードを探してください。 settings.py(一部) from pathlib import Path ここにimport osを追記します。 settings.py(一部)(変更後) from pathlib import Path import os データベースのセットアップ 作成したWebアプリのデータはデータベースに保管されます。 Djangoではデフォルトでsqlite3というデータベースを使うことになっているようです。 データベースの作成を行います。 再びコマンドプロンプトに戻って、python manage.py migrateを実行します。 cmd (myvenv) D:\myapp>python manage.py migrate ~~ いろいろ難しそうな文 ~~ 特にエラーなどを吐かなければこれでデータベースが作成されました。 上手く行かなければ、正しいディレクトリで実行しているか等確認してみましょう。 (仮想環境は起動されていますか?manage.pyが含まれるディレクトリで操作を行っていますか?) 以上ですべての環境構築が完了しました! Webサーバの動作確認 最後に、Webサーバが正しく動作するかを確認します。 コマンドプロンプトで、python manage.py runserverを実行します。 cmd (myvenv) D:\myapp>python manage.py runserver Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). February 23, 2022 - 12:27:45 Django version 3.2.12, using settings 'mysite.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CTRL-BREAK. 起動されたWebサーバ(Webページ?)には下から二行目にあるhttp://127.0.0.1:8000/ からアクセスすることが出来ます。 環境によっては違うかもしれないので、ご自身のコマンドプロンプトからコピペしてアクセスすることをお勧めします。 サーバを停止させるときにはCTRL + Cで停止させることが出来ます。 ここまででエラーが起こらなければ無事にプロジェクトの作成までが完了となります。お疲れ様でした! 次回から制作に取り掛かっていきます。 参考文献 Django Girls (https://tutorial.djangogirls.org/ja/) おわりに ここまで読んでいただきありがとうございました。 はじめてのWebアプリ制作、はじめての記事執筆ですが温かい目で見守っていただけると幸いです。 次の記事:(制作中・・・)
- 投稿日:2022-02-23T12:20:44+09:00
【pyenv+poetry】Pythonで簡単に仮想環境を作成しよう
はじめに 今まで私はAnaconda/minicondaで仮想環境を作成してきましたが、企業利用においてはどちらも有償化してしまいました。 そこで今回は今モダンだと思われるpyenv+poetryでの環境構築を書いていきます。 pyenv+poetryの解説記事はまぁまぁ多いと思ったが、ぱっと見どういうこと??とよくわからない記事が多いと個人的には感じたので、初心者でもわかるように途中図示交えながら解説を行う。 ※本記事は全面わかりやすくリニューアルして再投稿しました 動作環境 ・ OS : Windows10 pro ・pyenv 2.2.4 ・poetry 1.1.13 1.導入 1-1.pyenv-winの導入 pyenv自体はwindowsに対応してないため、pyenv-winの導入が必要になる。 ※pyenvとは、pythonのバージョンを簡単に切り替えるツールのこと 公式ではpipでのインストールも書かれているが、後でややこしくなる(うまくバージョンが切り替えられない等)あるので以下ではpip以外の2方法をお勧めしておく。 ※以下1-1-1か1-1-2のどっちかの方法で準備してください 1-1-1.gitコマンド git使えるならこれがオススメです。 PowerShell git clone https://github.com/pyenv-win/pyenv-win.git "$HOME/.pyenv" 1-1-2.Zipでダウンロードしディレクトリ配置 https://github.com/pyenv-win/pyenv-win/archive/master.zip よりZIPでダウンロードし、解凍後中身(pyenv-win-masterの中身)を.pyenvディレクトリを$HOME直下に作成し配置する。 そして図のように環境変数を設定する ※ほかのpythonより上に配置することが重要 $HOMEのpath調べ方は PowerShell(コマンドプロンプト) echo %HOMEPATH% 1-2.poetryの導入 poetryとは、pythonのパッケージ管理ツールのことで仮想環境とかも作成可能 powershellを開き、以下コマンドを実行する PowerShell (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python - すると、.poetryディレクトリが$HOME直下に出力されていることがわかる。 これで準備は完了。 2.プロジェクト(仮想環境)の設定とjupyterへの適用 2-3にイメージが湧くように全体像を記載した。 2-0~2-2まででやってることはそこで理解できると思うので、まずはこの通りやってみてほしい。 2-0.pyenv管理フォルダを作成しておく 適当なフォルダを作成し、そこにcdコマンドで移動しておく。 以下でプロジェクトを作成していくが、そのプロジェクトで使用するpythonのバージョンを適応させる範囲を限定させる為のフォルダ コマンドプロンプト mkdir *** ・・・(1) cd *** ・・・(2) (1):pyenvのpythonバージョン管理用ヂィレクトリの作成(***は適当な名前) (2):作成したディレクトリへ移動しておく 2-1.pyenvで使用するpythonを設定する pyenvのコマンドと説明はざっくり以下に記載しておく。 コマンド 内容 pyenv install --list pyenvでインストール可能なpythonのバージョンを確認 pyenv versions pyenvでインストール済のpythonバージョン一覧を表示 pyenv install *** pyenvで未インストールなpythonバージョンをインストール(***にバージョン指定) pyenv rehash shimファイル(.pyenv\pyenv-win\shims)の中身を更新して再構築 pyenv local *** 直下で使用するpyenvのpythonバージョンを設定しておく(***にバージョン指定) pyenv global *** global環境で使用されるpyenvのpythonバージョンを設定(***にバージョン指定) pyenv which python 今のディレクトリで操作するpyenvのpythonバージョンを確認 以下では実際に新規で作成していく流れだけ記載する ※2-0に引き続き以下を順番にコマンドを打ち込んでいく コマンドプロンプト pyenv install --list ・・・(1) pyenv versions ・・・(2) ※必要に応じて実行 pyenv install ××× ・・・(3) ※必要に応じて実行 pyenv rehash ・・・(4) ※必要に応じて実行 pyenv local 3.9.6 ・・・(5) pyenv which python ・・・(6) Python –V ・・・(7) (1):現状インストール済のpythonバージョン一覧を表示 (2):インストール可能なpythonのバージョンを確認 (3):(1)で使用したいpythonバージョンが無ければ(2)を参考に新規インストールする (4):shimファイルの再構築 (5):2-0で作成したディレクトリ以下の階層で適応するpythonバージョンを(1)から指定(今回は3.9.6とした) (6):(5)で指定したpythonバージョンを認識してるか確認 (7):pyenvコマンドでなくて普通にpythonコマンドでもバージョンを(5)と同じバージョンかを確認しておく ★以下つまづきポイントです もし(6)や(7)で指定したバージョンになってなければ、以下コマンドで環境変数を確認し、 .pyenv\pyenv-win\shimsがほかのPythonより上に来ているか?を確認する。 もし下図のようにpyenvのpathが上になければ、コントロールパネルの環境変数をいじってpyenv-win\bin、pyenv-win\shimsが一番上にくるように移動させる※prthは上にある方が優先される為 PowerShell $ENV:Path.Split(“;”) 2-2.poetryでプロジェクト(仮想環境)を作成 poetryのコマンドと説明はざっくり以下に記載しておく。 コマンド 内容 poetry config --list Poetryの設定を確認 poetry config --local virtualenvs.in-project true プロジェクト単位で仮想環境を設定する(おすすめ設定) poetry new ××× プロジェクト(ディレクトリ群)を作成(×××にプロジェクト名を入れる) poetry init pyproject.tomlを対話的に作成する poetry install 仮想環境の立ち上げ ※pyproject.tomlのあるディレクトリで要実行 poetry env info 現在作動している仮想環境の基本情報を確認 poetry run python -–version poetry内で動作するpythonのバージョンを確認(pyenvと同じはず) poetry add 〇〇〇 パッケージをインストール poetry remove 〇〇〇 パッケージのアンインストール poetry show インストール済パッケージ一覧を確認 poetry run python ***.py 仮想環境内でプログラムを実行※コマンド単位で仮想環境を適応 poetry shell 仮想環境に入る※↑とは違い環境に入り込む poetry env list アクティブな仮想環境の詳細を確認 exit poetry shellで入り込んだ仮想環境から抜ける 以下では実際に新規で作成する流れを記載する。 今回はテスト環境等が不要なので、あえてnewコマンドを使用しない。 ※newコマンドでプロジェクトを作成すると、余計なテスト用ライブラリが勝手にインストールされる為 ※newを使用しない例が具体的に書かれているサイトが無かったため コマンドプロンプト poetry config --list ・・・(0) poetry config --local virtualenvs.in-project true ・・・(1) ※必要に応じて実行 mkdir sample_project ・・・(2) cd sample_project ・・・(3) poetry init ・・・(4) poetry install ・・・(5) poetry env info ・・・(6) ※必要に応じて実行 poetry env list ・・・(7) ※必要に応じて実行 poetry show ・・・(8) ※必要に応じて実行 poetry add 〇〇〇 ・・・(9) poetry show ・・・(10) ※必要に応じて実行 poetry shell ・・・(11) ***(仮想環境内でなにかしら作業)*** exit ・・・(12) (0):Poetryの設定を確認し、virtualenvs.in-project = trueでなければ(1)を実行 (1):プロジェクト直下で仮想環境を管理する設定に変更 (2):2-0で作成したpyenv用フォルダの直下にsample_projectディレクトリを作成 (3):sample_project階層へ移動 (4):pyproject.tomlファイル(プロジェクト管理)を新規作成 ※下記補足あり (5):pyproject.tomlをベースに仮想環境を作成 ※.venvフォルダが作成される (6):作成した仮想環境の基本情報を確認 (7):アクティブな仮想環境を確認 (8):仮想環境にインストールされたパッケージを確認(この時点では空のはず) (9):パッケージをインストール ※pip同様=***でバージョン指定可能 (10):インストール済パッケージ一覧を確認 (11):仮想環境に入る (12):(11)の仮想環境から抜ける ※(4)の補足:pyproject.tomlファイルの作成では基本的にエンターキー連打でOK。 パッケージ名称(プロジェクト名称)は(2)でmkdirで作成した名称と同じになっている。 2-3.今までの説明を図示すると・・ さて、今までpyenvやらpoetryやら色々なコマンドをポチポチしてきたわけだが、よくわからん。。。という人の為に今までの作業を図示する。これでpyenvとpoetryの関係や仮想環境のイメージが湧くと思う。 ※フォルダ名称は適当。あくまでわかりやすいように命名しただけです。 2-0で作成したのが図で言うpython3.9.6フォルダ 2-1で設定したのは図で言う.python-version 2-2で設定したのはsample_project以下の作成+設定 ※poetry.lockはpoetry addすると自動作成される 2-4.jupyterをインストールして、仮想環境を適応(選択)できるようにする ここからは例としてさらに実際に作成した仮想環境をjupyterで実行できるように準備してみる。 コマンドプロンプト poetry add ipykernel ・・・(1) poetry shell ・・・(2) ipython kernel install --user --name=〇〇〇 ・・・(3) jupyter notebook ・・・(4) (1):ipykernelを仮想環境に導入(add) (2):仮想環境に侵入 (3):jupyterのカーネルから作成した仮想環境を選択可能にする ※〇〇〇は2-3の(2)で作成したプロジェクト名。 (4):jupyterを起動 すると、以下のように仮想環境が選べるようになり、poetryコマンドも中で使用できる。 3.さいごに いかがだっただろうか? anacondaを使うのもいいが、最近の流行はpyenv+poetryだと思ったので記事として書いてみた。 まずは気軽に試してみて個人で使いやすい方を選んでいけばいいと思います。 それでは今回はここまで。よい仮想環境ライフをお過ごしください。 参考リンク集
- 投稿日:2022-02-23T11:42:45+09:00
Pythonで複数入力(無限入力)を実行する方法
test.py for l in sys.stdin: lines.append(l.rstrip('\r\n')) 上記のようなコードでは、 python test.py のように実行しても無限に入力を受け付けてしまい、最後まで実行できませんでした。 解決策は下記の通りです。 echo "test1.txt" "text2.txt" | python test.py 参考:https://stackoverflow.com/questions/43554363/how-to-run-code-with-sys-stdin-as-input-on-multiple-text-files
- 投稿日:2022-02-23T11:41:03+09:00
PythonでDependency Injectionを実装する
備忘録としてDependency Injectionの基礎知識とPythonでの実装方法を整理します。 環境 Python 3.8.10 DIとは? Dependency Injection の略です。具体的に言うと、クラスAで利用するインスタンスをクラスAの内部で生成するのではなく、抽象に依存させて外部から渡すことです。 サンプルコードを見てみましょう。 悪い例 main.py class Cat(): def __init__(self, name: str): self.name = name def cry(self): print(f'{self.name} cry meow meow.') class A(): cat1 = Cat("ハチワレ") def action(self): self.cat1.cry() if __name__ == "__main__": a = A() a.action() Aクラスの内部でCatクラスインスタンスを生成し、cryメソッドを呼び出しています。 動作上は問題ありませんが、この実装だと猫の名前を変えるたびにAクラスを変更しなければなりません。名前が変わったというCatクラスの事情がAクラスに影響しています。 CatクラスとAクラスが密結合になっており、保守性が低い状態です。 そこで、以下ように変更します。 Aクラスインスタンスに外部からCatクラスインスタンスを渡す AクラスではCatクラス(具象)ではなく、Animalクラス(抽象)に依存する。 変更後のコードがこちらです。 良い例 main.py from abc import ABCMeta, abstractmethod #抽象クラス class Animal(metaclass=ABCMeta): @abstractmethod def cry(self): pass # 抽象クラスを実装した具象クラス class Cat(Animal): def __init__(self, name: str): self.name = name def cry(self): print(f'{self.name} cry meow meow.') class A(): # 抽象クラスに依存する def __init__(self, animal: Animal) -> None: self.animal: Animal = animal def action(self): self.animal.cry() if __name__ == "__main__": # 抽象型としてCatインスタンスを生成する cat: Animal = Cat("ハチワレ") a = A(cat) a.action() Catクラスの変更がAクラスに影響しにくい設計になりました。 (Catクラスのプロパティが変わってもAクラスはそれを知らなくても問題ない) DIのメリット ① クラス同士を疎結合に保ちやすい。 サンプルコードで確認した通り、DIを使うと依存先クラス(サンプルの場合はCatクラス)の変更が依存元クラスに影響しにくくなります。変更時の影響が小さくなるのでプログラムを修正しやすくなるのが嬉しいです。 ② 単体テストをしやすい 「DIのメリットはテストがしやすくなる」とよく言われますがこれには2つの意味があると思われます。1つはクラス同士が疎結合になることでテスト観点が明確になるという意味です。もう1つは依存対象のオブジェクトを外部から渡すことができるため、テスト用のモックを利用できるという意味です。 テスタビリティ向上はDIを採用する目的として大きいでしょう。 DIのデメリット 依存対象が増えると辛い サンプルコードではAクラスがAnimalクラスにのみ依存しているため気になりませんが、Aクラスが複数のクラスに依存する場合面倒なことになります。 依存先クラスのインスタンスを全て生成→ Aクラスのインスタンスを生成する時に引数に渡す という手順が必要になります。 実装コストが増える他、コードの可読性が下がります。 この問題を解決するためにDIコンテナやサービスロケーターという実装パターンが採用されることが多いです。DIコンテナについては別の記事で扱おうと思います。
- 投稿日:2022-02-23T11:36:23+09:00
pyenvとvenvを使って簡単にプロジェクト専用の環境構築をする
Pythonのパッケージを使いたいが、自分が使っているPythonのバージョンと互換性がなかったり、手持ちのパッケージの新しい/古いバージョンに依存していたりと色々と面倒な思いをすることは往々にしてある。そこで Pythonのバージョンを自由に切り替えられるようにpyenvを Pythonパッケージのバージョンを自由に切り替えられるようにvenvを 使う。 pyenvのインストール まずインストールする。Macならhomebrewで入れれば良い。 brew install pyenv MacでなくLinuxとかなら git clone https://github.com/yyuu/pyenv.git ~/.pyenv で入れる。 export PYENV_ROOT="$HOME/.pyenv" export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init --path)" eval "$(pyenv init -)" を実行しておいてから、 pyenv install --list を実行してPythonの利用可能なバージョン一覧が出力されたら成功。 pyenvで任意のバージョンのPythonをインストール すでにマシンに入っているPythonのバージョンとは違うバージョンでPythonを使いたいと思ったら、上記コマンドの出力結果の中からバージョンを選んで pyenv install [バージョン] を実行すれば今後それを使うことができるようになる。 pyenv versions の結果に指定したバージョンが入っていれば成功。 ちなみにインストールしたバージョンを消すには~/.pyenv/versions/の中から当該バージョンをrmすればOK。 プロジェクト用のPythonバージョンに切り替える ここからはプロジェクト毎に行う。 すでにマシンに入っているPythonのバージョンとは違うバージョンでPythonを使いたいときは、プロジェクトの作業ディレクトリに入ってから pyenv local [バージョン] でバージョンを切り替える。 python3 --version の結果が指定通りのバージョンになっていたら成功。 venvでパッケージを切り替える 作業ディレクトリにて、pyenvでバージョンを切り替えた状態で以下を実行すると.venvディレクトリができる。これが仮想環境のディレクトリである。 python3 -m venv .venv あとは仮想環境をアクティベートすればそのプロジェクト用に好き放題pip3 installして良くなる。 source .venv/bin/activate ちなみに仮想環境から抜ける時は deactivate すればよく、仮想環境自体を消したければ.venvディレクトリをrmすればOK。 ところで、pip installがうまくいかないことがある。その場合は、pipのバージョンが古かったり、Pythonのバージョンとして正しいものを使っていなかったりというのが原因であることが多い。それらのバージョン更新というのは自分でやるとして、何かバージョンを指定してインストールしようとしてうまく行かなかったときはURLを指定してインストールする方法がある。(参考) 今後簡単にセットアップできるようにする 以下のようなスクリプトを作業ディレクトリに入れておく。(普通は~/.bash_profileなどに書き込むが、必ずしもいつもpyenvを使いたいわけではないので自分はこうしている。) setup.sh export PYENV_ROOT="$HOME/.pyenv" export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init --path)" eval "$(pyenv init -)" pyenv local [使いたいPythonのバージョン] source .venv/bin/activate 今後新しいシェルでプロジェクトを使うときは最初にsource setup.shすればよい。 参考にしたサイト
- 投稿日:2022-02-23T11:08:48+09:00
MediaPipeを使ってリアルタイムでの手の座標取得をしてみた
はじめに 前回、MediaPipeを用いて静止画から手の座標取得を行うという記事を投稿しました。 ありがたい事にカメラからリアルタイムでの座標取得を行いたいというコメントを頂きましたので、公式ドキュメントに記載のサンプルとほぼほぼ似たような内容になりますが、投稿したいと思います。 やること PCカメラで取得中の映像に対してのリアルタイムでの手の座標取得 座標を描画した画像の表示 コード とりあえずコード全体を記載します。 import cv2 import numpy as np import mediapipe as mp mp_drawing = mp.solutions.drawing_utils mp_drawing_styles = mp.solutions.drawing_styles mp_hands = mp.solutions.hands # カメラキャプチャの設定 camera_no = 0 video_capture = cv2.VideoCapture(camera_no) video_capture.set(cv2.CAP_PROP_FRAME_WIDTH, 640) video_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) if __name__ == '__main__': with mp_hands.Hands(static_image_mode=True, max_num_hands=2, # 検出する手の数(最大2まで) min_detection_confidence=0.5) as hands: try: while video_capture.isOpened(): # カメラ画像の取得 ret, frame = video_capture.read() if ret is False: print("カメラの取得できず") break # 鏡になるよう反転 frame = cv2.flip(frame, 1) # OpenCVとMediaPipeでRGBの並びが違うため、 # 処理前に変換しておく。 # CV2:BGR → MediaPipe:RGB image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) image.flags.writeable = False # 推論処理 hands_results = hands.process(image) # 前処理の変換を戻しておく。 image.flags.writeable = True write_image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) # 背景を黒にする場合は下記コメントアウトを外してください。 # img_h, img_w, _ = frame.shape # blank = np.zeros((img_h, img_w, 3)) # write_image = blank # 有効なランドマーク(今回で言えば手)が検出された場合、 # ランドマークを描画します。 if hands_results.multi_hand_landmarks: for landmarks in hands_results.multi_hand_landmarks: mp_drawing.draw_landmarks( write_image, landmarks, mp_hands.HAND_CONNECTIONS, mp_drawing_styles.get_default_hand_landmarks_style(), mp_drawing_styles.get_default_hand_connections_style()) # ディスプレイ表示 cv2.imshow('hands', write_image) key = cv2.waitKey(1) if key == 27: # ESCが押されたら終了 print("終了") break finally: video_capture.release() cv2.destroyAllWindows() ほぼ公式ドキュメントと前回の記事の流用なので、あまり書ける事が無いですが… 前回との静止画からの座標取得との違いとしては、 OpenCVにてカメラからの画像取得をして、ループさせるという点になります。 # カメラキャプチャの設定 camera_no = 0 video_capture = cv2.VideoCapture(camera_no) video_capture.set(cv2.CAP_PROP_FRAME_WIDTH, 640) video_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) # ====省略==== try: while video_capture.isOpened(): # ====省略==== # ディスプレイ表示 cv2.imshow('hands', write_image) # ====省略==== finally: video_capture.release() cv2.destroyAllWindows() 途中のコメントアウトを外すことで黒い画像に座標のランドマークだけが描画できます。 # 背景を黒にする場合は下記コメントアウトを外してください。 - # img_h, img_w, _ = frame.shape - # blank = np.zeros((img_h, img_w, 3)) - # write_image = blank + img_h, img_w, _ = frame.shape + blank = np.zeros((img_h, img_w, 3)) + write_image = blank おわりに 普段の業務では違う分野の作業をしていまして、OpenCVとかもあまり触ったことないのですが、知らない分野のライブラリ使う事も新鮮で楽しいですね。 取得した座標情報から機械学習とかできたら面白そうだなと思っています。
- 投稿日:2022-02-23T10:55:41+09:00
Pythonでインスタンスを比較するために__eq__を実装するときの注意点
概要 Pythonでインスタンスを比較するとき、__eq__メソッドを使いたいことがある。 独自の方法で複数のインスタンスが同じか確認したいためだ。 ただ、この__eq__メソッドをオーバーライドした時は、同時に__hash__メソッドについても考える必要がある。 __eq__をオーバーライドすると、デフォルトでは__hash__がNoneを返すためだ。 もし、__eq__メソッドをオーバーライドしたオブジェクトをディクショナリのキーやセットで使うときなど(※)は__hash__メソッドもオーバーライドする。 ※ハッシュ可能オブジェクトの場合 __eq__メソッドをオーバーライドしない場合 example_not_override.py class TestNotEq: def __init__(self): self.a = "a" test_not_eq_1 = TestNotEq() test_not_eq_2 = TestNotEq() test_not_eq_1 == test_not_eq_2 Out: False test_not_eq_1.__hash__() Out: -9223371891910350837 test_not_eq_2.__hash__() Out: -9223371891910350809 この場合、self.aに同じ値が入っているインスタンスtest_not_eq_1とtest_not_eq_2は==で比較するとFalseとなる。 __eq__メソッドをオーバーライドした場合 example_override.py class TestEq: def __init__(self): self.a = "a" def __eq__(self, other): return self.a == other.a test_eq_1 = TestEq() test_eq_2 = TestEq() test_eq_1 == test_eq_2 Out: True test_eq_1.__hash__() Traceback (most recent call last): File "C:\Python\Python37\lib\site-packages\IPython\core\interactiveshell.py", line 3326, in run_code exec(code_obj, self.user_global_ns, self.user_ns) File "<ipython-input-7-10d68bdb2b74>", line 1, in <module> test_eq_1.__hash__() TypeError: 'NoneType' object is not callable ==での比較結果はTrueとなるが、__hash__メソッドの呼び出しは失敗する。 これは__hash__がNoneになっているためである。 test_eq_1.__hash__ == None Out: True 対応 >>> __hash__メソッドをオーバーライドする example_override_hash.py class TestEq: def __init__(self): self.a = "a" def __eq__(self, other): return self.a == other.a def __hash__(self): return super().__hash__() 参考 Python Document __hash__
- 投稿日:2022-02-23T10:48:20+09:00
pythonでDataFrameを処理するときに使う式
毎回データフレームを編集するとき、同じようなことをやっているのに毎回忘れてしまっている気がするので、使えそうなものを自分用にメモしておく # 必要ライブラリのimport import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns df_titanic = sns.load_dataset("titanic") # 項目名の日本語化 columns_t = ['生存', '等室', '性別', '年齢', '兄弟配偶者数', '両親子供数', '料金', '乗船港コード', '等室名', '男女子供', '成人男子', 'デッキ', '乗船港', '生存可否', '独身'] df_titanic.columns = columns_t df_titanic.head() 列ごとの欠損値の数を確認する df.isnull().sum() 列の項目ごとに個数をカウントする df["乗船港"].value_counts() 統計値を取得する df.describe() 特定の項目でグループ化(集約)する df.groupby("性別").mean() 特定の列を削除する df.drop("生存可否", axis = 1) axis = 0にしたら行を削除する 欠損値のある行を削除する df = df.dropna(subset = ["乗船港コード"]) 上記の[]は複数にしてもよい 欠損値を特定の値で埋める #平均値を入れる場合を考える age_average = df["年齢"].mean() df = df.fillna({"年齢": age_average}) list, numpy, series, dataframeの変換 このサイトがまとまっているので参照する https://irohaplat.com/python-convert-arrays-list-numpy-dataframe-series/ matplotlib関連 このサイト見てみる(メモ) https://lib-arts.hatenablog.com/archive/category/Matplotlib
- 投稿日:2022-02-23T10:25:21+09:00
認証用クラスベースビュー、汎用クラスベースビューのフォームがformで受け取れる理由
内容 Djangoでログイン処理を簡潔に実装するためのクラスベースビューであるLoginView(認証用クラスベースビュー)において、HTMLへフォームを表示させるときのformの正体を教えていただいたためメモします。AuthenticatedFormを継承している他の汎用クラスベースビューでも同様だと思います。 form が現れる例 ログイン画面の作成を想定し、以下のようなurl.pyを記述しました。 url.py from django.urls import path from django.contrib.auth.views import LoginView,LogoutView urlpatterns = [ path('login/',LoginView.as_view( redirect_authenticated_user=True, template_name='accounts/login.html' ),name='login'), ] このとき、Djangoが自動でログインに必要なフォームの生成を行ってくれるので利用し、login.htmlを作成します。 login.html {% extends 'base.html' %} {% block main %} {% csrf_token %} {{ form }} <button type="submit">ログイン</button> {% endblock %} ここで前述のフォームはlogin.htmlの {{ form }} で受け取り、表示されています。urls.py内ではformという変数は宣言しておらず、いったいどこから現れたのかが疑問でした。 調べても欲しい情報が見当たらなかったため、terartailで有識者のご意見を伺うとすぐに以下のような回答をいただきました。 LoginViewでform_class = AuthenticationFormでformを定義しています。 コード見て継承を辿っていくと、FormViewが継承しているFormMixinのget_context_dataでget_form,get_form_classからself.form_classをコンテキストとして渡しているためだと思われます。 [Django]クラスベースビューを利用した際のフォームの受け取り方 AuthenticatedFormが継承しているFormMixinのget_context_dataというメソッドから渡される値の名前がformだから、ということのようです。 書きたかったことは全てこの回答に詰まっているので後は実際にDjangoのソースコードを示すのみとします。 class LoginView(SuccessURLAllowedHostsMixin, FormView): """ Display the login form and handle the login action. """ form_class = AuthenticationForm class FormMixin(ContextMixin): """Provide a way to show and handle a form in a request.""" (中略) def get_context_data(self, **kwargs): """Insert the form into the context dict.""" if "form" not in kwargs: kwargs["form"] = self.get_form() return super().get_context_data(**kwargs) このget_context_dataメソッドの二行目がポイントみたいです。 kwargs["form"] = self.get_form() 確かにformという名前の値が登場しています。 反省点 疑問点が生じ、調べてもわからなかった場合はきちんと当該ソースコードの継承を追うことも頭に入れておく必要があることがわかりました。 Qiitaを利用するのは初めてなので、改善点や問題点とうご指摘いただければ大変助かります。
- 投稿日:2022-02-23T09:40:05+09:00
「新・明解Pythonで学ぶアルゴリズムとデータ構造」で勉強日記#17
【出典】「新・明解Pythonで学ぶアルゴリズムとデータ構造」 前回の記事はこちら 4-2 キュー キューはデータ構造の一つで、先入先出の機構です。(レジの行列のイメージ) キューに追加する操作をエンキュー、取り出す操作をデキューと呼び、取り出される側を先頭、データが押し込まれる側を末尾と呼びます。 コラム4-5 優先度付キュー 優先度付のキューを追加するとデキューする際に優先度順に取り出すことができる。 heapqモジュールで提供されており、heapq.heappsh(heap, data)でキューを、heap.heappop(heap)でデキューをします。(6章でまた扱います) リングバッファーによるキューの実現 デキューの際に配列内の要素をずらすことなくキューを実現するために用いるのがリングバッファーです。(切られたピザのようなイメージ) 先頭と末尾はfrontとrearで管理します。 リングバッファーを使うことで、要素の移動が不要になり、計算量はO(1)となります。 list4-3 #固定長キュークラス from typing import Any class FixedQueue: class Empty(Exception): """空のFixedQueueに対してdequeあるいはpeekが呼び出されたときに送出する例外""" class Full(Exception): """満杯のFixedQueueに対してenpueが呼び出されたときに送出する例外""" pass def __init__(self, capacity: int) -> None: """初期化""" self.no = 0 #現在のデータ数 self.front = 0 #先頭要素カーソル self.rear = 0 #末尾要素カーソル self.capacity = capacity #キューの容量 self.que = [None] * capacity #キューの本体 def __len__(self) -> int: """キューに押し込まれているデータ数を返す""" return self.no def is_empty(self) -> bool: """キューは空であるか""" return self.no <= 0 def is_full(self) -> bool: """キューは満杯か""" return self.no >= self.capacity def enque(self, x: Any) -> None: """データxをエンキュー""" if self.is_full(): raise FixedQueue.Full self.que[self.rear] = x self.rear += 1 self.no += 1 if self.rear == self.capacity: self.rear = 0 def deque(self) -> Any: """データをデキュー""" if self.is_empty(): raise FixedQueue.Empty x = self.que[self.front] self.front += 1 if self.front == self.capacity: self.front = 0 return x def peek(self) -> Any: """データをピーク(先頭データをのぞく)""" if self.is_empty(): raise FixedQueue.Empty #キューは空 return self.que[self.front] def find(self, value: Any) -> Any: """キューからvalueを探して添字(見つからなければ-1)を返す""" for i in range(self.no): idx = (i + self.front) % self.capacity if self.que[idx] == value: #探索成功 return idx return -1 #探索失敗 def count(self, value: Any) -> bool: """キューに含まれるvalueの個数を返す""" c = 0 for i in range(self.no): #底側から線形探索 idx = (i + self.front) % self.capacity if self.que[idx] == value: c += 1 return c def __contains__(self, value: Any) -> bool: """キューにvalueは含まれているか""" return self.count(value) def clear(self) -> None: """キューを空にする""" self.no = self.front = self.rear = 0 def dump(self) -> None: """全データを先頭→末尾の順に表示""" if self.is_empty(): print('キューは空です') else: for i in range(self.no): print(self.que[(i + self.front) % self.capacity], end=' ') print() list4-4 #固定長スタックStackの利用例 from enum import Enum #from fixed_queue import FixedQueue Menu = Enum('Menu', ['エンキュー', 'デキュー', 'ピーク', '探索', 'ダンプ', '終了']) def select_menu() -> Menu: """メニュー選択""" s = [f'({m.value}){m.name}' for m in Menu] while True: print(*s, sep=' ', end='' ) n = int(input(':')) if 1 <= n <= len(Menu): return Menu(n) q = FixedQueue(64) #最大64個プッシュできるスタック while True: print(f'現在のデータ数:{len(q)} / {q.capacity}') menu = select_menu() #メニュー選択 if menu == Menu.エンキュー: #エンキュー x = int(input('データ:')) try: q.enque(x) except FixedQueue.Full: print('キューが満杯です。') elif menu == Menu.デキュー: #デキュー try: x = q.deque() print(f'デキューしたデータは{x}です。') except FixedQueue.Empty: print('キューが空です。') elif menu == Menu.ピーク: #ピーク try: x = q.peek() print(f'ピークしたデータは{x}です。') except FixedQueue.Empty: print('キューが空です。') elif menu == Menu.探索: #探索 x = int(input('値:')) if x in q: print(f'{q.count(x)}個含まれ先頭の位置は{q.find(x)}です。') else: print('その値は含まれません。') elif menu == Menu.ダンプ: q.dump() else: break コラム4-6 両方向待ち行列(デック) デックと呼ばれる両方向待ち行列は、先頭末尾両方からデータの押し込み・取出しが行えるデータ構造で、collections.dequeとして提供されています。 コラム4-7 リングバッファの応用例 list4C-2 #好きな個数だけ値を読み込んで要素数nの配列に最後のn個を格納 n = int(input('何個の整数を記憶しますか:')) a = [None] * n #読み込んだ値を格納する配列 cnt = 0 while True: a[cnt % n] = int(input((f'{cnt + 1}個目の整数:'))) cnt += 1 retry = input(f'続けますか?(Y…Yes / N…No) :') if retry in {'N', 'n'}: break i = cnt - n if i < 0: i = 0 while i < cnt: print(f'{i + 1}個目={a[i % n]}') i += 1 以上です。今回で4章が終了です。 ありがとうございました。
- 投稿日:2022-02-23T09:25:31+09:00
Chalice on Local
この記事の目的 この記事の目的は、AWS製のサーバーレスフレームワークChaliceとDynamoDBを組み合わせて利用する手順を個人的なメモとして記すことです。 前回の内容を補完する意味でローカルでの実行に特化しています。 以下の内容を含みます。 Chalice, DynamoDBLocalを用いてローカルマシンで利用します ローカル環境でのIDEを利用したデバッグ docker-composeを利用して、DynamoDBLocalを起動します 前提事項 以下のインストールについては済んでいるものとします。 Python3 chalice docker-compose Python 3.8.9 (Pipenv利用) chalice 1.26.4 AWS-CLI 2.4.11 docer-compose 2.1.1 VSCode 1.63.2 macOS Monterey 12.1 References Chalice公式サイト https://aws.github.io/chalice/index.html 基本的な構築手順 前回記事やChalice公式サイトを参照ください。 DynamoDB localをdocker-composeで起動する。 ソースコードとは別のディレクトリ(同列の階層にする)に、docker-compose.ymlを作成します。 docker-compose.yml version: "3.8" services: dynamodb-local: command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ./data" image: "amazon/dynamodb-local:latest" container_name: dynamodb-local ports: - "8000:8000" volumes: - "./docker/dynamodb:/home/dynamodblocal/data" working_dir: /home/dynamodblocal dynamodb-admin: container_name: dynamodb-admin image: aaronshaf/dynamodb-admin:latest environment: - DYNAMO_ENDPOINT=dynamodb-local:8000 ports: - 8001:8001 depends_on: - dynamodb-local DynamoDB Localとdynamodb-adminを起動します。 % docker-compose up [+] Running 2/1 ⠿ Container dynamodb-local Created 0.1s ⠿ Container dynamodb-admin Created 0.0s Attaching to dynamodb-admin, dynamodb-local dynamodb-local | Initializing DynamoDB Local with the following configuration: dynamodb-local | Port: 8000 dynamodb-local | InMemory: false dynamodb-local | DbPath: ./data dynamodb-local | SharedDb: true dynamodb-local | shouldDelayTransientStatuses: false dynamodb-local | CorsParams: * dynamodb-local | dynamodb-admin | database endpoint: dynamodb-local:8000 dynamodb-admin | region: us-east-1 dynamodb-admin | accessKey: key dynamodb-admin | dynamodb-admin | dynamodb-admin listening on http://localhost:8001 (alternatively http://0.0.0.0:8001) dynamodb-adminでテーブルを作成する。 localhost:8001でdynamodb-adminのGUIに入れます。 CreateTableをします。(テーブル名とキーだけ指定すれば良いです) chalice localコマンドでローカル実行! config.jsonにローカル用の定義を追加 以下のように、stagesにlocalの定義を追加します。 .chalice/config.json { "version": "2.0", "app_name": "chalice_demo", "stages": { "dev": { "api_gateway_stage": "api", "environment_variables": { "DB_TABLE_NAME": "Records" }, "autogen_policy": false - } + }, + "local": { + "environment_variables": { + "IS_LOCAL": "true", + "DB_ENDPOINT": "http://localhost:8000", + "DB_TABLE_NAME": "LocalRecords" + } + } } } 説明: いずれも環境変数の設定です。 IS_LOCAL : ローカル実行か否かを示します(今後の記事で予定している認証の切り替えに使います) DB_ENDPOINT : DynamoDB Localのエンドポイントを指定します DB_TABLE_NAME : テーブル名です(ローカル用にあえて値を変えています) ローカルでchaliceを実行します。 注意! デフォルトだとstageはdev, portは8000となります。 いずれも、他で使用しているため、明示的にコマンド引数で指定します。 % chalice local --stage local --port 8080 Serving on http://127.0.0.1:8080 chalice local で --stage localとするのはかなり間抜けですが... 余談ですが、--stageを指定しないと、awsのDynamoDBにつながります。(awsのクレデンシャルを設定している場合) ローカルのAPIエンドポイントを叩いてみる!(出力は見やすく整形しています) % curl localhost:8080/records/166d6c4323d742bc9af87fcc0c3e2241 { "sub":"LOCAL_USER", "result_time":"00:24:59", "race":"3", "runner_name":"test-runner3", "description":"Memo3", "section":"3", "id":"166d6c4323d742bc9af87fcc0c3e2241", "team":"TeamX" } IDEでのデバッグ実行 IDEにはPyCharmを使いました。(VSCodeと迷いましたが、デバッグの設定に関してはPyCharmの方が簡単だったので) 実行済みのchalice localのプロセスにアタッチします。 プロセスを選択します。 ブレイクポイントで停止し変数のウォッチなどができます。(通常のデバッグと同様です) まとめ 今回は、Chalice on Localということで、ローカルでの実行とデバッグを行いました。 正直、awsにデプロイする方が手っ取り早い気もしますが、アプリケーションのロジックのデバッグがローカルで出来ると良い面もあるので、方法を知っておいても損はないかなぁと思いました。 次は、認証でAPIを保護するというのを試してみたいと思います。 ソースコードは、GitHubにあげてあります。
- 投稿日:2022-02-23T04:08:19+09:00
Pythonで最速のULIDライブラリを作った話(C++拡張)
TL;DR 卍爆速卍なPythonのULIDライブラリ"fast_ulid"を作った 使い方はここ ULIDとは みなさんはULIDをご存知でしょうか? ULIDはUUIDの代替となるべく策定されたもので UUIDと128ビット互換 1ミリ秒ごとに1.21e24のユニークなULIDを作成できる 辞書順にソート可能 36文字のUUIDに対して、26文字でエンコードされる Crockford's base32という、可読性の高いエンコード方式 ケースインセンシティブ 特殊文字を使わないので、URLセーフ 単調増加なバイト列(正しい生成順にソートされる) というものです。上と被る部分もありますが、現状最新とされるUUID4は 文字効率が悪い(128bitを36文字で表す) ソート不可能 ということで、少々使い勝手が悪いようです。 ULIDの何がうれしいのか 純粋に文字長が短いことで、より可読性が高くなります。 さらには、そのものにミリ秒単位でタイムスタンプが含まれていることで、例えばデータベースのPrimary Keyにしながらタイムスタンプのカラムを減らすことができます。 私にとっては後者は結構うれしく、見つけたときはこれからこれを使っていこうと思いました。 少々問題が ありました。 データベースのPrimary Keyにするべく使っており、テストデータの生成を行っていたのですが、Python内でのデータ生成段階からなんとなく... 遅い? PythonでULIDを使おうと思った時には、大体 python-ulid (mdomkeさん) ulid-py (ahawkerさん) ulid (mdipierroさん) のどれかになると思います。 2022/2/23時点でそれぞれのスターの数は、上から76, 289, 34となっており、ulid-pyが優勢なので私もこれを使っていたのですが、大体ULIDを1,000,000個生成してstrにするときに私の環境では5, 6秒かかっていました。 プログラマというのは技術に対して貪欲で、作れると思ったものは作らないとうずうずしてしまいますので、もっと高速なULIDライブラリを作ってみようと思い立ちました。 C++拡張 Pythonには、複数の拡張方法があります。 純粋にPythonでコードを書いてもいいですし、もしパフォーマンスにこだわるのであれば、CやC++拡張を使うのも手です。 今回は(私の技術力の範囲内で)全力で高速化するために、後者の拡張方法を用いたいと思います。 初めはCで拡張しようと思っていましたが、どうやらタイムスタンプに用いるミリ秒取得が環境によって上手くいったりいかなかったり、非推奨だったりでよくわかんないことになっているので、C++を使うことにしました。 以下に技術的につまったところを上げていこうと思います。 METH_FASTCALL Python3.7から追加された引数の受け取り方です。以前まではMETH_VARARGSを使用していました。 METH_FASTCALLを用いることで、かなり高速になりました(2倍程度) しかし、この受け取り方を用いることによって、かなりコードを書き換える必要が出てきます。 具体的にいうと、METH_VARARGSを用いる際は引数に引数のTupleを表すPyObjectが与えられますが、METH_FASTCALLでは引数のPyObjectの配列が与えられます。 ですので、引数をパースする便利関数のPyArg_ParseTupleなどが使えず、そのあたりを手作業で行う必要があります。 Python3.6以下でUTC Timezone Python3.7からはPyDateTime_TimeZone_UTCでUTCを表すタイムゾーンのシングルトンを取得できるのですが、Python3.6ではそういった機能はありません。 Pythonにおけるdatetime.datetime.fromtimestampはタイムゾーンを指定した方が断然早いので、パフォーマンスを重視する今回はつけない選択肢はありませんでした。 そこで今回は、PythonをC++から呼び出して、タイムゾーンオブジェクトを取得する方式としました(なんかもっといい方法ありそう。。。) PyObject* timezone_utc; void create_utc_tz() { PyObject* pytz = PyImport_ImportModule("datetime"); if (!pytz) { return; } PyObject* pytz_tz = PyObject_GetAttrString(pytz, "timezone"); // datetime.timezone を取得 Py_DECREF(pytz); // datetime自体の参照を減らす if (!pytz_tz) { return; } PyObject* pytz_tz_utc = PyObject_GetAttrString(pytz_utc, "utc"); // datetime.timezone.utc を取得 Py_DECREF(pytz_tz); // datetime.timezoneも同様 timezone_utc = pytz_tz_utc; Py_DECREF(pytz_tz_utc); } という感じになってます。 もっといい方法あるよって方がいらっしゃれば、是非コメントで教えてください。 完成したのでベンチマーク 完成しました。 血と汗と涙と睡眠時間と等価交換で、ライブラリ一つを錬成するのは非常に効率が悪いですね GitHub Copilotくん、README.mdからコードを自動生成してくれ。 さて、ここでベンチマークを行ってみます。 Python実装の5倍程度で及第点、10倍で御の字ですかね。今回作成したライブラリがfast-ulidで、他の二つが比較対象です。ulidだけ群を抜いて遅かった(いいえ、他二つがPythonなのに早すぎるだけ)ので、二つのみを比較対象にしました。 それぞれ左から ULID文字列作成 datetime.datetimeオブジェクトからULID文字列作成 タイムスタンプ(float)からULID文字列作成 ULID文字列からdatetime.datetimeオブジェクト作成 ULID文字列からタイムスタンプ(float)作成 となっており、1,000,000の連続試行を10回繰り返した平均時間です。 俺うれしい...? もっともスコアの低い"datetime.datetimeオブジェクトからULID文字列作成" でも10倍弱、もっともスコアの良い"ULID文字列からタイムスタンプ(float)作成"では50倍程度の速度になっています。 総じてスコアが低めなのはdatetime.datetimeオブジェクトを用いるもので、どうしてもPythonとしてオブジェクトの操作を行う必要があるところから、オーバーヘッドとなってしまっています。 ちなみに、内部的にULIDを作成したりパースしたりする部分はC++しか絡んでないので、(当然といえば当然ですが)これのfast_ulidの数十倍速いです。 使い方 以下モロマ(モロマーケティング)です。 GitHub: fast_ulid $ pip install fast_ulid でインストール 実行例は以下の通り import fast_ulid import time import datetime # ulid.ulid()で、現在のタイムスタンプからULIDを作成 ulid = fast_ulid.ulid() # timestamp や datetime オブジェクトを与えることも可能 # timestampのミリ秒部は小数点以下に配置しなければなりません # JavaScriptのようなほかの言語では、整数部に来ます ulid = fast_ulid.ulid(datetime.datetime.now()) ulid = fast_ulid.ulid(time.time()) print(ulid) # str が返されます # 01FVY4F1TYP63XM1YVHBVVCBSB # timestamp や datetime.datetime を ULID から取得することも可能 print(fast_ulid.decode_timestamp(ulid)) # 1644910053.214 print(fast_ulid.decode_datetime(ulid)) # 2022-02-15 07:27:33.214000+00:00 これですべての機能説明ですが、Simple is better than Complex ですよね! 是非便利にお使いください!