亚洲免费在线视频-亚洲啊v-久久免费精品视频-国产精品va-看片地址-成人在线视频网

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

帶你學(xué)習(xí)Python如何實(shí)現(xiàn)回歸樹(shù)模型

瀏覽:106日期:2022-07-17 10:43:52

所謂的回歸樹(shù)模型其實(shí)就是用樹(shù)形模型來(lái)解決回歸問(wèn)題,樹(shù)模型當(dāng)中最經(jīng)典的自然還是決策樹(shù)模型,它也是幾乎所有樹(shù)模型的基礎(chǔ)。雖然基本結(jié)構(gòu)都是使用決策樹(shù),但是根據(jù)預(yù)測(cè)方法的不同也可以分為兩種。第一種,樹(shù)上的葉子節(jié)點(diǎn)就對(duì)應(yīng)一個(gè)預(yù)測(cè)值和分類樹(shù)對(duì)應(yīng),這一種方法稱為回歸樹(shù)。第二種,樹(shù)上的葉子節(jié)點(diǎn)對(duì)應(yīng)一個(gè)線性模型,最后的結(jié)果由線性模型給出。這一種方法稱為模型樹(shù)。

今天我們先來(lái)看看其中的回歸樹(shù)。

回歸樹(shù)模型

CART算法的核心精髓就是我們每次選擇特征對(duì)數(shù)據(jù)進(jìn)行拆分的時(shí)候,永遠(yuǎn)對(duì)數(shù)據(jù)集進(jìn)行二分。無(wú)論是離散特征還是連續(xù)性特征,一視同仁。CART還有一個(gè)特點(diǎn)是使用GINI指數(shù)而不是信息增益或者是信息增益比來(lái)選擇拆分的特征,但是在回歸問(wèn)題當(dāng)中用不到這個(gè)。因?yàn)榛貧w問(wèn)題的損失函數(shù)是均方差,而不是交叉熵,很難用熵來(lái)衡量連續(xù)值的準(zhǔn)確度。

在分類樹(shù)當(dāng)中,我們一個(gè)葉子節(jié)點(diǎn)代表一個(gè)類別的預(yù)測(cè)值,這個(gè)類別的值是落到這個(gè)葉子節(jié)點(diǎn)當(dāng)中訓(xùn)練樣本的類別的眾數(shù),也就是出現(xiàn)頻率最高的類別。在回歸樹(shù)當(dāng)中,葉子節(jié)點(diǎn)對(duì)應(yīng)的自然就是一個(gè)連續(xù)值。這個(gè)連續(xù)值是落到這個(gè)節(jié)點(diǎn)的訓(xùn)練樣本的均值,它的誤差就是這些樣本的均方差。

另外,之前我們?cè)谶x擇特征的劃分閾值的時(shí)候,對(duì)閾值的選擇進(jìn)行了優(yōu)化,只選擇了那些會(huì)引起預(yù)測(cè)類別變化的閾值。但是在回歸問(wèn)題當(dāng)中,由于預(yù)測(cè)值是一個(gè)浮點(diǎn)數(shù),所以這個(gè)優(yōu)化也不存在了。整體上來(lái)說(shuō),其實(shí)回歸樹(shù)的實(shí)現(xiàn)難度比分類樹(shù)是更低的。

實(shí)戰(zhàn)

我們首先來(lái)加載數(shù)據(jù),我們這次使用的是scikit-learn庫(kù)當(dāng)中經(jīng)典的波士頓房?jī)r(jià)預(yù)測(cè)的數(shù)據(jù)。關(guān)于房?jī)r(jià)預(yù)測(cè),kaggle當(dāng)中也有一個(gè)類似的比賽,叫做:house-prices-advanced-regression-techniques。不過(guò)給出的特征更多,并且存在缺失等情況,需要我們進(jìn)行大量的特征工程。感興趣的同學(xué)可以自行研究一下。

首先,我們來(lái)獲取數(shù)據(jù),由于sklearn庫(kù)當(dāng)中已經(jīng)有數(shù)據(jù)了,我們可以直接調(diào)用api獲取,非常簡(jiǎn)單:

import numpy as npimport pandas as pdfrom sklearn.datasets import load_bostonboston = load_boston()X, y = boston.data, boston.target

我們輸出前幾條數(shù)據(jù)查看一下:

帶你學(xué)習(xí)Python如何實(shí)現(xiàn)回歸樹(shù)模型

這個(gè)數(shù)據(jù)質(zhì)量很高,sklearn庫(kù)已經(jīng)替我們做完了數(shù)據(jù)篩選與特征工程,直接拿來(lái)用就可以了。為了方便我們傳遞數(shù)據(jù),我們將X和y合并在一起。由于y是一維的數(shù)組形式是不能和二維的X合并的,所以我們需要先對(duì)y進(jìn)行reshape之后再進(jìn)行合并。

y = y.reshape(-1, 1)X = np.hstack((X, y))

hstack函數(shù)可以將兩個(gè)np的array橫向拼接,與之對(duì)應(yīng)的是vstack,是將兩個(gè)array縱向拼接,這個(gè)也是常規(guī)操作。合并之后,y作為新的一列添加在了X的后面。數(shù)據(jù)搞定了,接下來(lái)就要輪到實(shí)現(xiàn)模型了。

在實(shí)現(xiàn)決策樹(shù)的主體部分之前,我們先來(lái)實(shí)現(xiàn)兩個(gè)輔助函數(shù)。第一個(gè)輔助函數(shù)是計(jì)算一批樣本的方差和,第二個(gè)輔助函數(shù)是獲取樣本的均值,也就是子節(jié)點(diǎn)的預(yù)測(cè)值。

def node_mean(X): return np.mean(X[:, -1])def node_variance(X): return np.var(X[:, -1]) * X.shape[0]

這個(gè)搞定了之后,我們繼續(xù)實(shí)現(xiàn)根據(jù)閾值拆分?jǐn)?shù)據(jù)的函數(shù)。這個(gè)也可以復(fù)用之前的代碼:

from collections import defaultdictdef split_dataset(X, idx, thred): split_data = defaultdict(list) for x in X: split_data[x[idx] < thred].append(x) return list(split_data.values()), list(split_data.keys())

接下來(lái)是兩個(gè)很重要的函數(shù),分別是get_thresholds和split_variance。顧名思義,第一個(gè)函數(shù)用來(lái)獲取閾值,前面說(shuō)了由于我們做的是回歸模型,所以理論上來(lái)說(shuō)特征的每一個(gè)取值都可以作為切分的依據(jù)。但是也不排除可能會(huì)存在多條數(shù)據(jù)的特征值相同的情況,所以我們對(duì)它進(jìn)行去重。第二個(gè)函數(shù)是根據(jù)閾值對(duì)數(shù)據(jù)進(jìn)行拆分,返回拆分之后的方差和。

def get_thresholds(X, i): return set(X[:, i].tolist())# 每次迭代方差優(yōu)化的底線MINIMUM_IMPROVE = 2.0# 每個(gè)葉子節(jié)點(diǎn)最少樣本數(shù)MINIMUM_SAMPLES = 10def split_variance(dataset, idx, threshold): left, right = [], [] n = dataset.shape[0] for data in dataset: if data[idx] < threshold: left.append(data) else: right.append(data) left, right = np.array(left), np.array(right) # 預(yù)剪枝 # 如果拆分結(jié)果有一邊過(guò)少,則返回None,防止過(guò)擬合 if len(left) < MINIMUM_SAMPLES or len(right) < MINIMUM_SAMPLES: return None # 拆分之后的方差和等于左子樹(shù)的方差和加上右子樹(shù)的方差和 # 因?yàn)槭欠讲詈投皇蔷讲睿钥梢岳奂?return node_variance(left) + node_variance(right)

這里我們用到了MINIMUM_SAMPLES這個(gè)參數(shù),它是用來(lái)預(yù)剪枝用的。由于我們是回歸模型,如果不對(duì)決策樹(shù)的生長(zhǎng)加以限制,那么很有可能得到的決策樹(shù)的葉子節(jié)點(diǎn)和訓(xùn)練樣本的數(shù)量一樣多。這顯然就陷入了過(guò)擬合了,對(duì)于模型的效果是有害無(wú)益的。所以我們要限制每個(gè)節(jié)點(diǎn)的樣本數(shù)量,這個(gè)是一個(gè)參數(shù),我們可以根據(jù)需要自行調(diào)整。

接下來(lái),就是特征和閾值篩選的函數(shù)了。我們需要開(kāi)發(fā)一個(gè)函數(shù)來(lái)遍歷所有可以拆分的特征和閾值,對(duì)數(shù)據(jù)進(jìn)行拆分,從所有特征當(dāng)中找到最佳的拆分可能。

def choose_feature_to_split(dataset): n = len(dataset[0])-1 m = len(dataset) # 記錄最佳方差,特征和閾值 var_ = node_variance(dataset) bestVar = float(’inf’) feature = -1 thred = None for i in range(n): threds = get_thresholds(dataset, i) for t in threds: # 遍歷所有的閾值,計(jì)算每個(gè)閾值的variance v = split_variance(dataset, i, t) # 如果v等于None,說(shuō)明拆分過(guò)擬合了,跳過(guò) if v is None: continue if v < bestVar: bestVar, feature, thred = v, i, t # 如果最好的拆分效果達(dá)不到要求,那么就不拆分,控制子樹(shù)的數(shù)量 if var_ - bestVar < MINIMUM_IMPROVE: return None, None return feature, thred

和上面一樣,這個(gè)函數(shù)當(dāng)中也用到了一個(gè)預(yù)剪枝的參數(shù)MINIMUM_IMPROVE,它衡量的是每一次生成子樹(shù)帶來(lái)的收益。當(dāng)某一次生成子樹(shù)帶來(lái)的收益小于某個(gè)值的時(shí)候,說(shuō)明收益很小,并不劃算,所以我們就放棄這次子樹(shù)的生成。這也是預(yù)剪枝的一種。

這些都搞定了之后,就可以來(lái)建樹(shù)了。建樹(shù)的過(guò)程和之前類似,只是我們這一次的數(shù)據(jù)當(dāng)中沒(méi)有特征的name,所以我們?nèi)サ籼卣髅Q的相關(guān)邏輯。

def create_decision_tree(dataset): dataset = np.array(dataset) # 如果當(dāng)前數(shù)量小于10,那么就不再繼續(xù)劃分了 if dataset.shape[0] < MINIMUM_SAMPLES: return node_mean(dataset) # 記錄最佳拆分的特征和閾值 fidx, th = choose_feature_to_split(dataset) if fidx is None: return th node = {} node[’feature’] = fidx node[’threshold’] = th # 遞歸建樹(shù) split_data, vals = split_dataset(dataset, fidx, th) for data, val in zip(split_data, vals): node[val] = create_decision_tree(data) return node

我們來(lái)完整測(cè)試一下建樹(shù),首先我們需要對(duì)原始數(shù)據(jù)進(jìn)行拆分。將原始數(shù)據(jù)拆分成訓(xùn)練數(shù)據(jù)和測(cè)試數(shù)據(jù),由于我們的場(chǎng)景比較簡(jiǎn)單,就不設(shè)置驗(yàn)證數(shù)據(jù)了。

拆分?jǐn)?shù)據(jù)不用我們自己實(shí)現(xiàn),sklearn當(dāng)中提供了相應(yīng)的工具,我們直接調(diào)用即可:

from sklearn.model_selection import train_test_splitX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=23)

我們一般用到的參數(shù)就兩個(gè),一個(gè)是test_size,它可以是一個(gè)整數(shù)也可以是一個(gè)浮點(diǎn)數(shù)。如果是整數(shù),代表的是測(cè)試集的樣本數(shù)量。如果是一個(gè)0-1.0的浮點(diǎn)數(shù),則代表測(cè)試集的占比。random_state是生成隨機(jī)數(shù)的時(shí)候用到的隨機(jī)種子。

帶你學(xué)習(xí)Python如何實(shí)現(xiàn)回歸樹(shù)模型

我們輸出一下生成的樹(shù),由于數(shù)據(jù)量比較大,可以看到一顆龐大的樹(shù)結(jié)構(gòu)。建樹(shù)的部分實(shí)現(xiàn)了之后,最后剩下的就是預(yù)測(cè)的部分了。

預(yù)測(cè)部分的代碼和之前分類樹(shù)相差不大,整體的邏輯完全一樣,只是去掉了feature_names的相關(guān)邏輯。

def classify(node, data): key = node[’feature’] pred = None thred = node[’threshold’] if isinstance(node[data[key] < thred], dict): pred = classify(node[data[key] < thred], data) else: pred = node[data[key] < thred] # 放置pred為空,挑選一個(gè)葉子節(jié)點(diǎn)作為替補(bǔ) if pred is None: for key in node: if not isinstance(node[key], dict): pred = node[key] break return pred

由于這個(gè)函數(shù)一次只能接受一條數(shù)據(jù),如果我們想要批量預(yù)測(cè)的話還不行,所以最好的話再實(shí)現(xiàn)一個(gè)批量預(yù)測(cè)的predict函數(shù)比較好。

def predict(node, X): y_pred = [] for x in X: y = classify(node, x) y_pred.append(y) return np.array(y_pred)

后剪枝

后剪枝的英文原文是post-prune,但是翻譯成事后剪枝也有點(diǎn)奇怪。anyway,我們就用后剪枝這個(gè)詞好了。

在回歸樹(shù)當(dāng)中,我們利用的思想非常樸素,在建樹(shù)的時(shí)候建立一棵盡量復(fù)雜龐大的樹(shù)。然后在通過(guò)測(cè)試集對(duì)這棵樹(shù)進(jìn)行修剪,修剪的邏輯也非常簡(jiǎn)單,我們判斷一棵子樹(shù)存在分叉和沒(méi)有分叉單獨(dú)成為葉子節(jié)點(diǎn)時(shí)的誤差,如果修剪之后誤差更小,那么我們就減去這棵子樹(shù)。

整個(gè)剪枝的過(guò)程和建樹(shù)的過(guò)程一樣,從上到下,遞歸執(zhí)行。

整個(gè)邏輯很好理解,我們直接來(lái)看代碼:

def is_dict(node): return isinstance(node, dict)def prune(node, testData): testData = np.array(testData) if testData.shape[0] == 0: return node # 拆分?jǐn)?shù)據(jù) split_data, _ = split_dataset(testData, node[’feature’], node[’threshold’]) # 對(duì)左右子樹(shù)遞歸修剪 if is_dict(node[0]): node[0] = prune(node[0], split_data[0]) if is_dict(node[1]) and len(split_data) > 1: node[1] = prune(node[1], split_data[1]) # 如果左右都是葉子節(jié)點(diǎn),那么判斷當(dāng)前子樹(shù)是否需要修剪 if len(split_data) > 1 and not is_dict(node[0]) and not is_dict(node[1]): # 計(jì)算修剪前的方差和 baseError = np.sum(np.power(np.array(split_data[0])[:, -1] - node[0], 2)) + np.sum(np.power(np.array(split_data[1])[:, -1] - node[1], 2)) # 計(jì)算修剪后的方差和 meanVal = (node[0] + node[1]) / 2 mergeError = np.sum(np.power(meanVal - testData[:, -1], 2)) if mergeError < baseError: return meanVal else: return node return node

最后,我們對(duì)修剪之后的效果做一下驗(yàn)證:

帶你學(xué)習(xí)Python如何實(shí)現(xiàn)回歸樹(shù)模型

