1. 程式人生 > 程式設計 >python KNN演算法實現鳶尾花資料集分類

python KNN演算法實現鳶尾花資料集分類

一、knn演算法描述

1.基本概述

knn演算法,又叫k-近鄰演算法。屬於一個分類演算法,主要思想如下:
一個樣本在特徵空間中的k個最近鄰的樣本中的大多數都屬於某一個類別,則該樣本也屬於這個類別。其中k表示最近鄰居的個數。

用二維的圖例,說明knn演算法,如下:

二維空間下資料之間的距離計算:

在n維空間兩個資料之間:

2.具體步驟:
(1)計算待測試資料與各訓練資料的距離
(2)將計算的距離進行由小到大排序
(3)找出距離最小的k個值
(4)計算找出的值中每個類別的頻次
(5)返回頻次最高的類別

二、鳶尾花資料集

Iris 鳶尾花資料集內包含 3 類分別為山鳶尾(Iris-setosa)、變色鳶尾(Iris-versicolor)和維吉尼亞鳶尾(Iris-virginica),共 150 條記錄,每類各 50 個數據,每條記錄都有 4 項特徵:花萼長度、花萼寬度、花瓣長度、花瓣寬度,可以通過這4個特徵預測鳶尾花卉屬於哪一品種。

iris資料集包含在sklearn庫當中,具體在sklearn\datasets\data資料夾下,檔名為iris.csv。以本機為例。其路徑如下:
D:\python\lib\site-packages\sklearn\datasets\data\iris.csv

其中資料如下格式:

第一行資料意義如下:
150:資料集中資料的總條數
4:特徵值的類別數,即花萼長度、花萼寬度、花瓣長度、花瓣寬度。
setosa、versicolor、virginica:三種鳶尾花名

從第二行開始:
第一列為花萼長度值
第二列為花萼寬度值
第三列為花瓣長度值
第四列為花瓣寬度值
第五列對應是種類(三類鳶尾花分別用0,1,2表示)

三、演算法實現

1.演算法流程圖:

從以上流程圖可以看出,knn演算法包含後四步操作,所以將整個程式分為三個模組。

2.具體實現

(1)方法一
①利用slearn庫中的load_iris()匯入iris資料集
②使用train_test_split()對資料集進行劃分
③KNeighborsClassifier()設定鄰居數
④利用fit()構建基於訓練集的模型
⑤使用predict()進行預測
⑥使用score()進行模型評估
說明:本程式碼來源於《Python機器學習基礎教程》在此僅供學習使用。

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
import numpy as np

# 載入資料集
iris_dataset = load_iris()

# 資料劃分
X_train,X_test,y_train,y_test = train_test_split(iris_dataset['data'],iris_dataset['target'],random_state=0)

# 設定鄰居數
knn = KNeighborsClassifier(n_neighbors=1)

# 構建基於訓練集的模型
knn.fit(X_train,y_train)


# 一條測試資料
X_new = np.array([[5,2.9,1,0.2]])

# 對X_new預測結果
prediction = knn.predict(X_new)
print("預測值%d" % prediction)

# 得出測試集X_test測試集的分數
print("score:{:.2f}".format(knn.score(X_test,y_test)))

(2)方法二
①使用讀取檔案的方式,使用open、以及csv中的相關方法載入資料
②輸入測試集和訓練集的比率,對載入的資料使用shuffle()打亂後,計算訓練集及測試集個數對特徵值資料和對應的標籤資料進行分割。
③將分割後的資料,計算測試集資料與每一個訓練集的距離,使用norm()函式直接求二範數,或者載入資料使用np.sqrt(sum((test - train) ** 2))求得距離,使用argsort()將距離進行排序,並返回索引值,
④取出值最小的k個,獲得其標籤值,存進一個字典,標籤值為鍵,出現次數為值,對字典進行按值的大小遞減排序,將字典第一個鍵的值存入預測結果的列表中,計算完所有測試集資料後,返回一個列表。
⑤將預測結果與測試集本身的標籤進行對比,得出分數。

import csv
import random
import numpy as np
import operator


def openfile(filename):

  """
  開啟資料集,進行資料處理
  :param filename: 資料集的路徑
  :return: 返回資料集的資料,標籤,以及標籤名
  """

  with open(filename) as csv_file:
    data_file = csv.reader(csv_file)
    temp = next(data_file)

    # 資料集中資料的總數量
    n_samples = int(temp[0])

    # 資料集中特徵值的種類個數
    n_features = int(temp[1])

    # 標籤名
    target_names = np.array(temp[2:])

    # empty()函式構造一個未初始化的矩陣,行數為資料集數量,列數為特徵值的種類個數
    data = np.empty((n_samples,n_features))

    # empty()函式構造一個未初始化的矩陣,行數為資料集數量,1列,資料格式為int
    target = np.empty((n_samples,),dtype=np.int)

    for i,j in enumerate(data_file):

      # 將資料集中的將資料轉化為矩陣,資料格式為float
      # 將資料中從第一列到倒數第二列中的資料儲存在data中
      data[i] = np.asarray(j[:-1],dtype=np.float64)

      # 將資料集中的將資料轉化為矩陣,資料格式為int
      # 將資料集中倒數第一列中的資料儲存在target中
      target[i] = np.asarray(j[-1],dtype=np.int)

  # 返回 資料,標籤 和標籤名
  return data,target,target_names


