一、代碼
說明:step1、step2是爲了方便數據查看;step3、step4可單獨運行。
import numpy as np
import operator
def classify0(inX, dataSet, labels, k):
"""
函數說明:kNN算法,分類器
Parameters:
inX - 用於分類的數據(測試集)(1*m向量)
dataSet - 用於訓練的數據(訓練集)(n*m向量array)
labels - 分類標準(n*1向量array)
k - kNN算法參數,選擇距離最小的k個點
Returns:
sortedClassCount[0][0] - 分類結果
"""
# numpy函數shape[0]獲取dataSet的行數
dataSetSize = dataSet.shape[0]
# 將inX重複dataSetSize次並排成一列,即將inX賦值dataSetSize行、1列
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet # tile:複製函數
# 矩陣數乘:矩陣對應位置元素相乘(array()函數中矩陣的乘積可以使用np.matmul或者.dot()函數。而星號乘 (*)則表示矩陣對應位置元素相乘,與numpy.multiply()函數結果相同)
sqDiffMat = diffMat ** 2 # 每個元素 ** 2
# sum()所有元素相加,sum(0)列相加,sum(1)行相加
sqDistances = sqDiffMat.sum(axis=1)
# 開方,計算出距離
distances = sqDistances ** 0.5 # 每個元素 ** 0.5
# argsort函數返回的是distances值從小到大排序後的索引值
sortedDistIndicies = distances.argsort()
# 定義一個記錄類別次數的字典
classCount = {}
# 選擇距離最小的k個點
for i in range(k):
# 取出前k個元素的類別
voteIlabel = labels[sortedDistIndicies[i]]
# 字典的get()方法,返回指定鍵的值,如果值不在字典中返回0
# 計算類別次數
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]
def file2matrix(filename):
"""
函數說明:打開解析文件,對數據進行分類:不喜歡,魅力一般,極具魅力
Parameters:
filename - 文件名
Returns:
returnMat - 特徵矩陣
classLabelVector - 分類label向量
"""
with open(filename) as fr:
# 讀取文件所有內容
arrayOlines = fr.readlines()
# 得到文件行數
numberOfLines = len(arrayOlines)
# 返回的NumPy矩陣numberOfLines行,3列
returnMat = np.zeros((numberOfLines, 3))
# 創建分類標籤向量
classLabelVector = []
# 行的索引值
index = 0
# 讀取每一行
for line in arrayOlines:
# 去掉每一行首尾的空白符,例如'\n','\r','\t',' '
line = line.strip()
# 將每一行內容根據'\t'符進行切片,本例中一共有4列
listFromLine = line.split('\t')
# 將數據的前3列進行提取保存在returnMat矩陣中,也就是特徵矩陣
returnMat[index, :] = listFromLine[0:3]
# 根據文本內容進行分類1:不喜歡;2:一般;3:喜歡
classLabelVector.append(listFromLine[-1])
index += 1
# 返回標籤列向量以及特徵矩陣
return returnMat, classLabelVector
def showdatas(datingDataMat, datingLabels):
"""
函數說明:可視化數據
Parameters:
datingDataMat - 特徵矩陣
datingLabels - 分類Label
Returns:
None
"""
from matplotlib.font_manager import FontProperties
import matplotlib.lines as mlines
import matplotlib.pyplot as plt
# 設置漢字格式爲14號簡體字
font = FontProperties(fname=r"C:\Windows\Fonts\simsun.ttc", size=14)
# 將fig畫布分隔成1行1列,不共享x軸和y軸,fig畫布的大小爲(13,8)
# 當nrows=2,ncols=2時,代表fig畫布被分爲4個區域,axs[0][0]代表第一行第一個區域
fig, axs = plt.subplots(nrows=2, ncols=2, sharex=False, sharey=False, figsize=(13, 8))
# 獲取datingLabels的行數作爲label的個數
# numberOfLabels = len(datingLabels)
# label的顏色配置矩陣
LabelsColors = []
for i in datingLabels:
# didntLike
if i == "didntLike":
LabelsColors.append('black')
# smallDoses
if i == "smallDoses":
LabelsColors.append('orange')
# largeDoses
if i == "largeDoses":
LabelsColors.append('red')
# 圖一:每年獲得的飛行常客里程數與玩視頻遊戲所消耗時間佔比
# 畫出散點圖,以datingDataMat矩陣第一列爲x,第二列爲y,散點大小爲15, 透明度爲0.5
axs[0][0].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 1], color=LabelsColors, s=15, alpha=.5)
# 設置標題,x軸label, y軸label
axs0_title_text = axs[0][0].set_title(u'每年獲得的飛行常客里程數與玩視頻遊戲所消耗時間佔比', FontProperties=font)
axs0_xlabel_text = axs[0][0].set_xlabel(u'每年獲得的飛行常客里程數', FontProperties=font)
axs0_ylabel_text = axs[0][0].set_ylabel(u'玩視頻遊戲所消耗時間佔比', FontProperties=font)
plt.setp(axs0_title_text, size=9, weight='bold', color='red')
plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black')
# 圖二:每年獲得的飛行常客里程數與每週消費的冰淇淋公升數
# 畫出散點圖,以datingDataMat矩陣第一列爲x,第三列爲y,散點大小爲15, 透明度爲0.5
axs[0][1].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
# 設置標題,x軸label, y軸label
axs1_title_text = axs[0][1].set_title(u'每年獲得的飛行常客里程數與每週消費的冰淇淋公升數', FontProperties=font)
axs1_xlabel_text = axs[0][1].set_xlabel(u'每年獲得的飛行常客里程數', FontProperties=font)
axs1_ylabel_text = axs[0][1].set_ylabel(u'每週消費的冰淇淋公升數', FontProperties=font)
plt.setp(axs1_title_text, size=9, weight='bold', color='red')
plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black')
# 圖三:玩視頻遊戲所消耗時間佔比與每週消費的冰淇淋公升數
# 畫出散點圖,以datingDataMat矩陣第二列爲x,第三列爲y,散點大小爲15, 透明度爲0.5
axs[1][0].scatter(x=datingDataMat[:, 1], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
# 設置標題,x軸label, y軸label
axs2_title_text = axs[1][0].set_title(u'玩視頻遊戲所消耗時間佔比與每週消費的冰淇淋公升數', FontProperties=font)
axs2_xlabel_text = axs[1][0].set_xlabel(u'玩視頻遊戲所消耗時間佔比', FontProperties=font)
axs2_ylabel_text = axs[1][0].set_ylabel(u'每週消費的冰淇淋公升數', FontProperties=font)
plt.setp(axs2_title_text, size=9, weight='bold', color='red')
plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black')
# 設置圖例
didntLike = mlines.Line2D([], [], color='black', marker='.', markersize=6, label='didntLike')
smallDoses = mlines.Line2D([], [], color='orange', marker='.', markersize=6, label='smallDoses')
largeDoses = mlines.Line2D([], [], color='red', marker='.', markersize=6, label='largeDoses')
# 添加圖例
axs[0][0].legend(handles=[didntLike, smallDoses, largeDoses])
axs[0][1].legend(handles=[didntLike, smallDoses, largeDoses])
axs[1][0].legend(handles=[didntLike, smallDoses, largeDoses])
# 顯示圖片
plt.show()
def autoNorm(dataSet):
"""
函數說明:對數據進行歸一化
Parameters:
dataSet - 特徵矩陣
Returns:
normDataSet - 歸一化後的特徵矩陣
ranges - 數據範圍
minVals - 數據最小值
"""
# 獲取數據的最小值
minVals = dataSet.min(0)
# 獲取數據的最大值
maxVals = dataSet.max(0)
# 最大值和最小值的範圍
ranges = maxVals - minVals
# numpy函數shape[0]返回dataSet的行數
m = dataSet.shape[0]
# 原始值減去最小值(x-xmin)
normDataSet = dataSet - np.tile(minVals, (m, 1))
# 差值處以最大值和最小值的差值(x-xmin)/(xmax-xmin)
normDataSet = normDataSet / np.tile(ranges, (m, 1))
# 歸一化數據結果,數據範圍,最小值
return normDataSet, ranges, minVals
def datingClassTest():
"""
函數說明:分類器測試函數
Parameters:
None
Returns:
normDataSet - 歸一化後的特徵矩陣
ranges - 數據範圍
minVals - 數據最小值
"""
# 測試數據:取所有數據的10% hoRatio越小,錯誤率越低
hoRatio = 0.10
# 將返回的特徵矩陣和分類向量分別存儲到datingDataMat和datingLabels中
datingDataMat, datingLabels = file2matrix("datingTestSet.txt")
# 數據歸一化,返回歸一化數據結果,數據範圍,最小值
normMat, ranges, minVals = autoNorm(datingDataMat)
# 獲取normMat的行數
m = normMat.shape[0]
# 10%的測試數據的個數
numTestVecs = int(m * hoRatio)
# 分類錯誤計數
errorCount = 0.0
for i in range(numTestVecs):
# 前numTestVecs個數據作爲測試集,後m-numTestVecs個數據作爲訓練集
# k選擇label數+1(結果比較好)
classifierResult = classify0(normMat[i, :], # 遍歷數據集前m個樣本
normMat[numTestVecs:m, :], # 數據集從m開始爲訓練樣本
datingLabels[numTestVecs:m], # 標籤集從m開始爲訓練樣本的真實標籤
4)
print("分類結果:%s\t真實類別:%s" % (classifierResult, datingLabels[i]))
if classifierResult != datingLabels[i]:
errorCount += 1.0
print("錯誤率:%f%%" % (errorCount / float(numTestVecs) * 100))
def classifyPerson():
"""
函數說明:通過輸入一個人的三種特徵,進行分類輸出
Parameters:
None
Returns:
None
"""
# 三維特徵用戶輸入
percentTats = float(input("每年獲得的飛行常客里程數:"))
ffMiles = float(input("玩視頻遊戲所消耗時間百分比:"))
iceCream = float(input("每週消費的冰淇淋公升數:"))
# 打開數據文件並處理數據
datingDataMat, datingLabels = file2matrix("datingTestSet.txt")
# 訓練集歸一化
normMat, ranges, minVals = autoNorm(datingDataMat)
# 生成NumPy數組,測試集
inArr = np.array([percentTats, ffMiles, iceCream])
# 測試集歸一化
norminArr = (inArr - minVals) / ranges
# 返回分類結果
classifierResult = classify0(norminArr, normMat, datingLabels, 4)
# 打印結果
print("你可能%s這個人" % (classifierResult))
if __name__ == '__main__':
# step1: 獲取數據,格式化數據,圖表化
datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
print(datingDataMat)
print(datingLabels)
showdatas(datingDataMat, datingLabels)
# step2:數據預處理(歸一化)
normDataSet, ranges, minVals = autoNorm(datingDataMat)
print(normDataSet)
print(ranges)
print(minVals)
# step3:測試算法
datingClassTest()
# step4: 個人約會喜好的預測
classifyPerson()
二、運行結果
step1 的運行結果
step2 運行結果
step3 運行結果
step4 運行結果