#Julia言語

Juliaでの最適化では、函数の定義で

function f(x::Float64)::Float64

end

のように「型指定」しても全然__速くならない__ことを理解しておく必要があります。

function f(x)

end

のままで最適化できることを知らずに、一所懸命「型指定」するのは時間の無駄。
#Julia言語 函数の引数の「型指定」をするのは、multiple dispatchを使うためです。例えば

function f(x)
広く通用するアルゴリズムのコード
end

と定義しておいて、Float64専用の同名の函数を

function f(x::Float64)::Float64
Float64に最適化されたコード
end

と定義するとよい。
#Julia言語 しかし、

function f(x)
広く通用するアルゴリズムのコード
end

の段階で十分高速なコードを書ける場合も多いです。

それを可能にするのが、型の伝搬を促すコードの書き方です。

Juliaでは「型指定」ではなく、「引数の型の伝搬」で考える。
#Julia言語

f(x) = 2x



g(x::Float64) = 2.0x

のとき、

f(3.0)



g(3.0)

は完全に同じネイティブコードにコンパイルされてから実行されます。g(x::Float64)の「型指定」はこの場合には意味がないです。

こういう仕組みになっているので、「型指定」は多くの場合に時間の無駄になります。
#Julia言語

Juliaでは、初心者が素朴に書きそうな

f(x) = 2x

型のコードの書き方が合理的で

g(x::Float64)::Float64 = 2.0x

のような無駄な「型指定」入りにコードを書くのは不合理です。
#Julia言語 「型指定」がないと最適化されたコンパイルが不可能だという誤解が蔓延している。

Juliaのようなjust-in-timeでコンパイルする仕組みなら、実行時の引数の型の情報をコンパイラが利用できるので、函数の引数の「型指定」が無くても、コンパイラは引数の具体的な型の情報を利用できます。
#Julia言語 例えば抽象型Realで引数が型指定されている

function f(x::Real) ~ end



f(3.0)

の形で実行するときには、JuliaのコンパイラはRealという抽象型の情報を使ってコンパイルせずに、引数3.0の具体的な型であるFloat64という情報を使ってネイティブコードにコンパイルします。
#Julia言語 f(x::Real, y::Real, ...)のx,y,zとして可能なものすべてに対応できるように前もってコンパイルしておくのではなく(一般に非常に大変なことになる!)、具体的な値を持つx,y,zが与えられた後にそのx,y,zの具体的な型に応じて最適化されたコンパイルを実行する仕組みにJuliaはなっています。
#Julia言語 函数の引数の具体的な型の情報をJuliaのコンパイラは利用できる。

だから、函数の引数以外の変数に型が、函数の引数の方から自動的に決まってしまうようにコードを書けていれば、Juliaはよく最適化されたネイティブコードにコンパイルしてくれます。

これが基本中の基本!
#Julia言語 具体的な型が不明のモノが函数のコードに混じっていても、Juliaはその函数を実行してくれます。

速度だけが重要ではなく、速度はどうでもよい場合もあるので、そういう仕様はありがたいと思います。

具体的な型が不明になるモノも代表例がグローバル変数。速度が重要なら使っちゃダメ。
#Julia言語

具体的な型が不明になるモノの2つ目の代表例は中身の型が抽象型(型指定されていない場合を含む)のstructです。例えば

struct Foo
a::Array{Float64}
end

はアウト。

struct Foo
a::Array{Float64, 1}
end

もしくは

struct Foo
a::Vector{Float64}
end

とする必要あり。
#Julia言語

struct Foo
a::Vector{Float64}
end

だと、中身がVector{Float64}型で決め打ちされてしまい、Juliaの多くの便利なパッケージと組み合わせて使えなくなります。

Juliaでは基本的に「型指定」をすると損になることが多いです。
#Julia言語 Juliaでも「型指定」を無駄な制限が起こらないようにすることはできます。しかし、そのためにはJuliaの型システムの深い理解が必要になって、一般ライトユーザーには厳しい。

しかししかし、一般ライトユーザーが手抜きをしながら、計算効率を落とさない方法もあります。続く
#Julia言語 それは

struct Foo{A}
a::A
end

のように書くことです。

Foo(3)

とすると、3の型Int64が自動的に認識されて

Foo{Int64}(3)

が作られ、

Foo([1.0, 2.0])



Foo{Vector{Float64}}([1.0, 2.0])

になります。

