把經常使用的機器學習算法:$k$-近鄰算法、樸素貝葉斯、邏輯迴歸、$K$-均值聚類其思想有及 python 代碼實現總結一下。作到既要知其然又要知其因此然。參考《機器學習實戰》。 html
###基本原理 $k$-近鄰算法是分類數據最簡單有效的方法。簡單地來講,它採用測量不一樣特徵值之間的距離方法進行分類。提取樣本集中特徵最相鄰數據的分類標籤,通常來講,咱們只選擇樣本數據集中前 $k$ 個最類似的數據。 ###代碼實現 代碼的關鍵是計算數據集中每一個點與點之間的距離並按遞增排序。牢記 distances.argsort() 返回的是數組 distances 中數值從小到大排序以後的索引位置,不得不說, python 的封裝功能很強大。python
def classify0(inX, dataSet, labels, k): ''' inX: 用於分類的輸入向量 dataSet: 輸入的訓練樣本集 labels: 標籤向量,數目與 dataSet 行數相同 k: 用於選擇最近鄰居的數目 ''' dataSetSize = dataSet.shape[0] # 樣本數 diffMat = tile(inX, (dataSetSize,1)) - dataSet #np.tile把數據inX擴展成第二個參數形狀 sqDiffMat = diffMat**2 #平方求歐氏距離 sqDistances = sqDiffMat.sum(axis=1) # 列求和,返回數組 distances = sqDistances**0.5 # 獲得歐式距離 sortedDistIndicies = distances.argsort() # 數值從小到大 的索引位置 classCount = {} # 建立空字典,字典是無順序的 ''' 獲得字典的 key 值能夠用 get() 方法,若是 key 不存在,能夠返回 None,或者本身指定 的 value, 好比下邊的若是 key 不存在就給 0 值 ''' for i in range(k): # 選擇距離最小的 k 個點 voteIlabel = labels[sortedDistIndicies[i]] classCount[voteIlabel] = classCount.get(voteIlabel,0)+1 ''' python字典的items方法做用:是能夠將字典中的全部項,以列表方式返回,每項是元組。 由於字典是無序的,因此用items方法返回字典的全部項,也是沒有順序的。 eg:A = {'a':1, 'b':2, 'c':3} A.items() 輸出爲: [('a', 1), ('c', 3), ('b', 2)] python字典的iteritems方法做用:與items方法相比做用大體相同, 只是它的返回值不是列表,而是一個迭代器。 ''' # operator模塊提供的itemgetter函數用於獲取對象的哪些維的數據, # 參數爲一些序號(即須要獲取的數據在對象中的序號), #itemgetter函數獲取的不是值,而是定義了一個函數,經過該函數做用到對象上才能獲取值 sortedClassCount = sorted(classCount.iteritems(), #將字典變成迭代器(列表形式) key=operator.itemgetter(1), reverse=True) # 排序爲逆序,即從大到小 return sortedClassCount[0][0]
###優缺點 **優勢:**精度高、對異常值不敏感、無數據輸入假定面試
**缺點:**計算複雜度高、空間複雜度高,沒法給出任何數據的基礎結構信息算法
###基本原理 貝葉斯決策理論的核心思想:選擇具備最高几率的決策。數組
核心是貝葉斯準則,它告訴咱們如何交換條件機率中的條件與結果,即若是已知 $P(x|c)$,要求 $P(c|x)$,那麼可使用下面的計算方法: \begin{align} p(c|x) = \frac{p(x|c)p(c)}{p(x)} \notag \end{align}app
樸素貝葉斯假設特徵之間相互獨立,這個假設正是樸素貝葉斯中「樸素」一詞的含義。樸素貝葉斯分類器中的另外一個假設是每一個特徵同等重要。這兩個假設雖然存在一些小瑕疵,但樸素貝葉斯的實際效果卻很好。 ###使用條件機率來分類 貝葉斯決策理論要求計算兩個機率 $p(c_1|x)$ 與 $p(c_2|x)$(對於二分類)。具體意義是:給定某個由 $x$ 表示的數據點,那麼該數據點來自類別 $c_1$ 的機率是多少?來自 $c_2$ 的機率又是多少?注意這些機率和 $p(x|c_1)$ 並不同,可使用貝葉斯準則交換機率中條件與結果。使用這些定義,能夠定義貝葉斯分類準則:dom
對於一個實際的問題,咱們須要作如下步驟:機器學習
###優缺點 **優勢:**在數據較少的狀況下仍然有效,能夠處理多類別問題函數
**缺點:**對於輸入數據的準備方式較爲敏感 學習
###基本原理 邏輯迴歸的目標是尋找一個非線性函數 Sigmoid 的最佳擬合參數,求解過程由最優化算法來完成。最經常使用的就是梯度上升算法。邏輯迴歸其實包含很是多的內容,面試中常常會被問到的問題,請點擊.
Sigmoid 函數具體的計算公式以下: \begin{align} \sigma(z) = \frac{1}{1+\mathrm{e}^{-z}} \notag \end{align} 顯然 $\sigma(0) = 0.5$. 爲了實現 Logsitic 迴歸分類器,咱們能夠在每一個特徵上都乘以一個迴歸係數,而後把全部的結果值相加,將這個總和代入 Sigmoid 函數中,進而獲得一個範圍在 $0 \sim 1$ 之間的數值。任何大於 $0.5$ 的數據被分入 $1$ 類,小於 $0.5$ 即被納入 $0$ 類。因此 Logistic 迴歸也能夠被當作是一種機率估計。如今主要的問題是 :如何肯定最佳迴歸係數?咱們定義好代價函數以後,用梯度上升算法便可求解。 ###代碼實現 該算法的主要部分就是梯度上升算法的編寫,下面給出:
def gradAscent(dataMatIn, classLabels): # 梯度上升算法 m, n = np.shape(dataMatIn) alpha = 0.001 maxCycles = 500 weights = np.ones(n) # 1*n 的數組 for k in range(maxCycles): h = sigmoid(np.dot(dataMatIn, weights)) # 1*m 的數組,sigmoid 是 Sigmoid 函數,本身編寫 error = classLabels - h weights = weights+alpha*np.dot(error, dataMatIn) // 在這裏是按差值方向調整,也能夠求解出梯度 return weights def stocGradAscent0(dataMatIn, classLabels, numIter=40): # 隨機梯度上升算法 m, n = np.shape(dataMatIn) #maxCycles = 500 weights = np.ones(n) # 初始化權重,1*n 的數組 for j in range(numIter): dataIndex = range(m) for i in range(m): alpha = 4 / (1.+j+i)+0.01 # 迭代步長設定 randIndex = int(np.random.uniform(0, len(dataIndex))) # 與梯度上升惟一的區別:隨機選取更新 h = sigmoid(np.sum(dataMatIn[randIndex]*weights)) # 一個數 error = classLabels[randIndex] - h # 一個向量 weights = weights+alpha*error*dataMatIn[randIndex] del(dataIndex[randIndex]) return weights
###優缺點 **優勢:**計算代價不高,易於理解和實現
**缺點:**容易欠擬合,分類精度可能不高
###基本原理 聚類是一種無監督的學習,它將類似的對象歸到同一個簇中。$K$ 均值聚類之因此稱之爲 $K$ 均值是由於它能夠發現 $k$ 個不一樣的簇,且每一個簇的中心採用簇中所含值的均值計算而成。
$K$ 均值聚是發現給定數據集中 $k$ 個簇的算法。簇個數 $k$ 是用戶給定的,每個簇經過其質心,即簇中全部點的中心來描述。其算法流程:
建立 k 個點做爲起始質心(常常隨機選擇) 當任意一個點的簇分配結果發生改變時(說明還沒收斂) 對數據集中的每一個數據點 對每一個質心 計算質心與數據點之間的距離 數據點分配到距其最近的簇 對每個簇,計算簇中全部點的均值並將均值做爲質心
###代碼實現 假設咱們對一堆數據點進行聚類操做,數據點來自機器學習實戰。代碼以下:
# coding:utf-8 import numpy as np def loadDataSet(fileName): #general function to parse tab -delimited floats dataMat = [] #assume last column is target value fr = open(fileName) for line in fr.readlines(): curLine = line.strip().split('\t') fltLine = list(map(float,curLine)) #map all elements to float() dataMat.append(fltLine) #一個列表包含不少列表 return np.array(dataMat) def distEclud(vecA, vecB): return np.sqrt(sum(np.power(vecA - vecB, 2))) #la.norm(vecA-vecB) def randCent(dataSet, k): # 構建簇質心,矩陣dataSet 每行表示一個樣本 n = np.shape(dataSet)[1] centroids = np.zeros((k,n)) #create centroid mat for j in range(n): minJ = min(dataSet[:,j]) # 隨機質心必需要在整個數據集的邊界以內 rangeJ = float(max(dataSet[:,j]) - minJ) centroids[:,j] = (minJ + rangeJ * np.random.rand(k,1)).flatten() #隨機 return centroids def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent): m = np.shape(dataSet)[0] # m 個樣本 clusterAssment = np.zeros((m,2))#建立一個矩陣來存儲每一個點的分配結果 #兩列:一列記錄索引值,第二列存儲偏差 centroids = createCent(dataSet, k) # 建立 k 個質心 clusterChanged = True while clusterChanged: clusterChanged = False for i in range(m): #將每一個點分配到最近的質心 minDist = np.inf; minIndex = -1 for j in range(k): # 尋找最近的質心 distJI = distMeas(centroids[j,:],dataSet[i,:]) if distJI < minDist: minDist = distJI; minIndex = j if clusterAssment[i,0] != minIndex: # 直到數據點的簇分配結果再也不改變 clusterChanged = True clusterAssment[i,:] = minIndex,minDist**2 #print (centroids) for cent in range(k): #從新計算質心,更新質心的位置 ptsInClust = dataSet[np.nonzero(clusterAssment[:,0] == cent)] # 經過數組過濾來得到給定簇的全部點 centroids[cent,:] = np.mean(ptsInClust, axis=0) #計算全部點的均值 return centroids, clusterAssment # 返回全部的類質心與點分配結果 import matplotlib.pyplot as plt if __name__ == '__main__': datMat = loadDataSet('testSet.txt') k = 4 centroids = randCent(datMat, k) #print(centroids) myCentroids, clustAssing = kMeans(datMat, 4) fig = plt.figure() ax = fig.add_subplot(1,1,1) #ax.scatter(datMat[:,0], datMat[:,1]) # 必須是 array 類型 scatterMarkers = ['s', 'o', '^', '8', 'p', 'd', 'v', 'h', '>', '<'] for i in range(k): ptsIncurrCluster = datMat[np.nonzero(clustAssing[:,0] == i)] ax.scatter(ptsIncurrCluster[:,0], ptsIncurrCluster[:,1], marker=scatterMarkers[i], s=90) ax.scatter(myCentroids[:,0], myCentroids[:,1], marker='+', s=300) plt.show()
可視化以下圖:
<p align="center"><img src="https://images2018.cnblogs.com/blog/1255644/201803/1255644-20180325173334057-1556150779.png" width = "500"/>
因爲初始質心的隨機選擇,每次運行結果會稍微有所不一樣。
若是 $k$ 選擇的過於小,該算法收斂到了局部最小值,而非全局最小值。一種用於度量聚類效果的指標是 SSE(Sum of Squared Error,偏差平方和),對應程度中 clusterAssment 矩陣的第一列之和。SSE 值越小表示數據點越接近於它們的質心,聚類效果也越好。一種確定能夠下降 SSE 值的方法是增長簇的個數,但這違背了聚類的目標。聚類的目標是在保持簇數目不變的狀況下提升簇的質量。
那麼如何提升呢?一種方法是將具備最大 SSE 值的簇劃分紅兩個簇。具體實現時能夠將最大簇包含的點過濾出來並在這些點上運行 $K$ 均值算法,爲了保持簇總數不變,能夠將某兩個簇進行合併,這兩個簇的選擇通常有兩種能夠量化的方法:合併最近的質心,或者合併兩個使得 SSE 增幅最小的質心。
爲克服 $K$ 均值算法收斂於局部最小值的問題,有人提出了另外一個稱爲二分 $K$ 均值的算法。該算法首先將全部點做爲一個簇,而後將該簇一分爲二。以後選擇一個簇繼續進行劃分,選擇哪個簇進行劃分取決於對其劃分是否能夠最大程度下降 SSE 的值。上述基於 SSE 的劃分過程爲斷重複,直到獲得用戶指定的簇數爲止。另外一種作法是選擇 SSE 最大的簇進行劃分,直到簇數目達到用戶指定的數目爲止。