2017年6月28日水曜日

Kubernetes on IBM Bluemix 最速理解


IBM Bluemixのコンテナサービスでkubernetes(クバネティス)が利用できるようになった。bluemixのクラウド上でKubernetes環境を構築し、その手順を残しておく。


◆ Kubernetesクラスターサービスの作成
ブラウザでbluemix環境へアクセスし、bluemixのカタログからKubernetes Clusterサービスを選択する。
https://console.bluemix.net/catalog/
cluster(worker nodesの集合)名は、myClusterとする。



◆ 構築に必要なツールの用意
(Bluemix CLI)
bluemixを操作するコマンドラインのツールを用意する。
手順は公式に従う。
https://clis.ng.bluemix.net/ui/home.html


(Kubernetes CLI)
kubernetesを操作するコマンドラインのツールを用意する。
https://kubernetes.io/docs/tasks/tools/install-kubectl/


(IBM Bluemix Containerプラグイン)
kubernetesの利用には不要だが、namespaceの設定や、bluemix上でのコンテナの作成や管理に必要となる。
$ bx plugin install IBM-Containers -r Bluemix


(IBM Bluemix Container Serviceプラグイン)
Kubernetes クラスターを作成し、ワーカー・ノードを管理するために、
IBM Bluemix Container Serviceプラグインをインストールする。
$ bx plugin install container-service -r Bluemix



◆ 環境設定
(Bluemix環境へのログイン)
今回は米国南部のリージョンをAPIのエンドポイントとする。
使用可能なコンテナー・イメージと Bluemix サービスは、Bluemix 地域によって決まる。
$ bx login -a https://api.ng.bluemix.net


(IBM Bluemix Containerプラグインの初期化)
IBM Bluemix Container プラグインの初期化を行う。
$ bx ic init

環境変数の設定例が出力される。ローカルのdocker環境を上書きする。
$ export DOCKER_HOST=tcp://containers-api.ng.bluemix.net:8443
$ export DOCKER_CERT_PATH=/Users/*****/.ice/certs/containers-api.ng.bluemix.net/f5f0f7aa-2cbd-445c-b57f-a94cbb715bb7
$ export DOCKER_TLS_VERIFY=1


(namespaceの作成)
namespaceを作成する。
$ bx ic namespace-set <namespace>

namespaceの確認を行う。
$ bx ic namespace-get


(IBM Bluemix Container Serviceプラグインの初期化)
IBM Bluemix Container Service プラグインの初期化を行う。
$ bx cs init


(クラスタの作成)
サービス作成時にクラスタの作成をGUIから行っていれば、この手順は不要である。
$ bx cs cluster-create --name myCluster

クラスタの確認を行う。
$ bx cs clusters

クラスタ内のワーカーの確認を行う。
$ bx cs workers myCluster


(Kubernetes構成データと証明書のダウンロード)
この構成データにより、bluemix上のkubernetesクラスターにkubectlコマンドで接続することができるようになる。
$ bx cs cluster-config myCluster

出力でコンフィグの保存先が指定されるため、環境変数に設定する。
$ export KUBECONFIG=/Users/*****/.bluemix/plugins/container-service/clusters/myCluster/kube-config-hou02-myCluster.yml



◆ ダッシュボードの起動
ローカルでWEBブラウザからkubernetesを操作するためダッシュボードを立てておく。
kubernetesサーバへのプロキシとして動作する。
$ kubectl proxy --accept-hosts '.*'

ディフォルトホストは127.0.0.1、ポートは8001である。
引数で変更も可能である。
$ kubectl proxy --address=x.x.x.x --port=yyyy --accept-hosts '.*'

ローカル環境からブラウザ等でアクセスできるはずである。
http://127.0.0.1:8001/ui

ダッシュボードを起動せずとも、コマンドで操作できる。



◆ kubernetesのステータス確認
先ほど立てたプロキシサーバからもkubernetesのステータス確認はできるが、コマンドで一通り操作する。
$ kubectl get nodes
$ kubectl get deployments
$ kubectl get replicasets
$ kubectl get pods

※kubectl delete pod [pod名]



◆ コンテナの作成
kubernetes管理をすることを前提とした、適当なサンプルを元に、コンテナを作成する。
$ git clone https://github.com/IBM/container-service-getting-started-wt.git
$ cd container-service-getting-started-wt/Stage2

