いものやま。

雑多な知識の寄せ集め

越谷市の花田苑に行ってきた。

埼玉県越谷市にある日本庭園、花田苑

すごく地元なんだけど、自分も最近まで存在を知らなくて、この間、初めて行った。

桜がすごくキレイだったので、ちょっと紹介。

f:id:yamaimo0625:20160409023856p:plain

入り口

入り口からもう見事な桜。

f:id:yamaimo0625:20160409025043p:plain

この桜を眺めながら進むと門があるので、そこで入園料を100円払えば、中に入れる。

園内の様子

入るとまず目に入るのが、大きな池。

f:id:yamaimo0625:20160409025536p:plain

ちなみに、奥に見えるのは能楽堂

日本庭園と銘打っているけど、広くて見晴らしがいいので、風情のある公園という方があってるかもしれない。
侘び寂びという感じはあまりなく、人工的に整えられている感も否めない・・・のだけど、のどかな空気はなかなかいい。

ちょうど桜が見ごろで、キレイに咲いていた。

f:id:yamaimo0625:20160409025948p:plain

f:id:yamaimo0625:20160409030014p:plain

f:id:yamaimo0625:20160409030101p:plain

f:id:yamaimo0625:20160409030127p:plain

f:id:yamaimo0625:20160409030138p:plain

桜の足元には、キレイな水仙も。

f:id:yamaimo0625:20160409030243p:plain

池の端の方には、舟もあったり。
まぁ、飾りのようなものだと思うけどw

f:id:yamaimo0625:20160409030548p:plain

この船着き場の近くには茶室もあって、定期的にお茶会も開かれてるみたい。

他にも、藤棚があって、4月下旬〜5月上旬は藤も楽しめそうだった。

それ以外にも様々な草花が。

f:id:yamaimo0625:20160409031122p:plain

f:id:yamaimo0625:20160409031131p:plain

f:id:yamaimo0625:20160409031147p:plain

行ったときには終わっちゃってたけど、梅の樹もあったので、梅の頃もキレイだったかもしれない。

あと、カエデもあったり。

f:id:yamaimo0625:20160409031242p:plain

まだ若い緑だけど、秋にはキレイに紅葉しそう。

一角は小高い丘になっていて、ちょっとした趣向が凝らされてた。

f:id:yamaimo0625:20160409031905p:plain

f:id:yamaimo0625:20160409031919p:plain

岩が並んでいる風景は、去年の冬に行った松尾大社の上古の庭を少し思い出したり。
規模は違うけどw

丘の上では水が湧き出していて、流れを作っているのもまた面白い。

f:id:yamaimo0625:20160409032422p:plain

まぁ、わざわざ観光しに来るようなところかというと、そんな感じではないのだけど、四季折々でいろんな草花が楽しめるように管理されているようなので、天気のいい日にふらっと立ち寄って散歩をするにはいい場所だと思う。

アクセス

さて、アクセスだけど、駅からけっこう離れているので、遠方からだと少しアクセスしづらいかもしれない。
一応、駐車場があるので、車で行くことは可能。
公共交通機関を使うなら、武蔵野線南越谷駅(もしくは東武伊勢崎線新越谷駅)から朝日バスで市立病院方面に向かい、花田苑前で降りることになる。

あと、もうちょっと足をのばすと、キャンベルタウン野鳥の森という施設もある。
いろんな鳥を見ることが出来るみたいなので、ついでに行ってみるのもありかも。

今日はここまで!

強化学習用のニューラルネットワークをSwiftで書いてみた。(その7)

昨日はValueNetworkの保存とロードの実装をした。
(ただ、いろいろ問題があったので、後で修正する予定)

これで実際に学習をするために、今日は○×ゲームをSwiftで実装する。

Markクラス

まずはマークを表すMarkクラスから。
enumで実装するのも一つの手だけど(というか、その方がSwiftっぽくはある)、今回はJavaenumに実装した。
(Swiftのenumだと、switchを書くのが面倒)

//==============================
// TicTacToe
//------------------------------
// Mark.swift
//==============================

import Foundation

class Mark: CustomStringConvertible, Hashable {
  static let Empty = Mark(mark: ".", value: 0.0, hash: 0)
  static let Maru = Mark(mark: "o", value: 1.0, hash: 1)
  static let Batsu = Mark(mark: "x", value: -1.0, hash: 2)
  
  let mark: String
  let value: Double
  let hash: Int
  
  private init(mark: String, value: Double, hash: Int) {
    self.mark = mark
    self.value = value
    self.hash = hash
  }
  
  var description: String {
    return self.mark
  }
  
  var hashValue: Int {
    return self.hash
  }
  
  var isEmpty: Bool {
    return self === Mark.Empty
  }
  
  var opponent: Mark {
    if self === Mark.Maru {
      return Mark.Batsu
    } else if self === Mark.Batsu {
      return Mark.Maru
    } else {
      return Mark.Empty
    }
  }
}

func ==(left: Mark, right: Mark) -> Bool {
  return left === right
}

Stateクラス

次は状態を表すStateクラス。

//==============================
// TicTacToe
//------------------------------
// State.swift
//==============================

import Foundation

class State: CustomStringConvertible {
  private let state: [Mark]
  
  convenience init() {
    let state = Array(count: 9, repeatedValue: Mark.Empty)
    self.init(state: state)
  }
  
  private init(state: [Mark]) {
    self.state = state
  }
  
  var description: String {
    return (0..<3).map {
      [unowned self] (row: Int) in
      let from = 3 * row
      let to = 3 * (row + 1)
      return self.state[from..<to].map{$0.mark}.joinWithSeparator("")
    }.joinWithSeparator("\n")
  }
  
  subscript(index: Int) -> Mark {
    return self.state[index]
  }
  
  func set(mark: Mark, atIndex index: Int) -> State {
    var newState = Array(self.state)
    newState[index] = mark
    return State(state: newState)
  }
  
  func toVector() -> Vector {
    return Vector.fromArray(self.state.map{$0.value})
  }
  
  func validActions() -> [Int] {
    return (0..<9).filter {
      [unowned self] (index: Int) in
      self.state[index].isEmpty
    }
  }
  
  func win(mark: Mark) -> Bool {
    let lines = [
      (0, 1, 2), (3, 4, 5), (6, 7, 8),
      (0, 3, 6), (1, 4, 7), (2, 5, 8),
      (0, 4, 8), (2, 4, 6)]
    for (i, j, k) in lines {
      if (self.state[i] == mark) &&
         (self.state[j] == mark) &&
         (self.state[k] == mark) {
        return true
      }
    }
    return false
  }
  
  var isDraw: Bool {
    return self.validActions().isEmpty && (!self.win(Mark.Maru)) && (!self.win(Mark.Batsu))
  }
  
  var isEnd: Bool {
    return self.win(Mark.Maru) || self.win(Mark.Batsu) || self.validActions().isEmpty
  }
}

Playerプロトコル

そして、プレイヤーを表すPlayerプトロコル。

//==============================
// TicTacToe
//------------------------------
// Player.swift
//==============================

import Foundation

protocol Player {
  var mark: Mark { get }
  var isLearning: Bool { get set }
  func selectIndex(state: State) -> Int
  func learn(reward: Double)
}

HumanPlayerクラス

人間プレイヤーを表すHumanPlayerクラス。

//==============================
// TicTacToe
//------------------------------
// HumanPlayer.swift
//==============================

import Foundation

class HumanPlayer: Player {
  let mark: Mark
  
  var isLearning: Bool {
    get { return false }
    set { /* ignore */ }
  }
  
  init(mark: Mark) {
    self.mark = mark
  }
  
  func selectIndex(state: State) -> Int {
    let stdin = NSFileHandle.fileHandleWithStandardInput()
    
    print("<player: \(self.mark)>")
    let actions = state.validActions()
    while true {
      print("select index [\(actions.map{$0.description}.joinWithSeparator(","))]")
      let input = stdin.readString()
      if let selectedIndex = Int(input) {
        if actions.indexOf(selectedIndex) != nil {
          return selectedIndex
        } else {
          print("invalid number.")
        }
      } else {
        print("invalid input.")
      }
    }
  }
  
  func learn(reward: Double) {
    // do nothing
  }
}

Gameクラス

最後に、ゲームを行うためのGameクラス。

//==============================
// TicTacToe
//------------------------------
// Game.swift
//==============================

import Foundation

class Game {
  private class func verboseOutput(description: String) {
    print(description)
  }
  
  private class func emptyOutput(description: String) {
    // do nothing
  }
  
  let players: [Mark: Player]
  
  init(maruPlayer: Player, batsuPlayer: Player) {
    self.players = [
      Mark.Maru: maruPlayer,
      Mark.Batsu: batsuPlayer,
    ]
  }
  
  func start(verbose: Bool = false) -> Mark {
    let output = verbose ? Game.verboseOutput : Game.emptyOutput
    
    var state = State()
    var currentPlayerMark = Mark.Maru
    var winner = Mark.Empty
    
    while true {
      let currentPlayer = self.players[currentPlayerMark]!
      
      output(state.description)
      let index = currentPlayer.selectIndex(state)
      output("player \(currentPlayerMark) selected \(index).")
      
      state = state.set(currentPlayerMark, atIndex: index)
      currentPlayer.learn(0.0)
      
      if state.win(currentPlayerMark) {
        winner = currentPlayerMark
        currentPlayer.learn(1.0)
        self.players[currentPlayerMark.opponent]!.learn(-1.0)
        output(state.description)
        output("player \(currentPlayerMark) win.")
        break
      } else if state.isDraw {
        for (_, player) in self.players {
          player.learn(0.0)
        }
        output(state.description)
        output("draw.")
        break
      } else {
        currentPlayerMark = currentPlayerMark.opponent
      }
    }
    
    return winner
  }
}

ちょっと説明が必要かもしれないところは、冗長出力の切り替え。
冗長出力を行うクラスメソッドと行わないクラスメソッドを用意しておいて、関数ポインタの指し先を切り替えることで、冗長出力を行うかどうかを切り替えている。

