generated from rafaelesantos/swift-readme-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7f6bc80
commit 40ad824
Showing
3 changed files
with
92 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 86 additions & 18 deletions
104
Sources/RefdsAlgorithm/MachineLearning/LinearRegression.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,95 @@ | ||
import Foundation | ||
import Accelerate | ||
|
||
class LinearRegression { | ||
private var x: [Double] | ||
private var y: [Double] | ||
public class LinearRegression { | ||
private var features: [[NSNumber]] | ||
private var targets: [NSNumber] | ||
private var coefficients: [Double] = [] | ||
|
||
public init(x: [Double], y: [Double]) { | ||
self.x = x | ||
self.y = y | ||
} | ||
private let semaphore = DispatchSemaphore(value: 0) | ||
private let queue = DispatchQueue( | ||
label: "refds.algorithm.linearRegression", | ||
qos: .background | ||
) | ||
|
||
private func average(_ input: [Double]) -> Double { | ||
return input.reduce(0, +) / Double(input.count) | ||
public init( | ||
features: [[NSNumber]], | ||
targets: [NSNumber] | ||
) { | ||
self.features = features | ||
self.targets = targets | ||
|
||
queue.async { self.makeCoefficients() } | ||
} | ||
|
||
private func makeCoefficients() { | ||
self.coefficients = [] | ||
let rows = features.count | ||
|
||
guard let cols = features[safe: 0]?.count, | ||
rows == targets.count, | ||
!features.isEmpty && !features[0].isEmpty | ||
else { | ||
semaphore.signal() | ||
return | ||
} | ||
|
||
var X = features | ||
for i in 0 ..< rows { X[i].insert(1.0, at: 0) } | ||
|
||
let flatX = X.flatMap { $0 }.map { $0.doubleValue } | ||
let flatY = targets.map { $0.doubleValue } | ||
var coefficients = [Double](repeating: 0.0, count: cols + 1) | ||
|
||
var XT = [Double](repeating: 0.0, count: rows * (cols + 1)) | ||
vDSP_mtransD(flatX, 1, &XT, 1, vDSP_Length(cols + 1), vDSP_Length(rows)) | ||
|
||
var XTX = [Double](repeating: 0.0, count: (cols + 1) * (cols + 1)) | ||
vDSP_mmulD(XT, 1, flatX, 1, &XTX, 1, vDSP_Length(cols + 1), vDSP_Length(cols + 1), vDSP_Length(rows)) | ||
|
||
private func multiply(_ a: [Double], _ b: [Double]) -> [Double] { | ||
return zip(a, b).map(*) | ||
var N = __CLPK_integer(cols + 1) | ||
var pivots = [__CLPK_integer](repeating: 0, count: cols + 1) | ||
var workspace = [Double](repeating: 0.0, count: cols + 1) | ||
var error: __CLPK_integer = 0 | ||
|
||
let _ = withUnsafeMutablePointer(to: &N) { | ||
dgetrf_($0, $0, &XTX, $0, &pivots, &error) | ||
} | ||
|
||
guard error == 0 else { | ||
semaphore.signal() | ||
return | ||
} | ||
|
||
let _ = withUnsafeMutablePointer(to: &N) { | ||
dgetri_($0, &XTX, $0, &pivots, &workspace, $0, &error) | ||
} | ||
|
||
guard error == 0 else { | ||
semaphore.signal() | ||
return | ||
} | ||
|
||
var XTY = [Double](repeating: 0.0, count: cols + 1) | ||
vDSP_mmulD(XT, 1, flatY, 1, &XTY, 1, vDSP_Length(cols + 1), 1, vDSP_Length(rows)) | ||
vDSP_mmulD(XTX, 1, XTY, 1, &coefficients, 1, vDSP_Length(cols + 1), 1, vDSP_Length(cols + 1)) | ||
|
||
self.coefficients = coefficients | ||
semaphore.signal() | ||
} | ||
|
||
public var result: (Double) -> Double { | ||
let sum1 = average(multiply(x, y)) - average(x) * average(y) | ||
let sum2 = average(multiply(x, x)) - pow(average(x), 2) | ||
let slope = sum1 / sum2 | ||
let intercept = average(y) - slope * average(x) | ||
return { x in intercept + slope * x } | ||
|
||
public func predict(for features: [Double]) -> Double? { | ||
semaphore.wait() | ||
guard !coefficients.isEmpty, | ||
var prediction = coefficients[safe: 0] | ||
else { return nil } | ||
|
||
for i in features.indices { | ||
if let coefficient = coefficients[safe: i + 1] { | ||
prediction += coefficient * features[i] | ||
} | ||
} | ||
semaphore.signal() | ||
return prediction | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters