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

残プロ 第-11回 ~ラズパイに朝礼を教え込む~

孤独という伝染病 プログラマーとは孤独なものです.ひたすらにコードと向き合い,読み,書き,苦悩する. 毎朝早くに通勤(または通学)し,夜遅くまで大量のコードを編集.疲労困憊で帰路につき信号の赤にふと足を止めた瞬間,  「あの条件処理してない」 頭をよぎるエラーコード... 現代社会がかかえる『孤独という病』をプログラマーは生来,背負って生きています.責任の所在がはっきりするプログラミングという世界において,我々を支えているのは常に自分とエラーに対する不安だけです.仕事で疲れた体を労ってくれるパートナー,今日一日の出来事を楽しそうに喋ってくれる可愛い娘がいたとしても,頭を支配するのは数百行のエラーコード. まして,そんな恵まれた環境すら築けていない私のような人間に残された孤独への(ささやかな)抵抗は,RaspberryPiを人格化することでした. RaspberryPiとかいう生意気な後輩 RaspberryPi(通称ラズパイ)をご存知ですか?科学教育や産業,研究に利用されたりと多岐にわたる分野で活躍しているシングルボードコンピュータです. あなたがプログラミングや電子工作に興味をお持ちなら,自分用のラズパイが1つぐらい家にあるでしょう.ここで,少し思い出してほしいのですが,最初のラズパイを購入し初めて触れた時,どのような感情を抱きましたか?きっと「こんなに小さいのに賢くて偉い」という愛情や,「これで色んなことができるぞ」といった将来に対する希望を見出したのではないでしょうか. では,果たして現在もあの時と同じ感情といえますか?きっとラズパイができること,できないこと,様々な制約を知り,失望や怒りが湧いた方が少なくないと思います.あの頃RaspberryPiに対して向けていた『娘に対する愛情』は,今や『生意気な後輩に対する苛立ち』へと変化しました. RaspberryPiの寿命 ラズパイは短く見積もるとおおよそ2年ほどで寿命を迎えるようです(大抵はSDカードの方が先に逝きますが).私が今回使用するラズパイは,半年ほど前に購入したもので,生後6カ月と言ってもいいでしょう. これ,人間の年齢に換算すると20歳前後なんですよね.高校卒業したての生意気な後輩って年.購入後3カ月とかなるともう社会も知らないクソガキってレベル.人生の先輩として年上への敬意を教えてやる必要がありますね... 挨拶のできる後輩へ 後輩としてまず身に着けるべきは気持ちのいい挨拶でしょう. hello.py message = ("おはようございます!!") print(message) ラズパイを教育するには,直接指導する($ python3 hello.py)方法と,脳に叩き込む(crontab -e)方法があります.毎度指導するのも面倒なので,今回は後輩君の脳内に直接書き込みましょう. terminal $ crontab -e cron 0 7 * * * python3 /home/pi/Documents/hello.py # 書き方は[分 時 日 月 年 コマンド] terminal おはようございます!! これで後輩君は毎朝7時に挨拶できるようになりました. 意味のある朝礼を 毎朝おはようと言われても鬱陶しいだけなので,朝礼の内容を決めましょう.今回は, その日の天気・温度 直近の予定 新型コロナウイルス感染者数の変化 を報告してもらいます. また,朝礼はリモートで行ってもらいましょう. これらの実装にはwebスクレイピング,csv操作,matplotlibでのグラフ作成,LineNotifyでの通知を利用しています.それぞれ,かなり簡素ではありますが解説をしているので,興味ある方はご覧になってください. やればできる子,後輩君 ここまでできれば上出来でしょう! これで鬱屈な朝を後輩君の気持ちいい朝礼とともに迎えられます!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitHub APIを使ってPRのCreateからMergeまでの時間を取得する

やりたい事 レビュー待ちで、作業が滞ることがあり、改善の第一歩としてPRの時間を見える化する。 見える化の為に、PRのCreateからMergeまでの時間を取得するツールを作成する。 制限事項 最終的には、期間を指定して、その期間内の平均値を出すようにするが、この記事ではPR指定で時間を取得している。 取得する期間は営業日を考慮したものが好ましいが、この記事では考慮していない。 ツール 下記のGitHub APIを使ってPRの情報を取得する。 https://api.github.com/repos/{owner}/{repo}/pulls/{pull_number} 上記のレスポンスには、PRの作成された日時とマージされた日時が含まれるため、その差を計算する事で、時間を取得しています。 また、プライベートリポジトリにも対応できるように、Personal access tokensも設定できるようにしました。 ツールについては、昔少しかじったPythonで作成してみました。 GetElapsedTimeOfPr.py import urllib.request import json import ssl import datetime import sys argvs = sys.argv url = argvs[1] if len(argvs) > 2: token = argvs[2] else: token = '' ssl._create_default_https_context = ssl._create_unverified_context req = urllib.request.Request(url) if len(token) > 0: req.add_header('Authorization', 'token {}'.format(token)) with urllib.request.urlopen(req) as res: res_str = res.read().decode('utf-8') json_str = json.loads(res_str) KEY_TITLE = 'title' KEY_CREATED_AT = 'created_at' KEY_MERGED_AT = 'merged_at' print('title:{}'.format(json_str[KEY_TITLE])) print('created_at:{} '.format(json_str[KEY_CREATED_AT])) print('merged_at:{}'.format(json_str[KEY_MERGED_AT])) TIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ' created_dt = datetime.datetime.strptime(json_str[KEY_CREATED_AT], TIME_FORMAT) merged_dt = datetime.datetime.strptime(json_str[KEY_MERGED_AT], TIME_FORMAT) elapsed_time = merged_dt - created_dt print('Elapsed time:{}h'.format(elapsed_time.total_seconds() // 60)) 使い方 python3 GetElapsedTimeOfPr.py https://api.github.com/repos/{owner}/{repo}/pulls/{pull_number} {token} 第一引数:取得対象のURL {owner}:GitHubのアカウント名 {repo}:リポジトリ名 {pull_number}:PR番号 参考:https://docs.github.com/ja/rest/reference/pulls#get-a-pull-request 第二引数:トークン {token}:プライベートリポジトリの場合はトークンを設定してください。 参考:https://docs.github.com/ja/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NO.4 ルービックキューブの回転記号をえる

ルービックキューブの回転記号をえる 複数の記事になります。  ルービックキューブの色を取得して kociembaを利用して 最後は回転記号を取得します。 NO.1 カメラ起動+BGR取得 NO.2 ウィンドウに取得した色を表示 NO.3取得したいろが 何色なのか 区別する kociembaを使います 参考、こちらはkociiemba ルービックキューブの色を取得して kociembaを使えば 回転記号を得られます 参考、こちらは回転記号 kociembaをわかりやすくしてくれてます kociembaの使い方として  こちらでも 説明されてる 通り ルービックキューブを 展開してかんがえる必要があります。 ルービックキューブを展開するとこんなかんじ (日本配色と世界配色でルービックキューブの色の配置が違うので注意してください *これは世界配色) 緑が正面だとすると 緑:F(front)  赤:L(left)  橙:R(right) 黄:U(up)    白:D(down)  青:B(back) になります まづは 色を 記号(F,L,R,U,D,B)にしたあと 数字のように並べ替える必要があります U0~8 R9~17 F18~26 D27~35 L36~44 B45~53 ルービックキューブの回転記号をえる 考え方? 各色9個のブロックがあり それが6面あります なので 全部で 54個の色を取得するひつようがります。 No.3の記事で色の取得はおわってますので 'red'とかの部分をkociemba用の記号(F,L,R,U,D,B)に書き換えて 下の result_listにねじこみます。 まづ 54個の空リストをつくり そこに インデックスの0-8に黄色、9-17に橙をいれていけばいいのです result_list = [[] for i in range(54) ] #[[],[],[],,,,,,,,,[],[]] #これで空のリストが 54個作成されます。 サンプルコード(kociembaの記号にする 説明) #取得した色↓ color_key = ['L','U','F', 'B','U','B', 'B','U','U'] result_list = [[] for i in range(54) ] #取得した色を指定の場所へねじこむ(インデックスで指定) #0~8 if color_key[4] == 'U':#黄色 result_list[0:9] = color_key #9~17 elif color_key[4] == 'R':#橙(オレンジ) result_list[9:18] = color_key #18~26 elif color_key[4] == 'F':#緑 result_list[18:27] = color_key #27~35 elif color_key[4] == 'D':#(白) result_list[27:36] = color_key #36~44 elif color_key[4] == 'L':#赤 result_list[36:45] = color_key #45~53 elif color_key[4] == 'B':#青 result_list[45:54] = color_key print(result_list) #['L', 'U', 'F', 'B', 'U', 'B', 'B', 'U', 'U', #0-8 [], [], [], [], [], [], [], [], [],#9-17 [], [], [], [], [], [], [], [], [],#18-26 [], [], [], [], [], [], [], [], [],#27-35 [], [], [], [], [], [], [], [], [],#36-44 [], [], [], [], [], [], [], [], []]#44-53 どんなに回しての中心の色がかわることは ないので 中心の色に合わせて追加する 場所をきめています 中心が黄色ならresult_listのインデックスが0-8番目 中心が橙(オレンジ)ならresult_listのインデックスが9-17番目 サンプルコード(kociembaの記号に変える) import cv2 import time capture = cv2.VideoCapture(2) capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1080) # 横幅 capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) # 縦幅 result_list = [[] for i in range(54) ] color_index =[[0,0,0],[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0]] color_key = ['black','black','black', 'black','black','black', 'black','black','black'] colors_BGR = { 'L': ([5, 20, 160], [75, 90, 189]), #red 6 4 'F': ([25, 130, 0], [100, 180, 30]), #green 3 2 'D': ([130, 130, 130], [255, 255, 255]), #white 2 3 'B': ([140, 80, 0], [255, 180, 50]), #blue 5 5 'R': ([5, 91, 190], [130, 150, 255]), #orange 4 1 'U': ([5, 150, 150], [110, 200, 200]) #yellow 1 0 } #Y縦 X横 frame_xy = [[300, 300],[300, 450],[300, 600], [450, 300],[450, 450],[450, 600], [600, 300],[600, 450],[600, 600]] x_start = [280, 430, 580, 280, 430, 580, 280, 430, 580] y_start = [280, 280, 280, 430, 430, 430, 580, 580, 580] x_end = [320, 470, 620, 320, 470, 620, 320, 470, 620] y_end = [320, 320, 320, 470, 470, 470, 620, 620, 620] x_start2 = [30, 80, 130, 30, 80, 130, 30, 80, 130] y_start2 = [30, 30, 30, 80, 80, 80, 130, 130, 130] x_end2 = [70, 120, 170, 70, 120, 170, 70, 120, 170] y_end2 = [70, 70, 70, 120, 120, 120, 170, 170, 170] while True: ret, frame = capture.read() for k, (xs, ys, xe, ye, xs2, ys2, xe2, ye2) in enumerate(zip (x_start, y_start, x_end, y_end, x_start2, y_start2, x_end2, y_end2)): cv2.rectangle(frame, (xs, ys), (xe, ye), (255, 255, 255), 2) b1 = color_index[k][0] g1 = color_index[k][1] r1 = color_index[k][2] cv2.putText(frame, str(k), (xs2, ys2), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,0), 2) if color_key[k] == 'L': b1, g1 ,r1 = 0, 0, 255 elif color_key[k] == 'F': b1, g1 ,r1 = 0, 255, 0 elif color_key[k] == 'D': b1, g1 ,r1 = 255, 255, 255 elif color_key[k] == 'B': b1, g1 ,r1 = 255, 0, 0 elif color_key[k] == 'R': b1, g1 ,r1 = 0, 128, 255 elif color_key[k] == 'U': b1, g1 ,r1 = 0, 255, 255 #左上に 色を表示する cv2.rectangle(frame, (xs2, ys2), (xe2, ye2), (int(b1), int(g1), int(r1)), -1) #BGRの取得位置 for i, (x, y) in enumerate(frame_xy): pixel = frame[x, y] b = pixel[0] g = pixel[1] r = pixel[2] BGR = [b,g,r] color_index[i] = BGR #colors_BGRの範囲内に 真上のBGRがはいってるか 確認 for color, ( lower, upper) in colors_BGR.items(): if lower[0] < BGR[0] and BGR[0] < upper[0]: if lower[1] < BGR[1] and BGR[1] < upper[1]: if lower[2] < BGR[2] and BGR[2] < upper[2]: color_key[i] = color color_index[i] = BGR if color_key[i] != 'black': #print(color_key) #print(color_index) pass #TAB 取得した色をリストに追加 key3 = cv2.waitKey(1) if key3 == 9: #0~8 if color_key[4] == 'U': result_list[0:9] = color_key #9~17 elif color_key[4] == 'R': result_list[9:18] = color_key #18~26 elif color_key[4] == 'F': result_list[18:27] = color_key #27~35 elif color_key[4] == 'D': result_list[27:36] = color_key #36~44 elif color_key[4] == 'L': result_list[36:45] = color_key #45~53 elif color_key[4] == 'B': result_list[45:54] = color_key print(result_list) time.sleep(0.3) cv2.imshow('camra',frame) if cv2.waitKey(1) & 0xFF == ord('q'): break capture.release() cv2.destroyAllWindows() サンプルコード(kociembaの記号に変える) を実行 中心の色が黄色なので 0-8番目に追加されています。 ちなみに ずっとこの処理をするのも どうなのってことなので TABキーをおしたら 追加するようにしています。 #TAB 取得した色をリストに追加 key3 = cv2.waitKey(1) if key3 == 9: kociembaを使う #python3 pip3 install kociemba ターミナルでこれすれば できるはずです。だめなら一番上ほうのサイトで確認してみてください 大きくコードはかえてませんが import cv2 import time import kociemba capture = cv2.VideoCapture(0) capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1080) # 横幅 capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) # 縦幅 result_list = [[] for i in range(54) ] color_index =[[0,0,0],[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0]] color_key = ['black','black','black', 'black','black','black', 'black','black','black'] colors_BGR = { 'L': ([5, 20, 160], [75, 90, 189]), #red 6 4 'F': ([25, 130, 0], [100, 180, 30]), #green 3 2 'D': ([130, 130, 130], [255, 255, 255]), #white 2 3 'B': ([140, 80, 0], [255, 180, 50]), #blue 5 5 'R': ([5, 91, 190], [130, 150, 255]), #orange 4 1 'U': ([5, 150, 150], [110, 200, 200]) #yellow 1 0 } #Y縦 X横 frame_xy = [[300, 300],[300, 450],[300, 600], [450, 300],[450, 450],[450, 600], [600, 300],[600, 450],[600, 600]] x_start = [280, 430, 580, 280, 430, 580, 280, 430, 580] y_start = [280, 280, 280, 430, 430, 430, 580, 580, 580] x_end = [320, 470, 620, 320, 470, 620, 320, 470, 620] y_end = [320, 320, 320, 470, 470, 470, 620, 620, 620] x_start2 = [30, 80, 130, 30, 80, 130, 30, 80, 130] y_start2 = [30, 30, 30, 80, 80, 80, 130, 130, 130] x_end2 = [70, 120, 170, 70, 120, 170, 70, 120, 170] y_end2 = [70, 70, 70, 120, 120, 120, 170, 170, 170] while True: ret, frame = capture.read() for k, (xs, ys, xe, ye, xs2, ys2, xe2, ye2) in enumerate(zip (x_start, y_start, x_end, y_end, x_start2, y_start2, x_end2, y_end2)): cv2.rectangle(frame, (xs, ys), (xe, ye), (255, 255, 255), 2) b1 = color_index[k][0] g1 = color_index[k][1] r1 = color_index[k][2] cv2.putText(frame, str(k), (xs2, ys2), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,0), 2) if color_key[k] == 'L': b1, g1 ,r1 = 0, 0, 255 elif color_key[k] == 'F': b1, g1 ,r1 = 0, 255, 0 elif color_key[k] == 'D': b1, g1 ,r1 = 255, 255, 255 elif color_key[k] == 'B': b1, g1 ,r1 = 255, 0, 0 elif color_key[k] == 'R': b1, g1 ,r1 = 0, 128, 255 elif color_key[k] == 'U': b1, g1 ,r1 = 0, 255, 255 #左上に 色を表示する cv2.rectangle(frame, (xs2, ys2), (xe2, ye2), (int(b1), int(g1), int(r1)), -1) #BGRの取得位置 for i, (x, y) in enumerate(frame_xy): pixel = frame[x, y] b = pixel[0] g = pixel[1] r = pixel[2] BGR = [b,g,r] color_index[i] = BGR #colors_BGRの範囲内に 真上のBGRがはいってるか 確認 for color, ( lower, upper) in colors_BGR.items(): if lower[0] < BGR[0] and BGR[0] < upper[0]: if lower[1] < BGR[1] and BGR[1] < upper[1]: if lower[2] < BGR[2] and BGR[2] < upper[2]: color_key[i] = color color_index[i] = BGR if color_key[i] != 'black': #print(color_key) #print(color_index) pass #TABキー key3 = cv2.waitKey(1) if key3 == 9: #0~8 if color_key[4] == 'U': result_list[0:9] = color_key #9~17 elif color_key[4] == 'R': result_list[9:18] = color_key #18~26 elif color_key[4] == 'F': result_list[18:27] = color_key #27~35 elif color_key[4] == 'D': result_list[27:36] = color_key #36~44 elif color_key[4] == 'L': result_list[36:45] = color_key #45~53 elif color_key[4] == 'B': result_list[45:54] = color_key print(result_list) time.sleep(0.3) # @キー  key4 = cv2.waitKey(1) if key4 == 64: result = ''.join(result_list) result = kociemba.solve(result) print(result) cv2.imshow('camra',frame) if cv2.waitKey(1) & 0xFF == ord('q'): break capture.release() cv2.destroyAllWindows() 追加したコード import kociemba #@キー  key4 = cv2.waitKey(1) if key4 == 64: result = ''.join(result_list) result = kociemba.solve(result) print(result) result_list = ['R', 'F', 'D', 'L', 'U', 'U', 'F', 'B', 'L', #0-8 U 中心が黄色 'F', 'L', 'B', 'U', 'R', 'U', 'B', 'D', 'L', #9-17 R 中心がオレンジ 'U', 'D', 'D', 'R', 'F', 'B', 'R', 'D', 'L', #18-26 F 中心が緑 'U', 'F', 'U', 'D', 'D', 'L', 'D', 'L', 'F', #27-35 D 中心が白 'D', 'F', 'R', 'R', 'L', 'U', 'L', 'R', 'B', #36-44 L 中心が赤 'R', 'R', 'F', 'F', 'B', 'B', 'U', 'B', 'B'] #45-53 B 中心が青 #@キー key4 = cv2.waitKey(1) if key4 == 64: result = ''.join(result_list) result = kociemba.solve(result) print(result) 全部のキューブの色を取得して@キーをおすと  kociemba.solve()で 下の回転記号がでてきます U' D2 R2 U' B2 L2 B' U' F R' B L2 B2 R2 D B2 D' L2 U2 F2 L2
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ipadで個人サーバー内のjupyter-notebookに接続する [Juno connect設定方法]

ipadで個人サーバー内のjupyter-notebookに接続する方法をここで紹介します。 サーバーに公開鍵認証方式で接続する際の設定が面倒だったので、ここで紹介できればと思います。 これができれば、外出先などでjupyter-notebookをいじる必要ができた場合などに非常に便利です。 ※サーバーに接続できる端末は紛失しないようにしましょう。 公開鍵の作成 Termius をダウンロード、generating keysで公開鍵、秘密鍵の作成(RSA, 2048) (「編集する」マークを押すと秘密鍵が見える) 公開鍵をサーバーに登録 秘密鍵をコピーし、クリップボードにある状態にする。 次にJuno connectで鍵の登録を選ぶ ここでクリップボードからを選択 鍵の作成の際に入力したパスワードを入力する 接続先の情報を入力する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RPM ファイルのバイナリを読んで構造を知る