$ docker build --tag registry.ng.bluemix.net/<namespace>/hello-world:2 .
※サンプルのStage2のファイルを使っている関係で、タグバージョンを:2としている。

この後のpushは必要ない。先ほどのbuildはローカルではなくbluemix環境で行われているためである。"bx ic init"コマンドとその後のその環境設定の変更でdockerホストが変わっていることを思い出そう。
###$ docker push registry.ng.bluemix.net/<namespace>/hello-world:2

$ docker images



◆ デプロイメント・サービス
デプロイメントのフェーズでは、DeploymentがReplicaSetを生成・管理、ReplicaSetがPodを生成・管理するまでの一連の流れが実行され、最終的にポッドが起動する。

ReplicaSet: 同じ仕様のPodのレプリカ数を管理する。
Pod       : アプリケーションを動かすための最小単位。1つ以上のコンテナと共有されたボリュームで構成される。

その後デプロイされたPodを元にサービスを起動する。

デプロイメント、サービスまでそれぞれコマンドで操作できるが、サンプル内のymlの管理ファイルを利用して実施する。

(healthcheck.yml)
※ mynamespace名部分は変えること。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: hw-demo-deployment
spec:
  replicas: 3
  template:
    metadata:
      name: pod-liveness-http
      labels:
        run: hw-demo-health
        test: hello-world-demo
    spec:
      containers:
        - name: hw-demo-container
          image: "registry.ng.bluemix.net/<mynamespace>/hello-world:2"
          imagePullPolicy: Always
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: hw-demo-service
  labels:
    run: hw-demo-health
spec:
  type: NodePort
  selector:
    run: hw-demo-health
  ports:
   - protocol: TCP
     port: 8080
     nodePort: 30072

$ kubectl create -f healthcheck.yml

ステータスを確認する。
$ kubectl get services

アクセス確認を行う(portではなく、nodePortの番号、ここでは30072でアクセスする)。
http://クラスタノードIP:ノードポート/〜

動作させるまでは以上である。
以降は、その他利用しそうな操作手順を確認しておく。



◆ スケーリング
(手動によるpodのスケーリング設定)
$ kubectl scale deployment hw-demo-deployment --replicas=2

pod数が2つに減っているはずである。
$ kubectl get pods


(自動でのpodの数のスケーリング設定)
指定したCPUの利用率をターゲットに、pod数が最低3から最大10までの間でスケーリングされる。
$ kubectl autoscale deployment hw-demo-deployment --cpu-percent=80 --min=3 --max=10

オートスケールの設定が有効になったことが分かる。
hpa: Horizonal Pod Autocaling
$ kubectl get hpa

pod数は3つになっているはずである。
$ kubectl get pods

設定を削除する。
$ kubectl delete hpa hw-demo-deployment



◆ 更新
更新手順も覚えておく。

コンフィグマップを変更する。
例として今回は、適当にimage名でも変えてみる。
$ kubectl edit deployment/hw-demo-deployment
    spec:
      containers:
      - image: gcr.io/google-samples/node-hello:1.0:latest

ロールアウトする。
全て正常に動作していることをkubenetesが確認を行い、その後古いpodは削除される。
$ kubectl rollout status deployment/hw-demo-deployment

ヒストリを確認する
$ kubectl rollout history deployment/watson-talk-pod
deployments "hw-demo-deployment"
REVISION CHANGE-CAUSE
1
2        

トラブルが確認された場合は、切り戻しを行うことができる。
$ kubectl rollout undo deployment/hw-demo-deployment --to-revision=1



◆ クリーンナップ
$ kubectl delete -f healthcheck.yml
$ bx cs cluster-rm myCluster



(参考)
https://console.bluemix.net/docs/containers/cs_tutorials.html#cs_tutorials
https://kubernetes.io/docs/tutorials/
http://qiita.com/tkusumi/items/01cd18c59b742eebdc6a




2017年4月5日水曜日

Chrome extentionを使い、iframe内外でデータ通信

◆ 目的
Webページ(親ページ)とそこからiframeで読み込んだ子ページ間でデータの授受を行う。
ただし、iframe内のコンテンツは親ページのドメイン外に存在する任意のサイトとする。



◆ 課題
解決する課題は2つある。
1. クロスドメイン制約への対応
XMLHttpRequestを投げる際などにも必ず気に掛ける点だろう。
Same Origin Policy(同一生成元ポリシー)を回避しなければならない。

