1. 程式人生 > >K近鄰演算法(三)--kaggle競賽之Titanic

K近鄰演算法(三)--kaggle競賽之Titanic

小白好難得會用python做第分類,實踐一下用於kaggle入門賽之泰坦尼克生還預測

問題介紹:泰坦尼克電影大家都看過,大災難過後有些人生還了,有些人卻遭遇了不信,官方提供了1309名乘客的具體資訊以及提供了其中891名乘客的最後的存活情況,讓我們去預測另外418乘客的存活情況。是很基本的二分類問題。


一、資料分析

官方所給的資料長這樣:


Survived:是否存活(0代表否,1代表是)
Pclass:社會階級(1代表上層階級,2代表中層階級,3代表底層階級)
Name:船上乘客的名字
Sex:船上乘客的性別
Age: 船上乘客的年齡(可能存在 NaN)
SibSp:乘客在船上的兄弟姐妹和配偶的數量
Parch:乘客在船上的父母以及小孩的數量
Ticket:乘客船票的編號
Fare:乘客為船票支付的費用
Cabin:乘客所在船艙的編號(可能存在 NaN)
Embarked:乘客上船的港口( S 代表從 Southampton 登船, C 代表從 Cherbourg 登船,Q 代表從 Queenstown 登船,)

我們面臨的主要問題有兩個,一個是age和cabin存在很多缺失值(Age(年齡)屬性只有714名乘客有記錄,Cabin(客艙)只有204名乘客是已知的),很可能會影響模型效能;另一個是訓練樣本就891個,訓練集過小,很容易造成過擬合。

二、資料處理



由於影響因素太多,我們先對特徵向量進行篩選。根據資訊增益比進行排序畫圖(詳細知識點見《機器學習》周志華)

故我們就保留前三個影響因子。推測性別女士優先,年齡小孩優先,票價可能影響到其所在船艙而影響其存活率。

def file2matrix(filename):
    fr = open(filename)
    numberOfLines = len(fr.readlines())         #get the number of lines in the file
    returnMat = zeros((numberOfLines,3))        #prepare matrix to return
    classLabelVector = []                       #prepare labels return   
    fr = open(filename)
    index = 0#確保指標在最前面
    for line in fr.readlines():
        line = line.strip()
        listFromLine = line.split(',')
        if listFromLine[5]=='male':
            listFromLine[5]='0'
        else:
            listFromLine[5]='1'
        if listFromLine[6]=='':
            listFromLine[6]=29.7 
        if listFromLine[7]=='':
            listFromLine[7]=35.6 
                #年齡的預設用平均年齡29.7代替       
        returnMat[index,:] = listFromLine[5:8]      #對應著性別,年齡和船票價
        classLabelVector.append(int(listFromLine[1]))
        index += 1
    return returnMat,classLabelVector#return的位置一定要找對

二、分析資料

import matplotlib  
import matplotlib.pyplot as plt 
from os import listdir
import numpy as np

dataset,classlabel=file2matrix('train1.csv')
classlabel=array(classlabel)
id0=np.where(classlabel==0)
id1=np.where(classlabel==1)
fig=plt.figure()  
ax=fig.add_subplot(111) 
p1=ax.scatter(dataset[id1,1],dataset[id1,2],color = 'r',label='1',s=20)
p0=ax.scatter(dataset[id0,1],dataset[id0,2],color ='b',label='0',s=10)
plt.legend(loc = 'upper right')
plt.show 

這是以年齡和船票價分的

這是以年齡和性別分的

這是以性別和船票價分的

