hisurga.net

個人開発ログ

Flutter アプリに内蔵されている画像を表示する

アプリ内に登録した画像(assetsに登録した画像)を表示する方法です。

画像の表示

Flutterで画像を表示するにはいくつかの方法がありますが、
今回はアプリ内にあらかじめ画像を保管した画像を表示する方法になります。

使う画像は例によっていらすとやです。

f:id:hisurga:20190616231036p:plain:w200

1. 画像を保存するフォルダの作成

Flutterプロジェクト直下にフォルダを作成し、その中へ画像を保存します。

今回はファイル名をimagesとしましたが、他のフォルダ名にしても大丈夫です。

f:id:hisurga:20190616232807p:plain

Android Studio上では思った通りにフォルダを作成できなかったので、私はターミナル上で作成しました。

2. pubspec.yamlの編集

pubspec.yamlのflutter:に画像を追加します。

flutter:
  uses-material-design: true

  assets:
    - images/tv_talk.png

下記のように書くことで、フォルダごと登録することもできます。

flutter:
  uses-material-design: true

  assets:
    - images/

編集後はIDE右上に出てくるpackages getで反映させることを忘れないようにしてください。

3. 画像の表示

画像はImage.assetで画像を表示できます。

Image.asset('images/tv_talk.png'),

f:id:hisurga:20190616225641p:plain:w300

コード全体

シンプルな画像を表示するだけのアプリです。

import 'package:flutter/material.dart';

class MyImagePage extends StatefulWidget {
  MyImagePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyImagePageState createState() => _MyImagePageState();
}

class _MyImagePageState extends State<MyImagePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Image.asset('images/tv_talk.png'),
      ),
    );
  }
}

プロジェクトの全体像はGitHubに載せています。

github.com

公式リファレンス

Image class - widgets library - Dart API

Flutter/Dart サンプルコードのコンストラクタでは何をやっているのか

Flutterの勉強を始めると下記のようなコードをよく見ると思います。
プロジェクトを新規作成した時のサンプルコードも同じ書き方ですね。

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

その中のここのコードについて

MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

なんとなく「StatefulWidget / StatelessWidgetのサブクラスを作成する時のコンストラクタなんだな」まではわかると思いますが、Dartに慣れていないと何をやっているかわかりませんでした。

Dartの記法

まずはDartの書き方について調べてみます。

生成的コンストラクタ

一般的なものです。newで生成する時の話です。

もちろん引数を指定することができます。

class Foo {
  final String title;
  Foo(title) {
  this.title = title
  }
}
var foo = new Foo('piyo');

Automatic field initialization

コンストラクタでthis.フィールド名とすれば、代入処理を書かなくても自動で代入してくれます。

下記の例では、titleの初期化を記述しなくても自動で代入しています。

class Foo {
  final String title;
  Foo(this.title);
}
var foo = new Foo('piyo')

名前付き引数

関数を呼ぶときは{}で囲むことにより、名前付きで指定することができます。コンストラクタでも同じです。

void foo({String title, String subtitle}) {
}
foo(title: 'titleA', subtitle: 'subtitleB');

なお、名前付き引数は呼び出し時に必須ではありません。

void foo({String title, String subtitle}) {
}
foo(title: 'titleA'); // subtitleは省略可能

Redirecting Constructors

コンストラクタの後に:をつけて他のコンストラクタを指定することでリダイレクトすることができます。

class Foo {
  Foo() : this.test();
  Foo.test() {
    print('test');
  }
}

Flutterのコンストラクタに当てはめる

上記を元に、Flutterのサンプルアプリを確認しましょう。

// 省略
MyHomePage(title: 'Flutter Demo Home Page')
// 省略

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, String title}) {
    this.title = title;
  }
  String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

引数は名前付きで指定されています。
Automatic field initializationも利用しています。

また、:super()でsuperクラスのコンストラクタへリダイレクトしています。

Keyは指定していませんが、「名前付き引数」では「全ての引数を指定すること」が必須でありません。

MyHomePage({Key key, this.title}) : super(key: key);
final String title;

super()を無視するならば、こんな感じに書き換えることもできます。
しかしこの方法ではfinalな値を書き換えることはできませんので注意してください。

class MyHomePage extends StatefulWidget {
  MyHomePage(title) {
    this.title = title;
  }
  String title;

What's Key?

ここで気になるのが「Keyとは何か」です。

一番わかりやすいのが下記の動画です。

www.youtube.com

使い所は多くはありませんが、、
例えばTODOリストでは、優先順位を表すためにタイルウィジェットに対して順序変更、削除、追加などを行います。
その際、古いステートを取得することを防ぐために便利です。

Keyの説明は本記事の主題と少し異なるため、いずれ別記事として書くかもしれません。

今回の主題である「サンプルアプリのコンストラクタでは何をやっているか」ですが、
サンプルアプリ上ではKeyは指定していないので(名前付き引数は省略可能)、
結果として特に何もしていないことがわかります。

参考にさせていただいたサイト

Dartのコンストラクタについて | DevelopersIO

flutter MyhomePage({Key key, this.title}) : super(key: key); pls any one explain clearly with example flutter - Stack Overflow

When to Use Keys - Flutter Widgets 101 Ep. 4 - YouTube

Dartでfinalなメンバ変数を初期化する方法のまとめ

Flutter ListTileでIconをタップした時だけイベントを発生させる

codelabsの「Write Your First Flutter App, part 2」ではタップをListTile全体で取得していますが、

codelabs.developers.google.com

Tile全体のタップではなく、Iconのタップだけを認識するようにします。

f:id:hisurga:20190603005123j:plain:w200

結論

IconButtonを使う。

問題

codelabsのサンプルアプリでは、タップイベントをonTapで取得しています。

onTapはListTileへ結びついているため、Tileのどこをタップしてもハートの色が変化します。

これだとTile内でタップイベントを分けたい場合は困ります。

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  return ListTile(
    title: Text(
      pair.asPascalCase,
      style: _biggerFont,
    ),
    trailing: Icon(
      alreadySaved ? Icons.favorite : Icons.favorite_border,
      color: alreadySaved ? Colors.red : null,
    ),
    onTap: () {
      setState(() {
        if (alreadySaved) {
          _saved.remove(pair);
        } else { 
          _saved.add(pair); 
        } 
      });
    },
  );
}

解決策

trailingに表示するものをIconではなくIconButtonにすることで簡単に解決できます。

具体的にはtrailingIconButtonを指定し、IconButtonの中でIconとタップイベント取得用のonPressedを記載します。

  Widget _buildRow(WordPair pair) {
    final bool alreadySaved = _saved.contains(pair);
    return ListTile(
      title: Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),

      trailing: IconButton(
        icon: Icon(alreadySaved ? Icons.favorite : Icons.favorite_border,
            color: alreadySaved ? Colors.red : null),
        onPressed: () {
          setState(() {
            if (alreadySaved) {
              _saved.remove(pair);
            } else {
              _saved.add(pair);
            }
          });
        },
      ),
    );
  }

シンプルに、アイコンをボタンにすればいいよねってことです。

試せていませんがContainerでウィジェットを分けてからクリックリスナーを付けることもできると思います。

参考

ListTileについてはこのページがわかりやすいです。
ListTile class - material library - Dart API

Xcodeがアップデートできなくなった時の解決策

Flutterへ入門しようと下記手順に従って設定していた時の話です。

flutter.dev

Xcodeがアップデートできませんでした。

環境

  • macOS High Sierra (詳細忘れた) -> macOS Mojave 10.14.5へ更新
  • Xcode 6.x

現象

私のXcodeはversion6で時が止まっていました。

iOS開発なんてほとんどやったことがなかったので。

Flutter開発にはXcodeの最新が必要なので
App Storeで更新かけようと思ったのですが、

アップデート一覧にXcodeが出てこない。

Xcodeを直接検索すると「開く」ボタンが表示されるが、押しても何も起きない。

LaunchpadのXcodeには車両通行止めのマーク...
押したらApp Storeが開くけど、相変わらず「開く」ボタンしかない。

解決策

