株式会社ホクソエムのブログ R, Python, データ分析, 機械学習 2023-02-10T11:34:18+09:00 hoxo_m Hatena::Blog hatenablog://blog/10257846132610878846 書籍「評価指標入門」の出版に寄せて 〜監修の想い(O・MO・I) hatenablog://entry/4207112889961858242 2023-02-10T11:34:18+09:00 2023-10-19T10:56:17+09:00 監修させていただいている評価指標入門なんですが、株式会社ホクソエムの代表取締役CEOである私、牧山幸史(以下、コージー牧山)、はじめてこの企画を聞いた時は「その特徴は単に評価指標をまとめた辞書やないかい!そういう”売れそうだから書く”みたいな商業的なマインドが学術界の価値を貶め云々」と思ったのですが、上梓された高柳さん(タカヤナギ=サン)の壮大なるお話を聞いているうちに「これはひょっとして数理モデリングとしても奥深い世界が広がっているの?面白いかも!」と思い監修社として名乗りを上げた次第です。 一方、本書の内容と皆様の期待値がややズレているのではないか?と不安には思っておりまして、これは監修社… <p>監修させていただいている<a href="https://gihyo.jp/book/2023/978-4-297-13314-6">評価指標入門</a>なんですが、株式会社ホクソエムの代表取締役CEOである私、牧山幸史(以下、コージー牧山)、はじめてこの企画を聞いた時は「その特徴は単に評価指標をまとめた辞書やないかい!そういう”売れそうだから書く”みたいな商業的なマインドが学術界の価値を貶め云々」と思ったのですが、上梓された高柳さん(タカヤナギ=サン)の壮大なるお話を聞いているうちに「これはひょっとして数理モデリングとしても奥深い世界が広がっているの?面白いかも!」と思い監修社として名乗りを上げた次第です。</p> <p>一方、本書の内容と皆様の期待値がややズレているのではないか?と不安には思っておりまして、これは監修社として一肌脱いでおかなければいかんなと、自然界に存在する第5の力「期待値調整力」を見せなければならないなと思い筆を取った次第です。</p> <p>以下、私、コージー牧山の視点で「書いてあること・書いてないこと・書きそびれた」ことをここに記すことにより潜在、あるいはすでに予約されてしまった方々への期待値調整とさせていただきます。</p> <h2 id="書いていること">書いていること</h2> <p>まずMAEやRMSE、またAUCやF値などベタベタな評価指標については網羅的ではないですが説明しています。また「結局、方針としてどういう評価指標を選んだらいいんだ?」という問いに対する答えも用意しており、その”答え”を御社のビジネスに適用するための考え方、及びその考え方に則ったいくつかの例を示しています。コージー牧山的には評価指標自体よりも、”考え方”の方が重要だと考えており、そちらの点を厚めにやって欲しいなと執筆陣にお願いしこのような形となりました。こう考える詳しい理由は次の”書いていないこと”を参照ください。</p> <h2 id="書いていないこと">書いていないこと</h2> <p>逆に書いていないことでいうと「学術書・論文から評価指標を徹底的に調べ上げ全列挙」はしていません。これは本質的に無意味です。なぜなら、仮に全列挙していたとして、読者が「どこかに私の問題を解くための答えがあるんじゃないか?」と期待し辞書を”A”から”Z”まで全てBrute Forceに探索するようなものだからです。しかし、そもそもそこに答えはないと。結局のところ、”私の問題”が固有すぎてヘルスチェックの意味で教科書的な評価指標を使えはせよ、都度都度自分たちのやっていることに対して合わせて妥当な結論を出すべきであるとコージー牧山は考えます。私以外私じゃないんです、当たり前だけどね。</p> <h2 id="書きそびれたこと">書きそびれたこと</h2> <p>相当な言い訳ですが、今回はスケジュールやコージー牧山・執筆陣・編集含めた制作部隊の楽屋/舞台裏がかなりグチャグチャしてしまったため、書き損ねている話が結構出てしまいました。例えば</p> <ul> <li>カリブレーションとは何か?どういう場面で重要になるのか?評価指標とどう関連するのか?</li> <li>推薦/ランキングでの評価指標</li> <li>LTVなど評価指標を用いて陽に書き下すことが難しい問題への処方箋</li> <li>データサイエンティストなら誰もが持っていた第六感</li> <li>AIの心を壊すとある星の光</li> <li>PoC死の他にあった4つの結末</li> </ul> <p>などがあります。少なくとも1つ目については追々、本BLOGか技術評論社さんのサイト上で記事として公開したいと思います。</p> <p>以上、ご購入検討の際に本文章がお役に立つと幸いです。</p> <p>※この文章は株式会社ホクソエム開発”HoxoGPT”のサポートを受け執筆されました</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/4297133148?tag=hoxom-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/41dseAwl4+L._SL500_.jpg" class="hatena-asin-detail-image" alt="評価指標入門〜データサイエンスとビジネスをつなぐ架け橋" title="評価指標入門〜データサイエンスとビジネスをつなぐ架け橋"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/4297133148?tag=hoxom-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">評価指標入門〜データサイエンスとビジネスをつなぐ架け橋</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%B9%E2%CC%F8%20%BF%B5%B0%EC" class="keyword">高柳 慎一</a>,<a href="https://d.hatena.ne.jp/keyword/%C4%B9%C5%C4%CE%E7%BB%CE" class="keyword">長田怜士</a></li><li>技術評論社</li></ul><a href="https://www.amazon.co.jp/dp/4297133148?tag=hoxom-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> hoxo_m RでCQT(Constant-Q変換)をやってみる hatenablog://entry/13574176438078183529 2022-03-31T09:30:00+09:00 2022-03-31T09:30:02+09:00 ホクソエムサポーターの松本です。音楽を作ったり聴いたりするのが趣味なので、音楽分析に興味があります。音データの分析にはPythonだとlibrosaというとても便利なパッケージがあるのですが、Rにはそういった汎用的なパッケージがなくてちょっと不便です。 最近ふとRでCQT(Constant-Q変換)をしてみたいと思い、既存のパッケージを使ってできないか探してみたところ特に見つからなかったので、どのように実装すればいいのか調べてみました。 スペクトログラムについて 音声や音楽データの分析を行う際には生の波形をそのまま扱うのではなく、スペクトログラム(時間周波数表現)に変換したものを特徴量として利… <p>ホクソエムサポーターの松本です。音楽を作ったり聴いたりするのが趣味なので、音楽分析に興味があります。音データの分析にはPythonだとlibrosaというとても便利なパッケージがあるのですが、Rにはそういった汎用的なパッケージがなくてちょっと不便です。 最近ふとRでCQT(Constant-Q変換)をしてみたいと思い、既存のパッケージを使ってできないか探してみたところ特に見つからなかったので、どのように実装すればいいのか調べてみました。</p> <h2>スペクトログラムについて</h2> <p>音声や音楽データの分析を行う際には生の波形をそのまま扱うのではなく、スペクトログラム(時間周波数表現)に変換したものを特徴量として利用することがあります。下の画像は「あいうえお」という音声を録音したデータを表したものです。</p> <div class="images-row mceNonEditable"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsumototo180/20220330/20220330172638.png" alt="f:id:matsumototo180:20220330172638p:plain" width="727" height="454" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsumototo180/20220330/20220330172709.png" alt="f:id:matsumototo180:20220330172709p:plain" width="727" height="454" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></div> <p>左図の波形データは横軸は時間、縦軸は振幅を表します。右図のスペクトログラムは横軸は時間、縦軸は周波数、色はその時間・周波数の成分の強度を表します。</p> <p>このように視覚化した際、波形データは音の大きさの変化くらいしか分かりませんが、スペクトログラムは周波数ごとにどのように時間変化していくか等が分かります。視覚的に特徴を捉えやすく、画像のようにも扱うことができるので画像処理の手法を応用した分析や変換にも利用しやすいといった利点があります。</p> <p>スペクトログラムはSTFT(短時間フーリエ変換)によって算出するのが一般的ですが、この方法の問題点として、音高の観点からすると、低域で周波数分解能が低くなるという点があります。人間の聴覚特性として低域では周波数の違いに敏感である(周波数分解能が高い)が、高域になるにつれて鈍感になる(周波数分解能が低くなっていく)という特性があります。音高の国際的な基準はラ(A)=440Hzとなっていますが、1オクターブ上のラは880Hzでちょうど二倍の周波数になります。1オクターブの間には12個の音階があり、これらは公比 $\sqrt[12]{2}$ の等比数列となっています。</p> <p>STFTでは切り取った信号に対して全ての周波数で同じ窓幅でフーリエ変換を行いますが、CQTではこうした特性を考慮して対数周波数を利用し、かつ周波数ごとに窓幅を変化させて変換することで、音高に対応して周波数分解能を変化させています。</p> <h2>実装</h2> <p>以下ではピアノ演奏を録音した音源を使って、各種プロットを行う例を示します。</p> <p><audio src="https://drive.google.com/uc?id=1QmvJmvQhF_zIM4cMdEbAAokyPPX9G8Fg" controls></audio></p> <p>音源の読み込みには tuneR パッケージ、スペクトログラムのプロットには seewave パッケージを利用しています。</p> <pre class="code lang-r" data-lang="r" data-unlink><span class="synPreProc">library</span><span class="synSpecial">(</span>tuneR<span class="synSpecial">)</span> <span class="synPreProc">library</span><span class="synSpecial">(</span>seewave<span class="synSpecial">)</span> <span class="synComment"># wavファイルの読み込み</span> wav <span class="synStatement">&lt;-</span> readWave<span class="synSpecial">(</span><span class="synConstant">&quot;pf.wav&quot;</span><span class="synSpecial">)</span> <span class="synComment"># 波形のプロット</span> plot<span class="synSpecial">((</span>seq<span class="synSpecial">(</span>wav<span class="synSpecial">@</span>left<span class="synSpecial">)</span> <span class="synStatement">-</span> <span class="synConstant">1</span><span class="synSpecial">)</span> <span class="synStatement">*</span> <span class="synConstant">1</span> <span class="synStatement">/</span> wav<span class="synSpecial">@</span>samp.rate<span class="synSpecial">,</span> wav<span class="synSpecial">@</span>left<span class="synSpecial">,</span> type<span class="synStatement">=</span><span class="synConstant">&quot;l&quot;</span><span class="synSpecial">,</span> xlab<span class="synStatement">=</span><span class="synConstant">&quot;time&quot;</span><span class="synSpecial">,</span> ylab<span class="synStatement">=</span><span class="synConstant">&quot;amplitude&quot;</span><span class="synSpecial">)</span> <span class="synComment"># STFTによるスペクトログラムのプロット</span> ggspectro<span class="synSpecial">(</span>wav<span class="synSpecial">,</span> ovlp<span class="synStatement">=</span><span class="synConstant">50</span><span class="synSpecial">)</span> <span class="synStatement">+</span> scale_y_continuous<span class="synSpecial">()</span> <span class="synStatement">+</span> geom_tile<span class="synSpecial">(</span>aes<span class="synSpecial">(</span>fill<span class="synStatement">=</span>amplitude<span class="synSpecial">))</span> </pre> <div class="images-row mceNonEditable"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsumototo180/20220330/20220330172724.png" alt="f:id:matsumototo180:20220330172724p:plain" width="727" height="457" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsumototo180/20220330/20220330172722.png" alt="f:id:matsumototo180:20220330172722p:plain" width="727" height="457" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></div> <p><br></p> <p>次にCQTを算出してみます。実装はこちらの記事を参考にさせて頂きました。 <a href="https://yukara-13.hatenablog.com/entry/2013/12/01/222742">音楽プログラミングの超入門(仮): 【Python】 Constant-Q 変換 (対数周波数スペクトログラム)</a></p> <pre class="code lang-r" data-lang="r" data-unlink><span class="synPreProc">library</span><span class="synSpecial">(</span>signal<span class="synSpecial">)</span> <span class="synComment"># ハミング窓を使うためにsignalパッケージを利用</span> <span class="synComment"># パラメータの設定</span> qrate <span class="synStatement">&lt;-</span> <span class="synConstant">1</span> fmin <span class="synStatement">&lt;-</span> <span class="synConstant">60</span> fmax <span class="synStatement">&lt;-</span> <span class="synConstant">6000</span> bins_per_octave <span class="synStatement">&lt;-</span> <span class="synConstant">12</span> hop_length <span class="synStatement">&lt;-</span> <span class="synConstant">512</span> two_pi_j <span class="synStatement">&lt;-</span> <span class="synConstant">2</span> <span class="synStatement">*</span> <span class="synConstant">pi</span> <span class="synStatement">*</span> <span class="synConstant">1i</span> <span class="synComment"># 対数周波数ビンの数とそれらに対応する周波数を算出</span> nfreq <span class="synStatement">&lt;-</span> round<span class="synSpecial">(</span>log2<span class="synSpecial">(</span>fmax <span class="synStatement">/</span> fmin<span class="synSpecial">)</span> <span class="synStatement">/</span> <span class="synSpecial">(</span><span class="synConstant">1</span> <span class="synStatement">/</span> bins_per_octave<span class="synSpecial">))</span> <span class="synStatement">+</span> <span class="synConstant">1</span> freqs <span class="synStatement">&lt;-</span> fmin <span class="synStatement">*</span> <span class="synSpecial">(</span><span class="synConstant">2</span> <span class="synStatement">**</span> <span class="synSpecial">((</span>seq<span class="synSpecial">(</span>nfreq<span class="synSpecial">)</span> <span class="synStatement">-</span> <span class="synConstant">1</span><span class="synSpecial">)</span> <span class="synStatement">*</span> <span class="synSpecial">(</span><span class="synConstant">1</span> <span class="synStatement">/</span> bins_per_octave<span class="synSpecial">)))</span> <span class="synComment"># CQTを行うフレーム数とそれらに対応する時間を算出</span> nframe <span class="synStatement">&lt;-</span> ceiling<span class="synSpecial">(</span>length<span class="synSpecial">(</span>wav<span class="synSpecial">@</span>left<span class="synSpecial">)</span> <span class="synStatement">/</span> hop_length<span class="synSpecial">)</span> <span class="synStatement">+</span> <span class="synConstant">1</span> times <span class="synStatement">&lt;-</span> seq<span class="synSpecial">(</span>nframe<span class="synSpecial">)</span> <span class="synStatement">*</span> hop_length <span class="synStatement">*</span> <span class="synSpecial">(</span><span class="synConstant">1</span> <span class="synStatement">/</span> wav<span class="synSpecial">@</span>samp.rate<span class="synSpecial">)</span> <span class="synComment"># 窓幅を設定するためのパラメータ</span> Q <span class="synStatement">&lt;-</span> <span class="synSpecial">(</span><span class="synConstant">1</span> <span class="synStatement">/</span> <span class="synSpecial">((</span><span class="synConstant">2</span> <span class="synStatement">**</span> <span class="synSpecial">(</span><span class="synConstant">1</span> <span class="synStatement">/</span> bins_per_octave<span class="synSpecial">))</span> <span class="synStatement">-</span> <span class="synConstant">1</span><span class="synSpecial">))</span> <span class="synStatement">*</span> qrate <span class="synComment"># CQTスペクトログラムのための行列を初期化</span> ret <span class="synStatement">&lt;-</span> <span class="synType">matrix</span><span class="synSpecial">(</span><span class="synConstant">0</span><span class="synSpecial">,</span> nframe<span class="synSpecial">,</span> nfreq<span class="synSpecial">)</span> <span class="synComment"># 周波数ごとに窓幅を変えてCQT</span> <span class="synStatement">for</span> <span class="synSpecial">(</span>k <span class="synStatement">in</span> seq<span class="synSpecial">(</span>nfreq<span class="synSpecial">))</span> <span class="synSpecial">{</span> nsample <span class="synStatement">&lt;-</span> round<span class="synSpecial">(</span>wav<span class="synSpecial">@</span>samp.rate <span class="synStatement">*</span> Q <span class="synStatement">/</span> freqs<span class="synSpecial">[</span>k<span class="synSpecial">])</span> hsample <span class="synStatement">&lt;-</span> round<span class="synSpecial">(</span>nsample <span class="synStatement">/</span> <span class="synConstant">2</span><span class="synSpecial">)</span> phase <span class="synStatement">&lt;-</span> exp<span class="synSpecial">(</span><span class="synStatement">-</span>two_pi_j <span class="synStatement">*</span> Q <span class="synStatement">*</span> seq<span class="synSpecial">(</span>nsample<span class="synSpecial">)</span> <span class="synStatement">/</span> nsample<span class="synSpecial">)</span> weight <span class="synStatement">&lt;-</span> phase <span class="synStatement">*</span> hamming<span class="synSpecial">(</span>nsample<span class="synSpecial">)</span> <span class="synComment"># 各フレームごとに信号を切り取る→重みを掛けてretに代入</span> <span class="synStatement">for</span> <span class="synSpecial">(</span>iiter <span class="synStatement">in</span> seq<span class="synSpecial">(</span>nframe<span class="synSpecial">)){</span> iframe <span class="synStatement">&lt;-</span> iiter t <span class="synStatement">&lt;-</span> <span class="synSpecial">(</span>iframe <span class="synStatement">-</span> <span class="synConstant">1</span><span class="synSpecial">)</span> <span class="synStatement">*</span> hop_length <span class="synStatement">*</span> <span class="synSpecial">(</span><span class="synConstant">1</span> <span class="synStatement">/</span> wav<span class="synSpecial">@</span>samp.rate<span class="synSpecial">)</span> istart <span class="synStatement">&lt;-</span> <span class="synSpecial">(</span>iframe <span class="synStatement">-</span> <span class="synConstant">1</span><span class="synSpecial">)</span> <span class="synStatement">*</span> hop_length <span class="synStatement">-</span> hsample iend <span class="synStatement">&lt;-</span> istart <span class="synStatement">+</span> nsample sig_start <span class="synStatement">&lt;-</span> min<span class="synSpecial">(</span>max<span class="synSpecial">(</span><span class="synConstant">1</span><span class="synSpecial">,</span> istart <span class="synStatement">+</span> <span class="synConstant">1</span><span class="synSpecial">),</span> length<span class="synSpecial">(</span>wav<span class="synSpecial">@</span>left<span class="synSpecial">))</span> sig_end <span class="synStatement">&lt;-</span> min<span class="synSpecial">(</span>max<span class="synSpecial">(</span><span class="synConstant">0</span><span class="synSpecial">,</span> iend<span class="synSpecial">),</span> length<span class="synSpecial">(</span>wav<span class="synSpecial">@</span>left<span class="synSpecial">))</span> win_start <span class="synStatement">&lt;-</span> min<span class="synSpecial">(</span>max<span class="synSpecial">(</span><span class="synConstant">1</span><span class="synSpecial">,</span> sig_start <span class="synStatement">-</span> istart<span class="synSpecial">),</span> nsample<span class="synSpecial">)</span> win_end <span class="synStatement">&lt;-</span> min<span class="synSpecial">(</span>max<span class="synSpecial">(</span><span class="synConstant">0</span><span class="synSpecial">,</span> length<span class="synSpecial">(</span>wav<span class="synSpecial">@</span>left<span class="synSpecial">)</span> <span class="synStatement">-</span> istart<span class="synSpecial">),</span> nsample<span class="synSpecial">)</span> win_slice <span class="synStatement">&lt;-</span> weight<span class="synSpecial">[</span>win_start <span class="synSpecial">:</span> win_end<span class="synSpecial">]</span> y <span class="synStatement">&lt;-</span> wav<span class="synSpecial">@</span>left<span class="synSpecial">[</span>sig_start <span class="synSpecial">:</span> sig_end<span class="synSpecial">]</span> ret<span class="synSpecial">[</span>iiter<span class="synSpecial">,</span> k<span class="synSpecial">]</span> <span class="synStatement">&lt;-</span> sum<span class="synSpecial">(</span>y <span class="synStatement">*</span> win_slice<span class="synSpecial">)</span> <span class="synStatement">/</span> nsample <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <p><br></p> <p>次に算出したCQTスペクトログラムをggplotを使ってプロットしてみます。実装はseewaveパッケージのggspectroを参考にさせて頂きました。<a href="https://github.com/cran/seewave">https://github.com/cran/seewave</a></p> <pre class="code lang-r" data-lang="r" data-unlink><span class="synPreProc">library</span><span class="synSpecial">(</span>ggplot2<span class="synSpecial">)</span> P <span class="synStatement">&lt;-</span> abs<span class="synSpecial">(</span>t<span class="synSpecial">(</span>ret<span class="synSpecial">))</span> <span class="synComment"># 絶対値に変換し、振幅を取り出す</span> P <span class="synStatement">&lt;-</span> P<span class="synStatement">/</span>max<span class="synSpecial">(</span>P<span class="synSpecial">)</span> <span class="synComment"># ノーマライズ</span> P<span class="synSpecial">[</span>P <span class="synStatement">==</span> <span class="synConstant">0</span><span class="synSpecial">]</span> <span class="synStatement">&lt;-</span> .Machine<span class="synSpecial">$</span>double.eps <span class="synComment"># log10(0)で-Infにならないようにする</span> P <span class="synStatement">&lt;-</span> <span class="synConstant">20</span><span class="synStatement">*</span>log10<span class="synSpecial">(</span>P<span class="synSpecial">)</span> <span class="synComment"># dBスケールに変換</span> <span class="synComment"># 外れ値で表示が変にならないように範囲を設定</span> limit_upper <span class="synStatement">&lt;-</span> <span class="synConstant">0</span> limit_lower <span class="synStatement">&lt;-</span> <span class="synStatement">-</span><span class="synConstant">50</span> <span class="synComment"># ggplotで表示するためのデータフレームを作成</span> frequency <span class="synStatement">&lt;-</span> rep<span class="synSpecial">(</span>freqs<span class="synSpecial">,</span> times<span class="synStatement">=</span>ncol<span class="synSpecial">(</span>P<span class="synSpecial">))</span> time <span class="synStatement">&lt;-</span> rep<span class="synSpecial">(</span>times<span class="synSpecial">,</span> each<span class="synStatement">=</span>nrow<span class="synSpecial">(</span>P<span class="synSpecial">))</span> amplitude <span class="synStatement">&lt;-</span> as.vector<span class="synSpecial">(</span>P<span class="synSpecial">)</span> amplitude<span class="synSpecial">[</span>amplitude <span class="synStatement">&lt;</span> limit_lower<span class="synSpecial">]</span> <span class="synStatement">&lt;-</span> limit_lower df <span class="synStatement">&lt;-</span> <span class="synType">data.frame</span><span class="synSpecial">(</span>time<span class="synSpecial">,</span> frequency<span class="synSpecial">,</span> amplitude<span class="synSpecial">)</span> tlab <span class="synStatement">&lt;-</span> <span class="synConstant">&quot;Time (s)&quot;</span> flab <span class="synStatement">&lt;-</span> <span class="synConstant">&quot;Frequency (kHz)&quot;</span> <span class="synComment"># CQTスペクトログラムのプロット</span> ggplot<span class="synSpecial">(</span>df<span class="synSpecial">,</span> aes_string<span class="synSpecial">(</span>x<span class="synStatement">=</span><span class="synConstant">&quot;time&quot;</span><span class="synSpecial">,</span> y<span class="synStatement">=</span><span class="synConstant">&quot;frequency&quot;</span><span class="synSpecial">,</span> z <span class="synStatement">=</span> <span class="synConstant">&quot;amplitude&quot;</span><span class="synSpecial">))</span> <span class="synStatement">+</span> xlab<span class="synSpecial">(</span>tlab<span class="synSpecial">)</span> <span class="synStatement">+</span> ylab<span class="synSpecial">(</span>flab<span class="synSpecial">)</span> <span class="synStatement">+</span> scale_y_log10<span class="synSpecial">(</span>breaks<span class="synStatement">=</span>freqs<span class="synSpecial">[</span>seq<span class="synSpecial">(</span><span class="synConstant">1</span><span class="synSpecial">,</span> length<span class="synSpecial">(</span>freqs<span class="synSpecial">),</span> <span class="synConstant">12</span><span class="synSpecial">)])</span> <span class="synStatement">+</span> geom_tile<span class="synSpecial">(</span>aes<span class="synSpecial">(</span>fill<span class="synStatement">=</span>amplitude<span class="synSpecial">))</span> </pre> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsumototo180/20220330/20220330172719.png" alt="f:id:matsumototo180:20220330172719p:plain" width="727" height="457" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>CQTのスペクトログラムの算出とプロットを行うことができました。</p> <p>プロットを見ると比較的はっきりとピアノで演奏された音が浮き上がっていることが分かります。CQTはメロディーや和音など音楽的な要素の分析によく使われる特徴量で、音楽分析においてはSTFTを使うよりも有用な場合が多いと思います。</p> <h2>参考文献</h2> <ul> <li>高校数学の美しい物語 - 音階と数学 <a href="https://manabitimes.jp/math/1353">https://manabitimes.jp/math/1353</a></li> <li>音楽プログラミングの超入門(仮) - 【Python】 Constant-Q 変換 (対数周波数スペクトログラム) <a href="https://yukara-13.hatenablog.com/entry/2013/12/01/222742">https://yukara-13.hatenablog.com/entry/2013/12/01/222742</a></li> <li>Wizard Notes - 定Q変換 (CQT: Constant-Q Transform) の 解説(音高・コード・メロディの分析向け) <a href="https://www.wizard-notes.com/entry/music-analysis/constant-q-transform">https://www.wizard-notes.com/entry/music-analysis/constant-q-transform</a></li> <li>seewave ~ an R package dedicated to sound analysis and synthesis ~ <a href="https://rug.mnhn.fr/seewave/">https://rug.mnhn.fr/seewave/</a></li> </ul> matsumototo180 機械学習とビジネスを橋渡しするものこそ評価指標であり, ”全てのビジネスは条件付期待値の最大化問題として書ける”仮説についての一考察 hatenablog://entry/26006613780537291 2021-06-27T17:14:21+09:00 2021-06-29T20:22:06+09:00 はじめに 株式会社ホクソエム常務取締役のタカヤナギ=サンです、データサイエンスや意思決定のプロ・経営をしています。 掲題の件、現在、某社さんと”機械学習における評価指標とビジネスの関係、および宇宙の全て”というタイトルの書籍を書いているのですが、 本記事のタイトルにあるような考え方については、論文・書籍などを数多く調査しても未だお目にかかることができず、これをいきなり書籍にしてAmazonレビューなどでフルボッコに叩かれて炎上して枕を涙で濡らすよりも、ある程度小出しにして様々な人々の意見を聞いた方が良いのではないかと思い独断で筆を取った次第です。 筋が良さそうなら論文にするのも良いと思っている… <h2>はじめに</h2> <p>株式会社ホクソエム常務取締役のタカヤナギ=サンです、データサイエンスや意思決定のプロ・経営をしています。</p> <p>掲題の件、現在、某社さんと”機械学習における評価指標とビジネスの関係、および宇宙の全て”というタイトルの書籍を書いているのですが、 本記事のタイトルにあるような考え方については、論文・書籍などを数多く調査しても未だお目にかかることができず、これをいきなり書籍にしてAmazonレビューなどでフルボッコに叩かれて炎上して枕を涙で濡らすよりも、ある程度小出しにして様々な人々の意見を聞いた方が良いのではないかと思い独断で筆を取った次第です。</p> <p>筋が良さそうなら論文にするのも良いと思っている。</p> <p>「いや、そんなもん会社のBLOGに書くんじゃねーよ💢」という話があるかもしれないですが、ここは私の保有する会社なので何の問題もない、don't you?</p> <h2>こういうビジネスを考えてみよう</h2> <p><a href="https://www.itmedia.co.jp/news/articles/2106/22/news118.html">&#x300C;&#x3053;&#x306E;&#x4EBA;&#x3001;&#x5BB6;&#x8CC3;&#x3092;&#x6EDE;&#x7D0D;&#x3057;&#x305D;&#x3046;&#xFF1F;&#x300D;AI&#x304C;&#x4E88;&#x6E2C; &#x5165;&#x5C45;&#x5BE9;&#x67FB;&#x3092;45&#x5206;&rarr;16&#x5206;&#x306B; - ITmedia NEWS</a></p> <p>という話を見かけたので、これに似た題材にしてみようと思う。</p> <p>問題を簡単にするため、</p> <ul> <li>貴方は家賃保証会社の経営者としてN人の入居希望者に対し、家賃保証審査を行う</li> <li>貴方にできることは各入居希望者と家賃保証契約を「する・しない(結ぶ・結ばない)」を選ぶことだけ</li> <li>家賃保証契約をする場合(入居希望者に実際に入居して貰う場合) <ul> <li>ある期間(1年とか3年とか)において一度も滞納することなく家賃を払い続けてくれたら、入居者からS 円/人の保証料を手に入れる(これが売上)</li> <li>ある人が1回でも家賃を滞納したら、その人からもらったS円の保証料全額と保証会社としてD円の金額を上乗せして家のオーナーにお渡しする(これが家賃保証会社として見たときの損失)</li> </ul> </li> <li>家賃保証契約をしない場合 <ul> <li>その入居希望者からは何のお金ももらえない(家賃保証契約してないので当たり前)</li> </ul> </li> </ul> <p>という設定で考えてみよう。</p> <p>人件費や家賃などなどのコストは一切考えないことにし、この「家賃保証料 - 滞納への保証金」がこの家賃保証会社の利益です。</p> <h2>そのビジネス、こういう条件付期待値でかけまっせ</h2> <p>このビジネスの利益(ビジネス施策によって条件つけられた条件付き期待利益)を<img src="https://chart.apis.google.com/chart?cht=tx&chl=P" alt="P"/>と書くと、これは以下のように書ける。 ここは所謂「ビジネスの数理モデリング」で、各社・ビジネスによって異なる部分です。 最近はここをどれだけうまく書き下せるかが焦点だと考えています。</p> <div><img src="https://chart.apis.google.com/chart?cht=tx&chl=%0A%20%20%20%20P%20%3D%0A%20%20%20%20%5Csum_%7Bi%3D1%7D%5EN%0A%20%20%20%20%5Cmathbb%7BE%7D%0A%20%20%20%20%5Cleft%5B%0A%20%20%20%20%20%20%20%20%5Cleft%28%0A%20%20%20%20%20%20%20%20%20%20%20%20S%20%5Cmathbf%7B1%7D_%7B%5Cleft%5C%7BF%5Ei%5Cright%5C%7D%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20%0A%20%20%20%20%20%20%20%20%20%20%20%20D%281%20-%20%5Cmathbf%7B1%7D_%7B%5Cleft%5C%7BF%5Ei%5Cright%5C%7D%7D%29%20%0A%20%20%20%20%20%20%20%20%5Cright%29%0A%20%20%20%20%20%20%20%20%5Cmathbf%7B1%7D_%7B%5Cleft%5C%7B%20a_i%20%3D%20%5Ctext%7BGuarantee%7D%20%5Cright%5C%7D%7D%0A%20%20%20%20%20%20%20%20%5Cmid%20%0A%20%20%20%20%20%20%20%20a_i%0A%20%20%20%20%5Cright%5D%0A" alt=" P = \sum_{i=1}^N \mathbb{E} \left[ \left( S \mathbf{1}_{\left\{F^i\right\}} - D(1 - \mathbf{1}_{\left\{F^i\right\}}) \right) \mathbf{1}_{\left\{ a_i = \text{Guarantee} \right\}} \mid a_i \right] "/></div> <p>S、Dは先程説明したとおりの保証料としての儲けとうっかり滞納された場合の損失で 、</p> <div><img src="https://chart.apis.google.com/chart?cht=tx&chl=%5Cmathbf%7B1%7D_%7B%5Cleft%5C%7B%5Cdots%5Cright%5C%7D%7D" alt="\mathbf{1}_{\left\{\dots\right\}}"/></div> <p></p> <p>は <code>{}</code> の中がTRUEなら1, そうでなければ0になるような指示関数であり、 <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20F%5E%20i" alt=" F^ i"/> は「ユーザ i が家賃を滞納しない優良な入居者ならTRUE、そうじゃない(家賃を滞納すれば!)ならFALSE」となるような論理変数です。 同様に<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20a_i%20%3D%20%5Ctext%7BGuarantee%7D" alt=" a_i = \text{Guarantee}"/> は「ビジネス施策として家賃保証する(Guarantee)場合にはTRUEで、そうでなければFALSE」となるような論理変数です。 ようするにユーザiに対するビジネス施策(ここでは家賃保証契約をするかしないかということ)のこと。</p> <p>重要なポイントは</p> <ul> <li><strong>機械学習によるビジネス成果(ここでは利益向上)は、この <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20a_i" alt=" a_i"/> を通じてしか影響しえない</strong></li> <li><strong><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20a_i" alt=" a_i"/>は別に機械学習で決めなくてもよく、人間が赤ペンをなめて適当に決めても良い</strong></li> </ul> <p>という点です。 「機械学習は所詮ビジネスにとってのおまけ、あってもなくても良い」としばしば言われる所以はここにあるのです。</p> <p>しかし、Nがとても大きい場合には、各々の入居希望者iに対して適切なビジネス施策(ここでは家賃保証契約をするかしないか)を提供することは、 人間がやるには大変な手間とコストがかかるため敬遠される作業であり、機械学習やデータサイエンスを極めている我々の出番になるわけです。</p> <p>家賃を保証するかしないか、まさに2値クラス分類であり、プロのデータサイエンティストには朝飯前。</p> <p>サクッとやろうぜ、と。</p> <p>従って、以下の議論では <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20a_i" alt=" a_i"/> は何らかの2値クラス分類モデルで決定するとします。 ここでは"家賃を滞納しない入居者”をPositiveなケースとします。 従って、"家賃を滞納してしまう入居者"のケースがNegativeなケースです。</p> <h2>評価指標 〜機械学習とビジネスを橋渡しするもの〜</h2> <p>ここまでの話と機械学習や評価指標がどうつながっているのか、まだ皆さんその気配を感じれてないと思います。 ですので、ここで、少し先ほどの数式を変形してみましょう。</p> <div><img src="https://chart.apis.google.com/chart?cht=tx&chl=%0A%20%20%20%20P%20%3D%0A%20%20%20%20%5Csum_%7Bi%3D1%7D%5EN%0A%20%20%20%20%5Cleft%5C%7B%0A%20%20%20%20%20%20%20%20%5Cleft%28%0A%20%20%20%20%20%20%20%20%20%20%20%20S%20%2B%20D%0A%20%20%20%20%20%20%20%20%5Cright%29%0A%20%20%20%20%20%20%20%20%5Cmathbb%7BE%7D%0A%20%20%20%20%20%20%20%20%5Cleft%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Cmathbf%7B1%7D_%7B%5Cleft%5C%7BF%5Ei%5Cright%5C%7D%7D%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Cmathbf%7B1%7D_%7B%5Cleft%5C%7B%20a_i%20%3D%20%5Ctext%7BGuarantee%7D%20%5Cright%5C%7D%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Cmid%20%0A%20%20%20%20%20%20%20%20%20%20%20%20a_i%0A%20%20%20%20%20%20%20%20%5Cright%5D%0A%20%20%20%20%20%20%20%20-%0A%20%20%20%20%20%20%20%20D%0A%20%20%20%20%20%20%20%20%5Cmathbb%7BE%7D%0A%20%20%20%20%20%20%20%20%5Cleft%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Cmathbf%7B1%7D_%7B%5Cleft%5C%7B%20a_i%20%3D%20%5Ctext%7BGuarantee%7D%20%5Cright%5C%7D%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Cmid%20%0A%20%20%20%20%20%20%20%20%20%20%20%20a_i%0A%20%20%20%20%20%20%20%20%5Cright%5D%0A%20%20%20%20%5Cright%5C%7D%0A" alt=" P = \sum_{i=1}^N \left\{ \left( S + D \right) \mathbb{E} \left[ \mathbf{1}_{\left\{F^i\right\}} \mathbf{1}_{\left\{ a_i = \text{Guarantee} \right\}} \mid a_i \right] - D \mathbb{E} \left[ \mathbf{1}_{\left\{ a_i = \text{Guarantee} \right\}} \mid a_i \right] \right\} "/></div> <p>この第一項目の期待値の中身の部分をグッと睨んで見て欲しい。 この期待値の中身が1となるのは「実際に入居者が家賃を滞納しなかった、かつ、機械学習モデルの予測結果として"家賃保証契約をする"となっているケース」であり、これは機械学習の評価指標の文脈で言う True Positive (TP) の場合と近そうじゃないか?</p> <p>また同様に第二項目の中身の部分をグッと睨んで欲しい。 こっちは「機械学習モデルにおいて「家賃保証契約締結するぞ!」と予測した場合 1 」になるものであり、 これは(True Positive + False Positive)の個数になりそうではなかろうか?</p> <p>実際、この期待値計算をサンプルサイズ1(つまり、1個人から1データをもらう)としてみたものこそが、我々の手元にあるデータなのであり、我々が唯一手元でいじくり回して機械を学習させるために使えるデータなのである。</p> <p>実際に計算してみると以下のようになる。</p> <div><img src="https://chart.apis.google.com/chart?cht=tx&chl=%0A%20%20%20%20P%20%3D%0A%20%20%20%20%5Cleft%28%0A%20%20%20%20%20%20%20%20S%20%2B%20D%0A%20%20%20%20%5Cright%29%0A%20%20%20%20%5Ctimes%0A%20%20%20%20TP%0A%20%20%20%20-%0A%20%20%20%20D%0A%20%20%20%20%5Ctimes%0A%20%20%20%20%28TP%20%2B%20FP%29%0A" alt=" P = \left( S + D \right) \times TP - D \times (TP + FP) "/></div> <p>さらに、もう少し評価指標を全面に押し出した形式で書くと、</p> <div><img src="https://chart.apis.google.com/chart?cht=tx&chl=%0A%20%20%20%20P%20%3D%0A%20%20%20%20%5Cleft%5C%7B%0A%20%20%20%20%20%20%20%20%5Cleft%28%0A%20%20%20%20%20%20%20%20%20%20%20%20S%20%2B%20D%0A%20%20%20%20%20%20%20%20%5Cright%29%0A%20%20%20%20%20%20%20%20%5Ctimes%0A%20%20%20%20%20%20%20%20%5Ctext%7BPrecision%7D%0A%20%20%20%20%20%20%20%20-%0A%20%20%20%20%20%20%20%20D%0A%20%20%20%20%5Cright%5C%7D%0A%20%20%20%20%28TP%20%2B%20FP%29%0A" alt=" P = \left\{ \left( S + D \right) \times \text{Precision} - D \right\} (TP + FP) "/></div> <p>となる。できた!!!</p> <p>この数式は美しい形式になっており「家賃保証契約をすると機械学習モデルで決定した人数(TP + FP)に、機械学習モデルの評価指標であるPrecisionを適当な重みをかけて、計算したもの」という形式になっている。</p> <p>しばしば私が受ける質問として「2値クラス分類の予測確率のしきい値をどのように決めたら良いでしょうか?」というものがあるが、ここで紹介している問題については今それを答えることができます。 実際に皆さんが機械学習を活用して、このビジネスモデル(利益形式)の商売をしている場合、 家賃保証をする・しない2値分類問題の予測確率のしきい値は、この利益が最大になるようなしきい値を使ってやればよいということになる。 すなわち、ここではPrecision(とTP+FP)という評価指標が機械学習とビジネスを橋渡ししており、家賃保証する・しないを決める予測確率の閾値を制御することで条件付期待値の最大化問題を解き、利益を最大化することができるのだ!やった!</p> <p>ポイントとしては、 <strong>ビジネスモデル(利益計算)がデータサイエンスの教科書どおりのAUCやF値、PrecisionやRecallとはちょっとずれてくる</strong>点です。 ここが非常に重要で、この”ズレ”こそが機械学習をビジネスに適用した際に「あ、あれ?なんかうまく儲かってなくね?」となる一要因だとタカヤナギ=サンは考えています。 そして、厄介なことに「この利益計算こそがビジネスの根幹であり、ある程度の分類はできるだろうが、それは会社ごとに異なるので一般論を打ち立て得ない」という点です。 少なくともビジネスモデル(SaaSモデルなのか売り切りモデルなのかなどなど)ごとには考える必要があるのです。</p> <p>「君が機械学習において何を大事にしたいか?それは各社の皆さんのビジネスモデル次第、や!頑張って考えようぜ!」ということなのです。</p> <h2>まとめ</h2> <p>ここでは家賃保証会社のビジネスを通じて、そのビジネスの利益を条件付期待値として書き下し、それを最大化するための方法が実は機械学習の評価指標と密接にリンクしていることを紹介した。</p> <p>最近考えていることとして、この考え方はビジネス全般において普遍的に成り立つものであり、全てのビジネス(売上、利益などのKPI計算)はこの形式出かけるだろうという仮説を個人的に持っている。</p> <p>この考え方に明らかなミスや間違いを見つけた方は是非 <a href="https://twitter.com/_stakaya">Shinichi Takayanagi (@_stakaya) | Twitter</a> まで連絡してほしい(そして一緒に本を書いて欲しい)。</p> <p>今回はPrecisionだけが顔を出してRecallが一切顔を出してこなかった点にも注目して欲しい。 これは「家賃保証契約をしない」場合において、一切の損失が発生しないと見做しているからであり、そこを何がしかの形で取り込むと彼(Recall)も顔を出してくることになる。 この辺はちゃんと書籍にまとめようと思うので期待して欲しい(私が書くとは言ってない&共著者に書かせる!)</p> shinichi-takayanagi pytest fixtureの地味だけど重要な部分について hatenablog://entry/26006613767426682 2021-05-25T08:37:16+09:00 2021-05-25T10:30:29+09:00 こんにちは。ホクソエム支援部サポーターのPython担当、藤岡です。 最近はデータエンジニア見習いとしてBI周りを触っています。 今回はpytestのfixtureについての記事です。 pytest自体が有名で記事もたくさんあるので、今回は地味だけど重要だと個人的に思っている usefixturesとスコープについて取り上げます。 地味とはいえ、pytestの初心者がfixtureを使いこなすためのステップアップに必要な内容だと思います。 ぜひマスターしていただければ幸いです。 1. 前書き 基礎的なことに関してはこの記事にとても簡潔にまとまっているので、こちらをまず読むのがオススメです。とて… <p>こんにちは。ホクソエム支援部サポーターのPython担当、藤岡です。 最近はデータエンジニア見習いとしてBI周りを触っています。</p> <p>今回はpytestのfixtureについての記事です。 pytest自体が有名で記事もたくさんあるので、今回は地味だけど重要だと個人的に思っている <code>usefixtures</code>とスコープについて取り上げます。</p> <p>地味とはいえ、pytestの初心者がfixtureを使いこなすためのステップアップに必要な内容だと思います。 ぜひマスターしていただければ幸いです。</p> <h2>1. 前書き</h2> <ul> <li>基礎的なことに関しては<a href="https://www.m3tech.blog/entry/pytest-summary">この記事</a>にとても簡潔にまとまっているので、こちらをまず読むのがオススメです。とても良い記事です。</li> <li>pytestは独自の書き方を持ち込んでいるライブラリです。その機能を使いこなすと「綺麗」なコードにはなりますが、反面それは使われている機能を知らない人にとってはこの上なく読みにくいものです。やりすぎて可読性が下がらないよう、用法用量を守りましょう。</li> <li>本稿の環境は<a href="https://github.com/Arten013/fixture_sample">こちらのリポジトリ</a>からcloneできますので、試しながら読んでみてください。</li> </ul> <h2>2. fixtureとusefixtures</h2> <p>pytestのfixtureの機能としてもっとも基本的なものがオブジェクトの生成です。 例えば、</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">values</span>(): <span class="synStatement">return</span> [<span class="synConstant">2</span>, <span class="synConstant">1</span>, <span class="synConstant">3</span>] <span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">sorted_values</span>(): <span class="synStatement">return</span> [<span class="synConstant">1</span>, <span class="synConstant">2</span>, <span class="synConstant">3</span>] <span class="synStatement">def</span> <span class="synIdentifier">test_sorted</span>(values, sorted_values): <span class="synStatement">assert</span> <span class="synIdentifier">sorted</span>(values) == sorted_values </pre> <p>といったようなものです。 おそらく、fixtureのイメージとして一番強いのがこの使い方ではないでしょうか。</p> <p>しかし、実際はそれだけに止まりません。</p> <p>例えば、機械学習のコードなどでは乱数が使用されているため、結果を固定するには乱数シードの固定が必要です。 こうした処理をfixtureとして用意するとこのようになります。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> random <span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">set_seed</span>(): random.seed(<span class="synConstant">0</span>) </pre> <p>このように、何も返さず、テストにただ前処理を施すのもfixtureの機能なのです。</p> <p>さて、少し定義の話をします。 "test fixture"を辞書で引くと「試験装置」と出てきます。 <a href="https://en.wikipedia.org/wiki/Test_fixture">Wikipedia</a>の言葉を借りればtest "environment"、つまり環境です。</p> <p>なので、入出力のオブジェクトはもちろんのこと、乱数シードの固定、データベースやサーバへのコネクション(のスタブ)の確立、さらにファイルやフォルダの生成/削除などもfixtureであり、基本的にはfixtureデコレータを使って実装するべきものです。</p> <p>話を戻しますが、何かしらの処理だけをして値を返さないfixtureはテストケースの引数として渡すのは不適切です。 こういった場面では、<code>usefixtures</code>デコレータを使うことでテスト前にfixtureの処理を実行することができます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">pytest.mark.usefixtures</span>(<span class="synConstant">'set_seed'</span>) <span class="synStatement">def</span> <span class="synIdentifier">test_fix_seed</span>(): rand1 = random.random() ramdom.set_seed(<span class="synConstant">0</span>) rand2 = random.random() <span class="synStatement">assert</span> rand1 == rand2 </pre> <p>しかし、この例ではシードの固定を内部でも一回やっていてイマイチです。</p> <p>というわけで、今度はシードの初期化をさせるのではなく、その処理をするコールバックを返すことで解決します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> random <span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">seed_setter</span>(): <span class="synStatement">return</span> <span class="synStatement">lambda</span>: random.seed(<span class="synConstant">0</span>) <span class="synStatement">def</span> <span class="synIdentifier">test_fix_seed_2</span>(seed_setter): seed_setter() rand1 = random.random() seed_setter() rand2 = random.random() <span class="synStatement">assert</span> rand1 == rand2 </pre> <p>関数を返すのは公式でも使用されているテクニックです。 例えば、predefinedなfixtureには一時ディレクトリのパスを返す<code>tmpdir</code>があるのですが、 一時ディレクトリを生成するためのコールバック<code>tmpdir_factory</code>もあります。</p> <p>もちろん、fixtureではなくヘルパ関数として<code>seed_setter</code>を定義して呼び出すという選択肢もあるので、ケースバイケースで選択しましょう。 上記の例ではヘルパ関数の方がいいと思いますが、乱数シードの固定が至る所で使われるならばfixtureの方がいいです。</p> <p>他に<code>usefixtures</code>を使う例として、<code>unittest</code>の<code>patch</code>があります。 下のサンプルコードでは、<code>mymodule.ObjectWithDB</code>の<code>connect</code>メソッドをMagicMockに置き換えています。 これを<code>usefixtures</code>で宣言すれば、データベースコネクションをスキップして<code>ObjectWithDB</code>を使えます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> unittest.mock <span class="synPreProc">import</span> patch <span class="synPreProc">from</span> mymodule <span class="synPreProc">import</span> ObjectWithDB <span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">ignore_db_connection</span>(): <span class="synStatement">with</span> patch(<span class="synConstant">&quot;mymodule.ObjectWithDB.connect&quot;</span>): <span class="synStatement">yield</span> </pre> <p><code>usefixture</code>はとても便利ですが、テストケース以外では使えないという点に注意してください。 例えば、以下のようなことはできません (エラーは吐きませんが、無視されます)。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synPreProc">@</span><span class="synIdentifier">pytest.mark.usefixtures</span>(<span class="synConstant">'set_seed'</span>) <span class="synStatement">def</span> <span class="synIdentifier">random_value</span>(): <span class="synStatement">return</span> random.random() </pre> <p>代わりにこうしておけばOKです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">random_value_factory</span>(seed_setter): seed_setter() <span class="synStatement">return</span> random.random() </pre> <h2>3. fixtureスコープと変数スコープ</h2> <p>fixtureは基本的にはテストケースごとに実行されます。</p> <p>以下のサンプルコードで確かめてみましょう(pytest コマンドに-s オプションを付けるとprint出力が見られます)。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">foo</span>(): <span class="synIdentifier">print</span>(<span class="synConstant">&quot;start&quot;</span>) <span class="synStatement">yield</span> <span class="synIdentifier">print</span>(<span class="synConstant">&quot;end&quot;</span>) <span class="synStatement">def</span> <span class="synIdentifier">test_1</span>(foo): <span class="synIdentifier">print</span>(<span class="synConstant">&quot;test1&quot;</span>) <span class="synStatement">def</span> <span class="synIdentifier">test_2</span>(foo): <span class="synIdentifier">print</span>(<span class="synConstant">&quot;test2&quot;</span>) </pre> <p>start -> test1 -> end -> start -> test2 > end の順番でプリントされ、テストごとにfixtureの処理が実行されています。</p> <p>これは再現性の観点からは良いのですが、その反面オーバーヘッドが発生します。</p> <p>例えば、テスト用のデータセットにアクセスするfixtureがあったとします。 一回に3秒の初期化がかかったとして、1,000のテストケースで使用されるとしたら、それだけで50分かかります。</p> <p>そこで、試しにテスト実行順をstart -> test1 -> test2 > endというように変更してみます。 そのためには、<code>pytest.fixture</code>の引数に<code>scope="session"</code>を加えます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">fixture</span>(scope=<span class="synConstant">&quot;session&quot;</span>) <span class="synStatement">def</span> <span class="synIdentifier">foo_session</span>(): <span class="synIdentifier">print</span>(<span class="synConstant">&quot;start&quot;</span>) <span class="synStatement">yield</span> <span class="synIdentifier">print</span>(<span class="synConstant">&quot;end&quot;</span>) </pre> <p>実行してみると、意図した通りの挙動になっていることが分かります。 このように、fixtureにおける実行タイミング、つまりいつ<code>yield</code>(<code>return</code>)に入って、いつ<code>yield</code>に戻る(<code>return</code>の場合は特になし)のかを決定するためには、 <code>scope</code>というパラメータを設定します。</p> <p>変数のスコープと混同するので、本稿ではそれぞれ変数スコープ、fixtureスコープと呼ぶことにします。</p> <p>fixtureスコープは以下の4種類があり、それぞれ変数スコープとよく似た入れ子状のブロックとしてのまとまりを持ちます。</p> <ul> <li>そのテストケース自身のみ<a href="#f-a0540066" name="fn-a0540066" title=" parametrizeで複数回実行される場合には、その一回の実行を指します ">*1</a>を含む最小単位であるfuntionスコープ (デフォルト)。</li> <li>クラスの内部の変数スコープと対応する、classスコープ。</li> <li>一つのモジュールの変数スコープと対応するmoduleスコープ。</li> <li>全てのテストケース/fixtureを含むsession(package)スコープ。</li> </ul> <p>functionスコープ以外では、最初に<code>yield</code>した(<code>return</code>した)結果をキャッシュして同じスコープのテストに渡して、そのスコープの終端で<code>yield</code>後の処理を実施しています。 これは、<code>test_1</code>と<code>test_2</code>のそれぞれについて、同じオブジェクトIDのオブジェクトが渡されていることからも確かめられます。</p> <p>fixtureスコープは基本的には狭いものを使用しましょう。つまり、デフォルトから変更しないのがベストです。 多少の時間的なオーバーヘッドがある場合でも、問題にならないうちは広げるべきではないでしょう。 というのも、キャッシュするという性質上、広いスコープのfixtureを使い回すとそのテスト間に 予期しない依存関係が生じてしまう恐れがあるためです。 次節以降で詳しく解説していきます。</p> <h2>4. fixtureスコープの落とし穴</h2> <p>さて、以下のテストには問題があります。どこか分かりますか?</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">fixture</span>(scope=<span class="synConstant">&quot;session&quot;</span>) <span class="synStatement">def</span> <span class="synIdentifier">ids</span>(): <span class="synStatement">return</span> [<span class="synConstant">3</span>, <span class="synConstant">1</span>, <span class="synConstant">4</span>] <span class="synStatement">def</span> <span class="synIdentifier">test_ids_sort</span>(ids): ids.sort() <span class="synStatement">assert</span> ids == [<span class="synConstant">1</span>, <span class="synConstant">3</span>, <span class="synConstant">4</span>] <span class="synStatement">def</span> <span class="synIdentifier">test_ids_pop</span>(ids): ids.pop() <span class="synStatement">assert</span> ids == [<span class="synConstant">3</span>, <span class="synConstant">1</span>] <span class="synComment"># fail here</span> </pre> <p>わからない場合は実行してみましょう。すると、以下の行を含むログが表示されます。</p> <pre class="code" data-lang="" data-unlink>E assert [1, 3] == [3, 1] E At index 0 diff: 1 != 3</pre> <p>どうやら、<code>ids</code>が<code>test_ids_sort</code>の中でソートされた後にそのまま<code>test_ids_pop</code>に渡されてしまっているようです。 キャッシュした値がうっかり破壊的処理によって書き変わってしまう、典型的なバグです。</p> <p>今回の場合は簡単に分かる話ですが、実際にこのバグに遭遇する場合はたいていもっと厄介です。 現実には、同じfixtureを使うテストが別々のスクリプトに点在している場合もあります。 加えて、テストがバグっている場合、元のソースがバグっている場合の間で区別がしづらいのも問題です。 さらに、今回のケースだと<code>test_ids_pop</code>だけをテストしてやると通ってしまいます(PyCharmであれば簡単にできます)。</p> <p>こんな事例を想像してみてください。 あなたは新しくテストをいくつか追加しました。それらが通ることは確認済みです。しかしpushして「さあ帰るぞ」と支度をしていたら、CIからエラーが返ってきてしまいました。 どうやら、まったく弄っていない別のテストがエラーを吐いているようです。でも、そのテストだけを走らせてみるとエラーが再現できません……。 残業中なら、<code>xfail</code>を付けて逃げたくなるような話です。</p> <p>言うまでもないですが、この依存関係を利用するなんてことは論外です。</p> <p>他にも、広いfixtureスコープのfixtureから狭いfixtureスコープのfixtureは呼び出せないという制限があるので、 無闇に広げるとこの制限に引っかかります。 例えば、以下のfixtureを呼び出すとエラーを吐きます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">foo</span>(): <span class="synPreProc">@</span><span class="synIdentifier">fixture</span>(scope=<span class="synConstant">&quot;session&quot;</span>) <span class="synStatement">def</span> <span class="synIdentifier">foo_session</span>(foo): ... </pre> <p>ただ、どうしてもfixtureスコープを広げたい場合もありますので、 その場合には以下の事項に気をつけましょう。</p> <ul> <li>渡すオブジェクトがimmutableかどうか。 <ul> <li>極力immutableなオブジェクトを渡す。</li> <li>mutableオブジェクトならば、テストやテストされる関数等で破壊的なメソッドを呼ばないように細心の注意を払う。</li> </ul> </li> <li>immutableオブジェクトでも、DBコネクション等の外部参照をするfixtureを渡す場合には、それがテストごとにリセットされるかどうか(リセット用fixtureを作って常に使うようにするのも手です。)。</li> </ul> <h2>5. fixtureの可用範囲</h2> <p>これまでの例ではコードスニペットだけを扱ってきましたが、実際のテストスクリプトは複数のテストケース、それらをもつクラス、果ては複数のスクリプトにまたがります。 fixtureのスコープだけでなく、fixtureの可用範囲、変数でいうところの変数スコープを理解する必要が出てきます。</p> <p>本節ではその内容について解説します。</p> <p>まず、基本的には「テストケースが定義された場所」を基準に考えればOKです。</p> <p>例えば、以下の例では<code>test_foo</code>と<code>test_foo_2</code>は同じような挙動をします。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">foo_fixt</span>(): <span class="synStatement">return</span> <span class="synConstant">&quot;foo&quot;</span> <span class="synStatement">def</span> <span class="synIdentifier">test_foo</span>(foo_fixt): <span class="synStatement">assert</span> foo_fixt == <span class="synConstant">&quot;foo&quot;</span> foo_var = <span class="synConstant">&quot;foo&quot;</span> <span class="synStatement">def</span> <span class="synIdentifier">test_foo_2</span>(): <span class="synStatement">assert</span> foo_var == <span class="synConstant">&quot;foo&quot;</span> </pre> <p>テストケースはこのモジュールのグローバル領域に定義されているので、 同じ領域に定義された変数と同様に参照できます。 ここで注意してほしいのが、あくまでグローバル領域であり、これはテストケースの関数ブロックの<strong>外側</strong>の話です。</p> <p>クラスが絡むと、この差がもう少しはっきり出てきます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">class</span> <span class="synIdentifier">TestBar</span>(): <span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">bar_fixt</span>(self): <span class="synStatement">return</span> <span class="synConstant">&quot;bar&quot;</span> <span class="synStatement">def</span> <span class="synIdentifier">test_bar</span>(self, bar_fixt): <span class="synStatement">assert</span> bar_fixt == <span class="synConstant">&quot;bar&quot;</span> bar_var = <span class="synConstant">&quot;bar&quot;</span> ref_bar_var = bar_var <span class="synStatement">def</span> <span class="synIdentifier">test_bar_2</span>(self): <span class="synStatement">assert</span> <span class="synIdentifier">type</span>(self).bar_var == <span class="synConstant">&quot;bar&quot;</span> <span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">bar_fixt_2</span>(self): <span class="synStatement">return</span> <span class="synIdentifier">type</span>(self).bar_var <span class="synStatement">def</span> <span class="synIdentifier">test_bar_3</span>(self, bar_fixt_2): <span class="synStatement">assert</span> bar_fixt_2 == <span class="synConstant">&quot;bar&quot;</span> </pre> <p>クラスブロックでは特殊な名前解決が行われるので、例えば<code>bar_fixt_2</code>からクラス変数<code>bar_var</code>は参照できません。 上の例では<code>type</code>(<code>self</code>)を通じてアクセスしています。 一方、クラスブロック内では(当たり前ですが)参照可能なので、クラス変数<code>ref_bar_var</code>の定義時に<code>bar_var</code>を参照できます。</p> <p>fixtureについても、<code>bar_var</code>同様に直接参照可能です。 テストケースの定義されたブロックで名前解決をしていることが、先ほどの例よりもはっきりと分かります。</p> <p>さて、さらにテストが大きくなってきた場合を考えてみましょう。 多くのテストケースが作成され、似たようなfixtureが複数のスクリプトに定義されるようになってしまいます。 当然、fixtureを使い回したいという欲求が出てきます(よね?)。</p> <p>pytestでは、スクリプト間でfixtureを使い回すための仕組みが提供されています。 試しに、conftest.pyという名前のファイルをテストフォルダ直下に作成し、 その中にfixtureを入れてみてください(もちろん、サンプルリポジトリにも用意されています)。 すると、そのfixtureを全てのテストで使うことができます。</p> <p>このようにconftest.pyは便利なのですが、fixtureをどんどん作成していると次第に汚くなってきます<a href="#f-d89ed0ff" name="fn-d89ed0ff" title="fixture以外にも色々用途があるので、想像以上に早くカオスが生まれます">*2</a>。</p> <p>なので、conftest.pyをある程度分割することをオススメします。 conftest.py内で定義されたfixtureの使用可能な範囲は、正確には「conftest.pyの定義されたフォルダとそのサブディレクトリのテスト」です。 なので、テストをサブディレクトリに分割してその中にconftest.pyを作成すれば分割できます。 また、conftest.pyはいわゆるグローバルなオブジェクトが作られてしまうので、 ある程度狭い範囲で利用可能になるように(とはいえconftest.pyが増えすぎないように) するのがベストかなと思います。</p> <p>余談ですが、筆者は他のファイルで定義したfixtureをconftest.pyでimportすることでconftest.pyを綺麗に保っていたことがあります。 しかし、fixtureのimportは<strong>非推奨</strong>であり今後のバージョンでの<strong>動作は保証されない</strong>のでimportはしないようにしましょう <a href="#f-6c5560c2" name="fn-6c5560c2" title="本稿を書いてて初めて知りました。名案だと思って、趣味のプロジェクトでは結構使ってたんですけどね……。">*3</a>。</p> <h2>6. fixtureの連鎖と階層構造</h2> <p>pytestでは、fixtureを定義する際に別のfixtureを入力として受け取ることが可能です。 知っている方も多いと思うので、ここまでの例でもいくつかの例でこの機能を利用していました。 本節ではさらにその細かい部分に突っ込んでいきます。</p> <h3>6.1 fixtureの循環/再帰エラー</h3> <p>fixtureからfixtureを呼び出すことで、fixtureどうしに有向の依存関係が発生します。 そして、この依存関係を解決する必要があるので、循環や再帰があってはいけません。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># 循環の例</span> <span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">cycle_1</span>(cycle_3): <span class="synStatement">return</span> cycle_3 <span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">cycle_2</span>(cycle_1): <span class="synStatement">return</span> cycle_1 <span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">cycle_3</span>(cycle_2): <span class="synStatement">return</span> cycle_2 <span class="synStatement">def</span> <span class="synIdentifier">test_cycle_fixt</span>(cycle_3): ... <span class="synComment"># 再帰の例</span> <span class="synPreProc">@</span><span class="synIdentifier">pytest.fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">recursive_fixture</span>(recursive_fixture): ... <span class="synStatement">def</span> <span class="synIdentifier">test_recursive_fixture</span>(cycle_3): ... </pre> <p>上記の例を実行すると、</p> <pre class="code" data-lang="" data-unlink>recursive dependency involving fixture &#39;***&#39; detected</pre> <p>といったようなエラーが発生します。</p> <p>testからfixtureを呼び出す場合と同様に、fixtureからfixtureを呼び出す場合でも 変数スコープやconftest.pyの階層関係が成立します。 なお、最上位にあたるfixtureはルートディレクトリのconftest内のfixtureかと思いきや、 実はpredefinedなfixtureです<a href="#f-fef77839" name="fn-fef77839" title="pluginまで絡んでくるとどうなるのかは未検証ですが、おそらく同様の扱いになるかと思います。pluginの間で循環とかありえるのでしょうか? 気になるところです">*4</a>。</p> <h3>6.2 同名fixtureの連鎖</h3> <p>次に、下の例のように同じ名前のfixtureを複数作って、一つ目で二つ目を上書きするような例を考えてみます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">foo_fixture</span>(): <span class="synStatement">return</span> [<span class="synConstant">1</span>, <span class="synConstant">2</span>, <span class="synConstant">3</span>] <span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">foo_fixture</span>(foo_fixture): <span class="synStatement">return</span> foo_fixture + [<span class="synConstant">4</span>, <span class="synConstant">5</span>] </pre> <p>残念ながら、上の例はエラーとなってしまいます。</p> <p>同一のfixtureを定義した場合、この部分が含まれたモジュールがimportされた場合と同様に、後に定義された方が前に定義された方を上書きしてしまいます。 つまり、一つ目の<code>foo_fixture</code>が無視されて二つ目の<code>foo_fixture</code>が自身を再帰的に入力としていることになり、上記のエラーが出てしまいます。</p> <p>しかし、下のように変数スコープを変えることで同じ名前のfixtureを入力とすることが可能です。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">foo_fixture</span>(): <span class="synStatement">return</span> [<span class="synConstant">1</span>, <span class="synConstant">2</span>, <span class="synConstant">3</span>] <span class="synStatement">def</span> <span class="synIdentifier">test_foo</span>(foo_fixture): <span class="synStatement">assert</span> foo_fixture == [<span class="synConstant">1</span>, <span class="synConstant">2</span>, <span class="synConstant">3</span>] <span class="synStatement">class</span> <span class="synIdentifier">TestFoo</span>(): <span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">foo_fixture</span>(self, foo_fixture): <span class="synStatement">return</span> foo_fixture + [<span class="synConstant">4</span>, <span class="synConstant">5</span>] <span class="synStatement">def</span> <span class="synIdentifier">test_foo</span>(self, foo_fixture): <span class="synStatement">assert</span> foo_fixture == [<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>] </pre> <p>上の例では、<code>TestFoo.foo_fixture</code>がglobal領域の<code>foo_fixture</code>を引数にとり、それを変形したものを返しています。 このように複数の変数領域に分けることで二つのfixtureの間に上位下位関係が成立して循環と重複がなくなり、 下位のfixtureから上位のfixtureを利用することが可能となります。</p> <p>「別の名前のfixtureでいいじゃないか……」という意見もあるかと思いますし、役割が大きく変化してしまう場合などにはそれが正しいです。 一方、似通った名前のfixtureを量産することや、fixtureの名前が具体化するにつれて長くなってしまうのは あまり良くありません<a href="#f-e11e6a0a" name="fn-e11e6a0a" title="テストケースについてはそれ自身を呼び出すこともないので長い名前もOKです">*5</a>。</p> <h3>6.3 親子クラス間での同名fixtureの連鎖</h3> <p>では、最後にクラスを継承した場合はどうなるでしょうか。</p> <p>以下の例は、ベースとなるfixtureとテストケースを用意して、 それを継承したテストを作成することで様々なパターンのテストの実装を省力化する試みです。</p> <p>以下の<code>TestInherit.test_inherit_fixture</code>は通るでしょうか?</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">class</span> <span class="synIdentifier">TestBase</span>(): EXPECTED = [<span class="synConstant">1</span>, <span class="synConstant">2</span>] <span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">inherit_fixture</span>(self): <span class="synStatement">return</span> [<span class="synConstant">1</span>, <span class="synConstant">2</span>] <span class="synStatement">def</span> <span class="synIdentifier">test_inherit_fixture</span>(self, inherit_fixture): <span class="synStatement">assert</span> inherit_fixture == self.EXPECTED <span class="synStatement">class</span> <span class="synIdentifier">TestInherit</span>(TestBase): EXPECTED = [<span class="synConstant">1</span>, <span class="synConstant">2</span>, <span class="synConstant">3</span>, <span class="synConstant">4</span>] <span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">inherit_fixture</span>(self, inherit_fixture): <span class="synStatement">return</span> inherit_fixture + [<span class="synConstant">3</span>, <span class="synConstant">4</span>] </pre> <p>正解は、「通らない」です。 これはベースクラスの<code>inherit_fixture</code>が上書きされるので、再帰的なfixtureとなってエラーを吐きます。</p> <p>修正案としては、まずそもそもfixtureについてはベースクラスで定義しないでおいて、 ベースクラスをテスト対象から外すような修正をするのが一番だと思います。</p> <p>どうしてもfixtureも使い回したい場合、 以下のようにベースのfixtureを外に出してしまうという方法があります。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">inherit_fixture</span>(): <span class="synStatement">return</span> [<span class="synConstant">1</span>, <span class="synConstant">2</span>] <span class="synStatement">class</span> <span class="synIdentifier">TestBase</span>(): EXPECTED = [<span class="synConstant">1</span>, <span class="synConstant">2</span>] <span class="synStatement">def</span> <span class="synIdentifier">test_inherit_fixture</span>(self, inherit_fixture): <span class="synStatement">assert</span> inherit_fixture == self.EXPECTED <span class="synStatement">class</span> <span class="synIdentifier">TestInherit</span>(TestBase): EXPECTED = [<span class="synConstant">1</span>, <span class="synConstant">2</span>, <span class="synConstant">3</span>, <span class="synConstant">4</span>] <span class="synPreProc">@</span><span class="synIdentifier">fixture</span> <span class="synStatement">def</span> <span class="synIdentifier">inherit_fixture</span>(self, inherit_fixture): <span class="synStatement">return</span> inherit_fixture + [<span class="synConstant">3</span>, <span class="synConstant">4</span>] </pre> <p>テストでクラスの継承を使い始めるとややこしくなるので、テストケースを継承するようなクラスはそうそう作るべきではないという意見もあります。 とはいえ、自分はこれもケースバイケースであり必要に応じて継承は使うべきだと考えているので、あえてここで紹介しました。</p> <h1>7.まとめ</h1> <p>pytestについて自分の好きな話をなんとかテーマに沿って選抜して、まとめてみました。 正直、半年前まではpytestを含めてテストを書くのは好きではなかったのですが、 pytestのテクニカルな部分に触れるうちに段々と楽しくなっていき<del>やりすぎることも多々あり</del>ました。</p> <p>また、テストを何度も書くうちにテストをしやすいようなコードを書く意識がついて、 自分の設計能力も上がったのは嬉しい誤算でした。</p> <p>実務的にテストを書くという行為は、納期やリソース、チームのルールなど、非常に多くのパラメータが絡み合っており、 経験から程よいテストをいい感じに書くという、理論や知識よりも経験が求められる世界だと考えています。 なので、Pythonを書く全ての人が、まずはpytestの楽しさに気づいて、テストを書く機会を増やし、 やがてこの世界からレガシーコードが減っていけばと切に切に切に願っています。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/B01AN97W08/hoxom-22/"><img src="https://m.media-amazon.com/images/I/51f-xfTWkkL._SL500_.jpg" class="hatena-asin-detail-image" alt="レガシーコード改善ガイド" title="レガシーコード改善ガイド"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/B01AN97W08/hoxom-22/">レガシーコード改善ガイド</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%B1%A5%EB%A1%A6C%A1%A6%A5%D5%A5%A7%A5%B6%A1%BC%A5%BA" class="keyword">マイケル・C・フェザーズ</a></li><li><span class="hatena-asin-detail-label">発売日:</span> 2016/01/15</li><li><span class="hatena-asin-detail-label">メディア:</span> Kindle版</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> <div class="footnote"> <p class="footnote"><a href="#fn-a0540066" name="f-a0540066" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"> parametrizeで複数回実行される場合には、その一回の実行を指します </span></p> <p class="footnote"><a href="#fn-d89ed0ff" name="f-d89ed0ff" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">fixture以外にも色々用途があるので、想像以上に早くカオスが生まれます</span></p> <p class="footnote"><a href="#fn-6c5560c2" name="f-6c5560c2" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">本稿を書いてて初めて知りました。名案だと思って、趣味のプロジェクトでは結構使ってたんですけどね……。</span></p> <p class="footnote"><a href="#fn-fef77839" name="f-fef77839" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">pluginまで絡んでくるとどうなるのかは未検証ですが、おそらく同様の扱いになるかと思います。pluginの間で循環とかありえるのでしょうか? 気になるところです</span></p> <p class="footnote"><a href="#fn-e11e6a0a" name="f-e11e6a0a" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">テストケースについてはそれ自身を呼び出すこともないので長い名前もOKです</span></p> </div> kazuya_fujioka 「技術に正しく課金したいがためにアラフォーでも髪を染め続けているよ」というお話。 hatenablog://entry/26006613726373349 2021-05-09T15:59:43+09:00 2021-05-09T16:13:31+09:00 株式会社ホクソエム常務取締役のタカヤナギ=サンです、主に経営を担当しています。 株式会社ホクソエムの顧客、あるいは同僚から「何で君はアラフォーになっても変な髪色になっとるんじゃい?」という質問を結構いただくんで、 いい加減そのことについての私の考えをポエムにしたいなと思ってこのブログを書いています。 「いや、そんなもん会社のBLOGに書くんじゃねーよ💢」という話があるかもしれないですが、ここは私の保有する会社なので何の問題もない、don't you? タイトルにある"技術"(テクノロジー)はより正確には”技”(テクネ、スキル)のほうが正しい気もするが、まあここでは問題としないで同義として扱って… <p>株式会社ホクソエム常務取締役のタカヤナギ=サンです、主に経営を担当しています。</p> <p>株式会社ホクソエムの顧客、あるいは同僚から「何で君はアラフォーになっても変な髪色になっとるんじゃい?」という質問を結構いただくんで、 いい加減そのことについての私の考えをポエムにしたいなと思ってこのブログを書いています。</p> <p>「いや、そんなもん会社のBLOGに書くんじゃねーよ💢」という話があるかもしれないですが、ここは私の保有する会社なので何の問題もない、don't you?</p> <p>タイトルにある"技術"(テクノロジー)はより正確には”技”(テクネ、スキル)のほうが正しい気もするが、まあここでは問題としないで同義として扱っていきたく存じます。</p> <h2>美容室のビジネスモデル</h2> <p>美容室のビジネスモデルっていうのは比較的シンプルで、月当たりの売上は以下で計算できるわけです。</p> <ul> <li><b>売上 = 顧客単価 x 顧客人数/月</b></li> </ul> <p>これが一月の売上になるわけです。 利益は"売上 - 費用"で計算できますが、まあ1人でやってる小さな美容室の全部は固定費(自分の人件費と場所代)だと思えるわけです。 なので、利益を上げるためには売上を上げるしかない(固定はFixedだと思う)という構造になっています<a href="#f-97901806" name="fn-97901806" title="アップセルとして「独自に仕入れたシャンプーだのの販売」なんかもここでは全部顧客単価に含んでいると思おう、あまりデカくないだろうし。また、変動費の水道光熱費等はまあ無視しておこう。">*1</a>。</p> <h2>美容室の技に正しくお金を払うためには</h2> <p>私は今行っている美容室が好きで、適切に利益を上げ続けてビジネス的に上手く行って欲しいと思っているので、個人的にも利益に貢献していこうという気持ちがあるわけです。</p> <p>で、利益に貢献しようと思うと、上のビジネスモデルの話にあるように売上に貢献しなければいけないのですが、貢献できる因子としては</p> <ul> <li>顧客単価</li> <li>顧客人数/月</li> </ul> <p>のどちらかを上げることで貢献するしかないわけですね。</p> <p>一方、メンズ(おじさん)が美容室に行く頻度なんてまあ普通月に1回くらいなもんで、その回数(顧客人数/月)を増やして大いに利益貢献するってのもありと言えばあり、もう毎週「丸坊主にしてくれ!!!」とオーダーして通いつめればいいんですが、それはちょっと技術を尊重してるとは言えないなと、なのでありよりのなしだよということになり、取れる戦略は”顧客単価の向上”一択となるわけです。</p> <p>カットのみをオーダーして、高級な鮨🍣屋でやるような <a href="https://r.gnavi.co.jp/g-interview/entry/1567">お心づけ</a>をお渡しする、でもまあ良いと言えばいいんですが、それは技術に課金している感じがしないなと、腕や技をリスペクトしてる感じというよりも、全体的なサービス満足度に対するチップ的な気持ちになってしまうのでなしとしています。</p> <p>なので、顧客単価を上げようとすると所謂”アップセルな商品”、美容室で言うと通常のカットに加えパーマやらヘッドスパ、カラーをお願いするという選択肢がある中で、私は髪の色を変える(カラーをお願いする)ことで売上(≒利益)に貢献!という方針を取っていると、そういうことです<a href="#f-b981cd5e" name="fn-b981cd5e" title="一時期、パーマの時代もあったが何かの理由で止めてしまった。パーマ液が臭いとかしょうもない理由だったような気もする">*2</a>。 これが私なりの”正しい技術への課金”ということです。</p> <p>個人的な意見としては、”自分の見てくれ”に何の興味もない人は、髪の色ぐらいバンバンいじっちゃったらいいんじゃないかなと思っています 。 興味がない事柄だからプロにお任せして好き勝手にやってもらう、技術に課金していく、そういうことです。</p> <h2>まとめ</h2> <p>最近だと <a href="https://github.com/sponsors">Github Sponsors</a>や <a href="https://zenn.dev/">Zenn</a>のような”すぐ投げ銭できる仕組み”が出来てきてて、 非常に良い時代になってきたんですが、まだ匠の技や技術を持つ個人商店のようなスモールビジネスに対して、どうその技術に対して課金していけばよいのかはよくわかっておらず、試行錯誤しながら応援ヤッテイキをしていこうと思います。</p> <p>・・・次回も絶対見てくれよな!!!</p> <div class="footnote"> <p class="footnote"><a href="#fn-97901806" name="f-97901806" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">アップセルとして「独自に仕入れたシャンプーだのの販売」なんかもここでは全部顧客単価に含んでいると思おう、あまりデカくないだろうし。また、変動費の水道光熱費等はまあ無視しておこう。</span></p> <p class="footnote"><a href="#fn-b981cd5e" name="f-b981cd5e" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">一時期、パーマの時代もあったが何かの理由で止めてしまった。パーマ液が臭いとかしょうもない理由だったような気もする</span></p> </div> shinichi-takayanagi ホクソエムのおじさんたちを勝手に踊らせた話 hatenablog://entry/26006613679046013 2021-02-26T11:55:03+09:00 2021-02-26T13:50:28+09:00 毎週の歯科治療が一段落し, とうとう外に出る理由が一切なくなりました。 ホクソエムサポーターのKAZYです。 6畳の部屋に籠もり続けて健康を維持できるのか不安なこの頃。 運動不足も気になります。 ホクソエムのおじさんたちもきっと同じ悩みを抱えてることでしょう。 ところで最近は静止画を簡単に踊らせてやることができるらしいです。 referenceの動画の動きに合わせて、sourceの静止画をぐりぐり動かせるAI。Attention機構などを使い、referenceから抽出した動きの情報をsource画像に当てはめ、Discriminatorに真偽判定させるGANを主な機構として用いているとのこと… <p>毎週の歯科治療が一段落し, とうとう外に出る理由が一切なくなりました。</p> <p>ホクソエムサポーターのKAZYです。</p> <p>6畳の部屋に籠もり続けて健康を維持できるのか不安なこの頃。 運動不足も気になります。</p> <p>ホクソエムのおじさんたちもきっと同じ悩みを抱えてることでしょう。</p> <p>ところで最近は静止画を簡単に踊らせてやることができるらしいです。 <blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">referenceの動画の動きに合わせて、sourceの静止画をぐりぐり動かせるAI。<br>Attention機構などを使い、referenceから抽出した動きの情報をsource画像に当てはめ、Discriminatorに真偽判定させるGANを主な機構として用いているとのこと。<a href="https://t.co/YdsiRi0Enp">https://t.co/YdsiRi0Enp</a> <a href="https://t.co/7xQ8oohqyo">pic.twitter.com/7xQ8oohqyo</a></p>&mdash; 福田敦史 / Aillis CTO (@fukumimi014) <a href="https://twitter.com/fukumimi014/status/1338106865113210883?ref_src=twsrc%5Etfw">December 13, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>私は閃きました。</p> <p>この技術を使ってホクソエムのおじさん達をグリグリ動かす。</p> <p>そうすればおじさんの運動不足は解消される。</p> <p>それにより, おじさんたちは気分が良くなる。</p> <p>私は感謝されご褒美をたんまりもらえる。💰💰💰💰</p> <p>素晴らしいシナリオです。</p> <p>天才かもしれない。</p> <h1>今回のアウトプット(忙しい人用)</h1> <p><img src="https://user-images.githubusercontent.com/22516661/109253961-54b96200-7834-11eb-9438-777d6939eb6d.gif" alt="doukana" /> <img src="https://user-images.githubusercontent.com/22516661/109253989-669b0500-7834-11eb-8032-6633c579a0e7.gif" alt="arekana" /> <img src="https://user-images.githubusercontent.com/22516661/109254004-6dc21300-7834-11eb-9da8-0692fd21016f.gif" alt="koukana" /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KAZYPinkSaurus/20210226/20210226134504.png" alt="f:id:KAZYPinkSaurus:20210226134504p:plain" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>フリー素材です。</p> <p>ご自由にお持ち帰りください。</p> <h1>今回使う技術の流れの雰囲気なお気持ち</h1> <p>ホクソエムブログって実はテックブログ的なものらしい(NOT ポエム置き場)。</p> <p>なので今回使う技術のお気持ち程度の解説を記しておくことにする。</p> <p>この技術が発表された論文の名は<code>Liquid Warping GAN with Attention: A Unified Framework for Human Image Synthesis</code>。</p> <p>2020年のもの。</p> <p>論文に載っていたFramework OverViewに私がちょびっとコメントを追加画像がこちら。</p> <p><figure class="figure-image figure-image-fotolife" title="雑of雑な解説"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KAZYPinkSaurus/20210214/20210214193929.png" alt="f:id:KAZYPinkSaurus:20210214193929p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>雑of雑な解説</figcaption></figure></p> <p>やってることはだいたいこんな感じだ。</p> <p>やりたいことはSource Image <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20Is_%7Bi%7D" alt=" Is_{i}"/>をReference<img src="https://chart.apis.google.com/chart?cht=tx&chl=I_r" alt="I_r"/> のに姿勢にしたい。</p> <p>なるべく自然な感じに。</p> <p>それを実現するために考えられた流れは</p> <div> 1. 画像からHuman Mesh Recovery(HMR)というタスクを行い, 2次元画像から3次元のメッシュ情報を推定してする。 <br> 2. 3次元メッシュを考慮した画像間の人物部位の対応マップ的な<img src="https://chart.apis.google.com/chart?cht=tx&chl=T" alt="T"/>をつくる (Transeformation flowと呼んでいる)。<br> 3. <img src="https://chart.apis.google.com/chart?cht=tx&chl=T" alt="T"/>を使って<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20Is_i" alt=" Is_i"/>を<img src="https://chart.apis.google.com/chart?cht=tx&chl=I_r" alt="I_r"/>の姿勢にした<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20I%5E%7Bsyn%7D_%7Bt%7D" alt=" I^{syn}_{t}"/>を生成する。<br> 4. Convolutional Autoencoderライクな <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20G_%7BBG%7D%2C%20G_%7BSID%7D%2C%20G_%7BTSF%7D" alt=" G_{BG}, G_{SID}, G_{TSF}"/>を使って <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20Is_i" alt=" Is_i"/>の背景画像, <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20Is_i" alt=" Is_i"/>の人部分のマスク画像と人の画像, <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20I_r" alt=" I_r"/>の人部分のマスク画像と人の画像を作ってやる(このとき<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20LWB%2FAttLWB" alt=" LWB/AttLWB"/>という機構を使って<img src="https://chart.apis.google.com/chart?cht=tx&chl=G_%7BSID%7D" alt="G_{SID}"/>から<img src="https://chart.apis.google.com/chart?cht=tx&chl=G_%7BTSF%7D" alt="G_{TSF}"/>に情報を送り込んでやっているようだ)。<br> 5. <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20G_%7BBG%7D%2CG_%7BSID%7D" alt=" G_{BG},G_{SID}"/>から生成された背景画像と人の画像を合成して画像<img src="https://chart.apis.google.com/chart?cht=tx&chl=%5Chat%7BI%7D_%7Bsi%7D" alt="\hat{I}_{si}"/>を作る。<br> 6. <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20G_%7BBG%7D%2CG_%7BTSF%7D" alt=" G_{BG},G_{TSF}"/>から生成された背景画像と人の画像を合成して画像<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20syn%20%5Chat%7BI%7D_%7Bt%7D" alt=" syn \hat{I}_{t}"/>を作る (これがお目当てのやつやな)。<br> </div> <p>上の流れでいい感じの結果を得るために</p> <div> - <img src="https://chart.apis.google.com/chart?cht=tx&chl=%5Chat%7BI%7D_%7Bsi%7D" alt="\hat{I}_{si}"/>をなるだけ<img src="https://chart.apis.google.com/chart?cht=tx&chl=I_%7Bsi%7D" alt="I_{si}"/>っぽくしたい気持ち <br> - <img src="https://chart.apis.google.com/chart?cht=tx&chl=syn%20%5Chat%7BI%7D_%7Bt%7D" alt="syn \hat{I}_{t}"/>を偽物, <img src="https://chart.apis.google.com/chart?cht=tx&chl=I_%7Br%7D" alt="I_{r}"/>を本物として, それらが見分けがつかないようにした気持ち(ここがGANな要素や)<br> </div> <p>をあわせて学習する感じ (雑)。</p> <p>あと一旦3Dメッシュにして3次元的な情報も考えているんだってところがポイントらしいです。 あとあと<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20LWB%2FAttLWB" alt=" LWB/AttLWB"/>のところで人物を再構築するために使う特徴量を流し込んでやるところもポイントらしいです。<br></p> <p>詳細は論文を読んでほしいです。</p> <p>こちら↓のサイトに論文, コード, データセットなどなどが置いてあります。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.impersonator.org%2Fwork%2Fimpersonator-plus-plus.html" title="impersonator_plus_plus" 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://www.impersonator.org/work/impersonator-plus-plus.html">www.impersonator.org</a></cite></p> <p>2021年02月現在はComing soonとなっていますが, いいのアプリケーションも開発するプロジェクトもあるようです。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KAZYPinkSaurus/20210131/20210131194257.png" alt="f:id:KAZYPinkSaurus:20210131194257p:plain" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1>おじさんの画像を集める</h1> <p>本題に戻ります。</p> <p>おじさんたちの画像を集めます。</p> <p>画像を募ったらなおじさん3名から画像を拝借できました(たぶんフリー素材)。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><a href="http://f.hatena.ne.jp/KAZYPinkSaurus/20210131172138" class="hatena-fotolife" itemprop="url"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KAZYPinkSaurus/20210131/20210131172138.jpg" alt="f:id:KAZYPinkSaurus:20210131172138j:image:h100" title="" class="hatena-fotolife" style="height:100px" itemprop="image"></a></span><span itemscope itemtype="http://schema.org/Photograph"><a href="http://f.hatena.ne.jp/KAZYPinkSaurus/20210131172311" class="hatena-fotolife" itemprop="url"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KAZYPinkSaurus/20210131/20210131172311.jpg" alt="f:id:KAZYPinkSaurus:20210131172311j:image:h100" title="" class="hatena-fotolife" style="height:100px" itemprop="image"></a></span><span itemscope itemtype="http://schema.org/Photograph"><a href="http://f.hatena.ne.jp/KAZYPinkSaurus/20210214222953" class="hatena-fotolife" itemprop="url"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KAZYPinkSaurus/20210214/20210214222953.jpg" alt="f:id:KAZYPinkSaurus:20210214222953j:image:h100" title="" class="hatena-fotolife" style="height:100px" itemprop="image"></a></span><span itemscope itemtype="http://schema.org/Photograph"><a href="http://f.hatena.ne.jp/KAZYPinkSaurus/20210214222956" class="hatena-fotolife" itemprop="url"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KAZYPinkSaurus/20210214/20210214222956.jpg" alt="f:id:KAZYPinkSaurus:20210214222956j:image:h100" title="" class="hatena-fotolife" style="height:100px" itemprop="image"></a></span></p> <p>いい表情。</p> <h1>おじさんを動かす(その1)</h1> <p>この技術,嬉しいことにテストデータを動かしているノートブックが公開されているんです。</p> <p>脳みそが🐵な私は画像だけ差し替えればなんか動くだろの精神でおじさん達を投入してみます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcolab.research.google.com%2Fdrive%2F1bwUnj-9NnJA2EMr7eWO4I45UuBtKudg_%3Fusp%3Dsharing" title="Google Colaboratory" 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://colab.research.google.com/drive/1bwUnj-9NnJA2EMr7eWO4I45UuBtKudg_?usp=sharing">colab.research.google.com</a></cite></p> <p><figure class="figure-image figure-image-fotolife" title="保存されないぜって言っているし何してもokだろう...."><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KAZYPinkSaurus/20210214/20210214211616.png" alt="f:id:KAZYPinkSaurus:20210214211616p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>保存されないぜって言っているし何してもokだろう....</figcaption></figure></p> <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/K/KAZYPinkSaurus/20210214/20210214213035.png" alt="f:id:KAZYPinkSaurus:20210214213035p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>上からポチポチと実行していく</figcaption></figure></p> <p>トランプを踊らせている動画の設定のブロックにたどり着きました。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># This is a specific model name, and it will be used if you do not change it. This is the case of `trump`</span> model_id = <span class="synConstant">&quot;donald_trump_2&quot;</span> <span class="synComment"># the source input information, here \&quot; is escape character of double duote &quot;</span> src_path = <span class="synConstant">&quot;</span><span class="synSpecial">\&quot;</span><span class="synConstant">path?=/content/iPERCore/assets/samples/sources/donald_trump_2/00000.PNG,name?=donald_trump_2</span><span class="synSpecial">\&quot;</span><span class="synConstant">&quot;</span> <span class="synComment">## the reference input information. There are three reference videos in this case.</span> <span class="synComment"># here \&quot; is escape character of double duote &quot;</span> <span class="synComment"># ref_path = &quot;\&quot;path?=/content/iPERCore/assets/samples/references/akun_1.mp4,&quot; \</span> <span class="synComment"># &quot;name?=akun_2,&quot; \</span> <span class="synComment"># &quot;pose_fc?=300\&quot;&quot;</span> ref_path = <span class="synConstant">&quot;</span><span class="synSpecial">\&quot;</span><span class="synConstant">path?=/content/iPERCore/assets/samples/references/mabaoguo_short.mp4,&quot;</span> <span class="synSpecial">\</span> <span class="synConstant">&quot;name?=mabaoguo_short,&quot;</span> <span class="synSpecial">\</span> <span class="synConstant">&quot;pose_fc?=400</span><span class="synSpecial">\&quot;</span><span class="synConstant">&quot;</span> <span class="synComment"># ref_path = &quot;\&quot;path?=/content/iPERCore/assets/samples/references/akun_1.mp4,&quot; \</span> <span class="synComment"># &quot;name?=akun_2,&quot; \</span> <span class="synComment"># &quot;pose_fc?=300|&quot; \</span> <span class="synComment"># &quot;path?=/content/iPERCore/assets/samples/references/mabaoguo_short.mp4,&quot; \</span> <span class="synComment"># &quot;name?=mabaoguo_short,&quot; \</span> <span class="synComment"># &quot;pose_fc?=400\&quot;&quot;</span> <span class="synIdentifier">print</span>(ref_path) !python -m iPERCore.services.run_imitator <span class="synSpecial">\</span> --gpu_ids $gpu_ids <span class="synSpecial">\</span> --num_source $num_source <span class="synSpecial">\</span> --image_size $image_size <span class="synSpecial">\</span> --output_dir $output_dir <span class="synSpecial">\</span> --model_id $model_id <span class="synSpecial">\</span> --cfg_path $cfg_path <span class="synSpecial">\</span> --src_path $src_path <span class="synSpecial">\</span> --ref_path $ref_path </pre> <p>あー, 完全に理解した。</p> <p><code>ref_path</code>で動画, <code>src_path</code>で画像を指定しているっぽいので, <code>src_path</code>をおじさんに差し替えちゃえばいいんでしょう?</p> <p><figure class="figure-image figure-image-fotolife" title="おじさん01をアップロード"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KAZYPinkSaurus/20210214/20210214214847.png" alt="f:id:KAZYPinkSaurus:20210214214847p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>おじさん01をアップロード</figcaption></figure></p> <p><code>src_path</code>と<code>model_id</code>をおじさんに差し替えてみました。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># This is a specific model name, and it will be used if you do not change it. This is the case of `trump`</span> model_id = <span class="synConstant">&quot;ossan01&quot;</span> <span class="synComment"># the source input information, here \&quot; is escape character of double duote &quot;</span> src_path = <span class="synConstant">&quot;</span><span class="synSpecial">\&quot;</span><span class="synConstant">path?=/content/iPERCore/ossan-01.jpg,name?=ossan01</span><span class="synSpecial">\&quot;</span><span class="synConstant">&quot;</span> <span class="synComment">## the reference input information. There are three reference videos in this case.</span> <span class="synComment"># here \&quot; is escape character of double duote &quot;</span> <span class="synComment"># ref_path = &quot;\&quot;path?=/content/iPERCore/assets/samples/references/akun_1.mp4,&quot; \</span> <span class="synComment"># &quot;name?=akun_2,&quot; \</span> <span class="synComment"># &quot;pose_fc?=300\&quot;&quot;</span> ref_path = <span class="synConstant">&quot;</span><span class="synSpecial">\&quot;</span><span class="synConstant">path?=/content/iPERCore/assets/samples/references/mabaoguo_short.mp4,&quot;</span> <span class="synSpecial">\</span> <span class="synConstant">&quot;name?=mabaoguo_short,&quot;</span> <span class="synSpecial">\</span> <span class="synConstant">&quot;pose_fc?=400</span><span class="synSpecial">\&quot;</span><span class="synConstant">&quot;</span> <span class="synComment"># ref_path = &quot;\&quot;path?=/content/iPERCore/assets/samples/references/akun_1.mp4,&quot; \</span> <span class="synComment"># &quot;name?=akun_2,&quot; \</span> <span class="synComment"># &quot;pose_fc?=300|&quot; \</span> <span class="synComment"># &quot;path?=/content/iPERCore/assets/samples/references/mabaoguo_short.mp4,&quot; \</span> <span class="synComment"># &quot;name?=mabaoguo_short,&quot; \</span> <span class="synComment"># &quot;pose_fc?=400\&quot;&quot;</span> <span class="synIdentifier">print</span>(ref_path) !python -m iPERCore.services.run_imitator <span class="synSpecial">\</span> --gpu_ids $gpu_ids <span class="synSpecial">\</span> --num_source $num_source <span class="synSpecial">\</span> --image_size $image_size <span class="synSpecial">\</span> --output_dir $output_dir <span class="synSpecial">\</span> --model_id $model_id <span class="synSpecial">\</span> --cfg_path $cfg_path <span class="synSpecial">\</span> --src_path $src_path <span class="synSpecial">\</span> --ref_path $ref_path </pre> <p>あと, 一つ前のセルで<code>num_source = 2</code>となっていますが, 今回はおじさん画像は1枚なので<code>num_source = 1</code>に修正しました。</p> <p>そしてRun the trump caseのブロックを実行してみます。</p> <p>待つこと数分。。。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>----------------------MetaOutput---------------------- ossan01 imitates mabaoguo_short <span class="synError">in</span> ./results/primitives/ossan01/synthesis/imitations/ossan01-mabaoguo_short.mp4 ------------------------------------------------------ Step 3: running imitator <span class="synError">done</span>. </pre> <p>という表示がでました。 どうやら終わったようです。</p> <p><iframe width="560" height="315" src="https://www.youtube.com/embed/dei3ZEwoSNg?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><cite class="hatena-citation"><a href="https://youtu.be/dei3ZEwoSNg">youtu.be</a></cite></p> <p>おおおおおおおお!!!!! 動いたーーー!</p> <p>おじさんがおじさんの動画と同じ動きをしております。</p> <p>なにかの武術を完全にマスターしていますね。</p> <p>素晴らしい。 強そう。</p> <h1>おじさんを運動せてみる(その2)</h1> <p>今度は別のおじさんを動かしてみましょう。</p> <p>こちらのおじさん(多分ホクソエムの社長さん)。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><a href="http://f.hatena.ne.jp/KAZYPinkSaurus/20210131172311" class="hatena-fotolife" itemprop="url"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KAZYPinkSaurus/20210131/20210131172311.jpg" alt="f:id:KAZYPinkSaurus:20210131172311j:image:h100" title="" class="hatena-fotolife" style="height:100px" itemprop="image"></a></span></p> <p>先程はサンプルー動画でしたが動画もこちらが指定したものに差し替えてみましょう。</p> <p><code>ref_path</code>を適当に拾ってきた動画に変更します。 あとおじさんも別のおじさんに変えてみます。</p> <p><iframe width="420" height="315" src="https://www.youtube.com/embed/2PojGjSE8kY?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><cite class="hatena-citation"><a href="https://youtu.be/2PojGjSE8kY">youtu.be</a></cite></p> <p><a href="https://www.youtube.com/watch?v=qeh39bbZnUI">元ネタ</a></p> <p>社長さんに変な動きをさせている背徳感が堪らない。。。</p> <p>あと単純に普段していないであろう動きをしているおじさんが面白い。</p> <h1>おじさんを運動せてみる(その3)</h1> <p>調子に乗って最後は激しいダンスとかおじさんにさせてみようと思います。</p> <p>踊るおじさんがよりリアルになってほしいので今回は正面と背面と2枚の画像を入力してみます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><a href="http://f.hatena.ne.jp/KAZYPinkSaurus/20210214222953" class="hatena-fotolife" itemprop="url"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KAZYPinkSaurus/20210214/20210214222953.jpg" alt="f:id:KAZYPinkSaurus:20210214222953j:image:h100" title="" class="hatena-fotolife" style="height:100px" itemprop="image"></a></span><span itemscope itemtype="http://schema.org/Photograph"><a href="http://f.hatena.ne.jp/KAZYPinkSaurus/20210214222956" class="hatena-fotolife" itemprop="url"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KAZYPinkSaurus/20210214/20210214222956.jpg" alt="f:id:KAZYPinkSaurus:20210214222956j:image:h100" title="" class="hatena-fotolife" style="height:100px" itemprop="image"></a></span></p> <p>GO!!!</p> <p><iframe width="420" height="315" src="https://www.youtube.com/embed/UgSXliF7SZE?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><cite class="hatena-citation"><a href="https://youtu.be/UgSXliF7SZE">youtu.be</a></cite></p> <p><a href="https://www.youtube.com/watch?v=r1FjifJl3AM">元ネタ</a></p> <p>いやぁ...お腹が痛い。。。</p> <p>キレッキレじゃないですか。</p> <p>たまにありえない動きしちゃうあたりが, 手法的にはマイナスなんでしょうがお笑い的には◎。</p> <p>いやぁ 笑った笑った。</p> <h1>最後に</h1> <p>今回はLiquid Warping GAN with Attention: A Unified Framework for Human Image Synthesisという論文の手法を用いてホクソエムのおじさんの画像を動画と混ぜ合わせて動かしました。</p> <p>実装がColaboratoryにアップロードされておりとても簡単に動かすことができました。</p> <p>アウトプットを見るのがとにかく面白かったので, 皆様も身近な人で試してみてはいかがでしょうか。</p> <p>おじさん達の運動不足を解消したので私はホクソエムからボーナス間違いなしですね 。💴💴💴💴💴</p> KAZYPinkSaurus darts-cloneを使って最長一致法で分かち書きしてみる hatenablog://entry/26006613642264176 2020-10-21T10:00:00+09:00 2020-10-21T10:00:03+09:00 ホクソエムサポーターの白井です。 呪術廻戦をみて喜久福が食べたくなりました *1。 今回は形態素解析について深堀りしてみます。 日本語の自然言語処理において、形態素解析は必ずといっていいほど通る道です。 形態素解析を必要としないSentencePieceのような深層学習向けのtokenizerも出現していますが、品詞単位で分割する形態素解析が重要であることは変わりありません。 そんなこんなで、『実践・自然言語処理シリーズ2 形態素解析の理論と実装』 (以降「形態素解析本」と表記)を読んでいます。 リンク先の目次を見て分かるとおり、基礎の部分から実装まで説明されている本です。 今回は4章で紹介さ… <p>ホクソエムサポーターの白井です。</p> <p><a href="https://nijimen.net/tags/jujutsukaisen">呪術廻戦</a>をみて<a href="https://www.kikusuian.com/hpgen/HPB/categories/7290.html">喜久福</a>が食べたくなりました <a href="#f-3e241f0e" name="fn-3e241f0e" title="宮城にしかないと思っていたのですが、ググったら関東にも店舗を出店しているみたいで、正直驚いています。">*1</a>。</p> <p>今回は形態素解析について深堀りしてみます。</p> <p>日本語の自然言語処理において、形態素解析は必ずといっていいほど通る道です。 形態素解析を必要としない<a href="https://github.com/google/sentencepiece">SentencePiece</a>のような深層学習向けのtokenizerも出現していますが、品詞単位で分割する形態素解析が重要であることは変わりありません。</p> <p>そんなこんなで、<a href="https://www.kindaikagaku.co.jp/information/kd0577.htm">『実践・自然言語処理シリーズ2 形態素解析の理論と実装』</a> (以降「形態素解析本」と表記)を読んでいます。 リンク先の目次を見て分かるとおり、基礎の部分から実装まで説明されている本です。</p> <p>今回は4章で紹介されている <a href="https://github.com/s-yata/darts-clone">darts-clone</a> を使って、精度は粗いが高速で分かち書きができる最長一致法で、どれぐらい分かち書きが可能かを検証します。</p> <ul class="table-of-contents"> <li><a href="#事前知識辞書引き">事前知識・辞書引き</a></li> <li><a href="#darts-cloneを使ってみる">darts-cloneを使ってみる</a><ul> <li><a href="#単語辞書">単語辞書</a></li> <li><a href="#darts-clone">darts-clone</a></li> <li><a href="#辞書をdarts-clone形式に変換">辞書をdarts-clone形式に変換</a></li> </ul> </li> <li><a href="#最長一致法の実装">最長一致法の実装</a><ul> <li><a href="#python実装">python実装</a></li> <li><a href="#検証">検証</a><ul> <li><a href="#データ">データ</a></li> <li><a href="#結果と考察">結果と考察</a></li> </ul> </li> </ul> </li> <li><a href="#おわりに">おわりに</a></li> <li><a href="#参考資料">参考資料</a><ul> <li><a href="#おまけ資料SudachiPyで最長一致法">おまけ資料:SudachiPyで最長一致法</a></li> </ul> </li> </ul> <h2 id="事前知識辞書引き">事前知識・辞書引き</h2> <p>辞書を使って分かち書きする場合、単語の検索時間が課題となります。 単語を検索するのではなく、部分文字列から辞書に含まれる単語を探索するためです。</p> <p>すべての開始位置と終了位置を探索し、辞書に含まれるかどうか判定する場合、計算時間がO(n<sup>2</sup>)かかります。 (実際はハッシュ値を計算するのに文字列長に依存したコストがかかるため、O(n<sup>3</sup>)です)</p> <p>一般的に、効率的な単語の探索には <strong>共通接頭辞検索 (Common Prefix Search)</strong> による辞書引きが用いられます。 これは文字列を前から探索し、前方の部分文字列と一致する単語を探す方法です。</p> <p>共通接頭辞検索には <strong>トライ (Trie)</strong> と呼ばれるデータ構造を用います。 トライは木構造で、文字を1つのノードとし、遷移することで文字列を表現します。</p> <p><a title="Booyabazooka (based on PNG image by Deco). Modifications by Superm401., Public domain, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Trie_example.svg"><img width="256" alt="Trie example" src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/be/Trie_example.svg/256px-Trie_example.svg.png"></a></p> <p><small><a href="https://commons.wikimedia.org/wiki/File:Trie_example.svg">Booyabazooka (based on PNG image by Deco). Modifications by Superm401.</a>, Public domain, via Wikimedia Commons</small></p> <p><strong>ダブル配列</strong> はトライの実装方法のひとつであり、<a href="http://chasen.org/~taku/software/darts/">darts</a> や <a href="https://github.com/s-yata/darts-clone">darts-clone</a> はその実装ライブラリです。 ダブル配列についての詳しい説明は形態素解析本や <a href="https://github.com/s-yata/darts-clone/blob/master/doc/ja/Introduction.md#%E5%8F%82%E8%80%83%E6%96%87%E7%8C%AE">darts-cloneに記載の参考文献</a> を参照してください。</p> <h2 id="darts-cloneを使ってみる">darts-cloneを使ってみる</h2> <p>実際にdarts-cloneを動かしてみます。</p> <p>単語辞書を準備し、darts-clone形式で構築することを目指します。</p> <h3 id="単語辞書">単語辞書</h3> <p>辞書はmecab用の辞書であるIPA辞書を使います。 <a href="https://taku910.github.io/mecab/">Mecabのページ</a>からダウンロードし、インストールします。</p> <p>デフォルトの文字コードは <code>enc-jp</code> ですが、今回は扱いやすいように<code>utf-8</code> に変換します。 darts-cloneで辞書を作成するだけであれば、品詞などの情報が不要なので、単語だけ切り取り、ソートしておきます。</p> <pre class="code lang-zsh" data-lang="zsh" data-unlink>$ iconv -f EUC-JP /path/to/mecab-ipadic/Noun.csv<span class="synStatement">|</span>cut -d<span class="synConstant">&quot;,&quot;</span> -f1 <span class="synStatement">|</span>LC_ALL=C sort<span class="synStatement">|</span>uniq <span class="synStatement">&gt;</span> /output/path/Noun.csv </pre> <p>iconvを使って文字コードを変換していますが、configureで文字コードを再構築することもできるようです。(<a href="https://taku910.github.io/mecab/#charset">文字コード変更</a>)</p> <p>また、日本語をソートする場合、sortの前に <code>LC_ALL=C</code> のオプションが必要なので注意しましょう。正しくソートされていないと辞書を構築する際エラーになります。</p> <p>ちなみに複数ファイルをまとめる場合、awkを使ってiconvをかませました。</p> <pre class="code lang-zsh" data-lang="zsh" data-unlink>$ ls Noun.* <span class="synStatement">|</span> awk <span class="synConstant">'{print &quot;iconv -f EUC-JP &quot; $1}'</span><span class="synStatement">|</span>sh <span class="synStatement">|</span>cut -d<span class="synConstant">&quot;,&quot;</span> -f1 <span class="synStatement">|</span>LC_ALL=C sort<span class="synStatement">|</span>uniq <span class="synStatement">&gt;&gt;</span> /output/path/Noun_all.csv </pre> <h3 id="darts-clone">darts-clone</h3> <p><a href="https://github.com/s-yata/darts-clone">darts-clone</a> を <code>git clone</code> しインストールします。<code>configure</code> ファイルがないので、<code>autoreconf</code> で <code>configure</code> ファイルを作成します。 このとき、<code>autoreconf automake</code> パッケージをあらかじめインストールしておく必要があります。</p> <p>Mac OSで試しているため、<code>brew</code> でインストールしていますが、それぞれのOSに合わせて <code>darts-clone</code> をインストールしてください。</p> <pre class="code lang-zsh" data-lang="zsh" data-unlink>$ brew install autoreconf automake $ autoreconf -i $ ./configure $ make </pre> <h3 id="辞書をdarts-clone形式に変換">辞書をdarts-clone形式に変換</h3> <p>darts-cloneには、単語一覧をDoubleArrayFile に変換するプログラム <code>mkdarts</code> が付属しているので、それを用います。</p> <pre class="code lang-zsh" data-lang="zsh" data-unlink>$ ./src/mkdarts /path/to/Noun.csv /path/to/ipadic_dict/Noun.dic keys: <span class="synConstant">58793</span> total: <span class="synConstant">511631</span> Making double-array: <span class="synConstant">100</span>% <span class="synStatement">|</span>*******************************************<span class="synStatement">|</span> size: <span class="synConstant">275968</span> total_size: <span class="synConstant">1103872</span> </pre> <p>名詞だけを変換し、<code>Noun.dic</code> を構築しました。</p> <p>実際に動くか確認しましょう。 <code>darts</code> を実行すると共通接頭辞検索がインタラクティブに実行できます。</p> <pre class="code lang-zsh" data-lang="zsh" data-unlink>$ ./src/darts /path/to/ipadic_dict/Noun.dic もも もも: found, num = <span class="synConstant">1</span> <span class="synConstant">3822</span>:<span class="synConstant">6</span> すもも すもも: found, num = <span class="synConstant">2</span> <span class="synConstant">1996</span>:<span class="synConstant">3</span> <span class="synConstant">2072</span>:<span class="synConstant">9</span> ほげ ほげ: not found </pre> <p>出力ではマッチした要素の個数 (<code>num = 1</code>) と、単語keyに対応するvalueと文字長 (<code>3822:6</code>) が返ってきます。 文字長が単語の長さと一致しないのはバイト文字で表されているためです。 日本語で使う文字の1文字はだいたい3バイトなので、「もも」の結果にある文字長6は2文字 (=もも)にマッチしたという意味になります。</p> <p>(もちろん3バイトではない文字もあります。<a href="http://orange-factory.com/dnf/utf-8.html">UTF-8の文字コード表 - 備忘帳 - オレンジ工房</a>などで一覧で見られるので参考にしてください。)</p> <p>共通接頭辞の検索では、複数の単語と一致する場合もあります。 例えば、下の実行結果をみると「すもも」の場合、「す」と「すもも」の2つにマッチしているのがわかります。</p> <pre class="code lang-zsh" data-lang="zsh" data-unlink>$ ./src/darts /path/to/ipadic_dict/Noun.dic す す: found, num = <span class="synConstant">1</span> <span class="synConstant">1996</span>:<span class="synConstant">3</span> すも すも: found, num = <span class="synConstant">1</span> <span class="synConstant">1996</span>:<span class="synConstant">3</span> すもも すもも: found, num = <span class="synConstant">2</span> <span class="synConstant">1996</span>:<span class="synConstant">3</span> <span class="synConstant">2072</span>:<span class="synConstant">9</span> </pre> <h2 id="最長一致法の実装">最長一致法の実装</h2> <p>darts-cloneを使って、最長一致で分かち書きするプログラムを実装します。 最長一致法は最初の文字から共通接頭辞検索し、一番 <strong>長く</strong> 一致した単語を採用する、ルールベースの分かち書きです。</p> <p>「すもももももももものうち」の場合、以下のように実行します。</p> <pre class="code lang-zsh" data-lang="zsh" data-unlink>$ ./src/darts Noun.dic すもももももももものうち すもももももももものうち: found, num = <span class="synConstant">2</span> <span class="synConstant">1996</span>:<span class="synConstant">3</span> <span class="synConstant">2072</span>:<span class="synConstant">9</span> <span class="synComment"># 「す」「すもも」 → 「すもも」</span> もももももものうち もももももものうち: found, num = <span class="synConstant">1</span> <span class="synConstant">3822</span>:<span class="synConstant">6</span> <span class="synComment"># 「もも」 → 「もも」</span> もももものうち <span class="synComment"># </span> <span class="synComment"># 中略</span> <span class="synComment"># </span> のうち のうち: not found <span class="synComment"># 一致単語なし → 「の」</span> うち うち: found, num = <span class="synConstant">1</span> <span class="synConstant">358</span>:<span class="synConstant">6</span> <span class="synComment"># 「うち」→ 「うち」</span> </pre> <p>ここで注意したいのは以下の2点です。</p> <ul> <li>「す」「すもも」の2つと一致する場合、長い「すもも」を1単語する</li> <li>「のうち」のように一致する単語がない場合、一番最初の1文字である「の」を1単語とする</li> </ul> <p>結果「すもも/もも/もも/もも/の/うち」と分割できました。 名詞辞書だけの場合、「も」1文字ではなく「もも(桃)」で分割されていますね。</p> <p>この例だと最長一致法では全然うまくいかないように見えますが、「スモモも桃も桃のうち」のようにカタカナ・漢字を混ぜると正しく分割されそうなことは感覚的にわかるかと思います。</p> <p>実際、形態素解析本には以下のように述べられています。</p> <blockquote><p>単純なアルゴリズムにもかかわらず,90%以上の分割精度が得られるため,大規模なテキスト集合から大ざっぱな単語頻度を高速に求める処理に向いています.</p> <p>実践・自然言語処理シリーズ2 形態素解析の理論と実装 P76</p></blockquote> <p>では、どれぐらいの精度で分割可能なのか確かめてみます。</p> <h3 id="python実装">python実装</h3> <p>ここからはdarts-cloneのpythonバインディングを利用し、pythonで実装します。 <a href="https://github.com/rixwew/darts-clone-python">darts-clone-python</a>は<a href="https://github.com/WorksApplications/SudachiPy">SudachiPy</a>にも使われているdarts-cloneのpythonバインディングです。</p> <p>形態素解析本のC++実装とSudachiPyの実装を参考にしつつ実装します。</p> <p>Python 3.8 で動作確認しています。 darts-clone-pythonは <a href="https://github.com/rixwew/darts-clone-python/releases/tag/v0.9.0">v0.9.0</a> です。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> sys <span class="synPreProc">import</span> dartsclone <span class="synStatement">class</span> <span class="synIdentifier">DartsDict</span>: <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, filename): self.trie = self.load_darts(filename) <span class="synPreProc">@</span><span class="synIdentifier">staticmethod</span> <span class="synStatement">def</span> <span class="synIdentifier">load_darts</span>(filename): darts = dartsclone.DoubleArray() darts.open(filename) <span class="synStatement">return</span> darts <span class="synStatement">def</span> <span class="synIdentifier">longest_match</span>(self, line: <span class="synIdentifier">str</span>): line_b = line.encode(<span class="synConstant">&quot;utf-8&quot;</span>) begin_str = <span class="synConstant">0</span> begin = <span class="synConstant">0</span> end = <span class="synIdentifier">len</span>(line_b) <span class="synStatement">while</span> begin &lt; end: longest_length = <span class="synConstant">0</span> key = line_b[begin:] result = self.trie.common_prefix_search(key, length=<span class="synIdentifier">len</span>(key)) <span class="synStatement">for</span> (word_id, length) <span class="synStatement">in</span> result: longest_length = <span class="synIdentifier">max</span>(longest_length, length) <span class="synStatement">if</span> longest_length == <span class="synConstant">0</span>: <span class="synComment"># 一致する単語がなかった場合</span> longest_length = <span class="synIdentifier">len</span>(line[begin_str].encode(<span class="synConstant">&quot;utf-8&quot;</span>)) word = line_b[begin:begin+longest_length].decode() <span class="synStatement">yield</span> word begin += longest_length begin_str += <span class="synIdentifier">len</span>(word) <span class="synStatement">def</span> <span class="synIdentifier">sample</span>(): darts = DartsDict(<span class="synConstant">&quot;/path/to/ipadic_dict/Noun.dic&quot;</span>) <span class="synStatement">for</span> w <span class="synStatement">in</span> darts.longest_match(<span class="synConstant">&quot;すもももももももものうち&quot;</span>): <span class="synIdentifier">print</span>(w) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;EOS&quot;</span>) <span class="synStatement">def</span> <span class="synIdentifier">main</span>(dic_file, text_file): darts = DartsDict(dic_file) <span class="synStatement">with</span> <span class="synIdentifier">open</span>(text_file, <span class="synConstant">&quot;r&quot;</span>)<span class="synStatement">as</span> f: <span class="synStatement">for</span> line <span class="synStatement">in</span> f: words = [] <span class="synStatement">for</span> w <span class="synStatement">in</span> darts.longest_match(line.strip()): words.append(w) <span class="synIdentifier">print</span>(<span class="synConstant">&quot; &quot;</span>.join(words)) <span class="synStatement">if</span> __name__ == <span class="synConstant">'__main__'</span>: args = sys.argv[<span class="synConstant">1</span>:] <span class="synStatement">if</span> <span class="synIdentifier">len</span>(args) &lt; <span class="synConstant">2</span>: <span class="synIdentifier">print</span>(<span class="synConstant">&quot;Usage: python main.py dic_file text_file&quot;</span>) <span class="synStatement">else</span>: main(args[<span class="synConstant">0</span>], args[<span class="synConstant">1</span>]) </pre> <h3 id="検証">検証</h3> <p>実装したところで、最長一致法でどこまで正確に分かち書きできるのか検証していきます。</p> <p>今回は <a href="https://www.rondhuit.com/download.html">livedoor ニュースコーパス</a> のテキストを使ってmecabの分かち書きと比較していきます。</p> <h4 id="データ">データ</h4> <p><a href="https://www.rondhuit.com/download.html">livedoor ニュースコーパス</a>からダウンロードできるデータ <code>ldcc-20140209.tar.gz</code> の記事から、トピックごとそれぞれ1つずつ、合計9つの文書を使います。</p> <p>具体的にはディレクトリごとsortして一番上のテキストデータを使いました。 ただし、1~2行目のURL・日付は取り除いてます。</p> <p>辞書はipadic (<code>mecab-ipadic-2.7.0-20070801</code>) のcsvを使って4種類作成しました。 名詞・動詞を選んだのは単語数が多く、重要度が高いと予想したためです。</p> <table> <thead> <tr> <th> 名前 </th> <th> 説明 </th> <th> 単語数 </th> </tr> </thead> <tbody> <tr> <td> Noun </td> <td> 名詞・一般 (<code>Noun.csv</code>) だけ </td> <td> 58,793 </td> </tr> <tr> <td> NounAll </td> <td> 全ての名詞 (<code>Noun*.csv</code>で指定) </td> <td> 197,489 </td> </tr> <tr> <td> Verb </td> <td> 動詞 (<code>Verb.csv</code>)だけ </td> <td> 101,751 </td> </tr> <tr> <td> NounVerb </td> <td> NounAll + Verb </td> <td> 296,935 </td> </tr> </tbody> </table> <p>mecabのIPA辞書を使った <code>-Owakati</code> の出力結果を正解データとし、正解データと一致したかどうかで評価します。 <a href="https://teru-oka-1933.github.io/meval/">MevAL</a> の <a href="https://teru-oka-1933.github.io/meval/#wbd_err_analysis">単語境界判定のエラー分析</a> をベースに評価コードを作成しました。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">eval_line</span>(gold, pred): gold_cnt, pred_cnt = <span class="synConstant">0</span>, <span class="synConstant">0</span> idx = <span class="synConstant">0</span> tp, fp, fn = <span class="synConstant">0</span>, <span class="synConstant">0</span>, <span class="synConstant">0</span> <span class="synStatement">for</span> g <span class="synStatement">in</span> gold: gold_cnt += <span class="synIdentifier">len</span>(g) <span class="synStatement">if</span> g == pred[idx]: <span class="synComment"># print(&quot;true &quot;, g, pred[idx])</span> tp += <span class="synConstant">1</span> pred_cnt += <span class="synIdentifier">len</span>(pred[idx]) idx += <span class="synConstant">1</span> <span class="synStatement">else</span>: <span class="synStatement">if</span> gold_cnt &lt; pred_cnt + <span class="synIdentifier">len</span>(pred[idx]): fn += <span class="synConstant">1</span> <span class="synComment"># print(&quot;fn &quot;, g, pred[idx])</span> <span class="synStatement">else</span>: tmp = [] <span class="synStatement">while</span> gold_cnt &gt; pred_cnt: tmp.append(pred[idx]) pred_cnt += <span class="synIdentifier">len</span>(pred[idx]) idx += <span class="synConstant">1</span> fp += <span class="synConstant">1</span> <span class="synComment"># print(&quot;fp &quot;, g, &quot; &quot;.join(tmp))</span> <span class="synStatement">return</span> tp, fp, fn </pre> <h4 id="結果と考察">結果と考察</h4> <p>文書ごとのF値は以下の表のとおりです。 基本的に単語数が多いほどF値は高い結家となりました。</p> <table> <thead> <tr> <th> </th> <th style="text-align:right;"> Noun </th> <th style="text-align:right;"> NounAll </th> <th> Verb </th> <th style="text-align:right;"> NounVerb </th> <th style="text-align:right;"> 行数 </th> </tr> </thead> <tbody> <tr> <td> dokujo-tsushin-4778030.txt </td> <td style="text-align:right;"> 0.593 </td> <td style="text-align:right;"> 0.725 </td> <td> 0.556 </td> <td style="text-align:right;"> 0.766 </td> <td style="text-align:right;"> 25 </td> </tr> <tr> <td> it-life-hack-6292880.txt </td> <td style="text-align:right;"> 0.567 </td> <td style="text-align:right;"> 0.680 </td> <td> 0.449 </td> <td style="text-align:right;"> 0.725 </td> <td style="text-align:right;"> 32 </td> </tr> <tr> <td> kaden-channel-5774093.txt </td> <td style="text-align:right;"> 0.659 </td> <td style="text-align:right;"> 0.807 </td> <td> 0.574 </td> <td style="text-align:right;"> 0.849 </td> <td style="text-align:right;"> 19 </td> </tr> <tr> <td> livedoor-homme-4568088.txt </td> <td style="text-align:right;"> 0.693 </td> <td style="text-align:right;"> 0.847 </td> <td> 0.565 </td> <td style="text-align:right;"> 0.878 </td> <td style="text-align:right;"> 18 </td> </tr> <tr> <td> movie-enter-5840081.txt </td> <td style="text-align:right;"> 0.612 </td> <td style="text-align:right;"> 0.702 </td> <td> 0.642 </td> <td style="text-align:right;"> 0.788 </td> <td style="text-align:right;"> 23 </td> </tr> <tr> <td> peachy-4289213.txt </td> <td style="text-align:right;"> 0.638 </td> <td style="text-align:right;"> 0.765 </td> <td> 0.534 </td> <td style="text-align:right;"> 0.795 </td> <td style="text-align:right;"> 16 </td> </tr> <tr> <td> smax-6507397.txt </td> <td style="text-align:right;"> 0.485 </td> <td style="text-align:right;"> 0.536 </td> <td> 0.425 </td> <td style="text-align:right;"> 0.555 </td> <td style="text-align:right;"> 79 </td> </tr> <tr> <td> sports-watch-4597641.txt </td> <td style="text-align:right;"> 0.614 </td> <td style="text-align:right;"> 0.780 </td> <td> 0.562 </td> <td style="text-align:right;"> 0.841 </td> <td style="text-align:right;"> 10 </td> </tr> <tr> <td> topic-news-5903225.txt </td> <td style="text-align:right;"> 0.517 </td> <td style="text-align:right;"> 0.607 </td> <td> 0.463 </td> <td style="text-align:right;"> 0.658 </td> <td style="text-align:right;"> 58 </td> </tr> </tbody> </table> <p>一番F値が良いのNounVerbのprecisionとrecallをみてみるとprecisionが低いことがわかります。 FalsePositive(正解にはない境界があると予測)、つまり正解より細かく分割している数が多いということです。</p> <table> <thead> <tr> <th> </th> <th> precision </th> <th> recall </th> <th> F1 </th> </tr> </thead> <tbody> <tr> <td> dokujo-tsushin-4778030.txt </td> <td> 0.683 </td> <td> 0.872 </td> <td> 0.766 </td> </tr> <tr> <td> it-life-hack-6292880.txt </td> <td> 0.585 </td> <td> 0.954 </td> <td> 0.725 </td> </tr> <tr> <td> kaden-channel-5774093.txt </td> <td> 0.770 </td> <td> 0.946 </td> <td> 0.849 </td> </tr> <tr> <td> livedoor-homme-4568088.txt </td> <td> 0.802 </td> <td> 0.970 </td> <td> 0.878 </td> </tr> <tr> <td> movie-enter-5840081.txt </td> <td> 0.695 </td> <td> 0.909 </td> <td> 0.788 </td> </tr> <tr> <td> peachy-4289213.txt </td> <td> 0.704 </td> <td> 0.912 </td> <td> 0.795 </td> </tr> <tr> <td> smax-6507397.txt </td> <td> 0.398 </td> <td> 0.919 </td> <td> 0.555 </td> </tr> <tr> <td> sports-watch-4597641.txt </td> <td> 0.765 </td> <td> 0.934 </td> <td> 0.841 </td> </tr> <tr> <td> topic-news-5903225.txt </td> <td> 0.507 </td> <td> 0.937 </td> <td> 0.658 </td> </tr> </tbody> </table> <p>具体的に分割結果をみてみます。</p> <p>まず、以下のように名詞、特に漢字が多い文においてはほぼ同じ分割になりました。</p> <blockquote><p>今月 8 日 、 都内 ホテル で は 、 総合 格闘 家 ・ 吉田 秀彦 の 引退 試合 興行 「 ASTRA 」 の 開催 が 発表 さ れ た 。</p> <p>今月 8 日 、 都内 ホテル で は 、 総合 格闘 家 ・ 吉田 秀彦 の 引退 試合 興行 「 A S T R A 」 の 開催 が 発表 され た 。 上がmecab、下がNounVerbの分割 sports-watch-4597641.txt</p></blockquote> <p>一方、辞書の問題として、「Twitter」や「TV」のような英単語が「T w i t t e r」「T V」と1文字ごと分割されてしまう課題があります。 これは英単語を辞書に登録することで解消されるはずです。</p> <blockquote><p>画面 下 に は Facebook 、 Twitter 、 SHARE ( Facebook 、 Twitter 、 メール 、 SMS ) で の 共有 、</p> <p>画面 下 に は F a c e b o o k 、 T w i t t e r 、 S H A R E ( F a c e b o o k 、 T w i t t e r 、 メール 、 S M S ) で の 共有 、 上がmecab、下がNounVerbの分割 smax-6507397.txt</p></blockquote> <p>しかしながら、「すももも〜」と同様、ひらがなの多い部分については最長一致というルール上、mecabと同じ結果を出力することができないです。</p> <blockquote><p>その 過半数 は 毎年 5 , 000 円 程度 かかる 更新 費用 や その 手続き について 不満 を 持っ て いる 。(中略)性能 面 で 劣る の で は という 不安 から 導入 を 控え て いる という 状況 に ある 。</p> <p>そ の 過半数 は 毎年 5 , 0 0 0 円 程度 かかる 更新 費用 や そ の 手続き につい て 不満 を 持っ てい る 。(中略)性能 面 で 劣る の で はと いう 不安 から 導入 を 控え てい る とい う 状況 に ある 。 上がmecab、下がNounVerbの分割 it-life-hack-6292880.txt</p></blockquote> <p>上の例ではNounVerbが「持っている」「控えている」について、「持っ/てい/る」「控え/てい/る」と左に最長であるように分割されます。 ですが、正しい分割は以下のように「て/いる」であり、「いる」が動詞であるように分割したい部分です。</p> <pre class="code" data-lang="" data-unlink>持っ 動詞,自立,*,*,五段・タ行,連用タ接続,持つ,モッ,モッ て 助詞,接続助詞,*,*,*,*,て,テ,テ いる 動詞,非自立,*,*,一段,基本形,いる,イル,イル</pre> <h2 id="おわりに">おわりに</h2> <p>最長一致法を実装しながら、ダブル配列ライブラリdarts-cloneの使い方を解説しました。</p> <p>ナイーブな最長一致法は高速かつ、ある程度は使える実装ですが、分割には課題点もあります。</p> <p>mecabの実装のように、最適化アルゴリズムを使うべきだというお気持ちがちょっと分かった気がします。</p> <p>また、今回は細かい部分は割愛して <code>darts.open(filename)</code> で辞書を読み込みましたが、形態素解析本では全部をメモリに載せず <a href="https://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%A2%E3%83%AA%E3%83%9E%E3%83%83%E3%83%97%E3%83%88%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB"><em>メモリマップトファイル</em></a> (pythonでは <code>mmap</code>) を使って最適化する実装が提案されています。</p> <p>実際、IPA辞書の全単語でdarts-cloneの辞書を構築してメモリに乗せようとするとsegmatation faultします……。 この記事はあくまで「使ってみた」系記事ですので、ご了承ください。</p> <h2 id="参考資料">参考資料</h2> <p>mecabとdarts関連</p> <ul> <li>mecab <a href="https://taku910.github.io/mecab/">https://taku910.github.io/mecab/</a></li> <li>darts <a href="http://chasen.org/~taku/software/darts/">http://chasen.org/~taku/software/darts/</a></li> <li><a href="https://www.aclweb.org/anthology/W04-3230/">Applying Conditional Random Fields to Japanese Morphological Analysis</a> (paper)</li> <li><a href="https://www.kindaikagaku.co.jp/information/kd0577.htm">実践・自然言語処理シリーズ 第2巻 形態素解析の理論と実装</a></li> </ul> <h3 id="おまけ資料SudachiPyで最長一致法">おまけ資料:SudachiPyで最長一致法</h3> <p>前述のように、SudachiPyはdarts-cloneで辞書引きしているので、実装されたクラスをうまく活用すれば最長一致法での分かち書きが実装できます。</p> <p>具体的には <a href="https://github.com/WorksApplications/SudachiPy/blob/v0.4.9/sudachipy/dictionarylib/binarydictionary.py">BinaryDictionary</a> クラスで辞書の設定をしているので、そこから単語辞書 (<a href="https://github.com/WorksApplications/SudachiPy/blob/v0.4.9/sudachipy/dictionarylib/doublearraylexicon.py">DoubleArrayLexicon</a>のインスタンス)だけを抜き出して使います。</p> <p>ちなみに<a href="https://github.com/WorksApplications/SudachiPy/blob/v0.4.9/sudachipy/dictionarylib/doublearraylexicon.py">DoubleArrayLexicon</a>では <code>mmap</code> を使った辞書の読み込みを行ってます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> pathlib <span class="synPreProc">import</span> Path <span class="synPreProc">from</span> sudachipy.dictionarylib.binarydictionary <span class="synPreProc">import</span> BinaryDictionary <span class="synComment"># 辞書のパス</span> system_dic = (Path(import_module(<span class="synConstant">'sudachidict_core'</span>).__file__).parent / <span class="synConstant">'resources'</span> / <span class="synConstant">'system.dic'</span>) dict_ = BinaryDictionary.from_system_dictionary(system_dic) lexicon = dict_.lexicon <span class="synStatement">def</span> <span class="synIdentifier">longest_match</span>(line: <span class="synIdentifier">str</span>): line_b = line.encode(<span class="synConstant">&quot;utf-8&quot;</span>) begin_str = <span class="synConstant">0</span> begin = <span class="synConstant">0</span> end = <span class="synIdentifier">len</span>(line_b) <span class="synStatement">while</span> begin &lt; end: longest_length = <span class="synConstant">0</span> longest_word_id = <span class="synIdentifier">None</span> <span class="synStatement">for</span> (word_id, length) <span class="synStatement">in</span> lexicon.lookup(line_b, begin): <span class="synStatement">if</span> longest_length &lt; length: longest_length = length longest_word_id = word_id <span class="synStatement">if</span> longest_length==<span class="synConstant">0</span>: <span class="synComment"># print(line[begin_str])</span> longest_length = <span class="synIdentifier">len</span>(line[begin_str].encode(<span class="synConstant">&quot;utf-8&quot;</span>)) word = line_b[begin:longest_length].decode() <span class="synStatement">yield</span> word begin = longest_length begin_str += <span class="synIdentifier">len</span>(word) </pre> <p>SudachiPyは <code>v0.4.9</code> で確認しています。</p> <p>SudachiPyの辞書である<a href="https://github.com/WorksApplications/SudachiDict">SudachiDict</a> の語彙数が多いため、結構正確に分割できると思います。 当たり前ですがSudachiPyより高速です。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/B07J1NBNYW/hoxom-22/"><img src="https://m.media-amazon.com/images/I/51KM8RqknDL._SL160_.jpg" class="hatena-asin-detail-image" alt="実践・自然言語処理シリーズ2 形態素解析の理論と実装" title="実践・自然言語処理シリーズ2 形態素解析の理論と実装"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/B07J1NBNYW/hoxom-22/">実践・自然言語処理シリーズ2 形態素解析の理論と実装</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/%B9%A9%C6%A3%20%C2%F3" class="keyword">工藤 拓</a></li><li><span class="hatena-asin-detail-label">発売日:</span> 2018/10/04</li><li><span class="hatena-asin-detail-label">メディア:</span> Kindle版</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> <div class="footnote"> <p class="footnote"><a href="#fn-3e241f0e" name="f-3e241f0e" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">宮城にしかないと思っていたのですが、ググったら関東にも<a href="https://kikusuian.jp/shops/">店舗</a>を出店しているみたいで、正直驚いています。</span></p> </div> sh111h EDINET APIって知ってる? ~有価証券報告書をもっと楽にダウンロードする話~ hatenablog://entry/26006613639908244 2020-10-14T10:27:46+09:00 2020-10-14T10:29:41+09:00 はじめに こんにちは, ホクソエムサポーターのKAZYです。 最近はペンギンに興味があります🐧。 世界最大のペンギンであるコウテイペンギンを日本で見るならば名古屋港水族館 (愛知) かアドベンチャーワールド (和歌山) らしいです。 ところで, 平成31年3月17日からEDINETに提出された書類をAPIで取得できるようになったことをご存知でしょうか? だからなんなの?っていう方聞いてください。 もうブラウザポチポチやらなくても有価証券報告書ダウンロードできるんですよっ!!!! 「退屈なことはPythonにやらせよう」マンになる時が来たのです。 今回はEDINET APIで有価証券報告書を保存… <h2>はじめに</h2> <p>こんにちは, ホクソエムサポーターのKAZYです。</p> <p>最近はペンギンに興味があります🐧。</p> <p>世界最大のペンギンであるコウテイペンギンを日本で見るならば名古屋港水族館 (愛知) かアドベンチャーワールド (和歌山) らしいです。</p> <p>ところで, 平成31年3月17日から<a href="https://disclosure.edinet-fsa.go.jp/">EDINET</a>に提出された書類をAPIで取得できるようになったことをご存知でしょうか?</p> <p>だからなんなの?っていう方聞いてください。</p> <p><strong>もうブラウザポチポチやらなくても有価証券報告書ダウンロードできるんですよっ!!!!</strong></p> <p>「退屈なことはPythonにやらせよう」マンになる時が来たのです。</p> <p>今回は<code>EDINET APIで有価証券報告書を保存するための最低限の知識</code>と <code>任意の日付の有価証券報告書をダウンロードするPythonプログラム</code>を紹介します。</p> <h2>読んだら幸せになりそうな方</h2> <ul> <li>提出日を指定した有価証券報告書のダウンロードしたい方 (e.g. 1年分全部欲しいぜ!!!)</li> <li>ぼんやりEDINET APIで何ができるか知っておきたい方</li> </ul> <h2>読んでもあんまり幸せにならない方</h2> <ul> <li>企業名を指定した有価証券報告書のダウンロード (e.g. マクドナルドの有価証券報告書が5年分ほしいぜ!!!!) <ul> <li><a href="https://blog.hoxo-m.com/entry/2020/10/07/090258">ブラウザからダウンロード</a>が向いていると私は思います</li> </ul> </li> </ul> <p>↓ここで紹介しているぜ↓ <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.hoxo-m.com%2Fentry%2F2020%2F10%2F07%2F090258" title="有価証券報告テキストマイニング入門 - 株式会社ホクソエムのブログ" 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://blog.hoxo-m.com/entry/2020/10/07/090258">blog.hoxo-m.com</a></cite></p> <ul> <li>全然暇じゃないし, 正確of正確な情報を取りに行きたい <ul> <li><a href="https://disclosure.edinet-fsa.go.jp/download/ESE140192.zip">こちら</a>のAPIの仕様書に使い方は全て書いてある, Have a nice day👋</li> </ul> </li> </ul> <h2>どうでもいいからはよXBRLファイルをDLさせろ💢って方</h2> <p>KAZYの拙いスクリプトをどうぞ</p> <h3>poetry使える人</h3> <pre class="code" data-lang="" data-unlink>git clone https://github.com/KAZYPinkSaurus/disclosure-crowler.git cd disclosure-crowler # ライブラリのインストール poetry install #オプションを表示 poetry run python -m disclosure.main --help # 2020/09/04から2020/10/04の有価証券報告書をダウンロードしてxbrlファイルを抽出 poetry run python -m disclosure.main --from 2020-09-04 --to 2020-10-04 -x ls output/*</pre> <h3>poetry使えない人</h3> <pre class="code" data-lang="" data-unlink>git clone https://github.com/KAZYPinkSaurus/disclosure-crowler.git cd disclosure-crowler pip install requests==2.24.0 python-dateutil==2.8.1 loguru==0.5.3 click==7.1.2 #オプションを表示 poetry run python -m disclosure.main --help # 2020/09/04から2020/10/04の有価証券報告書をダウンロードしてxbrlファイルを抽出 python -m disclosure.main --from 2020-09-04 --to 2020-10-04 -x</pre> <p>KAZY&lt; ばいばい 👋</p> <p>🤖 &lt; ありがとうございました</p> <h2>APIって...?</h2> <p>According to Wikipedia, APIとは</p> <blockquote><p>アプリケーションプログラミングインタフェース(API、英: Application Programming Interface)とは、広義ではソフトウェアコンポーネント同士が互いに情報をやりとりするのに使用するインタフェースの仕様である。 前述のとおりAPIは各種システム/サービスがそのシステム/サービスを利用するアプリケーションに対して公開するインタフェースである。</p></blockquote> <p>ということのようです。 インターフェースと仕様提供するから退屈なことはPythonにやらせるんだぞっ!!と言う声が聞こえてくる気がします🦻。</p> <h2>EDINET APIについて</h2> <p>EDINET APIは以下の2つのAPIの総称です。</p> <ol> <li>提出された書類を<code>把握</code>するためのAPI <ul> <li>書類のメタ情報を教えてくれる</li> </ul> </li> <li>提出された書類を<code>取得</code>するためのAPI <ul> <li>実際に書類を手に入れられる</li> </ul> </li> </ol> <h3>取得できる期間</h3> <ul> <li>EDINET APIで取得できる書類は<code>直近5年分</code>です。<a href="#f-4d449fc8" name="fn-4d449fc8" title="ブラウザからダウンロードも同様">*1</a></li> </ul> <h2>特に注意してほしいこと</h2> <p>利用規約の第5条(禁止事項)には</p> <blockquote><p>1.利用者は、以下に掲げる行為を行ってはならないものとします。 <br>(1) 本機能の健全な運営を害する一切の行為 <br>(2) 短時間における大量のアクセスその他の本機能の運用に支障を与える行為</p></blockquote> <p>とあります。</p> <p>(2)の短時間の大量アクセスは起こしやすいので特に注意しましょう。</p> <p>とはどういう意味合いなのでしょう。まったくわかりません。</p> <p>〜〜〜以下、KAZYの脳内のやり取り〜〜〜</p> <p>KAZY &lt; どれくらいアクセスしたら大量アクセスなの?</p> <p>EDINET API&lt; う~ん, とにかく運用に支障を与えるくらいのアクセスはやめてくれってことや!</p> <p>KAZY.o0(運用次第やな....)</p> <p>EDINET API&lt; 無限ループするスクリプト書いてアクセスし続けるとかやめてくれよな!</p> <p>KAZY&lt; OK</p> <p>EDINET API&lt; アクセスするスクリプトをたくさん並列して動かすとかもやめてくれよな!!</p> <p>KAZY&lt; 理解</p> <h2>有価証券報告書のXBRLファイルをダウンロード(コマンドライン編)</h2> <p>きっと理解しやすい方がいると思うのでコマンドラインからの取得方法を見ていきましょう。</p> <h3>目標</h3> <ul> <li><code>2020/01/07</code>に提出された有価証券報告書を1つ取得する</li> </ul> <h3>流れ</h3> <ol> <li>日付をメタファイルを取得</li> <li>メタファイルから書類ID(docID)を取得</li> <li>docIDを指定して有価証券報告書をダウンロード</li> </ol> <h3>メタファイル取得(2020/01/07)</h3> <pre class="code" data-lang="" data-unlink>curl &#34;https://disclosure.edinet-fsa.go.jp/api/v1/documents.json?date=2020-01-07&amp;type=2&#34;</pre> <p>↓レスポンス</p> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">metadata</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">title</span>&quot;: &quot;<span class="synConstant">提出された書類を把握するためのAPI</span>&quot;, &quot;<span class="synStatement">parameter</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">date</span>&quot;: &quot;<span class="synConstant">2020-01-07</span>&quot;, &quot;<span class="synStatement">type</span>&quot;: &quot;<span class="synConstant">2</span>&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">resultset</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">count</span>&quot;: <span class="synConstant">179</span> <span class="synSpecial">}</span>, &quot;<span class="synStatement">processDateTime</span>&quot;: &quot;<span class="synConstant">2020-10-04 00:00</span>&quot;, &quot;<span class="synStatement">status</span>&quot;: &quot;<span class="synConstant">200</span>&quot;, &quot;<span class="synStatement">message</span>&quot;: &quot;<span class="synConstant">OK</span>&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">results</span>&quot;: <span class="synSpecial">[</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">seqNumber</span>&quot;: <span class="synConstant">1</span>, &quot;<span class="synStatement">docID</span>&quot;: &quot;<span class="synConstant">S100HNA6</span>&quot;, &quot;<span class="synStatement">edinetCode</span>&quot;: &quot;<span class="synConstant">E08957</span>&quot;, &quot;<span class="synStatement">secCode</span>&quot;: <span class="synIdentifier">null</span>, &quot;<span class="synStatement">JCN</span>&quot;: &quot;<span class="synConstant">4010401049128</span>&quot;, &quot;<span class="synStatement">filerName</span>&quot;: &quot;<span class="synConstant">三井住友DSアセットマネジメント株式会社</span>&quot;, &quot;<span class="synStatement">fundCode</span>&quot;: &quot;<span class="synConstant">G12668</span>&quot;, &quot;<span class="synStatement">ordinanceCode</span>&quot;: &quot;<span class="synConstant">030</span>&quot;, &quot;<span class="synStatement">formCode</span>&quot;: &quot;<span class="synConstant">07A000</span>&quot;, &quot;<span class="synStatement">docTypeCode</span>&quot;: &quot;<span class="synConstant">120</span>&quot;, &quot;<span class="synStatement">periodStart</span>&quot;: &quot;<span class="synConstant">2018-10-11</span>&quot;, &quot;<span class="synStatement">periodEnd</span>&quot;: &quot;<span class="synConstant">2019-10-10</span>&quot;, &quot;<span class="synStatement">submitDateTime</span>&quot;: &quot;<span class="synConstant">2020-01-07 09:01</span>&quot;, &quot;<span class="synStatement">docDescription</span>&quot;: &quot;<span class="synConstant">有価証券報告書(内国投資信託受益証券)-第2期(平成30年10月11日-令和1年10月10日)</span>&quot;, &quot;<span class="synStatement">issuerEdinetCode</span>&quot;: <span class="synIdentifier">null</span>, &quot;<span class="synStatement">subjectEdinetCode</span>&quot;: <span class="synIdentifier">null</span>, &quot;<span class="synStatement">subsidiaryEdinetCode</span>&quot;: <span class="synIdentifier">null</span>, &quot;<span class="synStatement">currentReportReason</span>&quot;: <span class="synIdentifier">null</span>, &quot;<span class="synStatement">parentDocID</span>&quot;: <span class="synIdentifier">null</span>, &quot;<span class="synStatement">opeDateTime</span>&quot;: <span class="synIdentifier">null</span>, &quot;<span class="synStatement">withdrawalStatus</span>&quot;: &quot;<span class="synConstant">0</span>&quot;, &quot;<span class="synStatement">docInfoEditStatus</span>&quot;: &quot;<span class="synConstant">0</span>&quot;, &quot;<span class="synStatement">disclosureStatus</span>&quot;: &quot;<span class="synConstant">0</span>&quot;, &quot;<span class="synStatement">xbrlFlag</span>&quot;: &quot;<span class="synConstant">1</span>&quot;, &quot;<span class="synStatement">pdfFlag</span>&quot;: &quot;<span class="synConstant">1</span>&quot;, &quot;<span class="synStatement">attachDocFlag</span>&quot;: &quot;<span class="synConstant">1</span>&quot;, &quot;<span class="synStatement">englishDocFlag</span>&quot;: &quot;<span class="synConstant">0</span>&quot; <span class="synSpecial">}</span>, <span class="synSpecial">{</span> &quot;<span class="synStatement">seqNumber</span>&quot;: <span class="synConstant">2</span>, &quot;<span class="synStatement">docID</span>&quot;: &quot;<span class="synConstant">S100HOID</span>&quot;, ︙続く </pre> <h3>メタファイルから書類ID(docID)を取得</h3> <p>文書IDは有価証券報告書のダウンロードのために必要です。</p> <p>先程のレスポンスを<code>results</code>-><code>docID</code>と辿ります。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>︙省略 <span class="synStatement">&quot;</span><span class="synConstant">docID</span><span class="synStatement">&quot;</span>: <span class="synStatement">&quot;</span><span class="synConstant">S100HNA6</span><span class="synStatement">&quot;</span>, ︙省略 </pre> <p>一番上の<code>docID</code>には<code>S100HNA6</code>という要素が入っていますね。</p> <p>これです。</p> <h3>有価証券報告書を取得</h3> <p>XBRLファイルが圧縮されたzipファイルを取得します。</p> <p>書類ID<code>S100HNA6</code>のファイルをダウンロードしてみましょう。</p> <p>エンドポイントは <code>https://disclosure.edinet-fsa.go.jp/api/v1/documents/ドキュメントのID </code> って感じで提供されています。</p> <p>そしてパラメータを<code>type=1</code>とすると書類が取得できます。<a href="#f-3dd698b5" name="fn-3dd698b5" title="ちなみに, typeを2にすると有価証券報告書のpdfファイルがダウンロードできます。 他はAppendixを参照してみてください。">*2</a></p> <p>ダウンロードしましょう (with <code>curl</code>コマンド)!!!</p> <pre class="code" data-lang="" data-unlink>## -o は標準週力じゃなくてファイルに書き出してくれるオプションだよ curl -o S100HNA6.zip &#34;https://disclosure.edinet-fsa.go.jp/api/v1/documents/S100HNA6?type=1&#34;</pre> <pre class="code" data-lang="" data-unlink>🐦.o0(たーみなる)% curl -o S100HNA6.zip &#34;https://disclosure.edinet-fsa.go.jp/api/v1/documents/S100HNA6?type=1&#34; % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 670k 100 670k 0 0 263k 0 0:00:02 0:00:02 --:--:-- 262k 🐦.o0(たーみなる)% ls -lh S100HNA6.zip -rw-r--r-- 1 kazy staff 670K 10 10 11:26 S100HNA6.zip</pre> <p>670KBのファイルがダウンロードされました。 やった!</p> <h2>有価証券報告書のXBRLファイルをダウンロード(Python編)</h2> <p>次にダウンロードするPythonスクリプトを書いてみます。</p> <p>🤖 &lt; requests 使うだけでしょ? そんなことでブログ膨らますなよ</p> <p>KAZY&lt; ....</p> <h3>目標(コマンドライン編と同じ)</h3> <ul> <li>2020/01/07に提出された有価証券報告書を1つ取得する</li> </ul> <h3>流れ(コマンドライン編と同じ)</h3> <ol> <li>日付をメタファイルを取得</li> <li>メタファイルから書類ID(docID)を取得</li> <li>docIDを指定して有価証券報告書をダウンロード</li> </ol> <h3>メタファイル取得(2020/01/07)</h3> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">import</span> requests &gt;&gt;&gt; 日付 = <span class="synConstant">&quot;2020-01-07&quot;</span> &gt;&gt;&gt; メタファイルのタイプ = <span class="synConstant">2</span> &gt;&gt;&gt; META_URL = f<span class="synConstant">&quot;https://disclosure.edinet-fsa.go.jp/api/v1/documents.json?date={日付}&amp;type={メタファイルのタイプ}&quot;</span> &gt;&gt;&gt; text = requests.get(META_URL).text &gt;&gt;&gt; <span class="synIdentifier">print</span>(text) { <span class="synConstant">&quot;metadata&quot;</span>: { <span class="synConstant">&quot;title&quot;</span>: <span class="synConstant">&quot;提出された書類を把握するためのAPI&quot;</span>, <span class="synConstant">&quot;parameter&quot;</span>: { ︙省略 </pre> <p>取得できました。</p> <h3>メタファイルから書類ID(docID)を取得</h3> <p>返ってきたjson形式のテキストを辞書として読み込みましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># さっきの続きだよ</span> &gt;&gt;&gt; <span class="synPreProc">import</span> json &gt;&gt;&gt; meta_dict = json.loads(text) &gt;&gt;&gt; meta_dict.keys() dict_keys([<span class="synConstant">'metadata'</span>, <span class="synConstant">'results'</span>]) </pre> <p>読み込めてそうですね。 1つ目の書類の情報を出力してみましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; meta_dict[<span class="synConstant">'results'</span>][<span class="synConstant">0</span>] {<span class="synConstant">'seqNumber'</span>: <span class="synConstant">1</span>, <span class="synConstant">'docID'</span>: <span class="synConstant">'S100HNA6'</span>, <span class="synConstant">'edinetCode'</span>: <span class="synConstant">'E08957'</span>, <span class="synConstant">'secCode'</span>: <span class="synIdentifier">None</span>, <span class="synConstant">'JCN'</span>: <span class="synConstant">'4010401049128'</span>, <span class="synConstant">'filerName'</span>: <span class="synConstant">'三井住友DSアセットマネジメント株式会社'</span>, <span class="synConstant">'fundCode'</span>: <span class="synConstant">'G12668'</span>, <span class="synConstant">'ordinanceCode'</span>: <span class="synConstant">'030'</span>, <span class="synConstant">'formCode'</span>: <span class="synConstant">'07A000'</span>, <span class="synConstant">'docTypeCode'</span>: <span class="synConstant">'120'</span>, <span class="synConstant">'periodStart'</span>: <span class="synConstant">'2018-10-11'</span>, <span class="synConstant">'periodEnd'</span>: <span class="synConstant">'2019-10-10'</span>, <span class="synConstant">'submitDateTime'</span>: <span class="synConstant">'2020-01-07 09:01'</span>, <span class="synConstant">'docDescription'</span>: <span class="synConstant">'有価証券報告書(内国投資信託受益証券)-第2期(平成30年10月11日-令和1年10月10日)'</span>, <span class="synConstant">'issuerEdinetCode'</span>: <span class="synIdentifier">None</span>, <span class="synConstant">'subjectEdinetCode'</span>: <span class="synIdentifier">None</span>, <span class="synConstant">'subsidiaryEdinetCode'</span>: <span class="synIdentifier">None</span>, <span class="synConstant">'currentReportReason'</span>: <span class="synIdentifier">None</span>, <span class="synConstant">'parentDocID'</span>: <span class="synIdentifier">None</span>, <span class="synConstant">'opeDateTime'</span>: <span class="synIdentifier">None</span>, <span class="synConstant">'withdrawalStatus'</span>: <span class="synConstant">'0'</span>, <span class="synConstant">'docInfoEditStatus'</span>: <span class="synConstant">'0'</span>, <span class="synConstant">'disclosureStatus'</span>: <span class="synConstant">'0'</span>, <span class="synConstant">'xbrlFlag'</span>: <span class="synConstant">'1'</span>, <span class="synConstant">'pdfFlag'</span>: <span class="synConstant">'1'</span>, <span class="synConstant">'attachDocFlag'</span>: <span class="synConstant">'1'</span>, <span class="synConstant">'englishDocFlag'</span>: <span class="synConstant">'0'</span>} </pre> <p>1つ目の書類の<code>docID</code>を出力してみましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; 書類のID = meta_dict[<span class="synConstant">'results'</span>][<span class="synConstant">0</span>][<span class="synConstant">'docID'</span>] &gt;&gt;&gt; 書類のID <span class="synConstant">'S100HNA6'</span> <span class="synComment"># ついでに名前も表示してみた</span> &gt;&gt;&gt; meta_dict[<span class="synConstant">'results'</span>][<span class="synConstant">0</span>][<span class="synConstant">'filerName'</span>] <span class="synConstant">'三井住友DSアセットマネジメント株式会社'</span> </pre> <p>取得できました。</p> <h3>有価証券報告書を取得</h3> <p>XBRLファイルが圧縮されたzipファイルを取得します。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; 書類のタイプ = <span class="synConstant">1</span> &gt;&gt;&gt; 書類取得のURL = f<span class="synConstant">&quot;https://disclosure.edinet-fsa.go.jp/api/v1/documents/{書類のID}?type={書類のタイプ}&quot;</span> &gt;&gt;&gt; response = requests.get(書類取得のURL) &gt;&gt;&gt; response &lt;Response [<span class="synConstant">200</span>]&gt; </pre> <p>ステータスコードが成功のレスポンスなので取れてるっぽいですね。</p> <p>保存してみましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">with</span> <span class="synIdentifier">open</span>(f<span class="synConstant">&quot;{書類のID}_python.zip&quot;</span>, <span class="synConstant">'wb'</span>) <span class="synStatement">as</span> f: f.write(response.content) </pre> <pre class="code" data-lang="" data-unlink>🐦.o0(たーみなる)% ls -lh S100HNA6_python.zip -rw-r--r-- 1 kazy staff 670K 10 11 13:32 S100HNA6_python.zip</pre> <p>取得できましたね。</p> <p>パチパチ👏</p> <p>................さて,</p> <p>ここまでネット上で説明されまくっている話をほぼそのまま説明しました。</p> <p>ここからは適当なワークをしてちっとは意味がある記事風に仕上げていきたいと思う。💪💪💪💪💪💪💪💪💪💪💪</p> <h2>(適当なワーク1)有価証券報告書が提出されるのが多いのは何月?</h2> <h3>結論</h3> <ul> <li><code>6月</code> <ul> <li>なんで?: <code>3月決算</code>の企業が多い + 有価証券報告書の提出は決算後<code>3ヶ月以内</code>と義務付けられている</li> </ul> </li> </ul> <h3>検証物語</h3> <p>企業によって有価証券報告書を提出する日付は様々です。</p> <p>提出が多い月, 少ない月などはあるのでしょうか?</p> <p>本日紹介したEDINET APIを使って集計してみます。</p> <p>〜〜〜以下に集計の際にKAZYの脳内で行われたやり取り〜〜〜</p> <p>KAZY&lt; 魔法使いさん, 集計して!!</p> <p>🧙‍♂️&lt; えいやっ! ぼんっ(魔法の音)</p> <p><img src="https://i.imgur.com/8kDx6Jo.jpg" alt="" /></p> <p>KAZY&lt; 1年分ないやんけ(2020年10月執筆時)</p> <p>🧙‍♂️&lt; 2019ね〜ん, えいやっ! ぼぼんっ(魔法の音)</p> <p><img src="https://i.imgur.com/fTT8HKr.jpg" alt="" /></p> <p>KAZY&lt; もう一声!!!</p> <p>🧙‍♂️&lt; 2018ね〜ん, えいやっ! ぼぼぼんっ(魔法の音)</p> <p><img src="https://i.imgur.com/5wN1lge.jpg" alt="" /></p> <p>KAZY&lt; 6月提出が圧倒的に多いな</p> <p>🧙‍♂️&lt; 決算月が3月の企業が多いからじゃな</p> <p>KAZY&lt; 3ヶ月ずれとるやんけ</p> <p>🤖 &lt; 有価証券報告書は各事業年度終了後、<code>3か月以内</code>の金融庁への提出が義務づけられているんやでぇ</p> <p>KAZY&lt; なるほど</p> <h2>(適当なワーク2)有価証券報告書が提出されるのが多いのはいつ?</h2> <h3>結論</h3> <ul> <li><code>金曜日</code> <ul> <li>(なんで?) しらん, 締切になりがちなんちゃう? <h3>検証物語</h3></li> </ul> </li> </ul> <p>以下に集計の際にKAZYの脳内で行われたやり取り。</p> <p>KAZY&lt; 魔法使いさん, こんどは曜日で集計して!!</p> <p>KAZY&lt; 3年分頼むわ!!!</p> <p>🧙‍♂️&lt; そいやっ! ぽんっぽんっぽんっ(魔法の音)</p> <p><img src="https://i.imgur.com/Kmgcesn.jpg" alt="" /> <img src="https://i.imgur.com/fBGhrxH.jpg" alt="" /> <img src="https://i.imgur.com/d4gqEZB.jpg" alt="" /></p> <p>KAZY&lt; 金曜日が多いな</p> <p>KAZY&lt; 締切に設定されがちなんやろな</p> <p>KAZY&lt; 土日に提出はしないんだな</p> <h2>(おまけ)期間を指定して有価証券報告書のXBRLファイルをダウンロードして展開してXBRLファイルのみ抽出するしてあとは削除しちゃうプログラムのスクリプト</h2> <p>どぞ。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2FKAZYPinkSaurus%2Fdisclosure-crowler" title="KAZYPinkSaurus/disclosure-crowler" 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://github.com/KAZYPinkSaurus/disclosure-crowler">github.com</a></cite></p> <p>↓使い方(再掲)↓</p> <h3>poetry使える人</h3> <pre class="code lang-sh" data-lang="sh" data-unlink>git clone https://github.com/KAZYPinkSaurus/disclosure-crowler.git <span class="synStatement">cd</span> disclosure-crowler <span class="synComment"># ライブラリのインストール</span> poetry install <span class="synComment">#オプションを表示</span> poetry run python <span class="synSpecial">-m</span> disclosure.main <span class="synSpecial">--help</span> <span class="synComment"># 2020/09/04から2020/10/04の有価証券報告書をダウンロードしてxbrlファイルを抽出</span> poetry run python <span class="synSpecial">-m</span> disclosure.main <span class="synSpecial">--from</span> 2020-09-04 <span class="synSpecial">--to</span> 2020-10-04 <span class="synSpecial">-x</span> <span class="synStatement">ls</span> output/* </pre> <h3>poetry使えない人</h3> <pre class="code lang-sh" data-lang="sh" data-unlink>git clone https://github.com/KAZYPinkSaurus/disclosure-crowler.git <span class="synStatement">cd</span> disclosure-crowler pip install <span class="synIdentifier">requests</span>=<span class="synStatement">=</span><span class="synConstant">2</span>.<span class="synConstant">24</span>.<span class="synConstant">0</span> python-dateutil<span class="synStatement">==</span><span class="synConstant">2</span>.<span class="synConstant">8</span>.<span class="synConstant">1</span> <span class="synIdentifier">loguru</span>=<span class="synStatement">=</span><span class="synConstant">0</span>.<span class="synConstant">5</span>.<span class="synConstant">3</span> <span class="synIdentifier">click</span>=<span class="synStatement">=</span><span class="synConstant">7</span>.<span class="synConstant">1</span>.<span class="synConstant">2</span> <span class="synComment">#オプションを表示</span> poetry run python <span class="synSpecial">-m</span> disclosure.main <span class="synSpecial">--help</span> <span class="synComment"># 2020/09/04から2020/10/04の有価証券報告書をダウンロードしてxbrlファイルを抽出</span> python <span class="synSpecial">-m</span> disclosure.main <span class="synSpecial">--from</span> 2020-09-04 <span class="synSpecial">--to</span> 2020-10-04 <span class="synSpecial">-x</span> </pre> <h2>おわりに</h2> <p>EDINET APIを使って簡単にXBRLファイルをダウンロードできましたね。 XBRLファイルをダウンロードしたら次にやりたいのが構文解析してテキストマイニングじゃあありませんか? けど...難しいんでしょ? って方は<a href="https://blog.hoxo-m.com/entry/2020/10/07/090258">こちら</a>を読んでみるといいかもしれませんよ。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.hoxo-m.com%2Fentry%2F2020%2F10%2F07%2F090258" title="有価証券報告テキストマイニング入門 - 株式会社ホクソエムのブログ" 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://blog.hoxo-m.com/entry/2020/10/07/090258">blog.hoxo-m.com</a></cite></p> <p>それでは。</p> <h2>Appendix</h2> <h3>書類一覧APIで変えたくなるパラメータ</h3> <table> <thead> <tr> <th>パラメータ名</th> <th> 値 </th> <th> 説明 </th> </tr> </thead> <tbody> <tr> <td>date</td> <td>YYYY-MM-DD</td> <td> ファイル日付を指定 </td> </tr> <tr> <td>type</td> <td>1</td> <td>メタデータのみを取得(typeが指定ないとこちらになる)</td> </tr> <tr> <td>type</td> <td>2</td> <td>提出書類一覧及びメタデータを取得</td> </tr> </tbody> </table> <h3>書類取得APIで変えたくなるパラメータ</h3> <table> <thead> <tr> <th> パラメータ名 </th> <th> 値 </th> <th> 説明 </th> <th> ファイル形式 </th> </tr> </thead> <tbody> <tr> <td> type </td> <td> <strong>1</strong> </td> <td> <strong>提出本文書及び監査報告書を取得</strong> </td> <td> <strong>ZIP</strong> </td> </tr> <tr> <td> type </td> <td> 2 </td> <td> PDFファイルを取得 </td> <td> PDF </td> </tr> <tr> <td> type </td> <td> 3 </td> <td> 代替書面・添付文書を取得 </td> <td> ZIP </td> </tr> <tr> <td> type </td> <td> 4 </td> <td> 英文ファイルを取得 </td> <td> ZIP </td> </tr> </tbody> </table> <h2>参考</h2> <ul> <li><a href="https://disclosure.edinet-fsa.go.jp/download/ESE140191.pdf">EDINET API機能利用規約</a></li> <li><a href="https://disclosure.edinet-fsa.go.jp/EKW0EZ0015.html">API関連資料</a>(EDINET API仕様書や,サンプルプログラムが配布されています)</li> </ul> <div class="footnote"> <p class="footnote"><a href="#fn-4d449fc8" name="f-4d449fc8" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">ブラウザからダウンロードも同様</span></p> <p class="footnote"><a href="#fn-3dd698b5" name="f-3dd698b5" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">ちなみに, typeを2にすると有価証券報告書のpdfファイルがダウンロードできます。 他はAppendixを参照してみてください。</span></p> </div> KAZYPinkSaurus 有価証券報告テキストマイニング入門 hatenablog://entry/26006613635819195 2020-10-07T09:02:58+09:00 2020-10-07T09:02:58+09:00 はじめに こんにちは, ホクソエムサポーターのKAZYです。 先日猫カフェデビューをして, 猫アレルギーであることがわかりました🐈。 次はフクロウカフェに挑戦してみようかなと思っています🦉。 ところで皆様, 有価証券報告書は読んでますか? 私は読んでいません。 読めません。 眺めていると眠くなります💤。 私は眠くなるんですが, 有価証券報告書ってテキストマイニングするのに向いているんです。企業の事業や財務情報が詳細に書かれています。 XBRL形式で構造化されています。 数千社分のテキストが手に入ります。 おまけに無料です。 どうです?興味湧いてきませんか? 本記事ではPythonを使って有価証… <h2>はじめに</h2> <p>こんにちは, ホクソエムサポーターのKAZYです。 先日猫カフェデビューをして, 猫アレルギーであることがわかりました🐈。 次はフクロウカフェに挑戦してみようかなと思っています🦉。</p> <p>ところで皆様, 有価証券報告書は読んでますか? 私は読んでいません。 読めません。 眺めていると眠くなります💤。</p> <p>私は眠くなるんですが, 有価証券報告書ってテキストマイニングするのに向いているんです。企業の事業や財務情報が詳細に書かれています。 XBRL形式で構造化されています。 数千社分のテキストが手に入ります。 おまけに無料です。</p> <p>どうです?興味湧いてきませんか?</p> <p>本記事ではPythonを使って有価証券報告書をテキストマイニングする方法を紹介します。 有価証券報告書をダウンロードするところからご紹介するのでご安心を。</p> <h2>こんな方が見たら役に立つかも</h2> <ul> <li>企業分析をプログラミングでやりたいが何していいか何もわからん方</li> <li>プログラミングできるけど有価証券報告書何もわからん方</li> <li>自然言語処理をするのに丁度いいテキストを探している方</li> </ul> <h2>やること</h2> <ul> <li>有価証券報告書についての説明</li> <li>有価証券報告書のダウンロード方法紹介</li> <li>XBRLファイルについての説明</li> <li>Pythonで有価証券報告書からテキスト抽出</li> <li>抽出したテキストから簡単なテキストマイニング</li> </ul> <h2>有価証券報告書とは</h2> <p>有価証券報告書, 通称「有報」👽🛸は<code>企業の状況を外部へ開示するための資料</code>です。 企業が自ら毎年作成しています。 企業分析をする際などによく利用されます。</p> <h2>提出企業</h2> <p>日本にうん100万社ある企業すべてが有報は提出をしている訳ではありません。</p> <p>以下の条件を満たす企業だけが各事業年度毎に国へ提出を義務付けられています。</p> <ul> <li><code>金融商品取引所(証券取引所)に株式公開している会社</code></li> <li>店頭登録している株式の発行会社</li> <li>有価証券届出書提出会社</li> <li>過去5年間において、事業年度末日時点の株券もしくは優先出資証券の保有者数が1000人以上となったことがある会社(ただし、資本金5億円未満の会社を除く)</li> </ul> <p>雰囲気としては<code>上場企業+α</code> が提出している感じです。 <a href="#f-1216715e" name="fn-1216715e" title="ちなみに日本の上場企業数は4000社程度です">*1</a></p> <h2>記載内容</h2> <p>企業の沿革, 事業内容, 財務状況, 研究開発状況, 抱える課題, 経営方針, キャッシュフロー, 役員状況... などの情報がとても詳細に記述されています。</p> <h2>情報量</h2> <p>PDFで<code>100ページを越える</code>ような報告書が多いです。 例えば日本マクドナルドホールディングス株式会社の第49期の<a href="https://ircms.irstreet.com/contents/data_file.php?template=1546&amp;brand=74&amp;data=270897&amp;filename=pdf_file.pdf">有価証券報告</a>は<code>94ページ</code>です。 (100ページ越えてない...)</p> <h2>取得方法</h2> <p>有価証券報告の取得方法は大きく分けて以下の2つあります。</p> <ol> <li>ブラウザからダウンロード</li> <li>APIからダウンロード</li> </ol> <p>今回は1.の<code>ブラウザからダウンロード</code>する方法を紹介します。</p> <p>有価証券報告書は金融庁が運営する<a href="https://disclosure.edinet-fsa.go.jp/">EDINET</a>と呼ばれるWEBサイトから自由にダウンロードができます。</p> <p>皆さんご存知の日本を代表する企業, 株式会社ホクソエムの有価証券を検索してダウンロードしてみます。</p> <p><img src="https://i.imgur.com/fu23smF.png" alt="" /></p> <blockquote><p><code>該当するデータが存在しません。</code></p></blockquote> <p>と出てしまいました。</p> <p>株式会社ホクソエムは<a href="https://hackmd.io/pb-9ZvanQWqNJp3ey88Zcw#%E6%8F%90%E5%87%BA%E4%BC%81%E6%A5%AD">有報の提出が義務付けられている会社</a>ではありませんでした。</p> <p>つぎに私が学生時代にお世話になった<code>日本マクドナルドホールディングス株式会社</code>を検索してみます🍔。<br> 4件ヒットしました。 そのなかに平成27年~令和2年の有価証券報告書がありますね。 <a href="#f-8fb71543" name="fn-8fb71543" title="ちなみに有価証券報告書の電子提出が義務付けられたのは2004年6月からですがEDINETでは直近5年間の有価証券報告書しかダウンロードできません。 全人類が思いつくであろうある企業の過去15年分の有価証券報告書を使って分析というのができないのは少し残念です。 実はこんなサイトには直近5年以上前の情報もあったりします。">*2</a></p> <p><img src="https://i.imgur.com/5t1iY0N.png" alt="" /></p> <p>XBRLと書かれたアイコンをクリックして見ましょう。 zipで圧縮されたファイルがダウンロードできます(PDFをクリックするとpdfの書類がダウンロードできます)。</p> <h2>ダウンロードしたzipファイルの中身</h2> <p>ダウンロードしたデータを展開すると以下のようなファイルが入っています。</p> <pre class="code" data-lang="" data-unlink>Xbrl_Search_20200909_221659 ├── S100ICBO │   └── XBRL │   ├── AuditDoc │   │   ├── jpaud-aai-cc-001_E03366-000_2019-12-31_01_2020-03-30.xbrl │   │   ├── jpaud-aai-cc-001_E03366-000_2019-12-31_01_2020-03-30.xsd │   │   ├── jpaud-aai-cc-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm │   │   ├── jpaud-aai-cc-001_E03366-000_2019-12-31_01_2020-03-30_pre.xml │   │   ├── jpaud-aar-cn-001_E03366-000_2019-12-31_01_2020-03-30.xbrl │   │   ├── jpaud-aar-cn-001_E03366-000_2019-12-31_01_2020-03-30.xsd │   │   ├── jpaud-aar-cn-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm │   │   ├── jpaud-aar-cn-001_E03366-000_2019-12-31_01_2020-03-30_pre.xml │   │   └── manifest_AuditDoc.xml │   └── PublicDoc │   ├── 0000000_header_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm │   ├── 0101010_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm │   ├── 0102010_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm │   ├── 0103010_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm │   ├── 0104010_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm │   ├── 0105010_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm │   ├── 0105020_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm │   ├── 0106010_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm │   ├── 0107010_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm │   ├── 0200010_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm │   ├── images │   │   ├── 0101010_001.png │   │   ├── 0101010_002.png │   │   └── 0104010_001.png │   ├── jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30.xbrl ←これ │   ├── jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30.xsd │   ├── jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_cal.xml │   ├── jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_def.xml │   ├── jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_lab-en.xml │   ├── jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_lab.xml │   ├── jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_pre.xml │   └── manifest_PublicDoc.xml └── XbrlSearchDlInfo.csv</pre> <p>XML, HTML, CSV, XBRLファイルが入っていますね。</p> <p>今回テキストマイニングに使用するのは<code>xbrl</code>の拡張子がついたファイルです。</p> <p>XBRLファイルは<code>AuditDoc</code>,<code>PublicDoc</code>の2つのディレクトリ内に入っています。 それぞれ有価証券報告書内の<code>監査に関する報告</code>, <code>それ以外の報告</code>が記載されています。</p> <h2>XBRLファイルとは</h2> <p>XBRLファイルについて, テキストマイニングに必要最低限の説明します。 XBRLファイルとは<code>XMLをベース</code> に事業報告用に拡張したマークアップ言語です。<a href="#f-65d9c3c2" name="fn-65d9c3c2" title="そもそもXMLを知らない方はこことかを読んで見るといいかもしれません。">*3</a></p> <p>実際にXBRLファイルを覗いてみると,</p> <pre class="code lang-xml" data-lang="xml" data-unlink><span class="synComment">&lt;?</span><span class="synType">xml version</span>=<span class="synConstant">&quot;1.0&quot;</span><span class="synType"> encoding</span>=<span class="synConstant">&quot;UTF-8&quot;</span><span class="synComment">?&gt;</span> <span class="synIdentifier">&lt;</span><span class="synSpecial">xbrli</span><span class="synComment">:</span><span class="synIdentifier">xbrl&gt;</span> ︙~中略~ <span class="synIdentifier">&lt;/</span><span class="synSpecial">xbrli</span><span class="synComment">:</span><span class="synIdentifier">xbrl&gt;</span> </pre> <p>と記述されています。 一行目に思いっきりxmlと書いてあることが確認できると思います。 そういうことです。</p> <p>また, 有報に記述する基本的な名前空間とタグは定義されており, <a href="https://www.fsa.go.jp/search/20191101.html">タクソノミ要素リスト</a>で確認できます(リンクは2020年版)。</p> <p>タグが統一されているので様々な企業が提出するXBRLファイルを一度にプログラムで処理しやすいのです。 素敵です。</p> <p>具体例として, 有報表紙の企業名部分の名前空間とタグはそれぞれ<code>jpcrp_cor</code>,<code>CompanyNameCoverPage</code>で書きましょうと決められております。</p> <p>実際にマクドナルドとモスバーガーのXBRLファイル内の対象部分を見ると,</p> <pre class="code lang-xml" data-lang="xml" data-unlink><span class="synIdentifier">&lt;</span><span class="synSpecial">jpcrp_cor</span><span class="synComment">:</span><span class="synIdentifier">CompanyNameCoverPage </span><span class="synType">contextRef</span>=<span class="synConstant">&quot;FilingDateInstant&quot;</span><span class="synIdentifier">&gt;</span>日本マクドナルドホールディングス株式会社<span class="synIdentifier">&lt;/</span><span class="synSpecial">jpcrp_cor</span><span class="synComment">:</span><span class="synIdentifier">CompanyNameCoverPage&gt;</span> <span class="synIdentifier">&lt;</span><span class="synSpecial">jpcrp_cor</span><span class="synComment">:</span><span class="synIdentifier">CompanyNameCoverPage </span><span class="synType">contextRef</span>=<span class="synConstant">&quot;FilingDateInstant&quot;</span><span class="synIdentifier">&gt;</span>株式会社モスフードサービス<span class="synIdentifier">&lt;/</span><span class="synSpecial">jpcrp_cor</span><span class="synComment">:</span><span class="synIdentifier">CompanyNameCoverPage&gt;</span> </pre> <p>とどちらも同じタグの中に会社名が囲まれていますね。</p> <p>これでどんな情報がどんなタグで表現されているかがわかるので早速プログラムを書いてテキスト抽出していきましょう。</p> <p>XBRLファイルについてもっと詳しく知りたいという方用にオススメ記事リンク貼っておくので読んでみてください。</p> <p>👇👇👇👇👇👇👇👇👇👇👇👇</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnote.com%2Fpiqcy%2Fn%2Fnf66dbe290ada" title="財務分析に欠かせない、XBRLの構造を理解する|piqcy|note" 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://note.com/piqcy/n/nf66dbe290ada">note.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnote.com%2Fpiqcy%2Fn%2Fn08f417c386cd" title="財務分析に欠かせない、XBRLを読む: インスタンス文書編|piqcy|note" 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://note.com/piqcy/n/n08f417c386cd">note.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnote.com%2Fpiqcy%2Fn%2Fn8dd5178852de" title="財務分析に欠かせない、XBRLを読む: タクソノミ文書編|piqcy|note" 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://note.com/piqcy/n/n8dd5178852de">note.com</a></cite></p> <h2>Pythonを使って任意の項目を抽出する</h2> <p>次に, Pythonを用いてXBRLファイルから任意の項目のテキストを抽出します。 今回は<a href="https://lxml.de/">lxml</a>というライブラリを使います。 <a href="#f-98bc8b1a" name="fn-98bc8b1a" title="標準ライブラリに入っているxmlではなくlxmlを使用した理由は名前空間のURIマップをライブラリで取得することができるからです。">*4</a></p> <h3>【会社名】項目の抽出</h3> <p><img src="https://i.imgur.com/ufyt6FS.png" alt="" /></p> <p>会社名をXBRLファイルから抽出してみます。 会社名は有価証券報告書の表紙に書いてあります。 以下のスクリプトで抽出きます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> lxml <span class="synPreProc">import</span> etree mcdonalds_xbrl = <span class="synConstant">&quot;./Xbrl_Search_20200914_000607/S100IW0P/XBRL/PublicDoc/jpcrp030000-asr-001_E02675-000_2020-03-31_01_2020-06-25.xbrl&quot;</span> <span class="synComment"># xbrlファイルを指定してパース</span> tree = etree.parse(mosuburger_xbrl) root = tree.getroot() <span class="synComment"># 名前空間とタグを指定して検索</span> company_name = root.find(<span class="synConstant">&quot;jpcrp_cor:CompanyNameCoverPage&quot;</span>,root.nsmap).text &gt;&gt; 日本マクドナルドホールディングス株式会社 </pre> <p>簡単ですね。 XBRLファイルをモスフードサービスのものに変更すると,</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> lxml <span class="synPreProc">import</span> etree mosuburger_xbrl = <span class="synConstant">&quot;./Xbrl_Search_20200914_000607/S100IW0P/XBRL/PublicDoc/jpcrp030000-asr-001_E02675-000_2020-03-31_01_2020-06-25.xbrl&quot;</span> <span class="synComment"># xbrlファイルを指定してパース</span> tree = etree.parse(mosuburger_xbrl) root = tree.getroot() <span class="synComment"># 名前空間とタグを指定して検索</span> company_name = root.find(<span class="synConstant">&quot;jpcrp_cor:CompanyNameCoverPage&quot;</span>,root.nsmap).text &gt;&gt; 株式会社モスフードサービス </pre> <p>同じコードで会社名が取り出せました。</p> <h3>【事業の内容】項目を抽出</h3> <p><img src="https://i.imgur.com/ezFC2dO.png" alt="" /> 次に事業の内容が記載された項目の抽出を行ってみます(上図)。</p> <p><a href="https://www.fsa.go.jp/search/20191101.html">タクソノミ要素リスト</a>から事業の内容のタグを調べます。<code>jpcrp_cor:DescriptionOfBusinessTextBlock</code>のタグでできると書いてあります。 検索するタグを変更してスクリプトを実行します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> lxml <span class="synPreProc">import</span> etree mcdonalds_xbrl = <span class="synConstant">&quot;./Xbrl_Search_20200914_000607/S100IW0P/XBRL/PublicDoc/jpcrp030000-asr-001_E02675-000_2020-03-31_01_2020-06-25.xbrl&quot;</span> <span class="synComment"># xbrlファイルを指定してパース</span> tree = etree.parse(mosuburger_xbrl) root = tree.getroot() <span class="synComment"># 名前空間とタグを指定して検索</span> bussiness_block = root.find(<span class="synConstant">&quot;jpcrp_cor:DescriptionOfBusinessTextBlock&quot;</span>,root.nsmap).text &gt;&gt; <span class="synConstant">'</span><span class="synSpecial">\n</span><span class="synConstant">&lt;h3&gt;3【事業の内容】&lt;/h3&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;p style=&quot;margin-left: 24px; text-align: left&quot;&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;span style=&quot;font-family: &amp;apos;MS Mincho&amp;apos;; font-size: 12px&quot;&gt;</span><span class="synSpecial">\u3000</span><span class="synConstant">当社グループの事業はハンバーガーレストラン事業単一であるため、セグメント情報に関連付けた記載を行っていません。&lt;/span&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;/p&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;p style=&quot;margin-left: 24px; text-align: left&quot;&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;span style=&quot;font-family: &amp;apos;MS Mincho&amp;apos;; font-size: 12px&quot;&gt;(当社の事業内容)&lt;/span&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;/p&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;p style=&quot;margin-left: 24px; text-align: left&quot;&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;span style=&quot;font-family: &amp;apos;MS Mincho&amp;apos;; font-size: 12px&quot;&gt;</span><span class="synSpecial">\u3000</span><span class="synConstant">当社は、日本マクドナルド株式会社の持株会社として、グループ企業の連結経営戦略の策定業務と実行業務及び不動産賃貸業務を主たる事業としております。&lt;/span&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;/p&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;p style=&quot;margin-left: 24px; text-align: left&quot;&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;span style=&quot;font-family: &amp;apos;MS Mincho&amp;apos;; font-size: 12px&quot;&gt;</span><span class="synSpecial">\u3000</span><span class="synConstant">なお、当社は特定上場会社等であります。特定上場会社等に該当することにより、インサイダー取引規制の重要事実の軽微基準については連結ベースの数値に基づいて判断することとなります。&lt;/span&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;/p&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;p style=&quot;margin-left: 24px; text-align: left&quot;&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;span style=&quot;font-family: &amp;apos;MS Mincho&amp;apos;; font-size: 12px&quot;&gt;(関係会社の事業内容)&lt;/span&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;/p&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;p style=&quot;margin-left: 24px; text-align: left&quot;&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;span style=&quot;font-family: &amp;apos;MS Mincho&amp;apos;; font-size: 12px&quot;&gt;</span><span class="synSpecial">\u3000</span><span class="synConstant">日本マクドナルド株式会社(当社出資比率100%)は、直営店方式による店舗運営とともにフランチャイズ方式による店舗展開を通じハンバーガーレストラン事業を展開しております。同社は、米国マクドナルド・コーポレーションから許諾されるライセンスに対するロイヤルティーを支払っております。日本国内においては、フランチャイズ店舗を経営するフランチャイジーに対してノウハウ及び商標等のサブ・ライセンスを許諾し、フランチャイジーからロイヤルティーを収受しております。&lt;/span&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;/p&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;p style=&quot;margin-left: 24px; text-align: left&quot;&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;span style=&quot;font-family: &amp;apos;MS Mincho&amp;apos;; font-size: 12px; font-weight: normal&quot;&gt;</span><span class="synSpecial">\u3000</span><span class="synConstant">当社と関係会社との当連結会計年度における資本関係及び取引関係の概要は、以下のとおりであります。&lt;/span&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;/p&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;p style=&quot;margin-left: 24px; text-align: left&quot;&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;span style=&quot;font-family: &amp;apos;MS Mincho&amp;apos;; font-size: 12px&quot;&gt;[事業系統図]&lt;/span&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;/p&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;p style=&quot;text-align: center&quot;&gt;</span><span class="synSpecial">\xa0</span><span class="synConstant">&lt;/p&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;p style=&quot;text-align: left&quot;&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;img style=&quot;height: 441px; width: 566.195739746094px&quot; src=&quot;images/0101010_002.png&quot; alt=&quot;0101010_002.png&quot;/&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;/p&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;p style=&quot;text-align: left&quot;&gt;</span><span class="synSpecial">\xa0</span><span class="synConstant">&lt;/p&gt;</span><span class="synSpecial">\n</span><span class="synConstant">&lt;p style=&quot;text-align: left&quot;&gt;</span><span class="synSpecial">\xa0</span><span class="synConstant">&lt;/p&gt;</span><span class="synSpecial">\n</span><span class="synConstant">'</span> </pre> <p>html形式の文字列が抽出されました。 XBRLの各タグの要素をhtml形式で表現しているものが多く存在します。 表形式の情報などはhtml形式としてパースを行って目的の情報を抽出できます。 今回はhtmlタグを取り除く処理を行います(ついでに改行コードも除く)。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># 正規表現操作のライブラリ</span> <span class="synPreProc">import</span> re <span class="synComment"># 改行や空白文字を削除する</span> bussiness_block = re.(<span class="synConstant">'\s'</span>,<span class="synConstant">''</span>,bussiness_block) <span class="synComment"># htmlタグを削除する</span> bussiness_block = re.(<span class="synConstant">'&lt;.*?&gt;'</span>,<span class="synConstant">''</span>,bussiness_block) &gt;&gt;<span class="synConstant">'3【事業の内容】当社グループの事業はハンバーガーレストラン事業単一であるため、セグメント情報に関連付けた記載を行っていません。(当社の事業内容)当社は、日本マクドナルド株式会社の持株会社として、グループ企業の連結経営戦略の策定業務と実行業務及び不動産賃貸業務を主たる事業としております。なお、当社は特定上場会社等であります。特定上場会社等に該当することにより、インサイダー取引規制の重要事実の軽微基準については連結ベースの数値に基づいて判断することとなります。(関係会社の事業内容)日本マクドナルド株式会社(当社出資比率100%)は、直営店方式による店舗運営とともにフランチャイズ方式による店舗展開を通じハンバーガーレストラン事業を展開しております。同社は、米国マクドナルド・コーポレーションから許諾されるライセンスに対するロイヤルティーを支払っております。日本国内においては、フランチャイズ店舗を経営するフランチャイジーに対してノウハウ及び商標等のサブ・ライセンスを許諾し、フランチャイジーからロイヤルティーを収受しております。当社と関係会社との当連結会計年度における資本関係及び取引関係の概要は、以下のとおりであります。[事業系統図]'</span> </pre> <p>事業の内容項目をきれいに抽出することができました。 ファイルをモスフードサービスに変更しても,</p> <pre class="code" data-lang="" data-unlink>&#34;3【事業の内容】当社グループは、㈱モスフードサービス(当社)及び子会社12社、関連会社14社により構成されており、主にフランチャイズシステムによる飲食店の展開を事業としております。事業は大きく「モスバーガー」等の商標を使用した飲食店を展開する「モスバーガー事業」、「マザーリーフ」「MOSDO」「ミアクッチーナ」「あえん」「chef&#39;sV」「GREENGRILL」等の商標を使用した飲食店を展開する「その他飲食事業」、これらの飲食事業を衛生、金融、保険等で支援する「その他の事業」に分けることができます。事業内容と当社及び関係会社等の当該事業における位置付け及びセグメントとの関連は、次のとおりであります。セグメントの名称主要製品主要な会社モスバーガー事業「モスバーガー」等の運営ハンバーガー、ライスバーガー、モスチキン、スープ、ドリンク等及びパティ、バンズ、ポテト等の食材並びにカップ、パッケージ等の包装資材[国内]㈱モスフードサービス㈱モスストアカンパニー[台湾]安心食品服務(股)[シンガポール]モスフード・シンガポール社安心フードサービスシンガポール社[香港]モスフード香港社香港モスバーガーインベストメント社[中国]広東摩斯貝格餐飲管理有限公司[タイ]モスバーガー・タイランド社[オーストラリア]モスバーガー・オーストラリア社[インドネシア]モグインドネシア社[韓国]モスバーガーコリア社[フィリピン]モスバーガー・フィリピン社食品製造、食材販売事業パティ、ソース類等[国内]紅梅食品工業㈱タミー食品工業㈱[台湾]魔術食品工業(股)[フィリピン]モスサプライ・フィリピン社アグリ事業トマト、レタス等[国内]㈱モスファーム熊本㈱モス・サンファームむかわ㈱モスファームすずなり㈱モスファームマルミツ㈱モスファーム信州㈱モスファーム千葉その他飲食事業喫茶紅茶、ワッフル、パスタ、スイーツ等[国内]㈱モスフードサービス㈱モスストアカンパニーレストラン和風旬菜料理、洋風旬菜料理等[国内]㈱モスフードサービス㈱モスダイニングその他の事業食品衛生検査業ハンバーガー等の衛生検査、衛生関連商品の販売[国内]㈱エム・エイチ・エス金銭貸付業フランチャイジー(加盟店)への事業資金貸付[国内]㈱モスクレジット保険代理業生命保険、損害保険[国内]㈱モスクレジットレンタル業POSレジスター、看板等[国内]㈱モスクレジットグループ内アウトソーシング事業グループ内アウトソーシング事業[国内]㈱モスシャイン以上の企業集団等について事業系統図を図示すると次のとおりであります。(注)海外における事業は「モスバーガー事業」であります。子会社及び関連会社の連結の範囲は、次のとおりであります。子会社関連会社㈱エム・エイチ・エス※紅梅食品工業㈱㈱モスクレジット※タミー食品工業㈱㈱モスストアカンパニー※安心食品服務(股)㈱モスダイニング※モスバーガー・オーストラリア社㈱モスシャイン※モスバーガーコリア社モスフード・シンガポール社※モスバーガー・タイランド社魔術食品工業(股)※モスバーガー・フィリピン社モスフード香港社㈱モスファーム熊本モスサプライ・フィリピン社㈱モス・サンファームむかわ※(モグインドネシア社)㈱モスファームすずなり※(香港モスバーガーインベストメント社)㈱モスファームマルミツ※(広東摩斯貝格餐飲管理有限公司)㈱モスファーム信州㈱モスファーム千葉安心フードサービスシンガポール社計12社計14社(注)1.()内は非連結子会社であります。2.※印は持分法適用会社であります。&#34;</pre> <p>この通りきれいに内容を抽出することができました。</p> <p>これでテキストの抽出ができるようになりました。</p> <h3>企業の課題を用いてワードクラウドを作ってみる</h3> <p>簡単なテキストマイニングをやってみます。 異なる2つの業界の企業から有価証券報告書の<code>【経営方針、経営環境及び対処すべき課題等】</code>の項目を抽出して頻出ワードを抽出してワードクラウドを作ります。</p> <p>各業界のおかれている状況の違いみたいなのが見えたらいいなぁというお気持ちです。</p> <p>まず,</p> <ul> <li>SaaS企業売上の高そうな<a href="https://note.com/_funeo/n/n641b21acae76">10社</a></li> <li>“ゴルフクラブ”と名のついた企業10社</li> </ul> <p>の有価証券報告書を用意します。</p> <p>【経営方針、経営環境及び対処すべき課題等】項目のテキストを抽出します。</p> <pre class="code" data-lang="" data-unlink>1文中の将来に関する事項は、当事業年度末現在において当社が判断したものであります。(1)経営方針①ゴルフ場は会員様(株主)の財産であるとの意識を高く持ち、そのハード・…略</pre> <p>形態素解析して名詞だけ抽出します(<a href="https://pypi.org/project/mecab-python3/">mecab-python3</a>を使用)。</p> <pre class="code" data-lang="" data-unlink>1文中 将来 事項 事業年度末現在 当社 判断 もの * 経営方針*ゴルフ場 会員様 株主 財産 意識 ハード …略</pre> <p>そこからを用いてワードクラウドを作ります(<a href="https://pypi.org/project/wordcloud/">wordcloud</a>を使用)。</p> <p><strong>SaaS企業</strong> <img src="https://i.imgur.com/AapTxyc.png" alt="" /></p> <p><strong>ゴルフ場経営企業</strong> <img src="https://i.imgur.com/jPTTIvk.png" alt="" /></p> <p>ワードクラウドが作成できました。 売上のあるSaaS経営企業とゴルフ場経営企業では頻出するワードの雰囲気がぜんぜん違いますね。</p> <p>名詞ではなくて形容詞を用いて同様にワードクラウドを作ってみました。</p> <p><strong>SaaS企業</strong> <img src="https://i.imgur.com/Ml126Va.png" alt="" /></p> <p><strong>ゴルフ場経営企業</strong> <img src="https://i.imgur.com/3foJN5q.png" alt="" /></p> <p>ところゴルフ場経営企業の厳しさが伝わってきました。</p> <p>👇ソースコードはこちら👇 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2FKAZYPinkSaurus%2Fpython-study60" title="KAZYPinkSaurus/python-study60" 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://github.com/KAZYPinkSaurus/python-study60">github.com</a></cite></p> <h1>おわりに</h1> <p>今回は有価証券報告書からテキストを抽出して単なテキストマイニングを紹介しました。 次のステップとして有価証券報告書から財務情報を抽出がオススメです。 html形式から情報抽出する技術が身につけられます。</p> <div class="footnote"> <p class="footnote"><a href="#fn-1216715e" name="f-1216715e" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">ちなみに日本の上場企業数は4000社程度です</span></p> <p class="footnote"><a href="#fn-8fb71543" name="f-8fb71543" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">ちなみに有価証券報告書の電子提出が義務付けられたのは2004年6月からですがEDINETでは直近5年間の有価証券報告書しかダウンロードできません。 全人類が思いつくであろうある企業の過去15年分の有価証券報告書を使って分析というのができないのは少し残念です。 実は<a href="https://ufocatch.com/">こんな</a>サイトには直近5年以上前の情報もあったりします。</span></p> <p class="footnote"><a href="#fn-65d9c3c2" name="f-65d9c3c2" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">そもそもXMLを知らない方は<a href="https://www.atmarkit.co.jp/ait/articles/0005/22/news009.html">ここ</a>とかを読んで見るといいかもしれません。</span></p> <p class="footnote"><a href="#fn-98bc8b1a" name="f-98bc8b1a" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">標準ライブラリに入っている<a href="https://docs.python.org/ja/3/library/xml.etree.elementtree.html#module-xml.etree.ElementTree">xml</a>ではなくlxmlを使用した理由は名前空間のURIマップをライブラリで取得することができるからです。</span></p> </div> KAZYPinkSaurus MLflowのXGBoost拡張を読んでみる hatenablog://entry/26006613615689854 2020-08-20T10:00:26+09:00 2020-08-20T10:00:26+09:00 はじめに ホクソエムサポーターの藤岡です。会社を移りましたが、相変わらずPythonを書く仕事をしています。 前回の記事に引き続き、今回もMLflowについての記事です。 前回はトラッキング寄りでしたが、今回はモデルのデプロイにも関わってくる内容です。 MLflowはXGBoost, PySpark, scikit-learnといった多様なライブラリに対応していて、様々な機械学習タスクに活用することができるのが売りの一つです。 その実現のため、設計や実装に様々な工夫がされているのですが、 この部分について詳しくなることで、オリジナルの機械学習モデルをMLflowとうまく繋ぐことができるようにな… <h1>はじめに</h1> <p>ホクソエムサポーターの藤岡です。会社を移りましたが、相変わらずPythonを書く仕事をしています。</p> <p><a href="https://blog.hoxo-m.com/entry/mlflow_store">前回の記事</a>に引き続き、今回もMLflowについての記事です。 前回はトラッキング寄りでしたが、今回はモデルのデプロイにも関わってくる内容です。</p> <p>MLflowはXGBoost, PySpark, scikit-learnといった多様なライブラリに対応していて、様々な機械学習タスクに活用することができるのが売りの一つです。 その実現のため、設計や実装に様々な工夫がされているのですが、 この部分について詳しくなることで、オリジナルの機械学習モデルをMLflowとうまく繋ぐことができるようになったり ETLのようなモデル学習にとどまらない使い方もできるようになったりします。</p> <p>本記事では、XGBoostをMLflowで扱うためのモジュール mlflow.xgboost について解説することで、拡張性の鍵であるflavorについて紹介します。 また、その前提知識として、MLflow Model, PyFunc Modelについても関連する部分をさらっと解説します。</p> <p>機械学習が完全に絡まない部分で使う必要があるのかはさておき、 機械学習が絡んでいるフローを広く一つのフレームワークで管理できるようにしておくのは有用だと思います。 実際、MLflowのサンプルコードにも<a href="https://github.com/mlflow/mlflow/tree/v1.10.0/examples/multistep_workflow">ETLを含めて管理している例</a>があります<a href="#f-65886ff7" name="fn-65886ff7" title="この例では、今回の記事の内容は不要です">*1</a>。</p> <p>本記事を通してMLflowについて理解を深め、活用の幅を広げましょう!</p> <p>なお、本記事はMLflowの使用方法と前回記事の内容 (のうちMLmodel周り) が前提知識となります。 また、MLflow v1.10.0 時点での内容です。</p> <h1>MLflow Model</h1> <p>今日では、日々新しいMLモデルが提案され、それらを実装したパッケージも次々と生まれています。 それらはsklearnの提供する形式に従っているものから、 xgboostのようなデータ構造まで自前で用意してパッケージングしたものまで様々であり、 統一的な形式のようなものはありません。</p> <p>MLflowは抽象的なモデルフォーマットを定義し、それを通じてこれらのフレームワークやパッケージを統一的なインターフェースで扱います。 このモデルフォーマットは MLflow Model と呼ばれます。 モデルとは言っても機械学習モデルというよりそれにまつわる種々の内容を定めたものです。 以下の例をはじめとする種々の項目についてそれぞれ形式を定めています。</p> <ul> <li>ストレージ</li> <li>モデルの入出力形式(Signature)</li> <li>モデルのAPI (セーブ、ロード、ロギング等)</li> </ul> <p>このフォーマットは<a href="https://github.com/mlflow/mlflow/blob/v1.10.0/mlflow/models">mlflow.models</a>以下で実装されています。 例えば、本体となるクラスはmlflow.models.model.pyの中に<code>Model()</code>クラスとして定義されています。 ちなみに、このクラスがどんなメソッドやパラメータを持っているか一通り目を通しておくと、MLflow Modelについて少しイメージがしやすくなります。</p> <h1>flavor</h1> <p>MLflow Modelは抽象度が高く、それ単体では使うことができません。 実際、<a href="https://github.com/mlflow/mlflow/blob/v1.10.0/mlflow/models/flavor_backend.py">モデルのデプロイ部分の実装</a>を見てみると、 入力とメソッド名のみを定義している以外はすっからかんとなっています。</p> <p>そのため、MLflow Modelは既存の機械学習フレームワーク等のモジュールやスクリプトがこのフォーマットに従うようにするためのインターフェースを必要とします。 これを、flavorと呼びます。 例えば、<code>xgboost.Booster</code> (xgboostのモデルクラス) をMLflowで扱う場合、<code>Booster</code>に特定のメソッドを生やすためのラッパなどをflavorとして実装すれば、 MLflowの学習トラッキングやモデルデプロイなどの機能を<code>xgboost.Booster</code>に対して実行できます。</p> <p>とはいっても、<code>xgboost.Booster</code>については<code>mlflow.xgboost</code>モジュールとしてflavorが既に定義されているので、 ユーザ側ではただ<code>mlflow.xgboost</code>の関数を呼び出すだけで使えます。</p> <p>さらに、あるflavorをベースに別のflavorを実装することも可能です。 ベースにするといっても、実際には両方を併用するような形になります。</p> <h1>flavorの種類</h1> <p>flavorは大きく3種類に分けることができます。</p> <h2>Built-in flavor</h2> <p>XGBoostを始めとして、MLflowが対応している種々のモジュールに対してそれぞれflavorが定義されています。 一般的なデータ分析タスクで使うような機械学習モデルはたいていの場合はこれらのflavorで対応ができるので、 小さいコーディングコスト(<code>log_model()</code> と <code>save_mode()</code>を呼び出すだけ)でMLflowの提供する機能を十全に使うことができます。</p> <p>さらに、いくつかのflavorにはautolog機能を備えたflavorがあり、 その場合はほぼ全自動でMLflowの機能を使うことができます。</p> <h2>PyFunc Model flavor</h2> <p>PyFunc Modelとは、特定の機械学習モデルを指すモデルではなく、以下の入出力をもつ関数の抽象モデルです。</p> <pre class="code" data-lang="" data-unlink>predict(model_input: pandas.DataFrame) -&gt; [numpy.ndarray | pandas.(Series | DataFrame)]</pre> <p>これ自身を使うのではなく、これをベースに別のflavorを作るために用意されています。 上記の入出力をもつ関数として表現可能なものであればこのflavorを元に作成可能です。 全てのBuilt-in FlavorはPyFunc Model Flavorをベースに作成されています。</p> <p>MLflowのdocumentやソースコードを読んでいるとき、PyFunc ModelとMLflow Modelを混同しやすいので注意しましょう。 基本的にはPyFunc ModelがMLflow Modelの一部だとみて問題ないとは思いますが、 とにかくmodelという単語がたくさん出てきて混乱するので区別を付けられるようになっておくのがいいです。</p> <p>ここでは紹介しませんが、PyFunc Modelの詳細について知りたい方は<a href="https://www.mlflow.org/docs/latest/python_api/mlflow.pyfunc.html">こちら</a>や<a href="https://www.mlflow.org/docs/latest/models.html#python-function-python-function">こちら</a> が詳しいです。 backendの実装等を覗いてみると、PyFunc Model flavorが非常に多くの機能を実装し、その他のflavorの作成を助けているのかが見て取れておもしろいです。</p> <h2>Custom Python flavor</h2> <p>MLflowが提供しているflavor以外にもユーザ側がflavorを開発することでMLflow互換のモデルを定義することが可能です。</p> <p>作成方法は以下の2通りがあります。</p> <ol> <li>実装したいモデルのクラスを<a href="https://github.com/mlflow/mlflow/blob/v1.10.0/mlflow/pyfunc/model.py#L47-L79">mlflow.pyfunc.PythonModel</a>のサブクラスとして作成</li> <li>既存のモデルをPyFunc Modelと相互変換するような入出力関数を定義</li> </ol> <p>基本的には1が簡単かつ有用ですが、MLflowを使わないで保存したモデルオブジェクトとの互換性はないので、既に学習済みのモデルを取り込むなどの互換性が必要な場合は2の方法を採る必要があります。</p> <h1>XGBoost flavor</h1> <h2>Overview</h2> <p><figure class="figure-image figure-image-fotolife" title="XGBoost Flavorとその関連する要素"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kazuya_fujioka/20200817/20200817090002.png" alt="f:id:kazuya_fujioka:20200817090002p:plain" title="f:id:kazuya_fujioka:20200817090002p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>XGBoost Flavorとその関連する要素</figcaption></figure></p> <p>図は、XGBoost flavor, MLflow, XGBoostの三者、そしてflavorを呼び出すスクリプトの4要素がどのオブジェクト (関数/クラス/モジュール) を通じて繋がっているのかを図示したものです。 黄色い四角で囲われた6つの関数がXGBoost flavorの主な構成要素です((<code>autolog()</code>等は本質ではなく、理解も容易なので省略しています。))。 なお、分かりやすさのためPyFunc Modelの実装である<code>pyfunc</code>モジュールもMLflowに入れています。</p> <p>ユーザ側は<code>log_model()</code>(MLflow Modelの記録)、<code>load_model()</code>(モデルの読み込み)を操作します。 内部的には、<code>log_model()</code>の呼び出しで<code>save_model()</code>(MLflow Modelの書き出し)が内部的に呼び出されるほか、<code>_load_pyfunc()</code>はモデルのデプロイ時に内部的に呼び出されます。</p> <p>これらは、基本的には全てのflavorに共通です。</p> <p>一方で<code>_load_model()</code>と<code>_XGBModelWrapper()</code>はxgboostライブラリに固有の実装です。 XGBoost flavor内から必要に応じて呼び出されます。</p> <h2>Components</h2> <p>では、これらを順を追って詳細に読んでいきます。 なお、解説のために動作が変化しない程度にコメントや変数等を変更している部分があります。</p> <h3>_load_pyfunc() &amp; _load_model() &amp; _XGBModelWrapper</h3> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">_load_pyfunc</span>(path): xgb_original = _load_model(path) <span class="synStatement">return</span> _XGBModelWrapper(xgb_original) <span class="synStatement">def</span> <span class="synIdentifier">_load_model</span>(path): <span class="synPreProc">import</span> xgboost <span class="synStatement">as</span> xgb model = xgb.Booster() model.load_model(os.path.abspath(path)) <span class="synStatement">return</span> model <span class="synStatement">class</span> <span class="synIdentifier">_XGBModelWrapper</span>: <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, xgb_model): self.xgb_model = xgb_model <span class="synStatement">def</span> <span class="synIdentifier">predict</span>(self, dataframe): <span class="synPreProc">import</span> xgboost <span class="synStatement">as</span> xgb <span class="synStatement">return</span> self.xgb_model.predict(xgb.DMatrix(dataframe)) </pre> <p>これらの3つの関数で実現していることは、引数で与えられたパス<code>path</code>にある学習済みxgboostモデルをロードする処理を、 <code>_load_pyfunc()</code>という名前の関数として実装することです。</p> <p><code>_load_pyfunc()</code>関数は<code>pyfunc.__init__.py</code>内に定義されている<code>load_model()</code>関数内で呼び出され、 <code>PyFuncModel</code>クラスの初期化に使用されます (<a href="https://github.com/mlflow/mlflow/blob/v1.10.0/mlflow/pyfunc/__init__.py#L473-L474">実装</a>)。</p> <p>モデルのロード部分はxgboostライブラリの機能をそのまま使って<code>_load_model()</code>関数として実装しています。 ただし、<code>xgboost.Booster()</code>は<code>predict()</code>メソッドを実装しているものの、その入力がPandas DataframeではなくDMatrixなのでそのままでは<code>PyFuncModel</code>に渡せません。 その間を埋めるためのラッパとして<code>_XGBModelWrapper</code>クラスが定義されています。</p> <h3>save_model()</h3> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">save_model</span>(xgb_model, path, conda_env=<span class="synIdentifier">None</span>, mlflow_model=<span class="synIdentifier">None</span>, signature: ModelSignature=<span class="synIdentifier">None</span>, input_example: ModelInputExample=<span class="synIdentifier">None</span>): <span class="synPreProc">import</span> xgboost <span class="synStatement">as</span> xgb path = os.path.abspath(path) <span class="synStatement">if</span> os.path.exists(path): <span class="synStatement">raise</span> MlflowException(<span class="synConstant">&quot;Path '{}' already exists&quot;</span>.format(path)) os.makedirs(path) <span class="synStatement">if</span> mlflow_model <span class="synStatement">is</span> <span class="synIdentifier">None</span>: mlflow_model = Model() <span class="synStatement">if</span> signature <span class="synStatement">is</span> <span class="synStatement">not</span> <span class="synIdentifier">None</span>: mlflow_model.signature = signature <span class="synStatement">if</span> input_example <span class="synStatement">is</span> <span class="synStatement">not</span> <span class="synIdentifier">None</span>: _save_example(mlflow_model, input_example, path) model_data_subpath = <span class="synConstant">&quot;model.xgb&quot;</span> model_data_path = os.path.join(path, model_data_subpath) <span class="synComment"># Save an XGBoost model</span> xgb_model.save_model(model_data_path) conda_env_subpath = <span class="synConstant">&quot;conda.yaml&quot;</span> <span class="synStatement">if</span> conda_env <span class="synStatement">is</span> <span class="synIdentifier">None</span>: conda_env = get_default_conda_env() <span class="synStatement">elif</span> <span class="synStatement">not</span> <span class="synIdentifier">isinstance</span>(conda_env, <span class="synIdentifier">dict</span>): <span class="synStatement">with</span> <span class="synIdentifier">open</span>(conda_env, <span class="synConstant">&quot;r&quot;</span>) <span class="synStatement">as</span> f: conda_env = yaml.safe_load(f) <span class="synStatement">with</span> <span class="synIdentifier">open</span>(os.path.join(path, conda_env_subpath), <span class="synConstant">&quot;w&quot;</span>) <span class="synStatement">as</span> f: yaml.safe_dump(conda_env, stream=f, default_flow_style=<span class="synIdentifier">False</span>) pyfunc.add_to_model(mlflow_model, loader_module=<span class="synConstant">&quot;mlflow.xgboost&quot;</span>, data=model_data_subpath, env=conda_env_subpath) mlflow_model.add_flavor(FLAVOR_NAME, xgb_version=xgb.__version__, data=model_data_subpath) mlflow_model.save(os.path.join(path, MLMODEL_FILE_NAME)) </pre> <p>MLflow Model<code>mlflow_model</code>をartifact化し、渡されたローカルのフォルダパス<code>path</code>に保存する関数です。 具体的には、フォルダの中に以下のファイルが作成されます。</p> <ol> <li><code>signature</code>として渡されたSignatureのダンプファイル</li> <li><code>xgb_model</code>として渡された<code>xgboost.Booster</code>のダンプ。</li> <li><code>conda_env</code>として渡された辞書/ファイル名のconda envファイル (ただし、無ければデフォルトを生成)</li> <li>MLflow Modelファイル(MLmodel)</li> </ol> <p>最も重要なのは、以下の3行です。</p> <pre class="code lang-python" data-lang="python" data-unlink>pyfunc.add_to_model(mlflow_model, loader_module=<span class="synConstant">&quot;mlflow.xgboost&quot;</span>, data=model_data_subpath, env=conda_env_subpath) mlflow_model.add_flavor(FLAVOR_NAME, xgb_version=xgb.__version__, data=model_data_subpath) </pre> <p>MLmodelファイル(yaml)にはflavorごとの設定項目を記録するセクションがあり、 その内容をここで生成しています。</p> <p>あるflavorの設定項目を記録するには、<code>Model.add_flavor()</code>メソッドを使って、 flavor名(第一引数)とその設定項目(キーワード引数)を渡します。</p> <p>例えば、3行目で、XGBoost flavorの項目が以下のように作成されます。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">xgboost</span><span class="synSpecial">:</span> <span class="synIdentifier">xgb_version</span><span class="synSpecial">:</span> &lt;xgb.__version__の値&gt; <span class="synIdentifier">data</span><span class="synSpecial">:</span> subpath/from/MLmodel/root/to/model.xgb </pre> <p>なお、セクション名"xgboost"はmlflow.xgboost内で</p> <pre class="code lang-python" data-lang="python" data-unlink>FLAVOR_NAME = <span class="synConstant">&quot;xgboost&quot;</span> </pre> <p>と定義されているところから来ています。</p> <p>さらに、PyFunc Model flavorをベースに作られているflavorを使った場合、 PyFunc Modelのセクション"python_function"を作成します。</p> <p>1-2行目の"pyfunc.add_to_model()"関数は"Model.add_flavor()"メソッドの呼び出しをPyFunc Model flavor向けにラップしたものです。 この中で conda env等のPyFuncの基本的な構成要素へのパスとPythonのバージョンを含んだ"python_function"セクションが<code>mlflow_model</code>に記録されます。</p> <h3>log_model()</h3> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">log_model</span>(xgb_model, artifact_path, conda_env=<span class="synIdentifier">None</span>, registered_model_name=<span class="synIdentifier">None</span>, signature: ModelSignature=<span class="synIdentifier">None</span>, input_example: ModelInputExample=<span class="synIdentifier">None</span>, **kwargs): Model.log(artifact_path=artifact_path, flavor=mlflow.xgboost, registered_model_name=registered_model_name, xgb_model=xgb_model, conda_env=conda_env, signature=signature, input_example=input_example, **kwargs) </pre> <p><code>mlflow.models.Model</code>の<code>log</code>メソッド(classmethod)のラッパです。 大まかには、以下の3ステップを実行します。</p> <ol> <li>MLflow Modelの生成</li> <li>artifactの生成</li> <li>生成したMLflow Model(artifactを含む)をrunとして記録</li> </ol> <p>なお、モデルのartifact化に使用するパラメータは<code>Model.log</code>の<code>kwargs</code>引数に入ってから<code>flavor.save_model()</code>呼び出し時に渡されます。 <code>save_model()</code>の<code>xgb_model</code>変数は<code>kwargs</code>を通じて<code>save_model()</code>に渡され、artifact化されます。</p> <h3>load_model()</h3> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">load_model</span>(model_uri): local_model_path = _download_artifact_from_uri(artifact_uri=model_uri) flavor_conf = _get_flavor_configuration(model_path=local_model_path, flavor_name=FLAVOR_NAME) xgb_model_file_path = os.path.join(local_model_path, flavor_conf.get(<span class="synConstant">&quot;data&quot;</span>, <span class="synConstant">&quot;model.xgb&quot;</span>)) <span class="synStatement">return</span> _load_model(path=xgb_model_file_path) </pre> <p>この関数の処理内容ですが、</p> <ol> <li>artifactをダウンロードして</li> <li>flavorの設定を読み込んで</li> <li>xgboost.Boosterをartifactからロード</li> </ol> <p>と、中で呼び出されている関数名を読んだ通りの処理内容です。</p> <p>特に解説することもないのですが、 <code>_download_artifact_from_uri()</code>や<code>_get_flavor_configuration()</code>といった 便利なutility関数が実装されていることを知っているとflavor実装などで役に立ちます。</p> <h1>おわりに</h1> <p>駆け足となりましたが、XGBoost flavorの内容とその前提となる知識を軽く紹介しました。 シンプルなflavorなので、MLflowの理解のために読むのはもちろんですが、flavorを作ってみる際の手本としても最適だと思います。 実際、今回書いていてすごく理解が深まり<s>、前回記事の内容が思いっきり間違っていることにも気付け</s>ました。</p> <p>当該箇所は修正済みです。本当にすみませんでした……。</p> <p>他にも、純粋にPythonモジュールの実装としても、多層に分けて段階的に抽象度を下げていくなど勉強になることが多かったです。 汎化と実装コストのトレードオフにはいつも苦心しているので、今後書くプログラムに色々と取り入れようと思っています。</p> <p>では、良きPython Lifeを!</p> <h1>Reference</h1> <ul> <li><a href="https://www.mlflow.org/docs/latest/index.html">公式ドキュメント</a></li> <li><a href="https://github.com/mlflow/mlflow/tree/v1.10.0">リポジトリ</a></li> </ul> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/B07L2V4H59/hoxom-22/"><img src="https://m.media-amazon.com/images/I/51iXSKmDasL._SL160_.jpg" class="hatena-asin-detail-image" alt="機械学習スタートアップシリーズ ベイズ推論による機械学習入門" title="機械学習スタートアップシリーズ ベイズ推論による機械学習入門"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/B07L2V4H59/hoxom-22/">機械学習スタートアップシリーズ ベイズ推論による機械学習入門</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/%BF%DC%BB%B3%C6%D8%BB%D6" class="keyword">須山敦志</a></li><li><span class="hatena-asin-detail-label">発売日:</span> 2018/12/07</li><li><span class="hatena-asin-detail-label">メディア:</span> Kindle版</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> <div class="footnote"> <p class="footnote"><a href="#fn-65886ff7" name="f-65886ff7" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">この例では、今回の記事の内容は不要です</span></p> </div> kazuya_fujioka 私の人生のロックマン(あるいは星のカービィ)戦略について hatenablog://entry/26006613616065942 2020-08-17T22:29:42+09:00 2020-08-18T07:07:47+09:00 株式会社ホクソエム常務取締役のタカヤナギ=サンです。 会社では主にα崩壊を起こしそうなシャチョーを制御するための制御棒を担当しています。 これは何の話なの? 私のやり方というか能力の上げ方はタイトルにあるように基本的に「ロックマン(あるいは星のカービィ)戦略」なんですが、それについて明示的に書いたポエムです。 勘の鋭い方はこのタイトルだけでどういう戦略なのかお分かりになられると思うので、ここでこの記事を読むのをストップすれば作業時間を確保できて良いかもしれない。 「いや、そんなもん会社のBLOGに書くんじゃねーよ💢」という話があるかもしれないですが、ここは私の保有する会社なので何の問題もない、… <p>株式会社ホクソエム常務取締役のタカヤナギ=サンです。</p> <p>会社では主にα崩壊を起こしそうなシャチョーを制御するための制御棒を担当しています。</p> <h2>これは何の話なの?</h2> <p>私のやり方というか能力の上げ方はタイトルにあるように基本的に「ロックマン(あるいは星のカービィ)戦略」なんですが、それについて明示的に書いたポエムです。</p> <p>勘の鋭い方はこのタイトルだけでどういう戦略なのかお分かりになられると思うので、ここでこの記事を読むのをストップすれば作業時間を確保できて良いかもしれない。</p> <p>「いや、そんなもん会社のBLOGに書くんじゃねーよ💢」という話があるかもしれないですが、ここは私の保有する会社なので何の問題もない、don't you?</p> <h2>とりあえずの結論</h2> <p>なんか長いんでとりあえず結論を書くと</p> <ul> <li>技術をたくさん持ってる/生み出してそうな優秀な技術者(エンジニア)に暗に陽に弟子入りしてたくさんの技術(Not技能)をコピーさせてもらおう!</li> <li>高度な技能(スキル)を持っている技能者(テクニシャン)さんを真似ようとする場合には「彼はコピー可能なもの(テクノロジーのレベルにまで至っているもの)を持っているのか?」を意識しよう <ul> <li>もしそうでないならばその人からコピーして学ぶことをやめよう、それは時間の無駄です <ul> <li>技能(スキル)は定義からしてコピー不可能なものだから</li> <li>このインターネット時代においては学ぶべきリソースは山ほど転がっているから</li> </ul> </li> </ul> </li> </ul> <p>です。</p> <h2>私の人生のロックマン(あるいは星のカービィ)戦略について</h2> <p>↓こんなかんじでロックマンって敵(BOSS)を倒すとそいつの能力使えるようになるじゃないですか?</p> <p><iframe width="459" height="344" src="https://www.youtube.com/embed/O6M0ic3XpJo?start=915&feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><cite class="hatena-citation"><a href="https://youtu.be/O6M0ic3XpJo?t=915">youtu.be</a></cite></p> <p>あるいは、カービーでも吸い込だ敵の能力コピーできるじゃないですか?</p> <p><iframe width="480" height="270" src="https://www.youtube.com/embed/Iyanlh4NddE?start=45&feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><cite class="hatena-citation"><a href="https://youtu.be/Iyanlh4NddE?t=45">youtu.be</a></cite></p> <p>これらと同じように、私の人生戦略って基本的に単純で「良いな・イケてるなと思った人のアイディア・やり方・技術・振る舞いを何でもコピーする」って話しなんですね。</p> <p>あるいはバトルボーナス中にトキを降臨させちゃう北斗の拳のケンシロウ(パチスロ)とかとか。</p> <p><iframe width="459" height="344" src="https://www.youtube.com/embed/ckgWHTek_p0?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><cite class="hatena-citation"><a href="https://www.youtube.com/watch?v=ckgWHTek_p0">www.youtube.com</a></cite></p> <p>「こ…この動きは…ト…トキ!」</p> <p>「ラオウよ、天に還る時が来たのだ」</p> <p>最高でしたね???あの頃は皆トキの動きのマネしてた。バトルボーナス、確定!!!</p> <h2>なんでこのやり方をするのか?それは私に個性がないからです</h2> <p>なんでこういうコピー戦略をするのかというと、それは私に個性がないからです。</p> <p>私はキャンバス、ほぼ真っ白なキャンバス。</p> <p>私を知ってる人は「いや、お前髪が変な色だったり大体似たような熊のTシャツ来てるし個性的じゃねぇかよ」と思うかも知れないが、それは違う。</p> <p>私の髪の色が変なのは親愛なる美容師殿への正しい課金手段として「好きな色にしていいよ(ブリーチやカラー入れた方が課金額があがる)」からやっているのであって特に私の好みではないし、逆に常に黒い髪をKeepされ続けている皆さんのほうがよほど個性的だ。</p> <p>”髪 is 黒いであるべし”という強いこだわりを感じる。もしこだわりがないというのなら適当な色にされたってよくない?<a href="#f-74b4b9a0" name="fn-74b4b9a0" title="実は一回全頭ドピンクにされたことがあって、そのときだけは鏡を見るたびになんか不安な気持ちになったので「ピンクだけはやめてくれ」と言うようになった。”髪の色がピンクなのは嫌だ”が私の個性かもしれない">*1</a>。</p> <p>もう少し言うとオリジナルの学び方というものを知らない。これはあれ、文化的資本って呼ばれているやつに近いと思う。 書籍を読むようになったのは大学院に入った時(24歳)でそれまではいわゆる教科書と読書感想文で嫌々読んだ本くらいしか読んだことがなかった。 なので、そもそも学び方を知らないので、人からコピるしかなかったわけです。 メタ学習(学び方を学ぶ)みたいな概念もなかった、メタ?なにそれ美味しいの?</p> <p>今ではおかげさまで書籍を買う余裕があるのでいっぱい本読んでるけど。</p> <p>もちろん私にもコピーして塗りつぶしたくない色や領域、そして大切にしているものがあると言えばあるが、それはそんなに多くないんですね。</p> <p>大多数の物事に対するこだわりを捨てる、特定の領域にしぶとく深く興味を持ち続ける、その他は基本人の全コピーか脳死状態で行動できるよう仕組み化にしておく、それが俺のやり方。</p> <p>ちなみに、このやり方を取った場合にみなさんがメリットと感じそうなこととしては”アンチ耐性の飛躍的な向上”があります。</p> <p>すごい!RPGだと最後の方に出てきそうな高級なアクセサリー的装備が手に入ります。</p> <p>例えば、最近おかげさまである程度書籍などが売れまして、インターネットにおいてもHOXO-Mアンチが沸いてきており、</p> <ul> <li>「くそっ、ホクソエムむかつく!」</li> </ul> <p>とか</p> <ul> <li>「なんか〜ホクソエム苦手〜生理的に無理〜」</li> </ul> <p>みたいな話がちょいちょいあるんだが、私はこの手の話はイチロー先生よろしくどんと来いだ。</p> <p><iframe width="480" height="270" src="https://www.youtube.com/embed/6BKo5bcceoQ?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><cite class="hatena-citation"><a href="https://www.youtube.com/watch?v=6BKo5bcceoQ">www.youtube.com</a></cite></p> <p>何故どんとこいなのかというと、最近よく聞く「ネットの誹謗中傷で慰謝料が取れて儲けのチャンスだワハハ!」という点はおいておくとして、上述したように私に個性がないからですね。</p> <p>もう少し言うと「皆さんが見ている・思っている私の個性的なものは大体私が他人から拝借したアイディア・やり方・技術・振る舞いであるので、 そもそも特に私自身がアンチにディスられているとは感じない」わけです。</p> <p>「あっ、あの時助けていただいた鶴さんのアイディア・やり方・技術・振る舞いをディスってるんだなこいつらは」くらいのイメージです。 なので虚無。何も感じない。</p> <p>もちろん私にもディスられたら嫌な興味関心のあるところがあるような気もするが、 それはそもそも大多数の社会を営んでいる皆様におかれましてはAlmost surely興味も関心もない話(特に広義の統計科学に関わるところ)なので、それがディスられることはまずないという状況なんですね、はい。</p> <h2>じゃぁ、貴方は誰からロックマン/星のカービィ戦略に基づいてコピーをするの?</h2> <p>ここまで何度か”コピーをする”と書いてきたが、その対象は何度か書いたようにある人の”アイディア・やり方・技術・振る舞い”です。 エンジニアリングにのみ対象を絞るとすると、コピーするするものは”技術(テクノロジー)”であって、”技能(スキル)”ではない、ここが重要です。</p> <p>なんでかと言うと技能(スキル)はコピーできなくて技術(テクノロジー)のみがコピー(人から人へと伝達)可能だからですね。</p> <p>「じゃぁ、技能と技術って何が違うんだ?」と言うと、昔、私も何度か調べてはいずれの回も厳密な定義にたどり着けずに力尽きているのだが、 ここではとりあえずWikipediaから”技術”と”技能”の違いについての定義を拝借するとしましょうか。</p> <pre class="code" data-lang="" data-unlink>技術と技能(スキル)の違い 「技術」と「技能」という言葉は、違いを理解せずに混同して会話などで使用されがちであるが、以下のように明確な違いがある。 技術 知識のことである。 教科書のように文書化したり、会話などで他人に伝達可能である。 技能 技術(知識)を使用し、作業を遂行する能力のことである。 個人の中に熟成されるため、他人に伝達不可能である。 例えば、自動車の運転の仕方を知識として習得しただけでは、自動車を正確に運転できる確率は低い。訓練を行い、運転の技能を上げることにより、より正確に運転できるようになる。</pre> <p><a href="https://ja.wikipedia.org/wiki/%E6%8A%80%E8%A1%93#%E6%8A%80%E6%B3%95%E3%81%A8%E6%8A%80%E8%83%BD%EF%BC%88%E3%82%B9%E3%82%AD%E3%83%AB%EF%BC%89">&#x6280;&#x8853; - Wikipedia</a></p> <p>・・・とさ。 なるほど、他人に伝達可能か否かが違うぞと、そういうことです。</p> <p>そもそもテクノロジー(技術)という語はギリシア語の「技(テクネ)を学問(XXXオロジー)のレベルにまで昇華したもの」という意味なので、ちゃんと原義に沿っていますね? なので、技術(テクノロジー)のみがコピー可能で技能(スキル)はコピー不可なわけです、人に宿る職人芸、強し。</p> <p>じゃぁ技術者(エンジニア)ってのは何なのかと言うと同じくWikipediaを参考にすると</p> <blockquote><p>現代ではエンジニアとは、自然法と社会の必要性の制限の中でテクノロジーを創り出す人のことをいう。</p></blockquote> <p><a href="https://ja.wikipedia.org/wiki/%E6%8A%80%E8%A1%93#%E6%8A%80%E6%B3%95%E3%81%A8%E6%8A%80%E8%83%BD%EF%BC%88%E3%82%B9%E3%82%AD%E3%83%AB%EF%BC%89">&#x6280;&#x8853; - Wikipedia</a></p> <p>ということだそうだ<a href="#f-8be05596" name="fn-8be05596" title="この辺の工学(者)・技術(者)の関係がWikipediaさんも曖昧で私も調べても曖昧なので誰か正しい定義を知ってたら教えて欲しい">*2</a> 。</p> <p>なんで、長いこと社会生活をしているとちょいちょい</p> <ul> <li>「いや〜XXXさんのCodeは良く高速に動きますが難解ですな、可読性0ですわいタハー(それメンテできなくね?)」</li> </ul> <p>とか</p> <ul> <li>「俺の!コードは!読めば分かる!だから!ノーマニュアルや!(実際は難解&README.md書くくらいまではどう考えても仕事じゃね?)」</li> </ul> <p>などと元気よく自慢してくるおじさんに出会うのだが、私は基本的にそれらの方々は無視。</p> <p>だって、それエンジニア(技術者)じゃなくてテクニシャン(技能者)だもん、コピーできるもの持ってないんだもの(いや持ってるかもしれないけどその凄いポイントはコピー不可なんだもの)。</p> <p>頑張ってチーフテクニシャンおじさん(CTO)、ゆくゆくは人間国宝を目指してもらいたい。 海老蔵の睨みばりのSomething技能を研ぎ澄ませて行ってほしい。 一方、優秀な技術者が優秀な技能者を兼ねている場合も当然多いので、その場合にはとても用があります。</p> <p>そんなかんじ。</p> <h2>結論</h2> <p>長くなったが私のやり方をまとめると</p> <ul> <li>技術をたくさん持ってる/生み出してそうな優秀な技術者(エンジニア)に暗に陽に弟子入りしてたくさんの技術(Not技能)をコピーさせてもらおう!</li> <li>高度な技能(スキル)を持っている技能者(テクニシャン)さんを真似ようとする場合には「彼はコピー可能なもの(テクノロジーのレベルにまで至っているもの)を持っているのか?」を意識しよう <ul> <li>もしそうでないならばその人からコピーして学ぶことをやめよう、それは時間の無駄です <ul> <li>技能(スキル)は定義からしてコピー不可能なものだから</li> <li>このインターネット時代においては学ぶべきリソースは山ほど転がっているから</li> </ul> </li> </ul> </li> </ul> <p>ということでした。</p> <p>次回もまた、見てくれよな!</p> <div class="footnote"> <p class="footnote"><a href="#fn-74b4b9a0" name="f-74b4b9a0" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">実は一回全頭ドピンクにされたことがあって、そのときだけは鏡を見るたびになんか不安な気持ちになったので「ピンクだけはやめてくれ」と言うようになった。”髪の色がピンクなのは嫌だ”が私の個性かもしれない</span></p> <p class="footnote"><a href="#fn-8be05596" name="f-8be05596" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">この辺の工学(者)・技術(者)の関係がWikipediaさんも曖昧で私も調べても曖昧なので誰か正しい定義を知ってたら教えて欲しい</span></p> </div> shinichi-takayanagi TRI-AD(TOYOTAの自動運転のとこ)の服部圭悟さんにカジュアル面談してもらった hatenablog://entry/26006613613074717 2020-08-11T21:20:13+09:00 2020-08-11T21:33:28+09:00 頭出し 前職の同僚(一時期私の真後ろの席に座っていた)で、今は「誰もが、安全に移動できる世界へ」を掲げるTRI-ADに勤めている服部圭悟さんとカジュアル面談したら面白かったのでまとめておきたい、そして彼のチームの採用へとつなげていきたい。 カジュアル面談中にメモった箇条書きを体裁整えただけなので、やや文が壊れているがご容赦&そこも含めて愛して欲しい。 「いや、そんなもん会社のBLOGに書くんじゃねーよ💢」という話があるかもしれないですが、ここは私の保有する会社なので何の問題もない、don't you? このカジュアル面談では、会社の全容とかまるで聞かないで私の聞きたいことだけ聞いてきたんで、そ… <h2>頭出し</h2> <p>前職の同僚(一時期私の真後ろの席に座っていた)で、今は「誰もが、安全に移動できる世界へ」を掲げる<a href="https://www.tri-ad.global/jp/home">TRI-AD</a>に勤めている服部圭悟さんとカジュアル面談したら面白かったのでまとめておきたい、そして彼のチームの採用へとつなげていきたい。</p> <p>カジュアル面談中にメモった箇条書きを体裁整えただけなので、やや文が壊れているがご容赦&そこも含めて愛して欲しい。 「いや、そんなもん会社のBLOGに書くんじゃねーよ💢」という話があるかもしれないですが、ここは私の保有する会社なので何の問題もない、don't you?</p> <p>このカジュアル面談では、会社の全容とかまるで聞かないで私の聞きたいことだけ聞いてきたんで、その質疑応答をまとめました。 本当に好き勝手に聞いたのにまじで全部真摯に答えてもらって大変ありがたかったです。</p> <p>みんなが話を聞きに行くとまた違った結果になるかもしれない、カジュアル面談はここからカジュアルに応募することができる、お気軽に、応募、是非。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fsites.google.com%2Fview%2Fspeedy-booker%2F%25E3%2583%259B%25E3%2583%25BC%25E3%2583%25A0%2F%25E6%259C%258D%25E9%2583%25A8-%25E5%259C%25AD%25E6%2582%259F%3Fauthuser%3D0" title="カジュアル面談 - 服部 圭悟" 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://sites.google.com/view/speedy-booker/%E3%83%9B%E3%83%BC%E3%83%A0/%E6%9C%8D%E9%83%A8-%E5%9C%AD%E6%82%9F?authuser=0">sites.google.com</a></cite></p> <p>まず、私は車はHONDA党(<a href="https://ja.wikipedia.org/wiki/%E3%83%9B%E3%83%B3%E3%83%80%E3%83%BB%E3%82%A4%E3%83%B3%E3%83%86%E3%82%B0%E3%83%A9%E3%82%BF%E3%82%A4%E3%83%97R">インテグラの96スペックTYPE-R</a>に乗っていた、当時Rはプログラミング言語じゃなくてRacing SpilitsのRだった)なのだが、<a href="https://www.youtube.com/channel/UCg621ZS-iKFLWLbXcSBG5mw">トヨタイムズ</a>のファンでもあるので、冷やかしではなく本当にTRI-ADに興味がある人材です。</p> <p>これとか最高に面白いから見て欲しい</p> <ul> <li><a href="https://youtube.com/watch?v=PXUjpfUGoOU">https://youtube.com/watch?v=PXUjpfUGoOU</a></li> <li><a href="https://youtube.com/watch?v=vJ8DsIiSb-U">https://youtube.com/watch?v=vJ8DsIiSb-U</a></li> </ul> <p>・・・さて、服部圭悟さんは(最近ちょいちょい色んな人からディスられる)MLOpsやData Engineerをやってる人だ。 私の中では<a href="https://eng.uber.com/michelangelo-machine-learning-platform/">Uberのミケランジェロ</a>に異常なライバル心を燃やす機械学習プラットフォーム作りたがってるおじさんという認識だ。 彼は私と同僚だった時、国際的な政治事情に巻き込まれてチームごと爆散してしまったという悲しい過去を持つ男だ。でも、俺はそういう過去を背負った男、嫌いじゃないぜ。 しかし、今は<a href="https://ja.wikipedia.org/wiki/%E3%83%A9%E3%82%B6%E3%83%AD">ラザロ</a>の如く蘇っている。素晴らしい。</p> <p>ラザロとミケランジェロ、なんか語感が似てる。え?そういうこと?</p> <p>トライアドって読んでたけどティーアールアイエーディーらしい知ったかぶりしてごめんなさい。 <a href="https://tri-ad.global/jp/news/20200728">そして2021年からはウーブン・プラネット・ホールディングス株式会社になるらしい</a>。</p> <h2>質問とそれに対する返答</h2> <h3>質問:ユー、今なにやっとん?</h3> <ul> <li>TRI(シリコンバレーにあるToyota Research Institute)と協力して機械学習基盤を作ってるよ</li> <li>自動車なんで莫大なセンサーデータを扱っています</li> <li>機械学習モデルとそのパイプラインつくるところまでがMISSION</li> <li>事故が起きた場合、機械学習モデルの説明性が求められるのでそこを頑張っています <ul> <li>今までも自動車のアーキテクチャを15年間保存してきた</li> <li>これらの情報整理(データ・モデルのVersion管理など)とシステム化もチームのMISSION</li> </ul> </li> <li>俺はミケランジェロを作りたい!(アッハイ)</li> </ul> <h3>質問:大企業で自動運転ってやれるのですかね?まだまだトライアンドエラーの塊ぽい印象だから失敗を許容して高速改善回さないといけないだろうが、大企業という”型”が足かせになりそう</h3> <ul> <li>割とやれる。</li> <li>今はリリースにとても時間がかかる(とても時間がかかる、大事なこと(ry )ので、リリースプロセスの最適化をやっている。</li> </ul> <h3>質問:顧客はどちらさまなのですか?ずっと研究開発してるだけでいいの?</h3> <ul> <li>社内とトヨタグループ。個人的にはもっと広げていきたいと思っている。</li> </ul> <h3>質問:言うて、TOYOTA系列の自動運転研究開発してるぽいところたくさんあるし、系列で仕事被りまくってんじゃないの?</h3> <ul> <li>意外と機械学習のタスクとしては被ってない(へぇ〜!!!)</li> </ul> <h3>質問:御チームの構成どうなっとんのや</h3> <ul> <li>MLOpsで3チームあるよ、そのうちの1つをKeigo HattoriがLead(4人)</li> <li>残りの2チーム <ul> <li>アノテーションチーム(各ベンダーに自動でタスクを投げる(1人))</li> <li>組み込み系チーム(ハードウェアへのデプロイ系とか(2人))</li> </ul> </li> <li>大増員中です!!!(入社チャンス!!!)</li> <li>機械学習つよつよ人材はいるので、ソフトウェアつよつよ人材がほしい</li> <li>チームに日本人が多いので日本語も使う。公用語は英語、ドキュメントも英語。</li> </ul> <h3>質問:R&amp;D、不況に弱い印象だし、会社ちゃんともつ?爆散はないの?</h3> <ul> <li>大丈夫(しらんけど)</li> <li>豊田章男社長がご熱心 <ul> <li>タカヤナギ=サン的には皆これを見て欲しいと思う <a href="https://toyotatimes.jp/insidetoyota/088.html">TOYOTA NEWS #88&#xFF5C;&#x8C4A;&#x7530;&#x7AE0;&#x7537;&#x304C;&#x79C1;&#x8CA1;&#x3092;&#x6295;&#x3058;&#x3066;&#x5C0E;&#x304F;&#x672A;&#x6765;&#x3010;&#x524D;&#x7DE8;&#x3011;&#xFF5C;&#x30C8;&#x30E8;&#x30BF;&#x30A4;&#x30E0;&#x30BA;&#xFF5C;&#x30C8;&#x30E8;&#x30BF;&#x30A4;&#x30E0;&#x30BA;</a></li> </ul> </li> </ul> <h3>その他</h3> <ul> <li>車専用のOSも作っているよ</li> <li>会社の文化的にはAmazonから来ている人が多いのでそれっぽい</li> <li>1年後にTeslaにおいつけおいこせ!</li> </ul> shinichi-takayanagi 書評:実用的でないPythonプログラミング hatenablog://entry/26006613613056661 2020-08-11T18:55:20+09:00 2020-08-11T19:02:30+09:00 共立出版さまから献本いただいた 実用的でないPythonプログラミング: 楽しくコードを書いて賢くなろう! の書評です! 入門書を読み終えた2冊めの書籍として心豊かになるトピック満載です! www.youtube.com <p>共立出版さまから献本いただいた <a href="https://amzn.to/3kqQMuZ">実用的でないPythonプログラミング: 楽しくコードを書いて賢くなろう!</a> の書評です! 入門書を読み終えた2冊めの書籍として心豊かになるトピック満載です!</p> <p><iframe width="459" height="344" src="https://www.youtube.com/embed/88KYvf8LTLE?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><cite class="hatena-citation"><a href="https://www.youtube.com/watch?v=88KYvf8LTLE">www.youtube.com</a></cite></p> shinichi-takayanagi 書評:AWS認定アソシエイト3資格対策 hatenablog://entry/26006613601153614 2020-07-20T11:09:17+09:00 2020-07-20T11:11:14+09:00 AWS認定アソシエイト3資格対策~ソリューションアーキテクト、デベロッパー、SysOpsアドミニストレーター~ の書評です。 AWSへこれから入門される方やある程度まとまったAWSの体系的な知識が欲しい方、また弊社のお若い人におすすめです! www.youtube.com <p><a href="https://amzn.to/30vgc1B">AWS認定アソシエイト3資格対策~ソリューションアーキテクト、デベロッパー、SysOpsアドミニストレーター~ </a>の書評です。</p> <p>AWSへこれから入門される方やある程度まとまったAWSの体系的な知識が欲しい方、また弊社のお若い人におすすめです!</p> <p><iframe width="459" height="344" src="https://www.youtube.com/embed/jydSLH_hfuo?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><cite class="hatena-citation"><a href="https://www.youtube.com/watch?v=jydSLH_hfuo">www.youtube.com</a></cite></p> shinichi-takayanagi 法人としての価格設定問題からの、おじさんエンジニアの辛さと賃金の関係性 hatenablog://entry/26006613596697758 2020-07-11T08:28:21+09:00 2020-07-11T16:11:29+09:00 株式会社ホクソエム常務取締役のタカヤナギ=サンです、主にバックオフィス業務を担当しています。 自分メモに書き溜めていたポエムネタが溜まってきたので少しずつ放出していこうと思い筆をとりました。 「いや、そんなもん会社のBLOGに書くんじゃねーよ💢」という話があるかもしれないですが、ここは私の保有する会社なので何の問題もない、don't you? これは何の話なの? 以前、社のお若い方が技術的に楽しそうな案件を持ってこられて、その価格設定をどうするかについて悩まれておられた時がありました。 その際に社内のSlackにいわゆる”おじさんの小言”のようなものをちらほら書いていたので、それを改めて文章に… <p>株式会社ホクソエム常務取締役のタカヤナギ=サンです、主にバックオフィス業務を担当しています。</p> <p>自分メモに書き溜めていたポエムネタが溜まってきたので少しずつ放出していこうと思い筆をとりました。 「いや、そんなもん会社のBLOGに書くんじゃねーよ💢」という話があるかもしれないですが、ここは私の保有する会社なので何の問題もない、don't you?</p> <h2>これは何の話なの?</h2> <p>以前、社のお若い方が技術的に楽しそうな案件を持ってこられて、その価格設定をどうするかについて悩まれておられた時がありました。</p> <p>その際に社内のSlackにいわゆる”おじさんの小言”のようなものをちらほら書いていたので、それを改めて文章にし、更に「あ、この話は私がちょいちょい感じているおじさんエンジニアの辛さと賃金の話にもつながってくるな」思い、そことも絡めて書いたものになります。</p> <h2>法人としての価格設定問題</h2> <p>既にご存じの方もいるかもしれないですが、弊社は全員が副業として回している会社で21世紀にふさわしい働き方改革ダイバージェンス2020を体現した会社となっております。 なので、OSSを開発するかの如く「うわぁ、この案件技術的に超楽しいナリ〜」と土日を潰してほぼ無料のご奉仕価格で開発しちゃうというのもありといえばあり、”ありよりのあり”になるのですがそれはやっていません。</p> <p>なので、”ありよりのなし”、です。</p> <p>「なんでじゃい、楽しければお安く引き受けてもいいんじゃないんかい💢(表現はもっとマイルドで低姿勢)」とお若い方がご本人の謙虚さからくる面もあってそう言われていたのですが、そこを諭す際によく引用させてもらっているのがこの「とんかつ屋の悲劇」という記事です。これを一部引用させていただくと</p> <blockquote><p>年金が形を変えた補助金に?  「何十年も変わらない値段と、チェーン店ではありえない品質の高さと格安さ」などとグルメサイトでも称賛されていることが多い。しかし、それを可能にしているのは、すでに減価償却の終わった古い設備、ローンを払い終えた自社店舗、そして年金をもらいながら夫婦で切り盛りしていることなどだ。  ある意味、年金が経営継続への補助金のようになっているわけだ。こうした経営を続けてきた場合、いよいよ世代交代の時期になると若い現役世代にはとても生活をしていけるだけの収入を得ることができない。  「そうなってから、急に値段を大幅に上げるなどはできないし、設備更新などに多額の費用がかかるので、後継者にとっては重荷になるでしょう。」商業関係の支援事業を行う行政職員は、そう話す。先の外食産業の社員も、「夫婦二人で一人分の給与しかなく、それでやっと可能になっているような低価格がウリでは、いくら有名でも、のれん代を出してまで買収する意味はあまりない」と言う。</p></blockquote> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnews.yahoo.co.jp%2Fbyline%2Fnakamuratomohiko%2F20180827-00094583%2F" title="とんかつ屋の悲劇 ~ 行列ができる人気店がなぜ廃業するのか(中村智彦) - Yahoo!ニュース" 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://news.yahoo.co.jp/byline/nakamuratomohiko/20180827-00094583/">news.yahoo.co.jp</a></cite></p> <p>という話です。</p> <p>要するに<span style="color: #ff0000">「(ご本人らは年金で食えるので)善意から値上げをしていないのかもしれないが、結局はそれが過度な価格下落競争を生んで、近隣の似たような(この場合だととんかつ屋をやりたいお若い方の)店を強制的に廃業に追い込でいる」</span>という話になるわけです。</p> <p>これに絡めて「いいかい?俺たちはとんかつ屋の悲劇を起こしてはいけない。技術的に面白い(主にRの)話だからといってもめちゃ低価格で引き受けるのはやめよう!それは巡り巡ってお若いRユーザの芽🌱を摘むことになる(Rが儲からない言語になるのは悲しい😢)」という話をしていたのです。</p> <p>うんうん、わかる、過度なダンピングよくない、それは強制的に海を赤く染める行為なわけです(しかもそこに流れる赤い血は自分のものではなく人のもの)。</p> <h2>おじさんエンジニアの辛さと賃金</h2> <p>これと同じ話が一個人のおじさんエンジニアの賃金にも当てはまってくるぞと、もうちょっと機械学習っぽくいうと”おじさんエンジニアの辛さと賃金”というトピックベクトルは良い感じに”法人の価格設定問題”空間に射影できるぞと、そういうことです。</p> <p>え?何が関係あるのかって?ちょっと落ち着いて私の経験を聞いて欲しい。</p> <p>私自身は社会人歴14年で転職を繰り替えしたりホクソエムったりで、合計6社ほどの経験があるのですが、転職の際に年収を2~300万円ほど下げて転職した経験が2度ほどあります。 その意図は「正直、自分を優秀な技術者と仮定するならば、そのうち成果出して給料もあがるだろうし昇給がだめなら転職/副業すればいいや〜」くらいの楽観論が根底にあります。 その一方で、更にガッツリ年収を下げる(年収300万円くらいになるイメージ)のはいくら副業で稼げてもNGです。</p> <p>もともとの「法人としての価格設定問題」の考えてに至った原点も実はこのへんにあります。 以前、同僚(私の心の中のインフラクラウド師匠)だった方が</p> <p><span style="color: #ff0000">「某D社では技術的に凄いおじさんエンジニアがたくさんいるんだが、彼らは全然給料交渉をしない。そして僕らは彼ら技術的に凄いおじさんエンジニアと給料を比較されるので僕らが交渉しても給料がまるで上がらないんだ。だから転職した。」 </span></p> <p>という話をしていてこれが原点です、めっちゃ印象的でした(かれこれ4〜5年前です)。 それまでは社会を知らなすぎてこの視点が私にはまるでなかったのです(当時社会人8年目くらい)、 <code>pip install 社会性</code> が実行された瞬間ですくぅぅ。</p> <p>もちろん人事(あるいは採用コストを管理されてる方々)の側としては「いや、技術的にめっちゃすごいおじさんエンジニアXさんの年収がY円なのに、それより技術的に優れていない(私から見れば優れているんだけど&色々パクらせてもらった)君(インフラクラウド師匠)の給料を上げる必要なくね?」と主張できるわけです。 いや、人事殿らの意見わかる、実に筋が通っている。その通りや!</p> <p>この場合、誰が(お若い方の給料を上げるという観点で)悪いのかというと私は技術的に凄いおじさんエンジニア氏らだと考えます。 じゃぁどうすべきなのか?犯人探しや批判は猿🐵でもできるので、私のこの状況における解決策とボヤキを書くと</p> <p>「技術的に凄いおじさんエンジニアはお若い方のお賃金上昇に迷惑をかけないように、会社の売上を上げる(あるいはコストを下げる)ような成果を出して、ある程度の高給を交渉して取っていく必要がある。 はぁ、おじさんエンジニアは辛いな、技術的なことだけ考えていてぇ」</p> <p>となるわけです。 技術的に凄いおじさんエンジニア、ヤッテイキたい。</p> <p>そんなポエムでした。 ・・・次回もまた、見てくれよな!</p> shinichi-takayanagi 【翻訳】機械学習の技術的負債の重箱の隅をつつく (後編) hatenablog://entry/26006613594154710 2020-07-06T10:00:00+09:00 2020-07-06T10:00:02+09:00 ホクソエムサポーターの白井です。 今回は前回 【翻訳】機械学習の技術的負債の重箱の隅をつつく (前編) の続きを紹介します。 blog.hoxo-m.com ※この記事は、Matthew McAteer氏によるブログ記事Nitpicking Machine Learning Technical Debtの和訳です。原著者の許可取得済みです。 後編では、コードのアンチパターンなど、エンジニアには身近な話題で、前編と比較して実践しやすいコンテンツも多いと思います。 Nitpicking Machine Learning Technical Debt (機械学習の技術的負債の重箱の隅をつつく) Pa… <p>ホクソエムサポーターの白井です。 今回は前回 <a href="https://blog.hoxo-m.com/entry/2020/06/21/190056">【翻訳】機械学習の技術的負債の重箱の隅をつつく (前編)</a> の続きを紹介します。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.hoxo-m.com%2Fentry%2F2020%2F06%2F21%2F190056" title="【翻訳】機械学習の技術的負債の重箱の隅をつつく (前編) - 株式会社ホクソエムのブログ" 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://blog.hoxo-m.com/entry/2020/06/21/190056">blog.hoxo-m.com</a></cite></p> <p>※この記事は、<a href="https://matthewmcateer.me/">Matthew McAteer</a>氏によるブログ記事<a href="https://matthewmcateer.me/blog/machine-learning-technical-debt/">Nitpicking Machine Learning Technical Debt</a>の和訳です。原著者の許可取得済みです。</p> <p>後編では、コードのアンチパターンなど、エンジニアには身近な話題で、前編と比較して実践しやすいコンテンツも多いと思います。</p> <ul class="table-of-contents"> <li><a href="#Nitpicking-Machine-Learning-Technical-Debt-機械学習の技術的負債の重箱の隅をつつく">Nitpicking Machine Learning Technical Debt (機械学習の技術的負債の重箱の隅をつつく)</a></li> <li><a href="#Part5-MLコードにある共通のダメなパターン">Part5 MLコードにある共通のダメなパターン</a></li> <li><a href="#Part6-構成の負債-退屈だけど修正は簡単">Part6 構成の負債 (退屈だけど修正は簡単)</a></li> <li><a href="#Part7-解決への夢を打ち砕く実世界">Part7 解決への夢を打ち砕く実世界</a></li> <li><a href="#Part8-奇妙なメタセクション">Part8 奇妙なメタセクション</a><ul> <li><a href="#サニティーチェック-Sanity-Checks">サニティーチェック (Sanity Checks)</a></li> <li><a href="#再現性-Reproducibility">再現性 (Reproducibility)</a></li> <li><a href="#プロセス管理-Process-management">プロセス管理 (Process management)</a></li> <li><a href="#文化的負債-Cultural-debt">文化的負債 (Cultural debt)</a></li> </ul> </li> <li><a href="#Part9-技術的負債のリトマス試験">Part9 技術的負債のリトマス試験</a></li> <li><a href="#The-25-Best-Practices-in-one-place">The 25 Best Practices in one place</a></li> <li><a href="#おわりに参考資料">おわりに・参考資料</a></li> </ul> <h1 id="Nitpicking-Machine-Learning-Technical-Debt-機械学習の技術的負債の重箱の隅をつつく">Nitpicking Machine Learning Technical Debt (機械学習の技術的負債の重箱の隅をつつく)</h1> <p><em>再注目されているNeurIPS2015の論文を再読する (そして、2020年のための、より関連した25のベストプラクティス)</em></p> <h1 id="Part5-MLコードにある共通のダメなパターン">Part5 MLコードにある共通のダメなパターン</h1> <p>技術的負債論文の 「アンチパターン」の章は前の章よりも実践可能でした。 このパートは前パート(フィードバックループ)よりもより高レベルな抽象的なパターンを扱います。</p> <blockquote><p>(これは実際は技術的負債論文の情報を表にしたもので、さらにその修正方法に関するアドバイスへのハイパーリンクをつけています。</p> <p>この表は、著者らがML特有のコードの臭いとアンチパターンについて議論したもので、もしかしたらちょっとやりすぎかもしれませんが、 すべてはじめに着手すべき標準的なソフトウェアエンジニアリングのアンチパターンに相当しています。)</p></blockquote> <table> <thead> <tr> <th style="text-align:center;"> 設計の失敗 </th> <th> 種類 </th> </tr> </thead> <tbody> <tr> <td style="text-align:center;"> <a href="https://refactoring.guru/smells/feature-envy">機能の横恋慕 (Feature Envy)</a> </td> <td> コードの臭い </td> </tr> <tr> <td style="text-align:center;"> <a href="https://refactoring.guru/smells/data-class">データクラス (Data Class)</a> </td> <td> コードの臭い </td> </tr> <tr> <td style="text-align:center;"> <a href="https://refactoring.guru/smells/long-method">長すぎるメソッド (Long Method)</a> </td> <td> コードの臭い </td> </tr> <tr> <td style="text-align:center;"> <a href="https://refactoring.guru/smells/long-parameter-list">長すぎるパラメーターリスト (Long Parameter List)</a> </td> <td> コードの臭い </td> </tr> <tr> <td style="text-align:center;"> <a href="https://refactoring.guru/smells/large-class">巨大なクラス (Large Class)</a> </td> <td> コードの臭い </td> </tr> <tr> <td style="text-align:center;"> <a href="https://en.wikipedia.org/wiki/God_object">神オブジェクト (God Class)</a> </td> <td> アンチパターン </td> </tr> <tr> <td style="text-align:center;"> <a href="http://antipatterns.com/arch_cat.htm">マルチツールナイフ(Swiss Army Knife)</a> </td> <td> アンチパターン </td> </tr> <tr> <td style="text-align:center;"> <a href="https://weekly-geekly.github.io/articles/251089/index.html">機能分解 (Functional Decomposition)</a> </td> <td> アンチパターン </td> </tr> <tr> <td style="text-align:center;"> <a href="https://en.wikipedia.org/wiki/Spaghetti_code">スパゲッティコード (Spaghetti Code)</a> </td> <td> アンチパターン </td> </tr> </tbody> </table> <p>(訳注: 日本語訳はwikipediaの<a href="https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%B3%E3%83%81%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3">アンチパターン</a>、<a href="https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E8%87%AD%E3%81%84">コードの臭い</a>と<a href="https://qiita.com/y_ujitoko/items/db5f05d9d8bab493f181">ソフトウェア開発 アンチパターンのまとめ</a> を参考にした)</p> <p>これらのパターンはその90%以上が、MLコードのモデル更新ではなく“モデルを維持している”部分に関連するものです。 これは、<a href="https://www.kaggle.com/c/recursion-cellular-image-classification">Kaggleコンペ</a>のほとんどの参加者は存在すると考えたことのないような配管 (plumbing) です。 細胞のセグメンテーションを解くのはずっと簡単です、モデルの入力を<a href="https://www.tecan.com/shop">Tacan Evo camera</a>とつなぐコードに大半の時間を浪費しない限りは。 (訳注: 上記Kaggleコンペのリンクは細胞のセグメンテーションコンペ)</p> <blockquote><p>Best practice #9 定期的なコードレビューを使おう (そして・またはコードチェックツールを使おう)</p></blockquote> <p>最初のMLのアンチパターンは 「グルーコード (glue code)」です。 グルーコードは汎用的な目的のパッケージから持ってきたデータやツールを、自分の持っているとても限定されたモデルにフィットしたい時に書いたコード全てを指します。 <a href="https://www.rdkit.org/">RDKit</a>のようなパッケージでなにかしようとしたことのある人なら誰もが、私の言っている意味がわかるでしょう。</p> <p>基本的に、 <code>utils.py</code> に押しやったものは大抵、これに該当します (誰もがやったことがあるはずです)。 これらは (できれば) もっと具体的なAPIエンドポイントに依存性をリパッケージして修正すべきです。</p> <p><figure class="figure-image figure-image-fotolife" title="私たちはみな、自慢できない utils.py を持っているものです。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200705/20200705190057.png" alt="f:id:sh111h:20200705190057p:plain" title="f:id:sh111h:20200705190057p:plain" class="hatena-fotolife" itemprop="image"></span></figure> <small>私たちはみな、自慢できない utils.py を持っているものです。</small></p> <blockquote><p>Best practice #10 汎用的な目的の依存関係は特定のAPIにリパッケージしよう</p></blockquote> <p>「パイプラインジャングル (pipeline jungles)」は少し厄介で、グルーコードが大量に蓄積された場所を指します。 すべてのちょっとした新しいデータに対する変換が、醜い混合物 (amalgam) として積み重なった場所です。 グルーコードとは異なり、著者らは、スクラッチからのコードベースを手放して、再設計するように強く薦めています。</p> <p>今や他の選択肢があると言いたいところですが、グルーコードがパイプラインジャングルに変貌した時には、<a href="https://eng.uber.com/michelangelo-machine-learning-platform/">Uber's Michelangelo</a>のようなツールですらむしろ問題の一部になりえます。</p> <p>もちろん、著者らのアドバイスのメリットは、変換したコードを興奮するようなかっこいい名前の新しいプロジェクトにみせられるということです。 トルーキンの参照が義務付けられている「Balrog」のように。 (<a href="https://venturebeat.com/2016/10/05/why-palantir-is-silicon-valleys-most-questionable-unicorn/">プロジェクトの名前の不幸な暗喩を無視しているのはPalantirのドメインだけではありません</a>。 あなたもまた自由です。)</p> <p>(訳注: Barlog(<a href="https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%AB%E3%83%AD%E3%82%B0">バルログ</a>) も Palantir(<a href="https://ja.wikipedia.org/wiki/%E3%83%91%E3%83%A9%E3%83%B3%E3%83%86%E3%82%A3%E3%83%BC%E3%82%A2">パランティーア</a>)もトルーキンの<a href="https://ja.wikipedia.org/wiki/%E6%8C%87%E8%BC%AA%E7%89%A9%E8%AA%9E">指輪物語</a> に登場する。 Palantirは物語において「見る石」であり、危険な道具である。)</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/s/sh111h/20200706/20200706083622.jpg" alt="f:id:sh111h:20200706083622j:plain" title="f:id:sh111h:20200706083622j:plain" class="hatena-fotolife" itemprop="image"></span></figure><small>ソフトウェアの形</small></p> <blockquote><p>Best practice #11 トップダウンの再設計・再実装によってパイプラインジャングルを取り除こう</p></blockquote> <p>考えたくない話題、試験的コード (experimental code)について見ていきましょう。 あなたは後々のために試験的コードを保存しようと当然思うはずです。 使っていない関数や参照していないファイルにおいておけば、問題ないと思ったでしょう。 あいにく、このようなものは後方互換性の管理が頭痛のタネになる理由のひとつになります。</p> <p>Tensorflowを深く掘り下げたことのある人であれば覚えがあるでしょう、<a href="https://github.com/tensorflow/tensorflow/tree/master/tensorflow/python/keras/legacy_tf_layers">一部だけ吸収されたフレームワーク</a>や、<a href="https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/data/experimental/benchmarks/parallel_interleave_benchmark.py">試験的コード</a>、<a href="https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/ops/gradients_impl.py#L322">のちの日に気が付くエンジニアのために残された 未完了の「TODO」 コード</a>に。 Tensorflowコードの謎の失敗をデバッグしようとした時、あなたは偶然これらを見つけたのではないでしょうか。 これは間違いなく、Tensorflow 1.Xと2.Xの間にある互換性の遅れを浮き彫りにしています。</p> <p>自分自身のためにも、5年もコードベースの剪定から逃げるのはやめましょう。 実験をつづけても、他のコードから実験的コードを隔離する基準を定めましょう。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200705/20200705190150.png" alt="f:id:sh111h:20200705190150p:plain" title="f:id:sh111h:20200705190150p:plain" class="hatena-fotolife" itemprop="image"></span> <small> Google Researchの散らかった (少なくとも上位5位に入る) githubレポジトリ、しかも公開されているものです。 そして、READMEは同じぐらい詳細とは程遠く、実際は真逆です。 </small></p> <blockquote><p>Best practice #12 定期的な点検を定めて、コードを取り除くための基準をつくろう、もしくはビジネスに重大な影響を及ぼすコードとは程遠いディレクトリやディスクにMLコードをおこう</p></blockquote> <p>古いコードといえば、ソフトウェアエンジニアリングが以前からなにをやってきたか知っていますか? それは本当に素晴らしい抽象化です! 関係データベースの概念からウェブページの表示まで、ありとあらゆることです。 応用カテゴリ理論には、コードを整理するためのベストなやり方を発見することに執念を燃やしている分野があります。</p> <p>応用カテゴリ理論が、何にいまだ悩みつづけているか知っていますか? そう、機械学習のコード整理です。 ソフトウェアエンジニアリングは壁に抽象的なスパゲティーを投げて、なにが刺さるかを見る経験がありました。</p> <p>機械学習? <a href="https://static.googleusercontent.com/media/research.google.com/es/us/archive/mapreduce-osdi04.pdf">Map-Reduce</a> (これは関係データベースのように印象的な概念ではない) や Async Parameter servers (どうやってすべきなのか誰も同意できない) や sync allreduce (多くの場合大失敗している) は別として、私たちが見せられるものはありません。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200705/20200705190240.png" alt="f:id:sh111h:20200705190240p:plain" title="f:id:sh111h:20200705190240p:plain" class="hatena-fotolife" itemprop="image"></span> <small>先ほど言及した、Sync AllReduce serverのアーキテクチャの大まかな概要 (かなり簡略化したバージョン)</small></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200705/20200705190302.png" alt="f:id:sh111h:20200705190302p:plain" title="f:id:sh111h:20200705190302p:plain" class="hatena-fotolife" itemprop="image"></span> <small>先ほど言及した、Async parameter serverのアーキテクチャの大まかな概要 (少なくともそのバージョンのひとつ)</small></p> <p>実際、ランダムネットワークの研究をしているグループとPytorchがニューラルネットワークのノードがいかに流動的であるかを宣伝している間に、 機械学習は抽象的なスパゲティーを窓から綺麗に投げ捨ててしまったのです! 著者らは、この問題が時間と共にとても悪くなっていることに気づいていたとは思いません。 私のおすすめ? 大まかな抽象化について、有名な文献を読みましょう。あと、プロダクションのコードにはPytorchを使わないほうがいいと思います。</p> <blockquote><p>Best practice #13 時間と共に強固になる抽象化を最新の状態にしておこう</p></blockquote> <p>私は今まで、お気に入りのフレームワークがあってほとんどの問題に対してそれをつかうのを好むシニアな機械学習エンジニアとたくさん会ってきました。 私はまた、彼らのお気に入りのフレームワークを新しい問題に適用するとき、そのフレームワークが破綻したり機能的に区別できない他のフレームワークに変更されたりするのを目の当たりにした同じエンジニアを見てきました。</p> <p>これは特に、分散させて計算するタイプの機械学習処理を行うチームに多く見られます。 私にははっきりさせておきたいことがあります。</p> <blockquote><p><strong>MapReduceは別として、なにかひとつのフレームワークに執着しすぎるのは避けるべきです。</strong></p> <p><strong>あなたの “シニア(先輩)” 機械学習エンジニアが「<a href="https://eng.uber.com/michelangelo-machine-learning-platform/">Micheloangelo</a>が最高で、全てを解決できる」と信じているならば、彼らはそれほどシニアではないかもしれません。</strong></p> <p><strong>MLエンジニアリングは成熟してきた一方で、まだ相対的には早熟なのです。</strong></p> <p><strong>本当の “シニア” なシニアMLエンジニアは、フレームワークにとらわれないワークフローを重要視するでしょう、なぜなら、フレームワークの多くは寿命が長くないと知っているからです。</strong> ⚰️</p></blockquote> <p>さて、前のセクションで機械学習における技術的負債の区別できるシナリオや質について言及しましたが、技術負債論文では機械学習開発のための大まかなアンチパターンの例も提供しています。</p> <p>これを読んでいる人の多くは、コードの臭い (code-smells) というフレーズを聞いたことがあるでしょう。 <a href="https://pypi.org/project/good-smell/">good-smell</a>や<a href="https://pypi.org/project/autopep8/">Pep8-auto-checking</a> (さらにはみんながPythonコードに使っている、新しい自動フォーマッタ <a href="https://black.readthedocs.io/en/stable/">Black</a>) のようなツールを使っているかもしれません。 正直に言うと、 「コードの臭い」という用語が好きではありません。</p> <p>「臭い」は常に微妙なものを暗に意味しているようにみえますが、次のセクションに書かれたパターンは逆にとても露骨なものです。 それにもかかわらず、著者らは負債を大まかに示すコードの臭いの種類を少しだけ並べています (普通のコードの臭いの種類を超えて)。 なぜか、“コードの臭い”のセクションの途中からコードの臭いを記載し始めています。</p> <p><strong>「プレーンなデータ」の臭い (The "Plain data" smell)</strong></p> <p>numpy float形式のデータを大量に扱うコードをもっているかもしれません。 RNAの読み込み回数がベールヌイ分布からのサンプルを表現しているのかどうか、floatが数値の対数かどうかといった、データの性質を保持する情報がほとんどないことがあるかもしれません。</p> <p>技術的負債論文では言及されていませんが、これはPythonのtypingが助けられる領域のひとつです。 不必要なfloatや、正確すぎるfloatの使用を避けることが大いに役に立ちます。 繰り返しますが、組み込みの <code>Decimal</code> や <code>Typing</code> パッケージを使うことはとても助けになります (コードを誘導するだけでなく、CPUのスピード向上にもなります)。</p> <blockquote><p>Best practice #14 <code>Typing</code> や <code>Deimal</code> のようなパッケージを使って、すべてのオブジェクトに対して 'float32' を使うのはやめよう</p></blockquote> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200705/20200705190349.png" alt="f:id:sh111h:20200705190349p:plain" title="f:id:sh111h:20200705190349p:plain" class="hatena-fotolife" itemprop="image"></span> <small> 過半数のハッカソンコード、そして残念なことにベンチャーキャピタルに資金提供されている「AI」スタートアップのコードの中身はこんな感じです。</small></p> <p><strong>「試作品」の臭い(The "Prototyping" smell)</strong></p> <p>ハッカソンに参加した人なら知っているでしょうが、24時間以内に寄せ集めでつくられたコードはこんな形をしています。 これは、先ほど言及した実際には誰にも使われない試験的コードともつながっています。 生物学データのための <a href="https://github.com/KrishnaswamyLab/PHATE">PHATE次元削減ツール</a>を試したくて興奮するかもしれませんが、コードをきれいにするか、投げ捨てるかしてください。</p> <blockquote><p>Best practice #15 進行中のものを同じディレクトリに置かない。片付けるか、きれいにしよう。</p></blockquote> <p><strong>「多言語」の臭い (The "Muitl-Language" smell)</strong></p> <p>プログラミング言語の種類について言うと、多言語のコードベースは技術的負債が複数積み重なるように、その積み重なりがより早くなるよう振る舞います。 もちろん、すべての言語にはそれぞれ利点があります。 Pythonはアイデアを素早く構築するためには良いです。 Javascriptはインタフェースに向いています。 C++はグラフィックに最適で、計算もはやいです。 PHPは、うーん、特にこれといったものはないです。 GolangはKubernetesを使う (もしくはGoogleで働く) には便利です。</p> <p>しかし、これらの言語でお互いに会話するとして、エンドポイントが壊れたり、メモリリークによって、うまくいかない点がたくさんあるでしょう。 少なくとも機械学習では、言語の間で似たセマンティクスを持つSparkやTensorflowのようなツールキットは少ないです。 もし複数の言語をどうしても使わなくてはいけなくても、少なくとも2015年以降はそれが可能になってはいます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200705/20200705190432.png" alt="f:id:sh111h:20200705190432p:plain" title="f:id:sh111h:20200705190432p:plain" class="hatena-fotolife" itemprop="image"></span> <small>C++のプログラマーがPythonに転向したら。この人が書いたレポジトリ全体のコードを思い描いてみてください</small></p> <blockquote><p>Best practice #16 エンドポイントが説明されていることを確認し、言語間で似たような抽象度を持つフレームワークを使用しよう</p></blockquote> <p>(これをコードの臭いと呼ぶのは奇妙な選択です、普通のコードの臭いを基準としても、これはとても露骨なパターンなので。)</p> <h1 id="Part6-構成の負債-退屈だけど修正は簡単">Part6 構成の負債 (退屈だけど修正は簡単)</h1> <p>技術的負債論文の「構成の負債」の章はおそらく楽しいものではないですが、そこに記述されている問題は簡単に修正できます。</p> <p>基本的に、機械学習のパイプラインについて、すべての調整可能で構成可能な情報を一箇所にまとめて、確かめる習慣であり、これはいうなればあなたの2番目のLSTMレイヤーのユニット数がどう設定されているか調べるために複数の辞書を探す必要のない状況のことです。 設定ファイルをつくる習慣を持っていても、すべてのパッケージと技術があなたを悩ませないわけではありません。</p> <p>一般的な方針は別として、技術的負債論文のこの部分は詳細を十分に深堀りしていません。 技術的負債論文の著者らはCaffeのようなパッケージを使うのに慣れていたのではないかと疑っています (Caffeのプロトコルバッファーで設定ファイルを設定することは客観的に見てバグだらけで恐ろしいものでした)。</p> <p>個人的には、もし設定ファイルを設定したいのであれば、<a href="https://www.tensorflow.org/"><code>tf.Keras</code></a> や <a href="https://chainer.org/"><code>Chainer</code></a> のようなフレームワークを使うことを提案します。 クラウドサービスはバージョンの構成管理機能をもっていますが、それ以外では、config.jsonか パラメーターフラグを使う準備をする必要があります。</p> <blockquote><p>Best practice #17 ファイルパスやハイパーパラメーター、レイヤーの種類、レイヤーの順番や他の設定が一つの場所から設定できるか確かめよう</p></blockquote> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200705/20200705190510.png" alt="f:id:sh111h:20200705190510p:plain" title="f:id:sh111h:20200705190510p:plain" class="hatena-fotolife" itemprop="image"></span> <small> 設定ファイルの素晴らしい例。 技術的負債論文はもっと例をあげたり、当時存在した設定ファイルのガイドについて指摘して欲しかったと切に思います。</small></p> <p>もしコマンドラインをつかってこれらの設定を調整したいなら、Argparseのかわりに<a href="https://click.palletsprojects.com/en/7.x/">Click</a>のようなパッケージをつかってみましょう。</p> <h1 id="Part7-解決への夢を打ち砕く実世界">Part7 解決への夢を打ち砕く実世界</h1> <p>7章は、技術的負債の管理の多くが、現実世界の絶えない変化を扱っているという事実のために準備することであると認めています。 例えば、モデルの出力を分類に変換するために閾値決定が必要なモデルや、あるいは真(True)または偽(False)のBool値を直接選ぶことができるモデルがあるかもしれません。</p> <p>生物学的もしくは健康データを扱うグループや企業は、診断の基準が急速に変化することをよく知っています。 <em>特にベイズ機械学習をしているときは</em>、あなたの扱っている閾値が永遠に続くとは仮定してはいけません。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200705/20200705190555.jpg" alt="f:id:sh111h:20200705190555j:plain" title="f:id:sh111h:20200705190555j:plain" class="hatena-fotolife" itemprop="image"></span> <small> オンライン学習アルゴリズムによる決定境界……デプロイから12分後 </small></p> <blockquote><p>Best practice #18 モデルの現実世界でのパフォーマンスと、決定境界を常に監視しよう</p></blockquote> <p>このセクションではリアルタイムの監視の重要性を強調しています、私は間違いなくこれの良さがわかります。 どのようなことを監視するかについて、この論文は包括的なガイドではありませんが、いくつかの例を与えています。</p> <p>ひとつは観測した観測したラベルの要約統計量と、予測ラベルの要約統計量を比較することです。 完璧ではありませんが、小さな動物の体重をチェックするようなものです。 何かがすごく間違っていれば、その問題をすぐに警告できます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200705/20200705190849.png" alt="f:id:sh111h:20200705190849p:plain" title="f:id:sh111h:20200705190849p:plain" class="hatena-fotolife" itemprop="image"></span> <small> 数えきれないほどたくさんのツールがあります。 近頃はだれもが、その母親さえもが、監視ツールのスタートアップを作っています。 ここでは、他に比べてバグが少ないSageMakerとWeights &amp; Biasesを例に挙げてみました。</p> <p>(訳注: それぞれのリンクは、<a href="https://aws.amazon.com/jp/blogs/news/amazon-sagemaker-model-monitor-fully-managed-automatic-monitoring-for-your-machine-learning-models/">Amazon SageMaker Model Monitor</a> と <a href="https://www.wandb.com/">Weights &amp; Biases</a>) </small></p> <blockquote><p>Best practice #19 予測ラベルの分布が、観測ラベルの分布と似ているか確認しよう</p></blockquote> <p>あなたのシステムが現実世界のなにかを意思決定しているなら、レート制限(何某かの呼び出し回数制限)をしたいと思うでしょう。 たとえシステムが株の入札のための数百万ドルを任せられているものではなく、細胞培養インキュベーターのなにかが正しくないと警告を出すだけのシステムであっても、 単位時間あたりの行動制限を設定していないことでのちのち後悔することになります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200705/20200705190909.png" alt="f:id:sh111h:20200705190909p:plain" title="f:id:sh111h:20200705190909p:plain" class="hatena-fotolife" itemprop="image"></span> <small> ML製品の初期の頭痛のタネのいくつかは、レート制限をしていないシステムで起こるでしょう。 </small></p> <blockquote><p>Best practice #20 機械学習システムによってもたらされる意思決定に制限を設けよう</p></blockquote> <p>MLパイプラインが消費している、データの上位生産者(データを提供してくれている人・会社たち)によるどんな変化も、心に留めておきたいところです。 例えば、人間の血液やDNAサンプルの機械学習を走らせているどんな企業も、当然ながら、それらのサンプルがすべて標準化された手順で収集されていることを確認したいと考えています。 もしサンプルの多くが特定の集団からきたものであれば、企業は分析がゆがんでいないか確かめなければいけません。 もし培養された人間の細胞のシングルセルシーケンシングを扱っているのであれば、薬剤が作用したために死滅したがん細胞と、インターンが誤って培養した細胞を脱水させてしまったために死滅したがん細胞を混同していないことを確認する必要があります。</p> <p>著者らは、理想的には、人間が対応可能でない時も、これらの変化に反応できるシステム (例えばログをつける、調節を自ら止める、決定閾値を変更する、技術者や修復可能な人に警告する) が必要だと言っています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200705/20200705190927.png" alt="f:id:sh111h:20200705190927p:plain" title="f:id:sh111h:20200705190927p:plain" class="hatena-fotolife" itemprop="image"></span> <small> 今は笑えるが、このようなミスは、思った以上に危険にさらされているのかもしれません。</p> <p>(訳注: DNA採取で用いる綿棒が汚染されていたことで、複数の事件で同一のDNAが検出され、架空の犯人<a href="https://ja.wikipedia.org/wiki/%E3%83%8F%E3%82%A4%E3%83%AB%E3%83%96%E3%83%AD%E3%83%B3%E3%81%AE%E6%80%AA%E4%BA%BA">ハイルブロンの怪人</a>として騒がれた事件のこと。 <a href="https://www.spiegel.de/international/germany/q-tip-off-police-fear-serial-killer-was-just-dna-contamination-a-615608.html">上記画像の記事リンク</a>。) </small></p> <blockquote><p>Best practice #21 入力データの背後にある仮説を確かめよう</p></blockquote> <h1 id="Part8-奇妙なメタセクション">Part8 奇妙なメタセクション</h1> <p>技術的負債論文の最後から2番目の章は他の領域について言及しています。 著者らは以前、技術的負債の種類として抽象化の失敗について言及してました、そしてどうも、論文の最初の7章の中に技術的負債の種類に収められなかったことにまで及んでいるようです。</p> <h3 id="サニティーチェック-Sanity-Checks">サニティーチェック (Sanity Checks)</h3> <p>データのサニティーチェック(訳注: <a href="https://eow.alc.co.jp/search?q=sanity+check">プログラムのソースコードの整合性・正当性を検査すること</a>)をすることは非常に重要です。 新しいモデルを学習しているとき、モデルが少なくともデータのカテゴリーの一つの種類に過学習する可能性があることを確かめたいでしょう。 もし学習が収束しないなら、ハイパーパラメーターを調整する前に、データはランダムなノイズでないか確認した方がいいかもしれません。 著者らはこのように具体的ではなかったですが、言及するのに良いテストだと考えました。</p> <blockquote><p>Best practice #22 モデルが過学習する可能性があることを確認し、データの全てがノイズであったり、無信号でないことを確かめよう</p></blockquote> <h3 id="再現性-Reproducibility">再現性 (Reproducibility)</h3> <p>再現性。研究チームにいるあなた方の多くがこれに遭遇すると思います。 seedが設定されていないのコードや、故障中のノートブック、パッケージバージョンなしのレポジトリをみたことがあるでしょう。 技術的負債論文以降、何人かが、再現性チェックリストをつくっています。 <a href="https://www.cs.mcgill.ca/~jpineau/ReproducibilityChecklist.pdf">ここに、4ヶ月前 hacker newsが特集したかなり良いリストがあります。</a></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200705/20200705190945.png" alt="f:id:sh111h:20200705190945p:plain" title="f:id:sh111h:20200705190945p:plain" class="hatena-fotolife" itemprop="image"></span> <small> これはとても素晴らしいチェックリストの一つで、私も使っており、共に働くチームにも使うよう勧めています。</p> <p>(訳注: このチェックリストのpdfのリンクは<a href="https://www.cs.mcgill.ca/~jpineau/ReproducibilityChecklist.pdf">ここ</a>) </small></p> <blockquote><p>Best practice #23 研究のコードを公開するときは、再現性に関するチェックリストを使おう</p></blockquote> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200705/20200705191004.png" alt="f:id:sh111h:20200705191004p:plain" title="f:id:sh111h:20200705191004p:plain" class="hatena-fotolife" itemprop="image"></span> <small> アカデミアからのコードを再現したことのある、産業側の人間なら、私の言っている意味がわかると思います。 </small></p> <h3 id="プロセス管理-Process-management">プロセス管理 (Process management)</h3> <p>今まで議論してきた技術的負債の種類の多くは単一の機械学習モデルについて言及してきましたが、プロセス管理負債はおなじ時間にたくさんのモデルを実行した時に起こります、 1つの遅れたモデルが終わるのを待っている間に、すべてを止めようとは考えないでしょう。 システムレベルの臭いを無視しないのが重要で、また、モデルの実行時間をチェックすることが極めて重要です。 技術的負債論文が書かれて以降、機械学習エンジニアリングは少なくても大まかなシステム設計について考えて改良されてきています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200705/20200705191020.png" alt="f:id:sh111h:20200705191020p:plain" title="f:id:sh111h:20200705191020p:plain" class="hatena-fotolife" itemprop="image"></span> <small> ボトルネックの同定に使われるチャートの一例 </small></p> <blockquote><p>Best practice #24 機械学習モデルのために実行時間を確認・比較する習慣をつけよう</p></blockquote> <h3 id="文化的負債-Cultural-debt">文化的負債 (Cultural debt)</h3> <p>文化的負債はとても厄介な種類の負債です。 著者らは、研究とエンジニアリングの間には時として格差があり、異種混合チームでは負債の返却行為は促しやすいと指摘しています。</p> <p>個人的には、最後のこの部分は好きではありません。 私は、エンジニアディレクターとリサーチディレクターの両方に、最終的に報告する個人がいるチームをたくさん目撃しました。 エンジニアたちに、負債の修正に必要な変更のための権限のない異なる2つの部門にレポーティングさせることは技術的負債の解決策ではありません。 一部のエンジニアが、この技術的負債の矛先になるという意味では、これは解決策です。 そのようなエンジニアは<a href="https://www.linkedin.com/pulse/responsibility-without-authority-how-drive-employees-crazy-fialka/">No Authority Gauntlet Syndrome (NAGS)</a> (訳注: 権力なき挑戦症候群) になり、燃え尽き、最も同情的なマネージャーがバーニングマンに出ている間に、そのエンジニアが達成すべき作業を目標として持っていたマネージャーによって解雇されてしまうのです。 もし異種混合が助けになるなら、チームの <em>全体</em> に渡ることが必要です。</p> <p>(訳注: <a href="https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0%E3%83%9E%E3%83%B3">バーニングマン</a> は8~9月にアメリカで開催される大規模なイベントのこと)</p> <p>それに加え、著者らは、チームや企業文化を語る時に、多くの人と同じ間違いをしていると思います。 特に、文化と価値観をごちゃまぜにしています。 企業やチームにとって向上心のある規則を列挙し、それを文化と呼ぶのは簡単です。 実行するのにMBAを持つ必要はないですが、それは実際の文化というより価値観です。 文化とは、二つの重みのある価値観のどちらかを選ぶよう要求した状況で、最終的に人々が実行することです。</p> <p>これはUberが大きな問題に陥った原因です。 競争力と誠実さの両方が企業の価値観の一部でしたが、最終的には、文化は競争力がなによりも強調されることを要求しました、 例え人事部が絶対的に嫌なやつを会社に留めるために法律を破ることを意味しても。</p> <p>(訳注: Uberの話はおそらく <a href="https://www.susanjfowler.com/blog/2017/2/19/reflecting-on-one-very-strange-year-at-uber">Reflecting on one very, very strange year at Uber</a> で述べられている話)</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200705/20200705191037.png" alt="f:id:sh111h:20200705191037p:plain" title="f:id:sh111h:20200705191037p:plain" class="hatena-fotolife" itemprop="image"></span> <small>悪循環</small></p> <p>技術的負債についてのこのイシューは似たような状況でも起こります。 どれぐらい“メインテイナブル(保守可能な)”コードが必要かについて話すのは簡単です。 しかし、誰もが締め切りで追われ、ドキュメントの執筆がJIRAボードの優先度の中で下がっていくなら、最善の努力をしていても負債は積み重なっていきます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200705/20200705191051.png" alt="f:id:sh111h:20200705191051p:plain" title="f:id:sh111h:20200705191051p:plain" class="hatena-fotolife" itemprop="image"></span> <small>技術的負債の様々な理由</p> <p>(訳注: 「無鉄砲/慎重」と「意図的/不注意」の2つの基準を使った<a href="https://bliki-ja.github.io/TechnicalDebtQuadrant/">技術的負債の四象限</a>) </small></p> <blockquote><p>Best practice #25 (それがどのような形であっても)技術的負債に対処するために、定期的に、交渉の余地のない時間を確保しておこう</p></blockquote> <h1 id="Part9-技術的負債のリトマス試験">Part9 技術的負債のリトマス試験</h1> <p>「負債」の一部は単なるメタファーだということを思い出してください。 どれだけ著者らがより厳格に見せようとしても、それにすぎません。</p> <p>多くの負債と違って、機械学習の技術的負債は測るのが難しいものです。 与えられた時間であなたのチームがどれぐらいはやく動くかはどれぐらい負債をもっているかの指標にはなりません (新卒のプロダクトマネージャーが主張しているようにみえるにもかかわらず)。 指標にかかわらず、著者らは自身に問うための5つの質問を提案しています(ここではわかりやすく言い換えています)</p> <ul> <li>最大のデータ資源を実行する、任意のNeurIPS論文からアルゴリズムを取得するのにどれぐらいかかるか?</li> <li>どのデータ依存性がコードの中で最も(もしくは全く)関わっているか?</li> <li>システムの一部を変更した場合、結果の変化をどれぐらい予測できるか?</li> <li>MLモデルの改良システムはゼロサムかpositive-sumか?</li> <li>ドキュメントはあるか?新人のための立ち上げプロセスではとっかかりになるものがあるか?</li> </ul> <p>もちろん、2015年以降、他の記事や論文がより正確なスコアリングメカニズムを考えようとしています (<a href="https://research.google/pubs/pub46555/">scoring rubics</a>のように)。 ツールのいくつかは、不正確であっても、一眼でわかるスコアリングメカニズムを作成可能で、技術的負債を追跡する助けになります。</p> <p>また、技術的負債のいくつかの解決策になると称賛されている解釈可能なMLツールは多くの進歩を遂げています。 それを心に留めたうえで、改めて <a href="https://christophm.github.io/interpretable-ml-book/">“Interpretable Machine Learning” by Christoph Molnar (オンラインで利用可能)</a> をおすすめします。</p> <h1 id="The-25-Best-Practices-in-one-place">The 25 Best Practices in one place</h1> <p>これまでに言及したベストプラクティスを以下にまとめます。 これよりたくさんあるでしょうが、技術的負債の解消のツールはパレートの法則に従います: 技術的な負債の救済策の20%は、あなたの問題の80%を修正することができます。</p> <ol> <li>解釈可能・説明可能なツールを使おう</li> <li>可能であれば、説明可能な種類のモデルを使おう</li> <li>常に、順番に下流モデルを再学習しよう</li> <li>アクセス鍵、ディレクトリの権限、サービス水準合意をセットアップしよう</li> <li>データのバージョン管理ツールを使おう</li> <li>使っていないファイル、膨大な関係する特徴量を削除し、因果関係推論ツールを使おう</li> <li>データの依存関係を追跡する、無数にあるDevOpsツールのいくつかを使おう</li> <li>モデルの背後にある独立性の仮定を確認しよう (セキュリティーエンジニアの近くで働こう)</li> <li>定期的なコードレビューを使おう (そして・またはコードチェックツールを使おう)</li> <li>汎用的な目的の依存関係は特定のAPIにリパッケージしよう</li> <li>トップダウンの再設計・再実装によってパイプラインジャングルを取り除こう</li> <li>定期的な点検を定めて、コードを取り除くための基準をつくろう、もしくはディレクトリかビジネスの物から遠い場所にコードをおこう</li> <li>時間と共に強固になる抽象化を最新の状態にしておこう</li> <li><code>Typing</code> や <code>Deimal</code> のようなパッケージを使って、すべてのオブジェクトに対して 'float32' を使うのはやめよう</li> <li>進行中のものを同じディレクトリに置かない。片付けるか、きれいにしよう。</li> <li>エンドポイントが説明されていることを確認し、言語間で似たような抽象度を持つフレームワークを使用しよう</li> <li>ファイルパスやハイパーパラメーター、レイヤーの種類、レイヤーの順番や他の設定が一つの場所から設定できるか確かめよう</li> <li>モデルの現実世界でのパフォーマンスと、決定境界を常に監視しよう</li> <li>予測ラベルの分布が、観測ラベルの分布と似ているか確認しよう</li> <li>機械学習システムによってもたらされる意思決定に制限を設けよう</li> <li>入力データの背後にある仮説を確かめよう</li> <li>モデルが過学習する可能性があることを確認し、データの全てがノイズであったり、無信号でないことを確かめよう</li> <li>研究のコードを公開するときは、再現性に関するチェックリストを使おう</li> <li>機械学習モデルのために実行時間を確認・比較する習慣をつけよう</li> <li>(それがどのような形であっても)技術的負債に対処するために、定期的に、交渉の余地のない時間を確保しておこう</li> </ol> <h1 id="おわりに参考資料">おわりに・参考資料</h1> <p>今回翻訳する上で、ブログの元ネタとなる <a href="https://papers.nips.cc/paper/5656-hidden-technical-debt-in-machine-learning-systems.pdf">技術的負債論文</a> と同じ著者で、<a href="https://papers.nips.cc/paper/5656-hidden-technical-debt-in-machine-learning-systems.pdf">技術的負債論文</a> が公開される1年前のNeurIPS2014で公開された論文 (<a href="https://research.google/pubs/pub43146/">Machine Learning:The High-Interest Credit Card of Technical Debt</a>)について、日本語で解説している以下の情報を参考にさせていただきました。</p> <ul> <li><a href="https://atl.recruit-tech.co.jp/blog/2745/">「機械学習:技術的負債の高利子クレジットカード」のまとめ | Advanced Technology Lab</a></li> <li><a href="http://research.nii.ac.jp/~f-ishikawa/work/files/1506-MLdebt.pdf">論文調査報告:Machine Learning:The High-Interest Credit Card of Technical Debt</a></li> <li><a href="http://englishforhackers.com/machine-learning-technical-debt.html">機械学習:技術的負債の高金利クレジットカード – エンジニアの英語学習法</a></li> </ul> <p>ちなみに、日本語の情報としては「High-Interest Credit Card~」のほうが豊富ですが、前編で紹介した、機械学習の技術的負債を象徴する図 (下記) は技術的負債論文が初出です。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200622/20200622074908.png" alt="f:id:sh111h:20200622074908p:plain" title="f:id:sh111h:20200622074908p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>改めて、翻訳の許可をしてくれた原著者の<a href="https://matthewmcateer.me/">Matthew McAteer</a>氏に感謝します。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4873118255/hoxom-22/"><img src="https://m.media-amazon.com/images/I/51FAFJYvbsL._SL160_.jpg" class="hatena-asin-detail-image" alt="仕事ではじめる機械学習" title="仕事ではじめる機械学習"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4873118255/hoxom-22/">仕事ではじめる機械学習</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/%CD%AD%B2%EC%20%B9%AF%B8%B2" class="keyword">有賀 康顕</a>,<a href="http://d.hatena.ne.jp/keyword/%C3%E6%BB%B3%20%BF%B4%C2%C0" class="keyword">中山 心太</a>,<a href="http://d.hatena.ne.jp/keyword/%C0%BE%CE%D3%20%B9%A7" class="keyword">西林 孝</a></li><li><span class="hatena-asin-detail-label">発売日:</span> 2018/01/16</li><li><span class="hatena-asin-detail-label">メディア:</span> 単行本(ソフトカバー)</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> sh111h 【翻訳】機械学習の技術的負債の重箱の隅をつつく (前編) hatenablog://entry/26006613586161214 2020-06-21T19:00:56+09:00 2020-06-22T11:10:45+09:00 ホクソエムサポーターの白井です。 今回は Matthew McAteer氏によるブログ記事Nitpicking Machine Learning Technical Debtの和訳を紹介します。 原著者の許可取得済みです。 Thank you! アメリカの国内ネタも含んでいて、日本語だと理解しにくい箇所もありますが、機械学習の技術的負債をどう対処していくかについて、とても役に立つ記事だと思います。 Nitpicking Machine Learning Technical Debt (機械学習の技術的負債の重箱の隅をつつく) イントロダクション Part1 技術的負債はあなたの予想以上に悪い … <p>ホクソエムサポーターの白井です。 今回は <a href="https://matthewmcateer.me/">Matthew McAteer</a>氏によるブログ記事<a href="https://matthewmcateer.me/blog/machine-learning-technical-debt/">Nitpicking Machine Learning Technical Debt</a>の和訳を紹介します。</p> <p>原著者の許可取得済みです。 Thank you!</p> <p>アメリカの国内ネタも含んでいて、日本語だと理解しにくい箇所もありますが、機械学習の技術的負債をどう対処していくかについて、とても役に立つ記事だと思います。</p> <ul class="table-of-contents"> <li><a href="#Nitpicking-Machine-Learning-Technical-Debt-機械学習の技術的負債の重箱の隅をつつく">Nitpicking Machine Learning Technical Debt (機械学習の技術的負債の重箱の隅をつつく)</a></li> <li><a href="#イントロダクション">イントロダクション</a></li> <li><a href="#Part1-技術的負債はあなたの予想以上に悪い">Part1 技術的負債はあなたの予想以上に悪い</a></li> <li><a href="#Part2-機械学習の漠然とした性質">Part2 機械学習の漠然とした性質</a></li> <li><a href="#Part3-通常の依存関係の頂上にある-データ依存関係">Part3 (通常の依存関係の頂上にある) データ依存関係</a></li> <li><a href="#Part4-イライラさせるほど未定義なフィードバックループ">Part4 イライラさせるほど未定義なフィードバックループ</a></li> <li><a href="#後編に続きます">後編に続きます</a></li> </ul> <h1 id="Nitpicking-Machine-Learning-Technical-Debt-機械学習の技術的負債の重箱の隅をつつく">Nitpicking Machine Learning Technical Debt (機械学習の技術的負債の重箱の隅をつつく)</h1> <p><em>再注目されているNeurIPS2015の論文を再読する (そして、2020年のための、より関連した25のベストプラクティス)</em></p> <p>最近 <a href="https://papers.nips.cc/paper/5656-hidden-technical-debt-in-machine-learning-systems.pdf">Hidden Technical Debt in Machine Learning Systems (Sculley et al. 2015)</a>を読み返しました。 (簡潔に、明確にするため、この投稿では<a href="https://papers.nips.cc/paper/5656-hidden-technical-debt-in-machine-learning-systems.pdf">技術的負債論文</a>とします) この論文は <a href="https://nips.cc/Conferences/2015">NeurIPS 2015</a> で公開されましたが、 Ian GoodFellowによる <a href="https://arxiv.org/pdf/1406.2661.pdf">「Generative Adversarial Network」</a>技術に皆が夢中になっていたので、そこまで注目されませんでした。</p> <p>今日、<a href="https://papers.nips.cc/paper/5656-hidden-technical-debt-in-machine-learning-systems.pdf">技術的負債論文</a>がまた盛り返してきています。 これを書いている間にも、<a href="https://scholar.google.com/scholar?as_ylo=2020&amp;hl=en&amp;as_sdt=2005&amp;sciodt=0,5&amp;cites=2255096949091421445&amp;scipsc=">直近75日のあいだに25の論文が引用しています</a>。 これは、機械学習について私たちが技術的負債について心配しなければいけなレベルにまで来たという意味でしょう。</p> <p>ですが、多くの人々がこの論文を引用しようとしているならば (「機械学習の技術的負債」というフレーズを持つ論文全てを引用しているだけでないならば)、少なくともどの箇所が本質的なもので、どこがそうでないのか認識すべきです。 そのことを念頭に置いて、どの部分が時代遅れなのかを紹介し、また取って代わる新しい方法を示すことは、機械学習に関係する人々全員の時間と手間を大幅に省くことができると考えました。 これが私がこのBLOG POSTを書いた理由です。 急成長中のスタートアップからグーグル (<a href="https://papers.nips.cc/paper/5656-hidden-technical-debt-in-machine-learning-systems.pdf">技術的負債論文</a>の著者の企業) のような大企業で働いてきて、 同じような機械学習の技術的負債による失敗がどこでも行われているのをみているので、私はこの件について執筆する資格があると考えます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200622/20200622074908.png" alt="f:id:sh111h:20200622074908p:plain" title="f:id:sh111h:20200622074908p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p><small> MLコードは小さくごくわずかなブラックボックスであることに注目してください。 この”小ささ”がこの論文の良い部分の一つですが、悲しいことにこの画像を参考に作られた画像はボックスの大きさの違いを考慮しておらず、この”小ささ”という要点を逃しています。 </small></p> <p>この記事は技術的負債論文と関連する点のいくつかを網羅しながら、5年前にはないアドバイスを付け加えています。 そのいくつかのアドバイスはその当時存在しなかったツールの形をとっています。 また、ツールや技術自体は間違いなく存在していたものの、当時の著者らが持ち出さなかったことで見逃してしまったアドバイスもあります。</p> <h1 id="イントロダクション">イントロダクション</h1> <p>技術的負債は、エンジニアが他の何よりもデプロイの速い設計を選んだ時のコスト増加を例えたものです。 技術的負債を修正するのは骨の折れる仕事です。 「早く動いて、破壊せよ (Move fast and break things)」から「ああ大変だ、急ぎすぎたから片付けなきゃ (Oh no, we went too fast and gotta clean some of this up)」に方向転換です。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200622/20200622075510.png" alt="f:id:sh111h:20200622075510p:plain" title="f:id:sh111h:20200622075510p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p><small> キャッチーでなくとも、Mark Zuckerburgが二つ目のスローガンに口を挟まなかった理由が私にはわかります。 </small></p> <p>(訳注: “Move fast and break things” はFacebookのMotto。その後 “Move fast with stable infrastructure” に変更した。 (<a href="https://en.wikipedia.org/wiki/Facebook,_Inc.#History">wikipedia</a>より) )</p> <p>ソフトウェアにおける技術的負債は悪いものですが、MLシステムの技術的負債は <em>もっと悪い</em> と論文の著者らは強く主張しています。 技術的負債論文はMLにおける技術的負債の種類と、いくつかの解決策を紹介しています (いろいろな種類のゴミ箱があるのと同じで、いろいろなクソコードに対し、いろいろな手法が必要です🚮) <a href="https://papers.nips.cc/paper/5656-hidden-technical-debt-in-machine-learning-systems.pdf">技術的負債論文</a>は、元は人々の注意を引きつけるための意見記事であったことを考えると、<strong>論文中のいくつかのアドバイスはもはや関係なかったり、現在ではよりよい解決策があることを覚えておくのが重要です。</strong></p> <h1 id="Part1-技術的負債はあなたの予想以上に悪い">Part1 技術的負債はあなたの予想以上に悪い</h1> <p>みなさんはもう、技術的負債についてご存知でしょう。 技術的負債論文ははじめに、技術的負債の解消というものがコードに新しい機能を追加することを意味しないことを明確にしています。 技術的負債の解消はユニットテストを書いたり、可読性を向上させたり、ドキュメントを追加したり、未使用の部分を取り除いたりする、華やかさにかけるタスクであり、将来の開発が楽になるためのタスクです。 まあ、標準的なソフトウェアエンジニアリングは機械学習エンジニアリングで必要なスキルの一部なので、より一般的なソフトウェアエンジニアリングの技術的負債は、MLの技術的負債の一領域にすぎません。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200622/20200622075741.png" alt="f:id:sh111h:20200622075741p:plain" title="f:id:sh111h:20200622075741p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p><small> 言い換えてはいますが、(Sculley et al. 2015) が言っていることとほぼ同じです。 </small></p> <h1 id="Part2-機械学習の漠然とした性質">Part2 機械学習の漠然とした性質</h1> <p>技術的負債論文の、イントロダクションの後の章は、機械学習モデルの漠然とした性質が、どのように技術的負債の扱いを難しくしているかに続きます。 技術的負債を防いだり直したりするための大部分は、コードが適切に整理され、モジュールとして分離されているかを確かめることです。 正確なルールや、コーディングでは細かい指定が難しい場合に、私たちは機械学習を使います。 データを出力するためのルールをハードコーディングする代わりに、多くの場合、アルゴリズムにデータと出力を与えてルールを出力しようとしています(時にはそれさえしないことがあります)。 その際、私たちは分離や整理に必要なルールが何なのかすらわからないのです。</p> <blockquote><p>Best practice #1 解釈可能・説明可能なツールを使おう</p></blockquote> <p>ここで、もつれた (Entanglement) 問題が出てきます。 要するに、もしモデルのどこかを変更すると、全体のパフォーマンスを変えてしまう恐れがあるということです。 例えば、個人の健康記録について100の特徴量を持つモデルに対して、(例えば、大麻を吸ったことがあるかをヒアリングしてデータ化するといった話です)101個目の特徴量を加えることを考えます。 全てがつながっているのです! まるで、カオスシステムを扱っているようなものです。(皮肉なことに、<a href="https://web.stanford.edu/~yplu/DynamicOCNN.pdf">何人かの数学者はニューラルネットワークを二重振り子や気象システムのようにカオスなアトラクターとして表現しようとしています</a>)</p> <p><img src="https://bbabenko.github.io/assets/posts/basic_cnn/hyperparameters.gif" alt="" /></p> <p><small> 一本の紐(調整パラメータ)をチューニングしていくと(実際は何百本もの紐がありもつれています)、全体がつながっているのでうっかりブラインドを完全に消すこともできるわけです (the blinds can go OUT the window too)。 (訳注: go out the windowは「完全に消えてなくなる」という口語表現) </small></p> <p>アンサンブルモデルや高次元可視化ツールを用いてこの問題を修正できる可能性について著者らは示していますが、もしアンサンブルモデルの出力が相関していたり、データが <em>超</em> 高次元である場合は不十分です。 解釈可能なMLとして提案されるものの多くは少し曖昧です。 それを踏まえた上で、私は<a href="https://ai.facebook.com/blog/hiplot-high-dimensional-interactive-plots-made-easy/">Facebookの高次元可視化ツール</a>をおすすめします。 また、解釈可能な機械学習についての最高の資料<a href="https://christophm.github.io/interpretable-ml-book/">“Interpretable Machine Learning” by Christoph Molnar (オンラインで利用可能)</a>を読むこともおすすめします。</p> <p>時には、決定木のような、より説明可能なモデルが、もつれた問題の手助けになるでしょうが、ニューラルネットワークで解決するベストプラクティスがあるので、確定したわけではありません。</p> <blockquote><p>Best practice #2 可能であれば、説明可能な種類のモデルを使おう</p></blockquote> <p>修正の伝播 (Correction Cascades) (訳注: <a href="https://papers.nips.cc/paper/5656-hidden-technical-debt-in-machine-learning-systems.pdf">Hidden Technical Debt in Machine Learning Systems</a>参照)は漠然とした機械学習モデルへ入力の一部、それ自体が漠然とした機械学習モデルである場合に起こります。 エラーのドミノラリーを準備しているようなものです。</p> <p>例えば、事前に存在しているモデルを新しいドメイン (もしくは多くの人が “startup pivot” と呼ぶもの) に適用する時、このようにモデルを繋げるのは非常に魅力的です。 ランダムフォレストの前に、教師なし次元削減ステップはさんだことがあるかもしれませんが、<a href="https://distill.pub/2016/misread-tsne/">t-SNEのパラメーターを変化させると残りのモデルのパフォーマンスが急落します</a>。 最悪の場合、全体のシステムのパフォーマンスを下げることなく、元のモデルや次元削減の部分などの “サブな部分” の精度を改善するのは不可能です。 機械学習パイプラインは、正の合算価値を生み出すものからゼロサムになります (これは技術的負債論文の用語ではないですが、載せるチャンスを逃しただけだと感じています)。</p> <p><img src="https://imgs.xkcd.com/comics/data_pipeline.png" alt="" /> <small> XKCDのRandal Munroe提供</p> <p>(訳注: </br> Cueball「これを見て。必要な情報全てを集めて処理した完全自動化データパイプラインを作りました。」 Ponytail「行き当たりばったりのスクリプトで構成された巨大な (トランプの家のような) 不安定な構造で、変な入力があった瞬間に崩れませんか?」 Cueball「そう…ではないかもしれない」 Ponytail「私が推測するに、なにか」 Cueball「おっと、壊れた。ちょっと待って、修正します。」</br> <a href="https://www.explainxkcd.com/wiki/index.php/2054:_Data_Pipeline">Explain xkcd</a>の書きおこしを参考に翻訳) </small></p> <p>これを防ぐためには、<a href="https://machinelearningmastery.com/greedy-layer-wise-pretraining-tutorial/">greedy unsupervised layer-wise pretraining (or GULP)</a> のvariantがよりよい技術の一つです。 なぜうまくいくかという数学的理由については意見がわかれているものの、基本的にはモデル初期段階やアンサンブルの最初部分を学習し、それらを固定してから、残りのシークエンスを順々に作業します (これまた、技術的負債論文で言及されていませんが、<a href="https://papers.nips.cc/paper/3048-greedy-layer-wise-training-of-deep-networks.pdf">この技術自体は少なくとも2007年から存在している</a>ので、機会を逃しています)。</p> <blockquote><p>Best practice #3 常に、順番に下流モデルを再学習しよう</p></blockquote> <p>他にも機械学習モデルの不都合な特徴があります。 あなたが認識している以上に、ただの機械学習モデルの域を超えて、その機械学習の出力を頼っている消費者がいるかもしれないということです。 著者らはこれを <em>宣言していない消費者</em> (Undeclared Consumers) として言及しています。 ここでの問題は、出力データが非構造だったり正しくフォーマットされていないことではなく、出力に依存しているシステムがどの程度あるかを把握できる人が誰もいないことです。</p> <p>例えば、kaggleのようなサイトでは、たくさんのカスタムデータセットがあり、そのデータセットの多くは機械学習の出力です。 多くのプロジェクトやスタートアップは、初期の機械学習モデルの構築や学習を、内部のデータセットのかわりにこれらのデータを使うことがあります。 ほぼ予告なしでデータセットが変更される可能性があるにもかかわらずに、です!(しかもこれらに依存したスクリプトやタスクがその変更を検知できることは稀です)</p> <p>また、データにアクセスするためサインインなどが必要ないAPIの場合、問題がより複雑になります。 アクセスキーやサービス水準合意のように、モデルにアクセスする入り口に障壁を作らない限り、扱いが難しくなります。 モデルの出力をファイルとして保存したら、共有ディレクトリにデータがあるからという理由で、チームのメンバーがその出力データを使うかもしれません。 試験的なコードだとしても、まだ確認できていないモデルの出力にアクセスできる人がいるかどうか、気をつけないといけません。 これは<a href="https://jupyterlab.readthedocs.io/en/stable/">JupyterLabのようなtoolkit</a>にとって大きな問題となる傾向があります (もし私が過去に遡って技術的負債論文に警告を付け加えるなら、JupyterLabについて警告するでしょう)。</p> <p>基本的に、このような技術的負債の解決は、機械学習エンジニアとセキュリティーエンジニアの協力が必要です。</p> <blockquote><p>Best practice #4 アクセス鍵、ディレクトリの権限、サービス水準合意をセットアップしよう</p></blockquote> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200622/20200622075851.png" alt="f:id:sh111h:20200622075851p:plain" title="f:id:sh111h:20200622075851p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p><small> 下流の消費者グラフはこのようにシンプルで包括的であればよいと思います。 </small></p> <h1 id="Part3-通常の依存関係の頂上にある-データ依存関係">Part3 (通常の依存関係の頂上にある) データ依存関係</h1> <p>3つめの章はデータ依存関係の問題について、深く掘り下げます。 ソフトウェアエンジニアリングにおける通常のコードに加え、機械学習システムは、開発者が認識する以上に不安定な、膨大なデータソースにも依存しています。 これは悪いニュースです。</p> <p>例えば、入力データは、裏で変化するルックアップテーブルや、連続的なデータストリームであったり、所有していないAPIからのデータを用いているかもしれません。 <a href="https://www.deepchem.io/deepchem.molnet.html">MolNet</a>データセットのホストが、より正確な数値で更新することを決めたとしたらどうなるかを想像してみてください(どうやって更新するかは無視するとして)。 更新したデータはより正確に現実を反映するかもしれませんが、数えきれないモデルが古いデータで構築されており、先週は確実に動いたノートブックを再実行したとき、多くの製作者が、精度が急降下したことに気づくでしょう。</p> <p>著者らによる提案のひとつはPhotonのようなデータ依存追跡ツールを使うことです。 (訳注: <a href="https://research.google/pubs/pub41318/">Photon: Fault-tolerant and Scalable Joining of Continuous Data Streams</a>) 2020年においては、新しいツール <a href="https://dvc.org/">DVC</a> (文字通り「Data Version Control」の意味) があり、Photonを多くの点において時代遅れにしています。 このツールはgitと同じように振る舞い、データセット・データベースの変更の追跡を保持するDAGを保存します。 他に2つのバージョン管理のために一緒に使われるツールとして挙げられるのは、 <a href="https://www.streamlit.io/">Streamlit</a> (実験とプロトタイプの追跡を保持) と<a href="https://github.com/Netflix/metaflow">Netflix’s Metaflow</a>です。</p> <p>どの程度バージョン管理をするかは、メモリ使用量の増加と、学習過程の大きなギャップ防止のトレードオフの関係にあります。 それでも、不十分ないし不適切なバージョン管理は、モデル学習のときに、過剰な生存バイアスが起こり(可能性を無駄にし)ます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200622/20200622080023.png" alt="f:id:sh111h:20200622080023p:plain" title="f:id:sh111h:20200622080023p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p><small> DVCのページです。ダウンロードしましょう。今すぐ! </small></p> <blockquote><p>Best Practice #5 データのバージョン管理ツールを使おう</p></blockquote> <p>データ依存性の怖い話はまだ続きます。 不安定なデータ依存性と比較すれば、十分に活用していないデータは悪くないようにみえますが、<em>そうやってハマるんです!</em> つまり、使っていないデータ、一度使ったもののレガシー扱いになったデータ、他と関連しているため冗長になったデータに注意し続ける必要があるのです。 あなたがデータパイプラインを管理していて、全体のギガバイトが過剰だと気づいた時、それだけで開発コストが発生しています。</p> <p>相互に関連したデータは特に扱いにくいです。 なぜならばどの変数が関連していて、どれが原因となるか、理解する必要があるからです。 これは生物学のデータにとって大きな問題です。 <a href="https://en.wikipedia.org/wiki/Analysis_of_covariance">ANCOVA</a> (訳注: 共分散分析)のようなツールはますます旧式となり、残念ながら<a href="https://towardsdatascience.com/one-of-the-most-common-unintentional-mistakes-when-running-an-anova-in-r-cfa55d332a">ANCOVAの仮定には間違いなく適用していない</a>いくつかの事態で使われています。 いくつかのグループは<a href="https://www.freenome.com/bridging-the-generalization-gap">ONION</a> (訳注: <a href="https://arxiv.org/abs/1812.04778">arXivリンク</a>) や<a href="https://www.geosci-model-dev.net/12/4261/2019/">Domain Aware Neural Networks</a>のような代替案を提案しようとしていますが、多くは特に印象的でない標準的な手法を改良しています。 MicrosoftやQuantumBlackのような企業は因果関係のもつれのためのパッケージを開発しました(それぞれ、<a href="https://github.com/microsoft/dowhy">DoWhy</a>と<a href="https://github.com/quantumblacklabs/causalnex">CasualNex</a>です)。 私は特に<a href="https://deepmind.com/blog/article/Causal_Bayesian_Networks">Deepmindのベイジアン因果推論</a>が気に入っています。</p> <p>これらの多くは技術的負債論文の書かれた時期にはなく、多くのパッケージは独自の操作性という負債を抱えていますが、 ANCOVAはどんな場合でもうまくいく (one-size-fits-all) 解決策では <em>ない</em> ことを知ってもらうことが重要だと思っています。</p> <blockquote><p>Best practice #6 使っていないファイル、膨大な関係する特徴量を削除し、因果関係推論ツールを使おう</p></blockquote> <p>ところで、著者らはこれらの修正に対してあまり悲観的ではありませんでした。 クリック予測の中でGoogleが使っているものを例として、彼らはデータ依存性の静的な分析を提案しました。</p> <p>技術的負債論文が出版されて以降、対処するための選択肢はとても発展しました。 例えば、<a href="https://www.snorkel.org/">Snorkel</a>のような、データのスライスを、どの実験で使ったか追跡するツールがあります。 AWSやAzureのようなクラウドサービスは、DevOpsのためのデータ依存性追跡サービスを持っていて、<a href="https://www.red-gate.com/products/sql-development/sql-dependency-tracker/">Red Gate SQL dependency tracker</a> のようなツールもあります。 著者らが楽観的だったのは正しかったようです。</p> <blockquote><p>Best practice #7 データの依存関係を追跡する、無数にあるDevOpsツールのいくつかを使おう</p></blockquote> <h1 id="Part4-イライラさせるほど未定義なフィードバックループ">Part4 イライラさせるほど未定義なフィードバックループ</h1> <p>前のセクションで、希望が少し見えましたが、データ依存性に関する悪いニュースはまだ終わりではないです。 論文の4章では未チェックのフィードバックループが機械学習の開発サイクルに対して、どのような影響をもたらすかに踏み込みます。 半教師あり学習や強化学習のような直接のフィードバックループ、他の機械学習の出力からエンジニアが選択する直接でないループについても言及しています。</p> <p>技術的負債論文において、イシューとして最低限しか定義されていないもののひとつですが、いまや数えきれないぐらいの組織がフィードバックループ問題に取り組んでいます、OpenAI全体がやっているようなものも含めて (少なくとも、<a href="https://openai.com/charter/">彼らの宣言の「長期的安全性」の章</a>ではそうなっています、<a href="https://techcrunch.com/2019/03/11/openai-shifts-from-nonprofit-to-capped-profit-to-attract-capital/">「上限つき利益 (Capped Profit)」</a>騒動の前から)。 <strong>なにが言いたいかというと、直接または間接的なフィードバックループの研究・調査をするのであれば、この論文よりも <em>はるかに</em> 優れた具体的な選択肢があるということです。</strong></p> <p>この章では、前のセクションよりも少し絶望的に見える解決策を掲げています。 彼らはバンディッドアルゴリズムを直接のフィードバックループに耐性がある例としてあげていますが、このアルゴリズムはスケールしないだけでなく、スケールしたシステムを構築 <em>しようと</em> するとき、技術的負債が最も蓄積されます。 役に立ちません。</p> <p>間接的なフィードバックの修正はあまり良くないです。 実際、間接的なフィードバックループのシステムは同じ構造の一部ですらありません。 お互いにメタゲームをしようとする、異なる企業の交易アルゴリズムのようで、代わりに<a href="https://www.ig.com/us/trading-strategies/flash-crashes-explained-190503">フラッシュ・クラッシュを起こす</a>かもしれません。 もしくは、バイオテックでより関連した例として、様々な実験装置の誤り確率を予測するモデルを考えてみてください。 時がたつにつれ、実際の誤り率は、人がより練習することで下がったり、科学者が装置をより頻繁に使うことで下がっていきますが、この部分を補正するためのモデルのカリブレーション頻度は増えません。 (訳注: <a href="https://ja.wikipedia.org/wiki/%E8%BC%83%E6%AD%A3">カリブレーション (較正)</a>は、この文脈では確率の較正ではなくパラメータ再調整だと思えばよい) 究極的には、これを修正するために大事なのは、高いレベルでの設計上の決定と、モデルのデータの背後にある仮説(特に独立性の仮説)を可能な限り多く確認することです。</p> <p>これはまた、セキュリティエンジニアリングの多くの原則と実践がとても有効になる領域です (例えば、システムを通るデータのフローを追跡し、悪質な関係者が利用する前にシステムが防ぐ方法を探す)。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20200622/20200622080115.png" alt="f:id:sh111h:20200622080115p:plain" title="f:id:sh111h:20200622080115p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p><small> 直接的なフィードバックループの一例です。 実際には、このダイアグラムのいくつかのブロックがMLモデルです。 これは間接的な相互作用をうわべだけ扱っているわけではありません。 </small></p> <blockquote><p>Best practice #8 モデルの背後にある独立性の仮定を確認しよう (セキュリティーエンジニアの近くで働こう)</p></blockquote> <p>今や、特にANCOVAについてコメントした後であれば、あなたはおそらく仮定の検証というテーマに気づいているでしょう。 私は、著者らがこの話題に少なくとも1セクションを使えばよかったのにと思っています。</p> <h1 id="後編に続きます">後編に続きます</h1> <p><a href="https://matthewmcateer.me/blog/machine-learning-technical-debt/">元のブログ</a> は1つの記事ですが、少々長いので、前後編に分けさせていただきました。</p> <p>続きでは「コードのアンチパターン」や「構成の負債」、「実世界での活用」について紹介します。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4873115655/hoxom-22/"><img src="https://m.media-amazon.com/images/I/51MgH8Jmr3L._SL160_.jpg" class="hatena-asin-detail-image" alt="リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)" title="リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4873115655/hoxom-22/">リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/Dustin%20Boswell" class="keyword">Dustin Boswell</a>,<a href="http://d.hatena.ne.jp/keyword/Trevor%20Foucher" class="keyword">Trevor Foucher</a></li><li><span class="hatena-asin-detail-label">発売日:</span> 2012/06/23</li><li><span class="hatena-asin-detail-label">メディア:</span> 単行本(ソフトカバー)</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> sh111h MLflowのデータストアを覗いてみる hatenablog://entry/26006613548489334 2020-04-30T09:30:00+09:00 2020-08-17T02:20:42+09:00 (2020/08/14 flavorについての記載を一部修正) はじめに こんにちは、ホクソエムサポーターの藤岡です。 最近、MLflowを分析業務で使用しているのですが、お手軽に機械学習のモデルや結果が管理できて重宝しています。 また、特定のライブラリに依存しないなど、使い方の自由度も非常に高いところが魅力的です。 ただ、ザ・分析用のPythonライブラリという感じでとにかく色々なものが隠蔽されており、 サーバにつなぐクライアントさえもプログラマあまりは意識する必要がないという徹底っぷりです。 もちろんマニュアル通りに使う分には問題ないですが、 ちゃんと中身を知っておくと自由度の高さも相まっ… <p>(2020/08/14 flavorについての記載を一部修正)</p> <h1>はじめに</h1> <p>こんにちは、ホクソエムサポーターの藤岡です。 最近、MLflowを分析業務で使用しているのですが、お手軽に機械学習のモデルや結果が管理できて重宝しています。 また、特定のライブラリに依存しないなど、使い方の自由度も非常に高いところが魅力的です。</p> <p>ただ、ザ・分析用のPythonライブラリという感じでとにかく色々なものが隠蔽されており、 サーバにつなぐクライアントさえもプログラマあまりは意識する必要がないという徹底っぷりです。 もちろんマニュアル通りに使う分には問題ないですが、 ちゃんと中身を知っておくと自由度の高さも相まって色々と応用が効くようになり、 様々なシチュエーションで最適な使い方をすることができるようになります。</p> <p>というわけで、今回はMLflowの記録部分を担う、 Experiment, Run, Artifactについてその正体に迫ってみます。</p> <p>なお、MLflow自体についての機能や使い方についての解説はすでに良記事がたくさんあり、 ググればすぐにヒットするのでここでは割愛します。</p> <h1>データストアの3要素</h1> <p>MLflowのデータ管理は<strong>Run</strong>、<strong>Experiment</strong>、<strong>Artifact</strong>の三つの要素から構成されています。 大まかには、それぞれの役割は以下の通りです。</p> <ul> <li>Run: 一回の試行(e.g. 実験, 学習, ... etc.)</li> <li>Experiment: Runを束ねるグループ</li> <li>Artifact: Runで得られた出力や中間生成物の保管先</li> </ul> <p>これらの関係は、図のようになっています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kazuya_fujioka/20200412/20200412001402.png" alt="f:id:kazuya_fujioka:20200412001402p:plain" title="f:id:kazuya_fujioka:20200412001402p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>なお、Artifact LocationはArtifactの格納先のことであり、 あるExperimentに対して一つのArtifact Locationが紐づいています。 逆に、あるArtifact Locationが複数のExperimentと結びつくようにすることも可能ですが、 経験的には管理の点からしてそのような設計は避けるべきだと思います。</p> <p>では、これらの三要素について、その役割と実装について説明していきます・</p> <h2>Run</h2> <h3>概要</h3> <p>まず、全ての基本となるのはRunです。 文字通り、一回の試行を表すものです。 例えば、データセットをXGBoostに投げ込んで学習させる実験を一回やったとすれば、それに対応するのが一つのRunです。</p> <p>MLflowを使うときは<code>mlflow.start_run</code>関数から始まることがほとんどだと思いますが、ここで<code>Run</code>オブジェクトが生成されています。</p> <p>Runの基本要素は、Parameter、Tag、Metricsの三つです。 XGBoostの例で言えば、</p> <ul> <li>Parameter: ハイパーパラメータ</li> <li>Tag: モデルの識別名</li> <li>Metrics: テストデータでのAUC</li> </ul> <p>みたいな感じでしょうか。 いずれも、複数のキーバリューを取ることができます。</p> <p>上では一例を挙げてみましたが、ParameterもTagもMetricsも自由に定義できるので、 個々人の裁量で最適な設計をしてあげるのがいいと思います。 特に、TagにするかParameterにするかはその後のMLflow UIのユーザビリティを大きく左右するので、 よく考えるのがいいと思います。 また、そもそもParameterのうち実験で扱わないようなものは敢えて記録しないなど、 スリム化することも念頭に置いておくといいかと思います。</p> <h3>実装</h3> <p>Runはいくつかのコンポーネントに分かれて定義されています。 低レベルAPIを触る際にキーとなる部分なので、それぞれ細かく解説していきます。</p> <h4>Runオブジェクト</h4> <p>まず、<a href="https://github.com/mlflow/mlflow/blob/dd05d9131a575797425b710b06f472346300e6d7/mlflow/entities/run.py#L8"><code>Run</code></a>オブジェクトはTag、 Parameter、 MetricsとRunに関するメタデータを記録したデータコンテナです<a href="#f-d366ed26" name="fn-d366ed26" title="実際にはもう少し別の機能が定義されていますが、コミッターでもない限り使わないと思うので省略しています">*1</a>。メタデータは以下のように階層的に格納されています<a href="#f-01ce6598" name="fn-01ce6598" title="v1.7.2時点でdeprecateされたものは省略しています">*2</a>。</p> <pre class="code" data-lang="" data-unlink>Run ┝ data │ ┝ params │ ┝ tags │ ┗ metrics ┗ info ┝ run_id ┝ experiment_id ┝ user_id ┝ status ┝ start_time ┝ end_time ┝ artifact_uri ┗ lifecycle_stage</pre> <p>なお、これらの要素は全てイミュータブルオブジェクトとして定義されています。 言い換えると、直接の書き換えは非推奨です。</p> <p>実際にインスタンス化する場合には、<a href="https://github.com/mlflow/mlflow/blob/dd05d9131a575797425b710b06f472346300e6d7/mlflow/tracking/client.py#L22"><code>MlflowClient</code></a>の<code>create_run</code>メソッドを使います。これは<a href="https://github.com/mlflow/mlflow/blob/dd05d9131a575797425b710b06f472346300e6d7/mlflow/tracking/fluent.py#L76"><code>start_run</code></a>関数の内部でも呼ばれていて、実はこのオブジェクト<a href="#f-abb75fb3" name="fn-abb75fb3" title="正確にはその子クラスのActiveRunオブジェクトなのですが、コンテキストマネージャ化されていること以外は同じなのでここでは同一のオブジェクトとして扱います">*3</a>が返されています。 Runを記録するときに直接呼び出すことは無いので、サンプルプログラム等ではたいてい捨てられてしまっていますが。</p> <p>一方、サーバから読み出す場合には、<code>MlflowClient.get_run</code>メソッドを使います。 <code>start_run</code>の場合と違って<code>run_id</code>が必要になるので注意しましょう。<code>run_id</code>はMLflow UIからも取得できますが、スクリプト中ではExperimentを通じて取得するのが楽です。 その取得方法についてはExperimentの節で扱います。</p> <p><code>Run</code>オブジェクトの内部構造と呼び出し方さえ分かってしまえば、実験結果の検索や操作もスクリプトから思いのままに実現可能です。 MLflow UIはリッチなUIを提供してくれている反面、小回りが利かないこともあるので、困ったら<code>Run</code>を自分で直接触るのがいいと思います。</p> <h4>RunDataオブジェクト</h4> <p><code>Run</code>の<code>data</code>プロパティにはRunの基本要素となる3要素が格納されています。このプロパティは<a href="https://github.com/mlflow/mlflow/blob/dd05d9131a575797425b710b06f472346300e6d7/mlflow/entities/run_data.py#L9"><code>RunData</code></a>というデータコンテナとして定義されており、Parameterが<code>params</code>に、Tagが<code>tags</code>に、Metricsが<code>metrics</code>にそれぞれ辞書オブジェクトとして格納されています。</p> <h4>RunInfoオブジェクト</h4> <p><code>Run</code>の<code>info</code>プロパティにはRunのメタデータが格納されています。このメタデータには以下の情報が入っています。</p> <h5>run_id</h5> <p>RunのIDです。 ある<code>run_id</code>は任意のExperimentに対して一意になります<a href="#f-20a122c5" name="fn-20a122c5" title="例えば、ファイル形式のストアの実装を見てみると、uuid.uuid4を使っているので、重複はまず無いです。REST形式のストアの方は不明ですが、sqlalchemyのストアでも同様です">*4</a>。</p> <h5>experiment_id</h5> <p>そのRunの属するExperimentのIDです。</p> <h5>user_id</h5> <p>そのRunを実行したUserのIDです。</p> <h5>status</h5> <p>Runの状態です。以下の5ステータスが定義されています。</p> <ul> <li>RUNNING: Runを実行中</li> <li>SCHEDULED: Runの実行がスケジュールされている状態 <a href="#f-ab6d2817" name="fn-ab6d2817" title="使ったことがないので詳細は不明ですが、おそらくDatabricksやKubernetesと連携した際に使用されるパラメータ">*5</a></li> <li>FINISHED: Runが正常終了した状態</li> <li>FAILED: Runが失敗して終了した状態</li> <li>KILLED: RunがKillされて終了した状態</li> </ul> <h5>start_time / end_time</h5> <p>Runが開始 / 終了した時間です。</p> <h5>artifact_uri</h5> <p>ArtifactのURIです。</p> <h5>lifecycle_stage</h5> <p>Runの削除判定用フラグです。Runが削除されていればDELETED、そうでなければACTIVEが設定されています。 あるRunをAPI経由で削除した場合、データ自体が削除されるのではなくこのフラグがDELETEDに更新されます。</p> <h2>Experiment</h2> <h3>概要</h3> <p>Runを束ねるのがExperimentの役割です。 モデルをたくさん投げ込んだときでもExperimentごとに整理しておけばアクセスしにくくなるのを防げますし、 MLflow UIの可視化機能やMetrics比較等を十全に活かすためにも、適切な単位ごとにExperimentで分けることは重要です。</p> <p>例えば、あるKaggleコンペでExperimentを作って、その中に作成したモデルをRunとして記録するのはもちろん、 コンペによってはXGBoostやRFのようにモデルの種別で複数のExperimentを作ることも有効かもしれません。</p> <p>Experimentの実現形式はメタデータの格納方式に依存します。 利用可能な格納方式は</p> <ul> <li>ファイル (ローカル)</li> <li>RDB (MySQL, MSSQL, SQLite, PostgreSQL)</li> <li>HTTP サーバー (MLflow Tracking Server)</li> <li>Databricks workspace</li> </ul> <p>のいずれかです。</p> <p>例えばファイルストア(Run, Experimentをファイルとして保管する形式)の場合、 以下のようなディレクトリがExperimentの実態となります。</p> <pre class="code" data-lang="" data-unlink>&lt;experiment-id&gt; ┝ meta.yaml ┝ &lt;run-1-id&gt;/ │ ┝ meta.yaml │ ┝ metrics/ │ ┝ params/ │ ┗ tags/ ┝ &lt;run-2-id&gt;/ ... ┗ &lt;run-N-id&gt;/ ┝ meta.yaml ┝ metrics/ ┝ params/ ┗ tags/</pre> <p>experimentとrunの親子関係がディレクトリ構成で表現され、各種メタデータがmeta.yamlに記録されています。 metrics, params, tagsの中には各種データがファイルで格納されています。 なお、artifact_uriが指定されなければ、tags等と同じフォルダにartifactsというフォルダが作成されてその中に格納されます。</p> <h3>実装</h3> <p><a href="https://github.com/mlflow/mlflow/blob/9fd60eeee77dbda37bae0ff97bc899e2bf87605f/mlflow/entities/experiment.py#L7"><code>Experiment</code></a>オブジェクトの扱いは<code>Run</code>オブジェクトとよく似ているので、相違点だけ述べて詳細な解説はスキップします。</p> <p>主な相違点として、以下のものが挙げられます。</p> <ul> <li>高レベルAPI経由での作成は<code>mlflow.start_run</code>ではなく<code>mlflow.create_experiment</code></li> <li>クライアント経由での作成は<code>MlflowClient.create_run</code>ではなく<code>MlflowClient.create_experiment</code></li> <li>取得方法はid経由 (<code>get_experiment</code>関数) だけではなく name経由 (<code>get_experiment_by_name</code>関数) も可能(1つのサーバ内で名前がユニークであるという制約のため)</li> <li>メタデータが以下の4つのみ <ul> <li><code>name</code>: Experimentの名前</li> <li><code>experiment_id</code>: ID</li> <li><code>artifact_location</code>: Artifact LocationのURI</li> <li><code>lifecycle_stage</code>: Runと同様のACTIVE/DELETED</li> <li><code>tags</code>: Runと同様</li> </ul> </li> </ul> <h2>Artifact</h2> <h3>概要</h3> <p>Artifactとは、Runの結果や途中経過で生じたファイルを格納するためのストレージです。 Artifactを置く先のファイルストレージが扱えるファイルであればなんでも格納が可能です。 もちろん、csv化ができるpandas DataFrame や pickle化ができるPythonオブジェクトも同様です。</p> <p>Artifactは以下のファイルシステムに対応しています。</p> <ul> <li>Amazon S3</li> <li>Azure Blob Storage</li> <li>Google Cloud Storage</li> <li>FTP server</li> <li>SFTP Server</li> <li>NFS</li> <li>HDFS</li> </ul> <p>上記のファイルシステムであればRunの場所に関わらず任意の格納場所 (Artifact Location) をURIで指定できますが、図で示したようにExperiment単位で指定しなければならないことに注意してください。 このURI以下にRunごとのフォルダが切られ、そこにRunの中間生成物等が入ります。</p> <p>Artifactのもっとも重要な機能としては、モデルオブジェクトの格納が挙げられます。 モデルの格納の場合、単にファイルにして格納するだけでなく、それをあとで読み込んでデプロイできるようにしなければ、あまり意味がありません。 MLflowは多くの外部ライブラリに対してこのデプロイ機能を、それも環境の変化に対しても頑健な形で実装しています。</p> <h3>実装</h3> <p>Artifactに格納されるモデルファイルは、どのような形でセーブ/ロードが実現されているのでしょうか。 答えはシンプルで、セーブの際にはローカルに一回保存してからファイルシステムごとに定められたプロトコルで指定したURIへと転送し、ロードはその逆のプロセスです。</p> <p>ただし、モデルファイルについては概要の節で述べたとおりのデプロイ機能を実現するために複数のファイルが生成・格納されます。 この格納方法は扱うモデルの実装されたモジュールごとに異なります。 MLflowでは、あるモジュールで生成したモデルを格納・呼び出しするための格納方法をflavor<s>と読んでいます</s> flavorと呼ばれるモジュールの中で定義しています<a href="#f-52f0fd72" name="fn-52f0fd72" title="flavorという名前の由来はこの辺りかと思います。">*6</a>。</p> <p><s>例えば、sklearn flavor, xgb flavorといった具合です。</s></p> <p>flavorには、例えば、sklearn flavor, xgboost flavorといったものがあります。</p> <p>各flavorはmlflow以下に<s>.pyファイル</s> モジュールとしてそれぞれ実装されています。 例えば、<a href="https://github.com/mlflow/mlflow/blob/9fd60eeee77dbda37bae0ff97bc899e2bf87605f/mlflow/xgboost.py">XGBoostのflavor</a>を見てみると、中には呼び出し可能な関数として、</p> <ul> <li><code>get_default_conda_env</code> : デフォルトのconda envを生成</li> <li><code>save_model</code> : ローカルへのモデルのセーブ</li> <li><code>log_model</code> : Artifactへのモデルの保存</li> <li><code>load_model</code> : ローカル/Artifactからのモデルの読み込み</li> </ul> <p>の4つの関数が定義されています<a href="#f-897c2d87" name="fn-897c2d87" title="autologはexperimentalかつoptionalなので省略しました">*7</a>。 これは他のflavorでも同様です。 <s>とはいっても、多くの場合ではこの中で直接使うのは<code>log_model</code>と<code>load_model</code>だけであり、それ以外は内部的に呼ばれるだけかと思います。</s> 頻度は高くないものの、他の二つも外部から使用する可能性があるものです。</p> <p>これらの関数を使い、モデル本体のファイル(xgbの場合はmodel.pkl) conda env (conda.env) , MLmodelの三つが入ったフォルダを作成 / 読み込みします。 このフォルダが、MLflowにおけるモデルオブジェクトになります。</p> <p>MLmodelファイルはモデル作成に使ったflavorの情報等が格納されたコンフィグファイルです。 この内容を元に読み込み方法を決定し、実行します。 また、モデルを実行(mlflow runコマンド)する場合にはconda envファイルを元に実行環境を作成します。</p> <p>flavorは上述の四つの関数がカギであり、これらを理解することができればオリジナルのflavorを作ることもできます。</p> <p>(2020/08/17 誤った内容を修正。なお、MLflow公式によるflavorの正確な定義は<a href="https://www.mlflow.org/docs/latest/models.html#storage-format">こちら</a>をどうぞ)。</p> <h1>終わりに</h1> <p>MLflowは本当に便利で知名度も高くて少しずつ記事も増えてきているのですが、Run, Experiment, Artifactの三つについての解説が物足りなかったので本記事を書いてみました。 話題を絞ったぶん少し深いところまで掘り下げてみましたが、いかがだったでしょうか?</p> <p>MLflowの実装は、flavorのあたりはちょっと無理矢理感がある気もしますが、低レベルのAPIのあたりは実装も参考になるし使いやすいしでぜひ読んで欲しいライブラリの一つです。 これを機に利用はもちろん、実装に興味を持っていただければ幸いです。</p> <p>では、よきPythonライフを!</p> <h1>おまけ</h1> <h2>Runの名は?</h2> <p>MLflowのWeb UIを触ると、Runにも名前 (Run Nameの項目) が設定できるようになっているのが分かります。 しかし、ここまでの説明の通り、RunのメタデータとしてRunの名前に該当する項目はありません。</p> <p>実は、Run Nameはtagsに"mlflow.runName"というタグ名で記録することで表示させることができます。 他にも、このような特殊なタグは<a href="https://github.com/mlflow/mlflow/blob/9fd60eeee77dbda37bae0ff97bc899e2bf87605f/mlflow/utils/mlflow_tags.py#L7">mlflow.utils.mlflow_tags</a>内で定義されています。</p> <h2>ところで、"Artifact"って?</h2> <p>Artifactを英和辞典で調べると、遺物とか人工物とかそんなふんわりとした意味しか出てこなくて、使い始めた当初は具体的に何を入れるものなのかがよく分かりませんでした。</p> <p>本記事を書くにあたって改めて<a href="https://www.lexico.com/definition/artefact">辞書</a>で調べてみると以下のような定義となっていました。</p> <pre class="code" data-lang="" data-unlink>Something observed in a scientific investigation or experiment that is not naturally present but occurs as a result of the preparative or investigative procedure.</pre> <p>ざっくりと訳すと、科学的調査や実験で生じた人工物、というような感じです。 機械学習の実験で使うことを考えると、やっぱり、中間生成物やモデル等なんでも入れていいというような意図を感じます。</p> <p>以前に業務で使用していたときには学習に使うデータマートとかもMLflowで管理してみたのですが、案外できてしまった(しかも割と便利だった)ので、本当に何を入れてもいいんだと思います。 もっとも、環境に特別な制約がなくマート管理に特化したものを採用できるのであれば、そちらの方がいいと思いますが。</p> <h2>NoneはTagに入りますか?</h2> <p>入ります。Parameterも受けつけます。 ただし、Metricsだけはエラーを吐きます。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4297113414/hoxom-22/"><img src="https://m.media-amazon.com/images/I/41qcLvpYdjL._SL160_.jpg" class="hatena-asin-detail-image" alt="データ活用のための数理モデリング入門" title="データ活用のための数理モデリング入門"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4297113414/hoxom-22/">データ活用のための数理モデリング入門</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/%BF%E5%BE%E5%20%A4%D2%A4%ED%A4%AD" class="keyword">水上 ひろき</a>,<a href="http://d.hatena.ne.jp/keyword/%B7%A7%C3%AB%20%CD%BA%B2%F0" class="keyword">熊谷 雄介</a>,<a href="http://d.hatena.ne.jp/keyword/%B9%E2%CC%EE%20%B2%ED%C5%B5" class="keyword">高野 雅典</a>,<a href="http://d.hatena.ne.jp/keyword/%C6%A3%B8%B6%20%C0%B2%CD%BA" class="keyword">藤原 晴雄</a></li><li><span class="hatena-asin-detail-label">発売日:</span> 2020/04/15</li><li><span class="hatena-asin-detail-label">メディア:</span> 単行本(ソフトカバー)</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> <div class="footnote"> <p class="footnote"><a href="#fn-d366ed26" name="f-d366ed26" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">実際にはもう少し別の機能が定義されていますが、コミッターでもない限り使わないと思うので省略しています</span></p> <p class="footnote"><a href="#fn-01ce6598" name="f-01ce6598" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">v1.7.2時点でdeprecateされたものは省略しています</span></p> <p class="footnote"><a href="#fn-abb75fb3" name="f-abb75fb3" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">正確にはその子クラスのActiveRunオブジェクトなのですが、コンテキストマネージャ化されていること以外は同じなのでここでは同一のオブジェクトとして扱います</span></p> <p class="footnote"><a href="#fn-20a122c5" name="f-20a122c5" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">例えば、<a href="https://github.com/mlflow/mlflow/blob/dd05d9131a575797425b710b06f472346300e6d7/mlflow/store/tracking/file_store.py#L399">ファイル形式のストアの実装</a>を見てみると、uuid.uuid4を使っているので、重複はまず無いです。REST形式のストアの方は不明ですが、sqlalchemyのストアでも同様です</span></p> <p class="footnote"><a href="#fn-ab6d2817" name="f-ab6d2817" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">使ったことがないので詳細は不明ですが、おそらくDatabricksやKubernetesと連携した際に使用されるパラメータ</span></p> <p class="footnote"><a href="#fn-52f0fd72" name="f-52f0fd72" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text">flavorという名前の由来は<a href="https://english.stackexchange.com/questions/102533/usage-of-flavour-vs-version">この辺り</a>かと思います。</span></p> <p class="footnote"><a href="#fn-897c2d87" name="f-897c2d87" class="footnote-number">*7</a><span class="footnote-delimiter">:</span><span class="footnote-text">autologはexperimentalかつoptionalなので省略しました</span></p> </div> kazuya_fujioka 深層学習系のトップ会議ICLR2020のNLP系論文についてざっくり紹介 hatenablog://entry/26006613553398020 2020-04-21T09:00:00+09:00 2020-04-22T09:59:30+09:00 ホクソエムサポーターの白井です。今回はICLR2020 の論文を紹介します。 The International Conference on Learning Representations (ICLR) は機械学習の中でも特に深層学習 を専門とした国際会議です。 OpenReview.net によるopen peer reviewを採用しているので、submitされた論文はだれでも閲覧可能です。(ICLR2020 open review) 2020年はエチオピアで開催予定でしたが、COVID-19の影響でvirtual conferenceとなりました。 今回はNLP系の論文について5本紹介… <p>ホクソエムサポーターの白井です。今回は<a href="https://iclr.cc">ICLR2020</a> の論文を紹介します。</p> <p><strong>The International Conference on Learning Representations (ICLR)</strong> は機械学習の中でも特に深層学習 を専門とした国際会議です。 <a href="https://openreview.net">OpenReview.net</a> によるopen peer reviewを採用しているので、submitされた論文はだれでも閲覧可能です。(<a href="https://openreview.net/group?id=ICLR.cc/2020/Conference">ICLR2020 open review</a>)</p> <p>2020年はエチオピアで開催予定でしたが、COVID-19の影響でvirtual conferenceとなりました。</p> <p>今回はNLP系の論文について5本紹介します。 すでに日本語ブログ記事で紹介されているような論文もありますが、自分が興味を持った部分を中心としてざっくりと紹介したいと思います。</p> <p>以降、とくに記載がない場合、図は論文またはブログからの引用です。</p> <ul class="table-of-contents"> <li><a href="#1-Reformer-The-Efficient-Transformer">1. Reformer: The Efficient Transformer</a></li> <li><a href="#2-GENERALIZATION-THROUGH-MEMORIZATION-NEAREST-NEIGHBOR-LANGUAGE-MODELS">2. GENERALIZATION THROUGH MEMORIZATION: NEAREST NEIGHBOR LANGUAGE MODELS</a></li> <li><a href="#3-Neural-Machine-Translation-with-Universal-Visual-Representation">3. Neural Machine Translation with Universal Visual Representation</a></li> <li><a href="#4-ELECTRA-PRE-TRAINING-TEXT-ENCODERS-AS-DISCRIMINATORS-RATHER-THAN-GENERATORS">4. ELECTRA: PRE-TRAINING TEXT ENCODERS AS DISCRIMINATORS RATHER THAN GENERATORS</a></li> <li><a href="#5-LANGUAGE-GANS-FALLING-SHORT">5. LANGUAGE GANS FALLING SHORT</a></li> <li><a href="#おわりに">おわりに</a><ul> <li><a href="#参考資料">参考資料</a></li> </ul> </li> </ul> <h2 id="1-Reformer-The-Efficient-Transformer">1. Reformer: The Efficient Transformer</h2> <ul> <li>paper <a href="https://openreview.net/forum?id=rkgNKkHtvB">https://openreview.net/forum?id=rkgNKkHtvB</a></li> <li>github <a href="https://github.com/google/trax/tree/master/trax/models/reformer">https://github.com/google/trax/tree/master/trax/models/reformer</a></li> <li>blog <a href="https://ai.googleblog.com/2020/01/reformer-efficient-transformer.html">https://ai.googleblog.com/2020/01/reformer-efficient-transformer.html</a></li> </ul> <p>タイトル通り、Transformerを効率的にした <strong>Reformer</strong> (Trax Transformer) という手法を提案する論文です。</p> <p>Transformer <a href="https://arxiv.org/abs/1706.03762">(Vaswani et al., 2017)</a> は学習に膨大なリソースが必要という欠点がありますが、提案手法ではこの欠点を <em>Locality-Sensitive Hashing Attention (LSH attention)</em> と <em>Reversible Transformer</em> の2つによって解決します。</p> <p>まずは LSH attention について説明します。</p> <ul> <li>Attenntion の計算 <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathrm%7BAttention%7D%28Q%2C%20K%2C%20V%29%20%3D%20%5Cmathrm%7Bsoftmax%7D%5Cleft%28%5Cfrac%7BQK%5E%5Ctop%7D%7B%5Csqrt%7Bd_%7Bk%7D%20%7D%7D%20%5Cright%29%20V" alt=" \mathrm{Attention}(Q, K, V) = \mathrm{softmax}\left(\frac{QK^\top}{\sqrt{d_{k} }} \right) V"/> において、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20QK%5E%5Ctop" alt=" QK^\top"/> の計算に着目します。</li> <li>この行列計算は、サイズが大きくてメモリが必要な割に、最終的にsoftmax関数を適用するため、無駄が多いと考えられます。</li> <li>そこで、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20Q%3DK" alt=" Q=K"/> として計算する <em>shared-QK Transformer</em> を提案します。</li> <li>次に、 <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20Q%3DK" alt=" Q=K"/> としたときの <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathrm%7Bsoftmax%7D%28QK%5E%5Ctop%29" alt=" \mathrm{softmax}(QK^\top)"/> に着目します。</li> <li>softmax は値の大きい要素ほど重要であり、値の小さい要素は無視できます。</li> <li>そこで、query <img src="https://chart.apis.google.com/chart?cht=tx&chl=q_i" alt="q_i"/> に最も近い key の部分集合だけを取り出して計算すれば、<img src="https://chart.apis.google.com/chart?cht=tx&chl=QK%5E%5Ctop" alt="QK^\top"/> のうち値の大きくなる要素だけを計算することができ、行列演算が簡略化できます。</li> <li>この近傍を探索する問題を解決するために <em>locality-sensitive hashing</em> (LSH, <a href="https://ja.wikipedia.org/wiki/%E5%B1%80%E6%89%80%E6%80%A7%E9%8B%AD%E6%95%8F%E5%9E%8B%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5">局所性鋭敏型ハッシュ</a>)を利用します。</li> <li>これにより、Attention の計算効率を上げることができます。</li> </ul> <p><img src="https://i.imgur.com/xyMtdta.png" alt="" /></p> <p>次に、Reversible Transformer について説明します。</p> <ul> <li>必要なメモリは少なくとも層 (layer) の数 <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20n_l" alt=" n_l"/> だけ増えるため、大きいTransfomerだと16GBという膨大なメモリが必要になります。</li> <li>RevNet <a href="https://openreview.net/forum?id=HkE06_-uZH">(Gomez et al., 2017)</a> を参考に、<img src="https://chart.apis.google.com/chart?cht=tx&chl=n_l" alt="n_l"/> を減らすことを考えます。</li> <li>RevNet はモデルのパラメータのみで、続く層の活性化から層の活性化を復元する画像分類モデルです。</li> <li>典型的な残差ネットワークでは <img src="https://chart.apis.google.com/chart?cht=tx&chl=x%20%5Cmapsto%20y" alt="x \mapsto y"/> は <img src="https://chart.apis.google.com/chart?cht=tx&chl=y%20%3D%20x%20%2B%20F%28x%29" alt="y = x + F(x)"/> のように1入力1出力 (図の <code>(a)</code> )となります。</li> <li>RevNetでは入力・出力のペア <img src="https://chart.apis.google.com/chart?cht=tx&chl=%28x_1%2C%20x_2%29%20%5Cmapsto%20%28y_1%2C%20y_2%29" alt="(x_1, x_2) \mapsto (y_1, y_2)"/> で、以下が成立します。 <ul> <li><img src="https://chart.apis.google.com/chart?cht=tx&chl=y_1%20%3D%20x_1%20%2B%20F%28x_2%29%2C%20y_2%20%3D%20x_2%20%2B%20G%28y_1%29" alt="y_1 = x_1 + F(x_2), y_2 = x_2 + G(y_1)"/></li> <li>残差を足し引きすることで層を反転できます <img src="https://chart.apis.google.com/chart?cht=tx&chl=x_2%20%3D%20y_2%20-%20G%28y_2%29%2C%20x_1%20%3D%20y_1%20-%20G%28x_2%29" alt="x_2 = y_2 - G(y_2), x_1 = y_1 - G(x_2)"/></li> <li>(図の <code>(b)</code>, <code>(c)</code> )</li> </ul> </li> <li>Fを Attention layer、 GをFeed-forword layerとみなすのが <em>Reversible Transformer</em> です。</li> </ul> <p><img src="https://i.imgur.com/uxqpLOn.png" alt="" /></p> <p>これら2つのテクニックによって、計算コストとメモリコストを効率化することで、長文も一度に扱えるようになります。</p> <p>ちなみに、この論文を紹介しているいくつかのブログでは、「罪と罰」全文を理解できてすごいのがウリっぽい見出しになっていますが、あくまで、モデルが小説のような長いテキストを <strong>一度</strong> に読み込めるだけです。 そこから要約するのか、翻訳するのか、読解タスクを解くのかは学習データ次第だと私は考えます。</p> <p>ブログでは画像生成タスクの結果が記載されています。(下の図) 効率化によって、断片的な画像から復元するような <em>large-context data</em> も扱えるようになったといえます。</p> <p><img src="https://i.imgur.com/4hA0YNN.png" alt="" /></p> <h2 id="2-GENERALIZATION-THROUGH-MEMORIZATION-NEAREST-NEIGHBOR-LANGUAGE-MODELS">2. GENERALIZATION THROUGH MEMORIZATION: NEAREST NEIGHBOR LANGUAGE MODELS</h2> <ul> <li>paper <a href="https://openreview.net/forum?id=HklBjCEKvH">https://openreview.net/forum?id=HklBjCEKvH</a></li> <li>arXiv <a href="https://arxiv.org/abs/1911.00172">https://arxiv.org/abs/1911.00172</a></li> <li>github <a href="https://github.com/urvashik/knnlm">https://github.com/urvashik/knnlm</a></li> </ul> <p>この論文では、例えば <em>Dickens is the author of</em> と <em>Dickens wrote</em> は次の単語 (作品名・書籍名) に対して同じ分布を持つことが理解できるように、予測問題よりも表現学習 (representation learning) のほうが簡単であるという仮説を立てた上で、既存の言語モデルの類似度 (similarity) によって補完する手法を提案しています。</p> <p>具体的には、学習済みの言語モデルに対して、kNN (<a href="https://ja.wikipedia.org/wiki/K%E8%BF%91%E5%82%8D%E6%B3%95">K近傍法</a>) を用いた拡張を行う <strong>kNN-LMs</strong> を提案します。</p> <p>式で表すと下記のようになります。</p> <div align="center"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%0Ap%28y%7Cx%29%20%3D%20%5Clambda%20p_%20%7BkNN%7D%28y%7Cx%29%20%2B%20%281-%5Clambda%29%20p_%20%7BLM%7D%28y%7Cx%29%0A" alt=" p(y|x) = \lambda p_ {kNN}(y|x) + (1-\lambda) p_ {LM}(y|x) "/></div> <p><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20p%20_%20%7BLM%7D" alt=" p _ {LM}"/> は学習済み言語モデル (この論文ではTransformerLM)、 <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20p%20_%20%7BkNN%7D" alt=" p _ {kNN}"/> は図の上部分を表しています</p> <p><img src="https://i.imgur.com/0ndcf1h.png" alt="" /> kNN-LMsの図</p> <ul> <li><img src="https://chart.apis.google.com/chart?cht=tx&chl=p%20_%20%7BkNN%7D" alt="p _ {kNN}"/> は学習済み言語モデルである <img src="https://chart.apis.google.com/chart?cht=tx&chl=p%20_%20%7BLM%7D" alt="p _ {LM}"/> のcontextを利用するため、(<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20p%20_%20%7BLM%7D" alt=" p _ {LM}"/>の)追加の学習が必要ない</li> <li>同じドメインのみならず、他のドメインにおいても <a href="https://www.slideshare.net/hoxo_m/perplexity">パープレキシティ</a> が低下</li> </ul> <p>既存のニューラル言語モデルをkNNという単純な手法によって拡張することでパフォーマンスが向上しているのが興味深いです。</p> <h2 id="3-Neural-Machine-Translation-with-Universal-Visual-Representation">3. Neural Machine Translation with Universal Visual Representation</h2> <ul> <li>paper <a href="https://openreview.net/forum?id=Byl8hhNYPS">https://openreview.net/forum?id=Byl8hhNYPS</a></li> <li>github <a href="https://github.com/cooelf/UVR-NMT">https://github.com/cooelf/UVR-NMT</a></li> </ul> <p>NMT (ニューラル機械翻訳) において、画像情報を利用する論文です。</p> <p>画像情報はNMTによっても有用な情報ですが、複数の言語について対応した画像+文のコーパスは少ないため、 画像+単一言語のアノテーションデータを利用して補う手法を提案しています。</p> <ul> <li>文と画像のペアから トピック単語に対応する画像の<a href="https://ja.wikipedia.org/wiki/%E3%83%AB%E3%83%83%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB">ルックアップテーブル</a> を作成 <ul> <li>トピック単語の抽出にはtfidfを使う</li> </ul> </li> <li>学習時にはsource sentenceと似たトピックの画像グループを抽出し、ResNet<a href="https://arxiv.org/abs/1512.03385">(He et al., 2016)</a> の画像表現にエンコード</li> </ul> <p><img src="https://i.imgur.com/xKpvTsi.png" alt="" /></p> <p>English-to-German+画像コーパス<a href="https://www.aclweb.org/anthology/W16-3210/">Multi30K</a> の画像情報を利用したEnglish-to-Romanian、English-to-German、English-to-Frenchの翻訳タスクにおいて、いずれも画像情報を用いた方が精度が上がる結果となっています。</p> <p>アイデアとしてはシンプルですが、テキスト以外の情報を利用するマルチモーダルな手法かつ、複数言語への対応というアプローチが新しいと感じました。</p> <h2 id="4-ELECTRA-PRE-TRAINING-TEXT-ENCODERS-AS-DISCRIMINATORS-RATHER-THAN-GENERATORS">4. ELECTRA: PRE-TRAINING TEXT ENCODERS AS DISCRIMINATORS RATHER THAN GENERATORS</h2> <ul> <li>paper <a href="https://openreview.net/forum?id=r1xMH1BtvB">https://openreview.net/forum?id=r1xMH1BtvB</a></li> <li>github <a href="https://github.com/google-research/electra">https://github.com/google-research/electra</a></li> <li>blog <a href="https://ai.googleblog.com/2020/03/more-efficient-nlp-model-pre-training.html">https://ai.googleblog.com/2020/03/more-efficient-nlp-model-pre-training.html</a></li> </ul> <p>ELECTRA (Efficiently Learning an Encoder that Classifies Token Replacements Accurately) は最初に紹介した Reformer と同じく、効率的であることがウリの手法です。</p> <p>系統としてはNNを用いた言語モデルですが、 BERTのような masked language model (MLM) と大きく異なるのは、GeneratorとDiscriminatorというGANのような構造を用いて、<strong>replaced token detection</strong>(RTD) で事前学習をする点です。(正確にはGANモデルでありません)</p> <p><img src="https://i.imgur.com/J5RMEJc.png" alt="" /></p> <p>ELECTRAの図</p> <ul> <li>GeneratorはGANのような敵対的モデルではなく、最大尤度を学習 <ul> <li>実質 MLM</li> </ul> </li> <li>Discriminator は Generator の渡した token が "real" かどうか区別する2値分類を学習</li> <li>Generator と Discriminator の lossを最小化するよう学習</li> <li>この2つを事前学習したのち、Discriminatorをdown stream taskに利用 (ELECTRA)</li> </ul> <p>興味深い点は、効率的に学習するために以下のような拡張 (MODEL EXTENSIONS) が述べられている点です。</p> <ul> <li>GeneratorはDiscriminatorより小さいサイズ <ul> <li>究極的には "unigram" generatorでも可能</li> </ul> </li> <li>GeneratorとDiscriminatorは重みを共有 <ul> <li>token and positional embeddings</li> </ul> </li> <li>Generatorの学習を最適化させる学習アルゴリズムtwo-stage training procedure <ul> <li>うまくいかなかったのでこれは採用されず</li> </ul> </li> </ul> <p>特に最後の学習アルゴリズムについては、敵対的学習よりも提案手法であるMLE (最尤推定) によるGeneratorの方がGLUE scoreが優れていたことが述べられています。 テキストとGANsの相性については次の論文 (LANGUAGE GANS FALLING SHORT) で紹介します。</p> <p>実験結果として、ELECTRAは従来のSOTAモデルよりも小さいモデル・単一GPUでの学習であっても、優れた<a href="https://gluebenchmark.com">GLUE score</a>を達成しています。</p> <p><img src="https://i.imgur.com/ce9qExL.png" alt="" /></p> <p>ちなみに論文中では計算量をfloating point operations(FLOPs) (<a href="https://ja.wikipedia.org/wiki/FLOPS">FLOPS</a>ではない)で表しています。 (FLOPsについて参考: <a href="https://daily.belltail.jp/?p=2537">Chainerで書いたニューラルネットの理論計算量を推定するchainer_computational_cost</a>)</p> <h2 id="5-LANGUAGE-GANS-FALLING-SHORT">5. LANGUAGE GANS FALLING SHORT</h2> <ul> <li>paper <a href="https://openreview.net/forum?id=BJgza6VtPB">https://openreview.net/forum?id=BJgza6VtPB</a></li> <li>arXiv <a href="https://arxiv.org/abs/1811.02549">https://arxiv.org/abs/1811.02549</a></li> <li>github <a href="https://github.com/pclucas14/GansFallingShort">https://github.com/pclucas14/GansFallingShort</a></li> </ul> <p>前述のELECTRAが引用している論文です。(今年のICLRに採択されていますが、論文自体はarXivに2018年からアップロードされていた模様。)</p> <p>従来の自然言語生成 (NLG)において、MLE(最尤推定)を学習に利用した手法では、学習と生成の入力が異なっていました。学習時には常に正解データを与えられますが、生成時には前のステップで予測した値のみ与えられます。</p> <p><img src="https://image.slidesharecdn.com/20180204kantocvrl-180204042305/95/sequence-level-training-with-recurrent-neural-networks-cv-25-638.jpg" alt="" /> from <a href="https://www.slideshare.net/YoshitakaUshiku/sequence-level-training-with-recurrent-neural-networks-cv">Sequence Level Training with Recurrent Neural Networks (関東CV勉強会 強化学習論文読み会)</a></p> <p>このようなサンプルの品質の低下の問題 <em>exposure bias</em> の解決案として、 generative adversarial networks (GANs) が提案されています。しかしながらGANsのモデルは品質についての検証が多く、多様性については無視されていました。</p> <p>この論文では自然言語生成の評価において、<strong>temperature sweep</strong> によって質と多様性のトレードオフ(quality-diversity trade-off) を特徴付ける方法を提案しています。 ここでいう temperature は <a href="https://ja.wikipedia.org/wiki/%E3%83%9C%E3%83%AB%E3%83%84%E3%83%9E%E3%83%B3%E3%83%9E%E3%82%B7%E3%83%B3">ボルツマンマシン</a> における温度の意味で使われています。</p> <p>Generator G と temperature <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Calpha" alt=" \alpha"/> の関係を式で表すと下記のようになります。</p> <div align="center"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%0AG_%5Ctheta%20%28x%20_%20t%20%7C%20x%20_%20%7B1%3At-1%7D%29%20%3D%20%5Cmathrm%7Bsoftmax%7D%28o%20_%20t%20%5Ccdot%20W%20%2F%20%5Calpha%29%20%0A" alt=" G_\theta (x _ t | x _ {1:t-1}) = \mathrm{softmax}(o _ t \cdot W / \alpha) "/></div> <ul> <li>generator's pre-logit activation <img src="https://chart.apis.google.com/chart?cht=tx&chl=o%20_%20t" alt="o _ t"/> <ul> <li>詳しく記述されていないがLSTMの output gate?</li> </ul> </li> <li>word embedding W</li> <li>Boltzmann temperature parameter <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Calpha" alt=" \alpha"/> (動かしてチューニングする) <ul> <li><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Calpha" alt=" \alpha"/> を1より小さくすると <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20o%20_%20t" alt=" o _ t"/> が増大し、Gの条件確率エントロピーが低下する</li> </ul> </li> </ul> <p>実際に <img src="https://chart.apis.google.com/chart?cht=tx&chl=%5Calpha" alt="\alpha"/> を変更した MLE(最尤推定) での結果が以下の表のようになります。</p> <p><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Calpha%3D1" alt=" \alpha=1"/> の例は統語的には合っていますが、文章全体の一貫性に欠けています。 <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Calpha" alt=" \alpha"/>を大きくすると統語的には間違った文が生成され、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Calpha" alt=" \alpha"/> を小さくすると生成される文が一意になります。</p> <p><img src="https://i.imgur.com/ermeQBV.png" alt="" /></p> <p>temperature sweepを利用し、seqGAN <a href="https://arxiv.org/abs/1609.05473">(Yu et al., 2017)</a> ベースのGANモデル RL-GANとそこから敵対的学習を取り除いたMLEモデルを比較したところ、MLEモデルの方が質と多様性のトレードオフの側面において、優れた結果であることがわかりました。</p> <p><img src="https://i.imgur.com/zYtXb8A.png" alt="" /></p> <p>つまり、MLEの<em>exposure bias</em> はGANsモデルの最適化よりも問題が少ないということです。 また、MLEによる学習は質と多様性に応じて良いpolicyのGeneratorに改善する一方で、GANsの学習では学習した分布からエントロピー下げることで高品質なサンプルが得られると解釈することもできます。</p> <p>パラメーターによって自然言語生成の出力を変化させて評価するアプローチを提案していると同時に、自然言語におけるGANsの扱いについて、疑問を提示するような論文にもなっています。</p> <p>自然言語とGANsはなにかしらのボトルネックがあるのかもしれません。</p> <h1 id="おわりに">おわりに</h1> <p>TransformerやBERTがメジャーである一方で、効率的な手法や拡張手法の提案がされている印象をうけました。</p> <p>モデルサイズが小さく・学習時間が短くなることで、機械学習の活用が、より手軽になっていく気がします。</p> <h2 id="参考資料">参考資料</h2> <p>ICLRについて</p> <ul> <li><a href="https://www.slideshare.net/shoheihido/iclr-icml-2019overview">ICLR &amp; ICML 2019 概要説明 by Shohei Hido</a></li> <li><a href="https://en.wikipedia.org/wiki/International_Conference_on_Learning_Representations">International Conference on Learning Representations - Wikipedia</a></li> </ul> <p>LSH</p> <ul> <li><a href="https://alpha.mixi.co.jp/entry/2010/10773/">LSH (Locality Sensitive Hashing) を用いた類似インスタンスペアの抽出</a></li> <li><a href="https://www.slideshare.net/yaruki_nil/lsh">https://www.slideshare.net/yaruki_nil/lsh</a></li> </ul> <p>GANs</p> <ul> <li><a href="https://elix-tech.github.io/ja/2017/02/06/gan.html">はじめてのGAN</a></li> <li><a href="https://www.slideshare.net/mamoruk/jsai2018gan4nlp">言語処理における GAN の展開</a></li> <li><a href="https://arxiv.org/abs/1701.00160">[1701.00160] NIPS 2016 Tutorial: Generative Adversarial Networks</a></li> </ul> <p>exposure bias</p> <ul> <li><a href="https://www.slideshare.net/DeepLearningJP2016/d-lhacks-0804pdf-78526975">[DL輪読会]Reward Augmented Maximum Likelihood for Neural Structured Prediction</a></li> <li><a href="https://www.slideshare.net/YoshitakaUshiku/sequence-level-training-with-recurrent-neural-networks-cv">Sequence Level Training with Recurrent Neural Networks (関東CV勉強会 強化学習論文読み会)</a></li> </ul> <p>NLG</p> <ul> <li><a href="https://arxiv.org/abs/1803.07133">[1803.07133] Neural Text Generation: Past, Present and Beyond</a></li> <li><a href="https://ai-lab.lapras.com/nlp/text-generation-2019/">2019年現在の文・文書生成に関してのまとめ - LAPRAS AI LAB</a></li> </ul> <p><a href="https://gluebenchmark.com">GLUE benchmark</a></p> <ul> <li>リーダーボード <a href="https://gluebenchmark.com/leaderboard">https://gluebenchmark.com/leaderboard</a> <ul> <li>ELECTRAは 89.4で、2020年4月15日現在6位 <a href="https://gluebenchmark.com/submission/GGCVWHqhp7eFdreYoZviJWV3gG93/-M-uj9nr0L7soR8N3Kbq">detail</a></li> </ul> </li> </ul> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4839967717/hoxom-22/"><img src="https://m.media-amazon.com/images/I/51Ri2cTWuML._SL160_.jpg" class="hatena-asin-detail-image" alt="実践GAN ~敵対的生成ネットワークによる深層学習~ (Compass Booksシリーズ)" title="実践GAN ~敵対的生成ネットワークによる深層学習~ (Compass Booksシリーズ)"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4839967717/hoxom-22/">実践GAN ~敵対的生成ネットワークによる深層学習~ (Compass Booksシリーズ)</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/Jakub%20Langr" class="keyword">Jakub Langr</a>,<a href="http://d.hatena.ne.jp/keyword/Vladimir%20Bok" class="keyword">Vladimir Bok</a></li><li><span class="hatena-asin-detail-label">発売日:</span> 2020/02/28</li><li><span class="hatena-asin-detail-label">メディア:</span> 単行本(ソフトカバー)</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> sh111h 簡単な"さんすう"で見積もる施策効果の要因分解 hatenablog://entry/26006613553344686 2020-04-20T20:16:47+09:00 2020-04-20T20:33:46+09:00 日々、最先端で高度なテクノロジーに基づくビジネス改善”施策”を実施されている読者諸氏の皆さんこんばんわ、株式会社ホクソエム・常務取締役(博士(統計科学))の高柳です。 "XXXというKPI(売上とか)を向上させるために、XXXを構成するYYYという要因(PVとか広告単価とか1人あたりの売上とか)を向上させれそうな施策を試してみたんだけど、ZZZというまた別の売上を構成する要因(Impressionとか来店客数)も増えてたおかげで、結局、施策が売上全体にどのくらいのインパクトがあったのかよくわからないんだ〜助けて〜” ・・・という状況、あると思います。 この記事ではこういった複数の要因が混み入っ… <p>日々、最先端で高度なテクノロジーに基づくビジネス改善”施策”を実施されている読者諸氏の皆さんこんばんわ、株式会社ホクソエム・常務取締役(博士(統計科学))の高柳です。</p> <p>"XXXというKPI(売上とか)を向上させるために、XXXを構成するYYYという要因(PVとか広告単価とか1人あたりの売上とか)を向上させれそうな施策を試してみたんだけど、ZZZというまた別の売上を構成する要因(Impressionとか来店客数)も増えてたおかげで、結局、施策が売上全体にどのくらいのインパクトがあったのかよくわからないんだ〜助けて〜” ・・・という状況、あると思います。</p> <p>この記事ではこういった複数の要因が混み入った状況でも ”各要因ごとに施策効果を分解して「PV要因で売上X円UP!」などと評価することができますよ、という話を紹介したい。</p> <p>あまりやってる人見たことないからメジャーじゃないとは思うんだけど、「引いて掛ける」という簡単なさんすうで計算することができるので覚えておきましょうという話でもある。</p> <h2>状況設定</h2> <p>まず、スーパーの店長になった気持ちで一日あたりの ”売上(円)” というKPIが以下のように分解されるとしておこう。 え?私の手元のKPIツリーではこんな簡単な算数で綺麗に分解できていない?その場合は下記の一般論のケース「結局なんなの?・・・もっと一般論をプログラマティックにやりたい貴方へ」を見てもらいたい。</p> <ul> <li><b>売上(円) = 1人あたりの売上(円/人)× 来店客数(人)</b></li> </ul> <p>字面通りに式を読み解くと、これは"売上(円)"というKPIを</p> <ul> <li>”1人あたりの売上(円/人)”</li> <li>"来店客数(人)"</li> </ul> <p>という2つの要因に分けているということだ。 そして今、店長としてのあなたは店の売上をあげるために ”1人あたりの売上(円/人)”を向上させようと躍起になっている、そんな状況を想像してもらいたい。</p> <p>それでは早速本題に入ろう。 まず現状、各々の要因が</p> <ul> <li>1人あたりの売上(円/人)= 1,000(円/人)</li> <li>来店客数(人)= 100(人)</li> </ul> <p>だったとしよう。このときの売上(円)はもちろん単純に掛け算をして 1,000(円/人)× 100(人)= 10万円となる。 これはさすがに簡単だ。小学3年生だって暗算でできちゃう。</p> <p>さて、次にあなたは店長として”1人あたりの売上(円/人)”を向上しようと何某かの単価向上キャンペーンを打ったと思おう。 なんかこう一回来店したときにたくさん買ってくれるような施策だ。牛肉がお買い得!マスクまとめ買いチャンス!トイレットペーパー無制限購入可!などなど何でも良い。</p> <p>さて、そのおかげもあって"1人あたりの売上(円/人)"が以下のように改善したとしよう(一方、天気が悪かったのか来店客数がしれっと減ってる点に注意)。</p> <ul> <li>1人あたりの売上(円/人)= 1,200(円/人)</li> <li>来店客数(人)= 90(人)</li> </ul> <p>やったー! ”1人あたりの売上(円/人)”は確かに 1,000(円/人)から 1,200(円/人)に向上している! 今回の”単価向上キャンペーン”は効果があったと言えそうだ!</p> <p>あった、効果はあった、あったはいいが、その効果は”いかほどの金額”だったのだろうか? 定量にうるさいおじさんたちはきっとこれを要求してくるだろう。これを見積もりたい。 だって貴方は店長だもの。エリア長が褒めてくれなくたっていい。自分だけでも自分を褒めてあげたい。 売上はすべてを癒す、そういうことです。</p> <p>さて、雑に考えると、単純に売上の増加分の8,000円((1,200(円/人)× 90(円) - 100,000円)だけ効果があったと見積っていいのだろうか?</p> <p>ここで示す要因分解はこの問題に答えを出してくれる。</p> <h2>要因分解の方法</h2> <p>答えを先に言っちゃうと、各要因による売上増の効果を以下のように計算すればよい。</p> <ul> <li>"1人あたりの売上(円/人)"要因 = (1,200 - 1,000) (円/人)* 100 (人)= 20,000円</li> <li>"来店客数(人)"要因 = (90 - 100)(人)* 1,000 (円/人) = -10,000円</li> <li>両要因の混合(相互作用)要因 = (1,200 - 1,000) (円/人)* (100 - 90)(人) = -2,000円</li> </ul> <p>上の3つの要因を足してもらうと元の売上増加分の8,000円に等しくなっていることがわかると思う。 つまりこういう分解が行われているのだ。</p> <ul> <li>全体の売上増(8,000円)= "1人あたりの売上(円/人)"要因(20,000円) + "来店客数(人)"要因(-10,000円) + 両要因の混合要因(-2,000円)</li> </ul> <p>したがって、この計算方法でいうと "今回の単価向上キャンペーンによって1人あたりの売上は200円向上した。その売上への効果は20,000円程度あったと見込まれる"ということができる(一方、悪天候効果により来店客数が減少したことによる売上への影響は -10,000円あったと言える、また謎の”両要因の混合要因”とやらは、他の2つの要因と比べて1桁小さい点にも注目だ)。 どういう計算をしているのかを日本語で書くとこういうことだ。</p> <ul> <li>"1人あたりの売上(円/人)"要因 = (1人あたりの売上(施策後) - 1人あたりの売上(施策前)× 来店客数(施策前)</li> <li>"来店客数(人)"要因 = (来店客数(施策後) - 来店客数(施策前)×1人あたりの売上(施策前)</li> <li>両要因の混合(相互作用)要因 = (1人あたりの売上(施策後) - 1人あたりの売上(施策前))×(来店客数(施策後) - 来店客数(施策前)</li> </ul> <p>要するに「要因ごとに施策前後での差分を計算し、それに”施策前”のもう一方の要因を掛けてやる」ということだ。 「引いて掛ける」という簡単な”さんすう”で施策効果の要因分解ができましたね?</p> <p><a href="https://docs.google.com/spreadsheets/d/1mg5JvZgrhIeOyNyL2ZXRNaRxFc4nqUbMZBqUxPQMYYU/edit?usp=sharing">この計算をGoogle SpreadSheetにしたものを用意したので自分で数式を確かめたい人は参考にしてほしい</a> 。</p> <p>あぁ、よかった。これでちゃんと単価向上キャンペーンの効果を見積もれるようになったぞ。 店長としての貴方は枕を高くして眠ることができるわけです。</p> <h2>結局なんなの?・・・もっと一般論をプログラマティックにやりたい貴方へ</h2> <p>ここでやってる分解は要するに「 ”施策や確率的なノイズによってもたらされる各要因の向上度合い” を微小量だと思ってそいつで一次近似してる」だけです。 ここでは2要因の場合を紹介したけど、N要因ある場合には(未定義な変数は心の目👀で補間してください)以下のように考えればよい。</p> <p>まず売上の増分はN個の要因(x)の変化(Δで書いてるやつ)を用いて以下のようにかける。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/shinichi-takayanagi/20200420/20200420195825.png" alt="f:id:shinichi-takayanagi:20200420195825p:plain" title="f:id:shinichi-takayanagi:20200420195825p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>この右辺をΔ(向上度合い)が小さいと思って頑張って展開をしてやると <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/shinichi-takayanagi/20200420/20200420201034.png" alt="f:id:shinichi-takayanagi:20200420201034p:plain" title="f:id:shinichi-takayanagi:20200420201034p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>と書ける。</p> <p>2要因の場合、この第一項が上記の"1人あたりの売上(円/人)"要因(x_1)と"来店客数(人)"要因(x_2)に相当しています。 ちゃんと導出したければ Sales(x_1, x_2) = x_1 * x_2 と定義して、これをx_1やx_2で微分してみると見通しよく分かると思います。</p> <p>また最後に残った”両要因の混合(相互作用)要因”と称しているものは上の数式の第二項、要するに高次のオーダーからの寄与ってことになります。</p> <p>なんで、N要因への拡張も簡単にできるし、Codeで計算しようと思えば簡単にできるわけです(売上関数をちゃんと構築できれば!)。</p> <h2>結論</h2> <p>いろんな要因がごちゃごちゃ同時に変化してしまったとしても、意外と施策効果って簡単な算数で測れるもんだな〜ってのがわかっていただけると幸甚です。</p> <p>そういえば、こういう”数理を用いたモデリング”(今回は要因分解のモデリング)に詳しくなりたい人にぴったりの本が最近出たようですよ。 (この記事は本書の1章に刺激を受けたのでスッと書きました)</p> <p><a href="https://www.amazon.co.jp/dp/B086YQWP8Z/ref=as_li_ss_il?_encoding=UTF8&btkr=1&linkCode=li2&tag=stakaya-fb-22&linkId=90aabd4e534a5479c3639ff875a38213&language=ja_JP" target="_blank"><img border="0" src="//ws-fe.amazon-adsystem.com/widgets/q?_encoding=UTF8&ASIN=B086YQWP8Z&Format=_SL160_&ID=AsinImage&MarketPlace=JP&ServiceVersion=20070822&WS=1&tag=stakaya-fb-22&language=ja_JP" ></a><img src="https://ir-jp.amazon-adsystem.com/e/ir?t=stakaya-fb-22&language=ja_JP&l=li2&o=9&a=B086YQWP8Z" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></p> shinichi-takayanagi sqlparse 入門 - 応用編 - hatenablog://entry/26006613518198564 2020-03-19T09:00:00+09:00 2020-03-19T09:00:09+09:00 1. はじめに こんにちは、ホクソエムサポーターの藤岡です。 初稿では一回で終わらせる予定だったはずの本記事もついに第三回。 ついに最後です。 ここまででsqlparseと構文解析の基本的な部分を解説したので、 いよいよ本格的に構文解析の結果をしっかりと使うプログラムを作っていきます。 今回はsqlparseの紹介というよりは、構文規則をどうやってPythonプログラムに落とし込むか、 という問題に対する自分なりの一解答例です。 もっと賢いやり方はあると思いますし、もしご存知の方がいたら、ぜひコメントでご教示いただければ幸いです。 2. 注意 本記事に書かれた内容は自分の理解に基づいたものであ… <h1>1. はじめに</h1> <p>こんにちは、ホクソエムサポーターの藤岡です。 初稿では一回で終わらせる予定だったはずの本記事もついに第三回。 ついに最後です。 ここまででsqlparseと構文解析の基本的な部分を解説したので、 いよいよ本格的に構文解析の結果をしっかりと使うプログラムを作っていきます。</p> <p>今回はsqlparseの紹介というよりは、構文規則をどうやってPythonプログラムに落とし込むか、 という問題に対する自分なりの一解答例です。 もっと賢いやり方はあると思いますし、もしご存知の方がいたら、ぜひコメントでご教示いただければ幸いです。</p> <h1>2. 注意</h1> <ul> <li>本記事に書かれた内容は自分の理解に基づいたものであり、誤りが含まれている可能性がありますが、ご了承ください。</li> <li>もしそういった不備にお気付きの際には、コメントでご指摘いただければ幸いです。</li> <li>また、以下の解説ではSQLが何度か登場しますが、すべてHiveQLです。</li> <li>今回のサンプルプログラムは説明用に作成したものであり、不具合等が含まれる可能性が多分にあります。</li> <li>リポジトリに入っているコードとはコメントの内容等を一部改変している部分があります。</li> </ul> <h1>3 サンプルプログラム: TableGraph</h1> <p>今回作成するのは、構文木を走査しながらテーブル/サブクエリ間の依存関係をグラフとして生成するプログラムです。</p> <p>例えば、</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SELECT</span> t3.col <span class="synSpecial">FROM</span> ( <span class="synStatement">SELECT</span> a+b <span class="synSpecial">AS</span> col <span class="synSpecial">FROM</span> t1 <span class="synStatement">UNION</span> <span class="synStatement">ALL</span> <span class="synStatement">SELECT</span> c+d <span class="synSpecial">AS</span> col <span class="synSpecial">FROM</span> t2 ) t3 </pre> <p>というSQLクエリから、</p> <pre class="code" data-lang="" data-unlink>クエリ └─ t3 ├─ t1 └─ t2</pre> <p>というグラフを書きます。</p> <p>実装は以下のリポジトリにあります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2FArten013%2Fsqlparse_sample2" title="Arten013/sqlparse_sample2" 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://github.com/Arten013/sqlparse_sample2">github.com</a></cite></p> <p>ただ、今回のサンプルプログラムは行数が前回よりも少しだけ多いため、重要な箇所のみの解説とさせていただきました。 代わりに、プログラムを動かして遊べるように簡単なインターフェースを実装したので、 適当にprint文を差し込みながら動きを見るなどして、色々と学んでいただければ幸いです。</p> <h2>3.1 概要</h2> <p>要件は以下の通りです。</p> <ul> <li>入力は1つのDMLクエリ。</li> <li>入力にはCTE (With節) は含まれない。<a href="#f-c9c455ce" name="fn-c9c455ce" title="現実に即しているとは言い難いですが、簡易化のためにこの前提を置きました">*1</a></li> <li>出力はエッジを表すタプル(始点、終点)の集合。</li> <li>エッジの始点・終点はテーブル/サブクエリ名の文字列。</li> <li>クエリ全体は<code>"__root__"</code>という文字列で表す。</li> <li>無名のサブクエリは識別できるようにIDを振る。</li> </ul> <p>上の例では、3つのタプル <code>("__root__", "t3"), ("t3", "t1"), ("t3", "t2")</code> からなる集合<a href="#f-c11bb42e" name="fn-c11bb42e" title="サンプルコードでは可視化結果を分かりやすくするために結果の得られた順序も保持したかったので、要素が重複しないリストを返しています">*2</a>が得られればOKです。</p> <p>グラフィカルにしたい場合はnetworkx等を使うのがいいかと思います。</p> <h3>3.2 実装方針</h3> <p>今回のプログラム作成においてポイントとなるのが、 トークンとHiveQLの構文規則とをどう結びつけるか、という点です。</p> <p>サンプルプログラムの主な処理はテーブル名の探索ですが、 その達成には現在走査している部分がSELECT節なのかFROM節なのか、といった情報の読み取りが必要です。</p> <p>こうした情報は<strong>非終端記号</strong>と呼ばれる記号で表記されます。 これは以下の構文規則における、 table_reference や table_factor のことです。</p> <pre class="code" data-lang="" data-unlink>table_reference: table_factor | join_table</pre> <p>一方、これらの非終端記号とsqlparseのトークンとは1対1で対応するものではありません。 そもそもsqlparseを用いて得られる構文木は、あるSQL方言の構文規則を完全に表現したものというより、 対応している各方言をだいたい全部包含したような、どっちつかずな構文木です<a href="#f-f8e5b9ab" name="fn-f8e5b9ab" title="3章のように、そもそも間違っている例があったり、非対応のルールもあったりするのでこのように表記しました。">*3</a>。</p> <p>なので、この構文木に対してさらに解析を加える必要があります。 このタスクに対するアプローチは自分の思いつく限りでは以下の二つです。</p> <ol> <li>構文木(もしくはその一部)をHiveQLの構文規則と対応するものに書き換える。</li> <li>構文木を走査して必要な情報を探索し、集約する。</li> </ol> <p>今回は、<del>1のアプローチに最初気づかなかったため</del>諸事情により2のアプローチを採用しました<a href="#f-6f64f7c7" name="fn-6f64f7c7" title="group_tokens等のメソッドが実装されているあたりを見ると、1のアプローチの方がsqlparseの正しい使い方なのかもしれません。">*4</a>。 基本的にHiveQLの構文規則にある各種非終端記号をクラスを使って表現し、 そのクラスを用いて根トークンから走査していく方法で実装を進めます。</p> <p>例えば、table_referenceを<code>TableReference</code>クラス、table_factorを<code>TableFactor</code>クラスによって表現していきます。</p> <p>これらのクラスは、以下のラッパクラスを基底としたクラスです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">class</span> <span class="synIdentifier">HQLTokenWrapper</span>: <span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant"> HiveQLの構文ルールを適用するためのトークンラッパの基底クラス。</span> <span class="synConstant"> あるトークンオブジェクトを対応する構文規則でラップしている。</span> <span class="synConstant"> &quot;&quot;&quot;</span> <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, token: TokenType): <span class="synStatement">if</span> token <span class="synStatement">is</span> <span class="synIdentifier">None</span>: <span class="synStatement">raise</span> <span class="synType">ValueError</span>(token) self.token = token <span class="synStatement">def</span> <span class="synIdentifier">traverse</span>(self) -&gt; Generator[<span class="synConstant">&quot;HQLTokenWrapper&quot;</span>, <span class="synIdentifier">None</span>, <span class="synIdentifier">None</span>]: <span class="synConstant">&quot;&quot;&quot;構文ルールを適用して得られるトークンをyieldするメソッド&quot;&quot;&quot;</span> <span class="synStatement">yield</span> <span class="synPreProc">from</span> [] <span class="synStatement">def</span> <span class="synIdentifier">nexts</span>(self) -&gt; List[<span class="synConstant">&quot;HQLTokenWrapper&quot;</span>]: <span class="synConstant">&quot;&quot;&quot;1回のtraverseの結果得られる全てのトークンのリスト&quot;&quot;&quot;</span> <span class="synStatement">return</span> <span class="synIdentifier">list</span>(self.traverse()) <span class="synPreProc">@</span><span class="synIdentifier">property</span> <span class="synStatement">def</span> <span class="synIdentifier">text</span>(self) -&gt; <span class="synIdentifier">str</span>: <span class="synConstant">&quot;&quot;&quot;トークンの文字列&quot;&quot;&quot;</span> <span class="synStatement">return</span> <span class="synIdentifier">str</span>(self.token) <span class="synStatement">def</span> <span class="synIdentifier">__str__</span>(self): <span class="synConstant">&quot;&quot;&quot;オブジェクト情報(主にデバッグ用)&quot;&quot;&quot;</span> clsname = self.__class__.__name__ statement = re.sub(<span class="synConstant">&quot;</span><span class="synSpecial">\n</span><span class="synConstant">&quot;</span>, <span class="synConstant">&quot; &quot;</span>, <span class="synIdentifier">str</span>(self.token).strip()) <span class="synStatement">if</span> <span class="synIdentifier">len</span>(statement) &gt; <span class="synConstant">10</span>: <span class="synStatement">return</span> <span class="synConstant">&quot;&lt;{} </span><span class="synSpecial">\&quot;</span><span class="synConstant">{}...</span><span class="synSpecial">\&quot;</span><span class="synConstant">&gt;&quot;</span>.format(clsname, statement[:<span class="synConstant">10</span>]) <span class="synStatement">return</span> <span class="synConstant">&quot;&lt;{} </span><span class="synSpecial">\&quot;</span><span class="synConstant">{}</span><span class="synSpecial">\&quot;</span><span class="synConstant">&gt;&quot;</span>.format(clsname, statement) </pre> <p>基本的には、<code>traverse</code>メソッドで<code>token</code>属性にあるトークンを解析し、 その子孫のトークンをトークンラッパでラップして、その<code>traverse</code>をまた呼ぶ......ということを繰り返します。</p> <p>といってもイメージしづらいと思うので、まずは簡単な例から順に実装を見ていきましょう。</p> <h2>3.3 実装解説</h2> <h3>3.3-a. Statementトークンの中から、SELECTトークンを全て抜き出す。</h3> <p>クエリのルートに当たる<code>Query</code>オブジェクトについて見ていきます。</p> <pre class="code lang-python" data-lang="python" data-unlink>QUERY_IDENTIFIER = <span class="synConstant">&quot;__root__&quot;</span> <span class="synStatement">class</span> <span class="synIdentifier">Query</span>(HQLTokenWrapper): <span class="synConstant">&quot;&quot;&quot;クエリと対応するトークンのラッパ&quot;&quot;&quot;</span> <span class="synStatement">def</span> <span class="synIdentifier">yield_edges</span>(self) -&gt; Generator[Tuple, <span class="synIdentifier">None</span>, <span class="synIdentifier">None</span>]: <span class="synConstant">&quot;&quot;&quot;エッジを生成する&quot;&quot;&quot;</span> token_stack = self.nexts() ident_stack = [(self.get_identifier(), <span class="synConstant">0</span>)] <span class="synStatement">while</span> <span class="synIdentifier">len</span>(token_stack): token = token_stack.pop() <span class="synStatement">if</span> <span class="synIdentifier">len</span>(token_stack) &lt; ident_stack[-<span class="synConstant">1</span>][<span class="synConstant">1</span>]: ident_stack.pop() <span class="synStatement">if</span> <span class="synIdentifier">isinstance</span>(token, (TblName, Query)): <span class="synStatement">yield</span> ident_stack[-<span class="synConstant">1</span>][<span class="synConstant">0</span>], token.get_identifier() <span class="synStatement">if</span> <span class="synIdentifier">isinstance</span>(token, Query): ident_stack.append((token.get_identifier(), <span class="synIdentifier">len</span>(token_stack))) token_stack.extend(token.nexts()) <span class="synStatement">def</span> <span class="synIdentifier">get_identifier</span>(self) -&gt; <span class="synIdentifier">str</span>: <span class="synConstant">&quot;&quot;&quot;クエリ全体に対する識別子として便宜的にQUERY_IDENTIFIERを割り当てる&quot;&quot;&quot;</span> <span class="synStatement">return</span> QUERY_IDENTIFIER <span class="synStatement">def</span> <span class="synIdentifier">traverse</span>(self): <span class="synConstant">&quot;&quot;&quot;全てのSELECTトークンを抜き出す&quot;&quot;&quot;</span> <span class="synStatement">for</span> t <span class="synStatement">in</span> self.token: <span class="synStatement">if</span> t.match(DML, <span class="synConstant">&quot;SELECT&quot;</span>): <span class="synStatement">yield</span> Select(t) </pre> <p><code>traverse</code>メソッドの中身は非常にシンプルで、<code>self.token</code>の子トークンの中からSELECTトークンを取り出して、 トークンラッパ<code>Select</code>(定義は後ほど紹介します)でラップしてyieldしているだけです。 このように、構文木を部分的に走査しながら、トークンに非終端記号を当てはめていく処理です。</p> <h4>3.3-b. SELECTトークンの兄弟からtable_referenceとwhere_conditionを探す。</h4> <p>実装に入る前に、まずはSELECT節の構文規則のうち今回関係する部分について見ていきます。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SELECT</span> [<span class="synStatement">ALL</span> | <span class="synStatement">DISTINCT</span>] select_expr, select_expr, ... <span class="synSpecial">FROM</span> table_reference [<span class="synSpecial">WHERE</span> where_condition] [<span class="synSpecial">GROUP</span> <span class="synSpecial">BY</span> col_list] [<span class="synSpecial">ORDER</span> <span class="synSpecial">BY</span> col_list] [<span class="synSpecial">CLUSTER</span> <span class="synSpecial">BY</span> col_list | [DISTRIBUTE <span class="synSpecial">BY</span> col_list] [SORT <span class="synSpecial">BY</span> col_list] ] [LIMIT [offset,] <span class="synSpecial">rows</span>] </pre> <p>今回の探索で重要となるテーブル名はtable_reference, where_conditionの二箇所に含まれます。 これら2つを抽出するルールを書いていくのですが、ロジック自体はものすごく単純です。</p> <ol> <li>SELECTトークンの兄弟を走査してFROM節の位置を特定し、そのうちのFROMトークンより後の部分を抜きだす。</li> <li>WHEREトークンを抜き出す。</li> </ol> <p>2については、WHERE節自体がWHEREトークンとしてまとまるように実装されているため、これだけで取り出すことができます。 1については範囲の特定が必要ですが、FROM節は始点も終点も簡単に判定できます。</p> <p>以下、実装です。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">class</span> <span class="synIdentifier">Select</span>(HQLTokenWrapper): FROM_END_KEYWORD = [ <span class="synConstant">&quot;GROUP&quot;</span>, <span class="synConstant">&quot;ORDER&quot;</span>, <span class="synConstant">&quot;CLUSTER&quot;</span>, <span class="synConstant">&quot;DISTRIBUTE&quot;</span>, <span class="synConstant">&quot;SORT&quot;</span>, <span class="synConstant">&quot;LIMIT&quot;</span>, <span class="synConstant">&quot;^UNION&quot;</span> ] <span class="synPreProc">@</span><span class="synIdentifier">classmethod</span> <span class="synStatement">def</span> <span class="synIdentifier">is_from_end_keyword</span>(cls, token): <span class="synStatement">if</span> <span class="synIdentifier">isinstance</span>(token, Where): <span class="synStatement">return</span> <span class="synIdentifier">True</span> <span class="synStatement">return</span> <span class="synIdentifier">any</span>(token.match(Keyword, kw, regex=<span class="synIdentifier">True</span>) <span class="synStatement">for</span> kw <span class="synStatement">in</span> cls.FROM_END_KEYWORD) <span class="synStatement">def</span> <span class="synIdentifier">traverse</span>(self): <span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant"> 以下のルールに従い、table_referenceとwhere_conditionを抜き出す。</span> <span class="synConstant"> [WITH CommonTableExpression (, CommonTableExpression)*]</span> <span class="synConstant"> SELECT [ALL | DISTINCT] select_expr, select_expr, ...</span> <span class="synConstant"> FROM table_reference</span> <span class="synConstant"> [WHERE where_condition]</span> <span class="synConstant"> [GROUP BY col_list]</span> <span class="synConstant"> [ORDER BY col_list]</span> <span class="synConstant"> [CLUSTER BY col_list</span> <span class="synConstant"> | [DISTRIBUTE BY col_list] [SORT BY col_list]</span> <span class="synConstant"> ]</span> <span class="synConstant"> [LIMIT [offset,] rows]</span> <span class="synConstant"> &quot;&quot;&quot;</span> token = self.token <span class="synComment"># UNION以降は別のSELECT節に当たるので探索範囲から外す。</span> <span class="synStatement">while</span> token <span class="synStatement">and</span> <span class="synStatement">not</span> token.match(Keyword, <span class="synConstant">&quot;^UNION&quot;</span>, regex=<span class="synIdentifier">True</span>): <span class="synStatement">if</span> token.match(Keyword, <span class="synConstant">&quot;FROM&quot;</span>): token_first_id = self.token.parent.token_index(token) + <span class="synConstant">1</span> token = get_token_next(self.token.parent, token) <span class="synComment"># Select.FROM_END_KEYWORDでFROM節の終わりを判定する</span> token = self.token.parent.token_matching( self.is_from_end_keyword, self.token.parent.token_index(token) ) <span class="synStatement">if</span> token <span class="synStatement">is</span> <span class="synIdentifier">None</span>: token_last = self.token.parent.tokens[-<span class="synConstant">1</span>] <span class="synStatement">yield</span> TableReference.from_grouping( self.token.parent, token_first_id, self.token.parent.token_index(token_last) ) <span class="synStatement">return</span> <span class="synStatement">else</span>: <span class="synStatement">yield</span> TableReference.from_grouping( self.token.parent, token_first_id, self.token.parent.token_index(token) - <span class="synConstant">1</span> ) <span class="synStatement">continue</span> <span class="synStatement">if</span> <span class="synIdentifier">isinstance</span>(token, Where): <span class="synStatement">yield</span> WhereCondition(token) <span class="synStatement">return</span> token = get_token_next(self.token.parent, token) </pre> <p><code>traverse</code>メソッドの他に、クラスメソッド<code>Select.is_from_end_keyword</code>が定義されていますが、これはFROM節の終端を特定するためのものです。<code>token.match</code>メソッドを呼び出すのが大まかな処理内容です。 ただし、ここではマッチングに正規表現を使い、<code>"^UNION"</code>パターンでUNION, UNION ALLの両方とマッチするようにしています。</p> <p>また、この方法ではマッチできないWHEREだけは別でマッチさせています。 WHEREトークン以外のトークンについては、<code>ttype</code>属性による判定が可能なのですが、WHEREについては<code>ttype</code>属性による判定ができないオブジェクト(<code>sqlparse.sql.Where</code>オブジェクト)なので、<code>match</code>メソッドが使えません。 これは<code>Where</code>以外のいくつかのトークンについても同様なのですが、どちらのケースなのかは基本的にはインポート元から判別できます。</p> <ol> <li><code>sqlparse.sql</code>: <code>isinstance</code>による判定</li> <li><code>sqlparse.tokens</code>, <code>sqlparse.keywords</code>: <code>token.ttype</code> を用いた判定もしくは <code>token.match</code>を呼び出して判定</li> </ol> <p>という認識で問題ないはずです。 念のため、<code>Where</code>のようなケースについては該当するトークンを本記事の<a href="#6">末尾</a>に掲載しておきます。</p> <p>では、<code>traverse</code>メソッドについても見ていきます。 まず、以下の部分はFROM節の終端に当たるトークンを探索しているコードです。</p> <pre class="code lang-python" data-lang="python" data-unlink>token_first_id = self.token.parent.token_index(token) + <span class="synConstant">1</span> token = get_token_next(self.token.parent, token) <span class="synComment"># Select.FROM_END_KEYWORDでFROM節の終わりを判定する</span> token = self.token.parent.token_matching( self.is_from_end_keyword, self.token.parent.token_index(token) ) </pre> <p><code>get_token_next</code>関数は、第一引数で渡したトークンの子の中から第二引数で渡したトークンから見て、コメントや空白をスキップした上で次のトークン(無ければNone)を返します。 実装の解説は省略しますが、気になる方は<a href="https://github.com/Arten013/sqlparse_sample2/blob/855d0dca2a8beaa0a5e5643ea243e45912bbcc12/table_graph/misc.py#L21-L31">こちら</a>をどうぞ。</p> <p>あとは、<code>token.token_matching</code>メソッドの第一引数に<code>is_from_end_keyword</code>を、 第二引数に探索開始地点のインデックスを渡せば目的のトークンが探索できます。</p> <h3>3.3-c. SELECTトークンの兄弟からtable_reference部分を抜き出す。</h3> <p>さて、ここまででtable_referenceは簡単に抜き出すことができましたが、ここからは少し複雑なことをしていく必要があります。</p> <p>実は、3.3-bのようなシンプルな方法は構文規則に循環が存在するとうまく動作せず無限ループに入る場合があります。循環しているとは、例えば、table_referenceの構文規則をたどっていく途中のどこかでtable_referenceが左辺に出現した、というような状況です。この循環は<strong>左再帰</strong>と呼ばれます。</p> <p>今回はこの左再帰が発生しているので、アプローチを変えます。 自分が思いついたのは2つのアプローチです。 どちらの方法も共通して、table_referenceに当たる複数のトークンを束ねる新しいトークンを定義します。</p> <p>一つ目のアプローチでは、<code>token.group_tokens</code>メソッドを使います。</p> <p>これは、複数のトークンを束ねて、<code>grp_cls</code>引数で指定したトークンクラスをそれらトークンの親としてインスタンス化するというメソッドです。 束ねるトークンはあるトークン列の部分列でなくてはいけません。言い換えると、兄弟関係にないトークンどうしや、隣どうしでないトークンどうしを束ねることはできません。 束ねる対象は、始点と終点のインデックスで指定します。</p> <p>というわけで、インデックスは既に取得できる状態なので、<code>grp_cls</code>を用意します。 複数のトークンを束ねたトークンなので、<code>sqlparse.sql.TokenList</code>を継承して作成します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> sqlparse.sql <span class="synPreProc">import</span> TokenList <span class="synStatement">class</span> <span class="synIdentifier">TableReferenceToken</span>(TokenList): <span class="synStatement">pass</span> </pre> <p>振る舞いを追加しないので作る意味がなさそうですが、今後の拡張性や、エラートラッキングのやりやすさ、 可読性を考えるとこのように定義しておいたほうがいいと思います。</p> <p>話を戻すと、トークンを実際に束ねるのが以下のコードです。</p> <pre class="code lang-python" data-lang="python" data-unlink>token_last = self.token.parent.group_tokens( TableReferenceToken, token_first_id, self.token.parent.token_index(token) - <span class="synConstant">1</span> ) <span class="synStatement">yield</span> TableReference(token_last) </pre> <p>tokenには3.3-bで探索したトークンが入っています。 実際はtokenがNullの場合も考慮しなければいけませんが、簡単なので説明は省略します。</p> <p>さて、このアプローチの最大のメリットは、定義済みのメソッド(<code>group_tokens</code>)を使うことで実装をシンプルに済ませられることです。 デメリットは、パース結果を書き換えてしまうため冪等性がなくなる可能性があったり、後続の処理に影響してしまうなどの点です。</p> <p>というわけで、ここからはもう一つのアプローチ、パース結果を変更しない事例を紹介します。 サンプルプログラムでもこちらの方法を採用しています。</p> <p>使うのは以下の関数です。この関数は<code>token.group_tokens</code>メソッドから、 元のパースツリーのトークンオブジェクトを変更する処理(+α)をごっそり削ったものです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">group_tokens</span>(token, grp_cls, start_idx, end_idx, include_end=<span class="synIdentifier">True</span>): <span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant"> tokenのサブグループをgrp_clsによってまとめる。</span> <span class="synConstant"> sqlparse純正のものから機能を大幅に少なくし、さらに元のパースツリーを書き換えないよう</span> <span class="synConstant"> 変更したもの。</span> <span class="synConstant"> &quot;&quot;&quot;</span> end_idx = end_idx + include_end subtokens = token.tokens[start_idx:end_idx] grp = grp_cls(subtokens) grp.parent = token <span class="synStatement">return</span> grp </pre> <p>つまり、元のパースツリーからは繋がっていないトークンを作って、そのトークンを起点にパースツリーを葉へと掘り下げていくという方法です。</p> <p>データ構造としては、グループ化するトークンとその子孫だけを切り出して有向部分木を新たに作るイメージに近いです<a href="#f-04091d88" name="fn-04091d88" title="厳密には、これらのトークンオブジェクトの持つ親への参照を無視すれば有向部分木になります">*5</a>。 この方法の注意点は、作成したトークンの子だけは親への参照が正しくないため、 <code>token.parent</code>属性やそれを参照する関数等を使う場合には気をつける必要があります。</p> <p><a href="https://github.com/Arten013/sqlparse_sample2/blob/84b760393e5e4f9f43fc9857661537913706c7e3/table_graph/hql_tokens.py#L131-L136">サンプルプログラムの当該箇所</a>とは異なりますが、以下のような実装になるかと思います。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> .misc <span class="synPreProc">import</span> group_tokens token_last = group_tokens( token, TableReferenceToken, token_first_id, self.token.parent.token_index(token) - <span class="synConstant">1</span> ) <span class="synStatement">yield</span> TableReference(token_last) </pre> <h3>3.3-d. テーブル名を取得する。</h3> <p>最後に、テーブル名取得についてちょっと細かい話を紹介します。 なお、取得方法自体は<a href="https://blog.hoxo-m.com/entry/sqlparse_parse">前回の記事</a>をご覧ください。</p> <p>まず、<code>get_alias</code>がWHERE IN構文に対して変な挙動を見せる点です。 具体的には<code>WHERE col_foo IN sub_query</code>という構文が出現した際に、<code>Where</code>オブジェクトの<code>get_alias</code>メソッドを使うと<code>col_foo</code>をaliasとして引っ張ってきてしまいます。</p> <p>元の<code>get_alias</code>メソッドの実装を読むと分かるのですが、厳密なパーサーを書いているというよりは様々な方言に広く使えるものをゆるく書いているような印象なので、バグとは言い切れないです。</p> <p>なお、サンプルコードでは以下のようにsqlparseの関数をコピーしてきて少しだけ書き換えたものを実装して使っています。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">class</span> <span class="synIdentifier">WhereCondition</span>(HQLTokenWrapper): <span class="synStatement">def</span> <span class="synIdentifier">get_subquery_alias</span>(self): <span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant"> WHERE IN 対応版のget_aliasメソッド</span> <span class="synConstant"> &quot;&quot;&quot;</span> <span class="synPreProc">from</span> sqlparse.tokens <span class="synPreProc">import</span> Whitespace <span class="synComment"># &quot;name AS alias&quot;</span> kw_idx, kw = self.token.token_next_by(m=(Keyword, <span class="synConstant">'AS'</span>)) <span class="synStatement">if</span> kw <span class="synStatement">is</span> <span class="synStatement">not</span> <span class="synIdentifier">None</span>: <span class="synStatement">return</span> self.token._get_first_name(kw_idx + <span class="synConstant">1</span>, keywords=<span class="synIdentifier">True</span>) <span class="synComment"># &quot;name alias&quot; or &quot;complicated column expression alias&quot;</span> _, ws = self.token.token_next_by(t=Whitespace) <span class="synStatement">if</span> <span class="synIdentifier">len</span>(self.token.tokens) &gt; <span class="synConstant">2</span> <span class="synStatement">and</span> ws <span class="synStatement">is</span> <span class="synStatement">not</span> <span class="synIdentifier">None</span>: kw_in_idx, _ = self.token.token_next_by(m=(Keyword, <span class="synConstant">&quot;IN&quot;</span>)) <span class="synStatement">return</span> self.token._get_first_name(idx=kw_in_idx, reverse=<span class="synIdentifier">True</span>) </pre> <p>次に、<code>get_real_name</code>の呼び出し元についてです。 テーブル名を表す最小単位のトークンはNameトークンです。 しかし、Nameトークンからget_real_nameトークンを直接呼び出すとNoneが返ってきてしまいます。 必ず、Identifier (Nameの場合はその親がIdentifierになっているはずです) から呼び出すようにしましょう。</p> <p>サンプルプログラムの実装は以下の通りです。 なお、定義されているのはTblNameというトークンラッパクラス中です。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">get_identifier</span>(self): <span class="synConstant">&quot;&quot;&quot;テーブル名を取得&quot;&quot;&quot;</span> <span class="synStatement">if</span> self.token.ttype == Name: <span class="synStatement">return</span> self.token.parent.get_real_name() <span class="synStatement">if</span> self.token.__class__ == Identifier: <span class="synStatement">return</span> self.token.get_real_name() </pre> <h1>4. おわりに</h1> <p>これまで全3回の記事を通してsqlparseを紹介してきました。 インターネット上で調べた感じだと、どうやらフォーマッタツールとして知られているようですが、 その内部に定義されている種々の機能もとてもパワフルで、 色々な可能性を秘めた「ライブラリ」でもあることが伝わっていれば幸いです。</p> <p>ここまで色々と書いてきましたが、なんだかんだSQLは好きじゃないです。 データ分析の現場では読み書きしなければいけないケースが多く、仕方なく使っているというような状態です。 でも、これさえあればSQLばかりの現場でもPythonで立ち向かえるはずです。多分。</p> <p>それでは、よきPythonライフを。</p> <p><a name="6"></a></p> <h1>5. おまけ: isinstanceで判定するトークンリスト</h1> <ul> <li>Statement</li> <li>Identifier</li> <li>IdentifierList</li> <li>TypedLiteral</li> <li>Parenthesis</li> <li>SquareBrackets</li> <li>Assignment</li> <li>If</li> <li>For</li> <li>Comparison</li> <li>Comment</li> <li>Where</li> <li>Having</li> <li>Case</li> <li>Function</li> <li>Begin</li> <li>Operation</li> <li>Values</li> <li>Command</li> </ul> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4873117399/hoxom-22/"><img src="https://m.media-amazon.com/images/I/41wJtgiHrXL._SL160_.jpg" class="hatena-asin-detail-image" alt="実践 Python 3" title="実践 Python 3"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4873117399/hoxom-22/">実践 Python 3</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/Mark%20Summerfield" class="keyword">Mark Summerfield</a></li><li><span class="hatena-asin-detail-label">発売日:</span> 2015/12/01</li><li><span class="hatena-asin-detail-label">メディア:</span> 単行本(ソフトカバー)</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> <div class="footnote"> <p class="footnote"><a href="#fn-c9c455ce" name="f-c9c455ce" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">現実に即しているとは言い難いですが、簡易化のためにこの前提を置きました</span></p> <p class="footnote"><a href="#fn-c11bb42e" name="f-c11bb42e" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">サンプルコードでは可視化結果を分かりやすくするために結果の得られた順序も保持したかったので、要素が重複しないリストを返しています</span></p> <p class="footnote"><a href="#fn-f8e5b9ab" name="f-f8e5b9ab" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">3章のように、そもそも間違っている例があったり、非対応のルールもあったりするのでこのように表記しました。</span></p> <p class="footnote"><a href="#fn-6f64f7c7" name="f-6f64f7c7" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">group_tokens等のメソッドが実装されているあたりを見ると、1のアプローチの方がsqlparseの正しい使い方なのかもしれません。</span></p> <p class="footnote"><a href="#fn-04091d88" name="f-04091d88" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">厳密には、これらのトークンオブジェクトの持つ親への参照を無視すれば有向部分木になります</span></p> </div> kazuya_fujioka sqlparse 入門 - 狭義の構文解析編 - hatenablog://entry/26006613502347776 2020-02-27T09:00:00+09:00 2020-02-27T09:00:10+09:00 1. はじめに こんにちは。ホクソエムサポーター(名称審議中)の藤岡です。 字句解析を紹介した前回の記事に続き、今回もsqlparseを中心に据えつつ狭義の構文解析について紹介・解説していきたいと思います。 また、狭義の構文解析で得られる構文木を解析するためのいくつかのメソッドについても解説します。 2. 注意 本記事に書かれた内容は自分の理解に基づいたものであり、誤りが含まれている可能性がありますが、ご了承ください。 もしそういった不備にお気付きの際には、コメントでご指摘いただければ幸いです。 また、以下の解説ではSQLが何度か登場しますが、すべてHiveQLです。 今回のサンプルプログラム… <h1>1. はじめに</h1> <p>こんにちは。ホクソエムサポーター(名称審議中)の藤岡です。 字句解析を紹介した<a href="https://blog.hoxo-m.com/entry/sqlparse_lex">前回の記事</a>に続き、今回もsqlparseを中心に据えつつ狭義の構文解析について紹介・解説していきたいと思います。 また、狭義の構文解析で得られる構文木を解析するためのいくつかのメソッドについても解説します。</p> <h1>2. 注意</h1> <ul> <li>本記事に書かれた内容は自分の理解に基づいたものであり、誤りが含まれている可能性がありますが、ご了承ください。</li> <li>もしそういった不備にお気付きの際には、コメントでご指摘いただければ幸いです。</li> <li>また、以下の解説ではSQLが何度か登場しますが、すべてHiveQLです。</li> <li>今回のサンプルプログラムは説明用に作成したものであり、不具合等が含まれる可能性が多分にあります。</li> <li>本記事の理解には木構造と字句解析についての簡単な知識が必要です。後者については、<a href="https://blog.hoxo-m.com/entry/sqlparse_lex">前回の記事</a>を読んでいれば問題ないかと思います。</li> <li>前回の記事で予告していた内容は<del>尺不足</del>諸般の事情により次回に持ち越しになりました。</li> </ul> <h1>3. 狭義の構文解析</h1> <p>狭義の構文解析とは、字句解析をして得られたトークン列に対して、ある文法規則に基づいた構文構造を与える処理です。 この部分を厳密に扱うと難解になってしまうので、字句解析同様sqlparseにフォーカスして解説していきます。</p> <h1>3.1 構文木とは</h1> <p>sqlparseの場合<a href="#f-e348db81" name="fn-e348db81" title="このように書いていますが、自分の知る限りでは狭義の構文解析の入出力はたいていの場合で sqlparse と同様です。">*1</a>、SQLクエリを字句解析して得られたトークン列(Statement, DML, Comment ... からなる系列)を入力として、<strong>構文木(parse tree)</strong> を返します。 これはその名の通り木構造データで、その各要素は以下の通りです。</p> <ul> <li>根ノード:  入力クエリ全体を表すトークン。</li> <li>内部ノード: 複数のトークンを束ねたトークン。</li> <li>葉ノード:  字句解析の結果得られた最小単位のトークン。</li> </ul> <p>まず、以下のような簡単な例から作られる構文木を見ていきましょう。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SELECT</span> id, age, <span class="synIdentifier">CAST</span>(age / <span class="synConstant">10</span> <span class="synSpecial">as</span> <span class="synType">integer</span>) <span class="synSpecial">as</span> generation <span class="synSpecial">FROM</span> ages </pre> <p>私たち人間がこのクエリを読むとき、ある程度のまとまりをもって解釈していきます。 つまり、<code>id</code>, <code>age</code>, <code>generation</code>の三つのカラムをSELECTして、そのうち<code>generation</code>は <code>CAST(age / 10 as integer)</code> の結果で、他の二つはそのまま<code>ages</code>テーブルから......といった具合です。 また、<code>generation</code>を解釈するためには、まず<code>age</code>カラムがあり (<code>age</code>) 、それを10で割った値を作り (<code>age / 10</code>) 、 最後にそれをIntegerにキャストする (<code>CAST(age / 10 as integer)</code>) といったように、意味的なまとまりで括りながら全体像をくみ上げるように理解していきます。</p> <p>一方、上のクエリをたんに字句解析して空白を取り除いただけだと、以下のような結果が返ってきます。</p> <pre class="code lang-python" data-lang="python" data-unlink>tokens = [t <span class="synStatement">for</span> t <span class="synStatement">in</span> sqlparse.parse(sql_1)[<span class="synConstant">0</span>].flatten() <span class="synStatement">if</span> <span class="synStatement">not</span> t.is_whitespace] tokens &gt;[&lt;DML <span class="synConstant">'SELECT'</span> at <span class="synConstant">0x112C43F48</span>&gt;, &lt;Name <span class="synConstant">'id'</span> at <span class="synConstant">0x1117190A8</span>&gt;, &lt;Punctuation <span class="synConstant">','</span> at <span class="synConstant">0x111719108</span>&gt;, &lt;Name <span class="synConstant">'age'</span> at <span class="synConstant">0x111719348</span>&gt;, &lt;Punctuation <span class="synConstant">','</span> at <span class="synConstant">0x1117193A8</span>&gt;, &lt;Name <span class="synConstant">'CAST'</span> at <span class="synConstant">0x1117195E8</span>&gt;, &lt;Punctuation <span class="synConstant">'('</span> at <span class="synConstant">0x111719648</span>&gt;, &lt;Name <span class="synConstant">'age'</span> at <span class="synConstant">0x1117196A8</span>&gt;, &lt;Operator <span class="synConstant">'/'</span> at <span class="synConstant">0x111719768</span>&gt;, &lt;Integer <span class="synConstant">'10'</span> at <span class="synConstant">0x111719828</span>&gt;, &lt;Keyword <span class="synConstant">'as'</span> at <span class="synConstant">0x1117198E8</span>&gt;, &lt;Builtin <span class="synConstant">'integer'</span> at <span class="synConstant">0x1117199A8</span>&gt;, &lt;Punctuation <span class="synConstant">')'</span> at <span class="synConstant">0x111719A08</span>&gt;, &lt;Keyword <span class="synConstant">'as'</span> at <span class="synConstant">0x111719AC8</span>&gt;, &lt;Name <span class="synConstant">'genera...'</span> at <span class="synConstant">0x111719B88</span>&gt;, &lt;Keyword <span class="synConstant">'FROM'</span> at <span class="synConstant">0x111719C48</span>&gt;, &lt;Name <span class="synConstant">'ages'</span> at <span class="synConstant">0x111719D08</span>&gt;] </pre> <p>この情報だけでは、先頭から順にSELECTがきて、<code>id</code>がきて、カンマがきて......という、人間の解釈方法とはかけ離れた読み方になってしまいます。 このギャップを埋めるために必要なのは、トークン列中のとある部分列を<strong>意味や役割に照らし合わせながら適切なまとまりとして抜き出すこと</strong>、その操作を<strong>再帰的に繰り返し、最後に一つの意味を構築すること</strong>の二つが必要です。</p> <p>この操作こそが狭義の構文解析であり、その結果として得られるのが構文木です。 <code>age / 10</code>の構文木を図で表すと図1のようになります。</p> <p><figure class="figure-image figure-image-fotolife" title="&#x60;age / 10&#x60;の構文木"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kazuya_fujioka/20200211/20200211011756.jpg" alt="f:id:kazuya_fujioka:20200211011756j:plain" title="f:id:kazuya_fujioka:20200211011756j:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>&#x60;age / 10&#x60;の構文木</figcaption></figure></p> <p>四角で囲われた部分がトークンです。この図では、トークンと対応する文字列が四角の中に表記されています。 このように、字句解析の結果では元のクエリのうち狭い範囲と対応するトークンがたくさん並んでいる状態だったのが、 狭義の構文解析によって一つに統合されているのが分かります。 そして、この図だと少し分かりにくいですが、きちんと木構造になっているのが分かります。</p> <p>では、今度はトークンの表記を少し変えてみます。</p> <p><figure class="figure-image figure-image-fotolife" title="&#x60;age / 10&#x60; の構文木(トークンでの表記)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kazuya_fujioka/20200211/20200211011932.jpg" alt="f:id:kazuya_fujioka:20200211011932j:plain" title="f:id:kazuya_fujioka:20200211011932j:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>&#x60;age / 10&#x60; の構文木(トークンでの表記)</figcaption></figure></p> <p>この図から、構文木の各ノードはその対応する部分文字列の意味を表していることが見てとれます。つまり、構文木は様々なレベルにおけるクエリの意味を格納したデータであると言い換えることができます。</p> <p>それでは、上の例における構文木をsqlparseを使って見てみましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># 1. 除算の演算子を取り出す。</span> t = tokens[<span class="synConstant">8</span>] &gt; &lt;Operator <span class="synConstant">'/'</span> at <span class="synConstant">0x111719768</span>&gt; <span class="synComment"># 2. 除算の演算を取り出す。</span> <span class="synIdentifier">str</span>(t.parent), t &gt; <span class="synConstant">'age / 10'</span> (<span class="synConstant">'age / 10'</span>, &lt;Operation <span class="synConstant">'age / ...'</span> at <span class="synConstant">0x1121180C0</span>&gt;) </pre> <p><code>/</code>は演算子を表すOperatorトークンで表現されています。 この演算は2つの項<code>age</code>と<code>10</code>とを合わせた<code>age / 10</code>というまとまりとして解釈されるのが自然です。 実際、その親(<code>parent</code>属性)には<code>age / 10</code>と対応するトークンオブジェクトが格納されていることが分かります。 加えて、このトークンオブジェクトは演算を表すOperationトークンとなっています。</p> <p>この結果から、<code>age</code>, <code>/</code>, <code>10</code>の3つの文字列に対応するトークンは、<code>age / 10</code>に対応するOperationトークンの子孫 とみなすことができます。</p> <p>当然、<code>age</code>, <code>10</code>に対応するトークンからもそれぞれ<code>age / 10</code>に対応するトークンへと遡ることができます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment">#1. `age`のgrandparent</span> tokens[<span class="synConstant">7</span>].parent.parent &gt; &lt;Operation <span class="synConstant">'age / ...'</span> at <span class="synConstant">0x1121180C0</span>&gt; <span class="synComment">#2. `10`のparent</span> tokens[<span class="synConstant">9</span>].parent &gt; &lt;Operation <span class="synConstant">'age / ...'</span> at <span class="synConstant">0x1121180C0</span>&gt; </pre> <p>では、このOperationトークンの先祖をさらに辿っていくとどうなるでしょうか?</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synIdentifier">str</span>(t.parent.parent), t.parent.parent &gt; (<span class="synConstant">'age / 10 as integer'</span>, &lt;Identifier <span class="synConstant">'age / ...'</span> at <span class="synConstant">0x112118138</span>&gt;) <span class="synIdentifier">str</span>(t.parent.parent.parent), t.parent.parent.parent &gt; (<span class="synConstant">'(age / 10 as integer)'</span>, &lt;Parenthesis <span class="synConstant">'(age /...'</span> at <span class="synConstant">0x111DC2C00</span>&gt;) <span class="synIdentifier">str</span>(t.parent.parent.parent.parent), t.parent.parent.parent.parent &gt; (<span class="synConstant">'CAST(age / 10 as integer)'</span>, &lt;Function <span class="synConstant">'CAST(a...'</span> at <span class="synConstant">0x111DC2CF0</span>&gt;) </pre> <p>と、このように意味的なまとまりを段階的に構成しながら、どんどんとトークンどうしが統合されていっていることが見て取れます。</p> <p>図で表すと以下の通りです。</p> <p><figure class="figure-image figure-image-fotolife" title="&#x60;CAST(age / 10 as integer)&#x60;の構文木"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kazuya_fujioka/20200211/20200211012852.jpg" alt="f:id:kazuya_fujioka:20200211012852j:plain" title="f:id:kazuya_fujioka:20200211012852j:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>&#x60;CAST(age / 10 as integer)&#x60;の構文木</figcaption></figure></p> <p>もっとも、<code>age / 10 as integer</code>がIdentifierトークンとして解釈されてしまっている(=型を表すintegerがエイリアスだと判定されている)ように、<code>sqlparse.parse</code>の結果には意味的に誤ったトークンも時折見られます。</p> <p>プログラムに組み込む際はきちんとテストケースを作るか、それが難しい場合は実際の挙動を確認しつつ組み込むのが確実です。</p> <h1>3.2 構文木の利点</h1> <p>クエリをトークンの木として扱えることの真価は、クエリが複雑である場合に発揮されます。 例えば、以下のクエリを考えてみます。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SELECT</span> id, a.age, a.generation, g.gender <span class="synSpecial">FROM</span> ( <span class="synStatement">SELECT</span> id, age, <span class="synIdentifier">CAST</span>(age / <span class="synConstant">10</span> <span class="synSpecial">as</span> <span class="synType">integer</span>) <span class="synSpecial">as</span> generation <span class="synSpecial">FROM</span> ages ) a <span class="synSpecial">INNER</span> <span class="synSpecial">JOIN</span> genders g <span class="synSpecial">ON</span> a.id = g.id </pre> <p>前の例との大きな違いはサブクエリの存在です。 SQLではサブクエリを再帰的にもつことができる、 つまりサブクエリの中に別のサブクエリ、ということが理論上は制限なく繰り返されるため、 これをトークンの列を使って解析するのは骨が折れる作業です。</p> <p>一方、木構造ではこうした再帰構造を部分木として表現することができるので、部分問題に分割して解くことができます。 また、よく使われるデータ構造であり、その解析をより一般的な問題に落とし込んで解くことができます。 例えば、サブクエリを全部探索する問題は、ただの部分木探索問題へと落とし込むことができます。 sqlparseを使えば、実装も簡単です。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> sqlparse.tokens <span class="synPreProc">import</span> DML <span class="synPreProc">from</span> sqlparse.sql <span class="synPreProc">import</span> TokenList, Token <span class="synStatement">def</span> <span class="synIdentifier">iter_subqueries</span>(token: Token): <span class="synStatement">if</span> token.ttype == sqlparse.tokens.DML: <span class="synStatement">yield</span> token.parent <span class="synStatement">if</span> <span class="synStatement">not</span> <span class="synIdentifier">isinstance</span>(token, TokenList): <span class="synComment"># リーフノードの場合は子を探索しない</span> <span class="synStatement">return</span> <span class="synStatement">for</span> t <span class="synStatement">in</span> token: <span class="synStatement">yield</span> <span class="synPreProc">from</span> iter_subqueries(t) </pre> <p>実行してみると、うまく動くことがわかります。</p> <pre class="code lang-python" data-lang="python" data-unlink>sql_2 = <span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">SELECT</span> <span class="synConstant"> id,</span> <span class="synConstant"> a.age,</span> <span class="synConstant"> a.generation,</span> <span class="synConstant"> g.gender</span> <span class="synConstant">FROM (</span> <span class="synConstant"> SELECT</span> <span class="synConstant"> id,</span> <span class="synConstant"> age,</span> <span class="synConstant"> CAST(age / 10 as integer) as generation</span> <span class="synConstant"> FROM ages</span> <span class="synConstant">) a</span> <span class="synConstant">INNER JOIN genders g</span> <span class="synConstant">ON a.id = g.id</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synIdentifier">list</span>(iter_subqueries(sqlparse.parse(sql_2)[<span class="synConstant">0</span>])) &gt; [&lt;Statement <span class="synConstant">' SELEC...'</span> at <span class="synConstant">0x11287C8B8</span>&gt;, &lt;Parenthesis <span class="synConstant">'( ...'</span> at <span class="synConstant">0x11287C840</span>&gt;] </pre> <p>構文木の部分木として表すことのできるのは、サブクエリだけではありません。 例えば、3.1節のサンプルのような関数やその引数、SELECT節の中のカラムリスト、JOINの条件節など、 様々な部分を部分木として取り出すことができます。</p> <h1>4. 構文木の走査</h1> <p>構文木を解析するときには、3節のように親や子、兄弟を渡り歩いていくように進めていきます。 このように木のノードを辿っていくことを<strong>走査(traverse)</strong>と言います。 sqlparseの構文木を構成するトークンオブジェクトには、構文木の走査を簡単に実装するための種々のメソッドや属性が定義されています。 本節ではそれらを簡単に紹介します。</p> <h2>4.1 親子の参照</h2> <p>sqlparseのトークンオブジェクト(sqlparse.sql.Token)はそれぞれが構文木における親への参照情報を持っています。 加えて、葉ノード以外のトークンオブジェクト(sqlparse.sql.TokenList)は子への参照情報も持っています。</p> <p>親へは<code>parent</code>属性、子へは<code>tokens</code>属性から参照できます。 後者については、トークンオブジェクトに対して直接インデックスを指定してもOKです。 例えば、<code>token.tokens[0]</code>は<code>token[0]</code>と同じ結果を返します。</p> <h2>4.2 子の走査・探索</h2> <p>基本的に構文木を走査する場合には根から葉へと進んでいきます。 つまり、あるトークン<code>t</code>が与えらたとき、その子<code>t.tokens[0]</code>, <code>t.tokens[1]</code>, ..., <code>t.tokens[i]</code>, ..., <code>t.tokens[N]</code>を再帰的に解析していくプロセスです。 0-Nのインデックス<code>i</code>を直接人手で扱うのもいいですが、sqlparseのトークンオブジェクトにはそれをサポートする種々のメソッドが定義されているので、 それらを活用した方が確実です。 ここでは、それらの機能を紹介します。</p> <h3>4.2.1 先頭トークンの取得</h3> <p>まず、一番シンプルに走査するなら先頭の子トークンを取り出すことになると思います。 先頭トークンは<code>token_first</code>メソッドから参照できます。 必須の引数はなく、<code>t.token_first()</code>とするだけでもOKです。</p> <p>なお、単に先頭を取り出すだけならば<code>t[0]</code>でいいと思われるかもしれませんが、 このメソッドの真骨頂は、状況に応じてコメントと空白をスキップすることができる点です。 コメントをスキップするには<code>skip_cm</code>引数をTrueに、空白をスキップするには<code>skip_ws</code>引数をTrueにします。</p> <p>ただし、戻り値はトークンのインデックスなので、トークンオブジェクト自体を取り出す場合には<code>t.tokens[t.token_first()]</code>としましょう。 該当するトークンがない場合にはインデックスの代わりにNoneが返ってきます。</p> <h3>4.2.2 前後のトークンの取得</h3> <p>あるノードからその前後の兄弟トークンへと辿っていくときには、 <code>token_next</code>, <code>token_prev</code>の2つのメソッドを使います。 例えば、<code>t</code>とその子トークン<code>u</code>のインデックス<code>i_u</code>が与えられたとき、 <code>t.token_next(i_u)</code>とすれば、その次のトークンが得られます。token_prevも同じ使い方で1つ前のトークンを取得できます。</p> <p>ただし、戻り値はタプル<code>(u, i_u)</code>なので、間違えないように注意してください。 また、token_firstと同様の方法でコメントや空白のスキップも可能です。 該当するトークンがない場合には<code>(None, None)</code>が返ってきます。</p> <h3>4.2.3 マッチングによる探索(ちょっと発展)</h3> <p>コールバック関数による条件指定<a href="#f-f63de80f" name="fn-f63de80f" title="Pythonだとsorted関数におけるkey引数が代表例です。">*2</a>をすることで、 その条件にマッチした最初のトークンを探し出すことができます。</p> <p>使うのは<code>token_matching</code>メソッドです。第一引数にコールバック(もしくはそのリストないしタプル)を渡し、 第二引数に探索範囲の先頭となるトークンのインデックスを渡します。</p> <p>コールバックの戻り値は、マッチした場合にif文の中で真と判定され、それ以外の場合に偽だと判定されるような定義とします。 第一引数で複数のコールバックを渡した場合、それらのいずれかを満たす(OR)を満たすトークンが返ってきます。</p> <p>例えば、文字列が"foo"となっている最初のトークンを探索する場合、</p> <pre class="code lang-python" data-lang="python" data-unlink>t.token_matching(<span class="synStatement">lambda</span> t: t.value==<span class="synConstant">&quot;foo&quot;</span>, <span class="synConstant">0</span>) </pre> <p>とします。 他にも、IFとELSEのいずれかのKeywordトークンとマッチさせる場合には、</p> <pre class="code lang-python" data-lang="python" data-unlink>t.token_matching(<span class="synStatement">lambda</span> t: t.match(Keyword, <span class="synConstant">&quot;IF&quot;</span>) <span class="synStatement">or</span> t.match(Keyword, <span class="synConstant">&quot;ELSE&quot;</span>), <span class="synConstant">0</span>) </pre> <p>もしくは</p> <pre class="code lang-python" data-lang="python" data-unlink>t.token_matching([<span class="synStatement">lambda</span> t: t.match(Keyword, <span class="synConstant">&quot;IF&quot;</span>), <span class="synStatement">lambda</span> t: t.match(Keyword, <span class="synConstant">&quot;ELSE&quot;</span>)], <span class="synConstant">0</span>) </pre> <p>とすればOKです。</p> <p>戻り値はトークンのインデックスです。 マッチしなかった場合にはNoneが返ってきます。</p> <h3>4.2.4 トークンインデックスの取得</h3> <p>トークンオブジェクトが得られているけれど、そのインデックスが不明という場合にはtoken_indexメソッドを使います。 <code>t</code>とその子トークン<code>u</code>が与えられたとき、<code>u</code>のインデックス<code>i_u</code>は<code>t.token_index(u)</code>で得られます。 <code>u</code>が<code>t</code>の子ではない場合にはValueErrorがraiseされるので注意しましょう。</p> <h2>4.3 値の解析</h2> <p>基本的には<code>value</code>属性を正規表現等で解析するだけなのですが、 いくつかのケースはsqlparseに定義されている機能を使った方が確実なので、それらも簡単に紹介します。</p> <h3>4.3.1 名前の取得</h3> <p>テーブルやサブクエリ、変数等はユーザーが定義した名前やエイリアスを持っている場合があり、それらは以下の4メソッドで取得できます。</p> <ul> <li><code>get_real_name</code>: 名前を返す。</li> <li><code>get_alias</code>: エイリアスを返す。</li> <li><code>get_name</code>: 呼び出し名を返す。エイリアスと名前が両方定義されている場合は、そのトークンが存在する場所での名称を返す。</li> <li><code>get_parent_name</code>: <code>parent_name</code> (<code>table_foo.col_bar</code>であれば<code>table_foo</code>) を返す。</li> </ul> <p>いずれも、値が取得できなかった場合にはNoneを返します。</p> <h3>4.3.2 引数の取得</h3> <p>Functionクラスのトークンオブジェクトは、その対応する関数の引数を<code>get_parameters</code>メソッドで取り出すことができます。</p> <p>戻り値は各引数を表現したトークンです。</p> <h3>4.3.3 CASE文の各条件の取得</h3> <p>CASE文で指定される複数の条件は、Caseクラスのトークンオブジェクトに定義されている<code>get_cases</code>メソッドで取り出すことができます。</p> <p>戻り値は<code>(条件, 値)</code>という形式のタプルのリストです。 ELSEの場合は条件の値がNoneになります。</p> <h3>4.3.4 値の正規化</h3> <p>value属性に入っている値は解析対象のクエリの文字列そのままなので、 SELECTが"Select"、" select"として格納されている可能性があります。 一方で<code>normalize</code>属性には正規化済みの値(上の例では共通して"SELECT")が入っているので、 各種キーワードを解析したい場合にはこちらを使いましょう。</p> <h1>5. おわりに</h1> <p>ここまでの2回の記事でsqlparseを使うための前提知識をあらかた押さえることはできると思います。</p> <p>とはいえ、sqlparseは「どんな機能があるのか」が分かった上で「それをどう活用するのか」という部分を理解するのが重要なライブラリだと思います。 そこで、次回の記事では(今度こそ)実際にsqlparseを本格的に活用するサンプルを紹介したいと思います。</p> <p>一応、次回解説するサンプルコードは記事の公開に先立って公開します。 プログラムを動かして遊べるように簡単なインターフェースも実装しているので、よければ遊んでみてください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2FArten013%2Fsqlparse_sample2" title="Arten013/sqlparse_sample2" 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://github.com/Arten013/sqlparse_sample2">github.com</a></cite></p> <p>Pythonプログラミングという観点からも、これまでより高度な内容になりますが、 次回もぜひ読んでいただければ幸いです。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/429711111X/hoxom-22/"><img src="https://images-fe.ssl-images-amazon.com/images/I/51I1%2BdhPwkL._SL160_.jpg" class="hatena-asin-detail-image" alt="Python実践入門 ── 言語の力を引き出し、開発効率を高める (WEB+DB PRESS plusシリーズ)" title="Python実践入門 ── 言語の力を引き出し、開発効率を高める (WEB+DB PRESS plusシリーズ)"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/429711111X/hoxom-22/">Python実践入門 ── 言語の力を引き出し、開発効率を高める (WEB+DB PRESS plusシリーズ)</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/%C6%AB%BB%B3%20%CE%E6" class="keyword">陶山 嶺</a></li><li><span class="hatena-asin-detail-label">出版社/メーカー:</span> 技術評論社</li><li><span class="hatena-asin-detail-label">発売日:</span> 2020/01/24</li><li><span class="hatena-asin-detail-label">メディア:</span> 単行本(ソフトカバー)</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> <div class="footnote"> <p class="footnote"><a href="#fn-e348db81" name="f-e348db81" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">このように書いていますが、自分の知る限りでは狭義の構文解析の入出力はたいていの場合で sqlparse と同様です。</span></p> <p class="footnote"><a href="#fn-f63de80f" name="f-f63de80f" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">Pythonだとsorted関数におけるkey引数が代表例です。</span></p> </div> kazuya_fujioka 学習済み日本語word2vecとその評価について hatenablog://entry/26006613512470499 2020-02-20T09:00:00+09:00 2020-02-21T12:25:21+09:00 ホクソエムサポーターの白井です。 今回は日本語の word2vec に着目し、日本語の学習済み word2vec の評価方法について紹介します。 自然言語は非構造化データであるため、単語や文章を計算機で扱いやすい表現に変換する必要があります。 そのための方法の1つに word2vec があり、Bag of Words (BoW) や tf-idf とならんでよく用いられます。 一般に、word2vec は Mikolovが提案した手法 (CBOW, Skip-gram) をはじめ、 GloVe や fastText など、単語をベクトルで表現する単語分散表現のことを指します。 word2vec… <p>ホクソエムサポーターの白井です。 今回は<strong>日本語</strong>の word2vec に着目し、日本語の学習済み word2vec の評価方法について紹介します。</p> <p>自然言語は非構造化データであるため、単語や文章を計算機で扱いやすい表現に変換する必要があります。 そのための方法の1つに word2vec があり、<a href="https://en.wikipedia.org/wiki/Bag-of-words_model">Bag of Words (BoW)</a> や <a href="https://en.wikipedia.org/wiki/Tf%E2%80%93idf">tf-idf</a> とならんでよく用いられます。</p> <p>一般に、word2vec は <a href="https://arxiv.org/abs/1301.3781">Mikolovが提案した手法 (CBOW, Skip-gram)</a> をはじめ、 <a href="https://nlp.stanford.edu/projects/glove/">GloVe</a> や <a href="https://fasttext.cc">fastText</a> など、単語をベクトルで表現する単語分散表現のことを指します。</p> <p>word2vec は教師なし学習のため、コーパスさえ準備できれば誰でも新しい単語分散表現を学習することができます。 しかし、実際に word2vec を使う際に、どのように評価すれば良いのかがよく分からず、配布されている学習済みモデルを適当に選んで使ってしまうことも多いかと思います。</p> <p>そこで、本記事では、日本語 word2vec の評価方法を紹介し、実際に日本語の学習済み word2vec を評価してみます。 基本的な評価方法は英語の場合と同様ですが、日本語では前処理として<strong>分かち書き</strong>が必要となるため、単語の分割単位を考慮する必要があります。</p> <p>今回評価するにあたって書いたコードはGithubで公開しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fshihono%2Fevaluate_japanese_w2v" title="shihono/evaluate_japanese_w2v" 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://github.com/shihono/evaluate_japanese_w2v">github.com</a></cite></p> <h2>1. word2vec の評価方法</h2> <p>ここでは、学習済み word2vec モデルの評価方法を2つ紹介します。 その他にも、固有表現抽出や文書分類などの、実際の解きたいタスクに適用した結果で word2vec を評価する方法もありますが、今回は word2vec そのものを評価する方法にフォーカスします。</p> <p>1つ目の評価方法は、単語同士の <strong>類似度・関連性</strong> を測る方法です。2つの単語が意味的に似ているかどうかを判定することで、学習された表現を評価します。</p> <p>これを行うためのデータセットとして、英語の場合、<a href="http://alfonseca.org/eng/research/wordsim353.html">WordSim353</a> が有名です。 このデータセットは353の単語ペアと、その単語ペアの類似度(1~10)で構成されています。</p> <p><img src="https://i.imgur.com/IpQXJyk.png" alt="" /> <br/>実際のデータ</p> <p>データセットにおける単語ペアの類似度と、学習済みモデルにおける単語ペアの類似度の<a href="https://en.wikipedia.org/wiki/Spearman%27s_rank_correlation_coefficient">スピアマンの順位相関係数</a>を算出し、評価指標とします。</p> <p>2つ目の評価方法は、<a href="https://arxiv.org/abs/1301.3781">Mikolovの論文</a> で紹介されている、単語を類推する <strong>アナロジータスク</strong> です。</p> <p>アナロジータスクとは、例えば、<code>king - man + woman = queen</code> のように、king と man の関係が queen における woman であることを予測するタスクです。</p> <p>これを行うためのデータセットは、 <a href="[https://aclweb.org/aclwiki/Google_analogy_test_set_(State_of_the_art)]">Google_analogy_test_set_(State_of_the_art)</a> から入手できます (論文に記載されているリンクは切れているので注意してください)。 このデータセットには次の表のようなデータが含まれています。</p> <p><img src="https://i.imgur.com/lInEkoe.png" alt="table1" /> from <a href="https://arxiv.org/abs/1301.3781">Efficient Estimation of Word Representations in Vector Space</a></p> <h2>2. 日本語の評価データセット</h2> <p>上記では英語の評価用データセットを紹介しましたが、日本語の word2vec モデルを評価するには、日本語のデータセットを使う必要があります。</p> <p>ここでは、日本語 word2vec の評価に使えるデータセットを紹介します。</p> <h4>日本語類似度・関連度データセット (JWSAN)</h4> <ul> <li>Data: <a href="http://www.utm.inf.uec.ac.jp/JWSAN/">http://www.utm.inf.uec.ac.jp/JWSAN/</a></li> <li>名詞・動詞・形容詞の <strong>類似度・関連度</strong> データセット</li> <li>類似度と関連度がそれぞれ1~7の間の値で付与されている</li> <li>すべての単語ペア2145組のデータセット (jwsan-2145) と、分散表現に適したデータに厳選した1400組のデータセット (jwsan-1400) が存在</li> </ul> <h4>日本語単語類似度データセット (JapaneseWordSimilarityDataset)</h4> <ul> <li>GitHub: <a href="https://github.com/tmu-nlp/JapaneseWordSimilarityDataset">https://github.com/tmu-nlp/JapaneseWordSimilarityDataset</a></li> <li>動詞・形容詞・名詞・副詞の <strong>類似度</strong> データセット</li> <li>類似度が0~10の間で付与されている</li> </ul> <h4>The Revised Similarity Dataset for Japanese (jSIM)</h4> <ul> <li>Data: <a href="https://vecto.space/projects/jSIM/">https://vecto.space/projects/jSIM/</a></li> <li>上記JapaneseWordSimilarityDatasetを追加・修正した <strong>類似度</strong> データセット <ul> <li>full version: 品詞カテゴリを修正</li> <li>tokenized version: full versionをmecabで分かち書き</li> <li>Unambiguous: tokenized versionから分かち書きに曖昧性がない単語のみ選出</li> </ul> </li> </ul> <h4>The Japanese Bigger Analogy Test Set (jBATS)</h4> <ul> <li>Data: <a href="https://vecto.space/projects/BATS/">https://vecto.space/projects/BATS/</a></li> <li>jSIMと同じ論文において紹介されている、日本語 <strong>アナロジー</strong> データセット。</li> <li><a href="https://pj.ninjal.ac.jp/corpus_center/bccwj/en/freq-list.html">BCCWJ Word</a> から構築</li> </ul> <h2>3. 日本語学習済み word2vec</h2> <p>上記で紹介した評価用データセットを使って、日本語の学習済み word2vec を評価することが本記事の目的です。 本記事では、新たに word2vec を学習することは避け、Web上から入手可能な日本語の学習済み word2vec で評価を行います。 ここでは、誰でも利用可能な日本語学習済み word2vec をまとめます。</p> <p>日本語学習済み word2vec には、エンティティベクトルや白ヤギコーポレーション(以下白ヤギ)のように、日本語だけ作成・公開している場合と、 fastTextのように多言語対応を目的として日本語のモデルを公開している場合があります。</p> <p>これらのモデルはそれぞれ、学習に利用するデータ・ツール・学習方法が異なります。 特に日本語の場合、文字を分かち書きする前処理が必要なため、どの方法・どの辞書を用いて分かち書きを行ったかが、モデルの大きな違いになってきます。</p> <h4>エンティティベクトル (WikiEntVec)</h4> <ul> <li>Site: <a href="http://www.cl.ecei.tohoku.ac.jp/~m-suzuki/jawiki_vector/">http://www.cl.ecei.tohoku.ac.jp/~m-suzuki/jawiki_vector/</a></li> <li>Github: <a href="https://github.com/singletongue/WikiEntVec/">https://github.com/singletongue/WikiEntVec/</a></li> </ul> <p>Wikipediaで学習されたモデルです。分かち書きにはmecab (neologd) が利用されています。</p> <p>Wikipediaが日々更新されているためか、定期的に新しいモデルがリリースされているようです。 <a href="https://github.com/singletongue/WikiEntVec/releases">https://github.com/singletongue/WikiEntVec/releases</a> で最新のモデルが確認可能です。</p> <h4>白ヤギ (Japanese Word2Vec Model Builder)</h4> <ul> <li>Site: <a href="https://aial.shiroyagi.co.jp/2017/02/japanese-word2vec-model-builder/">https://aial.shiroyagi.co.jp/2017/02/japanese-word2vec-model-builder/</a></li> <li>Github: <a href="https://github.com/shiroyagicorp/japanese-word2vec-model-builder">https://github.com/shiroyagicorp/japanese-word2vec-model-builder</a></li> </ul> <p>こちらもmecab (neologd) による分かち書きで、Wikipediaで学習されたモデルです。 50次元のみ公開されています。</p> <h4>chiVe</h4> <ul> <li>Github: <a href="https://github.com/WorksApplications/chiVe">https://github.com/WorksApplications/chiVe</a></li> </ul> <p>Sudachiの開発元であるWorks applications が公開しているモデルです。 こちらは 国立国語研究所の日本語ウェブコーパス(NWJC)で学習されたモデルです。また、分かち書きにはSudachiを使用しています。</p> <h4>fastText</h4> <ul> <li>Github: <a href="https://github.com/facebookresearch/fastText/">https://github.com/facebookresearch/fastText/</a> <ul> <li>crawl-vectors: <a href="https://github.com/facebookresearch/fastText/blob/master/docs/crawl-vectors.md">https://github.com/facebookresearch/fastText/blob/master/docs/crawl-vectors.md</a></li> </ul> </li> </ul> <p>fastText実装で学習されたモデルです。多言語モデルの実装中に日本語が含まれています。分かち書きにはmecabが用いられているという説明があります。 どの辞書を利用したかの記載がないのですが、おそらくデフォルトのipadicであると考えられます。</p> <h3>3.1 日本語学習済み word2vec まとめ</h3> <p>上記モデルを含め、公開されているword2vecについて、個人的に見つけた結果を以下にまとめました。</p> <table> <thead> <tr> <th> Name </th> <th> Model </th> <th> Data </th> <th style="text-align:right;"> Dim </th> <th> Tokenizer </th> <th> Dict </th> </tr> </thead> <tbody> <tr> <td> <a href="https://github.com/singletongue/WikiEntVec/">WikiEntVec</a> </td> <td> Skip-gram? </td> <td> Wikipedia </td> <td style="text-align:right;"> 100,200,300 </td> <td> mecab </td> <td> mecab-ipadic-NEologd </td> </tr> <tr> <td> <a href="https://github.com/shiroyagicorp/japanese-word2vec-model-builder">白ヤギ</a> </td> <td> CBOW? </td> <td> Wikipedia </td> <td style="text-align:right;"> 50 </td> <td> mecab </td> <td> mecab-ipadic-NEologd </td> </tr> <tr> <td> <a href="https://github.com/WorksApplications/chiVe">chiVe</a> </td> <td> Skip-gram </td> <td> NWJC </td> <td style="text-align:right;"> 300 </td> <td> Sudachi </td> <td> </td> </tr> <tr> <td> <a href="https://github.com/bizreach/ai/tree/master/word2vec">bizreach</a> </td> <td> Skip-gram </td> <td> 求人データ </td> <td style="text-align:right;"> 100, 200 </td> <td> mecab </td> <td> ipadic </td> </tr> <tr> <td> <a href="https://github.com/lapras-inc/dependency-based-japanese-word-embeddings">dependency-based-japanese-word-embeddings</a> </td> <td> <a href="https://levyomer.wordpress.com/2014/04/25/dependency-based-word-embeddings/">Dependency-Based Word Embeddings</a> </td> <td> Wikipedia </td> <td style="text-align:right;"> 100, 200, 300 </td> <td> Ginza </td> <td> </td> </tr> <tr> <td> <a href="https://github.com/hottolink/hottoSNS-w2v">hottoSNS-w2v</a><br/>(※要問い合わせ) </td> <td> CBOW </td> <td> ブログ, Twitter </td> <td style="text-align:right;"> 200 </td> <td> Juman, mecab </td> <td> mecab-ipadic-NEologd </td> </tr> <tr> <td> <a href="https://cl.asahi.com/api_data/wordembedding.html">朝日新聞単語ベクトル</a><br/> (※要問い合わせ) </td> <td> Skip-gram, CBOW, Glove </td> <td> 朝日新聞 </td> <td style="text-align:right;"> 300 </td> <td> mecab </td> <td> ipadic </td> </tr> <tr> <td> <a href="https://github.com/facebookresearch/fastText/blob/master/docs/crawl-vectors.md">fastText</a> </td> <td> CBOW </td> <td> Common Crawl, Wikipedia </td> <td style="text-align:right;"> 300 </td> <td> mecab </td> <td> ? </td> </tr> <tr> <td> <a href="https://github.com/wikipedia2vec/wikipedia2vec">wikipedia2vec</a> </td> <td> Skip-gram </td> <td> Wikipedia </td> <td style="text-align:right;"> 100, 300 </td> <td> mecab </td> <td> ? </td> </tr> <tr> <td> <a href="https://github.com/Kyubyong/wordvectors">wordvectors</a> </td> <td> Skip-gram?, fastText </td> <td> Wikipedia </td> <td style="text-align:right;"> 300 </td> <td> mecab </td> <td> ? </td> </tr> </tbody> </table> <p>学習方法 (Skip-gram or CBOW) について、READMEなどのドキュメントに明記されておらず、学習コードのパラメーターから判断したモデルに関しては <em>?</em> をつけています。 (具体的には<a href="https://radimrehurek.com/gensim/models/word2vec.html#gensim.models.word2vec.Word2Vec">gensim.models.word2vec.Word2Vec</a>のパラメータ <code>sg</code> で判断しています)</p> <h2>4. 日本語 word2vec の評価</h2> <p>学習済み word2vec に対して、単語類似度の評価データセットを使ってスコアを算出し、比較してみます。</p> <p>今回比較に利用するモデルとデータセットは以下の通りです。モデルの次元数が一致していないことには注意が必要です。 (次元数が大きいほど表現力が高くなるため)</p> <p><strong>word2vecモデル</strong> (カッコ内は <em>次元数 × 語彙数</em> )</p> <ul> <li>WikiEntVec (200 × 1,015,474) <ul> <li>HPで公開されている2017年のモデル</li> </ul> </li> <li>白ヤギ (50 × 335,476)</li> <li>chiVe (300 × 3,644,628)</li> <li>fastText (300 × 2,000,000) <ul> <li>gensimで読み込むため<code>txt</code> のデータ</li> </ul> </li> </ul> <p><strong>評価データセット</strong></p> <ul> <li>JWSAN <ul> <li>2145 (jwsan-2145)・ 1400 (jwsan-1400)</li> </ul> </li> <li>JapaneseWordSimilarityDataset <ul> <li>adv (副詞)・verb (動詞)・noun (名詞) ・adj (形容詞)</li> </ul> </li> </ul> <p>また、JWSANに評価用スクリプトがなかったため、評価コードを実装し、公開しています (<a href="https://github.com/shihono/evaluate_japanese_w2v">Github</a>)。 複数のデータセットで評価するにあたって、スピアマンの順位相関係数はSciPyで実装されている<a href="https://docs.scipy.org/doc/scipy-0.16.1/reference/generated/scipy.stats.spearmanr.html">spearmanr</a> で統一しました。 未知語については評価から取り除いています。</p> <p>実験結果 (スピアマンの順位相関係数) は以下のとおりです。太字がデータセットごとで最も良いスコアを示しています。 chiVeとfastTextが比較的良いスコアを出していることがわかります。</p> <table> <tr> <th></th> <th colspan="4">JapaneseWordSimilarityDataset</th> <th colspan="2">JWSAN (similarity)</th> </tr> <tr> <td></td> <td>adv</td> <td>verb</td> <td>noun</td> <td>adj</td> <td>2145</td> <td>1400</td> </tr> <tr> <td>WikiEntVec</td> <td align="right">0.250</td> <td align="right" >0.334</td> <td align="right">0.292</td> <td align="right">0.231</td> <td align="right">0.643</td> <td align="right">0.499</td> </tr> <tr> <td>白ヤギ</td> <td align="right">0.214</td> <td align="right">0.299</td> <td align="right">0.243</td> <td align="right">0.231</td> <td align="right">0.581</td> <td align="right">0.416</td> </tr> <tr> <td>chiVe</td> <td align="right"><b>0.394</b></td> <td align="right">0.326</td> <td align="right"><b>0.361</b></td> <td align="right"><b>0.475</b></td> <td align="right">0.701</td> <td align="right">0.541</td> </tr> <tr> <td>fastText</td> <td align="right">0.350</td> <td align="right"><b>0.386</b></td> <td align="right">0.357</td> <td align="right">0.459</td> <td align="right"><b>0.737</b></td> <td align="right"><b>0.610</b></td> </tr> </table> <p>また、実験における、未知語の割合は以下のとおりです。JWSDはJapaneseWordSimilarityDatasetを示しています。</p> <p><img src="https://i.imgur.com/McqbhJv.png" alt="oov" /></p> <h3>4.1 分かち書き</h3> <p>上記の結果で、未知語の割合が非常に高いのが気になります。 未知語とは辞書中に存在しない単語<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup> ですが、 word2vec においてはモデルの語彙に含まれない単語のことを意味します。しかしながら、膨大なデータから学習したモデルが、評価データセットに含まれる単語をほとんど学習していないというのはあり得ない気がします。</p> <p>未知語扱いになってしまう原因として、分かち書きが考えられます。 jSIMの紹介でも述べましたが、評価データセットには、分かち書きすると複数の単語に分かれるタイプの単語 (複合語・派生語) が含まれています。</p> <p>例えば、JapaneseWordSimilarityDatasetに含まれる動詞「掴んだ」「寂れた」はmecabの解析結果で以下のように、動詞と助動詞に分割されます。</p> <pre class="code" data-lang="" data-unlink>掴んだ 掴ん 動詞,自立,*,*,五段・マ行,連用タ接続,掴む,ツカン,ツカン だ 助動詞,*,*,*,特殊・タ,基本形,だ,ダ,ダ 寂れた 寂れ 動詞,自立,*,*,一段,連用形,寂れる,サビレ,サビレ た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ</pre> <p>また、「議論した」「ディスカッションする」のような単語は、以下のように、名詞と動詞(と助動詞)に分割されます。</p> <pre class="code" data-lang="" data-unlink>議論した 議論 名詞,サ変接続,*,*,*,*,議論,ギロン,ギロン し 動詞,自立,*,*,サ変・スル,連用形,する,シ,シ た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ ディスカッションする ディスカッション 名詞,サ変接続,*,*,*,*,ディスカッション,ディスカッション,ディスカッション する 動詞,自立,*,*,サ変・スル,基本形,する,スル,スル</pre> <p>このような、評価データとモデルの分割単位の不一致の問題点を解消するため、今回は分かち書きに対応した類似度も算出しました。 具体的には、モデルが未知語だった単語について、分かち書きをし、複数単語の和をその単語のベクトルとして扱います。 「議論した」であれば、「議論」「し」「た」のそれぞれのベクトルの和を「議論した」ベクトルとみなします。 (評価コードの <a href="https://github.com/shihono/evaluate_japanese_w2v/blob/ff2f240e7427e54d9a4a1514f41d1cc0b4b222b8/src/ja_tokenizer.py#L58-L75"> get_divided_wv</a> がその実装に当たります)</p> <p>学習済みword2vecの分かち書き手法に合わせた未知語の割合は以下グラフのとおりです。どのモデルについても、未知語が減ったことがわかります。 特にchiVeは全てのデータセットにおいて未知語が0になりました。語彙数が4つの中で最も多く、また、Sudachiの3つの分割方法を考慮したモデルのため、柔軟に対応できていることが理由として考えられます。</p> <p><img src="https://i.imgur.com/3xW8wJO.png" alt="compare" /></p> <p>分かち書きを利用した場合のスピアマンの順位相関係数は以下のようになりました。 評価するデータが増えていることもあり、スコア自体は下がっています。</p> <table> <tr> <th></th> <th colspan="4">JapaneseWordSimilarityDataset</th> <th colspan="2">JWSAN (similarity)</th> </tr> <tr> <td></td> <td>adv</td> <td>verb</td> <td>noun</td> <td>adj</td> <td>2145</td> <td>1400</td> </tr> <tr> <td>WikiEntVec</td> <td align="right">0.182</td> <td align="right">0.149</td> <td align="right">0.248</td> <td align="right">0.158</td> <td align="right"><b>0.733</b></td> <td align="right"><b>0.610</b></td> </tr> <tr> <td>白ヤギ</td> <td align="right">0.155</td> <td align="right">0.223</td> <td align="right">0.202</td> <td align="right">0.257</td> <td align="right">0.580</td> <td align="right">0.416</td> </tr> <tr> <td>chiVe</td> <td align="right">0.255</td> <td align="right"><b>0.260</b></td> <td align="right"><b>0.310</b></td> <td align="right"><b>0.404</b></td> <td align="right">0.701</td> <td align="right">0.541</td> </tr> <tr> <td>fasttext</td> <td align="right"><b>0.301</b></td> <td align="right">0.181</td> <td align="right">0.293</td> <td align="right">0.336</td> <td align="right"><b>0.733</b></td> <td align="right"><b>0.610</b></td> </tr> </table> <p>(参考) 分かち書きありなしのスコアの比較表 <img src="https://i.imgur.com/RWkzEcs.png" alt="compare_table" /></p> <h3>4.2 ケーススタディ</h3> <p>実際に算出された類似度をみてみます。</p> <p>未知語が0になったchiVeモデルの、jwsan-1400の結果に注目します。</p> <p>類似度が高いと出力した単語ペアをみると、 「高校」「中学」や「裁判」「訴訟」のように、 意味的に似ている単語が上位になっています。 しかし、「動詞」と「主語」のような (どちらも文の構成要素の一つではあるものの) 対義的な意味である単語ペアも似ている扱いになっていました。</p> <table> <thead> <tr> <th> word1 </th> <th> word2 </th> <th style="text-align:right;"> 正解 </th> <th style="text-align:right;"> 予測 </th> </tr> </thead> <tbody> <tr> <td> 高校 </td> <td> 中学 </td> <td style="text-align:right;"> 2.54 </td> <td style="text-align:right;"> 0.851 </td> </tr> <tr> <td> 書店 </td> <td> 本屋 </td> <td style="text-align:right;"> 5.45 </td> <td style="text-align:right;"> 0.836 </td> </tr> <tr> <td> 裁判 </td> <td> 訴訟 </td> <td style="text-align:right;"> 3.5 </td> <td style="text-align:right;"> 0.814 </td> </tr> <tr> <td> 出版 </td> <td> 刊行 </td> <td style="text-align:right;"> 4.01 </td> <td style="text-align:right;"> 0.792 </td> </tr> <tr> <td> 動詞 </td> <td> 主語 </td> <td style="text-align:right;"> 1.38 </td> <td style="text-align:right;"> 0.791 </td> </tr> </tbody> </table> <p>一方で、以下の「写し」「複製」のように、 正解データは類似度が高いにも関わらず、モデルは類似度が低いと出力したペアもありました。</p> <table> <thead> <tr> <th> word1 </th> <th> word2 </th> <th style="text-align:right;"> 正解 </th> <th style="text-align:right;"> 予測 </th> </tr> </thead> <tbody> <tr> <td> 写し </td> <td> 複製 </td> <td style="text-align:right;"> 4.73 </td> <td style="text-align:right;"> 0.283 </td> </tr> <tr> <td> 支度 </td> <td> 用意 </td> <td style="text-align:right;"> 4.71 </td> <td style="text-align:right;"> 0.339 </td> </tr> <tr> <td> 決まり </td> <td> 規律 </td> <td style="text-align:right;"> 4.64 </td> <td style="text-align:right;"> 0.167 </td> </tr> <tr> <td> 焼く </td> <td> 燃やす </td> <td style="text-align:right;"> 4.36 </td> <td style="text-align:right;"> 0.363 </td> </tr> </tbody> </table> <h2>5. まとめ</h2> <p>日本語学習済み word2vec とその評価方法について紹介しました。</p> <p>今回全体的に精度が良かった chiVe ですが、モデルサイズが12.5GB程度あるので、実際に利用する場合はメモリ等の環境を気にする必要がありそうです。 (fastTextが4.5GB、WikiEntVecが2.0GB程度ということも考慮すると、かなり大きいことがわかるかと思います。)</p> <p>白ヤギは未知語が多く精度が低かったものの、今回扱った4つのモデルの中では最もモデルサイズが小さい (24MB程度) です。</p> <p>今回紹介した類似度の精度だけでなく、環境や状況に応じて学習済み word2vec を使い分けることが必要だと思います。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4764905779/hoxom-22/"><img src="https://images-fe.ssl-images-amazon.com/images/I/51HIVHfLm2L._SL160_.jpg" class="hatena-asin-detail-image" alt="形態素解析の理論と実装 (実践・自然言語処理シリーズ)" title="形態素解析の理論と実装 (実践・自然言語処理シリーズ)"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4764905779/hoxom-22/">形態素解析の理論と実装 (実践・自然言語処理シリーズ)</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/%B9%A9%C6%A3%20%C2%F3" class="keyword">工藤 拓</a></li><li><span class="hatena-asin-detail-label">出版社/メーカー:</span> 近代科学社</li><li><span class="hatena-asin-detail-label">発売日:</span> 2018/10/04</li><li><span class="hatena-asin-detail-label">メディア:</span> 単行本</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> <div class="footnotes"> <hr/> <ol> <li id="fn:1"> <p><a href="https://ja.wikipedia.org/wiki/%E6%9C%AA%E7%9F%A5%E8%AA%9E">https://ja.wikipedia.org/wiki/未知語</a> より<a href="#fnref:1" rev="footnote">&#8617;</a></p></li> </ol> </div> sh111h GitHub Actions実行時に依存するRパッケージのインストールをキャッシュ化する hatenablog://entry/26006613501463731 2020-01-23T17:17:00+09:00 2020-01-23T17:48:30+09:00 ホクソエムの u_ribo です。漫画「ブリーチ」の石田雨竜に親近感を感じます。仕事はシュッと終わらせて趣味の時間を増やしたいですよね。 要約 GitHub Actionsに対してrenvを使ったキャッシュ機能を活用。依存するRパッケージのインストール時間を短縮する パッケージのインストールに要する時間を1/25に短縮 renvのキャッシュはOSによりパスが異なるため、GitHub Actionsを実行するOSに応じて変更が必要になる キャッシュ機能はpipでも使えるため、Pythonによる処理を適用するときも便利 GitHub Actionsでrenvのキャッシュを利用するサンプル pkgd… <p>ホクソエムの <a href="https://twitter.com/u_ribo">u_ribo</a> です。漫画「ブリーチ」の石田雨竜に親近感を感じます。仕事はシュッと終わらせて趣味の時間を増やしたいですよね。</p> <h2>要約</h2> <ul> <li>GitHub Actionsに対してrenvを使ったキャッシュ機能を活用。依存するRパッケージのインストール時間を短縮する <ul> <li><strong>パッケージのインストールに要する時間を1/25に短縮</strong></li> <li>renvのキャッシュはOSによりパスが異なるため、GitHub Actionsを実行するOSに応じて変更が必要になる</li> <li>キャッシュ機能はpipでも使えるため、Pythonによる処理を適用するときも便利</li> </ul> </li> <li>GitHub Actionsでrenvのキャッシュを利用するサンプル <ul> <li>pkgdownによるウェブサイトのビルド <a href="https://github.com/uribo/easyestat/actions">https://github.com/uribo/easyestat/actions</a></li> <li>リポジトリ中のRファイルを実行 <a href="https://github.com/uribo/renv_ghaction/actions">https://github.com/uribo/renv_ghaction/actions</a></li> <li>OSに応じた設定の例 <a href="https://github.com/actions/cache/blob/master/examples.md#r---renv">https://github.com/actions/cache/blob/master/examples.md#r---renv</a></li> </ul> </li> </ul> <h2>はじめに</h2> <p>GitHub上でビルド、テスト、デプロイ等の作業を自動的に行える<a href="https://github.com/features/actions">GitHub Actions</a>が便利ですね。RやPythonも実行可能なため、データ分析の作業を補助する機能を持たせることもできるかと思います。例えば、リポジトリ上のデータが更新されたタイミングで分析コードを走らせてレポートを作成するといった具合です。このブログでも <a href="http://blog.hatena.ne.jp/shinichi-takayanagi/">id:shinichi-takayanagi</a> さんが記事を書かれています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.hoxo-m.com%2Fentry%2F2019%2F12%2F14%2F174701" title="GitHub Actions でRのパッケージの継続的インテグレーション(CI)を行う - 株式会社ホクソエムのブログ" 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://blog.hoxo-m.com/entry/2019/12/14/174701">blog.hoxo-m.com</a></cite></p> <p>そんなGitHub Actionsですが、RやPythonのコードを実行する際にパッケージのインストールが必要になる場合があります。パッケージの追加もコマンドで行えるため、それ自体は問題になりません。しかし処理時間に関してはどうでしょう。パッケージのインストールはGitHub Actionsが動作するたびに実行されます。依存パッケージが多い・頻繁に実行されるジョブでは、ここでの作業がジョブ全体に要する時間の大部分を占める恐れがあります。</p> <p>そこで、ジョブの過程で取得した依存パッケージを次回の実行時に再利用できるようにする、<a href="https://github.com/actions/cache">キャッシュ機能</a>を活用します。これにより実行時間の短縮が期待できます。</p> <p>公式のExampleを見ると、Pythonであればpip、Nodeはnpmやyarnを利用したパッケージ管理ツールを利用する方法が書かれています。では<strong>Rの場合はどうするの?</strong>が本記事の話題です。ここではRパッケージのインストール結果をキャッシュ化する例として、Rのパッケージ管理に<a href="https://rstudio.github.io/renv/">renv</a>を利用して、pkgdownでのウェブサイトの構築を行うワークフローに導入することとします。</p> <p>pkgdownでのウェブサイトの構築を行うワークフローについては <a href="http://blog.hatena.ne.jp/yutannihilation/">id:yutannihilation</a> さんの下記の記事をご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnotchained.hatenablog.com%2Fentry%2F2020%2F01%2F10%2F221249" title="メモ: GitHub Actionsでpkgdownサイトをビルドする - Technically, technophobic." 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://notchained.hatenablog.com/entry/2020/01/10/221249">notchained.hatenablog.com</a></cite></p> <p>この記事を参考に、まずはキャッシュ機能を使わないGitHub Actionsの設定を済ませます。本記事では、ここで用意したyamlファイルを編集します。</p> <h2>renvでパッケージ管理</h2> <p>renvはRStudioにより開発されているパッケージ管理のためのパッケージです。プロジェクトで利用されるパッケージの依存関係を明らかにし、再現可能な形で環境を構築します。具体的にはプロジェクトで使われるRパッケージとそのバージョン、インストール元の情報等を <code>renv.lock</code> ファイルに記録します。</p> <p><iframe id="talk_frame_568107" src="//speakerdeck.com/player/3946f274dacc4860b6e0a0ff1305e2a5" width="710" height="399" style="border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe><cite class="hatena-citation"><a href="https://speakerdeck.com/s_uryu/pyrintegration?slide=9">speakerdeck.com</a></cite></p> <p>用意したプロジェクトに対してrenvによる管理を有効化しましょう。<code>renv::init()</code> を実行すると <code>renv/</code>フォルダ、<code>renv.lock</code>ファイルが生成されます(<code>.Rprofile</code>がない場合はこれも)。</p> <p>この時、すでにRコードが存在する場合、利用するパッケージおよびその依存パッケージがrenvによりインストールされ、その情報が<code>renv.lock</code>に記録されます。パッケージのインストール先は従来Rが利用する環境(<code>/usr/local/lib/R/site-library/</code> や <code>/Library/Frameworks/R.framework/Versions/{Rバージョン}/Resources/library</code>)とは異なる環境となります。それはホームディレクトリに近い場所とrenvを有効化したプロジェクトの中です。</p> <p>最初の「ホームディレクトリに近い場所」は<strong>OSごとに異なります</strong>。具体的には以下のとおりです。</p> <table> <thead> <tr> <th> プラットフォーム </th> <th> 場所 </th> </tr> </thead> <tbody> <tr> <td> Linix </td> <td> <code>~/.local/share/renv</code> </td> </tr> <tr> <td> macOS </td> <td> <code>~/Library/Application Support/renv</code> </td> </tr> <tr> <td> Windows </td> <td> <code>%LOCALAPPDATA%/renv</code> </td> </tr> </tbody> </table> <p>renvを使ったプロジェクトでパッケージをインストールするとこのディレクトリにファイルが保存されます(renvのキャッシュを無効化した場合はプロジェクトの中に直接保存されます)。そのため、他のRプロジェクトでパッケージのアップデートを行ってもその影響を受けません。また、依存関係も記述されているので再インストールも安全に行えます。</p> <p><code>renv.lock</code>に書かれたパッケージを復元するには<code>renv::restore()</code>を実行します。一度インストールされたパッケージであればキャッシュからインストールが行われるため、ファイルのダウンロード、ビルドの作業が省略されます。</p> <p>またプロジェクトで利用するパッケージに変更(追加や更新、削除)があった際は <code>renv::status()</code> で確認、必要に応じて <code>renv::snapshot()</code> で <code>renv::lock</code> を更新しましょう。</p> <p>詳しい利用方法はここでは省略します。興味のある方は</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fokiyuki99%2Fitems%2F688a00ca9a58e42e3bfa" title="Rのパッケージ管理のためのrenvを使ってみた - 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/okiyuki99/items/688a00ca9a58e42e3bfa">qiita.com</a></cite></p> <p><iframe id="talk_frame_557797" src="//speakerdeck.com/player/2182379304ad4e72ae6aeef562e7173b" width="710" height="399" style="border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe><cite class="hatena-citation"><a href="https://speakerdeck.com/black_tank_top/renv-version-control">speakerdeck.com</a></cite></p> <p>をご覧ください。</p> <h2>GitHub Actionsにrenvを導入する</h2> <p>続いてGitHub Actionsにrenvを導入する方法です。pkgdownによるウェブサイトのビルドを行うActionsではmacOS上で動作します。そこでrenvのキャッシュもmacOS仕様にする必要があります。</p> <p><code>.github/workflows/</code> にある pkgdownのウェブページをビルドするYAMLファイルにある以下の箇所を変更します。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Install dependencies <span class="synIdentifier">run</span><span class="synSpecial">:</span> | Rscript -e <span class="synConstant">'install.packages(&quot;remotes&quot;)'</span> \ -e <span class="synConstant">'remotes::install_deps(dependencies = TRUE)'</span> \ -e <span class="synConstant">'remotes::install_github(&quot;jimhester/pkgdown@github-actions-deploy&quot;)'</span> </pre> <ol> <li>renvをインストール</li> <li><code>renv::restore()</code>で必要なパッケージを復元する</li> </ol> <pre class="code lang-yaml" data-lang="yaml" data-unlink> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Install dependencies <span class="synIdentifier">run</span><span class="synSpecial">:</span> |- Rscript -e <span class="synConstant">&quot;install.packages('renv')&quot;</span> \ -e <span class="synConstant">&quot;renv::restore(confirm = FALSE)&quot;</span> </pre> <p>YAMLを書き換えたら、手元のRコンソールで<code>renv::install("jimhester/pkgdown@github-actions-deploy")</code>、<code>renv::snapshot()</code>を実行して<code>renv.lock</code>を更新します。これは元のYAMLに書かれている<code>remotes::install_github("jimhester/pkgdown@github-actions-deploy")</code>の代わりに必要な処理です。</p> <p>続いてキャッシュの指定です。今回はmacOSで動作させているので、キャッシュのpathもmacOSの<code>~/Library/Application Support/renv</code>とします。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink> <span class="synStatement">- </span><span class="synIdentifier">uses</span><span class="synSpecial">:</span> actions/cache@v1 <span class="synIdentifier">if</span><span class="synSpecial">:</span> startsWith(runner.os, <span class="synConstant">'macOS'</span>) <span class="synIdentifier">with</span><span class="synSpecial">:</span> <span class="synIdentifier">path</span><span class="synSpecial">:</span> ~/Library/Application Support/renv <span class="synIdentifier">key</span><span class="synSpecial">:</span> ${{ runner.os }}-renv-${{ hashFiles('**/renv.lock') }} <span class="synIdentifier">restore-keys</span><span class="synSpecial">:</span> | ${{ runner.os }}-renv- </pre> <p><a href="https://github.com/uribo/easyestat/commit/6ece26199282044f60181f381c9c452c9eeb95c5#diff-3f68805a591611b7440d90ea64e3c446">こちら</a>が編集後のyamlファイルです。</p> <p>それではキャッシュ化の効果を<a href="https://github.com/uribo/easyestat/actions">見てみましょう</a>。依存パッケージのインストールにかかった時間 (<code>Install Package Dependencies</code>の部分)を見ます。最初の処理で4分38秒だったのに対し、<strong>キャッシュが働く2回目はわずか11秒で完了</strong>しています。1/25の差は大きいですね。出力を見ても、きちんとキャッシュを利用しているのがわかります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/u/u_ribo/20200123/20200123174147.png" alt="f:id:u_ribo:20200123174147p:plain" title="f:id:u_ribo:20200123174147p:plain" class="hatena-fotolife" itemprop="image"></span></p> <h2>任意のOS、Rコードの実行に必要な依存パッケージを扱う例</h2> <p>renvのキャッシュはOSごとに異なることは述べたとおりです。OSごとのキャッシュ先の指定方法は、<a href="https://github.com/actions/cache/pull/151">PRを出してマージされた</a>のでGitHub Actionsのcacheリポジトリに記載されています。</p> <p><a href="https://github.com/actions/cache/blob/master/examples.md#r---renv">https://github.com/actions/cache/blob/master/examples.md#r---renv</a></p> <p>また、今回解説したウェブサイト構築以外の用途で利用する際のサンプルとして、以下のリポジトリへのリンクを示しておきます。</p> <p><a href="https://github.com/uribo/renv_ghaction/">GitHub - uribo/renv_ghaction: for demonstration</a></p> <p>投げていたジョブが完了しました。お先に失礼します! Enjoy!</p> u_ribo Rと3Dプリンターで八ヶ岳のミニチュアを作る。 hatenablog://entry/26006613485147177 2019-12-19T08:00:00+09:00 2019-12-19T08:00:15+09:00 この記事について この記事はR Advent Calendar 2019の19日目の記事です。 はじめに ホクソエムサポーターの輿石です。最近3Dプリンターを買いました。遠い世界のガジェットのように思っていましたが、家庭用であれば3万円前後で買えてしまうんですね。 3Dプリンターの使い方としてCADで自分の作りたいものを設計していくのが一般的かと思いますが、Rで3Dのプロットを作成することで、データから立体物を作ることが可能です。 この記事では、3Dのプロットを作成できるrayshaderパッケージと、基盤地図情報の地形図データをRに読み込むことができるfgdrパッケージを使って、故郷八ヶ岳周… <h2>この記事について</h2> <p>この記事は<a href="https://qiita.com/advent-calendar/2019/rlang">R Advent Calendar 2019</a>の19日目の記事です。</p> <h2>はじめに</h2> <p>ホクソエムサポーターの輿石です。最近3Dプリンターを買いました。遠い世界のガジェットのように思っていましたが、家庭用であれば3万円前後で買えてしまうんですね。<br/> 3Dプリンターの使い方としてCADで自分の作りたいものを設計していくのが一般的かと思いますが、Rで3Dのプロットを作成することで、データから立体物を作ることが可能です。<br/> この記事では、3Dのプロットを作成できるrayshaderパッケージと、基盤地図情報の地形図データをRに読み込むことができるfgdrパッケージを使って、故郷八ヶ岳周辺のミニチュアを作ってみます。</p> <h2>データの取得</h2> <p>国土地理院の<a href="https://www.gsi.go.jp/kiban/">基盤地図情報サイト</a>からデータをダウンロードします。 今回は数値標高モデルの10mメッシュのデータを使います。八ヶ岳周辺のメッシュコードは533862、533863、533872、533873でした。地名検索機能が付いた地図のUIが用意されており、クリックだけで欲しい場所のデータを指定することができました。</p> <h2>データの読み込み</h2> <p>まずは今回必要になるパッケージを読み込みます。</p> <pre class="code" data-lang="" data-unlink>library(fgdr) library(rayshader) library(raster) library(tidyverse)</pre> <p>基盤地図情報からダウンロードしたxml形式のデータをfgdrパッケージのread_fgd_dem関数を使ってraster形式で読み込みます。広い範囲(複数のメッシュコードの範囲)を対象にしたい場合には複数のxmlファイル結合する必要があるので、読み込んでからraster::margeで結合します。</p> <pre class="code" data-lang="" data-unlink>files &lt;- list.files(&#34;data/&#34;, full.names = T, pattern = &#34;dem10b&#34;) files</pre> <pre class="code" data-lang="" data-unlink>[1] &#34;data/FG-GML-5338-62-dem10b-20161001.xml&#34; &#34;data/FG-GML-5338-63-dem10b-20161001.xml&#34; &#34;data/FG-GML-5338-72-dem10b-20161001.xml&#34; [4] &#34;data/FG-GML-5338-73-dem10b-20161001.xml&#34;</pre> <pre class="code" data-lang="" data-unlink>raster_list &lt;- files %&gt;% map(~fgdr::read_fgd_dem(., resolution = 10, return_class = &#34;raster&#34;)) r &lt;- purrr::reduce(raster_list, raster::merge, tolerance = 0.2)</pre> <h2>rayshaderを使った3D出力</h2> <p>rayshaderは<a href="https://www.rayshader.com/">公式のドキュメント</a>が充実しているので、詳細はこちらが役に立つと思います。<br/> rayshaderで扱えるようにrasterをmatrixに変換してからプロットします。</p> <pre class="code" data-lang="" data-unlink>elmat &lt;- rayshader::raster_to_matrix(r) elmat %&gt;% rayshader::sphere_shade(texture = &#34;desert&#34;) %&gt;% rayshader::plot_map()</pre> <p><figure class="figure-image figure-image-fotolife" title="Yatsugatake_plot_map"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kosshi08/20191216/20191216015641.png" alt="f:id:kosshi08:20191216015641p:plain:w500" title="f:id:kosshi08:20191216015641p:plain:w500" class="hatena-fotolife" style="width:500px" itemprop="image"></span><figcaption>基盤数値情報DEMデータを元に作成</figcaption></figure></p> <pre class="code" data-lang="" data-unlink>elmat %&gt;% rayshader::sphere_shade(texture = &#34;desert&#34;) %&gt;% rayshader::plot_3d(elmat, zscale = 10, fov = 0, theta = 135, zoom = 0.75, phi = 45, windowsize = c(1000, 800)) rayshader::render_snapshot(clear=FALSE)</pre> <p><figure class="figure-image figure-image-fotolife" title="yatsugatake_plot_3d"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kosshi08/20191216/20191216020508.png" alt="f:id:kosshi08:20191216020508p:plain" title="f:id:kosshi08:20191216020508p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>基盤数値情報DEMデータを元に作成</figcaption></figure></p> <h2>3Dプリンターで印刷する</h2> <p>rayshaderパッケージのsave_3dprint関数を使うことで、3Dプリンターでは一般的なstlというフォーマットでデータを出力することができます。stlは造形物を三角形の集合体で表現するファイル形式なようです。<br/> 今回私が購入した熱溶解積層方式の3DプリンターはstlファイルをG-Codeというファイル形式に変換して読み込む必要があるので、3Dプリンターに付属しているソフトで変換します。(stlファイルがあればほとんどの3Dプリンターで何らかの方法で出力できるのではと思います。)</p> <pre class="code" data-lang="" data-unlink>rayshader::save_3dprint(&#34;Yatsugatake.stl&#34;) # このあと3Dプリンター付属のファイルでG-coede形式に変換</pre> <p>G-code形式のファイルを3Dプリンターに送って出力します。こんな感じで線を描き少しずつ重ねていって八ヶ岳を出力していきます。</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/k/kosshi08/20191218/20191218010817.jpg" alt="f:id:kosshi08:20191218010817j:plain:w500" title="f:id:kosshi08:20191218010817j:plain:w500" class="hatena-fotolife" style="width:500px" itemprop="image"></span></figure></p> <p>私が使っている3Dプリンターでは、15cm四方の大きさで8時間で出力できました。(7cm四方の大きさで約1時間弱、25cm四方だと6日間弱かかります!) <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/k/kosshi08/20191218/20191218011238.jpg" alt="f:id:kosshi08:20191218011238j:plain:w500" title="f:id:kosshi08:20191218011238j:plain:w500" class="hatena-fotolife" style="width:500px" itemprop="image"></span><figcaption>基盤数値情報DEMデータを元に作成</figcaption></figure></p> <p>八ヶ岳の裾野までプリントしたため山の部分が小さくなってしまいました。標高1600m以上の部分に絞ってプリントしてみます。</p> <pre class="code" data-lang="" data-unlink>values(r)[values(r) &lt; 1600] &lt;- NA elmat = rayshader::raster_to_matrix(r) elmat %&gt;% sphere_shade(texture = &#34;desert&#34;) %&gt;% plot_3d(elmat, zscale = 10, fov = 0, theta = 135, zoom = 0.5, phi = 45, windowsize = c(1000, 800)) save_3dprint(&#34;Yatsugatake_1600m.stl&#34;)</pre> <p><figure class="figure-image figure-image-fotolife" title="八ヶ岳over1600"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kosshi08/20191218/20191218010614.jpg" alt="f:id:kosshi08:20191218010614j:plain:w500" title="f:id:kosshi08:20191218010614j:plain:w500" class="hatena-fotolife" style="width:500px" itemprop="image"></span><figcaption>基盤数値情報DEMデータを元に作成</figcaption></figure></p> <p>山の稜線がはっきり認識できるミニチュアができました。</p> <h2>3Dプリンターについて</h2> <p>3Dプリンターにもいくつか種類があり、家庭用では主にプラスチックを熱で溶かして少しずつ積層していく熱溶解積層方式と、光をレジンに当てて硬化させていく光造形方式の二つが主流になっています。<br/> 私の購入した3DプリンターはANYCUBICというメーカーの熱溶解積層方式のエントリーモデルで、Amazonでもベストセラーになっている定番のものです。 光造形方式の方がきれいに出力できるようですが、レジンの取り扱いが難しかったり、造形時のレジンの臭いが気になる人もいるようなので、熱溶解積層方式を選びました。エントリーには熱溶解積層方式が良いのではと思います。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/B06XDNKVXL/hoxom-22/"><img src="https://images-fe.ssl-images-amazon.com/images/I/41gyJEbt5qL._SL160_.jpg" class="hatena-asin-detail-image" alt="Anycubic i3 Mega 3D プリンター 高精度 大きい プリンタサイズ 構造物取り易いヒートベッド (ブラック)" title="Anycubic i3 Mega 3D プリンター 高精度 大きい プリンタサイズ 構造物取り易いヒートベッド (ブラック)"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/B06XDNKVXL/hoxom-22/">Anycubic i3 Mega 3D プリンター 高精度 大きい プリンタサイズ 構造物取り易いヒートベッド (ブラック)</a></p><ul><li><span class="hatena-asin-detail-label">メディア:</span></li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> <h2>おわりに</h2> <p>この記事ではRとfgdrとrayshaderを使って八ヶ岳を3Dで可視化し、3Dプリンターで出力してみました。<a href="https://www.rayshader.com/#d-plotting-with-rayshader-and-ggplot2">rayshaderではggplot2のオブジェクトも3Dで表示できる</a>ので、一般的なプロットも3Dプリンターで印刷することができます(使いどころは??)。3Dプリンターがあると可視化の幅が広がりますね。2020年、一家に1台3Dプリンターはいかがでしょうか。</p> <p>Enjoy!</p> <h2>参考</h2> <ul> <li><a href="https://www.rayshader.com/">https://www.rayshader.com/</a></li> <li><a href="https://github.com/uribo/fgdr">https://github.com/uribo/fgdr</a></li> <li><a href="https://notchained.hatenablog.com/entry/2019/10/20/134020">https://notchained.hatenablog.com/entry/2019/10/20/134020</a></li> </ul> kosshi08 GitHub Actions でRのパッケージの継続的インテグレーション(CI)を行う hatenablog://entry/26006613483304758 2019-12-14T17:47:01+09:00 2019-12-16T06:23:56+09:00 本記事について R Advent Calendar 2019 1103日目の記事です。 空きがなかったので適当に書きます。 1103->11月03日は”いいおっさん”の日です、各位、よろしくお願いいたします。 はじめに 株式会社ホクソエムの高柳です。 この記事ではGitHub ActionsとR、特にRのパッケージ開発と組み合わせて使う方法を書きたいと思います。 GitHub Actionsとは”コードをビルド、テスト、パッケージング、リリース、デプロイするためのプロセスの集合”であるワークフローを、GitHub リポジトリに直接作成することができる仕組みです。 詳しくは この辺なんかを読むと… <h2>本記事について</h2> <p><a href="https://qiita.com/advent-calendar/2019/rlang">R Advent Calendar 2019</a> 1103日目の記事です。 空きがなかったので適当に書きます。</p> <p>1103->11月03日は”いいおっさん”の日です、各位、よろしくお願いいたします。</p> <h2>はじめに</h2> <p>株式会社ホクソエムの高柳です。</p> <p>この記事ではGitHub ActionsとR、特にRのパッケージ開発と組み合わせて使う方法を書きたいと思います。 GitHub Actionsとは”コードをビルド、テスト、パッケージング、リリース、デプロイするためのプロセスの集合”であるワークフローを、GitHub リポジトリに直接作成することができる仕組みです。 詳しくは <a href="https://codezine.jp/article/detail/11450">この辺</a>なんかを読むとよいでしょう。</p> <p>R言語への適用例としては、丁度<a href="https://qiita.com/kos59125/items/7684c3a0728804b73ac0">R Advent Calendar 2019 8日目:GitHub Actions で R の環境ごとのベンチマークをとった</a>にも記事があります。</p> <h2>なぜGitHub Actionsを使うのか?</h2> <p>R言語のパッケージのCIツールとしては<a href="https://travis-ci.org/">Travis CI</a>がほぼデファクトスタンダードかなと思うのですが、これを<a href="https://travis-ci.com/plans">Private repositoryや会社で使おうと思うと費用がかかって</a>しまいます。 もちろん会社・個人として応援できる場合は全然課金すれば良いと思うのですが、そうではない場合には何某かの代替手段が欲しいところです。</p> <p>そこでGitHub Actionsが使えるかどうか調べてみた、というお話です。</p> <h2>Rパッケージ開発での使い方</h2> <p>サッとググって調べたところ、</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fr-lib%2Fghactions" title="r-lib/ghactions" 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://github.com/r-lib/ghactions">github.com</a></cite></p> <p>が見つかるのですが、これを素直に使うよりも <a href="https://github.com/r-lib/usethis">usethis</a> パッケージに<a href="https://github.com/r-lib/usethis/pull/958">スッと導入された</a>関数を使う方が簡単そうなので、今回はこれを使うことにします。 もっといいやり方があったら教えてください!</p> <p>まず、CIしたいパッケージはRStudioや<code>usethis</code>から適当に作っておくとして、usethisパッケージをgithubからインストールしておきます。 これは、上述した機能がまだCRAN版にないための対応です。</p> <pre class="code lang-r" data-lang="r" data-unlink>devtools<span class="synSpecial">::</span>install_github<span class="synSpecial">(</span><span class="synConstant">&quot;r-lib/usethis&quot;</span><span class="synSpecial">)</span> </pre> <p><code>usethis</code>をインストール or 更新したのち、Rのコンソールから</p> <pre class="code lang-r" data-lang="r" data-unlink>usethis<span class="synSpecial">::</span>use_github_actions<span class="synSpecial">()</span> </pre> <p>を叩くと、開発しているパッケージのディレクトリに <code>.github/workflows/R-CMD-check.yaml</code> というファイルができています。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ cat .github/workflows/R-CMD-check.yaml on: <span class="synStatement">[</span>push, pull_request<span class="synStatement">]</span> name: R-CMD-check jobs: R-CMD-check: runs-on: macOS-latest steps: - uses: actions/checkout@v1 - uses: r-lib/actions/setup-r@master - name: Install dependencies run: Rscript <span class="synSpecial">-e</span> <span class="synStatement">&quot;</span><span class="synConstant">install.packages(c('remotes', 'rcmdcheck'))</span><span class="synStatement">&quot;</span> <span class="synSpecial">-e</span> <span class="synStatement">&quot;</span><span class="synConstant">remotes::install_deps(dependencies = TRUE)</span><span class="synStatement">&quot;</span> - name: Check run: Rscript <span class="synSpecial">-e</span> <span class="synStatement">&quot;</span><span class="synConstant">rcmdcheck::rcmdcheck(args = '--no-manual', error_on = 'error')</span><span class="synStatement">&quot;</span> </pre> <p>このファイルをgithubにアップロードするだけです、簡単ですね???</p> <p>なお、中で使われている<a href="https://github.com/r-lib/actions">r-lib/actions</a>やこの機能自体は、最近だと glueパッケージで(個人的にはよくお世話になる)有名なRStudioの <a href="https://twitter.com/jimhester_">Jim Hester</a> 氏がメインで開発しているので安心・安全ですね!</p> <p>README.mdにStatusバッジを張り付けたい!というオシャレさんは以下のようにするとバッチが張り付けられます。</p> <pre class="code" data-lang="" data-unlink>[![R build status](https://github.com/shinichi-takayanagi/ghactiontest/workflows/R-CMD-check/badge.svg)](https://github.com/shinichi-takayanagi/ghactiontest)</pre> <p>この辺のもう少し詳しい使い方や解説については英語での解説記事があるので、こちらも参考にするとよいでしょう。 - <a href="https://ropenscilabs.github.io/actions_sandbox/">Github actions with R</a></p> <h2>サンプルコード</h2> <p>ここでの手順を踏まえた上で、適当に作ったパッケージにPull Requestをこんな感じで出してみました。</p> <p><a href="https://github.com/shinichi-takayanagi/ghactiontest/pull/1">Update README.md by shinichi-takayanagi &middot; Pull Request #1 &middot; shinichi-takayanagi/ghactiontest &middot; GitHub</a></p> <p>勝手に単体テスト(正確にはR CMD check相当の処理)を行ってくれます。 これで安心してRのパッケージ開発に専念できますね!</p> <p>Enjoy!</p> shinichi-takayanagi sqlparse 入門 - 字句解析編 - hatenablog://entry/26006613479452665 2019-12-14T07:00:00+09:00 2019-12-14T07:00:10+09:00 本記事はPythonその2 Advent Calendar 2019に参加しています。 1. はじめに こんにちは。ホクソエムサポーターの藤岡です。 データアナリストらしいですが、分析そっちのけでPySparkと戯れてます。 メソッドチェインを積み上げていくスタイルで最初はちょっと使いづらいなと思ったのですが、 DataFrameが思いのほか使いやすくて、 気がつくとPySpark無しでは生きられない身体になってしまいました......。 さて、今回紹介するライブラリはsqlparseです。 sqlparseは、SQLエンジンを一切使わずにSQLを解析し、そこから種々の情報を得ることができる非… <p>本記事は<a href="https://qiita.com/advent-calendar/2019/python2">Pythonその2 Advent Calendar 2019</a>に参加しています。</p> <h1>1. はじめに</h1> <p>こんにちは。ホクソエムサポーターの藤岡です。 データアナリストらしいですが、分析そっちのけでPySparkと戯れてます。</p> <p>メソッドチェインを積み上げていくスタイルで最初はちょっと使いづらいなと思ったのですが、 DataFrameが思いのほか使いやすくて、 気がつくとPySpark無しでは生きられない身体になってしまいました......。</p> <p>さて、今回紹介するライブラリは<strong>sqlparse</strong>です。</p> <p>sqlparseは、SQLエンジンを一切使わずにSQLを解析し、そこから種々の情報を得ることができる非常に頼もしいライブラリです。 例えば、SQLの山の中から欲しいテーブルのDDLを簡単に検索できるようにしたり、 さらにそこからカラムの情報を抜き出してきたり、業務で大変お世話になっております。</p> <p>ただ、パーサー自体が情報分野にいないと馴染みがないものであり、どう使っていいのか分かりづらい一面があります。 加えて、トークン周りの実装にクセがあるため正直馴染みづらいです。</p> <p>例えば、トークンの種別を表す<code>sqlparse.tokens.Token</code>というオブジェクトがあるのですが、実体はtupleのサブクラス<strong>のインスタンス</strong>です。 isinstance(token, Token)をやると怒られます<a href="#f-fc58670f" name="fn-fc58670f" title="余談ですが、queue.Queueオブジェクトなんかも、ぱっと見クラスなのに実はただのファクトリ関数です。継承しようとするまで気づきませんでした......。">*1</a>。 ついでに、<code>sqlparse.sql.Token</code>というオブジェクトもあるんですが、こっちは<strong>クラス</strong>です。<del>なんで?</del></p> <p>というわけで、前置きもそこそこに解説に入りたいと思います! 色々と書いていたら内容が重くなってしまったので、字句解析編と狭義の構文解析編の二つに分けています。</p> <h1>2. 注意</h1> <ul> <li>本記事に書かれた内容は自分の理解に基づいたものであり、誤りが含まれている可能性がありますが、ご了承ください。</li> <li>もし本記事の不備にお気付きの際には、コメントでご指摘いただければ幸いです。</li> <li>また、以下の解説ではSQLが何度か登場しますが、すべてHiveQLです。</li> <li>今回のサンプルプログラムは説明用に作成したものであり、実際の業務での使用は一切想定していないことをご留意ください。</li> <li>本記事のプログラムは全て以下の環境で動作させています。     - Python:   3.6.4     - sqlparse: 0.3.0 (2019/12/07現在の最新ver)</li> </ul> <h1>3. サンプルコードについて</h1> <p>本記事のサンプルプログラムはリポジトリに完成品がありますので、ぜひ遊んでみてください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2FArten013%2Fsqlparse_sample" title="Arten013/sqlparse_sample" 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://github.com/Arten013/sqlparse_sample">github.com</a></cite></p> <h1>4. 導入</h1> <p>Python3.4以上<a href="#f-307cfba4" name="fn-307cfba4" title="2.7にも対応していますが、すぐにサポート対象外になるそうです。">*2</a>がインストールされている環境で、<code>$ pip install sqlparse</code>してください。</p> <h1>5. sqlparseについて</h1> <p>sqlparseは、作者によれば<code>A non-validating sql parser module for Python</code>、つまり、Pythonの非検証のSQLパーサーモジュールです。 といっても、計算機科学でもやってなければ、なんのこっちゃと戸惑うかと思います。</p> <p>そこで、本章ではパーサーと、その重要な機能の一つである字句解析について簡単に解説します。</p> <p>少し小難しい話かもしれませんが、 sqlparseを使うときには、ただ使い方を知るだけでなく、 そのコアとなるパーサーについて簡単に知っておくことが重要です。 それにより機能を正しく、かつ十全に活用することができるので、 ぜひ本章を読んでみていただければと思います。</p> <h2>5.1. パーサー (parser) について</h2> <p>パーサーとは、テキストを一定の文法にしたがって解析するアルゴリズム、もしくはその実装のことです。</p> <p>パーサーが行う処理を<strong>広義の構文解析</strong>と呼びます。</p> <p>例えば、SQLやPythonのようなプログラミング言語で書かれた命令を実行するためには、 そこに書かれた個々の命令やその実行フローなどを計算機が読み取る必要があります。 それを実現するにはパーサーによるテキスト解析が欠かせません。</p> <p>さらに、広義の構文解析は<strong>字句解析</strong>と<strong>狭義の構文解析</strong>の2ステップに分けることができます。 直感的なイメージとしては、私たちが英語を読解する場合と照らし合わせると、</p> <ul> <li>字句解析: アルファベットの並びを単語・熟語として認識すること</li> <li>狭義の構文解析:  文法の適用</li> </ul> <p>に近い処理です。</p> <p>こうした解析の結果として得られる情報は計算機だけでなくそれを扱う人間にとっても有用であり、 私たちがsqlparseを活用する意味もそこにあります。</p> <h2>5.2. 非検証パーサーについて</h2> <p>パーサーが処理するテキストの妥当性をチェックすることを検証と言います。 そして、検証を行うパーサーを検証パーサー、検証を行わないパーサーを非検証パーサーと言います。</p> <p>ただ、これは一般的にXMLパーサー等で使われる言葉であり、SQLパーサーの文脈ではあまり見ない言葉です。 なので、ここではエラーがあっても無視して解析を進める、くらいの認識でいいかと思います。</p> <h2>5.3. 字句解析についてもう少し詳しく</h2> <p>5.1節で軽く触れた字句解析について、<code>sqlparse</code>を使いながらもう少し詳細に見ていきます。 なお、ここで使うコードについては後ほど解説しますので、まずは結果に注目していただければと思います。</p> <p>さて、5.1節の説明では字句解析について、アルファベットの並びを単語・熟語として認識することと表現しましたが、 より正確には、<strong>文字列を入力としてその文字列を表すトークン列を得る処理</strong>のことです。</p> <p>例えば、以下のようなSQLをトークン列に変換していくことを考えます。​</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SELECT</span>     id,     age,     gender <span class="synSpecial">FROM</span>     hoge_table  <span class="synSpecial">WHERE</span>     age &gt; <span class="synConstant">10</span>; </pre> <p>このsql (<code>sql_1</code>とします) を入力として以下のコードを実行してみます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> sqlparse sql_1 = <span class="synConstant">&quot;SELECT id, age, gender FROM hoge_table WHERE age &gt; 10;&quot;</span> parsed_queries = sqlparse.parse(sql_1) parsed_query = parsed_queries[<span class="synConstant">0</span>] <span class="synIdentifier">list</span>(parsed_query.flatten()) </pre> <p>すると、以下のようなトークンオブジェクトのリストが得られます。</p> <pre class="code" data-lang="" data-unlink>[&lt;DML &#39;SELECT&#39; at 0x107D69A08&gt;, &lt;Whitespace &#39; &#39; at 0x107D841C8&gt;, &lt;Name &#39;id&#39; at 0x107D84348&gt;, &lt;Punctuation &#39;,&#39; at 0x107D84228&gt;, &lt;Whitespace &#39; &#39; at 0x107D842E8&gt;, &lt;Name &#39;age&#39; at 0x107D843A8&gt;, ...</pre> <p>一つ一つのトークンオブジェクトは、<code>&lt;トークン '値' at アドレス&gt;</code>という表記になっています。 ここで注意なのですが、構文解析におけるトークンとは"SELECT"のような文字列ではなく、 その種別の名称であるDMLの部分です。 また、空白があっても必ずしもそこを基準に分割される訳ではありません。 例えば、sqlparseは"LEFT JOIN"を"LEFT"と"JOIN"には分割しません。</p> <p>最初のトークンはDML、次のトークンはWhitespace、さらにName,Punctuation,...と続いていっています。 それぞれのトークンは、<code>sql_1</code>の文字列の分割である、"SELECT", " ", "id", ", ", ...といった値 (文字列) と対応しています。</p> <p>なお、上で紹介したトークンはsqlparseによるトークン分割で得られる最小単位のトークン列であり、より大きな単位でトークンに分割することもできます。 この部分には狭義の構文解析が深く関わってくるので、詳しくは次回の記事で触れます。</p> <h2>5.4. 基本的な使用方法</h2> <p>5.3節で使用したサンプルコードについて簡単に解説しながら、 sqlparseの使い方の基本について触れていきます。</p> <p>まず、<code>sqlparse.parse</code>関数にSQLの文字列を渡すと広義の構文解析が行われ、その結果が返されます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> sqlparse sql_1 = <span class="synConstant">&quot;SELECT id, age, gender FROM hoge_table WHERE age &gt; 10;&quot;</span> parsed_queries = sqlparse.parse(sql_1) </pre> <p>結果はタプルとして返されるので、ここで渡すsqlにはクエリが複数含まれていても大丈夫です。 ただし、今回のように一つしかクエリが入っていない場合も結果はタプルとして返ってくるので、 クエリを取り出すにはインデックスを指定する必要があります。</p> <pre class="code lang-python" data-lang="python" data-unlink>parsed_query = parsed_queries[<span class="synConstant">0</span>] </pre> <p><code>sqlparse.parse()</code>で得られる各パース結果は、 一つのクエリのパース結果が一つのトークンオブジェクト<a href="#f-933d3b32" name="fn-933d3b32" title="構文木の根に当たるトークンオブジェクトです。">*3</a>として得られるようになっています。</p> <p>例えば、</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synComment">-- hoge</span> <span class="synStatement">SELECT</span> hoge <span class="synSpecial">FROM</span> t1; <span class="synComment">-- fuga</span> <span class="synStatement">SELECT</span> fuga <span class="synSpecial">FROM</span> t2; <span class="synComment">-- piyo</span> <span class="synStatement">SELECT</span> piyo <span class="synSpecial">FROM</span> t3; </pre> <p>を渡すと、</p> <pre class="code" data-lang="" data-unlink>(&lt;Statement &#39; -- ho...&#39; at 0x107D86D68&gt;, &lt;Statement &#39; -- fu...&#39; at 0x107D90048&gt;, &lt;Statement &#39; -- pi...&#39; at 0x107D86DE0&gt;)</pre> <p>が返ってきます。</p> <p>最後に、今回は字句解析の結果(最小分割単位のトークン分割)だけを得たいので、<code>flatten</code>メソッドで狭義の構文解析の結果を捨てます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synIdentifier">list</span>(parsed_query.flatten()) </pre> <p>こうして、5.3節で得られたトークン列が得られます。</p> <pre class="code" data-lang="" data-unlink>[&lt;DML &#39;SELECT&#39; at 0x107D69A08&gt;, &lt;Whitespace &#39; &#39; at 0x107D841C8&gt;, &lt;Name &#39;id&#39; at 0x107D84348&gt;, &lt;Punctuation &#39;,&#39; at 0x107D84228&gt;, &lt;Whitespace &#39; &#39; at 0x107D842E8&gt;, &lt;Name &#39;age&#39; at 0x107D843A8&gt;, ...</pre> <p>以後、特に断りがない場合は、この最小分割単位のトークン列のことを、たんにトークン列と呼ぶこととします。</p> <h1>6. 使用例: CREATE TABLE DDL Finder</h1> <h2>6.1 概要</h2> <p>ある案件で、テーブルの定義のDDLが書かれた大量のsqlファイルがフォルダに格納されている現場に遭遇しました。 その中からいちいち目視で必要なファイルを探すのは面倒だった上に、ファイル名が日本語だったり、コメント中に別のテーブル名が入っていたりして、findコマンドでも探せない......。 自分はそんなとき、テーブル名を入力としてそのDDLを実行してCREATE TABLE &amp; MSCK REPAIRをする関数を作成しました。</p> <p>そのキモとなるのがSQLの解析部分です。当時は正規表現を使ってサクッと作ったのですが <a href="#f-a763325c" name="fn-a763325c" title="DDLの書き方が全部統一されていたのでそれを満たす正規表現を採用したのですが、別のDDLファイル群に転用できなくて泣いた記憶があります。">*4</a>、 今回はそれをsqlparseを使って実装してみます。</p> <p>想定する要件は以下の通りです。</p> <ul> <li>多数の.sqlファイルが存在するフォルダが探索対象。</li> <li>それぞれの.sqlファイルにはCREATE TABLE文だけでなく、MSCK REPAIR TABLE文のような別のクエリが書かれている。</li> <li>テーブル名は探索範囲内でユニーク。ただし、当該ファイル以外のファイルのコメント等には出現しうる。</li> <li>ファイル名はテーブル名とは無関係。</li> <li>テーブル名はコメントやlocation等にも出現。</li> <li>入力はテーブル名とフォルダのパス、出力は当該SQLファイルのパス。</li> <li>見つからなければFileNotFoundErrorをraiseさせる。</li> </ul> <h2>6.2. 実装</h2> <p>まず、sqlparseが関わる部分以外をさっくりと作ると、以下のような感じになります。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> glob <span class="synPreProc">import</span> iglob <span class="synPreProc">from</span> pathlib <span class="synPreProc">import</span> Path <span class="synStatement">def</span> <span class="synIdentifier">find_table_definition</span>( sql: <span class="synIdentifier">str</span>, table: <span class="synIdentifier">str</span>, db: Optional[<span class="synIdentifier">str</span>] = <span class="synIdentifier">None</span>) -&gt; Optional[TokenType]:   <span class="synConstant">&quot;&quot;&quot;引数で指定したテーブルの定義を文字列`sql`中から探し出す関数。&quot;&quot;&quot;</span>     &lt;&lt;メイン部分&gt;&gt; <span class="synStatement">def</span> <span class="synIdentifier">find_hive_ddl</span>(directory: <span class="synIdentifier">str</span>, table: <span class="synIdentifier">str</span>, db: Optional[<span class="synIdentifier">str</span>] = <span class="synIdentifier">None</span>): <span class="synConstant">&quot;&quot;&quot;引数で指定したテーブルの定義スクリプトをフォルダから探し出す関数&quot;&quot;&quot;</span> <span class="synStatement">for</span> p <span class="synStatement">in</span> iglob(directory, recursive=<span class="synIdentifier">True</span>): path = Path(p) <span class="synStatement">if</span> <span class="synStatement">not</span> path.is_file(): <span class="synStatement">continue</span> <span class="synStatement">with</span> p.open() <span class="synStatement">as</span> f: <span class="synStatement">if</span> find_table_definition(f.read(), table, db): <span class="synStatement">return</span> p <span class="synStatement">raise</span> <span class="synType">FileNotFoundError</span>( <span class="synConstant">&quot;DDL file of {table} not found.&quot;</span>.format(table=table) ) </pre> <p>次に、上記のメイン部分<code>find_table_definition</code>を作成していきます。</p> <p>処理の流れとしては以下の通りです。</p> <ol> <li>SQLテキストからCREATE TABLEクエリを抽出</li> <li>クエリ中からテーブル名のトークンオブジェクトを抽出。</li> <li>テーブル名を取得。</li> <li>一致判定の結果を返す。</li> </ol> <p>HiveQLの場合、以下の条件を満たすのならばCREATE TABLEのDDLです。</p> <ul> <li>コメントを除けば、DDLトークン"CREATE"から始まる。</li> <li>さらにTABLEトークンが続く。ただ、Keywordトークンの"TEMPORARY"と"EXTERNAL"を挟むことがある。</li> </ul> <p>上記の条件を満足しているかを判定する部分のコードは以下の通りです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> typing <span class="synPreProc">import</span> Optional, List, Sequence <span class="synPreProc">from</span> sqlparse.tokens <span class="synPreProc">import</span> Keyword, DDL, Token, Name, Punctuation <span class="synPreProc">from</span> sqlparse.sql <span class="synPreProc">import</span> Statement <span class="synPreProc">import</span> sqlparse TokenType = Token.__class__ DEFAULT_DB = <span class="synConstant">&quot;default&quot;</span> <span class="synStatement">def</span> <span class="synIdentifier">find_table_definition</span>( sql: <span class="synIdentifier">str</span>, table: <span class="synIdentifier">str</span>, db: Optional[<span class="synIdentifier">str</span>] = <span class="synIdentifier">None</span>) -&gt; Optional[TokenType]: <span class="synConstant">&quot;&quot;&quot;`table`で指定したテーブルの定義を文字列`sql`中から見つける&quot;&quot;&quot;</span> db = db <span class="synStatement">or</span> DEFAULT_DB <span class="synComment"># 1. パースしてトークン列に分割</span> <span class="synStatement">for</span> query <span class="synStatement">in</span> sqlparse.parse(sql): tokens = [ t <span class="synStatement">for</span> t <span class="synStatement">in</span> query.flatten() <span class="synStatement">if</span> <span class="synStatement">not</span> (t.is_whitespace <span class="synStatement">or</span> is_comment(t)) ] <span class="synComment"># 2. &lt;DDL CREATE&gt;を検証</span> <span class="synStatement">if</span> <span class="synStatement">not</span> match_consume(tokens, (DDL, <span class="synConstant">&quot;CREATE&quot;</span>)): <span class="synStatement">continue</span> <span class="synComment"># 3. &lt;Keyword TEMPORARY&gt; &lt;Keyword EXTERNAL&gt; をスキップ</span> match_consume(tokens, (Keyword, <span class="synConstant">&quot;TEMPORARY&quot;</span>)) match_consume(tokens, (Keyword, <span class="synConstant">&quot;EXTERNAL&quot;</span>)) <span class="synComment"># 4. &lt;Keyword TABLE&gt;を検証</span> <span class="synStatement">if</span> <span class="synStatement">not</span> match_consume(tokens, (Keyword, <span class="synConstant">&quot;TABLE&quot;</span>)): <span class="synStatement">continue</span> <span class="synComment"># 5. &lt;Keyword IF&gt;, &lt;Keyword NOT&gt;, &lt;Keyword EXISTS&gt;をスキップ</span> <span class="synStatement">if</span> match_consume(tokens, (Keyword, <span class="synConstant">&quot;IF&quot;</span>)): <span class="synStatement">if</span> <span class="synStatement">not</span> match_consume(tokens, (Keyword, <span class="synConstant">&quot;NOT&quot;</span>)): <span class="synStatement">continue</span> <span class="synStatement">if</span> <span class="synStatement">not</span> match_consume(tokens, (Keyword, <span class="synConstant">&quot;EXISTS&quot;</span>)): <span class="synStatement">continue</span> <span class="synComment"># 6. テーブル名が一致したら返す。</span> <span class="synStatement">if</span> db == DEFAULT_DB <span class="synStatement">and</span> match_consume(tokens, (Name, table)): <span class="synStatement">return</span> query <span class="synStatement">if</span> (match_consume(tokens, (Name, db)) <span class="synStatement">and</span> match_consume(tokens, (Punctuation, <span class="synConstant">&quot;.&quot;</span>)) <span class="synStatement">and</span> match_consume(tokens, (Name, table)) ): <span class="synStatement">return</span> query <span class="synStatement">def</span> <span class="synIdentifier">is_comment</span>(token: TokenType) -&gt; <span class="synIdentifier">bool</span>: <span class="synStatement">return</span> token.ttype <span class="synStatement">in</span> sqlparse.tokens.Comment <span class="synStatement">def</span> <span class="synIdentifier">match_consume</span>(tokens: List[TokenType], match_args: Iterable) -&gt; <span class="synIdentifier">bool</span>: <span class="synStatement">if</span> tokens[<span class="synConstant">0</span>].match(*match_args): tokens.pop(<span class="synConstant">0</span>) <span class="synStatement">return</span> <span class="synIdentifier">True</span> <span class="synStatement">return</span> <span class="synIdentifier">False</span> </pre> <p>試してみると、うまく動くことが確かめられます。 以下の例はサンプルコードリポジトリのSQLで試した場合です。</p> <pre class="code lang-python" data-lang="python" data-unlink>find_hive_ddl(<span class="synConstant">&quot;./ddls/*.sql&quot;</span>, <span class="synConstant">&quot;apachelog&quot;</span>) &gt; <span class="synConstant">'./ddls/apachelog_ddl.sql'</span> </pre> <h2>6.3. 実装詳細</h2> <p>ここでは、<code>find_table_definition()</code>の実装について、ポイントとなる箇所を解説していきます。 まず、文字列<code>sql</code>をパースして、そのflattenでトークン列に変換してから、空白文字とコメントを取り除いています。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">for</span> query <span class="synStatement">in</span> sqlparse.parse(sql):     tokens = [t <span class="synStatement">for</span> t <span class="synStatement">in</span> query.flatten() <span class="synStatement">if</span> <span class="synStatement">not</span> (t.is_whitespace <span class="synStatement">or</span> is_comment(t))] </pre> <p>空白文字の除去は簡単で、<code>is_whitespace</code>属性で判定できます。</p> <p>ですが、コメントは少し特殊で、 <code>is_comment</code>という以下の関数を呼び出して除いています。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">is_comment</span>(token: TokenType) -&gt; <span class="synIdentifier">bool</span>:     <span class="synStatement">return</span> token.ttype <span class="synStatement">in</span> sqlparse.tokens.Comment </pre> <p>トークンの種別を表すオブジェクト(Comment, DDL, Statement等)はttype属性に格納されています。 ttypeは<code>sqlparse.tokens</code><a href="#f-5fdac334" name="fn-5fdac334" title="ここで定義されているのはトークンであり、トークンオブジェクトはsqlparse.sqlに定義されていますので、ご注意ください。">*5</a>に定義されているのですが、 少し特殊な実装になっていて、sqlparse内で定義されている関数やメソッドに頼らずに扱おうとすると予期しない動作を引き起こす可能性があります。</p> <p>詳細については今回は省略しますが、面白い実装なのでソースコードを読んでみることをオススメします! そのうち自分のブログの方でも取り上げようと思います。</p> <p>話を戻すと、コメントの判定で<code>token.ttype</code>と<code>sqlparse.tokens.Comment</code>をinで比較しているのは、 コメントのttypeはCommentトークンだけではなく、 一行コメントトークンSingleや複数行コメントトークンMultilineとなる場合があるからです。</p> <p>先ほどの条件文は「トークンオブジェクト<code>token</code>がCommentトークンもしくはそのサブグループに属するSingle, Multilineのいずれかに該当する場合にのみ真」というものなので、これらを全て扱うことができます。</p> <p>邪魔なトークンを取り除けたら、CREATE TABLE文かどうかの判定を行います。 <del>ここまできたら正規表現でもいい気がしますが、</del> トークンオブジェクトの<code>match</code>メソッドを使って、個々のトークンを調べていきます。</p> <p>まず、先頭にCREATEトークンが来ていることを確認します。 もしもCREATEトークンが来ていれば、そのトークンの次のトークンの判定に移り、 そうでなければ、そのクエリの解析を終えます。 この処理をコード化すると以下の通りになります。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">if</span> <span class="synStatement">not</span> tokens[<span class="synConstant">0</span>].match(DDL, <span class="synConstant">&quot;CREATE&quot;</span>):     <span class="synStatement">continue</span> tokens.pop(<span class="synConstant">0</span>) </pre> <p><code>match</code>にttypeと値を渡すことでトークンのマッチングができます。 <code>regex</code>引数に<code>True</code>を渡すと、値を正規表現として処理することもできます。</p> <p>このトークンマッチ->トークンを進めるという処理を繰り返していくことになるので、 <code>match_consume</code>関数にまとめてしまいます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">match_consume</span>(tokens: List[TokenType], match_args: Sequence) -&gt; <span class="synIdentifier">bool</span>: <span class="synStatement">if</span> tokens[<span class="synConstant">0</span>].match(*match_args): tokens.pop(<span class="synConstant">0</span>) <span class="synStatement">return</span> <span class="synIdentifier">True</span> <span class="synStatement">return</span> <span class="synIdentifier">False</span> </pre> <p>この関数を作ることで、以下のように処理が簡略化されます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">if</span> <span class="synStatement">not</span> match_consume(tokens, (DDL, <span class="synConstant">&quot;CREATE&quot;</span>)):     <span class="synStatement">continue</span> </pre> <p>これを使って、以降の処理も同様に書くことができます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># 3. &lt;Keyword TEMPORARY&gt; &lt;Keyword EXTERNAL&gt; をスキップ</span> match_consume(tokens, (Keyword, <span class="synConstant">&quot;TEMPORARY&quot;</span>)) match_consume(tokens, (Keyword, <span class="synConstant">&quot;EXTERNAL&quot;</span>)) <span class="synComment"># 4. &lt;Keyword TABLE&gt;を検証</span> <span class="synStatement">if</span> <span class="synStatement">not</span> match_consume(tokens, (Keyword, <span class="synConstant">&quot;TABLE&quot;</span>)): <span class="synStatement">continue</span> <span class="synComment"># 5. &lt;Keyword IF&gt;, &lt;Keyword NOT&gt;, &lt;Keyword EXISTS&gt;をスキップ</span> <span class="synStatement">if</span> match_consume(tokens, (Keyword, <span class="synConstant">&quot;IF&quot;</span>)): <span class="synStatement">if</span> <span class="synStatement">not</span> match_consume(tokens, (Keyword, <span class="synConstant">&quot;NOT&quot;</span>)): <span class="synStatement">continue</span> <span class="synStatement">if</span> <span class="synStatement">not</span> match_consume(tokens, (Keyword, <span class="synConstant">&quot;EXISTS&quot;</span>)): <span class="synStatement">continue</span> <span class="synComment"># 6. テーブル名が一致したら返す。</span> <span class="synStatement">if</span> db == DEFAULT_DB <span class="synStatement">and</span> match_consume(tokens, (Name, table)): <span class="synStatement">return</span> query <span class="synStatement">if</span> (match_consume(tokens, (Name, db)) <span class="synStatement">and</span> match_consume(tokens, (Punctuation, <span class="synConstant">&quot;.&quot;</span>)) <span class="synStatement">and</span> match_consume(tokens, (Name, table)) ): <span class="synStatement">return</span> query </pre> <p>実装は以上です。</p> <p>もちろん、文法の細かいチェックなどは入っていないですが、 それでも着目している部分に関してはかなり正確なチェックをしつつ、 目的のDDL探索ができるようなスクリプトが簡単に書けることが分かります。</p> <h1>7. 次回予告</h1> <p>ここまで字句解析とその結果得られるトークン列の活用方法を紹介してきましたが、もっと高度なことをやろうと思うと色々と不便です。 例えば、SELECT文からサブクエリを抽出しようとすると、SELECT文の中の複雑な規則を条件分岐で表現することになり骨が折れます。</p> <p>そこで、次回は今回紹介しなかった狭義の構文解析について簡単に解説し、 SELECT文中のテーブル間の依存関係抽出のスクリプト作成に挑戦しながら、 少しだけ高度なsqlparseの活用方法について紹介したいと思います。</p> <h1>8. おわりに</h1> <p><code>sqlparse.parse</code>に色々と投げ込んでみると発見があって面白いので、ぜひぜひ試してみてください!</p> <h1>9. 補足</h1> <h2>CREATE TABLE文の判定条件の詳細</h2> <p>HiveQLのCREATE TABLE文<a href="#f-ed0a67b7" name="fn-ed0a67b7" title="LIKEによる定義コピーはここでは省略しています。">*6</a>は、<a href="https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL">公式</a>によると以下の通りです。</p> <pre class="code SQL" data-lang="SQL" data-unlink>CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name    -- (Note: TEMPORARY available in Hive 0.14.0 and later)   [(col_name data_type [column_constraint_specification] [COMMENT col_comment], ... [constraint_specification])]   [COMMENT table_comment]   [PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)]   [CLUSTERED BY (col_name, col_name, ...) [SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS]   [SKEWED BY (col_name, col_name, ...)                  -- (Note: Available in Hive 0.10.0 and later)]      ON ((col_value, col_value, ...), (col_value, col_value, ...), ...)      [STORED AS DIRECTORIES]   [    [ROW FORMAT row_format]     [STORED AS file_format]      | STORED BY &#39;storage.handler.class.name&#39; [WITH SERDEPROPERTIES (...)]  -- (Note: Available in Hive 0.6.0 and later)   ]   [LOCATION hdfs_path]   [TBLPROPERTIES (property_name=property_value, ...)]   -- (Note: Available in Hive 0.6.0 and later)   [AS select_statement];   -- (Note: Available in Hive 0.5.0 and later; not supported for external tables)</pre> <p>大事なのは最初の1行<code>CREATE [TEMPORARY] [EXTERNAL] TABLE</code>です。 HiveQLには、CREATE DATABASE文のような<code>&lt;DDL CREATE&gt;</code>から始まる別のタイプのSQLがあるため、 それを弾くためにTABLEまでを解析させる必要があるわけです。</p> <h1>10. References</h1> <p><a href="https://github.com/andialbrecht/sqlparse">GitHub - andialbrecht/sqlparse: A non-validating SQL parser module for Python</a> <a href="https://cwiki.apache.org/confluence/display/Hive/LanguageManual">LanguageManual - Apache Hive - Apache Software Foundation</a></p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/B06XRWPPC9/hoxom-22/"><img src="https://images-fe.ssl-images-amazon.com/images/I/610B98fjUdL._SL160_.jpg" class="hatena-asin-detail-image" alt="ビッグデータ分析・活用のためのSQLレシピ" title="ビッグデータ分析・活用のためのSQLレシピ"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/B06XRWPPC9/hoxom-22/">ビッグデータ分析・活用のためのSQLレシピ</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/%B2%C3%D6%BF%20%C4%B9%CC%E7" class="keyword">加嵜 長門</a>,<a href="http://d.hatena.ne.jp/keyword/%C5%C4%B5%DC%20%C4%BE%BF%CD" class="keyword">田宮 直人</a></li><li><span class="hatena-asin-detail-label">出版社/メーカー:</span> マイナビ出版</li><li><span class="hatena-asin-detail-label">発売日:</span> 2017/03/27</li><li><span class="hatena-asin-detail-label">メディア:</span> Kindle版</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> <div class="footnote"> <p class="footnote"><a href="#fn-fc58670f" name="f-fc58670f" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">余談ですが、queue.Queueオブジェクトなんかも、ぱっと見クラスなのに実はただのファクトリ関数です。継承しようとするまで気づきませんでした......。</span></p> <p class="footnote"><a href="#fn-307cfba4" name="f-307cfba4" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">2.7にも対応していますが、すぐにサポート対象外になるそうです。</span></p> <p class="footnote"><a href="#fn-933d3b32" name="f-933d3b32" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">構文木の根に当たるトークンオブジェクトです。</span></p> <p class="footnote"><a href="#fn-a763325c" name="f-a763325c" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">DDLの書き方が全部統一されていたのでそれを満たす正規表現を採用したのですが、別のDDLファイル群に転用できなくて泣いた記憶があります。</span></p> <p class="footnote"><a href="#fn-5fdac334" name="f-5fdac334" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">ここで定義されているのはトークンであり、トークンオブジェクトはsqlparse.sqlに定義されていますので、ご注意ください。</span></p> <p class="footnote"><a href="#fn-ed0a67b7" name="f-ed0a67b7" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text">LIKEによる定義コピーはここでは省略しています。</span></p> </div> kazuya_fujioka gganimateでバーチャートレースを作って競争心を煽る hatenablog://entry/26006613468343114 2019-11-21T08:30:00+09:00 2019-11-29T11:10:17+09:00 ホクソエムサポーターの輿石です。普段はデータ分析会社で分析業務や社内Rパッケージ開発をはじめ分析環境を整備する仕事をしています。 最近WEB系のメディアで「バーチャートレース(bar chart race )」と呼ばれるぬるぬる動く棒グラフを見ることが増えてきました。興味を惹くという点で優れた面白い可視化だと思います。Rではgganimateパッケージを使うことで簡単にggplot2のグラフをアニメーションにできたので、作成方法を細かい部分含めて紹介します。 なお、本記事ではggplot2の詳細には触れていませんが、詳細は「Rグラフィックスクックブック」がオススメです。なんと本記事が投稿された… <p>ホクソエムサポーターの輿石です。普段はデータ分析会社で分析業務や社内Rパッケージ開発をはじめ分析環境を整備する仕事をしています。</p> <p>最近WEB系のメディアで「バーチャートレース(bar chart race )」と呼ばれるぬるぬる動く棒グラフを見ることが増えてきました。興味を惹くという点で優れた面白い可視化だと思います。Rではgganimateパッケージを使うことで簡単にggplot2のグラフをアニメーションにできたので、作成方法を細かい部分含めて紹介します。</p> <p>なお、本記事ではggplot2の詳細には触れていませんが、詳細は「Rグラフィックスクックブック」がオススメです。なんと本記事が投稿された2019年11月21日は<b>約6年ぶりの改版となる第2版の発売日</b>なようです!6年前は少し面倒だったことも今ではより簡単にできるようになっていたりするので、すでにggplot2が使えるあなたも是非ッ。</p> <p><div class="hatena-asin-detail"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/4873116538/hoxom-22/"><img src="https://images-fe.ssl-images-amazon.com/images/I/51S2-F8zkRL._SL160_.jpg" class="hatena-asin-detail-image" alt="Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集" title="Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/4873116538/hoxom-22/">Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span> Winston Chang,石井弓美子,河内崇,瀬戸山雅人,古畠敦</li><li><span class="hatena-asin-detail-label">出版社/メーカー:</span> オライリージャパン</li><li><span class="hatena-asin-detail-label">発売日:</span> 2013/11/30</li><li><span class="hatena-asin-detail-label">メディア:</span> 大型本</li><li><a href="http://d.hatena.ne.jp/asin/4873116538/hoxom-22" target="_blank">この商品を含むブログ (3件) を見る</a></li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> <h2>1.可視化のテーマ【qiitaのイイネ数を競う】</h2> <p>可視化にしろレースにしろ、自分の興味・関心のある事柄の方が面白いと思います。自分自身と関わりのあるテーマが良いなと思っていたところ、<a href="https://qiita.com/tonio0720/items/2548d810e37c442aa540">所属組織のqiitaのイイネ数をバーチャートレースで可視化するという記事</a>が面白かったので、同じことをRからやってみます。<br/> この記事では筆者が所属するorganizationをピックアップしてアニメーションを作成します。qiitaからデータを取得するコードも公開するので、是非ご自身の組織のデータで可視化してみてください。</p> <p>最終的に作成したいアニメーションはこちらです。 <img src="https://i.imgur.com/Jwcay3e.gif" alt="" /></p> <h2>2.データの取得</h2> <p>qiitaではデータ取得のためのAPIが提供されています。主にRからAPIを叩くqiitrパッケージを使い、一部APIが対応していない部分はスクレイピングでデータを取得します。API利用のために事前に<a href="https://qiita.com/settings/applications">ユーザの管理画面</a>からtokenを取得してください。<br/> なお、qiitaのAPIには1時間に1000回のリクエスト制限があります。コード自体は放っておけば良い感じに休止してデータを最後まで取ってくれるよう記述していますが、イイネ数が多い組織で試す場合は時間に余裕をもってお試しください。参考までに、イイネ累計獲得数1位のメルカリさんの場合は2000強のリクエストだったので、2時間ちょっとかかります。</p> <p>まずはこの記事で必要になるパッケージをまとめて読み込みます。</p> <pre class="code lang-r" data-lang="r" data-unlink><span class="synPreProc">library</span><span class="synSpecial">(</span>tidyverse<span class="synSpecial">)</span> <span class="synPreProc">library</span><span class="synSpecial">(</span>gganimate<span class="synSpecial">)</span> <span class="synPreProc">library</span><span class="synSpecial">(</span>gifski<span class="synSpecial">)</span> <span class="synPreProc">library</span><span class="synSpecial">(</span>qiitr<span class="synSpecial">)</span> <span class="synPreProc">library</span><span class="synSpecial">(</span>rvest<span class="synSpecial">)</span> </pre> <p>定数を定義します。ぜひ自分の所属組織に変えて実行してみてください!</p> <pre class="code lang-r" data-lang="r" data-unlink>target_organization <span class="synStatement">&lt;-</span> <span class="synConstant">&quot;valuesccg&quot;</span> qiitr<span class="synSpecial">::</span>qiita_set_accesstoken<span class="synSpecial">()</span> </pre> <p>一部の処理を関数にまとめていきます。</p> <pre class="code lang-r" data-lang="r" data-unlink>qiita_get_organization_member <span class="synStatement">&lt;-</span> <span class="synType">function</span><span class="synSpecial">(</span>organization<span class="synSpecial">)</span> <span class="synSpecial">{</span> target_url <span class="synStatement">&lt;-</span> str_glue<span class="synSpecial">(</span><span class="synConstant">&quot;https://qiita.com/organizations/{organization}/members&quot;</span><span class="synSpecial">)</span> sess <span class="synStatement">&lt;-</span> html_session<span class="synSpecial">(</span>target_url<span class="synSpecial">)</span> member_ids <span class="synStatement">&lt;-</span> c<span class="synSpecial">()</span> <span class="synStatement">while</span> <span class="synSpecial">(</span><span class="synStatement">!</span>is.null<span class="synSpecial">(</span>sess<span class="synSpecial">))</span> <span class="synSpecial">{</span> ids <span class="synStatement">&lt;-</span> sess <span class="synStatement">%&gt;%</span> html_nodes<span class="synSpecial">(</span>xpath <span class="synStatement">=</span> <span class="synConstant">&quot;//*[@class='od-MemberCardHeaderIdentities_userid']&quot;</span><span class="synSpecial">)</span> <span class="synStatement">%&gt;%</span> html_text<span class="synSpecial">()</span> <span class="synStatement">%&gt;%</span> str_remove<span class="synSpecial">(</span><span class="synConstant">&quot;@&quot;</span><span class="synSpecial">)</span> member_ids <span class="synStatement">&lt;-</span> append<span class="synSpecial">(</span>member_ids<span class="synSpecial">,</span> ids<span class="synSpecial">)</span> sess <span class="synStatement">&lt;-</span> tryCatch<span class="synSpecial">(</span> sess <span class="synStatement">%&gt;%</span> follow_link<span class="synSpecial">(</span>xpath <span class="synStatement">=</span> <span class="synConstant">&quot;//*[@class='st-Pager_next']/a&quot;</span><span class="synSpecial">),</span> error <span class="synStatement">=</span> <span class="synType">function</span><span class="synSpecial">(</span>e<span class="synSpecial">)</span> <span class="synStatement">return</span><span class="synSpecial">(</span><span class="synConstant">NULL</span><span class="synSpecial">)</span> <span class="synSpecial">)</span> <span class="synSpecial">}</span> member_ids <span class="synSpecial">}</span> qiita_get_likes_date <span class="synStatement">&lt;-</span> <span class="synType">function</span><span class="synSpecial">(</span>item_id<span class="synSpecial">,</span> page_limit <span class="synStatement">=</span> <span class="synConstant">100</span><span class="synSpecial">){</span> res <span class="synStatement">&lt;-</span> try<span class="synSpecial">(</span>qiita_api<span class="synSpecial">(</span><span class="synConstant">&quot;GET&quot;</span><span class="synSpecial">,</span> path <span class="synStatement">=</span> sprintf<span class="synSpecial">(</span><span class="synConstant">&quot;/api/v2/items/%s/likes&quot;</span><span class="synSpecial">,</span> item_id<span class="synSpecial">),</span> page_limit <span class="synStatement">=</span> page_limit<span class="synSpecial">))</span> <span class="synStatement">while</span><span class="synSpecial">(</span>is.null<span class="synSpecial">(</span>res<span class="synSpecial">)</span> <span class="synStatement">|</span> <span class="synSpecial">(</span>class<span class="synSpecial">(</span>res<span class="synSpecial">)</span> <span class="synStatement">==</span> <span class="synConstant">&quot;try-error&quot;</span> <span class="synStatement">&amp;&amp;</span> str_detect<span class="synSpecial">(</span>res<span class="synSpecial">,</span> <span class="synConstant">&quot;Rate limit exceeded&quot;</span><span class="synSpecial">))){</span> Sys.sleep<span class="synSpecial">(</span><span class="synConstant">600</span><span class="synSpecial">)</span> res <span class="synStatement">&lt;-</span> try<span class="synSpecial">(</span>qiita_api<span class="synSpecial">(</span><span class="synConstant">&quot;GET&quot;</span><span class="synSpecial">,</span> path <span class="synStatement">=</span> sprintf<span class="synSpecial">(</span><span class="synConstant">&quot;/api/v2/items/%s/likes&quot;</span><span class="synSpecial">,</span> item_id<span class="synSpecial">),</span> page_limit <span class="synStatement">=</span> page_limit<span class="synSpecial">))</span> <span class="synSpecial">}</span> lubridate<span class="synSpecial">::</span>ymd_hms<span class="synSpecial">(</span>map_chr<span class="synSpecial">(</span>res<span class="synSpecial">,</span> <span class="synConstant">&quot;created_at&quot;</span><span class="synSpecial">))</span> <span class="synSpecial">}</span> </pre> <p>実際にデータを取得し、tidyに整形していきます。</p> <pre class="code lang-r" data-lang="r" data-unlink><span class="synComment"># organization所属メンバー一覧の取得</span> organization_members <span class="synStatement">&lt;-</span> qiita_get_organization_member<span class="synSpecial">(</span>organization <span class="synStatement">=</span> target_organization<span class="synSpecial">)</span> <span class="synComment"># メンバーの投稿記事一覧の取得</span> possibly_qiita_get_items <span class="synStatement">&lt;-</span> possibly<span class="synSpecial">(</span>qiita_get_items<span class="synSpecial">,</span> otherwise <span class="synStatement">=</span> <span class="synConstant">NULL</span><span class="synSpecial">)</span> item_data <span class="synStatement">&lt;-</span> organization_members <span class="synStatement">%&gt;%</span> tibble<span class="synSpecial">(</span>user_id <span class="synStatement">=</span> .<span class="synSpecial">)</span> <span class="synStatement">%&gt;%</span> mutate<span class="synSpecial">(</span>res <span class="synStatement">=</span> map<span class="synSpecial">(</span>user_id<span class="synSpecial">,</span> <span class="synStatement">~</span>possibly_qiita_get_items<span class="synSpecial">(</span>user_id <span class="synStatement">=</span> .<span class="synSpecial">,</span> page_limit <span class="synStatement">=</span> <span class="synConstant">100L</span><span class="synSpecial">)),</span> item_id <span class="synStatement">=</span> map<span class="synSpecial">(</span>res<span class="synSpecial">,</span> pluck<span class="synSpecial">,</span> <span class="synConstant">&quot;id&quot;</span><span class="synSpecial">),</span> likes <span class="synStatement">=</span> map<span class="synSpecial">(</span>res<span class="synSpecial">,</span> pluck<span class="synSpecial">,</span> <span class="synConstant">&quot;likes_count&quot;</span><span class="synSpecial">))</span> <span class="synStatement">%&gt;%</span> select<span class="synSpecial">(</span><span class="synStatement">-</span>res<span class="synSpecial">)</span> <span class="synStatement">%&gt;%</span> unnest<span class="synSpecial">(</span>cols <span class="synStatement">=</span> c<span class="synSpecial">(</span>item_id<span class="synSpecial">,</span> likes<span class="synSpecial">))</span> <span class="synStatement">%&gt;%</span> unnest<span class="synSpecial">(</span>cols <span class="synStatement">=</span> c<span class="synSpecial">(</span>item_id<span class="synSpecial">,</span> likes<span class="synSpecial">))</span> <span class="synStatement">%&gt;%</span> filter<span class="synSpecial">(</span>likes <span class="synStatement">&gt;</span> <span class="synConstant">0</span><span class="synSpecial">)</span> <span class="synStatement">%&gt;%</span> mutate<span class="synSpecial">(</span>query_num <span class="synStatement">=</span> ceiling<span class="synSpecial">(</span>likes<span class="synStatement">/</span><span class="synConstant">100</span><span class="synSpecial">))</span> sum<span class="synSpecial">(</span>item_data<span class="synSpecial">$</span>query_num<span class="synSpecial">)</span> <span class="synComment"># 記事ごとにイイネが付いた日時を取得</span> likes_data <span class="synStatement">&lt;-</span> item_data <span class="synStatement">%&gt;%</span> mutate<span class="synSpecial">(</span>likes_date <span class="synStatement">=</span> map<span class="synSpecial">(</span>item_id<span class="synSpecial">,</span> qiita_get_likes_date<span class="synSpecial">))</span> <span class="synStatement">%&gt;%</span> unnest<span class="synSpecial">(</span>cols <span class="synStatement">=</span> likes_date<span class="synSpecial">)</span> </pre> <h2>3.可視化のためのデータ加工</h2> <p>データを取得できたので、gganimateでの可視化に適した形にデータフレームを加工していきましょう。アニメーションの1コマ1コマを作成するためのデータフレームを作成し、1コマを識別できるidを付けてすべてを1つにまとめたデータフレームを作成します。<br/> ここでは、直近1年のデータを対象にイイネ累計獲得数のTOP10を週ごとに集計し、52週間分をまとめたデータフレームを作成していきます。</p> <pre class="code lang-r" data-lang="r" data-unlink>likes_data_sum <span class="synStatement">&lt;-</span> likes_data <span class="synStatement">%&gt;%</span> filter<span class="synSpecial">(</span>likes_date <span class="synStatement">&gt;=</span> lubridate<span class="synSpecial">::</span>today<span class="synSpecial">()</span> <span class="synStatement">-</span> lubridate<span class="synSpecial">::</span>dyears<span class="synSpecial">(</span><span class="synConstant">1</span><span class="synSpecial">))</span> <span class="synStatement">%&gt;%</span> mutate<span class="synSpecial">(</span>weeks <span class="synStatement">=</span> paste0<span class="synSpecial">(</span>lubridate<span class="synSpecial">::</span>year<span class="synSpecial">(</span>likes_date<span class="synSpecial">),</span> <span class="synConstant">&quot;_w&quot;</span><span class="synSpecial">,</span> str_pad<span class="synSpecial">(</span>lubridate<span class="synSpecial">::</span>week<span class="synSpecial">(</span>likes_date<span class="synSpecial">),</span> width <span class="synStatement">=</span> <span class="synConstant">2</span><span class="synSpecial">,</span> pad <span class="synStatement">=</span> <span class="synConstant">&quot;0&quot;</span><span class="synSpecial">)))</span> <span class="synStatement">%&gt;%</span> arrange<span class="synSpecial">(</span>user_id<span class="synSpecial">,</span> weeks<span class="synSpecial">)</span> <span class="synStatement">%&gt;%</span> group_by<span class="synSpecial">(</span>user_id<span class="synSpecial">,</span> weeks<span class="synSpecial">)</span> <span class="synStatement">%&gt;%</span> summarise<span class="synSpecial">(</span>likes <span class="synStatement">=</span> n<span class="synSpecial">())</span> <span class="synStatement">%&gt;%</span> mutate<span class="synSpecial">(</span>score_sum <span class="synStatement">=</span> cumsum<span class="synSpecial">(</span>likes<span class="synSpecial">))</span> <span class="synStatement">%&gt;%</span> ungroup<span class="synSpecial">()</span> likes_data_sum <span class="synStatement">&lt;-</span> likes_data_sum <span class="synStatement">%&gt;%</span> select<span class="synSpecial">(</span><span class="synStatement">-</span>likes<span class="synSpecial">)</span> <span class="synStatement">%&gt;%</span> pivot_wider<span class="synSpecial">(</span>names_from <span class="synStatement">=</span> weeks<span class="synSpecial">,</span> values_from <span class="synStatement">=</span> score_sum<span class="synSpecial">)</span> <span class="synStatement">%&gt;%</span> pivot_longer<span class="synSpecial">(</span><span class="synStatement">-</span>user_id<span class="synSpecial">,</span> names_to <span class="synStatement">=</span> <span class="synConstant">&quot;weeks&quot;</span><span class="synSpecial">,</span> values_to <span class="synStatement">=</span> <span class="synConstant">&quot;score_sum&quot;</span><span class="synSpecial">)</span> <span class="synStatement">%&gt;%</span> arrange<span class="synSpecial">(</span>user_id<span class="synSpecial">,</span> weeks<span class="synSpecial">)</span> <span class="synStatement">%&gt;%</span> group_by<span class="synSpecial">(</span>user_id<span class="synSpecial">)</span> <span class="synStatement">%&gt;%</span> fill<span class="synSpecial">(</span>score_sum<span class="synSpecial">)</span> <span class="synStatement">%&gt;%</span> ungroup<span class="synSpecial">()</span> <span class="synStatement">%&gt;%</span> mutate<span class="synSpecial">(</span>score_sum <span class="synStatement">=</span> if_else<span class="synSpecial">(</span>is.na<span class="synSpecial">(</span>score_sum<span class="synSpecial">),</span> <span class="synConstant">0L</span><span class="synSpecial">,</span> score_sum<span class="synSpecial">))</span> <span class="synStatement">%&gt;%</span> group_by<span class="synSpecial">(</span>weeks<span class="synSpecial">)</span> <span class="synStatement">%&gt;%</span> mutate<span class="synSpecial">(</span>ranking <span class="synStatement">=</span> row_number<span class="synSpecial">(</span><span class="synStatement">-</span>score_sum<span class="synSpecial">))</span> <span class="synStatement">%&gt;%</span> filter<span class="synSpecial">(</span>ranking <span class="synStatement">&lt;=</span> <span class="synConstant">10</span><span class="synSpecial">)</span> <span class="synStatement">%&gt;%</span> ungroup<span class="synSpecial">()</span> </pre> <p>下記の形のデータフレームができました。</p> <pre class="code lang-r" data-lang="r" data-unlink>head<span class="synSpecial">(</span>likes_data_sum<span class="synSpecial">)</span> </pre> <pre class="code" data-lang="" data-unlink># A tibble: 6 x 4 user_id weeks score_sum ranking &lt;chr&gt; &lt;chr&gt; &lt;int&gt; &lt;int&gt; 1 accakr 2018_w47 0 3 2 accakr 2018_w48 1 4 3 accakr 2018_w49 1 4 4 accakr 2018_w50 1 5 5 accakr 2018_w51 1 5 6 accakr 2018_w52 1 5</pre> <h2>4.アニメーションの作成①</h2> <p>バーチャートレースを作成する場合は、geom_bar()ではなく、<b>geom_tile()</b>を使うことがポイントです。<br/> 1つのコマに絞ってデータを可視化する要領で記述したコードに、transition_states()関数を追加しコマを識別する列を指定するだけでアニメーションが作成できます。(facet_wrap()の代わりにtransition_states()を使うイメージ。)</p> <pre class="code lang-r" data-lang="r" data-unlink>p <span class="synStatement">&lt;-</span> likes_data_sum <span class="synStatement">%&gt;%</span> ggplot<span class="synSpecial">(</span>aes<span class="synSpecial">(</span>x <span class="synStatement">=</span> ranking<span class="synSpecial">,</span> group <span class="synStatement">=</span> user_id<span class="synSpecial">))</span> <span class="synStatement">+</span> geom_tile<span class="synSpecial">(</span>aes<span class="synSpecial">(</span>y <span class="synStatement">=</span> score_sum<span class="synStatement">/</span><span class="synConstant">2</span><span class="synSpecial">,</span> height <span class="synStatement">=</span> score_sum<span class="synSpecial">,</span> fill <span class="synStatement">=</span> user_id<span class="synSpecial">,</span> width <span class="synStatement">=</span> <span class="synConstant">0.9</span><span class="synSpecial">))</span> <span class="synStatement">+</span> geom_text<span class="synSpecial">(</span>aes<span class="synSpecial">(</span>y <span class="synStatement">=</span> <span class="synConstant">0</span><span class="synSpecial">,</span> label <span class="synStatement">=</span> paste<span class="synSpecial">(</span>user_id<span class="synSpecial">,</span> <span class="synConstant">&quot; &quot;</span><span class="synSpecial">)),</span> vjust <span class="synStatement">=</span> <span class="synStatement">-</span><span class="synConstant">1</span><span class="synSpecial">,</span> hjust <span class="synStatement">=</span> <span class="synStatement">-</span><span class="synConstant">0.1</span><span class="synSpecial">)</span> <span class="synStatement">+</span> geom_text<span class="synSpecial">(</span>aes<span class="synSpecial">(</span>y <span class="synStatement">=</span> score_sum<span class="synSpecial">,</span> label <span class="synStatement">=</span> paste0<span class="synSpecial">(</span><span class="synConstant">&quot; &quot;</span><span class="synSpecial">,</span> score_sum<span class="synSpecial">),</span> vjust <span class="synStatement">=</span> <span class="synConstant">1</span><span class="synSpecial">,</span> hjust <span class="synStatement">=</span> <span class="synConstant">0</span><span class="synSpecial">))</span> <span class="synStatement">+</span> scale_x_reverse<span class="synSpecial">()</span> <span class="synStatement">+</span> coord_flip<span class="synSpecial">()</span> <span class="synStatement">+</span> theme_light<span class="synSpecial">()</span> p <span class="synStatement">+</span> transition_states<span class="synSpecial">(</span>weeks<span class="synSpecial">)</span> </pre> <p><img src="https://i.imgur.com/qABJdty.gif" alt="tmp_race_animation" /></p> <p>(なんだかカクカクしていますが、)ひとまずアニメーションを作ることができました!</p> <h2>5.アニメーションのカスタマイズ</h2> <p>ここではgganimateパッケージの関数について詳細をまとめます。アニメーションに関する設定を理解し、より質の高いアニメーションを作りましょう。</p> <h3>i. transition_xxx()</h3> <p>「transition_」から始まる関数で1コマ1コマの移り変わりの軸を何にするか、移り変わりの大まかな関係をどうするか、を決めます。アニメーション作成に最低限必要な関数です。沢山ありますが、以下の3つを覚えておけばいいでしょう。</p> <h4>a. transition_states()</h4> <p>カテゴリ変数・離散変数間の移り変わりをアニメーションにしたいときに使います。<br/> 引数transition_lengthとstate_lengthで1回のコマ遷移でアニメーションが動く時間と止まる時間の割合を決めることができます。<br/> また、wrap=T(デフォルト)の場合はアニメーションが最後のコマになった際に、最初のコマに戻るところまでがアニメーションになるようです。</p> <h4>b. transition_time()</h4> <p>日時を表す変数を軸に、時間の移り変わりをアニメーションにしたいときに使います。<br/> 時間の間隔とアニメーションの移り変わりを合わせてくれるようで、例えば<code>c(2015, 2016, 2018, 2019)</code>のように一部期間のデータがかけていても時間間隔が合うように補完されたアニメーションが出力されます。実際の時間間隔に合うようになっているので、transition_state()のようにアニメーションが止まる時間を作ることはできず、常にぬるぬると動くアニメーションになります。</p> <pre class="code lang-r" data-lang="r" data-unlink><span class="synComment">#2017年がないデータ</span> test_data <span class="synStatement">&lt;-</span> data_frame<span class="synSpecial">(</span>timing <span class="synStatement">=</span> c<span class="synSpecial">(</span><span class="synConstant">2015</span><span class="synSpecial">,</span> <span class="synConstant">2016</span><span class="synSpecial">,</span> <span class="synConstant">2018</span><span class="synSpecial">,</span> <span class="synConstant">2019</span><span class="synSpecial">),</span> id <span class="synStatement">=</span> <span class="synConstant">&quot;1&quot;</span><span class="synSpecial">,</span> y <span class="synStatement">=</span> c<span class="synSpecial">(</span><span class="synConstant">0</span><span class="synSpecial">,</span> <span class="synConstant">1</span><span class="synSpecial">,</span> <span class="synConstant">2</span><span class="synSpecial">,</span> <span class="synConstant">3</span><span class="synSpecial">))</span> test_data <span class="synStatement">%&gt;%</span> ggplot<span class="synSpecial">()</span> <span class="synStatement">+</span> geom_bar<span class="synSpecial">(</span>aes<span class="synSpecial">(</span>x <span class="synStatement">=</span> id<span class="synSpecial">,</span> y <span class="synStatement">=</span> y<span class="synSpecial">),</span> stat <span class="synStatement">=</span> <span class="synConstant">&quot;identity&quot;</span><span class="synSpecial">,</span> width <span class="synStatement">=</span> <span class="synConstant">0.5</span><span class="synSpecial">)</span> <span class="synStatement">+</span> coord_flip<span class="synSpecial">()</span> <span class="synStatement">+</span> transition_time<span class="synSpecial">(</span>timing<span class="synSpecial">)</span> <span class="synStatement">+</span> labs<span class="synSpecial">(</span>title <span class="synStatement">=</span> <span class="synConstant">&quot;[{as.integer(frame_time)}] : {frame_time}&quot;</span><span class="synSpecial">)</span> </pre> <p><img src="https://i.imgur.com/c7cNjMI.gif" alt="" /></p> <h4>c. transition_reveal()</h4> <p>主に折れ線グラフを作成する際など、徐々にデータを追加しながらplotしなければならないケースで使うようです。月ごとの折れ線グラフの場合は、1コマ目は1月目のデータ、2コマ目は1~~2ヶ月目までのデータ、という形でplotされ、アニメーションになります。</p> <p>なお、各transitionで「今何を描画しているか」などを表す変数が用意されており、glueの形式でタイトルなどテキストに挿入することが可能です。transition_states()ならば{closest_state}や{previous_state}、transition_time()ならば{frame_time}と、transitionごとに異なるので関数のヘルプで確認するようにしましょう。</p> <h3>ii. view_follow()</h3> <p>軸のメモリ範囲をコマごとのデータ範囲によって可変にしたいときに使います。<br/> ただし、今回のgeom_tile()を使ったチャートでは可変にするとaxis.titleやaxis.textなど軸系のラベルがうまく表示できないようです。themaの設定で非表示にしています。</p> <h3>iii. ease_aes()</h3> <p>コマ間の移り変わりの際に、「最初ゆっくり動いて早くなってまたゆっくり動く」など変化に抑揚を付けたいときに使います。"cubic-in-out", "sine-in-out"などの文字列を指定するのですが、これらはCSSやjQueryのアニメーションに緩急をつけるプロパティ「イージング」の用語なようです。<a href="https://easings.net/">このサイト</a>を見ると"cubic"や"sine"の違いや他の選択肢が分かります。ease_aes()を指定しない場合と、"cubic-in-out"を指定した場合の動きの違いを見てみましょう。</p> <pre class="code lang-r" data-lang="r" data-unlink>test_data <span class="synStatement">&lt;-</span> data_frame<span class="synSpecial">(</span>timing <span class="synStatement">=</span> c<span class="synSpecial">(</span><span class="synConstant">1</span><span class="synSpecial">,</span> <span class="synConstant">2</span><span class="synSpecial">,</span> <span class="synConstant">3</span><span class="synSpecial">),</span> id <span class="synStatement">=</span> <span class="synConstant">&quot;1&quot;</span><span class="synSpecial">,</span> y <span class="synStatement">=</span> c<span class="synSpecial">(</span><span class="synConstant">0</span><span class="synSpecial">,</span> <span class="synConstant">1</span><span class="synSpecial">,</span> <span class="synConstant">2</span><span class="synSpecial">))</span> p <span class="synStatement">&lt;-</span> test_data <span class="synStatement">%&gt;%</span> ggplot<span class="synSpecial">()</span> <span class="synStatement">+</span> geom_bar<span class="synSpecial">(</span>aes<span class="synSpecial">(</span>x <span class="synStatement">=</span> id<span class="synSpecial">,</span> y <span class="synStatement">=</span> y<span class="synSpecial">),</span> stat <span class="synStatement">=</span> <span class="synConstant">&quot;identity&quot;</span><span class="synSpecial">,</span> width <span class="synStatement">=</span> <span class="synConstant">0.5</span><span class="synSpecial">)</span> <span class="synStatement">+</span> coord_flip<span class="synSpecial">()</span> <span class="synStatement">+</span> transition_states<span class="synSpecial">(</span>timing<span class="synSpecial">,</span> transition_length <span class="synStatement">=</span> <span class="synConstant">6</span><span class="synSpecial">,</span> state_length <span class="synStatement">=</span> <span class="synConstant">1</span><span class="synSpecial">,</span> wrap <span class="synStatement">=</span> <span class="synConstant">FALSE</span><span class="synSpecial">)</span> animate<span class="synSpecial">(</span>p<span class="synSpecial">,</span> nframes <span class="synStatement">=</span> <span class="synConstant">100</span><span class="synSpecial">,</span> fps <span class="synStatement">=</span> <span class="synConstant">10</span><span class="synSpecial">,</span> width <span class="synStatement">=</span> <span class="synConstant">800</span><span class="synSpecial">,</span> height <span class="synStatement">=</span> <span class="synConstant">100</span><span class="synSpecial">)</span> animate<span class="synSpecial">(</span>p <span class="synStatement">+</span> ease_aes<span class="synSpecial">(</span><span class="synConstant">&quot;cubic-in-out&quot;</span><span class="synSpecial">),</span> nframes <span class="synStatement">=</span> <span class="synConstant">100</span><span class="synSpecial">,</span> fps <span class="synStatement">=</span> <span class="synConstant">10</span><span class="synSpecial">,</span> width <span class="synStatement">=</span> <span class="synConstant">800</span><span class="synSpecial">,</span> height <span class="synStatement">=</span> <span class="synConstant">100</span><span class="synSpecial">)</span> </pre> <p><strong>ease_aesを指定しない場合(一定の速度で増加)</strong> <img src="https://i.imgur.com/M3wHTGA.gif" alt="ease_aes_normal" /> <strong>ease_aesに"cubic-in-out"を指定(増加に緩急が付く)</strong> <img src="https://i.imgur.com/RqfqY3A.gif" alt="ease_aes_cubic" /></p> <h3>iv. enter_xxx/fade_xxx</h3> <p>新しいオブジェクト(棒や点など)の描画のされ方や、プロット内のオブジェクトが画面から消える際の消え方を指定できます。enter_fade()やexit_shrink()などがあります。</p> <h3>v. animate()</h3> <p>アニメーションはanimate()関数でレンダリングすることで最終的なアウトプットとなります。ここでは主に下記の項目を決めます。</p> <ul> <li>アニメーションの時間の長さとフレーム数</li> <li>出力形式</li> <li>アニメーションの縦横のサイズ</li> </ul> <h4>a. アニメーションの時間の長さとフレーム数</h4> <p>引数3つで<code>duration = nframe ÷ fps</code>の関係が成り立ちます。</p> <ul> <li>nframe:フレームの数(default = 100)</li> <li>fps:フレームレート。1秒当たりのフレーム数(default = 10)</li> <li>duration:アニメーション全体の時間の長さ(デフォルトは100/10で10秒)</li> </ul> <p>一般にfpsが高い方がデータが重くなり、映像が滑らかになります。データのプロットは10fpsもあれば十分なケースが多いでしょう。(テレビアニメは24fpsです)<br/> アニメーションがカクカクしている!もっと滑らかにしたい!という場合はfpsはそのままで、durationを長くしてみることをオススメします。fpsよりもコマ(今回のケースでは52週間分52コマ)を何秒に収めるかの方が全体的な滑らかさを決めるポイントになるように思います。</p> <h4>b. 出力形式</h4> <p>xxx_renderer()関数で出力形式を指定できます。デフォルトはgifski_renderer()でgifが出力されます。</p> <h4>c. アニメーションの縦横のサイズ</h4> <p>widthとheightで指定しましょう。</p> <h4>d. (おまけ)start_pause、end_pause引数でアニメーションを最初か最後で静止させる</h4> <p>start_pauseもしくはend_pause引数で最初か最後のフレームでアニメーションを一時停止させることができます。指定しないと最後に到達した瞬間に最初に戻るので、「最後どうなった!?」とツッコみたくなります。指定でちょっとハマったのでまとめました。</p> <ul> <li>frame数で指定する。<br/> 「1秒間静止させるframe数=fps」です。値をあらかじめ確認して指定しましょう。</li> <li>静止させた分duration(アニメーションの時間)を長くする。<br/> 静止させた分も含めてdurationなので、例えば5秒で最後まで動かして1秒静止させたいという場合はdurationは6にしましょう。</li> <li>transition_states()を使い最後で静止させる場合は、wrap=Fにする。<br/> デフォルトのwrap=Tの場合最後のコマから最初のコマに戻るところまでがアニメーションになります。最後のframeは最初のコマに戻る寸前のframeであり、中途半端ななんとも言えないタイミングで静止してしまいます。transition_states(wrap=F)で最後でぴったり終わるようにしましょう。</li> </ul> <h2>6.アニメーションの作成②</h2> <p>それでは最終的なアニメーションを出力したいと思います。</p> <pre class="code lang-r" data-lang="r" data-unlink>p <span class="synStatement">&lt;-</span> p <span class="synStatement">+</span> theme<span class="synSpecial">(</span>axis.line<span class="synStatement">=</span>element_blank<span class="synSpecial">(),</span> axis.title.x<span class="synStatement">=</span>element_blank<span class="synSpecial">(),</span> axis.title.y<span class="synStatement">=</span>element_blank<span class="synSpecial">(),</span> axis.text.x<span class="synStatement">=</span>element_blank<span class="synSpecial">(),</span> axis.text.y<span class="synStatement">=</span>element_blank<span class="synSpecial">(),</span> legend.position<span class="synStatement">=</span><span class="synConstant">&quot;none&quot;</span><span class="synSpecial">,</span> panel.border<span class="synStatement">=</span>element_blank<span class="synSpecial">(),</span> panel.grid.major.y<span class="synStatement">=</span>element_blank<span class="synSpecial">(),</span> panel.grid.minor.y<span class="synStatement">=</span>element_blank<span class="synSpecial">(),</span> plot.title<span class="synStatement">=</span>element_text<span class="synSpecial">(</span>size<span class="synStatement">=</span><span class="synConstant">25</span><span class="synSpecial">,</span> hjust<span class="synStatement">=</span><span class="synConstant">0.5</span><span class="synSpecial">,</span> face<span class="synStatement">=</span><span class="synConstant">&quot;bold&quot;</span><span class="synSpecial">,</span> vjust<span class="synStatement">=-</span><span class="synConstant">1</span><span class="synSpecial">,</span> lineheight <span class="synStatement">=</span> <span class="synConstant">1</span><span class="synSpecial">),</span> plot.subtitle<span class="synStatement">=</span>element_text<span class="synSpecial">(</span>size<span class="synStatement">=</span><span class="synConstant">18</span><span class="synSpecial">,</span> hjust<span class="synStatement">=</span><span class="synConstant">0.5</span><span class="synSpecial">))</span> <span class="synStatement">+</span> labs<span class="synSpecial">(</span>title <span class="synStatement">=</span> <span class="synConstant">&quot;直近1年間のqiita累計イイネ獲得数ランキング : {closest_state}&quot;</span><span class="synSpecial">,</span> subtitle <span class="synStatement">=</span> paste0<span class="synSpecial">(</span><span class="synConstant">&quot;organization:&quot;</span><span class="synSpecial">,</span> target_organization<span class="synSpecial">))</span> <span class="synStatement">+</span> transition_states<span class="synSpecial">(</span>weeks<span class="synSpecial">,</span> transition_length <span class="synStatement">=</span> <span class="synConstant">6</span><span class="synSpecial">,</span> state_length <span class="synStatement">=</span> <span class="synConstant">1</span><span class="synSpecial">,</span> wrap <span class="synStatement">=</span> <span class="synConstant">F</span><span class="synSpecial">)</span> <span class="synStatement">+</span> view_follow<span class="synSpecial">()</span> <span class="synStatement">+</span> enter_fade<span class="synSpecial">()</span> <span class="synStatement">+</span> ease_aes<span class="synSpecial">(</span><span class="synConstant">&quot;cubic-in-out&quot;</span><span class="synSpecial">)</span> final_animation <span class="synStatement">&lt;-</span> animate<span class="synSpecial">(</span>p<span class="synSpecial">,</span> fps <span class="synStatement">=</span> <span class="synConstant">10</span><span class="synSpecial">,</span> duration <span class="synStatement">=</span> <span class="synConstant">32</span><span class="synSpecial">,</span> end_pause <span class="synStatement">=</span> <span class="synConstant">20</span><span class="synSpecial">,</span> width <span class="synStatement">=</span> <span class="synConstant">800</span><span class="synSpecial">,</span> height <span class="synStatement">=</span> <span class="synConstant">400</span><span class="synSpecial">)</span> <span class="synComment"># 保存するときはanim_save()</span> dir.create<span class="synSpecial">(</span><span class="synConstant">&quot;images&quot;</span><span class="synSpecial">)</span> anim_save<span class="synSpecial">(</span><span class="synConstant">&quot;images/race_bar_chart.gif&quot;</span><span class="synSpecial">,</span> final_animation<span class="synSpecial">)</span> </pre> <p><img src="https://i.imgur.com/Jwcay3e.gif" alt="" /></p> <p>良い感じにぬるぬる動くアニメーションになりました! <br/> (私の順位は下から3番目なようです。 )</p> <p>ggplot2が書ければアニメーションも作れてしまいますね。R凄い。</p> <p>Enjoy!</p> <h2>参考文献</h2> <ul> <li><a href="https://github.com/thomasp85/gganimate">https://github.com/thomasp85/gganimate</a></li> <li><a href="https://towardsdatascience.com/how-to-create-plots-with-beautiful-animation-using-gganimate-912f4279b073">How to create plots with beautiful animation using gganimate.</a></li> <li><a href="https://emilykuehler.github.io/bar-chart-race/">Bar Chart Races With gganimate</a></li> <li><a href="http://design.kayac.com/topics/2013/11/easing-basics.php">KAYAC DESIGNER'S BLOG(これだけは抑えておきたい! jQuery や CSS の「イージング」の基礎知識) </a></li> </ul> kosshi08 EMNLP2019の気になった論文を紹介 hatenablog://entry/26006613463450261 2019-11-19T08:30:00+09:00 2019-12-03T22:28:16+09:00 ホクソエムサポーターの白井です。 EMNLP-IJCNLP 2019 (以降 EMNLP) が先日、香港で開催されました。 EMNLPは Empirical Methods in Natural Language Processing の略称で、ACLやNAACLと並ぶ、計算機科学のTop conferenceと言われてます*1。 今年採択されたEMNLPの論文は682本 (+システム/デモ論文45本) です。 (年々増えています。) 今回は、EMNLP2019の論文から、いくつか気になったものを紹介します。 前回に引き続き、検証系の論文とデータ構築についての論文をメインに扱います。 以降、記… <p>ホクソエムサポーターの白井です。</p> <p><a href="https://www.emnlp-ijcnlp2019.org/">EMNLP-IJCNLP 2019</a> (以降 EMNLP) が先日、香港で開催されました。 EMNLPは <em>Empirical Methods in Natural Language Processing</em> の略称で、ACLやNAACLと並ぶ、計算機科学のTop conferenceと言われてます<a href="#f-b9986182" name="fn-b9986182" title="参考: Conference Ranks">*1</a>。 今年採択されたEMNLPの論文は682本 (+システム/デモ論文45本) です。 (年々増えています。)</p> <p>今回は、EMNLP2019の論文から、いくつか気になったものを紹介します。 前回に引き続き、検証系の論文とデータ構築についての論文をメインに扱います。</p> <p>以降、記載する図表は、明記しない限り、論文から引用しています。</p> <ul class="table-of-contents"> <li><a href="#1-ner-and-pos-when-nothing-is-capitalized">1. ner and pos when nothing is capitalized</a></li> <li><a href="#2-A-Little-Annotation-does-a-Lot-of-Good-A-Study-in-Bootstrapping-Low-resource-Named-Entity-Recognizers">2. A Little Annotation does a Lot of Good: A Study in Bootstrapping Low-resource Named Entity Recognizers</a></li> <li><a href="#3-Are-We-Modeling-the-Task-or-the-Annotator-An-Investigation-of-Annotator-Bias-in-Natural-Language-Understanding-Datasets">3. Are We Modeling the Task or the Annotator? An Investigation of Annotator Bias in Natural Language Understanding Datasets</a></li> <li><a href="#4-Male-Bachelor-and-Female-PhD-have-different-connotations-Parallelly-Annotated-Stylistic-Language-Dataset-with-Multiple-Personas">4. (Male, Bachelor) and (Female, Ph.D) have different connotations: Parallelly Annotated Stylistic Language Dataset with Multiple Personas</a></li> <li><a href="#5-Open-Domain-Web-Keyphrase-Extraction-Beyond-Language-Modeling">5. Open Domain Web Keyphrase Extraction Beyond Language Modeling</a></li> <li><a href="#おわりに">おわりに</a><ul> <li><a href="#参考-Tutorialのリンク集">参考: Tutorialのリンク集</a></li> </ul> </li> </ul> <h2 id="1-ner-and-pos-when-nothing-is-capitalized">1. ner and pos when nothing is capitalized</h2> <p><em>※タイトルはtypoではないです</em></p> <ul> <li>ACL anthology <a href="https://www.aclweb.org/anthology/D19-1650/">https://www.aclweb.org/anthology/D19-1650/</a></li> <li>arXiv <a href="https://arxiv.org/abs/1903.11222">https://arxiv.org/abs/1903.11222</a></li> <li>Site <a href="https://cogcomp.seas.upenn.edu/page/publication_view/881">https://cogcomp.seas.upenn.edu/page/publication_view/881</a></li> </ul> <p>英語において、大文字・小文字の使い分けにはルールがあります。例えば、文の最初や固有名詞の最初の文字は大文字を使います (e.g. <em>"For these language, ..."</em> , <em>Hong Kong</em>)。 これらの大文字の情報は、NER (Named Entity Recognition: 固有表現抽出) やPOS (Part of Speech Tagging: 品詞タグ付け) タスクにおいて重要な情報となり得ます。</p> <p>ちなみに、NERは人物、場所、組織といった固有名詞を抽出するタスクです。下の例であれば、 <code>AllenNLP</code>、<code>PyTorch</code>、<code>Allen Institute for Artificial Intelligence</code> が <code>ORG</code> (組織)、<code>Seattle</code> が <code>LOC</code> (場所) だとモデルが予測しています。</p> <p><img src="https://i.imgur.com/zxvrw80.png" alt="" /> from <a href="https://demo.allennlp.org/named-entity-recognition/">https://demo.allennlp.org/named-entity-recognition/</a> のelmo-nerによる予測結果(文はサイトの例文より引用)</p> <p>一方、POSは単語に対して動詞や名詞などの品詞タグを付与するタスクです。下の <a href="https://stanfordnlp.github.io/CoreNLP/">Stanford CoreNLP</a>による結果では、 <a href="https://web.archive.org/web/20131109202842/http://www.cis.upenn.edu/~treebank/">Penn Tree Bank</a>の定義にしたがって単語にタグ付けされています。 例えば、 <code>we</code>は <code>PRP</code> (Personal pronoun: 人称代名詞)、<code>perform</code> は <code>VBP</code> (Verb, non-3rd person singular present: 動詞, 三単現でない) のタグが付与されています。</p> <p><img src="https://i.imgur.com/8jjLyXe.png" alt="" /> from <a href="http://nlp.stanford.edu:8080/corenlp/process">http://nlp.stanford.edu:8080/corenlp/process</a> の予測結果 (例文は本論文のアブストラクトより引用)</p> <p>これらのNERやPOSは、自然言語処理において、基礎となる重要な処理です。</p> <p>しかしながら、SNSの投稿のようなくだけたテキスト、音声認識の出力などでは、ルール通りに大文字が使われていないこともあります。その場合、大文字を学習に利用したモデルでは、精度が落ちることが示されています。</p> <p><img src="https://i.imgur.com/VIHKdLu.png" alt="" /> (Casedが大文字のテキスト、Uncasedが小文字化したテキストでのタスクの精度。Uncasedのほうが精度が低いことがわかる。)</p> <p>先行研究ではTruecasing (大文字を含む文に復元する方法) が試みられていますが、特有のcasingやtruecasingの間違いによってエラーが引き起こされるため、課題があるように思えます。</p> <p>本論文では、NERとPOSタスクにおいて、cased text (大文字を含むテキスト) と lower-cased/uncased text (小文字のみのテキスト) での結果を比較し、最も適切な方法について検証しています。</p> <p>検証した実験設定は以下のとおりです。</p> <ol> <li>Train on cased: 学習データもテストデータもそのまま</li> <li>Train on uncased: 学習データもテストデータも小文字化する</li> <li>Train on cased+uncased: そのままのデータと小文字化したデータどちらも学習データに利用 <ul> <li>Half Mixed: 学習データの50%のみ小文字化して利用</li> </ul> </li> <li>Train on cased, test on truecased: 学習データはそのまま使い、テストデータをtruecasingする</li> <li>Truecase train and test: 学習データもテストデータもtruecasingする</li> </ol> <p>結果としては、<em>3. Train on cased+uncased</em> がいずれのタスクにおいても良いパフォーマンスであることが示されています。</p> <p>学習に用いるデータ量が単純に2倍になっていることが要因の一つだと考えられます。</p> <p>当たり前の結果といえばそうなのですが、汎用的に利用できるモデルを考える上では、小文字化したデータも利用して学習が必要な可能性を示唆していて興味深いと思いました。</p> <p>日本語には英語における大文字小文字に対応するルールはありませんが、カタカナ・ひらがな・漢字のように複数の表記方法があるので、表記方法の違いについて検証する必要があるのかもしれません。</p> <h2 id="2-A-Little-Annotation-does-a-Lot-of-Good-A-Study-in-Bootstrapping-Low-resource-Named-Entity-Recognizers">2. A Little Annotation does a Lot of Good: A Study in Bootstrapping Low-resource Named Entity Recognizers</h2> <ul> <li>ACL anthology <a href="https://www.aclweb.org/anthology/D19-1520/">https://www.aclweb.org/anthology/D19-1520/</a></li> <li>arXiv <a href="https://arxiv.org/abs/1908.08983">https://arxiv.org/abs/1908.08983</a></li> <li>Github <a href="https://github.com/Aditi138/EntityTargetedActiveLearning">https://github.com/Aditi138/EntityTargetedActiveLearning</a></li> </ul> <p>上記論文の結果からも分かるとおり、ニューラルネットワークを用いたNER (固有表現抽出) モデルにおいて、重要なのはラベル付き学習データ (教師データ) です。英語では、そのようなデータが比較的豊富に存在しますが、他の言語の場合は、十分なデータ資源がないというのが現状です。</p> <p>しかし、ラベルつき (アノテーション) データを作成するのは大変で、コストもかかるため、最低限の資源で実装したいところです。</p> <p>本論文では、課題が多いLow-resourceな言語でのNERに対して、有効な "recipe" を紹介しています。流れとしては大きく3つ、cross-lingual transfer learning (言語を横断する転移学習)、active learning、fine-tunningです。</p> <p><img src="https://i.imgur.com/E45c9Ev.png" alt="" /></p> <ul> <li>Cross-Lingual Transfer Learning <ul> <li>2つの単語分散表現を用意し、bilingual word embeddings (BWE)を学習する</li> <li>BWEを利用して、the cross-domain similarity local scaling (CSLS) metric (<a href="https://github.com/facebookresearch/MUSE">Github</a>)によって単語間の辞書を作る</li> </ul> </li> <li>Entity-Targeted Active Learning <ul> <li>ラベルの付いていないsequenceから、最も有益なスパン (テキストの範囲) を選択する</li> <li>アノテーターの作業は、entity typeを選ぶか、スパンの修正だけですむ</li> <li><img src="https://i.imgur.com/sL24AX5.png" alt="" /></li> <li>図のETALが提案方法、SALが全てのシークエンスにアノテーションする方法</li> </ul> </li> <li>Fine-tunning <ul> <li>a BiLSTM-CNN-CRF model (<a href="https://www.aclweb.org/anthology/D18-1034/">Ma and Hovy (2016)</a>)を用いる</li> <li>Active Learningではアノテーションが一部しかついていないため、Partical-CRFを利用</li> </ul> </li> </ul> <p>実験では、ドイツ語・スペイン語・オランダ語・ヒンディー語・インドネシア語について検証しています。</p> <p>データ資源が少ないと、データを増やす方法を考えがちですが、必要最小限のリソースでタスクを解く工夫を提案している点が面白いと感じました。</p> <h2 id="3-Are-We-Modeling-the-Task-or-the-Annotator-An-Investigation-of-Annotator-Bias-in-Natural-Language-Understanding-Datasets">3. Are We Modeling the Task or the Annotator? An Investigation of Annotator Bias in Natural Language Understanding Datasets</h2> <ul> <li>ACL anthology <a href="https://www.aclweb.org/anthology/D19-1107/">https://www.aclweb.org/anthology/D19-1107/</a></li> <li>arXiv <a href="https://arxiv.org/abs/1908.07898">https://arxiv.org/abs/1908.07898</a></li> <li>Github <a href="https://github.com/mega002/annotator_bias">https://github.com/mega002/annotator_bias</a></li> </ul> <p>上記論文でも言及したように、人手によるアノテーションつきデータは自然言語処理の研究において重要であるものの、作業コストなどの課題が多く存在します。</p> <p>膨大なテキストに対してアノテーションする場合、多くは<a href="https://www.mturk.com">Amazon Mechanical Turk (AMT)</a> などのクラウドソーシングサービスを利用し、クラウドワーカー(アノテーター)に作成を依頼しています。近年の研究では、高い質のデータを作成できる少人数が膨大なデータを生成する方法を選んでいるようです。</p> <p>しかしながら、Natural Language Understanding (NLU) タスクなどで利用されるフリー記述の場合、少人数が作成したデータは、データの多様性やモデルの生成能力に影響する可能性があります。</p> <p>本論文では、NLUタスクにおける、アノテーターによる影響 <strong>annotator bias</strong> を調査しています。</p> <ul> <li>以下の、アノテーター情報が含まれるNLUデータセットについて調査 <ul> <li><a href="https://www.nyu.edu/projects/bowman/multinli/">MNLI</a>: テキストの含意を推論するデータセット</li> <li><a href="https://github.com/allenai/OpenBookQA">OpenBookQA</a>: multi-hop (多段階で推論が必要) に着目した多項式のQAデータセット</li> <li><a href="https://www.tau-nlp.org/commonsenseqa">CommonsenseQA</a>: 常識に着目した多項式のQAデータセット</li> </ul> </li> <li>BERTモデルに、入力文 <code>x</code> 正解ラベル <code>y</code> に加えてアノテーター情報 <code>z</code> を含む、 <code>((z, x),y)</code> を与えて学習を行う <ul> <li>結果、アノテーター情報を含むとモデルの精度が向上した</li> <li> <img src="https://i.imgur.com/5amVV21.png" alt="" /></li> <li> <em>With ID</em> がアノテーター情報を含むモデル</li> </ul> </li> <li>同様に、BERTをfine-tuningしてアノテーターの識別をしたところ、データを作成した割合が高いアノテーターほど、識別が容易であることがわかった</li> </ul> <p>これらの結果から、アノテーターによって学習データとテストデータのアノテーターは分離させるべきであることが論文中で提案されています。</p> <p>データを扱う側としても、アノテーションに何人関わっているのか、アノテーターによって偏りがないかどうか、確認する必要があると考えさせられました。</p> <ul> <li>参考 <blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Are We Modeling the Task or the Annotator? (Tel Aviv U, AI2) <a href="https://t.co/8lV1tggMom">https://t.co/8lV1tggMom</a> MNLI等のデータセットのアノテータの偏りに関する調査.(1) アノテータIDありで学習すると性能up,(2) データからアノテータが分かる (3) 訓練データを作ってないアノテータに対しての汎化性能低い EMNLP19 <a href="https://t.co/yDpPz3dNzj">pic.twitter.com/yDpPz3dNzj</a></p>&mdash; Kyosuke Nishida (@kyoun) <a href="https://twitter.com/kyoun/status/1191622720225603584?ref_src=twsrc%5Etfw">2019年11月5日</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></li> </ul> <h2 id="4-Male-Bachelor-and-Female-PhD-have-different-connotations-Parallelly-Annotated-Stylistic-Language-Dataset-with-Multiple-Personas">4. (Male, Bachelor) and (Female, Ph.D) have different connotations: Parallelly Annotated Stylistic Language Dataset with Multiple Personas</h2> <ul> <li>ACL anthology <a href="https://www.aclweb.org/anthology/D19-1179/">https://www.aclweb.org/anthology/D19-1179/</a></li> <li>Github <a href="https://github.com/dykang/PASTEL">https://github.com/dykang/PASTEL</a></li> </ul> <p>残りの2つはデータがメインの論文を紹介します。</p> <p>先ほどの <em>annotation bias</em> のように、実際の人間の扱う言葉には、スタイル (≒文体) の違いがつきものです。性別・年齢による違いはもちろん、同じ人物でも状況に合わせて、言葉遣いが変化します。</p> <p>このような、文章のスタイルの違いに着目し、文章を目的のスタイルに変換する研究は <em>Style transfer</em> と言います。Computer Visionでは、○○っぽい画風に変換する研究で有名かと思います。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sh111h/20191203/20191203222634.png" alt="f:id:sh111h:20191203222634p:plain" title="f:id:sh111h:20191203222634p:plain" class="hatena-fotolife" itemprop="image"></span> from <a href="https://research.preferred.jp/2015/09/chainer-gogh/">https://research.preferred.jp/2015/09/chainer-gogh/</a></p> <p>NLPにおけるStyle transferも、文のスタイル変換や、著者のプロファイルなどに応用されています。 しかしながら、これらの研究ではパラレルコーパス (翻訳における複数の言語で文同士の対応が付いたコーパス <a href="#f-6e362c4c" name="fn-6e362c4c" title="機械翻訳 - Wikipedia より">*2</a> 。Style transferにおいては 複数のスタイルで <em>同じ意味</em> のコーパス) を利用していないものが多いです。それにより、複数のスタイルのバリエーションを学習したり、評価することは困難です。</p> <p>そこで、本論文は、スタイル、特にペルソナ情報についてのパラレルコーパス、<strong>PASTEL</strong> (the parallel and annotated stylistic language dataset) について紹介しています。</p> <ul> <li>データ構築には <a href="https://www.aclweb.org/anthology/N16-1147/">Visual Story Telling</a>を利用 <ul> <li>元々の意味を保持しつつ、自身のスタイルで文書を作成してもらう</li> <li>どの情報を与えればスタイルが多様になるか予備実験</li> <li>最も良かった <em>Story (images + local keywords)</em> (画像とキーワードを与える) 方法でアノテーションを行う</li> </ul> </li> </ul> <p><img src="https://i.imgur.com/1LRIPcY.png" alt="" /></p> <ul> <li>アノテーターにはペルソナ情報を入力してもらう <ul> <li>性別・年齢・民族・出身・学歴・政治的立場 <img src="https://i.imgur.com/e5yUNDP.png" alt="" /></li> </ul> </li> </ul> <p>このパラレルコーパスによって、教師ありのスタイル変換が可能になります。</p> <p><img src="https://github.com/dykang/PASTEL/raw/master/dataset.png" alt="" /> from <a href="https://github.com/dykang/PASTEL">Github</a></p> <p>また、このパラレルコーパスを用いた応用方法として、<em>controlled style classification</em> を提案しています。 これは、他のスタイル情報を固定した上で、一つのスタイルについて分類するタスクです。例えば、与えられた文章の年齢や性別を当てることで、テキストにおける年齢・性別の違いを表現する特徴量がわかります。</p> <p>画像とは異なり、言語には様々なスタイルが絡んでくるため、明示的にスタイル情報が示されているパラレルコーパスが提案されるのは画期的だと思いました。</p> <h2 id="5-Open-Domain-Web-Keyphrase-Extraction-Beyond-Language-Modeling">5. Open Domain Web Keyphrase Extraction Beyond Language Modeling</h2> <ul> <li>ACL anthology <a href="https://www.aclweb.org/anthology/D19-1521/">https://www.aclweb.org/anthology/D19-1521/</a></li> <li>Github <a href="https://github.com/microsoft/OpenKP">https://github.com/microsoft/OpenKP</a></li> </ul> <p>Keyphrase Extraction (KPE: キーフレーズ抽出) は文書から特徴的なフレーズを抽出するタスクです。 先行研究では、学術論文のドメインにおいて、効果的なモデルが提案されていますが、これは、論文中に論文の著者が作成したキーワードが含まれていることがあり、ドメインの学習データが比較的豊富なことが要因です。</p> <p>しかし、実応用においては、学術ドメインに限らず、スパースなデータ量の、様々なドメインにおいてキーフレーズ抽出ができることが求められます。</p> <p>本論文では、オープンドメインのキーフレーズ抽出タスクに着目したデータセット <strong>OpenKP</strong> について紹介しています。</p> <ul> <li>文書はBing Search Engineからサンプリングされたウェブページ (詳細は <a href="http://www.msmarco.org)">http://www.msmarco.org)</a> <ul> <li>ドメインや文書の種類で制限はしていないため、ニュース記事・動画ページ・リンクまとめページも含んでいる</li> <li>トピックもばらついており、最も多いトピック <em>healthcare</em> でも全体の3.7%程度</li> </ul> </li> <li>1つの文書につき1~3のキーフレーズを人手でアノテーション <ul> <li>複数人の一致率は高くない</li> <li>ランダムに選択した50文書において、5人の上位3つのキーフレーズで、完全一致したのは43%</li> </ul> </li> </ul> <p>また、このデータセットのタスクを解くためのモデル <strong>BLING-KPE</strong> (Beyond Language UnderstandING KeyPhrase Extraction) を提案しています。アーキテクチャの大きな枠組みはCNNとTransformerですが、タイトルの <em>Beyond Language Understanding</em> を目的とした2つの特徴があります。</p> <ul> <li>visual feature (画像の特徴量) <ul> <li>データがウェブページであるため、画面の表示情報を特徴量として入力する</li> <li>具体的には、フォント、テキストサイズ、表示位置、DOM</li> </ul> </li> <li>weak supervision (弱い教師) <ul> <li>検索クエリからそのドキュメントのページがクリックされたかどうかを識別する Query Prediction タスクを、モデルの事前学習に利用</li> <li>クエリはBing search logから取得</li> </ul> </li> </ul> <p>OpenKPでの実験の結果、上記2つの特徴量は有意な特徴量であることが示されています。</p> <p><img src="https://i.imgur.com/EiaR979.png" alt="" /> 赤枠がvisual featureなし、緑枠がvisual featureありの予測結果。 この結果から、フォントサイズやテキストサイズなどの visual feature が有効であることがわかる。</p> <ul> <li>参考: Benchmark datasets for keyphrase extraction (<a href="https://github.com/boudinfl/ake-datasets">https://github.com/boudinfl/ake-datasets</a>)</li> </ul> <h2 id="おわりに">おわりに</h2> <p>EMNLP2019の論文を5つ紹介しました。</p> <p>その他、本会議中開催された<a href="https://www.emnlp-ijcnlp2019.org/program/tutorials/">Tutorial</a>の資料は多くの場合公開されています。 そのトピックについて包括的に知るのには良い資料だと思うので、気になるトピックがあれば読んでみるのもいいかもしれません。</p> <h3 id="参考-Tutorialのリンク集">参考: Tutorialのリンク集</h3> <ul> <li>[T1] Dive into Deep Learning for Natural Language Processing <ul> <li><a href="http://d2l.ai/">http://d2l.ai/</a></li> <li><a href="https://github.com/dmlc/gluon-nlp">https://github.com/dmlc/gluon-nlp</a></li> </ul> </li> <li>[T3] Data Collection and End-to-End Learning for Conversational AI: <a href="https://www.polyai.com/emnlp19/">https://www.polyai.com/emnlp19/</a></li> <li>[T4] Bias and Fairness in Natural Language Processing: <a href="https://twitter.com/kaiwei_chang/status/1190838941206761472">https://twitter.com/kaiwei_chang/status/1190838941206761472</a></li> <li>[T5] Discreteness in Neural Natural Language Processing: <a href="https://lili-mou.github.io">https://lili-mou.github.io</a></li> <li>[T6] Graph Neural Networks for Natural Language Processing: <a href="https://github.com/svjan5/GNNs-for-NLP">https://github.com/svjan5/GNNs-for-NLP</a></li> <li>[T7] Semantic Specialization of Distributional Representations Models: <a href="https://twitter.com/gg42554/status/1192124655533867009">https://twitter.com/gg42554/status/1192124655533867009</a></li> </ul> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4873118360/hoxom-22/"><img src="https://images-fe.ssl-images-amazon.com/images/I/51FoSTiivwL._SL160_.jpg" class="hatena-asin-detail-image" alt="ゼロから作るDeep Learning ❷ ―自然言語処理編" title="ゼロから作るDeep Learning ❷ ―自然言語処理編"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4873118360/hoxom-22/">ゼロから作るDeep Learning ❷ ―自然言語処理編</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/%BA%D8%C6%A3%20%B9%AF%B5%A3" class="keyword">斎藤 康毅</a></li><li><span class="hatena-asin-detail-label">出版社/メーカー:</span> オライリージャパン</li><li><span class="hatena-asin-detail-label">発売日:</span> 2018/07/21</li><li><span class="hatena-asin-detail-label">メディア:</span> 単行本(ソフトカバー)</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> <div class="footnote"> <p class="footnote"><a href="#fn-b9986182" name="f-b9986182" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">参考: <a href="http://www.conferenceranks.com/?searchall=EMNLP#data">Conference Ranks</a></span></p> <p class="footnote"><a href="#fn-6e362c4c" name="f-6e362c4c" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://ja.wikipedia.org/wiki/%E6%A9%9F%E6%A2%B0%E7%BF%BB%E8%A8%B3">機械翻訳 - Wikipedia</a> より</span></p> </div> sh111h 今年読んだNLP系論文で面白かった5つ hatenablog://entry/26006613450004725 2019-10-24T08:30:00+09:00 2019-10-24T08:30:04+09:00 ホクソエムサポーターの白井です。学生時代は自然言語処理の研究をしていました。 「今年読んだ論文、面白かった5つ」というテーマで、自然言語処理(NLP)の論文を紹介します。 主にACL anthologyに公開されている論文から選んでいます。 はじめに 今年のNLP界隈の概観 1. Text Processing Like Humans Do: Visually Attacking and Shielding NLP Systems 面白いと思った点 2. Errudite: Scalable, Reproducible, and Testable Error Analysis 面白いと思った点… <p>ホクソエムサポーターの白井です。学生時代は自然言語処理の研究をしていました。</p> <p>「今年読んだ論文、面白かった5つ」というテーマで、自然言語処理(NLP)の論文を紹介します。 主に<a href="https://www.aclweb.org/anthology/">ACL anthology</a>に公開されている論文から選んでいます。</p> <ul class="table-of-contents"> <li><a href="#はじめに-今年のNLP界隈の概観">はじめに 今年のNLP界隈の概観</a></li> <li><a href="#1-Text-Processing-Like-Humans-Do-Visually-Attacking-and-Shielding-NLP-Systems">1. Text Processing Like Humans Do: Visually Attacking and Shielding NLP Systems</a><ul> <li><a href="#面白いと思った点">面白いと思った点</a></li> </ul> </li> <li><a href="#2-Errudite-Scalable-Reproducible-and-Testable-Error-Analysis">2. Errudite: Scalable, Reproducible, and Testable Error Analysis</a><ul> <li><a href="#面白いと思った点-1">面白いと思った点</a></li> </ul> </li> <li><a href="#3-Language-Models-as-Knowledge-Bases">3. Language Models as Knowledge Bases?</a><ul> <li><a href="#面白いと思った点-2">面白いと思った点</a><ul> <li><a href="#余談">余談</a></li> </ul> </li> </ul> </li> <li><a href="#4-A-Structural-Probe-for-Finding-Syntax-in-Word-Representations">4. A Structural Probe for Finding Syntax in Word Representations</a><ul> <li><a href="#面白いと思った点-3">面白いと思った点</a></li> </ul> </li> <li><a href="#5-Emotion-Cause-Pair-Extraction-A-New-Task-to-Emotion-Analysis-in-Texts">5. Emotion-Cause Pair Extraction: A New Task to Emotion Analysis in Texts</a><ul> <li><a href="#面白いと思った点-4">面白いと思った点</a></li> </ul> </li> <li><a href="#おわりに">おわりに</a></li> </ul> <h2 id="はじめに-今年のNLP界隈の概観">はじめに 今年のNLP界隈の概観</h2> <p>NLP界隈は<a href="https://arxiv.org/abs/1802.05365">ELMo</a>や<a href="https://arxiv.org/abs/1810.04805">BERT</a>が提案されたことによって、多くのタスクが高い精度、人間のパフォーマンスに近い精度を達成できるようになりました。去年から、"BERTをfine tuningしてSOTA" という主張の論文が散見される状況であると個人的には感じています。</p> <p>その一方で、このモデルは本当に人間と同等の能力を持っていると言っていいのか?モデルのパフォーマンスをどう解釈すべきか?というような、 モデルの <strong>解釈性</strong> について疑問を問いかける、あるいは、モデルそのものを <strong>検証</strong> する研究も多く発表されている印象を受けています。</p> <p>同じ流れで、新しいデータや、既存のデータを拡張したデータの公開も常に盛んに行われています。 解釈性の話とも被る部分がありますが、今のモデルでは解けないようなタスクを提案し、データを公開することで、研究領域全体として高めていこうという流れを感じます。</p> <p>今回は、このような研究の流れを踏まえ、検証系の論文をメインに紹介したいと思います。</p> <h2 id="1-Text-Processing-Like-Humans-Do-Visually-Attacking-and-Shielding-NLP-Systems">1. Text Processing Like Humans Do: Visually Attacking and Shielding NLP Systems</h2> <p>NAACL2019</p> <ul> <li>arXiv <a href="https://arxiv.org/abs/1903.11508">https://arxiv.org/abs/1903.11508</a></li> <li>ACL anthology <a href="https://aclweb.org/anthology/papers/N/N19/N19-1165/">https://aclweb.org/anthology/papers/N/N19/N19-1165/</a></li> <li>github <a href="https://github.com/UKPLab/naacl2019-like-humans-visual-attacks">https://github.com/UKPLab/naacl2019-like-humans-visual-attacks</a></li> </ul> <p>Adversarial attack、もしくはAdversarial Perturbation (敵対的摂動) はComputer Vision (CV) 分野のパンダの例で有名な、ノイズを加えることで分類器に誤識別させるものです。</p> <p><img src="https://openai.com/content/images/2017/02/adversarial_img_1.png" alt="image" /></p> <p>From <a href="https://openai.com/blog/adversarial-example-research/">https://openai.com/blog/adversarial-example-research/</a></p> <p>この論文では、画像ではなく、文字のゆらぎを機械が扱うにはどうすればよいか検証しています。 adversarial attackがNLPにおいて重要である例のひとつとしてkaggleの<a href="https://www.kaggle.com/c/jigsaw-toxic-comment-classification-challenge">toxic-comment-classification-challenge</a>をあげています。</p> <p>toxic comment classificationは、wikipedia上のコメントが有害であるかどうかを分類するタスクです。このタスクの難しい点として、SNSなどweb上のコメントでは、有害な単語表現は、文字を置き換えて難読にしていることが挙げられます。</p> <p><img src="https://i.imgur.com/CUiaMKH.png" alt="" /></p> <p>From <a href="https://www.aclweb.org/anthology/N19-1165/">https://www.aclweb.org/anthology/N19-1165/</a></p> <p>上記例では、<code>idiot</code> という有害な単語を <code>idiøţ</code> と表記しています。 このように、人が見た目では読める単語 (<code>idiøţ</code>) も、分類器は文字コードでエンコードした分散表現を用いるため、<code>idiøţ</code> が <code>idiot</code> だと認識できません。これが、人間と機械の間にあるボトルネックです。</p> <p>そこで、本論文では、文字を画像として扱う敵対機構を提案しています。</p> <p>具体的には、以下の内容が述べられてます。</p> <ul> <li><p>敵対機構として <strong>VIPER (Visual Perturber)</strong> という、文字をその文字に似た文字に置き換える機構を提案し、Pos taggingやtoxic comment classificationなどの複数のタスクで 実験したところ、置き換える前より精度が下がった</p> <ul> <li>実際に置き換えた文の例</li> <li><img src="https://i.imgur.com/0wrmbyW.png" alt="perturbed image" /></li> </ul> </li> <li><p>このような置き換えによって精度が下がるのを防ぐ、Shield方法を3つ提案し、実験を行なった。3つの詳細は以下の通り。</p> <ul> <li>adversarial training: 学習データにVIPERで生成したデータを含める</li> <li>character embeddings: 画像情報を利用した単語分散表現を入力に利用</li> <li>rule-based recovery: 画像として近い文字にルールベースで置き換え</li> <li>→ これらの方法で精度の減少は防げるものの、まだ課題は多い</li> </ul> </li> </ul> <h3 id="面白いと思った点">面白いと思った点</h3> <ul> <li>中国語・韓国語・日本語のような文字レベルのcompositionality (構成性) がある言語とは異なり、ラテン語系は文字レベルで研究を行うことは多くないため、珍しい論文だと思いました</li> <li>CVの考え方が、NLPに輸入されることは多いが、モチベーションとして文字レベルのadversarial exampleを生成するという提案は直感的で良い。言語関係なく、表層で単語を認識することは重要だと思います</li> </ul> <h2 id="2-Errudite-Scalable-Reproducible-and-Testable-Error-Analysis">2. Errudite: Scalable, Reproducible, and Testable Error Analysis</h2> <p>ACL2019</p> <ul> <li>ACL anthology <a href="https://www.aclweb.org/anthology/P19-1073/">https://www.aclweb.org/anthology/P19-1073/</a></li> <li>github <a href="https://github.com/uwdata/errudite">https://github.com/uwdata/errudite</a></li> <li>website <a href="http://idl.cs.washington.edu/papers/errudite/">http://idl.cs.washington.edu/papers/errudite/</a></li> <li>blog <a href="https://medium.com/@uwdata/errudite-55d5fbf3232e">https://medium.com/@uwdata/errudite-55d5fbf3232e</a> <ul> <li>以降の図はblogから引用</li> </ul> </li> </ul> <p>NLPに限らず、モデルの特性を理解する上でエラー分析は必須です。 この論文では、エラー分析に関する原則 (principle) を挙げ、それらをインタラクティブにサポートするツール <strong>Errudite</strong> を紹介しています。</p> <p>論文では、Machine Comprehension(以下 MC)のモデルBiDAF<a href="https://arxiv.org/abs/1611.01603">(Seo et al.,2017)</a>のSQuADデータにおける結果を具体例として用い、原則を記述しています。 ちなみにMCとは、システムに文章を読ませ、理解させるタスクです。また、<a href="https://rajpurkar.github.io/SQuAD-explorer/">SQuAD</a>は文章と質問を入力とし、質問の回答を文章から選択するタスクのデータセットです。</p> <p>(参考:<a href="https://www.slideshare.net/takahirokubo7792/machine-comprehension">文章を読み、理解する機能の獲得に向けて-Machine Comprehensionの研究動向-</a>)</p> <p>提案されている原則の詳細は以下の3つです。</p> <ul> <li>エラーの仮説は具体的な描写で正確に定義されるべきである <ul> <li>e.g. 「質問文が長いと精度が悪い」ではなく「質問文が <em>20token</em> 以上だと精度が悪い」と書く</li> </ul> </li> <li>エラーのprevalence(分布率)は全体のデータセットで判断する <ul> <li><code>BiDAF is good at matching questions to entity types, but is often distracted by other spans with the same entity type</code> (BiDAFは名詞などのエンテティを一致させるのに優れているが、多くの場合、同じエンテティの種類が同じ別の範囲を予測してしまう) という <em>Distractor Hypothesis</em> がある。 <ul> <li><img src="https://miro.medium.com/max/3188/1*aHAQs9Dx6Lu-WX8YN9p8AA.png" alt="" /></li> <li>例えば、上記図のように、ドクターフーの2005年のテーマを作った人物は、<em>Murray Gold</em> が正答だが、同じ種類 (人物) である <em>John Debney</em> と回答してしまう場合のこと</li> </ul> </li> <li>しかしながら、すべてのinstanceの正答率が68%である一方、その中で、答えがentityであるデータの正答率は80%であることがわかった。</li> </ul> </li> <li>エラーの仮説は直接的に調べるべきである <ul> <li>counterfactual questions (反事実的な質問) <code>“If the predicted distractor was not there, would the model predict correctly?</code> を実証するため、distractorに当たる単語を書き替えてモデルの出力結果が変わるか分析する。 <ul> <li><img src="https://miro.medium.com/max/4280/1*l19AP12hBfFtO2KFkQxhdQ.png" alt="" /></li> <li>上記ドクターフーの例であれば、distractorである <em>John Debney</em> を他の単語( <em>#</em> )に置き換える。すると、モデルの出力はさらに異なる人物名である <em>Ron Grainer</em> と予測した。</li> <li>このように、他のdistractorがモデルに誤った予測をさせてしまうケースが29%存在する一方、他の単語( <em>#</em> )に置き換えても予測が変わらないケースが23%存在することがわかった。</li> </ul> </li> </ul> </li> </ul> <h3 id="面白いと思った点-1">面白いと思った点</h3> <ul> <li>エラー分析が意外とおざなりになっていることに注目している点 <ul> <li>appendixをみると、ACLのようなトップ会議に通っている論文でも、エラー分析のサンプルサイズが50程度のケースが存在するがわかります</li> <li>確かに、MCのような文章から答えとなる単語の範囲 (span) を当てるタスクにおいて、どのようにエラー分析すべきかは明文化されてなかったので、個人的に画期的だと思いました</li> </ul> </li> <li>エラー分析の提案に対して、ツールとして実装し、公開している点</li> </ul> <h2 id="3-Language-Models-as-Knowledge-Bases">3. Language Models as Knowledge Bases?</h2> <p>EMNLP2019 (to appear)</p> <ul> <li>arXiv <a href="https://arxiv.org/abs/1909.01066v2">https://arxiv.org/abs/1909.01066v2</a></li> <li>github <a href="https://github.com/facebookresearch/LAMA">https://github.com/facebookresearch/LAMA</a></li> </ul> <p>最初にも述べた、BERTやELMoは言語モデルです。言語モデルとは、尤もらしい文・文章を生成するモデルです。</p> <p>具体的には、ある文字の並び <img src="https://chart.apis.google.com/chart?cht=tx&chl=%5Cmathrm%7Bw%7D%20%3D%20%5Bw_1%2C%20w_2%2C%20...%20.%20%2Cw_n%20" alt="\mathrm{w} = [w_1, w_2, ... . ,w_n "/>] が生成される確率 <img src="https://chart.apis.google.com/chart?cht=tx&chl=P%28%5Cmathrm%7Bw%7D%29" alt="P(\mathrm{w})"/> は</p> <pre> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%0AP%28%5Cmathrm%7Bw%7D%29%3D%5Cprod_%7Bt%7D%7BP%28%20w_t%20%7C%20w_1%2C%20w_2%2C%20...%2C%20w_%7Bt-1%7D%29%7D%0A" alt=" P(\mathrm{w})=\prod_{t}{P( w_t | w_1, w_2, ..., w_{t-1})} "/> </pre> <p>で表されます。</p> <p>つまり、1からt-1番目まで、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%5Bw_1%2C%20w_2%2C%20...%20.%20%2Cw_%7Bt-1%7D%20" alt="[w_1, w_2, ... . ,w_{t-1} "/>] の順番で単語が並んでいる時、その次の単語が <img src="https://chart.apis.google.com/chart?cht=tx&chl=w_t" alt="w_t"/> である確率の総乗で表現されます。</p> <p>(これはシンプルな、一定方向の言語モデルの話です)</p> <p>本論文では、このような言語モデルが知識ベース (Knowledge Base 以下 KB) としてどの程度扱うことができるか、検証する <strong>LAMA (LAnguage Model Analysis)</strong> を提案し、実際に検証を行っています。</p> <p>本来KBは <code>(Dante, born-in, X)</code> のようなスキーマが定まった学習データを用いて <code>X</code> を予測するタスクです。 本論文では、 <code>Dante was born in [Mask] in the year 1265.</code> のように自然文で扱い、<code>[Mask]</code> を予測するタスクとして扱うことで、言語モデルで検証を行なっています。</p> <p>具体的には、fairseq-fconv, Transformer-XL large, ELMo, BERTといった言語モデルについて、複数のデータセット (Google-RE, T-REx, ConceptNet, SQuAD) を用いて実験を行なっています。</p> <p>結果として、BERTモデルが、Corpusと予測すべきrelationによっては既存のKBモデルよりも高い精度を達成していることを報告しています。特にT-REx (Wikipediaから抽出されたtripleを予測するタスク) において、1-to-1 relationでMean precision at one (P@1) が74.5という高いパフォーマンスになっています。</p> <h3 id="面白いと思った点-2">面白いと思った点</h3> <ul> <li>言語モデルでKBを解こうと試みている点</li> <li>大規模コーパスを用いた学習済み言語モデルを用いている以上、既存のKBモデルと単純な比較はできないものの、既存のKBタスクを解くことで学習済み言語モデルが構造的な知識を持っているか調査できる点</li> </ul> <h4 id="余談">余談</h4> <ul> <li><a href="https://talktotransformer.com">https://talktotransformer.com</a> <ul> <li>ここでtransformerの出力を試すことができます</li> </ul> </li> <li>こんな話もあるので、まだまだ検証の余地はあるかもしれません<blockquote class="twitter-tweet" data-lang="HASH(0x5605eed7ecd0)"><p lang="en" dir="ltr">&quot;Language models as knowledge bases?&quot; they asked: <a href="https://t.co/O7BCcy0V6r">https://t.co/O7BCcy0V6r</a><br>&quot;A cat has four kidneys&quot;, replied GPT-2. <a href="https://t.co/yMizwyOSpU">pic.twitter.com/yMizwyOSpU</a></p>&mdash; Graham Neubig (@gneubig) <a href="https://twitter.com/gneubig/status/1177276621172150272?ref_src=twsrc%5Etfw">September 26, 2019</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></li> </ul> <h2 id="4-A-Structural-Probe-for-Finding-Syntax-in-Word-Representations">4. A Structural Probe for Finding Syntax in Word Representations</h2> <p>NAACL2019</p> <ul> <li>ACL anthology <a href="https://www.aclweb.org/anthology/papers/N/N19/N19-1419/">https://www.aclweb.org/anthology/papers/N/N19/N19-1419/</a></li> <li>github <a href="https://github.com/john-hewitt/structural-probes/">https://github.com/john-hewitt/structural-probes/</a></li> <li>author site <a href="https://nlp.stanford.edu//~johnhew//">https://nlp.stanford.edu//~johnhew//</a> <ul> <li>blog <a href="https://nlp.stanford.edu//~johnhew/structural-probe.html">https://nlp.stanford.edu//~johnhew/structural-probe.html</a></li> <li>以降の図はblogから引用</li> </ul> </li> </ul> <p>言語モデルの検証を行っている論文をもう一つ紹介します。</p> <p>この論文では、言語モデルがsyntactic (統語的) な構造を持っているか?を検証する <strong>structural probe</strong> を提案しています。BERT,ELMoのような言語モデルが出力する単語分散表現を、木構造として扱い、正しくparseできるかどうかを検証します。</p> <p><img src="https://nlp.stanford.edu//~johnhew//public/tree.png" alt="image" /></p> <p>具体的な検証方法は大きく2つです。</p> <p>ひとつは、2つの単語ベクトルのL2距離の二乗を測るために空間を変換し、短い距離の単語同士をつなげることで構文木を作成する方法。もうひとつは、L2ノルムの二乗で単語の木の深さを測るための線形変換を行い、root (木の根) からの深さを検証する方法です。</p> <p>Penn Treebankを用いた実験の結果、ELMo・BERTともに変換可能であり、構文木を構築することができることがわかりました。 また、実際の構文木の結果は論文中で可視化されています。</p> <p><img src="https://nlp.stanford.edu//~johnhew//public/sp/firsttrees.png" alt="sample" /></p> <h3 id="面白いと思った点-3">面白いと思った点</h3> <ul> <li>stanfordが言語モデルが統語情報を持っていることを証明するための手法を提案している点 <ul> <li><a href="https://stanfordnlp.github.io/CoreNLP/">stanford core NLP</a>などを公開している、stanfordらしいアプローチだなと思いました</li> </ul> </li> </ul> <h2 id="5-Emotion-Cause-Pair-Extraction-A-New-Task-to-Emotion-Analysis-in-Texts">5. Emotion-Cause Pair Extraction: A New Task to Emotion Analysis in Texts</h2> <p>ACL2019</p> <ul> <li>ACL anthology <a href="https://www.aclweb.org/anthology/P19-1096">https://www.aclweb.org/anthology/P19-1096</a></li> <li>arXiv <a href="https://arxiv.org/abs/1906.01267">https://arxiv.org/abs/1906.01267</a></li> <li>github <a href="https://github.com/NUSTM/ECPE">https://github.com/NUSTM/ECPE</a></li> </ul> <p>個人的にsentiment analysisをはじめとした感情分析系に興味があるため、最後にその系統の論文を紹介します。</p> <p>文書からemotion (感情) とcause (原因) のペアを抽出するタスク <strong>emotion-cause pair extraction (ECPE)</strong> を新たに提案している論文です。</p> <p><img src="https://miro.medium.com/max/850/0*eSeItfnRt4slAAb-.png" alt="image" /> From <a href="https://medium.com/dair-ai/a-deep-learning-approach-to-improve-emotion-cause-extraction-135bd9ea3899">https://medium.com/dair-ai/a-deep-learning-approach-to-improve-emotion-cause-extraction-135bd9ea3899</a></p> <p>先行研究であるEmotion cause extraction (ECE) はemotionを入力として、causeに該当するclauseを抽出するタスクです。しかし、これだとemotionがアノテーションされている前提のタスクになってしまい、実応用に結びつきません。また、causeとemotionも相互に結びつかないことも問題です。</p> <p>そこで、emotion-cause pair extraction (ECPE) では、emotionとcauseのどちらも抽出するrelation extraction taskを提案しています。</p> <p>実際にタスクを解くアプローチとしては、emotion一覧、cause一覧をそれぞれ取得、emotionとcauseのペアにして正解のフィルタリングを行うマルチタスク学習を提案しています。実験を行なった結果としてはemotionの抽出結果をcauseの結果に利用する手法の方がF値が高くなりました。</p> <p>ちなみに図は英語ですが、論文中で利用しているデータセットは中国語です。</p> <h3 id="面白いと思った点-4">面白いと思った点</h3> <ul> <li>sentiment (ポジネガ) よりも粒度が細かいemotion、そしてその原因も同時に扱う、実応用に近いタスクを提案している点 <ul> <li>より詳細なsentiment analysis (感情分析) を行うaspect-based sentiment analysisやtargeted sentiment analysisと近いが、sentimentをより詳細に扱えるのは面白い。</li> </ul> </li> <li>個人的な意見ですが、sentiment、emotionを扱うNLPはレッドオーシャンにも関わらず、実応用として利用されることが多くなっているわけではないため、このようなタスクの精度が上がって実社会で扱えることを期待しています。</li> </ul> <h2 id="おわりに">おわりに</h2> <p>検証系の論文をメインに紹介しました。</p> <p>自然言語という人間が何気なく使っているモノに対して、計算機科学を用いて深い理解を得ようとするのがNLPの面白い点です。</p> <p>また、NLP系の論文では他の研究分野の流れを汲み取ったり (1番目に紹介したAdversarial attack) 、言語の違い (英語・中国語・日本語) によってアプローチに違いがあったり、多角・多様であることも面白い点です。</p> <p>その面白さが少しでも伝われば幸いです。</p> <p><div class="hatena-asin-detail"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/4061529242/hoxom-22/"><img src="https://images-fe.ssl-images-amazon.com/images/I/51VWk9HK-OL._SL160_.jpg" class="hatena-asin-detail-image" alt="深層学習による自然言語処理 (機械学習プロフェッショナルシリーズ)" title="深層学習による自然言語処理 (機械学習プロフェッショナルシリーズ)"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/4061529242/hoxom-22/">深層学習による自然言語処理 (機械学習プロフェッショナルシリーズ)</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span> 坪井祐太,海野裕也,鈴木潤</li><li><span class="hatena-asin-detail-label">出版社/メーカー:</span> 講談社</li><li><span class="hatena-asin-detail-label">発売日:</span> 2017/05/25</li><li><span class="hatena-asin-detail-label">メディア:</span> 単行本(ソフトカバー)</li><li><a href="http://d.hatena.ne.jp/asin/4061529242/hoxom-22" target="_blank">この商品を含むブログ (1件) を見る</a></li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> sh111h