Juliaのコンパイラにstructの中身の型が見えるようになる!
#Julia言語 structについても、コンストラクタの引数の具体的な型から型のパラメータが自動的に決まるように(型情報が伝搬するように)書けば、ユーザー側がVector{Float64}のような型名をコード中に書く必要がなくなります。

「型指定」がない部品の方が広く使えるので便利な場合が多いです。
#Julia言語 「前もってすべてをコンパイルしておくので、実行時の状況に合わせてコンパイルできない」という状況では「型指定」がどうしても必要になりますが、「実行直前にコンパイルする」という仕様ならば、「型指定」がなくて「型の伝搬」が保証されている方が便利です(structも含めて!)。
#Julia言語 初期化されていない配列も

A = Array{Float64, 3}(undef, l, m, n)

のように型名を書いて作る必要はなくて、

A = similar([0.0], l, m, n)

のように、Xと似ているサイズ(l, m,n)のモノのsimilar(X, l, m, n)を使える。引数xが配列と期待される函数内なら

A = similar(x)

も可。
#Julia言語 計算速度が気になる人は、公式ドキュメントの

docs.julialang.org/en/v1/manual/p…
Performance Tips

に忠実なコードを書くのが最も楽な道だと思います。

前もってすべてをコンパイルするという縛りに過剰適応してしまったスタイルを訂正するためにも上のTipsは役に立ちます。
#Julia言語

共通のパラメータをグローバル変数で与えたくなる気持ちは分かるのですが、やめた方は良いです。

問題を記述するパラメータを入れておくstructを

struct FooProblem{A,B,C}
a::A
b::B
c::C
end

のように定義しておいて、そこにパラメータを入れておいて~続く
#Julia言語 続き~、問題を解く函数を

using Parameters

function solve(foo::FooProblem, x, y)
@ unpack a, b, c = foo
~a,b,cを使って問題を解くコード~
end

のように書くというスタイルがJulia界では普及していると思います。
#Julia言語 Juliaでなくても、共通のパラメータをグローバル変数にしたりせずに、コードの形で

* 問題そのものの記述
* 問題を解く方法の記述

をきちんと行う方が、見易いコードになり、広く使い易える部品ができ上がるので好ましいと思います。
#Julia言語

docs.julialang.org/en/v1/manual/p…

でも in-place というキーワードで軽く説明されていますが、配列などを使う場合には無駄なメモリ割当をループ内ですると速度的な劣化が激しくなる。

それを防ぐ方法はあるのですが、経験がないと対処できない場合が結構あると思います。質問すると良いです。
#Julia言語

例えば、配列 v の時間発展を

for i in 1:n
v = update(v)
end

のように書くと、update(v)の結果の配列へのメモリ割当がループ内でn回起こります。

一回限りしか使わないコードならそれでも良いと思いますが、ライブラリとして使うコードだと速度劣化がつらいです。続く
#Julia言語

function update!(v)
配列vの中身の書き変え
end

function~

v = 初期条件
for i in 1:n
update!(v)
end

end

なら、ループ内でvの中身が書き変わるだけで、配列へのメモリ割当が繰り返されません。
#Julia言語 モンテカルロ法でループの内側で配列の乱数を発生させる場合にも同様の注意。

for i in 1:niters
a = rand(n)

end

と書くと、rand(n)の分のメモリ割当がniters回発生します。

処方箋↓

using Random

a = similar([0.0], n)
for i in 1:niters
rand!(a)

end
#Julia言語

基本原則: !付きの函数を利用できるなら、そうした方がメモリ効率が良くなる。

しかし、どういう!付きの函数があるかはすぐにはわからないと思う。現時点では、知っていそうな人に聞いた方が早い場合が少なくないと思う。
#Julia言語 あと

sum([f(x) for x in X])

は [f(x) for x in X] の配列の分だけメモリ割当が起こるのでやめた方がよいです。

sum(f(x) for x in X)

もしくは

sum(f, X)

と書くべき。

メモリ割当が発生する書き方とそうでない書き方があって、理解が伴っていれば暗記は必要なくなる。
#Julia言語

sum(f, X) 型の函数は沢山ある。

mean(f, X)
maximum(f, X)
minimum(f, X)
...

count(f, X)
all(f, X)
any(f, X)
...
#Julia言語

計算速度を気にするときにやってはいけないのは

a = []

よく見るのが、a = [] としてから、push!やappend!で内容を追加しようとする行為。計算速度が重要な場合には良くないです。

