A Day In The Life

とあるプログラマの備忘録

数学知識ほぼ0の人間が深層学習の勉強をはじめてみた

きっかけは Magenta プロジェクト

最近巷では機械学習や深層学習(ディープラーニング)などAI系の技術が流行っていますが自分は2-3ヶ月前まで全くと言っていいほど興味がありませんでした。機械学習とかって画像解析や翻訳で使う技術でそのうち頭のいい人がなんとかしてくれるんじゃないのくらいの認識でした。その認識が一変したのが Google Magenta プロジェクト の AI Duet by Yotam Mann - AI Experiments でした。自分も深層学習を使って自動作曲やりたいということで勉強を始めることにしました。これまで自分が勉強したことや読んだ本のことをメモがてらまとめてみます。

勉強をはじめたころのスペック

  • Pythonは使ったことがある、ただしnumpyは使ったことがない
  • 数学はほどんとわからない(または忘れた)、ゲームの開発をしていたので三角関数くらいはなんとか理解できるくらい
  • 機械学習と深層学習の違いがわからない
  • 昨今流行っているAIがゲームAIとどう違うのかわからない

自動作曲やるならニューラルネットワークを勉強するのがよさげ

軽くMagenta プロジェクトのことや「自動作曲+AI」みたいなキーワードで調べてみるとニューラルネットワークを理解すると色々できるようになるっぽいということがわかりました。

数学さっぱりだけどニューラルネットワークについて勉強したい

本屋さんでニューラルネットワークの本を探して見ましたがどれも最低限の数学的な知識が必要そうな雰囲気でした。自分は数学が苦手で高校生の頃まともに数学を勉強した記憶がほとんどない....これは敷居が高いどうしようとなりました。

ライブラリを使って学ぶかライブラリ無しで勉強するか

いろいろと調べているうちに TensorFlow と Chainer この2つのライブラリが人気らしいことがわかりました。ただこれらのライブラリのチュートリアル読んでも専門用語がわからなさすぎて使いこなせる気が全くしませんでした。なのでライブラリに頼らず1から自分で実装してみるのがよさそうとなりました。

数学が苦手でも読めるニューラルネットワーク本との出会い

数学が苦手な自分でも内容がわかりライブラリに頼らず1から実装を学べるそんなわがままに答えてくれる本なんてないと思っていたらなんとも自分にぴったりな本を見つけました。

数式はあるものの初心者にもわかるように丁寧に解説してくれている、ニューラルネットワークについての基本的な用語の解説もある、Pythonの実装例まである、知りたいことほとんど載ってるじゃないか...素晴らしすぎる。バイアスの説明が省略されていること以外は必要な知識がほぼ網羅されています。

もっと基本的なところから数学の勉強したい場合は

深層学習の本ではなく機械学習の本ですが、深層学習に比べて数式が簡単なので深層学習の本よんで数式が全く理解できないって場合にオススメです。

ものすごく基本的なところから説明してあってかつPythonの実装例もあるのでかなり理解しやすいです。

深層学習を使ってどんなことができるのか

覚えた用語

  • 入力層
  • かくれ層
  • 出力層
  • 重み
  • 誤差逆伝搬

学習って何?

誤差が最小になるような重みの組み合わせを見つけること

深層学習を理解する上で必要な知識

よく出てくる数学用語

スカラー、ベクトル、行列、テンソル

次元 名前 説明
0次元 スカラー 数値
1次元 ベクトル 配列
2次元 行列 2次元配列
3次元以上 テンソル 3次元配列以上

ただしベクトルには列ベクトルと行ベクトルがある、通常は列ベクトルだけ考えれば良い

ベクトルと行列関連の用語

実装してみる

Pythonの実装例を元にSwiftで実装してみました。 github.com 3層ニューラルネットだけでは物足りず、今はLSTMの実装に挑戦中です。

参考

オブジェクト指向の次に来るもの

自分が駆け出しのプログラマだった頃(10年以上前ですかね)、必死でオブジェクト指向プログラミングの勉強をしました。最近だとオブジェクト指向プログラミングなんて当たり前すぎて技術とすら言えないくらいになったと感じてます。 エンジニアが生き残るために次に必要な技術は何かここ数年いろいろ考えてきましたが、次は間違いなくリアクティブプログラミングだと予想しています。1年半ほど前、UniRxを使ったのをきっかけに現在もRxSwiftを使って日々リアクティブプログラミングと接しています。そんな大事な技術ですがあまり専門書がないのが困ったところです。そんな折に見つけたのが本書です。

