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

【Python】後退解析を用いてゴブレットゴブラーズの必勝を解析する

はじめに 前回の記事で○×ゲームの必勝を判定するプログラムを作りました → 【Python】ミニマックス法を用いて○×ゲームの必勝を判定する 今回の記事では前回の記事での考察を活かしながら、コンピュータの力を借りてゴブレットゴブラーズというボードゲームについて必勝を解析する。 ゴブレットゴブラーズとは ○×ゲームに少し複雑さを取り入れ、ゲーム性を高めたボードゲームである。→本家サイト 株式会社QuizKnockが紹介している動画がこちら → 進化した〇×ゲームが奥深すぎる【東大ボドゲ】 ルールについて簡潔な解説をする。詳しくは上記動画や制作元リンクを参照 一対一で対戦するボードゲームであり、二人はそれぞれ大中小3種類の駒が2つずつ(計6つ)の駒が与えられる。3x3のマス目のあるボードに交互に駒を置いていき、縦横斜めいずれかの一列が揃った方のプレイヤーが勝利となる。 また場に置いている駒より大きい駒であれば被せて置くことができ、既に場に置いてある駒を動かして異なるマスに再び置くこともできる。被せられている駒は動かせず、勝利条件に使うこともできない。 またルールには載っていないが便宜上、一回の対戦で同じ局面が二度出現した場合に千日手=引き分けとするという決まりを追加している。 概要 実行環境はPython3.8 Google ColabのJupyterNotebook(メモリ25.46GB)上で行った。 当初は前回の記事で使用したミニマックス法によって探索を行っていたがうまく行かず、調べたところ後退解析による解法が一般的らしかったのでこちらをメインのアルゴリズムとして採用した。 また持ち駒や千日手の概念があるという共通点から、どうぶつしょうぎの必勝解析を大いに参考にし実装を行った。 考察の手順 1.盤面数の考察 ①一種類のコマしかない場合(ここでは自分の大の駒2つ+相手の大の駒2つのみの場合)について考える。 このとき自分の駒の置き方は、盤面上にある自分の駒の数を自然数$k$と置くと、${}_9C_k$と表せる。 更に盤面上にある相手の駒の数を自然数$l$と置くと、相手の駒を含めた置き方は $${{}_9C_k} _{9-k}C_l = \frac{9!}{k!l!(9-k-l)!}$$ と表せる。このとき$k$, $l$は $0\leqq{k, l}\leqq2$ の範囲で値をとるので、場合の数は全部で $$\sum_{k=0}^2\sum_{l=0}^2\frac{9!}{k!l!(9-k-l)!}$$ となり、これを計算すると$1423$となる。よって、一種類の駒のみの場合1,423通りの置き方があることがわかった。 ②1の結果を用いて全てのコマがある場合について考える。 少し考えてみると、ある種類のコマの配置は違う種類の駒の配置に依存しないことがわかる。 例えば小さい駒と大きい駒が同じマスに配置されるとき、小さい駒に大きい駒が被せてあると考えることができるため、重複の問題が発生しない。よって異なる種類の駒はそれぞれ独立していると考えることができる。 したがって盤面の総数は$1423^3$=2,881,473,967通りと考えることができる。 また、本来であればこれに先手後手の情報が加わるため盤面の総数*2が全状態数となるが、後手の局面というのは敵味方の駒を全て入れ替えた先手の局面として扱うことができるため、考慮する必要がない。(これは前回の記事では気づけなかった反省点である) ここで求めたものは局面の番号付けに使う。局面数だけの長さの配列を作り、局面に一対一で対応する番号に評価値を格納していく。これにより各局面での勝敗を記録していく。 ※注意すべき点としてこれはあくまで場合の数の最大に過ぎず、実際には対称な局面や到達し得ない局面が含まれているため、探索が必要な局面数は計算結果より少なくなると考えられる。 しかしこれらを除いた局面数を求めるにはより高度な数学的考察が必要となり、更に28億程度であれば配列を作成するためのメモリを十分確保することができたため、これ以上は工夫せず$1423^3$通りとして考察をすすめる。 2.後退解析を用いて局面の勝ち負けを確定していく こちらの論文を参考にして実装を行った。 ( 1 )勝負のついた局面の集合から開始する. ( 2 )勝負のついた局面の1手前の局面を求める. •(手番のプレイヤーの)負け局面から1手前の局面は(手番のプレイヤーの)勝ち局面 •勝ち局面から1手前の局面の勝敗が未確定の時は,そこから可能な手がすべて勝ち局面に移行する時は,負け局面とする. ( 3 )操作を繰り返して,勝ち局面の集合も負け局面の集合がそれ以上増えなくなったら終了する. - 「どうぶつしょうぎ」の完全解析田 中 哲 朗より引用 こちらの文献を参考にさせて頂きながら、以下のような手順で後退解析を行った。 まず勝負のついた局面の集合は、先ほど求めた$1423^3$全ての局面について終了局面かどうかの判定を行うことで求める。また勝敗が確定した局面に対称な局面があれば同時に確定させ、探索回数を減らす。 この条件で計算した結果、終了局面は644,016,624通りと求められた。 次に1で求めた終了局面の集合からBFS(幅優先探索)を行うことにより、1手前の局面を求めていく。 ここでの注意点として、他の駒に隠れている駒は動かせないということと、持ち上げた瞬間に相手の駒が一列揃ってしまうような駒は持ち上げた瞬間負け=動かせないということに気をつける。 操作を繰り返して,勝ち局面の集合も負け局面の集合がそれ以上増えなくなったら終了する。 解析結果 終了局面の探索に約6.8時間、すべての局面の勝敗を求めるのに約1日を費やした結果、先手必勝であることがわかった。初手局面から両者が最善手を指し続けた場合、13手で先手が勝利する。 3. 解析結果の検討 各局面の終了局面までの局面数 それぞれの局面を開始局面として、先手後手が最善手を指し続けた場合に終了局面までかかる手数を求めた。結果、このような内訳となった。 なお、この局面の中には初手局面から到達できないものも含まれている。また明らかに終了局面を過ぎているような局面は「到達不可能」として除外してある。 終了局面までの手数 総局面数 0(終了局面) 644,016,624 1 1,228,663,244 2 121,570,146 3 59,679,998 4 31,619,662 5 18,065,938 6 10,012,296 7 6,323,572 8 3,359,624 9 2,174,189 10 1,030,263 11 630,495 12 298,247 13(初手局面含む) 178,654 14 86,783 15 50,854 16 30,888 17 15,672 18 10,412 19 6,396 20 4,220 21 2,004 22 1,852 23 1,244 24 688 25 328 26(最大) 76 到達不可能 752,732,880 合計 2,880,567,249 ※千日手の局面を含まない また勝ち負けでの分類はこの通りとなる。 勝ち局面 1,315,792,588 負け局面 812,041,781 千日手局面 906,718 合計 2,128,741,087 ※到達不可能な局面の数を含まない 最序盤考察 まずは初手の解析を行う。初手として取りうる手と終局までの手数を一覧にして出力した結果が以下である。駒の表現は▫=小、□=中、◇=大の駒としてそれぞれ表現している。(count = 終局までの手数) wins=18, draws=0, loses=9 wins: count = 12 --- ▫-- --- --- --- --- --- --- --- count = 12 ◇-- --- --- --- --- --- --- --- --- count = 12 --- --- --- --- ◇-- --- --- --- --- count = 14 ▫-- --- --- --- --- --- --- --- --- count = 14 --- --- --- --- ▫-- --- --- --- --- count = 14 --- ◇-- --- --- --- --- --- --- --- loses: count = 13 □-- --- --- --- --- --- --- --- --- count = 11 --- --- --- --- □-- --- --- --- --- count = 11 --- □-- --- --- --- --- --- --- --- (対称な局面は除外してある) 解析結果によると、初手は場所に拘らず小さな駒か大きな駒を置けば先手必勝となり、逆に中くらいの駒だとどこに置いても後手必勝になってしまうという興味深い結果が得られた。 さらに最短手順にするには小さな駒を辺に置くか、大きな駒を真ん中または端に置く事が必要だとわかった。 補足 もう少し踏み込んで解析したいところではあるが、実際に商品として販売されているゲームなので配慮が必要かと思い、必勝手順をそのまま載せるのではなくやや限定的な情報のみに留めた。(もしより詳細な情報を知りたいという方がいれば、ゴブレットゴブラーズを購入していることを条件にTwitterDMより質問などお答えします) また内部的な話として、当初ミニマックス法を使用して解析しようとして1週間ほど浪費してしまったため、最初に適切な情報を念入りに調べておくという必要性を学んだ。 あとこのような解析にはPythonではなくC++の方が適切な気がする。実行速度的にもバグの見つけやすさ的にも。 だが今回の実装によってJupyterNotebookの使い方に慣れることができたし、何よりプログラミングを有益に使う事ができたのは大きな自信に繋がった。AtCoderサボってたけど明日からやろう。 参考文献 ゲームを解く!Educational DP Contest K, L 問題の解説 「どうぶつしょうぎ」の完全解析田 中 哲 朗 Twitter @ysgrProgramming
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ラムダ式プログラミング一時間体験講座(Python/Ruby/JavaScript同時並行版)

