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

ABC192 E Train ~Dijkstraの実装でハマった話~

はじめに  本記事は競技プログラミングにおいて解くのに困った問題について、なぜその解法に思い至るのか、どのような所でハマったか等のポイントをメモした、筆者の筆者による筆者のための備忘録となっております。読まれることを意識していないので文章がとっ散らかってます。 問題 問題はこちら。 概略  $N$個の頂点と$M$本の辺からなる無向グラフがある。辺$i$は頂点$A_i, B_i$を結んでおり、この間の移動には$T_i$の時間を要する。ただし時刻が$K_i$の倍数である時のみこの辺の移動を開始できる。  頂点$X$を時刻0に出発して頂点$Y$に到達するまでの最短時間を求めよ。  提出解答 提出解答はこちら。 ABC192E.py from heapq import heappush, heappop, heapify n, m, x, y = map(int, input().split()) path = [[] for _ in range(n)] for _ in range(m): a, b, t, k = map(int, input().split()) path[a-1].append((b-1, t, k)) path[b-1].append((a-1, t, k)) s, g= x-1, y-1 INF = 2*10**14+1 def dijkstra(s, n): hq = [(0, s)] heapify(hq) d = [INF]*n d[s] = 0 decided = [0]*n while hq: l, v = heappop(hq) if decided[v]: continue decided[v] = 1 for nv, t, k in path[v]: if decided[nv]: continue d[nv] = min(d[nv], (d[v] + k-1)//k*k + t) heappush(hq, (d[nv], nv)) return d d = dijkstra(s, n) if d[g] == INF: print(-1) else: print(d[g]) Dijkstra法って?  Dijkstra(ダイクストラ)法とは、グラフ中のある1点から他の全ての頂点までの最短経路を求めるアルゴリズムです。使える条件は、各辺の重みが非負である時です。重みが負の時はベルマンフォード法というものがあるらしいですが良く勉強していないので口を閉じます。  さて、手順は以下の通りです。 $d[v]$をすべてINFで初期化し、始点$s$について$d[s]=0$とする。 まだ距離が確定していない頂点の内、$d[v]$が最小である$v$をとり、$d[v]$を確定する。 $v$から到達できる点であり、距離が確定していない点$nv$について、$d[nv] = min(d[nv], d[v]+|e(v, nv)|)$と更新する。 4.距離が未確定の頂点が残っているならば2に戻る。  上記のようにして作った$d[v]$が$s\to v$の最短距離となります。ちなみに$d[nv]$の更新の際に、「$nv$の前は$v$にいた」という情報を保存することで、最短距離のみならず具体的な最短経路も復元することができるらしいです(今回はしていません)。  計算量を見積もりましょう。2~4を1回しすると必ず頂点が1つずつ確定していくため、2~4は$|V|$回繰り返されます。ここで2を行う際、線形探索を行うならばこれは$|V|$回かかるため、全体の計算量は$O(|V|^2)$となります。一方で未確定の頂点を優先度付きキュー(プライオリティキュー、ヒープキューとも言う)で管理することで、全体の計算量を$O(|E|\log{|V|})$にすることが可能なようです(計算量の導出の理解はとりあえず諦めました)。頂点と辺の個数によって2つの手法を使い分けると良いらしいです(グラフが相当密、すなわち$|E|\simeq |V|^2$でない限りは後者が良い気がします)。  それでは次項より、実装で主にハマった2つの点について話していきます。 確定した頂点を管理する配列でハマった  私は"頂点$v$の最短経路が確定しているか否か"を配列$decided[v]$で管理しました。当然$decided[v] = 1$なる頂点$v$についての処理はcontinueでスキップするわけですが、2において$v$をヒープキューから取り出した際にはその処理を行いませんでした。冒頭に載せた提出解答のコメントアウトしている部分です。  一応根拠はあります。ヒープキューは最短経路が未確定の頂点($d[v]$=INFなる頂点、すなわち未更新の頂点は除く)を格納しているキューでした。故にそのキューに含まれる頂点は全て$decided[v]=0$なのではないかと考えていました。  しかしここで失念していた状況があります。それは、1つの頂点$v$が未確定の状態で複数の経路からヒープキューに格納されるような場合です。そのような場合、頂点$v$を取り出してきて距離を確定、すなわち$decided[v]=1$とすると、ヒープキュー内に$decided[v]=1$となっている頂点が含まれてしまうことになります。このような頂点を何度も取り出してきてしまってはおかしくなっています。  これに気づかずTLE,WAを連発して首を傾げまくって首が痛くなりました。養生します。 INFの値の設定でハマった  INFの設定を問題ごとにしっかり考えることはとても大切なことです。そこをかなり適当にやってしまい、WAを連発してこれまた首を傾げていました。今回のINFの値として適切な数字を見積もっていきましょう。  考えるべきは、頂点$X$から他の頂点$v$まで最大でどれほど時間がかかるか、です。まずグラフの形状は道(頂点が一直線上に並んでいるようなグラフ)であるとし、その両端に$X, v$が位置する時が一番時間がかかるでしょう。  次に移動にかかる時間について考えましょう。各頂点の間を移動するのには$T_i$だけ時間がかかります。今回は移動時間に加え、待ち時間も考えなければなりません。時刻が$K_i$の倍数になった時に移動できるわけですが、$K_i$の倍数は自然数の上で$K_i$ごとに現れるので、待ち時間は最長で$K_i-1$となります。よってある2頂点間を移動するのにかかる時間は最長で$T_i+K_i$程度となります。  $X\to v$の移動中に辺は$N-1$本あるため、この行動が$N-1$回行われ、全部でだいたい$\sum_i(T_i+K_i)$程度の時間がかかります。  さて、この値はどれくらいの数字で抑えられるでしょうか。制約に注意してみると、 $$\sum_i(T_i+K_i) \leq \sum_i(10^5+10^5)$$ $$=2\times10^5\times N \leq 2\times10^{14}$$ となります。よってINFの値としては$2\times 10^{14}$よりも大きい値を設定すれば問題ないでしょう。実際冒頭の提出解答ではINF$=2\times10^{14}+1$としています。試しこれよりもINFの値を小さくしてみた解答がこちらです。ちなみに$2\times 10^{14}-1$程度ではまだ大丈夫でした。  これで無事にACすることができました。 反省 人生で初めてDijkstraを使った。授業でアルゴリズムを聞きかじった程度だったのでいい経験になった。 ACするまでに数日だらだらと時間をかけてしまった。他の用事があったのもあるが、ちょっとずつ早くインプットできるようにしたい。 ヒープキューにタプルを入れるとき、どうやらノードの大小関係はタプルの第0成分で判定されるっぽい。なので(経路長, 頂点名)の順にしないと落ちそう。 首を労わりたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Pythonで量子力学】水素原子の電子模型のWeb授業ができるツールを作る

概要  つい先日、量子力学の輪読会がWebであって、そこで水素原子の電子軌道モデルを簡単に勉強できるツールを作りました。  ぴったり1ページに収まるので、量子力学以外の題材でも、学校の授業とかに使える感じです。 量子力学ゼミナール動画 ※水素原子の電子軌道については、次の講義にアップされると思います 実行結果 想定している教科書 量子力学、量子化学を学んでいる方なら、おそらく認知度が高い教科書です。 小出昭一郎, "量子力学(I)", 裳華房(1990改訂版), pp98-103 ファインマン他、砂川重信訳、"ファインマン物理学V 量子力学", 岩波書店(1978) 画面 趣旨  私が大学の時に、量子化学や量子力学を勉強していたが、1原子系のモデルであっても軌道の形状が計算では直感的に分かりにくいものがあったので、作成した。  私の中では「電子分布$φ_{nlm}$」が結構面白くて、これはmatplotlibのquiverを使ってベクトル場を表現しているのだが・・・。  実は電子の分布が多ければ多いほどベクトル場の長さを微妙に長くしているので、こんなにはっきりと電子軌道を映せることで直感的なイメージが掴めたら・・・とか、量子化学に興味を持つ人が増えたらな・・・と思い作成しました。 実行環境 Mac OS Catalina 10.15.7 Python 3.7 Matplotlib 3.4.1 numpy 1.20.2 githubソースコード 汚くて、コメント少ないのはご容赦ください. ただ、構造的には分かりやすい中身になっているはずなので、読めると思います。 修正・改善のご要望があれば、コメントをつけるなどして対応できますので、ご連絡ください。 自分で見たい方のために、以下にソースコードをちょっと概説したいと思います プログラム Selector.py : ボタンを選択、ボタンイベント関連。実行時のメインプログラム Function.py : 2d plot, 3d plot Selector.py コードの8割くらいが、GUIの表示とボタンイベントの登録処理ですw 動径関数(球ベッセル関数) def R_1s(self, r): return 2 * np.exp(-r) 球面調和関数 def Y_0_0(self, theta, phi): return 1 / np.sqrt(4 * np.pi) 計算の仕方 動径関数は$ r^2 \times R_{xx}(r)^2 $、 球面調和関数は$ {\rm Re} [Y_n^l(\theta, \phi)] $ 電子軌道は$ |r^2 \times R_{xx}(r)^2 \times Y_n^l(\theta, \phi)|^2 $ を使って計算しています。 ボタンイベント tk_btn = MarkerButton(self.parent_panel) tk_btn["text"] = "s軌道(l0,m0)" tk_btn["command"] = partial(self.press_for_graph, tk_btn, GraphView('3d', lambda theta, phi: np.abs( self.Y_0_0(theta, phi) ))) tk_btn.grid(row=cnt, column=2, sticky=tk.W+tk.E+tk.N+tk.S) tk_btn.configure(font=my_font) ボタンイベントの中身はself.press_for_graph def press_for_graph(self, sender, graph_func, graph_object=None): self.select(sender) if self._graph_object == None: self._graph_object = SphericalSurface(self.master) self._graph_object.plot(graph_func, graph_object) plotの中身はFunction.pyの下の方に class SphericalSurface(ttk.Frame): def __init__(self, root): super().__init__(root) self.master = root self._figure = plt.figure(figsize=(10,6)) self._canvas = FigureCanvasTkAgg(self._figure, master=root) self._canvas.draw() self._canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1) self._toolbar = NavigationToolbarExt(self._canvas, self, self._figure) self._axes_now = None self.init_plot() def init_plot(self): plt.clf() self._figure.clf() ax = plt.subplot(111) def plot(self, graph_func, fig_rule): plt.clf() self._axes_now = self._figure.add_subplot(111, projection='3d', title='Spherical Surface Function', xlabel='X', ylabel='Y', zlabel='Z') self._figure.add_axes(self._axes_now) graph_func.plot(self._axes_now, self._figure) 色々なグラフを条件で分けて表示 クラスGraphViewとして次のように書いてあります。 class GraphView: def __init__(self, datatype, func, fig_rule=None): self._datatype = datatype self._func = func self._fig_rule = fig_rule def plot(self, axes, figure): if self._datatype == '2d': self.plot2d(axes, figure) elif self._datatype == '3d': self.plot3d(axes, figure) elif self._datatype == '3d_field': self.plot3d_field(axes, figure) GraphViewというオブジェクトを生成して、それを渡すような形をとっています。 datatypeの中身によって、描かれるグラフが変わって、'2d', '3d', '3d_field'を選択して、funcの中に実際に描く関数を、fig_ruleに描画する時に特別な処理(z軸View, x軸Viewに移動など)を入れる場合に使用します。 フルスクリーン表示、画面表示 root = tk.Tk() root.title("Spherical Surface Demonstration") root.attributes('-fullscreen', True) app = Application(master=root) app.mainloop() 画面の中にmatplotlibを入れる FigureCanvasTkAggを使用して実装します。 self._canvas = FigureCanvasTkAgg(self._figure, master=root) self._canvas.draw() self._canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

無料利用可能なAPIで住所から緯度経度情報を得る

はじめに 本記事は以下の取り組みの一環です。 やりたいこと 住所を示すテキストデータから緯度、経度を得たい。それを使ってfoliumで可視化したい。(が、foliumについて深追いするのはまた今度) 手順 住所→緯度経度とするには住所の表記揺れも気になるし、そもそも1対1対応していないので、住所から代表点を得られるのかどうかも心配です。 決して網羅的な調査はしていないものの、住所から郵便番号を経由したら上手くいきそうな勘が働き、結果的にはうまくいきました。1パスの方法もあるかもしれません。 番地情報を落として、町域単位(市区町村の次の粒度のやつ)にする。 住所から郵便番号をGet by zipcoda.net 郵便番号から代表点の緯度経度をGet by HeartRailsGeoAPI 説明を読み返すとHeartRails1つで行けたような気もしますが、お好みで。 実践 1. 町域単位にする import re address='東京都千代田区永田町1丁目7−1'#ちなみに国会議事堂です address=re.sub("[1-9].*","", address) print(address) 東京都千代田区永田町 これでもよいのですが、実際にはpandas.DataFrameのcolumnに入った大量のデータを処理するので、こんな感じになります。数字が半角全角混在していることを想定した処理です。 df['address']=df['address'].str.replace("[1-9].*","").str.replace("[1-9].*","") 2. zipcoda.net import re import requests import json address='東京都千代田区永田町1丁目7−1'#ちなみに国会議事堂です address=re.sub("[1-9].*","", address) url='http://zipcoda.net/api?address='+address r = requests.get(url) print(r.json()) { 'status': 200, 'length': 45, 'items': [ {'zipcode': '1000014', 'pref': '東京都', 'state_name': '東京都', 'components': [ '東京都', '千代田区', '永田町'], 'address': '千代田区永田町' }, {'zipcode': '1006101', 'pref': '東京都', 'state_name': '東京都', 'components': [ '東京都', '千代田区', '永田町山王パークタワー1階'], 'address': '千代田区永田町山王パークタワー1階' } (後略) } 1フロア毎に郵便番号を持っている山王パークタワーはさておき、このように辞書形式でデータが得られます。入れ子構造になっていて一目で把握するのはちょっと大変ですが、知りたいのは'items'の1つ目の'zipcode'。 print(r.json()['items'][0]['zipcode']) 1000014 3. HeartRailsGeoAPI 多機能なAPIですが、SerchByPostalというのを使います。 url = 'http://geoapi.heartrails.com/api/json?method=searchByPostal&postal=' postal=str(1000014)#先ほど上で取得した郵便番号(int→strに型変換しておく) res_dict = requests.get(url+postal).json()['response']['location'][0] print(res_dict['x'],res_dict['y']) 139.745749 35.676328 なお、1丁目で代表する考えです。さらっと流しましたが、これも、欲しい経度緯度を取得するにはrequests.get(url+posatal).json()で得られるdict形式のデータの構造をよく眺めないといけません。notebook環境でprintとしても1行で返ってくるので何かしらのエディタにコピペして把握しました。 可視化 なぜ緯度経度を取得したのかといえば、地図上にマークしたいからです。foliumも奥が深そうなので、もう少し勉強してまた別の機会に詳しく紹介したいです。 import re import requests import json import folium address='東京都千代田区永田町1丁目7−1' address=re.sub("[1-9].*","", address) url1='http://zipcoda.net/api?address='+address postal=str(requests.get(url1).json()['items'][0]['zipcode']) url2 = 'http://geoapi.heartrails.com/api/json?method=searchByPostal&postal=' res_dict = requests.get(url2+postal).json()['response']['location'][0] x=float(res_dict['x']) y=float(res_dict['y']) map=folium.Map(location=[y, x], width=800, height=800, zoom_start=16, control_scale=True, tiles='OpenStreetMap') folium.Marker(location=[y, x]).add_to(map) map
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoアプリ上でFessを使って検索窓を設置する

はじめに Djangoでサイトを作っていた際に、検索機能を手軽に設置したいなぁと思い、 OSSの検索エンジン Fess を使って実現したので方法を共有します。 Fessについて知りたい方は こちら をご参照ください。 想定読者 Djangoでのアプリケーション開発手法を理解している Docker / docker-compose の最低限の使用法について理解している Nginxの最低限の設定方法について理解している 構成 docker-composeにて以下コンテナを起動します。 nginxコンテナ djangoコンテナ postgresqlコンテナ fessコンテナ elasticsearchコンテナ 以下は docker-compose.yml の例です。 なお、fessとelasticsearchの設定についてはこちらのGithubを参考にさせていただきました。 また、ディレクトリ構成などは各開発状況に依存します。 docker-compose.yml version: '3.7' services: nginx: image: nginx:1.19.3-alpine ports: - "80:8000" volumes: - ./nginx/conf:/etc/nginx/conf.d - ./nginx/uwsgi_params:/etc/nginx/uwsgi_params - ./nginx/log:/var/log/nginx - ./app/static:/static depends_on: - django django: build: ./app command: uwsgi --socket :8001 --module app.wsgi --py-autoreload 1 volumes: - ./app/:/usr/src/app/ ports: - 8001:8001 depends_on: - postgres - fess postgres: image: postgres:12.4-alpine volumes: - ./postgres/data:/var/lib/postgresql/data fess: image: ghcr.io/codelibs/fess:13.12.0 environment: - "ES_HTTP_URL=http://es:9200" - "FESS_DICTIONARY_PATH=/usr/share/elasticsearch/config/dictionary/" ports: - "8080:8080" depends_on: - es es: image: ghcr.io/codelibs/fess-elasticsearch:7.12.0 environment: - node.name=es01 - discovery.seed_hosts=es01 - cluster.initial_master_nodes=es01 - cluster.name=fess-es - bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms1g -Xmx1g" - "FESS_DICTIONARY_PATH=/usr/share/elasticsearch/config/dictionary" ulimits: memlock: soft: -1 hard: -1 nofile: soft: 65535 hard: 65535 volumes: - ./elasticsearch/data:/usr/share/elasticsearch/data - ./elasticsearch/dictionary:/usr/share/elasticsearch/config/dictionary ports: - 9200:9200 ただし、これで docker-compose up すると 「elasticsearch が立ち上がってないから fess が落ちたよ!」 と言われることがあります。 depends_onで指定はしていますが、内部の起動タイミングが合わないことがあるようです。 先にelasticsearchだけ立ち上げておけばよいのですが、よい解決法がありましたら教えてください。 Djangoのテンプレートへの検索窓追加 こちらのサンプルに従って検索窓をtemplateに配置します。 以下はサイトのURLが https://yoursite.co.jp の場合の例です。 <form id="searchForm" method="get" action="https://yoursite.co.jp/search/"> <input required pattern=".*\S+.*" id="query" type="text" name="q" maxlength="1000" autocomplete="off"> <input type="submit" name="search" value="検索"> </form> サンプルとの差分としては、required pattern=".*\S+.*" の部分です。 これがないと空、またはスペースのみの状態で検索ボタンが押せるのですが、 空で検索するとなぜか私の環境の場合はエラー画面となってしまったので追加しています。 Nginxの設定ファイル修正 フロントからのリクエストパスに応じて振り分けていきます。 以下の例では、/static, /admin/ などのDjangoのパスをまず設定し、 Fess関連のパスをfessコンテナに転送しています。 (とりあえず見つけた範囲を設定していますが他にもあるかもしれません) 最後にそれ以外のパスをdjangoコンテナに転送しています。 (Fess関連のパスがDjangoのパスと重複しないようにする必要があります) nginx.conf upstream django { ip_hash; server django:8001; } server { listen 8000; server_name 127.0.0.1; charset utf-8; location /static { alias /static; } location /admin/ { deny all; } location /search/ { proxy_pass http://fess:8080/search/; } location /css/ { proxy_pass http://fess:8080/css/; } location /js/ { proxy_pass http://fess:8080/js/; } location /images/ { proxy_pass http://fess:8080/images/; } location /go/ { proxy_pass http://fess:8080/go/; } location /cache/ { proxy_pass http://fess:8080/cache/; } location / { uwsgi_pass django; include /etc/nginx/uwsgi_params; } } server_tokens off; Fessの設定 ブラウザで http://[IP address]:8080 にアクセスするとFessの画面が表示されます。 ログインすると管理画面が表示されますので、クロールの設定やスケジューラの設定をしてください。 詳細はFess公式サイトに記載されていますので、自分がハマったところだけ記載しておきます。 (以下はすごく素人的なハマりだと思いますので、熟練者の方は生暖かく見守りください) Fessからのクロール先のURLはlocalhostでは機能しません。 なぜなら、fessコンテナから見たlocalhostは、通常fessコンテナ自身を表すからです。 また、クロール先を http://django:8001 や http://nginx:8000 にすると一見インデックスが作成されているように見えます。 しかし、検索結果のリンクURLも上記アドレスとなってしまうため、検索結果からサイトにアクセスできません。 こちらコメントにて「パスマッピング」の設定をすればよいとのご指摘をいただきました! なので、クロール先は https://yoursite.co.jp にしましょう。 この場合、クロールのリクエストは一旦外に出ることになります。 マシンのFW等でIP制限をしている場合は、マシン自体のグローバルIPを指定しないとはじかれてしまうので注意が必要です。 その他はまりどころ 以下の設定(Ubuntu 20.04の例)をしないとelasticsearchコンテナが落ちます。 $ sudo sysctl -w vm.max_map_count=262144
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DjangoとVue.jsを使ってモーダルコメントフォームを実装する