其實這裡應該繼續探討特徵間的相互聯絡,做整合演算法。不過初步先直接選定年齡和船票價為分類標準吧(誰叫小白還不會處理定性特徵值呢閉嘴

三、準備資料


歸一化數值

    def autoNorm(dataSet):  
        minVals = dataSet.min(0)  
        maxVals = dataSet.max(0)  
        ranges = maxVals - minVals  
        normDataSet = zeros(shape(dataSet))#建立框架  
        m = dataSet.shape[0]#行數  
        normDataSet = dataSet - tile(minVals, (m,1))#每個數減該列最小  
        normDataSet = normDataSet/tile(ranges, (m,1))   #差再除以max-min  
        return normDataSet, ranges, minVals  

四、測試演算法


匯入核心演算法

def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]#4l
    diffMat = tile(inX, (dataSetSize,1)) - dataSet#inx按4*1重複排列再與sataset做差
    sqDiffMat = diffMat**2#每個元素平方
    sqDistances = sqDiffMat.sum(axis=1)#一個框裡的都加起來
    distances = sqDistances**0.5#加起來之後每個開根號
    sortedDistIndicies = distances.argsort() #返回的是陣列值從小到大的索引值,最小為0
    classCount={}          
    for i in range(k):#即0到k-1.最後得到的classCount是距離最近的k個點的類分佈,即什麼類出現幾次如{'A': 1, 'B': 2}
        voteIlabel = labels[sortedDistIndicies[i]]#返回從近到遠第i個點所對應的類
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#字典模式,記錄類對應出現次數.這裡.get(a,b)就是尋找字典classcount的a對應的值,如果沒找到a,就顯示b
    sortedClassCount = sorted(classCount.iteritems(),key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

測試演算法
def surviorclasstest():
    hoRatio = 0.10      #hold out 10%
    dataset,classlabel=file2matrix('train1.csv')     #load data setfrom file
    normMat, ranges, minVals = autoNorm(dataset)
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],classlabel[numTestVecs:m],3)
        print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, classlabel[i])
        if (classifierResult != classlabel[i]): errorCount += 1.0
    print "the total error rate is: %f" % (errorCount/float(numTestVecs))
    print errorCount

得到

the total error rate is: 0.269
這裡要提到一點,此時我們可以調節K值來使錯誤率變小,實際上K值越大錯誤率越小(驗證了一下k=10時0.235,k=15時0.224,k=100時0.191)

如果選擇較小的k值,就相當於用較小的領域中的訓練例項學習,其近似誤差會小,但估計誤差會大,其泛化效能會比較差,因為預測結果會對近鄰的例項點非常敏感,即k值越小就意味著整體模型越複雜,容易發生過擬合;相對地,用較大的k值,可以減小學習的估計誤差,但是近似誤差會增大,這時與輸入例項較遠的不相似的訓練例項也會起預測作用,使預測發生錯誤,誇張一點k=N時無論輸入什麼例項都返回整個訓練集佔多數的類,k值越大意味著整體模型變得簡單,有可能欠擬合。在應用中,通常採用交叉驗證法來選取較優的k值。

(from《機器學習》周志華)

這裡我比較懶加上之前提過這裡樣本少很容易過擬合,再加上能上75%的準確率我已經很滿意啦。這裡就選擇k=10吧。

五、使用演算法

def classifyperson():
    dataset,classlabel=file2matrix('train1.csv')
    normMat, ranges, minVals = autoNorm(dataset)
    testset,whatever=file2matrix('test.csv')
    normMattest, rangestset, minValstest = autoNorm(testset)
    h=testset.shape[0]
    for i in range(h):
        classifierResult = classify0(normMattest[i,:],normMat[:,:],classlabel[:],10)
        print classifierResult

得到一串01的序列就可以提交啦。


果然還是太粗糙,換成k=3
排名立馬上升150,這臉打得好疼。

六、優化方向

1、其實性別影響很大的,後期要加入 2、關於K值的選擇和過擬合問題,可以引入正則化和交叉驗證 3、資料探勘分類演算法辣麼多,都試試


感覺靠譜的還有邏輯迴歸,決策樹和支援向量機(SVM)另外可以嘗試整合學習


另外可以Bagging :有放回的自助取樣,生成多棵決策樹訓練以及在此基礎上再引入隨機屬性,進一步增加“多樣性”(隨機森林)(據說這一串下來可以到81%)