データ読み込みに関するメモ

今日はDL4Jでのデータ読み込みに関するメモです。
簡単に書きたいので、ソースとコメントのみにします。

// RecordReaderはある媒体からデータを読み込んで共通形式のデータ(List<Writable>)を生成するインタフェースです。
// CSV・正規表現・画像・JSON・XML・YAMLなど、多くの実装があります。
// RNNで使用するシーケンシャルなデータはRecordReaderを継承するSequenceRecordReaderインタフェースが担当しますが、
// 時間という軸を表現するため、Listがもう一皮ついて、List<List<Writable>>を生成します。
RecordReader recordReader = new CSVRecordReader();

// 読み込むファイルをInputSplitに指定し、初期化します。
// InputSplitはRecordReaderに媒体の場所を分割して知らせます。今の場合、ファイル一つだけなので意味がないですが、
// 別の実装(NumberedFileInputSplit)を使えば、特定範囲の数字がついた複数のファイルを順に処理するといったことができます。
// (例: file1.csv, ..., fileX.csv)
InputSplit inputSplit = new FileSplit(new File("train.csv"));
recordReader.initialize(inputSplit);

// 共通形式(List<Writable>)のデータからミニバッチデータセット(DataSet)を生成するDataSetIteratorを生成します。
// 分類・回帰などそれぞれの用途に合うコンストラクタが存在します。今回は分類です。
int batchSize = 50;
// 行のうち、ラベル(正解)は何カラム目か?
int labelIndex = 0;
// 今回の場合、分類ですので、分類の数を指定しています。2種類ですね。
int numberOfClasses = 2;
DataSetIterator trainIter = new RecordReaderDataSetIterator(recordReader, batchSize, labelIndex, numberOfClasses);

// ...中略...

// Deep Learningのネットワークを生成・初期化します。
MultiLayerNetwork model = new MultiLayerNetwork(conf);
model.init();

// nEpochs回分、モデルを訓練させます。
for (int n = 0; n < nEpochs; n++) {
    model.fit(trainIter);
}

// 後はテストデータでモデルを評価(model.eval)をしたり、実データで予想(model.output)をしたり、
// モデルをディスクに保存(ModelSerializer.writeModel)すればいいですね。

sed簡単整理2 - パターンスペース・ホールドスペース -

パターンスペース及びホールドスペースに関してのメモです。

パターンスペース

下記のコマンドを

sed -e 's/hoge/Hoge/; s/foo/Foo/' sed_test.txt

疑似コードに変えると、下記のような感じになります(100%同じではないと思いますが)。

while (input.next()) {
    patternSpace = input.readLine()
    patternSpace = patternSpace.replace("hoge", "Hoge")
    patternSpace = patternSpace.replace("foo", "Foo")
    result.append(patternSpace)
}

つまり、パターンスペースは読み込んだ行そのものでありながら、コマンドの実行結果を保存する領域だといえます。

整理すると、通常のsedの処理を疑似コードに移すなら、以下のようになると思います。

while (input.next()) {
    行を読み込んでパターンスペースに入れる。

    直前のパターンスペースの内容に対し、コマンド1を実行し、パターンスペースに入れる。
    ...
    直前のパターンスペースの内容に対し、コマンドxを実行し、パターンスペースに入れる。

    直前のパターンスペースの内容を出力する。
}

コマンド1・・・コマンドxと書きましたが、セミコロン(;)で複数のコマンドが指定されたことを表します。

行 > パターンスペース > 処理 > ... > パターンスペース > 処理 > 結果の順ですね。

ホールドスペース

まず、下記はホールドスペースを使用した例と結果です。

cat << EOF > sed_test.txt
a apple haha
b apple hihi
c apple hopi
d man ama
EOF

sed -e 'G;h' sed_test.txt
#a apple haha
#
#b apple hihi
#a apple haha
#
#c apple hopi
#b apple hihi
#a apple haha
#
#d man ama
#c apple hopi
#b apple hihi
#a apple haha
#

上記のsedコマンドを疑似コードに変えると、下記のような感じになります(あくまで感じですが..)。

while (input.next()) {
    patternSpace = input.readLine();
    // Gコマンド
    patternSpace = patternSpace + "\n" + (holdSpace.isEmpty() ? "" : holdSpace)
    // hコマンド
    holdSpace = patternSpace
    patternSpace = patternSpace + "\n"
    print(patternSpace)
}

