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

Djangoチュートリアル⑦(静的ファイル)

はじめに Djangoチュートリアル⑥(自動テスト)に続いて、はじめての Django アプリ作成、その 6に沿ってチュートリアルを進めていく。チュートリアルだけでは理解できないので、現場で使える Django の教科書(基礎編)も読みながら進める。 静的ファイル Web アプリケーションでは完全な Web ページをレンダリングするために、画像、JavaScript、CSS など必要なファイルをリクエストに応じて中身を変更することなく提供する場合がある。Django では、これらのファイルを "静的 (static) ファイル" と呼ぶ。 静的ファイルのプロジェクト構成 チュートリアルでは、各アプリケーション単位で静的ファイルディレクトリ static を作成している。具体的には、polls/static/polls ディレクトリを作成し、style.css などの使用する静的ファイルを置いていく。しかし、Djangoチュートリアル④(テンプレート)でテンプレートのプロジェクト構成に関して述べた理由と同じ理由で、この構成はあまり適切でない。そこで、ここではベースディレクトリ直下に static ディレクトリを作成し、そのサブディレクトリとして各アプリケーション単位のテンプレートを扱うようにした。実際の構成を以下に示す。 config/settings.py mysite (<- ベースディレクトリ) |-- manage.py |-- config (<- 設定ディレクトリ) | |-- __init__.py | |-- asgi.py | |-- settings.py | |-- urls.py | `-- wsgi.py |-- polls (<- アプリケーション) | |-- __init__.py | |-- admin.py | |-- apps.py | |-- migrations | | `-- __init__.py | |-- models.py | |-- tests.py | `-- views.py |-- templates (<- テンプレート) | `-- polls | `-- index.html `-- static (<- 静的ファイル) `-- polls `--style.css 上記構成にすることで、わかりやすく、かつ各アプリケーションの静的ファイルをベースディレクトリから一元管理することができる。この構成で静的ファイルを扱うために、config/settings.py でいくつか設定を行う。まずベースディレクトリやプロジェクト名を設定する。PROJECT_NAME 以外はデフォルトで作成されているので、PROJECT_NAME のみ追記すればよい。 config/settings.py import os from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent PROJECT_NAME = BASE_DIR.name ### 以下の書き方でもよい # BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # PROJECT_NAME = os.path.basename(BASE_DIR) BASE_DIR は Path(config/settings.py).resolve() で実行ファイルの絶対パスを取得し、parent でその親ディレクトリを取得している。つまり、プロジェクト名である mysite までの絶対パスを設定している。PROJECT_NAME は BASE_DIR のディレクトリ名 mysite のみを取得している。 続いて、静的ファイルに関する設定を行う。具体的には STATIC_URL はデフォルトのままで、STATICFILES_DIRS と STATIC_ROOT を新たに追加する。 config/settings.py # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.1/howto/static-files/ STATIC_URL = '/static/' STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] STATIC_ROOT = '/var/www/{}/static'.format(PROJECT_NAME) 上記コードで設定した項目の意味は以下である。 STATIC_URL:静的ファイルの配信用のディレクトリで、URL の一部になる。 STATICFILES_DIRS:アプリケーションに紐付かない静的ファイルを置くディレクトリ。 STATIC_ROOT:静的ファイルの配信元。静的ファイルの集約用のコマンド collectstatic を実行時に静的ファイルを集約する際のコピー先。詳細は後述。 STATICFILES_DIRS を上記のように指定することで、ベースディレクトリである mysite 以下に作成した static ディレクトリ内の静的ファイルを探索するようになる。 STATIC_ROOT は DEBUG が FALSE 、つまり開発環境ではなく本番環境で動作させることを想定した設定である。DEBUG が FALSE のときには、自動配信がされないため静的ファイルを自前で配信するために集約させる必要があり、その配信元が STATIC_ROOT である。 Web アプリケーションでは慣例的に /var/www 以下に配信用のファイルを置くらしく、上記のような設定としている。(Web アプリケーション開発未経験のため知らず。。。) DEBUG が TRUE のときには、開発用 Web サーバーがベースディレクトリ直下の static ディレクトリなどから静的ファイルを自動配信してくれるためこの設定は必要ない。 また詳細はあまり理解できていないが、config/settings.py の INSTALLED_APPS にデフォルトで設定されている django.contrib.staticfiles が、上記で行った各種設定をもとに静的ファイルの管理をしてくれているらしい。 アプリのカスタマイズ 上記のプロジェクト構成としたのち、static/polls/style.css を作成して以下コードを書く。 static/polls/style.css li a { color: green; } さらに templates/polls/index.html の上部に以下コードを追記する。 templates/polls/index.html {% load static %} <link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}"> {% static %} テンプレートタグは、静的ファイルの配信 URL を取得するためのタグである。上記コードの {% static 'polls/style.css' %} で先ほど作成した static/polls/style.css を参照している。static タグは Django デフォルトで使えるタグではないため、{% load static %} でロードしている。 上記コードを記入後、$ python manage.py runserver で開発用サーバーを起動して、http://localhost:8000/polls/ にアクセスすると、スタイルシートが読み込まれていることを確認できる。 背景画像の追加 次に、画像のためのサブディレクトリを作る。images サブディレクトリを static/polls ディレクトリの中に作成して、このディレクトリの中に、background.gif を置く。つまり、画像は static/polls/images/background.gif に置かれる。 さらに、static/polls/style.css に次のコードを追加する。 static/polls/style.css body { background: white url("images/background.gif") no-repeat; } http://localhost:8000/polls/ をリロードすると、画像が表示されることを確認できる。 STATIC_ROOT への集約 本番環境での実行は現状できないが、設定した STATIC_ROOT へ静的ファイルの集約を行う collectstatic コマンドだけでも実行してみる。(sudo を付けない場合、PermissionError: [Errno 13] Permission denied: '/var/www/mysite' が出る。) $ cd mysite $ sudo python3 manage.py collectstatic 上記実行後、STATIC_ROOT に設定した /var/www/mysite/static/polls に作成した静的ファイルがコピーされていることが確認できる。その他にも admin というディレクトリもあり、いくつか静的ファイルがコピーされていた。 おわりに 本番環境などについて現場で使える Django の教科書(基礎編)を参考に書いたが、自身で試せていないので実施して書きたい。引き続き、はじめての Django アプリ作成、その 7を進めていく。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

janomeで形態素解析をするPython

janomeの使い方と内蔵している辞書へのアクセスに興味があったのでまとめました。 形態素解析という言葉に馴染みがない程度の知識水準なので記事にするのが恐縮ですが、ざっくりいうと日本語を品詞に分割するやつです。そんな人でもすぐ使える手軽さです。 janome 品詞に分割する機能はTokenizerです。これを何度も読み込むと処理が重くなるだけなので、プログラムの冒頭に1回だけやればよいです。 from janome.tokenizer import Tokenizer t = Tokenizer() すぐできる形態素解析 [e.surface for e in t.tokenize('吾輩は猫である。名前はまだニャイ。')] ['吾輩', 'は', '猫', 'で', 'ある', '。', '名前', 'は', 'まだ', 'ニャイ', '。'] [e.surface for e in t.tokenize('坊主が屏風に上手にジョーズの絵を描いた')] ['坊主', 'が', '屏風', 'に', '上手', 'に', 'ジョーズ', 'の', '絵', 'を', '描い', 'た'] [e.surface for e in t.tokenize('この竹垣に竹立てかけたのは竹立てかけたかったから竹立てかけた')] ['この', '竹垣', 'に', '竹', '立てかけ', 'た', 'の', 'は', '竹', '立てかけ', 'たかっ', 'た', 'から', '竹', '立てかけ', 'た'] 辞書をチラミする import janome type(janome.sysdic.entries()) dict このdict型のデータがjanomeの辞書の本体です。 {type(e) for e in list(janome.sysdic.entries())} {int} ということで、このdictのキーはすべて整数でできています。 len(janome.sysdic.entries()) 392127 多いのか少ないのか私にはよくわからないエントリ数です。(日本語ってそんな単語数しかないんでしょうか。もっとありそうな気がしてました。) set(list(janome.sysdic.entries())) == set(range(392127)) True ということで、抜けのない0から392126までの整数がキーになっていることが分かります。 辞書の先頭?(キーが0)にアクセスするには次のようにします。 janome.sysdic.entries()[0] ('恐るべき', 1315, 1315, 4877, '連体詞,*,*,*', '*', '*', '恐るべき', 'オソルベキ', 'オソルベキ') 一体どういう基準でこの並び順になっているのかはよく分かりませんが、先頭(キーが0)は「恐るべき」です。 タプルの(0番目から数えて)4番目が品詞の名称のようなので、どんな分類なのか見てみます。重複を排除するにはset型(集合型)を返す内包表記にするだけです。 {e[4] for e in janome.sysdic.entries().values()} {'その他,間投,*,*', 'フィラー,*,*,*', '副詞,一般,*,*', '副詞,助詞類接続,*,*', '助動詞,*,*,*', '助詞,並立助詞,*,*', '助詞,係助詞,*,*', '助詞,副助詞,*,*', '助詞,副助詞/並立助詞/終助詞,*,*', '助詞,副詞化,*,*', '助詞,接続助詞,*,*', '助詞,格助詞,一般,*', '助詞,格助詞,引用,*', '助詞,格助詞,連語,*', '助詞,特殊,*,*', '助詞,終助詞,*,*', '助詞,連体化,*,*', '動詞,接尾,*,*', '動詞,自立,*,*', '動詞,非自立,*,*', '名詞,サ変接続,*,*', '名詞,ナイ形容詞語幹,*,*', '名詞,一般,*,*', '名詞,代名詞,一般,*', '名詞,代名詞,縮約,*', '名詞,副詞可能,*,*', '名詞,動詞非自立的,*,*', '名詞,固有名詞,一般,*', '名詞,固有名詞,人名,一般', '名詞,固有名詞,人名,名', '名詞,固有名詞,人名,姓', '名詞,固有名詞,地域,一般', '名詞,固有名詞,地域,国', '名詞,固有名詞,組織,*', '名詞,引用文字列,*,*', '名詞,形容動詞語幹,*,*', '名詞,接尾,サ変接続,*', '名詞,接尾,一般,*', '名詞,接尾,人名,*', '名詞,接尾,副詞可能,*', '名詞,接尾,助動詞語幹,*', '名詞,接尾,助数詞,*', '名詞,接尾,地域,*', '名詞,接尾,形容動詞語幹,*', '名詞,接尾,特殊,*', '名詞,接続詞的,*,*', '名詞,数,*,*', '名詞,特殊,助動詞語幹,*', '名詞,非自立,一般,*', '名詞,非自立,副詞可能,*', '名詞,非自立,助動詞語幹,*', '名詞,非自立,形容動詞語幹,*', '形容詞,接尾,*,*', '形容詞,自立,*,*', '形容詞,非自立,*,*', '感動詞,*,*,*', '接続詞,*,*,*', '接頭詞,動詞接続,*,*', '接頭詞,名詞接続,*,*', '接頭詞,形容詞接続,*,*', '接頭詞,数接続,*,*', '記号,アルファベット,*,*', '記号,一般,*,*', '記号,句点,*,*', '記号,括弧閉,*,*', '記号,括弧開,*,*', '記号,空白,*,*', '記号,読点,*,*', '連体詞,*,*,*'} 結構いっぱいあるのですが、固有名詞の中から、地域、一般、人名をそれぞれ先頭の10個を見てみます。それぞれ、なんか、なんでその順番なの?って突っ込みたいです。何順なんだろう、ほんとに。 固有名詞・地域 words0 = [(i,janome.sysdic.entries()[i][0]) for i in range(392127) if janome.sysdic.entries()[i][4]=='名詞,固有名詞,地域,一般'] len(words0) 72691 words0[:10] [(135, '原村'), (136, '大倉谷地'), (137, '駒ケ崎'), (138, '里本江'), (139, '生野'), (140, '生野'), (141, '生野'), (142, '菱刈'), (143, '津之江'), (144, '本牧間門')] 同じジャンルで固まって並んでいるんですね。五十音順でもないし、不思議な感じです。 固有名詞・人名 words1 = [(i,janome.sysdic.entries()[i][0]) for i in range(392127) if janome.sysdic.entries()[i][4]=='名詞,固有名詞,人名,一般'] len(words1) 2195 words1[:10] [(73147, '金潤万'), (73197, 'アルワン'), (73206, '僧旻'), (73208, '高鳳翰'), (73215, 'フランキー堺'), (73236, '宇多天皇'), (73276, 'ノーベル'), (73278, '平維茂'), (73284, '上村売剣'), (73287, '池大納言')] フランキー 堺(フランキー さかい、本名;堺 正俊、1929年2月13日 - 1996年6月10日)は、日本のコメディアン、俳優、ジャズドラマー、司会者。鹿児島県鹿児島市出身。慶應義塾大学法学部卒業。 この並びの中にいると、なんだか浮いて見えます。 固有名詞・一般 words2 = [(i,janome.sysdic.entries()[i][0]) for i in range(392127) if janome.sysdic.entries()[i][4]=='名詞,固有名詞,一般,*'] len(words2) 27328 words2[:10] [(107336, '美濃赤坂線'), (107337, '広見山'), (107338, 'ファンタスト'), (107339, '村碆'), (107340, '甲森'), (107341, '千葉マリン'), (107342, '小日向山'), (107343, '草尾峠'), (107344, '八神'), (107345, 'ナカ崎')] 先頭がまさかの美濃赤坂線という事実にびっくりしました。大垣駅から中山道沿いの赤坂町まで出てる単線の短い路線です。終点の美濃赤坂の駅舎は小さな木造です。作者の恣意的なものを感じます。 ・・・それにしても、クセの強さを感じさせる並び順ですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ニューラルネットワークの基礎を整理しよう(3)ーPythonによるニューラルネットワークの実装

はじめに 前回に引き続きニューラルネットワークの基礎について整理します。 今回は最終的にXOR回路をニューラルネットワークに学習させることがゴールです。 勾配降下法について ニューラルネットワークの基礎を整理しよう(1)で軽く触れましたが、ニューラルネットワークの目標は出力と正解の誤差ができるだけ小さくなるような重みとバイアスを探すことでした。その目標へのアプローチの一つに勾配降下法があります。あるパラメータ$\omega$について、勾配降下法を用いて更新する様子を以下の式に示します。 $$\omega=\omega-\eta\frac{\partial f}{\partial \omega} -(1)$$ まず、$\eta$は学習率といい、どれくらいの大きさで更新するかの倍率を示しています。この定数は大きすぎると目標とする地点を過ぎてしまい、小さすぎると目標の地点に到達するのにたくさんの計算を要します。適度な値を我々が与えてあげなければいけません。$f$は損失関数を表します。つまり、式(1)は、あるパラメータから損失関数のあるパラメータに関する微分を引き算し続けて、最終的に損失関数の最小値を探し当てる、というプロセスを表しているのです。 勾配計算の実装です。 numericalgradient.py def numerical_gradient(f, x): h = 1e-4 grad = np.zeros_like(x) x_shape = x.shape x = x.reshape([1, x.size]) grad = grad.reshape([1, x.size]) for idx in range(x.size): tmp_val = x[0][idx] x[0][idx] = tmp_val + h fxh1 = f(x) x[0][idx] = tmp_val - h fxh2 = f(x) grad[0][idx] = (fxh1 - fxh2) / (2*h) x[0][idx] = tmp_val grad = np.reshape(grad, x_shape) return (grad) 今回は行列式xを一度、一行の行列に直してから計算しています。その後全ての変数x[i]に対する関数fの微分を計算し、最後はgradをxと同じ形に戻してその行列を返しています。 ソフトマックス関数 では本題のXOR回路の学習に入る前にソフトマックス関数について紹介します。ソフトマックス関数は分類問題における出力層の活性化関数です。活性化関数については前回の記事を参考にしてください。式を以下に示します。 $$y_k=\frac{e^{a_k}}{\sum_{i=1}^{n}e^{a_i} }-(2)$$ これは、出力層の数がn個ある場合の、k番目の出力を表しています。$y_k$は出力値全体$\sum_{i=1}^{n}e^{a_i}$に対するk番目の出力の割合を出力に持ちます。つまり分類問題で考えると、ある入力に対する出力について、どれくらいの確率で各ラベルの状態になっているかということを表しています。ただ指数関数をコンピュータで実装する際は桁が大きくなり過ぎて情報落ちしてしまう可能性があるため式(1)を次のように変形してから実装します。 $$y_k=\frac{e^{a_k+C}}{\sum_{i=1}^{n}e^{a_i+C} }-(3)$$ 詳細な計算は省きますが、式(2)の分母と分子に定数Cをかけてみてください。この形になるはずです。式(3)では新たに定数Cが登場しました。この定数Cで出力を正規化しています。(2)と(3)で計算結果は変わりません。では、実装してみましょう。 softmax.py def softmax(a): c = np.max(a) exp_a = np.exp(a - c) sum_exp_a = np.sum(exp_a) y = exp_a / sum_exp_a return y XOR回路を学習させる XOR回路ってAND回路とかとは違ってパーセプトロンじゃ表せないんです。入出力表を以下に示します。 入力$x_{1}$ 入力$x_{2}$ 出力$y$ 1 1 0 1 0 1 0 1 1 0 0 0 これをニューラルネットワークに学習させるのが今回の本題です。 では実装いきましょう。 two_layer_net.py import numpy as np import sigmoid import cross_empty_error import numericalgradient import softmax import random import csv class TwoLayerNet: def __init__(self, input_size=2, hidden_size=2, output_size=2, weight_init_std=0.1): self.params={} self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size) self.params['b1'] = np.zeros(hidden_size) self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size) self.params['b2'] = np.zeros(output_size) def predict(self, x): W1, W2 = self.params['W1'], self.params['W2'] b1, b2 = self.params['b1'], self.params['b2'] a1 = np.dot(x, W1) + b1 z1 = sigmoid(a1) a2 = np.dot(z1, W2) + b2 y = softmax(a2) return y def loss(self, x, t): y = self.predict(x) return cross_empty_error(y, t) def numerical_gradient(self, x, t): loss_W = lambda W: self.loss(x, t) grads = {} grads['W1'] = numerical_grdient(loss_W, self.params['W1']) grads['b1'] = numerical_grdient(loss_W, self.params['b1']) grads['W2'] = numerical_grdient(loss_W, self.params['W2']) grads['b2'] = numerical_grdient(loss_W, self.params['b2']) return grads def learn(self, X, T, step, learning_rate): for i in range(step): case = random.randrange(1, 5) case = str(case) x = 'x' + case t = 't' + case init_x = X[x] teacher = T[t] grads = self.numerical_gradient(init_x, teacher) for key in ('W1', 'b1', 'W2', 'b2'): self.params[key] -= learning_rate * grads[key] loss = net.loss(init_x, teacher) with open('loss_data.csv', 'a', newline='') as f: writer = csv.writer(f) writer.writerow([i, loss]) return self.params T = {} T['t1'] = np.array([1., 0.]) T['t2'] = np.array([0., 1.]) T['t3'] = np.array([0., 1.]) T['t4'] = np.array([1., 0.]) X = {} X['x1'] = np.array([1., 1.]) X['x2'] = np.array([1., 0.]) X['x3'] = np.array([0., 1.]) X['x4'] = np.array([0., 0.]) learning_rate = 0.1 step = 30000 net = TwoLayerNet(input_size=2, hidden_size=2, output_size=2) params = net.learn(X, T, step, learning_rate) for i in range(1, 5): case = str(i) x = 'x' + case x = X[x] y = net.predict(x) print(y) print(params) 出力 [0.99697848 0.00302152] [0.00186758 0.99813242] [0.00188986 0.99811014] [0.9981763 0.0018237] {'W1': array([[-6.61308387, -4.98237816], [-6.60971866, -4.99250007]]), 'b1': array([2.72834728, 7.42964638]), 'W2': array([[ 7.37978264, -7.57974664], [-7.2676722 , 7.33929759]]), 'b2': array([ 3.43058795, -3.43058795])} ここでは二層のニューラルネットワークについて学習させました。今まで言葉で説明したものをプログラムに書き起こした感じです。入力とそれに対する正解を与えてあげて、交差エントロピー誤差を計算し、各パラメータに感する偏微分を計算します。その値を元に重みとバイアスを更新していきます。この学習の流れはlearn関数に記述してあります。 今回は出力をone-hot表現を用いて計算しています。なかなかいい精度で学習出来ているのではないでしょうか? あわせて誤差関数のグラフを示します。 5000ステップくらいまですごく振動しています。おそらく学習率の値が大きかったのかと思われます。何故こうなったかもう少し考察する必要がありそうです...しかし学習が進むにつれて損失関数の値はほぼ0で落ち着いています。学習成功と言ってよいでしょう。 おわりに 今回のニューラルネットワークの学習では割と精度よく実装することができました。しかし、中間層の数が増えると行列計算が膨大になり、またそ温室関数の形が複雑になると勾配降下法が通用しなくなったりと結構穴が多いです。ここら辺の穴を補う考え方についてもいつか紹介します。 参考文献 ・「ゼロから作るDeepLearning--Pythonで学ぶディープラーニングの理論と実装」
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ニューラルネットワークの基礎を整理しよう(3)

