昨日は強化学習用の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
今日はここまで!