なぜKotlin?
普段は数値計算を含むプログラムはNumPyで書くことが多いんですが、Pythonは動的言語なのでコード補完があまり効かないことと、変数の型が後からわかりにくくなるという欠点があります。
そのため、コード補完に頼りたいときや複雑な型を扱うときはC#を使っています。KotlinのほうがC#よりもさらに使いやすそうだと思ったので、KotlinでNumPyのような数値計算をやってみることにしました。
Kotlinには将来的に伸びていきそうな理由もいくつかあります。
- 開発したのはIntelliJ IDEAなどのIDEで有名なJetBrains
- Javaとほぼ完全な互換性がある
- ネイティブバイナリへのコンパイルにも対応
- Androidの公式言語として採用された
Kotlinの特徴
Kotlinは静的言語でありながらPythonなどのスクリプト言語のようにかなり簡潔に記述できるのが特徴です。メジャーな言語の中では最後発に近いだけに、他の言語のいいとこ取りをしたような形になっています。
数値計算をする上で特に強力だと思う機能が拡張関数と演算子のオーバーロードです。C#にも拡張メソッドという同じようなものもあるし演算子のオーバーロードもありますが、使い勝手が決定的に違います。
1つ目の大きな違いは拡張関数や演算子のオーバーロードが簡潔に書けること。簡単なものなら1行で書くことができます。
2つ目は関数の中でローカルに定義できること。つまり、拡張関数や演算子を使いたい場所のすぐ上で定義することができるということです。これによって、プログラムの他の部分への影響なく安全に利用することができます。
例えば、次のような2次元の点とベクトルを表すデータクラスPointとVectorがあった場合、
data class Point(val x: Double, val y: Double)
data class Vector(val x: Double, val y: Double)
下のように関数内で演算子のオーバーロードを定義して、PointからPointを引いてVectorを求めるという計算を行うことができます。
fun main() {
val p1 = Point(1.0, 2.0)
val p2 = Point(2.0, 3.0)
// 関数内で演算子のオーバーロードを定義
operator fun Point.minus(other: Point) = Vector(x - other.x, y - other.y)
// 次の行ですぐに演算子を使用
val vec = p2 - p1
}
0. 開発環境
開発に必要なツールはインストールされているところから始めます。
- JDK 8
- IntelliJ IDEA 2018.3
1. プロジェクトの作成
1-1. プロジェクトのタイプ選択
ND4Jを使うためにはMavenを使ってビルドする必要があるようです。
そのためプロジェクトのタイプはKotlinではなくMavenを選択します。
そしてCreate from archetypeにチェックを入れると、下にkotlin-archetype-jvmという選択肢が出てくるのでそれを選択します。archetypeというのはテンプレートみたいな意味だと思います。
1-2. GroupIdとArtifactIdの入力
次はGroupIdとArtifactIdを入力します。
GroupIdは組織を表す識別子だと思います。なんでもいいので、ここではgroupと入力しています。
ArtifactIdはプロジェクト名みたいなものだと思いますがよくわかっていません。
あとで出てくるProject nameにはここで入力した名前が入るようになっています。
1-3. Mavenの選択
Maven 2とMaven 3が選べますが、デフォルトのMaven 3を選択しておきます。
1-4. プロジェクト名の入力
Project nameには前に入力したArtifactIdと同じ名前が入っているので、そのままFinishを選択します。
2. 依存ライブラリの追加
初めてのときはここからが大変だったんですが、pom.xmlというファイルの中に依存ライブラリを記述する必要があります。
pom.xmlは自動的に作成されるので、それを開いてdependenciesの中に、必要なライブラリを追記します。ライブラリはMaven Repositoryで探します。
本当に必要なライブラリが何かはよくわかっていませんが、エラーを潰していった結果以下のようになりました。
<dependencies>
...(他の依存ライブラリ)
<dependency>
<groupId>org.nd4j</groupId>
<artifactId>nd4j-api</artifactId>
<version>1.0.0-beta3</version>
</dependency>
<dependency>
<groupId>org.nd4j</groupId>
<artifactId>nd4j-native-platform</artifactId>
<version>1.0.0-beta3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
3. ビルドテスト
ND4Jを使った簡単なプログラムを書いて、ビルドができることをテストします。
package group
import org.nd4j.linalg.factory.Nd4j
fun main() {
val random_matrix = Nd4j.randn(3, 3)
println(random_matrix)
}
[[ 0.4890, 1.0201, 1.1111],
[ -0.4645, 0.6945, -0.8578],
[ -0.8721, 0.2166, -0.7046]]
ND4Jを使ってみた感想
関数名はNumPyに合わせてあるみたいなので覚えやすいです。
ただ、NumPyのndarrayに相当すると思われるINDArrayを使った計算には+
や*
などの演算子が使えないのが残念です。ND4JはJavaのライブラリなので演算子のオーバーロードができないJavaの仕様上しかたないですね。
演算子を使いたい場合は演算子のオーバーロードの定義をスニペットとして登録しておいて使うというのがいいんじゃないかと思います。
operator fun INDArray.plus(other: Number) = this.add(other)
operator fun INDArray.minus(other: Number) = this.sub(other)
operator fun INDArray.times(other: Number) = this.mul(other)
operator fun INDArray.div(other: Number) = this.div(other)