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

PythonのライブラリをUnrealEngine4上で起動させる

はじめに 今回はUnrealEngine4のエディタ上でPythonのライブラリのプログラムを記述したいと思います。 プラグインでのライブラリだとnumpyやsckitlearnが入っていないので使える様にする手引きまで今回は扱います(具体的な用途についてはまだ勉強中です) 環境 UnrealEngine4 4.25 *以下UE4と略します Python 3.7.0 osはWindowsになります。 プラグイン UE4上で起動させ,「Scripting」から「Python Editor Script Plugin」を選択 Hello World 「ウインドウ」から「アウトプットログ」を表示させておく。既存のunrealライブラリから呼び出します。その際に左下からPythonのコマンドを選択します。 以下のスクリプトを書きます import unreal unreal.log("Hello world") 以下の様に返ってきます。 外部ライブラリのインストール 今回はnumpy,matplotlib,sckitlearnのインストールします。まずはディレクトリはEpic内でPythonにあるディレクトリを探します。今回のosはWindowsなのでコマンドプロンプトで C:\Program Files\Epic Games\UE_4.26\Engine\Binaries\ThirdParty\Python3\Win64 を見つけて移動します(cd) そして移動後以下の様にインストールします(pipからだとダメでした) Python -m pip install numpy Python -m pip install scikit-learn Python -m pip install matplotlib スクリプト 実際に上記のライブラリを用いて,今回はKmeans法を試します。 人工データを生成し,クラスタを割り当てます。以下のファイルを作成 kmeans_Test.py import numpy as np import matplotlib.pyplot as plt from sklearn.cluster import KMeans #正規乱数による生成 np.random.seed(0) a=np.random.randn(40,2) b=np.random.randn(40,2)+np.array([1,0]) c=np.random.randn(40,2)+np.array([1,1]) #すべてを結合しシャッフル abc=np.r_[a,b,c] np.random.shuffle(abc) #学習 model= KMeans(n_clusters=3) result=model.fit(abc) marks=["x","+","*"] #表示 for i in range(3): p=[result.labels_[j]== i for j in range(len(result.labels_))] plt.scatter(abc[p][:,0],abc[p][:,1],marker=marks[i]) plt.show() 実行 ファイル作成後,UnrealEngineの「ファイル」から実行 以下の様に表示されたら成功 最後に Unreal内でのアセット管理などでPythonは有効活用出来るみたいなので,色々リサーチしたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonのライブラリをUnrealEngine4上で表示させる

はじめに 今回はUnrealEngine4のエディタ上でPythonのライブラリのプログラムを記述したいと思います。 プラグインでのライブラリだとnumpyやsckitlearnが入っていないので使える様にする手引きまで今回は扱います(具体的な用途についてはまだ勉強中です) 環境 UnrealEngine4 4.25 *以下UE4と略します Python 3.7.0 osはWindowsになります。 プラグイン UE4上で起動させ,「Scripting」から「Python Editor Script Plugin」を選択 Hello World 「ウインドウ」から「アウトプットログ」を表示させておく。既存のunrealライブラリから呼び出します。その際に左下からPythonのコマンドを選択します。 以下のスクリプトを書きます import unreal unreal.log("Hello world") 以下の様に返ってきます。 外部ライブラリのインストール 今回はnumpy,matplotlib,sckitlearnのインストールします。まずはディレクトリはEpic内でPythonにあるディレクトリを探します。今回のosはWindowsなのでコマンドプロンプトで C:\Program Files\Epic Games\UE_4.26\Engine\Binaries\ThirdParty\Python3\Win64 を見つけて移動します(cd) そして移動後以下の様にインストールします(pipからだとダメでした) Python -m pip install numpy Python -m pip install scikit-learn Python -m pip install matplotlib スクリプト 実際に上記のライブラリを用いて,今回はKmeans法を試します。 人工データを生成し,クラスタを割り当てます。以下のファイルを作成 kmeans_Test.py import numpy as np import matplotlib.pyplot as plt from sklearn.cluster import KMeans #正規乱数による生成 np.random.seed(0) a=np.random.randn(40,2) b=np.random.randn(40,2)+np.array([1,0]) c=np.random.randn(40,2)+np.array([1,1]) #すべてを結合しシャッフル abc=np.r_[a,b,c] np.random.shuffle(abc) #学習 model= KMeans(n_clusters=3) result=model.fit(abc) marks=["x","+","*"] #表示 for i in range(3): p=[result.labels_[j]== i for j in range(len(result.labels_))] plt.scatter(abc[p][:,0],abc[p][:,1],marker=marks[i]) plt.show() 実行 ファイル作成後,UnrealEngineの「ファイル」から実行 以下の様に表示されたら成功 最後に Unreal内でのアセット管理などでPythonは有効活用出来るみたいなので,色々リサーチしたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超シンプル(?)なジャンケン実装

最初にコード全体 これだけ import cmath _2pi = 2 * cmath.pi ROCK = 1 SCISSORS = complex(cmath.cos(_2pi/3), cmath.sin(_2pi/3)) PAPER = complex(cmath.cos(-_2pi/3), cmath.sin(-_2pi/3)) def judge(one, another): arg = cmath.phase(another / one) if arg == 0: return "あいこ" elif arg > 0: return "勝ち" else: return "負け" テストコード def test(): assert "勝ち" == judge(ROCK, SCISSORS) assert "勝ち" == judge(SCISSORS, PAPER) assert "勝ち" == judge(PAPER, ROCK) assert "負け" == judge(SCISSORS, ROCK) assert "負け" == judge(PAPER, SCISSORS) assert "負け" == judge(ROCK, PAPER) assert "あいこ" == judge(ROCK, ROCK) assert "あいこ" == judge(SCISSORS, SCISSORS) assert "あいこ" == judge(PAPER, PAPER) 解説 そもそも複素数を導入したモチベは、円上に✊✌✋を配置すれば三つ巴の強弱関係をうまいこと表現できるのではと思ったからである。 complex型(複素数)で定義しているROCK, SCISSORS , PAPER(それぞれ ✊, ✌, ✋)を複素平面上に図示すると下図のようになる。 \begin{align} \text{ROCK} &= 1 \\ \text{SCISSORS} &= \cos\left( \frac{2\pi}{3} \right) + i \sin\left( \frac{2\pi}{3} \right) \\ \text{PAPER} &= \cos\left( -\frac{2\pi}{3} \right) + i \sin\left( -\frac{2\pi}{3} \right) \end{align} 勝ち 負け これを反時計回りすると「✊は✌に勝ち、✌は✋に勝ち、✋は✊に勝つ」という勝ちパターンが現れ、逆に時計回りすると「✊は✋に負け、✋は✌に負け、✌は✊に負ける」という負けパターンが現れる。 ここで、✊を反時計回りに120°ずらすと✌になるので、✊から✌への偏角 $$ \arg \frac{\text{SCISSORS}}{\text{ROCK}}$$ は+120°である。逆に、✌から✊への偏角 $$ \arg \frac{\text{ROCK}}{\text{SCISSORS}}$$ は-120°である。 (一般的に、反時計回りはプラスの角度で、時計回りはマイナスの角度として扱われる。また、偏角の主値は $(-180°,~+180°]$ とする。) つまり、以下の手順で分母にした変数の勝敗を知ることができる。 対決させたい変数同士で割り算する。 偏角をとる。 プラスかマイナスかゼロかを判別する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

複素数を用いたジャンケン実装

最初にコード全体 import cmath _2pi = 2 * cmath.pi ROCK = 1 SCISSORS = complex(cmath.cos(_2pi/3), cmath.sin(_2pi/3)) PAPER = complex(cmath.cos(-_2pi/3), cmath.sin(-_2pi/3)) def judge(one, another): arg = cmath.phase(another / one) if arg == 0: return "あいこ" elif arg > 0: return "勝ち" else: return "負け" テストコード def test(): assert "勝ち" == judge(ROCK, SCISSORS) assert "勝ち" == judge(SCISSORS, PAPER) assert "勝ち" == judge(PAPER, ROCK) assert "負け" == judge(SCISSORS, ROCK) assert "負け" == judge(PAPER, SCISSORS) assert "負け" == judge(ROCK, PAPER) assert "あいこ" == judge(ROCK, ROCK) assert "あいこ" == judge(SCISSORS, SCISSORS) assert "あいこ" == judge(PAPER, PAPER) 解説 そもそも複素数を導入したモチベは、円上に✊✌✋を配置すれば三つ巴の強弱関係をうまいこと表現できるのではと思ったからである。 complex型(複素数)で定義しているROCK, SCISSORS , PAPER(それぞれ ✊, ✌, ✋)を複素平面上に図示すると下図のようになる。 \begin{align} \text{ROCK} &= 1 \\ \text{SCISSORS} &= \cos\left( \frac{2\pi}{3} \right) + i \sin\left( \frac{2\pi}{3} \right) \\ \text{PAPER} &= \cos\left( -\frac{2\pi}{3} \right) + i \sin\left( -\frac{2\pi}{3} \right) \end{align} 勝ち 負け これを反時計回りすると「✊は✌に勝ち、✌は✋に勝ち、✋は✊に勝つ」という勝ちパターンが現れ、逆に時計回りすると「✊は✋に負け、✋は✌に負け、✌は✊に負ける」という負けパターンが現れる。 ここで、✊を反時計回りに120°ずらすと✌になるので、✊から✌への偏角 $$ \arg \frac{\text{SCISSORS}}{\text{ROCK}}$$ は+120°である。逆に、✌から✊への偏角 $$ \arg \frac{\text{ROCK}}{\text{SCISSORS}}$$ は-120°である。 (一般的に、反時計回りはプラスの角度で、時計回りはマイナスの角度として扱われる。また、偏角の主値は $(-180°,~+180°]$ とする。) つまり、以下の手順で分母にした変数の勝敗を知ることができる。 対決させたい変数同士で割り算する。 偏角をとる。 プラスかマイナスかゼロかを判別する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VScodeでPython docstringを作成する

