一、前言
如何區分一部片是愛情片還是動作片,愛情片中也有動作,動作片中也有親吻的鏡頭。但是,動作片中打鬥的場景更多,愛情片中親吻的鏡頭更多。所以,可以引入“統計概率”的概念,基於某類場景出現的次數來分類。
二、k近鄰算法概述
(1)原理:
給定一堆數據集,對於新輸入的測試樣例,通過計算其與數據集不同樣本特徵值之間的距離,從中找出與測試樣例最相近的k個鄰居。通過判斷這k個鄰居中哪種類別佔多數,則預測該測試樣例也屬於該類。
(2)優點:
精度高,對異常值不敏感(只與相近的k個值相關),無數據輸入假定
(3)缺點:
計算複雜度高,空間複雜度高,學習效率低(算不上學習,只是統計)
(4)特別注意:
其中,最重要的參數是K的選擇。
如下圖所示,輸入綠色圓圈爲測試樣例。當K=3時,根據原理,最近的3個鄰居中,紅色三角佔多數,則預測綠色圓圈屬於紅色三角類;當K=5時,同理,5個鄰居中,藍色方塊佔多數,則預測綠色圓圈屬於藍色方塊類。
因此,K的選擇將直接影響了預測的結果。如果K值過小,則對於鄰近的訓練數據特別敏感,容易學習到噪點數據,而造成過擬合;但是如果K值過大,把所有數據都當做鄰居來預測,那就減弱了學習能力,只是簡單地統計數值而已。
因此,根據李航的《統計學習方法》書中所說,K的選擇,應該先以一個小值開始,利用交叉驗證的方法,通過不斷調參來選取最優值。通常k是不大於20的整數。
其次,對於距離公式的選擇,一般是選擇歐式距離,若p(p1,p2,…,pn)和q(q1,q2,…,qn)是空間的兩點,則他們的距離爲:
三、實踐出真知
(1)核心算法
創建一個kNN.py文件,根據以上原理,寫出核心算法代碼。
def kNN_classify(test_datas, train_data_set, labels, k):
""" k近鄰分類算法 """
data_set_size = train_data_set.shape[0] # 數據集個數,行數
# 1、計算新數據與訓練集的歐氏距離
# tile是將測試集複製成與訓練集同行數,一列
diff_mat = tile(test_datas, (data_set_size, 1)) - train_data_set #(x1-x2)
sq_diff_mat = diff_mat ** 2 #(x1-x2)平方
sq_distances = sq_diff_mat.sum(axis=1) # 按行累加
distances = sq_distances ** 0.5 # 開根號算出歐式距離
# 2、按照距離遞增排序
sorted_dist_indicies = distances.argsort()
# 3、選取距離最近的前 k個,計算類別個數
class_count = {} # key是類別,value是個數
for i in range(k):
vote_label = labels[sorted_dist_indicies[i]]
# 這裏字典的get函數是將找key爲vote_label的值,若找不到,則用0
class_count[vote_label] = class_count.get(vote_label, 0) + 1 # 計算類別個數
# 4、排序找出類別個數最高的作爲預測結果
sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1),
reverse=True) # 按照value(類的個數)逆序排序,找出類別最多的
return sorted_class_count[0][0] # [0][0]是預測的標籤,[0][1]爲k中最多的類別數
完整的驗證kNN算法的程序文件爲:
from numpy import *
import operator
def create_dataset():
""" 生成數據集 """
train_datas = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
labels = ['動作片', '動作片', '愛情片', '愛情片']
return train_datas, labels
def kNN_classify(test_datas, train_data_set, labels, k):
""" k近鄰分類算法 """
data_set_size = train_data_set.shape[0] # 數據集個數,行數
# 1、計算新數據與訓練集的歐氏距離
# tile是將測試集複製成與訓練集同行數,一列
diff_mat = tile(test_datas, (data_set_size, 1)) - train_data_set #(x1-x2)
sq_diff_mat = diff_mat ** 2 #(x1-x2)平方
sq_distances = sq_diff_mat.sum(axis=1) # 按行累加
distances = sq_distances ** 0.5 # 開根號算出歐式距離
# 2、按照距離遞增排序
sorted_dist_indicies = distances.argsort()
# 3、選取距離最近的前 k個,計算類別個數
class_count = {} # key是類別,value是個數
for i in range(k):
vote_label = labels[sorted_dist_indicies[i]]
# 這裏字典的get函數是將找key爲vote_label的值,若找不到,則用0
class_count[vote_label] = class_count.get(vote_label, 0) + 1 # 計算類別個數
# 4、排序找出類別個數最高的作爲預測結果
sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1),
reverse=True) # 按照value(類的個數)逆序排序,找出類別最多的
return sorted_class_count[0][0] # [0][0]是預測的標籤,[0][1]爲k中最多的類別數
if __name__ == '__main__':
train_datas, labels = create_dataset()
test_1 = [1.0, 0.9]
pred_class = kNN_classify(test_1, train_datas, labels, 3)
print("test_1預測爲:", pred_class)
test_2 = [0.2, 0.2]
pred_class = kNN_classify(test_2, train_datas, labels, 3)
print("test_2預測爲:", pred_class)
結果爲:
test_1預測爲: 動作片
test_2預測爲: 愛情片
(2)約會人員喜好預測
註釋已經很詳細,所以簡單粗暴上代碼:
from numpy import *
from kNN import kNN_classify
import matplotlib.pyplot as plt
import sys
def file_to_dataset(filename):
""" 將文件的數據集轉換成分類器的輸入格式 """
try:
with open(filename) as f_obj:
# 一次性讀整個文件,自動將內容分析成一個行的字符串列表
list_of_lines = f_obj.readlines()
except FileNotFoundError:
print("Sorry, the file " + filename + " does not exist.")
sys.exit()
numbers_of_lines = len(list_of_lines) # 獲取文件行數
dataset = zeros((numbers_of_lines, 3))
labels = []
index = 0
for line in list_of_lines:
line = line.strip() # 去掉頭尾的指定字符,默認是空格或換行符
list_from_line = line.split('\t') # 以'\t'分割行,形成列表
# 取前三列放進矩陣中, matrix[index, :]表示index行所有列
dataset[index, :] = list_from_line[0:3]
labels.append(int(list_from_line[-1])) # 取最後一列作爲標籤放進標籤列表
index += 1
return dataset, labels
def show_dataset(dataset, labels):
""" 可視化數據 """
fig = plt.figure() # 創建一個畫板對象
ax = fig.add_subplot(111) # 創建子畫板,位於第一行第一列第一個位置
colors = []
for i in labels:
if i == 1:
colors.append('red')
elif i== 2:
colors.append('green')
else :
colors.append('blue')
ax.scatter(x=dataset[:, 0], y=dataset[:, 1],
c=colors, s=15) # 取第2、3列作爲x、y畫散列圖
plt.show()
def normalization(dataset):
"""
將數據集歸一化到(0, 1)之間
new_value = (old_value-min)/(max-min)
"""
min_values = dataset.min(0) # 參數0確保取該矩陣每一列的最小值,1是每一行的最小值
max_values = dataset.max(0)
ranges = max_values - min_values
norm_dateset = zeros(shape(dataset))
num_of_lines = dataset.shape[0]
# tile是複製功能
norm_dateset = dataset - tile(min_values, (num_of_lines, 1))
norm_dateset = norm_dateset / tile(ranges, (num_of_lines, 1))
return norm_dateset, ranges, min_values
def dating_test():
""" 測試分類器效率 """
ratio = 0.10 # 測試集佔 10%
train_datas, labels = file_to_dataset('ch02_kNN\datingTestSet2.txt')
norm_dataset, ranges, min_values = normalization(train_datas)
num_of_lines = norm_dataset.shape[0] # 總行數
num_test_data = int(num_of_lines * ratio) # 測試行數
error_count = 0.0
for i in range(num_test_data):
res = kNN_classify(norm_dataset[i, :],
norm_dataset[num_test_data: num_of_lines, :],
labels[num_test_data: num_of_lines], 4)
print("the classifier prediction is: " + str(res) +
", the real answer is:" + str(labels[i]))
if res != labels[i] :
error_count += 1.0
print("Error rate : " + str((error_count/float(num_test_data)*100)) + "%")
print("Accuracy rate : "+str((1-(error_count/float(num_test_data)))*100)+"%")
def classify_person():
""" 輸出完整結果 """
res_lists = ['not at all', 'in small doses', 'in large doses']
percent_tats = float(input("percentage of time spend playing vodeo games?"))
ff_miles = float(input("frequent flier miles earned per year?"))
ice_cream = float(input("liters of ice cream consumed per year?"))
train_datas, labels = file_to_dataset('ch02_kNN\datingTestSet2.txt')
norm_dataset, ranges, min_values = normalization(train_datas)
new_test = array([ff_miles, percent_tats, ice_cream])
new_test = (new_test-min_values)/ranges # 對新數據歸一化
res = kNN_classify(new_test, norm_dataset, labels, 4)
print("You will probably like this person: ", res_lists[res-1])
if __name__ == '__main__':
'''
# 準備數據
train_datas, labels = file_to_dataset('ch02_kNN\datingTestSet2.txt')
print("train_datas:\n", train_datas)
print("labels:\n", labels[0:20])
# 可視化
show_dataset(train_datas, labels)
# 歸一化
norm_dataset, ranges, min_values = normalization(train_datas)
print("歸一化後數據集:\n", norm_dataset)
print("每列的數值範圍:\n", ranges)
print("每列最小值:\n", min_values)
# 測試
dating_test()
'''
# 使用
classify_person()
結果爲:
percentage of time spend playing vodeo games?10
frequent flier miles earned per year?10000
liters of ice cream consumed per year?0.5
You will probably like this person: in small doses
(3)手寫數字識別
簡單粗暴上代碼:
from numpy import *
from kNN import kNN_classify
import sys, os
def img_to_vector(filename):
""" 將一張數字圖像轉成向量格式 """
digit_vector = zeros((1, 1024))
try:
with open(filename) as f_obj:
for i in range(32):
list_of_line = f_obj.readline()
for j in range(32):
digit_vector[0, 32*i+j] = int(list_of_line[j])
return digit_vector
except FileNotFoundError:
print("Sorry, the file " + filename + " does not exist.")
sys.exit()
def create_train_set():
""" 將文件數據轉爲輸入格式 """
labels = [] # 存放訓練集的標籤
train_file_list = os.listdir(r"ch02_KNN\digits\trainingDigits") # 得到所有文件名
num_train_set = len(train_file_list) # 訓練的圖片個數
train_datas = zeros((num_train_set, 1024)) # 創建訓練集矩陣
# 遍歷處理每一張圖片
for i in range(num_train_set):
full_file_name = train_file_list[i] # 0_13.txt
file_name = full_file_name.split('.')[0] # 以‘。’分割,取第一個,即:0_13
label = int(file_name.split('_')[0]) # 以‘_’分割,取第一個,0就是標籤
labels.append(label)
train_datas[i,:] = img_to_vector(
r"ch02_KNN\digits\trainingDigits\%s" % full_file_name
)
return train_datas, labels
def hand_writing_test():
""" 使用kNN算法識別手寫數字 """
# 獲取訓練集
train_datas, labels = create_train_set()
# 處理測試集
test_file_list = os.listdir(r"ch02_KNN\digits\testDigits")
error_count = 0.0 # 統計錯誤率
num_test_set = len(test_file_list)
for i in range(num_test_set):
full_file_name = test_file_list[i]
file_name = full_file_name.split('.')[0]
label = int(file_name.split('_')[0])
test_data = img_to_vector(
r"ch02_KNN\digits\testDigits\%s" % full_file_name
)
# 使用 kNN分類器分類
pred_res = kNN_classify(test_data, train_datas, labels, 3)
print("the classifier prediction is: " + str(pred_res) +
", the real answer is:" + str(label))
if pred_res != label:
error_count += 1.0
print("Error rate : {:.2f}%".format(error_count/float(num_test_set)*100))
print("Accuracy rate : {:.2f}%".format((1-(error_count/float(num_test_set)))
*100))
if __name__ == "__main__":
hand_writing_test()
結果爲:
the classifier prediction is: 0, the real answer is:0
the classifier prediction is: 0, the real answer is:0
the classifier prediction is: 0, the real answer is:0
。。。
the classifier prediction is: 9, the real answer is:9
the classifier prediction is: 9, the real answer is:9
the classifier prediction is: 9, the real answer is:9
the classifier prediction is: 9, the real answer is:9
Error rate : 1.06%
Accuracy rate : 98.94%
以上,就是這個算法的介紹和實踐,有錯誤的希望留言指正,有疑問的也歡迎留言一起交流學習。