20220117のPythonに関する記事は27件です。

collections.abcから見るPythonデータ型の分類

まえがき 最近いろんなPython本を読み漁っているのですが、 コンテナ(container)やシーケンス(sequence)などの定義が混乱してしまうのは私だけでしょうか? 備忘がてら整理してみようというのが今回のテーマです。 TL;DR 型 mutable? container? iterable? iterator? collection? sequence? bool × × × × × × int × × × × × × float × × × × × × complex × × × × × × str × ○ ○ × ○ ○ tuple × ○ ○ × ○ ○ bytes × × ○ × × ○ frozenset × ○ ○ × ○ × bytearray ○ × ○ × × ○ list ○ ○ ○ × ○ ○ set ○ ○ ○ × ○ × dict ○ ○ ○ × ○ × 検証 組み込みモジュールの中に色々な抽象基底クラスを定義するcollections.abcがあります。 その中にContainerだのSequenceだのがありますよね。。 今回は、それらの定義を基にPythonの基本データ型を分類してみます。。 まずPythonの基本データ型を持つクラスを作ります。 class PythonData: """Pythonのデータ型たち""" def __init__(self) -> None: # immutable data type self.bool_ = True self.int_ = 123 self.float_ = 3.14 self.complex_ = 3j self.str_ = "abc" self.tuple_ = ("a", "b", "c") self.bytes_ = b"ab\x63" self.frozenset_ = frozenset({"a", "b", "c"}) # mutable data type self.bytearray_ = bytearray(range(0, 256)) self.list_ = ["a", "b", "c"] self.set_ = {"a", "b", "c"} self.dict_ = {"foo": "bar"} さぁ地道に検証していきましょう。。 abc.Container | コンテナ __contains__() メソッドを提供するクラスの ABC です とあるので、、 「コンテナとは、in演算子が使えるオブジェクト」になります。 def is_container(data: PythonData) -> None: """containerとは?: in演算子が使える""" for type in data.__dict__: try: # 判定結果は気にせず呼び出せればよし if "a" in data.__dict__[type]: print(f"* {type} is container.") else: print(f"* {type} is container.") except TypeError: pass is_container(PythonData()) # * str_ is container. # * tuple_ is container. # * frozenset_ is container. # * list_ is container. # * set_ is container. # * dict_ is container. 【検証結果】 文字列、タプル、リスト、集合、辞書、frozensetはコンテナである。 abc.Iterable | イテラブル __iter__() メソッドを提供するクラスの ABC です。 とあるので、、 「イテラブルとは、iter()メソッドが使えるオブジェクト」になります。 def is_iterable(data: PythonData) -> None: """iterableとは?: iter()を呼べる""" for type in data.__dict__: try: iter(data.__dict__[type]) print(f"* {type} is iterable.") except TypeError: pass is_iterable(PythonData()) # * str_ is iterable. # * tuple_ is iterable. # * bytes_ is iterable. # * frozenset_ is iterable. # * bytearray_ is iterable. # * list_ is iterable. # * set_ is iterable. # * dict_ is iterable. 【検証結果】 文字列、バイト、バイト配列、タプル、リスト、集合、辞書、frozensetはイテラブルである abc.Iterator | イテレータ abc.Iterableクラスを継承しています。 __iter__() メソッドと __next__() メソッドを提供するクラスの ABC です。 とあるので、、 「イテレータとは、iter()、next()メソッドが使えるオブジェクト」になります。 def is_iterator(data: PythonData) -> None: """iteratorとは?: next()を呼べる""" for type in data.__dict__: try: next(data.__dict__[type]) print(f"* {type} is iterator.") except TypeError: pass is_iterator(data) # 該当なし 【検証結果】 該当なし 【考察】 やや想定外の結果ですね。。 Pythonのイテラブルなデータ型は全て「イテレータではない」ということになります。 でも、リストもタプルもfor文で反復処理できますよね。。 公式の文言を引用すると、 通常は反復可能オブジェクトを使う際には、 iter() を呼んだりイテレータオブジェクトを自分で操作する必要はありません。 for 文ではこの操作を自動的に行い、一時的な無名の変数を作成してループを回している間イテレータを保持します。 とありますので、 for文は内部的にiter()メソッドを呼び、イテラブルからイテレータを生成している、 ということになります。 以下は、for文の内部処理のイメージです(大体合っているハズ) def my_for(iterable): iter_ = iter(iterable) while True: try: next(iter_) except StopIteration: break であれば、「該当なし」の結果も頷けますね。 リストやタプルがfor文で反復処理できることは自明なのでそこは割愛します。 abc.Sized 本編の趣旨からは若干外れますが、後続の説明のためです。 __len__() メソッドを提供するクラスの ABC です。 def is_sized(data: PythonData) -> None: """sizedとは?: len()を呼べる""" for type in data.__dict__: try: len(data.__dict__[type]) print(f"* {type} is collection.") except TypeError: pass is_sized(PythonData()) # * str_ is collection. # * tuple_ is collection. # * bytes_ is collection. # * frozenset_ is collection. # * bytearray_ is collection. # * list_ is collection. # * set_ is collection. # * dict_ is collection. abc.Collection | コレクション サイズ付きのイテラブルなコンテナクラスの ABC です。 曖昧な表現ですが、Sized、Iterable、Containerクラスを継承しているので、 「コレクションとは、len()、iter()、in演算子が使えるオブジェクト」になります。 これまでの結果の積集合を取れば良いので結果は、 文字列、タプル、リスト、集合、辞書、frozensetはコレクションである。 コンテナと同じ結果なので、上記は「コンテナかつコレクション」とも言えます。 abc.Sequence | シーケンス こちらはシーケンス自体の定義になります。 (シーケンス) 整数インデクスによる効率的な要素アクセスを __getitem__() 特殊メソッドを通じてサポートし、長さを返す __len__() メソッドを定義した iterable です。 またCollectionを継承しているので、 「シーケンスとは、整数インデックスで要素が参照できるオブジェクト」になります。 def is_sequence(data: PythonData): """sequenceとは?: インデックスによる要素アクセスができる""" for type in data.__dict__: try: data.__dict__[type][0] print(f"* {type} is sequence.") except TypeError: pass except KeyError: pass is_sequence(PythonData()) # * str_ is sequence. # * tuple_ is sequence. # * bytes_ is sequence. # * bytearray_ is sequence. # * list_ is sequence. 【検証結果】 文字列、バイト、バイト配列、タプル、リストはシーケンスである。 まとめ まとめたものが冒頭のマトリクスになります。 複素数(complex)とかいらんかったわーって後から思いましたw 検証ベースなので、本当の定義とは異なる可能性はあります。。 何卒ご了承くださいm(_ _)m sequenceとかは__getitem__に依存しているので文字列のインデックス(つまりキー)で 参照できるようにすれば辞書(abc.Mapping)になるんだー とか、 __setitem__と__delitem__を実装すればミュータブルなオブジェクトになるんだー とか違った気付きもあって良かったですw 【最後の一言】 マジックメソッド奥が深いわーー
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginners Selection 11問をpythonで解いてみた

AtCoderの初心者向け問題集である AtCoder Beginners Selection の11問をPythonで解説します。 Welcome to AtCoder PracticeA a = int(input()) b, c = map(int, input().split()) s = input() print(a+b+c, s) 詳しい解説はこちら Product ABC086A a, b = map(int, input().split()) ans = a * b if ans % 2 == 0: print('Even') else: print('Odd') 詳しい解説はこちら Placing Marbles ABC081A s = input() print(s.count('1')) 詳しい解説はこちら Shift only ABC081B N = int(input()) A = list(map(int, input().split())) flag = 0 count = 0 while True: for i in range(N): if A[i] % 2 != 0: flag = 1 if flag == 1: break for i in range(N): A[i] = A[i]//2 count += 1 print(count) 詳しい解説はこちら Coins ABC087B A = int(input()) B = int(input()) C = int(input()) X = int(input()) count = 0 for i in range(A+1): for j in range(B+1): for k in range(C+1): if 500*i + 100*j + 50*k == X: count += 1 print(count) 詳しい解説はこちら Some Sums ABC083B N, A, B = map(int, input().split()) count = 0 for i in range(N+1): if A <= sum(list(map(int, str(i)))) <= B: count += i print(count) 詳しい解説はこちら Card Game for Two ABC088B N = int(input()) a = list(map(int, input().split())) a.sort(reverse=True) Alice_calds = a[0::2] Bob_calds = a[1::2] ans = sum(Alice_calds)-sum(Bob_calds) print(ans) 詳しい解説はこちら Kagami Mochi ABC085B N = int(input()) d = [input() for _ in range(N)] print(len(set(d))) 詳しい解説はこちら Otoshidama ABC085C N, Y = map(int, input().split()) for i in range(N+1): for j in range(N-i+1): k = N - i - j if 10000*i + 5000*j + 1000*k == Y: print(i, j, k) exit() print(-1, -1, -1) 詳しい解説はこちら 白昼夢 ABC049C s = input() s = s.replace('eraser', '') s = s.replace('erase', '') s = s.replace('dreamer', '') s = s.replace('dream', '') if s: print('NO') else: print('YES') 詳しい解説はこちら Traveling ABC086C N = int(input()) t_old = 0 x_old = 0 y_old = 0 flag = True for _ in range(N): t, x, y = map(int, input().split()) distance = abs(x-x_old) + abs(y-y_old) time = t-t_old t_old = t x_old = x y_old = y if time < distance: flag = False elif time % 2 != distance % 2: flag = False if flag: print('Yes') else: print('No') 詳しい解説はこちら
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Blenderでフラクタル

シェルピンスキーのギャスケット Blenderでシェルピンスキーのギャスケットを作ってみました。 ※ 色は別途つけています。 作り方 Scriptingワークスペースで、下記をコピペして実行します。 import bpy import numpy as np def sier(pos, size): s3 = size * 1.73205 if size <= 0.2: opt = {"radius1": size, "depth": s3, "vertices": 3, "calc_uvs": False} bpy.ops.mesh.primitive_cone_add(location=pos + [s3 / 2, size / 2, s3 / 2], **opt) bpy.ops.mesh.primitive_cone_add(location=pos + [s3 * 1.5, size / 2, s3 / 2], **opt) bpy.ops.mesh.primitive_cone_add(location=pos + [s3, size * 2, s3 / 2], **opt) return sier(pos, size / 2) sier(pos + [s3, 0, 0], size / 2) sier(pos + [s3 / 2, size * 1.5, 0], size / 2) sier(np.zeros(3), 1) 0.2を小さくすると、三角錐が増えていきます。 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

代理店長私のPythonでRPA作ってみた。