はじめに 前回に引き続きニューラルネットワークの基礎について整理します。 今回は最終的にXOR回路をニューラルネットワークに学習させることがゴールです。 勾配降下法について ニューラルネットワークの基礎を整理しよう(1)で軽く触れましたが、ニューラルネットワークの目標は出力と正解の誤差ができるだけ小さくなるような重みとバイアスを探すことでした。その目標へのアプローチの一つに勾配降下法があります。あるパラメータ$\omega$について、勾配降下法を用いて更新する様子を以下の式に示します。 $$\omega=\omega-\eta\frac{\partial f}{\partial \omega} -(1)$$ まず、$\eta$は学習率といい、どれくらいの大きさで更新するかの倍率を示しています。この定数は大きすぎると目標とする地点を過ぎてしまい、小さすぎると目標の地点に到達するのにたくさんの計算を要します。適度な値を我々が与えてあげなければいけません。$f$は損失関数を表します。つまり、式(1)は、あるパラメータから損失関数のあるパラメータに関する微分を引き算し続けて、最終的に損失関数の最小値を探し当てる、というプロセスを表しているのです。 勾配計算の実装です。 numericalgradient.py def numerical_gradient(f, x): h = 1e-4 grad = np.zeros_like(x) x_shape = x.shape x = x.reshape([1, x.size]) grad = grad.reshape([1, x.size]) for idx in range(x.size): tmp_val = x[0][idx] x[0][idx] = tmp_val + h fxh1 = f(x) x[0][idx] = tmp_val - h fxh2 = f(x) grad[0][idx] = (fxh1 - fxh2) / (2*h) x[0][idx] = tmp_val grad = np.reshape(grad, x_shape) return (grad) 今回は行列式xを一度、一行の行列に直してから計算しています。その後全ての変数x[i]に対する関数fの微分を計算し、最後はgradをxと同じ形に戻してその行列を返しています。 ソフトマックス関数 では本題のXOR回路の学習に入る前にソフトマックス関数について紹介します。ソフトマックス関数は分類問題における出力層の活性化関数です。活性化関数については前回の記事を参考にしてください。式を以下に示します。 $$y_k=\frac{e^{a_k}}{\sum_{i=1}^{n}e^{a_i} }-(2)$$ これは、出力層の数がn個ある場合の、k番目の出力を表しています。$y_k$は出力値全体$\sum_{i=1}^{n}e^{a_i}$に対するk番目の出力の割合を出力に持ちます。つまり分類問題で考えると、ある入力に対する出力について、どれくらいの確率で各ラベルの状態になっているかということを表しています。ただ指数関数をコンピュータで実装する際は桁が大きくなり過ぎて情報落ちしてしまう可能性があるため式(1)を次のように変形してから実装します。 $$y_k=\frac{e^{a_k+C}}{\sum_{i=1}^{n}e^{a_i+C} }-(3)$$ 詳細な計算は省きますが、式(2)の分母と分子に定数Cをかけてみてください。この形になるはずです。式(3)では新たに定数Cが登場しました。この定数Cで出力を正規化しています。(2)と(3)で計算結果は変わりません。では、実装してみましょう。 softmax.py def softmax(a): c = np.max(a) exp_a = np.exp(a - c) sum_exp_a = np.sum(exp_a) y = exp_a / sum_exp_a return y XOR回路を学習させる XOR回路ってAND回路とかとは違ってパーセプトロンじゃ表せないんです。入出力表を以下に示します。 入力$x_{1}$ 入力$x_{2}$ 出力$y$ 1 1 0 1 0 1 0 1 1 0 0 0 これをニューラルネットワークに学習させるのが今回の本題です。 では実装いきましょう。 two_layer_net.py import numpy as np import sigmoid import cross_empty_error import numericalgradient import softmax import random import csv class TwoLayerNet: def __init__(self, input_size=2, hidden_size=2, output_size=2, weight_init_std=0.1): self.params={} self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size) self.params['b1'] = np.zeros(hidden_size) self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size) self.params['b2'] = np.zeros(output_size) def predict(self, x): W1, W2 = self.params['W1'], self.params['W2'] b1, b2 = self.params['b1'], self.params['b2'] a1 = np.dot(x, W1) + b1 z1 = sigmoid(a1) a2 = np.dot(z1, W2) + b2 y = softmax(a2) return y def loss(self, x, t): y = self.predict(x) return cross_empty_error(y, t) def numerical_gradient(self, x, t): loss_W = lambda W: self.loss(x, t) grads = {} grads['W1'] = numerical_grdient(loss_W, self.params['W1']) grads['b1'] = numerical_grdient(loss_W, self.params['b1']) grads['W2'] = numerical_grdient(loss_W, self.params['W2']) grads['b2'] = numerical_grdient(loss_W, self.params['b2']) return grads def learn(self, X, T, step, learning_rate): for i in range(step): case = random.randrange(1, 5) case = str(case) x = 'x' + case t = 't' + case init_x = X[x] teacher = T[t] grads = self.numerical_gradient(init_x, teacher) for key in ('W1', 'b1', 'W2', 'b2'): self.params[key] -= learning_rate * grads[key] loss = net.loss(init_x, teacher) with open('loss_data.csv', 'a', newline='') as f: writer = csv.writer(f) writer.writerow([i, loss]) return self.params T = {} T['t1'] = np.array([1., 0.]) T['t2'] = np.array([0., 1.]) T['t3'] = np.array([0., 1.]) T['t4'] = np.array([1., 0.]) X = {} X['x1'] = np.array([1., 1.]) X['x2'] = np.array([1., 0.]) X['x3'] = np.array([0., 1.]) X['x4'] = np.array([0., 0.]) learning_rate = 0.1 step = 30000 net = TwoLayerNet(input_size=2, hidden_size=2, output_size=2) params = net.learn(X, T, step, learning_rate) for i in range(1, 5): case = str(i) x = 'x' + case x = X[x] y = net.predict(x) print(y) print(params) 出力 [0.99697848 0.00302152] [0.00186758 0.99813242] [0.00188986 0.99811014] [0.9981763 0.0018237] {'W1': array([[-6.61308387, -4.98237816], [-6.60971866, -4.99250007]]), 'b1': array([2.72834728, 7.42964638]), 'W2': array([[ 7.37978264, -7.57974664], [-7.2676722 , 7.33929759]]), 'b2': array([ 3.43058795, -3.43058795])} ここでは二層のニューラルネットワークについて学習させました。今まで言葉で説明したものをプログラムに書き起こした感じです。入力とそれに対する正解を与えてあげて、交差エントロピー誤差を計算し、各パラメータに感する偏微分を計算します。その値を元に重みとバイアスを更新していきます。この学習の流れはlearn関数に記述してあります。 今回は出力をone-hot表現を用いて計算しています。なかなかいい精度で学習出来ているのではないでしょうか? あわせて誤差関数のグラフを示します。 5000ステップくらいまですごく振動しています。おそらく学習率の値が大きかったのかと思われます。何故こうなったかもう少し考察する必要がありそうです...しかし学習が進むにつれて損失関数の値はほぼ0で落ち着いています。学習成功と言ってよいでしょう。 おわりに 今回のニューラルネットワークの学習では割と精度よく実装することができました。しかし、中間層の数が増えると行列計算が膨大になり、またそ温室関数の形が複雑になると勾配降下法が通用しなくなったりと結構穴が多いです。ここら辺の穴を補う考え方についてもいつか紹介します。 参考文献 ・「ゼロから作るDeepLearning--Pythonで学ぶディープラーニングの理論と実装」
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DjangoのCRUD処理について

はじめに DjangoのCRUD処理について勉強した内容を記録に残しました。Djangoビギナーズブックを参考人させていただきました。余談ですが、本記事を書いている時点でプレミア価格になってました…!大変お世話になっているので納得です。電子版が欲しいなあ。 プロジェクトファイルを作成 下準備です。 $ django-admin startproject conf . conf/settings.py # 変更した部分のみ抜粋 LANGUAGE_CODE = 'ja' TIME_ZONE = 'Asia/Tokyo' homeアプリを作成 CRUD処理に入る前にホーム画面を表示するアプリを作ります。 $ python manage.py startapp home conf/settings.py INSTALLED_APPS = [ 'home.apps.HomeConfig', # これを追加 # 省略 TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], #ここを修正 ルーティングします。 conf/urls.py from django.contrib import admin from django.urls import path, include # 追加 urlpatterns = [ path('', include('home.urls')), # 追加 path('admin/', admin.site.urls), ] home/urls.py from django.urls import path from . import views app_name = 'home' urlpatterns = [ path('', views.top, name='top') ] home/views.py from django.shortcuts import render def top(request): return render(request, 'home/top.html') htmlを作成します。まずはベースとなるファイルを作ります。 templates/base.html {% load static %} <!doctype html> <html lang="ja"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- 互換表示の解除 --> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <title>ユーザー登録・更新</title> </head> <body> <!-- ナビバー --> <nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <a class="navbar-brand" href="{% url 'home:top' %}">テストサイト</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <a class="nav-link" href="{% url 'home:top' %}">Home</a> </li> </ul> </div> </nav> <!-- コンテンツ --> {% block content %}{% endblock %} <!-- Optional JavaScript --> <!-- jQuery first, then Popper.js, then Bootstrap JS --> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> </body> </html> home/top.html {% extends "base.html" %} <!-- コンテンツ --> {% block content %} <div class="container"> <h1>home</h1> </div> {% endblock %} 開発用サーバーを起動して、ブラウザからサーバーにアクセスすると、以下のようなページが表示されるはずです。 レビューアプリを作成 前置きが長くなりましたが、CRUD処理のためのアプリを作成します。ご飯屋さんをレビューするアプリをモチーフにします。 $ python manage.py startapp reviews conf/settings.py INSTALLED_APPS = [ 'home.apps.HomeConfig', 'reviews.apps.ReviewsConfig', # これを追加 ] conf/urls.py urlpatterns = [ path('', include('home.urls')), path('reviews/', include('reviews.urls')), # 追加 ] reviews/urls.py from django.urls import path from . import views app_name = 'reviews' urlpatterns = [ ] モデルを作成します。店名とタイトルとレビュー本文、星の数、作成日時をフィールドとします。 reviews/models.py from django.db import models from django.utils import timezone class Review(models.Model): STARS = ( (1, '★'), (2, '★★'), (3, '★★★'), (4, '★★★★'), (5, '★★★★★'), ) store_name = models.CharField('店名', max_length=255) title = models.TextField('タイトル', max_length=255) text = models.TextField('口コミテキスト', blank=True) stars = models.IntegerField('星の数', choices=STARS) created_at = models.DateTimeField('作成日', default=timezone.now) def __str__(self): return self.title モデルの定義が終わったら、マイグレーションします。 $ python manage.py makemigrations reviews $ python manage.py migrate 管理サイトで表示できるようにします。 reviews/admin.py from django.contrib import admin from .models import Review admin.site.register(Review) データを追加 スーパーユーザーを作成します。 $ python manage.py createsuperuser ブラウザから管理サイト(http://***/admin)を開き、ログインします。 適当にデータを追加します。 一覧と詳細ページ(Read) 一覧ページ(/)と詳細ページ(/detail/int)を作成します。int:pkは後でreview_list.htmlで指定することになります。 reviews/urls.py urlpatterns = [ path('', views.review_list, name='review_list'), path('detail/<int:pk>/', views.review_detail, name='review_detail'), ] views.pyを作成します。detail/intのintにデータベースのPrimary Keyに存在しない数字を入れたら、Not Foundになるようにしています。 reviews/views.py from django.shortcuts import render, get_object_or_404 from .models import Review def review_list(request): context = { 'review_list': Review.objects.all().order_by('-created_at'), } return render(request, 'reviews/review_list.html', context) def review_detail(request, pk): # pkはreview_idのこと #review = Review.objects.get(id=pk) # pkはreview_idのこと context = { #'review': review, 'review': get_object_or_404(Review, pk=pk), } return render(request, 'reviews/review_detail.html', context) htmlを作成します。 templates/base.html <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <a class="nav-link" href="{% url 'home:top' %}">Home</a> </li> <!-- レビューページへのリンク --> <li class="nav-item"> <a class="nav-link" href="{% url 'reviews:review_list' %}">Reviews</a> </li> </ul> </div> review_list.html(一覧)を作成します。int:pkはurlタグで指定します。'reviews:review_detail'の後に半角スペースを入れて、review.pkのように埋め込みたいデータを書きます。 templates/review_list.html {% extends "base.html" %} <!-- コンテンツ --> {% block content %} <div class="container"> <h1>Reviews</h1> <hr> {% for review in review_list %} <article> <h3><a href="{% url 'reviews:review_detail' review.pk %}">{{ review.store_name }}</a></h3> <p>{{ review.get_stars_display }} - {{ review.title }}</p> </article> {% endfor %} </div> {% endblock %} 詳細ページのhtmlを作成します。 templates/review_detail.html {% extends "base.html" %} <!-- コンテンツ --> {% block content %} <div class="container"> <article> <h1>{{ review.store_name }}</h1> <p>{{ review.get_stars_display }} - {{ review.title }}</p> {{ review.text | linebreaks }} </article> </div> {% endblock %} 開発用サーバーを起動し、ブラウザでアクセスすると、以下のページが表示されます。 レビュー作成ページ(Create) レビュー作成ページ(create/)を追加します。 reviews/urls.py urlpatterns = [ path('', views.review_list, name='review_list'), path('detail/<int:pk>/', views.review_detail, name='review_detail'), path('create/', views.review_create, name='review_create'), # 追加:作成ページと送信処理 #path('create/send/', views.review_create_send, name='review_create_send'), # 参考:送信処理 ] レビュー作成と送信処理を一つのビューに書くと以下のようになります。 reviews/views.py from .forms import ReviewCreateForm # フォーム機能 from .models import Review from django.shortcuts import render, get_object_or_404, redirect # 省略 '''追加:作成&送信''' def review_create(request): form = ReviewCreateForm(request.POST or None) # request.method == 'POST' : Submitボタンが押された場合 # form.is_valid() : 入力内容が問題ない # 上記を両方満たせば、データを保存して一覧ページへリダイレクトする。 if request.method == 'POST' and form.is_valid(): form.save() return redirect('reviews:review_list') # 通常のページアクセス時はmethodがGETになる # 入力内容に問題がある場合は、form.is_validがFalseになり、ReviewCreateFormにエラー情報が付与される context = { 'form': ReviewCreateForm() } return render(request, 'reviews/review_form.html', context) 分かりやすくするため、レビュー作成ページの表示と送信後の処理を分けて書くと以下のようになります。 reviews/views.py '''作成''' def review_create(request): context = { 'form': ReviewCreateForm() } return render(request, 'reviews/review_form.html', context) '''送信ボタンを押したときの処理''' def review_create_send(request): #name = request.POST.get('store_name') #print('送信されたデータ→{}'.format(name)) form = ReviewCreateForm(request.POST) # 入力内容に問題が無いかチェック if form.is_valid(): form.save() return redirect('reviews:review_list') #return render(request, 'reviews/review_list.html') # renderを使うと二重送信の恐れあり。 # 問題があれば、入力画面のテンプレートファイルにエラーメッセージ入りのformオブジェクトを渡す else: context = { 'form': form, } return render(request, 'reviews/review_form.html', context) # saveしてないので、renderでも二重送信の恐れはない。 forms.pyを作成します。フォームの役割は大まかに二つあります。 form内の各入力欄、選択欄を自動的に生成する 送信されてきたデータをまとめる reviews/forms.py from django import forms from . models import Review class ReviewCreateForm(forms.ModelForm): class Meta: model = Review fields = '__all__' #fields = ('store_name',) #exclude = ('created_at',) # bootstrap対応のため、classを指定する。 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields.values(): field.widget.attrs['class'] = 'form-control' review_list.htmlに作成ページへのリンクを追加します。 templates/reviews/review_list.html {% extends "base.html" %} <!-- コンテンツ --> {% block content %} <div class="container"> <!-- 省略 --> <a href="{% url 'reviews:review_create' %}">to create page</a> </div> {% endblock %} レビュー作成ページのhtmlを作成します。action属性を空文字にした場合、「現在のURL=review/create」がクリック後のアクセス先になります。こうすることでコードが再利用しやすくなります。 templates/reviews/review_form.html {% extends "base.html" %} <!-- コンテンツ --> {% block content %} <div class="container"> <!-- フォーム利用。 --> <form class="form-group mt-2" action="" method="POST"> {{ form.as_p }} {% csrf_token %} <button type="submit" class="btn btn-primary ml-1">Submit</button> </form> </div> {% endblock %} 実際の作成ページは以下のようになります。一覧ページに作成ページへのリンクが追加されています。 作成ページはこのような形です。(本当は作成日は入力可能です。。) 更新ページ(Update) 作成(Create)ができるようになったので、次は更新(Update)を実装します。 reviews/urls.py urlpatterns = [ # 省略 path('update/<int:pk>/', views.review_update, name='review_update'), # 更新 ] 関数ビューを追加します。ポイントは、作成(Create) で作ったreview_form.htmlを再利用している点です。また、3行目でReviewCreateFormをインスタンス化する際に、instance引数に、Reviewモデルインスタンスを渡しています。これによって、入力欄に現在の値(店名やレビュー本文など)が入った状態で表示されます。 reviews/views.py def review_update(request, pk): review = get_object_or_404(Review, pk=pk) form = ReviewCreateForm(request.POST or None, instance=review) if request.method == 'POST' and form.is_valid(): form.save() return redirect('reviews:review_list') context = { 'form': form } return render(request, 'reviews/review_form.html', context) 更新ページへのリンクを、詳細ページに追加します。 templates/reviews/review_detail.html <a href="{% url 'reviews:review_update' review.pk %}">更新</a> forms.pyを少し変更します。作成日フィールド(created_at)を除外します。 reviews/forms.py class ReviewCreateForm(forms.ModelForm): class Meta: model = Review #fields = '__all__' exclude = ('created_at',) 作成日は表示はしたいので、review_form.htmlを修正します。 templates/reviews/review_form.html <form class="form-group mt-2" action="" method="POST"> {{ form.as_p }} <p>作成日 : {{ form.instance.created_at }}</p> {% csrf_token %} <button type="submit" class="btn btn-primary ml-1">Submit</button> </form> 詳細ページに更新ページへのリンクが追加されています。 リンクをクリックすると、更新ページになります。作成ページと同様にreview_form.htmlを使用しているのでデザインは同じですが、店名など現在の値が入っています。 削除(Delete) 読み込み(Read)、作成(Create)、更新(Update)ができたので、最後に削除(Delete)を実装します。まずはURL定義から。どのレビューを削除するか指定する必要があるので、pkが要ります。 reviews/urls.py urlpatterns = [ # 省略 path('delete/<int:pk>/', viwes.review_delete, name='review_delete'), # 削除 ] 続いて関数ビューを定義します。削除ボタンを押すと、リクエストメソッドはPOSTになるので、ifの中に入流ので、データを削除し、一覧ページにリダイレクトします(二重送信防止のため)。通常のアクセスでは削除確認用のページに遷移します。その際、Reviewモデルインスタンスをテンプレートファイルに渡します。 review/views.py def review_delete(request, pk): review = get_object_or_404(Review, pk=pk) if request.method == 'POST': review.delete() return redirect('reviews:review_list') context = { 'review': review } return render(request, 'reviews/review_confirm_delete.html', context) テンプレートファイルを作成します。削除するだけなので、ユーザーが入力する欄はありませんが、CSRF対策とリクエストメソッドをPOSTにするためform要素を使っています。 templates/reviews/review_confirm_delete.html {% extends "base.html" %} <!-- コンテンツ --> {% block content %} <div class="container"> <p>{{ review.store_name }}のレビューを削除します。よろしいですか?</p> <form action="", method='POST'> {% csrf_token %} <button type="submit">削除</button> </form> </div> {% endblock %} 詳細ページから削除確認ページへのリンクを貼ります。 templates/reviews/review_detail.html <a href="{% url 'reviews:review_delete' review.pk %}">削除</a> 削除ページへのリンクが追加になっています。 削除リンクをクリックすると、削除確認ページへジャンプします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

株価分析(SMA) - ゴールデンクロスとデッドクロスは有効な指標か?