疑似コードを見ると分かると思いますが、ホールドスペースはパータンスペースの内容をコピーして保管したり、戻したりする領域です。
これでもっと複雑な文字列操作が行えます。

疑似コード的には次のようになったと考えればいいかなと思います。

while (input.next()) {
    行を読み込んでパターンスペースに入れる。

    直前のパターンスペースの内容に対し、コマンド1を実行し、パターンスペースに入れる。
    ...
    [パターンスペースの内容をホールドスペースにコピーしたり逆に戻したりする]
    ...
    直前のパターンスペースの内容に対し、コマンドxを実行し、パターンスペースに入れる。

    直前のパターンスペースの内容を出力する。
}

下記はsedの主なホールドスペース操作に必要なコマンドです。大文字は追記で、小文字は破棄してから追記です。

  • g : ホールドスペース -> パターンスペースにコピーします。パターンスペースにあった既存の内容は破棄されます。
  • G : ホールドスペース -> パターンスペースに追記します(次の行として追加)。
  • h : パターンスペース -> ホールドスペースにコピーします。ホールドスペースにあった既存の内容は破棄されます。
  • H : パターンスペース -> ホールドスペースに追記します(次の行として追加)。
  • d : パターンスペースを削除します。
  • D : パターンスペースの1行目を削除します。

他のコマンドと同じく"アドレス+コマンド+コマンドパラメータ"の形式で指定します。

次回はループ・bコマンドについて整理してみます。

sed簡単整理1 - 基本 -

簡単な変換はsedで、複雑な変換はawkというようにしていましたが、
sedawkぐらいのことができると分かったので、整理してみます。
sedperlっぽくて私にはawkの方が使いやすかったですが、
少しいじってみたら、sedの方が簡潔でよかったです。
状況次第でsedawkを使い分けたら、効率があがりそうです。

使い方

sed -e '処理内容' ファイル名
# 正規表現を使いたい場合、-rを付ける。
# 普段はリダイレクト(>)で別のファイルに出力し、原本は残すが、原本を修正したい場合は-iを付ける。

処理内容

アドレス+コマンド+コマンドパラメータ

アドレス

行番号又は文字列又は正規表現正規表現を使う時は-rオプションが必要。

1,5 : 1行目から5行目まで 
1~5 : 1行目から5行おき(1, 6, 11...)5,$ : 5行目から最後の行まで。
/apple/ : appleがある行。

コマンド

s, d, i, N, H, h, pなどがある。
各コマンドの前に!が付くと、逆の意味になる。

コマンドパラメータ

s/apple/pie/  #sコマンドのコマンドパラメータはappleとpie。
4asuper! #aコマンドのコマンドパラメータはsuper!。

sコマンド

置換。

cat << EOF > sed_test.txt
a apple haha
b apple hihi
c apple hopi
d man ama
e kkk appp
f apple mamo
g apple nami
abc apple !!
wow happy new year, duke!
EOF

sed -e '1,5s/apple/pie/g' sed_test.txt
# 1行目から5行目までのappleをpieに置換。
# 結果
# a pie haha
# b pie hihi
# c pie hopi
# ...

sed -r -e 's/happy.+year/>&</g' sed_test.txt
# &はパターンにマッチした文字列(下記の場合、happy new year)。
# 正規表現なので、-rオプションがついた。
# 結果
# ...
# wow >happy new year<, duke!

sed -e '/abc/s/apple/pie/g' sed_test.txt
# abcがある行のappleをpieに変換。
# 結果
# ...
# abc pie !!
# ...

dコマンド

削除。

sed -e '5d' sed_test.txt
#5行目を削除。

sed -e '5!d' sed_test.txt
#5行目以外を削除。

iコマンド

指定した行に文字列を挿入。

sed -e '4isuper!' sed_test.txt
# 4行目にsuper!を挿入。
# 結果
# a apple haha
# b apple hihi
# c apple hopi
# super!
# ...

aコマンド

指定した行の後ろに文字列を追加。

sed -e '4asuper!' sed_test.txt
# 4行目の後ろ(つまり、5行目)にsuper!を挿入。
# 結果
# a apple haha
# b apple hihi
# c apple hopi
# d man ama
# super!
# ...

Nコマンド

次の行を先読みする。複数の行をまとめて処理する時に利用できる。

