不確定特異点

広く深く、ところどころ超深く

誤差逆伝播法を計算中(続き2)

誤差逆伝播法で学習ができるようになったのですが、コードが手元にないので載せられません。(涙)というわけで明日に持ち越し。

現状は入力層、中間層、出力層の 3 層からなる最も単純なネットワークで学習してますが、以下の make-network のような関数に各層のノード数をリストで渡すことで、任意のノード数、深さのネットワークを構築できるようにするつもり。

(defun make-network (nodes)
  (labels ((make (nodes &key (bias nil))
             (when (second nodes)
               (cons (make-matrix (second nodes)
                                  (if bias 1 (first nodes)))
                     (make (cdr nodes) :bias bias)))))
    (when (< (length nodes) 2)
      (error "Node size is not enough."))
    (list (init-weight (make nodes))
          (init-weight (make nodes :bias t)))))

(defun init-weight (network)
  (loop for n in network
     do (loop for row below (dim n 0)
           do (loop for col below (dim n 1)
                 do (setf (aref n row col)
                          (- (random 2d0) 1d0))))
     finally (return network)))

make-network は入力の nodes を元に、各層の重みとバイアスを -1 ~ +1 の範囲の乱数で初期化した行列で返す関数になってます。

誤差逆伝播法を計算中(続き)

やっと \deltaの計算ができた!何でこんなに苦戦してるんだろう。たった 40 行のルーチンの実装に 2 日かかっている、というか、ほとんどの時間、式をこねくり回してました。今なら誤差逆伝播の導出がスラスラできる気がする。

あとは勾配法で重みを更新すれば学習してくれるはず!

本によっては対象としている問題が異なるせいか、NN の出力関数が恒等写像だったりシグモイド関数だったり、z という記号を順伝播における各段の出力の意味だったり、逆伝播における  \delta の意味だったりして、やたら混乱してきます。

簡易的にコメントアウトする

コードの一部をコメントアウトしてプログラムの挙動を確認したい場合があります。コメントアウトするたびに真面目にコード編集していると面倒くさいので、C 言語だと #if 0#endifコメントアウトするステートメントを囲い、#if 0 にしたり #if 1 にすることで素早くコードを切り替えることをよくやります。

Common Lisp ではステートメントというよりも、S 式単位にコメントアウトしたいことが多いので、C 言語のように簡単に編集するのが難しいと思います。

たまたま HyperSpec の Syntax の章のマクロ文字の説明を読んでいたところ、#+コメントアウトに使えるかもと思いました。コード中の任意の場所で、#+test expression と書くと、test で与えられたシンボルが *features* に存在するならば expression がそのままリーダに読み込まれ、存在しない場合は expression は空白として扱われるようです。

test はシンボルだけでなく、feature 式という and/or/not を使った論理式も書けます。そこで、feature 式に nil を与えて #+nil とすれば、続く expression は空白に置き換わるはずです。

次のコードは declare で始まる S 式をコメントアウトすることになります。

(defun fib (n)
  #+nil (declare (optimize (speed 3) (debug 0) (safety 0))
           (type fixnum n))
  (if (< n 2)
      1
      (the fixnum (+ (fib (- n 1)) (fib (- n 2))))))

コメントアウトを止める場合は、#+ マクロ文字の逆の意味の #- を使って 、#+nil#-nil に変えるだけです。とてもお手軽です。

(defun fib (n)
  #-nil (declare (optimize (speed 3) (debug 0) (safety 0))
           (type fixnum n))
  (if (< n 2)
      1
      (the fixnum (+ (fib (- n 1)) (fib (- n 2))))))

#+nil で最適化しないで実行速度を計測し、#-nil で最適化を有効にして効果を見る、という例を挙げてみました。自分が知らなかっただけで、もしかしたら常套手段なのかな?本当はエディタで簡単にコメントアウトできればいいんですけど。

5 分で書くパラメータ設定ルーチン

C言語でパラメータを設定するルーチンが欲しい、けれども、一時的な利用なので気合いの入ったのは要らない、というか適当でいいから早く動くものを!!という状況が発生したので、5 分で適当なパラメータ設定ルーチンを書いたのがこれ。

パラメータファイルは、

param0 123.456
param1 789

のように、一行内にパラメータと数値が空白で区切られているフォーマットを想定してます。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define KEY_SIZE 256
#define LENGTH(x) (sizeof(x)/sizeof(x[0]))

typedef struct {
    const char *key;
    double val;
} param_t;

param_t param[] = {
    {"param0", 0},
    {"param1", 0},
};

#define PARAM(x) get_param(param, x)

int set_param(FILE *fp, param_t *param)
{
    char key[KEY_SIZE];
    double val;
    int i;

    while (fscanf(fp, "%s %lf", key, &val) != EOF) {
        for (i = 0; param[i].key != NULL; i++) {
            if (strcmp(param[i].key, key) == 0) {
                param[i].val = val;
                break;
            }
        }
        if (param[i].key == NULL) { /* not found */
            fprintf(stderr, "[set_param] Unknown parameter: %s\n", key);
        }
    }
    
    return 1;
}