はじめに 拙作の記事「株価の指標を求める」では、TA-Libを使用していろいろな指標を求めてみました。本記事ではこの中のSMA(単純移動平均)を使って、ゴールデンクロスとデッドクロスを求めてみます。そしてゴールデンクロスとデッドクロス後に株価がどのように変化しているか、また短期と長期で最も変化率が大きい組み合わせは何かを調べていきたいと思います。 ゴールデンクロス/デッドクロスを求める 今回使用するデータは日経平均株価で、期間は2011/1/1〜2020/12/31の10年間としています。まずは短期線と長期線を求めます。短期線は25日、長期線は75日としました。計算はTA-Libで行っています。ゴールデンクロス/デッドクロスが正しく求められているかグラフも作成してみました。 import os import datetime import pandas as pd import pandas_datareader import talib import matplotlib.pyplot as plt import seaborn as sns START_DATE = datetime.date(2011, 1, 1) END_DATE = datetime.date(2020, 12, 31) def get_stock(ticker, start_date, end_date): ''' get stock data from Yahoo Finance ''' dirname = 'data' os.makedirs(dirname, exist_ok=True) fname = f'{dirname}/{ticker}.pkl' df_stock = pd.DataFrame() if os.path.exists(fname): df_stock = pd.read_pickle(fname) start_date = df_stock.index.max() + datetime.timedelta(days=1) if end_date > start_date: df = pandas_datareader.data.DataReader( ticker, 'yahoo', start_date, end_date) df_stock = pd.concat([df_stock, df[~df.index.isin(df_stock.index)]]) df_stock.to_pickle(fname) return df_stock def main(): short_term = 25 long_term = 75 df = get_stock('^N225', START_DATE, END_DATE) # 移動平均を求める df['short_ma'] = talib.SMA(df['Close'], timeperiod=short_term) df['long_ma'] = talib.SMA(df['Close'], timeperiod=long_term) # クロスしている箇所を見つける df['diff'] = df['long_ma'] - df['short_ma'] df_cross = df[df['diff'] * df['diff'].shift() < 0] df['golden_cross'] = df.index.isin(df_cross[df_cross['diff'] < 0].index) df['dead_cross'] = df.index.isin(df_cross[df_cross['diff'] > 0].index) # グラフで表示する sns.set() fig, ax = plt.subplots(figsize=(12, 8)) ax.plot(df['short_ma'], label=f'short ({short_term} days)') ax.plot(df['long_ma'], label=f'long ({long_term} days)') ax.set_title('Golden Cross / Dead Cross') ax.legend() for index, row in df[df['golden_cross']].iterrows(): ax.annotate('G', xy=(index, row['short_ma']), xytext=(index, row['short_ma'] + 1500), size=10, color='red', arrowprops={'arrowstyle': '->', 'color': 'red'}) for index, row in df[df['dead_cross']].iterrows(): ax.annotate('D', xy=(index, row['short_ma']), xytext=(index, row['short_ma'] - 1500), size=10, color='blue', arrowprops={'arrowstyle': '->', 'color': 'blue'}) plt.savefig('cross1.png') plt.show() if __name__ == '__main__': main() ゴールデンクロス/デッドクロスは正しく求められているようです。 ゴールデンクロス/デッドクロス後の株価の変化率を求める では、ゴールデンクロス/デッドクロス後の株価の変化率を求めます。ゴールデンクロス後の変化率は次のデッドクロスまでの期間で求めています。反対にデッドクロス後の変化率は次のゴールデンクロスまでの期間で求めています。途中で変化率の要約統計量(平均、最大値、最小値、標準偏差など)を出力しています。最後に見やすいようにグラフ化しています。 import os import math import datetime import numpy as np import pandas as pd import pandas_datareader import talib import matplotlib.pyplot as plt import seaborn as sns import japanize_matplotlib START_DATE = datetime.date(2011, 1, 1) END_DATE = datetime.date(2020, 12, 31) def get_stock(ticker, start_date, end_date): ''' get stock data from Yahoo Finance ''' dirname = 'data' os.makedirs(dirname, exist_ok=True) fname = f'{dirname}/{ticker}.pkl' df_stock = pd.DataFrame() if os.path.exists(fname): df_stock = pd.read_pickle(fname) start_date = df_stock.index.max() + datetime.timedelta(days=1) if end_date > start_date: df = pandas_datareader.data.DataReader( ticker, 'yahoo', start_date, end_date) df_stock = pd.concat([df_stock, df[~df.index.isin(df_stock.index)]]) df_stock.to_pickle(fname) return df_stock def pct_change(before, after): return (after / before) - 1.0 def analyze_golden_cross(df): ''' ゴールデンクロスの翌日から株価(終値)がどう変化したか求める デッドクロスになったらそこで処理を終える ''' results = [] for date, _ in df[df['golden_cross']].iterrows(): result = [] close_at_golden_cross = df.loc[date]['Close'] row = df.index.get_loc(date) for _, s in df.iloc[row+1:].iterrows(): result.append(pct_change(close_at_golden_cross, s['Close'])) if s['dead_cross']: results.append(result) result = [] break if len(result) > 0: results.append(result) fig, ax = plt.subplots(figsize=(12, 8)) for result in results: ax.plot(result) ax.set_title('ゴールデンクロス後の株価変化率推移') plt.xlabel('日数') plt.ylabel('変化率') plt.savefig('cross2-1.png') plt.show() # 平均変化率を求める all_result = [] for result in results: all_result.extend(result) all_result = np.array(all_result) print(pd.DataFrame(all_result).describe()) # 平均変化率の分布図 fig, ax = plt.subplots(figsize=(8, 6)) bins = math.ceil(math.log(len(all_result), 2) + 1) ax.hist(all_result, bins=bins) ax.set_title('ゴールデンクロス後の平均変化率の分布') plt.savefig('cross2-2.png') plt.show() def analyze_dead_cross(df): ''' デッドクロスの翌日から株価(終値)がどう変化したか求める ゴールデンクロスになったらそこで処理を終える ''' results = [] for date, _ in df[df['dead_cross']].iterrows(): result = [] close_at_golden_cross = df.loc[date]['Close'] row = df.index.get_loc(date) for _, s in df.iloc[row+1:].iterrows(): result.append(pct_change(close_at_golden_cross, s['Close'])) if s['golden_cross']: results.append(result) result = [] break if len(result) > 0: results.append(result) fig, ax = plt.subplots(figsize=(12, 8)) for result in results: ax.plot(result) ax.set_title('デッドクロス後の株価変化率推移') plt.xlabel('日数') plt.ylabel('変化率') plt.savefig('cross2-3.png') plt.show() # 平均変化率を求める all_result = [] for result in results: all_result.extend(result) all_result = np.array(all_result) print(pd.DataFrame(all_result).describe()) # 平均変化率の分布図 fig, ax = plt.subplots(figsize=(8, 6)) bins = math.ceil(math.log(len(all_result), 2) + 1) ax.hist(all_result, bins=bins) ax.set_title('デッドクロス後の平均変化率の分布') plt.savefig('cross2-4.png') plt.show() def main(): short_term = 25 long_term = 75 df = get_stock('^N225', START_DATE, END_DATE) # 移動平均を求める df['short_ma'] = talib.SMA(df['Close'], timeperiod=short_term) df['long_ma'] = talib.SMA(df['Close'], timeperiod=long_term) # クロスしている箇所を見つける df['diff'] = df['long_ma'] - df['short_ma'] df_cross = df[df['diff'] * df['diff'].shift() < 0] df['golden_cross'] = df.index.isin(df_cross[df_cross['diff'] < 0].index) df['dead_cross'] = df.index.isin(df_cross[df_cross['diff'] > 0].index) sns.set(font='IPAexGothic') analyze_golden_cross(df) analyze_dead_cross(df) if __name__ == '__main__': main() ゴールデンクロス後の変化率 要約統計量 統計量 値 count 1487.000000 mean 0.080796 std 0.118025 min -0.123680 25% 0.005977 50% 0.050059 75% 0.122404 max 0.709272 変化率の推移 変化率の分布 デッドクロス後の変化率 要約統計量 統計量 値 count 839.000000 mean -0.018099 std 0.059415 min -0.267749 25% -0.050816 50% -0.014894 75% 0.020195 max 0.153848 変化率の推移 変化率の分布 ゴールデンクロス後の変化率を見ると、大部分がにプラスであることがわかります。変化率を見てもその傾向が見て取れます。平均変化率も8%あります。 デッドクロス後の変化率を見ると、マイナスとなっているものが多いですが、ゴールデンクロスほどの傾向は見られません。変化率の分布も0近辺で高くなっており、平均変化率も1%ほどにとどまっています。 この10年間で株価が約2倍になっていることから、基本的には上昇のトレンドにあったためだと思われます。 短期線、長期線の期間はいくつがよいか 上記は短期線を25日、長期線を75日として計算した結果となります。では、もっとも変化率の大きい短期と長期の期間の組み合わせはいくつになるのでしょうか?上で行った計算を短期を2〜249、長期を3〜250として計算してみます。それぞれの結果から平均変化率を計算し、これをヒートマップにしてみます。ちなみに私のPCではこの計算に3時間かかりました。(もっと早いPCが欲しい...) import os import math import datetime import numpy as np import pandas as pd import pandas_datareader import talib import matplotlib.pyplot as plt import seaborn as sns import japanize_matplotlib START_DATE = datetime.date(2011, 1, 1) END_DATE = datetime.date(2020, 12, 31) def get_stock(ticker, start_date, end_date): ''' get stock data from Yahoo Finance ''' dirname = 'data' os.makedirs(dirname, exist_ok=True) fname = f'{dirname}/{ticker}.pkl' df_stock = pd.DataFrame() if os.path.exists(fname): df_stock = pd.read_pickle(fname) start_date = df_stock.index.max() + datetime.timedelta(days=1) if end_date > start_date: df = pandas_datareader.data.DataReader( ticker, 'yahoo', start_date, end_date) df_stock = pd.concat([df_stock, df[~df.index.isin(df_stock.index)]]) df_stock.to_pickle(fname) return df_stock def pct_change(before, after): return (after / before) - 1.0 def analyze_golden_cross(df): ''' ゴールデンクロスの翌日から株価(終値)がどう変化したか求める デッドクロスになったらそこで処理を終える ''' results = [] for date, _ in df[df['golden_cross']].iterrows(): result = [] close_at_golden_cross = df.loc[date]['Close'] row = df.index.get_loc(date) for _, s in df.iloc[row+1:].iterrows(): result.append(pct_change(close_at_golden_cross, s['Close'])) if s['dead_cross']: results.append(result) result = [] break if len(result) > 0: results.append(result) # 平均変化率を求める all_result = [] for result in results: all_result.extend(result) return np.array(all_result) def analyze_dead_cross(df): ''' デッドクロスの翌日から株価(終値)がどう変化したか求める ゴールデンクロスになったらそこで処理を終える ''' results = [] for date, _ in df[df['dead_cross']].iterrows(): result = [] close_at_golden_cross = df.loc[date]['Close'] row = df.index.get_loc(date) for _, s in df.iloc[row+1:].iterrows(): result.append(pct_change(close_at_golden_cross, s['Close'])) if s['golden_cross']: results.append(result) result = [] break if len(result) > 0: results.append(result) # 平均変化率を求める all_result = [] for result in results: all_result.extend(result) return np.array(all_result) def main(): df = get_stock('^N225', START_DATE, END_DATE) sns.set(font='IPAexGothic') golden_cross_result = [] dead_cross_result = [] for long_term in np.arange(3, 251): for short_term in np.arange(2, long_term): # 移動平均を求める df['short_ma'] = talib.SMA(df['Close'], timeperiod=short_term) df['long_ma'] = talib.SMA(df['Close'], timeperiod=long_term) # クロスしている箇所を見つける df['diff'] = df['long_ma'] - df['short_ma'] df_cross = df[df['diff'] * df['diff'].shift() < 0] df['golden_cross'] = df.index.isin( df_cross[df_cross['diff'] < 0].index) df['dead_cross'] = df.index.isin( df_cross[df_cross['diff'] > 0].index) result = analyze_golden_cross(df) golden_cross_avg = np.average(result) golden_cross_result.append( {'long': long_term, 'short': short_term, 'avg': golden_cross_avg}) result = analyze_dead_cross(df) dead_cross_avg = np.average(result) dead_cross_result.append( {'long': long_term, 'short': short_term, 'avg': dead_cross_avg}) print( f'long={long_term}, short={short_term}, g-avg={golden_cross_avg}, d-avg={dead_cross_avg}') # 移動平均期間の組み合わせによる変化率(ゴールデンクロス) results = pd.DataFrame(golden_cross_result).pivot('short', 'long', 'avg') fig, ax = plt.subplots(figsize=(12, 9)) sns.heatmap(results, square=True, cmap='Reds', ax=ax) ax.set_title('移動平均期間の組み合わせによる変化率(ゴールデンクロス)') ax.invert_yaxis() ax.grid() plt.savefig('cross3-1.png') plt.show() # 移動平均期間の組み合わせによる変化率(デッドクロス) results = pd.DataFrame(dead_cross_result).pivot('short', 'long', 'avg') fig, ax = plt.subplots(figsize=(12, 9)) sns.heatmap(results, square=True, cmap='Blues_r', ax=ax) ax.set_title('移動平均期間の組み合わせによる変化率(デッドクロス)') ax.invert_yaxis() ax.grid() plt.savefig('cross3-2.png') plt.show() if __name__ == '__main__': main() ゴールデンクロス後の変化率 濃い赤が変化率の大きい部分を表しています。上記の短期25日、長期75日のところを見ると、そんなに大きな変化率ではないことがわかります。このグラフだと、短期100日くらい、長期250日くらいところの変化率が非常に大きいです。最大変化率は34.7%です。詳しくは見れていませんが、期間が長くなるとゴールデンクロス/デッドクロスの回数も少なくなり、データが偏っているのではないかと思われます。 デッドクロス後の変化率 こちらはゴールデンクロス後ほど色の変化がありません。このグラフでは白が変化率0%が白でないのでわかりにくいですが、変化率がプラスとなっているところもたくさんあります。 最後に 今回はSMAを使用してゴールデンクロス/デッドクロス後の株価の変化を調べてみました。ゴールデンクロスで株を購入すればプラス8%の変化が期待できる、逆にデッドクロスで株を手放せばマイナス1%の変化を回避できる、と言えると思います。ただし、今回求めているのは変化率であって利益ではありません。次回は利益がどれくらいになるのかを計算してみたいと思います。 ソースはGitHubに置いてあります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flaskを使用したFacebook ログイン連携 メモ