概要 この記事は、以下を目標としています。 RPM ファイルには何が書かれているのかを調べます。バイナリエディタで *.rpm を開いて、どの辺りに何が書かれているのかを探ります 簡単な Python スクリプトでヘッダをダンプしてみます RPM パッケージの依存関係で provides name が使われているのを見ます とりあえず、以下のダンプを見るだけで雰囲気はわかると思います。 参照したドキュメント 自分が見つけられた中では、Linux Standard Base の以下のドキュメントが一番参考になりました。 Linux Standard Base Core Specification, Generic Part - 25.2. Package File Format (2015 年) https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/pkgformat.html RPM 公式の以下のドキュメントは図が乗っていてわかりやすいのですが、書きかけで止まっている感じです。 Description of RPM file format https://rpm.org/devel_doc/file_format.html 古いドキュメントのようですが、上よりは以下のほうが詳しそうでした。 RPM File Format http://ftp.rpm.org/max-rpm/s1-rpm-file-format-rpm-file-format.html RPM ファイル形式の解説記事です。 Argh-P-M! – Dissecting the RPM file format https://xyrillian.de/thoughts/posts/argh-pm.html 使用するサンプルファイル Remi リポジトリより、以下の rpm ファイルを例として使用します。 php70-php-pecl-imagick-3.4.4-17.el7.remi.x86_64.rpm このパッケージを選んだのはたまたまです。たまたま、このパッケージの依存関係を調べていた時に、とったメモをベースにして、本記事を作成しました。 以下のコマンドでパッケージの情報が得られます。 $ rpm -qip php70-php-pecl-imagick-3.4.4-17.el7.remi.x86_64.rpm Name : php70-php-pecl-imagick Version : 3.4.4 Release : 17.el7.remi Architecture: x86_64 Install Date: (not installed) Group : Unspecified Size : 522499 License : PHP Signature : DSA/SHA1, Mon 22 Feb 2021 06:07:07 PM JST, Key ID 004e6f4700f97f56 Source RPM : php70-php-pecl-imagick-3.4.4-17.el7.remi.src.rpm Build Date : Mon 22 Feb 2021 06:06:05 PM JST Build Host : builder.remirepo.net Relocations : (not relocatable) Packager : Remi Collet Vendor : Remi's RPM repository <https://rpms.remirepo.net/> URL : https://pecl.php.net/package/imagick Bug URL : https://forum.remirepo.net/ Summary : Extension to create and modify images using ImageMagick Description : Imagick is a native php extension to create and modify images using the ImageMagick API. Package built for PHP 7.0 as Software Collection (php70 by remi). 4つのセクション rpm ファイルは、4つのセクションに分かれています。 Lead セクション - 基本情報 Signature セクション - 署名 Header セクション - パッケージの詳細情報 Payload セクション - パッケージに含まれるファイル 2番めの Signature セクションと 3番目の Header セクションは、同じ形式 (Header Structure) で書かれています。 最初の Lead Section から見ていきます。 Lead Section - 基本情報 +---+---+---+---+---+---+---+---+---+---+ |M1 |M2 |M3 |M4 |MAJ|MIN| TYPE | ARCH | (more ->) +---+---+---+---+---+---+---+---+---+---+ +~~~~~~~~~~~~~~~~~~+---+---+ | 66 bytes of NAME |OS |SIG| (more ->) +~~~~~~~~~~~~~~~~~~+---+---+ +~~~~~~~~~~~~~~~~~~~~~~+ | 16 bytes of RESERVED | +~~~~~~~~~~~~~~~~~~~~~~+ 例: 00000000: edab eedb 0300 0000 0001 7068 7037 302d ..........php70- 00000010: 7068 702d 7065 636c 2d69 6d61 6769 636b php-pecl-imagick 00000020: 2d33 2e34 2e34 2d31 372e 656c 372e 7265 -3.4.4-17.el7.re 00000030: 6d69 0000 0000 0000 0000 0000 0000 0000 mi.............. 00000040: 0000 0000 0000 0000 0000 0000 0001 0005 ................ # name ここまで + 0001 + 0005 00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000060: Magic Number ed ab ee db で始まる Version 0300 - RPM ファイルフォーマットのバージョン 3.0 TYPE 0000 - バイナリが 0、ソースが 1 ARCH 0001 - x86 architecture は 1 NAME (66bytes): php70-php-pecl-imagick-3.4.4-17.el7.remi + 00* OS 0001 - Linux は 1 SIG 0005 - signature type 5 (現在使われているのは 5) RESERVED 00 * 16 バイト OS と SIG は図だと 1 バイトのように書かれていますが、2バイトずつあります。 Signature セクション - 署名 この Signature セクションと、次の Header セクションは、両方とも Header Structure という形式で書かれています。 Header Structure 形式は、 ヘッダ - インデックスの個数とデータのサイズが書かれています インデックス (* N個) - データストア内の位置、内容(タグ名)、形式、サイズが書かれています データ (* N個) - インデックスから参照されるデータが格納されています という3部構成になっています (ヘッダがいくつも出てきてわかりにくいですね……)。 Signature セクションのヘッダ +---+---+---+---+---+---+---+---+---+---+---+---+ |HM1|HM2|HM3|VER| RESERVED | INDEXCOUNT | (more ->) +---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+===============+============+ | STORESIZE | Index Entries | Data Store | +---+---+---+---+===============+============+ 例: 00000060: 8ead e801 0000 0000 0000 0007 0000 0140 ...............@ Magic Number 8e ad e8 で始まる VER 1 - バージョン1 RESERVED 4バイト - 0 で埋める INDEXCOUNT 4 バイト - 0000 0007 Index エントリが 7 個ある STORESIZE 4 バイト - 0000 0140 Data Store の長さは 320 バイト Signature セクションのインデックスとデータ 1つのインデックスは固定長で 16 バイトあります。上の INDEXCOUNT にある通り、7 個のインデックスが含まれています。 +---+---+---+---+---+---+---+---+---+---+---+---+ | TAG | TYPE | OFFSET | (more ->) +---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+ | COUNT | +---+---+---+---+ 例: 00000070: 0000 003e 0000 0007 0000 0130 0000 0010 ...>.......0.... # 1つ目のインデックス 00000080: 0000 010b 0000 0007 0000 0000 0000 0077 ...............w # 2つ目のインデックス 00000090: 0000 010d 0000 0006 0000 0077 0000 0001 ...........w.... # 3つ目のインデックス 000000a0: 0000 03e8 0000 0004 0000 00a0 0000 0001 ................ # 4つ目のインデックス 000000b0: 0000 03ec 0000 0007 0000 00a4 0000 0010 ................ # 5つ目のインデックス 000000c0: 0000 03ed 0000 0007 0000 00b4 0000 0077 ...............w # 6つ目のインデックス 000000d0: 0000 03ef 0000 0004 0000 012c 0000 0001 ...........,.... # 7つ目のインデックス 000000e0: それぞれのタグの定義は、このドキュメント にある程度リストアップされています。(全てではありません) 1 つ目のインデックスとデータ 00000070: 0000 003e 0000 0007 0000 0130 0000 0010 ...>.......0.... # 1つ目のインデックス 1つ目のインデックスの内容は以下の通り。 TAG 3e - 62: RPMTAG_HEADERSIGNATURES TYPE 7 - RPM_BIN_TYPE バイナリ OFFSET 130 - 304 バイト目 (データは一番最後の位置にある) COUNT 10 - 16 バイト 各インデックスは、後続のデータストア内のデータを指しています。 インデックスとデータは、同じ順序で並んでいるとは限りません。この 1 つ目のインデックスは、データストアの最後の項目を指しています。 OFFSET 304 バイト + COUNT 16 バイトで、合計 320 バイト。ヘッダに書かれていたデータストアのサイズ STORESIZE 320 バイトと一致しています。 TAG と TYPE により、データストアの中身が RPMTAG_HEADERSIGNATURES というバイナリであることがわかります。 インデックスが指しているデータの中身は以下の通り。 # 開始位置 e0 + オフセット 130 = 210 00000210: 0000 003e 0000 0007 ffff ff90 0000 0010 ...>............ RPMTAG_HEADERSIGNATURES の内容は、ドキュメントによると、 The signature tag differentiates a signature header from a metadata header, and identifies the original contents of the signature header. とのことですが、いまいちよくわかりませんでした。。 ffff ff90 の部分以外はインデックスと一致するので、Signature セクションの終わりを示しているのかもしれません。 2 つ目のインデックスとデータ 00000080: 0000 010b 0000 0007 0000 0000 0000 0077 ...............w # 2つ目のインデックス TAG 10b - 267: RPMSIGTAG_DSA TYPE 7 - RPM_BIN_TYPE バイナリ OFFSET 0 - 0 バイト目 COUNT 77 - 119 バイト 2つ目のインデックスに相当するデータは以下の通り。これがデータストアの最初のデータです。 # 開始位置 e0 + オフセット 0 = e0 000000e0: 8875 0400 1102 0035 1621 041e e04c ce88 .u.....5.!...L.. 000000f0: a4ae 4aa2 9a5d f500 4e6f 4700 f97f 5605 ..J..]..NoG...V. 00000100: 0260 3374 3b17 1c72 706d 7340 6661 6d69 .`3t;..rpms@fami 00000110: 6c6c 6563 6f6c 6c65 742e 636f 6d00 0a09 llecollet.com... 00000120: 1000 4e6f 4700 f97f 5609 f300 9f63 1f04 ..NoG...V....c.. 00000130: 49ca bfee 1ff7 2281 a07c b90b 342a c8c0 I....."..|..4*.. 00000140: bf00 9f4d 91aa e114 dc79 6ba9 8650 e7f4 ...M.....yk..P.. 00000150: 8854 89e1 cdfb 4163 3530 3863 3966 6364 .T....Ac508c9fcd # 41 まで 中身は、Header セクションの DSA 署名とのこと。 3 つ目のインデックスとデータ 00000090: 0000 010d 0000 0006 0000 0077 0000 0001 ...........w.... # 3つ目のインデックス TAG 10d - 269: RPMSIGTAG_SHA1 TYPE 6 - RPM_STRING_TYPE 文字列 OFFSET 77 - 119 バイト目 COUNT 1 - 文字列は NULL で終端する可変長の値 COUNT が 1 となっています。文字列 (RPM_STRING_TYPE) は NULL で終端し、サイズは 1 とだけ書かれるようです。データを読まないと、文字列の長さはわかりません。 # 開始位置 e0 + オフセット 77 = 157 00000150: 8854 89e1 cdfb 4163 3530 3863 3966 6364 .T....Ac508c9fcd 00000160: 3439 6530 6332 3735 6334 3532 6163 3531 49e0c275c452ac51 00000170: 3132 6334 3932 3735 6537 3832 3839 3600 12c49275e782896. # SHA1 文字列 + NULL 中身は Header セクションの SHA1 チェックサムです。値は文字列で c508c9fcd49e0c275c452ac5112c49275e782896 となっています。 4、5 つ目のインデックスとデータ 000000a0: 0000 03e8 0000 0004 0000 00a0 0000 0001 ................ # 4つ目のインデックス 000000b0: 0000 03ec 0000 0007 0000 00a4 0000 0010 ................ # 5つ目のインデックス 4つ目 TAG 3e8 - 1000: RPMSIGTAG_SIZE TYPE 4 - RPM_INT32_TYPE OFFSET a0 - 160 バイト目 COUNT 1 - 1 個 5つ目 TAG 3ec - 1004: RPMSIGTAG_MD5 TYPE 7 - RPM_BIN_TYPE バイナリ OFFSET a4 - 164 バイト目 COUNT 10 - 16 バイト RPMSIGTAG_SIZE は、この後に続く、Header セクションと Payload セクションの合計サイズで、RPMSIGTAG_MD5 は、その MD5 チェックサムです。 4つ目、5つ目のインデックスに相当するデータは以下の通り。 # 開始位置 e0 + オフセット a0 = 180 00000180: 0001 fe90 64f3 99f8 9d9d 524c 34f0 bca8 ....d.....RL4... # SIZE \x1fe90 00000190: 27f0 99df # MD5 64f399f89d9d524c34f0bca827f099df Header + Payload の合計サイズは \x1fe90 (130,704 バイト) あることがわかります。 これに \x220 (544 バイト) を足すと、rpm ファイルのサイズ 131,248 バイトに一致することが確認できます。544 バイトは Lead + Signature セクションのサイズです。\x220 から Header セクションが開始します。 Header + Payload セクションの MD5 は以下のコマンドで確認できます。冒頭の 544 バイトを飛ばして、以降の md5 を計算します。 # Mac で実行 $ dd if=php70-php-pecl-imagick-3.4.4-17.el7.remi.x86_64.rpm bs=1 skip=544 | md5 130704+0 records in 130704+0 records out 130704 bytes transferred in 0.235784 secs (554338 bytes/sec) 64f399f89d9d524c34f0bca827f099df 6 つ目のインデックスとデータ TAG 3ed - 1005: RPMSIGTAG_GPG TYPE 7 - RPM_BIN_TYPE バイナリ OFFSET b4 - 180 バイト目 COUNT 77 - 119 バイト データは以下の通り。 # 開始位置 e0 + オフセット b4 = 194 00000190: 27f0 99df 8875 0400 1102 0035 1621 041e '....u.....5.!.. # \x88 から 000001a0: e04c ce88 a4ae 4aa2 9a5d f500 4e6f 4700 .L....J..]..NoG. 000001b0: f97f 5605 0260 3374 3b17 1c72 706d 7340 ..V..`3t;..rpms@ 000001c0: 6661 6d69 6c6c 6563 6f6c 6c65 742e 636f famillecollet.co 000001d0: 6d00 0a09 1000 4e6f 4700 f97f 56c7 3500 m.....NoG...V.5. 000001e0: 9f65 7edd 2d28 d66f e9ec bbb7 d11f e72e .e~.-(.o........ 000001f0: 808e 0e6c cd00 9e2f ad0e d80d fd50 e2d0 ...l.../.....P.. 00000200: 656b a17f 0c58 acc8 d86a 5800 0008 025c ek...X...jX....\ #\x58 まで 中身は Header + Payload セクションの DSA 署名とのこと。 7 つ目のインデックスとデータ 000000d0: 0000 03ef 0000 0004 0000 012c 0000 0001 ...........,.... # 7つ目のインデックス TAG 3ef - 1007: RPMSIGTAG_PAYLOADSIZE TYPE 4 - RPM_INT32_TYPE OFFSET 12c - 300 バイト目 (上の OFFSET + COUNT は 299。アライメントで1バイト空いてる?) COUNT 1 - 1 個 内容は Payload セクションの、非圧縮状態のサイズです。 # 開始位置 e0 + オフセット 12c = 20c 00000200: 656b a17f 0c58 acc8 d86a 5800 0008 025c ek...X...jX....\ # \x0008025c Payload を展開したサイズ \x0008025c (524,892 バイト) は、rpm2cpio コマンドで確認できます。 $ rpm2cpio php70-php-pecl-imagick-3.4.4-17.el7.remi.x86_64.rpm > tmp.cpio $ ls -l tmp.cpio -rw-r--r-- 1 koseki koseki 524892 May 3 22:16 tmp.cpio Header セクション - パッケージの詳細情報 Signature セクションと同様、Header セクションも Header Structure 形式で記述されます。 8e ad e8 で始まるヘッダがあり、インデックスが複数個あり、インデックスから参照されるデータストアが続きます。 Header セクションには、パッケージの各種情報が書かれています。 パッケージ名 バージョン ライセンス 依存関係 スクリプトでダンプする 例で使用している rpm パッケージには、Header セクションに 70 個のデータが含まれていました。さすがに手動で読むには多すぎたので、Python スクリプトを書いてダンプしました。 dump-rpm-headers.py https://gist.github.com/koseki/9737a4490128bcb8b426c22a0b7c3885 ダンプ結果 https://gist.github.com/koseki/5e5386c99b1454bcacfca7b832ab35d0 Python の struct モジュール を使うことで、固定長のヘッダやインデックスは簡単に読むことができました。可変長のデータストアを読むには、もう少し面倒なコーディングが必要でした。 依存関係 Header セクションの中で、依存関係がどのように表現されているかを見てみます。 この RPM パッケージが提供する機能 (capability) が、 RPMTAG_PROVIDENAME RPMTAG_PROVIDEFLAGS RPMTAG_PROVIDEVERSION に書かれています。 [ 29] RPMTAG_PROVIDENAME b'config(php70-php-pecl-imagick)' b'php70-php-imagick' b'php70-php-imagick(x86-64)' b'php70-php-pecl(imagick)' b'php70-php-pecl(imagick)(x86-64)' b'php70-php-pecl-imagick' b'php70-php-pecl-imagick(x86-64)' b'scl-package(php70)' [ 50] RPMTAG_PROVIDEFLAGS 268435464 8 8 8 8 8 8 32768 [ 51] RPMTAG_PROVIDEVERSION b'3.4.4-17.el7.remi' b'3.4.4' b'3.4.4' b'3.4.4' b'3.4.4' b'3.4.4-17.el7.remi' b'3.4.4-17.el7.remi' b'' この RPM パッケージが何に依存しているかが、 RPMTAG_REQUIRENAME RPMTAG_REQUIREFLAGS RPMTAG_REQUIREVERSION に書かれています。 [ 31] RPMTAG_REQUIRENAME b'/bin/sh' b'/bin/sh' b'/bin/sh' b'config(php70-php-pecl-imagick)' b'libMagickCore-6.Q16.so.7()(64bit)' b'libMagickWand-6.Q16.so.7()(64bit)' b'libc.so.6()(64bit)' b'libc.so.6(GLIBC_2.14)(64bit)' b'libc.so.6(GLIBC_2.2.5)(64bit)' b'libc.so.6(GLIBC_2.3.4)(64bit)' b'libc.so.6(GLIBC_2.4)(64bit)' b'php70-php(api)' b'php70-php(zend-abi)' b'php70-runtime' b'php70-runtime(remi)(x86-64)' b'rpmlib(CompressedFileNames)' b'rpmlib(FileDigests)' b'rpmlib(PayloadFilesHavePrefix)' b'rtld(GNU_HASH)' b'rpmlib(PayloadIsXz)' [ 30] RPMTAG_REQUIREFLAGS 256 288 4352 268435464 16384 16384 16384 16384 16384 16384 16384 8 8 16384 0 16777226 16777226 16777226 16384 16777226 [ 32] RPMTAG_REQUIREVERSION b'' b'' b'' b'3.4.4-17.el7.remi' b'' b'' b'' b'' b'' b'' b'' b'20151012-64' b'20151012-64' b'' b'' b'3.0.4-1' b'4.6.0-1' b'4.0-1' b'' b'5.2-1' フラグはバージョンの大小比較 (>=, <=, ==, etc.) を表しています。 (25.2.4.4.2. Package Dependencies Attributes) RPM には、「ファイル名」や「パッケージ名」の他に、Provides Name と呼ばれる名前があることがわかります。これは、そのパッケージが提供する機能 (capability) に名前を付けた「仮想パッケージ名」です。 ファイル名: php70-php-pecl-imagick-3.4.4-17.el7.remi.x86_64.rpm パッケージ名: php70-php-pecl-imagick-3.4.4-17.el7.remi (Lead セクション name) php70-php-pecl-imagick (RPMTAG_NAME) 仮想パッケージ名 (Provides Name): config(php70-php-pecl-imagick) php70-php-imagick php70-php-imagick(x86-64) php70-php-pecl(imagick) php70-php-pecl(imagick)(x86-64) php70-php-pecl-imagick php70-php-pecl-imagick(x86-64) scl-package(php70) Provides Name は /bin/sh のような名前がついている場合があります。 Requires Name に、パッケージ名や Provides Name を列挙することで、RPM の依存関係が成り立っています。 依存関係を調べるコマンド RPM ファイルに含まれる Provides Name は以下のコマンドで調べることができます。 $ rpm -q --provides -p php70-php-pecl-imagick-3.4.4-17.el7.remi.x86_64.rpm config(php70-php-pecl-imagick) = 3.4.4-17.el7.remi php70-php-imagick = 3.4.4 php70-php-imagick(x86-64) = 3.4.4 php70-php-pecl(imagick) = 3.4.4 php70-php-pecl(imagick)(x86-64) = 3.4.4 php70-php-pecl-imagick = 3.4.4-17.el7.remi php70-php-pecl-imagick(x86-64) = 3.4.4-17.el7.remi scl-package(php70) Requires Name は、以下のコマンドで調べられます。 $ rpm -q --requires -p php70-php-pecl-imagick-3.4.4-17.el7.remi.x86_64.rpm /bin/sh /bin/sh /bin/sh config(php70-php-pecl-imagick) = 3.4.4-17.el7.remi libMagickCore-6.Q16.so.7()(64bit) libMagickWand-6.Q16.so.7()(64bit) libc.so.6()(64bit) libc.so.6(GLIBC_2.14)(64bit) libc.so.6(GLIBC_2.2.5)(64bit) libc.so.6(GLIBC_2.3.4)(64bit) libc.so.6(GLIBC_2.4)(64bit) php70-php(api) = 20151012-64 php70-php(zend-abi) = 20151012-64 php70-runtime php70-runtime(remi)(x86-64) rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1 rtld(GNU_HASH) rpmlib(PayloadIsXz) <= 5.2-1 yum search コマンドは Provides Name にはヒットしないようです。代わりに、yum provides コマンドを使います。 $ yum provides 'config(php70-php-pecl-imagick)' : php70-php-pecl-imagick-3.4.4-10.el7.remi.x86_64 : Extension to create and modify images using ImageMagick Repo : remi-safe Matched from: Provides : config(php70-php-pecl-imagick) = 3.4.4-10.el7.remi : RPM ファイルの URL は以下のようにして検索できます。 $ yumdownloader --urls 'config(php70-php-pecl-imagick)' : http://rpms.remirepo.net/enterprise/7/safe/x86_64/php70-php-pecl-imagick-3.4.4-17.el7.remi.x86_64.rpm Payload セクション - パッケージに含まれるファイル 最後の Payload セクションには、パッケージに含まれるファイルが、cpio アーカイブ形式 gzip 圧縮で格納されています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonエンジニア認定試験受験記