検索しても解決策が見つからなかったのですが、Launchpadでoption押してXcodeを削除したら、App Storeで最新Xcodeをインストールできました。

Cloud Firestoreでミニブログの構成を考える

NoSQLであるFirestoreでミニブログを作るときの構成を考えます。

Firestoreの組み方を勉強中ですので、自分の整理のためにも書いています。

ミニブログを考える

例として、ツイッターのようなつぶやきサービスについて考えます。

仕様

  • つぶやきを投稿できる
  • 他人をフォローできる
  • フォロワーを確認できる
  • 特定ユーザーのつぶやきだけを見られる
  • フォローしているユーザーのつぶやきだけを見られる

覚えておかなければいけないルール

  • 1つのドキュメントは最大1MB
  • フィールドに値は20000個まで(配列/Mapの一要素も1つとカウント)
  • ドキュメントへは1秒1回の書き込み制限がある
  • 課金はRead/Writeのドキュメント数

1つのドキュメントにまとめる

ユーザーコレクションでユーザー1人につき1ドキュメント与えられ、
ドキュメントはツイートの内容を配列として持ちます。

User
    user001{
        id:user001,
        tweets:[tweet1, tweet2, tweet3],
        followee:[user002, user003, user004],
        follower:[user007, user008, user009]
    }

問題点

以下の問題が挙げられます。

1MB制限/20000制限

ツイート数が増えていくと、いずれ容量/個数制限に引っかかってしまいます。

他にも問題点はありますが、後述します。

ツイートをサブコレクションにする

tweetsサブコレクションを用意し、1つのツイートを1つのドキュメントとして作成します。

サブコレクションであれば、Userドキュメントの1MB制限/20000制限に引っかからないのでうまくいきそうですね。

User
    user001{
        id:user001,
        tweets
            tweet1{
                tweetid:001,
                tweet_text:"ついーーと",
                }
            tweet2{
                tweetid:001,
                tweet_text:"ついーーと2",
                }
            tweet3{
                tweetid:001,
                tweet_text:"ついーーと3",
                }
        followee:[user002, user003, user004],
        follower:[user007, user008, user009]
    }

問題点

例えば全ユーザーから全てのツイートを取得しようと大変...でした。少し前まで。

今まではコレクション間を超えてQueryを出すことはできず、
db.collection("User").document("user001").collection("tweets")
のようにQueryを出す必要があったので、
全ツイートを取得するにはユーザーの数分のQueryが必要でした。

ですが最近のアップデートで、 同じコレクション名であれば横断的にQueryを出すことができるようになりました。

つまり、いけます。

qiita.com

もちろんよくある手法の通りにツイートコレクションをRoot直下に置くのもいいですね。
その際には所有者もフィードで示す必要があります。

User
    user001{
        id:user001,
        followee:[user002, user003, user004],
        follower:[user007, user008, user009]
    }
    user002{
        id:user002,
        followee:[user001, user003, user004],
        follower:[user001, user008, user009]
    }
Tweets
    tweet1{
        tweetid:001,
        author:user001,
        tweet_text:"ついーーと"
    }
    tweet2{
        tweetid:002,
        author:user002,
        tweet_text:"ついーーと2"
    }
    tweet3{
        tweetid:003,
        author:user003,
        tweet_text:"ついーーと3"
    }

見たところいい感じですが、問題点はまだあります。

followeeとfollowerをリストで持っていますと、
以下のような問題点が考えられます。

1MB制限/20000制限

フォロー / フォロワーに制限が出てしまいます。
2万フォロワーなんて世の中ザラにいます。私は20人にも満たないですが。

同期問題

例えばuser001がuser002のフォローを外したとします。
するとuser001ドキュメントのfolloweeからuser002を外すだけではなく、
user002ドキュメントのfollowerからuser001を外すこともしないといけません。面倒ですね。

User/ツイート/フォロー/フォロワーのコレクションをすべてRootに置く

全てをRootに置きました。

これなら1MB/20000制限にも掛かりません。

また、フォローフォロワーの更新は1つのドキュメント変更で済みます。

