admin のすべての投稿

TensorFlow GET STARTED その1

この記事は TensorFlow > Develop > GET STARTED > Getting Started With TensorFlow を自分なりに和訳しながら理解しようとしたメモです。

正確な情報は公式ページを確認してください。
このページの情報に不備があったとしてもいかなる責任も負えません。

TensorFlowを始める

低レベルAPI -> 研究者などの細かいレベルで制御したい人向け
高レベルAPI -> 共通した処理を内包してくれているので、ユーザプログラムは同じようなものになる
contrib と名のつくものはまだ開発中のAPIだそうな。正式に採用されているAPIではないため今後廃れていく可能性がある。

このドキュメントでは 低レベルAPIと高レベルAPIの2つの側面から確認する。
それによって内部的にどういう処理がなされているかということをイメージすることができるようになる。

Tensors

Tensorとはベクトル概念の一般化。
というわけではよくわからないのでもうちょっとイメージを高めると
ものすごく大きなものでもその様子に線形性があるときには、わざわざ別に全部の情報を記載する必要はなく、基底の情報だけを格納すればデータとしては十分である。
その正規化された情報のことをテンソルと呼ぶ。

tensor-flowにおいては特定のデータの集合のことをテンソルと呼び、任意の次元の配列で表現された値の集合で構成される。
テンソルは rank という概念を持つ。これは次元の深さに近い
たとえば
3 -> rank 0
 ただのスカラ値であるため次元を持たない
[1., 2., 3.] -> rank 1 (shape [3])
 要素3個で構成される配列
[[1., 2., 3.], [4., 5., 6.]] -> rank 2 (shape [2,3])
 (要素3個で構成される配列)を持つ(要素2個で構成される配列)
[[[1., 2., 3.]], [7., 8., 9.]]] -> rank 3 (shape [2,1,3])
 (要素3個で構成される配列)を持つ(要素1個構成される配列)を持つ(要素2個で構成される配列)

チュートリアル

import

TensorFlowをimportする際には下記の構文を用いる

import tensorflow as tf

computational graph

内部の計算処理をオペレーションノードとして表現し、その処理の流れを連結してできたグラフのことをcomputational graph(コンピュータグラフ)というような呼び方で表現する
各ノードは0個以上のテンソルをinputとしてうけとり、1個のテンソルをoutputとして出力する。

定数もテンソルとして表現される。
定数テンソルは0個のinputをうけとり、定数をoutputとして出力するノードとして表現できる。
定数テンソルを定義するコードは例えば下記のようになる。

import tensorflow as tf

node1 = tf.constant(3.0, tf.float32)
node2 = tf.constant(4.0)

print(node1, node2)

ここでこのコードを実行したときには下記のようになる。

$ python hoge.py
(<tf.Tensor 'Const:0' shape=() dtype=float32>, <tf.Tensor 'Const_1:0' shape=() dtype=float32>)

printはノードの保持するテンソルの値(この場合3.0とか4.0)を出力しない。
テンソル値は評価されるときに処理される。

computational graphを実行するには session を用いる。
session は tensorflow の実行時の内部状態を保持して computational graphを実行するためのインタフェースである。
先程の computational graph を実行するコードは下記のようになる

import tensorflow as tf

node1 = tf.constant(3.0, tf.float32)
node2 = tf.constant(4.0)

sess = tf.Session()
print(sess.run([node1, node2]))

その実行結果を下記に示す。
node1, node2のテンソル値が表示されることが確認できる。

$ python hoge.py
[3.0, 4.0]

また node Tensorノードが提供するオペレーションを利用してより複雑なノードを構成することができる。
例えば下記のように node1, node2 を結合して 新しいノード node3 を作成することができる。

import tensorflow as tf

node1 = tf.constant(3.0, tf.float32)
node2 = tf.constant(4.0)

node3 = tf.add(node1, node2)

sess = tf.Session()
print(sess.run([node3]))

これは実行すると下記のようなnode1とnode2の結合されたテンソルの値を表示する

$ python hoge.py
[7.0]

この例はあまり面白くない。
というのも定数値を利用したら常に同じ結果しか表示されないためである。
次に ノードのinput値を変数として取り扱う placeholders という概念を取り上げる。

import tensorflow as tf

a = tf.placeholder(tf.float32)
b = tf.placeholder(tf.float32)
adder_node = a + b  # + provides a shortcut for tf.add(a, b)

sess = tf.Session()
print(sess.run(adder_node, {a: 3, b: 4.5}))
print(sess.run(adder_node, {a: [1,3], b: [2, 4]}))

placeholder はあとでパラメータを受け取ることを宣言する。
イメージ的には関数やラムダに近い。
また ノードに対するオペレータ a + b は tf.add のエイリアスとして機能する。

結果は下記のようになる。
任意のパラメータをinputとして取り扱えていることが確認できる。

$ python hoge.py
7.5
[ 3.  7.]

また更に複雑なノードも表現することができる。

import tensorflow as tf

a = tf.placeholder(tf.float32)
b = tf.placeholder(tf.float32)
adder_node = a + b  # + provides a shortcut for tf.add(a, b)

add_and_triple = adder_node * 3.

sess = tf.Session()
print(sess.run(add_and_triple, {a: 3, b: 4.5}))

結果は下記のようになる

$ python hoge.py
22.5

公式ページに掲載されている TensorBoard による computational graph を確認すると下記のようになっており、確認すると
adder_node * 3 という表現を用いたときに内部的には 3 という表現を 定数ノードとして表現する。
add_and_triple ノードは adder_node ノードのoutputテンソルと 定数3 のノードの出力テンソルを入力として受け取るノードとして表現されていることが見て取れる。

機械学習においては典型的には入力値は様々な値を取る。
モデルを訓練可能にするためには graph を同じ入力から新しい output を得るために更新する必要がある。
これを実現するために variables を用いることで、訓練可能なパラメータをグラフ上に表現することが可能になる。

tf.constant はコールされた際に初期化されるのに対して、 tf.Variable はコールされた時点では初期化されない。
session.run を実行する前に tf.global_variables_initializer() をコールして variables を初期化する必要がある。

試しにパラメータを追加した線形モデルを構築する

import tensorflow as tf

W = tf.Variable([.3], tf.float32)
b = tf.Variable([-.3], tf.float32)
x = tf.placeholder(tf.float32)
linear_model = W * x + b

sess = tf.Session()
init = tf.global_variables_initializer()

sess.run(init)
print(sess.run(linear_model, {x:[1,2,3,4]}))

実行結果は下記のようになる

$ python hoge.py
[ 0.          0.30000001  0.60000002  0.90000004]

ここまででモデルを作成することに成功した。
だけれどもこのままではまだこのモデルが正しいかどうかを評価することができない。
したがって 求めるべき答え y と、誤差関数を実装する必要がある。

誤差関数は提供されたデータから現在のモデルがどのくらい正確なものかということを評価する。
ここでは linear_model を評価するための誤差関数として線形回帰のための標準的なものを使用する。
提供されたデータと現在のモデルの誤差を自乗して加算する、いわゆる自乗誤差を用いる。

import tensorflow as tf

W = tf.Variable([.3], tf.float32)
b = tf.Variable([-.3], tf.float32)
x = tf.placeholder(tf.float32)
y = tf.placeholder(tf.float32)
linear_model = W * x + b

squared_deltas = tf.square(linear_model - y)
loss = tf.reduce_sum(squared_deltas)

sess = tf.Session()
init = tf.global_variables_initializer()

sess.run(init)
print(sess.run(loss, {x:[1,2,3,4], y:[0,-1,-2,-3]}))

ここで y を定義して目的関数の評価をしてみると下記のようになる

$ python hoge.py
23.66

手動で w や b の定義を変更すると誤差関数を 0 に導くことができる(当たり前だが)
だが機械学習の目的は自動的に適切なパラメータを学習してくれることだ。
次にどうやってこれを達成するかを示す。

tf.train API

tensorflow では誤差関数を最小化するために variable を少しずつ変更する optimizer と言うものが存在する。
シンプルな optimizer として gradient descent(最急降下法)これは誤差関数の導関数から傾きを算出し、誤差が小さくなるように parameter を更新していく手法です。
ふつう導関数を手動で計算するのはうんざりするほど面倒くさい作業で、かつ間違いやすい。

そこで tensorflow では tf.gradients 関数を用いるだけでモデルの記述のみから導関数を自動的に生成することができる(すげえ)
コードはシンプルに下記のようになる

import tensorflow as tf

W = tf.Variable([.3], tf.float32)
b = tf.Variable([-.3], tf.float32)
x = tf.placeholder(tf.float32)
y = tf.placeholder(tf.float32)
linear_model = W * x + b

squared_deltas = tf.square(linear_model - y)
loss = tf.reduce_sum(squared_deltas)

optimizer = tf.train.GradientDescentOptimizer(0.01)
train = optimizer.minimize(loss)

init = tf.global_variables_initializer()
sess = tf.Session()

sess.run(init)
for i in range(1000):
    sess.run(train, {x:[1,2,3,4], y:[0,-1,-2,-3]})

print(sess.run([W, b]))

学習された W, b の結果は下記のようにほぼ正解に収束されていることがわかる。

$ python hoge.py
[array([-0.9999969], dtype=float32), array([ 0.99999082], dtype=float32)]

tf.contrib.learn API

tf.contrib.learn は高レベルのAPIで機械学習の実装をシンプルに行うことができます。その内容は下記を含みます。
・training loops を実行する
・evaluation loops を実行する
・データセットの管理
・feeding の管理(?)
また tf.contrib.learn は共通のモデルを多く定義しています

また既存のモデルだけしか使用できないかというとそういうわけでもなく、モデルをカスタムして用いることもできます。

(※サンプルコードに関しては割愛します)


javaにおけるURLとURIの違い

javaにおいて似たような(これはわかってる人からしたらおしかりを受けそうな表現ではあるが)URLとURIについて、実は結構その違いについてうやむやにしたままやってきた部分があるので、この際ですしその違いについて調べてみます。

概要