def random_number(data_size):
  """
  該函式使用shuffle()打亂一個包含從0到資料集大小的整數列表。因此每次執行程式劃分不同,導致結果不同

  改進:
  可使用random設定隨機種子,隨機一個包含從0到資料集大小的整數列表,保證每次的劃分結果相同。

  :param data_size: 資料集大小
  :return: 返回一個列表
  """

  number_set = []
  for i in range(data_size):
    number_set.append(i)

  random.shuffle(number_set)

  return number_set


def split_data_set(data_set,target_data,rate=0.25):
  """
  說明:分割資料集,預設資料集的25%是測試集

  :param data_set: 資料集
  :param target_data: 標籤資料
  :param rate: 測試集所佔的比率
  :return: 返回訓練集資料、訓練集標籤、訓練集資料、訓練集標籤
  """

  # 計算訓練集的資料個數
  train_size = int((1-rate) * len(data_set))

  # 獲得資料
  data_index = random_number(len(data_set))

  # 分割資料集(X表示資料,y表示標籤),以返回的index為下標
  x_train = data_set[data_index[:train_size]]

  x_test = data_set[data_index[train_size:]]

  y_train = target_data[data_index[:train_size]]

  y_test = target_data[data_index[train_size:]]
  return x_train,x_test,y_test


def data_diatance(x_test,x_train):
  """
  :param x_test: 測試集
  :param x_train: 訓練集
  :return: 返回計算的距離
  """

  # sqrt_x = np.linalg.norm(test-train) # 使用norm求二範數(距離)
  distances = np.sqrt(sum((x_test - x_train) ** 2))
  return distances


def knn(x_test,x_train,k):
  """
  :param x_test: 測試集資料
  :param x_train: 訓練集資料
  :param y_train: 測試集標籤
  :param k: 鄰居數
  :return: 返回一個列表包含預測結果
  """

  # 預測結果列表,用於儲存測試集預測出來的結果
  predict_result_set=[]

  # 訓練集的長度
  train_set_size = len(x_train)

  # 建立一個全零的矩陣,長度為訓練集的長度
  distances = np.array(np.zeros(train_set_size))

  # 計算每一個測試集與每一個訓練集的距離
  for i in x_test:
    for indx in range(train_set_size):

      # 計算資料之間的距離
      distances[indx] = data_diatance(i,x_train[indx])

    # 排序後的距離的下標
    sorted_dist = np.argsort(distances)

    class_count = {}

    # 取出k個最短距離
    for i in range(k):

      # 獲得下標所對應的標籤值
      sort_label = y_train[sorted_dist[i]]

      # 將標籤存入字典之中並存入個數
      class_count[sort_label]=class_count.get(sort_label,0) + 1

    # 對標籤進行排序
    sorted_class_count = sorted(class_count.items(),key=operator.itemgetter(1),reverse=True)

    # 將出現頻次最高的放入預測結果列表
    predict_result_set.append(sorted_class_count[0][0])

  # 返回預測結果列表
  return predict_result_set


def score(predict_result_set,y_test):
  """

  :param predict_result_set: 預測結果列表
  :param y_test: 測試集標籤
  :return: 返回測試集精度
  """
  count = 0
  for i in range(0,len(predict_result_set)):
    if predict_result_set[i] == y_test[i]:
      count += 1

  score = count / len(predict_result_set)

  return score


if __name__ == "__main__":

  iris_dataset = openfile('iris.csv')
  # x_new = np.array([[5,0.2]])
  x_train,y_test = split_data_set(iris_dataset[0],iris_dataset[1])
  result = knn(x_test,6)
  print("原有標籤:",y_test)

  # 為了方便對比檢視,此處將預測結果轉化為array,可直接列印結果
  print("預測結果:",np.array(result))
  score = score(result,y_test)
  print("測試集的精度:%.2f" % score)

四、執行結果

結果不同,因為每次劃分的訓練集和測試集不同,具體見random_number()方法。

五、總結

在本次使用python實現knn演算法時,遇到了很多困難,如資料集的載入,資料的格式不能滿足後續需要,因此閱讀了sklearn庫中的一部分程式碼,有選擇性的進行了複用。資料與標籤無法分離,或是資料與標籤排序後後無法對應的情況,查詢許多資料後使用argsort()完美解決該問題。出現了n多錯誤,通過多次除錯之後最終完成。

附:本次實驗參考 :

①*鄭捷《機器學習演算法原理與程式設計實踐》
②《Python機器學習基礎教程》

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。