フォロー数の取得は
where(u'followee', u'==', u'user001')
でまとめて所得できます。

いい感じですね。

User
    user001{
        id:user001,
        followee:[user002, user003, user004],
        follower:[user007, user008, user009]
    }
    user002{
        id:user002,
        followee:[user001, user003, user004],
        follower:[user001, user008, user009]
    }
Tweets
    tweet1{
        tweetid:001,
        author:user001,
        tweet_text:"ついーーと"
    }
    tweet2{
        tweetid:002,
        author:user002,
        tweet_text:"ついーーと2"
    }
    tweet3{
        tweetid:003,
        author:user003,
        tweet_text:"ついーーと3"
    }
Follow
    follow1{
        followee:user001,
        follower:user002
    }
    follow2{
        followee:user001,
        follower:user002
    }
    follow3{
        followee:user001,
        follower:user002
    }

NoSQLで冗長性を持たせることは別に変なことではないので、
RootにTweetsコレクションを用意し、Userドキュメントにも同じTweetsサブコレクションを置く考え方も間違っていないと思います。

参考させていただいたサイト

Cloud Firestoreの勘所 パート2 — データ設計. 投稿型のブログサービスを設計しながらデータ構造について考える | by mono  | google-cloud-jp | Medium

待ち焦がれたCollectionGroupがCloud Firestoreへやってきた。 - Qiita

https://www.youtube.com/watch?v=haMOUb3KVSo&list=PLl-K7zZEsYLluG5MCVEzXAQ7ACZBCuZgZ&index=5

【Firebase】Cloud Firestoreのデータ構造の決め方をFirebaseの動画から学ぶ - Qiita

アクセスカウンタのデータベース設計

アクセスカウンタを作るときのデータベースについて考えてみます。

RDB

例えばCloud SQL/MySQLならこんな感じのテーブルでしょうか。

PVカラムをインクリメントしていけばカウントできそうです。

f:id:hisurga:20190505023753p:plain

問題点

MySQLでは更新時にロックをかけます。

複数人が同時にアクセスすると1人1人処理する必要があるため、パフォーマンスが悪いです。

対策

1つのページに対するレコードの数を増やし、増やした行の中からランダムな行でインクリメントするようにします。

アクセス数を出すときは、それぞれのレコードを集計して出します。

f:id:hisurga:20190505024817p:plain

レコードを増やした分だけ同時アクセスに対するパフォーマンスの向上が期待できます。

ただし、あらかじめどの程度アクセスがあるかを予測した上でレコードの数を考える必要があります。

NoSQL

例えばGCPのCloud Datastoreとしたら、こんな感じでしょうか。

pvをインクリメントすればアクセス数をカウントできそうです。

class Pages(ndb.Model):
    page = ndb.StringProperty(indexed=True)
    pv = ndb.IntegerProperty(indexed=False)

問題点

Cloud Datastoreは1つのエンティティに対しての変更が1回/1secに制限されています。

そのため、上記実装だと1秒に1アクセスしか捌くことができません。

対策

高パフォーマンスなkey-value NoSQLを利用する。
(自信ない対策案です...)

GCPだとBigtableとかでしょうか。Bittableは大規模データ用なので微妙かもしれませんけど。

Firesttoreでは分散カウンタを紹介しています。

先述したMySQLの対策と同じコンセプトですかね。

firebase.google.com

ユニークユーザーを数える場合

上記データベースではPVは計測できてもUUはわかりません。

UUも数える場合は、来訪者が来るたびにIPでuserを判別し、重複不可で追加していけばいいと思います。
最後にまとめてSUMでUUがわかります。

これならロックの心配もありません。

f:id:hisurga:20190505032410p:plain

あるいはページごとにテーブルを作成するのもいいかもしれません。

f:id:hisurga:20190505032531p:plain

その他

メルカリのPV管理では一度プールしてからまとめてSQLを更新しているみたいです。

tech.mercari.com

参考にさせていただいたサイト

MySQLでハイパフォーマンスなアクセスカウンター - まるまるこふこふ

MySQL - カウントアップするだけのAPIのつくりかた|teratail

