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

Python を使用して Azure Database for MySQL に接続して 自動生成データを書き込みしてみました

概要 この記事 の応用編です。 指定した件数・間隔でデータを自動生成し、MySQLサーバのデータベースに書き込む Python プログラムです。テーブルの初期化、データの読み込み、データの削除も可能となっています。 ローカル環境 macOS Monterey 12.3 python 3.8.12 Azure CLI 2.34.1 mysql Ver 8.0.28 for macos12.2 on x86_64 (Homebrew) 前提条件 Azure環境がすでに用意されていること(テナント/サブスクリプション) ローカル環境に「azure cli」がインストールされていること ローカル環境に「mysql」がインストールされていること 事前準備 この記事 を実行し、Azure Database for MySQL を Single構成 で作成できていること 項目 値 MySQLサーバ名 iturumysql01.mysql.database.azure.com データベース名 iotdummydb 管理者名 adminadmin@iturumysql01 管理者パスワード HogeHogeHoge! PythonプログラムからのMySQLサーバへのDB操作 Pythonプログラム 作成するテーブル名:inventory mysql_IoTdummy.py import mysql.connector from mysql.connector import errorcode import time from datetime import date, datetime import random import json import argparse import string from faker.factory import Factory import pandas as pd import numpy as np from tqdm import tqdm # ダミーデータ作成のための Faker の使用 Faker = Factory.create fake = Faker() fake = Faker("ja_JP") # ダミーセクション?(大文字アルファベットを定義) section = string.ascii_uppercase # MySQLサーバ接続設定情報 config = { 'host': 'iturumysql01.mysql.database.azure.com', 'user': 'adminadmin@iturumysql01', 'password': 'HogeHogeHoge!', 'database': 'iotdummydb' } # ダミーデータの作成 def iot_json_data(count, proc): iot_items = json.dumps({ 'items': [{ 'iot_id': i, # IoT id 'proc': proc, # データ生成プロセス名 'section': random.choice(section), # IoT機器セクション 'iot_num': fake.zipcode(), # IoT機器番号 'iot_state': fake.prefecture(), # IoT設置場所 'val_1': random.uniform(100, 200), # IoT値−1 'val_2': random.uniform(50, 90), # IoT値−2 'created_at': generate_time() # データ生成時間 } for i in range(count) ] }, ensure_ascii=False).encode('utf-8') return iot_items # ダミーデータの生成時間 def generate_time(): dt_time = datetime.now() gtime = json_trans_date(dt_time) return gtime # date, datetimeの変換関数 def json_trans_date(obj): # 日付型を文字列に変換 if isinstance(obj, (datetime, date)): return obj.isoformat() # 上記以外は対象外. raise TypeError ("Type %s not serializable" % type(obj)) # MySQLサーバ接続 def ConnectionToMySQL(): try: conn = mysql.connector.connect(**config) print("\nConnection established\n") except mysql.connector.Error as err: if err.errno == errorcode.ER_ACCESS_DENIED_ERROR: return 0, "Something is wrong with the user name or password" elif err.errno == errorcode.ER_BAD_DB_ERROR: return 0, "Database does not exist" else: return 0, err else: cursor = conn.cursor() return 1, conn # メイン : ターミナル出力用 def tm_main(count, proc, wait): print('ターミナル 出力\n') # ダミーデータ生成 iotjsondata = iot_json_data(count, proc) json_dict = json.loads(iotjsondata) # ターミナル出力 for item in json_dict['items']: print(item) time.sleep(wait) # メイン : Show Database def show_db_main(): print('MySqlサーバへの接続確認') code, conn = ConnectionToMySQL() if code == 0 : print(conn) else : print(conn) conn.close() # メイン : Init Database def init_db_main(): print('データベース・テーブルの新規作成') # DBへの接続 code, conn = ConnectionToMySQL() if code == 0 : print(conn) return else : cursor = conn.cursor() # Drop previous table of same name if one exists cursor.execute("DROP TABLE IF EXISTS inventory;") print("\tFinished dropping table (if existed).") # Create table cursor.execute("\ CREATE TABLE inventory (\ id int NOT NULL AUTO_INCREMENT PRIMARY KEY, \ iot_id int NOT NULL, \ proc VARCHAR(10) NOT NULL, \ section VARCHAR(2) NOT NULL, \ iot_num VARCHAR(10) NOT NULL, \ iot_state VARCHAR(10) NOT NULL, \ val_1 float NOT NULL, \ val_2 float NOT NULL, \ created_at datetime DEFAULT NULL, \ updated_at datetime DEFAULT NULL \ );"\ ) print("\tFinished creating table.\n") # 作成したテーブル情報の取得 cursor.execute("show columns from inventory;") rows = cursor.fetchall() # 多次元リストを Pandas DataFrame を利用して表示する df = pd.DataFrame(rows, columns = ['Field','Type','Null','Key','Default','Extra']) print(df) # Cleanup cursor.close() conn.close() print("Done.") # メイン : Write Database def write_db_main(count, proc, wait): print('データベース・テーブルへのデータ書き込み') # DBへの接続 code, conn = ConnectionToMySQL() if code == 0 : print(conn) return else : cursor = conn.cursor() # ダミーデータ生成 iotjsondata = iot_json_data(count, proc) json_dict = json.loads(iotjsondata) # インサート項目の定義 insert_sql = "INSERT INTO inventory \ (iot_id, proc, section, iot_num, iot_state, val_1, val_2, created_at) \ values (%s,%s,%s,%s,%s,%s,%s,%s);" # プログレスバーで進捗状況を表示 for item in tqdm(json_dict['items']): # データのインサート # print(list(item.values())) cursor.execute(insert_sql, list(item.values())) conn.commit() time.sleep(wait) # Cleanup cursor.close() conn.close() print("Inserted",count,"row(s) of data.") print("Done.") # メイン : Read Database def read_db_main(): print('データベース・テーブルへのデータ読み込み') # DBへの接続 code, conn = ConnectionToMySQL() if code == 0 : print(conn) return else : cursor = conn.cursor() # Read table data cursor.execute("SELECT * FROM inventory;") rows = cursor.fetchall() print(f'Read: {cursor.rowcount}, row(s) of data.') # print all rows [print(f'Data row = {row}') for row in rows] # Cleanup cursor.close() conn.close() print("Done.") # メイン : Delete Database def delete_db_main(): print('データベース・テーブル・データの全削除') # DBへの接続 code, conn = ConnectionToMySQL() if code == 0 : print(conn) return else : cursor = conn.cursor() # Delete table data cursor.execute("DELETE FROM inventory;") rows = cursor.fetchall() print(rows) conn.commit() # Cleanup cursor.close() conn.close() print("Done.") # メイン if __name__ == '__main__': parser = argparse.ArgumentParser(description='ダミーデータの自動生成からのDBへの書込み') parser.add_argument('--count', type=int, default=5, help='データ作成件数(デフォルト:5件)') parser.add_argument('--proc', type=str, default='111', help='データ作成プロセス名(デフォルト:111)') parser.add_argument('--mode', type=str, default='tm', help='tm(Terminal(デフォルト))/ db(db操作)') parser.add_argument('--wait', type=float, default=1, help='データ生成間隔(デフォルト:1.0秒)') parser.add_argument('--db', type=str, default='show', help='show(デフォルト) / init / write / read / delete') args = parser.parse_args() start = time.time() if (args.mode == 'tm'): tm_main(args.count, args.proc, args.wait) elif (args.mode == 'db'): if (args.db == 'show'): show_db_main() elif (args.db == 'init'): init_db_main() elif (args.db == 'write'): write_db_main(args.count, args.proc, args.wait) elif (args.db == 'read'): read_db_main() elif (args.db == 'delete'): delete_db_main() else : print("パラメータ設定を確認ください --help") making_time = time.time() - start print("") print("処理時間:{0}".format(making_time) + " [sec]") print("") プログラムの実行 実行パラメータを指定することにより以下の処理が可能となります データ生成 パラメータ --mode tm : 生成データのターミナル出力(デフォルト) db : DB操作の実施 --proc : 本プログラム実行場所の識別子(デフォルト:'111') --count : 生成データの件数 --wait : データ書込み時の間隔(秒)(デフォルト:1.0秒、最小:0秒) DB操作 パラメータ(上記「mode」パラメータで「db」を選択することにより有効となります) --db show : データベースへの接続確認(デフォルト) init : テーブルの新規作成(存在していれば削除後に再作成) write : テーブルへの生成データ書き込み read : テーブルからの全件データ読み込み delete : テーブルの全件データ削除 ヘルプ表示 $ python mysql_IoTdummy.py --help usage: mysql_IoTdummy.py [-h] [--count COUNT] [--proc PROC] [--mode MODE] [--wait WAIT] [--db DB] ダミーデータの自動生成からのDBへの書込み optional arguments: -h, --help show this help message and exit --count COUNT データ作成件数(デフォルト:5件) --proc PROC データ作成プロセス名(デフォルト:111) --mode MODE tm(Terminal(デフォルト))/ db(db操作) --wait WAIT データ生成間隔(デフォルト:1.0秒) --db DB show(デフォルト) / init / write / read / delete ターミナルへの生成データの出力 $ python mysql_IoTdummy.py ターミナル 出力 {'iot_id': 0, 'proc': '111', 'section': 'B', 'iot_num': '080-6847', 'iot_state': '島根県', 'val_1': 140.61912005313204, 'val_2': 72.17919110391685, 'created_at': '2022-04-02T00:25:52.683694'} {'iot_id': 1, 'proc': '111', 'section': 'A', 'iot_num': '065-8086', 'iot_state': '兵庫県', 'val_1': 186.96659888711127, 'val_2': 65.56487417259912, 'created_at': '2022-04-02T00:25:52.683717'} {'iot_id': 2, 'proc': '111', 'section': 'G', 'iot_num': '879-6045', 'iot_state': '京都府', 'val_1': 181.3697189155115, 'val_2': 79.14644988820804, 'created_at': '2022-04-02T00:25:52.683730'} {'iot_id': 3, 'proc': '111', 'section': 'P', 'iot_num': '798-2794', 'iot_state': '京都府', 'val_1': 160.0290794152534, 'val_2': 71.33695982774306, 'created_at': '2022-04-02T00:25:52.683742'} {'iot_id': 4, 'proc': '111', 'section': 'C', 'iot_num': '026-9269', 'iot_state': '三重県', 'val_1': 193.99969925933686, 'val_2': 85.21973561601754, 'created_at': '2022-04-02T00:25:52.683753'} 処理時間:5.0174829959869385 [sec] DBへの接続確認 ## 正常時 $ python mysql_IoTdummy.py --mode db MySqlサーバへの接続確認 Connection established <mysql.connector.connection_cext.CMySQLConnection object at 0x7ff4589f91f0> 処理時間:0.3275420665740967 [sec] ## 異常時(DBサーバが停止していた場合) $ python mysql_IoTdummy.py --mode db MySqlサーバへの接続確認 9030 (28000): This MySQL server is currently stopped. Start the server to establish connectivity. 処理時間:0.22968602180480957 [sec] テーブルの新規作成 $ python mysql_IoTdummy.py --mode db --db init データベース・テーブルの新規作成 Connection established Finished dropping table (if existed). Finished creating table. Field Type Null Key Default Extra 0 id b'int(11)' NO PRI None auto_increment 1 iot_id b'int(11)' NO None 2 proc b'varchar(10)' NO None 3 section b'varchar(2)' NO None 4 iot_num b'varchar(10)' NO None 5 iot_state b'varchar(10)' NO None 6 val_1 b'float' NO None 7 val_2 b'float' NO None 8 created_at b'datetime' YES None 9 updated_at b'datetime' YES None Done. 処理時間:1.008288860321045 [sec] データの書き込み ## 8件のデータを0.2秒間隔で書き込む $ python mysql_IoTdummy.py --mode db --db write --count 8 --wait 0.2 データベース・テーブルへのデータ書き込み Connection established 100%|██████████████████████████████████████████████████████████████████████████████████████████| 8/8 [00:03<00:00, 2.46it/s] Inserted 8 row(s) of data. Done. 処理時間:3.45925235748291 [sec] データの全件読み込み $ python mysql_IoTdummy.py --mode db --db read データベース・テーブルへのデータ読み込み Connection established Read: 8, row(s) of data. Data row = (1, 0, '111', 'D', '825-0233', '北海道', 111.725, 76.5301, datetime.datetime(2022, 4, 2, 0, 32, 6), None) Data row = (2, 1, '111', 'W', '143-6705', '新潟県', 196.401, 60.7279, datetime.datetime(2022, 4, 2, 0, 32, 6), None) Data row = (3, 2, '111', 'V', '230-7952', '福島県', 121.454, 80.9434, datetime.datetime(2022, 4, 2, 0, 32, 6), None) Data row = (4, 3, '111', 'F', '932-1487', '島根県', 102.066, 80.4288, datetime.datetime(2022, 4, 2, 0, 32, 6), None) Data row = (5, 4, '111', 'I', '315-0935', '岐阜県', 168.707, 77.4312, datetime.datetime(2022, 4, 2, 0, 32, 6), None) Data row = (6, 5, '111', 'T', '919-3894', '島根県', 185.376, 51.655, datetime.datetime(2022, 4, 2, 0, 32, 6), None) Data row = (7, 6, '111', 'Z', '093-1699', '静岡県', 162.089, 66.7628, datetime.datetime(2022, 4, 2, 0, 32, 6), None) Data row = (8, 7, '111', 'N', '224-5406', '静岡県', 139.609, 63.2027, datetime.datetime(2022, 4, 2, 0, 32, 6), None) Done. 処理時間:0.33246612548828125 [sec] データの全件削除 $ python mysql_IoTdummy.py --mode db --db delete データベース・テーブル・データの全削除 Connection established [] Done. 処理時間:0.3926122188568115 [sec] まとめ これで、DBのデータ関連の各種テストがやりやすくなるかなぁ、、、、、
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】Tkinterで画面にフィットするフレームを実装する