今アルバイトをやっている飲食店で、店長が社員からアルバイトになって、私がお昼を中心に店長の代理的なポジションをやることになった。それに伴い、シフト作成と給料計算の一部を担当することになった。 なんだけど、給料計算の一部というのが、手動でやっていて意外と手間なことに気づいたのである。 というのも、お店でのシフトや打刻の管理は、2つのサイトで行っていて、連携もされていない。。。 手間がかかるなぁと困った代理店長私は、あることを思いついた。ちょうどPythonの勉強をし始めたところだったから、RPAを作って自動化しようじゃないか!と やりたいこと はい、ということで実現したいことを説明するとこんな感じだ。 実際の打刻が記録されている、データを引っ張りたいページ(以降、甲と呼びます) 事前に決まっていたシフトの表示と実際の打刻データを保存するための別サイトのページ(以降、乙と呼びます) 基本的な処理の流れとしては、 1.甲に自動ログイン 2.自動処理をしたい日付を入力し、ページ遷移 3.名前、出退勤データなどを、それぞれリストに格納 4.乙に自動ログインし、ページ遷移 5.乙にある全スタッフの名前をリストに格納 6.甲で取得したデータと合致する名前を持つ乙の列を特定する 7.乙上で事前に登録されているシフトデータを取得する 8.給料計算が30分単位のため、30分単位のデータに変換して、出退勤、休憩開始終了時間のフォームにデータを入力する。7で取得したシフトデータと実際の打刻データに相違がある場合は後でまとめて出力するためにnoticesをリストに格納。 9.保存 10.8でnoticesリストに格納したnoticeがある場合noticesを出力する。 実際にやってみた 基本的にはPythonを使ってスクレイピングをしていくことになる。 選択肢は幾つかあったが、どうやらseleniumというライブラリが使いやすいようで実際に調べながら使ってみた。 seleniumの使える環境設定をしていく上で、以下の記事を参考にさせていただいた。(ありがとうございました) https://masutomo.hatenablog.com/entry/2020/06/21/114424 で、実際に書いたコードがこちら。 rpa.py #github用 # chrome ver 97.* # coding: UTF-8 import chromedriver_binary from selenium import webdriver from selenium.webdriver.common.keys import Keys from time import sleep from bs4 import BeautifulSoup from selenium.webdriver.common.by import By notices = [] # WebDriver のオプションを設定する options = webdriver.ChromeOptions() options.add_argument('--headless') print('connectiong to remote browser...') driver = webdriver.Chrome(options=options) ###ここからデータを取ってくるページ####################################################### driver.get('##ログインページのURL##') print(driver.current_url + 'に接続しています...') # ID,pass入力 driver.find_element(By.NAME,'login_name').send_keys('##ログインID##') driver.find_element(By.NAME,'password').send_keys('##ログインパスワード##') driver.find_element(By.CSS_SELECTOR,"[type='submit']").send_keys(Keys.ENTER) print('HPログイン中...') sleep(1) date = input('取得したい日付を入力してください(例)20220101:') driver.get('##打刻データを取得するページのURL##?date=' + date) print(date + 'の実績管理に移動中...') sleep(1) soup = BeautifulSoup(driver.page_source, "lxml") #名前 names = soup.select("#attend-table td:nth-child(1)") #出勤時間 attend_times = soup.select("#attend-table td:nth-child(2)") #退勤時間 leave_times = soup.select("#attend-table td:nth-child(3)") #休憩時間 break_times = soup.select("#attend-table td:nth-child(4)") #ヘルプ店舗 help_stores = soup.select("#attend-table td:nth-child(5)") ###ここからAirShift####################################################### driver.get('##AirShiftログインページのURL##') print(driver.current_url + 'に接続しています') #AirShiftにログイン driver.find_element(By.ID, "account").send_keys('##ログインID##') driver.find_element(By.ID, "password").send_keys('##ログインパスワード##') driver.find_element(By.CSS_SELECTOR,"[type='submit']").send_keys(Keys.ENTER) sleep(1) #店舗選択 driver.find_elements(By.XPATH,'//a')[1].click() sleep(1) driver.get('##データを入力するページのURL##' + date) driver.find_element(By.CLASS_NAME,'label___1541MBG8').click() sleep(1) soup = BeautifulSoup(driver.page_source, "lxml") #名前 airshift_names = soup.select(".staffName___31nIRozl") x = 0 #実績管理側の配列番号 for name in names: #namesを回す処理 i = 1 #airshift側の配列番号 name = str(name).replace('<td>','') name = str(name).replace('</td>','') name = str(name).replace(' ',' ') help_store = help_stores[x] help_store = str(help_store).replace('<td>','') help_store = str(help_store).replace('</td>','') help_store = str(help_store).replace( '\n' , '' ) if help_store != '': #ヘルプ店舗の場合スキップ x += 1 print('別店舗ヘルプのため'+ name +'の入力をスキップします') continue for airshift_name in airshift_names: #nameに合致するairshift_nameを探す airshift_name = str(airshift_name).replace('<span class="staffName___31nIRozl">','') airshift_name = str(airshift_name).replace('</span>','') if name == airshift_name: #合致するものが見つかった時の処理 #airshift側の表示シフト時間 airshift_shift_path = ".summaryTableWrapper___2eKGavN6 > div tbody tr:nth-child(" + str(i) + ") td:nth-child(1) div" airshift_shift = soup.select(airshift_shift_path) #バグるので初期化 airshift_shift_break_start = '' airshift_shift_break_end = '' #airshift上の出勤退勤休憩開始終了時間取得処理 if len(airshift_shift) > 1: #airshift上において休憩がある場合の処理 airshift_shift[0] = str(airshift_shift[0]).replace('<div>','') airshift_shift[0] = str(airshift_shift[0]).replace('</div>','') airshift_shift[0] = str(airshift_shift[0]).replace('〜','') airshift_shift[0] = str(airshift_shift[0]).replace('<!-- -->','') airshift_shift[0] = str(airshift_shift[0]).replace(':','') airshift_shift[1] = str(airshift_shift[1]).replace('<div>','') airshift_shift[1] = str(airshift_shift[1]).replace('</div>','') airshift_shift[1] = str(airshift_shift[1]).replace('〜','') airshift_shift[1] = str(airshift_shift[1]).replace('<!-- -->','') airshift_shift[1] = str(airshift_shift[1]).replace(':','') airshift_shift_start = airshift_shift[0][:4] airshift_shift_break_start = airshift_shift[0][4:] airshift_shift_break_end = airshift_shift[1][:4] airshift_shift_end = airshift_shift[1][4:] elif len(airshift_shift) == 1: airshift_shift = str(airshift_shift[0]).replace('<div>','') airshift_shift = str(airshift_shift).replace('</div>','') airshift_shift = str(airshift_shift).replace('〜','') airshift_shift = str(airshift_shift).replace('<!-- -->','') airshift_shift = str(airshift_shift).replace(':','') airshift_shift_start = airshift_shift[:4] airshift_shift_end = airshift_shift[4:] else: #シフトに入ってないのに、出勤した人 airshift_shift = '' #break_time他加工処理 attend_time = str(attend_times[x]).replace('<td>','') attend_time = str(attend_time).replace('</td>','') attend_time = str(attend_time).replace(':','') #30分単位変換処理 if 0 < int(attend_time) % 100 <= 30: attend_time = attend_time[:-2] attend_time += '30' elif 30 < int(attend_time) % 100: attend_time = attend_time[:-2] attend_time += '00' attend_time = int(attend_time) + 100 attend_time = str(attend_time) leave_time = str(leave_times[x]).replace('<td>','') leave_time = str(leave_time).replace('</td>','') leave_time = str(leave_time).replace(':','') if leave_time == '': #退勤時間を打刻しわすれた時の処理 leave_time = str('2400') notices.append(airshift_name + 'の退勤時間の打刻がされていないため、ダミーデータで24:00を入力しています') #30分単位変換処理 if 0 <= int(leave_time) % 100 < 30: leave_time = leave_time[:-2] leave_time += '00' else: leave_time = leave_time[:-2] leave_time += '30' break_time = str(break_times[x]).replace('<td>','') break_time = str(break_time).replace('</td>','') break_time = str(break_time).replace(':','') #30分単位変換処理 if break_time != '': if 0 < int(break_time) % 100 <= 30: break_time = break_time[:-2] break_time += '30' elif 30 < int(break_time) % 100: break_time = break_time[:-2] break_time += '00' break_time = int(break_time) + 100 break_time = str(break_time) #出勤時間入力処理 start_xpath = "//tbody/tr[position()="+ str(i) +"]/td[position()=6]/div[position()=1]/span/input" start = driver.find_element(By.XPATH,start_xpath) if break_time == '' and int(leave_time) - int(attend_time) > 500: #5時間より長く働いた場合 assumption_attend_time = int(attend_time) - 100 assumption_attend_time = str(assumption_attend_time) start.send_keys(assumption_attend_time) print(airshift_name + 'に出勤時間' + assumption_attend_time + 'を入力しました') else: start.send_keys(attend_time) print(airshift_name + 'に出勤時間' + attend_time + 'を入力しました') if int(attend_time) != int(airshift_shift_start): notices.append(airshift_name + 'の出勤時間のデータに相違があります') #退勤時間入力処理 end_xpath = "//tbody/tr[position()="+ str(i) +"]/td[position()=6]/div[position()=2]/span/input" end = driver.find_element(By.XPATH,end_xpath) if int(leave_time) >= 2400: #2430などの値は不適切と判断されるため leave_time_r = int(leave_time) % 2400 leave_time_r = str(leave_time_r) end.send_keys(leave_time_r) if int(leave_time_r) != int(airshift_shift_end): notices.append(airshift_name + 'の退勤時間のデータに相違があります') else: end.send_keys(leave_time) if int(leave_time) != int(airshift_shift_end): notices.append(airshift_name + 'の退勤時間のデータに相違があります') print(airshift_name + 'に退勤時間' + leave_time + 'を入力しました') #休憩時間に関する処理 #休憩開始時間のフォーム b_start_xpath = "//tbody/tr[position()="+ str(i) +"]/td[position()=7]/div/div[position()=1]/span/input" b_start = driver.find_element(By.XPATH,b_start_xpath) #休憩終了時間のフォーム b_end_xpath = "//tbody/tr[position()="+ str(i) +"]/td[position()=7]/div/div[position()=2]/span/input" b_end = driver.find_element(By.XPATH,b_end_xpath) if break_time != '' and airshift_shift_break_start != '': #シフト上休憩があり、実際に休憩した時 b_start.send_keys(airshift_shift_break_start) print(airshift_name + 'に休憩開始時間' + airshift_shift_break_start + 'を入力しました') break_time_end = int(airshift_shift_break_start) + int(break_time) if str(break_time_end)[-2:] == '60': #XX60の形になってしまった場合 break_time_end = break_time_end[:-2] break_time_end += '00' break_time_end = int(break_time_end) + 100 break_time_end = str(break_time_end) b_end.send_keys(break_time_end) print(airshift_name + 'に休憩終了時間' + str(break_time_end) + 'を入力しました') if int(break_time) != int(airshift_shift_break_end) - int(airshift_shift_break_start): notices.append(airshift_name + 'の休憩した時間のデータに相違があります') elif break_time != '' and airshift_shift_break_start == '': #シフト上休憩がなかったが、実際休憩した時 b_start.send_keys(attend_time) print(airshift_name + 'に休憩開始時間' + attend_time + 'を入力しました(仮入力で、出勤時間と休憩開始時間を被らせています)') break_time_end = int(attend_time) + int(break_time) if str(break_time_end)[-2:] == '60': #XX60の形になってしまった場合 break_time_end = break_time_end[:-2] break_time_end += '00' break_time_end = int(break_time_end) + 100 break_time_end = str(break_time_end) b_end.send_keys(break_time_end) print(airshift_name + 'に休憩終了時間' + str(break_time_end) + 'を入力しました') elif break_time == '' and int(leave_time) - int(attend_time) > 500: #5時間より長く働いた場合 b_start.send_keys(assumption_attend_time) print(airshift_name + 'に休憩開始時間' + assumption_attend_time + 'を入力しました') b_end.send_keys(attend_time) print(airshift_name + 'に休憩終了時間' + attend_time + 'を入力しました※5時間より長く働いていてかつ休憩をとっていないため、出勤時間を1時間早め、1時間の休憩をしたことにしました') break i += 1 x += 1 button = "//div[@id='centerSpacer']//div[@class='root___F-opJFUK']/div[2]/button" sleep(1) driver.find_element(By.XPATH,button).click() print('実績管理より抽出した勤務時間をairshiftに保存中...') #エラー・相違があったら配列にぶち込んでループ処理で出力 if notices != []: print('!!!注意!確認してください!!!') for notice in notices: print(notice) # ブラウザを終了する driver.close() driver.quit() print('処理が正常に完了しました') ざっと300行ぐらいになってしまった。。。 んでこれを実行するとこんな感じ $ python3 rpa.py connectiong to remote browser... https://[甲]/loginに接続しています... HPログイン中... 取得したい日付を入力してください(例)20220101:20220114 20220114の実績管理に移動中... https://[乙]/login?[省略]に接続しています [スタッフA]に出勤時間1030を入力しました [スタッフA]に退勤時間2330を入力しました [スタッフA]に休憩開始時間1500を入力しました [スタッフA]に休憩終了時間1730を入力しました 別店舗ヘルプのため[スタッフE]の入力をスキップします [スタッフC]に出勤時間1100を入力しました [スタッフC]に退勤時間2400を入力しました [スタッフC]に休憩開始時間1500を入力しました [スタッフC]に休憩終了時間1700を入力しました [スタッフG]に出勤時間1700を入力しました [スタッフG]に退勤時間2400を入力しました [スタッフG]に休憩開始時間1700を入力しました [スタッフG]に休憩終了時間1800を入力しました※5時間より長く働いていてかつ休憩をとっていないため、出勤時間を1時間早め、1時間の休憩をしたことにしました [スタッフF]に出勤時間1800を入力しました [スタッフF]に退勤時間2400を入力しました 実績管理より抽出した勤務時間をairshiftに保存中... !!!注意!確認してください!!! [スタッフA]の退勤時間のデータに相違があります [スタッフA]の休憩した時間のデータに相違があります [スタッフF]の退勤時間の打刻がされていないため、ダミーデータで24:00を入力しています [スタッフF]の退勤時間のデータに相違があります 処理が正常に完了しました 乙(Airshift)に保存されたデータをみてもうまく行ってそう。 苦労した点や工夫した点 基本的に、データをもとに判別してそれに応じて処理を変えていく流れは、様々なパターンを考慮しなければならなかったからかなり工夫した。 特に以下に関しての判別 ・乙上シフトに登録されてないのに出勤した人がいる場合 ・他店舗にヘルプへ行った人がいる場合(乙上には登録してはいけない) if help_store != '': #ヘルプ店舗の場合スキップ x += 1 print('別店舗ヘルプのため'+ name +'の入力をスキップします') continue ・甲上には休憩開始時間の記載がされないため、乙上のデータに基づいて休憩開始時間を予測する if break_time != '' and airshift_shift_break_start != '': #シフト上休憩があり、実際に休憩した時 b_start.send_keys(airshift_shift_break_start) print(airshift_name + 'に休憩開始時間' + airshift_shift_break_start + 'を入力しました') break_time_end = int(airshift_shift_break_start) + int(break_time) ・5時間より長く働いたが、休憩をしなかった人は、出勤時間を1時間早めてそこから1時間休憩したことにする処理 #休憩時間処理 if break_time == '' and int(leave_time) - int(attend_time) > 500: #5時間より長く働いた場合 assumption_attend_time = int(attend_time) - 100 assumption_attend_time = str(assumption_attend_time) start.send_keys(assumption_attend_time) print(airshift_name + 'に出勤時間' + assumption_attend_time + 'を入力しました') #出勤時間処理 elif break_time == '' and int(leave_time) - int(attend_time) > 500: #5時間より長く働いた場合 b_start.send_keys(assumption_attend_time) print(airshift_name + 'に休憩開始時間' + assumption_attend_time + 'を入力しました') b_end.send_keys(attend_time) print(airshift_name + 'に休憩終了時間' + attend_time + 'を入力しました※5時間より長く働いていてかつ休憩をとっていないため、出勤時間を1時間早め、1時間の休憩をしたことにしました') ・30分単位の給料計算のため、超過分を切り捨てる処理 (出勤退勤休憩時間に適用した。以下は休憩時間を引用) if 0 < int(break_time) % 100 <= 30: break_time = break_time[:-2] break_time += '30' elif 30 < int(break_time) % 100: break_time = break_time[:-2] break_time += '00' break_time = int(break_time) + 100 break_time = str(break_time) 等々!! その他2430など、2400より大きいデータは、甲において不適切なデータとして弾かれるため、そこを突破するための処理や、 if int(leave_time) >= 2400: #2430などの値は不適切と判断されるため leave_time_r = int(leave_time) % 2400 leave_time_r = str(leave_time_r) end.send_keys(leave_time_r) シフトのデータと違う場合のnoticesの出力や、退勤の打刻し忘れた場合にダミーでデータを入れてそれをnoticesに格納することで、 甲に手動で打刻する際のヒューマンエラーの可能性のあるデータを自動で知らせる処理など... #エラー・相違があったら配列にぶち込んでループ処理で出力 if notices != []: print('!!!注意!確認してください!!!') for notice in notices: print(notice) まとめ やっぱり自分で作ったものが綺麗に動いているのがおもしろい。 pythonを触り始めて1週間たたずに作ろうと思って20時間くらいかかってしまったけど、初めてにしては上出来だと思い込むことにした。 と、気づきなんだけど、以前はバックエンドはほとんどPHPしか触ったことなかったけど、一個言語ある程度覚えれば他は大体一緒っていうのは間違いじゃないんだなと思った。事前に蓄えた知識がちょっと活きた!と思う。 cssのセレクタを扱う際も、昔ちょこっとやったjQueryでやったことが活きたんじゃないかなぁ(結局ほとんどXPATHを使ったけど)。 あとは冗長なコードにならないようにしたりオブジェクト指向を意識して書いたりできるようになればってのが課題。。。 以上、代理店長私のちょっと長い呟きでした。何かお気づきの点などありましたら、ご教授いただけると嬉しいです。 github: https://github.com/ugkajiwara/attendance_management_rpa
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

poetry installが動いているか確認したい

この記事について Poetryで、python仮想環境を作ろうとしたときに、下記の表示で止まっていてインストールが動いているか分からない。 もうチョット詳細なログを出して、インストールが動いていることを確認したい。 $ poetry install Using virtualenv: /hoge/foo/bar/.venv Updating dependencies Resolving dependencies... 確認方法 verboseすればよい ざっくりverbose(for normal output) $ poetry install -v 細かくverbose(for more verbose output) $ poetry install -vv もっと細かくverbose(for debug) $ poetry install -vvv まとめ 細かく見たい時は、-vvvする。(vvvは少し笑ったww) 小文字(v)で、大文字(V)はバージョン確認なので注意 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OpenCVでRGB画像と2値化画像を連結させて表示したい

OpenCVでの画像連結方法 OpenCVでは複数の画像を連結させたい時は、cv2.hconcat/vconcat関数を用います。例として、以下のコードのように記述します。 main.py import cv2 img1 = cv2.imread("img1.jpg") img2 = cv2.imread("img2.jpg") # サイズをそろえる window_size = (400,400) img1 = cv2.resize(img1, window_size) img2 = cv2.resize(img2, window_size) # 画像を横に連結 img = cv2.hconcat([img1,img2]) # img = cv2.vconcat([img1,img2]) # 画像を縦に連結する場合 # 画像を表示 while True: cv2.imshow("img",img) if cv2.waitKey(1) == ord('q'): break cv2.destroyAllWindows() ここで、ある画像のRGB画像と2値化画像を連結させようとします。 コードは以下のようになると思います。 main.py # 注:これは間違ったコードです。 import cv2 # 2値化閾値 th = 100 img1 = cv2.imread("img1.jpg") img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) img1_binary = cv2.threshold(img_gray, th, 255, cv2.THRESH_BINARY) # サイズをそろえる window_size = (400,400) img1 = cv2.resize(img1, window_size) img1_binary = cv2.resize(img2, window_size) # 画像を横に連結 img = cv2.hconcat([img1,img1_binary]) # 画像を表示 while True: cv2.imshow("img",img) if cv2.waitKey(1) == ord('q'): break cv2.destroyAllWindows() 上のコードを実行すると次のようなエラーが出ます cv2.error: OpenCV(4.5.3) C:\Users\runneradmin\AppData\Local\Temp\pip-req-build-1i5nllza\opencv\modules\core\src\matrix_operations.cpp:67: error: (-215:Assertion failed) src[i].dims <= 2 && src[i].rows == src[0].rows && src[i].type() == src[0].type() in function 'cv::hconcat' このエラーは、異なるタイプの画像を連結させようとしたために生じたものです。よって画像のタイプを合わせます。例えば連結させる直前に次のコードを記述することで2値化画像をRGB画像として変換させます。 img1_binary = cv2.cvtColor(img1_binary, cv2.COLOR_GRAY2BGR) すなわち、先ほどの「間違ったコード」を正しく修正したものは以下のようになります。 main.py import cv2 # 2値化閾値 th = 100 img1 = cv2.imread("img1.jpg") img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) img1_binary = cv2.threshold(img_gray, th, 255, cv2.THRESH_BINARY) # 画像のタイプを揃える img1_binary = cv2.cvtColor(img1_binary, cv2.COLOR_GRAY2BGR) # ←追加 # サイズをそろえる window_size = (400,400) img1 = cv2.resize(img1, window_size) img1_binary = cv2.resize(img2, window_size) # 画像を横に連結 img = cv2.hconcat([img1,img1_binary]) # 画像を表示 while True: cv2.imshow("img",img) if cv2.waitKey(1) == ord('q'): break cv2.destroyAllWindows()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MacのPythonで音声データを扱う:PyAudio環境構築メモ