EC2の機械学習環境が突然壊れる現象について

全く原因はわかっていませんが、何度か発生したので記します。

現象

EC2内の仮想環境(venv)で突然ライブラリ等の環境が無くなっている

経緯

AWS EC2 DeepLearning AMIの環境にssh接続していた場合、計算が終わればexitで抜けててインスタンスを止めますよね?

その後再度利用する際にはインスタンスを起動してssh接続をしています。

しかし稀に、再度接続してpythonファイルを実行した際にモジュールが無いと怒られます。
もちろん以前は動いていた環境です。

そのためpipで入れ直すと、今度はTensorflowが見つからないと怒られます。(仮想環境は"tensorflow_p36")

pipでTensorflowを入れても怒られたままです(何かしらの依存関係?)

そこからは面倒なので、いつも一度ファイルをローカルに落として再度環境構築しています。

対策

わかりません。

私のインスタンスの切り方が悪いのかなと思っていますが、インスタンスを止めなくても起きていたので謎です。

感覚的に、通信不良や強制的にsshを切った時に発生しやすい印象はありますが、、どうなんでしょう。

【DQN】強化学習でビットコインの価格予想をしてみる

学生時代にもML系について勉強していましたが、久々に1から始めてみようと思い立ちました。
しかし勉強したところで業務に利用することもないし、もちろんお金になりません。

だったら、ビットコインの価格予想で儲けることができればモチベも上がるんじゃないかと。 ということで頑張ってみます。

先に結果を記載しますと、
「一見すると予想はいい感じだけど、いざ取引してみると爆損」です。

一見するといい感じのグラフ

f:id:hisurga:20190430024517p:plain

少しでも興味のある方への手助けになれば幸いですが、私自身かなり浅い所にいるのは自覚しているのでご承知おきください。

使用するもの

今回はkerasとkeras-rlを利用します。なぜなら"私の"学習コストが安いからです。

エージェントにはたくさん学習させますが、私の学習時間は減らしたかったので。
社会人は時間がないのです。勉強しながら働ける職場を熱望します。

また、価格予想には強化学習を利用します。

Keras Keras-rl

Kerasは簡単に機械学習できるライブラリです。
簡単すぎて機械学習についての基礎は、良くも悪くもいらないです。

Keras-rlはそれをさらに簡単にしたライブラリです。(Keras拡張のイメージ)
「なんでもいいから機械学習させてくれ」って方におすすめです。

backendにTensorflowを使うこともできるので、
「Tensorflow? ああ、この前使ったよ」とか言って "AIエンジニア" っぽさをかもせます。

本当はkeras入門的な記事も書きたかったのですが、先駆者が多く、公式ドキュメントも充実していますのでやめました。

keras.io

ビットコイン

ビットコインFXは24時間稼働していますし、ボラティリティが大きいので成果が確認しやすいため、今回の実験対象としました。

私自身は少しだけアカウント取ってみただけでトレード知識はほとんどありません。

環境構築

kerasを用意したところで、まだ実験を始めることはできません。

機械学習には学習させるためのデータが必要です。

データ取得

FX予想に使われるデータとしては、時系列データであるOHLCが一般的ですね。

OHLCは過去のデータを簡単に取得できるので、学習データを集めやすいのが大きいメリットです。

しかし、私は下記仮定を立てました。

  1. 価格情報は学習データの中でユニークな値になりやすいのではないか
  2. ユニークな値で学習するとAgentは容易に学習してしまうのではないか

その結果、OHLCで学習しても使い物にならないのではないか(過学習になりやすいのではないか)と予想を立てました。
※過学習=データの丸暗記みたいなもの。汎用性がない。

対策としてOHLCを値そのままで使わず、増減率にするなどのアイデアもありましたが、今回は板情報から"板の厚み"を学習データにすることを考えました。

そこに価格情報は含めず、「midpriceからx円離れたところの厚み」を利用します。なんとなく汎用性が高まる気がしませんか?

これがいい選択である確信なんてありません。
ですが、何事もトライアル&エラーです。

板情報の取得

bitFlyer APIで板情報を取得してsqliteで保存します。

