星にゃーんのブログ

ほとんど無害。

Elixirで二重数を実装して自動微分するまで

最近プログラミングElixirを読んでいる。

実際にプロジェクトを作る流れを示した練習課題があったり、ライブラリ事情だったり、実用性の高いガイドブックのような形式で、リファレンスじみた退屈さがなくて読みやすい。 Elixir言語だけでなく、周りのエコシステムごと解説されていて、いざ手を動かそうとした時のハードルが非常に低かった。

Elixirでは、自分で型を定義したり、演算子オーバーロードしたりもできる、という内容が本の後半に登場する。といってもざっくりとした紹介があるだけなので少し物足りない。

そこで、練習がてら二重数で自動微分する - Qiitaを参考に、Elixirで二重数を実装してみた。


まずは演算子オーバーロードしたいので、デフォルトの定義をimportしないように設定する。

defmodule DualNumber do
  defstruct a: 0, b: 0

  import Kernel, except: [+: 2, -: 2, *: 2, /: 2]

Inspectプロトコルを実装して、 a + bεの形で表示することにする。

  defimpl Inspect do
    def inspect(%DualNumber{a: a, b: b}, _opts) do
      "#{a}+#{b}ε"
    end
  end

がりがりと算術処理を実装。中置演算子の定義について、引数名は特に宣言がないらしい。 Kernel.+のように書くと、importしなかった関数も呼び出せる。

  def left + right do
    %DualNumber{ a: Kernel.+(left.a, right.a),
                 b: Kernel.+(left.b, right.b) }
  end

  def left - right do
    %DualNumber{ a: Kernel.-(left.a, right.a),
                 b: Kernel.-(left.b, right.b) }
  end

  def left * right do
    %DualNumber{ a: Kernel.*(left.a, right.a),
                 b: Kernel.+(Kernel.*(left.a, right.b), Kernel.*(left.b, right.a)) }
  end

  def conj(d) do
    %DualNumber{ a: d.a, b: (- d.b)}
  end

  def left / right do
    (left * (conj right)) / %DualNumber{a: Kernel.*(right.a, right.a), b: 0}
  end

実際に微分したい関数と、比較用にすでに微分してある関数を定義する。 今回は二重数で自動微分する - Qiitaと同じく { \displaystyle
f(x) = 4 x^2 + 3 x + 2
} を使った。

  def f(x) do
    (%DualNumber{a: 4, b: 0} * x + %DualNumber{a: 3, b: 0}) * x + %DualNumber{a: 2, b: 0} 
  end

  def df(x) do
    %DualNumber{a: 8, b: 0} * x + %DualNumber{a: 3, b: 0}
  end
end

実行するとこうなる。

iex(1)> DualNumber.f(%DualNumber{a: 2.0, b: 0})
24.0+0.# f(2) = 4 * 2^2 + 3 * 2 + 2 = 24
iex(2)> DualNumber.df(%DualNumber{a: 2.0, b: 0})
19.0+0.# df(2) = 8 * 2 + 3 = 19
iex(3)> DualNumber.f(%DualNumber{a: 2.0, b: 1.0})
24.0+19.# f(2+ε) = 24 + 19ε = f(2) + df(2) * ε 

二重数の実部にf(x)が、虚部に微分結果が返っているのがわかる。 複雑な関数の微分をテストしてもおもしろそうだが、今日は時間がないのでここまで。

環境構築2016

homebrew

公式サイトにある通り。 /usr/localのアクセス権限を書き換えないといけなかった記憶があるが、以前インストールしていたからか特に問題なくインストールできた。

$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

一応brew doctorは走らせておいた方がいい。

emacs

Mac OS X El Capitanにはemacsがデフォルトでインストールされている。 しかし、バージョンが古いので、Homebrewで新たにインストールする。

$ brew install emacs

あとはgit cloneなりなんなりでGithub上の.emacs.dを引っ張ってくる。

Stack

$ brew install haskell-stack
$ stack setup

roswell

Homebrewでインストール。

$ brew tap roswell/roswell
$ brew install roswell
$ ros setup

あとはWikiを読んでいろいろ設定する。slime周りとか。

tmux

Homebrewでインストール。

$ brew install tmux

Go

$ brew install go

後はよしなにGOPATHを設定する。

ghq

なんかすごいやつ。まだ使っていないので良くわからない。

$ go get github.com/motemen/ghq

Open usp Tukubai

シェル芸のユーティリティ集。

$ghq get https://github.com/usp-engineers-community/Open-usp-Tukubai
$ cd ~/.ghq/github.com/usp-engineers-communitu/Open-usp-Tukubai
$ make install

その他

$ brew install tree
$ brew install reattach-to-user-namespace

iPhoneのバックアップ時に「"Macintosh HD"ディスクへのコピーに失敗しました。不明なエラーが発生しました(-54)。」

f:id:takoeight0821:20160130144528p:plain

これの解決法。

support.apple.com

上記リンクのヘルプを参考に、iPhoneのバックアップイメージを削除する。 続いて、f:id:takoeight0821:20160130145636p:plain「今すぐバックアップ」からバックアップを作成する。

とりあえずこれで治った。

C言語でmapとreduce

#include <stdio.h>

#define ASIZE(ARRAY) (sizeof(ARRAY) / sizeof(ARRAY[0]))

#define REDUCE(ARRAY, FUNC, RESULT) for (int i = 0; i < ASIZE(ARRAY); i++) RESULT = FUNC(RESULT, ARRAY[i])

#define MAP(ARRAY, FUNC, RESULT) for (int i = 0; i < ASIZE(ARRAY) || i < ASIZE(RESULT); i++) RESULT[i] = FUNC(ARRAY[i])

#define N 100
double height[N] = {
  148.7, 149.5, 133.7, 157.9, 154.2, 147.8, 154.6, 159.1, 148.2, 153.1,
  138.2, 138.7, 143.5, 153.2, 150.2, 157.3, 145.1, 157.2, 152.3, 148.3,
  152.0, 146.0, 151.5, 139.4, 158.8, 147.6, 144.0, 145.8, 155.4, 155.5,
  153.6, 138.5, 147.1, 149.6, 160.9, 148.9, 157.5, 155.1, 138.9, 153.0,
  153.9, 150.9, 144.4, 160.3, 153.4, 163.0, 150.9, 153.3, 146.6, 153.3,
  152.3, 153.3, 142.8, 149.0, 149.4, 156.5, 141.7, 146.2, 151.0, 156.5,
  150.8, 141.0, 149.0, 163.2, 144.1, 147.1, 167.9, 155.3, 142.9, 148.7,
  164.8, 154.1, 150.4, 154.2, 161.4, 155.0, 146.8, 154.2, 152.7, 149.7,
  151.5, 154.5, 156.8, 150.3, 143.2, 149.5, 145.6, 140.4, 136.5, 146.9,
  158.9, 144.4, 148.1, 155.5, 152.4, 153.3, 142.3, 155.3, 153.1, 152.3,
};

double add(double x, double y) {
  return x + y;
}
double add3(double x) {
  return x + 3;
}

int main(void) {
  double sum = 0;
  MAP(height, add3, height);
  REDUCE(height, add, sum);
  printf("sum = %f, avg = %f\n", sum, sum / N);
}

ASIZEマクロは配列変数名を受け取り、その要素数を返す。

REDUCEマクロは畳み込み、MAPマクロは写像を取る。どちらも、配列変数名、関数名、結果変数名を取る。

サンプルコードは http://www.geocities.jp/m_hiroi/linux/clang03.html からお借りしました。