厳密にはリアクティブプログラミング(RP)について書かれた本ではなく関数型リアクティブプログラミング(FRP)について書かれた本です。関数型リアクティブプログラミングはこの本によると「関数型プログラミング(FP)とリアクティブプログラミングの交わる部分」だそうです。 FRPの考え方はRx(リアクティブプログラミング)にも応用可能ということなんでこの本を読んで知見をためたいと思います(まだ全部読んでません)。

面倒な課金処理をRx化するRxStoreKitを作りました

StoreKit の SKProductsRequest と SKPaymentQueue を Rx化して課金処理を簡単に書けるライブラリを GitHub にて公開しました。 github.com RxStoreKit を使うと課金処理を以下のような感じで書けます。

let productRequest = SKProductsRequest(productIdentifiers: Set(["xxx.xxx.xxx"]))
productRequest.rx.productsRequest
    .flatMap { response -> Observable<SKProduct> in
        return Observable.from(response.products)
    }
    .flatMap { product -> Observable<SKPaymentTransaction> in
        return SKPaymentQueue.default().rx.add(product: product)
    }
    .subscribe(onNext: { transition in
        print(transition)
    }).disposed(by: disposeBag)
productRequest.start()

Rx以前の課金処理

だいぶ前の記事ですが iOS アプリの課金処理まわりの記事を書きました。 glassonion.hatenablog.com こちら見ていただくとわかると思いますが StoreKit(In-App Purchase) のプログラムはものすごく複雑です。中でも SKPaymentTransactionObserver の処理が特に大変です。StoreKit のような非同期コールバック地獄みたいなプログラムは RxSwift とRxCocoa を使って Rx化するとものすごく簡略化できます。というわけで RxStoreKit を宜しくお願いします。

関連記事

失敗しない iOS In-App Purchase プログラミング - A Day In The Life
RxSwift DelegateProxy with required methods – LazySequence<UnsafePointer>

Swift3でMIDIファイルをパースする方法

Swift3 で MIDI ファイルのパースをしようとしたら意外に方法が見つからなくて苦労したのでメモです。 MusicEventIteratorGetEventInfo 関数で取得したデータを MIDINoteMessage オブジェクトに変換する方法がわからなくてハマりました。

import AudioToolbox

public class MIDIParser {
    
  public static func parse(url midiFileUrl: URL) {
        
    var musicSequence: MusicSequence?
    var result = OSStatus(noErr)
    result = NewMusicSequence(&musicSequence)
    guard let sequence = musicSequence else {
      print("error creating sequence : \(result)")
      return
    }
        
    // MIDIファイルの読み込み
    MusicSequenceFileLoad(sequence, midiFileUrl as CFURL, .midiType, MusicSequenceLoadFlags.smf_ChannelsToTracks)
        
    var musicTrack: MusicTrack? = nil
    var sequenceLength: MusicTimeStamp = 0
    var trackCount: UInt32 = 0
    MusicSequenceGetTrackCount(sequence, &trackCount)
    for i in 0 ..< trackCount {
      var trackLength: MusicTimeStamp = 0
      var trackLengthSize: UInt32 = 0
            
      MusicSequenceGetIndTrack(sequence, i, & musicTrack)
      guard let track = musicTrack else {
        continue
      }
            
      MusicTrackGetProperty(track, kSequenceTrackProperty_TrackLength, &trackLength, &trackLengthSize)
            
      if sequenceLength < trackLength {
        sequenceLength = trackLength
      }
            
      var tmpIterator: MusicEventIterator?
      NewMusicEventIterator(track, &tmpIterator)
      guard let iterator = tempIterator else {
        continue
      }
            
      var hasNext: DarwinBoolean = false
      MusicEventIteratorHasCurrentEvent(iterator, &hasNext)
            
      var type: MusicEventType = 0
      var stamp: MusicTimeStamp = -1
      var data: UnsafeRawPointer?
      var size: UInt32 = 0
            
      while hasNext.boolValue {
        MusicEventIteratorGetEventInfo(iterator, &stamp, &type, &data, &size)
        if type == kMusicEventType_MIDINoteMessage {
          let messagePtr = UnsafePointer<MIDINoteMessage>(data?.assumingMemoryBound(to: MIDINoteMessage.self))
                    
          guard let channel = messagePtr?.pointee.channel,
            let note = messagePtr?.pointee.note,
            let velocity = messagePtr?.pointee.velocity,
            let duration = messagePtr?.pointee.duration else {
              continue
          }
            
          let message = MIDINoteMessage(channel: channel,
              note: note,
              velocity: velocity,
              releaseVelocity: 0,
              duration: duration)
          print(message)
        }
                
        MusicEventIteratorNextEvent(iterator)
        MusicEventIteratorHasCurrentEvent(iterator, &hasNext)
      }
      DisposeMusicEventIterator(iterator)
      MusicSequenceDisposeTrack(sequence, track)
    }
    DisposeMusicSequence(sequence)
  }
}

