珈琲とバイクとときどきプログラミング

狐が好きな編入生的な人間のブログです

AWSのインスタンスをSDKを使ってローカルからいい感じにする話

こんにちは. 趣味でGPUインスタンスを使うことがあったりなかったりするのですが とても不定期なので都度起動,停止をするのがめんどくさく,RakeTaskを書きました.

github.com

コード

require 'aws-sdk'

class SetupSSH
  class << self
    @@client = Aws::EC2::Client.new(access_key_id: ENV['GPU_AWS_ACCESS_ID'], secret_access_key: ENV['GPU_AWS_ACCESS_SECRET'])

    def fetch
      ids = fetch_instances(state: 'stopped').map(&:instance_id)
      @@client.start_instances(instance_ids: ids)
      @@client.wait_until(:instance_running, instance_ids: ids)
      config = fetch_instances(state: 'running').map(&:public_ip_address)
        .map { |ip_address| config_for_instance(ip_address) }
        .flatten
        .join("\n")

      target_dir = "#{ENV['HOME']}/.ssh/conf.d"
      FileUtils.mkdir_p(target_dir)
      File.write("#{target_dir}/config_essay", config)
      p config
    end

    def stop
      ids = fetch_instances(state: 'running').map(&:instance_id)
      @@client.stop_instances(instance_ids: ids)
      begin
        @@client.wait_until(:instance_stopped, instance_ids: ids) do |w|
          w.interval = 15
          w.max_attempts = 20
        end
        p 'instances have stopped'
      rescue Aws::Waiters::Errors::WaiterFailed => error
        p "failed waiting for stopping instance: #{error.message}"
      end
    end

    private

    def fetch_instances(state:)
      @@client
        .describe_instances
        .reservations
        .map { |r| r.instances.select { |i| i.state.name == state } }
        .flatten
    end

    def config_for_instance(ip_address)
      <<~EOS
        Host gpu-#{ip_address}
        HostName #{ip_address}
        User ubuntu
        LocalForward 8888 localhost:8888
        IdentityFile ~/.ssh/make/gpu.pem
      EOS
    end
  end
end

基本的な流れは 停止中のインスタンスのidをfetch→起動→waitしてconfigに吐き出すというという流れです (config中でconf.d以下のfileをincludeしていること前提です) pecoとか使っておくと複数個のインスタンスを管理しておくのに便利です。

yosshi-diary.hatenablog.com

ついでにローカルの8888をポートフォワーディングとかしておくと jupyterNoteBookとかを起動した時に何も考えずlocalの8888に繋げば良いだけなので便利だと思います. AWS上で運用するならcloudWatchとかでCPUUsageベースでシャットダウンでも良い気がしますが メトリクスを決めるのがめんどいので一応手動で切れるようにしてます.

Deep Count: Fruit Counting Based on Deep Simulated Learning の プログラムとか

Deep Count: Fruit Counting Based on Deep Simulated Learning

この前の記事で読んだとかいってた論文を見ながら実際にやってみて、精度でるんか? みたいなものを一旦出したいとのことなので手を動かしてみた(pythonクソ初心者です)

www.mdpi.com

環境

python 3.6.3

やったこと

花実の数を数えるにあたり、データセット(画像+写ってる花実の数)を用意するのがとりあえずめんどいから、synthetic imagesで代用したよ〜との記述があったのですが、

そのプログラムとか一ミリもなかったので、とりあえずpython練習がてらプログラムを書きました

f:id:yosshi0774:20180429021643p:plain

jupyter notebookとかで実行するのを考えてるのでガバガバです。

numpyとかのおかげでかなり簡単にできるみたいですね。

別のプログラムで使うことを考えてcsvに結果(ファイル名、花実数)を吐き出すようにしました

import numpy as np
import matplotlib.pyplot as plt
import cv2
import csv
from numpy.random import *