今回は任意の外部サイトをiframeに取り込むことを前提としているため、
CORS(Cross-Origin Resource Sharing)もJSONP(JSON with padding)も今回は使えない。

2. iframe内での読み込み不許可ヘッダへの対応
HTTPのレスポンスヘッダに、iframe内からWebページが読み込まれるのを防止するオプション(X-Frame-Options)が付与されていればiframe内で表示はできない。



◆ 解決策
1. postMessageの利用
クロスドメイン制約への対応として、HTML5で用意されたpostMessageの利用を思いつく。

(親サイト)
<iframe id="ifrm" src="外部サイト"></iframe>

<script type="text/javascript">
window.onload = function() {
  var ifrm = document.getElementById('ifrm').contentWindow
  ifrm.postMessage("hello", '外部サイト')
};
</script>

(外部サイト)
<script type="text/javascript">
window.addEventListener('message', function(event) {
    alert(event.data)
}, false);
</script>

しかし、Chromeが許可しない。
※Safariでは警告なく、実施できた。

(エラー例)
Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('http://親サイト') does not match the recipient window's origin ('http://外部サイト').


2. iframeを使わない
iframeを使う前提を変え、コンテンツをダウンロードし、外部サイトを親側で再現させる。つまり、前提を変える。
が、しかし、JavaScriptが生成する動的ページのリソースの管理は困難があるため、やはり、iframeは使いたい。前提は戻す。


3. Google Chromeの拡張機能の利用
Chromeに限定されるが、Chromeの拡張機能を使えば対応ができそうである。
データの共有は、バックグラウンドで動作させるスクリプト内でセッションストレージを使えばいいだろう。
また、ヘッダの書き換えも実施できる。



◆ 拡張コード概要
chrome拡張を利用することにした。
拡張コードを有効にするには、最低限以下の3つのファイルを用意し、それらを適当なフォルダに入れ、Chromeブラウザの拡張機能からインポートすれば良い。

○ マニフェスト ファイル
拡張機能に関する情報を与える。

○ コンテンツ スクリプト
ブラウザで表示させるページで読み込むjsとは別空間で実行させるjsである。
このファイルは親サイトと、iframe内の外部サイト、両方に読み込まれる。
空間は分かれているため、コンテンツスクリプト内で利用しているjQueryなどのライブラリがサイトで利用しているバージョンと異なっていても問題は起きない。

○ バックグラウンド スクリプト
Chromeのバックエンド側で処理させるjsである。
表示コンテンツには取り込まれないが、コンテント ファイルとの間でメッセージ通信ができる。


簡易図で表すと下記のような感じである。
   parent
+----------+
|          |← contentScripts.js
|  iframe  |                       
|  +----+  |
|  |    |  |
|  |    |←-|-- contentScripts.js
|  +----+  |
|          |
+----------+
background.js

contentScripts.js、background.jsの名称はmanifest.json内で指定する。



◆ 試験
1. iframe内でマウスを操作。マウスオーバしたタグ要素に色がつくようにしている。
  そのタグ要素でクリックすると、タグ名がセッションストレージへ保存される。

2. 親側でiframe外の要素をクリックする。
  iframeで取得した要素がalert表示されれば成功である。



◆ コード例
○ manifest.json


○ contentScripts.js


○ background.js



◆ コード解説
1点、解説を加えておく。
contentScript.jsは親子両方に読み込まれるため、共通処理以外のjsコードは、親子用で条件を加えている。
// iframe用
if (window != parent) {
  ~snip~
} // 親用
else{
  ~snip~
}

2017年3月4日土曜日

ChatHub(LINE上で動作する、Q&Aを中心としたSNSサービス)


LINE上で動作する、お互いに顔までは知らない関係の、身近なコミュニティ内で、匿名のまま情報交換ができるQ&Aを中心としたSNSサービス、ChatHubを作成した。最近はやりのbotの一つである。





























※内部でAI的な要素を使ったおもしろい仕組みを導入している。
  技術詳細に関しては随時公開予定。



2017年2月4日土曜日

AWS EC2にApache、Passenger、Sinatra環境を構築する


◆ 利用するEC2
t2.small
※メモリは2Gは欲しい