備忘録として 環境 ホストOS: indows11 ゲストOS: WSL2 Ubuntu20.04 VScode1.63.2 Python3.10.1 手順 拡張機能「Python Docstring Generator」をインストールしてVScodeを再起動します クラスや関数宣言箇所の直下でctrl + shift + 2 と入力するとdocstringが生成されます ※ """ (ダブルクォーテーション3つ)でも生成できますが、入力の手間を考えるとショートカットキーを使う方が良いでしょう あとは[sumary]や[description]欄に適宜関数や引数の情報を記述していきましょう。 備考 「Python Docstring Generator」ではdocstringに4通りのスタイルが利用可能です。 ① docblockr 標準的な形式です ② google docblockrをよりシンプルにした形です。個人的にはこれが好みです ③ sphinx パラメータの名前と型が縦に並んでいます。どうしてもdocstringが縦長になるので可読性が低くなります。 ④ numpy ②と③のあいの子みたいな感じです。 どれを使うか 関数の動作に影響しないので、個人開発では好み・チーム開発ではチームのルールに合わせれば良いでしょう。 私はgoogleがシンプルで好みです。(デフォルトの設定もgoogleになっています) 注意点 Python for VSCodeという拡張機能が有効になっている場合、戻り値の型が指定されているとdocstringが正しく表示されません。 詳しい原因は分かりませんがPython for VSCodeが有効化されていると、シンタックスハイライトに影響するようです。 差支えなければ無効にするかアンインストールしてしまってよいでしょう。 (レビューも低評価が多いですね...) 参考資料 VSCodeの拡張機能でPython docstringを生成する Python for VSCode - Visual Studio Marketplace VSCode Python Docstring Generator pythonシンタックスハイライト崩れ問題 in VSCode [python] syntax highlighting breaks on function annotations #138
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ラビットチャレンジ【E資格】 深層学習day3

初めに 本記事はJDLAのE資格の認定プログラム「ラビット・チャレンジ」における深層学習day3のレポート記事です。この記事では以下の内容について、そのモデルの概念から確認し、数式・実装を含めてまとめていきます。 再帰型ニューラルネットワークの概念 LSTM(Long Short Term Memory) GRU 双方向RNN Seq2Seq Word2Vec Attention Mechanism 再帰型ニューラルネットワークの概念 Recurrent Neural Network(RNN) 再帰型ニューラルネットワーク(Recurrent Neural Network:RNN)は、時系列データの扱いを得意とするニューラルネットワークです。時系列データとは、ある要素が順番に $$ x_{1}, x_{2}, x_{3}, \ldots, x_{T} $$ のように並んでいるデータのことを言います。このデータの添え字は通常tで表されますが、このtはデータの種類によって若干意味合いが異なります。時系列データの代表例として音声の波形、動画、文章(単語列)などがありますが、音声の波形なら一定の時間間隔(数ms)でのサンプル時間になりますし、文章なら単語を前から並べたときの番号になります。RNNの構造は下図のようになります。 ラビットチャレンジ講義資料 順伝播の式 \begin{gathered} u^{t}=W_{(i n)} x^{t}+W z^{t-1}+b \\ z^{t}=f\left(W_{(i n)} x^{t}+W z^{t-1}+b\right) \\ v^{t}=W_{(o u t)} z^{t}+c \\ y^{t}=g\left(W_{(o u t)} z^{t}+c\right) \end{gathered} RNNの特徴 通常のニューラルネットワークでは、ある層の出力は、次の層の入力に利用されるのみです。しかしRNNでは、ある層の出力は、次の層の入力として利用されるだけでなく、一般的なニューラルネットワークの最後の層のような出力としても利用されます。また、各層の入力として、前の層の入力のみではなく、時系列のデータポイントも入力とします。このように、ニューラルネットワークの出力を別のネットワークの入力として利用するような再帰的構造を持ったニューラルネットワークのことをRecursive Neural Networkと呼びます。Recursive Neural Networkの中でも、隠れ層同士の結合が時系列に沿って直線的であり、かつその隠れ層が同一構造のものであるような場合を「RNN」といいます。RNNでは、再帰的に出現する同一のネットワーク構造のことをセル(cell)と呼びます。 コード実装 Backpropagation Through Time(BPTT) 基本的には、全損失Lを時間t=1からt=Tまでのすべての損失関数の総和であると考えます。 $$ L=\sum_{t=1}^{T} L^{(t)} $$ 時間tまでの損失は、それ以前のすべての時間刻みの隠れユニットに依存するため、勾配は次のように計算されます。 $$ \frac{\partial L^{(t)}}{\partial W_{h h}}=\frac{\partial L^{(t)}}{\partial y^{(t)}} \times \frac{\partial y^{(t)}}{\partial h^{(t)}} \times\left(\sum_{k=1}^{t} \frac{\partial h^{(t)}}{\partial h^{(k)}} \times \frac{\partial h^{(k)}}{\partial W_{h h}}\right) $$ ここで、$\frac{\partial h^{(t)}}{\partial h^{(k)}}$は連続する時間刻みの総乗として計算されます。 $$ \frac{\partial h^{(t)}}{\partial h^{(k)}}=\prod_{i=k+1}^{t} \frac{\partial h^{(i)}}{\partial h^{(i-1)}} $$ 損失関数の勾配を計算するときの乗法係数$\frac{\partial h^{(t)}}{\partial h^{(k)}}$により、いわゆる勾配消失問題と勾配発散問題が発生してしまいます。 Backpropagation Through Time: What It Does and How to Do It 1 重み・バイアス \begin{gathered} \frac{\partial E}{\partial W_{(i n)}}=\frac{\partial E}{\partial u^{t}}\left[\frac{\partial u^{t}}{\partial W_{(i n)}}\right]^{T}=\delta^{t}\left[x^{t}\right]^{T} \\ \frac{\partial E}{\partial W_{(o u t)}}=\frac{\partial E}{\partial v^{t}}\left[\frac{\partial v^{t}}{\partial W_{(o u t)}}\right]^{T}=\delta^{\text {out }, t}\left[z^{t}\right]^{T} \\ \frac{\partial E}{\partial W}=\frac{\partial E}{\partial u^{t}}\left[\frac{\partial u^{t}}{\partial W}\right]^{T}=\delta^{t}\left[z^{t-1}\right]^{T} \\ \frac{\partial E}{\partial b}=\frac{\partial E}{\partial u^{t}} \frac{\partial u^{t}}{\partial b}=\delta^{t} \\ \frac{\partial E}{\partial c}=\frac{\partial E}{\partial v^{t}} \frac{\partial v^{t}}{\partial c}=\delta^{\text {out }, t} \end{gathered} \begin{gathered} \frac{\partial E}{\partial u^{t}}=\frac{\partial E}{\partial v^{t}} \frac{\partial v^{t}}{\partial u^{t}}=\frac{\partial E}{\partial v^{t}} \frac{\partial\left\{W_{\text {(out) }} f\left(u^{t}\right)+c\right\}}{\partial u^{t}}=f^{\prime}\left(u^{t}\right) W_{(c u t)}^{T} \delta^{\text {out }, t}=\delta^{t} \\ \delta^{t-1}=\frac{\partial E}{\partial u^{t-1}}=\frac{\partial E}{\partial u^{t}} \frac{\partial u^{t}}{\partial u^{t-1}}=\delta^{t}\left\{\frac{\partial u^{t}}{\partial z^{t-1}} \frac{\partial z^{t-1}}{\partial u^{t-1}}\right\}=\delta^{t}\left\{W f^{\prime}\left(u^{t-1}\right)\right\} \\ \delta^{t-z-1}=\delta^{t-z}\left\{W f^{\prime}\left(u^{t-z-1}\right)\right\} \end{gathered} 4 パラメータの更新 \begin{gathered} W_{(\text {in })}^{t+1}=W_{(\text {(in) }}^{t}-\epsilon \frac{\partial E}{\partial W_{(\text {in })}}=W_{(\text {in })}^{t}-\epsilon \sum_{z=0}^{T_{t}} \delta^{t-z}\left[x^{t-z}\right]^{T} \\ W_{(\text {out })}^{+1}=W_{(\text {out })}^{t}-\epsilon \frac{\partial E}{\partial W_{(\text {ou })}}=W_{(\text {out })}^{t}-\epsilon \delta^{\text {out }, t}\left[z^{t}\right]^{T} \\ W^{t+1}=W^{t}-\epsilon \frac{\partial E}{\partial W}=W_{(\text {in })}^{t}-\epsilon \sum_{z=0}^{T_{t}} \delta^{t-z}\left[z^{t-z-1}\right]^{T} \\ b^{t+1}=b^{t}-\epsilon \frac{\partial E}{\partial b}=b^{t}-\epsilon \sum_{z=0}^{T_{t}} \delta^{t-z} \\ c^{t+1}=c^{t}-\epsilon \frac{\partial E}{\partial c}=c^{t}-\epsilon \delta^{\text {out }, t} \end{gathered} コード実装 確認テスト 確認テスト1 サイズ5×5の入力画像を、サイズ3×3,ストライドは2、パディングは1のフィルタで畳み込んだ時の出力画像のサイズを答えよ。 縦 $$ \frac{5+2 \times 1-3}{2}+1=3 $$ 横 $$ \frac{5+2 \times 1-3}{2}+1=3 $$ -> 3×3 確認テスト2 RNNネットワークには大きく分けて3つの重みがある。1つは入力から現在の中間層を定義する際にかけられる重み、1つは中間層から出力を定義する際にかけられる重みである。残りひとつの重みについて説明せよ。 -> 前の中間層(t−1)から現在の中間層(t)を定義する際にかけられる重み 確認テスト3 連鎖律の原理を使い、dz/dxを求めよ。 \begin{gathered} z=t^{2} \\ t=x+y \end{gathered} \frac{d x}{d x}=\frac{d x}{d t} \frac{d t}{d x}=2 t \times 1=2(x+y) -> よって$2(x+y)$ 確認テスト4 下図のy1を$x \cdot z_{0} \cdot z_{1} \cdot w_{\text {in }} \cdot w \cdot w_{\text {out }}$を用いて数式で表わせ。 中間層の出力にシグモイド関数g(x)を作用させよ。 \begin{gathered} z_{1}=f\left(z_{0} \text { W }+x_{1} \text { Win }+b\right) \\ y_{1}=g\left(z_{1} \text { Wout }+c\right) \end{gathered} LSTM(Long Short Term Memory) LSTM(Long Short-Term Memory)は勾配消失問題を解決する方法として1997年に提唱されたものです。LSTMはRNNの中間層のユニットをLSTM Blockと呼ばれるメモリと3つのゲートを持つブロックに置き換えることで実現されています。 LSTMの基礎 LSTMのその最も大きな特長は、従来のRNNでは学習できなかった長期依存(long-term dependencies)を学習可能であるところにあります。その最も単純な一例を以下に示します。 $$ \begin{aligned} &\left(x, a_{1}, a_{2}, \cdots, a_{p-1}, x\right) \ &\left(y, a_{1}, a_{2}, \cdots, a_{p-1}, y\right) \end{aligned} $$ 通常のRNNでも数十ステップの短期依存(short-term dependencies)には対応できるのですが、1000ステップのような長期の系列は学習することができませんでした。LSTMはこのような系列に対しても適切な出力を行うことができます。 CEC 記憶セルのことです。時刻tにおけるLSTMの記憶が格納されており、この部分に過去から時刻t までにおいて必要な情報が格納されています。 入力ゲート 入力ゲートは、a(t)の各要素が新たに追加する情報としてどれだけ価値があるかを判断します。この入力ゲートによって、追加する情報の取捨選択を行います。別の見方をすると、入力ゲートによって重みづけされた情報が新たに追加されることになります。 出力ゲート 出力ゲートは、g(c(t))の各要素が次時刻の隠れ状態h(t)としてどれだけ重要かという事を調整します 入力ゲート同様、必要な誤差信号のみ伝達する仕組みを導入し出力の重み衝突問題を解決します。 忘却ゲート 記憶セルであるCECに対して、不要な記憶を忘れさせるための役割をするゲートです。 過去の情報がいらなくなる時に、過去の情報を削除する機能を持つために忘却ゲートを使用する。 覗き穴結合(peep hole) ゲートにより遮断された情報を再度活用するためにメモリセルから各ゲートへ情報を流し込むために使用します。 実装コード 確認テスト 確認テスト1 以下の文章にLSTMに入力し空欄に当てはまる単語を予測したいとする。文中の「とても」という言葉は空欄の予測において無くなっても影響を及ぼさないと考えられる。このような場合、どのゲートが作用すると考えられるか。 「映画面白かったね。ところで、とてもお腹が空いたから何か____。」 -> 忘却ゲート How LSTM networks solve the problem of vanishing gradients GRU LSTMではパラメータ数が多く、計算負荷が高くなることが問題でした。しかし、GRUでは、そのパラメータを大幅に削減し、さらにLSTMより計算量が少なく学習可能です。LSTMと比べて、音声モデリングなどの限定的な用途でのみ同等またはそれ以上の性能を発揮します。LSTMはGRUより厳密に強力なため、GRUで学習できることは全てLSTMで学習可能です。 実装コード 確認テスト 確認テスト1 LSTMとCECが抱える問題について、それぞれ簡潔に述べよ。 -> LSTMは多くのパラメータを持つため、複雑で計算負荷が大きくなってしまいます。CEC自体には学習機能がなく、周りに学習能力のあるゲート(入力ゲート、出力ゲート、忘却ゲート)が必要です。 確認テスト2 LSTMとGRUの違いを簡潔に説明せよ。 -> LSTMはCEC、入力ゲート、出力ゲート、忘却ゲートを持ちパラメータが多いため計算負荷が大きです。一方、GRUはリセットゲート、更新ゲートを持ち、パラメータが少ないので計算コストがLSTMよりも小さいです。 双方向RNN(BRNN (Bidirectional Recurrent Neural Networks)) 2つの正反対の方向の隠れ層を使用します。1つは過去に向かって、もう一つは未来に向かって学習します。これは手書き文字の認識などに効果を発揮します。過去の手書き文字と未来の手書き文字の双方向から現在の文字を予想することができます。 実装コード Seq2Seq EncoderRNNという入力部とDecoderRNNという出力部を持つRNNです。文章をトークン(単語などの単位)に区切って、EncoderRNNで最初のトークンから順に次のトークンへ時系列的に重みを伝搬させていきます。EncoderRNNの最後の重みの出力をDecoderRNNに学習結果として反映させます。同じく時系列ごとにDecoderRNNの前の時系列の出力(トークン単位)が、DecoderRNNの次の時系列の入力となって、次々とトークンを出力して文章を完成させます。 課題-> 一問一答しかできない。(文脈を学習することができない) Encoder RNN Decoder RNN HRED Seq2Seq+ Context RNN seq2seqは一問一答しかできません。問に対して文脈も何もなく、ただ応答が行われる続けます。この時、過去n-1個の発話から次の発話を生成します。Seq2Seqでは、会話の文脈無視で応答されましたが、HREDでは、前の単語の流れに即して応答されるため、Seq2Seqと比較してより人間らしい文章が生成されます。 課題-> HRED は確率的な多様性が字面にしかなく、会話の「流れ」のような多様性がありません。同じコンテキスト(発話リスト)を与えられても、答えの内容が毎回会話の流れとしては同じものしか出せません。 VHRED HREDに、VAEの潜在変数の概念を追加したもので、VAEの潜在変数の概念を追加することで解決した構造です。 Auto Encoder 教師なし学習の一つです。そのため学習時の入力データは訓練データのみで教師データは利用しません。 MNISTの場合、28x28の数字の画像を入れて、同じ画像を出力するニューラルネットワークということになります。 オートエンコーダ構造 入力データから潜在変数zに変換するニューラルネットワークをEncoder逆に潜在変数zをインプットとして元画像を復元するニューラルネットワークをDecoderです。 メリット 次元削減が行えることと、zの次元が入力データより小さい場合、次元削減とみなすことができることです。 VAE 通常のオートエンコーダーの場合、何かしら潜在変数zにデータを押し込めているものの、その構造がどのような状態かわかりません。VAEはこの潜在変数zに確率分布z∼N(0,1)を仮定したものです。VAEは、データを潜在変数zの確率分布という構造に押し込めることを可能にします。 確認テスト 確認テスト1 下記の選択肢から、seq2seqについて説明しているものを選べ。 (1)時刻に関して順方向と逆方向のRNNを構成し、それら2つの中間層表現を特徴量として利用するものである。 (2)RNNを用いたEncoder-Decoderモデルの一種であり、機械翻訳などのモデルに使われる。 (3)構文木などの木構造に対して、隣接単語から表現ベクトル(フレーズ)を作るという演算を再帰的に行い(重みは共通)、文全体の表現ベクトルを得るニューラルネットワークである。 (4)RNNの一種であり、単純なRNNにおいて問題となる勾配消失問題をCECとゲートの概念を導入することで解決したものである。 -> (2) 確認テスト2 seq2seqとHRED、HREDとVHREDの違いを簡潔に述べよ。 -> seq2seqは、一文の一問一答に対して処理できるモデルです。 HREDは一問一答ではなく、今までの会話の文脈から答えを導き出せるようにしたモデルです。HREDは会話の流れのような多様性はなく、情報量の乏しいな短い応答しかできないという課題がありました。VHREDはこの課題を、VAEの潜在変数の概念を追加することにより多様性を改善させたモデルです。 確認テスト3 VAEに関する下記の説明文中の空欄に当てはまる言葉を答えよ。 自己符号化器の潜在変数に____を導入したもの。 -> 確率分布z∼N(0,1) Word2Vec RNNでは、単語のような可変長の文字列をNNに与えることはできない。固定長形式で単語を表す必要があります。そこでWord2Vecでは、OneHotベクトルで重み1行分を単語1つと対応させたニューラルネットワークを採用しています。 メリット-> 大規模データの分散表現の学習が、現実的な計算速度とメモリ量で実現可能にしました。 Attention Mechanism 前述の通りSeq2Seqでは、長い文章への対応に問題がありました。これを解決するために、入力と出力のどの単語が関連しているかを学ぶAttention Mechanismという仕組みを導入しました。 確認テスト 確認テスト1 RNNとWord2vec、Seq2SeqとSeq2Seq+Attentionの違いを簡潔に述べよ。 RNNは時系列データを処理するのに適したネットワークです。 Word2Vecは単語の分散表現ベクトルを得る手法のことです。 Seq2Seqは一つの時系列データから別の時系列データを得るネットワークです。 Attentionは時系列データの中身のそれぞれの関係性に重みをつける手法のことです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Django REST framework で一括登録のエンドポイントを作る(bulk_create)

Django REST frameworkのレコードの一括作成 Django REST framework(以下DRF)のクラスベースViewには標準でレコードを一括で登録する機能は含まれていない。 複数のデータを登録したいとき以下の3つの方法が考えられる。 繰り返しPOSTを送る リストで送ってORMで繰り返し登録するエンドポイントを作る リストで送って一括で登録するエンドポイントを作る 1.繰り返しPOSTを送る この方法は通信回数が多くなってしまう。 2.リストで送ってORMで繰り返し登録するエンドポイントを作る 当初この方法で実現しようとしたが、同時登録数が多いと時間がかかり、フロントへのレスポンスが遅くなってしまった。 さらに、本番環境ではRDBへの繰り返し処理は料金に直結してしまう。 3.リストで送って一括で登録するエンドポイントを作る 調べていくと一括登録するbulk_create()というメソッドがあることを知った。 https://docs.djangoproject.com/en/4.0/ref/models/querysets/#bulk-create しかし、DRFで使う方法があまりまとめられていなくて、結構ハマってしまったので、使い方を共有します! 前提ファイル モデル 今回は例として本モデルで作っていく。 models.py class Book(models.Model): id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, unique=True) title = models.CharField(verbose_name='題名') author = models.CharField(verbose_name='著者') price = models.IntegerField(verbose_name='価格') シリアライザ serializers.py class BookSerializer(serializers.ModelSerializer): class Meta: model = Book fields = __all__ ビュー views.py class BookViewSet(viewsets.ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializer URL urls.py router.register('book', BookViewSet) この状態から本レコードを一括登録するエンドポイントを作っていきます! リストシリアライザの追加 シリアライザの作成で継承するクラスは rest_framework.serializers.SerializerとListSerializer の2種類がある。 単一のリソースを扱う場合は前者を、 複数のリソースを扱う場合は後者を継承する。 今回は、複数のデータを登録したいので、ListSerializerを継承するクラスを作る。 serializers.py class CreateBookListSerializer(serializers.ListSerializer): def create(self, validated_data): result = [Book(**attrs) for attrs in validated_data] Book.objects.bulk_create(result) return result 少し意外なのは、このシリアライザの時点でbulk_updateを使うということだ。 これを元々のシリアライザで使えるようにするために、1行追記する。 serializers.py class BookSerializer(serializers.ModelSerializer): class Meta: model = Book fields = __all__ list_serializer_class = CreateBookListSerializer # 追記 ビューの追加 上で作成したシリアライザを使って、複数登録を行うエンドポイントを作成していく。 今回は、createだけできればいいので、汎用APIViewであるCreateAPIViewを継承して作成する。 views.py class BulkCreateBookView(generics.CreateAPIView): serializer_class = BookSerializer def get_serializer(self, *args, **kwargs): if isinstance(kwargs.get("data", {}), list): kwargs["many"] = True return super(BulkCreateBookView, self).get_serializer(*args, **kwargs) 単一登録も複数登録も同じシリアライザ(BookSerializer)を通すので、リクエストのbodyが単一オブジェクトなら従来のもの、リスト形式であればリストシリアライザを使えるように、get_serializerをオーバーライドしている。 URLの登録 もうここまで来ればできたも同然。 複数登録するためのURLを作成したら終わりです! 好きな名前で登録しましょう。 urls.py router.register('book', BookViewSet) # 追記 urlpatterns = [ path('book/bulk', BulkCreateBookView.as_view(), name='books'), ] viewsetを継承したときとgenericsを継承したときで書き方が違うので注意! これで完成です? 使えるか確認 DRFには標準でGUIからAPIを試せるコンソールがあるが、そこから登録してしまうと TypeError at /api/book/bulk 'CreateBookListSerializer' object is not iterable とエラーが出てしまう。 これはこのコンソールがレスポンスとして複数のオブジェクトを想定していないことが原因だと考えられる。 APIに問題があるわけではないので、POSTMAN等を使って確認してみるとうまくいくことがわかる。 テストは他のものと同様に書けるので、書きましょう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GiNZA sub_phrases で文節の依存関係を解析

環境 Google Colaboratory Python 3.7.12 ginza-5.1.0 ginza.sub_phrases ginza.sub_phrasesを使うと、文節の依存関係を解析できます。 !pip install ginza ja_ginza from collections import defaultdict import spacy import ginza nlp = spacy.load("ja_ginza") def analyze(text: str, phrase_func): ret = defaultdict(list) doc = nlp(text) for sentence in doc.sents: for token in ginza.bunsetu_head_tokens(sentence): for _relation, sub_phrase in ginza.sub_phrases(token, phrase_func): ret[sub_phrase].append(phrase_func(token)) return dict(ret) analyze("焼肉を食べない日はない", ginza.phrase) {'日': ['ない'], '焼肉': ['食べ'], '食べ': ['日']} 出力するフレーズ単位の制御 ginza.sub_phrases()の第2引数で、出力の形式を変更できます。公式ドキュメントには下記のように記載されています。 トークンが属する文節を係り先とする文節を依存関係ラベルと共に返します。phrase_funcにはginza.bunsetuまたはginza.phraseを指定します。: ginza.phraseを使った場合 analyze("焼肉を食べない日はない", ginza.phrase) {'日': ['ない'], '焼肉': ['食べ'], '食べ': ['日']} ginza.bunsetuを使った場合 analyze("焼肉を食べない日はない", ginza.bunsetu) {'日+は': ['ない'], '焼肉+を': ['食べ+ない'], '食べ+ない': ['日+は']} ところで、このsub_phrasesのソースコードは下記のようになっています。 @sub_phrases.register(Token) def _sub_phrases( token: Token, phrase_func: Callable[[Token], U] = _phrase, condition_func: Callable[[Token], bool] = lambda token: True, ) -> Iterable[Tuple[str, U]]: return [ ( t.dep_, phrase_func(t), ) for t in bunsetu_span(token).root.children if t.i in bunsetu_head_list(token.doc) and condition_func(t) ] 実際の第2引数はphrase_func: Callable[[Token], U]であり、Tokenを引数に取るような関数となっています。 例えばginza.lemma_を渡すことで、原形を返り値として得ることができます。 analyze("焼肉を食べない日はない", ginza.lemma_) {'日': ['ない'], '焼肉': ['食べる'], '食べる': ['日']} 恒等写像lambda x: xを渡すことで、Tokenオブジェクトをそのまま受け取ることもできます。 analyze("焼肉を食べない日はない", lambda x: x) {焼肉: [食べ], 食べ: [日], 日: [ない]} 適当な独自定義関数を渡して動かすこともできます。 def genkei(x: ginza.Token): return str(ginza.bunsetu(ginza.lemma_)(x)) analyze("焼肉を食べない日はない", genkei) {'日+は': ['ない'], '焼肉+を': ['食べる+ない'], '食べる+ない': ['日+は']} 参考 GiNZA version 4.0: 多言語依存構造解析技術への文節APIの統合 - Megagon Labs | リクルート AI研究機関 GiNZA - Japanese NLP Library | Universal Dependenciesに基づくオープンソース日本語NLPライブラリ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

三角関数を使って円を描く

やったこと 学習用のプログラミングの問題をやっていて、三角関数がわからなくてプログラムが設計できなかったのが悔しかったので、数学を学びなおしています。 基本的な内容かもしれませんが、sinとcosを使えばx座標とy座標がわかるらしいので、円を描画するコードを作成しました。 # coding: utf-8 import matplotlib.pyplot as plt import numpy as np angle = np.arange(0,360) #0~360の角度のリストを作成 x = np.cos(np.radians(angle)) #x座標のリスト y = np.sin(np.radians(angle)) #y座標のリスト plt.plot(x,y) plt.axis('equal') #x軸とy軸のスケールを揃える plt.show() 出力結果
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンピュータとオセロ対戦37 ~新AIの準備~

今回の目標 今後予定しているAI作成のため、まずは4層の深層学習モデルを完成させる。 ここから本編 まず、「今後予定しているAI」について説明します。「34 ~性能評価~」にて、nleast、つまり相手の手数を減らしていく考え方が非常に有用であることが示されました。 また「32 ~深層学習で勝敗予測、実験2~」の結果から、深層学習モデルはRidge回帰と比べ高い精度が期待できることがわかりました。記事として書いてはいませんが、Ridge回帰モデルは数回の予測結果の平均を用いるため思考に時間がかかりますが、深層学習ならあまり時間がかかりませんでした。 以上のことから、深層学習による最終結果の予測値(ゲーム終了時点での自分の石数-相手の石数)と、次のターンなどでの相手の手数を総合的に考えれば強いAIが作れるのではないかと考えました。 具体的には、予測値と手数に対し適した重みをかけ、その合計値を「予想される勝利確率」とし、それが最も高くなる位置に石を置くというAIを作成したいと思います。 そのためにまず、ミスを見つけたまま修正していなかった4層の深層学習モデルについてミスを修正し、よく学習できるハイパーパラメータを探します。 ファイル構造 ファイル構造についてはこれまでとあまり変わりません。 具体的には以下の通りです(アルファベット順に並んでいないのはわざとです)。 . ├── fig │   └── 実験結果のグラフを格納 ├── BitBoard.py    ・・・オセロ実行のためのクラス ├── osero_learn.py   ・・・データ集めのためのクラス ├── Net.py       ・・・ニューラルネットワーク ├── deep_learn.py   ・・・データ集めや学習を行うクラス ├── run.ipynb     ・・・実行プログラム └── analyse.ipynb   ・・・csvファイル分析プログラム クラスの関係を図で表すと以下のようになっています。なお、クラス名とファイル名は一致しません。 BitBoard.py オセロをプレイするためのクラスを格納しています。 変更はありません。 osero_learn.py データ集めのためのクラスを格納しています。 これまでは、自分の石の配置及び相手の石配置が説明変数で、最終的な黒の石数-白の石数が目的変数でした。ですが、学習のためには黒のターンなら「最終的な黒の石数-白の石数」、白のターンなら「最終的な白の石数-黒の石数」とすべきでした。 そのため、試合を行うplayメソッド及びデータを追加するdata_setメソッドを変更し、それぞれのターンでその時のプレイヤー目線での最終結果が得られるようにしました。また、何も置かれていない場所はそれが明記されるようにしました。 def play(self) -> list: can, old_can = True, True turn_num = 0 data = [] turn_arr = [] can = self.check_all() while can or old_can: if can: turn_num += 1 if self.turn: self.think[self.black_method]() else: self.think[self.white_method]() if turn_num in self.check_point: self.data_set(data, turn_num) turn_arr.append([self.turn]) self.turn = not self.turn old_can = can can = self.check_all() self.count_last() turn_arr = np.array(turn_arr) last_score = self.score * (-1) ** turn_arr return data, last_score.tolist() def data_set(self, data: list, turn_num: int) -> None: data.append([]) if self.turn: my = ["b_u", "b_d"] opp = ["w_u", "w_d"] else: my = ["w_u", "w_d"] opp = ["b_u", "b_d"] for i in range(32): if self.bw[my[0]] & 1 << i: data[-1].append(1) data[-1].append(0) data[-1].append(0) elif self.bw[opp[0]] & 1 << i: data[-1].append(0) data[-1].append(1) data[-1].append(0) else: data[-1].append(0) data[-1].append(0) data[-1].append(1) for i in range(32): if self.bw[my[1]] & 1 << i: data[-1].append(1) data[-1].append(0) data[-1].append(0) elif self.bw[opp[1]] & 1 << i: data[-1].append(0) data[-1].append(1) data[-1].append(0) else: data[-1].append(0) data[-1].append(0) data[-1].append(1) deep_learn.py deep_learnについても、大幅に変更した箇所のみ記載します。 いままで、各条件ごとにグラフを作成していましたが、グラフ数が非常に多くなり手間が増えてしまうため一つにまとめるようにplotメソッドを変更しました。 def plot(self, row: int) -> None: self.ax[row-1][0].plot(self.results_train["MSE"], label="train") self.ax[row-1][0].plot(self.results_valid["MSE"], label="valid") self.ax[row-1][0].legend() self.ax[row-1][0].set_xlabel(self.xlabel1) self.ax[row-1][0].set_ylabel(self.ylabel1) self.ax[row-1][0].set_title(self.fig_name1) self.ax[row-1][1].plot(self.results_train["MAE"], label="train") self.ax[row-1][1].plot(self.results_valid["MAE"], label="valid") self.ax[row-1][1].legend() self.ax[row-1][1].set_xlabel(self.xlabel2) self.ax[row-1][1].set_ylabel(self.ylabel2) self.ax[row-1][1].set_title(self.fig_name2) run.ipynb 準備 import matplotlib.pyplot as plt from deep_learn import * 実験 以下、ハイパーパラメータなどを変えながら実験を行います。 データ量 データ量を変更しながら学習をするのは今までの実験と同じですが、条件ごとのグラフを別々に出力せずにまとめました。 run = deep_learn() num_arr = [1, 5, 10] run.fig, run.ax = plt.subplots(ncols=2, nrows=len(num_arr), figsize=(10, len(num_arr)*5)) plt.subplots_adjust(wspace=None, hspace=0.2) i = 1 MSE_test = [] MAE_test = [] for num in num_arr: print("\r%d/%d" % (i, len(num_arr)), end="") run.num = num run.fig_name1 = "MSE of each epoch (data quantity is %d)" % num run.fig_name2 = "MAE of each epoch (data quantity is %d)" % num run.set_data() run.fit() run.plot(i) test_error = run.cal_test_error() MSE_test.append(test_error[0]) MAE_test.append(test_error[1]) i += 1 run.fig.savefig("fig/data_quantity.png") run.fig.clf() 実行結果はこちら。 number: [1, 5, 10] MSE: [variable(152.81169), variable(372.01184), variable(643.15546)] MAE: [variable(8.03122), variable(15.009193), variable(20.94951)] これまでと同様、データ量が多すぎるとうまく学習しませんでした。 バッチサイズ プログラムはデータ量の時のものとほぼ同じですので省略します。 batch size: [100, 200, 300, 500, 700, 1000, 1500] MSE: [variable(162.9739), variable(152.05502), variable(153.27628), variable(162.2756), variable(145.58446), variable(204.26527), variable(167.85017)] MAE: [variable(8.527661), variable(8.186351), variable(8.13009), variable(8.399555), variable(7.9057674), variable(9.92684), variable(8.66958)] こちらもこれまでと同様、変更しても結果はあまり変わりませんでした。 バッチサイズは大きいほど学習が早く進みますが、大きすぎても怖いので今回は1000を採用することにします。 活性化関数の種類 f_name = [ "clipped_relu", "elu", "leaky_relu", # "log_softmax", "rrelu", "relu", "sigmoid", "softmax", # "softplus", "tanh" ] 上に示す活性化関数について調べました。今回は四層しかないため勾配消失の心配はないと判断し、sigmoid関数なども入れています。コメントアウトしている関数は、候補ではあったものの学習途中でオーバーフローしたという理由で除外したものです。 結果はこちら。 function: ['clipped_relu', 'elu', 'leaky_relu', 'rrelu', 'relu', 'sigmoid', 'softmax', 'tanh'] MSE: [variable(234.85422), variable(435.0493), variable(1528.1886), variable(1051.0135), variable(451.09824), variable(294.6485), variable(571.89453), variable(171.02132)] MAE: [variable(10.507885), variable(16.28336), variable(33.503345), variable(28.046625), variable(15.27029), variable(13.055871), variable(20.325909), variable(9.534906)] やはりrelu系列は全体的にうまく学習しませんでした。優秀だったのはsigmoidとtanhでした。 負の数が存在しない関数が学習しにくいのは今までの実験でも示されていましたが、softmaxは極端な結果になりました。負の数がないだけでなく上限の値が決められていることが大きな枷になったのだと考えられます。 最適化関数 opt_name = [ "SGD", "MomentumSGD", "AdaGrad", "RmSprop", "AdaDelta", "Adam", "RMSpropGraves", "SMORMS3", "AMSGrad", "AdaBound", "AMSBound" ] 上に示す最適化関数について調べました。 opt name: ['SGD', 'MomentumSGD', 'AdaGrad', 'RmSprop', 'AdaDelta', 'Adam', 'RMSpropGraves', 'SMORMS3', 'AMSGrad', 'AdaBound', 'AMSBound'] MSE: [variable(220.08337), variable(576.7117), variable(251.20909), variable(569.64264), variable(184.31363), variable(162.6182), variable(156.20638), variable(161.93105), variable(169.1945), variable(150.75018), variable(139.02298)] MAE: [variable(10.743682), variable(20.432753), variable(12.183864), variable(20.282295), variable(9.238301), variable(9.405695), variable(8.508823), variable(9.181299), variable(8.943705), variable(8.068955), variable(7.9657555)] 思っていたより多くの最適化関数で収束が見られました。 収束が見られたのはSGD、AdaGrad、AdaDelta、Adam、RMSpropGraves、SMORMS3、AMSGrad、AdaBound、AMSBoundで、見られなかったのはMomentumSGDとRmSpropのみでした。 SGDで収束し、MomentumSGDで収束しなかったということは、振動抑制の必要がなかった、むしろ悪手であったのではないかと最初は考えました。しかしSGDの最終的な誤差は、AdaGradを除く収束した最適化関数のすべてに劣っています。つまりSGDが今回たどり着いたのは局所解であり、MomentumSGDは局所解から抜け出そうとしたものの一歩足りなかったのではないかと考えます。 こういった理由から、MomentumSGD及びSGDはこの先除外したいと思います。 収束はしたものの精度の高くないAdaGrad、収束しなかったRMSpropから、パラメータごとの勾配にはあまり差がないのではないかと考えました。なおこれまでの実験でも、この二つはいい結果を残せていません(31 ~深層学習で勝敗予測、実験~、36 ~10層のハイパーパラメータ~)。 よって、残りの七つの最適化関数を後ほど再実験したいと思います。 bound 現在、学習の安定のためGradientHardClippingを用いていますが、その引数であるbound変数を変更し実験してみます。 bound : [1, 2, 3, 4, 5, 10] MSE: [variable(208.39401), variable(195.04422), variable(211.6011), variable(189.95116), variable(244.27588), variable(212.02213)] MAE: [variable(10.187112), variable(9.844666), variable(10.383668), variable(10.060881), variable(11.47002), variable(10.53158)] どれも大して変わらない結果でした。 なんでもよさそうです。 更新回数 更新回数を極端に増やして実験してみました。 n_epoch: [100, 1000] MSE: [variable(152.18834), variable(144.9502)] MAE: [variable(7.6830935), variable(7.124355)] 10層の時のような上昇はなく、4層の時のように一度誤差が下がりきるとそこからの更新はなくなりました。 150回程度更新させれば十分でしょう。 調査ターン データ集めをする際、何ターンごとにデータを集めるかを変更して学習させてみました。 ['every 1', 'every 5', 'every 10'] MSE: [variable(194.50024), variable(512.78925), variable(447.7704)] MAE: [variable(9.789661), variable(17.579159), variable(16.539795)] これまでの実験と同様、データ量が少ないほど過学習しやすい結果になりました。 hook function ['WeightDecay', 'Lasso', 'GradientHardClipping', 'GradientNoise', 'GradientLARS'] MSE: [variable(162.75676), variable(200.36641), variable(209.13129), variable(156.30049), variable(235.34283)] MAE: [variable(9.0745125), variable(10.085787), variable(9.9866905), variable(8.721768), variable(11.526583)] どれも収束していました。また、全て似たような結果となっておりどれが適しているかをここで判断することは難しそうです。 組み合わせ実験 上述した実験はパラメータの種類ごとで行いましたが、ここでは条件を絞ったうえで総当たりで調べます。 具体的な条件は以下の通りです。 データ量 1 バッチサイズ 1000 活性化関数 sigmoid、tanh 最適化関数 AdaDelta、Adam、RMSpropGraves、SMORMS3、AMSGrad、AdaBound、AMSBound 更新回数 40回 調査ターン every 1 hook function WeightDecay、Lasso、GradientHardClipping、GradientNoise、GradientLARS なおboundなど、追加関数のパラメータについては一般的なものを使います。 更新回数が40回なのは、実験時間を短くするためです。 プログラムはこちら。 import chainer.functions as F import chainer.optimizers as opt import chainer.optimizer_hooks as hf import pandas as pd import Net import deep_learn ################################ num = 1 batch_size = 1000 f_arr = [ F.sigmoid, F.tanh ] f_name = [ "sigmoid", "tanh" ] opt_arr = [ opt.AdaDelta(), opt.Adam(), opt.RMSpropGraves(), opt.SMORMS3(), opt.AMSGrad(), opt.AdaBound(), opt.AMSBound() ] opt_name = [ "AdaDelta", "Adam", "RMSpropGraves", "SMORMS3", "AMSGrad", "AdaBound", "AMSBound" ] hf_arr = [ hf.WeightDecay(0.00001), hf.Lasso(0.00001), hf.GradientHardClipping(-2, 2), hf.GradientNoise(0.3), hf.GradientLARS() ] hf_name = [ "WeightDecay", "Lasso", "GradientHardClipping", "GradientNoise", "GradientLARS" ] ################################ data = {} data["function"] = [] data["optimizer"] = [] data["hook_function"] = [] data["test_MSE"] = [] data["test_MAE"] = [] ################################ run = deep_learn.deep_learn() run.num = num run.batch_size = batch_size run.set_data() i, j, k = 0, 0, 0 for function in f_arr: Net.func = function run.Net = Net.Net j = 0 for optimizer in opt_arr: run.optimizer = optimizer k = 0 for hook_function in hf_arr: loading = "%d/%d" % (i+1, len(f_arr))\ + "#" * (j + 1)\ + "." * (k + 1) print("\r" + loading, end="") run.hook_f = hook_function run.fit() test_error = run.cal_test_error() data["function"].append(f_name[i]) data["optimizer"].append(opt_name[j]) data["hook_function"].append(hf_name[k]) data["test_MSE"].append(float(test_error[0].array)) data["test_MAE"].append(float(test_error[1].array)) k += 1 j += 1 i += 1 ################################ data_df = pd.DataFrame(data) data_df.to_csv("data.csv") 結果を確認します。 import pandas as pd df = pd.read_csv("data.csv") ################################ df_sorted = df.sort_values(by="test_MAE", inplace=False) df_sorted.head(20) ひどい結果でした。 理由として考えられるのは、バッチサイズが大きすぎたことでしょうか。 バッチサイズをもともとの設定である200、今回の1000、ついでに間をとって600の三種類にして再実験しようと思います。 また、ついでに望みの薄そうなパラメータについても実行時間短縮のためこの時点で除外しておこうと思います。まず、活性化関数について、明らかにtanhの方がよい結果となっているのでこれを使います。 次に、誤差が小さくなりやすいまたは大きくなりやすい最適化関数を調べてみます。 optimizers = df["optimizer"].unique() df_top = df.sort_values(by="test_MSE", inplace=False).head(25) df_worst = df.sort_values(by="test_MSE", inplace=False, ascending=False).head(25) print("top") for opt in optimizers: num = 0 num += len(df_top.query("optimizer=='%s'" % opt)) print("%s:\t%d" % (opt, num)) print("\nworst") for opt in optimizers: num = 0 num += len(df_worst.query("optimizer=='%s'" % opt)) print("%s:\t%d" % (opt, num)) top AdaDelta: 3 Adam: 4 RMSpropGraves: 3 SMORMS3: 2 AMSGrad: 4 AdaBound: 4 AMSBound: 5 worst AdaDelta: 1 Adam: 5 RMSpropGraves: 4 SMORMS3: 3 AMSGrad: 5 AdaBound: 2 AMSBound: 5 AdaDelataは非常に有用そうですが、ほかのパラメータは特に偏りがありませんでした。 同様に追加関数についても調べました。 top WeightDecay: 6 Lasso: 6 GradientHardClipping: 5 GradientNoise: 4 GradientLARS: 4 worst WeightDecay: 5 Lasso: 5 GradientHardClipping: 4 GradientNoise: 7 GradientLARS: 4 GradientNoiseは誤差が大きくなりやすい結果となりましたので、これのみ除外しようと思います。他は特に偏りありませんでした。 組み合わせ実験2 今度は以下のパラメータで実験します。 データ量 1 バッチサイズ 200、600、1000 活性化関数 tanh 最適化関数 AdaDelta、Adam、RMSpropGraves、SMORMS3、AMSGrad、AdaBound、AMSBound 更新回数 40回 調査ターン every 1 hook function WeightDecay、Lasso、GradientHardClipping、GradientLARS num = 1 batch_arr = [200, 600, 1000] func = F.tanh opt_arr = [ opt.AdaDelta(), opt.Adam(), opt.RMSpropGraves(), opt.SMORMS3(), opt.AMSGrad(), opt.AdaBound(), opt.AMSBound() ] opt_name = [ "AdaDelta", "Adam", "RMSpropGraves", "SMORMS3", "AMSGrad", "AdaBound", "AMSBound" ] hf_arr = [ hf.WeightDecay(0.00001), hf.Lasso(0.00001), hf.GradientHardClipping(-2, 2), hf.GradientLARS() ] hf_name = [ "WeightDecay", "Lasso", "GradientHardClipping", "GradientLARS" ] 一度目の実験と同様、誤差の小さくなりやすいまたは大きくなりやすいパラメータを探しました。 結果はこちら。 batch_size: top 200: 11 600: 7 1000: 7 worst 200: 7 600: 8 1000: 10 optimizer: top AdaDelta: 6 Adam: 1 RMSpropGraves: 0 SMORMS3: 0 AMSGrad: 1 AdaBound: 8 AMSBound: 9 worst AdaDelta: 4 Adam: 8 RMSpropGraves: 2 SMORMS3: 3 AMSGrad: 6 AdaBound: 1 AMSBound: 1 hook_function: top WeightDecay: 8 Lasso: 9 GradientHardClipping: 7 GradientLARS: 1 worst WeightDecay: 5 Lasso: 5 GradientHardClipping: 5 GradientLARS: 10 結果をまとめると、以下のことが言えそうです。 バッチサイズは、この中では200が最も適しており1000は適さない。ただし、思っていたほどの差はなかった。 最適化関数は、AdaBoundまたはAMSBoundが最も適する。Adam及びAMSGradは適さない。 追加関数は、WeightDecayまたはLassoが最も適する。GradientLARSは適さない。 一回目の実験と比べ数字にばらつきが出たのは、バッチサイズを変更したからだと思われます。バッチサイズを大きくしすぎると、ほかのパラメータを変えても等しく学習しにくくなるのかもしれません。 モデル作成 バッチサイズ、最適化関数、追加関数、そして各関数に与えるパラメータについてはまだ完全に検証できていませんが、キリがないこと、これは本題ではないという理由で、ここでいったん終了したいと思います。 ここまでで求めたパラメータを使い学習済みモデルを作成します。 具体的には、 データ量 1 バッチサイズ 200 活性化関数 tanh 最適化関数 AMSBound 更新回数 150回 調査ターン every 1 hook function Lasso です。 まず、これらの条件で学習を行います。 import chainer.functions as F from chainer.optimizers import AMSBound from chainer.optimizer_hooks import Lasso import matplotlib.pyplot as plt from chainer.serializers import save_npz import deep_learn import Net ######################################## run = deep_learn.deep_learn() run.num = 1 run.batch_size = 200 Net.func = F.tanh run.Net = Net.Net run.optimizer = AMSBound() run.n_epoch = 150 run.hook_f = Lasso(0.00001) ######################################## run.set_data() run.fit() 次に、きちんと学習できているか確認します。 まずはテスト用データでの精度を見てみます。 error = run.cal_test_error() error (variable(140.72816), variable(7.2066855)) 控えめに言って最高ですね、Ridgeでの平均絶対誤差の最高記録が全ターン平均して約13ですので、非常に良い結果です。 次に学習の様子を見てみます。 fig = plt.figure(figsize=(10, 10)) plt.plot(run.results_train["MSE"], label="train") plt.plot(run.results_valid["MSE"], label="valid") plt.legend() plt.xlabel("epoch number") plt.ylabel("mean squared error") plt.title("MSE of each epoch") plt.savefig("fig/model_MSE") plt.clf() plt.close() 記載はしませんが、絶対平均誤差についてもほぼ同じプログラムを使用しエポック数毎の誤差をグラフ化しました。 結果はこちら。 とてもいい結果になりました。 最後にモデルを保存します。 save_npz("model.net", run.net) フルバージョン 参考文献 深層学習の最適化アルゴリズム 次回は 今回作成したモデルを用いて、冒頭で構想を記したAIを作成したいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GMRES法のPythonの実装

Abstract GMRES法を実装したPythonのコードを示します。 GMRES法のアルゴリズムの説明はKrylov部分空間とGMRES法について に譲ります。 非線形モデル予測制御で登場する形の方程式がGMRES法でうまく解けることを示します。 Introduction 突然ですが非線形モデル予測制御(NMPC)において、とある手法(制御入力の追跡, 参考文献[1]参照)では解くべき方程式として以下のような形が出現します(厳密にはもう少し複雑ですが)。 \frac{\partial F}{\partial x} (x) \dot{x} = - F(x) ただし$x \in \mathbb{R}^n$及び$F: \mathbb{R}^n \mapsto \mathbb{R}^n $は既知で、$\dot{x} \in \mathbb{R}^n $が知りたいとい方程式です。 なお$\frac{\partial F}{\partial x}$は以下で定義されるヤコビ行列です。 \frac{\partial F}{\partial x} := \begin{bmatrix} \frac{\partial F_1}{\partial x_1} & \dots & \frac{\partial F_1}{\partial x_n} \\ \vdots & \ddots & \vdots \\ \frac{\partial F_n}{\partial x_1} & \dots & \frac{\partial F_n}{\partial x_n} \end{bmatrix} ここで、$x = \begin{bmatrix} x_1 \ x_2 \ \cdots \ x_n \end{bmatrix}$, $F(x) = \begin{bmatrix} F_1(x) \ F_2(x) \ \cdots \ F_n(x) \end{bmatrix}$, $F_i(x): \mathbb{R}^n \mapsto \mathbb{R} \ \ \mathrm{for} \ \ i = 1, \cdots, n$ としました。 この方程式は$F$が簡単なものであれば、事前に計算して普通に解くことができます。 右辺は既知なので単なる線型方程式ですから、逆行列とかで何とでもなります。 しかし次元$n$が大きくなったり、また$F$が複雑な場合はヤコビ行列を計算したくありません。 つまり線型方程式$Ax = b$において$A$を計算せずに解を求める手法が欲しいというわけです。は? 実はこれ、この記事で紹介するGMRES法はある条件を満たせば可能です。以下ではその手法を見ていきましょう。 前提知識 大学1年生レベルの線形代数学・微分積分学の知識を仮定します。 Pythonの基本文法は習得しているものとします。 GMRES法 GMRES法は一般化最小残差法のことで、Generaized Miminum RESidualの略です(明日から使えない豆知識)。 なお、[2] Krylov部分空間とGMRES法について に大体説明は載っていますので、こちらの記事が残っている限りはアルゴリズムの説明は省略します。 まずは線型方程式 Ax = b の解を求めることを考えます。ここでは$A \in \mathbb{R}^{n \times n}, b \in \mathbb{R}^n $は既知で$x \in \mathbb{R}^n$を求める問題を解きます。 実装(単純な線型方程式) from typing import List, Tuple, Callable import numpy as np from numpy import linalg as la def arnoldi(A: np.ndarray, v1: np.ndarray, m: int) -> Tuple[np.ndarray, np.ndarray]: vs: List[np.ndarray] = [v1] H = np.zeros((m+1, m)) for j in range(m): v_j = vs[j] Av = A.dot(v_j) for i in range(j+1): h_ij = Av.T.dot(vs[i]) H[i, j] = h_ij # Av - \sum_{i = 1}^j h_{ij} * v_{i} v_next = Av - np.concatenate([H[i, j] * vs[i] for i in range(j+1)], axis=1).sum(axis=1, keepdims=True) h_j1j = np.linalg.norm(v_next) H[j+1, j] = h_j1j vs.append(v_next / h_j1j) return np.concatenate(vs[:-1], axis=1), H def solve(A: np.ndarray, x0: np.ndarray, b: np.ndarray, m: int) -> Generator[np.ndarray, None, None]: # GMRES法でm回イテレーションした解を順番に返す r0 = b - A.dot(x0) v1 = r0 / la.norm(r0) for i in range(1, m+1): Vm, Hm = arnoldi(A, v1, i) e1 = np.zeros((i+1, 1)) e1[0, 0] = la.norm(r0) # 正規方程式により、最小問題argmin | e1 - Hm y |を解く。 ym = la.inv(Hm.T.dot(Hm)).dot(Hm.T).dot(e1) yield x0 + Vm.dot(ym) 当たり前ですが、多くの説明は1がindexの始まりになっているので、0がindexの始まりになっているPythonでは注意が必要です。 ポイントは計算で必要なのは行列$A$そのものではなく、任意の$v_j \in \mathbb{R}^n$に対して行列積$A v_j$さえ求まればよいという点です。これを使うことでヤコビ行列を求めずに方程式を解くという荒業が可能になっています。実装などは次のセクションで。 さて、上記の関数を用いて、以下の適当な方程式を解いてみます。 \begin{bmatrix} 1 & 0 & 1 \\ 0 & 1 & 1 \\ 1 & 1 & 1 \end{bmatrix} x = \begin{bmatrix} 1 \\ 2 \\ 3 \end{bmatrix} 解析解は x = \begin{bmatrix} 1 \\ 2 \\ 0 \end{bmatrix} ですが果たして・・・? jikken.py A = np.array([ [1, 0, 1], [0, 1, 1], [1, 1, 1] ]) b = np.array([1, 2, 3]).reshape((-1, 1)) x_true = np.linalg.inv(A).dot(b) print(f"x_true = {x_true}") x0 = np.zeros_like(b) m = 5 for i, xm in enumerate(gmres.solve(A, x0, b, m)): print(f"---- iter {i} ----") print(xm) 出力は以下です。最後の成分が怪しいですが、ほぼゼロなのでヨシ! x_true = [[1.] [2.] [0.]] ---- iter 0 ---- [[0.41558442] [0.83116883] [1.24675325]] ---- iter 1 ---- [[0.53846154] [0.84615385] [1.15384615]] ---- iter 2 ---- [[1.00000000e+00] [2.00000000e+00] [1.11022302e-15]] ---- iter 3 ---- [[1.00000000e+00] [2.00000000e+00] [1.11022302e-15]] ---- iter 4 ---- [[1.00000000e+00] [2.00000000e+00] [1.85622744e-15]] GMRES法 with ヤコビ行列 さて本題です。冒頭の形をした方程式 \frac{\partial F}{\partial x} (x) \dot{x} = - F(x) を解きます。今回は確認を兼ねて、簡単に求められる形で与えます。 F(x) = \begin{bmatrix} x_1 + x_2 \\ x_1 - x_2 \end{bmatrix} するとヤコビ行列は \frac{\partial F}{\partial x} (x) = \begin{bmatrix} 1 & 1 \\ 1 & -1 \end{bmatrix} となります。モデル予測制御でこの形の方程式を解く際は$x \in \mathbb{R}^n$は既知でよいので、以下ではとりあえず$x_1 = 2, x_2 = 1$, つまり$x = \begin{bmatrix}2\ \ 1 \end{bmatrix}^T$とします。この時方程式は \begin{bmatrix} 1 & 1 \\ 1 & -1 \end{bmatrix} \dot{x} = - \begin{bmatrix} 3 \\ 1 \end{bmatrix} と書けます。したがって解析解は \dot{x} = - \begin{bmatrix} 1 & 1 \\ 1 & -1 \end{bmatrix}^{-1} \begin{bmatrix} 3 \\ 1 \end{bmatrix} = \frac{1}{2} \begin{bmatrix} -1 & -1 \\ -1 & 1 \end{bmatrix} \begin{bmatrix} 3 \\ 1 \end{bmatrix} = \begin{bmatrix} -2 \\ -1 \end{bmatrix} と求まります。さて、前のセクションで述べた通り方程式$Ax = b$を解くのには、任意の$v \in \mathbb{R}^n$に対して$Av$が計算できれば良いということを述べました。上記方程式にそれを適用すると 任意の$v \in \mathbb{R}^n$に対して行列積$\frac{\partial F}{\partial x} (x) v$が計算できればよいです。ところでこの式、方向微分っぽくないですか? 方向微分 スカラー値関数$f: \mathbb{R}^n \mapsto \mathbb{R}$について、点$a \in \mathbb{R}^n$におけるある方向$v \in \mathbb{R}^n$に関する方向微分$\frac{\partial f}{\partial v}$は$h$を十分小さな実数として \frac{\partial f}{\partial v}(a) = \lim_{h \to 0} \frac{f(a + h v) - f(a)}{h} で定義されます(極限が存在すれば)。ここで点$a$において全微分可能ならば、任意の方向に関して方向微分可能で以下が成立します。 \frac{\partial f}{\partial v}(a) = \frac{\partial f}{\partial x}(a) v ただし$\frac{\partial f}{\partial x}$は勾配ベクトルで、以下のように定義されます。 \frac{\partial f}{\partial x}(x) = \begin{bmatrix} \frac{\partial f}{\partial x_1} & \cdots & \frac{\partial f}{\partial x_n} \end{bmatrix} 同様にして、ベクトル値関数$F: \mathbb{R}^n \mapsto \mathbb{R}^n$についても定義され、方向$v \in \mathbb{R}^n$に関する方向微分$\frac{\partial F}{\partial v}$は$h$を十分小さな実数として \frac{\partial F}{\partial v}(a) = \lim_{h \to 0} \frac{F(a + h v) - F(a)}{h} で定義されます。スカラー値同様に \frac{\partial F}{\partial v}(a) = \frac{\partial F}{\partial x}(a) v が成立します。 実装 したがって任意の$v \in \mathbb{R}^n$に対して$\frac{\partial F}{\partial x} (x) v$を以下で計算できます。 \frac{\partial F}{\partial x} (x) v = \frac{\partial F}{\partial v} (x) \simeq \frac{F(x + h v) - F(x)}{h} これにより、ヤコビ行列を求める作業を$F$を二回評価するだけにすることができます。 今回の問題では$F(x)$は F(x) = \begin{bmatrix} x_1 + x_2 \\ x_1 - x_2 \end{bmatrix} で定義されていました。$\frac{F(x + h v) - F(x)}{h}$を計算しておくと \frac{F(x + h v) - F(x)}{h} = \begin{bmatrix} v_1 + v_2 \\ v_1 - v_2 \end{bmatrix} となります。ヤコビ行列なしで本当にこれで求まるのでしょうか・・・?それではレッツ実装。 from typing import Callable, Generator, Tuple, List import numpy as np from numpy import linalg as la nptonp = Callable[[np.ndarray], np.ndarray] def arnoldi_with_no_mat(A_func: nptonp, v1: np.ndarray, m: int) -> Tuple[np.ndarray, np.ndarray]: vs: List[np.ndarray] = [v1] H = np.zeros((m+1, m)) for j in range(m): v_j = vs[j] Av = A_func(v_j) for i in range(j+1): h_ij = Av.T.dot(vs[i]) H[i, j] = h_ij # Av - \sum_{i = 1}^j h_{ij} * v_{i} v_next = Av - np.concatenate([H[i, j] * vs[i] for i in range(j+1)], axis=1).sum(axis=1, keepdims=True) h_j1j = np.linalg.norm(v_next) H[j+1, j] = h_j1j vs.append(v_next / h_j1j) return np.concatenate(vs[:-1], axis=1), H def solve_with_no_mat(A_func: nptonp, x0: np.ndarray, b: np.ndarray, m: int) -> Generator[np.ndarray, None, None]: r0 = b - A_func(x0) v1 = r0 / la.norm(r0) for i in range(1, m+1): Vm, Hm = arnoldi_with_no_mat(A_func, v1, i) e1 = np.zeros((i+1, 1)) e1[0, 0] = la.norm(r0) ym = la.inv(Hm.T.dot(Hm)).dot(Hm.T).dot(e1) yield x0 + Vm.dot(ym) 前のセクションと異なる点は、$Av$を計算する部分を関数で渡すようにしただけです。引数A_funcにはA(v)に相当する関数を入れます。今回は$\frac{F(x + h v) - F(x)}{h}$を計算する関数をいれます。 jikken2.py def F(x): x1 = x[0, 0] x2 = x[1, 0] return np.array([ [x1 + x2], [x1 - x2] ]) def F_grad(x): # x1 = x[0, 0] # x2 = x[1, 0] return np.array([ [1, 1], [1, -1] ]) x1, x2 = 2, 1 h = 1e-5 x = np.array([x1, x2]).reshape((-1, 1)) def A_func(v: np.ndarray): v1 = v[0, 0] v2 = v[1, 0] return np.array([ [v1 + v2], [v1 - v2] ]) A = F_grad(x) b = - F(x) # 解析解 x_dot_true = np.linalg.inv(A).dot(b) print(f"x_dot_true = {x_dot_true}") # 初期解は0でスタート。 x_dot0 = np.zeros_like(x) m = 5 for i, x_dotm in enumerate(eqsolver.solve_with_no_mat(A_func, x_dot0, b, m)): print(f"---- iter {i} ----") print(x_dotm) 結果は・・・ x_dot_true = [[-2.] [-1.]] ---- iter 0 ---- [[-2.1] [-0.7]] ---- iter 1 ---- [[-2.] [-1.]] ---- iter 2 ---- [[-2.] [-1.]] ---- iter 3 ---- [[-2.] [-1.]] ---- iter 4 ---- [[-2.] [-1.]] この通り、正しく解を推定できています。すごい・・・・・・・!!!!! 参考文献・補足 [1] 非線形最適制御入門, 大塚敏之, コロナ社, 第4版 GMRES法の存在を知った書籍。 [2] Krylov部分空間とGMRES法について 元のPDFと合わせてわかりやすい記事でした。この場を借りて感謝申し上げます。 元のPDF→大規模連立1次方程式に対する一般化最小残差法について [3] 正規方程式の導出と計算例 GMRES法の残差誤差を最小化する時に正規方程式を使って解いています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GMRES法のPythonの実装とNMPCでのトリック

Abstract GMRES法を実装したPythonのコードを示します。 GMRES法のアルゴリズムの説明はKrylov部分空間とGMRES法について に譲ります。 非線形モデル予測制御(NMPC)で登場する形の方程式がGMRES法でうまく解けることを示します。 Introduction 突然ですが非線形モデル予測制御(NMPC)において、とある手法(制御入力の追跡, 参考文献[1]参照)では解くべき方程式として以下のような形が出現します(厳密にはもう少し複雑ですが)。 \frac{\partial F}{\partial x} (x) \dot{x} = - F(x) ただし$x \in \mathbb{R}^n$及び$F: \mathbb{R}^n \mapsto \mathbb{R}^n $は既知で、$\dot{x} \in \mathbb{R}^n $が知りたいとい方程式です。 なお$\frac{\partial F}{\partial x}$は以下で定義されるヤコビ行列です。 \frac{\partial F}{\partial x} := \begin{bmatrix} \frac{\partial F_1}{\partial x_1} & \dots & \frac{\partial F_1}{\partial x_n} \\ \vdots & \ddots & \vdots \\ \frac{\partial F_n}{\partial x_1} & \dots & \frac{\partial F_n}{\partial x_n} \end{bmatrix} ここで、$x = \begin{bmatrix} x_1 \ x_2 \ \cdots \ x_n \end{bmatrix}$, $F(x) = \begin{bmatrix} F_1(x) \ F_2(x) \ \cdots \ F_n(x) \end{bmatrix}$, $F_i(x): \mathbb{R}^n \mapsto \mathbb{R} \ \ \mathrm{for} \ \ i = 1, \cdots, n$ としました。 この方程式は$F$が簡単なものであれば、事前に計算して普通に解くことができます。 右辺は既知なので単なる線型方程式ですから、逆行列とかで何とでもなります。 しかし次元$n$が大きくなったり、また$F$が複雑な場合はヤコビ行列を計算したくありません。 つまり線型方程式$Ax = b$において$A$を計算せずに解を求める手法が欲しいというわけです。は? 実はこれ、この記事で紹介するGMRES法はある条件を満たせば可能です。以下ではその手法を見ていきましょう。 前提知識 大学1年生レベルの線形代数学・微分積分学の知識を仮定します。 Pythonの基本文法は習得しているものとします。 GMRES法 GMRES法は一般化最小残差法のことで、Generaized Miminum RESidualの略です(明日から使えない豆知識)。 なお、[2] Krylov部分空間とGMRES法について に大体説明は載っていますので、こちらの記事が残っている限りはアルゴリズムの説明は省略します。 まずは線型方程式 Ax = b の解を求めることを考えます。ここでは$A \in \mathbb{R}^{n \times n}, b \in \mathbb{R}^n $は既知で$x \in \mathbb{R}^n$を求める問題を解きます。 実装(単純な線型方程式) from typing import List, Tuple, Callable import numpy as np from numpy import linalg as la def arnoldi(A: np.ndarray, v1: np.ndarray, m: int) -> Tuple[np.ndarray, np.ndarray]: vs: List[np.ndarray] = [v1] H = np.zeros((m+1, m)) for j in range(m): v_j = vs[j] Av = A.dot(v_j) for i in range(j+1): h_ij = Av.T.dot(vs[i]) H[i, j] = h_ij # Av - \sum_{i = 1}^j h_{ij} * v_{i} v_next = Av - np.concatenate([H[i, j] * vs[i] for i in range(j+1)], axis=1).sum(axis=1, keepdims=True) h_j1j = np.linalg.norm(v_next) H[j+1, j] = h_j1j vs.append(v_next / h_j1j) return np.concatenate(vs[:-1], axis=1), H def solve(A: np.ndarray, x0: np.ndarray, b: np.ndarray, m: int) -> Generator[np.ndarray, None, None]: # GMRES法でm回イテレーションした解を順番に返す r0 = b - A.dot(x0) v1 = r0 / la.norm(r0) for i in range(1, m+1): Vm, Hm = arnoldi(A, v1, i) e1 = np.zeros((i+1, 1)) e1[0, 0] = la.norm(r0) # 正規方程式により、最小問題argmin | e1 - Hm y |を解く。 ym = la.inv(Hm.T.dot(Hm)).dot(Hm.T).dot(e1) yield x0 + Vm.dot(ym) 当たり前ですが、多くの説明は1がindexの始まりになっているので、0がindexの始まりになっているPythonでは注意が必要です。 ポイントは計算で必要なのは行列$A$そのものではなく、任意の$v_j \in \mathbb{R}^n$に対して行列積$A v_j$さえ求まればよいという点です。これを使うことでヤコビ行列を求めずに方程式を解くという荒業が可能になっています。実装などは次のセクションで。 さて、上記の関数を用いて、以下の適当な方程式を解いてみます。 \begin{bmatrix} 1 & 0 & 1 \\ 0 & 1 & 1 \\ 1 & 1 & 1 \end{bmatrix} x = \begin{bmatrix} 1 \\ 2 \\ 3 \end{bmatrix} 解析解は x = \begin{bmatrix} 1 \\ 2 \\ 0 \end{bmatrix} ですが果たして・・・? jikken.py A = np.array([ [1, 0, 1], [0, 1, 1], [1, 1, 1] ]) b = np.array([1, 2, 3]).reshape((-1, 1)) x_true = np.linalg.inv(A).dot(b) print(f"x_true = {x_true}") x0 = np.zeros_like(b) m = 5 for i, xm in enumerate(gmres.solve(A, x0, b, m)): print(f"---- iter {i} ----") print(xm) 出力は以下です。最後の成分が怪しいですが、ほぼゼロなのでヨシ! x_true = [[1.] [2.] [0.]] ---- iter 0 ---- [[0.41558442] [0.83116883] [1.24675325]] ---- iter 1 ---- [[0.53846154] [0.84615385] [1.15384615]] ---- iter 2 ---- [[1.00000000e+00] [2.00000000e+00] [1.11022302e-15]] ---- iter 3 ---- [[1.00000000e+00] [2.00000000e+00] [1.11022302e-15]] ---- iter 4 ---- [[1.00000000e+00] [2.00000000e+00] [1.85622744e-15]] GMRES法 with ヤコビ行列 さて本題です。冒頭の形をした方程式 \frac{\partial F}{\partial x} (x) \dot{x} = - F(x) を解きます。今回は確認を兼ねて、簡単に求められる形で与えます。 F(x) = \begin{bmatrix} x_1 + x_2 \\ x_1 - x_2 \end{bmatrix} するとヤコビ行列は \frac{\partial F}{\partial x} (x) = \begin{bmatrix} 1 & 1 \\ 1 & -1 \end{bmatrix} となります。モデル予測制御でこの形の方程式を解く際は$x \in \mathbb{R}^n$は既知でよいので、以下ではとりあえず$x_1 = 2, x_2 = 1$, つまり$x = \begin{bmatrix}2\ \ 1 \end{bmatrix}^T$とします。この時方程式は \begin{bmatrix} 1 & 1 \\ 1 & -1 \end{bmatrix} \dot{x} = - \begin{bmatrix} 3 \\ 1 \end{bmatrix} と書けます。したがって解析解は \dot{x} = - \begin{bmatrix} 1 & 1 \\ 1 & -1 \end{bmatrix}^{-1} \begin{bmatrix} 3 \\ 1 \end{bmatrix} = \frac{1}{2} \begin{bmatrix} -1 & -1 \\ -1 & 1 \end{bmatrix} \begin{bmatrix} 3 \\ 1 \end{bmatrix} = \begin{bmatrix} -2 \\ -1 \end{bmatrix} と求まります。さて、前のセクションで述べた通り方程式$Ax = b$を解くのには、任意の$v \in \mathbb{R}^n$に対して$Av$が計算できれば良いということを述べました。上記方程式にそれを適用すると 任意の$v \in \mathbb{R}^n$に対して行列積$\frac{\partial F}{\partial x} (x) v$が計算できればよいです。ところでこの式、方向微分っぽくないですか? 方向微分 スカラー値関数$f: \mathbb{R}^n \mapsto \mathbb{R}$について、点$a \in \mathbb{R}^n$におけるある方向$v \in \mathbb{R}^n$に関する方向微分$\frac{\partial f}{\partial v}$は$h$を十分小さな実数として \frac{\partial f}{\partial v}(a) = \lim_{h \to 0} \frac{f(a + h v) - f(a)}{h} で定義されます(極限が存在すれば)。ここで点$a$において全微分可能ならば、任意の方向に関して方向微分可能で以下が成立します。 \frac{\partial f}{\partial v}(a) = \frac{\partial f}{\partial x}(a) v ただし$\frac{\partial f}{\partial x}$は勾配ベクトルで、以下のように定義されます。 \frac{\partial f}{\partial x}(x) = \begin{bmatrix} \frac{\partial f}{\partial x_1} & \cdots & \frac{\partial f}{\partial x_n} \end{bmatrix} 同様にして、ベクトル値関数$F: \mathbb{R}^n \mapsto \mathbb{R}^n$についても定義され、方向$v \in \mathbb{R}^n$に関する方向微分$\frac{\partial F}{\partial v}$は$h$を十分小さな実数として \frac{\partial F}{\partial v}(a) = \lim_{h \to 0} \frac{F(a + h v) - F(a)}{h} で定義されます。スカラー値同様に \frac{\partial F}{\partial v}(a) = \frac{\partial F}{\partial x}(a) v が成立します。 実装 したがって任意の$v \in \mathbb{R}^n$に対して$\frac{\partial F}{\partial x} (x) v$を以下で計算できます。 \frac{\partial F}{\partial x} (x) v = \frac{\partial F}{\partial v} (x) \simeq \frac{F(x + h v) - F(x)}{h} これにより、ヤコビ行列を求める作業を$F$を二回評価するだけにすることができます。 今回の問題では$F(x)$は F(x) = \begin{bmatrix} x_1 + x_2 \\ x_1 - x_2 \end{bmatrix} で定義されていました。$\frac{F(x + h v) - F(x)}{h}$を計算しておくと \frac{F(x + h v) - F(x)}{h} = \begin{bmatrix} v_1 + v_2 \\ v_1 - v_2 \end{bmatrix} となります。ヤコビ行列なしで本当にこれで求まるのでしょうか・・・?それではレッツ実装。 from typing import Callable, Generator, Tuple, List import numpy as np from numpy import linalg as la nptonp = Callable[[np.ndarray], np.ndarray] def arnoldi_with_no_mat(A_func: nptonp, v1: np.ndarray, m: int) -> Tuple[np.ndarray, np.ndarray]: vs: List[np.ndarray] = [v1] H = np.zeros((m+1, m)) for j in range(m): v_j = vs[j] Av = A_func(v_j) for i in range(j+1): h_ij = Av.T.dot(vs[i]) H[i, j] = h_ij # Av - \sum_{i = 1}^j h_{ij} * v_{i} v_next = Av - np.concatenate([H[i, j] * vs[i] for i in range(j+1)], axis=1).sum(axis=1, keepdims=True) h_j1j = np.linalg.norm(v_next) H[j+1, j] = h_j1j vs.append(v_next / h_j1j) return np.concatenate(vs[:-1], axis=1), H def solve_with_no_mat(A_func: nptonp, x0: np.ndarray, b: np.ndarray, m: int) -> Generator[np.ndarray, None, None]: r0 = b - A_func(x0) v1 = r0 / la.norm(r0) for i in range(1, m+1): Vm, Hm = arnoldi_with_no_mat(A_func, v1, i) e1 = np.zeros((i+1, 1)) e1[0, 0] = la.norm(r0) ym = la.inv(Hm.T.dot(Hm)).dot(Hm.T).dot(e1) yield x0 + Vm.dot(ym) 前のセクションと異なる点は、$Av$を計算する部分を関数で渡すようにしただけです。引数A_funcにはA(v)に相当する関数を入れます。今回は$\frac{F(x + h v) - F(x)}{h}$を計算する関数をいれます。 jikken2.py def F(x): x1 = x[0, 0] x2 = x[1, 0] return np.array([ [x1 + x2], [x1 - x2] ]) def F_grad(x): # x1 = x[0, 0] # x2 = x[1, 0] return np.array([ [1, 1], [1, -1] ]) x1, x2 = 2, 1 h = 1e-5 x = np.array([x1, x2]).reshape((-1, 1)) def A_func(v: np.ndarray): v1 = v[0, 0] v2 = v[1, 0] return np.array([ [v1 + v2], [v1 - v2] ]) A = F_grad(x) b = - F(x) # 解析解 x_dot_true = np.linalg.inv(A).dot(b) print(f"x_dot_true = {x_dot_true}") # 初期解は0でスタート。 x_dot0 = np.zeros_like(x) m = 5 for i, x_dotm in enumerate(eqsolver.solve_with_no_mat(A_func, x_dot0, b, m)): print(f"---- iter {i} ----") print(x_dotm) 結果は・・・ x_dot_true = [[-2.] [-1.]] ---- iter 0 ---- [[-2.1] [-0.7]] ---- iter 1 ---- [[-2.] [-1.]] ---- iter 2 ---- [[-2.] [-1.]] ---- iter 3 ---- [[-2.] [-1.]] ---- iter 4 ---- [[-2.] [-1.]] この通り、正しく解を推定できています。すごい・・・・・・・!!!!! 参考文献・補足 [1] 非線形最適制御入門, 大塚敏之, コロナ社, 第4版 GMRES法の存在を知った書籍。 [2] Krylov部分空間とGMRES法について 元のPDFと合わせてわかりやすい記事でした。この場を借りて感謝申し上げます。 元のPDF→大規模連立1次方程式に対する一般化最小残差法について [3] 正規方程式の導出と計算例 GMRES法の残差誤差を最小化する時に正規方程式を使って解いています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python3 コーディング規約(PEP8-ja)

Python標準ライブラリのコーディング規約を整理します。Pythonのコーディング規約はPEP8-jaという文書にまとめられており、命名規則やインデントのルールが記載されています。 標準的なコーディングルールに準ずることでプログラムの可読性を向上させることができます。 インデント インデントは1レベルにつき4つ 複数行を継続する場合は4つでなくてもよい タブではなくスペースでインデントをつける 1行の長さ 最大79文字 docstringやコメント等のテキストは最大72文字 2項演算子の前で改行する 命名規則 作成するもの 命名規則 パッケージ(ディレクトリ) 全て小文字アンダースコアは非推奨 モジュール(ファイル) 全て小文字アンダースコアは可 クラス名 CapWords(頭文字が大文字, それ以降はキャメルケース) 例外 CapWords 名前の末尾に「Error」を付ける 関数名 全て小文字・アンダースコアは可非公開の場合先頭に_を1つつける インスタンスメソッドの引数 第一引数は常にself クラスメソッドの引数 第一引数は常にcls 変数名 全て小文字・アンダースコアは可 定数 全て大文字単語をアンダースコアで区切る
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Excelの入力シートを基に、Pythonを使って自動でHTML生成してみた

目的 pythonスキルアップ用 前提 Pythonの環境構築が終わっていること 実践 入力シートを準備 ファイル名、見出しタイトルの準備 test.py # ブックを取得 book = openpyxl.load_workbook('入力シート.xlsx') # シートを取得 sheet1 = book['Sheet1'] # セルを取得 title = sheet1['B2'].value subTitle = sheet1['B3'].value 表データを作る test.py table = ""; if sheet1['B4'].value == "○": loopNumC = sheet1['C5'].value i=1 table = "<div><table border='1'>" for num in range(loopNumC): arrayB = "B"+str(5+i) arrayC = "C"+str(5+i) arrayD = "D"+str(5+i) tableDataRow = sheet1[arrayB].value tableDataA_i=sheet1[arrayC].value tableDataB_i=sheet1[arrayD].value table+="<tr><td>"+str(tableDataRow)+"</td>" table+="<td>"+tableDataA_i+"</td>" table+="<td>"+tableDataB_i+"</td></tr>" i=i+1 table+="</table></div>" ファイル作成~書き込み、ブラウザ表示まで test.py index = sheet1['B1'].value if(os.path.isfile("C:\\Users\\user\\Documents\\python\\html\\"+index+".html")): html= pathlib.Path("C:\\Users\\user\\Documents\\python\\html\\"+index+"_copy.html") else: html= pathlib.Path("C:\\Users\\user\\Documents\\python\\html\\"+index+".html") html.touch() head = "<DOCTYPE html><html lang='ja'><head>" head += "<meta name='viewport' content='width=device-width,initial-scale=1'>" head += "<link rel='stylesheet' href='../css/default.css'>" head += "<title>"+title+"</title>" head += "</head>" cssFile= pathlib.Path("C:\\Users\\user\\Documents\\python\\css\\default.css") with open(cssFile, mode="w") as f: f.write("html,body{background:rgb(29,29,29);color:rgb(200,200,200);}table{width:"+sheet1['E5'].value+";height:"+sheet1['F5'].value+";}") with open(html, mode="w") as f: f.write(head) f.write("<body>") f.write("<h1>"+title+"</h1>") f.write("<h2>"+subTitle+"</h2>") if table!="": f.write(table) else: f.write("") f.write("</body>") f.write("</html>") webbrowser.open(html) Python実行すると、ブラウザ自動起動しし、入力シートに書いた内容がHTMLに反映されます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

クローラー(?)を自作した話

何故作ったのか Scrapyの存在を知らなかったから 前からダウンローダーとかそういうの作ってみたかったんです それで調べてみたらサイトのクロールとか面白そうだし、ネット小説をまとめてローカルで見れるように出来たら結構いいんじゃないかと思ったので あと動けば良しの精神で作ってるので場合によっては誤作動起こすかもしれません…… インストール リポジトリをpipで指定してインストールします $ pip install git+https://github.com/mino-38/prop 使い方 りどみの方を見て頂ければ大体の使い方は分かると思います 基本、クロールは以下で出来ます $ prop -r [再帰回数(省略可)] -o [出力先ディレクトリ] -I [インターバル] URL 仕様について robots.txtの内容は守るようにされてます hrefなどの参照先を、ローカルに存在するものはローカルのファイルを指定するように変換します クロール以外も一応できます 保存ファイルのフォーマットを指定することができます 階層ごとにディレクトリは作られず、指定したフォルダに全てダウンロードされます(そもそもネット小説で2階層以上ダウンロードすることがない) 全部英語です(日本語よりは英語で作ったほうがいい気がしたので) 作ってみての感想 改めて見ると余計な機能付け過ぎましたね…… requests.getが引数に取る値をとりあえずオプションに追加したって感じです 引数解析部に関してはargparseを使おうか迷ったんですが一部特殊な解析が要るかなと判断したのでゴリ押ししました(おかげでelifが沢山) あとrequests、BeautifulSoupの偉大さがよ〜〜〜〜〜く分かりました
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

第2章2.3 [第2版]Python機械学習プログラミング 達人データサイエンティストによる理論と実践

振り返り 前回は初期の機械学習の歴史を見て、パーセプトロンをClassで定義しました。 今回は実際に動かしてみましょうというわけです。使うデータはおなじみのアヤメのデータセットです。 2.3 Irisデータセットでのパーセプトロンモデルのトレーニング 有名なアヤメのデータは、がく片や花びらの幅・長さと、それに対応する品種が用意されています。本来は3種類(Setosa, Versicolor, Virginica)ですが、前回作ったClassは陰と陽の2種類に分けるものだったので、アヤメデータセットからSetosaとVersicolorだけを抜き出して使用します。 本書ではPandasライブラリを使ってUCI Machine Learning Repositoryからデータを読み込むとありましたが、お手本通りやってもうまくいかなかったので、Scikit-learnから読み込みました。 from sklearn import datasets iris = datasets.load_iris() x = iris.data y = iris.target_names[iris.target] df_x, df_y = pd.DataFrame(x), pd.DataFrame(y) df = pd.concat([df_x, df_y], axis=1) --------dfの中身↓------------------------------ 0 1 2 3 0 0 5.1 3.5 1.4 0.2 setosa 1 4.9 3.0 1.4 0.2 setosa 2 4.7 3.2 1.3 0.2 setosa 3 4.6 3.1 1.5 0.2 setosa 4 5.0 3.6 1.4 0.2 setosa ... これで本と同じようなDataFrameを用意できますね。 ここから2種類のみを取り出すので、150個あるデータのうち100個までを抜き出します。 また抜き出す際は、Sepal length(0列目)とPetal Length(2列目)も一緒に抜き出し、setosaは-1に、Versicolorは1にします。 import matplotlib.pyplot as plt import numpy as np y = df.iloc[0:100,4].values y = np.where(y == 'setosa', -1, 1) X = df.iloc[0:100, [0,2]].values plt.scatter(X[:50, 0], X[:50,1], color='red', marker='o', label='setosa') plt.scatter(X[50:100, 0], X[50:100,1], color='blue', marker='x', label='versicolor') plt.xlabel('sepal length [cm]') plt.ylabel('petal length [cm]') plt.legend(loc='upper left') plt.show() DataFrameは.iloc[列, 行]で抜き出せます。Valuesをつけることで、numpy ndarray型で抜き出します。 そして前回もでできましたWhereです。なんだかExcelにIF関数に似ているような感じですね... あとはグラフを表示すると以下のようになります。 これをみるに、品種を分ける境界線は引けそうな雰囲気ですよね。よってパーセプトロンで分類できるということになります。 では早速トレーニングしましょう。前回各エポックでの誤分岐の数を見るため、グラフを表示します。 定義したClassを使いますので、事前に読み込んでおきましょう。 #パーセプトロンをインスタンス化 ppn = Perceptron(eta=0.1, n_iter=10) #モデル適合 ppn.fit(X, y) plt.plot(range(1, len(ppn.errors_) + 1), ppn.errors_, marker='o') plt.xlabel('Epochs') plt.ylabel('Number of update') plt.show() 結果は以下のようになります。 このグラフから6回目のエポックでパーセプトロンはすでに収束しており、サンプルを完璧に分類できているということになります。ここで決定境界を可視化するための関数を実装します。 from matplotlib.colors import ListedColormap def plot_decision_regions(X, y, classifier, resolution = 0.02): #マーカーとカラーマップの準備 markers = ('s', 'x', 'o', '^', 'v') colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan') cmap = ListedColormap(colors[:len(np.unique(y))]) #決定領域のプロット x1_min, x1_max = X[:,0].min() -1, X[:,0].max() + 1 x2_min, x2_max = X[:,1].min() -1, X[:,1].max() + 1 #グリットポイントの生成 xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),np.arange(x2_min, x2_max, resolution)) #各特徴量を1次元配列に変換して予測を実行 Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T) #予測結果を元のグリッドポイントのデータサイズに変換 Z = Z.reshape(xx1.shape) #グリッドポイントの等高線のプロット plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap) #軸の設定 plt.xlim(xx1.min(), xx1.max()) plt.ylim(xx2.min(), xx2.max()) #クラスごとにサンプルをプロット for idx, cl in enumerate(np.unique(y)): plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1], alpha=0.8, c=colors[idx], marker=markers[idx], label=cl, edgecolors='black') plot_decision_regions(X, y, classifier=ppn) plt.xlabel('sepal length [cm]') plt.ylabel('petal length [cm]') plt.legend(loc='upper left') plt.show() contourfを使って等高線を表示します。等高線を表示するためには格子点(ここでいうと特徴量のこと)と各点ごとの値(-1 or 1)を用意する必要があります。 meshgridは格子点を生成するのに使用しますが、範囲を決めるためにx1_min~x2_maxを用意しています。格子点の間隔はデフォルトで1ですが、今回は間隔をresolution=0.02を指定しています。 関数が用意できたので、実行しましょう。 plot_decision_regions(X, y, classifier=ppn) plt.xlabel('sepal length [cm]') plt.ylabel('petal length [cm]') plt.legend(loc='upper left') plt.show() この結果からパーセプトロンのアルゴリズムが決定境界を学習したということがわかります。 ※パーセプトロンの課題は収束性だそう。パーセプトロンが収束するのは線形超平面で分類できる場合に限るとFrank Rosenblatt(MCPニューロンモデルに基づいてパーセプトロンの学習規則に関する概念を発表した人)が数学的に証明しているそうです。つまり線形超平面で分離できない場合は、エポック最大数を指定しないといつまでも重みを更新し続けることになります。 2.4 ADALINEと学習の収束 2章で学ぶ分類問題にはパーセプトロンと、もう1つの単ADALINE(Adaptive Linear Neuron)がありましたので、今回は後半線ということです。 Frank Rosenblattがパーセプトロンのアルゴリズムを発表してから数年後、Bernard Widrowと博士課程学生Tedd HoffはADALINEを発表しました。 ADALINEとRosenblattのパーセプトロンの主な違いは重みの更新方法にあります。 パーセプトロンではステップ関数でしたが、ADALINEは線形活性化関数に基づいて重みが更新されます。 \phi(w^Tx)=w^Tx ADALINEの学習規則はWidrow-Hoff則と言われるそうです。 実際文章で見てもよくわからないということで、以下に違いを表す図を示します。(こちらから引用しました) 重みに誤差をフィードバックする際、ステップ関数ではなく線形関数を使うということですね。 まとめ 実際にアヤメのデータを使ってパーセプトロンを実装し、分離する境界を決めました。 またADALINEとパーセプトロンの違いについても軽くお話ししました。 ...なかなか大変...ほんとに続けられるのか??
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

第2章2.3~2.4 [第2版]Python機械学習プログラミング 達人データサイエンティストによる理論と実践

振り返り 前回は初期の機械学習の歴史を見て、パーセプトロンをClassで定義しました。 今回は実際に動かしてみましょうというわけです。使うデータはおなじみのアヤメのデータセットです。 2.3 Irisデータセットでのパーセプトロンモデルのトレーニング 有名なアヤメのデータは、がく片や花びらの幅・長さと、それに対応する品種が用意されています。本来は3種類(Setosa, Versicolor, Virginica)ですが、前回作ったClassは陰と陽の2種類に分けるものだったので、アヤメデータセットからSetosaとVersicolorだけを抜き出して使用します。 本書ではPandasライブラリを使ってUCI Machine Learning Repositoryからデータを読み込むとありましたが、お手本通りやってもうまくいかなかったので、Scikit-learnから読み込みました。 from sklearn import datasets iris = datasets.load_iris() x = iris.data y = iris.target_names[iris.target] df_x, df_y = pd.DataFrame(x), pd.DataFrame(y) df = pd.concat([df_x, df_y], axis=1) --------dfの中身↓------------------------------ 0 1 2 3 0 0 5.1 3.5 1.4 0.2 setosa 1 4.9 3.0 1.4 0.2 setosa 2 4.7 3.2 1.3 0.2 setosa 3 4.6 3.1 1.5 0.2 setosa 4 5.0 3.6 1.4 0.2 setosa ... これで本と同じようなDataFrameを用意できますね。 ここから2種類のみを取り出すので、150個あるデータのうち100個までを抜き出します。 また抜き出す際は、Sepal length(0列目)とPetal Length(2列目)も一緒に抜き出し、setosaは-1に、Versicolorは1にします。 import matplotlib.pyplot as plt import numpy as np y = df.iloc[0:100,4].values y = np.where(y == 'setosa', -1, 1) X = df.iloc[0:100, [0,2]].values plt.scatter(X[:50, 0], X[:50,1], color='red', marker='o', label='setosa') plt.scatter(X[50:100, 0], X[50:100,1], color='blue', marker='x', label='versicolor') plt.xlabel('sepal length [cm]') plt.ylabel('petal length [cm]') plt.legend(loc='upper left') plt.show() DataFrameは.iloc[列, 行]で抜き出せます。Valuesをつけることで、numpy ndarray型で抜き出します。 そして前回もでできましたWhereです。なんだかExcelにIF関数に似ているような感じですね... あとはグラフを表示すると以下のようになります。 これをみるに、品種を分ける境界線は引けそうな雰囲気ですよね。よってパーセプトロンで分類できるということになります。 では早速トレーニングしましょう。前回各エポックでの誤分岐の数を見るため、グラフを表示します。 定義したClassを使いますので、事前に読み込んでおきましょう。 #パーセプトロンをインスタンス化 ppn = Perceptron(eta=0.1, n_iter=10) #モデル適合 ppn.fit(X, y) plt.plot(range(1, len(ppn.errors_) + 1), ppn.errors_, marker='o') plt.xlabel('Epochs') plt.ylabel('Number of update') plt.show() 結果は以下のようになります。 このグラフから6回目のエポックでパーセプトロンはすでに収束しており、サンプルを完璧に分類できているということになります。ここで決定境界を可視化するための関数を実装します。 from matplotlib.colors import ListedColormap def plot_decision_regions(X, y, classifier, resolution = 0.02): #マーカーとカラーマップの準備 markers = ('s', 'x', 'o', '^', 'v') colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan') cmap = ListedColormap(colors[:len(np.unique(y))]) #決定領域のプロット x1_min, x1_max = X[:,0].min() -1, X[:,0].max() + 1 x2_min, x2_max = X[:,1].min() -1, X[:,1].max() + 1 #グリットポイントの生成 xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),np.arange(x2_min, x2_max, resolution)) #各特徴量を1次元配列に変換して予測を実行 Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T) #予測結果を元のグリッドポイントのデータサイズに変換 Z = Z.reshape(xx1.shape) #グリッドポイントの等高線のプロット plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap) #軸の設定 plt.xlim(xx1.min(), xx1.max()) plt.ylim(xx2.min(), xx2.max()) #クラスごとにサンプルをプロット for idx, cl in enumerate(np.unique(y)): plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1], alpha=0.8, c=colors[idx], marker=markers[idx], label=cl, edgecolors='black') plot_decision_regions(X, y, classifier=ppn) plt.xlabel('sepal length [cm]') plt.ylabel('petal length [cm]') plt.legend(loc='upper left') plt.show() contourfを使って等高線を表示します。等高線を表示するためには格子点(ここでいうと特徴量のこと)と各点ごとの値(-1 or 1)を用意する必要があります。 meshgridは格子点を生成するのに使用しますが、範囲を決めるためにx1_min~x2_maxを用意しています。格子点の間隔はデフォルトで1ですが、今回は間隔をresolution=0.02を指定しています。 関数が用意できたので、実行しましょう。 plot_decision_regions(X, y, classifier=ppn) plt.xlabel('sepal length [cm]') plt.ylabel('petal length [cm]') plt.legend(loc='upper left') plt.show() この結果からパーセプトロンのアルゴリズムが決定境界を学習したということがわかります。 ※パーセプトロンの課題は収束性だそう。パーセプトロンが収束するのは線形超平面で分類できる場合に限るとFrank Rosenblatt(MCPニューロンモデルに基づいてパーセプトロンの学習規則に関する概念を発表した人)が数学的に証明しているそうです。つまり線形超平面で分離できない場合は、エポック最大数を指定しないといつまでも重みを更新し続けることになります。 2.4 ADALINEと学習の収束 2章で学ぶ分類問題にはパーセプトロンと、もう1つの単ADALINE(Adaptive Linear Neuron)がありましたので、今回は後半線ということです。 Frank Rosenblattがパーセプトロンのアルゴリズムを発表してから数年後、Bernard Widrowと博士課程学生Tedd HoffはADALINEを発表しました。 ADALINEとRosenblattのパーセプトロンの主な違いは重みの更新方法にあります。 パーセプトロンではステップ関数でしたが、ADALINEは線形活性化関数に基づいて重みが更新されます。 \phi(w^Tx)=w^Tx ADALINEの学習規則はWidrow-Hoff則と言われるそうです。 実際文章で見てもよくわからないということで、以下に違いを表す図を示します。(こちらから引用しました) 重みに誤差をフィードバックする際、ステップ関数ではなく線形関数を使うということですね。 まとめ 実際にアヤメのデータを使ってパーセプトロンを実装し、分離する境界を決めました。 またADALINEとパーセプトロンの違いについても軽くお話ししました。 ...なかなか大変...ほんとに続けられるのか??
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonのParamikoでCisco IOS機器のバックアップを取得する

はじめに pythonのParamikoライブラリを使ってCisco IOS機器のバックアップを取得する(show runの結果を取得してファイルに保存する)スクリプトを作成したので参考までに共有します。 前提 実行環境(Cisco IOSに接続するクライアント):ubuntu 20.04 pythonバージョン:3.8.10 実行環境上でpramikoライブラリをインストールします。 pip install paramiko 以下図のUbuntuからParamikoを使ってIOUルータ3台のバックアップを取得します。 スクリプト paramikoの汎用モジュール(paramiko_mod.py)と、 バックアップを実行するメインファイル(ios_backup_executer.py)の2部構成です。  paramikoの汎用モジュール paramiko_mod.py import paramiko import time from paramiko.client import AutoAddPolicy def connect(server_ip: str, server_port: str, user: str, password: str): '''SSHサーバに接続''' ssh_client = paramiko.SSHClient() ssh_client.set_missing_host_key_policy(AutoAddPolicy()) print(f'Connecting to {server_ip}') ssh_client.connect( hostname=server_ip, port=server_port, username=user, password=password, look_for_keys=False, allow_agent=False ) return ssh_client def get_shell(ssh_client): '''対話型シェルを返す''' shell = ssh_client.invoke_shell() return shell def send_command(shell, command: str, timeout=1): '''コマンドを実行''' print(f'Sending command: {command}') shell.send(command + '\n') time.sleep(timeout) def show(shell, n=10000): '''結果をデコードして返す''' # 結果をバイト列で取得 output = shell.recv(n) return output.decode() def close(ssh_client): '''SSH接続を閉じる''' if ssh_client.get_transport().is_active() == True: print('Closing the connection') ssh_client.close() SSH認証方式はユーザ認証としています。公開鍵認証、SSHエージェントも利用できるようです。 send_command(shell, command: str, timeout=1): コマンド結果が指定秒以内に返って来ない場合があるため、timeout値は呼び出し時に変更できるようにしています。 バックアップを実行するメインファイル ios_backup_executer.py import os import datetime import threading import paramiko_mod from datetime import datetime def backup(router): # sshクライアントを生成 client = paramiko_mod.connect(**router) # 対話型シェルを呼び出す shell = paramiko_mod.get_shell(client) # ssh接続先で実行するコマンドをシェルに渡す paramiko_mod.send_command(shell, 'enable') paramiko_mod.send_command(shell, '######') # enableパスワード paramiko_mod.send_command(shell, 'terminal length 0') # sh runの結果が取得できない場合はtimeout値を調整 paramiko_mod.send_command(shell, 'sh run', timeout=3) # 結果を取得&リストに整形 output = paramiko_mod.show(shell) # [show run]コマンド 実行結果の不要な箇所を削除するため、一度リストに変換する output_list = output.splitlines() output_list = output_list[11:-1] # リストを文字列に戻す output = '\n'.join(output_list) print(output) # バックアップ先のディレクトリがなければ作成 backup_dir = f'backup/{router["server_ip"]}' if not os.path.exists(backup_dir): os.makedirs(backup_dir) # 結果をバックアップファイルに書き出す bk_file_name = f'{router["server_ip"]}_{now.year}{now.month}{now.day}' with open(f'{backup_dir}/{bk_file_name}', 'w') as f: f.write(output) paramiko_mod.close(client) router1 = { 'server_ip': '192.168.x.x', 'server_port': '22', 'user': 'XXXXXXX', 'password': 'XXXXXXX' } router2 = { 'server_ip': '192.168.x.y', 'server_port': '22', 'user': 'XXXXXXX', 'password': 'XXXXXXX' } router3 = { 'server_ip': '192.168.x.z', 'server_port': '22', 'user': 'XXXXXXX', 'password': 'XXXXXXX' } routers = [router1, router2, router3] if __name__ == '__main__': now = datetime.now() # バックアップ関数をスレッドリストに詰める threads = [threading.Thread(target=backup, args=(router,)) for router in routers] # マルチスレッドでバックアップ開始 for th in threads: th.start() # バックアップがすべて終わるまで待機 for th in threads: th.join() 本スクリプトでは、3台のCiscoルータのバックアップ処理をマルチスレッドで実行しています。 バックアップファイルはカレントディレクトのbackup/{router["server_ip"]}配下に保存される仕様です。 パスワードやユーザ情報はハードコーディングしていますが、環境変数や別の設定ファイル(ini, yml等)に分ける等の実装がよいと思います。(対象機器の台数が多くなった場合の設定管理の意味も含めて)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ラズパイ環境でinvestpyをpip installしようとしたら“error command gcc failed with exit status 1”のエラーで詰まったときの話

0.前置き 株や為替のデータを取得するためのpythonライブラリinvestpyを使用するため、ラズパイ上で動作しているRasbianOSでpip installを実施たところ、“error command gcc failed with exit status 1” や"ERROR: Command errored out with exit status 1" というエラーでインストールできず、詰まってしましました。 色々と試してみて、なんとか解決できたので、その際に実施したことをまとめます。 1.環境 raspberry Pi3B+:Raspbian GNU/Linux 10 (buster) python 3.10.1 pip 21.3.1 gcc version 8.3.0 (Raspbian 8.3.0-6+rpi1) 2.解決に繋がりそうなこと要約 色々と試しましたが、以下の項目がキーとなっていそうです。順番に試していただいて、解決できるかを試してください。 pipの存在確認&アップデート gccの存在確認&アップデート build-essentialの存在確認&インストール python3-devの存在確認&インストール libffi、 libxslt、opensslの存在確認&インストール なお、今回私の場合は、1~4はapt upgradeで一括でアップデートし、その後libxsltライブラリのインストール(アップデート)で無事解決しました。 3.“error command gcc failed with exit status 1”の解決 最初に出たエラーがこれでした。pip installでインストールするライブラリのコンパイルに失敗しているエラーなので、以下を実施すると解決できるかもしれません。 pipのアップデート pip install -U pip gccのアップデート sudo apt update sudo apt upgrade gcc build-essentialのインストール sudo apt install build-essential python3-devのインストール sudo apt install python3-dev 4. "ERROR: Command errored out with exit status 1:の解決 上記gccライブラリ関係のエラーは解決できましたが、まだexit status 1のエラーが表示されていました。 ライブラリのコンパイルに必要なツールがまだ足りていない可能性があり、利用頻度の高い3つのライブラリをインストールしました。 libffi、 libxslt、openssのインストール sudo apt install libxslt-dev libffi-dev libssl-dev 上記ライブラリの中で、私の場合はlibxslt-devが不足していたようです。このライブラリに関連する各種ソフトウェアがインストールされ、再度investpyのインストールを試みると、いかのように無事インストールすることができました。 terminal root@raspberrypi:/home/pi# pip install investpy WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip. Please see https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue. To avoid this problem you can invoke Python with '-m pip' instead of running pip directly. Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple Collecting investpy Using cached https://www.piwheels.org/simple/investpy/investpy-1.0.7-py3-none-any.whl (4.5 MB) Collecting lxml>=4.4.1 Using cached lxml-4.7.1.tar.gz (3.2 MB) Preparing metadata (setup.py) ... done Requirement already satisfied: Unidecode>=1.1.1 in /usr/local/lib/python3.10/site-packages (from investpy) (1.3.2) Requirement already satisfied: requests>=2.22.0 in /usr/local/lib/python3.10/site-packages (from investpy) (2.27.1) Requirement already satisfied: pandas>=0.25.1 in /usr/local/lib/python3.10/site-packages (from investpy) (1.3.5) Requirement already satisfied: pytz>=2019.3 in /usr/local/lib/python3.10/site-packages (from investpy) (2021.3) Requirement already satisfied: setuptools>=41.2.0 in /usr/local/lib/python3.10/site-packages (from investpy) (58.1.0) Requirement already satisfied: numpy>=1.17.2 in /usr/local/lib/python3.10/site-packages (from investpy) (1.22.0) Requirement already satisfied: python-dateutil>=2.7.3 in /usr/local/lib/python3.10/site-packages (from pandas>=0.25.1->investpy) (2.8.2) Requirement already satisfied: urllib3<1.27,>=1.21.1 in /usr/local/lib/python3.10/site-packages (from requests>=2.22.0->investpy) (1.26.7) Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/site-packages (from requests>=2.22.0->investpy) (2021.10.8) Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/site-packages (from requests>=2.22.0->investpy) (3.3) Requirement already satisfied: charset-normalizer~=2.0.0 in /usr/local/lib/python3.10/site-packages (from requests>=2.22.0->investpy) (2.0.10) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/site-packages (from python-dateutil>=2.7.3->pandas>=0.25.1->investpy) (1.16.0) Using legacy 'setup.py install' for lxml, since package 'wheel' is not installed. Installing collected packages: lxml, investpy Running setup.py install for lxml ... done Successfully installed investpy-1.0.7 lxml-4.7.1 5. そういえば... 一旦解決したあと、改めてエラーログを確認してみると、"Error: Please make sure the libxml2 and libxslt development packages are installed."と書かれている箇所を見つけました。やはりlibxsltあたりに問題があったようですね。 以上、どなたかのお役に立てば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Colab×SpreadSheet

1.はじめに はじめまして、社会人1年目のyappiです。 以前、形態素解析について投稿しまして、データの蓄積もしたいな~と思い、今回はGoogle SpreadSheetに格納してみました。連携方法や値の格納について備忘録としてまとめおきます。 2.連結 まずは、Colabの方でSpreadSheetとの連結をしていきます。 # 必要なライブラリをインポート from google.colab import auth from oauth2client.client import GoogleCredenstials import gspread # 認証処理 auth.authenticate_user() gc = gspread.authorize(GoogleCredentials.get_application_ 3.シートの作成 以前投稿したときに、スクレイピングして、形態素解析したものをcsvファイルにしたいと考えていました。改良していく中で、それよりもSpreadSheetに書き込んだ方が良くない?と思い...。 今回は、1日1回ニュースを取り込んでみようと考えました。日付ごとにシートを分け、そのシート内にデータを格納していきます。 そのため、シート名に日付をいれて新規シートを作成することで、全て自動でシートの作成とデータの格納をすることができます。 #日付を取得する import datetime dt = datetime.date.today() #記録するファイルに入る sheet = gc.open_by_key(' Google SpreadSheetのID ') #日付をシート名に指定して作成 #もちろんですが、title・rows・colsは自由にできます。 worksheet = sheet.add_worksheet(title = f'googleNews_{dt}', rows=10, cols=10) これで日にちごとのシートを作成することができました。 4.値の格納 格納の構造がいまいちイメージができませんでした。 今回は、改良するにあたって1次元と2次元配列の格納を試してみたので、その結果を載せておきます。 ※Mapでも試してみたんですが、わかりませんでした汗 #1次元配列 #datas = ['A1に追加', 'B1に追加', 'C1に追加'] #datas→複数   datum→単数(@shiracamus さんにご教示いただきました。) datum = ['A1に追加', 'B1に追加', 'C1に追加'] worksheet.append_row(datas) #2次元配列 datas = [['A1に追加', 'B1に追加', 'C1に追加'], ['A2に追加', 'B2に追加', 'C2に追加'] ] for temp in datas: worksheet.append_row(temp) 今回の改良版では、2次元配列を採用しました! #形態素解析前の単語と形態素解析後の単語を分けて格納する。 #単語羅列するとわかりにくいので、ここでは前が数字、後が英字だと思ってください datas = [['1', '2', '3'] ['A', 'B', 'C']] #2次元配列を格納していきます。 for temp in datas: worksheet.append_row(temp) これで、列ごとに格納したいデータを分けて格納することができます。 終わりに 次回の記事では、1週間分のGoogleNewsを取得して、どのような単語が多いのか、1週間のトレンドは何かなどを分析した記事をあげていこうと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【入門】AnacondaインストールからHello worldまで(for Mac)

なぜAnacondaを用いるの??? (興味がなければ読み飛ばしてください) ローカルな環境でPythonを使っていくと、いつの日かpipやらと競合(Conflict)し、エラーの海に飛ばされてしまいます。かといって、dockerを用いてコンテナ化やらなんやらというのも、いちいちやるのは面倒です。ここで、Anacondaは標準で仮想環境を用意してくれています。(それについては別途まとめます。)さらに、Anacondaにはデフォルトで「Numpy」や「Scipi」、「Pandas」、「jupyter」などの便利なライブラリの多くが入っています!本当にこれ一つで大体のことはできちゃうのです!(ただし、Anacondaでサポートされていないライブラリもあるので、その辺は別途仮想環境を用意してそっちにinstallが必要。) インストール 早速Anacondaをインストールしていきましょう! Anacondaは以下のサイトからダウンロードできます! URL: https://www.anaconda.com/products/individual リンク先は2022年1月8日現在、以下の写真のようになっています。画面右のAnaconda Indivisual Editionのダウンロードマークをクリックすると、Anacondaのインストーラーがインストールされます。 ダウンロードが開始されると色々出てきますが興味がなければ放置。Anacondaインストーラーのダウンロードを待ちます。 Anacondaインストーラーがダウンロードできたら、Finderのダウンロードに進み、以下の箱のようなアイコンをダブルクリック。 その後色々「許可してください」や「同意してください」などが表示されますので許可していきます。(中略) インストールをクリックすると、インストールが開始されます。インストールは環境にもよりますが、~5分くらいで終わります。 Anacondaのインストーラーをゴミ箱に入れたらば、Anacondaのインストールは完了です! ターミナルにて、conda -Vと入力し、conda+○.○.○の形でバージョンが帰ってきたらインストール成功です。 $conda -V conda 4.10.3 さらに、Launchpadか、アプリケーションから緑の丸のアイコンを見つけ、クリック! Anacondaナビゲーターが起動します! Hello world ここからjupyter labを開いてみましょう。起動は「Launch」をクリックするだけです。jupyter labはマークダウン式の記述もできる(ノートみたいなイメージ)優れものです! (ターミナルからjupyter labと入力しても起動します。) ここで、jupyter labの起動と同時にたくさんコードが流れるターミナルウィンドウが現れますが、これはjupyter labを裏から操っている本体なので、使用している際には閉じないでください。 「Notebook」の「Python 3(ipykernel)」をクリックすると、ノートブックが作成されます。 空白のシェル(グレーの部分)に、print("Hello World")と入力し、Shift+Enterキーを押します。(または、実行ボタン「▷」をクリック)すると、pythonプログラムが実行されます。 保存する際は、Fileから「Save notebook」や「Save notebook as...」をクリックします(前者はファイル名を編集しないとき、後者はファイル名を編集するとき) 終了する際は、Fileから「Shut Down」をクリックします。 終わりに こちら自分の初投稿記事になります。最後までお付き合いいただきありがとうございました。アドバイス、コメント等いただけると嬉しいです。 追記: jupyter notebookではなく、後継のjupyter labの方が開発環境として良いとのことで、jupyter labでの標準出力紹介に変更しました。アドバイスくださった@StrawBerryMoonさんありがとうございます。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

kivyMD開発其の壱 初期公開説明篇

正月気分がようやく薄れ、またいつもの毎日が戻ってまいりました。(強制) 今年もよろしくお願いしまーす!(いろんな方が見てると思うので簡素に) はい、ということで新年もKivyMDのお時間がやってきました!お正月気分は まだ抜けてない人が多いでしょうかね。投稿者も全然抜けてはいません というわけで、寒い中なんかお仕事も始まっていますが、(あと体重が増えたりも...) なんかやる気でなーとなった方はこちらの記事でも読んでお正月気分を抜いてもら えれば、この上なく幸いでございます。(おそらく抜けない) 強引に今日のお題に入っていきますが、まずは去年の年末にどんなことをやって いたかと言うと、ギリギリ滑りこみセーフで1stAppのリリースを告知していま した。なんそれ?そんなの知らないよという方は以下の記事を見てもらえればと 思います。 まぁ、全然大したことないTODOアプリでしかも完成はしていないんですけどね。。 今回はこちらのアプリの続編で、前回解説をしていなかったのでその解説をして みたいと思います。1回分で説明できればと思いますが、体力がなかった場合は その限りではないので、そこあたりはご了承頂ければと思います。 というわけで、ここを長くしてもしょうがないのでさっそく説明に入っていきます。 動き コードだけ見ても、動きを見ておかないとなんだか分からんとなると思うので、 前回を一部だけ振り返っておきます。 どんなアプリなのよ、と思われる方は上のキャプチャを見てもらえればなんとなく 雰囲気は伝わるかと思われます。 説明 ここからは、コードの方を説明していきます。コードについてはこれも前回告知済 ですが、知らない方も多くいられると思うので再掲しておきます。以下のリポジトリ にプッシュ済みで前回あげたときから改変はしておりません。 KV 普段はマニュアルから構成をガラリと変えていないので、KV言語を分けることは全然 ないのですが、今回に至ってはKVファイルとpyファイルとを分けることにしています。 分けるに至っては、KVファイルとクラス名(pyファイル名しかり)を同一にすることが 必要なので、今回は名前をmainという風に統一しています。ちゃんと公開するときは この形式が良さそうな気がしています。 ※この辺りはKivyのことなのでちゃんと知りたい方は公式マニュアルもしくは以下の 黄本(と呼ぶのか?)を参照ください ※当然ではあるがアフィリエイトではない ということで、寄り道が過ぎていますがこれからはKV側の説明に入っていきます。 まずはこちらでもコードの方を掲載しておきます。 MDBoxLayout: orientation: "vertical" MDToolbar: title: "TODO APPLICATION" #right_action_items: [["file-document", lambda x: app.setting_task_info_form()]] MDTabs: on_tab_switch: app.on_tab_switch(*args) Tab: text: "Create" orientation: "vertical" padding: "10dp" MDLabel: text: "TaskName" pos_hint: {"center_x": .5} MDTextField: id: taskname text: '' required: True pos_hint: {"center_x": .5} helper_text_mode: "on_error" halign: "center" MDLabel: text: "Description" pos_hint: {"center_x": .5} MDTextField: id: description text: '' pos_hint: {"center_x": .5} halign: "center" MDRaisedButton: id: button text: "SEND" pos_hint: {"center_x": .5} halign: "center" on_release: app.send() Tab: text: "Todo" ScrollView: MDList: id: todo text: "todo" Tab: text: "Doing" ScrollView: MDList: id: doing text: "doing" Tab: text: "Done" ScrollView: MDList: id: done text: "done" <Task> text: root.text secondary_text: root.secondary_text tertiary_text: root.tertiary_text IconLeftWidget: icon: "file-document-outline" on_release: app.change_task_status(root) IconRightWidget: icon: "delete" on_release: app.remove_widget(root) <DoneTask> text: root.text secondary_text: root.secondary_text tertiary_text: root.tertiary_text IconRightWidget: icon: "delete" on_release: app.remove_widget(root) 長くつらつらと書いていますが、構成は大まかに分けてこのようになっています。 KVの構成 MDBoxLayout(Root): - MDToolbar - MDTabs: - Tab × 4 <Task>: - IconLeftWidget - IconRightWidget <DoneTask>: - IconRightWidget なんでyaml形式なのかということはするどい視点だと思います。(いきなり何) 理由は単純で、KVの構成を描きやすいことと、単に私が好きなだけですw これにもとづいて説明していくと、まずルートウィジェットにMDBoxLayoutを 指定しています。んで、その中には毎度おなじみMDToolbar、さらにはMDTabs を入れ込んでいます。MDTabsにはTabが4つ配置しています。 そしてカスタムウィジェットにTask、DoneTaskウィジェットがあります。これは Icon(Left|Right)Widgetから察せられるとお目が高いです。MDListをまるまる 使っていますね。 おおまかに言うとこんな感じです。 MDBoxLayout(Root) ここからは、パッと見て何してんだこれ?というところや補足として過去投稿記事の リンクを載せていくために、ウィジェットごとに説明を繰り広げていきます。 まずは最初のあたりから。コードを再掲しておきます。 MDBoxLayout: orientation: "vertical" MDToolbar: title: "TODO APPLICATION" #right_action_items: [["file-document", lambda x: app.setting_task_info_form()]] 最初のところあたりは特に言うことでもないかもですが、コメントアウトしたところ は何をやっているか分からん状態になると思うので、少し補足をしておきます。これ はというと、最初は異なるウィジェットを考えていました。入力項目はタブにしないで ダイアログへと。初期構想は以下のようでした。 ですが、カスタムウィジェットが持っているタスク名や説明欄の内容がルートウィジェ ットに保持されなく、タブがうまく作れないという現象に陥っていました。いや、それ すら分からんわという方はニュアンスで受け取ってもらえればと思います。 ということがあったので、仕方なく入力項目をタブで作るということをしなければなら なかったということもありました。。 このへんに関しては以下リンクもしくは該当公式マニュアルを見てもらえればと思います。 さらに続きます。続きからのコードを再掲しておきます。 MDTabs: on_tab_switch: app.on_tab_switch(*args) Tab: text: "Create" orientation: "vertical" padding: "10dp" MDLabel: text: "TaskName" pos_hint: {"center_x": .5} (略) Tab: text: "Done" ScrollView: MDList: id: done text: "done" だいぶ端折ってる気はしますが、お気に留めなく。 まずは、on_tab_switchプロパティですが、これはタブをスイッチするときは 必要になるみたいです。マニュアルそんなこと書いてたっけな...となりましたが、 このバージョン(v0.104.2)ではこの仕様となっています。 そして次からは実際のタブが配置していますが、左から順にCreate -> Todo -> Doing -> Doneとなっています。Createで作ったあとはTodoでタスクが作られ、 ステータスを変更するときはDoing -> Doneと1方向になっています。これもどう なのかというご指摘はその通りかと思います。。時間オーバーでした。。 Createタブ自体はそれほど複雑ではなく、ラベルとテキストフィールド、ボタンが あるだけになります。ここも以下のリンクもしくは該当マニュアルを見てもらえれば それほど難しくはないかと思われます。 Createタブではorientationプロパティを指定していますが、これがないと面白い レイアウトになるので興味のある方は試してもらうとですね。再度試したところ、pad- dingプロパティはそれほどというか全然変わらんくて意味のないプロパティとなって います。。# なんで置いておいた 後のタブは共通していて、単にMDListらを配置していることになります。いつぞやの 投稿でMDListパターン!と馬鹿みたいにはしゃいでたのがまさにこれですね。 この辺りについては以下のリンクもしくは該当マニュアルをご覧ください。 ここまで見ると、最初の表示画面があぁなるほどねとなってくれるのを祈るばかりに なります。 Task&DoneTask これらは実はほとんど同じようなレイアウト&振る舞いをしています。というかもともと 同じものであったのですが、かくかくしかじか(投稿って便利)な理由で分けました。理由 については、pyファイルの方で説明するかもしれませんし、しないかもしれません。 ほとんど同じようなものなので、Taskウィジェットに絞ってコードを再掲しておきます。 見比べてもらうとわかり良いですが、DoneTaskウィジェットはIconLeftWidgetがない だけになります。 <Task> text: root.text secondary_text: root.secondary_text tertiary_text: root.tertiary_text IconLeftWidget: icon: "file-document-outline" on_release: app.change_task_status(root) IconRightWidget: icon: "delete" on_release: app.remove_widget(root) で、Taskウィジェットについてですが、こちらは伝えた通りほぼほぼというかまんま リストウィジェットになります。プロパティとしては3つの*textを持ち、それぞれ 必要な入力項目を入れるためにあります。そうでもないものもあるけど。 そしてリストの左に配置するアイコンとして、タスクのステータスを変更するもの。右に配置 するものとして、リストそのものを消去するためにあるアイコンがあります。コールバックメソ ッド自体がなんなのかは来週お伝えしようと思います。(急なネタバレ) ちゃんと作れると以下のようにリストが出来上がるようになります。 まとめ はい、ということで新年早々の投稿はいかがだったでしょうか。 個人的にはスムーズな出だしになったかと思われます(自意識過剰)。 すみません、力尽きました。。なので、実際に処理をおこなうところに関しては来週説明して いきたいと思います。ではまたお会いしましょう〜。 それでは、ごきげんよう。 参照 KivyMD https://kivymd.readthedocs.io/en/latest/ 時候の挨拶 1月(睦月:むつき) https://www.midori-japan.co.jp/letter/letter-manners/jikouaisatsu/4306
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

k-means++を実装してみる

k-means++とは k-meansの初期値の設定方法を改良したアルゴリズム。k-meansではセントロイドをランダムに選ぶため、うまくクラスタリングできないことがある。それを踏まえてk-means++では初期のk個のセントロイドをなるべく離す手法を取っている。セントロイドの選択が終了したら、通常のk-meansを行う。 セントロイドの選択方法 大まかな流れは下記の通り。 入力データからランダムに1つのデータを選択し、一つ目のセントロイドとする 下記の2~4の操作をセントロイドがk個決まるまで続ける 各データ点とセントロイドの距離を求める 各データ点とセントロイドの距離のうち最も近い距離を$d(x_{i})$とする 重み付き確率分布 $$ \frac{d(x_{i})^2}{\sum_{i} {d(x_{i})^2}} $$ を利用してデータの中から新たなセントロイドを選ぶ メモ $ \frac{d(x_{i})^2}{\sum_{i} {d(x_{i})^2}} $の確率分布は最も近い点が遠いほど選ばれる確率が高くなるイメージ このような選択方法はルーレット選択と呼ばれる 実装 実装していきます。 分類するデータ、k-meansの基本的なコードはk-means実装時(「k-meansを実装しながらnumpyを学ぶ」)と同じものを利用します。変更したのはセントロイドの初期化関数(init_centroid)のみです。 書籍「徹底攻略ディープラーニングE資格問題集」を参考にしています。 def init_centroid(X, n_data, k): np.random.seed() # 1つ目のセントロイドをランダムに選択 # random.choice(データ, 選択する個数) -> indexが取得される idx = np.random.choice(n_data, 1) centroids = X[idx] # クラスタごとに繰り返す for i in range(k - 1): # 各データの点とセントロイドの距離を計算 # 1回目はデータ数行×1列、2回目はデータ数行×2列...になる distances = compute_distance(X, len(centroids), n_data, centroids) # 各データ点と最も近いセントロイドとの距離の二乗を計算 # 1回目は全データと1つ目のセントロイドの距離、2回目以降は最も近いセントロイドとの距離 # 帰ってくる結果は常に1行×データ数列 closest_dist_sq = np.min(distances ** 2, axis = 1) # 距離の2乗を足す weights = closest_dist_sq.sum() # 0以上1未満([0,1))の乱数(1つ)と上の距離の二乗和をかける rand_vals = np.random.random_sample() * weights # 距離の二乗の累積和を計算 -> np.cumsum(closest_dist_sq) # 累積和とrand_valの値が最も近いデータ点のindexを取得 # 累積していったときにrand_valは何回目の累積と一番近いか求めてるイメージ candidate_ids = np.searchsorted(np.cumsum(closest_dist_sq), rand_vals) # 選ばれた点をあらたなセントロイドとして追加 centroids = np.vstack([centroids, X[candidate_ids]]) return centroids 結果 k-meansに比べるとかなり精度が上がりました。各セントロイドが離れて作成されていることもわかります。 何度か実行してみました。多少の誤分類はありますが、基本的に均等に4つのクラスタに分類されています。1回目にランダムで選択するセントロイドによって精度が変わるようです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

APIを利用して自分の投稿したQiita記事のサマリーを取得する

 はじめに 年があけ、突然昨年自分がどれくらい記事を書いたのか気になりはじめてしまい、本来なら年末くらいに、やってみたくなるような内容の記事となりました。 以前から気になっていたQiitaAPIも叩けたので概ね満足、さっそくハンズオンしていきます。  参考資料 参考リンクには今回作成した大元になる方のハンズオンです。QiitaAPIを利用する部分についてご参考にさせていただきました、なによりお手本があると大変助かり理解も捗りました。 自分の投稿したQiita記事のPV数をAPIで取得する [多分Python] ちょうど100記事目なので、記事データを引っこ抜いてきて簡単なランキングを作って、コメントしてみる [Pandas] 参考と言うにはおこがましいですが、Qiita公式APIの仕様リンクはも記述しておきます Qiita Developer公式 Qiita API v2仕様についての説明  構成図  ハンズオン 0:事前準備(Qiita_APIで利用するトークンの取得) 1:Qiitaの自分のアイコンを選択して設定を押下する 2:『アプリケーション』から『個人用アクセストークン』の『新しくトークンを発行する』を押下する 3:『個人用アクセストークン』にトークンが発行されるので、以下APIを利用する際に使用するので任意の場所に保存する 1:コードの記述 サンプルコード(コメントアウトで各動作の内容記述あり) qiita_article_acquisition.py import requests #リクエスト import json #json利用のため使用 import os #環境変数 from dotenv import load_dotenv #.env利用 load_dotenv() # 環境変数 Qiita_token = os.getenv('QIITA') #トークン直打ちでも可能 # エンドポイント endpoint_url = "https://qiita.com/api/v2/authenticated_user/items" # 記事データ取得APIのURL detail_endpoint = 'https://qiita.com//api/v2/items/' #詳細記事データ取得APIのURL # Qiita_apiを送る際のheaderとparam header = { "Authorization": "Bearer " + Qiita_token, #Authorizationヘッダーでアクセストークンを付与 "content-type": "application/json",# json形式 "charset": "utf-8" # 文字コード } param = { "page": "1",# 取得するページ番号 "per_page": "100"# 上記ページに含まれる要素数(今回で言えば記事の数) } # データ取得した項目をリストにする page_views = [] # リスト # 記事データ取得APIより、自分の記事IDの取得(page_views_countがNoneのため利用) def my_article_id_acauisition(): qiita_info = requests.get(endpoint_url, params=param, headers=header) qiita_obj = qiita_info.json() #qiita_objに自分の記事情報を代入 for item in qiita_obj:#自分の記事情報を利用して、詳細記事データ取得 detail_endpoint_url = detail_endpoint + item["id"] detail_qiita_info = requests.get(detail_endpoint_url, params=param, headers=header) detail_qiita_obj = json.loads(detail_qiita_info.text) title = item["title"] url = item["url"] tags = item["tags"] created_at = item["created_at"] likes_count = item["likes_count"] page_views_count = detail_qiita_obj['page_views_count'] page_views.append({ #リスト化(sortなどの利用を考慮) "title": title, "url": url, "tags": [ tag["name"] for tag in tags ], "created_at": created_at, "likes_count": likes_count, "page_views_count": page_views_count }) for page_view in page_views: #リストにした内容を表示させるための記述 print(f"""page_view: {page_view['page_views_count']} title: {page_view['title']} url: {page_view['url']} tags: {page_view['tags']} likes_count: {page_view['likes_count']} created_at: {page_view['created_at']}\n""") if __name__ == "__main__": #qiita_article_acquisition.pyが呼び出された場合、関数を呼び出す my_article_id_acauisition() 2:部分的な説明 2.1 今回利用するライブラリをインストールする qiita_article_acquisition.py # ライブラリのインポート import requests #リクエスト import json #json利用のため使用 import os #環境変数 from dotenv import load_dotenv #.env利用 load_dotenv() 2.2 .envの環境変数を代入する qiita_article_acquisition.py # 環境変数 Qiita_token = os.getenv('QIITA') #トークン直打ちでも可能 .envファイルをから、load_dotenvでファイルの中身を読み取り『0:事前準備(Qiita_APIで利用するトークンの取得)』で用意したトークンを環境変数として読み込む ディレクトリの構成 . ├── .env └── qiita_article_acquisition.py 2.3 関数で利用するの変数を代入する qiita_article_acquisition.py # エンドポイント endpoint_url = "https://qiita.com/api/v2/authenticated_user/items" # 記事データ取得APIのURL detail_endpoint = 'https://qiita.com//api/v2/items/' #詳細記事データ取得APIのURL 2.4 Qiita_APIを利用する際にheaderの設定、paramの設定 Qiita APIを呼び出すために『2.2 .envの~』で代入した値を、Authorizationヘッダーでアクセストークンを付与する qiita_article_acquisition.py # Qiita_apiを送る際のheaderとparam header = { "Authorization": "Bearer " + Qiita_token, #Authorizationヘッダーでアクセストークンを付与 "content-type": "application/json",# json形式 "charset": "utf-8" # 文字コード } param = { "page": "1",# 取得するページ番号 "per_page": "100"# 上記ページに含まれる要素数(今回で言えば記事の数) } # データ取得した項目をリストにする page_views = [] # リスト 2.5 my_article_acauisition()関数 qiita_article_acquisition.py # 記事データ取得APIより、自分の記事IDの取得(page_views_countがNoneのため利用) def my_article_acauisition(): qiita_info = requests.get(endpoint_url, params=param, headers=header) qiita_obj = qiita_info.json() #qiita_objに自分の記事情報を代入 APIドキュメントよりpage_views_countが取得できそうだったのですが、jsonで返ってくる値がNoneだったため調査をする。 詳細記事を利用することでpage_views_countを取得できそうだったので、ここでの動きとしては記事個別に割り振られているIDを取得するための動きをしていると理解する qiita_article_acquisition.py for item in qiita_obj:#自分の記事情報を利用して、詳細記事データ取得 detail_endpoint_url = detail_endpoint + item["id"] detail_qiita_info = requests.get(detail_endpoint_url, params=param, headers=header) detail_qiita_obj = json.loads(detail_qiita_info.text) 詳細記事のエンドポイントと詳細記事データ(記事個別のID)を併せて、requests.get(detail_endpoint_url, params=param, headers=header)して、json.loads()でJSONファイルを読み込む qiita_article_acquisition.py title = item["title"] url = item["url"] tags = item["tags"] created_at = item["created_at"] likes_count = item["likes_count"] page_views_count = detail_qiita_obj['page_views_count'] JSONより取得した値を代入する。page_views_count = detail_qiita_obj['page_views_count']のみ、取得するリストの場所が他の項目と異なっている qiita_article_acquisition.py page_views.append({ #リスト化(sortなどの利用を考慮) "title": title, "url": url, "tags": [ tag["name"] for tag in tags ], "created_at": created_at, "likes_count": likes_count, "page_views_count": page_views_count }) 取得した値をpage_views = []に.appendで要素を追加する。 "tags"は投稿の際に付与したタグの分だけ表示されるので、タグの名称だけを表示できるようにする 今後likes_countが多いものからなどのsort利用を考えて記述、いまのところ無くても差し支えない qiita_article_acquisition.py for page_view in page_views: #リストにした内容を表示させるための記述 print(f"""page_view: {page_view['page_views_count']} title: {page_view['title']} url: {page_view['url']} tags: {page_view['tags']} likes_count: {page_view['likes_count']} created_at: {page_view['created_at']}\n""") リストの内容の表示のされ方、今回は前述のsort利用もないので表示のされ方は最新の投稿記事順に表示される qiita_article_acquisition.py if __name__ == "__main__": #qiita_article_acquisition.pyが呼び出された場合、関数を呼び出す my_article_acauisition() qiita_article_acquisition.pyが呼び出された場合、関数を呼び出すための記述(おまじない的になっているけれど記述) 3:挙動の確認 ターミナルで押下する tetutetu214@mbp 0_Qiita_hanson % python qiita_article_acquisition.py page_view: 371 title: APIを利用して場所名の緯度経度から近隣の店舗を表示する構築 url: https://qiita.com/i3no29/items/2aabb51ca0eb6f08f700 tags: ['Python', 'api'] likes_count: 2 created_at: 2022-01-01T14:02:44+09:00 page_view: 353 title: 弊社ブログページが更新されたらLINE NotifyによりLINE通知の構築 url: https://qiita.com/i3no29/items/58f52dc315a6485939bd tags: ['Python', 'スクレイピング', 'LineNotify'] likes_count: 1 created_at: 2021-12-25T21:24:24+09:00 page_view: 375 title: 電車遅延情報をスクレイピング(Python)で、LINE NotifyによりLINE通知の構築 url: https://qiita.com/i3no29/items/c8e9363bd12a61228f82 tags: ['Python', 'スクレイピング', 'LineNotify'] likes_count: 0 created_at: 2021-12-20T07:35:16+09:00 ※※※※※※※※※※※※※※※※以下省略※※※※※※※※※※※※※※  さいごに page_viewなどを見ることが出来るようになると『投稿してやるぞ!』という自分のモチベーションアップにもつながり、意外と構築してみて良かったかなと思いました。なぜpage_views_countが取得できないのかなど、追求しないといけないところもありますが、来週あたりにでも再び手を動かしながらコード理解を深めていきたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pandas csvファイルにある縦持ちデータを連続で読み込んで横持ちデータへ変換する。

0. はじめに pandasをつかって、複数のcsvファイルに入っている縦持ちデータを連続で読み込み、横持ちデータへ変換する方法についてまとめる。 処理の概要 Step0. ライブラリの読み込み・データフレーム作成 Step1. データ読み込み   ・ os.walkでメインディレクトリ内のcsvファイルを取得   ・ pandas.read_csvで取得したcsvファイルを読み込む Step2. データ積み上げ   ・ pandas.concatで読み込んだcsvファイルを積み上げる     (Step1を行いながらStep2の処理を実施) Step3. 横持ち変換   ・ pandas.pivot_tableで縦持ちになってるデータフレームを横持ちへ変換 ディレクトリ Main_dir/  ├ file1.csv  ├ file2.csv  └ file3.csv Step0 ライブラリの読み込み・データフレーム作成 import pandas as pd import os df = pd.DataFrame() Step1と2 データ読み込み・データ積み上げ for foldername, subfolders, filenames in os.walk('/Main_dir'): pass for subfolder in subfolders: pass for filename in filenames: if filename.endswith('.csv'): read_df = pd.read_csv(filename) if df.empty: df = read_df else: df = pd.concat([df, read_df]) # ## Data A i 1 A ii 2 A iii 3 B i 4 B ii 5 B iii 6 C i 7 C ii 8 C iii 9 Step3.横持ち変換 pivoted_df = pd.pivot_table(df, values ='Data', index = ['##'], columns=['#']) ## A B C i 1 4 7 ii 2 5 8 iii 3 6 9 参考文献 Wes McKinney 著 瀬戸山雅人、小林儀匡、滝口開資 訳 (2018) Pythonによるデータ分析入門 第2版、オライリー・ジャパン、オーム社
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LightGBMによる物性予測モデルの計算過程を視覚化する

はじめに LightGBM の中身について理解するためにやってみたメモ 環境 本記事では以下を事前にインストールしておく必要がある。 Graphviz Python numpy pandas matplotlib scikit-learn LightGBM RDKit Graphvizについては、ツールをインストールした後、Pythonのライブラリをインストールする必要がある。Python: LightGBM の決定木を可視化して分岐を追ってみる や、【Windows10】Graphvizのインストール等を参考にしてほしい。 モデル構築 まずはRDKitを使ってもモデルを作成してみよう。 まずはモジュールのインポート import numpy as np import pandas as pd from matplotlib import pyplot as plt from sklearn.model_selection import train_test_split from rdkit import rdBase, Chem from rdkit.Chem import AllChem, Descriptors from rdkit.ML.Descriptors import MoleculeDescriptors import lightgbm as lgb from lightgbm import LGBMRegressor import graphviz 続いて、smiles列のあるcsvファイルからデータを読み込み記述子計算および、目的変数の取得を行う。 # ファイルの読み込み import codecs with codecs.open('../datas/raw/delaney-processed.csv', 'r', 'utf-8', 'ignore') as f: df = pd.read_csv(f) # SMILESのデータを取り出す smiles_list = df["smiles"].values # SMILESのリストをMOLに変換 mols = [] for i, smiles in enumerate(smiles_list): mol = Chem.MolFromSmiles(smiles) if mol: mols.append(mol) else: print("{0} th mol is not valid".format(i)) # RDKitの記述子計算 descriptor_names = [descriptor_name[0] for descriptor_name in Descriptors._descList] descriptor_calculator = MoleculeDescriptors.MolecularDescriptorCalculator(descriptor_names) rdkit_descriptors_results = [descriptor_calculator.CalcDescriptors(mol) for mol in mols] df_rdkit = pd.DataFrame(rdkit_descriptors_results, columns = descriptor_names, index=names_list) # 目的変数の取得 df_t = df["measured log solubility in mols per litre"] 学習データとテストデータに分割しよう。 # 学習データとテストデータに分割 df_train_X, df_test_X, df_train_t, df_test_t = train_test_split(df_rdkit, df_t, test_size=0.3, random_state=0) ここでのポイントはpandasのデータフレームの状態で分割することである。 pandasデータフレームのままLightGBMで学習を行うと可視化の際にpandasの列名が特徴名として表示されるので大変便利である。 最後に学習してモデルを構築しよう。今回は計算過程を追いやすくするため、決定木の数および深さはそれぞれ3としている。 model = LGBMRegressor(n_estimators=3, max_depth=3) model.fit(df_train_X, df_train_t) model.score(df_train_X, df_train_t) スコアは低いものの、これで準備はととのった。 predictメソッドによる計算過程の確認 テストデータの1件目の化合物で予測してみよう。 model.predict([df_test_X.values[0]]) -2.89344208 と出た。勾配ブースティングの原理から、3つの決定木の計算結果の和が最終の予測結果になることは想像できる。 predictメソッドを使ってこれを確認してみよう。 ここで役に立つのが predictメソッドの start_iteration と num_iteration オプションである。 start_iteration は何番目から後の決定木を使って予測を行うかを指定する。 num_iteration はstart_iterationから数えて何個分の決定木を使って予測を行うかを指定する。 従ってstart_iteration=<決定木のインデックス>, num_iteration=1を指定することで、ぞれぞれの決定木だけを使った予測値を得ることができる。 1番目の決定木による予測結果 まずは1番目の決定によるの予測結果を求めてみよう model.predict([df_test_X.iloc[0, :]], start_iteration=0, num_iteration=1) -2.97228578 となる。 2番目の決定木による予測結果 つづいて2番目の決定木による予測結果を求めてみよう model.predict([df_test_X.iloc[0, :]], start_iteration=1, num_iteration=1) 0.0604661 3番目の決定木による予測結果 最後に3番目の決定木による予測結果を求めてみよう model.predict([df_test_X.iloc[0, :]], start_iteration=2, num_iteration=1) 0.0183776 確認 最後にこれらを和をもとめて、予測値を求めてみよう print(-2.97228578 + 0.0604661 + 0.0183776) -2.89344208 となり、引数なしのpredictの結果と同じ値が得られる。 決定木の可視化による計算過程の確認 予測値が複数の決定木の計算結果の和になることは分かったが、それぞれの決定木はどうなってるのかを見ていこう。 このサイト にあるようにplot_treeメソッドにより可視化することができる。 rows = 3 cols = 1 fig = plt.figure(figsize=(30, 24)) # 一本ずつプロットしていく for i in range(rows * cols): ax = fig.add_subplot(rows, cols, i + 1) ax.set_title(f'Booster index: {i}') lgb.plot_tree(booster=model.booster_, tree_index=i, show_info='internal_value', ax=ax, ) plt.show() 1番目の決定木 1番目の決定木はこんな感じである。 pandasのデータフレームをLightGBMの学習メソッドの引数に与えたため、図に表示される説明変数がRDKitの記述子になっていて分かりやすいと思う。 決定木に利用されている対象化合物の記述子の値は以下となっており、これを上の決定木にあてはめていくことで、predictメソッドで算出した値と同じ, -2.972 という値が得られる。 MolLogP = 1.8034 BertzCT = 59.277302439017085 HeavyAtomMolWt = 100.07599999999998 MinPartialCharge = -0.3904614361418555 MaxPartialCharge = 0.05937549424601595 Chi4n = 0.9990707667627815 2番目の決定木 2番目の決定木は以下通りであるが、これも同様にpredictメソッドで算出した値と同じ, 0.060が得られる。 3番目の決定木 3番目の決定木は以下通りであるが、これも同様にpredictメソッドで算出した値と同じ, 0.018が得られる。 1番目~3番目の決定木の計算結果を足し合わせると最終的な予測値になることが分かる。 決定木のテキスト情報による計算過程の確認 決定木の数が何百となってくると、可視化して追っていくことは現実的ではない。 そこで、決定木の情報を解析しながら、テキストで情報を表示してみよう。 LightGBMのBoosterオブジェクトのdump_modelというメソッドを使うとjson形式でモデルの情報が得られる。 このjsonを解析することでモデル情報を得ることができる。 一例として決定木の情報をテキストで出力する関数を作ってみる。 # 決定木をテキスト情報として出力する。 # 特徴名の取得 feature_names = model.booster_.dump_model()["feature_names"] # 決定木をルートノードから再帰的にたどり出力するメソッド def print_tree_node(node): if 'split_index' in node: # non-leaf print("feature = " + feature_names[node["split_feature"]] + ", threshold = " + str(node["threshold"]) + ", decision_type = " + node["decision_type"]) print_tree_node(node['left_child']) print_tree_node(node['right_child']) else: print("leaf_value = " + str(node["leaf_value"])) 各項目の意味は lgb.model.dt.tree: Parse a LightGBM model json dump を参考にしてほしい。 これを使って3番目の決定木の情報を出力してみる。 tree = model.booster_.dump_model()['tree_info'][2]['tree_structure'] print_tree_node(tree) するとルートノードから順に以下のような出力が得られ、先ほど可視化した決定木と同じ情報が得られることが分かる。 feature = MolLogP, threshold = 2.7503500000000014, decision_type = <= feature = Chi4n, threshold = 0.8627604410602785, decision_type = <= feature = MolLogP, threshold = 1.1729500000000004, decision_type = <= leaf_value = 0.2197740837776413 leaf_value = 0.10397693878492793 feature = MolLogP, threshold = 1.0000000180025095e-35, decision_type = <= leaf_value = 0.14760532911866905 leaf_value = 0.018377602562091498 feature = MolLogP, threshold = 4.946240000000005, decision_type = <= feature = MolLogP, threshold = 3.715500000000003, decision_type = <= leaf_value = -0.07255746476103862 leaf_value = -0.1636293624306009 feature = MaxPartialCharge, threshold = 0.15543544661242248, decision_type = <= leaf_value = -0.3890352931889621 leaf_value = -0.2387371290297735 参考 Python: LightGBM の決定木を可視化して分岐を追ってみる 【Windows10】Graphvizのインストール lgb.model.dt.tree: Parse a LightGBM model json dump https://lightgbm.readthedocs.io/en/latest/R/reference/predict.lgb.Booster.html
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

心霊写真もインスタ映え? 暗い画像を明るくする機械学習がすごい

明度の低い画像を明るくしてくれる機械学習モデルを使う方法です GLADNetというモデルを使って、暗い画像を明るくします。 Pythonでかんたんに実行できます。 暗い画像から情報を取り出したい 暗くて上手く取れなかった画像や、夜の監視カメラや車載カメラの画像を明るくできれば、 役に立つ情報が取り出せる場合があります。 しかし、単にフィルターで明度を調整しても、情報は増えません。 GLADNetで綺麗に明るくなる GLADNetに画像を入れるだけで、明るい画像が返ってきます。 これが、本当に明るくしたみたいに見えます。 使用方法 GLADNetのリポジトリをクローンし、以下のコマンドで実行できます。 python main.py --use_gpu=1 --gpu_idx=0 --gpu_mem=0.5 --phase=test --test_dir=path/to/input_images --save_dir=path/to/output さまざまな用途に こういった手法を使うことで、必要な情報が取り逃がしなく取得でき、 心霊写真も怖くなくなりま ? フリーランスエンジニアです。 お仕事のご相談こちらまで rockyshikoku@gmail.com Core MLやARKitを使ったアプリを作っています。 機械学習/AR関連の情報を発信しています。 Twitter Medium
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

macOS Monterey (バージョン12.1) Python3.8.0がinstallできなかったときの対処法

macOS バージョン12にて、下記記事を参考にPython3.8.0をinstallしようとした際にエラーが発生 エラー内容 % pyenv install 3.8.0 //省略 error: implicit declaration of function 'sendfile' is invalid in C99 [-Werror,-Wimplicit-function-declaration] ret = sendfile(in, out, offset, &sbytes, &sf, flags); 試したこと https://github.com/pyenv/pyenv/issues/1643 上記のgithub issuesを読むと、homebrewからこのエラーのパッチが出てるとのことなので、参考にターミナルで以下のコマンドを入力 pyenv 3.8.0のインストール % CFLAGS="-I$(brew --prefix openssl)/include -I$(brew --prefix bzip2)/include -I$(brew --prefix readline)/include -I$(xcrun --show-sdk-path)/usr/include" LDFLAGS="-L$(brew --prefix openssl)/lib -L$(brew --prefix readline)/lib -L$(brew --prefix zlib)/lib -L$(brew --prefix bzip2)/lib" \ pyenv install --patch 3.8.0 < <(curl -sSL https://github.com/python/cpython/commit/8ea6353.patch\?full_index\=1) pyenv 3.8.3のインストール % CFLAGS="-I$(brew --prefix openssl)/include -I$(brew --prefix bzip2)/include -I$(brew --prefix readline)/include -I$(xcrun --show-sdk-path)/usr/include" LDFLAGS="-L$(brew --prefix openssl)/lib -L$(brew --prefix readline)/lib -L$(brew --prefix zlib)/lib -L$(brew --prefix bzip2)/lib" \ pyenv install --patch 3.8.3 < <(curl -sSL https://github.com/python/cpython/commit/8ea6353.patch\?full_index\=1) pyenv version コマンドでpyenvの3.8.0と3.8.3がインストールできているのを確認 pyenv global 3.8.0でバージョン切り替えを行い、ターミナルを再起動 python -Vでバージョン確認 % python -V Python 3.8.0 無事Python3.8.0のinstall成功
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【あったら便利】loggingは大事なのに書き方をいつも忘れてしまうのでメモ

目的 pythonのlogging機能を使ってエラーが起きたときなどにエラーログ等をターミナルに出力、ファイルに出力したいと思ったが、自分からするとちょっと複雑でなかなか覚えることができないため、ここに簡単に頻繁に使うものを記述していきたい。 概要 logging 下記全てのlogの親となるようなもの。 このloggingの設定はグローバル設定のようなもので、この設定を変更すると下記のloggerなどの設定よりもとにかく一番優先される このloggingは複数作らない。 loggers loggingから派生されたloggingの子供、クラス(logging)に対する各インスタンスのようなもの。 handlers loggerの設定を扱う設定親のようなもの。 例えばconsoleに出力する用のlogger、ログファイルに出力する用のloggerと設定を変更したい場合に分けると良い。 ログファイルにはwarning/errorのみ出力するが、consoleにはdebugやinfoなども出力したい場合などにはAのloggerにconsole_handler,file_handlerなどをアタッチするイメージ formaters handlerにアタッチするための、出力するログのフォーマット詳細を設定するもの。 二つのloggingの設定方法 ログ出力する文字などと同じファイルにログ設定を書く方法 下記のようにすることでシンプルに素早くログの実装が可能となる。 import logging # loggingの基本的な設定 logging.basicConfig( level = logging.WARNING, filename='sample.log', filemode='w', format='%(asctime)s-%(process)s-%(levelname)s-%(message)s' ) # 例 obj = self.get_object() # loggingを利用して上記のobjの中身を調べるために出力する logging.info(obj) setting.pyにログ設定を書く方法 実装部分にログの設定を書くと複雑になり可読性が落ちるため設定だけsettings.pyに記述し、実際の実装部分には最低限のログ出力実装しか記述しない。 # 例 obj = self.get_object() # test_loggerの設定を呼び出す logger = logging.getLogger('test_logger') # test設定でobjをログ出力する logger.info(obj) settings.py LOGGING = { 'version': 1, 'formatters': { # 全体に適用されるフォーマットを指定 },    # handlerの設定 'handlers': {      # file_handlerの設定 'file': { }, # console_handlerの設定 'console': { # consoleへの出力設定 }, }, 'loggers': { # loggingの子としてchildというloggerを定義 'child': { # child_loggerが利用するhandlerと出力するlogのレベルを記載 'handlers': ['file', 'console'], # debug以上のレベルのログを出力する 'level': 'DEBUG', }, }, } そして実際に出力したいログ実装ファイルは下記である。今回はdjango_restframeworkのviews.pyでログ出力しようと考えているため、下記のようなログ出力にしている。 views.py def error_handling(self, request, pk=None): try: # とあるテーブルのobject(レコード)を取得。何が入っているか常に知りたい。 obj = self.get_object() # test_loggerのログ設定を利用 logger = logging.getLogger('test_logger') # test_loggerの設定でobjを出力 # test_loggerではdebug以上のログは出力するように設定されているため下記のinfoログも出力される logger.info(obj) serializer = self.get_serializer(obj) return Response(serializer.data) except: raise NotFound 具体例(setting.pyにログ設定を書く方法) やはりログ出力の設定であるし、実装ファイルを複雑にしたくないためsetting.pyにログ出力設定は記述したいため、基本的にはsetting.pyにログ設定を書くこととする。 setting.py LOGGING = { 'version': 1, 'formatters': { # 全てのloggerに適用されるフォーマットを指定 'all': { # このフォーマット名は後にhandlerによって利用される 'format': '\t'.join([ "[%(levelname)s]", "asctime:%(asctime)s", "module:%(module)s", "message:%(message)s", "process:%(process)d", "thread:%(thread)d", ]) }, }, 'handlers': { # ファイル出力設定 'file': { 'level': 'DEBUG', # DEBUG以上のレベル設定のログを出力する 'class': 'logging.FileHandler', # 元々用意されているファイル出力のためのクラス 'filename': os.path.join(BASE_DIR, 'test.log'), # 出力先のファイルパス 'formatter': 'all', # 初めに記述したallというformatで出力 }, 'console': { # console出力設定 'level': 'DEBUG', # 元々用意されているconsole出力のためのクラス 'class': 'logging.StreamHandler', # 初めに記述したallというformatで出力 'formatter': 'all' }, }, 'loggers': {      # testという名前のloggerをloggingから定義 'test': { # 上に記述した二つのhandlerをloggerにアタッチする 'handlers': ['file', 'console'], # グローバルが優先されるがloggerごとにもログ出力レベルを定義 'level': 'DEBUG', }, }, } 参考文献 pythonのlogger奮闘記 ~簡単な使い方から複数ファイルを跨る使い方まで~
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Project Euler】Problem 12: 約数の多い三角数

本記事はProjectEulerの「100番以下の問題の説明は記載可能」という規定に基づいて回答のヒントが書かれていますので、自分である程度考えてみてから読まれることをお勧めします。 問題 12. 約数の多い三角数 原文 Problem 12: Highly divisible triangular number 問題の要約:約数の数が初めて500を超える三角数を求めよ まず三角数(Wikipedia)にあるように$n$番目の三角数$T_n$は$1$から$n$までの和なので、 T_n = n(n+1)/2 まずsympyのdivisorsを使うと、とても簡単プログラムになります。 import sympy import itertools for n in itertools.count(1): dn = len(sympy.divisors(n*(n+1)//2)) if dn>500: break print(f" n={n}, Tn= {n*(n+1)//2}, divisors={dn}") ただし、これだとすべての三角数の約数を求めるので速いとは言えません。 ここで約数の個数(Wikipedia)を求める公式を使います Nの素因数分解を \\ \large N = p_1^{a_1} p_2^{a_2} \dots p_k^{a_k} \\ とし、約数の個数を d(N)とすると\\ d(N)=(a_1+1)(a_2+1) \dots (a_k+1) 要は素因数分解さえできれば約数を全部列挙しなくても数だけを求めることができるということです。素因数分解はsympyのfactorintでDICT形式で求めることが出来ます。 from sympy import factorint for i in [4,5,6]: print(sympy.factorint(i)) #{2: 2} #{5: 1} #{2: 1, 3: 1} 約数の個数の公式を元にfactorintで素因数分解されたDICT形式から約数の個数を求める関数fct2divnは以下のようになります。 import numpy as np # Factors to number of disivors def fct2divn(fct): return np.prod(np.array(list(map(lambda x: x+1,list(fct.values()))))) print(fct2divn({2: 1, 3: 1})) # 4 そこで問題の三角数$T_n$の約数の数ですがポイントは以下になります。 $n$と$n+1$は共通の素因数がない(互いに素) 従って$d(n \times (n+1)) = d(n) \times d(n+1)$ $n$と$n+1$はどちらかが偶数なのであらかじめ2で割っておける 例えば$n$が偶数の時$d(Tn) = d(n/2) \times d(n+1)$となる これをプログラムに実装すると以下のようになります。divisorsとfactorintのプログラムの実行時間の比較表を見るとかなり速くなっているのが分かります。 dn = 1 for n in itertools.count(1): n1 = (n+1)//2 if (n+1)%2 == 0 else n+1 dn1 = fct2divn(factorint(n1)) dnt = dn * dn1 if dnt>500: break dn = dn1 print(f" n={n}, Tn= {n*(n+1)//2}, divisors={dnt}") N 500 1000 2000 3000 5000 divisors 0s 5s 133s x x factorint 0s 0s 8s 13s 90s
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む