ARKit+Swift 版本的機器學習演算法 k-NN

jiaxianhua發表於2019-04-08

kNN-Swift-2.png

k.png

ARKit-KNN-2.png

維基介紹

模式識別領域中,最近鄰居法KNN演算法,又譯K-近鄰演算法)是一種用於分類迴歸非引數統計方法[1]。在這兩種情況下,輸入包含特徵空間(Feature Space)中的***k***個最接近的訓練樣本。

  • k-NN分類中,輸出是一個分類族群。一個物件的分類是由其鄰居的“多數表決”確定的,k個最近鄰居(k為正整數,通常較小)中最常見的分類決定了賦予該物件的類別。若k = 1,則該物件的類別直接由最近的一個節點賦予。

  • k-NN迴歸中,輸出是該物件的屬性值。該值是其k個最近鄰居的值的平均值。

最近鄰居法採用向量空間模型來分類,概念為相同類別的案例,彼此的相似度高,而可以藉由計算與已知類別案例之相似度,來評估未知類別案例可能的分類。

K-NN是一種基於例項的學習,或者是區域性近似和將所有計算推遲到分類之後的惰性學習。k-近鄰演算法是所有的機器學習演算法中最簡單的之一。

無論是分類還是迴歸,衡量鄰居的權重都非常有用,使較近鄰居的權重比較遠鄰居的權重大。例如,一種常見的加權方案是給每個鄰居權重賦值為1/ d,其中d是到鄰居的距離。[注 1]

鄰居都取自一組已經正確分類(在迴歸的情況下,指屬性值正確)的物件。雖然沒要求明確的訓練步驟,但這也可以當作是此演算法的一個訓練樣本集。

k-近鄰演算法的缺點是對資料的區域性結構非常敏感。本演算法與K-平均演算法(另一流行的機器學習技術)沒有任何關係,請勿與之混淆。

ARKit + Swift + k-NN 實現

建立 KNN 類(結構體 struct 也行,我是為了 與 sklearn 儘量一致)。

/// KNN
public struct KNN<Feature, Label: Hashable> {
}
複製程式碼

屬性

    /// Number of neighbors to use by default for :meth:`kneighbors` queries
    private var k: Int
    /// Data set
    private var X = [Feature]()
    /// Target values
    private var y = [Label]()


    /// distance
    private let distanceMetric: (_ x1: Feature, _ x2: Feature) -> Double
    /// draw radius for debug
    public var debugRadiusCallback: (([Double]) -> ())? = nil
複製程式碼

資料:

  • k: 指定取 k 個最接近的訓練樣本
  • X: 樣本特徵 (陣列)一般要傳陣列的陣列
  • y: 樣本標籤 (陣列)

輔助:

  • distanceMetric: 用來計算距離的函式
  • debugRadiusCallback: 排程時候用,主要是畫出最近的 k 個樣本範圍

方法

    /// constructorLabels for xTest
    ///
    /// - Parameters:
    ///   - k: k
    ///   - distanceMetric: distance
    public init (k: Int, distanceMetric: @escaping (_ x1: Feature, _ x2: Feature) -> Double)
    
    /// train
    ///
    /// - Parameters:
    ///   - X: Training set
    ///   - y: Target values
    public mutating func fit(X: [Feature], y: [Label]) 
    
    
    /// Labels for xTest
    ///
    /// - Parameter XTest: Test set
    /// - Returns: Target values
    public func predict(XTest: [Feature]) -> [Label] 
複製程式碼
  • init(): 建構函式 需要預先決定 k 和距離計算方法
  • fit(): 擬合目標函式,kNN 不需要擬合,只要記下資料即可
  • predict(): 預測給定的特徵,返回對應的標籤

距離計算

public struct Distance {
    
    /// Helper function to calculate euclidian distance
    ///
    /// - Parameters:
    ///   - x0: source coordinate
    ///   - x1: target coordinate
    /// - Returns: euclidian distance
    public static func euclideanDistance(_ x0: [Double], _ x1: [Double]) -> Double 