sed -e 'N;N;s/\n/,/g' sed_test.txt
# 3行ずつ(基本1行 + N(次の1行) + N(次の1行))読み込んで、コンマでつなげる。
# 最後のgはglobal。マッチした全ての文字列を置換するという意味。これがないと1行目のみ置換される。
# 結果
# a apple haha,b apple hihi,c apple hopi
# d man ama,e kkk appp,f apple mamo
# g apple nami,abc apple !!,wow happy new year, duke!

H, h, p

ホールドスペース・パターンスペースに関連するコマンド。
sed簡単整理2に整理する予定。複雑な処理に使える。

JavaImporterの繰り返し生成時、with__noSuchProperty__ placeholderエラー

JDK 1.8.0_20で下記のJavascriptソースのようにJavaImporterインスタンスを繰り返し生成すると、
(実際のソースは違いますが、理解しやすさのため、ウェブからサンプルを持ってきました)

/* ファイル名 : test.js
    実行 : jrunscript test.js
    期待値 : 出力なし。
 */
var constant = 0.50;
var ind = 0.0;

for (var i = 0; i < 50; i++) {
    var math = new JavaImporter(java.lang.StrictMath);
    ind += 10.0;
    with (math) {
        StrictMath.exp(-constant*ind);
    }
}

for (var i = 0; i < 50; i++) {
    var math = new JavaImporter(java.lang.StrictMath);
    try {
        math.Foo();
    } catch (e) {
        if (! (e instanceof TypeError)) {
            throw e;
        }
    }
}

下記のようが例外がスローされました。
Exception in thread "main" java.lang.AssertionError: __noSuchProperty__ placeholder called
at jdk.nashorn.internal.objects.NativeJavaImporter.__noSuchProperty__(NativeJavaImporter.java:105)
at jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:557)
at jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:209)
at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:378)
at jdk.nashorn.internal.runtime.ScriptObject.invokeNoSuchProperty(ScriptObject.java:2113)
at jdk.nashorn.internal.runtime.ScriptObject.megamorphicGet(ScriptObject.java:1805)
at jdk.nashorn.internal.scripts.Script$test.runScript(test.js:8)
at jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:535)
at jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:209)
at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:378)
at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:568)
at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:525)
at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:521)
at jdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:187)
at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:249)
at com.sun.tools.script.shell.Main.evaluateReader(Main.java:332)
at com.sun.tools.script.shell.Main.evaluateStream(Main.java:368)
at com.sun.tools.script.shell.Main.processSource(Main.java:285)
at com.sun.tools.script.shell.Main.access$100(Main.java:37)
at com.sun.tools.script.shell.Main$2.run(Main.java:200)
at com.sun.tools.script.shell.Main.main(Main.java:48)

繰り返し生成というのはJavascript側でfor文を利用した繰り返しだけでなく、
Java側でJavascriptソースを実行するjavax.script.ScriptEngineのオブジェクトを
ThreadLocalやマップなどにキャッシュして繰り返し使用することも含みます。

JDKのバグのようで、JDK 1.8.0_40から直ったらしいです。
https://bugs.java.com/view_bug.do?bug_id=8060101

試しにJDK 1.8.0_60で上記のJavascriptソースを実行したら、エラーは出ませんでした。

ダブルクリックで任意のパスワードを簡単にコピペ

Windowsでダブルクリックで任意のパスワードをコピペできる方法です。

手順は以下の通りです。rubyが必要です。

  1. Windowsのデスクトップに拡張子がcmdのファイルを一つ作成します。仮にget_passwd.cmdとします。
  2. メモ帳でget_passwd.cmdの内容を以下にし、保存します。
@echo off
ruby -e "puts (('a'..'z').to_a  + ('A'..'Z').to_a + (0..9).to_a).shuffle[0..7].join" | clip

これで終わりです。
rubyスクリプトでパスワードを生成し、パイプでclipコマンドに渡しているところが要です。

任意のパスワードが必要な時、デスクトップにあるget_passwd.cmdをダブルクリックし、
必要なところにCtrl+Vすれば、パスワードがペーストされます。

rubyを利用していますが、コマンドラインの出力が得られれば、
どの言語でもかまいません。ここではrubyを基準に説明します。

もちろん、パスワードを生成してくれるウェブサイトも利用できますが、
ブラウザでアクセスすることすら面倒で、
(多分大丈夫だけど、オンラインだから)何か不安を感じている私のような方なら、
こちらの方法が楽でいいかなと思います。

