Sources/TensorUtils/Math.swift (90 lines of code) (raw):

// // Math.swift // CoreMLBert // // Created by Julien Chaumond on 27/06/2019. // Copyright © 2019 Hugging Face. All rights reserved. // import Accelerate import CoreML import Foundation /// /// From M.I. Hollemans /// /// https://github.com/hollance/CoreMLHelpers /// public struct Math { /** Returns the index and value of the largest element in the array. - Parameters: - ptr: Pointer to the first element in memory. - count: How many elements to look at. - stride: The distance between two elements in memory. */ public static func argmax(_ ptr: UnsafePointer<Float>, count: Int, stride: Int = 1) -> (Int, Float) { var maxValue: Float = 0 var maxIndex: vDSP_Length = 0 vDSP_maxvi(ptr, vDSP_Stride(stride), &maxValue, &maxIndex, vDSP_Length(count)) return (Int(maxIndex), maxValue) } /** Returns the index and value of the largest element in the array. - Parameters: - ptr: Pointer to the first element in memory. - count: How many elements to look at. - stride: The distance between two elements in memory. */ public static func argmax(_ ptr: UnsafePointer<Double>, count: Int, stride: Int = 1) -> (Int, Double) { var maxValue: Double = 0 var maxIndex: vDSP_Length = 0 vDSP_maxviD(ptr, vDSP_Stride(stride), &maxValue, &maxIndex, vDSP_Length(count)) return (Int(maxIndex), maxValue) } public static func argmax32(_ ptr: UnsafePointer<Float>, count: Int, stride: Int = 1) -> (Int, Float) { var maxValue: Float = 0 var maxIndex: vDSP_Length = 0 vDSP_maxvi(ptr, vDSP_Stride(stride), &maxValue, &maxIndex, vDSP_Length(count)) return (Int(maxIndex), maxValue) } /// MLMultiArray helper. /// Works in our specific use case. public static func argmax(_ multiArray: MLMultiArray) -> (Int, Double) { assert(multiArray.dataType == .double) let ptr = UnsafeMutablePointer<Double>(OpaquePointer(multiArray.dataPointer)) return Math.argmax(ptr, count: multiArray.count) } /// MLMultiArray helper. /// Works in our specific use case. public static func argmax32(_ multiArray: MLMultiArray) -> (Int, Float) { assert(multiArray.dataType == .float32) let ptr = UnsafeMutablePointer<Float32>(OpaquePointer(multiArray.dataPointer)) return Math.argmax32(ptr, count: multiArray.count) } /// Returns the cumulative sum of the array. public static func cumsum(_ arr: [Float]) -> [Float] { guard !arr.isEmpty else { return [] } let arrCount = vDSP_Length(arr.count) var weight: Float = 1.0 var result: [Float] = Array(repeating: 0.0, count: arr.count) var firstItem = arr[0] vDSP_vrsum(arr, 1, &weight, &result, 1, arrCount) vDSP_vsadd(result, 1, &firstItem, &result, 1, arrCount) return result } /// Multinomial sampling from an array of probs. Works well with topK public static func sample(indexes: [Int], probs: [Float]) -> Int { let i = randomNumber(probabilities: probs) return indexes[i] } /** Computes the "softmax" function over an array. Based on code from https://github.com/nikolaypavlov/MLPNeuralNet/ This is what softmax looks like in "pseudocode" (actually using Python and numpy): x -= np.max(x) exp_scores = np.exp(x) softmax = exp_scores / np.sum(exp_scores) First we shift the values of x so that the highest value in the array is 0. This ensures numerical stability with the exponents, so they don't blow up. */ public static func softmax(_ x: [Float]) -> [Float] { var x = x let len = vDSP_Length(x.count) // Find the maximum value in the input array. var max: Float = 0 vDSP_maxv(x, 1, &max, len) // Subtract the maximum from all the elements in the array. // Now the highest value in the array is 0. max = -max vDSP_vsadd(x, 1, &max, &x, 1, len) // Exponentiate all the elements in the array. var count = Int32(x.count) vvexpf(&x, x, &count) // Compute the sum of all exponentiated values. var sum: Float = 0 vDSP_sve(x, 1, &sum, len) // Divide each element by the sum. This normalizes the array contents // so that they all add up to 1. vDSP_vsdiv(x, 1, &sum, &x, 1, len) return x } /// Multinomial sampling /// /// From https://stackoverflow.com/questions/30309556/generate-random-numbers-with-a-given-distribution /// public static func randomNumber(probabilities: [Float]) -> Int { // Sum of all probabilities (so that we don't have to require that the sum is 1.0): let sum = probabilities.reduce(0, +) // Random number in the range 0.0 <= rnd < sum : let rnd = sum * Float(arc4random_uniform(UInt32.max)) / Float(UInt32.max) // Find the first interval of accumulated probabilities into which `rnd` falls: var accum: Float = 0.0 for (i, p) in probabilities.enumerated() { accum += p if rnd < accum { return i } } // This point might be reached due to floating point inaccuracies: return probabilities.count - 1 } } // MLShapedArray versions public extension Math { static func argmax(_ shapedArray: MLShapedArray<Float>) -> (Int, Float) { shapedArray.withUnsafeShapedBufferPointer { ptr, shape, strides in assert(shape.count == 1, "Only supported for 1-dimensional arrays or slices") return Math.argmax32(ptr.baseAddress!, count: shapedArray.count, stride: strides.first!) } } // TODO: handle Double, etc. static func argmax(_ shapedArray: some MLShapedArrayProtocol) -> (Int, Float) { shapedArray.withUnsafeShapedBufferPointer { ptr, shape, strides in assert(shape.count == 1, "Only supported for 1-dimensional arrays or slices") let floatsPtr = ptr.baseAddress as! UnsafePointer<Float> return Math.argmax32(floatsPtr, count: shapedArray.count, stride: strides.first!) } } }