# 画像の幅
width = 130
# 最小の花実の直径
min_r = 5
# 最大の花実の直径
max_r = 13
# 最大の花実の個数
max_count = 20
# 最低の花実の個数
min_count = 0
# ベースとなる緑(RGB)(乱数により揺れるので、適当で大丈夫)
green_base = (72, 107, 12)
# ベースとなる茶色
brown_base = (120, 101, 86)
# 花の色
flower_color =  (237, 223, 29)
# 作る画像の枚数
create_count = 1000
# データの保存先
data_path = "./data/dst/"
# 画像の名前
image_name = "train_image"
# 拡張子
extension = ".jpg"
# 背景画像
back_image = np.full((height, width, 3), 0, dtype=np.uint8)
# 背景色のベースカラー(緑、茶色)
color_pattern = np.array([green_base, brown_base])
# 10飛びのarrayを作成
list = np.array(range(0,width + 1,10))


with open('train_images.csv', 'w') as f:
    writer = csv.writer(f)
    # headerの書き込み
    writer.writerow(["file_name", "count"])
    for i in range(create_count):
        # 半径10の円で 埋めていく
        for x in list:
            for y in list:
                cv2.circle(back_image, (x, y), 10, (color_pattern[randint(0, 2)] + randint(0, 20,(1,3))[0]).tolist(), thickness=-1)

        # ガウシアンフィルターを書けて平滑化
        blured_back_image = cv2.GaussianBlur(back_image, (7, 7), 0)

        # 花実の数を生成
        flower_count = randint(min_count , max_count)

        for j in range(flower_count):
            #花実の大きさをランダムで生成
            flower_radius = randint(min_r, max_r)
            position = (randint(0, width + 1), randint(0, width + 1))
            cv2.circle(blured_back_image, position, flower_radius, flower_color, thickness= -1)

        # bgr になっているので rgb に変換する
        im_rgb = cv2.cvtColor(blured_back_image, cv2.COLOR_BGR2RGB)
        
        save_name = image_name + str(i) + extension
        cv2.imwrite(data_path + save_name, im_rgb)
        writer.writerow([save_name, flower_count])

色とかも適当にベースカラーきめてrandで適当なvarianceを付けるみたなことをしてて

ほんとにこれで大丈夫か?みたいなところはあります。

実行結果

画像達は一応できてそうです

f:id:yosshi0774:20180429022707p:plain

csvもfile名とそれの花実の数を吐き出されていますね。

$ head -n 10 train_images.csv
file_name,count
train_image0.jpg,9
train_image1.jpg,16
train_image2.jpg,11
train_image3.jpg,11
train_image4.jpg,2
train_image5.jpg,11
train_image6.jpg,17
train_image7.jpg,15
train_image8.jpg,18

研究が環境系なのでAWSとかへの知見がなく、研究費がおりないとか言われたので、一旦Google Colab とかでtrainは走らせたいと思います

自分でマシン組めとか言われましたが、流石にしんどいので(時間あればやりたいですが...)

https://colab.research.google.com/

google drive とかに↑で作った画像とcsv置いて、API叩けばgoogle colab上にfetchしてこれるみたいなので、それでいいかなーと思ってます

RxSwift + UIRefreshControl + MVVMで幸せになる

iOSにはUIRefreshControlというものがあります(twitterでいうところのシュッテしてポンってなるやつ)

あれ結構使うわりにはいちいちpropertyを設定するのが面倒だったので

MVVMを使うこと前提にRxRefreshingControlというものを作ってしまえば楽じゃねと思い作りました。

import RxSwift
import RxCocoa

protocol RefreshableModel: class {
    func refresh()
    var refreshingState: Driver<Bool> { get }
}

class RxRefreshControl: UIRefreshControl {
    
    private weak var refreshableModel: RefreshableModel?
    private let disposeBag = DisposeBag()
    
    private override init() {
        super.init()
        
        rx.controlEvent(.valueChanged)
            .asDriver()
            .drive(onNext: { [weak self] _ in
                self?.refreshableModel?.refresh()
        }).disposed(by: disposeBag)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init coder has not been implemented")
    }
    
    convenience init(_ refreshableModel: RefreshableModel) {
        self.init()
        self.refreshableModel = refreshableModel
    }
    