動作確認

とりあえずこれで人同士でプレイできるようにしたコードが以下:

//==============================
// TicTacToe
//------------------------------
// main.swift
//==============================

import Foundation

// Human v.s. Human

var maruPlayer: Player = HumanPlayer(mark: Mark.Maru)
var batsuPlayer: Player = HumanPlayer(mark: Mark.Batsu)
var game = Game(maruPlayer: maruPlayer, batsuPlayer: batsuPlayer)
game.start(true)

この実行例は、以下:

...
...
...
<player: o>
select index [0,1,2,3,4,5,6,7,8]
4
player o selected 4.
...
.o.
...
<player: x>
select index [0,1,2,3,5,6,7,8]
0
player x selected 0.
x..
.o.
...
<player: o>
select index [1,2,3,5,6,7,8]
8
player o selected 8.
x..
.o.
..o
<player: x>
select index [1,2,3,5,6,7]
2
player x selected 2.
x.x
.o.
..o
<player: o>
select index [1,3,5,6,7]
1
player o selected 1.
xox
.o.
..o
<player: x>
select index [3,5,6,7]
7
player x selected 7.
xox
.o.
.xo
<player: o>
select index [3,5,6]
3
player o selected 3.
xox
oo.
.xo
<player: x>
select index [5,6]
5
player x selected 5.
xox
oox
.xo
<player: o>
select index [6]
6
player o selected 6.
xox
oox
oxo
draw.

今日はここまで!

強化学習用のニューラルネットワークをSwiftで書いてみた。(その6)

昨日は強化学習用のHMEの実装を行った。

今日はそれらのデータの保存とロードが出来るようにする。

(2016-04-08追記:いろいろ問題があったので、大幅に修正する予定)

NSKeyedArchiver、NSKeyedUnarchiver

データをファイルに保存し、ロードする一つの方法は、NSDictionary#writeToFile()NSDictionary(contentsOfFile:)を利用する方法。

ただ、ValueHMEのような再帰的な構造を持っていると、保存するデータをDictionaryの形に変換するのは、ちょっと面倒。
(ただ、今から考えると、そうした方がよかったかも・・・)

そこで、NSKeyedArchiverとNSKeyedUnarchiverを使うことにした。
これらを使うと、NSCodingプロトコルに準拠したオブジェクトを、オブジェクト間の構造を保ったまま比較的簡単に保存することが出来る。

例えば、データをファイルに保存するには、次のようにする:

NSKeyedArchiver.archiveRootObject(保存したいオブジェクト, toFile: ファイル名)

逆に、データをファイルからロードするには、次のようにする:

let ロードされたオブジェクト = NSKeyedUnarchiver.unarchiveObjectWithFile(ファイル名) as! オブジェクトのクラス

NSCodingプロトコル

NSKeyedArchiverとNSKeyedUnarchiverでデータの保存とロードを出来るようにするためには、NSCodingプロトコルに準拠する必要がある。

NSCodingプロトコルでは、次の2つを実装する必要がある:

  • init(coder aDecoder: NSCoder)
    オブジェクトのデコード方法を定義する
  • func encodeWithCoder(aCoder: NSCoder)
    オブジェクトのエンコード方法を定義する

NSCoderプロトコルというのはNSKeyedArchiverとNSKeyedUnarchiverのいずれも準拠しているプロトコルで、基本的なデータのエンコード、デコードを行うためのメソッドが用意されている。
なので、これを使ってプロパティの保存をしたり、あるいは、プロパティの復元をしたりする。

ValueNetworkプロトコル、Weightプロトコルの修正

まず、保存とロードの対象となるValueNetworkプロトコルとWeightプロトコルを、NSCodingプロトコルを継承するように修正:

//==============================
// ValueNetwork
//------------------------------
// ValueNetwork.swift
//==============================

import Foundation

protocol ValueNetwork: NSCoding {
  // 省略
}
//==============================
// ValueNetwork
//------------------------------
// Weight.swift
//==============================

import Foundation

protocol Weight: NSCoding {
  // 省略
}

// 省略

ValueNNクラス、ValueNN.NNWeightクラスの修正

ValueNNクラス、ValueNN.NNWeightクラスを、NSCodingに準拠させるために、次のように修正:

//==============================
// ValueNetwork
//------------------------------
// ValueNN.swift
//==============================

import Foundation

@objc class ValueNN: NSObject, ValueNetwork {
  @objc class NNWeight: NSObject, Weight {
    private static let hiddenLayerWeightKey = "hiddenLayerWeight"
    private static let hiddenLayerBiasKey = "hiddenLayerBias"
    private static let outputLayerWeightKey = "outputLayerWeight"
    private static let outputLayerBiasKey = "outputLayerBias"
    
    // 省略
    
    private init(hiddenLayerWeight: Matrix,
                 hiddenLayerBias: Vector,
                 outputLayerWeight: Vector,
                 outputLayerBias: Double) {
      // 省略
      super.init()
    }
    
    convenience required init(coder aDecoder: NSCoder) {
      let hiddenLayerWeightBuffer = aDecoder.decodeObjectForKey(NNWeight.hiddenLayerWeightKey) as! [[Double]]
      let hiddenLayerBiasBuffer = aDecoder.decodeObjectForKey(NNWeight.hiddenLayerBiasKey) as! [Double]
      let outputLayerWeightBuffer = aDecoder.decodeObjectForKey(NNWeight.outputLayerWeightKey) as! [Double]
      let outputLayerBias = aDecoder.decodeDoubleForKey(NNWeight.outputLayerBiasKey)
      
      let hiddenLayerWeight = Matrix.fromArray(hiddenLayerWeightBuffer)
      let hiddenLayerBias = Vector.fromArray(hiddenLayerBiasBuffer)
      let outputLayerWeight = Vector.fromArray(outputLayerWeightBuffer)
      
      self.init(hiddenLayerWeight: hiddenLayerWeight,
                hiddenLayerBias: hiddenLayerBias,
                outputLayerWeight: outputLayerWeight,
                outputLayerBias: outputLayerBias)
    }
    
    func encodeWithCoder(aCoder: NSCoder) {
      let hiddenLayerWeightBuffer: [[Double]] = self.hiddenLayerWeight.toArray()
      let hiddenLayerBiasBuffer: [Double] = self.hiddenLayerBias.toArray()
      let outputLayerWeightBuffer: [Double] = self.outputLayerWeight.toArray()
      
      aCoder.encodeObject(hiddenLayerWeightBuffer, forKey: NNWeight.hiddenLayerWeightKey)
      aCoder.encodeObject(hiddenLayerBiasBuffer, forKey: NNWeight.hiddenLayerBiasKey)
      aCoder.encodeObject(outputLayerWeightBuffer, forKey: NNWeight.outputLayerWeightKey)
      aCoder.encodeDouble(self.outputLayerBias, forKey: NNWeight.outputLayerBiasKey)
    }
    
    // 省略
  }
  
  private static let outputMinKey = "outputMin"
  private static let outputMaxKey = "outputMax"
  private static let weightKey = "weight"
  
  // 省略
  
  init(inputSize: Int, hiddenUnitSize: Int, outputMin: Double, outputMax: Double) {
    // 省略
    super.init()
  }
  
  required init(coder aDecoder: NSCoder) {
    self.outputMin = aDecoder.decodeDoubleForKey(ValueNN.outputMinKey)
    self.outputMax = aDecoder.decodeDoubleForKey(ValueNN.outputMaxKey)
    self.weight = aDecoder.decodeObjectForKey(ValueNN.weightKey) as! NNWeight
    super.init()
  }
  
  func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeDouble(self.outputMin, forKey: ValueNN.outputMinKey)
    aCoder.encodeDouble(self.outputMax, forKey: ValueNN.outputMaxKey)
    aCoder.encodeObject(self.weight, forKey: ValueNN.weightKey)
  }
  
  // 省略
}

NSKeyedArchiverとNSKeyedUnarchiverで保存とロードが出来るようにするためには、Objective-Cのオブジェクトでないといけないので、@objcをつけ、NSObjectを継承するようにしてある。
そして、NSCodingプロトコルに準拠するようにメソッドを追加している。

GateNetworkクラス、GateNetwork.GateWeightクラスの修正

同様に、GateNetworkクラス、GateNetwork.GateWeightクラスの修正。

//==============================
// ValueNetwork
//------------------------------
// GateNetwork.swift
//==============================

import Foundation
import Accelerate

@objc class GateNetwork: NSObject, NSCoding {
  @objc class GateWeight: NSObject, Weight {
    private static let weightKey = "weight"
    
    // 省略
    
    private init(weight: Matrix) {
      // 省略
      super.init()
    }
    
    convenience required init(coder aDecoder: NSCoder) {
      let weightBuffer = aDecoder.decodeObjectForKey(GateWeight.weightKey) as! [[Double]]
      let weight = Matrix.fromArray(weightBuffer)
      self.init(weight: weight)
    }
    
    func encodeWithCoder(aCoder: NSCoder) {
      let weightBuffer: [[Double]] = self.weight.toArray()
      aCoder.encodeObject(weightBuffer, forKey: GateWeight.weightKey)
    }
    
    // 省略
  }
  
  private static let weightKey = "weight"
  
  // 省略
  
  init(inputSize: Int, outputSize: Int) {
    // 省略
    super.init()
  }
  
  required init(coder aDecoder: NSCoder) {
    self.weight = aDecoder.decodeObjectForKey(GateNetwork.weightKey) as! GateWeight
    super.init()
  }
  
  func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeObject(self.weight, forKey: GateNetwork.weightKey)
  }
  
  // 省略
}

ValueHMEクラス、ValueHME.HMEWeightクラスの修正

最後にValueHMEクラス、ValueHME.HMEWeightクラスの修正・・・なんだけど、ここでちょっと誤算が。
とりあえず、修正後のコードは以下:

//==============================
// ValueNetwork
//------------------------------
// ValueHME.swift
//==============================

import Foundation

@objc class ValueHME: NSObject, ValueNetwork {
  @objc class HMEWeight: NSObject, Weight {
    private static let expertWeightKey = "expertWeight"
    private static let gateWeightKey = "gateWeight"
    
    // 省略
    
    private init(expertWeight: [Weight], gateWeight: Weight) {
      // 省略
      super.init()
    }
    
    required init(coder aDecoder: NSCoder) {
      // workaround: protocol 'Weight' is not for Objective-C
      let expertWeightData = aDecoder.decodeObjectForKey(HMEWeight.expertWeightKey) as! [NSData]
      self.expertWeight = expertWeightData.map {
        (weightData: NSData) in
        return NSKeyedUnarchiver.unarchiveObjectWithData(weightData) as! Weight
      }
      self.gateWeight = aDecoder.decodeObjectForKey(HMEWeight.gateWeightKey) as! Weight
      super.init()
    }
    
    func encodeWithCoder(aCoder: NSCoder) {
      // workaround: protocol 'Weight' is not for Objective-C
      let expertWeightData: [NSData] = self.expertWeight.map {
        (weight: Weight) in
        if let nnWeight = weight as? ValueNN.NNWeight {
          return NSKeyedArchiver.archivedDataWithRootObject(nnWeight)
        } else if let hmeWeight = weight as? ValueHME.HMEWeight {
          return NSKeyedArchiver.archivedDataWithRootObject(hmeWeight)
        } else {
          fatalError("not supperted.")
        }
      }
      aCoder.encodeObject(expertWeightData, forKey: HMEWeight.expertWeightKey)
      aCoder.encodeObject(self.gateWeight, forKey: HMEWeight.gateWeightKey)
    }
    
    // 省略
  }
  
  private static let expertsKey = "experts"
  private static let gateNetworkKey = "gateNetwork"
  
  // 省略
  
  init(inputSize: Int, experts: [ValueNetwork]) {
    // 省略
    super.init()
  }
  
  required init(coder aDecoder: NSCoder) {
    // workaround: protocol 'ValueNetwork' is not for Objective-C
    let expertsData = aDecoder.decodeObjectForKey(ValueHME.expertsKey) as! [NSData]
    self.experts = expertsData.map {
      (expertData: NSData) in
      return NSKeyedUnarchiver.unarchiveObjectWithData(expertData) as! ValueNetwork
    }
    self.gateNetwork = aDecoder.decodeObjectForKey(ValueHME.gateNetworkKey) as! GateNetwork
    super.init()
  }
  
  func encodeWithCoder(aCoder: NSCoder) {
    // workaround: protocol 'ValueNetwork' is not for Objective-C
    let expertsData: [NSData] = self.experts.map {
      (expert: ValueNetwork) in
      if let valueNN = expert as? ValueNN {
        return NSKeyedArchiver.archivedDataWithRootObject(valueNN)
      } else if let valueHME = expert as? ValueHME {
        return NSKeyedArchiver.archivedDataWithRootObject(valueHME)
      } else {
        fatalError("not supperted.")
      }
    }
    aCoder.encodeObject(expertsData, forKey: ValueHME.expertsKey)
    aCoder.encodeObject(self.gateNetwork, forKey: ValueHME.gateNetworkKey)
  }
  
  // 省略
}

最初は、次のように保存が出来ると思ってた:

@objc class ValueHME: NSObject, ValueNetwork {

  // 省略

  func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeObject(self.experts, forKey: ValueHME.expertsKey)
    aCoder.encodeObject(self.gateNetwork, forKey: ValueHME.gateNetworkKey)
  }

  // 省略
}

でも、これだとダメ。
というのも、ValueNetworkプロトコルObjective-Cプロトコルになっていないので、これだとNSKeyedArchiverでは保存できないから。

かといって、ValueNetworkプロトコルObjective-Cプロトコルになるように、次のように修正したとする:

@objc protocol ValueNetwork: class, NSCoding {
  // 省略
}

すると今度は、インタフェースにVectorクラスが出てくるので、VectorクラスがObjective-Cのクラスであることが要求されてくる。
けど、Vectorクラスは引数の型によるメソッドのオーバーロードを行っているので、Objective-Cのクラスには簡単には出来ない・・・

妥協策として、Objective-Cのクラスにした具体的なクラスにダウンキャストして、そのオブジェクトをNSKeyedArchiverを使ってNSDataに変換し、保存するとしている。
ただ、ValueHMEはエキスパートネットワークに依存しないように実装していたのに、これで依存関係が生じるようになってしまった・・・
SwiftがObjective-Cの資産に頼らない独立したクラスライブラリを持っていれば、こんな問題は起きないんだけど、まぁ仕方ない。

動作確認

動作確認として、ValueNN、GateNetwork、ValueHMEの動作確認のコードを以下のように修正した:

//==============================
// ValueNetwork
//------------------------------
// main.swift
//
// Test code for ValueNetwork
//==============================

import Foundation

// 省略

// ValueNN

// 省略

NSKeyedArchiver.archiveRootObject(valueNN, toFile: "ValueNN.dat")

let restoredValueNN = NSKeyedUnarchiver.unarchiveObjectWithFile("ValueNN.dat") as! ValueNetwork
for col in (0..<inputMatrix.col) {
  let input = inputMatrix.colVector(col)
  let original = valueNN.getValue(input)
  let restored = restoredValueNN.getValue(input)
  print ("input: \(input.transpose()), original: \(original), restored: \(restored)")
}

// GateNetwork

// 省略

NSKeyedArchiver.archiveRootObject(gateNetwork, toFile: "GateNetwork.dat")

let restoredGateNetwork = NSKeyedUnarchiver.unarchiveObjectWithFile("GateNetwork.dat") as! GateNetwork
for col in (0..<inputMatrix.col) {
  let input = inputMatrix.colVector(col)
  let originalGateOutput = gateNetwork.getValue(input)
  let restoredGateOutput = restoredGateNetwork.getValue(input)
  let original = originalGateOutput +* expertOutput
  let restored = restoredGateOutput +* expertOutput
  print ("input: \(input.transpose()), original: \(original), restored: \(restored)")
}

// ValueHME

// 省略

NSKeyedArchiver.archiveRootObject(valueHME, toFile: "ValueHME.dat")

let restoredValueHME = NSKeyedUnarchiver.unarchiveObjectWithFile("ValueHME.dat") as! ValueNetwork
for col in (0..<inputMatrix.col) {
  let input = inputMatrix.colVector(col)
  let original = valueHME.getValue(input)
  let restored = restoredValueHME.getValue(input)
  print ("input: \(input.transpose()), original: \(original), restored: \(restored)")
}

いずれも、一度オブジェクトをファイルに保存した後、それをロードし、保存前のオブジェクトとロード後のオブジェクトで同じ動作をすることを確認している。

実行例は以下:

----------
ValueNN
----------
// 省略
input: [1.0, 1.0, 1.0], original: 1.20646309393211, restored: 1.20646309393211
input: [1.0, 1.0, 0.0], original: 1.11205199619916, restored: 1.11205199619916
input: [1.0, 0.0, 1.0], original: 1.1206058447007, restored: 1.1206058447007
input: [0.0, 1.0, 1.0], original: 1.16939746775197, restored: 1.16939746775197
input: [1.0, 0.0, 0.0], original: 1.02277258252449, restored: 1.02277258252449
input: [0.0, 1.0, 0.0], original: 1.0788044921628, restored: 1.0788044921628
input: [0.0, 0.0, 1.0], original: 1.08393743462573, restored: 1.08393743462573
input: [0.0, 0.0, 0.0], original: 0.811094155139819, restored: 0.811094155139819
----------
GateNetwork
----------
// 省略
input: [1.0, 1.0, 1.0], original: 0.736714777148314, restored: 0.736714777148314
input: [1.0, 1.0, 0.0], original: 0.685940425590263, restored: 0.685940425590263
input: [1.0, 0.0, 1.0], original: 0.683478674694305, restored: 0.683478674694305
input: [0.0, 1.0, 1.0], original: 0.681627244276781, restored: 0.681627244276781
input: [1.0, 0.0, 0.0], original: 0.605472969776552, restored: 0.605472969776552
input: [0.0, 1.0, 0.0], original: 0.602887409981527, restored: 0.602887409981527
input: [0.0, 0.0, 1.0], original: 0.59937319511023, restored: 0.59937319511023
input: [0.0, 0.0, 0.0], original: 0.5, restored: 0.5
----------
ValueHME
----------
// 省略
input: [1.0, 1.0, 1.0], original: 2.13632052620362, restored: 2.13632052620362
input: [1.0, 1.0, 0.0], original: 1.89965029982235, restored: 1.89965029982235
input: [1.0, 0.0, 1.0], original: 1.93688731245699, restored: 1.93688731245699
input: [0.0, 1.0, 1.0], original: 1.77723520125908, restored: 1.77723520125908
input: [1.0, 0.0, 0.0], original: 1.68732099031037, restored: 1.68732099031037
input: [0.0, 1.0, 0.0], original: 1.57164125986244, restored: 1.57164125986244
input: [0.0, 0.0, 1.0], original: 1.57578328859702, restored: 1.57578328859702
input: [0.0, 0.0, 0.0], original: 1.35896039021037, restored: 1.35896039021037

今日はここまで!

強化学習用のニューラルネットワークをSwiftで書いてみた。(その5)

昨日は強化学習用のHMEの計算を行列で表現した。

今日はそれを使って実際に実装していく。

なお、Rubyでの実装は、以下を参照:

GateNetworkクラス

まずはゲートネットワークの実装。

//==============================
// ValueNetwork
//------------------------------
// GateNetwork.swift
//==============================

import Foundation
import Accelerate

class GateNetwork {
  class GateWeight: Weight {
    private let weight: Matrix
    
