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

PythonでTorを使用する

はじめに スクレイピングの勉強として、pythonを使ってあるサイトから情報を抜き出すコーディングを試していたところ、急にレスポンスが遅くなったり、アクセスを拒否されたりしたことがあった。 テザリングでインターネットに接続したら元に戻ったので、同じIPから何度もリクエストが来ている事が理由と理解したのだが、その時色々と調べてTorという接続経路の匿名化を実現するソフトウェアを知ったので、試しにそれもどんな感じなのか使ってみた。今回の記事では、その時の構築内容や確認内容をメモとして残す。 実行環境 Ubuntu 20.04LTS(GCP上) Tor version 0.4.2.7. Python 3.8.5 メモ内容  $ sudo apt update $ sudo apt upgrade -y tor のインストール $ sudo apt install tor $ tor --version Tor version 0.4.2.7. torの設定 sudo nano /etc/tor/torrc で以下の様に追記。 /etc/tor/torrc # 除外する中継ノード ExcludeNodes {jp},{us},{gb},{ca},{au},{nz},{dk},{fr},{nl},{no},{de},{be},{it},{es},{il},{sg},{kr},{se},127.0.0.1 # 除外する出口ノードについて ExcludeExitNodes {jp},{us},{gb},{ca},{au},{nz},{dk},{fr},{nl},{no},{de},{be},{it},{es},{il},{sg},{kr},{se},{cn},{ru},127.0.0.1 # 経由するサーバーの数 NumEntryGuards 4 # 設定を確実に守るかどうか(`1`の場合は絶対に接続しない `0`の場合は接続する場合がある。) StrictNodes 1  ※所謂【スパイ協定】5-Eyes, 9-Eyes, 14-Eyes, 41-Eyesの中で、14-Eyesの国は中継ノードとして今回は除外。   詳しく知りたい人は、このサイトとか参考になるかもしれないです。【参考1】【参考2】 Python 関連のインストール $ python3 -V Python 3.8.5 $ sudo apt install -y python3-pip $ sudo apt install -y jupyter-notebook # python から tor のSOCKSポートにアクセスするため $ pip3 install pysocks # Python を使ったスクレピング関連のテストをしたかったので以下のパッケージをインストール $ pip3 install numpy pandas beautifulsoup4 $ pip3 install selenium $ pip3 install chromedriver-binary==89.0.4389.23 Chrome のインストール  ※今回はseleniumでGoogle操作も試したかったので $ sudo sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' # 公開鍵をダウンロードして登録する $ sudo wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - # ここで一旦、パッケージリストを最新化 $ sudo apt update # ダウンロードできる一覧を確認 $ apt list google* # Chromeのダウンロード $ sudo apt install google-chrome-stable Jupyter notebook の設定 # 設定ファイルの作成 $ jupyter notebook --generate-config # 初期ディレクトリとする場所を作成する $ mkdir ./workspace # sudo で作るとユーザーの書き込み権限が付かなくなる可能性がある # 設定ファイルの編集 $ sudo nano /home/【アカウント名】/.jupyter/jupyter_notebook_config.py # 以下を追記内容(jupyter_notebook_config.py) c.NotebookApp.ip = '0.0.0.0' # 外部からアクセスできる様にする。 c.NotebookApp.notebook_dir = '/home/【アカウント名】/workspace' # 上記で作成した初期ディレクトリを指定。 GCPコンソール側でFW設定をする(やり方は割愛)  jupyter-notebookのデフォルトポートが8888なので、今回はそこを開けておいた。 各サービスの起動 # Torサービスの起動 $ sudo service tor start $ sudo service tor status # 現状のIPアドレス確認 $ wget -qO - https://api.ipify.org; echo ***.***.***.*** # Tor経由のIPアドレス確認 $ torsocks wget -qO - https://api.ipify.org; echo ***.***.***.*** 上記により、一旦torが正常に動いているところは確認できた。 JupyterNotebookを起動して、ローカル⇒GCP上のVM(notebook)に接続 $ jupyter notebook #~~~~~~~省略~~~~~~~~~~~~~~~~~~~ http://localhost:8888/?token=d09865b1c31d039b59789f0071d4dfc53e0912d68a0c6363 or http://127.0.0.1:8888/?token=d09865b1c31d039b59789f0071d4dfc53e0912d68a0c6363 上記の様に、jupyter-notebookの起動が問題なくできていれば、ローカルPCより適当なブラウザで【GCP上のVMの外部IPアドレス】:8888に接続すれば、notebook画面に入るはず。  ※初回はTokenの入力を求められるので、上記のtoken=より先の英数字をコピペすればOK。 テスト用の .ipynb ファイルを作成 jupyter-notebookで以下のファイルを作成して、実行してみるとtor経由でIPが変更されていることが分かる。 test.ipynb import requests import subprocess proxies = { 'http': 'socks5://localhost:9050', 'https': 'socks5://localhost:9050' } # torを経由する場合と経由しない場合のIPアドレスをチェック res = requests.get('https://ipinfo.io').json() print('torを経由しない場合(GCP上のVM外部IPと同じはず):\n', res) res = requests.get('https://ipinfo.io', proxies=proxies).text print('\n\ntorを経由する場合(IPが変わるはず):\n', res) # torの再起動(これをするとtor経由のIPアドレスが変更される) args = ['sudo', 'service', 'tor','restart'] subprocess.call(args) Tor経由でChromeを使うことも、以下の記事を参考に試してみたのでご参考まで。 https://qiita.com/___xxx_/items/419a86ea2d3d1ab9f415 test2.ipynb # Chromeを使ったスクレイピング import requests from selenium import webdriver import chromedriver_binary from selenium.webdriver.common.keys import Keys from bs4 import BeautifulSoup # オプションでPROXYを設定 PROXY = "socks5://localhost:9050" options = webdriver.ChromeOptions() options.add_argument('--headless') options.add_argument('--lang=ja-JP') options.add_argument('--no-sandbox') options.add_argument('--disable-gpu') options.add_argument('--proxy-server=%s' % PROXY) driver = webdriver.Chrome(chrome_options=options) # Torを経由できているかの確認 driver.get("http://check.torproject.org") temp = BeautifulSoup(driver.page_source) print(temp) 最後に 今回の記事はあくまで、torの構築のメモと動きの確認が目的なので、tor の技術を悪用することは絶対にしないようにしましょう! (基本的に悪い事したらバレます。)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

素敵なデータ構造:ヒープ by Python

データ構造の説明 ヒープとは、 ・二分木を用いるデータ構造で、 ・木の深さh-1以下の部分については完全二分木を形成し、 ・各頂点と親頂点との間に親頂点≧子頂点の関係が成立し、 ・値xの検索には適さないですが、最大値の取得が得意 なようです。 heap.py class Heap(): heap = [] # 値の追加 # 一番最後の葉に追加したのち、「自分の親と比較して逆転していれば入れ替える」を繰り返す def push(self, x): self.heap.append(x) i = len(self.heap)-1 while i > 0: p = (i-1)//2 if self.heap[p] >= x: break self.heap[i] = self.heap[p] i = p self.heap[i] = x return self.heap # 最大値の取得 def top(self): if self.heap != "": return self.heap[0] else: return -1 # 最大値の削除 # 一番最後の葉を根と入れ替えたのち、「自分の子と比較して逆転していれば入れ替える」を繰り返す def pop(self): if self.heap == "": return x = self.heap[-1] self.heap.pop() i = 0 while i*2+1 < len(self.heap): child1 = i*2+1 child2 = i*2+2 if child2 < len(self.heap) and self.heap[child2] > self.heap[child1]: child1 = child2 if self.heap[child1] <= x: break self.heap[i] = self.heap[child1] i = child1 self.heap[i] = x return self.heap h = Heap() h.push(3) h.push(43) h.push(15) h.push(29) h.pop() h.push(732) h.push(88) h.push(14) h.push(51) h.pop() # 出力[88, 29, 51, 3, 15, 14] アルゴリズムへの適用 ダイクストラ法 Dijkstra-heap.py import heapq # アルゴリズム定義 # ヒープを用いたダイクストラ法 def Dijkstraheap(G, dist, s): used = [False for i in range(N)] dist[s] = 0 que = [] heapq.heappush(que, (dist[s], s)) while que: v = que[0][1] d = que[0][0] heapq.heappop(que) if d > dist[v]: continue for e in range(len(G[v])): if dist[G[v][e][0]] > dist[v] + G[v][e][1]: dist[G[v][e][0]] = dist[v] + G[v][e][1] heapq.heappush(que, (dist[G[v][e][0]], G[v][e][0])) # 入力受取 # 頂点数、辺数、始点 N, M, s = map(int, input().split()) # グラフの生成 # 重み付き単方向グラフ G = [[] for i in range(N)] for i in range(M): a, b, w = map(int, input().split()) G[a].append([b, w]) # ダイクストラ法の結果を格納するリストの生成 # 初期値として無限大の代わりに10000を格納 dist = [10000 for i in range(N)] # アルゴリズム実行 # ヒープを用いたダイクストラ法 Dijkstraheap(G, dist, s) # 結果出力 # sから各頂点への最短距離を出力 for i in range(N): print(i, ": ", dist[i]) # 入力 # 6 9 0 # 0 1 3 # 0 2 5 # 1 2 4 # 1 3 12 # 2 3 9 # 2 4 4 # 3 5 2 # 4 3 7 # 4 5 8 # 出力 # 0 : 0 # 1 : 3 # 2 : 5 # 3 : 14 # 4 : 9 # 5 : 16
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonでインスタンス情報を調べるためのtips

機械学習でクラスを調べるときに頻繁に使いそうだったのでメモ。 インスタンスが持つインスタンス変数の一覧を取得する # .__dict__属性を取得 class hoge: def __init__(self,x,y): self.x = x self.y = y ins_sample = hoge(30,20) from pprint import pprint # モジュール、クラス、インスタンス、あるいはそれ以外の dict 属性を持つオブジェクトの、 dict 属性を返す pprint(vars(ins_sample),width=100) # {'x': 30, 'y': 20} # そのオブジェクトの有効な属性のリストを返そうと試みる pprint(ins_sample.__dict__) # {'x': 30, 'y': 20} ex.PCA(主成分分析)においてインスタンスが持つインスタンス変数を調べる from sklearn.decomposition import PCA PCA = PCA() X0 = np.arange(100).reshape(100,1) X1 = 2*X0 X_demo = np.append(X0,X1, axis =1) + np.random.normal(loc = 0, scale=15, size=200).reshape(100,2) PCA.fit(X_demo) pc = PCA.transform(X_demo) # モジュール、クラス、インスタンス、あるいはそれ以外の dict 属性を持つオブジェクトの、 dict 属性を返します pprint(vars(PCA),width=100) #width=80だと見づらい {'_fit_svd_solver': 'full', 'components_': array([[ 0.31587351, 0.94880131], [-0.94880131, 0.31587351]]), 'copy': True, 'explained_variance_': array([9245.73144191, 190.24761265]), # 分散共分散行列の固有値 'explained_variance_ratio_': array([0.97983806, 0.02016194]), 'iterated_power': 'auto', 'mean_': array([ 47.17932959, 147.75046498]), # X_demo.mean(axis=0) --> array([ 47.17932959, 147.75046498]) 'n_components': None, 'n_components_': 2, # 主成分の数 'n_features_': 2, 'n_features_in_': 2, 'n_samples_': 100, 'noise_variance_': 0.0, 'random_state': None, 'singular_values_': array([956.72744956, 137.23889264]), 'svd_solver': 'auto', 'tol': 0.0, 'whiten': False} # そのオブジェクトの有効な属性のリストを返そうと試みます pprint(PCA.__dict__) # オブジェクトが持つ属性のリストを取得したい dir([object]) 引数がない場合、現在のローカルスコープにある名前のリストを返します。引数がある場合、そのオブジェクトの有効な属性のリストを返そうと試みます。 pprint(dir(X_demo)) # shape属性?メソッドやmean属性?メソッドを持っていることが分かる ['T', '__abs__', '__add__', '__and__', '__array__', '__array_finalize__', '__array_function__', '__array_interface__', '__array_prepare__', '__array_priority__', '__array_struct__', '__array_ufunc__', '__array_wrap__', '__bool__', '__class__', '__complex__', '__contains__', '__copy__', '__deepcopy__', '__delattr__', '__delitem__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__iand__', '__ifloordiv__', '__ilshift__', '__imatmul__', '__imod__', '__imul__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__ior__', '__ipow__', '__irshift__', '__isub__', '__iter__', '__itruediv__', '__ixor__', '__le__', '__len__', '__lshift__', '__lt__', '__matmul__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmatmul__', '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__setitem__', '__setstate__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__xor__', 'all', 'any', 'argmax', 'argmin', 'argpartition', 'argsort', 'astype', 'base', 'byteswap', 'choose', 'clip', 'compress', 'conj', 'conjugate', 'copy', 'ctypes', 'cumprod', 'cumsum', 'data', 'diagonal', 'dot', 'dtype', 'dump', 'dumps', 'fill', 'flags', 'flat', 'flatten', 'getfield', 'imag', 'item', 'itemset', 'itemsize', 'max', 'mean', 'min', 'nbytes', 'ndim', 'newbyteorder', 'nonzero', 'partition', 'prod', 'ptp', 'put', 'ravel', 'real', 'repeat', 'reshape', 'resize', 'round', 'searchsorted', 'setfield', 'setflags', 'shape', 'size', 'sort', 'squeeze', 'std', 'strides', 'sum', 'swapaxes', 'take', 'tobytes', 'tofile', 'tolist', 'tostring', 'trace', 'transpose', 'var', 'view'] おまけ 予約語を調べる時 import keyword 予約語であるかどうか一覧で確認 keyword.kwlist 'etc'という単語が予約語かどうか確認したい時 keyword.iskeyword('etc')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】手軽に始めたい方へ:Google Colaboratoryの始め方