参考

Objc や Swift2の情報はそこそこありました。

Swift3 のポインタ型についてちゃんと理解してればハマらなかったのかなと反省してます。

ReSwiftのステートをRxSwiftを使って監視する

普段から RxSwift(Reactive ExtensionsのSwift実装)と ReSwift(ReduxのSwift実装)を使っています。RxSwift を通信系の処理だったりデリゲートの処理(例えば StoreKit とか CoreBluetooth)だったりに使って、ReSwiftをアプリ全体で管理しなきゃいけないデータの監視なんかに使ってます。ReSwift をそのまま使ってもあまり困らないのですが、ReSwift のステートを Observable に変換してUI部品とバインドするといろいろと捗るなぁというわけで ReSwift の Rx 用ラッパークラスを作成しました。

ReduxのステートをRx化するクラス

Redux のステートを Rx の Variable に格納して、Redux からステート変更の通知が来たら Variable(エラーの発生しないBehaviorSubject)を更新するクラスを作成します。

import RxSwift
import ReSwift

class RxStore<AppStateType: StateType>: StoreSubscriber {
  // 監視する値
  let state: Variable<AppStateType>
  private let store: Store<AppStateType>
    
  init(store: Store<AppStateType>) {
    self.store = store
    self.state = Variable(store.state)
    // 購読開始
    self.store.subscribe(self)
  }
    
  deinit {
    // 購読終了
    self.store.unsubscribe(self)
  }
    
  func newState(state: AppStateType) {
    // Variableを更新する
    self.state.value = state
  }
}

使い方

Store を生成します。ここは通常の ReSwift の使い方そのままです。

// ストア
struct AppState: StateType {
  var count: Int = 0
}

// アクション
enum AppAction: Action {
  case incAction
}

// Reducer
struct AppReducer: Reducer {
  func handleAction(action: Action, state: AppState?) -> AppState {
    let state = state ?? AppState()
    return AppState(count: state.count + 1)
  }
}

// ストアオブジェクトの生成
let mainStore = Store<AppState>(
  reducer: AppReducer(),
  state: nil
)

ViewController で Rx化したステートを subscribe(購読)する場合は以下のようになります。

import UIKit
import RxSwift
import ReSwift

class ViewController: UIViewController {

  let disposeBag = DisposeBag()
    
  let rxStore = RxStore<AppState>(store: mainStore)

  override func viewDidLoad() {
    super.viewDidLoad()
    rxStore.state.asObservable()
      .subscribe(onNext: { state in
        // 更新された時の処理
        print(state.count)
      }).disposed(by: disposeBag)
  }
}
// ディスパッチ
mainStore.dispatch(AppAction.incAction)
mainStore.dispatch(AppAction.incAction)
mainStore.dispatch(AppAction.incAction)
mainStore.dispatch(AppAction.incAction)

UI部品とバインド

Rx化されて入れば UI 部品と簡単にバインドできます。

class ViewController: UIViewController {

  let disposeBag = DisposeBag()
  @IBOutlet weak var button: UIButton!

  override func viewDidLoad() {
    super.viewDidLoad()
    // カウントが3を越えたらtrueにする
    let appStateValid = rxStore.state.asObservable()
      .map { state -> Bool in
        return state.count > 3
      }.shareReplayLatestWhileConnected()
    // ボタンのEnabledとバインドする
    appStateValid
      .bind(to: button.rx.isEnabled)
      .addDisposableTo(disposeBag)
  }
}

参考記事

enum駆使してKeychainをUserDefaultsっぽく使う

以前、こちらの記事で enum(列挙型)を使って UserDefaults のキー名を管理する方法について紹介しました。この方法を応用(ってほど大袈裟なもんじゃないけど)して Keychain にセキュアなデータを保存してみたいと思います。

Keychainって何?

API アクセスに使う認証用のトークン文字列なんかは UserDefaults じゃなくてもっとセキュアなところに保存する必要があります。そんなときに使うのが Keychain です。詳しい説明は以下の記事が参考になると思いますので以下をご一読ください

Keychainに文字列を保存する

enum に Keychain 用のキー名を列挙してデータ保存と取り出しのためのメソッドを追加します。

import Foundation
import Security

public enum Keychain: String {
  // キー名    
  case accessToken = "accessToken"
  case password = "password"

