記事・メモ一覧

default eye-catch image.

階層型クラスタリングとdendrogram / 空間拡散性、単調性の確認

前回適当に作ったデータを使って階層型クラスタリングを試してみる。 階層型クラスタリングの様子をdendrogramというツールを使って可視化できるのと、 クラスタ間距離の違いによってクラスタの作られ方が大きく変化することを dendrogramの違いを見ながら確認してみる。 テストデータを作る データは前回と同様に以下のように作る。 import numpy as np import matplotlib.pyplot as plt import seaborn as sns mu = [[0,0], [20,20], [50,50], [40,30], [40,10], [20,40]] sigma = [ [[30,20],[20,50]], [[20,30],[10,20]], [[60,40],[20,20]], [[60,20],[20,60]] ,[[30,10],[10,30]],[[50,20],[20,50]] ] points = 100 clusteres = [] for index in range(len(mu)): cluster = np.random.multivariate_normal(mu[index], sigma[index], points) dig = np.full((points,1),index+1, dtype=int) cluster = np.hstack((cluster,dig)) clusteres = np.r_[clusteres,cluster] if len(clusteres) > 0 else cluster plt.scatter(x=clusteres[:,0], y=clusteres[:,1],c=clusteres[:,2]) dendrogram 階層型クラスタリングの可視化を行う方法の1つとしてdendrogramがある。 葉(=各データポイント)から始まり、クラスタがまとまっていく様を木構造で表した図。 根は全てのデータポイントを1つのクラスタで表した状態。 左部分木と右部分木に1度別れるところが2つのクラスタに別れた状態。 グラフの縦軸はクラスタ間距離を表していて、dendrogramによって各クラスタの離れ度合いがわかる。 from sklearn.preprocessing import StandardScaler from scipy.cluster.hierarchy import linkage, dendrogram, fcluster SS = StandardScaler() scaled_clusters = SS.fit_transform(clusteres) result = linkage(scaled_clusters, method = \"ward\") dendrogram(result) plt.show() クラスタを作っていく際にどうクラスタ間距離を設定するかで木の構造がだいぶ変わってくる。 空間拡散性 以下はクラスタ間距離として最短距離法を使った場合。 (クラスタ間距離としてそれぞれに含まれるデータポイント間の距離の最小値) 1度併合してクラスタが大きくなると、次に、そのクラスタと隣のクラスタがくっつきやすい。 (データ数が多すぎてよくわからないけれど)下のdendrogramは 右、その1つ左、その1つ左...というように、右から順番に併合する傾向が強いのがわかる。 つまり1度クラスタが作られると、そのクラスタは次のクラスタになりやすい。 1度作られたクラスタは次にクラスタになりにくい性質、 (=あちこちでバランスよくクラスタが作られていって最後にそれぞれがまとまる性質) を空間拡散性というらしい。ほぅ。下の図は空間拡張性が無いことを表す。 クラスタ数を多くしていったとき、空間拡張性がないと\"でかいクラスタ\"と\"小さいクラスタ\"が共存する アンバランスな構造になりそう。 距離の単調性 以下は重心法を使った場合。 重心法というのは、クラスタとクラスタの距離として各クラスタの重心間の距離を使う方法。 まぁ妥当な距離な感じがするが..。 本来は、同時多発的にクラスタが出来上がり、より大きくなるにつれて、 より遠いクラスタと併合するのが\"あるべき姿\"なのだけれども、 1度クラスタがくっつくと、他のクラスタとの距離が以前よりも小さくなってしまう状態が発生する。 距離の単調性がない、というらしい..。むむ. dendrogramの縦方向はクラスタ間距離なので、 距離の単調性があるのであれば、左右の部分木は両方とも下方向に伸びるはず。 無いのであれば、左右のいずれかが上方向に伸びる。 ward法を使った場合 以下はWard法を使った場合。詳細は以下。無茶苦茶面倒臭い。 [clink url=\"https://ikuty.com/2019/07/09/hierarchical_clustering/\"] 空間拡散性、距離の単調性があって(バランス木という言葉は全然違うが..) バランス良く木ができている。 データ数が多すぎて全然見えないけど...

default eye-catch image.

6個のデータポイント近隣に発生させたデータ達を6-meansでクラスタ分割できるか

multivariate_normalを使って6個のデータポイント近隣にデータ達を発生させる。 薄くクラスタを見つけられそうだけれど境界は曖昧で大分被っているという状況。 そんなデータ達に6-meansをかけてみたとき、どうクラスタが出来るのかという実験。 被っている部分がどう別のクラスタに入るのか確認するため。 import numpy as np import matplotlib.pyplot as plt import seaborn as sns mu = [[0,0], [20,20], [50,50], [40,30], [40,10], [20,40]] sigma = [ [[30,20],[20,50]], [[20,30],[10,20]], [[60,40],[20,20]], [[60,20],[20,60]] ,[[30,10],[10,30]],[[50,20],[20,50]] ] points = 100 clusteres = [] for index in range(len(mu)): cluster = np.random.multivariate_normal(mu[index], sigma[index], points) dig = np.full((points,1),index+1, dtype=int) cluster = np.hstack((cluster,dig)) clusteres = np.r_[clusteres,cluster] if len(clusteres) > 0 else cluster plt.scatter(x=clusteres[:,0], y=clusteres[:,1],c=clusteres[:,2]) おもむろにsklearnのKMeansを使ってみる。 n_clustersは6、max_iterを10に設定してみた。 クラスタ中心がわかるように重ねてみた。 from sklearn.cluster import KMeans kmeans_model = KMeans(n_clusters=6, init=\'random\',max_iter=10).fit(clusteres[:,:2]) labels = kmeans_model.labels_ centers = kmeans_model.cluster_centers_ centers fig = plt.figure() ax1 = fig.add_subplot(1,1,1) ax1.scatter(x=clusteres[:,0], y=clusteres[:,1],c=labels) ax2 = fig.add_subplot(1,1,1) ax2.scatter(x=centers[:,0], y=centers[:,1], alpha=0.5,s=600,c=\"pink\",linewidth=2,edgecolors=\"red\") 当然のごとく、微妙に被っていた部分は異なるクラスタに分類された。 今回は当たり前なことを確認して終了。 2次元データじゃつまらない...。

default eye-catch image.

自力で生成したデータポイントに対して自力でクラスタ分割してみる

クラスタリングについて凄まじくわかりやすい説明を聞いて感動しました。 k-meansを自力で書くことで理解が深まりそうなので、参考にしながらアプトプットしてみます。 前回のエントリで、多次元正規分布に従う標本を作れるようになったので、 もともと存在する中心から適当に散らばるように作ったデータポイントに対して、 どの程度クラスタ分割できるのか確認する例です。 見てわからないと仕方がないので2次元とします。 早速、2つの中心から適当に散らばるようなデータポイントを作ってみます。 cluster1 = np.random.multivariate_normal([-10,-10], [[20,10],[30,20]], 100) cluster2 = np.random.multivariate_normal([10,10], [[50,5],[5,50]], 100) X = np.r_[cluster1, cluster2] plt.scatter(X[:,0], X[:,1]) 合計200個の点に、確率0.5でラベル1を振って(=確率0.5でラベル0)、 それぞれ色を付けて表示してみます。これを初期クラスタとします。 0クラスタ、1クラスタそれぞれ混じっていて、このままでは適切なクラスタ分割ではありません。 y = np.random.binomial(n = 1, p = 0.5, size = 200) plt.scatter(X[:,0], X[:,1],c=y) ラベル0のデータをX1、ラベル1のデータをX2という風に名前を付けて、 pandasデータフレーム化します。 さらに振ったラベル列を付けます。列追加は.assign()で出来るみたい。 import pandas as pd center = pd.DataFrame(X, columns = [\"X1\", \"X2\"]) center.shape # (200, 2) center = center.assign(y=y) center.shape # (200, 3) 0クラスタと1クラスタでまとめます。SQLで言うところのGroupBy句。 メソッド名もまんまgroupbyなのでわかりやすい。 GroupByした後、平均を計算する。0クラスタ、1クラスタ毎のX1,X2の平均が出ているっぽい。 center = center.groupby(y).mean() # X1 X2 y # 0 -1.701594 -1.186065 0 # 1 1.278607 0.954203 1 いよいよ、各データポイントのクラスタを更新して、適切なクラスタに振っていきます。 pandasデータフレームからNumPyのArrayを取るのは結構面倒くさい。 locにより行を取得できる。その際pandasの列名を指定できる。 SQLでSELECT書いているのに近い。 クラスタ0、クラスタ1それぞれのデータポイントについて、 各クラスタにおけるクラスタ中心と距離を求めます。距離は2乗和。 center_cluster_0 = center.loc[0, [\"X1\", \"X2\"]].values center_cluster_1 = center.loc[1, [\"X1\", \"X2\"]].values center0 = center.loc[0,[\"X1\",\"X2\"]].values center1 = center.loc[0,[\"X1\",\"X2\"]].values dist0 = (X[:,0].reshape(200) - center0[0])**2 + (X[:,1].reshape(200) - center0[1])**2 dist1 = (X[:,0].reshape(200) - center1[0])**2 + (X[:,1].reshape(200) - center1[1])**2 クラスタ0のクラスタ中心との距離よりも、クラスタ1のクラスタ中心との距離の方が大きいということは クラスタ0に属するべきです。論理式の結果がTrueかFalseかのいずれかになることを使って 2つのクラスタに分けます。かなり無理やりな気がします。 真偽の扱いですが、Pythonの言語仕様上、0か0に類する値はFalse、それ以外はTrueです。 new_cluster_id = dist0 < dist1 元Webプログラマが普通にforを使って書いてみると以下の通りとなります。 new_cluster_id2 = [] for index in range(len(dist0)): new_cluster_id2.append( 0 if (dist0[index] < dist1[index]) else 1) np.reshape(new_cluster_id2,200) 生成した入力値に新しいクラスタIDを振って色分け表示してみます。 new_cluster_id = dist0 < dist1 plt.scatter(X[:,0], X[:,1], c = new_cluster_id) 綺麗にわかれた気がしますが、大元のクラスタが交差する部分において、 別のクラスタに分類されたデータがあるようです。

default eye-catch image.

多次元正規分布に従うデータを生成する

[mathjax] そろそろ適当なデータを見つけてきて手法を試すのとは別に、 自力でデータを作って試してみたいと思い、NumPyを使った生成法を調べてみた。 一口に乱数といっても、正規分布に従う標本の生成のこと。 多次元正規分布に従う標本をmultivariate_normalで生成して表示してみる。 1次元正規分布に従う標本 その前に普通の乱数。 平均(mu)、標準偏差(sigma)の正規分布(N(mu,sigma))に従う標本の生成。 numpy.random.normal((mu,sigma,n))。 以下、(mu=50,sigma=10)として1000個の標本を作って、 階級数100のヒストグラムとして表示する。 import numpy as np import matplotlib.pyplot as plt values = np.random.normal(50, 10,1000) plt .hist(values,bins=100) 多次元正規分布に従う標本 多次元の乱数を作るのに必要なのは、 各次元における平均(mu=begin{pmatrix}mu_1 \\ mu_2 end{pmatrix})と、分散共分散行列(sum=begin{pmatrix}sigma_{11} & sigma_{21} \\ sigma_{12} & sigma_{22}end{pmatrix})。 ちなみに、(sum=begin{pmatrix}sigma_{11} & sigma_{21} \\ sigma_{12} & sigma_{22}end{pmatrix} = begin{pmatrix} sigma_1^2 & sigma_{21} \\ sigma_{12} & sigma_2^2 end{pmatrix} = begin{pmatrix} sigma_1^2 & Cov(X_1,X_2) \\ Cov(X_2,X_1) & sigma_2^2 end{pmatrix} )。 対角に分散、それ以外にクロスする共分散が入る。 共分散は昔書いてた。 begin{eqnarray} r_{xy} &=& frac{C_{xy}}{S_x S_y} \\ &=& frac{sum_{i=1}^n(x_i-bar{x})(y_i-bar{y})/n}{sqrt{sum_{i=1}^n{(x_i-bar{x})^2}/n} sqrt{sum_{i=1}^n{(y_i-bar{y})^2}/n}} \\ &=& frac{sum_{i=1}^n(x_i-bar{x})(y_i-bar{y})}{sqrt{sum_{i=1}^n{(x_i-bar{x})^2}} sqrt{sum_{i=1}^n{(y_i-bar{y})^2}}} \\ end{eqnarray} [clink url=\"https://ikuty.com/2018/10/17/covariance_zero/\"] 平均(mu=begin{pmatrix} 0 \\ 0end{pmatrix})、分散共分散行列(sum=begin{pmatrix}30 & 20 \\ 20 & 50end{pmatrix})の2次元正規分布に従う標本を1000個作る。 import numpy as np import matplotlib.pyplot as plt import seaborn as sns mu = [0,0] sigma = [[30,20],[20,50]] values = np.random.multivariate_normal(mu, sigma, 1000) sns.jointplot(x=values[:,0],y=values[:,1]) 散布図とヒストグラムを良い感じに表示してくれるseaborn.jointplotを使って表示してみる。 第1軸、第2軸とも平均は0っぽい。 第1軸は-15から15、第2軸は-25から25くらいが入っていて、対角とあってそう。

default eye-catch image.

Ward距離を使った階層型クラスタリング (お試し実行なし)

[mathjax] 階層型クラスタリングについてわかりやすい解説を聞いたので頭の中にあるものを書き出してみます。 (せっかく聞いたシャープな解説が台無しになってしまっているかと思いますが...) 本当はもっとアクティブなアウトプット方法があれば良いのですが、今のところはブログしかないのと、 WordPressのエントリという単位が、アウトプットの量として個人的に丁度良いなと思いますので。 初学なので解釈の誤りとか検討不足があるかと思いますが、本日も書いていきます! 試しに実行してみるためのデータを作る面倒さの洗礼を受けまして、 実際に試すのは別記事になります。 [clink url=\"https://ikuty.com/2019/07/16/hierarchical_clustering_by_method/\"] 階層型クラスタリング 小さいクラスタを大きいクラスタで内包する様が木構造(階層構造)になることから、 階層型クラスタリング、という名前が付いているんでしょうかね。 最初にk個のクラスタに分割する! みたいな問題であればk-means法、 何個のクラスタに分割すべきかはやってみて考えるのであれば、階層型クラスタリング。 木の深さとクラスタ数を対応させるところに見事な美しさを感じます。 クラスタ間距離として以下のWard距離を使うことで空間拡張性と非単調性に優れた結果を得られやすく、 よく傾向が分かっていないデータに対して一発目に適用すべき方法の様子。 begin{eqnarray} d_{ward} (C,C\') = sum_{iin Ccup C\'} || x_i - bar{x}_{Ccup C\'}||^2 - left( sum_{i in C} ||x_i-bar{x}_C||^2 + sum_{iin C\'}||x_i - bar{x}_{C\'}||^2 right) end{eqnarray} 階層型クラスタリングのアルゴリズム 以下の通り。最初、全てのデータポイントをクラスタとみなすところからスタートする。 クラスタとクラスタを併合してより大きいクラスタを作る処理であって、 繰り返しを止めなければ最終的に一つのクラスタまで成長する。 つまり、どこかで止めることで、その時点のクラスタ分割を得ることができる。 全データポイントそれぞれをクラスタとする 全てのデータポイントが初期クラスタ以外に含まれるまで以下の計算を繰り返す 各クラスタ間の距離を計算する。 最もクラスタ間距離が小さいクラスタを併合する。 クラスタを成長させる際に、うまい具合に併合相手を選ばないとよくない。 全体としてクラスタがバラけて欲しいのだけれども、クラスタが隣のクラスタを併合した結果、 次の併合先としてその隣のクラスタが対象となってしまうかもしれない。 そうすると、単調に端っこから順番に成長することになり、 「大きなクラスタ」と「その他の小さなクラスタ」みたいになる。 何らかの距離に基づいてクラスタとクラスタを併合するのだけれども、 クラスタを併合した結果、併合後のクラスタと別のクラスタの距離が、 より短くなってしまう、というケースも起こってしまう。 クラスタ内のデータポイント間の距離、とか、クラスタの重心間の距離とかでは 上記の問題が起こりやすい。 先に距離があって次に併合を考えるのではなく、併合前後の条件を使って距離を作ることで、 上記の問題が起こりにくくなる。その一つがWard距離。 Ward距離 データポイント間の距離はユークリッド距離を使うとして、クラスタ間の距離をどう決めるのか? データポイントをクラスタ(C)とクラスタ(C\')に分けるとする。分ける前は(Ccup C\')。 (逆? クラスタ(C)とクラスタ(C\')を併合してクラスタ(Ccup C\')を作る)。 クラスタ(C)とクラスタ(C\')の距離(d_{ward}(C,C\'))を以下のように定義する。 begin{eqnarray} d_{ward} (C,C\') = sum_{iin Ccup C\'} || x_i - bar{x}_{Ccup C\'}||^2 - left( sum_{i in C} ||x_i-bar{x}_C||^2 + sum_{iin C\'}||x_i - bar{x}_{C\'}||^2 right) end{eqnarray} 併合後のクラスタ(Ccup C\')における重心との距離の2乗和( sum_{iin Ccup C\'} || x_i - bar{x}_{Ccup C\'}||^2)から、 併合前のクラスタ(C)における重心との距離の2乗和(sum_{i in C} ||x_i-bar{x}_C||^2)、 (C\')における重心との距離の2乗和(sum_{iin C\'}||x_i - bar{x}_{C\'}||^2)を引いたもの。 当たり前だけれども、大きいクラスタよりも小さいクラスタの方が凝集度が小さい。 つまり、併合前のクラスタ(C)、(C\')を併合してクラスタ(Ccup C\')を作るとなると、 (Ccup C\')の凝集度よりも、(C)、(C\')の凝集度の方が必ず小さい。 (C)の凝集度と(C\')の凝集度の和よりも、(Ccup C\')の凝集度の方が大きくなることがポイント。 要するに(Ccup C\')を(C)、(C\')に分割することは、全体の凝集度が小さくなるような操作を行うということ。 無数にある分割の仕方の中から、凝集度の総和が小さくなるような「マシな分割」を決めたい! (sum_{i in C} ||x_i-bar{x}_C||^2+sum_{iin C\'}||x_i - bar{x}_{C\'}||^2)よりも( sum_{iin Ccup C\'} || x_i - bar{x}_{Ccup C\'}||^2)の方が大きいから、 (d_{ward})は正の値となる。(d_{ward})が大きければそれはマシな分割。 そもそも、データ達が何をもってクラスタを形成しているのかって考えるとかなり微妙で、 人間が解釈するための合理的な根拠を作り出す手段でしかないことがわかった。 データポイント間の\"距離\"を何らかの方法で定義し、\"距離が近しい塊をクラスタと言う\" という話であるとすれば、確かに合理的なのだろうけども。 モヤモヤしているのは、k個のクラスタ集合とk+1個のクラスタ集合を比較したときに、 前者と後者で異なるクラスタに属するデータポイントが発生するところ。 クラスタリングとは単に境界を引く処理なのであって、 それにどういう意味を付けるかは目的次第なのだろうと改めて認識した次第。 そもそも、これらは単なる道具でしかないので、他の手段も同じく 出力結果にどう意味付けするのかは目的次第なんだろうなと。 最初から何個のクラスタに分割すべきか分かっているときはk-means法を使えば良さそう。 今回は、そもそも何個に分割すべきか分かっていないときに使う手法。 [clink url=\"https://ikuty.com/2019/06/29/k-means_idea/\"]

default eye-catch image.

k-means法と近似解法 考え方

[mathjax] 教師なし学習の問題。クラスタリング。 クラスタの個数を事前に指定するタイプと、自分でクラスタ数を設定できるタイプがあります。 今回、前者のk-means法をアイデアを聞いたので、まとめなおしてみようと思います。 k-means法 (C_1,cdots,C_K)がクラスタ。各々のクラスタにおける中心は(bar{x_k})(centroid)。 各々のクラスタにおけるcentroidとの距離の2乗を全部足して、 それが最小になるようなクラスタ(C_1,cdots,C_K)を求めるという問題。 begin{eqnarray} newcommand{argmin}[1]{underset{#1}{operatorname{arg},operatorname{min}};} hat{c_1},cdots,hat{c_K} = argmin {c_1,cdots,c_K} sum_{k=1}^K sum_{i in C_k} || x_i - bar{x_k}||^2 end{eqnarray} 距離の2乗なので異常値があると異常値に引っ張られて、クラスタが大きく変わってしまう。 centroid(標本平均)も異常値に引っ張られる。外れ値を除去しないといけない。 距離の2乗を計算するので、ベクトル(v_i)の各次元のスケールが揃っていないと、 計算の過程で単位が大きい要素に引っ張られてしまう。 単位の大きさは重要度とは関係ないのに、単位が大きいことが距離に影響を与えることは 避けないといけない。その対応として単位を揃える(正規化)する。 begin{eqnarray} |x_i-bar{x}|^2 = begin{pmatrix} x_{i1} - bar{x_1} \\ x_{i2} - bar{x_2} \\ vdots \\ x_{in} - bar{x_n} \\ end{pmatrix} = sum_{i=1}^{n} sqrt{x_{i1}-bar{x_1}}^2 end{eqnarray} そのままだとNP困難->近似解法 データポイントの個数、centroidの位置がそれぞれ自由だと、 定義通りに実装しようとしても(mathcal{O}(n^3))となって使えない。 begin{eqnarray} newcommand{argmin}[1]{underset{#1}{operatorname{arg},operatorname{min}};} hat{c_1},cdots,hat{c_K} = argmin {c_1,cdots,c_K} sum_{k=1}^K sum_{i in C_k} || x_i - bar{x_k}||^2 end{eqnarray} 以下のようにランダムなクラスタを初期値として設定し、 より良いクラスタに更新することで計算量を下げる。 データ数(n)、クラスタ数(k)だと(mathcal{O}(nk))。 各データポイントにランダムなクラスタを割り当てる 以下の処理を繰り返す 各データポイントに割り当てられたクラスタのクラスタ中心を計算する 各データポイントについてクラスタ中心との距離を計算する 各データポイントについて距離が最小のクラスタを設定する k-meansで実際にクラスタリングするコードを書いてみようと思いましたが、 そろそろ適当なデータを見つけてくるのではなく、シミュレーション用のデータを作るところも やってみようと思います。次回、pythonでコード書きます。

default eye-catch image.

特異値分解 Singular Value Decomposition

[mathjax] 特異値分解 (M times N)の行列(A)を以下の3つに分解する。 (M times M)の直行行列(V) (M times N)の対角行列(S) (N times N)の直行行列(U) 以下みたいな感じに分解するのが特異値分解。 begin{eqnarray} A &=& VSU^T \\ &=& begin{pmatrix} v_{11} & cdots & v_{1M} \\ vdots & ddots & vdots \\ v_{M1} & cdots & v_{MM} end{pmatrix} begin{pmatrix} lambda_1 & cdots & 0 \\ vdots & ddots & vdots \\ 0 & cdots & lambda_r \\ & & & 0 & \\ & & & & 0 end{pmatrix} begin{pmatrix} u_{11} & cdots & u_{N1} \\ vdots & ddots & cdots \\ u_{1N} & cdots & u_{NN} end{pmatrix} end{eqnarray} (V)は列ベクトル(v_1),(v_2),(cdots),(v_M)を横に並べたもの。 (U^T)は列ベクトル(u_1),(u_2),(cdots),(u_N)を横に並べたものの転値。 (A)のランクは(r)で、(S)は(r)個の(lambda)からなる対角行列。 (lambda_i)は対角行列なので対角の非ゼロの項だけ残す意味で以下のようにかける。 (lambda_i)自体はスカラだから前に寄せられる。 begin{eqnarray} A = sum_{i=1}^{r} lambda_i v_i u_i^T end{eqnarray} 特異値分解のやり方 やりたいことは、(v_i)、(u_i)、(lambda_i)を求めること。 (AA^T)、(A^TA)の固有値、固有ベクトルを計算することで求まる。 主成分分析の対象とするとき、(A)が縦長の行列であることが多い、 つまり高次元のベクトルであることが多いという事実から、 (AA^T)は巨大な行列となり、直接計算することは困難。 以下みたいに(AA^T)を計算する。 begin{eqnarray} AA^T &=& VSU^T(VSU^T)^T \\ &=& VSU^T US^T V^T \\ &=& VSS^TV^T \\ &=& VS_M^2 V^T end{eqnarray} 右から(V)をかける。 begin{eqnarray} AA^T V &=& VS_M^2 \\ AA^T v_i &=& lambda_i^2 v_i end{eqnarray} これは、(AA^T)を固有値(lambda_i^2)、固有ベクトル(v_i)に分解した形。 (固有値、固有ベクトルは計算できる)。 [clink url=\"https://ikuty.com/2019/06/17/eigenvalue-eigenvector/\"] 同様にして、(A^TA)を計算する。 (A^TA)の固有値、固有ベクトルの計算により(u_i)と(lambda_i)を求められる。 begin{eqnarray} A^TA &=& (VSU^T)^T VSU^T \\ &=& US_N^TS_nU^T \\ &=& US_N^2 U^T \\ (A^TA) U &=& US_N^2 \\ (A^TA) u_i &=& lambda_i^2 u_i end{eqnarray} (AA^T)と異なり、(A^TA)は大きくなく、容易に計算が可能。 (v_i)の求め方 (AA^T v_i = lambda_i^2 v_i)は大きすぎて計算できないが、(A^TA)を使って(AA^T)を計算できる。 以下のように式変形する。(u_k)の正規直行性を使う。 begin{eqnarray} A &=& sum_{i=1}^{r} lambda_i v_i u_i^T \\ Au_k &=& sum_{i=1}^{r} lambda_i v_i u_i^T u_k \\ &=& lambda_k v_k u_k ^T u_k \\ &=& lambda_k v_k \\ v_k &=& frac{1}{lambda_k} Au_k end{eqnarray} (A^TA)の特異値分解は計算可能。以下から(hat{u_i})と(hat{lambda_i})が求まる。 begin{eqnarray} (A^TA) hat{u_i} &=& hat{lambda}_i^2 hat{u_i} end{eqnarray} 得られた(hat{u_i})と(hat{lambda_i})を (v_k = frac{1}{lambda_k} Au_k)に入れることで、(v_k)が求まる。

default eye-catch image.

固有値、固有ベクトル

[mathjax] 線形代数もやりなおします。流石に時間ないので微妙に分かるところまでですが。 第1弾は固有値、固有ベクトル。 具体例を使って追ってみるやつ 行列(A)とベクトル(vec{v_1})をデタラメに選んで内積を取ってみる。 (A=begin{pmatrix} 2 & 5 \\ 3 & 7 end{pmatrix})。(vec{v_1}=begin{pmatrix} 1 \\ 4end{pmatrix})。 begin{eqnarray} vec{v_2} = Avec{v} = begin{pmatrix} 2 & 5 \\ 3 & 7 end{pmatrix} begin{pmatrix} 1 \\ 4end{pmatrix} = begin{pmatrix} 2 times 1 + 5 times 4 \\ 3 times 1 + 7 times 4 end{pmatrix} = begin{pmatrix} 22 \\ 31 end{pmatrix} end{eqnarray} (vec{v_1})と(vec{v_2})は向きが違うし大きさも違う。 (vec{v_1})に(A)をかけることで、回転して伸ばしてる。 (vec{v_1})と(vec{v_2})の向きが同じになるように(vec{v_1})を選べないか...。 下みたいな組み合わせだと、左と右が同じ向きになる。 begin{eqnarray} begin{pmatrix} 2 & 5 \\ 3 & 7 end{pmatrix} begin{pmatrix}0.575249 \\ 0.817978end{pmatrix} = begin{pmatrix}5.2403 \\ 7.4515end{pmatrix} end{eqnarray} 伸ばす分を外に出すと以下みたいになる。 begin{eqnarray} begin{pmatrix} 2 & 5 \\ 3 & 7 end{pmatrix} begin{pmatrix}0.575249 \\ 0.817978end{pmatrix} = 9.10977 begin{pmatrix}0.575249 \\ 0.817978end{pmatrix} end{eqnarray} 長さを伸ばす分の係数(9.10977)が行列(A)の固有値。 begin{pmatrix}0.575249 \\ 0.817978end{pmatrix}が行列(A)の固有ベクトル。 次数が多いやつ (A)は(n times n)の正方行列。固有ベクトル(vec{x})は(1 times n)。固有値(lambda)はスカラー。 begin{eqnarray} A vec{x} &=& lambda vec{x} end{eqnarray} 以下みたいに変形して(vec{x})と(lambda)を求める。 begin{eqnarray} (A-lambda E) vec{x} &=& vec{0} end{eqnarray} ((A-lambda E))に逆行列があると(Evec{x} = vec{0})とかになってゼロベクトルじゃない(vec{x})を求められないから、 逆行列が存在しない、という式を立てるらしい。。 \"逆行列が存在しない\"だけだと、(vec{x})も(lambda)も複数ありそうだけど絶対に1つしか存在しないらしい。 逆行列が存在しないのは、行列式がゼロ、とするらしい。 begin{eqnarray} det(A-lambda E) = 0 end{eqnarray} 手計算が合わないのでそれ以降は省略... まとめ 行列(A)の固有値、固有ベクトルは、 (A vec{x} = lambda vec{x})となる(vec{x})と(lambda)のセットを求めることでした。 向き、大きさの変換を表す(A)を、向きを表すベクトル(vec{x})と大きさ(lambda)で表しなおす..ような。

default eye-catch image.

sklearnに頼らずRidge回帰を自力で書いてみて正則化項の影響を考えてみるテスト

[mathjax] タイトルの通り。Losso回帰と違って損失関数を偏微分するだけで出来そうなのでやってみる。 Ridge回帰は線形回帰の1種だけれども、損失関数として最小二乗法をそのまま使わず、 (L_2)ノルムの制約を付けたものを使う((L_2)正則化)。 データとモデル 教師データ(boldsymbol{y})、訓練データ(boldsymbol{x})があるとする。 (または目的変数(boldsymbol{y})、説明変数(boldsymbol{x})があるとする。) 例えば(p)次の属性データが(n)個あり、それらと結果の対応が分かっている状況。 begin{eqnarray} boldsymbol{y} &=& begin{pmatrix} y_1 \\ y_2 \\ vdots \\ y_p end{pmatrix} , boldsymbol{x} &=& begin{pmatrix} x_{11} & x_{21} & cdots & x_{n1} \\ x_{12} & x_{22} & cdots & x_{n2} \\ vdots & vdots & ddots & vdots \\ x_{1p} & x_{2p} & cdots & x_{np} end{pmatrix} end{eqnarray} モデルは以下。特徴ベクトル(boldsymbol{w})は訓練データの重み。 特徴空間において損失を最小化する特徴ベクトルを求める問題。 begin{eqnarray} boldsymbol{y} &=& boldsymbol{w} boldsymbol{x} + k \\ boldsymbol{w} &=& begin{pmatrix} w_1 & w_2& cdots &w_p end{pmatrix} end{eqnarray} 損失関数 普通の2乗損失に正則化項((L_2)ノルムを定数倍した値)を付けたものを損失関数として利用する。 正則化項の係数はハイパーパラメータとして調整する値。逆数なのはsklearnに従う。 begin{eqnarray} L(boldsymbol{w}) = |boldsymbol{y} - boldsymbol{w} boldsymbol{x}|^2 +C |boldsymbol{w}|^2 end{eqnarray} 特徴ベクトルは以下。(mathjaxでargminが出せない...) begin{eqnarray} newcommand{argmin}[1]{underset{#1}{operatorname{arg},operatorname{min}};} boldsymbol{w} = argmin w L(boldsymbol{w}) = argmin w |boldsymbol{y} - boldsymbol{w} boldsymbol{x}|^2 + C |boldsymbol{w}|^2 end{eqnarray} 特徴ベクトルを求める 勾配=0と置けば上の式の解を得られる。 損失関数が微分可能だからできる技。 begin{eqnarray} frac{partial L(boldsymbol{w})}{partial boldsymbol{w}} &=& 2 boldsymbol{w}^T (boldsymbol{y} - boldsymbol{w} boldsymbol{x}) + C boldsymbol{w} \\ &=& 0 end{eqnarray} 変形する。 begin{eqnarray} 2 boldsymbol{x}^T (boldsymbol{x}boldsymbol{w}-boldsymbol{y}) + C boldsymbol{w} &=& 0 \\ boldsymbol{x}^T (boldsymbol{x}boldsymbol{w}-boldsymbol{y}) + C boldsymbol{w} &=& 0 \\ boldsymbol{x}^T boldsymbol{x} boldsymbol{w} -boldsymbol{x}^T boldsymbol{y} + Cboldsymbol{w} &=& 0 \\ (boldsymbol{x}^T boldsymbol{x} +C E) boldsymbol{w} &=& boldsymbol{x}^T boldsymbol{y} \\ boldsymbol{w} &=& (boldsymbol{x}^T boldsymbol{x} + C E)^{-1} boldsymbol{x}^T boldsymbol{y} end{eqnarray} テストデータを作る 練習用にsklearnのbostonデータを使ってみる。 ボストンの住宅価格が目的変数、属性データが説明変数として入ってる。 import pandas as pd import numpy as np from pandas import Series,DataFrame import matplotlib.pyplot as plt from sklearn.datasets import load_boston boston = load_boston() boston_df = DataFrame(boston.data) boston_df.columns = boston.feature_names print(boston_df.head()) boston_df[\"PRICE\"] = DataFrame(boston.target) # CRIM ZN INDUS CHAS NOX RM AGE DIS RAD TAX PTRATIO B LSTAT PRICE # 0 0.00632 18.0 2.31 0.0 0.538 6.575 65.2 4.0900 1.0 296.0 15.3 396.90 4.98 24.0 # 1 0.02731 0.0 7.07 0.0 0.469 6.421 78.9 4.9671 2.0 242.0 17.8 396.90 9.14 21.6 # 2 0.02729 0.0 7.07 0.0 0.469 7.185 61.1 4.9671 2.0 242.0 17.8 392.83 4.03 34.7 # 3 0.03237 0.0 2.18 0.0 0.458 6.998 45.8 6.0622 3.0 222.0 18.7 394.63 2.94 33.4 # 4 0.06905 0.0 2.18 0.0 0.458 7.147 54.2 6.0622 3.0 222.0 18.7 396.90 5.33 36.2 散布図行列を表示してみる。 PRICEと関係がありそうなZN,RM,AGE,DIS,LSTATの5個を使ってみる。 pg = sns.pairplot(boston_df) plt.show() pg.savefig(\'boston_fig.png\') 特徴ベクトルを自力で計算する これを自力で計算してみる。(C=0.01)、(C=0)、(C=100)としてみた。 begin{eqnarray} boldsymbol{w} &=& (boldsymbol{x}^T boldsymbol{x} + C E)^{-1} boldsymbol{x}^T boldsymbol{y} end{eqnarray} X_df = boston_df.drop(columns=[\'CRIM\',\'INDUS\',\'CHAS\',\'NOX\',\'RAD\',\'TAX\',\'PTRATIO\',\'B\',\'PRICE\']) X = X_df.values y = boston.target.T C1 = 0.01 C2 = 0 C3 = 100 e = np.identity(5) w1 = np.dot( np.linalg.inv(np.dot(X.T , X) + C1 * e), np.dot(X.T,y)) w2 = np.dot( np.linalg.inv(np.dot(X.T , X) + C2 * e), np.dot(X.T,y)) w3 = np.dot( np.linalg.inv(np.dot(X.T , X) + C3 * e), np.dot(X.T,y)) print(w1) # [ 0.05338557 5.40396159 -0.01209002 -0.83723303 -0.63725397] print(w2) # [ 0.05338539 5.40403743 -0.01209427 -0.83728837 -0.63725093] print(w3) # [ 0.05612977 4.76664789 0.02374402 -0.38576708 -0.66137596] (C=0)のとき、つまり最小二乗法のとき。 sklearnを使う sklearnのridge回帰モデルを使うと以下みたいになる。 from sklearn.linear_model import Ridge from sklearn.model_selection import train_test_split Xf_train,Xf_test,yf_train,yf_test = train_test_split(X,y,random_state=0) ridge = Ridge().fit(Xf_train,yf_train) print(f\"accuracy for training data:{ridge.score(Xf_train,yf_train):.2}\") print(f\"accuracy for test data:{ridge.score(Xf_test,yf_test):.2f}\") # accuracy for training data:0.68 # accuracy for test data:0.58 print(ridge.coef_) # [ 0.06350701 4.3073956 -0.02283312 -1.06820241 -0.73188192] 出てきた特徴ベクトルを並べてみる 自力で計算したものとsklearnに計算してもらったものを並べてみる。 似てるのか似ていないのかよくわからない .. けど、RMの寄与度が高いというのは似ている。 # 自力で計算 (C=100) # [ 0.05612977 4.76664789 0.02374402 -0.38576708 -0.66137596] # sklearnで計算 # [ 0.06350701 4.3073956 -0.02283312 -1.06820241 -0.73188192] 自力で計算したモデルの正答率を求めてみないとなんとも... そして、正規化項の係数の大小がどう影響するのか、あまり良くわからなかった..。 (L_2)ノルムの制約を付けると、パラメタの大小が滑らかになると言いたかったのだけども。 あと、訓練データに対して68%、テストデータに対して58%という感じで、 大して成績が良くない...。 

default eye-catch image.

NumPy uniqe, File I/O

集合関数 集合関数。ndarrayから重複を取り除きsortした結果を返す。 2dであってもその中から要素を抜き出して1dにする。 hoges = np.array([\"hoge\",\"fuga\",\"hoge\",\"fuga\"]) print(np.unique(hoges)) # [\'fuga\' \'hoge\'] fugas = np.array([[\"hoge\",\"fuga\",\"hoge\",\"fuga\"],[\"hoge2\",\"fuga2\",\"hoge2\",\"fuga2\"]]) print(np.unique(fugas)) # [\'fuga\' \'fuga2\' \'hoge\' \'hoge2\'] ファイルI/O pandasを使わずとも、NumPyだけでファイルI/Oができる。 以下でhoges.npyという無圧縮バイナリファイルが作られる。 それを読み込んで出力する。 hoges = np.array([\"hoge\",\"fuga\",\"hoge\",\"fuga\"]) np.save(\'hoges.npy\',hoges) fugas = np.load(\'hoges.npy\') print(fugas) # [\'hoge\' \'fuga\' \'hoge\' \'fuga\'] 複数の配列を同時に書き込むこともできる。 キーワードを指定して書き込む。キーワードを指定して1つずつ読み込む。 読み込む時はキーワードを指定して参照したときに遅延ロードされる。 hoges = np.array([\"hoge1\",\"fuga1\",\"hoge1\",\"fuga1\"]) fugas = np.array([\"hoge2\",\"fuga2\",\"hoge2\",\"fuga2\"]) np.savez(\'hogefuga.npz\', hoges=hoges, fugas=fugas) hogefugas = np.load(\'hogefuga.npz\') hoges_l = hogefugas[\'hoges\'] fugas_l = hogefugas[\'fugas\'] print(hgoes_l) # [\'hoge1\' \'fuga1\' \'hoge1\' \'fuga1\'] print(fugas_l) # [\'hoge2\' \'fuga2\' \'hoge2\' \'fuga2\']