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

残プロ 第-12回 ~第6回から第11回のまとめ~

今週のまとめ 第-6回(月) pandasを利用した.csvの読込,データを1行ずつ処理 第-7回(火) 正規表現モジュールreを使って文字列を分割 第-8回(水) datetime,calendarモジュールを使用,文字列をdateオブジェクトに変換 第-9回(木) 第6回から第8回のプログラムを組み込み 第-10回(金) 文字列に絵文字を含める,スクレイピングでデータ取得,matplotlibで画像化 第-11回(土) RaspberryPiのcronを利用し.pythonを定期実行 今週の学び 第-6回 pandas read_csv, to_csvは引数encodingが指定できる.指定しない場合,文字化けすることがある. to_csvは引数indexで保存の際にインデックスを含めるか指定できる. 空白はfloat型のnanとして読み込まれる.いまだ上手く処理する方法を発見できず. import pandas as pd def csvEdit(path_csv): csv = pd.read_csv(path_csv) for row in csv.itertuples(): csv.at[row[0], 'hoge'] = huga csv.to_csv(path_csv) 第-7回 re splitメソッドの返り値はリスト 簡単な処理であっても正規表現をつかった方が後々便利 import re def separateStr(string): separated_comma = re.split(',', string) separated_upper = [] for s in separated_comma: separated_upper.append(re.findall('[A-Z][a-z]+', s)) return separated_upper 第-8回 datetime, calendar 次の第何何曜日の計算には,今月の指定曜日を計算したのち今日と比較.過ぎていた場合は翌月を計算する 第-9回 組み込みのみのため割愛 第-10回 beautifulsoup4, matplotlib スクレイピングは意外と簡単 マトラボで綺麗な表を作るのは意外と大変 第-11回 cron cronはホームディレクトリで実行されるので,指定には絶対パスを使用する. プログラム内でファイルを扱う場合も,同ディレクトリまたは絶対パスを使用しないと上手く実行できない. 今週の総括,来週の予定 今週はなかなか時間が割けず,中途半端な記事が多くなってしまいました.推敲がほとんどできていないのは惜しいなぁと思います. 予定していたタスク管理(csv編集プログラム)も未完の部分があるので,来週はそこを終わらせたいと思います.また,RaspberryPiの外部ネットワーク接続を試します.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python Win32プログラミング その3

Win32 APIを使ってHTTP通信をやってみます。WinINetというライブラリを使います。 from ctypes import * InternetOpen = windll.wininet.InternetOpenW InternetConnect = windll.wininet.InternetConnectW HttpOpenRequest = windll.wininet.HttpOpenRequestW HttpSendRequest = windll.wininet.HttpSendRequestW InternetReadFile = windll.wininet.InternetReadFile InternetCloseHandle = windll.wininet.InternetCloseHandle GetLastError = windll.kernel32.GetLastError ドキュメント(InternetOpenW)を見ると、下の方にDLL Wininet.dllと書かれています。 これより、モジュールのパスは(user32ではなく)windll.wininetになります。 INTERNET_OPEN_TYPE_PRECONFIG = 0 lpszAgent = "test" # ユーザーエージェント(適当) hInet = InternetOpen(lpszAgent, INTERNET_OPEN_TYPE_PRECONFIG, None, None, 0) if hInet == 0: raise Exception INTERNET_OPEN_TYPE_PRECONFIG - レジストリから何か設定を読み込むみたいです。 詳しい意味や他のパラメータなど、ドキュメントにすべて書かれています。ただ、どれにすればいいかは試行錯誤です。また、InternetOpenAなど~Aでは通らなかったので、すべて~Wにしています。 (C言語では~Aでも通るのに) InternetOpenとInternetConnectで初期化、これは最初に一度だけやればよいです。 HttpOpenRequest,HttpSendRequestで送信して、InternetReadFileで応答を(バイト配列で)受信します。 import sys from ctypes import * InternetOpen = windll.wininet.InternetOpenW InternetConnect = windll.wininet.InternetConnectW InternetCloseHandle = windll.wininet.InternetCloseHandle HttpOpenRequest = windll.wininet.HttpOpenRequestW HttpSendRequest = windll.wininet.HttpSendRequestW InternetReadFile = windll.wininet.InternetReadFile GetLastError = windll.kernel32.GetLastError INTERNET_OPEN_TYPE_PRECONFIG = 0 INTERNET_SERVICE_HTTP = 3 INTERNET_DEFAULT_HTTP_PORT = 80 try: lpszAgent = "test" hInet = InternetOpen(lpszAgent, INTERNET_OPEN_TYPE_PRECONFIG, None, None, 0) if hInet == 0: raise Exception lpszServerName = "docs.microsoft.com" hConnect = InternetConnect(hInet, lpszServerName, INTERNET_DEFAULT_HTTP_PORT, None, None, INTERNET_SERVICE_HTTP, 0, None) if hConnect == 0: raise Exception lpszObjectName = "en-us/windows/win32/api/wininet/nf-wininet-internetopenw" hRequest = HttpOpenRequest(hConnect, "GET", lpszObjectName, None, None, None, 0, None) if hRequest == 0: raise Exception lpszHeaders = "Content-Type: application/x-www-form-urlencoded" ret = HttpSendRequest(hRequest, lpszHeaders, -1, None, 0) if ret == 0: raise Exception dwRead = c_long(1) while dwRead.value > 0: lpBuffer = create_string_buffer(1024) ret = InternetReadFile(hRequest, lpBuffer, 1024, pointer(dwRead)) if ret == 0: raise Exception sys.stdout.buffer.write(lpBuffer.value) # 応答を出力 except Exception as e: print(GetLastError()) raise e finally: if hRequest != 0: InternetCloseHandle(hRequest) if hConnect != 0: InternetCloseHandle(hConnect) if hInet != 0: InternetCloseHandle(hInet) 実行結果 <!DOCTYPE html> ...(略)... <div id="action-panel" role="region" aria-label="Action Panel" class="action-panel has-default-focus" tabindex="-1"></div> </body> </html> 参考までに、対応するC言語のソース gcc .\wininet_test.c -lWininet (Mingw-w64で確認) wininet_test.c #include <windows.h> #include <wininet.h> #include <stdio.h> int main(void) { char *lpszAgent = "test"; HINTERNET hInet = InternetOpenA(lpszAgent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); if (hInet == NULL) { printf("InternetOpenA error %d\n", GetLastError()); goto exit; } char *lpszServerName = "docs.microsoft.com"; HINTERNET hConnect = InternetConnectA(hInet, lpszServerName, INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, (DWORD_PTR)NULL); if (hConnect == NULL) { printf("InternetConnectA error %d\n", GetLastError()); goto exit; } char *lpszObjectName = "en-us/windows/win32/api/wininet/nf-wininet-internetopenw"; HINTERNET hRequest = HttpOpenRequestA(hConnect, "GET", lpszObjectName, NULL, NULL, NULL, 0, (DWORD_PTR)NULL); if (hRequest == NULL) { printf("HttpOpenRequestA error %d\n", GetLastError()); goto exit; } char *lpszHeaders = "Content-Type: application/x-www-form-urlencoded"; BOOL bRet = HttpSendRequestA(hRequest, lpszHeaders, -1, NULL, 0); if (bRet == FALSE) { printf("HttpSendRequestA error %d\n", GetLastError()); goto exit; } char buf[1024] = {0}; DWORD dwRead = 0; do { bRet = InternetReadFile(hRequest, buf, 1023, &dwRead); if (bRet == FALSE) { printf("InternetReadFile error %d\n", GetLastError()); goto exit; } buf[dwRead] = 0; // ヌル終端 printf(buf); } while (dwRead > 0); exit: if (hRequest != NULL) InternetCloseHandle(hRequest); if (hConnect != NULL) InternetCloseHandle(hConnect); if (hInet != NULL) InternetCloseHandle(hInet); return 0; }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rasberry PiとArduinoのI2C接続(IoTに必須なアナログ電圧の測定)

はじめに センサの値を取得するために必須とされるアナログ電圧の測定をRasberry Piを使って行います。これができるようになると、RasberryPiがさまざまなアナログ系のセンサ(温度センサや超音波による距離センサなど)の値を認識できるようになるので、飛躍的に活用の幅がひろがります。ただ、そのためのいくつか方法があります。専用のIC(ADコンバータ、MCP3208やMCP3008)を使う手法があり、おそらくそれが一番手っ取り早いと思われますが、今後の拡張性を考えてArduino UNO(これも互換ボードなら、1000円以下で入手できることもあるようです)を使う方法もポピュラーなようです。すでにたくさんの立派な解説(一番下の参考に記載)が出ていて、あえて記事にする必要もないかもしれませんが、実際にやってみたので、ざっとやることを理解したい方向けに、簡単にやったことを紹介します。これをみていただければ、そこまで大変な作業でないことはご理解いただけるのではないかと思います。なお、以下の内容と実践は自己責任でお願いいたします。 Arduinoの事前準備 まず、Arduino側でアナログ電圧の値の読み取りができるようにセッティングをします。今回は、A0とGND(接地)間の電圧値を取得することにしたいと思います。ラズパイ側からデータが要求された時に、analogRead(A0)で電圧値を取り出して、Wire.writeでI2C通信をさせてRasberry Piの方に読み取ったデータを送るようにします。 以下のプログラムをArduino IDEで作成し、コンパイルおよびボードへの書き込みを行います。 #include <Wire.h> void setup() { // I2Cバスにアドレス8を使うということで、設定します。 Wire.begin(0x8); // データが要求された時にsendReading関数が呼ばれるように設定します。 Wire.onRequest(sendReading); } // A0ピンの値を読み、4分の1にしてI2Cバスに書き込みます void sendReading() { int reading = analogRead(A0); Wire.write(reading/4); } void loop() { delay(100); } ラズパイ・Arduino・電子回路間の接続 次に、ラズパイ、ArduinoUNO,そして電子回路の接続を行います。ボードのピンのアサインメントをよく確認しながら、以下の通り、結線をしてください。 Rasberry Piの設定(OSの設定) デフォルトでは、ラズパイはI2C通信ができるようになっていないため、設定をする必要があります。Raspberry Pi OSを立ち上げて、スタートメニューから、設定 -> Raspberry Piの設定 を選択します。そうすると、設定画面が出てきますので、この画面の項目のI2Cのところを「有効」にします。 Rasberry PiでのPythonコード(ターミナル操作) いよいよ、Rasberry Pi側からI2C通信を使って電圧を取得する命令を出していきます。ターミナルを立ち上げて、python3を起動し、以下のコードを入力してみてください。 % python3 >> from smbus import SMBus >> arduino = 0x8 >> i2cbus = SMBus(1) >> i2cbus.read_byte(arduino) そうすれば、電圧相応の値が出てくると思います。可変抵抗でつまみを回して抵抗値をかえてやると、値の変化もみられると思います。 ①A0-GND間の実際の電圧値と②ここで表示される値の関係は、比例の関係にあるので、オシロスコープなどでその係数を出せば、抵抗にかかっている電圧値をラズパイだけで認識できるようになる、というわけです。 接続イメージ あまり綺麗なものではありませんが、一応見た目はこんな感じということで参考としてイメージをのせておきます。 参考情報
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バスの到着時刻が正規分布に従っているか実験してみた

背景 プラントエンジニアのKeisukeです。 去年まで千葉で業務をしていましたが、2021年4月から北海道に転勤して工場で仕事をしています。 現場目線でプラントを見れるので学ぶ事も多く良い経験になっています。 現場は経験がものを言う世界ですが、運転データや不具合記録を分析をして読み取れることも多々あります。今までになかった観点から現場の力になれればと思いながら日々活動しています。 動機 働いている工場までは自宅から10km、車なしでは到底通勤できない距離です。私は自家用車を持っていませんが、幸いなことに会社から社有バスが出ており、バス停から拾ってもらって通勤をしています。感謝感激です。 バスに揺られながら私はふとこんなことを考えました。 「バスの到着時刻って正規分布にのるのかなぁ?」 夏休みの自由研究でやるような素朴な疑問を解決すべく、私はバスの到着時刻をこの2ヶ月間、せっせと収集していました。社会人も腰を据えて自由研究に取り組める日がいつか来ることを祈りつつ、、、 データ収集 日付 バス到着時刻 4月2日 07:10:02 4月5日 07:08:27 4月6日 07:08:17 4月7日 07:11:02 4月8日 07:09:12 4月9日 07:10:07 4月12日 07:08:07 4月16日 07:12:17 4月19日 07:08:17 4月20日 07:09:57 4月21日 07:05:23 4月22日 07:08:55 4月26日 07:11:30 4月27日 07:07:22 4月28日 07:08:30 5月6日 07:08:22 5月7日 07:07:32 5月10日 07:11:35 5月11日 07:07:20 5月12日 07:07:35 5月13日 07:10:17 5月19日 07:08:00 5月20日 07:08:37 5月24日 07:07:57 5月25日 07:10:15 5月26日 07:07:17 5月27日 07:08:34 ということで上表がバス到着時刻のデータになります。会社の朝は結構早いです。6月現在はまだ暖かいですが、あと半年もすれば冬の猛烈な寒波がやってきます。冷え性な私が北海道の凍てつく寒さに耐えられるか心配で夜しか眠れません。 7時6分が到着予定の時刻でやや遅れがある感は否めませんが、いい感じに分布してそうな雰囲気です。 到着時刻データをヒストグラムで表したものが上図になります。7時00分00秒を基準(0[s])としています。 正規分布の形はおぼろげながら見えますが、ちょっと情報量が足りずジグザグな感じが拭えません。それもそのはず今回のデータ数は27個。 評価方法 正規分布に従っているかを確認する手法として ・Q-Qプロット ・シャピロ・ウィルク検定(S-W検定) ・コルモゴロフ・スミルノフ検定(K-S検定) があります。 Q-Qプロット Q-Qプロットはグラフに引いた直線と今回のデータがどれだけ一致しているかを見る定性的な評価方法です。実際に解析した結果が下図になります。 赤い直線にプロットが乗ると正規性が強い事を示します。 どうでしょうか、横軸の両端のデータも直線に乗っていて、正規性を捉えているように感じます。しかし、この手法では正規性を定量的に評価することが出来ません。なので他の方法もトライしてみます。 シャピロ・ウィルク検定(S-W検定) S-W検定はデータからP値を計算して定量的に判定する手法になります。 P<0.05であれば「母集団が正規分布である」という帰無仮説を棄却します。 今回は P = 0.2995 という結果で、母集団が正規分布であるという帰無仮説「母集団が正規分布である」は棄却されないという結果になりました。 コルモゴロフ・スミルノフ検定(K-S検定) K-S検定はデータ数が多い(1000以上)時に使われる検定です。今回のデータ数は少なく適切ではないですが一応トライしてみます。 P = 0 P<0.05となり、K-S検定では帰無仮説が棄却されるという結果になりました。 まとめ 今回の結果をまとめます。 評価方法 正規性 Q-Qプロット △(○) S-W検定 ○ K-S検定 × 2ヶ月分の少ないデータ量でしたが、一部の検定で正規性が垣間見える結果となりました。 6月13日現在も絶賛データ収集中なので、バスの到着時刻が正規分布に近づくのかを引き続き検証していきます! みなさんもバスや電車を待っている時の暇つぶしとしてトライしてみて結果を報告してくれると嬉しいです!! 参考文献
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginner Contest 205 参戦記

AtCoder Beginner Contest 205 参戦記 ABC205A - kcal 2分で突破. 書くだけ. A, B = map(int, input().split()) print(A * B / 100) ABC205B - Permutation Check 2分半で突破. 個数カウントしても良かったけど、まあいいかと一つづつチェックした. N, *A = map(int, open(0).read().split()) A.sort() for i in range(N): if i == A[i] - 1: continue print('No') break else: print('Yes') set に押し込んで個数が減らないかチェックするのが一番楽そうだと今気づいた. N, *A = map(int, open(0).read().split()) if len(set(A)) == N: print('Yes') else: print('No') ABC205C - POW 14分で突破、WA1. やらかした. どちらも乗数は同じなので偶奇が変わらないように減らしてあげれば OK. A, B, C = map(int, input().split()) C = (C + 1) % 2 + 1 if pow(A, C) < pow(B, C): print('<') elif pow(A, C) > pow(B, C): print('>') elif pow(A, C) == pow(B, C): print('=') ABC205D - Kth Excluded 30分で突破. Ai が一番右端だと思うと、Ai+1 は Ai-i+1 番目の数になる. 実際には右端とは限らないので、Kを超えない Ai を二分探索で探してそこをベースに計算すれば良い. from sys import stdin readline = stdin.readline N, Q = map(int, readline().split()) A = list(map(int, readline().split())) result = [] for _ in range(Q): K = int(readline()) ok = 0 ng = N + 1 while ng - ok > 1: m = ok + (ng - ok) // 2 if A[m - 1] - m < K: ok = m else: ng = m result.append(K + ok) print(*result, sep='\n')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Marqeta使ってみた(その1)

先日IPOしたばかりのMarqetaプラットフォームを使ってみました。MarqetaはSandboxが提供されているので気軽に遊んでみることができそうです。 Sandboxの使い方はこちらから: https://www.marqeta.com/docs/developer-guides/core-api-quick-start まずはアカウント作成 メールアドレス含む必要な情報を記入して進みます。 メール受信しアドレスが確認されます。その後、SNSでも確認されます。 ダッシュボードへアクセス しばらく経つとサンドボックス作成のメールが来るので、ダッシュボードへアクセスします。 SDK 現在PythonとRubyのSDKが用意されてるようです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonからPostgreSQLを操作

PythonからPostgreSQLを操作 ■Postgreをwindows10にinstall $ sudo apt update $ sudo apt install postgresql 再起動 $ sudo /etc/init.d/postgresql restart ユーザをpostgresに切り替え $ sudo -u postgres -i ユーザ(user)を作成. パスワードを聞かれるので,パスワードを設定 $ createuser -d -U postgres -P user user用データベースを作成 $ createdb userdb –encoding=UTF-8 –owner=user ■psycopg2をinstall PostgreSQLを操作するためpsycpg2をinstall $ pip install psycopg2 エラーが出力される場合は以下コマンドを実行 $ pip install psycopg2-binary $ sudo apt install libpq-dev $ sudo apt install postgresql-common コマンドを実行したら再度インストール $ pip install psycopg2 ■DataBaseに接続 postgresConect.py import psycopg2 connection = psycopg2.connect( host='localhost', database='userdb', user='user', password='password') cur = connection.cursor() cur.execute('SELECT * FROM mybook;') results = cur.fetchall() print(results) cur.close() connection.close() 参考 Windows10のWSL(Ubuntu)にPostgreSQLをインストールしたときのメモ https://qiita.com/syutaro/items/d08c5c7cc24cddf7694c psycopg2 インストールエラー https://qiita.com/b2bmakers/items/d1b0db5966ac145b0e29 初心者がPythonを使ってPostgreSQL に接続する https://qiita.com/arupaka__ri/items/5d4711c279d111622117 psycopg2 でよくやる操作まとめ https://qiita.com/hoto17296/items/0ca1569d6fa54c7c4732
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アノテーション→ならfiftyoneが使えるかも?