筆者『一時間体験講座』第4弾(1,2,3).内容的にはこちらの方が近いかもしれませんが,より実践的な内容となっています.複数言語対応の体験ということもあって,用語や仕組みの正確なところの解説はほとんどありません1. 前書き この記事は,ともすれば難読化扱いされかねないラムダ式を用いたプログラミングの普及の一助になればという筆者の被害妄想想いから生まれました.3言語同時並行で進めますが,いずれの言語も事前知識はあまり必要ありません.講座の目標として,次の記述が何を行うものかわかるようになればいいなあという感じです. Python $ python >>> (lambda u: u(u))(lambda u: (lambda n,a,b: a if n == 0 else u(u)(n - 1, b, a + b)))(40,0,1) 102334155 >>> exit() $ Ruby $ irb --simple-prompt >> ->u{u[u]}[->u{->n,a,b{n == 0 ? a : u[u][n-1,b,a+b]}}][40,0,1] => 102334155 >> exit $ JavaScript $ node > (u=>u(u))(u=>(n,a,b)=> n == 0 ? a : u(u)(n-1,b,a+b))(40,0,1) 102334155 > (Ctrlキーを押しながらD) $ 実行例はいずれも,UNIXシェルからの対話モード(REPL)で記載しています.それぞれの環境で適宜読み替えてもらえればと思います. ※JavaScriptについては,上記Node.jsの他,Chrome/EdgeでCtrl+Shift+Jで呼び出されるconsoleでも実行可能です. ラムダ式の基本 ラムダ式は無名関数とも呼ばれ,名前のない関数です.関数そのものは数学のそれと基本的には同じで,ある変数に依存して決まる値あるいはその対応を表す式によって構成されています.プログラミングでは,変数やその変数の値を引数(ひきすう)と呼んでいます.次の図は,関数処理の大まかな流れです. 引数として変数$x$,$y$をとってその変数の値を足した結果を返すラムダ式は,次のように記述できます. Python >>> lambda x, y: x + y Ruby >> ->x,y{x+y} JavaScript > (x,y)=>x+y 上記を実行2しただけでは『関数だよ』という結果表示しかありません.ですが,ラムダ式はこの実行時に,変数と値の対応を保持するための記憶領域を内部に作ります3. 変数に値を対応させて処理を行わせる4には,実際の値を引数として指定します. Python >>> (lambda x, y: x + y)(10, 20) 30 Ruby >> ->x,y{x+y}[10,20] => 30 JavaScript > ((x,y)=>x+y)(10,20) 30 実際の値については,ラムダ式を指定5することもできます.次は,3つの引数$f$,$x$,$y$をとり,$f(x,y)$,すなわち,関数$f$に$x$,$y$の値を渡して計算させるラムダ式です. Python >>> lambda f, x, y: f(x, y) Ruby >> ->f,x,y{f[x,y]} JavaScript > (f,x,y)=>f(x,y) $f$に上記の足し算を行うラムダ式を,$x$,$y$にそれぞれ10,20を引数として指定して実行すると,足し算のラムダ式の時と同じ結果が得られます6. Python >>> (lambda f, x, y: f(x, y))(lambda x, y: x + y, 10, 20) 30 Ruby >> ->f,x,y{f[x,y]}[->(x,y){x+y},10,20] => 30 JavaScript > ((f,x,y)=>f(x,y))((x,y)=>x+y,10,20) 30 注意してほしいのは,$f$,$x$,$y$のラムダ式の$x$,$y$と,足し算を行うラムダ式の$x$,$y$は全くの別物であるということです7.先の記憶領域の仕組みにより,$x$,$y$はそれぞれのラムダ式で独自に値の対応付けの管理が行われます. もし,あるラムダ式が別のラムダ式の変数を参照したい場合は,ラムダ式を返すラムダ式を定義して親子関係とし8,子が親の変数を参照可能とします.次は,引数$x$をとって『引数$y$をとって$x+y$を計算するラムダ式』を返すラムダ式の例です. Python >>> lambda x: lambda y: x + y Ruby >> ->x{->y{x+y}} JavaScript > x=>y=>x+y この式に値10を指定すると,引数xが10に対応付けられ,その対応付けを引き継いだ『引数$y$をとって$x+y$を計算するラムダ式』が返ります.最初のラムダ式の例と同じく,ラムダ式だけが返っても『関数だよ』という表示しか行われません. Python >>> (lambda x: lambda y: x + y)(10) Ruby >> ->x{->y{x+y}}[10] JavaScript > (x=>y=>x+y)(10) このラムダ式に値20を指定すると,引数$y$に20が対応付けられ,なおかつ,$x+y$が計算されますから,晴れて$10+20⇒30$が表示されます. Python >>> (lambda x: lambda y: x + y)(10)(20) 30 Ruby >> ->x{->y{x+y}}[10][20] => 30 JavaScript > (x=>y=>x+y)(10)(20) 30 ループ式と条件分岐 次は,ループを発生させるラムダ式です. Python >>> (lambda u: u(u))(lambda u: u(u)) Ruby >> ->u{u[u]}[->u{u[u]}] JavaScript > (u=>u(u))(u=>u(u)) 今回取り上げているプログラミング言語の処理系は,一定回数以上のループ(正確には,再帰)が発生するとエラーとなり停止します. Python >>> (lambda u: u(u))(lambda u: u(u)) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <lambda> File "<stdin>", line 1, in <lambda> File "<stdin>", line 1, in <lambda> [Previous line repeated 996 more times] RecursionError: maximum recursion depth exceeded Ruby >> ->u{u[u]}[->u{u[u]}] Traceback (most recent call last): 16: from (irb):1:in `block in irb_binding' 15: from (irb):1:in `block in irb_binding' 14: from (irb):1:in `block in irb_binding' 13: from (irb):1:in `block in irb_binding' 12: from (irb):1:in `block in irb_binding' 11: from (irb):1:in `block in irb_binding' 10: from (irb):1:in `block in irb_binding' 9: from (irb):1:in `block in irb_binding' 8: from (irb):1:in `block in irb_binding' 7: from (irb):1:in `block in irb_binding' 6: from (irb):1:in `block in irb_binding' 5: from (irb):1:in `block in irb_binding' 4: from (irb):1:in `block in irb_binding' 3: from (irb):1:in `block in irb_binding' 2: from (irb):1:in `block in irb_binding' 1: from (irb):1:in `block in irb_binding' SystemStackError (stack level too deep) JavaScript > (u=>u(u))(u=>u(u)) Thrown: RangeError: Maximum call stack size exceeded at u (repl:1:11) at u (repl:1:14) at u (repl:1:14) at u (repl:1:14) at u (repl:1:14) at u (repl:1:14) at u (repl:1:14) at u (repl:1:14) at u (repl:1:14) at u (repl:1:14) なぜ,ループが発生するのでしょうか?それは,上記の式が,引数に値として同じラムダ式を指定することで,全く同じ式が構成されるからです.構成された式は再び引数に同じラムダ式を指定していますから,引数への値の指定が次々と行われます. Python (lambda u: u(u))(lambda u: u(u)) ➡ u(u) ※ラムダ式の記憶領域に『u = lambda u: u(u)』を保持 ➡ (lambda u: u(u))(lambda u: u(u)) ➡ u(u) ※ラムダ式の記憶領域に『u = lambda u: u(u)』を保持 ➡ (lambda u: u(u))(lambda u: u(u)) ➡ … Ruby ->u{u[u]}[->u{u[u]}] ➡ u[u] ※ラムダ式の記憶領域に『u = ->u{u[u]}』を保持 ➡ ->u{u[u]}[->u{u[u]}] ➡ u[u] ※ラムダ式の記憶領域に『u = ->u{u[u]}』を保持 ➡ ->u{u[u]}[->u{u[u]}] ➡ … JavaScript (u=>u(u))(u=>u(u)) ➡ u(u) ※ラムダ式の記憶領域に『u = u=>u(u)』を保持 ➡ (u=>u(u))(u=>u(u)) ➡ u(u) ※ラムダ式の記憶領域に『u = u=>u(u)』を保持 ➡ (u=>u(u))(u=>u(u)) ➡ … このループ式の意味するところは,適切な脱出方法があれば,繰り返し相当の処理をラムダ式のみで記述できることです.ここでは,値として指定している(右側の)ラムダ式の中に,『繰り返すたびに値が変化する変数』と『その値によって条件分岐する仕組み』を組み込むことを考えます. まず,値として指定しているラムダ式について,同じラムダ式を指定している箇所の代わりに,単に『引数の値を1減らして返す』だけのラムダ式としてみます. Python (lambda u: u(u))(lambda u: u(u)) ⬇ (lambda u: u(u))(lambda u: (lambda n: n - 1)) Ruby ->u{u[u]}[->u{u[u]}] ⬇ ->u{u[u]}[->u{->n{n-1}}] JavaScript (u=>u(u))(u=>u(u)) ⬇ (u=>u(u))(u=>n=>n-1) この場合,ラムダ式の実行は2巡だけであり,ループで用いていた変数がなくなるため,『引数の値を1減らして返す』ラムダ式がそのまま戻ります.したがって,実際の値として数値を与えると,1減った値が結果として返ります. Python >>> (lambda u: u(u))(lambda u: (lambda n: n - 1))(3) # ➡ (lambda u: (lambda n: n - 1))(lambda u: (lambda n: n - 1))(3) # 左のラムダ式は,uがどのような値であっても(lambda n: n - 1)を返す # ➡ (lambda n: n - 1)(3) 2 Ruby >> ->u{u[u]}[->u{->n{n-1}}][3] # ➡ ->u{->n{n-1}}[->u{->n{n-1}}][3] # 左のラムダ式は,uがどのような値であっても->n{n-1}を返す # ➡ ->n{n-1}[3] => 2 JavaScript > (u=>u(u))(u=>n=>n-1)(3) // ➡ (u=>n=>n-1)(u=>n=>n-1)(3) // 左のラムダ式は,uがどのような値であってもn=>n-1を返す // ➡ (n=>n-1)[3] 2 そして,この『引数の値を1減らして返す』ラムダ式を更に,『引数の値が0ならば1を返し,そうでなければ,引数の値を1減らした値を返す』ラムダ式に修正します. Python (lambda u: u(u))(lambda u: (lambda n: n - 1)) ⬇ (lambda u: u(u))(lambda u: (lambda n: 1 if n == 0 else n - 1)) Ruby ->u{u[u]}[->u{->n{n-1}}] ⬇ ->u{u[u]}[->u{->n{n == 0 ? 1 : n - 1}}] JavaScript (u=>u(u))(u=>n=>n-1) ⬇ (u=>u(u))(u=>n=>n == 0 ? 1 : n - 1) 上記の条件分岐には,各言語の三項演算子を用いています.三項演算子は,条件式,条件式が真の時の式,条件式が偽の時の式から構成されています(PythonとRuby/JavaScriptで条件式の順番が異なることに注意して下さい). Python >>> a = -100 >>> a * -1 if a < 0 else a * 1 100 >>> a * -1 if a > 0 else a * 1 -100 Ruby >> a = -100 >> a < 0 ? a * -1 : a * 1 => 100 >> a > 0 ? a * -1 : a * 1 => -100 JavaScript > a = 100 100 > a < 0 ? a * -1 : a * 1 100 > a > 0 ? a * -1 : a * 1 -100 話を戻し,条件分岐を組み込んだラムダ式を用いて,引数が0の場合とそれ以外の場合で実行してみます. Python >>> (lambda u: u(u))(lambda u: (lambda n: 1 if n == 0 else n - 1))(0) # ➡ (lambda u: (lambda n: 1 if n == 0 else n - 1))(lambda u: (lambda n: 1 if n == 0 else n - 1))(0) # ➡ (lambda n: 1 if n == 0 else n - 1)(0) 1 >>> (lambda u: u(u))(lambda u: (lambda n: 1 if n == 0 else n - 1))(3) # ➡ (lambda u: (lambda n: 1 if n == 0 else n - 1))(lambda u: (lambda n: 1 if n == 0 else n - 1))(3) # ➡ (lambda n: 1 if n == 0 else n - 1)(3) 2 Ruby >> ->u{u[u]}[->u{->n{n == 0 ? 1 : n - 1}}][0] # ➡ ->u{->n{n == 0 ? 1 : n - 1}}[->u{->n{n == 0 ? 1 : n - 1}}][0] # ➡ ->n{n == 0 ? 1 : n - 1}[0] => 1 >> ->u{u[u]}[->u{->n{n == 0 ? 1 : n - 1}}][3] # ➡ ->u{->n{n == 0 ? 1 : n - 1}}[->u{->n{n == 0 ? 1 : n - 1}}][3] # ➡ ->n{n == 0 ? 1 : n - 1}[3] => 2 JavaScript > (u=>u(u))(u=>n=>n == 0 ? 1 : n - 1)(0) // ➡ (u=>n=>n == 0 ? 1 : n - 1)(u=>n=>n == 0 ? 1 : n - 1)(0) // ➡ (n=>n == 0 ? 1 : n - 1)(0) 1 > (u=>u(u))(u=>n=>n == 0 ? 1 : n - 1)(3) // ➡ (u=>n=>n == 0 ? 1 : n - 1)(u=>n=>n == 0 ? 1 : n - 1)(3) // ➡ (n=>n == 0 ? 1 : n - 1)(3) 2 引数が0の時は1を返し,それ以外では1を減らした値を返すようになりました.ただしこれは先と同じく,ループで用いていた変数がなくなるためであり,条件分岐を組み込んだラムダ式のみを用いた場合と同じです.異なるのは,この条件分岐を組み込んだラムダ式が2巡してから同じラムダ式として戻ってくる(そして,引数が0の時は1を返し,それ以外では1を減らした値を返す),ということです. ここで,条件分岐を組み込んだラムダ式について,次のように,『引数の値を1減らした値を返す』箇所に,一度なくした『同じラムダ式を指定する』記述を組み込み,そして,引数に0以外の値を渡してみます. Python >>> (lambda u: u(u))(lambda u: (lambda n: 1 if n == 0 else u(u)(n - 1)))(3) # ➡ (lambda u: (lambda n: 1 if n == 0 else u(u)(n - 1)))(lambda u: (lambda n: 1 if n == 0 else u(u)(n - 1)))(3) # ➡ (lambda n: 1 if n == 0 else u(u)(n - 1))(3) ➡ u(u)(2) # ➡ (lambda u: (lambda n: 1 if n == 0 else u(u)(n - 1)))(lambda u: (lambda n: 1 if n == 0 else u(u)(n - 1)))(2) # ➡ (lambda n: 1 if n == 0 else u(u)(n - 1))(2) ➡ u(u)(1) # ➡ (lambda u: (lambda n: 1 if n == 0 else u(u)(n - 1)))(lambda u: (lambda n: 1 if n == 0 else u(u)(n - 1)))(1) # ➡ (lambda n: 1 if n == 0 else u(u)(n - 1))(1) ➡ u(u)(0) # ➡ (lambda u: (lambda n: 1 if n == 0 else u(u)(n - 1)))(lambda u: (lambda n: 1 if n == 0 else u(u)(n - 1)))(0) # ➡ (lambda n: 1 if n == 0 else u(u)(n - 1))(0) 1 Ruby >> ->u{u[u]}[->u{->n{n == 0 ? 1 : u[u][n - 1]}}][3] # ➡ ->u{->n{n == 0 ? 1 : u[u][n - 1]}}[->u{->n{n == 0 ? 1 : u[u][n - 1]}}][3] # ➡ ->n{n == 0 ? 1 : u[u][n - 1]}[3] ➡ u[u][2] # ➡ ->u{->n{n == 0 ? 1 : u[u][n - 1]}}[->u{->n{n == 0 ? 1 : u[u][n - 1]}}][2] # ➡ ->n{n == 0 ? 1 : u[u][n - 1]}[2] ➡ u[u][1] # ➡ ->u{->n{n == 0 ? 1 : u[u][n - 1]}}[->u{->n{n == 0 ? 1 : u[u][n - 1]}}][1] # ➡ ->n{n == 0 ? 1 : u[u][n - 1]}[1] ➡ u[u][0] # ➡ ->u{->n{n == 0 ? 1 : u[u][n - 1]}}[->u{->n{n == 0 ? 1 : u[u][n - 1]}}][0] # ➡ ->n{n == 0 ? 1 : u[u][n - 1]}[0] => 1 JavaScript > (u=>u(u))(u=>n=>n == 0 ? 1 : u(u)(n - 1))(3) // ➡ (u=>n=>n == 0 ? 1 : u(u)(n - 1))(u=>n=>n == 0 ? 1 : u(u)(n - 1))(3) // ➡ (n=>n == 0 ? 1 : u(u)(n - 1))(3) ➡ u(u)(2) // ➡ (u=>n=>n == 0 ? 1 : u(u)(n - 1))(u=>n=>n == 0 ? 1 : u(u)(n - 1))(2) // ➡ (n=>n == 0 ? 1 : u(u)(n - 1))(2) ➡ u(u)(1) // ➡ (u=>n=>n == 0 ? 1 : u(u)(n - 1))(u=>n=>n == 0 ? 1 : u(u)(n - 1))(1) // ➡ (n=>n == 0 ? 1 : u(u)(n - 1))(1) ➡ u(u)(0) // ➡ (u=>n=>n == 0 ? 1 : u(u)(n - 1))(u=>n=>n == 0 ? 1 : u(u)(n - 1))(0) // ➡ (n=>n == 0 ? 1 : u(u)(n - 1))(0) 1 ループが復活しましたが,『繰り返すたびに値が変化する変数』と『その値によって条件分岐する仕組み』が揃いましたので,停止する繰り返し処理となりました.次は,この仕組みを応用した,5の階乗を求めるラムダ式です. Python >>> (lambda u: u(u))(lambda u: (lambda n,r: r if n < 1 else u(u)(n - 1, n * r)))(5, 1) 120 Ruby >> ->u{u[u]}[->u{->n,r{n < 1 ? r : u[u][n-1,n*r]}}][5,1] => 120 JavaScript > (u=>u(u))(u=>(n,r)=>n < 1 ? r : u(u)(n-1,n*r))(5,1) 120 今回のラムダ式による繰り返しパターン9を各言語でまとめると次のようになります. Python (lambda u: u(u))(lambda u: (lambda 引数変数: 停止時に返す式 if 停止の条件式 else u(u)と引数の変数を含む繰り返し計算))(引数値) Ruby ->u{u[u]}[->u{->引数変数{停止の条件式 ? 停止時に返す式 : u[u]と引数変数を含む繰り返し計算}}][引数値] JavaScript (u=>u(u))(u=>(引数変数)=>停止の条件式 ? 停止時に返す式 : u(u)と引数の変数を含む繰り返し計算)(引数値) 備考 記事に関する補足 説明はするけど対応する用語は出さないという….あとで用語一覧を追加するかも.→脚注にしてみました. 更新履歴 2021-07-25:用語の類を脚注に記載 2021-07-24:初版公開 用語の類は脚注にしました. ↩ 評価(eval) ↩ クロージャ ↩ 関数適用(apply) ↩ 高階関数(引数が関数) ↩ ラムダ計算のβ簡約相当 ↩ レキシカルスコープ ↩ 高階関数(戻り値が関数) ↩ Uコンビネータ ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ミス記録シート

目次 ★★★★★ 7/24 ABC211 C問題 MODでの割り忘れ ★★★★☆ 7/24 ABC211 D問題 ★★☆☆☆ 7/18 ARC123 C問題 はじめに このページでは、同じミスを繰り返さないよう(コンテスト中に)実際にやらかしたミスを記録しています ★:ミスの注意度 太文字:過去に複数回やらかしたミス abc211_c 問題 最後の最後に答えを求める部分でMODで割るのを忘れた WAした提出 false.py ans = 0 for i in range(len(mat[7])): ans += mat[7][i][1] ACした提出 true.py ans = 0 for i in range(len(mat[7])): ans = (ans + mat[7][i][1]) % MOD abc211_d 問題 都市間を道路を通って移動する問題で、「各都市において次にどの都市に向かえるか」をまとめず、入力で与えられた「各道路がどの都市を結んでいるか」の情報のみで解こうとした この手法だと、各都市において該当する道路を毎回二分探索する必要があるため、無駄がある よく使うし、都市間を道路を通って移動する問題の入力をチートシートに追加してもいいかも TLEした提出 false.py for i in range(M): A,B = map(int,input().split()) mat.append([A,B]) mat.append([B,A]) ACした提出 true.py for i in range(M): A,B = map(int,input().split()) root[A].append(B) root[B].append(A) arc123_c 問題 足し算において、「前の桁からの繰り上がりが1あってその桁の合計の1桁目が0の場合は、繰り上がり前の1桁目は(-1ではなく)9である」という例外処理をしたにも関わらず、繰り上がりがなく1桁目が9であるケースと同様にその桁では繰り上がりが起きてないとして処理してしまった より一般的にまとめると、「途中で一部ケースに対し例外処理を行ったにもかかわらず、その処理を行ったかどうかをその後区別せず同じものとして扱った結果バグが生じた」 TLEした提出 false.py if num == -1: num = 9 if mat[MAX][num] == 1: tmp += 1 kuriagari = 0 elif mat[MAX][num+10] == 1: tmp += 1 kuriagari = 1 ACした提出 true.py if num == -1: num = 9 piyo = 1 # <- add else: piyo = 0 # <- add if mat[MAX][num] == 1: tmp += 1 kuriagari = 0 if piyo == 1: # <- add kuriagari = 1 # <- add
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オリンピックの開会式の入場順がアルファベット順ではない回を調べてみた(入場順をクラスタリング)

はじめに 昨日がオリンピックの開会式でしたが、入場行進の国の順番がカタカナ表記の五十音順だということが少し話題になっていました 過去には2008年の北京オリンピックで「2008年北京大会でも、中国語(簡体字)による国名頭文字の画数順」だったらしいですが、日本でも1964年の東京オリンピックでは普通にアルファベット順だったらしいです そこで入場順と開催地の言語には関係があって、各開催年(開催地)での入場順を比較してグループ化(クラスタリング)した場合にアルファベット順で入場した回とそうでない回に分かれるのではないか、と思って実験がてら調べてみました 実行環境 python 3.6.5 オリンピックの情報(開催年、開催地、入場順)をWikipediaで調べる スクレイピング用の関数を用意してwikipediaから情報を取得します from bs4 import BeautifulSoup import requests def get_soup(url): response = requests.get(url) response.encoding = response.apparent_encoding html = response.text return BeautifulSoup(html, 'html.parser') 開会式のページのURL このページの下部にあるテーブル(下の図)からWikipediaに開会式の情報がある回URLを取得します url = "https://en.wikipedia.org/wiki/1972_Summer_Olympics_national_flag_bearers" soup = get_soup(url) summer_olympics = soup.select_one(".nowraplinks").select(".navbox-list")[0] winter_olympics = soup.select_one(".nowraplinks").select(".navbox-list")[1] celemony_urls = [tag.get("href") for tag in summer_olympics.select('a') if tag.get("class") is None] celemony_urls += [tag.get("href") for tag in winter_olympics.select('a') if tag.get("class") is None] celemony_urls = [url for url in celemony_urls if "closing" not in url] # closingは閉会式なのでいらない # 後で使いたいので開会式のURLがある、夏の開催年と冬の開催年をリストにしておく summer_years = [url.split("/wiki/")[-1].split("_")[0] for url in celemony_urls if "Summer" in url] winter_years = [url.split("/wiki/")[-1].split("_")[0] for url in celemony_urls if "Winter" in url] ''' celemony_urlsはこのような配列 ['/wiki/1952_Summer_Olympics_national_flag_bearers', '/wiki/1964_Summer_Olympics_national_flag_bearers', '/wiki/1976_Summer_Olympics_national_flag_bearers', '/wiki/1980_Summer_Olympics_national_flag_bearers', '/wiki/1984_Summer_Olympics_national_flag_bearers', ... ''' 開催年から開催地にアクセスする https://en.wikipedia.org/wiki/Olympic_Games のテーブルから開催年と開催地の対応を取得しておきます (このとき、すでに定義した celemony_urls に含まれている開催地のみを使用します) url = "https://en.wikipedia.org/wiki/Olympic_Games" soup = get_soup(url) country_by_year = {} summer_list = soup.select_one(".nowraplinks").select(".div-col")[0].select("li") winter_list = soup.select_one(".nowraplinks").select(".div-col")[1].select("li") for tag in summer_list: year, country = tag.text.split(" ")[0], " ".join(tag.text.split(" ")[1:]) if year not in summer_years: continue country = country.strip() country_by_year[year] = country for tag in winter_list: year, country = tag.text.split(" ")[0], " ".join(tag.text.split(" ")[1:]) if year not in winter_years: continue country = country.strip() country_by_year[year] = country ''' country_by_yearはこのような辞書型オブジェクト {'1952': 'Helsinki', '1964': 'Tokyo', '1976': 'Montreal', '1980': 'Moscow', '1984': 'Sarajevo', ... ''' 開催年(開催地)と開会式での入場順を取得する https://en.wikipedia.org/wiki/2020_Summer_Olympics_Parade_of_Nations では次のようなテーブルがあります。各開催年のページのこのテーブルから国ごとの入場順の数字を取得します def parse_order_by_country(soup): # 引数の開催年の国ごとの入場順を辞書にして返す order_by_country = {} for tr in soup.select_one(".wikitable").select("tr")[1:]: if len(tr.select("td")) < 3: continue # flag bearersのtrがrowspan=2で作られているので order, country = tr.select("td")[0].text, tr.select("td")[1].text country = unidecode.unidecode(country).strip() order_by_country[country] = int(order) return order_by_country base_url = "https://en.wikipedia.org" country_order_by_year = {} for celemony_url in celemony_urls: year = celemony_url.split("/wiki/")[-1].split("_")[0] url = f"{base_url}{celemony_url}" soup = get_soup(url) order_by_country = parse_order_by_country(soup) host_country = country_by_year[year] country_order_by_year[f"{year}_{host_country}"] = order_by_country ''' country_order_by_yearは、開催年・地を1番目、入場国を2番目のキーにした辞書 {'1952_Helsinki': {'Greece (GRE)': 1, 'Netherlands Antilles (AHO)': 2, 'Argentina (ARG)': 3, 'Australia (AUS)': 4, ... ''' ここまでで、各開催年(開催地)での各国の入場順が手元にある状態になりました 国の入場順から各開催年をクラスタリングする pandasのDataFrameに先程の辞書を渡すことで、出場国✕開催年(開催地)の2次元配列が出来上がります、 import pandas as pd df_country_order_by_year = pd.DataFrame(country_order_by_year) この列ベクトルを各開催年(開催地)の特徴量とすることで、開催年ごとの入場順の類似度を調べることができると考えられます。今回はこのベクトルで開催年をクラスタリングします このデータは当然ながら欠損値が多く、(他に良い方法が思いつかないので)今回はすべての開催で出場している国の入場順を対象に調べることにします。全部で17カ国となりました df_country_order_by_year_dropped = df_country_order_by_year.dropna() 最後にこれらのベクトルを階層的クラスタリングします import scipy.cluster.hierarchy as hcluster linkage = hcluster.linkage(df_sorted_index_of_country_by_year.T) dendro = hcluster.dendrogram(linkage, labels=df_sorted_index_of_country_by_year.columns, orientation="left") この樹形図からは、1994年(リレハンメル)、2004年(アテネ)、2008年(北京)、2018年(平昌)、2020年(東京)とそれ以外というように見えます おわりに 残念ながらリレハンメルオリンピックは普通にアルファベット順なのですが、それ以外は 2004年(アテネ) : ギリシア語のアルファベット順(α、β、γで始まり、χ、ψ、ωで終わる) 2008年(北京) : 国名を中国語(簡体字)表記したときの画数順 2018年(平昌) : 朝鮮語のハングルの字母カナダラ順 2020(東京) : 頭文字の五十音順 ということで、アルファベット順ではない入場順がクラスタで分かれていることが分かりました(その他の回でアルファベット順ではない回があるのかどうかは調べておりません)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python 入門1(Pythonの基本)

今回の記事では、Pythonの基礎的な文法について、Udemyの講座にて学習した内容を記載する(参考文献参照) 目次 1.変数宣言 2.print出力 3.数値 4.文字列 5.文字列のインデックスとスライス 6.文字のメソッド 7.文字の代入 1-変数宣言 変数名の付け方 変数名の先頭文字が数字の場合エラーとなる 先頭文字が_(アンダーバー)は問題なし 予約語と同じ名前は変数として宣言できない 0abc = 1 File "", line 1 0abc = 1 ^ SyntaxError: invalid syntax _abc0 = 1 print(_abc0) 1 変数の型 Pythonの場合、代入される値によって自動的に型が決定する 他の言語のようにvar や intなど型宣言する必要はない そのため、暗黙のキャストが発生する場合がある 変数に型が異なる値を代入すると変数の値が変換されてしまう。 例えばint型の変数にstr型の値を代入すると、int型の変数は自動的にstr型に変換される 暗黙のキャストは意図的に実施していないので、暗黙のキャストではなく、明示的な型変換が推奨されている。 a = 1 print(type(a)) a = a + 2.5 print(type(a)) <class 'int'> <class 'float'> 2-print出力 sepオプションで区切りを指定できる endオプションで文末に改行マークなどを指定できる #,区切りで変数名を出力することができる print('Hi', 'Mike', sep=',') # 改行ありで出力 print('ab', 'cde', sep=',', end='\n') print('fg', 'hij', sep=',', end='\n') # 改行なしで出力 print('ab', 'cde', sep=',', end='') print('fg', 'hij', sep=',', end='') Hi,Mike ab,cde fg,hij ab,cdefg,hij 3-数値 数値計算するためmathライブラリをインポートする import math print(help(math)) # mathライブラリのヘルプが表示される 数値計算で使用する演算子は下表のとおり 演算子 種類 + 加算 - 減算 * 乗算 / 除算 // 商 % 余り ** べき乗 「.6」と入力しただけでも「0.6」と認識する a = .6 print(a) 0.6 round関数:丸め誤差 import math pie = 3.14159 round(pie,2) 3.14 4-文字列 文字列を指定する場合、シングルクォーテーションあるいはダブルクォーテーションで囲む print('hello') print("hello") hello hello 文字列の中にシングルクオーテーションが1文字のみ入っている場合も認識する print("I don't know") I don't know 文字列の中の記号をそのまま表示させたい場合、一文字前に 「\」を置く print("I don\'t know") print('say"I don\'t know"') print("say\"I don\'t know\"") I don't know say"I don't know" say"I don't know" 文字列の前に r を入れることで生のデータをそのまま出力 r : rawという意味 print(r'C\name\name') C\name\name 文字列を、連結子なしで連結させる方法 「()」を使用する場合と「\」を使用する場合の2パターン 文字列の長さが1行では足りないほど長い場合に改行する際に使用することが多い s = ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb') print(s) s = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' \ 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' print(s) aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 演算子を利用して文章を作成可能 * : 文字列を繰り返す + : 文字列を連結させる print('Hi ' * 3 + 'Mike') Hi Hi Hi Mike 復習行出力する例を下記に示す ただし、#文字列とline*文字列の間に改行あり print("#####################") print(""" line1 line2 line3 """) print("#####################") ##################### line1 line2 line3 ##################### バックスラッシュを入れることで改行を防ぐ print("#####################") print("""\ line1 line2 line3\ """) print("#####################") ##################### line1 line2 line3 ##################### 5-文字列のインデックスとスライス 文字列は複数の文字で構成されているので、 対応するインデックスを指定することで特定の文字列のみ抽出することができる 配列番号は0から開始する -1は末尾のインデックス word = 'python' print(word[0]) print(word[1]) print(word[-1]) p y n スライス インデックスの範囲を指定することにより複数の文字列を抽出する word = 'python' print(word[0:2]) print(word[2:5]) py tho スライスの0は省略可能 word = 'python' print(word[:2]) print(word[2:]) py tho 特定の文字列を置き換え インデックス、およびスライスを用いることにより、特定の文字列のみ置き換えることが容易となる word = 'python' word = 'j' + word[1:] print(word) jython len関数 : 文字数を取得 len関数で文字数を得ることができる word = 'python' n = len(word) print(n) 6 6-文字のメソッド startswith : 文字列の始まりが、引数で指定した文字列と一致しているか確認する s = 'My name is Mike. Hi Mike.' isstart = s.startswith('My') print(isstart) isstart = s.startswith('X') print(isstart) True False find/rfind : 指定した文字列が何文字目から開始されるか確認するメソッド find:先頭(0)から数える rfind:後ろから数える s = 'My name is Mike. Hi Mike.' print(s.find('Mike')) print(s.rfind('Mike')) 11 20 count :指定した文字列が何回出現するか数える s = 'My name is Mike. Hi Mike.' print(s.count('Mike')) 2 capitalize : 先頭文字のみ大文字、その他はすべて小文字に変換 s = 'My name is Mike. Hi Mike.' print(s.capitalize()) My name is mike. hi mike. title : 各単語の先頭文字のみ大文字、それ以外は小文字に変換 s = 'My name is Mike. Hi Mike.' print(s.title()) My Name Is Mike. Hi Mike. upperはすべて大文字、lowerはすべて小文字に変換する s = 'My name is Mike. Hi Mike.' print(s.upper()) print(s.lower()) MY NAME IS MIKE. HI MIKE. my name is mike. hi mike. 7-文字の代入 formatメソッドを用いて文字列を代入する formatの引数に文字列を指定すると、{}に値が代入される 'a is {}'.format('a') a is a 'a is {}'.format('test') a is test {}は複数持たせることができる。 {}の中にインデックスを指定することにより値を代入する順番を変えることができる 'a is {} {} {}'.format(1,2,3) a is 1 2 3 'a is {0} {1} {2}'.format(1,2,3) a is 1 2 3 'a is {2} {1} {0}'.format(1,2,3) a is 3 2 1 formatの引数は数値だけでなく文字列も指定できる 'My name is {0} {1}. Watashi ha {1} {0}'.format('first_name','last_name')  My name is first_name last_name. Watashi ha last_name first_name 'My name is {name} {family}. Watashi ha {family} {name}'.format(name='first_name',family='last_name')  My name is first_name last_name. Watashi ha last_name first_name 文字列変換 どんな値でも''で囲んだりstrで変換したりすると、すべてstr型に置き換わる '1' '1' 1 1 str(1) '1' x = str(1) type(x) str str(3.14) '3.14' str(True) 'True' 参考文献 現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイル セクション3:「Pythonの基本」
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jupyter Lab3でPythonのコーディング

Jupyter Lab3でPythonのコーディング Jupyter lab3の起動 Anaconda promptを起動して、jupyter labと打つとJupyter Labがデフォルトのブラウザで立ち上がります。 新規のNotebookを作る 上記の画面のpython3のアイコンか、File>New>Notebookで新規ノートブックが作成されます。 Pythonのコーディングをして実行 a = 2020, b = 2021, c = 2022 として a+b+cを実行。RunボタンまたはCtrl+Enterを押せば実行できます。 packageのimport numpyとpandasとmatplotlibをインポートしてみます。 import PACKAGE as USENAME という形式で記述します。asは英語の「~として」インポートするよ、という意味なので、同一Notebook内で、「import numpy as np」はnumpyをnpという名前で使うよ、という意味になります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Z3Py 例題 コイン問題2

問題 以下のユーロ硬貨を持っているとする(総和は148セントになる)。 20セント硬貨 4枚 10セント硬貨 4枚 5セント硬貨 4枚 2セント硬貨 4枚 合計がちょうど93セントになる組合せはあるだろうか? 各3枚の場合はどうか? 回答 example_coin2.py from z3 import * coin__2cent = Int("coin__2cent") coin__5cent = Int("coin__5cent") coin_10cent = Int("coin_10cent") coin_20cent = Int("coin_20cent") s = Solver() s.add(And(0 <= coin__2cent, coin__2cent <= 4)) s.add(And(0 <= coin__5cent, coin__5cent <= 4)) s.add(And(0 <= coin_10cent, coin_10cent <= 4)) s.add(And(0 <= coin_20cent, coin_20cent <= 4)) s.add(2*coin__2cent + 5*coin__5cent + 10*coin_10cent + 20*coin_20cent == 93) print(s.check()) if s.check() == sat: print(s.model()) s = Solver() s.add(And(0 <= coin__2cent, coin__2cent <= 3)) s.add(And(0 <= coin__5cent, coin__5cent <= 3)) s.add(And(0 <= coin_10cent, coin_10cent <= 3)) s.add(And(0 <= coin_20cent, coin_20cent <= 3)) s.add(2*coin__2cent + 5*coin__5cent + 10*coin_10cent + 20*coin_20cent == 93) print(s.check()) if s.check() == sat: print(s.model()) 出力 sat [coin__2cent = 4, coin__5cent = 1, coin_10cent = 0, coin_20cent = 4] unsat 4枚の場合は解があるが、3枚の場合は解がないことが分かる。 解説 And()は、And条件を生成します。 そもそも、条件の追加(.add())はAnd条件として追加されているので、 s.add(0 <= coin_20cent, coin_20cent <= 4)も同じ。(今回の例では、必要なかったということ) And()の詳細 or()もある。 Or()の詳細 他の例題 Z3Py個人的ポータル へ 前の例題(コイン問題(制約充足問題)) 次の例題(部分和問題)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python>pandas>DataFrame:指定行の抽出

1.概要 初心者の自分はDataFrameでの行の抽出を練習するときにちょくちょく混乱。。。 今後の混乱予防対策のために少し操作方法を整理。 2.DataFrameの準備 以下のDataFramewoを操作して行の抽出操作を確認する。 import pandas as pd import numpy as np df = pd.DataFrame(np.arange(12).reshape(4,3), index=['R1','R2', 'R3', 'R4'], columns=['C1','C2', 'C3']) C1 C2 C3 R1 0 1 2 R2 3 4 5 R3 6 7 8 R4 9 10 11 3.ひとつの行を抽出 「R2」行のみの以下を抽出する操作を確認。 C1 C2 C3 R2 3 4 5 [ ]で囲う操作 df['R2':'R2'] .loc・.ilocを使う操作 df.loc[['R2']] df.iloc[[1]] pd.DataFrame(df.loc['R2']).T < memo > df.loc['R2']だけだと出力はSeries型。 C1     3 C2     4 C3     5 Name: R2, dtype: int32 →pd.DataFrame()で変換すると縦横逆。 R2 C1 3 C2 4 C3 5 →.Tで転置すれば同じ形になった。 .dropを使う操作 df.drop(labels =['R1','R3','R4'],axis = 0) df.drop(labels =[R for R in df.index if R!='R2'],axis = 0) True/Falseを使う操作 df[[False, True, False, False]] 例えば以下で[False,True,False,False]をつくることが可能。 [input]list(df['C1']==3) ↓ [output][False,True,False,False] なので以下でも「R2」行のみのDataFrameを抽出できる df[list(df['C1']==3)] list()でくくらない場合は出力はSeries型。これを使っても「R2」行のみのDataFrameを抽出できる [input]list(df['C1']==3) ↓ [output] R1 False R2 True R3 False R4 False Name: C1, dtype: bool df[list(df['C1']==3)] df (再掲) C1 C2 C3 R1 0 1 2 R2 3 4 5 R3 6 7 8 R4 9 10 11 4.ふたつの行を抽出 「R2」「R3」行を抽出して4以下を得る操作を確認。 C1 C2 C3 R2 3 4 5 R3 6 7 8 [ ]で囲う操作 df['R2':'R3'] .loc・.ilocを使う操作 df.loc[['R2','R3']] df.loc[1:2] df.iloc[[1,2]] df.iloc['R2':'R3'] .dropを使う操作 df.drop(labels =['R1','R4'],axis = 0) df.drop(labels =[R for R in df.index if R!='R2' and R!='R3'],axis = 0) True/Falseを使う操作 df[[False, True, True, False]] [ ]の中が以下になるSeriesをつくればよい。 R1 False R2 True R3 True R4 False Name: C1, dtype: bool 例えば以下で上記Seriesが作れるので * (df['C1']==3) | (df['C1']==6) * (df['C1']<9) & (df['C3']!=2) df[(df['C1']==3) | (df['C1']==6)] df[(df['C1']<9) & (df['C3']!=2)] df (再掲) C1 C2 C3 R1 0 1 2 R2 3 4 5 R3 6 7 8 R4 9 10 11 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Z3Py 例題 コイン問題(制約充足問題)

問題 1円硬貨,5円硬貨,10円硬貨を合計で15枚,それぞれを1枚以上持っている. 金額の合計は90円である. それぞれの硬貨を何枚持っているか? 回答 example01_coin.py from z3 import * coin__1yen = Int("coin__1yen") coin__5yen = Int("coin__5yen") coin_10yen = Int("coin_10yen") s = Solver() s.add(coin__1yen >= 1) s.add(coin__5yen >= 1) s.add(coin_10yen >= 1) s.add((coin__1yen + coin__5yen + coin_10yen) == 15) s.add((coin__1yen + 5 * coin__5yen + 10 * coin_10yen) == 90) print(s.check()) if s.check() == sat: print(s.model()) 出力 sat [coin__5yen = 3, coin_10yen = 7, coin__1yen = 5] 解説 【基本的な順序】 Int()等で変数を定義し、s=Solver()にs.add()で条件・制約を定義する。 s.check()で解があるかを確認する。(たぶん、ここで演算をしている) 解がある場合、「sat」が返ってき、解がない場合は「unsat」が返ってくる。 「sat」が返ってきた場合、s.model()で条件を満たす解の1つが取得できる。 (一つしか出てこないため、条件を満たす解が見つかった時点で探索を終了すると思われる) 他の例題 Z3Py個人的ポータル へ 次の例題(コイン問題2)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

"アラサーサラリーマンが趣味のプログラミング上達のため、Aidemy(データ分析3か月コース)を受講して中古車価格の予想モデルを作成する"

7/24現在 初版投稿...随時修正中 ❒本記事の目次 1.自己紹介:AIデータ分析を学ぶ理由 2.Aidemy(データ分析3か月コース)学習スケジュール 3.成果物として題目【中古自動車価格の予想モデルの作成】を選んだ理由や背景 4.データ分析~モデル作成と考察~ 5.まとめと今後の活動   1.自己紹介:AIデータ分析を学ぶに至った背景 当方、新卒で自動車関係の文系職種に就く、今年30歳となるサラリーマン。 普段の職場はプログラミングとは程遠く、アナログな業務も多い中、 エクセルで大量のデータを処理、分析することに多くの時間を費やし、計画や企画の検討を行っている。 その時間を費やすことに問題意識もなく、必要なものだと思っていたが、 部署移動に伴い、エクセルでマクロを使用する機会があり、業務の自動化に触れ、独学でPythonを学び始めた。 自動化や処理速度UPによる生産性の向上を行い、 限りある時間を付加価値のある行動に費やす投資的な考えが10年後大きな差になると思い、プログラミングを活用したいと思った。 プログラミングのレベル感としては以下の通り。 プログラミングレベル:使用できる言語はPython,VBAを多少。学習歴1年ほど。独学で基本文法を学び、            プロゲートのPythonコースを受講。仕事上で書類作成の自動化などを行う。 読んだ本や参考Web:Python1年生、Python2年生、Progate、その他業務自動化のためのWeb参考 保有資格:Python 3 エンジニア認定基礎試験 2021年3月合格 Pythonが昨今話題のAIを開発・活用するのに適した言語であることを知り、 今後の自分の能力開発/生涯学習のためにも本格的に学びたいと考えるようになる。 Aidemy受講期間:2021年5月3日~8月3日 自身の目標:様々なデータを業務の企画立案へ活かすこと。また、AI活用やデータ分析を通しての新たな付加価値の創出を行う。これは本業だけでなく、普段の暮らしの中でもデジタル化に伴うデータを分析し、AI活用することで客観的な判断や問題の可視化を行い、創造的に楽しみながら自身の技能を高めていきたいと思う。 想定読者:プログラミングに興味があり、新しいことを学びたいと考えている社会人の方 文系学部出身でもAIやデータ分析スキルを学び、使いこなすことで仕事に大きな付加価値を生むことができる。 学び続ける意欲と習慣さえあれば、仕事スキルの差別化ができる。 様々な業界業種で新しい創造的な工夫やビジネスを生み出す人と共にプログラミングを楽しみたい。 2.Aidemy(データ分析3か月コース)学習スケジュール   1か月目・・・Python基礎や各種ライブラリの学習 →Pythonの基本文法は資格取得や独学で学んでいたため、さらさらと復習するような流れでGW連休中にざっと進める。 2か月目・・・データ前処理や機械学習、ディープラーニングの項目を学び進める。 →コースを一読してコードを実行するだけでは十分理解できているとは程遠いと感じながらも全体を把握するように心がけ、後で添削問題を行う際に復習や学びなおしを何度も行った。 後々使用するテクニックや確認プロセスのコード等はその意味を理解して使用することが大切であると非常に思う。 本業が忙しくなり、休出もあったたため、平日は1~2時間、休日は3時間程度の学習時間を確保していた。 3か月目・・・Kaggleデータ分析やTwitteの感情分析、ブログ作成 →プライベートや本業が忙しいこともあり、中々時間が取れず、理解が追いつかない部分が非常に多かった。終盤のコースは集大成の為、コードの目的が分からないときは過去のコースに戻り学び直す事でより力になると思う。 主に開発環境は以下の通り 【使用環境】 言語:Python3 PC :HP ENVY x360 convertible ※タッチもできるのでコード見るときに便利 楽天スーパーセールで購入 ブラウザ:Windows10-Chrome 開発環境:Colaboratory 開発環境構築は最も初心者が躓くところだと個人的に思っている。 何とか自分のローカル環境を組み立てたかったが、 そんなことに時間を使いたくなかったので、結局colaboratory上で開発した。 いまだに個人使用としてanacondaかVSコードを使用しようか迷っているが、 colaboratoryはライブラリのインストールも簡単で至れり尽くせりでとても便利である。 デスクトップ等ローカルにあるデータをインプットする等も必要になるがそのためのライブラリもあるので4章で解説する。 ※CSVのアウトプットももちろん可能なため、ローカル環境構築のやる気がほぼなくなってしまった。 3.成果物として題目【中古自動車価格の予想モデルの作成】を選んだ理由や背景 今回は題目として【中古自動車価格の予想モデルの作成とお得な中古車の発見】を目標とした。 中古車には様々なモデルや仕様、状態等の特徴があり、それが価格に寄与している。 中古車市場においてどのような特徴が価格を決定するのに重要であるか分析し、実際にモデル作成と価格の評価を行うことでお得な中古車を見つけることができるのではないかと考えた。 また、単純に私は車が好きで特徴に関してどんなものが価格に影響するかがイメージしやすく、分析の解説などもしやすいというのがある。 データ収集と確認、分析の前にも解決したい問題への答えの仮説を立てる。 データセットの前提によるが、基本的に市場においては価格は需要と供給によって決定される。 したがって、対象が車であるという特徴を考慮して以下の仮説を立てて次章よりモデル作成、検証を行っていく。 仮説:年式が新しく、走行距離が少なく、モデルの人気度により、価格は決定される。 4.データ分析~モデル作成と考察~ 前章で決めた課題と仮説の解決に必要なデータは何かを考えながら収集~確認&仮設~モデル分析と評価~考察を行っていく。 ⓵訓練およびテストデータの取得 今回使用するデータセットはKaggleより引用した。 Kaggle:https://www.kaggle.com/adityadesai13/used-car-dataset-ford-and-mercedes?select=vw.csv 活用したデータや環境の前提条件としてはイギリス市場であることとなる。 ⓶ライブラリのインポートとデータの内容確認と仮説 colaboratoryで開発するための環境を整えていく。 #ダイアログが出てローカルからデータをアップロードできるようになる。 from google.colab import files uploaded = files.upload() 上記のライブラリを活用することでデスクトップなどのローカル環境からのデータ使用できるようになるため、非常に便利である。 データ分析や予測モデル作成に使用するライブラリは以下の通り、 #使用するライブラリのインポート import pandas as pd #データ処理 import numpy as np #数列計算 import matplotlib.pyplot as plt #データの可視化 import seaborn as sns #データの可視化 import mplcyberpunk #データの可視化 import io #データのインポート import lightgbm as lgb #予想モデル作成に使用 from sklearn.model_selection import train_test_split #予想モデル作成に使用:データ分割  from sklearn.metrics import mean_squared_error #予想モデルの評価 from sklearn.metrics import r2_score #予想モデルの評価スコア 必要なライブラリをインポートしたところで、まずはPandasを中心に利用して、データの確認を行っていく。 df_car = pd.read_csv(io.StringIO(uploaded['100,000 UK Used Car Data set_all.csv'].decode('utf-8')), header=1) df_car.columns = ['maker', 'model', 'year', 'price', 'transmission', 'mileage', 'fuelType', 'tax', 'mpg', 'engineSize'] #データの上の行をピックアップ df_car.head() #データ内容の確認 df_car.info() # データ形状の確認 print('データ形状:', df_car.shape) # 欠損値の確認 print('欠損値の数:{}\n'.format(df_car.isnull().sum().sum())) df_car.describe() 簡単な解説として、mileage:走行距離 fuel Type:燃料タイプ mpg:燃費 engineSize:排気量  データの情報を一括で見ることができる。 50640行に10列(column)・・・サンプルの車は5万台ほどということである。 欠損値もないので今回は補完等は割愛する。 しかし、describe()でようやく統計量を見ていくと 値そのものについて疑わしいものがいくつかあるため、確認していく必要があるようだ。 ❒外れ値の削除・・・今回異常と感じた点を確認し、必要であれば外れ値として削除していく。 1.中古車で2060年式は2021年現在ありえないので削除していく。 #year columnの2021年以上のインデックスを出力 df_car[df_car.year >= 2021] #外れ値の削除と確認 df_car2=df_car.drop(35244) df_car2.query('year == 2060') 無事、異常なデータに関しては削除完了。 同様に外れ値の確認と削除を行っていく。 2.価格1200万!?車種やプレミアによってもありえなくもないが、そもそもの前提がお得な中古車を探すモデルでもあり、 結果どんだけお得であろうとも買えない!削除! #価格1200万のインデックスを出力 df_car2[df_car2.price >= 123455] #外れ値の削除と確認 df_car3=df_car2.drop(10375) df_car3.query('price == 123456') 消えていることを確認。 3.税金0?580? 妥当性を確認 ```python 税金0 df_car3[df_car3.tax == 0] ``` HybridやDieselなのでおそらくこれはエコカー減税的なものだろうか・・・ 怪しいのでそのままにしておくことにする。 #税金580 df_car3[df_car3.tax == 580] こちらは車種やエンジンサイズを見る限り、車体も大きく、排気量が多く、環境に負荷が高いため、 税金が多く掛かっているのではないかと感じる。 EUは昨今、新車種を全てEVにするという政策や事業方向性も出しているので今後は多くなりそうだ。 こちらも削除なせず、そのままにしておく。 4.燃費 470?平均とかけ離れている #高燃費なデータの確認 df_car3[df_car3.mpg == 470.8] 見たところBMWのi3のみと見られる。 ググってみたが問題なさそうだ。 5.エンジンサイズ 0=人力? df_car3[df_car3.engineSize == 0] パッと見、問題なさそうだが、ガソリン車で排気量0が納得いかないので削除する。 #エンジンサイズ0のインデックスを取得して削除 df_car4=df_car3.drop(df_car3.index[df_car3['engineSize'] == 0]) df_car4.query('engineSize == 0') 完了! 次に、仮説の確認のため、カラムに対してのデータ統計を確認する。 #メーカごとのサンプル数の出力 import mplcyberpunk gry=df_car maker_buys= gry.groupby('maker')['maker'].count() maker_buys = pd.DataFrame(maker_buys) maker_buys.columns = ['Volume'] maker_buys.sort_values(by=['Volume'], inplace=True, ascending=False) maker_buys = maker_buys.head(10) print(maker_buys.head(20)) plt.style.use("cyberpunk") maker_buys.plot.bar() 結論:UKではFordが人気 EU系メーカが強い #サンプル数の多いモデルの出力 import mplcyberpunk gry=df_car4 model_buys= gry.groupby('model')['model'].count() model_buys = pd.DataFrame(model_buys) model_buys.columns = ['Buys'] model_buys.sort_values(by=['Buys'], inplace=True, ascending=False) model_buys = model_buys.head(10) print(model_buys.head(20)) plt.style.use("cyberpunk") model_buys.plot.bar() 結論:モデルはFordのFiesta、VWのGolfが人気 ※ヤ〇スに似てる #ギアボックスの種類 gry=df_car4 transmission_buys= gry.groupby('transmission')['transmission'].count() transmission_buys = pd.DataFrame(transmission_buys) transmission_buys.columns = ['Volume_trans'] transmission_buys.sort_values(by=['Volume_trans'], inplace=True, ascending=False) transmission_buys = transmission_buys.head(10) print(transmission_buys.head(20)) plt.style.use("cyberpunk") transmission_buys.plot.bar() 結論:Manualが主流 #エンジンタイプの種類 gry=df_car4 fuelType_buys= gry.groupby('fuelType')['fuelType'].count() fuelType_buys = pd.DataFrame(fuelType_buys) fuelType_buys.columns = ['Volume_fuel'] fuelType_buys.sort_values(by=['Volume_fuel'], inplace=True, ascending=False) fuelType_buys = fuelType_buys.head(10) print(fuelType_buys.head(20)) plt.style.use("cyberpunk") fuelType_buys.plot.bar() 結論:ガソリンが主流、ハイブリッドは少ない ※電動化は今後大きな特徴になっていくと思われる。 #年式ごとの市場にある数 gry=df_car4 model_buys1= gry.groupby('year')['model'].count() model_buys1 = pd.DataFrame(model_buys1) model_buys1.columns = ['Buys'] model_buys1.sort_values(by=['Buys'], inplace=True, ascending=False) model_buys1 = model_buys1.head(10) plt.style.use("cyberpunk") model_buys1.plot.bar() 結論:2019年が多い、以外にも2020年は7位、傾向としては新しいほど人気 #年式ごとの価格平均 model_buys2=df_car model_buys2=model_buys2[["price","year"]].groupby('year').mean() model_buys2.sort_values(by=['price'], inplace=True, ascending=False) model_buys2 = model_buys2.head(10) print(model_buys2.head(20)) 結論:年式が新しいほど価格は高いが、例外として1998年があるが、サンプル数も少ないので平均が高めに出たのかもしれない。 大まかにデータの傾向がつかめたところで特徴量ごと、特に価格への相関関係を見ていく。 df_car4.corr() 価格に対して相関関係を見るとエンジンサイズや年式は正の相関、走行距離に対しては負の相関が見られる。 よって、ここでは年式やエンジンサイズが大きいほど、走行距離が少ないほど価格は上昇傾向にあるようだ。 新たに特徴量を作成したのでデータを可視化して分析していく。 ```python #全体相関関係のプロット sns.pairplot(df_car4) sns.pairplot()を使用することで特徴量の相関関係がプロットで可視化の確認ができる。 ❒価格に言えること 年式が新しいほど高い 走行距離が少ないほど高い エンジンサイズが大きいほど高め? ⓷データ分析と予想モデル作成&評価 今回が予測モデルとしてlightgbmを使用する。以下のような特徴があり、使いこなせれば役に立ちそうである。 欠損値をそのまま扱える カテゴリ変数の指定ができる 特徴量のスケーリングが不要 feature importanceが確認できる 精度が出やすく最終的なモデルとして残る可能性が高い 比較的大きいデータも高速に扱える 過去の経験からハイパーパラメータの勘所がある 逆に汎用的過ぎてデータサイエンティストとしての力がつかないのではないかと不安ではあるが。。。 y = df_car4["price"] #LIGHTGBMはカテゴリ値も使える・・・objectタイプをカテゴリ値へ変換 df_car4['maker']=df_car4['maker'].astype('category') df_car4['model']=df_car4['model'].astype('category') df_car4['transmission']=df_car4['transmission'].astype('category') df_car4['fuelType']=df_car4['fuelType'].astype('category') X = df_car4.drop(['price'], axis=1) X_train, X_test, y_train, y_test = train_test_split(X, y,test_size=0.2, random_state=0) X_train, X_valid, y_train, y_valid = train_test_split(X_train, y_train,test_size=0.2, random_state=0) lgb_train = lgb.Dataset(X_train, y_train) lgb_eval = lgb.Dataset(X_valid, y_valid, reference=lgb_train) lgbm_params = { 'objective': 'regression', 'metric': 'rmse' #'min_data_in_leaf':5 #'num_leaves':80 } model = lgb.train(lgbm_params, lgb_train, valid_sets=lgb_eval, verbose_eval=-1, early_stopping_rounds=20,num_boost_round=1000) y_pred = model.predict(X_test, num_iteration=model.best_iteration) print(r2_score(y_test, y_pred) ) lgb.plot_importance(model, figsize=(12, 6)) plt.show() y_pred mse= mean_squared_error(y_test, y_pred)# 評価 print("LightGBM : %.2f" % (mse** 0.5)) このfearture importanceは非常に便利である。 これをもとにデータの中のエラーやモデルのパラメータを調整すればモデルや結果を改善していくことができる。 ⓸予測モデルの結果に対して、仮設の検証 モデルを作成した後、そのモデルを活用して仮説を検証していく。 まずは下のコードでモデルで作成した予測値と実価格の差、差の変動率を計算していく。 df_for_search = df_car4.copy() pred = pd.Series(y_pred,index=y_test.index, name="予測値") df_for_search = pd.merge(df_for_search, pd.DataFrame(pred),left_index=True, right_index=True) df_for_search['予測値との差'] = df_for_search['price']-df_for_search['予測値'] df_for_search['変動率'] = df_for_search['予測値との差']/df_for_search['price']*100 df_for_search = df_for_search[['maker','model','year',"予測値",'price','予測値との差','変動率']] df_for_search[df_for_search['model']=='Corolla'] わかりやすいので日本の国民車ともいえるカローラを出す。 見方としては変動率が正で大きいほど割高、逆に負で小さいほど割安ということになる。 データのまとめもみていく。 df_for_search.describe() 一番割安な車とは一体どんなものだろうか? df_for_search[df_for_search.price == 795] FordのKAである。ポンドが1ポンド200円ほどとすると、16万円。。。 df_for_search[df_for_search.変動率 >= 66.0] 割高なもの、言うなれば価値が落ちにくいともとれる。 BMWの5シリーズである。 df_car4.query('price == 1595') 変動率で出すと桁の違う安い価格帯の車が出てきてしまうので改善の余地はありそうだ。 おまけにcsvで結果をダウンロードする。 全体をさらーっと見ていくのはCSVが慣れているからである。 df_for_search.to_csv('output.csv',index=True) from google.colab import files files.download('output.csv') 5、まとめと今後の活動 今回はKaggleでまとまったデータセットをもとにデータ分析を行い、中古車価格の予測モデルを作成した。 今後は中古車のwebサイトよりグレードやオプション仕様等を特徴量として活用する為のスクレイピング、データクレンジングを実施し、より問題解決や企画提案できる実用的なモデルへと拡張を行なっていきたい。 今回Aidemyデータ分析3か月コースを受講して大まかにデータ分析の流れを通して興味のある分野をプログラミングで問題解決することを行った。 結論としては、受講して良かったと感じている。 カリキュラムのステップが細かく、完全に理解していなくとも進めることで全体が見えてくるようになり、 添削課題や成果物を自分で作成しようとしたときに主体的に考えて取り組むことができる。 プログラミングというツールを使用して何を行いたいのか、主体的であることがが重要だと改めて感じている。 どんな課題を解決したいのかや何を見つけたいのかの全体ストーリーがあれば、 プログラミングは非常に強力なツールとなり、今後の学びや創造的な工夫へのモチベーションにもつながる。 様々な言語があり、向き不向きもある上、情報の更新も速いが、前向きに捉えれば、生涯学習としてこれほどできると楽しいものはないだろうとも感じている。 今後もブログ等を活用して問題解決や付加価値を見つけるためのプログラミング活用の企画を行っていきたい。   参考文献/サイト ・Kaggleデータセット ・中古車予想モデル作成の考え方1 ・中古車予想モデル作成の考え方2 ・中古車予想モデル作成の考え方3 ・初手LightGBM」をする7つの理由 ・最低限知っておくべきデータの前処理  
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