    private init(weight: Matrix) {
      self.weight = weight
    }
    
    func scale(scalar: Double) -> Weight {
      let weight = self.weight * scalar
      return GateWeight(weight: weight)
    }
    
    func add(other: Weight) -> Weight {
      let otherWeight = other as! GateWeight
      let weight = self.weight + otherWeight.weight
      return GateWeight(weight: weight)
    }
    
    func subtract(other: Weight) -> Weight {
      let otherWeight = other as! GateWeight
      let weight = self.weight - otherWeight.weight
      return GateWeight(weight: weight)
    }
  }
  
  private var weight: GateWeight
  
  init(inputSize: Int, outputSize: Int) {
    let weight = Matrix.filledWith(0.0, row: outputSize, col: inputSize)
    self.weight = GateWeight(weight: weight)
  }
  
  func getValue(input: Vector) -> Vector {
    let weightedInput = (self.weight.weight * input) as! Vector
    let maxValue: Double = weightedInput.toArray().maxElement()!
    let limitedWeightedInput = weightedInput - Vector.filledWith(maxValue, size: weightedInput.size)
    
    let plainOutput = limitedWeightedInput.map(exp)
    let output = plainOutput.normalizedVector(Int(LA_L1_NORM))
    
    return output
  }
  
  func getValueAndWeightGradient(input: Vector, expertOutput: Vector) -> (Vector, Weight) {
    let output = self.getValue(input)
    
    let outputMatrix = output *+ Vector.filledWith(1.0, size: output.size)
    let gradientMatrix = outputMatrix <*> (Matrix.identityMatrix(output.size) - outputMatrix)
    let weightGradientMatrix = ((gradientMatrix * expertOutput) as! Vector) *+ input
    let weightGradient = GateWeight(weight: weightGradientMatrix)
    
    return (output, weightGradient)
  }
  
  func getValue(input: Vector, withWeightDiff weightDiff: Weight, scale: Double) -> Vector {
    let newWeight = (self.weight + weightDiff * scale) as! GateWeight
    
    let weightedInput = (newWeight.weight * input) as! Vector
    let maxValue: Double = weightedInput.toArray().maxElement()!
    let limitedWeightedInput = weightedInput - Vector.filledWith(maxValue, size: weightedInput.size)
    
    let plainOutput = limitedWeightedInput.map(exp)
    let output = plainOutput.normalizedVector(Int(LA_L1_NORM))
    
    return output
  }
  
  func addWeight(weightDiff: Weight) {
    self.weight = (self.weight + weightDiff) as! GateWeight
  }
}

細かい説明はしないけど、やってる計算は強化学習用のニューラルネットワークをSwiftで書いてみた。(その4) - いものやま。に書いたとおり。

ValueHMEクラス

そして、強化学習用のHMEの実装。

//==============================
// ValueNetwork
//------------------------------
// ValueHME.swift
//==============================

import Foundation

class ValueHME: ValueNetwork {
  class HMEWeight: Weight {
    private let expertWeight: [Weight]
    private let gateWeight: Weight
    
    private init(expertWeight: [Weight], gateWeight: Weight) {
      self.expertWeight = expertWeight
      self.gateWeight = gateWeight
    }
    
    func scale(scalar: Double) -> Weight {
      let expertWeight = self.expertWeight.map {
        (weight: Weight) in
        return weight * scalar
      }
      let gateWeight = self.gateWeight * scalar
      return HMEWeight(expertWeight: expertWeight, gateWeight: gateWeight)
    }
    
    func add(other: Weight) -> Weight {
      let otherWeight = other as! HMEWeight
      let expertWeight = zip(self.expertWeight, otherWeight.expertWeight).map {
        (selfExpertWeight: Weight, otherExpertWeight: Weight) in
        return selfExpertWeight + otherExpertWeight
      }
      let gateWeight = self.gateWeight + otherWeight.gateWeight
      return HMEWeight(expertWeight: expertWeight, gateWeight: gateWeight)
    }
    
    func subtract(other: Weight) -> Weight {
      let otherWeight = other as! HMEWeight
      let expertWeight = zip(self.expertWeight, otherWeight.expertWeight).map {
        (selfExpertWeight: Weight, otherExpertWeight: Weight) in
        return selfExpertWeight - otherExpertWeight
      }
      let gateWeight = self.gateWeight - otherWeight.gateWeight
      return HMEWeight(expertWeight: expertWeight, gateWeight: gateWeight)
    }
  }
  
  class func create(inputSize: Int, outputMin: Double, outputMax: Double, structure: [AnyObject]) -> ValueHME {
    var experts: [ValueNetwork] = []
    for item in structure {
      if let hiddenUnitSize = item as? Int {
        let valueNN = ValueNN(inputSize: inputSize,
                              hiddenUnitSize: hiddenUnitSize,
                              outputMin: outputMin,
                              outputMax: outputMax)
        experts.append(valueNN)
      } else if let lowerStructure = item as? [AnyObject] {
        let lowerHME = ValueHME.create(inputSize,
                                       outputMin: outputMin,
                                       outputMax: outputMax,
                                       structure: lowerStructure)
        experts.append(lowerHME)
      }
    }
    return ValueHME(inputSize: inputSize, experts: experts)
  }
  
  private let experts: [ValueNetwork]
  private let gateNetwork: GateNetwork
  
  init(inputSize: Int, experts: [ValueNetwork]) {
    self.experts = experts
    self.gateNetwork = GateNetwork(inputSize: inputSize, outputSize: experts.count)
  }
  
  func getValue(input: Vector) -> Double {
    let expertOutputArray = self.experts.map {
      (expert: ValueNetwork) in
      return expert.getValue(input)
    }
    let expertOutput = Vector.fromArray(expertOutputArray)
    let gateOutput = self.gateNetwork.getValue(input)
    
    return gateOutput +* expertOutput
  }
  
  func getValueAndWeightGradient(input: Vector) -> (Double, Weight) {
    var expertOutputArray: [Double] = []
    var expertWeightGradientArray: [Weight] = []
    for expert in self.experts {
      let (output, weightGradient) = expert.getValueAndWeightGradient(input)
      expertOutputArray.append(output)
      expertWeightGradientArray.append(weightGradient)
    }
    let expertOutput = Vector.fromArray(expertOutputArray)
    
    let (gateOutput, gateWeightGradient) = self.gateNetwork.getValueAndWeightGradient(input, expertOutput: expertOutput)
    
    let expertWeightGradient = zip(gateOutput.toArray(), expertWeightGradientArray).map {
      (gateOutput: Double, expertWeightGradient: Weight) in
      return gateOutput * expertWeightGradient
    }
    
    let output = gateOutput +* expertOutput
    let weightGradient = HMEWeight(expertWeight: expertWeightGradient, gateWeight: gateWeightGradient)
    
    return (output, weightGradient)
  }
  
  func getValue(input: Vector, withWeightDiff weightDiff: Weight, scale: Double) -> Double {
    let hmeWeightDiff = weightDiff as! HMEWeight
    
    let expertOutputArray = zip(self.experts, hmeWeightDiff.expertWeight).map {
      (expert: ValueNetwork, expertWeightDiff: Weight) in
      return expert.getValue(input, withWeightDiff: expertWeightDiff, scale: scale)
    }
    let expertOutput = Vector.fromArray(expertOutputArray)
    let gateOutput = self.gateNetwork.getValue(input, withWeightDiff: hmeWeightDiff.gateWeight, scale: scale)
    
    return gateOutput +* expertOutput
  }
  
  func addWeight(weightDiff: Weight) {
    let hmeWeightDiff = weightDiff as! HMEWeight
    
    for (expert, expertWeightDiff) in zip(self.experts, hmeWeightDiff.expertWeight) {
      expert.addWeight(expertWeightDiff)
    }
    self.gateNetwork.addWeight(hmeWeightDiff.gateWeight)
  }
}

こちらも強化学習用のニューラルネットワークをSwiftで書いてみた。(その4) - いものやま。に書いたとおり。

動作確認

動作確認として、次のようなコードを書いた。
(ValueNNの動作確認のコードを修正し、GateNetwork、ValueHMEの動作確認を追加している)

//==============================
// ValueNetwork
//------------------------------
// main.swift
//
// Test code for ValueNetwork
//==============================

import Foundation

let inputMatrix = Matrix.fromArray([[1.0, 1.0, 1.0],
                                    [1.0, 1.0, 0.0],
                                    [1.0, 0.0, 1.0],
                                    [0.0, 1.0, 1.0],
                                    [1.0, 0.0, 0.0],
                                    [0.0, 1.0, 0.0],
                                    [0.0, 0.0, 1.0],
                                    [0.0, 0.0, 0.0]]).transpose()

// ValueNN

print("----------")
print("ValueNN")
print("----------")

let valueNN = ValueNN(inputSize: 3, hiddenUnitSize: 10, outputMin: -1.0, outputMax: 1.0)

for col in (0..<inputMatrix.col) {
  let input = inputMatrix.colVector(col)
  let (output, weightGradient) = valueNN.getValueAndWeightGradient(input)
  let outputWithWeightGradient = valueNN.getValue(input, withWeightDiff: weightGradient, scale: 1.0)
  var diff = outputWithWeightGradient - output
  
  var scale = 1.0
  var upperBound: Double! = nil
  var lowerBound: Double! = nil
  var last = 100
  for i in (0..<100) {
    if (diff < 0.0) || (1.1 < diff) {
      upperBound =  scale
      scale = (lowerBound == nil) ? scale / 2.0 : (upperBound + lowerBound) / 2.0
    } else if diff < 0.9 {
      lowerBound = scale
      scale = (upperBound == nil) ? scale * 2.0 : (upperBound + lowerBound) / 2.0
    } else {
      last = i
      break
    }
    
    let outputWithScaledWeightGradient = valueNN.getValue(input, withWeightDiff: weightGradient, scale: scale)
    diff = outputWithScaledWeightGradient - output
  }
  
  let outputWith01Scaled = valueNN.getValue(input, withWeightDiff: weightGradient, scale: 0.1 * scale)
  let diffWith01Scaled = outputWith01Scaled - output
  
  print("input: \(input.transpose()), output: \(output)")
  print("  scale: \(scale), iterations: \(last)")
  print("  diff (scaled): \(diff), diff (0.1*scaled): \(diffWith01Scaled)")
  
  let weightDiff = weightGradient * 0.1 * scale
  valueNN.addWeight(weightDiff)
  let newOutput = valueNN.getValue(input)
  let newDiff = newOutput - output
  print("  new output: \(newOutput), diff: \(newDiff)")
}

