【機(jī)器學(xué)習(xí)筆記】Python機(jī)器學(xué)習(xí)基本語法
本來算法沒有那么復(fù)雜,但如果因?yàn)檎Z法而攻不下就很耽誤時(shí)間。于是就整理一下,搞python機(jī)器學(xué)習(xí)上都需要些什么基本語法,夠用就行,可能會(huì)持續(xù)更新。
Python四大類型
元組tuple,初始化之后不可元素值變更,其余還沒有感受到它和list什么差別,感覺也比較少用,聲明語法是()
>>> tp = ()
>>> type(tp)
<class 'tuple'>
字典dict,聲明語法{},對(duì)值 .items(),鍵值 .keys(),值 .values()
>>> d = {'left': {0}, 'right' :1}
>>> d
{'left': {0}, 'right': 1}
>>> d.keys()
dict_keys(['left', 'right'])
集合set,聲明語法set(),括號(hào)內(nèi)只能傳一個(gè)參數(shù),這個(gè)參數(shù)要可迭代。
整這么多類型干什么呢,主要是每個(gè)類型具有的屬性不同。
Python支持集合類型的交集(用&)、并集(用|)、差集(用-)、交叉補(bǔ)集(用^)等操作
>>> setA = set(1,2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: set expected at most 1 arguments, got 2
>>> setA = set([1,'a',2.0])
>>> setB = set(['a'])
>>> setA | setB
{1, 2.0, 'a'}
列表list,聲明語法[],這個(gè)類型很常用,
需要注意一點(diǎn)的是,在按列表索引查詢列表的數(shù)據(jù)時(shí),方括號(hào)里不能出現(xiàn)小寫逗號(hào)。這是目前我知道的,它與numpy.array類型的主要區(qū)別之一(前者用兩個(gè)方括號(hào))。
>>> lst = [[0,1,2],[3,4,5],[6,7,8],[9,10,11]]
>>> lst[1:3]
[[3, 4, 5], [6, 7, 8]]
>>> lst[1:5:2]
[[3, 4, 5], [9, 10, 11]]
[a:b:c]指從下標(biāo)a到下標(biāo)b(不包括下標(biāo)b)的以c行作為間隔取行,c默認(rèn)是1。例如1~10,如果取2:8:2就是2,4,6
如果a(或b)為負(fù)數(shù),意思是取倒數(shù)的a行(或b行)
數(shù)組 numpy.array,這個(gè),和list寫法差不多,但是它很多比較方便的屬性,
比如獲取矩陣大小shape,改變矩陣大小reshape,矩陣轉(zhuǎn)置T,等等。還有一個(gè)mat類型(聲明為mat()),就是矩陣(matrix)類型,也差不多。
array比list方便的一點(diǎn)還在于,按索引查詢當(dāng)中,array可以選擇查詢的列,其中對(duì)列的篩選,需要用小寫逗號(hào)隔開。
(如果是list類型,取第一行第一列是testlist[0][0])
>>> test = [[0,2],[1,3],[4,0],[2,1],[5,1]]
>>> test
[[0, 2], [1, 3], [4, 0], [2, 1], [5, 1]]
>>> test[0]
[0, 2]
>>> test[0][0]
0
>>> testA = np.array(test)
>>> testA[0, :]
array([0, 2])
循環(huán)
for item in iterator
常用for item in range(5)相當(dāng)于for (int i = 0; i < 5; i++)
或者for item in listInstance就是foreach item in listInstance
numpy函數(shù)介紹:
關(guān)于這個(gè),其實(shí)百度一下啥都有,或者在編輯器里懸停鼠標(biāo),可見描述。此處記一下我遇到的,感覺比較特殊的:
numpy.nonzero() #求非0元素所在位置
>>> import numpy as np
>>> test = [[1,2,5,0],[0,0,1,0]]
>>> np.nonzero(test)
(array([0, 0, 0, 1], dtype=int64), array([0, 1, 2, 2], dtype=int64))
dtype是dataType,指前面這個(gè)array里面數(shù)值的類型。
求出來是兩行內(nèi)容,第一行是非0的所在行下標(biāo),這個(gè)行下標(biāo)出現(xiàn)多少次,就表示這個(gè)行有多少個(gè)非0元素;第二行是對(duì)應(yīng)第一行的列中,非0元素所在列位置。
|
列下標(biāo)\行下標(biāo) |
0 |
1 |
2 |
3 |
|
0 |
1 |
2 |
5 |
0 |
|
1 |
0 |
0 |
1 |
0 |
出現(xiàn)0的位置:
|
行下標(biāo) |
0 |
0 |
0 |
1 |
|
列下標(biāo) |
0 |
1 |
2 |
2 |
var() #求方差
看見這個(gè)第一印象真是variable,弱類型,然而,np.var()此var是variance,方差。
關(guān)于查詢
有3個(gè)強(qiáng)大的函數(shù)
https://www.liaoxuefeng.com/wiki/1016959663602400/1017329367486080
>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
>>> from functools import reduce
>>> def add(x, y):
... return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
排序sorted(list, key = function)
最最重要的是這個(gè):filter()
接受兩個(gè)參數(shù),一個(gè)是函數(shù)類型,一個(gè)是迭代器類型
!! filter()這個(gè)函數(shù)非常重要,相當(dāng)于C#的linq to object
filter()函數(shù)返回的是一個(gè)Iterator,需要再轉(zhuǎn)換類型,可用list(返回內(nèi)容)
list類型處理下標(biāo)查詢,還能這樣:
>>> test = [[1,2,5,0],[0,0,1,0]]
>>> test = np.array(test)
>>> test[0, :] >= 2
array([False, True, True, False])
>>> test[[0,0,0,1],:]
array([[1, 2, 5, 0],
[1, 2, 5, 0],
[1, 2, 5, 0],
[0, 0, 1, 0]])
>>> test[:, np.nonzero((test[0, :] >= 2))[0]]
array([[2, 5],
[0, 1]])
最后一例這個(gè)查詢,看起來好像很厲害,但是吧,這個(gè)可讀性讓人感到難受,真是不要也罷。
而且,就此例而言,時(shí)間復(fù)雜度是多少,只是篩選≥2的數(shù)據(jù),列表遍歷了3次,這能忍嗎?
用filter()不是簡單明了很多嗎?
我這個(gè)寫法有點(diǎn)古怪,可能不太正確:
>>> def TestFilter(x):
... if x >= 2:
... return x
... else: return
>>> test1 = [5,2,3,1,0,0]
>>> list(filter(TestFilter, test1))
[5, 2, 3]
return就是默認(rèn)return None
于是,這樣也是可行的:
>>> def tt(x):
... if(x >= 2):
... return x
再簡略一些:
>>> list(filter(lambda x : x >=2, test1))
[5, 4, 3, 2]
然后問題又來了,不能處理二維的list,只能是原子型的list,這樣看來,《機(jī)器學(xué)習(xí)》那書上寫的辦法好像經(jīng)得起考驗(yàn)?
不,不可能這么沙雕。
對(duì)象化數(shù)據(jù)不就可以了么!真是多多感謝linq to object,有時(shí)候真覺得語言設(shè)計(jì)者太厲害了,方方面面想到了。
>>> def testFilter(x):
... if x.col1 > value:
... return x
... else: return
練一練:
搞明白上面的語法,基本就能直接看明白這個(gè)CART算法的決策樹(classification and regression tree)
(代碼按閱讀順序排序的,如果要走,注意函數(shù)聲明順序)
1 from numpy import * 2 def createTree(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)): 3 feat, val = chooseBestSplit(dataSet, leafType, errType, ops) 4 if feat == None: return val 5 retTree = {} 6 retTree['spInd'] = feat 7 retTree['spVal'] = val 8 lSet, rSet = binSplitDataSet(dataSet, feat, val) 9 retTree['left'] = createTree(lSet, leafType, errType, ops) 10 retTree['right'] = createTree(rSet, leafType, errType, ops) 11 return retTree 12 13 def chooseBestSplit(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)): 14 tolS = ops[0]; tolN = ops[1] 15 if len(set(dataSet[:, -1].T.tolist()[0])) == 1: #取最后一列,轉(zhuǎn)置為行,取第一行轉(zhuǎn)作集合類型,判斷集合元素個(gè)數(shù) 16 return None, leafType(dataSet) 17 m,n = shape(dataSet) #數(shù)據(jù)集大小 18 S = errType(dataSet) #errType是個(gè)方法,默認(rèn)為方法regErr,計(jì)算數(shù)據(jù)集數(shù)據(jù)波動(dòng)情況 19 bestS = inf; bestIndex = 0; bestValue = 0 20 for featIndex in range(n-1): #每一列 21 for splitVal in dataSet[:,featIndex]: #每一行 22 mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal) #按當(dāng)前單元格作為劃分列的閾值 23 if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN): continue #劃分出來的兩塊,任一塊元素個(gè)數(shù)太小的話 24 newS = errType(mat0) + errType(mat1) #劃分出來的兩塊大小比較合理,就分別計(jì)算誤差 25 if newS < bestS: 26 bestIndex = featIndex 27 bestValue = splitVal 28 bestS = newS 29 if (S - bestS) < tolS: 30 return None, leafType(dataSet) 31 mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue) 32 if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN): 33 return None, leafType(dataSet) 34 return bestIndex,bestValue 35 36 def regLeaf(dataSet): 37 return mean(dataSet[:,-1]) #我不僅知道m(xù)ean是吝嗇,我還知道m(xù)ean是平均值 38 39 def regErr(dataSet): 40 return var(dataSet[:,-1]) * shape(dataSet)[0] 41 42 def binSplitDataSet(dataSet, feature, value): 43 a = dataSet[nonzero(dataSet[:,feature] > value)[0],:] #所有大于value的行 44 b = dataSet[nonzero(dataSet[:,feature] <= value)[0],:] 45 if (len(a) > 0): 46 mat0 = a 47 else: mat0 = [] 48 if (len(b) > 0): 49 mat1 = b 50 else: mat1 = [] 51 return mat0,mat1 52 53 def loadDataSet(fileName): #general function to parse tab -delimited floats 54 dataMat = [] #assume last column is target value 55 fr = open(fileName) 56 for line in fr.readlines(): 57 curLine = line.strip().split('\t') 58 fltLine = list(map(float,curLine)) #此處的float是個(gè)函數(shù),float(),類型轉(zhuǎn)換為float 59 dataMat.append(fltLine) 60 #dataMat.append(curLine) 61 return mat(dataMat)
Q:代碼返回的是個(gè)什么樹?
兩列兩百行的數(shù)據(jù)集,其中最后一列是標(biāo)簽,返回的是一個(gè)節(jié)點(diǎn)的樹。
{'left': 1.0180967672413792, 'right': -0.04465028571428572, 'spInd': 0, 'spVal': matrix([[0.48813]])}
這個(gè)節(jié)點(diǎn)意思是:列下標(biāo)為0作為判斷依據(jù),當(dāng)待歸類數(shù)值>spval閾值,就走左分支樹;待歸類≤spval,走右分支。
樹剪枝
這個(gè)算法走下來,對(duì)這個(gè)ops=(1,4)的依賴,就很大,人很難把握到這個(gè)默認(rèn)值是多少比較合適。
樹的分支少了,很可能就意味著樹不準(zhǔn)確,擬合度不夠;樹的分支多了,就過擬合了,先不說計(jì)算成本可能增多,如果讓歸類范圍有太大偏差就不好了。
再,如何判斷這個(gè)樹擬合程度如何?——可以放一些新的帶已知結(jié)果的數(shù)據(jù)進(jìn)去,比較一下分類結(jié)果誤差情況。如果新數(shù)據(jù)在某個(gè)節(jié)點(diǎn)的分類誤差比較大,那倒不如不要這個(gè)節(jié)點(diǎn)了。當(dāng)然,這個(gè)過程要從葉節(jié)點(diǎn)開始計(jì)算。這就是樹剪枝。
樹剪枝,有時(shí)候真覺得,算法怎么可以沒有動(dòng)畫,沒有圖呢,只有公式,勸退多少人啊。