  // データの保存
  public func set(_ value: String) {
    let query: [String: AnyObject] = [
      kSecClass as String: kSecClassGenericPassword,
      kSecAttrAccount as String: self.rawValue as AnyObject,
      kSecValueData as String: value.data(using: .utf8)! as AnyObject
    ]
    SecItemDelete(query as CFDictionary)
    // エラー処理サボったのでこのへんはよしなに直してください
    SecItemAdd(query as CFDictionary, nil)
  }
  // データの取り出し
  public func value() -> String? {
    let query: [String: AnyObject] = [
      kSecClass as String: kSecClassGenericPassword,
      kSecAttrAccount as String: self.rawValue as AnyObject,
      kSecReturnData as String: kCFBooleanTrue,
      kSecMatchLimit as String: kSecMatchLimitOne
    ]
    var result: AnyObject?
    let status = SecItemCopyMatching(query as CFDictionary, &result)   
    guard status == noErr else {
      return nil
    }
    guard let data = result as? Data else {
      return nil
    }
    return String(data: data, encoding: .utf8)
  }
}

使い方

使い方は以下のようになります。

// データの保存
Keychain.accessToken.set("xxxxxx")
// データの取り出し
if let value = Keychain.accessToken.value() {
  print(value) // xxxxxxと出力される
}

ユニットテスト時の注意点

こちらのコードを XCTest でテストする時は Host Application を指定するのを忘れないでください。 f:id:glass-_-onion:20170201151902p:plain Host Application ってなんだそれって方は以下の記事を参考にしてください

参考記事

関連記事

Realmを前提としたリポジトリパターンの実装

いろんなところで Realm が良いと聞くようになり最近やっと Realm を使い始めました。実際 Realm を使ってみると予想していたよりも使い勝手がよく Core Data には戻れそうにないです…。ソースコードは Swift3 に対応しています。

リポジトリパターンの実装

Core Data に比べてお作法的なものが少ないとはいえ、いくつかのお決まりのパターンがあるので DDD のリポジトリパターンを適用してみました。クリーンアーキテクチャをまったく意識してないお気軽実装です。

import Foundation
import RealmSwift

class Repository<DomainType: Object> {
    
  var realm: Realm
    
  init() {
    self.realm = try! Realm()
  }
  // PK検索
  func find(primaryKey: String) -> DomainType? {
    return realm.objects(DomainType.self).filter("id == %@", primaryKey).first
  }
  // 全部取ってくる
  func findAll() -> [DomainType] {
    return realm.objects(DomainType.self).map({$0})
  }
  // 条件指定
  func find(predicate: NSPredicate) -> Results<DomainType> {
    return realm.objects(DomainType.self).filter(predicate)
  }
  // データ追加と更新
  func add(domains: [DomainType]) {
    try! realm.write {
      realm.add(domains, update: true)
    }
  }
  // データ削除
  func delete(domains: [DomainType]) {
    try! realm.write {
      realm.delete(domains)
    }
  }
  // トランザクション
  func transaction(_ transactionBlock: () -> Void) {
    try! realm.write {
      transactionBlock()
    }
  }
  // スレッドをまたいだオブジェクトの解決
  func resolve<Confined: ThreadConfined>(_ reference: ThreadSafeReference<Confined>) -> Confined? {
    return self.realm.resolve(reference)
  }
}

一つのリポジトリオブジェクトが一つの Realm オブジェクトを保持するようにしました。リポジトリオブジェクトをスレッドをまたいで使わない想定です。

リポジトリクラスの使い方

以下の Person クラスを例にリポジトリクラスの使い方をみてみましょう。

class Person: Object {
  dynamic var id = UUID().uuidString
  dynamic var name = ""
  // プライマリーキーを設定
  override static func primaryKey() -> String? {
    return "id"
  }
}

Person オブジェクトの生成から削除まで一通りやってみます。

// パーソンオブジェクト生成
let glassonion = Person()
glassonion.name = "glassonion"
let jude = Person()
jude.name = "jude"
// パーソンリポジトリ生成
let repo = Repository<Person>()
// データ追加
repo.add(domains: [glassonion, jude])
// 全件取得
let allPeople = repo.findAll()
// 条件を指定して取得
let predicate = NSPredicate(format: "name CONTAINS %@", "gl")
let people = repo.find(predicate: predicate)
// データの更新
repo.transaction {
  allPeople[0].name = "hoge"
}
// 削除
repo.delete(domains: allPeople)

スレッドをまたいだモデルオブジェクトのあつかいかた

スレッドをまたいでデータの更新をする場合は以下のようにします。

let repo = Repository<Person>()
let person = repo.findAll().first!
// スレッドセーフなオブジェクトに変換
let personRef = ThreadSafeReference(to: person)
// スレッド生成
DispatchQueue(label: "io.realm.RealmTasks.bg").async {
  // 新たにリポジトリ生成
  let repo = Repository<Person>()
  // スレッドの違うリポジトリと関連付ける
  if let person = repo.resolve(personRef)!
  // データ更新
  repo.transaction {
    person.name = "Michelle"
  }
}

参考記事

参考書籍