javaにおけるURL(java.net.URL)と URI(java.net.URI)の違いとかについて述べます

調査

調べましょう。と言っても実はjavaDocsにかなり詳細が書いてありますのでそちらを参照していただけば早い話です。

URLおよびURIを確認しましょう。

かいつまんで解説すると

両者を比較しながら解説しましょう。
※下記の内容は javaの公式 に含まれる内容なのでより正確な情報を確認したい方は参照元を確認してください。

URI(Uniform Resource Identifier)はその名の示すとおり、リソースが指す場所を一意に示すための識別子です。

これには当然おなじみの http://example.com/hoge/fuga.html などのウェブ上のURLなんかを含みます。
幾つか例を上げてみましょう。

http://java.sun.com/j2se/1.3/
docs/guide/collections/designfaq.html#28
../../../demo/jfc/SwingSet2/src/SwingSet2.java
file:///~/calendar

でこれをちょっとした表現で示すと下記のようになっています。

[スキーム:]スキーム固有部分[#フラグメント]
ここで、角括弧 […] は省略可能なコンポーネントを表し、文字 : と # はその文字自体を表します。

さらにスキームが指定してあるものを 絶対URI と呼びます。また絶対でないURIは 相対URI と及びます。
さらにさらにスキーム固有部分がスラッシュで始まらないものを 不透明URI と呼びます。

不透明URIには例えば下記のようなものがあります

mailto:java-net@java.sun.com
news:comp.lang.java
urn:isbn:096139210x

とまあ、こういったようにリソースの識別子を文字列で表現したものがいわゆる URI となります。
不透明URIという表現が示すように、割りと固有の表現もありますが、そこら辺はあくまでそのドメインが解釈するものとして「これもURIですよ!」みたいな感じで(割りと)ゆるい定義をしています。

というわけで上記の定義に則っていればなんだってURIになります。
java実装面の話をしますとURIは単なるリソースを識別するための文字列なんで、それ以上それ以下でもありません。

URI の役割は、あるURIと他のURIが指しているものが一緒かどうか?とか、あるベースURIとある相対URIをくっつけて(これを解決するといいます)新しいURIを作成するとか、そういう責務がメインになります。

それに対して URL はもっと具体的なことを表現します。
Uniform Resource Locator という名前が指すように、何かしらのリソースが存在することを表現します。

またまたjava実装の話になりますが URL は URI で行っていた単純な文字列表現だけでなく、そのリソースにアクセスする手段などを合わせて提供します。
もっというとjavaのURLクラスに関しては ウェブスキーム(http)限定のリソースを表現する実装として機能しているようです。

やや抽象的な感じも残りますが、より詳細が知りたい方は合わせて

RFC2396RFC2732 を参照してみてください。


モチベーションを管理する

ここ最近仕事が落ち着いてすごく時間ができた。それ自体はいいことなのだが。
悪いことにすごくやる気が起きない。
途中まで読み進めていた本や、塩漬けしていた個人プロジェクトなどやるべきことはあるのだが一日のほぼすべてを睡眠に費やしてしまった。

ダメだなーとなんとなく思いながらも相も変わらず体は惰眠を貪っている。
なぜモチベーションが起こらないのか、どう行動したらいいのかというところをぼんやりと考えた。

まず何もやる気が起こらない、と言うのは言い換えると何も体験することがない状態とほぼ等しいと思う。
自分が富を得たり成長したりすることがない反面、失敗してリスクを負ったり気分が落ち込むこともない。
つまり何も変化のない平衡状態だといえる。

ではなぜ何も変化のない状態に身をおきたがるのかというと、単純に体験を積む行為自体がエネルギーを消費するからである。
いわゆる面倒くさい。ということである。

もうちょっと掘り下げると面倒くさいと思う理由がさらに一つあることに気がついた。
そもそもリターンを欲していない場合、リターンがほしいがそのリターンが得られないと思っている場合、または頭ではリターンが得られそうだと思っていてもその実体験を積んでいない(リターンを得られるという確信がない)場合には特に面倒くさいと感じる。
ようするに面倒くさいというのは行動コスト対リターンのパフォーマンスが悪いと思っている行為とも言いかえられる。

例えば仕事の依頼が来てそれに対応することを考えてみよう。
これは自分の中でこの行為を行った実績がある。その為行動コストは少ない。
また行為の対価としてお金を得られることが見込める。
その為リターンも得られるため、モチベーションは悪くなさそうだ。

じゃあコンビニにご飯を買いに行くという行為はどうだろう。
これも自分の中で行為を行った実績があるため行動コストは少ない。
またそもそも腹が減っているという原始的な欲求を満たすことができるためリターンを得られる。

また更に行動コストというものを分解して考えてみる。
行動が完了したとみなすのは、いわゆる目的が達成できたという点である。成功するとも言える。
対して目的を達成できなかった場合を失敗と定義しよう。

この時に今までに体験したことのない新しい目的地に達する場合は、当然そう安々とは行かないだろう。
こうすればうまくいくだろうという計画を練って実施する際には何度か計画通りに行かないこともある。
そのたびに計画を細かく修正しながら進める必要があり、成功(目的を達成する)ためには非常に労力を要する(だろうということを今までの人生で学習している)
対して失敗するのは簡単である。そもそも何もしなければいいし、計画がうまく行かなかった時点で諦めてもいい。
成功を得るためには全ての工程をこなさないといけないのに対して、失敗するのはいつでもできる。

対して一度成功した体験を再度実施するのは極めて簡単である。
どういうリスクが有りどうすればそれを回避できるかなどをすでに知っているからである。

で、話を戻してなぜ新しい行動する意欲が起きないかというとコスパが悪いと思いこんでいる点にあると解釈した。
成功して初めてリターンが得られる。失敗したことにより得られるものはないと思っている。

というわけで考え方を変えることにした。

まず失敗の定義であるが、細かい点でうまく行かなかったとき(仮にこれを細かい失敗とでも呼ぼう)。これは失敗とは呼ばない。
目的を達成することを諦めた時点で初めて失敗と言える。

逆に細かい失敗は、成功とも解釈することができる。
なんでかというと、「目的に対してこういう計画で試してみたがうまく行かなかった」というリターンを得られたと解釈できるから。
最終的な成功に向けて計画を修正すれば良い。

人間はDNAレベルでじっとはしていられない。
日々成長できる何かがあればそれは十分に価値があるリターンと言って良い。

新しいことをやる際には、細かい成功体験を積み重ねていくしかない。
むしろ新しいことをやる際の細かい失敗に対応する体験が一番のリターンだ。

同じところぐるぐる回っているだけじゃ面白くないでしょう。
同じことを繰り返し続けているのはそれこそリスクだ。


makefileおさらい

概要

linuxシステムのカーネル触る機会があるんだかないんだかで、改めてmakefileについておさらい。
今回はほとんど自分のメモみたいなものです。

makefileファイル

今回取り上げるのは

PREFIX = /usr/local
DEST_HEADER = $(PREFIX)/include/

TARGET = librpi_gpio.a
SRCS = rpi_gpiolib.c
OBJS = $(subst .c,.o,$(SRCS))
HEADERS = rpi_gpiolib.h rpi_gpio.h

RM := rm
CC := gcc
AR := ar

CPPFLAGS = -g -fPIC -O2 -l./
LDFLAGS = -g -fPIC
ARFLAGS = cr

$(TARGET): $(OBJS)
	$(AR) $(ARFLAGS) $@ $^ $(LOADLIBES)

install: $(TARGET) $(HEADERS)
	mkdir -p $(PREFIX)/lib/
	install -m644 $(TARGET) $(PREFIX)/lib/$(TARGET)
	mkdir -p $(DEST_HEADER)
	install -m644 $(HEADERS) $(DEST_HEADER)

.PHONY: clean

clean:
	$(RM) $(OBJS) $(TARGET)

出典は下記の書籍を読み進めていて出てきました。話はそれますが低レイヤの知識を得る際にオススメです。興味のある方は是非どうぞ。

さて私はそんなにmakefileをガッツリ触ったことがあるわけではないので、分からない点をひとつひとつ掘り下げていきます。

subst関数

まずここsubst関数について

SRCS = rpi_gpiolib.c
OBJS = $(subst .c,.o,$(SRCS))

Makefileで使えるユーザ関数であり、substは3つの引数を受け取ります。
c言語ライクにかくならば subst(from, to, text); のようなイメージになります。

この関数は text 文字列を対象として from を to に置換します。
したがって上記のMakefileの中で言うと “.c” という文字列を “.o” という文字列に置換することになります。
最終的に OBJS変数には rpi_gpiolib.o という文字列が格納されることになります。
文字通りコンパイル時のオブジェクトファイルの文字列を格納しているようです。

ビルド

AR := ar
ARFLAGS = cr

$(TARGET): $(OBJS)
	$(AR) $(ARFLAGS) $@ $^ $(LOADLIBES)

まずは細かいメタ変数の部分から確認すると
$@ はターゲット名なので librpi_gpio.a
$^ は全依存関係のリストなので rpi_gpiolib.c
を指すことになります。

その後いきなり arコマンドが展開されているので、.oを生成するルールが無いように見えるのですが
makefileにはデフォルトルールがありそこに .oターゲットが定義されており .cを依存ファイルとしてコンパイルするルールが存在します。
このあたりは make -p で確認できるようです。

下記のようなルールを確認しました。

make -p
...
%.o: %.c
#  commands to execute (built-in):
        $(COMPILE.c) $(OUTPUT_OPTION) $<
...

$(LOADLIBES)に関しては定義されていないようなので、この場合は特に意味を成さないですが、通常はリンクするライブラリのパスを入れます。

さて最後に ar コマンドに関してですが、アーカイブを作成します。
現在ではもっぱらライブラリの作成時にのみ使用されるコマンドで、複数オブジェクトをまとめてライブラリを作成する際の典型的なパターンです。
これによって複数の .oファイル(この例に関しては1角ファイルですが)から .aファイルを作成します。

インストール

install: $(TARGET) $(HEADERS)
	mkdir -p $(PREFIX)/lib/
	install -m644 $(TARGET) $(PREFIX)/lib/$(TARGET)
	mkdir -p $(DEST_HEADER)
	install -m644 $(HEADERS) $(DEST_HEADER)

installコマンドにより作成したライブラリファイルを適切な場所にコピーします。
cpコマンドと何が違うのかというと、コピーと同時に属性を指定することができます。
こちらは特に難しいことはないですね。

以上となります。
はじめはよくわからなかったですが分解して見たらかなりしっかりした構造であることがわかりました。
また調べていてこちらのページを見つけたのですが、リンカ/ローダの仕組みを歴史的な側面からも見つつ体系的に非常によくまとめられていたのでオススメです。

参考ページ

http://qiita.com/chibi929/items/b8c5f36434d5d3fbfa4a
http://0xcc.net/blog/archives/000107.html
http://linuxjf.osdn.jp/JFdocs/Program-Library-HOWTO/shared-libraries.html


aws g2インスタンスでtensorflowをGPUサポートで動作させる

概要

機械学習をやる機会があり、巡り巡って こちら の word-rnn を tensorflow 上で動作させるものにめぐりあいました。

学習をするにあたって入力データがそれなりの量になってくると学習に時間がかかり、現実的な時間では終了しません。
そこで色々調べてみると tensorflow には GPUをサポートする機能があり、それによって処理の高速化が図れるようです。
GPUは通常名前のごとくグラフィック処理用の演算装置ですが、その演算処理が機械学習にも応用できるということですね。

今回はそれをaws環境で実行するべく、グラフィックボードを持つg2タイプのインスタンスで動かすことを目標としました。

先人たちの遺産について

一年くらい前から同じようなことをやろうとしていた人たちの記事が結構色々出てきます。
日本でのドキュメントもそこそこ出てきます、が初めから断言しておきますと、かなりの記事がもはや古くなりすぎていて参考になりません

このあたりの流れが非常に早く、現在では随分具合が異なるようです。

またこの記事も将来的に現実と乖離してくると思いますので、その点は十分注意してください。

インストールしてみる

色々参考にしたところ皆さん Ubuntu14.04 のAMIでやられていたので、自分もそれに倣うことにしました。
インスタンス起動後、まずは様々な記事でも導入されているように、必要なモジュールをインストールします。

$ sudo apt-get update
$ sudo apt-get upgrade -y
$ sudo apt-get install -y build-essential python-pip python-dev git python-numpy swig python-dev default-jdk zip zlib1g-dev ipython

二つ目のコマンドを実行するとGRUBローダーの設定を迫られたりしますが、自分の場合は何も選択せずに次に進み
次の選択肢では install package maintainers version を選択しました。

また参考記事の通りに実行していきます。

$ echo -e "blacklist nouveau\nblacklist lbm-nouveau\noptions nouveau modeset=0\nalias nouveau off\nalias lbm-nouveau off\n" | sudo tee /etc/modprobe.d/blacklist-nouveau.conf
$ echo options nouveau modeset=0 | sudo tee -a /etc/modprobe.d/nouveau-kms.conf
$ sudo update-initramfs -u
$ sudo reboot

ここで一旦再起動

$ sudo apt-get install -y linux-image-extra-virtual
$ sudo reboot
# Install latest Linux headers
$ sudo apt-get install -y linux-source linux-headers-<code>uname -r</code>

ここまで来たら tensorflowの公式ページ に従ってインストール作業を進めましょう。

以下は公式サイトの引用ですが、現在では Cubaのバージョンを8.0、cuDNNのバージョンを5をインストールすれば問題ないようです。

Download and install Cuda Toolkit
https://developer.nvidia.com/cuda-downloads
Install version 8.0 if using our binary releases.
Install the toolkit into e.g. /usr/local/cuda.
Download and install cuDNN
https://developer.nvidia.com/cudnn
Download cuDNN v5.

この2つのモジュールをインストールしたあとで 公式ページに則ってpip経由でインストール作業を行います。

自分の場合は python2系 を対象としたかったので下記のように実行しました。

$ export TF_BINARY_URL=https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-0.12.0rc0-cp27-none-linux_x86_64.whl
$ sudo pip install --upgrade $TF_BINARY_URL

アプリケーションコードを実行したところ下記のように正常にモジュールライブラリを実行できているようです。
(幾つか警告のようなものもありますが)
またGRID K520というg2インスタンスで搭載しているグラフィックボードの名前も確認できます。

I tensorflow/stream_executor/dso_loader.cc:128] successfully opened CUDA library libcublas.so locally
I tensorflow/stream_executor/dso_loader.cc:128] successfully opened CUDA library libcudnn.so locally
I tensorflow/stream_executor/dso_loader.cc:128] successfully opened CUDA library libcufft.so locally
I tensorflow/stream_executor/dso_loader.cc:128] successfully opened CUDA library libcuda.so.1 locally
I tensorflow/stream_executor/dso_loader.cc:128] successfully opened CUDA library libcurand.so locally
I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:936] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
I tensorflow/core/common_runtime/gpu/gpu_device.cc:885] Found device 0 with properties:
name: GRID K520