從圖中可以看到,修剪之前我們?cè)跍y(cè)試數(shù)據(jù)上的均方差是19.65,而修剪之后降低到了19.48。從數(shù)值上來(lái)看是有效果的,只是由于我們的訓(xùn)練數(shù)據(jù)比較少,同時(shí)進(jìn)行了預(yù)剪枝,影響了后剪枝的效果。但是對(duì)于實(shí)際的機(jī)器學(xué)習(xí)工程來(lái)說(shuō),一個(gè)方法只要是有明確效果的,在代價(jià)可以承受的范圍內(nèi),它就是有價(jià)值的,千萬(wàn)不能覺(jué)得提升不明顯,而隨便否定一個(gè)方法。

這里計(jì)算均方差的時(shí)候用到了sklearn當(dāng)中的一個(gè)庫(kù)函數(shù)mean_square_error,從名字當(dāng)中我們也可以看得出來(lái)它的用途,它可以對(duì)兩個(gè)Numpy的array計(jì)算均方差

總結(jié)

關(guān)于回歸樹(shù)模型的相關(guān)內(nèi)容到這里就結(jié)束了,我們不僅親手實(shí)現(xiàn)了模型,而且還在真實(shí)的數(shù)據(jù)集上做了實(shí)驗(yàn)。如果你是親手實(shí)現(xiàn)的模型的代碼,相信你一定會(huì)有很多收獲。

雖然從實(shí)際運(yùn)用來(lái)說(shuō)我們幾乎不會(huì)使用樹(shù)模型來(lái)做回歸任務(wù),但是回歸樹(shù)模型本身是非常有意義的。因?yàn)樵谒幕A(chǔ)上我們發(fā)展出了很多效果更好的模型,比如大名鼎鼎的GBDT。因此理解回歸樹(shù)對(duì)于我們后續(xù)進(jìn)階的學(xué)習(xí)是非常重要的。在深度學(xué)習(xí)普及之前,其實(shí)大多數(shù)高效果的模型都是以樹(shù)模型為基礎(chǔ)的,比如隨機(jī)森林、GBDT、Adaboost等等。可以說(shuō)樹(shù)模型撐起了機(jī)器學(xué)習(xí)的半個(gè)時(shí)代,這么說(shuō)相信大家應(yīng)該都能理解它的重要性了吧。

今天的文章就到這里,如果喜歡本文,可以的話,請(qǐng)點(diǎn)個(gè)關(guān)注,給我一點(diǎn)鼓勵(lì),也方便獲取更多文章。

以上就是帶你學(xué)習(xí)Python如何實(shí)現(xiàn)回歸樹(shù)模型的詳細(xì)內(nèi)容,更多關(guān)于Python實(shí)現(xiàn)回歸樹(shù)模型的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Python 編程
相關(guān)文章:
主站蜘蛛池模板: 亚洲高清在线播放 | 日韩 欧美 国产 师生 制服 | 手机看片久久青草福利盒子 | 91视频啪啪| 免费一级a毛片 | 毛片网站在线看 | 久久一二| 2022国产精品手机在线观看 | 精品成人免费视频 | 亚洲一区 在线播放 | 自拍理论片| 久久综合99re久久爱 | 日韩一级片在线观看 | 日韩欧美不卡一区二区三区 | 一区二区三区免费视频 www | 免费精品一区二区三区在线观看 | 国产精品伦理久久久久 | 国产精品黄网站 | 成 人色 网 站 欧美大片在线观看 | 久久免费观看国产精品 | 国产成人精品亚洲一区 | 欧美14videosex性欧美成人 | 草草影院视频 | 国产欧美在线观看不卡 | 国产伦久视频免费观看视频 | 欧美成人免费观看的 | 久久精品香蕉 | 亚洲高清国产拍精品影院 | 国产精品视频久久久 | 久久在线 | 欧美69精品国产成人 | 欧美午夜性春猛交 | 99精品国产在热久久 | 欧美亚洲国产成人精品 | 成人免费视频一区二区 | 免费一级毛片在线播放不收费 | 另类视频区第一页 | 国产成人免费午夜性视频 | 成年女人免费观看视频 | 黄色毛片免费 | 在线不卡国产 |