はじめに さて、fiftyoneとはなんぞやと思われたのではないでしょうか? 自分もこの前まではそんな感じでした。 ざっくりと言ってしまえば、アノテーションをmatplotとか使わずに、またlabelImgなどを使わずにお手軽に確認することができるツールです。さらに、フォーマットの変換もすることができる優れものです。 Deep learningとかで物体検知のモデルを学習する際には、絶対と言っていいほどアノテーションがついた画像が必要になってくるはずです。さらに、たくさんのデータセットを集めたときにはフォーマットが違って苦労します。 そんな困った問題を解決してくれるかもしれないのが、何を隠そうfiftyoneなのです。ちなみにここで断言してないのは、まだ対応してないフォーマットがあるからです。 今回はこのfiffyoneなるものを、日本語で書いてる方が見た感じ居られなかったので、自分のほうが簡単に紹介をしていけたらなと思います。 fiftyoneとは fiftyoneサイト fiftyoneの特徴を以下にまとめてみました。ちなみに僕もまだまだ使い慣れてないので知らない機能はあったりします。 アノテーションを手軽に確認することができる アノテーションのフォーマット変換ができる(できないやつもある) pipでインストール可能 アノテーションをすることができる 個人的によく使うのは、最初の2つかなと思います。アノテーションはコードでしか指定することができないので、流石にlabelImgのほうがいいですね。けど、fiftyoneには事前学習済みモデルがあり、それを使用してアノテーションを自動で付けることができたりします。(意外に精度はいいです) 上記のサイトを開いていただけると分かるのですが、Colabでお手軽に実験することができます。 実際に使ってみよう まず、fiftyoneを使用するためにインストールを行います。 pythonのバージョンは 3.6 - 3.9が推奨されています。 FIFTYONE INSTALLATION pip install fiftyone また、tensorflowがないとエラーを吐くことがあるのでtensorflowもついでにインストールしておくのがいいです。 バージョンは2番台になるとまだ対応してない部分があるので1.14などが無難です。 次にCOCOやKITTIといったそれぞれのフォーマットをfiftyoneで扱うためのフォーマットに変換します。 import fiftyone as fo name = "test" #fiftyoneのデータセットの名前 dataset_dir = "/content/test" #画像やアノテーションがあるディレクトリ dataset = fo.Dataset.from_dir(dataset_dir, fo.types.KITTIDetectionDataset, skip_unlabeled=True, name=name) #skip_unlabeldはオプション print(dataset) print(dataset.head()) 出力結果が 100% |█████████████| 14420/14420 [6.4m elapsed, 0s remaining, 73.3 samples/s] のように画像枚数がしっかりと表示されていたら成功です。 表示されていなければ、先程使ったnameと同じ名前は使えないので以下のコードで、一度削除して原因を探し、もう一度実行してみましょう。 print(fo.list_datasets()) dataset = fo.load_dataset("test") dataset.delete() print(dataset.deleted) ディレクトリの構造にはfiftyoneの決まりがあり、例えばKITTIフォーマットのデータを読み込もうとした場合は、 下の画像のようにメインディレクトリの中に画像を保存するdata、アノテーションのファイルを保存するlabelsというディレクトリを配置する必要があります。 ディレクトリの名前はfiftyone規定の名前、今回であれば「data」,「labels」という名前しか受け付けないので、 もしディレクトリの名前を変えるのに手間がかかる場合などはシンボリックリンクなどを活用しましょう。 fiftyoneの形にフォーマットを変更できれば後は簡単です。 以下のコードで表示までできます。 session = fo.launch_app(dataset) ちなみにリモートサーバー上の環境で作業をしていても、fiftyoneを使用することは可能です。ただ、ポートフォワーディングをする必要があり、デフォルトで使用されているポートは5151です。 ポートフォワーディングした状態で、先程のコードにremote=Trueを追加すれば動作させることができます。 session = fo.launch_app(dataset, remote=True) #port=XXXXを追加することで、ポートを変えることも可能 上記を実行すると以下のようにアプリが起動します。LABELSのところで、見たいアノテーションのラベルを選択したり、+add stageのところで ファイルをソートしたりすることも可能です。 アノテーションのフォーマットを変換 上記では、データをfiftyoneの形式に変換し、表示するところまで行いました。 ここからは、割と重宝されるのではないかなと思ってるフォーマット変換について触れていきたいと思います。 物体検出などのディープラーニングをするときには、学習用にデータセットを探し集めることがありますよね。このモデルはYOLOフォーマットしか受け付けない、あのモデルはKITTIしか受け付けない、といったことがあると思います。 そのときには、自分でjsonやtxt、matをpythonで読み込んで変更するなどの作業をすると思いますが、そこでfiftyoneが使えます。あ、ちなみに見たことないフォーマットは自分でYOLOなどのフォーマットに変換してください笑(一応オリジナルのフォーマットを読み込めるみたいですが、使い方はいまいちわかっていません) アノテーションのフォーマットの変換自体は意外に簡単に行なえます。実は、アノテーションデータをfiftyoneフォーマットに変換できた時点でほぼ終わっています。以下のコードで変換することができます。 EXPORTING FIFTYONE DATASETS export_dir = "/content/test/fiftyone" label_field = "ground_truth" #print(dataset)したときの「Sample fields」の欄から選択可能 dataset.export( export_dir=export_dir, #保存用のディレクトリ dataset_type=fo.types.VOCDetectionDataset, #exportするアノテーションのフォーマット label_field=label_field, overwrite=True, ) 先程見たことないフォーマットを変換するのは自分で、と書きましたがそのときにfifityoneが使えないわけではないです。 例えば、自分でフォーマットを変換するコードを書く → fiftyoneでアノテーションが正しいか確認する → もし、アノテーションを他のフォーマットで使いたいとなればすぐ変換することができる といったようにfiftyoneを有効に使うことができます。自分もよくそのように使っており、割と手間が省けているので便利だなと感じていたりします。 終わりに 今回はfiftyoneというアノテーションツールを紹介させていただきました。このツールを日本語で紹介されている記事がなく、自分の方から軽くにはなりますが、記事にしました。fiftyoneは奥が深く、いろいろなオプションであったり、モジュールがあります。自分もまだまだ勉強不足でわからない部分もあります。そこで、ぜひ本記事を読んだ方に詳しくなっていただき、ご教授願いたいなと思っていたりします笑。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PyAutoGUI】HYPER SBIからマザーズ指数のcsvを自動取得する

0.はじめに 株価指数を自動で落とせるところないかなぁ~なんて探しましたが、無料の場合API含めても全て網羅されているのがinvesting.comくらいしかなかったです。 でも実はSBI証券等の証券会社ツール経由だとこれらを簡単に取得できるので、最近巷でよく耳にすることが増えたPyAutoGUIというのを使ってcsvの保存を練習兼ねて自動化してみました。 この技術を使えば、株以外にも普段の業務や提携作業等の自動化への応用できると思うので、株やってない方でも軽い気持ちで見ていってください。 動作環境 OS : Windows10 pro Python : 3.8.3// Miniconda 4.9.1 PyAutoGUI : 0.9.52 HyperSBI Ver.2.5 jupyter notebook 作業日は「2021/6/13」時点なので注意。今後市場再編があるので・・ 1.PyAutoGUIの基礎 基本的には以下サイトに書いてありますが、本記事でもその中からいくつか紹介します 他にも便利な機能(キャプチャや画像検索等)あるので、気になった方は調べてみてください。 1-1.マウス位置取得と画面の基本 まずは基礎中の基礎。色んな位置にマウスを動かしながら試してみると何となくわかってくる。 なおpyautoguiはpipでインストール可能 #!pip install pyautogui import pyautogui #まずは現在のマウスカーソルの位置を出力 print(pyautogui.position()) #メイン画面のサイズを取得 ※マルチモニタの場合はメイン設定のディスプレイが基準になる print(pyautogui.size()) 実行結果 Point(x=755, y=826) ※今の位置なので数値は参考程度に Size(width=1920, height=1080) ※使用ディスプレイで結果は変わります 1-2.マウス操作 次にマウスがかかわる操作からいくつか紹介。 #座標を指定 x,y = 0,0 """ 以下試す時は1個ずつお願いします 様々なオプション等もありますが、ここでは省略してます """ #マウスカーソルを指定座標まで移動 pyautogui.moveTo(x, y) #マウスカーソルを指定座標まで移動させた後に1回だけ左クリック pyautogui.click(x, y, clicks=1, button='left') #マウスカーソルを指定座標まで移動させた後にダブルクリック pyautogui.doubleClick(x, y) #マウスカーソルを現在位置から指定位置までドラッグ pyautogui.dragTo(x, y) 1-3.キーボード操作 詳細は先程のドキュメントサイトを見るか、ほかの方の記事を見てください。 ここではよく使うものだけ紹介します。 pyautogui.press('enter') #Enterキーを押す pyautogui.press("left", presses=2) #矢印キーの左を2回押す pyautogui.keyDown('ctrl') #ctrlキーを押し続ける pyautogui.keyUp('ctrl') #ctrlキーを押し続けるのを解除 pyautogui.hotkey('ctrl','s') #hotkeyで2個同時押し pyautogui.typewrite('aiueo') #キーボード文字を連続入力する 2.(前準備)ユーザーアカウント制御の警告を停止 「よし、これでHyprSBIなんて楽勝だぜ!」と思って挑むと全く入力を受け付けてくれないことに気づきます。なぜならHyperSBIにはユーザーアカウント制御がかかっており、これをPyAutoGUIで操作できないからです。 以下アイコンを見てみると、右下にセキュリティマークがついていることに気づきます。これがそのユーザーアカウント制御なのです。 これを無効化するには、ユーザーアアカウント制御設定を「通知しない」にすればいいんですが、セキュリティの観点からよろしくありません。よって、このアプリだけ対応します。 2-1.プロパティのショートカットリンク先をコピー デスクトップにHyperSBIのショートカットをまず用意します。 右クリックのプロパティから「リンク先」をコピー 2-2.タスクスケジューラーを起動し、タスク名称を設定 全般タブの名前を適当につける ※今回はHyperSbiNoUacとしてます 2-3.操作タブからコピーしたリンク先をスクリプト指定 2-1で取得したリンク先をプログラム/スクリプトの個所に張り付ける  ※前後のダブルクオテーションも必要! 終わったらタスクとして登録する(プロパティ画面でOKを押す) 2-4.ショートカットのリンク先を変更する 2-1と同じショートカットのプロパティを開き、リンク先を以下のように変更してOKを押すと、セキュリティマークが消えている! C:\Windows\System32\schtasks.exe /run /tn HyperSbiNoUac ※最後の「HyperSbiNoUac」は設定した名前、schtasks.exe /run /tnでタスク実行という意味 これで準備OKである。実際にクリックしてユーザーアカウント制御画面が出ないことが確認できると思う。 このショートカットをデスクトップのわかりやすいところに配置しておきましょう。 3.HyperSBIから株価指数のcsvを自動的に取得する 今回はjupyter Notebookで実行するのだが、「管理者権限で実行」する必要がある為、コマンドプロンプトを「管理者として実行」した後に、jupyter notebookと打ち込んで起動すればいい。 私はめんどいので毎回batファイル経由でjupyterを起動しているのだが、その場合はbatのショートカットを作り、ショートカットプロパティから「管理者として実行」を設定するだけでいい 3-1.管理者権限が付与されているか確認する jupyterで以下コマンドをたたき、1と出力されれば管理者権限が付与。0なら付与されていない。 まずはここで1になることを確認すること。 もし0のまま進めると、最初のログイン画面でログインボタンが押せないので注意! import ctypes print(ctypes.windll.shell32.IsUserAnAdmin()) 実行結果 1 3-2.最初のログイン画面における注意点 アイコンクリックし、アプリにログインする画面ですが、ユーザネームとPASSを保存しておかないと、本解説のような動作が出来ないので注意! ※いちいち打つこともできるが、面倒なので・・ また、ここで触れておくが以下プログラムのマウスクリック箇所は例えばこのログインの場合、実際にログインボタンの個所へマウスを持って行って、print(pyautogui.position())とやれば調べられるのでご自身の環境で調べたうえで適宜置き換えてみてほしい 3-3.プログラム全体 何やってるか?はコメントに書いてあるので参照ください。 また、次の「4.起動中のイメージ」でログイン後の部分をGIF漫画にしてるのでイメージはそっちでつかんでください。 注意点 ・以下はPC環境や配置場所によって変わるので、数字等はあてになりません。あくまで操作の流れだけを参考にしてください ・また、カレンダーで2000年2月を選んでますが、マザーズは「2003年からのデータしかない」ので適当にやっているだけです ・HyperSBIで取得できるのは20年前までです。よって、日経225等の場合は2021/6/13なら2001/6/13以前は取得できません。 import pyautogui import sys import time import datetime def mouce_move(x, y): """ マルチモニタの場合の処置関数。人によっては不要 マウスの現在値がアイコンと別のモニタにある場合はなぜか一発で動いてくれないので 2回moveToで動かしている ※細かい理屈はまだわかってない """ # 現在のマウスカーソル位置がメイン画面の範囲に収まっているか否かを条件分岐 if pyautogui.position()[0] < 0 or pyautogui.size()[0] < pyautogui.position()[0]: #アイコンショートカットまで2回に分けて移動 pyautogui.moveTo(x,y) pyautogui.moveTo(x,y) else: #アイコンと同じ画面にカーソルがいる場合 pyautogui.moveTo(x,y) #各命令のインターバル設定(おまじない程度に入れている) pyautogui.PAUSE=1 #ユーザーアカウント制御を無くしたSBIアイコンへマウス移動+クリック x1,y1 = 49,134 mouce_move(x1, y1) #最初だけ先程の関数でまずはマウス移動を行う pyautogui.doubleClick(x1, y1) #ダブルクリックで起動 #ログイン画面表示までのWait time.sleep(1) #ログインボタンを押す x2,y2 = 1062,552 pyautogui.click(x2, y2, clicks=1, button='left') #Main画面出るまでのWait time.sleep(5) #main画面からチャートボタンを押す x3,y3 = 1595,80 pyautogui.click(x3, y3, clicks=1, button='left') #個別チャート画面表示までwait time.sleep(1) #ラジオボタンを指標へ切り替える x4,y4 = 493,121 pyautogui.click(x4, y4, clicks=1, button='left') """ ★以下は本記事の「4.起動中のイメージ」を見るとどういう動きなのかわかります """ #プルダウンからマザーズを選択してエンター x5,y5 = 507,179 pyautogui.click(x5, y5, clicks=1, button='left') pyautogui.press("down", presses=11) #マザーズは11個下を押すと出てくる pyautogui.press("enter") #時系列ボタンを押す x6,y6 = 841,127 pyautogui.click(x6, y6, clicks=1, button='left') """取得開始日を2020年の2月1日に設定""" #カレンダーの開始箇所をクリック x7,y7 = 704,299 pyautogui.click(x7, y7, clicks=1, button='left') #年を2000年に設定 ※3回クリック x8,y8 = 725,332 pyautogui.click(x8, y8, clicks=3, button='left') #2000年の2月に設定 ※3回クリック x9,y9 = 703,355 pyautogui.click(x9, y9, clicks=3, button='left') #2月1日に設定 x10,y10 = 703,362 pyautogui.click(x10, y10, clicks=1, button='left') #検索をクリック x11,y11 = 1016,301 pyautogui.click(x11, y11, clicks=1, button='left') #検索結果が出るまでwait time.sleep(3) #csv出力をクリック x12,y12 = 1090,305 pyautogui.click(x12, y12, clicks=1, button='left') """名前を付けて保存ダイアログ操作""" #ダイアログ表示までwait time.sleep(1) #ファイル名のcsvより後ろにクリックカーソルを移動 x13, y13 = 1026,725 pyautogui.click(x13, y13, clicks=1, button='left') #名前を付けて保存画面でキーボードの左ボタンを4回押す pyautogui.press("left", presses=4) #そこにマザーズと本日の日付をキー入力させる pyautogui.typewrite('_mothers_'+str(datetime.date.today())) #ダイアログ内の保存ボタンを押す x14, y14 = 1210,720 pyautogui.click(x14, y14, clicks=1, button='left') #エクスポートが完了しましたダイアログを消す x15, y15 = 1041,596 pyautogui.click(x15, y15, clicks=1, button='left') """ソフトを終了させる""" #右上のバツボタンを押す x16, y16 = 1912,3 pyautogui.click(x16, y16, clicks=1, button='left') #ダイアログ表示までwait time.sleep(1) #終了しますか? → はい x17, y17 = 899,578 pyautogui.click(x17, y17, clicks=1, button='left') これが終わると、デスクトップにcsvが置かれている。 中身を見てみると、以下のように本日の最新までのデータが取得されていることがわかる。 4.起動中のイメージ 個人情報等あるので最初の個所はカット。フォルダ名は別にどうでもよかったが、GIF作成の勉強兼ねて一応一部モザイク処理してます。 以下クリックすると拡大して見れるので、必要であればそれで見てください 5.さいごに 世間でもRPA等が流行っていたので、個人でしかもpythonでできるPyAutoGUIを使用してHyperSBIの操作をやってみました。これで定常作業はかなり楽に処理できるのではないでしょうか? 株価ですら、スクレイピングでなくてもこれで収集もできますし、応用範囲は無限だと思ってます。 バシバシ自動化していきましょう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoアプリ開発【友達リスト】〜その⑦〜

はじめに 今回はDjangoを使って友達リスト的なアプリを作成しようと思います。 プロジェクト名はTomodachi ログインしているユーザーのみ一覧表示画面に行けるようにする(エラー解決) 'AnonymousUser' object is not iterable このエラーはどうやらAnonymousUser(未ログインユーザー)によるエラーでした。 未ログインユーザーはカテゴリを持っていないよという感じですね。 前回のエラーの原因がわかりました。 一旦休憩して間を置くとわかることもあるもんですね。 それ以前にエラー文をしっかり理解できていればよかった。 # ブランチ作成 $ git branch feature-loginuser-access $ git checkout feature-loginuser-access friendslist/views.py # ログインユーザーしか処理できないようにする from django.contrib.auth.decorators import login_required @login_required def index(request): @login_required def create(request): @login_required def friend(request, pk): ログイン・新規登録以外のその他のmethodにも追加する friendslist/views.py # 新規登録後そのままログインできるようにする(そもそも前回これができていなかった) from django.contrib.auth import login def signup(request): context = {} if request.method == 'POST': form = UserCreationForm(request.POST) if form.is_valid(): user = form.save(commit=False) # user.is_active = False user.save() login(request, user)  ←☆追加する messages.success(request, '登録完了!!!') return redirect('/')  ←(/login/)から(/)に変更する return render(request, 'friendslist/auth.html', context) さいごに 今回は新規登録時のログインエラーを実装しました。 ログインユーザーのみがアプリを使用できる 新規登録後にログインできるようにする 次回は DBの設定とデプロイをします!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nginx + uWSGI + Flask をAmazon linux2上で動かすまでを丁寧に説明する

