第 1 周任務
分類問題:K-鄰近算法
分類問題:決策樹
第 2 周任務
分類問題:樸素貝葉斯
分類問題:邏輯迴歸
第 3 周任務
分類問題:支持向量機
第 4 周任務
分類問題:AdaBoost
第 5 周任務
迴歸問題:線性迴歸、嶺迴歸、套索方法、逐步迴歸等
迴歸問題:樹迴歸
第 6 周任務
聚類問題:K均值聚類
相關問題:Apriori
第 7 周任務
相關問題:FP-Growth
第 8 周任務
簡化數據:PCA主成分分析
簡化數據:SVD奇異值分解
文章目錄
1. K-近鄰算法
1.1. K-近鄰法簡介
K-近鄰法(K-nearest neighbor, K-NN)是1967年由Cover T和Hart P提出的一種基本分類與迴歸方法。
它的工作原理是:存在一個樣本數據集合,也稱作爲訓練樣本集,並且樣本集中每個數據都存在標籤,即我們知道樣本集中每一個數據與所屬分類的對應關係。輸入沒有標籤的新數據後,將新的數據的每個特徵與樣本集中數據對應的特徵進行比較,然後算法提取樣本最相似數據(最近鄰)的分類標籤。
一般來說,我們只選擇樣本數據集中前K個最相似的數據,這就是K-近鄰算法中K的出處,通常K是不大於20的整數。最後,選擇K個最相似數據中出現次數最多的分類,作爲新數據的分類。
舉個簡單的例子,我們可以使用k-近鄰算法分類一個電影是動作片還是喜劇片。
電影名稱 | 打鬥鏡頭 | 搞笑鏡頭 | 電影類型 |
---|---|---|---|
電影1 | 10 | 99 | 喜劇片 |
電影2 | 5 | 111 | 喜劇片 |
電影3 | 90 | 17 | 動作片 |
電影4 | 70 | 30 | 動作片 |
表1.1就是我們已有的數據集合,也就是訓練樣本集。這個數據集有兩個特徵,即打鬥鏡頭數和搞笑鏡頭數。除此之外,我們也知道每個電影的所屬類型,即分類標籤。用肉眼粗略地觀察,搞笑鏡頭多的,是喜劇片。打鬥鏡頭多的,是動作片。以我們多年的看片經驗,這個分類還算合理。如果現在給我一部電影,你告訴我這個電影打鬥鏡頭數和搞笑鏡頭數。不告訴我這個電影類型,我可以根據你給我的信息進行判斷,這個電影是屬於喜劇片還是動作片。而K-近鄰算法也可以像我們人一樣做到這一點,不同的地方在於,我們的經驗更豐富,而K-鄰近算法是靠已有的數據。比如,你告訴我這個電影打鬥鏡頭數爲2,搞笑鏡頭數爲102,我的經驗會告訴你這個是喜劇片,k-近鄰算法也會告訴你這個是喜劇片。你又告訴我另一個電影打鬥鏡頭數爲49,搞笑鏡頭數爲51,我們會意識到,這有可能是個喜劇&動作片,但是K-近鄰算法不會告訴你這些,因爲在它的眼裏,電影類型只有喜劇片和動作片,它會提取樣本集中特徵最相似數據(最鄰近)的分類標籤,得到的結果可能是喜劇片,也可能是動作片,但絕不會是喜劇&動作片。當然,這些取決於數據集的大小以及最近鄰的判斷標準等因素。
1.2. 距離度量
我們已經知道K-近鄰算法根據特徵比較,然後提取樣本集中特徵最相似數據(最鄰近)的分類標籤。那麼,如何進行比較呢?比如,我們還是以表1.1爲例,怎麼判斷紅色圓點標記的電影所屬的類別呢?如圖1.1所示。
我們可以從散點圖大致推斷,這個紅色圓點標記的電影可能屬於動作片,因爲距離已知的那兩個動作片的圓點更近。k-近鄰算法用什麼方法進行判斷呢?沒錯,就是距離度量。這個電影分類的例子有2個特徵,也就是在2維實數向量空間,可以使用我們高中學過的兩點距離公式計算距離,如圖1.2所示。
通過計算,我們可以得到如下結果:
- (40,60)到喜劇片(10,99)的距離約爲49.20
- (40,60)到喜劇片(5,111)的距離約爲61.85
- (40,60)到動作片(90,19)的距離約爲64.66
- (40,60)到動作片(70,30)的距離約爲42.43
通過計算可知,未知標記的電影到動作片 (70,30)的距離最近,爲42.43。如果算法直接根據這個結果,判斷該紅色圓點標記的電影爲動作片,這個算法就是最近鄰算法,而非K-近鄰算法。那麼K-鄰近算法是什麼呢?K-近鄰算法步驟如下:
- 計算已知類別數據集中的點與當前點之間的距離;
- 按照距離遞增次序排序;
- 選取與當前點距離最小的K個點;
- 確定前K個點所在類別的出現頻率;
- 返回前K個點所出現頻率最高的類別作爲當前點的預測分類。
比如,現在我這個K值取3,那麼在電影例子中,按距離依次排序的三個點分別是動作片(70,30)、喜劇片(10,99)、喜劇片(5,111)。在這三個點中,喜劇片出現的頻率爲三分之二,動作片出現的頻率爲三分之一,所以該紅色圓點標記的電影爲喜劇片。這個判別過程就是K-近鄰算法。
1.3. Python3代碼實現
我們已經知道了k-近鄰算法的原理,那麼接下來就是使用Python3實現該算法,依然以電影分類爲例。
1.3.1 準備數據集
對於表1.1中的數據,我們可以使用numpy直接創建,代碼如下:
import numpy as np
"""
Parameters:
無
Returns:
group - 數據集
labels - 分類標籤
"""
def createDataSet():
#四組二維特徵
group = np.array([[10,99],[5,111],[90,17],[70,30]])
#四組特徵的標籤
labels = ['喜劇片','喜劇片','動作片','動作片']
return group, labels
if __name__ == '__main__':
#創建數據集
group, labels = createDataSet()
#打印數據集
print(group)
print(labels)
"""
[[10,99]
[5,111]
[90,17]
[70,30]]
['喜劇片','喜劇片','動作片','動作片']
"""
1.3.2. K-近鄰算法
根據兩點距離公式,計算距離,選擇距離最小的前k個點,並返回分類結果。
import numpy as np
import operator
"""
Parameters:
inX - 用於分類的數據(測試集)
dataSet - 用於訓練的數據(訓練集)
labes - 分類標籤
K - KNN算法參數,選擇距離最小的K個點
Returns:
sortedClassCount[0][0] - 分類結果
"""
def classify0(inX, dataSet, labels, K):
#numpy函數shape[0]返回dataSet的行數
dataSetSize = dataSet.shape[0]
#在列向量方向上重複inX共1次(橫向),行向量方向上重複inX共dataSetSize次(縱向)
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
#二維特徵相減後平方
sqDiffMat = diffMat**2
#sum()所有元素相加,sum(0)列相加,sum(1)行相加
sqDistances = sqDiffMat.sum(axis=1)
#開方,計算出距離
distances = sqDistances**0.5
#返回distances中元素從小到大排序後的索引值
sortedDistIndices = distances.argsort()
#定一個記錄類別次數的字典
classCount = {}
for i in range(K):
#取出前k個元素的類別
voteIlabel = labels[sortedDistIndices[i]]
#dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回默認值。
#計算類別次數
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
#python3中用items()替換python2中的iteritems()
#key=operator.itemgetter(1)根據字典的值進行排序
#key=operator.itemgetter(0)根據字典的鍵進行排序
#reverse降序排序字典
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
#返回次數最多的類別,即所要分類的類別
return sortedClassCount[0][0]
1.3.3 整體代碼
這裏預測紅色圓點標記的電影(40,60)的類別,K-NN的K值爲3。創建KNN_test01.py文件,編寫代碼如下:
import numpy as np
import operator
"""
Parameters:
無
Returns:
group - 數據集
labels - 分類標籤
"""
def createDataSet():
#四組二維特徵
group = np.array([[10,99],[5,111],[90,17],[70,30]])
#四組特徵的標籤
labels = ['喜劇片','喜劇片','動作片','動作片']
return group, labels
"""
Parameters:
inX - 用於分類的數據(測試集)
dataSet - 用於訓練的數據(訓練集)
labes - 分類標籤
K - KNN算法參數,選擇距離最小的K個點
Returns:
sortedClassCount[0][0] - 分類結果
"""
def classify0(inX, dataSet, labels, K):
#numpy函數shape[0]返回dataSet的行數
dataSetSize = dataSet.shape[0]
#在列向量方向上重複inX共1次(橫向),行向量方向上重複inX共dataSetSize次(縱向)
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
#二維特徵相減後平方
sqDiffMat = diffMat**2
#sum()所有元素相加,sum(0)列相加,sum(1)行相加
sqDistances = sqDiffMat.sum(axis=1)
#開方,計算出距離
distances = sqDistances**0.5
#返回distances中元素從小到大排序後的索引值
sortedDistIndices = distances.argsort()
#定一個記錄類別次數的字典
classCount = {}
for i in range(K):
#取出前k個元素的類別
voteIlabel = labels[sortedDistIndices[i]]
#dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回默認值。
#計算類別次數
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
#python3中用items()替換python2中的iteritems()
#key=operator.itemgetter(1)根據字典的值進行排序
#key=operator.itemgetter(0)根據字典的鍵進行排序
#reverse降序排序字典
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
#返回次數最多的類別,即所要分類的類別
return sortedClassCount[0][0]
if __name__ == '__main__':
#創建數據集
group, labels = createDataSet()
#測試集
test = [40,60]
#KNN分類
test_class = classify0(test, group, labels, 3)
#打印分類結果
print(test_class)
"""
喜劇片
"""
2. 項目案例
2.1. 優化約會網站的配對效果
2.1.1. 項目概述
海倫使用約會網站尋找約會對象。經過一段時間之後,她發現曾交往過三種類型的人:
- 不喜歡的人
- 魅力一般的人
- 極具魅力的人
她希望:
- 不喜歡的人則直接排除掉
- 工作日與魅力一般的人約會
- 週末與極具魅力的人約會
現在她收集到了一些約會網站未曾記錄的數據信息,這更有助於匹配對象的歸類。
2.1.2. 開發流程
2.1.2.1. 收集數據
此案例書中提供了文本文件。
海倫把這些約會對象的數據存放在文本文件 datingTestSet2.txt
中,總共有 1000 行。海倫約會的對象主要包含以下 3 種特徵:
Col1
:每年獲得的飛行常客里程數Col2
:玩視頻遊戲所耗時間百分比Col3
:每週消費的冰淇淋公升數
文本文件數據格式如下:
40920 8.326976 0.953952 3
14488 7.153469 1.673904 2
26052 1.441871 0.805124 1
75136 13.147394 0.428964 1
38344 1.669788 0.134296 1
2.1.2.2. 準備數據
使用 Python 解析文本文件。將文本記錄轉換爲 NumPy 的解析程序如下所示:
import numpy as np
def file2matrix(filename):
"""
Desc:
導入訓練數據
parameters:
filename: 數據文件路徑
return:
數據矩陣 returnMat 和對應的類別 classLabelVector
"""
fr = open(filename)
# 獲得文件中的數據行的行數
lines = fr.readlines()
numberOfLines = len(lines) # type: int
# 生成對應的空矩陣
# 例如:zeros(2,3)就是生成一個 2*3的矩陣,各個位置上全是 0
returnMat = np.zeros((numberOfLines, 3)) # prepare matrix to return
classLabelVector = [] # prepare labels return
index = 0
for line in lines:
# str.strip([chars]) --返回已移除字符串頭尾指定字符所生成的新字符串
line = line.strip()
# 以 '\t' 切割字符串
listFromLine = line.split('\t')
# 每列的屬性數據
returnMat[index, :] = listFromLine[0:3]
# 每列的類別數據,就是 label 標籤數據
classLabelVector.append(int(listFromLine[-1]))
index += 1
# 返回數據矩陣returnMat和對應的類別classLabelVector
return returnMat, classLabelVector
2.1.2.3. 分析數據
使用 Matplotlib 畫二維散點圖。
import matplotlib.pyplot as plt
if __name__ == '__main__':
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
color = ['r', 'g', 'b']
fig = plt.figure()
ax = fig.add_subplot(311)
for i in range(1, 4):
index = np.where(np.array(datingLabels) == i)
ax.scatter(datingDataMat[index, 0], datingDataMat[index, 1], c=color[i - 1], label=i)
plt.xlabel('Col.0')
plt.ylabel('Col.1')
plt.legend()
bx = fig.add_subplot(312)
for i in range(1, 4):
index = np.where(np.array(datingLabels) == i)
bx.scatter(datingDataMat[index, 0], datingDataMat[index, 2], c=color[i - 1], label=i)
plt.xlabel('Col.0')
plt.ylabel('Col.2')
plt.legend()
cx = fig.add_subplot(313)
for i in range(1, 4):
index = np.where(np.array(datingLabels) == i)
cx.scatter(datingDataMat[index, 1], datingDataMat[index, 2], c=color[i - 1], label=i)
plt.xlabel('Col.1')
plt.ylabel('Col.2')
plt.legend()
plt.show()
圖中清晰地標識了三個不同的樣本分類區域,具有不同愛好的人其類別區域也不同。
歸一化特徵值,消除特徵之間量級不同導致的影響。
def autoNorm(dataSet):
"""
Desc:
歸一化特徵值,消除特徵之間量級不同導致的影響
parameter:
dataSet: 數據集
return:
歸一化後的數據集 normDataSet.ranges和minVals即最小值與範圍,並沒有用到
歸一化公式:
Y = (X-Xmin)/(Xmax-Xmin)
其中的 min 和 max 分別是數據集中的最小特徵值和最大特徵值。該函數可以自動將數字特徵值轉化爲0到1的區間。
"""
# 計算每種屬性的最大值、最小值、範圍
minVals = np.min(dataSet, axis=0)
maxVals = np.max(dataSet, axis=0)
# 極差
ranges = maxVals - minVals
m = dataSet.shape[0]
# 生成與最小值之差組成的矩陣
normDataSet = dataSet - np.tile(minVals, (m, 1))
# 將最小值之差除以範圍組成矩陣
normDataSet = normDataSet / np.tile(ranges, (m, 1)) # element wise divide
return normDataSet, ranges, minVals
2.1.2.4. 訓練算法
此步驟不適用於 k-近鄰算法。因爲測試數據每一次都要與全部的訓練數據進行比較,所以這個過程是沒有必要的。
import operator
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0]
# 距離度量 度量公式爲歐氏距離
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
sqDiffMat = diffMat ** 2
sqDistances = np.sum(sqDiffMat, axis=1)
distances = sqDistances ** 0.5
# 將距離排序:從小到大
sortedDistIndicies = distances.argsort()
# 選取前K個最短距離, 選取這K箇中最多的分類類別
classCount = {}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
2.1.2.5. 測試算法
計算錯誤率,使用海倫提供的部分數據作爲測試樣本。如果預測分類與實際類別不同,則標記爲一個錯誤。
def datingClassTest():
"""
Desc:
對約會網站的測試方法
parameters:
none
return:
錯誤數
"""
# 設置測試數據的的一個比例
hoRatio = 0.1 # 測試範圍,一部分測試一部分作爲樣本
# 從文件中加載數據
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt') # load data setfrom file
# 歸一化數據
normMat, ranges, minVals = autoNorm(datingDataMat)
# m 表示數據的行數,即矩陣的第一維
m = normMat.shape[0]
# 設置測試的樣本數量, numTestVecs:m表示訓練樣本的數量
numTestVecs = int(m * hoRatio)
print('numTestVecs=', numTestVecs)
errorCount = 0.0
for i in range(numTestVecs):
# 對數據測試
classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
print("分類器返回結果: %d, 實際結果: %d" % (classifierResult, datingLabels[i]))
if classifierResult != datingLabels[i]:
errorCount += 1.0
print("錯誤率: %f" % (errorCount / float(numTestVecs)))
print(errorCount)
2.1.2.6. 使用算法
產生簡單的命令行程序,然後海倫可以輸入一些特徵數據以判斷對方是否爲自己喜歡的類型。
約會網站預測函數如下:
def classifyPerson():
resultList = ['不喜歡的人', '魅力一般的人', '極具魅力的人']
ffMiles = float(input("每年獲得的飛行常客里程數?"))
percentTats = float(input("玩視頻遊戲所耗時間百分比?"))
iceCream = float(input("每週消費的冰淇淋公升數?"))
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = np.array([ffMiles, percentTats, iceCream])
intX = (inArr - minVals) / ranges
classifierResult = classify0(intX, normMat, datingLabels, 3)
print("這個人屬於: ", resultList[classifierResult - 1])
實際運行效果如下:
if __name__ == '__main__':
classifyPerson()
'''
每年獲得的飛行常客里程數? 10000
玩視頻遊戲所耗時間百分比? 10
每週消費的冰淇淋公升數? 0.5
這個人屬於: 魅力一般的人
'''
未完待續...
參考資料
- https://blog.csdn.net/c406495762/article/details/75172850
- https://blog.csdn.net/LSGO_MYP/article/details/103048796