深層強化学習(A2C)を用いたシステムトレーディング

はじめに  近年、人工知能ブームにより、人工知能を使ったトレーディング手法が盛んである。そこで、今回は深層強化学習を用いたシステムトレーディングを実施した。 まず、実際の深層強化学習モデルである。agentの行動として、 BUY、HOLD、SELLの三つの内一つを選択する。環境の戻り値として、状態(今現在保有しているポジションの価格、市場価格、手持ちのキャッシュ)、報酬(手持ちのキャッシュの変化値(含む益も含む))、終了(取引の終了か否か)、情報(ターミナルにディスプレイする情報)を返す。 使用データについて トレンド傾向の掴みやすさから、yahoo financeからGSPCの日足を使用した。 訓練データの期間:2015/1/1 - 2017/6/30 テストデータの期間:2017/7/1 - 2021/1/1 以下ソースコード TD学習 TD学習(時間的差分学習: Temporal Difference Learning)とは、代表的な価値ベース手法一つである。逐次的にデータ更新ができるのが特徴。 以下、TD学習での状態価値の更新式。 \Delta V(s) = r + \gamma V(s_{t+1}) - V(s_t) \\ A2C1  A2C(Advantage Actor-Critic)とは、A3C(Asynchronous Advantage Actor-Critic)から、非同期の部分を抜いたものであり、A3Cと比べてGPUの負荷が低い。  Q学習で使用したQ値は、状態価値関数V(s)とアドバンテージ値A(s, a)の2つに分解できる。このアドバンテージ関数とは、ある状態において、ある行動が他の行動に比較しどの程度優れているかを表す。価値関数とは、その状態に優位度を表す。  つまり、Actorはアドバンテージ値を通しQ値を学ぶ。これによりある行動の評価は、その行動がどれだけ良いかだけでなく、どれだけ良くなるかにも基づいて行われる。アドバンテージ関数の利点は、政策ネットワークの高い分散を減らし、モデルを安定させる。 A(s_t,a_t) = Q(s_t,a_t) - V(s_t) \\ 損失関数2 損失関数(更新式)は、損失関数(Actor)と損失関数(Critic)に分解できる。 L = L_v + L_π 損失関数(Actor)と損失関数(Critic)は以下の式に分解できる。アドバンテージ値A(s, a)は、一般的なTD学習での状態価値の更新式より、時系列を加味した形式になっている。Hはエントロピー。 L_v = (A(s_t,a_t))^2 \\ L_π = -log(π(a_t,s_t))A(s_t,a_t) - \beta H(π(s_t)) \\ A(s_t,a_t) \approx TD error= \sum_{k=1}^{n-1} \gamma^k r_{t+k} + \gamma^n V(s_{t+n}) - V(s_t) \\ H(π(s_t)) = 1/2(log(2π \sigma ^2) + 1) \\ \beta:ハイパーパラメータ、\gamma:割引係数、\sigma:分散 期待収益 G_t = \sum_{t'=t}^{T} \gamma ^{t'-t}r_{t'} \\ r:報酬,\gamma :割引係数 期待収益とは、行動に対する収益の期待を示したものである。割引係数により、現在より離れた報酬は小さくなるようになる。 売買ルール 1.空売りは認めない 2.ポジションを持っている場合、追加注文を出せない。 3.最後のステップでポジションを全て売却する。 4.ポジションは全買い、全売り 5.所持金は1000000ドル 実装と結果 ソースコードはこちら 前のqlearning、sarsaよりも、大幅に収益性、勝率、トレード回数とともに、改善された。 ソースコードはこちら V. Mnih, A. P. Badia, M. Mirza, A. Graves, T. Lillicrap, T. Harley, D. Silver, and K. Kavukcuoglu, “Asynchronous methods for deep rein- forcement learning,” in International conference on machine learning, (2016), pp. 1928–1937. ↩ S. Kuutti, R. Bowden, H. Joshi, R.D Temple, and S. Fallah, "End-to-end Reinforcement Learning for Autonomous Longitudinal Control Using Advantage Actor Critic with Temporal Context", IEEE Intelligent Transportation Systems Conference (ITSC),(2019) ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

元号正規表現

元号年を見つけるためだけの正規表現 大化の改新以降の日本元号にヒットすると思う import re re_gengo = re.compile("(令和|平[成治]|昭和|大[正永治同宝化]|明[治和暦応徳]|慶[応安長雲]|元[治文禄和亀中弘徳亨応仁久暦永慶]|文[久政化禄亀明正安和中保永応暦治]|万[延治寿]|安[政永貞元和]|嘉[永吉慶暦元禎禄応承保祥]|弘[化治和安長仁]|天[保明和正文授福養承治永仁喜元延禄徳暦慶安長応(?:平神護)(?:平宝字)(?:平勝宝)(?:平感宝)平]|享[和保禄徳]|寛[政延保文永正元喜治徳仁弘和平]|宝[暦永徳治亀]|延[享宝徳文元慶応久長喜暦]|正[徳保長平慶中和安応元嘉治暦]|貞[享治和永応元観]|承[応久元安徳暦保平和]|永[禄正享徳和仁万暦治久長保承祚延観]|長[享禄寛承治久暦元和保徳]|応[仁永安長保徳和]|康[正応暦安永元治和平保]|至徳|観応|暦[応仁]|建[徳武治長保暦永仁久]|興国|徳治|乾元|仁[治安平和寿]|寿永|養[和老]|治[承暦安]|保[元延安]|久[寿安]|昌泰|斉衡|神[(?:護景雲)亀]|霊亀|和銅|朱鳥|白雉)\s*([\d元〇一二三四五六七八九十壱弐参0-9]+\s*年)?") wikipedia より各元号の開始年月日をPython dictにしたもの 諸説あり開始年月日が仮説の場合もあった。。 g2d = { '令和': datetime(2019, 5, 1), '平成': datetime(1989, 1, 8), '昭和': datetime(1926, 12, 25), '大正': datetime(1912, 7, 30), '明治': datetime(1868, 10, 23), '慶応': datetime(1865, 5, 1), '元治': datetime(1864, 3, 27), '文久': datetime(1861, 3, 29), '万延': datetime(1860, 4, 8), '安政': datetime(1855, 1, 15), '嘉永': datetime(1848, 4, 1), '弘化': datetime(1845, 1, 9), '天保': datetime(1831, 1, 23), '文政': datetime(1818, 5, 26), '文化': datetime(1804, 3, 22), '享和': datetime(1801, 3, 19), '寛政': datetime(1789, 2, 19), '天明': datetime(1781, 4, 25), '安永': datetime(1772, 12, 10), '明和': datetime(1764, 6, 30), '宝暦': datetime(1751, 12, 14), '寛延': datetime(1748, 8, 5), '延享': datetime(1744, 4, 3), '寛保': datetime(1741, 4, 12), '元文': datetime(1736, 6, 7), '享保': datetime(1716, 8, 9), '正徳': datetime(1711, 6, 11), '宝永': datetime(1704, 4, 16), '元禄': datetime(1688, 10, 23), '貞享': datetime(1684, 4, 5), '天和': datetime(1681, 11, 9), '延宝': datetime(1673, 10, 30), '寛文': datetime(1661, 5, 23), '万治': datetime(1658, 8, 21), '明暦': datetime(1655, 5, 18), '承応': datetime(1652, 10, 20), '慶安': datetime(1648, 4, 7), '正保': datetime(1645, 1, 13), '寛永': datetime(1624, 4, 17), '元和': datetime(1615, 9, 5), '慶長': datetime(1596, 12, 16), '文禄': datetime(1593, 1, 10), '天正': datetime(1573, 8, 25), '元亀': datetime(1570, 5, 27), '永禄': datetime(1558, 3, 18), '弘治': datetime(1555, 11, 7), '天文': datetime(1532, 8, 29), '享禄': datetime(1528, 9, 3), '大永': datetime(1521, 9, 23), '永正': datetime(1504, 3, 16), '文亀': datetime(1501, 3, 18), '明応': datetime(1492, 8, 12), '延徳': datetime(1489, 9, 16), '長享': datetime(1487, 8, 9), '文明': datetime(1469, 6, 8), '応仁': datetime(1467, 4, 9), '文正': datetime(1466, 3, 14), '寛正': datetime(1461, 2, 1), '長禄': datetime(1457, 10, 16), '康正': datetime(1455, 9, 6), '享徳': datetime(1452, 8, 10), '宝徳': datetime(1449, 8, 16), '文安': datetime(1444, 2, 23), '嘉吉': datetime(1441, 3, 10), '永享': datetime(1429, 10, 3), '正長': datetime(1428, 6, 10), '応永': datetime(1394, 8, 2), '明徳': datetime(1390, 4, 12), '康応': datetime(1389, 3, 7), '嘉慶': datetime(1387, 10, 5), '至徳': datetime(1384, 3, 19), '永徳': datetime(1381, 3, 20), '康暦': datetime(1379, 4, 9), '永和': datetime(1375, 3, 29), '応安': datetime(1368, 3, 7), '貞治': datetime(1362, 10, 11), '康安': datetime(1361, 5, 4), '延文': datetime(1356, 4, 29), '文和': datetime(1352, 11, 4), '観応': datetime(1350, 4, 4), '貞和': datetime(1345, 11, 15), '康永': datetime(1342, 6, 1), '暦応': datetime(1338, 10, 11), '元中': datetime(1384, 5, 18), '弘和': datetime(1381, 3, 6), '天授': datetime(1375, 6, 26), '文中': datetime(1372, 5, 1), '建徳': datetime(1370, 8, 16), '正平': datetime(1347, 1, 20), '興国': datetime(1340, 5, 25), '延元': datetime(1336, 4, 11), '建武': datetime(1334, 3, 5), '正慶': datetime(1332, 5, 23), '元弘': datetime(1331, 9, 11), '元徳': datetime(1329, 9, 22), '嘉暦': datetime(1326, 5, 28), '正中': datetime(1324, 12, 25), '元亨': datetime(1321, 3, 22), '元応': datetime(1319, 5, 18), '文保': datetime(1317, 3, 16), '正和': datetime(1312, 4, 27), '応長': datetime(1311, 5, 17), '延慶': datetime(1308, 11, 22), '徳治': datetime(1307, 1, 18), '嘉元': datetime(1303, 9, 16), '乾元': datetime(1302, 12, 10), '正安': datetime(1299, 5, 25), '永仁': datetime(1293, 9, 6), '正応': datetime(1288, 5, 29), '弘安': datetime(1278, 3, 23), '建治': datetime(1275, 5, 22), '文永': datetime(1264, 3, 27), '弘長': datetime(1261, 3, 22), '文応': datetime(1260, 5, 24), '正元': datetime(1259, 4, 20), '正嘉': datetime(1257, 3, 31), '康元': datetime(1256, 10, 24), '建長': datetime(1249, 5, 2), '宝治': datetime(1247, 4, 5), '寛元': datetime(1243, 3, 18), '仁治': datetime(1240, 8, 5), '延応': datetime(1239, 3, 13), '暦仁': datetime(1238, 12, 30), '嘉禎': datetime(1235, 11, 1), '文暦': datetime(1234, 11, 27), '天福': datetime(1233, 5, 25), '貞永': datetime(1232, 4, 23), '寛喜': datetime(1229, 3, 31), '安貞': datetime(1228, 1, 18), '嘉禄': datetime(1225, 5, 28), '元仁': datetime(1224, 12, 31), '貞応': datetime(1222, 5, 25), '承久': datetime(1219, 5, 27), '建保': datetime(1214, 1, 18), '建暦': datetime(1211, 4, 23), '承元': datetime(1207, 11, 16), '建永': datetime(1206, 6, 5), '元久': datetime(1204, 3, 23), '建仁': datetime(1201, 3, 19), '正治': datetime(1199, 5, 23), '建久': datetime(1190, 5, 16), '文治': datetime(1185, 9, 9), '元暦': datetime(1184, 5, 27), '寿永': datetime(1182, 6, 29), '養和': datetime(1181, 8, 25), '治承': datetime(1177, 8, 29), '安元': datetime(1175, 8, 16), '承安': datetime(1171, 5, 27), '嘉応': datetime(1169, 5, 6), '仁安': datetime(1166, 9, 23), '永万': datetime(1165, 7, 14), '長寛': datetime(1163, 5, 4), '応保': datetime(1161, 9, 24), '永暦': datetime(1160, 2, 18), '平治': datetime(1159, 5, 9), '保元': datetime(1156, 5, 18), '久寿': datetime(1154, 12, 4), '仁平': datetime(1151, 2, 14), '久安': datetime(1145, 8, 12), '天養': datetime(1144, 3, 28), '康治': datetime(1142, 5, 25), '永治': datetime(1141, 8, 13), '保延': datetime(1135, 6, 10), '長承': datetime(1132, 9, 21), '天承': datetime(1131, 2, 28), '大治': datetime(1126, 2, 15), '天治': datetime(1124, 5, 18), '保安': datetime(1120, 5, 9), '元永': datetime(1118, 4, 25), '永久': datetime(1113, 8, 25), '天永': datetime(1110, 7, 31), '天仁': datetime(1108, 9, 9), '嘉承': datetime(1106, 5, 13), '長治': datetime(1104, 3, 8), '康和': datetime(1099, 9, 15), '承徳': datetime(1097, 12, 27), '永長': datetime(1097, 1, 3), '嘉保': datetime(1095, 1, 23), '寛治': datetime(1087, 5, 11), '応徳': datetime(1084, 3, 15), '永保': datetime(1081, 3, 22), '承暦': datetime(1077, 12, 5), '承保': datetime(1074, 9, 16), '延久': datetime(1069, 5, 6), '治暦': datetime(1065, 9, 4), '康平': datetime(1058, 9, 19), '天喜': datetime(1053, 2, 2), '永承': datetime(1046, 5, 22), '寛徳': datetime(1044, 12, 16), '長久': datetime(1040, 12, 16), '長暦': datetime(1037, 5, 9), '長元': datetime(1028, 8, 18), '万寿': datetime(1024, 8, 19), '治安': datetime(1021, 3, 17), '寛仁': datetime(1017, 5, 21), '長和': datetime(1013, 2, 8), '寛弘': datetime(1004, 8, 8), '長保': datetime(999, 2, 1), '長徳': datetime(995, 3, 25), '正暦': datetime(990, 11, 26), '永祚': datetime(989, 9, 10), '永延': datetime(987, 5, 5), '寛和': datetime(985, 5, 19), '永観': datetime(983, 5, 29), '天元': datetime(978, 12, 31), '貞元': datetime(976, 8, 11), '天延': datetime(974, 1, 16), '天禄': datetime(970, 5, 3), '安和': datetime(968, 9, 8), '康保': datetime(964, 8, 19), '応和': datetime(961, 3, 5), '天徳': datetime(957, 11, 21), '天暦': datetime(947, 5, 15), '天慶': datetime(938, 6, 22), '承平': datetime(931, 5, 16), '延長': datetime(923, 5, 29), '延喜': datetime(901, 8, 31), '昌泰': datetime(898, 5, 20), '寛平': datetime(889, 5, 30), '仁和': datetime(885, 3, 11), '元慶': datetime(877, 6, 1), '貞観': datetime(859, 5, 20), '天安': datetime(857, 3, 20), '斉衡': datetime(854, 12, 23), '仁寿': datetime(851, 6, 1), '嘉祥': datetime(848, 7, 16), '承和': datetime(834, 2, 14), '天長': datetime(824, 2, 8), '弘仁': datetime(810, 10, 20), '大同': datetime(806, 6, 8), '延暦': datetime(782, 9, 30), '天応': datetime(781, 1, 30), '宝亀': datetime(770, 10, 23), '神護景雲': datetime(767, 9, 13), '天平神護': datetime(765, 2, 1), '天平宝字': datetime(757, 9, 6), '天平勝宝': datetime(749, 8, 19), '天平感宝': datetime(749, 5, 4), '天平': datetime(729, 9, 2), '神亀': datetime(724, 3, 3), '養老': datetime(717, 12, 24), '霊亀': datetime(715, 10, 3), '和銅': datetime(708, 2, 7), '慶雲': datetime(704, 6, 16), '大宝': datetime(701, 5, 3), '朱鳥': datetime(686, 8, 14), '白雉': datetime(650, 3, 22), '大化': datetime(645, 7, 17) }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

python の SpeechRecognition で長めの wav ファイルを日本語音声認識する

pip install SpeechRecognition でインストールできる python の SpeechRecognition ライブラリでは、日本語音声認識向けとして主に Google Speech Recognition Google Cloud Speech API が使えます。が、前者は長めの wav ファイルが使いづらく後者は有償ということで、試験運用的に前者で済ませられないかと格闘したメモです。 import speech_recognition as sr import time r = sr.Recognizer() # https://github.com/Uberi/speech_recognition/blob/master/speech_recognition/__init__.py#L500-L509 r.dynamic_energy_threshold = False # デフォルトだと True だが、音声が長めになりがちなので False にしている r.energy_threshold = 500 # minimum audio energy to consider for recording (300) r.phrase_threshold = 0.2 # minimum seconds of speaking audio before we consider the speaking audio a phrase - values below this are ignored (for filtering out clicks and pops) (0.3) r.pause_threshold = 0.1 # seconds of non-speaking audio before a phrase is considered complete (0.8) r.non_speaking_duration = 0.1 # seconds of non-speaking audio to keep on both sides of the recording (0.5) with sr.AudioFile("long.wav") as source: while True: audio_data = r.listen(source) # flac でリクエストしているので、flac でのバイト数を気にする # https://github.com/Uberi/speech_recognition/blob/master/speech_recognition/__init__.py#L866-L877 flac_data = audio_data.get_flac_data( convert_rate=None if audio_data.sample_rate >= 8000 else 8000, # audio samples must be at least 8 kHz convert_width=2 # audio samples must be 16-bit ) print("len: {} ← 10MB に近づくとエラーになりやすいので、サイズを見てパメータをいじる".format(len(flac_data))) try: print(r.recognize_google(audio_data, language='ja-JP')) except sr.UnknownValueError: print("Oops! Didn't catch that") # セグメントによっては音声認識できない、無視 if len(flac_data) == 0: # ファイルの終わり break time.sleep(1) # ちょっと投げすぎを気にしている と言っても、Uberi/speech_recognition には音声の無音を検知して区切る機能が既にあるので、リクエスト不能な長さ(10MBぐらいで Bad request になる)で投げないようパラメータをチューニングするだけです。デフォルトで dynamic_energy_threshold は True ですが、True のままパラメータをチューニングするのが辛かったので False にしています。 1 度のリクエストが 10MB 近くにならなければ問題ないので、元の wav ファイルを固定長でバラバラにしておく方法もありますが、上記チューニングで対応することで、音声の途中で切れたりするケースを若干減らせそうです。 なお結局、YouTube の字幕機能の方が認識精度が良さそうだったので現状あんまり使ってはいないですが……。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonの画像処理ライブラリPillow(PIL)の読込み・保存まとめ (JPEG編)

はじめに Pythonの画像処理ライブラリPillowは、手軽に画像の読み込み~加工~保存ができる大変便利なライブラリです。ただし、画像の読込み・保存の詳細について、日本語で書かれたものが少なかったのでまとめました。 本稿は、JPEGの読込み・保存について記載します。 情報は2021年7月時点のものです。PillowのWebページの情報をもとにしました。 JPEGの読込み 読込み & パラメータ表示の例 open.py from PIL import Image im = Image.open("./input.jpg") print(im.info.items()) 読込みパラメータ一覧 読込んだ画像のパラメータは、オブジェクトのinfoプロパティに、ディクショナリ形式で格納されます。 パラメータ 説明 jfif .jfifファイル = True jfif_version jtifのバージョン番号(メジャー, マイナー) jfif_density 水平、垂直 解像度(画素密度) jfif_unit 解像度の単位(0 : 単位なし、1 : dpi、2 : dpcm) dpi dpi(水平、垂直) adobe Adobe JPEGファイル = True adobe_transform ベンダー固有のタグ progression プログレッシブJPEG = True icc_profile ICCプロファイルの内容 exif EXIFデータの内容 comment コメントの内容 JPEGの保存 Image.save(fp, format=None, **params) fp : ファイル名、pathlib.Pathオフジェクト、またはファイルオブジェクト format : 保存するファイル形式(省略可) params : 保存するパラメータ(省略可) 保存の例 save.py from PIL import Image im = Image.open("./input.jpg") im.save('./output.jpg', 'JPEG' ,quality=95, optimize=True, progressive=True, dpi=im.info.get('dpi'), icc_profile=im.info.get('icc_profile'), exif=im.info.get('exif'), subsampling=-1, qtables='keep') 保存パラメータ一覧 パラメータ 説明 quality 保存画像の品質。0(最低)~95(最高)。初期値は75。(95以上は使用しない) optimize True = 最適化して保存 progressive True = プログレッシブJPEGで保存 dpi dpi(水平、垂直)の指定 icc_profile True = ICCプロファイルを保存。既存のプロファイルを指定する場合 = icc_profile=im.info.get('icc_profile') exif 保存するEXIFデータの指定 subsampling 色差成分の保存形式を指定。 -1 : 元のフォーマット、 0 : 4:4:4、 1 : 4:2:2、 2 : 4:2:0 qtables エンコーダーで使用するQテーブルの指定。64個の整数のリストやディクショナリ形式でも指定できますが、"keep"、”web_low”、"web_high"といった名前でも指定できます。画像のサイズを小さくできますが、半面、精細感が失われます。 その他 あとは、pngとgifについて、解説記事を書きたいです。 蛇足。JPEGの量子化テーブルは普通に『Q Table』なんですね。動画のエンコーダー & デコーダーを開発していたときは、『キューマト(Q Matrixの略)』って呼んでました。 参考リンク
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

③NiceHashマイニング収益をLambda×LINE Notifyでグラフ化してLINE通知する

目次 1.背景 2.構成 2-1.Lambdaの構築 2-1-1.Lambda関数の作成 2-1-2.IAMロールの権限付与 2-1-3.ソースコード 2-1-3-1.Lambda①:nicehash-balance-notification-LINE のソース 2-1-3-2.Lambda②:nicehash-draw-figure のソース 2-1-3-3.コンフィグ 2-1-4.Module組み込み 2-1-5.基本設定の編集 2-2.NiceHash APIによる収益情報取得 2-3.EventBridgeによるトリガー定義 2-4.S3バケットの作成 2-5.LINE NotifyによるLINE通知 2-6.RDBの作成 3.実行結果 4.終わりに 5.更新履歴 1.背景  「②NiceHashマイニング収益をAWS Lambda×LINE NotifyでLINE通知する」で日々のマイニング収益を定期的に通知するシステムを構築してから数カ月経って、統計データがたまってきていたので収益と円相場の変遷をグラフ化したいと思った。 2.構成 システム構成は、AWS Lambdaベースのアーキテクチャ。 処理の流れ  ①. EventBridgeの日次実行cronがトリガーとなり、   [Lambda①]nicehash-balance-notification-LINEがキックされる  ②. 外部APIから当日のマイニング収益情報を取得  ③. MySQL on EC2に当日の収益情報をレコード追加  ④. MySQL on EC2から収益情報の統計を取得  ⑤. S3へ収益の統計情報をアップロード  ⑥. [Lambda②]nicehash-draw-figureのinvoke  ⑦. 残高/円相場の統計情報をグラフ描画し、S3へアップロード  ⑧. S3から統計情報グラフをダウンロード  ⑨. POSTで通知メッセージ/統計情報グラフをLINE Notifyへ渡して    スマホへLINE上で通知 2-1.Lambdaの構築 2-1-1.Lambda関数の作成 呼び出されるLambda関数本体を作成する ・Lambdaデプロイ上限250MB回避のため、Lambda関数を2つに分けて作成 【Lambda①】  関数名:「nicehash-balance-notification-LINE」  ランタイム:「Python 3.6」  ※DBへアクセスするためVPC指定も必要 【Lambda②】  関数名:「nicehash-draw-figure」  ランタイム:「Python 3.7」 2-1-2.IAMロールの権限付与 サービス間アクセスに必要となる権限をLambdaへ付与する Lambda①:nicehash-balance-notification-LINEに対して、下記の権限を付与する。 S3に対するread/write権限 EC2に対するアクセス権限 Lambdaに対するinvoke権限 Lambda②:nicehash-draw-figureに対して、下記の権限を付与する。 S3に対するwrite権限 2-1-3.ソースコード 2-1-3-1.Lambda①:nicehash-balance-notification-LINE のソース nicehash-balance-notification-LINE nicehash-balance-notification-LINE/ ├ lambda_function.py ├ db_data_deal.py ├ nicehash.py ├ marketrate.py ├ s3_deal.py ├ create_message.py ├ line_config.py ├ mysql_config.py ├ nicehash_config.py └ s3_config.py Lambda①メインプログラム lambda_function.py import json import requests import os import datetime import boto3 import db_data_deal import s3_deal import create_message import mysql_config as MYSQLconfig import s3_config as S3config import line_config as LINEconfig ### AWS Lambda handler method def lambda_handler(event, context): Messenger = create_message.create_messenger() (today_balance,market_price) = Messenger.get_balance_and_rate() Sqldealer = db_data_deal.sqldealer() db_data_dict = Sqldealer.road_data() Sqldealer.insert_data(today_balance,market_price) db_data_dict = Sqldealer.road_data() Sqldealer.save_dict_to_json(df_dict = db_data_dict, out_path = S3config.dict_file_path) S3dealer = s3_deal.s3_dealer(bucket=S3config.bucket) S3dealer.save_dict_to_s3(local_file_path=S3config.dict_file_path,file_name_base=S3config.dict_file_name_base) ### Call Lambda②(nicehash-draw-figure) response = boto3.client('lambda').invoke( FunctionName='nicehash-draw-figure', InvocationType='RequestResponse', Payload=json.dumps(db_data_dict, cls = db_data_deal.DateTimeEncoder) ) get_file_local_path = S3dealer.get_s3_file_item(file_name_base=S3config.figure_file_name_base) msg = Messenger.create_notification_msg(db_data_dict) notify(msg,get_file_local_path) print(msg) def notify(msg, *args): headers = {"Authorization": "Bearer %s" % LINEconfig.LINE_NOTIFY_ACCESS_TOKEN} url = "https://notify-api.line.me/api/notify" payload = {'message': msg} if len(args) == 0: requests.post(url, data=payload, headers=headers) else: files = {"imageFile": open(args[0], "rb")} requests.post(url, data=payload, headers=headers,files=files) os.remove(args[0]) return 0 DBからの情報取得/DB更新処理を行うクラス db_data_deal.py import os import json from json import JSONEncoder import mysql.connector import boto3 import datetime import mysql_config as SQLconfig class sqldealer: def __init__(self): self.connection = mysql.connector.connect(user=SQLconfig.user, password=SQLconfig.password, host=SQLconfig.host, database=SQLconfig.database) self.columns = ['db-id','date','balance','diff','market'] self.db_data_dict = dict() ### Get statistics information from MySQL def road_data(self): try: with self.connection.cursor() as cur: select_sql = 'SELECT * FROM nicehash_info;' cur.execute(select_sql) row_db_data = cur.fetchall() except: err_msg = 'Error001:DB-data取得に失敗' print(err_msg) return err_msg return self.datashaping_tuple_to_dict(row_db_data) ### Insert today's balance record into MySQL def insert_data(self,today_balance,market_price): try: db_id = self.db_data_dict['db-id'][-1] + 1 date = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=9))).strftime('%Y-%m-%d') balance = today_balance diff = today_balance - self.db_data_dict['balance'][-1] insert_info = str(db_id)+','+'"'+date+'"'+','+str(balance)+','+str(diff)+','+str(market_price) insert_sql = 'INSERT INTO nicehash_info VALUES('+insert_info+');' with self.connection.cursor() as cur: cur.execute(insert_sql) cur.execute('commit;') print(insert_sql) except: err_msg = 'Error002:DB更新に失敗' print(err_msg) return err_msg ### Cast DB row data to dict type def datashaping_tuple_to_dict(self,tupple_data): try: db_data_list = [[],[],[],[],[]] db_data_dict = dict() for i in range(len(tupple_data)): for j in range(len(tupple_data[i])): db_data_list[j].append(tupple_data[i][j]) self.db_data_dict = dict(zip(self.columns, db_data_list)) except: err_msg = 'Error003:DBデータの型変換に失敗' print(err_msg) return err_msg return self.db_data_dict ### Save dict object in json format def save_dict_to_json(self,df_dict,out_path): if os.path.exists(out_path): with open(out_path,'w') as json_obj: json_obj.write("") print("jsonファイル作成") with open(out_path, 'w') as json_file: json.dump(df_dict, json_file, cls = DateTimeEncoder) class DateTimeEncoder(JSONEncoder): ### Override the default method def default(self, obj): if isinstance(obj, (datetime.date, datetime.datetime)): return obj.isoformat() NiceHashから残高情報を取得するためのクラス nicehash.py from datetime import datetime from time import mktime import uuid import hmac import requests import json from hashlib import sha256 import optparse import sys class private_api: def __init__(self, host, organisation_id, key, secret, verbose=False): self.key = key self.secret = secret self.organisation_id = organisation_id self.host = host self.verbose = verbose def request(self, method, path, query, body): xtime = self.get_epoch_ms_from_now() xnonce = str(uuid.uuid4()) message = bytearray(self.key, 'utf-8') message += bytearray('\x00', 'utf-8') message += bytearray(str(xtime), 'utf-8') message += bytearray('\x00', 'utf-8') message += bytearray(xnonce, 'utf-8') message += bytearray('\x00', 'utf-8') message += bytearray('\x00', 'utf-8') message += bytearray(self.organisation_id, 'utf-8') message += bytearray('\x00', 'utf-8') message += bytearray('\x00', 'utf-8') message += bytearray(method, 'utf-8') message += bytearray('\x00', 'utf-8') message += bytearray(path, 'utf-8') message += bytearray('\x00', 'utf-8') message += bytearray(query, 'utf-8') if body: body_json = json.dumps(body) message += bytearray('\x00', 'utf-8') message += bytearray(body_json, 'utf-8') digest = hmac.new(bytearray(self.secret, 'utf-8'), message, sha256).hexdigest() xauth = self.key + ":" + digest headers = { 'X-Time': str(xtime), 'X-Nonce': xnonce, 'X-Auth': xauth, 'Content-Type': 'application/json', 'X-Organization-Id': self.organisation_id, 'X-Request-Id': str(uuid.uuid4()) } s = requests.Session() s.headers = headers url = self.host + path if query: url += '?' + query if self.verbose: print(method, url) if body: response = s.request(method, url, data=body_json) else: response = s.request(method, url) if response.status_code == 200: return response.json() elif response.content: raise Exception(str(response.status_code) + ": " + response.reason + ": " + str(response.content)) else: raise Exception(str(response.status_code) + ": " + response.reason) def get_epoch_ms_from_now(self): now = datetime.now() now_ec_since_epoch = mktime(now.timetuple()) + now.microsecond / 1000000.0 return int(now_ec_since_epoch * 1000) def algo_settings_from_response(self, algorithm, algo_response): algo_setting = None for item in algo_response['miningAlgorithms']: if item['algorithm'] == algorithm: algo_setting = item if algo_setting is None: raise Exception('Settings for algorithm not found in algo_response parameter') return algo_setting def get_accounts(self): return self.request('GET', '/main/api/v2/accounting/accounts2/', '', None) def get_accounts_for_currency(self, currency): return self.request('GET', '/main/api/v2/accounting/account2/' + currency, '', None) def get_withdrawal_addresses(self, currency, size, page): params = "currency={}&size={}&page={}".format(currency, size, page) return self.request('GET', '/main/api/v2/accounting/withdrawalAddresses/', params, None) def get_withdrawal_types(self): return self.request('GET', '/main/api/v2/accounting/withdrawalAddresses/types/', '', None) def withdraw_request(self, address_id, amount, currency): withdraw_data = { "withdrawalAddressId": address_id, "amount": amount, "currency": currency } return self.request('POST', '/main/api/v2/accounting/withdrawal/', '', withdraw_data) def get_my_active_orders(self, algorithm, market, limit): ts = self.get_epoch_ms_from_now() params = "algorithm={}&market={}&ts={}&limit={}&op=LT".format(algorithm, market, ts, limit) return self.request('GET', '/main/api/v2/hashpower/myOrders', params, None) def create_pool(self, name, algorithm, pool_host, pool_port, username, password): pool_data = { "name": name, "algorithm": algorithm, "stratumHostname": pool_host, "stratumPort": pool_port, "username": username, "password": password } return self.request('POST', '/main/api/v2/pool/', '', pool_data) def delete_pool(self, pool_id): return self.request('DELETE', '/main/api/v2/pool/' + pool_id, '', None) def get_my_pools(self, page, size): return self.request('GET', '/main/api/v2/pools/', '', None) def get_hashpower_orderbook(self, algorithm): return self.request('GET', '/main/api/v2/hashpower/orderBook/', 'algorithm=' + algorithm, None ) def create_hashpower_order(self, market, type, algorithm, price, limit, amount, pool_id, algo_response): algo_setting = self.algo_settings_from_response(algorithm, algo_response) order_data = { "market": market, "algorithm": algorithm, "amount": amount, "price": price, "limit": limit, "poolId": pool_id, "type": type, "marketFactor": algo_setting['marketFactor'], "displayMarketFactor": algo_setting['displayMarketFactor'] } return self.request('POST', '/main/api/v2/hashpower/order/', '', order_data) def cancel_hashpower_order(self, order_id): return self.request('DELETE', '/main/api/v2/hashpower/order/' + order_id, '', None) def refill_hashpower_order(self, order_id, amount): refill_data = { "amount": amount } return self.request('POST', '/main/api/v2/hashpower/order/' + order_id + '/refill/', '', refill_data) def set_price_hashpower_order(self, order_id, price, algorithm, algo_response): algo_setting = self.algo_settings_from_response(algorithm, algo_response) price_data = { "price": price, "marketFactor": algo_setting['marketFactor'], "displayMarketFactor": algo_setting['displayMarketFactor'] } return self.request('POST', '/main/api/v2/hashpower/order/' + order_id + '/updatePriceAndLimit/', '', price_data) def set_limit_hashpower_order(self, order_id, limit, algorithm, algo_response): algo_setting = self.algo_settings_from_response(algorithm, algo_response) limit_data = { "limit": limit, "marketFactor": algo_setting['marketFactor'], "displayMarketFactor": algo_setting['displayMarketFactor'] } return self.request('POST', '/main/api/v2/hashpower/order/' + order_id + '/updatePriceAndLimit/', '', limit_data) def set_price_and_limit_hashpower_order(self, order_id, price, limit, algorithm, algo_response): algo_setting = self.algo_settings_from_response(algorithm, algo_response) price_data = { "price": price, "limit": limit, "marketFactor": algo_setting['marketFactor'], "displayMarketFactor": algo_setting['displayMarketFactor'] } return self.request('POST', '/main/api/v2/hashpower/order/' + order_id + '/updatePriceAndLimit/', '', price_data) def get_my_exchange_orders(self, market): return self.request('GET', '/exchange/api/v2/myOrders', 'market=' + market, None) def get_my_exchange_trades(self, market): return self.request('GET','/exchange/api/v2/myTrades', 'market=' + market, None) def create_exchange_limit_order(self, market, side, quantity, price): query = "market={}&side={}&type=limit&quantity={}&price={}".format(market, side, quantity, price) return self.request('POST', '/exchange/api/v2/order', query, None) def create_exchange_buy_market_order(self, market, quantity): query = "market={}&side=buy&type=market&secQuantity={}".format(market, quantity) return self.request('POST', '/exchange/api/v2/order', query, None) def create_exchange_sell_market_order(self, market, quantity): query = "market={}&side=sell&type=market&quantity={}".format(market, quantity) return self.request('POST', '/exchange/api/v2/order', query, None) def cancel_exchange_order(self, market, order_id): query = "market={}&orderId={}".format(market, order_id) return self.request('DELETE', '/exchange/api/v2/order', query, None) CoinGeckoを利用して仮装通貨相場をリアルタイムで取得するクラス marketrate.py import requests import json class trade_table: def __init__(self, market="BTC"): ### currency-name conversion table self.currency_rename_table = {'BTC':'Bitcoin','ETH':'Ethereum','LTC':'Litecoin', 'XRP':'XRP','RVN':'Ravencoin','MATIC':'Polygon', 'BCH':'Bitcoin Cash','XLM':'Stellar','XMR':'Monero','DASH':'Dash'} self.market = self.currency_rename_table[market] def get_rate(self): body = requests.get('https://api.coingecko.com/api/v3/coins/markets?vs_currency=jpy') coingecko = json.loads(body.text) idx = 0 while coingecko[idx]['name'] != self.market: idx += 1 if idx > 100: return "trade_table_err" else: return int(coingecko[idx]['current_price']) 残高の統計情報及び統計グラフをS3バケットへread/writeするクラス s3_deal.py import boto3 import json import datetime class s3_dealer: def __init__(self, bucket = 'nice-hash-graph-backet'): self.datestamp = str(datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=9))).strftime('%Y-%m-%d')) self.s3 = boto3.resource('s3') self.bucket = self.s3.Bucket(bucket) def save_dict_to_s3(self, local_file_path, file_name_base = 'balance_stat_data'): file_name = file_name_base + '_' + self.datestamp + '.json' self.bucket.upload_file(Filename=local_file_path,Key=file_name) print("Completed json object upload to s3...") def save_figure_to_s3(self, local_file_path, file_name_base = 'balance_stat_graph'): file_name = file_name_base + '_' + self.datestamp + '.png' self.bucket.upload_file(Filename=local_file_path,Key=file_name) print("Completed figure upload to s3...") def get_s3_file_item(self, file_name_base = 'balance_stat_graph'): file_name = file_name_base + '_' + self.datestamp + '.png' local_file_path = '/tmp/'+file_name self.bucket.download_file(Filename=local_file_path,Key=file_name) print("Data download from s3 is completed...") return local_file_path LINE通知メッセージの作成するクラス create_message.py import datetime import nicehash import marketrate import nicehash_config as NICEHASHconfig class create_messenger: def __init__(self): self.host = 'https://api2.nicehash.com' self.organisation_id = NICEHASHconfig.organisation_id self.key = NICEHASHconfig.key self.secret = NICEHASHconfig.secret self.market='BTC' def get_balance_and_rate(self): host = 'https://api2.nicehash.com' ### Get mining information from NiceHash API PrivateApi = nicehash.private_api(self.host, self.organisation_id, self.key, self.secret) accounts_info = PrivateApi.get_accounts_for_currency(self.market) balance_row = float(accounts_info['totalBalance']) ### Get currency_to_JPY_rate from CoinGecko API TradeTable = marketrate.trade_table(self.market) rate = TradeTable.get_rate() balance_jpy = int(balance_row*rate) return (balance_jpy,rate) def create_notification_msg(self, df_dict): diff = df_dict['diff'][-1] rate = df_dict['market'][-1] balance = df_dict['balance'][-1] pre_balance = df_dict['balance'][-2] ### Create nortification message time_text = "時刻: " + str(datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=9))))[:19] market_text = "仮想通貨: " + self.market rate_text = "単位仮想通貨価値: " + str(rate) + "円" balance_text = "現在の残高: " + str(balance) + "円" pre_balance_text = "昨日の残高: " + str(pre_balance) + "円" symbol = "+" if diff > 0 else "" diff_txt = "【日次収益: " + str(symbol) + str(diff) + "円】" mon_revenue = "推定月次収益: " + str(diff*30) + "円" ann_revenue = "推定年次収益: " + str(diff*365) + "円" msg = '\n'.join(["",time_text,market_text,rate_text,balance_text,pre_balance_text,diff_txt,mon_revenue,ann_revenue]) return msg 2-1-3-2.Lambda②:nicehash-draw-figure のソース nicehash-draw-figure nicehash-draw-figure/ ├ lambda_function.py ├ plot_stat.py ├ s3_deal.py └ s3_config.py nicehash-balance-notification-LINEによって呼び出されるメインプログラム lambda_function.py import json import plot_stat import s3_deal import s3_config as S3config ### AWS Lambda handler method def lambda_handler(event, context): df_dict = event ### Graph drawing of statistical data Statdrawer = plot_stat.statdrawer() Statdrawer.drawfig(df_dict) S3dealer = s3_deal.s3_dealer(bucket=S3config.bucket) S3dealer.save_figure_to_s3(local_file_path=S3config.figure_file_path,file_name_base=S3config.figure_file_name_base) return event 統計情報をグラフ描画するクラス plot_stat.py import matplotlib.pyplot as plt import datetime import s3_config as S3config class statdrawer: def __init__(self): self.fig = plt.figure() def drawfig(self,df_dict): ax1 = self.fig.add_subplot(111) date1 = [datetime.datetime.strptime(str(s),'%Y-%m-%d') for s in df_dict['date']] ### Balance drawing ln1=plt.bar(date1,df_dict['balance'], width=0.5,linewidth=0.5,label='Balance') h1, l1 = ax1.get_legend_handles_labels() ax1.set_xlabel('Date') ax1.set_ylabel('Balance [yen]') ax1.grid(True) plt.xticks(rotation=45,fontsize=6) ### Adjustment of drawing range ax1_min = min(df_dict['balance']) ax1_max = max(df_dict['balance']) ax1.set_ylim(ax1_min, ax1_max*1.1) ax2_min = min(df_dict['market']) ax2_max = max(df_dict['market']) ax2 = ax1.twinx() ### Market price drawing ln2=plt.plot(date1,df_dict['market'], color="red", linestyle="solid", markersize=8, label='BTC-rate') ax2.set_ylabel('BTC-rate [million]') ax2.set_ylim(ax2_min*0.9, ax2_max*1.01) h2, l2 = ax2.get_legend_handles_labels() ax1.legend(h1+h2, l1+l2, loc='lower left') ### Output of statistical graph self.fig.subplots_adjust(bottom=0.1) self.fig.savefig(S3config.figure_file_path,bbox_inches='tight') 2-1-3-3.コンフィグ 各サービス/外部APIと連携するためにコンフィグに必要な設定値を指定する 下記コンフィグの設定値詳細については、②NiceHashマイニング収益をAWS Lambda×LINE NotifyでLINE通知するを参照。 line_config.py LINE_NOTIFY_ACCESS_TOKEN = '[LINEアクセストークン]' ### ※設定値は2-5節参照 mysql_config.py user='[MySQLアクセスユーザ]' password='[MySQLアクセスユーザpw]' host='[EC2インスタンスの静的IP]' database='[MySQLに構築したDatabase名]' nicehash_config.py organisation_id = '[NiceHash組織ID]' key = '[NiceHash APIアクセスキー]' secret = '[NiceHash APIシークレットアクセスキー]' ### ※設定値は2-2節参照 s3_config.py bucket = '[S3バケット名]' dict_file_name_base = 'balance_stat_data' dict_file_path = '/tmp/balance_stat_data.json' figure_file_name_base = 'balance_stat_graph' figure_file_path = '/tmp/balance_stat_graph.png' ### ※設定値は2-4節参照 2-1-4.Module組み込み 実行に必要なパッケージを取り込む ・Lambda①:nicehash-balance-notification-LINEには「mysql-connector-python」が必要なので、AWS Cloud9上でディレクトリを切って、下記コマンドを実行して環境を整備する。LambdaへのデプロイもCloud9上で行う。 nicehash-balance-notification-LINE ec2-user:~/environment (master) $ mkdir nicehash-balance-notification-LINE ec2-user:~/environment (master) $ cd nicehash-balance-notification-LINE ec2-user:~/environment/nicehash-balance-notification-LINE (master) $ pip install -t ./ mysql-connector-python ・Lambda②:nicehash-draw-figureについても「matplotlib」が必要なので、同様にCloud9上にディレクトリを切って環境を整備しLambdaへデプロイする。 nicehash-draw-figure ec2-user:~/environment (master) $ mkdir nicehash-draw-figure ec2-user:~/environment (master) $ cd nnicehash-draw-figure ec2-user:~/environment/nicehash-draw-figure (master) $ pip install -t ./ matplotlib 2-1-5.基本設定の編集 メモリ/タイムアウトエラーを回避するために基本設定値を変更する ・Lambdaはデフォルトだと、メモリ:128MB、タイムアウト:3秒になっているため、実行状況の様子をみてメモリ「128MB ⇒ 200MB」、タイムアウト「3秒 ⇒ 15秒」程度へ変更しておく。 2-2.NiceHash APIによる収益情報取得 外部APIからマイニング収益情報を取得できるようKEYを取得する ・API Keys取得手順はこちらを参照。 2-3.EventBridgeによるトリガー定義 日次ジョブとしてLambdaをキックするためのトリガーを定義する ・下記トリガーを作成して、Lambda①:nicehash-balance-notification-LINEにアタッチする ルール:「新規ルールの作成」 ルール名:DailyTrigger ルールタイプ:スケジュール式 スケジュール式:cron(0 15 * * ? *) # 毎日0:00に実行するcron 2-4.S3バケットの作成 ファイルの受け渡しを行うS3バケットを用意する ・s3_config.pyに指定したバケットをあらかじめS3上に作成しておく 2-5.LINE NotifyによるLINE通知 AWSへLINE Nortifyを連携するために必要なトークンを発行する ・LINE連携、Access tokenの取得方法については、こちら を参照。 2-6.RDBの作成 収益の統計情報を管理するDBを用意する ・RDBでNiceHash収益の統計情報を管理するために、EC2上にMySQLを導入する  ※Amazon RDSを使うべきだが、料金的な都合からMySQL on EC2で代用 ・Lambda①:nicehash-balance-notification-LINEを配置したVPC上の同サブネットにEC2インスタンスを作成 ・作成したEC2インスタンスにMySQLをインストール  ※MySQLのインストールはこの辺を参照 ・シンプルなテーブル一つで事足りるので、とりあえず下記のようにnicehash_infoテーブルを定義 nicehash_infoテーブル構造 mysql> SHOW COLUMNS FROM nicehash_info; +---------+------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +---------+------+------+-----+---------+----------------+ | id | int | NO | PRI | NULL | auto_increment | | date | date | YES | | NULL | | | balance | int | NO | | NULL | | | diff | int | YES | | NULL | | | market | int | NO | | NULL | | +---------+------+------+-----+---------+----------------+ 5 rows in set (0.00 sec) id:レコードID date:日付 balance:残高 diff:前日残高との差分 market:BTCの円相場 3. 実行結果 ・毎日0:00になると、日次収益と残高/円相場の変遷グラフがLINE通知されるようになりました。 ・5月の暴落が顕著すぎて、分かってたけど悲しくなった。。。 4. 終わりに ・pandas.DataFrameなどは使わずlistやdict等の組み込み関数のみで実装したが、パッケージの都合上、250MB以内には収められなかったのでLambdaのデプロイ上限250MB(圧縮50MB)は結構ボトルネックになると痛感した。。。 ・そもそもLambdaは、機能毎に分割した最小単位で定義してシステムはLambdaを組み合わせて構築するものという前提があるのかと思った。 5. 更新履歴 ver. 1.0 初版投稿 2021/07/24
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

多期間にわたる在庫モデルをGoogle OR-toolで解く

はじめに 今回は数理最適化の練習として、多期間にわたる在庫モデルの問題をPythonを使って解いてみたいと思います。 制約条件が複雑化したときの定式化の参考になれば幸いです。 問題概要 参考例題としてMathworksのHPで公開されていたMATLAB用の例題を一部※使います。 ※本当は全部解きたかったんですが、変数設定と制約条件が200近くあり、これをうまくForループで処理することができなかったため。そのうちやります。 問題は以下のようになります。 経時的コスト変動が予測できるさまざまな原料を使用した、ある期間の配合肥料の生産スケジュールを立てたいです。あらかじめ、肥料への需要はわかっているものとします。目的は、需要を満たしながら利益を最大化することです。コストは、原材料の購入と経時的な肥料の保管にかかります。 また、制約条件は以下になります。 ①2つの肥料製品(Balanced, HighN)のそれぞれの含有栄養素の重量比 ②原材料の名前と栄養素の重量比※原料のSandには栄養素は無いですが、必要に応じてほか原料を希釈して栄養素の必要な重量比になるよう調整します。 ③製品需要(精度の高い需要予測ができている想定。単位はトン) ④製品それぞれの1トン当たりの価格($) - Balanced:400 - HighN:550    ⑤月ごとの原材料の価格($/t)(価格予測が精度良くできている想定) ⑥各種コスト・制約 - 保管コスト:10$/t・月 - 倉庫容量:1000t - 月々の最大生産量:1200t ⑦その他 各月で満たされなかった需要は失われるものとします。つまり、期間内に注文を満たすことができない場合、超過分の注文は次の期間に繰り越されません。 Google OR-toolで解く 今回は1~3月分の生産計画を立ててみたいと思います。 ③の製品需要を見ると1月、2月は最大生産量(1200t)以下の需要量なのでその月に必要量を生産すれば需給が間に合いますが、3月に入ると急に需要が増え、合わせて1500トンの需要が発生します。 これに対応するために需要が少ない月に多めに生産して、在庫として持っておく必要があります。 以下ではこれをGoogle OR-toolを使って解いて生産計画を立てて見ようと思います。 まず固定の変数を設定します。 変数設定 #固定の変数を設定 inventory_cost_rate =10 inventory_capacity = 1000 production_capacity = 1200 blend_price = 400 highn_price = 550 次に月ごとの情報をdfとして保存しておきます。 (本当はdfを使ってforループを回して変数設定、制約条件の追加をしたかったのですが、エラーが解消できなかったので今回は手打ちでやっています) 月ごとの需要・価格情報をデータフレーム化 import pandas as pd import numpy as np demand_Balanced = [750, 800, 900, 850, 700, 700, 700, 600, 600, 550, 550, 550]#1~12月のBalanced需要量 demand_HighN = [300, 310, 600, 400, 350, 300, 200, 200, 200, 200, 200, 200]#1~12月のHighN需要量 rawcost_MAP = [350, 360, 350, 350, 320, 320, 320, 320, 320, 310, 310, 340]#1~12月のMAP原料価格 rawcost_Potash = [610, 630, 630, 610, 600, 600, 600, 600, 600, 600, 600, 600]#1~12月のPotash原料価格 rawcost_AN = [300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300]#1~12月のAN原料価格 rawcost_AS = [135, 140, 135, 125, 125, 125, 125, 125, 125, 125, 125, 125]#1~12月のAS原料価格 rawcost_TSP = [250, 275, 275, 250, 250, 250, 250, 240, 240, 240, 240, 240]#1~12月のTSP原料価格 rawcost_Sand = [80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80]#1~12月のSand原料価格 df = pd.DataFrame(np.arange(96).reshape(12,8), columns=["demand_Balanced", "demand_HighN", "rawcost_MAP", "rawcost_Potash", "rawcost_AN", "rawcost_AS", "rawcost_TSP", "rawcost_Sand"], index=["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"]) df["demand_Balanced"]=demand_Balanced df["demand_HighN"]=demand_HighN df["rawcost_MAP"]=rawcost_MAP df["rawcost_Potash"]=rawcost_Potash df["rawcost_AN"]=rawcost_AN df["rawcost_AS"]=rawcost_AS df["rawcost_TSP"]=rawcost_TSP df["rawcost_Sand"]=rawcost_Sand df = df.astype(float) df.head(12) dfの中身は以下です。 最後に線形計画問題をGoogle or-toolsを使って解きます。 or-toolsで線形計画問題を解く #or-toolsの線形計画法のライブラリをインポート from ortools.linear_solver import pywraplp #ソルバーの設定(バックエンドはSCIP)。 solver = pywraplp.Solver.CreateSolver('SCIP') #変数の設定。非負制約も入れておく。 infinity = solver.infinity() #1月分の変数設定 p1_1= solver.NumVar(0.0, infinity, '')#1月のBalancedの生産量 p2_1= solver.NumVar(0.0, infinity, '')#1月のHighNの生産量 s1_1 = solver.NumVar(0.0, df["demand_Balanced"][0], '')#1月のBalanced販売量。需要量を超えない制約あり。 s2_1 = solver.NumVar(0.0, df["demand_HighN"][0], '')#1月のHighN販売量。需要量を超えない制約あり。 u1_1 = solver.NumVar(0.0, infinity, '')#1月のMAP原材料の使用量 u2_1 = solver.NumVar(0.0, infinity, '')#1月のPotash原材料の使用量 u3_1 = solver.NumVar(0.0, infinity, '')#1月のAN原材料の使用量 u4_1 = solver.NumVar(0.0, infinity, '')#1月のAS原材料の使用量 u5_1 = solver.NumVar(0.0, infinity, '')#1月のTSP原材料の使用量 u6_1 = solver.NumVar(0.0, infinity, '')#1月のSand原材料の使用量 production_1 = solver.NumVar(0.0, production_capacity, '')#1月の生産量合計 inventory1_1=solver.NumVar(0.0, inventory_capacity, '')#1月の在庫量Balanced inventory2_1=solver.NumVar(0.0, inventory_capacity, '')#1月の在庫量HighN inventory_1=solver.NumVar(0.0, inventory_capacity, '')#1月の在庫量合計 #2月分の変数設定 p1_2= solver.NumVar(0.0, infinity, '')#2月のBalancedの生産量 p2_2= solver.NumVar(0.0, infinity, '')#2月のHighNの生産量 s1_2 = solver.NumVar(0.0, df["demand_Balanced"][1], '')#2月のBalanced販売量。需要量を超えない制約あり。 s2_2 = solver.NumVar(0.0, df["demand_HighN"][1], '')#2月のHighN販売量。需要量を超えない制約あり。 u1_2 = solver.NumVar(0.0, infinity, '')#2月のMAP原材料の使用量 u2_2 = solver.NumVar(0.0, infinity, '')#2月のPotash原材料の使用量 u3_2 = solver.NumVar(0.0, infinity, '')#2月のAN原材料の使用量 u4_2 = solver.NumVar(0.0, infinity, '')#2月のAS原材料の使用量 u5_2 = solver.NumVar(0.0, infinity, '')#2月のTSP原材料の使用量 u6_2 = solver.NumVar(0.0, infinity, '')#2月のSand原材料の使用量 production_2 = solver.NumVar(0.0, production_capacity, '')#2月の生産量合計 inventory1_2=solver.NumVar(0.0, inventory_capacity, '')#2月の在庫量Balanced inventory2_2=solver.NumVar(0.0, inventory_capacity, '')#2月の在庫量HighN inventory_2=solver.NumVar(0.0, inventory_capacity, '')#2月の在庫量合計 #3月分の変数設定 p1_3= solver.NumVar(0.0, infinity, '')#3月のBalancedの生産量 p2_3= solver.NumVar(0.0, infinity, '')#3月のHighNの生産量 s1_3 = solver.NumVar(0.0, df["demand_Balanced"][2], '')#3月のBalanced販売量。需要量を超えない制約あり。 s2_3 = solver.NumVar(0.0, df["demand_HighN"][2], '')#3月のHighN販売量。需要量を超えない制約あり。 u1_3 = solver.NumVar(0.0, infinity, '')#3月のMAP原材料の使用量 u2_3= solver.NumVar(0.0, infinity, '')#3月のPotash原材料の使用量 u3_3 = solver.NumVar(0.0, infinity, '')#3月のAN原材料の使用量 u4_3 = solver.NumVar(0.0, infinity, '')#3月のAS原材料の使用量 u5_3 = solver.NumVar(0.0, infinity, '')#3月のTSP原材料の使用量 u6_3 = solver.NumVar(0.0, infinity, '')#3月のSand原材料の使用量 production_3 = solver.NumVar(0.0, production_capacity, '')#3月の生産量合計 inventory1_3=solver.NumVar(0.0, inventory_capacity, '')#3月の在庫量Balanced inventory2_3=solver.NumVar(0.0, inventory_capacity, '')#3月の在庫量HighN inventory_3=solver.NumVar(0.0, inventory_capacity, '')#3月の在庫量合計 #制約条件の設定 #1月分の制約条件 solver.Add(u1_1*0.11 + u2_1*0 + u3_1*0.35 + u4_1*0.21 + u5_1*0 + u6_1*0 == p1_1*0.1 + p2_1*0.2)#マテバラ:N solver.Add(u1_1*0.48 + u2_1*0 + u3_1*0 + u4_1*0 + u5_1*0.46 + u6_1*0 == p1_1*0.1 + p2_1*0.1)#マテバラ:P solver.Add(u1_1*0 + u2_1*0.6 + u3_1*0 + u4_1*0 + u5_1*0 + u6_1*0 == p1_1*0.1 + p2_1*0.1)#マテバラ:K solver.Add(u1_1 +u2_1 + u3_1 + u4_1 + u5_1 + u6_1 == p1_1 + p2_1)#マテバラ:生産量 solver.Add(p1_1 + p2_1 == production_1)#マテバラ:生産量 solver.Add(s1_1 <=p1_1)#販売量の制約 solver.Add(s2_1 <=p2_1)#販売量の制約 solver.Add(inventory_1 <= inventory_capacity)#各月の在庫量はinventory_capacity以下 solver.Add(p1_1 + p2_1 <=production_capacity)#各月の生産量の制約 solver.Add(inventory1_1 + inventory2_1== inventory_1)#品目別の在庫の合計 solver.Add(p1_1 - s1_1== inventory1_1)#品目別在庫 solver.Add(p2_1 - s2_1== inventory2_1)#品目別在庫 #2月分の制約条件 solver.Add(u1_2*0.11 + u2_2*0 + u3_2*0.35 + u4_2*0.21 + u5_2*0 + u6_2*0 == p1_2*0.1 + p2_2*0.2)#マテバラ:N solver.Add(u1_2*0.48 + u2_2*0 + u3_2*0 + u4_2*0 + u5_2*0.46 + u6_2*0 == p1_2*0.1 + p2_2*0.1)#マテバラ:P solver.Add(u1_2*0 + u2_2*0.6 + u3_2*0 + u4_2*0 + u5_2*0 + u6_2*0 == p1_2*0.1 + p2_2*0.1)#マテバラ:K solver.Add(u1_2 +u2_2 + u3_2 + u4_2 + u5_2 + u6_2 == p1_2 + p2_2)#マテバラ:生産量 solver.Add(p1_2 + p2_2 == production_2)#マテバラ:生産量 solver.Add(s1_2 <=p1_2 + inventory1_1)#販売量の制約。最初の月以降は前月の在庫も追加。 solver.Add(s2_2 <=p2_2 + inventory2_1)#販売量の制約。最初の月以降は前月の在庫も追加。 solver.Add(p1_2 - s1_2 + inventory1_1 == inventory1_2)#2月の品目別在庫。前月の残量追加。 solver.Add(p2_2 - s2_2 + inventory2_1 == inventory2_2)#2月の品目別在庫。前月の残量追加。 solver.Add(inventory1_2 + inventory2_2== inventory_2)#2月在庫の合計 solver.Add(inventory_2 <= inventory_capacity)#各月の在庫量はinventory_capacity以下 solver.Add(p1_2 + p2_2 <=production_capacity)#各月の生産量の制約 #3月分の制約条件 solver.Add(u1_3*0.11 + u2_3*0 + u3_3*0.35 + u4_3*0.21 + u5_3*0 + u6_3*0 == p1_3*0.1 + p2_3*0.2)#マテバラ:N solver.Add(u1_3*0.48 + u2_3*0 + u3_3*0 + u4_3*0 + u5_3*0.46 + u6_3*0 == p1_3*0.1 + p2_3*0.1)#マテバラ:P solver.Add(u1_3*0 + u2_3*0.6 + u3_3*0 + u4_3*0 + u5_3*0 + u6_3*0 == p1_3*0.1 + p2_3*0.1)#マテバラ:K solver.Add(u1_3 +u2_3 + u3_3 + u4_3 + u5_3 + u6_3 == p1_3 + p2_3)#マテバラ:生産量 solver.Add(p1_3 + p2_3 == production_3)#マテバラ:生産量 solver.Add(s1_3 <=p1_3 + p1_1 + p1_2 -s1_1 - s1_2)#販売量の制約 solver.Add(s2_3 <=p2_3 +p2_1 + p2_2 - s2_1 - s2_2)#販売量の制約 solver.Add(p1_3 - s1_3 + inventory1_2 == inventory1_3)#3月の品目別在庫。前月の残量追加。 solver.Add(p2_3 - s2_3 + inventory2_2 == inventory2_3)#3月の品目別在庫。前月の残量追加。 solver.Add(inventory1_3 + inventory2_3== inventory_3)#3月在庫の合計 solver.Add(inventory_3 <= inventory_capacity)#各月の在庫量はinventory_capacity以下 solver.Add(p1_3 + p2_3 <=production_capacity)#各月の生産量の制約 #各費用計算 inventory_cost = (inventory_1+inventory_2+inventory_3)*inventory_cost_rate sales = blend_price*(s1_1+s1_2+s1_3) + highn_price*(s2_1 + s2_2 + s2_3) raw_costs = u1_1*df["rawcost_MAP"][0] + u2_1*df["rawcost_Potash"][0] + u3_1*df["rawcost_AN"][0] + u4_1*df["rawcost_AS"][0] + u5_1*df["rawcost_TSP"][0] + u6_1*df["rawcost_Sand"][0]\ +u1_2*df["rawcost_MAP"][1] + u2_2*df["rawcost_Potash"][1] + u3_2*df["rawcost_AN"][1] + u4_2*df["rawcost_AS"][1] + u5_2*df["rawcost_TSP"][1] + u6_2*df["rawcost_Sand"][1]\ +u1_3*df["rawcost_MAP"][2] + u2_3*df["rawcost_Potash"][2] + u3_3*df["rawcost_AN"][2] + u4_3*df["rawcost_AS"][2] + u5_3*df["rawcost_TSP"][2] + u6_3*df["rawcost_Sand"][2] #目的関数の設定。収益とコストからなる利益を最大化する。 solver.Maximize(sales - inventory_cost - raw_costs) #ソルバーで設定した混合整数問題を解く。 solver.Solve() 結果確認 Objective().Value()で目的関数の値、solution_value()で目的関数が最大となるときの説明変数を呼び出すことができます。 例えば以下のようなコードを実行すると計算結果を確認できます。 結果確認(一部) print('Solution:') print('Objective value =', solver.Objective().Value())#722982.6562995873 print('p1_1 =', p1_1.solution_value())#847.8260869565217 3ヶ月分の各変数を出力してデータフレームにまとめた結果は以下になります。(ちょっと小さいですが、拡大すると各月の変数の結果が見れます) 最初に予測したとおり、3月の需要に対応するため、1月2月で作りだめをするように生産計画が立てられていることがわかりました。 また、3月分は1月の在庫を0からスタートすると、どう頑張ってもすべての需要を満たすことができないのですが、結果として利益最大化のために単価の大きなHighNの需要は100%満たすように作りだめが行われ、Balancedの需要はできる限り満たすが一部不足分が失われる結果になりました。 このように設定した制約条件や最大化したい目的関数の設定がきちんと反映された生産計画が立てられていることがわかりました。 以下で2つの製品Balanced, HighNについて各月の最適化結果を確認してみます。 ・1~3月の生産量 ・1~3月の販売量 ・1~3月の在庫量 おわりに 今回は3ヶ月分の毎月変動する需要と原料価格をもとに利益が最大化するように各月の生産量・原料使用量・在庫量の使用量の線形計画問題を解きました。 実際にはもう少し細かく複雑な制約が現場にはありますが、複数月にまたがる生産計画を人間の頭で考えることは難しいので、定式化できるものについては数理最適化手法を使って効率化していきたいですね。 また、最近ではGoogle or-toolsのような使いやすい数理最適化ライブラリも出てきていますので数理最適化の実験を誰でも無料で簡単にできるようになってきています。 ある意味、現代は数理最適化を使う人間側の制約条件が緩和され、これまでよりも何かをする際に複数の選択肢を持てる、そんな時代になって来たように思えます。 世の中をより良くするために数理最適化がこれまで以上に活用されて行くことを願っています。 参考文献 MathWorks 問題ベース フレームワークでの多期間にわたる在庫モデルの作成 Google OR-tools
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

yukicoder contest 306 参戦記

yukicoder contest 306 参戦記 A 1622 三角形の面積 答えは当然、円に内接する正三角形の面積. ググれば公式がでてくるかもしれないが、2次元なので答えは半径の二乗に比例するので、サンプルを見れば定数が分かる. t = int(input()) for _ in range(t): r = int(input()) print(1.299038105676657 * r * r) B 1623 三角形の制作 n は大きくて2重ループは無理だが、ri,gi,bi の値域は2重ループできる程度には小さいのがポイント. OK な条件は ri≧gj かつ ri≧bk かつ ri<gj+bk. rの小さい順に処理しながら順次有効なg, bを投入していけば良い. 条件に合う個数を単純に集計すると遅くて仕方がないので、BIT で高速化した. class BinaryIndexedTree: def __init__(self, size): self._data = [0] * size def add(self, i, x): data = self._data i += 1 n = len(data) while i <= n: data[i - 1] += x i += i & -i def _sum(self, stop): data = self._data result = 0 i = stop while i > 0: result += data[i - 1] i -= i & -i return result def range_sum(self, start, stop): return self._sum(stop) - self._sum(start) n = int(input()) r = list(map(int, input().split())) g = list(map(int, input().split())) b = list(map(int, input().split())) rr = [0] * 3001 for i in r: rr[i] += 1 gg = [0] * 3001 for i in g: gg[i] += 1 bb = [0] * 3001 for i in b: bb[i] += 1 gb = [[] for _ in range(3001)] for i in range(1, 3001): x = gg[i] if x == 0: continue for j in range(1, 3001): y = bb[j] if y == 0: continue gb[max(i, j)].append((i << 12) + j) bit = BinaryIndexedTree(6001) result = 0 for i in range(1, 3001): for v in gb[i]: x = v >> 12 y = v & 4095 bit.add(x + y, gg[x] * bb[y]) result += rr[i] * bit.range_sum(i + 1, 6001) print(result) gj と bk から ri の値域を考えるほうが簡単だった. 累積和しておけば個数は O(1) で求まるし. from itertools import accumulate n = int(input()) r = list(map(int, input().split())) g = list(map(int, input().split())) b = list(map(int, input().split())) rr = [0] * 3001 for i in r: rr[i] += 1 gg = [0] * 3001 for i in g: gg[i] += 1 bb = [0] * 3001 for i in b: bb[i] += 1 a = list(accumulate(rr)) result = 0 for i in range(1, 3001): x = gg[i] if x == 0: continue for j in range(1, 3001): y = bb[j] if y == 0: continue result += (a[min(i + j - 1, 3000)] - a[max(i, j) - 1]) * (x * y) print(result)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初投稿】streamlitで求人掲載件数の可視化アプリを作ってみた。

まずは自己紹介 求人広告の代理店の営業として新卒から10年ほど同じ会社で働いています。3年程前から少しずつプログラミングなどを独学で行っています。最初はエクセルのマクロから始まり、RPA(uwsc,Uipath)、プログラミング(html,css,javascript,PHP,pythonなど)と少しずつ学ぶことの幅を広げていっています。 お仕事は営業なので直接的にお仕事でプログラミングを使うことはあまりありません。たまにエクセルで業務効率化のツールを作成したり、RPAを使って競合調査を自動的に行ったりと業務に活かしていることもあります。 今後はWEBアプリなどを作成して公開していく予定です。 (自己紹介だけの投稿はダメなんですね。知りませんでした。なので、現在進めているstreamlitの内容も書き足してみたいと思います。) 今作っているもの pythonのstreamlitを用いて求人掲載件数の可視化アプリを作成中です。 streamlitはすごく使いやすくて優秀です。例えば、サイドバーの機能も下記のような少ないコードで簡単に実装できます。 main.py st.sidebar.write(""" 掲載件数の知りたい日付を指定してください。 """) from_day = st.sidebar.date_input('いつから?', min_value=date(2020, 4, 1), max_value=date.today(), value=date(2020, 4, 1), ) to_day = st.sidebar.date_input('いつまで?', min_value=date(2020, 4, 1), max_value=date.today(), value=date.today(), ) selected_pref = st.sidebar.selectbox( '掲載件数の知りたい都道府県を指定してください。', ('北海道',                 : '沖縄県')) 完成したらherokuでデプロイする予定なのでまた記事にしていければと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初投稿】自己紹介などです。

自己紹介 求人広告の代理店の営業として新卒から10年ほど同じ会社で働いています。3年程前から少しずつプログラミングなどを独学で行っています。最初はエクセルのマクロから始まり、RPA(uwsc,Uipath)、プログラミング(html,css,javascript,PHP,pythonなど)と少しずつ学ぶことの幅を広げていっています。 お仕事は営業なので直接的にお仕事でプログラミングを使うことはあまりありません。たまにエクセルで業務効率化のツールを作成したり、RPAを使って競合調査を自動的に行ったりと業務に活かしていることもあります。 今後はWEBアプリなどを作成して公開していく予定です。 投稿目的 今まで完全に自己満足だけで終わっていたので、今後は作成したものを公開していきお仕事などにも繋がるようにしていきたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python エクセルが読めない print “EXTERNSHEET(b7-):” pandas エラー対応の例

pythonで急にエクセルが読めなくなった 「Python による経済・経営分析のための実践的データサイエンス」第5章 (pp.136-160) サンプル import pandas as pd input_book = pd.ExcelFile('AB_NYC_2019_2.xlsx') こんなエラー File "/path to/python3.9/site-packages/xlrd/__init__.py", line 1187 print "EXTERNSHEET(b7-):" ^ SyntaxError: invalid syntax とりあえずアップグレード % pip install --upgrade xlrd 今度はこんなエラー 168 # files that xlrd can parse don't start with the expected signature. 169 if file_format and file_format != 'xls': --> 170 raise XLRDError(FILE_FORMAT_DESCRIPTIONS[file_format]+'; not supported') 171 172 bk = open_workbook_xls( XLRDError: Excel xlsx file; not supported openpyxl で対応 入ってなかったらインストール % pip install openpyxl コードをこんなふうに変更する import pandas as pd input_book = pd.ExcelFile('AB_NYC_2019_2.xlsx', engine='openpyxl') できた!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

node.js+Express+pugでDBDのマップカウンターを作る

はじめに DBDという対人ゲームで、あるキャラクターを使うと苦手なマップばかり当たるので、猜疑心からマップカウンターを作りました。(なお結果としては私が苦手なマップがDBDに多いだけでした!) 目次 node.js+Express+pugインストール DBDのマップをスクリピング JSONの読み書き 動的リンクを扱う .pugへ変数を渡す .pugでhtmlを生成 cssの記述 1.node.js+Express+pugインストール ここを見ながらインストール 2.DBDのマップをスクレイピング 稚拙なソースですが動きます! dbdmap.py from time import sleep import json def main(): import requests from bs4 import BeautifulSoup FullURL = "https://deadbydaylight.fandom.com" URL = "https://deadbydaylight.fandom.com/wiki/Dead_by_Daylight_Wiki" page = requests.get(URL) soup = BeautifulSoup(page.content, "html.parser") results = soup.find(id="fpRealms") results = results.find("div", {"class": "fplinks"}) results = results.find_all("a") maplst = [] mapdict = {} for result in results: dbdmap = result["href"] if not dbdmap in maplst: maplst.append(dbdmap) for mapdbd in maplst: mapurl = FullURL + mapdbd mappage = requests.get(mapurl) mapsoup = BeautifulSoup(mappage.content, "html.parser") mapresults = mapsoup.find("table", {"class": "wikitable"}) mapresults = mapresults.find("tbody") mapresults = mapresults.find("tr") mapresults = mapresults.find_all("td") for mapresult in mapresults: mapresult = mapresult.find("center") mapresult = mapresult.find("a") mapdict[mapresult.text.strip()] = 0 sleep(3) with open('<保存したい場所>.json', 'w') as outfile: json.dump(mapdict, outfile) if __name__ == "__main__": main() これにより以下の様なJSONを得られる。 {"Coal Tower": 0, "Groaning Storehouse": 0, "Ironworks of Misery": 0, ... 3.JSONの読み書き ここで詰まったのでいくつか注意点を書いておきます jsonはクライアント側からは読み書きできません。 つまり生成されたウェブページに組み込まれたスクリプトからは操作できないです。そのためサーバー側で操作する必要があります。 また、サーバー側での操作はroutes/index.jsonで行います。(リクエストされたページに対応したファイル) const fs = require('fs'); const filePath = "./routes/dbdmap.json" //jsonを読み取る const dbdmaplst = require("./dbdmap.json"); //jsonに書き込む fs.writeFile(filePath, JSON.stringify(dbdmaplst), (err) => { if (err) console.log('Error writing file:', err) }) 4.動的リンクを扱う カウンターなので、index/"map名"/"up/down"のような動的リンクを扱えるようにします。 複数個設定できます。 router.get('/', function(req, res, next) { router.get('/:map/:vote', function(req, res, next) { 受け取ったリンクのキーワードを引数のように扱えます router.get('/:map/:vote', function(req, res, next) { const dbdmaplst = require("./dbdmap.json"); const vote = req.params.vote; if (vote == "up") { dbdmaplst[req.params.map] += 1; } else if (vote == "down") { if (dbdmaplst[req.params.map] > 0) { dbdmaplst[req.params.map] -= 1; } } 5.pugへ変数を渡す json形式で変数を渡します。 res.render('index', { title: 'Epress', dbdmap: dbdmaplst}); 以上index.jsの完全なソースはこちら index.js var express = require('express'); var router = express.Router(); //read json const fs = require('fs'); const filePath = "./routes/dbdmap.json" /* GET home page. */ router.get('/', function(req, res, next) { const dbdmaplst = require("./dbdmap.json"); res.render('index', { title: 'Epress', dbdmap: dbdmaplst}); }); router.get('/:map/:vote', function(req, res, next) { const dbdmaplst = require("./dbdmap.json"); const vote = req.params.vote; if (vote == "up") { dbdmaplst[req.params.map] += 1; } else if (vote == "down") { if (dbdmaplst[req.params.map] > 0) { dbdmaplst[req.params.map] -= 1; } } fs.writeFile(filePath, JSON.stringify(dbdmaplst), (err) => { if (err) console.log('Error writing file:', err) }) res.render('index', { title: 'DBD MAP LOG', dbdmap: dbdmaplst}); }); module.exports = router; 6 .pugでhtmlを生成 pugはpythonのようにインデントで管理されているようです。 以下ソース内に注意点を記載 index.pug //layout.pugをテンプレートに使う extends layout //テンプレート内のblock contentに置き換えられる block content //'-'を先頭に記載することでjavascriptを組み込むことができる。 -var count = 0; div(class="tbl") table(cellpadding=10) //pug 独自のfor/while/if-else文を扱える //ここの'dbdmap'はindex.jsから受け取ったもの each val, key in dbdmap -count += val; tr //'#{}'で変数を扱うことができる。 td #{val} td #{key} td a(href= "/" + key + "/up") up td a(href= "/" + key + "/down") down h2 Match count: #{count} pugの記述方法はこちら 7.cssの記述 cssよく分からないのですが、こんな感じにしたらいい感じになりました。。。 style.css body { padding: 50px; font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; } a { color: #00B7FF; } table, tr, td { border: 1px solid black; padding: 10px; } table { display: inline-block; float: left; } cssの読み込み方。テンプレートのlayout.pugに記載する。 layout.pug doctype html html head title= title body block content //javascript script(src='/javascripts/todo.js') //css link(rel='stylesheet', href='/stylesheets/style.css') 終わりに 以上すべてのソースコードを記載したわけではありませんが、詰まる恐れがある点について解説できました。 もし質問があればどうぞコメントにどうぞ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

kivyMDチュートリアル其の弍什漆 Components - SelectionControls篇

みなさん、おはこんばんは!元気ですかー? やや元気が空回りしていますが、特に異常ではありません。 若干頭が重いことと、少々肩が痛いことは誤差の範囲という感じでしょうか。 さて、特にオリンピックが開催されたことくらいしかとりとめもなくさっそく 始めていきましょうかと言いたいところですが、お詫びしなければいけないこと があります。 それほど、畏まっていうことでもないのですが、先週は順番通りSelection- Controlsをやっていくと宣言していました。正しくはSelectionを飛ばして SelectionControlsをやっていくということになります。お詫びして訂正します。 # 理由は明確で動かなかっただけです ということで、今日はSelectionControlsになります!今日も元気にやっていき ますよー!(謎のテンション) SelectionControls はい、お詫びしているにも関わらず、MaterialDesignのリンクは恒例で飛ばします。 HTMLとかでは、チェックボックスやラジオボタンに該当するものになりますね。それら がなんかオシャンティー(死語)になっていると思ってもらえればと思いますが、それ以前 にそれほど以前のAndroidとかを使っていると別ですが結構見たことあるようなコンポー ネントになります。マテリアルからかは断定できていませんが、スイッチ形式のコントロ ールもあることも特徴と言えるでしょう。 kivyMDとしてもこのような冒頭文から始まっています。 Selection controls allow the user to select options. 確かにあまり設定画面以外では見かけない気もしますね。 MDCheckbox with group_Control state おいおい、急にどうした?と言われそうですが、合体しました。ほとんどMDCheckboxと 変わらないのでこうしたというのがありますが、でもこうした結果よく分からんぞという 方はマニュアルにあるMDCheckbox単体のものを見ていただければと思います。というか 先にそっちを見たほうがいいかも・・・ さてコードもマニュアルから変更(というか厳密にいうと合体しただけ)をしているので、 マニュアル通りにいかず、さっそくコードに入っていきます。 xxvii/mdcheckbox_withgroup_withcontrolstate.py from kivy.lang import Builder from kivymd.app import MDApp KV = ''' <Check@MDCheckbox>: group: 'group' size_hint: None, None size: dp(48), dp(48) on_active: app.on_checkbox_active(*args) MDFloatLayout: Check: active: True pos_hint: {'center_x': .4, 'center_y': .5} Check: pos_hint: {'center_x': .6, 'center_y': .5} ''' class Test(MDApp): def build(self): return Builder.load_string(KV) def on_checkbox_active(self, checkbox, value): if value: print('The checkbox', checkbox, 'is active', 'and', checkbox.state, 'state') else: print('The checkbox', checkbox, 'is inactive', 'and', checkbox.state, 'state') なんか複雑なことをやってそうですが、単純明快、マニュアルを合体しただけに なりますw ではここからは恒例行事に入ります。 kv側 インポート文はとりとめもありませんので、省きます。 kv側で定義されているものを抜粋します。 KV = ''' <Check@MDCheckbox>: group: 'group' size_hint: None, None size: dp(48), dp(48) on_active: app.on_checkbox_active(*args) MDFloatLayout: Check: active: True pos_hint: {'center_x': .4, 'center_y': .5} Check: pos_hint: {'center_x': .6, 'center_y': .5} ''' まずはMDFloatLayoutの配下にある、Checkウィジェットから。 これはまず最初に目の当たりにするのは、MDCheckboxを継承しているということ ですね。 そして、プロパティで定義されているものは何かというと、group、size_hint、 size、on_activeになりますね。size_hintはお馴染み感しかありませんね。 不明な方は、Layout篇を辿るか、kivyから入門のほどを。 あとは、sizeについてはなんとなく分かるとは思いますが、説明文もありますし ここで触れておきます。説明文についてはマニュアル通りですが、以下にて記載して おきます。 Be sure to specify the size of the checkbox. By default, it is (dp(48), dp(48)), but the ripple effect takes up all the available space. これ使いたかっただけかと言われそうですが、それは確かかもしれませんw まぁ、そんなことは置いておいて、デフォルト値が縦横dp(48)ということは分かり ます。 後半は少し分かってないこともありますが、あぁなんだマニュアル通りのことかという ことになります。これは動かしてみて分かったことですが、ただただリップルエフェクトの 大きさということで、値を変更してみるとこれの円形の大きさが変更されるだけになります。 まぁ、書いてある通りっちゃ書いてある通りなんですけども。 あとは、on_activeコールバックとgroupになります。on_activeはControl state の話で、メインとしてはTestクラス側で触れる形となるので詳細はこの後で。ここでは Checkウィジェットでコールバックを持たせる形を取っています。groupはなんでしょうね。 クラス側でも出てこないしで、これが何か分からないということが現状です。 あとは、ちゃんと説明できているか不明なCheckウィジェットがMDFloatLayoutによって 配置されていることでしょうか。手前で定義されている方に、初期表示時でチェックが付く ようにactiveプロパティが定義されています。値はもちろんTrueに設定されていますね。 あとは触れるまでもないかもですが、pos_hintで位置付けされています。 Testクラス側 ということで、kv側は以上になります。 ここからは、Testクラス側に入っていきますが以下にて再掲しておきます。 class Test(MDApp): def build(self): return Builder.load_string(KV) def on_checkbox_active(self, checkbox, value): if value: print('The checkbox', checkbox, 'is active', 'and', checkbox.state, 'state') else: print('The checkbox', checkbox, 'is inactive', 'and', checkbox.state, 'state') buildメソッドなんかはこれまで通りになりますが、キモはon_checkbox_active メソッドになります。先程のkv側でコールバックで指定していたところになります。 まぁ、これもマニュアル通りになるので特に触れるまでもないのですが、それを言って しまうとこの投稿も意味なくなっちゃうので・・・ まず、このメソッドの引数としてはcheckboxとvalueの2つがあります。ここは見た まんまになりますが、valueの値(チェックが入っているかいないか)によってprint文 でcheckboxインスタンスのメモリ番地とステータスを出力しているだけになります。ス テータスについては結果の方で詳細を。 結果 今日もさくさくと進んできましたが、さっそく結果の方に移りたいと思います。 まずは、初期表示がどうなるか見てみましょう。 ラジオボタンでのウィジェットが2つ表示され、初めにactiveプロパティをTrueに 設定しておいたウィジェット(画面左側)がチェック付きで表示されます。また、チェ ックを違う方に付けてみると、 というようにチェックが別のものに付くようになります。 今度は、なんだかよく分からないと言っていたgroupプロパティですが、こちらを コメントアウトしてみて動作がどうなるか見てみます。 あらま、という感じになりますがまずラジオボタンがチェックボックスに切り替わっています。 なるほど、グルーピングすれば勝手にラジオボタンのように切り替わるというのですね。 さらにこれを初期表示でチェックが付いているものと異なるボックスを押してみると、 まぁ、それはそうかと言わんばかりに両方ともチェックが付いてしまいました。 なので、ラジオ・チェックボックスの切り替えはgroupプロパティで切り替え られるとみてもらった方がいいかもしれません。 さらに、さっき言ってたステータスの件を見てみましょう。今度はgroupプロパティを 設定したところに戻ります。初期表示から2~3回ほど切り替えたときのprintで出力さ れたようすを以下に載せておきます。 [INFO ] [GL ] NPOT texture support is available The checkbox <kivy.factory.Check object at 0x110731ed0> is active and down state [INFO ] [Base ] Start application main loop The checkbox <kivy.factory.Check object at 0x110731ed0> is inactive and normal state The checkbox <kivy.factory.Check object at 0x1107422d0> is active and down state The checkbox <kivy.factory.Check object at 0x1107422d0> is inactive and normal state The checkbox <kivy.factory.Check object at 0x110731ed0> is active and down state The checkbox <kivy.factory.Check object at 0x110731ed0> is inactive and normal state The checkbox <kivy.factory.Check object at 0x1107422d0> is active and down state Start application main loopがなされる前に1回、on_checkbox_activeメソッド が実行されていますね。 これは別途調査をして分かったことですが、Checkウィジェットを定義したときにbuild メソッドの中でkvを読み込んだあと、インスタンスが生成され自動的にon_check_active メソッドが動いているような気がしてなりません。根拠としてはそれぞれのウィジェット (MDFloatLaytoutの配下)にコールバックを定義すると、このような挙動にならなかったため です。 Start appli~以降は挙動としては同じですね。切り替わったときには元のウィジェットが normal state(チェックがオフ(inactive))になり、切り替え先ではdown state(チェック がオン(active))になります。切り替え時には、groupプロパティでまとめられているところが 全てprint出力の対象となります。groupプロパティがないときはそれぞれのウィジェットごとに 出力されることになりました。 MDSwitch ということで、終わった感もありますがまだ続きます。Switchの方ですね。 こちらもさっそくコードに移っていきます。それほど触れる量は多くはありません。 xxvii/mdswitch.py from kivy.lang import Builder from kivymd.app import MDApp KV = ''' MDFloatLayout: MDSwitch: pos_hint: {'center_x': .5, 'center_y': .6} MDSwitch: pos_hint: {'center_x': .5, 'center_y': .4} width: dp(64) ''' class Test(MDApp): def build(self): return Builder.load_string(KV) Test().run() こちらの方はマニュアルより少しだけ変更になります。というかこちらもNoteパネルで 指し示したところをくっつけただけになります。オリジナリティは安定して0です。 ここも先程のMDCheckboxを見ていただいたら、触れるまでもないかもしれません。 というか触れるところが、kvの中の2つ目のMDSwitchウィジェットくらいしかあり ません。こちらはwidthがデフォルトからdp(64)に変更しているのみになります。 結果 ということで、こちらも結果の方に移りたいと思います。 初期表示はこのような感じになります。dp(64)に設定したウィジェットは横に伸びて いる印象は強いですよね。そして、両方ともスイッチを切り替えると、 このようになりました。んー、これだとデフォルトでことは足りそうな気がします。 ご利用は計画的に。 API - kivymd.uix.selectioncontrol ということでまとめの手前に使用したAPIについて引用しておきます。 ここでは、さらっといきます。 class kivymd.uix.selectioncontrol.MDCheckbox(**kwargs) Class implements a circular ripple effect. 円状に広がるリップルエフェクトが実装されています。 active Indicates if the checkbox is active or inactive. active is a BooleanProperty and defaults to False. コードでも動作でも出たプロパティになりますね。デフォルトはもちろんFalseに なります。 checkbox_icon_normal checkbox_icon_down radio_icon_normal radio_icon_down ※ 説明文は割愛 表示を変えられるようですね。変え方も知りたいのにな・・・ まぁ、これはコードをみてくれよなということでしょうか。 class kivymd.uix.selectioncontrol.MDSwitch(**kwargs) This mixin class provides Button behavior. Please see the button behaviors module documentation for more information. ボタンとのmixinであるようです。ということなのでそちらの方の詳細を知りたい、 ということになればマニュアルのButtonページかButton篇を見るようにすると知識 ネットワークが構築されそうです。Button篇はこちら(マニュアルもここから辿れます)。 active これはMDCheckboxと同じなので、特に説明は必要なさそうですね。 あとはMDCheckbox同様、押したときの振る舞いを変えられるのとテーマカラーなどが 変更出来そうです。時間があればこの辺も見たかったなぁ・・・ まとめ ということで以上になります!いかがだったでしょうか。 思ったより時間が掛かったというはあるんですけれども、まぁそんなどうでもよいことは よくて、設定画面とかを作り込むケースだと必須のようなウィジェットになるかもしれません。 あとは、リストとどのように組み合わせられそうかなとか見たかったのですが、残念ながら タイムアップ・・・実際のアプリ作成のときにお披露目という感じになりそうです。。 では、今日はここまでとなります。次回はちゃんと順番通りにSlider篇という予定です。 次回、お楽しみに! それでは、ごきげんよう。 参照 Components » SelectionControls https://kivymd.readthedocs.io/en/latest/components/selectioncontrols/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

あなたもdocker, 私もdocker

Docker上のみでシステムを作るときの構成 https://qiita.com/official-events/339b6440dbd578f4f66f 参加記事です。 普段はmacOSHigh Sierra ver. 10.13.6 メモリ16GB, intel Core i5 HDD 250GB, 空 6GB です。 docker(0) 資料集 [あなたもdocker私もdocker] https://qiita.com/kaizen_nagoya/items/45699eefd62677f69c1d に140ほど記事を書いています。 dockerで実現したいことが似ている場合は、そちらを参照くださると幸いです。 gcc dockerの使い道の1つ目は、gcc. macOSでgccのクロスコンパイラをコンパイルしようとして1日かけてもうまく行かず、斉藤直希さんに相談し、docker上でgccクロスコンパイラを構築してもらった。 Dockerをどっかーらどうやって使えばいいんでしょう。TOPPERS/FMP on RaspberryPi with Macintosh編 5つの関門「名古屋のIoTは名古屋のOSで」docker (37) https://qiita.com/kaizen_nagoya/items/9c46c6da8ceb64d2d7af gccだけ使いたい場合は、 bash $ docker run -it gcc /bin/bash 例えば、 Misra Example Suite at docker コンパイル完了までの道のり。docker(200) https://qiita.com/kaizen_nagoya/items/71f04a0204d5a1114577 python dockerの使い道の2つ目は、python. 機械学習の教育の助手を担当して、Windowsにpytohnを導入するのに失敗した人たちが大勢いて、落ちこぼれの原因がpython導入であることがわかった。 M.S.WindowsにPython3 (Anaconda3) を導入する(7つの罠) https://qiita.com/kaizen_nagoya/items/7bfd7ecdc4e8edcbd679 自分のQiitaの記事で、viewsが圧倒的に一番で、161987 viewsある。 二番目が プログラマが知っているとよい色使い(JIS安全色) https://qiita.com/kaizen_nagoya/items/cb7eb3199b0b98904a35 の82114 viewsだから約倍。 その後、dockerにPythonを導入してもらう方が楽かなって思うようになった。 docker(18) なぜdockerでpython/Rを使って機械学習するか 書籍・ソース一覧作成中 (目標100) https://qiita.com/kaizen_nagoya/items/ddd12477544bf5ba85e2 dockerに書籍のソースコードを読み込んで、動かす実験を順次してきた。 言語学習 pythonの言語教育を担当することになり、python のいろいろな版を利用するには、dockerが最適であると感じた。 pyenvなど、いろいろなpythonの複数の版を切り替える方法は面倒くさいだけでなく、python 初心者に使い方を教える時間が無駄だと感じた。 bash $ docker run -it continuumio/anaconda3 /bin/bash 言語処理100本ノックは、こちらの記事から参考にしてみてください。 言語処理100本ノック 2015(python) 落ち穂拾い 第1章: 準備運動 https://qiita.com/kaizen_nagoya/items/ee1b625b0b65cd63d42a 英語辞書作成 dockerの使い道の3つ目は、英語の文献を読んで単語帳を作る時。 「量子アニーリングの基礎」を読む勉強会を開催した。 「量子アニーリングの基礎」を読む https://qiita.com/kaizen_nagoya/items/29580dc526e142cb64e9 で参考文献の半分以上が英語論文だった。順次単語帳を作った。 プログラムちょい替え(10)英語(14) 単語帳作成 dockerで(文字コード対応)量子計算機 arXiv掲載 西森 秀稔 論文(shell, awk), docker(82) https://qiita.com/kaizen_nagoya/items/319672853519990cee42 この時は、素のubuntuから始める。 bash $ docker run -it ubuntu /bin/bash dockerが起動したら bash # apt install -y poppler-utils vim 更新 どの場合も、dockerが立ち上がったら、 bash # apt update; apt -y upgrade をまずしよう。 全部で300くらいdockerを使った記事を書いているような気がする。 エラーがあった時に、検索する場合、 docker kaizen_nagoyaの文字を入れるとずばりヒットするかも。docker関連のエラーをなるべく記事にしています。 困りごと 1. loginできない。 アプリは起動し、メニューからloginしているはずなのに、 dockerコマンド打つと、loginしてないと怒られることがある。 コマンドで、docker login し直すといいことがある。 それでも駄目な時は、OSを再起動して、dockerアプリでログインし、 コマンドでもloginするといいことがある。MS Windowsでも類似の経験あります。 docker(80)「DockerでPHP7.0×Apacheの環境を構築する@kurkuru」IT業界新人利用時の16の壁(mac mini編) https://qiita.com/kaizen_nagoya/items/315e8d05a6eef00b56d1 2. run, pushできない 自分の経験では綴りがちがっていることが一番多い。 anaconda3ではだめで、continuumio/anaconda3じゃないといけないみたいに全部指定しないと駄目なことがある。 その次には、同じ名前で作業していたりとか。 docker(17) docker入門の入門 5つの壁 https://qiita.com/kaizen_nagoya/items/e73ceab051a5556a652c 3. ブラウザで確認したい。 今回の趣旨とは少しずれるかもしれないが、 dockerで動作させたものを、ブラウザで確認したいことがしばしばある。 python でjupiter notebookを使うときや、 javascript, PHPなどで作ったソフトを確かめる時など。 4. HDD等の空きがない。 dockerをいっぱい立ち上げてrm, rmiするといいかも。 docker(9) rmiのための順番 https://qiita.com/kaizen_nagoya/items/0bc05d08cf18af4a8801 5. メモリが足りない 機械学習などをしているとdockerに10GB以上割り当ててないと、処理が進まないことがある。本体を32GBメモリにしてdockerに24GB割り当てると結構楽かも。 この作業のエラー 今日のdocker error :Error response from daemon: conflict: unable to delete https://qiita.com/kaizen_nagoya/items/a486385d6af636c98f0a 参考資料 プログラミング言語教育のXYZ。Youtube(1) 仮説・検証(52) https://qiita.com/kaizen_nagoya/items/1950c5810fb5c0b07be4
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Z3Py 例題 魔方陣(magic square)

問題 1から9の数字を 3×3 に配置し、各行・各列・各対角線の和がいずれも15になるようにせよ。(魔方陣) 回答 example03_magic_square.py from z3 import * X = [[Int("x_%s_%s" % (i, j)) for j in range(3)] for i in range(3)] s = Solver() s.add([And(1 <= X[i][j], X[i][j] <= 9) for i in range(3) for j in range(3)]) s.add([Distinct([X[i][j] for i in range(3) for j in range(3)])]) s.add([Sum([X[i][j] for i in range(3)]) == 15 for j in range(3)]) s.add([Sum([X[i][j] for j in range(3)]) == 15 for i in range(3)]) s.add([Sum([X[i][i] for i in range(3)]) == 15]) s.add([Sum([X[i][2-i] for i in range(3)]) == 15]) print(s.check()) m = s.model() for i in range(3): row = [] for j in range(3): row.append(m[X[i][j]]) print(row) 解説 Distinct()は、「入力した変数群は互いに異なる」という制約を生成します。 例)Distinct(x, y, z) → And(Not(x == y), Not(x == z), Not(y == z)) http://z3prover.github.io/api/html/namespacez3py.html#a9eae89dd394c71948e36b5b01a7f3cd0 他の例題 Z3Py個人的ポータル へ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Z3Py 例題3 魔方陣(magic square)

問題 1から9の数字を 3×3 に配置し、各行・各列・各対角線の和がいずれも15になるようにせよ。(魔方陣) 回答 example03_magic_square.py from z3 import * X = [[Int("x_%s_%s" % (i, j)) for j in range(3)] for i in range(3)] s = Solver() s.add([And(1 <= X[i][j], X[i][j] <= 9) for i in range(3) for j in range(3)]) s.add([Distinct([X[i][j] for i in range(3) for j in range(3)])]) s.add([Sum([X[i][j] for i in range(3)]) == 15 for j in range(3)]) s.add([Sum([X[i][j] for j in range(3)]) == 15 for i in range(3)]) s.add([Sum([X[i][i] for i in range(3)]) == 15]) s.add([Sum([X[i][2-i] for i in range(3)]) == 15]) print(s.check()) m = s.model() for i in range(3): row = [] for j in range(3): row.append(m[X[i][j]]) print(row) 解説 Distinct()は、「入力した変数群は互いに異なる」という制約を生成します。 例)Distinct(x, y, z) → And(Not(x == y), Not(x == z), Not(y == z)) http://z3prover.github.io/api/html/namespacez3py.html#a9eae89dd394c71948e36b5b01a7f3cd0 他の例題 Z3Py個人的ポータル へ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「天気図っぽい」気象前線の自動描画を日本から世界に拡張してみる〜領域拡張と地図投影

