1. 程式人生 > >基於歐幾里德距離的K最近鄰(KNN)演算法的實現(JAVA版)

基於歐幾里德距離的K最近鄰(KNN)演算法的實現(JAVA版)

K鄰近(k-Nearest Neighbor,KNN)分類演算法是最簡單的機器學習演算法了。它採用測量不同特徵值之間的距離方法進行分類。它的思想很簡單:計算一個點A與其他所有點之間的距離,取出與該點最近的k個點,然後統計這k個點裡面所屬分類比例最大的,則點A屬於該分類。

下面用一個例子來說明一下:

電影名稱

打鬥次數

接吻次數

電影型別

California Man

3

104

Romance

He’s Not Really into Dudes

2

100

Romance

Beautiful Woman

1

81

Romance

Kevin Longblade

101

10

Action

Robo Slayer 3000

99

5

Action

Amped II

98

2

Action

簡單說一下這個資料的意思:這裡用打鬥次數和接吻次數來界定電影型別,如上,接吻多的是Romance型別的,而打鬥多的是動作電影。還有一部名字未知(這裡名字未知是為了防止能從名字中猜出電影型別),打鬥次數為18次,接吻次數為90次的電影,它到底屬於哪種型別的電影呢?

KNN演算法要做的,就是先用打鬥次數和接吻次數作為電影的座標,然後計算其他六部電影與未知電影之間的距離,取得前K個距離最近的電影,然後統計這k個距離最近的電影裡,屬於哪種型別的電影最多,比如Action最多,則說明未知的這部電影屬於動作片型別。

在實際使用中,有幾個問題是值得注意的:K值的選取,選多大合適呢?計算兩者間距離,用哪種距離會更好呢?計算量太大怎麼辦?假設樣本中,型別分佈非常不均,比如Action的電影有200部,但是Romance的電影只有20部,這樣計算起來,即使不是Action的電影,也會因為Action的樣本太多,導致k個最近鄰居里有不少Action的電影,這樣該怎麼辦呢?

沒有萬能的演算法,只有在一定使用環境中最優的演算法。

1.1 演算法指導思想

kNN演算法的指導思想是“近朱者赤,近墨者黑”,由你的鄰居來推斷出你的類別。

先計算待分類樣本與已知類別的訓練樣本之間的距離,找到距離與待分類樣本資料最近的k個鄰居;再根據這些鄰居所屬的類別來判斷待分類樣本資料的類別。

1.2相似性度量

用空間內兩個點的距離來度量。距離越大,表示兩個點越不相似。距離的選擇有很多[13],通常用比較簡單的歐式距離。

歐式距離

 

馬氏距離:馬氏距離能夠緩解由於屬性的線性組合帶來的距離失真,是資料的協方差矩陣。

曼哈頓距離

切比雪夫距離

閔氏距離:r取值為2時:曼哈頓距離;r取值為1時:歐式距離。

平均距離

 

弦距離

測地距離

1.2 類別的判定

投票決定:少數服從多數,近鄰中哪個類別的點最多就分為該類。

加權投票法:根據距離的遠近,對近鄰的投票進行加權,距離越近則權重越大(權重為距離平方的倒數)

 優缺點

1.2.1              優點

  1. 簡單,易於理解,易於實現,無需估計引數,無需訓練;
  2. 適合對稀有事件進行分類;
  3. 特別適合於多分類問題(multi-modal,物件具有多個類別標籤), kNN比SVM的表現要好。
  4. 懶惰演算法,對測試樣本分類時的計算量大,記憶體開銷大,評分慢;
  5. 當樣本不平衡時,如一個類的樣本容量很大,而其他類樣本容量很小時,有可能導致當輸入一個新樣本時,該樣本的K個鄰居中大容量類的樣本佔多數;
  6. 可解釋性較差,無法給出決策樹那樣的規則。

1.2.2              缺點

1.3 常見問題

1.3.1              k值的設定

k值選擇過小,得到的近鄰數過少,會降低分類精度,同時也會放大噪聲資料的干擾;而如果k值選擇過大,並且待分類樣本屬於訓練集中包含資料數較少的類,那麼在選擇k個近鄰的時候,實際上並不相似的資料亦被包含進來,造成噪聲增加而導致分類效果的降低。

如何選取恰當的K值也成為KNN的研究熱點。k值通常是採用交叉檢驗來確定(以k=1為基準)。

經驗規則:k一般低於訓練樣本數的平方根。

1.3.2              類別的判定方式