◆ rubyのバージョンアップ
$ sudo yum install -y git gcc gcc-c++ openssl-devel readline-devel
$ git clone git://github.com/sstephenson/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ exec $SHELL -l
$ rbenv -v
$ git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ rbenv install 2.3.3
$ rbenv global 2.3.3
$ ruby -v



◆ apacheとpassengerの連携
$ gem install passenger
$ sudo yum install libcurl-devel httpd httpd-devel apr-devel apr-util-devel
$ passenger-install-apache2-module
※apacheのconfigへ設定する内容をコピー

$ cd /usr/local/src/mydir/
$ vi hello.rb
require 'sinatra'
get '/' do
  'Hello World!'
end

$ mkdir public
$ mkdir tmp
$ vi config.ru
require 'rubygems'
require 'sinatra'
require File.expand_path '../hello.rb', __FILE__

run Sinatra::Application


$ vi /etc/httpd/conf.d/passenger.conf
LoadModule passenger_module /home/ec2-user/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/passenger-5.1.2/buildout/apache2/mod_passenger.so
<IfModule mod_passenger.c>
  PassengerRoot /home/ec2-user/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/passenger-5.1.2
  PassengerDefaultRuby /home/ec2-user/.rbenv/versions/2.3.3/bin/ruby
</IfModule>

Listen 8080

<VirtualHost *:8080>
  DocumentRoot /usr/local/src/bot/line/public
  <Directory /usr/local/src/bot/line/public>
    Require all granted
    Allow from all
    Options -MultiViews
    #Relax Apache security settings
    #AllowOverride all
    #MultiViews must be turned off
    #Options -MultiViews
  </Directory>
</VirtualHost>

$ sudo /etc/init.d/httpd restart



◆ 確認
ブラウザからアクセス
http://x.x.x.x:8080/
500 Internal Server Error

WEBrickによる切り分け

