Python實現K近鄰演算法_分類器
阿新 • • 發佈:2019-01-01
收集資料
31,65,4,1
33,58,10,1
33,60,0,1
34,59,0,2
34,66,9,2
這是關於乳腺癌已手術患者存活時間(壽命)的樣本集,文字檔案中共包含306個樣本,樣本包含的屬性有:
1. 患者做手術時的年齡 opAge
2. 患者做手術的年份-1900 opYear,比如1970年做的手術,則opYear屬性的值為70
3. 陽性腋窩淋巴結的數目 cellNum
4. 存活時間 status,其中,status等於1表示該患者存活了5年以上,status等於2表示該患者在5年之內死亡。
使用pandas讀取文字檔案,將資料轉換為DataFrame物件:
data = pd.read_table(r"C:\data\Haberman's Survival Data.txt" , sep=",", header=None, names=['opAge', 'opYear', 'cellNum', 'status'], engine="python")
data1 = data[data['status'] == 1] # status為1的資料
data2 = data[data['status'] == 2] # status為2的資料
檢視data.shape為(306, 4)
資料散點圖
fig = plt.figure(figsize=(16, 12))
ax = fig.gca(projection="3d") #get current axis
ax.scatter(data1['opAge' ], data1['opYear'], data1['cellNum'], c='r', s=100, marker="*", label="survived 5 years or longer") #status為1的樣本的散點圖
ax.scatter(data2['opAge'], data2['opYear'], data2['cellNum'], c='b', s=100, marker="^", label="died within 5 year") #status為2的樣本的散點圖
ax.set_xlabel("operation age", size=15)
ax.set_ylabel("operation year" , size=15)
ax.set_zlabel("cell number", size=15)
ax.set_title('Haberman\'s Survival Scatter Plot', size=15, weight='bold')
ax.set_zlim(0, 30)
ax.legend(loc="lower right", fontsize=15)
plt.show()
得到的3D散點圖如下所示:
準備資料
KNN演算法的原則是:找到距離目標樣本距離最近的K個樣本,這K個樣本中類別出現次數最多的那個類,作為目標樣本的類別。這裡採用的距離是歐式距離,那麼數值較大的屬性在衡量距離的時候起的作用較大,這會導致樣本距離衡量出現偏差,為了遮蔽這種影響,需要對資料進行歸一化:
# 歸一化 (x-min)/(max-min)∈[0,1]
def autoNorm(dataSet):
minVals = samples.min(axis=0) # 按列求最小值,即求每個屬性的最小值
maxVals = samples.max(axis=0) # 求每個屬性的最大值
factors = maxVals - minVals # 歸一化因子
sNum = dataSet.shape[0] # 資料集的行數,即樣本數
normDataSet = (dataSet - np.tile(minVals, (sNum, 1))) / np.tile(factors, (sNum, 1)) # 先將minVals和歸一化因子轉換成與dataSet相同的shape,再做減法和除法運算,最終歸一化後的資料都介於[0,1]
return normDataSet
訓練演算法
劃分資料集
採用K折交叉驗證的方法來評估分類器的效能,從樣本集中隨機抽取10%作為測試集testing data,其餘的90%用來做10 fold cross validation
testIdxs = random.sample(range(0, len(samples), 1), len(samples) * 1 / 10) # 隨機選取testing data的索引
testingSet = samples.ix[testIdxs] # 根據索引從樣本集中獲取testing data
idxs = range(0, len(samples), 1) # 總的資料索引序列
#以下for迴圈是從總的資料索引序列中將testing data的索引去除
for i in range(0, len(testIdxs)):
idxs.remove(testIdxs[i])
trainData = samples.ix[idxs] # 獲取用作訓練的資料集
對trainData使用10折交叉驗證,首先隨機打亂trainData的索引序列random.shuffle(idxs)
idxs是引用型別,在執行完remove操作後,idxs已只剩trainData的索引。
k nearest neighbor
採用歐氏距離作為距離評價準則:
#inX: 目標樣本
#dataSet: 用來找k nearest neighbor的資料集,labels是該資料集對應的類別標籤,dataSet和labels的索引是一一對應的
def classifyKNN(inX, dataSet, labels, k):
#以下程式碼是為了防止出現這種情況:dataSet和labels的索引不是從0開始的有序自然數,導致在argsort排序的時候出現錯亂,因為argsort排序結果是從0開始的自然數,因此首先需要重置dataSet和labels的索引,使其索引變為依次從0開始自然數。
nDataSet = zeros((dataSet.shape[0], dataSet.shape[1])) #與dataSet同型的0矩陣
j = 0
for i in dataSet.index:
nDataSet[j] = dataSet.ix[i]
j += 1
nDataSet = pd.DataFrame(nDataSet)
nLabels = zeros(labels.shape[0]) #與labels同型的0向量
h = 0
for i in labels.index:
nLabels[h] = labels.ix[i]
h += 1
dataSetNum = nDataSet.shape[0] # 樣本數(DataFrame行數)
diffMat = tile(inX, (dataSetNum, 1)) - nDataSet #目標樣本與參照樣本集的差,對應屬性相減,結果為與nDataSet同型的矩陣
sqDiffMat = diffMat ** 2 #平方
sqDistances = sqDiffMat.sum(axis=1) #矩陣sqDiffMat的列之和,即目標樣本與樣本集中每個樣本對應屬性的差值的平方和
distances = sqDistances ** 0.5 #平方根,歐氏距離,即目標樣本與每個樣本點的距離
sortedDistanceIdx = distances.argsort() # 距離從小到大的索引值,sortedDistanceIdx的索引是從0開始的自然數,sortedDistanceIdx的值表示對應的distance的索引,比如sortedDistanceIdx[0]是150,表示最小的距離在distances中的索引是150
classCount = {}
for i in range(k):
#找出distance最小的k個索引,然後在nLabels中獲取其對應類別
voteLabel = nLabels[int(sortedDistanceIdx[i])]
classCount[voteLabel] = classCount.get(voteLabel, 0) + 1
#classCount字典中存放了統計的label和對應的出現次數
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True) #倒序
return sortedClassCount[0][0] #出現次數最大的label
10折交叉驗證
一次驗證
#返回在該驗證集上的錯誤率
def train(trainingSet, validationSet, kn):
errorCount = 0
vIdxs = validationSet.index
#遍歷驗證集,對每個樣本使用KNN
for i in range(0, len(validationSet)):
pred = classifyKNN(validationSet.loc[vIdxs[i], ['opAge', 'opYear', 'cellNum']], trainingSet[['opAge', 'opYear', 'cellNum']], trainingSet['status'], kn)
if (pred != validationSet.at[vIdxs[i], 'status']):
errorCount += 1
return errorCount / float(len(validationSet))
10次驗證
使用10次驗證的平均錯誤率來評價KNN分類器的效能
# dataSet:用來交叉驗證的資料集,idxs是對應的索引序列
# k: k折交叉驗證
# kn: kn近鄰
def crossValidation(dataSet, idxs, k, kn):
step = len(idxs) / k
errorRate = 0
for i in range(k):
validationIdx = []
for i in range(i * step, (i + 1) * step):
validationIdx.append(idxs[i])
validationSet = dataSet.ix[validationIdx] # 獲得驗證集資料
temp = idxs[:]
for i in validationIdx: # 把驗證集的索引去除
temp.remove(i)
trainingSet = dataSet.ix[temp] # 獲取訓練集資料
errorRate += train(trainingSet, validationSet, kn)
aveErrorRate = errorRate / float(k)
return aveErrorRate
測試演算法
交叉驗證完畢之後,使用全部的trainData,對testingData進行預測
def predict(trainingSet, testingSet, kn):
errorCount = 0
for i in range(0, len(testingSet)):
vIdxs = testingSet.index
pred = classifyKNN(testingSet.loc[vIdxs[i], ['opAge', 'opYear', 'cellNum']], trainingSet[['opAge', 'opYear', 'cellNum']], trainingSet['status'], kn)
print "The prediction label is %s"%(pred)
print "The real label is %s"%(testingSet.at[vIdxs[i], 'status'])
if (pred != testingSet.at[vIdxs[i], 'status']):
errorCount += 1
return errorCount / float(len(testingSet))
print "The cross validation error ratio is %d" %crossValidation(trainData, idxs, 10, 3)
print "The testing data error ratio is %d"%predict(samples,testingSet,3)
執行結果為:
The cross validation error ratio is 0.28
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 1
The prediction result is 2
The real result is 2
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 2
The prediction result is 1
The real result is 1
The prediction result is 2
The real result is 1
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 1
The prediction result is 2
The real result is 2
The prediction result is 1
The real result is 2
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 1
The prediction result is 2
The real result is 2
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 2
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 1
The prediction result is 1
The real result is 2
The testing data error ratio is 0.17