1.目的 タイトルの通りの構成でwebアプリを作成する機会があったため、備忘録として保存する。 作成するために当たったソースが、初心者向けではなかったので、細かく実行方法を記載する。 2.前提 Amazon EC2でインスタンスを作成済みである。 teraterm等のssh接続環境がある。 EC2インスタンスのセキュリティグループでSSH接続を許可している。 シェルの操作に慣れてない人 全面的にこちらを参考にさせていただいております。https://qiita.com/meriam_dev/items/2fe225cd46bbae3a0dc2 3.実装方法 3.1準備 EC2インスタンスを作成する。ここは割愛。 インスタンスにSSH接続する。ここも割愛。 接続した直後、ディレクトリが/home/ec2-user/になっていることを確認。 必要なソフトウェア一式をインストール(下記参照) /home/ec2-user/ # timezoneの設定 sudo timedatectl set-timezone Asia/Tokyo #amazon-linux-extrasのインストール sudo amazon-linux-extras install epel #各種ツールのインストール sudo yum groupinstall "Development Tools" #python3関連のインストール sudo yum install python3 sudo yum install python3-devel #flask,uwsgiのインストール sudo pip3 install flask sudo pip3 install uwsgi #yum update sudo yum update #このディレクトリにapp,logsディレクトリを作成(後で使う) mkdir app logs ※途中出てくる[y/N]はyでenter 参考:https://qiita.com/meriam_dev/items/2fe225cd46bbae3a0dc2#%E6%BA%96%E5%82%99 nginxのインストール /home/ec2-user sudo amazon-linux-extras install nginx1 ※途中出てくる[y/N]はyでenter 参考:https://qiita.com/tamorieeeen/items/07743216a3662cfca890#2-nginx%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B nginxを試しに起動 /home/ec2-user sudo systemctl start nginx sudo systemctl status nginx ※statusがactive(running)なら成功 3.2 nginxの設定を変更する uwsgiとのつなぎこみを行うために、ポート80番でアクセスした場合にuwsgiにアクセスするために下記コマンドを指定した場所に追記します。 テキストエディタで該当ファイルを開く /home/ec2-user sudo vi /etc/nginx/nginx.conf 該当箇所にコードを追記 /etc/nginx/nginx.conf #上記省略 server { listen 80; listen [::]:80; server_name _; root /usr/share/nginx/html; #ここから location = / { include uwsgi_params; uwsgi_pass unix:///run/uwsgi.sock; } #ここまでを追記する # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; #下記省略 ※viは、挿入モードにするために、開いたらiキーを一回たたいてください。 ※閉じるときは、Escでインサートモードを終了してから、:wqを入力してエンター 3.3flaskアプリの準備 最初に仮アプリとして軽微なものを用意して、3.1で準備したappフォルダの中に入れておきましょう。 /home/ec2-user/app/app.py from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == "__main__": app.run() 3.4uWSGIとflaskのつなぎこみ app.pyと同じところにapp.iniを作成してつなぎこみを行う。 app.iniをviで開く /home/ec2-user/ sudo vi /home/ec2-user/app/app.ini インサートモードで下記を入力 /home/ec2-user/app/app.ini [uwsgi] #appを入れているフォルダ chdir = /home/ec2-user/app #flaskアプリのファイル名(今回はapp.pyなのでapp) module = app #app = Flask(__name__)ならばapp callable = app master = true processes = 1 socket = /run/uwsgi.sock chmod-socket = 666 vacuum = true die-on-term = true #準備で作成したlogsディレクトリ(作ってないとエラーになるよ) logto = /home/ec2-user/logs/uwsgi.log 3.5uwsgiのサービス化 systemctlで起動できるように下記ファイルを作成する。 viでファイル作成 /home/ec2-user/ sudo vi /etc/systemd/system/uwsgi.service 上記ファイルに下記入力 /etc/systemd/system/uwsgi.service [Unit] Description = uWSGI After = syslog.target [Service] #/usr/local/bin/uwsgiは、uwsgiがインストールされている場所。インストールの順番によって変わっていることがあるので注意。which uwsgiで確認。 #/home/ec2-user/app/app.iniは先ほど作成したiniファイルの格納場所。違うなら修正。 ExecStart = /usr/local/bin/uwsgi --ini /home/ec2-user/app/app.ini Restart=always KillSignal=SIGQUIT Type=notify StandardError=syslog NotifyAccess=all [Install] WantedBy=multi-user.target 3.6動かす! 下記コマンド実行 /home/ec2-user/ sudo systemctl enable uwsgi sudo systemctl start uwsgi sudo systemctl restart nginx パブリックIP確認してアクセス。hello worldしてれば成功! 4.最後に 今回アプリの保存場所を/home/ec2-user/配下にしていたが、/var/www/配下に入れたほうが安全らしい(ec2-user配下はファイルが何かと入りやすく混乱または干渉してしまう可能性があるらしい。) /var/www/配下でappを保存する場合は、権限をいじる必要がありそう。。。 ご質問・ご指摘はコメントまで! 5.参考 https://qiita.com/meriam_dev/items/2fe225cd46bbae3a0dc2 https://qiita.com/tamorieeeen/items/07743216a3662cfca890 https://qiita.com/digitalhimiko/items/a19596fdff7a4cc8764e https://zenn.dev/shota_imazeki/books/7a0f8e2f4cccd846fb16/viewer/717e178159909f8c12e5
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonエンジニア認定試験受験記パート3

本記事について 以下のオデッセイ開催のpython基礎認定試験を今度受験するにあたり 勉強して上で学んだことを記事でアウトプットしていく。 前回記載の記事が長くなっていたのでパート3で分けていく 自作モジュールのインポートについて まず以下のおなじみフィボナッチ級数を書き出すプログラムを書く fibo.py def fib(n): a, b = 0, 1 while b < n: print(b, end=' ') a, b = b, a+b print() def fib_l(n): result = [] a, b = 0, 1 while b < n: result.append(b) a, b = b, a+b return result この自作のモジュールを読み込むにはインタープリター上で次のようにする from fibo import fib, fib_l fib(1000) fib_l(1000) 実行結果 >>> fib(1000) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 >>> fib_l(1000) [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987] ポイントは次の部分 from fibo import fib, fib_l fiboというモジュールからfibとfib_lという関数をインポートしますという指定である 他にも from fibo import * という指定で全て読み込めるが余計なものまで読み込んでしまうため非推奨である from <モジュール> import <関数名> と覚えておくと良い さらにディレクトリにこの自作のプログラムを入れることでパッケージとして利用できる ※別途__init__.pyというからファイルを作成することでパッケージとして認識される ディレクトリ構成 $ ls -l fib_package -rw-r--r-- 1 xxx-pc staff 234 6 13 19:36 fib_package/fibo.py -rw-r--r-- 1 xxx-pc staff 0 6 13 20:54 fib_package/__init__.py from fib_package.fibo import fib, fib_l fib(1000) fib_l(1000) 結果は同じなので割愛する 因みに __init__.py に以下のように記載することでimport *と実行した時に読み込まれるモジュールを指定できる ※今回はfibo.pyしかないので次の通り __all__ = ["fibo"]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[随時更新]コーディング対策~こんな時は、こうする~

この記事は 筆者がコーディング対策で学習する際の備忘録&これから、もしくは今絶賛コーディング対策中の方に向けてお助けとなる記事にしたいと思います。 メガベンチャーをはじめとするエンジニア募集企業では新卒の場合、書類選考とともにコーディング試験があります。その為、ただアプリを作ってそれを面接でアピるだけではダメで、コーディング試験もしっかりと得点しないといけないわけです。 と、筆者は最近気づき、急いで対策を始めました。 コーディング試験で用いる言語は、Pythonにしたいと思います。 理由は、文法がシンプルだから。 Androidエンジニアなので、Kotlinを使おうと思いましたが、コーディング対策をする意義は、コーディング試験を通ることが全てなので、それを達成しやすい言語を選択しました。 ケーススタディ集 二つの入力値をint型で受け取る X, Y = map(int, input().split(" ")) 数値リストで入力値を受け取る num_list = [int(i) for i in input().split()] リストを並べ替えしたい(ただのチートメソッド) hoge_list.sort() リストを分割したい hoge_list = [0,1,2] hoge_list.pop(0) print(hoge_list) # 0が出力される リスト要素の和 sum(hoge_list)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【LeetCode】就活に向けたコーディングテスト対策 #04

