基本機器學習算法思想以及編程實現

概要

把經常使用的機器學習算法:$k$-近鄰算法、樸素貝葉斯、邏輯迴歸、$K$-均值聚類其思想有及 python 代碼實現總結一下。作到既要知其然又要知其因此然。參考《機器學習實戰》。  html


 

$k$-近鄰算法

  ###基本原理   $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

  • 若是 $p(c_1|x) > p(c_2|x)$,那麼屬於類別 $c_1$.
  • 若是 $p(c_1|x) < p(c_2|x)$,那麼屬於類別 $c_2$.

對於一個實際的問題,咱們須要作如下步驟:機器學習

  • 統計計算 $p(c_i),\quad i=1,2$.
  • 接下來計算 $p(x| c_i)$,這裏要用到樸素貝葉斯假設,若是將 $x$ 展開一個個獨立特徵,則 $p(x|c_i)=p(x_0,x_1,\cdots,x_n | c_i)= p(x_0|c_i)p(x_1|c_i)\cdots p(x_n|c_i)$.(若是每一個數字太小,能夠用到取對數的技巧)
  • 假如要分類的向量是 $w$, 計算 $p(w|c_i)p(c_i),\quad i=1,2$,哪一個值大歸於哪一個類。

  ###優缺點   **優勢:**在數據較少的狀況下仍然有效,能夠處理多類別問題函數

**缺點:**對於輸入數據的準備方式較爲敏感  學習

邏輯迴歸

  ###基本原理   邏輯迴歸的目標是尋找一個非線性函數 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$ 是用戶給定的,每個簇經過其質心,即簇中全部點的中心來描述。其算法流程:

建立 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$?

若是 $k$ 選擇的過於小,該算法收斂到了局部最小值,而非全局最小值。一種用於度量聚類效果的指標是 SSE(Sum of Squared Error,偏差平方和),對應程度中 clusterAssment 矩陣的第一列之和。SSE 值越小表示數據點越接近於它們的質心,聚類效果也越好。一種確定能夠下降 SSE 值的方法是增長簇的個數,但這違背了聚類的目標。聚類的目標是在保持簇數目不變的狀況下提升簇的質量

那麼如何提升呢?一種方法是將具備最大 SSE 值的簇劃分紅兩個簇。具體實現時能夠將最大簇包含的點過濾出來並在這些點上運行 $K$ 均值算法,爲了保持簇總數不變,能夠將某兩個簇進行合併,這兩個簇的選擇通常有兩種能夠量化的方法:合併最近的質心,或者合併兩個使得 SSE 增幅最小的質心。  

二分 $K$ 均值算法

爲克服 $K$ 均值算法收斂於局部最小值的問題,有人提出了另外一個稱爲二分 $K$ 均值的算法。該算法首先將全部點做爲一個簇,而後將該簇一分爲二。以後選擇一個簇繼續進行劃分,選擇哪個簇進行劃分取決於對其劃分是否能夠最大程度下降 SSE 的值。上述基於 SSE 的劃分過程爲斷重複,直到獲得用戶指定的簇數爲止。另外一種作法是選擇 SSE 最大的簇進行劃分,直到簇數目達到用戶指定的數目爲止。

相關文章
相關標籤/搜索