mike-neckのブログ

Java or Groovy or Swift or Golang

Kotlin用のjunit-quickcheck風なテスティングフレームワークを作った

表記のとおりです。

テストデータをランダムに自動生成して複数回実行させるというライブラリーです。調べてみたところ、Kotlin用のものがないようなので、作ってみました。名前はKuickCheckという名前にしていますが、今後どうなるかわかりません。あと、僕は残念ながらProperty based testingに詳しくありませんし、QuickCheckとかScalaCheckとかScalaPropのこともよく知りません(不勉強)。

github.com

入手方法

Mavenにあがっています。

<dependency>
    <groupId>org.mikeneck</groupId>
    <artifactId>kuickcheck-core</artifactId>
    <version>0.1</version>
    <scope>test</scope>
</dependency>
  • Gradle
dependency {
  testCompile 'org.mikeneck:kuickcheck-core:0.1'
}

依存ライブラリーはkotlin-runtimeとkotlin-reflectだけです。

JUnitにまったく依存していないので、Junitとともに走らせることはできません。

テストの書き方

テストの書き方はシンプルです。

  1. クラスかオブジェクトを作成します。
  2. forAllメソッドにチェックしたいデータを指定します(下のサンプルでは正のintであるpositiveIntを指定しています)。
  3. チェックする性質をsatisfy{}ブロックに記述します。
  4. 1-3によって返されるオブジェクトをなんらかのメンバーに指定します。このメンバーにはテストする性質がわかるような名前をつけます。
  5. 4.に対して@Propertyアノテーションを付与します。

これにより、ランダムな値でデフォルトで100回のテストを実行します。

object GettingStarted {

  @Property
  val positiveNumberIsMoreThan0 =
      forAll(positiveInt).satisfy{ it > 0 }

  @Property
  val `positiveInt x negativeInt becomes negative` =
      forAll(positiveInt, negativeInt).satisfy {l,r -> l * r < 0}
}

テストの実行

テストの実行は今のところjava -jarによる実行方法しかありません。頑張って作成したテストのjar、kotlin-runtime.jar、kotlin-reflect.jar、kuickcheck.jarを集めて、org.mikeneck.kuickcheck.KuickCheckをメインに指定して実行します。Gradleを使っている場合はJavaExecタスクで実行するのがよいでしょう。

f:id:mike_neck:20160613164251p:plain

実行すると、このような形で成功したテストが緑、失敗したデータが有る場合は赤、例外が発生した場合は黄で表示されます。

Javaとのinterop