注意事項 自分用の簡単なまとめであり,説明はほとんどありません. 内容は時間があるときに補充します. 心の声 いまどき私のようにPythonでGUIを作成する人はいるのか... ポイント Frameをpackで配置する場合は,expand,fillに注意する. expand:親フレームの余ったスペースに対して,その分配を決定する. 分配は,expand=Trueを持つフレームのみに行われる. fill:該当フレームの利用可能なスペースに対して,指定された方向に拡大する. fill=tk.Yでは,縦方向にのみ拡張する. fill=tk.Xでは,横方向にのみ拡張する. fill=tk.BOTHでは,縦方向と横方向の両方に拡張する. Frameをgridで配置する場合は,rowconfigure,columnconfigure,stickyに注意する. rowconfigure:行の重みを指定する.対象の行は縦方向に引き伸ばされる. 複数行に設定された場合は,それぞれの重みで引き伸ばされる. rowconfigure(0, weight=1)とrowconfigure(1, weight=1)なら,0行目と1行目は同じ量だけ引き伸ばされる. rowconfigure(0, weight=3)とrowconfigure(1, weight=1)なら,0行目は1行目の 3倍 引き伸ばされる. columnconfigure:列の重みを指定する.対象の列は横方向に引き伸ばされる. 複数列に設定された場合は,それぞれの重みで引き伸ばされる. (rowconfigureと同様) sticky:該当フレームの利用可能なスペースに対して,指定された方向に拡大する.また,配置位置も指定する. sticky=tk.N+tk.Sでは,縦方向にのみ拡張する. sticky=tk.W+tk.Eでは,横方向にのみ拡張する. sticky=tk.N+tk.S+tk.W+tk.Eでは,縦方向と横方向の両方に拡張する. 使用コード import tkinter as tk class MyGui(tk.Frame): # コンストラクタの定義 def __init__(self, master): # 継承元クラス(tk.Frame)のコンストラクタを呼び出し tk.Frame.__init__(self, master) # このクラスで使用するメインフレームを定義 self.frame = tk.Frame(self) self.frame.pack(expand=True, fill=tk.BOTH) self.frame.rowconfigure(0, weight=1) # 0行目を縦方向に引き伸ばす self.frame.columnconfigure(1, weight=1) # 1列目を横方向に引き伸ばす # メインフレームを2つに分割(左はサイド,右はヘッダー・ボディ・フッター) self.leftFrame = tk.Frame(self.frame, bg="#3182bd") self.leftFrame.grid(row=0, column=0, ipadx=5, ipady=5, sticky=tk.N+tk.S) self.rightFrame = tk.Frame(self.frame, bg="#9ecae1") self.rightFrame.grid(row=0, column=1, ipadx=5, ipady=5, sticky=tk.W+tk.E+tk.N+tk.S) # 縦横に引き伸ばす self.rightFrame.rowconfigure(1, weight=1) # 1行目を縦方向に引き伸ばす設定 self.rightFrame.columnconfigure(0, weight=1) # 0列目を横方向に引き伸ばす設定 # self.rightFrame内にヘッダー・ボディ・フッター用のフレームを定義 self.headerFrame = tk.Frame(self.rightFrame, bg="#31a354") self.headerFrame.grid(row=0, column=0, ipadx=5, ipady=5, sticky=tk.W+tk.E) self.bodyFrame = tk.Frame(self.rightFrame, bg="#a1d99b") self.bodyFrame.grid(row=1, column=0, ipadx=5, ipady=5, sticky=tk.W+tk.E+tk.N+tk.S) self.footerFrame = tk.Frame(self.rightFrame, bg="#e5f5e0") self.footerFrame.grid(row=2, column=0, ipadx=5, ipady=5, sticky=tk.W+tk.E) # サイド・ヘッダー・ボディ・フッターを描画 self.createSide() self.createHeader() self.createBody() self.createFooter() def createSide(self): widgetSide = tk.Label(self.leftFrame, text="Side", font=("", 14)) widgetSide.pack(expand=True) return def createHeader(self): widgetHeader = tk.Label(self.headerFrame, text="Header", font=("", 14)) widgetHeader.pack(expand=True) return def createBody(self): widgetBody = tk.Label(self.bodyFrame, text="Body", font=("", 14)) widgetBody.pack(expand=True) return def createFooter(self): widgetFooter = tk.Label(self.footerFrame, text="Footer", font=("", 14)) widgetFooter.pack(expand=True) return 使用例 # rootインスタンスを生成 root = tk.Tk() # 最大化 root.state("zoomed") # For Windows # root.attribute("-zoom", "1") # For Linux # root下にフレームを追加 frame = tk.Frame(root) frame.pack(expand=True, fill=tk.BOTH) # frame下に自作フレームを追加 myGui = MyGui(frame) myGui.pack(expand=True, fill=tk.BOTH) root.mainloop()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】Tkinterで画面にフィットするフレームを作成

注意事項 自分用の簡単なまとめであり,説明はほとんどありません. 内容は時間があるときに補充します. 心の声 いまどき私のようにPython&TkinterでGUIを作成する人はいるのか... ポイント Frameをpackで配置する場合は,expand,fillに注意する. expand:親フレームの余ったスペースに対して,その分配を決定する. 分配は,expand=Trueを持つフレームのみに行われる. fill:該当フレームの利用可能なスペースに対して,指定された方向に拡大する. fill=tk.Yでは,縦方向にのみ拡張する. fill=tk.Xでは,横方向にのみ拡張する. fill=tk.BOTHでは,縦方向と横方向の両方に拡張する. Frameをgridで配置する場合は,rowconfigure,columnconfigure,stickyに注意する. rowconfigure:行の重みを指定する.対象の行は縦方向に引き伸ばされる. 複数行に設定された場合は,それぞれの重みで引き伸ばされる. rowconfigure(0, weight=1)とrowconfigure(1, weight=1)なら,0行目と1行目は同じ量だけ引き伸ばされる. rowconfigure(0, weight=3)とrowconfigure(1, weight=1)なら,0行目は1行目の 3倍 引き伸ばされる. columnconfigure:列の重みを指定する.対象の列は横方向に引き伸ばされる. 複数列に設定された場合は,それぞれの重みで引き伸ばされる. (rowconfigureと同様) sticky:該当フレームの利用可能なスペースに対して,指定された方向に拡大する.また,配置位置も指定する. sticky=tk.N+tk.Sでは,縦方向にのみ拡張する. sticky=tk.W+tk.Eでは,横方向にのみ拡張する. sticky=tk.N+tk.S+tk.W+tk.Eでは,縦方向と横方向の両方に拡張する. 使用コード import tkinter as tk class MyGui(tk.Frame): # コンストラクタの定義 def __init__(self, master): # 継承元クラス(tk.Frame)のコンストラクタを呼び出し tk.Frame.__init__(self, master) # このクラスで使用するメインフレームを定義 self.frame = tk.Frame(self) self.frame.pack(expand=True, fill=tk.BOTH) self.frame.rowconfigure(0, weight=1) # 0行目を縦方向に引き伸ばす self.frame.columnconfigure(1, weight=1) # 1列目を横方向に引き伸ばす # メインフレームを2つに分割(左はサイド,右はヘッダー・ボディ・フッター) self.leftFrame = tk.Frame(self.frame, bg="#3182bd") self.leftFrame.grid(row=0, column=0, ipadx=5, ipady=5, sticky=tk.N+tk.S) self.rightFrame = tk.Frame(self.frame, bg="#9ecae1") self.rightFrame.grid(row=0, column=1, ipadx=5, ipady=5, sticky=tk.W+tk.E+tk.N+tk.S) # 縦横に引き伸ばす self.rightFrame.rowconfigure(1, weight=1) # 1行目を縦方向に引き伸ばす設定 self.rightFrame.columnconfigure(0, weight=1) # 0列目を横方向に引き伸ばす設定 # self.rightFrame内にヘッダー・ボディ・フッター用のフレームを定義 self.headerFrame = tk.Frame(self.rightFrame, bg="#31a354") self.headerFrame.grid(row=0, column=0, ipadx=5, ipady=5, sticky=tk.W+tk.E) self.bodyFrame = tk.Frame(self.rightFrame, bg="#a1d99b") self.bodyFrame.grid(row=1, column=0, ipadx=5, ipady=5, sticky=tk.W+tk.E+tk.N+tk.S) self.footerFrame = tk.Frame(self.rightFrame, bg="#e5f5e0") self.footerFrame.grid(row=2, column=0, ipadx=5, ipady=5, sticky=tk.W+tk.E) # サイド・ヘッダー・ボディ・フッターを描画 self.createSide() self.createHeader() self.createBody() self.createFooter() def createSide(self): widgetSide = tk.Label(self.leftFrame, text="Side", font=("", 14)) widgetSide.pack(expand=True) return def createHeader(self): widgetHeader = tk.Label(self.headerFrame, text="Header", font=("", 14)) widgetHeader.pack(expand=True) return def createBody(self): widgetBody = tk.Label(self.bodyFrame, text="Body", font=("", 14)) widgetBody.pack(expand=True) return def createFooter(self): widgetFooter = tk.Label(self.footerFrame, text="Footer", font=("", 14)) widgetFooter.pack(expand=True) return 使用例 # rootインスタンスを生成 root = tk.Tk() # 最大化 root.state("zoomed") # For Windows # root.attribute("-zoom", "1") # For Linux # root下にフレームを追加 frame = tk.Frame(root) frame.pack(expand=True, fill=tk.BOTH) # frame下に自作フレームを追加 myGui = MyGui(frame) myGui.pack(expand=True, fill=tk.BOTH) root.mainloop()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

機械学習に向けた日記

教養数学(微分積分学・統計学・線形代数) 2022年2月20日『統計の歴史』 題名通り統計を歴史という切り口で説明する本です。。統計が組織や個人にとってどのような立ち位置で発展してきたか知るために読みました。 「数字で表現できない知識は貧弱で不十分で、科学的な思考手順を踏んでいない。」ウィリアムトムソン 2022年4月2日『数学公式のはなし』 機械学習Pythonモジュール Numpy/Scipy/ 『東京大学のデータサイエンティスト育成講座』 Django 2022年4月3日チュートリアル進める
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

機械学習の習得に向けた日記

教養数学(微分積分学・統計学・線形代数) 2022年2月20日『統計の歴史』 題名通り統計を歴史という切り口で説明する本です。。統計が組織や個人にとってどのような立ち位置で発展してきたか知るために読みました。 「数字で表現できない知識は貧弱で不十分で、科学的な思考手順を踏んでいない。」ウィリアムトムソン 2022年4月2日『数学公式のはなし』 機械学習Pythonモジュール Numpy/Scipy/ 『東京大学のデータサイエンティスト育成講座』 Django 2022年4月3日チュートリアル進める
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

俺は長文matplotlibをやめるぞ!ジョジョーーーッ!!

タイトルオチです。 記事の背景 あきとしのスクラップノートさんの以下の記事を読み、感銘を受けました。 [python] context manger を使ってmatplotlibの図を大量生産する そう、matplotlibってやたら行数多くなるんですよね…! まだ読まれてない方はこの記事より先にぜひ読んでください。 記事の内容をgithubでも公開してくださっています。 contextplt -github- 本記事では上記の記事に全面的に依拠しつつ、 データの渡し方をdictからdataclassにアレンジしてみました。 ささやかな差分ですが、自分の備忘録代わりにメモします。 dataclassにしてみたコード from typing import NamedTuple, Optional import dataclasses from dataclasses import dataclass import pandas as pd import numpy as np import matplotlib.pyplot as plt import japanize_matplotlib @dataclass class BasicPlotArgs: xlim: Optional[tuple[float]] = None ylim: Optional[tuple[float]] = None xlabel: str = "" ylabel: str = "" title: str = "" save_path: Optional[str] = None figsize: tuple[int] = (6,4) dpi: int = 150 tight: bool = False show: bool = True class BasicPlot(): def __init__(self, pa: Optional[BasicPlotArgs]=None): if not pa: pa = BasicPlotArgs() self.fig = plt.figure(figsize=pa.figsize, dpi=pa.dpi) self.ax = self.fig.add_subplot(111) self.ax.set_xlabel(pa.xlabel) self.ax.set_ylabel(pa.ylabel) self.ax.set_xlim(pa.xlim) if pa.xlim else None self.ax.set_ylim(pa.ylim) if pa.ylim else None self.save_path = pa.save_path self.title = pa.title self.tight = pa.tight self.show = pa.show def __enter__(self): return(self) def __exit__(self, exc_type, exc_value, exc_traceback): self.option() plt.title(self.title) plt.tight_layout() if self.tight else None plt.savefig(self.save_path) if self.save_path else None plt.show() if self.show else None def option(self): '''This method is for additional graphic setting. See DatePlot for example.''' pass xx = np.linspace(-5,5,20) yy = xx*xx with BasicPlot() as p: p.ax.plot(xx,yy) dataclassを定義した以外は元の記事とほぼそのままです。 元の記事の中ほどに出てきますが、複数の引数を辞書で渡すやり方も紹介されています。 辞書の代わりにdataclassを使うことで、図の設定を少し流用しやすくするという意図です。 BasicPlotArgsを @dataclass class NewPlotArgs(BasicPlotArgs): xlabel:str = 'NEW!' newplotargs = NewPlotArgs(xlim=(0,10), ylim=(0,10)) with BasicPlot(newplotargs) as p: p.ax.plot(xx,yy) いくつかの図の設定をテンプレート的に使い分けたいときや、 BasicPlotを外部モジュールとして、複数のファイルから利用するときに便利かもしれません。 ただし、dataclassを使うことによる不便もあります。 新たな引数を設定するときに、Plot用のクラスとdataclassの2箇所変更する必要が出てきます。 アドホックな分析であれば、with文の中に書く方が手っ取り早いでしょう。 たとえば新たにx軸の目盛りをいじりたいときは、以下のようにします。 xx = np.linspace(-5,5,20) yy = xx*xx with BasicPlot() as p: p.ax.set_xticks(range(-5,6)) p.ax.plot(xx,yy) 簡単ですね! もし頻繁にその設定を変更する必要があれば、クラスのオーバーライドをしてもいいかもしれません。 @dataclass class AnotherPlotArgs(BasicPlotArgs): xticks: Any = None class AnotherPlot(BasicPlot): def __init__(self, pa: Optional[AnotherPlotArgs] = None): super().__init__(pa) self.ax.set_xticks(pa.xticks) 先ほどのset_xticksはシンプルに1つの位置引数を取りました。 もしset_xticksにlabelsのようなキーワード引数も渡したいときは、 やや強引ですが、位置引数をリスト、キーワード引数を辞書にして、 Plot用のクラスの中でアンパックするやり方も使えるかもしれません。 class Args(NamedTuple): ag: list = [] kw: dict = {} @dataclass class AnotherPlotArgs(BasicPlotArgs): xticks: Optional[Args] = None class AnotherPlot(BasicPlot): def __init__(self, pa: Optional[AnotherPlotArgs] = None): super().__init__(pa) self.ax.set_xticks(*pa.xticks.ag,**pa.xticks.kw) if pa.xticks else None self.ax.bar(*pa.bar.ag, **pa.bar.kw) if pa.bar else None xticks = Args([np.arange(5)], {'labels':['G1', 'G2', 'G3', 'G4', 'G5']}) apa = AnotherPlotArgs(xticks=xticks)) with AnotherPlot(apa) as p: p.ax.plot(xx,yy) が、この煩雑さに見合うほど楽になるかというと正直微妙です。 特にキーワード引数をいちいち辞書にするのがめんどうです。 やろうと思えば可能ではある(が、めんどくさい)くらいの認識がちょうどよいかもしれません。 まとめ 基本的にはwith文に都度書いていくのが1番手っ取り早そうです。 繰り返しが多くなってテンプレート化したくなったら、この記事の内容が役に立つかもしれません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonの判定(排他的論理和)

pythonでの排他的論理和の判定 例えば、入力チェックをする際に、両方値が入っているもしくは、入っていない場合は不可で、 どちらか一方のみ値が入力されている状態を正とする場合のチェック処理 xor.py # 入力データ(判定対象のデータ) # data = { # "data1": "", # "data2": "test" #} # boolでキャスト(しなくてもよいが明示的にしてみた) if bool(data["data1"]) != bool(data["data2"]): print("排他的") 上記のように!=を使えば排他的論理和の確認ができる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者が始める】githubの環境構築(前編)