    func setupEndRefresingListener(_ listner: @escaping () -> Void) {
        refreshableModel?.refreshingState
                        .drive(onNext: { [weak self] _ in
                            self?.endRefreshing()
                            listner()
                        }).disposed(by: disposeBag)
    }
}

refreshingControlの役割としてはシュってしたらrefreshのリクエストが走り、responseが会ったらポンってなってほしいだけです。

そのため、VMをRefreshableModelに準拠させ、①refreshの状態をDriverによって流してやること、②refresh時APIを叩くメソッド を用意しといて上げれば

ViewController では

private lazy var refreshControl: RxRefreshControl = {
  return RxRefreshControl(viewModel)
}()

としてrefreshControlをしてrefreshしたいtableViewcollectionViewに対して.addSubView(refreshControl)して

もしrefresh時のコールバックを仕込みたければviewDidLoadなどで

refreshControl.setupEndRefreshingListener {
 // 任意のコールバック
}

とすれば良くなるわけですね。

これでrefreshが少しは扱いやすくなるかと思います

植物工場のための画像認識

会社で働きながらB4で卒論を書いているという謎のステータスなのでたまにはメモ程度に卒論について

僕が専攻しているところが工学部でありながらなんでもありな学科(経済系からいわゆるAI系や環境系など)ですが

僕はキャンパスが家から近いのが絶対条件だったので人気がなさそうな環境系を選びました。

そしたら”植物工場を作るために植物の状態を観測したい、ディープラーニングってやつでなんとかして”って感じの研究室で

運良く趣味の延長線上みたいなことをやることになりました。

そのため、備忘程度に呼んだ論文や軽く調べたことを書こうと思います。

生育状態の観測

”植物の生育状態がどうか”というのを継続的に&&自動的に観測するというのが、植物工場実現の上で重要のようです。

例えば、生育状態を元にしたフィードバック制御で温度管理などを行うようで、

”生育状態”をいくつかにブレイクダウンし継続的に計測をしたいみたいですね。

”生育状態”の指標として、例えば、花実の数、葉面積などのメトリクスがあり、これらを計測したいというニーズがあるみたいです。

そんなわけで最初はこの論文を読みました。

①Deep Count: Fruit Counting Based on Deep Simulated Learning

www.mdpi.com

この論文のすごいとこ

trainの画像データは実際の写真でなく人工的に作ったもので代用可能である

② 花実の数を90%程度の精度で当てる

いくつかある学習済みのモデルは植物の種類が変わったとしても多少大丈夫程度のロバストさはあるものの、

やはり精度は足りないようで、植物の種類、生育環境ごとにモデルを組み直すのがよくあるみたいなようですが、

アノテーション済みのデータセットの不足(手間がかかる)により、あまり芳しくない結果となることが多いようです。

そのため、人工的に訓練データを作成してしまえばええやんというコンセプトで

実際にそこそこの精度を出しているのがこの論文のようです。

いくつか関連しそうな論文の中でまず手につけるタスクとして一番これがイメージに近いかなという感じです。

コードを作って共有できればと思います。

②Data synthesis methods for semantic segmentation in agriculture: A Capsicum annuum dataset. Computers and Electronics in Agriculture

www.sciencedirect.com

この論文のすごいところ

① 花実にとどまらずに植物の3次元データからtrainDataを作り出せるっぽい?

まだ読み途中ですが、わりと有用そう。

ただ本筋とはそれそうなので時間があったら読む

やはりapplicationの人間なのでacademicな議論は向いてないなぁと思った一日でした

pecoがめっちゃ便利だった

ssh hogehogeなんかを使ってインスタンスの中いって状況みたり

いくつかのprojectのファイルを行き来するために頻繁に cd ~/hogeとか打つことが多くて

若干ストレスがあったのですが、どうやらpecoってものが便利らしく使ってみたという話です。

github.com

READMEを見るだけでワクワクしてきますね。

pecoとは

peco can be a great tool to filter stuff like logs, process stats, find files, because unlike grep, you can type as you think and look through the current results.

ということで、lsとかpsで吐き出される出力をインクリメンタルにサーチ→選択ができ、

選択した値を使ってゴニョゴニョしたりできるみたいです。