目的 pythonの実力を試したいなあと色々調べていたところオデッセイというところがやってるpythonの民間資格があることを知り受験を決意 https://cbt.odyssey-com.co.jp/pythonic-exam.html なるほど公式ドキュメントはpython3のオライリーのチュートリアル本なのね〜ふむふむ ・・・あの読みにくいやつね! というわけでせっかく勉強するので勉強した内容を残しておこうと思う。 ミュータブルとイミュータブルについて 文字列というのは基本イミュータブルなので書き換えができない 例えば以下のように最後の文字をTAMAOではなくTAMAEに変えたいなあという時 文字列はそのままでは置換できない 実行結果 words = 'My_Name_is_TAMAO.' words[-2] = 'E' --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-81-18312fda4fb1> in <module> 1 words = 'My_Name_is_TAMAO.' ----> 2 words[-2] = 'E' TypeError: 'str' object does not support item assignment これを置換するには単純にreplaceを使って置換する方法が一つ words = 'My_Name_is_TAMAO.' words.replace('TAMAO',TAMAE') 文字列を一旦空のリストに入れてやってから必要なところだけ置換して最終的にそれを連結させる方法がある words = 'My_Name_is_TAMAO.' word_list = [] for word in words: word_list.append(word) word_list[-2] = 'E' print(''.join(word_list)) いずれも出力は以下の通り 実行結果 'My_Name_is_TAMAE.' 最初の方のが簡単なのかなとは思うが、リストはミュータブルなので変更できるというお話である。 ※追記 因みにこういう時は先のコードをリスト内包表記で書くともっと簡単に書けることを思い出した。 words = 'My_Name_is_TAMAO.' word_list = [word for word in words] word_list[-2] = 'E' print(''.join(word_list)) ※追記2 そもそもリストに文字列を入れてやればforで回す必要すらないのでは?とユーザさんより教えていただく(@shiracamusさんどうもありがとうございます。) words = 'My_Name_is_TAMAO.' word_list = list(words) word_list[-2] = 'E' print(''.join(word_list)) 確かに!!!また一つ賢くなってしまった・・・ そもそもスライシング必要なところだけ抜き出してくっつけるだけで良いのではという話も頂きました。多分これが一番シンプルですね。文字列は編集できないだけで必要な箇所だけ抜き出して文字列同士を連結できないわけではないので。 words = 'My_Name_is_TAMAO.' print(words[:-2] +'E.') ラムダ式 正直これを書いている間もlambdaというのがよくわからない。 というかわからないのであまり私自身が使わないが多分使った方が楽な時もあるのだろう 例えばリスト内包表記とかは1~10までを足し合わせた結果を出力したい場合、こんな風にかけて便利とかはなんとなくわかるのだ sum(i for i in range(11)) 因みに以下のようにリストをsum関数に入れても結果は同じだが、そもそもsum関数はリストのようなイテラブル受け取れるイテレータなのでわざわざ先のようにリストを書かなくても良いという話だ sum([i for i in range(11)]) 話が逸れたがlambdaも似たようなもので関数を一々定義しなくても簡単にワンライナーでかけるというものだ。(そもそもlambda自体が無名関数と呼ばれているらしい) 例えば円周率求める関数を書いたとして import math def Circumference(r): return round(math.pow(r, 2) * math.pi, 2) Circumference(7) 実行結果は以下のようになる 実行結果 153.94 これと同じことをlambdaで書きたい場合はこうなる import math Circumference = lambda r: round(math.pow(r, 2) * math.pi, 2) Circumference(7) ※実行結果は同じ つまりlambdaで書くと関数のreturn文を省略して lambda 引数: リターンを省略した戻り値 という形になるということなのである ・・・なのだがいつも聞くとああそれそれと思うが直感的に使えるようには中々ならない 慣れなのだろうか
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonの@property

クラスの中で@propertyのデコレータを使うと、 属性として取り出すことが可能になります。 class Point: def __init__(self, x, y): self._x = x self._y = y @property def x(self): return self._x @property def y(self): return self._y @property def sum(self): return self._x + self._y point = Point(10, 20) print(point.x, point.y, point.sum) # 10 20 30 この状態で再代入しようとしてもエラーが出てしまいます。 xを15に置き換えようとします。 point.x = 15 すると下記のエラーがでます。 setterの設定 再代入でエラー表示させないためにsetterを設定していきます。 class Point: def __init__(self, x, y): self._x = x self._y = y @property def x(self): return self._x @x.setter def x(self, value): self._x = value @property def y(self): return self._y @y.setter def y(self, value): self._y = value @property def sum(self): return self._x + self._y point = Point(10, 20) print(point.x, point.y, point.sum) # 10 20 30 このように各プロパティでsetterを用意すると再代入が可能になります。 point.x = 15 print(point.x, point.y, point.sum) # 15 20 35 setterの設定を限定させる 例えば、0より大きい値しか入れさせたくない場合は 下記のようにsetterを書いてあげます。 @x.setter def x(self, value): if value <= 0: raise ValueError(f'xは0より大きい値にしてください。入力値:{value}') self._x = value 全体のコードは下記になります。 class Point: def __init__(self, x, y): self._x = x self._y = y @property def x(self): return self._x @x.setter def x(self, value): if value <= 0: raise ValueError(f'xは0より大きい値にしてください。入力値:{value}') self._x = value @property def y(self): return self._y @y.setter def y(self, value): self._y = value @property def sum(self): return self._x + self._y ただし、この方法だとインスタンス時にエラーが表示されません。 point = Point(-10, 20) # 負の値を入れてもエラーにならない print(point.x, point.y, point.sum) インスタンス時にsetterを動かして入力制限をする方法を次に記載します。 インスタンス化時点で引数の制限を実装する方法 親クラスを作成して継承させるとインスタンス時の引数も判定してくれます。 class Meta: def __init__(self, x, y): self.x = x self.y = y class Point(Meta): def __init__(self, x, y): super().__init__(x, y) @property def x(self): return self._x @x.setter def x(self, value): if value <= 0: raise ValueError(f'xは0より大きい値にしてください。入力値:{value}') self._x = value @property def y(self): return self._y @y.setter def y(self, value): self._y = value @property def sum(self): return self._x + self._y このように設定した場合、インスタンス時の引数でxに負の値を渡すとエラーが生じます。 # こちらは問題なく動く point = Point(10, 20) print(point.x, point.y, point.sum) # 10 20 30 # 負の値を渡すとエラーになる point = Point(-10, 20) # ValueError: xは0より大きい値にしてください。入力値:-10 どのようなときにプロパティの設定をするのか? これは完全に自分で考えた内容なので、違うかもしれない前提で読んでいただきたいと思います。 プロパティを設定する場合は大きく2つあるかなと思います。 参照と変更が簡単に可能になる メソッドなどで指定しなくても変更したら自動計算されて出力が可能 2に関しては上記例題のsumの例です。 前提として、プロパティの設定をする際はsetterと一緒に使わないと意味がないです。 なぜなら@propertyを設定しなくてもコンストラクタの内容は 属性として取得可能だからです。 pandasの例だとDataFrame.columnsがわかりやすいと思います。 項目名を変えるときは下記のように変更が可能です。 print(df.columns) #これで項目名一覧の取得が可能 df.columns = ['new_col1', 'new_col2', 'new_col3'] 自分で作成しclassでも活用したいなと思いつつもどういうときに使用するかが 明確でなかったので、言語化してみました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JDLA E資格取得(ラビット★チャレンジ)

はじめに 2021年8月に実施される日本ディープラーニング協会(JDLA) E資格の受験を目標に「JDLA認定プログラム」のひとつであるStudy-AIの「ラビット★チャレンジ」を受講しており、そこで学習した内容についてまとめていきます。 日本ディープラーニング協会(JDLA) ラビット★チャレンジ E資格を受験するために必要なこと E資格を受験するには、JDLAが認定したディープラーニングに関するプログラム(講座)を受講し修了する必要があります。 JDLA認定プログラム なぜラビット★チャレンジなのか 簡潔に言うと「安いから」です。 と言うのも、少し前までは、認定プログラムを受講するのに数十万かかりましたが、最近になって低価格の講座も出てきてます。そのひとつが「ラビット★チャレンジ」です。(そろそろ★を打つのがめんどくさくなってきた。) ただし、安いのには理由があります。 基本的に講座は全てビデオ視聴のみで、講師への質問等は一切できません。 そのため、ある程度前提の知識があったり、わからないことを自分で調べることに慣れている人にはおすすめできますが、そうじゃない人にはあまりおすすめできません。 また、ラビット・チャレンジ以外にもスキルアップAIから同様のプログラムが提供されています。 スキルアップAI 勉強を進めるにあたって注意すること ちゃんと調べてなかったからなのかわかりませんが、申し込み後に受講できる期間に制限があることを知りました。(月額制だから特にないと思ってた) 基本的には、次回のE資格を受験するのを目標に勉強するため、短期集中型のプログラムとなってます。 受講しようと思ってる方は、勉強に十分な時間が充てられるか、今一度確認した方が良いと思います。 ブログを書く理由について 修了にはレポートを提出する必要があるのが一番の理由です。 あとは、備忘録と復習のため。 普通に書いても面白くないので、できるだけ噛み砕いて書くように努力します。 ただ、ここだけの話、だいぶ進んだ時点でレポートが必要なことに気づいて焦ってます。。 私の経歴について 高専卒。今は、車載関係で音のチューニングに関わる仕事をメインにしてます。 AIやディープラーニングについては、音声認識の性能評価で少し触れたことがある程度。 そこから「これからの自分の生きる道はこっちじゃないか?」と思い始めて(間違ってるかもしれないけど)いくつか研修を受けてきました。 そのため、全くの無知ではないですが、コードをゴリゴリ書くような仕事でもないのでほとんど素人同然です。 まとめ 見切り発車で始めた感は否めませんが、なんとかE資格が取れるように頑張っていきます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python仮想環境の構築方法

概要 この記事では、Pythonの仮想環境を構築する方法を簡潔にメモします。 構築後の仮想環境への入り方、抜け方、不要になった仮想環境の削除の方法も含みます。 前提条件 Visual Studio Code:1.56.2 ターミナルはWindows PowerShellを使用 方法 それでは、具体的な方法の説明に入ります。 途中でつまづかないように、できるだけ細かくステップ分けしています。 ①任意のディレクトリを選択 ターミナルを開き、仮想環境を構築したいディレクトリまで移動しておきます。 例:Documentsの直下に仮想環境ディレクトリを置くこととします。 ②仮想環境を構築 ターミナルで下記のコマンドを入力して、仮想環境を構築します。 例:仮想環境名はvenv_sampleとします。 PS C:Documents> python -m venv venv_sample コマンドが正しく動作していれば、下記のとおり選択したディレクトリの直下に仮想環境ディレクトリが作成されています。 例:C:Documents>venv_sample なお、作成された仮想環境ディレクトリは、次のような構成になっています。 Documents>  venv_sample>   Include>    (空のディレクトリ)   Lib>    site-packages>     (以下略)   Scripts>    (以下略) ③仮想環境に入る ③-1 Scriptsディレクトリに移動 ターミナルで下記のコマンドを入力して、Scriptsディレクトリに移動します。 PS C:Documents> cd ./venv_sample/Scripts コマンドが正しく動作していれば、ターミナルが下記の表示になります。 PS C:Documents\venv_sample\Scripts> ③-2 仮想環境を有効化 ターミナルで下記のコマンドを入力して、仮想環境を有効化します。 PS C:Documents\venv_sample\Scripts> .Activate.ps1 仮想環境が有効化されれば、ターミナルが下記の表示になります。 (venv_sample) PS C:Documents\venv_sample\Scripts> ④仮想環境から抜ける ターミナルで下記のコマンドを入力して、仮想環境から抜けます。 (venv_sample) PS C:Documents\venv_sample\Scripts> deactivate 仮想環境から抜けられれば、ターミナルが下記の表示になります。 PS C:Documents\venv_sample\Scripts> ⑤仮想環境を削除 仮想環境を削除するには、作成した仮想環境ディレクトリC:Documents>venv_sampleを削除します。 まとめ この記事では、Pythonの仮想環境の構築の仕方を簡潔にまとめました。 素人のメモですので、誤りや不足の点があるかもしれませんがご容赦ください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Matplotlib定量比較-散布図

散布図とは 散布図は2つの変数に対して、x-y座標上に点をプロットしたグラフです。 散布図で2つの変数の関係性が見えます。 簡単な散布図 import matplotlib.pyplot as plt fig, ax = plt.subplots() ax.scatter(data['col01'], data['col02']) ax.set_xlabel('Col01') ax.set_ylabel('Col02') plt.show() 色で第3次元を表現する 色を使用して、時間を第3次元として表現できます。 下図では早い日付を暗い色、遅い日付を明るい色で表現しています。 import matplotlib.pyplot as plt fig, ax = plt.subplots() ax.scatter(data['col01'], data['col02'], c=data['date']) ax.set_xlabel('Col01') ax.set_ylabel('Col02') plt.show()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでフォルダのファイル名を小文字(大文字)にする方法

フォルダに格納されているファイルが大文字小文字混在のファイル名だったので、これを小文字に統一したくPythonでちょこっとプログラムをかいたのでメモ。 import os # OS操作 import glob # パスのパターン(ワイルドカードなど)を使用するため path = './cards/*.png' # cardsフォルダ内のpngファイルを指定 # パス指定のファイル名を取得して出力してみる file_list = glob.glob(path) print(file_list) for file in file_list: # ファイル名を大文字→小文字変換(大文字変換の時はupper()を使用) os.rename(file, file.lower()) # ファイル名が変更されているか確認 file_list = glob.glob(path) print(file_list) 悩み 小文字の「png」と大文字の「PNG」を両方取得するにはどうしたらいいんだろう?誰かPythonの賢者教えて。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python 有理数のp進展開を計算する

実装準備 今回実装したアルゴリズムです。 $\alpha = \displaystyle\frac{1}{15},\ p = 5$ (1) $p$ べきと $p$ の因子を持たない部分に分ける $\displaystyle \alpha = \frac{1}{3} \cdot 5^{-1}$ (2) $p$ 因子を持たない部分を ${\rm mod}\ p$ で考える $\displaystyle \frac{1}{3} \equiv 2 \ ({\rm mod} \ 5)$ (3) 式変形をする $ \displaystyle \alpha = \frac{1}{3} \cdot 5^{-1} = 2 \cdot 5^{-1} + \left(\frac{1}{3} - 2 \right) \cdot 5^{-1} \\ \displaystyle \ \ = 2 \cdot 5^{-1} + \left(-\frac{5}{3} \right) \cdot 5^{-1} = 2 \cdot 5^{-1} + \left(-\frac{1}{3} \right) $ (4) (1) ~ (3) を繰り返す $ \displaystyle \alpha = \frac{1}{3} \cdot 5^{-1} = 2 \cdot 5^{-1} + \left(\frac{1}{3} - 2 \right) \cdot 5^{-1} \\ \displaystyle \ \ = 2 \cdot 5^{-1} + (-\frac{5}{3}) \cdot 5^{-1} = 2 \cdot 5^{-1} + \left(-\frac{1}{3} \right) \\ \displaystyle \ \ = 2 \cdot 5^{-1} + (-\frac{1}{3}) = 2 \cdot 5^{-1} + 3 \cdot 5^0 + \left(-\frac{1}{3} - 3 \right) \cdot 5^0 \\ \displaystyle \ \ = 2 \cdot 5^{-1} + 3 \cdot 5^0 + \left(-\frac{2}{3} \right) \cdot 5^1 = \cdots $ 実装 こちらに置きました。 import math def get_num_divisible(n, p): i = 1 while n % p ** i == 0: i += 1 return i - 1 def get_inv_elem(n, p): if n % p == 0: return False for i in range(1, p): if i * n % p == 1: return i def get_p_adic(frac, p, prec=10): if frac[1] == 0: return False index_dict = {} p_pow = get_num_divisible(frac[0], p) - get_num_divisible(frac[1], p) i = p_pow while i < prec: if frac[0] == 0: index_dict[i] = 0 i += 1 continue p_pow = get_num_divisible(frac[0], p) - get_num_divisible(frac[1], p) if p_pow == i: if p_pow > 0: beta = [int(frac[0] / p ** p_pow), frac[1]] elif p_pow < 0: beta = [frac[0], int(frac[1] / p ** - p_pow)] else: beta = frac x = get_inv_elem(beta[1], p) a = beta[0] * x % p index_dict[i] = a frac = [int((beta[0] - a * beta[1]) * p ** p_pow), beta[1]] else: index_dict[i] = 0 i += 1 return index_dict def convert_to_formula(index_dict, p): if not index_dict: return False f = '' for k, v in index_dict.items(): if v > 1: f += str(v) + '*' + str(p) + '^' + str(k) + ' + ' elif v == 1: f += str(p) + '^' + str(k) + ' + ' elif v == 0: pass f += f'O({p}^{list(index_dict.keys())[-1] + 1})' return f if __name__ == '__main__': p = 3 prec = 10 frac = [2, 5] index_dict = get_p_adic(frac, p, prec) f = convert_to_formula(index_dict, p) print('p = {}, alpha = {}/{} のp進展開:\n {}'.format(p, frac[0], frac[1], f)) 実行例 素数 $p$ と 有理数 $\alpha$ について、$\alpha$ の $\mathbb{Q}_p$ における $p$ 進展開を求めてみます。 実行例1 $p = 3,\ \alpha = \displaystyle \frac{2}{5}$ p = 3, alpha = 2/5 のp進展開: 3^0 + 3^1 + 2*3^2 + 3^3 + 3^5 + 2*3^6 + 3^7 + 3^9 + O(3^10) PARI/GP gp > 2/5 + O(3^10) %24 = 1 + 3 + 2*3^2 + 3^3 + 3^5 + 2*3^6 + 3^7 + 3^9 + O(3^10) 実行例2 $p = 5,\ \alpha = \displaystyle \frac{1}{15}$ p = 5, alpha = 1/15 のp進展開: 2*5^-1 + 3*5^0 + 5^1 + 3*5^2 + 5^3 + 3*5^4 + 5^5 + 3*5^6 + 5^7 + 3*5^8 + 5^9 + 3*5^10 + 5^11 + O(5^12) PARI/GP gp > 1/15 + O(5^12) %25 = 2*5^-1 + 3 + 5 + 3*5^2 + 5^3 + 3*5^4 + 5^5 + 3*5^6 + 5^7 + 3*5^8 + 5^9 + 3*5^10 + 5^11 + O(5^12) 実行例3 $p = 7,\ \alpha = \displaystyle \frac{49}{5}$ p = 7, alpha = 49/5 のp進展開: 3*7^2 + 7^3 + 4*7^4 + 5*7^5 + 2*7^6 + 7^7 + 4*7^8 + 5*7^9 + 2*7^10 + O(7^11) 処理時間: 0.0 PARI/GP gp > 49/5 + O(7^11) %26 = 3*7^2 + 7^3 + 4*7^4 + 5*7^5 + 2*7^6 + 7^7 + 4*7^8 + 5*7^9 + 2*7^10 + O(7^11) PARI/GP 今回比較に使用した PARI/GP は数論の計算ソフトです。 x + O(p^k) で p 進数の近似値を求めることができます。 gp > 1/15 + O(5^10) %19 = 2*5^-1 + 3 + 5 + 3*5^2 + 5^3 + 3*5^4 + 5^5 + 3*5^6 + 5^7 + 3*5^8 + 5^9 + O(5^10) 上の画像のように SageMath の CoCalc を使って Python で PARI/GP を使うことができます。 以下の赤枠で囲ったアイコンをクリックします。 「Run CoCalc Now」をクリックします。 「SageMath」をクリックします。 参考資料 PARI/GPの公式ホームページです。 こちらのスライドがわかりやすく解説されております。 コマンドはこちらのリファレンスにまとめられています。 こちらの記事は非常に細かく、かつ丁寧に整理されています。 Qiita にも丁寧に解説されているのを見つけました。 こちらにはSageMathについて詳しく書かれています。 SageMath のクイックリファレンスです。コマンドが整理されていて見やすいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gitの内部データ構造をGraphvizで描画してみた 第4回 ワークツリーとインデックスとblob