    // Convenience
    public static func euclideanDistance() -> (([Double], [Double]) -> Double) {
        return { self.euclideanDistance($0, $1) }
    }
複製程式碼

這裡使用 歐式距離(Euclidean Distance)

KNN 使用:

    func testKNN() {
        let kNN = KNN<Double, Int>(k: 3)
        let X = [[1.0], [2], [3], [4]]
        let y = [0, 0, 1, 1]
        kNN.fit(X, y)
        
        let label = kNN.predict([[1.2], [3]])
        
        XCTAssertEqual([0, 1], label)
    }
複製程式碼

顯示 2 維

enum MLStep: Int {
    case train
    case predict
}

enum GeometryType: Int  {
    case box
    case pyramid
    case sphere
    case cylinder
    case cone
    case tube
    case torus
...
}
複製程式碼
  • MLStep: 分成 訓練 和 預測 ,訓練一次,可以一直預測。
  • GeometryType: 定義幾種形狀,這裡定義給 ARKIT 使用的

KNNViewController

class KNNViewController: UIViewController {

    let radius: CGFloat = 5

    public var X: [[Double]] = []
    public var y: [GeometryType] = []
    public var XTest: [[Double]] = []
    public var yTest: [GeometryType] = []
    public var radiuses: [Double] = [] {
        didSet {
            for (center, r) in zip(XTest, radiuses) {
                drawCircle(center: CGPoint(x: center[0], y: center[1]), radius: CGFloat(r))
            }
        }
    }
    public var predictLayers: [CALayer] = []

    var model = KNN<[Double], GeometryType>(k: 1, distanceMetric: Distance.euclideanDistance())

    @IBOutlet weak var kNNPickerView: UIPickerView!
    @IBOutlet weak var panelView: UIView!
    @IBOutlet weak var trainBarButtonItem: UIBarButtonItem!

    var mlStep = MLStep.train {
        didSet {
            switch mlStep {
            case .train:
                trainBarButtonItem.title = "train"
            default:
                trainBarButtonItem.title = "predict"
            }
        }
    }
...
}
複製程式碼

新增 LayerpanelView 上實現類別,2D 只分了三個類別,分別用 方形(紅),三角形(藍),花形(綠)表示。

使用 alpha 表示預測類別,以預測樣本為中心畫一個圈,圈內為最近的 k 個樣本。

    func drawCircle(center: CGPoint, radius: CGFloat, alpha: CGFloat = 0.1) {
        let r = self.radius + radius
        let kNNCircleLayer = CAShapeLayer()
        kNNCircleLayer.path = UIBezierPath(roundedRect: CGRect(x: center.x - r, y: center.y - r, width: r * 2, height: r * 2), cornerRadius: r).cgPath
        kNNCircleLayer.fillColor = UIColor(red: 0.1, green: 0.1, blue: 0.1, alpha: alpha).cgColor
        kNNCircleLayer.borderColor = UIColor(red: 0.8, green: 0.8, blue: 0.8, alpha: 1).cgColor
        kNNCircleLayer.borderWidth = 1
        panelView.layer.addSublayer(kNNCircleLayer)
    }
複製程式碼

圓內為 k 個樣本。

kNN-Swift-1.png

ARKit 實現

能 3D 展示多好,別急,下面就是用 ARKit 實現的 3D 版本。

class ARKNNViewController: UIViewController 
複製程式碼

基本實現和 ARKNNViewController 類似。

    func drawSphere(center: SCNVector3, radius: Float) {
        let geometry = SCNSphere(radius: CGFloat(radius) + self.radius)
  
        geometry.firstMaterial?.diffuse.contents = UIColor(red: 0.1, green: 0.1, blue: 0.8, alpha: 0.7)
        
        let node = SCNNode()
        node.geometry = geometry
        node.position = center
        sceneView.scene.rootNode.addChildNode(node)
    }
複製程式碼

這是畫最外面的範圍球體,球體內為 k 個樣本。

ARKit-KNN-1.png

視訊

b站視訊:www.bilibili.com/video/av486…

相關文章