維基介紹
在模式識別領域中,最近鄰居法(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"
}
}
}
...
}
複製程式碼
新增 Layer
到 panelView
上實現類別,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
個樣本。
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
個樣本。