解決すべき問題 git addコマンドやgit commitコマンドを実行したときGitレポジトリのなかで何がおきているのだろう?図で説明してほしい。 解決方法 Pythonでツール kazurayam/visualize_git_repository.py を開発した。これを使えばいま自分の手元にあるプロジェクトの .git ディレクトリのなかにあるオブジェクト群の実物を読み出し、Graphvizでグラフを生成してPNG画像ファイルを出力することができる。 説明 デモ用にディレクトリを作りファイルを3つ作ろう。git initコマンドでGitレポジトリを作ろう。git addしたらインデクスが更新されblobオブジェクトが作られる。そしてgit commitしたらblobがレポジトリに登録される。このときGitレポジトリの中で何が起きているのだろうか?Graphvizで図示してみよう。 おまけに応用問題をひとつ。ファイルを修正してgit addしたあと、git commitせずに続けてもう一度ファイルを修正してgit addしたとしよう。Gitレポジトリの中で何が起きるのだろうか? Graphvizで図示してみよう。 ステップ1 git initした後でgit addする前 デモ用のディレクトリを作ろう。このディレクトリのことを以下で $project という記号で表すことにします。 % mkdir $project % cd $project このディレクトリのなかにファイルを3つ作ろう。 % echo '*~' > .gitignore % echo '#Read me plase' > README.md % mkdir src % echo 'print("How do you do?");' > src/greeting.pl このディレクトリにGitレポジトリを作ろう。 % git init シェルコマンド ls で $projectディレクトリの内容を確認しよう。 % ls -la . total 16 drwxr-xr-x 6 kazuakiurayama staff 192 6 12 12:58 . drwxr-xr-x 4 kazuakiurayama staff 128 6 12 12:58 .. drwxr-xr-x 7 kazuakiurayama staff 224 6 12 12:58 .git -rw-r--r-- 1 kazuakiurayama staff 3 6 12 12:58 .gitignore -rw-r--r-- 1 kazuakiurayama staff 17 6 12 12:58 README.md drwxr-xr-x 3 kazuakiurayama staff 96 6 12 12:58 src $project/.gitというディレクトリができている。.gitディレクトリがいわゆる「Gitレポジトリ」の実体だ。 .gitディレクトリの中に何があるのだろうか?lsコマンドでみてみよう。 % ls -la ./.git total 16 drwxr-xr-x 7 kazuakiurayama staff 224 6 12 12:58 . drwxr-xr-x 6 kazuakiurayama staff 192 6 12 12:58 .. -rw-r--r-- 1 kazuakiurayama staff 23 6 12 12:58 HEAD -rw-r--r-- 1 kazuakiurayama staff 137 6 12 12:58 config drwxr-xr-x 5 kazuakiurayama staff 160 6 12 12:58 hooks drwxr-xr-x 4 kazuakiurayama staff 128 6 12 12:58 objects drwxr-xr-x 4 kazuakiurayama staff 128 6 12 12:58 refs $project/.git/objectsというディレクトリができている。このなかにcommitオブジェクト、treeオブジェクト、blobオブジェクトのファイルが作られて保存される。 この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 Git用語「インデックス」とは具体的には $project/.git/index という名前のバイナリファイル1個である。ところがこの段階ではgit initした直後なのでindexファイルはできていない。indexファイルはgit addコマンドで作られるのだ。 $project/.git/objectsディレクトリはできているがまだ中身が空っぽだ。git initした直後なので無理もない。 ステップ2 git addしたらインデックスとblobが更新された git addコマンドを実行しよう。何が起こるだろうか? % git add この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 3つのファイルに対応するblobオブジェクトが.git/objectsディレクトリの中にひとつづつ計3個できた。 インデックスが作られた。インデックスの中には3行あって、ファイルのパスとそれに対応するblobオブジェクトのhash値が記録されている。 ワークツリーのなかにsrcというディレクトリがある。ところがインデックスのなかにはディレクトリに対応する行が無い。ワークツリーのなかのファイルに相当する行だけがインデックスのなかにある。サブディレクトリの下にあるファイルのパス文字列としてサブディレクトリがあることが示唆されている。たとえばsrc/greeting.plのように。 ワークツリーのなかにsrcというディレクトリがある。ところが.git/objectsのなかにはそれに対応するモノがない。 git addコマンドは2つの仕事をするのだ。第一にワークツリーにあるファイルを変換してblobオブジェクトを作りだすこと。第二にインデックスを更新すること。 git addコマンドはtreeオブジェクトを作らない。treeオブジェクトを作りだすのはgit commitコマンドの役割なのだ。 ステップ3 git commitしたらblobがツリーにつながった さあ、コミットしよう。 % git commit -m "initial commit" この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 .git/objectsディレクトリのなかにcommitオブジェクトとtreeオブジェクトができた。commitからtreeへ線がつながり、treeからblobへ線がつながった。追加した3つのファイルのblobオブジェクトはひとつ残らずcommitから参照可能な形になった。 ワークツリーのsrc ディレクトリに相当する行がインデックスのなかには無かった。ところが.git/objectsディレクトリのなかにはsrcに相当するtreeオブジェクトができている。git commitコマンドが実行されたとき、インデックスに記録されていたsrc/greeting.plというファイルパス文字列に基づいてtreeオブジェクトが生成されたのだ。 インデックスの内容に変化は無い。git commitコマンドがインデックスを変更しないことがわかる ステップ4 TODO.txtファイルを追加してgit addする前 ワークツリーにsrc/TODO.txtファイルを追加しよう。 % mkdir doc % echo 'Sleep well tonight.' > doc/TODO.txt まだ git add しない。この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 ワークツリーでファイルをどんなにいじってもgit addしないうちはGitレポジトリになんら影響を及ぼさない。 ステップ5 git addしたらインデックスとblobが更新された ではgit addしよう。 % git add . この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 ワークツリーに追加されたsrc/TODO.txtファイルに対応するblobオブジェクトが作られた。 そのblobがインデックスに含まれるようになった。 addされたblobオブジェクトはtreeオブジェクトと線でつながっていない。孤立している。 ステップ6 git commitしたらblobがツリーにつながった ではgit commitしよう。 % git commit -m "add src/TODO.txt" この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 doc/TODO.txtファイルに対応するblobオブジェクトがほかのtreeオブジェクトと線でつながった。つまりdoc/TODO.txtファイルがコミットされた。 これでひと仕事済んだ。 ステップ7 READMEファイルを修正してgit addした 応用問題をやろう。すなわちワークツリーにすでにあるREADME.mdファイルを修正し、git addしよう。Gitレポジトリのなかで何が変化するだろうか? まずREDME.mdファイルを修正しよう。 % echo 'Read me more carefully' > README.md git addしよう。 % git add . この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 修正した後のREADME.mdファイルに対応するblobオブジェクト(hash=5a79541)が追加された。 インデックスをみるとREADME.mdファイルのhashは新しいblobオブジェクトのhashに交換されている。 修正される前のREADME.mdファイルに対応するblobオブジェクト(hash=aadb69a)は.git/objectsディレクトリの中に残っている。いったんcommitされたblobは原則的に削除されないのだ。このblobは過去のcommitオブジェクトからリンクされていて履歴として参照可能だ。 ステップ8 READMEファイルをもう一度修正してgit addした READMEファイルを修正してgit addしたあとふつうならgit commitするところだがここでひとつ実験しよう。もう一度READMEファイルを修正してgit addしたら、インデックスやblobオブジェクトはどういう状態になるのだろうか? READMEファイルを修正しよう。 % echo 'I know you didnt read me.' > README.md addしよう。 % git add . この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 修正されたREADME.mdファイルに対応する新しいblobオブジェクト(hash=9230643)ができた。インデックスが新しいblobを参照するように更新された。 前回git addしたとき作られたblobオブジェクト(hash=5a79541)は.git/objectsディレクトリに残っている。ただしこのblobは完全に孤立している。すなわちtreeオブジェクトと線でつながっていないし、インデックスともつながっていない。 ステップ9 READMEファイルをgit commitした コミットして締めくくろう。 % git commit -m "modified README.md"` この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 commitオブジェクトがひとつ増えた。最新のcommitオブジェクトから線が伸びて最新状態のREADME.mdファイルのblobが参照される形になった。 addされたがcommitされなかったblobオブジェクト(hash=5a79541)は放置されている。 三度目のコミットが完了した。めでたしめでたし。 ゴミ掃除 孤立したblobオブジェクトはこの先どうなる運命なのでしょうか? じつはGitはGarbage Collectionを備えていて、適切なタイミングで自動的にゴミを掃除してくれる。 git gc ここでは深堀りしません。 ツールについて 本稿で示したPNG画像は自作のツール visualize_git_repository で描画した。このツールはPython言語で開発した。ソースコードは下記のGitHubレポジトリにある。 https://github.com/kazurayam/visualizing-git-repository このツールは下記2つのライブラリを利用している。 pytest python graphviz PNG画像を生成するにはコマンドラインで下記の操作をする。 $ cd $visualize_git_repository $ pytest -s kazurayam/visualize_git_repository.py::test_4_index 上記の例を作るのにどういうgitコマンドを実行したのかを知りたいならプログラムのソースコードを読み解いてください。下記を入り口として解読してください。 kazurayam/visualize_git_repository.py まとめ git addとやったとき、git commitとやったときGitレポジトリの中でデータがどのように変化していくのかをクリアに図示することができたとおもいます。 author: kazurayam date: June, 2021 連作の目次 第1回 commitとtreeとblob 第2回 ブランチとマージ 第3回 タグ 第4回 ワークツリーとインデックスとblob
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JupyterNotebookでファイルを作成しようとするとpermission deniedとなる

はじめに 環境作成をしていると権限関係でエラーが生じることがたまにあります。 権限関係のエラーは解決が難しいイメージがあります。 本日は、JupyterNotebook (JupyterLab)で権限関係のエラーがでて、解決しましたのでまとめたいと思います。 環境 windows 10 Docker Desktop 3.3.0 利用したDockerfileはこちら 問題 Dockerを利用して、JupyterNotebook (JupyterLab)の環境を作成して、アクセスしました。 そして新規ファイル(.ipynb)を作成したところ以下のようなエラーが出ました。 JupyterNotebook JupyterLab permission deniedとあるとおり権限が付与されていないことによるエラーです。 また、このエラーはDocker imageをdocker runして起動したときに起きるもので、docker-compose upでコンテナを起動したときには出ませんでした。 調査 まずはDockerのコンテナの中に入り、以下のコマンドを入れます。 $ ls -la このコマンドについてはこちらを確認してください。 すると、以下のように表示されます。 今回、JupyterNotebook (JupyterLab)を利用しているディレクトリは、最後から2行目のdataというところですが、権限がrootになっています。 Jupyterを利用する場合、jovyanにする必要があります。これがエラーの原因でした。 解決方法 方法1. Dockerfileの見直し Dockerfileで./dataというディレクトリを作成しているところを確認すると、 FROM jupyter/datascience-notebook # Setting jupyter notebook environment. RUN rmdir work ARG password=not_password # install libraries ENV GRANT_SUDO=yes USER root (途中省略) RUN mkdir ./data # jupyter config COPY ./settings/jupyter_notebook_config.py .jupyter/ rootユーザーでディレクトリを作成していました。 このユーザーをjovyanに変更して、dataディレクトリを作成します。 FROM jupyter/datascience-notebook # Setting jupyter notebook environment. RUN rmdir work ARG password=not_password # install libraries ENV GRANT_SUDO=yes USER root (途中省略) # ユーザー変更 ENV GRANT_SUDO=no USER jovya RUN mkdir ./data # upyter config COPY ./settings/jupyter_notebook_config.py .jupyter/ すると、無事成功しました。 このDockerfileに関してはこちらのリポジトリで確認できます。 方法2. コマンドで権限を変更する まず、コンテナの中に入ります。 $ docker exec -it [コンテナ名] sh そのあと、権限を変更します。 # sudo chown -R [変更するユーザー名] [変更するディレクトリorファイル] $ sudo chown -R jovyan data このコマンドはこちらを参考にしました。 パスワードが求められるのでいれてください。 私の場合は、Dockerfileでパスワードを設定しています。 FROM jupyter/datascience-notebook # Setting jupyter notebook environment. RUN rmdir work ARG password=not_password (省略) パスワードはnot_passwordといれました。 password: (not_password ただし表示はされない) userからjovyanに変更されました。 ただしこの方法ではdocker runするたびに変更が必要になるので、Dockerfileで権限変更の処理をします。 Dockerfileを以下のように変更します。 FROM jupyter/datascience-notebook # Setting jupyter notebook environment. RUN rmdir work ARG password=not_password # install libraries ENV GRANT_SUDO=yes USER root (途中省略) RUN mkdir ./data # jupyter config COPY ./settings/jupyter_notebook_config.py .jupyter/ # 権限変更 RUN chown -R jovyan data これでファイルが作成できるようになりました。 おわりに 今回は権限の対処をしました。最近権限で同じようなことをしていたのでうまく対処ができました。 動かなかったらとりあえず確認してみるのは大切です。 参考記事 lsコマンドの使い方と覚えたい15のオプション【Linuxコマンド集】 Anacondaインストール後のJupyterの設定: ブラウザとパスワードの設定・nbextensionsとその他拡張機能の追加・設定
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Sphinxでrecommonmarkを使用する際の問題と対策

概要 recommonmarkを使うと、Sphinxは全てのドキュメントを毎回ビルドし直します そもそも当該エクステンションはdeprecated1なエクステンションなので、myst-parserを使う方法を調べました 目次 全てのドキュメントを毎回ビルドし直す問題 myst-parserの使用方法 myst-parserをインストールする conf.pyを編集する makeする 全てのドキュメントを毎回ビルドし直す問題 recommonmarkを使用すると、全てのドキュメントが毎回再生成される現象が発生します。2 recommonmarkの公式ドキュメントがconf.pyに以下の★の記述を追加することを推奨していることが原因です conf.py # for Sphinx-1.4 or newer extensions = ['recommonmark'] # for Sphinx-1.3 from recommonmark.parser import CommonMarkParser source_parsers = { '.md': CommonMarkParser, ★ ① } source_suffix = ['.rst', '.md'] conf.py # At top on conf.py (with other import statements) import recommonmark from recommonmark.transform import AutoStructify # At the bottom of conf.py def setup(app): app.add_config_value('recommonmark_config', { 'url_resolver': lambda url: github_doc_root + url,  ★ ② 'auto_toc_tree_section': 'Contents', }, True) app.add_transform(AutoStructify) ①/②の設定はSphinxのキャッシュ(environment.pickle)に保存することができません。3 このため、Sphinxはビルドの度に「conf.pyが変化した」と判断し、全てのドキュメントをビルドしなおします。 この問題を回避するために、myst-parserを使用することにしました myst-parserの使用方法 myst-parserに乗り換えることで問題を回避できました。使用方法は以下の通り。 myst-parserをインストールする $ python3 -m pip install myst-parser conf.pyを編集する conf.py extensions = ['myst_parser'] makeする $ make html https://github.com/readthedocs/recommonmark ↩ https://github.com/readthedocs/recommonmark/issues/197 ↩ https://github.com/sphinx-doc/sphinx/blob/a55816deb546828db1383f2875be815bf805f858/sphinx/config.py#L46 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kaggleのタイタニックに挑戦してみた(その1)

はじめに 最近データサイエンスに関して勉強する機会があったので覚えた技術を忘れないように記事に書いておきます。 対象の事例としてKaggleチュートリアルのタイタニックを使います。 Kaggleみたいなちゃんとしたデータセットでデータ分析するのは初めてなので間違っている点は多々あると思います。 コード全体 作成したコードは以下です。 Kaggle notebook 1.データの確認 タイタニックのデータセットは、各乗客の情報を元に生き残ったかどうかを学習するデータセットです。 以下の3個のファイルが提供されています。 train.csv:学習用データ test.csv:予測データ gender_submission.csv:提出例 "gender_submission.csv" は女性=生存、男性=死亡で予測したファイルで、これを提出するとスコアは 0.76555 になります。(パーセントなので正解率77%ですね) まずは、train.csv にどんなデータが入っているか確認してみます。 データの読み込みは以下です。 import pandas as pd from matplotlib import pyplot as plt # グラフ表示用 import seaborn as sns # グラフ表示用 df_train = pd.read_csv(os.path.join(os.path.dirname(__file__), "./titanic/train.csv")) df_test = pd.read_csv(os.path.join(os.path.dirname(__file__), "./titanic/test.csv")) target_column = "Survived" # 目的変数 random_seed = 1234 # 乱数固定用 1-1.カラム一覧と欠損値の確認 まずはどんなカラムがあるかと欠損値を確認します。 # 総データ数 print(len(df_train)) print(len(df_test)) # 欠損値の数を表示 print(df_train.isnull().sum()) print(df_test.isnull().sum()) 表示結果 891 # trainデータ数 418 # testデータ数 # train PassengerId 0 Survived 0 Pclass 0 Name 0 Sex 0 Age 177 SibSp 0 Parch 0 Ticket 0 Fare 0 Cabin 687 Embarked 2 dtype: int64 # test PassengerId 0 Pclass 0 Name 0 Sex 0 Age 86 SibSp 0 Parch 0 Ticket 0 Fare 1 Cabin 327 Embarked 0 dtype: int64 AgeとCabinはやたら欠損値が多いですね。 Embarked と Fare の欠損値は数が少ないのでデータを見てもいいかもしれません。 # Fareのデータを見る例 print(df_test[df_test["Fare"].isnull()].T) # .Tは記事として見やすいように転置 #出力結果 152 PassengerId 1044 Pclass 3 Name Storey, Mr. Thomas Sex male Age 60.5 SibSp 0 Parch 0 Ticket 3701 Fare NaN Cabin NaN Embarked S 1-2.各カラムの要約情報の確認 どういうデータが入っているか確認してみます。 # 要約情報 print(df_train.describe(include='all')) print(df_test.describe(include='all')) 記事だと見づらくなるので表示は一部のみにしています。 print(df_train[["PassengerId", "Pclass", "Sex", "Age"]].describe(include='all')) PassengerId Pclass Sex Age count 891.000000 891.000000 891 714.000000 unique NaN NaN 2 NaN top NaN NaN male NaN freq NaN NaN 577 NaN mean 446.000000 2.308642 NaN 29.699118 std 257.353842 0.836071 NaN 14.526497 min 1.000000 1.000000 NaN 0.420000 25% 223.500000 2.000000 NaN 20.125000 50% 446.000000 3.000000 NaN 28.000000 75% 668.500000 3.000000 NaN 38.000000 max 891.000000 3.000000 NaN 80.000000 なんとなく確認した箇所は以下です。 (数字データ)外れ値がないか(meanに対するminとmaxらへんを確認) (数字データ)データの散らばり具合(meanに対する50%(中央値)がどれだけ離れているか) (文字データ)カテゴリデータかどうか(countとuniqueの数) また合わせてuniqueな要素についても詳細を見ておきます。 for column in df_train.columns: # uniq情報を取得 uniq = df_train[column].unique() # 表示 print("{:20} unique:{:5} {}".format( column, len(uniq), uniq[:5], # 多すぎる場合があるので5件に抑える )) PassengerId unique: 891 [1 2 3 4 5] Survived unique: 2 [0 1] Pclass unique: 3 [3 1 2] Name unique: 891 ['Braund, Mr. Owen Harris' 'Cumings, Mrs. John Bradley (Florence Briggs Thayer)' 'Heikkinen, Miss. Laina' 'Futrelle, Mrs. Jacques Heath (Lily May Peel)' 'Allen, Mr. William Henry'] Sex unique: 2 ['male' 'female'] Age unique: 89 [22. 38. 26. 35. nan] SibSp unique: 7 [1 0 3 4 2] Parch unique: 7 [0 1 2 5 3] Ticket unique: 681 ['A/5 21171' 'PC 17599' 'STON/O2. 3101282' '113803' '373450'] Fare unique: 248 [ 7.25 71.2833 7.925 53.1 8.05 ] Cabin unique: 148 [nan 'C85' 'C123' 'E46' 'G6'] Embarked unique: 4 ['S' 'C' 'Q' nan] ここで確認したい点はunique数です。 count数とunique数が同じ場合は各データを区別するユニークIDな意味合いが強く、unique数が少ない場合はカテゴリなデータの可能性が高くなります。 ドメイン知識なしで各カラムを見た場合をまとめてみました。 カラム名 予測データ形式 どんなデータ? PassengerId ID count数=unique数なのでIDっぽい Survived 目的変数です。(生存したか)(これだけドメイン知識あり) Pclass カテゴリ unique 3なのでカテゴリっぽい(intなので数字で分類していそう) Name ID count数=unique数なのでIDっぽい Sex カテゴリ unique 2、データタイプが文字列なのでカテゴリ Age 小数 平均29で中央値28とばらつきはなさそう SibSp カテゴリ? unique 7と少し多いのでカテゴリかどうかは微妙 Parch カテゴリ? unique 7と少し多いのでカテゴリかどうかは微妙 Ticket ? unique 681 と全部違うわけでもない…。よくわからないですね Fare 小数 平均35、中央値31に対してmaxが512なので外れ値があるかも? Cabin ? 欠損値が多く、unique 148とかなりバラバラ Embarked カテゴリ unique 4、データタイプが文字列なのでカテゴリ 1-3.各カラムと目的変数との関係 各カラムに対して目的変数との関係を見てみます。 カラムはすべて見るわけではなく、上記まとめから気になった項目のみ見ています。 Survived まずは目的変数です。 print(df_train['Survived'].value_counts()) # 数 print(df_train['Survived'].value_counts(normalize=True)) # 割合 sns.countplot(x="Survived", data=df_train) plt.show() 0 549 1 342 Name: Survived, dtype: int64 0 0.616162 1 0.383838 Name: Survived, dtype: float64 カテゴリ系の表示 カテゴリ系は以下のようなコードで表示しています。(Pclassの例) def plot_category(df, column, target_column): # カウント情報 print(pd.crosstab(df[column],df[target_column])) print("各クラス毎の生存率") print(pd.crosstab(df[column],df[target_column], normalize='index')) print("生存率に対する各クラスの割合") print(pd.crosstab(df[column],df[target_column], normalize='columns')) # plot sns.countplot(df[column], hue=df[target_column]) plt.show() plot_count(df_train, "Pclass", "Survived") Survived 0 1 Pclass 1 80 136 2 97 87 3 372 119 各クラス毎の生存率 Survived 0 1 Pclass 1 0.370370 0.629630 2 0.527174 0.472826 3 0.757637 0.242363 生存率に対する各クラスの割合 Survived 0 1 Pclass 1 0.145719 0.397661 2 0.176685 0.254386 3 0.677596 0.347953 以下画像のみ表示します。 Sex 男性の死亡率、女性の生存率がかなり高いですね。 SibSp 0と1がほとんどの割合を占めていますね。 Parch 同じく、0,1,2がほとんどの割合を占めていますね。 Embarked Sが多いですね。 小数系データの表示 ヒストグラムを表示しています。 なんとなく全体のヒストグラムと生存者毎のヒストグラムを表示しています。 def plot_float(df, column, target_column, bins=20): # 全体plot sns.distplot(df[column], kde=True, rug=False, bins=bins) plt.show() # 目的変数毎のplot sns.distplot(df[df[target_column]==1][column], kde=True, rug=False, bins=bins, label=1) sns.distplot(df[df[target_column]==0][column], kde=True, rug=False, bins=bins, label=0) plt.legend() plt.show() # Age出力例 plot_float(df_train, "Age", target_name) kdeはカーネル密度推定というものらしく、ざっくりいうとヒストグラムを連続値にした場合のグラフです。 Age 10才以下あたりで生存率が高い点が気になりますね。 Fare 生存率毎のグラフのみ表示しています。 低いと死亡率が高く、高いと死亡率が低そうです。 2.学習データの作成(前処理) データが確認できたので次は学習用データを作成します。 まずは最低限の処理のみを実装し、改善は後回しにします。(とりあえず一連のフローを作ることを優先しています) また、この最低限の状態のスコアをベースとすることで改善されているかを比較する目的もあります。 2-1.欠損値の補填 欠損値の埋め方は、平均値や中央値がよく使われるようです。 また、Cabinは欠損値の割合が多いので今回は説明変数に含めない方向で対処します。 Age とりあえず中央値で埋めます。 また、欠損値を埋めたことを表すフラグを新規に作っています。 def missing_value(df): # 欠損値フラグ df["Age_na"] = df["Age"].isnull().astype(np.int64) # 欠損値を中央値で埋める df["Age"].fillna(df["Age"].median(), inplace=True) missing_value(df_train) # trainデータ missing_value(df_test) # testデータ Embarked 2件だけですので一番多かった S で補完します。 df_train["Embarked"].fillna("S", inplace=True) Fare こちらも1件だけですので中央値で補完します。 df_test["Fare"].fillna(df_test['Fare'].median(), inplace=True) 2-2.(正規化) データの単位を揃えて学習しやすくする方法です。 今回は正規化しませんがコードのみ書いておきます。 def normalization(df, name): # 正規化(不偏分散) df[name] = (df[name] - df[name].mean()) / df[name].std() #normalization(df_train, "Age") #normalization(df_train, "Fare") #normalization(df_test, "Age") #normalization(df_test, "Fare") 2-3.ダミー化 カテゴリなカラムに対してカテゴリ別にカラムを増やします。 (Onehot化と同じ変換です) 例えば以下の表をダミー化すると以下になります。 Sex 1 male 2 female 3 male ↓ ダミー化 Sex_male Sex_female 1 1 0 2 0 1 3 1 0 SibSpとParchのダミー化はとりあえずおいておきます。 def dummy(df): df = pd.get_dummies(df, columns=[ "Pclass", "Sex", #"SibSp", #"Parch", "Embarked", ]) return df df_train = dummy(df_train) df_test = dummy(df_test) 2-4.説明変数の選択 ここまで実行した後のカラムは以下です。 print(list(df_train.columns)) # ['PassengerId', 'Survived', 'Name', 'Age', 'SibSp', 'Parch', 'Ticket', 'Fare', 'Cabin', 'Age_na', 'Pclass_1', 'Pclass_2', 'Pclass_3', 'Sex_female', 'Sex_male', 'Embarked_C', 'Embarked_Q', 'Embarked_S'] この中から実際に学習で使う説明変数を選択します。 とりあえずデータが数字で表されている要素をすべて使ってみます。 またDummy化した変数ですが、1つだけは情報がかぶっており不要なので最後のDummy変数を除外しておきます。 select_columns = [ "Age", "Age_na", "SibSp", "Parch", "Fare", "Pclass_1", "Pclass_2", #"Pclass_3", # dummy除外 "Sex_male", #"Sex_female", # dummy除外 "Embarked_C", "Embarked_Q", #"Embarked_S", # dummy除外 ] 3.ローカルで学習 まずはローカルで学習して評価してみます。 今後変わるかもしれませんが、今回は以下の方針で学習してみます。 複数のモデルで性能を比較(前処理方法の妥当性検証がメインになりそうなので) 交差検証で検証(未知のデータの正答率を上げたいので、複数の学習パターンで学習し平均をとる) 3-1.学習モデル 複数のモデルで比較しています。 モデルは参考のサイトから持ってきています。 参考:クレジットカード不正利用予測モデルを作成・評価してみた import sklearn.ensemble import sklearn.gaussian_process import sklearn.naive_bayes import sklearn.linear_model import sklearn.neighbors import sklearn.tree import sklearn.discriminant_analysis import xgboost as xgb import lightgbm as lgb def create_models(random_seed): models = [ #Ensemble Methods sklearn.ensemble.AdaBoostClassifier(random_state=random_seed), sklearn.ensemble.BaggingClassifier(random_state=random_seed), sklearn.ensemble.ExtraTreesClassifier(random_state=random_seed), sklearn.ensemble.GradientBoostingClassifier(random_state=random_seed), sklearn.ensemble.RandomForestClassifier(random_state=random_seed), #Gaussian Processes sklearn.gaussian_process.GaussianProcessClassifier(random_state=random_seed), #GLM sklearn.linear_model.LogisticRegressionCV(random_state=random_seed), sklearn.linear_model.RidgeClassifierCV(), #Navies Bayes sklearn.naive_bayes.BernoulliNB(), sklearn.naive_bayes.GaussianNB(), #Nearest Neighbor sklearn.neighbors.KNeighborsClassifier(), #Trees sklearn.tree.DecisionTreeClassifier(random_state=random_seed), sklearn.tree.ExtraTreeClassifier(random_state=random_seed), #Discriminant Analysis sklearn.discriminant_analysis.LinearDiscriminantAnalysis(), sklearn.discriminant_analysis.QuadraticDiscriminantAnalysis(), #xgboost xgb.XGBClassifier(eval_metric="logloss", use_label_encoder=False, random_state=random_seed), # light bgm lgb.LGBMClassifier(random_state=random_seed), ] return models 3-2.(サンプリング) 目的変数のデータに差がある場合データ数を調整します。(いわゆる不均衡データ) Survived の個数を見てみます。 sns.countplot(x="Survived", data=df_train) plt.show() 死亡者の人数が多いのでアンダーサンプリング(死亡者の人数を減らして数を合わせる)してもいい気はしますが、今回は何もしません。 3-3.k-分割交差検証 k-分割交差検証(KFold)はデータをk個に分割し、k-1個のデータを学習用、1個のデータを検証用にして学習する方法です。 評価は正解率、適合率、再現率、F値とすべて見てみました。 コードは以下です。 def fit(df, columns, target_column, random_seed): # 教師データを作成 x = df[columns].to_numpy() y = df[target_name].to_numpy() # 交叉検証 model_scores = {} kf = sklearn.model_selection.KFold(n_splits=3, shuffle=True, random_state=random_seed) for train_idx, true_idx in kf.split(x, y): # 各学習データとテストデータ x_train = x[train_idx] y_train = y[train_idx] x_true = x[true_idx] y_true = y[true_idx] # 各モデル毎に学習 for model in create_models(random_seed): name = model.__class__.__name__ if name not in model_scores: model_scores[name] = [] # モデルの学習と評価 model.fit(x_train, y_train) pred_y = model.predict(x_true) # 結果を評価 model_scores[name].append(( sklearn.metrics.accuracy_score(y_true, pred_y), sklearn.metrics.precision_score(y_true, pred_y), sklearn.metrics.recall_score(y_true, pred_y), sklearn.metrics.f1_score(y_true, pred_y), )) accs = [] for k, scores in model_scores.items(): scores = np.mean(scores, axis=0) # モデル毎の平均 print("正解率 {:.3f}, 適合率 {:.3f}, 再現率 {:.3f}, F値 {:.3f} : {}".format( scores[0], scores[1], scores[2], scores[3], k, )) accs.append(scores) # 全モデルの中央値 accs = np.median(accs, axis=0) # 中央値 print("正解率 {:.3f}, 適合率 {:.3f}, 再現率 {:.3f}, F値 {:.3f}".format(accs[0], accs[1], accs[2], accs[3])) # 実行 fit(df_train, select_columns, target_column, random_seed) 実行結果 正解率 0.806, 適合率 0.762, 再現率 0.720, F値 0.740 : AdaBoostClassifier 正解率 0.811, 適合率 0.786, 再現率 0.699, F値 0.740 : BaggingClassifier 正解率 0.801, 適合率 0.743, 再現率 0.737, F値 0.740 : ExtraTreesClassifier 正解率 0.818, 適合率 0.796, 再現率 0.708, F値 0.749 : GradientBoostingClassifier 正解率 0.807, 適合率 0.764, 再現率 0.720, F値 0.741 : RandomForestClassifier 正解率 0.707, 適合率 0.639, 再現率 0.547, F値 0.589 : GaussianProcessClassifier 正解率 0.805, 適合率 0.782, 再現率 0.684, F値 0.729 : LogisticRegressionCV 正解率 0.799, 適合率 0.767, 再現率 0.687, F値 0.724 : RidgeClassifierCV 正解率 0.774, 適合率 0.711, 再現率 0.696, F値 0.703 : BernoulliNB 正解率 0.774, 適合率 0.722, 再現率 0.673, F値 0.696 : GaussianNB 正解率 0.686, 適合率 0.600, 再現率 0.531, F値 0.564 : KNeighborsClassifier 正解率 0.773, 適合率 0.699, 再現率 0.719, F値 0.709 : DecisionTreeClassifier 正解率 0.777, 適合率 0.718, 再現率 0.690, F値 0.704 : ExtraTreeClassifier 正解率 0.799, 適合率 0.767, 再現率 0.687, F値 0.724 : LinearDiscriminantAnalysis 正解率 0.800, 適合率 0.763, 再現率 0.698, F値 0.728 : QuadraticDiscriminantAnalysis 正解率 0.817, 適合率 0.783, 再現率 0.726, F値 0.753 : XGBClassifier 正解率 0.824, 適合率 0.796, 再現率 0.728, F値 0.760 : LGBMClassifier 正解率 0.800, 適合率 0.763, 再現率 0.698, F値 0.728 モデル全て見る必要はなさそうですね。 最後の行が全モデルの中央値になり、これだけ見ればよさそうです。 最初から正解率80%とかなり高い結果になりましたね…。 4.提出用データの学習 提出用データはとりあえずランダムフォレストを採用してハイパーパラメータはデフォルトのままでいきます。 4-1.(ハイパーパラメータの調整) ハイパーパラメータの調整ですが、今回は実施しません。 まだモデルが決まっていないのと調整に時間がかかりそうなので、前処理の方針が決まった後に実施する予定です。 4-2.ランダムフォレスト 学習コードです。 # 学習データを作成 x = df_train[select_columns].to_numpy() y = df_train[target_column].to_numpy() # 出力用データ x_test = df_test[select_columns].to_numpy() # ランダムフォレストで学習し、評価する model = sklearn.ensemble.RandomForestClassifier(random_state=random_seed) model.fit(x, y) pred_y = model.predict(x_test) # 提出用にデータ加工 output = pd.DataFrame({'PassengerId': df_test["PassengerId"], 'Survived': pred_y}) output.to_csv("result.csv", header=True, index=False) print("Your submission was successfully saved!") 提出結果 結果は 0.76315 でした。 gender_submission.csv より低いですね… 5.その他の分析 ここまでで一連の流れはできましたが、説明変数に対してもうちょっと分析手法を取り入れてみます。 5-1.説明変数の影響度 いくつかのモデルは説明変数に対して目的変数をどの程度説明できているかを見ることができます。 RandomForestClassifier と XGBClassifier と LGBMClassifier で見てみます。 def print_feature_importance(df, columns, target_column, random_seed): x = df[columns] y = df[target_column] print("--- RandomForestClassifier") model = sklearn.ensemble.RandomForestClassifier(random_state=random_seed) model.fit(x, y) fti1 = model.feature_importances_ for i, column in enumerate(columns): print('{:20s} : {:>.6f}'.format(column, fti1[i])) print("--- XGBClassifier") model = xgb.XGBClassifier(eval_metric="logloss", use_label_encoder=False) model.fit(x, y) fti2 = model.feature_importances_ for i, column in enumerate(columns): print('{:20s} : {:>.6f}'.format(column, fti2[i])) print("--- LGBMClassifier") model = lgb.LGBMClassifier(random_state=random_seed) model.fit(x, y) fti3 = model.feature_importances_ for i, column in enumerate(columns): print('{:20s} : {:>.2f}'.format(column, fti3[i])) #--- 結果をplot fig = plt.figure() ax1 = fig.add_subplot(3, 1, 1, title="RandomForestClassifier(Feature Importance)") ax2 = fig.add_subplot(3, 1, 2, title="XGBClassifier(Feature Importance)") ax3 = fig.add_subplot(3, 1, 3, title="LGBMClassifier(Feature Importance)") ax1.barh(columns, fti1) ax2.barh(columns, fti2) ax3.barh(columns, fti3) fig.tight_layout() plt.show() # 実行 print_feature_importance(df_train, select_columns, target_column, random_seed) 実行結果 --- RandomForestClassifier Age : 0.244622 Age_na : 0.016238 SibSp : 0.054778 Parch : 0.039426 Fare : 0.264062 Pclass_1 : 0.042784 Pclass_2 : 0.035545 Sex_male : 0.269452 Embarked_C : 0.019326 Embarked_Q : 0.013766 --- XGBClassifier Age : 0.026774 Age_na : 0.022875 SibSp : 0.070061 Parch : 0.023662 Fare : 0.029577 Pclass_1 : 0.145431 Pclass_2 : 0.127607 Sex_male : 0.503674 Embarked_C : 0.023957 Embarked_Q : 0.026382 --- LGBMClassifier Age : 1069.00 Age_na : 59.00 SibSp : 106.00 Parch : 109.00 Fare : 1356.00 Pclass_1 : 44.00 Pclass_2 : 58.00 Sex_male : 97.00 Embarked_C : 70.00 Embarked_Q : 31.00 Sex,Fare,Age が高い値をだしていますね。 割と重要(生存率にかなり影響がある)ようです。 5-2.回帰分析 回帰分析による分析結果を乗せます。 回帰分析では各説明変数の重要度だけではなく外れ値かどうかも見ることができます。 from statsmodels.stats.outliers_influence import OLSInfluence from statsmodels.stats.outliers_influence import variance_inflation_factor import statsmodels.api as sm def print_statsmodels(df, columns, target_column): # 重回帰分析 X1_train = sm.add_constant(df[columns]) y = df[target_column] model = sm.OLS(y, X1_train) fitted = model.fit() # summary見方の参考 # https://self-development.info/%E3%80%90%E5%88%9D%E5%BF%83%E8%80%85%E8%84%B1%E5%87%BA%E3%80%91statsmodels%E3%81%AB%E3%82%88%E3%82%8B%E9%87%8D%E5%9B%9E%E5%B8%B0%E5%88%86%E6%9E%90%E7%B5%90%E6%9E%9C%E3%81%AE%E8%A6%8B%E6%96%B9/ #print('summary = \n', fitted.summary()) print("--- 重回帰分析の決定係数") for i, column in enumerate(columns): print('\t{:15s} : {:7.4f}(coef) {:5.1f}%(P>|t|)'.format( column, fitted.params[i+1], fitted.pvalues[i]*100 )) print("") # 各columnにおけるクック距離をだす print("--- 外れ値(cook_distance threshold:0.5)") for column in columns: # 単回帰分析 X1_train = sm.add_constant(df[column]) model = sm.OLS(y, X1_train) fitted = model.fit() cook_distance, p_value = OLSInfluence(fitted).cooks_distance kouho = np.where(cook_distance > 0.5)[0] if len(kouho) == 0: print("{:20s} cook_distance is 0(max: {:.4f})".format(column, np.max(cook_distance))) else: for index in kouho: print("{:20s} cook_distance: {}, index: {}".format(column, cook_distance[index], index)) print("") # 実行 print_statsmodels(df_train, select_columns, target_column) 重回帰分析 --- 重回帰分析の決定係数(P値:5%以上の場合は結果が怪しい) Age : -0.0058(coef) 0.0%(P>|t|) Age_na : -0.0456(coef) 0.0%(P>|t|) SibSp : -0.0399(coef) 19.0%(P>|t|) Parch : -0.0186(coef) 0.2%(P>|t|) Fare : 0.0003(coef) 31.0%(P>|t|) Pclass_1 : 0.3328(coef) 32.3%(P>|t|) Pclass_2 : 0.1852(coef) 0.0%(P>|t|) Sex_male : -0.5023(coef) 0.0%(P>|t|) Embarked_C : 0.0718(coef) 0.0%(P>|t|) Embarked_Q : 0.0827(coef) 4.1%(P>|t|) 出力を分けています。 まずは重回帰分析時の結果ですが、各説明変数の回帰係数(coef)は5-1.の影響度と同じく、目的変数にどれだけ影響があるかの度合いになります。 またP値(有意確率)ですが、低いと優位性が高いと言えます。 0.05以上になると優位性が低くなり、回帰係数が実際には0の可能性があります。(P値はなかなか難しい話なので間違った言い回しになっているかも) 今回ですと、SibSp,Fare,Pclass_1,Embarked_QはP値が高いので外したほうが良いかもしれません。 また、回帰係数では Sex_male が高いので性別は影響度が高そうなことが分かります。 クックの距離 クックの距離ですが、簡単に言うと回帰分析において各データがどれだけ回帰係数に影響あるかを表した指標となります。 影響度が大きいデータは外れ値と考えることもできます。(あくまで回帰分析における影響度です) コードは全説明変数に対して(重回帰分析)でクックの距離を出してしまうとど説明変数の影響かが分からないので、各説明変数に対して(単回帰分析)結果を出しています。 --- 外れ値(cook_distance threshold:0.1) Age cook_distance is 0(max: 0.0217) Age_na cook_distance is 0(max: 0.0061) SibSp cook_distance is 0(max: 0.0120) Parch cook_distance is 0(max: 0.0579) Fare cook_distance: 0.10554792163598595, index: 258 Fare cook_distance: 0.10554792163598595, index: 679 Fare cook_distance: 0.10554792163598595, index: 737 Pclass_1 cook_distance is 0(max: 0.0043) Pclass_2 cook_distance is 0(max: 0.0032) Sex_male cook_distance is 0(max: 0.0053) Embarked_C cook_distance is 0(max: 0.0040) Embarked_Q cook_distance is 0(max: 0.0105) 閾値は正直よくわかっていません。 今回ですと、Fare に対してクックの距離0.1を超えるデータが3個ありました。 5-3.説明変数同士の相関 説明変数同士に強い相関がある場合、片方だけを採用すればよくなります。 その相関を見てみました。 コードは以下です。 def print_correlation(df, columns): # 相関係数1:1 print("--- 相関係数1:1 (threshold: 0.5)") cor = df[columns].corr() count = 0 for i in range(len(columns)): for j in range(i+1, len(columns)): val = cor[columns[i]][j] if abs(val) > 0.5: print("{} {}: {:.2f}".format(columns[i], columns[j], val)) count += 1 if count == 0: print("empty") print("") # heatmap plt.figure(figsize=(12,9)) sns.heatmap(df[columns].corr(), annot=True, vmax=1, vmin=-1, fmt='.1f', cmap='RdBu') plt.show() # 相関係数1:多 # 5以上だとあやしい、10だとかなり確定 print("--- VIF(5以上だと怪しい)") vif = pd.DataFrame() x = df[columns] vif["VIF Factor"] = [ variance_inflation_factor(x.values, i) for i in range(x.shape[1]) ] vif["features"] = columns print(vif) plt.barh(columns, vif["VIF Factor"]) plt.vlines([5], 0, len(columns), "blue", linestyles='dashed') plt.vlines([10], 0, len(columns), "red", linestyles='dashed') plt.title("VIF") plt.tight_layout() plt.show() # 実行 print_correlation(df_train, select_columns) 相関係数 相関係数は-1~1の値を取り、0が関係なしとなります。 表示としては、相関係数の絶対値が0.5以上のものを表示しています。 --- 相関係数1:1 (threshold: 0.5) Fare Pclass_1: 0.59 FareとPclass_1の相関が高そうですね。 ヒートマップで表すと以下です。 VIF 相関係数は1対1の関係を表す指標でした。 VIFは1対多で相関があるかを測る指標らしいです。 --- VIF(5以上だと怪しい) VIF Factor features 0 4.174449 Age 1 1.453804 Age_na 2 1.504096 SibSp 3 1.543488 Parch 4 2.484896 Fare 5 2.717305 Pclass_1 6 1.428871 Pclass_2 7 2.484806 Sex_male 8 1.381078 Embarked_C 9 1.298479 Embarked_Q 5と10の地点に線を入れています。 Ageがかなり高そうですね、Pclass_1やFareも少し高そうです。 あとがき 一旦手法をまとめたかったのでまとめてみました。 次回は実際に前処理をしてスコアを上げていきたいと思います。 参考 タイタニック号の乗客の生存予測〜80%以上の予測精度を超える方法(探索的データ解析編)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【YOLO v5】Google Colaboratory で学習してみる

1. はじめに Yoloは、とっても扱いやすいフレームワークで、すぐに物体検出ができますが、やっぱり自分が検出したいものは、自分の用意したデータで学習させるしかないですよね。 ということで、学習させてみました。 2. やること データセットを作るのは、たいへんですので、既存のデータセットを使わせてもらい、学習の手順を確認したいと思います。 学習は、マシンパワーが必要そうですから、やっぱりGPUを使いたいところです。ですので、Google Colaboratoryを使います。 3. やってみる 3.1 まずは、準備 新しいノートブックを作り、Googleドライブをマウントします。 from google.colab import drive drive.mount('/content/drive') yolo_train というフォルダの中で作業します。 %cd /content/drive/My Drive/Colab Notebooks %mkdir yolo_train %cd yolo_train Yoloをクローンしてきます。 !git clone https://github.com/ultralytics/yolov5 %cd yolov5 必要なパッケージをインストールします。 !pip install -r yolov5/requirements.txt いろいろ準備します。 import torch from IPython.display import Image, clear_output from utils.google_utils import gdrive_download Yoloがちゃんと動くか、物体検出してみます。 以下を実行すると、ジダンが検出されます。 !python detect.py --weights yolov5s.pt --img 416 --conf 0.4 --source ./data/images/ Image(filename='runs/detect/exp/zidane.jpg', width=600) ちゃんとできましたよね? 3.2 学習 はい、やっと準備ができましたので、ここから本題です。 まずは、rboflowのPublic Datasetsから学習用データをとってきます。サイコロの目を検出させるための学習用データセットです。すでにYOLOv5用に編集されていますから、ダウンロードするだけですぐに使えます。 Downloadsのmedium-colorを選びます。 Available Download Formatsで「YOLO v5 PyTorch」を選びます。 データをダウンロードするかダウンロードのコードを得るか選べます。今回は、ダウンロードのコードを取得して直接GoogleDriveに保存するため、ダウンロードコードを得ることにします。[show download code]を選択して[continue]を押すと、ダウンロードするコードが表示されます。 ノートブックに戻り、以下の通りダウンロードと展開をします。今回は、diceというフォルダにデータを入れました。 torch.hub.download_url_to_file('<<ダウンロードコード>>', 'tmp.zip') !unzip -q tmp.zip -d ../dice/ && rm tmp.zip diceのフォルダ内にあるdata.yamlを少しだけ変更します。学習用データと検証データが分かれていないので、同じデータを使います。 data.yaml train: ../dice/export/images val: ../dice/export/images nc: 6 names: ['1', '2', '3', '4', '5', '6' もう、これで準備完了です。驚くほど簡単です。 学習してみます。 # Train YOLOv5s on dice for 10 epochs !python train.py --img 640 --batch 16 --epochs 200 --data ../dice/data.yaml --cfg ./models/yolov5s.yaml --weights "" --name dice --nosave --cache 画像点数が少ないため、エポック数を200ぐらいにしないと、物体検出ができませんでした。学習時間は、1時間ほどかかりました。 結果は、runs/train/diceの中に入っています。ちょっと見てみましょう。 Image(filename='runs/train/dice/train_batch0.jpg', width=800) Tensorbordで結果を見てみます。 %load_ext tensorboard %tensorboard --logdir "runs" なかなかいいのではないでしょうか? では、サイコロの目を検出してみます。 !python detect.py --weights runs/train/dice/weights/last.pt --source ../dice/dice.jpg Image(filename='runs/detect/exp/dice.jpg', width=800) ちゃんと検出できてきますね! こんなに簡単に学習できるなんて感激です。 お疲れさまでした!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

はじめに 今回はDjangoを使って友達リスト的なアプリを作成しようと思います。 プロジェクト名はTomodachi カテゴリ機能実装 # ブランチを切り替える $ git branch feature-category $ git checkout feature-category カテゴリモデル作成 friendslist/models.py # ユーザー対カテゴリ(1対多)のリレーションで作成 from django.contrib.auth import get_user_model class Category(models.Model): name = models.CharField(default="", max_length=20) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) # マイグレーションする $ python manage.py makemigrations $ python manage.py migrate friendslist/admin.py # 管理画面から投稿できるようにする from friendslist.models import User, Friend, Category admin.site.register(Category) カテゴリ一覧表示機能実装 friendslist/views.py # 友達一覧ページのカテゴリ一覧表示の記述をする(request.userに紐付いたカテゴリのみ表示) from friendslist.models import Friend, Category def index(request): user = request.user  ←☆追記する(ログインユーザー) categories = Category.objects.filter(user=user)  ←☆追記する friends = Friend.objects.all() context = { 'friends': friends, 'categories': categories, } return render(request, 'friendslist/index.html', context) config/urls.py # カテゴリ一覧ページのカテゴリ一覧表示のURLを設定する path('category/', views.category_index), friendslist/views.py # カテゴリ一覧ページのカテゴリ一覧表示の記述をする def category_index(request): user = request.user categories = Category.objects.filter(user=user) context = { 'categories': categories, } return render(request, 'friendslist/category/index.html', context) friendslist/category/index.html # カテゴリ一覧ページを作成 {% extends 'friendslist/base.html' %} {% block content %} <main class="container mb-5"> <div class="row"> <div class="col col-md-4"> <h2>カテゴリ一覧</h2> <ul class="list-group"> {% for category in categories %} <li class="list-group-item"> {{ category.name }} </li> {% endfor %} </ul> </div> </div> </main> {% endblock %} カテゴリ作成機能実装 config/urls.py # URLを設定する path('category/create', views.category_create), friendslist/forms.py # Categoryフォームを設定する from friendslist.models import Friend, Category class CategoryForm(forms.ModelForm): class Meta: model = Category fields = ( 'name', ) friendslist/views.py # カテゴリ作成機能の記述をする from friendslist.forms import FriendForm, UserCreationForm, CategoryForm def category_create(request): if request.method == 'POST': form = CategoryForm(request.POST) if form.is_valid(): category = form.save(commit=False) category.user = request.user category.save() return redirect('/category/') return render(request, 'friendslist/category/create.html') category/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-4"> <h2>カテゴリ作成</h2> <ul class="list-group"> <li class="list-group-item"> <label class="form-label">カテゴリ名</label> <input type="text" class="form-control" id="id_name" name="name" placeholder="仕事関係, 大学"> </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('category/<slug:pk>/delete', views.category_delete, name="category_delete"), friendslist/views.py # カテゴリ削除機能を実装する def category_delete(request, pk): try: category = Category.objects.get(pk=pk) except Category.DoesNotExist: raise Http404 category.delete() return redirect('/category/') friendslist/category/index.html # カテゴリ削除ボタンを実装する <a href="{% url 'category_delete' category.pk %}" class="btn btn-danger btn-sm float-end">削除</a> カテゴリと友達のリレーション カテゴリと友達のリレーションを貼る firendslist/models.py # カテゴリ対友達を1対多で設定する class Friend(models.Model): name = models.CharField(default="", max_length=20) furigana = models.CharField(blank=True, null=True, max_length=20) nickname = models.CharField(blank=True, null=True, max_length=20) sex = models.IntegerField(default=0) birthday = models.DateField(blank=True, null=True, auto_now=False, auto_now_add=False) birthplace = models.CharField(blank=True, null=True, max_length=30) hobby = models.CharField(blank=True, null=True, max_length=30) company = models.CharField(blank=True, null=True, max_length=30) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) category = models.ForeignKey(Category, on_delete=models.CASCADE, default=1)  ←☆追加する # マイグレーションする $ python manage.py makemigrations $ python manage.py migrate データ追加後のモデルにForeignKeyを実装する場合は、エラーが出るので、 こちらを参照にして実装します。 結果的にデフォルト値を1にしました。(あとからカテゴリを変更する機能をつけるしかないようです) 仕様変更(カテゴリ一覧にてカテゴリごとの友達が見れるようにする)(友達一覧では友達すべてが見れるようにする) config/urls.py # 仕様変更(カテゴリ一覧にてカテゴリごとの友達が見れるようにする) (友達一覧では友達すべてが見れるようにする) path('category/<slug:pk>', views.category_index), friendslist/views.py # 仕様変更(カテゴリ一覧にてカテゴリごとの友達が見れるようにする) (友達一覧では友達すべてが見れるようにする) def category_index(request, pk): user = request.user categories = Category.objects.filter(user=user) current_category = Category.objects.get(pk=pk) friends = Friend.objects.filter(category=current_category) context = { 'friends': friends, 'categories': categories, 'current_category': current_category, } return render(request, 'friendslist/category/index.html', context) index.html # 仕様変更(カテゴリ一覧にてカテゴリごとの友達が見れるようにする) (友達一覧では友達すべてが見れるようにする) {% extends 'friendslist/base.html' %} {% block content %} <main class="container mb-5"> <div class="row"> <div class="column col-md-8"> <h2>友達一覧</h2> <ul class="list-group"> {% for friend in friends %} <li class="list-group-item"> {{ friend.name }} <div class="float-end"> <a href="{% url 'friend' friend.pk %}" class="btn btn-success btn-sm">詳細</a> <a href="{% url 'delete' friend.pk %}" class="btn btn-danger btn-sm">削除</a> </div> </li> {% endfor %} </ul> </div> </div> </main> {% endblock %} category/index.html # 仕様変更(カテゴリ一覧にてカテゴリごとの友達が見れるようにする) (友達一覧では友達すべてが見れるようにする) {% extends 'friendslist/base.html' %} {% block content %} <main class="container mb-5"> <div class="row"> <div class="col col-md-4"> <h2>カテゴリ一覧</h2> <ul class="list-group"> {% for category in categories %} <li class="list-group-item"> {{ category.name }} <a href="{% url 'category_delete' category.pk %}" class="btn btn-danger btn-sm float-end">削除</a> </li> {% endfor %} </ul> </div> <div class="column col-md-8"> <h2>「{{ current_category.name }}」の友達一覧</h2> <ul class="list-group"> {% for friend in friends %} <li class="list-group-item"> {{ friend.name }} <div class="float-end"> <a href="{% url 'friend' friend.pk %}" class="btn btn-success btn-sm">詳細</a> <a href="{% url 'delete' friend.pk %}" class="btn btn-danger btn-sm">削除</a> </div> </li> {% endfor %} </ul> </div> </div> </main> {% endblock %} 上記の仕様ではエラーが出てしまうので、ユーザーが持つ最初のカテゴリページをそのユーザーのカテゴリ一覧ページの基準とする friendslist/views.py # 最初のカテゴリページを基準とする(作成と削除のリダイレクト先にする) def category_create(request): if request.method == 'POST': form = CategoryForm(request.POST) if form.is_valid(): category = form.save(commit=False) category.user = request.user category.save() user = request.user categories = Category.objects.filter(user=user) first_category = categories.first() return redirect('/category/{}'.format(first_category.id)) return render(request, 'friendslist/category/create.html') def category_delete(request, pk): try: category = Category.objects.get(pk=pk) except Category.DoesNotExist: raise Http404 category.delete() user = request.user categories = Category.objects.filter(user=user) first_category = categories.first() return redirect('/category/{}'.format(first_category.id)) カレントカテゴリのリストをactiveにする category/index.html # カレントカテゴリのリストをactiveにする <li class="list-group-item {% if category.pk == current_category.pk %}list-group-item-warning{% endif %}"> カテゴリ一覧のリンクを作成する category/index.html # カレントカテゴリのリストをactiveにする <a href="/category/{{ category.pk }}" class="text-decoration-none text-dark">{{ category.name }}</a> リンクボタンのリンクを修正する snippets/linkbtns.html # リンクボタンのリンクを修正する <div class="container mb-5"> <div class="row"> <div class="col"> <a class="btn btn-danger" href="/" role="button">友達一覧</a> </div> <div class="col"> <a class="btn btn-success" href="/create/" role="button">友達登録</a> </div> <div class="col"> <a class="btn btn-warning" href="/category/{{ first_category.pk }}/" role="button">カテゴリ一覧</a> </div> <div class="col"> <a class="btn btn-info" href="/category/create/" role="button">カテゴリ登録</a> </div> </div> </div> friendslist/views.py # 色んなページからのfirst_categoryを記述する(後ほど、一つの関数にしたい) user = request.user categories = Category.objects.filter(user=user) first_category = categories.first() context = { 'first_category': first_category, } 友達作成時にカテゴリに紐付けて作成する forms.py # カテゴリを追加する class FriendForm(forms.ModelForm): class Meta: model = Friend fields = ( 'name', 'furigana', 'nickname', 'sex', 'birthday', 'birthplace', 'hobby', 'company', 'category',  ←☆追加する ) views.py # categoriesを追加する context = { 'categories': categories,  ←☆追加する 'first_category': first_category, } create.html # カテゴリを選択するボックスを作成 <select class="form-control" id="id_category" name="category"> <option value="0" selected>選択しない</option> {% for category in categories %} <option value="{{ category.pk }}">{{ category.name }}</option> {% endfor %} </select> models.py # カテゴリのdefault値を0(選択しない)にする category = models.ForeignKey(Category, on_delete=models.CASCADE, default=0) # マイグレーションする $ python manage.py makemigrations $ python manage.py migrate 見た目はadmin画面のFriendsのCategoryを検証して参考にする 友達一覧で紐付いたカテゴリボタンを表示する(bootstrapiconsを使う) index.html # カテゴリリンクボタンを作成する <a href="/category/{{ friend.category.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-bookmarks" viewBox="0 0 16 16"> <path d="M2 4a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v11.5a.5.5 0 0 1-.777.416L7 13.101l-4.223 2.815A.5.5 0 0 1 2 15.5V4zm2-1a1 1 0 0 0-1 1v10.566l3.723-2.482a.5.5 0 0 1 .554 0L11 14.566V4a1 1 0 0 0-1-1H4z"/> <path d="M4.268 1H12a1 1 0 0 1 1 1v11.768l.223.148A.5.5 0 0 0 14 13.5V2a2 2 0 0 0-2-2H6a2 2 0 0 0-1.732 1z"/> </svg> {{ friend.category.name }} </a> 友達編集画面にカテゴリ紐付けの選択肢を追加する views.py # categoriesを追加する 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) context = { 'categories': categories, 'friend': friend } return render(request, 'friendslist/friend.html', context) friend.html # 選択肢を追加する <li class="list-group-item"> <label class="form-label">カテゴリ</label> <select class="form-control" id="id_category" name="category"> <option value="0" {% if friend.category.pk == 0 %}selected{% endif %}>選択しない</option> {% for category in categories %} <option value="{{ category.pk }}" {% if category.pk == friend.category.pk %}selected{% endif %}>{{ category.name }}</option> {% endfor %} </select> </li> 友達のカテゴリが選択されないときに作成と更新ができないので修正 models.py # default値を決めるのではなく、nullでOKとする としたかったが、上手く行かなかった。 おそらく、データベース側の処理で カテゴリを選択しないときのデータの作成がvalidで止められてしまう。 # てことでカテゴリ選択は必須とする category = models.ForeignKey(Category, on_delete=models.CASCADE, default=1) create.html,friend.html # てことでカテゴリ選択は必須とする(以下を削除) <option value="0" selected>選択しない</option> さいごに 今回は カテゴリ機能を実装しました カテゴリとユーザーのリレーション カテゴリと友達のリレーション カテゴリの一覧表示 カテゴリの作成 カテゴリの削除 カテゴリに紐付いた友達の一覧表示 カテゴリに紐付いた友達の作成 次回はメモと友達のリレーションを作成したいと思います。 デプロイも残っていますね(HerokuとDB)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

あみだくじの確率の偏りをPythonで検証した話をわかりやすく解説

はじめに やったこと あみだくじにはどうやら偏りがあるらしい。 Wikipediaにも書いてあるし、数学的に正しい「あみだくじ必勝法」とはにも書いてある。 僕は理系ながら、数学が致命的にできないためプログラマらしくパソコンを使ってゴリ押し検証してみました。 実行条件とコード この記事のコードはGoogleColaboratoryを用い、Python 3.7.10で実行しています。 私のGitHubに公開しており、そこからGoogleColaboratoryのソースコードを見ることができます。 あみだくじの定義 この記事でいうあみだくじとは、ごくごく一般的な真っ直ぐな横棒と縦棒で構成されるものを言います。 こんなものとか こんなものはあみだくじと言いません(過激派) プログラム中でのあみだくじはどう作るか 定義 縦棒をV本引き、横棒をH本引くとします。 縦棒をV個分の配列として考えれば、初期の値を通し番号として格納できます。 横棒はH回隣同士で置換することと考えることができます。 例 縦棒が4本なので、配列の値の個数は4つですね。 初期値として、配列potision = [0,1,2,3]が入っています。 横棒は4本なので、4回置換しましよう。 最初の横棒は、potision[0]とpotision[1]を置換します。 この時点で、potisionは[1,0,2,3]になります。 これをあと3回繰り返せば、[0,1,2,3]→[1,0,2,3]→[1,2,0,3]→[1,0,2,3]→[1,0,3,2]と結果が出ました。 偏りがあることの検証 まず、変数を準備します。今回は、35人クラスの席替えを想定し、35人分の縦棒と、1人1回横棒を引くと想定します。 #縦棒の本数 v_num = 35 #横棒の本数 h_num = 35 #試行回数 test_num = 10000 potision = [] result = np.zeros((v_num,v_num)) 次にあみだくじを作成→記録を繰り返し行います。 関数は後述しますが、repoti(potision)は配列を通し番号で初期化、swap(potision)はランダムに配列中の場所を選び、、隣と入れ替えを行なう関数です。 横棒の本数である、h_num回入れ替えを行なっています。 最終的にできた結果を二次元result配列に整理しています。j番目の縦棒に、どの数字が入っているかをインクリメントしています。 for i in tqdm.tqdm(range(test_num)): potision = repoti(potision) for j in range(h_num): swap(potision) for j in range(v_num): result[j][potision[j]] += 1 さっき飛ばした関数の中身 repoti()関数は0からv_numまで初期化をしています。 swap()関数は0からv_num-2までの値をランダムに取ります。 ランダムに取った値の次の値と置換するため、-2にしないとオーバーフローしますね。 def repoti(potision): potision = list(range(v_num )) return potision def swap(potision): s = random.randint(0,v_num-2) temp = potision[s] potision[s] = potision [s+1] potision [s+1] = temp return potision この結果を表で見たかったため、エクセルで開けるCSV形式で保存します。Pandasを用いました。 GoogleColabratoryを使ったのでfiles.download()関数を用いてダウンロードが必要です。 sheet = pd.DataFrame(result) sheet.to_csv("result.csv") files.download('result.csv') 結果 何パターンか試行したため、もし結果だけ見たい方はGitHubからCSVファイルダウンロードしてください。 今回は35×35の結果を紹介します。 まず表をお見せしましょう。 明らかに偏りがありますね。 特に、一番左である0番目の縦棒に、元々の数値である0が入った回数は10,000回中5,134回となりました。 真ん中ら辺を見てみましょう。下は17番目の縦棒の結果をグラフにしたものです。 明らかに17が多い、結果として正規分布のようなグラフになりました。 さらに特筆すべき点として、7ほど離れると個数が0になります。 偏りがあるということが、これでわかったと思います。 結論 あみだくじをやるときは、狙うあたりの真上を選択しましょう。 作る側は、結果部分を隠さないと狙われます。 あとがき この記事は、私の最初のQiita記事になります。 知ってる人は知ってることで、今更記事にすることではないかもしれませんが、私の衝撃がこれを上回り思わず記事にしました。 過不足や改善点などありましたら教えていただけると、とってもとっても嬉しいです。 参考文献 Qiita pythonであみだくじもどきを作ってみた。 公平なあみだくじって何回横線を引けばいいの? その他 あみだくじ - Wikipedia 数学的に正しい「あみだくじ必勝法」とは 私のGitHub
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NO.3取得したいろが 何色なのか 区別する

前回の記事 NO.1 カメラ起動+BGR取得 NO.2 ウィンドウに取得した色を表示 取得したいろが 何色なのか 区別する ふたつの 方法があるです ひとつめ、BGRの範囲を指定して その範囲内なら 特定の色をかえす 取得した BGR [10, 20, 180] 赤色 Low[0, 0, 100] Up[50, 50, 255] Low < BGR < Up Lowより 大きくて Upより 小さい この場合は 赤色ですよーとする Low = [0, 0, 100] BGR = [10, 20, 180] Up = [50, 50, 255] if Low[0] < BGR[0] and BGR[0] < Up[0]: if Low[1] < BGR[1] and BGR[1] < Up[1]: if Low[2] < BGR[2] and BGR[2] < Up[2]: print("True") #True ---------------------------------------------------- Low = [0, 0, 100] BGR = [10, 80, 180] Up = [50, 50, 255] #もし このように BGR[1](80) が Up(50)より 大きいと 最後までいかず Trueが表示されません このように 範囲を指定してあげて このときは この色と あの色 としてあげればよいです。 しかし 部屋の明るさ などで かなり こまかくかわるため 範囲の指定がすごく難しいです。 オレンジと赤は 同じような 値になりやすかったりするので。 メリット 処理が軽い、コードが短い デメリット 色の取得が難しくなる。 環境に左右されやすい(明るさ) ふたつめ、色のデータ[B,G,R]をあつめる 範囲で指定しないで [B,G,R]このデータを たっくさんあつめて 取得したBGRと一致したら色を返す方法 green_list = [[68, 73, 41],[59, 73, 40],[58, 75, 35],[66, 77, 40]] BGR = [59, 73, 40] for i in green_list: if i == BGR: print("BGRはgreenです") break else: print("False") #greenリストの二つ目の [59, 73, 40] が BGR = [59, 73, 40]と一致 "BGRはgreenです" #とかえってきます。 データにないものは 色を返さないので 精度はかなりよくなりますが そうとうな データが必要になります。 単純に 256×256×256 = 16777216個 [0,0,0],[0,0,1] こんなデータが 16777216個あるので 仮に これを for文で ひとつづつまわしたら すっごいしんどいです メリット 色の取得制度が高い。 環境に左右されにくい(データ量次第) 1色(赤で 4万通りくらいのデータがあったらまぁまぁ精度よかったです 9/10くらいは成功 デメリット 処理が激重、コードが激長 ひとつめのBGRの範囲を指定して特定の色をかえす 今回は これを やります。 (データーをたくさん 取得するのは 別の記事で書くかもしれません) サンプルコード(指定した範囲に 取得したBGRがあてはまるか) color_key = ['black','black','black', 'black','black','black', 'black','black','black'] color_index =[[0,0,0],[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0]] colors_BGR = { 'red': ([5, 20, 160], [75, 90, 189]), 'green': ([25, 130, 0], [100, 180, 30]), 'white': ([130, 130, 130], [255, 255, 255]), 'blue': ([140, 80, 0], [255, 180, 50]), 'orange': ([5, 91, 190], [130, 150, 255]), 'yellow': ([5, 150, 150], [110, 200, 200]) } BGR = [20, 70, 180] for color, ( lower, upper) in colors_BGR.items(): if lower[0] < BGR[0] and BGR[0] < upper[0]: if lower[1] < BGR[1] and BGR[1] < upper[1]: if lower[2] < BGR[2] and BGR[2] < upper[2]: color_key[0] = color color_index[0] = BGR #----------------------------------------------------- #実行したあと for 'red', ( [5, 20, 160], [75, 90, 189]) in colors_BGR.items(): if 5 < 20 and 20 < 75: if 20 < 70 and 70 < 90: if 160 < 180 and 180 < 189: color_key[0] = 'red' color_index[0] = [20, 70, 180] #color_index = [[20, 70, 180], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]] #color_key = ['red', 'black', 'black', 'black', 'black', 'black', 'black', 'black', 'black']  .items() 辞書型を展開するのに もってこいです。 画像のように してくれます lowe[5,20,160] BGR[20, 70, 160] upper[75, 90, 189] BGRが lowerとuperの間に入ってるか[0]インデックス ひとつごと 確認します lowe[5, -, -] < BGR[20, -, -] < upper[75, -, -] OKだったら [1]にいきます lower[-, 20, -] < BGR[-, 70, -] < upper[-, 90, -] OKだったら[2] (省略 もし どこか [0],[1],[2]で BGRが範囲外だったら次の グリーンを確認します 'green': ([25, 130, 0], [100, 180, 30] だめだったらwhite blue、、、順番に探します 今回は redが 指定した範囲のなかになってるので color_key[0] = 'red' color_index[0] = [20, 70, 180] color_index = [[20, 70, 180], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]] color_key = ['red', 'black', 'black', 'black', 'black', 'black', 'black', 'black', 'black'] リストに追加されます。 サンプルコード(指定した範囲に 取得したBGRがあてはまるか キューブ色を取得) import cv2 capture = cv2.VideoCapture(2) capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1080) # 横幅 capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) # 縦幅 color_index =[[0,0,0],[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0], [0,0,0],[0,0,0],[0,0,0]] color_key = ['black','black','black', 'black','black','black', 'black','black','black'] colors_BGR = { 'red': ([5, 20, 160], [75, 90, 189]), #red 6 4 'green': ([25, 130, 0], [100, 180, 30]), #green 3 2 'white': ([130, 130, 130], [255, 255, 255]), #white 2 3 'blue': ([140, 80, 0], [255, 180, 50]), #blue 5 5 'orange': ([5, 91, 190], [130, 150, 255]), #orange 4 1 'yellow': ([5, 150, 150], [110, 200, 200]) #yellow 1 0 } #Y縦 X横 frame_xy = [[300, 300],[300, 450],[300, 600], [450, 300],[450, 450],[450, 600], [600, 300],[600, 450],[600, 600]] x_start = [280, 430, 580, 280, 430, 580, 280, 430, 580] y_start = [280, 280, 280, 430, 430, 430, 580, 580, 580] x_end = [320, 470, 620, 320, 470, 620, 320, 470, 620] y_end = [320, 320, 320, 470, 470, 470, 620, 620, 620] x_start2 = [30, 80, 130, 30, 80, 130, 30, 80, 130] y_start2 = [30, 30, 30, 80, 80, 80, 130, 130, 130] x_end2 = [70, 120, 170, 70, 120, 170, 70, 120, 170] y_end2 = [70, 70, 70, 120, 120, 120, 170, 170, 170] while True: ret, frame = capture.read() for k, (xs, ys, xe, ye, xs2, ys2, xe2, ye2) in enumerate(zip (x_start, y_start, x_end, y_end, x_start2, y_start2, x_end2, y_end2)): cv2.rectangle(frame, (xs, ys), (xe, ye), (255, 255, 255), 2) b1 = color_index[k][0] g1 = color_index[k][1] r1 = color_index[k][2] cv2.putText(frame, str(k), (xs2, ys2), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,0), 2) if color_key[k] == 'red': b1, g1 ,r1 = 0, 0, 255 elif color_key[k] == 'green': b1, g1 ,r1 = 0, 255, 0 elif color_key[k] == 'white': b1, g1 ,r1 = 255, 255, 255 elif color_key[k] == 'blue': b1, g1 ,r1 = 255, 0, 0 elif color_key[k] == 'orange': b1, g1 ,r1 = 0, 128, 255 elif color_key[k] == 'yellow': b1, g1 ,r1 = 0, 255, 255 #左上に 色を表示する cv2.rectangle(frame, (xs2, ys2), (xe2, ye2), (int(b1), int(g1), int(r1)), -1) #BGRの取得位置 for i, (x, y) in enumerate(frame_xy): pixel = frame[x, y] b = pixel[0] g = pixel[1] r = pixel[2] BGR = [b,g,r] color_index[i] = BGR #colors_BGRの範囲内に 真上のBGRがはいってるか 確認 for color, ( lower, upper) in colors_BGR.items(): if lower[0] < BGR[0] and BGR[0] < upper[0]: if lower[1] < BGR[1] and BGR[1] < upper[1]: if lower[2] < BGR[2] and BGR[2] < upper[2]: color_key[i] = color color_index[i] = BGR if color_key[i] != 'black': #print(color_key) #print(color_index) pass cv2.imshow('camra',frame) if cv2.waitKey(1) & 0xFF == ord('q'): break capture.release() cv2.destroyAllWindows() if color_key[i] != 'black': print(color_key) print(color_index) コードの下のほうに これがあります print(color_index)これをすれば つねに 取得したBGRをみれるので 赤色は、緑色は、、、青色は、、 色の範囲の指定に役立ちます サンプルコード(指定した範囲に 取得したBGRがあてはまるか キューブ色を取得)を実行 これは うまくいってるほうです   BGRの範囲を指定して その範囲内なら 特定の色をかえすのむずかしさが下です 実際のところ 5番と7番が色が違っています。 環境でかなりかわります 精度がひくい。 この制度の低さを 解消するには 環境に左右されないようにすれば かなり精度がよくなります。 周りから 光をいれずに 一定の光量を キューブにあてればよいのです。 ルービックキューブの台をつくり(3Dプリンター) 型紙とかでも つくれると思います(そのときは 必ず白色にしてくだい かつ光が反射しない素材がいいです、、、ルービックキューブもテカテカしてるものだと 光があたると 白っぽくなってしまうのでなかなか精度がだしにくくなります。) 下からスマートフォンをいれて カメラを起動させてますライトをONにすれば あるていど 一定のあかるさになるので これで 安定した BGRを取得することができます ちなみに ルービックキューブはこれを使用してます。 Gancube xs 参考にしたサイトが見つからない、、、申し訳ない
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PyAutoGUIでお絵描き

ペイントで正弦波を描きます。 曲線は折れ線(直線ツール)でなく鉛筆ツールで描いています。 ペイントで折れ線を描くのは難しいので。 ソース(座標軸部分は省略) from ctypes import * import math import numpy as np import pyautogui FindWindowEx = windll.user32.FindWindowExW SetForegroundWindow = windll.user32.SetForegroundWindow hwnd = FindWindowEx(None, None, None, "無題 - ペイント") if hwnd == 0: raise Exception # ペイントを前面に出す SetForegroundWindow(hwnd) # 鉛筆ツール pyautogui.moveTo(500, 160) # アイコンの座標(PCに依存) pyautogui.click() # 原点(キャンバス上の適当な点) [x0, y0] = [400, 400] pyautogui.moveTo(x0, y0) R = 80 for t in np.arange(0.0, 2.1*math.pi, 0.1): [x, y] = [x0 + 40*t, y0 - R*math.sin(t)] pyautogui.dragTo(x, y) こんなことも。 こういうシーケンス図を生成できるツールがないかと思ってるんだけど、さすがにこれは無理がある... きっかけ 今年のMATLAB EXPOのライトニングトーク MATLABとArduinoのRPA活用 ターゲット(Windows XPなどの古いPC)のマウス入力を、MATLABとArduinoでエミュレートして自動化するというもので、ターゲット上のペイントでお絵描きをしていました。 それが面白かったので、PyAutoGUIでやってみようかなと。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dockerでpythonの開発環境を作成し、requests-htmlでのスクレイピング結果をslackに送信する

はじめに スクレイピングを行うにあたりこちらの注意事項まとめを参考にさせていただきました。 概要 requests-htmlでスクレイピングし、slackにメッセージを飛ばす仕組みを実装します。 スクレイピング対象のページが動的なページのためBeautiful Soupが使えず、requests-htmlを使用しました。 今回、個人利用の目的で 1. yahooニュースの東京都のコロナ関連情報ページをスクレイピング 2. 表示されている3件のニュースのタイトルと記事urlを取得する 3. 取得結果をslackの任意のチャンネルに投稿する という仕組みを実装します。 環境/使用する技術 MacOS Big Sur python 3.6 スクレイピングするためのライブラリ requests-html Docker version 19.03.13(開発環境構築) docker-compose version 1.27.4(開発環境構築) slack webhook 開発環境構築 pythonのコードを書くための使い捨て開発環境を作成します。Dockerを使用します。 今回使用するイメージはpythonの3.6バージョンです。 ※ requests-htmlがpython3.6のみサポートしているため 開発マシン(Mac)側では適当なディレクトリを掘ってソースコードを以下のように配置します。 ├── Dockerfile ├── docker-compose.yml └── src └── checkNews.py // スクレイピングするファイル Dockerfile Dockerfileを書きます。 touch Dockerfile Dockerfileは以下とします。 Dockerfile FROM python:3.6 WORKDIR /usr/src/pythonwork RUN apt-get update && \ pip install requests-html && \ pip install pyppeteer && \ pip install slackweb docker-compose.yml docker-compose.ymlを作成します。 touch docker-compose.yml docker-compose.ymlは以下とします。 docker-compose.yml version: "3.8" services: python: build: . volumes: - ./src:/usr/src/pythonwork tty: true working_dir: /usr/src/pythonwork コンテナの作成 コンテナを作成します。 docker-compose build 開発環境構築は以上です。 webhookの利用設定 slackに投稿する際、Incoming webhookを使うのでこちらに沿って設定を行ってください。 スクレイピングするソースコード yahooの東京都のコロナニュースの件名と記事のurlを3件取得し、slackの#generalチャンネルに投稿します。 from requests_html import HTMLSession import slackweb import re news_url = "https://news.yahoo.co.jp/pages/article/covid19tokyo" slackweb_url = "https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxxx" # セッション開始 session = HTMLSession() r = session.get(news_url) # HTMLを生成 r.html.render() for num in range(1, 4): # スクレイピング article_title = r.html.find("#tab_1 > ul.dlpThumbLink > li:nth-child(" + str(num) + ") > a > span.dlpThumbText > span:nth-child(1)", first=True) article_title_text = article_title.text article_link = r.html.find("#tab_1 > ul.dlpThumbLink > li:nth-child(" + str(num) + ") > a", first=True) article_url = article_link.absolute_links # slackへ投稿 slack = slackweb.Slack(url=slackweb_url) slack.notify(text=article_title_text + '\n' + re.sub("\{|\}|'", "", str(article_url)), channel="#general", username="covid19-news-bot", icon_emoji=":eyes:", mrkdwn=True) スクレイピングを実行する コンテナを立ち上げ、コンテナ内に入ります。 docker-compose up -d docker-compose exec python bash checkNesw.pyを動かします。 初回実行ではヘッドレスブラウザをインストールします。[W:pyppeteer.chromium_downloader] start chromium download. # python checkNews.py エラー:pyppeteer.errors.BrowserError: Browser closed unexpectedlyが出力される root@353345a20664:/usr/src/pythonwork# python checkNews.py [W:pyppeteer.chromium_downloader] start chromium download. Download may take a few minutes. 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 108773488/108773488 [00:03<00:00, 32959625.14it/s] [W:pyppeteer.chromium_downloader] chromium download done. [W:pyppeteer.chromium_downloader] chromium extracted to: /root/.local/share/pyppeteer/local-chromium/588429 Traceback (most recent call last): File "checkNews.py", line 15, in <module> r.html.render() File "/usr/local/lib/python3.6/site-packages/requests_html.py", line 586, in render self.browser = self.session.browser # Automatically create a event loop and browser File "/usr/local/lib/python3.6/site-packages/requests_html.py", line 730, in browser self._browser = self.loop.run_until_complete(super().browser) File "/usr/local/lib/python3.6/asyncio/base_events.py", line 488, in run_until_complete return future.result() File "/usr/local/lib/python3.6/site-packages/requests_html.py", line 714, in browser self._browser = await pyppeteer.launch(ignoreHTTPSErrors=not(self.verify), headless=True, args=self.__browser_args) File "/usr/local/lib/python3.6/site-packages/pyppeteer/launcher.py", line 306, in launch return await Launcher(options, **kwargs).launch() File "/usr/local/lib/python3.6/site-packages/pyppeteer/launcher.py", line 167, in launch self.browserWSEndpoint = get_ws_endpoint(self.url) File "/usr/local/lib/python3.6/site-packages/pyppeteer/launcher.py", line 226, in get_ws_endpoint raise BrowserError('Browser closed unexpectedly:\n') pyppeteer.errors.BrowserError: Browser closed unexpectedly: エラーメッセージで調べてみると、ヘッドレスブラウザを使うにはライブラリが不足しているようです。 こちらの記事を参考にpyppeteerに必要なライブラリをapt-get installしました。 以下参考先サイトさまから引用させていただきます。 sudo apt-get install gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget コンテナにはrootログインしているのでsudoは省き、実行します。(※DockerfileのRUNに↓を書いておけば再発しません。その際は-yを忘れずに) apt-get install gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget 再度実行。 root@353345a20664:/usr/src/pythonwork# python checkNews.py root@353345a20664:/usr/src/pythonwork# slackに投稿できました。urlをクリックするとちゃんと記事のページを開くことができました。 参考サイト pyppeteer.errors.BrowserErrorの対応策 Dockerのバージョンとdocker-composeのサポート対応表 requests-htmlについて
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンピュータ演習A 21 Day 5th(5/24): c5(Python hit and blow text), blow判定のバグ修正

blow判定注意 「いちばんやさしいPython入門教室」15章のヒットアンドブローゲームのブロー判定にバグがあります. テキストの blow = 0 for j in range(4): for…
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ArcGIS Pro のマップフレームをポリゴンにする

対象読者 ArcGIS Pro で図面を作成する方 実行環境 Windows10, Python 3.7.9 はじめに 図枠の異なる図面を複数作成すると、それぞれの図面の位置関係を把握するための索引図が欲しくなります。 ここでは索引図に使えるような図枠を、ポリゴンタイプのシェープファイルで作成します。 処理としては、ArcGIS Pro レイアウト内のマップフレームからポリゴンデータへの変換になります。 レイアウトやマップフレームの設定の読み取りは、素直に ArcPy を使います。 ポリゴンデータの作成には ArcPy のデータアクセスモジュールを使ってもよいのですが、個人的趣味で pyshp ライブラリを使います。 なお pyshp は、ArcGIS Pro のデフォルト環境にインストールされています。 スクリプトをツールボックスにツールとして登録し、ArcGIS Pro アプリケーションから実行します。 マップの角度について マップの角度設定が0であれば、マップの範囲の南西端・北東端座標が作成したいポリゴンの四隅座標と一致するので、取得した座標値で四角形のポリゴンを作成することができます。 マップに角度をつけている場合、すなわち北がマップの上を向いてない場合は工夫が必要になります。 ここでは、マップの角度をもとに戻してからマップ範囲の南西端・北東端座標を取得した後、マップの中心座標を中心にマップに設定されていた角度だけ回転させてポリゴンの四隅座標を計算することにしています。 つまり、点Aを中心として点Bをθ度回転する処理を四隅座標に対して行います。 numpy で行列計算を使う方法がスマートですが、行列があまり得意ではないという個人的理由により、以下の式1で計算しています。 二次元直交座標系の点 (x, y) を原点 (0, 0) を中心として反時計回りに角度θ度回転する式です。 x' = x*cosθ - y*sinθ y' = x*sinθ + y*cosθ マップ中心 (X, Y) は座標系の原点ではないので、式を変形して使います。 x' = (x-X)*cosθ - (y-Y)*sinθ + X y' = (x-X)*sinθ + (y-Y)*cosθ + Y コード import os.path import math import arcpy import shapefile # 関数:(x0, y0)を中心として(x_in, y_in)を時計回りにrotation[度]回転した座標を返す def rotate(x0, y0, rotation, x_in, y_in): # 式の定義を考慮して角度の正負を逆転し、ラジアンに変換 rad = math.radians(-1*rotation) # 相対座標 x = x_in - x0 y = y_in - y0 # 回転 x_out = x*math.cos(rad) - y*math.sin(rad) + x0 y_out = x*math.sin(rad) + y*math.cos(rad) + y0 return x_out, y_out # レイアウト名 lyt_name = arcpy.GetParameterAsText(0) # マップフレーム名 mpf_name = arcpy.GetParameterAsText(1) # 出力フォルダ path_fold = arcpy.GetParameterAsText(2) # シェープファイル名 path_shp = os.path.join(path_fold, mpf_name + '.shp') # 座標系定義情報ファイル名 path_prj = os.path.join(path_fold, mpf_name + '.prj') # ArcGIS Pro で現在開いているプロジェクトを取得 aprx = arcpy.mp.ArcGISProject('CURRENT') # レイアウトを取得 lyt = aprx.listLayouts(lyt_name)[0] # マップフレームを取得 mpf = lyt.listElements('MAPFRAME_ELEMENT', mpf_name)[0] # マップの座標系を取得 sr = mpf.map.spatialReference # マップフレームの縮尺を取得 scale = mpf.camera.scale # マップフレームの傾き(反時計回りの回転角[度])を取得 rotation = mpf.camera.heading # 中心座標を取得 x0 = mpf.camera.X y0 = mpf.camera.Y # マップフレームの回転を解除(それぞれの座標軸と辺の方向をそろえるため) mpf.camera.heading = 0 # マップフレームの範囲を取得 extent = mpf.camera.getExtent() # 四隅座標を取得 xmin, ymin, xmax, ymax = [float(coord) for coord in str(extent).split(' ')[:4]] # マップフレームの角度を戻す mpf.camera.heading = rotation # ポリゴンの四隅座標を取得 x1, y1 = rotate(x0, y0, rotation, xmin, ymin) x2, y2 = rotate(x0, y0, rotation, xmin, ymax) x3, y3 = rotate(x0, y0, rotation, xmax, ymax) x4, y4 = rotate(x0, y0, rotation, xmax, ymin) # シェープファイル書き込み with shapefile.Writer(path_shp) as w: w.field('scale', 'N') w.field('rotation', 'N') w.poly([ [[x1, y1], [x2, y2], [x3, y3], [x4, y4], [x1, y1]] ]) w.record(scale, rotation) # プロジェクトファイル作成 with open(path_prj, 'w') as f: # 座標系を定義するパラメータの文字列を取得 text = sr.exportToString() f.write(text) ツールボックスへの実装 ツールボックスに上述のスクリプトを追加します。 第2パラメータにレイアウトとの依存関係をもたせたマップフレームを指定したかったのですが、該当しそうなデータタイプが見当たらなかったため、String としました。 実行 マップを30度(反時計回り)回転させたマップフレームをレイアウトに配置し、ツールを実行します。 結果 マップフレームで表示している範囲を示すのポリゴンデータが作成されました。 テーブルには縮尺と角度が格納されています。 最後に マップフレームをポリゴンに変換しました。 アプリケーションに同様のツールが標準で組み込まれていても良さそうなものですが、どうも無いようです。 なお ArcGIS Pro の Python リファレンス はほとんど英語ですが、マップやレイアウトをクラスとして扱えるので、英語が分からなくてもそれらの設定値の取得は結構簡単です。 フリー百科事典『ウィキペディア(Wikipedia)』 回転 (数学) ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NumbaでCUDAが動かないときに試すこと

*本記事で報告する不具合はNumbaのPR #7106 で修正予定です。質問者が私で、いちおう日本語訳まとめ的に書いておきます。 https://github.com/numba/numba/issues/7104 背景 Ubuntu-20.04 on WSL2にCUDAを何度か入れ直した結果、/usr/lib/wsl/lib/配下にlibcudaとつくファイルがいくつもできてしまった。このためNumbaがドライバを認識できず動作しなかった。 対応策 もちろんベストはアップデートにPR #7160が反映されるのを待つ。 一刻も早く対処したい場合、応急処置的に環境変数 NUMBA_CUDA_DRIVERに値"/usr/lib/wsl/lib/libcuda.soを設定すると問題なく動作する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MaixduinoをWiFiに接続する!(MaixPyを使ったPythonコード)

1.概説 Maixduinoには、ESP32-WROOM-32チップが搭載されているため、WIFI通信が可能です。 以下のSipeed社公式Githubサイトに参考となるコードがあるので、これを踏まえて、Socket通信ができるところまでのコードを紹介します。試しにヤフージャパンのIPアドレスを取得しています。内容の信憑性と実行については自己責任でお願いしますね。 2.コード(Maixduino) import socket import network import gc import os import lcd, image import machine from board import board_info import utime from Maix import GPIO from fpioa_manager import * #iomap at MaixDuino fm.register(25,fm.fpioa.GPIOHS10)#cs fm.register(8,fm.fpioa.GPIOHS11)#rst fm.register(9,fm.fpioa.GPIOHS12)#rdy fm.register(28,fm.fpioa.GPIOHS13)#mosi fm.register(26,fm.fpioa.GPIOHS14)#miso fm.register(27,fm.fpioa.GPIOHS15)#sclk nic = network.ESP32_SPI(cs=fm.fpioa.GPIOHS10,rst=fm.fpioa.GPIOHS11,rdy=fm.fpioa.GPIOHS12, mosi=fm.fpioa.GPIOHS13,miso=fm.fpioa.GPIOHS14,sclk=fm.fpioa.GPIOHS15) wifi = nic.scan() for v in wifi: print(v) print() nic.isconnected() nic.connect("<SSID>", "<WifiPassword>") print(nic.isconnected()) s = socket.socket() addr = socket.getaddrinfo("www.yahoo.co.jp", 80)[0][-1] s.connect(addr) 3.最後に MaixPyはMicropythonをベースにしていると思われるのですが、今のところ、なんとインターネット通信でお決まりのSSLやREQUESTSモジュール(MicropythonでいうところのUSSLやUREQUESTS)が使えません。SIPEED社の更新を待つか、もしインストールの仕方がわかる人がいれば教えてほしいのですが、これは結構面倒な話です。つまり、Socket通信でコードを書いていくしかないということ。SSLが使えないとすると、データベースへのセキュアアクセスなどもできないのではないか?と心配になります。このあたりの解決策をご存知の方、ぜひ教えてください! ここがMaixduino、Maixpyの改善点だと思います。少し価格で妥協ができるのであれば、やはりRasberryPiの方がモジュールの多様性という点では、優れているように思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pyenv globalでpythonのバージョンが切り替わらない(mac)

pythonの環境設定をこちらの記事に沿って進めていきました。 macではデフォルトでpythonがインストールされていますが、2系の古いバージョンであるため、こちらを使わず使わず新たにインストールしていきます。 ここで、pythonのバージョン管理ツールpyenvを使用してインストールしていきます。 記事通りの内容は割愛しています。 zsh ..... % pyenv global 3.9.5 % python --version Python 2.7.16 ...切り替わらない ググると eval "$(pyenv init -)" をやれと書いてある。 早速実行 zsh % eval "$(pyenv init -)" WARNING: `pyenv init -` no longer sets PATH. Run `pyenv init` to see the necessary changes to make to your configuration. ここでエラーが発生 pyenv initをしろと書いてある。 実行 % pyenv init # (The below instructions are intended for common # shell setups. See the README for more guidance # if they don't apply and/or don't work for you.) # Add pyenv executable to PATH and # enable shims by adding the following # to ~/.profile and ~/.zprofile: export PYENV_ROOT="$HOME/.pyenv" export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init --path)" # Load pyenv into the shell by adding # the following to ~/.zshrc: eval "$(pyenv init -)" # Make sure to restart your entire logon session # for changes to profile files to take effect. 記事の方では echo 'export ~'とやっていたが。。 echoなしで、もう一度設定をやり直す必要があるようだ。 実行 zsh % export PYENV_ROOT="$HOME/.pyenv" % export PATH="$PYENV_ROOT/bin:$PATH" % eval "$(pyenv init --path)" % eval "$(pyenv init -)" % pyenv global 3.9.5 % python --version Python 3.9.5 切り替えに成功 今後なぜechoコマンドでの設定が成功しなかった理由を調べます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python]Seleniumを用いたDocker環境でのE2Eテスト方法 メモ

Docker環境でのSeleniumを用いたE2Eテスト方法についてメモする。 構成 事前準備 Docker環境で動かすため、こちらの構成でdocker-compose.ymlやDockerfileなどdocker-seleniumを利用するための設定を準備する。 テストコードtest.py seleniumでQiita検索した結果を確認する。 テストコードの大まかな書き方 テスト前処理:ドライバ設定などを記述する。 テスト実行:ブラウザ操作とその際の期待結果を記述する。 テスト後処理:ドライバクローズなどを記述する。 import os import unittest from selenium.webdriver.common.keys import Keys from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.expected_conditions import presence_of_element_located from selenium.webdriver.common.desired_capabilities import DesiredCapabilities class SampleTest(unittest.TestCase): # テスト前処理: ドライバ設定 def setUp(self): self.driver = webdriver.Remote( command_executor=os.environ["SELENIUM_URL"], desired_capabilities=DesiredCapabilities.CHROME.copy() ) # テスト後処理:ドライバクローズ def tearDown(self): self.driver.quit() # Qiita検索 テスト def test_qiita_search(self): # 任意のHTMLの要素が特定の状態になるまで待つ # ドライバとタイムアウト値を指定 wait = WebDriverWait(self.driver, 10) # テスト対象へアクセス self.driver.get("https://qiita.com/") # 検索ボックス入力→検索実行 self.driver.find_element(By.NAME, "q").send_keys( "selenium" + Keys.RETURN) # 1件目の検索結果を取得(描画されるまで待機) first_result = wait.until( presence_of_element_located((By.CSS_SELECTOR, "h1"))) print("first_result:"+first_result.get_attribute("textContent")) # タイトルに`selenium`が含まれるかを判定 assert "selenium" in ( first_result.get_attribute("textContent")).lower() if __name__ == '__main__': unittest.main() 動作確認 ビルド→起動→コンテナの中に入る。 docker-compose build --no-cache docker-compose up -d docker-compose exec app bash コード実行 cd test python test.py 期待値 ...省略 ---------------------------------------------------------------------- Ran 1 test in 7.221s OK 参考情報 pythonでseleniumのテストコードを書いてみる unittest --- ユニットテストフレームワーク
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

streamlitで動的なウェジェット(ボックス)のまとめ

はじめに 前回の続きでstleamlitを活用してインタラクティブなウェジェットを作っていく。 インタラクティブとは双方向や対話式といった意味で動的な物を指す インタラクティブなウェジェットは簡単に言うと動的なパーツということである。 今回作るのは 「チェックボックス、セレクトボックスとテキストボックス、スライダーである。」 チェックボックス st.checkbox 結果はtrueとfalseで帰ってくる→if文に変えることでチェックボックスに条件を付けることが出来る。 ここではチェックボックスにチェックを入れることで画像を表示させてみる。 if st.checkbox('Show Image') img = Image.open('~~~~.~~~') st.image(img,caption='kizunaAI',use_column_width=True) セレクトボックス st.selectbox 1~10の好きな数字を選択するシステムを作ってみる。 st.selectbox('あなたの好きな数字は', list(range(1,11)) これでラベル付けと1~10の範囲のリストを作ることが出来た。 さらに今回はoptionという変数を付けることで「あなたの数字は~です」という形を作りたい。 option=st.selectbox('あなたの好きな数字は', list(range(1,11)) 'あなたが好きな数字は',option,'です。' 上の画像は結果画面をスクショしたものだが、好きな数字は隠れてしまっているがきちんと反映されていた。 テキストボックス テキストボックスも先のセレクトボックスやチェックボックスと大差なく、 st.text_inputと置くことで出来上がる。 変数を使って、結果を丁寧に表示させることをしてみた 結果を下に貼っておく。 スライダー 最後にスライダーだ。これはゲージのような物で、 動く範囲の数値と初期位置を最初にとってあげる必要がある。 st.slider('あなたの今日の調子は?',0,100,50) カッコ内、最初の2つの数字が範囲で後ろの50というのが初期位置を表している。 これも先ほど同様変数で変数に代入させ、ゲージの数値を表示させたものが下の画像である。 まとめ 1行の簡単な文のみでこれほどまでに本格的に出来上がるのかととても感動した。 まだまだstreamlitのさわりでしかないが、やはりCSS無しでもpythonのみで完結できる簡潔さはちょっとした物を作る上ですごく楽だと感じた。 それと今回はコメントアウトの方法を覚えたのでメモっておく。 ・コメントアウトのやり方 選択 ➡ Ctrl+(K→C)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python]docker-selenium環境 構築方法 メモ

環境依存しないスクレイピング/E2Eテスト環境を作りたかったため、docker-seleniumの利用方法を調べた。その備忘録記事。 docker-selenium Selenium Web アプリケーションをテストするためのフレームワーク。 各種 Web ブラウザに対する操作を自動化することができる。 docker-selenium SeleniumをDocker環境で動かすためのイメージ 構成 コード ディレクトリ構成 ルート - app ----- text --- test.py | |__ Dockerfile | |__ requirements.txt |__ docker-compose.yml docker-compose.yml version: "3" services: selenium: image: selenium/standalone-chrome-debug:3.2.0-actinium ports: - 4444:4444 - 5900:5900 app: build: ./app volumes: - ./app:/app environment: SELENIUM_URL: http://selenium:4444/wd/hub tty: true volumes: - /dev/shm:/dev/shm Dockerfile Python実行環境設定。依存ライブラリインストール含む。 FROM python:3.7 ENV PYTHONIOENCODING utf-8 WORKDIR /app COPY . . RUN pip install -r requirements.txt requirements.txt 依存ライブラリ定義 selenium test.py seleniumでGoogle検索した結果の1番目のタイトルを表示するコード。 from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.expected_conditions import presence_of_element_located from selenium.webdriver.common.desired_capabilities import DesiredCapabilities if __name__ == '__main__': # Selenium サーバーへ接続する。 driver = webdriver.Remote( command_executor=os.environ["SELENIUM_URL"], desired_capabilities=DesiredCapabilities.CHROME.copy() ) # 任意のHTMLの要素が特定の状態になるまで待つ # ドライバとタイムアウト値を指定 wait = WebDriverWait(driver, 10) # Googleにアクセス driver.get("https://google.com") # "selenium"で検索 driver.find_element(By.NAME, "q").send_keys("selenium" + Keys.RETURN) # 1件目の検索結果を取得(描画されるまで待機) first_result = wait.until( presence_of_element_located((By.CSS_SELECTOR, "h3"))) print(first_result.get_attribute("textContent")) driver.quit() 動作確認 ビルド→起動→コンテナの中に入る。 docker-compose build --no-cache docker-compose up -d docker-compose exec app bash コード実行 cd test python test.py 期待値:10分で理解する Selenium - Qiitaのように検索結果が表示される。 参考情報 Dockerコンテナからseleniumを使ってスクレイピング DockerでPython-Seleniumスクレイピング環境を立てた docker-seleniumを動かしてみる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む