主成分分析 - 静かなる名辞 pythonとプログラミングのこと 2020-05-07T20:42:34+09:00 hayataka2049 Hatena::Blog hatenablog://blog/10328537792367869878 SVMのsupport vectorを可視化してみた hatenablog://entry/17680117127211806773 2019-07-01T21:52:44+09:00 2019-09-01T00:11:54+09:00 はじめに SVMはヒンジ関数を使ってマージン最大化を行い、境界付近のデータに基づいて分離超平面を決定する……ということはよく言われています。でも、実際のデータで確認している図はあまり見たことがありません。 sklearnのSVMのドキュメントを読んでいたら、属性からサポートベクトル関連の情報が取れることがわかったので、いつものようにirisで見てみます。 見方 ここに書いてあります。 sklearn.svm.SVC — scikit-learn 0.21.3 documentation support_ : array-like, shape = [n_SV] Indices of suppo… <div class="section"> <h3>はじめに</h3> <p> SVMはヒンジ関数を使ってマージン最大化を行い、境界付近のデータに基づいて分離超平面を決定する……ということはよく言われています。でも、実際のデータで確認している図はあまり見たことがありません。</p><p> sklearnのSVMのドキュメントを読んでいたら、属性からサポートベクトル関連の情報が取れることがわかったので、いつものようにirisで見てみます。</p> </div> <div class="section"> <h3>見方</h3> <p> ここに書いてあります。<br /> <a href="https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html">sklearn.svm.SVC &mdash; scikit-learn 0.21.3 documentation</a><br /> </p> <blockquote> <p>support_ : array-like, shape = [n_SV]<br />   Indices of support vectors.</p><p>support_vectors_ : array-like, shape = [n_SV, n_features]<br />   Support vectors.</p><p>n_support_ : array-like, dtype=int32, shape = [n_class]<br />   Number of support vectors for each class.</p> </blockquote> <p> これを使うと便利です。support_とsupport_vectors_はどちらを使っても良いのですが、散布図でサポートベクトルとそれ以外の点を分けてプロットしたいという都合上、support_の方を使います。</p><p> なお、小ネタとして、indiciesの配列がある場合、以下のようにすることでboolean maskに変換できます。今回は論理反転を使いたいので、これが使えると便利です。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np &gt;&gt;&gt; a = np.arange(<span class="synConstant">10</span>) &gt;&gt;&gt; idx = np.where(a &gt; <span class="synConstant">4</span>) &gt;&gt;&gt; idx (array([<span class="synConstant">5</span>, <span class="synConstant">6</span>, <span class="synConstant">7</span>, <span class="synConstant">8</span>, <span class="synConstant">9</span>]),) &gt;&gt;&gt; mask = np.zeros(<span class="synConstant">10</span>, dtype=<span class="synConstant">&quot;bool&quot;</span>) &gt;&gt;&gt; mask[idx] = <span class="synIdentifier">True</span> &gt;&gt;&gt; mask array([<span class="synIdentifier">False</span>, <span class="synIdentifier">False</span>, <span class="synIdentifier">False</span>, <span class="synIdentifier">False</span>, <span class="synIdentifier">False</span>, <span class="synIdentifier">True</span>, <span class="synIdentifier">True</span>, <span class="synIdentifier">True</span>, <span class="synIdentifier">True</span>, <span class="synIdentifier">True</span>]) &gt;&gt;&gt; ~mask array([ <span class="synIdentifier">True</span>, <span class="synIdentifier">True</span>, <span class="synIdentifier">True</span>, <span class="synIdentifier">True</span>, <span class="synIdentifier">True</span>, <span class="synIdentifier">False</span>, <span class="synIdentifier">False</span>, <span class="synIdentifier">False</span>, <span class="synIdentifier">False</span>, <span class="synIdentifier">False</span>]) </pre><p> 参考:<a href="https://stackoverflow.com/questions/9503510/how-to-invert-numpy-where-np-where-function">python - How to invert numpy.where (np.where) function - Stack Overflow</a><br /> </p> </div> <div class="section"> <h3>コード</h3> <p> 素直にやります。以前やったRGB予測確率プロットも同時に出します。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F06%2F22%2F235133" title="sklearnとmatplotlibでiris(3クラス)の予測確率を可視化した話 - 静かなる名辞" 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/22/235133">sklearn&#x3068;matplotlib&#x3067;iris&#xFF08;3&#x30AF;&#x30E9;&#x30B9;&#xFF09;&#x306E;&#x4E88;&#x6E2C;&#x78BA;&#x7387;&#x3092;&#x53EF;&#x8996;&#x5316;&#x3057;&#x305F;&#x8A71; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a><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.svm <span class="synPreProc">import</span> SVC <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): iris = load_iris() <span class="synComment"># とりあえず二次元に</span> X = PCA(n_components=<span class="synConstant">2</span>).fit_transform(iris.data) <span class="synComment"># 学習</span> clf = SVC(gamma=<span class="synConstant">&quot;scale&quot;</span>, probability=<span class="synIdentifier">True</span>) clf.fit(X, iris.target) <span class="synComment"># support_をboolean maskにしておく</span> support = np.zeros(X.shape[<span class="synConstant">0</span>], dtype=<span class="synConstant">&quot;bool&quot;</span>) support[clf.support_] = <span class="synIdentifier">True</span> <span class="synComment"># --散布図--</span> ax = plt.subplot() cm = ListedColormap([<span class="synConstant">&quot;b&quot;</span>, <span class="synConstant">&quot;g&quot;</span>, <span class="synConstant">&quot;r&quot;</span>]) <span class="synComment"># サポートベクトルとそれ以外を違うmarkerで</span> ax.scatter(X[~support,<span class="synConstant">0</span>], X[~support,<span class="synConstant">1</span>], marker=<span class="synConstant">&quot;2&quot;</span>, c=iris.target[~support], cmap=cm, alpha=<span class="synConstant">0.5</span>) ax.scatter(X[support,<span class="synConstant">0</span>], X[support,<span class="synConstant">1</span>], marker=<span class="synConstant">&quot;.&quot;</span>, c=iris.target[support], cmap=cm) <span class="synComment"># 確率のプロット</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>)) 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.1</span>, aspect=<span class="synConstant">&quot;auto&quot;</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">&quot;result.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</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/20190701/20190701214551.png" alt="&#x7D50;&#x679C;" title="f:id:hayataka2049:20190701214551p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>結果</figcaption></figure></p><p> 濃いめの点がサポートベクトル、薄い三菱マークがそれ以外です。なるほど、こうなっているのかという発見というか納得感が得られます。あと、よく見るとマージン最大化のため、緑色の領域がずいぶん青い側に寄っているみたいですね。</p> </div> <div class="section"> <h3>まとめ</h3> <p> 簡単に見れるので、2次元のデータでいろいろ突っ込んでみると面白いと思います。実際には全体の数割くらいのデータしか使わないで学習している様子がわかります。</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 &mdash; 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="&#x4EE5;&#x524D;&#x306E;&#x8A18;&#x4E8B;&#x3088;&#x308A;" 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">&#x541B;&#x306F;KNN&#xFF08;k nearest neighbor&#xFF09;&#x306E;&#x672C;&#x5F53;&#x306E;&#x3059;&#x3054;&#x3055;&#x3092;&#x77E5;&#x3089;&#x306A;&#x3044; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><p> ただ、これが素直にできるのは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="&#x4EE5;&#x524D;&#x306E;&#x8A18;&#x4E8B;&#x3088;&#x308A;" 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">&#x3010;python&#x3011;&#x9AD8;&#x6B21;&#x5143;&#x306E;&#x5206;&#x96E2;&#x5883;&#x754C;&#x3092;&#x306A;&#x3093;&#x3068;&#x304B;2&#x6B21;&#x5143;&#x3067;&#x898B;&#x308B; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</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">&quot;brg&quot;</span>) plt.savefig(<span class="synConstant">&quot;fig1.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</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">&quot;brg&quot;</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">&quot;brg&quot;</span>, shading=<span class="synConstant">&quot;gouraud&quot;</span>) plt.savefig(<span class="synConstant">&quot;fig2.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p><br />  参考:<a href="https://www.haya-programming.com/entry/2019/05/24/030924">matplotlib&#x306E;pcolormesh&#x3067;alpha&#x3092;&#x5C0F;&#x3055;&#x304F;&#x3059;&#x308B;&#x3068;&#x7DB2;&#x76EE;&#x304C;&#x51FA;&#x3066;&#x304F;&#x308B;&#x5BFE;&#x7B56; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</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 &mdash; 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">&quot;brg&quot;</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">&quot;fig3.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</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 &mdash; 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">&quot;brg&quot;</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">&quot;auto&quot;</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">&quot;fig4.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</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">&quot;b&quot;</span>, <span class="synConstant">&quot;g&quot;</span>, <span class="synConstant">&quot;r&quot;</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">&quot;auto&quot;</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">&quot;fig5.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</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">&quot;b&quot;</span>, <span class="synConstant">&quot;g&quot;</span>, <span class="synConstant">&quot;r&quot;</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">&quot;auto&quot;</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">&quot;fig6.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</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">&quot;gouraud&quot;</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">&quot;scale&quot;</span>) show_hyperplane(iris, svm, <span class="synConstant">&quot;iris_svm.png&quot;</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">&quot;iris_rf.png&quot;</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">&quot;iris_ada.png&quot;</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">&#x3010;python&#x3011;sklearn&#x306E;AdaBoost&#x3092;&#x30C7;&#x30D5;&#x30A9;&#x30EB;&#x30C8;&#x30D1;&#x30E9;&#x30E1;&#x30FC;&#x30BF;&#x3067;&#x4F7F;&#x3063;&#x3066;&#x306F;&#x3044;&#x3051;&#x306A;&#x3044; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</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&#x3068;RandomForest&#x306E;&#x6BD4;&#x8F03; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</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">&quot;iris_tree.png&quot;</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">&quot;gouraud&quot;</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">&quot;scale&quot;</span>) score = svm.fit( digits.data, digits.target).score( digits.data, digits.target) <span class="synIdentifier">print</span>(score) <span class="synComment"># =&gt; 0.9744017807456873</span> show_hyperplane(digits, svm, <span class="synConstant">&quot;digits_svm.png&quot;</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"># =&gt; 1.0</span> show_hyperplane(digits, rfc, <span class="synConstant">&quot;digits_rfc.png&quot;</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">&quot;digits_ada.png&quot;</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">&quot;digits_tree.png&quot;</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">&#x975E;&#x8CA0;&#x5024;&#x884C;&#x5217;&#x56E0;&#x5B50;&#x5206;&#x89E3;&#xFF08;NMF&#xFF09;&#x3092;&#x3075;&#x308F;&#x3063;&#x3068;&#x7406;&#x89E3;&#x3059;&#x308B; - 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">&quot;PCA&quot;</span>, <span class="synConstant">&quot;NMF&quot;</span>], [pca, nmf]): X_2d = method.fit_transform(iris.data) <span class="synComment"># title</span> axes[i].set_title(<span class="synConstant">&quot;{} {}&quot;</span>.format(<span class="synConstant">&quot;iris&quot;</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">&quot;__main__&quot;</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="&#x7D50;&#x679C;" 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 本当は怖いSVMと交差検証 hatenablog://entry/98012380839181892 2019-01-26T01:21:13+09:00 2019-09-04T02:46:58+09:00 概要 SVMと交差検証を組み合わせて使うと、たとえ交差検証で高いスコアが出て汎化性能確保できた! と思っても想像とかけ離れた分離超平面になっていることがままある。 なのでこの組み合わせは少し怖いということを説明する。 コード irisを分類します。二次元で決定境界を可視化するために、irisを主成分分析を使って2次元に落としておきます。 GridSearchCVを使って交差検証し、ベストパラメータを探ります。その後、ベストパラメータの分類器で、平面上で散布図と決定境界を可視化してみます。 ちょっと長いけど、ざっくり読んでみてください。 import numpy as np import pan… <div class="section"> <h3>概要</h3> <p> SVMと交差検証を組み合わせて使うと、たとえ交差検証で高いスコアが出て汎化性能確保できた! と思っても想像とかけ離れた分離超平面になっていることがままある。</p><p> なのでこの組み合わせは少し怖いということを説明する。</p> </div> <div class="section"> <h3>コード</h3> <p> irisを分類します。二次元で決定境界を可視化するために、irisを主成分分析を使って2次元に落としておきます。</p><p> GridSearchCVを使って交差検証し、ベストパラメータを探ります。その後、ベストパラメータの分類器で、平面上で散布図と決定境界を可視化してみます。</p><p> ちょっと長いけど、ざっくり読んでみてください。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> 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.svm <span class="synPreProc">import</span> SVC <span class="synPreProc">from</span> sklearn.model_selection <span class="synPreProc">import</span> GridSearchCV <span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): <span class="synComment"># データの準備</span> iris = load_iris() X, y = PCA(<span class="synConstant">2</span>).fit_transform(iris.data), iris.target <span class="synComment"># GridSearchCVの準備</span> svm = SVC() params = {<span class="synConstant">&quot;C&quot;</span>:<span class="synConstant">10</span>**np.arange(-<span class="synConstant">3</span>, <span class="synConstant">3</span>, dtype=<span class="synIdentifier">float</span>), <span class="synConstant">&quot;gamma&quot;</span>:<span class="synConstant">10</span>**np.arange(-<span class="synConstant">3</span>, <span class="synConstant">3</span>, dtype=<span class="synIdentifier">float</span>)} gscv = GridSearchCV(svm, params, cv=<span class="synConstant">8</span>, iid=<span class="synIdentifier">False</span>, return_train_score=<span class="synIdentifier">False</span>, verbose=<span class="synConstant">1</span>, n_jobs=-<span class="synConstant">1</span>) gscv.fit(X, y) <span class="synComment"># GridSearchCVの結果を表示する</span> result_df = pd.DataFrame(gscv.cv_results_) <span class="synIdentifier">print</span>(result_df[[<span class="synConstant">&quot;param_C&quot;</span>, <span class="synConstant">&quot;param_gamma&quot;</span>, <span class="synConstant">&quot;rank_test_score&quot;</span>, <span class="synConstant">&quot;mean_test_score&quot;</span>] ][result_df[<span class="synConstant">&quot;rank_test_score&quot;</span>]==<span class="synConstant">1</span>]) <span class="synComment"># 最良推定器をclfに代入</span> clf = gscv.best_estimator_ <span class="synComment"># 可視化の準備</span> xmin, xmax, ymin, ymax = (X[:,<span class="synConstant">0</span>].min()-<span class="synConstant">1</span>, X[:,<span class="synConstant">0</span>].max()+<span class="synConstant">1</span>, X[:,<span class="synConstant">1</span>].min()-<span class="synConstant">1</span>, X[:,<span class="synConstant">1</span>].max()+<span class="synConstant">1</span>) x_ = np.arange(xmin, xmax, <span class="synConstant">0.01</span>) y_ = np.arange(ymin, ymax, <span class="synConstant">0.01</span>) xx, yy = np.meshgrid(x_, y_) <span class="synComment"># 予測</span> zz = clf.predict(np.stack([xx.ravel(), yy.ravel()], axis=<span class="synConstant">1</span>) ).reshape(xx.shape) <span class="synComment"># 可視化</span> plt.pcolormesh(xx, yy, zz, cmap=<span class="synConstant">&quot;winter&quot;</span>, alpha=<span class="synConstant">0.1</span>, shading=<span class="synConstant">&quot;gouraud&quot;</span>) plt.scatter(X[:, <span class="synConstant">0</span>], X[:, <span class="synConstant">1</span>], c=y, edgecolors=<span class="synConstant">'k'</span>, cmap=<span class="synConstant">&quot;winter&quot;</span>) plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre> </div> <div class="section"> <h3>結果</h3> <p> printされた結果は良さげなものでした。</p> <pre class="code" data-lang="" data-unlink> param_C param_gamma rank_test_score mean_test_score 22 1 10 1 0.972222</pre><p> 0.972222なら悪くないaccuracyです。</p><p> しかし、出力された画像は思っていたものとは違いました。</p><p><figure class="figure-image figure-image-fotolife" title="SVMの結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190126/20190126004232.png" alt="SVM&#x306E;&#x7D50;&#x679C;" title="f:id:hayataka2049:20190126004232p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>SVMの結果</figcaption></figure></p><p> こわっ。どう考えてもこういうデータではないと思いますが、こういうことが現実に起こります。(-1.5, 0)あたりにデータが来たら、青かねずみ色のどちらかの色のグループに分類されてほしいところですが、実際は緑になってしまう訳です。</p><p> ちょっと信頼ならないですね、SVM。</p> </div> <div class="section"> <h3>怖くない線形SVM</h3> <p> ついでにいろいろな分類器を見てみます。</p><p> LinearSVCをimportし、「# GridSearchCVの準備」以下「# 最良推定器をclfに代入」の上までを次のように書き換える。</p> <pre class="code lang-python" data-lang="python" data-unlink> <span class="synComment"># GridSearchCVの準備</span> svm = LinearSVC() params = {<span class="synConstant">&quot;C&quot;</span>:<span class="synConstant">10</span>**np.arange(-<span class="synConstant">3</span>, <span class="synConstant">3</span>, dtype=<span class="synIdentifier">float</span>)} gscv = GridSearchCV(svm, params, cv=<span class="synConstant">8</span>, iid=<span class="synIdentifier">False</span>, return_train_score=<span class="synIdentifier">False</span>, verbose=<span class="synConstant">1</span>, n_jobs=-<span class="synConstant">1</span>) gscv.fit(X, y) <span class="synComment"># GridSearchCVの結果を表示する</span> result_df = pd.DataFrame(gscv.cv_results_) <span class="synIdentifier">print</span>(result_df[[<span class="synConstant">&quot;param_C&quot;</span>, <span class="synConstant">&quot;rank_test_score&quot;</span>, <span class="synConstant">&quot;mean_test_score&quot;</span>] ][result_df[<span class="synConstant">&quot;rank_test_score&quot;</span>]==<span class="synConstant">1</span>]) </pre><p> 結果。</p> <pre class="code" data-lang="" data-unlink> param_C rank_test_score mean_test_score 4 10 1 0.960317</pre><p> accuracyはわずかに下がるだけ。</p><p><figure class="figure-image figure-image-fotolife" title="線形SVMの結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190126/20190126004932.png" alt="&#x7DDA;&#x5F62;SVM&#x306E;&#x7D50;&#x679C;" title="f:id:hayataka2049:20190126004932p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>線形SVMの結果</figcaption></figure></p><p> はるかに納得感の高い結果になっています。こういうことがあるので、ほぼ線形分離可能なことがわかっているデータは、まずは線形なモデルで試すことがおすすめです。非線形なモデルで1%とかaccuracyをあげられるとしても、それで未知のデータに対して良い推定ができるかどうかは交差検証ではわからないのです<a href="#f-b512c293" name="fn-b512c293" title="手持ちのデータの分布に依存する話ですが。未知のデータなんてないぜ! といえるくらい豊富なデータがあればそれはそれで構いません">*1</a>。</p> </div> <div class="section"> <h3>怖いかどうか悩むランダムフォレスト</h3> <p> LinearSVCと同様にRandomForestClassifierをimportし、同じ箇所を書き換えます。</p> <pre class="code lang-python" data-lang="python" data-unlink> <span class="synComment"># GridSearchCVの準備</span> clf = RandomForestClassifier(n_jobs=-<span class="synConstant">1</span>) params = {<span class="synConstant">&quot;n_estimators&quot;</span>:<span class="synConstant">10</span>**np.arange(<span class="synConstant">3</span>), <span class="synConstant">&quot;min_samples_leaf&quot;</span>:[<span class="synConstant">1</span>,<span class="synConstant">2</span>]} gscv = GridSearchCV(clf, params, cv=<span class="synConstant">8</span>, iid=<span class="synIdentifier">False</span>, return_train_score=<span class="synIdentifier">False</span>, verbose=<span class="synConstant">1</span>, n_jobs=-<span class="synConstant">1</span>) gscv.fit(X, y) <span class="synComment"># GridSearchCVの結果を表示する</span> result_df = pd.DataFrame(gscv.cv_results_) <span class="synIdentifier">print</span>(result_df[[<span class="synConstant">&quot;param_n_estimators&quot;</span>, <span class="synConstant">&quot;param_min_samples_leaf&quot;</span>, <span class="synConstant">&quot;rank_test_score&quot;</span>, <span class="synConstant">&quot;mean_test_score&quot;</span>] ][result_df[<span class="synConstant">&quot;rank_test_score&quot;</span>]==<span class="synConstant">1</span>]) </pre><p> 性能。SVMと比べると少し低下するかな。理由はよくわからないけど、木の本数10本で葉の最小サンプル数2のときが最高性能(本当になんで? 基本的に木が多い方が性能が高いはずなのだが・・・)。</p> <pre class="code" data-lang="" data-unlink> param_n_estimators param_min_samples_leaf rank_test_score mean_test_score 4 10 2 1 0.953373</pre><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/20190126/20190126010103.png" alt="&#x30E9;&#x30F3;&#x30C0;&#x30E0;&#x30D5;&#x30A9;&#x30EC;&#x30B9;&#x30C8;&#x306E;&#x7D50;&#x679C;" title="f:id:hayataka2049:20190126010103p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>ランダムフォレストの結果</figcaption></figure></p><p> この状態ではrefitしているので、accuracyは1になるはずです。さて、ランダムフォレストの特徴は、決定木なので軸と90度の直線の組み合わせで決定境界が表現されることです。また、少ない分割でエントロピーが下がるような決定境界を追求していくので、データの全体の傾向はあまり見てくれません。</p><p> それでもSVMよりはマシな感じでしょうか。</p> </div> <div class="section"> <h3>怖くない気がする多層パーセプトロン</h3> <p> 疲れてきたので最後。やり方は上と同じ。</p> <pre class="code lang-python" data-lang="python" data-unlink> <span class="synComment"># GridSearchCVの準備</span> clf = MLPClassifier(max_iter=<span class="synConstant">3000</span>) params = {<span class="synConstant">&quot;hidden_layer_sizes&quot;</span>:[(<span class="synConstant">5</span>*x,) <span class="synStatement">for</span> x <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">1</span>, <span class="synConstant">5</span>)]} gscv = GridSearchCV(clf, params, cv=<span class="synConstant">8</span>, iid=<span class="synIdentifier">False</span>, return_train_score=<span class="synIdentifier">False</span>, verbose=<span class="synConstant">1</span>, n_jobs=-<span class="synConstant">1</span>) gscv.fit(X, y) <span class="synComment"># GridSearchCVの結果を表示する</span> result_df = pd.DataFrame(gscv.cv_results_) <span class="synIdentifier">print</span>(result_df[[<span class="synConstant">&quot;param_hidden_layer_sizes&quot;</span>, <span class="synConstant">&quot;rank_test_score&quot;</span>, <span class="synConstant">&quot;mean_test_score&quot;</span>] ][result_df[<span class="synConstant">&quot;rank_test_score&quot;</span>]==<span class="synConstant">1</span>]) </pre><p> 同率一位が3つもありました。ま、どれでも良いか。</p> <pre class="code" data-lang="" data-unlink> param_hidden_layer_sizes rank_test_score mean_test_score 1 (10,) 1 0.96627 2 (15,) 1 0.96627 3 (20,) 1 0.96627</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/20190126/20190126011657.png" alt="&#x591A;&#x5C64;&#x30D1;&#x30FC;&#x30BB;&#x30D7;&#x30C8;&#x30ED;&#x30F3;&#x306E;&#x7D50;&#x679C;" title="f:id:hayataka2049:20190126011657p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>多層パーセプトロンの結果</figcaption></figure></p><p> まともな感じ。右側の決定境界はちょっと甘いかなー、という気はします。</p><p> ただしパラメータ数の少ない隠れ層1層の多層パーセプトロンだからこれくらいの素直な結果に落ち着くのであって、深層学習は油断するとすぐ過学習するので注意が必要です。</p> </div> <div class="section"> <h3>SVMも怖くない!</h3> <p> 汎化重視のパラメータにすれば変なことにはならないので安心してください。基本的にCもgammaも低ければ低いほど過学習しづらくなります。</p><p> 今までと同じ箇所をこう書き換える。</p> <pre class="code lang-python" data-lang="python" data-unlink> <span class="synComment"># SVMをfit</span> clf = SVC(C=<span class="synConstant">1</span>, gamma=<span class="synConstant">0.2</span>) clf.fit(X, y) </pre><p><figure class="figure-image figure-image-fotolife" title="汎化重視のパラメータのSVMの結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190126/20190126015451.png" alt="&#x6C4E;&#x5316;&#x91CD;&#x8996;&#x306E;&#x30D1;&#x30E9;&#x30E1;&#x30FC;&#x30BF;&#x306E;SVM&#x306E;&#x7D50;&#x679C;" title="f:id:hayataka2049:20190126015451p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>汎化重視のパラメータのSVMの結果</figcaption></figure></p><p> 別に問題ないですよね。SVMそのものはこのようにパラメータで自由に分離超平面の複雑さを調整できる、優れた分類器です。</p><p> ただし、交差検証で機械的にパラメータを決めてしまうとあまり良くない結果を招く可能性がある訳です。</p> </div> <div class="section"> <h3>現実的な話</h3> <p> 二次元で見ているから違和感があるのであって、高次元空間はサクサクメロンパン問題があるのでまた異なった挙動になります。</p><p> ↓サクサクメロンパン問題</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwindfall.hatenablog.com%2Fentry%2F2015%2F07%2F02%2F084623" title="次元の呪い、あるいは「サクサクメロンパン問題」 - 蛍光ペンの交差点[別館]" 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://windfall.hatenablog.com/entry/2015/07/02/084623">&#x6B21;&#x5143;&#x306E;&#x546A;&#x3044;&#x3001;&#x3042;&#x308B;&#x3044;&#x306F;&#x300C;&#x30B5;&#x30AF;&#x30B5;&#x30AF;&#x30E1;&#x30ED;&#x30F3;&#x30D1;&#x30F3;&#x554F;&#x984C;&#x300D; - &#x86CD;&#x5149;&#x30DA;&#x30F3;&#x306E;&#x4EA4;&#x5DEE;&#x70B9;[&#x5225;&#x9928;]</a></p><p> 直感的な理解は難しいと思いますが、こういうことが問題になるケースは少ないということはなんとなくわかります。</p><p> とはいえ、「交差検証して最高性能だったSVMを投入したらトンチンカンな結果を出してくる」みたいなことが実際に起こらないとも限らないので、ある程度は注意した方が良いでしょう。</p> </div> <div class="section"> <h3>まとめ</h3> <p> SVMはパラメータ設定を過学習するようにしたとき、他の手法と比べても群を抜いて変な結果になるのですが、何が駄目なんでしょうね。やっぱりカーネル使ってるからか。</p><p> ちょっと注意が要るよなー、というお話でした。</p> </div><div class="footnote"> <p class="footnote"><a href="#fn-b512c293" name="f-b512c293" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">手持ちのデータの分布に依存する話ですが。未知のデータなんてないぜ! といえるくらい豊富なデータがあればそれはそれで構いません</span></p> </div> hayataka2049 【python】sklearnのOneClassSVMを使って外れ値検知してみる hatenablog://entry/10257846132684685770 2018-12-14T11:11:26+09:00 2019-09-05T02:56:04+09:00 はじめに OneClassSVMというものがあると知ったので使ってみます。 「1クラスSVM?」と思われると思いますが、要するに異常検知・外れ値検出などで使う手法です。信頼区間を出すのに似ていますが、複雑な分布だったりそもそも分布が想定できないようなデータでも計算してくれるので、シチュエーションによっては役に立ちそうです。 なお、わかりやすい記事があったので先に紹介しておきます。異常検知のための One Class SVM - Qiita 実験 異常検知・外れ値検出系で使える手法なので、センサデータの処理とか、為替や株価のアルゴリズム取引用の処理なんかをやると適当だと思いますが、私はそんなカッ… <div class="section"> <h3>はじめに</h3> <p> OneClassSVMというものがあると知ったので使ってみます。</p><p> 「1クラスSVM?」と思われると思いますが、要するに異常検知・外れ値検出などで使う手法です。信頼区間を出すのに似ていますが、複雑な分布だったりそもそも分布が想定できないようなデータでも計算してくれるので、シチュエーションによっては役に立ちそうです。</p><p> なお、わかりやすい記事があったので先に紹介しておきます。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fkznx%2Fitems%2F434d98bf1a0e39327542" title="異常検知のための One Class SVM - 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/kznx/items/434d98bf1a0e39327542">&#x7570;&#x5E38;&#x691C;&#x77E5;&#x306E;&#x305F;&#x3081;&#x306E; One Class SVM - Qiita</a><br /> </p> </div> <div class="section"> <h3>実験</h3> <p> 異常検知・外れ値検出系で使える手法なので、センサデータの処理とか、為替や株価のアルゴリズム取引用の処理なんかをやると適当だと思いますが、私はそんなカッコいいデータは持っていません。</p><p> なので、例によって例のごとく、irisをPCAで二次元に落としたデータを使います。</p><p> 使い方は簡単で、nuに異常値の割合を指定すれば良いようです。なんかドキュメントには意味深なことが書いてありますが、この理解で良さそうです。</p><p> ちなみにデフォルトはnu=0.5なので、データの半数が異常値扱いになります。最初は、一体何事かと思いました。あと、predictすると正常値=1,異常値=-1という予測になります。</p><p> ドキュメント</p><p> <a href="https://scikit-learn.org/stable/modules/generated/sklearn.svm.OneClassSVM.html#sklearn.svm.OneClassSVM">sklearn.svm.OneClassSVM &mdash; scikit-learn 0.21.3 documentation</a></p><p> コードは以下のとおりです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> sklearn.svm <span class="synPreProc">import</span> OneClassSVM <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>) data = pca.fit_transform(iris.data) x = np.linspace(-<span class="synConstant">5</span>, <span class="synConstant">5</span>, <span class="synConstant">500</span>) y = np.linspace(-<span class="synConstant">1.5</span>, <span class="synConstant">1.5</span>, <span class="synConstant">250</span>) X, Y = np.meshgrid(x, y) ocsvm = OneClassSVM(nu=<span class="synConstant">0.1</span>, gamma=<span class="synConstant">&quot;auto&quot;</span>) ocsvm.fit(data) df = ocsvm.decision_function( np.array([X.ravel(), Y.ravel()]).T).reshape(X.shape) preds = ocsvm.predict(data) plt.scatter(data[:,<span class="synConstant">0</span>], data[:,<span class="synConstant">1</span>], c=preds, cmap=plt.cm.RdBu, alpha=<span class="synConstant">0.8</span>) r = <span class="synIdentifier">max</span>([<span class="synIdentifier">abs</span>(df.min()), <span class="synIdentifier">abs</span>(df.max())]) plt.contourf(X, Y, df, <span class="synConstant">10</span>, vmin=-r, vmax=r, cmap=plt.cm.RdBu, alpha=<span class="synConstant">.5</span>) plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> 予測と決定関数を見るだけという手抜き。雰囲気はこれでわかると思うので、勘弁してください。</p><p> なんかcontourfあたりでごちゃごちゃやっていますが、決定境界がcmapの中心と一致するように配慮しています。こうすることで、白色のあたり(というか青と赤の境界)が決定境界になります。</p><p> 余談ですが、このコードのためにlevelsをキーワード引数で指定しようとしたら、matplotlibのバグを踏みました。ひどい。</p><p><a href="https://github.com/matplotlib/matplotlib/issues/11913/">plt.contour levels parameter don&#39;t work as intended if receive a single int &middot; Issue #11913 &middot; matplotlib/matplotlib &middot; GitHub</a></p><br /> <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> <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/20181214/20181214110854.png" alt="result.png" title="f:id:hayataka2049:20181214110854p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result.png</figcaption></figure></p><p> このように、お手軽に良さげな結果が得られます。分布の形状が複雑でもうまく推定できる訳です。良いですね。</p> </div> <div class="section"> <h3>まとめ</h3> <p> SVMなので使いやすくて、うまく動くようです。手軽に良好な異常検知ができる手法としては、かなり便利だと思います。</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 &mdash; 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=&lt;function mean&gt;)</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">&quot;PCA&quot;</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">&quot;FeatureAgglomeration</span><span class="synSpecial">\n</span><span class="synConstant">{}&quot;</span>.<span class="synIdentifier">format</span>(agg.labels_)) plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> 動作原理、目的と用途を考えると、事前にスケーリングしておいた方が恐らく無難です。</p><p> 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="&#x30D7;&#x30ED;&#x30C3;&#x30C8;&#x306E;&#x7D50;&#x679C;" 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 &mdash; 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 &mdash; 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">&quot;rainbow&quot;</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">&quot;PCA&quot;</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">&quot;SPCA alpha={:.2f}&quot;</span>.<span class="synIdentifier">format</span>(alpha)) plt.savefig(<span class="synConstant">&quot;result.png&quot;</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">&quot;__main__&quot;</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 GridSearchCV『の』パラメータ・チューニング 高速化中心に hatenablog://entry/17391345971655425350 2018-06-18T22:11:44+09:00 2019-07-15T05:48:21+09:00 はじめに 機械学習でパラメータ・チューニングをしたい場合、グリッドサーチを行うのが定石とされています。sklearnではグリッドサーチはGridSearchCVで行うことができます。sklearn.model_selection.GridSearchCV — scikit-learn 0.21.2 documentation それで何の問題もないかというと、さにあらず。 グリッドサーチは計算コストの高い処理ですから*1、素直に書くとデータとアルゴリズム次第ではとんでもない処理時間がかかります。 もちろん「寝ている間、出かけている間に回すから良い」と割り切るという方法もありますが、可能なら速くし… <div class="section"> <h3 id="はじめに">はじめに</h3> <p> 機械学習でパラメータ・チューニングをしたい場合、グリッドサーチを行うのが定石とされています。sklearnではグリッドサーチはGridSearchCVで行うことができます。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html">sklearn.model_selection.GridSearchCV &mdash; scikit-learn 0.21.2 documentation</a></p><p> それで何の問題もないかというと、さにあらず。</p><p> グリッドサーチは計算コストの高い処理ですから<a href="#f-815eb06f" name="fn-815eb06f" title="なにせすべての組み合わせを計算する">*1</a>、素直に書くとデータとアルゴリズム次第ではとんでもない処理時間がかかります。</p><p> もちろん「寝ている間、出かけている間に回すから良い」と割り切るという方法もありますが、可能なら速くしたいですよね。</p><p> そうすると、パラメータ・チューニングのために使うGridSearchCV『の』パラメータを弄り回すという本末転倒気味な目に遭います。そういうとき、どうしたら良いのかを、この記事で書きます。</p><p> 先に結論を言ってしまうと、本質的に計算コストの高い処理なので、劇的に速くすることは不可能です。それでも、ちょっとの工夫で2倍程度なら速くすることができます。その2倍で救われる人も結構いると思うので<a href="#f-317cc7e2" name="fn-317cc7e2" title="たとえば「今から回して、朝までにデータを出さないと教授への報告が間に合わないんだ!」みたいな状況を想定しています">*2</a>、単純なことですがまとめておきます。</p><p> 目次</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#下準備とベースライン">下準備とベースライン</a></li> <li><a href="#cvを指定する効果大">cvを指定する(効果:大)</a></li> <li><a href="#return_train_scoreFalseする効果それなり">return_train_score=Falseする(効果:それなり)</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><p></p> </div> <div class="section"> <h3 id="下準備とベースライン">下準備とベースライン</h3> <p> とりあえず、何も考えずに「GridSearchCVをデフォルトパラメタで使ってみた場合」の時間を測ります。</p><p> そのためには適当なタスクを回してやる必要がありますが、今回はPCA+SVMでdigitsの分類でもやってみることにします。</p><p> コードはこんな感じです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> timeit <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <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.svm <span class="synPreProc">import</span> SVC <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 digits = load_digits() svm = SVC() pca = PCA(svd_solver=<span class="synConstant">&quot;randomized&quot;</span>) pl = Pipeline([(<span class="synConstant">&quot;pca&quot;</span>, pca), (<span class="synConstant">&quot;svm&quot;</span>, svm)]) params = {<span class="synConstant">&quot;pca__n_components&quot;</span>:[<span class="synConstant">10</span>, <span class="synConstant">15</span>, <span class="synConstant">30</span>, <span class="synConstant">45</span>], <span class="synConstant">&quot;svm__C&quot;</span>:[<span class="synConstant">1</span>, <span class="synConstant">5</span>, <span class="synConstant">10</span>, <span class="synConstant">20</span>], <span class="synConstant">&quot;svm__gamma&quot;</span>:[<span class="synConstant">0.0001</span>, <span class="synConstant">0.0005</span>, <span class="synConstant">0.001</span>, <span class="synConstant">0.01</span>]} <span class="synStatement">def</span> <span class="synIdentifier">print_df</span>(df): <span class="synIdentifier">print</span>(df[[<span class="synConstant">&quot;param_pca__n_components&quot;</span>, <span class="synConstant">&quot;param_svm__C&quot;</span>, <span class="synConstant">&quot;param_svm__gamma&quot;</span>, <span class="synConstant">&quot;mean_score_time&quot;</span>, <span class="synConstant">&quot;mean_test_score&quot;</span>]]) <span class="synStatement">def</span> <span class="synIdentifier">main1</span>(): clf = GridSearchCV(pl, params, n_jobs=-<span class="synConstant">1</span>) clf.fit(digits.data, digits.target) df = pd.DataFrame(clf.cv_results_) print_df(df) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synIdentifier">print</span>(timeit.timeit(main1, number=<span class="synConstant">1</span>)) </pre><p> 色々なテクニックを使っているコードなので多少解説すると、とりあえずPipelineを使っています。また、GridSearchCV.cv_results_はそのままpandas.DataFrameに変換できる辞書として扱えることも利用しています。</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><cite class="hatena-citation"><a href="https://www.haya-programming.com/entry/2018/02/22/234011">www.haya-programming.com</a></cite></p><p> digits, svm, pca, pl, paramsの変数はmain関数の外でグローバル変数として作っていますが、これはあとでmain2とかmain3を作って使い回すための処置です。</p><p> あと、速くするために必要と思われる常識的なこと(PCAのsvd_solver="randomized"とか、GridSearchCVのn_jobs=-1とか)はすでに実施しています。</p><p> そんなことより本当にやりたいことは、この処理にどれだけ時間がかかるかを知ることです。そのために、timeitを使って時間を計測しています。</p><p><a href="https://docs.python.jp/3/library/timeit.html">timeit --- &#x5C0F;&#x3055;&#x306A;&#x30B3;&#x30FC;&#x30C9;&#x65AD;&#x7247;&#x306E;&#x5B9F;&#x884C;&#x6642;&#x9593;&#x8A08;&#x6E2C; &mdash; Python 3.7.4 &#x30C9;&#x30AD;&#x30E5;&#x30E1;&#x30F3;&#x30C8;</a></p><p> さて、私の環境(しょぼいノートパソコン)ではこのプログラムの実行には42.2秒かかりました。</p><p> これをベースラインにします。ここからどれだけ高速化できるかが今回のテーマです。</p> </div> <div class="section"> <h3 id="cvを指定する効果大">cvを指定する(効果:大)</h3> <p> さて、GridSearchCVにはcvというパラメータがあります。default=3であり、この設定だと3分割交差検証になります。交差検証について理解していれば、特に不自然なところはないと思います。</p><p> これを2にしてみます。交差検証できる最低の数字です。こうすると、</p> <ul> <li>交差検証のループ回数が3回→2回になり、それだけで1.5倍速くなる</li> <li>チューニング対象のモデルの計算量が学習データサイズnに対してO(n)以上なら、それ(nが小さくなること)によるご利益もある。なお予測データサイズmに対する予測時間は普通O(m)なので、影響はない</li> </ul><p> この相乗効果で、高速化が期待できます。</p><p> この方法のデメリットは学習データを減らしたことで性能が低めになることですが、チューニングのときはパラメータの良し悪し(スコアの大小関係)がわかれば良いので、あまり問題になりません。とにかくやってみます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">main2</span>(): clf = GridSearchCV(pl, params, cv=<span class="synConstant">2</span>, n_jobs=-<span class="synConstant">1</span>) clf.fit(digits.data, digits.target) df = pd.DataFrame(clf.cv_results_) print_df(df) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># print(timeit.timeit(main1, number=1))</span> <span class="synIdentifier">print</span>(timeit.timeit(main2, number=<span class="synConstant">1</span>)) </pre><p> 上のコードと重複する部分は削ってあります。見比べると、ほとんど変わっていないことが、おわかりいただけるかと思います。</p><p> この処置で、処理時間は28.0秒に改善しました。ちょっといじっただけで、2/3くらいに改善してしまった訳です。そして「mean_test_score」はやはり全体的に低くなりましたが、傾向は同じでした。よってパラメータチューニングには使えます。</p> </div> <div class="section"> <h3 id="return_train_scoreFalseする効果それなり">return_train_score=Falseする(効果:それなり)</h3> <p> さて、GridSearchCVはデフォルトの設定ではreturn_train_score='warn'になっています。「'warn'って何さ?」というと、こんな警告を出します。</p> <pre class="code" data-lang="" data-unlink>FutureWarning: You are accessing a training score (&#39;std_train_score&#39;), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True</pre><p> return_train_scoreは要するに学習データに対するスコアを計算するかどうかを指定できる引数です。この警告は割とくだらないことが書いてあるのですが、将来的にはこれがdefault=Falseにされるという警告です。</p><p> 基本的に、パラメータチューニングで見たいのはテストデータに対するスコアであるはずです。なのに、現在のデフォルト設定では学習データに対する評価指標も計算されてしまいます。</p><p> これは無駄なので、return_train_score=Falseすると学習データに対する評価指標の計算分の計算コストをケチれます。予測時間なんてたかが知れていますが、それでも一応やってみましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">main3</span>(): clf = GridSearchCV(pl, params, cv=<span class="synConstant">2</span>, return_train_score=<span class="synIdentifier">False</span>, n_jobs=-<span class="synConstant">1</span>) clf.fit(digits.data, digits.target) df = pd.DataFrame(clf.cv_results_) print_df(df) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># print(timeit.timeit(main1, number=1))</span> <span class="synComment"># print(timeit.timeit(main2, number=1))</span> <span class="synIdentifier">print</span>(timeit.timeit(main3, number=<span class="synConstant">1</span>)) </pre><p> この措置によって、処理時間は22.1秒まで短縮されました。ベースラインと比較すると1/2強の時間で済んでいる訳です。</p> </div> <div class="section"> <h3 id="まとめ">まとめ</h3> <p> この記事では</p> <ul> <li>cv=2にする</li> <li>return_train_score=Falseにする</li> </ul><p> という方法で、パラメータチューニングの機能を損なわないまま2倍弱の速度の改善を実現しました。</p> <table> <tr> <th>工夫なし</th> <th>cv=2</th> <th>cv=2&return_train_score=False</th> </tr> <tr> <td>42.2秒</td> <td>28.0秒</td> <td>22.1秒</td> </tr> </table><p> このテクニックはきっと皆さんの役に立つと思います。</p> </div> <div class="section"> <h3 id="それでも時間がかかりすぎるときは">それでも時間がかかりすぎるときは</h3> <p> そもそもグリッドサーチしようとしているパラメータ候補が多すぎる可能性があります。</p><p> たとえば、3つのパラメータでそれぞれ10個の候補を調べるとなると、10*10*10=1000回の交差検証が行われます。いつまで経っても終わらない訳です。</p><p> 今回の記事では4*4*4=64回としましたが、これでもけっこう多い方だと思います。それでも解こうとしている問題が単純なので、デフォルトパラメータでも1分以内には処理できていますが、ちょっと重いモデルにちょっと多量のデータを突っ込んだりすると、もうダメです。何十分とか何時間もかかってしまいます。</p><p> そういう場合、まずは粗いステップ(少ないパラメータ候補数)でざっくりパラメータチューニングしてしまい、どの辺りのパラメータが良いのかわかったら、その周辺に絞ってもう一回パラメータチューニングを行います。こういうのを二段グリッドサーチと言ったりします。</p><p> あるいはベイズ最適化とか、他のアルゴリズムに走るのも一つの手かもしれません。</p><p> 粗いグリッドである程度チューニングしてから、RandomizedSearchCVを使うというのもいい手だと思います。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F03%2F16%2F044745" title="【python】sklearnのRandomizedSearchCVを使ってみる - 静かなる名辞" 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/03/16/044745">www.haya-programming.com</a></cite></p> </div><div class="footnote"> <p class="footnote"><a href="#fn-815eb06f" name="f-815eb06f" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">なにせすべての組み合わせを計算する</span></p> <p class="footnote"><a href="#fn-317cc7e2" name="f-317cc7e2" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">たとえば「今から回して、朝までにデータを出さないと教授への報告が間に合わないんだ!」みたいな状況を想定しています</span></p> </div> hayataka2049 【python】MeanShiftのbandwidthを変えるとどうなるか実験してみた hatenablog://entry/17391345971648895092 2018-05-28T22:18:19+09:00 2019-06-16T20:52:16+09:00 前回の記事ではMeanShiftクラスタリングを試してみました。www.haya-programming.com このMeanShiftにはbandwidthというパラメータがあり、クラスタ数を決定する上で重要な役割を果たしているはずです。 いまいち結果に納得がいかないというとき、bandwidthをいじって改善が見込めるのかどうか確認してみます。 プログラム 例によってirisとwineで比較。簡単に書きました。 import numpy as np import matplotlib.pyplot as plt import matplotlib.cm as cm from sklearn… <p> 前回の記事ではMeanShiftクラスタリングを試してみました。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2018%2F05%2F27%2F182320" title="【python】sklearnのMeanShiftクラスタリングを試してみる - 静かなる名辞" 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/2018/05/27/182320">www.haya-programming.com</a></cite></p><p> このMeanShiftにはbandwidthというパラメータがあり、クラスタ数を決定する上で重要な役割を果たしているはずです。</p><p> いまいち結果に納得がいかないというとき、bandwidthをいじって改善が見込めるのかどうか確認してみます。</p> <div class="section"> <h3>プログラム</h3> <p> 例によってirisとwineで比較。簡単に書きました。</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">import</span> matplotlib.cm <span class="synStatement">as</span> cm <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris, load_wine <span class="synPreProc">from</span> sklearn.cluster <span class="synPreProc">import</span> MeanShift, estimate_bandwidth <span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA <span class="synStatement">def</span> <span class="synIdentifier">process</span>(dataset, name): origin_bandwidth = estimate_bandwidth(dataset.data) rates = np.logspace(np.log10(<span class="synConstant">0.2</span>), np.log10(<span class="synConstant">5</span>), <span class="synConstant">11</span>) fig, axes = plt.subplots(nrows=<span class="synConstant">3</span>, ncols=<span class="synConstant">4</span>, figsize=(<span class="synConstant">24</span>,<span class="synConstant">18</span>)) PCA_X = PCA().fit_transform(dataset.data) <span class="synStatement">for</span> target <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">3</span>): axes[<span class="synConstant">0</span>,<span class="synConstant">0</span>].scatter(PCA_X[dataset.target==target, <span class="synConstant">0</span>], PCA_X[dataset.target==target, <span class="synConstant">1</span>], c=cm.Paired(target/<span class="synConstant">3</span>)) axes[<span class="synConstant">0</span>,<span class="synConstant">0</span>].set_title(<span class="synConstant">&quot;original label&quot;</span>, fontsize=<span class="synConstant">28</span>) <span class="synStatement">for</span> r, ax <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(rates, axes.ravel()[<span class="synConstant">1</span>:]): ms = MeanShift(bandwidth=r*origin_bandwidth, n_jobs=-<span class="synConstant">1</span>) y = ms.fit_predict(dataset.data) n_cluster = ms.cluster_centers_.shape[<span class="synConstant">0</span>] <span class="synStatement">for</span> target <span class="synStatement">in</span> <span class="synIdentifier">range</span>(n_cluster): ax.scatter(PCA_X[y==target, <span class="synConstant">0</span>], PCA_X[y==target, <span class="synConstant">1</span>], c=cm.Paired(target/n_cluster)) ax.set_title(<span class="synConstant">&quot;r:{0:.3f} b:{1:.3f}&quot;</span>.<span class="synIdentifier">format</span>( r, origin_bandwidth), fontsize=<span class="synConstant">28</span>) fig.savefig(name+<span class="synConstant">&quot;.png&quot;</span>) <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): iris = load_iris() wine = load_wine() process(iris, <span class="synConstant">&quot;iris&quot;</span>) process(wine, <span class="synConstant">&quot;wine&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> bandwidthをsklearn.cluster.estimate_bandwidthの推定値(デフォルトで用いられる値)の1/5倍から5倍まで変化させ、結果をプロットします。</p> </div> <div class="section"> <h3>結果</h3> <p> プロットされた結果を示します。</p><p> 結果の図の見方は、まずタイトルが</p> <ul> <li>b</li> </ul><p> sklearn.cluster.estimate_bandwidthによる推定値</p> <ul> <li>r</li> </ul><p> かけた比率</p><p> という風に対応しており、あとは便宜的に2次元上に主成分分析で写像した散布図が、クラスタごとに色分けされて出ています。一枚目が本来のクラスに基づく色分け、r=1の図が推定値による色分けです。</p><p> まずiris。<figure class="figure-image figure-image-fotolife" title="iris.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180528/20180528221215.png" alt="iris.png" title="f:id:hayataka2049:20180528221215p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>iris.png</figcaption></figure> きれいに元通りになるrは今回見た中にはありませんでした。クラスタ数的にはr=0.525とr=0.725の間くらいで3クラスタになりそうですが、この図を見るとそれでうまく元通りまとまるかは疑問です。</p><p> 次にwine。<figure class="figure-image figure-image-fotolife" title="wine.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180528/20180528221339.png" alt="wine.png" title="f:id:hayataka2049:20180528221339p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>wine.png</figcaption></figure> こちらもうまく元通りにはならないようです。そもそもデータが悪いという話はあると思います。</p> </div> <div class="section"> <h3>結論</h3> <p> 確かにクラスタ数は変わるが、クラスタリングの良し悪しが改善するかはなんともいえないですね。</p><p> データをスケーリングしたり、もっと色々頑張ると改善は見込めるかもしれません。</p> </div> hayataka2049 【python】sklearnのMeanShiftクラスタリングを試してみる hatenablog://entry/17391345971648436868 2018-05-27T18:23:20+09:00 2019-06-29T20:03:10+09:00 はじめに MeanShiftはクラスタリングアルゴリズム。クラスタ数を自動で決定してくれるという長所がある。 理論的には最急降下法で各クラスタの極大点を探していく感じらしいです。わかりやすい解説があったので、リンクを張っておきます(ただし私自身はすべては読み込めていない)。Mean Shift Clustering このMeanShiftはsklearnに実装されているので、簡単に試してみることができます。 sklearn.cluster.MeanShift — scikit-learn 0.20.1 documentation sklearnのトイデータで遊んでみましょう。 目次 はじめに … <div class="section"> <h3 id="はじめに">はじめに</h3> <p> MeanShiftはクラスタリングアルゴリズム。クラスタ数を自動で決定してくれるという長所がある。</p><p> 理論的には最急降下法で各クラスタの極大点を探していく感じらしいです。わかりやすい解説があったので、リンクを張っておきます(ただし私自身はすべては読み込めていない)。</p><p><a href="http://takashiijiri.com/study/ImgProc/MeanShift.htm">Mean Shift Clustering</a></p><p> このMeanShiftはsklearnに実装されているので、簡単に試してみることができます。</p><p> <a href="http://scikit-learn.org/stable/modules/generated/sklearn.cluster.MeanShift.html">sklearn.cluster.MeanShift &mdash; scikit-learn 0.20.1 documentation</a></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><ul> <li><a href="#プログラム">プログラム</a></li> <li><a href="#結果">結果</a></li> </ul> </li> <li><a href="#結論">結論</a></li> </ul> </div> <div class="section"> <h3 id="使い方">使い方</h3> <p> いつものsklearnのモデルです。fitしてpredictするだけ。</p><p> いつだったか<a href="https://www.haya-programming.com/entry/2018/03/03/202558">Fuzzy C-Means&#x3092;&#x3084;&#x3063;&#x305F;&#x3068;&#x304D;</a>は苦労しましたが、とりあえずそんな心配は要りません。</p><p> となると気になるのはパラメータですが、指定しなくても</p> <ul> <li>bandwidth</li> </ul><p> 勝手に推定される</p> <ul> <li>seeds</li> </ul><p> 乱数のシードなので勝手に決まる。指定するときは[n_samples, n_features]が必要。</p> <ul> <li>bin_seeding</li> </ul><p> よくわからないけど、初期値の選び方みたいな。上のと関係がありそう。Trueにすると速くなるらしい。デフォルトのFalseの方がアルゴリズムとしては厳密なはず(よくわからんけど)。</p> <ul> <li>min_bin_freq</li> </ul><p> これも上のと関係がありそう。わかるようなわからないような感じ。</p> <ul> <li>cluster_all</li> </ul><p> すべての点をクラスタリングするかどうか。default=Trueなので敢えてFalseにする理由は・・・(高速化なんだろうな)。</p> <ul> <li>n_jobs : int</li> </ul><p> いつもの並列化数</p><p> 本質的な挙動に関わるのはbandwidthで、あとは高速化のために計算を端折るための引数がいっぱいあるだけっぽいです。</p><p> そしてbandwidthも勝手に推定してくれるので、敢えて指定する必要性を感じません(推定の良し悪しがどうかという議論はありますが)。</p><p> 今回はn_jobs以外すべてデフォルトでやってみます。</p> </div> <div class="section"> <h3 id="実験">実験</h3> <p> iris, wineで見てみる。せっかくなのでK-Meansと比較してみます。ついでに、入力をスケーリングすると結果が変わるかも見ます。</p> <div class="section"> <h4 id="プログラム">プログラム</h4> <p> いろいろ手抜きをしています。が、とにかく結果は出ます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris, load_wine <span class="synPreProc">from</span> sklearn.cluster <span class="synPreProc">import</span> MeanShift, KMeans <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.pipeline <span class="synPreProc">import</span> Pipeline <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): iris = load_iris() wine = load_wine() kmeans = KMeans(n_clusters=<span class="synConstant">3</span>, n_jobs=-<span class="synConstant">1</span>) mean_shift = MeanShift(n_jobs=-<span class="synConstant">1</span>) s_kmeans = Pipeline([(<span class="synConstant">&quot;scaler&quot;</span>, StandardScaler()), (<span class="synConstant">&quot;kmeans&quot;</span>, KMeans(n_clusters=<span class="synConstant">3</span>, n_jobs=-<span class="synConstant">1</span>))]) s_mean_shift = Pipeline([(<span class="synConstant">&quot;scaler&quot;</span>, StandardScaler()), (<span class="synConstant">&quot;meanshift&quot;</span>, MeanShift(n_jobs=-<span class="synConstant">1</span>))]) <span class="synComment"># iris and wine</span> pca = PCA(n_components=<span class="synConstant">2</span>) <span class="synStatement">for</span> dataset, dataset_name <span class="synStatement">in</span> <span class="synIdentifier">zip</span>([iris, wine], [<span class="synConstant">&quot;iris&quot;</span>, <span class="synConstant">&quot;wine&quot;</span>]): fig, axes = plt.subplots(nrows=<span class="synConstant">2</span>, ncols=<span class="synConstant">3</span>, figsize=(<span class="synConstant">20</span>,<span class="synConstant">8</span>)) axes = axes.ravel() PCA_X = pca.fit_transform(dataset.data) origin_y = dataset.target km_y = kmeans.fit_predict(dataset.data) ms_y = mean_shift.fit_predict(dataset.data) s_km_y = s_kmeans.fit_predict(dataset.data) s_ms_y = s_mean_shift.fit_predict(dataset.data) n_clusters = [<span class="synConstant">3</span>, <span class="synConstant">3</span>, mean_shift.cluster_centers_.shape[<span class="synConstant">0</span>], <span class="synConstant">3</span>, s_mean_shift.named_steps.meanshift.cluster_centers_.shape[<span class="synConstant">0</span>]] <span class="synStatement">for</span> i, (y, name, n_cluster) <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>( <span class="synIdentifier">zip</span>([origin_y, km_y, ms_y, s_km_y, s_ms_y], [<span class="synConstant">&quot;original&quot;</span>, <span class="synConstant">&quot;k-means&quot;</span>, <span class="synConstant">&quot;mean-shift&quot;</span>, <span class="synConstant">&quot;scaling+k-means&quot;</span>, <span class="synConstant">&quot;scaling+mean-shift&quot;</span>], n_clusters)): <span class="synStatement">for</span> target <span class="synStatement">in</span> <span class="synIdentifier">range</span>(n_cluster): axes[i].scatter(PCA_X[y==target, <span class="synConstant">0</span>], PCA_X[y==target, <span class="synConstant">1</span>], c=<span class="synConstant">&quot;rgb&quot;</span>[target]) axes[i].set_title(<span class="synConstant">&quot;{0} n_cluster:{1}&quot;</span>.<span class="synIdentifier">format</span>(name, n_cluster)) plt.savefig(<span class="synConstant">&quot;{0}.png&quot;</span>.<span class="synIdentifier">format</span>(dataset_name)) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre> </div> <div class="section"> <h4 id="結果">結果</h4> <p> まずirisの結果から。</p><p><figure class="figure-image figure-image-fotolife" title="iris.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180527/20180527181319.png" alt="iris.png" title="f:id:hayataka2049:20180527181319p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>iris.png</figcaption></figure></p><p> MeanShiftは2クラスタと解釈しているようです。本来のデータとは異なりますが、敢えて人の目で見ると妥当な結果な気もします。この場合、スケーリングによる変化は微々たるものです。</p><p> 次に、wineの方。</p><p><figure class="figure-image figure-image-fotolife" title="wine.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180527/20180527181344.png" alt="wine.png" title="f:id:hayataka2049:20180527181344p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>wine.png</figcaption></figure></p><p> 一見するとoriginalが悪すぎるように見えますが、PCAでむりやり二次元に写像しているためです。クラスタリング自体は写像前のオリジナルの空間で行っているため、影響はありません。</p><p> この場合、合格と言って良いのは入力をスケーリングしたKMeansだけで、あとはダメダメです。特徴量の次元数が大きいから、うまく動いていないのでしょうか。ちょっと謎。</p> </div> </div> <div class="section"> <h3 id="結論">結論</h3> <p> 良いか悪いかの判断はつきかねますが、できることはわかりました。</p><p> たぶんbandwidthを変えるとコロコロ結果が変わるのでしょう。どんな感じで変わるのかは、今後気が向いたときに検証しようと思います。</p><p> →やりました。<br /> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2018%2F05%2F28%2F221819" title="【python】MeanShiftのbandwidthを変えるとどうなるか実験してみた - 静かなる名辞" 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/2018/05/28/221819">www.haya-programming.com</a></cite></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 &mdash; 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">&quot;pca&quot;</span>, pca), (<span class="synConstant">&quot;gnb&quot;</span>, gnb)]) lda_gnb = Pipeline([(<span class="synConstant">&quot;lda&quot;</span>, lda), (<span class="synConstant">&quot;gnb&quot;</span>, gnb)]) pca_lda_gnb = Pipeline([(<span class="synConstant">&quot;reduction&quot;</span>, FeatureUnion([(<span class="synConstant">&quot;pca&quot;</span>, pca), (<span class="synConstant">&quot;lda&quot;</span>, lda)])), (<span class="synConstant">&quot;gnb&quot;</span>, gnb)]) scoring = {<span class="synConstant">&quot;p&quot;</span>: <span class="synConstant">&quot;precision_macro&quot;</span>, <span class="synConstant">&quot;r&quot;</span>: <span class="synConstant">&quot;recall_macro&quot;</span>, <span class="synConstant">&quot;f&quot;</span>:<span class="synConstant">&quot;f1_macro&quot;</span>} <span class="synStatement">for</span> name, model <span class="synStatement">in</span> <span class="synIdentifier">zip</span>([<span class="synConstant">&quot;pca_gnb&quot;</span>, <span class="synConstant">&quot;lda_gnb&quot;</span>, <span class="synConstant">&quot;pca_lda_gnb&quot;</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">&quot;test_p&quot;</span>].mean() r = scores[<span class="synConstant">&quot;test_r&quot;</span>].mean() f = scores[<span class="synConstant">&quot;test_f&quot;</span>].mean() <span class="synIdentifier">print</span>(name) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;precision:{0:.3f} recall:{1:.3f} f1:{2:.3f}&quot;</span>.<span class="synIdentifier">format</span>(p,r,f)) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</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 複数の目的変数で回帰を行う方法 hatenablog://entry/17391345971642259530 2018-05-07T11:51:44+09:00 2019-06-16T20:52:16+09:00 はじめに 回帰分析を行う際、複数の目的変数に対して回帰をしたい場合があります。普通のモデルではできないのでちょっと面食らいますが、やり方は色々あるようです。 目次 はじめに 目的変数の数だけ回帰モデルを作る方法 複数の目的変数に対応したモデルを使う 正準相関分析 ランダムフォレスト回帰 多層パーセプトロン(ニューラルネットワーク回帰) まとめ スポンサーリンク (adsbygoogle = window.adsbygoogle || []).push({}); 目的変数の数だけ回帰モデルを作る方法 単純に考えると、一つの目的変数を出力する回帰モデルを目的変数の数だけ用意してやれば、所要を達しそ… <div class="section"> <h3 id="はじめに">はじめに</h3> <p> 回帰分析を行う際、複数の目的変数に対して回帰をしたい場合があります。普通のモデルではできないのでちょっと面食らいますが、やり方は色々あるようです。</p><p> 目次</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#目的変数の数だけ回帰モデルを作る方法">目的変数の数だけ回帰モデルを作る方法</a></li> <li><a href="#複数の目的変数に対応したモデルを使う">複数の目的変数に対応したモデルを使う</a><ul> <li><a href="#正準相関分析">正準相関分析</a></li> <li><a href="#ランダムフォレスト回帰">ランダムフォレスト回帰</a></li> <li><a href="#多層パーセプトロンニューラルネットワーク回帰">多層パーセプトロン(ニューラルネットワーク回帰)</a></li> </ul> </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><p></p> </div> <div class="section"> <h3 id="目的変数の数だけ回帰モデルを作る方法">目的変数の数だけ回帰モデルを作る方法</h3> <p> 単純に考えると、一つの目的変数を出力する回帰モデルを目的変数の数だけ用意してやれば、所要を達しそうです。</p><p> python+sklearnを使えば、これに対応したモデルが最初から用意されています。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.multioutput.MultiOutputRegressor.html">sklearn.multioutput.MultiOutputRegressor &mdash; scikit-learn 0.20.2 documentation</a></p><p> コンストラクタには好きな回帰モデルを渡してあげることができます。それが目的変数の数だけコピーされ、内部で束ねられて回帰に使われます<a href="#f-037c7862" name="fn-037c7862" title="ということだと思う・・実装読んでいないので断言しかねます">*1</a>。</p> </div> <div class="section"> <h3 id="複数の目的変数に対応したモデルを使う">複数の目的変数に対応したモデルを使う</h3> <p> 上の方法は単純ですが、回帰モデルの中には自然に複数の出力に対応しているものもあります。</p><p> そういったモデルを使うことにどんなメリットがあるのか? というと、まず目的変数の数だけ回帰モデルを作るのと比べて無駄が減るので、計算コストがケチれる可能性があります(あくまでも「可能性」の話)。</p><p> また、複数存在する目的変数の間に何らかの相関性があれば、それも踏まえて上手く学習することでモデルの性能が上がる可能性があります(こちらもあくまでも「可能性」)。</p><p> そういった複数の目的変数に対応したモデルを幾つか紹介します。すべては網羅しきれないので、その点はご承知ください。</p> <div class="section"> <h4 id="正準相関分析">正準相関分析</h4> <p> 正準相関分析はこの手の話で出てくる代表的なモデルです。単純な手法ですが、けっこう奥深いといえば奥深いです。</p><p> 参考(過去に書いた記事):<a href="https://www.haya-programming.com/entry/2018/02/16/021314">&#x3010;python&#x3011;&#x6B63;&#x6E96;&#x76F8;&#x95A2;&#x5206;&#x6790;&#xFF08;Canonical Correlation Analysis&#xFF09;&#x3092;&#x8A66;&#x3057;&#x3066;&#x307F;&#x308B; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><p> これの良いところは、説明変数と目的変数<a href="#f-8aea2c2e" name="fn-8aea2c2e" title="回帰の記事なのでそう呼ぶが、ぶっちゃけ妥当ではない。CCAの枠組みではどっちがどっちでも大して構わないのだし">*2</a>のそれぞれでPCAみたく新たな軸を張り、次元削減を行ってくれることです。説明変数ン百次元、目的変数20次元みたいなケースだったとしても、次元削減の効果で「わかりやすい」結果が得られる可能性があります。つまり、現象を説明するモデルとしては非常に適しています。</p><p> 欠点は、回帰モデルとして考えると性能が高いと言えるかは微妙なこと、非線形への対応は基本的にはないことです。カーネルPCAみたくカーネル法で非線形対応させたモデルもありますが、良さげなライブラリが見当たらないのと、そこまでするなら他の手法を使いたいという気持ちがあるので紹介しません。</p><p>  sklearnのモデルはこれです。上に書いた通りカーネル正準相関の実装はありません。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.cross_decomposition.CCA.html">sklearn.cross_decomposition.CCA &mdash; scikit-learn 0.20.2 documentation</a></p><p> predictメソッドでXからYを予測できるので、普通に回帰に使えます。 </p><p> 入出力が割と線形なデータで、「説明」を重視したいときは使えると思います。</p> </div> <div class="section"> <h4 id="ランダムフォレスト回帰">ランダムフォレスト回帰</h4> <p> なぜかランダムフォレスト回帰は複数出力に対応しています。<a href="http://bada.hb.se/bitstream/2320/12407/1/2013MAGI04.pdf">&#x89E3;&#x8AAC;&#x8AD6;&#x6587;</a>を見つけたので貼っておきます。興味のある方はどうぞ(私は読んでいません)。</p><p> とにかく使いたければsklearnのRandomForestRegressorはそのまま使えます。目的変数も説明変数と同様に配列で入れてあげてください。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestRegressor.html">3.2.4.3.2. sklearn.ensemble.RandomForestRegressor &mdash; scikit-learn 0.20.2 documentation</a></p><p></p> </div> <div class="section"> <h4 id="多層パーセプトロンニューラルネットワーク回帰">多層パーセプトロン(ニューラルネットワーク回帰)</h4> <p> ニューラルネットですからできて当然。複数出力にするためにやることといったら出力層ユニット数を増やすだけですから、一番シンプルかもしれません。これもsklearnのがそのまま使えます。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.neural_network.MLPRegressor.html">sklearn.neural_network.MLPRegressor &mdash; scikit-learn 0.20.2 documentation</a><br /> </p> </div> </div> <div class="section"> <h3 id="まとめ">まとめ</h3> <p> 複数の目的変数に対して回帰を行う場合について、2種類の方法を説明しました。</p> <ul> <li>単純に目的変数の数だけ回帰モデルを用意する方法</li> <li>複数の目的変数を出力できるモデルを用いる方法</li> </ul><p> どちらが良いかは一概には言えません。データや目的に応じて、あるいは実際に走らせてみて評価指標や計算コストを勘案して考える必要があります。複数の目的変数に最初から対応したモデルの方が良いような気もしますが、<a href="http://scikit-learn.org/stable/auto_examples/ensemble/plot_random_forest_regression_multioutput.html">&#x305D;&#x3046;&#x3068;&#x3082;&#x8A00;&#x3048;&#x306A;&#x3044;&#x3093;&#x3058;&#x3083;&#x3068;&#x3044;&#x3046;&#x8A71;</a>もあったりします。</p><p> でもまあ、色々な選択肢があることは良いことです。いろいろ勘案して選べば良いでしょう。適当ですがこんな感じでシメます。</p> </div><div class="footnote"> <p class="footnote"><a href="#fn-037c7862" name="f-037c7862" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">ということだと思う・・実装読んでいないので断言しかねます</span></p> <p class="footnote"><a href="#fn-8aea2c2e" name="f-8aea2c2e" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">回帰の記事なのでそう呼ぶが、ぶっちゃけ妥当ではない。CCAの枠組みではどっちがどっちでも大して構わないのだし</span></p> </div> 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">&#x81EA;&#x5DF1;&#x7D44;&#x7E54;&#x5316;&#x5199;&#x50CF; - 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">&#x5B50;&#x4F9B;&#x3067;&#x3082;&#x308F;&#x304B;&#x308B;&#x300C;&#x81EA;&#x5DF1;&#x7D44;&#x7E54;&#x5316;&#x30DE;&#x30C3;&#x30D7;&#x300D;</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">&#x81EA;&#x5DF1;&#x7D44;&#x7E54;&#x5316;&#x30DE;&#x30C3;&#x30D7;(SOM)&#x306E;Python&#x30E9;&#x30A4;&#x30D6;&#x30E9;&#x30EA;sompy&#x3092;&#x516C;&#x958B;&#x3057;&#x307E;&#x3057;&#x305F; - &#x4FFA;&#x3068;&#x30D7;&#x30ED;&#x30B0;&#x30E9;&#x30DF;&#x30F3;&#x30B0;</a><br /> </p> <ul> <li>PyMVPA </li> </ul><p> 多変量解析のためのそれなりに大きいライブラリで、SOMも実装されている。これが使えればよかったのだと思うが、python2系のサポートしかないので没・・・。<br /> <a href="http://www.pymvpa.org/examples/som.html">Self-organizing Maps &mdash; 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 &mdash; 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">&quot;pca&quot;</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">&quot;umatrix.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</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&#x306B;&#x3088;&#x308B;iris&#x306E;&#x53EF;&#x8996;&#x5316;&#x7D50;&#x679C;" 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">&quot;loading data&quot;</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">&quot;tsne&quot;</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">&quot;tsne_digits.png&quot;</span>) <span class="synComment"># somで可視化</span> <span class="synIdentifier">print</span>(<span class="synConstant">&quot;som&quot;</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">&quot;pca&quot;</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">&quot;som_digits.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</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&#x3067;&#x53EF;&#x8996;&#x5316;&#x3057;&#x305F;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&#x3067;&#x53EF;&#x8996;&#x5316;&#x3057;&#x305F;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のPCAでloading(主成分負荷量)を計算する hatenablog://entry/17391345971630888436 2018-03-31T01:24:28+09:00 2019-06-22T14:12:00+09:00 PCA(主成分分析)のloading*1がほしいときがあります。 sklearnでは一発では出ません。 ドキュメントはここ。 sklearn.decomposition.PCA — scikit-learn 0.21.2 documentation 目次 PCA.components_は確かにあるけど・・・ loadingを計算しよう 罠だった 共分散行列のときはどうするのか loadingを使うと何が良いのか PCA.components_は確かにあるけど・・・ 結論から先に言うと、PCA.components_はノルム1の固有ベクトルです。ノルムを測ってみましょう。 >>> import … <p> PCA(主成分分析)のloading<a href="#f-2ca0a7c0" name="fn-2ca0a7c0" title="主成分負荷量、あるいは因子負荷量とも(なぜか)言われますが、この記事ではloadingで通します。けっきょくヘタに和訳しないのがいちばんわかりやすい">*1</a>がほしいときがあります。</p><p> sklearnでは一発では出ません。</p><p> ドキュメントはここ。<br /> <a href="http://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html">sklearn.decomposition.PCA &mdash; scikit-learn 0.21.2 documentation</a></p><p> 目次</p> <ul class="table-of-contents"> <li><a href="#PCAcomponents_は確かにあるけど">PCA.components_は確かにあるけど・・・</a></li> <li><a href="#loadingを計算しよう">loadingを計算しよう</a></li> <li><a href="#罠だった">罠だった</a></li> <li><a href="#共分散行列のときはどうするのか">共分散行列のときはどうするのか</a></li> <li><a href="#loadingを使うと何が良いのか">loadingを使うと何が良いのか</a></li> </ul> <div class="section"> <h3 id="PCAcomponents_は確かにあるけど">PCA.components_は確かにあるけど・・・</h3> <p> 結論から先に言うと、PCA.components_はノルム1の固有ベクトルです。ノルムを測ってみましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np &gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris &gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA &gt;&gt;&gt; iris = load_iris() &gt;&gt;&gt; pca = PCA(n_components=<span class="synConstant">2</span>) &gt;&gt;&gt; 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>) &gt;&gt;&gt; pca.components_ array([[ <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>]]) &gt;&gt;&gt; np.linalg.norm(pca.components_, axis=<span class="synConstant">1</span>) array([<span class="synConstant">1.</span>, <span class="synConstant">1.</span>]) </pre><p> まあ、loadingも固有ベクトルには違いないのですが、ノルムを整えてやる必要があります。</p> </div> <div class="section"> <h3 id="loadingを計算しよう">loadingを計算しよう</h3> <p> 教科書などによく書いてあることですが、第<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20i" alt=" i"/>主成分に対応する元の変数のloadingは次の式で出せます。<br /> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20loading%20%3D%20%5Csqrt%28%5Clambda_i%29%20eigenvector" alt=" loading = \sqrt(\lambda_i) eigenvector"/><br />  <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Clambda_i" alt=" \lambda_i"/>は固有値。 eigenvectorは固有ベクトルで、元の変数の数だけ次元がありますから、これで良いわけです(雑な説明ですが・・・)。</p><p> ということで、pythonで同様にやってみましょう。固有値はexplained_varianceに入っています。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; pca.components_*np.c_[np.sqrt(pca.explained_variance_)] <span class="synComment"># 縦ベクトルに変換する必要がある</span> array([[ <span class="synConstant">0.74322652</span>, -<span class="synConstant">0.16909891</span>, <span class="synConstant">1.76063406</span>, <span class="synConstant">0.73758279</span>], [ <span class="synConstant">0.32313741</span>, <span class="synConstant">0.35915163</span>, -<span class="synConstant">0.08650963</span>, -<span class="synConstant">0.03676921</span>]]) </pre><p> できました。これがloadingです。・・・と思ったけど、1を超えちゃってますね。なんてこった。</p> </div> <div class="section"> <h3 id="罠だった">罠だった</h3> <p> 固有値は分散なので、データのスケールに依存します。</p><p> とりあえず入力をスケーリングしてみよう。上の式は相関行列から行くときのものでした。なのでこれで平気なはず。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.preprocessing <span class="synPreProc">import</span> StandardScaler <span class="synStatement">as</span> SS &gt;&gt;&gt; ss = SS() &gt;&gt;&gt; data = ss.fit_transform(iris.data) &gt;&gt;&gt; pca.fit(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>) &gt;&gt;&gt; pca.components_*np.c_[np.sqrt(pca.explained_variance_)] array([[ <span class="synConstant">0.89421016</span>, -<span class="synConstant">0.45081822</span>, <span class="synConstant">0.99500666</span>, <span class="synConstant">0.96822861</span>], [ <span class="synConstant">0.35854928</span>, <span class="synConstant">0.89132754</span>, <span class="synConstant">0.02031465</span>, <span class="synConstant">0.06299656</span>]]) </pre><p> 1を超えなくなくてめでたし、ということよりも、数字が変わったことの方が問題で、これで本当に正しいのかという疑念が生じてきました。</p><p> 確認のために元の特徴と主成分の相関係数を直接測ってみます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; X = pca.fit_transform(data) &gt;&gt;&gt; np.corrcoef(np.hstack([iris.data, X]), rowvar=<span class="synIdentifier">False</span>) array([[ <span class="synConstant">1.00000000e+00</span>, -<span class="synConstant">1.09369250e-01</span>, <span class="synConstant">8.71754157e-01</span>, <span class="synConstant">8.17953633e-01</span>, <span class="synConstant">8.91224479e-01</span>, <span class="synConstant">3.57352114e-01</span>], [-<span class="synConstant">1.09369250e-01</span>, <span class="synConstant">1.00000000e+00</span>, -<span class="synConstant">4.20516096e-01</span>, -<span class="synConstant">3.56544090e-01</span>, -<span class="synConstant">4.49312976e-01</span>, <span class="synConstant">8.88351481e-01</span>], [ <span class="synConstant">8.71754157e-01</span>, -<span class="synConstant">4.20516096e-01</span>, <span class="synConstant">1.00000000e+00</span>, <span class="synConstant">9.62757097e-01</span>, <span class="synConstant">9.91684422e-01</span>, <span class="synConstant">2.02468206e-02</span>], [ <span class="synConstant">8.17953633e-01</span>, -<span class="synConstant">3.56544090e-01</span>, <span class="synConstant">9.62757097e-01</span>, <span class="synConstant">1.00000000e+00</span>, <span class="synConstant">9.64995787e-01</span>, <span class="synConstant">6.27862218e-02</span>], [ <span class="synConstant">8.91224479e-01</span>, -<span class="synConstant">4.49312976e-01</span>, <span class="synConstant">9.91684422e-01</span>, <span class="synConstant">9.64995787e-01</span>, <span class="synConstant">1.00000000e+00</span>, <span class="synConstant">2.08904471e-17</span>], [ <span class="synConstant">3.57352114e-01</span>, <span class="synConstant">8.88351481e-01</span>, <span class="synConstant">2.02468206e-02</span>, <span class="synConstant">6.27862218e-02</span>, <span class="synConstant">2.08904471e-17</span>, <span class="synConstant">1.00000000e+00</span>]]) </pre><p>  下の二行の4列目までを見てください。微妙に誤差があるような気はしますが(小数点以下3桁でずれてきてるので微妙ってほど微妙でもないが)、たぶん同じ数字になっています。</p><p> 微妙な誤差については、丸め誤差などが累積した、実は計算間違ってる、といった理由が考えられます。前者ならまだ許せるけど、後者はやだな・・・。</p> </div> <div class="section"> <h3 id="共分散行列のときはどうするのか">共分散行列のときはどうするのか</h3> <p> どうするんだろうね・・・。</p><p> 2019/06/22追記<br />  手順は増えますが、スケールを考慮すれば同様に行えるようです。</p> <blockquote> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190622/20190622141121.png" alt="&#x8CA0;&#x8377;&#x91CF;" title="f:id:hayataka2049:20190622141121p:plain" class="hatena-fotolife" itemprop="image"></span></p><p>出典:<a href="http://manabukano.brilliant-future.net/document/text-PCA.pdf">http://manabukano.brilliant-future.net/document/text-PCA.pdf</a> p.10</p> </blockquote> </div> <div class="section"> <h3 id="loadingを使うと何が良いのか">loadingを使うと何が良いのか</h3> <p> 相関係数なので、「どれくらい効いてるか」がよくわかります。よくある0.3以下なら~とか0.7以上なら~という論法ができます。それだけといえばそれだけ。</p><p> このように取扱が大変なので、固有ベクトルのまま解釈した方が楽かもという気がしてきました。各主成分の寄与率はexplained_variance_ratio_で得られる訳だし、寄与率の大きい軸の固有ベクトルの大きい次元を見ればどんな感じかはわかるし・・・。</p><p> でもまあ、一応(入力をスケーリングすれば)大体出るということはわかったので、これでよしとします。</p><p> 共分散行列でやるときのやり方は、どなたか詳しい方に教えて頂けると助かります。</p> </div><div class="footnote"> <p class="footnote"><a href="#fn-2ca0a7c0" name="f-2ca0a7c0" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">主成分負荷量、あるいは因子負荷量とも(なぜか)言われますが、この記事ではloadingで通します。けっきょくヘタに和訳しないのがいちばんわかりやすい</span></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 &mdash; 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">&quot;scaler&quot;</span>, ss), (<span class="synConstant">&quot;pca&quot;</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">&quot;pca_plt.png&quot;</span>) decomp_and_plot(iris, pl, <span class="synConstant">&quot;spca_plt.png&quot;</span>) decomp_and_plot(iris, fa, <span class="synConstant">&quot;fa_plt.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</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">&quot;pca&quot;</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">&quot;fa&quot;</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 &mdash; scikit-learn 0.22.1 documentation</a></p><p> PCA.components_が固有ベクトルであり、データをセンタリングしてこれと掛けるとPCAの出力が出てくることは<a href="https://www.haya-programming.com/entry/2018/03/28/222101">&#x524D;&#x56DE;&#x306E;&#x8A18;&#x4E8B;</a>で確認しました。</p><p> 固有ベクトル行列が主成分*元のデータの特徴という形になっているとして、横に見ると負荷量(みたいなもの。本当は対応する固有値のsqrtを掛け算してやらないといけない)に、縦に見ると元の写像先で表現された特徴の軸になります。</p><p> つまり、その軸をプロットするだけです。</p><p> なお、この辺は微妙に議論があるようです。私もこれがどこまで正しい方法なのかは自信が持てません。</p><p> 参考:<br /> <a href="https://cis-jp.blogspot.jp/2012/09/blog-post_9.html">&#x8272;&#x3005;&#x3068;&#x8003;&#x3048;&#x3066;&#x307F;&#x308B;: &#x6587;&#x7CFB;&#x306E;&#x305F;&#x3081;&#x306E;&#x300C;&#x4E3B;&#x6210;&#x5206;&#x5206;&#x6790;&#x306E;&#x53EF;&#x8996;&#x5316;&#x300D;&#xFF08;&#xFF12;&#xFF09;</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">&quot;feature_names&quot;</span>): feature_names = <span class="synIdentifier">list</span>(dataset.feature_names) <span class="synStatement">else</span>: feature_names = [<span class="synConstant">&quot;F{0}&quot;</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">&quot;.&quot;</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">&quot;__main__&quot;</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&#x306E;&#x30D0;&#x30A4;&#x30D7;&#x30ED;&#x30C3;&#x30C8;" 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&#x306E;&#x30D0;&#x30A4;&#x30D7;&#x30ED;&#x30C3;&#x30C8;" 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">&quot;&quot;&quot;横着してfit_transformしか実装してない</span> <span class="synConstant"> &quot;&quot;&quot;</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">&quot;sklearn_resut.png&quot;</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">&quot;my_result.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</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&#x306E;PCA&#x3067;iris&#x3092;&#x53EF;&#x8996;&#x5316;" 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="&#x81EA;&#x4F5C;PCA&#x3067;iris&#x3092;&#x53EF;&#x8996;&#x5316;" 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 &mdash; 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">&quot;rbf&quot;</span>, n_components=<span class="synConstant">3</span>) lda = LDA() pl_pca = Pipeline([(<span class="synConstant">&quot;pca&quot;</span>, pca), (<span class="synConstant">&quot;lda&quot;</span>, lda)]) pl_kpca = Pipeline([(<span class="synConstant">&quot;kpca&quot;</span>, kpca), (<span class="synConstant">&quot;lda&quot;</span>, lda)]) parameters = {<span class="synConstant">&quot;kpca__gamma&quot;</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">&quot;f1_macro&quot;</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">&quot;f1_macro&quot;</span>).mean()) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</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 &mdash; 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 &middot; Issue #2689 &middot; scikit-learn/scikit-learn &middot; 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>&gt;&gt;&gt; <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np &gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris &gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.preprocessing <span class="synPreProc">import</span> StandardScaler &gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA &gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.pipeline <span class="synPreProc">import</span> Pipeline &gt;&gt;&gt; iris = load_iris() &gt;&gt;&gt; pca = PCA(n_components=<span class="synConstant">2</span>) &gt;&gt;&gt; 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>) &gt;&gt;&gt; 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>]]) &gt;&gt;&gt; ss = StandardScaler() &gt;&gt;&gt; p = Pipeline([(<span class="synConstant">&quot;scaler&quot;</span>, ss), (<span class="synConstant">&quot;pca&quot;</span>, pca)]) &gt;&gt;&gt; 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>))]) &gt;&gt;&gt; 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>]]) &gt;&gt;&gt; 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>&gt;&gt;&gt; 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>]]) &gt;&gt;&gt; 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">&quot;scaler&quot;</span>, ss), (<span class="synConstant">&quot;pca&quot;</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">&quot;iris_cov_pca.png&quot;</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">&quot;iris_corr_pca.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</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="&#x5171;&#x5206;&#x6563;&#x884C;&#x5217;&#x3067;&#x4E3B;&#x6210;&#x5206;&#x5206;&#x6790;&#x3057;&#x305F;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="&#x76F8;&#x95A2;&#x884C;&#x5217;&#x3067;&#x4E3B;&#x6210;&#x5206;&#x5206;&#x6790;&#x3057;&#x305F;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">&#x524D;&#x56DE;</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">&#x3010;python&#x3011;LDA&#xFF08;&#x7DDA;&#x5F62;&#x5224;&#x5225;&#x5206;&#x6790;&#xFF09;&#x3067;&#x6B21;&#x5143;&#x524A;&#x6E1B; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</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">&quot;english&quot;</span>) matrix = cv.fit_transform(news20.data).toarray() pca = PCA(n_components=<span class="synConstant">1000</span>, svd_solver=<span class="synConstant">&quot;randomized&quot;</span>) lda = LDA() rfc = RFC(n_estimators=<span class="synConstant">1000</span>, n_jobs=-<span class="synConstant">1</span>) clf = Pipeline([(<span class="synConstant">&quot;pca&quot;</span>, pca), (<span class="synConstant">&quot;lda&quot;</span>, lda), (<span class="synConstant">&quot;rfc&quot;</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">&quot;macro&quot;</span>)[:<span class="synConstant">3</span>] <span class="synIdentifier">print</span>(<span class="synConstant">&quot;p:{0:.6f} r:{1:.6f} f1:{2:.6f}&quot;</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">&quot;__main__&quot;</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">&#x3010;python&#x3011;sklearn&#x306E;fetch_20newsgroups&#x3067;&#x6587;&#x66F8;&#x5206;&#x985E;&#x3092;&#x8A66;&#x3059;(1) - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a><br /> <a href="https://www.haya-programming.com/entry/2018/02/20/215416">&#x3010;python&#x3011;sklearn&#x306E;fetch_20newsgroups&#x3067;&#x6587;&#x66F8;&#x5206;&#x985E;&#x3092;&#x8A66;&#x3059;(2) - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a><br /> <a href="https://www.haya-programming.com/entry/2018/02/22/060148">&#x3010;python&#x3011;sklearn&#x306E;fetch_20newsgroups&#x3067;&#x6587;&#x66F8;&#x5206;&#x985E;&#x3092;&#x8A66;&#x3059;(3) - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p> </div> hayataka2049 【python】matplotlibで3次元データを描画し、回転アニメーションにする hatenablog://entry/17391345971628992658 2018-03-24T22:16:53+09:00 2019-06-16T20:52:16+09:00 3次元くらいのデータを描画したいときがある。簡単に散布図にできると便利。 データの用意 sklearnのload_irisなどで取得できるデータセットを入力にする前提の次のような関数を作った。 from sklearn.decomposition import PCA def gen_3d_data(dataset): pca = PCA(n_components=3) return pca.fit_transform(dataset.data), dataset.target あとはirisなり何なりを入れてやる。スポンサーリンク (adsbygoogle = window.adsbygoo… <p> 3次元くらいのデータを描画したいときがある。簡単に散布図にできると便利。</p> <div class="section"> <h3>データの用意</h3> <p> sklearnのload_irisなどで取得できるデータセットを入力にする前提の次のような関数を作った。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA <span class="synStatement">def</span> <span class="synIdentifier">gen_3d_data</span>(dataset): pca = PCA(n_components=<span class="synConstant">3</span>) <span class="synStatement">return</span> pca.fit_transform(dataset.data), dataset.target </pre><p> あとはirisなり何なりを入れてやる。</p><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><p></p> </div> <div class="section"> <h3>3次元プロット</h3> <p> とりあえずプロットしたいだけならこれだけ。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> mpl_toolkits.mplot3d <span class="synPreProc">import</span> axes3d <span class="synStatement">def</span> <span class="synIdentifier">matplotlib_plt</span>(X, y, filename): fig = plt.figure() ax = fig.add_subplot(<span class="synConstant">111</span>, projection=<span class="synConstant">&quot;3d&quot;</span>) ax.scatter(X[:,<span class="synConstant">0</span>], X[:,<span class="synConstant">1</span>], X[:,<span class="synConstant">2</span>], c=y/<span class="synIdentifier">len</span>(<span class="synIdentifier">set</span>(y))) plt.savefig(filename) plt.show() </pre><p> 点の色の指定がちょっとセコいが、まあ良いこととする。axes3dはパっと見使ってないように見えるが、importしないとエラーになるので必要と思われる。</p><p> 呼び出し元のmainも書く。</p> <pre class="code lang-python" data-lang="python" data-unlink><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_X, iris_y = gen_3d_data(load_iris()) matplotlib_plt(iris_X, iris_y, <span class="synConstant">&quot;iris.png&quot;</span>) </pre><p> 実行すると次のような画像が出力される。<figure class="figure-image figure-image-fotolife" title="irisの3次元描画"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180324/20180324211630.png" alt="iris&#x306E;3&#x6B21;&#x5143;&#x63CF;&#x753B;" title="f:id:hayataka2049:20180324211630p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>irisの3次元描画</figcaption></figure> あと、ぐりぐり回せるグラフのようなものが別ウィンドウで開く(plt.sow()に対応)。</p> </div> <div class="section"> <h3>回転させたアニメーションを表示</h3> <p> このような例が公式で示されている。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">for</span> angle <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">0</span>, <span class="synConstant">360</span>): ax.view_init(<span class="synConstant">30</span>, angle) plt.draw() plt.pause(<span class="synConstant">.001</span>) </pre><p><a href="https://matplotlib.org/examples/mplot3d/rotate_axes3d_demo.html">mplot3d example code: rotate_axes3d_demo.py &mdash; Matplotlib 2.0.2 documentation</a></p><p> やると確かにぐるぐる回るアニメーションが表示される。こりゃあええわ、ということで、次はこれをgifアニメにすることを考える。</p><p> matplotlibにもanimationというモジュールがあり、色々できるようだが使い方を覚えるのが大変そうだった。なので、「上のforループ内で一枚ずつ画像出力してffmpegで繋げば良いだろ」という手抜きの方針で行くことにする。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">matplotlib_rotate</span>(X, y, dataname): fig = plt.figure() ax = fig.add_subplot(<span class="synConstant">111</span>, projection=<span class="synConstant">&quot;3d&quot;</span>) ax.scatter(X[:,<span class="synConstant">0</span>], X[:,<span class="synConstant">1</span>], X[:,<span class="synConstant">2</span>], c=y/<span class="synIdentifier">len</span>(<span class="synIdentifier">set</span>(y))) <span class="synStatement">for</span> angle <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">0</span>, <span class="synConstant">360</span>): ax.view_init(<span class="synConstant">30</span>, angle) plt.savefig(<span class="synConstant">&quot;figs/{0}_{1:03d}.jpg&quot;</span>.<span class="synIdentifier">format</span>(dataname, angle)) </pre><p> 呼び方は、</p> <pre class="code lang-python" data-lang="python" data-unlink>matplotlib_rotate(iris_X, iris_y, <span class="synConstant">&quot;iris&quot;</span>) </pre><p> こうするとfigs/以下に画像が360枚吐かれるので、ffmpegでつなぐ。</p> <pre class="code" data-lang="" data-unlink>$ ffmpeg -r 10 -i figs/iris_%03d.jpg -pix_fmt rgb24 -f gif out.gif</pre><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/20180324/20180324220744.gif" alt="&#x56DE;&#x308B;iris" title="f:id:hayataka2049:20180324220744g:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>回るiris</figcaption></figure></p><p> 上出来ではないだろうか。</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">&quot;n_components&quot;</span>, <span class="synConstant">&quot;pca-gnn precision&quot;</span>, <span class="synConstant">&quot;pca-gnn recall&quot;</span>, <span class="synConstant">&quot;pca-gnn f1&quot;</span>, <span class="synConstant">&quot;lda-gnn precision&quot;</span>, <span class="synConstant">&quot;lda-gnn recall&quot;</span>, <span class="synConstant">&quot;lda-gnn f1&quot;</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">&quot;pca&quot;</span>, <span class="synConstant">&quot;gnb&quot;</span>], [pca, gnb])) steps2 = <span class="synIdentifier">list</span>(<span class="synIdentifier">zip</span>([<span class="synConstant">&quot;lda&quot;</span>, <span class="synConstant">&quot;gnb&quot;</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">&quot;pca&quot;</span>, <span class="synConstant">&quot;lda&quot;</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">&quot;macro&quot;</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">&quot;n_components&quot;</span>, y=[<span class="synConstant">&quot;pca-gnn f1&quot;</span>, <span class="synConstant">&quot;lda-gnn f1&quot;</span>]) plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre> </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="&#x7D50;&#x679C;&#xFF08;n_components&#x5BFE;F1&#x5024;&#xFF09;" 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>&gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_digits &gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.discriminant_analysis <span class="synPreProc">import</span> LinearDiscriminantAnalysis <span class="synStatement">as</span> LDA &gt;&gt;&gt; lda = LDA(n_components=<span class="synConstant">15</span>) &gt;&gt;&gt; lda.fit(digits.data, digits.target) &gt;&gt;&gt; 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 &mdash; 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">&quot;english&quot;</span>) X = cv.fit_transform(news20.data).toarray() <span class="synIdentifier">print</span>(<span class="synConstant">&quot;min_df:{0} X.shape:{1}&quot;</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">&quot;full&quot;</span>, <span class="synConstant">&quot;arpack&quot;</span>, <span class="synConstant">&quot;randomized&quot;</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">&quot;n_components:{0} solver:{1:&gt;10} &quot;</span><span class="synSpecial">\</span> <span class="synConstant">&quot;time:{2:&gt;6.2f} CP:{3:.4f}&quot;</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">&quot;&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</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=&#39;arpack&#39;</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 sklearnのclassification_reportで多クラス分類の結果を簡単に見る hatenablog://entry/17391345971625009146 2018-03-12T21:35:24+09:00 2019-06-16T20:52:17+09:00 多クラス分類をしていると、「どのクラスが上手く分類できてて、どのクラスが上手く行ってないんだろう」と気になることがままあります。そういった情報を簡単に要約して出力してくれるのがsklearnのclassification_reportで、簡単に使える割に便利なので実験中や開発中に威力を発揮します。 <div class="section"> <h3>はじめに</h3> <p> 多クラス分類をしていると、「どのクラスが上手く分類できてて、どのクラスが上手く行ってないんだろう」と気になることがままあります。</p><p> そういった情報を簡単に要約して出力してくれるのがsklearnのclassification_reportで、簡単に使える割に便利なので実験中や開発中に威力を発揮します。</p><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><p> ※この記事はsklearn 0.19の時代に書きましたが、その後sklearn 0.20で使い方が変更されたので、2019/03/18に全面的に改稿しました。</p> </div> <div class="section"> <h3>使い方</h3> <p> ドキュメントを見るととても簡単そうです。<br /> <a href="http://scikit-learn.org/stable/modules/generated/sklearn.metrics.classification_report.html">sklearn.metrics.classification_report &mdash; scikit-learn 0.20.3 documentation</a></p> <pre class="code lang-python" data-lang="python" data-unlink>sklearn.metrics.classification_report( y_true, y_pred, labels=<span class="synIdentifier">None</span>, target_names=<span class="synIdentifier">None</span>, sample_weight=<span class="synIdentifier">None</span>, digits=<span class="synConstant">2</span>, output_dict=<span class="synIdentifier">False</span>) </pre><p> 要するに真のラベルと予測ラベル、あとラベルに対応する名前を入れてあげればとりあえず使えます。文字列の返り値が出力になります。sample_weight, digitsはそれぞれサンプルの重みと結果に出力される桁数を表しますが、とりあえず入れなくても大した問題は普通はありません。output_dictはsklearn 0.20から追加された引数で、pandasデータフレームに変換可能な辞書を返します。</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_iris <span class="synPreProc">from</span> sklearn.svm <span class="synPreProc">import</span> SVC <span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> classification_report <span class="synPreProc">from</span> sklearn.model_selection <span class="synPreProc">import</span> StratifiedKFold <span class="synStatement">as</span> SKF <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): <span class="synComment"># irisでやる</span> iris = load_iris() <span class="synComment"># svmで分類してみる</span> svm = SVC(C=<span class="synConstant">3</span>, gamma=<span class="synConstant">0.1</span>) <span class="synComment"># 普通の交差検証</span> trues = [] preds = [] <span class="synStatement">for</span> train_index, test_index <span class="synStatement">in</span> SKF().split(iris.data, iris.target): svm.fit(iris.data[train_index], iris.target[train_index]) trues.append(iris.target[test_index]) preds.append(svm.predict(iris.data[test_index])) <span class="synComment"># 今回の記事の話題はここ</span> <span class="synIdentifier">print</span>(<span class="synConstant">&quot;iris&quot;</span>) <span class="synIdentifier">print</span>(classification_report(np.hstack(trues), np.hstack(preds), target_names=iris.target_names)) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> すると、次のような出力が得られます。</p> <pre class="code" data-lang="" data-unlink>iris precision recall f1-score support setosa 1.00 1.00 1.00 50 versicolor 0.96 0.98 0.97 50 virginica 0.98 0.96 0.97 50 micro avg 0.98 0.98 0.98 150 macro avg 0.98 0.98 0.98 150 weighted avg 0.98 0.98 0.98 150 </pre><p> precision, recall, f1-scoreという代表的な評価指標と、support(=y_trueに含まれるデータ数)が、クラスごとと全体の各種平均(後述)で出る、というのが基本的な仕組みです。</p><p> まずクラスごとの結果を見ると、setasoは100%分類できていますが、versicolorとvirginicaはどうも混ざっているようです。以前の記事でirisを二次元にした画像を作ったので、再掲します。</p><p><figure class="figure-image figure-image-fotolife" title="irisをPCAで二次元にしたもの"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180303/20180303195615.png" alt="iris&#x3092;PCA&#x3067;&#x4E8C;&#x6B21;&#x5143;&#x306B;&#x3057;&#x305F;&#x3082;&#x306E;" title="f:id:hayataka2049:20180303195615p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>irisをPCAで二次元にしたもの</figcaption></figure></p><p> RGBの順でsetaso, versicolor, virginicaに対応しているはずです。ということはsetasoが綺麗に分離できてversicolorとvirginicaが混ざるというのは極めて妥当な結果ということになりそうです。</p><p> また、下にあるmicro avg, macro avg, weighted avgは、それぞれマイクロ平均、マクロ平均、サンプル数で重み付けられた平均です。</p><p> 出る評価指標などの詳細については別途記事を書いたので、そちらを御覧ください。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2018%2F03%2F14%2F112454" title="【python】分類タスクの評価指標の解説とsklearnでの計算方法 - 静かなる名辞" 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/2018/03/14/112454">www.haya-programming.com</a></cite><br /> </p> </div> <div class="section"> <h3>output_dictを使って便利に集計する</h3> <p> sklearn 0.20ではoutput_dictという引数がこの関数に追加されました。これを使うとデフォルトの文字列ではなく辞書形式で結果を得ることができ、結果をプログラム上で取り扱うことが容易になります。</p><p> 上のコードの出力部分を2行書き換えます。</p> <pre class="code lang-python" data-lang="python" data-unlink> <span class="synPreProc">from</span> pprint <span class="synPreProc">import</span> pprint pprint(classification_report(np.hstack(trues), np.hstack(preds), target_names=iris.target_names, output_dict=<span class="synIdentifier">True</span>)) </pre><p> 結果はこのようになります。</p> <pre class="code lang-python" data-lang="python" data-unlink>{<span class="synConstant">'macro avg'</span>: {<span class="synConstant">'f1-score'</span>: <span class="synConstant">0.97999799979998</span>, <span class="synConstant">'precision'</span>: <span class="synConstant">0.9801253834867282</span>, <span class="synConstant">'recall'</span>: <span class="synConstant">0.98</span>, <span class="synConstant">'support'</span>: <span class="synConstant">150</span>}, <span class="synConstant">'micro avg'</span>: {<span class="synConstant">'f1-score'</span>: <span class="synConstant">0.98</span>, <span class="synConstant">'precision'</span>: <span class="synConstant">0.98</span>, <span class="synConstant">'recall'</span>: <span class="synConstant">0.98</span>, <span class="synConstant">'support'</span>: <span class="synConstant">150</span>}, <span class="synConstant">'setosa'</span>: {<span class="synConstant">'f1-score'</span>: <span class="synConstant">1.0</span>, <span class="synConstant">'precision'</span>: <span class="synConstant">1.0</span>, <span class="synConstant">'recall'</span>: <span class="synConstant">1.0</span>, <span class="synConstant">'support'</span>: <span class="synConstant">50</span>}, <span class="synConstant">'versicolor'</span>: {<span class="synConstant">'f1-score'</span>: <span class="synConstant">0.9702970297029702</span>, <span class="synConstant">'precision'</span>: <span class="synConstant">0.9607843137254902</span>, <span class="synConstant">'recall'</span>: <span class="synConstant">0.98</span>, <span class="synConstant">'support'</span>: <span class="synConstant">50</span>}, <span class="synConstant">'virginica'</span>: {<span class="synConstant">'f1-score'</span>: <span class="synConstant">0.9696969696969697</span>, <span class="synConstant">'precision'</span>: <span class="synConstant">0.9795918367346939</span>, <span class="synConstant">'recall'</span>: <span class="synConstant">0.96</span>, <span class="synConstant">'support'</span>: <span class="synConstant">50</span>}, <span class="synConstant">'weighted avg'</span>: {<span class="synConstant">'f1-score'</span>: <span class="synConstant">0.9799979997999799</span>, <span class="synConstant">'precision'</span>: <span class="synConstant">0.980125383486728</span>, <span class="synConstant">'recall'</span>: <span class="synConstant">0.98</span>, <span class="synConstant">'support'</span>: <span class="synConstant">150</span>}} </pre><p> この辞書の形式はpandasデータフレームに変換することも可能です。</p> <pre class="code lang-python" data-lang="python" data-unlink> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd d = classification_report(np.hstack(trues), np.hstack(preds), target_names=iris.target_names, output_dict=<span class="synIdentifier">True</span>) df = pd.DataFrame(d) <span class="synIdentifier">print</span>(df) </pre><p> とすると、</p> <pre class="code lang-python" data-lang="python" data-unlink> macro avg micro avg setosa versicolor virginica weighted avg f1-score <span class="synConstant">0.979998</span> <span class="synConstant">0.98</span> <span class="synConstant">1.0</span> <span class="synConstant">0.970297</span> <span class="synConstant">0.969697</span> <span class="synConstant">0.979998</span> precision <span class="synConstant">0.980125</span> <span class="synConstant">0.98</span> <span class="synConstant">1.0</span> <span class="synConstant">0.960784</span> <span class="synConstant">0.979592</span> <span class="synConstant">0.980125</span> recall <span class="synConstant">0.980000</span> <span class="synConstant">0.98</span> <span class="synConstant">1.0</span> <span class="synConstant">0.980000</span> <span class="synConstant">0.960000</span> <span class="synConstant">0.980000</span> support <span class="synConstant">150.000000</span> <span class="synConstant">150.00</span> <span class="synConstant">50.0</span> <span class="synConstant">50.000000</span> <span class="synConstant">50.000000</span> <span class="synConstant">150.000000</span> </pre><p> のようにデータフレームとして見ることができます。ここからCSV, TeX, HTML, グラフなど任意のフォーマットに変換できるので、なにかと捗ると思います。</p> </div> <div class="section"> <h3>sklearn 0.20での変更点のまとめ</h3> <p> 別途記事を書いたので、そちらを御覧ください。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F03%2F18%2F052035" title="【python】sklearn 0.20でclassification_reportの仕様が変わっていた - 静かなる名辞" 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/03/18/052035">www.haya-programming.com</a></cite></p><p></p> </div> <div class="section"> <h3>classification_reportを使わないとしたら</h3> <p> このように大変便利なのですが、参考のためにこれを使わない方法も紹介しておきます。sklearn.metrics.precision_recall_fscore_supportを使います。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.metrics.precision_recall_fscore_support.html#sklearn.metrics.precision_recall_fscore_support">sklearn.metrics.precision_recall_fscore_support &mdash; scikit-learn 0.20.3 documentation</a></p><p> 使い方はこんな感じです。 </p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> precision_recall_fscore_support precision_recall_fscore_support(y_true, y_pred, average=<span class="synIdentifier">None</span>) </pre><p> 結果はこんな感じになります(上のプログラムを対象に計算し、返り値をpprintしました)。</p> <pre class="code" data-lang="" data-unlink>(array([1. , 0.96078431, 0.97959184]), array([1. , 0.98, 0.96]), array([1. , 0.97029703, 0.96969697]), array([50, 50, 50]))</pre><p> numpy配列を格納したタプルが返ってますね。それぞれのnumpy配列がprecision, recall, fscore, supportに対応します。</p> </div> <div class="section"> <h3>まとめ</h3> <p> 簡単に使えるので、分類結果を見てみたいときはとりあえずこれに放り込むと良いかと思います。また、sklearn 0.20からはかなり便利になったので、汎用的な分類結果集計方法としても使えるようになりました。</p> </div> hayataka2049 【python】RandomForestの木の本数を増やすとどうなるか? hatenablog://entry/17391345971623361740 2018-03-08T20:14:00+09:00 2019-08-22T17:57:33+09:00 はじめに RandomForest(ランダムフォレスト)には木の本数という重要なパラメータがある。slearnのデフォルトは10だが、実際に使うときは1000以上にしてやらないと良い性能が得られないということをよく経験する。 これを大きくすることで、一体どんな効果が得られるのだろうか? 予想1:より複雑な形状の分離超平面を学習できるようになる 予想2:汎化性能が向上する 予想1の効果は恐らく木の本数が相対的に少ないとき(100本以下)に顕著に現れると考えられる。その後、木の本数が増えていくに従ってモデルのバリアンスが下がり、予想2の通り汎化性能は向上する方向に向かうと考えられる。 ここで思い浮… <div class="section"> <h3>はじめに</h3> <p> RandomForest(ランダムフォレスト)には木の本数という重要なパラメータがある。slearnのデフォルトは10だが、実際に使うときは1000以上にしてやらないと良い性能が得られないということをよく経験する。</p><p> これを大きくすることで、一体どんな効果が得られるのだろうか?</p> <ul> <li>予想1:より複雑な形状の分離超平面を学習できるようになる</li> <li>予想2:汎化性能が向上する</li> </ul><p> 予想1の効果は恐らく木の本数が相対的に少ないとき(100本以下)に顕著に現れると考えられる。その後、木の本数が増えていくに従ってモデルのバリアンスが下がり、予想2の通り汎化性能は向上する方向に向かうと考えられる。</p><p> ここで思い浮かぶ疑問は、「とにかく木の本数を増やしさえすれば、SVMみたいに高い汎化性能が得られるのか?」という点である。RandomForestは決定木なので、基本的にデータの次元軸に直交する決定境界しか引けないという弱点がある。それでも、とにかく木を増やしていけば、丸くてぬるぬるした決定境界になったりするのだろうか?</p><p> 実際にどうなるのか、やってみよう。</p><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><br /> </p> </div> <div class="section"> <h3>実験:2次元データで木の本数を変えながら予測確率を評価する</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.ensemble <span class="synPreProc">import</span> RandomForestClassifier <span class="synStatement">as</span> RFC <span class="synPreProc">from</span> sklearn.svm <span class="synPreProc">import</span> SVC <span class="synPreProc">from</span> matplotlib <span class="synPreProc">import</span> pyplot <span class="synStatement">as</span> plt <span class="synStatement">def</span> <span class="synIdentifier">make_circle</span>(a=<span class="synConstant">1</span>, b=<span class="synConstant">1</span>, xy=(<span class="synConstant">0</span>,<span class="synConstant">0</span>), phi=<span class="synConstant">0</span>, n=<span class="synConstant">100</span>, random_s=<span class="synConstant">0.1</span>): theta = np.arange(<span class="synConstant">0</span>, <span class="synConstant">2</span>*np.pi, <span class="synConstant">2</span>*np.pi/n) X = a*np.cos(theta) Y = b*np.sin(theta) data_mat = np.matrix(np.vstack([X, Y])) phi_d = np.deg2rad(phi) rot = np.matrix([[np.cos(phi_d), -np.sin(phi_d)], [np.sin(phi_d), np.cos(phi_d)]]) rot_data = rot*data_mat X = rot_data[<span class="synConstant">0</span>].A Y = rot_data[<span class="synConstant">1</span>].A rand1 = np.random.normal(scale=random_s, size=theta.shape) rand2 = np.random.normal(scale=random_s, size=theta.shape) <span class="synStatement">return</span> X+rand1+xy[<span class="synConstant">0</span>], Y+rand2+xy[<span class="synConstant">1</span>] <span class="synStatement">def</span> <span class="synIdentifier">gen_data</span>(): n = <span class="synConstant">150</span> X1, Y1 = make_circle(a=<span class="synConstant">6.5</span>, b=<span class="synConstant">4.5</span>, n=n, random_s=<span class="synConstant">0.4</span>) X2, Y2 = make_circle(a=<span class="synConstant">5</span>, b=<span class="synConstant">3</span>, n=n, random_s=<span class="synConstant">0.4</span>) X3, Y3 = make_circle(a=<span class="synConstant">2.5</span>, b=<span class="synConstant">1.5</span>, n=n, random_s=<span class="synConstant">0.4</span>) X = np.hstack([X1, X2, X3]) Y = np.hstack([Y1, Y2, Y3]) data = np.vstack([X, Y]).T target = np.array([<span class="synConstant">0</span>]*n + [<span class="synConstant">1</span>]*n + [<span class="synConstant">0</span>]*n) <span class="synStatement">return</span> data, target <span class="synStatement">def</span> <span class="synIdentifier">plot_proba</span>(data, target, clf, filename=<span class="synConstant">&quot;fig.png&quot;</span>, text=<span class="synConstant">&quot;&quot;</span>): plt.figure() ax = plt.subplot() ax.set_xlim((-<span class="synConstant">9</span>, <span class="synConstant">9</span>)) ax.set_ylim((-<span class="synConstant">6</span>, <span class="synConstant">6</span>)) x = np.arange(-<span class="synConstant">9</span>, <span class="synConstant">9</span>, <span class="synConstant">18</span>/<span class="synConstant">250</span>) y = np.arange(-<span class="synConstant">6</span>, <span class="synConstant">6</span>, <span class="synConstant">12</span>/<span class="synConstant">250</span>) X, Y = np.meshgrid(x, y) points = np.c_[X.ravel(), Y.ravel()] probs = clf.predict_proba(points) probs = probs[:,<span class="synConstant">1</span>].reshape(X.shape) plt.pcolormesh(X, Y, probs, cmap=plt.cm.RdBu) plt.scatter(data[:,<span class="synConstant">0</span>], data[:,<span class="synConstant">1</span>], c=[<span class="synConstant">&quot;rb&quot;</span>[t] <span class="synStatement">for</span> t <span class="synStatement">in</span> target], edgecolors=<span class="synConstant">'k'</span>) plt.title(text) plt.savefig(filename) <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): data, target = gen_data() <span class="synStatement">for</span> i, n_estimators <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>([<span class="synConstant">5</span>, <span class="synConstant">10</span>, <span class="synConstant">30</span>, <span class="synConstant">50</span>, <span class="synConstant">100</span>, <span class="synConstant">300</span>, <span class="synConstant">500</span>, <span class="synConstant">1000</span>]): rfc = RFC(n_estimators=n_estimators, oob_score=<span class="synIdentifier">True</span>, n_jobs=-<span class="synConstant">1</span>) rfc.fit(data, target) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;{0}:{1}&quot;</span>.format(n_estimators, rfc.oob_score_)) plot_proba(data, target, rfc, <span class="synConstant">&quot;rfc_{0}.png&quot;</span>.format(n_estimators), <span class="synConstant">&quot;RFC n_estimators={0}&quot;</span>.format(n_estimators)) svm = SVC(C=<span class="synConstant">20</span>, gamma=<span class="synConstant">0.05</span>, probability=<span class="synIdentifier">True</span>) svm.fit(data, target) plot_proba(data, target, svm, <span class="synConstant">&quot;svm.png&quot;</span>, <span class="synConstant">&quot;SVM C=20, gamma=0.05&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> 昨日の記事の楕円形データ生成を使っています。<br /> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2018%2F03%2F07%2F220612" title="【python】numpyで楕円形のデータを生成する - 静かなる名辞" 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="http://hayataka2049.hatenablog.jp/entry/2018/03/07/220612">hayataka2049.hatenablog.jp</a></cite><br /> </p> </div> <div class="section"> <h3>結果</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180308/20180308194151.png" alt="f:id:hayataka2049:20180308194151p:plain" title="f:id:hayataka2049:20180308194151p:plain" class="hatena-fotolife" itemprop="image"></span><br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180308/20180308194158.png" alt="f:id:hayataka2049:20180308194158p:plain" title="f:id:hayataka2049:20180308194158p:plain" class="hatena-fotolife" itemprop="image"></span><br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180308/20180308194207.png" alt="f:id:hayataka2049:20180308194207p:plain" title="f:id:hayataka2049:20180308194207p:plain" class="hatena-fotolife" itemprop="image"></span><br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180308/20180308194223.png" alt="f:id:hayataka2049:20180308194223p:plain" title="f:id:hayataka2049:20180308194223p:plain" class="hatena-fotolife" itemprop="image"></span><br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180308/20180308194232.png" alt="f:id:hayataka2049:20180308194232p:plain" title="f:id:hayataka2049:20180308194232p:plain" class="hatena-fotolife" itemprop="image"></span><br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180308/20180308194241.png" alt="f:id:hayataka2049:20180308194241p:plain" title="f:id:hayataka2049:20180308194241p:plain" class="hatena-fotolife" itemprop="image"></span><br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180308/20180308194248.png" alt="f:id:hayataka2049:20180308194248p:plain" title="f:id:hayataka2049:20180308194248p:plain" class="hatena-fotolife" itemprop="image"></span></p><p> そこそこ良くなるが、100以上では改善度合いは微妙かもしれない。OOB errorは、</p> <pre class="code" data-lang="" data-unlink>5:0.8288888888888889 10:0.8822222222222222 30:0.9044444444444445 50:0.9133333333333333 80:0.9155555555555556 100:0.9222222222222223 300:0.92 500:0.9177777777777778 1000:0.9222222222222223</pre><p> やはり100以上の改善は微妙という、画像を見て思う感覚を裏付けるものになっている。</p><p> では、SVMだとどんな画像が得られるだろうか?<br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180308/20180308194626.png" alt="f:id:hayataka2049:20180308194626p:plain" title="f:id:hayataka2049:20180308194626p:plain" class="hatena-fotolife" itemprop="image"></span><br />  これは勝てない。RandomForestだとどうしてもカクカクが残るのに。</p> </div> <div class="section"> <h3>考察</h3> <p> この結果の妥当性は率直に言って判断しづらい。</p><p> そもそも、2次元データを入力している以上、ランダムフォレストはデフォルトで(<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Csqrt%7B2%7D" alt=" \sqrt{2}"/>)の特徴量を使って木を作ってくれている訳で、つまり1次元だけで判断してくれている。ちょっとあんまりなので、max_features=2としたのが次の画像。データが(ランダム絡みで)変わってるのでそこだけ注意。<br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180308/20180308200130.png" alt="f:id:hayataka2049:20180308200130p:plain" title="f:id:hayataka2049:20180308200130p:plain" class="hatena-fotolife" itemprop="image"></span><br />  まあ、これを見てもSVMみたいに滑らかな決定境界が引けてるとは言い難いものがあるけど・・・(考えようによっては1次元でやった上の画像の方が汎化性能は高い、ような気もしてこなくはない。全体的にもやっとしてて、相対的に滑らかに見える)。</p><p> でも、SVMもSVMで、パラメタ次第ではどんな複雑怪奇な決定境界だって引けるといえば引ける。<br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180308/20180308200634.png" alt="f:id:hayataka2049:20180308200634p:plain" title="f:id:hayataka2049:20180308200634p:plain" class="hatena-fotolife" itemprop="image"></span></p><p> こういう問題(けっきょく汎化性能が得られるかどうかはパラメタ次第)があるので、SVMの方が良いと一概に言えるかはけっこう微妙。</p><p> もっと言えば、「それぞれの軸に意味がある」「ノイズもけっこう混ざってる」「スパース」「高次元」という性質のデータを対象とする場合、SVMの汎化性能(滑らかな決定境界を引ける)はかえって邪魔になるのではないだろうか? そのようなデータでは、とにかく重要な軸を見つけてきて、そこで判断するRandomForestの方が良い性能が得られることが多いと経験的に感じる。ちなみに自然言語処理で使うBoW(Bag of Words)はその典型例である。</p><p> 逆に、軸に意味がなくて相対的に低次元でデンスな空間を相手にする場合、SVMの方が良い結果を産むということもよく経験することである。PCAで低次元に落としてしまったデータとか、word2vecで生成される単語の空間とかが割とそんな感じである。</p><p> なんだか話が脱線してきたのでこれくらいにするけど、けっきょく「滑らかな決定境界を引く能力はどうやってもSVMの方が高い(あたりまえ)」「滑らかだから良いというものでもない」「使い分けが重要」という当たり障りのない結論に落ち着いてしまった。</p><p> あと、木の本数は無尽蔵に増やすわけにはいかない。ランダムフォレストは計算量は軽いけど意外とリソース消費の激しいアルゴリズムで、増やしすぎると効率が悪化する。</p><p><a href="https://www.haya-programming.com/entry/2019/06/22/235209">&#x30E9;&#x30F3;&#x30C0;&#x30E0;&#x30D5;&#x30A9;&#x30EC;&#x30B9;&#x30C8;&#x306F;&#x30B5;&#x30F3;&#x30D7;&#x30EB;&#x6570;&#x304C;&#x591A;&#x3044;&#x3068;&#x30E1;&#x30E2;&#x30EA;&#x6D88;&#x8CBB;&#x91CF;&#x304C;&#x5927;&#x304D;&#x3044; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><p> 汎化性能はできるだけ他の方法で確保したいところ。</p> </div> <div class="section"> <h3>まとめ</h3> <p> SVMの方が滑らかでした(小並感)。</p> </div> hayataka2049 【python】混合ガウスモデル (GMM)でハード・ソフトクラスタリング hatenablog://entry/17391345971622376100 2018-03-06T04:39:50+09:00 2019-06-16T20:52:17+09:00 ソフトクラスタリングの有名な手法としては混合ガウスモデル(混合正規分布モデル)を使った手法があります。この手法はデータが「複数の正規分布から構成されている」と仮定し、その正規分布のパラメタをEMアルゴリズム(expectation–maximization algorithm)という手法を使って最尤推定します。 <div class="section"> <h3>はじめに</h3> <p> 先日はFuzzy c-meansによるソフトクラスタリングを行いました。</p><p><a href="http://hayataka2049.hatenablog.jp/entry/2018/03/03/202558">&#x3010;python&#x3011;skfuzzy&#x306E;Fuzzy c-means&#x3067;&#x30BD;&#x30D5;&#x30C8;&#x30AF;&#x30E9;&#x30B9;&#x30BF;&#x30EA;&#x30F3;&#x30B0; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><p> ソフトクラスタリングの有名な手法としてはFuzzy c-meansの他に、混合ガウスモデル(混合正規分布モデル)を使った手法があります。この手法はデータが「複数の正規分布から構成されている」と仮定し、その正規分布のパラメタ<a href="#f-c5112e5f" name="fn-c5112e5f" title="一次元なら平均と分散、多次元なら共分散みたいな話になってくるのだろうか?">*1</a>をEMアルゴリズム(expectation–maximization algorithm)という手法を使って最尤推定します。</p><p> ごちゃごちゃと書きましたが、要するに「3つのクラスタにクラスタリングしたければ、(各クラスタのデータの分布が正規分布に従うと仮定して)3つの正規分布が重なりあってると思ってGMMを使って解く」という乱暴なお話です。正規分布が重なりあっているとみなすということは、どの分布に属するかも確率でわかる訳で、これがソフトクラスタリングに使える理由です。ハードクラスタリングに使いたいときは、確率最大のクラスタラベルに振ることになるかと思います。</p><p> このGMM、pythonではsklearnに入っているので簡単に使えます。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.mixture.GaussianMixture.html">sklearn.mixture.GaussianMixture &mdash; scikit-learn 0.20.1 documentation</a></p><p> ということで、他のクラスタリング手法と比較してみることにしました。</p><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> <div class="section"> <h3>実験の説明</h3> <p> 先日の記事でやったのと同様、irisをPCAで二次元に落としたデータに対してクラスタリングを行います。クラスタリング結果(所属するクラスタの確率)はirisが3クラスのデータなのを利用し、色(RGB)で表現します。</p><p> 比較するクラスタリング手法はk-means(ハード)、Fuzzy c-means(ソフト)、GMM(ハード・ソフト)です。</p><p> 前回はFuzzy c-meansのパラメタmを動かして結果を見たりしましたが、今回これは2で決め打ちにします。</p><p> 実験用ソースコードは次のものです。走らせるにはいつもの定番ライブラリ以外にscikit-fuzzyというライブラリを入れる必要があります(あるいはFuzzy c-means関連の部分をコメントアウトするか。でもskfuzzyはpipで一発で入るし、入れておいても別に損はない)。</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">from</span> sklearn.cluster <span class="synPreProc">import</span> KMeans <span class="synStatement">as</span> KM <span class="synPreProc">from</span> sklearn.mixture <span class="synPreProc">import</span> GaussianMixture <span class="synStatement">as</span> GMM <span class="synPreProc">from</span> matplotlib <span class="synPreProc">import</span> pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> skfuzzy.cluster <span class="synPreProc">import</span> cmeans <span class="synStatement">def</span> <span class="synIdentifier">target_to_color</span>(target): <span class="synStatement">if</span> <span class="synIdentifier">type</span>(target) == np.ndarray: <span class="synStatement">return</span> (target[<span class="synConstant">0</span>], target[<span class="synConstant">1</span>], target[<span class="synConstant">2</span>]) <span class="synStatement">else</span>: <span class="synStatement">return</span> <span class="synConstant">&quot;rgb&quot;</span>[target] <span class="synStatement">def</span> <span class="synIdentifier">plot_data</span>(data, target, filename=<span class="synConstant">&quot;fig.png&quot;</span>): plt.figure() plt.scatter(data[:,<span class="synConstant">0</span>], data[:,<span class="synConstant">1</span>], c=[target_to_color(t) <span class="synStatement">for</span> t <span class="synStatement">in</span> target]) plt.savefig(filename) <span class="synStatement">def</span> <span class="synIdentifier">gen_data</span>(): iris = load_iris() pca = PCA(n_components=<span class="synConstant">2</span>) <span class="synStatement">return</span> pca.fit_transform(iris.data), iris.target <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): data, target = gen_data() plot_data(data, target, filename=<span class="synConstant">&quot;origin.png&quot;</span>) km = KM(n_clusters=<span class="synConstant">3</span>) km_target = km.fit_predict(data) plot_data(data, km_target, filename=<span class="synConstant">&quot;kmeans.png&quot;</span>) cm_result = cmeans(data.T, <span class="synConstant">3</span>, <span class="synConstant">2</span>, <span class="synConstant">0.003</span>, <span class="synConstant">10000</span>) plot_data(data, cm_result[<span class="synConstant">1</span>].T, filename=<span class="synConstant">&quot;cmeans_2.png&quot;</span>) gmm = GMM(n_components=<span class="synConstant">3</span>, max_iter=<span class="synConstant">1000</span>) gmm.fit(data) gmm_target = gmm.predict(data) gmm_target_proba = gmm.predict_proba(data) plot_data(data, gmm_target, filename=<span class="synConstant">&quot;gmm.png&quot;</span>) plot_data(data, gmm_target_proba, filename=<span class="synConstant">&quot;gmm_proba.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre> </div> <div class="section"> <h3>結果</h3> <div class="section"> <h4>オリジナルデータ</h4> <p><div style="text-align: center;"><br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180306/20180306041207.png" alt="f:id:hayataka2049:20180306041207p:plain" title="f:id:hayataka2049:20180306041207p:plain" class="hatena-fotolife" itemprop="image"></span><br /> 元データ</div> これが元のデータです。できるだけこれに近いようなクラスタリング結果を得ることを目標とします。</p> </div> <div class="section"> <h4>k-means</h4> <p><div style="text-align: center;"><br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180306/20180306041256.png" alt="f:id:hayataka2049:20180306041256p:plain" title="f:id:hayataka2049:20180306041256p:plain" class="hatena-fotolife" itemprop="image"></span><br /> k-means</div> 図の左側のクラスタは分離できていますが、右側は割と悲惨です。クラスタ同士が隣接していて細長い形だったりすると上手く行かないことが多いのがk-meansの特徴です。</p> </div> <div class="section"> <h4>c-means</h4> <p><div style="text-align: center;"><br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180306/20180306041313.png" alt="f:id:hayataka2049:20180306041313p:plain" title="f:id:hayataka2049:20180306041313p:plain" class="hatena-fotolife" itemprop="image"></span><br /> Fuzzy c-means</div></p><p> こうして見るとc-meansは「ファジー理論を入れて境界を曖昧にしたk-means」という気がしてきます。実際アルゴリズムもそんな感じなんですけど。</p> </div> <div class="section"> <h4>GMM</h4> <p><div style="text-align: center;"><br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180306/20180306041716.png" alt="f:id:hayataka2049:20180306041716p:plain" title="f:id:hayataka2049:20180306041716p:plain" class="hatena-fotolife" itemprop="image"></span><br /> GMM-based clustering (hard)</p><p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180306/20180306041818.png" alt="f:id:hayataka2049:20180306041818p:plain" title="f:id:hayataka2049:20180306041818p:plain" class="hatena-fotolife" itemprop="image"></span><br /> GMM-based clustering (soft)</div> 一見して「おお」って感じですね。k-means、c-meansと比較して、元データのラベルに近いクラスタリング結果が得られています(図の右側の2つのクラスタの境界が右肩上がりになっている)。まあ、ちょっと元データのラベルとはずれているんですが(右下の方はかなり怪しい)、普通はこちらの方がk-meansやc-meansより「良い」クラスタリング結果だ、と判断されることが多いでしょう。</p><p> どうしてこうなるのかというと、「irisのデータが正規分布していた」ということに尽きます。ま、アヤメの花びらの大きさとかのデータですから、正規分布しているんでしょう、きっと。</p><p> こうして見るとGMMの方が良さそうな気もしますが、「ちゃんと正規分布してるか」が怪しいとちょっと適用するのを躊躇うのと、あと計算コスト自体はk-meansより高いはずなので<a href="#f-947394f5" name="fn-947394f5" title="Fuzzy c-meansとどっちが良いかは未調査">*2</a>、いまいちk-meansと比べて使われていない、というのが実情に近いかもしれません。</p> </div> </div> <div class="section"> <h3>まとめ</h3> <p> GMMを使ってみたらけっこう良かったです。</p> </div><div class="footnote"> <p class="footnote"><a href="#fn-c5112e5f" name="f-c5112e5f" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">一次元なら平均と分散、多次元なら共分散みたいな話になってくるのだろうか?</span></p> <p class="footnote"><a href="#fn-947394f5" name="f-947394f5" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">Fuzzy c-meansとどっちが良いかは未調査</span></p> </div> hayataka2049 【python】skfuzzyのFuzzy c-meansでソフトクラスタリング hatenablog://entry/17391345971621559489 2018-03-03T20:25:58+09:00 2019-06-16T20:52:17+09:00 はじめに Fuzzy c-meansはソフトクラスタリングの手法です。 pythonではscikit-fuzzyというライブラリで利用できるようです。ということで、実際に使ってみました。 基本的な理論はこちらのサイトなどを参考にしてください。ファジィc-means法 - 機械学習の「朱鷺の杜Wiki」 目次 はじめに sklearnではなかった 基本的な使い方 パラメタ 返り値 実験 結果 元のデータ k-means Fuzzy c-means まとめ ※追記 付録 スポンサーリンク (adsbygoogle = window.adsbygoogle || []).push({}); skle… <div class="section"> <h3 id="はじめに">はじめに</h3> <p> Fuzzy c-meansはソフトクラスタリングの手法です。</p><p> pythonではscikit-fuzzyというライブラリで利用できるようです。ということで、実際に使ってみました。</p><p> 基本的な理論はこちらのサイトなどを参考にしてください。</p><p><a href="http://ibisforest.org/index.php?%E3%83%95%E3%82%A1%E3%82%B8%E3%82%A3c-means%E6%B3%95">&#x30D5;&#x30A1;&#x30B8;&#x30A3;c-means&#x6CD5; - &#x6A5F;&#x68B0;&#x5B66;&#x7FD2;&#x306E;&#x300C;&#x6731;&#x9DFA;&#x306E;&#x675C;Wiki&#x300D;</a></p><p> 目次</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#sklearnではなかった">sklearnではなかった</a></li> <li><a href="#基本的な使い方">基本的な使い方</a><ul> <li><a href="#パラメタ">パラメタ</a></li> <li><a href="#返り値">返り値</a></li> </ul> </li> <li><a href="#実験">実験</a></li> <li><a href="#結果">結果</a><ul> <li><a href="#元のデータ">元のデータ</a></li> <li><a href="#k-means">k-means</a></li> <li><a href="#Fuzzy-c-means">Fuzzy c-means</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a><ul> <li><a href="#追記">※追記</a></li> </ul> </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><p></p> </div> <div class="section"> <h3 id="sklearnではなかった">sklearnではなかった</h3> <p> ドキュメントはここです。<br /> <a href="http://pythonhosted.org/scikit-fuzzy/api/skfuzzy.cluster.html#cmeans">Module: cluster &mdash; skfuzzy v0.2 docs</a></p><p> 親切なことに使用例のページもあります。<br /> <a href="http://pythonhosted.org/scikit-fuzzy/auto_examples/plot_cmeans.html">Fuzzy c-means clustering &mdash; skfuzzy v0.2 docs</a></p><p> 一読してわかるとおり、sklearnライクなインターフェースがある訳ではないようです。それも一つの方針として悪くはないと思いますが、ちょっとカルチャーショックを受けました。</p> </div> <div class="section"> <h3 id="基本的な使い方">基本的な使い方</h3> <p> sklearnライクではないので、使い方を理解するまで手間取りました。簡単に(コメントで)説明しておきます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># cmeans『関数』をimportする</span> <span class="synComment"># 間違ってもクラスなどではないことを理解すること</span> <span class="synPreProc">from</span> skfuzzy.cluster <span class="synPreProc">import</span> cmeans <span class="synComment"># dataは(データ数,次元数)のいつもの特徴量</span> <span class="synComment"># が、転置して渡してやる必要がある</span> <span class="synComment"># 5つのパラメタは省略できない。詳しくは後述</span> cm_result = cmeans(data.T, <span class="synConstant">3</span>, <span class="synConstant">2</span>, <span class="synConstant">0.003</span>, <span class="synConstant">10000</span>) <span class="synComment"># 返り値も実際は7つくらい返っている</span> <span class="synComment"># 必要なものだけ使う</span> </pre><p> 率直な感想は「作りが古いなぁ・・・」です。「sklearn? なにそれ食えるの。オブジェクト指向? 知らんよ」みたいな。別に使う側としてはどんなインターフェースでも、解析アルゴリズムがちゃんと走れば良いので、文句言うつもりはないんですが。それでも正直なところ、ちょっと引きました。</p><p> とりあえずパラメタと返り値について説明します。上記ドキュメントを適当に(省略しつつ)和訳しただけなので、ちゃんと使いたい人はドキュメントを読んで下さい。</p> <div class="section"> <h4 id="パラメタ">パラメタ</h4> <ul> <li>data : 2d array, size (S, N)</li> </ul><p> データ。Sはデータの次元数、Nはデータ数です。</p> <ul> <li>c : int</li> </ul><p> クラスタ数</p> <ul> <li>m : float</li> </ul><p> c-meansのパラメタ。詳しくは上の理論のページを読んで下さい。変えたときの変化は後で見せます。</p> <ul> <li>error : float</li> </ul><p> fuzzy c-meansは処理を繰り返して収束させていく系のアルゴリズムなので、こういうものが必要になります。エラー率が一定以下になったらループを抜ける訳です。理論をちゃんと勉強していないので、エラー率の計算方法とか私は知りませんけれど。</p> <ul> <li>maxiter : int</li> </ul><p> 最大繰り返し回数。</p> <ul> <li>init : 2d array, size (S, N)</li> </ul><p> これは省略できます。計算に使う初期値です。省略するとランダム配列にされます。普通「よい初期値」なんて持ってないので、省略して使うことになると思います。</p> <ul> <li>seed : int</li> </ul><p> initが省略されたときのランダム初期化で使うseedです。<br /> <br /> </p> </div> <div class="section"> <h4 id="返り値">返り値</h4> <ul> <li>cntr : 2d array, size (S, c)</li> </ul><p> 各クラスタの中心。</p> <ul> <li>u : 2d array, (S, N)</li> </ul><p> 各データがどのクラスタに割り当てられたかを比率で示した行列です。要するにこれがc-meansの結果です。あと、ドキュメントには(S, N)って書いてあるけど、これはどう考えても(c, N)の間違いです。</p> <ul> <li>u0 : 2d array, (S, N)</li> </ul><p> uの初期値みたいなことが書いてある。</p> <ul> <li>d : 2d array, (S, N)</li> </ul><p> 「Final Euclidian distance matrix.」。クラスタ中心に対する距離ってことか?</p> <ul> <li>jm : 1d array, length P</li> </ul><p> 「Objective function history.」だって。さっぱりわからない。</p> <ul> <li>p : int</li> </ul><p> ループが回った回数</p> <ul> <li>fpc : float</li> </ul><p> 「Final fuzzy partition coefficient.」</p><p> まあ、要するに適当な引数で呼び出してあげて、実質的に使う返り値はuだけということです・・・。</p><p> とにかく使い方はわかったので、実験してみましょう。</p> </div> </div> <div class="section"> <h3 id="実験">実験</h3> <p> irisでやってみます。irisは4次元データですが、4次元だと見づらいのでPCAで2次元に落とし、2次元空間上で3クラスタ(irisが3種類のアヤメの花のデータなので)にクラスタリングすることにします。</p><p> 結果として、</p> <ol> <li>元のデータを色分けした絵</li> <li>k-meansでクラスタリングした結果(比較用)</li> <li>mを1.5, 2, 3, 4, 5で変化させたときの結果それぞれ</li> </ol><p> を得ることにし、考察します。</p><p> 実験に使ったソースコードは記事の最後に付録として載せます。次の章で先に結果を見せます。</p> </div> <div class="section"> <h3 id="結果">結果</h3> <p> 1つずつ見せます</p> <div class="section"> <h4 id="元のデータ">元のデータ</h4> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180303/20180303195615.png" alt="f:id:hayataka2049:20180303195615p:plain" title="f:id:hayataka2049:20180303195615p:plain" class="hatena-fotolife" itemprop="image"></span><br />  似たような絵を何回も見たことがあります。いつも感じることですが、irisは綺麗なデータで良い子だと思います。</p> </div> <div class="section"> <h4 id="k-means">k-means</h4> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180303/20180303195619.png" alt="f:id:hayataka2049:20180303195619p:plain" title="f:id:hayataka2049:20180303195619p:plain" class="hatena-fotolife" itemprop="image"></span><br />  上とはクラスタの色が違いますが、気にしないでください。以下のデータでも(出力するたびにクラスタ番号変わるので)色はバラバラです。</p><p> 右側の境界をうまく捉えられていませんが(クラスタリングだから当たり前)、それを除けば真っ当な結果に見えます。</p> </div> <div class="section"> <h4 id="Fuzzy-c-means">Fuzzy c-means</h4> <p> 上から順にm=1.5, 2, 3, 4, 5です。各クラスタへの帰属の度合いをそのままRGBにしました。</p><p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180303/20180303195624.png" alt="f:id:hayataka2049:20180303195624p:plain" title="f:id:hayataka2049:20180303195624p:plain" class="hatena-fotolife" itemprop="image"></span><br /> <div style="text-align: center;"><br /> m=1.5</div></p><p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180303/20180303195628.png" alt="f:id:hayataka2049:20180303195628p:plain" title="f:id:hayataka2049:20180303195628p:plain" class="hatena-fotolife" itemprop="image"></span><br /> <div style="text-align: center;"><br /> m=2</div></p><p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180303/20180303195634.png" alt="f:id:hayataka2049:20180303195634p:plain" title="f:id:hayataka2049:20180303195634p:plain" class="hatena-fotolife" itemprop="image"></span><br /> <div style="text-align: center;"><br /> m=3</div></p><p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180303/20180303195639.png" alt="f:id:hayataka2049:20180303195639p:plain" title="f:id:hayataka2049:20180303195639p:plain" class="hatena-fotolife" itemprop="image"></span><br /> <div style="text-align: center;"><br /> m=4</div></p><p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180303/20180303195644.png" alt="f:id:hayataka2049:20180303195644p:plain" title="f:id:hayataka2049:20180303195644p:plain" class="hatena-fotolife" itemprop="image"></span><br /> <div style="text-align: center;"><br /> m=5</div></p><p> 少しずつ色が暗くなっていく傾向にありますが、これは「どのクラスタにも同程度に帰属する」みたいな結果になっていることを表します。m=1.5のときは[0.7, 0.2, 0.1]みたいな結果が、m=5のときは[0.4, 0.3, 0.3]みたいな結果が得られていると解釈してください。</p><p> どの程度のmを設定するとちょうど良いのか・・・? 私にはわかりません。要するにmを小さくするとシャープに切れ、大きくするとゆるーく移り変わるってことなんですが。適当に決めるしかないってことなのかな。</p><p> それはそうとして、よく見ると各クラスタの中心付近が(相対的に)鮮明な色になっていて、クラスタ中心から外れると(正確には他のクラスタ中心に接近していくと)色が混ざり合うのがお分かりいただけたでしょか? これがソフトクラスタリングの成果です。</p> </div> </div> <div class="section"> <h3 id="まとめ">まとめ</h3> <p> とりあえず、使える(意図したとおり動かせる)のはわかりました。</p><p> 何に使えるかは・・・ごめんなさい、思いつきそうで思いつかなかったです。</p><p> 一応フォローしておくと、ソフトクラスタリング自体は「高次元の特徴量を相対的に低次元に持ってくる」とか「元の空間の情報を保持したまままったく別の空間に移す」みたいな用途のために使われることがあるようです(各クラスタへの帰属度合いを新たな特徴量とする)。たとえばこんな応用があるらしいです。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Ffufufukakaka%2Fitems%2Fa7316273908a7c400868" title="文書ベクトルをお手軽に高い精度で作れるSCDVって実際どうなのか日本語コーパスで実験した(EMNLP2017) - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/fufufukakaka/items/a7316273908a7c400868">qiita.com</a></cite></p><p> ただ、上のページでは混合正規分布モデルという別の手法を使って実装していますけど・・・。どっちが良いんだろう? ぶっちゃけ謎(たぶん既存の知見があるんだろうけど)。そのへんも含めて今後検討していきたいところです。</p> <div class="section"> <h4 id="追記">※追記</h4> <p> 混合正規分布モデルでもやりました。こちらの記事をご参照ください。<br /> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2018%2F03%2F06%2F043950" title="【python】混合ガウスモデル (GMM)でハード・ソフトクラスタリング - 静かなる名辞" 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="http://hayataka2049.hatenablog.jp/entry/2018/03/06/043950">hayataka2049.hatenablog.jp</a></cite></p><p></p> </div> </div> <div class="section"> <h3 id="付録">付録</h3> <p> 実験に使ったソースコードです。python3系で必要なパッケージを入れればそのまま動くと思います。</p><p><div onclick="obj=document.getElementById('oritatami_part').style; obj.display=(obj.display=='none')?'block':'none';"><br /> <a style="cursor:pointer;">▶クリックで展開</a><br /> </div><div id="oritatami_part" style="display:none;clear:both;"></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">from</span> sklearn.cluster <span class="synPreProc">import</span> KMeans <span class="synStatement">as</span> KM <span class="synPreProc">from</span> matplotlib <span class="synPreProc">import</span> pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> skfuzzy.cluster <span class="synPreProc">import</span> cmeans <span class="synStatement">def</span> <span class="synIdentifier">target_to_color</span>(target): <span class="synStatement">if</span> <span class="synIdentifier">type</span>(target) == np.ndarray: <span class="synStatement">return</span> (target[<span class="synConstant">0</span>], target[<span class="synConstant">1</span>], target[<span class="synConstant">2</span>]) <span class="synStatement">else</span>: <span class="synStatement">return</span> <span class="synConstant">&quot;rgb&quot;</span>[target] <span class="synStatement">def</span> <span class="synIdentifier">plot_data</span>(data, target, filename=<span class="synConstant">&quot;fig.png&quot;</span>): plt.figure() plt.scatter(data[:,<span class="synConstant">0</span>], data[:,<span class="synConstant">1</span>], c=[target_to_color(t) <span class="synStatement">for</span> t <span class="synStatement">in</span> target]) plt.savefig(filename) <span class="synStatement">def</span> <span class="synIdentifier">gen_data</span>(): iris = load_iris() pca = PCA(n_components=<span class="synConstant">2</span>) <span class="synStatement">return</span> pca.fit_transform(iris.data), iris.target <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): data, target = gen_data() plot_data(data, target, filename=<span class="synConstant">&quot;origin.png&quot;</span>) km = KM(n_clusters=<span class="synConstant">3</span>) km_target = km.fit_predict(data) plot_data(data, km_target, filename=<span class="synConstant">&quot;kmeans.png&quot;</span>) <span class="synStatement">for</span> m <span class="synStatement">in</span> [<span class="synConstant">1.5</span>, <span class="synConstant">2</span>, <span class="synConstant">3</span>, <span class="synConstant">4</span>, <span class="synConstant">5</span>]: cm_result = cmeans(data.T, <span class="synConstant">3</span>, m, <span class="synConstant">0.003</span>, <span class="synConstant">10000</span>) plot_data(data, cm_result[<span class="synConstant">1</span>].T, filename=<span class="synConstant">&quot;cmeans_{0}.png&quot;</span>.<span class="synIdentifier">format</span>(m)) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p></div> </p> </div> hayataka2049 【python】sklearnのPipelineを使うとできること hatenablog://entry/17391345971618807092 2018-02-22T23:40:11+09:00 2019-08-27T18:29:07+09:00 機械学習では、何段もの前処理をしてから最終的な分類や回帰のアルゴリズムに入力するということがよくあります。 前処理にはけっこう泥臭い処理も多く、leakageの問題なども絡んできます。はっきり言って自分で書こうとすると面倒くさいです。 こういう問題を(ある程度)解決できるのがsklearnのPipelineです。これについては、以前から「何かあるらしいな」というのは知っていましたが、実際に使ったことはありませんでした。でも、このたび使ってみたら「すげえ」となったので、こうして記事を書いている訳です。 この記事ではPipelineのコンセプトと使い方を簡単に説明します。雰囲気は伝わるかと思います… <p> 機械学習では、何段もの前処理をしてから最終的な分類や回帰のアルゴリズムに入力するということがよくあります。</p><p> 前処理にはけっこう泥臭い処理も多く、leakageの問題なども絡んできます。はっきり言って自分で書こうとすると面倒くさいです。</p><p> こういう問題を(ある程度)解決できるのがsklearnのPipelineです。これについては、以前から「何かあるらしいな」というのは知っていましたが、実際に使ったことはありませんでした。でも、このたび使ってみたら「すげえ」となったので、こうして記事を書いている訳です。</p><p> この記事ではPipelineのコンセプトと使い方を簡単に説明します。雰囲気は伝わるかと思いますが、細かい使い方はライブラリの公式ドキュメントを参照してください。</p> <ul> <li>sklearn公式</li> </ul><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.pipeline.Pipeline.html">sklearn.pipeline.Pipeline &mdash; scikit-learn 0.21.3 documentation</a><br /> </p> <ul class="table-of-contents"> <li><a href="#問題設定">問題設定</a></li> <li><a href="#実装">実装</a><ul> <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> </ul> </li> <li><a href="#結果">結果</a></li> <li><a href="#参考サイト">参考サイト</a></li> </ul> <div class="section"> <h3 id="問題設定">問題設定</h3> <p> 今回は例として、sklearnのdigits(load_digits)を対象データにして説明します。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_digits.html">sklearn.datasets.load_digits &mdash; scikit-learn 0.21.3 documentation</a></p><p> これは0~9の数字を分類する問題で、特徴量は8*8の画像データをflattenして64次元にしたものです。このデータの分類は割と簡単な方で、直接SVMにでもかけてパラメタを追い込めばF1値にして0.95以上のスコアが得られたりするのですが、もうちょっとタチの悪いデータのつもりで扱います。</p><p> 具体的には、以下の処理をしてやることにします。</p> <ol> <li>RandomForestの特徴重要度を使って特徴選択</li> <li>PCAで累積寄与率pに次元削減</li> <li>SVMで分類</li> <li>グリッドサーチでパラメタチューニング</li> <li>交差検証して性能評価</li> </ol><p> 大変そうです。でも、Pipelineを使えばすぐできます。</p> </div> <div class="section"> <h3 id="実装">実装</h3> <p> まず、上の1~3について、それぞれの部品を作ります。それからPipelineのインスタンスで一つにまとめます。グリッドサーチと交差検証は自分で書くことにします。</p> <div class="section"> <h4 id="特徴選択">特徴選択</h4> <p> これにはSelectFromModelを使うと良さそうです。分類器のfeature_importances_に基づき、重要度の高い特徴だけ残してくれます。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.SelectFromModel.html">sklearn.feature_selection.SelectFromModel &mdash; scikit-learn 0.21.3 documentation</a></p><p> 何次元残すかの指定ができると使いやすかったのですが、実際はmean, medianとそれらのfloat倍、そして重要度の下限を指定できるようです。とりあえずmeanとmedianのどちらかにしてみましょう。</p><p> 分類器には今回はRandomForestを使います。つまり、次のようなコードになります。</p> <pre class="code lang-python" data-lang="python" data-unlink><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_selection <span class="synPreProc">import</span> SelectFromModel rfc = RFC(n_estimators=<span class="synConstant">100</span>, n_jobs=-<span class="synConstant">1</span>) fs = SelectFromModel(rfc) </pre><p> パラメタはあとでGridsearchSVを使ってチューニングするので、今の段階で指定する必要はありません。</p> </div> <div class="section"> <h4 id="次元削減">次元削減</h4> <p> 次元削減にはPCAを使います。普通にやるだけなので説明は割愛します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA pca = PCA() </pre> </div> <div class="section"> <h4 id="分類">分類</h4> <p> 分類にはSVMを使います。これもインスタンスを作っておきます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> sklearn.svm <span class="synPreProc">import</span> SVC svm = SVC() </pre> </div> <div class="section"> <h4 id="パイプライン化">パイプライン化</h4> <p> 今回の記事のキモです。パイプラインを使って上記「特徴選択」「次元削減」「分類」をすべてまとめてしまいます。</p><p> 書き方はこんな感じです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> sklearn.pipeline <span class="synPreProc">import</span> Pipeline estimators = <span class="synIdentifier">zip</span>([<span class="synConstant">&quot;feature_selection&quot;</span>, <span class="synConstant">&quot;pca&quot;</span>, <span class="synConstant">&quot;svm&quot;</span>], [fs, pca, svm]) pl = Pipeline(estimators) </pre><p> とてもあっさりしていますが、これで特徴選択をし、次元削減して、分類するという一連の流れをまとめて行うインスタンスができました。</p> </div> <div class="section"> <h4 id="パラメタチューニング">パラメタチューニング</h4> <p> パラメタチューニングはGridsearchCVを使うと簡単?にできます。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html">sklearn.model_selection.GridSearchCV &mdash; scikit-learn 0.21.3 documentation</a><br /> </p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> sklearn.model_selection <span class="synPreProc">import</span> GridSearchCV parameters = {<span class="synConstant">&quot;feature_selection__threshold&quot;</span> : [<span class="synConstant">&quot;mean&quot;</span>, <span class="synConstant">&quot;median&quot;</span>], <span class="synConstant">&quot;pca__n_components&quot;</span> :[<span class="synConstant">0.8</span>, <span class="synConstant">0.5</span>], <span class="synConstant">&quot;svm__gamma&quot;</span> : [<span class="synConstant">0.001</span>, <span class="synConstant">0.01</span>, <span class="synConstant">0.05</span>], <span class="synConstant">&quot;svm__C&quot;</span>: [<span class="synConstant">1</span>, <span class="synConstant">10</span>]} clf = GridSearchCV(pl, parameters) </pre><p> ちょっとパラメタ指定周りが面倒くさいですが、とにかくこうすれば後は全部自動でやってくれます。パラメタは「モデルにつけた名前__(アンダーバー2つ)パラメータ名」という形で書いてください。</p> </div> <div class="section"> <h4 id="交差検証">交差検証</h4> <p> これはStratifiedKFoldを使い、後は自分で書きます。クロスバリデーションをやってくれる関数もsklearnにはありますが、今回はちょっと複雑な制御(1回目のFoldでパラメタチューニングして2回目以降はそのパラメタを使いまわしたい)をするので使いません。</p> </div> </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_digits <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, GridSearchCV <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.svm <span class="synPreProc">import</span> SVC <span class="synPreProc">from</span> sklearn.feature_selection <span class="synPreProc">import</span> SelectFromModel <span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA <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>(): rfc = RFC(n_estimators=<span class="synConstant">100</span>, n_jobs=-<span class="synConstant">1</span>) fs = SelectFromModel(rfc) pca = PCA() svm = SVC() estimators = <span class="synIdentifier">zip</span>([<span class="synConstant">&quot;feature_selection&quot;</span>, <span class="synConstant">&quot;pca&quot;</span>, <span class="synConstant">&quot;svm&quot;</span>], [fs, pca, svm]) pl = Pipeline(estimators) parameters = {<span class="synConstant">&quot;feature_selection__threshold&quot;</span> : [<span class="synConstant">&quot;mean&quot;</span>, <span class="synConstant">&quot;median&quot;</span>], <span class="synConstant">&quot;pca__n_components&quot;</span> :[<span class="synConstant">0.8</span>, <span class="synConstant">0.5</span>], <span class="synConstant">&quot;svm__gamma&quot;</span> : [<span class="synConstant">0.001</span>, <span class="synConstant">0.01</span>, <span class="synConstant">0.05</span>], <span class="synConstant">&quot;svm__C&quot;</span>: [<span class="synConstant">1</span>, <span class="synConstant">10</span>]} gclf = GridSearchCV(pl, parameters, n_jobs=-<span class="synConstant">1</span>, verbose=<span class="synConstant">2</span>) digits = load_digits() X = digits.data y = digits.target first_fold = <span class="synIdentifier">True</span> trues = [] preds = [] <span class="synStatement">for</span> train_index, test_index <span class="synStatement">in</span> SKF().split(X, y): <span class="synStatement">if</span> first_fold: gclf.fit(X[train_index], y[train_index]) clf = gclf.best_estimator_ first_fold = <span class="synIdentifier">False</span> clf.fit(X[train_index,], y[train_index]) trues.append(y[test_index]) preds.append(clf.predict(X[test_index])) true_labels = np.hstack(trues) pred_labels = np.hstack(preds) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;p:{0:.6f} r:{1:.6f} f1:{2:.6f}&quot;</span>.format( *prf(true_labels, pred_labels, average=<span class="synConstant">&quot;macro&quot;</span>))) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> 実行するとGridsearchCVのverboseがたくさん出力された後、スコアが出てきます。スコアは今回は</p> <pre class="code" data-lang="" data-unlink>p:0.948840 r:0.948205 f1:0.948379</pre><p> でした。</p><p> かなり面倒くさい処理なのに、Pipelineのおかげでシンプルに書けているのがお分かりいただけたでしょうか。</p><p> 便利なのでこれから色々使っていこうと思います。</p> </div> <div class="section"> <h3 id="参考サイト">参考サイト</h3> <p><a href="http://tkzs.hatenablog.com/entry/2016/06/26/093502">Scikit-learn&#x306E;pipeleine.Pipeline&#x304C;&#x4FBF;&#x5229; - KAZ log TechMemo</a><br /> <a href="https://qiita.com/SE96UoC5AfUt7uY/items/c81f7cea72a44a7bfd3a">Scikit learn&#x3088;&#x308A; &#x30B0;&#x30EA;&#x30C3;&#x30C9;&#x30B5;&#x30FC;&#x30C1;&#x306B;&#x3088;&#x308B;&#x30D1;&#x30E9;&#x30E1;&#x30FC;&#x30BF;&#x6700;&#x9069;&#x5316; - Qiita</a></p> </div> hayataka2049 【python】正準相関分析(Canonical Correlation Analysis)を試してみる hatenablog://entry/17391345971616763473 2018-02-16T02:13:14+09:00 2019-08-22T18:36:02+09:00 正準相関分析を使うと、2つの多次元データ同士の関連性を分析できるらしい。 面白そうなので試してみた。ちなみに正準相関はsklearn.cross_decomposition.CCAで使える。正準相関自体の解説はほとんどしないので、文中のリンクを参考にして欲しい*1。 目次 一応概要だけ ノイズに埋もれた波形を取り出す もうちょっとデータ分析っぽいことをしてみる 正準相関向きのデータを探すのは困難 作成したデータ 実験と結果 スポンサーリンク (adsbygoogle = window.adsbygoogle || []).push({}); 一応概要だけ 代表的な多変量解析の手法(といって良い… <p> 正準相関分析を使うと、2つの多次元データ同士の関連性を分析できるらしい。</p><p> 面白そうなので試してみた。ちなみに正準相関はsklearn.cross_decomposition.CCAで使える。正準相関自体の解説はほとんどしないので、文中のリンクを参考にして欲しい<a href="#f-e0b910be" name="fn-e0b910be" title="正準相関でググって1ページ目に出てくるようなページばかり・・・">*1</a>。</p><p> 目次</p> <ul class="table-of-contents"> <li><a href="#一応概要だけ">一応概要だけ</a></li> <li><a href="#ノイズに埋もれた波形を取り出す">ノイズに埋もれた波形を取り出す</a></li> <li><a href="#もうちょっとデータ分析っぽいことをしてみる">もうちょっとデータ分析っぽいことをしてみる</a><ul> <li><a href="#正準相関向きのデータを探すのは困難">正準相関向きのデータを探すのは困難</a></li> <li><a href="#作成したデータ">作成したデータ</a></li> <li><a href="#実験と結果">実験と結果</a></li> </ul> </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><br /> </p> <div class="section"> <h3 id="一応概要だけ">一応概要だけ</h3> <p> 代表的な多変量解析の手法(といって良いのかどうか少し悩むけど)として、主成分分析や重回帰分析が存在する。</p> <ul> <li>主成分分析:一つの多変量データを直交するより少ない変数に縮約する</li> <li>重回帰分析:一つの多変量データを一つの単変量データに変換する</li> </ul><p> 主成分分析にしろ重回帰分析にしろ、変換の係数だったり行列だったりを求めてそれで変換するのが実際にやることである。</p><p> さて、正準相関は上の流れで説明すると、</p> <ul> <li>正準相関分析:二つの多変量データをそれぞれ直交するより少ない変数に縮約して、かつ二つの変換されたデータの間で相関を最大化する</li> </ul><p> という目的の分析である。主成分分析と重回帰を混ぜた感じ。</p><p> 気づいた人もいると思うけど、多変量vs多変量のデータでどちらかを単変量に分解して個別に重回帰で解くことも可能である。それに対するメリットとしては、</p> <ul> <li>個別に重回帰するより全体の構造みたいなものを捉えられる可能性がある</li> <li>個別に重回帰すると係数の数が全体でとても多くなるので解釈が面倒くさいが、一度次元を下げて直交した空間に持っていくことでそこが楽になる</li> </ul><p> というあたりがあり、要するに解釈性がいいということ。</p><p> この説明でもよくわからん、という人は、ニューラルネットのオートエンコーダーとか思い浮かべていただくとかえってわかりやすいかもしれない。</p> </div> <div class="section"> <h3 id="ノイズに埋もれた波形を取り出す">ノイズに埋もれた波形を取り出す</h3> <p> 参考URLの通りにやることにする。</p><p> 単一の信号源に複数のプローブを当てていて、それぞれに独立のノイズが乗って信号が埋もれてしまった・・・みたいな状況から元の信号を取り出そうとしているらしい。脳波計測とかで使えるのだろうか?</p><p> 参考URL:<a href="https://www.jstage.jst.go.jp/article/jnns/20/2/20_62/_pdf">https://www.jstage.jst.go.jp/article/jnns/20/2/20_62/_pdf</a></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">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> sklearn.cross_decomposition <span class="synPreProc">import</span> CCA <span class="synStatement">def</span> <span class="synIdentifier">plot_wave</span>(data, filename): fig, [ax1,ax2] = plt.subplots(<span class="synConstant">2</span>,<span class="synConstant">1</span>,figsize=(<span class="synConstant">16</span>,<span class="synConstant">9</span>)) ax1.plot(data[:,<span class="synConstant">0</span>], color=<span class="synConstant">&quot;b&quot;</span>) ax2.plot(data[:,<span class="synConstant">1</span>], color=<span class="synConstant">&quot;r&quot;</span>) plt.savefig(filename) <span class="synStatement">def</span> <span class="synIdentifier">gen_pulse_data</span>(): common_pulse = np.array([-<span class="synConstant">1</span>]*<span class="synConstant">50</span> + [<span class="synConstant">0</span>]*<span class="synConstant">50</span> + [<span class="synConstant">1</span>]*<span class="synConstant">50</span> + [<span class="synConstant">0</span>]*<span class="synConstant">50</span> + [-<span class="synConstant">1</span>]*<span class="synConstant">50</span> + [<span class="synConstant">0</span>]*<span class="synConstant">50</span> + [<span class="synConstant">1</span>]*<span class="synConstant">50</span> + [<span class="synConstant">0</span>]*<span class="synConstant">50</span>, dtype=np.float64) common_pulse += (np.random.random(common_pulse.shape) - <span class="synConstant">0.5</span>)*<span class="synConstant">0.1</span> noise1 = (np.random.random(common_pulse.shape) - <span class="synConstant">0.5</span>)*<span class="synConstant">50</span> noise2 = (np.random.random(common_pulse.shape) - <span class="synConstant">0.5</span>)*<span class="synConstant">50</span> data1 = np.vstack([common_pulse + noise1, common_pulse - noise1]).T data2 = np.vstack([common_pulse + noise2, common_pulse - noise2]).T <span class="synStatement">return</span> data1, data2 <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): X1, X2 = gen_pulse_data() plot_wave(X1, <span class="synConstant">&quot;X1.png&quot;</span>) plot_wave(X2, <span class="synConstant">&quot;X2.png&quot;</span>) cca = CCA(n_components=<span class="synConstant">2</span>) cca.fit(X1, X2) Y1 = cca.transform(X1) Y2 = cca.transform(X2) plot_wave(Y1, <span class="synConstant">&quot;Y1.png&quot;</span>) plot_wave(Y2, <span class="synConstant">&quot;Y2.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> 実行する。</p><p> まずは元の信号。</p><p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180216/20180216011146.png" alt="f:id:hayataka2049:20180216011146p:plain" title="f:id:hayataka2049:20180216011146p:plain" class="hatena-fotolife" itemprop="image"></span><br />  X1</p><p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180216/20180216011150.png" alt="f:id:hayataka2049:20180216011150p:plain" title="f:id:hayataka2049:20180216011150p:plain" class="hatena-fotolife" itemprop="image"></span><br />  X2</p><p> わかる訳ねえな、という感じ。</p><p> CCAでX1とX2の相関が最大になるような変換を計算し、その変換に基いてX1を変換したものをY1とすると、</p><p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180216/20180216011240.png" alt="f:id:hayataka2049:20180216011240p:plain" title="f:id:hayataka2049:20180216011240p:plain" class="hatena-fotolife" itemprop="image"></span><br />  Y1</p><p> こんな感じになった。Y2も同じようなものなので省略。ここでは2次元出しているが、元のパルス信号が1次元なので2次元目(下)はノイズだけ出ている。</p><p> まあ、上手く動いているのではないだろうか。</p> </div> <div class="section"> <h3 id="もうちょっとデータ分析っぽいことをしてみる">もうちょっとデータ分析っぽいことをしてみる</h3> <p> 如何せん、「ノイズに埋もれた信号を取り出せる!」というだけでは、データ分析っぽくなくて(個人的には)面白くない。正準相関自体はもっと色々なことができるはず。</p><p> ここで足を引っ張るのは「正準相関向きのサンプルデータが見つからない」ということである。</p> <div class="section"> <h4 id="正準相関向きのデータを探すのは困難">正準相関向きのデータを探すのは困難</h4> <p> 2つの多次元データが対応しているようなデータで、適当にわかりやすいものがあれば良いのだが・・・なかなか良いデータがない。上に挙げた解説論文でも、「知名度は低い」とか書かれちゃってるし、正準相関自体、ニッチな感じがする。そこが素敵なのだが。</p><p> 一応、ネット上にある解説例だと、</p> <ul> <li><a href="http://www.snap-tck.com/room04/c01/stat/stat19/stat1901.html">&#x7D71;&#x8A08;&#x5B66;&#x5165;&#x9580;&minus;&#x7B2C;19&#x7AE0;</a></li> </ul><p> 医学の分野で、肝機能の検査値(複数)と腎機能の検査値(複数)の対応を見るとか、</p> <ul> <li><a href="http://ogasun.la.coocan.jp/hanbetsubunseki.pdf">http://ogasun.la.coocan.jp/hanbetsubunseki.pdf</a></li> </ul><p> 中学生の体格(身長、体重、座高とか)と運動能力(50m走、走り幅跳びとか)の対応を見るとか、</p><p> そういう感じのことをやっているのだが、この手のデータを探してくるのがまず面倒くさいし、見つけてもプログラムに流し込めるようにするまでがまた苦行だろうな、ということは容易に想像できるのである。</p><p> この点で悩んで、この記事も一週間くらい出すか出さないか迷ってたんだけど、やることにした。ただ、結局良いデータは見つからなかったので、それっぽくでっちあげることにした。</p> </div> <div class="section"> <h4 id="作成したデータ">作成したデータ</h4> <p> ある架空の中学校で集計したという設定の、20人の生徒のデータである。「学外での勉強や取り組み」と「学校の成績」が対応付いている。</p><p> 「学外での勉強や取り組み」には、</p> <ul> <li>一ヶ月に何冊読書するか</li> <li>一年に何回博物館に行くか</li> <li>毎週何日塾に通っているか</li> <li>毎日何時間自習しているか</li> </ul><p> の4つの変数がある。一方、「学校の成績」は、</p> <ul> <li>国語</li> <li>数学</li> <li>社会</li> <li>理科</li> <li>英語</li> </ul><p> の5つの科目があり、5段階評価で成績が付く。</p><p> 本来であれば適当に線形モデルでも作ってあげて数字を作るべきところだが、面倒くさいので私の想像で適当に埋めた(ツッコミポイント)。</p><p> 一応、次のような方針を考え、それに沿ったデータになるようにでっちあげた・・・つもり。</p> <ul> <li>読書量と国語の成績は比例する</li> <li>博物館に行った回数と社会、理科の成績は比例する</li> <li>塾に通う頻度、自習時間は成績全体に影響を及ぼす</li> </ul><p> よって、こういう結果が出てくるか、という勝負になる。</p> </div> <div class="section"> <h4 id="実験と結果">実験と結果</h4> <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> scipy.stats <span class="synPreProc">import</span> pearsonr <span class="synPreProc">from</span> sklearn.cross_decomposition <span class="synPreProc">import</span> CCA <span class="synStatement">def</span> <span class="synIdentifier">gen_data</span>(): <span class="synComment"># X1:</span> <span class="synComment"># 毎月何冊の本を読むか,</span> <span class="synComment"># 一年に何回博物館に行くか,</span> <span class="synComment"># 塾に週何日通うか,</span> <span class="synComment"># 毎日何時間自習するか</span> X1 = np.array([[<span class="synConstant">1</span>,<span class="synConstant">0</span>,<span class="synConstant">2</span>,<span class="synConstant">1</span>], [<span class="synConstant">3</span>,<span class="synConstant">2</span>,<span class="synConstant">4</span>,<span class="synConstant">2</span>], [<span class="synConstant">0</span>,<span class="synConstant">0</span>,<span class="synConstant">2</span>,<span class="synConstant">0</span>], [<span class="synConstant">9</span>,<span class="synConstant">4</span>,<span class="synConstant">2</span>,<span class="synConstant">1</span>], [<span class="synConstant">1</span>,<span class="synConstant">1</span>,<span class="synConstant">3</span>,<span class="synConstant">1</span>], [<span class="synConstant">8</span>,<span class="synConstant">1</span>,<span class="synConstant">6</span>,<span class="synConstant">3</span>], [<span class="synConstant">0</span>,<span class="synConstant">9</span>,<span class="synConstant">7</span>,<span class="synConstant">8</span>], [<span class="synConstant">2</span>,<span class="synConstant">2</span>,<span class="synConstant">4</span>,<span class="synConstant">1</span>], [<span class="synConstant">5</span>,<span class="synConstant">0</span>,<span class="synConstant">0</span>,<span class="synConstant">1</span>], [<span class="synConstant">2</span>,<span class="synConstant">0</span>,<span class="synConstant">4</span>,<span class="synConstant">0</span>], [<span class="synConstant">0</span>,<span class="synConstant">0</span>,<span class="synConstant">7</span>,<span class="synConstant">8</span>], [<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">2</span>,<span class="synConstant">2</span>], [<span class="synConstant">5</span>,<span class="synConstant">1</span>,<span class="synConstant">2</span>,<span class="synConstant">1</span>], [<span class="synConstant">1</span>,<span class="synConstant">1</span>,<span class="synConstant">5</span>,<span class="synConstant">2</span>], [<span class="synConstant">8</span>,<span class="synConstant">6</span>,<span class="synConstant">2</span>,<span class="synConstant">1</span>], [<span class="synConstant">0</span>,<span class="synConstant">0</span>,<span class="synConstant">0</span>,<span class="synConstant">1</span>], [<span class="synConstant">6</span>,<span class="synConstant">1</span>,<span class="synConstant">3</span>,<span class="synConstant">1</span>], [<span class="synConstant">2</span>,<span class="synConstant">0</span>,<span class="synConstant">3</span>,<span class="synConstant">1</span>], [<span class="synConstant">4</span>,<span class="synConstant">8</span>,<span class="synConstant">5</span>,<span class="synConstant">3</span>], [<span class="synConstant">5</span>,<span class="synConstant">0</span>,<span class="synConstant">1</span>,<span class="synConstant">1</span>]]) <span class="synComment"># X2:</span> <span class="synComment"># 国語,数学,社会,理科,英語の成績</span> X2 = np.array([[<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>], [<span class="synConstant">4</span>,<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>], [<span class="synConstant">2</span>,<span class="synConstant">2</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">2</span>], [<span class="synConstant">5</span>,<span class="synConstant">4</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>], [<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">4</span>], [<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>], [<span class="synConstant">3</span>,<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>], [<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>,<span class="synConstant">3</span>], [<span class="synConstant">5</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>], [<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">3</span>], [<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>,<span class="synConstant">5</span>], [<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">3</span>], [<span class="synConstant">4</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>], [<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>], [<span class="synConstant">5</span>,<span class="synConstant">3</span>,<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">3</span>], [<span class="synConstant">2</span>,<span class="synConstant">2</span>,<span class="synConstant">2</span>,<span class="synConstant">1</span>,<span class="synConstant">2</span>], [<span class="synConstant">5</span>,<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">4</span>], [<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">3</span>], [<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">5</span>], [<span class="synConstant">5</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>]]) <span class="synStatement">return</span> X1, X2 <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): X1, X2 = gen_data() cca = CCA(n_components=<span class="synConstant">4</span>) cca.fit(X1, X2) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;Correlation Coefficient&quot;</span>) <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">4</span>): <span class="synIdentifier">print</span>(<span class="synConstant">&quot;{0}:{1:.4f}&quot;</span>.format(i, pearsonr(cca.x_scores_[:,i], cca.y_scores_[:,i])[<span class="synConstant">0</span>])) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;&quot;</span>) np.set_printoptions(formatter={<span class="synConstant">'float'</span>: <span class="synConstant">'{: 0.4f}'</span>.format}) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;X1 loadings&quot;</span>) <span class="synIdentifier">print</span>(cca.x_loadings_.T) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;&quot;</span>) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;X2 loadings&quot;</span>) <span class="synIdentifier">print</span>(cca.y_loadings_.T) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> 「学外での勉強や取り組み」=X1と「学校の成績」=X2を4次元の空間上に写像して相関を最大化する、という問題を解かせる。軸同士は直交していて無相関なので、写像したデータの各軸の値同士の相関だけ見てやれば良い。写像したデータは、cca.x(or y)_scores_かcca.transform(X1(or X2))で取得できる<a href="#f-57cab790" name="fn-57cab790" title="今回はどちらも同じ値が返るが、transformだと学習時とは違うデータも入れられる">*2</a>。</p><p> あとはX1とX2の各成分が、写像先の各軸にどれだけ寄与しているかがわかれば良い。そのためにはcca.x(or y)_loadings_を見る。転置した方が見やすいのでそうしている。</p><p> こうして見ると、PCAに似ている。実際、CCAとPCAは親戚らしい。ま、あまり理論的な話に深入りしてもボロが出るので、これくらいにしておく。</p><p> さて、結果はこのようになった。</p> <pre class="code" data-lang="" data-unlink>Correlation Coefficient 0:0.9558 1:0.8978 2:0.5980 3:0.2927 X1 loadings [[-0.4224 0.4244 1.0047 0.7353] [ 0.9326 0.4315 0.2511 0.3450] [ 0.2350 0.9269 -0.1714 -0.2514] [-0.4357 0.4062 -0.0634 0.8007]] X2 loadings [[-0.0558 0.7802 0.6574 0.6206 0.8044] [ 0.9392 0.5254 0.4702 0.3366 0.4271] [-0.2184 -0.0705 0.9353 0.5442 -0.3528] [-0.4310 0.0676 -0.2136 -0.8751 0.0008]]</pre><p> まず見るべきはCorrelation Coefficientで、写像先の空間の軸にどれだけ相関(=やった意味)があるかを示している。0,1,2次元目はまあまあ強い相関だが、3次元目は相関係数0.3じゃ大した意味はなさそうだな、という風に解釈しておく。</p><p> 次にX1 loadingsとX2 loadingsを見る。X1 loadingsは4*4、X2 loadingsは4*5で、つまり行が写像先の軸、列が元の空間の軸に対応するように表示している。</p><p> X1 loadingsの各行を見ていくと、</p> <ul> <li>1行目</li> </ul><p> 塾と自習に熱心</p> <ul> <li>2行目</li> </ul><p> 読書</p> <ul> <li>3行目</li> </ul><p> 博物館</p> <ul> <li>4行目</li> </ul><p> 自習と博物館だけ?</p><p> なんとか解釈できる。数字がでかいところだけ重視するのがこつ。X2 loadingsも同様にやると、</p> <ul> <li>1行目</li> </ul><p> 国語以外のすべて。国語にはほぼ中立。特に強いのは英語</p> <ul> <li>2行目</li> </ul><p> 国語。他もそれなりに</p> <ul> <li>3行目</li> </ul><p> 社会と理科</p> <ul> <li>4行目</li> </ul><p> 理科にとてもネガティブ。全体的にネガティブな感じ</p><p> ここまで出揃えば後はなんとかなる。このデータを作った方針を再掲する。</p> <ul> <li>読書量と国語の成績は比例する</li> <li>博物館に行った回数と社会、理科の成績は比例する</li> <li>塾に通う頻度、自習時間は成績全体に影響を及ぼす</li> </ul><p> 0次元目は「塾に通う頻度、自習時間は成績全体に影響を及ぼす」とに、1次元目は「読書量と国語の成績は比例する」に、2次元目は「博物館に行った回数と社会、理科の成績は比例する」に対応していることがわかり、まあ妥当な結果なんじゃないの、という気はする。相関係数の低い3次元目はそこまで重視する必要はない。</p><p> 今回は先に方針を決めてデータをでっち上げたのであまり感動がないような気もするが、実際はデータにどんな構造があるのかは分析してみないとわからない。その構造を理解する上で正準相関が役に立つことは、上の例でなんとなく理解できた。</p> </div> </div><div class="footnote"> <p class="footnote"><a href="#fn-e0b910be" name="f-e0b910be" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">正準相関でググって1ページ目に出てくるようなページばかり・・・</span></p> <p class="footnote"><a href="#fn-57cab790" name="f-57cab790" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">今回はどちらも同じ値が返るが、transformだと学習時とは違うデータも入れられる</span></p> </div> hayataka2049