はじめに  Pythonを始めたいけれど設定の仕方がわからない方でも気軽に始めれる『Google Colaboratory』の始め方・簡単な使い方について紹介します。  Google ColaboratoryはWindows/Mac関係なく、インターネットにアクセスできる環境であれば利用できます。 1. Google Colaboratoryって? 2. 始め方 3. ノートブックの操作 4. ファイルのアップロードの仕方 5. ファイルのダウンロードの仕方 6. アップロードしたファイルをPythonで開く 7. おわりに 1. Google Colaboratoryって?  Googleが提供する誰でもブラウザ上でPythonを記述・実行できる無料のサービスです。機械学習、データ分析、教育でPythonを気軽に扱うことができます。面倒な環境構築はほとんどありません。 2. 始め方  まずは簡単なPythonのプログラミングができるところまでを紹介します。  ① 公式サイトにアクセスする。Google Colaboratory公式サイト  ② 左上の『ファイル』をクリックして『ノートブックを新規作成』を選ぶ。  ③ 新しいウインドウでノートブックが開き、Pythonでコードを入力できる状態になる。  たったこれだけです。  それでは実際に簡単なコードを入力して実行してみます。   『Welcome!Google Colaboratory.』を表示させるだけのシンプルなものです。  ① セルの中にprint("Welcome! Google Colaboratory.")と入力する。  ② 実行ボタン(左側にある▷)を押す。  あとは処理が進み下ような結果が出てきたのではないでしょうか。  これでPythonのプログラミングがいつでもできます。 3. ノートブックの操作 【コードの保存】  基本的に自動保存です。初期はUntitled1.ipynbで保存されています(画面の左上に表記があります)。自分で保存したいときには、左上の『ファイル』をクリックして『保存』を選択するだけです。 【ファイル名の変更】  左上に表示されているファイル名にカーソルを合わせ、クリックすると変更できるようになります。  また、左上の『ファイル』をクリックして『名前の変更』を選択してもできます。 【コードを保存したファイルを開く】  左上の『ファイル』をクリックして『ノートブックを開く』を選択し、表示されたウインドウ内の『最近』または『Googleドライブ』のどちらかのタブからファイルを選択すれば開けます。 4. ファイルのアップロードの仕方  外部データをGoogle Colaboratoryで使用するにはデータファイルをアップロードしなければなりません。アップロード方法には①12時間までしか保存されない一時的な方法と②12時間以上保存される長期的な方法があります。  それでは、順番に説明していきます。 4.1 一時的なアップロード(簡単)  ① 左側にあるフォルダマークをクリックする。  ② ↑の付いたマークをクリックする。  ③ 所定の場所に保存した目的のファイルを選択して開く。    すると、下のようなメッセージが出てきますが気にせずOKします(12時間で消えますよ、という確認のメッセージです)。  これで終了です。 4.2 長期的なアップロード(ちょっと手間)  自動的にファイルが消去されないようにGoogleドライブにアップロードする方法です。  事前準備としてGoogleアカウントを取得しておいてください。  ① 左側にあるフォルダマークをクリックする。  ② 三角形の付いたフォルダマークをクリックする。  ③ 下のようなメッセージが出るので閉じ、メッセージの側にある▷をクリックする。  ④ コードが実行されると下のような表示が出てくるので、URLをクリックする。  すると、新しいウインドウが開いてGoogleアカウントを選択するように言われますので、使用したいアカウントを選びます。  ⑤ アカウントへのアクセス許可を求められますので、許可する。  ⑥ ログイン用のコードが表示されるのでコピーする(紙が2枚重なったようなマークを押すと簡単にコピーできます)。  ⑦ コードが表示されているウインドウを閉じてGoogle Colaboratoryのウインドウに戻り、④でクリックしたURLの下の"Enter your authorization code:"と書かれた下の四角の中に、⑥でコピーしたコードをペーストしてリターンキーを押す。  これでGoogleドライブと繋がりましたので、次にファイルのアップロードを行っていきます。  ⑧ 左側にdriveというフォルダとその中にMyDriveという表記が追加されているのを確認する。  ⑨ 確認できたらMyDriveの右側にある縦並びの点3つをクリックし、表示されたアップロードの項目を選ぶ。  ⑩ ファイルを選ぶ。  これで終了です。 5. ファイルのダウンロードの仕方  ダウンロードは両方とも簡単です。ダウンロードしたいファイルにカーソルを合わせると、右側に縦並びの点3つが表示されるので、それをクリックします。ダウンロードの項目が表示されるので選び、あとは自分のPCの保存先を選ぶだけです。 6. アップロードしたファイルをPythonで開く  アップロードしたファイル(今回はCSVファイル)をPythonで開いて内容を表示させる方法を紹介します。 ① ノートブックを新規作成し、上で説明した通りにファイルをアップロードする。 ② セルの中に下記のコードを入力する(#より右側はコメントなので入力不要です)。 #pandasというライブラリーをpdという略記にし、使用できるようにした import pandas as pd #csvファイルを読み込み、データをdfに格納 df = pd.read_csv('ここにはファイルの置いてある場所(パス)を入力') #dfの中身を表示 print(df)  ③ 実行ボタンを押して実行します。  ファイルの内容がセルの下に表示されたら成功です。  ファイルパスは、ファイルにカーソルを置いたときに現れる縦並びの点3つをクリックすると『パス』という項目が出てきますので、それを選択すればパスがコピーされます。あとはコードの目的の場所にペーストすればOKです。 7. おわりに  PythonやGoogle Colaboratoryを初めて使う方でもわかりやすく書いたつもりですが、うまく進まなかったりわかり難いと感じましたらご遠慮なくご連絡ください。質問にお答えし、その後内容を修正いたします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python インタラクティブ モードのプロンプトに色づけ (ついでに cmd.exe, PowerShell, Bash のプロンプトにも言及)

動機といきさつ CLI のプロンプトは色がついていたほうが見やすいし、それだけで使うのがちょっと楽しくなる感じがします。 Windows のコマンド プロンプトならば %SystemRoot%\System32\cmd.exe /f:on /k prompt $e[36m$p$g$e[0m というショートカットをつくるとか…。 PowerShell ならば $PROFILE.CurrentUserAllHosts ファイルに function prompt { #"PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "; $principal = [Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent() $foregroundColor = if (Test-Path -Path Variable:\PSDebugContext) { [ConsoleColor]::Yellow } elseif ($principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { [ConsoleColor]::Red } else { [ConsoleColor]::Cyan } Write-Host -Object 'PS ' -NoNewline -ForegroundColor $foregroundColor Write-Host -Object "$($executionContext.SessionState.Path.CurrentLocation)" -ForegroundColor Magenta Write-Output -InputObject "$('>' * ($nestedPromptLevel + 1)) " # .Link # https://go.microsoft.com/fwlink/?LinkID=225750 # .ExternalHelp System.Management.Automation.dll-help.xml } とかいておくとか…1。 Cygwin のデフォルトのプロンプトが好きで、他の GNU/Linux ディストロでも同様の記述を ~/.bashrc に追記していたりします。 PS1='\[\e]0;\w\a\]\n\[\e[32m\]\u@\h \[\e[33m\]\w\[\e[0m\]\n\$ ' \e[32m, \e[33m, \e[0m が色を指定している部分です2。 本題 Python のインタラクティブ モードのプロンプト文字列は sys.ps1 と sys.ps2 に入っています。また、環境変数 PYTHONSTARTUP に指定したファイルの内容を起動時に読み込んでくれます3。 .bashrc export PYTHONSTARTUP=$HOME/.pythonrc.py .pythonrc.py import sys sys.ps1 = '\001\033[31m\002>>>\001\033[0m\002 ' sys.ps2 = '\001\033[31m\002...\001\033[0m\002 ' \033 が ESC です。ここでは The Python Tutorial のコード表示部分にならって赤 (31) にしています。 なお、ここでのハマりどころはエスケープ シーケンス部分を \001 と \002 でくくってやらないと Ctrl + r などの readline 編集で行の表示がくずれてしまうことです4。 また、 sys.ps1 のドキュメント によると If a non-string object is assigned to either variable, its str() is re-evaluated each time the interpreter prepares to read a new interactive command; this can be used to implement a dynamic prompt. とのことですので、こんなふうにもかいてみました。 "re-evaluated" のタイミングのおかげか、いい感じに ps1 と ps2 が同期してくれます。 .pythonrc.py import sys class _PS: _phase = 0 def __init__(self, string): self.string = string def __str__(self): color = 31 + self._phase self._phase = (self._phase + 1) % 6 return ('\001\033[' + str(color) + 'm\002' + self.string + '\001\033[0m\002 ') sys.ps1 = _PS('>>>') sys.ps2 = _PS('...') https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_prompts ↩ https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters ↩ https://docs.python.org/3/tutorial/appendix.html#the-interactive-startup-file ↩ https://stackoverflow.com/questions/9468435/how-to-fix-column-calculation-in-python-readline-if-using-color-prompt ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Matplotlibで可変数ラインのリアルタイムグラフを描写する

たくさんのセンサー出力値リアルタイムグラフ化したい 例えば、ジャイロのX,Y,Z 加速度計のX,Y,Z などを同時表示したいと思い matplotlibをいじっていたのですが、決められたライン数のみしか表示するサンプルしか見つからず、試行錯誤しながら可変数ラインを表示できるリアルタイムグラフ表示を自分で作ってみました。 使用環境 windows , winpython, numpy, matplotlib 実行結果 Sin, Cos, Tan計算結果をグラフ表示とは別のスレッドで入力しています。 実際に動作させれば、リアルタイムに動作します サンプルでは3ラインだけですが、理論的に無限にグラフを増やせるはずです。 苦労したこと Pythonのタプルをちゃんと理解していなかったので、とても時間がかかった。 (今でもちゃんと理解できていないかもしれませんが...) ソースコード Variable_graph.py import numpy as np import tkinter as tk import tkinter.ttk as ttk import math from matplotlib.backends.backend_tkagg import ( FigureCanvasTkAgg, NavigationToolbar2Tk) from matplotlib.figure import Figure import matplotlib.animation as animation class GraphFrame(ttk.Frame): def __init__(self, master, graph_num): #Graph view fig = Figure() canvas = FigureCanvasTkAgg(fig, master=master) self.x = np.arange(0, 10, 0.1) self.y_datas = [] for y in range(graph_num): print(y) self.y_datas.append( np.zeros(100) ) l = np.arange(0, 10, 0.01) plt = fig.add_subplot(111) plt.set_ylim([-2, 2]) plt.set_position([0.07, 0.05, 0.9, 0.9]) self.lines = [] for y in range(graph_num): yp, = plt.plot(self.y_datas[y]) self.lines.append( yp, ) ani = animation.FuncAnimation(fig, self.animate, l, interval=20, blit=True, ) canvas.get_tk_widget().pack() def animate(self, i): count = 0 for line in self.lines: line.set_ydata(self.y_datas[count]) count+=1 return self.lines[0:] def setValues(self, vals): count = 0 for val in vals: self.y_datas[count] = np.append(self.y_datas[count], val) self.y_datas[count] = np.delete(self.y_datas[count], 0) count+=1 class DataUpdateGraph(ttk.Frame): def __init__(self, master): self.test_count = 0 self.graph_num = 3 #Selectable lines self.master = master graph_frame = ttk.Frame(master) self.gf =GraphFrame(graph_frame, self.graph_num) graph_frame.pack(side = 'right', fill = 'x') self.update() def update(self): vals = [] vals.append( math.sin(math.radians(10*self.test_count))) vals.append( math.cos(math.radians(10*self.test_count))) vals.append( math.tan(math.radians(10*self.test_count))) self.gf.setValues( vals ) self.test_count+=1 self.master.after(100, self.update) if __name__ == "__main__": win = tk.Tk() gh = DataUpdateGraph(win) win.mainloop()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BaseNEncodersのbase=って何を表しているのだろうと考えたらとてもシンプルだった件について

はじめに Category Encodingに関して代表的なものを学習していたのですが、どうもライブラリの使い方がわからず調べるのに時間を費やしたのでまとめたいと思います。 ここからのコードはすべて以下の記事の内容を利用していますので、そちらを簡単に読んでから読み進めていただくと分かりやすいかもしれません。 Category Encodingについてとてもまとまっていますのでお勧めです。 BaseNEncodersについて こちらの記事のコードを引用しています。 BaseNはbaseオプションに指定する値によって、One-Hot / Binary / Ordinal Encodingを使い分けることができます。とあるがどのように指定すればどのエンコーディングが使えるのかよくわからず、今回の記事を書くことになりました。 ちなみに公式ドキュメントにはこのようにあります。 base: int when the downstream model copes well with nonlinearities (like decision tree), use higher base. # 翻訳 ダウンストリームモデルが非線形性(デシジョンツリーなど)にうまく対応する場合は、より高いベースを使用します。 うーんわからない。 すこし調べるのに時間がかかったので、わからない方のために指定の仕方をまとめます。 問題 base=1だとOne-Hotエンコーディング # base=1 bne = ce.BaseNEncoder(cols=cate_col, base=1, drop_invariant=True) bne_df = bne.fit_transform(df[cate_col]) pd.concat([df[cate_col], bne_df], axis=1) base=5だとラベルエンコーディング # base=5 bne = ce.BaseNEncoder(cols=cate_col, base=5, drop_invariant=True) bne_df = bne.fit_transform(df[cate_col]) pd.concat([df[cate_col], bne_df], axis=1) なぜ1でOne-Hotなのでしょうか。なぜ5でlabel-encodingなのでしょうか。 これにはしっかりと理由があります。 この数値はエンコーディングの対象にしているカテゴリ数によって変化します。 base=の指定の仕方 このbaseという引数が表しているものはズバリ〇進数のことでした。 (たしかに言われてみればと思いました) というわけで、base=1を指定すると1進数でカテゴリ変数を表すことになるのでOne-Hotエンコーディングとなります。 今回はカテゴリが4つあります。 ですので、base=5 (5進数)を選択するとラベルエンコーディングになります。(1-4で表現するため) base=2を選択したところ、Binaryになることが確かめられました。 # base=2 bne = ce.BaseNEncoder(cols=cate_col, base=2, drop_invariant=True) bne_df = bne.fit_transform(df[cate_col]) pd.concat([df[cate_col], bne_df], axis=1) おわりに Category Encoderはとても便利なライブラリだと感じました。 これから導入していきたいと思います。カテゴリ変数はどの方法を用いるかとても大切なので、どういうときに利用できるのかまで理解する必要がありそうです。 参考記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python+MeCab】マルコフ連鎖と形態素解析を使って本田圭佑っぽいTwitterつぶやきを自動生成してみた話

はじめに 以前、TwitterでBotを作ってアフィリエイターとして活動していた頃は、 文章は全部、自分で編集なんかしてて、めちゃくちゃ時間かけてたけど、 2021/5/13に初めて、 形態素解析とマルコフ連鎖を使えば、自動で文章が生成できることを知り、感動したので、 実践してみました。 ちなみに、前の会社でインターンをやっていた大学生の子が、 自分のTwitterアカウントのつぶやきを解析させて、 自動生成したつぶやきをBot化していたので、 まずは「自分もエンジニアになったからには、そのスタートラインに立とう!!」 と思い立った次第であります。 ※最初、座学多めです。下の方にコードあります。?‍♂️ 形態素解析とは 難しい漢字が並んでてゲシュタルト崩壊したわっww と言いたいところですが、 形態素解析(けいたいそかいせき、Morphological Analysis)とは、文法的な情報の注記の無い自然言語のテキストデータ(文)から、対象言語の文法や、辞書と呼ばれる単語の品詞等の情報にもとづき、形態素(Morpheme, おおまかにいえば、言語で意味を持つ最小単位)の列に分割し、それぞれの形態素の品詞等を判別する作業である。 参照元:Wikipedia ふむふむw 「けいたいそかいせき」 だそうですw 形態素解析ソフトのMeCabはこんな感じに文章を分解してくれる $ echo "魚を買う。" | mecab 魚 名詞,一般,*,*,*,*,魚,サカナ,サカナ を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ 買う 動詞,自立,*,*,五段・ワ行促音便,基本形,買う,カウ,カウ 。 記号,句点,*,*,*,*,。,。,。 EOS イメージ掴めた。(⌒▽⌒) MeCabのインストールはこちらを参照ください。 マルコフ連鎖とは マルコフ連鎖(マルコフれんさ、英: Markov chain)とは、確率過程の一種であるマルコフ過程のうち、とりうる状態が離散的(有限または可算)なもの(離散状態マルコフ過程)をいう。また特に、時間が離散的なもの(時刻は添え字で表される)を指すことが多い(他に連続時間マルコフ過程というものもあり、これは時刻が連続である)。マルコフ連鎖は、未来の挙動が現在の値だけで決定され、過去の挙動と無関係である(マルコフ性)。各時刻において起こる状態変化(遷移または推移)に関して、マルコフ連鎖は遷移確率が過去の状態によらず、現在の状態のみによる系列である。特に重要な確率過程として、様々な分野に応用される。 参照元:Wikipedia ま る で な に を 言 っ て い る の か 、 全 く わ か ら な い 。 。 。 わざわざ分かち書きしてしまうほどでしたorz 実際にマルコフ連鎖でやること 例えば、下記の4つの文章がある時、 まずは形態素解析で分かち書き出力(文章において語の区切りに空白を挟んで記述すること)したものを用意し、辞書を作る。 文章 魚 を 買う 。 魚 は 好き 。 魚 は かわいい 。 猫 は かわいい けど 高い 。 マルコフ連鎖の辞書 一つ一つの単語をスライドしていって登録することになる。 1~3語の三つの組み合わせがブロック。(※単語数は可変にできるはず。) 「名詞」「形容詞」「動詞」は、文章の意図を示していることが多いと想定され、始点の単語で選ばれるようにする模様。 1語目 2語目 3語目 魚 を 買う を 買う 。 魚 は 好き は 好き 。 魚 は かわいい は かわいい 。 猫 は かわいい は かわいい けど かわいい けど 高い けど 高い 。 単語の組み合わせと選択ロジックの例 始まりが「魚 は」の場合、次に「は」から始まるブロックを探す。 「は 好き 。」「は かわいい 。」「は かわいい けど」からランダムに続くブロックが決定される。 同様の作業を「(文末)」がくるまで繰り返す。 「魚 は かわいい けど 高い 。」、「猫 は 好き 。」といった文章がランダムに生成される。 ちなみに、 1ブロックあたりの単語数が多くなればなるほど、元の文章に近いものが生成される。 一方で、少なくなればなるほどぐちゃぐちゃな文章になるようです。 なんとなく分かってきたので、、、 マルコフ連鎖とMeCab形態素解析でやりたいこと 本田圭佑っぽいツイッターつぶやきを生成しちゃおう!!!! ファイル構成 app ├── text_editor.py # テキスト精製 ├── make_dictionary.py # 辞書作成 ├── tweet.py # つぶやき生成と出力 └── text ├── keisuke_honta.txt # 精製前のRawデータ ├── new_keisuke_honta.txt # 精製後のテキストデータ └── learned_data.json # 辞書データJSONフォーマット 事前準備 $ pip install markovify $ pip install mecab-python3 本田圭佑の名言的なつぶやきを取得する まだTwitterAPIの申請が降りないので、 All My Tweetsを使って、ツイートを取得しよう。 公式アカウント+Bot3つからテキスト取得 どうなりたいのか。そこから全てが動き出す。— Keisuke Honda (@kskgroup2017) February 22, 2021 本田圭佑 bot @hondak_bot 本田圭佑bot @ksk_nonrotation 本田圭佑bot @hondahanpanai4 つぶやきテキストを精製してキレイなテキストファイルに text_editor.pyのコード 純粋な日本語の文章だけ欲しかったので、 RT @warpspaceとかApr 19, 2021とかその辺を除去する。 text_editor.py import MeCab import re text = open("./text/keisuke_honda.txt","r").read() # Replace Bad Symbols for markovify to function # Refer: https://github.com/jsvine/markovify/issues/84 table = str.maketrans({ # '\n': '', # '\r': '', '(': '(', ')': ')', '[': '[', ']': ']', '"':'”', "'":"’", }) text = text.translate(table).split() url_pattern = "https?://[\w/:%#\$&\?\(\)~\.=\+\-]+" for i in range(10): # remove()を使ってるから10回くらいやると綺麗になった for line in text: if re.match(url_pattern, line): # URL text.remove(line) elif bool(re.search(r'[a-zA-Z0-9]', line)): text.remove(line) elif re.match('^@.*', line): text.remove(line) elif re.match('.*,$', line): text.remove(line) elif re.match('^#.*', line): text.remove(line) elif re.match('RT', line): text.remove(line) # Parse text using MeCab m = MeCab.Tagger('-Owakati') f = open('./text/new_keisuke_honda.txt', 'w') for line in text: splited_line = m.parse(line) f.write(str(splited_line)) f.close() 精製前 ごちゃついてるなあ。。。 keisuke_honda.txt RT @warpspace_inc: 【プレスリリース】 宇宙フロンティアファンドや @kskgroup2017 が率いるKSK Angel Fund LLC、SMBCベンチャーキャピタル産学連携2号投資事業有限責任組合等を引受先とした第三者割当増資による4億円の資金調達を実施… Apr 19, 2021 Super guests visited our game yesterday! ?? https://t.co/JHWX77DuVa Apr 18, 2021 次は決める! Apr 18, 2021 I will be on @NowVoice_jp about yesterday's game at 14:15. Apr 18, 2021 本当に必要な情報にたどり着くために、時間とお金をかけ続ける。 Apr 15, 2021 ... 精製後 キレイになった。1行1行、分かち書きもされて、文末では改行も入ってる。 new_keisuke_honda.txt 恋愛 っていう もの に は 必ず 喧嘩 を し て しまう と 。 その 時 の 状況 に 寄っ て 最悪 別れ て しまう 人 も 居る と 。 でも よく 考え て み て 下さい よ 。 思い出 の 方 が 沢山 ある し 、 お互い 笑い 合っ た 数 の 方 が 多い ん や し 、 そんな 数少ない 喧嘩 で 負け て どう する ん や と 。 ... 精製テキスト使って、マルコフ連鎖用の辞書登録 make_dictionary.pyのコード markovify.NewlineText()を使っているのは、 句読点「。」で文章の終わりを示すために改行を入れて、1行1行にしたテキストをちゃんと読み込ませるため。 new_keisuke_honda.txtでも分かる通り、こうすることでちゃんと学習されて辞書登録がスムーズにいくように。 ちなみに、英語とかだと.で勝手に判定されるからmarkovify.Text()でいいけど、日本語は少し工夫が必要。 make_dictionary.py import markovify # Load file text = open("./text/new_keisuke_honda.txt", "r").read() # Build model text_model = markovify.NewlineText(text, state_size=3, well_formed=False) # Make Dictionary as Json_format with open('./text/learned_data.json', 'w') as f: f.write(text_model.to_json()) state_sizeは、1ブロックあたりの単語セット 1ブロックあたりの単語数が多くなればなるほど、元の文章に近いものが生成される。一方で、少なくなればなるほどぐちゃぐちゃな文章になるようです。 well_formed=Trueにすると、markovifyで読み込むことができない単語があるとKeyError: ('___BEGIN__', '___BEGIN__')が発生するから、falseにしておくといい感じになる。 ただし、mal_formedで読み込みができない文章は無視される模様 learned_data.jsonが出力される 一部のみ紹介 { "state_size": 3, "chain": "[[[\"___BEGIN__\", \"___BEGIN__\", \"___BEGIN__\"], {\"\恋\愛\": 2, \"\そ\の\": 22, \"\で\も\": 31, \"\思\い\出\": 1, \"\ま\ぁ\": 13, \"\俺\": 191, \"\脱\": 1, ... マルコフ連鎖用に生成した辞書を使ってつぶやきを作成 tweet.pyのコード tweet.py # -*- coding: utf-8 -*- import markovify with open('./text/learned_data.json', 'r') as f: text_model = markovify.Text.from_json(f.read()) for i in range(10): #10個のつぶやき生成 print(text_model.make_short_sentence(140)) 自動生成されたつぶやきを見てみる ・自分は何でもかんでも理由をつけて勝ち上がっていくことが大事。 ・好きな事だけではなく継続して、俺は次の試合に向けて自分を高めましたよね。。 ・信じることっていうのは、僕にとって希望なんですよね。そういうサッカー選手としてだけでなく、批判してくれました? ・ありがとうございます!ちょっと考えてください。仕事つくってブラジルに来てください???⚽️ ・おれもあいつらに“欲”という意味ではホントいい感じできてますけどね ・そろそろ皆さんも旅立ちの日ですと。だからなんだと。 ・ボール来るなって言っても遅い ・革命家ですね。この壁だって神様に感謝しないと上にはいけない ・色々と周りが騒がしいけど、俺は選ばれてたのかなと。 ・俺のパンチング見たか?ナイス飛び込みやろ。 ・昔の選手も好きですけど、やはりみんなで戦っているんじゃないかという風に思いますね ・僕なんかゴール尽きまくってますよ。自分の情熱が弱かったと。 ・あの白いユニフォームには特別な思いがありますよ。自分の用事が終わったら、すぐドリブルしようかと思っている。その二つは連動してはいるけど。 ・嘘でもいいから「わかる」って言ってもなくならないし、正直 ・スポーツを止めるな!素晴らしい活動は皆んなで応援したいというものに投資します! ・どこでプレーしても、自分の人生に誇りを持ってくれるまで突き進みますよと。 本田圭佑っぽいwwwめっちゃ言いそうwww深良さがより深まっているwwww エモいものありました?w ハマったポイント 最初のテキスト精製のところがなかなか難しかった。 list.remove()って最初にヒットしたものしか消さないので、何個もテキスト上にあると、ゴミが残ってしまうから、forを10回とか15回とか回す必要があるかも。 もっと効率の良い方法があるんだけど、突貫でやったからまだ考えきれていない。 markovifyのstate_sizeの意味と、well_formedの意味がなかなか掴めなかったので、難しかった。 well_formed=Trueにすると今でも、key errorが出てしまうから、理由がまだ分かっていない。 githubで言われている、シンボルたちは全て置換したんだけど、なんでだろう、、、、 日本語は、句読点を目印に、文章を1行ごと改行して、NewlineText()を使った方が相性が良いということを知らなかったから、テキスト精製のところで、つまずいた。 感想とまとめ できた時は、まじで、めーっちゃテンション上がった!ぶち上げwwww(☝︎ ՞ਊ ՞)☝︎ 素直に面白いw 自分は本田圭佑のファンなので、 バーチャル本田圭佑が誕生した瞬間を目の当たりにして感動したwwwwwww 次の目標は twitterに生成したつぶやきを自動投稿する「本田圭佑っぽいことをつぶやくBot」を作成すること Twitter APIを使って、本田圭佑だけでなく、他の著名人もトライしてみること。 いずれはLSTMとか自然言語処理系の機械学習モデルも使って挑戦すること。 最後に、マルコフィファイってまじで言いにくい。特にvifyのところ、めっちゃ吐息出るわw 以上、ありがとうございました!!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【2021年版】Binance自動売買システムを作ろう。

どうも、TAKUと申します。 2021年になり、2018年時点とはかなり状況が変わってきていますね。 新型コロナウイルスの影響や、ビットコインの価格が再び爆上げするなど、 様々な変化が起こっている年でもあります。 このような状況ですが、 Binanceは健在で、以前よりもさらに使いやすくなり、 様々ある仮想通貨取引所の中でも 日々改良を続けている良取引所の1つだと思います。 今日は以前のBinance自動トレードシステムをもっとかみ砕いて 機能を単純にして、 手軽に自動売買システムを作る事が出来るような 記事を書いてみたいいと思います。 それでは、参りましょう! まずは、システムの構築手順 プログラムを書く前に、 トレードの手順を考えてみたいと思います。 文章で書くとなかなか伝わりづらいと思いますので、 フローチャートで作ったものを貼り付けます。 ざっくりですが、こんな感じでコードは動くと思いますので、 これでプログラムを書いてみたいと思います。 実際のコード Binance自動トレード Github ソース import binance.client import time import re import datetime # for order on binance from binance.enums import * api_key = "" api_secret = "" client = binance.client.Client(api_key, api_secret) prices = client.get_all_tickers() symbolLen = len(prices) # 下落率 rateOfDecline = 3.0 # 現在価格、30分後の価格 beforePrices = [prices[i]['price'] for i in range(symbolLen)] afterPrices = [0 for i in range(symbolLen)] def buyCoin( symbol, afterPrice ): print("called buy") order = client.create_test_order( symbol=symbol, side=SIDE_BUY, type=ORDER_TYPE_LIMIT, timeInForce=TIME_IN_FORCE_GTC, price=afterPrice, quantity=1) print(order) return while 1==1: time.sleep(1800) prices = client.get_all_tickers() for i in range(symbolLen): # 30分後の価格取得 afterPrices[i] = prices[i]['price'] # 30分前の価格と現在の価格を比較して、下落率・上昇率を計算 pricePropotion = 100 - (float(afterPrices[i]) / float(beforePrices[i])) * 100 # それぞれの銘柄で指定したパーセンテージ下落していたら買いを入れる。 if pricePropotion > rateOfDecline : if re.search(r'BTC', prices[i]['symbol']): #買いを入れたときの時間 print('time: ' + datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S") + ' , ticker: ' + prices[i]['symbol'] + ' , PricePropotion: ' + str(pricePropotion) + ' , ' + afterPrices[i] + ' , ' + beforePrices[i]) #買い注文 afterPrice = round(float(afterPrices[i]), 8) if afterPrice > 0.0001: print(afterPrice) buyCoin(prices[i]['symbol'], afterPrice) break for i in range(symbolLen): beforePrices[i] = prices[i]['price'] 動いた! 動きました!! あまり小さい金額だとエラーがでるので、 「0.0001」以下の場合は除外しています。 これでOKです。 あとは、みなさま、 このテスト関数を実際の関数に置き換えてもらえれば 自動売買システムが完成です! お疲れさまでした! さいごに Pythonの基礎知識が全くなかった方、プログラミングに全く触れたことがなかった方は なかなか難しい内容になっていたかもしれません。 ですが、 しっかり記事内容を理解いただき、 「仮想通貨の自動売買を身につけるんや。。。」 という熱い心があれば、かならずや乗り切れると信じております。 Pythonの基礎知識、Binanceのアカウント解説を含めて、 UdemyにてBinance自動トレードの開設を行っている動画も作成いたしました。 もし、興味があれば、 覗いてみて頂ければ幸いです。 ※下記リンクから見て頂ければ、最初の10名様まで無料で受講いただけます。  そして、レビューをもらえると主が死ぬほど喜びます。 Udmey Binance自動トレード
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

トニカクカワイイで星空くんが作ってたシューティングゲーム作ってみた

はじめに 畑先生の作品トニカクカワイイの4巻P88にて,星空君が古いPCを使って縦スクロールシューティングゲーム(LOVE IMPORTANT)を作っててかっこいいーってなり, ターミナルのみで動くゲームってどうやって動くんだろーって思って調べながら作ってみました. 作りたいもの ターミナルのみで動く 上から降ってくる敵を打つシューティングゲーム 漫画内で星空くんはCOBOLで作ってましたが,ちょっとCOBOLを新しく学ぶのはしんどかったのでpythonで代用. 設定 星歴4098年, 銀河統一を目指すデビルギャラクシー軍の 最終兵器として開発された主人公ダークシャリバーンが 敵の母星にとらわれている姫を救出する物語 らしい,,, 構成 攻撃の仕組み 敵(Enemy),味方(Bullet)ともにN行M列のMapを持ち, 敵の存在は-2,味方の砲弾は+1,それ以外の部分は0にします. そして,敵Mapと味方Mapの行列の足し合わせで攻撃を表現します. class Enemy or Bullet(): def __init__(self,High,Width): self.N = High self.M = Width self.map = [[0 for _ in range(self.M)] for _ in range(self.N)] 敵 randomな箇所に敵を発生させた行を1行目に追加し,N行目の行列を削除します. class Enemy(): def create(self): newRow = [0 for _ in range(self.M)] if random.random() > 0.6: index = random.randint(0,self.M-1)//2 newRow[index] = -2 self.map = [newRow] + self.map self.map.pop(self.N) 味方 入力に合わせて味方の位置(1を入力)を変えた行をN行目に追加し,1行目を削除します. class Bullet(): def create(self, control): # bullet position change if str(control) == "1": self.position -= 1 elif str(control) == "2": self.position += 1 # range fix if self.position < 0: self.position = 0 elif self.position >= self.M: self.position = self.M -1 newRow = [0 for _ in range(self.M)] newRow[self.position] = 1 self.map = self.map + [newRow] self.map.pop(0) 計算 敵mapと味方mapを足し合わせます. このとき,攻撃があたった(-1になった)場合にscoreを加点します. class View(): def cal(self): self.enemy.create() self.bullet.create(self.control) map = [] for i in range(self.N): row = [] for n in range(self.M): row.append(self.enemy.map[i][n] + self.bullet.map[i][n]) # マス目が-1になったもの if self.enemy.map[i][n] + self.bullet.map[i][n] == -1: self.enemy.map[i][n] = 0 self.bullet.map[i][n] = 0 self.bullet.score += 10 # 正面に対峙した場合もマイナス if n is not self.M: if self.enemy.map[i][n] == -2 and self.bullet.map[i][n+1] == 1: self.enemy.map[i][n] = 0 self.bullet.map[i][n+1] = 0 self.bullet.score += 10 map.append(row) return map 表示 0と-1をスペース,-2を敵?,1を弾丸¥として置き換えます. その後,mapを上書きする形でプリントします. class View(): def plot(self): map = self.cal() str_map = [] for i in range(self.N): row = [] if i is not self.N-1: for n in range(self.M): if map[i][n] == 1: row.append("¥") elif map[i][n] == -2: row.append("?") else: row.append(" ") else: for n in range(self.M): if map[i][n] == 1: row.append("?") elif map[i][n] == -2: row.append("?") self.bullet.life -= 1 else: row.append(" ") str_map.append(row) tex = ''.join(str_map[0])+"\n" for i in range(1,self.N): tex = tex + ''.join(str_map[i]) +"\n" tex = tex + "Life:"+ str(self.bullet.life) + " " + "Score:" + str(self.bullet.score)+ "/"+ str(self.bullet.MaxScore)+ "\n" return tex +"\033["+str(self.N+1)+"A", self.bullet.life, self.bullet.score [Python]printの上書きをする方法 操作入力 timeout_decoratorを用いて,時間制限を用いて入力を判定することで,入力がない場合でも画面が動き続けるようにします. import getch from timeout_decorator import timeout, TimeoutError class Controller(): def __init__(self): self.key = 0 @timeout(0.5) def keyinput(self): self.key = ord(getch.getch()) timeout-decorator まとめ 一応作りたかったものはできたが, 入力の処理(遅延,複数入力があったときの処理)が結構微妙だったので,また時間がある時に修正したい. プレイ動画 https://www.youtube.com/watch?v=sesPXrOc3yA Githubのコード https://github.com/yuhi-sa/invader
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Problems Hard No.91~100解説

Hard No.91 : D - Coloring Edges on Tree D - Coloring Edges on Tree 発想 使用する色の数$K$はグラフの中で1つの頂点を端点とする辺の数の最大値になります。1つの頂点に接続している辺を順番に1,2,3...と色分けしていけば良いです。 実装 幅優先探索をしていきます。 from collections import deque N = int(input()) G = [] for _ in range(N): G.append([]) for i in range(N-1): a, b = map(int, input().split()) a -= 1 b -= 1 G[a].append((b, i)) G[b].append((a, i)) k = 0 for i in range(len(G)): k = max(k, len(G[i])) col = [0]*(N-1) Q = deque([(0, 0)]) visited = [False]*N while Q: #xは頂点、cは色 x, c = Q.popleft() visited[x] = True tmp_c = 1 #yは頂点、eは辺の番号 for y, e in G[x]: if visited[y]: continue if tmp_c == c: tmp_c += 1 col[e] = tmp_c Q.append((y, tmp_c)) tmp_c += 1 print(k) for c in col: print(c) Hard No.92 : B - Unplanned Queries B - Unplanned Queries 発想 木の辺は頂点間にあるので、すべての頂点の登場回数が偶数になればよいです。奇数が1個でもあれば、条件を満たす木はありません。 実装 N, M = map(int, input().split()) ver = [0]*N for i in range(M): a, b = map(int, input().split()) ver[a-1] += 1 ver[b-1] += 1 for i in range(N): if ver[i] % 2 == 1: print('NO') exit() print('YES') Hard No.93 : D - Transit Tree Path D - Transit Tree Path 発想 頂点$x$から頂点$y$まで頂点$K$を経由した最短距離は$d_{xK}+d_{Ky}$と表せます。結局は頂点$K$からすべて頂点までの距離が分かればよいので、heapを使って最短距離をすべて求めればよいです。 実装 import heapq N = int(input()) G = [] for i in range(N): G.append([]) for i in range(N-1): a, b, c = map(int, input().split()) G[a-1].append((c, b-1)) G[b-1].append((c, a-1)) Q, K = map(int, input().split()) XY = [] for i in range(Q): x, y = map(int, input().split()) XY.append((x-1, y-1)) dist = [-1]*N dist[K-1] = 0 done = [False]*N q = [] #頂点Kからの距離をheapを使って求める heapq.heappush(q, (0, K-1)) while len(q) > 0: d, i = heapq.heappop(q) if done[i]: continue done[i] = True for (c, j) in G[i]: if dist[j] == -1 or dist[j] > dist[i] + c: dist[j] = dist[i] + c heapq.heappush(q, (dist[j], j)) for i in range(Q): x, y = XY[i][0], XY[i][1] ans = dist[x] + dist[y] print(ans) Hard No.94 : D - Blue and Red Balls D - Blue and Red Balls 発想 操作回数$i$のとき、青いボールを置く場所の選び方は$_{N-K+1}C_i$になります。 操作回数$i$のとき、$K$個の青いボールを$i$箇所に分ける分け方(それぞれ少なくとも1個以上の青いボールがある)は、$_{K-1}C_{i-1}$通りあります。 実装 N,K = map(int,input().split()) Bp = 1 Rp = N-K+1 for k in range(K): print(Bp*Rp%(10**9+7)) Bp = Bp*(K-k-1)//(k+1) Rp = Rp*(N-K-k)//(k+2) Hard No.95 : D - Xor Sum 4 D - Xor Sum 4 発想 $_NC_2$通りを試すとTLEします。xorの演算では各桁ごとに独立してできます。xorの演算をすると、 1{ \ } ⊕{ \ } 1 = 0\\ 0{ \ } ⊕{ \ } 0 = 0\\ 1{ \ } ⊕{ \ } 0 = 1 となります。演算結果が1になるのは3番目のパターンのみなので、1の個数と0の個数をそれぞれ数えてその積を取ればよいです。 実装 N = int(input()) A = list(map(int, input().split())) ans = 0 mod = 10**9+7 #i桁目の数字をそれぞれ独立に考える for i in range(60): S = 0 #i桁目の1の個数S、0の個数N-S for j in range(N): S += A[j]>>i&1 ans += S*(N-S)<<i%mod ans %= mod print(ans) Hard No.96 : D - Make Them Even D - Make Them Even 発想 コインの枚数が奇数のマスを見つけたら、下のマスに1枚移動させることを繰り返します。1番下の行の時に奇数枚のマスを見つけたら、右のマスに1枚コインを移動させます。最終的に$(H,W)$のマスのみが奇数枚のままになる可能性があります。 実装 H, W = map(int, input().split()) A = [] for i in range(H): a = list(map(int, input().split(' '))) A.append(a) ans = [] for i in range(H-1): for j in range(W): if A[i][j] % 2 == 1: ans.append((i+1, j+1, i+2, j+1)) A[i+1][j] += 1 for j in range(W-1): if A[-1][j] % 2 == 1: ans.append((H, j+1, H, j+2)) A[-1][j+1] += 1 print(len(ans)) for i in range(len(ans)): print(*ans[i]) Hard No.97 : C - Base -2 Number C - Base -2 Number 発想 2で割ることを繰り返せばよいです。ただし今回の問題は2進数でなく、-2進数ですので、計算するたびに$N$の符号を変えます。 実装 N = int(input()) ans = '' if N == 0: ans = 0 while N: ans = str(N%2) + ans N = -(N//2) print(ans) Hard No.98 : C - Palindromic Matrix C - Palindromic Matrix 発想 例えば$H,W$ともに偶数の場合、全ての文字は4の倍数個なければならないです。 一方で$H,W$ともに奇数の場合、行列の中心に文字を1つだけ置くことができます(奇数個の文字を1個置ける)。さらに行列の中心を通る1行と1列には偶数個の文字を置くことができます(4の倍数でない)。 逆にいえば、奇数個の文字種が多すぎたり、偶数個の文字種(4の倍数でない)が多すぎれば、その行列は回文にすることができません。 以上のような考え方で実装をしていきます。 実装 import collections H, W = map(int, input().split()) A = '' for i in range(H): a = input() A += a A = list(A) even = 0 odd = 0 C = collections.Counter(A) for c in C: #4で割ったあまりが2または3の場合にevenは+1される #あまりが3の時は、それを1と2に分けるから、oddもevenも+1される even += C[c] % 4 // 2 odd += C[c] % 2 #H,W両方奇数のときに奇数個の文字が2つ以上 #または、H,Wのうち少なくとも1方が偶数のときに、奇数個の文字が2つ以上のとき'No' if (H % 2) * (W % 2) < odd: print("No") #偶数個の文字を置ける個数よりも偶数個の文字が多ければ'No' elif (H % 2) * (W // 2) + (H // 2) * (W % 2) < even: print("No") else: print("Yes") Hard No.99 : B - Simplified mahjong B - Simplified mahjong 発想 各$i$に対して$[A_i/2]$個のペアを作ることができる。$A_i=0$なる$i$が一つも存在せず、$i$のカードが1枚だけ余った場合は、その1枚と$i+1$のカードでペアを作ることができる。よってペアの数は$[sum(A)/2]$になる。 $A_i=0$なる$i$が登場したら、$i-1$までで同じように計算し、$i$を飛ばして$i+1$から計算を再開すればよい。 実装 N = int(input()) ans = 0 tmp = 0 for i in range(N): a = int(input()) if a == 0: ans += tmp//2 tmp = 0 tmp += a else: ans += tmp//2 print(ans) Hard No.100 : C - Vacant Seat C - Vacant Seat 発想 空席の場所を20回程度で見つけ出します。$O(N)$では間に合いませんので、二分探索で見つけます。smallをmidに寄せる条件を考えて実装します。 実装 (1)smallとmidの位置の性別が同じ場合 (mid - small)を2で割った時のあまりが0の時、smallからmidまで男女が交互に空席なく並んでいるので、smallをmidに寄せる (2)smallとmidの位置の性別が異なる場合 (mid - small)を2で割った時のあまりが1の時、smallからmidまで男女が交互に空席なく並んでいるので、smallをmidに寄せる 以上2つの場合以外ではlargeをmidに寄せれば良いです。 N = int(input()) print(0) s = input() if s == "Vacant": exit() fm = s small = 0 large = N while True: mid = (small + large) // 2 print(mid) s = input() if s == "Vacant": exit() if (s == fm) ^ ((mid - small) % 2 == 1): small = mid fm = s else: large = mid
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向け】PythonとJupyterの環境構築手順2021年版(Windows編)

前提 本記事はWindowsユーザーが対象となります デフォルトの状態から環境構築していきます。コマンドプロンプトではなく今回はpowershellを使用します。 Macの方はこちらを参照ください。 はじめに Windows上にPythonでのデータ分析環境を整えることを目的とします。 ひとまず、MacにPython3系をインストールしてJupyter Notebookで作業できるようにすることを目標にします。Jupyter Notebookとは、ブラウザ上で動作するPythonの対話型実行環境です。 以下の2種類の方法を紹介します。 手っ取り早く始めたい方向け 長く使い続けたい方向け 手っ取り早く始めたい方へ 手っ取り早く始めた方は、AnacondaさえインストールしてしまえばOKです。 Pythonそのものとデータ分析でよく使われるライブラリを一括で管理できるパッケージです。これを入れておくとひとまず必要なモノはすべてそろうので、ある意味オススメではあります。 ただ、Anacondaというものは、Python環境をそれひとつで形成しきってしまっているため、その外部のことをやろうとすると、途端に難しくなります。(筆者の感想も入ってます) 例えば、Anacondaのパッケージ管理ツールのcondaではインストールできないものが多々あります。初心者だと無理矢理ほかのツール(pipなど)でいれてしまい、環境が壊れてしまうという悲しいことも起こりえます。 ただメリットも多いため、しっかりとデメリットを意識して使いましょう。 anacondaのインストール 下記公式ページに沿ってインストールしましょう。 こちら 長く使い続けたい方へ Pythonのインストール Macとは異なり、Windowsには最初からPythonは入っていません。 まずPython公式ページにアクセス。 Python.org Dowonlodsからwindowsを選択します。 インストーラの形式にも色々ありますが、実行ファイル形式が楽なので今回は「Windows x86-64 executable installer」を選びます。ヴァージョンは私は3.9にしました。 ダウンロードしたファイルを開くとこのようなウィンドウが出ます。 「Add Python to Path」にチェックを入れるとパスの書き込みも行ってくれるので、チェックを入れましょう。後から自分でやるのは少しめんどくさいですよね。 > python -V Python 3.9.0と表示されれば問題ないです。 これで > python と入力すればpowershellでPythonが使用できるようになります。 抜けたいときはCtrl + zです。 poetry つぎにpoetryを使ってPythonのモジュール管理を行います。poetryはディレクトリ下に仮想環境を作ってくれるため、必要なモジュールはその都度入れる必要がありますが、環境を簡単に使い分けられ、他人と環境の共有も容易なためおすすめです。いらなくなったらすぐに環境ごと消せるためストレージにも優しいですね。 公式ドキュメントはこちら 日本語版もありました。こちら インストール スタートメニューからPowerShellを検索し、起動して下記を実行してください。 インストールが開始されます。 > (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python - powershellを再起動し、 > poetry --version ヴァージョン情報が表示されたら問題ないです。 パッケージ管理方法 新しい作業ディレクトリを作成し、移動したら下記コマンドで新規の環境を構築します。 > poetry init 対話形式でいくつかの質問に答えていくと環境が作られます。ここでモジュールを入れ忘れても後から追加できるので大丈夫です。 今回はnumpyとpandasをいれます。 > poetry add numpy pandas バージョン指定をしたい場合は下記 > poetry add pandas==1.2.4 Jupyter データ分析に必須のツール、jupyter環境を入れていきます。 個人的にはjupyterlabがおすすめですが、初心者はnotebookでもいいと思います。 > poetry add jupyter 容量が大きいため、少し時間がかかると思います。 起動 jupyter notebookを起動します。jupyter labも同様にして起動できます。 > poetry run jupyter notebook #もしくは > poetry shell #で環境内に入って > jupyter notebook poetryの便利なその他のコマンド # poetry自身のアプデ > poetry self update # poetry内の.pyファイルの実行 > poetry run python hoge.py # 環境内に入る(出る時はexit) > poetry shell 最後に Googlecolabも非常に強力ですが、最初から環境が用意してあるがゆえに、中で何が行われているか理解しにくいです。ローカルに一度でも環境を構築した経験があれば、この先も非常に強い武器となります。ぜひ臆せず挑戦してみてください。質問はいつでも受け付けています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

#1 PowerApps アプリ で撮影した画像を FaceAPI で感情分析してみました

概要 PowerApps のカメラで撮影した画像を API Management 経由で FaceAPI に転送し、その画像の分析結果「感情、性別、年齢」を PoweApps に表示するアプリの実装手順を数回に分け記載しています。また、並行して、その分析結果を CosmosDB に保存しておき、PowerApps からの累積情報取得リクエストにより累積クエリ結果「感情分布、性別分布」を PowerApps に返し、円グラフ表示する機能の実装手順も複数回に分け記載します。なお、PowerApp の画面作成については省略し、APIコール部分とその戻り値の部分に焦点をあてて記載しています。 本アプリの実行結果は下図となります。 この時の全体構成は下図のようになります。 初回は PowerAppsで扱う画像データの書式と、画像情報を FaceAPI へ転送し分析結果「感情、性別、年齢」を取得するプログラムについて記載します。 なお、PowerApps で扱う画像データの書式についてはこの記事を参考に、 Azure Face API 関連については この記事 と この記事を参考にさせていただきました。 実行環境 macOS Big Sur 11.3 Python 3.8.3 PowerApps 関連 PowerApps のカメラで画像を取得します。 画像データのエンコード 画面構成とそれぞれのオブジェクトは以下となります。 画像データのエンコード PowerAppsのカメラで撮影した画像データを Data URI Scheme で base64 のテキストに変換します。 Camera1.OnSelect に以下を定義します。 UpdateContext({result: "Not yet"}); UpdateContext({idnum: idnum + 1}); UpdateContext( { photouri: JSON(Camera1.Photo,JSONFormat.IncludeBinaryData) } ); Collect( Photos, { No: idnum, Emotion: result, Data: Substitute( photouri, """", "" ) , Image:Camera1.Photo} ); 画像データのエンコード結果を Gallery1.Subtitle1.Text に表示させておきます(別にする必要はありませんが、、、)。 ThisItem.Data ちなみに、エンコード結果は以下のような書式となります。 data:image/png;base64,iVBORw0KG・・・・中略・・・・0MbGNgGwPbGNjGwDYG/hgM/H/SA2X6kn3ItwAAAABJRU5ErkJggg== また、撮影した画像データをGalleryに降順で表示しておきたいので Gallery1.Items に以下を定義しておきます。 Sort(Photos,No,Descending) 分析結果の表示 PowerAppsのカメラで撮影した画像データの Face API での分析結果「感情(確率)、性別、年齢」は以下の位置に表示させます。 - 感情 : Gallery1.Title1.Text - 感情確率+性別+年齢 : Gallery1.Subtitle1.Text Face API 関連 次に、PowerAppsのことは一時的に忘れて、画像データをFaceAPIに送って感情分析するローカルプログラムを作成します。 Faceの作成 最初に、以下の内容でFaceを作成します。 作成されたFaceのキーとエンドポイントを取得しておきます。 バイナリデータでの FaceAPI アクセス まずは、ローカルにある画像データをバイナリデータとしてして、FaceAPIに送信し分析結果を取得してみます。 プログラムは以下となります。 FaceEmotionBinary.py import cognitive_face as CF import numpy as np import argparse import json import time import pprint import base64 import requests # FaceAPI情報 KEY = 'zzzzzzzzzzzzzzzzzzzzzzzzzzzz' ENDPOINT = 'https://face-apxxxxxxxxxxxxxxxx.cognitiveservices.azure.com/face/v1.0/' # Emotionの定義 emobase = ['anger', 'contempt', 'disgust', 'fear', 'happiness', 'neutral', 'sadness', 'surprise'] emolist = ['怒', '侮', '嫌', '恐', '幸', '無', '悲', '驚'] # 感情分析 def face_emotion(image) : print("face_emotion") try: # FACE_APIの情報セット CF.Key.set(KEY) CF.BaseUrl.set(ENDPOINT) # FACE_APIでの感情分析取得 faces = CF.face.detect(image, attributes='emotion') f=json.dumps(faces) j=json.loads(f) # 分析する対象の画像に複数人いようが最初の1人のみを分析の対象とする emotion_data = j[0]['faceAttributes']['emotion'] print(emotion_data) # 感情分析結果からスコアのみを分別 emotion = [] for name in emobase: emotion.append(emotion_data[name]) # スコアの高いものをその人の感情として決定する num = np.argmax(emotion) emotion_weight = str(emotion[num]*100) + "%" emotion_dict = {'emotion': emolist[num], 'data': emotion_weight} # 辞書データの作成 emotion_json = json.dumps(emotion_dict, ensure_ascii=False) # Jsonエンコード return emotion_json except Exception as e: print(e) return "ERROR !!!" if __name__ == '__main__': parser = argparse.ArgumentParser(description='顔写真から感情を判断します!') parser.add_argument('--image', type=str, default='004.png', help='顔写真ファイル名') args = parser.parse_args() start = time.time() emotion = face_emotion(args.image) making_time = time.time() - start print("") print(f"分析結果:{emotion}") print("分析時間:{0}".format(making_time) + " [sec]") print("") 実行結果は以下となります(画像はローカルにあるものを使用)。 $ python FaceEmotionBinary.py --image 001.png face_emotion {'anger': 0.0, 'contempt': 0.0, 'disgust': 0.0, 'fear': 0.0, 'happiness': 0.982, 'neutral': 0.0, 'sadness': 0.018, 'surprise': 0.0} 分析結果:{"emotion": "幸", "data": "98.2%"} 分析時間:1.2856347560882568 [sec] 問題なく取得できました、、、、、 Base64テキストデータでの FaceAPI アクセス しかし、PowerAppsから送られてくる画像データはBase64テキストデータなので、そのデータ形式でFaceAPIに送信し分析結果を取得できることを確認します。 プログラムは以下となります。今度は、「感情」だけでなく「感情確率+性別+年齢」も戻り値として取得します。 FaceEmotionBase64.py import cognitive_face as CF import numpy as np import argparse import json import time import pprint import base64 import requests # FaceAPI情報 KEY = 'zzzzzzzzzzzzzzzzzzzzzzzzzzzz' ENDPOINTDETECT = 'https://face-apxxxxxxxxxxxxxxxx.cognitiveservices.azure.com/face/v1.0/detect' # Emotionの定義 emobase = ['anger', 'contempt', 'disgust', 'fear', 'happiness', 'neutral', 'sadness', 'surprise'] emolist = ['怒', '侮', '嫌', '恐', '幸', '無', '悲', '驚'] # 感情分析 def face_emotion_base64(data64) : print("face_emotion_base64") # data = base64.b64decode(data64.replace("data:image/png;base64,", "")) data = base64.b64decode(data64[22:]) try: headers = { 'Content-Type': 'application/octet-stream', 'Ocp-Apim-Subscription-Key': KEY, } params = { 'returnFaceId': 'false', 'returnFaceLandmarks': 'false', 'faceRectangle': 'false', 'returnFaceAttributes': 'age,gender,emotion', } # FACE_APIでの感情分析取得 response = requests.post(ENDPOINTDETECT, params=params, headers=headers, data=data) j=json.loads(response.text) # 分析する対象の画像に複数人いようが最初の1人のみを分析の対象とする # 年齢情報 age_data = j[0]['faceAttributes']['age'] print(age_data) # 感情情報 emotion_data = j[0]['faceAttributes']['emotion'] print(emotion_data) # 性別情報 gender_data = j[0]['faceAttributes']['gender'] print(gender_data) # 感情分析結果からスコアのみを分別 emotion = [] for name in emobase: emotion.append(emotion_data[name]) # スコアの高いものをその人の感情として決定する num = np.argmax(emotion) num = np.argmax(emotion) emotion_weight = str(emotion[num]*100) + "%, " + gender_data + ", " + str(age_data) emotion_dict = {'emotion': emolist[num], 'data': emotion_weight} # 辞書データの作成 emotion_json = json.dumps(emotion_dict, ensure_ascii=False) # Jsonエンコード return emotion_json except Exception as e: print(e) return "ERROR !!!" if __name__ == '__main__': parser = argparse.ArgumentParser(description='顔写真から感情を判断します!') parser.add_argument('--image', type=str, default='004.png', help='顔写真ファイル名') args = parser.parse_args() start = time.time() with open(args.image, "rb") as f: data = "data:image/png;base64," + base64.b64encode(f.read()).decode("UTF-8") emotion = face_emotion_base64(data) making_time = time.time() - start print("") print(f"分析結果:{emotion}") print("分析時間:{0}".format(making_time) + " [sec]") print("") 実行結果は以下となります(画像はローカルにあるものを使用)。 $ python FaceEmotionBase64.py --image 001.png face_emotion_base64 18.0 {'anger': 0.0, 'contempt': 0.0, 'disgust': 0.0, 'fear': 0.0, 'happiness': 0.982, 'neutral': 0.0, 'sadness': 0.018, 'surprise': 0.0} female 分析結果:{"emotion": "幸", "data": "98.2%, female, 18.0"} 分析時間:0.8755090236663818 [sec] 問題なく処理できました。これで想定する戻り値をJSON形式で取得できていることを確認できました。 FaceAPIのエンドポイント(URL)が、画像データの形式(バイナリー or Base64テキスト)により異なるところに注意ください(ハマりました、、、)。 次回について 次回(#2)は本ローカルプログラムをFunctionsで動作させ、同様の結果が得られることを確認してみます。 参考情報 以下の情報を参考にさせていただきました。感謝申し上げます。 PowerApps のJSON関数を利用した写真の一括登録 Azure Face API で写真から感情判定 LINEボット2 FaceAPI 画像をjsonでPOSTしてflaskで受け取る
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】sympyによるグラフの図示

最近Pythonのモジュールであるnumpyやscipy, sympy, matplotlibを勉強中。 個人的備忘録として簡易的な関数の微分とグラフ図示を記事として纏める。 今回はガウス積分でよく出てくる $$y = e ^ {-x^2} $$ これをサンプルとして使う。 python import sympy as sp import matplotlib.pyplot as plt x = sp.symbols('x') fx = sp.E ** -x ** 2 # f(x)の一階微分 fx_1 = sp.diff(fx, x) p = sp.plot(fx, fx_1, (x, -3.5, 3), legend=True, show=False) p[0].label = "$f(x) = e^{-x^2}$" p[1].label = "$f(x) = -2xe^{-x^2}$" p[1].line_color = "red" plt.rcParams['figure.figsize'] = (9, 2.7) p.show()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

aws-cdkでリソースにタグを追加・上書きしたい

cdkを使ってリソース定義しようとしたらメソッドにtagsが用意されてない!でもCloudformationでは対応してるよ〜っていうときにタグを入れる方法 リソース名が気に入らないときの上書きにも使える ①Aspects.ofを使う ネットワークACLにNameタグをつける例 stack.py # vpcの定義は省略 nacl = ec2.NetworkAcl(self, 'id', vpc=vpc, subnet_selection=ec2.SubnetSelection( subnet_type=ec2.SubnetType.PUBLIC ), network_acl_name='nacl' ) core.Aspects.of(nacl).add(core.Tag("Name", 'your-public-nacl')) サブネットにまとめてNameタグをつける例 stack.py vpc = ec2.Vpc(self, 'vpc', max_azs=2, cidr='10.0.0.0/24', subnet_configuration=[ec2.SubnetConfiguration( subnet_type=ec2.SubnetType.PRIVATE, name="Private", cidr_mask=27 ), ec2.SubnetConfiguration( subnet_type=ec2.SubnetType.ISOLATED, name="Isolated", cidr_mask=27 ), ec2.SubnetConfiguration( subnet_type=ec2.SubnetType.PUBLIC, name='Public', cidr_mask=27, ) ], ) subnets_list = [vpc.public_subnets, vpc.private_subnets, vpc.isolated_subnets] for subnets in subnets_list: for isubnet in subnets: core.Aspects.of(isubnet).add(core.Tag("Name", 'your-subnet-name')) ②apply_aspectを使う (古いversionの人向け) ネットワークACLにNameタグをつける例 stack.py # vpcの定義は省略 nacl = ec2.NetworkAcl(self, 'id', vpc=vpc, subnet_selection=ec2.SubnetSelection( subnet_type=ec2.SubnetType.PUBLIC ), network_acl_name='nacl' ) nacl.node.apply_aspect(core.Tag("Name", 'your-public-nacl')) サブネットにまとめてNameタグをつける例 stack.py vpc = ec2.Vpc(self, 'vpc', max_azs=2, cidr='10.0.0.0/24', subnet_configuration=[ec2.SubnetConfiguration( subnet_type=ec2.SubnetType.PRIVATE, name="Private", cidr_mask=27 ), ec2.SubnetConfiguration( subnet_type=ec2.SubnetType.ISOLATED, name="Isolated", cidr_mask=27 ), ec2.SubnetConfiguration( subnet_type=ec2.SubnetType.PUBLIC, name='Public', cidr_mask=27, ) ], ) subnets_list = [vpc.public_subnets, vpc.private_subnets, vpc.isolated_subnets] for subnets in subnets_list: for isubnet in subnets: isubnet.node.apply_aspect(core.Tag("Name", "your-subnet-name"))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DeepLearningを用いた超解像手法/DRCNの実装

概要 深層学習を用いた、単一画像における超解像手法であるDRCNの実装したので、それのまとめの記事です。 Python + Tensorflow(Keras)で実装を行いました。 論文では長時間学習させているみたいですけど、今回は凄く控えめな規模で行っているので、ほぼ変化はありません... この論文は、CVPR2016で採択された論文で、以前実装したVDSRと同じ著者です。 同時期に2本も論文を執筆していたそうです... 凄い... 今回紹介するコードはGithubにも載せています。 学習済みモデルは容量の関係でアップロードできませんでした... 1. 超解像のおさらい 超解像について簡単に説明をします。 超解像とは解像度の低い画像に対して、解像度を向上させる技術のことです。 ここでいう解像度が低いとは、画素数が少なかったり、高周波成分(輪郭などの鮮鋭な部分を表す成分)がないような画像のことです。 以下の図で例を示します。(図は[論文]より引用) (a)は原画像、(b)は画素数の少ない画像を見やすいように原画像と同じ大きさにした画像、(c)は高周波成分を含まない画像の例です。 (b)と(c)は、荒かったりぼやけていたりしていると思います。 このような状態を解像度が低い画像といいます。 そして、超解像はこのような解像度が低い画像に処理を行い、(a)のような精細な画像を出力することを目的としています。 2. DRCNの超解像アルゴリズム DRCN(Deeply-Recursive Convolutional Network)は、その名の通りリカーシブなモデルとなっています。 リカーシブは、IT用語としてはよく聞くかもしれません。 日本語では、再帰的という意味です。 関数の定義の一部で,その関数自身が用いられるものを示します。 これを、深層学習的な意味合いに置き換えると、同じ畳み込み層を使用して、層の重みを共有しよう! といった感じになっています。 実際、今回紹介するモデルでは同じ畳み込み層を何度も繰り返し使用することで重みの共有をしています。 DRCNのアルゴリズムの概要図は以下の通りです。(図は論文から引用) 主に、 Embedding net:特徴マップの生成ネットワーク Inference net:メインの超解像ネットワーク Reconstruction net:再構成のネットワーク + Skip_connection の3つのネットワークに分かれています。 図では多数のネットワークが描かれていますが、同じ畳み込み層を繰り返し利用するので、パラメータ数は控えめになっています。 このモデルのイメージとしては、繰り返しCNNに画像を入力することで、徐々に画像をきれいにしていく感じです。 もう少し詳しい概要図は以下の通りです。(図は論文から引用) 図の通り、Conv + ReLUが基本構成になっています。また、フィルター数やフィルターサイズは基本的には同じものを使用します。 今回実装したDRCNは、事前にbicubicで拡大処理を行います。 3. 実装したアルゴリズム 今回、実装したDRCNのモデルは以下のように組みました。(コードの一部を抽出) 今回は繰り返し数を16、フィルター数は256、フィルターサイズは3*3で実装しています。 def DRCN(recursive_depth, input_channels, filter_num = 256): """ recursive_depth : numbers of recursive_conv2d. input_channels : channels of input_img.(gray → 1, RGB → 3) filter_num : filter numbers.(default 256) """ Inferencd_conv2d = Conv2D(filters = filter_num, kernel_size = (3, 3), padding = "same", activation = "relu") """ Inferencd_conv2d : Inference net. """ #model input_shape = Input((None, None, input_channels)) #Embedding net. conv2d_0 = Conv2D(filters = filter_num, kernel_size = (3, 3), padding = "same", activation = "relu")(input_shape) conv2d_1 = Conv2D(filters = filter_num, kernel_size = (3, 3), padding = "same", activation = "relu")(conv2d_0) #Inference net and Reconstruction net. weight_list = recursive_depth * [None] pred_list = recursive_depth * [None] for i in range(recursive_depth): Inferencd_output = Inferencd_conv2d(conv2d_1) Recon_0 = Conv2D(filters = filter_num, kernel_size = (3, 3), padding = "same", activation = "relu")(Inferencd_output) Recon_1 = Conv2D(filters = input_channels, kernel_size = (3, 3), padding = "same", activation = "relu")(Recon_0) weight_list[i] = Recon_1 for i in range(recursive_depth): skip_connection = Add()([weight_list[i], input_shape]) pred = Multiply()([weight_list[i], skip_connection]) pred_list[i] = pred pred = Add()(pred_list) model = Model(inputs = input_shape, outputs = pred) model.summary() return model 簡単なコードの説明をします。 まず、モデルの関数を呼び出す時に、いくつか値を設定します。 def DRCN(recursive_depth, input_channels, filter_num = 256): """ recursive_depth : numbers of recursive_conv2d. input_channels : channels of input_img.(gray → 1, RGB → 3) filter_num : filter numbers.(default 256) """ ここでは、recursive_depthが繰り返し数、input_channelsは入力画像のチャンネル数、filter_numがフィルター数を示しています。 今回は繰り返し数を16、フィルター数は256で実装したので、それを入れてもらえれば大丈夫です。 また、学習にグレースケール画像を使用したので、input_channelsは1です。 Kerasでは、レイヤーの重みを共有するために、事前にレイヤーを定義しておく必要があります。 事前にレイヤーを定義しておき、重みを共有したい層でそのレイヤーを都度呼び出すという感じです。 Kerasのドキュメントに詳細が書かれています。 本モデルでは以下のように、定義しました。Inference netの重みを共有させています。 Inferencd_conv2d = Conv2D(filters = filter_num, kernel_size = (3, 3), padding = "same", activation = "relu") """ Inferencd_conv2d : Inference net. """ また、最終的な結果は以下のように出力しました。 各再構成層の重みを $w_{n}$、Skip_connectionで足し合わせる入力画像を $y$、最終的な出力画像を $\hat{y}$、繰り返し数を $d$とすると、 $\hat{y} = \sum_{n = 1}^{d} w_{n}(y + w_{n}) $ となります。 各Reconstruction netの重みとSkip_connectionを足し合わせ、更に重みを掛け合わせ、それぞれの結果の和を出力結果としました。 4. 使用したデータセット 今回は、データセットにDIV2K datasetを使用しました。 このデータセットは、単一画像のデータセットで、学習用が800種、検証用とテスト用が100種類ずつのデータセットです。 今回は、学習用データと検証用データを使用しました。 パスの構造はこんな感じです。 train_sharp - 0001.png - 0002.png - ... - 0800.png val_sharp - 0801.png - 0802.png - ... - 0900.png このデータをBicubicで縮小したりしてデータセットを生成しました。 5. 画像評価指標PSNR 今回は、画像評価指標としてPSNRを使用しました。 PSNR とは Peak Signal-to-Noise Ratio(ピーク信号対雑音比) の略で、単位はデジベル (dB) で表せます。 PSNR は信号の理論ピーク値と誤差の2乗平均を用いて評価しており、8bit画像の場合、255(最大濃淡値)を誤差の標準偏差で割った値です。 今回は、8bit画像を使用しましたが、計算量を減らすため、全画素値を255で割って使用しました。 そのため、最小濃淡値が0で最大濃淡値が1です。 dB値が高いほど拡大した画像が元画像に近いことを表します。 PSNRの式は以下のとおりです。 PSNR = 10\log_{10} \frac{1^2 * w * h}{\sum_{x=0}^{w-1}\sum_{y=0}^{h-1}(p_1(x,y) - p_2(x,y))^2 } なお、$w$は画像の幅、$h$は画像の高さを表しており、$p_1$は元画像、$p_2$はPSNRを計測する画像を示しています。 6. コードの使用方法 このコード使用方法は、自分が執筆した別の実装記事とほとんど同じです。 ① 学習データ生成 まず、Githubからコードを一式ダウンロードして、カレントディレクトリにします。 Windowsのコマンドでいうとこんな感じ。 C:~/keras_DRCN> 次に、main.pyから生成するデータセットのサイズ・大きさ・切り取る枚数、ファイルのパスなどを指定します。 main.pyの12~21行目です。 使うPCのメモリ数などに応じで、画像サイズや学習データ数の調整が必要です。 main.py parser.add_argument('--train_height', type=int, default=41, help="Train data size(height)") parser.add_argument('--train_width', type=int, default=41, help="Train data size(width)") parser.add_argument('--test_height', type=int, default=360, help="Test data size(height)") parser.add_argument('--test_width', type=int, default=640, help="Test data size(width)") parser.add_argument('--train_dataset_num', type=int, default=10000, help = "Number of train datasets to generate") parser.add_argument('--test_dataset_num', type=int, default=5, help="Number of test datasets to generate") parser.add_argument('--train_cut_num', type=int, default=10, help="Number of train data to be generated from a single image") parser.add_argument('--test_cut_num', type=int, default=1, help="Number of test data to be generated from a single image") parser.add_argument('--train_path', type=str, default="../../dataset/DIV2K_train_HR", help="The path containing the train image") parser.add_argument('--test_path', type=str, default="../../dataset/DIV2K_valid_HR", help="The path containing the test image") 指定したら、コマンドでデータセットの生成をします。 C:~/keras_DRCN>python main.py --mode train_datacreate これで、train_data_list.npzというファイルのデータセットが生成されます。 ついでにテストデータも同じようにコマンドで生成します。コマンドはこれです。 C:~/keras_DRCN>python main.py --mode test_datacreate ② 学習 次に学習を行います。 設定するパラメータの箇所は、epoch数と学習率、今回のモデルの層の数です。 まずは、main.pyの22~26行目 main.py parser.add_argument('--recursive_depth', type=int, default=16, help="Number of Inference nets in the model") parser.add_argument('--input_channels', type=int, default=1, help="Number of channels for the input image") parser.add_argument('--BATCH_SIZE', type=int, default=64, help="Training batch size") parser.add_argument('--EPOCHS', type=int, default=100, help="Number of epochs to train for") 後は、学習のパラメータをあれこれ好きな値に設定します。74~90行目です。 今回は、モデルを使用するために、モデルの層の数とチャンネル数を設定しないといけません。 また、Loss functionの値に応じて、学習率を変更させる必要があるため、81行目でreduce_lrというKerasのコールバックを使用しています。詳しくはKerasのドキュメントをご覧ください。 main.py train_model = model.DRCN(args.recursive_depth, args.input_channels) optimizers = tf.keras.optimizers.SGD(lr=0.01, momentum=0.9, decay=1e-4, nesterov=False) train_model.compile(loss = "mean_squared_error", optimizer = optimizers, metrics = [psnr]) reduce_lr = tf.keras.callbacks.ReduceLROnPlateau(monitor = 'loss', factor = 0.1, patience = 5, mode = "min", min_lr = 1e-6) train_model.fit(train_x, train_y, epochs = args.EPOCHS, verbose = 2, callbacks = [reduce_lr], batch_size = args.BATCH_SIZE) train_model.save("DRCN_model.h5") optimizerはmomentum、損失関数はmean_squared_errorを使用しています。 学習はデータ生成と同じようにコマンドで行います。 C:~/keras_DRCN>python main.py --mode train_model これで、学習が終わるとモデルが出力されます。 ③ 評価 最後にモデルを使用してテストデータで評価を行います。 これも同様にコマンドで行いますが、事前に①でテストデータも生成しておいてください。 C:~/keras_DRCN>python main.py --mode evaluate このコマンドで、画像を出力してくれます。 7. 結果 出力した画像はこのようになりました。 なお、今回は輝度値のみで学習を行っているため、カラー画像には対応していません。 元画像 低解像度画像 生成画像 PSNR:24.48 平滑化フィルタみたいな感じの結果になってしまいました... パラメータとかモデルの構造をもう一回見直したほうがいいかもしれません。 最後に元画像・低解像度画像・生成画像の一部を並べて表示してみます。 8. コードの全容 前述の通り、Githubに載せています。 pythonのファイルは主に3つあります。 各ファイルの役割は以下の通りです。 data_create.py : データ生成に関するコード。 model.py : 超解像のアルゴリズムに関するコード。 main.py : 主に使用するコード。 9. まとめ 今回は、最近読んだ論文のDRCNを元に実装してみました。 これ本当に超解像なのか???という残念な結果になってしまいましたが、論文ではちゃんと結果が出ています!!! 詳しくは論文を読んでいただけると幸いです。 私自身もモデルの構造の再検討を後日してみようと思います。 記事が長くなってしまいましたが、最後まで読んでくださりありがとうございました。 参考文献 ・Deeply-Recursive Convolutional Network for Image Super-Resolution  実装の参考にした論文。 ・画素数の壁を打ち破る 複数画像からの超解像技術  超解像の説明のために使用。 ・DIV2K dataset  今回使用したデータセット。 ・Kerasドキュメント  重み共有・コールバックの実装の参考に使用。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

2021年 給料が一番高いプログラミング言語 5選

多くの人が高給のためにプログラミング業界を選びますが、世界で最も収益性の高いプログラミング言語は何か知っていますか? 1)Java 「Javaは時代遅れですか?」と尋ねるかもしれません。もちろん、そうではありません。 なぜJavaはまだ人気があるのですか?Javaは最も古く、堅牢なプログラミング言語の1つです。また、主にAndroidアプリケーション開発に使用されるオブジェクト指向言語です。これが、今日でも使用されている主な理由の1つです。しかし、Kotlin(Android開発にも適しています)のようなプログラミング言語の出現により、Javaの人気は低下しています。 ただし、Javaは依然として最も高額なプログラミング言語の1つであり、かなりの需要があります。Indeedによると、ソフトウェア開発会社はJava開発者に興味があり、毎年10万ドル以上の給料を支払う。 2)Swift Swift iOSアプリケーション開発は現在非常に人気があります。Swiftは非常に安定したプログラミング言語であり、勉強する価値があります。Javaに比べて、習得しやすいです。YouTubeには、学習に役立つリソースがたくさんあり、それを使ったプログラミングも楽しいものです。今Swiftの自由開発者であると、或いは関する仕事を従業すると、年給11.5万ドルに達する可能もあります。 3)SQL SQLまたはSequelは構造的な検索言語です。実際にはプログラミング言語ではないと思う人がいます。SQLは主にデータの管理とインタラクティブに用いられる。ですから、SQLはプログラミングに欠かせないスキルであり、どのタイプのWeb開発(バックエンドまたはフルスタック)でも、データを管理するためにそれを学ぶ必要があります。統計によると、SQL開発者の平均年収は9万ドルを超えています。 4)JavaScript これは不思議なプログラミング言語で、一部の人はそれが一番いいと思っています。JavaScriptは非常に人気のある言語です。GitHubをチェックすると、JavaScriptをサポートする新しいフレームワークが常に表示されます。さらに、すべてのブラウザはJavaScriptをサポートします。したがって、JavaScriptを習得することは、ソフトウェア開発に把握しなければならないスキルの一つであると言えます。JavaScript開発者は、9万ドルから11.3万ドルの範囲の収入を得ることができます。 5)Python GoogleトレンドとPyPI人気指数によると、Pythonは世界で最も人気のあるプログラミング言語の1つであり、確かに最も高額なプログラミング言語の1つです。GoogleはPythonで構築され、YouTubeもPythonで開発されました。 Pythonの驚くべき点は、汎用プログラミング言語であり、幅広いアプリケーションを構築するために使われています。更に人工知能に活躍しています。自動運転車、ウォルマートの自動精算、多くの自動化とマシン学習(ML)アプリはPythonを通じて開発されました。これはこの言語をより重要にし、急速に普及させます。さらに、Pythonは他のすべての言語よりも習得が容易であり、初心者にも優しいです。 また、複雑なアプリケーションを比較的簡単かつ迅速に構築することもできます。米国では、Python開発者の平均給与は約7.8万ドルですが、経験豊富な開発者は12.2万ドルにもなります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LEGO SPIKEプライムとRaspberryPiをUARTで連携する

今回はLEGO SPIKEPrimeとRaspberryPiを連携して使用していく前段階としてUARTを用いて信号をやり取りする仕組みを作ってみた。 今後はこれを応用してRaspberryPi側でカメラなどを用いて機械学習をしその結果に応じてSPIKEのモーターなどを駆動させる連携をさせていくう予定。 使うもの LEGO SPIKE PRIME RaspberryPi4(3でも可能なはず) Breadboard Connector Kit for SPIKE Prime Ultrasonic sensor cable(要はんだ付け) T6 トルクスドライバー (超音波センサーを分解するのに必要) RaspberryPiを環境構築するためのネットワーク環境 (RaspberrypiOSを用いるため別途SDHCカードも必要) RaspberryPiの環境構築 OS:RaspberryPi OSを利用する PCからリモートアクセスする場合はVNCviewerを使うと便利である。 RaspberryPiOSの準備が完了したら必要なものをインストールしていく。 今回はPythonエディタMuを利用する。 MuはPython3を簡単に実行できるエディタな他、SPIKEをUSB接続することでSPIKE内にあるMicroPythonを直接実行することができる。 RaspberryPi上でターミナルを起動してコマンドを入力してMuをインストールしていく。 1.Raspberrypi OSのリポジトリから必要なものをインストールする。 sudo apt-get install python3-pyqt5 python3-pyqt5.qsci python3-pyqt5.qtserialport python3-pyqt5.qtsvg python3-dev python3-gpiozero python3-pgzero libxmlsec1-dev libxml2 libxml2-dev 2.virtualenvの環境を作成する。 sudo pip3 install virtualenv virtualenv -p /usr/bin/python3 --system-site-packages ~/mu-venv 3.作成したvirtualenvの環境をアクティブにする。 source ~/mu-venv/bin/activate 4.Muをgitからクローンする。 git clone https://github.com/mu-editor/mu.git ~/mu-source 5.virtualenvを有効にした状態で、pipでRaspberry Pi用のPythonパッケージをインストールする。 cd ~/mu-source pip install -e ".[dev]" 6.Muのエディタを起動する。 mu-editor これでMuの導入は完了。 次回以降Muを起動するときは source ~/mu-venv/bin/activate mu-editor の順でターミナルでコマンドを実行すれば起動できる。(複数のターミナルで複数のMuを起動することもできる) RaspberryPiのGPIOを有効にする(UART5を利用する) SPIKEとの信号のやりとりに利用するGPIOを利用できるようにRaspberryPiの設定を行っていく。 1. RaspberryPi内の「/boot/config.txt」を編集する。 ファイル内の末尾に「dtoverlay=uart5」を追加で入力し保存する。 ファイルの編集はnanoエディターやviエディターを利用する。 以下はnanoエディターを利用して編集する際のコマンド。 sudo nano /boot/config.txt ↓ ファイル内編集、保存 2.新しいポートが開いてるかコマンドで確認する。 ls /dev/ttyA* ↓ 表示されるリストの中にttyAMA1があればOK 3.RaspberrypiとBreadboard Connector Kitを用いてSPIKEと接続する。 4.SPIKEをRaspberryPiのUSBポートに接続する。 これで通信を行う準備は完了。 通信のやり取りを行ってみる。 以下の手順でプログラムを作成し実行することで通信を行ってみた。 1.RaspberryPiでMuエディタを2つ立ち上げる。 RaspberryPi上でターミナルを2つ立ち上げ、それぞれで source ~/mu-venv/bin/activate mu-editor を実行することでMuのウィンドウが2つ立ち上がる。 2.2つ立ち上げたMuエディタのモードを変更する。 一方はPython3モード、一方はSPIKE MicroPythonモードに変更する。 3.RaspberryPi側、SPIKE側のPythonプログラムをそれぞれMuエディタにて作成する。 それぞれのPythonプログラムは以下 Raspberrypi側(Python3モード側) Test.py import time import serial,time ser = serial.Serial('/dev/ttyAMA1',115200) while True: if ser.in_waiting: #if data, read until \n x=ser.readline() print(x) ser.write(b’read: %s\n’ % x) SPIKE側(SPIKE MicroPythonモード側) Main.py import hub, time serial = hub.port.C serial.mode(hub.port.MODE_FULL_DUPLEX) time.sleep(1) serial.baud(115200) serial.read(1000) serial.write("\n testing \n\n") serial.read(1000) 4.RaspberryPiとSPIKEの接続を行い、2つのMuエディタにてそれぞれのPythonプログラムを実行する。 接続は以下の画像をのようにしている。(Aポートのモーターは次の項目で使用する。) プログラム内のwriteの記述部分が送信されている。 実行するとPython3側Muエディタのコンソール部分に受信したデータが出力される。(testing) 受信をフラグにしてSPIKEを制御する 信号のやり取りはできたので、それをSPIKEの制御に当てる。 同じようにそれぞれのMuエディタでプログラムを作成する。 Raspberrypi側(Python3モード側) Test2.py import time import serial # serial ini ser = serial.Serial('/dev/ttyAMA1', 115200) # start send print('start') while True: time.sleep(5.0) ser.write(b't') print('send test') SPIKE側(SPIKE MicroPythonモード側) Main2.py import hub, time from spike import Motor # serial ini serial = hub.port.C serial.mode(hub.port.MODE_FULL_DUPLEX) time.sleep(1) serial.baud(115200) # motor ini motor = Motor('A') # start recive print('start') while True: reply = serial.read(1000) if reply: response = reply.decode('utf-8') print(response) if response == 't': motor.run_for_degrees(360,speed=50) time.sleep(1.0) プログラムを作成できたらSPIKEのAポートにMモーターを接続する。 プログラムを実行して動作を確認するが、今回は受信待機状態になるSPIKE側から先に実行し、 その後Raspberyypi側を実行する。 正常に動作していればRaspberyypi側から5秒おきにバイト型のtが送信され、 受け取ったSPIKE側がutf-8にデコードし、tを受け取っていることを判定できたらモーターを360度回すという処理が行われる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VScodeで指定したパスにPython.exeが存在するのにインタプリタを選択できない

ターミナルからも起動できるのでインタプリタには問題ない 解決法 同じ階層にある.vscode>setting.jsonの一部が壊れていた(波括弧が閉じていなかった)のでそこを修正したら選択できた
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】超簡単!Flaskフレームワークで作るREST APIを試してみた

はじめに 社内のオンライン開発合宿というイベント向けに、アプリケーションを作る一環として、 (コロナ禍もあってオンライン...) Pythonを開発で使ってみたい Flaskが軽量フレームワークでDjangoよりも学習コストが低くく、とっかかりやすそう という理由で、バックエンドのアーキテクチャに組み込んで、試してみました◎ Flaskとは Flask(フラスク)は、プログラミング言語Python用の、軽量なウェブアプリケーションフレームワークである。標準で提供する機能を最小限に保っているため、自身を「マイクロフレームワーク」と呼んでいる。Werkzeug WSGIツールキットとJinja2テンプレートエンジンを基に作られている。BSDライセンスで公開されている。 参照元:wikipedia ふむふむ。 つまり、Flask自体の機能は最小限だから、自分の好きなように後でカスタマイズしてね ってことか。 Flaskの特徴 標準で提供する機能は最小限 拡張ライブラリは第三者によって提供 オブジェクトリレーショナルマッパ フォーム値の検証 ファイルのアップロード ユーザログイン 種々のオープンな認証技術 等々 自分のFlaskアプリで使うライブラリの準備 Flask自体と、それにまつわる拡張ライブラリをインストールしていきます。 ※[#] 以降に2021/05/14時点のversionを記載 ※Flask-Corsがpython3.7までのサポートとなっていたので、Python3.7で開発してます。 $ pip install Flask # 1.1.2 $ pip install Flask-Cors # 3.0.10 $ pip install python-dotenv # 0.17.1 $ pip install Flask-SQLAlchemy # 2.5.1 $ pip install flask-marshmallow # 0.14.0 $ pip install marshmallow-sqlalchemy # 0.25.0 自分のFlaskアプリのファイル構成 src ├── .env #環境変数の登録 ├── main.py #アプリ起動 ├── router.py #APIエンドポイントの集約。ルーター目的 ├── db.py #dbインスタンスの初期化 ├── settings.py #[.env]から環境変数の読込と設定 ├── logger.py #ロギング用のデコレータ ├── auth.py #APIの実行認可、認証目的 ├── controller │ └── user_controller.py #コントローラー ├── service │ └── user_service.py #ビジネスロジック ├── model │ └── user.py #モデル ├── config │ └── logging.json #ロギングの設定ファイル │ └── config.py #DBの各種設定 └── logs └── application.log #ログが出力されるファイル Flask(Python)で書いたコード 各ファイル自体の目的を明確にして、その用途にあった必要なコードのみの記述を意識しました。 今回の目的:FlaskのRest APIで主にやること DBに保存したユーザー情報を、APIを叩いて取得するまでの実践。 main.pyでアプリ起動 DB関連の初期化 アプリ起動 アプリに必要なインスタンスをBlueprint機能を使って登録(router) (__init__.pyとか作ってファイルを分けた方がいいかも?) main.py #!/usr/bin/python3 from flask import Flask from flask_cors import CORS import router from config import config import db def create_app(): # Generate Flask App Instance app = Flask(__name__) # Read DB setting & Initialize app.config.from_object(config.Config) db.init_db(app) db.init_ma(app) # Register Router Instance app.register_blueprint(router.router) # Additional Configuration app.config['JSON_AS_ASCII'] = False #日本語文字化け対策 app.config["JSON_SORT_KEYS"] = False #ソートをそのまま CORS( app, resources = { r"/api/*": {"origins": ["http://localhost", "http://localhost:4200"]} } ) return app app = create_app() if __name__ == "__main__": app.run(host='0.0.0.0', debug=True, port=8080, threaded=True) router.pyでAPIエンドポイントの管理 コントローラーへのルーティング設定を管理しています。 router.py from flask import Blueprint from controller import user_controller from logging import config from json import load import auth import logger # Generate Router Instance router = Blueprint('router', __name__) # Read Logging Configuration with open("./config/logging.json", "r", encoding="utf-8") as f: config.dictConfig(load(f)) @router.route("/", methods=['GET']) @logger.http_request_logging @auth.requires_auth def hello_world(): return "Hello World!!" @router.route("/api/v1/users/getUserList", methods=['GET']) @logger.http_request_logging @auth.requires_auth def api_v1_users_get_user_list(): return user_controller.get_user() @router.after_request def after_request(response): # response.headers.add('Access-Control-Allow-Origin', '*') response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization') response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS') return response controllerでシンプルに結果を返す クライアント側にビジネスロジックで生成物をレスポンスする。 user_controller.py from service.user_service import get_user_logic def get_user(): return get_user_logic() serviceでビジネスロジックを管理 DBから抽出したRawデータを加工する目的。 user_service.py from flask import make_response, jsonify from model.user import User, UserSchema def get_user_logic(): users = User.get_user_list() user_schema = UserSchema(many=True) return make_response(jsonify({ 'code': 200, 'users': user_schema.dump(users) })) modelでDBに問い合わせ Userモデルの定義と、CRUD系のメソッドを用意します。 user.py from db import db, ma from sqlalchemy.dialects.mysql import TIMESTAMP as Timestamp from sqlalchemy.sql.functions import current_timestamp class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, autoincrement=True, primary_key=True) name = db.Column(db.String(225), nullable=False) created_at = db.Column(Timestamp, server_default=current_timestamp(), nullable=False) updated_at = db.Column(Timestamp, server_default=current_timestamp(), nullable=False) # Contructor def __init__(self, id, name, created_at, updated_at): self.id = id self.name = name self.created_at = created_at self.updated_at = updated_at def __repr__(self): return '<User %r>' % self.name def get_user_list(): # SELECT * FROM users user_list = db.session.query(User).all() if user_list == None: return [] else: return user_list def get_user_by_id(id): return db.session.query(User)\ .filter(User.id == id)\ .one() def create_user(user): record = User( name = user['name'], ) # INSERT INTO users(name) VALUES(...) db.session.add(record) db.session.commit() return user # Difinition of User Schema with Marshmallow # refer: https://flask-marshmallow.readthedocs.io/en/latest/ class UserSchema(ma.SQLAlchemyAutoSchema): class Meta: model = User db.pyでdbインスタンスの初期化 MarshmallowというSQLAlchemyで受け取ったデータをJSONに変換してくれるライブラリも起動して、Flaskアプリのappに関連づけます。 db.py from flask_sqlalchemy import SQLAlchemy from flask_marshmallow import Marshmallow db = SQLAlchemy() ma = Marshmallow() def init_db(app): db.init_app(app) def init_ma(app): ma.init_app(app) config.pyでdb情報の設定を管理 settings.pyで設定したコンスタントを使って各種プロパティを指定。 config.py import settings class SystemConfig: # Flask DEBUG = True # SQLAlchemy SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@{host}/{db}?charset=utf8mb4'.format(**{ 'user': settings.MYSQL_USER, 'password': settings.MYSQL_PASSWORD, 'host': settings.MYSQL_HOST, 'db': settings.MYSQL_DATABASE }) SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_ECHO = False Config = SystemConfig settings.pyで環境変数を定数にセット settings.py # coding: UTF-8 import os from os.path import join, dirname from dotenv import load_dotenv dotenv_path = join(dirname(__file__), '.env') load_dotenv(dotenv_path) # Set Environment Variables to Constant AP = os.environ.get("API_KEY") MYSQL_ROOT_PASSWORD = os.environ.get("MYSQL_ROOT_PASSWORD") MYSQL_HOST = os.environ.get("MYSQL_HOST") MYSQL_DATABASE = os.environ.get("MYSQL_DATABASE") MYSQL_USER = os.environ.get("MYSQL_USER") MYSQL_PASSWORD = os.environ.get("MYSQL_PASSWORD") auth.pyでAPIに対する認証・認可周りを管理 ここで、APIの共通の認証処理ができるようになるデコレータ用の関数です。 HttpヘッダーのAuthorizationを使う場合に、何かしらできるみたい。 auth.py from flask import request from functools import wraps def requires_auth(func): @wraps(func) def wrapper(*args, **kwargs): auth = request.headers.get("Authorization", None) # ここで 認証処理 (...まだ書いていない) return func(*args, **kwargs) return wrapper logger.pyで共通のロギング関数デコレータを用意 APIを叩いた時に、ログを残せるようにしてます。 logger.py from flask import request, current_app from functools import wraps def http_request_logging(f): @wraps(f) def decorated_function(*args, **kwargs): logger = current_app.logger try: logger.info('%s - %s - %s - %s', request.remote_addr, request.method, request.url, request.query_string) except Exception as e: logger.exception(e) pass return f(*args, **kwargs) return decorated_function logging.jsonの設定内容 一例です。この設定を元に、loggerが頑張ってログ出力してくれます。 consoleとファイル出力ができるようにしてます。 logging.json { "version": 1, "formatters": { "default": { "format": "[%(asctime)s] [%(levelname)s] : %(message)s" } }, "loggers": { "file": { "handlers": ["file"], "level": "WARN", "qualname": "file", "propagate": "no" }, "wsgi": { "handlers": ["wsgi"], "level": "WARN", "qualname": "wsgi", "propagate": "no" } }, "handlers": { "file": { "class": "logging.handlers.TimedRotatingFileHandler", "formatter": "default", "filename": "./logs/application.log" }, "wsgi": { "class": "logging.StreamHandler", "stream": "ext://flask.logging.wsgi_errors_stream", "formatter": "default" } }, "root": { "level": "WARN", "handlers": ["file", "wsgi"] } } application.logに出力される内容 application.log [2021-05-14 11:06:28,844] [INFO] : * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit) [2021-05-14 11:06:28,845] [INFO] : * Restarting with stat [2021-05-14 11:06:29,080] [WARNING] : * Debugger is active! [2021-05-14 11:06:29,090] [INFO] : * Debugger PIN: 143-258-047 Flask REST API の実践と動作検証!! main.pyがある階層に移動して、アプリ起動!! $ cd /Users/username/src/github.com/flask-challenge/api/src $ python main.py * Serving Flask app "main" (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: on [2021-05-14 14:37:17,400] [INFO] : * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit) [2021-05-14 14:37:17,402] [INFO] : * Restarting with stat [2021-05-14 14:37:17,694] [WARNING] : * Debugger is active! [2021-05-14 14:37:17,704] [INFO] : * Debugger PIN: 143-258-047 アプリ起動できましたね〜 それでは、用意したAPIを叩いてみましょう!! レスポンスが返ってきた!!!! 嬉しす✨ Flaskでハマったポイント routerを独立したファイルにさせたいと思い、Blueprintの機能にたどり着いた。 ただ、Blueprintの機能が最初わからず、調べてもFlaskのtemplate(html)と関連した記事が結構出て戸惑った =>なんやかんやで、Flaskアプリ(appのこと)に、他でBlueprintとして起動したインスタンスを登録できるシンプルな機能だった。(便利) flask_corsの互換性が盲点だった。エラると嫌だからバージョンをPython3.7に下げた loggerやauthの実装をする時に、デコレータというPythonの概念でつまずいた。 2時間くらいデコレータのソースを見つめて、動画とか見て感覚を養ったら想像できるようになった。 結局は、元の関数の処理に、前処理として何か追加できるようになるよってこと。関数の装備品。 Marshmallowの英語の発音が難しすぎた SQLAlchemyと合わせて使うようで、結構調べる時間が必要だった FlaskとPython触った感想とまとめ 軽量フレームワークというだけあって、最初の導入はめちゃくちゃ楽だった。 import Flaskでアプリ起動すれば、すぐ使えるこの手軽さはやばい。 APIとかすぐ作れちゃうなこれ。 色々ライブラリも揃ってて組み合わせするともっと楽しそうだこれ。 しかもPython自体もすげえ感覚的にかける言語で、Javascriptにも似てるし、かなりストレスフレー!! 勉強にもなったし、最初のとっかかりにはとてもよかった。 業界で屈指のPythonフレームワークであるDjangoも、これから経験積んで色んな開発に携われるようになりたいでごわす。 以上、ありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[HUGO+GAE+FastAPI] HUGOで作成したサイトをBasic認証によるアクセス制限付きで公開

Hugoで作成したサイトをアクセス制限付きで公開したい Hugoはとても簡単にサイトが作れて、かっこいいテーマもたくさんあって楽しいですね。 あるコミュニティ向けにちょっとした情報サイトを作ろうと思い、そうすると閲覧制限を付けたいので、どうすると良いのかなと考えました。 Hugoで作成したサイトを Google App Engine で公開します。 GETリクエストをFastAPIで処理して、特定のパスに対してはBasic認証を通さないとコンテンツを戻さないようにすることで、HUGOのコンテンツを、制限なし、制限付きに分けて公開するできるようにしました。 構成 . ├── app.py ├── app.yaml ├── mysite │   ├── archetypes │   ├── assets │   ├── config.toml │   ├── content │   ├── data │   ├── layouts │   ├── public │   ├── resources │   ├── static │   └── themes └── requirements.txt 最終的なファイル構成はこのようになります。 mysite以下にHugoのサイトを置きます。 Hugoでサイトを作る Hugoでサイトを作ります。例として、以下のようにしています。 . ├── content │   ├── _index.md │   ├── docs │   │   ├── caution.md │   │   ├── doc.md │   │   └── secret │   │   └── secret.md docs/doc.md は通常のページ。制限無しに公開するページです。 docs/secret 以下にあるページは制限付きのページにします。 docs/caution.md は制限付きページの認証に失敗した際に表示するページです。 config.toml baseURL = "https://xxxxxxxxx.df.r.appspot.com/" languageCode = "ja" title = "おもしろサイト" ... config.toml の baseURL を GAEのURLに変えておきます。 GAE向けのファイルを用意する app.yaml runtime: python39 entrypoint: uvicorn app:app --port $PORT app.yaml シンプル。 requirements.txt fastapi uvicorn aiofiles requirements.txt aiofiles はFastAPIが FileResponse を返すのに必要みたいです。 webフレームワークはFastAPIでなくとも良いですが、FastAPIは起動が早いので気に入っています。 app.py import secrets from pathlib import Path from fastapi import FastAPI, Depends, status, HTTPException from fastapi.responses import FileResponse from fastapi.security import HTTPBasic, HTTPBasicCredentials app = FastAPI() security = HTTPBasic() @app.get("/docs/secret/{file_path:path}") async def secret_path( file_path: str, credentials: HTTPBasicCredentials = Depends(security) ): correct_username = secrets.compare_digest(credentials.username, "user") correct_password = secrets.compare_digest(credentials.password, "pass") if not (correct_username and correct_password): response = FileResponse("mysite/public/docs/caution/index.html") response.status_code = status.HTTP_401_UNAUTHORIZED return response target = Path("mysite/public/docs/secret/{}".format(file_path)) return read_file(target) @app.get("/{file_path:path}") async def public_path(file_path: str): target = Path("mysite/public/{}".format(file_path)) return read_file(target) def read_file(target: Path): if not target.exists(): response = FileResponse("mysite/public/404.html") response.status_code = status.HTTP_404_NOT_FOUND elif target.is_dir(): response = FileResponse(target.joinpath("index.html")) elif target.is_file(): response = FileResponse(target) else: raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) return response 説明 app.py @app.get("/docs/secret/{file_path:path}") async def secret_path( file_path: str, credentials: HTTPBasicCredentials = Depends(security) ): correct_username = secrets.compare_digest(credentials.username, "user") correct_password = secrets.compare_digest(credentials.password, "pass") if not (correct_username and correct_password): response = FileResponse("mysite/public/docs/caution/index.html") response.status_code = status.HTTP_401_UNAUTHORIZED return response target = Path("mysite/public/docs/secret/{}".format(file_path)) return read_file(target) ↑ まず制限付きページへのGETリクエストを受けるデコレータを、定義します。 Basic認証のログインダイアログを表示して、IDとPASSを受け取ります。 入力されたIDとPASSを検証して、一致していなければ、認証失敗を示すページをFileResponseで返します。 ステータスコードは認証失敗を示す401にしておきます。そうしておかないと、再度制限付きページにアクセスしたときにログインダイアログが表示されなくなります(一度入力したものが自動で使われてしまう?)。 ID/PASSが一致していれば、mysite/public以下のファイルパスへの置き換えをしてFileResponseを返します。 @app.get("/{file_path:path}") async def public_path(file_path: str): target = Path("mysite/public/{}".format(file_path)) return read_file(target) ↑ 制限のないページへのGETリクエストを受けるデコレータも定義します。 def read_file(target: Path): if not target.exists(): response = FileResponse("mysite/public/404.html") response.status_code = status.HTTP_404_NOT_FOUND elif target.is_dir(): response = FileResponse(target.joinpath("index.html")) elif target.is_file(): response = FileResponse(target) else: raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) return response ↑ mysite/public以下のファイルパスをチェックして、もし存在していなければ404.htmlを返します。(テーマによってはパスが違うのかもしれません) ディレクトリであれば、その直下のindex.htmlを戻します。ファイルであれば、そのまま返します。 さいごよくわからないものはとりあえず500 internal server error にしておきました。 こんなかんじ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoのmiddleware_chain

今回やる事 Djangoのmiddleware_chainの挙動に興奮したので、簡単なコードに書き換えて挙動を分かりやすく整理してみた。 どこの部分の話か Djangoで立てたサーバーに、リクエストが来た時にミドルウェアを通して動くように実装されている部分。 middleware_chainとは おおまかにまとめると、settings.pyで指定したミドルウェアリストの上から順にリクエストを渡し、一連のミドルウェアの処理が終わったら最終的にレスポンスが返されるやつ。 デコレータの仕組みにより、ミドルウェアリストを逆順でループさせて、後続のミドルウェアを自身のメソッドとして登録していく事で、この一連の流れを作っている。 まさにその名の通りの動きをする。 簡単に実装してみた ※色々省きます。 まずはミドルウェア関連 インスタンス化されると、引数で渡されたget_responseを自身のインスタンスにセットする。 コールで呼ばれたら process_requestが実装されていたら実行する。 self.get_responseにより、次のチェーンへとつなぐ。次がミドルウェアだったら1 ~ 2を繰り返す。 get_responseメソッドにたどり着いたら、今度はミドルウェアリストの逆順からprocess_responseを処理していく。 middlewares.py class MiddlewareMixin: def __init__(self, get_response): print('【', self.__class__.__name__, '】の 【 get_response 】に', res,'を指定\n') self.get_response = get_response def __call__(self, request): print('【call】', self.__class__) response = None if hasattr(self, 'process_request'): response = self.process_request(request) # 1 response = response or self.get_response(request) # 2 print('========================================') if hasattr(self, 'process_response'): response = self.process_response(request, response) # 3 return response class MiddlewareA(MiddlewareMixin): def process_request(self, request): print(' 1. MiddlewareAのprocess_request') def process_response(self, request, response): print(' 5. MiddlewareAのprocess_response') return response class MiddlewareB(MiddlewareMixin): def process_request(self, request): print(' 2. MiddlewareBのprocess_request') def process_response(self, request, response): print(' 4. MiddlewareBのprocess_response') return response リクエスト、レスポンスクラス http.py class HTTPRequest: pass class HTTPResponse: pass ハンドラークラス ・インスタンス化されたら、ミドルウェアチェーンを作成する。 ・コールで呼ばれたらリクエストをミドルウェアチェーンに渡す。 handler.py class Handler: def __init__(self): self.load_middleware() def __call__(self, request): self._middleware_chain(request) def load_middleware(self): handler = convert_exception_to_response(get_response) for mw in reversed(middleware_list): mw_instance = mw(handler) handler = convert_exception_to_response(mw_instance) print('ミドルウェアチェーンに直前のhandlerを指定: ', res, '\n') self._middleware_chain = handler メソッドなど ・get_responseメソッドがリクエストを処理するメソッド。 ・convert_exception_to_responseメソッドは、受け取った関数 or クラスを返すクロージャを返す。 utils.py def get_response(request): print('3. get_responseメソッド') print('get_response終わり') return HTTPResponse def convert_exception_to_response(get_response): global res if isinstance(get_response, Middleware): res = str(get_response.__class__.__name__) + ' を記憶したinner関数' elif hasattr(get_response, '__name__'): res = str(get_response.__name__) + ' を記憶したinner関数' print(res) print(' ↓ ') def inner(request): response = get_response(request) return response return inner ミドルウェアリスト settings.py middleware_list = [ MiddlewareA, MiddlewareB ] ハンドラーを動かしてみる main.py # インスタンス化されミドルウェアチェーンをセット handler = Handler() # callが呼ばれミドルウェアチェーンにリクエストを渡す handler(HTTPRequest) ↓ 結果 output.sh # ★ load_middlewareが動く。 # 最初はget_responseを記憶したinner関数を作る get_response を記憶したinner関数 ↓ # ミドルウェアリストを逆順にループ # 上で作ったinner関数を一番下のミドルウェアBにセット 【 MiddlewareB 】の 【 get_response 】に get_response を記憶したinner関数 を指定 # ミドルウェアBを記憶したinner関数を作る MiddlewareB を記憶したinner関数 ↓ # ミドルウェアBを記憶したinner関数をミドルウェアAにセット 【 MiddlewareA 】の 【 get_response 】に MiddlewareB を記憶したinner関数 を指定 # ミドルウェアAを記憶したinner関数を作る MiddlewareA を記憶したinner関数 ↓ # ミドルウェアAを記憶したinner関数を、ミドルウェアチェーンとしてセットする。 ミドルウェアチェーンに直前のhandlerを指定: MiddlewareA を記憶したinner関数 # ★ ミドルウェアチェーンにリクエストを渡すと、ミドルウェアAがinner関数内でコールで呼ばれる。 【call】 <class '__main__.MiddlewareA'> 1. MiddlewareAのprocess_request 【call】 <class '__main__.MiddlewareB'> 2. MiddlewareBのprocess_request 3. get_responseメソッド get_response終わり # get_responseメソッドまで行ったら、今度は逆順にミドルウェアのprocess_responseが動く。 ======================================== 4. MiddlewareBのprocess_response ======================================== 5. MiddlewareAのprocess_response このように、ミドルウェアチェーンにリクエストを渡したら、 MiddlewareAのprocess_request MiddlewareBのprocess_request get_responseメソッド MiddlewareBのprocess_response MiddlewareAのprocess_response という風に動いた。 まとめ ・予めミドルウェアチェーンを作成  ↓ ・やってきたリクエストをミドルウェアチェーンに渡す  ↓ ・ポンポンポンとミドルウェアを通った後get_responseメソッドが動く  ↓ ・興奮した これを踏まえてDjangoのソースを深く追ってみようと思う。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AzureMLで機械学習/推論を実行する(データ登録、コンピューティングクラスタでの学習、AKSへのデプロイ)

目的 AzureMLでデータを準備する方法と、それをAKSにデプロイ、使用する方法についてまとめました。 基本的には、Microsoftのチュートリアル通りです。 準備編 Workspaceへの接続 まずは、AzureMLのWorkspaceに接続します。 import azureml.core from azureml.core import Workspace # check core SDK version number print("Azure ML SDK Version: ", azureml.core.VERSION) ws = Workspace.from_config() print(ws.name, ws.location, ws.resource_group, sep='\t') ちなみにWorkspace接続は、以下のInteractiveLoginAuthenticationからでも可能です。 from azureml.core.authentication import InteractiveLoginAuthentication interactive_auth = InteractiveLoginAuthentication(tenant_id="テナントID") ws = Workspace(subscription_id="サブスクリプションID", resource_group="リソースグループ名", workspace_name="ワークスペース名", auth=interactive_auth) この際、デバイスログイン( https://microsoft.com/devicelogin )によるAzure ADへの認証が要求される場合があります。いつまでたっても接続できないときは、ちゃんと出力ログをチェックしましょう。 (筆者は、最初、気づかずになかなかできないなぁ、とずっと待っていました^^;) デバイスログインを避けるには、Service principalを使用したRBACによる自動ログインもできます。この話は別途、記述予定です。 データの準備 次は、学習に使うデータの準備です。 AzureMLでは、学習に使うデータはDatasetsにデータの場所を登録する必要があります。 Datasetsとして、利用可能なデータの場所は、"ローカルデータ"、"Datastoreに格納したデータ"、"任意のウェブ上のデータ"、"オープンデータセット"が利用可能です。 "Datastoreに格納したデータ"以外はあえて説明する必要もないので、説明は割愛します。 ここでは、pythonからローカルに準備したデータをDatastoreへ格納、Datasetsに登録する方法を示します。 from azureml.core import Datastore, Dataset import os import glob # workspaceblobstoreというデータストアを取得 # workspaceblobstoreはAzureMLを作成した際に自動的に作成されているはずです datastore = Datastore.get(ws, datastore_name='workspaceblobstore') target_path = 'data/train' # Datastore内でのデータの格納パス # ローカルのtrainディレクトリ内のpngファイルをDatastoreへアップロード datastore.upload_files(files=glob.glob(os.path.join('train', '*.png')), target_path=target_path, overwrite=True, show_progress=True) # データセットをDatastore上のパスから選択して登録 dataset = Dataset.File.from_files(path=[(datastore, target_path)]) print(dataset.to_path()) 学習用のコンピューティングクラスタにアタッチ コンピューティングクラスタとは、学習の時だけに立ち上げるコンピューティング環境です。 Tesla M60などの超高性能なGPUも利用可能です。稼働時間を学習時だけに限定できるので、コストを抑えることができます。 新たにコンピューティングクラスタを作成する際は、Pythonからもできますが、グラフィカルに比較しながら評価できるAzure Machine Learning StudioのGUIから作成したほうがベターです。 PriorityをLow priorityにすると劇的にコストが下がります。 Standard_NV6 (Nvidia Tesla M60)の場合: Dedicated 1.58\$/hr ⇒ Low priority 0.32\$/hr Low priorityに設定すると他のAzureML使用者が利用開始した場合に学習の実行が待たされる場合もあるようです。 ただ、高価なGPUをお安く利用できるメリットは計り知れないですね... 既存のコンピューティングクラスタへアタッチは以下の通りです。 from azureml.core.compute import AmlCompute from azureml.core.compute import ComputeTarget compute_name = os.environ.get("AML_COMPUTE_CLUSTER_NAME", "作成したクラスタ名") if compute_name in ws.compute_targets: compute_target = ws.compute_targets[compute_name] if compute_target and type(compute_target) is AmlCompute: print("found compute target: " + compute_name) else: # クラスタが作成されていない場合の処理を記載 学習の実行 Experimentの設定 学習の前に学習の実行を管理するExperimentを作成または設定する必要があります。 適当なExperiment名を設定しましょう。 from azureml.core import Experiment exp_name = 'my_exp' exp = Experiment(workspace=ws, name=exp_name) コンピューティングクラスタでの学習 コンピューティングクラスタでの学習は、Anaconda仮想環境がインストールされたDockerイメージを作成して実行しています。 Dockerイメージの作成は、SKLearnTensorFlow Estimatorなどのライブラリによって、自動的に作成されるので、Dockerの知識がなくとも問題ありません。 学習の実行で主にやることは、学習実行スクリプト、必要なPython/condaパッケージ指定となります。 Tensorflow Estimatorを使った場合の例を示します。 from azureml.train.dnn import TensorFlow from azureml.core import Workspace, Datastore, Dataset # train.pyへの入力引数定義 script_params = { '--data-path': 'data/train', '--batch-size': 10, '--epoch': 100 } est = TensorFlow( source_directory='scripts', # ローカルのscripts以下のスクリプトを使用 entry_script='train.py', # 学習実行のエントリーポイントとなるスクリプト script_params=script_params # train.pyへの入力引数 compute_target=compute_target, # コンピューティングクラスタインスタンス conda_packages=['mesa-libgl-cos6-x86_64', 'opencv=3.4.2'], # インストールするcondaパッケージ pip_packages=['keras<=2.3.1', 'scikit-learn'] # インストール pipパッケージ ) # 学習に必要な設定を登録 run = exp.submit(est) # 学習の実行 run.wait_for_completion(show_output=True) 画像前処理としてOpenCVを使うケースがあるかと思いますが、OpenCVを使用するためには、libGL.so.1が必要となるため、condaパッケージからインストールすることになります。 実行時にDockerイメージの作成をキャッシュなしで毎回行うので、実行開始までしばらく時間を要します。 このイメージの作成時間を短縮するためにキュレーションされた環境も利用できるようです。今後は、こちらが主流になりそうですね。 選別された環境を使用する Azure Machine Learning のキュレーションされた環境 学習用スクリプト 学習用スクリプト(train.py)は以下のように書きます。 入力として引数を受け付ける部分と出力を特殊ディレクトリ(outputs)を指定する以外に変わったところはないです。 train.py import argparse import numpy as np import os import glob import cv2 import keras import re from keras.callbacks import Callback, ModelCheckpoint from keras.layers import * from keras.models import Model from keras import losses from keras.utils.np_utils import to_categorical from sklearn.model_selection import train_test_split # Azure ML上で実行の結果をプロットするのに必要 import matplotlib.pyplot as plt from azureml.core import Run # 引数に指定した値を取得 parser = argparse.ArgumentParser() parser.add_argument('--data-path', type=str, dest='data_path', default='data_path', help='target data path') parser.add_argument('--batch-size', type=int, dest='batch_size', default=10, help='mini batch size for training') parser.add_argument('--epoch', type=int, dest='epoch', default=40, help='number of epochs') # 変数data_path, batch_size, epochに目的の値が格納される args = parser.parse_args() data_path = args.data_path print('training dataset is stored here:', data_path) # 画像データの読み込み train_files = glob.glob(os.path.join(data_path, '*.png')) train_imgs = [cv2.imread(path) for path in train_files] # 画像ファイル名から正解ラベルを指定 [ラベル]_0.png, [ラベル]_1_.pngなどを想定 targets = [os.path.basename(file).split('_')[0] for file in train_files] targets = to_categorical(targets, num_classes = len(np.unique(targets)) # 画像のshapeをkeras向けに変換 train_imgs = np.reshape(train_imgs, (train_imgs.shape[0], train_imgs.shape[1], train_imgs.shape[2], 1)) # 学習用データと訓練データの分割 x_train, x_test, y_train, y_test = train_test_split(train_imgs, targets, test_size=0.1) n_epochs = args.epoch batch_size = args.batch_size # TensorFlowのサイト通りのネットワーク(https://www.tensorflow.org/tutorials/images/cnn?hl=ja) model = models.Sequential() model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1))) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Conv2D(64, (3, 3), activation='relu')) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Conv2D(64, (3, 3), activation='relu')) model.add(layers.Flatten()) model.add(layers.Dense(64, activation='relu')) model.add(layers.Dense(10, activation='softmax')) model.summary() model.compile(optimizer='adam', loss=loss='sparse_categorical_crossentropy') # 学習の実行 run = Run.get_context() # outputsディレクトリにモデルが保存されます # outputsは特殊ディレクトリで、AzureML向けに作成されたのモデル保存用のAzure永続化ボリュームをマウントしています。 # この例ではModelCheckPointを使って、バリデーションロスが更新された場合のモデルを保存しています history = model.fit(x_train, y_train, batch_size=batch_size, epochs=n_epochs, callbacks=[ModelCheckpoint('outputs/model/weights.{epoch:03d}-{val_loss:.3f}.hdf5', monitor='val_loss', save_best_only=True, period=10)], validation_data=(x_test, y_test)) score = model.evaluate(x_test, y_test, verbose=0) # log a single value run.log("Final test loss", score) plt.figure(figsize=(6, 3)) plt.plot(history.history['val_loss'], 'r--', label='Loss', lw=4, alpha=0.5) plt.legend(fontsize=12) plt.grid(True) # log an image run.log_image('Accuracy vs Loss', plot=plt) # serialize NN architecture to JSON model_json = model.to_json() # モデルそのものも保存 with open('outputs/model/model.json', 'w') as f: f.write(model_json) # 最終エポック時のモデルも保存 model.save('outputs/model/model.h5') モデルの登録 学習が完了した後は、デプロイのためのモデルの登録です。 # 最終の重みを'cnn_model'というモデル名として登録 model = run.register_model(model_name='cnn_model', model_path='outputs/model/ model.h5') 学習が終わったモデルは、以下のコマンドでダウンロードして使用も可能です。 model_path = Model(ws, 'cnn_model').download(target_dir=os.getcwd(), exist_ok=True) あとは通常通り推論に使用できます。 モデルのデプロイ AzureMLでサポートされているモデルのデプロイ先は、ACI(Azure Container Instances)かAKS(Azure Kubernetes Service)のどちらかになります。 ACIは、Web APIの呼び出し時だけコンテナを起動して結果を返すサーバーレスサービスです。そのため、コストはかなり抑えられますが、重みのロードに時間がかかる画像処理系のサービスではオーバーヘッドが大きくなるため向いていません。 AKSは常時起動する仮想マシンクラスタです。常時起動のためコストはかかりますが、オーバーヘッドは小さくなります。 今回は画像解析系のサービスを動かすのでAKS一択です。 デプロイイメージの環境設定 デプロイ先の実行イメージ作成に必要な環境に必要なpip/anacondaのライブラリを定義したyamlファイルを作成します。 CondaDependenciesライブラリを使用して作成します。 from azureml.core.conda_dependencies import CondaDependencies myenv = CondaDependencies() myenv.add_pip_package("azureml-defaults") myenv.add_pip_package("scikit-learn==0.22.1") myenv.add_pip_package("tensorflow==2.2.0") myenv.add_pip_package("joblib") myenv.add_pip_package("keras") myenv.add_pip_package("scipy") myenv.add_conda_package('opencv=3.4.2') myenv.add_conda_package('mesa-libgl-cos6-x86_64') with open("myenv.yml","w") as f: f.write(myenv.serialize_to_string()) 元のdockerイメージの相性の問題で、ライブラリごとにcondaとpipどちらでインストールしたらいいかはトライ&エラーが必要になるかもしれません。 少なくともOpenCVを含める場合はcondaでインストールしましょう。 出力されたmyenv.ymlは以下のようになります。 myenv.yml # Conda environment specification. The dependencies defined in this file will # be automatically provisioned for runs with userManagedDependencies=False. # Details about the Conda environment file format: # https://conda.io/docs/user-guide/tasks/manage-environments.html#create-env-file-manually name: project_environment dependencies: # The python interpreter version. # Currently Azure ML only supports 3.5.2 and later. - python=3.6.2 - pip: - tensorflow==2.1.0 - azureml-defaults - joblib - keras==2.3.1 - opencv=3.4.2 - mesa-libgl-cos6-x86_64 channels: - anaconda - conda-forge AKSへの接続 デプロイ先のAKSへ接続します。 この中でKubernetes内でこのサービスが利用するリソース(CPU数、メモリ量)を定義します。 また、HTTPSアクセスにしたい場合は、証明書の設定も行います。 自前で準備するのが大変であれば、Microsoftが証明書を自動的に準備してくれます。 自前の証明書を使用することも可能です。 from azureml.core.webservice import AksWebservice, Webservice from azureml.core.compute import AksCompute, ComputeTarget from azureml.core.compute_target import ComputeTargetException resource_group = 'リソースグループ名' cluster_name = 'クラスタ名' name = 'サービス名' attach_config = AksCompute.attach_configuration(resource_group=resource_group, cluster_name=cluster_name) # microsoft発行のSSHを有効化する場合 ssl_enable = True if ssl_enable: domain_label = 'azureml-test-deploy' provisioning_config = AksCompute.provisioning_configuration() provisioning_config.enable_ssl(leaf_domain_label=domain_label) attach_config.enable_ssl(leaf_domain_label=domain_label) try: aks_target = ComputeTarget(workspace=ws, name=name) # すでにサービス名が存在してる場合(モデルをアップデートする場合) print('found exsiting') except ComputeTargetException: # サービス名が存在しない場合は新規に作成して、アタッチ print('not found') aks_target = ComputeTarget.attach(ws, name, attach_config) # デプロイの構成を設定 aksconfig = AksWebservice.deploy_configuration( cpu_cores=1, # 使用するCPUコア数 memory_gb=1, # 最大メモリ量 tags={"tag1": "任意のタグ値"}, # 任意のタグ指定 description='デプロイサンプル', # 任意の説明 # 以下は、自前のTLS証明を使用する場合 # ssl_enabled=True, # ssl_cert_pen_file='cert.pem', # ssl_key_pem_file='key.pem', # ssl_cname='ドメイン名' ) デプロイ 事前に用意したデプロイイメージの環境設定と、推論時に実行するスクリプトを指定して、モデルのデプロイを行います。 from azureml.core.webservice import AksWebservice, Webservice from azureml.core.model import Model from azureml.core.model import InferenceConfig from azureml.core.environment import Environment # イメージの環境設定 myenv = Environment.from_conda_specification(name="myenv", file_path="myenv.yml") # 推論用スクリプト inference_config = InferenceConfig(entry_script="scripts/score.py", environment=myenv) # デプロイ service = Model.deploy(ws, name, [model], inference_config, aksconfig, aks_target, overwrite=True) service.wait_for_deployment(show_output = True) print(service.state) print(service.get_logs()) 推論スクリプト 推論スクリプトにはデプロイ直後に1度だけ実行されるinit()とリクエストごとに実行するrun()を実装します。 init()は1度しか実行されないので、ここでモデルをメモリのロードしておくことで、リクエスト時の処理を高速化できます。 ここでは、rawhttpを使って、生の画像バイナリデータを受け付ける特殊な場合です。 import json import numpy as np import os import keras import cv2 from azureml.core.model import Model from azureml.contrib.services.aml_request import AMLRequest, rawhttp from azureml.contrib.services.aml_response import AMLResponse def init(): global model # 登録したモデルを指定 model_path = Model.get_model_path(model_name = 'cnn_model') model = keras.models.load_model(model_path) # 画像の生データを受け付ける場合 @rawhttp def run(request): try: if request.method == 'GET': respBody = str.encode(request.full_path) return AMLResponse(respBody, 200) elif request.method == 'POST': data = request.get_data(False) data = np.frombuffer(data, dtype=np.uint8) img = cv2.imdecode(data, cv2.IMREAD_UNCHANGED) result = model.predict(img) return AMLResponse(result, 200) else: return AMLResponse('bad request', 500) except Exception as e: result = str(e) return {'error': result} return デプロイしたモデルを使用する デプロイに成功するとAzure Machine Learning StudioでEndpointが確認できるようになります。 このEndpointsに記載されているREST endpointとprimary keyもしくはsecondary keyを使ってアクセスします。 Swaggerに関するエラーが出力されていますが、Swaggerを定義していなくとも利用自体は問題ありません。ただ、定義しておくと通常のRESTfulなAPIとして利用が可能となり、PowerBI等からアクセスすることが可能となります。これは別途、記事を書きたいと思います。 以下にpythonでの使用例を示します。 この場合もやはり、生の画像データを送信する特殊なパターンです。 jsonデータをやり取りする場合は、headerのContent-Typeをapplication/jsonにすればよいです。 import requests key1 = 'primary key' key2 = 'secondary key' endpoint = 'https://ドメイン名.japaneast.cloudapp.azure.com:443/api/v1/service/サービス名/score' headers = {'Content-Type':'application/octet-stream', 'Authorization':('Bearer ' + key1)} with open(filename, 'rb') as f: img = f.read() response = requests.post(endpoint, headers=headers, data=img) 結構長くなってしまいましたが、これでAzure MLの一通りの操作ができるはずです。 お疲れ様でした。 参考文献 Azure Machine Learning ワークスペースを作成して管理する 機械学習モデルを Azure にデプロイする Web サービスとしてデプロイされた Azure Machine Learning モデルを使用する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで1変数のニュートン法を実装してみる

16 - 2x^3 = 0 の解を求めてください。 簡単ですね、x = 2 が答えです。 x - y = 1, x + y = 3 なんてどうでしょう。 (x, y) = (2, 1)が答えです、これもすぐに求まりました。 では、5x - exp(x) = 0 の解はいくつでしょう。 答えは、x = 0.259171, 2.542641 です、ペンと紙があっても時間がかかりそう。 このような複雑化された方程式の解を求める数値解法アルゴリズムには、 ニュートン法 二分法 Regula-falsi法 Bairstow法 などがあります。 今回は、そのうちのニュートン法をPythonで実装してみたいと思います。 ニュートン法とは まず初めに、予想される真の解に近いと思われる値をひとつとる。次に、そこでグラフの接線を考え、その x 切片を計算する。このx切片の値は、予想される真の解により近いものとなるのが一般である。以後、この値に対してそこでグラフの接線を考え、同じ操作を繰り返していく。 https://ja.wikipedia.org/wiki/%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%88%E3%83%B3%E6%B3%95 要は適当にxの値を決めて、徐々に真の解へと値を近づけていくというものです。 プログラムの流れは以下のような感じで実装します。 初期値 x = x0 (ここで f(x0) ≠ 0)とします。 真の解を xtrue とし、xtrue = x0 + Δx を仮定すると f(x0 + Δx) = 0   ‥‥‥① が成り立ちます。 ①をテイラー展開すると f(xi + Δx) = 0 = f(xi) + f'(xi)Δx + ‥‥   ‥‥‥② ②式のΔxの一次項までを考慮してΔxの近似値を計算すると (Δxの近似値) ≅ - f(xi) / f'(xi) 次に xi += (Δxの近似値) これを繰り返して、 (Δxの近似値) ≅ 0 となった時、 xi を真の解 xtrue としてプログラムを終了します。 ソースコード from math import exp def func(x): return 5 * x - exp(x) def diff_func(x): return 5 - exp(x) def main(): max = 10 dx = 0.0 x = float(input("初期値x0:")) for i in range(max): dx = - func(x)/diff_func(x) print("{}:dx = {:.6f}\n".format(i+1, dx)) x += dx if -1.0e-6 &lt; dx and dx &lt; 1.0e-6: print("x = {:.6f}\n".format(x)) exit() print("error\n") if __name__ == "__main__": main() 出力 >>出力1 初期値x0:1 1:dx = -1.000000 2:dx = 0.250000 3:dx = 0.009157 4:dx = 0.000015 5:dx = 0.000000 x = 0.259171 >>出力2 初期値x0:2 1:dx = 1.092877 2:dx = -0.385907 3:dx = -0.145130 4:dx = -0.018900 5:dx = -0.000298 6:dx = -0.000000 x = 2.542641 20数行とコンパクトに収めることができました。 特段難しい箇所があるわけでもなく、理解ができていれば実装は容易だと思います。 注意する点は、解が2つ存在するので初期値によって収束する値が異なるといったことくらいでしょうか。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ABC146-C問題で二分探索法を理解する

今日は二分探索(binary search)について書いてみます。 使用言語はPythonです。 二分探索とは ソート済みのリストや配列に入ったデータ(同一の値はないものとする)に対する検索を行うにあたって、 中央の値を見て、検索したい値との大小関係を用いて、検索したい値が中央の値の右にあるか、左にあるかを判断して、片側には存在しないことを確かめながら検索していく。 https://ja.wikipedia.org/wiki/%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2 二分探索は資格試験でも度々登場する非常に有名で基本的なアルゴリズムです。 また、AtCoderなどに参加している方がまず学習すべきアルゴリズムでもあります。 ABCのA問題やB問題では全探索(full search)で解ける問題が多いですが、計算量がO(n)であることから多くの計算が求められる問題になるとACを出すことが難しくなります。 それに対して二分探索は計算量がO(log_2(n))と非常に高速に計算処理を行うことが可能です。 ABC146 - C問題を例に、全探索と二分探索の2つの手法で問題を解いて二分探索を体感してみます。 実行結果 全探索 まずは、全探索で解いてみます。 import time def cost(n): return a * n + b * len(str(n)) a, b, x = map(int, input().split()) n_max = 10**9 start_time = time.time() if cost(1) > x: print(0) exit() elif cost(10**9) &lt;= x: print(10**9) exit() for n in range(n_max): d = len(str(n)) if cost(n) > x: elapsed_time = time.time() - start_time   print(n-1) print("\n経過時間:{}".format(elapsed_time) + "[秒]") exit()</pre> >>入力 13 48 498974678 >>出力 38382638 実行時間:32.40821957588196[秒] データnに対する全探索の計算量はO(n)で今回の入力ケースでは、実行時間は約32秒でした。 このまま提出してももちろん結果はTLEです。 次は二分探索法を用いて解いてみます。 二分探索 import time def cost(n): return a * n + b * len(str(n)) a, b, x = map(int, input().split()) n_max = 10**9 start_time = time.time() bottom = 1 top = 10**9 if cost(1) > x: print(0) exit() elif cost(10**9) &lt; x: print(10**9) exit() while top - bottom > 1: mid = (bottom + top) // 2 if cost(mid) > x: top = mid else: bottom = mid elapsed_time = time.time() - start_time print(bottom) print("経過時間:{}".format(elapsed_time) + "[秒]")</pre> >>入力 13 48 498974678 >>出力 38382638 実行時間:0.0[秒] 実行時間は限りなく0に近い値が出ました、ちなみにループ回数は30回です。 これからわかるように、二分探索は全探索と比較して確かに計算量が小さいことがわかります。 では、具体的な計算量の違いを以下で確認していきます。 データ量 n 全探索 O(n) 二分探索 O(log_2(n)) 1 1 0 10 10 3.321928 100 100 6.643856 500 500 8.965784 1000 1000 9.965784 5000 5000 12.287712 10000 10000 13.287712 50000 50000 15.609640 100000 100000 16.609640 500000 500000 18.931569 1000000 1000000 19.931569 %matplotlib inline import matplotlib.pyplot as plt import numpy as np x = np.arange(1,50) #全探索 f = x #二分探索 g = np.log2(x) plt.xlabel("n", size="14") plt.ylabel("amount of calculation", size="14") plt.plot(x, y1, label="full search") plt.plot(x, y2, label="binary search") plt.legend() 以上から二分探索が如何に優れたアルゴリズムであるかがわかります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoのチュートリアルを読んでいく!【その2 Django初心者】

はじめに 私はプログラミング歴1年の初心者です。 実務でWebサイトのコーディングを1年間行ってきました。 そろそろシステム開発もできるようになりたいということで LaravelやReactをこれから勉強していこうと思っております。 今回の目的 Djangoでアプリを実際に作っていく流れを学ぼうと思います。 公式チュートリアルを読んでいきます。 目次 オーバービュー 簡単なフォーム テスト モデル アプリ公開 実践 オーバービュー 一覧ビューを実装 polls/views.py # ビューを追加 def detail(request, question_id): return HttpResponse("You7re looking at question %s." % question_id) def results(request, question_id): response = "You're looking at the results of question %s." return HttpResponse(response % question_id) def vote(request, question_id): return HttpResponse("You're voting on question %s." % question_id) polls/urls.py # Routingを追加 urlpatterns = [ # ex: /polls/ path('', views.index, name='index'), # ex: /polls/5/ path('<int:question_id>/', views.detail, name='detail'), # ex: /polls/5/results/ path('<int:question_id>/results/', views.results, name='results'), # ex: /polls/5/vote/ path('<int:question_id>/vote/', views.vote, name='vote'), ] polls/views.py # 動的なサイトを作ってみる(index()を変更) from django.http import HttpResponse from .models import Question def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] output = ', '.join([q.question_text for q in latest_question_list]) return HttpResponse(output) polls/templates/polls/index.html # テンプレートフォルダを作成してビューを作成する(不完全HTML) {% if latest_question_list %} <ul> {% for question in latest_question_list %} <li><a href="/polls/{{ quetsion.id }}/">{{ question.question_text }}</a></li> {% endfor %} </ul> {% else %} <p>No polls are available.</p> {% endif %} polls/views.py # indexビューを変更する from django.template import loader def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] template = loader.get_template('polls/index.html') context = { 'latest_question_list': latest_question_list, } return HttpResponse(template.render(context, request)) polls/view.py # renderショートカットを使う(リファクタリング) →こうすることでloaderやHttpResponseは必要なくなる from django.shortcuts import render def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] context = { 'latest_question_list': latest_question_list, } return render(request, 'polls/index.html', context) 詳細ビューを実装 polls/views.py # 詳細ビューを実装(404エラー画面) from django.http import Http404 def detail(request, question_id): try: question = Question.objects.get(pk=question_id) except Question.DoesNotExist: raise Http404("Question does not exist") return render(request, 'polls/detail.html', {'question': question}) polls/templates/polls/detail.html # とりあえず簡単に実装 {{ question }} polls/views.py # 404エラーショートカットを使う(リファクタリング) from django.shortcuts import get_object_or_404, render def detail(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/detail.html', {'question': question}) polls/templates/polls/detail.html # 詳細画面を実装する <h1>{{ question.questionn_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }}</li> {% endfor %} </ul> ハードコードされているindexのURLを変更 polls/templates/polls/index.html # 今までの書き方だと変更に強くない(テンプレートを直す必要あり) <li><a href="/polls/{{ quetsion.id }}/">{{ question.question_text }}</a></li> ↓↓↓↓ 変更 ↓↓↓↓ # 変更に強くする(urlsモジュールを変更すればいい) <li><a href="{% url 'detail' question_id %}">{{ question.question_text }}</a></li> 名前空間を使ってリファクタリング polls/urls.py # 名前空間を追加する app_name = 'polls' polls/templates/polls/index.html # 名前空間のついた記述に変更する <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li> ↓↓↓↓ 以下のように変更 ↓↓↓↓ <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li> 作成保存機能 簡単なフォーム実装 polls/templates/polls/details.html <h1>{{ question.question_text }}</h1> {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} <form action="{% url 'polls:vote' question.id %}" method="post"> {% csrf_token %} {% for choice in question.choice_set.all %} <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}"> <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br> {% endfor %} <input type="submit" value="Vote"> </form> polls/views.py # urlsにはvoteを作成してあるので、vote()を実装 from django.http import HttpResponse, HttpResponseRedirect from django.urls import reverse from .models import Choice, Question def vote(request, question_id): question = get_object_or_404(Question, pk=question_id) try: selected_choice = question.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): return render(request, 'polls/details.html', { 'question': question, 'error_message': "You didn't select a choice.", }) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse('polls:results', args=(question.id,))) 結果画面を実装する polls/views.py # 結果画面ビューを編集する from django.shortcuts import get_object_or_404, render def results(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/results.html', {'question': question}) polls/templates/polls/results.html # 結果画面の実装 <h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li> {% endfor %} </ul> <a href="{% url 'polls:detail' question.id %}">Vote again?</a> リファクタリング polls/urls.py # indexとdetailとresultのRoutingを変更する urlpatterns = [ # ex: /polls/ path('', views.IndexView.as_view(), name='index'), # ex: /polls/5/ path('<int:pk>/', views.DetailView.as_view(), name='detail'), # ex: /polls/5/results/ path('<int:pk>/results/', views.ResultsView.as_view(), name='results'), # ex: /polls/5/vote/ path('<int:question_id>/vote/', views.vote, name='vote'), ] polls/views.py # index,detail,resultsのビューをDjangoの汎用ビューに変更する from django.http import HttpResponseRedirect from django.urls import reverse from django.views import generic class IndexView(generic.ListView): template_name = 'polls/index.html' context_object_name = 'latest_question_list' def get_queryset(self): """Return the last five published questions.""" return Question.objects.order_by('-pub_date')[:5] class DetailView(generic.DetailView): model = Question template_name = 'polls/detail.html' class ResultsView(generic.DetailView): model = Question template_name = 'polls/results.html' テスト プログラムが正しく動くことは先に確認しておくべき。 のちのち動かないことがわかったときに困る。 テストの無いコードは、デザインとして壊れている。 テスト駆動開発 →コードを書く前にテストを書く テストコードは長くなってしまってもいいので 何をしているのかをわかりやすく テスト作成 # shellでバグを確認する $ python manage.py shell >>> import datetime >>> from django.utils import timezone >>> from polls.models import Question >>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30)) >>> future_question.was_published_recently() polls/tests.py # コメントも書きつつ、テストコードを書く # テスト用のメソッドはtestから始まる import datetime from django.test import TestCase from django.utils import timezone from .models import Question class QuestionModelTests(TestCase): def test_was_published_recently_with_future_questions(self): """ was_published_recently() returns False for questions whose pub_date is in the future. """ time = timezone.now() + datetime.timedelta(days=30) future_question = Question(pub_date=time) self.assertIs(future_question.was_published_recently(), False) # テスト実行 $ python3 manage.py test polls バグ修正 polls/models.py # バグ修正をする def was_published_recently(self): return self.pub_date >= timezone.now() - datetime.timedelta(days=1) ↓↓↓↓ 変更する ↓↓↓↓ def was_published_recently(self): now = timezone.now() return now - datetime.timedelta(days=1) <= self.pub_date <= now 他のテストも追加する polls/tests.py # 他の包括的なテストも同じクラスに追加する import datetime from django.test import TestCase from django.utils import timezone from .models import Question class QuestionModelTests(TestCase): def test_was_published_recently_with_future_questions(self): """ was_published_recently() returns False for questions whose pub_date is in the future. """ time = timezone.now() + datetime.timedelta(days=30) future_question = Question(pub_date=time) self.assertIs(future_question.was_published_recently(), False) def test_was_published_recently_with_old_question(self): """ was_published_recently() returns False for questions whose pub_date is older than 1 day. """ time = timezone.now() - datetime.timedelta(days=1, seconds=1) old_question = Question(pub_date=time) self.assertIs(old_question.was_published_recently(), False) def test_was_published_recently_with_recent_question(self): """ was_published_recently() returns True for questions whose pub_date is within the last day. """ time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59) recent_question = Question(pub_date=time) self.assertIs(recent_question.was_published_recently(), True) ビューに関するテストも行う ユーザーがブラウザを通して経験する動作をチェックする # shellを使って確認する $ python3 manage.py shell >>> from django.test.utils import setup_test_environment >>> setup_test_environment() >>> from django.test import Client >>> client = Client() >>> response = client.get('/') >>> response.status_code >>> from django.urls import reverse >>> response = client.get(reverse('polls:index')) >>> response.status_code >>> response.content >>> response.context['latest_question_list'] polls/views.py # get_querysetを修正する class IndexView(generic.ListView): template_name = 'polls/index.html' context_object_name = 'latest_question_list' def get_queryset(self): """Return the last five published questions.""" return Question.objects.order_by('-pub_date')[:5] ↓↓↓↓ 以下のように変更する ↓↓↓↓ from django.utils import timezone def get_queryset(self): """ Return the last five published questions (not including those set to be published in the future). """ return Question.objects.filter( pub_date__lte=timezone.now() ).order_by('-pub_date')[:5] polls/tests.py # 以下のビューのテストコードを追記する from django.urls import reverse def create_question(question_text, days): """ Create a question with the given `question_text` and published the given number of `days` offset to now (negative for questions published in the past, positive for questions that have yet to be published). """ time = timezone.now() + datetime.timedelta(days=days) return Question.objects.create(question_text=question_text, pub_date=time) class QuestionIndexViewTests(TestCase): def test_no_questions(self): """ If no questions exist, an appropriate message is displayed. """ response = self.client.get(reverse('polls:index')) self.assertEqual(response.status_code, 200) self.assertContains(response, "No polls are available.") self.assertQuerysetEqual(response.context['latest_question_list'], []) def test_past_question(self): """ Questions with a pub_date in the past are displayed on the index page. """ question = create_question(question_text="Past question.", days=-30) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], [question], ) def test_future_question(self): """ Questions with a pub_date in the future aren't displayed on the index page. """ create_question(question_text="Future question.", days=30) response = self.client.get(reverse('polls:index')) self.assertContains(response, "No polls are available.") self.assertQuerysetEqual(response.context['latest_question_list'], []) def test_future_question_and_past_question(self): """ Even if both past and future questions exist, only past questions are displayed. """ question = create_question(question_text="Past question.", days=-30) create_question(question_text="Future question.", days=30) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], [question], ) def test_two_past_questions(self): """ The questions index page may display multiple questions. """ question1 = create_question(question_text="Past question 1.", days=-30) question2 = create_question(question_text="Past question 2.", days=-5) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], [question2, question1], ) 詳細ビューのテスト polls/views.py # 以下のget_queryset()を追加する class DetailView(generic.DetailView): model = Question template_name = 'polls/detail.html' def get_queryset(self): """ Excludes any questions that aren't published yet. """ return Question.objects.filter(pub_date__lte=timezone.now()) polls/test.py # 以下のテストを追加する class QuestionDetailViewTests(TestCase): def test_future_question(self): """ The detail view of a question with a pub_date in the future returns a 404 not found. """ future_question = create_question(question_text='Future question.', days=5) url = reverse('polls:detail', args=(future_question.id,)) response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_past_question(self): """ The detail view of a question with a pub_date in the past displays the question's text. """ past_question = create_question(question_text='Past Question.', days=-5) url = reverse('polls:detail', args=(past_question.id,)) response = self.client.get(url) self.assertContains(response, past_question.question_text) モデル アプリ公開 さいごに views.pyは ビューコントローラみたいなものか? チュートリアルを読むと 実際にPythonってこうやって使うのかというのがわかる postメソッドというのは サーバー側のデータの更新につながるものに使う
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonのバージョンが2から3に変わらない現象【体験談】

エンジニア歴2年のkitagawaです。 Pythonを触り始めて、バージョンが変わらない現象が起きました。 調べてみてたくさん記事が出てきましたが、手っ取り早くバージョンアップできる方法を忘備録として記載します。 % python -V Python 2.7.16 と表示されてしまいます。 そこで、~/.bash_profileの内容を変更し反映させれば、最新のバージョンになります。 vi ~/.bash_profile ~/.bash_profile export PATH=/Library/Frameworks/Python.framework/Versions/3.9/bin:$PATH export PYENV_ROOT="$HOME/.pyenv" export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init -)" 上記が記入できたらESC押下し、:wqと入力で保存して終了。(:qaだと保存せず終了) source ~/.bash_profileをし反映させると、Pythonのバージョンが変わっていることが確認できます。 % source ~/.bash_profile % python -V Python 3.9.4 ちなみに原因はわからないのですが、再起動してしまうとバージョンが戻ってしまうので、私はいつもsourceで反映させてからバージョンアップしています(謎)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Safariでmp4動画が再生できません!

はじめに こんにちは streampack チームのメディです。 https://cloudpack.jp/service/option/streampack.html Copyrights コピーレフトのイラスト : giraffe-africa-tanzania-wild コピーレフトの動画 : bright-autumn-colors 目的 一部のサーバーを使用しているときにSafariでmp4動画ファイルを再生できない理由を理解します。 ツール 2つのHTTPサーバーを比較します。 最初のサーバーはPython3のデフォルトのHTTPサーバーです。 2番目のサーバーはPython3のデフォルトサーバーに似ていますが、Rangeリクエストもサポートしています。 サーバーの比較 Python3デフォルトサーバー python -m http.server Python3 RangeHTTPServer サーバー pip install RangeHTTPServer python -m RangeHTTPServer 結果 デスクトップバージョンのSafariバージョン14.1でのテスト。 Rangeリクエストサポートなしのサーバー Rangeリクエストサポートありのサーバー まとめ Range HTTPリクエストヘッダーは、Safariでのmp4動画ファイル再生をサポートするために必須です。 情報元 https://github.com/danvk/RangeHTTPServer https://blog.logrocket.com/streaming-video-in-safari/ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range https://pixabay.com/photos/giraffe-africa-tanzania-wild-1330814/ https://www.pexels.com/video/bright-autumn-colors-1583096/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Safariでmp4動画が再生できないときの原因

はじめに こんにちは streampack チームのメディです。 https://cloudpack.jp/service/option/streampack.html Copyrights コピーレフトのイラスト : giraffe-africa-tanzania-wild コピーレフトの動画 : bright-autumn-colors 目的 一部のサーバーを使用しているときにSafariでmp4動画ファイルを再生できない理由を理解します。 ツール 2つのHTTPサーバーを比較します。 最初のサーバーはPython3のデフォルトのHTTPサーバーです。 2番目のサーバーはPython3のデフォルトサーバーに似ていますが、Rangeリクエストもサポートしています。 サーバーの比較 Python3デフォルトサーバー python -m http.server Python3 RangeHTTPServer サーバー pip install RangeHTTPServer python -m RangeHTTPServer 結果 デスクトップバージョンのSafariバージョン14.1でのテスト。 Rangeリクエストサポートなしのサーバー Rangeリクエストサポートありのサーバー まとめ Range HTTPリクエストヘッダーは、Safariでのmp4動画ファイル再生をサポートするために必須です。 情報元 https://github.com/danvk/RangeHTTPServer https://blog.logrocket.com/streaming-video-in-safari/ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range https://pixabay.com/photos/giraffe-africa-tanzania-wild-1330814/ https://www.pexels.com/video/bright-autumn-colors-1583096/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Big Sur で PyOpenGL が使えなくなった時の解決策

現象 macOS Catalina までは問題なく動いていた PyOpenGLがimport できなくなった。 from OpenGL.GL import * from OpenGL.GLUT import * 以下のようなエラーが出る File "/Users/******/lib/python2.7/site-packages/OpenGL/platform/darwin.py", line 41, in GL raise ImportError("Unable to load OpenGL library", *err.args) ImportError: ('Unable to load OpenGL library', 'dlopen(OpenGL, 10): image not found', 'OpenGL', None) 理由 Big Sur 以降、OpenGL が deprecated されているため。 解決策 エラーが出ているファイルの近くにある ****/lib/python2.7/site-packages/OpenGL/platform/ctypesloader.py を修正する。 fullName = util.find_library( name ) ↓ fullName = "/System/Library/Frameworks/{}.framework/{}".format(name,name) いつかは使えなくなるのかな...残して欲しい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む