はじめに JavaScriptはほとんど触ったことがないのですが、最近はVue.jsを勉強しています。 ブログを想定したDjangoアプリ内で、モーダルコメントフォームの実装ができましたので、記事を書いていきます。 こういう感じの動きです。 記事に対するコメントと、コメントに対する返信の投稿をモーダルフォームで作っていきます。 なおモーダルと言えばBootStrapで実装することも多いと思うのですが、今回は自力での実装に挑戦してみました。 Django側のアプリ構築 まずは下記のようなモデルを用意します。 # project/models.py from django.db import models from django.conf import settings from django.utils import timezone class Post(models.Model): title = models.CharField('タイトル', max_length=255) text = models.TextField('本文') writer = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.PROTECT, verbose_name='投稿者') created_at = models.DateTimeField('作成日', auto_now_add=True) updated_at = models.DateTimeField('更新日', auto_now=True) def __str__(self): return self.title class Comment(models.Model): """記事に紐づくコメント""" name = models.CharField('名前', max_length=255, default='名無し') text = models.TextField('本文') target = models.ForeignKey( Post, on_delete=models.PROTECT, verbose_name='記事に対するコメント') created_at = models.DateTimeField('作成日', default=timezone.now) def __str__(self): return self.text[:20] class Reply(models.Model): """コメントに紐づく返信""" name = models.CharField('名前', max_length=255, default='名無し') text = models.TextField('本文') target = models.ForeignKey(Comment, on_delete=models.CASCADE, verbose_name='コメントに対する返信') created_at = models.DateTimeField('作成日', default=timezone.now) def __str__(self): return self.text[:20] 記事に対する投稿のCommentとコメントに対する返信のReplyをここで定義しました。 コメントやリプライは独立したmodelを定義して、models.ForeignKeyで親に結びつけるようにすると、 後に取り出す際に簡単になります。 次にURLパターンを定義します。 # app/urls.py from django.urls import path from . import views app_name = 'app' urlpatterns = [ path('post_detail/<int:pk>', views.PostDetail.as_view(), name='post_detail'), path('comment_create/<int:pk>', views.CommentCreate.as_view(), name='comment_create'), path('reply_create/<int:pk>', views.ReplyCreate.as_view(), name='reply_create'), ] 通常は一覧画面を用意すると思いますが、モーダルコメントの動きの部分だけ実装したいので、上記のような内容となりました。 今回は記事ページからコメントやリプライを投稿するので、コメントやリプライの画面は作成しませんが、Postをする先を指定する必要があるので、定義が必要です。 viewを作成する前にフォームを作っておきます。 #app/forms.py from django import forms from .models import Comment, Reply class CommentCreateForm(forms.ModelForm): """コメント投稿フォーム""" class Meta: model = Comment exclude = ('target', 'created_at') class ReplyCreateForm(forms.ModelForm): """返信コメント投稿フォーム""" class Meta: model = Reply exclude = ('target', 'created_at') 名前と投稿だけの簡易的なものです。 viewを定義していきます。 # app/views.py from django.shortcuts import redirect, get_object_or_404 from django.views import generic from .models import Post, Comment, Reply from .forms import CommentCreateForm, ReplyCreateForm class PostDetail(generic.DetailView): template_name = 'app/post_detail.html' model = Post class CommentCreate(generic.CreateView): """記事へのコメント作成ビュー。""" model = Comment form_class = CommentCreateForm def form_valid(self, form): post_pk = self.kwargs['pk'] post = get_object_or_404(Post, pk=post_pk) comment = form.save(commit=False) comment.target = post comment.save() return redirect('blog:post_detail', pk=post_pk) class ReplyCreate(generic.CreateView): """コメントへの返信作成ビュー。""" model = Reply form_class = ReplyCreateForm def form_valid(self, form): comment_pk = self.kwargs['pk'] comment = get_object_or_404(Comment, pk=comment_pk) reply = form.save(commit=False) reply.target = comment reply.save() return redirect('blog:post_detail', pk=comment.target.pk) コメント作成とリプライ作成はgeneric.CreateViewで作ります。画面は必要ないので、テンプレートは定義しません。 htmlの記述 html部分を記述していきます。 <!-- app/templates/app/post_detail.html --> {% load static %} <html lang="ja"> <head> <meta charset="utf-8"> <title>modal comment</title> <link rel="stylesheet" href="{% static "css/style.css" %}"> <!-- vueの読み込み 本番環境では ...global.prod.jsとする --> <script src="https://cdn.jsdelivr.net/npm/vue@3.0.0/dist/vue.global.js"></script> </head> <body> <div class="container"> <h1 class="post-title">{{ post.title }}</h1> <div class="post-text"> {{ post.text | linebreaks }} </div> <!-- vueのマウント開始 --> <div id="comment-vue"> <p class="text-link" v-on:click="openModal('comment',{{ post.pk }})">記事にコメントする</p> <!-- コメント一覧 --> {% for comment in post.comment_set.all %} <div class="comment"> <h3 class="comment-writer">{{ comment.name }}</h3> {{ comment.text | linebreaks}} </div> <p class="text-link" v-on:click="openModal('reply', {{ comment.pk }})"> コメントに返信する </p> <!-- リプライ一覧 --> {% for reply in comment.reply_set.all %} <div class="reply"> <h3>{{ reply.name }}</h3> {{ reply.text | linebreaks }} </div> {% endfor %} <!-- リプライ一覧終わり --> {% endfor %} <!-- コメント一覧終わり --> <!-- modalテンプレート --> <modal-template v-show="isVisible" v-on:close="closeModal" v-bind:actionurl="actionUrl"></modal-template> </div> <!-- vueのマウント終わり --> </div> <script type="text/x-template" id="modal-template"> <transition tag='div' name="modal"> <div class="modal-container"> <!-- モーダル外をクリックしたら閉じる --> <div class="modal-overlay" v-on:click.self="$emit('close')"> <div class="modal-body"> <!-- 親コンポーネントからaction属性に割り当てるurlを受け取る --> <form v-bind:action='actionurl' method="POST" id="comment-form"> <div class="field"> <p>名前</p> <input type="text" name="name" value="名無し" maxlength="255" required="true" id="id_name"> </div> <div class="field"> <p>本文</p> <textarea name="text" cols="40" rows="10" required="true" id="id_text"></textarea> </div> {% csrf_token %} <button type="submit" class="button">送信</button> </form> <!-- closeボタンを押したら閉じる --> <button class="button close-button" v-on:click="$emit('close')">Close</button> </div> </div> </div> </transition> </script> <script src="{% static "js/comment-vue.js" %}"></script> </body> </html> 記事(Post)に紐づくコメントの一覧はmodels.pyで定義した際にForeignKeyで紐づけていたので… {% for comment in post.comment_set.all %} とすると取り出すことができます。コメントに紐づくリプライも同様に取り出せます。 今回はコメントとリプライで処理をわけたいので、モーダル部分をコンポーネント化して、クリックされた箇所に応じて、処理を分岐させるようにします。 同じ画面は使うけど、投稿文を転送する先が分かれる、というイメージです。 <p class="text-link" v-on:click="openModal('comment',{{ post.pk }})">記事にコメントする</p> ここの部分で、ポストに対するコメントの際はJavaScriptサイドに"comment"という文字列と、Postのpkを渡すようにしています。 リプライのほうは <p class="text-link" v-on:click="openModal('reply', {{ comment.pk }})"> コメントに返信する </p> として、"reply"という文字列とコメントのpkを渡します。 JavaScript内で上記の受け取った情報からモーダル内に配置されたフォームのaction属性を割り当てて、投稿の処理を完了させるような流れです。 最低限の見た目を整えるためにcssも書いておきます。 /* static/css/style.css */ @charset "UTF-8"; /* 共通設定 */ html { font-size: 62.5%; } body { font-family: "Yu Gothic Medium", "游ゴシック Medium", YuGothic, "游ゴシック体". "ヒラギノ角ゴ", "ヒラギノ角ゴ Pro W3", sans-serif; background-color: #FAFAFA; color: #333; } /* コンテナ */ .container { max-width: 660px; margin: 50px auto; } /* 記事部分 */ .post-title { font-size: 2.4rem; font-weight: bold; border-bottom: 1px solid #ccc; } .post-text { font-size: 1.4rem; line-height: 1.7; border-bottom: 1px solid #ccc; } .comment { font-size: 1.2rem; margin-left: 10px; border-bottom: 1px dotted #ccc; line-height: 1.7; } .reply { font-size: 1.2rem; margin-left: 30px; border-bottom: 1px solid #ccc; line-height: 1.7; } /* コメントと返信の文字は青文字に */ .text-link { font-size: 1.4rem; color: #00809d; cursor: pointer; } .text-link:hover { color: #00809d; opacity: 0.5; } /* フォーム内のウィジェット */ .label { display: block; margin-bottom: 30px; } input[type=text], textarea { font-size: 1.2rem; padding: 4px 8px; box-sizing: border-box; border-radius: 4px; border: none; background-color: rgba(136, 136, 136, .3); outline: none; } input[type=text]:focus, textarea:focus { box-shadow: 0 0 8px rgba(130, 170, 170, .5); } .button { margin-top: 30px; display: block; width: 100px; padding: 5px; border-radius: 4px; background-color: #82aaaa; color: #fff; letter-spacing: 1px; font-size: 1.2rem; border: none; cursor: pointer; } .close-button { background-color: #888888; color: #fff; } .button:hover, .close-button:hover { opacity: 0.7; } /* モーダル構成要素 */ .modal-container { position: relative; z-index: 9998; width: 100%; height: 100%; } .modal-body { position: relative; z-index: 9999; box-sizing: border-box; height: 80%; width: 80%; max-width: 640px; padding: 16px; margin: auto; background-color: #fff; } .modal-overlay { position: fixed; top: 0; left: 0; display: flex; overflow: auto; width: 100%; height: 100%; padding: 20px 60px; background-color: rgba(0, 0, 0, 0.7); } /* モーダル Transition */ .modal-enter-from, .modal-leave-to { opacity: 0; } .modal-enter-to, .modal-leave-from { opacity: 1; } .modal-enter-active, .modal-leave-active { transition: opacity .7s ease; } JavaScript Vue処理のjsの記述です。 // static/js/comment-vue.js const commentModalForm = { // ③template内で使用するため、action属性に割り当てるurl文字列を受け取る props: { actionurl: { type: String, default: '', }, }, template: "#modal-template", } Vue.createApp({ data: function() { return { isVisible: false, formActionPk: '', commentType: '', } }, methods: { // ①modalオープンイベント時に変数を受け取る openModal: function(strCommentType, pk) { this.isVisible = true this.formActionPk = pk this.commentType = strCommentType }, closeModal: function() { this.isVisible = false }, }, computed: { // ②受け取った変数から、form内のaction属性に割り当てるurl文字列を生成 actionUrl: function() { if (this.commentType === 'comment') { return '/comment_create/' + this.formActionPk } else { return '/reply_create/' + this.formActionPk } } }, components: { 'modal-template': commentModalForm, } }).mount("#comment-vue") 全体的な処理の流れとしては、、、 1. ユーザーがページで、記事に対するコメントか、コメントに対する返信をクリックする 2. comment-vue.jsのopenModalメソッドが呼び出される。その際に受け取った情報から、処理を分岐させて、urlを生成 3. モーダル内のフォームは子コンポーネントに定義をしているので、url文字列を受け取る。 4. ユーザーが送信ボタンを押すと、データが受け取ったurlに送信される。 5. 投稿完了 という風な感じです。 画面遷移をしないので、ちょっとかっこよさが増しますね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データサイエンティストになるべく明日からの勉強 記録①

5月目標 サンプルコードを見ながら、機械学習を学ぶ python 機械学習プログラミングを読む 6月目標 Kaggleのタイタニックの生存者判定をする Kaggle で勝つデータ分析の技術を読む https://web.stanford.edu/~hastie/ElemStatLearn/printings/ESLII_print12.pdf これを読み進める 7月以降未定
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flask-WTFでWebフォームを作る