天気図っぽい気象前線の自動描画を日本から世界に拡張してみる〜領域拡張と地図投影 目次 0. はじめに 1. 日本付近の気象データから「天気図っぽい」前線を自動描画する(機械学習) 2. 学習したネットワーク・パラメタを他の地域の気象データに適用する(機械学習と地図) 3. ステレオ投影された前線画像から正投影地図への座標変換(地図) 4. pcolormeshを用いての画像の貼り付け(地図と可視化) 5. まとめ 0. はじめに 以前に数値気象予報モデルのデータから、気象庁が解析した「気象前線」を、機械学習(ニューラルネットワーク)を用いて自動的に描画する、というプログラムを作成したことがありました。 これは日本付近のデータと日本付近の天気図データをもとにしたものだったのですが、オリンピックも始まったことだし、せっかく日本で学習した結果を世界に拡張したらどうなるか?と思い立ってやってみました。 実は世界各地で行われている気象前線解析にはそれぞれの国や機関の流儀があるようで、ヨーロッパやアメリカの前線の解析の仕方は日本と異なりますので、いわば「日本流解析」の世界拡張ということになります。 日本的な気象前線解析を学習したAIがどこまで世界の気象データを日本流に解析できるでしょうか。 結論からいうと現在のところ北半球中緯度帯まで拡張してみたところ、それらしく前線が出てきました。 こんな感じです。 梅雨前線が日本周辺からさらに西側に伸びていたり、太平洋北部を進む低気圧の前線も見えています。 ヨーロパや北米大陸の低気圧にも前線が解析されました。大西洋域の高気圧の西縁の前線はやや?なところですが。 さて今回は、この図が出てくるまでに下記のような地図の投影で少し苦労した話を、前線学習の話とともに纏めました。 地図の投影での苦労 ・投影後の画像を別形式地図に投影するための座標変換  Basemapの座標変換機能を用います ・この座標変換により使えなくなる画像貼り付け機能の代替機能の工夫  カラー画像をグレー変換して、自作したカラーマップを用いてpcolormeshを用います 1. 日本付近の気象データから「天気図っぽい」前線を自動描画する(機械学習) 詳細は以前ここに纏めています。 気象データをもとに「天気図っぽい前線」を機械学習で描いてみる(5) 機械学習編 Automatic Front Detection in Weather Data 簡単に言ってしまうと、 気象データ :気象庁が日々作成している数値気象予報モデルのデータを画像化したもの。 天気図っぽい前線 :気象庁が日々作成している「速報天気図(SPAS)」という天気図の前線部分を抜き出したもの。 というデータの対を大量に用意して、それらの間の関係をニューラルネットワーク(CNN)によりセマンティックセグメンテーションとして学習させるわけです。気象データ画像のピクセル単位に寒冷前線・温暖前線・閉塞前線・それ以外という分類を行わせることで、気象前線を描画するものです。学習データは3年9ヶ月(3枚/日)程度の約5000組です。 GPVデータはいつもいつものことですが、京都大学生存圏研究所様から利用させていただいております。 2. 学習したネットワーク・パラメタを他の地域の気象データに適用する 日本付近のデータで学習したものが他地域で通じるのか?という疑問も湧きますが、北半球の同じ緯度帯ならまず大丈夫では?と考えてすすめることにしました。 2.1 学習時のデータの地理的な範囲について 元々学習に使用したデータは日本付近のものです。例えば以下のような画像です。 2019年1月の地上気圧・気温・風 2019年1月の850hPaでの相当温位 上の例では画像を連ねてアニメーションGIFにしています。 こういう画像を2018年8月から2021年4月までの3年半強使って、「天気図っぽい」前線を描く学習を行わせています。 (上段左より 地上気圧 850hPa相当温位 700hPa湿数、中段左より 850hPa気圧・温度・風 700hPa鉛直流 500hPa気圧・温度・風、下段左より 地上気圧・温度・風 300hPa風速) 入力するデータの種類は、以前にやったものと少し違っていて(*)、下記の7種類です。 それぞれ3チャンネルの画像データなので、ニューラルネットワークの入力としてはこれらをConcatenateした21チャネルの画像情報となります。 No 高度 項目 1 地上 気圧・気温・風ベクトル 2 850hPa 気圧(ジオポテンシャル高度)・気温・風ベクトル 3 850hPa 相当温位 4 700hPa 湿数 5 700hPa 鉛直風速 6 500hPa 気圧(ジオポテンシャル高度)・気温・風ベクトル 7 300hPa 風速分布 (*)以前との違い 増やしたのはNo.7の300hPaでの風速分布で、上図の下段右のようなものです。 以前から閉塞前線も分類として増やしました。 気象学的に閉塞点と高層ジェット気流の速度の強いところには関係がありますので、ニューラルネットワークにとってヒントとなるようにと思ったためです。 2.2 他の地域の入力データの作成 2.1 で例示したデータはBasemapのステレオ投影による記法を用いて作成しています。 入力するGPVデータは全球分ありますので、描画する対象の緯度経度を変更してあげれば簡単に作成できます。 なお、格子点形式の気象データ(GPV)をpygribを用いてデコードして、Basemapやmatplotlibを用いて可視化するまでについては、気象データをもとに「天気図っぽい前線」を機械学習で描いてみる(2)に纏めております。 Basemap.py from mpl_toolkits.basemap import Basemap m = Basemap(projection='stere', llcrnrlat=9, urcrnrlat=54, llcrnrlon=115, urcrnrlon=178, lat_0=60, lon_0=140, resolution='i' ) (緯度、経度)= (60, 140) を投影直下点(lat_0, lon_0)として、 左下コーナー(llcrnrlat, llcrnrlon)、 右上コーナー(urcrnrlat, urcrnrlon) について(9,115),(54,178)という指定をしています。 こうすると例えば下記のような図が描画されます(気圧図を重ねています)。 2021年6月1日0時(UTC)の地上気圧 他の地域データを作るには、これらの投影直下点、左下コーナー、右上コーナーを変更すればOKです。 北米大陸付近(投影直下点 西経100度、北緯60度) ヨーロッパ・北アフリカ付近(投影直下点 東経20度、北緯60度) 中央アジア付近(投影直下点 東経100度、北緯60度) このように、範囲としては経度を40度づつずらして、北半球一周で9個の領域を対象としました。 2.3 セマンティックセグメンテーション実行結果 北半球中緯度帯ということで気象パターンが似ているせいか、日本付近しか見たことがない日本代表AIにしては(思ったよりも)それなりな前線が描画されました。 北米領域(上段左より 地上気圧 850hPa相当温位 700hPa湿数、中段左より 850hPa気圧・温度・風 700hPa鉛直流 500hPa気圧・温度・風、下段左より 地上気圧・温度・風 300hPa風速) 大西洋領域(図の並びは北米領域と同じ) 大西洋海域にも速報天気図(SPAS)と同じような前線記号、高気圧・低気圧の記号が描画されました。 日本代表AIとしては初めて見る地図に惑わされてはいないようです。やはり相当温位の等温位線の密な部分、湿数の大きなところを前線を引くべきところと思って、日本で学んだ知識を活かして描画しているようです(笑)。 3. ステレオ投影された前線画像から正投影地図への座標変換 せっかく日本代表として描画してくれた図をもう少し見やすくしたいところ。 AIは学んだサイズで、領域別に前線画像を出力しているのですが、これらを地球全体の地図に貼り付けたほうが見やすいというものです。これはAIには教えていないのでこちらでやってあげます。 ここでひとつ考えなければならない話があります。 ニューラルネットワークが出力するのは下図左のような「ステレオ投影済みの画像」です。 この画像の中のそれぞれの点の緯度・経度が分からないと、右のような地球儀への正投影ができません。緯度線、経度線を引いてみるとわかるように、「ステレオ投影済み画像」の中で緯度経度が直交座標系にはなっていないので、この画像から元の緯度経度へ座標変換をしてやる必要があります。 3.1 ステレオ投影済画像の画像座標系から緯度経度への変換 Basemapが持っている座標変換機能を用います。 まず出力画像と同じステレオ投影形式のBasemapを作成します。 stereo_basemap.py # 左下、右上、投影直下の経度 ll_lon, ur_lon, ss_lon = 117 , 172 , 140 # ステレオ投影のBasemapを作成 m0 = Basemap(projection='stere', llcrnrlat=12, urcrnrlat=54, llcrnrlon=ll_lon, urcrnrlon=ur_lon, lat_0=60, lon_0=ss_lon, resolution='i') (ll_lon, ur_lon, ss_lonを変数にしているのは別領域用に入れ替えやすくしたため) この準備をした上で、ステレオ投影の地図に(256+1)x(256+1)のグリッドを割り当てます。 makegrid.py lons, lats, x, y = m0.makegrid(256+1,256+1,returnxy=True) こうすることで、ステレオ投影画像の縦横に256×256の等間隔格子を作成し、かつそれぞれの格子が該当する緯度・経度を取得する事ができます。 3.2 正投影図上のxy座標への変換 次に、投影する全球地図のBasemapを作成します。 全球のデータを一度に見えるように、東半球と西半球それぞれの投影画像を作成します。 globalmap.py # 1行2列の図を作成します。 fig, ax = plt.subplots(1, 2, figsize=(12,6)) plt.subplots_adjust(left=0.01, right=0.98, top=0.99, bottom=0.01) tstr = タイトル文字列を設定 # 投影直下点を北緯10度、東経120度とした半球の正投影図 m1 = Basemap(projection='ortho', lat_0=10, lon_0=120, resolution='i', ax=ax[0]) ax[0].set_title(tstr) # タイトルを設定 im1=m1.drawcoastlines(linewidth=0.5) # 海岸線を描画 # 同様に投影直下点を北緯10度、西経60度とした半球の正投影図 m2 = Basemap(projection='ortho', lat_0=10, lon_0=-60, resolution='i', ax=ax[1]) ax[1].set_title(tstr) im2=m2.drawcoastlines(linewidth=0.5) この準備をしておくことで、3.1で得られた緯度経度lons, latsから、Basemapの機能を用いて xy_trans.py xx, yy = m1(lons, lats) とすることで、m1の正投影図の上でのxy座標(xx, yy)に変換されます。 4 pcolormeshを用いての画像の貼り付け 座標変換もできたので、下記のようにimshowの位置指定機能を用いて前線画像を貼り付けよう!と考えたのですが、これはうまくいきません。 imsho.py ### うまく行かないコードです!! # 前線画像ファイルをオープンしてX座標を反転させておく t_img = (Image.open(sampleimg_file).convert('RGB')).resize((256,256),Image.HAMMING) t_array_r = np.array(t_img)/255 t_array = t_array_r[::-1,:] #画像をm1のBasemapに貼り付けて、array_extentにより画像の左下と右上コーナーを指定する。 x0, y0 = m1(117, 12) x1, y1 = m1(172, 54) array_extent = (x0, x1, y0, y1) im1 = m1.imshow(t_array,origin="lower") im1.set_extent(array_extent) なぜならば、imshowで指定できるset_extentでは、左下と左上、右上と右下の経度座標は同じでなければならないからです(同様に左上と右上、左下と右下の緯度座標も同じでなければなりません)。 つまり下の図の一番左側の矩形イメージをimshowによって投影図に貼り付けることは可能なのですが、中央の図のようになってしまいます。本来は、右側の図のようにならなければなりません。左上と右下の位置が違っていることがわかります(わかりやすいように背景に地形図を重ねています)。 そこで画像配列を緯度経度にもとづき、pcolormeshを用いて球面上に「塗る」ことにしました。 (右図は実際にそのようにして作成しています) 4.2 カラーマップ(pcolormesh)による画像描画 pcolormeshでは、格子状に並んだデータと、その格子を割り当てる座標を指定することができます。 このため4.1で示したような少し歪んだ並びについてもうまく塗ることができるわけです。 ただ、データについてはRGBのような3チャンネルではなくひとつの値となっていなければなりません。 そこで、RGB形式の出力データをグレースケールに変換して1チャンネルのデータとして、その値を表現するカラーマップを自作して元の色を再現することにしました。 このあたりの方法に関しては、下記サイトで纏めておられた先達のお知恵を拝借しています。 【Python/Pillow(PIL)】カラー,モノクロ,HSVなどの変換 不連続カラーバーの作成(Python) まずグレースケール変換後に赤、青、ピンク、白がそれぞれどんな数値に変換されるかを調査します。 colormap.py >>> from PIL import Image # 4x1 という調査用のRGB画像を用意 >>> img_rgb = Image.new("RGB",(4,1)) >>> img_rgb.putpixel((0,0),(255,0,0)) # 赤色(温暖前線)をセット >>> img_rgb.putpixel((1,0),(0,0,255)) # 青色(寒冷前線)をセット >>> img_rgb.putpixel((2,0),(255,0,255)) # ピンク(閉塞前線)をセット >>> img_rgb.putpixel((3,0),(255,255,255)) # 白(前線以外)をセット # 画像をグレースケールに変換 >>> img_L = img_rgb.convert("L") # 変換後にそれぞれの色がどういう数値に変換されたかを調査 >>> print(img_L.getpixel((0,0))) 76 # 赤は76 >>> print(img_L.getpixel((1,0))) 29 # 青は29 >>> print(img_L.getpixel((2,0))) 105 # ピンクは105 >>> print(img_L.getpixel((3,0))) 255 # 白は255 前線画像(RGB)をグレースケール変換したデータ配列がこのような値に変換されることを見越して、pcolormeshで用いるカラーマップを作成します。 変換の際は上で調べた値を挟んで色が再現される範囲をもたせるように敷居を設定します。 custom_cmap.py # しきい値を設定。青(29:20-70),赤(76:70-95),ピンク(105:95-240),白(255:240-255) level_rgb = [20/255,70/255,95/255,240/255,255/255] # 各しきいのカラーマップデータをRGBで指定。白は背景画像が透過されるように透明にする。 cmap_data = [(0,0,1),(1,0,0),(1,0,1),(1,1,1,0)] # camp_orig という名のカラーマップを作成。 cmap_orig = cls.ListedColormap(cmap_data) cmap_orig.set_under((1,1,1,0)) # しきい値より下の値がきたら透明白で塗る cmap_orig.set_over((1,1,1,0)) # しきい値より上の値がきたら透明白で塗る # camp_origとしきい値をくくりつける norm = cls.BoundaryNorm( level_rgb, cmap_orig.N ) このカラーマップを用いると、pcolormeshで使用できるように1チャンネルの配列化したデータから元のカラー図に戻すことができます。 前線画像をオープンするときにグレースケール化します。 pcolormesh.py # 前線画像(samplefile)をグレースケールでオープンし、 # t_array_Lというndarrayに変換する t_img_L = (Image.open(samplefile).convert('L')).resize((256,256),Image.HAMMING) t_array_L_t = np.asarray(t_img_L)/255 t_array_L = t_array_L_t[::-1,:] # 描画準備 fig, ax = plt.subplots(1, 2, figsize=(18,6)) plt.subplots_adjust(left=0.01, right=0.98, top=0.99, bottom=0.01) # Basemapで正投影(m2)とステレオ投影(m0)を作成する。 # 前線画像はm0の形式で作成されている m2 = Basemap(projection='ortho', lat_0=35, lon_0=135, resolution='i', ax=ax[1]) m0 = Basemap(projection='stere', llcrnrlat=12, urcrnrlat=54, llcrnrlon=115, urcrnrlon=178, lat_0=60, lon_0=140, resolution='i' , ax=ax[0]) # 前線画像に256x256格子を割り当て、それぞれの格子の緯度経度を取得する lons, lats, x, y = m0.makegrid(256+1,256+1,returnxy=True) # もとの前線画像をimshowでステレオ投影の地図に貼り付ける # これは読み取った元画像なので緯度経度と画像座標は一致している im0 = m0.imshow(t_array_L, origin="lower", cmap="gray") im0 = m0.drawcoastlines(linewidth=0.4) # 緯度経度を正投影図の画像座標に変換する xx, yy = m2(lons, lats) # pcolormeshで作成したカラーマップを用いてグレースケールデータを表示 im2=m2.shadedrelief() im2=m2.pcolormesh(xx, yy, t_array_L, cmap=cmap_orig, norm=norm) fig.colorbar(im2, ax=ax[1]) 下図左のようにグレースケール化したデータを、右図のカラーマップでpcolormeshを用いて表示することで元の図の色を再現しました。白色については、地上気圧図と重ねることを想定して透明にしてあります。 4.3 地上気圧図との重ね合わせ あとは天気図らしく地上気圧図を重ね合わせます。 ここにも気象図の可視化に関しては書いていますが、pygribというライブラリを用います。 GPVデータもそれ自体が緯度・経度ごとのデータなので、正投影図用に座標変換します。 mslp.py import pygrib # 等気圧線の間隔を定義 levels_prs = np.arange(930.0 , 1080.0, 4.0) # gpvfileにセットしたGRIB形式のGPVファイルをオープン grbs = pygrib.open(gpvfile) # GPVデータから地上気圧(海面更正気圧)を抜き出す letter_mslp = "Pressure reduced to MSL" grb_prs = grbs.select(parameterName=letter_mslp , level=0 , forecastTime=fcsttime) # GPVデータ自体の座標を取得し、投影図用に変換 gpvlats, gpvlons = grb_prs[0].latlons() x1, y1 = m1(gpvlons, gpvlats) x2, y2 = m2(gpvlons, gpvlats) # ラベル付きの等圧線を描画。GRIBの気圧はPa単位なので100で割ってhPa単位で表示。 plt.clabel(m1.contour(x1, y1, grb_prs[0].values / 100.0 , levels_prs , linewidths=0.3, colors='k') , fontsize=4, inline=1, fmt='%4.0f') # 等圧線の上から色付きのコンターを重ね合わせ m1.contourf(x1, y1, grb_prs[0].values / 100.0 , levels_prs , cmap="coolwarm" ) # 別半球にも同じことを実施 plt.clabel(m2.contour(x2, y2, grb_prs[0].values / 100.0 , levels_prs , linewidths=0.3, colors='k') , fontsize=4, inline=1, fmt='%4.0f') m2.contourf(x2, y2, grb_prs[0].values / 100.0 , levels_prs , cmap="coolwarm" ) # このあとにpcolormeshを用いた前線の描画が続きます。 4.4 その他 pcolormeshのデータをBasemapの正投影図に渡した際に、見えない側の半球のデータについても描画をしようとしてしまい、不要かつ奇妙な線や色が表示されることがあります。 この問題というか仕様の回避が下記で議論されておりました。 Problem with ortho projection and pcolormesh #470 結論としては不要な部分のデータはnp.nanにセットしてマスクすべし!ということです。 このために下記のようなコードを追加します。 AvoidingOrthoProjectionProblem.py xx, yy = m2(lons, lats) # 不要な部分(画像座標が下記を満たす部分)のインデックスを取得 ii = ( \ ( xx[:-1, :-1] > 1e20) | \ ( xx[1: , :-1] > 1e20) | \ ( xx[:-1, 1: ] > 1e20) | \ ( xx[1: , 1: ] > 1e20) | \ ( yy[:-1, :-1] > 1e20) | \ ( yy[1: , :-1] > 1e20) | \ ( yy[:-1, 1: ] > 1e20) | \ ( yy[1: , 1: ] > 1e20) \ ) # 上記条件のインデックスのデータをnp.nanでマスク t_array_2[ii] = np.nan # pcolormesh を実行する im2=m2.pcolormesh(xx, yy, t_array_2, cmap=cmap, norm=norm) こうして最終的に下図のような画像ができます。 下図では予報時間別に作った図をアニメーションGIFにしています。 2021/7/2 UTC12時を初期値とした予報値に前線検出を行ったもの 2021/7/16 UTC12時を初期値とした予報値に前線検出を行ったもの 2021/6/19 UTC12時を初期値とした予報値に前線検出を行ったもの 4. まとめ 日本付近の気象図(SPAS)を用いて前線を自動描画するよう学習させたニューラルネットワークを北半球の他の領域に適用して日本的な前線解析をした全球画像を作成しました。 全球画像作成にあたっては、 ・投影後の画像を別形式地図に投影するための座標変換  Basemapの座標変換機能を用います ・この座標変換により使えなくなる画像貼り付け機能の代替機能の工夫  カラー画像をグレー変換して、自作したカラーマップを用いてpcolormeshを用います という工夫を行いました。 今後、南半球の同緯度帯や、別の緯度帯にも展開していきたいです。 いまのところ、南半球の同緯度帯をうまくステレオ表示できず(南が地図の上部にくるステレオ投影ができない?)、入力データの作成ができていません。 長文・乱文のところ最後まで読んでいただいてありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】for文を用いた要素の取り出しとカウント enumerate関数