Pythonの勉強を始めました。 『ゼロから作るDeep Learning』にて、githubで公開しているスクリプトを使用するために、今回githubを使い始めました。 githubを使い始める際の手順は下記リンクで行なっています。 上記サイトの手順5 「Githubにプッシュする」で、git initを実行すると下記のようなヒントが表示されました。 hint: Using 'master' as the name for the initial branch. This default branch name hint: is subject to change. To configure the initial branch name to use in all hint: of your new repositories, which will suppress this warning, call: hint: hint: git config --global init.defaultBranch <name> hint: hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and hint: 'development'. The just-created branch can be renamed via this command: hint: hint: git branch -m <name> Initialized empty Git repository in /Users/XXX/Documents/XXX/.git/ このヒントは英語で丁寧に説明されており、詳しく理解はしていませんが、どうやらinitial branchの名前を現在「master」に設定しているが、名前を変更される場合があるため、これからレポジトリを作成する際には名前を変更した方がよい。と言う内容らしい。 そこでわざわざ下記変更方法まで記述してくれている丁寧さ。 git config --global init.defaultBranch <name> 基本的にはinitial branchの名前は「master」の代わりに「main」、「trunk」、「development」などがあるそうだ。 そこで私は下記コマンドで、現在作成したinitial branchの名前を「development」に名前変更しようと思ったのだが、 git config --global init.defaultBranch <development> そしてら下記のエラーが発生。 sh: parse error near `\n' どうやら、先ほどinit.defaultBranch <development>とコマンドを打ち込んでいたが、この上記リンクの説明では< >は打ち込まなくて良いらしい。つまり下記が正解。 git config --global init.defaultBranch development 初心者あるある、説明の説明が必要ってやつですね! 残りの環境構築も進めていきます! 下記リンク参考。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【python初学者】 クロージャー

クロージャーとは 外側の変数を記憶した関数である ex) def outer(a,b): def inner(): return a+b return inner f = outer(1,2) r = f() print(r) #print #3 初めに f = outer(1,2) が実行されたときに outer関数の引数には[1,2]が入る そして、inner関数の return a+b にも[1,2]が入る しかし、inner関数は実行されておらず、実行するための関数が返ってくる 試しに中身を見てみると def outer(a,b): def inner(): return a+b return inner f = outer(1,2) print(f) #print #<function outer.<locals>.inner at 0x00000248884AEB00> 確かに関数が返ってきている まだ実行されていないinnerを実行してやると、3が返ってくるというものだ 使いどころ ex) def circle_area_func(pi): def circle_area(r): return pi*r*r return circle_area cal1 = circle_area_func(3.14) cal2 = circle_area_func(3.141592) print(cal1(10)) print(cal2(10)) #print #314.0 #314.1592 circle_area_funに引数を渡しておいて、実行しておく のちにcircle_areaを実行する 便利かもしれない
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

カーネル密度推定は何次元から次元の呪いでダメになるのか

次元の呪い 次元の呪いとは、高次元データの距離や密度や角度に関する特異な数学的性質によって、機械学習の際に必要なデータ数が指数的に増える、高次元の数値積分が困難になるといった「高次元のデータはいろいろと困った性質を持つ」ことを表す言葉です。 本記事では次元の呪いの影響を受けやすいと言われているカーネル密度推定に注目して、高次元で実際に密度推定精度が悪くなることを確認します カーネル密度推定 (ガウスカーネルを用いた)カーネル密度推定はデータが従う分布の確率密度関数を $$ f(x) \propto \sum_{i\in \text{ 訓練データ}}\exp \left[ -\frac{1}{2\sigma^2 }|x-x_i|^2 \right] $$ の形で推定するノンパラメトリックな分布推定手法です。簡単なアルゴリムにもかかわらず滑らかな分布推定結果が得られ表現力も高いためヒストグラムや散布図と併せてよく描画されますが、高次元では次元の呪いを受けやすいとよく言われます。 カーネル密度推定は学習データを中心に極小のカーネル$\exp \left[ -\frac{1}{2\sigma^2 }|x-x_i|^2 \right]$を無数に配置することで滑らかな分布推定を実現していますので、学習データが十分密集して存在していることを暗に仮定していると言えます。ところが高次元ではデータ同士がどんどん離れていくことが次元の呪いの一端として知られており、密な学習データ点を用意することが困難です。このため高次元では分布推定の精度が低下してしまいます。 それでは具体的に何次元くらいになるとカーネル密度推定はうまくいかなくなるのでしょうか?本記事は数値的な実験で示してみたいと思います。 設定 $\mu_1 = (2,0,0,0,...)$と$\mu_2 = (-2,0,0,0,...)$を中心とする$d$次元の2成分混合ガウス分布に従うデータを$N=5000$件用意し、3/4を学習データとして分布推定し、残りの1/4に対する対数尤度の合計値を分布推定の精度指標として計算します。共分散行列は2成分とも対角行列とします。 データの生成過程は以下。 import numpy as np import pandas as pd import matplotlib.pyplot as plt %matplotlib inline from scipy import optimize from sklearn.neighbors import KernelDensity from sklearn.mixture import GaussianMixture from sklearn.model_selection import train_test_split N=5000 #データ件数 D=40 #最大次元 #各成分の平均ベクトル mu1 = np.zeros(D) mu1[0] = 2 mu2 = np.zeros(D) mu2[0] = -2 #2成分から乱数生成して結合 data = np.vstack((np.random.multivariate_normal(mean=mu1, cov=np.identity(D),size=N//2 ), np.random.multivariate_normal(mean=mu2, cov=np.identity(D),size=N//2 ))) data_train, data_test = train_test_split(data, shuffle=True) モデルの学習及び精度指標の算出は以下の通りです。 分布推定手法としては真のモデルである2成分混合ガウスモデル、正規分布モデル、カーネル密度推定の3種を比較します。カーネル密度推定のハイパーパラメータであるバンド幅$\sigma$は学習データの中で適当に最適化するものとします。本来は分割を変えた交差検証を行うべきですが、今回は簡単のために分割は1回のみです。またデータ数が変われば最適なバンド幅も変わるはずですが、そこも今回は無視です。 def find_best_bw(data): n=len(data) dtrain,dvalid = train_test_split(data) def to_minimize(x): kde = KernelDensity(bandwidth=x) kde.fit(dtrain) return -kde.score(dvalid) opt = optimize.minimize_scalar(lambda x:to_minimize(np.exp(x)) ) return np.exp(opt.x) def calc_score(data_train,data_test,d): bw = find_best_bw(data_train[:,:d]) gauss = GaussianMixture(n_components=1) gmm = GaussianMixture(n_components=2) kde = KernelDensity(bandwidth=bw) gauss.fit(data_train[:,:d]) gmm.fit(data_train[:,:d]) kde.fit(data_train[:,:d]) gauss_score = gauss.score_samples(data_test[:,:d]).sum() gmm_score = gmm.score_samples(data_test[:,:d]).sum() kde_score = kde.score_samples(data_test[:,:d]).sum() return gauss_score, gmm_score, kde_score, gauss, gmm, kde, bw 低次元での結果 d=1 d=1 gauss_score,gmm_score,kde_score, gauss, gmm, kde, _ = calc_score(data_train,data_test,d) #各推定結果の図示用 x = np.linspace(-6,6,100) y_gauss = gauss.score_samples(x.reshape(-1,1)) y_gmm = gmm.score_samples(x.reshape(-1,1)) y_kde = kde.score_samples(x.reshape(-1,1)) #プロット plt.figure(figsize=(6,4)) plt.hist(data_test[:,:d],bins=40, density=True) plt.plot(x,np.exp(y_gauss), label=f"gauss score = {gauss_score:.0f}") plt.plot(x,np.exp(y_gmm),label=f"gmm score = {gmm_score:.0f}") plt.plot(x,np.exp(y_kde), label=f"kde score = {kde_score:.0f}") plt.xlabel("x1") plt.legend() plt.show() テストデータのヒストグラムと併せて各モデルの分布推定結果を表示すると以下の図のようになります。 ヒストグラムは設定どおり二峰性を示しており、混合ガウスモデル(gmm)とカーネル密度推定(kde)はそれをとらえた良い推定が出来ています。スコアを比較すると、真のモデルであるgmmの方がよい性能を示しています。一方で正規分布モデル(gauss)は当然二峰性をとらえられず、見た目的にもスコア的にも悪い結果です。 d=2 gauss_score,gmm_score,kde_score, gauss, gmm, kde, _ = calc_score(data_train,data_test,d) #各推定結果の図示用 mesh=40 dx1 = dx2 = np.linspace(-7,7,mesh) x1,x2 = np.meshgrid(dx1,dx2) x_2D = np.hstack((x1.reshape(-1,1),x2.reshape(-1,1))) #プロット i=0 plt.figure(figsize=(13,4)) for model,label,score in zip((gauss,gmm,kde), ("gauss","gmm","kde"), (gauss_score,gmm_score,kde_score)): i+=1 z = model.score_samples(x_2D).reshape(mesh,mesh) plt.subplot(1,3,i) plt.contour(x1,x2, z, levels=np.linspace(-30,2,10)) plt.scatter(data_test[:,0],data_test[:,1], s=2) plt.title(f"{label} score = {score:.0f}") plt.xlabel("x1") plt.ylabel("x2") plt.show() テストデータの散布図と併せて各モデルの分布推定結果を表示すると以下の図のようになります。 当然テストデータは二峰性を示し、1次元と同様にgmmとkdeは二峰性をとらえておりスコアも高いですが、gaussはここでも散々です。 ここまで見てきた通り、明らかに二峰性を持つ低次元のデータに対してカーネル密度推定は真のモデルに迫る高いスコアを出しています。一方で当然ながら正規分布モデルは明らかに二峰性を捉えられていません。高次元でもこの差は埋まらないように感じられますが、実際にやってみると... 高次元での結果 d=1からd=40まで、各手法によるスコアを算出してプロットしてみます。 見やすさのため縦軸を次元dで割っています。 df_result = pd.DataFrame(columns = ["bw","gauss","gmm","kde"]) dimensions = np.array(range(1,D+1)) for d in dimensions: gauss_score,gmm_score,kde_score, gauss, gmm, kde, bw = calc_score(data_train,data_test,d) df_result.loc[d] = [bw, gauss_score, gmm_score, kde_score] #プロット plt.plot(df_result["gauss"]/dimensions, label="gauss",marker="o") plt.plot(df_result["gmm"] /dimensions, label="gmm",marker="o") plt.plot(df_result["kde"] /dimensions, label="kde",marker="o") plt.xlabel("dimension d") plt.ylabel("score / d") plt.grid() plt.legend() 結果のプロットを見ると10次元くらいからカーネル密度推定のスコアは他と比べて落ち始め、正規分布モデルにすら負けていることがわかります。一方で正規分布モデルは、高次元において真のモデルである混合ガウスモデルに迫るスコアを示しています。 より詳しく見るため、真のモデルである混合ガウスモデルのスコアを基準とし、これに如何に迫れているかをプロットしてみます。 plt.plot((df_result["gauss"]-df_result["gmm"])/dimensions,label="gauss",marker="o") plt.plot((df_result["kde"]-df_result["gmm"])/dimensions,label="kde",marker="o") plt.xlabel("dimension") plt.ylabel("(score-score_gmm) / d") plt.grid() plt.legend() これを見るとカーネル密度推定のスコアは真のモデルと比較してどんどん低下していき、一方で正規分布モデルのスコアは真のモデルに漸近していくことがわかります。大体6~7次元くらいで両者の優劣が逆転し、カーネル密度推定が次元の呪いによって使い物にならなくなっていることがわかります。 一方で正規分布モデルが意外とすごいということもわかります。今回の設定では2成分のガウシアンの中心を4だけ離しました。高次元の標準正規分布では以前の記事に示した通り、適当な二点間の距離が$\sqrt{2d}$に漸近していきますので、$\sqrt{2d}>4$、すなわち$d>8$程度になれば4の距離など無視できるようになり、正規分布モデルが真のモデルに迫る精度を出せるようになる、と言えそうです。 まとめ カーネル密度推定は図示用にも便利な手法ですが、次元の呪いを受けやすいです。 今回の例では7次元程度でも正規分布モデルに密度推定性能が劣る、という結果が得られました。データ数や分布にもよりますが、5~10次元程度以上で分布推定を行う場合には注意が必要、と言えそうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GradientTapeを使ってfitするとエラーが出た。そして解決した。

GradientTapeを使ってfitするとエラー出た Variatioanal AutoEncoderを実行する際に普段はself.add_loss()を使い学習させていました。 以下のようなコードです。 #モデルを構築 class VAE(tf.keras.Model): def __init__(self,latent_dim): super(VAE,self).__init__() self.encoder = Encode(latent_dim) self.decoder = Decode() #lossを計算 def VAE_loss(self,x): mean,logvar,z = self.encoder(x) x_sigmoid = self.decoder(z) #reshape shape = tf.shape(x) x = tf.reshape(x,[shape[0],-1]) x_sigmoid = tf.reshape(x_sigmoid,[shape[0],-1]) #復元誤差 reconstruction_loss = tf.keras.losses.BinaryCrossentropy(reduction=tf.keras.losses.Reduction.NONE)(x,x_sigmoid) #MNISTのshape image_shape=28*28 reconstruction_loss *= image_shape #KL-divergence(正規分布との差) kl_divergence =-0.5* tf.reduce_sum(1 + logvar - tf.square(mean) - tf.exp(logvar),axis=1) vae_loss = tf.reduce_mean(reconstruction_loss + kl_divergence) return vae_loss def call(self,inputs): loss = self.VAE_loss(inputs) self.add_loss(loss,inputs=inputs) return x encoder、decoderのコードは以下から見てください。 Variatinal AutoEoncoderをSubclassing APIで書いてみた call関数内でVAE_lossという独自の損失関数を定義し、そのlossをself.add_lossに入れます。これでmodel.compile、model.fitをすることでトレーニング開始してくれます。 しかし、このself.add_lossは、研究室で先輩方や同期に聞いても知らないという人が多くて、GradientTapeを使って学習させてると。ならば自分もGradientTapeを使おうと思ったわけですよ。 上のコードを書き換えます。 #モデルを構築 class VAE(tf.keras.Model): def __init__(self,latent_dim): super(VAE,self).__init__() self.encoder = Encode(latent_dim) self.decoder = Decode() def call(self,x): mean,logvar,z = self.encoder(x) y_pred = self.decoder(z) return mean,logvar,z,y_pred #lossを計算 def VAE_loss(self,x,x_sigmoid,mean,logvar,z): #reshape shape = tf.shape(x) x = tf.reshape(x,[shape[0],-1]) x_sigmoid = tf.reshape(x_sigmoid,[shape[0],-1]) #復元誤差 reconstruction_loss = tf.keras.losses.BinaryCrossentropy(reduction=tf.keras.losses.Reduction.NONE)(x,x_sigmoid) image_shape=28*28 reconstruction_loss *= image_shape #KL-divergence(正規分布との差) kl_divergence =-0.5* tf.reduce_sum(1 + logvar - tf.square(mean) - tf.exp(logvar),axis=1) vae_loss = tf.reduce_mean(reconstruction_loss + kl_divergence) return vae_loss def train_step(self,x): with tf.GradientTape() as tape: mean,logvar,z,y_pred= self(x, training=True) loss = self.VAE_loss(x,y_pred,mean,logvar,z) gradients = tape.gradient(loss, self.trainable_variables) self.optimizer.apply_gradients(zip(gradients, self.trainable_variables)) return {"loss":loss} さて、これでOKと思い、実行すると TypeError: Dimension value must be integer or None or have an __index__ method, got TensorShape([None, 28, 28, 1]) え?どゆこと?特におかしくなるようにいじったわけではないが、なぜかエラーが出る。Google様でこのエラー文を検索しても解決策が出てこない... 解決しました さて問題が解決しました。なにがダメだったのかをみると、入力データの型でした。 #train_stepに入る前の型 type(train_images) <class 'numpy.ndarray'> #train_stepに入ったあとの型 type(x) <class 'tuple'> 型がなんかtupleに変わってますね。なんでtupleになってんだ? if isinstance(x, tuple): x = x[0] #TensorShape([None, 28, 28, 1]) これでtupleからTensorShapeに変わりました。 このコードをwith tf.GradientTape() as tape:の上に書くと、動きました。これに気づくまでかなり時間かかりました。ずっとほかの部分を見てました。同じようなエラーで悩んでる方は参考にしてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

closure 對 partial

closureとfunctools.partialの比較記事です。 特に何も工夫せずに比較 両者は同じ目的で使われる事があると思います。以下のように関数の引数をあらかじめ埋めておきたい時です。 01.py from functools import partial def add(a, b): return a + b def main(): a = 1 ver_partial = partial(add, a) ver_closure = lambda b: add(a, b) assert ver_partial(2) == 3 assert ver_closure(2) == 3 main() どちらが優れているのでしょうか?コードの読みやすさは同程度に感じるので速度を比べてみます。 01_timeit.py from timeit import timeit from functools import partial def add(a, b): return a + b def main(): a = 1 ver_partial = partial(add, a, 2) ver_closure = lambda: add(a, 2) t_nothing = timeit(lambda:None) # 何もしない関数にかかる時間を差し引いて少しでも差が浮き立つようにする t_partial = timeit(ver_partial) t_closure = timeit(ver_closure) print(f"{t_partial - t_nothing = }") print(f"{t_closure - t_nothing = }") main() t_partial - t_nothing = 0.05276537095778622 t_closure - t_nothing = 0.13743187597719952 ver_partialの方が速い事が分かりました。という事は特に理由がなければ速度が重要な場面では常にpartialを使っておけば良いという事でしょうか? 似てもなく非なるもの 実は速度うんぬんの前にまず両者はコードの意味が全く異なる事を理解しておかないといけませんでした。同じような物に見えた両者ですが01.pyのassert文の手前でaを書き換えると異なる様相を呈します。 02.py from functools import partial def add(a, b): return a + b def main(): a = 1 ver_partial = partial(add, a) ver_closure = lambda b: add(a, b) a = 2 # 変更点 assert ver_partial(2) == 3 assert ver_closure(2) == 4 # 変更点 main() こうなる理由はver_closureが自身が呼ばれた時にmainのaを辿るのに対してver_partialは作られた時に既にaを辿った先にある1を持っているからと思われます。後者に関しては以下のコードで明らかですし def main(): a = 1 ver_partial = partial(add, a) assert ver_partial.args == (1, ) # 呼ばれる前に既に1を持っている。 a = 2 # 例えここで書き換えても assert ver_partial.args == (1, ) # 関係ない。 前者に関してもaを消すとはっきりしてきます。 03.py def main(): a = 1 ver_partial = partial(add, a) ver_closure = lambda b: add(a, b) del a # 削除 assert ver_partial(2) == 3 print("ver_partialは成功") assert ver_closure(2) == 3 ver_partialは成功 (中略) NameError: free variable 'a' referenced before assignment in enclosing scope そうなるとver_closureの方が遅い事には納得がいきます。どこかにあるであろうmain内の変数の一覧表を実行時に経由しているわけですから。なので速度の前にまずコードの意味として正しい方を選ぶ必要があると言えます。 ver_closureの方はaだけでなくaddに関しても実行時にどこかにある変数の一覧表(addの場合はglobal変数の一覧表)を経由している 公平な比較 コードの意味が異なるから競合していないように見える両者ですが、関数にdefault引数を与えることでclosureでもpartialと同じ事をできます。 04.py def main(): a = 1 ver_partial = partial(add, a) ver_closure = lambda b, _add=add, _a=a: _add(_a, b) # addとaを関数生成時に解決 assert ver_partial.args == (1, ) assert ver_closure.__defaults__ == (add, 1) # 呼ばれる前に既にaddと1を持っている del a assert ver_partial(2) == 3 assert ver_closure(2) == 3 # mainのaはもう関係ない こうなるともうclosureと呼ぶべきではないかもしれませんが混乱を避けるために同じ識別子を使うことにします。ともかく速度を比べるのなら同じ意味のコードであるこちらとすべきでしょう。 def main(): a = 1 ver_partial = partial(add, a, 2) ver_closure = lambda _add=add, _a=a: _add(_a, 2) t_nothing = timeit(lambda:None) t_partial = timeit(ver_partial) t_closure = timeit(ver_closure) print(f"{t_partial - t_nothing = }") print(f"{t_closure - t_nothing = }") t_partial - t_nothing = 0.05504554999060929 t_closure - t_nothing = 0.13577678697765805 結果 差は僅かに縮んだものの前回と大差はありませんでした。 partialは何故速い? ひとつ分かるのはpartialがC言語で実装されている事です。functools.pyには純粋にpythonで実装されたpartialがあるため最初はpythonで実装されていると思ったのですが、その下に以下の行がある事で誤解に気づきました。 try: from _functools import partial except ImportError: pass 実際に使われているのはこっち(C言語版)であり、純python版はC言語版が無い時の予備として置いてあるだけのようです。 だからといってver_partialがver_closureよりも速い理由にはなっていないわけですが、それ以上の追求はCPythonのコードを理解していない自分にとっては無理な話なのでここで諦めます。 partialの大きな利点 これは最近twitterで知ったのですがpartialには重ねがけでも重くならない工夫がしてあります。つまりは以下のコードにおいて f1 = partial(add, 1) f1 = partial(f1, 2) f2 = partial(add, 1, 2) f1とf2の重さは同じだということです。対してver_closureのやり方だと重ねがければその分だけ関数呼び出し回数が増えて重くなっていきます。 まとめ 以上を踏まえると次のような事が言えると思います。 closureとpartialはそもそも動作が大きく異なる partialがやっている事をclosureはdefault引数を用いる事で真似られる closureがやっている事をpartialは真似られない closureがpartialを真似ても速さでは負けるのでpartialで済む状況ではpartialを用いたほうが良い 検証環境 CPython 3.8.12
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python-Sounddeviceでマルチチャネルから同時再生

この記事について pythonのsounddeviceというモジュールを使ってマルチチャネル(3つ以上)から同時再生する方法を紹介します。 音響関係の研究に使えると思います。 必要な機材 マルチチャネルDAC 今回はMOTUの8AというDACを使いました。 PC OSは基本的にはなんでも動くと思われます。(Windows,Ubuntuで確認済みです) 参考にした記事・サイト インストール方法、初期設定、使い方などは以下の良記事を参考にしてください。 Python-Sounddevice ASIOで使える音響信号処理モジュール[基本編] Python-Sounddevice ASIOで使える音響信号処理モジュール[応用編] 歪み率・周波数特性測定向けオーディオ信号発生の自動化(Python-Sounddevice) 公式ドキュメント サンプルコード コード内で説明します。今回は4つの異なる信号を4チャネルから同時再生させました。 import numpy as np import sounddevice as sd #デバイス番号の設定、今回はMOTU 8Aを選択した。 dev = 0 #サンプリングレート, 192kHzまで可能らしいが安定しなかったため96kHzに fs = 96000 #sounddevice の初期設定 sd.default.samplerate = fs sd.default.device = dev #使用するデバイスを確認 print("query devices") print(sd.query_devices()) #信号を生成(例) sec = 1 #信号長(秒) A = 1 #振幅 f1 = 1000 #信号1の周波数 f2 = 2000 #信号2の周波数 f3 = 3000 #信号3の周波数 f4 = 4000 #信号4の周波数 #送信信号、今回は1kHzずつ離れた正弦波を作成 n = np.arange(fs*sec) swav1 = A*np.sin(2*np.pi*f1*n/fs) #信号1 swav2 = A*np.sin(2*np.pi*f2*n/fs) #信号2 swav3 = A*np.sin(2*np.pi*f3*n/fs) #信号3 swav4 = A*np.sin(2*np.pi*f4*n/fs) #信号4 #4つの再生信号を結合させ、縦ベクトルになるように転置 sdata = np.array([swav1,swav2,swav3,swav4]).T #再生開始 #引数は順番に(送信信号、サンプリングレート、送信信号の各列の出力チャネル番号、etc) sd.play(sdata, fs, mapping = [1,2,3,4], blocking = True, loop = True) 補足 MOTU 8Aの公式ドキュメントによると最大サンプリングレートは192kHzとあったが設定すると異音が出た 色々試してみたが安定するのは96kHzまでか(要検証)? mappingは再生信号の1列目から順番に、DACの何番チャネルから出力するかを決定する引数 再生信号の列数=mappingの個数、となるようにする mappingの中の数字(チャネル番号)は1度しか使えない。[1,2,2,3]などにするとエラーとなる 需要があるのか分からないが、気が向いたら課筆していく
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

趣味としての技術、職としての技術

興味関心から始めた技術  最初に「IT」という言葉の意味を知った時のことをなぜか覚えています。 私が小学校の高学年の頃、私が父に間違った知識で「ITはインターネットテクノロジーの略だよね」と聞いたことがあります。もちろん「インフォメーションテクノロジーの略だよ」という答えが返ってくるのですが、その頃はまだインターネットの技術とインフォメーションの技術の違いすら判らず、知識がないまま父のコンピュータを使って、ローマ字表を印刷してそれを見ながらキーボードで文字を打っていたのを覚えています。思い返せば、生まれた時から近くにコンピュータがありました。  しばらくして私は、興味があるという感情をきっかけにプログラミングを始めました。 最初の作品はGASで記述した、ウェブサイトの情報をスプレッドシートに記録するスクレイピングプログラムでした。初めてプログラムが思い通りの結果に反映された時の感動を覚えています。  私は情報や工学の出身ではありませんが、このように試行錯誤しながら勉強していく中でITの自由度やインフラとしての確かさに改めて気付かされました。今では、ITは、世界の政治・経済を構成する様々な場所で扱う情報の精度と速度を加速させる革命的な技術であると認識しています。 人の需要に応えるための技術レベル  この流行が激しく比較的新しい分野をモチベーションを維持・拡大していきながら継続的に学習するためには、前述のような技術の可能性の広さを知りながら興味関心を中心に自分が扱うことのできる技術が増えていく体験が重要だと思います。  一方で、興味や関心といった感情のみで培った技術を人の需要に応えるレベルまで昇華させるとなると話は変わってきます。そのためには相手の需要を満たす手段を得る必要があり、それが必ず興味のあることだとは限りません。そもそも興味関心とは、無知の知と自分の実績から更に達成できそうな期待が重なった限られた範囲で起こる一時的で不安定な感情であると考えてます。 この定義が正しいとすると、人の需要に応えるという目標を達成するには自分の興味関心を判断基準にして手段を習得していくだけでは足らず、人の需要を満たすための手段を逆算して出し、段階的に習得していくことが両者満足の最適な解決策だと考えました。 今後について そこで実際に需要から逆算した技術を自分なりに調べてみて、自分の興味関心が社会の需要が高い技術から勉強をし始めました。 今後学習進捗やメモを記録として投稿しようと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【自作モジュール編】Pythonで書き出すファイルの名前に現在の実行スクリプト(.py)の名前を挿入する方法

Pythonでファイルを書き出すときに、保存するファイルの名前に、 実行中のスクリプトの名前を挿入するコードを書いたので記事にします(前回と同じ)。 ただし、書き出す関数は自作のモジュールからimportしているものとします(前回との差分)。 今回の主題となるコードは記事の末尾に記載しています(コピペ用)。 本記事の想定読者 Pythonユーザー 複数のスクリプトを使って図などを書き出すことがある 保存したファイルの整理に苦労することがある 自作のモジュールをimportして利用することがある(前回との差分) 前回の記事 前回はファイルの書き出し処理が1つのスクリプトの中で完結するケースについて書きました。 今回は実行するスクリプトとは別に、自作のモジュールを利用するケースについて書きます。 結論 importしたモジュールの関数を使う場合はos.path.abspath(sys.modules['__main__'].__file__)を使う 1 最終的なアウトプットの例: 動物の研究 複数の動物について、matplotlibで図を作った場合のディレクトリ構成図 共通する処理を自作のモジュールreport.pyにまとめている report.pyが自動的にフォルダを作成し、ファイル名に各動物の名前を挿入している 動物ごとのスクリプトに実行ファイル名(動物の名前)についてのコードを書く必要がない animal_researchのディレクトリ構成図 animal_research/ ├── Lion │ ├── Lion_height_chart.png │ ├── Lion_lifespan_chart.png │ └── Lion_weight_chart.png ├── cheetah │ ├── cheetah_height_chart.png │ ├── cheetah_lifespan_chart.png │ └── cheetah_weight_chart.png ├── cheetah.py ├── leptailurus serval │ ├── leptailurus_serval_height_chart.png │ ├── leptailurus_serval_lifespan_chart.png │ └── leptailurus_serval_weight_chart.png ├── leptailurus serval.py ├── lion.py └── report.py lion.py # 他の動物のファイルも全く同じ中身 # 各動物のファイルに動物名(ファイル名)を直接書く必要がないので流用が楽 import report report.make_figure('weight_chart') report.make_figure('height_chart') report.make_figure('lifespan_chart') 記事の背景 複雑な処理を記述しようとすると、コードが長くなり、読みづらくなりがちです。 特にmatplotlibを使う場合は、細かい指定をするほどコードが長くなります。 メインのコードから関数などを括りだして別のモジュールにまとめるとよさそうです。 ただし、その場合は実行しているスクリプトの名前の取得方法が変わります。 単一のスクリプトでファイル名を取得する方法とは異なるアプローチが必要になります。 記事の概要 別のモジュール内でos.path.basename(__file__)を実行するとそのモジュール名が返ってきてしまう (実行中のスクリプトの名前が得られない) importするモジュール内でos.path.abspath(sys.modules['__main__'].__file__)を使うとよい 階層が深くなる場合も問題なさそう コードの説明 うまくいかないケース main.pyで実行中のスクリプトの名前を表示します。 main.py import os print(os.path.basename(__file__)) # main.py 同様の処理を別のファイルsub.pyで関数として定義します。 sub.py import os def print_basename(): print(os.path.basename(__file__)) この関数をmain.pyでimportして実行すると、 main.pyではなくsub.pyが表示されます。 main.py import sub sub.print_basename() # sub.py うまくいくケース sub.pyの中身を書き換えて、再びmain.pyを実行します。1 sub.py import os import sys def print_basename(): print(os.path.abspath(sys.modules['__main__'].__file__)) main.py import sub sub.print_basename() # main.py 狙いどおりmain.pyが表示されました。 ちなみに、sub.py自体を実行した場合はちゃんとsub.pyと表示されます 実際にファイルの命名に利用する 実行中のスクリプトの名前を保存先に反映させるには、 たとえば以下のような関数をsub.pyに定義して、main.pyで実行します。 2 sub.py import os import sys abspath = os.path.abspath(sys.modules['__main__'].__file__) main_name = os.path.basename(abspath).rstrip('.py') if not os.path.exists(main_name): os.mkdir(main_name) def make_textfile(num): for i in range(num): with open(f'{main_name}/{i+1:03}_output.txt', mode='w')as f: f.write('something') main.py import sub import glob sub.make_textfile(3) print(sorted(glob.glob('main/*.txt'))) # ['main/001_output.txt', 'main/002_output.txt', 'main/003_output.txt'] これでimportしたモジュールの関数を使う場合でも、 保存するファイルの名前にmain.pyの名前を反映することができました。 階層が深くなる場合 階層が深くなる場合も問題なさそうです。 ディレクトリ構成図 . ├── main.py ├── my_package │ ├── __init__.py │ └── src │ └── my_module.py └── sub.py my_module.py # 先ほどのsub.pyと同じなので省略 main.py from my_package.src import my_module my_module.print_basename() # main.py __init__.py内でimportした場合も同様。 my_package/__init__.py from my_package.src.my_module import print_basename main.py import my_package my_package.print_basename() # main.py もう少しだけ具体的な例: 動物の研究 架空の例ですが、もう少しだけイメージしやすそうな例を出します。 動物の調査をしており、各動物に関してレポートをまとめる必要があると仮定します。 そのために、動物の種類ごとにPythonファイルを作成しました。 report.pyには共通の処理をまとめた関数が定義されています。 animal_researchのディレクトリ構成図 animal_research/ ├── report.py ├── lion.py ├── cheetah.py └── leptailurus_serval.py どの動物についても共通する処理は、ある程度パターン化して書きたいところです。 それぞれの動物の名前を定数として先に定義しておくと、パターン化も楽にすみそうです。 でもそれをするのもめんどくさいとします。 よって、各ファイルの名前を自動で取得し、動物ごとにフォルダを分けて、 図のファイル名にその動物の名前を挿入してくれるようなreport.pyを作ります。 なお、今回は例の提示が目的のため、作図の具体的な処理はすべて省略します。 report.py import os import sys import matplotlib.pyplot as plt abspath = os.path.abspath(sys.modules['__main__'].__file__) main_name = os.path.basename(abspath).rstrip('.py') if not os.path.exists(main_name): os.mkdir(main_name) def make_figure(title): fig = plt.figure() fig.savefig(f'{main_name}/{main_name}_{title}.png') report.pyで実行ファイルの名前を取得してくれるので、 各動物ごとの処理が簡潔に書けるようになりました。 (実際には図の中身のデータを入れないとですが、それも省略します) lion.py import report report.make_figure('weight_chart') report.make_figure('height_chart') report.make_figure('lifespan_chart') lion.pyのコードを示しましたが、今回は他の動物に関しても全く同じコードです。 それぞれのスクリプトを実行したあとのディレクトリ構成は以下のとおりです。 animal_researchのディレクトリ構成図 animal_research/ ├── Lion │ ├── Lion_height_chart.png │ ├── Lion_lifespan_chart.png │ └── Lion_weight_chart.png ├── cheetah │ ├── cheetah_height_chart.png │ ├── cheetah_lifespan_chart.png │ └── cheetah_weight_chart.png ├── cheetah.py ├── leptailurus serval │ ├── leptailurus_serval_height_chart.png │ ├── leptailurus_serval_lifespan_chart.png │ └── leptailurus_serval_weight_chart.png ├── leptailurus serval.py ├── lion.py └── report.py まとめ 複数のスクリプトでファイルを書き出すときの命名が楽になった! 別モジュール内の関数からでも、実行スクリプトの名前をつけれるようになった!(前回との差分) これで少しは分析も楽になる…かも 今回の主題となるコード コード全文(クリックで表示) 今回は主題となる処理をするsub.pyのコードだけ載せておきます。 sub.py import os import sys abspath = os.path.abspath(sys.modules['__main__'].__file__) main_name = os.path.basename(abspath).rstrip('.py') if not os.path.exists(main_name): os.mkdir(main_name) def make_textfile(num): for i in range(num): with open(f'{main_name}/{i+1:03}_output.txt', mode='w')as f: f.write('something') 参照: How to get filename of the main module in Python? ↩ ↩2 参考: 前回の記事 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

書き出すファイルの名前に、書き出しを実行したPythonファイルの名前を自動で挿入する方法【自作モジュール編】

Pythonでファイルを書き出すときに、保存するファイルに簡単に名前をつけるコードを書いたので記事にします。 具体的には、実行しているPythonのファイル名をそのまま保存するファイルの名前に流用します。 ただし、書き出す関数は自作のモジュールからimportしているものとします(前回との差分)。 今回の主題となるコードは記事の末尾に記載しています(コピペ用)。 本記事の想定読者 Pythonユーザー 複数のスクリプトを使って図などを書き出すことがある 保存したファイルの整理に苦労することがある 自作のモジュールをimportして利用することがある(前回との差分) 前回の記事 前回はファイルの書き出し処理が1つのスクリプトの中で完結するケースについて書きました。 今回は実行するスクリプトとは別に、自作のモジュールを利用するケースについて書きます。 結論 importしたモジュールの関数を使う場合はos.path.abspath(sys.modules['__main__'].__file__)を使う 1 最終的なアウトプットの例: 動物の研究 複数の動物について、matplotlibで図を作った場合のディレクトリ構成図 共通する処理を自作のモジュールreport.pyにまとめている report.pyが自動的にフォルダを作成し、ファイル名に各動物の名前を挿入している 動物ごとのスクリプトに実行ファイル名(動物の名前)についてのコードを書く必要がない animal_researchのディレクトリ構成図 animal_research/ ├── Lion │ ├── Lion_height_chart.png │ ├── Lion_lifespan_chart.png │ └── Lion_weight_chart.png ├── cheetah │ ├── cheetah_height_chart.png │ ├── cheetah_lifespan_chart.png │ └── cheetah_weight_chart.png ├── cheetah.py ├── leptailurus serval │ ├── leptailurus_serval_height_chart.png │ ├── leptailurus_serval_lifespan_chart.png │ └── leptailurus_serval_weight_chart.png ├── leptailurus serval.py ├── lion.py └── report.py lion.py # 他の動物のファイルも全く同じ中身 # 各動物のファイルに動物名(ファイル名)を直接書く必要がないので流用が楽 import report report.make_figure('weight_chart') report.make_figure('height_chart') report.make_figure('lifespan_chart') 記事の背景 複雑な処理を記述しようとすると、コードが長くなり、読みづらくなりがちです。 特にmatplotlibを使う場合は、細かい指定をするほどコードが長くなります。 メインのコードから関数などを括りだして別のモジュールにまとめるとよさそうです。 ただし、その場合は実行しているスクリプトの名前の取得方法が変わります。 単一のスクリプトでファイル名を取得する方法とは異なるアプローチが必要になります。 記事の概要 別のモジュール内でos.path.basename(__file__)を実行するとそのモジュール名が返ってきてしまう (実行中のスクリプトの名前が得られない) importするモジュール内でos.path.abspath(sys.modules['__main__'].__file__)を使うとよい 階層が深くなる場合も問題なさそう コードの説明 うまくいかないケース main.pyで実行中のスクリプトの名前を表示します。 main.py import os print(os.path.basename(__file__)) # main.py 同様の処理を別のファイルsub.pyで関数として定義します。 sub.py import os def print_basename(): print(os.path.basename(__file__)) この関数をmain.pyでimportして実行すると、 main.pyではなくsub.pyが表示されます。 main.py import sub sub.print_basename() # sub.py うまくいくケース sub.pyの中身を書き換えて、再びmain.pyを実行します。1 sub.py import os import sys def print_basename(): print(os.path.abspath(sys.modules['__main__'].__file__)) main.py import sub sub.print_basename() # main.py 狙いどおりmain.pyが表示されました。 ちなみに、sub.py自体を実行した場合はちゃんとsub.pyと表示されます 実際にファイルの命名に利用する 実行中のスクリプトの名前を保存先に反映させるには、 たとえば以下のような関数をsub.pyに定義して、main.pyで実行します。 2 sub.py import os import sys abspath = os.path.abspath(sys.modules['__main__'].__file__) main_name = os.path.basename(abspath).rstrip('.py') if not os.path.exists(main_name): os.mkdir(main_name) def make_textfile(num): for i in range(num): with open(f'{main_name}/{i+1:03}_output.txt', mode='w')as f: f.write('something') main.py import sub import glob sub.make_textfile(3) print(sorted(glob.glob('main/*.txt'))) # ['main/001_output.txt', 'main/002_output.txt', 'main/003_output.txt'] これでimportしたモジュールの関数を使う場合でも、 保存するファイルの名前にmain.pyの名前を反映することができました。 階層が深くなる場合 階層が深くなる場合も問題なさそうです。 ディレクトリ構成図 . ├── main.py ├── my_package │ ├── __init__.py │ └── src │ └── my_module.py └── sub.py my_module.py # 先ほどのsub.pyと同じなので省略 main.py from my_package.src import my_module my_module.print_basename() # main.py __init__.py内でimportした場合も同様。 my_package/__init__.py from my_package.src.my_module import print_basename main.py import my_package my_package.print_basename() # main.py もう少しだけ具体的な例: 動物の研究 架空の例ですが、もう少しだけイメージしやすそうな例を出します。 動物の調査をしており、各動物に関してレポートをまとめる必要があると仮定します。 そのために、動物の種類ごとにPythonファイルを作成しました。 report.pyには共通の処理をまとめた関数が定義されています。 animal_researchのディレクトリ構成図 animal_research/ ├── report.py ├── lion.py ├── cheetah.py └── leptailurus_serval.py どの動物についても共通する処理は、ある程度パターン化して書きたいところです。 それぞれの動物の名前を定数として先に定義しておくと、パターン化も楽にすみそうです。 でもそれをするのもめんどくさいとします。 よって、各ファイルの名前を自動で取得し、動物ごとにフォルダを分けて、 図のファイル名にその動物の名前を挿入してくれるようなreport.pyを作ります。 なお、今回は例の提示が目的のため、作図の具体的な処理はすべて省略します。 report.py import os import sys import matplotlib.pyplot as plt abspath = os.path.abspath(sys.modules['__main__'].__file__) main_name = os.path.basename(abspath).rstrip('.py') if not os.path.exists(main_name): os.mkdir(main_name) def make_figure(title): fig = plt.figure() fig.savefig(f'{main_name}/{main_name}_{title}.png') report.pyで実行ファイルの名前を取得してくれるので、 各動物ごとの処理が簡潔に書けるようになりました。 (実際には図の中身のデータを入れないとですが、それも省略します) lion.py import report report.make_figure('weight_chart') report.make_figure('height_chart') report.make_figure('lifespan_chart') lion.pyのコードを示しましたが、今回は他の動物に関しても全く同じコードです。 それぞれのスクリプトを実行したあとのディレクトリ構成は以下のとおりです。 animal_researchのディレクトリ構成図 animal_research/ ├── Lion │ ├── Lion_height_chart.png │ ├── Lion_lifespan_chart.png │ └── Lion_weight_chart.png ├── cheetah │ ├── cheetah_height_chart.png │ ├── cheetah_lifespan_chart.png │ └── cheetah_weight_chart.png ├── cheetah.py ├── leptailurus serval │ ├── leptailurus_serval_height_chart.png │ ├── leptailurus_serval_lifespan_chart.png │ └── leptailurus_serval_weight_chart.png ├── leptailurus serval.py ├── lion.py └── report.py まとめ 複数のスクリプトでファイルを書き出すときの命名が楽になった! 別モジュール内の関数からでも、実行スクリプトの名前をつけれるようになった!(前回との差分) これで少しは分析も楽になる…かも 今回の主題となるコード コード全文(クリックで表示) 今回は主題となる処理をするsub.pyのコードだけ載せておきます。 sub.py import os import sys abspath = os.path.abspath(sys.modules['__main__'].__file__) main_name = os.path.basename(abspath).rstrip('.py') if not os.path.exists(main_name): os.mkdir(main_name) def make_textfile(num): for i in range(num): with open(f'{main_name}/{i+1:03}_output.txt', mode='w')as f: f.write('something') 参照: How to get filename of the main module in Python? ↩ ↩2 参考: 前回の記事 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonanywhereというものを使ってデプロイしてみる

Pythonanywhereというものを使ってデプロイしてみる Pythonanywhereとは Pythonで制作したプロダクトをデプロイすることができるPaasクラウド IDEとして利用することもでき、ブラウザ上でコードを編集することができる デプロイの流れ こちらで制作したものをデプロイする(フレームワークはFlaskです) コンソールでGithubリポジトリをクローンする git clone https://github.com/sayyyyyy/flask-api 成功したらFilesの中にクローンしたディレクトリが追加されてるはず! この時にディレクトリのPythonファイルがある位置を確認しておく アプリの作成 WebをクリックしてAdd a new web app選択する フレームワークを選択する(今回はFlask) Pythonのバージョンを選択する(今回はPython 3.9) pythonファイルがある位置を入力する Pythonファイルが更新されてしまうので指定したPythonファイルを更新し直す(もっと良い方法があるかも) Webの中にある下記の部分をEnabledにするとHTTPSになる WebのReload~という緑色のボタンをクリックしてアプリを最新の状態に更新する デプロイの確認 クリックしてデプロイできているか確認する 参考資料
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flaskを使って10分でAPI作る

Flaskを使って10分でAPI作る PythonのFlaskを使ってAPIを作ってみようと思います。 Flaskのインストール mkdir flask-api cd flask-api python3 -m venv .venv //任意 source .venv/bin/activate //任意 pip install Flask もっとも単純なもの urlにリクエストが来たらdataを返すAPI @app.route('/') def return_data(): data = {1: "a", 2: "b", 3: "c", 4: "d", 5: "e"} return data 条件をつけたもの @app.route('/id=<id>') def designated_return_data(id): if id == '1': data = {1: "a", 2: "b", 3: "c", 4: "d", 5: "e"} elif id == '2': data = {1: "あ", 2: "い", 3: "う", 4: "え", 5: "お"} else: data = {1: "Not Found"} return data 制作したリポジトリ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonにいろんな計算をさせる

初めに pythonで変数に数字を代入して計算してみる ゴール 計算結果が出力されること 実践 式 >>> tax = 12.5 / 100 >>> price = 100.50 >>> price * tax 12.5625 >>> price + _ 113.0625 >>> round(_, 2) 113.06 _は直前の結果を覚えている。便利
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでドラクエ風対戦ゲームを作ってみた

Progateを終わらせたので、そこで学んだことアウトプットするためにドラクエ風対戦ゲームを作りました。 概要 勇者とモンスターの名前、HP、攻撃力を設定して戦い勝敗を判定する。同じモンスターと戦っても面白くないので、クラスを使いモンスターを追加する。random.choiceを使ってドラクエと同じようにランダムにモンスターと戦うようにする。 ドラクエ風対戦ゲーム まずはクラスMonsterを定義します。名前とHP、攻撃力を入力できるようにします。 battle class Monster: def __init__(self, name, hp, attack): self.name = name self.hp = hp self.attack = attack 次に勇者のステータスを設定します。まだまだ駆け出しですね。 battle hero_hp = 30 hero_attack = 5 いよいよモンスターを追加します!! battle slime = Monster('スライム', 10, 1) drakey = Monster('ドラキー', 13, 2) darkdream = Monster('ダークドレアム', 130000, 999) モンスターとランダムに戦えるようにします。 battle import random monsters = [slime, drakey, darkdream] appeard_monster = random.choice(monsters) モンスターと戦います。 battle print(appeard_monster.name + 'が現れた!') while hero_hp > 0: print('勇者の攻撃!') appeard_monster.hp -= hero_attack if appeard_monster.hp > 0: print(appeard_monster.name + 'は' + str(hero_attack) + 'のダメージを受けた!') print(' ') else: print(appeard_monster.name + 'は' + str(hero_attack) + 'のダメージを受けた!') print(appeard_monster.name + 'は力尽きた') print(' ') print('勇者は戦闘に勝った!') break print(appeard_monster.name + 'の攻撃!') print('勇者は' + str(appeard_monster.attack) + 'のダメージを受けた!') print(' ') hero_hp -= appeard_monster.attack else: print('勇者は力尽きた') print(' ') print('勇者は戦闘に負けた!') 実行するとこうなります。まずはドラキーが現れました。 ドラキーが現れた! 勇者の攻撃! ドラキーは5のダメージを受けた! ドラキーの攻撃! 勇者は2のダメージを受けた! 勇者の攻撃! ドラキーは5のダメージを受けた! ドラキーの攻撃! 勇者は2のダメージを受けた! 勇者の攻撃! ドラキーは5のダメージを受けた! ドラキーは力尽きた 勇者は戦闘に勝った! 次はスライム。楽勝ですね。 スライムが現れた! 勇者の攻撃! スライムは5のダメージを受けた! スライムの攻撃! 勇者は1のダメージを受けた! 勇者の攻撃! スライムは5のダメージを受けた! スライムは力尽きた 勇者は戦闘に勝った! いきなり裏ボス登場。コテンパンにやられる、、、 ダークドレアムが現れた! 勇者の攻撃! ダークドレアムは5のダメージを受けた! ダークドレアムの攻撃! 勇者は999のダメージを受けた! 勇者は力尽きた 勇者は戦闘に負けた! 最後に 基本的な形はできたので、今後ステータスやレベルの次元を取り入れたり、特技を選択できるようにしていきたいですね。 勇者共々プログラミングスキルも成長していきたいものです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで書き出すファイルの名前に現在の実行スクリプト(.py)の名前を挿入する方法

Pythonでファイルを書き出すときに、保存するファイルの名前に 実行中のスクリプトの名前を挿入するコードを書いたので記事にします。 コード全文は記事の末尾に記載しています(コピペ用)。 筆者はデータ分析でmatplotlibで作成したグラフなどの保存の際にこの方法を使いました。 ファイル名に一定のルールを設け、命名を自動化することは結果の再現性にもつながります。 本記事の想定読者 Pythonユーザー 複数のスクリプトを使って図などを書き出すことがある 保存したファイルの整理に苦労することがある 最終的なアウトプットの例 今回は簡単のためにテキストファイルを例に使います。 main.pyとsub.pyからファイルを書き出す場合を考えます。 本記事の方法を使うと、以下のようなディレクトリ構成を自動でつくることができます。 最終的なディレクトリ構成 . ├── main │ ├── 001_output.txt │ ├── 002_output.txt │ └── 003_output.txt ├── main.py ├── main_001_output.txt ├── main_002_output.txt ├── main_003_output.txt ├── sub.py ├── sub_001_output.txt ├── sub_002_output.txt └── sub_003_output.txt 概要 os.path.basename(__file__)で実行中のスクリプト名を取得する f文字列を使って保存するファイルにスクリプト名を含んだファイル名を設定する os.mkdir()でスクリプト名のディレクトリを作成する サンプルコード import os current_name = os.path.basename(__file__) current_name = current_name.rstrip('.py') if not os.path.exists(current_name): os.mkdir(current_name) for i in range(3): with open(f'{current_name}/{i+1:03}_output.txt', mode='w')as f: f.write('something') コードの説明 スクリプト名の取得とファイルの書き出し 実行中のPythonのスクリプトの名前は以下のようにして取得できます。 main.py import os print(os.path.basename(__file__)) # main.py 今回は拡張子を除いたファイル名を利用するので、rstrip()を使います。 main.py current_name = os.path.basename(__file__) current_name = current_name.rstrip('.py') print(current_name) # main 実際にファイルを書き出します。 その後、globを使ってファイルを確認します。 main.py with open(f'{current_name}_output.txt', mode='w')as f: f.write('something') import glob print(glob.glob('*.txt')) # ['main_output.txt'] 保存したファイル名に実行中のスクリプト名が挿入されました。 複数ファイルの場合 1つのファイルだけだとメリットがわかりづらいので、 次は複数のファイルを作成します。 for i in range(3): with open(f'{current_name}_{i+1:03}_output.txt', mode='w')as f: f.write('something') print(sorted(glob.glob('*.txt'))) # ['main_001_output.txt', 'main_002_output.txt', 'main_003_output.txt', 'main_output.txt'] 100個以上のファイルの並び替えを想定して、 {i+1:03}を使って3桁になるように0埋めを行っています。 さらに、別のスクリプトでもファイルを書き出して、結果を出力してみます。 sub.py import os import glob for i in range(3): with open(f'{current_name}_{i+1:03}_output.txt', mode='w')as f: f.write('something') print(sorted(glob.glob('*.txt'))) # ['main_001_output.txt', 'main_002_output.txt', 'main_003_output.txt', 'main_output.txt', # 'sub_001_output.txt', 'sub_002_output.txt', 'sub_003_output.txt'] ファイルにスクリプト名と連番を振ったおかげで、ディレクトリの見通しがよくなりました。 新しくディレクトリをつくる場合 いくらファイル名を工夫しても、ファイルを大量に書き出す場合は、 スクリプトと同じ階層に書き出すと、ディレクトリの中がごちゃごちゃしてしまいます。 見通しをよくするために新しいディレクトリを作成します。 スクリプト名を挿入する手順はファイル作成時とほぼ同じです。 current_name = os.path.basename(__file__) current_name = current_name.rstrip('.py') if not os.path.exists(current_name): os.mkdir(current_name) print(glob.glob('*/')) # ['main/'] すでに同名のディレクトリがあるとエラーが起きるので、 先にディレクトリの有無をos.path.exists()で調べています。 先ほどと同様に複数のファイルを作成してみます。 今回は保存先を新しく作成したディレクトリの下に設定しています。 #%% for i in range(3): with open(f'{current_name}/{i+1:03}_output.txt', mode='w')as f: f.write(f'something') print(sorted(glob.glob('main/*.txt'))) # ['main/001_output.txt', 'main/002_output.txt', 'main/003_output.txt'] 狙いどおりmain/の下にファイルが作成されたことを確認できました。 最終的なディレクトリ構成 最終的なディレクトリ構成(再掲) . ├── main │ ├── 001_output.txt │ ├── 002_output.txt │ └── 003_output.txt ├── main.py ├── main_001_output.txt ├── main_002_output.txt ├── main_003_output.txt ├── sub.py ├── sub_001_output.txt ├── sub_002_output.txt └── sub_003_output.txt まとめ 複数のスクリプトでファイルを書き出すときの命名が楽になった! どのスクリプトで実行したかがわかるので、再現性が高まる(はず) コード全文 コード全体(クリックで表示) main.py import os import glob current_name = os.path.basename(__file__) current_name = current_name.rstrip('.py') print(current_name) with open(f'{current_name}_output.txt', mode='w')as f: f.write('something') print(glob.glob('*.txt')) for i in range(3): with open(f'{current_name}_{i+1:03}_output.txt', mode='w')as f: f.write(f'something') print(sorted(glob.glob('*.txt'))) if not os.path.exists(current_name): os.mkdir(current_name) print(glob.glob('*/')) for i in range(3): with open(f'{current_name}/{i+1:03}_output.txt', mode='w')as f: f.write(f'something') print(sorted(glob.glob('main/*.txt'))) sub.py import os current_name = os.path.basename(__file__) current_name = current_name.rstrip('.py') for i in range(3): with open(f'{current_name}_{i+1:03}_output.txt', mode='w')as f: f.write(f'something')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで保存するファイルに簡単に名前をつける方法

Pythonでファイルを書き出すときに、保存するファイルに簡単に名前をつけるコードを書いたので記事にします。 具体的には、実行しているPythonのファイル名をそのまま保存するファイルの名前に転用します。 コード全文は記事の末尾に記載しています(コピペ用)。 筆者はデータ分析でmatplotlibで作成したグラフなどの保存の際にこの方法を使いました。 ファイル名に一定のルールを設け、命名を自動化することは結果の再現性にもつながります。 本記事の想定読者 Pythonユーザー 複数のスクリプトを使って図などを書き出すことがある 保存したファイルの整理に苦労することがある 最終的なアウトプットの例 今回は簡単のためにテキストファイルを例に使います。 main.pyとsub.pyからファイルを書き出す場合を考えます。 本記事の方法を使うと、以下のようなディレクトリ構成を自動でつくることができます。 最終的なディレクトリ構成 . ├── main │ ├── 001_output.txt │ ├── 002_output.txt │ └── 003_output.txt ├── main.py ├── main_001_output.txt ├── main_002_output.txt ├── main_003_output.txt ├── sub.py ├── sub_001_output.txt ├── sub_002_output.txt └── sub_003_output.txt 概要 os.path.basename(__file__)で実行中のスクリプト名を取得する f文字列を使って保存するファイルにスクリプト名を含んだファイル名を設定する os.mkdir()でスクリプト名のディレクトリを作成する サンプルコード import os current_name = os.path.basename(__file__) current_name = current_name.rstrip('.py') if not os.path.exists(current_name): os.mkdir(current_name) for i in range(3): with open(f'{current_name}/{i+1:03}_output.txt', mode='w')as f: f.write('something') コードの説明 スクリプト名の取得とファイルの書き出し 実行中のPythonのスクリプトの名前は以下のようにして取得できます。 main.py import os print(os.path.basename(__file__)) # main.py 今回は拡張子を除いたファイル名を利用するので、rstrip()を使います。 main.py current_name = os.path.basename(__file__) current_name = current_name.rstrip('.py') print(current_name) # main 実際にファイルを書き出します。 その後、globを使ってファイルを確認します。 main.py with open(f'{current_name}_output.txt', mode='w')as f: f.write('something') import glob print(glob.glob('*.txt')) # ['main_output.txt'] 保存したファイル名に実行中のスクリプト名が挿入されました。 複数ファイルの場合 1つのファイルだけだとメリットがわかりづらいので、 次は複数のファイルを作成します。 for i in range(3): with open(f'{current_name}_{i+1:03}_output.txt', mode='w')as f: f.write('something') print(sorted(glob.glob('*.txt'))) # ['main_001_output.txt', 'main_002_output.txt', 'main_003_output.txt', 'main_output.txt'] 100個以上のファイルの並び替えを想定して、 {i+1:03}を使って3桁になるように0埋めを行っています。 さらに、別のスクリプトでもファイルを書き出して、結果を出力してみます。 sub.py import os import glob for i in range(3): with open(f'{current_name}_{i+1:03}_output.txt', mode='w')as f: f.write('something') print(sorted(glob.glob('*.txt'))) # ['main_001_output.txt', 'main_002_output.txt', 'main_003_output.txt', 'main_output.txt', # 'sub_001_output.txt', 'sub_002_output.txt', 'sub_003_output.txt'] ファイルにスクリプト名と連番を振ったおかげで、ディレクトリの見通しがよくなりました。 新しくディレクトリをつくる場合 いくらファイル名を工夫しても、ファイルを大量に書き出す場合は、 スクリプトと同じ階層に書き出すと、ディレクトリの中がごちゃごちゃしてしまいます。 見通しをよくするために新しいディレクトリを作成します。 スクリプト名を挿入する手順はファイル作成時とほぼ同じです。 current_name = os.path.basename(__file__) current_name = current_name.rstrip('.py') if not os.path.exists(current_name): os.mkdir(current_name) print(glob.glob('*/')) # ['main/'] すでに同名のディレクトリがあるとエラーが起きるので、 先にディレクトリの有無をos.path.exists()で調べています。 先ほどと同様に複数のファイルを作成してみます。 今回は保存先を新しく作成したディレクトリの下に設定しています。 #%% for i in range(3): with open(f'{current_name}/{i+1:03}_output.txt', mode='w')as f: f.write(f'something') print(sorted(glob.glob('main/*.txt'))) # ['main/001_output.txt', 'main/002_output.txt', 'main/003_output.txt'] 狙いどおりmain/の下にファイルが作成されたことを確認できました。 最終的なディレクトリ構成 最終的なディレクトリ構成(再掲) . ├── main │ ├── 001_output.txt │ ├── 002_output.txt │ └── 003_output.txt ├── main.py ├── main_001_output.txt ├── main_002_output.txt ├── main_003_output.txt ├── sub.py ├── sub_001_output.txt ├── sub_002_output.txt └── sub_003_output.txt まとめ 複数のスクリプトでファイルを書き出すときの命名が楽になった! どのスクリプトで実行したかがわかるので、再現性が高まる(はず) コード全文 コード全体(クリックで表示) main.py import os import glob current_name = os.path.basename(__file__) current_name = current_name.rstrip('.py') print(current_name) with open(f'{current_name}_output.txt', mode='w')as f: f.write('something') print(glob.glob('*.txt')) for i in range(3): with open(f'{current_name}_{i+1:03}_output.txt', mode='w')as f: f.write(f'something') print(sorted(glob.glob('*.txt'))) if not os.path.exists(current_name): os.mkdir(current_name) print(glob.glob('*/')) for i in range(3): with open(f'{current_name}/{i+1:03}_output.txt', mode='w')as f: f.write(f'something') print(sorted(glob.glob('main/*.txt'))) sub.py import os current_name = os.path.basename(__file__) current_name = current_name.rstrip('.py') for i in range(3): with open(f'{current_name}_{i+1:03}_output.txt', mode='w')as f: f.write(f'something')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

書き出すファイルの名前に、書き出しを実行したPythonファイルの名前を自動で挿入する方法

Pythonでファイルを書き出すときに、保存するファイルに簡単に名前をつけるコードを書いたので記事にします。 具体的には、実行しているPythonのファイル名をそのまま保存するファイルの名前に流用します。 コード全文は記事の末尾に記載しています(コピペ用)。 筆者はデータ分析でmatplotlibで作成したグラフなどの保存の際にこの方法を使いました。 ファイル名に一定のルールを設け、命名を自動化することは結果の再現性にもつながります。 本記事の想定読者 Pythonユーザー 複数のスクリプトを使って図などを書き出すことがある 保存したファイルの整理に苦労することがある 最終的なアウトプットの例 今回は簡単のためにテキストファイルを例に使います。 main.pyとsub.pyからファイルを書き出す場合を考えます。 本記事の方法を使うと、以下のようなディレクトリ構成を自動でつくることができます。 最終的なディレクトリ構成 . ├── main │ ├── 001_output.txt │ ├── 002_output.txt │ └── 003_output.txt ├── main.py ├── main_001_output.txt ├── main_002_output.txt ├── main_003_output.txt ├── sub.py ├── sub_001_output.txt ├── sub_002_output.txt └── sub_003_output.txt 概要 os.path.basename(__file__)で実行中のスクリプト名を取得する f文字列を使って保存するファイルにスクリプト名を含んだファイル名を設定する os.mkdir()でスクリプト名のディレクトリを作成する サンプルコード import os current_name = os.path.basename(__file__) current_name = current_name.rstrip('.py') if not os.path.exists(current_name): os.mkdir(current_name) for i in range(3): with open(f'{current_name}/{i+1:03}_output.txt', mode='w')as f: f.write('something') コードの説明 スクリプト名の取得とファイルの書き出し 実行中のPythonのスクリプトの名前は以下のようにして取得できます。 main.py import os print(os.path.basename(__file__)) # main.py 今回は拡張子を除いたファイル名を利用するので、rstrip()を使います。 main.py current_name = os.path.basename(__file__) current_name = current_name.rstrip('.py') print(current_name) # main 実際にファイルを書き出します。 その後、globを使ってファイルを確認します。 main.py with open(f'{current_name}_output.txt', mode='w')as f: f.write('something') import glob print(glob.glob('*.txt')) # ['main_output.txt'] 保存したファイル名に実行中のスクリプト名が挿入されました。 複数ファイルの場合 1つのファイルだけだとメリットがわかりづらいので、 次は複数のファイルを作成します。 for i in range(3): with open(f'{current_name}_{i+1:03}_output.txt', mode='w')as f: f.write('something') print(sorted(glob.glob('*.txt'))) # ['main_001_output.txt', 'main_002_output.txt', 'main_003_output.txt', 'main_output.txt'] 100個以上のファイルの並び替えを想定して、 {i+1:03}を使って3桁になるように0埋めを行っています。 さらに、別のスクリプトでもファイルを書き出して、結果を出力してみます。 sub.py import os import glob for i in range(3): with open(f'{current_name}_{i+1:03}_output.txt', mode='w')as f: f.write('something') print(sorted(glob.glob('*.txt'))) # ['main_001_output.txt', 'main_002_output.txt', 'main_003_output.txt', 'main_output.txt', # 'sub_001_output.txt', 'sub_002_output.txt', 'sub_003_output.txt'] ファイルにスクリプト名と連番を振ったおかげで、ディレクトリの見通しがよくなりました。 新しくディレクトリをつくる場合 いくらファイル名を工夫しても、ファイルを大量に書き出す場合は、 スクリプトと同じ階層に書き出すと、ディレクトリの中がごちゃごちゃしてしまいます。 見通しをよくするために新しいディレクトリを作成します。 スクリプト名を挿入する手順はファイル作成時とほぼ同じです。 current_name = os.path.basename(__file__) current_name = current_name.rstrip('.py') if not os.path.exists(current_name): os.mkdir(current_name) print(glob.glob('*/')) # ['main/'] すでに同名のディレクトリがあるとエラーが起きるので、 先にディレクトリの有無をos.path.exists()で調べています。 先ほどと同様に複数のファイルを作成してみます。 今回は保存先を新しく作成したディレクトリの下に設定しています。 #%% for i in range(3): with open(f'{current_name}/{i+1:03}_output.txt', mode='w')as f: f.write(f'something') print(sorted(glob.glob('main/*.txt'))) # ['main/001_output.txt', 'main/002_output.txt', 'main/003_output.txt'] 狙いどおりmain/の下にファイルが作成されたことを確認できました。 最終的なディレクトリ構成 最終的なディレクトリ構成(再掲) . ├── main │ ├── 001_output.txt │ ├── 002_output.txt │ └── 003_output.txt ├── main.py ├── main_001_output.txt ├── main_002_output.txt ├── main_003_output.txt ├── sub.py ├── sub_001_output.txt ├── sub_002_output.txt └── sub_003_output.txt まとめ 複数のスクリプトでファイルを書き出すときの命名が楽になった! どのスクリプトで実行したかがわかるので、再現性が高まる(はず) コード全文 コード全体(クリックで表示) main.py import os import glob current_name = os.path.basename(__file__) current_name = current_name.rstrip('.py') print(current_name) with open(f'{current_name}_output.txt', mode='w')as f: f.write('something') print(glob.glob('*.txt')) for i in range(3): with open(f'{current_name}_{i+1:03}_output.txt', mode='w')as f: f.write(f'something') print(sorted(glob.glob('*.txt'))) if not os.path.exists(current_name): os.mkdir(current_name) print(glob.glob('*/')) for i in range(3): with open(f'{current_name}/{i+1:03}_output.txt', mode='w')as f: f.write(f'something') print(sorted(glob.glob('main/*.txt'))) sub.py import os current_name = os.path.basename(__file__) current_name = current_name.rstrip('.py') for i in range(3): with open(f'{current_name}_{i+1:03}_output.txt', mode='w')as f: f.write(f'something')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mtg-sdk-pythonで四苦八苦する②

前回までのあらすじ MTG Arenaのデッキリストを画像出力するために奮闘するposlogithubであったが、MTG DevelopersとGathererのポンコツっぷりを前に苦戦を強いられているんだ! ここで躓くよmtg-sdk-python 流石にもう問題が起きることは無い。 その考えが《蜂蜜マンモス》より甘いことに気づかされるのに、大して時間はかからなかった。 from hashlib import md5 from urllib.request import urlopen from mtgsdk import Card CARD_BACK_IMAGE_MD5 = 'db0c48db407a907c16ade38de048a441' cards = Card.where(set='AFR').where(number=1).all() # メイス+2 name = None image_url = None for card in cards: if card.foreign_names: for foreign_name in card.foreign_names: if foreign_name['language'] == 'Japanese': image_url = foreign_name['imageUrl'] if image_url: with urlopen(url=image_url) as response: image_data = response.read() if md5(image_data).hexdigest() != CARD_BACK_IMAGE_MD5: name = foreign_name['name'] else: image_url = None break if image_url is None: name = card.name image_url = card.image_url if image_url: break print(name, card.set, card.number, image_url) +2 Mace AFR 1 None は~~~~~??? 英語版カード画像すら無いカードが存在する card.image_urlもcard.multiverse_idもNoneである。 実は《メイス+2》は日本語を主言語とするMTG Arena関連ツール開発者にとっては知らない者はいないほどの問題児で、以下のような逸話を持つ。 mtg-jpのカード・ギャラリーに存在しない。カード個別ページも無い。 MTGJSONにも無い。事実上、multiverse_idが不明。 《メイス+2》を含むデッキリストはMTG Arenaでインポートできない。 こいつに関しては、もう諦めるしかない。 その他 アルケミー アルケミーで調整されたカードは、デッキリストでは以下の扱いとなる。 カード名の先頭に「A-」が付く。 コレクター番号は調整前後で変わらない。 一方、MTG Developersでは以下の扱いとなる。 カード名の先頭に「A-」が付く。 カード番号は調整前後で変わる。 「A-n」というセット番号になる(nは調整前のカード番号) カード番号は文字列型ですよ。あれ、言ってませんでしたっけ? つまり、コレクター番号の先頭に「A-」を付けて検索すればよい。 from hashlib import md5 from urllib.request import urlopen from mtgsdk import Card CARD_BACK_IMAGE_MD5 = 'db0c48db407a907c16ade38de048a441' cards = Card.where(set='STX').where(number='A-41').all() # A-ゼロ除算 name = None image_url = None for card in cards: if card.foreign_names: for foreign_name in card.foreign_names: if foreign_name['language'] == 'Japanese': image_url = foreign_name['imageUrl'] if image_url: with urlopen(url=image_url) as response: image_data = response.read() if md5(image_data).hexdigest() != CARD_BACK_IMAGE_MD5: name = foreign_name['name'] else: image_url = None break if image_url is None: name = card.name image_url = card.image_url if image_url: break print(name, card.set, card.number, image_url) A-Divide by Zero STX A-41 None まあ、カード画像があるとは限らんのですが。 日本語版カード画像はあるのに、カードの日本語名が英語名になっている from hashlib import md5 from urllib.request import urlopen from mtgsdk import Card CARD_BACK_IMAGE_MD5 = 'db0c48db407a907c16ade38de048a441' cards = Card.where(set='NEO').where(number=219).all() # 闇叫び name = None image_url = None for card in cards: if card.foreign_names: for foreign_name in card.foreign_names: if foreign_name['language'] == 'Japanese': image_url = foreign_name['imageUrl'] if image_url: with urlopen(url=image_url) as response: image_data = response.read() if md5(image_data).hexdigest() != CARD_BACK_IMAGE_MD5: name = foreign_name['name'] else: image_url = None break if image_url is None: name = card.name image_url = card.image_url if image_url: break print(name, card.set, card.number, image_url) Gloomshrieker NEO 219 http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=550055&type=card ネオ神河の優良アンコ。 カード画像のフォーマットは実際のデータから確認すること Gathererのカード画像のフォーマットは、JPEGとPNGが混在している。 レスポンスヘッダのContent-Typeを見れば判断できるでしょう。 念のために、Pillowでフォーマット確認もしようかな。 from hashlib import md5 from urllib.request import urlopen from mtgsdk import Card from PIL import Image from io import BytesIO CARD_BACK_IMAGE_MD5 = 'db0c48db407a907c16ade38de048a441' cards = Card.where(set='NEO').where(number=226).all() # 漆月魁渡 name = None image_url = None for card in cards: if card.foreign_names: for foreign_name in card.foreign_names: if foreign_name['language'] == 'Japanese': image_url = foreign_name['imageUrl'] if image_url: with urlopen(url=image_url) as response: image_data = response.read() content_type = response.info()['Content-Type'] format = Image.open(BytesIO(image_data)).format print('Content-Type: '+content_type) print('Format: '+format) if md5(image_data).hexdigest() != CARD_BACK_IMAGE_MD5: name = foreign_name['name'] else: image_url = None break if image_url is None: name = card.name image_url = card.image_url if image_url: break print(name, card.set, card.number, image_url) Content-Type: image/jpeg Format: PNG 漆月魁渡 NEO 226 http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=550063&type=card Gathererくん???? 仕事して???????
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者がPythonでスクレイピングをして気づいたこと_メモ

初投稿 初めまして!Pythonを独学で勉強している超初心者です。 最近webスクレイピングが少しできるようになったので、気づいたことをメモに残したいと思います。 誤った理解や表現が含まれている可能性が大いにあります。 そういった部分はご指摘いただけると幸いです。 モジュール等 ・requests → HTMLファイルを読み込んで表示 ・BeautifulSoup(HTMLデータ, "html.parser") → HTMLを解析 ・time → サーバーに負担をかけないための設定 ・re → 正規表現(使ったのか不明) ・tqdm → 進捗度を表示 ・pandas → データフレーム化 find と find_all ・find  最初に見つけた要素を返す。  要素が見つからなかった場合はNoneを返す ・find_all  全ての要素を返す  要素が見つからなかった場合に空のリストを返す for文とリスト 下記のコードはあるサイトから漫画の情報をスクレイピングしてみた時に書いたコードです。 サイトの漫画一覧表示ページから一度に取得できれば良かったのですが、そのページだけでは欲しい情報が全て載っている訳ではなかったので、タイトルごとのページを見に行くために、一旦ジャンル別に漫画ごとのidを取得したコードです。 ''' max_page = 345 comics = [] comic_id = [] for page in tqdm(range(1,max_page+1)): res = requests.get("https://●●●●●●●●●/title/genre/2?page="+str(page)) time.sleep(1) soup = BeautifulSoup(res.content, "html.parser") comics = comics + soup.find_all("div", class_="mBookListViewDetail") for comic in comics: comic_id.append(comic.find("a").get("href").replace("/title/","")) ''' まず最初にやったことは、ジャンル(genre/2)の最大ページは何ページなのかを調べました。 345ページが最大ということがわかったので、これを変数max_pageに代入しています。 次にfor文とrange関数を使って、ページごとに漫画のidを取得していきます。 まず空のリストを作成し、そこにappendで取得したidを入れていきます。 この仕組みを理解するまでにもだいぶ時間がかかりました。 実際に漫画ごとの情報を取得する ここからはど素人が書いたコードだと見え見えなんじゃないかと思います。汗 取得する情報は[タイトル・著者・発刊・巻数・出版社・掲載誌・対象・ジャンル・評価・評価数・ストーリー]です。 先ほどのコードと同じように、まずはそれぞれ取得する情報の空のリストを作ります。 あとは、またfor文でタイトルごとに情報を取得していき、それぞれ作成したリストに入れていきます。 この時に、タイトルによっては情報が無いものがあり困りました(例えば出版社の情報がない)。 「著者・出版社・掲載誌・対象・ジャンル」が同じタグ名なので、find_all()[2]といった形で取得していきました。 全て載っていれば 著者が[0] 出版社[1] 掲載誌[2] 対象[3] ジャンル[4] で取得できますが、 例えば掲載誌の情報が載ってなければ 著者が[0] 出版社[1] 対象[2] ジャンル[3] となるので、[4]はないよというエラーが起きてしまいました。 そこでif文とlen関数を使ってなんとかうまくいきましたが、この書き方は良くない気がしています。 もっと上手な書き方があるはずと思いながら、力及ばずなところです。 一応やったこととしては 全ての情報があればlen()=5 1つ抜けていればlen()=4 2つ抜けていればlen()=3 となるので、数に応じて[]の中身を変えていくという処理をしています。 あとtryとexceptを使っていますが、これも1つ1つに設定しているので、一括して設定できないのかなぁと思っています。コードが長すぎる。。。 この辺は良い方法があればぜひ教えていただきたいです。 ''' id_count = len(comic_id) titles= [] authors = [] publications = [] rolls = [] publishers = [] magazines = [] subjects = [] genres = [] scores = [] counts = [] storys = [] for id_ in tqdm(comic_id): res = requests.get("https://●●●●●●●●●/title/"+str(id_)) soup = BeautifulSoup(res.content, "html.parser") time.sleep(1) titles.append(soup.find("h1").text) authors.append(soup.find_all("div",class_="mDefinitionListDescription")[0].text) try: publications.append(re.sub(r"\D","",soup.find("p",class_="_publish").text)) except AttributeError: publications.append("None") try: rolls.append(re.sub(r"\D","",soup.find("p",class_="_status").text)) except AttributeError: rolls.append("None") publishers.append(soup.find_all("div",class_="mDefinitionListDescription")[1].text) if len(soup.find_all("div",class_="mDefinitionListDescription")) == 5: magazines.append(soup.find_all("div",class_="mDefinitionListDescription")[2].text) else: magazines.append("None") if len(soup.find_all("div",class_="mDefinitionListDescription")) == 5: subjects.append(soup.find_all("div",class_="mDefinitionListDescription")[3].text) elif len(soup.find_all("div",class_="mDefinitionListDescription")) == 3: subjects.append(soup.find_all("div",class_="mDefinitionListDescription")[1].text) else: subjects.append(soup.find_all("div",class_="mDefinitionListDescription")[2].text) if len(soup.find_all("div",class_="mDefinitionListDescription")) == 5: genres.append(soup.find_all("div",class_="mDefinitionListDescription")[4].text) elif len(soup.find_all("div",class_="mDefinitionListDescription")) == 3: genres.append(soup.find_all("div",class_="mDefinitionListDescription")[2].text) else: genres.append(soup.find_all("div",class_="mDefinitionListDescription")[3].text) try: scores.append(soup.find_all("p",class_="aNumberText")[0].text) except IndexError: scores.append("None") try: counts.append(re.sub(r"\D","",soup.find_all("div",class_="mBigScoreWrap")[0].find(class_="aText").text)) except IndexError: counts.append("None") try: storys.append(soup.find_all("div",class_="_outlineText")[0].text) except IndexError: storys.append("None") 取得した情報を結合 タイトルごとに取得した情報をくっつけます。 ''' comic_info = [] for i in range(0,len(titles)): infos = titles[i],authors[i],publications[i],rolls[i],publishers[i],magazines[i],subjects[i],genres[i],scores[i],counts[i],storys[i] comic_info.append(infos) ''' 完成まで pandasを使ってデータフレーム化します。 ヘッダーを付けます。 ジャンルが複数設定されているので、メインジャンルの列を作成します。 並び替えができるように、対象となるデータをint型に置き換えます。 最後に並び替えをして、CSVに書き込んで完成しました! ''' import pandas as pd df = pd.DataFrame(comic_info) header =["タイトル","著者","発刊","巻数","出版社", "掲載誌","対象","ジャンル","評価","評価数","ストーリー"] df = pd.DataFrame(data=comic_info ,columns=header) #メインジャンル名 df["メインジャンル"] = "SF・ファンタジー" df["巻数"] = round(df["巻数"].replace("","0").astype(int)).replace(0,"None") df["評価"] = round(df["評価"].replace("None","0").astype(float),1).replace(0,"None") df["評価数"] = round(df["評価数"].replace("None","0").astype(int)).replace(0,"None") df =df.reindex(["タイトル","著者","発刊","巻数","出版社", "掲載誌","対象","メインジャンル","ジャンル","評価","評価数","ストーリー"],axis='columns') import csv df.to_csv("Comic_info.csv") '''
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで始めるアルゴリズム入門:FizzBuzzとフローチャート

目次 1.はじめに 2.参考図書 3.フローチャート 4.FizzBuzz 5.ツイッターやってます 6.ホームページはこちら 1. はじめに  私は昔コンピュータサイエンスの学科で「アルゴリズムとデータ構造」をC言語で学んだが、単位はギリギリで、とても苦手意識を持った。セジウィック氏の重厚感のある「アルゴリズムとC」の上下巻を携え、アルゴリズムの1つ1つをC言語で実装していく。C言語自体はカーニハン&リッチーのプログラミング言語Cを用いて学ぶ。カバンも脳みそもパンパンに膨れ上がって消化不良に陥った。今から10年以上前の話である。  時は経ち、より自然言語に近いプログラムで直感的に書くことができるPython3.xシリーズがそのエコシステムとともに世の中に普及し、プログラミング入門と名乗る教材も多数出版されUdemyをはじめとした動画講義も豊富に提供されるようになった。今コンピュータサイエンスを学ぶ人たちは、本当に恵まれている。今の当たり前は先人たちの挫折と汗と涙の上に形成されたものであるということを忘れないでほしい。  前置きはここまでとし、本記事では時代の潮流を捉えたPythonによるモチベーションドリブンなアルゴリズム入門の良著に出会ったので、これを噛み砕いて解説したいと思う。 2. 参考図書 「Pythonではじめるアルゴリズム入門(増井 敏克著)」 3. フローチャート 非プログラマとのコミュニケーションにおいて、フローチャートは非常に強力なツールである。その書き方をおさらいしておく。 意味 記号 詳細 開始・終了 処理の開始と終了 処理 処理内容を書く 条件分岐 ifなどの条件を表す。記号の中に条件書く。 繰り返し forなどでの繰り返しを表す。開始(上)、終了(下)を使う。 定義済み処理 他で定義されている関数や処理を使う。 キー入力 利用者によるキーボード入力を表す。 4. FizzBuzz プログラマーの入社試験としてもよく利用される、FizzBuzz問題を Pythonとフローチャートで見ていく。 まず、FizzBuzzの問題はこちら 1から100までの数を順に出力するプログラムを書きなさい。 ただし、3の倍数の代わりに「Fizz」を5の倍数の代わりに「Buzz」を3と5の両方の 倍数の代わりに「FizzBuzz」を出力しなさい。 これの問題を順に解いていく。 まず、1から100までforループで回して、その数を 順に出力する、という意味のフローチャートはこうだ! これをPythonのコードにするとこうだ!range関数はある値からある値までの連続した 数字をある値ずつ足して返す。 range関数の引数は、range(start, stop[, step])となるが、 stop-1までループするので101と書いているのに注意。 また、[,step]はいくつ足していくかで省略すると1足すことになる。 for i in range(1, 101): print(i) では、今度このコードで3の倍数のときは'Fizz', 5の倍数のときは'Buzz', 3と5の倍数のときは'FizzBuzz'と表示してみる。いきなり1から100までだと、 表示が見ずらいので、1から15までで間違いながらトラブルシュートしていく。 for i in range(1, 16): if i % 3 == 0: print('Fizz') elif i % 5 == 0: print('Buzz') elif i % 5 == 0 and i % 3 == 0: print('FizzBuzz') else: print(i) Output: 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 Fizz ん???んん?? 3と5の倍数もFizzになっている。日本語を上から順にコードに落としたのに、何かおかしい。 それでフローチャートにこの間違ったコードを変換して確認してみる。 このフローチャートで変なのは、条件の判定で、先に3の倍数判定をしているので、 3と5の倍数である、15を流したときには、3で割り切れてしまうので、FizzBuzzが表示されず Fizzが表示されていたことが確認できる。 ではどうすればよかったのか? 変えたところを、黄色で塗りつぶしてある。 iが3と5で割り切れる、というのを先に判定すると、 15のような数が来たときにFizzが出ちゃうことはなくなり正しく 判定できる。 このように、問題を解く順番で答えは変わる。 これを1から100までとして、コードに起こしてみるとこうだ。 for i in range(1, 101): if i % 3 == 0 and i % 5 == 0: print('FizzBuzz') elif i % 5 == 0: print('Buzz') elif i % 3 == 0: print('Fizz') else: print(i) フローチャートはこうだ。 フローチャートで線を交差したら駄目だ。 頭もこんがらがる。最終形はこちら。 プログラマー界隈でフローチャートはオワコンだからUML書こうよという論調もある。 でも、アルゴリズムを考え、非プログラマとコミュニケーションしたり、大規模なコードの どこが間違っているのかを机上で考えるのに、フローチャートほど便利なものはない、 と個人的には思う。 以上。 5. ツイッターやってます 6. ホームページはこちら
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

単純な線形回帰モデルの回帰係数を3つの言語で推定した結果

やったこと 線形回帰モデルの原理について、よさそうな記事を資料中で紹介した。 線形回帰モデルによる回帰係数を、python、R、Juliaの3言語で推定した。 データセットはおなじみいつもの安心と信頼と実績のirisを使った。 推定方法は各言語でよく用いられているライブラリやパッケージによるもののほかに、行列計算用ライブラリを用いた数値計算によるスクラッチ版がある。 記述には.Rmdを用い、ソースをgithubにあげるほかRPubsで公開した。 参考文献 1.はJuliaの行列計算についてわかりやすい記載をされていました。2.は、線形回帰モデルについてよい記載をしているサイトのひとつです。参考になる記事をありがとうございました。 https://zenn.dev/hyrodium/articles/3fa3882e4bca04#2%E3%81%A4%E3%81%AE%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E3%81%AE%E7%A9%8D https://qiita.com/fujiisoup/items/e7f703fc57e2dfc441ad 関連サイト 1.は筆者による、今回作った.Rmdファイルとなっています。2.は、1.をRPubsでみられるようにしたものです。 https://github.com/yaminabeworks/qiitakiji/blob/main/ols.Rmd https://rpubs.com/yaminabeworks/ols 感想 各言語によるデータの取り出し方は筆者が忘れていることもあり、思いのほか時間をとられた気がする。 さすがに線形回帰モデルだけだと寂しいので、よりこじらせた線形モデルや加法モデルくらいはまとめておきたい。 次にやる、この記事に関連すること まずは一般化線形モデルについてスクラッチ実装をしたい。 Rの数値計算に関する本でスクラッチ実装をしていた気がするので、それを参考にする予定です。 次にやる、必ずしもこの記事と関連しないこと ベイズ統計学 ちょっと時間かかりそうです。 情報検索分野 今bm25の計算等をまとめているので時間かかりそうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pytorch-ONNXを量子化して高速化する

変換前に量子化することで高速化できます。 AnimeGANを量子化 import torch model = torch.hub.load("bryandlee/animegan2-pytorch:main", "generator", pretrained="celeba_distill").eval() model_int8 = torch.quantization.convert(model) dummy_input = torch.randn(1, 3, 512, 512) input_names = [ "input" ] output_names = [ "var_444" ] torch.onnx.export(model_int8z, dummy_input, "animegan2_celeba_distill.onnx", verbose=True, input_names=input_names, output_names=output_names, opset_version=11) ColabのGPUランタイムで試すと、これで推論が倍高速になりました。 ? フリーランスエンジニアです。 お仕事のご相談こちらまで rockyshikoku@gmail.com Core MLやARKitを使ったアプリを作っています。 機械学習/AR関連の情報を発信しています。 Twitter Medium GitHub
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RLlibで強化学習したモデルをC#で使う

目的 Ray RLlibで強化学習したモデルをC#で動かせるようにします。 背景 AI・機械学習は python でやるのがライブラリ等、環境が整備されていて一番楽だと思います。 一方でゲーム開発はUnityをよく使うと思いますが、こちらはC#でスクリプトを書きます。 自作ゲームにつよつよAIを導入したくないですか?したいですよね。 僕がまさにその状況にあるので、pythonで学習したモデルをC#に移植する方法を調べてみました。 注意 ここでは僕の用途の関係上、RLlibという強化学習ライブラリで学習させたAIを、C#で使う方法について書きます。 学習済みモデルをC#で使うだけならMicrosoftのこの記事を読んだ方が早いです。 本記事もこのMicrosoftの記事をめちゃくちゃ参考にして書いています。 具体的な目標 PPOエージェントを用いてSimpleCorriderという環境に対して強化学習を実施して、出来上がった学習済みモデルをC#のML.Netライブラリを使って動かします。 作業の概要 pythonでやること: RLlibで強化学習 → onnx形式でエクスポート C#でやること: onnx形式のモデルをインポート → 学習した通りの結果をPredictできることの確認 【Python】RLlibで強化学習 RLlibのGetting Started Guideにある例で強化学習モデルを作成します。 必要なパッケージ 以下のパッケージを使います。 Ray: 強化学習 Tensorflow: ニューラルネットワーク作成・学習 tf2onnx: onnx形式への変換 pip install "ray[rllib]" tensorflow tf2onnx SimpleCorrider環境 これから強化学習するSimpleCorrider環境を説明します。 SimpleCorriderは動作確認用の環境で、面白いものではないです。 直線状にスタート地点とゴール地点があり、スタート地点から開始してプラスかマイナスのどちらかに進むという2つのアクションをとります。 行動のたびにペナルティとしてマイナスの報酬が課され、ゴール地点までたどり着ければプラスの報酬がもらえます。 オブザベーション[int]: 現在の位置 アクション[int]: 0 or 1 (0のとき-1, 1のとき+1進む) 報酬[float]: ゴールしたら+1.0, それ以外は -0.1 うまく学習できれば、ポリシーは1を連打するわけです。 学習したモデルのエクスポート Ray Getting Started GuideのSimpleCorriderを強化学習している使用例のコードをコピペしてください。Ray ML Quick StartのRLlibのドロップダウンリストになっている箇所です。 このコードはPPOTrainerで学習し、最後に学習済みモデルを使って1エピソードを走らせて累積報酬を求めるという内容になっています。この学習の後(trainer.train()のあるfor文を抜けた後)に以下のように追記します。 追記する outdir = "exported_onnx" # 適当なディレクトリ trainer.export_policy_model(outdir, onnx=11) 追記したスクリプトを実行すれば、 outdirディレクトリにsaved_model.onnxというファイル名で学習済みモデルがonnx形式でエクスポートされます。 ただし次のセクションの内容もスクリプトに追加してからの実行をおすすめします。 対応するグラフの変数名を調べる この後C#にモデルをインポートするわけですが、ここでいうモデルというのは、ニューラルネットワークのグラフのことです。 グラフにC#から入出力するには、グラフ上の変数名を指定してやる必要があります。 なので変数名を調べておきましょう。 policy = trainer.get_policy() print(policy._obs_input) # >> Tensor("default_policy/obs:0", shape=(?, 1), dtype=float32) print(policy._sampled_action) # >> Tensor("default_policy/cond_1/Merge:0", shape=(?,), dtype=int64) PPOエージェントの場合はpolicy._obs_inputにあるdefault_policy/obs:0がオブザベーションに対応するグラフの入力名、policy._sampled_actionにあるdefault_policy/cond_1/Merge:0がアクションに対応するグラフの出力名になります。 注意 今回はアクションが離散値なので、default_policy/cond_1/Merge:0が直接アクションに対応しますが、例えば境界のある連続値(gym envでいうBox)であればスケーリング処理はグラフの外側で行われる仕様のため、C#上でグラフの出力を期待したアクションの形式になるように変換する必要があります。 【C#】学習済みonnxモデルの実行 先ほどエクスポートしたsaved_model.onnxをC#で動かします。 必要なパッケージ 以下のパッケージを使います。 Microsoft.ML Microsoft.ML.OnnxRuntime Microsoft.ML.OnnxTransformer 学習モデルのインポート ML.Netでモデルを予測させるのは結構面倒です。 Load() → Predict()くらいシンプルにならんもんかね。。。 入出力の定義 まずグラフの入力と出力を定義します Program.cs public class OnnxInput { [ColumnName("default_policy/obs:0")] public float CurPos { get; set; } } public class OnnxOutput { [ColumnName("default_policy/cond_1/Merge:0"), OnnxMapType(typeof(Int64), typeof(Single))] public Int64[] Action { get; set; } } Attributeにグラフとの対応を指定します。変数名や型については「対応するグラフの変数名を調べる」のセクションを参照ください。 float型は型変換の必要はないですが、それ以外の場合はOnnxMapTypeで変換しないとエラーになります。 インポート Predict()メソッドを持っているのはPredictEngineというオブジェクトです。 このオブジェクトを作るまでが長いです。おまじないだと思って我慢しましょう。 以下のようにします。 Program.csのProgramクラス static PredictionEngine<OnnxInput, OnnxOutput> CreatePredictEngine() { string modelPath = "..../saved_model.onnx"; // Python編でエクスポートしたonnxファイルを指定 var mlContext = new MLContext(); var tensorFlowModel = mlContext.Transforms.ApplyOnnxModel(modelPath); var emptyDv = mlContext.Data.LoadFromEnumerable(new OnnxInput[] { }); var trnsf = tensorFlowModel.Fit(emptyDv); var engine = mlContext.Model.CreatePredictionEngine<OnnxInput, OnnxOutput>(trnsf); return engine; } 愚痴 var trnsf = tensorFlowModel.Fit(emptyDv); この行やばすぎる。 trnsfはOnnxTranformerという名前のクラスのインスタンスなんだけど、NNでTransformerといったらAll you needしか思いつかん。それにFit()はいまにも学習しそうな名前。ややこしいのやめて>< Predict ここまで来れば予測は簡単です。 さっき作成した入出力クラスで型付けされるのでpythonよりも整然でエレガント。 var predictEngine = CreatePredictEngine() var onnxInput = new OnnxInput { CurPos = obs }; var onnxOutput = predictEngine.Predict(onnxInput); 動作確認 ちゃんとonnxモデルを読み込めていて、動作するかを確認します。 せっかくなのでSimpleCorrider EnvのC#版を作ってみます。 SimpleCorriderクラス(C#版) Program.cs public class SimpleCorridor { public int EndPos { get; set; } public int CurPos { get; set; } public SimpleCorridor(int corridorLength) { this.EndPos = corridorLength; this.CurPos = 0; } public int Reset() { this.CurPos = 0; return this.CurPos; } public (int, double, bool) Step(int action) { this.CurPos += ((action == 0) && (this.CurPos > 0)) ? -1 : 1; var done = (this.CurPos >= this.EndPos); var reward = done ? 1.0 : -0.1; return (this.CurPos, reward, done); } } 動作確認 インポートしたモデルで1エピソード実行してみます。 Program.cs static void Main(string[] args) { var env = new SimpleCorridor(10); var predictEngine = CreatePredictEngine(); var obs = env.Reset(); var done = false; double reward; var totalReward = 0.0; while (!done) { var onnxInput = new OnnxInput { CurPos = obs }; var onnxOutput = predictEngine.Predict(onnxInput); // 予測 var action = (int)onnxOutput.Action[0]; // intに変換 (obs, reward, done) = env.Step(action); totalReward += reward; } Console.WriteLine("Total Reward = {0}", totalReward); } これで Total Reward がpythonのときと同じくらいになれば成功です。 おつかれさまでした。 参考文献 Ray documentation RLlibのエクスポートの例 Make predictions with an AutoML ONNX model in .NET
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【トリビアのDelta Lake】#3 Pysparkで全行とも同じ値が入った列を新設したい

Pysparkで、同じ値だけを各行に入れた列を新設したい 今回は少しマイナーなTipsかもしれませんが、意外と「あれどうやるんだっけ?」となることが多いので書いてみます。 今回も例によってサンプルデータを用意します。 sample_data = [{'name': 'Alice',"age": '2',}, {'name': 'Bob',"age": '50'}, {'name': 'Matilda',"age": '12'}] df = spark.createDataFrame(sample_data) df.show() 【結果】 +---+-------+ |age| name| +---+-------+ | 2| Alice| | 50| Bob| | 12|Matilda| +---+-------+ ここに、birthplace(出身地)という列を新設しようと思います。 値となる地名は何でもいいですが、せっかく名前にマチルダを追加したのでここでは映画『レオン』に則って「NewYork」としましょうか。 3人とも、全員ニューヨーク出身として、NewYorkという値が各行に入った列を追加します。 litで値を囲むのがミソ 結論、こんな感じで書くことが出来ます! from pyspark.sql.functions import lit df_add_birthplace = ( df .withColumn("birthplace",lit("NewYork")) ) df_add_birthplace.show() 【結果】 +---+-------+----------+ |age| name|birthplace| +---+-------+----------+ | 2| Alice| NewYork| | 50| Bob| NewYork| | 12|Matilda| NewYork| +---+-------+----------+ 列を追加するコマンドとして.withColumnがある、というのは割と有名ですよね。 ここでは df.withColumn("新設するカラム名",lit("新設列に入れる値")) といった文法になります。 想定されるユースケース:名前からメアドを生成 といっても、Pysparkで行うETL作業の中で、元データから必要なものだけに削ぐことはあっても追加する、ってことは経験上あまりないのかなと思っています。 そのなかでも、想定されるユースケースとしては、例えば「メールアドレスの生成」があるかなと思います! sample_data = [{'name': 'Alice',"age": '2',}, {'name': 'Bob',"age": '50'}, {'name': 'Matilda',"age": '12'}] df = spark.createDataFrame(sample_data) #「mail_domain」列を追加する from pyspark.sql.functions import lit df_add_mail_domain = ( df .withColumn("mail_domain",lit("@amail.com")) ) df_add_mail_domain.show() 【結果】 +---+-------+-----------+ |age| name|mail_domain| +---+-------+-----------+ | 2| Alice| @gmail.com| | 50| Bob| @gmail.com| | 12|Matilda| @gmail.com| +---+-------+-----------+ そして、nameとmail_domainをconcatで結合すると、晴れてメールアドレスの出来上がりです! (今回は架空ドメインの「@amail.com」を使うことにします) from pyspark.sql.functions import col,concat df_add_mail_address = ( df_add_mail_domain .withColumn("mail_address",concat(col("name"),col("mail_domain")) ) ) df_add_mail_address.show() 【結果】 +---+-------+-----------+-----------------+ |age| name|mail_domain| mail_address| +---+-------+-----------+-----------------+ | 2| Alice| @amail.com| Alice@amail.com| | 50| Bob| @amail.com| Bob@amail.com| | 12|Matilda| @amail.com|Matilda@amail.com| +---+-------+-----------+-----------------+ ちなみに文法はこんな感じ。 (データフレーム).withColumn("新設するカラム名",concat(col("結合列①"),col("結合列②")) いかがでしょうか? この他にも「この列の値に特定の値を付与したいよね」というニーズには、上記のような「litで新列作る」→「concatで結合」の流れを踏むことで応えられる気がします。 あ、マチルダにメールは送らないでくださいね笑 このシリーズの目的 クラウドストレージに蓄積された生データ(CSV,JSON等)を加工するのに必要なPysparkの知識を溜めていく、まさに「Pysparkに関するトリビアのDalta Lake」を目指しています。 Pysparkに関する記事はまだ海外のものが多く、このシリーズが私のような国内の駆け出しデータエンジニアの一助になることを願っています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む