保存方法はここや

qiita.com

ここを参考にしました。

note.mu

コード全文は上記有料noteを改変したものですので載せるのは控えます。
累積方法は以下のコードを参考にしてください。

# 欲しい深さによって変更
DEPTH = [10, 20, 30, 50, 80, 130, 210, 340, 550, 890, 1440, 2330, 3770, 6100, 9870]
askvol = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
bidvol = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

for i in range(len(DEPTH)):
    for ask in board['asks']:
        if DEPTH[i] < ask['price'] - board['mid_price']:
            break
        else:
            askvol[i] += ask['size']
    
    for bid in board['bids']:
        if DEPTH[i] < board['mid_price'] - bid['price']:
            break
        else:
            bidvol[i] += bid['size']

私はだいたい1週間を1つのファイルに保存していましたが、データベースが大きくなっていくと徐々に処理が重くなっていくので注意です。
そのため、sleepが固定だとデータの取り始めと最後で間隔が異なってしまいます。適当にこんな感じにしておきましょう。

while datetime.datetime.now() < start + datetime.timedelta(seconds=SLEEP):
    sleep(0.01)

ビットフライヤーのメンテ時にも止める処理を入れておきましょう。

計算リソースの確保

効率的にMLを進めるには計算能力のある環境が必要です。

そこで今回はAWSのEC2インスタンス "p2-xlarge"と、AMI "Deep Learning AMI (Ubuntu)" の組み合わせで環境を作ります。

AWSアカウントについての詳細は割愛しますが、インスタンスの作成で下記AMIを選びましょう。

f:id:hisurga:20190426004237p:plain

インスタンスに接続すると「環境を選べ」みたいなメッセージが出ますが、source activate tensorflow_p36を実行しましょう。

p2.xlargeの費用は0.90USD/h(オハイオ/執筆時点)です。
マシンのスペックから考えるとコスパ的には最高なんですが、消し忘れるととても痛いです。

f:id:hisurga:20190427174032p:plain

価格予想で一攫千金を狙うより、素直に節約した方がお金は貯まると思います。

もちろん、モデルが複雑でない限りはハイスペックはいらないです。

私は途中からモデルをシンプルにしたので、t2.midiumのCPUで計算させていました。
お手元のmac bookでもいけますが、他の作業ができなくなるのでやめておきましょう。

AWSとローカル間のデータのやり取りはこの記事を参考にしてください。

blog.hisurga.com

実装と実験

とにかく改変 & 改変で作ったので、色々と非効率だったり、コード文法的にレベルは低いかもしれません。ご了承ください。

整備できてなくてお恥ずかしいですが、GitHubならこちら。

github.com

DQNBot

  • Agentには3つの選択肢 "BUY/SELL/STAY(何もしない)" を与える
  • 与える状況は "一定間隔の板の厚さ"
  • BUY -> (STAY) -> SELL もしくはその逆の順番でactionが選択された時、その差額を報酬として与える

実装1

結果1

「Agentはノーポジとガチホを覚えた!!!」

何十万ステップもトレーニングさせると、
「これ取引するより何もしないほうがいいよね」
「最初に買ってガチホが一番だよね」
と考えるようになってしまいました。

逆に学習が少なくしても、ひたすらに負け続けていました。

実装2 LSTMを導入する

モデルを変えてLSTMを導入してみます。
簡単に書くと長期依存に対応することができます。

詳しい解説してくださる方がいらしたので紹介。

qiita.com

「実はこの板の厚みの変化って長期的に見る必要があるんじゃないか」と仮説を立て、LSTMを選定しました。

物は試しです。ネットワーク定義を以下に変えてみます。

# DQNのネットワーク定義
model = Sequential()
model.add(LSTM(units=512, return_sequences=True, input_shape=input_shape))
model.add(Dropout(dropout))
model.add(LSTM(units=512, return_sequences=False))
model.add(Dropout(dropout))
model.add(Dense(units=nb_actions))

結果2

「Agentはノーポジとガチホを覚えた!!!」

変わらん。

反省と仮説