PythonでAudioデータを扱いたくなったのでプログラミングを始めようと思いました。 環境構築に思ったより時間がかかったので、メモとして残します。 開発環境 ・Mac(Mac Book Air 2013) ・MacOS Big Sur(11.5) ・Python 3.9.1 ・pip 21.3.1 目的 Audioファイル入力、外部入力音声を利用して、加工後の音声データを音声出力、ファイル出力したいと考えています。 わかったこと ・Audioデータの録音と再生にはPyAudioモジュールが使える ・PyAudioモジュールは、PortAudioというソフトのPython用インターフェース ・要するに、PortAudioを入れて、その後PyAudioを入れると良さそう 参考にしたページ %brew install portaudio 実行するとエラーでした。ログを残せていないのですが、HomeBrewの更新が必要と判断。 %brew upgrade またまたエラー。以下のページと同様の状態だったはずです。 とても参考になりました。ありがとうございます。 https://techracho.bpsinc.jp/wingdoor/2021_04_09/104821 OS更新は流石に時間がかかりすぎるので、コマンドラインツールだけ更新します。 %sudo rm -rf /Library/Developer/CommandLineTools %sudo xcode-select --install ようやくPortAudioを導入できそうです。 % brew install portaudio 取得に失敗したのか、ソースからビルドしたようでした。なんとか入りました。 いよいよPyAudioです。 % pip install pyaudio Collecting pyaudio Using cached PyAudio-0.2.11.tar.gz (37 kB) Preparing metadata (setup.py) ... done Building wheels for collected packages: pyaudio Building wheel for pyaudio (setup.py) ... done Created wheel for pyaudio: filename=PyAudio-0.2.11-cp39-cp39-macosx_10_15_x86_64.whl size=23691 sha256=141ea65aa52f3d6f8e74d9f9c7b0ddb2dee65dad1d0ba83a75111a6a642da89d Stored in directory: /Users/hogehoge/Library/Caches/pip/wheels/76/e7/d6/193c174cc4cba9409e8eecea8f9e986fc9c88e08160759dfe8 Successfully built pyaudio Installing collected packages: pyaudio Successfully installed pyaudio-0.2.11 ようやく入りました。 なかなか時間がかかりました。 開発ツールはこまめに更新を確認して適時反映しておかないと このように苦労することになりますね。 Dockerなどの意義も身に染みて理解できました。 今回はここまで。ようやく音声をつかったプログラミングができそうです。 以下は本家のリンク
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

bottleでresponseを返すとき日本語を含めたい場合

bottleでresponseを返すとき日本語を含めたい場合 問題の背景 pythonのフレームワークにbottleというのがある。 これは非常に軽量なフレームワークであるのだが、 これを使っているときにちょっと日本語まわりの取り扱いで問題が生じたので解決方法を備忘録としてメモ。 余談だがこれはpython3系にバージョンアップするときに発生した問題。 python2系なら問題なく動いたのだが、 3系にあげるときに読み込むライブラリ等も諸々アップデートしたところ発生した。 そのため正直直接的に何が関係してこれが発生したのかは断言できない、悪しからず。 環境 ubuntu 16.04(諸事情でちょっと古い) python3.9.9 bottle=0.12.9 gunicorn=20.1.0 発生した事象 set_cookieを用いて日本語文字列をcookieに登録しページを表示しようとしたところ、 from bottle import response user_name = '管理者' response.set_cookie('user_name', user_name, max_age=26784000) 以下のようなエラーが発生した。 UnicodeEncodeError: 'latin-1' codec can't encode characters in position 349-351: ordinal not in range(256) エンコードの問題か!ならencodeすればええ!と安直に思ったけど、 response.set_cookie('user_name', user_account.user_name.encode('utf-8'), max_age=26784000) 結果は TypeError: Secret key missing for non-string Cookie. stringじゃないもの(byte形式とか)はcookieに登録できませんと。 対応手法 厳密には解決したわけではないので対応としているが、 以下のようにURLエンコードをかけてパースすることで対応した。 response.set_cookie('user_name', urllib.parse.quote(user_account.user_name), max_age=26784000) URLエンコードなら比較的メジャーであるので対応できる。 いちいちデコードしなければならない手間はあるが、致し方ない。 ついでなのでもう1パターン こちらはファイルをダウンロードする時、ファイル名に日本語を含めたいとき。 例えばKMLの情報がDBに保存されており、それを取得してファイルとして返却する、 こんなソースコードがあったとする。(一部簡略化) with self.session() as s: mime_type = 'application/vnd.google-earth.kml+xml' # DBに保存してあるKMLファイルの情報を取得 geo_layer = s.query(GeoLayer).filter_by(id=kml_id).first() # httpresponseインスタンスを生成(予めimport済) resp = HTTPResponse(status=200, body=geo_layer.content) resp.set_header('Content-Type', mime_type) resp.set_header('Content-Length', str(geo_layer.file_size)) fname = geo_layer.name + '.kml' # このnameに日本語が入っている resp.set_header('Content-Disposition', 'attachment; filename="%s"' % fname) return resp このままダウンロードしようとすると、 UnicodeEncodeError: 'latin-1' codec can't encode characters in position 209-211: ordinal not in range(256) さっきも見たよあんた! ってなる。 そこで後ろから2行目、fnameをパースしてやるとうまくいく。 これより前で予め変換してあげても問題ない。 resp.set_header('Content-Disposition', 'attachment; filename="%s"' % urllib.parse.quote(fname)) ダウンロード時はしっかり日本語名に変換され、無事ダウンロードに対応完了。 ちなみに このエラーのTracebackを見てみるとgunicorn由来であることがわかる。 Traceback (most recent call last): File "/usr/local/rakutoban-web-viewer/.venv/lib/python3.9/site-packages/gunicorn/workers/sync.py", line 136, in handle self.handle_request(listener, req, client, addr) File "/usr/local/rakutoban-web-viewer/.venv/lib/python3.9/site-packages/gunicorn/workers/sync.py", line 185, in handle_request resp.write(item) File "/usr/local/rakutoban-web-viewer/.venv/lib/python3.9/site-packages/gunicorn/http/wsgi.py", line 326, in write self.send_headers() File "/usr/local/rakutoban-web-viewer/.venv/lib/python3.9/site-packages/gunicorn/http/wsgi.py", line 322, in send_headers util.write(self.sock, util.to_bytestring(header_str, "latin-1")) File "/usr/local/rakutoban-web-viewer/.venv/lib/python3.9/site-packages/gunicorn/util.py", line 565, in to_bytestring return value.encode(encoding) UnicodeEncodeError: 'latin-1' codec can't encode characters in position 209-211: ordinal not in range(256 pythonを2系から3系にバージョンアップする作業をした時にgunicornのバージョンも上げた。 様々な要因が重なって、めちゃめちゃ調べても全く引っかからないようなエラーに出くわしたのかもしれない。 同じような境遇に出会う人は稀だろうけど、一応対応策として残しておく。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超音波センサーで距離を測ってみた

秋月から超音波センサーが届いたので、下記の記事を参考に作成してみた。 ブレッドボードより基板使った方がかっこいいかなと思い、久ぶりに電子工作工作してみた。 完成したのがこちら。半田に手こずったし、機材そろえるのに少しお金かかったけど、手を動かすと楽しい。。 回路図 HC-SR04のECHOから出力は、5Vが出てくる。ラズパイのGPIOは、3.3Vまでしか許容しないため、抵抗を使って3.3Vにしている。 code TRIG,ECHOの指定ができれば、DistanceSensorを使えばm単位で結果を出力される。 1.1 pigpio Raspberry PiのGPIOを制御するため、再起動して有効になるようにsystemctlのenableをonにした。 $ sudo service pigpiod start $ sudo systemctl enable pigpiod.service 1.2 python 測定誤差が生じるため、10回測定しそのmedian値を結果に出力するようにした。 単位は、cmにしている。 hc_cr04.py from gpiozero import DistanceSensor from gpiozero.pins.pigpio import PiGPIOFactory import statistics import math import time PIN_TRIG = 22 PIN_ECHO = 27 point = 10 tmp = [num for num in range(point)] factory = PiGPIOFactory() sensor = DistanceSensor(PIN_ECHO, PIN_TRIG, pin_factory=factory) for i in range(point): print('Distance to nearest object is', sensor.distance, 'm') tmp[i] = sensor.distance * 100 time.sleep(0.5) median = statistics.median(tmp) print('Distance to nearest object is', '{:.2f}'.format(median), 'cm(median', point ,'point)') 感想 ひとまず超音波センサーを使って距離を測定することができるようになった。電子工作をしてみて手を動かすのは、難しいけど、信号が出なかったときにどこが原因か考えて答えを見つける楽しさがある。テスターとオシロスコープ使うような複雑なことやれると、もう少し面白くなりそう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Qiitaとはてなブログの記事のソース(Markdown)を取得するキーボードショートカット

背景 QiitaやはてなブログではMarkdownで記事がかけます。Markdownエディタ(Obsidian)をつかっているので、エディタの保管庫(Vault)に記事のソース(Markdown)を貼り付けておけると便利だろうなとおもうのです。 Markdownは軽量なので、エディタの保管庫にデータを持っていても軽快に動きます。エディタの検索機能で過去に書いた記事が検索できると、自分用ナレッジデータベースとしてはとても便利になりそうです。 なのですが、ソースMarkdownを取得するのに、手を動かさなければならなくて、面倒臭いなあとおもっていました。面倒くさいことはやらなくなります。抜け漏れが出てくると、データベースとしてはイマイチになってしまいます。 そこで、キーボードショートカットで、さっくりとソースMarkdownを取得できるようなスクリプトを書きました。 環境 ここで書いていることは、下記のバージョンで実施しました。 Alfred 4.6.1 VS Code 1.63.2 macOS Monterey 12.1 MacBook Pro (13-inch, 2020, Four Thunderbolt 3 Ports) 概要 操作の概要 ↓ option + command + Eを押すと といったふうに[記事タイトル][記事URL]と記事のソースMarkdownを、クリップボードに取得します。 スクリプトの概要 Alfredを利用して、option + command + Eで起動します 選択した部分がQiitaなら、curlコマンドでソースを取得します(シェルスクリプト) 選択した部分がはてなブログなら、はてなブログのAPIを叩いてソースを取得します(Pythonスクリプト) 詳細 Alfred Workflowの構成 4つのブロックで構成しています。 トリガー(option + command + E) スクリプトの実行 画面への出力(即時確認用) クリップボードへのコピー 以下にそれぞれのブロックについて補足します。 トリガー Hotkeyにキーボードショートカットを登録します ArgumentをSelection in macOSに変更しています。選択した範囲が、次のブロックのクエリで使えます スクリプトの実行 Languageをzshにしています。べつにbashでもいいですがなんとなく with input as {query}を選択しています。スクリプト内で{query}と書くと、macOSで選択した部分を使えます(今回の場合はURLを想定) Qiitaの場合 # 変数・定数をセット url="{query}" qiita="qiita.com" hatena="hatenablog.com" suffix=".md" # はてブロの場合とQiitaの場合で処理をわけた if [[ "$url" == *$hatena* ]]; then /usr/local/bin/python3.9 220117_copyHatenaSource.py $url elif [[ "$url" == *$qiita* ]]; then curl -s $url | grep -o '<title>.*</title>' | sed 's#<title>\(.*\)</title>#\1#' | pbcopy title=`pbpaste` echo "[$title]($url)\n" curl "$url$suffix" else exit fi 選択した範囲のURLがはてなブログであるなら、$url(macOSでの選択範囲)を引数にして、Pythonスクリプトを起動します → Pythonスクリプトは後述します 選択した範囲のURLがQiitaなら、シェルコマンドを走らせます curl -s $url | grep -o '<title>.*</title>' | sed 's#<title>\(.*\)</title>#\1#' | pbcopy title=`pbpaste` echo "[$title]($url)\n" curl "$url$suffix" まず1つ目のcurlでタイトルを取得します <title>タグで囲まれているところをgrepで抽出し、sedでタグを消してます 改行が含まれているとダメとか、[]が含まれているとダメとかありますが、ここではあまり細かいことは気にしません(それぐらいは手で直せばよいのではという考え) [記事タイトル](記事URL)を標準出力に吐いておきます Qiitaのばあいは、記事URL.mdでMarkdownが取れます。2つ目のcurlでMarkdownを取得しています curlでタイトルを抜き出す方法は、下記の記事を参考にさせていただきました(なかでも簡易な方法を使いました) HTMLコンテンツをcurlで取得して特定タグ内だけ抜き出す - Qiita ウェブページのタイトルをターミナルから取得する方法 Bash で HTML のタイトルを取得する - Sarchitect はてなブログの場合 /usr/local/bin/python3.9 220117_copyHatenaSource.py $url のところで、Pythonスクリプトを起動しています(引数にはmacOSの選択範囲)。 スクリプトは、Alfred Workflowのフォルダに入れておきます(Workflowリストを右クリック > Open in Finder...が便利)。 スクリプトの内容は下記のとおりです。 #!/usr/local/bin python3.9 # -*- coding: utf-8 -*- import sys import requests import bs4 import re import time # コレクションURIを取得 def get_collection_uri(hatena_id, blog_id, password): service_doc_uri = "https://blog.hatena.ne.jp/{hatena_id:}/{blog_id:}/atom".format(hatena_id=hatena_id, blog_id=blog_id) res_service_doc = requests.get(url=service_doc_uri, auth=(hatena_id, password)) if res_service_doc.ok: soup_servicedoc_xml = bs4.BeautifulSoup(res_service_doc.content, features="xml") collection_uri = soup_servicedoc_xml.collection.get("href") return collection_uri return False # エントリIDを指定し、contentを取得 def get_entry_content_str(hatena_id, blog_id, password, entry_id): member_uri = "https://blog.hatena.ne.jp/{hatena_id}/{blog_id}/atom/entry/{entry_id}".format(hatena_id=hatena_id, blog_id=blog_id,entry_id=entry_id) res_member = requests.get(member_uri, auth=(hatena_id, password)) if not res_member.ok: print("Failed: status_code: " + str(res_member.status_code)) return False soup_response_xml = bs4.BeautifulSoup(res_member.content, features="xml") return soup_response_xml.find("content").string # エントリIDを指定し、タイトルを取得 def get_entry_title_str(hatena_id, blog_id, password, entry_id): member_uri = "https://blog.hatena.ne.jp/{hatena_id}/{blog_id}/atom/entry/{entry_id}".format(hatena_id=hatena_id, blog_id=blog_id,entry_id=entry_id) res_member = requests.get(member_uri, auth=(hatena_id, password)) if not res_member.ok: print("Failed: status_code: " + str(res_member.status_code)) return False soup_response_xml = bs4.BeautifulSoup(res_member.content, features="xml") return soup_response_xml.find("title").string # エントリIDを指定し、URL(link)を取得 def get_entry_link_str(hatena_id, blog_id, password, entry_id): member_uri = "https://blog.hatena.ne.jp/{hatena_id}/{blog_id}/atom/entry/{entry_id}".format(hatena_id=hatena_id, blog_id=blog_id,entry_id=entry_id) res_member = requests.get(member_uri, auth=(hatena_id, password)) if not res_member.ok: print("Failed: status_code: " + str(res_member.status_code)) return False soup_response_xml = bs4.BeautifulSoup(res_member.content, features="xml") return soup_response_xml.find("link", rel="alternate").get("href") hatena_id="<hatena_id>" blog_id="<blog_id>" password="<password>" collection_uri = get_collection_uri(hatena_id, blog_id, password) entry_id_list = [] # 記事一覧を2回(過去記事を20件)取得し、エントリIDのリストを作成 # NOTE: したがって古い記事は取得できない MAX_ITERATER_NUM = 2 for i in range(MAX_ITERATER_NUM): # Basic認証で記事一覧を取得 res_collection = requests.get(collection_uri, auth=(hatena_id, password)) if not res_collection.ok: print("faild") continue # Beatifulsoup4でDOM化 soup_collectino_xml = bs4.BeautifulSoup(res_collection.content, features="xml") # entry elementのlistを取得 entries = soup_collectino_xml.find_all("entry") # 下書きを無視 pub_entry_list = list(filter(lambda e: e.find("app:draft").string != "yes", entries)) # entry idを取得 entry_id_list.extend([re.search(r"-(\d+)$", string=e.id.string).group(1) for e in pub_entry_list]) # next link_next = soup_collectino_xml.find("link", rel="next") if not link_next: break collection_uri = link_next.get("href") if not collection_uri: break time.sleep(0.01)# 10ms # 引数で指定したURLに合致するエントリがあれば、MD形式のリンクとソーステキストを出力 for entry_id in entry_id_list: # print (entry_id) link = get_entry_link_str(hatena_id, blog_id, password, entry_id) # print (entry_id, link, sys.argv[1]) if link == sys.argv[1]: title = get_entry_title_str(hatena_id, blog_id, password, entry_id) content = get_entry_content_str(hatena_id, blog_id, password, entry_id) print ("[" + title + "](" + link + ")\n" ) print ("---\n") print ("# " + title + "\n") print (content) break モジュールが必要なので、下記コマンドでインストールします。 $ python3.9 -m pip install requests $ python3.9 -m pip install beautifulsoup4 処理の大まかな流れは下記のとおりです。 コレクションURIを取得 記事一覧(最新10件)を取得し、そのなかからエントリIDを取得(10件) 次の10件の記事を取得し、エントリIDを取得(合計20件) 20件の記事のうち、macOSで選択したURLと一致する記事を探す もし見つかれば、ソースを取得して標準出力に吐く あまり古い記事を探しに行かないという前提で、記事の取得は20件で止めています。もっと過去の記事を取得する場合は、個別に対応するつもりで書いています。また、パフォーマンス的にベストの方法ではないとおもうんですが、記事の取得件数を抑えているので、改善せずここで止めています。といったところはジェネラルなコードになっていないのでご留意ください。 はてなブログのAPIを叩く方法は、下記の記事を参考にさせていただきました。 はてなブログAtomPub - Hatena Developer Center はてなブログAPIで記事一覧を取得 はてなブログAPIで記事の取得と編集 また、macOSのPythonは鬼門の方向ですので、macOS環境のPythonを参考にさせていただきました。 macOSに入っているpythonを使おうとするな Homebrewでインストールせよ pip3でなくてpython3.9 -m pip xxxとせよ venvなど仮想環境で動かせ とのことです。仮想環境はつかいませんでしたが、モジュールのインストールには$ python3.9 -m pip install xxxをつかいました。 画面への出力 AlfredのLarge Typeというモジュールが便利なのでつかいました。スクリプトが標準出力に吐いたテキストを、画面に表示します。もともとはデバッグ用なのですけれども、さくっとプレビューできるのは便利なので、消さずに残しています。動作が安定して、ぜったい要らんわとなったら、消そうとおもいます。 クリップボードへのコピー 標準出力に吐いたテキストを、クリップボードにコピーします。エディタ(Obsidian)にペーストすれば一連のタスクは終了です。 おわりに Qiitaとはてなブログにしか対応していない 選択範囲が適切か否かなど、エラーハンドリングはしていない はてなブログは最近20件の記事からしか取得できない などとおよそジェネリックとは言えないスクリプトですけれども、実運用上はそこそこいい感じかなとおもいます。「判断が複雑なところは人間がやって、単純作業はコンピュータがやる」の比率が、まあまあいいところに落ち着いたのではないかなあと。しばらく使ってみて、手に馴染むかどうか、たしかめてみたいとおもいます。 ご参考 Obsidian|使いかたとコツ(目次)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Project Euler】Problem 34: 各桁の階乗の和