double get_param(param_t *param, const char *key)
{
    int i;

    for (i = 0; param[i].key != NULL; i++) {
        if (strcmp(param[i].key, key) == 0) {
            return param[i].val;
        }
    }
    
    fprintf(stderr, "[get_param] Unknown parameter: %s\n", key);

    return 0;
}

int main(int argc, char *argv[])
{
    FILE *fp;
    
    if (argc <= 1) {
        fprintf(stderr, "No file specified.\n");
        exit(1);
    }

    fp = fopen(argv[1], "r");
    if (fp == NULL) {
        fprintf(stderr, "File not found: %s", argv[1]);
        exit(1);
    }
    
    set_param(fp, param);

    printf("param0 = %lf\n", PARAM("param0"));
    printf("param1 = %lf\n", PARAM("param1"));    
    
    return 0;
}

ちょっと改良してみる

モジュールとしての I/F をもうちょっと真面目に考えつつ、任意のパラメータもサイズが許す限り登録できるように機能 UP した 20 分バージョン。

param.h

#ifndef PARAM_H
#define PARAM_H

#include <stdio.h>

#define KEY_SIZE 256

typedef struct {
    char key[KEY_SIZE];
    double val;
} param_t;

void read_param(FILE *fp, param_t *param, size_t size);
double get_param(param_t *param, const char *key);

#endif

param.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "param.h"

void read_param(FILE *fp, param_t *param, size_t size)
{
    int i;
    char key[KEY_SIZE];
    double val;

    for (i = 0; i < size; i++) {
        param[i].key[0] = '\0';
        param[i].val = 0;
    }
    
    while (fscanf(fp, "%s %lf", key, &val) != EOF) {
        for (i = 0; param[i].key[0] != '\0'; i++) {
            if (strcmp(param[i].key, key) == 0) {
                param[i].val = val;
                break;
            }
        }
        if (param[i].key[0] == '\0') {    /* parameter not found */
            if (i >= size - 1) {       /* leave one slot for sentinel */
                fprintf(stderr, "Not enough space for parameter\n");
                exit(1);
            }

            strcpy(param[i].key, key);
            param[i].val = val;
        }
    }
}

double get_param(param_t *param, const char *key)
{
    int i;

    for (i = 0; param[i].key[0] != '\0'; i++) {
        if (strcmp(param[i].key, key) == 0) {
            return param[i].val;
        }
    }
    
    fprintf(stderr, "Unknown parameter: %s\n", key);

    return 0;
}

このモジュールを利用する側はこんな感じになります。

#include <stdio.h>
#include <stdlib.h>

#include "param.h"
#define PARAM_SIZE 1024
param_t param[PARAM_SIZE];
#define PARAM(x) get_param(param, x)

int main(int argc, char *argv[])
{
    FILE *fp;
    
    if (argc <= 1) {
        fprintf(stderr, "No file specified.\n");
        exit(1);
    }

    fp = fopen(argv[1], "r");
    if (fp == NULL) {
        fprintf(stderr, "File not found: %s", argv[1]);
        exit(1);
    }
    
    read_param(fp, param, PARAM_SIZE);
    
    printf("param0 = %lf\n", PARAM("param0"));
    printf("param1 = %lf\n", PARAM("param1"));
    
    return 0;
}

このくらいの低機能でもプロトタイピングには十分使えそうです。

vagrant up でタイムアウトする件

新しい PC に Vagrant をインストールしていたときにハマったお話。

vagrant up すると以下のようなメッセージが表示されたままになってしまいました。しばらく経つと終わるのですが、正常に起動できているはずもなく vagrant ssh してもウンスンなのです。

default: Warning: Connection timeout. Retrying...

最初は何が起きているか全くわかりませんでしたが、Vagrantfile に、

config.vm.provider :virtualbox do |vb|
  vb.gui = true
end

のような設定をして、GUI モードで起動してみると、

仮想化支援機能(VT-x/AMD-V)を有効化できません。64ビット ゲストOSは64ビットCPUを検出できず、起動できません。ホストマシンのBIOS設定でVT-x/AMD-Vを有効化してください。

なるダイアログが!これで止まっていたんですね。。

このエラーは以前ハマったことがあったのですが、仮想化機能を有効にするとその後一切意識しなくなるため、すっかり忘れてました。

CODE COMPLETE(上)再読

WRITING SOLID CODE の再読が終わったので、続いて CODE COMPLETE に移ります。以前読んだので、再読になりますが、だいぶ時間が経って忘れているところがありそうなので、もう一度おさらいです。

Code Complete 第2版 上 完全なプログラミングを目指して

Code Complete 第2版 上 完全なプログラミングを目指して