投票法沒有考慮近鄰的距離的遠近,距離更近的近鄰也許更應該決定最終的分類,所以加權投票法更恰當一些。

1.3.3              距離度量方式的選擇

高維度對距離衡量的影響:眾所周知當變數數越多,歐式距離的區分能力就越差。

變數值域對距離的影響:值域越大的變數常常會在距離計算中佔據主導作用,因此應先對變數進行標準化。

1.3.4              訓練樣本的參考原則

學者們對於訓練樣本的選擇進行研究,以達到減少計算的目的,這些演算法大致可分為兩類。第一類,減少訓練集的大小。KNN演算法儲存的樣本資料,這些樣本資料包含了大量冗餘資料,這些冗餘的資料增了儲存的開銷和計算代價。縮小訓練樣本的方法有:在原有的樣本中刪掉一部分與分類相關不大的樣本樣本,將剩下的樣本作為新的訓練樣本;或在原來的訓練樣本集中選取一些代表樣本作為新的訓練樣本;或通過聚類,將聚類所產生的中心點作為新的訓練樣本。

在訓練集中,有些樣本可能是更值得依賴的。可以給不同的樣本施加不同的權重,加強依賴樣本的權重,降低不可信賴樣本的影響。

1.3.5              效能問題

kNN是一種懶惰演算法,而懶惰的後果:構造模型很簡單,但在對測試樣本分類地的系統開銷大,因為要掃描全部訓練樣本並計算距離。

已經有一些方法提高計算的效率,例如壓縮訓練樣本量等。

1.4 演算法流程

  1. 準備資料,對資料進行預處理
  2. 選用合適的資料結構儲存訓練資料和測試元組
  3. 設定引數,如k
  4. 維護一個大小為k的的按距離由大到小的優先順序佇列,用於儲存最近鄰訓練元組。隨機從訓練元組中選取k個元組作為初始的最近鄰元組,分別計算測試元組到這k個元組的距離,將訓練元組標號和距離存入優先順序佇列
  5. 遍歷訓練元組集,計算當前訓練元組與測試元組的距離,將所得距離L 與優先順序佇列中的最大距離Lmax
  6. 進行比較。若L>=Lmax,則捨棄該元組,遍歷下一個元組。若L < Lmax,刪除優先順序佇列中最大距離的元
  7. 組,將當前訓練元組存入優先順序佇列。
  8. 遍歷完畢,計算優先順序佇列中k 個元組的多數類,並將其作為測試元組的類別。

    9.測試元組集測試完畢後計算誤差率,繼續設定不同的k 值重新進行訓練,最後取誤差率最小的k 值。

注意:

下面關於KNN的JAVA程式碼 只適合學習使用,可以大致瞭解一下KNN演算法的原理。

演算法作了如下的假定與簡化處理:

1.小規模資料集

2.假設所有資料及類別都是數值型別的

3.直接根據資料規模設定了k值

4.對原訓練集進行測試


Java程式碼實現

KNN演算法主體類:
package KNN; 
import java.util.ArrayList; 
import java.util.Comparator; 
import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 
import java.util.PriorityQueue; 
 