本記事はProjectEulerの「100番以下の問題の説明は記載可能」という規定に基づいて回答のヒントが書かれていますので、自分である程度考えてみてから読まれることをお勧めします。 問題 34:各桁の階乗の和 原文 Problem 34: Digit factorials 問題の要約:各桁の階乗の和が元の数と同じになる数の合計を求めよ これも上限が問題になりますが、7桁を超えると全桁が9でも階乗の和は7桁にならないので$9! \times 7$まで調べればOKです。 import math fct = [math.factorial(d) for d in range(10)] def digFactSum(n): return sum(fct[int(d)] for d in str(n)) print(f"Answer = {sum([n for n in range(11,fct[9]*7) if n == digFactSum(n)])}") (開発環境:Google Colab)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Project Euler】Problem 33: 桁を消して約分

本記事はProjectEulerの「100番以下の問題の説明は記載可能」という規定に基づいて回答のヒントが書かれていますので、自分である程度考えてみてから読まれることをお勧めします。 問題 33:桁を消して約分 原文 Problem 33: Digit cancelling fractions 問題の要約:以下の例のように1より小さい分子・分母ともに2桁の分数の共通の1つの数字を消したときに約分になっているような分数(4つある)の積を約分したときの分母を求めよ \frac{4 \underline{9}}{\underline{9}8}=\frac{4}{8} 条件を満たす分数は以下のような形をしているので、$n,d,i$を$n<d$の条件で全探索します。答えは4つの分数の積を約分した分母なので、pythonのFractionモジュールを使って自動的に約分しました。 \frac{10n+i}{10i+d} = \frac{n}{d} \ \ \ \ (n<d) import itertools from fractions import Fraction prd = Fraction(1) for n, d in itertools.combinations(range(1,10),2): for i in range(1,10): if (10*n+i)*d == (10*i+d)*n: print((10*n+i),(10*i+d), n, d ) prd *= Fraction(n,d) print(f"Answer is {prd.denominator}, (prodct={prd})") #16 64 1 4 #19 95 1 5 #26 65 2 5 #49 98 4 8 #Answer is 100, (prodct=1/100) (開発環境:Google Colab)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

小説家になろう ブクマ数予測 ~”伸びる”タイトルとは?~ 3rd place solution & 振り返り