a は要素がAny型の空の配列になり、aを使うごとに計算速度の劣化が引き起こされます。
#Julia言語

作りたい配列のサイズ n が前もって分かっているなら、

a = similar([0.0], n)
for i in 1:n
a[i] = f(i)
end

とか

a = f.(1:n)

のようにした方がお得。

一挙に必要サイズ分の配列を作った方が速い。
#Julia言語

最終サイズが不明の場合は

a = similr([0.0], 0)
for i in 1:n
if c(i)
push!(a, f(i))
end
end

とか

a = [f(i) for i in 1:n if c(i)]

とか。

速度が重要なら

* 最終サイズが既知の場合には一挙に配列を確保。
* 未知の場合はpush!を使う。
* a=[]は避ける。
#Julia言語 速度が重要でない場合には、細かいことを気にする必要はない。効率の悪いコードを書いてもJuliaはきちんと仕事をしてくれます。←これ、実は結構ありがたい。
#Julia言語 インタラクティブにJuliaを使っていて、Vector{Any}やVector{Real}型の配列を間違って作ってしまっても、内容の具体的な型が全部同じなら、

a = Vector{typeof(a[begin])}(a)

のようにして変換してから、aを使えば速度の劣化を防げます。
#Julia言語

a = Real[1, 2, 3.0, 4.0]

4-element Vector{Real}:
1
2
3.0
4.0

a = Vector{typeof(a[begin])}(a)

4-element Vector{Int64}:
1
2
3
4
#Julia言語 Julia側からstructの中身の型がどう見えているかは、fieldtypes(typeof(foo)) のようにすれば分かります。AnyやRealのような抽象型があると速度的劣化が生じる場合が出て来る。

Chain.jl 結構便利。

|> という記号法にこだわるのをやめた方がスッキリする場合がある。

• • •

Missing some Tweet in this thread? You can try to force a refresh
 

Keep Current with 黒木玄 Gen Kuroki

黒木玄 Gen Kuroki Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

More from @genkuroki

12 Jan
#数楽 そうなんです!ベータ函数や超幾何函数達は非常に面白い!高校で微積分を習っていればめっちゃ楽しめる。

B(p,q)=∫_0^1 x^{p-1}(1-x)^{q-1}dx

がよく使われるが、

B(p,q)=∫_0^∞ t^{p-1}/(1+t)^{p+q} dy

およびさらにt=u^{1/p}やt=u^2とおいた場合も応用上重要な点は盲点になり易い。 Image
#数楽

B(p,q)=∫_0^∞ t^{p-1}/(1+t)^{p+q} dy

型のベータ函数の表示で t を t²/ν で置き換えて、p=1/2, q=ν/2 とおけば、本質的に自由度 ν のt分布が得られます。

t分布は非常に基本的な確率分布なのですが、ベータ分布の特別な場合(p=1/2)の変種と思えます。F分布はp=1/2の特殊化をやめた場合。 Image
#数楽

B(p,q)=∫_0^∞ t^{p-1}/(1+t)^{p+q} dy

型のベータ函数の表示を知っていれば

Γ(p)Γ(q)=Γ(p+q)B(p,q)

を y = tx (yを直線の傾きtに変数変換)の形の積分変数変換で示せます。その計算の過程も面白いので知っておいて損がないです。

大学新入生向けの計算練習の題材としてもよい。 Image
Read 19 tweets
11 Jan
#Julia言語

Juliaでは

foo(f::函数, X::配列など)

の形式で、配列Xなど(generatorやiteratorを含む)のすべての要素に函数fを施した結果に "foo" の操作を施せる場合が多数あります。

例えばXの要素の絶対値の最大値と和はそれぞれ

maximum(abs, X)
sum(abs, X)

二乗和は

sum(x->x^2, X)
#Julia言語 二乗和は

X = randn(10^6)
sum(Base.Fix2(^, 2), X)
sum(abs2, X)

のようにも書ける。Fix2(f, a)は本質的に x->f(x, a) です。読み易さは x->f(x,a) の方が上のことが多い。

1.96より大きい要素の割合は

count(>(1.96), X)/length(X)

using Statistics
mean(>(1.96), X)
#Julia言語

Xのすべての要素に手続き f を施すには

foreach(f, X)

返り値はforループと同じnothingになります。

Xの要素に函数 f を作用させた結果を集めたものは f.(X) だけではなく

map(f, X)

で作れる。