$ ruby hello.rb
`require': cannot load such file -- sinatra


$ gem install bundle
$ vi Gemfile
source 'https://rubygems.org'
gem "sinatra"

$ bundle install
An error occurred while installing pg (0.19.0), and Bundler cannot continue.

$ sudo yum install postgresql postgresql-devel sqlite sqlite-devel
$ bundle install
$ ruby hello.rb
$ sudo /etc/init.d/httpd restart

ブラウザからアクセス
http://x.x.x.x:8080/
500 Internal Server Error

$ tail -f /var/log/httpd/error_log
configuration error:  couldn't perform authentication. AuthType

$ sudo vi /etc/httpd/conf.d/passenger.conf
#以下をいったん無効にする
#Require all granted

改めて、
$ sudo /etc/init.d/httpd restart

ブラウザからアクセス
http://x.x.x.x:8080/
'Hello World!'


(参考)
http://recipes.sinatrarb.com/p/deployment/apache_with_passenger




2017年1月10日火曜日

Cのselect、epollを使ったI/O多重化


複数のファイルディスクリプタを監視し、その中のいずれが入出力可能な状態であるかを確認するCのシステムコールとしては、select、epoll、kqueue(BSD系)が有名だろう。
ここでは、selectとepollを使い、1プロセス1スレッドでのイベント駆動によるI/O多重化コードを備忘録として残しておく。

(参考)
最近ではselectではなく、epoll、kqueueが使われることが多い。
selectは待ち受けられるファイルディスクリプタの数に上限があり、またパフォーマンス問題も存在するためである。
I/Oからの入力に応じて発生するイベントを処理するライブラリとしてはlibevent, libev, libuvなどがあるがこの内部でもepollやkqueueが利用されている。
※libevはlibeventの速度改善、FDの制限撤廃の対応がされた改良版である。
※libuvはnode.js用のためにlibevをベースに開発されたライブラリである。



(select.c)


(epoll.c)


$ gcc ファイル名

$ ./a.out /dev/tty /dev/input/mouse0 10
hello
/dev/tty: hello

no input after 10 seconds

マウス操作
/dev/input/mouse0: (

2017年1月6日金曜日

Rubyで深さ優先探索(DFS)と幅優先探索(BFS)

グラフ操作の基本、深さ優先探索(DFS: Depth First Search)と幅優先探索(BFS: Breath First Search)のアルゴリズムをRubyで書く。


◆ DFS


◆ BFS

2016年10月18日火曜日

数学の素養がなくてもできるディープラーニング


◆ 目的
ディープラーニング(深層学習)とは、多層構造のニューラルネットワークを用いた機械学習である。ディープラーニングの代表例である、畳み込みニューラルネットワーク、CNN(Convolutional Neural Network)を使い、10種類の手書き数字を認識させる処理フローを追いかけてみる。また、TensorFlowのコードを実際にどう書いて組むかも理解する。数学に対する抵抗があったとしても難しい部分はTensorFlowが対応してくれるため、誰でもディープラーニングを試すことはできる。脱苦手意識!



◆ CNNの全体像
大きな流れは、特徴を抽出(①)し、それを元に分類(②)することである。①→②のフローが基本である。

CNNでは畳み込みフィルタとプーリング層の組み合わせで特徴を引き出す。
またこのセットを何回繰り返すかは目的によりけりである。また目的が違えば抽出方法も異なる。

識別部での分類処理では、隠れ層を例として2段にして書いているが、単層でも目的に叶う場合もあれば、より多層にする必要も出てくる。


                                 ↗︎
       畳み込みフィルタ → プーリング層 →
   ↗︎                            ↘︎
              ・               ・
input         ・               ・
              ・               ・
   ↘︎                            ↗︎
       畳み込みフィルタ → プーリング層 →
                                 ↘︎

|---------------特徴量の抽出部---------------|



      全結合層
  隠れ層   隠れ層
         ↗︎
     ○  →  ○
↗︎       ↘︎    ↘︎
→ 
↘︎                出力
         ↗︎      ↗︎
     ○  →  ○ → □ →
         ↘︎      ↘︎

↗︎       ↗︎    ↗︎
→   ○  →  ○
↘︎       ↘︎

|----------識別部----------|


◆ 識別部
先に②の識別部から覗いてみる。識別部は、入力される特徴量に基づいて分類処理を行う部分であった。識別部の役割を具体的なノード数(縦列)を当て込んで見ていくことにする。

・入力層
入力値として2つの値のペア(x0, x1)を受け取る。
このペアがデータとして複数入力されてくるわけである。
正確に書けば、n番目のデータとして、(x1n, x2n)としたほうがいいだろうか。

・全結合層
隠れ層が2つの多段ニューラルネットワーク。3ノードから成るとする。

・出力層
2次元だった入力データ(x0, x1)が3つの領域へ分けられる(3分割)。

簡易図での示しやすさを優先しているだけだり、それぞれの数に必然性はない。


入力層         結合層     出力層
          隠れ層0 隠れ層1

                 ↗︎
            ○z0 → ○z'0
      ↗︎         ↘︎     ↘︎
  x0  →                       P0
      ↘︎          ↗︎         ↗︎
            ○z1 → ○z'1 →  □ → P1
      ↗︎          ↘︎         ↘︎
  x1  →                       P2
      ↘︎          ↗︎    ↗︎
            ○z2 → ○z'2
                  ↘︎


(入力層から隠れ層0まで)
未知データを予測する数式は、与えられるデータをX、各項の係数をW、定数項をBの行列とし計算すればいいだろう。

Z = h(XW1 + B1)
z0 = h(x0w00 + x1w10 + b1)
z1 = h(x0w01 + x1w11 + b1)
z2 = h(x0w02 + x1w12 + b1)


実計算では行列でまとめる。行と列の縦横を混乱しがちになるが落ち着いて考えれば難しくない。

X =              W1 =                B1 =
[[x0, x1],       [[w00, w01, w02]     [b1, b2]
    ・             [w10, w11, w12]]
    ・
    ・    ]]

|--入力の次元数--| |--隠れ層のノード数--| |--隠れ層のノード数--|

Z =
[[z0, z1, z2],
      ・
      ・
      ・     ]]

X
入力データである。Placeholderと呼ばれる。
トレーニングやテスト用のデータとして(x0, x1)のペアが複数ある。
この入力データが最終的に3つのデータ(z0, z1, z2)に拡張される。

W
最適化するパラメータ(Weight)変数である。Variableと呼ばれる。

B
定数項(Bias)である

・h
x軸の0を境に値が1へ増加する、活性化関数である。
入力信号の変化に応じて出力が活性化するニューロンのような模式である。
以下のようなものがある。
  σ(x): シグモイド関数
  tanhx: 双曲線関数(hyperbolic function)
  relu(x): ReLU(Rectified Linear Unit, Rectifier)

TensorFlowの実コードは後半改めて見る。


(隠れ層1そして出力)
入力層から隠れ層0までと基本同じである。

違いは、隠れ層0の出力が隠れ層1の入力となり、
最後に、ソフトマックス関数を適用させる点である。
ソフトマックス関数は出力をK個に、総和は1となる、ある分類領域に所属する確率を求める関数である。
境界を境にハードに領域が変化するのではなく、ソフトに確率が変化していく。

隠れ層から出力層へはこの式で計算する。
Z' = ZW0 + b
※'はただの別識別の記号として使っているだけである。

P = softmax(Z')
z'0 = z0w00 + z1w10 + z2w20 + b0
z'1 = z0w01 + z1w11 + z2w21 + b0
z'2 = z0w02 + z1w12 + z2w22 + b0

p0 = softmax(z'0)
p1 = softmax(z'1)
p2 = softmax(z'2)


Z =              W0 =              B0 =
[[z0, z1, z2],   [[w00, w01, w02]  [b0, b1, b2]
      ・           [w10, w11, w12]
      ・           [w20, w21, w22]]
      ・    ]]
                |---出力の分割数---|

Z' =
[[z'0, z'1, z'2],
       ・
       ・
       ・       ]]

softmax関数を適用し、
P =
[[p0, p1, p2],
      ・
      ・
      ・     ]]



◆ 特徴量の抽出部
入力データから畳み込みフィルタ、プーリング層を経由させ特徴量の取り出しを行う。

・入力データ
画像ファイルとする。

・畳み込みフィルタ
画像のエッジ抽出などを行うフィルタである。
ディープラーニング専用のファンクションではない。

・プーリング層
解像度を落とす役割を担う。

早速tensolflowのコードを見ながら処理を追いかけたい。
こちらのコードを参考にさせてもらう。

先に書いたフロー図と異なり、畳み込みフィルターとプーリング層の処理セットを2つ並べている。

(1段目の畳み込みフィルターとプーリング層)
# 入力の次元数が784(画像サイズ28x28ピクセル)。
# Noneとしているのは入力数は状況に合わせて動的対応させるため。
x = tf.placeholder(tf.float32, [None, 784])

# 配列内は、[画像枚数, 画像サイズ(縦x横), レイヤ数]。
# 画像枚数はplaceholderに格納したデータ数に任せるよう-1で指示。
# レイヤ数は、複数レイヤを重ね合わせて一つの画像にしている場合に増える。
x_image = tf.reshape(x, [-1,28,28,1])

# フィルタのノード数(縦列)は32とする。
num_filters1 = 32

# 事前に抽出したい特徴が分かっていればそのフィルタを適用すればよい。
# フィルタ配列は、[フィルタサイズ(縦x横), 入力レイヤ数, フィルタ数]である。 
# ただし、手書き文字ではどういった特徴を抽出するか、それ自体が分からないため、
# フィルタ配列をVariableとし、それ自体を勾配降下法による最適化の対象にしてしまう。
# 今回はフィルタサイズを5x5とし、それを当てはめる入力画像はグレースケールの1レイヤとする。
W_conv1 = tf.Variable(tf.truncated_normal([5, 5, 1, num_filters1], stddev=0.1))

# 畳み込みフィルタの計算はconv2d関数を適用するだけである。
h_conv1 = tf.nn.conv2d(x_image, W_conv1, strides=[1,1,1,1], padding='SAME')

# 画像濃度が、ある値b_conv1より小さい場合は0として扱いたい。e.g) b_conv1 = 0.1
# 活性化関数ReLUはx軸が負の値を0にする。この性質を利用する。
# 定数項b_conv1の初期値を0ではなく0.1とするのは、誤差関数の停留値を避けるためである。
# ただし、0.1も最適かどうか分からない。よってこの閾値も最適化対象のパラーメータとするVariableとする。
b_conv1 = tf.Variable(tf.constant(0.1, shape=[num_filters1]))
h_conv1_cutoff = tf.nn.relu(h_conv1 + b_conv1)

# プーリング層である。
# 28x28ピクッッセルの画像を2x2のピクセルブロックに分解する。
# それぞれを1つのピクセルにすることで14x14ピクセルになる。
h_pool1 =tf.nn.max_pool(h_conv1_cutoff, ksize=[1,2,2,1], strides=[1,2,2,1], padding='SAME')


(2段目の畳み込みフィルターとプーリング層)
1段目と基本変わらない。入力と出力、ノード数が変わっているだけである。

1段目は32このフィルタがあった。2段目は64個のフィルタを使っている。
1段目でフィルタ適用され出力された32個の画像データに2段目のフィルタを当てはめ、それをレイヤ合成して1つにして完成である。2段目フィルタは64個あるため、出力も64個である。

num_filters2 = 64

W_conv2 = tf.Variable(tf.truncated_normal([5, 5, num_filters1, num_filters2], stddev=0.1))

h_conv2 = tf.nn.conv2d(h_pool1, W_conv2, strides=[1,1,1,1], padding='SAME')

b_conv2 = tf.Variable(tf.constant(0.1, shape=[num_filters2]))

h_conv2_cutoff = tf.nn.relu(h_conv2 + b_conv2)

h_pool2 =tf.nn.max_pool(h_conv2_cutoff, ksize=[1,2,2,1], strides=[1,2,2,1], padding='SAME')



◆ 識別部
説明が前後するが、上の特徴量の抽出ができたので、省略していた識別部のコードを見てみる。

注意点が2つ。
サンプルでは、隠れ層は1層である(最初のフロー図では2段だった)。
また、全結合層から出力時に適合させるソフトマックス関数の間にドロップアウト層を設けている。過学習(オーバーフッティング)を防止するため、一部のノードを切断することが目的である。トレーニングセットの正解率は高いが未知データへの精度の悪さを回避する処理である。


# 入力次元数(7 x 7ピクセル x フィルタ数) ※28⇒14⇒7
num_units1 = 7*7*num_filters2

# h_pool2をnum_units1個のピクセル値を一列に並べた1次元リストへ変換する
h_pool2_flat = tf.reshape(h_pool2, [-1, num_units1])

# 隠れ層のノード数
num_units2 = 1024

# 縦、横が紛らわいい。XWの行列計算思い浮かべること。
#                                     入力の次元数, 層のノード数
w1 = tf.Variable(tf.truncated_normal([num_units1, num_units2]))

b1 = tf.Variable(tf.constant(0.1, shape=[num_units2]))
hidden1 = tf.nn.relu(tf.matmul(h_pool2_flat, w1) + b1)

# ドロップアウト層である。
keep_prob = tf.placeholder(tf.float32)
hidden1_drop = tf.nn.dropout(hidden1, keep_prob)

# 最後にソフトマックス関数を適用する。
w2 = tf.Variable(tf.zeros([num_units2, 10]))
b2 = tf.Variable(tf.zeros([10]))
p = tf.nn.softmax(tf.matmul(hidden2_drop, w2) + b2)



◆誤差関数、トレーニングアルゴリズム、正解率の定義
定義した数式のパラメータの良し悪しを判断する誤差関数と、それを最小にするパラメータを決める。

コードを見てから説明する。

t = tf.placeholder(tf.float32, [None, 10])

loss = -tf.reduce_sum(t * tf.log(p))

train_step = tf.train.AdamOptimizer(0.0001).minimize(loss)

correct_prediction = tf.equal(tf.argmax(p, 1), tf.argmax(t, 1))

accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))


tは正解部分にビットが立っている10次元のデータである。
下の例の一行目であれば、答えは手書きの2という数字を表す。
t =
[[0, 0, 1, 〜, 0],
       ・
       ・
       ・        ]]

次に誤差を求める。誤差関数として有名かつ直感的に理解が容易なのは最小二乗法だろうか。TensorFlowでももちろん利用できる。
loss = tf.reduce_sum(tf.square(y-t))
しかし最小二乗法では推定した関数から値を予測する確率を最大化する情報を得られない。そこで最尤(さいゆう)推定法を使っている。

誤差を最小にするパラメータを決定する処理には、勾配降下法によるトレーニングアルゴリズムを使う。学習率のディフォルト値は明示的に0.0001を使っている(AdamOptimizer部分)。
トレーニングアルゴリズムにより学習率は動的に調整されるが、ディフォルトの初期値(0.001)がふさわしいとも限らない。学習率が大きいと、パラメータの最適化処理にかかる時間が短くなるが、パラメータが収束せず発散することがある。

argmaxは配列の行、または列内での最大値を取り出す関数である。
1を指定すると横方向、つまり行単位での検索となる。
そして予測と実データの比較をしている。

最終行は、castでbool値を1、0に変換して、reduce_meanによる平均の計算で正解率を求めている。