KNN算法原理
本篇博客基于《機器學習實戰》實現
算法原理簡要概括,重在代碼實現
k-近鄰算法(kNN)的工作原理是:存在一個樣本數據集合,稱訓練樣本集,并且樣本集中每個數據都存在標簽,即樣本集中每一數據與所屬分類的對應關系。輸入沒有標簽的新數據后,將新數據的每個特征與樣本集中數據對應的特征進行比較,然后算法提取樣本集中特征最相似的數據(距離最近)的分類標簽。
如圖,圖中綠點的標簽是未知的,但已知它屬于藍方塊和紅三角二者其一,怎么判斷出它屬于哪一方呢?
kNN算法的核心思想是如果一個樣本在特征空間中的k個最相鄰的樣本中的大多數屬于某一個類別,則該樣本也屬于這個類別,并具有這個類別上樣本的特性。
在上圖實線圓圈內,紅三角有兩個,而藍方塊只有一個,所以它是紅三角的可能性大;但在虛線圈內,紅三角有兩個,藍方塊卻有三個,那么它是藍方塊的可能性就越大;所以對于kNN算法,k的取值不同,得出的結果可能也會不同,k的取值很大程度上決定了這個模型的準確率。
KNN算法步驟
- 收集數據:爬蟲、公開數據源
- 數據清洗:處理缺失值、無關特征
- 導入數據,轉化為結構化的數據格式
- 數據歸一化、標準化
- 計算距離(歐氏距離最通用)
- 對距離升序排列,取前K個
- 判斷測試數據屬于哪個類別
- 計算模型準確率
KNN算法實現
其中Pclass,Sex,Age,SibSp,Parch五個特征會對標簽Survived造成較大影響,在Age這列中有缺失值,這里采用中位數(median),也可以選擇平均數(mean)填充。
數據獲取:泰坦尼克號生存數據
方法一
首先我們需要導入數據,將DataFrame轉化為一個矩陣,并將標簽存入一個列表
'''
Survived:1代表生存,0代表死亡
Sex:1代表男性,0代表女性
pclass:艙位等級
sibsp:配偶、兄弟姐妹個數
parch:父母、子女個數
'''
#打開文件,導入數據
def file(path):
# 打開文件
data = pd.read_csv(path)
#將DataFrame轉化為矩陣
feature_matrix = array(data.iloc[:, 1:6])
label = []
for i in data['Survived']:
label.append(i)
return feature_matrix, label
多維數組轉化為矩陣在后期對于數據歸一化很友好,將標簽存入列表在比較真實結果與預測結果時索引簡便
k-NN算法的核心步驟就是計算兩者之間的距離、距離排序、類別統計,本文采用歐幾里得距離公式
具體函數如下
#計算距離
def classify(test_data,train_data,label,k):
Size = train_data.shape[0]
#將測試數據每一行復制Size次減去訓練數據,橫向復制Size次,縱向復制1次
the_matrix = tile(test_data,(Size,1)) - train_data
#將相減得到的結果平方
sq_the_matrix = the_matrix ** 2
#平方加和,axis = 1 代表橫向
all_the_matrix = sq_the_matrix.sum(axis = 1)
#結果開根號得到最終距離
distance = all_the_matrix ** 0.5
#將距離由小到大排序,給出結果為索引
sort_distance = distance.argsort()
dis_Dict = {}
#取到前k個
for i in range(k):
#獲取前K個標簽
the_label = label[sort_distance[i]]
#將標簽的key和value傳入字典
dis_Dict[the_label] = dis_Dict.get(the_label,0)+1
#將字典按value值的大小排序,由大到小,即在K范圍內,生存和死亡的個數
sort_Count = sorted(dis_Dict.items(), key=operator.itemgetter(1), reverse=True)
return sort_Count[0][0]
numpy有一個tile方法,可以將一個一維矩陣橫向復制若干次,縱向復制若干次,所以將一個測試數據經過tile方法處理后再減去訓練數據,得到新矩陣后,再將該矩陣中每一條數據(橫向)平方加和并開根號后即可得到測試數據與每一條訓練數據之間的距離。
下一步將所有距離升序排列,取前K個距離,并在這個范圍里,統計1(生存)、0(死亡)兩個類別的個數,并返回出現次數較多那個類別的標簽。
這份數據中,就Age這一列而言,數據分布在0-80之間,而其他特征中,數據都分布在0-3之間,相比而言,Age這個特征的權重比較大,所以在計算距離時,需要進行歸一化處理,不然會出現大數吃小數的情況
歸一化公式: x' = (x - X_min) / (X_max - X_min)
#歸一化
def normalize(train_data):
#獲得訓練矩陣中的最小和最大的一個
min = train_data.min(0)
max = train_data.max(0)
#最大值和最小值的范圍
ranges = max - min
#訓練數據減去最小值
normalmatrix = train_data - tile(min, (train_data.shape[0], 1))
#除以最大和最小值的范圍,得到歸一化數據
normalmatrix = normalmatrix / tile(ranges, (train_data.shape[0], 1))
#返回歸一化數據結果,數據范圍,最小值
return normalmatrix
這個函數返回的是一個所有數據都分布在0-1之間的特征矩陣,不會出現偏重的情況。
最后一步:劃分數據集,取九份作為訓練數據集,取一份作為測試數據集,比較預測結果和真實結果,并計算出該模型的準確率,代碼如下:
#測試數據
def Test():
#打開的文件名
path = "Titanic.csv"
#返回的特征矩陣和特征標簽
feature_matrix, label = file(path)
#返回歸一化后的特征矩陣
normalmatrix = normalize(feature_matrix)
#獲取歸一化矩陣后的行數
m = normalmatrix.shape[0]
#取所有數據的百分之十
num = m//10
correct = 0.0
for i in range(num):
#前num數據作為測試集,num-m的數據作為訓練集
classifierResult = classify(normalmatrix[i,:], normalmatrix[num:m,:],
label[num:m], 9)
#比對預測結果和真實結果
print("預測結果:%d\t真實結果:%d" % (classifierResult, label[i]))
if classifierResult == label[i]:
correct += 1.0
print("正確率:{:.2f}%".format(correct/float(num)*100))
# 程序結束時間,并輸出程序運行時間
end = time.time()
print (str(end-start))
代碼部分結束,代碼運行截圖如下
不同K取值對應模型準確率如下
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|
69.66% | 69.66% | 75.28% | 73.03% | 74.16% | 73.03% | 77.53% | 77.53% | 79.78% | 77.53% |
對部分數據分析
由上圖可以得到以下結論:
- 三艙遇難的人數最多,幸存人數微乎其微;二艙遇難的人年齡分布較廣,但年齡比較小的都幸存了;而一艙遇難人數最少且大多為年紀較大的老人——艙位等級比較重要
- 女性整體存活率要比男性高出很多——這里存在著真愛
方法二
部分代碼如下:
#計算距離
def distance(d1,d2):
res = 0
for key in ('Pclass','Sex','Age','SibSp','Parch'):
#將每一行數據兩兩對應相減,計算距離
res += (float(d1[key])-float(d2[key]))**2
return res ** 0.5
#KNN算法
def KNN(data,train_data):
data_list = [
#只保留Survived和數據之間的距離兩個量
({'result':train['Survived'],'distance':distance(data,train)})
for train in train_data
]
#將列表按照distance的大小排序
data_list = sorted(data_list,key= lambda item:item['distance'])
#取到前K個
data_list2 = data_list[0:K]
result_list = []
#判斷在K范圍內,測試數據更偏向哪一方
for i in data_list2:
m = i['result']
result_list.append(m)
sum_1 = 0
sum_0 = 0
for i in result_list:
if i == '1':
sum_1 +=1
else:
sum_0 +=1
if sum_1>sum_0:
return '1'
else:
return '0'
方法二主要是運用字典方法,對數據進行讀取與統計,不同于方法一的特征矩陣,但萬變不離其宗,算法的核心思想都是一致的。代碼運行截圖如下:
方法一K值最終取9,方法二K值最終取8,兩種方法相比,方法一建模的準確率更高,并且程序運行時間也較短,個人認為方法二運用字典知識比較容易理解,而方法一較多運用矩陣知識。
公眾號“奶糖貓”后臺回復“Titanic”可獲取源碼和數據供參考,感謝支持。