// GateNetwork

print("----------")
print("GateNetwork")
print("----------")

let gateNetwork = GateNetwork(inputSize: 3, outputSize: 2)
let expertOutput = Vector.fromArray([0.2, 0.8])

for col in (0..<inputMatrix.col) {
  let input = inputMatrix.colVector(col)
  let (gateOutput, weightGradient) = gateNetwork.getValueAndWeightGradient(input, expertOutput: expertOutput)
  let output = gateOutput +* expertOutput
  
  let gateOutputWithWeightGradient = gateNetwork.getValue(input, withWeightDiff: weightGradient, scale: 1.0)
  let outputWithWeightGradient = gateOutputWithWeightGradient +* expertOutput
  
  let diff = outputWithWeightGradient - output
  
  print("input: \(input.transpose()), output: \(output)")
  print("  with gradient: \(outputWithWeightGradient), diff: \(diff)")
  
  gateNetwork.addWeight(weightGradient)
  let newGateOutput = gateNetwork.getValue(input)
  let newOutput = newGateOutput +* expertOutput
  let newDiff = newOutput - output
  print("  new output: \(newOutput), diff: \(newDiff)")
}

// ValueHME

print("----------")
print("ValueHME")
print("----------")

let valueHME = ValueHME.create(3, outputMin: -1.0, outputMax: 1.0, structure: [10, 10])

for col in (0..<inputMatrix.col) {
  let input = inputMatrix.colVector(col)
  let (output, weightGradient) = valueHME.getValueAndWeightGradient(input)
  let outputWithWeightGradient = valueHME.getValue(input, withWeightDiff: weightGradient, scale: 1.0)
  var diff = outputWithWeightGradient - output
  
  var scale = 1.0
  var upperBound: Double! = nil
  var lowerBound: Double! = nil
  var last = 100
  for i in (0..<100) {
    if (diff < 0.0) || (1.1 < diff) {
      upperBound =  scale
      scale = (lowerBound == nil) ? scale / 2.0 : (upperBound + lowerBound) / 2.0
    } else if diff < 0.9 {
      lowerBound = scale
      scale = (upperBound == nil) ? scale * 2.0 : (upperBound + lowerBound) / 2.0
    } else {
      last = i
      break
    }
    
    let outputWithScaledWeightGradient = valueHME.getValue(input, withWeightDiff: weightGradient, scale: scale)
    diff = outputWithScaledWeightGradient - output
  }
  
  let outputWith01Scaled = valueHME.getValue(input, withWeightDiff: weightGradient, scale: 0.1 * scale)
  let diffWith01Scaled = outputWith01Scaled - output
  
  print("input: \(input.transpose()), output: \(output)")
  print("  scale: \(scale), iterations: \(last)")
  print("  diff (scaled): \(diff), diff (0.1*scaled): \(diffWith01Scaled)")
  
  let weightDiff = weightGradient * 0.1 * scale
  valueHME.addWeight(weightDiff)
  let newOutput = valueHME.getValue(input)
  let newDiff = newOutput - output
  print("  new output: \(newOutput), diff: \(newDiff)")
}

実行例は、以下:

----------
ValueNN
----------
input: [1.0, 1.0, 1.0], output: -0.183942077296883
  scale: 0.1875, iterations: 4
  diff (scaled): 1.01724963088052, diff (0.1*scaled): 0.105130155911477
  new output: -0.0788119213854061, diff: 0.105130155911477
input: [1.0, 1.0, 0.0], output: -0.0727086141640791
  scale: 0.25, iterations: 2
  diff (scaled): 1.09166909286381, diff (0.1*scaled): 0.129576278870883
  new output: 0.0568676647068041, diff: 0.129576278870883
input: [1.0, 0.0, 1.0], output: -0.383684591887409
  scale: 0.25, iterations: 2
  diff (scaled): 1.07005618759768, diff (0.1*scaled): 0.115689382429312
  new output: -0.267995209458097, diff: 0.115689382429312
input: [0.0, 1.0, 1.0], output: 0.501642422844154
  scale: 1.25, iterations: 3
  diff (scaled): 1.03458420400201, diff (0.1*scaled): 0.463696492593989
  new output: 0.965338915438143, diff: 0.463696492593989
input: [1.0, 0.0, 0.0], output: 0.0147913651058598
  scale: 0.5, iterations: 1
  diff (scaled): 1.09849345190749, diff (0.1*scaled): 0.224957607158544
  new output: 0.239748972264404, diff: 0.224957607158544
input: [0.0, 1.0, 0.0], output: 1.00349356627074
  scale: 18.0, iterations: 8
  diff (scaled): 1.04838464458158, diff (0.1*scaled): 0.0657863751854824
  new output: 1.06927994145622, diff: 0.0657863751854824
input: [0.0, 0.0, 1.0], output: 1.00445024397176
  scale: 18.0, iterations: 8
  diff (scaled): 1.02882994407428, diff (0.1*scaled): 0.070282826622567
  new output: 1.07473307059433, diff: 0.070282826622567
input: [0.0, 0.0, 0.0], output: 1.04718514605304
  scale: 20.0, iterations: 7
  diff (scaled): 0.965905785136957, diff (0.1*scaled): 0.0690312508752593
  new output: 1.1162163969283, diff: 0.0690312508752593
----------
GateNetwork
----------
input: [1.0, 1.0, 1.0], output: 0.5
  with gradient: 0.626569701575002, diff: 0.126569701575002
  new output: 0.626569701575002, diff: 0.126569701575002
input: [1.0, 1.0, 0.0], output: 0.587393783735477
  with gradient: 0.627638891939002, diff: 0.0402451082035251
  new output: 0.627638891939002, diff: 0.0402451082035251
input: [1.0, 0.0, 1.0], output: 0.60807465845349
  with gradient: 0.637937581551879, diff: 0.0298629230983883
  new output: 0.637937581551879, diff: 0.0298629230983883
input: [0.0, 1.0, 1.0], output: 0.623373727874239
  with gradient: 0.645955025290896, diff: 0.022581297416657
  new output: 0.645955025290896, diff: 0.022581297416657
input: [1.0, 0.0, 0.0], output: 0.583841706173601
  with gradient: 0.605472969776552, diff: 0.0216312636029506
  new output: 0.605472969776552, diff: 0.0216312636029506
input: [0.0, 1.0, 0.0], output: 0.580303255512027
  with gradient: 0.602887409981527, diff: 0.0225841544694998
  new output: 0.602887409981527, diff: 0.0225841544694998
input: [0.0, 0.0, 1.0], output: 0.575481724278274
  with gradient: 0.59937319511023, diff: 0.0238914708319556
  new output: 0.59937319511023, diff: 0.0238914708319556
input: [0.0, 0.0, 0.0], output: 0.5
  with gradient: 0.5, diff: 0.0
  new output: 0.5, diff: 0.0
----------
ValueHME
----------
input: [1.0, 1.0, 1.0], output: 0.583369090800088
  scale: 12.0, iterations: 5
  diff (scaled): 0.955143642897197, diff (0.1*scaled): 0.55711728368868
  new output: 1.14048637448877, diff: 0.55711728368868
input: [1.0, 1.0, 0.0], output: 1.17912976992144
  scale: 10.0, iterations: 6
  diff (scaled): 0.958244431157407, diff (0.1*scaled): 0.218163118461483
  new output: 1.39729288838292, diff: 0.218163118461483
input: [1.0, 0.0, 1.0], output: 1.26053893688435
  scale: 7.0, iterations: 5
  diff (scaled): 1.00825100197074, diff (0.1*scaled): 0.100641423159278
  new output: 1.36118036004363, diff: 0.100641423159278
input: [0.0, 1.0, 1.0], output: 1.25521721395406
  scale: 7.0, iterations: 5
  diff (scaled): 0.98711439450816, diff (0.1*scaled): 0.0774174523384112
  new output: 1.33263466629247, diff: 0.0774174523384112
input: [1.0, 0.0, 0.0], output: 1.37228160440216
  scale: 24.0, iterations: 6
  diff (scaled): 0.926736515167874, diff (0.1*scaled): 0.151453564687681
  new output: 1.52373516908985, diff: 0.151453564687681
input: [0.0, 1.0, 0.0], output: 1.42285096816873
  scale: 10.0, iterations: 6
  diff (scaled): 0.995109500116182, diff (0.1*scaled): 0.0862919554937307
  new output: 1.50914292366246, diff: 0.0862919554937307
input: [0.0, 0.0, 1.0], output: 1.33805920315436
  scale: 7.0, iterations: 5
  diff (scaled): 1.0092130994799, diff (0.1*scaled): 0.0818008367414018
  new output: 1.41986003989576, diff: 0.0818008367414018
input: [0.0, 0.0, 0.0], output: 1.33449511265373
  scale: 16.0, iterations: 4
  diff (scaled): 0.947467726275787, diff (0.1*scaled): 0.0741106840380314
  new output: 1.40860579669177, diff: 0.0741106840380314

これでValueNetworkの実装はOK、と言いたいところなんだけど、このままだとデータの保存や復帰が出来ないので、ちょっと困る。
なので、データの保存・復帰が出来るように修正していきたい。

今日はここまで!

強化学習用のニューラルネットワークをSwiftで書いてみた。(その4)