/**
* KNN演算法主體類
* @author Rowen
* @qq 443773264
* @mail [email protected]
* @blog blog.csdn.net/luowen3405
* @data 2011.03.25
*/ 
public class KNN { 
    /**
     * 設定優先順序佇列的比較函式,距離越大,優先順序越高
     */ 
    private Comparator<KNNNode> comparator = new Comparator<KNNNode>() { 
        public int compare(KNNNode o1, KNNNode o2) { 
            if (o1.getDistance() >= o2.getDistance()) { 
                return 1; 
            } else { 
                return 0; 
            } 
        } 
    }; 
    /**
     * 獲取K個不同的隨機數
     * @param k 隨機數的個數
     * @param max 隨機數最大的範圍
     * @return 生成的隨機數陣列
     */ 
    public List<Integer> getRandKNum(int k, int max) { 
        List<Integer> rand = new ArrayList<Integer>(k); 
        for (int i = 0; i < k; i++) { 
            int temp = (int) (Math.random() * max); 
            if (!rand.contains(temp)) { 
                rand.add(temp); 
            } else { 
                i--; 
            } 
        } 
        return rand; 
    } 
    /**
     * 計算測試元組與訓練元組之前的距離
     * @param d1 測試元組
     * @param d2 訓練元組
     * @return 距離值
     */ 
    public double calDistance(List<Double> d1, List<Double> d2) { 
        double distance = 0.00; 
        for (int i = 0; i < d1.size(); i++) { 
            distance += (d1.get(i) - d2.get(i)) * (d1.get(i) - d2.get(i)); 
        } 
        return distance; 
    } 
    /**
     * 執行KNN演算法,獲取測試元組的類別
     * @param datas 訓練資料集
     * @param testData 測試元組
     * @param k 設定的K值
     * @return 測試元組的類別
     */ 
    public String knn(List<List<Double>> datas, List<Double> testData, int k) { 
        PriorityQueue<KNNNode> pq = new PriorityQueue<KNNNode>(k, comparator); 
        List<Integer> randNum = getRandKNum(k, datas.size()); 
        for (int i = 0; i < k; i++) { 
            int index = randNum.get(i); 
            List<Double> currData = datas.get(index); 
            String c = currData.get(currData.size() - 1).toString(); //得到類值0或1 BY WS
            KNNNode node = new KNNNode(index, calDistance(testData, currData), c); 
            pq.add(node); 
        } 
        for (int i = 0; i < datas.size(); i++) { 
            List<Double> t = datas.get(i); 
            double distance = calDistance(testData, t); 
            KNNNode top = pq.peek(); 
            if (top.getDistance() > distance) { 
                pq.remove(); 
                pq.add(new KNNNode(i, distance, t.get(t.size() - 1).toString())); 
            } 
        } 
         
        return getMostClass(pq); 
    } 
    /**
     * 獲取所得到的k個最近鄰元組的多數類
     * @param pq 儲存k個最近近鄰元組的優先順序佇列
     * @return 多數類的名稱
     */ 
    private String getMostClass(PriorityQueue<KNNNode> pq) { 
        Map<String, Integer> classCount = new HashMap<String, Integer>(); 
        for (int i = 0; i < pq.size(); i++) { 
            KNNNode node = pq.remove(); 
            String c = node.getC(); 
            if (classCount.containsKey(c)) { 
                classCount.put(c, classCount.get(c) + 1); 
            } else { 
                classCount.put(c, 1); 
            } 
        } 
        int maxIndex = -1; 
        int maxCount = 0; 
        Object[] classes = classCount.keySet().toArray(); 
        for (int i = 0; i < classes.length; i++) { 
            if (classCount.get(classes[i]) > maxCount) { 
                maxIndex = i; 
                maxCount = classCount.get(classes[i]); 
            } 
        } 
        return classes[maxIndex].toString(); 
    } 
} 
 //KNN結點類,用來儲存最近鄰的k個元組相關的資訊:
package KNN;


public class KNNNode {
private int index; // 元組標號
 private double distance; // 與測試元組的距離
 private String c; // 所屬類別
 public KNNNode(int index, double distance, String c) {
  super();
  this.index = index;
  this.distance = distance;
  this.c = c;
 }
 
 
 public int getIndex() {
  return index;
 }
 public void setIndex(int index) {
  this.index = index;
 }
 public double getDistance() {
  return distance;
 }
 public void setDistance(double distance) {
  this.distance = distance;
 }
 public String getC() {
  return c;
 }
 public void setC(String c) {
  this.c = c;
 }


}
package KNN; 
import java.io.BufferedReader; 
import java.io.File; 
import java.io.FileReader; 
import java.util.ArrayList; 
import java.util.List; 
/**
* KNN演算法測試類
* @author Rowen
* @qq 443773264
* @mail [email protected]
* @blog blog.csdn.net/luowen3405
* @data 2011.03.25
*/ 
public class TestKNN { 
     