これら以外にもmaximum, minimum, count, sum, mean, …も似た使用法が可能。
Read 15 tweets
10 Jan
#統計

amazon.co.jp/dp/4130413007
竹内啓・竹村彰通編
数理統計学の理論と応用
1994
第5章 竹内啓 統計的推測理論の諸問題

に最尤法が漸近的に全然最良でないシンプルな例が載っていたので(添付画像)、#Julia言語 で数値的に確認してみた↓

nbviewer.jupyter.org/gist/genkuroki…

ベイズ統計に繋がる話題。続く ImageImage
#統計 その節のタイトルは「5 非正則な場合の漸近推定論」で、尤度函数が漸近的にフラットになる場合(一様事前分布の事後分布が漸近的に一様分布になる場合)のシンプルな例を作っているので、渡辺澄雄『ベイズ統計の理論と方法』の読者は特に興味を持つと思ったので、紹介することにしました。
#統計 データを生成する真の分布は添付画像の密度函数を持つtruncated normal distributionです。標準正規分布を |x|>1 なら確率密度が0になるように改変したもの。

モデルはこの分布を並行移動したものです。パラメータは平均μのみ。

分布の台がパラメータμごとに違うモデルになっている。 Image
Read 26 tweets
4 Jan
#統計 統計学の哲学者のMayoさんによれば

* Royallの3つの問いに、びっくりするほど多くの(しかも異なる信念を持つ)統計家達・哲学者達が賛同している。(大変なことになっている、とてもひどいことになっているというニュアンス)

続く

errorstatistics.com/2014/10/10/bre…
#統計 Mayoさん曰く

* Royallの3つの問いの「戒律」によれば

①何を信じるか→ベイジアンの事後分布

②どう行動するか→Neyman-Pearsonの方法による長期的なパフォーマンスの良さ

③証拠の比較→尤度比較法

となるが、あなたはこれら全部を拒否したいかもしれません。私が拒否しているように。
#統計 日本語圏でも同様にRoyallの3つの問いの「戒律」という合理的であろうとすれば到底受け入れることができない考え方を受け入れている人達を容易に発見できます。

ソーバー著『科学と証拠―統計の哲学入門―』とその翻訳者の松王政浩さんが大変な悪影響を与えているように見える。
Read 11 tweets
3 Jan
#Julia言語 個人的にJuliaはPythonにあまり似ていないと思うのですが、Pythonから来たっぽい人のJuliaのコードで気になるのは、

A = []
for ~

push!(A, a)

end

におけるA = []という書き方(Anyの配列になって効率悪化!)と、

sum([f(x) for x in X])

のような書き方。続く
#Julia言語

sum([f(x) for x in X]) だと、すべてのXの要素についてf(x)を計算した結果を配列として保存してから、その配列の要素の和を計算してしまいます。無駄にメモリを消費する。

sum(f(x) for x in X) # generatorを使う

もしくは

sum(f, X)

と書いた方が得だし、コードも短くなります。
#Julia言語

A = []
for i in 1N
push!(A, randn())
end

とするとAnyの配列Aができてしまう。

Anyの配列に格納されたサンプルによるモンテカルロ積分はFloat64の配列の場合と比較すると180倍くらい遅くなった!

A = [] → push!(A, ~) の繰り返しは要注意。

gist.github.com/genkuroki/7cd0…
Read 4 tweets
3 Jan
他にも似たような反応がありますが、小5の算数の教科書にある割合の3つの公式を覚えさせたり、「もとにする量」「比べられる量」という割合の概念を理解するために不要な用語を使うことを児童に強制していたら、教わっている子の将来が暗くなると思います。

教科書通りの教え方がまずい証拠がある。
お勧めしたいのは以下の2冊の本です。
実際に子供に算数を教えていたら楽しんで読めると思います。

amazon.co.jp/dp/4101370311
お母さんは勉強を教えないで
見尾 三保子

このタイトルはひどいし、内容とも一致していません。

内容の大部分は算数や数学の真っ当な教え方の話です。

2冊目紹介に続く
2冊目

amazon.co.jp/dp/4788508435
学力低下をどう克服するか―子どもの目線から考える
2003/3/25
吉田 甫

タイトルを見ても分からないことですが、教科書通りの教え方の対照群と実験的な教え方を、実際に授業をしてみて比較してみた研究結果が書いてあります。分数と割合の教え方を詳しく扱っています。
Read 8 tweets

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!

Follow Us on Twitter!