昨日は強化学習用のニューラルネットワークの実装を行った。

今日はHMEの実装を行うために、同様にHMEの計算を行列で表現していく。

強化学習用のHMEの計算

ここでは、HMEへの入力を  \boldsymbol{x} \in \mathbb{R}^{n}、出力を  y \in \mathbb{R} とする。
また、エキスパートネットワーク  i の出力を  y_i \in \mathbb{R}、パラメータを  \boldsymbol{\theta}_i とし、それに対応するゲートネットワークの出力を  g_i \in [0, 1]、重みを  \boldsymbol{w}_i \in \mathbb{R}^{n} とする。

行列で表現しない計算

ニューラルネットワークのときと同様に、まずは行列で表現しない計算から。

  1. エキスパートネットワークの出力  y_i とその出力のパラメータに関する勾配  \frac{\partial y_i}{\partial \boldsymbol{\theta}_i} を求める。
  2. ゲートネットワークの出力  g_i とHMEの出力の重みに関する勾配  \frac{\partial y}{\partial \boldsymbol{w}_i} を求める:
    1.  s_i = \boldsymbol{w}_i^{\mathrm{T}} \boldsymbol{x}
    2.  g_i = \frac{\mathrm{exp}(s_i)}{\sum_j \mathrm{exp}(s_j)}
    3.  \frac{\partial y}{\partial \boldsymbol{w}_i} = \sum_j y_j \cdot \frac{\partial g_j}{\partial s_i} \cdot \boldsymbol{x}
      ただし、 i = j ならば  \frac{\partial g_j}{\partial s_i} = g_j (1 - g_j) i \neq j ならば  \frac{\partial g_j}{\partial s_i} = - g_j^{2}
  3. HMEの出力  y を求める:
    1.  y = \sum_i g_i y_i
  4. HMEの出力のエキスパートネットワークのパラメータに関する勾配  \frac{\partial y}{\partial \boldsymbol{\theta}_i} を求める:
    1.  \frac{\partial y}{\partial \boldsymbol{\theta}_i} = g_i \frac{\partial y_i}{\partial \boldsymbol{\theta}_i}

行列で表現した計算

まず、エキスパートネットワークの各出力をまとめて、ベクトル  \boldsymbol{y} \in \mathbb{R}^{m}(ただし、 m はエキスパートネットワークの数)で表現することにする。

そして、ゲートネットワークの重みを  W \in \mathbb{R}^{m \times n} で次のように表すことにする:

 {
W = \left( \begin{array}{c}
\boldsymbol{w}_1 {}^{\mathrm{T}} \\
\vdots \\
\boldsymbol{w}_m {}^{\mathrm{T}}
\end{array} \right)
}

さらに、次のような行列  G \in \mathbb{R}^{m \times m} を考える:

 {
\begin{align}
G &= \left( \begin{array}{ccc}
\frac{\partial g_1}{\partial s_1} & \cdots & \frac{\partial g_m}{\partial s_1} \\
\vdots & \ddots & \vdots \\
\frac{\partial g_1}{\partial s_m} & \cdots & \frac{\partial g_m}{\partial s_m}
\end{array} \right) \\
&= \left( \begin{array}{ccc}
g_1 (1 - g_1) & \cdots & - g_m^2 \\
\vdots & \ddots & \vdots \\
- g_1^2 & \cdots & g_m (1 - g_m)
\end{array} \right) \\
&= (\boldsymbol{g} \otimes \boldsymbol{1}) \odot (I - \boldsymbol{g} \otimes \boldsymbol{1})
\end{align}
}

ただし、 I \in \mathbb{R}^{m \times m}単位行列 \boldsymbol{1} \in \mathbb{R}^{m} はすべての要素が1のベクトル、 \otimes はベクトルの直積(外積)、 \odotアダマール積(要素ごとの積)。

すると、上の計算は次のように書き直すことが出来る:

  1. エキスパートネットワークの出力  \boldsymbol{y} とその出力のパラメータに関する勾配  \frac{\partial y_i}{\partial \boldsymbol{\theta}_i} を求める。
  2. ゲートネットワークの出力  \boldsymbol{g} とHMEの出力の重みに関する勾配  \frac{\partial y}{\partial W} を求める:
    1.  \boldsymbol{s} = W \boldsymbol{x}
    2.  \boldsymbol{t} = \mathrm{exp}(\boldsymbol{s})
    3.  \boldsymbol{g} = \frac{1}{\|\boldsymbol{t}\|_1} \boldsymbol{t} (※L1ノルムで正規化)
    4.  \frac{\partial y}{\partial W} = (G \boldsymbol{y}) \otimes \boldsymbol{x}
  3. HMEの出力  y を求める:
    1.  y = \boldsymbol{g}^{\mathrm{T}} \boldsymbol{y}
  4. HMEの出力のエキスパートネットワークのパラメータに関する勾配  \frac{\partial y}{\partial \boldsymbol{\theta}_i} を求める:
    1.  \frac{\partial y}{\partial \boldsymbol{\theta}_i} = g_i \frac{\partial y_i}{\partial \boldsymbol{\theta}_i}

かなりトリッキーな感じもするけど、地道に追えば、元の計算と同等になっていることが分かると思う。

なお、途中、 \boldsymbol{t} をL1ノルムで正規化しているけど、これは指数関数の値域が  (0, \infty) であることから、 \| \boldsymbol{t} \|_1 = \sum_i \left|t_i\right| = \sum_i t_i = \sum_i \mathrm{exp}(s_i) となることを利用している。

あと、実際に実装する場合には、 \boldsymbol{s} の一番大きい値を見つけて、その値を  \boldsymbol{s} のすべての要素から一律に引く必要がある。
というのも、指数関数は急激に大きくなるので、下手するとNaNになってしまって計算が破綻することがあるから。
ソフトマックス関数は各要素から定数を引いても値は変わらないという性質があるので、一番大きい値を一律に引いておくことで、指数関数の値が  (0, 1] に収まるようになる。
強化学習とニューラルネットワークを組合せてみた。(その12) - いものやま。も参照)

今日はここまで!

強化学習用のニューラルネットワークをSwiftで書いてみた。(その3)

昨日は強化学習用のニューラルネットワークの計算を行列で表現した。

今日はそれを使って実際に実装していく。

なお、Swiftでの行列演算については、以下を参照:

ここで定義したMatrixクラス、Vectorクラスを使っていくことになる。

また、Rubyでの実装は以下を参照:
(行列計算を使ってないので、読むのがかなり大変だけど・・・)

ニューラルネットワークの仕様は、上記のRubyのものと同じにする。

ValueNetworkプロトコル

まず、強化学習関数近似に使うのがニューラルネットワークでもHMEでもいいように、インタフェースとなるプロトコルを定義する:

//==============================
// ValueNetwork
//------------------------------
// ValueNetwork.swift
//==============================

import Foundation

protocol ValueNetwork {
  func getValue(input: Vector) -> Double
  func getValueAndWeightGradient(input: Vector) -> (Double, Weight)
  func getValue(input: Vector, withWeightDiff weightDiff: Weight, scale: Double) -> Double
  func addWeight(weightDiff: Weight)
}

定義の中でWeightという型が使われてるけど、これは次に定義するプロトコルで、重みの内部構成を隠蔽するためのインタフェース。

Weightプロトコル

ということで、Weightプロトコルの定義:

//==============================
// ValueNetwork
//------------------------------
// Weight.swift
//==============================

import Foundation

protocol Weight {
  func scale(scalar: Double) -> Weight
  func add(other: Weight) -> Weight
  func subtract(other: Weight) -> Weight
}

func *(left: Weight, right: Double) -> Weight {
  return left.scale(right)
}

func *(left: Double, right: Weight) -> Weight {
  return right.scale(left)
}

func +(left: Weight, right: Weight) -> Weight {
  return left.add(right)
}

func -(left: Weight, right: Weight) -> Weight {
  return left.subtract(right)
}

重みに対して行いたいことは、足し算(と引き算)と掛け算なので、それらをインタフェースとして定義している。
また、記述が簡単になるように、演算子オーバーロードも行っている。

ValueNNクラス

そして、強化学習用のニューラルネットワークの実装。
ValueNNクラスとして実装した:

//==============================
// ValueNetwork
//------------------------------
// ValueNN.swift
//==============================

import Foundation

class ValueNN: ValueNetwork {
  class NNWeight: Weight {
    private let hiddenLayerWeight: Matrix
    private let hiddenLayerBias: Vector
    private let outputLayerWeight: Vector
    private let outputLayerBias: Double
    
    private init(hiddenLayerWeight: Matrix,
                 hiddenLayerBias: Vector,
                 outputLayerWeight: Vector,
                 outputLayerBias: Double) {
      self.hiddenLayerWeight = hiddenLayerWeight
      self.hiddenLayerBias = hiddenLayerBias
      self.outputLayerWeight = outputLayerWeight
      self.outputLayerBias = outputLayerBias
    }
    
    func scale(scalar: Double) -> Weight {
      let hiddenLayerWeight = self.hiddenLayerWeight * scalar
      let hiddenLayerBias = self.hiddenLayerBias * scalar
      let outputLayerWeight = self.outputLayerWeight * scalar
      let outputLayerBias = self.outputLayerBias * scalar
      return NNWeight(hiddenLayerWeight: hiddenLayerWeight,
                      hiddenLayerBias: hiddenLayerBias,
                      outputLayerWeight: outputLayerWeight,
                      outputLayerBias: outputLayerBias)
    }
    
    func add(other: Weight) -> Weight {
      let otherNNWeight = other as! NNWeight
      let hiddenLayerWeight = self.hiddenLayerWeight + otherNNWeight.hiddenLayerWeight
      let hiddenLayerBias = self.hiddenLayerBias + otherNNWeight.hiddenLayerBias
      let outputLayerWeight = self.outputLayerWeight + otherNNWeight.outputLayerWeight
      let outputLayerBias = self.outputLayerBias + otherNNWeight.outputLayerBias
      return NNWeight(hiddenLayerWeight: hiddenLayerWeight,
                      hiddenLayerBias: hiddenLayerBias,
                      outputLayerWeight: outputLayerWeight,
                      outputLayerBias: outputLayerBias)
    }
    