行列演算 API(ND4J)の簡単整理

Javaディープラーニングライブラリー「Deeplearning 4 j(以下、DL4J)」で行列の演算を行う方法を整理します。

今まで数冊のディープラーニングや数学の本を読んで
ディープラーニングについて少しは分かった気はしますが、
数学が苦手なので、DL4で行列に関する引数及び戻り値の解析に難がありました。
なんというか、行列に関するAPIを呼び出した時、紙に書いてみないと、
頭の中に結果行列の姿がパット思い浮かべないです。

そこでここに行列演算のAPIについて書いて自分の理解を深めると同時に参考ドキュメントにしたいと思います。

DL4Jを使う時、行列の演算を行う時、依存ライブラリーとして、ND4Jという多次元配列のライブラリーのAPIを使用します。
以下はその説明です。
CSVやDBからDL4Jが自動で行列を作ってくれるので、以下のAPIを使うのはまれだと思いますが、
基礎となる部分ですので、理解して損はないと思います。

ND4Jのメソッドの結果はINDArrayで、多次元配列です。内部はC++で実装されているようです。

行列の作成

説明

INDArray nd = Nd4j.create(new float[]{1, 2, 3, 4, 5, 6, 7, 8}, new int[]{ 4, 2 });
/*
結果:
[[1.00, 2.00],
 [3.00, 4.00],
 [5.00, 6.00],
 [7.00, 8.00]]
*/

2番目の引数に行列を格納する多次元配列の形(shape)を指定します。
数字の数が次元の数(ここでは2つ)を表しており、要素は各次元の長さ(1次元 : 4つ, 2次元 : 2つ)です。
まず、長さ4の配列が作られ、それぞれの要素に長さ2の配列が割り当てられます。
2次元なので、まさに行・列ですね。

1番目の引数に指定された配列の要素が最後の次元(2次元)の長さずつ(ここでは2つずつ)、順に配列を埋めていきます。
ここではまず、[1, 2]と埋めて、次は[ 3, 4]、[5, 6]、[7, 8]と埋めていき、4x2の多次元配列、つまり、行列が完成します。

1番目の引数に指定される要素の数は2番目の引数に指定される要素同士の掛け算の結果と同じでなければなりません。
ここでは4 * 2 = 8ですから、8つの要素が必要です。

練習問題

以下の結果は何でしょうか。次元の数は3つです。答えは最後にあります。