這個(gè)圖取自李航的《統(tǒng)計(jì)學(xué)習(xí)方法》,感覺一下子get到精髓——這個(gè)損失得怎么設(shè)置?
1 def isTree(obj): 2 return (type(obj).__name__=='dict') #此代碼樹的類型是dict 3 4 def getMean(tree): 5 if isTree(tree['right']): tree['right'] = getMean(tree['right']) 6 if isTree(tree['left']): tree['left'] = getMean(tree['left']) 7 return (tree['left']+tree['right'])/2.0 8 9 def prune(tree, testData): #這個(gè)testData最后一列還是標(biāo)簽列,是個(gè)數(shù)值 10 if shape(testData)[0] == 0: return getMean(tree) #if we have no test data collapse the tree 11 if (isTree(tree['right']) or isTree(tree['left'])): #if the branches are not trees try to prune them 12 lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal']) 13 if isTree(tree['left']): tree['left'] = prune(tree['left'], lSet) 14 if isTree(tree['right']): tree['right'] = prune(tree['right'], rSet) 15 #if they are now both leafs, see if we can merge them 16 if not isTree(tree['left']) and not isTree(tree['right']): 17 lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal']) 18 errorNoMerge = sum(power(lSet[:,-1] - tree['left'],2)) + sum(power(rSet[:,-1] - tree['right'],2)) 19 treeMean = (tree['left']+tree['right'])/2.0 20 errorMerge = sum(power(testData[:,-1] - treeMean,2)) 21 if errorMerge < errorNoMerge: 22 print("merging") 23 return treeMean 24 else: return tree 25 else: return tree
原來按節(jié)點(diǎn)的閾值劃分testData的數(shù)據(jù)后,分別求兩個(gè)數(shù)據(jù)塊與左右子樹的距離總數(shù) errorNoMerge,如果這個(gè)距離比按另一個(gè)數(shù) errorMerge 大時(shí),就合并。這個(gè)損失函數(shù)的選定,看似有些拓展空間,但我更喜歡具體問題具體分析,所以此處不展開。
隨機(jī)森林
隨機(jī)森林(Random Forest)是一種集成學(xué)習(xí)方法。
集成學(xué)習(xí),我的理解是,有幾種決策模型,可能大家意見不同,然后投票(取平均或設(shè)置決策模型權(quán)重)決出結(jié)果。
集成學(xué)習(xí)是一種“投票法”,少數(shù)服從多數(shù)。因?yàn)榧蓪W(xué)習(xí)中每個(gè)個(gè)體學(xué)習(xí)器可能學(xué)到的是任務(wù)的不同方面,綜合不同方面的結(jié)果可以達(dá)到一定程度上的泛化作用。是由多個(gè)弱學(xué)習(xí)器組成一個(gè)強(qiáng)學(xué)習(xí)器。
隨機(jī)森林里設(shè)置有多顆決策樹,任務(wù)結(jié)果由這些決策樹投票決出。
設(shè)定現(xiàn)有M * N大小的DataSet,其中M為數(shù)據(jù)行數(shù),N為特征列數(shù)目。隨機(jī)森林算法步驟如下:
1.有放回地(有放回抽樣Bootstrap Sample)x次取出y行n個(gè)(n應(yīng)遠(yuǎn)小于N)特征列,其中x * y = m,創(chuàng)建x個(gè)決策樹(每棵樹都不需要剪枝);