    func subtract(other: Weight) -> Weight {
      let otherNNWeight = other as! NNWeight
      let hiddenLayerWeight = self.hiddenLayerWeight - otherNNWeight.hiddenLayerWeight
      let hiddenLayerBias = self.hiddenLayerBias - otherNNWeight.hiddenLayerBias
      let outputLayerWeight = self.outputLayerWeight - otherNNWeight.outputLayerWeight
      let outputLayerBias = self.outputLayerBias - otherNNWeight.outputLayerBias
      return NNWeight(hiddenLayerWeight: hiddenLayerWeight,
                      hiddenLayerBias: hiddenLayerBias,
                      outputLayerWeight: outputLayerWeight,
                      outputLayerBias: outputLayerBias)
    }
  }
  
  private static let activationNormalGradient: Double = 1.0
  private static let activationLesserGradient: Double = 0.1
  
  private var weight: NNWeight
  let outputMin: Double
  let outputMax: Double
  
  init(inputSize: Int, hiddenUnitSize: Int, outputMin: Double, outputMax: Double) {
    self.outputMin = outputMin
    self.outputMax = outputMax
    
    let hiddenLayerWeightVariance = 1.0 / (Double(inputSize) + 1.0)
    let hiddenLayerWeightGenerator = NormalDistRandom.init(expected: 0.0, variance: hiddenLayerWeightVariance)
    
    let hiddenLayerWeightBuffer = (0..<hiddenUnitSize).map { _ in
      return (0..<inputSize).map { _ in
        return hiddenLayerWeightGenerator.getRandom()
      }
    }
    let hiddenLayerWeight = Matrix.fromArray(hiddenLayerWeightBuffer)
    
    let hiddenLayerBiasBuffer = (0..<hiddenUnitSize).map { _ in
      return hiddenLayerWeightGenerator.getRandom()
    }
    let hiddenLayerBias = Vector.fromArray(hiddenLayerBiasBuffer)
    
    let outputLayerWeightVariance = 1.0 / (Double(hiddenUnitSize) + 1.0)
    let outputLayerWeightGenerator = NormalDistRandom(expected: 0.0, variance: outputLayerWeightVariance)
    
    let outputLayerWeightBuffer = (0..<hiddenUnitSize).map { _ in
      return outputLayerWeightGenerator.getRandom()
    }
    let outputLayerWeight = Vector.fromArray(outputLayerWeightBuffer)
    
    let outputLayerBias = outputLayerWeightGenerator.getRandom()
    
    self.weight = NNWeight(hiddenLayerWeight: hiddenLayerWeight,
                           hiddenLayerBias: hiddenLayerBias,
                           outputLayerWeight: outputLayerWeight,
                           outputLayerBias: outputLayerBias)
  }
  
  func getValue(input: Vector) -> Double {
    let hiddenLayerWeightedInput = ((self.weight.hiddenLayerWeight * input) as! Vector) + self.weight.hiddenLayerBias
    let hiddenLayerOutput = hiddenLayerWeightedInput.map {
      [unowned self] (weightedInput: Double) in
      return self.hiddenLayerOutputForWeightedInput(weightedInput)
    }
    
    let outputLayerWeightedInput = self.weight.outputLayerWeight +* hiddenLayerOutput + self.weight.outputLayerBias
    let outputLayerOutput = self.outputLayerOutputForWeightedInput(outputLayerWeightedInput)

    return outputLayerOutput
  }
  
  func getValueAndWeightGradient(input: Vector) -> (Double, Weight) {
    let hiddenLayerWeightedInput = ((self.weight.hiddenLayerWeight * input) as! Vector) + self.weight.hiddenLayerBias
    let hiddenLayerOutput = hiddenLayerWeightedInput.map {
      [unowned self] (weightedInput: Double) in
      return self.hiddenLayerOutputForWeightedInput(weightedInput)
    }
    let hiddenLayerGradient = hiddenLayerWeightedInput.map {
      [unowned self] (weightedInput: Double) in
      return self.hiddenLayerGradientForWeightedInput(weightedInput)
    }
    
    let outputLayerWeightedInput = self.weight.outputLayerWeight +* hiddenLayerOutput + self.weight.outputLayerBias
    let outputLayerOutput = self.outputLayerOutputForWeightedInput(outputLayerWeightedInput)
    let outputLayerGradient = self.outputLayerGradientForWeightedInput(outputLayerWeightedInput)
    
    let outputLayerDelta = outputLayerGradient
    let hiddenLayerDelta = outputLayerDelta * self.weight.outputLayerWeight <*> hiddenLayerGradient
    
    let hiddenLayerWeightGradient = hiddenLayerDelta *+ input
    let hiddenLayerBiasGradient = hiddenLayerDelta
    let outputLayerWeightGradient = outputLayerDelta * hiddenLayerOutput
    let outputLayerBiasGradint = outputLayerDelta
    let weightGradient = NNWeight(hiddenLayerWeight: hiddenLayerWeightGradient,
                                  hiddenLayerBias: hiddenLayerBiasGradient,
                                  outputLayerWeight: outputLayerWeightGradient,
                                  outputLayerBias: outputLayerBiasGradint)
    
    return (outputLayerOutput, weightGradient)
  }
  
  func getValue(input: Vector, withWeightDiff weightDiff: Weight, scale: Double) -> Double {
    let newWeight = (self.weight + weightDiff * scale) as! NNWeight
    
    let hiddenLayerWeightedInput = ((newWeight.hiddenLayerWeight * input) as! Vector) + newWeight.hiddenLayerBias
    let hiddenLayerOutput = hiddenLayerWeightedInput.map {
      [unowned self] (weightedInput: Double) in
      return self.hiddenLayerOutputForWeightedInput(weightedInput)
    }
    
    let outputLayerWeightedInput = newWeight.outputLayerWeight +* hiddenLayerOutput + newWeight.outputLayerBias
    let outputLayerOutput = self.outputLayerOutputForWeightedInput(outputLayerWeightedInput)
    
    return outputLayerOutput
  }
  
  func addWeight(weightDiff: Weight) {
    self.weight = (self.weight + weightDiff) as! NNWeight
  }
  
  private func hiddenLayerOutputForWeightedInput(weightedInput: Double) -> Double {
    if weightedInput >= 0.0 {
      return ValueNN.activationNormalGradient * weightedInput
    } else {
      return ValueNN.activationLesserGradient * weightedInput
    }
  }
  
  private func hiddenLayerGradientForWeightedInput(weightedInput: Double) -> Double {
    if weightedInput >= 0.0 {
      return ValueNN.activationNormalGradient
    } else {
      return ValueNN.activationLesserGradient
    }
  }
  
  private func outputLayerOutputForWeightedInput(weightedInput: Double) -> Double {
    if weightedInput < self.outputMin {
      return (ValueNN.activationLesserGradient * weightedInput
                + (ValueNN.activationNormalGradient - ValueNN.activationLesserGradient) * self.outputMin)
    } else if weightedInput < self.outputMax {
      return ValueNN.activationNormalGradient * weightedInput
    } else {
      return (ValueNN.activationLesserGradient * weightedInput
                + (ValueNN.activationNormalGradient - ValueNN.activationLesserGradient) * self.outputMax)
    }
  }
  
  private func outputLayerGradientForWeightedInput(weightedInput: Double) -> Double {
    if (weightedInput < self.outputMin) || (self.outputMax < weightedInput) {
      return ValueNN.activationLesserGradient
    } else {
      return ValueNN.activationNormalGradient
    }
  }
}

細かく説明しないけど、強化学習用のニューラルネットワークをSwiftで書いてみた。(その2) - いものやま。に書いた行列計算をそのまま実装している。
やっている計算はRubyのものと一緒なんだけど、Matrixクラス、Vectorクラスを定義してあるので、(比較的)スッキリした実装になっている。

動作確認

動作確認として、Rubyのときと同様に、以下のコードを書いた:

//==============================
// ValueNetwork
//------------------------------
// main.swift
//
// Test code for ValueNetwork
//==============================

import Foundation

// ValueNN

let valueNN = ValueNN(inputSize: 3, hiddenUnitSize: 10, outputMin: -1.0, outputMax: 1.0)

let inputMatrix = Matrix.fromArray([[1.0, 1.0, 1.0],
                                    [1.0, 1.0, 0.0],
                                    [1.0, 0.0, 1.0],
                                    [0.0, 1.0, 1.0],
                                    [1.0, 0.0, 0.0],
                                    [0.0, 1.0, 0.0],
                                    [0.0, 0.0, 1.0]]).transpose()

for col in (0..<inputMatrix.col) {
  let input = inputMatrix.colVector(col)
  let (output, weightGradient) = valueNN.getValueAndWeightGradient(input)
  let outputWithWeightGradient = valueNN.getValue(input, withWeightDiff: weightGradient, scale: 1.0)
  var diff = outputWithWeightGradient - output
  
  var scale = 1.0
  var upperBound: Double! = nil
  var lowerBound: Double! = nil
  var last = 100
  for i in (0..<100) {
    if (diff < 0.0) || (1.1 < diff) {
      upperBound =  scale
      scale = (lowerBound == nil) ? scale / 2.0 : (upperBound + lowerBound) / 2.0
    } else if diff < 0.9 {
      lowerBound = scale
      scale = (upperBound == nil) ? scale * 2.0 : (upperBound + lowerBound) / 2.0
    } else {
      last = i
      break
    }
    
    let outputWithScaledWeightGradient = valueNN.getValue(input, withWeightDiff: weightGradient, scale: scale)
    diff = outputWithScaledWeightGradient - output
  }
  
  let outputWith01Scaled = valueNN.getValue(input, withWeightDiff: weightGradient, scale: 0.1 * scale)
  let diffWith01Scaled = outputWith01Scaled - output
  
  print("input: \(input.transpose()), output: \(output)")
  print("  scale: \(scale), iterations: \(last)")
  print("  diff (scaled): \(diff), diff (0.1*scaled): \(diffWith01Scaled)")
  
  let weightDiff = weightGradient * 0.1 * scale
  valueNN.addWeight(weightDiff)
  let newOutput = valueNN.getValue(input)
  let newDiff = newOutput - output
  print("  new output: \(newOutput), diff: \(newDiff)")
}

