swift で一次元、二次元配列を取り扱うクラスを作る
swiftで一次元、二次元配列を操作するクラスを作成しKMeansで有用性を確認しました
ソースはこちら
配列クラス: https://github.com/ikuo0/swift_number/blob/main/numxd.swift
KMeans実行部: https://github.com/ikuo0/swift_number/blob/main/main.swift
コマンド実行する場合は各々同じディレクトリに設置し
swiftc -emit-executable main.swift numxd.swift
とします、コマンドを実行すると main という名前のファイルが出来ますのでそれを実行してください
KMeans実行部
KMeans部分のソースです、70行ほどでKMeansが実行できていますのでだいぶ簡単に配列操作ができていると思います。
func Test2D() {
do {
let n1dI = Num1D<Int>()
let n1dD = Num1D<Double>()
let n2d = Num2D<Double>()
let tsvData = try TSV.Read("seeds_dataset.txt")
let data = try TSV.ToDouble(tsvData)
let xIndexes = n1dI.Arange(0, 7)
let xData = data.IndexingT(xIndexes)
let yData = data.LineT(7)
////////////////////////////////////////
// scaling
////////////////////////////////////////
let scaleMean = xData.Mean()
let scaleVriance = xData.Variance()
let scaleStddev = scaleVriance.Sqrt()
let scaledX = (xData - scaleMean) / scaleStddev
////////////////////////////////////////
// KMeans
////////////////////////////////////////
let Clusters = 3
let maxIter = 100
let tol = 1e-5
// Initialize
let rowIndexes = n1dI.Arange(0, yData.Count)
let shuffled = rowIndexes.Shuffle()
let initIndexes = shuffled.Slice(0, Clusters)
let initMeans = scaledX.Indexing(initIndexes)
var means = initMeans
let predict = n1dI.Create(xData.Row)
for iter in 0..<maxIter {
// EStep
for m in 0..<xData.Row {
let distances = n1dD.Create(Clusters)
for cluster in 0..<Clusters {
let subtract = scaledX.Line(m) - means.Line(cluster)
let power = subtract.Power(2)
let total = power.Total()
let distance = sqrt(total)
distances[cluster] = distance
}
predict[m] = distances.ArgMin()
}
// MStep
let newMeans = n2d.Create(Clusters, scaledX.Col)
for cluster in 0..<Clusters {
let cIndexes = predict.WhereEq(cluster)
let cMean = scaledX.Indexing(cIndexes)
newMeans[cluster] = cMean.Mean().Value
}
// threshold
let subtract = means - newMeans
let power = subtract.Power(2)
let total = power.TotalX()
let meansDistance = sqrt(total / Double(Clusters))
means = newMeans
print("iter=\(iter), means distance=\(meansDistance)")
if meansDistance < tol {
break
}
}
Dump2D(means)
Dump1D(predict)
} catch {
print("exception", error)
}
}
わざわざクラス化する必要があったかどうか
C/C++と異なりメモリ確保の面倒さが無いためわざわざクラス化する事も無かったんじゃないかという気もしましたが、作ってみたら思いの外短く書けているので有りかなと思いました
ジェネリクス関数の書き込みの仕組み
https://github.com/ikuo0/swift_number/blob/main/numxd.swift#L27
自作の WriteGVariable という関数でジェネリクス関数内の代入処理を行っています
WriteGVariable関数を使用しない場合に型を確定させてからキャストをするコードをその都度書かなければいけません
if T.self is Double.Type {
dst[i] = Double(r) as! T
} else if T.self is Int.Type {
dst[i] = Int(r) as! T
}
// 冗長になってしまう
WriteGVariable関数を使用することで1行で代入処理を書くことができます
WriteGVariable(&dst[i], r)
// 1行で済む
swiftが厳格な型付け言語であるためにジェネリクス型を使用した場合に冗長になるか冗長回避のため対策が必要になるのは仕方のないことだと思います。
ジェネリクス型に限らず型違い同士の代入処理等については下記URLにて答えが出ているように思います
https://academy.realm.io/jp/posts/richard-fox-casting-swift-2/
https://qiita.com/ObuchiYuki/items/a945efd14d3a05a19f75
答えが出ているにも関わらず真似しないのはoperator演算子のようなソースを隠蔽してしまう仕様が好みではないのと私のswift理解度が低いからです、理解しきれていない物をコピーするのは気が引けますので。
swiftの勉強のためC++で既に実装した物をswiftで実装し直しました、色々と発見があったりしておもしろかったです。
以上です。