Flaskを使用したFacebookログイン連携についてメモする。 Google連携を試したもののFacebook版 大まかな処理の流れ Facebookへ認可リクエスト(Authorization Request)を行う。 ユーザー認証/同意を行い、認可レスポンスを受け取る。 認可レスポンスを使ってトークンリクエストを行う。 アプリアクセストークンの生成を行う。※Googleと違うところ。 アクセストークンを検査する。※Googleと違うところ。 事前準備 Facebookデベロッパーコンソールからアプリケーションを登録する。 クライアントID(アプリID)/app secret 取得(アプリダッシュボード -> 設定 -> ベーシックから確認) クライアントトークン取得(アプリダッシュボード -> 設定 -> 詳細設定から確認) Facebookログイン設定 「アプリに製品を追加」→「Facebookログイン」設定→「ウェブ」選択→サイトURL入力(例:http://localhost:5000) →残りの項目はすべて「次へ」を選択。 Facebookログイン -> 設定 -> 「有効なOAuthリダイレクトURI」にhttp://localhost:5000/callbackを入力する。※開発中の場合は、localhostも設定可能。 Pythonライブラリインストール flask ウェブアプリ開発用フレームワーク 実装 認可リクエスト ~ アクセストークン検査までのコード import hashlib from flask import Flask, request as r, redirect, session from urllib import request, parse, error from http import HTTPStatus import json import base64 from pprint import pprint import os # クライアント情報(ダッシュボードから確認) client_id = <YOUR_CLIENT_ID> client_secret = <YOUR_CLIENT_SECRET> client_token = <YOUR_CLIENT_TOKEN> redirect_uri = 'http://localhost:5000/callback' # Facebook エンドポイント authorization_base_url = 'https://www.facebook.com/v10.0/dialog/oauth' token_url = 'https://graph.facebook.com/v10.0/oauth/access_token' app_token_url = 'https://graph.facebook.com/oauth/access_token' token_validation_url = 'https://graph.facebook.com/debug_token' app = Flask(__name__) app.secret_key = 'session_key' # 1. Facebookへ認可リクエスト(Authorization Request)を行う。 @app.route("/login") def login(): # ステート生成+保存 state = hashlib.sha256(os.urandom(32)).hexdigest() session['state'] = state # 認可リクエスト return redirect(authorization_base_url+'?{}'.format(parse.urlencode({ 'client_id': client_id, 'scope': 'email public_profile', 'redirect_uri': redirect_uri, 'state': state, 'response_type': 'code' }))) # Authoriztion Redirection and Token Request Endpoint # 2.ユーザー認証/同意を行い、認可レスポンスを受け取る。 @app.route("/callback") def callback(): # ステート検証 state = r.args.get('state') if state != session['state']: print("invalid_redirect") code = r.args.get('code') req, res, body, err_str = '', '', '', '' # 3. 認可レスポンスを使ってトークンリクエストを行う。 token_req_url = token_url+'?{}'.format(parse.urlencode({ 'client_id': client_id, 'client_secret': client_secret, 'redirect_uri': redirect_uri, 'code': code })) try: req = request.Request(token_req_url, method='GET') with request.urlopen(req) as res: body = res.read() except error.HTTPError as err: err_str = str(err.code) + ':' + err.reason + ':' + str(err.read()) except error.URLError as err: err_str = err.reason access_token = json.loads(body)['access_token'] # 4. アプリアクセストークンの生成を行う。 # https://developers.facebook.com/docs/facebook-login/access-tokens/#apptokens req_url = app_token_url+'?{}'.format(parse.urlencode({ 'client_id': client_id, 'client_secret': client_secret, 'grant_type': 'client_credentials' })) try: req = request.Request(req_url, method='GET') with request.urlopen(req) as res: body = res.read() except error.HTTPError as err: err_str = str(err.code) + ':' + err.reason + ':' + str(err.read()) except error.URLError as err: err_str = err.reason app_token = json.loads(body)['access_token'] # 5.アクセストークンを検査する。 req_url = token_validation_url+'?{}'.format(parse.urlencode({ 'input_token': access_token, 'access_token': app_token })) try: req = request.Request(req_url, method='GET') with request.urlopen(req) as res: body = res.read() except error.HTTPError as err: err_str = str(err.code) + ':' + err.reason + ':' + str(err.read()) except error.URLError as err: err_str = err.reason return json.loads(body) if __name__ == "__main__": app.run(debug=True) 参考情報 ログインフローを手動で構築する アクセストークン
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

競プロ備忘録過去問編#1 ABC042

ABCは毎週やるみたいではないので過去問も解いていく なんだかんだそこまで時間とれなそうで悲しい AtCoder Beginner Contest 042 なぜか4問しかなかった。4問目までだったので易し目だった A.和風いろはちゃんイージー 3つの数字A,B,Cが与えられるので、それを並び替えて5・7・5にできるかどうか判断する問題 入力 A B C 出力 できるなら"YES"、できないなら"NO"を出力 A,B,Cを順番に見てって、5または7ならそれぞれカウント 最後に5のカウントが2、7のカウントが1なら"YES"、そうでないなら"NO"を出力 解答 myList=input().split() five=0 seven=0 for i in range(3): if int(myList[i])==5: five+=1 elif int(myList[i])==7: seven+=1 if five==2 and seven==1: print("YES") else: print("NO") B.文字列大好きいろはちゃんイージー 長さがLの文字列がN個ある。これらを並び替えて繋げたもののうち、一番辞書順で小さいものを求めよという問題 入力 $ N\quad L\ $ $ S_1\ $ $ S_2\ $ $ S_3\ $ $ \vdots\ $ $ S_N $ 出力 答えを出力 辞書順に小さいものからつなげていけば終了 list.sort()で辞書順にリストを並び替える sorted(list)で辞書順に並び替えたリストを返す 解答 N,L=map(int,input().split()) S=[] for i in range(N): S.append(input()) S.sort() ans="" for i in range(N): ans+=S[i] print(ans) C.こだわり者いろはちゃん Kこの数字が与えられる。その数字を含まない数字のうちでN以上の数字のうち一番小さい数字を答えよという問題 入力 N K $ D_1\quad D_2\quad \cdots\quad D_K\quad$ 出力 答えを出力せよ 数学の問題ではにのでどうプログラムを走らせれば良いのか考える 虱潰しは立派な方法 Nから初めて、各数値で1桁目から調べていく。cntで何桁目までオッケーかカウントしておいて答えかどうか判別 while文とbreak組み合わせてもいけそう 解答 N,K=map(int,input().split()) D=input().split() for i in range(N,100000): cnt=0 for j in D: if str(j) in str(i): break else: cnt+=1 if cnt==len(D): print(i) break D.いろはちゃんとマス目 縦H、横Wマスのマス目がある。下からAマス以内かつ左からBマス以内のマス目には入れない。左上から出発して左か下にのみ移動する時、右下にたどり着く道は何通りあるかという問題。答えは$10^9+7$で割った余りで求める。 入力 H W A B 答え 答えを$10^9+7$で割った余りを出力 単純なマス目であればよくある数学の問題で、H+W-2会の移動のうち、W-1回の下への移動を何回目で行うかを求める問題、つまりH+W-2からW-1を選ぶ組み合わせの問題 今回もこれを応用 いかなる道であれ、上からH-A-1マス目で左からBマス目より右のマスのどこかを通る。 よって左上を(0,0)として、(0,0)→(H-A-1,i)→(H-A,i)→(H-1,W-1)(B≦i≦W-1)の順番に通る道順を数えて、それぞれ足していけばいい。 組み合わせは$\frac{n!}{r!(n-r)!}$で計算 やっぱり数字がごっちゃになるのでちゃんと図を書く、かつ例題は1のような小さいのではなく、2のような中くらいの大きさの方が頭の整理しやすいかも 階乗はmath.factorial(n)で計算できる。ループで回すよりははるかに早い ただし正面から計算すると数字が大きくなりすぎてオーバーフローする よって逆元を利用する 逆元とはa÷b=a×b^{-1}(mod p)となるようなb^{-1}でb^{p-2}(mod p)(pは素数)で求められる 逆元について、詳しくはググって 先にx!とその逆元を求めるとコスパ良し 冪剰余はpow()の第3引数を設定することで効率よく求められる。自作でやるとめっちゃ重い処理になるので注意 pow()の返り値はfloat型なので注意 全部求めるならx!はいちいちmath.factorial(x)で求めないで、fact[x-1]*xで求めれば良い 解答 H,W,A,B=map(int,input().split()) fact=[[]for i in range(H+W-2)] inv=[[] for i in range(H+W-2)] fact[0]=1 inv[0]=1 ans=0 for i in range(1,H+W-2): fact[i]=fact[i-1]*i%(10**9+7) inv[i]=pow(fact[i],10**9+5,10**9+7) for i in range(B,W): ans+=(fact[H-A+i-1]*fact[A+W-i-2]*inv[i]*inv[H-A-1]*inv[A-1]*inv[W-i-1]) ans=ans%(10**9+7) print(int(ans))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python演算処理】単位球面(Unit Shere)を巡る数理②そして単位トーラス(Unit Torus)へ

考えてみれば、以下のアニメーションをちゃんと目で追える様になれば三角関数における$\cos(θ)$と$\sin(θ)$の働きを概ね理解出来たといえそうなのです。 【Python演算処理】単位球面を巡る数理①とりあえず描画してみる。 \cos(θ)=\frac{e^{θi}+e^{-θi}}{2}\\ \sin(θ)=\frac{e^{θi}-e^{-θi}}{2i} 最後のアニメーションを目で追う際には、以下の円筒アニメーションが参考となるかもしれません。 【Python演算処理】単位円筒を巡る数理 極座標系(Polar Coordinate System)表示の二次元版/3次元版 ここで改めて極座標系(Polar Coordinate System)表示の二次元版/三次元版について振り返ってみたいと思います。 二次元空間(円弧)におけるデカルト座標系(x,y)と極座標系(r,φ)の相互変換 原点(0,0)からの距離$r=\sqrt{x^2+y^2}$ $φ=-(\arctan^2(x,y)-\frac{π}{2})$ $x=r×\cos(φ)$ $y=r×\sin(φ)$ 三次元空間(球面)におけるデカルト座標系(x,y,z)と極座標系(r,φ,θ)の相互変換 原点(0,0,0)からの距離$r=\sqrt{x^2+y^2+z^2}$ $φ=-(\arctan^2(x,y)-\frac{π}{2})$ $θ=-(\arctan^2(\sqrt{x^2+y^2+z^2},z)-\frac{π}{2})$ $x=r×\sin(θ)\cos(φ)$ $y=r×\sin(θ)\sin(φ)$ $z=r×\cos(θ)$ また、球の体積を求める三重積分は以下の様に規定されています。 EMANの物理学>物理数学>線積分 EMANの物理学>物理数学>面積分と体積分 EMANの物理学>物理数学>ヤコビアン \int U(x,y,z)dV=\int_{0}^{2π}\int_{0}^{π}\int_{0}^{1}U(r,θ,φ)r^2\sinθdrdθdφ\\ =\int_{0}^{2π}\int_{0}^{π}\int_{0}^{1}\begin{vmatrix} \frac{∂x}{∂r} & \frac{∂x}{∂θ} & \frac{∂x}{∂φ}\\ \frac{∂y}{∂r} & \frac{∂y}{∂θ} & \frac{∂y}{∂φ}\\ \frac{∂z}{∂r} & \frac{∂z}{∂θ} & \frac{∂z}{∂φ} \end{vmatrix} drdθdφ 積分範囲の積み重ね方に注目すると以下となってます。 【Python演算処理】「N次球概念導入による円/球関係の数理の統合」? まずは角度$φ=0→2π$の範囲で積分して円周$2πr$に到達。 さらに角度$θ=0→π$の範囲で積分を重ね球の表面積$4πr^2$に到達。 さらに半径$r=0→1$の範囲で積分を重ね球の体積$\frac{4}{3}πr^3$に到達。 こうした計算では「θがφの半分になる事」事が自明の場合(Trival Case)として扱われていますが、実はこの辺りが曲者だったりするのです。 そして単位トーラス(Unit Torus)へ 結論から先に述べると、どうやら上掲の円/球面座標系はトーラス座標系(Torus Coordinates System)のごく一部を援用しているに過ぎない様なのです。大半径(Major Radius)Rと小半径(Minor Radius)の組み合わせで表現されるそれとデカルト座標系の関係は、一般に媒介変数t,p(0≦t≦2π,0≦p≦2π)を用いて以下の様に表されます。 x=$R×\cos(t)+r×\cos(p)\cos(t)$ y=$R×cos(t)+r×\cos(p)\sin(t)$ z=$r×\sin(p)$ 上掲の円/球面座標系との関係は以下。 大半径R=0,小半径r=1の時、単位球面(Unit Sphere)を二重に描く。従って経緯度法(経度-180度→+180度に対して緯度-90度→+90度)や3次元極座標系(水平角φ=-π→+πに対して垂直角θ=0→π)は適用範囲を半分に減らしてなお単位球面(Unit Sphere)上の全座標を示せる訳である。 逆に大半径R=1,小半径r=0の時、Z=0となってただの単位円(Unit Circle)となる。 そう、両者が本来連続しているという事は隠された中間状態が存在するのです。特に大半径R=1,小半径r=1の場合が重要なので、これを単位トーラス(Unit Torus)と呼び分けたいと思います。 Rの場合 トーラス構造と古典数学】「単位円筒」から「トーラス構造」へ pythonの場合 %matplotlib nbagg import math as m import cmath as c import numpy as num import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D import matplotlib.animation as animation #円柱データ作成 c0=num.linspace(0,m.pi*120,1201,endpoint = True) s0=[] for nm in range(len(c0)): s0.append(complex(m.cos(c0[nm]),m.sin(c0[nm]))) s1=num.array(s0) z0=num.linspace(-1,1,1201,endpoint = True) #「曲率」を計算 cv0=num.linspace(-1,1,1201,endpoint = True) cv1=num.sqrt(1-cv0**2) #トーラス計算 s2=s1*(1+cv1) s3=s1*(1-cv1) #小半径追加 MiHz0=num.linspace(-1,1,61,endpoint = True) MiHz=MiHz0[::-1] MiPx=1+num.sqrt(1-MiHz**2) MiMx=1-num.sqrt(1-MiHz**2) MiHy=num.repeat(0,61) #単位円データ作成 u0=num.linspace(0,m.pi*2,61,endpoint = True) u1=[] for nm in range(len(u0)): u1.append(complex(m.cos(u0[nm]),m.sin(u0[nm]))) uc=num.array(u1) uz0=num.repeat(-1,61) uz1=num.repeat(-0,61) uz2=num.repeat(1,61) #グラフ表示 plt.style.use('default') fig = plt.figure() ax = Axes3D(fig) #関数定義 def unit_cylinder(n): plt.cla() #円柱描画 ax.plot(s2.real,s2.imag,z0,color="gray",lw=0.5) ax.plot(s3.real,s3.imag,z0,color="gray",lw=0.5) #スポーク描画 #for num in range(len(uc)): # ax.plot([0,uc[num].real],[0,uc[num].imag],[0,0],color="gray",lw=0.5) #for num in range(len(uc)): # ax.plot([0,uc[num].real],[0,uc[num].imag],[1,1],color="gray",lw=0.5) #for num in range(len(uc)): # ax.plot([0,uc[num].real],[0,uc[num].imag],[0,0],color="olivedrab",lw=0.5) #単位円描画 ##ax.plot(uc.real,uc.imag,uz0,color="red",lw=1) ax.plot(uc.real,uc.imag,uz1,color="blue",lw=1) ##ax.plot(uc.real,uc.imag,uz2,color="blue",lw=1) #実数線追加 ax.plot([0,0],[0,0],[-1,1],color="black",lw=1) ax.plot([0,2],[0,0],[-1,-1],color="black",lw=1) ax.plot([0,1],[0,0],[0,0],color="blue",lw=1) ax.plot([1,1],[0,0],[0,1],color="red",lw=1) ax.plot([0,2],[0,0],[1,1],color="black",lw=1) ax.plot([2,2],[0,0],[-1,1],color="black",lw=1) #小半径描画 ax.plot(MiPx,MiHy,MiHz,color="red",lw=1) ax.plot(MiMx,MiHy,MiHz,color="red",lw=1) #諸元追加 ax.set_ylim([-2.1,2.1]) ax.set_xlim([-2.1,2.1]) ax.set_zlim([-2.1,2.1]) ax.set_title("Unit Cylinder") ax.set_xlabel("Real") ax.set_ylabel("Imaginal") ax.set_zlabel("Cycle") # グラフを回転 ax.view_init(elev=25, azim=Time_code[n]) Time_code0=num.arange(0,360,6) Time_code=Time_code0[::-1] #unit_cylinder(len(s1)) #plt.show() ani = animation.FuncAnimation(fig, unit_cylinder, interval=50,frames=len(Time_code)) ani.save("output531.gif", writer="pillow") 今度は「ヘモグロビン」が現れた? 健康診断で、ヘモグロビン値が高い・低いと言われたら? そしてどうやら、かかる単位トーラス概念の登場こそが偶奇(Patity)の概念の出現と密接に結びついている様なのです。 【数理考古学】とある実数列の規定例①等差数列から加法整数群へ 0.5次元と1次元の狭間 【数理考古学】とある実数列の規定例③オイラーの等式が意味するもの? 0.5次元の到達地点は2次元/3次元の極座標系の検知である。 自明の場合(Trival Case)として次の段階において獲得済の半径と垂直に交わる新たな半径を伸ばしたのがトーラス座標系といえよう。しかし実際の次段階となる1次元は数直線を軸とする円筒座標系なのである。 この矛盾を解決するにはトーラス座標系を分枝切断して「アイロンを当てた様に真っ直ぐに伸ばした上で」改めて連続性を確保しないといけません。ならば、どうやって? それについては以下続報…
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】pandasというライブラリを使ってCSVファイルを読み込み、積上げ棒グラフを作成する。

pythonを使用してExcelファイルの操作を勉強しています。 本日の気づき(復習)は、CSVの読み込みとグラフ作成に関しての続きです。 pythonでExcelを操作するため、openpyxlというパッケージを使用しています。 売上.csv 部門,1月,2月,3月 商品A,600,700,800 商品B,4100,3800,4500 商品C,2900,1800,3000 商品D,800,900,1000 商品E,600,550,720 前回同様、上記のようなCSVファイルを読み込み この様な積上げ棒グラフを作り貼り付けたいです。 BarChartオブジェクトのgrouping属性と、overlap属性 bar.grouping = 'stacked' bar.overlap = 100 基本的には棒グラフを作成する場合と同じですが grouping属性を'stacked'、overlap属性を 100 にすれば、積上げ棒グラフになるのですが 今回の表はもう一工夫必要です。 グラフの参照データを、行/列で切り替え bar.add_data(Referenceオブジェクト, from_rows=True, titles_from_data=True) 先ずは、元となるデータの範囲を変更して 上記のように「from_rows=True」を記述すると グラフの参照データを、行/列で切り替える事が出来ます。 最終的なコード import pandas as pd from openpyxl import Workbook from openpyxl.chart import BarChart, Reference from openpyxl.utils.dataframe import dataframe_to_rows wb = Workbook() ws = wb.active # CSVファイル読込み df = pd.read_csv('売上.csv', encoding='utf-8') for row in dataframe_to_rows(df, index=None, header=True): # データを1行ずつ追加 ws.append(row) # 棒グラフを選択 bar = BarChart() # 横棒グラフに設定 bar.type = 'col' # 積上げ棒グラフに変更 bar.grouping = 'stacked' bar.overlap = 100 # グラフのデータ範囲を設定 data = Reference(ws, min_col=1, min_row=2, max_col=ws.max_column, max_row=ws.max_row) # ラベルの範囲を設定 labels = Reference(ws, min_col=2, min_row=1, max_col=ws.max_column) # グラフにデータを追加(参照方法を行列で切り替え) bar.add_data(data, from_rows=True, titles_from_data=True) # 縦軸のラベルを追加 bar.set_categories(labels) # 系列ごとにグラフの色を変更 bar.varyColors = True # 凡例項目の位置を設定 bar.legend.position = 'b' # 横軸(グラフ上では縦軸)タイトルを追加 bar.x_axis.title = '月度' # 縦軸(グラフ上では横軸)タイトルを追加 bar.y_axis.title = '売上高(千円)' # グラフのタイトルを追加 bar.title = '部門別売上高' # 棒グラフをシートに追加 ws.add_chart(bar, 'A9') wb.save('部門別売上_積上げ棒グラフ.xlsx') 前回、前々回と同じデータを基にグラフを作っていますが 表現方法でだいぶ印象が変わりますね。 どこで使うモノなのかを考えながら作成するといい感じですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FastAPI/SQLAlchemy初心者が1日で理解したこと(1週間だけ頑張る Day3of9)

はじめに 1週間N(E)ETになった社会人の学習記録です。 今日はCRUDのサンプルを写経し、使い方を覚えます。 私のレベルは、「RailsのTutorialはやったことある。Rails3が主流なときにRails2の改修をやらされて、情報が全然なくてトラウマになっている。」です。 使用技術 私の状況 python 読み書きできなくはないが、自分で一から書くならrubyを選ぶ SQLAlchemy 完全に初見。RailsのActiveRecordは触ったことある。 FastAPI 完全に初見。RailsのapiモードでHello Worldを書いたことある程度。 前日の記事(Dockerの学習)ではPostgreSQLのデータの永続化はできていないが、とりあえず先に進む。 CRUDの写経 Google先生に教えてもらった記事から、分かりやすそうなページを参考に写経する。 変更箇所 環境依存の所だけ自分の環境に合わせて適切に変更 値 旧 新 理由 DATABASE postgresql 同左 USER testuser postgres Dockerイメージのデフォルト PASSWORD secret p@assw0rd お好み設定 HOST localhost db docker-compose.ymlのservices内の名前がhost名になる1 PORT 5432 同左 DB_NAME test_db document_db お好み設定 Dockerfileの編集 SQLAlchemyに必要なパッケージとして以下追加する。 怒られたのでとりあえず入れた感じ。 Railsのように最初にドカッと入るよりは自分で選択して入れるほうが学習にはよさげ。 管理 名前 備考 pip sqlalchemy 今回使いたいやつ pip psycopg2-bynary PostgresSQL用のpthonライブラリlibpq5必要 pip databases 非同期用のライブラリ?後で調べるかも apt libpq5 PostgresSQL用のCライブラリ 所感 とりあえず、コピペしただけだけど動いてなにより。 次回は、今回作ったFastAPIに対して、Reactを通じてCRUDできることを目標にします。 免責事項(言い訳) 上記の技術については初心者なので、あてにしないように!!! 今後の予定のための参考 多対多 このまどろっこしさは解消されないんですかね。 Word 「python-docx」は使ったことあるので、たぶん行ける。 ちなみにExcelは「openpyxl」を使い、pdfは「pyPDF2」と「reportlab」を使い分ける。 E-Mail とりあえずGoogle先生から以下を教えてもらったが、テストメールを準備するのがだるいので、ここには書かないかも。 https://qiita.com/dyoshikawa/items/05d627b962da35f7d5b6 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アノテーションツールELANのデータをpythonからテキストデータにエクスポート

この記事はこちらの記事を読んでくれた人向けです. ELANのアノテーションデータを分析したいな! という時は日常的にありますよね. 何はともあれ,ELANのデータからcsvでもtsvでも,何らかのテキストデータへエクスポートしたいところです.そのためにはELANのGUI上からメニューをポチポチすればよいのですが,手元にたくさんのELANのファイル(*.eaf)がある場合はいささか面倒です. 当エントリでは複数のeafファイルを一括でテキストデータにエクスポートできるスクリプトを組んだ結果を報告します. 環境など eafファイルは所詮xmlなので自前でパースすればよいのですが1,それも面倒なのでよさそうなパッケージを探すことにしました. そこでよさげパッケージであるpympiを使用することにします.詳細は以下URLを参照. 以上より,環境としてpython3,パッケージとしてpympi,またpandasも使用することにします. また以下のtest.eafを入力とすることにしました.これは3つの注釈層(tier1, tier2, tier3)が定義されたファイルで,ELANの基本的な注釈付け機能しか使用していないような簡単なものです.展開すれば詳細が確認できます. test.eaf test.eaf <?xml version="1.0" encoding="UTF-8"?> <ANNOTATION_DOCUMENT AUTHOR="" DATE="2021-04-12T18:18:38+09:00" FORMAT="3.0" VERSION="3.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.mpi.nl/tools/elan/EAFv3.0.xsd"> <HEADER MEDIA_FILE="" TIME_UNITS="milliseconds"> <MEDIA_DESCRIPTOR MEDIA_URL="file:///D:/Dropbox/Qiita/ELAN/elan-example1.wav" MIME_TYPE="audio/x-wav" RELATIVE_MEDIA_URL="../ELAN/elan-example1.wav"/> <PROPERTY NAME="URN">urn:nl-mpi-tools-elan-eaf:bc21cfb1-4991-4fce-9374-e6e106fb9b3e</PROPERTY> <PROPERTY NAME="lastUsedAnnotationId">6</PROPERTY> </HEADER> <TIME_ORDER> <TIME_SLOT TIME_SLOT_ID="ts1" TIME_VALUE="1360"/> <TIME_SLOT TIME_SLOT_ID="ts2" TIME_VALUE="2550"/> <TIME_SLOT TIME_SLOT_ID="ts3" TIME_VALUE="4600"/> <TIME_SLOT TIME_SLOT_ID="ts4" TIME_VALUE="5340"/> <TIME_SLOT TIME_SLOT_ID="ts5" TIME_VALUE="9360"/> <TIME_SLOT TIME_SLOT_ID="ts6" TIME_VALUE="10340"/> <TIME_SLOT TIME_SLOT_ID="ts7" TIME_VALUE="17615"/> <TIME_SLOT TIME_SLOT_ID="ts8" TIME_VALUE="18607"/> <TIME_SLOT TIME_SLOT_ID="ts9" TIME_VALUE="19037"/> <TIME_SLOT TIME_SLOT_ID="ts10" TIME_VALUE="19546"/> <TIME_SLOT TIME_SLOT_ID="ts11" TIME_VALUE="19670"/> <TIME_SLOT TIME_SLOT_ID="ts12" TIME_VALUE="20779"/> </TIME_ORDER> <TIER LINGUISTIC_TYPE_REF="default-lt" TIER_ID="tier1"> <ANNOTATION> <ALIGNABLE_ANNOTATION ANNOTATION_ID="a1" TIME_SLOT_REF1="ts7" TIME_SLOT_REF2="ts8"> <ANNOTATION_VALUE>ハンバーグ</ANNOTATION_VALUE> </ALIGNABLE_ANNOTATION> </ANNOTATION> <ANNOTATION> <ALIGNABLE_ANNOTATION ANNOTATION_ID="a2" TIME_SLOT_REF1="ts9" TIME_SLOT_REF2="ts10"> <ANNOTATION_VALUE>食べたい</ANNOTATION_VALUE> </ALIGNABLE_ANNOTATION> </ANNOTATION> <ANNOTATION> <ALIGNABLE_ANNOTATION ANNOTATION_ID="a3" TIME_SLOT_REF1="ts11" TIME_SLOT_REF2="ts12"> <ANNOTATION_VALUE>ひき肉買わなきゃ</ANNOTATION_VALUE> </ALIGNABLE_ANNOTATION> </ANNOTATION> </TIER> <TIER LINGUISTIC_TYPE_REF="default-lt" TIER_ID="tier2"> <ANNOTATION> <ALIGNABLE_ANNOTATION ANNOTATION_ID="a4" TIME_SLOT_REF1="ts1" TIME_SLOT_REF2="ts2"> <ANNOTATION_VALUE>長ネギ</ANNOTATION_VALUE> </ALIGNABLE_ANNOTATION> </ANNOTATION> <ANNOTATION> <ALIGNABLE_ANNOTATION ANNOTATION_ID="a5" TIME_SLOT_REF1="ts3" TIME_SLOT_REF2="ts4"> <ANNOTATION_VALUE>ネギ</ANNOTATION_VALUE> </ALIGNABLE_ANNOTATION> </ANNOTATION> </TIER> <TIER LINGUISTIC_TYPE_REF="default-lt" TIER_ID="tier3"> <ANNOTATION> <ALIGNABLE_ANNOTATION ANNOTATION_ID="a6" TIME_SLOT_REF1="ts5" TIME_SLOT_REF2="ts6"> <ANNOTATION_VALUE>すし酢やん</ANNOTATION_VALUE> </ALIGNABLE_ANNOTATION> </ANNOTATION> </TIER> <LINGUISTIC_TYPE GRAPHIC_REFERENCES="false" LINGUISTIC_TYPE_ID="default-lt" TIME_ALIGNABLE="true"/> <LANGUAGE LANG_DEF="http://cdb.iso.org/lg/CDB-00130975-001" LANG_ID="und" LANG_LABEL="undetermined (und)"/> <CONSTRAINT DESCRIPTION="Time subdivision of parent annotation's time interval, no time gaps allowed within this interval" STEREOTYPE="Time_Subdivision"/> <CONSTRAINT DESCRIPTION="Symbolic subdivision of a parent annotation. Annotations refering to the same parent are ordered" STEREOTYPE="Symbolic_Subdivision"/> <CONSTRAINT DESCRIPTION="1-1 association with a parent annotation" STEREOTYPE="Symbolic_Association"/> <CONSTRAINT DESCRIPTION="Time alignable annotations within the parent annotation's time interval, gaps are allowed" STEREOTYPE="Included_In"/> </ANNOTATION_DOCUMENT> 実装 かなり簡単にできます.pympiは優秀です. parse_eaf.py import pympi.Elan import pandas as pd def eaf_to_df( eaf: pympi.Elan.Eaf ) -> pd.DataFrame: tier_names = list( eaf.tiers.keys() ) def timeslotid_to_time( timeslotid: str ) -> float: return eaf.timeslots[ timeslotid ] / 1000 def parse( tier_name: str, tier: dict ) -> pd.DataFrame: values = [ (key,) + value[:-1] for key, value in tier.items() ] df = pd.DataFrame( values, columns=[ "id", "start", "end", "transcription"] ) df["start"] = df["start"].apply( timeslotid_to_time ) df["end"] = df["end"].apply( timeslotid_to_time ) df["ID"] = df.apply( lambda x: f"{tier_name}-{x.name}", axis=1 ) df = df.reindex( columns=["ID", "start", "end", "transcription"] ) return df dfs = [ parse(tier_name=name, tier=eaf.tiers[name][0]) for name in tier_names ] df = pd.concat( dfs ) df = df.sort_values( "start" ) df = df.reset_index( drop=True ) return df if __name__ == '__main__': src = r"test.eaf" eaf = pympi.Elan.Eaf( src ) df = eaf_to_df( eaf ) print( df ) df.to_csv( r"test.txt", sep="\t", index=False ) ちなみに,出力されるファイルは以下のようなものです.個人的な趣味として,注釈それぞれに一意のIDを割り当てています. test.txt ID start end transcription tier2-0 1.36 2.55 長ネギ tier2-1 4.6 5.34 ネギ tier3-0 9.36 10.34 すし酢やん tier1-0 17.615 18.607 ハンバーグ tier1-1 19.037 19.546 食べたい tier1-2 19.67 20.779 ひき肉買わなきゃ 終わりに 以上より,eafファイルをテキストデータにエクスポートできるスクリプトを紹介しました.複数の入力ファイルを一括で処理したい場合は,適当にfor文でも書いてください. またeafファイルのバージョンが3.0だと,pympiが文字列Parsing unknown version of ELAN spec... This could result in errors...をコンソールに出力しました.基本的な機能だけが使用されたeafファイルなら問題なくパースはできるようですが,最新の機能が使用されたeafファイルでどのような挙動を示すかはわかりません. 筆者はあほなので昔そうしましたが ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

print関数について

初めに 初学者です。学習した内容をアウトプットしていくことで定着するのが目的です。 pythonについて phytonとは機械学習やデータ分析などの分野で人気があるプログラミング言語です。 特徴 1.文法がシンプルで読みやすい 2.専門的なライブラリが豊富にあるそうです。 私のような初学者でもとっつきやすいそうです。 具体的にpythonでできることは ・組み込み開発 ・wedアプリケーション ・デスクトップアプリケーション ・機会学習 などがあるそうです。 print関数を使い文字を表示させます ではpythonで用意されている関数を使い文字列を表示させていきたいと思います。 ちなみに関数とはpythonに初めから用意されているもので、「コンピューターに命令できるもの」だそうです。 そして今回使うprintは組み込み関数といい、最初から用意されている関数のことです。 print関数とは print関数とはコンピューターに表示しろと命令することができる関数です。恐らくrubyで言うところのputsだと思います。 書き方は print('表示したい文字列') '(シングルクォート)で囲んだ文字列が表示されます。ちなみに”(ダブルクォート)でもオッケーだそうです。 ターミナルに書いていきと以下のようになります。 'ハロー'(シングルクォート)で囲われているハローが出力されました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ジョークアプリ】壊れたファイルを生成するツール

はじめに 大学の課題が終わらない。そんなことありますよね? そんな時に役立つツールを開発しました。 (ジョークアプリです。このアプリの使用により起きたトラブルに当方は一切責任を負いません。) 概要  拡張子、ファイルサイズ、ファイル名を指定することで、あたかも提出する際に壊れてしまったかの様なファイルを作成します。締め切りに間に合わなくてもとりあえず出したという既成事実を得られるのではないでしょうか?  そんなコードを紹介します。  まずは雛形であるmain関数を作成し、そこから文字化け作成用の関数をimportすることで動作する仕組みです。 main.py import os.path import mojibake as moji def main(func): file_name = input("ファイル名を入力してください:") ext = input("拡張子を入力してください:") size = float(input("ファイルサイズを入力してください(MB):")) size *= 1024 ** 2 file_size = 0 while(size > file_size): file = file_name + '.' + ext f = open(file, "a") f.write(func) f.write('\n') file_size = os.path.getsize(file) f.close() if __name__ == '__main__': main(moji.yabainari)  これは、importした関数に引数を与えるだけのプログラムなので文字化けアルゴリズムであるパッケージは自分で用意する必要があります。mainにあたえる引数を変えてお楽しみください。  文字化けアルゴリズムの作成例を2つ書きました。 例1  ダイバージェンス1%の向こう側へいけずに、課題提出できなかった人がお使いください。 mojibake.py def suzuha(size, file): s = '失敗した' # 任意の文字列 s_size = 18 # 文字列のbyte数 with open(file, 'w', encoding='utf-8') as f: i = 0 while size > i: f.write(s) i += s_size else: f.write('わたしは失敗した') # 任意の文字列にするときはelse文をコメントアウト このコードの出力結果をテキストエディタで見てみましょう。 最後だけ処理を変えているため、多少ファイルサイズは変わります。大まかなサイズがあっていればよいでしょう。 pdfビューアで開くとこんな感じです。 バッチリ壊れてます。 メモ帳で開かれたら失敗したどころではなくいろいろと終わります。 任意の文字列とそのサイズに書き換えることで好きな言葉でファイルを埋められますが、教授への悪口などは絶対にやめましょう。  例2  例1では文字化けと言うより、テキストファイルを拡張子を変えて開くことで、あたかも壊れているかのように見せるコードでした。例2ではよりリアルな文字化けを引き起こすプログラムを作成しました。そんなコードがこちらです! mojibake.py def yabainari(size, file): import struct, random with open(file, 'wb') as f: for i in range(0, int(size)): f.write(struct.pack('B', random.randint(0, 255))) ランダムなbit列を作成して、バイナリモードで書き込むことで文字化けを引き起こします。 実際に出力結果をテキストエディタで見てみましょう。 完璧ですね。もちろんpdfビューアでも開けません。 まとめ  こんなコード書いてる暇があったら、課題にとりかかりましょう。 今回のコードはgithubにものせています -> https://github.com/yoh333/broken_maker 最後に一言… 課題は期限内に!(戒め)  
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MYSQLdbのexecuteメソッドに文字列を代入する時の注意点

メモ帳がわりの記事 python側のコードから直接SQLを書いてMySQLに投げる時の注意点 これはエラーにならない hoge = 1 cursor.execute(""" SELECT 〜いろんなカラム〜 FROM てーぶる名 WHERE カラム1=%s ; """, (hoge,)) これはエラーになる hoge = ' IN (0, 1)' cursor.execute(""" SELECT 〜いろんなカラム〜 FROM てーぶる名 WHERE カラム1%s ; """, (hoge,)) 下記のようなエラーが表示される。改行コード\nが含まれている。 文字列が入ると改行コードも含まれてしまうようになる? (よくわからないので、詳しい人は教えていただきたい django.db.utils.ProgrammingError: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''=1 '\n AND latitude IS NOT NULL\n AND カラム1 IS NOT NULL' at line 4") なので下記のようにするとよい。 重要なポイントは先にquery文を変数に格納することだそうです。 (query = f"""のところ) hantei = True hoge = ' IN (0, 1)' if hantei else ' =1 ' query = f""" SELECT いろんなカラム FROM テーブル名 WHERE カラム1{hoge}; """ cursor.execute(query) 文字列を入れると改行コードを急に含むようになるとか、その辺り良い感じにやってほしい(文句) この記事と関係ないけどf''''''はとても便利
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python向けIDE「PyCharm」のインストールと日本語化

はじめまして。 富山県で小学生~高校生向けのプログラミング教室を運営している屋鋪(やしき)です。 当教室では、今年度からPyCharmというソフトを使ってPythonを学習してもらっています。 この記事では、自宅等にあるPCにPyCharmをインストールして、Pythonを動かすところまでを解説します。 なお、PyCharmのインストールには「JetBrains Toolbox App」を使用します。 下記手順は、Windows10でのインストール手順となっていますが、他のOS(macOS等)でもほぼ同じ手順でインストール可能です。 また、下記手順ではブラウザにEdgeを用いていますが、Chrome等のブラウザを用いた場合もほぼ同じ手順でインストールが可能です。 本記事では、日本語化にプラグイン「Japanese Language Pack/日本語言語パック」を使います。 これはバージョン2020.1以降、非公式の日本語化ツールを使用すると起動しない等の不具合が報告されているからです。 詳しくはサムライズム社の下記ページ等をご確認ください https://samuraism.com/jetbrains/localization PyCharmのインストール ブラウザを起動し、下記URLへ移動する https://www.jetbrains.com/ja-jp/toolbox-app/ 「ダウンロード」のボタンを押す。 edgeの場合は、画面下部の「実行」をクリックする。  Chromeの場合は直接ダウンロードが開始されるので、ダウンロードの完了後、画面下部のファイルをクリックする。 4.インストール画面が表示されるので、「インストール」をクリックする 5.インストールが始まるので、しばらく待つ 6.インストールが完了すると画面が切り替わるので、「JetBrains Toolboxを実行」にチェックを入れて、「完了」をクリックする 7.画面右下のタスクトレイにToolBoxのアイコンがあるのでクリックする 8.最初にToolBoxのアイコンをクリックしたときだけ、User Agreement(利用規約)が表示されるので、内容を確認して「Accept」をクリック ※「Allow sending……」は『使用状況の統計情報をJetBrains社に匿名で送るか』についてなので、気にする人はチェックを外して、JetBrains社に貢献したい人はチェックを入れてください 9.インストール可能なアプリの一覧が表示されるので、PyCharmの無料版である「PyCharm Community」をクリック ※「PyCharm Professional」は有料版で30日間の無料体験が可能です。学生の方が所有する個人PCや、学校等のPCへのインストールは申請すると無料や割引を適用できる場合がありますが、ここでは説明を割愛します。 10.インストールが始まるとプログレスバーが表示されるので、完了まで待つ 11.インストールが完了されたら、「PyCharm Community」をクリックする ※次回以降PyCharmを起動するときも、この方法で起動してください。 12.最初にPyCharmを起動したときだけUser Agreement(利用規約)が表示されるので、内容を確認して「I confirm……」にチェックを入れて「Continue」をクリック 13.「Data Charing」の画面は『使用状況の統計情報をJetBrains社に匿名で送るか』についてなので、気にする人は「Don't Send」を、JetBrains社に貢献したい人は「Send Anonymous Statistics」をクリックしてください 14.起動するまでしばらく待つ ※画面はバージョンによって多少異なります 15.起動が完了すると、下のような画面が表示されます。 英語ではありますが、このままでも十分使うこともできます。 表示を日本語にしたい場合は次の手順に進んでください。 PyCharmの日本語化 1.起動後の画面左側にある「Plugins」をクリックし、中央上部の検索欄に「japanese」と入力する。 2.検索結果にある「Japanese Language Pack/日本語言語パック」の「install」ボタンをクリックする 3.インストール完了後、ボタンが「Restart IDE」に変わるのでクリックする 4.再起動を確認する画面が表示されるので、「Restart」をクリックする 5.起動されるまでしばらく待つ 6.再起動が完了すると、表示が日本語に切り替わっているのが確認できます PyCharmでPythonを実行する ※以下、日本語化した前提で手順を説明します。 1.「新規プロジェクト」をクリックします 2.新規プロジェクトの設定画面が表示されます。 3.「場所」にファイルの格納先を設定します。初期状態で「pythonProject」の部分が選択されているので、この部分を適当なフォルダ名(例ではsample)に修正します。 修正が終わったら「作成」をクリックします。(他の部分は特に変更する必要はありません) 4.セットアップが始まるのでしばらく待ちます。 ※「今日のヒント」画面が表示される場合があります。時間つぶしに読むのもいいかもしません。表示されるのがわずらわしければ、「ヒントを表示しない」にチェックを入れて、「閉じる」をクリックします。 5.準備が整うと、以下のような画面になります。  左側にファイルの一覧、右側に「main.py」ファイルのソースコードが表示されています。 6.Pythonが正しく実行できるか確認してみましょう。右側のソースコード画面上で右クリックし、メニューから「実行」をクリックします。 7.正しくインストールできていれば、画面下部に『Hi, PyCharm』と表示されます。 以上で、インストールは完了です。 PyCharmの使い方など 最後に実行した「main.py」ファイルはなくても大丈夫なので、動作確認ができたら削除しても構いません。 画面左側のファイル一覧から、「main.py」上で右クリックし、「削除」をクリックすると削除されます。 確認画面が表示されるので、通常そのまま「OK」をクリックして問題ありません。 最後に、新規にpythonのファイルを作成する方法を確認します。 画面左側のファイル一覧で右クリックし、「新規」-「Pythonファイル」をクリックします。 ※フォルダ上で右クリックすることで、そのフォルダの中にファイルが作成されます。 適当なファイル名を入力してEnterを押します。 ファイルが作成され、画面右側に作成した空のファイルが表示されます。 PyCharmには、デバッグ機能を使ったステップ実行(ソースコードを1行ずつ実行していくこと)や、ブレークポイント(到達したらプログラムを一時停止する行)の設定ができるので、使いこなせればバグの原因究明をすごく効率的に行うことができます。 またGitを使ったバージョン管理(ソースコードの変更履歴を管理したり、複数人での編集を管理したりする仕組み)を、視覚的に(コマンドを使わずに)行えるので、家のPCと教室のPCでソースコードを共有することも手軽に行なえます。 PyCharmでPythonをどんどん試してみましょう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoで作成したアプリをHerokuにデプロイする際の環境変数の扱い

環境変数の扱いについて 自分自身も完全に忘れていたため、投稿。 settings.pyにSECRET_KEYなどを直書きして、そのままgitなどにpushすると、漏洩の問題が出てきます。 そこで環境変数を用いて、管理することができます。 django-environをインストールして使用します。 $ pip install django-environ Djangoプロジェクトのmanage.pyがある階層に.envファイルを作成して、その中に変数を書いていきます。 .env SECRET_KEY=xxx # settings.py内の値を代入 local環境では、先ほど作成した.envファイルを読み込み、記述した変数を使用します。 settings.py import environ if not HEROKU_ENV: env.read_env('.env') SECRET_KEY=env("SECRET_KEY") この.envファイルは.gitignoreに追加しておきます。 .gitignore .env ローカル環境では、これで環境変数を読み込むことができます。 しかし、.envファイルは追跡対象から外しているため、Heroku環境では使用することができません。 そのため、下記コマンドでherokuの環境変数に追加する必要があります。 $ heroku config:set SECRET_KEY=xxx # settings.py内の値を代入 これで、Heroku環境でも環境変数を使用することができます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonでOutlookメールをフォルダに振り分ける

はじめに  日々メールっていっぱいきますよね、ハラ立つほどに。  重要なメールに混じって読まずに速攻削除したいやつとか、一応見るけどやっぱ入らないやつとか、読んで処理したあととっておきたいやつとか。処理済みのメールをどうするかって話はいろいろ流派があると思うんですが、私はテーマごとのサブフォルダをつくってそこに放り込むことにしています。ちまちま1件ずつ移動するのもめんどくさいので自動でやってみようかと思い立ちました。  Outlookにも振り分け用の機能はあるんですが、結構めんどくさいんですよね設定が。メンテナンス性も悪いし。  ということで、pythonで作ってみました。振り分けの設定はjsonで記述しています。 前提 Windows10 Outlook2016 Anaconda3 Python3 win32comのインストール pip install pywin32 outlookフォルダ構成 メール用のフォルダを作っておきます。 Outlook xxx@aaa.com -受信トレイ -送信済みアイテム -削除済みアイテム -#01 カテゴリ1 -01 サブテーマ1 -#02 カテゴリ2 -#99 archive #01から下が振り分け用のフォルダです。フォルダ名先頭の#01は並び順用です。Outlookは数字より記号が先にくるので、#+番号とするときれいに並んでくれます。  なお、Outlookフォルダ検索は部分一致で検索するので、なるべくフォルダ名がかぶらないのが推奨です。同じ名前だったり、複数ヒットする条件にした場合は先にヒットした方を対象にします。 振り分け設定ファイルの書き方 JSONで記述します。 mail.json { "cat1":{ "subject":["カテゴリ1"], "address":[], "folder":"カテゴリ1", "unread":false }, "del":{ "subject":["怪しいタイトル","迷惑メール"], "address":["@ayashii.com"], "folder":"trush", "unread":true }, ... } "cat1"とか"del"は一意の識別子です。delだけは予約後になっており、削除(ゴミ箱)する対象です。ここの先頭に$をつけると処理対象外になります。 "subject"はメールの件名です。カンマ区切りでOR条件検索します。正規表現で記載できます。 ”address"は差出人または宛先のメールアドレスで、部分一致検索します。正規表現は使ってません。 "folder"は振り分け先のフォルダ名です。上から順に小フォルダまで潜っていき、最初に部分一致したフォルダを振り分け先とします。 "unread"は未読でも対象とするかどうかを設定します。迷惑メールなど問答無用で移動したければtrue、一応開封済みにするまでは振り分け対象都市な場合はfalseにします。 条件はOR条件です。AND条件には対応してません。経験上、これだけで結構いけます。あとは手動で。 pythonコード 初期化 mailmove.py import win32com.client import json import re # Outlook関係のオブジェクト初期化 app = win32com.client.Dispatch("Outlook.Application") root = app.Session.DefaultStore.GetRootFolder() ns = app.GetNamespace("MAPI") inbox = ns.GetDefaultFolder(6) messages = inbox.Items root:振り分け先フォルダの親フォルダ inbox:受信トレイ messages:受信トレイ内のメール一覧 サブ関数群 mailmove.py # Outlookのフォルダ検索 def findfolder(root, name): for folder in root.Folders: # フォルダ名の部分一致 if name in folder.name: print(folder.folderpath) return folder # サブフォルダも検索 ret = findfolder(folder, name) if ret is not None: return ret return None # 条件適合判定 def isit(message, subjects, addresses=[]): # 件名(正規表現)での判定 for subject in subjects: if re.search(subject,message.subject)!=None: return True # メールアドレス(部分一致)判定 for address in addresses: # 差出人名 if address in message.sendername: return True # 差出人アドレス if address in message.senderemailaddress: return True # 宛先 for recip in message.recipients: if address in recip.name or address in recip.address: return True return False # アーカイブ先フォルダ検索 def whichFolder(message, dic): # 会議案内などは除外 if message.messageClass == "IPM.Note": for key in dic: if isit(message, dic[key]["subject"], dic[key]["address"]): return key return None # JSONファイルから移動条件をロードする def load_json(filename="mail.json"): with open("mail.json", "r", encoding="utf-8") as f: dic = json.load(f) folders = {} for k in dic: # 識別名先頭が$ならコメント扱い if not k.startswith('$'): folders[k] = findfolder(root, dic[k]["folder"]) return dic, folders メインの処理部 mailmove.py # メールのアーカイブ処理メイン部 def move_mail( dic, folders, target_folder=inbox, view_none=True, view_move=True, view_delete=True ): i = 1 counter_move = 0 counter_remain = 0 counter_delete = 0 list_move = list() for message in target_folder.Items: key = whichFolder(message, dic) # print(key) if key == "del": counter_delete += 1 list_move.append((message, None)) elif ( key is None or folders[key] is None or folders[key].folderpath == target_folder.folderpath ): counter_remain += 1 if view_none: print(counter_remain, "none", message.subject) elif dic[key]["unread"] or not message.unread: counter_move += 1 if view_move: print(folders[key].name, message.subject) list_move.append((message, folders[key])) else: counter_remain += 1 if view_none: print("unread", message.subject) i += 1 for item in list_move: message = item[0] dest = item[1] if dest is None: if view_delete: print("delete", message.subject) message.delete() else: print(dest.name, message.subject) message.unread = False message.move(dest) print("moved:", counter_move, "delete:", counter_delete, "remain:", counter_remain) # アーカイブ処理を全アーカイブ対象フォルダに対して実行 def do_all_folder(dic, folders): for k in dic: print(k) if k != "del": move_mail(dic, folders, target_folder=folders[k], view_none=False) print("do all done.") 処理実行部 処理実行は3種類ほど用意してあります。 受信トレイ(inbox)に対して実行 通常はこれを実行します。 mailmove.py dic, folders = load_json() # 受信トレイ(inbox)に対して処理を行う場合 move_mail(dic,folders) 特定のフォルダに対して実行 特定フォルダのメールが増えてきた場合、さらに条件をつけて別フォルダにしたくなります。そんなとき、指定のフォルダに対して振り分け処理を再実行します。 mailmove.py dic, folders = load_json() # 特定のフォルダに対して処理を行う場合 tf = findfolder(root, "xx") move_mail(dic, folders, target_folder=tf) 全てのフォルダに対して実行 mail.jsonいじりすぎてわけがわからなくなったとき、全フォルダにたいして振り分け処理を実行してしまえ、って場合に使います。ただし、フォルダとメールが大量にあるとそれなりに時間かかるのでご注意を。 mailmove.py dic, folders = load_json() # 全てのフォルダに対して再処理を行う場合 do_all_folder(dic,folders) batファイル実行 バッチファイル作っておくと便利です。定期的に実行しようかと思ったのですが、メール一通り開封したあと実行したいので今のところ手動で起動しています。 プログラムはユーザプロファイルのフォルダ下のrepos/pyOfficeってフォルダに格納しています。  python環境はanaconda3で構築しているので、activate.batを先に実行しています。 mailmove.bat cd %USERPROFILE%\repos\pyOffice call activate.bat python mailmove.py pause
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Macでpythonダウンロード、仮想環境構築、OpenCVダウンロード

Python,venv,Opencvまで最短ルート 実行環境 macOS Catalina 10.15.7 zsh python 3.9.4 一行ずつ実行してください。 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" brew install python python3 -V mkdir [your directory(project)] cd [your directory(project)] python3 -m venv (new env name) source (new env name)/bin/activate pip install opencv-python deactivate 説明 Homebrewをダウンロードする /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" pythonをダウンロードする この時は明示的にpython3と書かなくてもpython3がダウンロードされる brew install python pythonのversionを確認 python -V スクリプト保存するための特定の場所を作り、移動する mkdir [your directory(project)] cd [your directory(project)] 仮想環境を作成し、起動 python3 -m venv (new env name) source (new env name)/bin/activate OpenCVダウンロード pip install opencv-python 仮想環境を終了 deactivate 注意点 pythonのversionは初めに入れたものに固定 pythonのversionごと変更したければconda使うのもあり 参照リンク
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでプログラミングを学ぶ 改訂版

参考書籍のchap18のエクササイズ18_2 Think Python: How to Think Like a Computer Scientist (English Edition) https://www.amazon.co.jp/dp/B018UXJ9EQ/ref=cm_sw_r_tw_dp_H69W6TFMGB5G29K61QKN 同内容を公開しています。 http://facweb.cs.depaul.edu/sjost/it211/documents/think-python-2nd.pdf 終了条件 改訂版が付いていた時 やって見て 答えをいろいろ見て自分なりに納得した回答です。 ソリューション ・問題がわかっていれば ・整理していれば→ メソッド名/変数名/実行順番/キーワードの値の動き ・ロジックがわかっていれば ・知識があれば ・ひとつひとつ実行していれば ・アウトプットイメージがしっかりしていれば 問題 ex 18_2 2つのパラメータを取るdeal_handsと呼ばれるDeckメソッドを記述します。 手と手あたりのカードの数。適切な数のを作成する必要があります オブジェクトを手札にし、手札ごとに適切な数のカードを配り、次のリストを返します。 回答 def deal_hands(self, num_hands, num_cards): """Takes two parameters, the number of hands and the number of cards per hand, and that creates new Hand objects, deals the appropriate number of cards per hand, and returns a list of Hand objects. """ hands = [] for h in range(num_hands): hand = Hand() for c in range(num_cards): hand.cards.append(self.cards.pop()) hands.append(hand) return hands コードの整理 class Card: Attributes: suit: integer 0-3 rank: integer 1-13 """ 変数 suit_names rank_names """ def __init__(self, suit=0, rank=2): def __str__(self): return '%s of %s' % (Card.rank_names[self.rank], Card.suit_names[self.suit]) def __eq__(self, other): return self.suit == other.suit and self.rank == other.rank def __lt__(self, other): return t1 < t2 class Deck: """ Attributes: cards: list of Card objects. """ def __init__(self): def __str__(self): return '\n'.join(res) def add_card(self, card): def remove_card(self, card): def pop_card(self, i=-1): return self.cards.pop(i) def shuffle(self): random.shuffle(self.cards) def sort(self): self.cards.sort() def move_cards(self, hand, num): class Hand(Deck): def __init__(self, label=''): def find_defining_class(obj, method_name): return None ロジック アウトプットイメージ  
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでプログラミングを学ぶ 改訂版 ex 18_2

参考書籍のchap18のエクササイズ18_2 Think Python: How to Think Like a Computer Scientist (English Edition) https://www.amazon.co.jp/dp/B018UXJ9EQ/ref=cm_sw_r_tw_dp_H69W6TFMGB5G29K61QKN 同内容を公開しています。 http://facweb.cs.depaul.edu/sjost/it211/documents/think-python-2nd.pdf 終了条件 改訂版が付いていた時 やって見て エクササイズ18_3は一旦スキップします。 →  Think Javaやってからでもいいと思います。 pyQかな? 基礎はやるけどDjangoは一旦恐れずに突き進もう。 19,20,21やったらもう一回チャレンジします。 答えをいろいろ見て自分なりに納得した回答です。 ソリューション ・問題がわかっていれば ・整理していれば→ メソッド名/変数名/実行順番/キーワードの値の動き ・ロジックがわかっていれば ・知識があれば ・ひとつひとつ実行していれば ・アウトプットイメージがしっかりしていれば 問題 ex 18_2 2つのパラメータを取るdeal_handsと呼ばれるDeckメソッドを記述します。 手と手あたりのカードの数。適切な数のを作成する必要があります オブジェクトを手札にし、手札ごとに適切な数のカードを配り、次のリストを返します。 回答 def deal_hands(self, num_hands, num_cards): """Takes two parameters, the number of hands and the number of cards per hand, and that creates new Hand objects, deals the appropriate number of cards per hand, and returns a list of Hand objects. """ hands = [] for h in range(num_hands): hand = Hand() for c in range(num_cards): hand.cards.append(self.cards.pop()) hands.append(hand) return hands コードの整理 class Card: Attributes: suit: integer 0-3 rank: integer 1-13 """ 変数 suit_names rank_names """ def __init__(self, suit=0, rank=2): def __str__(self): return '%s of %s' % (Card.rank_names[self.rank], Card.suit_names[self.suit]) def __eq__(self, other): return self.suit == other.suit and self.rank == other.rank def __lt__(self, other): return t1 < t2 class Deck: """ Attributes: cards: list of Card objects. """ def __init__(self): def __str__(self): return '\n'.join(res) def add_card(self, card): def remove_card(self, card): def pop_card(self, i=-1): return self.cards.pop(i) def shuffle(self): random.shuffle(self.cards) def sort(self): self.cards.sort() def move_cards(self, hand, num): class Hand(Deck): def __init__(self, label=''): def find_defining_class(obj, method_name): return None ロジック アウトプットイメージ  
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonによるDatabricks MLflowクイックスタートガイド

Databricksクイックスタートガイドのコンテンツです。 Quickstart Python | Databricks on AWS [2021/3/30時点]の翻訳です。 MLflowは、機械学習のエンドツーエンドのライフサイクルを管理するためのオープンソースプラットフォームです。MLflowはメトリクス(モデルのロスなど)、パラメーター(学習率など)、学習モデルをロギングするためのシンプルなAPIを提供し、トレーニング結果の分析、モデルのデプロイを容易にします。 本記事では以下を説明します。 MLflowのインストール ランの自動ロギング 結果の表示 追加のメトリクス、パラメーター、モデルの追跡 サンプルノートブック 追加情報 MLflowのインストール Databricks Runtime for Machine Learningを使用しているのであれば、既にMLflowはインストールされています。そうでない場合には、PyPIからMLflowパッケージをインストール(英語)します。 ランの自動ロギング MLflowは様々な機械学習フレームワークで記述されたトレーニングのコードを自動でロギングするmlflow.<framework>.autolog()を提供しています。トレーニング前に、このAPIを呼び出すことで、モデル特有のメトリクス、パラメーター、モデルのアーティファクトをロギングできます。 TensorFlow # Also autoinstruments tf.keras import mlflow.tensorflow mlflow.tensorflow.autolog() Keras # Use import mlflow.tensorflow and mlflow.tensorflow.autolog() if using tf.keras import mlflow.keras mlflow.keras.autolog() XGBoost import mlflow.xgboost mlflow.xgboost.autolog() LightGBM import mlflow.lightgbm mlflow.lightgbm.autolog() Scikit-learn import mlflow.sklearn mlflow.sklearn.autolog() PySpark pyspark.mlでチューニングを行なっている際には、メトリクス、モデルは自動的にMLflowにロギングされます。Apache Spark MLlib and automated MLflow tracking(英語)を参照ください。 結果の表示 機械学習コードを実行後、エクスペリメントランサイドバーを使って、結果を参照することができます。どのように、エクスペリメント、ラン、ノートブックのバージョンを参照するかについては、View notebook experiment(英語)を参照ください。 追加のメトリクス、パラメーター、モデルの追跡 直接MLflow Tracking logging APIs(英語)を呼び出すことで、追加情報をロギングすることができます。 数値のメトリクス import mlflow mlflow.log_metric("accuracy", 0.9) トレーニングパラメーター import mlflow mlflow.log_param("learning_rate", 0.001) モデル Scikit-learn import mlflow.sklearn mlflow.sklearn.log_model(model, "myModel") PySpark import mlflow.spark mlflow.spark.log_model(model, "myModel") XGBoost import mlflow.xgboost mlflow.xgboost.log_model(model, "myModel") TensorFlow import mlflow.tensorflow mlflow.tensorflow.log_model(model, "myModel") Keras import mlflow.keras mlflow.keras.log_model(model, "myModel") PyTorch import mlflow.pytorch mlflow.pytorch.log_model(model, "myModel") SpaCy import mlflow.spacy mlflow.spacy.log_model(model, "myModel") 他のアーティファクト(ファイル) import mlflow mlflow.log_artifact("/tmp/my-file", "myArtifactPath") サンプルノートブック 要件 Databricksランタイム6.4以降かDatabricksランタイムML6.4以降が必要です。 ノートブック PythonでMLflowによるトラッキングを始めるおすすめの方法は、autolog()APIを使うというものです。MLflowのオートロギング機能によって、一行追加するだけで、結果のモデル、パラメーター、メトリクスを自動で記録することができます。以下のノートブックでは、オートロギングを実行するための手順を説明しています。 MLflow autologging quickstart Python notebook(英語) それぞれのトレーニングで、何をロギングするのかを自分で設定したい、テーブルやグラフなど追加のアーティファクトを記録したい場合には、以下のノートブックで説明されているようにMLflowのロギングAPIを使うことができます。 MLflow logging API quickstart Python notebook(英語) 追加情報 MLflow overview(英語) Track machine learning training runs(英語) Run MLflow Projects on Databricks(英語) Log, load, register, and deploy MLflow Models(英語) Databricks 無料トライアル Databricks 無料トライアル
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】FlaskでpartialなAjax(htmlをAjaxで制御)

Ruby on the RailsでAjaxを実装して動的なサイトを作成していく場合、partialというhtmlファイルを部品化して、そのまま非同期処理する便利な方法がありますが、それと同じことをFlaskでもできないかと探ってみたところ、 実は簡単にできてしまった(その割に情報がなかった)ので、その備忘録と方法を置いておきます。 手順 結論から言えば、やっていることはRailsと同じで、イベントをjQueryで制御してから、partial(部品)化したテンプレートファイルをレスポンスさせるというものです。 レイアウトを作る まず、ページのレイアウト部分です。ここに制御用のスクリプトを置きます(どこに置いてもいいですし、jQuery以外の方法(FetchAPI、axiosなど)でも問題ありません。Ajaxがうまくいくと変数resにpartial(部品)が返ってくるようにします。 layout.html <head> <title>{{ title }}</title> <script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"> </script> <script> $(function(){ $("#city_no").on("change",function(){ let city_no = $(this).val(); $.ajax({ type: 'POST', url: '/_result', //問い合わせ先 data: {"city_no":city_no}, //選択された都市番号 }).done(function(res){ $('#result').html(res) }).fail(function(){ console.log("NG") }) }) }) </script> </head> <body> {% block content %} <!-- ここにメインコンテンツを書く --> {% endblock %} </body> </html> ビュー部分 これが選択フォーム部分となります。ここのフォームにactionプロパティに値を設定すると、通常ページのように画面遷移します。今回はこのページを遷移せずに、プルダウンの値を選択することで、#resultへpartial(部品)化したテンプレートを埋め込みます。 index.html {% extends "layout.html" %} {% block content %} <!-- ここにメインコンテンツを書く --> <content class="box"> <article class="box_left"> <form method="post"> <select id="city_no" name="city_no"> <option value=""> --対象の都市を選択-- </option> {% for option in options %} <option value="{{option.city_no}}">{{ option.city_nm }}</option> {% endfor %} </select> </form> <article id="result" class="box_right"></article> </content> {% endblock %} コントローラ部分 さて、一番肝心な部分がコントローラ部分です。sportsというデータベース内にあるmajorというテーブルに問い合わせ、プルダウンメニューを生成したり、照会結果をAjaxに返すようにします。 app.py #Mysqlに接続 from flask import Flask, request, render_template, jsonify import pymysql.cursors app = Flask(__name__) @app.route('/') def index(): db = Db() #インスタンス作成 db.con() #db接続 sql = 'select city_no,city_nm from major' rows = db.query(sql) #クエリ取得 return render_template('index.html',options=rows) class Db: rows = '' def con(self): conn = pymysql.connect( user='hogehoge', passwd='fugafuga', host='127.0.0.1', db='sports', charset='utf8', cursorclass=pymysql.cursors.DictCursor #json化処理 ) c = conn.cursor() self.c = c def query(self,sql): c = self.c c.execute(sql) #sql実行 rows = c.fetchall() #self.rows = rows return rows #レンダリング @app.route('/_result',methods=["POST"]) def result(): city_no = request.form['city_no'] rows = [] db = Db() #インスタンス作成 db.con() #db接続 sql = f'select city_nm,mlb,nba,nfl,nhl,mls from major where city_no = {city_no}' rows = db.query(sql) #クエリ取得 return render_template('result.html',rows=rows) if __name__ == "__main__": app.run(debug=False, host= '0.0.0.0', port=5000) ですが、この制御を見てもらえばわかると思いますが、Ajaxだからといって別段特別な記述をしているわけではなく、render_templateはこのようにAjaxにレスポンスを返せるみたいです(Pythonに精通した人なら常識なのかも知れませんが…) パーシャル部分 パーシャル(部品)部分の制御となります。これも特別なことをしているわけではないですが、違いはブロック化せず、素のままhtmlを記述している点です。 result.html <table border= "1" cellspacing="0"> <tr> <th>都市名</th> <th>MLB</th> <th>NBA</th> <th>NFL</th> <th>NHL</th> <th>MLS</th> </tr> <tbody> {% for row in rows %} <tr> <td>{{ row.city_nm }}</td> <td>{{ row.mlb }}</td> <td>{{ row.nba }}</td> <td>{{ row.nfl }}</td> <td>{{ row.nhl }}</td> <td>{{ row.mls }}</td> </tr> {% endfor %} </tbody> </table> これで準備完了です。このように選択されたプルダウンの値に連動して、テーブル表示も変わります(ステータスもxhrとなっています)。 htmlをそのまま引っ張ってきているのはセキュリティ上問題があるのではと思い、対策を調べてみたところ、公式マニュアルによるとjinjaテンプレートは自動でサニタイズ処理をしてくれるそうなので、これで問題はないようです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】OSMnxで車両走行道路の最短経路探索&可視化をしてみる

最近まで高精度地図データ整備や交通情報生成の業務に携わっていたmoshiです。 オープンソースの地図データ「OpenStreetMap」ではどんなことができるのかなと、ふと気になって最近調べてました。その中で、最短経路探索・可視化まで手軽にできる「OSMnx」が面白いなと思ったので、紹介したいと思います。 OSMnxとは OSMnxは、OpenStreetMapから地理空間データをダウンロードし、実世界の道路網やその他の地理空間形状をモデル化・投影・可視化・分析することができるPythonパッケージ。 たった一行のPythonコードで、歩きやすい、運転しやすい、または自転車に乗りやすい都市ネットワークをダウンロードしてモデル化し、簡単に分析して視覚化することができる。 また他のインフラタイプ・公共施設/ポイント・建物の占有面積・標高データ・道路の方位/方向・速度/移動時間なども同様に簡単にダウンロードして操作することができる。 道路情報取得 〜 最短経路探索・可視化手順 愛知県名古屋市中村区を対象地域例として、車両走行道路における最短経路探索・可視化までの手順を以下に紹介します。 0. 環境準備 下記pipコマンドにより必要なライブラリをインストール (可視化用にfoliumも必要)。 pip install osmnx folium ただし、上記はWindowsでエラーが出ることがあるそうです。 (コンパイル時の問題?) エラーが出る場合は、以下の方法を試すと良さそうです。 OSMnxの依存パッケージGDAL・Fionaをwheel形式ファイルにより事前に直接ダウンロード・インストールする。 その後、pipコマンドを利用してインストール pip install osmnx folium wheel形式パッケージのGDAL・Fionaダウンロード・インストールは下記記事が参考になります。 GeoPandasのインストールに失敗した場合の対処法 pipではなくAnacondaのcondaコマンドを利用してインストール conda install osmnx folium 1. 対象地域の道路情報(グラフネットワーク)取得・可視化 import folium import osmnx as ox # 対象地域の道路情報取得 (愛知県名古屋市中村区) query = "Nakamuraku,Nagoya,Aichi,Japan" G = ox.graph_from_place(query, network_type="drive") # 道路グラフネットワーク可視化 fmap = ox.plot_graph_folium(G) fmap.save(outfile="road_network.html") opts = {"node_size": 5, "bgcolor": "white", "node_color": "blue", "edge_color": "blue"} ox.plot_graph(G, show=False, save=True, filepath="road_network.png", **opts) 愛知県名古屋市中村区の道路情報は、クエリーを"Nakamuraku,Nagoya,Aichi,Japan"として、ox.graph_from_place()関数を利用することで取得できる。 ※ その他に、ある座標地点から1km範囲内の道路情報取得、等も可能です (道路情報取得用の関数一覧はこちら)。 PythonライブラリのNetworkXにより、道路情報はグラフネットワークとして格納されます。 ⇒ NetworkXについては、こちらのサイトが分かりやすく参考になるかと思います。 道路情報の可視化は下記の2パターンで行うことができる。 foliumによるHTML形式での可視化 (road_network.html) ox.plot_graph_folium()関数を利用した出力結果 matplotlibによるPNG形式での可視化 (road_network.png) ox.plot_graph()関数による出力結果 2. 取得道路情報のCSV出力 # 道路グラフネットワークの各ノード・エッジ取得・CSV出力 nodes, edges = ox.graph_to_gdfs(G) nodes.to_csv("road_network_nodes.csv") edges.to_csv("road_network_edges.csv") ox.graph_to_gdfs()関数により、グラフネットワーク(G)の各ノード・エッジデータを取得できる。 PandasのDataFrameを地理情報データ用に拡張したGeoDataFrame(gdf)形式でデータは取得される。 to_csv()関数によりCSV形式で各ノード・エッジデータのファイル出力・確認ができる。 3. 任意の二地点間での最短経路探索・可視化 # 最短経路探索 start_point = (35.18253738854321, 136.85996828365532) start_node = ox.get_nearest_node(G, start_point) end_point = (35.16163249834248, 136.8824509819242) end_node = ox.get_nearest_node(G, end_point) shortest_path = ox.shortest_path(G, start_node, end_node) # 最短経路探索結果の可視化 new_fmap = ox.plot_route_folium(G, shortest_path, route_map=fmap, color="red") folium.Marker(location=start_point, tooltip="start").add_to(new_fmap) folium.Marker(location=end_point, tooltip="end").add_to(new_fmap) new_fmap.save(outfile="shortest_path_road_network.html") outfile = "shortest_path_road_network.png" opts = {"node_size": 5, "bgcolor": "white", "node_color": "blue", "edge_color": "blue"} ox.plot_graph_route(G, shortest_path, show=False, save=True, filepath=outfile, **opts) ox.get_nearest_node()関数で始終点座標の各最近傍ノードを取得し、 ox.shortest_path()関数により二地点間での最短経路探索が実行される。 最短経路探索結果は以下のように可視化される。 foliumによるHTML形式での可視化 (shortest_path_road_network.html) ox.plot_route_folium()関数を利用した出力結果 下図のマーカーで示す始点・終点間で問題なく最短経路が算出されていそう。 matplotlibによるPNG形式での可視化 (shortest_path_road_network.png) ox.plot_graph_route()関数による出力結果 まとめ OSMnxを利用することで、数行のPythonコードでOSMの道路可視化・分析ができたので、とても便利だなと思いました。 これだけのデータ可視化・分析がオープンソースで手軽に実施できるのは、とてもありがたいことだなと感じます。 最後に最短経路探索&可視化を利用しやすいようにひとまとめにしたコードを記載します。 osmnx_search_shortest_path.py import os from pathlib import Path import folium import osmnx as ox # 対象地域検索クエリ (愛知県名古屋市中村区) query = "Nakamuraku,Nagoya,Aichi,Japan" # 各種出力ファイルパス outdir_path = Path(query.replace(",", "_")) os.makedirs(outdir_path, exist_ok=True) # 道路グラフネットワーク取得 graphml_outfile = outdir_path / "road_network.graphml" if not os.path.isfile(graphml_outfile): # 走行可能な道路グラフネットワークを取得 G = ox.graph_from_place(query, network_type="drive") # 取得データを再利用目的でGraphml形式にて保存 ox.save_graphml(G, filepath=graphml_outfile) else: # 前回取得の道路グラフネットワークを再利用 G = ox.load_graphml(graphml_outfile) # 道路グラフネットワーク可視化 fmap = ox.plot_graph_folium(G) folium_outfile = outdir_path / "road_network.html" fmap.save(outfile=str(folium_outfile)) png_outfile = outdir_path / "road_network.png" opts = {"node_size": 5, "bgcolor": "white", "node_color": "blue", "edge_color": "blue"} ox.plot_graph(G, show=False, save=True, filepath=png_outfile, **opts) # 道路グラフネットワークの各ノード・エッジ取得・CSV出力 nodes, edges = ox.graph_to_gdfs(G) nodes_csv_outfile = outdir_path / "road_network_nodes.csv" nodes.to_csv(nodes_csv_outfile) edges_csv_outfile = outdir_path / "road_network_edges.csv" edges.to_csv(edges_csv_outfile) # 最短経路探索 start_point = (35.18253738854321, 136.85996828365532) start_node = ox.get_nearest_node(G, start_point) end_point = (35.16163249834248, 136.8824509819242) end_node = ox.get_nearest_node(G, end_point) shortest_path = ox.shortest_path(G, start_node, end_node) # 最短経路探索結果の可視化 new_fmap = ox.plot_route_folium(G, shortest_path, route_map=fmap, color="red") folium.Marker(location=start_point, tooltip="start").add_to(new_fmap) folium.Marker(location=end_point, tooltip="end").add_to(new_fmap) folium_path_outfile = outdir_path / "shortest_path_road_network.html" new_fmap.save(outfile=str(folium_path_outfile)) path_png_outfile = outdir_path / "shortest_path_road_network.png" ox.plot_graph_route( G, shortest_path, show=False, save=True, filepath=path_png_outfile, **opts ) 参考 OSMnxを用いて,オープンストリートマップから道路ネットワークデータを取得しよう. OSMnx開発者による実行サンプル例 (GitHubリポジトリ)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【DRF】Django REST FrameworkのSerializerの引数、特にcontextについて

こんにちは、増山です。最近はDjango REST Frameworkを使ってiOS, React.js用のバックエンドを書いたり、Dockerを詳しく調べたりしています。 Django, Django REST Frameworkの構造を深くまで知りたくてソースコードを読むのが趣味です。 Serializerのcontextって何? 今回のテーマは、DRFのデータのシリアル化に使われるSerializerのcontextについて理解することです。出てくる単語の簡単な説明も交えて説明していきたいと思います。 Serializerとは? Serializerは、アプリケーションが送受信するデータをバリデートし、さらにシリアル化してくれるものです。ただし、ここで言うシリアル化については、Serializerのみではバイト列にはならず、単一の辞書型、またはリスト型(many=Trueのとき)の文字列に変換することになります。Serializerについての詳しい記事も今後書こうと思っているのでそこでソースコードやドキュメントを見ながら具体的に説明したいと思います。 Serializerをインスタンス化する際の引数 serializer = Serializer(instance, data=data, partial=True) や、 serializer = Serializer(queryset, many=True) などのように、Serializerをインスタンスかする時には必要な引数があります。 dataには、例えばPOSTされたデータをrequest.dataなどから取得して格納することで、Serializerがis_valid()を呼べるようになり、データバリデートすることができます。 このまま説明し続けると脱線しすぎてもう一回線路に乗るくらいの大脱線をしそうなので、ここら辺の解説も他の記事に書こうと思います。 context引数について 本題です。 contextを引数に含めるパターンは大きく分けて2つあります。 Hyperlink系のSerializerの機能を使っているとき requestなど、何かしらの情報をSerializer内部で使いたいとき 1. Hyperlink系のSerializerの機能を使っているとき Serializerで明示的にcontext使ってないのになんでcontext渡さなきゃいけないの?って思ったときは大抵の場合これです。 drfでは、レスポンスに含めるデータとして、オブジェクト自身のurlを使うことができます。 drfの公式ドキュメントにも書いてありますが、そのときSerializerは、正しくURLを表現するためにViewが受け取るrequestオブジェクトを必要とします。 2. requestなど、何かしらの情報をSerializer内部で使いたいとき 単純なコンテキストの受け渡しにももちろん使うことができます。やり方は、Serializerをインスタンスかする時に、 serializer = Serializer(context={'名前': 渡したいデータ}) として、Serializer内部では、 self.context.get('名前', デフォルト値) とするだけです。 終わりに DRFは、ドキュメントがすごくしっかりしているので読めば大体わかると思います。初めて記事を投稿するので、ここが良くないとかここが良いとか教えていただけるとありがたいです。すぐに改善します。 公式ドキュメント:Django Rest Framework
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで学ぶ制御工学 第25弾:ディジタル実装

#Pythonで学ぶ制御工学< ディジタル実装 > はじめに 基本的な制御工学をPythonで実装し,復習も兼ねて制御工学への理解をより深めることが目的である. その第25弾として「ディジタル実装」を扱う. 概要 ディジタル実装 以下にディジタル実装についてまとめたものを示す. 0次ホールドによる離散化 双一次変換による離散化 実装 先ほどの説明に従って,以下にソースコードとそのときの出力を示す. ソースコード:連続時間システムから離散時間システムへの変換 transform_c2d.py """ 2021/04/11 @Yuya Shimizu 連続時間システムから離散時間システムへの変換 """ import matplotlib.pyplot as plt import numpy as np from control import tf, c2d from control.matlab import step from for_plot import plot_set P = tf([0, 1], [0.5, 1]) print(P) ###数式 ts = 0.2 #サンプリング時間 # 0次ホールドによる離散化 Pd1 = c2d(P, ts, method='zoh') print('離散時間システム(zoh)', Pd1) # 双一次変換による離散化 Pd2 = c2d(P, ts, method='tustin') print('離散時間システム(tustin)', Pd2) ###図示 fig, ax = plt.subplots(1, 2, figsize = (6, 2.6)) #連続時間システム Tc = np.arange(0, 3, 0.01) y, t = step(P, Tc) ax[0].plot(t, y, ls = '-.') ax[1].plot(t, y, ls = '-.') #離散時間システム(0次ホールドによる離散化) T = np.arange(0, 3, ts) y, t = step(Pd1, T) ax[0].plot(t, y, ls = ' ', marker = 'o', label = 'zoh') #離散時間システム(双一次変換による離散化) y, t = step(Pd2, T) ax[1].plot(t, y, ls = ' ', marker = 'o', label = 'tustin') title = ['0th hold', 'Tustin'] for i in range(2): ax[i].set_xlabel('t') ax[i].set_ylabel('y') ax[i].set_title(title[i]) ax[i].legend() fig.tight_layout() plt.show() 出力 1 --------- 0.5 s + 1 離散時間システム(zoh) 0.3297 ---------- z - 0.6703 dt = 0.2 離散時間システム(tustin) 0.1667 z + 0.1667 ----------------- z - 0.6667 dt = 0.2 離散化された値がうまく連続時間の信号に乗っていることが確認できる. ソースコード:連続時間システムから離散時間システムへの変換(時間応答) 入力を$u = 0.5sin(6t) + 0.5cos(8t)$として,離散化方法の差を確認する. transform_c2d_complex.py """ 2021/04/11 @Yuya Shimizu 連続時間システムから離散時間システムへの変換(やや複雑) """ import matplotlib.pyplot as plt import numpy as np from control import tf, c2d from control.matlab import lsim from for_plot import plot_set P = tf([0, 1], [0.5, 1]) print(P) ###数式 ts = 0.2 #サンプリング時間 # 0次ホールドによる離散化 Pd1 = c2d(P, ts, method='zoh') print('離散時間システム(zoh)', Pd1) # 双一次変換による離散化 Pd2 = c2d(P, ts, method='tustin') print('離散時間システム(tustin)', Pd2) ###図示 fig, ax = plt.subplots(1, 2, figsize = (6, 2.6)) #連続時間システム Tc = np.arange(0, 3, 0.01) Uc = 0.5 * np.sin(6*Tc) + 0.5*np.cos(8*Tc) y, t, x0 = lsim(P, Uc, Tc) ax[0].plot(t, y, ls = '-.') ax[1].plot(t, y, ls = '-.') #離散時間システム(0次ホールドによる離散化) T = np.arange(0, 3, ts) U = 0.5 * np.sin(6*T) + 0.5*np.cos(8*T) y, t, x0 = lsim(Pd1, U, T) ax[0].plot(t, y, ls = ' ', marker = 'o', label = 'zoh') #離散時間システム(双一次変換による離散化) y, t, x0 = lsim(Pd2, U, T) ax[1].plot(t, y, ls = ' ', marker = 'o', label = 'tustin') title = ['0th hold', 'Tustin'] for i in range(2): ax[i].set_xlabel('t') ax[i].set_ylabel('y') ax[i].set_title(title[i]) fig.tight_layout() fig.suptitle("input; u(t) = 0.5sin(6t) + 0.5cos(8t)") plt.show() 出力 先ほどの入力よりも少し複雑な信号にしたところ,双一次変換のほうが0次ホールドによる離散化よりもうまく離散化できていることが分かる. ソースコード:ボード線図 周波数応答により,周波数特性を確認する. transform_c2d_bode.py """ 2021/04/11 @Yuya Shimizu ボード線図 """ import matplotlib.pyplot as plt import numpy as np from control import tf, c2d from control.matlab import bode from for_plot import plot_set P = tf([0, 1], [0.5, 1]) print(P) ###数式 ts = 0.2 #サンプリング時間 # 0次ホールドによる離散化 Pd1 = c2d(P, ts, method='zoh') print('離散時間システム(zoh)', Pd1) # 双一次変換による離散化 Pd2 = c2d(P, ts, method='tustin') print('離散時間システム(tustin)', Pd2) ###図示 fig, ax = plt.subplots(2, 1) #連続時間システム gain, phase, w = bode(P, np.logspace(-2, 2), Plot = False) ax[0].semilogx(w, 20*np.log10(gain), ls = '-.', label = 'continuous') ax[1].semilogx(w, phase*180/np.pi, ls = '-.', label = 'continuous') #離散時間システム(0次ホールドによる離散化) gain, phase, w = bode(Pd1, np.logspace(-2, 2), Plot = False) ax[0].semilogx(w, 20*np.log10(gain), ls = '-.', label = 'zoh') ax[1].semilogx(w, phase*180/np.pi, ls = '-.', label = 'zoh') #離散時間システム(双一次変換による離散化) gain, phase, w = bode(Pd2, np.logspace(-2, 2), Plot = False) ax[0].semilogx(w, 20*np.log10(gain), ls = '-.', label = 'tustin') ax[1].semilogx(w, phase*180/np.pi, ls = '-.', label = 'tustin') #周波数がw=pi/tsのところに線を引く ax[0].axvline(np.pi/ts, lw = 0.5, c = 'k') ax[1].axvline(np.pi/ts, lw = 0.5, c = 'k') ax[1].legend() ax[0].set_ fig.tight_layout() plt.show() 出力 低周波域で連続時間システムとほぼ同じ特徴になっているが,高周波域で特徴が異なっている.0次ホールドによる離散化したシステムのゲイン特性はほぼ連続時間システムのゲイン特性に近くなっているが,位相が大きくずれていることが確認できる.一方,双一次変換で離散化したシステムでは,位相特性が連続時間の位相特性に近くなっているこ確認できる. 感想 ようやく参考書の内容をすべて終えた.最後は,ディジタル実装ということで主に離散化について学んだが,離散化にもいくつかの手法があるのは知らなかった.また,その手法それぞれに特徴があることを学び,用途に合わせて離散化の方法を変えることで望みの実装を実現するのかなと思った.まだまだ経験がないため,いまいちイメージはつかめていないが,それについては経験していく中で学んでいけたらと思う. 参考文献 Pyhtonによる制御工学入門  南 祐樹 著  オーム社
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Toml & Python & PHP & vue & levelDB でSSL証明書の有効期限を毎日チェック

前置き 我々のような受託システム開発会社は、開発依頼を受ける度に、さまざまなドメインにそのシステムをロンチしていきます。 常時SSLが当たり前となった今日において、そのドメインの数だけSSL証明書が必要になります。 そしてSSL証明書にはLet's encryptを採用するケースも多いため、その更新期限管理は定期的なチェックが欠かせません。 (バッチで自動更新は当然としても) うちではもうなんだかんだと管理している客先ドメインが100超になっているので、日次バッチで全ドメインの有効期限を取得して、これが一覧画面から確認できるようにしておくことで、ある日突然サイトにアクセスできなくなって大慌てで証明書を更新しなけれけばならないような事態に未然対策しています。 本題 というのはまあいいとして、ここでの趣旨は、このようなワンオペサービスの裏側で実装される数々の周辺技術を束ねてひとつのアプリケーションとして構築する醍醐味感、のようなものになると思います。 目的はひとつでもそれを実現するための中間過程はいくつも存在し、それぞれを実現するための手段も多岐に及ぶため、適材適所な取捨選択をしていくのであれば応用技術の醍醐味がやってくるのは当然なのです。 登場人物 crontab スクリプトを定期実行されるためのスケジューラ。実体はシェル ここではドメインの有効期限を日次バッチで取得するための設定に使用 levelDB キーバリューストアな軽量ストレージ。使用頻度の高いものを優先に階層的なインデックスをおこなうのが特徴 ここでは日次バッチで取得した各ドメインの有効期限をFQDN単位で記録するのに使用 openSSL SSL証明書等の暗号プロトコルを触るためのデファクトスタンダード ここでは外部SSL証明書のパースに使用 python 昔からあるけど最近超花形言語として返り咲いた万能言語(3.7からハッシュが順番を保証してくれるようになったので私も3.7から大好き) ここではcrontabをトリガーにopenSSLで取得した有効期限をlevelDBに登録するためのバッチスクリプトとして使用 PHP 昔からあるけど今でも人気なWEB開発言語(私はずっと大好き) ここではバッチで記録した全ドメインの有効期限を後述のVueにAPIとして提供するのに使用 Vue JSフレームワークの雄。SPAや簡単なアプリケーションなら相性抜群 ここではフロントエンドの更新期限一覧画面として使用 nginx スクリプトをWEB通信規格を経由して提供するためのミドルウェア ここではPHPとVueをそれぞれインターフェースするのに使用 toml 設定ファイルのマークアップ言語。可読性がよく様々な言語にもライブラリが提供されているが、まだダークホース感有 ここでは各言語を横断してDRYな設定を管理するのに使用 醍醐味 以下、さっそくです。 前提 pwd # 適宜読み替えてください /home/you/projects/ssl-check tree -L 2 -I node_modules # 最終的な成果物 +__ api | +__ composer.json | +__ index.php | L__ vendor +__ app | +__ README.md | +__ babel.config.js | +__ dist | +__ package.json | +__ public | +__ src | L__ vue.config.js +__ batch | L__ ssl_check.py +__ configs | L__ common.toml +__ logs +__ package.json L__ results +__ 000005.ldb +__ 000006.log +__ CURRENT +__ LOCK +__ LOG +__ LOG.old L__ MANIFEST-000004 crontab 5 4 * * * cd /home/you/projects/ssl-check/batch/ && /usr/local/bin/python3.7 ssl_check.py & > /dev/null 2>&1 levelDB # for PHP sudo apt-get install libleveldb-dev cd /usr/local/src/ git clone https://github.com/reeze/php-leveldb.git cd php-leveldb/ phpize ./configure --prefix=/home/you/.phpenv/versions/7.4.0/lib/php/leveldb --with-leveldb=/home/you/.phpenv/versions/7.4.0/include/php/include/leveldb --with-php-config=/home/you/.phpenv/versions/7.4.0/bin/php-config make make install vi ~/.phpenv/versions/7.4.0/etc/php.ini extension=/home/you/.phpenv/versions/7.4.0/lib/php/extensions/no-debug-non-zts-20180731/leveldb.so sudo service php-fpm restart # for python sudo pip install plyvel pip list | grep plyvel plyvel 1.3.0 toml # for PHP cd api/ composer require yosymfony/toml # for python sudo pip install toml pip list | grep toml toml 0.10.2 configs/common.toml APP_DIR = "/home/you/projects/ssl-check/" RESULT_DB = "results" CHECK_CMD = "openssl s_client -connect {fqdn}:443 -servername {fqdn} </dev/null 2>/dev/null | openssl x509 -text | grep \"Not After\"" STATUS_OK = 1 STATUS_NOTE = 2 STATUS_NG = 3 STATUS_ERROR = 4 # ↓適宜編集 [DOMAIN_LIST] "example.com" = [ "example.com", "test.example.com", ] "google.com" = [ "google.com", ] "yahoo.co.jp" = [ "yahoo.co.jp", ] python batch/ssl_check.py import logging import os import sys import logging import subprocess import datetime import calendar import pytz import dateutil.parser import pickle import click import plyvel import json import toml from pprint import pprint logger = logging.getLogger('Batch') logger.setLevel(10) configs = toml.load(open('../configs/common.toml')) print(configs['DOMAIN_LIST']) fh = logging.FileHandler(configs['APP_DIR'] + 'logs/batch.log') logger.addHandler(fh) sh = logging.StreamHandler() logger.addHandler(sh) format = logging.Formatter('%(asctime)s - [%(levelname)s] (%(lineno)d) %(message)s') fh.setFormatter(format) sh.setFormatter(format) curr_tz = pytz.timezone('Asia/Tokyo') curr_ts = int(datetime.datetime.now().timestamp()) results = {}; index = 0; for brand in configs['DOMAIN_LIST']: logger.info(brand + ' >>> start') results[brand] = {} for fqdn in configs['DOMAIN_LIST'][brand]: try: limit_at = subprocess.check_output(configs['CHECK_CMD'].format(fqdn = fqdn), shell = True) index += 1 if limit_at != '': limit_at = str(limit_at).split(' : ')[1] limit_at = limit_at.split('\\n')[0] limit_at = dateutil.parser.parse(limit_at) limit_ts = calendar.timegm(limit_at.timetuple()) if limit_ts < curr_ts: status = configs['STATUS_NG'] elif limit_ts < curr_ts - (60 * 60 * 24 * 30): status = configs['STATUS_NOTE'] else: status = configs['STATUS_OK'] limit_at = limit_at.astimezone(curr_tz).replace(tzinfo=curr_tz) limit_at = str(limit_at).split('+')[0].replace('-', '/') results[brand][fqdn] = { 'index': index, 'status': status, 'limit_at':limit_at } else: logger.warning(fqnd + ' unable to load certificate') results[brand][fqdn] = { 'index': index, 'status': configs['STATUS_ERROR'], 'limit_at':None } except Exception as e: logger.warning(e) results[brand][fqdn] = { 'index': index, 'status': configs['STATUS_ERROR'], 'limit_at':None } logger.info(brand + ' <<< end') logger.debug(results) try: plyvel.destroy_db(configs['APP_DIR'] + configs['RESULT_DB']) result_db = plyvel.DB(configs['APP_DIR'] + configs['RESULT_DB'], create_if_missing=True) for brand in results: for fqdn, result in results[brand].items(): result_db.put(str(fqdn).encode(), json.dumps(result).encode()) except Exception as e: logger.warning(e) finally: result_db.close() PHP api/index.php <?php require_once 'vendor/autoload.php'; use Yosymfony\Toml\Toml; $configs = Toml::ParseFile(dirname(__FILE__) . '/../configs/common.toml'); $db = new LevelDB($configs['APP_DIR'] . $configs['RESULT_DB'], ['create_if_missing' => true]); $results = []; foreach ($configs['DOMAIN_LIST'] as $brand => $list) { isset($results[$brand]) or $results[$brand] = []; foreach ($list as $fqdn) { $result = $db->get($fqdn); $results[$brand][$fqdn] = $result; } } $checked_at = date('Y/m/d H:i:s', filemtime($configs['APP_DIR'] . $configs['RESULT_DB'])); echo json_encode(['list' => $results, 'checked_at' => $checked_at], JSON_UNESCAPED_UNICODE); Vue app/vue.config.js module.exports = { lintOnSave: false, publicPath: 'https://ssl-check.yourdomain.com/', devServer: { host: 'localhost', port: 8030, disableHostCheck: true, public: 'https://ssl-check.yourdomain.com/', } } app/package.json { "name": "app", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "axios": "^0.21.1", "bootstrap": "^4.6.0", "bootstrap-vue": "^2.21.2", "core-js": "^3.6.5", "register-service-worker": "^1.7.2", "vue": "^2.6.11", "vue-meta": "^2.4.0" }, "devDependencies": { "@vue/cli-plugin-babel": "~4.5.0", "@vue/cli-plugin-eslint": "~4.5.0", "@vue/cli-service": "~4.5.0", "babel-eslint": "^10.1.0", "eslint": "^6.7.2", "eslint-plugin-vue": "^6.2.2", "vue-template-compiler": "^2.6.11" }, "eslintConfig": { "root": true, "env": { "node": true }, "extends": [ "plugin:vue/essential", "eslint:recommended" ], "parserOptions": { "parser": "babel-eslint" }, "rules": {} }, "browserslist": [ "> 1%", "last 2 versions", "not dead" ] } app/src/configs/common.js export default Object.freeze({ CNAME: 'SSL証明書 更新状況一覧', CURL: 'https://ssl-check.yourdomain.com/', }); app/src/main.js import Vue from 'vue' import { BootstrapVue, IconsPlugin } from 'bootstrap-vue' import App from './App.vue' import axios from 'axios' import './registerServiceWorker' import VueMeta from 'vue-meta' Vue.use(VueMeta) Vue.use(BootstrapVue) Vue.config.productionTip = false Vue.prototype.$axios = axios new Vue({ render: h => h(App), }).$mount('#app') app/src/App.vue <template> <div id="app"> <Ssl/> </div> </template> <script> import 'bootstrap/dist/css/bootstrap.css' import 'bootstrap-vue/dist/bootstrap-vue.css' import Configs from './configs/common' import Ssl from './components/Ssl.vue' export default { metaInfo: { title: Configs.CNAME, titleTemplate: Configs.CNAME, htmlAttrs: { lang: 'ja', amp: true }, meta: [ { charset: 'utf-8' }, { name: 'application-name', content: Configs.CNAME }, { name: 'robot', content: 'noindex,nofollow' }, { name: 'author', content: 'ketoha' }, { name: 'copyright', content: '&copy;ketoha' }, { name: 'og:site_name', content: Configs.CNAME }, { name: 'og:url', content: Configs.CURL }, { name: 'og:title', content: Configs.CNAME } ] }, name: 'App', components: { Ssl } } </script> <style> @import "../public/index.css"; </style> app/src/components/Ssl.vue <template> <div> <h1>SSL証明書 更新状況一覧<span>{{ checked_at }}</span></h1> <table> <thead> <tr> <th>#</th><th>##</th><th>ドメイン</th><th>FQDN</th><th>ステータス</th><th>期限終了日時</th> </tr> </thead> <tbody> <template v-for="(data, brand, pidx) in list"> <tr v-for="(result, fqdn, sidx) in data"> <td>{{ pidx+1 }}</td> <td>{{ sidx+1 }}</td> <td>{{ brand }}</td> <td>{{ fqdn }}</td> <td v-if="result.status"> <span v-if="result.status === 1" class="ok">正常</span><span v-if="result.status === 2" class="note">間近</span><span v-if="result.status === 3" class="ng">失効</span><span v-if="result.status === 4" class="error">失敗</span> </td><td v-else>-</td> <td v-if="result.limit_at">{{ result.limit_at }}</td><td v-else>-</td> </tr> </template> </tbody> </table> </div> </template> <script> export default { name: 'Ssl', data: function(){ return { list:{}, checked_at:null, is_error:false } }, mounted:function() { window.addEventListener('DOMContentLoaded', this.getList) }, methods: { getList:function(){ document.body.classList.add('loading') this.$axios.get('https://ssl-check.ketoha.xyz/api/').then(function(response){ this.list = {} this.checked_at = response.data.checked_at let list = response.data.list //console.log(list) for (let brand in list) { this.list[brand] = {} for (let fqdn in list[brand]) { this.list[brand][fqdn] = JSON.parse(list[brand][fqdn]) } } }.bind(this)).catch(function(error){ this.is_error = true }.bind(this)).finally(function(){ document.body.classList.remove('loading') }.bind(this) )} } } </script> nginx server { listen 80 default; server_name ssl-cjeck.yourdomain.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name ssl-cjeck.yourdomain.com; ssl_certificate /etc/letsencrypt/live/ssl-check.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/ssl-check.yourdomain.com/privkey.pem; root /home/you/projects/ssl-check/app/dist; index index.html; access_log /home/you/projects/ssl-check/logs/access.log; error_log /home/you/projects/ssl-check/logs/error.log; location / { root /home/you/projects/ssl-check/app/dist; index index.html; } location /api/ { root /home/you/projects/ssl-check/api; index index.php; try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { root /home/you/projects/ssl-check/api; index index.php; try_files $uri = 404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/home/you/.phpenv/versions/7.4.0/var/run/php-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } 最後に 適材適所した結果、毎日このような画面で更新期限管理ができるようになります。 ※画面にはないですが、ここでは有効期限が切れている場合は赤で、一ヶ月を切っている場合はオレンジで表示されるようになっています。 オレンジを検知した場合はメールで通知、とかするともっといいかもしれません。 技術の応用しかしていない身としては、日々OSSでソリューションを提供してくれる先人たちには感謝しかありません。 しっかり活用していきたいものですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pandasの結果を 表示する行(column)・列(row)の数のカスタマイズ方法 .no.39

こんにちは、まゆみです。 Pandasについての記事をシリーズで書いています。 今回は第39回目で、最終回になります。(また新しい情報が入れば、その時は記事を書きますね。) 今回の記事では、Pandasの結果の表示をカスタマイズする方法を書いていきます。 今までに大きなデータを扱って、表示される部分が大きいなと感じた事はありませんか? 例えば、下記のようなデータとか... row を最初と最後の3つづつ表示したい column を5つづつ表示したい 逆に省略(...で中間を抜かずに)せずに全て表示させたい という時に使えるアトリビュートを紹介します では、さっそく始めていきますね。 numpy を使って、大きなデータを作る 実験用データをnumpy を使って作りました。 コラムの最大表示数を取り出す pd.options.display.max_columns と打ち込むと表示されているコラム数が取り出されます。 また、『pd.options.display...』って長くて覚えられないっていう人も大丈夫です。 忘れたら、アトリビュート名.(ドット) まで入力した時点で『Tab』キーを押しましょう。 候補を出してくれます。(最初のpd.option くらいまでは覚えておいた方が良いかもですが。。。) 20のコラムが最大表示されているとの結果が出ました。 これは多すぎると感じる時は、 例えば『10』を代入します。 前後が5づつの最大コラム数が10で表示されました。 もしcolumn(行)ではなく、row(列)の最大表示数を変更したい時は .max_columnsの代わりに.max_rowsを使います。 代替方法 先ほど、 pd.options.display.max_columns = 表示したい列・行数 で、表示する列・行を操作する方法を書きました。 その方法に代替するものとして ①.get_option() ②.set_option() ③.reset_option() get_option("max_columns")で"max_columns"数をゲット(get)して set_option("max_columns")の"max_columns" 数を設定(set)してみます 前後10づつ合計20表示されていたコラム数を 前後6づつ合計12のコラム数の表示に変えました。 .reset_option()は元のデフォルト値に戻す(reset)するメソッドです。 12のコラム数に変えたものを再び20のデフォルト値に変えました。 逆にrow もcolumnも全て表示したい時は? 途中が『...』で抜けているデータではなく、全て表示させたい時は pd.set_option("display.max_rows", None, "display.max_columns", None) で表示させることができます。 この方法についてはこちらのサイトを参考にして解答しています まとめ 今回はPandasの結果の表示を変更するメソッドを紹介させていただきました。 一応、今回は最終回ですが、また新しく皆様と共有したい情報を知った時には投稿させていただきますね。 最終的なゴールは『データサイエンティストとかデータを扱うエンジニアを目指すぞ』って感じで、データサイエンティストになるために必要になりそうな情報を次回からも投稿していきます。(ちなみに次はSQLの記事を書きます)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Beginners guide to Naïve Bayes algorithm (Part 2)

Continue from the last article... What is Naïve Bayes? It is named Naïve since it is Naïve. Because it pretends that the appearance of a particular feature in a class is irrelevant to any other feature's appearance. Let us take an example. Fruit can judge as orange if the fruit colouration is orange, the shape is round, and the diameter is 2.5 inches. All those characteristics independently contribute to the probability, although those characteristics depend on each other. Naïve Bayes is practical when there are lots of data points. Otherwise, the model can be biased. Even the model is simple; it can outclass some advanced models for the same exercise Naïve Bayes algorithm I think the most straightforward way to experience how the algorithm work is to use an example. Cricket is a popular game in some countries. I will use a data set that contains pitch condition and target variable batting first (Many believe if the pitch is grassy, it is good to ball first.). Now we need to classify whether the team chooses batting first based on pitch condition. (Team need to win the coin toss first) Steps: 1. Making of frequency table (of classes). 2. Creation of likelihood table of classes falling in a class given a feature value. 3. Calculation of posterior probability for each class using the Naïve Bayesian equation. Data Table Pitch Condition Batting First Dead Yes Grassy No Dusty Yes Dead Yes Grassy No Grassy Yes Dead Yes Dusty Yes Grassy No Dusty Yes Dusty No Dead Yes Grassy No Dead No Dead No Frequency Table Pitch type No Yes Dead 2 4 Grassy 4 1 Dusty 1 3 Total 7 8 Likelihood Table Pitch type No Yes Dead 2 4 = 6/15 = 0.4 Grassy 4 1 = 5/15 = 0.33 Dusty 1 3 = 4/15 = 0.266 Total 7 8 = 7/15 = 0.46 = 8/15 = 0.53 Now the Question; A Team will pick batting first if the pitch is Dusty. Is this comment correct or not? To solve, let us calculate the posterior probability. P(Yes, Dusty) = P(Dusty | Yes) * P(Yes) / P(Dusty) = (3/8 * 8/15) / (4/15) = 0.75 So, there is 75% portability batting first if the pitch is Dusty. Naive Bayes uses a similar process to predict the probability of various classes based on several properties. This algorithm is primarily used in text categorization and with problems having various classes. Naïve Bayes in Python. The most popular library that includes the Naive Bayes algorithm is SciKit Learn. There are 3 types of models in the sci-kit learn library. 1) Gaussian 2) Multinomial 3) Bernoulli We have to decide the model based on the dataset. I am not going to explain one by one here. Example Code: nb.py from sklearn.naive_bayes import GaussianNB import numpy as np # feature and target variables X = np.array([[8,2],[3,6], [5,1], [2,0], [4,3], [-3,0], [-2,1], [3,1], [-2,4], [5,7], [-1,1]]) y = np.array([1, 2, 3, 3, 2, 1, 1, 2, 3, 3, 2]) model = GaussianNB() model.fit(X, y) pred= model.predict([[5,2],[1,4], [-1,3]]) print(pred) #[2 2 3] Things to consider: * Removing correlated features. * Careful when selecting features and concern about data preprocessing. * Applying smoothing. * Convert continuous features to normal distribution. Let us look advantages and disadvantages of the Naïve Bayes algorithm. Advantages: * Easy and fast. * Worked well in multiclass. * Good for categorical variable. Disadvantages: * Work best if have many data points. * The training data set must contain all the categorical variables. Otherwise, we have to use smoothing methods. * Difficult to use in real life situation since algorithm assumes predictors are independent. *本記事は @qualitia_cdevの中の一人、@nuwanさんが書いてくれました。 *This article is written by @nuwan a member of @qualitia_cdev.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

picoCTF Practice Writeup 2

picoCTF Practice Writeup 2 picoGym Practice Challenges page=2 の12問を勉強した記録 このページの難問は, 352 solves の tunn3l v1s10n (壊れたbmpの修復) 450 solves の Easy Peasy (pythonの問題) 469 solves の ARMssembly 0 (ARM 64bit のアセンブリ) あと,Cookiesを力業に頼らず,bashでループ処理をして,curlできるようになった。 keygenme-py Category: Reverse Engineering Description: keygenme-trial.py Hints: (None) keygenme-trial.py #============================================================================# #============================ARCANE CALCULATOR===============================# #============================================================================# import hashlib from cryptography.fernet import Fernet import base64 # GLOBALS --v arcane_loop_trial = True jump_into_full = False full_version_code = "" username_trial = "GOUGH" bUsername_trial = b"GOUGH" key_part_static1_trial = "picoCTF{1n_7h3_|<3y_of_" key_part_dynamic1_trial = "xxxxxxxx" key_part_static2_trial = "}" key_full_template_trial = key_part_static1_trial + key_part_dynamic1_trial + key_part_static2_trial star_db_trial = { "Alpha Centauri": 4.38, "Barnard's Star": 5.95, "Luhman 16": 6.57, "WISE 0855-0714": 7.17, "Wolf 359": 7.78, "Lalande 21185": 8.29, "UV Ceti": 8.58, "Sirius": 8.59, "Ross 154": 9.69, "Yin Sector CL-Y d127": 9.86, "Duamta": 9.88, "Ross 248": 10.37, "WISE 1506+7027": 10.52, "Epsilon Eridani": 10.52, "Lacaille 9352": 10.69, "Ross 128": 10.94, "EZ Aquarii": 11.10, "61 Cygni": 11.37, "Procyon": 11.41, "Struve 2398": 11.64, "Groombridge 34": 11.73, "Epsilon Indi": 11.80, "SPF-LF 1": 11.82, "Tau Ceti": 11.94, "YZ Ceti": 12.07, "WISE 0350-5658": 12.09, "Luyten's Star": 12.39, "Teegarden's Star": 12.43, "Kapteyn's Star": 12.76, "Talta": 12.83, "Lacaille 8760": 12.88 } def intro_trial(): print("\n===============================================\n\ Welcome to the Arcane Calculator, " + username_trial + "!\n") print("This is the trial version of Arcane Calculator.") print("The full version may be purchased in person near\n\ the galactic center of the Milky Way galaxy. \n\ Available while supplies last!\n\ =====================================================\n\n") def menu_trial(): print("___Arcane Calculator___\n\n\ Menu:\n\ (a) Estimate Astral Projection Mana Burn\n\ (b) [LOCKED] Estimate Astral Slingshot Approach Vector\n\ (c) Enter License Key\n\ (d) Exit Arcane Calculator") choice = input("What would you like to do, "+ username_trial +" (a/b/c/d)? ") if not validate_choice(choice): print("\n\nInvalid choice!\n\n") return if choice == "a": estimate_burn() elif choice == "b": locked_estimate_vector() elif choice == "c": enter_license() elif choice == "d": global arcane_loop_trial arcane_loop_trial = False print("Bye!") else: print("That choice is not valid. Please enter a single, valid \ lowercase letter choice (a/b/c/d).") def validate_choice(menu_choice): if menu_choice == "a" or \ menu_choice == "b" or \ menu_choice == "c" or \ menu_choice == "d": return True else: return False def estimate_burn(): print("\n\nSOL is detected as your nearest star.") target_system = input("To which system do you want to travel? ") if target_system in star_db_trial: ly = star_db_trial[target_system] mana_cost_low = ly**2 mana_cost_high = ly**3 print("\n"+ target_system +" will cost between "+ str(mana_cost_low) \ +" and "+ str(mana_cost_high) +" stone(s) to project to\n\n") else: # TODO : could add option to list known stars print("\nStar not found.\n\n") def locked_estimate_vector(): print("\n\nYou must buy the full version of this software to use this \ feature!\n\n") def enter_license(): user_key = input("\nEnter your license key: ") user_key = user_key.strip() global bUsername_trial if check_key(user_key, bUsername_trial): decrypt_full_version(user_key) else: print("\nKey is NOT VALID. Check your data entry.\n\n") def check_key(key, username_trial): global key_full_template_trial if len(key) != len(key_full_template_trial): return False else: # Check static base key part --v i = 0 for c in key_part_static1_trial: if key[i] != c: return False i += 1 # TODO : test performance on toolbox container # Check dynamic part --v if key[i] != hashlib.sha256(username_trial).hexdigest()[4]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[5]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[3]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[6]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[2]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[7]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[1]: return False else: i += 1 if key[i] != hashlib.sha256(username_trial).hexdigest()[8]: return False return True def decrypt_full_version(key_str): key_base64 = base64.b64encode(key_str.encode()) f = Fernet(key_base64) try: with open("keygenme.py", "w") as fout: global full_version global full_version_code full_version_code = f.decrypt(full_version) fout.write(full_version_code.decode()) global arcane_loop_trial arcane_loop_trial = False global jump_into_full jump_into_full = True print("\nFull version written to 'keygenme.py'.\n\n"+ \ "Exiting trial version...") except FileExistsError: sys.stderr.write("Full version of keygenme NOT written to disk, "+ \ "ERROR: 'keygenme.py' file already exists.\n\n"+ \ "ADVICE: If this existing file is not valid, "+ \ "you may try deleting it and entering the "+ \ "license key again. Good luck") def ui_flow(): intro_trial() while arcane_loop_trial: menu_trial() # Encrypted blob of full version full_version = \ b""" gAAAAABgT_nv39GmDRYkPhrc2hba8UHCHnSTHqdFxXNdemW0svN2hYYw-6n56ErD3NrQYQlNL0sfdsGTmvWKxh5gVRGeCv5kNq-l6PpL0Fzzjo1x_E2Jjbw_xWKIwbvd7BRXFQZKnhs2ehcSEacqES4gsVMOExHUetxFtmYiHLMB0_kqueeT8zf_vcXAPzbiYA0hvD_QSAXzPiKwM2IsGpGzIS5O4_ODq6-knKszeQFstWKFNH_-jNAylCTWSQpPrWqJxCWhSINPhOZ9-PkBsy8lpqmksa6ZBCMvej4W9YFldupRHNoHUHzt8xScEvcsTzIgNmvzOsCBSf5GJGHbLw4yVjsNWmbKRKiE_6BrRMHZW01hcYbfNa1TdJ1MLUX64e_tpDDjMfKvlXZ1qMx4GDwR2lFza9_fm98zoaV-ccgQ1qiSf3wDU1KuKxd9e9TbUAn2TTJfVH9d6IU8emK3QWcn8XRFcMRzVMvlBuNnCVrZmHCYZUzRmwneo15FS-giH63hPzfvjuRfzwp1sFa3wqo5YTJHWejsU0suORvViiDuIpDozmlXTLKLhKj51NkI6QqqDXhMcWkHwKy9V1LN2Furmz_rPbahbNAxnTAWpjF0VELQAvyNHdVy0yxBIbbOJq1oMvHiDJo2adecADc8hMRb4RZJoLqokXxtKLulywhagQjX9METL9bw1YTP9orWXAMwKhTdDbEUdnHViEq8MHo5DcnVvH0yPlnc9Zn2s3_UOfswnhz5vKm0ZbDc5aX0sFTNiMJVjjCrMhQ6HYp5yf_ybd9Tcx_u6xLtwUZBERZWt931n4hQN8n4C_XmDsMehuoSuFmi2NpAuDhX2rcEQK86Ito0KYp-8n2RmbOjzcjo5V5aqHXujmEfX8GYIUWEUKXVcFouF5rZtxtNz3Wsm_j4tqL4Tom27YE5eK7LQSi5B-AsmSF5JGTam2mWeykOGyE-3pHZmNxxkRfdRjxM0uFV13yjQSLFgNIkZ--8n0uAoTb62c7ZFxoFItMNrWasd5zMvp9Nqq70se2KOUieV6VbPJdSL0Sf1uGDmbRFdMmopDm-AuS-7-MLBGiOPmwXtse_9yXjUggeuo3UU4bxyQxCgwh17Ul1ZgxGeopcU7s7Sjm3rqxwlaJWTPRzeF5AXxtZHgyyZjwQ3EB9xYeoMCFh6gsF06bcwnK1Esgar7IYR3JBUfBH6KnWiTyhx_dLkUdomAPMPY0cRoreYsXmFKkEWhYg-TCdifL0nRT8BTEhVyUwFTvqn4PJknTn8NXelYu8co3n8_PoxsOnTrbdNXBJP9vD8Qp2oMi0ZsyCIeekwuX7MCcK4oFVpLGwOrhJdQhJWVqxQdt0ULS-ROB08eOglsXifnVrDl0hi2B0EYcWxxGs-CzsXJPSBvqKWti9XdU5oIhuUH2d7jnAx0pM9tTKqNiL5sfL2mhakMI8XGcljZw2KI0ldgaOW_UvAgh8N3FgKUR4qh_iJ0raoQaaJJFbFneKRoDNT3QsywP_qj6avStEbMGnhN3iBOoc7S3VrN8853X5fow2yDUJaexAKjpYGphE7K4e1g3fHWYjvgnJ-AoXfqALOwDLzaLRjVHSsgF1TQl39XgiAgEzJL-7w_zBn_Hxl5BfYtqe4vxf4PVMZGvof0jXMpM4W-AQ8IW-41LbNgNbPnRuTLubiJCV7MWYu8J7wO0ADSqgv0aXK60IOl0NphAzflWRvjytyT1CljFa0wcsBZvTyyks_ZOoa2__iAj3VlQjcrQynzrxoT5NASs_k51IYPr913nkfOT29oekedYMcxaHzlICLXmjlVHctJfATgYue7xc1BotMDO0Uj5q-wfcg03dq1cZmJ_qhe3AqWrZt3RraYVcvTT2B9Nu2KHBTyjQvCsQMXyFjlqasFZ4jSuNcqybxS3ocP4z-5oGV43zsVjr6YAZyjQiUoLJN6i6h39G5YfH4SStpbTcj7WXWj9WNqxcrF_swHNeIkOPByEa34TIXyJvEOGefZOw7h-F-hxCGuho7LOwabIopS6IykeSLMw1Ra4COmYN-VamVHVUGo50AVEXcmcnHL9NmXP_812Y_sSFdNPo-jglCzjv3KS5jajY1tReYKC9ehz5phUgReaVkiawSc5Tm5BZ42dfJuYZeTwnknsgTWiyGt3Ov6PddqD_40Ye6oHMLO0VjjT_Ul8GWh8hhmxcWxbN8H6dYwLJD0_-YbXvFpRQSi8IQ7BKjY0ZrZm1_tYO87Gg5YcUJznce53ltjXtGCgNIqywt3FDyJ709hOATCIHWf_u-Jfmc7QcIuSss7Rjh68ZgQoQu0Ybjt0Y5bEGEymuyYbgvdUwW8xTksnpl9Jju4x8hMORUQtkyxD0SBG1j7OEsDCK4axMjWxBj4D0liLOSwUuCWr5COJ0Bf_SlydQmufol1HzVIwxTSUG2m7gXEO6cv6gvBIzK0DdcUMjEzXNnqa8davVM0tFvXfuQcgjz8C7tj4-fu4UvikQyAvO5PdSIhClyl06fAyuUmmJgKvyyuoX3plOaMqq5rJbCzXl8OV1anQzSscXIR2Ur_ePhX5IoZNe2XifzLkgVk-lc-Z0gj5Q4WRuo2IYxOcJG-woHvml0oHDY-hQU-gNflauD38YQcfpwdXV0WgcQseWgKNlXfEuldWVktYXn8JNvqVUTOXrNJBEGB9RDyQqp9IubjhQqOJh31eunKYq6oTx4PgjSii0QOKaLKkonBsYtAbb3cUwSoCvek719cI2tp33XWYq2UqQ8J74PtRNzG061_RR_TxHKyWBll-6ii9dgFPki104UzjFkFXkYPzButkzcvcXIDAWD2RNBK6-bshYKS2xr5XxJXgr3QBTWdjrm-p6EwlbFd4DGDR7ran7b38NRkrFD0ignYiKc68xlAPGg9E084LBVXCVlRas8YvYReJH_sl6ZR7faNme2F-qYFzbcvD3jmp5fX0nzvyJuTGWa51qh7siaVBxHETZ_rzoqTh-tr91b_aHPFdcQMfe1Pd-GBQiy9e5N41GQ4MCpvzs87kV5spprXd_DOKnkjeC5bJDFUoIdMk5r-UO2boRH0tHONCbUOzw7HOgFcJUA13yjtvGGbfPPMHvhFMtMDMRw8gacd-5WHaLeh05yBy4UjT_9flAGAqYMWbvrhkAbwEYPJ0abxp1weANOcYZ-gMHm7kn9kF_eTpzKXxWsViR0AekfepQICVZI1eJzLjV2w6qWq7yDA2ALUxFW10GuEqhP9DI_OVVg6AILHPgokj0pcVA9zUizVTWaGnB1Te8_Zlw5Ik-MwNFPJHYLAug14JI4iYeY0zVsgvkpPJmg_dJD4U7Lr4PBwANvyz5NmGZiITqslCAwUDRMK10u3o2ZmSMn-MuBje_9NRYvh8SRvtbWCB46Yj1YMSJvaqci0MaJK8FdPeDPJ84uSK3eWzq75X96k9nVPnHPnlLkcls3480mlq_81V9MTWlLcvgqhEU4FxE7lGjSF9orw7HCK_9lx2rXwuFAaovFweQw2bu7Nr7pH1X82y0XQCI7aeP687QHdONEoIkWikG5Oub8kEGTBq1D4yeRLocq8dPSoRUAPOb6g-QVAOlu3fiJBGIikubJUWSdQ97pbLgxhnpCrRYS3uFZVo-4f5lnwBNEHrR7DuVc13M-rkUXO-oeqrz6Txmr-xAjYtWrg7IsMr-UPihTJC0Gsmm1FAlXtVOmuKYjwOV7DG4aPzE1MjDAHMWidls3ECcueaLdUV-oY6Hw3WwOK_Nnj10sPmWSFSuMPeOBwPEL2M-1tCkbOvilqccCAelhS87qU_fDUKzD68TV1tJIoXEKW4sdwAVGxguEv1BAm4G7LhrH08McB5n3ja5I_3IqkeYdyHaxAXJ-O2thg== """ # Enter main loop ui_flow() if jump_into_full: exec(full_version_code) Solution: コードが長いので心が折れそうになるが,じっくり解析すると,チェックルーチンのif文の直前にデバッグ用のprint()文を入れたら答えが出ることがわかる。 def check_key(key, username_trial): global key_full_template_trial #solver start ----- print(key_full_template_trial) print("xxxxxxxx is ...") print(hashlib.sha256(username_trial).hexdigest()[4]) print(hashlib.sha256(username_trial).hexdigest()[5]) print(hashlib.sha256(username_trial).hexdigest()[3]) print(hashlib.sha256(username_trial).hexdigest()[6]) print(hashlib.sha256(username_trial).hexdigest()[2]) print(hashlib.sha256(username_trial).hexdigest()[7]) print(hashlib.sha256(username_trial).hexdigest()[1]) print(hashlib.sha256(username_trial).hexdigest()[8]) #solver end ----- if len(key) != len(key_full_template_trial): 実行結果 Matryoshka doll Category: Forensics Description: Matryoshka dolls are a set of wooden dolls of decreasing size placed one inside another. What's the final one? Image: this Hints: 1. Wait, you can hide files inside files? But how do you find them? 2. Make sure to submit the flag as picoCTF{XXXXX} Solution: 拡張子はjpgだが中身はpngにzipがくっついてる ddでzipを取り出す dd if=dolls.jpg ibs=1 skip=272492 of=dolls.zip zipを解凍 少し小さくなった。 dd if=2_c.jpg ibs=1 skip=187707 of=2.zip dd if=3_c.jpg ibs=1 skip=123606 of=3.zip dd if=4_c.jpg ibs=1 skip=79578 of=4.zip 繰り返すと最後にflag.txtが出てきた。 crackme-py Category: Reverse Engineering Description: crackme.py Hints: (None) crackme.py # Hiding this really important number in an obscure piece of code is brilliant! # AND it's encrypted! # We want our biggest client to know his information is safe with us. bezos_cc_secret = "A:4@r%uL`M-^M0c0AbcM-MFE0cdhb52g2N" # Reference alphabet alphabet = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"+ \ "[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" def decode_secret(secret): """ROT47 decode NOTE: encode and decode are the same operation in the ROT cipher family. """ # Encryption key rotate_const = 47 # Storage for decoded secret decoded = "" # decode loop for c in secret: index = alphabet.find(c) original_index = (index + rotate_const) % len(alphabet) decoded = decoded + alphabet[original_index] print(decoded) def choose_greatest(): """Echo the largest of the two numbers given by the user to the program Warning: this function was written quickly and needs proper error handling """ user_value_1 = input("What's your first number? ") user_value_2 = input("What's your second number? ") greatest_value = user_value_1 # need a value to return if 1 & 2 are equal if user_value_1 > user_value_2: greatest_value = user_value_1 elif user_value_1 < user_value_2: greatest_value = user_value_2 print( "The number with largest positive magnitude is " + str(greatest_value) ) choose_greatest() Solution: ソースを見ると呼ばれていない関数 decode_secret(secret) がある。 末尾を変更 # choose_greatest() decode_secret(bezos_cc_secret) 実行 >python crackme_solv.py picoCTF{1|\/|_4_p34|\|ut_4593da8a} Magikarp Ground Mission Category: General Skills Description: Do you know how to move between directories and read files in the shell? Start the container, ssh to it, and then ls once connected to begin. Login via ssh as ctf-player with the password, abcba9f7 ssh ctf-player@venus.picoctf.net -p 49915 Hints: 1. Finding a cheatsheet for bash would be really helpful! Solution: sshでつなげて簡単な調査をするだけ。 tunn3l v1s10n picoCTF 2021 tunn3l v1s10n Writeup Easy Peasy Category: Cryptography Description: A one-time pad is unbreakable, but can you manage to recover the flag? (Wrap with picoCTF{}) nc mercury.picoctf.net 20266 otp.py Hints: 1. Maybe there's a way to make this a 2x pad. otp.py #!/usr/bin/python3 -u import os.path KEY_FILE = "key" KEY_LEN = 50000 FLAG_FILE = "flag" def startup(key_location): flag = open(FLAG_FILE).read() kf = open(KEY_FILE, "rb").read() start = key_location stop = key_location + len(flag) key = kf[start:stop] key_location = stop result = list(map(lambda p, k: "{:02x}".format(ord(p) ^ k), flag, key)) print("This is the encrypted flag!\n{}\n".format("".join(result))) return key_location def encrypt(key_location): ui = input("What data would you like to encrypt? ").rstrip() if len(ui) == 0 or len(ui) > KEY_LEN: return -1 start = key_location stop = key_location + len(ui) kf = open(KEY_FILE, "rb").read() if stop >= KEY_LEN: stop = stop % KEY_LEN key = kf[start:] + kf[:stop] else: key = kf[start:stop] key_location = stop result = list(map(lambda p, k: "{:02x}".format(ord(p) ^ k), ui, key)) print("Here ya go!\n{}\n".format("".join(result))) return key_location print("******************Welcome to our OTP implementation!******************") c = startup(0) while c >= 0: c = encrypt(c) Solution: まず nc してみる $ nc mercury.picoctf.net 20266 ******************Welcome to our OTP implementation!****************** This is the encrypted flag! 5b1e564b6e415c0e394e0401384b08553a4e5c597b6d4a5c5a684d50013d6e4b What data would you like to encrypt? picoCTF{aaa} Here ya go! 115f3f177b321a0300503d05 What data would you like to encrypt? flagが暗号化され,さらに任意の文字を暗号化してくれる ソースコードを読み解いてコメントを付加していく otp.py #!/usr/bin/python3 -u import os.path KEY_FILE = "key" KEY_LEN = 50000 #keyのサイズ FLAG_FILE = "flag" def startup(key_location): flag = open(FLAG_FILE).read() kf = open(KEY_FILE, "rb").read() start = key_location stop = key_location + len(flag) #消費するkeyの長さはflagの長さと同じ key = kf[start:stop] key_location = stop #keyの先頭を更新 result = list(map(lambda p, k: "{:02x}".format(ord(p) ^ k), flag, key)) print("This is the encrypted flag!\n{}\n".format("".join(result))) ''' lambdaが苦手なので,変形してみた enc = [] i = 0 while i < len(flag): enc.append(flag[i] ^ key[i]) i = i + 1 print(enc) print(''.join(map(chr,enc))) ''' return key_location def encrypt(key_location): ui = input("What data would you like to encrypt? ").rstrip() if len(ui) == 0 or len(ui) > KEY_LEN: return -1 start = key_location stop = key_location + len(ui) #入力された文字長だけkeyを消費 kf = open(KEY_FILE, "rb").read() if stop >= KEY_LEN: #5000バイトを超えたら一周する stop = stop % KEY_LEN key = kf[start:] + kf[:stop] else: key = kf[start:stop] key_location = stop result = list(map(lambda p, k: "{:02x}".format(ord(p) ^ k), ui, key)) print("Here ya go!\n{}\n".format("".join(result))) return key_location print("******************Welcome to our OTP implementation!******************") c = startup(0) #keyの先頭は0バイト目に位置づけ while c >= 0: c = encrypt(c) 整理すると encrypted flag 5b1e564b6e415c0e394e0401384b08553a4e5c597b6d4a5c5a684d50013d6e4b flag長 32バイト key長 5000バイト(keyの開始位置が5000を超えると先頭に戻る) よって What data would you like to encrypt? で4968バイト送信した後,次の What data would you like to encrypt? で\x00を32バイト送信するとkeyが丸見えになる(\x00とxorするとkeyが丸見え) $ python3 -c "print('\x00'*(50000-32)+'\n'+'\x00'*32)" | nc mercury.picoctf.net 20266 (中略) What data would you like to encrypt? Here ya go! 6227667c5c7865385c7862365c7831625c7839384b5c7864385c7861365e5c78 あとは 5b1e564b6e415c0e394e0401384b08553a4e5c597b6d4a5c5a684d50013d6e4bと 6227667c5c7865385c7862365c7831625c7839384b5c7864385c7861365e5c78を xorするだけ ARMssembly 0 Category: Reverse Engineering Description: What integer does this program print with arguments 182476535 and 3742084308? File: chall.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb}) Hints: Simple compare chall.S .arch armv8-a .file "chall.c" .text .align 2 .global func1 .type func1, %function func1: sub sp, sp, #16 str w0, [sp, 12] str w1, [sp, 8] ldr w1, [sp, 12] ldr w0, [sp, 8] cmp w1, w0 bls .L2 ldr w0, [sp, 12] b .L3 .L2: ldr w0, [sp, 8] .L3: add sp, sp, 16 ret .size func1, .-func1 .section .rodata .align 3 .LC0: .string "Result: %ld\n" .text .align 2 .global main .type main, %function main: stp x29, x30, [sp, -48]! add x29, sp, 0 str x19, [sp, 16] str w0, [x29, 44] str x1, [x29, 32] ldr x0, [x29, 32] add x0, x0, 8 ldr x0, [x0] bl atoi mov w19, w0 ldr x0, [x29, 32] add x0, x0, 16 ldr x0, [x0] bl atoi mov w1, w0 mov w0, w19 bl func1 mov w1, w0 adrp x0, .LC0 add x0, x0, :lo12:.LC0 bl printf mov w0, 0 ldr x19, [sp, 16] ldp x29, x30, [sp], 48 ret .size main, .-main .ident "GCC: (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0" .section .note.GNU-stack,"",@progbits Solution: 重要な場所だけ見ていく "以下のコメントはx86のアセンブラで説明 ldr w1, [sp, 12] " mov ecx, [ebp+c] ecx に 第2引数(ebp+c)を代入 ldr w0, [sp, 8] " mov edx, [ebp+8] edx に 第1引数(ebp+8)を代入 cmp w1, w0 " cmp ecx, edx 比較 bls .L2 " jle .L2 第1引数のほうが大きいとき .L2へ ldr w0, [sp, 12] " mov eax, [ebp+c] JUMPしなかったので第2引数(ebp+c)のほうが大きいので戻り値EAXにセット b .L3 " jmp .L3 .L2: ldr w0, [sp, 8] " mov eax, [ebp+8] .L3: add sp, sp, 16 つまり2つの引数の大きい方を返してる 3742084308 を 16進にしたら正解 Cookies Category: Web Exploitation Description: Who doesn't love cookies? Try to figure out the best one. http://mercury.picoctf.net:27177/ Hints: (None) Solution: aaaを入力 Burp Suiteでは テキストボックスに薄く見えている snickerdoodle を入力 おしいみたい。 Burpは? snickerdoodle name=0 今度は,burp で intersept して name=1 を送ってみる name=1 は chocolate chip cookies name=2は oatmeal raisin cookies 中略 name=18 力業に頼らない方法 $ curl --cookie "name=0" --data "name=aaa" http://mercury.picoctf.net:27177/search <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> <title>Redirecting...</title> <h1>Redirecting...</h1> <p>You should be redirected automatically to target URL: <a href="/">/</a>. If not click the link. Redirecting...でエラーになっている -L オプションをつけ,リダイレクトも追うように curl --cookie "name=0" --data "name=aaa" -L http://mercury.picoctf.net:27177/search <!DOCTYPE html> <html lang="en"> <head> <title>Cookies</title> <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet"> <link href="https://getbootstrap.com/docs/3.3/examples/jumbotron-narrow/jumbotron-narrow.css" rel="stylesheet"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> </head> <body> <div class="container"> <div class="header"> <nav> <ul class="nav nav-pills pull-right"> <li role="presentation"><a href="/reset" class="btn btn-link pull-right">Home</a> </li> </ul> </nav> <h3 class="text-muted">Cookies</h3> </div> <!-- Categories: success (green), info (blue), warning (yellow), danger (red) --> <div class="alert alert-success alert-dismissible" role="alert" id="myAlert"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button> <!-- <strong>Title</strong> --> That is a cookie! Not very special though... </div> <div class="jumbotron"> <p class="lead"></p> <p style="text-align:center; font-size:30px;"><b>I love snickerdoodle cookies!</b></p> </div> <footer class="footer"> <p>&copy; PicoCTF</p> </footer> </div> <script> $(document).ready(function(){ $(".close").click(function(){ $("myAlert").alert("close"); }); }); </script> </body> </html> クッキーの名前は <p style="text-align:center; font-size:30px;"><b>I love snickerdoodle cookies!</b></p> "<b>"をgrepすればいいみたい $ curl --cookie "name=0" --data "name=aaa" -L http://mercury.picoctf.net:27177/search | grep "<b>" % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 237 100 219 100 18 341 28 --:--:-- --:--:-- --:--:-- 369 100 1779 100 1779 0 0 1732 0 0:00:01 0:00:01 --:--:-- 1732 <p style="text-align:center; font-size:30px;"><b>I love snickerdoodle cookies!</b></p> なんか進捗が出た -s で進捗が消えるみたい $ curl --cookie "name=0" --data "name=aaa" -s -L http://mercury.picoctf.net:27177/search | grep "<b>" <p style="text-align:center; font-size:30px;"><b>I love snickerdoodle cookies!</b></p> イイ感じ。あとは回すだけ $ for i in {0..99} ; do > echo -n $i > echo -n " " > curl --cookie "name=$i" --data "name=aaa" -s -L http://mercury.picoctf.net:27177/search | grep "<b>" | awk -F "<b>" '{print $2}' > done 0 I love snickerdoodle cookies!</b></p> 1 I love chocolate chip cookies!</b></p> 2 I love oatmeal raisin cookies!</b></p> 3 I love gingersnap cookies!</b></p> 4 I love shortbread cookies!</b></p> 5 I love peanut butter cookies!</b></p> 6 I love whoopie pie cookies!</b></p> 7 I love sugar cookies!</b></p> 8 I love molasses cookies!</b></p> 9 I love kiss cookies!</b></p> 10 I love biscotti cookies!</b></p> 11 I love butter cookies!</b></p> 12 I love spritz cookies!</b></p> 13 I love snowball cookies!</b></p> 14 I love drop cookies!</b></p> 15 I love thumbprint cookies!</b></p> 16 I love pinwheel cookies!</b></p> 17 I love wafer cookies!</b></p> 18 Flag</b>: <code>picoCTF{3v3ry1_l0v3s_c00k135_064663be}</code></p> 19 I love macaroon cookies!</b></p> 20 I love fortune cookies!</b></p> 21 I love crinkle cookies!</b></p> 22 I love icebox cookies!</b></p> 23 I love gingerbread cookies!</b></p> 24 I love tassie cookies!</b></p> 25 I love lebkuchen cookies!</b></p> 26 I love macaron cookies!</b></p> 27 I love black and white cookies!</b></p> 28 I love white chocolate macadamia cookies!</b></p> 29^C vault-door-training Category: Reverse Engineering Description: Your mission is to enter Dr. Evil's laboratory and retrieve the blueprints for his Doomsday Project. The laboratory is protected by a series of locked vault doors. Each door is controlled by a computer and requires a password to open. Unfortunately, our undercover agents have not been able to obtain the secret passwords for the vault doors, but one of our junior agents obtained the source code for each vault's computer! You will need to read the source code for each level to figure out what the password is for that vault door. As a warmup, we have created a replica vault in our training facility. The source code for the training vault is here: VaultDoorTraining.java Hints: 1. The password is revealed in the program's source code. VaultDoorTraining.java import java.util.*; class VaultDoorTraining { public static void main(String args[]) { VaultDoorTraining vaultDoor = new VaultDoorTraining(); Scanner scanner = new Scanner(System.in); System.out.print("Enter vault password: "); String userInput = scanner.next(); String input = userInput.substring("picoCTF{".length(),userInput.length()-1); if (vaultDoor.checkPassword(input)) { System.out.println("Access granted."); } else { System.out.println("Access denied!"); } } // The password is below. Is it safe to put the password in the source code? // What if somebody stole our source code? Then they would know what our // password is. Hmm... I will think of some ways to improve the security // on the other doors. // // -Minion #9567 public boolean checkPassword(String password) { return password.equals("w4rm1ng_Up_w1tH_jAv4_3808d338b46"); } } Solution: 表層解析 Insp3ct0r Category: Web Exploitation Description: Kishor Balan tipped us off that the following code may need inspection: https://jupiter.challenges.picoctf.org/problem/9670/ (link) or http://jupiter.challenges.picoctf.org:9670 Hints: 1. How do you inspect web code on a browser? 2. There's 3 parts Solution: ページのソース CSS js Lets Warm Up Category: General Skills Description: If I told you a word started with 0x70 in hexadecimal, what would it start with in ASCII? Hints: 1. Submit your answer in our flag format. For example, if your answer was 'hello', you would submit 'picoCTF{hello}' as the flag. Solution: Glory of the Garden Description: This garden contains more than it seems. Hints: What is a hex editor? Solution: 表層解析
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む