// 以下略

実行例は、以下:

----------
ValueNN
----------
input: [1.0, 1.0, 1.0], output: 0.131021133192784
  scale: 0.125, iterations: 3
  diff (scaled): 0.939252246852243, diff (0.1*scaled): 0.155481391989248
  new output: 0.286502525182032, diff: 0.155481391989248
input: [1.0, 1.0, 0.0], output: 0.363438002291251
  scale: 0.5, iterations: 1
  diff (scaled): 0.92473632645851, diff (0.1*scaled): 0.331184714810343
  new output: 0.694622717101594, diff: 0.331184714810343
input: [1.0, 0.0, 1.0], output: 0.0041779249095818
  scale: 0.125, iterations: 3
  diff (scaled): 1.03338785763323, diff (0.1*scaled): 0.13662619759527
  new output: 0.140804122504852, diff: 0.13662619759527
input: [0.0, 1.0, 1.0], output: 0.257684625662284
  scale: 0.5, iterations: 1
  diff (scaled): 0.992301758621281, diff (0.1*scaled): 0.335977958448971
  new output: 0.593662584111254, diff: 0.335977958448971
input: [1.0, 0.0, 0.0], output: 0.184531558718531
  scale: 0.5, iterations: 1
  diff (scaled): 1.01382568012975, diff (0.1*scaled): 0.27650775017151
  new output: 0.461039308890041, diff: 0.27650775017151
input: [0.0, 1.0, 0.0], output: 0.166668276265283
  scale: 0.5, iterations: 1
  diff (scaled): 0.974321711025463, diff (0.1*scaled): 0.217514514547388
  new output: 0.384182790812671, diff: 0.217514514547388
input: [0.0, 0.0, 1.0], output: 0.180236823017496
  scale: 0.5, iterations: 1
  diff (scaled): 0.945552060789391, diff (0.1*scaled): 0.200679893784258
  new output: 0.380916716801753, diff: 0.200679893784258
# 以下略

今日はここまで!

強化学習用のニューラルネットワークをSwiftで書いてみた。(その2)

昨日は乱数生成器の実装を行った。

今日は強化学習用のニューラルネットワークの計算を行列で表現する。

強化学習用のニューラルネットワークの計算

説明を簡単にするために、ここでは次のようなニューラルネットワークを考える:

  • 3層ニューラルネットワーク
  • 入力は  \boldsymbol{x} \in \mathbb{R}^{n}、出力は  y \in \mathbb{R}
  • 中間層のユニット数は  m
    • 各ユニットの重みは  \boldsymbol{w}_i^{(h)} \in \mathbb{R}^{n}、バイアスは  b_i^{(h)} \in \mathbb{R}
    • 活性化関数は  f^{(h)} : \mathbb{R} \rightarrow \mathbb{R}
  • 出力層のユニット数は1
    • ユニットの重みは  \boldsymbol{w}^{(o)} \in \mathbb{R}^{m}、バイアスは  b^{(o)} \in \mathbb{R}
    • 活性化関数は  f^{(o)} : \mathbb{R} \rightarrow \mathbb{R}

行列で表現しない計算

まず、おさらいとして、行列で表現していない計算を書いておく:

  1. 入力  \boldsymbol{x} から、中間層の出力  \boldsymbol{z} \in \mathbb{R}^{m} を求める:
    1.  u^{(h)}_i = \boldsymbol{w}_i^{(h)} {}^{\mathrm{T}} \boldsymbol{x} + b^{(h)}_i
    2.  z_i = f^{(h)}(u^{(h)}_i)
  2. 中間層の出力  \boldsymbol{z} から、出力層の出力  y を求める:
    1.  u^{(o)} = \boldsymbol{w}^{(o)} {}^{\mathrm{T}} \boldsymbol{z} + b^{(o)}
    2.  y = f^{(o)}(u^{(o)})
  3. 出力層のデルタ  \delta^{(o)} \in \mathbb{R} を求める:
    1.  \delta^{(o)} = {f^{(o)}}'(u^{(o)})
  4. 中間層のデルタ  \boldsymbol{\delta}^{(h)} \in \mathbb{R}^{m} を求める:
    1.  \delta^{(h)}_i = {f^{(h)}}'(u^{(h)}_i) \delta^{(o)} w^{(o)}_i
  5. 偏微分を求める:
    1.  \frac{\partial y}{\partial \boldsymbol{w}^{(h)}_i} = \delta^{(h)}_i \boldsymbol{x}
    2.  \frac{\partial y}{\partial b^{(h)}_i} = \delta^{(h)}_i
    3.  \frac{\partial y}{\partial \boldsymbol{w}^{(o)}} = \delta^{(o)} \boldsymbol{z}
    4.  \frac{\partial y}{\partial b^{(o)}} = \delta^{(o)}

行列で表現した計算

ここで、中間層の重みを  W^{(h)} \in \mathbb{R}^{m \times n} で次のように表すことにする:

 {
W^{(h)} = \left( \begin{array}{c}
\boldsymbol{w}^{(h)}_1 {}^{\mathrm{T}} \\
\vdots \\
\boldsymbol{w}^{(h)}_m {}^{\mathrm{T}}
\end{array} \right)
}

そして、表記をもう少しスマートにすると、上の計算は次のように書き直すことが出来る:

  1. 入力  \boldsymbol{x} から、中間層の出力  \boldsymbol{z} を求める:
    1.  \boldsymbol{u}^{(h)} = W^{(h)} \boldsymbol{x} + \boldsymbol{b}^{(h)}
    2.  \boldsymbol{z} = f^{(h)}(\boldsymbol{u}^{(h)})
  2. 中間層の出力  \boldsymbol{z} から、出力層の出力  y を求める:
    1.  u^{(o)} = \boldsymbol{w}^{(o)} {}^{\mathrm{T}} \boldsymbol{z} + b^{(o)}
    2.  y = f^{(o)}(u^{(o)})
  3. 出力層のデルタ  \delta^{(o)} \in \mathbb{R} を求める:
    1.  \delta^{(o)} = {f^{(o)}}'(u^{(o)})
  4. 中間層のデルタ  \boldsymbol{\delta}^{(h)} \in \mathbb{R}^{m} を求める:
    1.  \boldsymbol{\delta}^{(h)} = {f^{(h)}}'(\boldsymbol{u}^{(h)}) \odot \left(\delta^{(o)} \boldsymbol{w}^{(o)} \right)
  5. 偏微分を求める:
    1.  \frac{\partial y}{\partial W^{(h)}} = \boldsymbol{\delta}^{(h)} \otimes \boldsymbol{x}
    2.  \frac{\partial y}{\partial \boldsymbol{b}^{(h)}} = \boldsymbol{\delta}^{(h)}
    3.  \frac{\partial y}{\partial \boldsymbol{w}^{(o)}} = \delta^{(o)} \boldsymbol{z}
    4.  \frac{\partial y}{\partial b^{(o)}} = \delta^{(o)}

なお、表記法として、関数  f の引数にベクトル(や行列)が来ている場合、各要素に関数  f を適用したベクトル(や行列)を返すものとする。
また、 \odot は行列(ベクトルを含む)のアダマール積(要素ごとの積)、 \otimes はベクトルの直積(外積)を表すものとする。

ここまでくれば、Swiftでの行列計算について調べてみた。(その4) - いものやま。で作ったクラスを使って計算が出来るようになる。

中間層や出力の次元が増えた場合

おまけとして、中間層が増えた場合や、出力の次元が増えた場合の計算も書いておく。

以下では、各層  (l = 2, \cdots, L) の重みが  W^{(l)}、バイアスが  \boldsymbol{b}^{(l)}、活性化関数が  f^{(l)} とする。
(第1層は入力層、第  L 層は出力層)

  1. 入力層の出力  \boldsymbol{z}^{(1)} \boldsymbol{z}^{(1)} = \boldsymbol{x} とする。
  2.  l (l = 2, \cdots, L) の出力  \boldsymbol{z}^{(l)} を求める:
    1.  \boldsymbol{u}^{(l)} = W^{(l)} \boldsymbol{z}^{(l - 1)} + \boldsymbol{b}^{(l)}
    2.  \boldsymbol{z}^{(l)} = f^{(l)}(\boldsymbol{u}^{(l)})
  3. 出力  \boldsymbol{y} \boldsymbol{y} = \boldsymbol{z}^{(L)} とする。
  4. 出力層のデルタ  \boldsymbol{\delta}^{(L)} を求める:
    1.  \boldsymbol{\delta}^{(L)} = {f^{(L)}}'(\boldsymbol{u}^{(L)})
  5.  l (l = L-1, \cdots, 2) のデルタ  \boldsymbol{\delta}^{(l)} を求める:
    1.  \boldsymbol{\delta}^{(l)} = {f^{(h)}}'(\boldsymbol{u}^{(l)}) \odot \left( W^{(l + 1)} {}^{\mathrm{T}} \boldsymbol{\delta}^{(l + 1)} \right)
  6. 偏微分  (l = 2, \cdots, L) を求める:
    1.  \frac{\partial y}{\partial W^{(l)}} = \boldsymbol{\delta}^{(l)} \otimes \boldsymbol{z}^{(l - 1)}
    2.  \frac{\partial y}{\partial \boldsymbol{b}^{(l)}} = \boldsymbol{\delta}^{(l)}

今日はここまで!