Flask-WTFはPythonでWebフォームとそのバリデーションを簡単に実装できるフレームワークWTFormsのFlask版です。 まずはインストール pip install flask-wtf 次に秘密鍵を設定をします。 Flaskで作成するアプリの設定はFlaskクラスのインスタンス(ここではapp)の.configに設定します。 app.config['SECRET KEY'] = 'hard to guess string' 今回はソースコードにベタ打ちしてますが、商用のアプリケーションなどでは環境変数から取得したり、端末ごとに変えたりする必要があります。 Flask-WTFではこの秘密鍵をCSRF攻撃などへの対策として利用します。 CSRF攻撃はざっくりとログイン状態にある(セッションにログイン情報を保持している)ユーザーが悪意のあるサイトにアクセスすることで、不正なリクエストがフォーム経由などで送信されるものです。 アカウント乗っ取りやなりすまし投稿などが被害例としてあります。 Flask-WTFでは正しいフォームからのリクエストなのかを確認するためにセキュリティトークンを発行し、ユーザーセッションの中にその情報を格納しますが、その際の暗号化に秘密鍵が使用されます。 では実際にフォームを作ります。 Flask-WTFではフォームをFlaskFormクラスを継承したクラスとして定義し、各項目をクラス変数として定義します。 なお各項目は入力されるデータ形式(string型など)ごとにwtformsが用意するFieldクラスのインスタンスとして作成されます。 ※例えばStringField型の場合はhtmlでいうところのinput要素でtype="text"を指定した状態になります。 from flask_wtf import FlaskForm from wtforms import StringFields, SubmitField from wtforms.validators import DataRequired class NameForm(FlaskForm): name = StringField('What is your name?', validators = [DataRequired()]) submit = SubmitField('submit') Field型のオブジェクトは最初の引数に項目名(htmlでいうところのlabelに対応)をとることができます。 また上記の例ではバリデーターとしてDataRequiredを指定しています。これによってこの項目が入力されていない場合、フォームの送信がさせないようにすることができます。 あとはこのクラスを利用して、view関数経由でテンプレートをレンダリングする際にフォームオブジェクトを変数として渡してあげればOKです。 @app.route('/', methods = ['GET', 'POST'] def index(): name = None form = NameForm() if form.validate_on_submit(): name = form.name.data form.name.data = '' return render_template('index.html', form = form, name = name) 変数nameにNoneを代入することで初期化し、NameFormクラスのインスタンスとして変数formを作ります。 NameFormクラスの(厳密にはFlaskFormクラスの)メソッドvalidate_on_submit()で先ほど設定してバリデーションを行います。 正の場合にはform.name.dataで入力されたデータを取得して変数nameに格納し、フォームのデータ自体は空文字でクリアします。 そうすることで再度フォームを読み込んだ際に入力されたデータをリセットすることができます。 テンプレートは下記です。 <div class="page-header"> <h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1> </div> <form method="POST"> {{ form.hidden_tag() }} {{ form.name.label }} {{ form.name }} {{ form.submit }} </form> 変数nameがNoneではない場合はその名前を表示し、Noneである場合にはStrangerを表示します。 フォーム自体はhtmlのform要素で作成し、中身をview関数から受け取ったformの各変数として書いてあげることでフォームを作ることができます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

大学に入って読んでよかった情報系の本

はじめに インターネットには日々様々な情報が投稿され、紙媒体のほんの市場は日々縮小する一方。プログラミング関係の情報なんてまさにネットで無料に拾える情報の代表ですがそんな中で生き残っている本はすごくわかりやすく書かれていてお金を払う価値は十分あります。今回は私を難しい大学の課題から助けてくれた本たちを紹介します。 【その1】斎藤康毅(2016)『ゼロから作るDeep Learning』オライリー・ジャパン  理論は微分の考え方から、実装はPythonの環境構築から。まさにゼロからDeep Learningを教えてくれる教科書です。実際に手を動かしてみて手書き数字データセット「MNIST」で95%以上の精度が出た時は感動しました。さらに、CIFAR-10などのカラー画像や畳み込みの処理も記してあって本格的です。到達度は高いと思うのですが、少しずつ実装していくので詰まったら簡単に戻れるので挫折の心配はないでしょう。見やすいレイアウトも最高です。  2巻ではリカレントニューラルネットワークの解説もあるのでさらにお世話になりそうです。Pythonのコードがなかなか理解できない方にはAtCoderで少し練習してから読むのもおすすめです。 【その2】掌田津耶乃(2020)『Python Django3 超入門』秀和システム 情報業界といえばAIとTwitter, Facebookなどのアプリケーションなどがまず思い浮かぶのではないでしょうか。私はそうでした。この本はDeep Learningの勉強でPythonを身につけた後に自分で物を作りたいという思いから購入したものでした。実際にその思いは尽きることなく、簡単なものですがDjangoで掲示板を作成し、サーバーを借りてドメインを取得し実際に公開することができました。(詳細は別の記事に書いてます) 【その3】Mana(2019)『1冊で全て身につくHTML&CSSとWebデザイン入門講座』SB Creative 自分でWebアプリケーションをつくりためのHTML,Webデザインの参考になりました。情報系の本には珍しく、とてもカラフルなデザインで読みやすいです。こちらも挫折の心配はいりません。 【その4】ミカエル・シプサ(2008)『計算理論の基礎』(太田和夫ほか訳)共立出版 NP完全問題は競技プログラミングなどでよく出くわしますが、それがどのような理論に基づいているのか知っている方は少ないのでは?この章では全てのNP問題がSATに多項式帰着することを示すクック-レビンの定理をはじめ、さまざまな問題が登場して非常に興味深いです。前提知識は意外と多くないので、計算理論が専門でなくても読めるようになっています。 最後に そのほかに面白かった本があれば追加していく予定です。マセマやJavaScriptの本とかも面白いのでいつか紹介したいです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

大学に入って読んでよかった情報系の本4選

はじめに インターネットには日々様々な情報が投稿され、紙媒体のほんの市場は日々縮小する一方。プログラミング関係の情報なんてまさにネットで無料に拾える情報の代表ですがそんな中で生き残っている本はすごくわかりやすく書かれていてお金を払う価値は十分あります。今回は私を難しい大学の課題から助けてくれ、さらにプログラミングの楽しさを教えてくれた本たちを紹介します。 【その1】斎藤康毅(2016)『ゼロから作るDeep Learning』オライリー・ジャパン 出版社HP  理論は微分の考え方から、実装はPythonの環境構築から。まさにゼロからDeep Learningを教えてくれる教科書です。実際に手を動かしてみて手書き数字データセット「MNIST」で95%以上の精度が出た時は感動しました。さらに、CIFAR-10などのカラー画像や畳み込みの処理も記してあって本格的です。到達度は高いと思うのですが、少しずつ実装していくので詰まったら簡単に戻れるので挫折の心配はないでしょう。見やすいレイアウトも最高です。  2巻ではリカレントニューラルネットワークの解説もあるのでさらにお世話になりそうです。Pythonのコードがなかなか理解できない方にはAtCoderで少し練習してから読むのもおすすめです。 【その2】掌田津耶乃(2020)『Python Django3 超入門』秀和システム 出版社HP 情報業界といえばAIとTwitter, Facebookなどのアプリケーションなどがまず思い浮かぶのではないでしょうか。私はそうでした。この本はDeep Learningの勉強でPythonを身につけた後に自分で物を作りたいという思いから購入したものでした。実際にその思いは尽きることなく、簡単なものですがDjangoで掲示板を作成し、サーバーを借りてドメインを取得し実際に公開することができました。(詳細は別の記事に書いてます) 【その3】Mana(2019)『1冊で全て身につくHTML&CSSとWebデザイン入門講座』SB Creative 出版社HP 自分でWebアプリケーションをつくりためのHTML,Webデザインの参考になりました。情報系の本には珍しく、とてもカラフルなデザインで読みやすいです。こちらも挫折の心配はいりません。 【その4】ミカエル・シプサ(2008)『計算理論の基礎』(太田和夫ほか訳)共立出版 出版社HP NP完全問題は競技プログラミングなどでよく出くわしますが、それがどのような理論に基づいているのか知っている方は少ないのでは?この章では全てのNP問題がSATに多項式帰着することを示すクック-レビンの定理をはじめ、さまざまな問題が登場して非常に興味深いです。前提知識は意外と多くないので、計算理論が専門でなくても読めるようになっています。 最後に そのほかに面白かった本があれば追加していく予定です。JavaScriptの練習本とかも面白いのでいつか紹介したいです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

大学に入って読んでよかった情報系の本4選!!!

はじめに  インターネットには日々様々な情報が投稿され、紙媒体の本の市場は日々縮小する一方。プログラミング関係の情報なんてまさにネットで無料に拾える情報の代表ですがそんな中で生き残っている本はすごくわかりやすく書かれていてお金を払う価値は十分あります。今回は私を難しい大学の課題から助けてくれ、さらにプログラミングの楽しさを教えてくれた本たちを紹介します。 【その1】斎藤康毅(2016)『ゼロから作るDeep Learning』オライリー・ジャパン 出版社HP  理論は微分の考え方から、実装はPythonの環境構築から。まさにゼロからDeep Learningを教えてくれる教科書です。実際に手を動かしてみて手書き数字データセット「MNIST」で95%以上の精度が出た時は感動しました。さらに、CIFAR-10などのカラー画像や畳み込みの処理も記してあって本格的です。到達度は高いと思うのですが、少しずつ実装していくので詰まったら簡単に戻れるので挫折の心配はないでしょう。見やすいレイアウトも最高です。  2巻ではリカレントニューラルネットワークの解説もあるのでさらにお世話になりそうです。Pythonのコードがなかなか理解できない方にはAtCoderで少し練習してから読むのもおすすめです。 【その2】掌田津耶乃(2020)『Python Django3 超入門』秀和システム 出版社HP  情報業界といえばAIとTwitter, Facebookなどのアプリケーションなどがまず思い浮かぶのではないでしょうか。私はそうでした。この本はDeep Learningの勉強でPythonを身につけた後に自分で物を作りたいという思いから購入したものでした。実際にその思いは尽きることなく、簡単なものですがDjangoで掲示板を作成し、サーバーを借りてドメインを取得し実際に公開することができました。(詳細は別の記事に書いてます) 【その3】Mana(2019)『1冊で全て身につくHTML&CSSとWebデザイン入門講座』SB Creative 出版社HP  自分でWebアプリケーションをつくるためのHTML,Webデザインの参考になりました。情報系の本には珍しく、とてもカラフルなデザインで読みやすいです。こちらも挫折の心配はいりません。 【その4】ミカエル・シプサ(2008)『計算理論の基礎』(太田和夫ほか訳)共立出版 出版社HP  NP完全問題は競技プログラミングなどでよく出くわしますが、それがどのような理論に基づいているのか知っている方は少ないのでは?  この本では全てのNP問題がSATに多項式帰着することを示すクック-レビンの定理をはじめ、さまざまな重要問題が登場して非常に興味深いです。前提知識は意外と多くないので、計算理論が専門でなくても読めるようになっています。 最後に そのほかに面白かった本があれば追加していく予定です。JavaScriptの練習本とかも面白いのでいつか紹介したいです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

satasmodelsのソースコードを追ってロジスティック回帰の回帰係数の標準誤差の計算を確認した話

結論は対数尤度関数のへシアン(Fisherの情報量行列)の逆行列の対角成分なんだけどソースコード追っかけるのに割と時間を食ったのでメモを残す。 設定 statsmodels ver0.12.2 statsmodels.discrete.discrete_model.Logitを利用したロジスティック回帰分析について調べました 要旨 statsmodels.discrete.discrete_model.Logitの戻り値のsummaryメソッドを実行した時に表示される回帰係数の標準誤差はLogit.hess()で計算される対数尤度関数のへシアンの逆行列の対角成分。 本題 Logitのfitを実行すると返されるのはstatsmodels.discrete.discrete_model.BinaryResultsWrapper これのsummaryメソッドを実行した時に表示される回帰係数の標準誤差がどう計算されているか知りたかった BinaryReuslts.summary()内でさらに Super(DiscreteResults)のsummaryメソッドを呼び出している そこでさらにstatsmodels.iolib.summaryのadd_table_paramsメソッドを呼び出して、さらに同じクラス内のsummary_paramsメソッドにとぶ。 そこでようやくstd_errにresults.bseを代入しているのを発見。 もう一度BinaryResultsクラスに戻るがこの中でも、親クラス(DescreteResults)でも標準誤差を計算している様子はない。のでさらに親クラス(LikelihoodModelResults)まで戻ってようやくbse()というメソッドを発見・・・・長かった。 bse()の中身は同じクラス内のメソッドcov_params()を呼び出していて、、、、まだ旅が続く cov_params()、if分岐が多くてミスってる可能性あるけど、基本設定(特にへシアンとかの計算に自作の関数与えてない)では多分self.normalized_cov_params * scaleを返しているだけ。 normalized_cov_paramsは__init__内で引数で与えられているので、、、ようやくコード内で、BinaryResults(DescreteResults(LikelihoodModelResults))にnormalized_cov_paramsを渡している箇所を見ればいいことにたどり着く。 元々BinaryResulutsはLogit.fit()の戻り値だったので、今度はLogit.fit()を辿るたびに出るが、、こっちはもはや長旅の後の下り坂みたいなもんで、大したことはない。 LogitのfitはSuper(BinaryModel)のfitを呼び出しているので、そっちをみにいくと、そこで定義されていないから、さらにその上のDiscreteModelを見に行く、、、、ここでもfitは親(base.LikelihoodModel)のfitを呼び出していて、、、LikelihoodModel.fit()の中で、LikelihoodModelResults(self,xopt,Hinvとnormalized_cov_paramsにHinvを渡している箇所を発見!!! Hinvは同fitメソッドの中で計算されているH = self.hessian(xopt)の逆行列で、hessian()はLogitの中で定義されているからこれで行き着いた!! BinaryResults.summary()で表示されるロジスティック回帰の回帰係数の標準誤差は対数尤度関数のへシアンの逆行列の対角成分!!! TODO へシアンの逆行列を標準誤差に使っているのはパラメタの推定量の分散の下限はクラメル・ラオの定理でFisherの情報量行列の逆行列が下限だと知られているから。最尤推定量の時その分散は下限に一致することを使っている。 そのうちこれについてもまとめたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Facebook 最適化ライブラリ Ax でベイズ最適化を用いて SVR のパラメータをチューニングしてみた

Axは Facebook 製の最適化ライブラリです。 バンディットアルゴリズムやベイズ最適化の中身に触れなくてもパラメータ最適化ができる API が特徴です。 インストール pip か conda でインストールします。 pip install ax-platform jupyter APIs Ax の API には loop, service, developer の3つがあります。 中でも developer api はデータサイエンティスト、機械学習エンジニア、研究者向けに作られた api で、カスタム性が高いのが特徴です。今回は developer api を使ってパラメータチューニングをします。 ベイズ最適化について ベイズ最適化は目的関数が最大になるパラメータを効率よく探索する方法です。 手順 1. ガウス過程回帰で目的関数の事後確率分布を計算します。 2. 事後確率分布を獲得関数に入力して獲得関数が最大になるパラメータを推薦します。 期待値が大きいところを採用する「活用」と、分散が大きいところを採用する「探索」を数値化したものが獲得関数になり、獲得関数が最大のパラメータはより良い目的関数を得る確率が高くなります。これを繰り返して目的関数が最大になるパラメータをベストパラメータとします。 パラメータのチューニングには膨大な時間がかかりますが、ベイズ最適化で効率よく探索することにより試行回数が少なくて済みます。 Ax はカーネルや獲得関数を決める必要がないので、知識がなくてもベイズ最適化が使えます。 SVR のパラメータチューニング ボストン住宅価格データセットを用いて SVR のパラメータチューニングをします。 データセットを読み込み前処理をします。 import numpy as np from ax import ( ParameterType, RangeParameter, SearchSpace, SimpleExperiment, modelbridge, models, ) from sklearn.svm import SVR from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split, KFold from sklearn.datasets import load_boston boston = load_boston() X = boston.data y = boston.target X_train, X_test, y_train, y_test = train_test_split(X, y) scaler = StandardScaler() autoscale_X_train = scaler.fit_transform(X_train) autoscale_X_test = scaler.transform(X_test) kf = KFold() 次に SVR のハイパーパラメータ C, epsilon, gamma の探索領域を定義します。 svr_search_space = SearchSpace( parameters=[ RangeParameter(name="C", parameter_type=ParameterType.FLOAT, lower=0.1, upper=100, log_scale=True ), RangeParameter(name="epsilon", parameter_type=ParameterType.FLOAT, lower=0.001, upper=100, log_scale=True ), RangeParameter(name="gamma", parameter_type=ParameterType.FLOAT, lower=0.001, upper=100, log_scale=True ), ] ) K-分割交差検証とベイズ最適化でパラメータチューニングをします。 SVR のスコアを返す関数 svr_evaluation_function を事前に定義し、SimpleExperiment オブジェクトを生成します。 15回の探索で最もスコアの高かったパラメータをベストパラメータとします。 そのパラメータを用いてテストデータのスコアを計算します。 kfold_scores=[] kfold_parameters=[] for train_index, valid_index in kf.split(X_train): X2d_train = X_train[train_index] X2d_valid = X_train[valid_index] y2d_train = y_train[train_index] y2d_valid = y_train[valid_index] scaler_2d = StandardScaler() autoscale_X2d_train = scaler_2d.fit_transform(X2d_train) autoscale_X2d_valid = scaler_2d.transform(X2d_valid) # 目的関数を出力する関数 def svr_evaluation_function(parameterization, weight=None): svr = SVR(C=parameterization["C"], epsilon=parameterization["epsilon"], gamma=parameterization["gamma"] ) svr.fit(autoscale_X2d_train, y2d_train) accuracy = svr.score(autoscale_X2d_valid, y2d_valid) return {"accuracy": (accuracy, 0.0)} exp = SimpleExperiment(name="test_svm", search_space=svr_search_space, evaluation_function=svr_evaluation_function, objective_name="accuracy" ) # ベイズ最適化 sobol = modelbridge.get_sobol(search_space=exp.search_space) print(f"Running Sobol initialization trials...") for _ in range(5): exp.new_trial(generator_run=sobol.gen(1)) for i in range(15): print(f"Running GP+EI optimization tiral {i+1}/15...") gpei=modelbridge.get_GPEI(experiment=exp, data=exp.eval()) exp.new_trial(generator_run=gpei.gen(1)) best_objectives = exp.eval().df["mean"] bestParameter_score = np.max(best_objectives) bestParameter_trial = np.argmax(best_objectives) bestParameter = exp._trials[bestParameter_trial].arm.parameters kfold_scores.append(bestParameter_score) kfold_parameters.append(bestParameter) # チュー二ングされたパラメータ svr_parameter = kfold_parameters[np.argmax(kfold_scores)] svr = SVR(**svr_parameter) # テストデータでスコアを計算 svr.fit(autoscale_X_train, y_train) svr_score = svr.score(autoscale_X_test, y_test) >>> svr_score 0.77991406620649 計算時間は1分15秒でした。早いですね。 グリッドサーチCVで同じパラメータチューニングをしてみました。 from sklearn.pipeline import make_pipeline from sklearn.model_selection import GridSearchCV pipe = make_pipeline(StandardScaler(), SVR()) C_grid = np.linspace(0.01, 100, 50) gamma_grid = np.linspace(0.001, 100, 50) epsilon_grid = np.linspace(0.001, 100, 50) param_grid = [{pipe.steps[1][0] + "__C": C_grid, pipe.steps[1][0] + "__gamma": gamma_grid, pipe.steps[1][0] + "__epsilon": epsilon_grid }] reg = GridSearchCV(pipe, param_grid=param_grid) reg.fit(X_train, y_train) score = reg.score(X_test, y_test) >>> score 0.6963721758604782 計算時間は10分49秒でした。分割数が50点で少なめですが、ベイズ最適化よりもおよそ10倍時間がかかり、スコアは低くなっています。 まとめ Ax で SVR のパラメータチューニングを行いました。sklearn のモデルにも簡単に組み込めて使いやすかったです。機械学習のパラメータチューニングにはもってこいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Flask]ブログをサクッと作る

はじめに 備忘録的適当記事。 sqlalchemyやbootstrapの練習がてら。 完成形: これを使いたい方は git clone https://github.com/matcha1024/flask-blog.git してお使いください。 改変等はご自由にどうぞ。 追加したい機能 ブラウザからマークダウンで記事投稿 PV数計測 タグ機能 記事投稿 SQLAlchemyでさくっと。 SQL部分 class Article(db.Model): id = db.Column(db.Integer, primary_key=True) created_at = db.Column(db.DateTime, nullable=False, default=datetime.now) article_title = db.Column(db.String(255)) article = db.Column(db.Text()) pv = db.Column(db.Integer) tag = db.Column(db.String(80)) def __init__(self, created_at, article_title, article, pv, tag): self.created_at = created_at self.article_title = article_title self.article = article self.pv = pv self.tag = tag @app.route("/upload", methods=["POST"]) def upload(): date=datetime.now() article_title = request.form["title"] article = request.form["article"] tag = request.form["tag"] new_article = Article(created_at=date, article_title=article_title, article=article, pv=0, tag=tag) db.session.add(new_article) db.session.commit() return redirect("/") new.html <div class="container" style="background:white; opacity=0.8;"> <form action="/upload" method="post"> タイトル:<br> <input type="text" name="title"></textarea><br> 本文:<br> <textarea name="article" rows=25 cols=100></textarea><br> タグ:<br> <input type="text" name="tag"> <button type="submit">送信</button> </form> </div> 記事投稿が誰でもできては困るので、 MY_IP を設定しておいて、それとアクセス元IPが一致すれば投稿できるようにします。 # My Global Ip Adress -> https://www.cman.jp/network/support/go_access.cgi MY_IP = "123.45.67.89" @app.route("/new") def new(): if(not request.environ["HTTP_X_FORWARDED_FOR"] == MY_IP): return "ERROR: 権限がありません" top.html {% if admin %} <a href="/new">新規投稿</a> {% endif %} マークダウンですが、Pythonにmarkdownというライブラリがあるため、そちらを使います。 編集機能をつけたいので、SQL上にはmarkdown形式で保存し、表示するときにhtmlに変換するようにします。 import markdown md = markdown.Markdown() : : articles = Article.query.filter_by(id=art_id).first() md_convert_article = md.convert(articles.article) PV アクセスされるたびに、 article.pv に1ずつ加算して、commitすることでUPDATEさせます。 @app.route("/archive/<art_id>") def archive(art_id): global MY_IP articles = Article.query.filter_by(id=art_id).first() md_convert_article = md.convert(articles.article) articles.pv += 1 db.session.commit() タグ filter_by でいい感じに。 @app.route("/tag/<art_tag>") def tag(art_tag): articles = Article.query.filter_by(tag=art_tag).all()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python × Excel】OpenpyxlでVlookupをやってみる

はじめに PythonでExcelを操作していて、Vlookupしたいときありませんか?Vlookup便利ですよね。 ということでOpenpyxlを使ってブックをまたいでVlookupする方法を解説します。 今回は下記の評価データと給与データをサンプルとして使用します。 評価データ 給与データ 準備 必要なライブラリをインストールしましょう。今回使うのはOpenpyxlです。 import openpyxl as px 必要ファイルの読み込み ここで評価データと給与データの2つのエクセルを読み込みます。 wb = px.load_workbook("C:\\Users\\ユーザー名\\Documents\\Qiita用サンプル\\評価データ.xlsx") ws = wb["評価"] wb2 = px.load_workbook("C:\\Users\\ユーザー名\\Documents\\Qiita用サンプル\\給与データ.xlsx") ws2 = wb2["給与"] Vlookup いよいよVlookupをしていきます。 #Get last row lastrow = ws.max_row lastrow2 = ws2.max_row #get values from IMaster Sheet for i in range(2, lastrow + 1): name_hyoka = ws['B' + str(i)].value for j in range(2, lastrow2 + 1): name_salary = ws2['A' + str(j)].value # get values by vlook up if name_hyoka == name_salary: salary = ws2['B' + str(j)].value ws.cell(row = i, column = 5, value = salary) break #Save def save(): wb.save(filename = "C:\\Users\\ユーザー名\\Documents\\Qiita用サンプル\\評価データ_給与.xlsx") save() 上記コードでは、評価データの社員名を検索値として給与データの社員名カラムで検索をかけています。そして社員名が一致した場合は給与データの給与を、評価データのE列に書き込むようにしています。 ポイントは ①2つのシート入れ子にする形でそれぞれをForループで回す。 ②if文で文字列一致の判定をする。 ③一致していた場合のみ値をセルに書き込む ④break忘れない*ここ大事 ⑤最後にwbを保存 データの読み込みはカラムのアルファベット指定で出来たのですが、書き込みの時はそれがうまくいかなかったので数字でカラムを指定しています。 ちなみにbreakを入れるのは文字列一致を発見した後、再び外側のループに戻って次の検索値を検索するためです。 コード全体としてはこんな感じです。 import openpyxl as px wb = px.load_workbook("C:\\Users\\ユーザー名\\Documents\\Qiita用サンプル\\評価データ.xlsx") ws = wb["評価"] wb2 = px.load_workbook("C:\\Users\\ユーザー名\\Documents\\Qiita用サンプル\\給与データ.xlsx") ws2 = wb2["給与"] #Get last row lastrow = ws.max_row lastrow2 = ws2.max_row #get values from IMaster Sheet for i in range(2, lastrow + 1): name_hyoka = ws['B' + str(i)].value for j in range(2, lastrow2 + 1): name_salary = ws2['A' + str(j)].value # get values by vlook up if name_hyoka == name_salary: salary = ws2['B' + str(j)].value ws.cell(row = i, column = 5, value = salary) break #Save def save(): wb.save(filename = "C:\\Users\\ユーザー名\\Documents\\Qiita用サンプル\\評価データ_給与.xlsx") save() 動作の結果はこちらです。 E列に給与が入ってますね。 参考にしたサイト https://fastclassinfo.com/entry/python_excel_vlookup/ こちらのサイトの解説を参考に実践しました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【5年分データ分析】ボリンジャーバンドが有効な銘柄ランキング

はじめに 株式相場を予測・分析するテクニカル指標を検証した記事を投稿しています。 過去には、TOPIX500銘柄の過去5年分のデータを使って、移動平均線のゴールデンクロス、MACD、RSI、ボリンジャーバンドについて検証した内容をQiitaやYouTubeに投稿してきました。※YouTube動画はQiitaに投稿した記事をSofTalkで読み上げています。 ・【5年分データ分析】ゴールデンクロスの数日後に株価は上がっているのか    [Qiita][YouTube] ・【5年分データ分析】MACDの買いサインから上がる確率を検証しました   [Qiita][YouTube] ・【5年分データ分析】投資タイミングを判断するときに必ず見てほしい指標RSIをデータ分析   [Qiita][YouTube] ・【5年分データ分析】ボリンジャーバンドを利用した逆張り戦略は効率的に稼げない?   [Qiita][YouTube] 今後も様々なテクニカル指標について検証していきます。 今回の検証は 過去に投稿した記事ではボリンジャーバンドを有効性をTOPIX500構成銘柄の5年分の全てのデータを使って検証した結果、売り/買いのタイミングを高い確率で当てれるわけではないということがわかりました。 しかし、ボリンジャーバンドが全くの無意味な指標ではなく、多くの投資家はボリンジャーバンドを指標として、売り/買いタイミングを考えます。 そこで、どんな条件でボリンジャーバンドを使えば、高い確率で売り/買いのタイミングを捉えることができるかを見つけたいと思います。 今回の検証では、TOPIX500を構成する各銘柄毎に、ボリンジャーバンドの売り/買いのタイミングから高確率で株価が上昇/下落した銘柄を調査し、ボリンジャーバンドで勝てる銘柄は存在するのかについて調査しました。 【結果】 ボリンジャーバンドの売り/買いのタイミングから約60〜75%という高い確率で、売り/買いタイミングから株価が上昇/下落する銘柄が存在する。 ボリンジャーバンド 有名なテクニカル分析の手法の1つです。 ボリンジャーバンドは株価の移動平均に加えて、ばらつき具合(標準偏差)を取り入れた分析手法です。 ミドルバンド、アッパーバンド、ローバンドがあり、ミドルバンドは20日の移動平均、アッパーバンドは20日の移動平均に20日の標準偏差の2倍を加えたもの、ローバンドは20日の移動平均から20日の標準偏差の2倍を引いたものです。 アッパーバンド、ローバンドが示す範囲と実際の価格の関係を見て分析を行います。 基本的な使い方として、アッパーバンド、ローバンドで囲まれた領域から株価が外れた時が売りや買いのタイミングです。 ボリンジャーバンドの使われ方は2通りあります。 逆張り指標として使う(逆張り戦略) まず1つ目は、ボリンジャーバンドのローバンドを支持線、アッパーバンドを抵抗線と見なす投資戦略です。 ・買いシグナル:価格が下部バンドを下抜けた場合 ・売りシグナル:価格が上部バンドを上抜けた場合 この戦略は株価が正規分布に従うと仮定する戦略です。 つまり、株の値動きが±2σのバンドの内側に約95%の確率で収まるだろうと考え、株価がバンドから外れた場合は、平均値への回帰するはずという考え方に基づいた戦略です。 ※正規分布において、1σ(標準偏差)内に事象が存在する確率は約68%、2σ内に事象が存在する確率は約95%となる。 武田薬品工業(TYO: 4502)を例に見ていきましょう。 2020年8月から2021年3月の株価時系列データです。 図で示したように、上に抜けたときには高値になっていると考えられるため売り、下に抜けたときには安値になっていると考えられるため買いの判断をします。 武田薬品工業の例では、緑点で示したように買いタイミングが8回あります。その後の株価に注目すると、8回のうち6回は数日中に株価上昇しています。また赤点で示したように売りタイミングは2回あります。その後の株価に注目すると、2回のうち1回は数日中に株価下落しています。 この例を見ると、買いタイミングがうまく捉えられていることがわかります。 順張り指標として使う(順張り戦略) 次に2つ目は、ボリンジャーバンドのローバンドを下回ったとき、新しい下降トレンド発生、アッパーバンドを上回った時、上昇トレンド発生とみなす投資戦略です。 ・買いシグナル:価格が上部バンドを上抜けた場合⇨新しい上昇トレンド ・売りシグナル:価格が下部バンドを下抜けた場合⇨新しい下降トレンド この戦略は新しいトレンドが発生したという考えに基づく投資戦略です。 凸版印刷を例に見ていきましょう。 凸版印刷の例では、緑点で示すように売りタイミングが9回、赤点で示すように買いタイミングは5回あります。 それぞれの点に注目してみると、±2σラインに点が並ぶようなバンドウォークと呼ばれる現象が発生しており、トレンドの発生が見られます。したがって、ボリンジャーバンドは新しいトレンド発生のサインとして見なすことができ、ボリンジャーバンドの順張り戦略は、相場の勢いに乗るかたちでポジションをとる戦略です。 検証内容 武田薬品工業と凸版印刷の例を使って、ボリンジャーバンドを利用した逆張り戦略、順張り戦略を紹介しました。 今回の検証では、TOPIX500銘柄を対象に5年分のデータからボリンジャーバンドの逆張り戦略、順張り戦略が有効な銘柄を検証していきます。 大まかな流れとして、5年分のすべてのデータに対してボリンジャーバンドから外れたタイミングを見つけ、その数日後の株価上昇率を調査します。 また、検証プログラムは記事の最後に記載します。 【ボリンジャーバンドの設定期間】 20日(最も使われる) 【基準値】 ・ローバンド(2σ)を下回るとき ・アッパーバンド(-2σ)を上回るとき 【検証期間】 2015年1月~2020年3月の約5年間 【対象銘柄】 TOPIX500を構成する各銘柄 ※対象銘柄は月1回以上投資タイミングがあるものに限っています。 【株価の上昇を確認する日】 1. 1日後 2. 3日後 3. 5日後 4. 10日後 ▼ ①逆張り戦略の買いサイン ローバンドから外れたとき、数日後に株価が上昇しているかを確認します。 ▼ ②逆張り戦略の売りサイン アッパーバンドから外れたとき、数日後に株価が下落しているかを確認します。 ▼ ③順張り戦略の売りサイン ローバンドから外れたとき、数日後に株価が下落しているかを確認します。 ▼ ④順張り戦略の買いサイン アッパーバンドから外れたとき、数日後に株価が上昇しているかを確認します。 検証結果 検証内容①〜④の結果を紹介していきます。 ここでは数日後に上昇/下落している確率と分布図を示します。 基本的に緑が上昇、赤が下落を意味し、分布図中の数字は上昇/下落している確率を意味します。 分布図では、変動率の階級の幅を2%としており、-24%から24%までの階級ごとの相対度数を示しています。図が見にくい場合は画像をクリックして拡大してご覧ください。 【結果の図からわかること】 ・約60〜75%という高い確率で、売り/買いタイミングから株価が上昇/下落する銘柄が存在する ▼ ①逆張り戦略の買いサイン ローバンドを下回った数日後(1、3、5、10日後)の株価の上昇確率が高い銘柄です。 1日後に上昇確率が高かった銘柄です。 3日後です。 5日後です。 10日後です。 ▼ ②逆張り戦略の売りサイン アッパーバンドを上回った数日後(1、3、5、10日後)の株価の下落確率が高い銘柄です。 1日後です。 3日後です。 5日後です。 10日後です。 ▼ ③順張り戦略の売りサイン ローバンドを下回った数日後(1、3、5、10日後)の株価の下落確率が高い銘柄です。 1日後です。 3日後です。 5日後です。 10日後です。 ▼ ④順張り戦略の買いサイン アッパーバンドを上回った数日後(1、3、5、10日後)の株価の上昇確率が高い銘柄です。 1日後です。 3日後です。 5日後です。 10日後です。 まとめ 今回は、ボリンジャーバンド分析を使って、売り/買いのタイミングから高確率で、株価が上昇/下落する銘柄は存在するのかについて調査しました。 冒頭に記載したように、ボリンジャーバンドは順張り指標として使用する方法(順張り戦略)と逆張り指標として使う方法(逆張り戦略)の2種類の方法があります。今回の検証では、ボリンジャーバンドのアッパーバンドを下から上に抜けた時から数日後に株価が1.上昇する確率2.下落する確率、ローバンドを上から下に抜けた時から数日後に株価が3.上昇する確率、4.下落する確率を計算し、順張り戦略が有効な銘柄、または逆張り戦略が有効な銘柄を探していきました。 検証①〜④の結果から、どちらの戦略についても約60〜75%という高い確率で、売り/買いタイミングから株価が上昇/下落する銘柄が存在するということがわかりました。 次回はどんな条件でボリンジャーバンドを使えば、売り/買いのタイミングを高確率で捉えられるかを探していきたいと思います。ex)株価が上昇または下降トレンドにある場合、ボリンジャーバンドのそれぞれの戦略は使えるか?など。 付録(分析プログラム) プログラム内で使用している自作の関数や株価データの取得方法は以下の記事をご参照ください。(執筆中) ・株分析ツールの使い方(備忘録) bollinger_bands_each_brand.py import trade_package.get as get import trade_package.tech as tech import pandas as pd # 銘柄コードの読み込み stocks = get.topix500() # 結果保存 df_result_low = pd.DataFrame() df_result_upper = pd.DataFrame() #start_time = '2020-09-22' #end_time = '2021-02-22' def roc(df, day): return (df.shift(-day)["Close"]-df["Close"])/df["Close"]*100 for code in stocks.code: #print(code) data = get.price(code)#[start_time:end_time] tech.bb(data, period=20, sigma=2) data["sign_l"] = data["Close"]-data["bb_l"] data["sign_u"] = data["bb_u"]-data["Close"] data["roc1"] = roc(data,1) data["roc3"] = roc(data,3) data["roc5"] = roc(data,5) data["roc10"] = roc(data,10) low_total = len(data[data["sign_l"]<0]) uppper_total = len(data[data["sign_u"]<0]) low_roc1_up = len(data[(data["sign_l"]<0)&(data["roc1"]>0)]) low_roc3_up = len(data[(data["sign_l"]<0)&(data["roc3"]>0)]) low_roc5_up = len(data[(data["sign_l"]<0)&(data["roc5"]>0)]) low_roc10_up = len(data[(data["sign_l"]<0)&(data["roc10"]>0)]) uppper_roc1_up = len(data[(data["sign_u"]<0)&(data["roc1"]>0)]) uppper_roc3_up = len(data[(data["sign_u"]<0)&(data["roc3"]>0)]) uppper_roc5_up = len(data[(data["sign_u"]<0)&(data["roc5"]>0)]) uppper_roc10_up = len(data[(data["sign_u"]<0)&(data["roc10"]>0)]) res_low = pd.DataFrame([low_total, low_roc1_up, low_roc3_up, low_roc5_up, low_roc10_up], columns=[code],index=['total', 'roc1', 'roc3', 'roc5', 'roc10']).T res_upper = pd.DataFrame([uppper_total, uppper_roc1_up, uppper_roc3_up, uppper_roc5_up, uppper_roc10_up], columns=[code],index=['total', 'roc1', 'roc3', 'roc5', 'roc10']).T df_result_low = pd.concat([df_result_low,res_low]) df_result_upper = pd.concat([df_result_upper,res_upper]) #data.to_csv('result_{}.csv'.format(code)) df_roc_bb_low = pd.concat([df_result_low.total, round(df_result_low.roc1/df_result_low.total*100,2), round(df_result_low.roc3/df_result_low.total*100,2), round(df_result_low.roc5/df_result_low.total*100,2), round(df_result_low.roc10/df_result_low.total*100,2)],axis=1) df_roc_bb_low.columns = ['total', 'roc1', 'roc3', 'roc5', 'roc10'] df_roc_bb_upper = pd.concat([df_result_upper.total, round(df_result_upper.roc1/df_result_upper.total*100,2), round(df_result_upper.roc3/df_result_upper.total*100,2), round(df_result_upper.roc5/df_result_upper.total*100,2), round(df_result_upper.roc10/df_result_upper.total*100,2)],axis=1) df_roc_bb_upper.columns = ['total', 'roc1', 'roc3', 'roc5', 'roc10'] print(df_roc_bb_low.sort_values('roc1', ascending=False)) print(df_roc_bb_upper.sort_values('roc1', ascending=False)) df_roc_bb_low.to_csv('roc_low.csv') df_roc_bb_upper.to_csv('roc_upper.csv') sum_low = df_result_low.sum() #print(sum_low) print('1D: {}, 3D: {}, 5D: {}, 10D: {}' .format(round(sum_low.roc1/sum_low.total*100,2), round(sum_low.roc3/sum_low.total*100,2), round(sum_low.roc5/sum_low.total*100,2), round(sum_low.roc10/sum_low.total*100,2))) sum_upper = df_result_upper.sum() #print(sum_upper) print('1D: {}, 3D: {}, 5D: {}, 10D: {}' .format(round(sum_upper.roc1/sum_upper.total*100,2), round(sum_upper.roc3/sum_upper.total*100,2), round(sum_upper.roc5/sum_upper.total*100,2), round(sum_upper.roc10/sum_upper.total*100,2))) 実行例 # ローバンドを下回ったタイミングから数日後の株価上昇確率(各銘柄) total roc1 roc3 roc5 roc10 7337.T 2 100.00 100.00 100.00 100.00 5463.T 86 66.28 60.47 55.81 58.14 4578.T 73 65.75 63.01 57.53 54.79 7747.T 58 65.52 65.52 67.24 62.07 3382.T 77 64.94 64.94 66.23 59.74 ... ... ... ... ... ... 6923.T 88 36.36 39.77 42.05 46.59 6770.T 89 35.96 43.82 42.70 41.57 7518.T 70 35.71 54.29 54.29 67.14 9601.T 96 34.38 35.42 42.71 48.96 9104.T 99 33.33 34.34 38.38 37.37 [498 rows x 5 columns] # アッパーバンドを上回ったタイミングから数日後の株価上昇確率(各銘柄) total roc1 roc3 roc5 roc10 7337.T 11 72.73 72.73 72.73 45.45 8304.T 76 63.16 67.11 56.58 46.05 6406.T 127 62.99 66.93 62.99 55.12 6981.T 124 62.90 64.52 67.74 64.52 8136.T 109 62.39 57.80 54.13 35.78 ... ... ... ... ... ... 2502.T 85 35.29 40.00 36.47 47.06 3765.T 88 35.23 38.64 31.82 32.95 6417.T 85 31.76 41.18 44.71 55.29 3938.T 77 31.17 40.26 42.86 53.25 2229.T 88 30.68 40.91 47.73 48.86 [498 rows x 5 columns] # ローバンドを下回ったタイミングから数日後の株価上昇確率(全銘柄) 1D: 50.08, 3D: 51.16, 5D: 51.95, 10D: 51.52 # アッパーバンドを上回ったタイミングから数日後の株価上昇確率(全銘柄) 1D: 48.46, 3D: 50.96, 5D: 51.81, 10D: 50.88
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Problems Hard No.81~90解説

Hard No.81 : C - Boxes and Candies C - Boxes and Candies 発想 隣り合う2つの箱のうち、どちらの箱からどれだけキャンディを取れば良いのか。左から箱を見ていって、$i$番目と$i+1$番目の箱うち、$i+1$番目から可能な限りキャンディを食べるのが良いです。なぜなら$i+1$番目からキャンディを食べることで$i+2$番目の箱から食べるキャンディの個数を少なくすることができるからです。 実装 N, X = map(int, input().split()) A = list(map(int, input().split())) ans = 0 for i in range(N-1): if A[i] + A[i+1] > X: #食べるべきキャンディの個数 x = A[i+1] + A[i] - X ans += x A[i+1] -= x #取りすぎた分のキャンディをi番目から取る if A[i+1] < 0: A[i] += A[i+1] A[i+1] = 0 print(ans) Hard No.82 : C - Different Strokes C - Different Strokes 発想 単に自分の幸福度だけ追求すると、相手の幸福度も高くなり、(自分の幸福度)ー(相手の幸福度)を最大化できない場合があります。自分の幸福度だけでなく、相手の幸福度も高い料理を選ぶことで、相手が幸福度を上げづらくすることができます。 実装 以下のようなリストを作成します。 [A_i+B_i, { \ }A_i, { \ }B_i] $A_i+B_i$が大きい順にソートして、左から順に高橋、青木の順で料理を食べればよいです。 N = int(input()) arr = [] for i in range(N): a, b = list(map(int, input().split())) arr.append([a+b, a, b]) arr = sorted(arr, reverse=True) ans = 0 for i in range(N): c, a, b = arr[i] #高橋が食べる if i%2 == 0: ans += a #青木が食べる else: ans -= b print(ans) Hard No.83 : A - Darker and Darker A - Darker and Darker 発想 黒を増やすか白を減らすか少し悩みます。最終的にはすべてのマスが黒になるので、黒マスから移動すると考えたほうが良さそうです。黒マスの距離はすべて0にしておいて、幅優先探索で白マスすべてに距離を与えれば良さそうです。答えは距離の最大値になります。 実装 from collections import deque H, W = map(int, input().split()) A = [] for i in range(H): A.append(list(input())) Black = deque() for i in range(H): for j in range(W): if A[i][j] == '#': Black.append([i, j]) #距離を入れるリスト dist = [] for i in range(H): dist.append([-1]*W) #黒マスの距離をすべて0にする for i in range(H): for j in range(W): if A[i][j] == '#': dist[i][j] = 0 #幅優先探索 while len(Black) > 0: i, j = Black.popleft() for di, dj in [[1, 0], [0, 1], [-1, 0], [0, -1]]: i2 = i+di j2 = j+dj #マスからはみ出ちゃダメ! if not (0 <= i2 < H and 0 <= j2 < W): continue #黒マスに移動しちゃダメ! if A[i2][j2] == '#': continue if dist[i2][j2] == -1: dist[i2][j2] = dist[i][j] + 1 Black.append([i2, j2]) ans = 0 for i in range(H): ans = max(ans, max(dist[i])) print(ans) Hard No.84 : C - Shopping Street C - Shopping Street 発想 読みづらいです。苛々です。 午前午後や曜日に深い意味はなく、単に10個の時間帯があると思えばよいです。10個の時間帯それぞれで店を開けるか閉めるかを全探索すれば良さそうです。 実装 店$i$とjoisinoお姉ちゃんの店が同時に空いている時間帯の個数を数えていきます。例えば店2とjoisinoお姉ちゃんの店が3つの時間帯で両方とも店を開けていたら答えに$P_{2,3}$を加えます。 N = int(input()) F = [] for i in range(N): f = list(map(int, input().split())) F.append(f) P = [] for i in range(N): p = list(map(int, input().split())) P.append(p) ans = -10**10-1 #10個の時間帯でjoisinoお姉ちゃんの店を開けるかどうか全探索 for i in range(1, 2**10): #C[i]:店iとjoisinoお姉ちゃんの店が両方空いている時間帯の個数 C = [0]*N for j in range(10): if i >> j & 1 == 1: for k in range(N): if F[k][j] == 1: C[k] += 1 tmp = 0 for i in range(N): tmp += P[i][C[i]] ans = max(ans, tmp) print(ans) Hard No.85 : C - Pyramid C - Pyramid 発想 得られた情報の個数が3個以上であり、かつそれらの高さが0より大きいならば、確実にピラミッドの中心座標と高さが求められます。しかし、この問題設定はそのようになっていないです。 (1)情報→答えを求める という方針がうまくいかないので、 (2)答えを決め打ち→情報に矛盾しない という方針が良さそうです。 実装 ピラミッドの中心座標は101×101通りがあります。高さを決めるためには、高さ1以上の情報が1つ必要です。 N = int(input()) C = [] for i in range(N): c = list(map(int, input().split())) C.append(c) #高さ1以上の情報を1つ用意する for i in range(N): if C[i][2] > 0: xo = C[i][0] yo = C[i][1] ho = C[i][2] break #中心座標(cy, cx)を決める for cy in range(101): for cx in range(101): #中心座標が(cy, cx)の時のピラミッドの高さを求める Ho = abs(cx-xo) + abs(cy-yo) + ho for i in range(N): #計算結果と与えられた情報が違ったらbreak if max(Ho - abs(C[i][0]-cx) - abs(C[i][1]-cy), 0) != C[i][2]: break else: #すべての情報に矛盾しなければ、それが答え print(cx, cy, Ho) exit() Hard No.86 : D - Coloring Dominoes D - Coloring Dominoes 発想 ドミノを左から敷き詰めた時、次のドミノの色の場合の数は現在のドミノの色と置き方によって変わります。左から$i$番目の位置のドミノの色の場合の数を考えます。 (1)$i$番目の位置のドミノを横に置くとき (1.1)$i-1$番目の位置のドミノが横に置いてあったとき →3通り (1.2)$i-1$番目の位置のドミノが縦に置いてあったとき →2通り (2)$i$番目の位置のドミノを縦に置くとき (2.1)$i-1$番目の位置のドミノが横に置いてあったとき →1通り (2.2)$i-1$番目の位置のドミノが縦に置いてあったとき →2通り 以上のように場合分けして考えます。 実装 $i$番目の位置のドミノを横に置くときは次のドミノは$i+2$番目の位置になることに注意が必要です。 N = int(input()) S1 = input() S2 = input() mod = 10**9 + 7 cur = 0 #初期値 if S1[0] != S2[0]: ans = 6 cur = 2 else: ans = 3 cur = 1 while cur < N: #横に置くとき if S1[cur] != S2[cur]: #手前のドミノも横に置いてある if S1[cur-1] != S2[cur-1]: ans *= 3 ans %= mod #手前のドミノは縦 else: ans *= 2 ans %= mod #位置を+2することに注意 cur += 2 #縦に置くとき else: #手前のドミノも縦に置いてある if S1[cur-1] == S2[cur-1]: ans *= 2 ans %= mod cur += 1 print(ans) Hard No.87 : D - Friend Suggestions D - Friend Suggestions 発想 友達とかいう言葉があると、「友達グループ」とか「友達関係」とか「あんたなんか友達じゃないんだからねっ!関係」とかあったりして、ああUnionとFindをするんだなって思います(病気)。 友達関係で結ばれている人たちを1つのグループにまとめたとします。このとき、人$i$に対して求める答え$ans[i]$は \begin{align} ans[i]&=(人iが所属するグループの人数)\\ &\quad -(人iが所属するグループの中で、人iと友達関係かブロック関係の人数)\\ &\quad -1 \end{align} となります(最後の-1は自分自身)。以上のような計算はUnion-Findという根つき木のデータ構造を用いることで実装できます。 実装 今回のUnion-Findは3つの関数からなります。 (1)find(x) 頂点$x$の根を返します。$x$の1つ上の頂点(親)を見た時に、それが根でなかったら、親を根につなぎ直すことをします(経路圧縮)。こうすることで計算量を減らせます。 (2)union(x, y) 頂点$x,y$が別々のグループに所属していた場合に、頂点$x,y$それぞれが所属しているグループを1つのグループに併合します。 (3)same(x, y) 頂点$x,y$が同じグループに所属していればTrue、そうでなければFalseを返します。 N, M, K = map(int,input().split()) #par[i]:頂点iの根。par[i]==iの時、自分が木の根。 par = [i for i in range(N)] #sz[i]:頂点iを部分木の根とみなしたときのその木の大きさ。 sz = [1]*N ans = [0]*N def find(x): #根に到達したら、その頂点を返す if par[x] == x: return x else: #経路圧縮 par[x] = find(par[x]) return par[x] def union(x, y): rx, ry = find(x), find(y) #同じグループなら何もしない if rx == ry: return #どちらの部分木が大きいか判断して、大きいほうの木に小さい木を併合する if sz[rx] < sz[ry]: sz[ry] += sz[rx] par[rx] = ry else: sz[rx] += sz[ry] par[ry] = rx def same(x, y): return find(x) == find(y) for _ in range(M): a, b = map(int,input().split()) a -= 1 b -= 1 #友達関係は-1する ans[a] -= 1 ans[b] -= 1 union(a, b) for _ in range(K): c,d = map(int,input().split()) c -= 1 d -= 1 if same(c, d): #同じグループに所属しているブロック関係は-1する ans[c] -= 1 ans[d] -= 1 for i in range(N): ans[i] += sz[find(i)] - 1 print(ans[i], end=' ') Hard NO.88 : D - XOR World D - XOR World 発想 制約に$10^{12}$があるので$O(1)$か$O(logN)$で解かなければならないです。排他的論理和の性質を考えます。任意の正整数$n$に対して$n⊕n=0$なので、 \begin{align} f(A,B) &= A⊕(A+1)⊕(A+2)⊕...⊕B\\ &= (0⊕1⊕...(A-1))⊕(0⊕1⊕...⊕B)\\ &= f(0,A-1)⊕f(0,B) \end{align} となります。また、任意の偶数$n$に対して$n⊕(n+1)=1$なので f(0,x) = \left\{ \begin{array}{ll} x & (x \equiv0{ \ } mod { \ }4 )\\ 1 & (x \equiv1{ \ } mod { \ }4 )\\ 1⊕x & (x \equiv2{ \ } mod { \ }4 )\\ 0 & (x \equiv3{ \ } mod { \ }4 )\\ \end{array} \right. となります。 実装 A, B = map(int, input().split()) def f(x): M = [x, 1, 1^x, 0] m = M[x % 4] return m print(f(A - 1) ^ f(B)) Hard No.89 : E - Colorful Hats 2 E - Colorful Hats 2 発想 例えば$i=1,2,3$のときにすべて0が続いたときの場合の数は$3×2×1$通りになります。さらにその後に0が来るような場合は実現しません。つまり$A$の中で同じ数字を使うことができるのは3回までということがわかります。また、同じ数字を使うごとに、場合の数は3,2,1と減っていくことがわかります。 次に$i=1,2,3$のときの$A_i$の並びが$[0,0,1]$となるような場合を考えます。$i=1,2$の時にそれぞれ赤、青を使ったとすると、$i=3$の帽子の色は赤、または青の2通りになります。つまり$A$の中で0を使うたびに1として考えられる場合の数が1増えることがわかります。 以上のように (1)同じ数字を使えるのは3回まで (2)ある数字$a$を使うごとに$a$として考えられる場合の数は1減り、$a+1$として考えられる場合の数が1増える ということがわかりました。これらを実装します。 実装 N = int(input()) A = map(int, input().split()) mod = 10**9+7 ans = 1 cnt = [3] + [0]*N for a in A: ans = ans * cnt[a] % mod if ans == 0: break cnt[a] -= 1 cnt[a+1] += 1 print(ans) Hard No.90 : D - Even Relation D - Even Relation 発想 2頂点間の距離が偶数です。頂点0から頂点$i,j$までの距離をそれぞれ$d_i,d_j$と置くと、$d_i,d_j$の組み合わせは (d_i,d_j)=(偶,奇),(奇,偶),(偶,偶),(奇,奇), が考えられます。このとき、 (1)$d_i,d_j$の偶奇が一致しない場合、2頂点間の距離は奇数 (2)$d_i,d_j$の偶奇が一致する場合、2頂点間の距離は偶数 が成り立ちます。例えば距離が偶数の頂点をすべて白に塗り、奇数の頂点を黒に塗ることで答えを得られます。 実装 辺の重み付き最短距離を計算するのでheapを使います。 import heapq N = int(input()) G = [] for i in range(N): G.append([]) for i in range(N-1): u, v, w = map(int, input().split()) G[u-1].append((w, v-1)) G[v-1].append((w, u-1)) dist = [-1]*N dist[0] = 0 done = [False]*N Ans = [0]*N Q = [] heapq.heappush(Q, (0, 0)) 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(N): if dist[i] % 2 == 0: Ans[i] = 0 else: Ans[i] = 1 for i in range(N): print(Ans[i])
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(2021年5月時点) VSCodeのPythonのバージョン更新で有効になったセマンティックハイライトを無効にする

2021年5月時点の話です VSCodeでPythonのコードを開いてみたところ、何か変数とかの色の付き方に違和感が... 今まではselfが青だったし、全体的に変数やメソッド名がこんな水色ではなかったような? この色の付け方はなんか気持ち悪いから前のバージョンに戻せないかな...と思い色々調べてみたところ 2021年5月にPython拡張機能が更新され、Pylanceがデフォルトで使用されるようになった Visual Studio Code向けPython拡張機能の2021年5月版が登場、Pylanceがデフォルト言語サーバに:CodeZine(コードジン) Pylanceのセマンティックハイライトは、これまでのVSCodeのPythonのデフォルトだったTextMate文法とは異なるルールで色付けしている Semantic highlighting colors every variable with same color · Issue #323 · microsoft/pylance-release · GitHub ということが分かりました。 このセマンティックハイライトは、settingsのjsonに以下の記述を追加することで無効化できるようです。 (上記のGitHubのissuesのコメント参照) { "[python]": { "editor.semanticHighlighting.enabled": false } } 上記の設定追加後、馴染みの色に戻っていることが確認できました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ubuntuでpythonのバージョンを切り換える

pythonのバージョンを管理したり切り替える方法です 現在使う設定になっているpythonのバージョンを確認 $ python --version Python 2.7.17 確認すると、python3を使いたいのですがpython2系の設定になっているようです インストールされているpythonのバージョンと場所を確認 $ which python #すべてのpythonがインストールされている場所を確認 /usr/bin/python #/usr/bin以下にあるようです $ ls /usr/bin/ | grep python #/usr/bin/以下にインストールされている全pythonのバージョンを確認 python2 python2.7 python3 python3.6 python3.6-config python3-config update-alternativesが存在することを確認 update-alternativeはpythonのバージョン管理を行います。 $ which update-alternatives /usr/bin/update-alternatives pythonのバージョン登録・管理 登録されているpythonのバージョンや優先順位を見ることができます $ sudo update-alternatives --config python update-alternatives: error: no alternatives for python #まだpythonが登録されていない状態です pythonのバージョンを登録します sudo update-alternatives --install [pythonの場所] python [pythonの場所][バージョン] [優先順位の数字] 今回はpython3.6と2.7を登録してみます $ sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.6 1 #python3.6を優先順位1で登録 $ sudo update-alternatives --install /usr/bin/python python /usr/bin/python2.7 2 #python2.7を優先順位2で登録 登録されたpythonのバージョンやリストの順、優先順位を確認 $ update-alternatives --config python There are 2 choices for the alternative python (providing /usr/bin/python). Selection Path Priority Status ------------------------------------------------------------ 0 /usr/bin/python2.7 2 auto mode * 1 /usr/bin/python2.7 2 manual mode 2 /usr/bin/python3.6 1 manual mode Press <enter> to keep the current choice[*], or type selection number: 2 #python3.6を使用したいのでSelectionの2を入力 update-alternatives: using /usr/bin/python3.6 to provide /usr/bin/python (python) in manual mode $ python --version #使用する設定になっているpythonのバージョンを確認 Python 3.6.9 無事python3系に切り替えられました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像処理初心者がOpenCVを使ってみた(2)

この記事は、画像処理エンジニア検定を受験するにあたり、OpenCVを実際に利用して画像処理について学ぶことを目的としています。勉強した内容をメモします。 画僧処理初心者がOpenCVを使ってみた(1) の続きです。 ※画像処理でできることをメインに書いていくので、Pythonや使用する各関数の説明などは省略しています。 目次 エッジの検出 直線の検出 モルフォロジー演算 特徴抽出 顔検出 エッジの検出 エッジとは何かについては、以下のサイトで勉強させていただきました。 ソーベル法(Sobel)でエッジ検出 ここでは画像のX軸とY軸、それぞれについて画像のエッジ検出結果を見てみたいと思います。 import cv2 img = cv2.imread("data/src/favorite.jpg", 0) #用意した画像が大きいためリサイズして縮小させてます size = (300, 200) img = cv2.resize(img,size) img_sobelx = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize = 3) img_sobely = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize = 3) img_sobelx = cv2.convertScaleAbs(img_sobelx) img_sobely = cv2.convertScaleAbs(img_sobely) cv2.imshow("x",img_sobelx) cv2.imshow("y",img_sobely) cv2.waitKey(0) cv2.destroyAllWindows() 上記のコード実行結果は以下の画像のようになりました。 X軸とY軸でエッジの検出結果が違うことが分かります。 ラプラシアン法でエッジ検出 import cv2 img = cv2.imread("data/src/favorite.jpg", 0) #用意した画像が大きいためリサイズして縮小させてます size = (300, 200) img = cv2.resize(img,size) img_lap = cv2.Laplacian(img, cv2.CV_32F) img_lap = cv2.convertScaleAbs(img_lap) cv2.imshow("laplacian",img_lap) cv2.imshow("img",img) cv2.waitKey(0) cv2.destroyAllWindows() この方法では、画像の軸に関係なくエッジを検出します。 以下の画像が出力結果です。 さらにエッジ検出をわかりやすくするために、GaussianBlurを用いてラプラシアン法を適用してみました。 import cv2 img = cv2.imread("data/src/favorite.jpg", 0) #用意した画像が大きいためリサイズして縮小させてます size = (300, 200) img = cv2.resize(img,size) img_blur = cv2.GaussianBlur(img, (3,3), 2) img_lap = cv2.Laplacian(img_blur, cv2.CV_32F) img_lap = cv2.convertScaleAbs(img_lap) cv2.imshow("laplacian",img_lap) cv2.imshow("img",img) cv2.waitKey(0) cv2.destroyAllWindows() 上記のコードの実行結果は以下のようになりました。 上のラプラシアン法よりもノイズが少ないことが分かります。 キャニー法でエッジ検出 import cv2 img = cv2.imread("data/src/favorite.jpg", 0) #用意した画像が大きいためリサイズして縮小させてます size = (300, 200) img = cv2.resize(img,size) img_canny = cv2.Canny(img, 10, 200) cv2.imshow("canny",img_canny) cv2.imshow("img",img) cv2.waitKey(0) cv2.destroyAllWindows() 上記のコードを実行した結果が以下です。 cv2.Cannyの関数の閾値を変更してエッジの検出がどうなるか確認しました。 import cv2 img = cv2.imread("data/src/favorite.jpg", 0) #用意した画像が大きいためリサイズして縮小させてます size = (300, 200) img = cv2.resize(img,size) #引数でエッジの閾値を変更する img_canny = cv2.Canny(img, 100, 200) cv2.imshow("canny",img_canny) cv2.imshow("img",img) cv2.waitKey(0) cv2.destroyAllWindows() 上記のコードを実行した結果が以下です。上の例よりノイズが少ないことが分かります。 直線の検出 画像から直線を検出する方法を試しました。 import cv2 import numpy as np img = cv2.imread("data/src/favorite.jpg") img_gray = cv2.imread("data/src/favorite.jpg",0) #用意した画像が大きいためリサイズして縮小させてます size = (300, 200) img = cv2.resize(img,size) img_gray = cv2.resize(img_gray,size) img_canny = cv2.Canny(img_gray, 300, 400) lines = cv2.HoughLines(img_canny, 1, np.pi/180, 100) for i in lines[:]: rho = i[0][0] theta = i[0][1] a = np.cos(theta) b = np.sin(theta) x0 = rho * a y0 = rho * b x1 = int(x0 + 1000 * (-b)) y1 = int(y0 + 1000 * a) x2 = int(x0 - 1000 * (-b)) y2 = int(y0 - 1000 * a) cv2.line(img, (x1,y1), (x2, y2), (255,0,0), 1) cv2.imshow("canny",img_canny) cv2.imshow("img",img) cv2.waitKey(0) cv2.destroyAllWindows() 上記のコードを実行した結果、以下の画像が出力されました。直線部分が検出されていることが分かります。 モルフォロジー演算 画像ピクセルを収縮させたり、膨張させて画像内の図形を分離するとき、ノイズを消したりするときに使用します。 詳しくは以下のページを参考に勉強させていただきました。 以下のコードは、元画像(img)に対して、膨張と縮小を行った結果です。 import cv2 import numpy as np img = cv2.imread("data/src/favorite.jpg") #用意した画像が大きいためリサイズして縮小させてます size = (300, 200) img = cv2.resize(img,size) #画像の二値化 ret, img_th = cv2.threshold(img, 100, 255, cv2.THRESH_BINARY) kernel = np.ones((3, 3), dtype=np.uint8) #膨張 imf_d = cv2.dilate(img_th, kernel) #縮小 img_e = cv2.erode(img_th, kernel) cv2.imshow("img",img) cv2.imshow("imf_d",imf_d) cv2.imshow("img_e",img_e) cv2.waitKey(0) cv2.destroyAllWindows() 実行した結果が以下の画像です。膨張させた際は画像の白い部分を拾ってきていますが、縮小させた際は黒い部分を拾ってきていることが分かります。 次にクロージングをしてみます。 画像を膨張させた後に縮小を行ってみました。 以下が実行コードです。 #上のコードの続きです img_c = cv2.morphologyEx(img_th, cv2.MORPH_CLOSE, kernel) cv2.imshow("img",img) cv2.imshow("img_c",img_c) cv2.imshow("img_e",img_e) cv2.waitKey(0) cv2.destroyAllWindows() 上記のコードを実行した結果が以下の画像です。左のimg_c画像を膨張させたのち、収縮させました。元の画像に近い状態を保っていることが分かります。中央のimg_d画像は、膨張させただけの画像です。比較するために表示させてみました。 特徴抽出 特徴とはパターン認識に役立つ情報量の多い部分のことを指します。 詳しくまとまっている記事がありましたので、こちらで勉強させていただきました。 Harris ハリスのコーナー検出は、画像からコーナーを検出する手法のことです。 以下のコードでハリスの手法を用いてコーナーの特徴量を抽出しました。 import cv2 import numpy as np import copy img = cv2.imread("data/src/favorite.jpg") img_gray = cv2.imread("data/src/favorite.jpg",0) #用意した画像が大きいためリサイズして縮小させてます size = (300, 200) img = cv2.resize(img,size) img_gray = cv2.resize(img_gray,size) #元の画像が上書きされないようにコピーを作成しておきます img_harris = copy.deepcopy(img) img_dst = cv2.cornerHarris(img_gray, 2, 3, 0.05) #特徴量(コーナー)に赤色を付けます img_harris[img_dst > 0.05 * img_dst.max()] = [0, 0, 255] cv2.imshow("img",img) cv2.imshow("imf_harris",img_harris) cv2.waitKey(0) cv2.destroyAllWindows() 実行結果は以下の画像です。 コーナーの部分が赤色で表示されていることが分かります。 kaze こちらも特徴量を抽出するアルゴリズムです。 上と同様にコーナーの特徴量の抽出を行いました。 import cv2 import numpy as np import copy img = cv2.imread("data/src/favorite.jpg") img_gray = cv2.imread("data/src/favorite.jpg",0) #用意した画像が大きいためリサイズして縮小させてます size = (300, 200) img = cv2.resize(img,size) img_gray = cv2.resize(img_gray,size) #元の画像が上書きされないようにコピーを作成しておきます img_kaze = copy.deepcopy(img) kaze = cv2.KAZE_create() kp1 = kaze.detect(img, None) img_kaze = cv2.drawKeypoints(img_kaze, kp1, None) cv2.imshow("img",img) cv2.imshow("img_kaze",img_kaze) cv2.waitKey(0) cv2.destroyAllWindows() 上記のコードの実行結果は以下の画像です。 ORB こちらも特徴量を抽出するアルゴリズムです。 上と同様にコーナーの特徴量の抽出を行いました。 import cv2 import numpy as np import copy img = cv2.imread("data/src/favorite.jpg") img_gray = cv2.imread("data/src/favorite.jpg",0) #用意した画像が大きいためリサイズして縮小させてます size = (300, 200) img = cv2.resize(img,size) img_gray = cv2.resize(img_gray,size) #元の画像が上書きされないようにコピーを作成しておきます img_orb = copy.deepcopy(img) orb = cv2.ORB_create() kp2 = orb.detect(img_orb) img_orb = cv2.drawKeypoints(img_orb, kp2, None) cv2.imshow("img",img) cv2.imshow("img_orb",img_orb) cv2.waitKey(0) cv2.destroyAllWindows() 上記のコードを実行した結果が以下の画像です。kazeを使用したときより特徴量の検出が少ないのが分かります。 顔検出 OpenCVの中にすでに顔を検出するための検出器が用意されているので、それを用いて顔検出を行いました。 以下、顔検出するためコードです。 import cv2 #検出器のパスです HAAR_FILE = "C:/Users/natsumin/anaconda3/pkgs/libopencv-3.4.2-h20b85fd_0/Library/etc/haarcascades/haarcascade_frontalface_default.xml" cascade = cv2.CascadeClassifier(HAAR_FILE) img = cv2.imread("data/src/BTS.jpg") img_gray = cv2.imread("data/src/BTS.jpg",0) face = cascade.detectMultiScale(img_gray) #検出した顔部分に緑の枠を付けます for x, y, w, h in face: cv2.rectangle(img, (x, y),(x + w, y + h),(0, 255,0), 1) cv2.imshow("img",img) cv2.imshow("img_gray",img_gray) cv2.waitKey(0) cv2.destroyAllWindows() 上記の実行結果は以下の画像です。顔検出できた部分は緑の枠で囲まれています。RMさんだけ検出されませんでした。 BTSの新しいシングルのイメージ画像を使わせていただきました。 最後に まだまだ画像処理を理解するには道のりが長そうです。 引き続き勉強します。 間違いなどありましたら、ご指摘いただけると幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自分が書いたQiita記事まとめ

はじめに 私はプログラミング歴1年の初心者です。 実務でWebサイトのコーディングを1年間行ってきました。 そろそろシステム開発もできるようになりたいということで LaravelやReactをこれから勉強していこうと思っております。 この記事の目的 この記事をエントリーポイントにしようと思います。 自分が書いた記事をまとめておく記事です。 目次 HTML/CSS関係(初心者) Ruby関係(初心者) PHP関係(1年目以降〜) Python関係(1年目以降〜) JavaScript関係(1年目以降〜) リンク集 HTML/CSS関係(初心者) Ruby関係(初心者) Railsアプリ作成の記事(プログラミング学習を始めてから1ヶ月くらい?) PHP関係(1年目以降〜) Laravelの記事(就職後に勉強し始めたのである程度わかってきたくらい?) Python関係(1年目以降〜) Pythonの記事 Djangoの記事 JavaScript関係(1年目以降〜) パッケージ関係(1年目以降〜) Homebrewの記事 Composerの記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonを使ってTwitterに動画を簡単に投稿する方法

Tweepyは開発版しか動画投稿がサポートされていない これを使うと投稿できるらしいが、まだpipには無い。バージョンはpipで管理したいので色々調べたものをまとめる。 python-twitterを使って投稿する方法(おすすめ) Twitterのエンドポイントにライブラリを使わないでやる方法(ひたすらダルい) python-twitterを使う api.PostUpdate(status=text, media=mediaId)のmediaにmp4のアドレスを指定しても30秒までなら何故かいけた。 30秒以上の動画は分割して圧縮しなきゃならないらしい(後述処理でまんまそれをやっている)、python-twitterだと内部処理で行ってくれているらしい、非常にありがたい。 欲を言うならステータス確認的なコードが欲しかった。 import twitter # 認証 api = twitter.Api(consumer_key=consumer_key, consumer_secret=consumer_secret, access_token_key=access_key, access_token_secret=access_secret) # アップロード mediaId = api.UploadMediaChunked( media="./a.mp4", media_category="tweet_video") # 仕様です https://github.com/bear/python-twitter/issues/654 time.sleep(10) api.PostUpdate(status=text, media=mediaId) 地味にByteIO型も対応しているのでFaaSでも安心! from io import BytesIO import requests movieUrl = "http://~~~~~~.mp4" ioimg = requests.get(movieUrl) upimg = BytesIO(ioimg.content) upimg.mode = 'rb' upimg.name = 'mobie.mp4' # 可能 mediaId = api.UploadMediaChunked( media=upimg, media_category="tweet_video") 素でエンドポイントにPostする 解説動画はあったがコードが無かった。 別コードもある。 from requests_oauthlib import OAuth1Session import os # ツイート用エンドポイント twUrl = "https://api.twitter.com/1.1/statuses/update.json" # メディア用エンドポイント upUrl = "https://upload.twitter.com/1.1/media/upload.json" # 認証 twitter = OAuth1Session(consumer_key, consumer_secret, access_key, access_secret) # ローカルの動画 #ByteIO型は作りこみが必要 with open("./a.mp4", 'wb') as f: f.write(response.content) # サイズ指定が必要なので後で使う totalBytes = os.path.getsize("./a.mp4") # ID取得 initParams = { "command": "INIT", "media_type": "video/mp4", "total_bytes": totalBytes, "media_category": "tweet_video" } initResponse = twitter.post(url=upUrl, data=initParams) mediaId = initResponse.json()['media_id'] # 分割アップロード処理 segmentId = 0 bytesSent = 0 with upimg as f: while bytesSent < totalBytes: chunk = f.read(1*1024*1024) addParams = { "command": "APPEND", "media_id": mediaId, "segment_index": segmentId } files = { "media": chunk } appendResponse = twitter.post(url=upUrl, data=addParams, files=files) print(appendResponse) segmentId = segmentId + 1 bytesSent = f.tell() # バイナリモードの時にファイルの先頭からのバイト数を返却する print("%s of %s bytes uploaded" % (str(bytesSent), str(totalBytes))) print("upload complete") # ファイナライズ処理 finalizeParams = { "command": "FINALIZE", "media_id": mediaId } finalizeResponse = twitter.post(url=upUrl, data=finalizeParams) statusParams = { "command": "STATUS", "media_id": mediaId } statusResponse = twitter.get(url=upUrl, params=statusParams) print(statusResponse) processingInfo = statusResponse.json().get("processing_info", None) while processingInfo['state'] == 'in_progress': time.sleep(1) statusResponse = twitter.get(url=upUrl, params=statusParams) processingInfo = statusResponse.json().get("processing_info", None) print(processingInfo) # tweetする params = { "status": "動画ツイート", "media_ids": mediaId } twitter.post(url=twUrl, data=params) 悪魔が生まれる 悪用例はこちら すぐ消すbot
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonを使ってTwitterに動画を簡単に投稿する方法

Tweepyは開発版しか動画投稿がサポートされていない これを使うと投稿できるらしいが、まだpipには無い。バージョンはpipで管理したいので色々調べたものをまとめる。 python-twitterを使って投稿する方法(おすすめ) Twitterのエンドポイントにライブラリを使わないでやる方法(ひたすらダルい) python-twitterを使う api.PostUpdate(status=text, media=mediaId)のmediaにmp4のアドレスを指定しても30秒までなら何故かいけた。 30秒以上の動画は分割して圧縮しなきゃならないらしい(後述処理でまんまそれをやっている)、python-twitterだと内部処理で行ってくれているらしい、非常にありがたい。 欲を言うならステータス確認的なコードが欲しかった。 import twitter # 認証 api = twitter.Api(consumer_key=baka, consumer_secret=aho, access_token_key=manuke, access_token_secret=darazu) # アップロード mediaId = api.UploadMediaChunked( media="./a.mp4", media_category="tweet_video") # 仕様です https://github.com/bear/python-twitter/issues/654 time.sleep(10) # ツイート api.PostUpdate(status=text, media=mediaId) 地味にByteIO型も対応しているのでFaaSでも安心! from io import BytesIO import requests movieUrl = "http://~~~~~~.mp4" ioimg = requests.get(movieUrl) upimg = BytesIO(ioimg.content) upimg.mode = 'rb' upimg.name = 'mobie.mp4' # 可能 mediaId = api.UploadMediaChunked( media=upimg, media_category="tweet_video") 素でエンドポイントにPostする 解説動画はあったがコードが無かった。 別コードもある。 from requests_oauthlib import OAuth1Session import os # ツイート用エンドポイント twUrl = "https://api.twitter.com/1.1/statuses/update.json" # メディア用エンドポイント upUrl = "https://upload.twitter.com/1.1/media/upload.json" # 認証 twitter = OAuth1Session(consumer_key, consumer_secret, access_key, access_secret) # ローカルの動画 #ByteIO型は作りこみが必要 with open("./a.mp4", 'wb') as f: f.write(response.content) # サイズ指定が必要なので後で使う totalBytes = os.path.getsize("./a.mp4") # ID取得 initParams = { "command": "INIT", "media_type": "video/mp4", "total_bytes": totalBytes, "media_category": "tweet_video" } initResponse = twitter.post(url=upUrl, data=initParams) mediaId = initResponse.json()['media_id'] # 分割アップロード処理 segmentId = 0 bytesSent = 0 with upimg as f: while bytesSent < totalBytes: chunk = f.read(1*1024*1024) addParams = { "command": "APPEND", "media_id": mediaId, "segment_index": segmentId } files = { "media": chunk } appendResponse = twitter.post(url=upUrl, data=addParams, files=files) print(appendResponse) segmentId = segmentId + 1 bytesSent = f.tell() # バイナリモードの時にファイルの先頭からのバイト数を返却する print("%s of %s bytes uploaded" % (str(bytesSent), str(totalBytes))) print("upload complete") # ファイナライズ処理 finalizeParams = { "command": "FINALIZE", "media_id": mediaId } finalizeResponse = twitter.post(url=upUrl, data=finalizeParams) statusParams = { "command": "STATUS", "media_id": mediaId } statusResponse = twitter.get(url=upUrl, params=statusParams) print(statusResponse) processingInfo = statusResponse.json().get("processing_info", None) while processingInfo['state'] == 'in_progress': time.sleep(1) statusResponse = twitter.get(url=upUrl, params=statusParams) processingInfo = statusResponse.json().get("processing_info", None) print(processingInfo) # ツイートする params = { "status": "動画ツイート", "media_ids": mediaId } twitter.post(url=twUrl, data=params) 悪魔が生まれる 悪用例はこちら すぐ消すbot
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonを使ってTwitterに長い動画を簡単に投稿する方法

Tweepyは開発版しか動画投稿がサポートされていない これを使うと投稿できるらしいが、まだpipには無い。色々調べたら結果的に遠回りになったがTweepy以外のほうが楽そうだったので情報をまとめる。 python-twitterを使って投稿する方法(おすすめ) Twitterのエンドポイントにライブラリを使わないでやる方法(ひたすらダルい) python-twitterを使う api.PostUpdate(status=text, media=mediaId)のmediaにmp4のアドレスを指定しても30秒までなら何故かいけた。 30秒以上の動画は分割して圧縮しなきゃならないらしい(後述処理でまんまそれをやっている)、python-twitterだと内部処理で行ってくれているらしい、非常にありがたい。 欲を言うならステータス確認的なコードが欲しかった。 import twitter # 認証 api = twitter.Api(consumer_key=baka, consumer_secret=aho, access_token_key=manuke, access_token_secret=darazu) # アップロード mediaId = api.UploadMediaChunked( media="./a.mp4", media_category="tweet_video") # 仕様です https://github.com/bear/python-twitter/issues/654 time.sleep(10) # ツイート api.PostUpdate(status=text, media=mediaId) 地味にByteIO型も対応しているのでFaaSでも安心! from io import BytesIO import requests movieUrl = "http://~~~~~~.mp4" ioimg = requests.get(movieUrl) upimg = BytesIO(ioimg.content) upimg.mode = 'rb' upimg.name = 'mobie.mp4' # 可能 mediaId = api.UploadMediaChunked( media=upimg, media_category="tweet_video") 素でエンドポイントにPostする 解説動画はあったがコードが無かった。 別コードもある。 from requests_oauthlib import OAuth1Session import os # ツイート用エンドポイント twUrl = "https://api.twitter.com/1.1/statuses/update.json" # メディア用エンドポイント upUrl = "https://upload.twitter.com/1.1/media/upload.json" # 認証 twitter = OAuth1Session(consumer_key, consumer_secret, access_key, access_secret) # ローカルの動画 #ByteIO型は作りこみが必要 with open("./a.mp4", 'wb') as f: f.write(response.content) # サイズ指定が必要なので後で使う totalBytes = os.path.getsize("./a.mp4") # ID取得 initParams = { "command": "INIT", "media_type": "video/mp4", "total_bytes": totalBytes, "media_category": "tweet_video" } initResponse = twitter.post(url=upUrl, data=initParams) mediaId = initResponse.json()['media_id'] # 分割アップロード処理 segmentId = 0 bytesSent = 0 with upimg as f: while bytesSent < totalBytes: chunk = f.read(1*1024*1024) addParams = { "command": "APPEND", "media_id": mediaId, "segment_index": segmentId } files = { "media": chunk } appendResponse = twitter.post(url=upUrl, data=addParams, files=files) print(appendResponse) segmentId = segmentId + 1 bytesSent = f.tell() # バイナリモードの時にファイルの先頭からのバイト数を返却する print("%s of %s bytes uploaded" % (str(bytesSent), str(totalBytes))) print("upload complete") # ファイナライズ処理 finalizeParams = { "command": "FINALIZE", "media_id": mediaId } finalizeResponse = twitter.post(url=upUrl, data=finalizeParams) statusParams = { "command": "STATUS", "media_id": mediaId } statusResponse = twitter.get(url=upUrl, params=statusParams) print(statusResponse) processingInfo = statusResponse.json().get("processing_info", None) while processingInfo['state'] == 'in_progress': time.sleep(1) statusResponse = twitter.get(url=upUrl, params=statusParams) processingInfo = statusResponse.json().get("processing_info", None) print(processingInfo) # ツイートする params = { "status": "動画ツイート", "media_ids": mediaId } twitter.post(url=twUrl, data=params) 悪魔が生まれる 悪用例はこちら すぐ消すbot
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フィボナッチ数列の隣り合う2項の比率をグラフに表してみる

フィボナッチ数列とは フィボナッチ数列は数学Ⅲの漸化式でやりますよね。 $1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ...$ という具合に、初項が $F_1 = 1, F_2 = 1$ そして $\quad F_{n+2} = F_{n+1} + F_n \quad (n \geq 1)\quad $ という漸化式であらわされる数列です。 (0から始める場合もある) この数列はとても不思議な性質を持っていて、その一つに隣り合う2項の比 $\large \frac{F_n}{F_{n-1}}$ の極限が黄金比に収束するというもの。 つまり $$ \lim_{n \to \infty}\frac{F_n}{F_{n-1}} = 1.6180339887498948482...$$ となることを示しています。 今回は、それを完全な形ではありませんが、視覚的に表現してみようと思います。 Pythonでのグラフの描画 Pythonのグラフ描画モジュールの扱いに慣れていないため、コードが汚いですが気にしないでください。 python import matplotlib.pyplot as plt import numpy as np a = [1, 1] x = [] ratio_list = [] for i in range(0, 10): x.append(i+3) a.append(a[i] + a[i+1]) ratio = a[-1] / a[-2] ratio_list.append(ratio) # 漸近線の表示 y1 = 1.6180339887 plt.axhline(y=y1, xmin=0, xmax=13, c='blue', ls='--') # 比率のグラフ plt.xlim([2, 13]) plt.ylim([1.45, 2.05]) plt.xlabel('x') plt.ylabel('$\\frac{F_x}{F_{x-1}}$', rotation=0) plt.xticks(np.arange(3, i+4, 1)) plt.grid(which="major", color="black", alpha=0.5) plt.plot(x, ratio_list, c='red', marker="o") # 表示 plt.show() 下図の青色の破線が黄金比の近似値 $y = 1.6180339887$ を表しています 隣り合う2項の比が早い段階で黄金比へ収束していることがわかります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PS5が入荷したらLINEに通知してくれるプログラム書いた

はじめに こちらは在庫が復活したらLINEに通知してくれるプログラムです。 PS5ほしいけど、Twitterの速報に気づいた時には売り切れなんてことがありますよね。 店舗でもごく稀にゲリラで販売されるくらいで、かなり入手困難なPS5です。 そこで、転売ヤーに負けないくらいの速さで買おうと思って、本プログラムを書きました。転売ヤーもぜひ!笑 言語 ・python3系 やること 1 LINE Developerアカウントの作成。 2 スプレッドシートの設定。(無くても良い) 3 対象ページをスクレイピング(プログラムで実行) 4 欲しい値段だったらLINEに通知(プログラムで実行) 5 定期実行(プログラムで実行) 別のページでも応用できるので、やってみてください! 3,4,5の工程のコードは長いので、別ページで公開しております。 1 LINE Developerアカウントの作成 1-1 LINEビジネスアカウントの取得 こちらからアクセスして、自分が個人で使っているLINEでログインしてください。 上から順に入力していきます。 ・チャンネルの種類はMessaging API、プロバイダーは自分の好きな名前を新規作成してください。 ・アイコンも適当に設定します。 ・チャンネル名はbotの名前になりますので、「在庫復活」とか好きな名前を設定してください。 ・業種は自分について設定します。 1-2 ユーザーIDとトークンをプログラムにコピペする 設定が完了すると ・トップ→プロバイダ名→Messaging API設定→QRコードから友達に追加します。 ・登録が終わったら、あとはチャンネル基本設定の「あなたのユーザーID」とMessagin API設定の「チャンネルアクセストークン(長期)」をコピーして、プログラムの該当部分に貼り付ける。 注意「あなたのユーザーID」は「チャンネルID」ではありません。ページの下の方にあります。チャンネルアクセストークンもページの下の方にあり、めっちゃ長いやつです。 2 スプレッドシートの設定。 この工程はこちらの記事を参考にさせていただきました。 スプレッドシートの値をpythonのプログラム上で取得できればオッケーです。 また、商品IDと値段をプログラムに直書きする場合はこちらは不要です。PS51つだけであればスプレッドシートに記入する意味はあまりないかもしれません。 スプレッドシートは以下のように1列目にASIN(Amazonの商品ID)、2列目に価格、3列目に名前を設定します。 ASINというのはAmazonの商品IDです。別のページであれば、そのページの商品IDを使用します。 だいたいのECサイトはURLに商品IDがあって、"https:ABCDEF/商品ID"みたいに管理されています。 Amazonであれば、"https://www.amazon.co.jp/dp/商品ID"で管理されています。 先程のLINE Devolorsの設定と合わると以下のようなコードになります。 # スプレッドシートよりASINと価格を取得。 import gspread from oauth2client.service_account import ServiceAccountCredentials import time import requests import pandas as pd import re from bs4 import BeautifulSoup from linebot import LineBotApi from linebot.models import TextSendMessage from linebot.exceptions import LineBotApiError from datetime import datetime scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive'] #おまじない # 秘密鍵(JSONファイル)のファイル名を入力 credentials = ServiceAccountCredentials.from_json_keyfile_name('設定したJSONファイル', scope) gc = gspread.authorize(credentials) wb = gc.open('スプレッドシートの名前') SPREADSHEET_KEY = 'スプレッドシートのキーの名前' wb = gc.open_by_key(SPREADSHEET_KEY) ws = wb.get_worksheet(0) # 0番目のスプレッドシートの取得 ASIN = ws.col_values(1) # 1列目に欲しいASINを並べる。 price = ws.col_values(2) # 2列目にこの値段以下だったら通知して欲しい値段を設定。 LINE_ACCESS_TOKEN = "ご自身のLINE_ACCESS_TOKEN" LINE_USER_ID = "ユーザーID" line_bot_api = LineBotApi(LINE_ACCESS_TOKEN) これで前準備は終わりです。 3 対象ページをスクレイピング(プログラムで実行) おわりに参照。 4 欲しい値段だったらLINEに通知(プログラムで実行) おわりに参照。 5 プログラムの定期実行(プログラムで実行) これは色々やり方があると思います。 cronやタスクマネージャーで定期実行する手段もあると思いますが、私は外部サーバーを立ててwhile文で無限ループさせることで、常にプログラムが起動している状態です。 なぜかというと、通知してくれる商品がすぐ売り切れてくれればいいのですが、数時間在庫がある状態が続くと通知が毎回来ることになってしまいます。 よって、通知が来るとその商品の「通知count」をプラス1することで、カウントが2以上は通知が来ないように設定しました。このカウントは0時00分にリセットされます。 このツールのメリット ・E-mailではなくLINEに通知してくれるので早く気づくことが出来る。 ・少しコードを書き換えれば、好きなサイトを設定できる。 ・スクレイピングの勉強になる。このやり方でほとんどのページのスクレイピングができると思います。全プログラムを公開してます。 おわりに 最後までご覧いただきありがとうございました。 3,4,5の工程はこちらで詳しく解説しております。結構時間をかけて作ったので、有料にさせていただいておりますが、買ってくれた方はきっと役に立つと思います。スクレイピングする際のIP分散の仕方もそちらで記載しております。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pybind11 アプリを Windows 向けに cross-compile するメモ

python.org では .h/.lib だけのパッケージは配布されていないので, Wine で (Windows の) Python をインストールしているものとします. Wine での Python のありかのメモ(Python 使う app の cross-compile 用) https://qiita.com/syoyo/items/2c537d8b0f81be1ce4e1 ホストは Linux(WSL 含む)とします. llvm-mingw msvcrt と ucrt 版があります. ucrt 版のほうがいいのかしら? (cross-compile とかで VC 依存性減らしたい場合だと ucrt が推奨という理解でいいのかしら...?) Cmake 設定 SET(CMAKE_SYSTEM_NAME Windows) IF (DEFINED ENV{LLVM_MINGW_DIR}) SET(LLVM_MINGW_ROOT "$ENV{LLVM_MINGW_DIR}") ELSE () SET(LLVM_MINGW_ROOT "C:/ProgramData/llvm-mingw") ENDIF() SET(CMAKE_C_COMPILER ${LLVM_MINGW_ROOT}/bin/x86_64-w64-mingw32-clang) SET(CMAKE_CXX_COMPILER ${LLVM_MINGW_ROOT}/bin/x86_64-w64-mingw32-clang++) SET(CMAKE_RC_COMPILER ${LLVM_MINGW_ROOT}/bin/x86_64-w64-mingw32-windres) #SET(CMAKE_C_LINK_EXECUTABLE x86_64-w64-mingw32-gcc) #SET(CMAKE_CXX_LINK_EXECUTABLE x86_64-w64-mingw32-g++) SET(CMAKE_FIND_ROOT_PATH ${LLVM_MINGW_ROOT}/x86_64-w64-mingw32) # We need some advanced thread APIs, so use 0x601(Win7) # Python 3.9 だと Windows 7 対応は打ち切られたようなので, WINNT のバージョン上げたほうがいいかもです! SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_WIN32_WINNT=0x601") SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) こんな感じの toolchain ファイルを用意して, Python 関連の設定を cmake bootstrap で明示的に指定します. PYTHONLIBS_FOUND=On PYTHON_MODULE_EXTENSION を設定にすると, find_package(Python) 相当の処理では Python が見つかったとして find 処理をスキップしてくれます. (pybind11 の tools/FindPythonLibsNew.cmake 参照) curdir=`pwd` # Set path to llvm-mingw in env var. export LLVM_MINGW_DIR=$HOME/local/llvm-mingw-20210423-ucrt-ubuntu-18.04-x86_64/ builddir=${curdir}/build-llvm-mingw rm -rf ${builddir} mkdir ${builddir} # Manually set python include dir path and lib path. cd ${builddir} && cmake \ -DCMAKE_TOOLCHAIN_FILE=${curdir}/cmake/llvm-mingw-cross.cmake \ -DPYTHONLIBS_FOUND=On \ -DPYTHON_MODULE_EXTENSION=pyd \ -DPYBIND11_PYTHON_VERSION=3.9 \ -DPYTHON_INCLUDE_DIRS="$HOME/.wine/drive_c/users/$USER/Local Settings/Application Data/Programs/Python/Python39/include" \ -DPYTHON_LIBRARIES="$HOME/.wine/drive_c/users/$USER/Local Settings/Application Data/Programs/Python/Python39/libs/python39.lib" \ -G "Ninja" \ -DCMAKE_VERBOSE_MAKEFILE=1 \ .. これでいけるはずです! clang-cl or MSVC(cl.exe) T.B.W.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初学者なりにPythonのクラス定義を調べてみた

Pythonのクラス定義の特徴 自分はJavaからプログラム言語の世界に入ったため、静的型付け言語のJavaやC#の記述方法が好き。 そのため、Pythonの勉強を始めたときも、クラス定義はJavaのように、クラス宣言、フィールド、コンストラクタ、メソッドのように作ったりしていた。 ただ、当然ながらPythonはJavaとは違うので、適当に作るとうまく動かなかったり、ネットに転がっている既存のPythonのクラスを見てもいまいちよく分からない記述があったりで、理解するのになかなか苦労した。 というわけで本記事は、Pythonのクラス定義に関する自分向けもしくは初学者用の備忘録だ。 自分もPythonに触れ始めてまだ間もないため、誤っている点がいくつかあると思う... Pythonのクラス定義の基本 Pythonのクラス宣言は以下のようにやるらしい python class TestClass: pass tc = TestClass() passは何もしないということ tc = TestClass() は、Javaでいう java TestClass tc = new TestClass(); のようなことをしている。 インスタンス変数を定義することもできる ただし python class TestClass: val1 = "変数" tc = TestClass() print(tc.val1) 実行結果 変数 ちなみに注意が必要で このTestClassクラス直下に宣言されている変数val1はインスタンス変数ではない python class TestClass: val1 = "変数" tc = TestClass() print(tc.val1) tc.val1 = "変更してやったぜ" # tcオブジェクトのval1は変わったが、TestClassのクラス変数val1は変わらない print(tc.val1) print(TestClass.val1) 実行結果 変数 変更してやったぜ 変数 この変数はあくまでクラス変数であるため、定義する際には注意が必要だ。 Javaで例えるとstaticがつく感じだろうか java public class TestClass{ public static String val1 = "変数"; } Pythonのインスタンス変数、コンストラクタの定義 Pythonのクラスでインスタンス変数を使うには、コンストラクタの中で定義してやる必要がある。 そこでPythonのコンストラクタの定義方法についてだが、これもまたJavaとはだいぶ違う。 python class TestClass: def __init__(self): self.val1 = "変数" tc = TestClass() print(tc.val1) 実行結果 変数 Pythonでは、コンストラクタは python def __init__(self): # 処理 のように定義する。 Javaではコンストラクタは大文字から始まるクラスメイト同じ名称である必要があったため、ここも毛色が違う。 この__init__と名前の付いたメソッドは、クラスのインスタンスが作られたときにそのオブジェクト当たり一回だけ実行される(つまりコンストラクタ) このメソッドの引数にあるselfは、このクラスのインスタンス自身を指している。 Pythonのクラス内でメソッドを定義する際に、必ず引数が1つは必要というPythonの仕様がある。 この引数に渡されるものの正体はインスタンス自身であり、メソッドを呼び出すときに自動的に第一引数として渡される。 そのため定義側に引数を用意してやらないと {メソッド名} takes 0 positional arguments but 1 was given というエラーが発生する。このエラーの内容は メソッドに引数が定義されていないのに1つ渡されたぞオイ! みたいな感じ、自動的に渡されるため定義してやる必要があるね。 selfという引数名を付けるのは慣習だとかなんとか。 整合性が取れればhogeでもhugaでもargでもなんでもいいらしい。 また python def __init__(self): self.val1 = "変数" のように、インスタンス変数にもselfをつけてあげる必要がある これによって、val1というのがこのクラスのインスタンスの変数だよという事を表している。 なのでインスタンスが別のものであれば、このval1という変数もまた別に用意される。 コンストラクタの中で定義された変数は、インスタンス変数として使うことが出来る。 インスタンス変数を宣言するときに、代入なしで宣言できないものか?と思った java public class TestClass{ String val1; int val2; } Javaでは、上記のように変数を宣言だけして用意しておくことが出来るが、Pythonでは、変数の宣言は必ず代入をセットで行わないとならないようだ。 なので、これと似たような宣言方法をとりたい場合、Pythonではインスタンス変数にNoneを代入して初期化しながら用意しておくと良い(らしい) Pythonで使われるNoneは、Javaにおけるnullのように存在しないという意味合いです 厳密には他にも意味があるのですが、nullみたいなものと思えばよいです。(初学者だし) python class TestClass: def __init__(self): self.val1 = None self.val2 = None tc = TestClass() print(tc.val1) tc.val1 = "変数1" tc.val2 = "変数2" print(tc.val1 + "と" + tc.val2) 実行結果 None 変数1と変数2 Pythonのメソッドの定義 もう既にコンストラクタの定義の部分で触れてはいるが、あえて分けた。 といってもPythonではあくまでどちらもメソッドとして構造は全く同じため、あまり気にする必要はない。 ただ、Javaを学ぶ上ではコンストラクタとメソッドって結構しっかり分けられてた印象があったため、こちらでもそれに従った。 python class TestClass: def __init__(self): self.val1 = None self.val2 = None def method1(self): print("method1が呼び出されました") def method2(self): print("val1 = " + self.val1 + "\nval2 = " + self.val2) tc = TestClass() tc.val1 = "変数1" tc.val2 = "変数2" tc.method1() tc.method2() 実行結果 method1が呼び出されました val1 = 変数1 val2 = 変数2 このように、コンストラクタと同じように定義する。 違う点は、コンストラクタはインスタンスが生成されたときに自動的に一度だけ呼び出されるのに対して、通常のメソッドは明示的に呼び出す必要がある。 また、戻り値のあるメソッドに関しては、return文を用意するだけで良い。 python class TestClass: def __init__(self): self.val1 = None self.val2 = None def method1(self): return self.val1 + self.val2 tc = TestClass() # 文字列を渡した場合 tc.val1 = "変数1" tc.val2 = "変数2" print(tc.method1()) # 数値を渡した場合 tc.val1 = 2 tc.val2 = 5 print(tc.method1()) 実行結果 変数1変数2 7 ここでJavaと違う点は、メソッドの戻り値をカンマ区切りで複数用意することが出来る。 python class TestClass: def __init__(self): self.val1 = None self.val2 = None def method1(self): return self.val1, self.val2, self.val1 + self.val2 tc = TestClass() tc.val1 = 3 tc.val2 = 5 result = tc.method1() # この状態だとtuple型のデータが返される print(result) # tuple型として返された複数の戻り値は要素番号で個別に呼び出すことが出来る print(result[0]) print(result[1]) print(result[2]) 実行結果 (3, 5, 8) 3 5 8 カンマで区切られた戻り値は、タプル型として返される。 Javaでは配列やリスト、マップで返していたのに比較すると、直感的でわかりやすいと思う。 if __name__ == "__main__": とは Pythonで書かれたクラスのコードを見ると度々出会うこの謎の条件文 この条件文のブロックの中にメインの処理が書かれている場合が多い。 Pythonは実行させたいメインの処理をグローバルな場所に記述しても動く。 しかし、グローバルな場所に記述した場合、そのクラスをimportした時も勝手に処理が呼び出されてしまう。 if __name__ == "__main__": という条件文のブロック内にメインの処理を記述すると、そのクラスを実行したときのみ処理が呼び出されるようにできる。 Javaでいうと java public static void main(String[] args){} おなじみのメインメソッドのようなものと思ってもらえればいい。 class_test1.py class TestClass: def __init__(self): self.val1 = None self.val2 = None def pow_numbers(self): return self.val1 ** self.val2 if __name__ == "__main__": tc = TestClass() tc.val1 = 5 tc.val2 = 3 print(tc.pow_numbers()) 実行結果 125 多くのサイトやブログでは、この条件文をおまじないのようなものとして説明していることが多い。 実際Javaのメインメソッドも、しばらくはおまじないとして書かされがちだ。 しかしPythonのこれは、あくまで条件文 __name__ と "__main__" さえ分かれば意味も理解できそうだ。 __name__ とはそもそも何か 下記の結果を見ればわかりやすいと思う class_test1.py import numpy as np print(__name__) print(np.__name__) 実行結果 __main__ numpy __name__の正体は、Pythonがコードを実行するときに自動的に生成するグローバル変数の一つ 中には、モジュール名が格納される。つまり、importされたモジュールの__name__には、そのモジュール名が入る しかし、プログラムを実行しているPythonスクリプトは、自動的に__main__というモジュール名で識別される。 要するに、if __name__ == "__main__":という条件文は このスクリプトが実行元である場合、という意味合いになる。 別のスクリプトファイルでclass_test1.pyをインポートしてモジュールとして使う時は、この条件文のブロック内の処理は行われない。 Pythonのクラス定義のおさらい 今回はPythonのクラスを構成するインスタンス変数、コンストラクタ、メソッドについてまとめてみた。 これらを理解すると、下記のようにある程度自由にクラスを定義することが出来る。 class_test2.py class TestClass1: def __init__(self): self.array = [] self.num1 = None self.num2 = None self.str1 = None self.str2 = None def set_numbers(self, num1, num2): self.num1 = num1 self.num2 = num2 def pow_numbers(self): return self.num1 ** self.num2 def set_strings(self, str1, str2): self.str1 = str1 self.str2 = str2 # このclass_test.pyが実行されたときに動く if __name__ == '__main__': # 上に定義したTestClass1クラスをインスタンス化 tc1 = TestClass1() # set_numbers()メソッドで数値をインスタンス変数にセット # sum_numbers()メソッドがインスタンス変数の数値同士を冪乗して戻す tc1.set_numbers(2, 6) sum_num = tc1.pow_numbers() print(str(tc1.num1) + "の" + str(tc1.num2) + "乗は" + str(sum_num) + "です") # set_strings()メソッドで文字列をインスタンス変数にセット tc1.set_strings("こんにちは", "今日はいい天気ですね") print(tc1.str1 + "、" + tc1.str2) # インスタンス変数arrayに1から25までの奇数を順番に追加 for i in range(1, 27, 2): tc1.array.append(i) print(tc1.array) # 一つ飛ばしで要素を削除 last_index = len(tc1.array) count = int(last_index / 2) for i in range(count): tc1.array.pop(i + 1) print(tc1.array) # arrayの中身をクリア tc1.array.clear() print(tc1.array) 実行結果 2の6乗は64です こんにちは、今日はいい天気ですね [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25] [1, 5, 9, 13, 17, 21, 25] [] 自分がまだまだPythonの勉強を始めたばかりのため、色々と誤った認識のままでいる点等あると思う 今後新たな発見があったり、修正点を見つけたりしたら、その都度またまとめていこうと思う。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Django アプリを AWS EC2 にデプロイ

はじめに フロントエンド React + バックエンド Django REST Framework でアプリを作成したが、ローカル環境でしか動かすことができないので、AWS EC2 インスタンス上にデプロイして外部からもアクセスができるようにする。React アプリを AWS EC2 にデプロイにて、フロントエンド側をデプロイしたので、今回はバックエンドである DRF API をデプロイすることとし、Web サーバーソフトウェアには、nginx を用いる。なお DRF API はすでに作成されていることを前提とする。【丁寧解説】秒速でもDjango 3アプリをAWS EC2で公開【Nginx, gunicorn, postgresqlデプロイ】を参考にした。 GitHub への push デプロイ前にコードを GitHub へ push する。DB との接続情報など機密情報があるので、(レポジトリ自体を private にしているが念のため)あらかじめ別ファイルとして GitHub 上へはあげないようにする。まず環境変数の管理を行う django-environ パッケージをインストールする。 $ pip install django-environ [Django] django-environで環境変数を管理してみる djangoを参考に .env ファイルを作成する。ここでは SECRET_KEY, DEBUG, DATABASE_URL の3項目を記述している。.env ファイルは GitHub 上にあげないように、以下のような .gitignore ファイルも合わせて作成する。 .gitignore .env 次に .env ファイルに分けた情報を settings.py にて読み込むために以下のようにコードを修正する。 settings.py import environ import os env = environ.Env() env.read_env(os.path.join(BASE_DIR, '.env')) SECRET_KEY = env('SECRET_KEY') DEBUG = env('DEBUG') DATABASES = { 'default': env.db(), } 以下コマンドで、必要なライブラリを requirements.txt に書き出す。 $ pip freeze > requirements.txt $ sudo apt update $ sudo apt install python3-pip $ pip install --upgrade pip $ pip install -r requirements.txt 上記までを実行したら、GitHub のリモートレポジトリに push する。 EC2 インスタンスの作成、nginx のインストールおよびセットアップ 基本的には React アプリを AWS EC2 にデプロイと同様に実行するだけなので、ここでは割愛。 DRF API の起動 GitHub に push されているアプリをクローンして起動する。まず git をインストールして、レポジトリをクローンする。 $ sudo yum -y install git $ git clone https://github.com/<your repository> Django アプリが動作する環境を作成するため、Python3 およびパッケージ管理ツールである pip をインストールする。 $ sudo apt update $ sudo apt install python3 python3-pip アプリに必要なライブラリをインストールして、起動する。 $ cd <your repository> $ pip install -r requirements.txt $ python3 manage.py runserver 以上で、Django アプリが起動できる。 おわりに 1つのサーバーに1つのアプリをデプロイすることができた。nginx について理解を深め、より複雑な設定等も扱えるようにしたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Selenium Python クロームドライバー(chromedriver.exe)の自動インストール

環境:WIndows10 pythonバージョン:3.8.32 chromedriver.exeの自動インストール(更新も自動) 以下の本にchromedriver.exeの自動インストールの方法があったので自分の環境に合わせてメモします。↓ chromedriver.exeにパスが通ってなくても、プログラムをexeファイル化しても、自動でインストールされました。 スクレイピング・ハッキングラボ:アマゾンのページ webdriver_manager webdriver_manager webdriver_managerを使うとchromedriver.exeを自動でインストールしてくれます。 chromedriver.exeを自動でインストールしてヤフーに接続するサンプル python from selenium import webdriver from selenium.webdriver.chrome.options import Options from webdriver_manager.chrome import ChromeDriverManager import time options = Options() options.add_argument('--hide-scrollbars') options.add_argument('--incognito')  #シークレットモードのオプション #旧 pythonの実行ファイルと同じ場所に chromedriver.exeを設置してた。 #file_path = os.path.join(os.path.dirname(sys.argv[0]), "chromedriver.exe")  #driver = webdriver.Chrome(options=options, executable_path=file_path) #新  わざわざchromedriver.exeを設置しなくても自動でインストール更新されます。 driver=webdriver.Chrome(ChromeDriverManager().install(),options=options) driver.get("http://yahoo.co.jp") time.sleep(5) driver.quit() 上のソースを実行するにはwebdriver_managerのインストールが必要です。 自分のWindwos10環境の場合 py -m pip で webdriver_managerをインストール pip py -m pip install webdriver_manager Selenium+クロームドライバーで作業していて必ず必要なのがchromedriver.exeの変更やインストール。 3か月に1回程度は更新が必要なようで、スクレイピングをタイマー起動しても、エラーで止まってしまうことしばしば。コピーして別のPCで使うにもchromedriver.exeがなくて、とまったりということがなくなる予定です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoで文字置換サービスを作ってみる

formをカスタマイズして文字置換サービスを作ってみるに挑戦してみます。 まずはプロジェクトとアプリを作成 ドメインの許可 全体設定pj_form/settings.pyの28行目を以下のように修正。 ※これを追加しないとブラウザで開いた時に、「このドメインではアクセス出来ません。」というエラーが出ます。 ALLOWED_HOSTS = ["*"] アプリを追加 同じく全体設定33行目のINSTALLED_APPSの配列の最後にtext_editアプリを追加。 ※これを追加しないとdjangoがアプリのことを知らないので、「テンプレートが見つかりません。」というエラーが出ます。 'text_edit', ベースのテンプレートの作成 前回と同じくBootstrap 4を使っています。 text_edit/templates/base.html <!doctype HTML> <html> <head> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script> </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <a class="navbar-brand" href="/">テキスト置換</a> </nav> <div class="container mt-4"> {% block main %} <p>※コンテンツがありません。</p> {% endblock %} </div> </body> </html> FormViewを使って簡単にフォームを表示してみよう フォームを追加 text_edit/forms.py (新規作成) from django import forms class TextForm(forms.Form): text = forms.CharField() search = forms.CharField() replace = forms.CharField() *フォームの役割 ・表示させる ・バリデーションをかける ・OKなら処理をする らしい。。です。。。バリデーションもかけられるのかぁ(~O~;) モデルの作り方と似ていますが、これはあくまでフォームの表示用途だけです。 DBに保存はされません。 CharFieldでテキストを受け取るフォームが作れます。 他にもモデルと同様にIntegerField、DateTimeFieldなど多くのフィールドがデフォルトで用意されています。 ↑にゃるほど〜。。。 ビューを追加 from django.views.generic.edit import FormView from . import forms class Index(FormView): form_class = forms.TextForm template_name = "index.html" 前回出てきたCreateFormと似ています。 CreateFormはフォームを送信すると自動でDBにデータを保存してくれました。 一方でFormViewは勝手にDBに保存はしません。 ユーザーから入力は受け取れますが、それをどう処理するかは自由にカスタマイズ出来ます。 template_nameは自分で作成する必要がある。 ↑なるほど、、表示だけしたい場合とかに有効だね。 テンプレートを追加 text_edit/templates/index.html {% extends "base.html" %} {% block main %} <form method="post"> {% csrf_token %} {{ form.as_p }} <input type="submit" value="変換" class="btn btn-info" /> </form> {% endblock %} {% csrf_token %} - djangoでフォームを使うときのおまじないです。 {{ form.as_p }} - フォームが表示されます。 URLを設定 pj_form/urls.pyを以下のように修正。 from django.urls import path from text_edit import views urlpatterns = [ path("", views.Index.as_view(), name="index"), ] 実はアプリのURL設定ではなく、全体のURL設定に直接追加することも出来ます。 今回のような小さなプロジェクトではこちらの方が簡単です。 表示してみよう サーバーを起動して、トップページにアクセスしてフォームが表示されていたらOKです。 サーバーの起動 python manage.py runserver すいませんが、私はこの通りやったらエラーがでて表示できなくて 従来のやり方にしました。 プロジェクトの方のurls アプリのurls そしてランサーバーで なんとか表示できましたよ〜(´;ω;`) 諦めかけた・・・ フォームのデータを処理しよう 実際にテキストを置換してユーザーに表示してみましょう。 ビューにフォームの処理を追加 text_edit/views.pyのIndexクラスに以下を追記。 # フォームの入力にエラーが無かった場合に呼ばれます def form_valid(self, form): # form.cleaned_dataにフォームの入力内容が入っています data = form.cleaned_data text = data["text"] search = data["search"] replace = data["replace"] # ここで変換 new_text = text.replace(search, replace) # テンプレートに渡す ctxt = self.get_context_data(new_text=new_text, form=form) return self.render_to_response(ctxt) テンプレートに変換後のテキストを表示 text_edit/templates/index.htmlを以下に変更。 {% extends "base.html" %} {% block main %} <div class="row"> <div class="col-sm"> <h2>変換前</h2> <form method="post"> {% csrf_token %} {{ form.as_p }} <input type="submit" value="変換" class="btn btn-info" /> </form> </div> <div class="col-sm"> <h2>変換後</h2> <p>{{ new_text }}</p> </div> </div> {% endblock %} rowやcol-smというのはBootstrapで簡単に2カラムのページを作れるクラスです {{ new_text }}に変換後のテキストが渡されます 実際にブラウザで確認 お〜!!!!感動? ここで終わりじゃなのね〜 バリデーション(入力エラーチェック)を追加しよう テキストが5文字以下の場合はエラーとしましょう。 フォームにバリデーションを追加 text_edit/forms.pyにValidationErrorのインポートを追加。 from django.core.exceptions import ValidationError このエラーはフォームで自動的に扱うことが出来て便利です。 TextFormクラスに以下を追加。 # 自動的に呼ばれます。エラーを発生させると簡単に表示できます def clean(self): # djangoもともとのバリデーションを実行してデータを取得 data = super().clean() text = data["text"] if len(text) <= 5: raise ValidationError("テキストが短すぎます。") # 最後は必ずデータ全体を返します return data テンプレート text_edit/templates/index.htmlの本文の一番上に以下を追加。 {% for error in form.non_field_errors %} <div class="alert alert-danger" role="alert"> {{ error }} </div> {% endfor %} そして確認 でました〜エラー!! 2つでてますが、あとで消します。 フォームの見た目を整えよう ラベルを変更しよう text_edit/forms.pyのTextFormクラスのフィールドを以下のように変更。 text = forms.CharField(label="") search = forms.CharField(label="検索") replace = forms.CharField(label="置換") 長文テキストを入力できるようにしよう 同じくTextFormのtextフィールドを以下のように変更。 text = forms.CharField(widget=forms.Textarea, label="") 入力するデータの種類は同じでフォームの見た目だけを変えるときにはwidgetを使用します。 お〜!!!変わったぞ〜!! Bootstrapのクラスを追加して見た目をおしゃれにしよう Bootstrapのクラスを設定してみましょう。 djangoで作ったフォームに独自のHTMLを設定するのには、独自のwidgetを作成します。 ウィジェットの作成 text_edit/forms.pyのTextFormの上に以下を追加。 widget_textarea = forms.Textarea( attrs={ "class": "form-control" } ) widget_textinput = forms.TextInput( attrs={ "class": "form-control" } ) それぞれ独自のクラスform-controlを追加したテキストエリアとテキストインプットです。 この方法を知っておけば、readonlyをつけて編集不可にしたりと色々なことが出来ます。 フォームに適用 TextFormの各フィールドに先程作ったウィジェットを使うように設定しましょう。 text = forms.CharField(label="", widget=widget_textarea) search = forms.CharField(label="検索", widget=widget_textinput) replace = forms.CharField(label="置換", widget=widget_textinput) すると・・・ ちょっとかこよくなりましたね〜。 余分に出ているエラーを非表示にしよう text_edit/templates/index.htmlの{{ form.as_p }}を以下のように変更します。 {% for field in form %} <p> <label>{{ field.label }}</label> {{ field }} </p> {% endfor %} forループを使うことでフォームのフィールドを1つずつ表示することが出来ます。 field.label - ラベル field - 本体 field.errors - エラー 今回はfield.errorsを書かないことでエラーを非表示にしています。 表示を確認しよう 変更したところの見た目が変わっていたらOKです。 変わってました〜!! よかたぁ〜!! おまけ:ModelFormでモデルからフォームを自動生成しよう モデルからフォームを自動で作成することが出来ます。 前回使ったCreateViewでフォームをカスタマイズしたい時に便利な方法です。 ベースとなるモデルを作成 text_edit/models.pyにPostモデルを追加 class Post(models.Model): title = models.CharField(max_length=255) body = models.TextField() Postモデルを使ってPost用のフォームを自動生成 text_edit/forms.pyの最後に以下を追加。 from .models import Post class PostForm(forms.ModelForm): class Meta: model = Post fields = ["title", "body"] ModelFormを使うことで、モデルと編集可能なフィールドを指定するだけでフォームが作成出来ます。 ビューに適用 ここでは表示確認のために先程作成したIndexのフォームを変えてみましょう。 text_edit/views.pyのform_classをforms.PostFormに変更します。 form_class = forms.PostForm 表示を確認しよう トップページにアクセスして、PostモデルがそのままフォームになっていればOKです。 なってました〜!!ずぅ〜とimportエラーがでていて諦めかけておりましたが。。 表示できました(´;ω;`) 何が原因だったかは不明・・・ 今回はimportエラーに泣かされながらもなんとかできました!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む