はじめに こんばんは. M1就活生がLeetCodeから,easy問題を中心にPythonを用いて解いていきます. ↓では,解いた問題のまとめを随時更新しています. まとめ記事 問題 今回解いたのは,難易度easyから 問題13のRoman to Integer です. 問題としては,文字列sで与えられるローマ数字を整数に直して値を出力するというもの. ローマ数字は7種類の記号で表されており,それぞれ次の値となっています. Symbol Value I 1 V 5 X 10 L 50 C 100 D 500 M 1000 入力例と出力例は以下の通りです. Example 1: Input: s = "III" Output: 3 Example 2: Input: s = "IV" Output: 4 Example 3: Input: s = "IX" Output: 9 Example 4: Input: s = "LVIII" Output: 58 Explanation: L = 50, V= 5, III = 3. Example 5: Input: s = "MCMXCIV" Output: 1994 Explanation: M = 1000, CM = 900, XC = 90 and IV = 4. このとき,4を表す数字はIIIIではなくIV,9はIXとなっています. 書いたコード とりあえず,思いついたままに書いてみました. class Solution: def romanToInt(self, s: str) -> int: dic = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000} reverse_s = s[::-1] total, prev = 0, 0 for i in reverse_s: if dic[i] >= prev: total += dic[i] else: total -= dic[i] prev = dic[i] return total まず,それぞれローマ数字とその値に対応する辞書を用意します.ローマ数字は通常,左から順に大きい数字となるように書くルールがあるそうです(知らなかった).そのためまずは,ローマ数字を反転させて,末尾から順に加算していきます.このとき,4はIV,9はIXなどのルールがあるため,前の値と比較してそれ以上であれば現在の値を加算,そうでなければ減算することで引き算のルールに適用させました. 議論ページを見てみると,なるほどなと思うコードがありました. class Solution: def romanToInt(self, s: str) -> int: translations = { "I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000 } number = 0 s = s.replace("IV", "IIII").replace("IX", "VIIII") s = s.replace("XL", "XXXX").replace("XC", "LXXXX") s = s.replace("CD", "CCCC").replace("CM", "DCCCC") for char in s: number += translations[char] return number ローマ数字の引き算を行うルールは全部で6通りらしいです.そのため,このコードのようにそれぞれのローマ数字を減算を使わない形に置換することで簡単に計算ができるみたいです(これはいい...). おわりに Discussページでは,様々な言語での解き方を参照できるのでとてもおもしろいです.多角的な思考で問題を解けるようにこれからも頑張っていきたいですね. 今回書いたコードはGitHubにもあげておきます. 前回 次回
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonを使ってみた

初めに 業務でPythonを使った事がないが、人気がある言語として、よく話を聞き、興味を持ったので調査及びコーディングをして感じた事をまとめました。 これからPythonについて知りたいと思う方に向けた記事となります。 また既にPythonのコーディング経験のある方も、コメント頂ければ幸いです。 Pythonとは? 世界で人気あるプログラミング言語です。 1991年にオランダ人に開発された言語で、他の言語と比べて、少ないコードでプログラムを構成出来る特徴があります。 Pythonで出来る事 Pythonは主にWebアプリケーションの開発に活用されています。Instagram、YouTube、DropBox等がこの言語を使っています。 また機会学習やゲームアプリケーションの開発に使われています。 これだけ多様性が高いのは、C言語等他の言語との連携がしやすいのと、統計処理や数値処理を得意としている為です。 開発する上で必要な事 Pythonで開発をするに当たり必要なものは、PCにPythonをインストールする必要があります。 Pythonインストールページ PythonはWindows、iOS等のOSに問わずインストールする事が可能です。 コーディングしたプログラムを実行するには下記を使います。 ✓「windows」の場合:コマンドプロンプト ✓「iOS」の場合:ターミナル またmicrosoft社の「Visual Studio」を経由してPythonのインストール及び開発環境の構築が出来ます。 Visual StudioでPython開発環境を構築 Visual StudioでPythonの開発環境を構築してみたので、これをまとめます。 使用したVisual Studioは以下になります。 ・Micorsoft Visual Studio Comunnity 2019  -VerSion 16.9.3 開発環境構築手順 1.Visual Studio インストーラー を起動 「Python 開発」を選択し「ダウンロードしながらインストールする」を押下 開発手順 1.Visual Studioを起動し、「新しいプロジェクトを追加」ページを開く 2.テンプレートで「Pythonアプリケーション」を選択 コーディングして 業務でC#、JavaScriptを使用しているのですが、実際に独学で使用した印象としては、やはりコーディングする行が少なく、扱いやすい印象でした。 他の言語ではimport等の追加設定が必要が、どうしても行が多くなるのですがPythonでは少ないコーディングで色々出来る様です。 まだ勉強中で勉強した事、感じた事を今後もまとめていこうと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

kivyMDチュートリアル其の弍什壱 Components - List篇

みなさん、おはこんばんは。 いかが、お過ごしでしょうか。 梅雨もいつの間にか開けて(開けてないというかきていない)、 夏らしい季節になってきましたね。 最近、結構大規模なネットワーク障害がありましたね。Fastlyという会社は世に大きく 知られたのではないでしょうか(自分も全然知らなかった)。 でも、安心してください。ネットがなくならない(もっというとQiita自体)以上はこの チュートリアルはなくなることはありません。なくなればどこかにお引っ越ししないと ですね。#あ、まずい、バックアップとらないと まぁ、そんななかなか起こり得ないこと考えてもしょうがないので、チュートリアルを 充実させることを優先します。ということで、今日も元気にやっていきましょう。今日は List篇となります。 List 恒例行事で端折ることはさながら、今日はいつもと事情が異なっていますね。 なんだか説明文がとても長い様子です。 The class MDList in combination with a BaseListItem like OneLineListItem will create a list that expands as items are added to it, working nicely with Kivy’s ScrollView. Due to the variety in sizes and controls in the Material Design spec, this module suffers from a certain level of complexity to keep the widgets compliant, flexible and performant. For this KivyMD provides list items that try to cover the most common usecases, when those are insufficient, there’s a base class called BaseListItem which you can use to create your own list items. This documentation will only cover the provided ones, for custom implementations please refer to this module’s source code. 長いですね・・・ 最初の段落は、MDListとOneLineListItemを組み合わせることによってListを構成して いるようです。んで、それらはScrollViewとうまく連携されると。 次の段落は、MaterialDesignと適合するためにKivyMD側では煩わしさを感じながらも複雑性が 必要となるということですかね。理由としてはモジュールの準拠や柔軟性などを維持するためだとか。 最後の段落は、これから出てくる最もよく使われそうな使用例を用意しているが、カスタムリストを 作りたいというわがままな方にBaseListItemというカスタム用の基底クラスを持っているぜという ことですね。で、使用例はサンプルコードを見てちょということが言われています。 まぁ、分かった方はいるかもですが、これも恒例行事で依頼を出しています。 ちょっとそっちも見たいのだがという方に依頼内容を以下にまとめておきます。 # どっちかでいいんじゃねというロジハラはなしの方向で MDListクラスとOneLineListItemのようなBaseListItemを組み合わせることで、 アイテムが追加されると拡大するリストが作成され、KivyのScrollViewとうまく連携します。 Material Design仕様ではサイズやコントロールが多様であるため、このモジュールでは、 ウィジェットの準拠性、柔軟性、パフォーマンスを維持するために、ある程度の複雑さが必要となります。 このため、KivyMDは最も一般的な使用例をカバーしようとするリストアイテムを提供していますが、 それらが不十分な場合は、独自のリストアイテムを作成するために使用できるBaseListItemと 呼ばれる基本クラスがあります。このドキュメントでは、提供されているもののみをカバーしており、 カスタム実装については、このモジュールのソースコードを参照してください。 ※ 依頼内容も内容で正確な期待結果とはならないのであくまで参考程度にしておいてもらえれば ちょっと、まだ長いですが説明は続きます。 KivyMD provides the following list items classes for use: KivyMD側が用意しているのは以下だぜという紹介があります。 Text only ListItems OneLineListItem TwoLineListItem ThreeLineListItem ListItems with widget containers なにやら、まだ喋らせろということを言われていますね。これでは、まるで話が長い上司 ではないですか。おっと、失礼。今のことはなかったことに・・・ These widgets will take other widgets that inherit from ILeftBody, ILeftBodyTouch, IRightBody or IRightBodyTouch and put them in their corresponding container. As the name implies, ILeftBody and IRightBody will signal that the widget goes into the left or right container, respectively. ILeftBodyTouch and IRightBodyTouch do the same thing, except these widgets will also receive touch events that occur within their surfaces. KivyMD provides base classes such as ImageLeftWidget, ImageRightWidget, IconRightWidget, IconLeftWidget, based on the above classes. なにやら、また基底クラスがあるようですね。ILeftBody、ILeftBodyTouch、IRightBody、 IRightBodyTouchやImageLeftWidget、ImageRightWidget、IconRightWidget、Icon- LeftWidgetがあるようです。詳しい内容は依頼結果を以下に載せておきますので適宜参照して もらえれば。 これらのウィジェットは、ILeftBody、ILeftBodyTouch、IRightBody、IRightBodyTouchを 継承した他のウィジェットを、それぞれ対応するコンテナに入れます。 その名の通り、ILeftBodyとIRightBodyはそれぞれ、ウィジェットが左または右のコンテナに入ることを 知らせます。 ILeftBodyTouchとIRightBodyTouchは同じことをしますが、これらのウィジェットは表面内で発生する タッチイベントも受け取ります。 KivyMDでは、これらのクラスをベースにImageLeftWidget、ImageRightWidget、IconRightWidget、 IconLeftWidgetなどのベースクラスを用意しています。 次は、具体的なリストの種類で左にコンテンツを仕込むのは これらだよーということを言われています。 Allows the use of items with custom widgets on the left. OneLineAvatarListItem TwoLineAvatarListItem ThreeLineAvatarListItem OneLineIconListItem TwoLineIconListItem ThreeLineIconListItem 次も同様ですね。左右どちらでもコンテンツを仕込めるようです。 It allows the use of elements with custom widgets on the left and the right. OneLineAvatarIconListItem TwoLineAvatarIconListItem ThreeLineAvatarIconListItem ではこれから実際に、これらも含めサンプルコードで確認してみましょう。 Usage まずは、簡単なリストの使い方ですね。テキストのみのリストアイテムが載っけられています。 xxi/onelinelistitem.py from kivy.lang import Builder from kivymd.app import MDApp from kivymd.uix.list import OneLineListItem KV = ''' ScrollView: MDList: id: container ''' class Test(MDApp): def build(self): return Builder.load_string(KV) def on_start(self): for i in range(20): self.root.ids.container.add_widget( OneLineListItem(text=f"Single-line item {i}") ) Test().run() このあと実際に実行してみる結果と同じコード、つまりマニュアルと同等です。 まぁ、いたってシンプルですね。ピックアップすると以下のようになるでしょうか。 (略) from kivymd.uix.list import OneLineListItem KV = ''' ScrollView: MDList: id: container ''' (略) for i in range(20): self.root.ids.container.add_widget( OneLineListItem(text=f"Single-line item {i}") ) Testクラスのon_startメソッドで必要になるので、インポート文は必要ということは 当然で、まず注目ポイントはkv側ですね。冒頭の説明文通り、ScrollView・MDListが 持ち合わさっています。これはもうListパターンと名付けてもよいくらいのものですね。 あとは、idを定義して(ここではcontainer)クラス側でOneLineListItemオブジェクトを 生成してcontainerに流し込んでもよし、MDList配下に直置きしてもよしという感じでしょうか。 まぁこれは設計次第ですが、個人的には設定内容とかで決め打ちしているのであれば直置き、そう ではなく何十・何百というコンテンツを仕込むのであればクラス側で生成する方法をという感じで しょうか。 まぁ、なんにせよよく分からんという方は上記パターン化して覚えておきましょう。 実行結果 本日もコード例が多いので、結果をコードごとに貼り付けておきます。 確認ヨシ!という感じでしょうか。すみません、面白みがほぼ0に近いもので・・・ Events of List こちら側には、先程言っていた直置きするサンプルコードが載せられていますね。 サンプルをそのままずっと載せるのは面白みが0なので、少し改変をします。 先に少し説明をしておきますと、ListItemの種類を全て載っけたコードにしてみました。 OneLineListItemはさっき出てきてたんですけどね・・・まぁ、でも直置きパターンは 初登場ということで再度載っけておきます。 xxi/listitem_iroiro.py from kivy.lang import Builder from kivymd.app import MDApp KV = ''' ScrollView: MDList: OneLineListItem: text: "Single-line item" TwoLineListItem: text: "Two-line item" secondary_text: "Secondary text here" ThreeLineListItem: text: "Three-line item" secondary_text: "This is a multi-line label where you can" tertiary_text: "fit more text than usual" (略) OneLineAvatarIconListItem: on_release: print("Click 2!") IconLeftWidget: icon: "gitlab" ''' class MainApp(MDApp): def build(self): return Builder.load_string(KV) MainApp().run() こちらは、一部コードを省略しています。安心してください、マニュアルで記載のパーツは 全て揃ってますよ(いらない報告)。詳しい使い方は公式マニュアル、もしくはGitHubでの コードを参照ください。 こちらもListパターンの通りで特にとりとめもありませんが、最後のOneLineAvatarIcon- ListItemでコールバックを用意しているくらいですかね。サンプルでは全てのアイテムに コールバックを定義されていましたが、ここでは動作確認のために最後だけ定義してみました。 当然っちゃ当然ですが、直置きの場合はimport文が必要ありません。 実行結果 ではさっそく、結果をみる方が何百倍も早いので見ることにしましょう。 問題ありませんね。 随分とアプリっぽくなってきました。 なお、ThreeLineAvatarIconListItemについては両側のアイコンがオプション扱い なのかどうか見るために試しに右側アイコンを除いてみました。問題なく取り除かれて いますね。 Custom list item こちらは冒頭の案内であったカスタムリストの作成方法になります。こちらもコードを見た 方が早いと思うので、さっそくコードに入っていきます。コードはマニュアルのままで、要所 要所で区切っていきたいと思います。 import文 xxi/custom_list_item.py from kivy.lang import Builder from kivy.properties import StringProperty from kivymd.app import MDApp from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem from kivymd.uix.selectioncontrol import MDCheckbox from kivymd.icon_definitions import md_icons まぁ、これはわざわざ取り上げるものでもないですが、しつこいくらい触っていきます。 はい、予想されたとは思いますが、これはkv以外で定義や宣言をしたときに必要になる ものですね。StringPropertyやIRightBodyTouchとOneLineAvatarIconListItem、 MDCheckboxやmd_iconsを使用するために列記しておきます。 kv側 KV = ''' <ListItemWithCheckbox>: IconLeftWidget: icon: root.icon RightCheckbox: BoxLayout: ScrollView: MDList: id: scroll ''' まずはカスタムレイアウト(ListItemWithCheckbox)とルートウィジェット(Box- Layout以下)に分かれていますね。カスタムレイアウトでは、IconLeftWidgetと RightCheckboxに分かれているようです。これらは何をもとに構成されているので しょうかね。実はクラス側にヒントが隠されています。 ルートウィジェットに関しては、触れるまでもなさそうですかね。新しくBoxLayoutが 加わったことと、その配下にListパターンが追加されているだけです。 ListItemWithCheckbox側 class ListItemWithCheckbox(OneLineAvatarIconListItem): '''Custom list item.''' icon = StringProperty("android") なんとListItemWithCheckboxではOneLineAvatarIconListItemを継承している のでしたね。それってなんだっけという方は今すぐマニュアルを振り返ってください。 まぁそれも面倒だという方は、左右にアイコンが出ているものだということを目を凝ら しておいてください。で、あとはiconプロパティを生成させてデフォルトをandroid アイコンにしています。 RightCheckbox class RightCheckbox(IRightBodyTouch, MDCheckbox): '''Custom right container.''' こちらは単なるクラス定義だけですが、IRightBodyTouchとMDCheckboxをMixin させているところがポイントですね。冒頭にあったIRightBodyTouchはこうやって 継承させてから、使用することができるようになります。 MainApp側 class MainApp(MDApp): def build(self): return Builder.load_string(KV) def on_start(self): icons = list(md_icons.keys()) for i in range(30): self.root.ids.scroll.add_widget( ListItemWithCheckbox(text=f"Item {i}", icon=icons[i]) ) ここも、取り留めもありません。ただし、アイコンの使い方で、ここからここを使いたいの だけどなぁということを思ってたらこういう使い方をした方がいいかもしれません。これから 私のほうでも重宝するかも。 icons = list(md_icons.keys()) あとはMDListに流し込むのは同じことをしているだけですね。 実行結果 はい、同様に結果を見てみましょう。 こちらも問題ないようすですね。ちゃんとチェックボックスも見えています。 もしもIconRightWidgetを何かのLayoutにしたいとなったら すみません、いいキャプションがなかったので適当に自分で作ってみました。 はい、タイトルの通りですが、1つだけ左右どちらかにアイコンを載せるだけでは何か 足りない!という超わがままな方向けのやり方ということでしょうか。まぁ、こういう 言い方だと語弊がありますが、色々アイコンとかで複数配置されているのをよく見かけ ますよね。いいねボタン、よくないねボタンを並べて配置したとか。 ということで、挙動を見てみましょうか。まずはコードから。 xxi/ from kivy.lang import Builder from kivymd.app import MDApp from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.list import IRightBodyTouch KV = ''' OneLineAvatarIconListItem: text: "cup cup cup!" on_size: self.ids._right_container.width = container.width self.ids._right_container.x = container.width IconLeftWidget: icon: "cup" Container: id: container MDIconButton: icon: "thumb-up" MDIconButton: icon: "thumb-down" ''' class Container(IRightBodyTouch, MDBoxLayout): adaptive_width = True class MainApp(MDApp): def build(self): return Builder.load_string(KV) MainApp().run() 変わったところでいうと、まずは順番前後しますがContainerウィジェットになりますかね。 以前では、IconRightWidgetだったのに今回はContainerウィジェットに切り替わって います。Containerクラスである通り、MDBoxLayoutを継承しているので、MDIconButt- onを2つ並べて配置出来ているわけになります。 on_sizeプロパティに関しては、これはなんなんですかねぇ。取り除いてはダメな気がプン プンします。_right_container.widthと.xは何が違うんだろう・・・とりあえずここは このままにしておきます。 実行結果 さてどうなっているか確認しましょう。 cogアイコンが見えなかったから、こちらもカスタムしてみたということがあるのですが 挙動的には問題なさそうですね。コードの方ではとくに触れてませんでした、すみません。 私の環境だけであればいいのですが、cogアイコンはなぜか表示されませんでした。 んで、テキストとContainerウィジェットの中のアイコンも変更して見ました。さっき いいねボタンとか言ってたしで。どうでもいいですが、テキスト文字列と言い、いいね ボタンと言いちょっとおバカなリストアイテムですね。IQがちょっとよろしくなさそう。 で、試しにon_sizeプロパティを取り除くと、こうなりました。さらにおバカになり ましたね。あとは、アイコンを3つ以上に増やすといったこともしましたが、2つだけ しか正確に表示されませんでしたね。3つ目から途中で見切れました。 うーん、これはon_sizeプロパティでは中央にあるtextプロパティの領域を調整したい くらいですがねぇ・・・このままだとアイコンは2つだけしか表示されない。 Behavior すみません、ここは力尽きました。マニュアル通りで見たい方はご自分で試されてくだ さい。ただただ、ここはアイコンがタッチされてもタッチされた振る舞いがなくなるよ ということだけしか言ってないと思うので。 API - kivymd.uix.list 最後に使用したAPIについて触れておきます。ほとんど、引用するのみになりそうですが。 class kivymd.uix.list.MDList(**kwargs) ListItem container. Best used in conjunction with a kivy.uix.ScrollView. When adding (or removing) a widget, it will resize itself to fit its children, plus top and bottom paddings as described by the MD spec. class kivymd.uix.list.ILeftBodyTouch Same as ILeftBody, but allows the widget to receive touch events instead of triggering the ListItem’s ripple effect. コードとかでは出てきませんでしたが、念のため。 class kivymd.uix.list.IRightBodyTouch Same as IRightBody, but allows the widget to receive touch events instead of triggering the ListItem’s ripple effect class kivymd.uix.list.OneLineListItem(**kwargs) A one line list item. すみません、他のリストアイテムについては種類が多すぎるので省略します。 class kivymd.uix.list.OneLineRightIconListItem(**kwargs) Overrides add_widget in a ListItem to include support for I*Body widgets when the appropiate containers are present. こういうのもあるのですね。取り留めておきます。 まとめ いやー、今日も長かった! という所感はどうでも良い話ですが、いかがだったでしょうか。 マニュアルの冒頭では、熱いお気持ち表明はされていましたが強い意気込みだとか思い というのはひしひしと伝わってきましたね。結構苦労してるんだぜ...というぼやきみた いなものも伝わってきたりして・・・ まぁ、なんにせよコントリビューターのおかげでこれらの機能が使えることは事実になり ます。ありがたく使わさせてもらいましょう。コンテンツを充実させるためには不可欠の 素材となります。 ということで今週はここまでということで!来週はMenu篇となります。 あれっ、MDSwiperが先なのでは?と思われた方はその通りなのですが、色々事情が あってですね・・・まぁでもそれは来週話すとします。 では一旦ここにて、さようなら〜。 それでは、ごきげんよう。 参照 Components » List https://kivymd.readthedocs.io/en/latest/components/list/ DeepL 翻訳ツール https://www.deepl.com/ja/translator
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Get last monday in python3

source from datetime import datetime as ddatetime, date as ddate, timedelta as dtimedelta from argparse import ArgumentParser usage = 'Usage: python {} [--ndt <ndt>] [--help]'\ .format(__file__) argparser = ArgumentParser(usage=usage) argparser.add_argument('-n', '--ndt', type=str, dest='ndt', help='now datetime') args = argparser.parse_args() if args.ndt: print("ndt set:" + args.ndt) ndt = args.ndt nd = ddatetime.strptime(ndt, '%Y-%m-%d %H:%M:%S') else: nd = ddatetime.now() cd = nd - dtimedelta(days=((nd.weekday()))) print(cd.date()) execution # python3 test.py 2021-06-07 # python3 test.py --ndt "2021-06-09 00:00:00" ndt set:2021-06-09 00:00:00 2021-06-07 # python3 test.py --ndt "2021-06-06 23:59:59" ndt set:2021-06-06 23:59:59 2021-05-31 # python3 test.py --ndt "2021-06-07 00:00:02" ndt set:2021-06-07 00:00:02 2021-06-07 # python3 test.py --ndt "2021-06-07 00:00:02" ndt set:2021-06-07 00:00:02 2021-06-07 # python3 test.py --ndt "2021-06-10 00:00:02" ndt set:2021-06-10 00:00:02 2021-06-07
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

エンジニアが片手間にシステムトレードできそうな対象(投資生活3年生 ①)

(はじめに)コードを書いて取引を自動化しやすい投資対象は? 最近、片手間投資作業が増えてきたのである程度は自動化できないかなと思ってます。ということで、取引を自動化して安定的に資産を増やせそうな投資対象として投資信託(投信)を選定し、半年ほどかけて人力で取引して得られた知見に基づいて、コードに落としていくことにしました。今回は何をしようとしているのかを書いて、次回以降、ちょこちょこ書いてるコードを公開していきます。目指すところは、投信への自動売買で、平均年リターン30%超え(年リターン50%~10%程度)。 使用(予定)の技術スタックは以下のような感じ: python(with pandas/seaborn etc.) web API(FRED / eMAXIS Web API /kabu.com API etc.) streamlit(可視化用のwebフレームワーク) データの永続化はどうするか未定ですが、当初はpython組込のsqliteと(pyarrowでアクセスする)parquetファイルかな、と。 (付記) 以下、投資タームが解説無しでいろいろ出てきますが、ググれば出で来るタームばかりですので、ご容赦を。自動投資を始めてしばらくたったら、投資対象と結果を自動投稿する仕組みを作りたいなと思っているので、その際には、ブログを立ち上げて投資タームなどなどの解説を書きたいな。 自動化対象として投資信託を選んだ経緯 さてさて、これまで僕がいろいろ試した投資対象と成果(当人の感想)は概ね以下の通り: FX(レバレッジあり為替トレード) : 大損 ETFオートトレード(レバレッジあり) : 勝ち 投資信託(1日1回の金融商品投資) : 勝ち CFD(レバレッジあり株式・証券等のトレード) : ちょい勝ち 個別株(ミニ株で挑戦中) : ちょい勝ち 簡単に言うと、FXでの大損(計4回で100万円以上)を株式・債券系のトレードで取り戻してちょっとだけプラスというのがこの3年弱の成果。まぁ、しょぼい成果なのだけれど、なんとか勝ちパターンは見えてきたところ。 ということで、自分なりの勝ちパターンを自動化したいと考え始めた。システムトレードをネットで調べると、FXの情報が一番多いように思えるけれど、自分はFXのセンスがないらしいので、FXのシステムトレードは手出ししないことする。 では、どれを自動化対象とするのが良さそうか?現時点で僕が考えていることは以下の通り: 値動きが激しいレバレッジをかけた取引(FX/CFD)は完全自動化は厳しそう(市場の動向が変った時に大きく損する可能性がある) 業績で価格が上下動する個別株の取引きの自動化も厳しそう(PERなどの指標は過去の業績から推測された値に過ぎない) 複数の株式等を組み合わせて組成されている投資信託は値動きが比較的緩やかで、リバランスなどを自動的に無理なく行えそう。 投信は100円から1円単位の指値で売買できるので、投資資金が少ない人間(≒自分)には、取り組みやすい。 ということで、今年の目標は、投資信託(投信)を対象に自動化を試みることとしている。 ※なお、現時点での僕の投資ポートフォリオの中で、投資信託が占める割合は多くはない。 2021年6月時点の投資ポートフォリオの概要: レバレッジETF(TQQQ/TMF/EDCなどなど) : 45% 投資信託(投信) : 25%(詳細は後述) 個別株と普通のETF : 20% (債券ETF : グロース株 : バリュー株 ≒ 1:1:2) CFD : 10%(グロース : バリュー : 商品 : 各国株式市場ETF ≒ 1:2:2:2) 今年に入ってハイテク株が下がった2月、3月にグロース系のセクター別ETF(情報通信、一般消費財など)をレバレッジかけて買いすぎてしまい、ちょっとレバレッジETFに偏ってしまっているので、レバETFを利益確定するたびに、投信等に資金を振り向けなおそうとしているところ。 投信取引をどのように自動化すると良さそうか? 試行結果 これは、いわゆる要求開発にあたるところ。ただし、昨年までは投資信託は(企業年金で毎月積み立ててるMSCIコクサイを補充する程度に)適当にやってきたので、僕にさしたる知見はない。ということで、試験取引をしてまずは仮説を立てられることを目指すことにした。 そのために、(これまで使っていたマネックス証券、GMOクリック証券等に加え)投信取引用の口座を2つ開設。選択したのは、auカブコム証券と松井証券。共に投信商品が豊富に用意されており、以下の利点もある。 auカブコム証券を選んだ理由: ・web API経由で投資信託取引を自動化できる ・pontaポイントが貯まる(お得ポイント) 松井証券を選んだ理由: ・毎日積立ができる ・信託報酬の現金還元がある(お得ポイント) ・投信の自動リバランスサービスがある(使ってないけど) 投信取引を自動化するには、今のところauカブコム証券一択なので外せない。 他方、毎日定額積立に勝てるかどうかを自動取引のベンチマークにしたいと考えたので毎日積立ができる証券会社として松井証券を選んだ。 当初の感触では、UIは松井証券の方が分かりやすい。特に3月頃の株価下落時に松井証券の毎日積立を主に使い、auカブコム証券は月に1~2回リバランスする程度で放置気味だった(レバレッジをかけた投信商品などでは値動きが読みにくいため、毎日少額を積み立てる戦略が良さそうに思えた)。 ということで、こまめに日々人力でメンテし続けた松井証券と、お得感がありそうな時にピンポイントで売買したauカブコム証券。半年間での投資成果はどうかというと、どうもauカブコム証券でのピンポイント売買が優勢である。 ラフな試算では、毎日積立+リバランスあり戦略の松井証券のリターンが年換算で23.0%ほど。ピンポイント・リバランス戦略のauカブコム証券のリターンが年換算で30.9%ほど。毎日松井証券にアクセスしいろいろと投信の目論見書を読んで、ベストな積立戦略を追求し続けた自分としてはちょっと微妙な結果だね。 さて、これらの試行から得られた知見を確認しておこう。 はじめにトライアル勝者のピンポイント・リバランス戦略@auカブコム証券。 こちらは良さげな投信を買って放置。3か月以内に10%以上の上昇をした投信は解約して利益確定し、その時々に良さげな投信に乗り換えるという戦略をとっている。リターンに大きく寄与したのが、ハイテク株にテーマ毎(ドローン、VR、ウェアラブル)に投資するeMaxis Neoシリーズの売買(↑の緑)。こうしたテーマ別の投信商品は値動きが大きい(月に数十%動くことも)ため、こまめな利益確定がリターンに寄与した。後は、底値に近いところでレバレッジ型投信(上ではNasdaqの新興ハイテク株(NXTQ)に投資する紫色の奴)もしばしの忍耐期間を経て、そこそこのリターンを上げつつある。これらを下支えしているのが債券を中心に株・REITなども併せてレバレッジをかけるレバレッジバランスファンド(↑の黄色)。一般にハイテク株と債券との値動きは結構違うので個々に利益を上げることができるハイテク株投信と債券系投信を組み合わせ、適宜リバランスすることで、より高いリターンを追求できる。 高いリターンの追求にレバレッジ投信商品を活用するアイデアは、以下の 「ROKOHOUSE式 可変レバレッジド・ポートフォリオ」を参考にしている。感謝。 http://www.rokohouse.net/archives/1921 利益計算は当初少額から始めて、買いましたことを考慮して、 (評価益5583円+売却益5156円)/現在価値104035円*12か月/平均投資期間4か月≒30.967% とした。2017年時点の投資環境で算出された↑の「ROKOHOUSE式 可変レバレッジド・ポートフォリオ」(リスク許容度・大)の年リターン26.3%と同等以上の成果を上げつつあるようで、ピンポイント・(人力)リバランス戦略は満足いく成果を上げることができた。 次いで、トライアル敗者の毎日積立&多数の商品をリバランス戦略の松井証券の方の結果。 こちらは、トータルリターンをより分かりやすく表示してくれている。 リターン計算は、 同様に (全収益17324円/現在価値225934)*12か月/平均投資期間4 *100≒23.003% と算出した。 毎日積立戦略が若干残念な結果となってしまったのは、当初の3か月くらい、レバレッジをかけずに日本株と債券を積み立てたためである。 日本株が昨年末の上昇の反動で不調であったため、積立を止めた後も評価損を抱えている。また、レバレッジをかけない債券のリターンはいずれも微妙。つまりは、毎日積立がいけないのではなく、僕が選んだ投資対象が間違っていた可能性が高いということ。 ということで、3月のNasdaq下落を受けて戦略を変更。レバレッジ投信を中心に、下落時にハイテク株投信を毎日積み立てる戦略とした(下図)。 結果、10%以下の年リターンに低迷していた3月時点から、6月に入って年20%以上の年リターンへと大きく改善することができている。 人力試行で得られた投資ルールとは? 僕が試行で得た仮説を以下に列挙しておく(仮説それぞれの関係はコードに落とすときに考察する): 値動きが激しいテーマ別ハイテク株投信(自動運転、遺伝子工学、VR などなど)の間でのリバランス(値上がりしたものを売って、値下がりしたものを買う)は、投資リターンを向上させる。 株式指数レバレッジ投信が一定以上価格が下落した際には、価格が戻るまで(例、5%値を戻す)まで毎日積立を行うことは、投資リターンを向上させる。 債券中心のレバレッジバランス投信(↑で黄色にしたもの)を毎日積立てそこそこのリターンを得つつ、値下がりしたハイテク株投信・レバレッジ投信へとリバランスすることは投資リターンを向上させる。 投信の値動き情報はAPI経由で取得できるからこれらはコードに落とせそうである。 加えて、投資家たちが長年かけて蓄積してきた経験値(以下に例示)を加えることができればなお良さそうだ。 経験的なシーズナリティ(例 株が下がりやすいのは2月と8月。あと、夏枯れ相場があるので5月に売っておくと良い。)を利益確定のルールに盛り込む。 長期金利(≒米国長期国債(10年債)の相場の逆数)の変動(一回微分?)と逆相関しがちなハイテク株のための、利益確定ルールを作る。 先進国とは別の値動きをする途上国株、シクリカルな日本株も売買対象に加え、値動きを安定化させる。 まぁ、あまり複雑なルールをコードに落とすのは割に合わない気もするから、こうした個別の考慮は、人力でメンテする形で良いかもだけれど。 今後は。 APIの仕様書を読んでセキュリティの考察をしてから、コードをそこそこちゃんと書こう。 ほんとはFXでも利益を上げたいな。 追記:現在の毎日・毎週積立設定 ぁ、そうだ。参考までに、半年間の試行錯誤の末の松井投信の積立設定を貼っておこう。現時点ではこれをちょこちょこ人力リバランスすることで年リターン30%超えできるのかな、と期待している。当然、auカブコムAPIに仕込む自動売買ではこの設定に勝ちに行くなり。 ちなみに、毎週少額積立ているのは、下落時に買い増すためのブックマーク用途。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

QGISプラグインで気象庁降水ナウキャストをアニメ表示したりする

降水ナウキャストについて 気象庁が公開する降水ナウキャストは、過去の降水量および1時間先までの予報です。データ形式はXYZタイルですが、時点ごとに別のURLが振られるため時系列表示が容易ではありません。そんなQGISプラグインがあったら便利だなと思い思い立って作りました(トータル10時間くらい)。 NowcastTool ※さっき公式リポジトリにアップロードしたので、承認されるまで多少時間がかかります(長くて2,3日)。 このQGISプラグインを使えば、各時点のタイルの表示やアニメーション表示を簡単にできるようになります。冒頭のGIFアニメはこのプラグインを活用して作ったアニメーションです。 注意 非公式のソフトウェアです、このプログラムに関してデータ配信元である気象庁様に問い合わせはしないでください タイル画像の配信方式などは予期せずして変更される可能性があり、その場合このプラグインは使えなくなります 使い方 プラグインをインストールすると、ブラウザにNowcastToolという雨雲アイコンの項目が追加されます。 QGIS起動時に自動で気象庁が配信しているナウキャストの時系列情報を取得し、NowcastTool以下にアイテムが追加されます。取得する時間範囲は、デフォルトで3時間前~1時間後です。過去データの取得範囲は変更可能です(後述)。 1時点のタイルをマップに表示 各時点のアイテムをダブルクリックもしくは右クリック「マップに追加」とすると、地図画面に表示されます。 アニメーション表示 NowcastTool以下に存在する全時点のタイルを、QGIS3.14以降の基本機能であるTemporalControllerでアニメーション表示が行えるよう設定したうえで、マップへ追加します。 ブラウザのNowcastToolを右クリックしてアニメーション表示をクリックして実行します。 TemporalControllerの使い方の説明は割愛します https://www.youtube.com/watch?v=-feo9urzCj4&t=119sなどを参照してください。 過去データの取得範囲設定 ブラウザのNowcastToolを右クリック、設定をクリックして設定画面を開きます。 5分刻みで1200分前~180分前まで指定することができます。上限の1200分に特に意味はないので、要望などがあれば見直します。 終わりに 思い付きのわりに結構使い道があるプログラムになった気がする QGISプラグインはこういうかゆい所に手を届かせるのが得意です、かゆい所をどうにかしたい方はご相談ください、お仕事待ってます:) 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】f文字列

Pythonで文字列の中に変数を組み込む場合、Python3.6でf文字列が導入されるまでは、次のようにformatメソッドを使うのが一般的でした。 sample1.py fruits = ['apple', 'orange', 'grape'] for i, fruit in enumerate(fruits): print('{}: {}'.format(i, fruit)) $ python sample1.py 0: apple 1: orange 2: grape ただ、formatメソッドは上記の例を見て分かるように少し冗長な書き方になります。さらに、文字列の中に組み込む変数が増えた場合、括弧と変数の対応関係が直感的に分かりづらくなる欠点があります。 そこで登場したのがf文字列です。先の例は次のように書き換えることができます。 sample2.py fruits = ['apple', 'orange', 'grape'] for i, fruit in enumerate(fruits): print(f'{i}: {fruit}') $ python sample2.py 0: apple 1: orange 2: grape 出力結果は全く同じですが、書き方がシンプルになったと思います。文字列の中に組み込む変数が増えた場合でも、どの括弧にどの変数が埋め込まれるのかというのが直感的に分かります。 f文字列に組み込む変数として配列を指定すると、次のように配列としてそのまま展開されます。 sample3.py fruits = ['apple', 'orange', 'grape'] print(f'fruits: {fruits}') $ python sample3.py fruits: ['apple', 'orange', 'grape'] f文字列の中で改行したい場合、次のように改行文字(\n)を入れるだけです。 sample4.py fruits = ['apple', 'orange', 'grape'] print(f'{fruits[0]}\n{fruits[1]}\n{fruits[2]}') $ python sample4.py apple orange grape 改行文字を単なる文字列として扱いたい場合、次のようにバックスラッシュでエスケープするか、もしくはfの前(後ろでもOK)にrを追加するとできます。後述しますが、rはr文字列(raw文字列)のrとなります。 sample5.py fruits = ['apple', 'orange', 'grape'] print(f'{fruits[0]}\\n{fruits[1]}\\n{fruits[2]}') print(rf'{fruits[0]}\n{fruits[1]}\n{fruits[2]}') $ python sample5.py apple\norange\ngrape apple\norange\ngrape 先に述べたr文字列(raw文字列)ですが、これは読んで字のごとく括弧の中の変数を展開せずにそのままの文字列として扱います。 sample6.py fruits = ['apple', 'orange', 'grape'] for i, fruit in enumerate(fruits): print(r'{i}: {fruit}') $ python sample6.py {i}: {fruit} {i}: {fruit} {i}: {fruit} f文字列をPythonのデフォルトの文字列にしようという動きもあるので、f文字列を知っておいて損はないと思います。他の言語でもf文字列と同様の機能があり使われることも多いので、個人的にはデフォルトになってもいいのではと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

基本情報技術者試験の対策を1ヶ月未満で乗り切ろうとした記録

やばい、受験日まで1ヶ月もないじゃん… もともと、後期(10月)に受験しようかなーと思っていたが、 10月までに特に受けたい資格もないし、モチベーションも上がらないしで 時間を持て余していた2021年5月23日(日)のこと。 「そういえば、受験申し込みっていつからやっているんだろう?」 ふと気になって、受験に関する記事をつまんでいくと、 こんな情報が目に飛び込んできた。 . ▼2021年5月23日に見た申し込みに関する記事一部 … え、対策期間が短い… ⇦to be continued この記事を見た後の、私の行動を1日ごとに記録していきます。 . それでは、どうぞ 5/23(日) ▼受験申し込み ↓ここにアクセスした、とりあえず受験申し込みをしました。 http://pf.prometric-jp.com/testlist/fe/index.html 午前試験と午後試験の2つに別れているのは認識していたのですが、 同日に午前と午後を受けるものかなーと思っていました。 しかし、よく調べていると別日でもOKとの情報が(ラッキー) 私のスケジュールで試験終了日ぎりぎりに予約をとるために、 午前試験は6月12日、午後試験は6月22日で予約をとりました。 ■午前試験予約 ■午後試験予約 ▼攻略方法を探す 基本情報に関する攻略サイトはたくさんありましたが、 余計な回り道はせずに対策していきたい。 ので、下記の記事を参考に取り組んでいくことにしました。 記事:【2021年】基本情報技術者試験―1週間で合格する勉強法 なかなかハードな試験対策だなと思いましたが、 1週間対策を3~4回できれば、なんとか乗り切れるんじゃないかという 単純な考えで、この方法を採用させていただきました。(途中まで) んで、 ▼1日目終了(約3時間半) 基本情報技術者試験ドットコムの問題集を 1問ずつ見ていき、問題と解説をさらりと理解できるまで読んでいきました。 . ▼問題を読んで、解説を読む。 ▼わからなかった単語はこんな感じでブックマーク!! 何回か振り返って、理解できてきたらブックマークを消していく作戦にしていきました。 勉強時間とかの記録 日付 起床時間 学習時間 就寝時間 備考 5/23 6時30分 3:45 23:00 とりあえず攻略方法を探す 5/24 5時30分 2:55 23:00 過去問やってみるが、全然わからん 5/25 5時30分 2:50 23:00 ここまで、じっくりやり込む 5/26 5時30分 4:20 23:00 効率が悪いことに気づく 5/27 6時00分 2:50 23:00 問題を多く解くように切り替え 5/28 5時30分 2:55 23:00 テクノロジ 5/29 6時30分 4:45 23:30 ↑ 5/30 7時00分 5:50 23:30 マネジメント系 5/31 6時30分 2:00 23:00 ↑ 6/1 5時30分 2:45 23:00 過去、出題傾向が高い部分を対策 6/2 5時30分 2:40 23:00 データベース・基礎理論 6/3 5時30分 2:30 23:00 ↑ 6/4 5時30分 2:30 24:00 ネットワーク・基礎理論 6/5 6時30分 4:30 23:00 ↑ 6/6 5時30分 5:30 23:00 ソフトウェア・開発技術 6/7 5時30分 2:00 23:00 セキュリティ 6/8 5時30分 2:00 23:00 ↑ 6/9 5時30分 3:20 23:00 アルゴリズム 6/10 5時30分 2:20 23:00 マネジメント・ストラテジ 6/11 5時30分 2:00 23:00 総まとめ 学習時間合計:64:15 6月12日 受験 試験時間は15分余らせて終了。 過去問の出題数が少なかったのか、 「あ、これ過去問のやつやん!」 っていうのは少なかった気がする… ・ あと、用語の意味がわからないのが多くて、勉強不足を痛感しました。 トホホ 結果 57.5点でした。 こりゃ、10月に再挑戦だな  反省 短期間で合格できた系の記事は、初学者には向いていない。 →男は黙って、こつこつやりましょう 用語の意味が理解できていないのが多かった。 →キタミ式の本を読み込んでもっと理解を深める 対策しきれてないとことがあった →うーん、どうしよう。やっぱやり込むしかないのか?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoアプリ開発【友達リスト】〜その⑥〜

はじめに 今回はDjangoを使って友達リスト的なアプリを作成しようと思います。 プロジェクト名はTomodachi ユーザー作成時にデフォルトカテゴリを作成する(エラー解決のため) # ブランチを切り替える $ git branch feature-user-add-category $ git checkout feature-user-add-category ユーザー作成時にデフォルトカテゴリを作成(エラー解決のため) friendslist/models.py # ユーザー作成時にデフォルトカテゴリを作成する @receiver(post_save, sender=User) def create_category(sender, **kwargs): if kwargs['created']: default_category = Category.objects.create(user=kwargs['instance']) default_category.name = "デフォルト" default_category.save() 友達一覧を自分が作成した友達のみにする(ユーザーと友達にもリレーションを貼る) friendslist/models.py # ユーザーと友達のリレーション user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) friendslist/views.py # 友達一覧と作成をユーザーに紐付ける def index(request): user = request.user categories = Category.objects.filter(user=user) first_category = categories.first() friends = Friend.objects.filter(user=user)  ←☆追加する context = { 'friends': friends, 'categories': categories, 'first_category': first_category, } return render(request, 'friendslist/index.html', context) def create(request): if request.method == 'POST': form = FriendForm(request.POST) if form.is_valid(): friend = form.save(commit=False) friend.user = request.user ←☆追加する friend.save() return redirect('/') user = request.user categories = Category.objects.filter(user=user) first_category = categories.first() context = { 'categories': categories, 'first_category': first_category, } return render(request, 'friendslist/create.html', context) まだ新規登録直後のエラーが解消されないので、仕様変更 friendslist/views.py # 新規登録したら直接ログインではなく、ログイン画面からログインしてもらう(応急処置) def signup(request): context = {} if request.method == 'POST': form = UserCreationForm(request.POST) if form.is_valid(): user = form.save(commit=False) # user.is_active = False user.save() messages.success(request, '登録完了!!!') return redirect('/login/')  ←☆('/')から変更 おそらく問題は、ユーザー作成直後カテゴリ作成のタイミングが遅いのかな? そこをどうするかが問題。  もう一個エラーがあった friendslist/views.py # 誕生日がなければそのままNoneにする if friend.birthday is not None: friend_birthday = "{0:%Y-%m-%d}".format(friend.birthday) else: friend_birthday = friend.birthday さいごに 今回は新規登録後にデフォルトカテゴリ作成の処理をしましたが もともとのエラーが解決できませんでした。 そのため応急処置として 新規登録後は一度ログインすることにしました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ラジオ気象通報に挑戦!

キッズの実習に天気図を描こう! 現在 NHK ラジオR2 でやっている気象通報は午後4時00分~ 午後4時20分。 聞きのがしでWebから聞けるかな? おおっと思ったがクリックしてもタイトルが出るだけなのですね。一体・・・ 仕方がないので自前で録音します。しかし毎日16時に録音は絶対に忘れるので、自動化しましょう。 環境 Ubuntu 20.04 らじる★らじる の自動録音 以下のページに収録されている、 python プログラムを使用しました。 ライセンスはGPLv3+ です。 元記事は RaspberryPi ですが、PC 上のUbuntu でも問題なく動作します。 「らじる★らじるの再生や録音を自動化::kakurasanのLinux書庫」 https://kakurasan.tk/raspberrypi/raspberrypi-automate-playing-and-recording-radiru/ ダウンロードしたプログラム play-radiru.py radiru-get-streamurl.py rec-radiru.py テストします $ ./rec-radiru.py tokyo r1 100 r1.aac としたら、100秒録音した r1.aac ができて、VLCで再生できた。 /home/nanbuwks/Downloads/radiru/rec-radiru.py tokyo r2 120 /home/nanbuwks/Downloads/radiru/気象通報date "+%Y%m%d".aac としたら、 /home/nanbuwks/Downloads/radiru/気象通報20210613.aac ができた。 crontab に登録しよう。 00 16 * * * /home/nanbuwks/Downloads/radiru/rec-radiru.py tokyo r2 1200 /home/nanbuwks/Downloads/radiru/気象通報`date "+\%Y\%m\%d"`.aac という内容の cronset ファイルを作り、 $ crontab cronset で登録した。 聞き取り 観測地点リスト 天気記号 リストや地図に記入するのは以下の日本式天気記号です。慣れないうちは印刷して手元に置いておくのがいいでしょう。 File:天気記号.png https://commons.wikimedia.org/wiki/File:%E5%A4%A9%E6%B0%97%E8%A8%98%E5%8F%B7.png Creative Commons Attribution-ShareAlike 3.0 license. 海洋観測ブイ記入表 漁業気象記入表 天気図を描く 表に記入したら天気図を作ります。PublicDomainで白地図を作ろうと思いましたが取り急ぎ以下のものを使用させていただくことにしました。 「天気図用紙をつくってみた: ふみんのつぶやき」
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoアプリ開発【友達リスト】〜その⑤〜

はじめに 今回はDjangoを使って友達リスト的なアプリを作成しようと思います。 プロジェクト名はTomodachi メモ機能実装(友達とのリレーションを貼る) # ブランチを切り替える $ git branch feature-memo $ git checkout feature-memo メモモデル作成 friendslist/models.py # 友達対メモ(1対多)のリレーションで作成 class Memo(models.Model): text = models.CharField(default="", max_length=100) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) friend = models.ForeignKey(Friend, on_delete=models.CASCADE) # マイグレーションする friendslist/admin.py # 管理画面を投稿できるようにする from friendslist.models import User, Friend, Category, Memo admin.site.register(Memo) 友達詳細画面にてメモを表示する friendslist/views.py # メモを表示する記述をする from friendslist.models import Friend, Category, Memo def friend(request, pk): if request.method == 'POST': friend = Friend.objects.get(pk=pk) form = FriendForm(request.POST, instance=friend) if form.is_valid(): friend.save() user = request.user categories = Category.objects.filter(user=user) friend = Friend.objects.get(pk=pk) memos = Memo.objects.filter(friend=friend) ←☆追加する context = { 'categories': categories, 'friend': friend, 'memos': memos,  ←☆追加する } return render(request, 'friendslist/friend.html', context) friend.html # メモを表示する <div class="column col-md-8"> <h2>友達メモ</h2> <ul class="list-group"> {% for memo in memos %} <li class="list-group-item">{{ memo.text }}</li> {% endfor %} </ul> </div> 友達詳細画面にてメモを作成する config/urls.py # URLを設定する path('<slug:pk>/memo/create/', views.memo_create, name="memo_create"), friendslist/forms.py # フォームを設定する from friendslist.models import Friend, Category, Memo class MemoForm(forms.ModelForm): class Meta: model = Memo fields = ( 'text', ) friendslist/views.py # メモを作成する記述をする from friendslist.forms import FriendForm, UserCreationForm, CategoryForm, MemoForm def memo_create(request, pk): if request.method == 'POST': form = MemoForm(request.POST) friend = Friend.objects.get(pk=pk) if form.is_valid(): memo = form.save(commit=False) memo.friend = friend memo.save() return redirect('/{}/'.format(pk)) context = {} return render(request, 'friendslist/memo/create.html', context) friend.html # メモ作成ページへのリンクを貼る <a class="btn btn-info" href="{% url 'memo_create' friend.pk %}" role="button">メモを登録する</a> memo/create.html # メモ作成ページを作る {% extends 'friendslist/base.html' %} {% block content %} <main class="container mb-5"> <form class="row" method="POST"> {% csrf_token %} <div class="col col-md-8"> <h2>メモ作成</h2> <ul class="list-group"> <li class="list-group-item"> <label class="form-label">メモ</label> <textarea name="text" class="form-control" id="id_text" cols="30" rows="10" placeholder="ここにメモを書いてください"></textarea> </li> <li class="list-group-item"> <button type="submit" class="btn btn-success float-end">保存</button> </li> </ul> </div> </form> </main> {% endblock %} メモを削除する機能を実装する config/urls.py # URLを設定 path('<slug:pk>/memo/<slug:memo_pk>/delete/', views.memo_delete, name="memo_delete"), friendslist/views.py # 削除する記述をする def memo_delete(request, pk, memo_pk): try: memo = Memo.objects.get(pk=memo_pk) except Memo.DoesNotExist: raise Http404 memo.delete() return redirect('/{}/'.format(pk)) ```friend.html # メモを削除するボタンを作成する <ul class="list-group"> {% for memo in memos %} <li class="list-group-item"> <a href="{% url 'memo_delete' friend.pk memo.pk %}" class="btn btn-outline-secondary btn-sm"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-archive" viewBox="0 0 16 16"> <path d="M0 2a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1v7.5a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 1 12.5V5a1 1 0 0 1-1-1V2zm2 3v7.5A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5V5H2zm13-3H1v2h14V2zM5 7.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/> </svg> </a> {{ memo.text }} </li> {% endfor %} </ul> 見た目の修正 フッター削除 base.html # footerの削除(以下を削除) {% include 'friendslist/snippets/footer.html' %} base_auth.html # footerの削除(以下を削除) {% include 'friendslist/snippets/footer.html' %} 認証画面修正 auth.html # タイトル見た目修正 <h1 class="display-4 fst-italic text-center "><a href="/" class="text-decoration-none">Tomodachi</a></h1> # サインインボタンを修正 <div class="form-label-group my-3 d-flex justify-content-center"> <button class="btn btn-primary btn-block" type="submit"> {% if 'login' in request.path %} サインイン {% elif 'signup' in request.path %} サインアップ {% endif %} </button> </div> ヘッダーを修正 snippets/header.html # ヘッダのリンクと認証の有無で表示の有無 {% if user.is_authenticated %} <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link" aria-current="page" href="/">友達一覧</a> </li> <li class="nav-item"> <a class="nav-link" href="/create/">友達登録</a> </li> <li class="nav-item"> <a class="nav-link" href="/category/{{ first_category.pk }}/">カテゴリ一覧</a> </li> <li class="nav-item"> <a class="nav-link" href="/category/create/" tabindex="-1" aria-disabled="true">カテゴリ登録</a> </li> </ul> </div> {% endif %} 友達一覧ページ index.html # ふりがながあれば表示する {% if friend.furigana is not None %} ({{ friend.furigana }}) {% endif %} 友達詳細ページ friendslist/views.py # 誕生日をinputに表示させる(フォーマット変更) friend_birthday= "{0:%Y-%m-%d}".format(friend.birthday) friend.html # 誕生日をinputに表示させる(フォーマット変更) <li class="list-group-item"> <label class="form-label">誕生日</label> <input type="date" class="form-control" id="id_birthday" name="birthday" value="{{ friend_birthday }}"> </li> 友達作成ページ friendslist/create.html # 友達メモの部分を削除する(以下の部分を削除) <div class="column col-md-8"> <h2>友達メモ</h2> <ul class="list-group"> <li class="list-group-item">メモ1</li> <li class="list-group-item">メモ2</li> <li class="list-group-item">メモ3</li> </ul> </div> カテゴリ一覧ページ category/index.html # カテゴリ削除ボタンを修正 <a href="{% url 'category_delete' category.pk %}" class="btn btn-danger btn-sm float-end"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-archive" viewBox="0 0 16 16"> <path d="M0 2a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1v7.5a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 1 12.5V5a1 1 0 0 1-1-1V2zm2 3v7.5A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5V5H2zm13-3H1v2h14V2zM5 7.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/> </svg> </a> # ふりがな表示 {% if friend.furigana is not None %} ({{ friend.furigana }}) {% endif %} リンクボタンを真ん中寄せにする snippets/linkbtns.html # リンクボタンを真ん中に寄せる <div class="col d-flex justify-content-center"> <a class="btn btn-danger" href="/" role="button">友達一覧</a> </div> <div class="col d-flex justify-content-center"> <a class="btn btn-success" href="/create/" role="button">友達登録</a> </div> <div class="col d-flex justify-content-center"> <a class="btn btn-warning" href="/category/{{ first_category.pk }}/" role="button">カテゴリ一覧</a> </div> <div class="col d-flex justify-content-center"> <a class="btn btn-info" href="/category/create/" role="button">カテゴリ登録</a> </div> さいごに 今回はメモのリレーションをしました。 メモの一覧表示機能 メモの作成機能 メモの削除機能 見た目のリファクタリングをしました。 base.htmlの修正 認証画面の修正 ヘッダーの修正 友達一覧表示 友達詳細表示 友達作成 カテゴリ一覧表示 リンクボタン あと、サインアップ後に一覧表示できないエラーがおきました。 →この原因はユーザー作成時にカテゴリが作成されていないことが原因のようです。 →つまり、ユーザー作成時にデフォルトカテゴリを作成する機能を実装する必要がありそうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

flaskの仮想環境の作り方

前提 MAC os Anacondaをインストールしていること 仮想環境をflaskenv2という名前で、Python3.8で作成 conda create -n flaskenv2 python=3.8 作成されているか、確認をする。 conda env list 仮想環境を有効化する conda activate flaskenv2 成功すれば、以下のように変わる (flaskenv2) yusuke@mbp 現在インストールしている環境の確認 conda list Name Version Build Channel ca-certificates 2021.5.25 hecd8cb5_1 certifi 2021.5.30 py38hecd8cb5_0 libcxx 10.0.0 1 libffi 3.3 hb1e8313_2 ncurses 6.2 h0a44026_1 openssl 1.1.1k h9ed2024_0 pip 21.1.2 py38hecd8cb5_0 python 3.8.10 h88f2d9e_7 readline 8.1 h9ed2024_0 setuptools 52.0.0 py38hecd8cb5_0 sqlite 3.35.4 hce871da_0 tk 8.6.10 hb0a8c7a_0 wheel 0.36.2 pyhd3eb1b0_0 xz 5.2.5 h1de35cc_0 zlib 1.2.11 h1de35cc_3 flaskがないので、flaskをインストール pip install flask 現在インストールしている環境の確認 conda list Name Version Build Channel ca-certificates 2021.5.25 hecd8cb5_1 certifi 2021.5.30 py38hecd8cb5_0 click 8.0.1 pypi_0 pypi flask 2.0.1 pypi_0 pypi itsdangerous 2.0.1 pypi_0 pypi jinja2 3.0.1 pypi_0 pypi libcxx 10.0.0 1 libffi 3.3 hb1e8313_2 markupsafe 2.0.1 pypi_0 pypi ncurses 6.2 h0a44026_1 openssl 1.1.1k h9ed2024_0 pip 21.1.2 py38hecd8cb5_0 python 3.8.10 h88f2d9e_7 readline 8.1 h9ed2024_0 setuptools 52.0.0 py38hecd8cb5_0 sqlite 3.35.4 hce871da_0 tk 8.6.10 hb0a8c7a_0 werkzeug 2.0.1 pypi_0 pypi wheel 0.36.2 pyhd3eb1b0_0 xz 5.2.5 h1de35cc_0 zlib 1.2.11 h1de35cc_3 flaskのインストールが確認できた。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【pix2pix VS Excel】AIに強いExcelのアート効果はどれだ!

概要  皆さんは、Excelのアート効果というものをご存じでしょうか?  アート効果は図を絵具やクレヨンで書いたように加工してくれる機能なのですが、使い方によっては以下のように、個人情報を隠すことにも使えます。  しかしAIの進化でモザイク画像の元画像を予測する技術が出てきており、これらのアート効果も簡単に取り除かれるのも時間の問題と考えられます。  そこで、pix2pixという技術を用いて、アート効果をあたえた文章の復元を行い、どのアート効果の復元が最も難しいのかを調査します。 目次 使用するデータやアート効果 評価方法 使用したコード 結果 まとめ 使用するデータやアート効果  使用する文章については、驚くほど難解な漢字とカタカナやひらがながメチャクチャ大量に混じっている方がいいと考えて「クソデカ羅生門」を用いることにしました。  この「クソデカ羅生門」の一部をExcelのテキストボックスに入れ、そのテキストボックスを画像に変換したものに、アート効果をあたえます。  そして、使用したアート効果が以下の図です。  自分で一部が読めないもののみ残し、そのまま読めてしまうアート効果は除外しました。 評価方法  評価方法は、IoUを用いて、元画像の文字と予想結果の文字を比べ、一番IoUの値が小さいアート効果が「最もAIに強い」とします。 IoU = \frac{選択できた場所[赤]}{選択できなかった場所[青]+選択できた場所[赤]+間違った選択をした場所[緑]}   使用したコード  Excelのテキストボックスに特定の文章を設定し、画像として保存するコードが以下のものです。  動かす前に、Sheet1にテキストボックスと、"文章"と名前を付けたセルを用意し、"文章"と名前を付けたセルに好きな文章を入力して、main()を実行します。  注意点としては、動作環境によっては、ウィンドウを画面に表示していないと、正常に画像が保存されませんし、まれに画像の保存が失敗します。  ほんとうに、VBAの画像関連の処理がクソザコナメクジなところが大嫌いです。 Module1.bas Option Explicit #If VBA7 Then Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal ms As LongPtr) #Else Private Declare Sub Sleep Lib "kernel32" (ByVal ms As Long) #End If Sub Sleep_(time As Long) Dim I As Long For I = 1 To Int(time / 25) Sleep 25 DoEvents Next I End Sub Sub main() Call Makeimg(msoEffectPencilGrayscale, "PencilGrayscale") Call Makeimg(msoEffectPencilSketch, "PencilSketch") Call Makeimg(msoEffectLineDrawing, "LineDrawing") Call Makeimg(msoEffectChalkSketch, "ChalkSketch") Call Makeimg(msoEffectPaintStrokes, "PaintStrokes") Call Makeimg(msoEffectPaintBrush, "PaintBrush") Call Makeimg(msoEffectGlowDiffused, "GlowDiffused") Call Makeimg(msoEffectBlur, "Blur") Call Makeimg(msoEffectLightScreen, "LightScreen") Call Makeimg(msoEffectWatercolorSponge, "WatercolorSponge") Call Makeimg(msoEffectFilmGrain, "FilmGrain") Call Makeimg(msoEffectMosiaicBubbles, "MosiaicBubbles") Call Makeimg(msoEffectGlass, "Glass") Call Makeimg(msoEffectCement, "Drawing") Call Makeimg(msoEffectTexturizer, "Texturizer") Call Makeimg(msoEffectCrisscrossEtching, "CrisscrossEtching") Call Makeimg(msoEffectPastelsSmooth, "PastelsSmooth") Call Makeimg(msoEffectPlasticWrap, "PlasticWrap") Call Makeimg(msoEffectCutout, "Cutout") Call Makeimg(msoEffectPhotocopy, "Photocopy") Call Makeimg(msoEffectGlowEdges, "GlowEdges") Call Makeimg(0, "None") End Sub Sub Makeimg(msoEffect As Long, savefolder As String) Dim textobj As Object, imageobj As Object Dim text As String Dim count As Long Application.DisplayAlerts = False Application.ScreenUpdating = False '保存するフォルダを選択 If Dir(ThisWorkbook.Path & "\" & savefolder, vbDirectory) = "" Then MkDir ThisWorkbook.Path & "\" & savefolder End If text = Sheet1.Range("文章") count = 1 Do While Len(text) > 140 'テキストをセット Set textobj = ActiveSheet.Shapes.Range(Array("TextBox 1")) textobj.TextFrame2.TextRange.Characters.text = text 'テキストボックスを画像としてコピーする Do While Not (Copy_Text_Box(textobj)) DoEvents Loop '先ほどコピーした画像を選択 DoEvents Set imageobj = ActiveSheet.Shapes(2) DoEvents 'アート効果をつける If msoEffect <> 0 Then Call imageobj.Fill.PictureEffects.Insert(msoEffect) Sleep_ (100) End If DoEvents '画像を保存する Do While Not (Save_Pic(imageobj, savefolder & "\" & count & ".png")) DoEvents Loop '新しいテキストを選択する count = count + 1 text = Right(text, Len(text) - 10) imageobj.Delete DoEvents Loop Application.DisplayAlerts = True Application.ScreenUpdating = True End Sub Function Copy_Text_Box(textobj As Object) As Boolean On Error GoTo copy_Error textobj.Select DoEvents Selection.copy Sheet1.Range("D1").Select DoEvents ActiveSheet.Pictures.Paste.Select Copy_Text_Box = True Exit Function copy_Error: Copy_Text_Box = False End Function Function Save_Pic(imageobj As Object, savefile As String) As Boolean On Error GoTo save_pic_Error Application.ScreenUpdating = True DoEvents imageobj.CopyPicture DoEvents ActiveSheet.ChartObjects.Add(0, 0, imageobj.Width, imageobj.Height).Name = "貼付用" DoEvents With ActiveSheet.ChartObjects("貼付用") .Chart.PlotArea.Fill.Visible = msoFalse DoEvents .Chart.ChartArea.Fill.Visible = msoFalse DoEvents .Chart.ChartArea.Border.LineStyle = 0 End With Sleep_ (1000) ActiveSheet.ChartObjects("貼付用").Chart.Paste Sleep_ (1000) ActiveSheet.ChartObjects("貼付用").Chart.Export ThisWorkbook.Path & "\" & savefile ActiveSheet.ChartObjects("貼付用").Delete Application.ScreenUpdating = False Save_Pic = True Exit Function save_pic_Error: Application.ScreenUpdating = False Save_Pic = False End Function  VBAは画像を処理するには不向きなので、以下のように、Excelから出力された画像を、pix2pixに読み込めるように変換する、pythonのコードを作成しました。 main.py from PIL import Image import numpy as np import os import glob dirlist = os.listdir(path='./excelimg') #フォルダのループ for folder_ in dirlist: folder_fll = './excelimg/' + folder_ print(folder_fll) list_ = glob.glob(folder_fll + "/*") for file_ in list_: filename = file_.split("/")[-1].split("\\")[-1] print(filename) if os.path.isfile("./excelimg/None/" + filename): #画像の読み込みを行う img1 = np.asarray(Image.open(file_).convert('RGB').resize((256, 256))) img2 = np.asarray(Image.open("./excelimg/None/" + filename).convert('RGB').resize((256, 256))) outimg = np.zeros((img1.shape[0],img1.shape[1] * 2,3),dtype = "int") #pix2pix用の画像の作成 outimg[:,:img1.shape[1],:] = img1 outimg[:,img1.shape[1]:,:] = img2 #フォルダがなければ作成する if not os.path.exists('./pix2pixdata/' + folder_): os.mkdir('./pix2pixdata/' + folder_) if not os.path.exists('./pix2pixdata/' + folder_ + "/train"): os.mkdir('./pix2pixdata/' + folder_+ "/train") if not os.path.exists('./pix2pixdata/' + folder_ + "/test"): os.mkdir('./pix2pixdata/' + folder_+ "/test") if not os.path.exists('./pix2pixdata/' + folder_ + "/val"): os.mkdir('./pix2pixdata/' + folder_+ "/val") #画像を保存する pilImg = Image.fromarray(np.uint8(outimg)) if filename[0] == "1": pilImg.save('./pix2pixdata/' + folder_ +"/val/"+ filename) elif filename[0] == "2": pilImg.save('./pix2pixdata/' + folder_ +"/test/"+ filename) else: pilImg.save('./pix2pixdata/' + folder_ +"/train/"+ filename)  次に以下のサイトからpix2pixのソースコードをクローンし、README.mdに書かれている通りにpix2pixの学習を実行していきます。  IoUの計算を行うため、test.pyを以下のコードと入れ替えて実行してください。 main.py import os from options.test_options import TestOptions from data import create_dataset from models import create_model from util.visualizer import save_images from util import html from util import util import cv2 import numpy as np if __name__ == '__main__': opt = TestOptions().parse() # get test options # hard-code some parameters for test opt.num_threads = 0 # test code only supports num_threads = 0 opt.batch_size = 1 # test code only supports batch_size = 1 opt.serial_batches = True # disable data shuffling; comment this line if results on randomly chosen images are needed. opt.no_flip = True # no flip; comment this line if results on flipped images are needed. opt.display_id = -1 # no visdom display; the test code saves the results to a HTML file. dataset = create_dataset(opt) # create a dataset given opt.dataset_mode and other options model = create_model(opt) # create a model given opt.model and other options model.setup(opt) # regular setup: load and print networks; create schedulers # create a website web_dir = os.path.join(opt.results_dir, opt.name, '{}_{}'.format(opt.phase, opt.epoch)) # define the website directory if opt.load_iter > 0: # load_iter is 0 by default web_dir = '{:s}_iter{:d}'.format(web_dir, opt.load_iter) print('creating web directory', web_dir) webpage = html.HTML(web_dir, 'Experiment = %s, Phase = %s, Epoch = %s' % (opt.name, opt.phase, opt.epoch)) # test with eval mode. This only affects layers like batchnorm and dropout. # For [pix2pix]: we use batchnorm and dropout in the original pix2pix. You can experiment it with and without eval() mode. # For [CycleGAN]: It should not affect CycleGAN as CycleGAN uses instancenorm without dropout. area_of_ove = 0 area_of_uni = 0 if opt.eval: model.eval() for i, data in enumerate(dataset): if i >= opt.num_test: # only apply our model to opt.num_test images. break model.set_input(data) # unpack data from data loader model.test() # run inference visuals = model.get_current_visuals() # get image results img_path = model.get_image_paths() # get image paths if i % 5 == 0: # save images to an HTML file print('processing (%04d)-th image... %s' % (i, img_path)) save_images(webpage, visuals, img_path, aspect_ratio=opt.aspect_ratio, width=opt.display_winsize) #Iou for label, im_data in visuals.items(): im = util.tensor2im(im_data) if "fake_B" == label: fake_B = (cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) < 128) elif "real_B" == label: real_B = (cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) < 128) area_of_ove_flag = (fake_B & real_B) area_of_uni_flag = (fake_B | real_B) area_of_ove = area_of_ove + np.count_nonzero(area_of_ove_flag) area_of_uni = area_of_uni + np.count_nonzero(area_of_uni_flag) print("------------IoU----------------") print(area_of_ove/area_of_uni) print("-------------------------------") webpage.save() # save the HTML 結果  以下の画像が、アート効果をあたえた画像を復元した結果です。  ひらがなやカタカタは、うまく復元できているように見えますが、「パッチワーク」は日本語っぽい文字を張り付けているだけで、言語として読むことができません。  漢字は全体的に復元がうまくいっていないようで、謎の漢字っぽい何かが出現することがあります。  そして、IoUの計算結果が、以下の表です。  この表から、パッチワークとガラスが最も復元が難しいようです。  一番驚いたのが、人間が見ても元の文章が分かりにくい「光彩:輪郭」が2番目に復元しやすかったということで、人間にはわからない文字の痕跡を、機械は簡単に見つけられるようです。 アート効果 IoU パッチワーク 0.353 ガラス 0.369 スポンジ 0.414 ぼかし 0.428 線画 0.452 鉛筆:モノクロ 0.471 チョークスケッチ 0.491 光彩:輪郭 0.527 カットアウト 0.536 まとめ  pix2pixを用いてExcelのアート効果から元画像の復元を行いました。  結果は、パッチワークが最も復元しにくく、カットアウトが最も復元しやすかったようです。  ただし、今回行った方法はアート効果ごとに機械学習のモデルを作成するという方法であり、ちゃんと復元するには、どのようなアート効果を使用したかの詳細が必要です。  例えば「ぼかし」を復元するpix2pixのモデルで「チョークスケッチ」を復元すると、以下のような画像になりIoUは0.201まで落ちます。  実際に他人の修正の入った文章を復元しようとすると、使用したアート効果の詳細は分かりませんから、復元することは困難であると考えられます。  当然、技術は日々様々な問題を解決していっているので、いずれこれらの問題も解決されるでしょうから、機密情報を隠したいときは、黒塗りを用いる方がいいでしょう。    
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでcsvを扱う方法

はじめに 機械学習関連の開発で、大量に出力されたCSVデータを扱う機会があったので、Pythonでcsvデータを扱う方法を備忘録としてまとめてみました。 CSVファイルの中身を1行ずつ表示する csvファイルを扱うにはpythonに標準的に備わっているcsvモジュールを使用します。 csvファイルを指定して、行の内容をリストとして表示するには以下のように書きます。 import csv #読み出したいファイルのパス f = open("C:\\Users\\redpe\\Desktop\\XXXXX.csv") reader = csv.reader(f) #1行ずつ出力させる for row in reader: print(row) f.close() ヘッダーの行を読み飛ばす csvファイルを処理する際に、先頭のヘッダ部分は使わないことがあります。 その時は以下のように読み飛ばす処理を入れます。 import csv #読み出したいファイルのパス f = open("C:\\Users\\redpe\\Desktop\\XXXXX.csv") reader = csv.reader(f) #1行ずつ出力させる for row in reader: #1行目を読み飛ばす場合 if reader.line_num == 1: continue print(row) f.close() CSVファイルにデータを追記する もともとのCSVファイルにデータを1行追加したい場合には以下のようにします。 指定したCSVファイルに ['2021/6/13', '11:00', '30', '60', 'piglet']の行を追加しています。 import csv #追記したファイルのパス f = open("C:\\Users\\redpe\\Desktop\\XXXXX.csv",mode="a",newline="") writer = csv.writer(f) #追記したいデータリストを作成 data = ['2021/6/13', '11:00', '30', '60', 'piglet'] #データを追記する writer.writerow(data) f.close()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UbuntuやRaspbianでPython3をデフォルトにするDebian的方法

現在配布されているRaspbianOSなど主要なUbuntuから派生したディストリビューションは今もPhyton2.7がデフォルトでPython3を使うには明示的にpython3とコマンドを打たなければいけないことが多いです。普段の用途ではpythonいえばpython3なので、pythonコマンドでpython3を起動したいところです。Ubuntuを含むDebian系では複数のコマンドが一つのコマンド名を使う時の調停の仕組みとしてupdate-alternativeというシステムが用意されていますが、なぜか/usr/bin/pythonだけはこの仕組みを利用していないようなので、alternativeを使った方法を紹介します。おそらくDebian的により正式な方法です 普通にpythonコマンドのalternativeを変更しようとすると以下のように出てきます。 $ sudo update-alternatives --list python update-alternatives: error: no alternatives for python エラーにひるまずalternativeを設定します。 $ sudo update-alternatives --install /usr/bin/python python /usr/bin/python2 10 $ sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 20 あとはupdate-alternatives --config pythonをするとpythonのデフォルトを聞かれるようになります。 $ sudo update-alternatives --config python There are 2 choices for the alternative python (providing /usr/bin/python). Selection Path Priority Status ------------------------------------------------------------ * 0 /usr/bin/python3 20 auto mode 1 /usr/bin/python2 10 manual mode 2 /usr/bin/python3 20 manual mode pipは自動的に追随してくれるようです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

相対性理論とかよく分からないけどPythonしてみた 〜投げたボールの行方〜

概要 光の速さで爆進する宇宙船からボールを投げたらどうなるかを、pythonの助けを借りて理解したいと思います。 相対性理論 有名な相対性理論の話に ロケットが光の速度に近づくほど時間の流れが遅くなる というのがありますよね。 浦島効果とかいうんでしたっけ。 では、光の速さに近いロケットからボールを前に投げるとどうなるのか? 時間の流れが遅いのだから、大谷翔平投手の豪速球でもかなり遅くなるはずですね。 ガリレイ変換 さて、ガリレイ変換というのがあります。 u = v + w 宗教裁判で有名なガリレオ・ガリレイ先生が400年も前に考えた式で、速度wで移動している馬車の窓から前方にリンゴを速度vで投げると、v+wの速度で通行人に当たるよ、という意味です。危ないからやめましょうね。 ローレンツ変換 実はガリレイ変換は100%正しいという訳ではなかった。 ホントはこうだよ、というのが300年後の1905年にアインシュタイン先生によって示されました。これはローレンツ変換という式から導かれます。 u = \frac{v + w}{1 + \frac{v w}{c^2}} ガリレイ先生の式よりかなり複雑になっていますね。 式に出てくるcは光の速度です。 Python では、アインシュタイン先生の式をpythonにぶち込んで大谷投手のボールがどうなるのか見てみましょう。 その前に環境。 環境 Mac Jupyter Notebook Python3 計算 光の速さに近いロケットから投球する前に、まずは歩きながらボールを投げてもらいましょう。 歩く速度を秒速1メートルとします。大谷投手のボールの速さは165km/h、秒速では45.83メートルです。速いですね。 import math # 光速 約30万キロメートル/s (メートルで計算) speedOfLight = 299792458 #ローレンツ変換の関数(変換式から元の速度を引いたもの) def convertSpeed(baseSpeed, throwSpeed): return (baseSpeed + throwSpeed) / (1 + baseSpeed * throwSpeed / speedOfLight ** 2) - baseSpeed #歩きながらボールを投げる speed = convertSpeed(1, 45.83) print(speed) 計算では簡単にするためにボールの速度から歩く速度を引きました。 相対性理論によれば動いていると時間の流れは遅くなるので、球の速さは45.83メートルより遅くなっているはずです。 結果は、 45.82999999999998 m/s でした。 ほとんど変わっていません。 実はガリレイ変換の誤りに300年間も誰も気づかなかったのは、日常的なスピードではローレンツ変換とほとんど差がないからなのです。 一体どのくらいのスピードで移動すれば時間が遅くなったことを体感できるのか? 次は新幹線に乗って投球してもらいましょう。 新幹線は最高時速320km、秒速では88.88メートルです。 #新幹線に乗ってボールを投げる speed = convertSpeed(88.88, 45.83) print(speed) 45.82999999999387 m/s ボールの速度は小数点12桁目でようやく遅くなりました。 これではどうみても誤差の範囲内にされるレベルです。 次は月ロケットに乗ってボールを投げてもらいましょう。 地球を脱出するのに必要な月ロケットの速度は時速40,300km、秒速11,194.44メートルです。ムチャクチャ速いです。 これなら流石に分かるくらい時間の流れが遅くなるでしょう。 #ロケットに乗ってボールを投げる speed = convertSpeed(11194.44, 45.83) print(speed) 45.829999935836895 m/s なんだ、全然変わらないぞ? 月ロケットでも相対性理論による時間の流れの変化は全く体感できません。 ここから先はSFの世界です。 スターウォーズのミレニアムファルコンに登場してもらいましょう。 銀河系最速の宇宙船からボールを投げる 光速の20%のスピードで爆走する宇宙船を用意します。 凄まじいスピードですがファルコン号なら余裕でしょう。 #光速の20%で移動する宇宙船に乗ってボールを投げる speed = convertSpeed(60000000, 45.83) print(speed) 43.99425957351923 m/s やりました! ついに秒速2mくらい大谷投手のボールが遅くなりました。 でも思ったほどではないですね。光速の20%も出しているのに・・・ もっとファルコン号のスピードを上げてみましょう。 #光速の40%で移動する宇宙船に乗ってボールを投げる speed = convertSpeed(120000000, 45.83) print(speed) 38.48704135417938 m/s 光速の40%という猛烈なスピードで移動してようやくボールを体感できるくらい遅くすることができました。 ちなみに光速の60%ではボールは29 m/sくらいまで遅くなってしまいます。時速にして104kmですね。それでも速いですが。 100%光速で移動する宇宙船からボールを投げると・・・ では光速で移動しながらボールを投げるとどうなるのでしょう。 #光速の100%で移動するロケットに乗ってボールを投げる speed = convertSpeed(speedOfLight, 45.83) print(speed) 0 m/s 結果は0メートルです。これはつまりボールは大谷投手の手を離れていない、ということを意味しています。光速で移動した場合、時間は止まってしまい誰も動けなくなる、ということですね。不思議ですね。 グラフ 今までの実験結果をグラフにしてみましょう。 ファルコン号の初速を秒速1万kmにして、1000kmずつスピードを上げていきましょう。 スピードアップごとにボールを投げてどのくらい遅くなっているか計測します。 これを光の速さに到達するまで続けます。 import math # matplotlib をインラインで使用 %matplotlib inline # ライブラリの読み込み import matplotlib.pyplot as plt from matplotlib.ticker import ScalarFormatter # 光速 約30万キロメートル/s (メートルで計算) speedOfLight = 299792458 #ローレンツ変換の関数(変換式から元の速度を引いたもの) def convertSpeed(baseSpeed, throwSpeed): return (baseSpeed + throwSpeed) / (1 + baseSpeed * throwSpeed / speedOfLight ** 2) - baseSpeed baseSpeedList = list() convSpeedList = list() #宇宙船の初速 baseSpeed = 10000000 #追加する速度 addSpeed = 100000 #ボールの速度 ballSpeed = 45.83 #宇宙船が光速に到達するまで速度を追加 while baseSpeed < speedOfLight: baseSpeedList.append(baseSpeed) convSpeedList.append(convertSpeed(baseSpeed, ballSpeed)) baseSpeed += addSpeed #グラフ plt.xlabel("speed of millennium falcon (m/s)") plt.ylabel("speed of Otani's ball (m/s)") plt.plot(baseSpeedList, convSpeedList) ax = plt.gca() ax.yaxis.set_major_formatter(ScalarFormatter(useMathText=True)) ax.xaxis.set_major_formatter(ScalarFormatter(useMathText=True)) plt.show() 結果のグラフです。 滑らかな曲線を描いていますね。 ファルコン号の速度に反比例して大谷投手のボールが遅くなっているのが分かります。 ただし一様に遅くなるのではなくて、光速に近づくほど遅くなる割合が大きくなるようです。 分かったこと 月ロケットに乗っても時間の流れはほとんど変わらない ミレニアムファルコン号なら時間が遅くなったと体感できる(光速の40%くらいで飛行時) 100%光速で移動すると時間は止まる それではまた次回〜
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

欠損がある入力データをベイズ的に補完しつつ線形回帰

予測モデルを構築する中で,データが欠損値を含むことは多々あります.素朴な対処方法としては①欠損値を含む行・列を捨てる,②平均値などの適当な値で埋める,の二つが考えられるでしょう.①・②いずれの場合も,情報を捨ててしまっていることになるため精度の低下につながります.いい方法がないか調べたところ,ベイズ的な考え方により補完が可能であると知りました. なお,今回の内容は以下の須山さんの記事を参考にしています.須山さんは2値分類をされているので,こちらではベイズ線形回帰をやってみることにしました. 欠損の分類と扱いやすさ あまり気にしたことはありませんでしたが,欠損にもいくつかの種類があるそうです.ここでは例として,性別と体重を説明変数とし,ある病気にかかっているか否かを予測するような問題を考えます.欠損の分類については初めて学んだので,誤解などあればご指摘いただけますと幸いです. MCAR (Missing Completely At Random): 欠損値の発生が完全にランダムである場合です.つまり,ほかの説明変数の値によって欠損が生じやすくなったりしないというケースです.例えば,何人かに対して体重の情報を聞き取るのをうっかり忘れてしまったとします.これはまさにMCARにあたります. MAR (Missing At Random): 欠損値の発生がほかの説明変数に依る場合です.先ほどの例では,性別が女性の場合に体重を申告してくれない場合が多かったとすると,これはMARに該当します. MNAR (Missing Not At Random): 他の説明変数を固定しても,欠損値の発生が欠損値の値に依る場合です.例としては,男女別で考えたとしても,体重が重い人は申告してくれないというケースが考えられます.他の説明変数を固定するというのが重要なポイントです. 扱いやすさとしては,MCAR > MAR > MNARとなっています.MCARは以下で述べるベイズ的な考え方で対処することができます.MARに関しては,欠損の発生を表す確率変数を導入することで対処するようですが,わりと難しそうです.MNARへの対処も少し調べてみましたが,かなり厳しそうだということがわかりました. 今回の記事ではMCARの取り扱い方を説明します. 理論 まず問題設定を明確にし,事後分布の直接計算が困難であることを示します.また他の変数を所与としたときの事後分布を解析的に計算し,ギブスサンプリングの手順を示します. 問題設定 訓練データの入力全体を$X$で表し,その中で観測できたデータを$X_{\rm observed}$,欠損したデータを$X_{\rm missing}$と表すことにします.説明変数の数を$M$,データ数を$N$とすると,入力データ$X$は$N \times M$行列になっています.訓練データの出力値を${\bf y}$とします.また簡単のため,説明変数と目的変数はそれぞれ標準化(つまり平均0,分散1になるように)されているものとします. 今回はパラメータ${\bf w}$による線形回帰を扱うことにします: $$ {\bf y} = X {\bf w} + \hat{\bf \epsilon} $$ ただし$\hat{\bf \epsilon}$は平均$0$,分散$1/\beta$の正規分布に従うものとします.ここから,${\bf y}$の事後分布は $$ p({\bf y} \mid X, {\bf w}) = \left( \dfrac{\beta}{2\pi} \right)^{N/2} \exp \left[ - \dfrac{\beta}{2} ({\bf y} - X{\bf w})^2 \right] $$ となります. さらにパラメータ${\bf w}$と入力データ$X$の事前分布を以下のように仮定します: p({\bf w}) = \left( \dfrac{\alpha}{2\pi} \right)^{M/2} \exp \left[ -\dfrac{\alpha}{2} {\bf w}^2 \right] \\ p(X) = \prod_n \left( \dfrac{ |\Lambda| }{2\pi} \right)^{1/2} \exp \left[ -\dfrac{1}{2} {\bf x}_n^t \Lambda {\bf x}_n \right] ただし$\alpha$と$\Lambda$はそれぞれパラメータ${\bf w}$と入力$X$の分布を決めるパラメータです. いま考えたい問題はいくつかに分けることができるでしょう. 1. 与えられた訓練データ$X_{\rm observed}$と${\bf y}$から${\bf w}$と$X_{\rm missing}$の分布を推定する 2. ハイパーパラメータ$\alpha$, $\beta$として適切な値を選ぶ($\Lambda$は$X_{\rm observed}$からうまく選べるはずなのでここでは無視) 3. 新しい入力${\bf x}$に対する出力値$y$を予測する 以下では1から3を実現する手続きを示します. 1. 訓練データからパラメータの分布を推定 ベイズの定理を用いると${\bf w}$と$X_{\rm missing}$の事後分布は p({\bf w}, X_{\rm missing} \mid X_{\rm observed}, {\bf y}) \propto p({\bf y} \mid {\bf w}, X) p({\bf w}) p(X_{\rm missing} \mid X_{\rm observed}) という形に分解することができます(各変数の依存関係に注意しつつ,丁寧に変形すると導けます). 目的は事後分布$p({\bf w}, X_{\rm missing} \mid X_{\rm observed}, {\bf y})$の計算により${\bf w}$と$X_{\rm missing}$を推定することですが,この事後分布は簡単に取り扱うことができません.いつも通り${\bf w}$について平方完成しようとすると,その「おつり」の項が$X_{\rm missing}$を含んでおり,$X_{\rm missing}$について複雑な形状となっていしまうことが問題です. 一方で,他のすべての変数を所与としたときの事後分布は(少し大変な計算により) {\bf w} \mid * \sim \mathcal{N} \left( \left(X^t X+ \dfrac{\alpha}{\beta} 1_m \right)^{-1} X^t {\bf y},~ \left(\beta X^t X + \alpha 1_m \right)^{-1} \right) \\ x_{n, m} \mid * \sim \mathcal{N} \left( \dfrac{\beta y_n w_m - \sum_{m' \neq m} (\beta w_m' w_m + \Lambda_{m, m'} ) x_{n,m'} }{\beta w_m^2 + \Lambda_{m,m} },~ \dfrac{1 }{\beta w_m^2 + \Lambda_{m,m}} \right) となることが示せます. ゆえに以下に示す手続きから,事後分布$p({\bf w}, X_{\rm missing} \mid X_{\rm observed}, {\bf y})$に従う${\bf w}$と$X_{\rm missing}$を抽出することができます: (0) $X$を計算するために,平均値などを使い適当に欠損値を埋める. (1) ${\bf w} \mid *$の従う分布に基づき${\bf w}$をサンプリングする. (2) $x_{n, m} \mid *$の従う分布に基づき$x_{n, m}$をサンプリングする(ただし$(n,m)$は欠損値に対応するデータ数,説明変数のラベル). (3) (1)(2)を繰り返す. これはまさにギブスサンプリングであり,サンプリングした結果の事後分布$p({\bf w}, X_{\rm missing} \mid X_{\rm observed}, {\bf y})$への収束が保証されている点が大きなメリットです.また,メトロポリス・ヘイスティング法では提案分布を決めるために新しいハイパーパラメータを導入する必要がありますが,ギブスサンプリングでは不要であるという点も便利です. ギブスサンプリングが可能だったのは,他の変数が所与であるとしたときの分布$p({\bf w} \mid *)$,$p(x_{n, m} \mid *)$が計算可能だったことに由来します.ここには今回のモデルが線形回帰であるという点が強く効いており,一般の場合にはギブスサンプリングは可能でないかもしれません.その場合は,メトロポリス・ヘイスティング法などを利用する必要があるでしょう. 何にせよ,事後分布$p({\bf w}, X_{\rm missing} \mid X_{\rm observed}, {\bf y})$に従う${\bf w}$と$X_{\rm missing}$をサンプリングすることができるようになりました.ここから,事後分布$p({\bf w}, X_{\rm missing} \mid X_{\rm observed}, {\bf y})$を推定することができるようになります.実際には,${\bf w}, X_{\rm missing}$の平均や分散をもとめれば分布の特徴をおおよそ捉えることができるため,${\bf w}, X_{\rm missing} $のサンプル平均を計算することになります. 2. ハイパーパラメータの調整 今回は最尤推定によってハイパーパラメータを調整しています.最尤推定とは$p({\bf y} \mid X_{\rm observed}, \alpha, \beta)$を最大化する$\alpha$, $\beta$を計算することですが, \begin{align} p({\bf y} \mid X_{\rm observed}, \alpha, \beta) &= \int p({\bf y}, {\bf w}, X_{\rm missing} \mid X_{\rm observed}, \alpha, \beta)d{\bf w} dX_{\rm missing} \\ &= \int p({\bf y} \mid {\bf w}, X, \alpha, \beta) p({\bf w}, X_{\rm missing} \mid X_{\rm observed}, \alpha, \beta) d{\bf w} dX_{\rm missing} \\ &= \mathbb{E}_{{\bf w}, X_{\rm missing} ~\mid X_{\rm observed}~~, \alpha, \beta} \left[ \mathcal{N}({\bf y} \mid X {\bf w}, \beta) \right] \end{align} によって$p({\bf y} \mid X_{\rm observed}, \alpha, \beta)$を計算できるため,これを最大化する$(\alpha, \beta)$を求めればよいことになります.事後分布$p({\bf w}, X_{\rm missing} ~\mid X_{\rm observed}~~, \alpha, \beta)$に基づく${\bf w}, X_{\rm missing}$をサンプリングすることができるため,以下の手続きで$\alpha$, $\beta$を調整できます: (0) $\alpha_0$, $\beta_0$を適当に初期化 (1) $L(\alpha_t, \beta_t) = \mathbb{E} _ {{\bf w}, X _ {\rm missing} ~\mid X _ {\rm observed}~~, \alpha_t, \beta_t} \left[ \mathcal{N}({\bf y} \mid X {\bf w}, \beta_t) \right]$を,事後分布に基づきサンプリングされた${\bf w}, X_{\rm missing}$から計算 (2) 数値微分により$\alpha_{t+1} = \alpha_t + \eta \dfrac{\partial L}{\partial \alpha}(\alpha_t, \beta_t)$のように更新 ここで$\eta$は適当に決め打ちする必要があります.またスケールの問題から,実際には対数尤度を計算する方がよいでしょう. 3. 新しい入力に対する予測 新しい入力${\bf x}$に対して予測をしたいものとします.今回は${\bf x}$が欠損している可能性があるため,欠損している${\bf x} _ {\rm missing}$と${\bf x} _ {\rm observed}$に分けます.事後分布$p({\bf x} _ {\rm missing}, y \mid {\bf x} _ {\rm observed}, X_{\rm observed}, {\bf y}, \alpha, \beta)$を解析的に計算することはできないので,ギブスサンプリングによって分布を推定します. 先ほどと同様に,他の変数を条件づけたときの分布は {\bf w} \mid * \sim \mathcal{N} \left( \left(X^t X+ \dfrac{\alpha}{\beta} 1_m \right)^{-1} X^t {\bf y},~ \left(\beta X^t X + \alpha 1_m \right)^{-1} \right) \\ x_{m} \mid * \sim \mathcal{N} \left( \dfrac{\beta y w_m - \sum_{m' \neq m} (\beta w_m' w_m + \Lambda_{m, m'} ) x_{m'} }{\beta w_m^2 + \Lambda_{m,m} },~ \dfrac{1 }{\beta w_m^2 + \Lambda_{m,m}} \right) \\ y \mid * \sim \mathcal{N}({\bf w}^t {\bf x}, 1/\beta) となっているため,事後分布$p({\bf x} _ {\rm missing}, y , {\bf w} \mid {\bf x} _ {\rm observed}, X_{\rm observed}, {\bf y}, \alpha, \beta)$をサンプリングによって推定できます.ここで関係ないはずの${\bf w}$が混入していることに違和感があるかもしれませんが,${\bf w}$の事後分布がサンプルを通じてしか求められず,$y$の分布が${\bf w}$を通じて求められる以上,${\bf w}$も含めてサンプルする必要があります. いま,${\bf w}$は訓練データ$X, {\bf y}$のみに依ることに注意します.そのため,${\bf w}$は依然サンプリングした結果を保存してそのまま使うことができます.求めたいのは${\bf x}$と$y$の分布なので,サンプリングした結果から平均や分散を計算すればよいでしょう. 実装 先ほどの理論に基づき,説明変数の補間が可能なベイズ線形回帰のクラスBayesian_LR_with_intepolationを実装しました.このクラスの便利なところは,欠損に対する前処理をすることなくデータを入れてしまえばよいという点です.また標準化や事前分布の設定もクラス内でいい感じにやってくれます. 主なメソッドは以下のようになっています: fit:訓練データを標準化し,ハイパーパラメータを調整する.その後,事後分布$p({\bf w}, X_{\rm missing} \mid *)$に従う${\bf w}$と$X_{\rm missing}$をサンプリングする.サンプルの平均と標準偏差(つまり事後分布に基づくの期待値と標準偏差)を計算できる. predict:与えられたテストデータに対する予測をする.訓練データとテストデータを使いサンプリングし,出力の期待値と標準偏差を返す.テストデータに欠損がある場合は,欠損値に対する予測もする. class Bayesian_LR_with_interpolation: # 説明変数の補間が可能な,ベイズ線形回帰 def __init__(self, alpha = 1.0, beta = 1.0): self.alpha = alpha # alphaはパラメータwの事前分布のパラメータで,alphaが大きいほどwの分布は狭くなる. self.beta = beta # betaは誤差のオーダーの逆数 def fit(self, X_train_original, y_train_original, num_sample = 1000, fit_hyper_parameters = True, num_iteration = 100, eta = 0.1): dalpha = 0.01 dbeta = 0.01 self.hyper_param_record = np.zeros((num_iteration, 2)) # ハイパーパラメータの軌跡を保存 self.normalize_train(X_train_original, y_train_original) # 標準化されたself.X_train, self.y_trainが生成される if len(self.nan_list) == 0: print("Note: There are no missing values in the explanatory variables.") w_sample_mean = np.zeros(len(self.X_train[0])) # サンプリングから計算したwの期待値を格納 w_sample_std = np.zeros(len(self.X_train[0])) # サンプリングから計算したwの標準偏差を格納 X_sample_mean = self.X_train.copy() # サンプリングから計算したXの期待値を格納,欠損値だけ更新 X_sample_std = np.zeros(self.X_train.shape) # サンプリングから計算したXの標準偏差を格納,欠損値だけ更新 X_sample = self.X_train.copy() # 現時点でのサンプルの値,欠損値だけ更新する self.w_samples = np.zeros((num_sample, len(self.X_train[0]))) # wに関するサンプリング結果をすべて集める,予測の際に必要 # ハイパーパラメータを調整,勾配法により尤度を上げるようなハイパーパラメータを探す if fit_hyper_parameters: # ハイパーパラメータを調整する場合 likelihood = self.likelihood(num_sample) # 初期化 for idx_iteration in range(num_iteration): self.hyper_param_record[idx_iteration] = np.array([self.alpha, self.beta]) self.alpha = self.alpha + dalpha likelihood_new = self.likelihood(num_sample) self.alpha = self.alpha - dalpha + eta*(likelihood_new - likelihood) / dalpha likelihood = likelihood_new self.beta = self.beta + dbeta likelihood_new = self.likelihood(num_sample) # 新しい尤度を計算 self.beta = self.beta - dbeta + eta*(likelihood_new - likelihood) / dbeta likelihood = likelihood_new print("iteraton = {0:} / {1:}: alpha = {2:.6f}, beta = {3:.6f}, L = {4:.6f}".format(idx_iteration, num_iteration, self.alpha, self.beta, likelihood), end = "\r") # サンプリングによりwとXの統計量を計算 for t in range(num_sample): # wのサンプリング w_sample = self.sample_w(X_sample) self.w_samples[t] = w_sample # predictionで使うため w_sample_mean = self.update_mean(w_sample_mean, w_sample, t) w_sample_std = self.update_std(w_sample_std, w_sample, t) # Xのサンプリング(欠損している箇所のみ) for idx, (n, m) in enumerate(self.nan_list): X_sample[n, m] = self.sample_x_nm(n, m, w_sample, X_sample[n], self.y_train[n]) X_sample_mean[n, m] = self.update_mean(X_sample_mean[n, m], X_sample[n, m], t) X_sample_std[n, m] = self.update_std(X_sample_std[n, m], X_sample[n, m], t) # 標準化を解除 X_sample_mean = np.tile(self.x_train_original_mean, reps = (len(X_train_original), 1)) \ + X_sample_mean * np.tile(self.x_train_original_std, reps = (len(X_train_original), 1)) X_sample_std = np.tile(self.x_train_original_std, reps = (len(X_train_original), 1)) * X_sample_std print("Fitting has finished: ") print("alpha = {:.6f}".format(self.alpha)) print("beta = {:.6f}".format(self.beta)) return w_sample_mean, w_sample_std, X_sample_mean, X_sample_std def normalize_train(self, X_train_original, y_train_original): # 入力を標準化しつつ,標準化を解除できるように値を保存 # Xについての処理 self.x_train_original_mean = np.nanmean(X_train_original, axis = 0) self.x_train_original_std = np.nanstd(X_train_original, axis = 0) self.X_train = (X_train_original - np.tile(self.x_train_original_mean, reps = (len(X_train_original),1)) ) \ / np.tile(self.x_train_original_std, reps = (len(X_train_original), 1)) # 標準化 self.Lambda = np.linalg.inv( np.cov(self.X_train[np.isnan(X_train_original).sum(1) == 0].T) )# 説明変数がどれも欠損していないデータ行から計算した共分散行列の逆行列 self.nan_list = list(zip(*np.where(np.isnan(X_train_original)))) # もとの入力データでnullの場所を格納 self.X_train[np.isnan(X_train_original)] = 0.0 # 標準化しているため0で埋めてok # yについての処理 self.y_train_original_mean = y_train_original.mean() self.y_train_original_std = y_train_original.std() self.y_train = (y_train_original - self.y_train_original_mean) / self.y_train_original_std def likelihood(self, num_sample): L = 0.0 X_sample = self.X_train.copy() for t in range(num_sample): w_sample = self.sample_w(X_sample) for idx, (n, m) in enumerate(self.nan_list): X_sample[n, m] = self.sample_x_nm(n, m, w_sample, X_sample[n], self.y_train[n]) L += np.exp( -0.5 * self.beta * ((self.y_train - X_sample.dot(w_sample))**2.0).sum() ) # 訓練データ数が増えるにつれてlog_likelihoodは線形に増える(はず)なので規格化.これをしないとデータ数を変えるたびにetaを変更する必要がありそう L = np.log(L) / len(self.y_train) + 0.5*np.log(self.beta) return L def update_mean(self, x_mean, x, t): return (1.0 / (t+1.0)) * (t*x_mean + x) def update_std(self, x_std, x, t): return np.sqrt( (t/(t+1.0)) * (x_std**2.0 + (x_std-x)**2.0 / (t+1.0) ) ) def sample_w(self, X): # 他を条件づけたときのwをサンプル y, alpha, beta = self.y_train, self.alpha, self.beta w_mu = np.linalg.inv(X.T.dot(X) + (alpha/beta)*np.eye(len(X[0]))).dot(X.T).dot(y) # 他の変数を条件づけたときの分布の期待値n w_sigma = np.linalg.inv(beta * X.T.dot(X) + alpha*np.eye(len(X[0]))) # 標準偏差 return np.random.multivariate_normal(w_mu, w_sigma) def sample_x_nm(self, n, m, w, x_n, y_n): # 他を条件づけたときのx_nm(欠損値)をサンプル alpha, beta = self.alpha, self.beta x_nm_mu = x_n[m] + (beta*y_n*w[m] - (beta*w*w[m]+self.Lambda[m]).dot(x_n)) / (beta*w[m]*w[m] + self.Lambda[m,m]) x_nm_sigma = 1.0 / np.sqrt(beta*w[m]*w[m] + self.Lambda[m,m]) return np.random.normal(x_nm_mu, x_nm_sigma) def sample_y(self, X, w): # 他を条件づけたときの出力yをサンプル y_mu = X.dot(w) y_sigma = (1.0/self.beta) * np.eye(len(y_mu)) return np.random.multivariate_normal(y_mu, y_sigma) def predict(self, X_test_original, num_sample = 1000): # 欠損がある可能性のある入力から予測 X_test = (X_test_original - np.tile(self.x_train_original_mean, reps = (len(X_test_original),1)) ) \ / np.tile(self.x_train_original_std, reps = (len(X_test_original), 1)) # 標準化 nan_list = list(zip(*np.where(np.isnan(X_test_original)))) # もとの入力データでnullの場所を格納 X_test[np.isnan(X_test_original)] = 0.0 # 標準化しているため0で埋めてok X_test_sample_mean = X_test.copy() # サンプリングから計算したXの期待値を格納,欠損値だけ更新 X_test_sample_std = np.zeros(X_test.shape) # サンプリングから計算したXの標準偏差を格納,欠損値だけ更新 X_test_sample = X_test.copy() # 現時点でのサンプルの値,欠損値だけ更新する y_test_sample_mean = np.zeros(len(X_test_original)) y_test_sample_std = np.zeros(len(X_test_original)) # サンプリングによりyとXの統計量を計算 for t in range(num_sample): print("prediction: t = {0:} / {1:}".format(t, num_sample), end = "\r") # wのサンプル(前の結果を流用) w_sample = self.w_samples[t] # yのサンプル y_test_sample = self.sample_y(X_test_sample, w_sample) y_test_sample_mean = self.update_mean(y_test_sample_mean, y_test_sample, t) y_test_sample_std = self.update_std(y_test_sample_std, y_test_sample, t) # Xのサンプル for idx, (n, m) in enumerate(nan_list): X_test_sample[n, m] = self.sample_x_nm(n, m, w_sample, X_test_sample[n], y_test_sample[n]) X_test_sample_mean[n, m] = self.update_mean(X_test_sample_mean[n, m], X_test_sample[n, m], t) X_test_sample_std[n, m] = self.update_std(X_test_sample_std[n, m], X_test_sample[n, m], t) # 標準化を解除 y_test_sample_mean = self.y_train_original_mean + self.y_train_original_std * y_test_sample_mean y_test_sample_std = self.y_train_original_std * y_test_sample_std X_test_sample_mean = np.tile(self.x_train_original_mean, reps = (len(X_test), 1)) \ + X_test_sample_mean * np.tile(self.x_train_original_std, reps = (len(X_test), 1)) X_test_sample_std = np.tile(self.x_train_original_std, reps = (len(X_test), 1)) * X_test_sample_std print("Predictions has finished.") return y_test_sample_mean, y_test_sample_std, X_test_sample_mean, X_test_sample_std 数値実験 実装がうまくいっているか確認するために,トイモデルを作成してみました.実装は以下のリンクにあります. https://github.com/yotapoon/Bayesian_LR_with_interpolation 設定 説明変数は$(x_1, x_2)$とし,目的変数を$y$とします.これらは x_1 \sim \mathcal{N}(2.0, 0.4) \\ x_2 \mid x_1 \sim \mathcal{N}(-2.0 x_1 + 0.5, 0.8) \\ y \mid x_1,x_2 \sim \mathcal{N}(3.0 x_1 + 1.5x_2, 0.8) のような関係にあるものとし,訓練データとして$N = 100$点を生成します. ここではMCARの状況を考えます.つまり,欠損の発生が他の変数に依存しないような扱いやすい状況です.また訓練データの入力値$X$の各要素には確率$p = 0.3$で欠損が生じるものとします.$X$は以下のような形状です: [[ 1.58535427 nan] [ 2.10601671 -4.55318535] [ 1.80926526 -3.5396434 ] ... 上のようなデータに対して,以下の四つの方法によりfitします: ① 欠損のないデータ(complete data)を用いて回帰. ② $x_2$をその平均値により補完. ③ $x_2$が欠損している行をそのまま削除. ④ ベイズ線形回帰により補完. また同様のテストデータを$N' = 1000$点生成し,テスト誤差を計算することでそれぞれの性能を評価します.ただし,各手法を比較しやすくするためにテストデータに欠損はないものとします. 期待される結果は以下の通りです: ・欠損のないデータによる回帰①は最も良い性能を与えてほしい. ・平均値による補完②はあまりよくないと聞くので,それほど精度がでないはず ・欠損している行をそのまま削除する方法③は情報量が減るためそこまでいい結果にはならないはず ・ベイズ線形回帰④をすると欠損のないデータによる回帰①より精度は出ないはずだが,適当な補完②や行の削除③よりは精度は出てほしい 結果と考察 数値実験の結果を以下に示します.確認のため,今回実装したクラスを使わない最尤推定の結果も示しています.y_errorは$y$に関するテスト誤差の平均値,X_errorは$X$に関する訓練誤差の平均値です. ① 欠損のないデータ(complete data)を用いて回帰. =====最尤推定===== y_error = 0.6841894767175021 =====ベイズ線形回帰===== Note: There are no missing values in the explanatory variables. alpha = 1.482402 beta = 2.115292 y_error = 0.6814939439707199 ② $x_2$をその平均値により補完. =====最尤推定===== y_error = 0.9199739418129449 =====ベイズ線形回帰===== Note: There are no missing values in the explanatory variables. alpha = 4.767952 beta = 1.785241 X_error = 0.03779597748274369, 0.5054058111998435 y_error = 0.9434735826603966 ③ $x_2$が欠損している行をそのまま削除. =====最尤推定===== y_error = 0.7259293059445424 =====ベイズ線形回帰===== Note: There are no missing values in the explanatory variables. alpha = 1.721914 beta = 3.476663 y_error = 0.7766452769398361 ④ ベイズ線形回帰により補完. =====ベイズ線形回帰===== alpha = 2.567708 beta = 4.346280 X_error = 0.024276115858132833, 0.2436076829842362 y_error = 0.7147843731641442 最尤推定とベイズ線形回帰の$y$に関する誤差が割と整合しているので,結果は正しそうです. またy_errorを比較すると,平均で補完 > そのまま削除 > ベイズ線形回帰 > complete dataとなっているため,期待された結果が得られています.さらにX_errorをみると,ベイズ線形回帰は平均値による補完と比較して優れていることがわかります. 今回は欠損の生じていないテストデータに対する予測により評価しましたが,本手法は欠損が生じているデータに対してもある程度の精度で予測が可能です.これは他の手法と比べると大きなアドバンテージです. 一方で,生成したデータによってはベイズ線形回帰の優位性がそれほど明らかでなかった場合がありました.特にデータ数が多かったりノイズが小さかったりするときには,欠損している行を削除してしまってもよい結果が得られます.そのため,本手法が効果を発揮するのは「入出力がある程度の相関を持ちつつ,ノイズも大きいような場合」であるといえそうです. まとめ 今回は欠損のある入力をベイズ的に補完しつつ,欠損値の推定,ハイパーパラメータの調整,新しい入力に対する予測を行うクラスを実装しました.数値実験から,実装が正しそうであることとベイズ線形回帰の効果を確認しました.本手法のメリットとしては, ・情報量増加による予測精度の向上 ・訓練データへの前処理の自動化 ・欠損のあるデータに対する予測能力 があります.特に最後の点に関しては,その他の手法に比べ非常に大きな利点であるといえます. 一方で,デメリットとしては ・導出,実装が非常に面倒であること ・サンプリングのために計算量が増加すること が挙げられます.前者に関しては,欠損値を埋めたいという強いモチベーションがない限り,割に合わないというのが正直な感想です(導出・実装するのに丸二日かかった).後者に関しては,事後分布を解析的に書けないためどうしても避けられないと考えます(もしかしたらできるのかもしれませんが...たかが線形回帰と思っていましたが,意外と複雑になるようです). 今回は最も扱いやすいMCARにフォーカスしましたが,MARの場合は欠損が生じる確率をモデル化することにより同様の解析が可能になります.きちんとは読んでいませんが, https://bookdown.org/marklhc/notes_bookdown/missing-data.html がそのような内容になっていると思います.時間があれば,MARの場合にも同様の実装をするつもりです. また,平均値による補完と欠損値の削除がそれぞれ有利になるのはどのような状況なのかが少し気になりました.データ数やノイズの大きさ,変数間の相関に依存すると思いますが,理論的な解析もできるのかもしれません.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む