    /**
     * 從資料檔案中讀取資料
     * @param datas 儲存資料的集合物件
     * @param path 資料檔案的路徑
     */ 
    public void read(List<List<Double>> datas, String path){ 
        try { 
            BufferedReader br = new BufferedReader(new FileReader(new File(path))); 
            String data = br.readLine(); 
            List<Double> l = null; 
            while (data != null) { 
                String t[] = data.split(" "); 
                l = new ArrayList<Double>(); 
                for (int i = 0; i < t.length; i++) { 
                    l.add(Double.parseDouble(t[i])); 
                } 
                datas.add(l); 
                data = br.readLine(); 
            } 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
    } 
     
    /**
     * 程式執行入口
     * @param args
     */ 
    public static void main(String[] args) { 
        TestKNN t = new TestKNN(); 
        String datafile = new File("").getAbsolutePath() + "\\src\\KNN"+File.separator + "datafile"; 
        String testfile = new File("").getAbsolutePath() + "\\src\\KNN"+File.separator + "testfile"; 
        try { 
            List<List<Double>> datas = new ArrayList<List<Double>>(); 
            List<List<Double>> testDatas = new ArrayList<List<Double>>(); 
            t.read(datas, datafile); 
            t.read(testDatas, testfile); 
            KNN knn = new KNN(); 
            for (int i = 0; i < testDatas.size(); i++) { 
                List<Double> test = testDatas.get(i); 
                System.out.print("測試元組: "); 
                for (int j = 0; j < test.size(); j++) { 
                    System.out.print(test.get(j) + " "); 
                } 
                System.out.print("類別為: "); 
                System.out.println(Math.round(Float.parseFloat((knn.knn(datas, test, 3))))); 
            } 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
    } 
} 


訓練資料檔案datafile,訓練集最後一列代表分類(0或者1)
1.0 1.1 1.2 2.1 0.3 2.3 1.4 0.5 1 
1.7 1.2 1.4 2.0 0.2 2.5 1.2 0.8 1 
1.2 1.8 1.6 2.5 0.1 2.2 1.8 0.2 1 
1.9 2.1 6.2 1.1 0.9 3.3 2.4 5.5 0 
1.0 0.8 1.6 2.1 0.2 2.3 1.6 0.5 1 
1.6 2.1 5.2 1.1 0.8 3.6 2.4 4.5 0
測試集testfile
1.0 1.1 1.2 2.1 0.3 2.3 1.4 0.5 
1.7 1.2 1.4 2.0 0.2 2.5 1.2 0.8 
1.2 1.8 1.6 2.5 0.1 2.2 1.8 0.2 
1.9 2.1 6.2 1.1 0.9 3.3 2.4 5.5 
1.0 0.8 1.6 2.1 0.2 2.3 1.6 0.5 
1.6 2.1 5.2 1.1 0.8 3.6 2.4 4.5
程式執行結果
測試元組: 1.0 1.1 1.2 2.1 0.3 2.3 1.4 0.5 類別為: 1
測試元組: 1.7 1.2 1.4 2.0 0.2 2.5 1.2 0.8 類別為: 1
測試元組: 1.2 1.8 1.6 2.5 0.1 2.2 1.8 0.2 類別為: 1
測試元組: 1.9 2.1 6.2 1.1 0.9 3.3 2.4 5.5 類別為: 0
測試元組: 1.0 0.8 1.6 2.1 0.2 2.3 1.6 0.5 類別為: 1
測試元組: 1.6 2.1 5.2 1.1 0.8 3.6 2.4 4.5 類別為: 0

相關推薦

基於距離K近鄰(KNN)演算法實現(JAVA)

K鄰近(k-Nearest Neighbor,KNN)分類演算法是最簡單的機器學習演算法了。它採用測量不同特徵值之間的距離方法進行分類。它的思想很簡單:計算一個點A與其他所有點之間的距離,取出與該點最近的k個點,然後統計這k個點裡面所屬分類比例最大的,則點A屬於該分類。

基於聚類的鐳射雷達點雲分割及ROS實現——學習總結

1、特別說明 本部落格是在復現大神部落格的過程中遇到問題的解決方式,具體的部落格地址是: https://blog.csdn.net/AdamShan/article/details/83015570#commentsedit 寫的非常好的博主,在此大力推薦!!! 2、實現過程

【機器學習】距離和皮爾遜相關係數(筆記)

歐幾里德距離() 歐幾里德距離和皮爾遜相關係數在機器學習中都是對相關度的計算,歐幾里德距離是以人們一直評價的物品作為座標軸,將參與評價的人繪製到圖中,並考察他們彼此距離的遠近。例子(摘自集體智慧程式設計): #資料集 critics={ 'Lisa Rose':

距離評價(Python3.x程式碼實現

1.定義 歐幾里得度量(euclidean metric)(也稱歐氏距離)是一個通常採用的距離定義,指在m維空間中兩個點之間的真實距離,或者向量的自然長度(即該點到原點的距離)。在二維和三維空間中的歐氏距離就是兩點之間的實際距離。 2.公式 3.注意事項 (1)因

機器學習與資料探勘-K近鄰(KNN)演算法實現java和python

KNN演算法基礎思想前面文章可以參考,這裡主要講解java和python的兩種簡單實現,也主要是理解簡單的思想。 python版本: 這裡實現一個手寫識別演算法,這裡只簡單識別0~9熟悉,在上篇文章中也展示了手寫識別的應用,可以參考:機器學習與資料探勘-logistic迴

【2018.11.3】阿伏伽羅 / 聯絡 / 距離

int main(){   while(模擬賽) 降智++;     return inf; } 題目 T1 剛看到題時還以為不可做,重新看了幾遍之後才發現以前好像做過…… 做法很顯然吧…… 由於第一行存在 $1-n$ 的數各一個,我們可以先把列 按照第一行的數從大到小排序

C語言輾轉相除法(演算法)求大公約數

演算法敘述: 設(a,b)表示a和b的最大公約數 若c為a/b的餘數(c=a%b) 則(a,b)=(b,c). #include<stdio.h> int gcd(int a,int b

推廣的演算法(求大公約數和乘法逆元)

歐幾里德演算法 歐幾里德演算法又稱輾轉相除法,用於計算兩個整數a,b的最大公約數。其計算原理依賴於下面的定理: 定理:gcd(a,b) = gcd(b,a mod b) 證明:a可以表示成a = kb + r,則r = a mod b 假設d是a,b的一個公約數,

演算法大公約數

歐幾里德演算法又稱輾轉相除法,用於計算兩個整數a,b的最大公約數。 定理:gcd(a,b) = gcd(b,a mod b)。 第一種可以寫成: int Gcd(int a, int b) { while(b != 0) { int r =

AES基於擴充套件求逆元的S-Box生成

#define _CRT_SECURE_NO_WARNINGS #include<cstdio> #include<iostream> using namespace std; const int Bit_Num = sizeof(i

大公因子(輾轉相除法原理)(擴充套件的演算法

while(n != 0) { r = m % n; m = n; n = r; } printf("Their greatest common divisor is %d.\n", m);

演算法(求兩個正整數的大公約數)

  getchar()會接受前一個scanf的回車符 */ #include<stdio.h> void main() {     int temp;     int a,b;     s

大公約數演算法及Python實現

歐幾里德演算法又稱輾轉相除法,用於計算兩個整數m, n的最大公約數。其計算原理依賴於下面的定理:    gcd(m, n) = gcd(n, m mod n)這個定理的意思是:整數m、n的最大公約數等於n和m除以n的餘數的最大公約數。 例如:有兩個整數:120和45,我們按照

演算法(求兩數大公因數)

     兩個整數的最大公因數(gcd)是同時整除兩個大最大整數。即gcd(50,15)=5.      演算法連續計算餘數直到除數為0,最後的非0餘數就是最大公因數。因此若M=1989,N=1590

洛谷 P1290 的遊戲 題解

一、題目: 洛谷原題 二、思路: 什麼數論,什麼歐幾里得演算法,都不需要!要的只是搜尋和記憶化! 看到題,沒思路。考慮了SG函式,太暴力。這麼大的資料範圍似乎過不去。索性打打試試! woc!60分!這題資料好水水啊! 再一看,加個記憶化好像沒毛病。交上去,A了!!! 這就是記憶化的重要性。 S

HDU-2669 Romantic(擴充套件

                                          &nb

倒酒(擴充套件)90分

題目描述 Winy是一家酒吧的老闆,他的酒吧提供兩種體積的啤酒,a ml和b ml,分別使用容積為a ml和b ml的酒杯來裝載。 酒吧的生意並不好。Winy發現酒鬼們都非常窮。有時,他們會因為負擔不起aml或者bml啤酒的消費,而不得不離去。因此,Winy決定出售第三種體積的啤酒(較小體積的啤酒)。

同餘方程(擴充套件演算法

同餘方程 時間限制: 1 Sec  記憶體限制: 128 MB 題目描述 求關於 x 的同餘方程 ax ≡ 1 (mod b)的最小正整數解。 輸入 輸入只有一行,包含兩個正整數 a, b,用一個空格隔開。 輸出 輸出只有一行,包含

洛谷P1290 的遊戲

題目描述 歐幾里德的兩個後代Stan和Ollie正在玩一種數字遊戲,這個遊戲是他們的祖先歐幾里德發明的。給定兩個正整數M和N,從Stan開始,從其中較大的一個數,減去較小的數的正整數倍,當然,得到的數不能小於0。然後是Ollie,對剛才得到的數,和M,N中較小的那個數,再進

Luogu4433:[COCI2009-2010#1] ALADIN(類演算法)

先套用一個線段樹維護離散化之後的區間的每一段的答案 那麼只要考慮怎麼下面的東西即可 ∑