処理自体もCPUでの処理と比べて高速化できているようでしたので、これにてインストール作業を完了しました。

参考記事

http://qiita.com/h860a/items/294262d98e1223008252


mariadbのcollationをutf8mb4 に対応させる

概要

アプリケーション開発を行っていたところ下記のようなエラーが出た。
この現象について調査する。

Caused by: java.sql.SQLException: Illegal mix of collations (utf8mb4_bin,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation '='

原因

これは utf8mb8 で作成したテーブル内部に varchar binary のカラムに対して where 条件を指定したときに発現しているようである。

collation の設定を確認したところ下記のようになっている。

MariaDB [(none)]> show variables like '%collation%';
+----------------------+--------------------+
| Variable_name        | Value              |
+----------------------+--------------------+
| collation_connection | utf8_general_ci    |
| collation_database   | utf8_general_ci    |
| collation_server     | utf8_general_ci    |
+----------------------+--------------------+

どうやら mysqld の持っている collation が色々存在して
それとテーブルの照合順序が食い違っているということが原因のようである。
というわけで collation を一致させることで不具合の修正を図る。

対応

この辺を参考にしながら文字コードを設定する。

[client]
default-character-set = utf8mb4

[mysql]
default-character-set = utf8mb4

[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_bin

あまりMariaDBの設定には詳しくないが、どうやらMySQLのそれとは多少なり異なるようである。
修正して再度 collation を表示してみると無事に修正された。

MariaDB [(none)]> show variables like '%collation%';
+----------------------+--------------------+
| Variable_name        | Value              |
+----------------------+--------------------+
| collation_connection | utf8mb4_general_ci |
| collation_database   | utf8mb4_bin        |
| collation_server     | utf8mb4_bin        |
+----------------------+--------------------+

アプリケーションの方も無事に動作した。


osx brew で easy_install が正常にインストールされない

概要

python を使用する機会があり、brew経由でインストールを行った。
ついで pip をインストールしようとしたところ下記のようなエラーがでてしまって pip のインストールができない。
これについて対応してみる。

$ easy_install
Traceback (most recent call last):
  File "/usr/local/bin/easy_install", line 9, in <module>
    load_entry_point('setuptools==29.0.1', 'console_scripts', 'easy_install')()
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/pkg_resources.py", line 357, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/pkg_resources.py", line 2394, in load_entry_point
    return ep.load()
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/pkg_resources.py", line 2108, in load
    entry = __import__(self.module_name, globals(),globals(), ['__name__'])
  File "build/bdist.macosx-10.11-intel/egg/setuptools/__init__.py", line 10, in <module>
  File "build/bdist.macosx-10.11-intel/egg/setuptools/extern/__init__.py", line 1, in <module>
ImportError: No module named extern

調査する

brew を用いてインストールしたのだが、一度消してまた入れてみる

$ brew install python
==&gt; Downloading https://homebrew.bintray.com/bottles/python-2.7.12_2.el_capitan.bottle.tar.gz
Already downloaded: .../Caches/Homebrew/python-2.7.12_2.el_capitan.bottle.tar.gz
==&gt; Pouring python-2.7.12_2.el_capitan.bottle.tar.gz
==&gt; Using the sandbox
Warning: The post-install step did not complete successfully
You can try again using <code>brew postinstall python</code>
==&gt; Caveats
Pip and setuptools have been installed. To update them
  pip install --upgrade pip setuptools

You can install Python packages with
  pip install &lt;package&gt;

They will install into the site-package directory
  /usr/local/lib/python2.7/site-packages

See: https://github.com/Homebrew/brew/blob/master/docs/Homebrew-and-Python.md

.app bundles were installed.
Run <code>brew linkapps python</code> to symlink these to /Applications.
==&gt; Summary
🍺  /usr/local/Cellar/python/2.7.12_2: 2,948 files, 39.8M

すると読み飛ばしていたのだがインストール後の処理が正常に完了していないようなことが表示されていた。

手動で実行してみると

$ brew postinstall python
==> Using the sandbox
Error: Permission denied - /usr/local/lib/python2.7/site-packages/sitecustomize.py20161201-35997-9nuclf

すると権限がないようなことを言われる。これが原因か?

対応

brew は sudo で実行することはできないので(詳しくないができるかも。できても推奨していないが。)
権限で文句を言われている lib/python2.7 に直接一般ユーザでの権限を与える

$ sudo chown -R <user>:<group> /usr/local/lib/python2.7

そして再度後処理を実行してみる

$ brew postinstall python
==> Using the sandbox
==> /usr/local/Cellar/python/2.7.12_2/bin/python -s setup.py --no-user-cfg install --force --verbose
==> /usr/local/Cellar/python/2.7.12_2/bin/python -s setup.py --no-user-cfg install --force --verbose
==> /usr/local/Cellar/python/2.7.12_2/bin/python -s setup.py --no-user-cfg install --force --verbose

原因はよくわからないが動いた。easy_installも動作することが確認できました。
なんてことはありませんでした。


ftpクライアントを実装してみる

まとまった時間ができたので、ちょっと前からやろうと思っていましたftp clientの実装に取り組んでみました。

とりあえず形にしようというところまでを着地地点として仕上げたので、本当にftpのみの実装になっています。
引き続きftpsなどの実装を拡張して行きたいと思います。

実装した感じとしては、ftp自体は非常にシンプルなプロトコルだなと。
C実装ではすでにTCP/IPを利用するためのライブラリが豊富に存在するので、ほぼほぼアプリケーションを実装する感覚に近いですね。
ソケットの上にテキストベースのメッセージでやり取りするだけなので。


SeleniumのStaleElementReferenceExceptionに対応する

概要

Selenium (javaAPI) を用いてスクレイピングを実施しするような仕組みを作っていたところタイトルにもあるエラーに遭遇した
(そうでなくてもSeleniumはなれるまでなかなかピーキーな動きをするときもあるが)

厄介なことに毎回必ず同じタイミングで発生するわけでもなく、その発生はかなり不定期である。
実施しているスクレピング処理においては、条件を引き起こすページすら一致せずにどのページでもまんべんなく発生する可能性が存在するようである。

今回はその解決方法について調査して実装までを検討する。

調査

まずはスタックトレースを見てみる、関連部分だけを抽出すると概ね下記のようなメッセージが出ている。

Caused by: org.openqa.selenium.StaleElementReferenceException: Element not found in the cache - perhaps the page has changed since it was looked up
Command duration or timeout: 7.64 seconds
For documentation on this error, please visit: http://seleniumhq.org/exceptions/stale_element_reference.html
Build info: version: 'unknown', revision: 'unknown', time: 'unknown'
System info: host: 'something host', ip: 'xxx.xxx.xxx.xxx', os.name: 'Linux', os.arch: 'amd64', os.version: 'something os', java.version: '1.8.0_91'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Capabilities [{applicationCacheEnabled=true, rotatable=false, pageLoadStrategy=unstable, handlesAlerts=true, databaseEnabled=true, version=42.0, platform=LINUX, nativeEvents=false, acceptSslCerts=true, webStorageEnabled=true, locationContextEnabled=true, browserName=firefox, takesScreenshot=true, javascriptEnabled=true, pageLoadingStrategy=unstable, cssSelectorsEnabled=true}]
Session ID: xxxxxxxxxx

ご丁寧にドキュメントのURLがエラーメッセージに含まれているので、公式ページへアクセスしてみる。

読んでみるとこの例外が発生するパターンは大きく分けて2つ存在するようだ。


参照しているエレメントが削除された場合

大概はこちらのケースに該当するようである。
例えばエレメントの参照を取得した時点から、別ページに移動、その後さらに保持していた参照を引き続き利用しようとした場合や、はたまた何らかのjsライブラリによって保持していたエレメントの参照が別の参照に置き換えられてしまった場合などがある。

この場合例えばid属性などは同じものを保持しているのにもかかわらず、内部的には別のDOMインスタンスとして管理されているため、例えば同じ属性を用いてセレクタを作成し、参照を引っ張ってきたとしても以前参照していたものはなくなってしまったためこのような例外を引き起こす。

エレメントがDOMにアタッチされていない場合

例えばtabを表現するテクニックにDivを予め用意しておいて、実際に表示されるDomは一つだけで他のDomは単純に値を保持するためだけに使用されているような場合、場合によっては他のDivはDOMから参照が保持されていない場合があるようである。
こんなケースは非常にまれでしょう。


その他jsによってエレメントタイプが変更された場合などもこの例外を引き起こす可能性があるようです。
が、どれにしても今回のケースに当てはまっていないように思う。

そのためweb上を検索しほかのサイトを調査していたところ、ページが読み込まれる際に間髪入れずにDOMを検索する際にうまくいかないことがあるようである。
またリトライすることでうまくDOMを検索できるケースもあるようであった。

これらを踏まえて対応を考える。

対応

はっきりとした原因はわからなかったが、おそらく今回のケースはページを移動した直後にDOMを検索することが原因だと予想する。

そのためページを移動した後にwait処理を導入する。またそれと同時にリトライ機構を導入する。

実装

下記の用に実装を行った。概念的なものしか示していないがなんとなくイメージは掴めると思います。
もともとの処理としてはループで色々なページを探索していたので、forの概形だけ残してあります。

変更前

    for( someCondition ) {
        driver.get(url);

        // スクレイピング処理
        someScrape();
    }

変更後

    for ( someCondition ) {
        driver.get(url);

        // StaleElementReferenceException を回避するため、ここで明示的にDOMの読み込みを待つ
        _sleepSec(5);

        for(int count=1; count<=RETRY_COUNT; count++) {
            try {

                // スクレイピング処理
                someScrape();

                break;
            } catch (StaleElementReferenceException e) {
                // StaleElementReferenceException が発生した場合は規定回数内であればリトライを行う
                if (count >= RETRY_COUNT) {
                    throw e;
                }
                _sleepSec(5);
            }
        }
    }

対応したところ、今のところ問題なさそうである。

参考ページ

http://www.software-testing-tutorials-automation.com/2015/02/how-to-handle-stale-element-reference.html
http://blog.afnf.net/blog/69


MVCというデザインパターンはもはや十分ではない

概要

今さら私が提示するような話でもないほど同じような話はネット上に腐るほど存在しているだろう。
にも関わらず不思議とシステム開発の現場レベルで見てみると、昔と変わらない手法をずっと続けていたり、また優秀なアーキテクトに恵まれていない現場などはひどく絡みあったコードと格闘していることは多い。
改めて近代的なデザインパターン・アーキテクチャとは何かということについて自分なりに詰めてみようと思う。

初めてに言ってしまうと、ずばりこの記事の着地は ドメイン駆動設計(DDD)への招待である。
同じような疑問を感じている人の何かヒントになればと思う。

人々はオブジェクト指向を手にした

昨今のソフトウェア製作の設計パターンは変化しているのだろうか、ということをまず考えてみよう。
「コンピュータの登場から今まで」のような大きなくくりで見た時には、ソフトウェアの設計パターンは大きく変化していると断言していいだろう。(といってもそんな昔に私は生まれていないが)

時代ごとに変化する大きな要因の一つに、偉大な先人たちが開発してきたコンピュータ言語がある。
昔はアセンブラ・C言語などの、手続き型言語が大部分を占めていた時代があるが、この頃開発においてドメインに着目するというよりかは手続きそれ自体に着目している。
これは言語のパラダイム的にやむを得ない部分が大きいが、いずれにしてもプログラミングされたその内容はそれぞれが意味をあまり持たない。個別には意味がわからない処理の連続が、結果として業務ロジックとして存在している。

その後時代は流れてついに人類はオブジェクト指向言語を手にする。このオブジェクト指向というのはプログラミングパラダイムの革命の一つと言っていいだろう。
オブジェクト指向によってドメインモデルと実装がかなり親和性を持って結合できるようになったのである。
そしてコンピュータ資源も豊富になってきた今、高級言語を使っていればあまりメモリなどの資源を期にすることもない。
実装都合でねじ曲げてきた手続き型への束縛からも開放されつつある。

これが爆発的となり、様々なツールなどが世にでる。sbtなどのマルチプロジェクトを取り替えるような機構ができたりして、その実装性能よりも、実装とドメインモデルをどれだけ近づけて、人間の脳に理解しやすいようにプログラミングを行う時代になっている。

MVCの誕生

Web業界ではそれまで多くのシステムは cgi と呼ばれる仕組みを利用してシステムを実装していた。
これは先程の話で言うとオブジェクト指向の誕生前で、割りかし手続き型の側面が強かった。

これがオブジェクト指向の出現によってMVCというデザインパターンの実装が用意になり、それをサポートする多くのフレームワークが誕生した。
多くの(初級)開発者たちはとりあえずMVCすればそれなりに構造化された実装を手にすることができた。

MVCの功として、まず多くの初級開発者たちにとって開発の目印になってくれたことである。
それまでとくらべてエンジニアたちの一種のヒントのようなトピックを投下して議論の対象となった。

また実際そのデザインパターンはシステムがあまり大きくないうちは非常に効率的に開発できる(ように見える)ためそれほど規模の大きくないシステムにとっては問題がそれほど露呈しない場合も多い。

MVCは銀の弾丸ではない

実際MVCのすべてが否定されるべきものではない、一種のアンチパターンとして利口なUIパターンなどというものがあるがそういった多くの場合はとらないでおくべき選択肢を序盤に排除してくれるような働きをしている。

ただ単純にMVCという一言で対応するにはシステム開発はそれほどシンプルではなかったという話である。

個人的に思うところは MVC の表す Model の責務が大きすぎるのである。
そのため人によって解釈が異なったり、またそもそもそこの設計を怠ったりということで大きなシステムに対応できなくなっているパターンをよく目にするように思う。

具体的によく目にするのが Model 内部にDBレイヤへのアクセス処理などが記述されていて、どんどんモデルが膨らんでいくなどのようなことである。

本来のクラスの責務をしっかり検討する必要があり、MVC を拡張するようなよりドメインとマッチした設計思想が必要なのである。

DDDへの招待

そこで DDD である。昔からあったのかもしれないが、最近特に多く目にするようになってきた。

DDD とはドメイン駆動設計の略称であるが、これはドメイン(業務ロジック)に特に着目して、それをいかに齟齬なく実装に落としこむかということに着目した設計手法である。

実際かなり具体的な設計思想を(先人たちの経験の蓄積として)もっており、これらを盛り込むことでシステムが大きくなってきた際にも拡張性がある実装を担保できる。

たとえば具体的には先程の話で一緒くたに Model と称していたものを Entity や ValueObject など(他にもいろいろあるが)いうようなより詳細なドメインモデルに落としこむ。
これによりMVCで取り扱っていたものを、より適切な責務へと分割することができる、、、などなど

その内容については長くなるのでまたの機会に割愛するが、DDDのバイブルである下記の書籍に目を通すと良いと思う。一度目を通しておいて損はないです。