2.由步驟1組成隨機(jī)森林,其中,對(duì)于分類問題:根據(jù)多棵樹分類器投票[0]決出分類結(jié)果;對(duì)于回歸問題,取多棵樹預(yù)測(cè)值的均值[0]作為預(yù)測(cè)結(jié)果。
[0]對(duì)于步驟2,如何對(duì)子模型賦予“其結(jié)果重要性”還可以再玩些花樣。
3.取未被步驟1選中過的數(shù)據(jù)集(OOB,out-of-bag[1])作為測(cè)試數(shù)據(jù),需要測(cè)試準(zhǔn)確率(袋外錯(cuò)誤率out-of-bag error),這個(gè)準(zhǔn)確率可以粗暴地這樣處理:比較經(jīng)過隨機(jī)森林得到的決策結(jié)果與真實(shí)結(jié)果,以求得準(zhǔn)確率。
[1]關(guān)于有放回抽樣 Bootstrap Sample,
設(shè)在袋中有x個(gè)不同的小球,有放回抽取x個(gè),那么抽到不同小球的個(gè)數(shù)大概是多少個(gè)?
此時(shí),有一個(gè)小球在一次抽取中被抽中的概率是1/x,設(shè)小球不被抽中的概率是P,x次不被抽中的概率是:

其中又:

所以,當(dāng)x取無窮大時(shí),有:

所以,不會(huì)被抽中的球大概有:x·P個(gè)。
假如設(shè)x取100,那么大概會(huì)取到不同的小球個(gè)數(shù)是100 – 36.79 ≈ 63
參考自:https://blog.csdn.net/cholocatehe/article/details/42130341
4.得到準(zhǔn)確率后給步驟2得到的眾決策樹添加決策權(quán)重,準(zhǔn)確率高的權(quán)重稍高一些,準(zhǔn)確率低的權(quán)重稍小一些。當(dāng)然,此步不走,那么步驟2每棵樹權(quán)重就取平均。
隨機(jī)森林 缺點(diǎn):
想要得出超過范圍的獨(dú)立變量或非獨(dú)立變量,可能不行;
關(guān)于網(wǎng)上所說的一點(diǎn),“噪音較大的數(shù)據(jù),RF容易陷入過擬合”,這個(gè)說法的缺點(diǎn),不太贊成叭,如果噪音這么大,就應(yīng)該預(yù)處理,應(yīng)該很少有方法可以不需要預(yù)處理數(shù)據(jù)(神經(jīng)網(wǎng)絡(luò)好像可以);
比較明顯的缺點(diǎn)還沒感受到,如果后面有實(shí)操,再來補(bǔ)充。
其他:
Python實(shí)現(xiàn):
在scikit-learn類庫里面有很多現(xiàn)成的集成學(xué)習(xí)方法
https://scikit-learn.org/stable/modules/classes.html#module-sklearn.ensemble
更多文獻(xiàn) github 上也有人歸納了:
https://github.com/kjw0612/awesome-random-forest

浙公網(wǎng)安備 33010602011771號(hào)