リストからの要素の取り出し for文を使うことで簡単にリストから要素を取り出すことができる。PythonのFor文はイテラブルに対して繰り返しを行うことができるため、リストから要素を取り出すには以下のように書く。 drink=["coffee","tea","milk"] for x in drink: print(x) 実行結果 coffee tea milk このようにイテラブルから要素を一個ずつ取り出すようなfor文をforeach文という。 要素の取り出しとカウント  要素を取り出すと同時に何番目に要素を取り出したかをカウントしてみる。 i=1 drink=["coffee","tea","milk"] for x in drink: print(i,x) i+=1 実行結果 1 coffee 2 milk 3 tea 上のプログラムではdrinkというリストに3個の要素があるため1,2,3とカウントしながら要素を取り出すことができた。このプログラムでもカウントしながら要素の取り出しができるが、もっと簡単に書く方法もある。 enumerate関数 enumerate関数を使うことで要素を取り出す際に何番目に取り出したかがすぐに分かる。同じようにdrinkのリストから3個の要素を取り出してみる。 drink=["coffe","tea","milk"] for a,b in enumerate(drink,1): print(a,b) 実行結果 1 coffee 2 tea 3 milk enumerate関数を使うことで要素の取り出しとカウントを行う処理を簡潔に書くことができる。 enumerate関数は以下のように使う。enumerate関数は要素を取り出し、カウントと要素をタプルで返す。 変数aにはカウントが代入され、変数bには要素が代入される。またenumerate関数にはカウントの開始値を指定することができる。 for 変数a,変数b in enumerate(イテラブル,開始値):
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SMTソルバー Z3Py

