次元削減 - 静かなる名辞
pythonとプログラミングのこと
2020-05-07T20:42:34+09:00
hayataka2049
Hatena::Blog
hatenablog://blog/10328537792367869878
【python】scikit-learnで大規模疎行列を扱うときのTips
hatenablog://entry/26006613394927158
2019-08-14T02:33:31+09:00
2020-02-08T07:35:57+09:00 はじめに 自然言語処理などで大規模疎行列を扱うことがあります。一昔前はNLPといえばこれでした(最近は低次元密行列で表現することのほうが多いですが)。 疎行列はその特性をうまく生かして扱うとパフォーマンス上のメリットが得られる反面、うかつにdenseな表現に展開してしまうと効率が悪くなって激遅になったり、あっさりメモリから溢れたりします。 scikit-learnでやる場合、うっかり使うと自動的にdenseな表現に展開されてしまう、という事故が起こりがちで、要するに使えるモデルに制約があり、注意が必要です。その辺の基本的なことをまとめておきます。 目次 はじめに 疎行列ってなに? 特徴抽出する…
<div class="section">
<h3 id="はじめに">はじめに</h3>
<p> 自然言語処理などで大規模疎行列を扱うことがあります。一昔前はNLPといえばこれでした(最近は低次元密行列で表現することのほうが多いですが)。</p><p> 疎行列はその特性をうまく生かして扱うとパフォーマンス上のメリットが得られる反面、うかつにdenseな表現に展開してしまうと効率が悪くなって激遅になったり、あっさりメモリから溢れたりします。</p><p> scikit-learnでやる場合、うっかり使うと自動的にdenseな表現に展開されてしまう、という事故が起こりがちで、要するに使えるモデルに制約があり、注意が必要です。その辺の基本的なことをまとめておきます。</p><p> 目次</p>
<ul class="table-of-contents">
<li><a href="#はじめに">はじめに</a></li>
<li><a href="#疎行列ってなに">疎行列ってなに?</a></li>
<li><a href="#特徴抽出する">特徴抽出する</a></li>
<li><a href="#特徴選択する">特徴選択する</a></li>
<li><a href="#標準化する">標準化する</a></li>
<li><a href="#次元削減する">次元削減する</a></li>
<li><a href="#その他の各種transformerを使う">その他の各種transformerを使う</a></li>
<li><a href="#分類や回帰など予測に使う">分類や回帰など、予測に使う</a></li>
<li><a href="#実際にやってみる">実際にやってみる</a></li>
<li><a href="#まとめ">まとめ</a></li>
</ul>
</div>
<div class="section">
<h3 id="疎行列ってなに">疎行列ってなに?</h3>
<p> まず、Pythonで疎行列といえばscipy.sparse.csr_matrixなどのことです。</p><p><a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csr_matrix.html">scipy.sparse.csr_matrix — SciPy v1.4.1 Reference Guide</a></p><p> 内部の構造の詳細などは、こちらの記事が参考になります。</p><p><a href="http://hamukazu.com/2014/12/03/internal-data-structure-scipy-sparse/">scipy.sparseの内部データ構造 – はむかず!</a></p><p> 重要なのは、まずこの方式にすると「メモリ効率がいい」ということです。これは単純に嬉しいですし、CPUキャッシュのことを考えても、パフォーマンス上大きなメリットがあります。また、0の要素は飛ばして探索できるので、うまく使うと効率も良くなります。</p><p> 大規模疎行列を相手にするときは、できるだけ疎行列のまま取り扱うことが重要になります。</p>
</div>
<div class="section">
<h3 id="特徴抽出する">特徴抽出する</h3>
<p> 自然言語処理系のタスクだと、CountVectorizerやTfidfVectorizerが使えます。どちらもデフォルトでcsr_matrixを返してくれるので、素直に使えば疎行列が出てきます。</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html">sklearn.feature_extraction.text.CountVectorizer — scikit-learn 0.22.1 documentation</a><br />
<a href="https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html">sklearn.feature_extraction.text.TfidfVectorizer — scikit-learn 0.22.1 documentation</a></p><p> もう少し幅広いタスクで使いたい場合は、DictVectrizerが便利でしょう。こちらもデフォルトではsparseな表現を返します(オプションでnumpy配列を返すようにすることも可能)。</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.DictVectorizer.html">sklearn.feature_extraction.DictVectorizer — scikit-learn 0.22.1 documentation</a><br />
</p>
</div>
<div class="section">
<h3 id="特徴選択する">特徴選択する</h3>
<p> 特徴抽出したあと素直に使うとだいたい変数が多すぎて使いづらいので、普通は変数選択をすると思います。sklearn.feature_selectionのものなら、これはだいたいデフォルトで疎行列のままの取り扱いに対応しています。</p><p><a href="https://scikit-learn.org/stable/modules/feature_selection.html">1.13. Feature selection — scikit-learn 0.22.1 documentation</a></p><p> 疎行列としてinputすれば疎行列で出てきます。速度もそうした方が速いです。</p><p><a href="https://www.haya-programming.com/entry/2019/07/14/043514">sklearnの変数選択は疎行列型(csr_matrix)でやると速いっぽいよ - 静かなる名辞</a><br />
</p>
</div>
<div class="section">
<h3 id="標準化する">標準化する</h3>
<p> StandardScalerで標準化する場合は、with_mean=Falseを指定してください。これは平均0にしない標準化です。標準化の式の分子で平均を引かないものです。</p><p> それだけで疎行列型のまま標準化することができます。</p>
<blockquote>
<p>This scaler can also be applied to sparse CSR or CSC matrices by passing with_mean=False to avoid breaking the sparsity structure of the data.</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html">sklearn.preprocessing.StandardScaler — scikit-learn 0.22.1 documentation</a></p>
</blockquote>
<p><br />
<a href="https://www.haya-programming.com/entry/2020/02/08/073241">scikit-learnのStandardScalerで疎行列型のまま標準化する - 静かなる名辞</a></p><p></p>
</div>
<div class="section">
<h3 id="次元削減する">次元削減する</h3>
<p> Truncated SVDという素晴らしい手法があり、実装上も疎行列に対応しているので、こちらを使ってください。逆に、これ以外の選択肢は(おそらく)ありません。</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.TruncatedSVD.html">sklearn.decomposition.TruncatedSVD — scikit-learn 0.22.1 documentation</a></p><p> ただし、次元削減した方が良いのか、しない方が良いのかはなんとも言えません。次元削減は行わないで、疎行列型のまま後段のモデルに突っ込むという選択もあるからです。ぶっちゃけ性能は大して変わらないし、次元削減に時間がかかるのと大規模密行列になってしまうぶんだけ遅くなるかもしれない……という微妙な性質があります。</p><p> それでも、次元削減が必要ならやればできます。</p>
</div>
<div class="section">
<h3 id="その他の各種transformerを使う">その他の各種transformerを使う</h3>
<p> transformの返り値がsparse matrixになるかどうかを確認してください。油断しているとnumpy配列に変換されます。</p><p> リファレンスからある程度は読み取れますが、わからないことも多いので、一度動かして確かめた方が良いと思います。</p>
</div>
<div class="section">
<h3 id="分類や回帰など予測に使う">分類や回帰など、予測に使う</h3>
<p> Truncated SVDで次元削減をした場合は勝手にnumpy配列になっているので、どんなモデルにも入力できます(実用的な速度と性能が両立できるかは別)。</p><p> csr_matrixのまま突っ込む場合は、そのまま入力できるestimatorとできないestimatorがあるので、注意が必要です。これを確認するには、リファレンスのfitメソッドのパラメータを見ます。</p><p> たとえばRandomForestClassifierだと、</p>
<blockquote>
<p>X : array-like or sparse matrix of shape = [n_samples, n_features]</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html#sklearn.ensemble.RandomForestClassifier.fit">3.2.4.3.1. sklearn.ensemble.RandomForestClassifier — scikit-learn 0.22.1 documentation</a></p>
</blockquote>
<p> という記述があり、sparse matrixと書いてあるのが「疎行列型でも受け付けて、適切に取り扱ってあげますよ」という意味です。一方、たとえばLinearDiscriminantAnalysisだと(あまり使う人もいないと思いますが)、</p>
<blockquote>
<p>X : array-like, shape (n_samples, n_features)</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.discriminant_analysis.LinearDiscriminantAnalysis.html#sklearn.discriminant_analysis.LinearDiscriminantAnalysis.fit">sklearn.discriminant_analysis.LinearDiscriminantAnalysis — scikit-learn 0.22.1 documentation</a></p>
</blockquote>
<p> と書いてあります。array-likeのときは、渡せば動くけど、内部的にはdenseな表現(numpy配列)に変換されてしまう公算が大きいです。でもけっきょくのところはよくわからないので、実際に入れて動くかどうか試してみた方が良いでしょう。</p><p> 他に実例は省略しますがnumpy arrayと書いてあるときも(たぶん)あり、この場合はたぶんsparse matrixだとエラーを吐きます。</p><p> 実際に動かしてみないと挙動がわからないこともままあるので、突っ込んでみてエラーが出ないか、メモリ消費が異常に膨れ上がらないかを確認しながら作業した方が良いと思います。</p><p> 以下は疎行列型でも行ける(と思う)代表的なestimatorのリストです。</p><p> <b>分類器</b></p>
<ul>
<li>sklearn.ensemble.RandomForestClassifier</li>
<li>sklearn.svm.SVC</li>
<li>sklearn.svm.LinearSCV</li>
<li>sklearn.naive_bayes.MultinomialNB</li>
</ul><p> 非負のみ。文書分類向き</p>
<ul>
<li>sklearn.linear_model.LogisticRegression</li>
</ul><p> <b>回帰モデル</b></p>
<ul>
<li>sklearn.ensemble.RandomForestRegressor</li>
<li>sklearn.svm.SVR</li>
<li>sklearn.linear_model.ElasticNet</li>
</ul><p> 代表的なものはだいたい対応しているけど、たまに使えないのもあるという感じです。</p>
</div>
<div class="section">
<h3 id="実際にやってみる">実際にやってみる</h3>
<p> 20newsgroupsの文書ベクトルを返してくれるものがあるので、それでやります。</p>
<blockquote>
<p>Classes 20<br />
Samples total 18846<br />
Dimensionality 130107<br />
Features real</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.datasets.fetch_20newsgroups_vectorized.html">sklearn.datasets.fetch_20newsgroups_vectorized — scikit-learn 0.22.1 documentation</a></p>
</blockquote>
<p><a href="https://www.haya-programming.com/entry/2018/12/26/034613">sklearnのfetch_20newsgroups_vectorizedでベクトル化された20 newsgroupsを試す - 静かなる名辞</a></p><p> ご覧の通りでかいので、ナイーブにnumpy配列に変換して扱おうとすると苦労します。前にやったときは密行列に変換しようとしていろいろ苦労していましたが、疎行列型のままやった方がシンプルです。</p><p> もちろん通例通り、Pipelineを使ってモデルを組み合わせます。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2018%2F02%2F22%2F234011" title="【python】sklearnのPipelineを使うとできること - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2018/02/22/234011">【python】sklearnのPipelineを使うとできること - 静かなる名辞</a><br />
</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> fetch_20newsgroups_vectorized
<span class="synPreProc">from</span> sklearn.feature_selection <span class="synPreProc">import</span> SelectKBest
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> TruncatedSVD
<span class="synPreProc">from</span> sklearn.svm <span class="synPreProc">import</span> LinearSVC
<span class="synPreProc">from</span> sklearn.pipeline <span class="synPreProc">import</span> Pipeline
<span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> classification_report
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
train = fetch_20newsgroups_vectorized(subset=<span class="synConstant">"train"</span>)
test = fetch_20newsgroups_vectorized(subset=<span class="synConstant">"test"</span>)
X_train, y_train = train.data, train.target
X_test, y_test = test.data, test.target
target_names = train.target_names
skb = SelectKBest(k=<span class="synConstant">5000</span>)
tsvd = TruncatedSVD(n_components=<span class="synConstant">1000</span>)
svm = LinearSVC()
clf = Pipeline([(<span class="synConstant">"skb"</span>, skb), (<span class="synConstant">"tsvd"</span>, tsvd), (<span class="synConstant">"svm"</span>, svm)])
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
<span class="synIdentifier">print</span>(classification_report(
y_test, y_pred, target_names=target_names))
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p> 実行したところ、1分くらいで処理が完了しました。パフォーマンスのよさが伺えます。</p><p> 結果。</p>
<pre class="code lang-python" data-lang="python" data-unlink> precision recall f1-score support
alt.atheism <span class="synConstant">0.70</span> <span class="synConstant">0.68</span> <span class="synConstant">0.69</span> <span class="synConstant">319</span>
comp.graphics <span class="synConstant">0.70</span> <span class="synConstant">0.71</span> <span class="synConstant">0.70</span> <span class="synConstant">389</span>
comp.os.ms-windows.misc <span class="synConstant">0.71</span> <span class="synConstant">0.71</span> <span class="synConstant">0.71</span> <span class="synConstant">394</span>
comp.sys.ibm.pc.hardware <span class="synConstant">0.66</span> <span class="synConstant">0.64</span> <span class="synConstant">0.65</span> <span class="synConstant">392</span>
comp.sys.mac.hardware <span class="synConstant">0.76</span> <span class="synConstant">0.76</span> <span class="synConstant">0.76</span> <span class="synConstant">385</span>
comp.windows.x <span class="synConstant">0.79</span> <span class="synConstant">0.72</span> <span class="synConstant">0.75</span> <span class="synConstant">395</span>
misc.forsale <span class="synConstant">0.81</span> <span class="synConstant">0.87</span> <span class="synConstant">0.84</span> <span class="synConstant">390</span>
rec.autos <span class="synConstant">0.83</span> <span class="synConstant">0.83</span> <span class="synConstant">0.83</span> <span class="synConstant">396</span>
rec.motorcycles <span class="synConstant">0.91</span> <span class="synConstant">0.90</span> <span class="synConstant">0.90</span> <span class="synConstant">398</span>
rec.sport.baseball <span class="synConstant">0.84</span> <span class="synConstant">0.89</span> <span class="synConstant">0.87</span> <span class="synConstant">397</span>
rec.sport.hockey <span class="synConstant">0.92</span> <span class="synConstant">0.95</span> <span class="synConstant">0.94</span> <span class="synConstant">399</span>
sci.crypt <span class="synConstant">0.89</span> <span class="synConstant">0.88</span> <span class="synConstant">0.89</span> <span class="synConstant">396</span>
sci.electronics <span class="synConstant">0.66</span> <span class="synConstant">0.65</span> <span class="synConstant">0.66</span> <span class="synConstant">393</span>
sci.med <span class="synConstant">0.81</span> <span class="synConstant">0.78</span> <span class="synConstant">0.79</span> <span class="synConstant">396</span>
sci.space <span class="synConstant">0.86</span> <span class="synConstant">0.87</span> <span class="synConstant">0.86</span> <span class="synConstant">394</span>
soc.religion.christian <span class="synConstant">0.76</span> <span class="synConstant">0.91</span> <span class="synConstant">0.83</span> <span class="synConstant">398</span>
talk.politics.guns <span class="synConstant">0.68</span> <span class="synConstant">0.88</span> <span class="synConstant">0.77</span> <span class="synConstant">364</span>
talk.politics.mideast <span class="synConstant">0.91</span> <span class="synConstant">0.81</span> <span class="synConstant">0.85</span> <span class="synConstant">376</span>
talk.politics.misc <span class="synConstant">0.71</span> <span class="synConstant">0.54</span> <span class="synConstant">0.62</span> <span class="synConstant">310</span>
talk.religion.misc <span class="synConstant">0.61</span> <span class="synConstant">0.41</span> <span class="synConstant">0.49</span> <span class="synConstant">251</span>
accuracy <span class="synConstant">0.78</span> <span class="synConstant">7532</span>
macro avg <span class="synConstant">0.78</span> <span class="synConstant">0.77</span> <span class="synConstant">0.77</span> <span class="synConstant">7532</span>
weighted avg <span class="synConstant">0.78</span> <span class="synConstant">0.78</span> <span class="synConstant">0.78</span> <span class="synConstant">7532</span>
</pre><p> 今回は性能を重視していないのでこの程度です。このタスクだとできるだけ次元を維持したまま(疎行列型のまま)ナイーブベイズに入れたほうが速くて性能が出るという知見を以前に得ています。その場合は0.83くらいまで出ています。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F05%2F15%2F232429" title="【python】sklearnのfetch_20newsgroupsで文書分類を試す(5) - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/05/15/232429">【python】sklearnのfetch_20newsgroupsで文書分類を試す(5) - 静かなる名辞</a><br />
</p>
</div>
<div class="section">
<h3 id="まとめ">まとめ</h3>
<p> うまく疎行列型配列を使うと数桁くらい時間を節約できます。ぜひ活用してみてください。</p><p> こちらの記事もおすすめです。<br />
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F05%2F10%2F191111" title="scikit-learnのモデルに疎行列(csr_matrix)を渡したときの速度 - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/05/10/191111">scikit-learnのモデルに疎行列(csr_matrix)を渡したときの速度 - 静かなる名辞</a><br />
</p>
</div>
hayataka2049
カーネルPCAで文字列の編集距離を可視化してみる
hatenablog://entry/17680117127214949650
2019-07-08T01:20:53+09:00
2019-09-01T00:09:33+09:00 はじめに 以前に編集距離が計算された文字列間の位置関係をMDSを使ってまったく同じことをしましたが、今度はカーネルPCAでやってみます。 違いとしては、MDSは距離行列から計算を行うのに対してカーネルPCAは類似度行列から計算を行えるということがあると思います。逆に言えば、それくらいしか違わないし、だいたい同じようなことができるということです。 なお、この記事を読む前に、以下の2記事を先にご覧いただくとやっていることがわかりやすいと思います。多次元尺度構成法(MDS)で文字列の編集距離を可視化してみる - 静かなる名辞 scikit-learnのSVMを自分で計算したカーネルで使う - 静かな…
<div class="section">
<h3>はじめに</h3>
<p> 以前に編集距離が計算された文字列間の位置関係をMDSを使ってまったく同じことをしましたが、今度はカーネルPCAでやってみます。</p><p> 違いとしては、MDSは距離行列から計算を行うのに対してカーネルPCAは類似度行列から計算を行えるということがあると思います。逆に言えば、それくらいしか違わないし、だいたい同じようなことができるということです。</p><p> なお、この記事を読む前に、以下の2記事を先にご覧いただくとやっていることがわかりやすいと思います。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F06%2F27%2F231442" title="多次元尺度構成法(MDS)で文字列の編集距離を可視化してみる - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/06/27/231442">多次元尺度構成法(MDS)で文字列の編集距離を可視化してみる - 静かなる名辞</a><br />
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F07%2F07%2F042622" title="scikit-learnのSVMを自分で計算したカーネルで使う - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/07/07/042622">scikit-learnのSVMを自分で計算したカーネルで使う - 静かなる名辞</a><br />
</p>
</div>
<div class="section">
<h3>実験</h3>
<p> カーネルPCAのドキュメントはこちらです。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fscikit-learn.org%2Fstable%2Fmodules%2Fgenerated%2Fsklearn.decomposition.KernelPCA.html" title="sklearn.decomposition.KernelPCA — scikit-learn 0.21.3 documentation" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.KernelPCA.html">sklearn.decomposition.KernelPCA — scikit-learn 0.21.3 documentation</a></p><p> kernel="precomputed"として、あとは淡々と実装します。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> Levenshtein
<span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> KernelPCA
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
<span class="synComment"># データ(自作)</span>
X = [<span class="synConstant">"hogehoge"</span>, <span class="synConstant">"hogghheg"</span>, <span class="synConstant">"fogefoge"</span>, <span class="synConstant">"hagbhcgd"</span>, <span class="synConstant">"hogeratt"</span>,
<span class="synConstant">"hohohoho"</span>, <span class="synConstant">"fugefuge"</span>, <span class="synConstant">"hokehoke"</span>, <span class="synConstant">"hogehope"</span>, <span class="synConstant">"kogekoge"</span>,
<span class="synConstant">"fugafuga"</span>, <span class="synConstant">"fugafuge"</span>, <span class="synConstant">"fufufufu"</span>, <span class="synConstant">"faggufaa"</span>, <span class="synConstant">"fuuuuuuu"</span>,
<span class="synConstant">"fhunfhun"</span>, <span class="synConstant">"ufagaguf"</span>, <span class="synConstant">"agufaguf"</span>, <span class="synConstant">"fogafoga"</span>, <span class="synConstant">"fafafaoa"</span>]
label = np.array([<span class="synConstant">"hoge"</span>]*<span class="synConstant">10</span> + [<span class="synConstant">"fuga"</span>]*<span class="synConstant">10</span>, dtype=<span class="synIdentifier">object</span>)
<span class="synComment"># 類似度行列の作成(levenshtein距離)</span>
A = np.zeros((<span class="synIdentifier">len</span>(X), <span class="synIdentifier">len</span>(X)))
<span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synIdentifier">len</span>(X)):
<span class="synStatement">for</span> j <span class="synStatement">in</span> <span class="synIdentifier">range</span>(i + <span class="synConstant">1</span>, <span class="synIdentifier">len</span>(X)):
d = Levenshtein.distance(X[i], X[j])
A[i,j] = A[j,i] = d
A = -A <span class="synComment"># 距離->類似度変換のためマイナスをかける</span>
<span class="synComment"># kernel pcaによる変換</span>
kpca = KernelPCA(n_components=<span class="synConstant">2</span>, kernel=<span class="synConstant">"precomputed"</span>)
X_2d = kpca.fit_transform(A)
<span class="synComment"># plot</span>
<span class="synStatement">for</span> (x, y), l <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(X_2d, X):
plt.text(x, y, l)
<span class="synStatement">for</span> l <span class="synStatement">in</span> <span class="synIdentifier">set</span>(label):
plt.scatter(X_2d[label==l, <span class="synConstant">0</span>], X_2d[label==l, <span class="synConstant">1</span>], label=l)
plt.xlim(X_2d[:,<span class="synConstant">0</span>].min() - <span class="synConstant">1</span>, X_2d[:,<span class="synConstant">0</span>].max() + <span class="synConstant">1</span>)
plt.ylim(X_2d[:,<span class="synConstant">1</span>].min() - <span class="synConstant">1</span>, X_2d[:,<span class="synConstant">1</span>].max() + <span class="synConstant">1</span>)
plt.legend()
plt.savefig(<span class="synConstant">"result.png"</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p><figure class="figure-image figure-image-fotolife" title="result.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190708/20190708010823.png" alt="result.png" title="f:id:hayataka2049:20190708010823p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result.png</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/h/hayataka2049/20190628/20190628154814.png" alt="以前の結果(再掲)" title="f:id:hayataka2049:20190628154814p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>以前の結果(再掲)</figcaption></figure></p><p> なぜか色の関係が逆転している気もしますがそれはともかく、基本的な構造はどちらでもつかめていると思います。</p>
</div>
<div class="section">
<h3>違いについて</h3>
<p> カーネルPCAだとtransformを呼べるので、データを追加して元の空間でどの辺に来るのかを予想する、といった用途には便利です。</p><p> たとえば、を追加してみます。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> Levenshtein
<span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> KernelPCA
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
<span class="synComment"># データ(自作)</span>
X = [<span class="synConstant">"hogehoge"</span>, <span class="synConstant">"hogghheg"</span>, <span class="synConstant">"fogefoge"</span>, <span class="synConstant">"hagbhcgd"</span>, <span class="synConstant">"hogeratt"</span>,
<span class="synConstant">"hohohoho"</span>, <span class="synConstant">"fugefuge"</span>, <span class="synConstant">"hokehoke"</span>, <span class="synConstant">"hogehope"</span>, <span class="synConstant">"kogekoge"</span>,
<span class="synConstant">"fugafuga"</span>, <span class="synConstant">"fugafuge"</span>, <span class="synConstant">"fufufufu"</span>, <span class="synConstant">"faggufaa"</span>, <span class="synConstant">"fuuuuuuu"</span>,
<span class="synConstant">"fhunfhun"</span>, <span class="synConstant">"ufagaguf"</span>, <span class="synConstant">"agufaguf"</span>, <span class="synConstant">"fogafoga"</span>, <span class="synConstant">"fafafaoa"</span>]
label = np.array([<span class="synConstant">"hoge"</span>]*<span class="synConstant">10</span> + [<span class="synConstant">"fuga"</span>]*<span class="synConstant">10</span>, dtype=<span class="synIdentifier">object</span>)
<span class="synComment"># 類似度行列の作成(levenshtein距離)</span>
A = np.zeros((<span class="synIdentifier">len</span>(X), <span class="synIdentifier">len</span>(X)))
<span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synIdentifier">len</span>(X)):
<span class="synStatement">for</span> j <span class="synStatement">in</span> <span class="synIdentifier">range</span>(i + <span class="synConstant">1</span>, <span class="synIdentifier">len</span>(X)):
d = Levenshtein.distance(X[i], X[j])
A[i,j] = A[j,i] = d
A = -A <span class="synComment"># 距離->類似度変換のためマイナスをかける</span>
<span class="synComment"># kernel pcaによる変換</span>
kpca = KernelPCA(n_components=<span class="synConstant">2</span>, kernel=<span class="synConstant">"precomputed"</span>)
X_2d = kpca.fit_transform(A)
<span class="synComment"># plot</span>
<span class="synStatement">for</span> (x, y), l <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(X_2d, X):
plt.text(x, y, l)
<span class="synStatement">for</span> l <span class="synStatement">in</span> <span class="synIdentifier">set</span>(label):
plt.scatter(X_2d[label==l, <span class="synConstant">0</span>], X_2d[label==l, <span class="synConstant">1</span>], label=l)
<span class="synComment"># hogefugaを写像(追加)</span>
data = <span class="synConstant">"hogefuga"</span>
A = np.zeros((<span class="synConstant">1</span>, <span class="synIdentifier">len</span>(X)))
<span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synIdentifier">len</span>(X)):
d = Levenshtein.distance(data, X[i])
A[<span class="synConstant">0</span>,i] = d
A = -A <span class="synComment"># 距離->類似度変換のためマイナスをかける</span>
hg_X = kpca.transform(A)
plt.text(hg_X[<span class="synConstant">0</span>,<span class="synConstant">0</span>], hg_X[<span class="synConstant">0</span>,<span class="synConstant">1</span>], data, color=<span class="synConstant">"r"</span>)
<span class="synComment"># 見た目調整</span>
plt.xlim(X_2d[:,<span class="synConstant">0</span>].min() - <span class="synConstant">1</span>, X_2d[:,<span class="synConstant">0</span>].max() + <span class="synConstant">1</span>)
plt.ylim(X_2d[:,<span class="synConstant">1</span>].min() - <span class="synConstant">1</span>, X_2d[:,<span class="synConstant">1</span>].max() + <span class="synConstant">1</span>)
plt.legend()
plt.savefig(<span class="synConstant">"result2.png"</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p><figure class="figure-image figure-image-fotolife" title="result2.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190708/20190708011836.png" alt="result2.png" title="f:id:hayataka2049:20190708011836p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result2.png</figcaption></figure></p><p> このようにhogefugaがどのあたりに位置するのかを、既存の点を再計算せずに写像して見てみることができます。MDSだと全体を再計算しないとできないはずなので、これができることがカーネルPCAの明確なメリットの一つだと思います。</p><p> transformに渡す配列のshapeに関しては若干自信がなかったのですが、逆にしたら(つまり.Tをつけたら)</p>
<blockquote>
<p>ValueError: Precomputed metric requires shape (n_queries, n_indexed). Got (20, 1) for 20 indexed.</p>
</blockquote>
<p> なる見たことも聞いたこともないようなエラーが出たので、たぶんこれで合っています。</p>
</div>
<div class="section">
<h3>まとめ</h3>
<p> まあ同じようなことができるんだなぁと思いました。</p>
</div>
hayataka2049
多次元尺度構成法(MDS)で文字列の編集距離を可視化してみる
hatenablog://entry/17680117127209787468
2019-06-27T23:14:42+09:00
2019-09-01T00:12:09+09:00 はじめに ベクトルとして表現するのが難しいけど、個体間の距離(非類似度)は定義できる……というデータがたまにあります。こういうとき、多次元尺度構成法を使うと可視化がうまくいきます。 ということで、編集距離を可視化してみようと思います。 データ hogehogeとfugafugaを適当に変えた文字列を10個ずつ考えます。こんなのです。 X = ["hogehoge", "hogghheg", "fogefoge", "hagbhcgd", "hogeratt", "hohohoho", "fugefuge", "hokehoke", "hogehope", "kogekoge", "fugafu…
<div class="section">
<h3>はじめに</h3>
<p> ベクトルとして表現するのが難しいけど、個体間の距離(非類似度)は定義できる……というデータがたまにあります。こういうとき、多次元尺度構成法を使うと可視化がうまくいきます。</p><p> ということで、編集距離を可視化してみようと思います。</p>
</div>
<div class="section">
<h3>データ</h3>
<p> hogehogeとfugafugaを適当に変えた文字列を10個ずつ考えます。こんなのです。</p>
<pre class="code lang-python" data-lang="python" data-unlink>X = [<span class="synConstant">"hogehoge"</span>, <span class="synConstant">"hogghheg"</span>, <span class="synConstant">"fogefoge"</span>, <span class="synConstant">"hagbhcgd"</span>, <span class="synConstant">"hogeratt"</span>,
<span class="synConstant">"hohohoho"</span>, <span class="synConstant">"fugefuge"</span>, <span class="synConstant">"hokehoke"</span>, <span class="synConstant">"hogehope"</span>, <span class="synConstant">"kogekoge"</span>,
<span class="synConstant">"fugafuga"</span>, <span class="synConstant">"fugafuge"</span>, <span class="synConstant">"fufufufu"</span>, <span class="synConstant">"faggufaa"</span>, <span class="synConstant">"fuuuuuuu"</span>,
<span class="synConstant">"fhunfhun"</span>, <span class="synConstant">"ufagaguf"</span>, <span class="synConstant">"agufaguf"</span>, <span class="synConstant">"fogafoga"</span>, <span class="synConstant">"fafafaoa"</span>]
label = np.array([<span class="synConstant">"hoge"</span>]*<span class="synConstant">10</span> + [<span class="synConstant">"fuga"</span>]*<span class="synConstant">10</span>, dtype=<span class="synIdentifier">object</span>)
</pre><p> 私のセンスが疑われそうな気もしますが、ご容赦ください。</p>
</div>
<div class="section">
<h3>距離行列の計算</h3>
<p> 距離(非類似度)の行列は自分で計算する必要があります。今回は編集距離なので、Levenshteinパッケージを使います。</p>
<pre class="code lang-python" data-lang="python" data-unlink>A = np.ones((<span class="synIdentifier">len</span>(X), <span class="synIdentifier">len</span>(X)))
<span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synIdentifier">len</span>(X)):
<span class="synStatement">for</span> j <span class="synStatement">in</span> <span class="synIdentifier">range</span>(i + <span class="synConstant">1</span>, <span class="synIdentifier">len</span>(X)):
d = Levenshtein.distance(X[i], X[j])
A[i,j] = A[j,i] = d
</pre><p> これも特に難しいことはありません。</p>
</div>
<div class="section">
<h3>コード全体</h3>
<p> importから可視化のプロットなども含めたコードの全体像がこちらです。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> Levenshtein
<span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synPreProc">from</span> sklearn.manifold <span class="synPreProc">import</span> MDS
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
X = [<span class="synConstant">"hogehoge"</span>, <span class="synConstant">"hogghheg"</span>, <span class="synConstant">"fogefoge"</span>, <span class="synConstant">"hagbhcgd"</span>, <span class="synConstant">"hogeratt"</span>,
<span class="synConstant">"hohohoho"</span>, <span class="synConstant">"fugefuge"</span>, <span class="synConstant">"hokehoke"</span>, <span class="synConstant">"hogehope"</span>, <span class="synConstant">"kogekoge"</span>,
<span class="synConstant">"fugafuga"</span>, <span class="synConstant">"fugafuge"</span>, <span class="synConstant">"fufufufu"</span>, <span class="synConstant">"faggufaa"</span>, <span class="synConstant">"fuuuuuuu"</span>,
<span class="synConstant">"fhunfhun"</span>, <span class="synConstant">"ufagaguf"</span>, <span class="synConstant">"agufaguf"</span>, <span class="synConstant">"fogafoga"</span>, <span class="synConstant">"fafafaoa"</span>]
label = np.array([<span class="synConstant">"hoge"</span>]*<span class="synConstant">10</span> + [<span class="synConstant">"fuga"</span>]*<span class="synConstant">10</span>, dtype=<span class="synIdentifier">object</span>)
A = np.zeros((<span class="synIdentifier">len</span>(X), <span class="synIdentifier">len</span>(X)))
<span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synIdentifier">len</span>(X)):
<span class="synStatement">for</span> j <span class="synStatement">in</span> <span class="synIdentifier">range</span>(i + <span class="synConstant">1</span>, <span class="synIdentifier">len</span>(X)):
d = Levenshtein.distance(X[i], X[j])
A[i,j] = A[j,i] = d
mds = MDS(n_components=<span class="synConstant">2</span>, dissimilarity=<span class="synConstant">"precomputed"</span>)
X_2d = mds.fit_transform(A)
<span class="synStatement">for</span> (x, y), l <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(X_2d, X):
plt.text(x, y, l)
<span class="synStatement">for</span> l <span class="synStatement">in</span> <span class="synIdentifier">set</span>(label):
plt.scatter(X_2d[label==l, <span class="synConstant">0</span>], X_2d[label==l, <span class="synConstant">1</span>], label=l)
plt.xlim(X_2d[:,<span class="synConstant">0</span>].min() - <span class="synConstant">1</span>, X_2d[:,<span class="synConstant">0</span>].max() + <span class="synConstant">1</span>)
plt.ylim(X_2d[:,<span class="synConstant">1</span>].min() - <span class="synConstant">1</span>, X_2d[:,<span class="synConstant">1</span>].max() + <span class="synConstant">1</span>)
plt.legend()
plt.savefig(<span class="synConstant">"result.png"</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p> 可視化では文字列のプロットと、散布図をやっています。これでわかりやすく見えるはずです。</p>
</div>
<div class="section">
<h3>実行結果</h3>
<p> 結果を以下に示します。</p><p><figure class="figure-image figure-image-fotolife" title="result.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190628/20190628154814.png" alt="result.png" title="f:id:hayataka2049:20190628154814p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result.png</figcaption></figure></p><p> 似ているものが近くに来るように配置されることがわかります。だいたいきれいに2グループに分かれています。</p>
</div>
<div class="section">
<h3>まとめ</h3>
<p> このように多次元尺度構成法(MDS)が使えるときがあるので、名前だけは覚えておいても損はしません。こういうものもある、ということを知っておきましょう。</p>
</div>
<div class="section">
<h3>追記</h3>
<p> 書き上げてから「同じことをやっている人がいるかなぁ」と思って「多次元尺度構成法 編集距離」で検索したら、いくつか出てきました。</p><p><a href="https://www.jstage.jst.go.jp/article/iieej/38/5/38_5_634/_pdf/-char/ja">https://www.jstage.jst.go.jp/article/iieej/38/5/38_5_634/_pdf/-char/ja</a><br />
これは画像処理かな。専門外なのでよくわかってはいません。</p><p><a href="http://hoxo-m.hatenablog.com/entry/20120313/p1">主座標分析について簡単に紹介するよ! - ほくそ笑む</a><br />
編集距離も使える、という説明だけ。</p><p><a href="http://db-event.jpn.org/deim2012/proceedings/final-pdf/c10-1.pdf">http://db-event.jpn.org/deim2012/proceedings/final-pdf/c10-1.pdf</a><br />
学会の抄録ですかね。</p><p> そんなに目ぼしいものはないといえばない状況です。今回は単純なデータで実験したのでいい感じの結果になりましたが、実用的にはなかなか厳しいのかもしれません。</p><p> あと、MDSでは距離の公理という面倒くさいものが絡んでくることがあります。これを満たさないものは普通のMDSでは扱えないので、「非計量多次元尺度構成法(nMDS)」で取り扱え、ということが言われていたりします。レーベンシュタイン距離は一応大丈夫らしいですが、(研究やビジネスなどで)ちゃんと使いたい場合はこの辺りにも気を配るべきでしょう。</p>
</div>
<div class="section">
<h3>追記2</h3>
<p> 同じデータに対して、SVMで分類もやってみました。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F07%2F07%2F042622" title="scikit-learnのSVMを自分で計算したカーネルで使う - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/07/07/042622">scikit-learnのSVMを自分で計算したカーネルで使う - 静かなる名辞</a></p><p> カーネルPCAでも見てみました。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F07%2F08%2F012053" title="カーネルPCAで文字列の編集距離を可視化してみる - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/07/08/012053">カーネルPCAで文字列の編集距離を可視化してみる - 静かなる名辞</a></p>
</div>
hayataka2049
sklearnとmatplotlibでiris(3クラス)の予測確率を可視化した話
hatenablog://entry/17680117127203563499
2019-06-22T23:51:33+09:00
2019-09-01T22:24:23+09:00 はじめに よく分類器の性質などを把握するために、2次元で可視化している図があります。 特に予測確率なんかを平面的に出せるとかっこいいですよね。つまり、こういうのです。Classifier comparison — scikit-learn 0.21.3 documentation以前の記事より君はKNN(k nearest neighbor)の本当のすごさを知らない - 静かなる名辞 ただ、これが素直にできるのは2クラス分類までで、3クラス分類だと下のような図にしかなりません。以前の記事より【python】高次元の分離境界をなんとか2次元で見る - 静かなる名辞 ということでずっと諦めていたの…
<div class="section">
<h3>はじめに</h3>
<p> よく分類器の性質などを把握するために、2次元で可視化している図があります。</p><p> 特に予測確率なんかを平面的に出せるとかっこいいですよね。つまり、こういうのです。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fscikit-learn.org%2Fstable%2Fauto_examples%2Fclassification%2Fplot_classifier_comparison.html" title="Classifier comparison — scikit-learn 0.21.3 documentation" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://scikit-learn.org/stable/auto_examples/classification/plot_classifier_comparison.html">Classifier comparison — scikit-learn 0.21.3 documentation</a></p><p><figure class="figure-image figure-image-fotolife" title="以前の記事より"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190526/20190526100917.png" alt="以前の記事より" title="f:id:hayataka2049:20190526100917p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>以前の記事より</figcaption></figure><a href="https://www.haya-programming.com/entry/2019/05/26/103518">君はKNN(k nearest neighbor)の本当のすごさを知らない - 静かなる名辞</a></p><p> ただ、これが素直にできるのは2クラス分類までで、3クラス分類だと下のような図にしかなりません。</p><p><figure class="figure-image figure-image-fotolife" title="以前の記事より"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190524/20190524032324.png" alt="以前の記事より" title="f:id:hayataka2049:20190524032324p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>以前の記事より</figcaption></figure><a href="https://www.haya-programming.com/entry/2019/05/24/040131">【python】高次元の分離境界をなんとか2次元で見る - 静かなる名辞</a></p><p> ということでずっと諦めていたのですが、ふと思いました。</p><p>「RGBに各クラスの予測確率あてればできるじゃん」</p><p> 簡単にできると思ったら思いの外手間取ったので、備忘録として書いておきます。</p>
</div>
<div class="section">
<h3>まずやる</h3>
<p> とりあえずirisを二次元でプロットします。この辺は定石どおりにやるだけです。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
iris = load_iris()
pca = PCA(n_components=<span class="synConstant">2</span>)
X = pca.fit_transform(iris.data)
ax = plt.subplot()
ax.scatter(X[:,<span class="synConstant">0</span>], X[:,<span class="synConstant">1</span>], c=iris.target, cmap=<span class="synConstant">"brg"</span>)
plt.savefig(<span class="synConstant">"fig1.png"</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p><figure class="figure-image figure-image-fotolife" title="fig1.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190620/20190620123052.png" alt="fig1.png" title="f:id:hayataka2049:20190620123052p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>fig1.png</figcaption></figure></p><p> kNNを学習させて、まずは普通に分離境界を描きます。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synPreProc">from</span> sklearn.neighbors <span class="synPreProc">import</span> KNeighborsClassifier
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
iris = load_iris()
pca = PCA(n_components=<span class="synConstant">2</span>)
X = pca.fit_transform(iris.data)
ax = plt.subplot()
ax.scatter(X[:,<span class="synConstant">0</span>], X[:,<span class="synConstant">1</span>], c=iris.target, cmap=<span class="synConstant">"brg"</span>)
clf = KNeighborsClassifier()
clf.fit(X, iris.target)
XX, YY = np.meshgrid(np.arange(-<span class="synConstant">5</span>, <span class="synConstant">5</span>, <span class="synConstant">0.025</span>),
np.arange(-<span class="synConstant">2</span>, <span class="synConstant">2</span>, <span class="synConstant">0.025</span>))
Z = clf.predict(np.stack([XX.ravel(), YY.ravel()], axis=<span class="synConstant">1</span>))
ZZ = Z.reshape(XX.shape)
ax.pcolormesh(XX, YY, ZZ, alpha=<span class="synConstant">0.05</span>, cmap=<span class="synConstant">"brg"</span>, shading=<span class="synConstant">"gouraud"</span>)
plt.savefig(<span class="synConstant">"fig2.png"</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p><br />
参考:<a href="https://www.haya-programming.com/entry/2019/05/24/030924">matplotlibのpcolormeshでalphaを小さくすると網目が出てくる対策 - 静かなる名辞</a></p><p><figure class="figure-image figure-image-fotolife" title="fig2.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190620/20190620123115.png" alt="fig2.png" title="f:id:hayataka2049:20190620123115p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>fig2.png</figcaption></figure></p><p> さ、次はpredict_probaを呼ぶ訳ですが……pcolormeshとかこの辺の関数にはRGBのデータは渡せません。</p><p><a href="https://matplotlib.org/3.1.0/api/_as_gen/matplotlib.pyplot.pcolormesh.html">matplotlib.pyplot.pcolormesh — Matplotlib 3.1.0 documentation</a></p><p> しばし思案したあと、imshowならできると思いました。</p><p> なにも考えずに書くと下のようなコードになります。</p><p><b>(これは動かないので注意してください)</b></p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synPreProc">from</span> sklearn.neighbors <span class="synPreProc">import</span> KNeighborsClassifier
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
iris = load_iris()
pca = PCA(n_components=<span class="synConstant">2</span>)
X = pca.fit_transform(iris.data)
ax = plt.subplot()
ax.scatter(X[:,<span class="synConstant">0</span>], X[:,<span class="synConstant">1</span>], c=iris.target, cmap=<span class="synConstant">"brg"</span>)
clf = KNeighborsClassifier()
clf.fit(X, iris.target)
XX, YY = np.meshgrid(np.arange(-<span class="synConstant">5</span>, <span class="synConstant">5</span>, <span class="synConstant">0.025</span>),
np.arange(-<span class="synConstant">2</span>, <span class="synConstant">2</span>, <span class="synConstant">0.025</span>))
Z = clf.predict_proba(np.stack([XX.ravel(), YY.ravel()], axis=<span class="synConstant">1</span>))
ZZ = Z.reshape(XX.shape + (<span class="synConstant">3</span>, ))
ax.imshow(ZZ, alpha=<span class="synConstant">0.2</span>)
plt.savefig(<span class="synConstant">"fig3.png"</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p><figure class="figure-image figure-image-fotolife" title="fig3.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190620/20190620123140.png" alt="fig3.png" title="f:id:hayataka2049:20190620123140p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>fig3.png</figcaption></figure></p><p> なにこれ?</p><p> ああ、縮尺を合わせないといけないんですね。aspect, extentという引数でできそうです。</p><p><a href="https://matplotlib.org/3.1.0/api/_as_gen/matplotlib.pyplot.imshow.html">matplotlib.pyplot.imshow — Matplotlib 3.1.0 documentation</a></p><p><b>(これもちゃんと動かないので注意してください)</b></p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synPreProc">from</span> sklearn.neighbors <span class="synPreProc">import</span> KNeighborsClassifier
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
iris = load_iris()
pca = PCA(n_components=<span class="synConstant">2</span>)
X = pca.fit_transform(iris.data)
ax = plt.subplot()
ax.set_xlim((-<span class="synConstant">5</span>, <span class="synConstant">5</span>))
ax.set_ylim((-<span class="synConstant">2</span>, <span class="synConstant">2</span>))
ax.scatter(X[:,<span class="synConstant">0</span>], X[:,<span class="synConstant">1</span>], c=iris.target, cmap=<span class="synConstant">"brg"</span>)
clf = KNeighborsClassifier()
clf.fit(X, iris.target)
XX, YY = np.meshgrid(np.arange(-<span class="synConstant">5</span>, <span class="synConstant">5</span>, <span class="synConstant">0.025</span>),
np.arange(-<span class="synConstant">2</span>, <span class="synConstant">2</span>, <span class="synConstant">0.025</span>))
Z = clf.predict_proba(np.stack([XX.ravel(), YY.ravel()], axis=<span class="synConstant">1</span>))
ZZ = Z.reshape(XX.shape + (<span class="synConstant">3</span>, ))
ax.imshow(ZZ, alpha=<span class="synConstant">0.2</span>,
aspect=<span class="synConstant">"auto"</span>, extent=(-<span class="synConstant">5</span>, <span class="synConstant">5</span>, -<span class="synConstant">2</span>, <span class="synConstant">2</span>))
plt.savefig(<span class="synConstant">"fig4.png"</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p> まず、先にax.set_xlimとax.set_ylimで図の範囲を指定し、そこにextentをあわせるようにしています。aspectはドキュメントを見た感じだとautoが無難そうに思います。</p><br />
<p><figure class="figure-image figure-image-fotolife" title="fig4.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190620/20190620123203.png" alt="fig4.png" title="f:id:hayataka2049:20190620123203p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>fig4.png</figcaption></figure></p><br />
<p> どう見ても上下反転しているので、ZZを上下反転します。ついでに、マーカーの色を揃えることにします。</p><p><b>これが動くコードです</b></p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synPreProc">from</span> matplotlib.colors <span class="synPreProc">import</span> ListedColormap
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synPreProc">from</span> sklearn.neighbors <span class="synPreProc">import</span> KNeighborsClassifier
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
iris = load_iris()
pca = PCA(n_components=<span class="synConstant">2</span>)
X = pca.fit_transform(iris.data)
ax = plt.subplot()
ax.set_xlim((-<span class="synConstant">5</span>, <span class="synConstant">5</span>))
ax.set_ylim((-<span class="synConstant">2</span>, <span class="synConstant">2</span>))
cm = ListedColormap([<span class="synConstant">"b"</span>, <span class="synConstant">"g"</span>, <span class="synConstant">"r"</span>])
ax.scatter(X[:,<span class="synConstant">0</span>], X[:,<span class="synConstant">1</span>], c=iris.target, cmap=cm)
clf = KNeighborsClassifier()
clf.fit(X, iris.target)
XX, YY = np.meshgrid(np.arange(-<span class="synConstant">5</span>, <span class="synConstant">5</span>, <span class="synConstant">0.025</span>),
np.arange(-<span class="synConstant">2</span>, <span class="synConstant">2</span>, <span class="synConstant">0.025</span>))
Z = clf.predict_proba(np.stack([XX.ravel(), YY.ravel()], axis=<span class="synConstant">1</span>))
ZZ = np.flip(Z.reshape(XX.shape + (<span class="synConstant">3</span>, )), axis=<span class="synConstant">1</span>)
ax.imshow(ZZ, alpha=<span class="synConstant">0.2</span>,
aspect=<span class="synConstant">"auto"</span>, extent=(-<span class="synConstant">5</span>, <span class="synConstant">5</span>, -<span class="synConstant">2</span>, <span class="synConstant">2</span>))
plt.savefig(<span class="synConstant">"fig5.png"</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p><figure class="figure-image figure-image-fotolife" title="fig5.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190620/20190620123229.png" alt="fig5.png" title="f:id:hayataka2049:20190620123229p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>fig5.png</figcaption></figure></p><p> だいたい不満のない結果になりました。ここまで長かった。</p>
</div>
<div class="section">
<h3>他の分類器も試す</h3>
<p> せっかくなのでいろいろやってみます。SVM, ロジスティック回帰, ランダムフォレストを追加してやってみましょう。<br />
</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synPreProc">from</span> matplotlib.colors <span class="synPreProc">import</span> ListedColormap
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synPreProc">from</span> sklearn.neighbors <span class="synPreProc">import</span> KNeighborsClassifier
<span class="synPreProc">from</span> sklearn.svm <span class="synPreProc">import</span> SVC
<span class="synPreProc">from</span> sklearn.linear_model <span class="synPreProc">import</span> LogisticRegression
<span class="synPreProc">from</span> sklearn.ensemble <span class="synPreProc">import</span> RandomForestClassifier
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
iris = load_iris()
pca = PCA(n_components=<span class="synConstant">2</span>)
X = pca.fit_transform(iris.data)
fig, axes = plt.subplots(nrows=<span class="synConstant">2</span>, ncols=<span class="synConstant">2</span>, figsize=(<span class="synConstant">9</span>, <span class="synConstant">9</span>))
knn = KNeighborsClassifier()
svm = SVC(probability=<span class="synIdentifier">True</span>)
lr = LogisticRegression()
rfc = RandomForestClassifier(n_estimators=<span class="synConstant">100</span>)
cm = ListedColormap([<span class="synConstant">"b"</span>, <span class="synConstant">"g"</span>, <span class="synConstant">"r"</span>])
XX, YY = np.meshgrid(np.arange(-<span class="synConstant">5</span>, <span class="synConstant">5</span>, <span class="synConstant">0.025</span>),
np.arange(-<span class="synConstant">2</span>, <span class="synConstant">2</span>, <span class="synConstant">0.025</span>))
<span class="synStatement">for</span> ax, clf <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(axes.ravel(), [knn, svm, lr, rfc]):
ax.set_xlim((-<span class="synConstant">5</span>, <span class="synConstant">5</span>))
ax.set_ylim((-<span class="synConstant">2</span>, <span class="synConstant">2</span>))
ax.scatter(X[:,<span class="synConstant">0</span>], X[:,<span class="synConstant">1</span>], c=iris.target, cmap=cm)
clf.fit(X, iris.target)
Z = clf.predict_proba(np.stack([XX.ravel(), YY.ravel()], axis=<span class="synConstant">1</span>))
ZZ = np.flip(Z.reshape(XX.shape + (<span class="synConstant">3</span>, )), axis=<span class="synConstant">1</span>)
ax.imshow(ZZ, alpha=<span class="synConstant">0.2</span>,
aspect=<span class="synConstant">"auto"</span>, extent=(-<span class="synConstant">5</span>, <span class="synConstant">5</span>, -<span class="synConstant">2</span>, <span class="synConstant">2</span>))
ax.set_title(clf.__class__.__name__)
plt.tight_layout()
plt.savefig(<span class="synConstant">"fig6.png"</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p><figure class="figure-image figure-image-fotolife" title="fig6.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190620/20190620123251.png" alt="fig6.png" title="f:id:hayataka2049:20190620123251p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>fig6.png</figcaption></figure></p><p> こんなもんか、という感じ。</p>
</div>
<div class="section">
<h3>まとめ</h3>
<p> やればできることはわかりました。もう少しかっこいい図にするには、さらなる工夫が要るのかもしれません。</p>
</div>
hayataka2049
【python】高次元の分離境界をなんとか2次元で見る
hatenablog://entry/17680117127162236111
2019-05-24T04:01:31+09:00
2019-09-01T22:25:09+09:00 はじめに 分類器の特性を把握するために2次元データで分離境界を見るということが行われがちですが、高次元空間における分離器の特性を正確に表している訳ではありません。 ということがずっと気になっていたので、なんとか高次元空間で分類させて2次元で見る方法を考えます。 方法 PCAで2次元に落とせれば、線形変換で逆変換もできるので、それでやります。当然ながら情報は落ちますし、2次元でもなんとか見える程度のデータしか扱えませんが、妥協します。 sklearnならinverse_transformという便利なメソッドがあるので、簡単です。 というあたりまで考えた上で、こんなコードを書きました。show_h…
<div class="section">
<h3>はじめに</h3>
<p> 分類器の特性を把握するために2次元データで分離境界を見るということが行われがちですが、高次元空間における分離器の特性を正確に表している訳ではありません。</p><p> ということがずっと気になっていたので、なんとか高次元空間で分類させて2次元で見る方法を考えます。</p>
</div>
<div class="section">
<h3>方法</h3>
<p> PCAで2次元に落とせれば、線形変換で逆変換もできるので、それでやります。当然ながら情報は落ちますし、2次元でもなんとか見える程度のデータしか扱えませんが、妥協します。</p><p> sklearnならinverse_transformという便利なメソッドがあるので、簡単です。</p><p> というあたりまで考えた上で、こんなコードを書きました。</p><p><b>show_hyperplane.py</b></p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synStatement">def</span> <span class="synIdentifier">show_hyperplane</span>(dataset, clf, filename):
pca = PCA(n_components=<span class="synConstant">2</span>)
X = pca.fit_transform(dataset.data)
plt.scatter(X[:,<span class="synConstant">0</span>], X[:,<span class="synConstant">1</span>], c=dataset.target)
x_min, x_max = X[:, <span class="synConstant">0</span>].min() - <span class="synConstant">1</span>, X[:, <span class="synConstant">0</span>].max() + <span class="synConstant">1</span>
y_min, y_max = X[:, <span class="synConstant">1</span>].min() - <span class="synConstant">1</span>, X[:, <span class="synConstant">1</span>].max() + <span class="synConstant">1</span>
xx, yy = np.meshgrid(np.arange(x_min, x_max, <span class="synConstant">0.01</span>),
np.arange(y_min, y_max, <span class="synConstant">0.01</span>))
clf.fit(dataset.data, dataset.target)
Z = clf.predict(
pca.inverse_transform(np.c_[xx.ravel(), yy.ravel()]))
plt.pcolormesh(xx, yy, Z.reshape(xx.shape),
alpha=<span class="synConstant">0.03</span>, shading=<span class="synConstant">"gouraud"</span>)
plt.savefig(filename)
</pre><p> 汎用的に作ったので、これでいろいろなものを見てみようという算段です。</p>
</div>
<div class="section">
<h3>実験</h3>
<p> まずirisとSVM。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> show_hyperplane <span class="synPreProc">import</span> show_hyperplane
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
<span class="synPreProc">from</span> sklearn.svm <span class="synPreProc">import</span> SVC
iris = load_iris()
svm = SVC(C=<span class="synConstant">50</span>, gamma=<span class="synConstant">"scale"</span>)
show_hyperplane(iris, svm, <span class="synConstant">"iris_svm.png"</span>)
</pre><p><figure class="figure-image figure-image-fotolife" title="iris_svm.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190524/20190524032324.png" alt="iris_svm.png" title="f:id:hayataka2049:20190524032324p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>iris_svm.png</figcaption></figure></p><p> 特に興味深い知見は得られませんでした。</p><p> 次、irisとランダムフォレスト。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> show_hyperplane <span class="synPreProc">import</span> show_hyperplane
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
<span class="synPreProc">from</span> sklearn.ensemble <span class="synPreProc">import</span> RandomForestClassifier
iris = load_iris()
rfc = RandomForestClassifier(n_estimators=<span class="synConstant">500</span>, n_jobs=-<span class="synConstant">1</span>)
show_hyperplane(iris, rfc, <span class="synConstant">"iris_rf.png"</span>)
</pre><p><figure class="figure-image figure-image-fotolife" title="iris_rf.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190524/20190524032403.png" alt="iris_rf.png" title="f:id:hayataka2049:20190524032403p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>iris_rf.png</figcaption></figure></p><p> ランダムフォレストで斜めの分離超平面の図を出したサイトはここくらいしかないのでは? だからどうしたって話ですが。</p><p> 簡単なのでAdaBoostも試します。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> show_hyperplane <span class="synPreProc">import</span> show_hyperplane
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
<span class="synPreProc">from</span> sklearn.tree <span class="synPreProc">import</span> DecisionTreeClassifier
<span class="synPreProc">from</span> sklearn.ensemble <span class="synPreProc">import</span> AdaBoostClassifier
iris = load_iris()
ada = AdaBoostClassifier(
base_estimator=DecisionTreeClassifier(max_depth=<span class="synConstant">4</span>),
n_estimators=<span class="synConstant">200</span>)
show_hyperplane(iris, ada, <span class="synConstant">"iris_ada.png"</span>)
</pre><p><figure class="figure-image figure-image-fotolife" title="iris_ada.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190524/20190524033015.png" alt="iris_ada.png" title="f:id:hayataka2049:20190524033015p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>iris_ada.png</figcaption></figure></p><p> 面白いんですが、性能はいまいち悪そう。</p><p> ちなみに、base_estimatorのパラメータでコロコロ結果が変わります。パラメータ設定については、以下の2記事を参照してください。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F04%2F22%2F183347" title="【python】sklearnのAdaBoostをデフォルトパラメータで使ってはいけない - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/04/22/183347">【python】sklearnのAdaBoostをデフォルトパラメータで使ってはいけない - 静かなる名辞</a><br />
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F04%2F23%2F022038" title="AdaBoostとRandomForestの比較 - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/04/23/022038">AdaBoostとRandomForestの比較 - 静かなる名辞</a></p><p> ただの決定木もやっておきましょう。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> show_hyperplane <span class="synPreProc">import</span> show_hyperplane
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
<span class="synPreProc">from</span> sklearn.tree <span class="synPreProc">import</span> DecisionTreeClassifier
iris = load_iris()
dtc = DecisionTreeClassifier()
show_hyperplane(iris, dtc, <span class="synConstant">"iris_tree.png"</span>)
</pre><p><figure class="figure-image figure-image-fotolife" title="iris_tree.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190524/20190524035713.png" alt="iris_tree.png" title="f:id:hayataka2049:20190524035713p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>iris_tree.png</figcaption></figure></p><p> つまらない。</p><p> さて、irisは飽きてきたのでdigitsで同じことをやります。こちらは何しろ元が64次元で、2次元に落とすとかなり重なり合うので、カオスな結果になってくれそうです。</p><p> が、その前にshow_hyperplane.pyをいじります。元のままだといろいろうまくいかなかったからです。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synStatement">def</span> <span class="synIdentifier">show_hyperplane</span>(dataset, clf, filename):
pca = PCA(n_components=<span class="synConstant">2</span>)
X = pca.fit_transform(dataset.data)
plt.scatter(X[:,<span class="synConstant">0</span>], X[:,<span class="synConstant">1</span>], s=<span class="synConstant">5</span>, c=dataset.target)
x_min, x_max = X[:, <span class="synConstant">0</span>].min() - <span class="synConstant">1</span>, X[:, <span class="synConstant">0</span>].max() + <span class="synConstant">1</span>
y_min, y_max = X[:, <span class="synConstant">1</span>].min() - <span class="synConstant">1</span>, X[:, <span class="synConstant">1</span>].max() + <span class="synConstant">1</span>
xx, yy = np.meshgrid(np.arange(x_min, x_max, <span class="synConstant">0.3</span>),
np.arange(y_min, y_max, <span class="synConstant">0.3</span>))
clf.fit(dataset.data, dataset.target)
Z = clf.predict(
pca.inverse_transform(np.c_[xx.ravel(), yy.ravel()]))
plt.pcolormesh(xx, yy, Z.reshape(xx.shape),
alpha=<span class="synConstant">0.05</span>, shading=<span class="synConstant">"gouraud"</span>)
plt.savefig(filename)
</pre><p> よし、やろう。</p><p> まずSVM。今回からついでに学習データに対するスコアを見ます。コメントで記します。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> show_hyperplane <span class="synPreProc">import</span> show_hyperplane
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_digits
<span class="synPreProc">from</span> sklearn.svm <span class="synPreProc">import</span> SVC
digits = load_digits()
svm = SVC(C=<span class="synConstant">0.1</span>, gamma=<span class="synConstant">"scale"</span>)
score = svm.fit(
digits.data, digits.target).score(
digits.data, digits.target)
<span class="synIdentifier">print</span>(score) <span class="synComment"># => 0.9744017807456873</span>
show_hyperplane(digits, svm, <span class="synConstant">"digits_svm.png"</span>)
</pre><p><figure class="figure-image figure-image-fotolife" title="digits_svm.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190524/20190524034333.png" alt="digits_svm.png" title="f:id:hayataka2049:20190524034333p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>digits_svm.png</figcaption></figure></p><p> あたりまえですが、64→2次元で情報落ちしているので、こんなふうにしか見えません。それでも、後々出てくるやつに比べればまともな方です。</p><p> 次。ランダムフォレスト。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> show_hyperplane <span class="synPreProc">import</span> show_hyperplane
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_digits
<span class="synPreProc">from</span> sklearn.ensemble <span class="synPreProc">import</span> RandomForestClassifier
digits = load_digits()
rfc = RandomForestClassifier(n_estimators=<span class="synConstant">500</span>, n_jobs=-<span class="synConstant">1</span>)
score = rfc.fit(
digits.data, digits.target).score(
digits.data, digits.target)
<span class="synIdentifier">print</span>(score) <span class="synComment"># => 1.0</span>
show_hyperplane(digits, rfc, <span class="synConstant">"digits_rfc.png"</span>)
</pre><p><figure class="figure-image figure-image-fotolife" title="digits_rfc.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190524/20190524034611.png" alt="digits_rfc.png" title="f:id:hayataka2049:20190524034611p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>digits_rfc.png</figcaption></figure></p><p> これ面白いですね。ところどころ凹凸がありますが、それでもぱっと見SVMと同じくらい滑らかな分離超平面に見えます。高次元データほど強いというのもわかる気がします。</p><p> アダブースト。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> show_hyperplane <span class="synPreProc">import</span> show_hyperplane
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_digits
<span class="synPreProc">from</span> sklearn.tree <span class="synPreProc">import</span> DecisionTreeClassifier
<span class="synPreProc">from</span> sklearn.ensemble <span class="synPreProc">import</span> AdaBoostClassifier
digits = load_digits()
ada = AdaBoostClassifier(
base_estimator=DecisionTreeClassifier(max_depth=<span class="synConstant">3</span>),
n_estimators=<span class="synConstant">200</span>)
score = ada.fit(
digits.data, digits.target).score(
digits.data, digits.target)
<span class="synIdentifier">print</span>(score) <span class="synComment"># 0.9660545353366722</span>
show_hyperplane(digits, ada, <span class="synConstant">"digits_ada.png"</span>)
</pre><p><figure class="figure-image figure-image-fotolife" title="digits_ada.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190524/20190524035038.png" alt="digits_ada.png" title="f:id:hayataka2049:20190524035038p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>digits_ada.png</figcaption></figure></p><p> 大丈夫なんかこれ。決定木のアダブーストはランダムフォレストと比べて個人的にいまいち信頼していないのですが、こういうの見るとその思いが強まります。</p><p> 決定木もやりましょう。irisではつまらなかったけど、こちらではどうなるでしょうか。<br />
</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> show_hyperplane <span class="synPreProc">import</span> show_hyperplane
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_digits
<span class="synPreProc">from</span> sklearn.tree <span class="synPreProc">import</span> DecisionTreeClassifier
digits = load_digits()
dtc = DecisionTreeClassifier()
score = dtc.fit(
digits.data, digits.target).score(
digits.data, digits.target)
<span class="synIdentifier">print</span>(score) <span class="synComment"># 1.0</span>
show_hyperplane(digits, dtc, <span class="synConstant">"digits_tree.png"</span>)
</pre><p><figure class="figure-image figure-image-fotolife" title="digits_tree.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190524/20190524035906.png" alt="digits_tree.png" title="f:id:hayataka2049:20190524035906p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>digits_tree.png</figcaption></figure></p><p> あははー、なにこれカオス。デフォルトのまま高次元で使うな、ということですね。</p>
</div>
<div class="section">
<h3>まとめ</h3>
<p> 元が64次元くらいでもだいぶ情報落ちするので、本当の高次元データでも使えるかというと微妙なのですが、それでもなんとなく傾向はつかめますし、面白かったです。</p><p> SVMとランダムフォレストはどっちも優秀ですね。</p>
</div>
hayataka2049
【python】PCAと非負値行列因子分解のバイプロットを見比べる
hatenablog://entry/17680117127130883607
2019-05-14T20:48:35+09:00
2019-09-02T01:39:30+09:00 はじめに 非負値行列因子分解は負の値が出現しないような行列に対して行える分解で、主成分分析とか因子分析に似ています。 参考: 非負値行列因子分解(NMF)をふわっと理解する - Qiita 上の記事によると、いいところとしては、 非負なので現実のデータに向く 非負なので解釈が楽 さらにスパースになる というあたりらしい。 なので、PCAと比べます。sklearnを使います。 比較実験 irisでやります。なんとかの一つ覚えです。 import numpy as np import matplotlib.pyplot as plt from sklearn.decomposition impor…
<div class="section">
<h3>はじめに</h3>
<p> 非負値行列因子分解は負の値が出現しないような行列に対して行える分解で、主成分分析とか因子分析に似ています。</p><p> 参考:<br />
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fnozma%2Fitems%2Fd8dafe4e938c43fb7ad1" title="非負値行列因子分解(NMF)をふわっと理解する - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://qiita.com/nozma/items/d8dafe4e938c43fb7ad1">非負値行列因子分解(NMF)をふわっと理解する - Qiita</a></p><br />
<p> 上の記事によると、いいところとしては、</p>
<ul>
<li>非負なので現実のデータに向く</li>
<li>非負なので解釈が楽</li>
<li>さらにスパースになる</li>
</ul><p> というあたりらしい。</p><p> なので、PCAと比べます。sklearnを使います。</p>
</div>
<div class="section">
<h3>比較実験</h3>
<p> irisでやります。なんとかの一つ覚えです。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA, NMF
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
iris = load_iris()
pca = PCA(n_components=<span class="synConstant">2</span>)
nmf = NMF(n_components=<span class="synConstant">2</span>)
fig, axes = plt.subplots(nrows=<span class="synConstant">1</span>, ncols=<span class="synConstant">2</span>)
<span class="synStatement">for</span> i, mname, method <span class="synStatement">in</span> <span class="synIdentifier">zip</span>([<span class="synConstant">0</span>,<span class="synConstant">1</span>], [<span class="synConstant">"PCA"</span>, <span class="synConstant">"NMF"</span>], [pca, nmf]):
X_2d = method.fit_transform(iris.data)
<span class="synComment"># title</span>
axes[i].set_title(<span class="synConstant">"{} {}"</span>.format(<span class="synConstant">"iris"</span>, mname))
<span class="synComment"># scatter</span>
axes[i].scatter(X_2d[:,<span class="synConstant">0</span>], X_2d[:,<span class="synConstant">1</span>], c=iris.target)
<span class="synComment"># arrows</span>
pc0 = method.components_[<span class="synConstant">0</span>]
pc1 = method.components_[<span class="synConstant">1</span>]
pc0 = pc0 * (np.abs(X_2d[:,<span class="synConstant">0</span>]).max() / np.abs(pc0).max()) * <span class="synConstant">0.8</span>
pc1 = pc1 * (np.abs(X_2d[:,<span class="synConstant">1</span>]).max() / np.abs(pc1).max()) * <span class="synConstant">0.8</span>
<span class="synStatement">for</span> j <span class="synStatement">in</span> <span class="synIdentifier">range</span>(pc0.shape[<span class="synConstant">0</span>]):
axes[i].arrow(
<span class="synConstant">0</span>, <span class="synConstant">0</span>, pc0[j], pc1[j], color=<span class="synConstant">'r'</span>)
axes[i].text(
pc0[j]*<span class="synConstant">1.1</span>, pc1[j]*<span class="synConstant">1.1</span>, iris.feature_names[j], color=<span class="synConstant">'r'</span>)
plt.show()
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p><figure class="figure-image figure-image-fotolife" title="結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190514/20190514204103.png" alt="結果" title="f:id:hayataka2049:20190514204103p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>結果</figcaption></figure></p><p> こうして見るとそんなに違いませんが、原点の右上だけが図に含まれるのが見ての通り特徴です。相違点としては</p>
<ul>
<li>スケールの違いなどがわかるような気がする</li>
<li>PCAでは重なっているpetal width(cm)とpetal length(cm)の違いが出ている</li>
</ul><p> などがあるでしょうか。また、NMFではpetal width(cm)のy方向成分はゼロのようです。</p>
</div>
<div class="section">
<h3>メリット</h3>
<p> 上の図を見る限りでは、別にどっちでも大差はなさそうだし、PCAの方が慣れているので意味的な解釈もしやすい気がします。</p><p> 非負であることが要請されるような特殊ケース以外は別にPCAでもこまらないという気もするのですが、実際のところどうなんでしょうね。</p>
</div>
<div class="section">
<h3>まとめ</h3>
<p> とにかく使えます。</p>
</div>
hayataka2049
【python】sklearnのFeatureAgglomerationを使ってみる
hatenablog://entry/10257846132682806820
2018-12-10T03:56:41+09:00
2019-06-29T20:03:10+09:00 はじめに FeatureAgglomerationは階層的クラスタリングを用いた教師なし次元削減のモデルです。特徴量に対して階層的クラスタリングを行い(つまり通常のサンプルに対するクラスタリングと縦横の向きが入れ替わる)、似ている特徴量同士をマージします。マージの方法はデフォルトでは平均のようです。 使用例をあまり見かけませんが、直感的な次元削減方法なので何かしらの役に立つかもしれないと思って使ってみました。sklearn.cluster.FeatureAgglomeration — scikit-learn 0.20.1 documentation 使い方 パラメータは以下の通り。 clas…
<div class="section">
<h3>はじめに</h3>
<p> FeatureAgglomerationは階層的クラスタリングを用いた教師なし次元削減のモデルです。特徴量に対して階層的クラスタリングを行い(つまり通常のサンプルに対するクラスタリングと縦横の向きが入れ替わる)、似ている特徴量同士をマージします。マージの方法はデフォルトでは平均のようです。</p><p> 使用例をあまり見かけませんが、直感的な次元削減方法なので何かしらの役に立つかもしれないと思って使ってみました。</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.cluster.FeatureAgglomeration.html">sklearn.cluster.FeatureAgglomeration — scikit-learn 0.20.1 documentation</a><br />
</p>
</div>
<div class="section">
<h3>使い方</h3>
<p> パラメータは以下の通り。</p>
<pre class="code" data-lang="" data-unlink>class sklearn.cluster.FeatureAgglomeration(
n_clusters=2, affinity=’euclidean’, memory=None, connectivity=None,
compute_full_tree=’auto’, linkage=’ward’, pooling_func=<function mean>)</pre><p> 色々いじれるように見えますが、主要パラメータは2つだけです。</p>
<ul>
<li>n_clusters</li>
</ul><p> PCAでいうところのn_componentsです。変換先の次元数を表します。</p>
<ul>
<li>pooling_func</li>
</ul><p> 似ている特徴量をマージする方法。callableが渡せます。何もしなければ平均が使われるので、平均より気の利いた方法を思いつく人以外はそのままで大丈夫です。</p><p> あとは階層的クラスタリングのオプションが色々あります。それはそれで大切なものだと思いますが、今回は無視することにします。</p>
</div>
<div class="section">
<h3>実験</h3>
<p> もう何番煎じかわかりませんが、irisの2次元写像で試します。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synPreProc">from</span> sklearn.preprocessing <span class="synPreProc">import</span> StandardScaler
<span class="synPreProc">from</span> sklearn.cluster <span class="synPreProc">import</span> FeatureAgglomeration
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
iris = load_iris()
pca = PCA(n_components=<span class="synConstant">4</span>)
ss = StandardScaler()
agg = FeatureAgglomeration(n_clusters=<span class="synConstant">2</span>)
pca_X = pca.fit_transform(iris.data)
agg_X = agg.fit_transform(
ss.fit_transform(iris.data))
<span class="synIdentifier">print</span>(pca.components_)
<span class="synIdentifier">print</span>(agg.labels_)
fig, axes = plt.subplots(nrows=<span class="synConstant">1</span>, ncols=<span class="synConstant">2</span>)
axes[<span class="synConstant">0</span>].scatter(pca_X[:,<span class="synConstant">0</span>], pca_X[:,<span class="synConstant">1</span>], c=iris.target)
axes[<span class="synConstant">0</span>].set_title(<span class="synConstant">"PCA"</span>)
axes[<span class="synConstant">1</span>].scatter(agg_X[:,<span class="synConstant">0</span>], agg_X[:,<span class="synConstant">1</span>], c=iris.target)
axes[<span class="synConstant">1</span>].set_title(<span class="synConstant">"FeatureAgglomeration</span><span class="synSpecial">\n</span><span class="synConstant">{}"</span>.<span class="synIdentifier">format</span>(agg.labels_))
plt.savefig(<span class="synConstant">"result.png"</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p> 動作原理、目的と用途を考えると、事前にスケーリングしておいた方が恐らく無難です。</p><p> printされた出力。</p>
<pre class="code" data-lang="" data-unlink>[[ 0.36138659 -0.08452251 0.85667061 0.3582892 ]
[ 0.65658877 0.73016143 -0.17337266 -0.07548102]]
[0 1 0 0]</pre><p> FeatureAgglomerationは圧倒的に結果の解釈性が良いことがわかります。写像先の0次元目は元の0,2,3次元目の平均で<a href="#f-0a6fcba6" name="fn-0a6fcba6" title="厳密にはどれか2つが先に平均されて、更に残りと平均されるはず。つまり3つの比重が違う順番はチェックしていないのでわかりませんが、children_属性をちゃんと読み取ればわかると思います">*1</a>、写像先の1次元目は元の1次元目ですね。こういうのはシチュエーション次第ですが、ちょっと嬉しいかもしれません。</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/h/hayataka2049/20181210/20181210033550.png" alt="プロットの結果" title="f:id:hayataka2049:20181210033550p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>プロットの結果</figcaption></figure></p><p> 概ねPCAと同等に使えています。うまく言葉で表現はできませんが、FeatureAgglomerationの方はなんとなくギザギザ感?みたいなものがあります。平均するとそうなる、というのがなんとなくわかる気もするし、わからない気もする。</p>
</div>
<div class="section">
<h3>考察</h3>
<p> 結果の解釈性が良いのと、まがりなりにすべての特徴量の情報が結果に反映されるので、PCAより使いやすいシチュエーションはあると思います。分類前の次元削減とかで使ったときの性能とかは今回検討していませんが、たぶんそんなに良いということはないはず。</p><p> あとドキュメントをあさっていたら、こんなページがあったので、</p><p><a href="https://scikit-learn.org/stable/auto_examples/cluster/plot_digits_agglomeration.html">Feature agglomeration — scikit-learn 0.20.1 documentation</a></p><p> 真似してPCAでも同じものを出してみたら(コードはほとんど書き換えていないので省略。agglo = の行で代入するモデルをコメントアウトで切り替えて、あとlabels_の出力を外しただけです)、やっぱりFeatureAgglomerationはヘボかった(低次元で元の情報を保持することに関しては性能が低かった)です。</p><p> 10次元に落として元の情報をどこまで復元できるかという実験。</p><p><figure class="figure-image figure-image-fotolife" title="PCA"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20181210/20181210035404.png" alt="PCA" title="f:id:hayataka2049:20181210035404p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>PCA</figcaption></figure></p><p><figure class="figure-image figure-image-fotolife" title="FeatureAgglomeration"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20181210/20181210035259.png" alt="FeatureAgglomeration" title="f:id:hayataka2049:20181210035259p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>FeatureAgglomeration</figcaption></figure></p><p> まあ、これは仕方ないか。</p>
</div>
<div class="section">
<h3>まとめ</h3>
<p> とにかく結果の解釈性の良さを活かしたい、とか、なにか特別な理由があって使う分には良いと思います。</p>
</div><div class="footnote">
<p class="footnote"><a href="#fn-0a6fcba6" name="f-0a6fcba6" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">厳密にはどれか2つが先に平均されて、更に残りと平均されるはず。つまり3つの比重が違う順番はチェックしていないのでわかりませんが、children_属性をちゃんと読み取ればわかると思います</span></p>
</div>
hayataka2049
【python】sklearnのSparsePCAを使ってみる
hatenablog://entry/10257846132671526087
2018-11-17T22:30:03+09:00
2019-06-16T20:52:16+09:00 はじめに SparsePCAというものがあることを知ったので、使ってみようと思います。 SparsePCAとは? その名の通り、スパースな主成分分析です。スパースな主成分ベクトルを推定します。Sparse PCA - Wikipedia 原理などは理解しないで、カジュアルに使えるかどうか試してみるだけという趣旨です。なので「どうやって動いているの?」という質問には答えられません。許してください。 sklearnの実装 きっちり存在しています(存在しなかったらこんな記事は書きませんが)。sklearn.decomposition.SparsePCA — scikit-learn 0.20.1 d…
<div class="section">
<h3>はじめに</h3>
<p> SparsePCAというものがあることを知ったので、使ってみようと思います。</p>
</div>
<div class="section">
<h3>SparsePCAとは?</h3>
<p> その名の通り、スパースな主成分分析です。スパースな主成分ベクトルを推定します。</p><p><a href="https://en.wikipedia.org/wiki/Sparse_PCA">Sparse PCA - Wikipedia</a></p><p> 原理などは理解しないで、カジュアルに使えるかどうか試してみるだけという趣旨です。なので「どうやって動いているの?」という質問には答えられません。許してください。</p>
</div>
<div class="section">
<h3>sklearnの実装</h3>
<p> きっちり存在しています(存在しなかったらこんな記事は書きませんが)。</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.SparsePCA.html">sklearn.decomposition.SparsePCA — scikit-learn 0.20.1 documentation</a></p><p> 主要なパラメータとしては、以下のものがあります。</p>
<ul>
<li>n_components</li>
</ul><p> PCAのと同じです。</p>
<ul>
<li>alpha</li>
</ul><p> スパースPCAのキモで、L1正則化の強さを調整できます。</p>
<ul>
<li>ridge_alpha</li>
</ul><p> こちらはtransformの際に使われるリッジ回帰(L2正則化)の正則化パラメータです。なんでリッジを使うのかは、実のところよくわかりません。</p>
<ul>
<li>max_iter</li>
</ul><p> このパラメータがあるということは、最適化とか勾配法的なもので推定するのだな、というくらいに思っておきます。</p>
<ul>
<li>normalize_components</li>
</ul><p> 主成分ベクトルのノルムを1にするかどうか。Trueにしておくと良いと思います。</p><p> 結果に大きな影響を及ぼすのは上くらいだと思います。他のパラメータについてはドキュメントを参照してください。</p>
</div>
<div class="section">
<h3>実験</h3>
<p> 今回はwineデータセットでやってみました。素のPCAでやった場合、alphaを0.5と5にした場合の結果をバイプロットで示します。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_wine
<span class="synPreProc">from</span> sklearn.preprocessing <span class="synPreProc">import</span> StandardScaler
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA, SparsePCA
<span class="synStatement">def</span> <span class="synIdentifier">biplot</span>(X_2d, components, target, ax):
r1 = <span class="synConstant">5</span>
r2 = <span class="synConstant">1.01</span>
<span class="synStatement">for</span> i, coef <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>(components.T):
ax.arrow(<span class="synConstant">0</span>, <span class="synConstant">0</span>, coef[<span class="synConstant">0</span>]*r1, coef[<span class="synConstant">1</span>]*r1, color=<span class="synConstant">'r'</span>)
ax.text(coef[<span class="synConstant">0</span>]*r1*r2, coef[<span class="synConstant">1</span>]*r1*r2, i, color=<span class="synConstant">'b'</span>, fontsize=<span class="synConstant">8</span>)
ax.scatter(X_2d[:,<span class="synConstant">0</span>], X_2d[:,<span class="synConstant">1</span>], c=target, cmap=<span class="synConstant">"rainbow"</span>)
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
wine = load_wine()
ss = StandardScaler()
X = ss.fit_transform(wine.data)
pca = PCA(n_components=<span class="synConstant">2</span>)
spca = SparsePCA(n_components=<span class="synConstant">2</span>,
max_iter=<span class="synConstant">3000</span>,
n_jobs=-<span class="synConstant">1</span>,
normalize_components=<span class="synIdentifier">True</span>)
fig, axes = plt.subplots(figsize=(<span class="synConstant">12</span>, <span class="synConstant">6</span>), nrows=<span class="synConstant">1</span>, ncols=<span class="synConstant">3</span>)
X_2d = pca.fit_transform(X)
biplot(X_2d, pca.components_, wine.target, axes[<span class="synConstant">0</span>])
axes[<span class="synConstant">0</span>].set_title(<span class="synConstant">"PCA"</span>)
<span class="synStatement">for</span> i,alpha <span class="synStatement">in</span> <span class="synIdentifier">zip</span>([<span class="synConstant">1</span>, <span class="synConstant">2</span>], [<span class="synConstant">0.5</span>, <span class="synConstant">5</span>]):
spca.set_params(alpha=alpha)
X_2d = spca.fit_transform(X)
biplot(X_2d, spca.components_, wine.target, axes[i])
axes[i].set_title(<span class="synConstant">"SPCA alpha={:.2f}"</span>.<span class="synIdentifier">format</span>(alpha))
plt.savefig(<span class="synConstant">"result.png"</span>)
<span class="synComment"># 図と突き合わせて確認するために特徴量の名前を出力しておく</span>
<span class="synStatement">for</span> i, name <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>(wine.feature_names):
<span class="synIdentifier">print</span>(i, name)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p> max_iterをきもち高めにしましたが、結果は数秒程度で出ました。</p><p><figure class="figure-image figure-image-fotolife" title="result.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20181117/20181117222322.png" alt="result.png" title="f:id:hayataka2049:20181117222322p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result.png</figcaption></figure></p>
<pre class="code" data-lang="" data-unlink>0 alcohol
1 malic_acid
2 ash
3 alcalinity_of_ash
4 magnesium
5 total_phenols
6 flavanoids
7 nonflavanoid_phenols
8 proanthocyanins
9 color_intensity
10 hue
11 od280/od315_of_diluted_wines
12 proline</pre><p> とりあえず、PCAの結果とSparsePCAの結果で左右が反転しているのに注意。</p><p> あとは見ての通りで、alpha=0.5で一部の係数が主成分にべたっと張り付くようになり、alpha=5では大半の係数が主成分に張り付いています。これがSparsePCAの効果で、結果の解釈が容易になるということらしいです(この次元数だとあまり威力はありませんが、高次元では活躍しそうです)。</p><p> ワインにはあまり詳しくないので、今回は結果を細かく解釈することはしませんが……。</p>
</div>
<div class="section">
<h3>まとめ</h3>
<p> 使えることがわかりました。</p>
</div>
hayataka2049
【python】複数の特徴をまとめるFeatureUnion
hatenablog://entry/17391345971644731424
2018-05-15T14:41:24+09:00
2019-06-16T20:52:16+09:00 単一の入力データから、複数の処理方法で幾つもの異なる特徴量が得られる・・・というシチュエーションがある。 この場合、「どれが最善か」という観点でどれか一つを選ぶこともできるけど、そうすると他の特徴量の情報は捨ててしまうことになる。総合的な性能では他に一歩譲るが、有用な情報が含まれている特徴量がある・・・というような場合は、ちょっと困る。 こういう状況で役に立つのがFeatureUnion。特徴抽出や次元削減などのモデルを複数まとめることができる。 結果はConcatenateされる。Concatenateというのがわかりづらい人もいると思うけど、たとえば手法1で10次元、手法2で20次元の特徴…
<p> 単一の入力データから、複数の処理方法で幾つもの異なる特徴量が得られる・・・というシチュエーションがある。</p><p> この場合、「どれが最善か」という観点でどれか一つを選ぶこともできるけど、そうすると他の特徴量の情報は捨ててしまうことになる。総合的な性能では他に一歩譲るが、有用な情報が含まれている特徴量がある・・・というような場合は、ちょっと困る。</p><p> こういう状況で役に立つのがFeatureUnion。特徴抽出や次元削減などのモデルを複数まとめることができる。</p><p> 結果はConcatenateされる。Concatenateというのがわかりづらい人もいると思うけど、たとえば手法1で10次元、手法2で20次元の特徴量ベクトルが得られたら、これをそのまま横に繋げて30次元のベクトルとして扱うということ。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.pipeline.FeatureUnion.html">sklearn.pipeline.FeatureUnion — scikit-learn 0.20.1 documentation</a></p><p> ちなみに、こいつはsklearn.pipeline以下に存在する。Pipelineの兄弟みたいな扱い。引数の渡し方とかもほとんど同じである。</p><p> 簡単に試してみよう。digitsの分類を行うことにする。PCA+GaussianNB, LDA+GNB, FeatureUnion(PCA, LDA)+GNBの3パターンでスコアを見比べる。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> warnings
warnings.filterwarnings(<span class="synConstant">'ignore'</span>)
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_digits
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synPreProc">from</span> sklearn.discriminant_analysis <span class="synPreProc">import</span> LinearDiscriminantAnalysis <span class="synStatement">as</span> LDA
<span class="synPreProc">from</span> sklearn.naive_bayes <span class="synPreProc">import</span> GaussianNB
<span class="synPreProc">from</span> sklearn.pipeline <span class="synPreProc">import</span> Pipeline, FeatureUnion
<span class="synPreProc">from</span> sklearn.model_selection <span class="synPreProc">import</span> cross_validate, StratifiedKFold
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
digits = load_digits()
pca = PCA(n_components=<span class="synConstant">30</span>)
lda = LDA()
gnb = GaussianNB()
pca_gnb = Pipeline([(<span class="synConstant">"pca"</span>, pca), (<span class="synConstant">"gnb"</span>, gnb)])
lda_gnb = Pipeline([(<span class="synConstant">"lda"</span>, lda), (<span class="synConstant">"gnb"</span>, gnb)])
pca_lda_gnb = Pipeline([(<span class="synConstant">"reduction"</span>, FeatureUnion([(<span class="synConstant">"pca"</span>, pca),
(<span class="synConstant">"lda"</span>, lda)])),
(<span class="synConstant">"gnb"</span>, gnb)])
scoring = {<span class="synConstant">"p"</span>: <span class="synConstant">"precision_macro"</span>,
<span class="synConstant">"r"</span>: <span class="synConstant">"recall_macro"</span>,
<span class="synConstant">"f"</span>:<span class="synConstant">"f1_macro"</span>}
<span class="synStatement">for</span> name, model <span class="synStatement">in</span> <span class="synIdentifier">zip</span>([<span class="synConstant">"pca_gnb"</span>, <span class="synConstant">"lda_gnb"</span>, <span class="synConstant">"pca_lda_gnb"</span>],
[pca_gnb, lda_gnb, pca_lda_gnb]):
skf = StratifiedKFold(shuffle=<span class="synIdentifier">True</span>, random_state=<span class="synConstant">0</span>)
scores = cross_validate(model, digits.data, digits.target,
cv=skf, scoring=scoring)
p = scores[<span class="synConstant">"test_p"</span>].mean()
r = scores[<span class="synConstant">"test_r"</span>].mean()
f = scores[<span class="synConstant">"test_f"</span>].mean()
<span class="synIdentifier">print</span>(name)
<span class="synIdentifier">print</span>(<span class="synConstant">"precision:{0:.3f} recall:{1:.3f} f1:{2:.3f}"</span>.<span class="synIdentifier">format</span>(p,r,f))
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p> 結果は、</p>
<pre class="code" data-lang="" data-unlink>pca_gnb
precision:0.947 recall:0.944 f1:0.945
lda_gnb
precision:0.955 recall:0.953 f1:0.953
pca_lda_gnb
precision:0.959 recall:0.957 f1:0.957</pre><p> ちょっと微妙だけど、誤差ではないみたい。このように比較的手軽に性能を改善できることがわかる(効くかどうかはケースバイケースだけど)。</p>
hayataka2049
【python】SOMのライブラリSomocluはかなりおすすめ
hatenablog://entry/17391345971633041868
2018-04-07T16:12:49+09:00
2019-06-26T00:01:18+09:00 SOM(Self-organizing maps:自己組織化写像)は割と古めの、データの可視化手法です(それ以外にも使えると思いますが)。 今回はpythonのSOMライブラリSomocluを使ってみたら、けっこう良かったというネタです。 目次 SOMの概要 ライブラリがない それでも頑張ってググった 使ってみた 今どきSOMなんか使うの?(蛇足パート) まとめ スポンサーリンク (adsbygoogle = window.adsbygoogle || []).push({}); SOMの概要 昨今は深層学習が流行りですが、SOM、自己組織化写像は敢えて言えば単層学習とでも言うべきでしょうか。…
<p> SOM(Self-organizing maps:自己組織化写像)は割と古めの、データの可視化手法です(それ以外にも使えると思いますが)。</p><p> 今回はpythonのSOMライブラリSomocluを使ってみたら、けっこう良かったというネタです。</p><p> 目次</p>
<ul class="table-of-contents">
<li><a href="#SOMの概要">SOMの概要</a></li>
<li><a href="#ライブラリがない">ライブラリがない</a></li>
<li><a href="#それでも頑張ってググった">それでも頑張ってググった</a></li>
<li><a href="#使ってみた">使ってみた</a></li>
<li><a href="#今どきSOMなんか使うの蛇足パート">今どきSOMなんか使うの?(蛇足パート)</a></li>
<li><a href="#まとめ">まとめ</a></li>
</ul><p><br />
<span style="font-size: 80%">スポンサーリンク</span><br />
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script></p>
<p><ins class="adsbygoogle"
style="display:block"
data-ad-client="ca-pub-6261827798827777"
data-ad-slot="1744230936"
data-ad-format="auto"
data-full-width-responsive="true"></ins><br />
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script></p><p></p>
<div class="section">
<h3 id="SOMの概要">SOMの概要</h3>
<p> 昨今は深層学習が流行りですが、SOM、自己組織化写像は敢えて言えば単層学習とでも言うべきでしょうか。平面上だったり立体状(まあ理屈の上では何次元でも定義できる)に並べたニューロンにデータをマッピングします。それ以上の説明はwikipediaとか、ググれば色々出てくるページを読んでください。</p>
<ul>
<li>wikipedia</li>
</ul><p><a href="https://ja.wikipedia.org/wiki/%E8%87%AA%E5%B7%B1%E7%B5%84%E7%B9%94%E5%8C%96%E5%86%99%E5%83%8F">自己組織化写像 - Wikipedia</a><br />
</p>
<ul>
<li>九州工業大学大学院の先生が書いた読みやすかったページ</li>
</ul><p><a href="http://www.brain.kyutech.ac.jp/~furukawa/data/SOMtext.pdf">http://www.brain.kyutech.ac.jp/~furukawa/data/SOMtext.pdf</a><br />
</p>
<ul>
<li>わかりやすい解説</li>
</ul><p><a href="http://gaya.jp/spiking_neuron/som.htm">子供でもわかる「自己組織化マップ」</a><br />
</p>
</div>
<div class="section">
<h3 id="ライブラリがない">ライブラリがない</h3>
<p> SOM、けっこう面白い性質があるみたいなのて使ってみたいのですが、ググってみるとpythonで使えそうなライブラリがとにかくあまり出てきません。</p>
<ul>
<li>SOMPY</li>
</ul><p> 申し訳ないけど、ちょっと使いづらかった。というかインストールしても挙動が変な感じだった。<br />
<a href="https://github.com/sevamoo/SOMPY">GitHub - sevamoo/SOMPY: A Python Library for Self Organizing Map (SOM)</a><br />
</p>
<ul>
<li>sompy</li>
</ul><p> 日本人の方が実装されたようです。率直に言って「作ってみた」レベルで、実用にはどうかという感じ<br />
<a href="http://www.iandprogram.net/entry/2016/09/20/175441">自己組織化マップ(SOM)のPythonライブラリsompyを公開しました - 俺とプログラミング</a><br />
</p>
<ul>
<li>PyMVPA </li>
</ul><p> 多変量解析のためのそれなりに大きいライブラリで、SOMも実装されている。これが使えればよかったのだと思うが、python2系のサポートしかないので没・・・。<br />
<a href="http://www.pymvpa.org/examples/som.html">Self-organizing Maps — PyMVPA 2.6.1.dev1 documentation</a></p><p> 他にも色々あったのですが、割愛。古い手法なので、敢えて作ろうという人がいないのかな・・・。</p><p> というか、SOMでググると「実装してみた」系の記事はたくさん出てくるのに、まともに使えるライブラリは出てこないというの、かなり異常というか残念というか・・・。</p>
</div>
<div class="section">
<h3 id="それでも頑張ってググった">それでも頑張ってググった</h3>
<p> Somocluというのを見つけました。</p><p><a href="https://somoclu.readthedocs.io/en/stable/">Introduction — Somoclu 1.7.5 documentation</a></p><p> ウリの部分を適当に訳したり訳さなかったりしつつ抜粋</p>
<ul>
<li>OpenMPとCUDAがサポートされていてGPUでも計算できる</li>
<li>当然マルチプラットフォームでLinux, macOS, and Windowsでサポートされている</li>
<li>「Planar and toroid maps」平面とドーナツみたいな形のSOM両方が作れる</li>
<li>「Rectangular and hexagonal grids」四角と六角形がいける</li>
<li>「Gaussian or bubble neighborhood functions」近傍の計算を効率化する系のがある</li>
<li>「Visualization of maps, including those that were trained outside of Python.」</li>
<li>マップの初期化にはPCAが使える</li>
</ul><p> すごく良さそう。あと、pythonに依存しないツールでコマンドラインから直接コマンドで叩けます。pythonバインディングもあるよ、という位置づけ。真剣に開発されてる感じです。</p>
</div>
<div class="section">
<h3 id="使ってみた">使ってみた</h3>
<p> とりあえず使ってみました。SOMの可視化結果でよく見るU-matrixという奴を出します。以下のコードで動きました。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span>
<span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">from</span> somoclu <span class="synPreProc">import</span> Somoclu
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
<span class="synComment"># データを読み込む</span>
dataset = load_iris()
X = dataset.data
y = dataset.target
<span class="synComment"># SOMに入れる前にPCAして計算コスト削減を測る(iris程度では無駄) </span>
pca = PCA(n_components=<span class="synConstant">0.95</span>)
X = pca.fit_transform(X)
<span class="synComment"># SOMの定義</span>
n_rows = <span class="synConstant">16</span>
n_cols = <span class="synConstant">24</span>
som = Somoclu(n_rows=n_rows, n_columns=n_cols,
initialization=<span class="synConstant">"pca"</span>, verbose=<span class="synConstant">2</span>)
<span class="synComment"># 学習</span>
som.train(data=X, epochs=<span class="synConstant">1000</span>)
<span class="synComment"># U-matrixをファイル出力</span>
som.view_umatrix(labels=y, bestmatches=<span class="synIdentifier">True</span>,
filename=<span class="synConstant">"umatrix.png"</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p> 説明不要な感じ。コードも直感的だし、特に不満がないです。</p><p> こんな画像が出てきます。</p><p><figure class="figure-image figure-image-fotolife" title="U-matrix"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180407/20180407152829.png" alt="U-matrix" title="f:id:hayataka2049:20180407152829p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>U-matrix</figcaption></figure></p><p> この画像の見方は色の濃淡が重要で、色の明るい部分は相対的に縮尺が縮んでおり、逆に暗い部分は縮尺が相対的に大きい訳です。PCAで可視化した結果を参考に貼っておきます。</p><p><figure class="figure-image figure-image-fotolife" title="PCAによるirisの可視化結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180330/20180330232127.png" alt="PCAによるirisの可視化結果" title="f:id:hayataka2049:20180330232127p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>PCAによるirisの可視化結果</figcaption></figure></p><p> 紫がラベル0に、緑と黄色が1と2に対応している訳です。SOMを使うと、このようにデータの構造を捉えることができます。</p><p> 使いやすいし動作もまともだし、Somocluは素晴らしいライブラリです。SOMが必要になったら積極的に使っていきたいところ。</p>
</div>
<div class="section">
<h3 id="今どきSOMなんか使うの蛇足パート">今どきSOMなんか使うの?(蛇足パート)</h3>
<p> t-SNEみたいなよくできた手法があるのに今更SOM? と思う方もおられるかと思いますが、SOMはSOMでメリットがあると感じています。</p><p> というのは、t-SNEはけっきょくパラメタに依存するし、ミクロな構造を捉えるのは得意でもマクロな構造はどこまで正しいのか? という問題があるからです。</p><p> 例として、digitsを可視化してみます。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span>
<span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_digits
<span class="synPreProc">from</span> sklearn.manifold <span class="synPreProc">import</span> TSNE
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synPreProc">from</span> somoclu <span class="synPreProc">import</span> Somoclu
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
<span class="synIdentifier">print</span>(<span class="synConstant">"loading data"</span>)
digits = load_digits()
pca = PCA(n_components=<span class="synConstant">0.95</span>)
pca_data = pca.fit_transform(digits.data)
<span class="synComment"># tsneで可視化</span>
<span class="synIdentifier">print</span>(<span class="synConstant">"tsne"</span>)
tsne = TSNE()
X = tsne.fit_transform(pca_data)
fig, ax = plt.subplots()
plt.scatter(X[:,<span class="synConstant">0</span>], X[:,<span class="synConstant">1</span>], c=digits.target/<span class="synConstant">10</span>)
i = <span class="synConstant">0</span>
<span class="synStatement">for</span> xy, l <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(X, digits.target):
<span class="synStatement">if</span> i%<span class="synConstant">8</span> == <span class="synConstant">0</span>: <span class="synComment"># 描画されるtextが多いと汚いので省く</span>
ax.annotate(l, xy=xy)
i += <span class="synConstant">1</span>
plt.savefig(<span class="synConstant">"tsne_digits.png"</span>)
<span class="synComment"># somで可視化</span>
<span class="synIdentifier">print</span>(<span class="synConstant">"som"</span>)
<span class="synComment"># データを適当に省く</span>
sample_index = np.random.choice(X.shape[<span class="synConstant">0</span>], <span class="synConstant">400</span>, replace=<span class="synIdentifier">False</span>)
sample_X = pca_data[sample_index]
sample_y = digits.target[sample_index]
<span class="synComment"># som</span>
som = Somoclu(n_rows=<span class="synConstant">30</span>, n_columns=<span class="synConstant">40</span>,
initialization=<span class="synConstant">"pca"</span>)
som.train(data=sample_X, epochs=<span class="synConstant">1000</span>)
som.view_umatrix(labels=sample_y, bestmatches=<span class="synIdentifier">True</span>,
filename=<span class="synConstant">"som_digits.png"</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p><figure class="figure-image figure-image-fotolife" title="t-SNEで可視化したdigits"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180407/20180407160400.png" alt="t-SNEで可視化したdigits" title="f:id:hayataka2049:20180407160400p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>t-SNEで可視化したdigits</figcaption></figure></p><p><figure class="figure-image figure-image-fotolife" title="SOMで可視化したdigits"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180407/20180407160407.png" alt="SOMで可視化したdigits" title="f:id:hayataka2049:20180407160407p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>SOMで可視化したdigits</figcaption></figure></p><p> 一見するとt-SNEは同じラベルごとにまとまっていて綺麗なんですが、形の似ている数字が近くに来るのはむしろSOMの方という気もします。0の周りに5,6,9が来るというのは(数字の形を考えると)妥当そうですね。主観的になってしまいますが、SOMも捨てたものではないという気がします。</p>
</div>
<div class="section">
<h3 id="まとめ">まとめ</h3>
<p> SOMとSomocluは良いのでみんな使おう。</p>
</div>
hayataka2049
【python】sklearnで因子分析を試す
hatenablog://entry/17391345971630875476
2018-03-31T00:22:11+09:00
2019-06-26T00:01:18+09:00 pythonで因子分析をやる人はあまりいないようだが、sklearnにはしっかりモデルが存在している。ついさっき気づいた。sklearn.decomposition.FactorAnalysis — scikit-learn 0.20.1 documentation 因子分析自体は前からどんなものなのか興味があり、かといってググるとRだったりSPSSだったりばっかり出てきて辟易していたのだが、sklearnにあると都合が良い。さっそく使ってみよう。 目次 とりあえずirisをプロットする とりあえずcomponentsを見る 使えることはわかった とりあえずirisをプロットする 私だけでも何…
<p> pythonで因子分析をやる人はあまりいないようだが、sklearnにはしっかりモデルが存在している。ついさっき気づいた。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.decomposition.FactorAnalysis.html">sklearn.decomposition.FactorAnalysis — scikit-learn 0.20.1 documentation</a></p><p> 因子分析自体は前からどんなものなのか興味があり、かといってググるとRだったりSPSSだったりばっかり出てきて辟易していたのだが、sklearnにあると都合が良い。さっそく使ってみよう。</p><p> 目次</p>
<ul class="table-of-contents">
<li><a href="#とりあえずirisをプロットする">とりあえずirisをプロットする</a></li>
<li><a href="#とりあえずcomponentsを見る">とりあえずcomponentsを見る</a></li>
<li><a href="#使えることはわかった">使えることはわかった</a></li>
</ul>
<div class="section">
<h3 id="とりあえずirisをプロットする">とりあえずirisをプロットする</h3>
<p> 私だけでも何十回もやってきた、世界中では何万回とやられてきたirisの二次元可視化をやってみる。</p><p> 次のようなコードを書いた。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span>
<span class="synPreProc">from</span> copy <span class="synPreProc">import</span> deepcopy
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
<span class="synPreProc">from</span> sklearn.preprocessing <span class="synPreProc">import</span> StandardScaler
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA, FactorAnalysis <span class="synStatement">as</span> FA
<span class="synPreProc">from</span> sklearn.pipeline <span class="synPreProc">import</span> Pipeline
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synStatement">def</span> <span class="synIdentifier">decomp_and_plot</span>(dataset, model, file_name):
X = model.fit_transform(dataset.data)
plt.figure()
plt.scatter(X[:,<span class="synConstant">0</span>], X[:,<span class="synConstant">1</span>], c=dataset.target/<span class="synIdentifier">len</span>(dataset.target_names))
plt.savefig(file_name)
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
iris = load_iris()
ss = StandardScaler()
pca = PCA(n_components=<span class="synConstant">2</span>)
pl = Pipeline([(<span class="synConstant">"scaler"</span>, ss), (<span class="synConstant">"pca"</span>, deepcopy(pca))])
fa = FA(n_components=<span class="synConstant">2</span>, max_iter=<span class="synConstant">5000</span>)
decomp_and_plot(iris, pca, <span class="synConstant">"pca_plt.png"</span>)
decomp_and_plot(iris, pl, <span class="synConstant">"spca_plt.png"</span>)
decomp_and_plot(iris, fa, <span class="synConstant">"fa_plt.png"</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p> PCA、変数をスケーリングしたPCA(相関行列を使うことと等価)、因子分析でそれぞれplotしてみる。</p><p> 結果はこれ。</p><p><figure class="figure-image figure-image-fotolife" title="PCAの結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180330/20180330232127.png" alt="f:id:hayataka2049:20180330232127p:plain" title="f:id:hayataka2049:20180330232127p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>PCAの結果</figcaption></figure></p><p><figure class="figure-image figure-image-fotolife" title="PCA(相関行列)の結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180330/20180330232132.png" alt="f:id:hayataka2049:20180330232132p:plain" title="f:id:hayataka2049:20180330232132p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>PCA(相関行列)の結果</figcaption></figure> 相関行列はぱっと見いまいち(この絵一枚でダメかどうかは判断できないが)。</p><p><figure class="figure-image figure-image-fotolife" title="因子分析の結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180330/20180330232134.png" alt="f:id:hayataka2049:20180330232134p:plain" title="f:id:hayataka2049:20180330232134p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>因子分析の結果</figcaption></figure> うーん、相関行列のとも違うし、なんとも言い難いというか、素人目にはぶっちゃけあんまり良くないように見えるのだが、確率モデルなのでノイズの存在を仮定して見るとこうなるということだろう。</p>
</div>
<div class="section">
<h3 id="とりあえずcomponentsを見る">とりあえずcomponentsを見る</h3>
<p> 次のようなmain2を作り、実行した。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">main2</span>():
iris = load_iris()
<span class="synIdentifier">print</span>(iris.feature_names)
<span class="synIdentifier">print</span>(<span class="synConstant">"pca"</span>)
pca = PCA(n_components=<span class="synConstant">2</span>)
pca.fit(iris.data)
<span class="synIdentifier">print</span>(pca.components_)
<span class="synIdentifier">print</span>(<span class="synConstant">"fa"</span>)
fa = FA(n_components=<span class="synConstant">2</span>, max_iter=<span class="synConstant">5000</span>)
fa.fit(iris.data)
<span class="synIdentifier">print</span>(fa.components_)
</pre><p> 結果</p>
<pre class="code lang-python" data-lang="python" data-unlink>[<span class="synConstant">'sepal length (cm)'</span>, <span class="synConstant">'sepal width (cm)'</span>, <span class="synConstant">'petal length (cm)'</span>, <span class="synConstant">'petal width (cm)'</span>]
pca
[[ <span class="synConstant">0.36158968</span> -<span class="synConstant">0.08226889</span> <span class="synConstant">0.85657211</span> <span class="synConstant">0.35884393</span>]
[ <span class="synConstant">0.65653988</span> <span class="synConstant">0.72971237</span> -<span class="synConstant">0.1757674</span> -<span class="synConstant">0.07470647</span>]]
fa
[[ <span class="synConstant">0.72577591</span> -<span class="synConstant">0.17754023</span> <span class="synConstant">1.75733754</span> <span class="synConstant">0.73196365</span>]
[-<span class="synConstant">0.37036948</span> -<span class="synConstant">0.24060118</span> <span class="synConstant">0.02793388</span> <span class="synConstant">0.04121372</span>]]
</pre><p> プロット結果から予想される通り、両者のcomponentsはよく似通っている。</p><p> これがloadingなのかどうかはぶっちゃけよくわからないのだが(というか1を超えてくる時点でたぶん違うのだろうが)、とりあえずloadingだと思って解釈する。</p><p> 第一因子は花弁の長さと幅、がく片の長さに対応しているので花の大きさに対応しているっぽい。花の大きさとがく片の幅はなぜか若干反比例する。</p><p> 第二因子は花弁に関する係数が小さいので、がく片の大きさを表す因子と言って良さそうである。</p><p> こんなところか。</p>
</div>
<div class="section">
<h3 id="使えることはわかった">使えることはわかった</h3>
<p> だから何? って言われると、正直答えに窮しますが・・・とにかく使えます。主成分分析で良いじゃんと言われたら何も言い返せません。<br />
</p>
</div>
hayataka2049
【python】pythonで主成分分析のバイプロット
hatenablog://entry/17391345971630318038
2018-03-28T23:13:05+09:00
2020-02-04T09:11:46+09:00 バイプロット(Biplot)という主成分分析(PCA)の結果の可視化方法があります。 すごく大雑把に言うと、PCAによる写像の前の空間の各特徴(軸)が写像先の空間のどこに向いているかを可視化する方法です。 具体的には、主成分ベクトル(因子負荷量などを使う場合もあります)と散布図を同じ図にplotします。これらを組み合わせることで、元の空間の性質が二次元(もしかしたら3次元)で手に取るようにわかります*1。 バイプロットはR言語だと簡単に描けるらしいのですが、我らがpythonには(少なくとも一般的なライブラリには)そんな便利なものはありません。ちょっと困るのですが、幸い英語圏にはちらほらやりか…
<p> バイプロット(Biplot)という主成分分析(PCA)の結果の可視化方法があります。</p><p> すごく大雑把に言うと、PCAによる写像の前の空間の各特徴(軸)が写像先の空間のどこに向いているかを可視化する方法です。</p><p> 具体的には、主成分ベクトル(因子負荷量などを使う場合もあります)と散布図を同じ図にplotします。これらを組み合わせることで、元の空間の性質が二次元(もしかしたら3次元)で手に取るようにわかります<a href="#f-d53b0a21" name="fn-d53b0a21" title="本当に手に取るようにわかるかはデータと見る人に依存しますが・・・">*1</a>。</p><p> バイプロットはR言語だと簡単に描けるらしいのですが、我らがpythonには(少なくとも一般的なライブラリには)そんな便利なものはありません。ちょっと困るのですが、幸い英語圏にはちらほらやりかたの情報があります。しかし、それはそれでページごとにやってることが違ったりして、(申し訳ないのですが)微妙に信用できなかったりします。</p><p> で、けっきょく自分で書いてみることにしました。なお、参考にしたのはこの辺です。</p>
<ul>
<li><a href="https://github.com/teddyroland/python-biplot">GitHub - teddyroland/python-biplot: Generates simple biplot using common scientific Python packages</a></li>
<li><a href="https://sukhbinder.wordpress.com/2015/08/05/biplot-with-python/">Biplot with Python | SukhbinderSingh.com</a></li>
<li><a href="http://okomestudio.net/biboroku/?p=2292">http://okomestudio.net/biboroku/?p=2292</a></li>
</ul>
<div class="section">
<h3>方針</h3>
<p> まずsklearnの公式ドキュメントをできるだけ良く読み込みます。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html">sklearn.decomposition.PCA — scikit-learn 0.22.1 documentation</a></p><p> PCA.components_が固有ベクトルであり、データをセンタリングしてこれと掛けるとPCAの出力が出てくることは<a href="https://www.haya-programming.com/entry/2018/03/28/222101">前回の記事</a>で確認しました。</p><p> 固有ベクトル行列が主成分*元のデータの特徴という形になっているとして、横に見ると負荷量(みたいなもの。本当は対応する固有値のsqrtを掛け算してやらないといけない)に、縦に見ると元の写像先で表現された特徴の軸になります。</p><p> つまり、その軸をプロットするだけです。</p><p> なお、この辺は微妙に議論があるようです。私もこれがどこまで正しい方法なのかは自信が持てません。</p><p> 参考:<br />
<a href="https://cis-jp.blogspot.jp/2012/09/blog-post_9.html">色々と考えてみる: 文系のための「主成分分析の可視化」(2)</a></p><p> だけど今回は、データをセンタリングしてPCAを学習させた上で、各軸に対応するone-hot vectorを渡してtransformしたら確かに上に書いた方法通りで上手く行きました(biplotの線の上に載った)。なので、「これで良いんだろう」と勝手に判断しました。どこまで妥当かはよくわからないんですけど。</p>
</div>
<div class="section">
<h3>実装</h3>
<p> こんな感じで書きました。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span>
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris, load_wine
<span class="synPreProc">from</span> sklearn.preprocessing <span class="synPreProc">import</span> StandardScaler
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synStatement">def</span> <span class="synIdentifier">biplot</span>(dataset, scale=<span class="synIdentifier">False</span>, arrow_mul=<span class="synConstant">1</span>, text_mul=<span class="synConstant">1.1</span>):
<span class="synStatement">if</span> scale:
ss = StandardScaler()
X = ss.fit_transform(dataset.data)
<span class="synStatement">else</span>:
X = dataset.data
<span class="synStatement">if</span> <span class="synIdentifier">hasattr</span>(dataset, <span class="synConstant">"feature_names"</span>):
feature_names = <span class="synIdentifier">list</span>(dataset.feature_names)
<span class="synStatement">else</span>:
feature_names = [<span class="synConstant">"F{0}"</span>.format(i)
<span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(dataset.data.shape[<span class="synConstant">1</span>])]
pca = PCA(n_components=<span class="synConstant">2</span>)
X = pca.fit_transform(X)
x_data = X[:,<span class="synConstant">0</span>]
y_data = X[:,<span class="synConstant">1</span>]
pc0 = pca.components_[<span class="synConstant">0</span>]
pc1 = pca.components_[<span class="synConstant">1</span>]
plt.figure()
plt.scatter(x_data, y_data,
c=dataset.target/<span class="synIdentifier">len</span>(<span class="synIdentifier">set</span>(dataset.target)),
marker=<span class="synConstant">"."</span>)
<span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(pc0.shape[<span class="synConstant">0</span>]):
plt.arrow(<span class="synConstant">0</span>, <span class="synConstant">0</span>,
pc0[i]*arrow_mul, pc1[i]*arrow_mul,
color=<span class="synConstant">'r'</span>)
plt.text(pc0[i]*arrow_mul*text_mul,
pc1[i]*arrow_mul*text_mul,
feature_names[i],
color=<span class="synConstant">'r'</span>)
plt.show()
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
iris = load_iris()
wine = load_wine()
biplot(iris, arrow_mul=<span class="synConstant">2.5</span>, scale=<span class="synIdentifier">True</span>)
biplot(wine, arrow_mul=<span class="synConstant">6</span>, scale=<span class="synIdentifier">True</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p> 今回はsklearnのデータセットを渡す形で関数にまとめました。ま、もしこのコードを流用したい人がいたら、必要なロジックだけ上手く切り出してください。</p><p> 結果は、こんな画像が出ます。</p><p><figure class="figure-image figure-image-fotolife" title="irisのバイプロット"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180328/20180328230804.png" alt="irisのバイプロット" title="f:id:hayataka2049:20180328230804p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>irisのバイプロット</figcaption></figure></p><p><figure class="figure-image figure-image-fotolife" title="wineのバイプロット"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180328/20180328230809.png" alt="wineのバイプロット" title="f:id:hayataka2049:20180328230809p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>wineのバイプロット</figcaption></figure></p><p> 上手く行ってる感じです。</p><p> なお、上のコードでは変数をスケーリングしています(相関行列でPCAするのと等価)。スケーリングしなくてもできますが、やった方が矢印の長さが揃いやすいです(逆に変数のスケールを重視してPCAしたいときは、スケーリングしてはいけない。ケースバイケース)。</p>
</div>
<div class="section">
<h3>まとめ</h3>
<p> これくらい自作しなくても済めば良いのにと思いました。</p>
</div><div class="footnote">
<p class="footnote"><a href="#fn-d53b0a21" name="f-d53b0a21" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">本当に手に取るようにわかるかはデータと見る人に依存しますが・・・</span></p>
</div>
hayataka2049
【python】numpyで主成分分析を実装してみた
hatenablog://entry/17391345971630301357
2018-03-28T22:21:01+09:00
2019-06-26T00:01:18+09:00 numpyでPCA(principal component analysis:主成分分析)を実装してみました。自分の理解を深めるためです。 sklearnに実装されているものと同じ結果を出すことを目標にしました。最終的には上手く行きました。 目次 概要 実装 結果 まとめ 概要 主成分分析のアルゴリズムの解説は他に譲ります。これは実装してみた記事です。 実装のやり方は色々あるようですが、一番基本的な(だと思う)共分散行列の固有値と固有ベクトルを求める方法で行きます。 やるべきこととしては、 データをセンタリングする(列ごとに平均を引く) 共分散行列を計算する 固有値と固有ベクトルを計算 データ…
<p> numpyでPCA(principal component analysis:主成分分析)を実装してみました。自分の理解を深めるためです。</p><p> sklearnに実装されているものと同じ結果を出すことを目標にしました。最終的には上手く行きました。</p><p> 目次</p>
<ul class="table-of-contents">
<li><a href="#概要">概要</a></li>
<li><a href="#実装">実装</a></li>
<li><a href="#結果">結果</a></li>
<li><a href="#まとめ">まとめ</a></li>
</ul>
<div class="section">
<h3 id="概要">概要</h3>
<p> 主成分分析のアルゴリズムの解説は他に譲ります。これは実装してみた記事です。</p><p> 実装のやり方は色々あるようですが、一番基本的な(だと思う)共分散行列の固有値と固有ベクトルを求める方法で行きます。</p><p> やるべきこととしては、</p>
<ol>
<li>データをセンタリングする(列ごとに平均を引く)</li>
<li>共分散行列を計算する</li>
<li>固有値と固有ベクトルを計算</li>
<li>データを固有ベクトルを使って写像する</li>
</ol><p> これらを実装すれば行けるはずです。というか、これで行くことにしました。</p>
</div>
<div class="section">
<h3 id="実装">実装</h3>
<p> 書いたソースコードを以下に示します。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span>
<span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synStatement">class</span> <span class="synIdentifier">MyPCA</span>:
<span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, n_components=<span class="synConstant">2</span>):
self.n_components = n_components
<span class="synStatement">def</span> <span class="synIdentifier">fit_transform</span>(self, X):
<span class="synConstant">"""横着してfit_transformしか実装してない</span>
<span class="synConstant"> """</span>
<span class="synComment"># 平均を0にする</span>
X = X - X.mean(axis=<span class="synConstant">0</span>)
<span class="synComment"># 共分散行列を作る</span>
self.cov_ = np.cov(X, rowvar=<span class="synIdentifier">False</span>)
<span class="synComment"># 固有値と固有ベクトルを求めて固有値の大きい順にソート</span>
l, v = np.linalg.eig(self.cov_)
l_index = np.argsort(l)[::-<span class="synConstant">1</span>]
self.l_ = l[l_index]
self.v_ = v[:,l_index] <span class="synComment"># 列ベクトルなのに注意</span>
<span class="synComment"># components_(固有ベクトル行列を途中まで取り出す)を作る</span>
self.components_ = self.v_[:,:self.n_components].T
<span class="synComment"># データとcomponents_をかける</span>
<span class="synComment"># 上と下で二回転置してるのアホ・・・</span>
T = (np.mat(X)*(np.mat(self.components_.T))).A
<span class="synComment"># 出力</span>
<span class="synStatement">return</span> T
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
iris = load_iris()
pca = PCA(n_components=<span class="synConstant">2</span>)
sklearn_X = pca.fit_transform(iris.data)
my_pca = MyPCA()
my_X = my_pca.fit_transform(iris.data)
<span class="synIdentifier">print</span>(pca.explained_variance_)
<span class="synIdentifier">print</span>(my_pca.l_)
<span class="synIdentifier">print</span>(pca.components_)
<span class="synIdentifier">print</span>(my_pca.components_)
plt.figure()
plt.scatter(sklearn_X[:,<span class="synConstant">0</span>], sklearn_X[:,<span class="synConstant">1</span>], c=iris.target/<span class="synConstant">3</span>)
plt.savefig(<span class="synConstant">"sklearn_resut.png"</span>)
plt.figure()
plt.scatter(my_X[:,<span class="synConstant">0</span>], my_X[:,<span class="synConstant">1</span>]*-<span class="synConstant">1</span>, c=iris.target/<span class="synConstant">3</span>)
plt.savefig(<span class="synConstant">"my_result.png"</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p> numpyを使ったので簡単に書けました。アルゴリズム部分はコメントで解説を入れたので、それを読めばどんな感じかは理解して頂けると思います。</p>
</div>
<div class="section">
<h3 id="結果">結果</h3>
<p> mainのテキスト出力を見ると、次のようになっていました。</p>
<pre class="code" data-lang="" data-unlink># 固有値
[4.22484077 0.24224357]
[4.22484077 0.24224357 0.07852391 0.02368303]
# components_
[[ 0.36158968 -0.08226889 0.85657211 0.35884393]
[ 0.65653988 0.72971237 -0.1757674 -0.07470647]]
[[ 0.36158968 -0.08226889 0.85657211 0.35884393]
[-0.65653988 -0.72971237 0.1757674 0.07470647]]</pre><p> 固有値が余計に出ちゃってますが、これは別に構いません。また、componentsの2次元目が符号反転していますが、これも特に問題ないこと(のはず)なので無視します。</p><p> 自作の方は第二主成分を反転させてプロットしてみました。</p><p><figure class="figure-image figure-image-fotolife" title="sklearnのPCAでirisを可視化"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180328/20180328221612.png" alt="sklearnのPCAでirisを可視化" title="f:id:hayataka2049:20180328221612p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>sklearnのPCAでirisを可視化</figcaption></figure></p><p><figure class="figure-image figure-image-fotolife" title="自作PCAでirisを可視化"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180328/20180328221633.png" alt="自作PCAでirisを可視化" title="f:id:hayataka2049:20180328221633p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>自作PCAでirisを可視化</figcaption></figure></p><p> 同じ図を2つ載せるなって怒られそうですが・・・とにかく上手く行ったようです。</p>
</div>
<div class="section">
<h3 id="まとめ">まとめ</h3>
<p> numpyで実装してみたら思ったより簡単だったので、これで当分は「わかった気」になれそうです。</p><p> ただ、今回は特異値分解やらなかったので、それはまた宿題ということで・・・。</p>
</div>
hayataka2049
【python】カーネル主成分分析を試してみる
hatenablog://entry/17391345971630054142
2018-03-28T00:19:12+09:00
2019-07-16T23:33:57+09:00 カーネル主成分分析(Kernel PCA)はカーネル法と主成分分析を組み合わせて用い、データを非線形次元圧縮する方法です(こんな説明で良いのか・・・)。 カーネル法のことは勉強中・・・というか正直勉強しようとしてもよくわからないで跳ね返されるのをこれまで4回くらい繰り返してきたのですが、とりあえず使ってみました。 試してみた 非線形データが手元にあると良いのですが、あいにくありません。輪っか状のデータなどを生成してやってみるのは簡単にできますが、面白くなさそうです。だいたいsklearnの公式サンプルにすらあります。 Kernel PCA — scikit-learn 0.21.2 docum…
<p> カーネル主成分分析(Kernel PCA)はカーネル法と主成分分析を組み合わせて用い、データを非線形次元圧縮する方法です(こんな説明で良いのか・・・)。</p><p> カーネル法のことは勉強中・・・というか正直勉強しようとしてもよくわからないで跳ね返されるのをこれまで4回くらい繰り返してきたのですが、とりあえず使ってみました。</p>
<div class="section">
<h3>試してみた</h3>
<p> 非線形データが手元にあると良いのですが、あいにくありません。輪っか状のデータなどを生成してやってみるのは簡単にできますが、面白くなさそうです。だいたいsklearnの公式サンプルにすらあります。<br />
<a href="http://scikit-learn.org/stable/auto_examples/decomposition/plot_kernel_pca.html">Kernel PCA — scikit-learn 0.21.2 documentation</a></p><p> そこで、分類問題での適用を考えます。これならいつものようにPCA+CLFとKPCA+CLFで比較するだけなので、簡単そうです。更に、カーネルのgammaはグリッドサーチして最適値を探すだけ・・・。</p><p> ただし、irisやdigitsで散々色々試してみましたが、ぶっちゃけ普通にやるとなかなかPCAを上回る性能が得られませんでした。最終的に、「digitsを3次元に次元削減し、LDAで分類する」という問題でどうにかそれなりに性能が上回ることがわかりましたが、実用的な意味はあまりありません。</p><p> たぶん、sklearnのtoy datasetは低次元で線形分離できるタチの良いデータばっかりなのだと思います。それはそれで良いことですが、ちょっとタチの悪いデータも混ぜておいてもらえると嬉しいところです(かといって20newsgroupsのBoWだとタチが悪すぎるし・・・2000データ400次元くらいのちょうど良いデータはどこかにないものか)。</p><p> コードを以下に示します。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span>
<span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_digits
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA, KernelPCA <span class="synStatement">as</span> KPCA
<span class="synPreProc">from</span> sklearn.discriminant_analysis <span class="synPreProc">import</span> LinearDiscriminantAnalysis <span class="synStatement">as</span> LDA
<span class="synPreProc">from</span> sklearn.pipeline <span class="synPreProc">import</span> Pipeline
<span class="synPreProc">from</span> sklearn.model_selection <span class="synPreProc">import</span> GridSearchCV, StratifiedKFold <span class="synStatement">as</span> SKF
<span class="synPreProc">from</span> sklearn.model_selection <span class="synPreProc">import</span> cross_val_score
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
dataset = load_digits()
<span class="synIdentifier">print</span>(dataset.data.shape)
pca = PCA(n_components=<span class="synConstant">3</span>)
kpca = KPCA(kernel=<span class="synConstant">"rbf"</span>, n_components=<span class="synConstant">3</span>)
lda = LDA()
pl_pca = Pipeline([(<span class="synConstant">"pca"</span>, pca), (<span class="synConstant">"lda"</span>, lda)])
pl_kpca = Pipeline([(<span class="synConstant">"kpca"</span>, kpca), (<span class="synConstant">"lda"</span>, lda)])
parameters = {<span class="synConstant">"kpca__gamma"</span> : np.arange(<span class="synConstant">0.00001</span>, <span class="synConstant">0.003</span>, <span class="synConstant">0.0001</span>)}
clf = GridSearchCV(pl_kpca, parameters, verbose=<span class="synConstant">0</span>, n_jobs=-<span class="synConstant">1</span>)
<span class="synIdentifier">print</span>(cross_val_score(pl_pca, dataset.data, dataset.target,
cv=SKF(shuffle=<span class="synIdentifier">True</span>, random_state=<span class="synConstant">0</span>),
scoring=<span class="synConstant">"f1_macro"</span>).mean())
<span class="synIdentifier">print</span>(cross_val_score(clf, dataset.data, dataset.target,
cv=SKF(shuffle=<span class="synIdentifier">True</span>, random_state=<span class="synConstant">0</span>),
scoring=<span class="synConstant">"f1_macro"</span>).mean())
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p> PCAでは0.68らい、KPCAでは0.71くらいのF1値が得られました。</p><p> だから? って言われると、返す言葉は思いつきませんが・・・。</p>
</div>
<div class="section">
<h3>まとめ</h3>
<p> やってみた記事ですが、何かの参考になればと思います。意外と上手く使うのは難しいと感じました。というか分類の次元削減としてはたぶんそんなに適当ではないです。</p><p> どんな問題に応用されてるんだろうか。やっぱり可視化?</p>
</div>
<div class="section">
<h3>追記</h3>
<p> 文字列の編集距離の可視化に使ってみました。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F07%2F08%2F012053" title="カーネルPCAで文字列の編集距離を可視化してみる - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.haya-programming.com/entry/2019/07/08/012053">www.haya-programming.com</a></cite></p><p> 文字列カーネルというのもあるらしいのですが、sklearnで対応していないし、未確認。編集距離を使う分には無難に使えます。</p>
</div>
hayataka2049
【python】sklearnのPCAで相関行列を使う
hatenablog://entry/17391345971629788047
2018-03-27T02:41:44+09:00
2019-06-29T20:03:10+09:00 主成分分析には共分散行列を用いる方法、相関行列を使う方法がある。 sklearnのPCAを見ると、これに対応するオプションは存在しない。sklearn.decomposition.PCA — scikit-learn 0.20.1 documentation ずっと不思議に思っていたが、ググってたらこんなものを見つけた。Enhance: PCA options for using Correlation or covariance matrix · Issue #2689 · scikit-learn/scikit-learn · GitHub 要約:特徴量をスケーリングしてPCAすれば相関行…
<p> 主成分分析には共分散行列を用いる方法、相関行列を使う方法がある。</p><p> sklearnのPCAを見ると、これに対応するオプションは存在しない。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html">sklearn.decomposition.PCA — scikit-learn 0.20.1 documentation</a></p><p> ずっと不思議に思っていたが、ググってたらこんなものを見つけた。</p><p><a href="https://github.com/scikit-learn/scikit-learn/issues/2689">Enhance: PCA options for using Correlation or covariance matrix · Issue #2689 · scikit-learn/scikit-learn · GitHub</a></p><p> 要約:特徴量をスケーリングしてPCAすれば相関行列でやったのと同じことになるよ。PipelineでStandardScalerと組み合わせてね。おわり。</p>
<div class="section">
<h3>本当か確認する</h3>
<p> 確認してみる。</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
>>> <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
>>> <span class="synPreProc">from</span> sklearn.preprocessing <span class="synPreProc">import</span> StandardScaler
>>> <span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
>>> <span class="synPreProc">from</span> sklearn.pipeline <span class="synPreProc">import</span> Pipeline
>>> iris = load_iris()
>>> pca = PCA(n_components=<span class="synConstant">2</span>)
>>> pca.fit(iris.data)
PCA(copy=<span class="synIdentifier">True</span>, iterated_power=<span class="synConstant">'auto'</span>, n_components=<span class="synConstant">2</span>, random_state=<span class="synIdentifier">None</span>,
svd_solver=<span class="synConstant">'auto'</span>, tol=<span class="synConstant">0.0</span>, whiten=<span class="synIdentifier">False</span>)
>>> pca.get_covariance()
array([[ <span class="synConstant">0.67919741</span>, -<span class="synConstant">0.03258618</span>, <span class="synConstant">1.27066452</span>, <span class="synConstant">0.5321852</span> ],
[-<span class="synConstant">0.03258618</span>, <span class="synConstant">0.18113034</span>, -<span class="synConstant">0.31863564</span>, -<span class="synConstant">0.13363564</span>],
[ <span class="synConstant">1.27066452</span>, -<span class="synConstant">0.31863564</span>, <span class="synConstant">3.11934547</span>, <span class="synConstant">1.28541527</span>],
[ <span class="synConstant">0.5321852</span> , -<span class="synConstant">0.13363564</span>, <span class="synConstant">1.28541527</span>, <span class="synConstant">0.58961806</span>]])
>>> ss = StandardScaler()
>>> p = Pipeline([(<span class="synConstant">"scaler"</span>, ss), (<span class="synConstant">"pca"</span>, pca)])
>>> p.fit(iris.data)
Pipeline(memory=<span class="synIdentifier">None</span>,
steps=[(<span class="synConstant">'scaler'</span>, StandardScaler(copy=<span class="synIdentifier">True</span>, with_mean=<span class="synIdentifier">True</span>, with_std=<span class="synIdentifier">True</span>)), (<span class="synConstant">'pca'</span>, PCA(copy=<span class="synIdentifier">True</span>, iterated_power=<span class="synConstant">'auto'</span>, n_components=<span class="synConstant">2</span>, random_state=<span class="synIdentifier">None</span>,
svd_solver=<span class="synConstant">'auto'</span>, tol=<span class="synConstant">0.0</span>, whiten=<span class="synIdentifier">False</span>))])
>>> p.steps[<span class="synConstant">1</span>][<span class="synConstant">1</span>].get_covariance()
array([[ <span class="synConstant">0.9779242</span> , -<span class="synConstant">0.10104477</span>, <span class="synConstant">0.87069468</span>, <span class="synConstant">0.86134879</span>],
[-<span class="synConstant">0.10104477</span>, <span class="synConstant">1.00395722</span>, -<span class="synConstant">0.41916911</span>, -<span class="synConstant">0.37286994</span>],
[ <span class="synConstant">0.87069468</span>, -<span class="synConstant">0.41916911</span>, <span class="synConstant">1.04639367</span>, <span class="synConstant">0.93676197</span>],
[ <span class="synConstant">0.86134879</span>, -<span class="synConstant">0.37286994</span>, <span class="synConstant">0.93676197</span>, <span class="synConstant">0.99857055</span>]])
>>> np.corrcoef(iris.data, rowvar=<span class="synIdentifier">False</span>)
array([[ <span class="synConstant">1.</span> , -<span class="synConstant">0.10936925</span>, <span class="synConstant">0.87175416</span>, <span class="synConstant">0.81795363</span>],
[-<span class="synConstant">0.10936925</span>, <span class="synConstant">1.</span> , -<span class="synConstant">0.4205161</span> , -<span class="synConstant">0.35654409</span>],
[ <span class="synConstant">0.87175416</span>, -<span class="synConstant">0.4205161</span> , <span class="synConstant">1.</span> , <span class="synConstant">0.9627571</span> ],
[ <span class="synConstant">0.81795363</span>, -<span class="synConstant">0.35654409</span>, <span class="synConstant">0.9627571</span> , <span class="synConstant">1.</span> ]])
</pre><p> 違うじゃん。妥当そうなのはnumpyの結果だが(対角成分が1になってる)、とりあえずしょうがないのでスケーリングしたデータの共分散をnumpyで計算してみる。</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> np.cov(ss.fit_transform(iris.data), rowvar=<span class="synConstant">0</span>, bias=<span class="synConstant">1</span>)
array([[ <span class="synConstant">1.00671141</span>, -<span class="synConstant">0.11010327</span>, <span class="synConstant">0.87760486</span>, <span class="synConstant">0.82344326</span>],
[-<span class="synConstant">0.11010327</span>, <span class="synConstant">1.00671141</span>, -<span class="synConstant">0.42333835</span>, -<span class="synConstant">0.358937</span> ],
[ <span class="synConstant">0.87760486</span>, -<span class="synConstant">0.42333835</span>, <span class="synConstant">1.00671141</span>, <span class="synConstant">0.96921855</span>],
[ <span class="synConstant">0.82344326</span>, -<span class="synConstant">0.358937</span> , <span class="synConstant">0.96921855</span>, <span class="synConstant">1.00671141</span>]])
>>> np.cov(ss.fit_transform(iris.data), rowvar=<span class="synConstant">0</span>, bias=<span class="synConstant">1</span>)
array([[ <span class="synConstant">1.</span> , -<span class="synConstant">0.10936925</span>, <span class="synConstant">0.87175416</span>, <span class="synConstant">0.81795363</span>],
[-<span class="synConstant">0.10936925</span>, <span class="synConstant">1.</span> , -<span class="synConstant">0.4205161</span> , -<span class="synConstant">0.35654409</span>],
[ <span class="synConstant">0.87175416</span>, -<span class="synConstant">0.4205161</span> , <span class="synConstant">1.</span> , <span class="synConstant">0.9627571</span> ],
[ <span class="synConstant">0.81795363</span>, -<span class="synConstant">0.35654409</span>, <span class="synConstant">0.9627571</span> , <span class="synConstant">1.</span> ]])
</pre><p> 標本分散はnp.corrcoefと等価だ。</p><p> ここまでやったところでもう一回ドキュメントを読み、PCA.get_covariance()の結果が「Estimated covariance of data.」であり、厳密ではないことに気づいたので、問題は解決した。</p><p> 理論的にこうなる理由は、説明しようと思えばできるのだと思いますが、今回は大変なので触れません。</p>
</div>
<div class="section">
<h3>irisでやってみる</h3>
<p> irisの可視化にそれぞれを使ってみる。コードを以下に示す。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span>
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris
<span class="synPreProc">from</span> sklearn.preprocessing <span class="synPreProc">import</span> StandardScaler
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synPreProc">from</span> sklearn.pipeline <span class="synPreProc">import</span> Pipeline
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
iris = load_iris()
ss = StandardScaler()
pca = PCA(n_components=<span class="synConstant">2</span>)
p = Pipeline([(<span class="synConstant">"scaler"</span>, ss), (<span class="synConstant">"pca"</span>, pca)])
X = pca.fit_transform(iris.data)
plt.figure()
plt.scatter(X[:,<span class="synConstant">0</span>], X[:,<span class="synConstant">1</span>], c=iris.target/<span class="synConstant">3</span>)
plt.savefig(<span class="synConstant">"iris_cov_pca.png"</span>)
X = p.fit_transform(iris.data)
plt.figure()
plt.scatter(X[:,<span class="synConstant">0</span>], X[:,<span class="synConstant">1</span>], c=iris.target/<span class="synConstant">3</span>)
plt.savefig(<span class="synConstant">"iris_corr_pca.png"</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p> 結果は、<figure class="figure-image figure-image-fotolife" title="共分散行列で主成分分析したiris"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180327/20180327023453.png" alt="共分散行列で主成分分析したiris" title="f:id:hayataka2049:20180327023453p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>共分散行列で主成分分析したiris</figcaption></figure></p><p><figure class="figure-image figure-image-fotolife" title="相関行列で主成分分析したiris"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180327/20180327023502.png" alt="相関行列で主成分分析したiris" title="f:id:hayataka2049:20180327023502p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>相関行列で主成分分析したiris</figcaption></figure></p><p> こうして見ると相関行列はあまりメリットがないように見えますが、実際には相関行列の方が良いタスクは色々あるようです。相関行列を使うことでbiplotが上手く行っているという例を出しているページを載せておきます。<br />
<a href="https://stats.stackexchange.com/questions/53/pca-on-correlation-or-covariance">PCA on correlation or covariance? - Cross Validated</a><br />
</p>
</div>
<div class="section">
<h3>まとめ</h3>
<p> とりあえずできることはわかったので良しとする。</p><p> でも、「pipelineで出来るから要らねーよ」ってつもりらしいけど、ぶっちゃけオプション一つでできた方が親切だと思った(小並感)。</p>
</div>
hayataka2049
【python】sklearnのfetch_20newsgroupsで文書分類を試す(4)
hatenablog://entry/17391345971629687490
2018-03-26T21:21:12+09:00
2019-06-17T20:44:43+09:00 前回は性能を追い求めると次元がでかくなりすぎて・・・というところで終わっていた。今回はもうちょっと頑張って次元を減らしてみる。 目次 ストップワードの除去 PCA(主成分分析)とLDA(線形判別分析) 分類 ソースコード 結果とまとめ 次回 過去の回 ストップワードの除去 とりあえずstop_wordsを指定していなかったので、指定してみる。 stop_words="english"とすると、ストップワードを除去してくれる。 結果だけ言うと、min_df=0.005のとき、 stop_words指定なし:3949次元 stop_words指定あり:3705次元 だった。焼石に水。 PCA(主成…
<p> <a href="https://www.haya-programming.com/entry/2018/02/22/060148">前回</a>は性能を追い求めると次元がでかくなりすぎて・・・というところで終わっていた。今回はもうちょっと頑張って次元を減らしてみる。</p><p> 目次</p>
<ul class="table-of-contents">
<li><a href="#ストップワードの除去">ストップワードの除去</a></li>
<li><a href="#PCA主成分分析とLDA線形判別分析">PCA(主成分分析)とLDA(線形判別分析)</a></li>
<li><a href="#分類">分類</a></li>
<li><a href="#ソースコード">ソースコード</a></li>
<li><a href="#結果とまとめ">結果とまとめ</a></li>
<li><a href="#次回">次回</a></li>
<li><a href="#過去の回">過去の回</a></li>
</ul>
<div class="section">
<h3 id="ストップワードの除去">ストップワードの除去</h3>
<p> とりあえずstop_wordsを指定していなかったので、指定してみる。</p><p> stop_words="english"とすると、ストップワードを除去してくれる。</p><p> 結果だけ言うと、min_df=0.005のとき、</p>
<ul>
<li>stop_words指定なし:3949次元</li>
<li>stop_words指定あり:3705次元</li>
</ul><p> だった。焼石に水。</p>
</div>
<div class="section">
<h3 id="PCA主成分分析とLDA線形判別分析">PCA(主成分分析)とLDA(線形判別分析)</h3>
<p> PCAとLDAをかけ、次元削減をする。leakage怖いのでPipelineを使う(厳密なことを言い出すと、単語文書行列を作る段からPipelineに入れるべきなのだろうか? きついのでパスさせて頂くが)。</p><p> PCAは主にLDAの計算負荷削減と、変数の相関を除去することを意図してかける。1000次元まで落としてみたが、これでも累積寄与率は90%弱になる。まあ、正規化も何もしてないから、重要な情報を落としている可能性は否定できないのだが。</p><p> LDAは次元削減に使う。有効性についてはこの前試してみたので、この記事を読んで欲しい。<br />
<a href="https://www.haya-programming.com/entry/2018/03/20/164352">【python】LDA(線形判別分析)で次元削減 - 静かなる名辞</a><br />
20newsgroupsは20クラスのデータなので、19次元に落とすことになる。相当早くなるだろうが、どこまで性能を維持できるかはデータの線形性にかかっている。</p>
</div>
<div class="section">
<h3 id="分類">分類</h3>
<p> ランダムフォレストを使った。n_estimators=1000とし、他のパラメタはデフォルト。</p>
</div>
<div class="section">
<h3 id="ソースコード">ソースコード</h3>
<p> 実験に使ったソースコードを以下に示す。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span>
<span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> fetch_20newsgroups
<span class="synPreProc">from</span> sklearn.ensemble <span class="synPreProc">import</span> RandomForestClassifier <span class="synStatement">as</span> RFC
<span class="synPreProc">from</span> sklearn.feature_extraction.text <span class="synPreProc">import</span> CountVectorizer <span class="synStatement">as</span> CV
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synPreProc">from</span> sklearn.discriminant_analysis <span class="synPreProc">import</span> LinearDiscriminantAnalysis <span class="synStatement">as</span> LDA
<span class="synPreProc">from</span> sklearn.model_selection <span class="synPreProc">import</span> StratifiedKFold
<span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> precision_recall_fscore_support <span class="synStatement">as</span> prf
<span class="synPreProc">from</span> sklearn.pipeline <span class="synPreProc">import</span> Pipeline
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
news20 = fetch_20newsgroups()
cv = CV(min_df=<span class="synConstant">0.005</span>, max_df=<span class="synConstant">0.5</span>, stop_words=<span class="synConstant">"english"</span>)
matrix = cv.fit_transform(news20.data).toarray()
pca = PCA(n_components=<span class="synConstant">1000</span>, svd_solver=<span class="synConstant">"randomized"</span>)
lda = LDA()
rfc = RFC(n_estimators=<span class="synConstant">1000</span>, n_jobs=-<span class="synConstant">1</span>)
clf = Pipeline([(<span class="synConstant">"pca"</span>, pca), (<span class="synConstant">"lda"</span>, lda), (<span class="synConstant">"rfc"</span>, rfc)])
trues = []
preds = []
<span class="synStatement">for</span> train_index, test_index <span class="synStatement">in</span> StratifiedKFold().split(matrix, news20.target):
clf.fit(matrix[train_index], news20.target[train_index])
trues.append(news20.target[test_index])
preds.append(clf.predict(matrix[test_index]))
scores = prf(np.hstack(trues), np.hstack(preds), average=<span class="synConstant">"macro"</span>)[:<span class="synConstant">3</span>]
<span class="synIdentifier">print</span>(<span class="synConstant">"p:{0:.6f} r:{1:.6f} f1:{2:.6f}"</span>.<span class="synIdentifier">format</span>(scores[<span class="synConstant">0</span>],
scores[<span class="synConstant">1</span>],
scores[<span class="synConstant">2</span>]))
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre>
</div>
<div class="section">
<h3 id="結果とまとめ">結果とまとめ</h3>
<pre class="code" data-lang="" data-unlink>p:0.764012 r:0.760731 f1:0.761510</pre><p> 前回の0.8を超えるスコアには届かなかったが、とりあえずそれなりに軽くはなった。もうちょっと真面目に追い込めばという話はあるが、追求しない。次回はもうちょっと違うことをやってみたい。</p>
</div>
<div class="section">
<h3 id="次回">次回</h3>
<p> このシリーズずっと放置していましたが、気が向いたので書きました。<br />
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F05%2F15%2F232429" title="【python】sklearnのfetch_20newsgroupsで文書分類を試す(5) - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.haya-programming.com/entry/2019/05/15/232429">www.haya-programming.com</a></cite></p><p></p>
</div>
<div class="section">
<h3 id="過去の回">過去の回</h3>
<p><a href="https://www.haya-programming.com/entry/2018/02/19/200006">【python】sklearnのfetch_20newsgroupsで文書分類を試す(1) - 静かなる名辞</a><br />
<a href="https://www.haya-programming.com/entry/2018/02/20/215416">【python】sklearnのfetch_20newsgroupsで文書分類を試す(2) - 静かなる名辞</a><br />
<a href="https://www.haya-programming.com/entry/2018/02/22/060148">【python】sklearnのfetch_20newsgroupsで文書分類を試す(3) - 静かなる名辞</a></p>
</div>
hayataka2049
【python】LDA(線形判別分析)で次元削減
hatenablog://entry/17391345971627552434
2018-03-20T16:43:52+09:00
2019-06-16T20:52:16+09:00 一般によく使われる次元削減手法としてはPCA(主成分分析)がありますが、他にLDA(Linear Discriminant Analysis:線形判別分析)を使う方法もあります。 これは本来は分類に使われる判別分析という古典的なアルゴリズムで、データが一番分離しやすくなる軸を求めていくものです。つまり教師ラベルを使います。教師ラベルを使うので、PCAのような教師なしの手法と比べて有利な可能性があります。 線形判別分析の詳しい原理の説明などが欲しい方は、ググって出てくるwikipediaやqiitaなどを参考にしてください(投げやり)。この記事では、分類問題でこれを使ったとき、どのようなご利益が…
<p> <br />
一般によく使われる次元削減手法としてはPCA(主成分分析)がありますが、他にLDA(Linear Discriminant Analysis:線形判別分析)を使う方法もあります。</p><p> これは本来は分類に使われる判別分析という古典的なアルゴリズムで、データが一番分離しやすくなる軸を求めていくものです。つまり教師ラベルを使います。教師ラベルを使うので、PCAのような教師なしの手法と比べて有利な可能性があります。</p><p> 線形判別分析の詳しい原理の説明などが欲しい方は、ググって出てくるwikipediaやqiitaなどを参考にしてください(投げやり)。この記事では、分類問題でこれを使ったとき、どのようなご利益があるのかを検証します。</p>
<div class="section">
<h3>実験</h3>
<p> sklearnのdigitsデータセットを使い、次元削減→分類というタスクを行って交差検証でスコアを出します。</p><p> 分類器は最初はSVMでやろうかと思ったけど、パラメタチューニングで幾らでも恣意的な結果になることに気づいたのでガウシアン・ナイーブベイズでやることにしました。</p><p> 実験に使ったコードは以下に示します。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span>
<span class="synPreProc">import</span> warnings
warnings.filterwarnings(<span class="synConstant">'ignore'</span>)
<span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np
<span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd
<span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_digits
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synPreProc">from</span> sklearn.discriminant_analysis <span class="synPreProc">import</span> LinearDiscriminantAnalysis <span class="synStatement">as</span> LDA
<span class="synPreProc">from</span> sklearn.naive_bayes <span class="synPreProc">import</span> GaussianNB <span class="synStatement">as</span> GNB
<span class="synPreProc">from</span> sklearn.pipeline <span class="synPreProc">import</span> Pipeline
<span class="synPreProc">from</span> sklearn.model_selection <span class="synPreProc">import</span> StratifiedKFold <span class="synStatement">as</span> SKF
<span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> precision_recall_fscore_support <span class="synStatement">as</span> prf
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
digits = load_digits()
gnb = GNB()
df = pd.DataFrame([], columns=[
<span class="synConstant">"n_components"</span>,
<span class="synConstant">"pca-gnn precision"</span>, <span class="synConstant">"pca-gnn recall"</span>, <span class="synConstant">"pca-gnn f1"</span>,
<span class="synConstant">"lda-gnn precision"</span>, <span class="synConstant">"lda-gnn recall"</span>, <span class="synConstant">"lda-gnn f1"</span>])
<span class="synStatement">for</span> n_components <span class="synStatement">in</span> [<span class="synConstant">5</span>, <span class="synConstant">10</span>, <span class="synConstant">15</span>, <span class="synConstant">20</span>, <span class="synConstant">25</span>, <span class="synConstant">30</span>, <span class="synConstant">40</span>]:
pca = PCA(n_components=n_components)
lda = LDA(n_components=n_components)
steps1 = <span class="synIdentifier">list</span>(<span class="synIdentifier">zip</span>([<span class="synConstant">"pca"</span>, <span class="synConstant">"gnb"</span>], [pca, gnb]))
steps2 = <span class="synIdentifier">list</span>(<span class="synIdentifier">zip</span>([<span class="synConstant">"lda"</span>, <span class="synConstant">"gnb"</span>], [lda, gnb]))
p1 = Pipeline(steps1)
p2 = Pipeline(steps2)
score_lst = []
<span class="synStatement">for</span> decomp_name, clf <span class="synStatement">in</span> <span class="synIdentifier">zip</span>([<span class="synConstant">"pca"</span>, <span class="synConstant">"lda"</span>], [p1, p2]):
trues = []
preds = []
<span class="synStatement">for</span> train_index, test_index <span class="synStatement">in</span> SKF(
shuffle=<span class="synIdentifier">True</span>, random_state=<span class="synConstant">0</span>).split(
digits.data, digits.target):
clf.fit(digits.data[train_index],
digits.target[train_index])
trues.append(digits.target[test_index])
preds.append(clf.predict(digits.data[test_index]))
scores = prf(np.hstack(trues), np.hstack(preds), average=<span class="synConstant">"macro"</span>)
score_lst.extend(scores[:-<span class="synConstant">1</span>])
df = df.append(pd.Series([n_components, *score_lst],
index=df.columns),
ignore_index=<span class="synIdentifier">True</span>)
<span class="synIdentifier">print</span>(df)
plt.figure()
df.plot(x=<span class="synConstant">"n_components"</span>, y=[<span class="synConstant">"pca-gnn f1"</span>, <span class="synConstant">"lda-gnn f1"</span>])
plt.savefig(<span class="synConstant">"result.png"</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre>
</div>
<div class="section">
<h3>結果</h3>
<p> 次のようになりました。</p><p> テキスト出力</p>
<pre class="code" data-lang="" data-unlink> n_components pca-gnn precision pca-gnn recall pca-gnn f1 \
0 5.0 0.847918 0.841684 0.841109
1 10.0 0.915834 0.911346 0.912563
2 15.0 0.926992 0.923032 0.924061
3 20.0 0.934522 0.930192 0.931194
4 25.0 0.941886 0.938611 0.939205
5 30.0 0.946139 0.944251 0.944669
6 40.0 0.945330 0.943644 0.943960
lda-gnn precision lda-gnn recall lda-gnn f1
0 0.917464 0.917144 0.917031
1 0.953751 0.952588 0.952950
2 0.953751 0.952588 0.952950
3 0.953751 0.952588 0.952950
4 0.953751 0.952588 0.952950
5 0.953751 0.952588 0.952950
6 0.953751 0.952588 0.952950 </pre><p><figure class="figure-image figure-image-fotolife" title="結果(n_components対F1値)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180320/20180320163706.png" alt="結果(n_components対F1値)" title="f:id:hayataka2049:20180320163706p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>結果(n_components対F1値)</figcaption></figure> </p><p> LDAを使った方が低い次元で、より高い分類性能が得られているようです。</p>
</div>
<div class="section">
<h3>まとめ</h3>
<p> LDAは良い。</p>
</div>
<div class="section">
<h3>おまけ</h3>
<p> ソースコードをちゃんと読んだ方は、最初に書かれた以下の記述に気づいたかと思います。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> warnings
warnings.filterwarnings(<span class="synConstant">'ignore'</span>)
</pre><p> これを付けないとLDAはけっこうな警告(主に以下の2つ)を吐いてくれます。</p>
<pre class="code" data-lang="" data-unlink>UserWarning: Variables are collinear
UserWarning: The priors do not sum to 1. Renormalizing</pre><p> 上の警告はPCAで説明変数の多重共線性を除去してやると消えます(本末転倒っぽいけど)。下の警告は、正直調べてもよくわかりませんでした。</p><p> とりあえず、警告が出てもちゃんと動いてるみたいなので別に良いか・・・。</p>
</div>
<div class="section">
<h3>追記</h3>
<p> LDAのn_componentsには上限があり、クラス数-1以上のn_componentsは指定しても無意味です。</p><p> 実際にやってみても、クラス数-1以上にはなりません。</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_digits
>>> <span class="synPreProc">from</span> sklearn.discriminant_analysis <span class="synPreProc">import</span> LinearDiscriminantAnalysis <span class="synStatement">as</span> LDA
>>> lda = LDA(n_components=<span class="synConstant">15</span>)
>>> lda.fit(digits.data, digits.target)
>>> lda.explained_variance_ratio_
array([<span class="synConstant">0.28912041</span>, <span class="synConstant">0.18262788</span>, <span class="synConstant">0.16962345</span>, <span class="synConstant">0.1167055</span> , <span class="synConstant">0.08301253</span>,
<span class="synConstant">0.06565685</span>, <span class="synConstant">0.04310127</span>, <span class="synConstant">0.0293257</span> , <span class="synConstant">0.0208264</span> ])
</pre><p> 決定境界をクラス数-1個引くので(SVMで言うところのone-versus-the-rest)、n_componentsも必然的にそれだけ必要になります(逆にそれ以上は必要になりません)。</p><p> 上のグラフはそのつもりで眺めてください。また、LDAはけっきょくのところ線形変換なので、クラス数-1次元の線形空間にうまく張り直せないような入力に対しては無力なことも覚えておく必要があるでしょう(PCAも非線形構造はダメだが・・・カーネルでも持ってくる必要がある)。</p>
</div>
hayataka2049
【python】sklearnのPCAでsvd_solverによる速度差を比較
hatenablog://entry/17391345971627226798
2018-03-19T17:23:15+09:00
2019-06-17T20:44:43+09:00 sklearnのPCA(主成分分析)がやたら遅くて腹が立ちました。計算コストを下げるために次元削減してるのに、次元削減で計算コスト食ったら意味がありません。 とにかくこのPCAを高速化したかったので、svd_solverを変えてどうなるか試しました。なお、腹が立つくらい遅かった理由は最終的にちゃんとわかったので、この記事の最後に載せます。 目次 svd_solverとは 実験 結果 まとめ おまけ:腹が立った理由 スポンサーリンク (adsbygoogle = window.adsbygoogle || []).push({}); svd_solverとは PCAは内部で特異値分解(SVD)を…
<p> sklearnのPCA(主成分分析)がやたら遅くて腹が立ちました。計算コストを下げるために次元削減してるのに、次元削減で計算コスト食ったら意味がありません。</p><p> とにかくこのPCAを高速化したかったので、svd_solverを変えてどうなるか試しました。なお、腹が立つくらい遅かった理由は最終的にちゃんとわかったので、この記事の最後に載せます。</p><p> 目次</p>
<ul class="table-of-contents">
<li><a href="#svd_solverとは">svd_solverとは</a></li>
<li><a href="#実験">実験</a></li>
<li><a href="#結果">結果</a></li>
<li><a href="#まとめ">まとめ</a></li>
<li><a href="#おまけ腹が立った理由">おまけ:腹が立った理由</a></li>
</ul><p><span style="font-size: 80%">スポンサーリンク</span><br />
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script></p>
<p><ins class="adsbygoogle"
style="display:block"
data-ad-client="ca-pub-6261827798827777"
data-ad-slot="1744230936"
data-ad-format="auto"
data-full-width-responsive="true"></ins><br />
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script></p><br />
<p></p>
<div class="section">
<h3 id="svd_solverとは">svd_solverとは</h3>
<p> PCAは内部で特異値分解(SVD)を使っています。この特異値分解がコンピュータにやらせるにはそれなりに計算コストの高い処理で、とりあえずアルゴリズムが何種類かあるようです。</p><p> sklearnのPCAで使える(指定できる)アルゴリズムは次の4つです。</p>
<ul>
<li>auto</li>
</ul><p> デフォルト値。500*500以下の入力データならfullを、それ以上ならrandomizedを使うそうです<a href="#f-933e13bd" name="fn-933e13bd" title="300*800だったりしたらどうなるんだろう? それとも共分散行列のサイズなのだろうか?">*1</a></p>
<ul>
<li>full</li>
</ul><p> standard LAPACK solverを使うそうです。とりあえずぜんぶ丸ごと特異値分解してから、n_componentsで指定した次元数だけ取ってくるそうな</p>
<ul>
<li>arpack</li>
</ul><p> Truncate SVDという手法を使う。一次元ずつ寄与率の大きい主成分から計算していくらしい。n_componentsが小さければ速いことが期待されるんだと思う</p>
<ul>
<li>randomized</li>
</ul><p> randomized SVDという手法で計算する。乱数使って速くした。乱数なので厳密解ではない</p><p> なお、以上の情報はすべて公式ドキュメントから得ました。<br />
<a href="http://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html">sklearn.decomposition.PCA — scikit-learn 0.20.1 documentation</a></p><p> とりあえずautoはどうでも良いので、残りの3つを比較することにします。</p>
</div>
<div class="section">
<h3 id="実験">実験</h3>
<p> PCAをかけたくなるような高次元データといえばBag of Words、ということでこのブログですでに何回も取り上げたことのある、sklearnのfetch_20newsgroupsとCountVectorizerの組み合わせを使います。前者はテキストのデータセット、後者はBoWを生成するクラスです。</p><p> 次のような実験用コードを書きました。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span>
<span class="synPreProc">import</span> time
<span class="synPreProc">from</span> itertools <span class="synPreProc">import</span> product
<span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> fetch_20newsgroups
<span class="synPreProc">from</span> sklearn.feature_extraction.text <span class="synPreProc">import</span> CountVectorizer
<span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA
<span class="synStatement">def</span> <span class="synIdentifier">main</span>():
news20 = fetch_20newsgroups()
<span class="synStatement">for</span> min_df <span class="synStatement">in</span> [<span class="synConstant">0.02</span>, <span class="synConstant">0.01</span>, <span class="synConstant">0.008</span>, <span class="synConstant">0.005</span>]:
cv = CountVectorizer(min_df=min_df, max_df=<span class="synConstant">0.5</span>,
stop_words=<span class="synConstant">"english"</span>)
X = cv.fit_transform(news20.data).toarray()
<span class="synIdentifier">print</span>(<span class="synConstant">"min_df:{0} X.shape:{1}"</span>.<span class="synIdentifier">format</span>(min_df, X.shape))
<span class="synStatement">for</span> n_components, svd_solver <span class="synStatement">in</span> product(
[<span class="synConstant">100</span>, <span class="synConstant">500</span>],
[<span class="synConstant">"full"</span>, <span class="synConstant">"arpack"</span>, <span class="synConstant">"randomized"</span>]):
pca = PCA(n_components=n_components, svd_solver=svd_solver)
t1 = time.time()
pca.fit_transform(X)
t2 = time.time()
<span class="synIdentifier">print</span>(<span class="synConstant">"n_components:{0} solver:{1:>10} "</span><span class="synSpecial">\</span>
<span class="synConstant">"time:{2:>6.2f} CP:{3:.4f}"</span>.<span class="synIdentifier">format</span>(
n_components, svd_solver, t2-t1,
pca.explained_variance_ratio_.<span class="synIdentifier">sum</span>()))
<span class="synIdentifier">print</span>(<span class="synConstant">""</span>)
<span class="synStatement">if</span> __name__ == <span class="synConstant">"__main__"</span>:
main()
</pre><p> BoWの次元数をmin_dfで変えていき、n_componentsを100と500、svd_solverを上記3つで変化させてPCAをかけたときの速度と累積寄与率(CP:Cumulative Proportion)をそれぞれ測ります。</p>
</div>
<div class="section">
<h3 id="結果">結果</h3>
<p> 次のようになりました。</p>
<pre class="code" data-lang="" data-unlink>min_df:0.02 X.shape:(11314, 866)
n_components:100 solver: full time: 3.60 CP:0.7455
n_components:100 solver: arpack time: 3.90 CP:0.7455
n_components:100 solver:randomized time: 1.72 CP:0.7443
n_components:500 solver: full time: 3.89 CP:0.9528
n_components:500 solver: arpack time: 19.42 CP:0.9528
n_components:500 solver:randomized time: 8.91 CP:0.9516
min_df:0.01 X.shape:(11314, 1916)
n_components:100 solver: full time: 22.38 CP:0.8029
n_components:100 solver: arpack time: 8.41 CP:0.8029
n_components:100 solver:randomized time: 4.86 CP:0.8028
n_components:500 solver: full time: 22.06 CP:0.9304
n_components:500 solver: arpack time: 53.73 CP:0.9304
n_components:500 solver:randomized time: 13.47 CP:0.9293
min_df:0.008 X.shape:(11314, 2391)
n_components:100 solver: full time: 34.24 CP:0.7899
n_components:100 solver: arpack time: 10.42 CP:0.7899
n_components:100 solver:randomized time: 5.75 CP:0.7897
n_components:500 solver: full time: 34.88 CP:0.9193
n_components:500 solver: arpack time: 63.37 CP:0.9193
n_components:500 solver:randomized time: 15.18 CP:0.9182
min_df:0.005 X.shape:(11314, 3705)
n_components:100 solver: full time:100.52 CP:0.7701
n_components:100 solver: arpack time: 16.46 CP:0.7701
n_components:100 solver:randomized time: 8.70 CP:0.7699
n_components:500 solver: full time:100.73 CP:0.9000
n_components:500 solver: arpack time: 94.33 CP:0.9000
n_components:500 solver:randomized time: 20.04 CP:0.8988</pre><p> 要約すると、</p>
<ul>
<li>fullは基本的に遅い。入力の次元数が増えるとびっくりするくらい遅くなる</li>
<li>arpackは100次元に落とすときは威力を発揮している。500次元に落とすケースではかえって遅くなる。ヘタするとfullより遅い</li>
<li>randomizedは速い。ただし厳密解ではないことがCPからわかる(full、arpackとは微妙に違う数字になっている)</li>
</ul><p> こういう状況です。わかりやすいですね。</p><p> それぞれの使い分けは、</p>
<ol>
<li>入力次元数の小さい入力ではfullで良い。というかヘタにそれ以外を指定するとかえって遅いケースもある</li>
<li>入力次元数が大きく、入力次元数>>出力次元数で厳密解がほしければならarpackの使用を検討する</li>
<li>厳密解じゃなくても良いのでとにかく速いのを! ってときはrandomized</li>
</ol><p> ってことになるかと思う・・・。</p>
</div>
<div class="section">
<h3 id="まとめ">まとめ</h3>
<p> けっこう変わる。頑張って使い分けよう。</p>
</div>
<div class="section">
<h3 id="おまけ腹が立った理由">おまけ:腹が立った理由</h3>
<p> sklearnのPCAではn_componentsに小数を指定できます。そうすると累積寄与率がその数字になるように勝手に次元数を決めてくれるので、こりゃ便利だわいと思って私はよく使っていました。</p><p> しかし、実はarpack、randomizedではこの小数での指定は使えません。そのことはドキュメントにもちゃんと書いてあります。無理矢理に指定すると次のようなエラーを吐かれます。</p>
<pre class="code" data-lang="" data-unlink>ValueError: n_components=0.95 must be between 1 and n_features=866 with svd_solver='arpack'</pre><p> ということは何が起こるか? 勝手にfullにされます。遅い訳です。なんてこった。</p><p> わかってしまえば下らない話で、要するに私が使いこなせていなかっただけなのですが、このことは「ちゃんとドキュメントをよく読んで使おうね」という教訓を私に残したのでした。</p>
</div><div class="footnote">
<p class="footnote"><a href="#fn-933e13bd" name="f-933e13bd" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">300*800だったりしたらどうなるんだろう? それとも共分散行列のサイズなのだろうか?</span></p>
</div>
hayataka2049