静かなる名辞 pythonとプログラミングのこと 2020-05-07T20:42:34+09:00 hayataka2049 Hatena::Blog hatenablog://blog/10328537792367869878 numbaとnumpy.emptyでbool配列が作れないとき hatenablog://entry/26006613563527206 2020-05-07T20:42:34+09:00 2020-05-07T22:09:05+09:00 タイトル通りのことをやろうとして、なんかエラーになったんですよね。 import numpy as np from numba import jit @jit("b1[:]()", nopython=True) def f(): a = np.empty(100, np.bool) return a f() 動きそうに見えますが、 Traceback (most recent call last): File "filename.py", line 4, in <module> @jit("b1[:]()", nopython=True) File "/*/site-packages/numb… <p> タイトル通りのことをやろうとして、なんかエラーになったんですよね。<br /> <br /> </p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">from</span> numba <span class="synPreProc">import</span> jit <span class="synPreProc">@</span><span class="synIdentifier">jit</span>(<span class="synConstant">&quot;b1[:]()&quot;</span>, nopython=<span class="synIdentifier">True</span>) <span class="synStatement">def</span> <span class="synIdentifier">f</span>(): a = np.empty(<span class="synConstant">100</span>, np.bool) <span class="synStatement">return</span> a f() </pre><p> 動きそうに見えますが、</p> <pre class="code" data-lang="" data-unlink>Traceback (most recent call last): File &#34;filename.py&#34;, line 4, in &lt;module&gt; @jit(&#34;b1[:]()&#34;, nopython=True) File &#34;/*/site-packages/numba/decorators.py&#34;, line 200, in wrapper disp.compile(sig) File &#34;/*/site-packages/numba/compiler_lock.py&#34;, line 32, in _acquire_compile_lock return func(*args, **kwargs) File &#34;/*/site-packages/numba/dispatcher.py&#34;, line 768, in compile cres = self._compiler.compile(args, return_type) File &#34;/*/site-packages/numba/dispatcher.py&#34;, line 81, in compile raise retval File &#34;/*/site-packages/numba/dispatcher.py&#34;, line 91, in _compile_cached retval = self._compile_core(args, return_type) File &#34;/*/site-packages/numba/dispatcher.py&#34;, line 109, in _compile_core pipeline_class=self.pipeline_class) File &#34;/*/site-packages/numba/compiler.py&#34;, line 551, in compile_extra return pipeline.compile_extra(func) File &#34;/*/site-packages/numba/compiler.py&#34;, line 331, in compile_extra return self._compile_bytecode() File &#34;/*/site-packages/numba/compiler.py&#34;, line 393, in _compile_bytecode return self._compile_core() File &#34;/*/site-packages/numba/compiler.py&#34;, line 373, in _compile_core raise e File &#34;/*/site-packages/numba/compiler.py&#34;, line 364, in _compile_core pm.run(self.state) File &#34;/*/site-packages/numba/compiler_machinery.py&#34;, line 347, in run raise patched_exception File &#34;/*/site-packages/numba/compiler_machinery.py&#34;, line 338, in run self._runPass(idx, pass_inst, state) File &#34;/*/site-packages/numba/compiler_lock.py&#34;, line 32, in _acquire_compile_lock return func(*args, **kwargs) File &#34;/*/site-packages/numba/compiler_machinery.py&#34;, line 302, in _runPass mutated |= check(pss.run_pass, internal_state) File &#34;/*/site-packages/numba/compiler_machinery.py&#34;, line 275, in check mangled = func(compiler_state) File &#34;/*/site-packages/numba/typed_passes.py&#34;, line 95, in run_pass raise_errors=self._raise_errors) File &#34;/*/site-packages/numba/typed_passes.py&#34;, line 67, in type_inference_stage infer.propagate(raise_errors=raise_errors) File &#34;/*/site-packages/numba/typeinfer.py&#34;, line 985, in propagate raise errors[0] numba.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend) Invalid use of Function(&lt;built-in function empty&gt;) with argument(s) of type(s): (Literal[int](100), Function(&lt;class &#39;bool&#39;&gt;)) * parameterized In definition 0: All templates rejected with literals. In definition 1: All templates rejected without literals. This error is usually caused by passing an argument of a type that is unsupported by the named function. [1] During: resolving callee type: Function(&lt;built-in function empty&gt;) [2] During: typing of call at filename.py (6) File &#34;filename.py&#34;, line 6: def f(): a = np.empty(100, np.bool) ^</pre><p> ふざけたエラーが出ました。</p><p> ググって解決しました。</p> <blockquote> <p>Related to #1311. np.bool_ works as expected.<br /> <a href="https://github.com/numba/numba/issues/2529">inference failure with np.empty on bool type &middot; Issue #2529 &middot; numba/numba &middot; GitHub</a></p> </blockquote> <p> 代わりにnp.bool_を使えばいいらしいです。馬鹿馬鹿しいですね。numbaの開発的には直すつもりはなさそうです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">jit</span>(<span class="synConstant">&quot;b1[:]()&quot;</span>, nopython=<span class="synIdentifier">True</span>) <span class="synStatement">def</span> <span class="synIdentifier">f</span>(): a = np.empty(<span class="synConstant">100</span>, np.bool_) <span class="synStatement">return</span> a </pre><p> こうするとちゃんと動きました。</p><p> bool型のdtypeを指定したいときとかは気をつけましょう。</p> hayataka2049 concurrent.futuresはなかなか便利かもしれない hatenablog://entry/26006613556901307 2020-05-01T07:53:09+09:00 2020-05-01T07:53:09+09:00 概要 「いまさら?」と思われるかもしれませんが、concurrent.futuresを使う機会があり、けっこう幸せでした。 本当に「いまさら?」なのですが、どういうとき便利でどういう風に使えるのか書いておきます。 リファレンス concurrent.futures -- 並列タスク実行 — Python 3.8.3rc1 ドキュメント 並列化の処理を向こうでやってくれる concurrent.futuresにはThreadPoolExecutorとProcessPoolExecutorの2つのクラスがあります。名前に入っているPoolという文字列から想像できる通り、これらは スレッド/プロセス… <div class="section"> <h3>概要</h3> <p> 「いまさら?」と思われるかもしれませんが、concurrent.futuresを使う機会があり、けっこう幸せでした。</p><p> 本当に「いまさら?」なのですが、どういうとき便利でどういう風に使えるのか書いておきます。</p><p> リファレンス<br /> <a href="https://docs.python.org/ja/3/library/concurrent.futures.html#module-concurrent.futures">concurrent.futures -- &#x4E26;&#x5217;&#x30BF;&#x30B9;&#x30AF;&#x5B9F;&#x884C; &mdash; Python 3.8.3rc1 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a></p><p></p> </div> <div class="section"> <h3>並列化の処理を向こうでやってくれる</h3> <p> concurrent.futuresにはThreadPoolExecutorとProcessPoolExecutorの2つのクラスがあります。名前に入っているPoolという文字列から想像できる通り、これらは</p> <ul> <li>スレッド/プロセスの束を最初に生成する</li> <li>作った束に処理を投げつけると適当に割り振ってくれる</li> </ul><p> という機能を持ちます。</p><p> これの良いところは、スレッド/プロセスの数をmax_workersで指定すれば、あとはそんなに考えることがないことです。処理待ちのタスクは内部的にキューで管理され、前のタスクが終わって空いたスレッド/プロセスに順次放り込まれます。自分でforkさせて……とかやっていると、たとえばタスクで処理対象になるデータ量に応じてスレッド/プロセスが増えていくようなあほくさい実装になってしまうときがありますが、その辺をよしなに制御してくれるのが強みです。</p><p> まあ、これに関してはもっと昔からあったmultiprocessing.Poolでもできるのですが、次のFutureは更に魅力的です。</p> </div> <div class="section"> <h3>Futureはすごい!</h3> <p> concurrent.futuresにはThreadPoolExecutorとProcessPoolExecutorの2つのクラスがあり、このインスタンスにsubmitメソッドを呼ぶと(呼び方はsubmit(fn, *args, **kwargs)です)Futureオブジェクトが返ってきます。</p><p> Futureオブジェクトの中では勝手に処理を進めてくれていて、処理が終わればfnとして渡した関数の返り値をresultメソッドで取得することができます。Futures.resultメソッド自体は処理が終わっていなければ呼び出し元をブロックしますが、重要なのはresultを呼ぼうが呼ぶまいが、子スレッド/子プロセスはノンブロッキングで勝手に走って勝手に終わるということです。呼び出し元がブロックされようがされまいが、子スレッド/子プロセスは走り出してしまえばあとはフルに動き続けるのが良いところです(処理するタスクがmax_workers以上の数残っている限りは)。</p><p> たとえば、以下のプログラムの処理結果はどのようになるでしょうか? ちょっと先に考えてみてください。printが出てくる順番とタイミング(UNIX時間の下5桁を出しています)に注目です。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> time <span class="synPreProc">import</span> threading <span class="synPreProc">from</span> concurrent.futures <span class="synPreProc">import</span> ThreadPoolExecutor <span class="synStatement">def</span> <span class="synIdentifier">f</span>(t): <span class="synIdentifier">print</span>(f<span class="synConstant">&quot;{t:2} start {threading.get_ident()} {time.time() % 10 ** 5}&quot;</span>) time.sleep(t) <span class="synIdentifier">print</span>(f<span class="synConstant">&quot;{t:2} end {threading.get_ident()} {time.time() % 10 ** 5}&quot;</span>) <span class="synStatement">return</span> t <span class="synStatement">with</span> ThreadPoolExecutor(max_workers=<span class="synConstant">2</span>) <span class="synStatement">as</span> executor: futures = [executor.submit(f, t) <span class="synStatement">for</span> t <span class="synStatement">in</span> [<span class="synConstant">20</span>, <span class="synConstant">10</span>, <span class="synConstant">1</span>, <span class="synConstant">2</span>, <span class="synConstant">3</span>, <span class="synConstant">4</span>]] time.sleep(<span class="synConstant">25</span>) result = [f.result() <span class="synStatement">for</span> f <span class="synStatement">in</span> futures] <span class="synIdentifier">print</span>(result, time.time() % <span class="synConstant">10</span> ** <span class="synConstant">5</span>) </pre><p> 正解はこうです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">20</span> start <span class="synConstant">140118759839488</span> <span class="synConstant">40802.507059812546</span> <span class="synConstant">10</span> start <span class="synConstant">140118677518080</span> <span class="synConstant">40802.507822752</span> <span class="synConstant">10</span> end <span class="synConstant">140118677518080</span> <span class="synConstant">40812.518659353256</span> <span class="synConstant">1</span> start <span class="synConstant">140118677518080</span> <span class="synConstant">40812.52083444595</span> <span class="synConstant">1</span> end <span class="synConstant">140118677518080</span> <span class="synConstant">40813.52251338959</span> <span class="synConstant">2</span> start <span class="synConstant">140118677518080</span> <span class="synConstant">40813.52270030975</span> <span class="synConstant">2</span> end <span class="synConstant">140118677518080</span> <span class="synConstant">40815.52499437332</span> <span class="synConstant">3</span> start <span class="synConstant">140118677518080</span> <span class="synConstant">40815.525171756744</span> <span class="synConstant">3</span> end <span class="synConstant">140118677518080</span> <span class="synConstant">40818.52837443352</span> <span class="synConstant">4</span> start <span class="synConstant">140118677518080</span> <span class="synConstant">40818.528562784195</span> <span class="synConstant">20</span> end <span class="synConstant">140118759839488</span> <span class="synConstant">40822.52741456032</span> <span class="synConstant">4</span> end <span class="synConstant">140118677518080</span> <span class="synConstant">40822.533390283585</span> [<span class="synConstant">20</span>, <span class="synConstant">10</span>, <span class="synConstant">1</span>, <span class="synConstant">2</span>, <span class="synConstant">3</span>, <span class="synConstant">4</span>] <span class="synConstant">40827.53366971016</span> </pre><p> スレッドプールがうまく効いている様子がわかります。また、Futuresを捕まえておけば結果の取得も問題なくできます。</p><p> 遅延評価……とはちょっと違いますね。バックグラウンド評価とでも言うべきでしょうか。</p><p> また、submitとは別にmapもあります。こちらは組み込みのmapと同様にジェネレータを返してくれるのが強みです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> time <span class="synPreProc">import</span> threading <span class="synPreProc">from</span> concurrent.futures <span class="synPreProc">import</span> ThreadPoolExecutor <span class="synStatement">def</span> <span class="synIdentifier">f</span>(t): <span class="synIdentifier">print</span>(f<span class="synConstant">&quot;{t:2} start {threading.get_ident()} {time.time() % 10 ** 5}&quot;</span>) time.sleep(t) <span class="synIdentifier">print</span>(f<span class="synConstant">&quot;{t:2} end {threading.get_ident()} {time.time() % 10 ** 5}&quot;</span>) <span class="synStatement">return</span> t <span class="synStatement">with</span> ThreadPoolExecutor(max_workers=<span class="synConstant">2</span>) <span class="synStatement">as</span> executor: mapgen = executor.map(f, [<span class="synConstant">20</span>, <span class="synConstant">10</span>, <span class="synConstant">1</span>, <span class="synConstant">2</span>, <span class="synConstant">3</span>, <span class="synConstant">4</span>]) time.sleep(<span class="synConstant">25</span>) <span class="synIdentifier">print</span>(mapgen) result = <span class="synIdentifier">list</span>(mapgen) <span class="synIdentifier">print</span>(result, time.time() % <span class="synConstant">10</span> ** <span class="synConstant">5</span>) <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant">20 start 139664387626752 41522.07571768761</span> <span class="synConstant">10 start 139664378906368 41522.0759935379</span> <span class="synConstant">10 end 139664378906368 41532.08786392212</span> <span class="synConstant"> 1 start 139664378906368 41532.08807730675</span> <span class="synConstant"> 1 end 139664378906368 41533.08958363533</span> <span class="synConstant"> 2 start 139664378906368 41533.08977293968</span> <span class="synConstant"> 2 end 139664378906368 41535.09212422371</span> <span class="synConstant"> 3 start 139664378906368 41535.092309713364</span> <span class="synConstant"> 3 end 139664378906368 41538.09562587738</span> <span class="synConstant"> 4 start 139664378906368 41538.09581398964</span> <span class="synConstant">20 end 139664387626752 41542.09483766556</span> <span class="synConstant"> 4 end 139664378906368 41542.10075592995</span> <span class="synConstant">&lt;generator object Executor.map.&lt;locals&gt;.result_iterator at 0x7f062a5ea258&gt;</span> <span class="synConstant">[20, 10, 1, 2, 3, 4] 41547.102033138275</span> <span class="synConstant">&quot;&quot;&quot;</span> </pre><p> 中身はFutureオブジェクトのlistで、イテレートするとresultメソッドを呼びながら結果を返してくれると考えると理解しやすい気がします。</p> </div> <div class="section"> <h3>パフォーマンスがよくなる</h3> <p> ThreadPoolExecutorは並列処理といってもPythonのGILに縛られていますから、Pythonプログラムをマルチコアで並列化して高速化することはできません。ただ、だからThreadPoolExecutorが駄目なのかというとそうではなくて、</p> <ul> <li>I/Oやネットワークの待ちがある</li> <li>子プロセスの待ちがある</li> </ul><p> など、Pythonの処理の外側で時間がかかっているときはちゃんと威力を発揮してくれます。子プロセスの場合は結果的にマルチコア並列化になります(かなり特殊な状況ですが、私はこれをやりました)。</p><p> インターフェースは互換なのでProcessPoolExecutorでも同じことができる訳ですが、スレッド並列のいいところはメモリ空間を共有することです。プロセス立ち上げ・プロセス間通信のオーバーヘッドとは無縁です(Pythonのmultiprocessingはpickle化してデータを送る実装で、これがまたかなり遅い)。しかもスレッド間で共有すべきリソースがあれば、グローバル変数に置いておけばそのまま使えます(ただし同じデータに複数スレッドからアクセスする場合、状況によっては同期処理を考慮する必要はある。threadingを使って書く)。また、スレッドごとに継続して情報やリソースを持たせておくこともできます。</p><p> 参考:<br /> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2020%2F04%2F28%2F081104" title="ThreadPoolExecutorのinitializerについて調べたのでメモ - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.haya-programming.com/entry/2020/04/28/081104">www.haya-programming.com</a></cite></p><br /> <p> そしてProcessPoolExecutorですが、こちらはなんといってもプロセス並列ができ、処理の高速化に寄与します。ただ、multiprocessingの仕組みを理解して慣れておいた方が良さげではあります。オーバーヘッドも大きいし、微妙に癖があったりするので、活用するには慣れが必要です。それでも素のPythonではどうあがいても実現できないマルチコア並列化をPythonの枠組みの中でやれるということは素晴らしく、安直な高速化が可能です。もちろん何をどう並列化するかはよく考えておかないといけないのですが。<br /> <br /> </p> </div> <div class="section"> <h3>プロセス並列ではmp_contextも指定できる</h3> <p> プロセス並列化を行う際には、mp_contextというオプションを渡すことができます。これはmultiprocessing.get_contextで取得可能です。</p><p> 実用的な使い方は、プロセス生成に使う方法をforkやspawnなどの間で切り替えることでしょう。経験的には、メモリ消費が大きいけど速いfork、省メモリだけど遅いspawnといった関係があり、状況に応じて簡単に切り替えられるのは便利です。</p> </div> <div class="section"> <h3>地味に嬉しいshutdown・with文との組み合わせ</h3> <p> 自分でthreadingやmultiprocessingを書くと、使い終わったスレッド/プロセスの終了がけっこう手間で、しかも失敗しがちです。特にプロセスが生き残ると、メモリを無駄に食ったりしていいことがありません。</p><p> その辺を深く考えなくてもよしなにやってくれるのがshutdownメソッドで、単純に結果を取ってから呼べばいいのです。</p><p> もっと嬉しいことに、Executorはwith文と組み合わせて使うことができ、そうするとwith文を抜けるとき勝手にshutdownしてくれます。open関数のようなものです。便利です。</p> </div> <div class="section"> <h3>まとめ</h3> <p> 非同期処理をしたいけどasyncioはとっつきづらいしとか、マルチコアを活かした並列化を書きたいけどmultiprocessingを直接叩くのはしんどいしとか、そういう状況ではだいたいこれを使っておけばいいだろうという性能になっています。しかもスレッド/プロセスプールは無駄なく実行されるので、自分で書いて最適化するより効率が良かったりします。並列化したいときに最初に検討するのはこれ、くらいの扱いでいいですね。</p> </div> hayataka2049 ThreadPoolExecutorのinitializerについて調べたのでメモ hatenablog://entry/26006613556686087 2020-04-28T08:11:04+09:00 2020-04-28T08:11:44+09:00 概要 ThreadPoolExecutorにはinitializerという便利そうなオプションがあります。でもリファレンスの説明があっさりしていて、挙動がよくわからなかったので調べました。 先に断っておくと、このオプションはPython3.7で追加されたもので、それ以前のバージョンでは存在しません。その場合の代替案も書いておくので参考にしてください。 はじめに とりあえず先に書いておくと、concurrent.futures.ThreadPoolExecutorは以下のように使えるものです。 import time import threading from concurrent.future… <div class="section"> <h3>概要</h3> <p> ThreadPoolExecutorにはinitializerという便利そうなオプションがあります。でもリファレンスの説明があっさりしていて、挙動がよくわからなかったので調べました。</p><p> 先に断っておくと、このオプションはPython3.7で追加されたもので、それ以前のバージョンでは存在しません。その場合の代替案も書いておくので参考にしてください。</p> </div> <div class="section"> <h3>はじめに</h3> <p> とりあえず先に書いておくと、concurrent.futures.ThreadPoolExecutorは以下のように使えるものです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> time <span class="synPreProc">import</span> threading <span class="synPreProc">from</span> concurrent.futures <span class="synPreProc">import</span> ThreadPoolExecutor <span class="synStatement">def</span> <span class="synIdentifier">job</span>(t): <span class="synIdentifier">print</span>(<span class="synConstant">&quot;in job:&quot;</span>, t, <span class="synIdentifier">int</span>(time.time()) % <span class="synConstant">1000</span>, threading.get_ident()) time.sleep(t) <span class="synStatement">return</span> t <span class="synIdentifier">print</span>(threading.get_ident()) <span class="synStatement">with</span> ThreadPoolExecutor(max_workers=<span class="synConstant">2</span>) <span class="synStatement">as</span> executor: futures = [executor.submit(job, i) <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">6</span>)] result = [f.result() <span class="synStatement">for</span> f <span class="synStatement">in</span> futures] <span class="synIdentifier">print</span>(<span class="synConstant">&quot;finished:&quot;</span>, <span class="synIdentifier">int</span>(time.time()) % <span class="synConstant">1000</span>) <span class="synIdentifier">print</span>(result) <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant">140248999868224</span> <span class="synConstant">in job: 0 233 140248964409088</span> <span class="synConstant">in job: 1 233 140248964409088</span> <span class="synConstant">in job: 2 233 140248955684608</span> <span class="synConstant">in job: 3 234 140248964409088</span> <span class="synConstant">in job: 4 235 140248955684608</span> <span class="synConstant">in job: 5 237 140248964409088</span> <span class="synConstant">finished: 242</span> <span class="synConstant">[0, 1, 2, 3, 4, 5]</span> <span class="synConstant">&quot;&quot;&quot;</span> </pre><p> 他の使い方もありますが、今回はこれしか検討しません。順番通りに結果が得られた方が都合が良いことの方が多いでしょう。</p><p> リファレンス<br /> <a href="https://docs.python.org/ja/3/library/concurrent.futures.html#threadpoolexecutor">concurrent.futures -- &#x4E26;&#x5217;&#x30BF;&#x30B9;&#x30AF;&#x5B9F;&#x884C; &mdash; Python 3.8.2 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a></p><p> 表示されているUNIX時間の下3桁と、<a href="https://docs.python.org/ja/3/library/threading.html#threading.get_ident">threading.get_ident</a>で取得されているスレッドのIDに注目しておいてください。親スレッドと、他に子スレッド2つが存在していることがわかります。ThreadPoolExecutorなので子スレッドは2つっきりで、同じスレッドが最初から最後まで使いまわされます。submitされたものはキューに突っ込まれて、空いているスレッドに放り込まれて終了までそのスレッドで実行されると考えるとわかりやすいでしょう。</p><p> なお、後述するinitializerはget_identでIDを取らないと基本的に役に立たないと思います。</p> </div> <div class="section"> <h3>initializerを使う</h3> <p> initializerはスレッド開始時の処理を書いておくと良いようです。</p><p> ちょっと改造したプログラムです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> time <span class="synPreProc">import</span> threading <span class="synPreProc">from</span> concurrent.futures <span class="synPreProc">import</span> ThreadPoolExecutor <span class="synStatement">def</span> <span class="synIdentifier">initializer</span>(): <span class="synIdentifier">print</span>(<span class="synConstant">&quot;in init&quot;</span>, threading.get_ident()) <span class="synStatement">def</span> <span class="synIdentifier">job</span>(t): <span class="synIdentifier">print</span>(<span class="synConstant">&quot;in job:&quot;</span>, t, <span class="synIdentifier">int</span>(time.time()) % <span class="synConstant">1000</span>, threading.get_ident()) time.sleep(t) <span class="synStatement">return</span> t <span class="synIdentifier">print</span>(threading.get_ident()) <span class="synStatement">with</span> ThreadPoolExecutor(max_workers=<span class="synConstant">2</span>, initializer=initializer) <span class="synStatement">as</span> executor: futures = [executor.submit(job, i) <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">6</span>)] result = [f.result() <span class="synStatement">for</span> f <span class="synStatement">in</span> futures] <span class="synIdentifier">print</span>(<span class="synConstant">&quot;finished:&quot;</span>, <span class="synIdentifier">int</span>(time.time()) % <span class="synConstant">1000</span>) <span class="synIdentifier">print</span>(result) <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant">140134438733632</span> <span class="synConstant">in init 140134403274496</span> <span class="synConstant">in job: 0 369 140134403274496</span> <span class="synConstant">in init 140134394550016</span> <span class="synConstant">in job: 1 369 140134403274496</span> <span class="synConstant">in job: 2 369 140134394550016</span> <span class="synConstant">in job: 3 370 140134403274496</span> <span class="synConstant">in job: 4 371 140134394550016</span> <span class="synConstant">in job: 5 373 140134403274496</span> <span class="synConstant">finished: 378</span> <span class="synConstant">[0, 1, 2, 3, 4, 5]</span> <span class="synConstant">&quot;&quot;&quot;</span> </pre><p> スレッド開始時に一回だけ呼ばれている様子がここからわかります。</p><p> そうは言ってもどう使うの? と思う人もいるかもしれませんが、意外と使いではあります。「スレッドごとに一回だけ最初にやれば後は使いまわせるが、最初の1回にはそこそこ時間がかかる」ような処理を考えてみます。たとえばI/OとかDBのコネクションを作るのに時間がかかる、みたいなシチュエーションでしょうか。</p><p> 問題は、initializerはtargetとスコープを共有してくれたりはしない(そんな特殊なことはできるはずもない)ので、いまいち使いづらいことです。けっきょく自分でリソース管理を書かないといけません。といっても、GILがあるので、スレッドごとにリソースを確保して互いに触らない、という条件なら同期を考える必要まではありません。スレッドごとに独立にリソースアクセスできるようにグローバル変数でdictを置いておけば十分でしょう。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> time <span class="synPreProc">import</span> random <span class="synPreProc">import</span> threading <span class="synPreProc">from</span> concurrent.futures <span class="synPreProc">import</span> ThreadPoolExecutor resources = <span class="synIdentifier">dict</span>() <span class="synStatement">def</span> <span class="synIdentifier">initializer</span>(): id_ = threading.get_ident() resources[id_] = random.random() <span class="synIdentifier">print</span>(<span class="synConstant">&quot;in&quot;</span>, id_, <span class="synConstant">&quot;, its resource is &quot;</span>, resources[id_]) <span class="synStatement">def</span> <span class="synIdentifier">job</span>(t): <span class="synIdentifier">print</span>(threading.get_ident(), resources[threading.get_ident()]) time.sleep(<span class="synConstant">2</span>) <span class="synStatement">return</span> t <span class="synIdentifier">print</span>(threading.get_ident()) <span class="synStatement">with</span> ThreadPoolExecutor(max_workers=<span class="synConstant">2</span>, initializer=initializer) <span class="synStatement">as</span> executor: futures = [executor.submit(job, i) <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">6</span>)] result = [f.result() <span class="synStatement">for</span> f <span class="synStatement">in</span> futures] <span class="synIdentifier">print</span>(<span class="synConstant">&quot;finished:&quot;</span>, <span class="synIdentifier">int</span>(time.time()) % <span class="synConstant">1000</span>) <span class="synIdentifier">print</span>(result) <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant">140374921525056</span> <span class="synConstant">in 140374878914304 , its resource is 0.06718002352497798</span> <span class="synConstant">140374878914304 0.06718002352497798</span> <span class="synConstant">in 140374870189824 , its resource is 0.4636817738099904</span> <span class="synConstant">140374870189824 0.4636817738099904</span> <span class="synConstant">140374878914304 0.06718002352497798</span> <span class="synConstant">140374870189824 0.4636817738099904</span> <span class="synConstant">140374878914304 0.06718002352497798</span> <span class="synConstant">140374870189824 0.4636817738099904</span> <span class="synConstant">finished: 781</span> <span class="synConstant">[0, 1, 2, 3, 4, 5]</span> <span class="synConstant">&quot;&quot;&quot;</span> </pre> </div> <div class="section"> <h3>代替案</h3> <p> 普通にtarget先頭でif使えばいいのでは・・・</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> time <span class="synPreProc">import</span> random <span class="synPreProc">import</span> threading <span class="synPreProc">from</span> concurrent.futures <span class="synPreProc">import</span> ThreadPoolExecutor resources = <span class="synIdentifier">dict</span>() <span class="synStatement">def</span> <span class="synIdentifier">job</span>(t): id_ = threading.get_ident() <span class="synStatement">if</span> id_ <span class="synStatement">not</span> <span class="synStatement">in</span> resources: resources[id_] = random.random() <span class="synIdentifier">print</span>(id_, <span class="synIdentifier">int</span>(time.time()) % <span class="synConstant">1000</span>, resources[id_]) time.sleep(<span class="synConstant">2</span>) <span class="synStatement">return</span> t <span class="synIdentifier">print</span>(threading.get_ident()) <span class="synStatement">with</span> ThreadPoolExecutor(max_workers=<span class="synConstant">2</span>) <span class="synStatement">as</span> executor: futures = [executor.submit(job, i) <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">6</span>)] result = [f.result() <span class="synStatement">for</span> f <span class="synStatement">in</span> futures] <span class="synIdentifier">print</span>(<span class="synConstant">&quot;finished:&quot;</span>, <span class="synIdentifier">int</span>(time.time()) % <span class="synConstant">1000</span>) <span class="synIdentifier">print</span>(result) <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant">140465058035520</span> <span class="synConstant">140465015424768 933 0.8531529694670728</span> <span class="synConstant">140465006700288 933 0.279548446733512</span> <span class="synConstant">140465015424768 935 0.8531529694670728</span> <span class="synConstant">140465006700288 935 0.279548446733512</span> <span class="synConstant">140465015424768 937 0.8531529694670728</span> <span class="synConstant">140465006700288 937 0.279548446733512</span> <span class="synConstant">finished: 939</span> <span class="synConstant">[0, 1, 2, 3, 4, 5]</span> <span class="synConstant">&quot;&quot;&quot;</span> </pre><p> 最初の一回だけ実行されればいいので、なければないでなんとかなります。initializer使うとちょっと見通しが良いかな?</p> </div> <div class="section"> <h3>まとめ</h3> <p> なんかあってもなくても良いような気がしてきましたが、とにかくこんな感じで使えはします。</p><p> どちらかというと同じインターフェースのProcessPoolExecutorの方が使いでがあるかもしれません。プロセス内のグローバル変数として確保すれば素直に使えるはずだからです。インターフェースを揃えないといけないのでThreadPoolExecutorにもつけたとか・・・</p><p> もっと他に有益な使い方がある、ということを知っている人は教えてください。</p> </div> hayataka2049 Pythonで遅いサブプロセスをスレッド並列でたくさん叩く hatenablog://entry/26006613556678201 2020-04-26T20:49:07+09:00 2020-04-26T20:49:22+09:00 概要 いつ使うんだと言われてしまうPythonのスレッドですが、Pythonの外で遅い原因があるときは高速化に威力を発揮します。 たとえばこんな感じです。言語はbashです。 #!/bin/bash sleep 3 echo "hoge" 特にひねりはありません。slow_command.shとでもして保存しておきます。 Pythonから呼ぶと、 import subprocess for _ in range(5): result = subprocess.run(["./slow_command.sh"], stdout=subprocess.PIPE) print(result.stdo… <div class="section"> <h3>概要</h3> <p> いつ使うんだと言われてしまうPythonのスレッドですが、Pythonの外で遅い原因があるときは高速化に威力を発揮します。</p><p> たとえばこんな感じです。言語はbashです。<br /> <br /> </p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment">#!/bin/bash</span> <span class="synStatement">sleep</span> <span class="synConstant">3</span> <span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">&quot;</span><span class="synConstant">hoge</span><span class="synStatement">&quot;</span> </pre><p> 特にひねりはありません。slow_command.shとでもして保存しておきます。</p><p> Pythonから呼ぶと、</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> subprocess <span class="synStatement">for</span> _ <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">5</span>): result = subprocess.run([<span class="synConstant">&quot;./slow_command.sh&quot;</span>], stdout=subprocess.PIPE) <span class="synIdentifier">print</span>(result.stdout.decode()) </pre><p> いい加減ですがこんな感じでしょうか。当たり前のように15秒かけて実行してくれます。</p> </div> <div class="section"> <h3>ThreadPoolExecutorを使う</h3> <ul> <li>CPUバウンドではない</li> <li>遅いのはPythonではない</li> </ul><p> といった条件が揃っているので、Pythonのスレッドを使う良い機会です。</p><p> threading直叩きはこの場合つらいものがあるので、concurrent.futures.ThreadPoolExecutorを使います。</p><p><a href="https://docs.python.org/ja/3/library/concurrent.futures.html#threadpoolexecutor">concurrent.futures -- &#x4E26;&#x5217;&#x30BF;&#x30B9;&#x30AF;&#x5B9F;&#x884C; &mdash; Python 3.8.2 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a><br /> </p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> subprocess <span class="synPreProc">from</span> concurrent.futures <span class="synPreProc">import</span> ThreadPoolExecutor <span class="synStatement">def</span> <span class="synIdentifier">f</span>(): result = subprocess.run([<span class="synConstant">&quot;./slow_command.sh&quot;</span>], stdout=subprocess.PIPE) <span class="synStatement">return</span> result.stdout.decode() <span class="synStatement">with</span> ThreadPoolExecutor(max_workers=<span class="synConstant">5</span>) <span class="synStatement">as</span> pool: futures = [pool.submit(f) <span class="synStatement">for</span> _ <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">5</span>)] <span class="synStatement">for</span> f <span class="synStatement">in</span> futures: <span class="synIdentifier">print</span>(f.result()) </pre><p> 今度はちゃんと5秒で終わります。</p> </div> <div class="section"> <h3>asyncio</h3> <p> asyncioはこれまでとっつきづらい気がしてほとんど触ってこなかったのですが、やればできるんじゃないでしょうか。</p><p><a href="https://stackoverflow.com/questions/41063331/how-to-use-asyncio-with-existing-blocking-library">python - How to use asyncio with existing blocking library? - Stack Overflow</a></p><p> まだ試していないので、そのうち試してみて記事をアップデートします(か、別記事になるかもしれない)。</p> </div> <div class="section"> <h3>使いみち</h3> <p> 遅いサブプロセスの結果を大量に受けるときはありです。</p><p> スレッド並列といってもけっきょく子プロセスが生えてしまうので、ワーカーの数には気をつけた方が良いでしょう(同時に子プロセス生やしすぎてメモリエラー、とか)。</p> </div> hayataka2049 Pythonの文字列は同じ長さでもメモリ消費量が違うときがある hatenablog://entry/26006613555246115 2020-04-24T03:51:51+09:00 2020-04-24T03:51:51+09:00 概要 Pythonの文字列は、内容によって一文字の幅が違います。 なお、Python3のstrを前提にさせてください。 実験 sys.getsizeofで測ってみます。これを使うのはちょっと議論の余地がありますが、 object のサイズをバイト数で返します。object は任意の型のオブジェクトです。すべての組み込みオブジェクトは正しい値を返します。サードパーティー製の型については実装依存になります。 オブジェクトに直接起因するメモリ消費のみを表し、参照するオブジェクトは含みません。 sys --- システムパラメータと関数 — Python 3.8.2 ドキュメント とりあえず信じていいと… <div class="section"> <h3>概要</h3> <p> Pythonの文字列は、内容によって一文字の幅が違います。</p><p> なお、Python3のstrを前提にさせてください。</p> </div> <div class="section"> <h3>実験</h3> <p> sys.getsizeofで測ってみます。これを使うのはちょっと議論の余地がありますが、</p> <blockquote> <p>object のサイズをバイト数で返します。object は任意の型のオブジェクトです。すべての組み込みオブジェクトは正しい値を返します。サードパーティー製の型については実装依存になります。<br /> オブジェクトに直接起因するメモリ消費のみを表し、参照するオブジェクトは含みません。<br /> <a href="https://docs.python.org/ja/3/library/sys.html#sys.getsizeof">sys --- &#x30B7;&#x30B9;&#x30C6;&#x30E0;&#x30D1;&#x30E9;&#x30E1;&#x30FC;&#x30BF;&#x3068;&#x95A2;&#x6570; &mdash; Python 3.8.2 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a></p> </blockquote> <p> とりあえず信じていいと信じます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">import</span> sys &gt;&gt;&gt; sys.getsizeof(<span class="synConstant">&quot;hogefuga&quot;</span>) <span class="synConstant">57</span> &gt;&gt;&gt; sys.getsizeof(<span class="synConstant">&quot;ほげふが&quot;</span>) <span class="synConstant">82</span> </pre><p> なんかすでにヘンなことになっています。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synIdentifier">len</span>(<span class="synConstant">&quot;hogefuga&quot;</span>) <span class="synConstant">8</span> &gt;&gt;&gt; <span class="synIdentifier">len</span>(<span class="synConstant">&quot;ほげふが&quot;</span>) <span class="synConstant">4</span> </pre><p> あ、lenが違いました。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; sys.getsizeof(<span class="synConstant">&quot;ほげふが&quot;</span>*<span class="synConstant">2</span>) <span class="synConstant">90</span> </pre><p> ひらがなで4文字増えて、さっきの82バイトから8バイト増えたので、1文字2バイトで持っている訳です。</p><p> ASCIIのhogefugaでも同じ要領で1文字のメモリ消費量を測ってみましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; sys.getsizeof(<span class="synConstant">&quot;hogefuga&quot;</span>*<span class="synConstant">2</span>) <span class="synConstant">65</span> </pre><p> (65-57)/8で、1文字1バイトです。</p> </div> <div class="section"> <h3>理由</h3> <p> まあ、こういう仕様が合理的というのは誰でも理解はできるのではないかと思います。単バイト文字なのかマルチバイト文字なのかによって扱いが変わる訳です。</p><p> リファレンスにも記述があります。「Python/C API リファレンスマニュアル」という、普段馴染みのない方に書いてありますが。</p> <blockquote> <p>Python3.3 の PEP 393 実装から、メモリ効率を維持しながらUnicode文字の完全な範囲を扱えるように、Unicodeオブジェクトは内部的に多様な表現形式を用いています。</p><p><a href="https://docs.python.org/ja/3/c-api/unicode.html">Unicode &#x30AA;&#x30D6;&#x30B8;&#x30A7;&#x30AF;&#x30C8;&#x3068; codec &mdash; Python 3.8.2 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a></p> </blockquote> <p> 内部表現は1バイトか2バイトか4バイトのどれかです! ASCIIなら1バイト、日本語はだいたい2バイトでしょうね。他の言語とか絵文字記号の類はきっと4バイトもあり得るのでしょう。</p><p> 内部でうまく処理するには1文字の長さが固定長でないとめちゃくちゃ都合が悪く、かといって32bitに揃えるのもメモリ効率が悪いので(というかASCII主義のアルファベット話者から見たら無駄なので)、こういう策を取っているのだと思います。詳しくは調査していませんが。</p><p> Pythonの文字列はimmutableなので、これで困らない訳です。操作するときに適切に扱う処理を、組み込みで実装してくれているから。</p> </div> <div class="section"> <h3>こうなっているから起こること</h3> <p> 表現できる文字種に応じて内部表現が決まるので、ASCIIの長い文にマルチバイト文字1つ付け足すと内部表現の大きさがほぼ二倍になったりします。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; sys.getsizeof(<span class="synConstant">&quot;hogefuga&quot;</span>*<span class="synConstant">1000</span>) <span class="synConstant">8049</span> &gt;&gt;&gt; sys.getsizeof(<span class="synConstant">&quot;hogefuga&quot;</span>*<span class="synConstant">1000</span> + <span class="synConstant">&quot;あ&quot;</span>) <span class="synConstant">16076</span> </pre><p> 異なる内部表現の文字列同士を結合すると、当然大きい方に揃えられます。処理速度に差が出るかもしれません。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">import</span> timeit &gt;&gt;&gt; a = <span class="synConstant">&quot;hoge&quot;</span> * <span class="synConstant">1000</span> &gt;&gt;&gt; b = <span class="synConstant">&quot;fuga&quot;</span> * <span class="synConstant">1000</span> &gt;&gt;&gt; timeit.timeit(<span class="synStatement">lambda</span> : a + b) <span class="synConstant">0.4121553519998997</span> &gt;&gt;&gt; c = <span class="synConstant">&quot;ぴよ&quot;</span> * <span class="synConstant">1000</span> &gt;&gt;&gt; timeit.timeit(<span class="synStatement">lambda</span> : a + c) <span class="synConstant">1.6905145230000471</span> </pre><p> 出ました。</p> </div> <div class="section"> <h3>結論</h3> <p> 普通にPythonを使う分には気にする必要はまったくない事柄なのですが、知っておくと文字列がちょっと思っていたのと違うものに見えてくるときがあるかもしれないと思いました。</p> </div> hayataka2049 Pythonの文字列メソッドとバイト列の微妙な関係 hatenablog://entry/26006613554051757 2020-04-21T23:05:28+09:00 2020-04-21T23:07:29+09:00 Python3になってからは普段あまり気にしなくても良いようになりましたが、Pythonの文字列っぽい型にはstrとbytesがあります*1。そして、strもbytesも同じようなメソッドを実装してくれています。組み込み型 — Python 3.8.2 ドキュメント組み込み型 — Python 3.8.2 ドキュメント >>> [x for x in dir(str) if "__" not in x] ['capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'forma… <p> Python3になってからは普段あまり気にしなくても良いようになりましたが、Pythonの文字列っぽい型にはstrとbytesがあります<a href="#f-56a230bc" name="fn-56a230bc" title="すべてPython3準拠で説明します。さすがにPython2はもういいでしょ">*1</a>。そして、strもbytesも同じようなメソッドを実装してくれています。</p><p><a href="https://docs.python.org/ja/3/library/stdtypes.html#bytes-and-bytearray-operations">&#x7D44;&#x307F;&#x8FBC;&#x307F;&#x578B; &mdash; Python 3.8.2 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a></p><p><a href="https://docs.python.org/ja/3/library/stdtypes.html#string-methods">&#x7D44;&#x307F;&#x8FBC;&#x307F;&#x578B; &mdash; Python 3.8.2 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a><br /> </p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; [x <span class="synStatement">for</span> x <span class="synStatement">in</span> <span class="synIdentifier">dir</span>(<span class="synIdentifier">str</span>) <span class="synStatement">if</span> <span class="synConstant">&quot;__&quot;</span> <span class="synStatement">not</span> <span class="synStatement">in</span> x] [<span class="synConstant">'capitalize'</span>, <span class="synConstant">'casefold'</span>, <span class="synConstant">'center'</span>, <span class="synConstant">'count'</span>, <span class="synConstant">'encode'</span>, <span class="synConstant">'endswith'</span>, <span class="synConstant">'expandtabs'</span>, <span class="synConstant">'find'</span>, <span class="synConstant">'format'</span>, <span class="synConstant">'format_map'</span>, <span class="synConstant">'index'</span>, <span class="synConstant">'isalnum'</span>, <span class="synConstant">'isalpha'</span>, <span class="synConstant">'isdecimal'</span>, <span class="synConstant">'isdigit'</span>, <span class="synConstant">'isidentifier'</span>, <span class="synConstant">'islower'</span>, <span class="synConstant">'isnumeric'</span>, <span class="synConstant">'isprintable'</span>, <span class="synConstant">'isspace'</span>, <span class="synConstant">'istitle'</span>, <span class="synConstant">'isupper'</span>, <span class="synConstant">'join'</span>, <span class="synConstant">'ljust'</span>, <span class="synConstant">'lower'</span>, <span class="synConstant">'lstrip'</span>, <span class="synConstant">'maketrans'</span>, <span class="synConstant">'partition'</span>, <span class="synConstant">'replace'</span>, <span class="synConstant">'rfind'</span>, <span class="synConstant">'rindex'</span>, <span class="synConstant">'rjust'</span>, <span class="synConstant">'rpartition'</span>, <span class="synConstant">'rsplit'</span>, <span class="synConstant">'rstrip'</span>, <span class="synConstant">'split'</span>, <span class="synConstant">'splitlines'</span>, <span class="synConstant">'startswith'</span>, <span class="synConstant">'strip'</span>, <span class="synConstant">'swapcase'</span>, <span class="synConstant">'title'</span>, <span class="synConstant">'translate'</span>, <span class="synConstant">'upper'</span>, <span class="synConstant">'zfill'</span>] &gt;&gt;&gt; [x <span class="synStatement">for</span> x <span class="synStatement">in</span> <span class="synIdentifier">dir</span>(<span class="synIdentifier">bytes</span>) <span class="synStatement">if</span> <span class="synConstant">&quot;__&quot;</span> <span class="synStatement">not</span> <span class="synStatement">in</span> x] [<span class="synConstant">'capitalize'</span>, <span class="synConstant">'center'</span>, <span class="synConstant">'count'</span>, <span class="synConstant">'decode'</span>, <span class="synConstant">'endswith'</span>, <span class="synConstant">'expandtabs'</span>, <span class="synConstant">'find'</span>, <span class="synConstant">'fromhex'</span>, <span class="synConstant">'hex'</span>, <span class="synConstant">'index'</span>, <span class="synConstant">'isalnum'</span>, <span class="synConstant">'isalpha'</span>, <span class="synConstant">'isdigit'</span>, <span class="synConstant">'islower'</span>, <span class="synConstant">'isspace'</span>, <span class="synConstant">'istitle'</span>, <span class="synConstant">'isupper'</span>, <span class="synConstant">'join'</span>, <span class="synConstant">'ljust'</span>, <span class="synConstant">'lower'</span>, <span class="synConstant">'lstrip'</span>, <span class="synConstant">'maketrans'</span>, <span class="synConstant">'partition'</span>, <span class="synConstant">'replace'</span>, <span class="synConstant">'rfind'</span>, <span class="synConstant">'rindex'</span>, <span class="synConstant">'rjust'</span>, <span class="synConstant">'rpartition'</span>, <span class="synConstant">'rsplit'</span>, <span class="synConstant">'rstrip'</span>, <span class="synConstant">'split'</span>, <span class="synConstant">'splitlines'</span>, <span class="synConstant">'startswith'</span>, <span class="synConstant">'strip'</span>, <span class="synConstant">'swapcase'</span>, <span class="synConstant">'title'</span>, <span class="synConstant">'translate'</span>, <span class="synConstant">'upper'</span>, <span class="synConstant">'zfill'</span>] </pre><p> 完全に互換という訳でもないようですが、それでもだいたい同じ操作がサポートされています。</p><p> 問題は、strとbytesを混在させて使えないことです。</p><p> splitで見てみます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synConstant">&quot;a,b,c&quot;</span>.split(b<span class="synConstant">&quot;,&quot;</span>) Traceback (most recent call last): File <span class="synConstant">&quot;&lt;stdin&gt;&quot;</span>, line <span class="synConstant">1</span>, <span class="synStatement">in</span> &lt;module&gt; <span class="synType">TypeError</span>: Can<span class="synConstant">'t convert '</span><span class="synIdentifier">bytes</span><span class="synConstant">' object to str implicitly</span> </pre><pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; b<span class="synConstant">&quot;a,b,c&quot;</span>.split(<span class="synConstant">&quot;,&quot;</span>) Traceback (most recent call last): File <span class="synConstant">&quot;&lt;stdin&gt;&quot;</span>, line <span class="synConstant">1</span>, <span class="synStatement">in</span> &lt;module&gt; <span class="synType">TypeError</span>: a <span class="synIdentifier">bytes</span>-like <span class="synIdentifier">object</span> <span class="synStatement">is</span> required, <span class="synStatement">not</span> <span class="synConstant">'str'</span> </pre><p> エラーを回避するためには、型を合わせる必要があります。strのメソッドならstrを、bytesのメソッドならbytesを渡してあげてください。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synConstant">&quot;a,b,c&quot;</span>.split(<span class="synConstant">&quot;,&quot;</span>) [<span class="synConstant">'a'</span>, <span class="synConstant">'b'</span>, <span class="synConstant">'c'</span>] &gt;&gt;&gt; b<span class="synConstant">&quot;a,b,c&quot;</span>.split(b<span class="synConstant">&quot;,&quot;</span>) [b<span class="synConstant">'a'</span>, b<span class="synConstant">'b'</span>, b<span class="synConstant">'c'</span>] </pre><p> 駄目押しで、joinでも見てみます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synConstant">&quot;,&quot;</span>.join([b<span class="synConstant">'a'</span>, b<span class="synConstant">'b'</span>, b<span class="synConstant">'c'</span>]) Traceback (most recent call last): File <span class="synConstant">&quot;&lt;stdin&gt;&quot;</span>, line <span class="synConstant">1</span>, <span class="synStatement">in</span> &lt;module&gt; <span class="synType">TypeError</span>: sequence item <span class="synConstant">0</span>: expected <span class="synIdentifier">str</span> instance, <span class="synIdentifier">bytes</span> found &gt;&gt;&gt; b<span class="synConstant">&quot;,&quot;</span>.join([<span class="synConstant">'a'</span>, <span class="synConstant">'b'</span>, <span class="synConstant">'c'</span>]) Traceback (most recent call last): File <span class="synConstant">&quot;&lt;stdin&gt;&quot;</span>, line <span class="synConstant">1</span>, <span class="synStatement">in</span> &lt;module&gt; <span class="synType">TypeError</span>: sequence item <span class="synConstant">0</span>: expected a <span class="synIdentifier">bytes</span>-like <span class="synIdentifier">object</span>, <span class="synIdentifier">str</span> found </pre><p> やっぱりできません。</p><p> 逆にこの点に注意しておけば、bytesのまま扱うことも可能ですが、実際問題としてはなかなか厄介です。素直に入力された時点でstrにデコードしてしまうことをおすすめします。str→bytes変換はencode, 逆はdecodeメソッドです。</p> <div class="footnote"> <p class="footnote"><a href="#fn-56a230bc" name="f-56a230bc" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">すべてPython3準拠で説明します。さすがにPython2はもういいでしょ</span></p> </div> hayataka2049 DataFrameをprintしたときヘッダの日本語の列名がずれないようにする hatenablog://entry/26006613547505039 2020-04-10T06:05:32+09:00 2020-04-10T06:11:29+09:00 日本語の列名のDataFrameを扱うことは、日本人のpandasユーザにとってはありがちな展開だと思うのですが、問題はprintするとヘッダがずれてしまうことです。 >>> import pandas as pd >>> pd.DataFrame({"あああ":[1, 2], "いいい":[3, 4], "ううう":[5, 6], "えええ":[7, 8]}) あああ いいい ううう えええ 0 1 3 5 7 1 2 4 6 8 ASCIIの等幅文字を前提にしているからこんなものなのだろうと諦めていましたが、実はよしなに表示するオプションがありました。teratailでこの件の質問を見かけ… <p> 日本語の列名のDataFrameを扱うことは、日本人のpandasユーザにとってはありがちな展開だと思うのですが、問題はprintするとヘッダがずれてしまうことです。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd &gt;&gt;&gt; pd.DataFrame({<span class="synConstant">&quot;あああ&quot;</span>:[<span class="synConstant">1</span>, <span class="synConstant">2</span>], <span class="synConstant">&quot;いいい&quot;</span>:[<span class="synConstant">3</span>, <span class="synConstant">4</span>], <span class="synConstant">&quot;ううう&quot;</span>:[<span class="synConstant">5</span>, <span class="synConstant">6</span>], <span class="synConstant">&quot;えええ&quot;</span>:[<span class="synConstant">7</span>, <span class="synConstant">8</span>]}) あああ いいい ううう えええ <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">3</span> <span class="synConstant">5</span> <span class="synConstant">7</span> <span class="synConstant">1</span> <span class="synConstant">2</span> <span class="synConstant">4</span> <span class="synConstant">6</span> <span class="synConstant">8</span> </pre><p> ASCIIの等幅文字を前提にしているからこんなものなのだろうと諦めていましたが、実はよしなに表示するオプションがありました。<a href="https://teratail.com/questions/252215">teratail&#x3067;&#x3053;&#x306E;&#x4EF6;&#x306E;&#x8CEA;&#x554F;&#x3092;&#x898B;&#x304B;&#x3051;&#x3066;&#x3001;&#x3075;&#x3068;&#x4E45;&#x3005;&#x306B;&#x30EA;&#x30D5;&#x30A1;&#x30EC;&#x30F3;&#x30B9;&#x3092;&#x898B;&#x305F;&#x3089;&#x898B;&#x3064;&#x3051;&#x307E;&#x3057;&#x305F;&#x3002;</a>こういうことがあるからQAサイトは楽しいのです。</p> <blockquote> <p>Some East Asian countries use Unicode characters whose width corresponds to two Latin characters. If a DataFrame or Series contains these characters, the default output mode may not align them properly.<br /> (中略)<br /> Enabling display.unicode.east_asian_width allows pandas to check each character’s “East Asian Width” property. These characters can be aligned properly by setting this option to True. However, this will result in longer render times than the standard len function.<br /> <a href="https://pandas.pydata.org/pandas-docs/stable/user_guide/options.html#unicode-formatting">Options and settings &mdash; pandas 1.0.3 documentation</a></p> </blockquote> <p> うまく表示してもらうためには、</p> <pre class="code lang-python" data-lang="python" data-unlink>pd.set_option(<span class="synConstant">'display.unicode.east_asian_width'</span>, <span class="synIdentifier">True</span>) </pre><p> を打ちます。東アジアで使われている文字幅で表示してあげますよというオプション名。要するに我々のためにあるようなものなので、ありがたく使わせてもらいましょう。</p><p> このようにうまくいきます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; pd.set_option(<span class="synConstant">'display.unicode.east_asian_width'</span>, <span class="synIdentifier">True</span>) &gt;&gt;&gt; pd.DataFrame({<span class="synConstant">&quot;あああ&quot;</span>:[<span class="synConstant">1</span>, <span class="synConstant">2</span>], <span class="synConstant">&quot;いいい&quot;</span>:[<span class="synConstant">3</span>, <span class="synConstant">4</span>], <span class="synConstant">&quot;ううう&quot;</span>:[<span class="synConstant">5</span>, <span class="synConstant">6</span>], <span class="synConstant">&quot;えええ&quot;</span>:[<span class="synConstant">7</span>, <span class="synConstant">8</span>]}) あああ いいい ううう えええ <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">3</span> <span class="synConstant">5</span> <span class="synConstant">7</span> <span class="synConstant">1</span> <span class="synConstant">2</span> <span class="synConstant">4</span> <span class="synConstant">6</span> <span class="synConstant">8</span> </pre><p>(といいつつ、ブラウザ環境次第ですがこのブログ記事上ではうまく見えていない可能性が高いです。全角文字をきっちり半角文字二文字分の幅で表示してくれる環境で確認してください。)</p><p> なお、これはPythonを起動する度に毎回設定する必要があります。永続化させるために、Pythonのスタートアップ時にpandasをimportして走らせる方法が公式では推奨されています。</p><p><a href="https://pandas.pydata.org/pandas-docs/stable/user_guide/options.html#setting-startup-options-in-python-ipython-environment">Options and settings &mdash; pandas 1.0.3 documentation</a></p><p> 素のCPythonならPYTHONSTARTUP、IPythonなら$IPYTHONDIR/profile_default/startupなどに仕掛けておけとのことですが、そのためだけにpandasをimportさせるのも微妙な感じですね。そこだけは残念なところです。</p><p><a href="https://docs.python.org/ja/3/using/cmdline.html?highlight=pythonstartup#envvar-PYTHONSTARTUP">1. &#x30B3;&#x30DE;&#x30F3;&#x30C9;&#x30E9;&#x30A4;&#x30F3;&#x3068;&#x74B0;&#x5883; &mdash; Python 3.8.2 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a><br /> <a href="https://maku77.github.io/python/dev/python-startup.html">PYTHONSTARTUP &#x3067; Python &#x306E;&#x30A4;&#x30F3;&#x30BF;&#x30E9;&#x30AF;&#x30C6;&#x30A3;&#x30D6;&#x30B7;&#x30A7;&#x30EB;&#x3092;&#x4FBF;&#x5229;&#x306B;&#x3059;&#x308B; | &#x307E;&#x304F;&#x307E;&#x304F;Python&#x30CE;&#x30FC;&#x30C8;</a><br /> <a href="https://qiita.com/Kodaira_/items/1dbb33f77038782a0d96">IPython&#x8D77;&#x52D5;&#x6642;&#x306B;&#x3044;&#x3064;&#x3082;&#x4F7F;&#x3046;&#x30E2;&#x30B8;&#x30E5;&#x30FC;&#x30EB;&#x3092;import&#x3059;&#x308B;&#x8A2D;&#x5B9A; - Qiita</a></p> hayataka2049 numbaとnumpyで速いループ処理を書くためのガイド(スレッド並列化のおまけつき)(実はポエム) hatenablog://entry/26006613547037812 2020-04-09T06:26:24+09:00 2020-04-26T20:49:22+09:00 はじめに この記事は「Pythonおっせーよ」と思っている、そこのあなたのためのものです。 PythonはLLなので遅いです。その分、楽に書けるし、動的型付けでダイナミックなことができて「楽しぃぃいい」のですが、それでも遅くて困るときがあります。特に数値計算的なことをやらせようとすると*1。 え、numpy使えばいける? numpyは、numpyに組み込まれている関数では処理できなくて、ベクトル演算も効かないようなタイプの処理には基本的に無力です。いけません。 どうせC/C++で書かれてコンパイルされたバイナリのwrapperと割り切って使うから良い? ライブラリが見つかればそれで良いでしょう… <div class="section"> <h3 id="はじめに">はじめに</h3> <p> この記事は「Pythonおっせーよ」と思っている、そこのあなたのためのものです。</p><p> PythonはLLなので遅いです。その分、楽に書けるし、動的型付けでダイナミックなことができて「楽しぃぃいい」のですが、それでも遅くて困るときがあります。特に数値計算的なことをやらせようとすると<a href="#f-66fb5e68" name="fn-66fb5e68" title="ちなみにこの記事では実践的な数値計算はしません。それでも多少はなにかの手助けになるでしょう。">*1</a>。</p><p> え、numpy使えばいける? numpyは、numpyに組み込まれている関数では処理できなくて、ベクトル演算も効かないようなタイプの処理には基本的に無力です。いけません。</p><p> どうせC/C++で書かれてコンパイルされたバイナリのwrapperと割り切って使うから良い? ライブラリが見つかればそれで良いでしょう。問題はライブラリに頼るまでもない、ちょっとした処理の実装です。コンパイル言語で書いたりすると(Cythonも含みます)、面倒くさくて割りに合わない気がします。なんでこんなことやってるんだという気持ちになれます。</p><p> numbaだと、並のコンパイル言語よりは気楽に書けて、単純な数値計算とかなら並のコンパイル言語くらいの速度が実際に出ます。素晴らしいですね。</p><p> ということで、numpy+numbaで書くときに気をつけることを色々と書いておこうと思います。</p><p> はい。気をつけることが色々あるんです。気をつければ速いんですけどねー。けっこう面倒くさいんですよねー。それでもCythonより楽というのがnumbaの位置づけなので、知っておくとたまに得するかもしれません。どうしても高速に走らないといけないループだけnumbaで書き下すといったプログラミングが可能になります。それくらいのものです。</p><p> 目次</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#コンセプト">コンセプト</a></li> <li><a href="#jitを効かせる">jitを効かせる</a></li> <li><a href="#numpy配列で入出力のやり取りをする">numpy配列で入出力のやり取りをする</a></li> <li><a href="#jitコンパイルできるコードを書く">jitコンパイルできるコードを書く</a></li> <li><a href="#ちょっとだけ実践編">ちょっとだけ実践編</a></li> <li><a href="#GILを切ってマルチスレッドするおまけ">GILを切ってマルチスレッドする(おまけ)</a></li> <li><a href="#まとめ">まとめ</a></li> </ul> </div> <div class="section"> <h3 id="コンセプト">コンセプト</h3> <p> 前提として、numpyがどんな仕組みで高速な処理を達成しているのかを知らない人は、この記事を読んでも活用できないと思います。一応、この章で最低限のことは説明しますが、これを読んでわかる人には要らないような説明です。わからない人はCの勉強から。</p><p> 先にこの章の結論を書いてしまいます。numpyはぶっちゃけていえば、C配列のwrapperみたいなものです<a href="#f-edb7a899" name="fn-edb7a899" title="動的メモリ確保してるから配列じゃないとか面倒くさいこと言わないでください、配列です。">*2</a>。</p><p> A = np.zeros(shape=(3, 4), dtype=np.float64)とかすると内部でmalloc(sizeof(double) * 3 * 4)されてメモリ領域が確保され、そしてこれへアクセスするためのPython Objectも作られます。プロキシ・オブジェクトです。PythonでA[i, j] = 1.0すると、内部で確保した配列に対して[i][j] = 1.0が走るわけです。</p><p> ということで、numpy配列は原理的には、その気になればCの配列っぽく扱えます。scikit-learnなどの多くの機械学習ライブラリはnumpy配列で表現されたデータを受け取るように実装されていますが、「すべてがオブジェクト」的な世界観とは無縁のバイナリの世界で処理をするからこそ高速に処理を達成できる訳です。</p><p> 逆に、Python側からnumpy配列の中にある数値を取り出したり、numpy配列に数値を書き込んだりする処理は、びっくりするほど遅くなります。それをやる度に、いちいち対応するPython Objectを生成せざるを得ないのです。なにしろPythonの世界は「すべてがオブジェクト」だから、そうならざるを得ない。</p><p> そういう事情から、Pythonでnumpyにバリバリ添字アクセスする処理を書いても、だいたい悲惨な結果になります。</p><p> それでもnumpyは、Pythonで高速な数値計算を実現する方法を、不完全ではありますが提供してくれています。それがベクトル演算で、複数要素にまとめて同じような処理をするのであればPythonの世界とのやり取りはそんなにしなくて済むので、速いという発想です。</p><p> 多くの場合ベクトル演算は有用です。が、本質的には予め用意されている処理しか書けないし、どんな処理でもベクトル演算で書ける訳ではないので、限界があります。そういう限界に突き当たった人は添字アクセスの処理を書いて、あまりの遅さに絶望し、なんとかならないかと思ってnumbaにたどり着く訳です<a href="#f-0ac3a5e2" name="fn-0ac3a5e2" title="あるいはCythonかもしれないし、Pythonを見限ってJuliaに行ってしまうかもしれませんが">*3</a>。</p><p> さらに言うと、numpyのベクトル演算にはもう一つ問題っぽいものがあります。素直に書くとベクトル演算した結果が新たな配列として返ることが多いので、けっこう豪快にメモリを使います。ある程度は累積代入文などでカバーできますが、駄目なときもあります。これもなんとかしたい点です。メモリを余計に食うから……ではなくて(GBとかの死ぬほどでかい配列でない限り、メモリ消費自体は普通は問題にはなりません)、メモリを新規に確保するのはそもそも遅い処理だし、キャッシュ効率も悪くなるので、できればインプレースで済むことはインプレースで済ませたいからです。</p><p> ということで、numpy配列の中にあるC配列を直接添字アクセスしてループできると嬉しい、という需要がある訳です。というかそのためのnumpy配列です。もし本格的にやるならC/C++かCythonの出番ですが、もう少し気楽にやりたいという需要もある訳です。それがnumbaの存在する理由です。</p><p> そしてこの記事は、実際にnumbaを使う考え方というか実践的なコツみたいなものを、思いつくままに書いていくというものです。</p> </div> <div class="section"> <h3 id="jitを効かせる">jitを効かせる</h3> <p> numbaはJust-in-time compilationという技術でコードの高速化を行います。背後ではLLVMが動きます。私にとってはほとんど謎の技術ですが、ここではnumbaのバックエンドと割り切って気にしないことにします。コンパイラの賢さを信じる。</p><p> 重要なのはjitです。というかjitデコレータです。numbaの中核です。極論すればfrom numba import jitだけでnumbaは使えます。</p><p> ただし、日本語Web圏でjitデコレータについて言われていることには注意が必要です。</p><p> 検索すると普通に出てくるnumbaの解説記事では「関数に@jitつけるだけでjitコンパイルされて速くなる!」とか書いてあることがあります。それしか内容がなかったら、ブラウザバックしましょう。それかタブを閉じましょう。役に立たない記事です。</p><p> numbaは「オレの書いたイケイケなPythonコードをチョースゴイ技術でJITコンパイルしてくれる」技術ではありません。「色々気を配ってCっぽいPythonを書けばCっぽい速さで動いてくれる」というものです。そこを勘違いした人たちはみんなインターネットの夜空に輝く素晴らしい星々になりました。<br /> (この流れでついでに書いておくと、Cythonは「PythonっぽいC」「Cではないにしても少なくともPythonではないなにか」です。numbaの場合はギリギリ構文まではPythonしています。Pythonパーサで構文解析されてPythonの抽象構文木をJITコンパイルするのです。あれ、それはそれでPythonではないっ?)</p><p> なんで「@jitだけで」が駄目なのかについてもう少し書いておきます。単純な話、そもそもjitコンパイルできていないnumbaの記事が死ぬほどたくさんあります。numba非対応の機能を使うとそうなるのです。その場合は「Python互換モード」みたいなこれまた正体のよくわからないもので動かしてくれますが、こちらは劇的に遅いのが普通です。</p><p> ということで、最低限以下の使い方をしましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">jit</span>(nopython=<span class="synIdentifier">True</span>) <span class="synStatement">def</span> <span class="synIdentifier">f</span>(...): ... </pre><p> nopython=Trueというオプションが追加されました。こうすると謎の「Python互換モード」とはおさらばでき、コンパイルに失敗したらエラーが出ます。うまく動かないときは動かさない、これが第一歩です。<br /> <br /> <br /> </p> </div> <div class="section"> <h3 id="numpy配列で入出力のやり取りをする">numpy配列で入出力のやり取りをする</h3> <p> JITが速いのは実は静的型付けでコンパイルして動かすからです。</p><p> 私個人の心情としては静的型付けには窮屈とか面倒くさいといった印象を抱いてしまいがちですが<a href="#f-1abad7bb" name="fn-1abad7bb" title="あ、でもCとか古めの静的型付けは好きです。苦手な気がするのはオブジェクト指向以降のクラスと絡みついたモダンな静的型付けです。Javaのインターフェースとか必要なのはわかるけど好きになれる気がしません。新しめのpandasのリファレンスは丁寧にシグネチャにPythonの型ヒントを書いてくれていますが、あれとか正気の沙汰とは思えません。">*4</a>、パフォーマンス上の理由なので好き嫌いは置いておいて静的型付けを使いましょう。</p><p> numbaの良いところは型推論してくれることです。a = 42と書いてあったら、aはたぶんint64です、とか。なので型の指定は書かなくて済みます。cdefまみれになるCythonとの違いです。</p><p> ただし、引数と返り値の型は指定した方が何かと高速化の恩恵を受けられます。というか、やらないとたぶんコンパイルエラーでまともに走らないでしょう。</p><p><a href="http://numba.pydata.org/numba-doc/latest/reference/jit-compilation.html#jit-functions">2.2. Just-in-Time compilation &mdash; Numba 0.48.0-py3.6-macosx-10.7-x86_64.egg documentation</a></p><p> 指定の仕方も実は3通りもあったりします。1つ目はtupleで引数の型のみ指定する方法、2つ目はnumbaの型オブジェクトを作って渡す方法、3つ目は文字列で書く方法です。文字列で書きたい人が多いと思うので、それを紹介します。</p><p> シグネチャはデコレータの第一引数であり、「使え」という強い意志を感じます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">jit</span>(<span class="synConstant">&quot;i8(f8, f8)&quot;</span>, nopython=<span class="synIdentifier">True</span>) <span class="synStatement">def</span> <span class="synIdentifier">f</span>(a, b): ... </pre><p> "i8(f8, f8)"というのが型の指定になります。カッコの前が関数の返り値、後ろのカッコの中身が引数の型であり、i8は8バイト整数(つまりint64)、f8は8バイト浮動小数点数(float64)です。浮動小数点数を二つ受け取って整数を返す関数です。</p><p> なお、Pythonオブジェクトをこういうnumbaの関数に渡した場合はそれなりに相互変換はしてくれますが、普通に「numpy配列の要素を取り出すとき」の問題が生じるので損な選択になります。</p><p> ということで、numpy配列でやり取りしましょう。配列はスライス表記で表します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">jit</span>(<span class="synConstant">&quot;i8[:](f8[:, :], f8[:, :, :])&quot;</span>, nopython=<span class="synIdentifier">True</span>) <span class="synStatement">def</span> <span class="synIdentifier">f</span>(a, b): ... </pre><p> 返り値は整数の一次元配列、第一引数は浮動小数点数の二次元配列、第二引数は浮動小数点数の三次元配列みたいな関数です。</p><p> これが、最初に書いた「numpy配列の中にあるC配列を直接いじりたい」を実現するための魔法です。こうしておけばnumbaが適切にやり取りしてやってくれます。なかなか夢が広がりますね。</p><p> 使える型とかの詳細はリファレンスを見てください。</p><p><a href="http://numba.pydata.org/numba-doc/latest/reference/types.html#numba-types">2.1. Types and signatures &mdash; Numba 0.48.0-py3.6-macosx-10.7-x86_64.egg documentation</a></p><p> また、numba.typeofを使うことで任意のオブジェクトに適合するシグネチャを調べることもできます。簡略表記で返してくれないというか、numbaの型オブジェクトを返してくれる(なので型オブジェクトを作って渡す方法向きではあります)のが微妙ですが。このオブジェクトどう書くんだ? というとき使いましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">import</span> numba &gt;&gt;&gt; numba.typeof(<span class="synConstant">1</span>) int64 &gt;&gt;&gt; numba.typeof(<span class="synConstant">1.0</span>) float64 &gt;&gt;&gt; <span class="synPreProc">@</span><span class="synIdentifier">numba.jit</span>(<span class="synConstant">&quot;float64(int64)&quot;</span>, nopython=<span class="synIdentifier">True</span>) ... <span class="synStatement">def</span> <span class="synIdentifier">f</span>(a): ... <span class="synStatement">return</span> a + <span class="synConstant">1.5</span> ... &gt;&gt;&gt; f(<span class="synConstant">10</span>) <span class="synConstant">11.5</span> </pre><p><a href="http://numba.pydata.org/numba-doc/latest/reference/types.html#numba.typeof">2.1. Types and signatures &mdash; Numba 0.48.0-py3.6-macosx-10.7-x86_64.egg documentation</a></p><br /> <p> なお、当たり前ですが、合致しない型とか形状の配列を渡すと、実行時にエラーになります。JITコンパイルする本丸の関数とは別に、pythonレイヤで形状チェックとかastypeとかしてから本丸を呼び出すようなwrapper関数を書いておくと実用的です。どうせ自分しか使わないしコードのこの箇所では入ってくる型は確定しているしということで割り切るなら、それはそれで実用的です。</p><p> あと、numpy配列を渡せば普通に引数に副作用を及ぼせるので、返り値は必須ではありません。インプレース処理にしてもいいし、返り値用の配列を引数として渡してもいいのです。返り値が要らないときは返り値の型のところにはvoidと書いてください。</p> </div> <div class="section"> <h3 id="jitコンパイルできるコードを書く">jitコンパイルできるコードを書く</h3> <p> <br />  ここと、</p><p><a href="http://numba.pydata.org/numba-doc/latest/reference/pysupported.html">2.6. Supported Python features &mdash; Numba 0.48.0-py3.6-macosx-10.7-x86_64.egg documentation</a></p><p> ここを、</p><p><a href="http://numba.pydata.org/numba-doc/latest/reference/numpysupported.html">2.7. Supported NumPy features &mdash; Numba 0.48.0-py3.6-macosx-10.7-x86_64.egg documentation</a></p><br /> <p> 見て、書いてある機能だけで書いてください。終了。</p><p> では不親切なので、もう少し書きます。</p><p> たとえばnumbaではループを書くのにPythonのrangeが普通に使えます。いえ、使えるようにみえます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># @jit(nopython=True)しても動く</span> <span class="synStatement">def</span> <span class="synIdentifier">f</span>(): a = <span class="synConstant">0</span> <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">10</span>): a += i <span class="synIdentifier">print</span>(i) </pre><p> Pythonだとrangeオブジェクトを生成して、そこから生成したイテレータから値を一つずつ取り出して……という感じでこのコードは動きます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synIdentifier">range</span>(<span class="synConstant">10</span>) <span class="synIdentifier">range</span>(<span class="synConstant">0</span>, <span class="synConstant">10</span>) </pre><p> numbaはまったく違う仕組みで実行します。最終的には機械語のループに変換されます。カウンタ足してってジャンプ命令とかする奴です。</p><p> なので当然ながら制約があります。rangeオブジェクトを引数として受け取ったりはできません。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># @jit(nopython=True)できない</span> <span class="synStatement">def</span> <span class="synIdentifier">f</span>(r): <span class="synComment"># f(range(10))とか呼び出したいができない</span> a = <span class="synConstant">0</span> <span class="synStatement">for</span> i <span class="synStatement">in</span> r: a += i <span class="synIdentifier">print</span>(a) </pre><p> といった面倒くさい決まりごとが色々あるのがnumbaです。細かく把握して慣れ親しんでいけば、そのうち「こんなのnumbaで書けるんだ!」という驚きが発生するかもしれませんが、普通はだるいのでコンサバティブなコードを書くことになります。</p><p> それでも一昔前のnumbaに比べれば最近のnumbaはかなり立派になっていて、たとえばリスト内包表記なんか昔はできなかったのにいつの間にかできるようになっていて私なんかはかなりびびったのですが、でもPythonオブジェクトのリストを作るんだから遅えんだろうな……という諦観も生じてきて複雑な気持ちになったりして、まあ要するに「Python的な」コード、向いてませんよということです。どうせ相手にするのはnumpy配列、CかFORTRANを書くつもりで書くくらいの気持ちで扱いましょう。そうすると高級なコンパイル言語……? という気がしてきて幸せになれます。</p><p> ということで、手っ取り早いのはrangeループによるインデックスアクセスとnumpyの機能とを組み合わせて使うことなので、numpyへのサポートを見てみます。</p><p> 結論を先に言ってしまうと、我々が想像することはだいたいできます。基本的なindexingやslicingはできますし、代表的な関数もだいたい使えます。</p><p> numpyのリッチな機能が使えても、それはつまりnumpyとして走るだけなので、numba的に速くはないのが微妙なのですが。<br /> (たとえば単一のnumpy関数を呼び出して時間がかかっている処理をそのまま関数に入れて@jitしても、高速化されません。つーかそれで速くなったら大慌てだよね。たぶんみんな使うし、それができるならnumpy開発者が最初から組み込むよね)</p><p> コツとしては「Pythonコードで書かれた複雑な処理」をそのまま移すんじゃなくて、ボトルネックになっている書かざるを得ないループをnumbaに移すようにしましょう。短い方がコンパイルを通すのが楽です。あとは走らせるとコンパイルエラーがたくさん出ると思うので、だいたい読んでググればなんとかなります。それと、最初から書いたものを分析やシミュレーションなどの処理の中に組み込で使おうとすると、書き換える度に五分前処理してエラーになるとか辛い事態が発生するので、ダミーデータで走らせるテストも書いておくと良いでしょう(これはnumbaに限らず大切なことなのですが)。</p> </div> <div class="section"> <h3 id="ちょっとだけ実践編">ちょっとだけ実践編</h3> <p> お待たせしました、実践編です。実際にnumba+numpyでどんなことができるのか、簡単に見ていきます。</p><p> この記事ではfloat64のcumsumを実装します。二次元配列を受け取り、行ごとにcumsumするものを目指します<a href="#f-a0111520" name="fn-a0111520" title="numpyのaxis=1相当">*5</a>。</p><p> え、cumsumはnumpyにありますよね。numba要らないって? ブラウザバックしないでください</p><p> 私が定義するcumsum(mycumsumと名付けます)は以下のような計算です。</p><p> 任意の要素xに対して</p> <ul> <li>0.0 <= x</li> </ul><p> 普通にcumsm</p> <ul> <li>-1.0 <= x < 0</li> </ul><p> リセットしてxを0にする</p> <ul> <li>-2.0 <= x < -1.0</li> </ul><p> 前の値を0.5倍したものをxにする(最初なら0)</p> <ul> <li>x < -2.0</li> </ul><p> 前の値に42.0を足してxにする(最初なら42.0)</p><p> はい、cumsumじゃないですよね。名前が思いつきませんでしたが、cumsumに余計な処理が増えたなにかです。</p><p> とりあえずpythonで書いてみましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> random <span class="synStatement">def</span> <span class="synIdentifier">mycumsum</span>(lst): t = <span class="synConstant">0.0</span> result = [] <span class="synStatement">for</span> x <span class="synStatement">in</span> lst: <span class="synStatement">if</span> <span class="synConstant">0.0</span> &lt;= x: t += x <span class="synStatement">elif</span> -<span class="synConstant">1.0</span> &lt;= x &lt; <span class="synConstant">0.0</span>: t = <span class="synConstant">0.0</span> <span class="synStatement">elif</span> -<span class="synConstant">2.0</span> &lt;= x &lt; <span class="synConstant">1.0</span>: t *= <span class="synConstant">0.5</span> <span class="synStatement">else</span>: t += <span class="synConstant">42.0</span> result.append(t) <span class="synStatement">return</span> result random.seed(<span class="synConstant">42</span>) data = [random.uniform(-<span class="synConstant">10.0</span>, <span class="synConstant">10.0</span>) <span class="synComment"># 見づらいので[-10, 10]にする</span> <span class="synStatement">for</span> _ <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">10</span>)] <span class="synIdentifier">print</span>(data) <span class="synIdentifier">print</span>(mycumsum(data)) <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant">[2.7885359691576745, -9.49978489554666, -4.499413632617615, -5.5357852370235445, 4.729424283280249, 3.533989748458225, 7.843591354096908, -8.261223347411677, -1.561563606294591, -9.404055611238594]</span> <span class="synConstant">[2.7885359691576745, 44.78853596915768, 86.78853596915768, 128.7885359691577, 133.51796025243794, 137.05195000089617, 144.89554135499307, 186.89554135499307, 93.44777067749654, 135.44777067749652]</span> <span class="synConstant">&quot;&quot;&quot;</span> </pre><p><br />  書けたけど……と思いますよね。これは遅いPythonの処理です。上述の理由によりnumpy配列の要素にPythonレイヤからアクセスすると遅いので、numpy配列を渡してもそんなに変わりません(かえって遅い可能性もあります)。そしてこの関数をnumpyのベクトル演算で効率的に実装することはできません(たぶん)。</p><p> まあでも、一応速度を測ってみるか。意外と速いかもしれない。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> time <span class="synPreProc">import</span> random <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synStatement">def</span> <span class="synIdentifier">mycumsum_row</span>(lst): t = <span class="synConstant">0.0</span> result = [] <span class="synStatement">for</span> x <span class="synStatement">in</span> lst: <span class="synStatement">if</span> <span class="synConstant">0.0</span> &lt;= x: t += x <span class="synStatement">elif</span> -<span class="synConstant">1.0</span> &lt;= x &lt; <span class="synConstant">0.0</span>: t = <span class="synConstant">0.0</span> <span class="synStatement">elif</span> -<span class="synConstant">2.0</span> &lt;= x &lt; <span class="synConstant">1.0</span>: t *= <span class="synConstant">0.5</span> <span class="synStatement">else</span>: t += <span class="synConstant">42.0</span> result.append(t) <span class="synStatement">return</span> result <span class="synStatement">def</span> <span class="synIdentifier">mycumsum</span>(A): <span class="synStatement">return</span> [mycumsum_row(row) <span class="synStatement">for</span> row <span class="synStatement">in</span> A] random.seed(<span class="synConstant">42</span>) <span class="synComment"># (1000, 1000)配列とします</span> data = [[random.uniform(-<span class="synConstant">10.0</span>, <span class="synConstant">10.0</span>) <span class="synStatement">for</span> _ <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">1000</span>)] <span class="synStatement">for</span> _ <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">1000</span>)] <span class="synComment"># 実行時間計測</span> times = [] <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">10</span>): t1 = time.time() res = mycumsum(data) t2 = time.time() times.append(t2 - t1) <span class="synIdentifier">print</span>(np.mean(times)) <span class="synComment"># =&gt; 0.11440501213073731</span> <span class="synComment"># numpyのcumsumもやります</span> data = np.array(data) <span class="synComment"># numpy配列にしないと遅いんで・・・</span> <span class="synComment"># 実行時間計測</span> times = [] <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">10</span>): t1 = time.time() res = np.cumsum(data, axis=<span class="synConstant">1</span>) t2 = time.time() times.append(t2 - t1) <span class="synIdentifier">print</span>(np.mean(times)) <span class="synComment"># =&gt; 0.003912162780761719</span> <span class="synComment"># 全体の結果を改めて</span> <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant">0.11293444633483887</span> <span class="synConstant">0.003912162780761719</span> <span class="synConstant">&quot;&quot;&quot;</span> </pre><p> 知 っ て た 。</p><p> まあ、処理の複雑性を考えると意外と健闘している方かもしれません。約29倍で済んでいます。普通にpythonでループさせるとこれくらいになるという例です。</p><p> じゃあ、次はnumbaで書きましょう。実装の仕方はこんな感じです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">jit</span>(<span class="synConstant">&quot;f8[:, :](f8[:, :])&quot;</span>, nopython=<span class="synIdentifier">True</span>) <span class="synStatement">def</span> <span class="synIdentifier">mycumsum_numba</span>(A): result = np.empty(A.shape, dtype=np.float64) <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(A.shape[<span class="synConstant">0</span>]): <span class="synComment"># 行のループ</span> t = <span class="synConstant">0.0</span> <span class="synStatement">for</span> j <span class="synStatement">in</span> <span class="synIdentifier">range</span>(A.shape[<span class="synConstant">1</span>]): <span class="synComment"># 列のループ</span> x = A[i, j] <span class="synStatement">if</span> <span class="synConstant">0.0</span> &lt;= x: t += x <span class="synStatement">elif</span> -<span class="synConstant">1.0</span> &lt;= x &lt; <span class="synConstant">0.0</span>: t = <span class="synConstant">0.0</span> <span class="synStatement">elif</span> -<span class="synConstant">2.0</span> &lt;= x &lt; <span class="synConstant">1.0</span>: t *= <span class="synConstant">0.5</span> <span class="synStatement">else</span>: t += <span class="synConstant">42.0</span> result[i, j] = t <span class="synStatement">return</span> result </pre><p> 特に解説は要らないでしょう。最適化とかはまったく考えていない愚直な実装です。</p><p> また測る。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> time <span class="synPreProc">import</span> random <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">from</span> numba <span class="synPreProc">import</span> jit <span class="synStatement">def</span> <span class="synIdentifier">mycumsum_row</span>(lst): t = <span class="synConstant">0.0</span> result = [] <span class="synStatement">for</span> x <span class="synStatement">in</span> lst: <span class="synStatement">if</span> <span class="synConstant">0.0</span> &lt;= x: t += x <span class="synStatement">elif</span> -<span class="synConstant">1.0</span> &lt;= x &lt; <span class="synConstant">0.0</span>: t = <span class="synConstant">0.0</span> <span class="synStatement">elif</span> -<span class="synConstant">2.0</span> &lt;= x &lt; <span class="synConstant">1.0</span>: t *= <span class="synConstant">0.5</span> <span class="synStatement">else</span>: t += <span class="synConstant">42.0</span> result.append(t) <span class="synStatement">return</span> result <span class="synStatement">def</span> <span class="synIdentifier">mycumsum</span>(A): <span class="synStatement">return</span> [mycumsum_row(row) <span class="synStatement">for</span> row <span class="synStatement">in</span> A] <span class="synPreProc">@</span><span class="synIdentifier">jit</span>(<span class="synConstant">&quot;f8[:, :](f8[:, :])&quot;</span>, nopython=<span class="synIdentifier">True</span>) <span class="synStatement">def</span> <span class="synIdentifier">mycumsum_numba</span>(A): result = np.empty(A.shape, dtype=np.float64) <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(A.shape[<span class="synConstant">0</span>]): <span class="synComment"># 行のループ</span> t = <span class="synConstant">0.0</span> <span class="synStatement">for</span> j <span class="synStatement">in</span> <span class="synIdentifier">range</span>(A.shape[<span class="synConstant">1</span>]): <span class="synComment"># 列のループ</span> x = A[i, j] <span class="synStatement">if</span> <span class="synConstant">0.0</span> &lt;= x: t += x <span class="synStatement">elif</span> -<span class="synConstant">1.0</span> &lt;= x &lt; <span class="synConstant">0.0</span>: t = <span class="synConstant">0.0</span> <span class="synStatement">elif</span> -<span class="synConstant">2.0</span> &lt;= x &lt; <span class="synConstant">1.0</span>: t *= <span class="synConstant">0.5</span> <span class="synStatement">else</span>: t += <span class="synConstant">42.0</span> result[i, j] = t <span class="synStatement">return</span> result random.seed(<span class="synConstant">42</span>) <span class="synComment"># (1000, 1000)配列とします</span> data = [[random.uniform(-<span class="synConstant">10.0</span>, <span class="synConstant">10.0</span>) <span class="synStatement">for</span> _ <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">1000</span>)] <span class="synStatement">for</span> _ <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">1000</span>)] <span class="synComment"># 実行時間計測</span> times = [] <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">10</span>): t1 = time.time() res_mycumsum = mycumsum(data) t2 = time.time() times.append(t2 - t1) <span class="synIdentifier">print</span>(np.mean(times)) <span class="synComment"># =&gt; 0.11440501213073731</span> <span class="synComment"># numpyのcumsum</span> data = np.array(data) <span class="synComment"># 実行時間計測</span> times = [] <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">10</span>): t1 = time.time() res = np.cumsum(data, axis=<span class="synConstant">1</span>) t2 = time.time() times.append(t2 - t1) <span class="synIdentifier">print</span>(np.mean(times)) <span class="synComment"># =&gt; 0.003912162780761719</span> <span class="synComment"># 自作numba版</span> times = [] <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">10</span>): t1 = time.time() res_mycumsum_numba = mycumsum_numba(data) t2 = time.time() times.append(t2 - t1) <span class="synStatement">assert</span> (res_mycumsum_numba == np.array(res_mycumsum)).all() <span class="synComment"># 同じ結果か確認</span> <span class="synIdentifier">print</span>(np.mean(times)) <span class="synComment"># =&gt; 0.005931735038757324</span> <span class="synComment"># 全体の結果を改めて</span> <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant">0.11293444633483887</span> <span class="synConstant">0.003912162780761719</span> <span class="synConstant">0.005931735038757324</span> <span class="synConstant">&quot;&quot;&quot;</span> </pre><p> numpyのcumsumの1.5倍で実行できます。処理の複雑性を考えると大健闘と言えます。</p><p> という感じで使えるのがnumbaで、これはなかなかすごいことだと思います。</p> </div> <div class="section"> <h3 id="GILを切ってマルチスレッドするおまけ">GILを切ってマルチスレッドする(おまけ)</h3> <p> もっと速くしたいときは、numbaではGILを切れます。</p><p> これを使ってマルチスレッド並列化による高速化ができます。Python的には未知の世界です。しかもpython標準のthreadingから叩けば勝手にGILなしで動くので、使いやすいものです。</p><p> ただし先に言っておきますが、スレッド間の複雑な同期処理は難しそうなので、独立に動かせるスレッドがあることを前提にしてください……</p><p> 公式の例はこんなのですが、</p><p><a href="http://numba.pydata.org/numba-doc/latest/user/examples.html?highlight=nogil#multi-threading">1.19. Examples &mdash; Numba 0.48.0-py3.6-macosx-10.7-x86_64.egg documentation</a></p><p> 私もやってみます。</p><br /> <br /> <p> まず、並列化する前のコードです。速すぎるので調子に乗って配列を大きくしました。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> time <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">from</span> numba <span class="synPreProc">import</span> jit <span class="synPreProc">@</span><span class="synIdentifier">jit</span>(<span class="synConstant">&quot;f8[:, :](f8[:, :])&quot;</span>, nopython=<span class="synIdentifier">True</span>) <span class="synStatement">def</span> <span class="synIdentifier">mycumsum_numba</span>(A): result = np.empty(A.shape, dtype=np.float64) <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(A.shape[<span class="synConstant">0</span>]): <span class="synComment"># 行のループ</span> t = <span class="synConstant">0.0</span> <span class="synStatement">for</span> j <span class="synStatement">in</span> <span class="synIdentifier">range</span>(A.shape[<span class="synConstant">1</span>]): <span class="synComment"># 列のループ</span> x = A[i, j] <span class="synStatement">if</span> <span class="synConstant">0.0</span> &lt;= x: t += x <span class="synStatement">elif</span> -<span class="synConstant">1.0</span> &lt;= x &lt; <span class="synConstant">0.0</span>: t = <span class="synConstant">0.0</span> <span class="synStatement">elif</span> -<span class="synConstant">2.0</span> &lt;= x &lt; <span class="synConstant">1.0</span>: t *= <span class="synConstant">0.5</span> <span class="synStatement">else</span>: t += <span class="synConstant">42.0</span> result[i, j] = t <span class="synStatement">return</span> result np.random.seed(<span class="synConstant">42</span>) data = np.random.uniform(-<span class="synConstant">10.0</span>, <span class="synConstant">10.0</span>, size=(<span class="synConstant">5000</span>, <span class="synConstant">50000</span>)) <span class="synComment"># 自作numba版</span> times = [] <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">10</span>): t1 = time.time() res_mycumsum_numba = mycumsum_numba(data) t2 = time.time() times.append(t2 - t1) <span class="synIdentifier">print</span>(np.mean(times)) <span class="synComment"># =&gt; 1.8200997829437255</span> </pre><p> そろそろ速度的にきつくなってきました。スレッド並列化でこれを数倍速くします。</p><p> GILを切るにはnogil=Trueを指定します。が、GILを切ることで速くなるわけではありません。切った上でマルチスレッド処理を書くことで速くなるのです。</p><p> 実装としてはこんな感じです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># 並列版の中身</span> <span class="synPreProc">@</span><span class="synIdentifier">jit</span>(<span class="synConstant">&quot;void(f8[:, :], f8[:, :], i8, i8)&quot;</span>, nopython=<span class="synIdentifier">True</span>, nogil=<span class="synIdentifier">True</span>) <span class="synStatement">def</span> <span class="synIdentifier">mycumsum_numba_pi</span>(A, result, start, stop): <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(start, stop): <span class="synComment"># 行のループ</span> t = <span class="synConstant">0.0</span> <span class="synStatement">for</span> j <span class="synStatement">in</span> <span class="synIdentifier">range</span>(A.shape[<span class="synConstant">1</span>]): <span class="synComment"># 列のループ</span> x = A[i, j] <span class="synStatement">if</span> <span class="synConstant">0.0</span> &lt;= x: t += x <span class="synStatement">elif</span> -<span class="synConstant">1.0</span> &lt;= x &lt; <span class="synConstant">0.0</span>: t = <span class="synConstant">0.0</span> <span class="synStatement">elif</span> -<span class="synConstant">2.0</span> &lt;= x &lt; <span class="synConstant">1.0</span>: t *= <span class="synConstant">0.5</span> <span class="synStatement">else</span>: t += <span class="synConstant">42.0</span> result[i, j] = t <span class="synComment"># 並列版の外側(マルチスレッドで呼び出すためのもの)</span> <span class="synStatement">def</span> <span class="synIdentifier">mycumsum_numba_p</span>(A, n_threads=<span class="synConstant">8</span>): <span class="synComment"># n_threadsはマシンに合わせて調整してください。当方8C/16Tです。16も試しましたがかえって遅かったので8にしています</span> n = A.shape[<span class="synConstant">0</span>] result = np.empty(A.shape, dtype=np.float64) cl = n // n_threads l = [cl * x <span class="synStatement">for</span> x <span class="synStatement">in</span> <span class="synIdentifier">range</span>(n_threads)] + [n] args = [(A, result, start, stop) <span class="synStatement">for</span> start, stop <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(l, l[<span class="synConstant">1</span>:])] threads = [threading.Thread(target=mycumsum_numba_pi, args=a) <span class="synStatement">for</span> a <span class="synStatement">in</span> args] <span class="synStatement">for</span> th <span class="synStatement">in</span> threads: th.start() <span class="synStatement">for</span> th <span class="synStatement">in</span> threads: th.join() <span class="synStatement">return</span> result </pre><p> 中身にはnogil=Trueしないと何も意味のないことになるので付けましょう。外側はnumbaの関数にしなくてオッケーです。</p><p> 結果を受け取るために、返り値を使うのをやめて引数への副作用で結果を取り出すテクを使います。公式の例を見る限り、うまくviewを活かせば、もしかしたらここまで愚直なインデックスアクセスに頼らなくてもできそうな気もしますが、よくわからないのでstartとstopで実装です。</p><p> これによりnumbaで走るスレッドの外側で生成されたresultに対してスレッド並列に書き込みが行われるはずです。</p><p> 計測に使ったコードです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> time <span class="synPreProc">import</span> threading <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">from</span> numba <span class="synPreProc">import</span> jit <span class="synComment"># 愚直な実装</span> <span class="synPreProc">@</span><span class="synIdentifier">jit</span>(<span class="synConstant">&quot;f8[:, :](f8[:, :])&quot;</span>, nopython=<span class="synIdentifier">True</span>) <span class="synStatement">def</span> <span class="synIdentifier">mycumsum_numba</span>(A): result = np.empty(A.shape, dtype=np.float64) <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(A.shape[<span class="synConstant">0</span>]): <span class="synComment"># 行のループ</span> t = <span class="synConstant">0.0</span> <span class="synStatement">for</span> j <span class="synStatement">in</span> <span class="synIdentifier">range</span>(A.shape[<span class="synConstant">1</span>]): <span class="synComment"># 列のループ</span> x = A[i, j] <span class="synStatement">if</span> <span class="synConstant">0.0</span> &lt;= x: t += x <span class="synStatement">elif</span> -<span class="synConstant">1.0</span> &lt;= x &lt; <span class="synConstant">0.0</span>: t = <span class="synConstant">0.0</span> <span class="synStatement">elif</span> -<span class="synConstant">2.0</span> &lt;= x &lt; <span class="synConstant">1.0</span>: t *= <span class="synConstant">0.5</span> <span class="synStatement">else</span>: t += <span class="synConstant">42.0</span> result[i, j] = t <span class="synStatement">return</span> result <span class="synComment"># 並列版の中身</span> <span class="synPreProc">@</span><span class="synIdentifier">jit</span>(<span class="synConstant">&quot;void(f8[:, :], f8[:, :], i8, i8)&quot;</span>, nopython=<span class="synIdentifier">True</span>, nogil=<span class="synIdentifier">True</span>) <span class="synStatement">def</span> <span class="synIdentifier">mycumsum_numba_pi</span>(A, result, start, stop): <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(start, stop): <span class="synComment"># 行のループ</span> t = <span class="synConstant">0.0</span> <span class="synStatement">for</span> j <span class="synStatement">in</span> <span class="synIdentifier">range</span>(A.shape[<span class="synConstant">1</span>]): <span class="synComment"># 列のループ</span> x = A[i, j] <span class="synStatement">if</span> <span class="synConstant">0.0</span> &lt;= x: t += x <span class="synStatement">elif</span> -<span class="synConstant">1.0</span> &lt;= x &lt; <span class="synConstant">0.0</span>: t = <span class="synConstant">0.0</span> <span class="synStatement">elif</span> -<span class="synConstant">2.0</span> &lt;= x &lt; <span class="synConstant">1.0</span>: t *= <span class="synConstant">0.5</span> <span class="synStatement">else</span>: t += <span class="synConstant">42.0</span> result[i, j] = t <span class="synComment"># 並列版の外側(マルチスレッドで呼び出すためのもの)</span> <span class="synStatement">def</span> <span class="synIdentifier">mycumsum_numba_p</span>(A, n_threads=<span class="synConstant">8</span>): n = A.shape[<span class="synConstant">0</span>] result = np.empty(A.shape, dtype=np.float64) cl = n // n_threads l = [cl * x <span class="synStatement">for</span> x <span class="synStatement">in</span> <span class="synIdentifier">range</span>(n_threads)] + [n] args = [(A, result, start, stop) <span class="synStatement">for</span> start, stop <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(l, l[<span class="synConstant">1</span>:])] threads = [threading.Thread(target=mycumsum_numba_pi, args=a) <span class="synStatement">for</span> a <span class="synStatement">in</span> args] <span class="synStatement">for</span> th <span class="synStatement">in</span> threads: th.start() <span class="synStatement">for</span> th <span class="synStatement">in</span> threads: th.join() <span class="synStatement">return</span> result np.random.seed(<span class="synConstant">42</span>) data = np.random.uniform(-<span class="synConstant">10.0</span>, <span class="synConstant">10.0</span>, size=(<span class="synConstant">5000</span>, <span class="synConstant">50000</span>)) <span class="synComment"># 検証</span> <span class="synStatement">assert</span> (mycumsum_numba(data) == mycumsum_numba_p(data)).all() <span class="synComment"># 自作numba版</span> times = [] <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">10</span>): t1 = time.time() res = mycumsum_numba(data) t2 = time.time() times.append(t2 - t1) <span class="synIdentifier">print</span>(np.mean(times)) <span class="synComment"># =&gt; 1.819163990020752</span> <span class="synComment"># 自作numba並列版</span> times = [] <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">10</span>): t1 = time.time() res = mycumsum_numba_p(data) t2 = time.time() times.append(t2 - t1) <span class="synIdentifier">print</span>(np.mean(times)) <span class="synComment"># =&gt; 0.2936941385269165</span> <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant">1.8091564416885375</span> <span class="synConstant">0.2936941385269165</span> <span class="synConstant">&quot;&quot;&quot;</span> </pre><p> ということで8スレッドで並列に動かして6倍くらい速くなりました。オーバーヘッドもあれば記憶領域の効率(キャッシュとメモリ帯域)的にも悪いのでこんなものです。それでもmultiprocessingでプロセス並列でやるより、原理的にははるかに効率的な方法です。</p> </div> <div class="section"> <h3 id="まとめ">まとめ</h3> <p> こんな感じで使えて速くて便利なnumbaを使わない手はないっ……!</p><p> という訳でもないんですよね。必要になるシチュエーションは限られているので、数値計算寄りのことをしている一般的Pythonユーザでも一年に一回あるかどうかでしょう。私は4年間くらい実用的に使わずに過ごしてきましたが<a href="#f-0c30f57e" name="fn-0c30f57e" title="存在は知ってるけどどう使うのかよくわからない、@jitしても速くならないし……とかそんなレベルでした。">*6</a>、そんなに困りませんでした。</p><p> 本格的にアルゴリズムの実装などに使うには何かと機能的に物足りないのは事実で、C/C++やCythonの方がたぶん向いています。また、日常的にインデックスループを書いて走らせる必要がある人はJuliaの方がいいかもしれません。</p><p> じゃあなんで? という話になるのですが、こちらの記事の冒頭にある表を見てください。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fyniji%2Fitems%2Fb7acffa02f03a94882e5" title="Python を高速化する Numba, Cython 等を使って Julia Micro-Benchmarks してみた - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/yniji/items/b7acffa02f03a94882e5">qiita.com</a></cite></p><p> 速度的には、CやJuliaと互角に見えます(CとJuliaが互角なのがそもそもびっくりですが、numbaはそれと互角、という意味)。つまりCの速度で動くギリギリPythonです。唯一無二の存在と言っていいでしょう。</p><p> これは強い……</p><p> どんな感じでやるのか知っておいて、頭の引き出しに入れておけば、必要になったときに取り出せます。雑に使うだけなら学習コスト低いのが良いところです<a href="#f-a4aa90f7" name="fn-a4aa90f7" title="この記事読めばできるでしょという期待を込めて書いています。">*7</a>。それで一年に一回の行き詰まりのときに、諦めるしかないか、突破できるかが決まると思います。</p> </div><div class="footnote"> <p class="footnote"><a href="#fn-66fb5e68" name="f-66fb5e68" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">ちなみにこの記事では実践的な数値計算はしません。それでも多少はなにかの手助けになるでしょう。</span></p> <p class="footnote"><a href="#fn-edb7a899" name="f-edb7a899" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">動的メモリ確保してるから配列じゃないとか面倒くさいこと言わないでください、配列です。</span></p> <p class="footnote"><a href="#fn-0ac3a5e2" name="f-0ac3a5e2" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">あるいはCythonかもしれないし、Pythonを見限ってJuliaに行ってしまうかもしれませんが</span></p> <p class="footnote"><a href="#fn-1abad7bb" name="f-1abad7bb" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">あ、でもCとか古めの静的型付けは好きです。苦手な気がするのはオブジェクト指向以降のクラスと絡みついたモダンな静的型付けです。Javaのインターフェースとか必要なのはわかるけど好きになれる気がしません。新しめのpandasのリファレンスは丁寧にシグネチャにPythonの型ヒントを書いてくれていますが、あれとか正気の沙汰とは思えません。</span></p> <p class="footnote"><a href="#fn-a0111520" name="f-a0111520" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">numpyのaxis=1相当</span></p> <p class="footnote"><a href="#fn-0c30f57e" name="f-0c30f57e" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text">存在は知ってるけどどう使うのかよくわからない、@jitしても速くならないし……とかそんなレベルでした。</span></p> <p class="footnote"><a href="#fn-a4aa90f7" name="f-a4aa90f7" class="footnote-number">*7</a><span class="footnote-delimiter">:</span><span class="footnote-text">この記事読めばできるでしょという期待を込めて書いています。</span></p> </div> hayataka2049 scikit-learnのStandardScalerで疎行列型のまま標準化する hatenablog://entry/26006613508419701 2020-02-08T07:32:41+09:00 2020-02-08T07:32:41+09:00 ことのあらまし データの標準化は機械学習の前処理としてとても重要です。そして疎行列型データ構造は、スパースなデータを表現するためにはとても適しています。 残念ながら、普通に疎行列型を標準化しようとすると、疎行列性が失せます。考えてみればわかるのですが、普通の標準化では平均0にしてしまいます。たとえば非負の疎行列だとすると、大半を占める0のデータはたぶん負値になることでしょう。そしてスパース性は維持できません。 scikit-learnの素敵な対策 ま、この辺は当然考えられていて、なんと超親切なメッセージが出ます。 from scipy.sparse import csr_matrix from… <div class="section"> <h3>ことのあらまし</h3> <p> データの標準化は機械学習の前処理としてとても重要です。そして疎行列型データ構造は、スパースなデータを表現するためにはとても適しています。</p><p> 残念ながら、普通に疎行列型を標準化しようとすると、疎行列性が失せます。考えてみればわかるのですが、普通の標準化では平均0にしてしまいます。たとえば非負の疎行列だとすると、大半を占める0のデータはたぶん負値になることでしょう。そしてスパース性は維持できません。</p> </div> <div class="section"> <h3>scikit-learnの素敵な対策</h3> <p> ま、この辺は当然考えられていて、なんと超親切なメッセージが出ます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> scipy.sparse <span class="synPreProc">import</span> csr_matrix <span class="synPreProc">from</span> sklearn.preprocessing <span class="synPreProc">import</span> StandardScaler a = csr_matrix([[<span class="synConstant">0</span>, <span class="synConstant">1</span>, <span class="synConstant">0</span>, <span class="synConstant">3</span>, <span class="synConstant">0</span>], [<span class="synConstant">1</span>, <span class="synConstant">0</span>, <span class="synConstant">0</span>, <span class="synConstant">1</span>, <span class="synConstant">2</span>], [<span class="synConstant">0</span>, <span class="synConstant">0</span>, <span class="synConstant">4</span>, <span class="synConstant">0</span>, <span class="synConstant">0</span>]]) <span class="synIdentifier">print</span>(<span class="synIdentifier">repr</span>(a)) ss = StandardScaler() result = ss.fit_transform(a) <span class="synComment"># ここでエラー発生</span> <span class="synIdentifier">print</span>(<span class="synIdentifier">repr</span>(result)) </pre><pre class="code" data-lang="" data-unlink>ValueError: Cannot center sparse matrices: pass `with_mean=False` instead. See docstring for motivation and alternatives.</pre><p> 少し前のscikit-learnだと本当にこの挙動だったかどうかは少し記憶が怪しいんですが、今0.22で試したらこうなっていました。</p> </div> <div class="section"> <h3>with_mean=Falseとは?</h3> <p> 標準化の式というと、普通は</p><p><img src="https://chart.apis.google.com/chart?cht=tx&chl=%5Cdisplaystyle%20z_i%20%3D%20%5Cfrac%7Bx_i%20-%20%5Cbar%7Bx%7D%7D%7B%5Csigma%20_x%7D" alt="\displaystyle z_i = \frac{x_i - \bar{x}}{\sigma _x}"/></p><p> これで考えると思います。しかし今回は中心化を行わずに</p><p><img src="https://chart.apis.google.com/chart?cht=tx&chl=%5Cdisplaystyle%20z_i%20%3D%20%5Cfrac%7Bx_i%7D%7B%5Csigma%20_x%7D" alt="\displaystyle z_i = \frac{x_i}{\sigma _x}"/></p><p> で処理します。</p><p> こうすると0は0のままで、かつ分散は1に調整されます。</p><p> この場合は疎行列として表現できてよさげですね。scikit-learnはこの場合、疎行列型を維持したまま計算してくれます。とても助かります。</p><p> 値としてはこんな結果になります。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> scipy.sparse <span class="synPreProc">import</span> csr_matrix <span class="synPreProc">from</span> sklearn.preprocessing <span class="synPreProc">import</span> StandardScaler a = csr_matrix([[<span class="synConstant">0</span>, <span class="synConstant">1</span>, <span class="synConstant">0</span>, <span class="synConstant">3</span>, <span class="synConstant">0</span>], [<span class="synConstant">1</span>, <span class="synConstant">0</span>, <span class="synConstant">0</span>, <span class="synConstant">1</span>, <span class="synConstant">2</span>], [<span class="synConstant">0</span>, <span class="synConstant">0</span>, <span class="synConstant">4</span>, <span class="synConstant">0</span>, <span class="synConstant">0</span>]]) <span class="synIdentifier">print</span>(<span class="synIdentifier">repr</span>(a.A)) <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant">array([[0, 1, 0, 3, 0],</span> <span class="synConstant"> [1, 0, 0, 1, 2],</span> <span class="synConstant"> [0, 0, 4, 0, 0]], dtype=int64)</span> <span class="synConstant">&quot;&quot;&quot;</span> ss = StandardScaler(with_mean=<span class="synIdentifier">False</span>) result = ss.fit_transform(a) <span class="synIdentifier">print</span>(<span class="synIdentifier">repr</span>(result.A)) <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant">array([[0. , 2.12132034, 0. , 2.40535118, 0. ],</span> <span class="synConstant"> [2.12132034, 0. , 0. , 0.80178373, 2.12132034],</span> <span class="synConstant"> [0. , 0. , 2.12132034, 0. , 0. ]])</span> <span class="synConstant">&quot;&quot;&quot;</span> </pre><p> なお、ただのnumpy配列としてスパースなデータを入力した場合は特にメリットはありません。</p> </div> <div class="section"> <h3>まとめ</h3> <p> 疎行列型のまま扱うとパフォーマンス上のメリットが得られることが多いので、積極的に試してみるべきだと思います。</p> </div> hayataka2049 【python】zipを使ってn-gram列を生成する hatenablog://entry/26006613506825950 2020-02-04T10:09:57+09:00 2020-02-04T10:09:57+09:00 はじめに n-gramは自然言語処理でよく使われる方法です。n-gram - Wikipedia さて、以下のような関数を作りたいとします。 n_gram("abcde", n=2, sep="-") # ["a-b", "b-c", "c-d", "d-e"] n=2ならbigram, n=3ならtrigramという言い方があります。さて、たとえばbigramなら以下のように書けます。 >>> def bigram(seq, sep="-"): ... return [sep.join(x) for x in zip(seq, seq[1:])] ... >>> bigram("abcde"… <div class="section"> <h3>はじめに</h3> <p> n-gramは自然言語処理でよく使われる方法です。</p><p><a href="https://en.wikipedia.org/wiki/N-gram">n-gram - Wikipedia</a></p><p> さて、以下のような関数を作りたいとします。</p> <pre class="code lang-python" data-lang="python" data-unlink>n_gram(<span class="synConstant">&quot;abcde&quot;</span>, n=<span class="synConstant">2</span>, sep=<span class="synConstant">&quot;-&quot;</span>) <span class="synComment"># [&quot;a-b&quot;, &quot;b-c&quot;, &quot;c-d&quot;, &quot;d-e&quot;]</span> </pre><p> n=2ならbigram, n=3ならtrigramという言い方があります。さて、たとえばbigramなら以下のように書けます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synStatement">def</span> <span class="synIdentifier">bigram</span>(seq, sep=<span class="synConstant">&quot;-&quot;</span>): ... <span class="synStatement">return</span> [sep.join(x) <span class="synStatement">for</span> x <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(seq, seq[<span class="synConstant">1</span>:])] ... &gt;&gt;&gt; bigram(<span class="synConstant">&quot;abcde&quot;</span>) [<span class="synConstant">'a-b'</span>, <span class="synConstant">'b-c'</span>, <span class="synConstant">'c-d'</span>, <span class="synConstant">'d-e'</span>] </pre><p> これは私の発明ではありませんが、pythonに慣れている人なら誰でも思いつく可能性はあると思います。</p><p> trigramはこう。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synStatement">def</span> <span class="synIdentifier">trigram</span>(seq, sep=<span class="synConstant">&quot;-&quot;</span>): ... <span class="synStatement">return</span> [sep.join(x) <span class="synStatement">for</span> x <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(seq, seq[<span class="synConstant">1</span>:], seq[<span class="synConstant">2</span>:])] ... &gt;&gt;&gt; trigram(<span class="synConstant">&quot;abcde&quot;</span>) [<span class="synConstant">'a-b-c'</span>, <span class="synConstant">'b-c-d'</span>, <span class="synConstant">'c-d-e'</span>] </pre> </div> <div class="section"> <h3>一般化</h3> <p> zipの引数部分をnの数だけ繰り返せば、一般化することができます。ここはジェネレータ式の出番です。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synStatement">def</span> <span class="synIdentifier">n_gram</span>(seq, n=<span class="synConstant">2</span>, sep=<span class="synConstant">&quot;-&quot;</span>): ... <span class="synStatement">return</span> [sep.join(x) <span class="synStatement">for</span> x <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(*(seq[i:] <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(n)))] ... &gt;&gt;&gt; n_gram(<span class="synConstant">&quot;abcde&quot;</span>, n=<span class="synConstant">1</span>) [<span class="synConstant">'a'</span>, <span class="synConstant">'b'</span>, <span class="synConstant">'c'</span>, <span class="synConstant">'d'</span>, <span class="synConstant">'e'</span>] &gt;&gt;&gt; n_gram(<span class="synConstant">&quot;abcde&quot;</span>, n=<span class="synConstant">2</span>) [<span class="synConstant">'a-b'</span>, <span class="synConstant">'b-c'</span>, <span class="synConstant">'c-d'</span>, <span class="synConstant">'d-e'</span>] &gt;&gt;&gt; n_gram(<span class="synConstant">&quot;abcde&quot;</span>, n=<span class="synConstant">3</span>) [<span class="synConstant">'a-b-c'</span>, <span class="synConstant">'b-c-d'</span>, <span class="synConstant">'c-d-e'</span>] &gt;&gt;&gt; n_gram(<span class="synConstant">&quot;abcde&quot;</span>, n=<span class="synConstant">4</span>) [<span class="synConstant">'a-b-c-d'</span>, <span class="synConstant">'b-c-d-e'</span>] &gt;&gt;&gt; n_gram(<span class="synConstant">&quot;abcde&quot;</span>, n=<span class="synConstant">5</span>) [<span class="synConstant">'a-b-c-d-e'</span>] &gt;&gt;&gt; n_gram(<span class="synConstant">&quot;abcde&quot;</span>, n=<span class="synConstant">6</span>) [] </pre> </div> <div class="section"> <h3>効率化</h3> <p> 上の実装はラフに見てseqのn倍のメモリ領域を食ってしまいます。</p><p> そんなに深刻な問題でもありませんが、とはいえこのままではなんとなくエレガントではないので、isliceを使いたいと思います。</p><p><a href="https://docs.python.org/ja/3/library/itertools.html">itertools --- &#x52B9;&#x7387;&#x7684;&#x306A;&#x30EB;&#x30FC;&#x30D7;&#x5B9F;&#x884C;&#x306E;&#x305F;&#x3081;&#x306E;&#x30A4;&#x30C6;&#x30EC;&#x30FC;&#x30BF;&#x751F;&#x6210;&#x95A2;&#x6570; &mdash; Python 3.8.1 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a></p><p> ドキュメントには明記されていなかったと思いますが、seq[i:]とするにはislice(seq, i, None)でいいのだと思います。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">from</span> itertools <span class="synPreProc">import</span> islice &gt;&gt;&gt; <span class="synStatement">def</span> <span class="synIdentifier">n_gram</span>(seq, n=<span class="synConstant">2</span>, sep=<span class="synConstant">&quot;-&quot;</span>): ... <span class="synStatement">return</span> [sep.join(x) <span class="synStatement">for</span> x <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(*(islice(seq, i, <span class="synIdentifier">None</span>) <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(n)))] ... &gt;&gt;&gt; n_gram(<span class="synConstant">&quot;abcde&quot;</span>, n=<span class="synConstant">1</span>) [<span class="synConstant">'a'</span>, <span class="synConstant">'b'</span>, <span class="synConstant">'c'</span>, <span class="synConstant">'d'</span>, <span class="synConstant">'e'</span>] &gt;&gt;&gt; n_gram(<span class="synConstant">&quot;abcde&quot;</span>, n=<span class="synConstant">2</span>) [<span class="synConstant">'a-b'</span>, <span class="synConstant">'b-c'</span>, <span class="synConstant">'c-d'</span>, <span class="synConstant">'d-e'</span>] &gt;&gt;&gt; n_gram(<span class="synConstant">&quot;abcde&quot;</span>, n=<span class="synConstant">3</span>) [<span class="synConstant">'a-b-c'</span>, <span class="synConstant">'b-c-d'</span>, <span class="synConstant">'c-d-e'</span>] &gt;&gt;&gt; n_gram(<span class="synConstant">&quot;abcde&quot;</span>, n=<span class="synConstant">4</span>) [<span class="synConstant">'a-b-c-d'</span>, <span class="synConstant">'b-c-d-e'</span>] &gt;&gt;&gt; n_gram(<span class="synConstant">&quot;abcde&quot;</span>, n=<span class="synConstant">5</span>) [<span class="synConstant">'a-b-c-d-e'</span>] &gt;&gt;&gt; n_gram(<span class="synConstant">&quot;abcde&quot;</span>, n=<span class="synConstant">6</span>) [] </pre> </div> <div class="section"> <h3>シーケンスを第一引数に取る意味</h3> <p> 任意のイテラブルを取れるようにしておくと、</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; n_gram([<span class="synConstant">&quot;I&quot;</span>, <span class="synConstant">&quot;Am&quot;</span>, <span class="synConstant">&quot;a&quot;</span>, <span class="synConstant">&quot;Cat&quot;</span>]) [<span class="synConstant">'I-Am'</span>, <span class="synConstant">'Am-a'</span>, <span class="synConstant">'a-Cat'</span>] </pre><p> のように応用が効いたりして便利です。</p> </div> hayataka2049 sklearnで混同行列をヒートマップにして描画するplot_confusion_matrix hatenablog://entry/26006613483852024 2019-12-15T04:01:51+09:00 2019-12-15T04:01:51+09:00 はじめに scikit-learnのv0.22で、混同行列をプロットするための便利関数であるsklearn.metrics.plot_confusion_matrixが追加されました。 使いやすそうなので試してみます。 使い方 リファレンスはこちらです。sklearn.metrics.plot_confusion_matrix — scikit-learn 0.22 documentation 引数のフォーマットを見ると、 sklearn.metrics.plot_confusion_matrix(estimator, X, y_true, labels=None, sample_weight… <div class="section"> <h3>はじめに</h3> <p> scikit-learnのv0.22で、混同行列をプロットするための便利関数であるsklearn.metrics.plot_confusion_matrixが追加されました。</p><p> 使いやすそうなので試してみます。</p> </div> <div class="section"> <h3>使い方</h3> <p> リファレンスはこちらです。</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.metrics.plot_confusion_matrix.html">sklearn.metrics.plot_confusion_matrix &mdash; scikit-learn 0.22 documentation</a></p><p> 引数のフォーマットを見ると、</p> <blockquote> <p>sklearn.metrics.plot_confusion_matrix(estimator, X, y_true, labels=None, sample_weight=None, normalize=None, display_labels=None, include_values=True, xticks_rotation='horizontal', values_format=None, cmap='viridis', ax=None)</p> </blockquote> <p> あ、予測器とXとyを入れるタイプの関数だ。なんか微妙に使いづらいですね。この時点でなんか困惑気味ですが、やってみます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_wine <span class="synPreProc">from</span> sklearn.model_selection <span class="synPreProc">import</span> train_test_split <span class="synPreProc">from</span> sklearn.linear_model <span class="synPreProc">import</span> LogisticRegression <span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> plot_confusion_matrix wine = load_wine() X_train, X_test, y_train, y_test = train_test_split( wine.data, wine.target, stratify=wine.target) clf = LogisticRegression() clf.fit(X_train, y_train) plot_confusion_matrix(clf, X_test, y_test, display_labels=wine.target_names, cmap=plt.cm.Blues,) plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="result.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20191215/20191215035932.png" alt="result.png" title="f:id:hayataka2049:20191215035932p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result.png</figcaption></figure></p><p> ソースを見る限り、内部で交差検証などしてくれる訳ではないようなので、学習済みモデルとテストデータを渡してプロットさせます。また、labelsという引数がありますが微妙に罠っぽくて、表示に使われるラベルはdisplay_labelsの方です。</p><p> 一応各引数の説明など。</p> <ul> <li>estimator, X, y_true</li> </ul><p> は、説明要らないよね。上で示したのと同じ使い方をします。</p> <ul> <li>labels</li> </ul><p> ラベルの順序を並び替えたり、一部のラベルのみ取り出してプロットしたいとき使うそうです。y_trueの中身が[0,0,0,1,1,1]なら[0,1]や[1,0]などが指定できます。別に要らないでしょう。</p> <ul> <li>sample_weightarray-like of shape (n_samples,), default=None</li> </ul><p> サンプルの重み。</p> <ul> <li>normalize{"true", "pred", "all"}, default=None</li> </ul><p> 全体を正規化するかどうか。するならその方法を文字列で指定します。</p> <ul> <li>display_labels</li> </ul><p> 表示されるラベルの名前はこちらで指定します。使用頻度は高いはずです。</p> <ul> <li>include_valuesbool, default=True</li> </ul><p> Falseに設定すると数字が出てこなくなります。普通は数字があったほうが好ましいでしょう。</p> <ul> <li>xticks_rotation{"vertical", "horizontal"} </li> </ul><p> x軸のラベルが回転するかどうか。デフォルトでは回転しません。</p> <ul> <li>values_format</li> </ul><p> "d"や".2f"などが指定できる。表示の書式で、format関数などに準じると思われる。</p> <ul> <li>cmap, ax</li> </ul><p> matplotlib関連です。デフォルトのcmapの"viridis"がexampleで「ダサいよねこれ」とBluesにされているあたり泣けます。 </p> </div> <div class="section"> <h3>使いづらいのでConfusionMatrixDisplayを使うことにする</h3> <p> なんで混同行列を描くためだけにpredictメソッド走らせなきゃいかんのだと思ったので、仕様を確認します。すると、ConfusionMatrixDisplayなるクラスがあることがわかります。</p> <blockquote> <p>It is recommend to use plot_confusion_matrix to create a ConfusionMatrixDisplay. <br /> <a href="https://scikit-learn.org/stable/modules/generated/sklearn.metrics.ConfusionMatrixDisplay.html">sklearn.metrics.ConfusionMatrixDisplay &mdash; scikit-learn 0.22 documentation</a></p> </blockquote> <p> うるせえ、あるもんは使うんじゃ。</p><p> インスタンスを作ってplotメソッドを呼ぶと動きます。引数はだいたい上と共通ですが、インスタンスを作るときに</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">class</span> <span class="synIdentifier">sklearn</span>.metrics.ConfusionMatrixDisplay(confusion_matrix, display_labels) </pre><p> なので柔軟性が多少上がります。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_wine <span class="synPreProc">from</span> sklearn.model_selection <span class="synPreProc">import</span> train_test_split <span class="synPreProc">from</span> sklearn.linear_model <span class="synPreProc">import</span> LogisticRegression <span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> confusion_matrix, ConfusionMatrixDisplay wine = load_wine() X_train, X_test, y_train, y_test = train_test_split( wine.data, wine.target, stratify=wine.target) clf = LogisticRegression() clf.fit(X_train, y_train) y_pred = clf.predict(X_test) cmx = confusion_matrix(y_test, y_pred) cmd = ConfusionMatrixDisplay(cmx, wine.target_names) cmd.plot() plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>) </pre><p> 結果の図は同じなので省略。任意の予測ラベルで描画しようと思えばできます。ちょっと微妙な感じもしますが、許容範囲でしょう。</p> </div> <div class="section"> <h3>まとめ</h3> <p> このようなものができましたので、今後は混同行列のプロットではそんなに困らないと思います。</p><p> あとどうでもいい話、そもそもこのブログは混同行列の描き方がわからなくて調べてまとめたのが始まりですが、</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2016%2F12%2F15%2F222339" title="【python】混同行列(Confusion matrix)をヒートマップにして描画 - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.haya-programming.com/entry/2016/12/15/222339">www.haya-programming.com</a></cite></p><p> なんとなく原点回帰した感があって感動しています。</p> </div> hayataka2049 pandasで年月日時刻の列を結合して一列にする(datetime64で) hatenablog://entry/26006613474664125 2019-12-01T23:55:28+09:00 2019-12-01T23:55:28+09:00 概要 ローデータ(生データ)を取り込むと、年月日が独立して入っている感じの嫌なデータになっていることがあります。 年,月,日 1996,8,1 1998,12,2 2012,05,3 こういうのは嬉しくないので、できるだけ単一のdatetime風の型に変換しておきたいのですが、意外と難しかったりします。 文字列操作として考える 以下のように読み込みます(io.StringIOを使っていますが実際はCSVファイルだと思ってください)。 import io import pandas as pd data = """ 年,月,日 1996,8,1 1998,12,2 2012,05,3 """ d… <div class="section"> <h3>概要</h3> <p> ローデータ(生データ)を取り込むと、年月日が独立して入っている感じの嫌なデータになっていることがあります。</p> <pre class="code" data-lang="" data-unlink>年,月,日 1996,8,1 1998,12,2 2012,05,3</pre><p> こういうのは嬉しくないので、できるだけ単一のdatetime風の型に変換しておきたいのですが、意外と難しかったりします。</p> </div> <div class="section"> <h3>文字列操作として考える</h3> <p> 以下のように読み込みます(<a href="https://www.haya-programming.com/entry/2018/07/03/042203">io.StringIO&#x3092;&#x4F7F;&#x3063;&#x3066;&#x3044;&#x307E;&#x3059;&#x304C;</a>実際はCSVファイルだと思ってください)。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> io <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd data = <span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">年,月,日</span> <span class="synConstant">1996,8,1</span> <span class="synConstant">1998,12,2</span> <span class="synConstant">2012,05,3</span> <span class="synConstant">&quot;&quot;&quot;</span> df = pd.read_csv(io.StringIO(data), dtype={k:<span class="synIdentifier">object</span> <span class="synStatement">for</span> k <span class="synStatement">in</span> <span class="synConstant">&quot;年月日&quot;</span>}) </pre><p> 型をobject型にしておくのがミソで、整数型にされると文字列操作で変換できません。読み込んでからastypeで変換してもいいですが、二度手間ですね。</p> <pre class="code lang-python" data-lang="python" data-unlink>df[<span class="synConstant">&quot;DateTime&quot;</span>] = pd.to_datetime(df[<span class="synConstant">&quot;年&quot;</span>].str.cat([df[<span class="synConstant">&quot;月&quot;</span>], df[<span class="synConstant">&quot;日&quot;</span>]], sep=<span class="synConstant">&quot; &quot;</span>)) <span class="synIdentifier">print</span>(df) <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant"> 年 月 日 DateTime</span> <span class="synConstant">0 1996 8 1 1996-08-01</span> <span class="synConstant">1 1998 12 2 1998-12-02</span> <span class="synConstant">2 2012 05 3 2012-05-03</span> <span class="synConstant">&quot;&quot;&quot;</span> </pre><p> これはこれでできるのですが、文字列を介すると二度手間感が否めません。</p> </div> <div class="section"> <h3>時刻もある場合</h3> <p> とにかくそれっぽいフォーマットに無理矢理仕立てれば、この方法でできます(というかできるはずです)。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> io <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd data = <span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">年,月,日,時,分</span> <span class="synConstant">1996,8,1,12,5</span> <span class="synConstant">1998,12,2,4,12</span> <span class="synConstant">2012,05,3,23,56</span> <span class="synConstant">&quot;&quot;&quot;</span> df = pd.read_csv(io.StringIO(data), dtype={k:<span class="synIdentifier">object</span> <span class="synStatement">for</span> k <span class="synStatement">in</span> <span class="synConstant">&quot;年月日時分&quot;</span>}) df[<span class="synConstant">&quot;DateTime&quot;</span>] = pd.to_datetime(df[<span class="synConstant">&quot;年&quot;</span>].str.cat([df[<span class="synConstant">&quot;月&quot;</span>], df[<span class="synConstant">&quot;日&quot;</span>], df[<span class="synConstant">&quot;時&quot;</span>]], sep=<span class="synConstant">&quot; &quot;</span>).str.cat(df[<span class="synConstant">&quot;分&quot;</span>], sep=<span class="synConstant">&quot;:&quot;</span>)) <span class="synIdentifier">print</span>(df) <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant"> 年 月 日 時 分 DateTime</span> <span class="synConstant">0 1996 8 1 12 5 1996-08-01 12:05:00</span> <span class="synConstant">1 1998 12 2 4 12 1998-12-02 04:12:00</span> <span class="synConstant">2 2012 05 3 23 56 2012-05-03 23:56:00</span> <span class="synConstant">&quot;&quot;&quot;</span> </pre><p> やはりスマートではない。</p> </div> <div class="section"> <h3>内包表記でdatetimeっぽい型のリストにすればいいんだよ</h3> <p> そう、普通はそうしたいところ。</p><p> 型で迷うと思いますが、たぶんTimestampでいいと思います。</p><p><a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Timestamp.html">pandas.Timestamp &mdash; pandas 0.25.3 documentation</a><br /> </p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> io <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd data = <span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">年,月,日,時,分</span> <span class="synConstant">1996,8,1,12,5</span> <span class="synConstant">1998,12,2,4,12</span> <span class="synConstant">2012,05,3,23,56</span> <span class="synConstant">&quot;&quot;&quot;</span> df = pd.read_csv(io.StringIO(data)) df[<span class="synConstant">&quot;DateTime&quot;</span>] = [ pd.Timestamp( year=row[<span class="synConstant">&quot;年&quot;</span>], month=row[<span class="synConstant">&quot;月&quot;</span>], day=row[<span class="synConstant">&quot;日&quot;</span>], hour=row[<span class="synConstant">&quot;時&quot;</span>], minute=row[<span class="synConstant">&quot;分&quot;</span>]) <span class="synStatement">for</span> i, row <span class="synStatement">in</span> df.iterrows()] <span class="synIdentifier">print</span>(df) <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant"> 年 月 日 時 分 DateTime</span> <span class="synConstant">0 1996 8 1 12 5 1996-08-01 12:05:00</span> <span class="synConstant">1 1998 12 2 4 12 1998-12-02 04:12:00</span> <span class="synConstant">2 2012 5 3 23 56 2012-05-03 23:56:00</span> <span class="synConstant">&quot;&quot;&quot;</span> </pre><p> 読み込みで文字列にしないといけない、二度手間、といった微妙さがなくなりました。わーい。</p><p> これはこれで上手くいきます。が、スマートなはずなのにスマートに見えない。キーワード引数の指定が汚すぎるからですね。</p><p> ダブルアスタリスクのアンパックを使えば……とか一瞬は思いましたが、そのためには列名を変えたdfをコピーして作らないといけません。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> io <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd data = <span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">年,月,日,時,分</span> <span class="synConstant">1996,8,1,12,5</span> <span class="synConstant">1998,12,2,4,12</span> <span class="synConstant">2012,05,3,23,56</span> <span class="synConstant">&quot;&quot;&quot;</span> df = pd.read_csv(io.StringIO(data)) df_d = df[[<span class="synConstant">&quot;年&quot;</span>, <span class="synConstant">&quot;月&quot;</span>, <span class="synConstant">&quot;日&quot;</span>, <span class="synConstant">&quot;時&quot;</span>, <span class="synConstant">&quot;分&quot;</span>]].copy() df_d.columns = [<span class="synConstant">&quot;year&quot;</span>, <span class="synConstant">&quot;month&quot;</span>, <span class="synConstant">&quot;day&quot;</span>, <span class="synConstant">&quot;hour&quot;</span>, <span class="synConstant">&quot;minute&quot;</span>] df[<span class="synConstant">&quot;DateTime&quot;</span>] = [pd.Timestamp(**row) <span class="synStatement">for</span> i, row <span class="synStatement">in</span> df_d.iterrows()] <span class="synIdentifier">print</span>(df) <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant"> 年 月 日 時 分 DateTime</span> <span class="synConstant">0 1996 8 1 12 5 1996-08-01 12:05:00</span> <span class="synConstant">1 1998 12 2 4 12 1998-12-02 04:12:00</span> <span class="synConstant">2 2012 5 3 23 56 2012-05-03 23:56:00</span> <span class="synConstant">&quot;&quot;&quot;</span> </pre><p> こっちの方が多少スマートかな。上の書き方でも別に困ることはないです。</p> </div> <div class="section"> <h3>まとめ</h3> <p> 普通にTimestampのiterableを突っ込めばいいだけだけど、このやり方が調べても出てこなくて、できないのかなとか思って焦りつつやってみたらできたので記事にしました。</p><p> 日付時刻の扱いは割と面倒ですが、けっきょくのところ素直に組んでいけば良いはず。</p> </div> <div class="section"> <h3>参考</h3> <p><a href="https://qiita.com/kawagucchi_suzuki/items/a1b3e1ca142a4454934c">Pandas&#x3067;&#x306E;&#x65E5;&#x4ED8;&#x30FB;&#x6642;&#x9593;&#x5468;&#x308A;&#x306E;&#x3061;&#x3087;&#x3063;&#x3068;&#x3057;&#x305F;&#x30C1;&#x30FC;&#x30C8;&#x30B7;&#x30FC;&#x30C8; - Qiita</a><br />  これと同じようなことをやっています。</p> </div> hayataka2049 【python】UnboundLocalErrorの原因と対処法 hatenablog://entry/26006613467329884 2019-11-19T20:06:59+09:00 2019-11-19T20:16:22+09:00 はじめに 関数の中で関数の外の変数を操作するようなコードを書いていると、たまに下記のようなエラーが出ます。UnboundLocalError: local variable '***' referenced before assignment 初歩的ですが、意外とまとまった良い解説がないので、記事にしておきます。 原因 まず、実際にこのエラーが出るコードを示します。 def f(): print(a) a = 10 f() 実際に出るエラーメッセージは以下のようなものです。 Traceback (most recent call last): File "***.py", line 5, in… <div class="section"> <h3>はじめに</h3> <p> 関数の中で関数の外の変数を操作するようなコードを書いていると、たまに下記のようなエラーが出ます。</p><p><b>UnboundLocalError: local variable '***' referenced before assignment</b></p><p> 初歩的ですが、意外とまとまった良い解説がないので、記事にしておきます。</p> </div> <div class="section"> <h3>原因</h3> <p> まず、実際にこのエラーが出るコードを示します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">f</span>(): <span class="synIdentifier">print</span>(a) a = <span class="synConstant">10</span> f() </pre><p> 実際に出るエラーメッセージは以下のようなものです。</p> <pre class="code" data-lang="" data-unlink>Traceback (most recent call last): File &#34;***.py&#34;, line 5, in &lt;module&gt; f() File &#34;***.py&#34;, line 2, in f print(a) UnboundLocalError: local variable &#39;a&#39; referenced before assignment</pre><p> シンプルな例ですが、ローカル変数aが代入の前にprintで参照されているのがおわかりいただけるかと思います。</p><p> さて、実は以下のようにしても同じエラーが出ます。</p> <pre class="code lang-python" data-lang="python" data-unlink>a = <span class="synConstant">20</span> <span class="synStatement">def</span> <span class="synIdentifier">f</span>(): <span class="synIdentifier">print</span>(a) a = <span class="synConstant">10</span> f() </pre><p> 今度はaが定義されているのになぜ? と思うかもしれませんが、変数のスコープについて考察すると理解できます。</p><p><a href="https://wa3.i-3-i.info/word1222.html">&#x30B9;&#x30B3;&#x30FC;&#x30D7; (scope)&#x3068;&#x306F;&#xFF5C;&#x300C;&#x5206;&#x304B;&#x308A;&#x305D;&#x3046;&#x300D;&#x3067;&#x300C;&#x5206;&#x304B;&#x3089;&#x306A;&#x3044;&#x300D;&#x3067;&#x3082;&#x300C;&#x5206;&#x304B;&#x3063;&#x305F;&#x300D;&#x6C17;&#x306B;&#x306A;&#x308C;&#x308B;IT&#x7528;&#x8A9E;&#x8F9E;&#x5178;</a></p><p> a = 20と書かれている部分のaはグローバル変数、関数内でa = 10と書かれている部分のaは別物です。前者がある位置をグローバルスコープ、後者がある位置を関数のスコープと言ったりします。この違いがあるからこそ、外の名前空間に悪影響を及ぼさないようにしつつ自由な処理を関数で行うことができるわけです。</p><p><b>ローカル変数とグローバル変数の動作の例</b></p> <pre class="code lang-python" data-lang="python" data-unlink>a = <span class="synConstant">20</span> <span class="synStatement">def</span> <span class="synIdentifier">printa</span>(): a = <span class="synConstant">10</span> <span class="synIdentifier">print</span>(a) <span class="synIdentifier">print</span>(a) <span class="synComment"># 20</span> printa() <span class="synComment"># 10</span> <span class="synIdentifier">print</span>(a) <span class="synComment"># 20</span> </pre><p> もちろん関数内のスコープから外のスコープの変数が見えないという訳ではなく、通常同名のローカル変数がなければ外側のスコープから変数が探索されます。</p> <pre class="code lang-python" data-lang="python" data-unlink>a = <span class="synConstant">20</span> <span class="synStatement">def</span> <span class="synIdentifier">printa</span>(): <span class="synIdentifier">print</span>(a) printa() <span class="synComment"># 20</span> </pre><p> ただし、同名のローカル変数がなければ、というのが曲者で、ローカル変数があるかどうかは「構文から静的に決定される」ことになります。具体的にいうと、関数の中でその変数に対して代入文があれば、それはローカル変数とみなされます。なお、+=などの累積代入文なども代入文とみなされます。</p><p> どのタイミングで代入文が実行されるのかは考慮されません。一度ローカル変数とみなされてしまえば、最初から最後までローカル変数です。</p><p><b>なんとなく動きそうだけど実際は駄目なコード</b></p> <pre class="code lang-python" data-lang="python" data-unlink>a = <span class="synConstant">30</span> <span class="synComment"># 関数の中とは無関係</span> <span class="synStatement">def</span> <span class="synIdentifier">f</span>(): <span class="synIdentifier">print</span>(a) <span class="synComment"># ここで30になったりはしない(この時点でエラー)</span> a = <span class="synConstant">40</span> <span class="synComment"># これがある限りaはローカル変数</span> <span class="synIdentifier">print</span>(a) <span class="synComment"># 40が出たりはしない()</span> f() </pre><p> 繰り返しますが、「関数の中でその変数に対して代入したらローカル変数」「ローカル変数に決まったとしても、その変数が参照されたタイミングで変数が実在している保障はない(むしろない可能性だって十分にある)」ということです。気をつけましょう。</p> </div> <div class="section"> <h3>対処法</h3> <p> ローカル変数なんて面倒くさいものは使わなくていい、という場合はglobal宣言を使うことが出来ます。当たり前ですが、こうしてしまうとグローバル変数を直接扱うのと同じことになり、呼び出しで副作用が発生します。</p> <pre class="code lang-python" data-lang="python" data-unlink>a = <span class="synConstant">20</span> <span class="synStatement">def</span> <span class="synIdentifier">f</span>(): <span class="synStatement">global</span> a <span class="synIdentifier">print</span>(a) a = <span class="synConstant">10</span> <span class="synIdentifier">print</span>(a) <span class="synComment"># 20</span> f() <span class="synComment"># 20</span> <span class="synIdentifier">print</span>(a) <span class="synComment"># 10</span> </pre><p> ローカル変数でいいのだが外部の情報を使いたい、という場合は、引数で値を渡す設計にするべきでしょう。</p> <pre class="code lang-python" data-lang="python" data-unlink>a = <span class="synConstant">20</span> <span class="synStatement">def</span> <span class="synIdentifier">f</span>(a): <span class="synIdentifier">print</span>(a) a = <span class="synConstant">10</span> <span class="synIdentifier">print</span>(a) <span class="synIdentifier">print</span>(a) <span class="synComment"># 20</span> f(a) <span class="synComment"># 20</span> <span class="synComment"># 10 </span> <span class="synIdentifier">print</span>(a) <span class="synComment"># 20</span> </pre><p> また、この記事でこれまで書いてきた解説とはあまり関係ありませんが、通常のNameErrorと同様のシチュエーションでもUnboundLocalErrorになることがあります。条件分岐が絡んだりすると、起こりがちです。</p><p> たとえばこういうコードは通常NameErrorになります。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">if</span> <span class="synIdentifier">False</span>: <span class="synComment"># 実際にはTrueにもFalseにもなり得る条件。エラーになるのはFalseになったとき</span> a = <span class="synConstant">10</span> <span class="synIdentifier">print</span>(a) <span class="synComment"># NameError: name 'a' is not defined</span> </pre><p> 関数の中で同様のことをやるとUnboundLocalErrorです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">f</span>(): <span class="synStatement">if</span> <span class="synIdentifier">False</span>: a = <span class="synConstant">10</span> <span class="synIdentifier">print</span>(a) <span class="synComment"># UnboundLocalError: local variable 'a' referenced before assignment</span> f() </pre><p> この場合の対処法はNameErrorに準じます。if文の上でaに初期値を代入しておくか、else節を設けてaに適切な値を代入するかで解決します。<br /> <br /> </p> </div> <div class="section"> <h3>参考文献</h3> <p> 公式のFAQの関連項目がとてもわかりやすいので、こちらにも目を通しておいてください。<br /> <a href="https://docs.python.org/ja/3/faq/programming.html#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value">&#x306A;&#x305C;&#x5909;&#x6570;&#x306B;&#x5024;&#x304C;&#x3042;&#x308B;&#x306E;&#x306B; UnboundLocalError &#x304C;&#x51FA;&#x308B;&#x306E;&#x3067;&#x3059;&#x304B;&#xFF1F; | &#x30D7;&#x30ED;&#x30B0;&#x30E9;&#x30DF;&#x30F3;&#x30B0; FAQ &mdash; Python 3.8.0 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8; </a><br /> <a href="https://docs.python.org/ja/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python">Python &#x306E;&#x30ED;&#x30FC;&#x30AB;&#x30EB;&#x3068;&#x30B0;&#x30ED;&#x30FC;&#x30D0;&#x30EB;&#x5909;&#x6570;&#x306E;&#x30EB;&#x30FC;&#x30EB;&#x306F;&#x4F55;&#x3067;&#x3059;&#x304B;&#xFF1F; | &#x30D7;&#x30ED;&#x30B0;&#x30E9;&#x30DF;&#x30F3;&#x30B0; FAQ &mdash; Python 3.8.0 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a></p> </div> hayataka2049 scikit-learnで重み付きk近傍法(Weighted kNN)を試してみる hatenablog://entry/26006613467593911 2019-11-18T17:14:08+09:00 2019-11-18T17:14:08+09:00 はじめに k近傍法には、近傍点の重み付けをどうするかで複数のやり方が考えられます。普通のk近傍点では予測対象の点のkつの近傍点を取ってきて、そのクラスを単純に多数決します。一方で、より近い点にはより大きい重みを持たせるという発想もまた自然です。 という訳で、scikit-learnのKNeighborsClassifierはweightsオプションを指定することで重み付けを加味したKNNモデルを作成できます。これはとても簡単なので、やってみようと思います。sklearn.neighbors.KNeighborsClassifier — scikit-learn 0.21.3 documenta… <div class="section"> <h3>はじめに</h3> <p> k近傍法には、近傍点の重み付けをどうするかで複数のやり方が考えられます。普通のk近傍点では予測対象の点のkつの近傍点を取ってきて、そのクラスを単純に多数決します。一方で、より近い点にはより大きい重みを持たせるという発想もまた自然です。</p><p> という訳で、scikit-learnのKNeighborsClassifierはweightsオプションを指定することで重み付けを加味したKNNモデルを作成できます。これはとても簡単なので、やってみようと思います。</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html">sklearn.neighbors.KNeighborsClassifier &mdash; scikit-learn 0.21.3 documentation</a><br /> </p> </div> <div class="section"> <h3>しくみとやること</h3> <p> weightsオプションには"uniform"と"distance"、または任意のcallableを渡せます。"uniform"は重みなし(普通のkNN、デフォルト)、"distance"は距離の逆数を重みにします。callableを渡す場合、距離行列を引数に取り、重み行列を返すようなcallableでなければなりません。たとえば、lambda x:1/xを指定することは(おそらく)"distance"を指定することと同じ意味を持ちます。</p><p> 今回はk=3, 5, 10の条件で、デフォルト(指定なし:"uniform")と"distance"を比べてみます。二次元の2クラスのデータに対して予測させることで、そのまま可視化します。</p> </div> <div class="section"> <h3>コード</h3> <p> 以前やった実験のコードを書き換えて使用しました。</p><p><a href="https://www.haya-programming.com/entry/2019/05/26/103518">&#x541B;&#x306F;KNN&#xFF08;k nearest neighbor&#xFF09;&#x306E;&#x672C;&#x5F53;&#x306E;&#x3059;&#x3054;&#x3055;&#x3092;&#x77E5;&#x3089;&#x306A;&#x3044; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><p> そもそものベースになっているのは、scikit-learnが公式で出している分類器の比較のためのコードです。下記ページで雰囲気を掴んでおくと読みやすいと思います。</p><p><a href="https://scikit-learn.org/stable/auto_examples/classification/plot_classifier_comparison.html">Classifier comparison &mdash; scikit-learn 0.21.3 documentation</a></p><p> 今回の実験だとサンプル数が少ないほうが傾向がわかりやすいので、サンプル数50でやっています。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> matplotlib.colors <span class="synPreProc">import</span> ListedColormap <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> make_moons, make_circles,<span class="synSpecial">\</span> make_classification <span class="synPreProc">from</span> sklearn.neighbors <span class="synPreProc">import</span> KNeighborsClassifier <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): knn3 = KNeighborsClassifier(n_neighbors=<span class="synConstant">3</span>) knn3w = KNeighborsClassifier(n_neighbors=<span class="synConstant">3</span>, weights=<span class="synConstant">&quot;distance&quot;</span>) knn5 = KNeighborsClassifier(n_neighbors=<span class="synConstant">5</span>) knn5w = KNeighborsClassifier(n_neighbors=<span class="synConstant">5</span>, weights=<span class="synConstant">&quot;distance&quot;</span>) knn10 = KNeighborsClassifier(n_neighbors=<span class="synConstant">10</span>) knn10w = KNeighborsClassifier(n_neighbors=<span class="synConstant">10</span>, weights=<span class="synConstant">&quot;distance&quot;</span>) X, y = make_classification( n_samples=<span class="synConstant">50</span>, n_features=<span class="synConstant">2</span>, n_redundant=<span class="synConstant">0</span>, n_informative=<span class="synConstant">2</span>, random_state=<span class="synConstant">1</span>, n_clusters_per_class=<span class="synConstant">1</span>) rng = np.random.RandomState(<span class="synConstant">2</span>) X += <span class="synConstant">2</span> * rng.uniform(size=X.shape) linearly_separable = (X, y) datasets = [make_moons(n_samples=<span class="synConstant">50</span>, noise=<span class="synConstant">0.3</span>, random_state=<span class="synConstant">0</span>), make_circles( n_samples=<span class="synConstant">50</span>, noise=<span class="synConstant">0.2</span>, factor=<span class="synConstant">0.5</span>, random_state=<span class="synConstant">1</span>), linearly_separable] fig, axes = plt.subplots( nrows=<span class="synConstant">3</span>, ncols=<span class="synConstant">6</span>, figsize=(<span class="synConstant">32</span>, <span class="synConstant">18</span>)) plt.subplots_adjust(wspace=<span class="synConstant">0.2</span>, hspace=<span class="synConstant">0.3</span>) cm = plt.cm.RdBu cm_bright = ListedColormap([<span class="synConstant">'#FF0000'</span>, <span class="synConstant">'#0000FF'</span>]) <span class="synStatement">for</span> i, (X, y) <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>(datasets): x_min = X[:, <span class="synConstant">0</span>].min()-<span class="synConstant">0.5</span> x_max = X[:, <span class="synConstant">0</span>].max()+<span class="synConstant">0.5</span> y_min = X[:, <span class="synConstant">1</span>].min()-<span class="synConstant">0.5</span> y_max = X[:, <span class="synConstant">1</span>].max()+<span class="synConstant">0.5</span> xx, yy = np.meshgrid(np.arange(x_min, x_max, <span class="synConstant">0.1</span>), np.arange(y_min, y_max, <span class="synConstant">0.1</span>)) <span class="synStatement">for</span> j, (cname, clf) <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>( [(<span class="synConstant">&quot;KNN k=3&quot;</span>, knn3), (<span class="synConstant">&quot;KNN k=3 weighted&quot;</span>, knn3w), (<span class="synConstant">&quot;KNN k=5&quot;</span>, knn5), (<span class="synConstant">&quot;KNN k=5 weighted&quot;</span>, knn5w), (<span class="synConstant">&quot;KNN k=10&quot;</span>, knn10), (<span class="synConstant">&quot;KNN k=10 weighted&quot;</span>, knn10w)]): clf.fit(X, y) Z = clf.predict_proba( np.c_[xx.ravel(), yy.ravel()])[:, <span class="synConstant">1</span>] Z = Z.reshape(xx.shape) axes[i,j].contourf(xx, yy, Z, <span class="synConstant">20</span>, cmap=cm, alpha=<span class="synConstant">.8</span>) axes[i,j].scatter(X[:,<span class="synConstant">0</span>], X[:,<span class="synConstant">1</span>], c=y, s=<span class="synConstant">20</span>, cmap=cm_bright, edgecolors=<span class="synConstant">&quot;black&quot;</span>) axes[i,j].set_title(cname) plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>, bbox_inches=<span class="synConstant">&quot;tight&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre> </div> <div class="section"> <h3>結果</h3> <p> 結果はこんな感じになりました。</p><p><figure class="figure-image figure-image-fotolife" title="result.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20191118/20191118170348.png" alt="result.png" title="f:id:hayataka2049:20191118170348p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result.png</figcaption></figure></p><p> 全体の傾向はさほど変わりませんが、</p> <ul> <li>重み付きの方が確率の階段がなめらか</li> <li>ノイズっぽいサンプルに引っ張られる(過学習気味になる)。特に周囲に他のサンプルがないと引っ張られやすい</li> </ul><p> という傾向が見られました。</p> </div> <div class="section"> <h3>考察</h3> <p> 距離の逆数で重みを付けて多数決することで、分離境界が少しなめらかになります。</p><p> また、近くの点をより重視するので、kを小さくしたかのような効果があります。そのままでは過学習っぽくなってしまいますが、kを大きくして使うことで、汎化性能もある程度は確保できます。</p><p> 原理的には、より広い領域の上方をまったく捨てるのではなく多少は活用できることになります。そういうポリシーのもとで使うべきでしょう。</p><p> また、"distance"を指定すると少し重み付けが効きすぎるかな? という気がするので、lambda x:x**(-1/2)を試してみるといった努力の方向性もありだと色々試してみて感じました。</p><p> 以前やったバギングと比べると計算が軽く、境界の確率がなめらかになるという意味では近い効果があります。</p> </div> <div class="section"> <h3>まとめ</h3> <p> ということで気軽に指定できるので、kNNで精度を求める時(なにそれ)は"distance"も試してみる価値はあります。</p> </div> hayataka2049 【python】キーワード引数と可変長キーワード引数(kwargs)の競合によるエラー hatenablog://entry/26006613466555062 2019-11-17T12:55:37+09:00 2019-11-17T12:55:37+09:00 はじめに 既存の関数のwrapperを作るときなど、可変長キーワード引数を使いたいときがあります。 これは通常のキーワード引数と併用できますが、稀に問題になることがあります。 関数定義のとき 定義するときは割と単純で、問題も少ないです。 以下のような例を考えます。 >>> def f(a, b=0, **kwargs): ... print(a, b, kwargs) ... a, bをキーワード引数として渡した場合、明示的に定義されている引数が吸い取ります。kwargsには与えられることはありません。 >>> f(a=10, b=20, c=30) 10 20 {'c': 30} 逆に言うと… <div class="section"> <h3>はじめに</h3> <p> 既存の関数のwrapperを作るときなど、可変長キーワード引数を使いたいときがあります。</p><p> これは通常のキーワード引数と併用できますが、稀に問題になることがあります。</p> </div> <div class="section"> <h3>関数定義のとき</h3> <p> 定義するときは割と単純で、問題も少ないです。</p><p> 以下のような例を考えます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synStatement">def</span> <span class="synIdentifier">f</span>(a, b=<span class="synConstant">0</span>, **kwargs): ... <span class="synIdentifier">print</span>(a, b, kwargs) ... </pre><p> a, bをキーワード引数として渡した場合、明示的に定義されている引数が吸い取ります。kwargsには与えられることはありません。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; f(a=<span class="synConstant">10</span>, b=<span class="synConstant">20</span>, c=<span class="synConstant">30</span>) <span class="synConstant">10</span> <span class="synConstant">20</span> {<span class="synConstant">'c'</span>: <span class="synConstant">30</span>} </pre><p> 逆に言うと明示的に定義されていなければキーワード引数として渡された引数はkwargsに入るので、この声質をうまく利用すると関数のwrapperが書きやすくなるときがあります。</p> </div> <div class="section"> <h3>アンパックで呼び出すとき</h3> <p> 辞書のアンパック展開によって引数を渡す場合、いささか問題を含んでいます。</p><p> 端的に言うと、辞書のキーと他の引数とが名前が被るとエラーになります。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; kwargs = {<span class="synConstant">&quot;a&quot;</span>:<span class="synConstant">20</span>, <span class="synConstant">&quot;c&quot;</span>:<span class="synConstant">100</span>} &gt;&gt;&gt; f(a=<span class="synConstant">10</span>, **kwargs) <span class="synComment"># aの値として優先したいのは10</span> Traceback (most recent call last): File <span class="synConstant">&quot;&lt;stdin&gt;&quot;</span>, line <span class="synConstant">1</span>, <span class="synStatement">in</span> &lt;module&gt; <span class="synType">TypeError</span>: f() got multiple values <span class="synStatement">for</span> keyword argument <span class="synConstant">'a'</span> </pre><p> こういう事態を避けるのは一見簡単そうな気もしますが、</p> <ul> <li>任意のkwargs(どこかからやってきた)でとりあえず無事に渡せるようにしておきたい</li> <li>引数aは(今この場所で)値を指定して使いたい</li> </ul><p> というようなときは意外と困難があります。</p><p> こういうときは、kwargsを渡さない訳にはいかないですから、aを指定するのをやめてあげます。代わりにkwargsの中に新しくaを入れます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; kwargs = {<span class="synConstant">&quot;a&quot;</span>:<span class="synConstant">20</span>, <span class="synConstant">&quot;c&quot;</span>:<span class="synConstant">100</span>} &gt;&gt;&gt; kwargs_copy = kwargs.copy() <span class="synComment"># 変更するのでコピーに対して操作する</span> &gt;&gt;&gt; kwargs_copy[<span class="synConstant">&quot;a&quot;</span>] = <span class="synConstant">10</span> &gt;&gt;&gt; f(**kwargs_copy) <span class="synConstant">10</span> <span class="synConstant">0</span> {<span class="synConstant">'c'</span>: <span class="synConstant">100</span>} </pre><p> たぶんこれでいいでしょう。</p> </div> <div class="section"> <h3>まとめ</h3> <p> ということで、Pythonの引数周りは意外と面倒くさいんです。柔軟といえば柔軟ですが、破綻すれすれな感じもする。</p> </div> <div class="section"> <h3>参考文献</h3> <p> 公式<br /> <a href="https://docs.python.org/ja/3/reference/expressions.html#calls">6. &#x5F0F; (expression) &mdash; Python 3.8.0 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a><br /> <a href="https://docs.python.org/ja/3/reference/compound_stmts.html#function-definitions">8. &#x8907;&#x5408;&#x6587; (compound statement) &mdash; Python 3.8.0 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a></p> </div> hayataka2049 【python】seabornで棒グラフを信頼区間付きで描く hatenablog://entry/26006613464535857 2019-11-15T20:53:15+09:00 2019-11-15T20:53:15+09:00 はじめに 信頼区間付きの棒グラフはよく見かけます。複数グループの差が優位かどうかという議論に向いているからです。 他のツールだと割と簡単に描けるグラフだったりするのですが、Pythonでやろうとするとmatplotlibは勝手に信頼区間を計算してくれません。信頼区間を計算してからエラーバーを出して……といったノウハウが紹介されていますが、正直面倒くさいです。qiita.com そこでseabornを使います。こちらは何もしなくても信頼区間を出してくれるタイプのライブラリです。ただし、使い勝手には癖があります。 リファレンス通りに使ってみる 凝ったことをする意味はあまりないので、まずはリファレン… <div class="section"> <h3>はじめに</h3> <p> 信頼区間付きの棒グラフはよく見かけます。複数グループの差が優位かどうかという議論に向いているからです。</p><p> 他のツールだと割と簡単に描けるグラフだったりするのですが、Pythonでやろうとするとmatplotlibは勝手に信頼区間を計算してくれません。信頼区間を計算してからエラーバーを出して……といったノウハウが紹介されていますが、正直面倒くさいです。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fmytk0u0%2Fitems%2F5b87a17d48ff344e333d" title="Python/Matplotlibで両側95%信頼区間を図に加える - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/mytk0u0/items/5b87a17d48ff344e333d">qiita.com</a></cite></p><p> そこでseabornを使います。こちらは何もしなくても信頼区間を出してくれるタイプのライブラリです。ただし、使い勝手には癖があります。</p> </div> <div class="section"> <h3>リファレンス通りに使ってみる</h3> <p> 凝ったことをする意味はあまりないので、まずはリファレンス通りに使います。</p><p><a href="https://seaborn.pydata.org/generated/seaborn.barplot.html">seaborn.barplot &mdash; seaborn 0.9.0 documentation</a></p><p> リファレンスの記述を多少補ったのが以下のコードです。tipsデータセットでやっているようです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> seaborn <span class="synStatement">as</span> sns <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt tips = sns.load_dataset(<span class="synConstant">&quot;tips&quot;</span>) <span class="synIdentifier">print</span>(tips.head()) <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant"> total_bill tip sex smoker day time size</span> <span class="synConstant">0 16.99 1.01 Female No Sun Dinner 2</span> <span class="synConstant">1 10.34 1.66 Male No Sun Dinner 3</span> <span class="synConstant">2 21.01 3.50 Male No Sun Dinner 3</span> <span class="synConstant">3 23.68 3.31 Male No Sun Dinner 2</span> <span class="synConstant">4 24.59 3.61 Female No Sun Dinner 4</span> <span class="synConstant">&quot;&quot;&quot;</span> sns.barplot(x=<span class="synConstant">&quot;day&quot;</span>, y=<span class="synConstant">&quot;total_bill&quot;</span>, data=tips) plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="result.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20191111/20191111183703.png" alt="result.png" title="f:id:hayataka2049:20191111183703p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result.png</figcaption></figure></p><p> 折れ線グラフのときと使い方はほとんど変わりません。というか、見え方が違うだけのようにも見えます(折れ線の方だとx軸の位置は指定できますが)。</p><p><a href="https://www.haya-programming.com/entry/2019/11/08/185328">&#x3010;python&#x3011;seaborn&#x3067;&#x6298;&#x308C;&#x7DDA;&#x30B0;&#x30E9;&#x30D5;&#x3092;&#x4FE1;&#x983C;&#x533A;&#x9593;&#x4ED8;&#x304D;&#x3067;&#x63CF;&#x304F; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><p> なお、エラーバーの形式はいじれます。普通は傘がほしいと思うので、capsizeを指定します。また、デフォルトでは少しエラーバーが濃すぎる気がするので、errwidthで調整するとよさげです。</p><p> 適当に調整したのが下のものです。</p> <pre class="code lang-python" data-lang="python" data-unlink>sns.barplot(x=<span class="synConstant">&quot;day&quot;</span>, y=<span class="synConstant">&quot;total_bill&quot;</span>, data=tips, capsize=<span class="synConstant">0.1</span>, errwidth=<span class="synConstant">1.2</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="result.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20191115/20191115201058.png" alt="result.png" title="f:id:hayataka2049:20191115201058p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result.png</figcaption></figure></p> </div> <div class="section"> <h3>もともとDataFrameになっていない任意のデータで行う</h3> <p> 例によって例のごとく、seabornの開発方針はpandasべったりなので、どんな形式・内容のデータであっても一回DataFrameに突っ込んでからやった方が良さそうです。ただ、受け付けてくれる形式が少し特殊なので、変換方法を覚えておいた方が得です。</p><p> 普通のデータはこんな感じで定義したいことが多いと思います。棒ごとに配列があるイメージですね。</p> <pre class="code lang-python" data-lang="python" data-unlink>df = pd.DataFrame({<span class="synConstant">&quot;A&quot;</span>: [<span class="synConstant">11</span>, <span class="synConstant">12</span>, <span class="synConstant">11</span>, <span class="synConstant">10</span>, <span class="synConstant">10</span>, <span class="synConstant">13</span>, <span class="synConstant">11</span>, <span class="synConstant">10</span>, <span class="synConstant">15</span>, <span class="synConstant">11</span>, <span class="synConstant">10</span>], <span class="synConstant">&quot;B&quot;</span>: [<span class="synConstant">20</span>, <span class="synConstant">21</span>, <span class="synConstant">20</span>, <span class="synConstant">21</span>, <span class="synConstant">20</span>, <span class="synConstant">21</span>, <span class="synConstant">20</span>, <span class="synConstant">21</span>, <span class="synConstant">20</span>, <span class="synConstant">21</span>, <span class="synConstant">20</span>]}) </pre><p> meltを使って縦持ちに変換すると、seabornと親和性の高いフォーマットになります。結論から言うと、こう書くと良いでしょう。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> seaborn <span class="synStatement">as</span> sns <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt df = pd.DataFrame({<span class="synConstant">&quot;A&quot;</span>: [<span class="synConstant">11</span>, <span class="synConstant">12</span>, <span class="synConstant">11</span>, <span class="synConstant">10</span>, <span class="synConstant">10</span>, <span class="synConstant">13</span>, <span class="synConstant">11</span>, <span class="synConstant">10</span>, <span class="synConstant">15</span>, <span class="synConstant">11</span>, <span class="synConstant">10</span>], <span class="synConstant">&quot;B&quot;</span>: [<span class="synConstant">20</span>, <span class="synConstant">21</span>, <span class="synConstant">20</span>, <span class="synConstant">21</span>, <span class="synConstant">20</span>, <span class="synConstant">21</span>, <span class="synConstant">20</span>, <span class="synConstant">21</span>, <span class="synConstant">20</span>, <span class="synConstant">21</span>, <span class="synConstant">20</span>]}) sns.barplot(x=<span class="synConstant">&quot;class&quot;</span>, y=<span class="synConstant">&quot;data&quot;</span>, data=df.melt(var_name=<span class="synConstant">&quot;class&quot;</span>, value_name=<span class="synConstant">&quot;data&quot;</span>), capsize=<span class="synConstant">0.1</span>, errwidth=<span class="synConstant">1.2</span>) plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>) </pre><p> これでちゃんとABごとに集計してくれます。ちなみにmeltした結果はこうなります。</p> <pre class="code" data-lang="" data-unlink> class data 0 A 11 1 A 12 2 A 11 3 A 10 4 A 10 5 A 13 6 A 11 7 A 10 8 A 15 9 A 11 10 A 10 11 B 20 12 B 21 13 B 20 14 B 21 15 B 20 16 B 21 17 B 20 18 B 21 19 B 20 20 B 21 21 B 20</pre><p><a href="https://pandas.pydata.org/pandas-docs/version/0.23.4/generated/pandas.DataFrame.melt.html">pandas.DataFrame.melt &mdash; pandas 0.23.4 documentation</a><br /> </p> </div> <div class="section"> <h3>まとめ</h3> <p> データを縦持ちに変換するところでひと手間余計にかかりますが、matplotlibでゼロからやるよりは楽だし、間違いも少なさそうなので良いと思います。</p> </div> hayataka2049 Python対話的インタプリタでアンダースコアが便利(誰も知らない機能) hatenablog://entry/26006613463023230 2019-11-08T19:39:40+09:00 2019-11-08T19:39:40+09:00 概要 対話モードだとアンダースコアの変数が自動的にできています。最後に評価した結果が入るようです。 >>> 1 + 2 3 >>> _ 3 これはチュートリアルに書いてあったのですが、他の入門記事で触れられているのを見た記憶はまったくありません。私自身も、チュートリアルの「Pythonを電卓として使う」はなんとなく読み流していたので気づいていませんでした。 対話モードでは、最後に表示された結果は変数 _ に代入されます。このことを利用すると、Python を電卓として使うときに、計算を連続して行う作業が多少楽になります。 3. 形式ばらない Python の紹介 — Python 3.8.0 … <div class="section"> <h3>概要</h3> <p> 対話モードだとアンダースコアの変数が自動的にできています。最後に評価した結果が入るようです。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synConstant">1</span> + <span class="synConstant">2</span> <span class="synConstant">3</span> &gt;&gt;&gt; _ <span class="synConstant">3</span> </pre><p> これはチュートリアルに書いてあったのですが、他の入門記事で触れられているのを見た記憶はまったくありません。私自身も、チュートリアルの「Pythonを電卓として使う」はなんとなく読み流していたので気づいていませんでした。</p> <blockquote> <p>対話モードでは、最後に表示された結果は変数 _ に代入されます。このことを利用すると、Python を電卓として使うときに、計算を連続して行う作業が多少楽になります。<br /> <a href="https://docs.python.org/ja/3/tutorial/introduction.html">3. &#x5F62;&#x5F0F;&#x3070;&#x3089;&#x306A;&#x3044; Python &#x306E;&#x7D39;&#x4ECB; &mdash; Python 3.8.0 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a></p> </blockquote> <p> 面白いですね。</p> </div> <div class="section"> <h3>性質を調べる</h3> <p> あくまでも「最後に評価された式の値」を保持するものなので、標準出力に吐いた結果は反映されません。また、関数呼び出しでも結果は反映されますが、返り値のない関数を呼び出してもNoneになることはないようです。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synConstant">4</span> + <span class="synConstant">5</span> <span class="synConstant">9</span> &gt;&gt;&gt; _ <span class="synConstant">9</span> &gt;&gt;&gt; <span class="synIdentifier">pow</span>(<span class="synConstant">4</span>, <span class="synConstant">2</span>) <span class="synConstant">16</span> &gt;&gt;&gt; _ <span class="synConstant">16</span> &gt;&gt;&gt; <span class="synIdentifier">print</span>(<span class="synConstant">&quot;hoge&quot;</span>, <span class="synConstant">&quot;fuga&quot;</span>) hoge fuga &gt;&gt;&gt; _ <span class="synConstant">16</span> </pre> </div> <div class="section"> <h3>ドキュメントを調べる</h3> <p> こんな簡単なものなのですが、ちゃんとしたドキュメントの記述を探そうとすると検索性が悪くて一苦労です。けっきょく正攻法で探すのは諦め、たぶん英語版スタックオーバーフローあたりの質問が出てくるだろうと思って「python interactive shell underscore」でgoogle検索したら、案の定出てきました。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fstackoverflow.com%2Fquestions%2F1538832%2Fis-the-single-underscore-a-built-in-variable-in-python" title="Is the single underscore &quot;_&quot; a built-in variable in Python?" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://stackoverflow.com/questions/1538832/is-the-single-underscore-a-built-in-variable-in-python">stackoverflow.com</a></cite></p><p> sys.displayhookというのがそれに該当する関数だそうです。</p><p><a href="https://docs.python.org/ja/3/library/sys.html#sys.displayhook">sys --- &#x30B7;&#x30B9;&#x30C6;&#x30E0;&#x30D1;&#x30E9;&#x30E1;&#x30FC;&#x30BF;&#x3068;&#x95A2;&#x6570; &mdash; Python 3.8.0 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a></p><p> 性質はなかなか面白そうですが、まるごと引用するには少し長かったのでドキュメントをじっくり読んでください。</p> </div> <div class="section"> <h3>Jupyterでもできる</h3> <p> あいつも対話環境の端くれならできるかもしれないと思って試したら、できました。</p><p><figure class="figure-image figure-image-fotolife" title="やってみた(ブラウザの幅をとてもせまくしています)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20191108/20191108193649.png" alt="&#x3084;&#x3063;&#x3066;&#x307F;&#x305F;&#xFF08;&#x30D6;&#x30E9;&#x30A6;&#x30B6;&#x306E;&#x5E45;&#x3092;&#x3068;&#x3066;&#x3082;&#x305B;&#x307E;&#x304F;&#x3057;&#x3066;&#x3044;&#x307E;&#x3059;&#xFF09;" title="f:id:hayataka2049:20191108193649p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>やってみた(ブラウザの幅をとてもせまくしています)</figcaption></figure></p><p> 正確な挙動は確認していませんが、試した範囲ではセルの評価が終わった瞬間の結果が代入されているようです。一つのセルで、</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">10</span> + <span class="synConstant">20</span> _ </pre><p> とやっても望む結果は得られないということです。</p> </div> <div class="section"> <h3>まとめ</h3> <p> 割と便利。たのしい。</p> </div> hayataka2049 【python】seabornで折れ線グラフを信頼区間付きで描く hatenablog://entry/26006613463001650 2019-11-08T18:53:28+09:00 2019-11-15T20:53:41+09:00 はじめに 信頼区間というとなんとなく棒グラフにつけるものという印象がありますが、折れ線グラフでも計算すること自体はたやすくて、しかもけっこうかっこいいグラフになります。 大抵は別に無くても良いのですが、たまに信頼区間が出ていると便利なときがあります(有意な差があるかどうかを視覚的に示せます)。 seabornでできるのでやってみます。seaborn.lineplot — seaborn 0.9.0 documentation DataFrameを入力して使う リファレンスではこの方法が推されています。DataFrameがあるのであれば簡単にできます。 公式の例をパクってきたような感じの実行例を… <div class="section"> <h3>はじめに</h3> <p> 信頼区間というとなんとなく棒グラフにつけるものという印象がありますが、折れ線グラフでも計算すること自体はたやすくて、しかもけっこうかっこいいグラフになります。</p><p> 大抵は別に無くても良いのですが、たまに信頼区間が出ていると便利なときがあります(有意な差があるかどうかを視覚的に示せます)。</p><p> seabornでできるのでやってみます。</p><p><a href="https://seaborn.pydata.org/generated/seaborn.lineplot.html">seaborn.lineplot &mdash; seaborn 0.9.0 documentation</a></p><p> </p> </div> <div class="section"> <h3>DataFrameを入力して使う</h3> <p> リファレンスではこの方法が推されています。DataFrameがあるのであれば簡単にできます。</p><p> 公式の例をパクってきたような感じの実行例を以下に示します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> seaborn <span class="synStatement">as</span> sns <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt fmri = sns.load_dataset(<span class="synConstant">&quot;fmri&quot;</span>) <span class="synIdentifier">print</span>(fmri.head()) <span class="synComment"># どんなデータなのか見ておく</span> <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant"> subject timepoint event region signal</span> <span class="synConstant">0 s13 18 stim parietal -0.017552</span> <span class="synConstant">1 s5 14 stim parietal -0.080883</span> <span class="synConstant">2 s12 18 stim parietal -0.081033</span> <span class="synConstant">3 s11 18 stim parietal -0.046134</span> <span class="synConstant">4 s10 18 stim parietal -0.037970</span> <span class="synConstant">&quot;&quot;&quot;</span> sns.lineplot(x=<span class="synConstant">&quot;timepoint&quot;</span>, y=<span class="synConstant">&quot;signal&quot;</span>, data=fmri) plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="result.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20191108/20191108183316.png" alt="result.png" title="f:id:hayataka2049:20191108183316p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result.png</figcaption></figure></p><p> 何をやっているのかと言うと、timepointで集計してsignalの信頼区間(デフォルトは95%)を計算しています。たったこれだけの指定でこれだけのことをしてくれるので、便利なものです。</p><p> 裏を返せば、同じx軸の値に対して対応するy軸の値が複数ないと信頼区間は出ません(当たり前のことですが)。</p> </div> <div class="section"> <h3>面倒くさいのでただの配列で計算できるようにする</h3> <p> seabornのリファレンスではDataFrameを入力にして使う方法がやたら推されていますが、私はpandasは扱うのが面倒くさいので、numpy配列ですませたいと思うタイプの人間です。</p><p> その場合のやり方ですが、まず上に挙げた例は、以下のように書いても同じ結果が得られます。</p> <pre class="code lang-python" data-lang="python" data-unlink>sns.lineplot(x=fmri[<span class="synConstant">&quot;timepoint&quot;</span>], y=fmri[<span class="synConstant">&quot;signal&quot;</span>]) </pre><p> 性質はよくわかったので、numpyで作ったサイン波でやってみます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> seaborn <span class="synStatement">as</span> sns <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt x = np.repeat(np.linspace(<span class="synConstant">0</span>, <span class="synConstant">10</span>, <span class="synConstant">20</span>), <span class="synConstant">20</span>) <span class="synComment"># たとえばnp.repeat(np.arange(5), 2)は</span> <span class="synComment"># array([0, 0, 1, 1, 2, 2, 3, 3, 4, 4])になる性質があります</span> y = np.sin(x) + np.random.normal(size=x.shape) sns.lineplot(x=x, y=y) plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="result.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20191108/20191108185149.png" alt="result.png" title="f:id:hayataka2049:20191108185149p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result.png</figcaption></figure></p><p> 微妙な使い勝手ですが(どちらかといえば外側でgroupbyした結果で渡したいんだけどな、とか)、使えるには使えます。</p><p> ちなみに上のグラフを見て気づいた方もいるかと思いますが、基本的にはxの値でグルーピングしたあとそれぞれのxに対応する信頼区間を計算しているだけのようです。点の信頼区間同士も線形に折れ線で繋がれます(ので、点と点の間で妥当なことになっている保障はないと考えた方が無難です)。</p> </div> <div class="section"> <h3>まとめ</h3> <p> 折れ線グラフで信頼区間を議論することはあまりないかもしれませんが、それでもたま~に便利なときがあるので、覚えておいて損はないです。</p> </div> hayataka2049 ランダムフォレストを使うなら変数選択はしなくてもいいのか? hatenablog://entry/26006613461156163 2019-11-07T20:20:27+09:00 2019-11-07T20:21:17+09:00 はじめに 表題の通りの話をたまに聞きます。「ランダムフォレストは内部で変数選択を行う。なので変数選択は必要ない」という主張です。 しかし個人的には、それはあくまでも 他の手法*1と比べれば変数選択しなかった場合の悪影響が少ない ということであって、ランダムフォレストであっても変数選択した方が良いんじゃ? ということを昔からずっと思っていました。 検証してみます。 思考実験 実際に検証する前に思考実験を行います。 まずパターンA(変数選択なし)とパターンB(変数選択あり)の2通りを考えます。 パターンA 有効な変数:10個 無効な変数:90個 パターンB 有効な変数:10個 のみ(無効な変数なし… <div class="section"> <h3>はじめに</h3> <p> 表題の通りの話をたまに聞きます。「ランダムフォレストは内部で変数選択を行う。なので変数選択は必要ない」という主張です。</p><p> しかし個人的には、それはあくまでも</p> <ul> <li>他の手法<a href="#f-fb887cab" name="fn-fb887cab" title="特に個体間の距離や内積に基づくものは変数選択しないとまずいでしょう">*1</a>と比べれば変数選択しなかった場合の悪影響が少ない</li> </ul><p> ということであって、ランダムフォレストであっても変数選択した方が良いんじゃ? ということを昔からずっと思っていました。</p><p> 検証してみます。</p> </div> <div class="section"> <h3>思考実験</h3> <p> 実際に検証する前に思考実験を行います。</p><p> まずパターンA(変数選択なし)とパターンB(変数選択あり)の2通りを考えます。</p> <ul> <li>パターンA</li> </ul><p> 有効な変数:10個<br />  無効な変数:90個</p> <ul> <li>パターンB</li> </ul><p> 有効な変数:10個<br />  のみ(無効な変数なし)</p><p> ランダムフォレストの弱分類器では、元々の変数の数の平方根くらいの数の変数を使うのが一般的です。そうすると、</p> <ul> <li>パターンAの場合</li> </ul><p> 弱分類器で使う変数は10個。うち有効なもの(の期待値)は1個。</p> <ul> <li>パターンBの場合</li> </ul><p> 弱分類器で使う変数は3個(切り捨てたとする)。うち有効なものは3個。</p><p> となり、どう見てもパターンBの方が良い結果を生みそうです。期待される結果としては、</p> <ul> <li>各弱分類器の性能が上がることで、全体の性能が向上</li> <li>少ない木の本数で済む</li> <li>過学習しづらくなる</li> </ul><p> などがあり、いいことずくめです。</p> </div> <div class="section"> <h3>実験</h3> <p> データセットや条件を変えてやってみたら、変数選択が効くデータや条件、そうでもないデータや条件がいろいろ出てきてしまいました。</p><p> 要するにケースバイケースなのですが、結果がわかりやすかったやつだけ見せることにします(いい加減)。</p><p> <b>やったこと</b></p> <ul> <li>データセットはscikit-learnのdigits</li> <li>元々の特徴量ベクトルは64次元だが、ノイズを128次元足して192次元にした</li> <li>SelectPercentileで特徴選択して10%ずつ増やしながら見ていく</li> <li>分類器はランダムフォレスト</li> <li>訓練データとテストデータに半分ずつ分けて評価した</li> </ul><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.SelectPercentile.html">sklearn.feature_selection.SelectPercentile &mdash; scikit-learn 0.21.3 documentation</a></p><p> <b>コード</b></p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_digits <span class="synPreProc">from</span> sklearn.model_selection <span class="synPreProc">import</span> train_test_split <span class="synPreProc">from</span> sklearn.feature_selection <span class="synPreProc">import</span> SelectPercentile <span class="synPreProc">from</span> sklearn.ensemble <span class="synPreProc">import</span> RandomForestClassifier <span class="synPreProc">from</span> sklearn.pipeline <span class="synPreProc">import</span> Pipeline <span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> f1_score <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): dataset = load_digits() X, y = dataset.data, dataset.target X = np.hstack([X, np.random.normal(size=(X.shape[<span class="synConstant">0</span>], X.shape[<span class="synConstant">1</span>]*<span class="synConstant">2</span>))]) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=<span class="synConstant">0.5</span>, stratify=y, shuffle=<span class="synIdentifier">True</span>, random_state=<span class="synConstant">0</span>) sp = SelectPercentile() rfc = RandomForestClassifier(n_jobs=-<span class="synConstant">1</span>) pl = Pipeline([(<span class="synConstant">&quot;sp&quot;</span>, sp), (<span class="synConstant">&quot;rf&quot;</span>, rfc)]) fig, ax = plt.subplots() ps = [<span class="synConstant">10</span>*x <span class="synStatement">for</span> x <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">1</span>, <span class="synConstant">11</span>)] <span class="synStatement">for</span> n_estimators <span class="synStatement">in</span> [<span class="synConstant">50</span>, <span class="synConstant">100</span>, <span class="synConstant">150</span>, <span class="synConstant">200</span>]: f1s = [] <span class="synStatement">for</span> p <span class="synStatement">in</span> ps: pl.set_params(sp__percentile=p, rf__n_estimators=n_estimators) pl.fit(X_train, y_train) y_pred = pl.predict(X_test) f1s.append(f1_score(y_test, y_pred, average=<span class="synConstant">&quot;macro&quot;</span>)) ax.plot(ps, f1s, label=<span class="synConstant">&quot;RF_n:&quot;</span> + <span class="synIdentifier">str</span>(n_estimators)) plt.legend() plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> 結果です。</p><p><figure class="figure-image figure-image-fotolife" title="result.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20191107/20191107201147.png" alt="result.png" title="f:id:hayataka2049:20191107201147p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result.png</figcaption></figure></p><p> ほら、変数選択した方が結果が良い。一番良いのは30%のときなので、57個くらいの変数が使われたことになります。</p> </div> <div class="section"> <h3>考察</h3> <p> 思考実験でも示した通り、理屈の上ではやったほうが良いのですが、これを実験で示そうとするとけっこう難しかったというのが率直な感想です。</p><p> 上述の通り、ここに載せていないものも色々試したのですが、まず「明らかに無意味なノイズ特徴量」をわざと入れるくらいの人工的な条件でないと、特徴選択しない方が悪いという結果はほとんど得ることができませんでした。</p><p> ランダムフォレストを使っていると「変数選択しなくても(大真面目に変数選択してちゃんとチューニングしたときに得られる)最高性能に近い」ということはありがちで、「たとえ統計的に有意な関係がなくても、目的変数と何らかの形で関係がある(気がする)」、くらいの説明変数(特徴量)は入れておいてもそうそう悪さはしません。</p><p> 適当に変数を取っているのでゴミがたくさん入っている、というようなシチュエーションでは威力を発揮することでしょう。</p> </div> <div class="section"> <h3>まとめ</h3> <p> わざとらしい感じはしますが、一応ランダムフォレストでも特徴選択した方が良いときはあり得る、ということは示せたのでよしとします。</p> </div><div class="footnote"> <p class="footnote"><a href="#fn-fb887cab" name="f-fb887cab" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">特に個体間の距離や内積に基づくものは変数選択しないとまずいでしょう</span></p> </div> hayataka2049 【python】sklearnのIterativeImputerで欠損値補完 hatenablog://entry/26006613461100523 2019-11-05T22:54:24+09:00 2019-11-05T22:54:24+09:00 注意:IterativeImputerは本記事の執筆時点(2019年11月)で実験的な実装とされており、最新の仕様等はこの記事の内容と異なる可能性があります。常にstable版の公式のドキュメントを確認してください。 公式のドキュメント sklearn.impute.IterativeImputer — scikit-learn 0.21.3 documentation はじめに 説明変数の欠損値補完には様々な方法があり、いろいろな研究がなされています。 直感的に思いつくのは、欠損部分は他の変数から回帰すれば良いという発想です。それを発展させて高級にすると、多重代入法というような手法になったり… <p> 注意:IterativeImputerは本記事の執筆時点(2019年11月)で実験的な実装とされており、最新の仕様等はこの記事の内容と異なる可能性があります。常にstable版の公式のドキュメントを確認してください。</p><p> 公式のドキュメント<br /> <a href="https://scikit-learn.org/stable/modules/generated/sklearn.impute.IterativeImputer.html">sklearn.impute.IterativeImputer &mdash; scikit-learn 0.21.3 documentation</a><br /> </p> <div class="section"> <h3>はじめに</h3> <p> 説明変数の欠損値補完には様々な方法があり、いろいろな研究がなされています。</p><p> 直感的に思いつくのは、欠損部分は他の変数から回帰すれば良いという発想です。それを発展させて高級にすると、多重代入法というような手法になったりするらしいのですが、詳しくないので説明できません。どうせよく知らなくても機械学習モデルは構築できます。</p><p> scikit-learnではIterativeImputerなるモデルを「開発中」です。ここで開発中と書いたのは、公式ですら</p> <blockquote> <p>Note This estimator is still experimental for now: the predictions and the API might change without any deprecation cycle.</p> </blockquote> <p> という扱いになっているからです。「いつの間にか仕様が変わったり、最悪消えたりしてても文句言うなよ!」といったところでしょうか。それでも一応masterブランチにマージされていて、普通にsklearnをインストールすれば(2019年11月時点では)使えます。</p><p> ということで、便利そうなので紹介します。ただし、そこそこ癖があるので気をつけます。</p> </div> <div class="section"> <h3>使い方</h3> <p> まずリファレンスにも書いてあることですが、こいつを使うためには余計なものもimportする必要があります。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> sklearn.experimental <span class="synPreProc">import</span> enable_iterative_imputer <span class="synComment"># 本当におまじない</span> <span class="synPreProc">from</span> sklearn.impute <span class="synPreProc">import</span> IterativeImputer <span class="synComment"># 使うのこっち</span> </pre><p> 使っているlintによっては「imported but unused」とか言われたりしますが、無視してください。</p><p> あとはハイパーパラメータをどうするかですが、あんまり凝ったことはしないのをおすすめします。と言いつつ、何も設定しないで使うのもおすすめしません。</p><p> 重要なパラメータを重要な順番に並べてみます。</p> <ul> <li>estimator=None, </li> </ul><p> 内部で回帰を行って補完するので、回帰モデルを渡せるという訳です。デフォルトはベイジアンリッジ(sklearn.linear_model.BayesianRidge)になります。なんでデフォルトがこれなのかは後述します。</p> <ul> <li>sample_posterior=False</li> </ul><p> そのままだと単一代入法、Trueにすると多重代入法になるのだと思いますが、いかんせん私が知識不足で、またドキュメントの記述がそっけないので細かいアルゴリズムまではよくわかりません。多重代入法の方がかっこよくて性能も良さそうに思えますが、実はその場合は</p> <blockquote> <p> Estimator must support return_std in its predict method if set to True. </p> </blockquote> <p> という制約があり、これは割ときつい制約です(対応しているモデルはほとんどありません)。デフォルトのBayesianRidgeはベイズ法なので予測の標準偏差が出せるということから選定されていると思われます。</p> <ul> <li>max_iter=10</li> <li>tol=0.001</li> </ul><p> 特に説明は要らないと思います。なんか上手く行かないときはこの辺をいじる(増やしてみる)。</p><p> ということで、議論の余地があるのは多重代入法にするのか単一代入法にするのかです。多重代入法の場合、現実的なestimatorの選択肢はたぶんベイジアンリッジだけで、これは「ベイズでリッジだからつよつよ」と見せかけて基本的にただの線形モデルです(回帰式が一次式のやつ)。機械学習に突っ込むようなデータだとこれは微妙なことが多そうなので、多重代入法を諦めて単一代入法にすることで自由に好きな非線形回帰手法を使うということにした方が良いケースが多いと思います。</p><p> あとは何で回帰するか? ですが、ここの回帰モデルのパラメータチューニングのことなんて考えたくもないので、適当な設定でもそこそこ動く奴を使います。と言って真っ先に思いつくのはランダムフォレストですね。</p><p> 以上をまとめると、だいたいこんな感じで定義することになると思います。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> sklearn.ensemble <span class="synPreProc">import</span> RandomForestRegressor <span class="synPreProc">from</span> sklearn.experimental <span class="synPreProc">import</span> enable_iterative_imputer <span class="synPreProc">from</span> sklearn.impute <span class="synPreProc">import</span> IterativeImputer rf = RandomForestRegressor(n_estimators=<span class="synConstant">500</span>, n_jobs=-<span class="synConstant">1</span>) imp = IterativeImputer(estimator=rf) </pre><p> 他のパラメータは気になったら適当に弄ってください。大抵は弄らなくても大勢に影響しません。あと、ランダムフォレストはそこそこ遅いので、気になったら木を浅くして本数を減らすことで高速向きのチューニングにするか、他のものも試してみてください。<br />  </p> </div> <div class="section"> <h3>実験</h3> <p> SimpleImputerのときと同じことを試します。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F10%2F13%2F025136" title="【python】sklearnのSimpleImputerで欠損値補完をしてみる - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/10/13/025136">&#x3010;python&#x3011;sklearn&#x306E;SimpleImputer&#x3067;&#x6B20;&#x640D;&#x5024;&#x88DC;&#x5B8C;&#x3092;&#x3057;&#x3066;&#x307F;&#x308B; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><p> irisを欠損させて補完して予測精度を見ます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris <span class="synPreProc">from</span> sklearn.neighbors <span class="synPreProc">import</span> KNeighborsClassifier <span class="synPreProc">from</span> sklearn.model_selection <span class="synPreProc">import</span> train_test_split <span class="synPreProc">from</span> sklearn.ensemble <span class="synPreProc">import</span> RandomForestRegressor <span class="synPreProc">from</span> sklearn.experimental <span class="synPreProc">import</span> enable_iterative_imputer <span class="synPreProc">from</span> sklearn.impute <span class="synPreProc">import</span> IterativeImputer <span class="synPreProc">from</span> sklearn.pipeline <span class="synPreProc">import</span> Pipeline <span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> classification_report <span class="synComment"># 再現性確保</span> np.random.seed(<span class="synConstant">0</span>) <span class="synComment"># データ生成</span> dataset = load_iris() X_train, X_test, y_train, y_test = train_test_split( dataset.data, dataset.target, stratify=dataset.target, random_state=<span class="synConstant">0</span>) mask = ~np.random.randint(<span class="synConstant">10</span>, size=X_train.shape).astype(np.bool) X_train[mask] = np.nan <span class="synComment"># モデル定義</span> knn = KNeighborsClassifier() <span class="synComment"># 処理</span> <span class="synStatement">def</span> <span class="synIdentifier">drop</span>(): <span class="synIdentifier">print</span>(<span class="synConstant">&quot;# drop&quot;</span>) idx = ~(np.isnan(X_train).any(axis=<span class="synConstant">1</span>)) X_train_, y_train_ = X_train[idx], y_train[idx] knn.fit(X_train_, y_train_) y_pred = knn.predict(X_test) <span class="synIdentifier">print</span>(classification_report( y_test, y_pred, digits=<span class="synConstant">3</span>, target_names=dataset.target_names)) <span class="synStatement">def</span> <span class="synIdentifier">impute</span>(): <span class="synIdentifier">print</span>(<span class="synConstant">&quot;# impute&quot;</span>) rf = RandomForestRegressor( n_estimators=<span class="synConstant">200</span>, max_depth=<span class="synConstant">3</span>, n_jobs=-<span class="synConstant">1</span>) imp = IterativeImputer(estimator=rf, max_iter=<span class="synConstant">20</span>) pl = Pipeline([(<span class="synConstant">&quot;imputer&quot;</span>, imp), (<span class="synConstant">&quot;KNN&quot;</span>, knn)]) pl.fit(X_train, y_train) y_pred = pl.predict(X_test) <span class="synIdentifier">print</span>(classification_report( y_test, y_pred, digits=<span class="synConstant">3</span>, target_names=dataset.target_names)) drop() impute() </pre><pre class="code" data-lang="" data-unlink># drop precision recall f1-score support setosa 1.000 1.000 1.000 13 versicolor 0.929 1.000 0.963 13 virginica 1.000 0.917 0.957 12 accuracy 0.974 38 macro avg 0.976 0.972 0.973 38 weighted avg 0.976 0.974 0.974 38 # impute precision recall f1-score support setosa 1.000 1.000 1.000 13 versicolor 1.000 1.000 1.000 13 virginica 1.000 1.000 1.000 12 accuracy 1.000 38 macro avg 1.000 1.000 1.000 38 weighted avg 1.000 1.000 1.000 38 </pre><p> 単に欠損値のある行を学習データから落とすより良いわけです。まあ、irisのときは平均値補完でやっても同じ結果になりましたが。</p><p> データセットをwineにしてみます。</p> <pre class="code" data-lang="" data-unlink># drop precision recall f1-score support class_0 0.812 0.867 0.839 15 class_1 0.750 0.667 0.706 18 class_2 0.538 0.583 0.560 12 accuracy 0.711 45 macro avg 0.700 0.706 0.702 45 weighted avg 0.714 0.711 0.711 45 # impute precision recall f1-score support class_0 0.800 0.800 0.800 15 class_1 0.733 0.611 0.667 18 class_2 0.533 0.667 0.593 12 accuracy 0.689 45 macro avg 0.689 0.693 0.686 45 weighted avg 0.702 0.689 0.691 45 </pre><p> dropより良くなるかと思いましたが、そうでもないようです。平均値で補完するよりは良いみたいですが……。</p> </div> <div class="section"> <h3>考察</h3> <p> そもそも欠損値補完というアプローチは、</p> <ul> <li>欠損値が多すぎるとそもそもまともに予測できないし、予測の悪さが大勢に影響を及ぼして全体のパフォーマンスを悪化させかねない</li> <li>欠損値が少ないならdropしても大勢に影響はない</li> </ul><p> という矛盾を抱えている訳ですが、そこそこの欠損があるときにdropするよりちょっと良くなるかな? という可能性を追求するためにあるものだと思います。</p> </div> <div class="section"> <h3>まとめ</h3> <p> 使うの面倒くさいしこの先どうなるかは読めないけど、使えるみたいです。ただし、できるからといってパフォーマンスの上で凄い効能があるとか、そういうものではないみたいです。</p> </div> hayataka2049 【python】sklearnのSimpleImputerで欠損値補完をしてみる hatenablog://entry/26006613448583664 2019-10-13T02:51:36+09:00 2019-11-05T22:54:49+09:00 はじめに 欠損値補完(nanの処理)はだいたいpandasでやる人が多いですが、最近のscikit-learnはこの辺りの前処理に対するサポートも充実してきているので、平均値で補完する程度であればかえってscikit-learnでやった方が楽かもしれません。 ということで、sklearn.impute.SimpleImputerを使ってみようと思います。sklearn.impute.SimpleImputer — scikit-learn 0.21.3 documentation 使い方 とても単純です。とりあえず、これを見てください。 import numpy as np from skle… <div class="section"> <h3>はじめに</h3> <p> 欠損値補完(nanの処理)はだいたいpandasでやる人が多いですが、最近のscikit-learnはこの辺りの前処理に対するサポートも充実してきているので、平均値で補完する程度であればかえってscikit-learnでやった方が楽かもしれません。</p><p> ということで、sklearn.impute.SimpleImputerを使ってみようと思います。</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.impute.SimpleImputer.html">sklearn.impute.SimpleImputer &mdash; scikit-learn 0.21.3 documentation</a></p><p></p> </div> <div class="section"> <h3>使い方</h3> <p> とても単純です。とりあえず、これを見てください。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris <span class="synPreProc">from</span> sklearn.impute <span class="synPreProc">import</span> SimpleImputer <span class="synComment"># 再現性確保</span> np.random.seed(<span class="synConstant">0</span>) <span class="synComment"># irisでやることにする</span> iris = load_iris() X = iris.data.copy() <span class="synComment"># 全体の約1割のデータをnanに置き換える</span> mask = ~np.random.randint(<span class="synConstant">10</span>, size=X.shape).astype(np.bool) X[mask] = np.nan <span class="synIdentifier">print</span>(X[:<span class="synConstant">10</span>]) <span class="synComment"># 表示</span> <span class="synComment"># モデルの定義</span> imp = SimpleImputer() <span class="synComment"># トランスフォーム</span> X_imputed = imp.fit_transform(X) <span class="synIdentifier">print</span>(X_imputed[:<span class="synConstant">10</span>]) <span class="synComment"># 表示</span> </pre><p><b>結果</b></p> <pre class="code lang-python" data-lang="python" data-unlink>[[<span class="synConstant">5.1</span> nan <span class="synConstant">1.4</span> <span class="synConstant">0.2</span>] [<span class="synConstant">4.9</span> <span class="synConstant">3.</span> <span class="synConstant">1.4</span> <span class="synConstant">0.2</span>] [<span class="synConstant">4.7</span> <span class="synConstant">3.2</span> <span class="synConstant">1.3</span> <span class="synConstant">0.2</span>] [<span class="synConstant">4.6</span> <span class="synConstant">3.1</span> <span class="synConstant">1.5</span> <span class="synConstant">0.2</span>] [<span class="synConstant">5.</span> <span class="synConstant">3.6</span> <span class="synConstant">1.4</span> <span class="synConstant">0.2</span>] [<span class="synConstant">5.4</span> <span class="synConstant">3.9</span> <span class="synConstant">1.7</span> <span class="synConstant">0.4</span>] [<span class="synConstant">4.6</span> <span class="synConstant">3.4</span> nan <span class="synConstant">0.3</span>] [<span class="synConstant">5.</span> nan <span class="synConstant">1.5</span> <span class="synConstant">0.2</span>] [<span class="synConstant">4.4</span> <span class="synConstant">2.9</span> <span class="synConstant">1.4</span> <span class="synConstant">0.2</span>] [<span class="synConstant">4.9</span> <span class="synConstant">3.1</span> nan <span class="synConstant">0.1</span>]] [[<span class="synConstant">5.1</span> <span class="synConstant">3.06296296</span> <span class="synConstant">1.4</span> <span class="synConstant">0.2</span> ] [<span class="synConstant">4.9</span> <span class="synConstant">3.</span> <span class="synConstant">1.4</span> <span class="synConstant">0.2</span> ] [<span class="synConstant">4.7</span> <span class="synConstant">3.2</span> <span class="synConstant">1.3</span> <span class="synConstant">0.2</span> ] [<span class="synConstant">4.6</span> <span class="synConstant">3.1</span> <span class="synConstant">1.5</span> <span class="synConstant">0.2</span> ] [<span class="synConstant">5.</span> <span class="synConstant">3.6</span> <span class="synConstant">1.4</span> <span class="synConstant">0.2</span> ] [<span class="synConstant">5.4</span> <span class="synConstant">3.9</span> <span class="synConstant">1.7</span> <span class="synConstant">0.4</span> ] [<span class="synConstant">4.6</span> <span class="synConstant">3.4</span> <span class="synConstant">3.8162963</span> <span class="synConstant">0.3</span> ] [<span class="synConstant">5.</span> <span class="synConstant">3.06296296</span> <span class="synConstant">1.5</span> <span class="synConstant">0.2</span> ] [<span class="synConstant">4.4</span> <span class="synConstant">2.9</span> <span class="synConstant">1.4</span> <span class="synConstant">0.2</span> ] [<span class="synConstant">4.9</span> <span class="synConstant">3.1</span> <span class="synConstant">3.8162963</span> <span class="synConstant">0.1</span> ]] </pre><p> ご覧の通り、埋まります。桁数が違うので場所がわかりやすいですね。</p><p> 欠損値部分は列の平均値で埋められます。これはSimpleImputerのstrategyパラメータで変更できます(たとえばstrategy="median"とすれば中央値で補完してくれます)。</p><p> なお、sklearnの他のモデルと同様、補完に使われる値は「学習データから取得される」ことを覚えておきましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np &gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.impute <span class="synPreProc">import</span> SimpleImputer &gt;&gt;&gt; imp = SimpleImputer() &gt;&gt;&gt; imp.fit([[<span class="synConstant">100</span>], [<span class="synConstant">101</span>],[<span class="synConstant">99</span>]]) SimpleImputer(add_indicator=<span class="synIdentifier">False</span>, copy=<span class="synIdentifier">True</span>, fill_value=<span class="synIdentifier">None</span>, missing_values=nan, strategy=<span class="synConstant">'mean'</span>, verbose=<span class="synConstant">0</span>) &gt;&gt;&gt; imp.transform([[np.nan], [<span class="synConstant">1</span>], [<span class="synConstant">2</span>], [<span class="synConstant">0</span>]]) array([[<span class="synConstant">100.</span>], <span class="synComment"># ここが1になったりはしない</span> [ <span class="synConstant">1.</span>], [ <span class="synConstant">2.</span>], [ <span class="synConstant">0.</span>]]) </pre><p> こういう望ましい性質があるので、pandasでやるより機械学習向きです。カテゴリ変数のときにも書いたことですが。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F08%2F17%2F184527" title="【python】機械学習でpandas.get_dummiesを使ってはいけない - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/08/17/184527">&#x3010;python&#x3011;&#x6A5F;&#x68B0;&#x5B66;&#x7FD2;&#x3067;pandas.get_dummies&#x3092;&#x4F7F;&#x3063;&#x3066;&#x306F;&#x3044;&#x3051;&#x306A;&#x3044; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a><br /> </p> </div> <div class="section"> <h3>効果のほどを見る</h3> <p> 欠損値のあるデータを単にdropするのと、平均値補完とどちらが良いのでしょうか?</p><p> せっかくなのでirisの分類で試してみます。なお、面倒臭いので欠損値を作るのは学習データだけです。また、分類器は議論の余地が少ないKNNとします。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris <span class="synPreProc">from</span> sklearn.neighbors <span class="synPreProc">import</span> KNeighborsClassifier <span class="synPreProc">from</span> sklearn.model_selection <span class="synPreProc">import</span> train_test_split <span class="synPreProc">from</span> sklearn.impute <span class="synPreProc">import</span> SimpleImputer <span class="synPreProc">from</span> sklearn.pipeline <span class="synPreProc">import</span> Pipeline <span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> classification_report <span class="synComment"># 再現性確保</span> np.random.seed(<span class="synConstant">0</span>) <span class="synComment"># データ生成</span> dataset = load_iris() X_train, X_test, y_train, y_test = train_test_split( dataset.data, dataset.target, stratify=dataset.target, random_state=<span class="synConstant">0</span>) mask = ~np.random.randint(<span class="synConstant">10</span>, size=X_train.shape).astype(np.bool) X_train[mask] = np.nan <span class="synComment"># モデル定義</span> knn = KNeighborsClassifier() <span class="synComment"># 処理</span> <span class="synStatement">def</span> <span class="synIdentifier">drop</span>(): <span class="synIdentifier">print</span>(<span class="synConstant">&quot;# drop&quot;</span>) idx = ~(np.isnan(X_train).any(axis=<span class="synConstant">1</span>)) X_train_, y_train_ = X_train[idx], y_train[idx] knn.fit(X_train_, y_train_) y_pred = knn.predict(X_test) <span class="synIdentifier">print</span>(classification_report( y_test, y_pred, digits=<span class="synConstant">3</span>, target_names=dataset.target_names)) <span class="synStatement">def</span> <span class="synIdentifier">impute</span>(): <span class="synIdentifier">print</span>(<span class="synConstant">&quot;# impute&quot;</span>) imp = SimpleImputer() pl = Pipeline([(<span class="synConstant">&quot;imputer&quot;</span>, imp), (<span class="synConstant">&quot;KNN&quot;</span>, knn)]) pl.fit(X_train, y_train) y_pred = pl.predict(X_test) <span class="synIdentifier">print</span>(classification_report( y_test, y_pred, digits=<span class="synConstant">3</span>, target_names=dataset.target_names)) drop() impute() </pre><p><b>結果</b></p> <pre class="code" data-lang="" data-unlink># drop precision recall f1-score support setosa 1.000 1.000 1.000 13 versicolor 0.929 1.000 0.963 13 virginica 1.000 0.917 0.957 12 accuracy 0.974 38 macro avg 0.976 0.972 0.973 38 weighted avg 0.976 0.974 0.974 38 # impute precision recall f1-score support setosa 1.000 1.000 1.000 13 versicolor 1.000 1.000 1.000 13 virginica 1.000 1.000 1.000 12 accuracy 1.000 38 macro avg 1.000 1.000 1.000 38 weighted avg 1.000 1.000 1.000 38 </pre><p> 補完の方が良いのは確かですが、どちらも良すぎて議論しづらいので、データセットを難易度が少し高いwineにします。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># 中略</span> <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_wine <span class="synComment"># 中略</span> dataset = load_wine() </pre><p><b>結果</b></p> <pre class="code" data-lang="" data-unlink># drop precision recall f1-score support class_0 0.812 0.867 0.839 15 class_1 0.750 0.667 0.706 18 class_2 0.538 0.583 0.560 12 accuracy 0.711 45 macro avg 0.700 0.706 0.702 45 weighted avg 0.714 0.711 0.711 45 # impute precision recall f1-score support class_0 0.737 0.933 0.824 15 class_1 0.769 0.556 0.645 18 class_2 0.538 0.583 0.560 12 accuracy 0.689 45 macro avg 0.682 0.691 0.676 45 weighted avg 0.697 0.689 0.682 45 </pre><p> 今度はdropした方が良いです。このように、どう処理するのが良いのかは割とケースバイケースです。たとえば欠損値の出現率を変えると勝ち負けが変わります。</p><p> どんなときでも躊躇なく平均値で補完すれば良いとは言えないのが、ちょっと嫌な所です。</p> </div> <div class="section"> <h3>まとめ</h3> <p> なにはともあれ、気楽に使えるものがある、というのは素晴らしいことです。</p> </div> <div class="section"> <h3>余談</h3> <p> 実はもう少し高級な補完をしてくれるIterativeImputerなるモデルも存在しますが、</p> <blockquote> <p>This estimator is still experimental for now: the predictions and the API might change without any deprecation cycle.<br /> <a href="https://scikit-learn.org/stable/modules/generated/sklearn.impute.IterativeImputer.html">sklearn.impute.IterativeImputer &mdash; scikit-learn 0.21.3 documentation</a></p> </blockquote> <p> という扱いなのと、軽く触ってみた感じそこそこ使いづらいので、今後紹介するかどうかは未定です。気が向いたら書こうと思います。</p><p> 追記:けっきょく書きました。<br /> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F11%2F05%2F225424" title="【python】sklearnのIterativeImputerで欠損値補完 - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.haya-programming.com/entry/2019/11/05/225424">www.haya-programming.com</a></cite></p> </div> hayataka2049 mecab-pythonで品詞を見るときはfeature.splitしない方が速い hatenablog://entry/26006613446350535 2019-10-09T09:51:14+09:00 2019-10-09T09:51:14+09:00 はじめに mecab-pythonで形態素解析を行って何らかの処理をするとき、特定の品詞だけ取り出したいということがよくあります。 そういう目的で書かれたコードとして、よくこんなものを見たりすると思います。 import MeCab tagger = MeCab.Tagger() tagger.parse("") node = tagger.parseToNode("MeCabのテストをする") lst = [] while node: if "名詞" == node.feature.split(",")[0]: # ここが問題 lst.append(node.surface) node = … <div class="section"> <h3>はじめに</h3> <p> mecab-pythonで形態素解析を行って何らかの処理をするとき、特定の品詞だけ取り出したいということがよくあります。</p><p> そういう目的で書かれたコードとして、よくこんなものを見たりすると思います。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> MeCab tagger = MeCab.Tagger() tagger.parse(<span class="synConstant">&quot;&quot;</span>) node = tagger.parseToNode(<span class="synConstant">&quot;MeCabのテストをする&quot;</span>) lst = [] <span class="synStatement">while</span> node: <span class="synStatement">if</span> <span class="synConstant">&quot;名詞&quot;</span> == node.feature.split(<span class="synConstant">&quot;,&quot;</span>)[<span class="synConstant">0</span>]: <span class="synComment"># ここが問題</span> lst.append(node.surface) node = node.next <span class="synIdentifier">print</span>(lst) <span class="synComment"># =&gt; ['MeCab', 'テスト']</span> </pre><p> それはそれで良いんですが、改めて冷静に考えてみると「str.splitはそこそこ負荷の高い処理なので、全形態素に対してやるのは問題にならないか?」という懸念が生じてきます。</p><p> ということで、他の方法がないのか考えて速度を比較してみようと思います。</p> </div> <div class="section"> <h3>やり方の検討</h3> <p> featureはIPA辞書を使っていれば、</p> <pre class="code" data-lang="" data-unlink>蟻 名詞,一般,*,*,*,*,蟻,アリ,アリ</pre><p> のように出てきます(タブのあとの部分がfeatureです)。</p><p> よって、これでもできます。</p> <pre class="code" data-lang="" data-unlink> if &#34;名詞,&#34; in node.feature:</pre><p> この方法だと「名詞」という単語が出てくると若干問題があるのですが、実用上はほとんど問題ないでしょうし、回避策もその気になれば色々あるでしょう。</p><p> これと同じことは正規表現でもできます。正規表現エンジンでやった方が速いかもという淡い期待を抱いて、そちらも試すことにします。</p> </div> <div class="section"> <h3>実験</h3> <p> まず実験用データとして、wikipediaの「形態素解析」の記事の冒頭部分をコピペして、data.txtとして実行時カレントディレクトリに保存しておきました。文字数は445文字になりました。</p><p><a href="https://ja.wikipedia.org/wiki/%E5%BD%A2%E6%85%8B%E7%B4%A0%E8%A7%A3%E6%9E%90">&#x5F62;&#x614B;&#x7D20;&#x89E3;&#x6790; - Wikipedia</a></p><p> その上で以下のコードを書いて実行しました。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> re <span class="synPreProc">import</span> timeit <span class="synPreProc">import</span> MeCab <span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synConstant">&quot;data.txt&quot;</span>) <span class="synStatement">as</span> f: text = f.read() tagger = MeCab.Tagger(<span class="synConstant">&quot;&quot;</span>) tagger.parse(<span class="synConstant">&quot;&quot;</span>) <span class="synStatement">def</span> <span class="synIdentifier">f1</span>(): <span class="synConstant">&quot;&quot;&quot;分割して条件式を作成</span> <span class="synConstant"> &quot;&quot;&quot;</span> lst = [] node = tagger.parseToNode(text).next <span class="synStatement">while</span> node.next: feature = node.feature.split(<span class="synConstant">&quot;,&quot;</span>) <span class="synStatement">if</span> feature[<span class="synConstant">0</span>] == <span class="synConstant">&quot;名詞&quot;</span>: lst.append(node.surface) node = node.next <span class="synStatement">return</span> lst <span class="synStatement">def</span> <span class="synIdentifier">f2</span>(): <span class="synConstant">&quot;&quot;&quot;カンマ区切りのfeatureに対してinで直接調べる</span> <span class="synConstant"> &quot;&quot;&quot;</span> lst = [] node = tagger.parseToNode(text).next <span class="synStatement">while</span> node.next: <span class="synStatement">if</span> <span class="synConstant">&quot;名詞,&quot;</span> <span class="synStatement">in</span> node.feature: lst.append(node.surface) node = node.next <span class="synStatement">return</span> lst <span class="synStatement">def</span> <span class="synIdentifier">f3</span>(): <span class="synConstant">&quot;&quot;&quot;正規表現を使う</span> <span class="synConstant"> &quot;&quot;&quot;</span> r = re.compile(<span class="synConstant">r&quot;名詞,&quot;</span>) lst = [] node = tagger.parseToNode(text).next <span class="synStatement">while</span> node.next: <span class="synStatement">if</span> r.match(node.feature): lst.append(node.surface) node = node.next <span class="synStatement">return</span> lst <span class="synComment"># 確認</span> <span class="synIdentifier">print</span>(f1() == f2() == f3()) <span class="synComment"># 計測</span> <span class="synIdentifier">print</span>(timeit.timeit(f1, number=<span class="synConstant">1000</span>)) <span class="synIdentifier">print</span>(timeit.timeit(f2, number=<span class="synConstant">1000</span>)) <span class="synIdentifier">print</span>(timeit.timeit(f3, number=<span class="synConstant">1000</span>)) </pre><p> 結果は以下のようになりました。</p> <pre class="code" data-lang="" data-unlink>True 1.841627002999303 1.6039621180025279 1.7953744690021267</pre><p> 案の定、inが一番速いです。正規表現はsplitするよりは速いものの、inには負けるようです。</p> </div> <div class="section"> <h3>解説</h3> <p> str.splitを行うと、</p> <ul> <li>splitの結果生じた各strオブジェクトの生成</li> <li>それを格納するlistオブジェクトの生成</li> </ul><p> という処理が行われ、新規にメモリ領域を確保するタイプの処理が走るため<a href="#f-45b0bc60" name="fn-45b0bc60" title="処理系内部でバッファ領域はあると思いますが">*1</a>、とても遅くなります。ついでにいうと、参照カウンタも余計に走ります。</p><p> inの操作は値比較だけでできます。また、処理系の組み込み型の比較演算などは、多くの場合は極めて高度な最適化が施されていて、比較的に高速に実行できます。</p> </div> <div class="section"> <h3>まとめ</h3> <p> まあでも、これで期待される改善幅は1割とかそんなものなので、柔軟性と可読性を犠牲にしてまでやるかというと微妙かもしれません。</p><p> むしろ安直に書きたいときに使えます。</p> </div><div class="footnote"> <p class="footnote"><a href="#fn-45b0bc60" name="f-45b0bc60" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">処理系内部でバッファ領域はあると思いますが</span></p> </div> hayataka2049 【python】matplotlibのboxplotで外れ値を表示しないようにする hatenablog://entry/26006613446026969 2019-10-07T20:24:16+09:00 2019-10-07T20:28:30+09:00 はじめに matplotlibのboxplotを使うと簡単に箱ひげ図が描けます。ただし、デフォルト設定では外れ値が黒い円で表示されます。 どんなデータでも、サンプル数が多いと一定数の外れ値は出てしまいます。ただ、図を見る人は気にするところですし、外れ値がたくさんあると見た目にも悪いので、何らかの処置が必要です。 外れ値が描画されてしまうプログラムの例 import numpy as np import matplotlib.pyplot as plt np.random.seed(0) x = np.random.normal(size=3*10**3) # サンプル数に注目 plt.boxp… <div class="section"> <h3>はじめに</h3> <p> matplotlibのboxplotを使うと簡単に箱ひげ図が描けます。ただし、デフォルト設定では外れ値が黒い円で表示されます。</p><p> どんなデータでも、サンプル数が多いと一定数の外れ値は出てしまいます。ただ、図を見る人は気にするところですし、外れ値がたくさんあると見た目にも悪いので、何らかの処置が必要です。</p><p> <b>外れ値が描画されてしまうプログラムの例</b></p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt np.random.seed(<span class="synConstant">0</span>) x = np.random.normal(size=<span class="synConstant">3</span>*<span class="synConstant">10</span>**<span class="synConstant">3</span>) <span class="synComment"># サンプル数に注目</span> plt.boxplot(x) plt.savefig(<span class="synConstant">&quot;result1.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="外れ値がたくさん出ている"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20191007/20191007201240.png" alt="&#x5916;&#x308C;&#x5024;&#x304C;&#x305F;&#x304F;&#x3055;&#x3093;&#x51FA;&#x3066;&#x3044;&#x308B;" title="f:id:hayataka2049:20191007201240p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result1.png 外れ値がたくさん出ている</figcaption></figure></p><p> ということで、外れ値を表示させない方法を解説します。なお、この記事の内容は公式リファレンスに基づいています。</p><p><a href="https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.boxplot.html">matplotlib.pyplot.boxplot &mdash; Matplotlib 3.1.1 documentation</a></p><p></p> </div> <div class="section"> <h3>シンプルに表示させない</h3> <p> boxplotのキーワード引数のsymを使うと外れ値を「描画させない」設定が可能になります。具体的には、空文字列を指定します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt np.random.seed(<span class="synConstant">0</span>) x = np.random.normal(size=<span class="synConstant">3</span>*<span class="synConstant">10</span>**<span class="synConstant">3</span>) plt.boxplot(x, sym=<span class="synConstant">&quot;&quot;</span>) <span class="synComment"># 変更点</span> plt.savefig(<span class="synConstant">&quot;result2a.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="外れ値が表示されなくなった"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20191007/20191007201415.png" alt="&#x5916;&#x308C;&#x5024;&#x304C;&#x8868;&#x793A;&#x3055;&#x308C;&#x306A;&#x304F;&#x306A;&#x3063;&#x305F;" title="f:id:hayataka2049:20191007201415p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result2a.png 外れ値が表示されなくなった</figcaption></figure></p><p> 注意点としては、これはあくまでも外れ値の描画を行わないだけで、外れ値そのものはデフォルト通り計算されるということが挙げられます。つまり、本来の最大値・最小値よりひげが短くなります。</p><p> プロット上でだけ「消している」ということですね。</p><p> 参考:<br /> <a href="https://bellcurve.jp/statistics/course/5222.html">4-3. &#x5916;&#x308C;&#x5024;&#x691C;&#x51FA;&#x306E;&#x3042;&#x308B;&#x7BB1;&#x3072;&#x3052;&#x56F3; | &#x7D71;&#x8A08;&#x5B66;&#x306E;&#x6642;&#x9593; | &#x7D71;&#x8A08;WEB</a></p><p> また、外れ値のマーカーに好きな記号を指定したりすることもできます。本来はこの用途で用いる引数です。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt np.random.seed(<span class="synConstant">0</span>) x = np.random.normal(size=<span class="synConstant">3</span>*<span class="synConstant">10</span>**<span class="synConstant">3</span>) plt.boxplot(x, sym=<span class="synConstant">&quot;+&quot;</span>) <span class="synComment"># 変更点</span> plt.savefig(<span class="synConstant">&quot;result2b.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="マーカーを十字にしてみた"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20191007/20191007201833.png" alt="&#x30DE;&#x30FC;&#x30AB;&#x30FC;&#x3092;&#x5341;&#x5B57;&#x306B;&#x3057;&#x3066;&#x307F;&#x305F;" title="f:id:hayataka2049:20191007201833p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result2b.png マーカーを十字にしてみた</figcaption></figure></p><p> 使えるマーカーの一覧はこちらを参照。<br /> <a href="https://matplotlib.org/3.1.1/api/markers_api.html">matplotlib.markers &mdash; Matplotlib 3.1.1 documentation</a><br /> </p> </div> <div class="section"> <h3>外れ値の計算そのものをやめる</h3> <p> (外れ値も含めた)本来の最大値・最小値に基づいてひげを出す場合は、whis="range"を指定します。</p><p> そもそも外れ値の概念なしで箱ひげ図を描くというやり方になります。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt np.random.seed(<span class="synConstant">0</span>) x = np.random.normal(size=<span class="synConstant">3</span>*<span class="synConstant">10</span>**<span class="synConstant">3</span>) plt.boxplot(x, whis=<span class="synConstant">&quot;range&quot;</span>) <span class="synComment"># 変更点</span> plt.savefig(<span class="synConstant">&quot;result3.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="result3.png 上の方法で非表示にするよりひげが長くなる"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20191007/20191007202110.png" alt="result3.png&#x3000;&#x4E0A;&#x306E;&#x65B9;&#x6CD5;&#x3067;&#x975E;&#x8868;&#x793A;&#x306B;&#x3059;&#x308B;&#x3088;&#x308A;&#x3072;&#x3052;&#x304C;&#x9577;&#x304F;&#x306A;&#x308B;" title="f:id:hayataka2049:20191007202110p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result3.png 上の方法で非表示にするよりひげが長くなる</figcaption></figure></p><p> この方が誤解を招く恐れがないので(つまり、外れ値検出をやっておいて外れ値をプロットしないという図は少しイレギュラーで伝わりづらい気がするので)、これでやるのがおすすめです。ただし、外れ値が少なくて、そんな極端に外れていないとき向きのやり方です(やたらひげが長いのも不格好ですから……)。</p> </div> <div class="section"> <h3>まとめ</h3> <p> 二つやり方がありますが、意味が微妙に違うし、実際にそれぞれで異なった結果になるので注意してください。</p> </div> hayataka2049 matplotlibで図全体にタイトルを付けるにはsuptitleを使う hatenablog://entry/26006613413439053 2019-09-01T00:39:49+09:00 2019-09-01T00:39:49+09:00 はじめに matplotlibではよく一つの図の中に複数のグラフを描きます。そうすると全体に共通してタイトルを付けたくなるのですが、普通にやろうとしても個別のAxesに対して呼んでしまいがちです。 図全体に対してタイトルを付けるには、suptitleを使います。matplotlib.pyplot.suptitle — Matplotlib 3.1.0 documentation 使い方 以下のように使います。 import numpy as np import matplotlib.pyplot as plt # データ生成 x = np.arange(0, 10, 0.1) y1 = x *… <div class="section"> <h3>はじめに</h3> <p> matplotlibではよく一つの図の中に複数のグラフを描きます。そうすると全体に共通してタイトルを付けたくなるのですが、普通にやろうとしても個別のAxesに対して呼んでしまいがちです。</p><p> 図全体に対してタイトルを付けるには、suptitleを使います。</p><p><a href="https://matplotlib.org/3.1.0/api/_as_gen/matplotlib.pyplot.suptitle.html">matplotlib.pyplot.suptitle &mdash; Matplotlib 3.1.0 documentation</a><br /> </p> </div> <div class="section"> <h3>使い方</h3> <p> 以下のように使います。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synComment"># データ生成</span> x = np.arange(<span class="synConstant">0</span>, <span class="synConstant">10</span>, <span class="synConstant">0.1</span>) y1 = x ** <span class="synConstant">2</span> y2 = np.sin(x) <span class="synComment"># figure, axesの生成</span> fig, axes = plt.subplots(nrows=<span class="synConstant">1</span>, ncols=<span class="synConstant">2</span>) <span class="synComment"># 個別のAxesに対するプロットとタイトル付け</span> axes[<span class="synConstant">0</span>].plot(x, y1) axes[<span class="synConstant">0</span>].set_title(<span class="synConstant">&quot;y = x^2&quot;</span>) axes[<span class="synConstant">1</span>].plot(x, y2) axes[<span class="synConstant">1</span>].set_title(<span class="synConstant">&quot;y = sin(x)&quot;</span>) <span class="synComment"># figure全体のタイトル</span> fig.suptitle(<span class="synConstant">&quot;graphs&quot;</span>) <span class="synComment"># 保存</span> plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="result.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190901/20190901003727.png" alt="result.png" title="f:id:hayataka2049:20190901003727p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result.png</figcaption></figure></p><br /> <p> 結果の図を見ればわかる通り、図の上に大きなタイトルが付いています。このように全体にタイトルを付けることができます。なお、今回はfigureのメソッドとして呼んでいますが、例のごとくpltからplt.suptitleという形でも呼べます。</p><p> 今回は行数が少ないので大丈夫でしたが、たまにタイトル同士で被ったりしてグラフが不格好になることもあります。そういうときは適宜余白を調整してください。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2018%2F10%2F11%2F030103" title="【python】matplotlibで図の余白を調整する - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2018/10/11/030103">&#x3010;python&#x3011;matplotlib&#x3067;&#x56F3;&#x306E;&#x4F59;&#x767D;&#x3092;&#x8ABF;&#x6574;&#x3059;&#x308B; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p> </div> hayataka2049 matplotlibでAxesを真っ白にする(x軸とかy軸なんかを消して非表示にする) hatenablog://entry/26006613409251230 2019-08-29T23:48:11+09:00 2019-08-30T00:50:29+09:00 matplotlibでAxesを真っ白にする(x軸とかy軸とか目盛りなんかを消して非表示にする) matplotlibでsubplotsを使って適当にグラフを並べるのはよくある処理だと思います。しかし、きれいに長方形で配置できないときもあります。タイル状に作るので、場合によっては無駄なAxesが生成されます。これが邪魔なので消したいという場合、どうしたらいいのでしょうか? <div class="section"> <h3>はじめに</h3> <p> matplotlibでsubplotsを使って適当にグラフを並べるのはよくある処理だと思います。しかし、きれいに長方形で配置できないときもあります。<br /> <br /> </p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt np.random.seed(<span class="synConstant">0</span>) data = [(np.random.randint(<span class="synConstant">10</span>, size=(<span class="synConstant">10</span>, )), np.random.randint(<span class="synConstant">10</span>, size=(<span class="synConstant">10</span>, ))) <span class="synStatement">for</span> x <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">5</span>)] fig, axes = plt.subplots(nrows=<span class="synConstant">2</span>, ncols=<span class="synConstant">3</span>) <span class="synStatement">for</span> (x, y), ax <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(data, axes.ravel()): ax.scatter(x, y) plt.savefig(<span class="synConstant">&quot;fig1.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="fig1.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190829/20190829234713.png" alt="fig1.png" title="f:id:hayataka2049:20190829234713p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>fig1.png</figcaption></figure></p><p> タイル状に作るので、場合によっては無駄なAxesが生成されます。これが邪魔なので消したいという場合、どうしたらいいのでしょうか?</p> </div> <div class="section"> <h3>解決策</h3> <p> ax.axis("off")みたいにすると消せます。</p><p><a href="https://matplotlib.org/3.1.0/api/_as_gen/matplotlib.axes.Axes.axis.html">matplotlib.axes.Axes.axis &mdash; Matplotlib 3.1.0 documentation</a></p><p> あとはこれが呼ばれるようにすればオッケーです。やり方はいろいろありますが(それこそデータを1つずつ個別にプロットすれば簡単)、今回はforループとzipを使っているので、zip_longestで対応してみましょう(この辺は臨機応変に書いてください)。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> itertools <span class="synPreProc">import</span> zip_longest <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt np.random.seed(<span class="synConstant">0</span>) data = [(np.random.randint(<span class="synConstant">10</span>, size=(<span class="synConstant">10</span>, )), np.random.randint(<span class="synConstant">10</span>, size=(<span class="synConstant">10</span>, ))) <span class="synStatement">for</span> x <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">5</span>)] fig, axes = plt.subplots(nrows=<span class="synConstant">2</span>, ncols=<span class="synConstant">3</span>) <span class="synStatement">for</span> xy, ax <span class="synStatement">in</span> zip_longest(data, axes.ravel()): <span class="synStatement">if</span> xy <span class="synStatement">is</span> <span class="synIdentifier">None</span>: ax.axis(<span class="synConstant">&quot;off&quot;</span>) <span class="synStatement">continue</span> x, y = xy ax.scatter(x, y) plt.savefig(<span class="synConstant">&quot;fig2.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="fig2.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190829/20190829234731.png" alt="fig2.png" title="f:id:hayataka2049:20190829234731p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>fig2.png</figcaption></figure></p><p> うまくいきました。右下がちゃんと真っ白になっています。</p> </div> <div class="section"> <h3>まとめ</h3> <p> 私は長年これを知らなくて、画像編集で消したりしていたのですが、ax.axis("off")を覚えたので、これで今後はかっこいいグラフがスマートに描けます。</p> </div> hayataka2049 【python】機械学習でpandas.get_dummiesを使ってはいけない hatenablog://entry/26006613398191700 2019-08-17T18:45:27+09:00 2019-08-31T22:13:42+09:00 「pandasのget_dummiesでダミー変数が作れるぜ」という記事がとてもたくさんあって初心者を混乱させているのですが、これは「データ分析」には使えても「機械学習」には向きません。もう少し正確に言い換えると「訓練データからモデルを作り、未知のデータの予測を行うタスク」には使い勝手が悪いものです。 <div class="section"> <h3>はじめに</h3> <p> 「pandasのget_dummiesでダミー変数が作れるぜ」という記事がとてもたくさんあって初心者を混乱させているのですが、これは「データ分析」には使えても「機械学習」には向きません。もう少し正確に言い換えると「訓練データからモデルを作り、未知のデータの予測を行うタスク」には使い勝手が悪いものです。</p><p> 機械学習に使ってはいけないというのは大げさかもしれませんが、でも間違った使い方をよく見かけますし、こう言い切った方がぶっちゃけ良いと思います。</p><p> この記事では「こういうときにはget_dummies使うんじゃねえ!」ということと、どういう問題があるのかと、代替方法について書きます。</p><p><a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.get_dummies.html">pandas.get_dummies &mdash; pandas 0.25.1 documentation</a></p><p></p> </div> <div class="section"> <h3>問題点</h3> <p> こんなデータを考えましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd &gt;&gt;&gt; df = pd.DataFrame({<span class="synConstant">&quot;A&quot;</span>:[<span class="synConstant">&quot;hoge&quot;</span>, <span class="synConstant">&quot;fuga&quot;</span>], <span class="synConstant">&quot;B&quot;</span>:[<span class="synConstant">&quot;a&quot;</span>, <span class="synConstant">&quot;b&quot;</span>]}) &gt;&gt;&gt; df A B <span class="synConstant">0</span> hoge a <span class="synConstant">1</span> fuga b &gt;&gt;&gt; pd.get_dummies(df) A_fuga A_hoge B_a B_b <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> </pre><p> 問題なさそうに見えますか?</p><p> でも、複数のデータに対して適用しようとするととたんに大問題が発生します。普通、kaggleのコンペとかならtrainとtestのデータはあるわけですよね。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; df_train = pd.DataFrame({<span class="synConstant">&quot;A&quot;</span>:[<span class="synConstant">&quot;hoge&quot;</span>, <span class="synConstant">&quot;fuga&quot;</span>], <span class="synConstant">&quot;B&quot;</span>:[<span class="synConstant">&quot;a&quot;</span>, <span class="synConstant">&quot;b&quot;</span>]}) &gt;&gt;&gt; df_test = pd.DataFrame({<span class="synConstant">&quot;A&quot;</span>:[<span class="synConstant">&quot;hoge&quot;</span>, <span class="synConstant">&quot;piyo&quot;</span>], <span class="synConstant">&quot;B&quot;</span>:[<span class="synConstant">&quot;a&quot;</span>, <span class="synConstant">&quot;c&quot;</span>]}) &gt;&gt;&gt; pd.get_dummies(df_train) A_fuga A_hoge B_a B_b <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> &gt;&gt;&gt; pd.get_dummies(df_test) A_hoge A_piyo B_a B_c <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">1</span> </pre><p> shapeは同じ。だけど、各カラムの意味するものは異なっています。一致しているのはB_aだけという惨状です。</p><p> ユニークな要素は6つあるので、下のようになればまずまずの結果と言っていいかもしれません。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># trainに対して</span> A_fuga A_hoge A_piyo B_a B_b B_c <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synComment"># testに対して</span> A_fuga A_hoge A_piyo B_a B_b B_c <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> </pre><p> 実際は学習データに含まれない値なんて落ちてくれて構わない(逆に落ちないと厄介)ので、理想的な結果はこうでしょうか。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># trainに対して</span> A_fuga A_hoge B_a B_b <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synComment"># testに対して</span> A_fuga A_hoge B_a B_b <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">0</span> </pre><p> ドキュメントを軽く読んでいろいろ試した感じ、これをget_dummiesで得るのは無理っぽいです。つかえねー。<br /> (私が見落としているだけかもしれないので、「できるよ」という人はコメントで教えて下さい。確認した上で記事に反映させていただきます。)<br /> (↑さっそくコメントを頂いて、追記させていただきました。この章の末尾を御覧ください。)</p><p> こういう問題があるので、get_dummiesはダメと言っています。</p><p> なお、先に示した6列のデータなら、pandas.concatしてから変換すれば得ることができます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; ret = pd.get_dummies(pd.concat([df_train, df_test])) A_fuga A_hoge A_piyo B_a B_b B_c <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> &gt;&gt;&gt; ret = pd.get_dummies(pd.concat([df_train, df_test])) &gt;&gt;&gt; X_train, X_test = ret.iloc[:<span class="synConstant">2</span>], ret.iloc[<span class="synConstant">2</span>:] &gt;&gt;&gt; X_train A_fuga A_hoge A_piyo B_a B_b B_c <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> &gt;&gt;&gt; X_test A_fuga A_hoge A_piyo B_a B_b B_c <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> <span class="synConstant">0</span> <span class="synConstant">0</span> <span class="synConstant">1</span> </pre><p> これをやっているコードも見かけたことがありますが、「予測モデル側で学習データぜんぶ持っておくの?」ということを考えると現実的なソリューションではないでしょう。おすすめしません。</p><p><b>追記</b><br />  列をpandas.Categorical型とすれば、明示的にカテゴリを指定することで変換が可能なようです。</p> <blockquote> <pre class="code lang-python" data-lang="python" data-unlink>df_train = pd.DataFrame({<span class="synConstant">&quot;A&quot;</span>: [<span class="synConstant">&quot;hoge&quot;</span>, <span class="synConstant">&quot;fuga&quot;</span>], <span class="synConstant">&quot;B&quot;</span>: [<span class="synConstant">&quot;a&quot;</span>, <span class="synConstant">&quot;b&quot;</span>]}) df_test = pd.DataFrame({<span class="synConstant">&quot;A&quot;</span>: [<span class="synConstant">&quot;hoge&quot;</span>, <span class="synConstant">&quot;piyo&quot;</span>], <span class="synConstant">&quot;B&quot;</span>: [<span class="synConstant">&quot;a&quot;</span>, <span class="synConstant">&quot;c&quot;</span>]}) df_test[<span class="synConstant">&quot;A&quot;</span>] = pd.Categorical(df_test[<span class="synConstant">&quot;A&quot;</span>], categories=[<span class="synConstant">&quot;hoge&quot;</span>, <span class="synConstant">&quot;fuga&quot;</span>]) df_test[<span class="synConstant">&quot;B&quot;</span>] = pd.Categorical(df_test[<span class="synConstant">&quot;B&quot;</span>], categories=[<span class="synConstant">&quot;a&quot;</span>, <span class="synConstant">&quot;b&quot;</span>]) pd.get_dummies(df_test) </pre><p><a href="http://tenergima.hateblo.jp/">&#x30C6;&#x30CA;&#x30B8;&#x30DE;&#x69D8;</a>コメントより</p> </blockquote> <p> scikit-learnでやるのと比べて記述は増えますが、pandasの枠組みの中で取り扱うこと自体は可能なようです。</p><p> <a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Categorical.html">pandas.Categorical &mdash; pandas 0.25.1 documentation</a><br /> </p> </div> <div class="section"> <h3>代替する方法</h3> <p> sklearnのOneHotEncoderで変換すれば一発です。</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html">sklearn.preprocessing.OneHotEncoder &mdash; scikit-learn 0.21.3 documentation</a><br /> </p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.preprocessing <span class="synPreProc">import</span> OneHotEncoder &gt;&gt;&gt; ohe = OneHotEncoder(handle_unknown=<span class="synConstant">&quot;ignore&quot;</span>, sparse=<span class="synIdentifier">False</span>) &gt;&gt;&gt; ohe.fit(df_train) OneHotEncoder(categorical_features=<span class="synIdentifier">None</span>, categories=<span class="synIdentifier">None</span>, drop=<span class="synIdentifier">None</span>, dtype=&lt;<span class="synStatement">class</span> <span class="synConstant">'numpy.float64'</span>&gt;, handle_unknown=<span class="synConstant">'ignore'</span>, n_values=<span class="synIdentifier">None</span>, sparse=<span class="synIdentifier">False</span>) &gt;&gt;&gt; ohe.transform(df_train) array([[<span class="synConstant">0.</span>, <span class="synConstant">1.</span>, <span class="synConstant">1.</span>, <span class="synConstant">0.</span>], [<span class="synConstant">1.</span>, <span class="synConstant">0.</span>, <span class="synConstant">0.</span>, <span class="synConstant">1.</span>]]) &gt;&gt;&gt; ohe.transform(df_test) array([[<span class="synConstant">0.</span>, <span class="synConstant">1.</span>, <span class="synConstant">1.</span>, <span class="synConstant">0.</span>], [<span class="synConstant">0.</span>, <span class="synConstant">0.</span>, <span class="synConstant">0.</span>, <span class="synConstant">0.</span>]]) </pre><p> 一撃で理想的な結果を得られています。scikit-learnは偉大ですね。</p><p> pandasなんて最初から要らなかった。</p><p> これはscikit-learnのモデルなので、Pipelineなどと組み合わせて使うのにも親和性が高いです。というか、そのように使ってください(Pipelineにすることで、transformするべきところでfit_transformするといった凡ミスを防げます)。</p><p> 使い方についてはこっちの記事も参照してください。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2018%2F12%2F02%2F042049" title="【python】sklearnでのカテゴリ変数の取り扱いまとめ LabelEncoder, OneHotEncoderなど - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2018/12/02/042049">&#x3010;python&#x3011;sklearn&#x3067;&#x306E;&#x30AB;&#x30C6;&#x30B4;&#x30EA;&#x5909;&#x6570;&#x306E;&#x53D6;&#x308A;&#x6271;&#x3044;&#x307E;&#x3068;&#x3081; LabelEncoder, OneHotEncoder&#x306A;&#x3069; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><p> ColumnTransformerと組み合わせると使い方の幅が広がります。カテゴリ変数だけ投げて数値変数はそのまま通すといった処理が可能になります。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F06%2F29%2F194801" title="scikit-learnのColumnTransformerを使ってみる - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/06/29/194801">scikit-learn&#x306E;ColumnTransformer&#x3092;&#x4F7F;&#x3063;&#x3066;&#x307F;&#x308B; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><p> あとこれは完全に余談ですが、その気になればnanもsklearnの中で落とせます。前処理からすべてscikit-learnの枠組みの中で書けるので、pandasの出る幕はCSVの読み込みと探索的データ分析でやる各種プロットとか以外にはないと言っても過言ではないでしょう。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fscikit-learn.org%2Fstable%2Fmodules%2Fimpute.html" title="5.4. Imputation of missing values — scikit-learn 0.21.3 documentation" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://scikit-learn.org/stable/modules/impute.html">5.4. Imputation of missing values &mdash; scikit-learn 0.21.3 documentation</a></p><p> ……え、結果がpandasのDataFrameになってないのがいやだって? そしたら、結果を改めてDataFrameに変換すればいいんじゃないでしょうか。こんな感じですね。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; pd.DataFrame(ohe.transform(df_test), columns=[c + <span class="synConstant">&quot;_&quot;</span> + x <span class="synStatement">for</span> lst, c <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(ohe.categories_, <span class="synConstant">&quot;AB&quot;</span>) <span class="synStatement">for</span> x <span class="synStatement">in</span> lst]) A_fuga A_hoge B_a B_b <span class="synConstant">0</span> <span class="synConstant">0.0</span> <span class="synConstant">1.0</span> <span class="synConstant">1.0</span> <span class="synConstant">0.0</span> <span class="synConstant">1</span> <span class="synConstant">0.0</span> <span class="synConstant">0.0</span> <span class="synConstant">0.0</span> <span class="synConstant">0.0</span> </pre> </div> <div class="section"> <h3>まとめ</h3> <p> pandasは基本的にこういう用途には向いていないので、安易に使わないほうが良いという話です。機械学習ライブラリとして枠組みを整備してくれているscikit-learnは偉大なツールなので、積極的にこっちを活用していけばいいと思います。</p> </div> hayataka2049 【python】クラスを関数として使う hatenablog://entry/26006613396339104 2019-08-15T15:10:38+09:00 2019-08-31T22:14:05+09:00 はじめに クラスはcallされたら自分のクラスのインスタンスを返さないといけないと思っていませんか? 一般論としてはその通りなのですが、Pythonではそうしないメカニズムも用意されています。 __new__を使えば割となんでもできます。もっとも、実用的な用途は相当限られるでしょう。 原理 こんな感じ。 クラス クラスは呼び出し可能です。そのオブジェクトは通常、そのクラスの新たなインスタンスのファクトリとして振舞いますが、 __new__() をオーバーライドして、バリエーションを持たせることもできます。クラス cls の新しいインスタンスを作るために呼び出されます。3. データモデル — P… <div class="section"> <h3>はじめに</h3> <p> クラスはcallされたら自分のクラスのインスタンスを返さないといけないと思っていませんか? 一般論としてはその通りなのですが、Pythonではそうしないメカニズムも用意されています。</p><p> __new__を使えば割となんでもできます。もっとも、実用的な用途は相当限られるでしょう。</p> </div> <div class="section"> <h3>原理</h3> <p> こんな感じ。</p> <blockquote> <p>クラス<br /> クラスは呼び出し可能です。そのオブジェクトは通常、そのクラスの新たなインスタンスのファクトリとして振舞いますが、 __new__() をオーバーライドして、バリエーションを持たせることもできます。</p><p>クラス cls の新しいインスタンスを作るために呼び出されます。</p><p><a href="https://docs.python.org/ja/3/reference/datamodel.html#object.__new__">3. &#x30C7;&#x30FC;&#x30BF;&#x30E2;&#x30C7;&#x30EB; &mdash; Python 3.7.4 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a></p> </blockquote> <p> なんのこっちゃと思うかもしれませんが、</p> <ul> <li>Hogeがcallされる(コード的にはHoge(*args, **kwargs))</li> <li>Hoge.__new__(Hoge, *args, **kwargs)がcallされる。返り値はHogeのインスタンス(を適切に生成して返すのが__new__の役割ということ)で、この段階でselfができる</li> <li>Hoge.__init__がcallされる(いつもの)</li> </ul><p> という流れで処理されている訳ですね。</p> </div> <div class="section"> <h3>やってみる</h3> <p> 以上を理解したら、自由自在にクラスを関数として使えます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">class</span> <span class="synIdentifier">add</span>: <span class="synStatement">def</span> <span class="synIdentifier">__new__</span>(cls, a, b): <span class="synStatement">return</span> a + b <span class="synIdentifier">print</span>(add(<span class="synConstant">1</span>, <span class="synConstant">2</span>)) <span class="synComment"># =&gt; 3</span> </pre><p> 意外と単純です。</p><p> なお、目ざとい人は「あれ、__init__の引数フォーマットでエラーにならない?」と思うかもしれませんが、上述のドキュメントにも書いてある通り</p> <blockquote> <p>__new__() が cls のインスタンスを返さない場合、インスタンスの __init__() メソッドは呼び出されません。</p> </blockquote> <p> <br />  という仕様があり、上記の記述を可能にしています。</p> </div> <div class="section"> <h3>実用的(?)な用途</h3> <p> 別にわざわざこんなことをする必要はないのですが、使いみちがまったくないわけではありません。</p> <div class="section"> <h4>引数によってインスタンスを返すときとそうでないときがあったりする</h4> <p> コンストラクタに引数が与えられたかどうかによって返り値の型が変わるパターン。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">class</span> <span class="synIdentifier">Hoge</span>: <span class="synStatement">def</span> <span class="synIdentifier">__new__</span>(cls, a=<span class="synIdentifier">None</span>): <span class="synStatement">if</span> a <span class="synStatement">is</span> <span class="synIdentifier">None</span>: <span class="synStatement">return</span> <span class="synConstant">&quot;this is not Hoge's instance, is a str&quot;</span> <span class="synStatement">else</span>: self = <span class="synIdentifier">super</span>().__new__(cls) <span class="synStatement">return</span> self <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, a=<span class="synIdentifier">None</span>): self.a = a <span class="synStatement">def</span> <span class="synIdentifier">__repr__</span>(self): <span class="synStatement">return</span> <span class="synConstant">&quot;Hoge(a={})&quot;</span>.format(self.a) h1 = Hoge() h2 = Hoge(<span class="synConstant">42</span>) <span class="synIdentifier">print</span>(h1) <span class="synComment"># =&gt; this is not Hoge's instance, is a str</span> <span class="synIdentifier">print</span>(h2) <span class="synComment"># =&gt; Hoge(a=42)</span> </pre><p> 面白い使いみちがあるかもしれません。かえって厄介かもしれません。</p> </div> <div class="section"> <h4>デコレータを書くのに使う</h4> <p> デコレータをクラスで作るというテクニックがあります。</p><p> 参考:<br /> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F01%2F17%2F055322" title="【python】クラスでデコレータ! - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/01/17/055322">&#x3010;python&#x3011;&#x30AF;&#x30E9;&#x30B9;&#x3067;&#x30C7;&#x30B3;&#x30EC;&#x30FC;&#x30BF;&#xFF01; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><p> で、このときに</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">deco</span> <span class="synStatement">def</span> <span class="synIdentifier">f</span>(args): ... <span class="synPreProc">@</span><span class="synIdentifier">deco</span>(options) <span class="synStatement">def</span> <span class="synIdentifier">f</span>(args): ... </pre><p> の両方に対応させたいケースがあります。面倒くさいですね。</p><p> こういうときは__new__で切り分ける実装になるのだと思いますが、書こうとしたら相当ややこしかったのでコードは割愛させてください。</p> </div> </div> <div class="section"> <h3>まとめ</h3> <p> 知れば知るほど奥が深い動的言語の世界。</p><p> クラスが自分のインスタンスを返さなくても良いということは知らなかった。</p> </div> hayataka2049 【python】scikit-learnで大規模疎行列を扱うときのTips hatenablog://entry/26006613394927158 2019-08-14T02:33:31+09:00 2020-02-08T07:35:57+09:00 はじめに 自然言語処理などで大規模疎行列を扱うことがあります。一昔前はNLPといえばこれでした(最近は低次元密行列で表現することのほうが多いですが)。 疎行列はその特性をうまく生かして扱うとパフォーマンス上のメリットが得られる反面、うかつにdenseな表現に展開してしまうと効率が悪くなって激遅になったり、あっさりメモリから溢れたりします。 scikit-learnでやる場合、うっかり使うと自動的にdenseな表現に展開されてしまう、という事故が起こりがちで、要するに使えるモデルに制約があり、注意が必要です。その辺の基本的なことをまとめておきます。 目次 はじめに 疎行列ってなに? 特徴抽出する… <div class="section"> <h3 id="はじめに">はじめに</h3> <p> 自然言語処理などで大規模疎行列を扱うことがあります。一昔前はNLPといえばこれでした(最近は低次元密行列で表現することのほうが多いですが)。</p><p> 疎行列はその特性をうまく生かして扱うとパフォーマンス上のメリットが得られる反面、うかつにdenseな表現に展開してしまうと効率が悪くなって激遅になったり、あっさりメモリから溢れたりします。</p><p> scikit-learnでやる場合、うっかり使うと自動的にdenseな表現に展開されてしまう、という事故が起こりがちで、要するに使えるモデルに制約があり、注意が必要です。その辺の基本的なことをまとめておきます。</p><p> 目次</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#疎行列ってなに">疎行列ってなに?</a></li> <li><a href="#特徴抽出する">特徴抽出する</a></li> <li><a href="#特徴選択する">特徴選択する</a></li> <li><a href="#標準化する">標準化する</a></li> <li><a href="#次元削減する">次元削減する</a></li> <li><a href="#その他の各種transformerを使う">その他の各種transformerを使う</a></li> <li><a href="#分類や回帰など予測に使う">分類や回帰など、予測に使う</a></li> <li><a href="#実際にやってみる">実際にやってみる</a></li> <li><a href="#まとめ">まとめ</a></li> </ul> </div> <div class="section"> <h3 id="疎行列ってなに">疎行列ってなに?</h3> <p> まず、Pythonで疎行列といえばscipy.sparse.csr_matrixなどのことです。</p><p><a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csr_matrix.html">scipy.sparse.csr_matrix &mdash; SciPy v1.4.1 Reference Guide</a></p><p> 内部の構造の詳細などは、こちらの記事が参考になります。</p><p><a href="http://hamukazu.com/2014/12/03/internal-data-structure-scipy-sparse/">scipy.sparse&#x306E;&#x5185;&#x90E8;&#x30C7;&#x30FC;&#x30BF;&#x69CB;&#x9020; &ndash; &#x306F;&#x3080;&#x304B;&#x305A;&#xFF01;</a></p><p> 重要なのは、まずこの方式にすると「メモリ効率がいい」ということです。これは単純に嬉しいですし、CPUキャッシュのことを考えても、パフォーマンス上大きなメリットがあります。また、0の要素は飛ばして探索できるので、うまく使うと効率も良くなります。</p><p> 大規模疎行列を相手にするときは、できるだけ疎行列のまま取り扱うことが重要になります。</p> </div> <div class="section"> <h3 id="特徴抽出する">特徴抽出する</h3> <p> 自然言語処理系のタスクだと、CountVectorizerやTfidfVectorizerが使えます。どちらもデフォルトでcsr_matrixを返してくれるので、素直に使えば疎行列が出てきます。</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html">sklearn.feature_extraction.text.CountVectorizer &mdash; scikit-learn 0.22.1 documentation</a><br /> <a href="https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html">sklearn.feature_extraction.text.TfidfVectorizer &mdash; scikit-learn 0.22.1 documentation</a></p><p> もう少し幅広いタスクで使いたい場合は、DictVectrizerが便利でしょう。こちらもデフォルトではsparseな表現を返します(オプションでnumpy配列を返すようにすることも可能)。</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.DictVectorizer.html">sklearn.feature_extraction.DictVectorizer &mdash; scikit-learn 0.22.1 documentation</a><br /> </p> </div> <div class="section"> <h3 id="特徴選択する">特徴選択する</h3> <p> 特徴抽出したあと素直に使うとだいたい変数が多すぎて使いづらいので、普通は変数選択をすると思います。sklearn.feature_selectionのものなら、これはだいたいデフォルトで疎行列のままの取り扱いに対応しています。</p><p><a href="https://scikit-learn.org/stable/modules/feature_selection.html">1.13. Feature selection &mdash; scikit-learn 0.22.1 documentation</a></p><p> 疎行列としてinputすれば疎行列で出てきます。速度もそうした方が速いです。</p><p><a href="https://www.haya-programming.com/entry/2019/07/14/043514">sklearn&#x306E;&#x5909;&#x6570;&#x9078;&#x629E;&#x306F;&#x758E;&#x884C;&#x5217;&#x578B;&#xFF08;csr_matrix&#xFF09;&#x3067;&#x3084;&#x308B;&#x3068;&#x901F;&#x3044;&#x3063;&#x307D;&#x3044;&#x3088; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a><br /> </p> </div> <div class="section"> <h3 id="標準化する">標準化する</h3> <p> StandardScalerで標準化する場合は、with_mean=Falseを指定してください。これは平均0にしない標準化です。標準化の式の分子で平均を引かないものです。</p><p> それだけで疎行列型のまま標準化することができます。</p> <blockquote> <p>This scaler can also be applied to sparse CSR or CSC matrices by passing with_mean=False to avoid breaking the sparsity structure of the data.</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html">sklearn.preprocessing.StandardScaler &mdash; scikit-learn 0.22.1 documentation</a></p> </blockquote> <p><br /> <a href="https://www.haya-programming.com/entry/2020/02/08/073241">scikit-learn&#x306E;StandardScaler&#x3067;&#x758E;&#x884C;&#x5217;&#x578B;&#x306E;&#x307E;&#x307E;&#x6A19;&#x6E96;&#x5316;&#x3059;&#x308B; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><p></p> </div> <div class="section"> <h3 id="次元削減する">次元削減する</h3> <p> Truncated SVDという素晴らしい手法があり、実装上も疎行列に対応しているので、こちらを使ってください。逆に、これ以外の選択肢は(おそらく)ありません。</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.TruncatedSVD.html">sklearn.decomposition.TruncatedSVD &mdash; scikit-learn 0.22.1 documentation</a></p><p> ただし、次元削減した方が良いのか、しない方が良いのかはなんとも言えません。次元削減は行わないで、疎行列型のまま後段のモデルに突っ込むという選択もあるからです。ぶっちゃけ性能は大して変わらないし、次元削減に時間がかかるのと大規模密行列になってしまうぶんだけ遅くなるかもしれない……という微妙な性質があります。</p><p> それでも、次元削減が必要ならやればできます。</p> </div> <div class="section"> <h3 id="その他の各種transformerを使う">その他の各種transformerを使う</h3> <p> transformの返り値がsparse matrixになるかどうかを確認してください。油断しているとnumpy配列に変換されます。</p><p> リファレンスからある程度は読み取れますが、わからないことも多いので、一度動かして確かめた方が良いと思います。</p> </div> <div class="section"> <h3 id="分類や回帰など予測に使う">分類や回帰など、予測に使う</h3> <p> Truncated SVDで次元削減をした場合は勝手にnumpy配列になっているので、どんなモデルにも入力できます(実用的な速度と性能が両立できるかは別)。</p><p> csr_matrixのまま突っ込む場合は、そのまま入力できるestimatorとできないestimatorがあるので、注意が必要です。これを確認するには、リファレンスのfitメソッドのパラメータを見ます。</p><p> たとえばRandomForestClassifierだと、</p> <blockquote> <p>X : array-like or sparse matrix of shape = [n_samples, n_features]</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html#sklearn.ensemble.RandomForestClassifier.fit">3.2.4.3.1. sklearn.ensemble.RandomForestClassifier &mdash; scikit-learn 0.22.1 documentation</a></p> </blockquote> <p> という記述があり、sparse matrixと書いてあるのが「疎行列型でも受け付けて、適切に取り扱ってあげますよ」という意味です。一方、たとえばLinearDiscriminantAnalysisだと(あまり使う人もいないと思いますが)、</p> <blockquote> <p>X : array-like, shape (n_samples, n_features)</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.discriminant_analysis.LinearDiscriminantAnalysis.html#sklearn.discriminant_analysis.LinearDiscriminantAnalysis.fit">sklearn.discriminant_analysis.LinearDiscriminantAnalysis &mdash; scikit-learn 0.22.1 documentation</a></p> </blockquote> <p> と書いてあります。array-likeのときは、渡せば動くけど、内部的にはdenseな表現(numpy配列)に変換されてしまう公算が大きいです。でもけっきょくのところはよくわからないので、実際に入れて動くかどうか試してみた方が良いでしょう。</p><p> 他に実例は省略しますがnumpy arrayと書いてあるときも(たぶん)あり、この場合はたぶんsparse matrixだとエラーを吐きます。</p><p> 実際に動かしてみないと挙動がわからないこともままあるので、突っ込んでみてエラーが出ないか、メモリ消費が異常に膨れ上がらないかを確認しながら作業した方が良いと思います。</p><p> 以下は疎行列型でも行ける(と思う)代表的なestimatorのリストです。</p><p> <b>分類器</b></p> <ul> <li>sklearn.ensemble.RandomForestClassifier</li> <li>sklearn.svm.SVC</li> <li>sklearn.svm.LinearSCV</li> <li>sklearn.naive_bayes.MultinomialNB</li> </ul><p> 非負のみ。文書分類向き</p> <ul> <li>sklearn.linear_model.LogisticRegression</li> </ul><p> <b>回帰モデル</b></p> <ul> <li>sklearn.ensemble.RandomForestRegressor</li> <li>sklearn.svm.SVR</li> <li>sklearn.linear_model.ElasticNet</li> </ul><p> 代表的なものはだいたい対応しているけど、たまに使えないのもあるという感じです。</p> </div> <div class="section"> <h3 id="実際にやってみる">実際にやってみる</h3> <p> 20newsgroupsの文書ベクトルを返してくれるものがあるので、それでやります。</p> <blockquote> <p>Classes 20<br /> Samples total 18846<br /> Dimensionality 130107<br /> Features real</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.datasets.fetch_20newsgroups_vectorized.html">sklearn.datasets.fetch_20newsgroups_vectorized &mdash; scikit-learn 0.22.1 documentation</a></p> </blockquote> <p><a href="https://www.haya-programming.com/entry/2018/12/26/034613">sklearn&#x306E;fetch_20newsgroups_vectorized&#x3067;&#x30D9;&#x30AF;&#x30C8;&#x30EB;&#x5316;&#x3055;&#x308C;&#x305F;20 newsgroups&#x3092;&#x8A66;&#x3059; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><p> ご覧の通りでかいので、ナイーブにnumpy配列に変換して扱おうとすると苦労します。前にやったときは密行列に変換しようとしていろいろ苦労していましたが、疎行列型のままやった方がシンプルです。</p><p> もちろん通例通り、Pipelineを使ってモデルを組み合わせます。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2018%2F02%2F22%2F234011" title="【python】sklearnのPipelineを使うとできること - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2018/02/22/234011">&#x3010;python&#x3011;sklearn&#x306E;Pipeline&#x3092;&#x4F7F;&#x3046;&#x3068;&#x3067;&#x304D;&#x308B;&#x3053;&#x3068; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a><br /> </p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> fetch_20newsgroups_vectorized <span class="synPreProc">from</span> sklearn.feature_selection <span class="synPreProc">import</span> SelectKBest <span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> TruncatedSVD <span class="synPreProc">from</span> sklearn.svm <span class="synPreProc">import</span> LinearSVC <span class="synPreProc">from</span> sklearn.pipeline <span class="synPreProc">import</span> Pipeline <span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> classification_report <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): train = fetch_20newsgroups_vectorized(subset=<span class="synConstant">&quot;train&quot;</span>) test = fetch_20newsgroups_vectorized(subset=<span class="synConstant">&quot;test&quot;</span>) X_train, y_train = train.data, train.target X_test, y_test = test.data, test.target target_names = train.target_names skb = SelectKBest(k=<span class="synConstant">5000</span>) tsvd = TruncatedSVD(n_components=<span class="synConstant">1000</span>) svm = LinearSVC() clf = Pipeline([(<span class="synConstant">&quot;skb&quot;</span>, skb), (<span class="synConstant">&quot;tsvd&quot;</span>, tsvd), (<span class="synConstant">&quot;svm&quot;</span>, svm)]) clf.fit(X_train, y_train) y_pred = clf.predict(X_test) <span class="synIdentifier">print</span>(classification_report( y_test, y_pred, target_names=target_names)) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> 実行したところ、1分くらいで処理が完了しました。パフォーマンスのよさが伺えます。</p><p> 結果。</p> <pre class="code lang-python" data-lang="python" data-unlink> precision recall f1-score support alt.atheism <span class="synConstant">0.70</span> <span class="synConstant">0.68</span> <span class="synConstant">0.69</span> <span class="synConstant">319</span> comp.graphics <span class="synConstant">0.70</span> <span class="synConstant">0.71</span> <span class="synConstant">0.70</span> <span class="synConstant">389</span> comp.os.ms-windows.misc <span class="synConstant">0.71</span> <span class="synConstant">0.71</span> <span class="synConstant">0.71</span> <span class="synConstant">394</span> comp.sys.ibm.pc.hardware <span class="synConstant">0.66</span> <span class="synConstant">0.64</span> <span class="synConstant">0.65</span> <span class="synConstant">392</span> comp.sys.mac.hardware <span class="synConstant">0.76</span> <span class="synConstant">0.76</span> <span class="synConstant">0.76</span> <span class="synConstant">385</span> comp.windows.x <span class="synConstant">0.79</span> <span class="synConstant">0.72</span> <span class="synConstant">0.75</span> <span class="synConstant">395</span> misc.forsale <span class="synConstant">0.81</span> <span class="synConstant">0.87</span> <span class="synConstant">0.84</span> <span class="synConstant">390</span> rec.autos <span class="synConstant">0.83</span> <span class="synConstant">0.83</span> <span class="synConstant">0.83</span> <span class="synConstant">396</span> rec.motorcycles <span class="synConstant">0.91</span> <span class="synConstant">0.90</span> <span class="synConstant">0.90</span> <span class="synConstant">398</span> rec.sport.baseball <span class="synConstant">0.84</span> <span class="synConstant">0.89</span> <span class="synConstant">0.87</span> <span class="synConstant">397</span> rec.sport.hockey <span class="synConstant">0.92</span> <span class="synConstant">0.95</span> <span class="synConstant">0.94</span> <span class="synConstant">399</span> sci.crypt <span class="synConstant">0.89</span> <span class="synConstant">0.88</span> <span class="synConstant">0.89</span> <span class="synConstant">396</span> sci.electronics <span class="synConstant">0.66</span> <span class="synConstant">0.65</span> <span class="synConstant">0.66</span> <span class="synConstant">393</span> sci.med <span class="synConstant">0.81</span> <span class="synConstant">0.78</span> <span class="synConstant">0.79</span> <span class="synConstant">396</span> sci.space <span class="synConstant">0.86</span> <span class="synConstant">0.87</span> <span class="synConstant">0.86</span> <span class="synConstant">394</span> soc.religion.christian <span class="synConstant">0.76</span> <span class="synConstant">0.91</span> <span class="synConstant">0.83</span> <span class="synConstant">398</span> talk.politics.guns <span class="synConstant">0.68</span> <span class="synConstant">0.88</span> <span class="synConstant">0.77</span> <span class="synConstant">364</span> talk.politics.mideast <span class="synConstant">0.91</span> <span class="synConstant">0.81</span> <span class="synConstant">0.85</span> <span class="synConstant">376</span> talk.politics.misc <span class="synConstant">0.71</span> <span class="synConstant">0.54</span> <span class="synConstant">0.62</span> <span class="synConstant">310</span> talk.religion.misc <span class="synConstant">0.61</span> <span class="synConstant">0.41</span> <span class="synConstant">0.49</span> <span class="synConstant">251</span> accuracy <span class="synConstant">0.78</span> <span class="synConstant">7532</span> macro avg <span class="synConstant">0.78</span> <span class="synConstant">0.77</span> <span class="synConstant">0.77</span> <span class="synConstant">7532</span> weighted avg <span class="synConstant">0.78</span> <span class="synConstant">0.78</span> <span class="synConstant">0.78</span> <span class="synConstant">7532</span> </pre><p> 今回は性能を重視していないのでこの程度です。このタスクだとできるだけ次元を維持したまま(疎行列型のまま)ナイーブベイズに入れたほうが速くて性能が出るという知見を以前に得ています。その場合は0.83くらいまで出ています。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F05%2F15%2F232429" title="【python】sklearnのfetch_20newsgroupsで文書分類を試す(5) - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/05/15/232429">&#x3010;python&#x3011;sklearn&#x306E;fetch_20newsgroups&#x3067;&#x6587;&#x66F8;&#x5206;&#x985E;&#x3092;&#x8A66;&#x3059;(5) - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a><br /> </p> </div> <div class="section"> <h3 id="まとめ">まとめ</h3> <p> うまく疎行列型配列を使うと数桁くらい時間を節約できます。ぜひ活用してみてください。</p><p> こちらの記事もおすすめです。<br /> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F05%2F10%2F191111" title="scikit-learnのモデルに疎行列(csr_matrix)を渡したときの速度 - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/05/10/191111">scikit-learn&#x306E;&#x30E2;&#x30C7;&#x30EB;&#x306B;&#x758E;&#x884C;&#x5217;&#xFF08;csr_matrix&#xFF09;&#x3092;&#x6E21;&#x3057;&#x305F;&#x3068;&#x304D;&#x306E;&#x901F;&#x5EA6; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a><br />  </p> </div> hayataka2049 【python】itertools.chainを使って複数のiterableを一つにまとめる hatenablog://entry/26006613390314776 2019-08-09T07:57:04+09:00 2019-08-09T08:00:27+09:00 概要 複数のiterable(リストとか)を結合させてループさせたいときがあります。 >>> lst1 = [1, 2, 3] >>> lst2 = [4, 5, 6] >>> # 1, 2, 3, 4, 5, 6というループをやりたい 連結すればできたりしますが、余計なメモリを確保するのでスマートではないし、パフォーマンスが気になります。 >>> for x in lst1 + lst2: ... print(x) ... 1 2 3 4 5 6 というマニアックなお悩みを解決してくれるのがitertools.chainです。 使い方 リファレンスを見ると使い方が書いてあります。iterto… <div class="section"> <h3>概要</h3> <p> 複数のiterable(リストとか)を結合させてループさせたいときがあります。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; lst1 = [<span class="synConstant">1</span>, <span class="synConstant">2</span>, <span class="synConstant">3</span>] &gt;&gt;&gt; lst2 = [<span class="synConstant">4</span>, <span class="synConstant">5</span>, <span class="synConstant">6</span>] &gt;&gt;&gt; <span class="synComment"># 1, 2, 3, 4, 5, 6というループをやりたい</span> </pre><p> 連結すればできたりしますが、余計なメモリを確保するのでスマートではないし、パフォーマンスが気になります。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synStatement">for</span> x <span class="synStatement">in</span> lst1 + lst2: ... <span class="synIdentifier">print</span>(x) ... <span class="synConstant">1</span> <span class="synConstant">2</span> <span class="synConstant">3</span> <span class="synConstant">4</span> <span class="synConstant">5</span> <span class="synConstant">6</span> </pre><p> というマニアックなお悩みを解決してくれるのがitertools.chainです。</p> </div> <div class="section"> <h3>使い方</h3> <p> リファレンスを見ると使い方が書いてあります。</p><p><a href="https://docs.python.org/ja/3/library/itertools.html#itertools.chain">itertools --- &#x52B9;&#x7387;&#x7684;&#x306A;&#x30EB;&#x30FC;&#x30D7;&#x5B9F;&#x884C;&#x306E;&#x305F;&#x3081;&#x306E;&#x30A4;&#x30C6;&#x30EC;&#x30FC;&#x30BF;&#x751F;&#x6210;&#x95A2;&#x6570; &mdash; Python 3.7.4 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a></p><p> まあまったく難しいことはなく、</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">from</span> itertools <span class="synPreProc">import</span> chain &gt;&gt;&gt; <span class="synStatement">for</span> x <span class="synStatement">in</span> chain(lst1, lst2): ... <span class="synIdentifier">print</span>(x) ... <span class="synConstant">1</span> <span class="synConstant">2</span> <span class="synConstant">3</span> <span class="synConstant">4</span> <span class="synConstant">5</span> <span class="synConstant">6</span> </pre><p> たったこれだけで使えます。単純ですね。</p> </div> <div class="section"> <h3>速度を測る</h3> <p> 実際にパフォーマンスがいいのかどうか試してみましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> timeit <span class="synPreProc">from</span> itertools <span class="synPreProc">import</span> chain <span class="synStatement">def</span> <span class="synIdentifier">f1</span>(a, b): <span class="synStatement">for</span> x <span class="synStatement">in</span> a + b: <span class="synStatement">pass</span> <span class="synStatement">def</span> <span class="synIdentifier">f2</span>(a, b): <span class="synStatement">for</span> x <span class="synStatement">in</span> chain(a, b): <span class="synStatement">pass</span> <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): <span class="synStatement">for</span> n <span class="synStatement">in</span> [<span class="synConstant">10</span>, <span class="synConstant">100</span>, <span class="synConstant">1000</span>, <span class="synConstant">10000</span>]: <span class="synIdentifier">print</span>(n) a, b = <span class="synIdentifier">list</span>(<span class="synIdentifier">range</span>(n)), <span class="synIdentifier">list</span>(<span class="synIdentifier">range</span>(n, <span class="synConstant">2</span>*n)) t1 = timeit.timeit(<span class="synStatement">lambda</span> : f1(a, b), number=<span class="synConstant">1000</span>) t2 = timeit.timeit(<span class="synStatement">lambda</span> : f2(a, b), number=<span class="synConstant">1000</span>) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;{0:.6f}</span><span class="synSpecial">\n</span><span class="synConstant">{1:.6f}&quot;</span>.format(t1, t2)) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant">10</span> <span class="synConstant">0.000657</span> <span class="synConstant">0.000822</span> <span class="synConstant">100</span> <span class="synConstant">0.005128</span> <span class="synConstant">0.004024</span> <span class="synConstant">1000</span> <span class="synConstant">0.032347</span> <span class="synConstant">0.025703</span> <span class="synConstant">10000</span> <span class="synConstant">0.249460</span> <span class="synConstant">0.232873</span> <span class="synConstant">&quot;&quot;&quot;</span> </pre><p> なんかどっちでも良いような気が……論理的にはchainを使ったほうがスマートですが、</p> <ul> <li>リストの結合と値の取り出しは速い</li> <li>chainはかえって遅い</li> </ul><p> というあたりが本質かな、という気がします。</p><p> 素のリストとchainの速度も比較してみる。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> timeit <span class="synPreProc">from</span> itertools <span class="synPreProc">import</span> chain <span class="synStatement">def</span> <span class="synIdentifier">f1</span>(lst): <span class="synStatement">for</span> x <span class="synStatement">in</span> lst: <span class="synStatement">pass</span> <span class="synStatement">def</span> <span class="synIdentifier">f2</span>(lst): <span class="synStatement">for</span> x <span class="synStatement">in</span> chain(lst): <span class="synStatement">pass</span> <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): lst = <span class="synIdentifier">list</span>(<span class="synIdentifier">range</span>(<span class="synConstant">10</span>**<span class="synConstant">4</span>)) t1 = timeit.timeit(<span class="synStatement">lambda</span> : f1(lst), number=<span class="synConstant">1000</span>) t2 = timeit.timeit(<span class="synStatement">lambda</span> : f2(lst), number=<span class="synConstant">1000</span>) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;{0:.6f}</span><span class="synSpecial">\n</span><span class="synConstant">{1:.6f}&quot;</span>.format(t1, t2)) <span class="synComment"># 0.092083</span> <span class="synComment"># 0.130389</span> <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> かえって遅い可能性もあるということです。</p> </div> <div class="section"> <h3>諦めて二重forで書く</h3> <p> これで良いんじゃ……</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> timeit <span class="synPreProc">from</span> itertools <span class="synPreProc">import</span> chain <span class="synStatement">def</span> <span class="synIdentifier">f1</span>(a, b): <span class="synStatement">for</span> x <span class="synStatement">in</span> a + b: <span class="synStatement">pass</span> <span class="synStatement">def</span> <span class="synIdentifier">f2</span>(a, b): <span class="synStatement">for</span> x <span class="synStatement">in</span> chain(a, b): <span class="synStatement">pass</span> <span class="synStatement">def</span> <span class="synIdentifier">f3</span>(a, b): <span class="synStatement">for</span> lst <span class="synStatement">in</span> [a, b]: <span class="synStatement">for</span> x <span class="synStatement">in</span> lst: <span class="synStatement">pass</span> <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): <span class="synStatement">for</span> n <span class="synStatement">in</span> [<span class="synConstant">10</span>, <span class="synConstant">100</span>, <span class="synConstant">1000</span>, <span class="synConstant">10000</span>]: <span class="synIdentifier">print</span>(n) a, b = <span class="synIdentifier">list</span>(<span class="synIdentifier">range</span>(n)), <span class="synIdentifier">list</span>(<span class="synIdentifier">range</span>(n, <span class="synConstant">2</span>*n)) t1 = timeit.timeit(<span class="synStatement">lambda</span> : f1(a, b), number=<span class="synConstant">1000</span>) t2 = timeit.timeit(<span class="synStatement">lambda</span> : f2(a, b), number=<span class="synConstant">1000</span>) t3 = timeit.timeit(<span class="synStatement">lambda</span> : f3(a, b), number=<span class="synConstant">1000</span>) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;{0:.6f}</span><span class="synSpecial">\n</span><span class="synConstant">{1:.6f}</span><span class="synSpecial">\n</span><span class="synConstant">{2:.6f}&quot;</span>.format(t1, t2, t3)) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() <span class="synConstant">&quot;&quot;&quot; =&gt;</span> <span class="synConstant">10</span> <span class="synConstant">0.000675</span> <span class="synConstant">0.000837</span> <span class="synConstant">0.000726</span> <span class="synConstant">100</span> <span class="synConstant">0.003831</span> <span class="synConstant">0.003594</span> <span class="synConstant">0.002471</span> <span class="synConstant">1000</span> <span class="synConstant">0.033191</span> <span class="synConstant">0.025650</span> <span class="synConstant">0.016758</span> <span class="synConstant">10000</span> <span class="synConstant">0.279039</span> <span class="synConstant">0.225997</span> <span class="synConstant">0.163289</span> <span class="synConstant">&quot;&quot;&quot;</span> </pre><p> こんなのが一番速いです。無理に一つにまとめない方がパフォーマンス上良い、というしょうもない結論に。<br />  </p> </div> <div class="section"> <h3>まとめ</h3> <p> なんかchainするとパフォーマンス上は微妙な気もしますが、使うと連結よりスマートな感じのコードになるので、好みで使うと良いでしょう。</p> </div> hayataka2049 【python】namedtupleはすごい(きもちわるい) hatenablog://entry/26006613385678620 2019-08-09T03:49:44+09:00 2019-08-09T03:49:44+09:00 namedtupleは存在は知ってたけど、使い方を知ったら「すげえ(きめえ)」という感想にしかならなかったので、記事に書きました。 <div class="section"> <h3>概要</h3> <p> namedtupleは存在は知ってたけど、使い方を知ったら「すげえ(きめえ)」という感想にしかならなかったので、記事に書きました<a href="#f-bc8d4e35" name="fn-bc8d4e35" title="そんなことも知らないでPython書いてたのかよって感じですが">*1</a>。</p> </div> <div class="section"> <h3>namedtupleはクラスではない</h3> <p> 普通は、namedtupleというクラスがあると思いませんか? さにあらず、関数です。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">from</span> collections <span class="synPreProc">import</span> namedtuple &gt;&gt;&gt; namedtuple &lt;function namedtuple at <span class="synConstant">0x7f8c288800d0</span>&gt; </pre><p> お、おう。使い方を調べます。</p> <blockquote> <p>名前付きフィールドを持つタプルのサブクラスを作成するファクトリ関数</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synComment"># Basic example</span> &gt;&gt;&gt; Point = namedtuple(<span class="synConstant">'Point'</span>, [<span class="synConstant">'x'</span>, <span class="synConstant">'y'</span>]) &gt;&gt;&gt; p = Point(<span class="synConstant">11</span>, y=<span class="synConstant">22</span>) <span class="synComment"># instantiate with positional or keyword arguments</span> &gt;&gt;&gt; p[<span class="synConstant">0</span>] + p[<span class="synConstant">1</span>] <span class="synComment"># indexable like the plain tuple (11, 22)</span> <span class="synConstant">33</span> &gt;&gt;&gt; x, y = p <span class="synComment"># unpack like a regular tuple</span> &gt;&gt;&gt; x, y (<span class="synConstant">11</span>, <span class="synConstant">22</span>) &gt;&gt;&gt; p.x + p.y <span class="synComment"># fields also accessible by name</span> <span class="synConstant">33</span> &gt;&gt;&gt; p <span class="synComment"># readable __repr__ with a name=value style</span> Point(x=<span class="synConstant">11</span>, y=<span class="synConstant">22</span>) </pre><p><a href="https://docs.python.org/ja/3/library/collections.html#collections.namedtuple">collections --- &#x30B3;&#x30F3;&#x30C6;&#x30CA;&#x30C7;&#x30FC;&#x30BF;&#x578B; &mdash; Python 3.7.4 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a></p> </blockquote> <p> あー、そういうものだったの。</p><p> 第一引数はクラス名、第二引数は「属性」の名前ですね。はい。こいつはクラスを返します。</p><p> 大切なことなのでもう一度書きます。<b>namedtupleはクラスを生成して返す関数</b>です。</p><p> 動的言語の面目躍如という感じ……。</p> </div> <div class="section"> <h3>一応タプルらしい</h3> <p> タプルらしく、中身を変えられないし、hashableです。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; Hoge = namedtuple(<span class="synConstant">&quot;Hoge&quot;</span>, <span class="synConstant">&quot;a b&quot;</span>) <span class="synComment"># 空白orカンマ区切り文字列で属性リストにできる</span> &gt;&gt;&gt; h = Hoge(a=<span class="synConstant">1</span>, b=<span class="synConstant">2</span>) &gt;&gt;&gt; h Hoge(a=<span class="synConstant">1</span>, b=<span class="synConstant">2</span>) &gt;&gt;&gt; h[<span class="synConstant">0</span>] = <span class="synConstant">2</span> Traceback (most recent call last): File <span class="synConstant">&quot;&lt;stdin&gt;&quot;</span>, line <span class="synConstant">1</span>, <span class="synStatement">in</span> &lt;module&gt; <span class="synType">TypeError</span>: <span class="synConstant">'Hoge'</span> <span class="synIdentifier">object</span> does <span class="synStatement">not</span> support item assignment &gt;&gt;&gt; h.a = <span class="synConstant">2</span> Traceback (most recent call last): File <span class="synConstant">&quot;&lt;stdin&gt;&quot;</span>, line <span class="synConstant">1</span>, <span class="synStatement">in</span> &lt;module&gt; <span class="synType">AttributeError</span>: can<span class="synConstant">'t set attribute</span> <span class="synConstant">&gt;&gt;&gt; {h}</span> <span class="synConstant">{Hoge(a=1, b=2)}</span> </pre><p> だいたい期待する性質は持っていると言っていいでしょう。いや、気持ち悪いですが。</p> </div> <div class="section"> <h3>でもしょせんただのクラスなので</h3> <p> 継承して好きなメソッドを定義したりできます。すごい!</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synStatement">class</span> <span class="synIdentifier">Hoge2</span>(Hoge): ... <span class="synStatement">def</span> <span class="synIdentifier">product_of_a_and_b</span>(self): ... <span class="synStatement">return</span> self.a * self.b ... &gt;&gt;&gt; h2 = Hoge2(a=<span class="synConstant">10</span>, b=<span class="synConstant">100</span>) &gt;&gt;&gt; h2.product_of_a_and_b() <span class="synConstant">1000</span> </pre><p> 元のクラスが要らなければ、こういう書き方もあります。上のドキュメントに紹介されている例です。</p> <blockquote> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synStatement">class</span> <span class="synIdentifier">Point</span>(namedtuple(<span class="synConstant">'Point'</span>, [<span class="synConstant">'x'</span>, <span class="synConstant">'y'</span>])): ... __slots__ = () ... @<span class="synIdentifier">property</span> ... <span class="synStatement">def</span> <span class="synIdentifier">hypot</span>(self): ... <span class="synStatement">return</span> (self.x ** <span class="synConstant">2</span> + self.y ** <span class="synConstant">2</span>) ** <span class="synConstant">0.5</span> ... <span class="synStatement">def</span> <span class="synIdentifier">__str__</span>(self): ... <span class="synStatement">return</span> <span class="synConstant">'Point: x=%6.3f y=%6.3f hypot=%6.3f'</span> % (self.x, self.y, self.hypot) &gt;&gt;&gt; <span class="synStatement">for</span> p <span class="synStatement">in</span> Point(<span class="synConstant">3</span>, <span class="synConstant">4</span>), Point(<span class="synConstant">14</span>, <span class="synConstant">5</span>/<span class="synConstant">7</span>): ... <span class="synIdentifier">print</span>(p) Point: x= <span class="synConstant">3.000</span> y= <span class="synConstant">4.000</span> hypot= <span class="synConstant">5.000</span> Point: x=<span class="synConstant">14.000</span> y= <span class="synConstant">0.714</span> hypot=<span class="synConstant">14.018</span> </pre> </blockquote> <p> 理解はできるけど、すごく○○です……。</p> </div> <div class="section"> <h3>まとめ</h3> <p> こういうものだとは知らなかったのと、発想が斜め上を行っているのと、いろいろな意味で驚きました。</p><p> クラス定義のコンストラクタ(__init__)の記述を省略したりするのにも使えるそうです。言われるとなるほどと思いますが、pythonはほんと奥が深いですね……。</p> </div><div class="footnote"> <p class="footnote"><a href="#fn-bc8d4e35" name="f-bc8d4e35" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">そんなことも知らないでPython書いてたのかよって感じですが</span></p> </div> hayataka2049