ちょっと前に「Answer Set Programming」というのが使えそうだと思ったのだが、記述表現がよくわからなく停滞していたが、同じようなことをが「Z3Py」を使うとPythonで書けるということで、例題を通じて理解を深めようと思う。 SMTソルバー Z3Pyとは Z3は、Microsoft Researchで開発された高性能の定理証明器(theorem prover)です。 https://ericpony.github.io/z3py-tutorial/guide-examples.htm SMT は背景理論付き充足可能性問題(Satisfiability Modulo Theories)の略語です。命題論理よりも表現能の高い論理体系で記述さた背景理論を、SAT技法で効果的に取り扱うことを目的とした技術だそうです。(条件を書くと、それが解けるか解けないかを判定してくれるもの、と理解していますorz) 例題 コイン問題(制約充足問題) コイン問題2 魔法陣 以下予定 部分和問題 グラフ彩色問題 数独 ビンパッキング問題 ナップサック問題 クイーン問題 制約最適化問題 ハノイの塔 巡回セールスマン問題 ブロックワールド問題 参考 Z3: Theorem Prover Coprisによる制約プログラミング入門
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

差分を取って動きがある間だけ画像を順番に差し替えていくPGM

差分を取って動きがある間だけ画像を順番に差し替えていくPGM 差分を取っていき、動きがある間だけ画像を更新していくpythonのPGMを作りました これで散歩画像データリストがあれば、足踏みしている間だけ画像を差し替えるようにして仮想散歩ができそうです... 内容としては前に作った差分が出ているうちだけ動画を再生するPGMの 中身を修正して作成しました githubURL: 使い方 以下のようなパラメータを指定してコマンドラインから実行します python 実行ファイルパス 差し替え画像のリストファイルのパス 差分検出判定率(ex:0.5) 画像差し替え間隔秒(ex:1.0) 参考にしたサイト ファイル入出力 — Pythonオンライン学習サービス PyQ(パイキュー)ドキュメント pip で OpenCV のインストール - Qiita
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