1step(1sec)毎に3択を与えるってAgentにとってめっちゃしんどいんじゃないか?
-> ゲームを攻略できるDQNがあるくらいだからいけるはずだけど...

シンプルにパラメータや考え方が悪いんじゃないか?
-> 技術力不足

一定期間後に上か下か予想させる方が効率よく学習できるんじゃないか?
-> やってみよう

DQNBot_Label

  • Agentには3つの選択肢 "BUY/SELL/STAY(何もしない)" を与える
  • 与える状況は "一定間隔の板の厚さ"
  • BUY or SELLが選択された時、一定期間後のmidpriceとの差額を報酬として与える
  • Labelって名前に特に意味はない

報酬の与え方も少し工夫します。
報酬をシンプルにすることで、汎用性を高めることが目的です。

  • 一定額以上のプラス差額 "報酬1"
  • 一定額未満のプラス差額 "報酬0"
  • マイナス差額 "報酬-1"

実装

結果

trainデータそのままをtestしてたところ、かなり儲けているようです。 (パラメータや学習回数は記録できていません。。)

青がSELL 赤がBUY

f:id:hisurga:20190430023358p:plain

しかし他のデータセットでtestしたところ、惨敗です。

f:id:hisurga:20190430023538p:plain

このモデルでbitFlyerのBotも作ってみましたが、取引の回数が少なく、十分なデータを集められませんでした。(STAYばかり選択される)

反省

  • 汎用性がない どうやら過学習になっているように見受けられる
  • 急騰/急落時にリバを期待して逆張りしていくっぽい
  • もっとガンガン取引してスキャって欲しい

対策

以下の案で考えています。まだ検証は進められていません。

汎用性がない
-> モデルをシンプルにする dropoutを高めに設定する 学習データをもっと増やす

急騰/急落時にリバを期待して逆張りしていくっぽい
-> モデルをシンプルにする

もっとガンガン取引してスキャって欲しい
-> モデルをシンプルにする actionをBUY/SELLの2択にする

まとめ

「DQNBotで儲けられたのか」については、

「今の所、儲けられていません」が回答になります。

バックテストでこの状況では、確実に実環境では大損します。
実環境ではスプレッドなども考慮しなければならず、それを賄えるほどの報酬を安定して出さなければ行けません。

過学習を乗り越えた先に何かがあると信じて、暇を見つけて進めたいと思います。
上手くいった日には、続編として書くかもしれません。

最初のグラフ

最初に載せたグラフはactionをBUY/SELLの2択にして1秒毎1分先の価格予想した結果です。

めちゃくちゃいい感じに伸びていますが、これも過学習でした。

f:id:hisurga:20190430024517p:plain

参考にさせていただいたサイト

DQNで機械学習した人工知能がBitcoinをシストレして月700万円儲けるまでの話(失敗) - Qiita

Deep Q-LearningでFXしてみた

機械学習やディープラーニングでFX予測をする際に超参考になる記事まとめ

AIが学習しすぎる?「過学習」問題とそれを抑制する方法 | AI入門ブログ(人工知能の作り方など人工知能に関する情報を公開)

[Python] Keras-RLで簡単に強化学習(DQN)を試す - Qiita

EC2インスタンスとローカルでファイルをやり取りする方法

AWSインスタンスへscpコマンドを利用してファイルを受け渡しします。

ec2のアドレスはインスタンス管理画面から"接続"を選択すれば確認できます。 ubuntuの箇所はインスタンスのOSによって変える必要があると思います。

ファイルのダウンロード

scp -i 'pemファイル' ubuntu@ec2アドレス.リージョンcompute.amazonaws.com:/home/ubuntu/foo.file /ローカル/bar/

ファイルのアップロード

scp -i 'pemファイル' /ローカル/foo.file ubuntu@ec2アドレス.リージョン.compute.amazonaws.com:/home/ubuntu/アップロード場所

-rオプションでフォルダごと移せます。

scp -r -i 'pemファイル' /ローカル/foo/ ubuntu@ec2アドレス.リージョン.compute.amazonaws.com:/home/ubuntu/アップロード場所

scpってssh+cpって意味なんですって。