試していないのでわかりません(´・ω・`)

intとかlongとかJavaのプリミティブ型と同じ名前のAPIを準備しているのでコンパイルできないのではないかと思います。

この後

一応、今のところできているのは

  • パッケージをスキャンしてテストを抽出
  • intlongBigIntegerなどの基本的な型の値を自動で生成してテストに流す
  • テスト結果をコンソールに表示

です。

できていないところは

  • テスト結果をファイルに吐き出す
  • Ktで終わるソースを検知できない(Ktファイルを除外するために末尾がKtのものを除外している)
  • JUnitに依存していないので、JUnitとともに走らせることができない

などなどです。

Kotlinのコンパイラーが吐き出すクラスまとめ

リフレクションをいろいろいじっていて気になったので中途半端にまとめてみた

Kotlin クラスのメモ

Kotlinをコンパイルすると生成されるクラス

  • クラス/インターフェース/enum/オブジェクト1つにつき1つのクラス
  • パッケージに所属する関数/プロパティ -> Ktクラス
  • インターフェースに記述した実装 -> $DefaultImplsクラス
  • 関数クラスやobjectワードによって生成される実装クラス -> $1など数字で終わるクラス
  • enumのメンバーに実装する -> EnumClass$Memberクラス
  • when式でenumによる条件マッピング -> $WhenMappingsクラス
  • コンパニオンオブジェクト -> $Companionクラス

例: compile-sample.kt

fun hello(name: String) = "Hello, $name"
class Foo {
  val upper: (String) -> String = String::toUpperCase
  companion object {
    fun printable(c: Color): Boolean =
        when(c) {
          BLACK -> true
          WHITE -> false
          BLUE  -> true
        }
  }
}
enum class Color {
  BLACK { override fun asString() = "black" },
  WHITE { override fun asString() = "white" },
  BLUE  { override fun asString() = "blue"  };
  abstract fun asString(): String
}
interface Bar {
  val name: String
  fun plus(x: Int, y: Int): Int = x + y
}

上記のコードをコンパイルした場合は次のクラスファイルが生成される

  • Bar$DefaultImpls.class
  • Bar.class
  • Color$BLACK.class
  • Color$BLUE.class
  • Color$WHITE.class
  • Color.class
  • Compile_sampleKt.class
  • Foo$Companion$WhenMappings.class
  • Foo$Companion.class
  • Foo$upper$1.class
  • Foo.class

なお、これらのファイルをjavapすると次のようになる。

Bar$DefaultImpls.class

Compiled from "compile-sample.kt"
public final class Bar$DefaultImpls {
  public static int plus(Bar, int, int);
}

Bar.class

Compiled from "compile-sample.kt"
public interface Bar {
  public abstract java.lang.String getName();
  public abstract int plus(int, int);
}

Color$BLACK.class

Compiled from "compile-sample.kt"
public final class Color$BLACK extends Color {
  public java.lang.String asString();
  Color$BLACK();
}

Color.class

Compiled from "compile-sample.kt"
public abstract class Color extends java.lang.Enum<Color> {
  public static final Color BLACK;
  public static final Color WHITE;
  public static final Color BLUE;
  static {};
  public abstract java.lang.String asString();
  protected Color();
  public static Color[] values();
  public static Color valueOf(java.lang.String);
}

Compile_sampleKt.class

Compiled from "compile-sample.kt"
public final class Compile_sampleKt {
  public static final java.lang.String hello(java.lang.String);
}

Foo$Companion$WhenMappings.class

Compiled from "compile-sample.kt"
public final class Foo$Companion$WhenMappings {
  public static final int[] $EnumSwitchMapping$0;
  static {};
}

Foo$Companion.class

Compiled from "compile-sample.kt"
public final class Foo$Companion {
  public final boolean printable(Color);
  public Foo$Companion(kotlin.jvm.internal.DefaultConstructorMarker);
}

Foo$upper$1.class

Compiled from "compile-sample.kt"
final class Foo$upper$1 extends kotlin.jvm.internal.FunctionReference implements kotlin.jvm.functions.Function1<java.lang.String, java.lang.String> {
  public static final Foo$upper$1 INSTANCE;
  public java.lang.Object invoke(java.lang.Object);
  public final java.lang.String invoke(java.lang.String);
  public final kotlin.reflect.KDeclarationContainer getOwner();
  public final java.lang.String getName();
  public final java.lang.String getSignature();
  Foo$upper$1();
  static {};
}

Foo.class

Compiled from "compile-sample.kt"
public final class Foo {
  public static final Foo$Companion Companion;
  public final kotlin.jvm.functions.Function1<java.lang.String, java.lang.String> getUpper();
  public Foo();
  static {};
}

特にオチはない(おわり)

OpenCV/Java 環境構築メモ

JavaOpenCVをごにょごにょやるのに、少し手間取ったのでメモ。

インストールする環境などは次のとおり。

OpenCVのインストール

(1) pythonbrewでインストール

$ brew install python
$ brew install python3

(2) numpyをインストールするためにgccをインストール

$ brew install gcc

numpyがgfortrunに依存していて、gfortrunがgccに吸収されているので、インストールされる。

なお、makeに時間がかかる(1時間くらい)

(3) numpyをインストール

$ brew install numpy --with-python3

(4) opencv3をインストール

$ brew install opencv3 --with-python3 --with-java

Javaでいじる場合には--with-javaをいれておく

Pythonでインストールの確認をするので、次のコマンドをうっておく

$ brew link opencv3 --force

インストール後にpython3を起動してimport cv2をおこなって、エラーが出なければインストール成功

Javaプロジェクト

(1) libopencv_java310.so のファイル名を変更

github.com

OpenCVに同梱されているネイティブライブラリーのlibopencv_java310の拡張子がsoになっているので、dyLibに変更する

$ cd /usr/local/Cellar/opencv3/3.1.0_3/share/OpenCV/java
$ sudo mv libopencv_java310.so libopencv_java310.dyLib

(2) opencv_310.jarからjavaファイルのみを取り出してサブプロジェクトにする

OpenCVに同梱されているopencv_310.jarにはいっているclassファイルがJava9eaでコンパイルされていて、IntelliJ IDEAでクラスファイルを認識できない。そこでopencv_310.jarからjavaファイルを取り出してサブプロジェクトを作り、メインのほうから依存するようにプロジェクトを構成する。

settings.gradle

include 'opencv'

build.gradle

dependencies {
    // サブプロジェクトに依存
    compile project(':opencv')
}

// jarからjavaファイルを取り出す
task copySource(type: Copy) {
    def jarFile = file('/usr/local/Cellar/opencv3/3.1.0_3/share/OpenCV/java/opencv-310.jar')
    from zipTree(jarFile)
    exclude '**/*.class'
    exclude 'META-INF/*'
}

// サブプロジェクトの設定
project(':opencv') {
    apply plugin: 'java'
}

(3) プログラム実行時の起動オプション

ネイティブライブラリーのパスをjava.library.pathに設定して起動する

-Djava.library.path=/usr/local/Cellar/opencv3/3.1.0_3/share/OpenCV/java

System.setPropertyでやってみたが、その場合にはネイティブライブラリーをうまく探しだしてくれなかった

おわり

Javaの機械学習ライブラリーでFizzBuzzしてみた

FizzBuzzをするのにTensorFlowというネタツイートがあったので、「機械学習 FizzBuzz」で検索したらすでにいくつかあったので、真似してみたくなった。ちなみに機械学習のことは全然わかってません。

qiita.com

blog.amedama.jp

github.com


ライブラリー/フレームワーク

EncogというJava機械学習ライブラリーを使いました(TensorFlow使いたかった…)。

EncogはJeff Heatonという方が開発している機械学習ライブラリーで、サポートベクターマシンニューラルネットワークベイジアンネットワークなどいくつかのアルゴリズムをサポートしているJava/C#フレームワークだそうです。ドキュメントもしっかりしているので、初心者でも使いやすいライブラリーとなっています。

github.com


導入

dependencies {
  compile 'org.encog:encog-core:3.3.0'
}

学習データ

201〜1000を学習データとして用いました。

特徴量は「3で割り切れた場合1.0、割り切れなかった場合0.0」「5で割り切れた場合1.0、割り切れなかった場合0.0」という二つの量を設定しました。

//入力の数値
data class InputNumber(
        val num: Int, val fizz: Boolean, val buzz: Boolean)

//Intから入力の数値に変換する
val toInput: (Int) -> InputNumber = {
    val fizz = it % 3 == 0
    val buzz = it % 5 == 0
    InputNumber(it, fizz, buzz)
}

Qiitaの記事のSVMFizzBuzzと同じように、FizzBuzzのフィルター、Buzzのフィルター、Fizzのフィルターを用意し、入力された数値がそれぞれのフィルターに適合する場合は1.0、適合しない場合0.0となるような教師データを用意します。たとえばFizz用のフィルターには次のような学習データを与えてトレーニングします。

数値 特徴量 出力
201 [1.0, 0.0] 1.0
202 [0.0, 0.0] 0.0
203 [0.0, 0.0] 0.0
204 [1.0, 0.0] 1.0
205 [0.0, 1.0] 0.0
//Fizz/Buzzをあらわす
enum class FizzBuzz {
    FIZZ,
    BUZZ,
    FIZZ_BUZZ,
    NONE
}

//入力値のFizzBuzz結果
fun InputNumber.toFizzBuzz(): FizzBuzz {
    return when(Pair(fizz, buzz)) {
        Pair(true, true)  -> FizzBuzz.FIZZ_BUZZ
        Pair(false, true) -> FizzBuzz.BUZZ
        Pair(true, false) -> FizzBuzz.FIZZ
        else              -> FizzBuzz.NONE
    }
}

//FizzBuzzの教師データ変換
fun FizzBuzz.ideal(fizzBuzz: FizzBuzz): DoubleArray =
        if (this == fizzBuzz) doubleArrayOf(1.0)
        else doubleArrayOf(0.0)

//学習データ
data class FizzBuzzStudy(
        val input: InputNumber,
        val fizzBuzz: FizzBuzz)

//学習データの入力値を取り出す
fun FizzBuzzStudy.inputArray(): DoubleArray =
        booleanArrayOf(input.fizz, input.buzz).toDoubleArray()

//学習データの教師データを取り出す
fun FizzBuzzStudy.idealDataArray(): DoubleArray =
        input.toFizzBuzz().ideal(fizzBuzz)

学習の実行

今回はBasicNetworkというネットワークを使います。これはEncogのQuick Start GuideにあるXORの学習で使われているものです。

学習エラーが0.01以下になるまで繰り返し学習を行います。

//dataは201〜1000までの学習データを保持するオブジェクト
//fizzBuzzはフィルターをあらわすFizzBuzz(FizzBuzz専用フィルターかFizz専用フィルターか)
class Filter(val data: MLDataSet, val fizzBuzz: FizzBuzz) {

    val network = BasicNetwork()

    init {
        network.addLayer(BasicLayer(null, true, 2))
        network.addLayer(BasicLayer(ActivationSigmoid(), true, 4))
        network.addLayer(BasicLayer(ActivationSigmoid(), false, 1))
        network.structure.finalizeStructure()
        network.reset()

        val training = ResilientPropagation(network, data)
        for (t in 1..30) {
            training.iteration()
            if (training.error < 0.01) break
        }
        training.finishTraining()
    }

    //etc...
}

フィルター

学習が終わると、Filternetworkというオブジェクトはcomputeというメソッドで、入力値(特徴量)が適合する場合に1に近い数値を返すようになります。

これをFizzBuzzの場合、Buzzの場合、Fizzの場合の3回おこなって、FizzBuzzの判断をおこないます。

class Filter(val data: MLDataSet, val fizzBuzz: FizzBuzz) {
    //一部省略

    //入力値から値を予測する
    private fun compute(input: MLData): MLData = network.compute(input)

    //入力値が適合するかチェックする
    fun match(input: InputNumber): Boolean =
        compute(BasicMLData(input.toDataArray())).getData(0).round() == 1
}

//数値をFizzBuzzに変換する
fun InputNumber.fizzBuzz(filters: List<Filter>): String {
    val found = filters.find { it.match(this) }
    return when(found?.fizzBuzz) {
        FizzBuzz.FIZZ_BUZZ -> FizzBuzz.FIZZ_BUZZ.name
        FizzBuzz.BUZZ      -> FizzBuzz.BUZZ.name
        FizzBuzz.FIZZ      -> FizzBuzz.FIZZ.name
        else               -> "$num"
    }
}

プログラム実行

学習データは201〜1000、テストするデータは1〜40として機械学習によるFizzBuzzを実行してみます。

object StudyRange {
    val STUDY_START = 201
    val STUDY_END = 1000
}

fun main(args: Array<String>) {
    val inputList: List<InputNumber> =
            StudyRange.STUDY_START.rangeTo(StudyRange.STUDY_END).map(toInput)
    val studyDataList: List<StudyData> = fizzBuzzList().map { StudyData(inputList, it) }
    val filters: List<Filter> = studyDataList.map { Filter(it.dataSet(), it.fizzBuzz) }

    val testData: List<InputNumber> = (1..40).map(toInput)
    testData.map {
        "${it.num} -> ${it.fizzBuzz(filters)} (correct: ${it.correct()})"
    }.forEach{
        println(it)
    }
    Encog.getInstance().shutdown()
}

実行結果

1 -> 1 (correct: 1)
2 -> 2 (correct: 2)
3 -> FIZZ (correct: FIZZ)
4 -> 4 (correct: 4)
5 -> BUZZ (correct: BUZZ)
6 -> FIZZ (correct: FIZZ)
7 -> 7 (correct: 7)
8 -> 8 (correct: 8)
9 -> FIZZ (correct: FIZZ)
10 -> BUZZ (correct: BUZZ)
11 -> 11 (correct: 11)
12 -> FIZZ (correct: FIZZ)
13 -> 13 (correct: 13)
14 -> 14 (correct: 14)
15 -> FIZZ_BUZZ (correct: FIZZ_BUZZ)
16 -> 16 (correct: 16)
17 -> 17 (correct: 17)
18 -> FIZZ (correct: FIZZ)
19 -> 19 (correct: 19)
20 -> BUZZ (correct: BUZZ)
21 -> FIZZ (correct: FIZZ)
22 -> 22 (correct: 22)
23 -> 23 (correct: 23)
24 -> FIZZ (correct: FIZZ)
25 -> BUZZ (correct: BUZZ)
26 -> 26 (correct: 26)
27 -> FIZZ (correct: FIZZ)
28 -> 28 (correct: 28)
29 -> 29 (correct: 29)
30 -> FIZZ_BUZZ (correct: FIZZ_BUZZ)
31 -> 31 (correct: 31)
32 -> 32 (correct: 32)
33 -> FIZZ (correct: FIZZ)
34 -> 34 (correct: 34)
35 -> BUZZ (correct: BUZZ)
36 -> FIZZ (correct: FIZZ)
37 -> 37 (correct: 37)
38 -> 38 (correct: 38)
39 -> FIZZ (correct: FIZZ)
40 -> BUZZ (correct: BUZZ)

どうやらうまくいったようです。

全体のコードはgistにあります。

機械学習でFizzBuzz · GitHub