読者です 読者をやめる 読者になる 読者になる

いものやま。

雑多な知識の寄せ集め

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

強化学習の関数近似ニューラルネットワークを組合せるということをやってきていた。

強化学習については以下から:

ニューラルネットワークについては以下から:

複数ニューラルネットワークを組合せるHME(Hierarchical Mixtures of Experts)については以下から:

強化学習とニューラルネットワーク(HME含む)の組合せについては以下から:

この強化学習用のニューラルネットワークRubyで書いてたけど、iOSアプリを作るとなると、Swiftに移植しないといけない。
ということで、Swiftに移植してみた。

乱数生成器

まず必要となるのが、乱数生成器。

一様乱数の生成器は以前も書いたけど、再掲。

//==============================
// Random
//------------------------------
// Random.swift
//==============================

import Foundation
import Security

class Random {
  static func getUniformedRandom(upperBound: Int) -> Int {
    let upper = UInt32(upperBound)
    let ignoreRegion = 0xffff_ffff - 0xffff_ffff % upper

    while true {
      let buffer = UnsafeMutablePointer<UInt32>.alloc(1)
      SecRandomCopyBytes(kSecRandomDefault, 4, UnsafeMutablePointer<UInt8>(buffer))
      let randomValue = buffer.memory
      buffer.dealloc(1)
      if randomValue < ignoreRegion {
        return Int(randomValue % upper)
      }
    }
  }

  static func getRandomProbability() -> Double {
    return Double(Random.getUniformedRandom(0x1_0000)) / Double(0xffff)
  }

  private init() {}
}

extension Array {
  func sample() -> Element {
    let index = Random.getUniformedRandom(self.count)
    return self[index]
  }
}

この一様乱数の生成器の説明は、以下から:

そして、ニューラルネットワークの重みの初期化で正規分布に従う乱数生成器も必要になるので、移植。

正規分布に従う乱数生成器の説明は、以下から:

実装は以下:

//==============================
// Random
//------------------------------
// NormalDistRandom.swift
//==============================

import Foundation

class NormalDistRandom {
  private let expected: Double
  private let variance: Double
  private var values: [Double]
  
  init(expected: Double, variance: Double) {
    self.expected = expected
    self.variance = variance
    self.values = []
  }
  
  func getRandom() -> Double {
    if self.values.count == 0 {
      var a = 0.0
      var b = 0.0
      repeat {
        a = Random.getRandomProbability()
        b = Random.getRandomProbability()
      } while (a == 0.0) || (a == 1.0) || (b == 0.0) || (b == 1.0)
      
      let z1 = sqrt(-2.0 * log(a)) * cos(2.0 * M_PI * b)
      let z2 = sqrt(-2.0 * log(a)) * sin(2.0 * M_PI * b)
      
      let rand1 = z1 * sqrt(self.variance) + self.expected
      let rand2 = z2 * sqrt(self.variance) + self.expected
      
      self.values = [rand1, rand2]
    }
    
    return self.values.removeLast()
  }
}

乱数生成器の動作確認

これらの乱数生成器の動作確認として、以下のコードを書いた:

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

import Foundation

let count = 1000

// Random.getUniformedRandom()

var valueCount = [
  0: 0, 1: 0, 2: 0, 3: 0, 4: 0,
  5: 0, 6: 0, 7: 0, 8: 0, 9: 0,
]
for _ in 0..<count {
  let randomValue = Random.getUniformedRandom(10)
  valueCount[randomValue] = valueCount[randomValue]! + 1
}

print("----------")
print("Random.getUniformedRandom()")
print("----------")
for value in valueCount.keys.sort() {
  print("\(value): \(valueCount[value]!)")
}

// Random.getRandomProbability()

var sum = 0.0
var squareSum = 0.0
var minValue = 100.0
var maxValue = -100.0
for i in 0..<count {
  let randomValue = Random.getRandomProbability()
  sum += randomValue
  squareSum += randomValue * randomValue
  if randomValue < minValue {
    minValue = randomValue
  }
  if maxValue < randomValue {
    maxValue = randomValue
  }
}
var mean = sum / Double(count)
var variance = squareSum / Double(count) - mean * mean

print("----------")
print("Random.getRandomProbability()")
print("----------")
print("min: \(minValue)")
print("max: \(maxValue)")
print("mean: \(mean)")
print("variance: \(variance)")

// Array#sample()

let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

valueCount = [
  0: 0, 1: 0, 2: 0, 3: 0, 4: 0,
  5: 0, 6: 0, 7: 0, 8: 0, 9: 0,
]
for _ in 0..<count {
  let randomValue = array.sample()
  valueCount[randomValue] = valueCount[randomValue]! + 1
}

print("----------")
print("Array#sample()")
print("----------")
for value in valueCount.keys.sort() {
  print("\(value): \(valueCount[value]!)")
}

// NormalDistRandom

let normalDistRandom = NormalDistRandom(expected: 1.0, variance: 2.0)

sum = 0.0
squareSum = 0.0
minValue = 100.0
maxValue = -100.0
for i in 0..<count {
  let randomValue = normalDistRandom.getRandom()
  sum += randomValue
  squareSum += randomValue * randomValue
  if randomValue < minValue {
    minValue = randomValue
  }
  if maxValue < randomValue {
    maxValue = randomValue
  }
}
mean = sum / Double(count)
variance = squareSum / Double(count) - mean * mean

print("----------")
print("normalDistRandom.getRanodm()")
print("----------")
print("min: \(minValue)")
print("max: \(maxValue)")
print("mean: \(mean)")
print("variance: \(variance)")

実行例は、以下:

----------
Random.getUniformedRandom()
----------
0: 98
1: 104
2: 100
3: 85
4: 105
5: 112
6: 117
7: 86
8: 103
9: 90
----------
Random.getRandomProbability()
----------
min: 9.15541313801785e-05
max: 0.998825055313954
mean: 0.472300785839628
variance: 0.0830635419408054
----------
Array#sample()
----------
0: 90
1: 110
2: 100
3: 112
4: 98
5: 91
6: 101
7: 102
8: 106
9: 90
----------
normalDistRandom.getRanodm()
----------
min: -3.76411577670718
max: 5.42937254648993
mean: 0.989138466448474
variance: 2.1180087175273

今日はここまで!