discord.py×Google Cloud TTS×Herokuで(100万文字まで)無料24時間稼働のDiscord読み上げbotを作る

背景 少し前にこんな記事を投稿しました。 夜でも接続が安定したDiscord読み上げbotをお金払わずに使いたい、 というモチベーションで始めました。安定性には満足したのですが、 喋るスピードが遅かったり、声質が耳に刺さる感じがしてイマイチだったんですよね。 そこでGoogle Cloud TTSを使った読み上げbotの開発に着手しました。 Google Cloud TTSはGoogleが公式に提供する音声合成APIで、 深層学習を使って作成したボイスモデルを用意しています。 自然な発音で、耳に刺さる感じも無くて良いです。 ↓このページの少し下にスクロールしたところで、発音のデモができます。 注意点として、Google Cloud TTSが無料なのは100万文字まで。 それ以降は従量課金となっています。身内で使うだけであれば十分ですね。 有料のDiscord読み上げbotでもこの音声合成APIを使っているところも あるくらいですので、結構快適に使えます。自分はこれをメインで運用しています。 実装した機能 自動入退室 入退室者の名前読み上げ(〇〇さんが入室/退室しました) ボイスチャンネル間の自動移動(ミュートしているユーザーに付いていく) メッセージの読み上げに名前付加(名前+メッセージ) 文末のwwwを笑に変換 添付ファイル有無の読み上げ 複数サーバー対応 やり方 まず、リンク先サイトを参考に、Google Cloud PlatformのText-to-Speech APIを 有効にして、json形式のキーファイルを入手してください。 リンク先サイトの「Google Cloud SDK のインストール」の項と 「Google Cloud SDK の初期化」の項は今回関係ないので飛ばしてください。 「認証情報の作成」までの項に従ってjson形式のキーファイルが入手できたら、 それ以降の項の作業はやらなくて大丈夫です。 次に、herokuの使い方を理解するために、こちらの記事を参考に 動作確認まで行ってください。 動作確認までできましたら、一旦そのプロジェクトは削除して、私が作ったコードでDeploy to Herokuしてください。 こんな画面が出ると思いますので、入力欄を埋めてDeploy appしてください。 あと、json形式のキーファイルを適当なエディタで開いておいてください。 その中の情報も使います。 App name: 好きな名前(他の人と被るとダメ) Choose a region: どこの国でもOK DISCORD_BOT_PREFIX: コマンドのプレフィックス。空欄でも可。 例えば、喋太郎では”?”、shovelでは"!sh "になっています。空欄にすると”?”に設定されます。 DISCORD_BOT_TOKEN: Botのトークン DISCORD_BOT_VOICE: Botの声質。空欄でも可。以下のページから選んで「音声名」を貼り付けてください (例: ja-JP-Wavenet-B)。空欄にすると女性の声になります。 GOOGLE_AUTH_PROVIDER_X509_CERT_URL: キーファイルの中のauth_provider_x509_cert_uriの値 キーファイルの中はこうなっていると思いますが "auth_provider_x509_cert_uri": "https://www.googleapis.com/oauth2/v1/certs", どの部分が値に相当するかというと、:の右側の""で囲まれた部分です。 https://www.googleapis.com/oauth2/v1/certs これから他のキーファイルの値も入力して頂きますが、ルールはすべて同じです。 GOOGLE_AUTH_URI: キーファイルの中のauth_uriの値 GOOGLE_CLIENT_EMAIL: キーファイルの中のclient_emailの値 GOOGLE_CLIENT_ID: キーファイルの中のclient_idの値 GOOGLE_CLIENT_X509_CERT_URL: キーファイルの中のclient_x509_cert_urlの値 GOOGLE_PRIVATE_KEY: キーファイルの中のprivate_keyの値 GOOGLE_PRIVATE_KEY_ID: キーファイルの中のprivate_key_idの値 GOOGLE_PROJECT_ID: キーファイルの中のproject_idの値 GOOGLE_TOKEN_URI: キーファイルの中のtoken_uriの値 GOOGLE_TYPE: キーファイルの中のtypeの値 一応コマンドもありますが、自動入退室なのであまり使わないと思います。 (プレフィックスを変更している人は、”?"を自分の設定したプレフィックスに読み替えてください) - ?接続: ボイスチャンネルに接続 - ?切断: ボイスチャンネルから切断 ボイスチャンネルにbotが入ってこなければDiscordトークンの間違い、 ボイスチャンネルに入ってくるけど喋らない場合は Text-to-Speech APIがちゃんと有効化されていないか、 キーファイルの値に写し間違いがあると思います。 最後に Google Cloud TTSはログインにjson形式のキーファイルが必要なので、 Deploy to herokuボタン経由では無理かなと思っていました。 今回は、キーファイルの内容を環境変数に全部登録してもらって、 そこから/tmpディレクトリにjsonファイルを生成するという荒技で対応しています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む