MacでDockerお砂場を作る
docker on vagrant Docker for Macが遅いので,Webのコード書くのはvagrant+ansibleで済ませていたのだけれども, vagrant上にdockerを立てることでLinux上で走らせるのと同レベルの速度を得られるようなので, docker on vagrantを立ててdockerに入門してみる。 Web開発していない人から見ると,なんでdocker使わないの? ってことなのだが, 気持ちは以下の通り. 工夫無しだと1度試したら2度とやりたくないレベルで使い物にならない. Docker For Macが遅い:対策の実験 Macのdockerが遅いストレスから解放されよう ansibleで自動化してたから手間に気づかなかったけど, 公式がイメージ配ってるんだから,手間なしなのはdocker. 本番環境と開発環境を揃えにくいかなとも思うけど, AWS ECS的なものもあって,そもそもdockerだけで完結する世界が主流になりそうな感. docker on vagrantで遅さを回避できるので, 言い訳してないでアップデートしていく.. なぜ遅いのか Docker for Macは, AlpineLinuxベースのHyperkitVMの上で透過的にContainerを扱う. Mac上でDockerコマンドを実行すると,透過的にHyperkitVM上に反映される. Macの上で直接Containerが動いているのではなくこのVMの上でContainerを動かしている. HostとGuestのファイルシステムをマウントする観点ではvagrantも同じで, 実際,VagrantfileでマウントオプションとしてNFSを指定したとしてもNativeよりかなり遅い. ファイルシステムの対称性がある分,vagrantはHostとGuestをSyncする方法に手を入れやすく, HostとGuestのファイルシステムをNativeと同等レベルの速度でSyncする手段を導入できる. この仕組みによると, vagrantの上でDockerを動かすパフォーマンスがNative並に速くなる. Mutagen HostとGuestのファイルシステムをSyncするためにMutagenを利用する. Overviewによると,Mutagenは双方向の同期ツールで,低レイテンシをうたっている. もともとHostToGuestというよりはLocalToCloudの統合を目指している感じ. キーボードを打ってから反映されるまでのラグが気になる, とかOSが違う場合のアレコレ(permissionとかシンボリックリンックとか),とか, そういうところの解消を目指している様子. エージェントレス,TCP,でリモートへの導入コストが少ないのが良い. VagrantにはMutagen over SSHの形を取る. インストール手順 こちらが参考になりました。わかりやすく,手順通りやれば10分かかりません. Vagrantを使う「Mac最速のDocker環境」を初心者向けに解説【遅いMac for Dockerを卒業】 とりあえず箱だけ作った.Laravelだと分かりづらいのでRailsで試したところ激しく速かった. 副次的な効果として、Macを汚さないのでお砂場にぴったり.
微分フィルタだけで時系列データの過渡応答終了を検知したい
ある値の近傍を取る状態から別の値の近傍を取る状態へ遷移する時系列データについて、 どちらの状態でもない過渡状態を経て状態が遷移するみたい。 今回知りたいのは理論ではなく、定常状態に遷移したことをいかにして検知するかという物理。 まぁいかようにでも検知できなくもないけれども、非定常状態の継続時間は不明であるという。 なるべく遅れなしに定常状態への遷移を検知したい。 ARモデルとかMAモデルとかの出番ではない。 DeepLearningとかやってる暇はないw 過去の微小時間内に含まれるデータだけから次が定常状態なのかを検知したい。 前状態と後状態のベースラインがどんな値であっても、 それを考慮することなしに過渡応答の後にくる定常状態の先頭を検知できる、という特徴がほしい。 移動平均だとベースラインがケースごとに変わるので扱いづらい。 微分であればベースラインがゼロに揃うのが良いな、と思った。 結局、今度はゼロへの収束を検知する問題に差し代わるだけだけども、 前ラインと後ラインの2つも見ないでも、確実に楽にはなっている。 微分するとノイズが増えるが...。 微分値が上下の閾値を超えたラインから先、 ゼロ収束の判定条件が一定時間継続した最初のデータポイントがソコ。 これは過渡状態の終了検知なだけだから、それとRawデータの変化をみる。
ansibleでaws-cliをインストールする (+S3)
やりたいことは以下の2つ。 ansibleでaws-cliをインストールする ansibleでインストールしたaws-cliでs3コマンドを打てるようにする なお、相手には既にpipがインストールがしてあるものとします。 ansibleを実行するために最小構成でPythonをインストールしたもののpipは入れていない、 という状況であれば、先にpipをインストールする必要があります。 リージョン、S3のkey,secretは仮に以下とします。 事前にAWSのコンソールで設定,取得してください。 region: ap-northeast-1 s3.key: AHJKOKODAJOIFAJDJDIOA s3.secret: AugioaiARJOIfjop20FJIOADOiFJAODA ファイル達 構成は以下の通りです。(※)のファイルが核心です。 stagingとかになってますが、もちろん成立する範囲で修正してください。 ├──provision.yml (※) ├──ansible.cfg ├──group_vars │ └───staging.yml (※) ├──hosts | └───staging ├──host_vars | └───default.yml └──roles └─awscli (※) ├─templates | └─config.conf.j2 | └─credentials.conf.j2 └─tasks └─main.yml group_vars/staging.ymlに設定を書きます。 user: ubuntu s3: region: ap-northeast-1 # S3.region key: AHJKOKODAJOIFAJDJDIOA # S3.key secret: AugioaiARJOIfjop20FJIOADOiFJAODA # S3.secret roles/awscli/templates/config.conf.j2にaws-cliの設定を書きます。 s3.regionが評価され値が入ります。相手の~/.aws/configに配置します。 [default] output = json region = {{s3.region}} roles/awscli/templates/credentials.conf.j2にs3の設定を書きます。 s3.keyとs3.secretが評価され値が入ります。相手の~/.aws/credentialsに配置します。 [default] aws_access_key_id = {{s3.key}} aws_secret_access_key = {{s3.secret}} rokes/awscli/tasks/main.ymlに状態を定義します。 内容は以下の通りです。 1) aws-cliがpip installされた状態 2) ~/.aws/以下に設定ファイルがコピーされた状態 --- - name: install aws-cli pip: name: awscli - name: create .aws dir file: dest: /home/{{user}}/.aws state: directory owner: \"{{user}}\" group: \"{{user}}\" - name: copy config template: src: config.conf.j2 dest: /home/{{user}}/.aws/config owner: \"{{user}}\" group: \"{{user}}\" - name: copy credential template: src: credential.conf.j2 dest: /home/{{user}}/.aws/credentials owner: \"{{user}}\" group: \"{{user}}\" ~ ~ Playbook(provision.yml)は以下の通りです。 - hosts: remote_machine remote_user: \"{{ user }}\" gather_facts: \"{{ check_env | default(True) }}\" become: yes become_method: sudo roles: - { role: awscli } 実行結果 Playbookを実行します。 $ ansible-playbook -i hosts/staging provisiong.yml 相手のユーザディレクトリに.awsというディレクトリが作られ、中にファイルが作られます。 ~/ └─.aws ├─config └─credentials 相手側でaws s3 lsコマンドを打って設定しろと言われなければ成功です。 $ aws s3 ls 2019-10-20 11:11:20 hogehoge おわり。
勾配降下法
[mathjax] 各地点において関数の値を最大にするベクトル((frac{partial f}{partial x_0},frac{partial f}{partial x_1}))を全地点に対して計算したものを勾配とかいう。 ある地点において、このベクトルの方向に向かうことにより最も関数の値を大きくする。 で、今後のために正負を反転して関数の値を最小にするベクトルを考えることにした。 関数の値を小さくする操作を繰り返していけば、いずれ\"最小値\"が見つかるはず。 というモチベを続けるのが勾配降下法。学習率(eta)を使って以下みたいに書ける。。 begin{eqnarray} x_0 = x_0 - eta frac{partial f}{partial x_0} \\ x_1 = x_1 - eta frac{partial f}{partial x_1} end{eqnarray} ということで(f(x_0,x_1)=x_0^2+x_1^2)の最小値を初期値((3.0,4.0))、 学習率(eta=0.1)に設定して計算してみる。 import numpy as np def numerical_gradient(f, x): h = 1e-4 grad = np.zeros_like(x) for idx in range(x.size): tmp_val = x[idx] x[idx] = tmp_val + h fxh1 = f(x) x[idx] = tmp_val - h fxh2 = f(x) grad[idx] = (fxh1 - fxh2) / (2*h) x[idx] = tmp_val return grad def gradient_descent(f, init_x, lr=0.01, step_num=100): x = init_x for i in range(step_num): grad = numerical_gradient(f,x) x -= lr * grad return x def function2(x): return x[0]**2 + x[1]**2 init_x = np.array([-3.0, 4.0]) v = gradient_descent(function2, init_x=init_x, lr=0.1, step_num=100) v # array([-6.11110793e-10, 8.14814391e-10]) ((0,0))に収束した。 ニューラルネットワークの勾配 損失関数を重みパラメータで微分する。以下みたいな感じ。 損失関数の大小を見るとして、例えば(w_{11})以外の重みを固定したとして(w_{11})をわずかに 増やしたときに損失関数の値がどれだけ大きくなるか。 損失関数の値はパラメータ(W)と入力(x)から決まるベクトルだけれども、それぞれ乱数と入力値が設定されている。 begin{eqnarray} W= begin{pmatrix} w_{11} & w_{12} & w_{13} \\ w_{21} & w_{22} & w_{23} end{pmatrix}, frac{partial L}{partial W}= begin{pmatrix} frac{partial L}{partial w_{11}} & frac{partial L}{partial w_{12}} & frac{partial L}{partial w_{13}} \\ frac{partial L}{partial w_{21}} & frac{partial L}{partial w_{22}} & frac{partial L}{partial w_{23}} end{pmatrix} end{eqnarray} 重み(W)が乱数で決まるネットワークがあるとする。このネットワークは入力と重みの積を出力 として返す。出力はSoftmaxを経由するとする。 ネットワークの出力と教師データのクロスエントロピー誤差を誤差として使う。 その前に、数値微分関数を多次元対応する。 普通、配列の次元が(n)個になると(n)重ループが必要になるけれども、 Numpy.nditer()を使うと(n)乗ループを1回のループにまとめることができる。 下のmulti_indexが((0,0),(0,1),(0,2),(1,0),(1,1),(1,2))みたいに イテレータが(n)次のタプルを返す。反復回数はタプルの要素数の直積。 Numpy配列にそのタプルでアクセスすることで晴れて全ての要素にアクセスできる。 def numerical_gradient_md(f, x): h = 1e-4 grad = np.zeros_like(x) it = np.nditer(x, flags=[\'multi_index\'], op_flags=[\'readwrite\']) while not it.finished: idx = it.multi_index tmp_val = x[idx] x[idx] = tmp_val + h fxh1 = f(x) # f(x+h) x[idx] = tmp_val - h fxh2 = f(x) # f(x-h) grad[idx] = (fxh1 - fxh2) / (2*h) x[idx] = tmp_val # 値を元に戻す it.iternext() return grad 初期値(x=(0.6,0.9))、教師データ(t=(0,0,1))をネットワークに入力する。 predict()は(1 times 3)を返す。 それをSoftmax()を通して、(t)とのクロスエントロピー誤差を求めたものが以下。 import numpy as np def cross_entropy_error(y, t): if y.ndim == 1: t = t.reshape(1, t.size) y = y.reshape(1,y.size) batch_size = y.shape[0] delta = 1e-7 return -np.sum( t * np.log( y + delta)) / batch_size def softmax(x): c = np.max(x) return np.exp(x-c) / np.sum(np.exp(x-c)) import sys, os sys.path.append(os.pardir) import numpy as np class simpleNet: def __init__(self): self.W = np.random.randn(2,3) def predict(self, x): return np.dot(x, self.W) def loss(self, x, t): z = self.predict(x) y = softmax(z) loss = cross_entropy_error(y, t) return loss net = simpleNet() x = np.array([0.6, 0.9]) p = net.predict(x) t = np.array([0, 0, 1]) net.loss(x, t) # 0.9463818740797788 このlossを(W)で微分したのが以下。 あえてパラメータ(W)を引数にとり損失関数の値を計算する(f(W))を定義することで、 数値微分が何と何の演算なのかをわかりやすくしている。 実際は(f(W))は(W)とは関係なく(x)と(t)だけから結果を返すけれども、 損失関数(f(W))を(W)で微分するという操作が自明になるようにコードを合わせている。 def f(W): return net.loss(x, t) dW = numerical_gradient_md(f, net.W) dW # array([[ 0.07627371, 0.49923236, -0.57550607], # [ 0.11441057, 0.74884853, -0.8632591 ]]) 結果の解釈 上記の(w),(W),(t)から(frac{partial L}{partial W})が求まった。 損失関数が何か複雑な形をしているという状況で、 (frac{partial L}{partial w_{11}})は(w_{11})がわずかに動いたときに損失関数の値が変化する量を表している。 それが(w_{11})から(w_{32})まで6個分存在する。 begin{eqnarray} frac{partial L}{partial W} = begin{pmatrix} frac{partial L}{partial w_{11}} & frac{partial L}{partial w_{21}} & frac{partial L}{partial w_{31}} \\ frac{partial L}{partial w_{12}} & frac{partial L}{partial w_{22}} & frac{partial L}{partial w_{32}} end{pmatrix} = begin{pmatrix} 0.07627371 & 0.49923236 & -0.57550607 \\ 0.11441057 & 0.74884853 & -0.8632591 end{pmatrix} end{eqnarray}
勾配の可視化
[mathjax] 2変数関数(f(x_0,x_1))を各変数で偏微分する。 地点((i,j))におけるベクトル((frac{partial f(x_0,j)}{partial x_0},frac{partial f(i,x_1)}{partial x_1}))を全地点で記録していき、ベクトル場を得る。 このベクトル場が勾配(gradient)。 (f(x_0,x_1)=x_0^2+x_1^2)について、(-4.0 le x_0 le 4.0)、(-4.0 le x_1 le 4.0)の範囲で、 勾配を求めてみる。また、勾配を可視化してみる。 まず、2変数関数(f(x_0,x_1))の偏微分係数を求める関数の定義。 ((3.0,3.0))の偏微分係数は((6.00..,6.00..))。 def numerical_gradient(f, x): h = 10e-4 grad = np.zeros_like(x) for idx in range(x.size): tmp_val = x[idx] x[idx] = tmp_val + h fxh1 = f(x) x[idx] = tmp_val - h fxh2 = f(x) grad[idx] = (fxh1 - fxh2) / 2*h x[idx] = tmp_val return grad def function2(x): return x[0]**2 + x[1]**2 p = np.array([3.0,3.0]) v = numerical_gradient(function2, p) v # array([6.e-06, 6.e-06]) (-4.0 le x_0 le 4.0)、(-4.0 le x_1 le 4.0)の範囲((0.5)刻み)で偏微分係数を求めて、 ベクトル場っぽく表示してみる。matplotlibのquiver()は便利。 各地点において関数の値を最も増やす方向が表示されている。 w_range = 4 dw = 0.5 w0 = np.arange(-w_range, w_range, dw) w1 = np.arange(-w_range, w_range, dw) wn = w0.shape[0] diff_w0 = np.zeros((len(w0), len(w1))) diff_w1 = np.zeros((len(w0), len(w1))) for i0 in range(wn): for i1 in range(wn): d = numerical_gradient(function2, np.array([ w0[i0], w1[i1] ])) diff_w0[i1, i0], diff_w1[i1, i0] = (d[0], d[1]) plt.xlabel(\'$x_0$\',fontsize=14) #x軸のラベル plt.ylabel(\'$x_1$\',fontsize=14) #y軸のラベル plt.xticks(range(-w_range,w_range+1,1)) #x軸に表示する値 plt.yticks(range(-w_range,w_range+1,1)) #y軸に表示する値 plt.quiver(w0, w1, diff_w0, diff_w1) plt.show() 値が大きい方向に矢印が向いている。例えば((-3.0,3.0))における偏微分係数は((-6.0,6.0))。 左上方向へのベクトル。 参考にしている本にはことわりが書いてあり、勾配にマイナスをつけたものを図にしている。 その場合、関数の値を最も減らす方向が表示されることになる。 各地点において、この勾配を参照することで、どちらに移動すれば関数の値を最も小さくできるかがわかる。
おっさんが数値微分を復習する
引き続き、ゼロDの写経を続ける。今回は、学生の頃が懐かしい懐ワード、数値微分。 全然Deepに入れないけれどおっさんの復習。解析的な微分と数値微分が一致するところを確認してみる。 昔と違うのは、PythonとJupyterNotebookで超絶簡単に実験できるし、 こうやってWordPressでLaTeXで記事を書いたりできる点。 [mathjax] まず、微分の基本的な考え方は以下の通り。高校数学の数3の範囲。 begin{eqnarray} frac{df(x)}{fx} = lim_{hrightarrow infty} frac{f(x+h)-f(x)}{h} end{eqnarray} 情報系学科に入って最初の方でEuler法とRunge-Kutta法を教わってコードを 書いたりレポート書いたりする。懐すぎる..。 または、基本情報の試験かなんかで、小さい値と小さい値どうしの計算で発生する問題が現れる。 ゼロDにはこの定義を少し改良した方法が載っている。へぇ。 begin{eqnarray} frac{df(x)}{fx} = lim_{hrightarrow infty} frac{f(x+h)-f(x-h)}{2h} end{eqnarray} 写経なので、がんばって数値微分を書いて動かしてみる。 簡単な2次関数(f(x))。 begin{eqnarray} f(x) &=& x^2 - 5x +3 \\ f\'(x) &=& 2x - 5 end{eqnarray} def numerical_diff(f, x): h = 10e-4 return (f(x+h) - f(x-h)) / (2*h) (f(x))と、(x=2.5)のところの(f\'(x))をmatplotlibで書いてみる。懐い... import matplotlib.pyplot as plt import numpy as np def f(x): return x**2 - 5*x + 3 x = np.arange(-10, 10, 0.1) y = f(x) dy = numerical_diff(f,x) plt.plot(x, y) x1 = -2.5 dy1 = numerical_diff(f, x1) y1 = f(x1) # y-y1 = dy1(x-x1) -> y = dy1(x-x1) + y1 j = lambda x: dy1 * (x-x1) + y1 plt.plot(x,j(x)) plt.xlabel(\'x\') plt.ylabel(\'y\') plt.grid() plt.show() 偏微分 2変数以上の関数の数値微分は以下の通り。片方を止める。 数値微分の方法は上記と同じものを使った。 begin{eqnarray} frac{partial f(x_0,x_1)}{partial x_0} &=& lim_{hrightarrow infty} frac{f(x_0 +h,x_1)-f(x_0-h,x_1)}{2h} \\ frac{partial f(x_0,x_1)}{partial x_1} &=& lim_{hrightarrow infty} frac{f(x_0,x_1+h)-f(x_0,x_1-h)}{2h} end{eqnarray} ((x_0,x_1)=(1,1))における(x_0)に対する偏微分(frac{partial f(x_0,x_1)}{x_0})、(x_1)に対する偏微分(frac{partial f(x_0,x_1)}{x_1})を求めてみる。 ちゃんと(frac{partial f(x_0,1.0)}{x_0}=2.00..)、(frac{partial f(1.0,x_1)}{x_1}=2.00..)になった。 import matplotlib.pyplot as plt import numpy as np from mpl_toolkits.mplot3d import Axes3D def f(x): return x[0]**2 + x[1]**2 X = np.meshgrid(np.arange(-5., 5., 0.2),np.arange(-5., 5., 0.2)) Z = f(X) fig = plt.figure(figsize=(6, 6)) axes = fig.add_subplot(111, projection=\'3d\') axes.plot_surface(X[0],X[1], Z) f0 = lambda x: x**2 + 1.0**2 f1 = lambda x: 1.0**2 + x**2 df0 = numerical_diff(f0, 1.0) df1 = numerical_diff(f1, 1.0) print(df0) # 2.0000000000000018 print(df1) # 2.0000000000000018 plt.show()
誤差, 2乗誤差と交差エントロピー誤差
台風で自宅に篭れるから勉強時間をとれるな..、と見積もってたのだけれども、 近所の多摩川がマジで溢れそうでそれどころではなく...。 時間が空いてしまったがゼロから作るDeepLearningを読んで実際に実装する作業を再開する。 今後、パラメータを更新していくのだが、どういう方針でパラメータを更新するか決めておく必要がある。 教師ありデータを使った学習を扱っている訳で、訓練データと対応する教師データが与えられている前提。 何かの学習をした結果のモデルの出力と教師データの差を「誤差」として、「誤差」が小さくなるように パラメータを決めていこうという方針。 例えば手書き文字認識で言うところの「認識精度」を指標に使ってしまうと、 モデルの出力が微小に変化したところで「認識精度」は微小に変化しない状況が発生する。 「認識精度」が変化するときは一気に変化する。これではパラメータをどの方向にずらして良いかわからない。 ※SVMの解説で非線形分離を行う決定境界を0/1損失で決めることの問題点に通じる。 非線形分離を行う決定境界も損失関数により微小な変化に追従して決めていく。 2乗和誤差,クロスエントロピー誤差 ということで誤差関数を導入する。 (y_k)はモデルの出力で、最終段でSoftMax関数を通してある。 まずは2乗和誤差。まぁ簡単で、正解と出力の差を2乗した値を足す。 [mathjax] begin{eqnarray} E = frac{1}{2} sum_{k=1}^N (y_k - t_k)^2 end{eqnarray} 次にクロスエントロピー誤差。 begin{eqnarray} E = - sum_{k=1}^N t_k log y_k end{eqnarray} どっちでも良いんじゃ..、と思う訳だけれども、非線形分離問題で決定境界を決めるときに、 正解をより正解として、誤りをより誤りとして表現できる誤差がより優秀なので、 クロスエントロピー誤差の方が適切ではある。 クロスエントロピー誤差の方は(t_k)がゼロの項はゼロになるので、(t_k)が1の項だけ計算すれば良い。 つまり、正解が1のケースについてのみ誤差値が発生する。 (-log y_k)は(y_k)がゼロに近いと急激に値が大きくなる。 これにより、(t_k=1)なのにゼロに近い(y_k)が出力されたときに大きなペナルティを与えられる。 クロスエントロピー誤差関数の実装 バッチ(並列実行)対応のクロスエントロピー誤差関数。 def cross_entropy_error(y, t): if y.ndim == 1: t = t.reshape(1, t.size) y = y.reshape(1,y.size) batch_size = y.shape[0] delta = 10e-7 return -np.sum( t * np.log( y + delta)) / batch_size 1次元のデータを与えてみる。 t1 = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0] y1 = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0] cross_entropy_error(np.array(y1), np.array(t1)) # 0.5108239571007129 2次元のデータを与えてみる。 t2 = [ [0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]] y2 = [[0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0], [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]] cross_entropy_error(np.array(y2), np.array(t2)) # 7.163167257532494 バッチ対応が簡単に書けるところがかなり美しい。
MNISTの手書き文字認識用データ取得クラスの作成
MNISTから手書き文字認識用のデータセットをロードするクラスを作ってみた。 ロードしたデータセットをpickleでシリアライズ、デシリアライズする機能付き。 後々改造する予定でここに貼ったのはメンテしない予定。 たったこれだけ書くのに40分も要してしまった...。 Pythonの冗長感が半端ないけども慣れるしかない。 マジックコードだらけだけども、MNISTの手書き文字認識用データ取得専用だから仕方ない。 get_image()関数により訓練データ、訓練ラベル、テストデータ、テストラベルを取れる。 データは1次元になって入る。つまり1ファイルごとに28*28のサイズがある。 import os import urllib.request import numpy as np import gzip import pickle class MnistLoader: def __init__(self, mnistdir): self.url_base = \'http://yann.lecun.com/exdb/mnist/\' self.dataset_dir = mnistdir self.save_path = self.dataset_dir + \'mnist.pkl\' self.key_file = { \'train_img\':\'train-images-idx3-ubyte.gz\', \'train_label\':\'train-labels-idx1-ubyte.gz\', \'test_img\':\'t10k-images-idx3-ubyte.gz\', \'test_label\':\'t10k-labels-idx1-ubyte.gz\' } self.dataset = {} if os.path.exists(self.save_path): with open(self.save_path,\'rb\') as f: self.dataset = pickle.load(f) else: self.__load_mnist() self.dataset[\'train_img\'] = self.__load_img(self.key_file[\'train_img\']) self.dataset[\'train_label\'] = self.__load_label(self.key_file[\'train_label\']) self.dataset[\'test_img\'] = self.__load_img(self.key_file[\'test_img\']) self.dataset[\'test_label\'] = self.__load_label(self.key_file[\'test_label\']) with open(self.save_path, \'wb\') as f: pickle.dump(dataset, f, -1) def __load_mnist(self): \'\'\' load mnist data and store to file \'\'\' for v in self.key_file.values(): file_path = self.dataset_dir + v urllib.request.urlretrieve(url_base + v, self.dataset_dir + v) def __load_img(self, file_name): file_path = self.dataset_dir + file_name with gzip.open(file_path, \'rb\') as f: data = np.frombuffer(f.read(), np.uint8, offset=16) data = data.reshape(-1, 784) return data def __load_label(self,file_name): file_path = dataset_dir + \'/\' + file_name with gzip.open(file_path, \'rb\') as f: labels = np.frombuffer(f.read(), np.uint8, offset=8) return labels def get_image(self): return (self.dataset[\'train_img\'] , self.dataset[\'train_label\']), (self.dataset[\'test_img\'], self.dataset[\'test_label\']) mnist_loader = MnistLoader(mnistdir=\'/Users/ikuty/Documents/mnist/\') PILを使って訓練データの1枚目を表示するテスト。 import sys, os sys.path.append(os.pardir) import numpy as np from PIL import Image def img_show(img): pil_img = Image.fromarray(np.uint8(img)) pil_img.show() (x_train, t_train), (x_test, t_test) = mnist_loader.get_image() img = x_train[0] label = t_train[0] bimg = img.reshape(28, 28) img_show(bimg) こんなのが出る。(28x28しかなくて小さすぎなので256x256に引き伸ばして表示。)
SoftMax関数
[mathjax] 出力層の総和を1にするように調整できれば、出力層を確率としてとらえることができるようになる。 入力層に画像を放り込み、出力層でラベルに属する確率を出せば、画像にラベルをつける分類器になる。 入力が(n)ノードあるとして、全てのノードを入力を受けて出力する。 出力側のノードが(m)ノードあるとして、和が(1)になるようにどう操作するか。 分母は入力側の全ノードの和、分子は対応する1ノードだけ。そうやって1にする。 指数関数に入力を食わせて和を取る。指数関数は単調増加関数だから、 入力の大小と出力の大小は一致する。定義上は以下の通り。 begin{eqnarray} y_k &=& frac{e^{a_k}}{sum_{i=1}^n e^{a_i}} end{eqnarray} import numpy as np def softmax1(x): return np.exp(x) / np.sum(np.exp(x)) a = np.array([1010,1000,990]) r1 = softmax1(a) r1 # array([nan, nan, nan]) ただ、この通りにするとオーバーフローを起こすので、 式変形してオーバーフローを回避する。(C)は入力ノードの最大値。 begin{eqnarray} y_k &=& frac{e^{a_k}}{sum_{i=1}^n e^{a_i}} \\ &=& frac{C e^{a_k}}{Csum_{i=1}^n e^{a_i}} \\ &=& frac{e^{a_k+log{C}}}{sum_{i=1}^{n} e^{a_i + log{C}}} \\ &=& frac{e^{a_k+C\'}}{sum_{i=1}^{n} e^{a_i + C\'}} end{eqnarray} import numpy as np def softmax2(x): c = np.max(x) return np.exp(x-c) / np.sum(np.exp(x-c)) a = np.array([1010,1000,990]) r2 = softmax2(a) r2 # array([9.99954600e-01, 4.53978686e-05, 2.06106005e-09]) SoftMax後の和は1 前述の通り、SoftMax後の和は1。 z = np.sum(r2) z # 1.0
行列の積和と順伝播
[mathjax] 順伝搬(forward)はNumpyの積和計算を使って超絶簡単に記述できる。 行列の積和計算 何はともあれNumpyで行列の積を計算する方法について。 この仕組みがあるから、forなどの制御構造を使わずに行列の積を実現できる。 begin{eqnarray} A &=& begin{pmatrix} a_{11} & a_{21} \\ a_{12} & a_{22} end{pmatrix} \\ B &=& begin{pmatrix} b_{11} & b_{21} \\ b_{12} & b_{22} end{pmatrix} \\ A cdot B &=& begin{pmatrix} a_{11} b_{11} + a_{21} b_{12} && a_{11} b_{21} + a_{21} b_{22} \\ a_{12} b_{11} + a_{22} b_{12} && a_{12} b_{21} + a_{22} b_{22} end{pmatrix} end{eqnarray} 積はNumpyのdot関数で一発。 左行列と右行列の\"対応する行\"は一致している必要がある。 つまり(N_1 times M_1 cdot N_2 times M_2)という積計算において(M_1 = N_2)である必要がある。 a11,a21,a12,a22 = (1,2,3,4) b11,b21,b12,b22 = (1,2,3,4) a = np.array([[a11,a21],[a12,a22]]) b = np.array([[b11,b21],[b12,b22]]) np.dot(a,b) # array([[ 7, 10], # [15, 22]]) 和はNumpyの+オペレータで一発。 begin{eqnarray} A &=& begin{pmatrix} a_{11} & a_{21} \\ a_{12} & a_{22} end{pmatrix} C &=& begin{pmatrix} c_{1} \\ c_{2} end{pmatrix} end{eqnarray} begin{eqnarray} A+C &=& begin{pmatrix} a_{11} + c_1 & a_{21} + c_1 \\ a_{12} + c_1 & a_{22} + c_2 end{pmatrix} end{eqnarray} c1, c2 = (5,5) c = np.array([[c1],[c2]]) a + c # array([[6, 7], # [8, 9]]) 各層における信号伝達の実装 Numpyで積和が簡単にかける。 すなわち、各層における伝達が積和で表されるとすると、 入力層から各層における信号伝達の実装を簡単にかける。 左側の層が(X=begin{pmatrix}x_1 x_2end{pmatrix})、重みが(W=begin{pmatrix}w_{11} & w_{21} \\ w_{12} & w_{22} end{pmatrix})、 右側の層が(A=begin{pmatrix} a_{11} & a_{21} end{pmatrix})とする。 すると、右層と左層の関係は以下の通り。 begin{eqnarray} A &=& XW \\ begin{pmatrix} a_{11} & a_{21} end{pmatrix} &=& begin{pmatrix}x_1 x_2end{pmatrix} begin{pmatrix}w_{11} & w_{21} \\ w_{12} & w_{22} end{pmatrix} \\ &=& begin{pmatrix} w_{11}x_1 + w_{21} x_2 \\ w_{12}x_1 + w_{22}x_2end{pmatrix} end{eqnarray} 左層にバイアス(B=begin{pmatrix}b_1 \\ b_1 end{pmatrix})があった場合、線形和で表現できる。 行と列がどちらの方向に対応するかに注意。 1つの層に(N)個のノードが存在することを(Ntimes 1)の行列で表現する。 begin{eqnarray} A &=& XW + B \\ begin{pmatrix} a_{11} & a_{21} end{pmatrix} &=& begin{pmatrix}x_1 x_2end{pmatrix} begin{pmatrix}w_{11} & w_{21} \\ w_{12} & w_{22} end{pmatrix} + begin{pmatrix}b_1 \\b_1 end{pmatrix} \\ &=& begin{pmatrix} w_{11}x_1 + w_{21} x_2 + b_1 \\ w_{12}x_1 + w_{22}x_2 + b_1end{pmatrix} end{eqnarray} x1,x2 = (0.4,0.9) w11,w21,w12,w22 = (0.8,0.5,0.1,0.3) b1 = 0.1 x = np.array([x1,x2]) w = np.array([[w11,w21],[w12,w22]]) b = np.array([b1,b1]) # これで一発 a = np.dot(x,w) + b # array([0.51, 0.57]) # 活性化関数としてsigmoid関数をかます z = sigmoid(a) # array([0.62480647, 0.63876318]) 順伝播 入力層から出力層まで上記のような計算を繰り返していく。これを\"順伝播\"とか言う。 順伝\"搬\"ではなく、順伝\"播\"。ネットワークが多層になったとしても全く同様。 以下みたいにNumpyの積和計算を繰り返すだけで出来る。 def init_network(): network = {} network[\'W1\'] = np.array([[0.1,0.3,0.5],[0.2,0.4,0.6]]) network[\'b1\'] = np.array([0.1,0.2,0.3]) network[\'W2\'] = np.array([[0.1,0.4],[0.2,0.5],[0.3,0.6]]) network[\'b2\'] = np.array([0.1,0.2]) network[\'W3\'] = np.array([[0.1,0.3],[0.2,0.4]]) network[\'b3\'] = np.array([0.1,0.2]) return network def forward(network, x): W1, W2, W3 = network[\'W1\'], network[\'W2\'], network[\'W3\'] b1,b2,b3 = network[\'b1\'], network[\'b2\'], network[\'b3\'] a1 = np.dot(x, W1) + b1 z1 = sigmoid(a1) a2 = np.dot(z1, W2) + b2 z2 = sigmoid(a2) a3 = np.dot(z2, W3) + b3 y = a3 return y network = init_network() x = np.array([1.0,0.5]) y = forward(network,x) # array([0.31682708, 0.69627909])
						