Nishika株式会社が主催「小説家になろう ブクマ数予測 ~”伸びる”タイトルとは?~」コンペに参加しました。本記事で私の取り組みの共有と振り返りをしたいと思います。 コンペ概要 本コンペでは、日本最大級の小説投稿サイトである「小説家になろう」のデータを用いて、ジャンルや作者名などの関連データから各小説のブックマーク数を予測することをテーマとします。予測対象となるブックマーク数を5段階にビニングし、予測確率とのMulti-class loglossが評価指標とされています。 コンペ結果 最終ランキングでは参加者575人中3位で入賞させていただきました。 データ 小説名・あらすじのようなテキスト特徴量とジャンル・キーワードのような非テキスト特徴量を含むデータセットになります。 説明 ncode 小説のNコード general_firstup 初回掲載日 title 小説名 story 小説のあらすじ keyword キーワード userid 作者のユーザID(数値) writer 作者名 biggenre 大ジャンル genre ジャンル novel_type 連載の場合は1、短編の場合は2 end 短編小説と完結済小説は0となっています。連載中は1 isstop 長期連載停止中なら1、それ以外は0 isr15 登録必須キーワードに「R15」が含まれる場合は1、それ以外は0 isbl 登録必須キーワードに「ボーイズラブ」が含まれる場合は1、それ以外は0 isgl 登録必須キーワードに「ガールズラブ」が含まれる場合は1、それ以外は0 iszankoku 登録必須キーワードに「残酷な描写あり」が含まれる場合は1、それ以外は0 istensei 登録必須キーワードに「異世界転生」が含まれる場合は1、それ以外は0 istenni 登録必須キーワードに「異世界転移」が含まれる場合は1、それ以外は0 pc_or_k 1はケータイのみ、2はPCのみ、3はPCとケータイで投稿された作品 fav_novel_cnt_bin ブックマーク度(目的変数) 解法 全体の考え方は以前Nishika様が主催しました「 AIは芥川龍之介を見分けられるのか?」コンペの2nd Solution を踏襲していました。 LightGBMを用いて特徴量選定を行い、実際の分類モデルではLightGBM、CatBoostとBERTを使いました。最終的に各モデル出力の重み付き幾何平均を利用し、後処理では5-foldのCVスコアを参照として各クラスの予測確率をn乗(n=0.95~1.00)する処理を加えました。 データセットに対するEDAは下記他の方の記事で詳しく説明されましたため、今回では独自の取り組みを中心に紹介したいと思います。 ・【AI Shift Advent Calendar 2021】Nishikaコンペ振り返り 小説家になろう ブクマ数予測 ~”伸びる”タイトルとは?~ ・Nishikaコンペ参加記録 小説家になろう ブクマ数予測 ~”伸びる”タイトルとは?~ Trust LB/CV ? Tips and tricks to win kaggle data science competitions において、時系列データコンペにおけるリーダーボードのスコアを信用する/信用しないパターンを分析しました。今回のコンペにおいて、学習データは2021/08/12の8時より前のデータで、PublicとPrivateはそれ以降のデータからサンプリングしたもののため、以下の図の「MUST TRUST LB」に該当します。実際PrivateとPublicの全体順位もあまりshakeしませんでした。このパターンでは早期参戦することで多数Submitによる試行錯誤がアドバンテージになります。 特徴量 useridから同じ作家が過去投稿した作品のブックマーク数に対するTarget Encoding 作成した特徴量の中で圧倒的にスコアに寄与した特徴量であり、このコンペにおけるMagicと言っても過言ではないくらい差があります。これは流行りのタイトルやジャンルを狙うより、結局はクリエイターの質によって作品の人気が決まることになります。実際投稿した小説家の中にも「他人の評価を優先」タイプと「自己満足を優先」タイプに分かれているため、ブックマーク数もそれに応じて分かれています。ただしリークになりやすいため利用時には注意する必要があります。今回はout-of-fold target encodingを利用しました。 同じ作家の一つ前に投稿した作品の情報を学習データを加える 前述したTarget Encoding特徴量は強力ですが、欠点としてテストデータの期間(2021/08/12以降)ではじめて投稿した作家の作品では すべて欠損値になるため、それを補うため「一つ前の投稿までの日数」「一つ前の投稿作品のタイトル」などを追加しました。実際一日に複数の投稿を行う作家の作品の評価が低い傾向を示しているほか、タイトルの類似度が高いものはシリーズ作品としてブクマ数が同じ傾向が見られることが多いです。 複数のペンネームを使用しているか こちらの記事にも言及されていますが、作者のユーザIDと作者名のnunique数は一致していません。約600名のユーザは複数のペンネームで投稿していますが、実は複数のペンネームを使用する/使用しないには大きな差があります。「小説家になろう」サイトの仕様上、ペンネーム欄を入力せず投稿すると、小説情報ページでは「作者名 = ユーザ名」と表示され、作者名のリンクをクリックしたら、その作者の過去の投稿作品の情報を確認できますが、ペンネームを入力すると作者名のリンクが付与されなくなり、読者はそこから過去の投稿作品の情報を確認できなくなります。 作品のブクマ数と過去作の評価には大きな相関があると分かった以上、これは大きなハンデと言わざるを得ません。 作者名にリンクが付与されている事例 (https://ncode.syosetu.com/novelview/infotop/ncode/n6316bn/) モデル 事前学習済みモデルの選定 BERTなどの事前学習済みモデルを利用する手法は今はもはやデファクトスタンダードになっていますが、 計算コストが多いため、事前にタスクのドメインに適するモデルの選定が必要です。 HuggingFaceのHosted inference APIを使えばある程度モデルとドメインの相性を事前に確認することがてきます。 以下はcl-tohoku/bert-base-japanese-v2 と rinna/japanese-roberta-base がHosted inference APIのFill-Maskタスクにおける結果を表示しています。 ①cl-tohoku/bert-base-japanese-v2 ② rinna/japanese-roberta-base ①の結果がより期待した出力に近いため、本コンペのドメインにおいて①モデルをFine-tuningしたものがより良い性能が得られる可能性が高いことが分かりました。ダウンロードする前にモデルのドメイン適正を味見することで、試行錯誤の回数を減らすことが可能です。 テキスト情報と非テキスト情報の結合 GBDTモデルにおいてテキスト情報の利用方法は下記記事できれいにまとめらています。 テーブルデータ向けの自然言語特徴抽出術 一方、BERTなどのモデルにおいてカテゴリ・数値特徴などの非テキスト情報を取り込む方法はまだ確立されていないようです。 Jigsaw Unintended Bias in Toxicity Classificationの1st Solution のように、カテゴリ情報をTransformerの追加トークンとして入力する手法が使われていますが、以下2つの懸念点があります。 事前学習のタスクと違うため小規模データセットでのFine-tuning性能が期待できない Denseなテキスト分散表現に対してカテゴリ・数値特徴量はSparseであることが多い 今回実装したモデルのおいて、小説名とあらすじを連結したテキストをBERTでエンコードした埋め込みに対して、ジャンルなどのカテゴリ特徴を付随条件として与えられた際、テキストの意味の変化をベクトル空間上の変位として表現し、埋め込みを変位ベクトルで補正する形で実装しました。結果、シングルモデルのスコアではGBDT系モデルに及びませんでしたが、GBDTとのアンサンブルでLB/CVスコアが大きな向上が見られました。 おわりに 言語情報と非言語情報のマルチモーダルデータを扱うコンペとして非常に面白かったです。 時系列情報に関していろんなアプローチを試しましたが、あまり効果がなかったため活用した方の解法に気になります。 本コンペで稼いた賞金が全部とある「なろう」発小説に還元しましたので、これからも「小説家になろう」を応援いたします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python printデバック】bound method Request.body of <starlette.requests.Request object at >の展開方法

sample.py import pprint pprint.pprint(vars(request)) ダンプの意味はこちら
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MLflowチュートリアル

はじめに MLflow という実験管理用ツールを試す。まずはどのような操作で何ができるのか、概要をつかみたいので MLflow チュートリアルを一通り行ってみる。 Training the Model Training the Model にあるソースコードを順に読み解いていく。前半は一般的な処理であるので、簡潔な説明のみとする。 まず必要なパッケージのインポートとロガーの設定、メトリクス計算のための関数を定義。 import os import warnings import sys import pandas as pd import numpy as np from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score from sklearn.model_selection import train_test_split from sklearn.linear_model import ElasticNet from urllib.parse import urlparse import mlflow import mlflow.sklearn import logging logging.basicConfig(level=logging.WARN) logger = logging.getLogger(__name__) def eval_metrics(actual, pred): rmse = np.sqrt(mean_squared_error(actual, pred)) mae = mean_absolute_error(actual, pred) r2 = r2_score(actual, pred) return rmse, mae, r2 学習データの読み込み、訓練データとテストデータの分割といった基本的な処理を実行。パラメータである alpha と l1_ratio はスクリプト実行時に変数を与えることができ、ない場合はデフォルト値として 0.5 となっている。 if __name__ == "__main__": warnings.filterwarnings("ignore") np.random.seed(40) # Read the wine-quality csv file from the URL csv_url = ( "http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv" ) try: data = pd.read_csv(csv_url, sep=";") except Exception as e: logger.exception( "Unable to download training & test CSV, check your internet connection. Error: %s", e ) # Split the data into training and test sets. (0.75, 0.25) split. train, test = train_test_split(data) # The predicted column is "quality" which is a scalar from [3, 9] train_x = train.drop(["quality"], axis=1) test_x = test.drop(["quality"], axis=1) train_y = train[["quality"]] test_y = test[["quality"]] alpha = float(sys.argv[1]) if len(sys.argv) > 1 else 0.5 l1_ratio = float(sys.argv[2]) if len(sys.argv) > 2 else 0.5 ここから mlflow の処理となる。mlflow.start_run() は新しい MLflow run を開始し、run がアクティブな状態であるときにメトリクスやパラメータを記録することができる。with 文を用いない場合は、明示的に mlflow.end_run() を呼び出し、run を終了させる必要がある。 その他は学習を行い、予測結果から各種メトリクスを計算している。 with mlflow.start_run(): lr = ElasticNet(alpha=alpha, l1_ratio=l1_ratio, random_state=42) lr.fit(train_x, train_y) predicted_qualities = lr.predict(test_x) (rmse, mae, r2) = eval_metrics(test_y, predicted_qualities) print("Elasticnet model (alpha=%f, l1_ratio=%f):" % (alpha, l1_ratio)) print(" RMSE: %s" % rmse) print(" MAE: %s" % mae) print(" R2: %s" % r2) 上記で計算したメトリクスやパラメータをログとして残していく。log_param() は引数に key と value を持ち、パラメータ名とパラメータ値を記録する。以下では、alpha と l1_ratio の2つが記録されている。 同様に log_metric() はメトリクス名と値を記録し、以下では rmse、 r2、 mae の3つが記録されている。 mlflow.log_param("alpha", alpha) mlflow.log_param("l1_ratio", l1_ratio) mlflow.log_metric("rmse", rmse) mlflow.log_metric("r2", r2) mlflow.log_metric("mae", mae) 上記は log_params() や mlflow.log_metrics() を使えば、以下のようにしてまとめて書くこともできる。 (other pattern) params = {"alpha": alpha, "l1_ratio": l1_ratio} mlflow.log_params(params) metrics = {"rmse": rmse, "r2": r2, "mae": mae} mlflow.log_metrics(metrics) get_tracking_uri() は tracking_uri(file:///home/user/mlflow_test/mlruns のような形式)を取得している。得られた tracking_uri を urlparse() を用いて解析し、スキーマ(上記例だと file に相当)が得られる。 これが file かどうかで log_model() のモデルの記録方法を変更している。(変更している意図は不明) tracking_url_type_store = urlparse(mlflow.get_tracking_uri()).scheme # Model registry does not work with file store if tracking_url_type_store != "file": # Register the model # There are other ways to use the Model Registry, which depends on the use case, # please refer to the doc for more information: # https://mlflow.org/docs/latest/model-registry.html#api-workflow mlflow.sklearn.log_model(lr, "model", registered_model_name="ElasticnetWineModel") else: mlflow.sklearn.log_model(lr, "model") Comparing the Models alpha と l1_ratio を変更したモデルをいくつか作成した後、mlflow ui をターミナルで実行し、http://localhost:5000 にアクセスすると以下のような画面に遷移する。 各モデルに関して、先ほど記録したメトリクスやパラメータを見ることができる。また複数のモデルを選択して比較したり、フィルタをかけることなどもできる。 おわりに MLflow チュートリアルに則って、シンプルな使い方や GUI を用いたモデルの比較などを行った。業務レベルに落としこんで、さまざまなことを試していきたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

tweepy4.0以降のリアルタイムのストリーム取得

はじめに 自分への備忘録として書きます。 tweepy 4.0以降でStreamListenerがStreamにマージされ、過去のサンプルコードが動かなくなったので、書き換えました。 環境 Python 3.10.1 tweepy 4.4.0 機能 自分のタイムラインからリアルタイムにツイートを取得して、"検索文"が含まれていた場合のみコンソールに表示する tweepy 4.0以降に対応したプログラム test.py import tweepy import settings CK = settings.CONSUMER_KEY CS = settings.CONSUMER_SECRET AK = settings.ACCESS_TOKEN AS = settings.ACCESS_TOKEN_SECRET word = "検索文" auth = tweepy.OAuthHandler(CK, CS) auth.set_access_token(AK, AS) api = tweepy.API(auth) print('実行中') class IDPrinter(tweepy.Stream): def on_status(self, status): print(status.id) print(status.user.screen_name) print(status.user.name) print(status.text) printer = IDPrinter(CK,CS,AK,AS) printer.filter(track=[word]) printer.sample() tweepy 3.6頃までは以下のコードの書き方が公式サンプルとして紹介されていましたが、 4.0でStreamListenerがStreamにマージされたので、そのままコピーでは使えなくなりました。 そのため、上記ソースのように書き換えたところ動くようになりました。 old.py class MyStreamListener(tweepy.StreamListener): def on_status(self, status): print(status.text) myStreamListener = MyStreamListener() myStream = tweepy.Stream(auth = api.auth, listener=myStreamListener) 3.6までのやり方 1.StreamListener を継承するクラスを作成する 2.そのクラスを使用して、 Stream オブジェクトを作成する 3.Stream を使用してTwitter APIに接続します
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Django】SNS app作成手順02-サインアップページ作成2

—————signup.htmlを編集---------- 使うなら、その後base.htmlの作成。 継承する内容を記載する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mac標準のPython2.7から3系にアップデートしようとして詰まった

経緯 mac標準のPython2.7を3系にアップデートしようとして詰まった話。 Python2系から3系へアップデートしようとすると brewのアップデートが必要に。。。 そうするとxcodeのアップデートが必要に。。。 さらにそうするとmacOSのアップデートが必要に。。。 macOS,xcode,brewのアップデートを終え、やっとのことでPythonをアップデート出来るぞと思ったところで詰まりました。 Pythonのアップデートはたくさん参考になる記事様がありますのでそちらをご覧ください。 環境 macOS Monterey(12.1) エラー内容 順にアップデートを終え、インストールしようとすると Inspect or clean up the working tree at このようなエラーが出ました。 これで検索をかけて出てきた情報から、 brewのライブラリをインストールし環境変数を設定したり、、、 $ brew install zlib bzip2 $ export LDFLAGS="-L/usr/local/opt/bzip2/lib -L/usr/local/opt/zlib/lib" $ export CPPFLAGS="-I/usr/local/opt/bzip2/include -I/usr/local/opt/zlib/include" xcodeの設定をしたり、、、 $ sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer $ sudo xcodebuild -runFirstLaunch これらでpyenvを使ったインストールを試みたが失敗。 もう一回エラー分と向き合ってみると Inspect or clean up the working tree at ------------(中略)--------------- No module named '_scproxy' 下の方にこんな感じの文章があったので、それで検索 https://github.com/pyenv/pyenv/issues/1107 こちらを参考に include_old を作ってそこに退避させる $ sudo mv /usr/local/include /usr/local/include_old $ sudo mkdir /usr/local/include $ sudo chown $YOU_USERNAME:admin /usr/local/include これでPython3系をインストールできるようになりました! Pythonのバージョンを変更する処理をして、無事使えることも確認できました。 これでも解決できたかも brew doctorをすると Warning: Unbrewed header files were found in /usr/local/include. If you didn't put them there on purpose they could cause problems when building Homebrew formulae, and may need to be deleted. /usr/local/includeを削除することで解決が出来そうでした。 今回は、恐かったので一旦退避する策にいたしました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自作matplotlib拡張をmpl-third-paryに登録する

mpl-third-paryへの登録 僕は基本matplotlibを使って論文等のFigureを作るのだが、たまに、目的の図を描くための既存ライブラリがなく、自分用にツールを作る羽目になることがある。最近、これらのツールが他人の役に立つこともあるかなと思い、2つのツール(patchworklibとpyCircos)をmpl-third-party に登録してみたので、その手順をメモしておく。   ついでに、登録するとmatplotlib公式がTwitterで宣伝してくれる。実際に今回登録したpatchworklibとpyCircosのmaplotlib公式による宣伝tweetが以下のものである。 patchworklibの宣伝Tweet Our awesome 3rd party package listing page https://t.co/T2DVwRUW7F constantly has new gems, for example Mori Hideto implemented operator based subplot composition (like patchwork) as https://t.co/mmEjNsVL2T pic.twitter.com/LBQ0Z7Zxsj— Matplotlib (@matplotlib) January 13, 2022 pycircosの宣伝Tweet More cool libraries c/o our third party page, this one a package by @Morihideponn for visualizing circular genomes https://t.co/ZkdbDpm8inAs always, let us know about yours by opening a PR on https://t.co/T2DVwRUW7F pic.twitter.com/n564mAOl03— Matplotlib (@matplotlib) January 16, 2022 登録の手順 登録の手順は全てgithubのweb上で完結できる。 1. GitHub - matplotlib/mpl-third-party: Webpage のrepositoryをFolkする。 2. Folkしたレポジトリで、./packagesに ”[追加したいpackageの名前].yml” を追加する。ymlの中身は最低限の以下の4項目が記述されていればいいっぽい。 repo: ponnhide/patchworklib #githubのrepository path([user_name]/[repository_name]) site: https://github.com/ponnhide/patchworklib #DocumentのURL。特別にDocumentを用意していないのであれば、githubのrepositoryのURLで問題なし。 section: plotting utilities #section名。利用可能なsection名は、https://github.com/matplotlib/mpl-third-party/blob/main/section_names.yml から確認できる。 description: A subplot manager for intuitive layouts #短い説明分。多分長すぎると短くさせられる。 3. ymlを追加したら、commitする。フォークしてあるのでmain branchにcommitしてしまって、特に問題はない。 4. フォークしたレポジトリのページからからpull requestを選択。以下のように、fork元であるmatplotlib/mpl-third-partyに対してpull requestを行うこと。 5. 追加したいpackageについての簡単な説明を求められるので、説明を書く。私の場合は https://github.com/matplotlib/mpl-third-party/pull/104 のように、簡単なsample codeと結果の図を載せたのだが、他の人の例をみるともっとsimpleで良かったらしい。 6. Create Pull Requestしておしまい。 正直なところ、今回登録してみたpatchworklibとpyCircosはgithubのスター数が100にも達していないショボいものだったので、pull requestがrejectされるのではないかと危惧していた。しかし、ものの数時間でmergeされたばかりか、twitterでも宣伝してくれるという親切対応であった。 maptlotlibは色々な図が作れるものも、コードが煩雑に成りがちであり、自作パッケージを作って対処している人も多いのではないだろうか。僕自身、これまでは、そうしたパッケージを作っては眠らせてしまっていた。しかし、公開してみると、意外と反応があり、似たようなことで困ってる人は世界にそこそこいたんだなぁ知ることができた。もし、論文や学会で発表するほどではないが、便利な描画ツールを作れた!という人は登録してみると良いかもしれない。きっと、数人の不毛な時間を減らす一助になるだろう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

miniforge / windows (うまくいっていない)

windows にベタに miniforge インストールする。 ダウンロードは、miniforge の Releases から一番シンプルなファイル名の Miniforge3-Windows-x86_64.exe をダウンロードしてインストール。 インストールが終わって、仮想環境を作ろうとして、 $HOME\miniforge3\Scripts\conda.exe create -n py39 すると、proxy 関係ないはずなのに HTTP CONNECTION のエラーでつながらない。 というときは、 $HOME\miniforge3\condabin\conda.bat create -n py39 python=3.9 インストーラーを信じて、各自環境にインストールして、環境変数の PATH に miniforge3 のパスを設定しないようにしたのに、正解の conda のパスを教えてくれないというイケズな公式。 たぶん、miniforge3 の下に PATH を通しまくったら動くとは思うけど、他の環境と混在しているときに動かんよとインストーラーで書かれているので、一番上の選択肢をチェックせずにインストールしたのに。 ※ 2022/1/17現在、自分の環境では miniforge3\condabin\activate.bat ph39 してもなんだかうまく動いていない。。。とりえあず WSL2 とか python3 のインストーラーで py.exe で使うからいいのだけど、Tk 動かさねばなのでネイティブ環境要るのですよ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

windows10でPython用MeCabを使用するための準備(2022年1月)

はじめに windows10でpython用MeCabを使用するため環境を整えようとしたところ、いろいろエラーが出て、いくつかの記事をはしごしました。まとまった記事があっても良いな~と思ったので下記、一連の流れをざっくりとした記録として残します。 手順0. 環境 Windows10 64bit Python 3.8.6 Anaconda3 64bit 手順1. MeCab実行環境を導入:MeCabインストール Windows10 64bitのため、以下からインストーラをダウンロード。  https://github.com/ikegami-yukino/mecab/releases/tag/v0.996.2 mecab-64-0.996.2.exeをダウンロードしたら、右クリックから管理者として実行。 基本的には「OK」「次へ」「はい」ですが、文字コードをデフォルトをShift-JISからUTF-8にしておくこと。  ★参考URL①: https://obenkyolab.com/?p=2682 手順2. mecab-pythonの導入:(mecab-python3の代わりに)mecabをpip install 次は、上記の参考URL①ではなく、  ★参考URL②:https://qiita.com/menon/items/f041b7c46543f38f78f7 上記のURL②を参考に、まずはアプリ一覧からAnaconda Promptを起動し、 pip install ipykernel を実行。これは無事成功。次に「pip install mecab-python-windows」を実行したのですが、下記エラーが発生。 Collecting mecab-python-windows Using cached mecab-python-windows-0.996.3.tar.gz (53 kB) Building wheels for collected packages: mecab-python-windows Building wheel for mecab-python-windows (setup.py) ... error ERROR: Command errored out with exit status 1: command: 'C:\★\anaconda3\python.exe' -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\★\\pip-install-ost7lbw6\\mecab-python-windows\\setup.py'"'"'; __file__='"'"'C:\\★\\pip-install-ost7lbw6\\mecab-python-windows\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' bdist_wheel -d 'C:\★\pip-wheel-b3rz0w3o' cwd: C:\★\pip-install-ost7lbw6\mecab-python-windows\ Complete output (8 lines): running bdist_wheel running build running build_py file MeCab.py (for module MeCab) not found file MeCab.py (for module MeCab) not found running build_ext building '_MeCab' extension error: Microsoft Visual C++ 14.0 is required. Get it with "Build Tools for Visual Studio": https://visualstudio.microsoft.com/downloads/ ---------------------------------------- ERROR: Failed building wheel for mecab-python-windows Running setup.py clean for mecab-python-windows Failed to build mecab-python-windows Installing collected packages: mecab-python-windows Running setup.py install for mecab-python-windows ... error ERROR: Command errored out with exit status 1: command: 'C:\★\anaconda3\python.exe' -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\★\\pip-install-ost7lbw6\\mecab-python-windows\\setup.py'"'"'; __file__='"'"'C:\\★\\pip-install-ost7lbw6\\mecab-python-windows\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record 'C:\★\pip-record-0axhvgxu\install-record.txt' --single-version-externally-managed --compile --install-headers 'C:\★\anaconda3\Include\mecab-python-windows' cwd: C:\★\pip-install-ost7lbw6\mecab-python-windows\ Complete output (8 lines): running install running build running build_py file MeCab.py (for module MeCab) not found file MeCab.py (for module MeCab) not found running build_ext building '_MeCab' extension error: Microsoft Visual C++ 14.0 is required. Get it with "Build Tools for Visual Studio": https://visualstudio.microsoft.com/downloads/ ---------------------------------------- ERROR: Command errored out with exit status 1: 'C:\★\anaconda3\python.exe' -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\★\\pip-install-ost7lbw6\\mecab-python-windows\\setup.py'"'"'; __file__='"'"'C:\\★\\pip-install-ost7lbw6\\mecab-python-windows\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record 'C:\★\pip-record-0axhvgxu\install-record.txt' --single-version-externally-managed --compile --install-headers 'C:\★\mecab-python-windows' Check the logs for full command output. ということでまたググると、やはり上手くいっていない方が自分の他にもいる様子。代わりに実行して上手くいったのが、下記。  ★参考URL③:https://teratail.com/questions/282254 pip install mecab これは無事成功!インストールが完了したら参考URL②の通りに、 (MeCabのインストール先)/bin にある"libmecab.dll"というファイルを (Anacondaのインストール先)/Lib/site-packages にコピー&ペースト。 超簡単に動作確認してみる 手順2まで完了したら、PowerShell・CommandPromptを起動。pythonを実行状態("python"を入力してEnter)にして、まずは下記を打ってEnterでエラーが出ないことを確認する。 import MeCab エラー出ず!良かった。ということで上記に続けて下記も打ってEnterで実行してみる。 m = MeCab.Tagger ("-Ochasen") print(m.parse ("すもももももももものうち")) すると下記のように分析・出力される。上手くいっていそう! すもも スモモ すもも 名詞-一般 も モ も 助詞-係助詞 もも モモ もも 名詞-一般 も モ も 助詞-係助詞 もも モモ もも 名詞-一般 の ノ の 助詞-連体化 うち ウチ うち 名詞-非自立-副詞可能 EOS ちなみに…手順2で何故mecab-python3のインストールをしなかったのか なぜ参考URL①のStep2「mecab-python3のインストール」を実行しなかったかというと、下記のようなエラーが発生したからです。その解決のためにググっていたらより良い記事(参考URL②)を見つけまして。 WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1124)'))': /simple/mecab-python3/ WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1124)'))': /simple/mecab-python3/ WARNING: Retrying (Retry(total=2, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1124)'))': /simple/mecab-python3/ WARNING: Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1124)'))': /simple/mecab-python3/ WARNING: Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1124)'))': /simple/mecab-python3/ Could not fetch URL https://pypi.org/simple/mecab-python3/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/mecab-python3/ (Caused by SSLError(SSLError(1, '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1124)'))) - skipping ERROR: Could not find a version that satisfies the requirement mecab-python3 ERROR: No matching distribution found for mecab-python3 Could not fetch URL https://pypi.org/simple/pip/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/pip/ (Caused by SSLError(SSLError(1, '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1124)'))) - skipping これに関しても何でかな~とググってみたところ、windowsには適していないらしく…代わりに「pip install mecab-python-windows」を実行するのが良いとのこと。  ・参考URL②-1:https://teratail.com/questions/256363 前述の通り「pip install mecab-python-windows」は上手くいかなかったので、いずれにせよ手順2の通り「pip install mecab」を実行するのが良いようです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[py2rb] 直列化

はじめに 移植やってます。 ( from python 3.7 to ruby 2.7 ) 直列化 (Python) import pickle self.assertEqual(dict_, pickle.loads(pickle.dumps(dict_))) オブジェクトと、オブジェクトを直列化しさらに復元されたオブジェクトを比較していますが、Equal にならないこともあるのでしょうかねえ? 直列化 (Ruby) assert_equal dict_, Marshal.load(Marshal.dump(dict_)) 独習Ruby 330p 7.2.7 オブジェクトのシリアライズ ここでは、dumpをオーバーライドして特定のデータのみシリアライズする例が紹介されています。 メモ Python の 直列化 を学習した 百里を行く者は九十里を半ばとす
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

『エラトステネスの篩』の記述例(Python版)

元記事より分離しました.アルゴリズム等は元記事を御参照下さい.趣旨は元記事と同じく『「エラトステネスの篩」だとこれだけ速く素数が求まる!』ですが,ライブラリ等によってよりシンプルな記述表現がある場合など,他のプログラミング言語との比較を避けるため,個別記事としている次第です.特にPythonについては,現状で標準ライブラリと化しているNumPyを用いる場合とそうでない場合とで,記述表現や処理速度に大きな違いが出ています. 記述例および実行例(NumPy未使用その1) sieve.py x = int(input()) a = [True] * (x+1) a[1], r = False, [] for i in range(1, x+1): if a[i]: if i <= x**0.5: for j in range(i*i, x+1, i): a[j] = False r += [i] print(len(r)) print(r[-1]) 次の実行環境にて,百万までの素数の個数と最大の素数を表示しています. ASUS Zenfone 5: Qualcomm Snapdragon 636 1.8GHz, 6GBメモリ Android 9 + Termux + AnLinux(Debian GNU/Linux 10) + Python 3.7.3 $ time python3 sieve.py <<< 1000000 78498 999983 real 0m1.268s user 0m0.970s sys 0m0.050s 記述例および実行例(NumPy未使用その2) sieve2.py x = int(input()) a = [True] * (x+1) a[0] = a[1] = False for i in range(1, int(x**0.5) + 1): if a[i]: a[i*i::i] = [False] * (x//i - i + 1) r = [i for i, p in enumerate(a) if p] print(len(r)) print(r[-1]) 上記と同じ次の実行環境にて,一千万までの素数の個数と最大の素数を表示しています. ASUS Zenfone 5: Qualcomm Snapdragon 636 1.8GHz, 6GBメモリ Android 9 + Termux + AnLinux(Debian GNU/Linux 10) + Python 3.7.3 $ time python3 sieve2.py <<< 10000000 664579 9999991 real 0m2.657s user 0m2.270s sys 0m0.290s 記述例および実行例(NumPy使用) sieve.py from numpy import ones, sqrt, arange x = int(input()) a = ones(x+1, dtype=bool) a[0:2] = False for i in range(2, int(sqrt(x))+1): if a[i]: a[i*i::i] = False r = arange(x+1)[a] print(len(r)) print(r[-1]) 上記と同じ次の実行環境にて,一億までの素数の個数と最大の素数を表示しています. ASUS Zenfone 5: Qualcomm Snapdragon 636 1.8GHz, 6GBメモリ Android 9 + Termux + AnLinux(Debian GNU/Linux 10) + Python 3.7.3 $ time python3 sieve-numpy.py <<< 100000000 5761455 99999989 real 0m6.163s user 0m5.010s sys 0m1.940s 備考 更新履歴 2022-01-17:NumPyなし記述例その2追加(コメントより) 2022-01-17:元記事より移行
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python]コードサンプルが正しく動くことを担保し続けるためのdoctest入門

最近自作Pythonライブラリでがっつりdoctestを使い始めたので記事にまとめておきます。 doctestってなに? dostring内に書くコードサンプルを実際にPythonで動かしてエラーにならないことや返却値が正しいかどうかをチェックすることができる機能です。 ※docstringについては必要に応じて以下の記事などをご確認ください。 ※Rustなどにも似たような機能がビルトインで入っています。 doctestを書くとなにが嬉しいの? docstringにコードサンプルが載っているとユーザーがエディタ上などでさくっと使い方を確認できてユーザーフレンドリーです。 一方で書いただけだとそのコードサンプルはテストやLintでチェックされるわけではありませんので正常に動作しないケースが発生し得ます。書いたときは動いていても日々のアップデートでいつの間にか動かなくなってしまうこともあるかもしれません。 コードサンプルが動いてくれないとその資料を見ているユーザーに取って開発体験が良くありません。 そこで通常の単体テストのようにdocstring内のコードサンプルで実行・返却値の確認などのテストが実行されることでコードサンプルが常に動いていることを担保することができます。もちろん通常のテストやLintなどと同様にCI/CDのフローに組み込むことも可能です。 また、単体テストなどにはpytestを使われている方が多いと思いますが、pytestはdoctestの機能を有しているためpytestをお使いの方は既存のテストと同じ感覚ですぐにdoctestを使い始めることができます。本記事でもpytestで動かす形で進めていきます。 ※コメントにて @shiracamusさんがご指摘くださいましたが、pytestを使わなくてもビルトインのみでさくっと使うことができます。お好きな方をご利用ください。 この記事で使うもの Python 3系環境(3.6以降を想定しています) pytest==6.2.5 pytestでのdoctestの動かし方 基本的にpytestのコマンドの引数に--doctest-modulesと加えるだけです。これで通常の単体テストではなくdocstring内のサンプルコードがテストとして実行され、通常の単体テストと同様にテスト結果を得ることができます。 他のpytestの引数やpytestのプラグインなどもそのまま動きます。 doctestの書き方 以降の節ではdoctestの書き方について触れていきます。なお、本記事ではdocstringはNumPyスタイルを前提として進めていきます。 まずはPandasのコードを軽く見てみる doctestがどんな雰囲気なのかを先に軽く確認するため他のライブラリの実装を確認しておきます。 PythonコミュニティではNumPyスタイルでdocstringが書かれたライブラリが色々とありますが、代表的(且つdocstringもしっかり書かれている)なライブラリのPandasのものを見てみましょう。 データフレームのコードでshape属性のdocstringを見ると以下のようになっています。 ... @property def shape(self) -> tuple[int, int]: """ Return a tuple representing the dimensionality of the DataFrame. See Also -------- ndarray.shape : Tuple of array dimensions. Examples -------- >>> df = pd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) >>> df.shape (2, 2) >>> df = pd.DataFrame({'col1': [1, 2], 'col2': [3, 4], ... 'col3': [5, 6]}) >>> df.shape (2, 3) """ ... サンプルコードは上記のコードのExamplesセクション部分が該当します。 なお、このフォーマットで書かれた場合VS Codeなどで対象のインターフェイスにマウスオーバーした際などに以下のようにちゃんとシンタックスハイライトなどが以下のように反映された状態で表示されます。 以降の節で詳細を一つずつ触れていきます。 NumPyスタイルのdocstringではExamplesのセクションに書かれることが多い NumPyスタイルのdocstringではコードサンプル部分はExampleセクション以降に書かれることが多いようです。 例えば(関数は適当ですが)以下のようにExamplesセクションを追加して書き進めていく形になります。 def get_int_sum(x: int, y: int) -> int: """ 引数に指定された2つの整数の合計を取得する。 Parameters ---------- x : int 1つ目の整数値。 y : int 2つ目の整数値。 Returns ------- result : int 結果の合計値。 Examples -------- ※ここにコードサンプルを書いていきます... """ return x + y コード実行行は先頭に>>>を付ける コードサンプルで実行して欲しい行には先頭に>>>を付けて書きます。 sample/sample_1.py def get_int_sum(x: int, y: int) -> int: """ 引数に指定された2つの整数の合計を取得する。 Parameters ---------- x : int 1つ目の整数値。 y : int 2つ目の整数値。 Returns ------- result : int 結果の合計値。 Examples -------- >>> result: int = get_int_sum(x=10, y=20) """ return x + y これでdoctestでresult: int = get_int_sum(x=10, y=20)というコードが実行される形になります。試しにpytestで動かしてみます。上記のコードはsampleというフォルダにてsample_1.pyというモジュール名で追加されている想定で進めます。 $ pytest ./sample/ --doctest-modules -v -s =============================== test session starts =============================== ... collected 1 item sample/sample_1.py::sample.sample_1.get_int_sum PASSED ================================ 1 passed in 0.11s ================================ テストが実行されて1件のテストを通った・・・と表示されました。この辺の挙動はpytestの通常の単体テスト等と同様です。 実行行は1つだけではなく複数の行を実行していくことが可能です。各行の変数なども保持される形になります(この辺はインタラクティブシェルやJupyterなどと同じ感覚です)。 def get_int_sum(x: int, y: int) -> int: """ 引数に指定された2つの整数の合計を取得する。 Parameters ---------- x : int 1つ目の整数値。 y : int 2つ目の整数値。 Returns ------- result : int 結果の合計値。 Examples -------- >>> result_1: int = get_int_sum(x=10, y=20) >>> result_2: int = get_int_sum(x=result_1, y=20) """ return x + y 試しにテストがわざと失敗するように、引数に整数ではなく文字列を指定してpytestを実行してみましょう。 def get_int_sum(x: int, y: int) -> int: """ 引数に指定された2つの整数の合計を取得する。 Parameters ---------- x : int 1つ目の整数値。 y : int 2つ目の整数値。 Returns ------- result : int 結果の合計値。 Examples -------- >>> result: int = get_int_sum(x=10, y='Hello!') """ return x + y pytestのコマンドを実行してみるとテストは通らず、失敗したテストの詳細を確認することができます。 $ pytest ./sample/ --doctest-modules -v -s =============================== test session starts =============================== ... collected 1 item sample/sample_1.py::sample.sample_1.get_int_sum FAILED ==================================== FAILURES ===================================== __________________________ [doctest] sample.sample_1.get_int_sum __________________________ 010 2つ目の整数値。 011 012 Returns 013 ------- 014 result : int 015 結果の合計値。 016 017 Examples 018 -------- 019 >>> result: int = get_int_sum(x=10, y='Hello!') UNEXPECTED EXCEPTION: TypeError("unsupported operand type(s) for +: 'int' and 'str'",) Traceback (most recent call last): ... File "<doctest sample.sample_1.get_int_sum[0]>", line 1, in <module> File ".../sample/sample_1.py", line 21, in get_int_sum return x + y TypeError: unsupported operand type(s) for +: 'int' and 'str' .../sample/sample_1.py:19: UnexpectedException ============================= short test summary info ============================= FAILED sample/sample_1.py::sample.sample_1.get_int_sum ================================ 1 failed in 0.14s ================================ 多少通常のpytestの単体テストとは表示が異なりますが、大体似たような雰囲気でエラー内容やエラーの箇所などが表示されます。 返却値の行には先頭に何も付けずに書く Jupyterの[Out]部分のアウトプットなどと同じような感じで、返却値などが標準出力に表示される場合には>>>などを先頭に加えずに記述します。 以下のコードサンプルではget_int_sum関数の返却値として30を指定しています。 def get_int_sum(x: int, y: int) -> int: """ 引数に指定された2つの整数の合計を取得する。 Parameters ---------- x : int 1つ目の整数値。 y : int 2つ目の整数値。 Returns ------- result : int 結果の合計値。 Examples -------- >>> get_int_sum(x=10, y=20) 30 """ return x + y この値はdoctsetでチェックされます。つまりget_int_sum関数の実行結果が30になっていないとテストが失敗します。 試しに返却値の指定を40としてわざと失敗するようにしてpytestを動かしてみます。 def get_int_sum(x: int, y: int) -> int: """ 引数に指定された2つの整数の合計を取得する。 Parameters ---------- x : int 1つ目の整数値。 y : int 2つ目の整数値。 Returns ------- result : int 結果の合計値。 Examples -------- >>> get_int_sum(x=10, y=20) 40 """ return x + y $ pytest ./sample/ --doctest-modules -v -s =============================== test session starts =============================== ... collected 1 item sample/sample_1.py::sample.sample_1.get_int_sum FAILED ==================================== FAILURES ===================================== __________________________ [doctest] sample.sample_1.get_int_sum __________________________ 010 2つ目の整数値。 011 012 Returns 013 ------- 014 result : int 015 結果の合計値。 016 017 Examples 018 -------- 019 >>> get_int_sum(x=10, y=20) Expected: 40 Got: 30 .../sample/sample_1.py:19: DocTestFailure ============================= short test summary info ============================= FAILED sample/sample_1.py::sample.sample_1.get_int_sum ================================ 1 failed in 0.13s ================================ 想定値(40)と実際に返却値として得られた値(30)が違っているよ、といった具合に表示されてテストが失敗していることが分かります。 このチェックはJupyterなどと同様にprint文などを通さなくても変数単体で入力行に記述しても動作します。 例えば以下のようにresult変数単体で入力行に指定してみてもテストを通ります。 def get_int_sum(x: int, y: int) -> int: """ 引数に指定された2つの整数の合計を取得する。 Parameters ---------- x : int 1つ目の整数値。 y : int 2つ目の整数値。 Returns ------- result : int 結果の合計値。 Examples -------- >>> result: int = get_int_sum(x=10, y=20) >>> result 30 """ return x + y $ pytest ./sample/ --doctest-modules -v -s ... sample/sample_1.py::sample.sample_1.get_int_sum PASSED ================================ 1 passed in 0.12s ================================ 1つのdocstring内で複数の返却値などを指定することもできます。間に空行などを入れても問題なく動作します。 def get_int_sum(x: int, y: int) -> int: """ 引数に指定された2つの整数の合計を取得する。 Parameters ---------- x : int 1つ目の整数値。 y : int 2つ目の整数値。 Returns ------- result : int 結果の合計値。 Examples -------- >>> result: int = get_int_sum(x=10, y=20) >>> result 30 >>> result = get_int_sum(x=result, y=20) >>> result 50 """ return x + y ... sample/sample_1.py::sample.sample_1.get_int_sum PASSED ================================ 1 passed in 0.12s ================================ 複数行に処理を繋げる場合には先頭に...と書く PEP8準拠などの都合で1行の長さ制限を設けている場合に関数呼び出し箇所などで改行を入れたくなることが結構あります。そういった場合には先頭に>>>の代わりに...を記述します。 def get_int_sum(x: int, y: int) -> int: """ 引数に指定された2つの整数の合計を取得する。 Parameters ---------- x : int 1つ目の整数値。 y : int 2つ目の整数値。 Returns ------- result : int 結果の合計値。 Examples -------- >>> result: int = get_int_sum( ... x=10, y=20) >>> result 30 """ return x + y テストを動かしてみても動作します。 $ pytest ./sample/ --doctest-modules -v -s sample/sample_1.py::sample.sample_1.get_int_sum PASSED ================================ 1 passed in 0.09s ================================ 関数定義などの基本的に改行が必要な箇所でも同様に...の記述を行うことが必要になります。 def get_int_sum(x: int, y: int) -> int: """ 引数に指定された2つの整数の合計を取得する。 Parameters ---------- x : int 1つ目の整数値。 y : int 2つ目の整数値。 Returns ------- result : int 結果の合計値。 Examples -------- >>> def get_x() -> int: ... return 10 >>> result: int = get_int_sum( ... x=get_x(), y=20) >>> result 30 """ return x + y sample/sample_1.py::sample.sample_1.get_int_sum PASSED ================================ 1 passed in 0.12s ================================ ...を書くべき箇所を>>>としてしまうとシンタックスエラーになります。これはPythonが>>>で始まる行単位で個別に動かしていくことに起因するためです(分割された時に正しく動くコードになっていないと怒られてしまいます)。 以下のコードではresult: int = get_int_sum(という行単体で実行されるため、閉じ括弧が無いといった具合でシンタックスエラーとなってテストが失敗します。 def get_int_sum(x: int, y: int) -> int: """ 引数に指定された2つの整数の合計を取得する。 Parameters ---------- x : int 1つ目の整数値。 y : int 2つ目の整数値。 Returns ------- result : int 結果の合計値。 Examples -------- >>> result: int = get_int_sum( >>> x=10, y=20) >>> result 30 """ return x + y ... ==================================== FAILURES ===================================== ______________________ [doctest] sample.sample_1.get_int_sum ______________________ 010 2つ目の整数値。 011 012 Returns 013 ------- 014 result : int 015 結果の合計値。 016 017 Examples 018 -------- 019 >>> result: int = get_int_sum( UNEXPECTED EXCEPTION: SyntaxError('unexpected EOF while parsing', ('<doctest sample.sample_1.get_int_sum[0]>', 1, 27, 'result: int = get_int_sum(\n')) Traceback (most recent call last): ... File "<doctest sample.sample_1.get_int_sum[0]>", line 1 result: int = get_int_sum( ^ SyntaxError: unexpected EOF while parsing .../sample/sample_1.py:19: UnexpectedException ============================= short test summary info ============================= FAILED sample/sample_1.py::sample.sample_1.get_int_sum ================================ 1 failed in 0.13s ================================ 行末のエスケープを指定した際には注意 コードの途中で通常は改行ができない箇所で行末に\の記号を入れて改行を行う・・・というケースもあると思います。 この場合には先頭に...の指定が入っているとテストがエラーになってしまいます。というのも\による行末のエスケープの指定は「改行を無いものと判定する」挙動をするため、実行対象のコード内に...の記述が入り込んでしまって正常な挙動にならなくなります。 例えば以下のようなdoctestの記述だとテストが失敗します。 def get_int_sum(x: int, y: int) -> int: """ 引数に指定された2つの整数の合計を取得する。 Parameters ---------- x : int 1つ目の整数値。 y : int 2つ目の整数値。 Returns ------- result : int 結果の合計値。 Examples -------- >>> result: int = \ ... get_int_sum(x=10, y=20) >>> result 30 """ return x + y ... ==================================== FAILURES ===================================== ______________________ [doctest] sample.sample_1.get_int_sum ______________________ 010 2つ目の整数値。 011 012 Returns 013 ------- 014 result : int 015 結果の合計値。 016 017 Examples 018 -------- 019 >>> result: int = ... get_int_sum(x=10, y=20) UNEXPECTED EXCEPTION: SyntaxError('invalid syntax', ('<doctest sample.sample_1.get_int_sum[0]>', 1, 37, 'result: int = ... get_int_sum(x=10, y=20)\n')) Traceback (most recent call last): ... File "<doctest sample.sample_1.get_int_sum[0]>", line 1 result: int = ... get_int_sum(x=10, y=20) ^ SyntaxError: invalid syntax .../sample/sample_1.py:19: UnexpectedException ============================= short test summary info ============================= FAILED sample/sample_1.py::sample.sample_1.get_int_sum ================================ 1 failed in 0.13s ================================ トレースバックを見てみてもresult: int = ... get_int_sum(x=10, y=20)といったように間に...が入ったコードが実行されてしまっていることが分かります。 このエラーを避けるためにはそのまま...を先頭に加えずに書きます。 def get_int_sum(x: int, y: int) -> int: """ 引数に指定された2つの整数の合計を取得する。 Parameters ---------- x : int 1つ目の整数値。 y : int 2つ目の整数値。 Returns ------- result : int 結果の合計値。 Examples -------- >>> result: int = \ get_int_sum(x=10, y=20) >>> result 30 """ return x + y これで正常に動いてくれます。 sample/sample_1.py::sample.sample_1.get_int_sum PASSED ================================ 1 passed in 0.13s ================================ 返却値を返したり標準出力がされる関数などには注意 返却値を使わないのだけれどもテスト時に事前の設定用などに呼び出しが必要・・・といった類の関数を入力行に指定する際には注意が必要です。 例えばJupyterなどで作業している時にmatplotlibなどでも軸やFigureなどの返却値は返ってくるもののそれらは特に使わない・・・といったケースがあると思いますが、あのようなケースがdoctest中にあるとdoctestで引っかかってしまうケースが発生してきます。 以下のコードサンプルではsetupという関数をコードサンプルの最初に呼び出しています。仮に正常に処理が通った場合にTrueが返る関数としましょう。実際のコードサンプルでは返却値は使わないので返却値を変数に入れたりなどはしていません。 def get_int_sum(x: int, y: int) -> int: """ 引数に指定された2つの整数の合計を取得する。 Parameters ---------- x : int 1つ目の整数値。 y : int 2つ目の整数値。 Returns ------- result : int 結果の合計値。 Examples -------- >>> def setup() -> bool: ... return True >>> setup() >>> result: int = get_int_sum(x=10, y=20) >>> result 30 """ return x + y しかしながらテストを実行してみるとこのコードサンプルだと引っかかってしまいます。 ==================================== FAILURES ===================================== ______________________ [doctest] sample.sample_1.get_int_sum ______________________ 012 Returns 013 ------- 014 result : int 015 結果の合計値。 016 017 Examples 018 -------- 019 >>> def setup() -> bool: 020 ... return True 021 >>> setup() Expected nothing Got: True .../sample/sample_1.py:21: DocTestFailure ============================= short test summary info ============================= FAILED sample/sample_1.py::sample.sample_1.get_int_sum ================================ 1 failed in 0.12s ================================ >>> setup()の行で引っかかっていることが分かります。そちらで想定値はnothing、つまり返却値(出力される値)は何もない想定ではあるものの、実際にはTrueが出力されている・・・という状態になります。 このように「使わないけど返却値が返る関数」などを使いたい場合には_を変数として指定しておくと良いかなと思います。Python界隈ではこの変数名は基本的に設定されても使わないといったケースなどに利用されます。 先ほどのコードを>>> _ = setup()と書き換えることでテストが通ることを確認できます。 def get_int_sum(x: int, y: int) -> int: """ 引数に指定された2つの整数の合計を取得する。 Parameters ---------- x : int 1つ目の整数値。 y : int 2つ目の整数値。 Returns ------- result : int 結果の合計値。 Examples -------- >>> def setup() -> bool: ... return True >>> _ = setup() >>> result: int = get_int_sum(x=10, y=20) >>> result 30 """ return x + y sample/sample_1.py::sample.sample_1.get_int_sum PASSED ================================ 1 passed in 0.12s ================================ 外部モジュールの読み込みなども効く コードサンプル中で外部のモジュールをimportして使ったりすることもできます。 def get_int_sum(x: int, y: int) -> int: """ 引数に指定された2つの整数の合計を取得する。 Parameters ---------- x : int 1つ目の整数値。 y : int 2つ目の整数値。 Returns ------- result : int 結果の合計値。 Examples -------- >>> import math >>> result: int = get_int_sum(x=math.floor(10.5), y=20) >>> result 30 """ return x + y sample/sample_1.py::sample.sample_1.get_int_sum PASSED ================================ 1 passed in 0.13s ================================ 独自クラスなどではreprのダンダーメソッドを書いておくと快適 独自に定義したクラスのインスタンス自体をテストの出力として設定したい場合がたまにあると思います。 たとえば以下のPointという自前で定義したクラスで試してみます。 class Point: _x: int _y: int def __init__(self, x: int, y: int) -> None: """ XとY座標の値を扱うクラス。 Parameters ---------- x : int X座標。 y : int Y座標。 Examples -------- >>> point: Point = Point(x=10, y=20) >>> point """ self._x = x self._y = y テストを流してみると以下のように引っかかります。 ==================================== FAILURES ===================================== ____________________ [doctest] sample.sample_1.Point.__init__ _____________________ 011 ---------- 012 x : int 013 X座標。 014 y : int 015 Y座標。 016 017 Examples 018 -------- 019 >>> point: Point = Point(x=10, y=20) 020 >>> point Expected nothing Got: <sample.sample_1.Point object at 0x7f2bc1a7e9b0> .../sample/sample_1.py:20: DocTestFailure ============================= short test summary info ============================= FAILED sample/sample_1.py::sample.sample_1.Point.__init__ ================================ 1 failed in 0.14s ================================ 0x7f2bc1a7e9b0といった値はid関数で取れるメモリアドレス的な値になるため実行の度に変わってしまいます。こういった値は出力用で指定するには厄介です。出来たらPoint(x=10, y=20)みたいな属性に応じた表示だとテストを書くのが楽です。 そういった場合には__repr__のダンダーメソッドをクラスで定義しておくと任意のフォーマットで出力するように調整することができます。 ※ダンダーメソッド(Dunder methods)はPythonのアンダースコアが2つ指定される特殊なメソッドのことです(double underscore methodの略)。詳しくは以下の記事などをご確認ください。 以下のコードでは__repr__の記述を追加してPoint(x=10, y=20)の形式で出力されているようにしているため、コードサンプル部分でPoint(x=10, y=20)という出力値を指定していればテストを通ってくれます。 class Point: _x: int _y: int def __init__(self, x: int, y: int) -> None: """ XとY座標の値を扱うクラス。 Parameters ---------- x : int X座標。 y : int Y座標。 Examples -------- >>> point: Point = Point(x=10, y=20) >>> point Point(x=10, y=20) """ self._x = x self._y = y def __repr__(self) -> str: return f'Point(x={self._x}, y={self._y})' ... sample/sample_1.py::sample.sample_1.Point.__init__ PASSED ================================ 1 passed in 0.13s ================================ doctest内で例外を発生させる記述に付いて assert_raises的なことをしたい場合、そちらも対応しているようで@shiracamusさんがコメントでリンクなどをご共有くださったためそちらも合わせてご確認ください! 関数やメソッド以外の箇所のdocstringでも対象となる doctestの実行対象は関数やメソッドに記載されているもの以外のdocstringも対象となります。例えばクラス自体のdocstringなどもテスト対象となります。 クラスであればVS Codeではコンストラクタのdocstringはコンストラクタの呼び出しを書いている際に表示され、クラス自体のdocstringはクラスに対してマウスオーバーした際に表示されるなど表示されるタイミングが異なります。そのため両方にコードサンプルを書いておくというのもアリだと思います(コンストラクタにはインスタンス化時のコードサンプル、クラス自体にはクラスのインターフェイス全体的なコードサンプルを書くなど)。 例えば以下のような形になります。 class Point: """ XとY座標の値を扱うクラス。 Examples -------- >>> point: Point = Point(x=10, y=20) >>> point Point(x=10, y=20) >>> point.x 10 """ _x: int _y: int def __init__(self, x: int, y: int) -> None: """ XとY座標の値を扱うクラス。 Parameters ---------- x : int X座標。 y : int Y座標。 Examples -------- >>> point: Point = Point(x=10, y=20) >>> point Point(x=10, y=20) """ self._x = x self._y = y @property def x(self) -> int: """ 設定されているX座標の属性値を取得する。 Returns ------- x : int 設定されているX座標の属性値。 Examples -------- >>> point: Point = Point(x=10, y=20) >>> point.x 10 """ return self._x def __repr__(self) -> str: return f'Point(x={self._x}, y={self._y})' テストを実行してみるとクラス自体のdocstringの分も含めてテストが実行されていることが確認できます。 ... sample/sample_1.py::sample.sample_1.Point PASSED sample/sample_1.py::sample.sample_1.Point.__init__ PASSED sample/sample_1.py::sample.sample_1.Point.x PASSED ================================ 3 passed in 0.13s ================================ 編集の際にはマルチカーソルを使うと楽 余談気味となりますが、>>>や...の記述などは複数行同じ記述となったりすることが多いためVS Codeなどでマルチカーソル等を使っておくと編集が短時間で終わって楽です。 既にマルチカーソルを普段から使われている方が多いと思いますが、一応参考としてリンクを貼っておきます。 実はJupyterだとコードの先頭に>>>などがあっても動く さらに余談ですがJupyterは実行セルで>>>や...の記述があっても動きます(ただし入れているLintなどは引っかかったりします)。 コードサンプルなどを直接コピペなどして動かしたりする際などに便利な時があるかもしれません。 以下のスクショはVS Code上のJupyterですが、セルを実行してもエラーにならずに動いていることを確認できます。 実際にdoctestを反映してみたプロジェクト 自作のPythonでSVGベースのフロントエンドを書けることを目指してちまちま作っていっているapyscというライブラリで今回のdoctestをpublicなインターフェイスで全体的に追加したりGitHub Actionsでのデプロイのワークフローでのチェックに組み込んだりしています。 実際のコードでのdoctestを確認したい・・・といった場合にご利用ください。 参考文献・参考サイトまとめ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

公開level botを改変して見やすくしていく

このサイトからcodeを取っていく 人のプログラムを改変していく その前にlevelというフォルダ作っていてください 上の画像をlevelフォルダにrank.pngという名前でインストール ここからフォントをlevelフォルダにラノベPOP.ttfという名前でインストールしてください level.dbは自動で作られます from discord.ext import commands import discord import os import sqlite3 conn=sqlite3.connect("level.db", check_same_thread=False) c=conn.cursor() prefix="?" token="トークン" bot=commands.Bot(command_prefix=prefix) c.execute("CREATE TABLE IF NOT EXISTS level(userid, level, exp)") @bot.event async def on_ready(): print("起動") @bot.listen("on_message") async def level_count(message): if message.author.bot: return if message.content.startswith(prefix): return c.execute("SELECT * FROM level WHERE userid=?", (message.author.id,)) data=c.fetchone() if data is None: c.execute("INSERT INTO level VALUES(?, ?, ?)",(message.author.id, 1, 0)) conn.commit() return c.execute("UPDATE level set exp=? WHERE userid=?",(data[2]+1, message.author.id)) conn.commit() c.execute("SELECT * FROM level WHERE userid=?", (message.author.id,)) data=c.fetchone() if data[2] >= data[1]*5: c.execute("UPDATE level set level=?,exp=? WHERE userid=?",(data[1]+1,0,message.author.id)) conn.commit() await message.channel.send("レベルアップしました") @bot.command() async def level(ctx, target:discord.User=None): if target is None: user=ctx.author else: user=target c.execute("SELECT * FROM level WHERE userid=?", (user.id,)) data=c.fetchone() if data is None: await ctx.send("ユーザーが登録されていません") e=discord.Embed(title=f"{user}のランク", description=f"Lv.{data[1]}") await ctx.send(embed=e) @bot.command() async def rank(ctx): r={} title="ランク(トップ3まで)" c.execute("SELECT * FROM level") for i in c.fetchall(): user=await bot.fetch_user(i[0]) r[i[1]]=user.name rag=[i for i in r] rank=sorted(rag, reverse=True) b=0 description="\n".join(f"{r[f]}" for f in rank) e=discord.Embed(title=title, description=description) await ctx.send(embed=e) bot.run(token) これが改変してないcodeです 改変していきます from discord.ext import commands import discord import os import aiohttp import sqlite3 import datetime #datetimeを入れました import asyncio from PIL import ImageFont, ImageDraw, Image from io import BytesIO conn=sqlite3.connect("level/level.db", check_same_thread=False) c=conn.cursor() prefix="?" token = "your token" def jst(): now = datetime.datetime.utcnow() now = now + datetime.timedelta(hours=9) return now client=commands.Bot(command_prefix=prefix) c.execute("CREATE TABLE IF NOT EXISTS level(userid, level, exp)") @client.event async def on_ready(): print("起動") @client.listen("on_message") async def level_count(message): if message.author.bot: return if message.content.startswith(prefix): return c.execute("SELECT * FROM level WHERE userid=?", (message.author.id,)) data=c.fetchone() if data is None: c.execute("INSERT INTO level VALUES(?, ?, ?)",(message.author.id, 1, 0)) conn.commit() return c.execute("UPDATE level set exp=? WHERE userid=?",(data[2]+1, message.author.id)) conn.commit() c.execute("SELECT * FROM level WHERE userid=?", (message.author.id,)) data=c.fetchone() if data[2] >= data[1]*5: c.execute("UPDATE level set level=?,exp=? WHERE userid=?",(data[1]+1,0,message.author.id)) conn.commit() await message.channel.send("レベルアップしました") @client.command() async def level(ctx, target:discord.User=None): if target is None: user = ctx.author else: user=target c.execute("SELECT * FROM level WHERE userid=?", (user.id,)) data=c.fetchone() if data is None: await ctx.send("ユーザーが登録されていません") img = Image.open("level/rank.png") draw = ImageDraw.Draw(img) font = ImageFont.truetype("level/ラノベPOP.ttf", 35) font1 = ImageFont.truetype("level/ラノベPOP.ttf", 24) font2 = ImageFont.truetype("level/ラノベPOP.ttf", 20) async with aiohttp.ClientSession() as session: async with session.get(str(ctx.author.avatar_url)) as response: image = await response.read() icon = Image.open(BytesIO(image)).convert("RGBA") img.paste(icon.resize((156, 156)), (50, 60)) draw.text((265, 125), f"{data[1]}", (255, 255, 255), font=font) now = jst() draw.text((510, 250), now.strftime('%Y年%m月%d日%H時%M分%S秒'), (255, 255, 255), font=font2) draw.text((50,220), f"{ctx.author.name}", (255, 255, 255), font=font1) draw.text((50,240), f"#{ctx.author.discriminator}", (255, 255, 255), font=font1) img.save('level/infoimg2.png') #Change Leveling/infoimg2.png if needed. ffile = discord.File("level/infoimg2.png") await ctx.send(file=ffile) @client.command() async def rank(ctx): r={} title="ランク(トップ3まで)" c.execute("SELECT * FROM level") for i in c.fetchall(): user=await client.fetch_user(i[0]) r[i[1]]=user.name rag=[i for i in r] rank=sorted(rag, reverse=True) b=0 description="\n".join(f"{r[f]}" for f in rank) e=discord.Embed(title=title, description=description) await ctx.send(embed=e) client.run(token, bot=True) ファイル2個フォルダ一個 計3個 引用 私は画像を改変して使っています こういう感じです 完成系 終わります
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OpenVINOで推論してみた2 age-gender-recognition

初めに OpenVINOで推論してみた第二回になります。前回の記事で顔検出をやりましたが、それを使って今度は顔認識を行っていきます。今回は年齢と性別を判定するものですね! それでは早速やっていきます!! 今回も最後に使った全コードを載せておきますので説明等不要な方はとりあえず実装してみてくださいね!! 実行環境 windows10 OpenVINO toolkit 2021.4.752 OpenVINO 2021.4.2 python 3.6 opencv 3.4.2 age-gender-recognition-retail-0013 モデル詳細 githubはこちらになります。 Use Case and High-Level Description Fully convolutional network for simultaneous Age/Gender recognition. The network is able to recognize age of people in [18, 75] years old range, it is not applicable for children since their faces were not in the training set. 18歳から75歳までの人を判別できるみたいですね、子供の顔のデータセットがないため、それ以下の人は無理みたいです。さすがのAIでもデータがなければどうしようもないですからね。。。 今回特にどんなモデルを使っているのかの説明はないので詳細説明は書いてあるそのままになります。 Specification Metric Value Rotation in-plane ±45˚ Rotation out-of-plane Yaw: ±45˚ / Pitch: ±45˚ Min object width 62 pixels GFlops 0.094 MParams 2.138 Source framework Caffe* 仕様ですが、こんなもんかあと流してみてもらえれば大丈夫だと思います。 判別できる顔の角度と顔の大きさが書いてあります。 GFlopsも大きくないのでサクサク動くと思います。 frameworkはCaffeですね。 Accuracy Metric Value Avg. age error 6.99 years Gender accuracy 95.80% 年齢の誤差が6.99年で性別判定の精度は95.8%とさすがの高性能ですね!! Inputs Image, name: input, shape: 1, 3, 62, 62 in 1, C, H, W format, where: C - number of channels H - image height W - image width Expected color order is BGR. 今回の入力は人の顔画像になります。書いてあることは前回とさほど変わらないので省略します。 ただ、OpenVINOのモデルは入力の名称がgithubに書いてあるものと違うことが多々あるので実装の時に確認しようと思います。 Outputs Name: age_conv3, shape: 1, 1, 1, 1 - Estimated age divided by 100. Name: prob, shape: 1, 2, 1, 1 - Softmax output across 2 type classes [0 - female, 1 - male]. 今回の出力は年齢と性別の二つです。年齢は100で割ったもので、性別はsoftmax出力になっていますね。 それでは張り切って実装行きましょう! 実装 ディレクトリ構造 openvino/ ├─ models/ ├─ age-gender-recognition-retail-0013/ ├─ intel/ ├─ face-detection-0200/ ├─ FP16/ ├─ FP16-INT8/ ├─ FP32/ ├─ face-detection-0200.bin ├─ face-detection-0200.xml ├─ age-gender-recognition-retail-0013/ ├─ FP16/ ├─ FP16-INT8/ ├─ FP32/ ├─ age-gender-recognition-retail-0013.bin ├─ age-gender-recognition-retail-0013.xml ├─ age-gender-recognition-retail-0013.py ├─ input_image.jpg age-gender-recognition-retail-0013.py 顔認識で顔画像を入れて年齢と性別を判定してもらうのもいいのですがそれだけでは味気ないので、前回使用した顔検出モデルの出力を使って判別をしていきたいと思います。 モデルの読み込み 流れは前回と変わりないのでモデルの読み込みをしていきます。 前回はそのまま読み込みをしましたが今回はモデルの詳細を出力するように作っていきます。使うモデルも二つになってます。 import cv2 from openvino.inference_engine import IECore import numpy as np import time # read module ie = IECore() # ***** detection model ***** # read IR model model1 = './intel/face-detection-retail-0004/FP32/face-detection-retail-0004' net1 = ie.read_network(model=model1+'.xml', weights=model1+'.bin') # print model data input_blob_name = list(net1.inputs.keys()) output_blob_name = list(net1.outputs.keys()) print("input_blob_name: {}".format(input_blob_name)) print("output_blob_name: {}".format(output_blob_name)) batch,channel,height,width = net1.inputs[input_blob_name[0]].shape print("input shape: {} x {}".format(height, width)) print("input channnel: {}".format(channel)) # select target device exec_net1 = ie.load_network(network=net1, device_name='CPU', num_requests=1) # ***** recognition model ***** # read IR model model2 = './intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013' net2 = ie.read_network(model=model2+'.xml', weights=model2+'.bin') # print model data input_blob_name = list(net2.inputs.keys()) output_blob_name = list(net2.outputs.keys()) print("input_blob_name: {}".format(input_blob_name)) print("output_blob_name: {}".format(output_blob_name)) batch,channel,height,width = net2.inputs[input_blob_name[0]].shape print("input shape: {} x {}".format(height, width)) print("input channnel: {}".format(channel)) # select target device exec_net2 = ie.load_network(network=net2, device_name='CPU', num_requests=1) 前回のとほとんど同じですね!量は二倍になってますが、。あとはモデル情報をそれぞれ出力しているだけです。 出力は以下のようになっています。 input_blob_name: ['data'] output_blob_name: ['detection_out'] input shape: 300 x 300 input channnel: 3 input_blob_name: ['data'] output_blob_name: ['age_conv3', 'prob'] input shape: 62 x 62 input channnel: 3 githubと比較してみると、input_name以外は同じですね。入力の時だけ気を付ければ問題なさそうです。 input こちらも前回と同じ画像を使っていきます。 顔検出の部分は前回のコードをそのまま使っていきます。 frame = cv2.imread('input_image.jpg') img = cv2.resize(frame, (width, height)) img = img.transpose((2, 0, 1)) img = img.reshape((1, channel, height, width)) # inference (face) out = exec_net.infer(inputs={'image': img}) out = out['detection_out'] out = np.squeeze(out) # detection process for detection in out: # conf value confidence = float(detection[2]) # outputs rect if confidence > 0.6: # translate box into image xmin = int(detection[3] * frame.shape[1]) ymin = int(detection[4] * frame.shape[0]) xmax = int(detection[5] * frame.shape[1]) ymax = int(detection[6] * frame.shape[0]) # adjustment if xmin < 0: xmin = 0 if ymin < 0: ymin = 0 if xmax > frame.shape[1]: xmax = frame.shape[1] if ymax > frame.shape[0]: ymax = frame.shape[0] 顔認識モデルへの入力はこの画像の顔部分、このxmin, ymin, xmax, ymaxなのでこの四点で画像をトリミングします。 ここから先のコードは全部if confidence > 0.6:の条件文の中のものなのでインデントにご注意ください。 face = frame[ymin: ymax, xmin: xmax] そのあとの処理は一つ目のモデルと同じなのでパパっとやっちゃいましょう!ここでもgithubとinput_nameが違うので注意しましょう。 face = cv2.resize(face, (62, 62)) face = face.transpose((2, 0, 1)) face = face.reshape((1, 3, 62, 62)) out = exec_net2.infer(inputs={'data': face}) print(out) >>> {'age_conv3': array([[[[0.41843498]]]], dtype=float32), 'prob': array([[[[0.0455569]], [[0.9544431]]]], dtype=float32)} {'age_conv3': array([[[[0.2380444]]]], dtype=float32), 'prob': array([[[[0.97024095]], [[0.02975903]]]], dtype=float32)} 年齢のほうは先ほど確認したように100で割った値が出力されているので100をかけてあげましょう。 また、性別はsoftmax出力になっていることがわかりますね!この出力はただ単に大きいほうを採用すればよいです。インデックスの0が女性で1が男性です。 Name: prob, shape: 1, 2, 1, 1 - Softmax output across 2 type classes [0 - female, 1 - male]. age = int(np.squeeze(out['age_conv3']) * 100) prob = np.squeeze(out['prob']) if prob[0] > prob[1]: sex = "female" else: sex = "male" あとはこれを表示して終了になります。 cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), color=(240, 180, 0), thickness=2) cv2.putText(frame, text=f"{age} {sex}", org=(xmin, ymax+25), fontFace=cv2.FONT_HERSHEY_PLAIN, fontScale=2.0, color=(0, 0, 255), thickness=2, lineType=cv2.LINE_AA) cv2.imwrite('output.jpg', frame) 全コード こちらに記述しているコードは画像を保存ではなく表示するものになっております。また、comment outと書いてある部分のコメントアウトを外すとカメラでのストリーミングもできるようにしておりますのでやってみてください。 import cv2 from openvino.inference_engine import IECore import numpy as np import time # read module ie = IECore() # ***** detection model ***** # read IR model model1 = './intel/face-detection-retail-0004/FP32/face-detection-retail-0004' net1 = ie.read_network(model=model1+'.xml', weights=model1+'.bin') # print model data input_blob_name = list(net1.inputs.keys()) output_blob_name = list(net1.outputs.keys()) print("input_blob_name: {}".format(input_blob_name)) print("output_blob_name: {}".format(output_blob_name)) batch,channel,height,width = net1.inputs[input_blob_name[0]].shape print("input shape: {} x {}".format(height, width)) print("input channnel: {}".format(channel)) # select target device exec_net1 = ie.load_network(network=net1, device_name='CPU', num_requests=1) # ***** recognition model ***** # read IR model model2 = './intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013' net2 = ie.read_network(model=model2+'.xml', weights=model2+'.bin') # print model data input_blob_name = list(net2.inputs.keys()) output_blob_name = list(net2.outputs.keys()) print("input_blob_name: {}".format(input_blob_name)) print("output_blob_name: {}".format(output_blob_name)) batch,channel,height,width = net2.inputs[input_blob_name[0]].shape print("input shape: {} x {}".format(height, width)) print("input channnel: {}".format(channel)) # select target device exec_net2 = ie.load_network(network=net2, device_name='CPU', num_requests=1) """comment out # set video # cap = cv2.VideoCapture(0) # cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) """ while(True): start_time = time.time() """comment out ret, frame = cap.read() # このコメントアウトを外した際は一つ下のコードのframe = cv2.imread('test.jpg')は消してください。 """ frame = cv2.imread('input_image.jpg') img = cv2.resize(frame, (300, 300)) img = img.transpose((2, 0, 1)) img = img.reshape((1, 3, 300, 300)) # inference (face) out = exec_net1.infer(inputs={'data': img}) out = out['detection_out'] out = np.squeeze(out) # detection process for detection in out: # conf value confidence = float(detection[2]) # outputs rect if confidence > 0.6: # translate box into image xmin = int(detection[3] * frame.shape[1]) ymin = int(detection[4] * frame.shape[0]) xmax = int(detection[5] * frame.shape[1]) ymax = int(detection[6] * frame.shape[0]) # adjustment if xmin < 0: xmin = 0 if ymin < 0: ymin = 0 if xmax > frame.shape[1]: xmax = frame.shape[1] if ymax > frame.shape[0]: ymax = frame.shape[0] # recognition phase face = frame[ymin: ymax, xmin: xmax] face = cv2.resize(face, (62, 62)) face = face.transpose((2, 0, 1)) face = face.reshape((1, 3, 62, 62)) out = exec_net2.infer(inputs={'data': face}) age = int(np.squeeze(out['age_conv3']) * 100) prob = np.squeeze(out['prob']) if prob[0] > prob[1]: sex = "female" else: sex = "male" cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), color=(240, 180, 0), thickness=2) cv2.putText(frame, text=f"{age} {sex}", org=(xmin, ymax+25), fontFace=cv2.FONT_HERSHEY_PLAIN, fontScale=2.0, color=(0, 0, 255), thickness=2, lineType=cv2.LINE_AA) cv2.imshow('frame', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break """"comment out cap.release() """ cv2.destroyAllWindows() 終わりに age-gender-recognition編は終了になります。ここまで一緒にやってくださった方ありがとうございます! 今回は二つのモデルを使って入力画像から顔検出を行って年齢と性別の判定までの推論を行いました。 これができればだいぶ楽しくなったのではないでしょうか? 上のコードのcomment outを外してみれば、自分の顔でライブストリーミングができると思いますので是非やってみて、友人にでも見せてみてください。きっとすごいと言ってくれるでしょう! 次回は何をするか迷いましたが、顔識別の違うモデルか、人検出あたりをしようかなと思っています。 それではお疲れさまでした! よい機械学習ライフを!!!! 参考 第一回の記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ラズパイ+OpenCVの画像処理の速度調査

はじめに 以前、ラズパイを使って子供の寝相の監視をしたが、そのときにMicroSDの容量不足に悩まされた。 OpenCVで保存すれば容量は減らせるのだが、撮影間隔を守ることができなかったので、 実際にどれぐらい時間に対して不安定なのかを確認していた。 もったいないのでとりあえず公開しておく。 時間測定用コード 適当な時間待ってから写真を撮って保存するのにかかった時間を計測するプログラムを書いた。 これを起動して、一晩放置してみた。 from picamera import PiCamera from picamera import array from time import sleep import datetime import cv2 count = 0 with open("time.csv", "w") as w: with PiCamera() as cam: while count < 10000: with array.PiRGBArray(cam) as stream: start = datetime.datetime.now() cam.capture(stream, 'rgb') cv2.imwrite("test_bgr.jpg", stream.array) end = datetime.datetime.now() w.write(str((end - start).total_seconds())) w.write("\n") sleep(0.5) count += 1 結果 1万回の撮影結果を時間ごとにまとめた結果が下記の表。 例えば、0.5~0.6秒かかったのは10000回中5750回となる。 もっとsleepの時間を短くしたら結果は変わるかもしれないが、 毎秒撮影するときに10000回中に数回は1秒以上の時間がかかるとなると、 1時間に一度、撮影に1秒以上かかるタイミングがあると考えて良さそうだ。 毎秒動作させることが必須な使い方には、ラズパイ+OpenCVは向かなさそうだということがこの実験からわかった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む