INDArray nd = Nd4j.create(new float[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, new int[]{ 3, 2, 2 });

前置行列(Transpose)

説明

縦の要素と横の要素を入れ替えた行列です。行列を表すINDArrayオブジェクトに対してtranpose()メソッドを呼び出すことで前置行列を求められます。

INDArray nd = Nd4j.create(new float[]{1, 2, 3, 4, 5, 6, 7, 8}, new int[]{ 4, 2 });
/*
結果1:
[[1.00, 2.00],
 [3.00, 4.00],
 [5.00, 6.00],
 [7.00, 8.00]]
*/
INDArray tnd = nd.transpose();
/*
結果2:  結果1の縦と横が変わりました。
[[1.00, 3.00, 5.00, 7.00],
 [2.00, 4.00, 6.00, 8.00]]
*/

変形(Reshape)

説明

元の行列の形(行と列の数)を変えます。例えば、4x2行列を2x4行列に変えるなどです。
注意すべきところは変形前後の行と列の掛け算の結果が同じでなければならないということです。
例えば、4x2行列は3x3行列に変えることはできません。掛け算の結果(8 != 9)が違うためです。

INDArray nd = Nd4j.create(new float[]{1, 2, 3, 4, 5, 6, 7, 8}, new int[]{ 4, 2 });
/*
結果1:
[[1.00, 2.00],
 [3.00, 4.00],
 [5.00, 6.00],
 [7.00, 8.00]]
*/

INDArray rnd = nd.reshape(2, 4); // rows:2 , columns: 4
/*
結果2:
[[1.00, 2.00, 3.00, 4.00],
 [5.00, 6.00, 7.00, 8.00]]
*/

hstack(水平積み上げ)

引数に指定された複数のINDArrayを水平に埋めた行列を返します。
サンプルを見た方が早いでしょう。

INDArray nd1 = Nd4j.create(new float[]{1, 2, 3, 4}, new int[]{ 2, 2 });
/*
結果1:
[[1.00, 2.00],
 [3.00, 4.00]]
*/
INDArray nd2 = Nd4j.create(new float[]{5, 6, 7, 8}, new int[]{ 2, 2 });
/*
結果2:
[[5.00, 6.00],
 [7.00, 8.00]]
*/
INDArray hstack = Nd4j.hstack(nd1, nd2);
/*
結果3:
[[1.00, 2.00, 5.00, 6.00],
 [3.00, 4.00, 7.00, 8.00]]
結果1の行列と結果2の行列が水平に埋められた結果になりました。
*/

hstackの引数は可変長なので、複数のINDArrayが指定できます。

linspace(行ベクトル作成)

lower(下限値)、upper(上限値)、num(長さ)をそれぞれ順にlinspaceメソッドに指定して実行すると、
lowerからupperまでの範囲からnumだけの数を要素として持つX行1列の行列(行ベクトル)が返されます。
要素はnumに合わせてlower~upperの範囲を一定の間隔(つまり、一定のstepで)で分割し、求められます。
例えば、lower: 1, upper: 10, num: 3なら、3個の要素を持つ行列になりますが、
最初は1、2番目は真ん中の5.5, 3番目は10になります。

以下にサンプルを示します。

INDArray nd1 = Nd4j.linspace(1, 10, 3);
/*
結果1:
[1.00, 5.50, 10.00]
*/

INDArray nd2 = Nd4j.linspace(1, 10, 10);
/*
結果2:
[1.00, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00, 9.00, 10.00]
*/

INDArray nd3 = Nd4j.linspace(1, 10, 20);
/*
結果3:
[1.00, 1.47, 1.95, 2.42, 2.89, 3.37, 3.84, 4.32, 4.79, 5.26, 5.74, 6.21, 6.68, 7.16, 7.63, 8.11, 8.58, 9.05, 9.53, 10.00]
*/

今日はここまでです。思い出したら、vstack, diag, zeroも書きます。
恥ずかしい水準ではありますが、やはり書きながら、理解度が上がる気がします。

練習問題の解答

行列の作成

[[[1.00, 2.00],
  [3.00, 4.00]],

 [[5.00, 6.00],
  [7.00, 8.00]],

 [[9.00, 10.00],
  [11.00, 12.00]]]

2*2の行列が3つ生成された形です。

メソッド呼出時、引数にJSON(マップ)を渡せます。

Javaのテンプレートエンジン Velocityでメソッド呼出時、引数にJSONを渡せます。
色々いじってみて知りました。
方法は引数のところに{ "key1": "value1"....のように普段のJSONと同じく書けばいいです。
実際メソッドの引数に渡されるオブジェクトはMapです。
以下のサンプルを参照して下さい。

import java.io.StringWriter;
import java.util.Map;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.context.Context;

public class TestVelocityJson {
    // テンプレートで使うBeanクラス。
    public static class TestBean {
        public void duke(Map<String, Object> t) {
            // 適切にキャストして使用します。
            String param1 = (String) t.get("param1");
            int param2 = (int) t.get("param2");
            System.out.println("param1: " + param1);
            System.out.println("param2: " + param2);
        }
    }

    public static void main(String[] args) throws Exception {
        {
            // メソッド呼出。
            String template =
                "${testBean.duke({\"param1\" : \"v1\", \"param2\": 11})} ";
            Context vc = new VelocityContext();
            vc.put("testBean", new TestBean());
            StringWriter sw = new StringWriter();
            System.out.println("-- Test1 -- ");
            Velocity.evaluate(vc, sw, "test", template);
            /*
             * 結果
             * -- Test1 --
             * param1: v1
             * param2: 11
             */
        }
        {
            // マクロからメソッド呼出。
            String template =
                "#macro(dukeTest $p) " +
                "  $testBean.duke($p) " +
                "#end " +
                "#dukeTest({\"param1\" : \"v2\", \"param2\": 22})";

            Context vc = new VelocityContext();
            vc.put("testBean", new TestBean());
            StringWriter sw = new StringWriter();
            System.out.println("-- Test2 -- ");
            Velocity.evaluate(vc, sw, "test", template);
            /*
             * 結果
             * -- Test2 --
             * param1: v2
             * param2: 22
             */
        }
    }
}