使ってみた

Homebrewを使ってとりあえずインストール

$ brew install peco

こんだけです。

$ ls -al | peco

みたいにpecoに渡して上げると

f:id:yosshi0774:20180321071435p:plain

エモいインターフェイスになり検索できるわけですね。良い。。。

さて一番最初にいったような いくつかのprojectのファイルを行き来するために頻繁に cd ~/hoge

とかの解消法ですが

bashを使っている方なら ~/.bashrcとかに

function pcd() {
  local target=$(ls $HOME/(探したいdir) | grep -e '絞りたい語句1' -e '絞り込みたい語句2' | peco)

  if [ ! -z "$target" ]; then
    cd $HOME/(探したいdir)/$target
  fi
}

みたいなものを作っておき source ~/.bashrcとかして更新

$ pcd

として選択すれば指定のディレクトリに遷移できるわけです。きもちいですね

あと~/.ssh/configとかに設定をまとめておけば

function pssh() {
  local host=$(grep -r 'Host ' $HOME/.ssh/* | cut -d' ' -f2 | sort | peco)

  if [ ! -z "$host" ]; then
    ssh "$host"
  fi
}

で接続先を選んでsshとかができます。

iOSアプリでのstateごとのviewを出し分ける話

こんにちは

趣味みたいな仕事でstateごとにviewを出し分けたくなったのでその話です。

アプリって基本的に

遷移→APIのコール、loading→成功したらそれを表示、失敗したらエラー画面の表示

の繰り返しになり、

AndroidならViewAnimator | Android Developersを使って

同一階層にstateごとにviewを作成し、viewAnimatorのdisplayChildにbindして上げるとキレイに解決できますが

iOS appの場合それに当たるものなんだろうなーと思い奮闘していました。

正直ベストプラクティスな感じはしませんが一応メモ程度に残します。

やりたいこと

Loading Error
f:id:yosshi0774:20180321015909p:plain f:id:yosshi0774:20180321015924p:plain

みたいな画面を統一的に出したい

できたもの

とりあえず使い回すためにゴリゴリviewを書いていく

まずはloading側

class LoadingStateView: UIView {

    private lazy var indicator: UIActivityIndicatorView = {
        let indicator = UIActivityIndicatorView()
        indicator.color = UIColor.black
        return indicator
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    private func setup() {
        backgroundColor = UIColor.vcBg
        addSubview(indicator)
        indicator.startAnimating()
        indicator.snp.makeConstraints {
            $0.width.height.equalTo(50)
            $0.center.equalToSuperview()
        }
    }
}

次にError側

import RxSwift
import RxCocoa

class ErrorStateView: UIView {

    private let disposeBag = DisposeBag()

    private lazy var reloadButton: UIButton = {
        let button = UIButton()
        button.setTitle("もう一回読み込む", for: .normal)
        button.titleLabel?.font = UIFont(name: "HiraKakuProN-W3", size: 15)
        button.backgroundColor = // pinkっぽい色
        button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)
        button.layer.cornerRadius = button.intrinsicContentSize.height / 2
        return button
    }()

    private lazy var messageLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont(name: "HiraKakuProN-W3", size: 15)
        label.textColor = UIColor.black
        label.text = "通信に失敗しました"
        label.sizeToFit()
        return label
    }()

    private lazy var stackView: UIStackView = {
        let stackView = UIStackView()
        stackView.addArrangedSubview(messageLabel)
        stackView.addArrangedSubview(reloadButton)
        stackView.spacing = 10.0
        stackView.axis = .vertical
        stackView.distribution = .fill
        stackView.alignment = .center
        return stackView
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    private func setup() {
        backgroundColor = UIColor.vcBg
        addSubview(stackView)

        stackView.snp.makeConstraints {
            $0.right.left.equalToSuperview()
            $0.centerY.equalToSuperview()
        }

    }

    func setupButtonClickListener(handler: @escaping () -> Void) {
        reloadButton.rx.tap
                    .asDriver(onErrorDriveWith: Driver.empty())
                    .drive(onNext: {
                        handler()
        }).disposed(by: disposeBag)
    }
}

snapKitを使ってゴリゴリconstraintsとか書くのが好きなのでこんな感じになりました。

これらを使いまわせばいけそうですね

使い方(storyBoardの設定)

普通はviewのしたにゴリゴリviewを配置していくのところをとりあえず3つのviewを作ってみる(successタイポしてた...)

f:id:yosshi0774:20180321020524p:plain

各Viewの大きさはメインのview(SuccessView)に合わせて適当に配置

f:id:yosshi0774:20180321020715p:plain

LoadingStateView, ErrorStateViewにはそれぞれさっきのcustomClassを当ててあげる

f:id:yosshi0774:20180321020825p:plain

あとはIBから使いたいViewControllerにoutletをつなぎましょう

使い方(コード上の設定)

初心者ながらMVVMっぽく書きたくなったので

class ProductRankingsPageViewController: UIViewController {
    
    private let disposeBag = DisposeBag()
    
    @IBOutlet weak private var loadingView: LoadingStateView!
    @IBOutlet weak private var mainView: UIView!
    @IBOutlet weak private var errorView: ErrorStateView!
    
    private var stateViews: [UIView] {
        return [loadingView, mainView, errorView]
    }

    private let viewModel = TestPageViewModel()

    override func viewDidLoad() {
       super.viewDidLoad()
       viewModel.viewDidLoad()
       setupListener()
    }

    private func setupListener(){
        errorView.setupButtonClickListener { [weak self] in self?.viewModel.reconnect() }

        viewModel.viewState
            .drive(onNext: { [weak self] state in
                guard let strongSelf = self else { return }
                strongSelf.view.bringSubview(toFront: (strongSelf.stateViews[state.rawValue]))
                strongSelf.collectionView.reloadData()
                strongSelf.collectionView.setContentOffset(CGPoint.zero, animated: false)
            }).disposed(by: disposeBag)


    }
}

こんな感じになっており

さっきの3つのviewを配列として返すようなcomputed propertyを作っておき、

viewの状態をsubscribeして、現状のviewを最前列に持ってくるみたいなロジックです。(つねに3つのView持ってるの不健全っぽい)

また、errorの時のボタンを押した時の挙動をセットしてあげています。

viewModelはこんな感じで

import RxSwift
import RxCocoa

class TestPageViewModel {
    private let state: BehaviorRelay<LoadingState> = BehaviorRelay(value: .loading)

    private var busy = false

   var viewState: Driver<LoadingState> {
       return state.asDriver(onErrorDriveWith: Driver.empty())
   }

   func viewDidLoad(){
        state.accept(.loading)
        fetch()
   } 
   
   func reconnect(){
        state.accept(.loading)
        fetch()
   }

   private func fetch() {
      
      let single = // rxMoyaを使ってSingle<T>を返すようなrequestをここに書いています
      single.observeOn(MainScheduler.instance)
                .subscribe { [weak self] event in
                    guard let strongSelf = self else { return }
                    switch event {
                    case .success(let dailyRanking, let categoryRanking):
                        // 成功時の処理
                        strongSelf.state.accept(.success)
                    case .error:
            // 失敗時の処理
                        strongSelf.state.accept(.failure)
                    }
                    strongSelf.busy = false
                    strongSelf.refreshing.accept(false)
                }.disposed(by: disposeBag)
   }
}

stateにerrorを流すことは基本的にないのでviewStateでdriverに変換し、適当にerrorは潰して上げます。

APIのコールが成功→state.accept(.sucess)

APIのコールが失敗→state.accept(.failure)

としてます。

ここでLoadingStateは簡単なEnum

enum LoadingState: Int {
    case loading = 0
    case success = 1
    case failure = 2
}

こんなものです。

これで動くかと思います。

MVVMは状態やインスタンスの変数をVMに吐き出せるのでVCがスッキリしていいですね。

VCにすべて書こうとするならば

private var state: LoadingState = .loading {
    didSet {
            DispatchQueue.main.async {
                self.view.bringSubview(toFront: self.stateViews[self.state.rawValue])
            }
     }
}

みたいにしてあげればよいかなという感じです。