機(jī)器學(xué)習(xí)實(shí)戰(zhàn)-08-樹回歸

一渺绒、樹回歸介紹

線性回歸包含了一些強(qiáng)大的方法歹叮,但這些方法創(chuàng)建的模型需要擬合所有的樣本點(diǎn)(局部加權(quán)線性回歸除外)哗讥。當(dāng)數(shù)據(jù)擁有眾多特征并且特征之間關(guān)系十分復(fù)雜時(shí)包颁,構(gòu)建全局模型的想法就顯得太難了瞻想,也略顯笨拙。而且娩嚼,實(shí)際生活中很多問題都是非線性的蘑险,不可能使用全局線性模型來(lái)擬合任何數(shù)據(jù)。一種可行的方法是將數(shù)據(jù)集切分成很多份易建模的數(shù)據(jù)岳悟,然后利用第8章的線性回歸技術(shù)來(lái)建模佃迄。如果首次切分后仍然難以擬合線性模型就繼續(xù)切分。在這種切分方式下贵少,樹結(jié)構(gòu)和回歸法就相當(dāng)有用呵俏。

1、ID3算法的弊端
決策樹的樹構(gòu)建算法是ID3滔灶,ID3的做法是每次選取當(dāng)前最佳的特征來(lái)分割數(shù)據(jù)普碎,并按照該特征的所有可能取值來(lái)切分。也就是說(shuō)录平,如果一個(gè)特征有4種取值麻车,那么數(shù)據(jù)將被切分成4份缀皱。一旦按某特征切分后,該特征在之后的算法執(zhí)行過(guò)程中將不會(huì)再起作用动猬,所以有觀點(diǎn)認(rèn)為這種切分方式過(guò)于迅速啤斗。
除了切分過(guò)于迅速外,ID3算法還存在另一個(gè)問題枣察,它不能直接處理連續(xù)型特征争占。只有事先將連續(xù)型特征離散化,才能在ID3算法中使用序目。但這種轉(zhuǎn)換過(guò)程會(huì)破壞連續(xù)型變量的內(nèi)在特性臂痕。

2、CART算法
與ID3算法相反猿涨,CART算法正好適用于連續(xù)型特征握童。CART算法使用二元切分法來(lái)處理連續(xù)型變量。而使用二元切分法則易于對(duì)樹構(gòu)建過(guò)程進(jìn)行調(diào)整以處理連續(xù)型特征叛赚。具體的處理方法是:如果特征值大于給定值就走左子樹澡绩,否則就走右子樹。

CART算法有兩步:

  • 決策樹生成:遞歸地構(gòu)建二叉決策樹的過(guò)程俺附,基于訓(xùn)練數(shù)據(jù)集生成決策樹肥卡,生成的決策樹要盡量大;自上而下從根開始建立節(jié)點(diǎn)事镣,在每個(gè)節(jié)點(diǎn)處要選擇一個(gè)最好的屬性來(lái)分裂步鉴,使得子節(jié)點(diǎn)中的訓(xùn)練集盡量的純。不同的算法使用不同的指標(biāo)來(lái)定義"最好":
  • 決策樹剪枝:用驗(yàn)證數(shù)據(jù)集對(duì)已生成的樹進(jìn)行剪枝并選擇最優(yōu)子樹璃哟,這時(shí)損失函數(shù)最小作為剪枝的標(biāo)準(zhǔn)氛琢。
    決策樹剪枝我們先不管,我們看下決策樹生成随闪。

在決策樹的文章中阳似,我們先根據(jù)信息熵的計(jì)算找到最佳特征切分?jǐn)?shù)據(jù)集構(gòu)建決策樹。CART算法的決策樹生成也是如此铐伴,實(shí)現(xiàn)過(guò)程如下:

  • 使用CART算法選擇特征
  • 根據(jù)特征切分?jǐn)?shù)據(jù)集合
  • 構(gòu)建樹

下面將利用以下數(shù)據(jù)集實(shí)現(xiàn)cart算法

先看下根據(jù)特征切分?jǐn)?shù)據(jù)集合的效果

上貼全文代碼

from numpy import *
from matplotlib.font_manager import FontProperties
from tkinter import *
import matplotlib

# 將matplotlib后端設(shè)置為TkAgg
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from sklearn.tree import DecisionTreeRegressor


def load_data_set(file_name):
    """
        Function:
            加載數(shù)據(jù)
        Parameters:
            file_name - 文件名
        Returns:
            data_mat - 數(shù)據(jù)矩陣
        Modify:
            2018-10-30
    """
    data_mat = []
    fr = open(file_name)
    for line in fr.readlines():
        cur_line = line.strip().split('\t')
        flt_line = list(map(float, cur_line))
        data_mat.append(flt_line)
    return data_mat


# 根據(jù)特征切分?jǐn)?shù)據(jù)集合
def bin_split_data_set(data_set, feature, value):
    """
        Function:
            根據(jù)特征切分?jǐn)?shù)據(jù)集合
        Parameters:
            data_set - 數(shù)據(jù)集合
            feature - 待切分的特征
            value - 該特征的值
        Returns:
            mat0 - 切分的數(shù)據(jù)集合0
            mat1 - 切分的數(shù)據(jù)集合1
        Modify:
            2018-10-30
    """
    mat0 = data_set[nonzero(data_set[:, feature] > value)[0], :]
    mat1 = data_set[nonzero(data_set[:, feature] <= value)[0], :]
    return mat0, mat1


def reg_leaf(data_set):
    """
        Function:
            生成葉結(jié)點(diǎn)
        Parameters:
            data_set - 數(shù)據(jù)集合
        Returns:
            mean - 目標(biāo)變量的均值
        Modify:
            2018-10-31
    """
    return mean(data_set[:, -1])


def reg_err(data_set):
    """
        Function:
            生成葉結(jié)點(diǎn)
        Parameters:
            data_set - 數(shù)據(jù)集合
        Returns:
            var - 目標(biāo)變量的總方差
        Modify:
            2018-10-31
    """
    # var()計(jì)算平方誤差
    return var(data_set[:, -1]) * shape(data_set)[0]


def choose_best_split(data_set, leaf_type=reg_leaf, err_type=reg_err, ops=(1, 4)):
    """
        Function:
            找到數(shù)據(jù)的最佳二元切分方式函數(shù)
        Parameters:
            data_set - 數(shù)據(jù)集合
            leaf_type - 生成葉結(jié)點(diǎn)
            err_type - 誤差估計(jì)函數(shù)
            ops - 用戶定義的參數(shù)構(gòu)成的元組
        Returns:
            best_index - 最佳切分特征
            best_value - 最佳特征值
        Modify:
            2018-10-31
    """
    # tol_s允許的誤差下降值撮奏,tol_n切分的最少樣本數(shù)
    tol_s = ops[0]
    tol_n = ops[1]
    # 將數(shù)組或者矩陣轉(zhuǎn)換成列表,統(tǒng)計(jì)不同剩余特征值的數(shù)目
    if len(set(data_set[:, -1].T.tolist()[0])) == 1:
        return None, leaf_type(data_set)
    m, n = shape(data_set)
    s = err_type(data_set)
    # 分別為最佳誤差盛杰,最佳特征切分的索引值挽荡,最佳特征值
    best_s = float('inf')
    best_index = 0
    best_value = 0
    # 遍歷所有特征列
    for feat_index in range(n - 1):
        # 遍歷所有特征值
        for split_val in set(data_set[:, feat_index].T.A.tolist()[0]):
            # 根據(jù)特征和特征值切分?jǐn)?shù)據(jù)集
            mat0, mat1 = bin_split_data_set(data_set, feat_index, split_val)
            # 如果數(shù)據(jù)少于tol_n,則退出
            if (shape(mat0)[0] < tol_n) or (shape(mat1)[0] < tol_n): continue
            # 計(jì)算誤差估計(jì)
            new_s = err_type(mat0) + err_type(mat1)
            # 如果誤差估計(jì)更小,則更新最佳特征索引值和特征值
            if new_s < best_s:
                best_index = feat_index
                best_value = split_val
                best_s = new_s
    # 如果誤差減少不大則退出
    if (s - best_s) < tol_s:
        return None, leaf_type(data_set)
    # 根據(jù)最佳的切分特征和特征值切分?jǐn)?shù)據(jù)集合
    mat0, mat1 = bin_split_data_set(data_set, best_index, best_value)
    # 如果切分出的數(shù)據(jù)集很小則退出
    if (shape(mat0)[0] < tol_n) or (shape(mat1)[0] < tol_n):
        return None, leaf_type(data_set)
    # 返回最佳切分特征和特征值
    return best_index, best_value


def create_tree(data_set, leaf_type=reg_leaf, err_type=reg_err, ops=(1, 4)):
    """
        Function:
            構(gòu)建回歸樹
        Parameters:
            data_set - 數(shù)據(jù)集合
            leaf_type - 建立葉結(jié)點(diǎn)的函數(shù)
            err_type - 誤差計(jì)算函數(shù)
            ops - 包含樹構(gòu)建所有其他參數(shù)的元組
        Returns:
            ret_tree - 構(gòu)建的回歸樹
        Modify:
            2018-10-31
    """
    feat, val = choose_best_split(data_set, leaf_type, err_type, ops)
    if feat == None: return val
    ret_tree = {}
    ret_tree['spInd'] = feat
    ret_tree['spVal'] = val
    lset, rset = bin_split_data_set(data_set, feat, val)
    # print(lset)
    # print(rset)
    ret_tree['left'] = create_tree(lset, leaf_type, err_type, ops)
    ret_tree['right'] = create_tree(rset, leaf_type, err_type, ops)
    return ret_tree


def plot_data_set(file_name):
    """
        Function:
            繪制數(shù)據(jù)集
        Parameters:
            file_name - 文件名
        Returns:
            無(wú)
        Modify:
            2018-10-31
    """
    font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
    data_mat = load_data_set(file_name)
    n = len(data_mat)
    xcord = []
    ycord = []
    for i in range(n):
        # ex00.txt、ex2.txt即供、exp2.txt數(shù)據(jù)集
        xcord.append(data_mat[i][0])
        ycord.append(data_mat[i][1])
        # ex0.txt數(shù)據(jù)集
        # xcord.append(data_mat[i][1])
        # ycord.append(data_mat[i][2])
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(xcord, ycord, s=20, c='blue', alpha=0.5)
    # plt.title('data_set')
    # plt.xlabel('X')
    ax_xlabel_text = ax.set_xlabel(u'騎自行車的速度', FontProperties=font)
    ax_ylabel_text = ax.set_ylabel(u'智商(IQ)', FontProperties=font)
    plt.show()


def is_tree(obj):
    """
        Function:
            判斷測(cè)試輸入變量是否是一棵樹
        Parameters:
            obj - 判斷對(duì)象
        Returns:
            是否是一棵樹
        Modify:
            2018-11-02
    """
    return (type(obj).__name__ == 'dict')


def get_mean(tree):
    """
        Function:
            對(duì)樹進(jìn)行塌陷處理(即返回樹平均值)
        Parameters:
            tree - 樹
        Returns:
            樹的平均值
        Modify:
            2018-11-02
    """
    if is_tree(tree['right']):
        tree['right'] = get_mean(tree['right'])
    if is_tree(tree['left']):
        tree['left'] = get_mean(tree['left'])
    return (tree['left'] + tree['right']) / 2.0


def prune(tree, test_data):
    """
        Function:
            后剪枝
        Parameters:
            tree - 樹
            test_data - 測(cè)試集
        Returns:
            后剪枝的樹
        Modify:
            2018-11-02
    """
    # 如果測(cè)試集為空定拟,則對(duì)樹進(jìn)行塌陷處理
    if shape(test_data)[0] == 0:
        return get_mean(tree)
    # 如果有左子樹或者右子樹,則切分?jǐn)?shù)據(jù)集
    if (is_tree(tree['right']) or is_tree(tree['left'])):
        lset, rset = bin_split_data_set(test_data, tree['spInd'], tree['spVal'])
    # 處理左子樹(剪枝)
    if is_tree(tree['left']):
        tree['left'] = prune(tree['left'], lset)
    # 處理右子樹(剪枝)
    if is_tree(tree['right']):
        tree['right'] = prune(tree['right'], rset)
    # 如果當(dāng)前結(jié)點(diǎn)的左右結(jié)點(diǎn)為葉結(jié)點(diǎn)
    if not is_tree(tree['left']) and not is_tree(tree['right']):
        lset, rset = bin_split_data_set(test_data, tree['spInd'], tree['spVal'])
        # 計(jì)算沒有合并的誤差
        error_nomerge = sum(power(lset[: -1] - tree['left'], 2)) + sum(power(rset[:, -1] - tree['right'], 2))
        # 計(jì)算合并的均值
        tree_mean = (tree['left'] + tree['right']) / 2.0
        # 計(jì)算合并的誤差
        error_merge = sum(power(test_data[:, -1] - tree_mean, 2))
        # 如果合并的誤差小于沒有合并的誤差,則合并
        if error_merge < error_nomerge:
            return tree_mean
        else:
            return tree
    else:
        return tree


def liner_solve(data_set):
    """
        Function:
            模型樹葉節(jié)點(diǎn)生成
        Parameters:
            data_set - 數(shù)據(jù)集
        Returns:
            ws -回歸系數(shù)
            x - 數(shù)據(jù)集矩陣
            y - 目標(biāo)變量值矩陣
        Modify:
            2018-11-03
    """
    m, n = shape(data_set)
    # 構(gòu)建大小為(m,n)和(m,1)的矩陣
    x = mat(ones((m, n)))
    y = mat(ones((m, 1)))
    # 數(shù)據(jù)集矩陣的第一列初始化為1青自,偏置項(xiàng)
    x[:, 1:n] = data_set[:, 0:n - 1]
    # 每個(gè)樣本目標(biāo)變量值存入Y
    y = data_set[:, -1]
    # 對(duì)數(shù)據(jù)集矩陣求內(nèi)積
    x_t_x = x.T * x
    # 計(jì)算行列式值是否為0株依,即判斷是否可逆
    if linalg.det(x_t_x) == 0.0:
        # 不可逆,打印信息
        raise NameError('This matrix is singular, cannot du inverse, \ntry increasing the second value of ops')
    # 可逆延窜,計(jì)算回歸系數(shù)
    ws = x_t_x.I * (x.T * y)
    return ws, x, y


def model_leaf(data_set):
    """
        Function:
            模型樹的葉節(jié)點(diǎn)模型恋腕,當(dāng)數(shù)據(jù)不再需要切分時(shí)生成葉節(jié)點(diǎn)的模型
        Parameters:
            data_set - 數(shù)據(jù)集
        Returns:
            ws -回歸系數(shù)
        Modify:
            2018-11-03
    """
    ws, x, y = liner_solve(data_set)
    return ws


def model_err(data_set):
    """
        Function:
            模型樹的誤差計(jì)算函數(shù)
        Parameters:
            data_set - 數(shù)據(jù)集
        Returns:
            返回誤差平方和,平方損失
        Modify:
            2018-11-03
    """
    ws, x, y = liner_solve(data_set)
    y_hat = x * ws
    return sum(power(y - y_hat, 2))


def plot_model_data_set(data_set, tree):
    """
        Function:
            繪制模型樹
        Parameters:
            data_set - 數(shù)據(jù)集
            tree - 模型樹
        Returns:
            無(wú)
        Modify:
            2018-10-31
    """
    n = len(data_set)
    xcord = []
    ycord = []
    xcord_1 = []
    xcord_2 = []
    for i in range(n):
        xcord.append(data_set[i][0])
        ycord.append(data_set[i][1])
        if data_set[i][0] <= tree['spVal']:
            xcord_1.append(data_set[i][0])
        else:
            xcord_2.append(data_set[i][0])
    xcord_1_mat = mat(xcord_1)
    xcord_2_mat = mat(xcord_2)
    ws_1 = tree['right']
    ws_2 = tree['left']
    y_hat_1 = ws_1[0] + ws_1[1] * xcord_1_mat
    y_hat_2 = ws_2[0] + ws_2[1] * xcord_2_mat
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.plot(xcord_1_mat.flatten().A[0], y_hat_1.flatten().A[0], c='red')
    ax.plot(xcord_2_mat.flatten().A[0], y_hat_2.flatten().A[0], c='red')
    ax.scatter(xcord, ycord, s=20, c='blue', alpha=0.5)
    plt.title('data_set')
    plt.xlabel('X')
    plt.show()


def reg_tree_eval(model, in_dat):
    """
        Function:
            用樹回歸進(jìn)行預(yù)測(cè)代碼
        Parameters:
            model - 樹回歸模型
            in_dat - 輸入數(shù)據(jù)
        Returns:
            回歸樹的葉節(jié)點(diǎn)為float型常量
        Modify:
            2018-11-12
    """
    return float(model)


def model_tree_eval(model, in_dat):
    """
        Function:
            模型樹的葉節(jié)點(diǎn)浮點(diǎn)型參數(shù)的線性方程
        Parameters:
            model - 模型樹模型
            in_dat - 輸入數(shù)據(jù)
        Returns:
            浮點(diǎn)型的回歸系數(shù)向量
        Modify:
            2018-11-12
    """
    # 獲取輸入數(shù)據(jù)的列數(shù)
    n = shape(in_dat)[1]
    # 構(gòu)建n+1維的單列矩陣
    x = mat(ones((1, n + 1)))
    # 第一列設(shè)置為1逆瑞,線性方程偏置項(xiàng)b
    x[:, 1:n + 1] = in_dat
    return float(x * model)


def tree_fore_cast(tree, in_data, model_eval=reg_tree_eval):
    """
        Function:
            樹預(yù)測(cè)
        Parameters:
            tree - 樹回歸模型
            in_dat - 輸入數(shù)據(jù)
            model - 模型樹模型
        Returns:
            浮點(diǎn)型的回歸系數(shù)向量
        Modify:
            2018-11-13
    """
    # 如果當(dāng)前樹為葉節(jié)點(diǎn)荠藤,生成葉節(jié)點(diǎn)
    if not is_tree(tree): return model_eval(tree, in_data)
    # 非葉節(jié)點(diǎn),對(duì)該子樹對(duì)應(yīng)的切分點(diǎn)對(duì)輸入數(shù)據(jù)進(jìn)行切分
    if in_data[tree['spInd']] > tree['spVal']:
        # 該樹的左分支為非葉節(jié)點(diǎn)類型
        if is_tree(tree['left']):
            # 遞歸調(diào)用treeForeCast函數(shù)繼續(xù)樹預(yù)測(cè)過(guò)程获高,直至找到葉節(jié)點(diǎn)
            return tree_fore_cast(tree['left'], in_data, model_eval)
        else:
            # 左分支為葉節(jié)點(diǎn)哈肖,生成葉節(jié)點(diǎn)
            return model_eval(tree['left'], in_data)
    # 小于切分點(diǎn)值的右分支
    else:
        # 非葉節(jié)點(diǎn)類型
        if is_tree(tree['right']):
            # 非葉節(jié)點(diǎn)類型
            return tree_fore_cast(tree['right'], in_data, model_eval)
        else:
            # 葉節(jié)點(diǎn),生成葉節(jié)點(diǎn)類型
            return model_eval(tree['right'], in_data)


def create_fore_cast(tree, test_data, model_eval=reg_tree_eval):
    """
        Function:
            創(chuàng)建預(yù)測(cè)樹
        Parameters:
            tree - 樹回歸模型
            test_data - 測(cè)試數(shù)據(jù)
            model - 模型樹模型
        Returns:
            預(yù)測(cè)值
        Modify:
            2018-11-13
    """
    m = len(test_data)
    # 初始化行向量各維度值為1
    y_hat = mat(zeros((m, 1)))
    for i in range(m):
        y_hat[i, 0] = tree_fore_cast(tree, mat(test_data[i]), model_eval)
    return y_hat


def re_draw(tol_s, tol_n):
    """
        Function:
            以用戶輸入的終止條件為參數(shù)繪制樹
        Parameters:
            tol_s - 允許的誤差下降值
            tol_n - 切分的最少樣本數(shù)
        Returns:
            無(wú)
        Modify:
            2018-11-14
    """
    # 清空之前的圖像
    re_draw.f.clf()
    re_draw.a = re_draw.f.add_subplot(111)
    # 檢查復(fù)選框是否選中念秧,選中就構(gòu)建模型樹淤井,選中默認(rèn)構(gòu)回歸樹
    if chk_btn_val.get():
        if tol_n < 2: tol_n = 2
        my_tree = create_tree(re_draw.raw_dat, model_leaf, model_err, (tol_s, tol_n))
        y_hat = create_fore_cast(my_tree, re_draw.test_dat, model_tree_eval)
    else:
        my_tree = create_tree(re_draw.raw_dat, ops=(tol_s, tol_n))
        y_hat = create_fore_cast(my_tree, re_draw.test_dat)
    # 繪制真實(shí)值
    re_draw.a.scatter(re_draw.raw_dat[:, 0].tolist(), re_draw.raw_dat[:, 1].tolist(), s=5)
    # 繪制預(yù)測(cè)值
    re_draw.a.plot(re_draw.test_dat, y_hat, linewidth=2.0)
    re_draw.canvas.show()


def get_inputs():
    """
        Function:
            從文本輸入框中獲取樹創(chuàng)建終止條件,沒有則用默認(rèn)值
        Parameters:
            無(wú)
        Returns:
            tol_s - 允許的誤差下降值
            tol_n - 切分的最少樣本數(shù)
        Modify:
            2018-11-14
    """
    try:
        tol_n = int(tol_n_entry.get())
    # 清除錯(cuò)誤的輸入并用默認(rèn)值替換
    except:
        tol_n = 10
        print('enter Integer for tol_n')
        tol_n_entry.delete(0, END)
        tol_n_entry.insert(0, '10')
    try:
        tol_s = float(tol_n_entry.get())
    except:
        tol_n = 10
        print('enter Integer for tol_s')
        tol_n_entry.delete(0, END)
        tol_n_entry.insert(0, '1.0')
    return tol_n, tol_s


def draw_new_tree():
    """
        Function:
            按下re_draw按鈕時(shí)摊趾,開始繪圖
        Parameters:
            無(wú)
        Returns:
            無(wú)
        Modify:
            2018-11-14
    """
    # 從文本輸入框中獲取樹創(chuàng)建終止條件
    tol_n, tol_s = get_inputs()
    re_draw(tol_s, tol_n)


if __name__ == '__main__':
    # test_mat = mat(eye(4))
    # mat0, mat1 = bin_split_data_set(test_mat, 1, 0.5)
    # print('原始集合:\n', test_mat)
    # print('mat0:\n', mat0)
    # print('mat1:\n', mat1)

    # file_name = './machinelearninginaction/Ch09/ex00.txt'
    # plot_data_set(file_name)

    # data_set = load_data_set('./machinelearninginaction/Ch09/ex00.txt')
    # data_mat = mat(data_set)
    # best_feat, val = choose_best_split(data_mat)
    # print(best_feat)
    # print(val)


    # data_set = load_data_set('./machinelearninginaction/Ch09/ex00.txt')
    # data_mat = mat(data_set)
    # my_tree = create_tree(data_mat)
    # print(my_tree)

    # file_name = './machinelearninginaction/Ch09/ex0.txt'
    # plot_data_set(file_name)

    # data_set = load_data_set('./machinelearninginaction/Ch09/ex0.txt')
    # data_mat = mat(data_set)
    # my_tree = create_tree(data_mat)
    # print(my_tree)


    # 預(yù)剪枝
    # file_name = './machinelearninginaction/Ch09/ex2.txt'
    # plot_data_set(file_name)

    # data_set = load_data_set('./machinelearninginaction/Ch09/ex2.txt')
    # data_mat = mat(data_set)
    # my_tree = create_tree(data_mat, ops=(1000, 4))
    # print(my_tree)

    # 后剪枝
    # print('剪枝前:')
    # train_data = load_data_set('./machinelearninginaction/Ch09/ex2.txt')
    # train_mat = mat(train_data)
    # tree = create_tree(train_mat)
    # print(tree)
    # print('\n剪枝后:')
    # test_data = load_data_set('./machinelearninginaction/Ch09/ex2test.txt')
    # test_mat = mat(test_data)
    # print(prune(tree, test_mat))


    # file_name = './machinelearninginaction/Ch09/exp2.txt'
    # plot_data_set(file_name)

    # data_set = load_data_set('./machinelearninginaction/Ch09/exp2.txt')
    # data_mat = mat(data_set)
    # tree = create_tree(data_mat, model_leaf, model_err, (1, 10))
    # print(tree)

    # data_set = load_data_set('./machinelearninginaction/Ch09/exp2.txt')
    # data_mat = mat(data_set)
    # tree = create_tree(data_mat, model_leaf, model_err, (1, 10))
    # plot_model_data_set(data_set, tree)


    # plot_data_set('./machinelearninginaction/Ch09/bikeSpeedVsIq_train.txt')


    # train_mat = mat(load_data_set('./machinelearninginaction/Ch09/bikeSpeedVsIq_train.txt'))
    # test_mat = mat(load_data_set('./machinelearninginaction/Ch09/bikeSpeedVsIq_test.txt'))
    # # 回歸樹
    # reg_tree = create_tree(train_mat, ops=(1, 20))
    # # 模型樹
    # model_tree = create_tree(train_mat, model_leaf, model_err, ops=(1, 20))
    # y_hat_reg_tree = create_fore_cast(reg_tree, test_mat[:, 0])
    # y_hat_model_tree = create_fore_cast(model_tree, test_mat[:, 0], model_tree_eval)
    # relation_reg_tree = corrcoef(y_hat_reg_tree, test_mat[:, 1], rowvar=0)[0, 1]
    # relation_model_tree = corrcoef(y_hat_model_tree, test_mat[:, 1], rowvar=0)[0, 1]
    # # 線性方程
    # ws, x, y = liner_solve(train_mat)
    # m = shape(test_mat)[0]
    # y_hat = mat(zeros((m, 1)))
    # for i in range(m):
    #     y_hat[i] = test_mat[i, 0] * ws[1, 0] + ws[0, 0]
    # relation_linear_solve = corrcoef(y_hat, test_mat[:, 1], rowvar=0)[0, 1]
    # print('回歸樹相關(guān)系數(shù):', relation_reg_tree)
    # print('模型樹相關(guān)系數(shù):', relation_model_tree)
    # print('線性方程相關(guān)系數(shù):', relation_linear_solve)

    # root = Tk()
    #
    # # 標(biāo)簽部件
    # # Label(root, text='Plot Place Holder').grid(row=0, columnspan=3)
    # re_draw.f = Figure(figsize=(5, 4), dpi=100)
    # re_draw.canvas = FigureCanvasTkAgg(re_draw.f, master=root)
    # re_draw.canvas.show()
    # re_draw.canvas.get_tk_widget().grid(row=0, columnspan=3)
    # # 文本輸入框部件tol_n
    # Label(root, text='tol_n').grid(row=1, column=0)
    # tol_n_entry = Entry(root)
    # tol_n_entry.grid(row=1, column=0)
    # tol_n_entry.insert(0, '10')
    # # 文本輸入框部件
    # Label(root, text='tol_s').grid(row=2, column=0)
    # tol_n_entry = Entry(root)
    # tol_n_entry.grid(row=2, column=0)
    # tol_n_entry.insert(0, '1.0')
    # # 按鈕部件
    # Button(root, text='re_draw', comman=draw_new_tree).grid(row=1, column=2, rowspan=3)
    # chk_btn_val = IntVar()
    # chk_btn = Checkbutton(root, text='Model Tree', variable=chk_btn_val)
    # chk_btn.grid(row=3, column=0, columnspan=2)
    # re_draw.raw_dat = mat(load_data_set('./machinelearninginaction/Ch09/sine.txt'))
    # re_draw.test_dat = arange(min(re_draw.raw_dat[:, 0]), max(re_draw.raw_dat[:, 0]), 0.01)
    #
    # re_draw(1.0, 10)
    # root.mainloop()

    train_mat = mat(load_data_set('./machinelearninginaction/Ch09/bikeSpeedVsIq_train.txt'))
    test_mat = mat(load_data_set('./machinelearninginaction/Ch09/bikeSpeedVsIq_test.txt'))
    reg_tree = DecisionTreeRegressor(max_depth=4)
    reg_tree = reg_tree.fit(train_mat[:, 0], train_mat[:, 1])
    y_hat_reg_tree = reg_tree.predict(test_mat[:, 0])
    relation_reg_tree = corrcoef(y_hat_reg_tree, test_mat[:, 1], rowvar=0)[0, 1]
    print('模型樹相關(guān)系數(shù):', relation_reg_tree)

二币狠、實(shí)現(xiàn)CART算法

可以看到,切分的最佳特征為第1列特征砾层,最佳切分特征值為0.48813漩绵,這是根據(jù)誤差估計(jì)的大小,我們選擇的這個(gè)特征值可以使誤差最小化肛炮。

有了最佳的切分的特征和特征值渐行,接下來(lái)就是利用選出的這兩個(gè)變量創(chuàng)建回歸樹了。根據(jù)切分的特征和特征值切分出兩個(gè)數(shù)據(jù)集铸董,然后將兩個(gè)數(shù)據(jù)集分別用于左子樹的構(gòu)建和右子樹的構(gòu)建,直到無(wú)法找到切分的特征為止肴沫∷诤Γ可以通過(guò)遞歸實(shí)現(xiàn)這個(gè)過(guò)程。

從上圖可知颤芬,這棵樹只有兩個(gè)葉結(jié)點(diǎn)悲幅。
再看一個(gè)多次切分的例子,數(shù)據(jù)集如下:

第0列的數(shù)據(jù)都是1.0站蝠,為了可視化方便汰具,我們將第1列作為x軸數(shù)據(jù),第2列作為y軸數(shù)據(jù)菱魔。對(duì)數(shù)據(jù)進(jìn)行可視化留荔。

可以看到,這個(gè)數(shù)據(jù)集是分段的澜倦,針對(duì)此數(shù)據(jù)集創(chuàng)建回歸樹聚蝶。

可以看到杰妓,該數(shù)的結(jié)構(gòu)中包含5個(gè)葉結(jié)點(diǎn)。

到現(xiàn)在為止碘勉,已經(jīng)完成回歸樹的構(gòu)建巷挥,但是需要某種措施來(lái)檢查構(gòu)建過(guò)程否得當(dāng)。下面將介紹樹剪枝(tree pruning)技術(shù)验靡,它通過(guò)對(duì)決策樹剪枝來(lái)達(dá)到更好的預(yù)測(cè)效果倍宾。

四、預(yù)剪枝

一棵樹如果節(jié)點(diǎn)過(guò)多胜嗓,表明該模型可能對(duì)數(shù)據(jù)進(jìn)行了“過(guò)擬合”高职。通過(guò)降低決策樹的復(fù)雜度來(lái)避免過(guò)擬合的過(guò)程稱為剪枝(pruning),在函數(shù)chooseBestSplit()中的提前終止條件兼蕊,實(shí)際上是在進(jìn)行一種所謂的預(yù)剪枝(prepruning)操作初厚。另一種形式的剪枝需要使用測(cè)試集和訓(xùn)練集,稱作后剪枝(postpruning)孙技。

4.11产禾、預(yù)剪枝

樹構(gòu)建算法其實(shí)對(duì)輸入的參數(shù)tol_s和tol_n非常敏感,如果使用其他值將不太容易達(dá)到這么好的效果牵啦,預(yù)剪枝有一定的局限性亚情,比如我們現(xiàn)在使用一個(gè)新的數(shù)據(jù)集。

可以看到哈雏,這個(gè)數(shù)據(jù)集與使用的第一個(gè)數(shù)據(jù)集很相似楞件,但是區(qū)別在于y的數(shù)量級(jí)差100倍,數(shù)據(jù)分布相似裳瘪,因此構(gòu)建出的樹應(yīng)該也是只有兩個(gè)葉結(jié)點(diǎn)土浸,我們使用默認(rèn)tol_s和tol_n參數(shù)創(chuàng)建樹:

可以看到,構(gòu)建出的樹有很多葉結(jié)點(diǎn)彭羹。產(chǎn)生這個(gè)現(xiàn)象的原因在于黄伊,停止條件tol_s對(duì)誤差的數(shù)量級(jí)十分敏感。如果在選項(xiàng)中花費(fèi)時(shí)間并對(duì)上述誤差容忍度取平均值派殷,或許也能得到僅有兩個(gè)葉結(jié)點(diǎn)組成的樹:

可以看到还最,將參數(shù)tol_s修改為10000后,構(gòu)建的樹就是只有兩個(gè)葉結(jié)點(diǎn)毡惜。然而拓轻,顯然這個(gè)值,需要我們經(jīng)過(guò)不斷測(cè)試得來(lái)经伙,顯然通過(guò)不斷修改停止條件來(lái)得到合理結(jié)果并不是很好的辦法扶叉。事實(shí)上,我們常常甚至不確定到底需要尋找什么樣的結(jié)果。因?yàn)閷?duì)于一個(gè)很多維度的數(shù)據(jù)集辜梳,你也不知道構(gòu)建的樹需要多少個(gè)葉結(jié)點(diǎn)粱甫。

可見,預(yù)剪枝有很大的局限性作瞄。接下來(lái)茶宵,我們討論后剪枝,即利用測(cè)試集來(lái)對(duì)樹進(jìn)行剪枝宗挥。由于不需要用戶指定參數(shù)乌庶,后剪枝是一個(gè)更理想化的剪枝方法。

4.2后剪枝

后剪枝的剪枝過(guò)程是刪除一些子樹契耿,然后用其葉子節(jié)點(diǎn)代替瞒大,在剪枝過(guò)程中, 將一些子樹刪除而用葉節(jié)點(diǎn)代替,這個(gè)葉節(jié)點(diǎn)所標(biāo)識(shí)的類別用這棵子樹中大多數(shù)訓(xùn)練樣本所屬的類別來(lái)標(biāo)識(shí)。

使用后剪枝方法需要將數(shù)據(jù)集分成測(cè)試集和訓(xùn)練集搪桂。首先指定參數(shù)透敌,使得構(gòu)建出的樹足夠大、足夠復(fù)雜踢械,便于剪枝酗电。剪枝的過(guò)程是對(duì)擁有同樣父節(jié)點(diǎn)的一組節(jié)點(diǎn)進(jìn)行檢查,判斷如果將其合并内列,熵的增加量是否小于某一閾值撵术。如果確實(shí)小,則這一組節(jié)點(diǎn)可以合并一個(gè)節(jié)點(diǎn)话瞧,其中包含了所有可能的結(jié)果嫩与。后剪枝是目前最普遍的做法。

可以看到交排,大量的節(jié)點(diǎn)已經(jīng)被剪枝掉了划滋,但沒有像預(yù)期的那樣剪枝成兩部分,這說(shuō)明后剪枝可能不如預(yù)剪枝有效埃篓。一般地古毛,為了尋求最佳模型可以同時(shí)使用兩種剪枝技術(shù)。

剪枝作為決策樹后期處理的重要步驟都许,是必不可少的。沒有剪枝嫂冻,就是一個(gè)完全生長(zhǎng)的決策樹胶征,是過(guò)擬合的,需要去掉一些不必要的節(jié)點(diǎn)以使得決策樹模型更具有泛化能力桨仿。

五睛低、模型樹

用樹來(lái)對(duì)數(shù)據(jù)建模,除了把葉節(jié)點(diǎn)簡(jiǎn)單地設(shè)定為常數(shù)值之外,還有一種方法是把葉節(jié)點(diǎn)設(shè)定為分段線性函數(shù)钱雷,這里所謂的分段線性(piecewise linear)是指模型由多個(gè)線性片段組成骂铁。

考慮上圖中的數(shù)據(jù),如果使用兩條直線擬合是否比使用一組常數(shù)來(lái)建模好呢 罩抗?答案顯而易見拉庵。可以設(shè)計(jì)兩條分別從0.0~0.3套蒂、從0.3~1.0的直線钞支,于是就可以得到兩個(gè)線性模型。因?yàn)閿?shù)據(jù)集里的一部分?jǐn)?shù)據(jù)(0.0~0.3)以某個(gè)線性模型建模操刀,而另一部分?jǐn)?shù)據(jù)(0.3~1.0)則以另一個(gè)線性模型建模烁挟,因此我們說(shuō)采用了所謂的分段線性模型。

可以看到骨坑,該以0.285477為界創(chuàng)建了兩個(gè)模型撼嗓,而圖9-4的數(shù)據(jù)實(shí)際在0.3處分段。create_tree() 生成的這兩個(gè)線性模型分別是y=3.468+1.1852 和y=0.0016985+11.96477x欢唾,與用于生成該數(shù)據(jù)的真實(shí)模型非常接近且警。

模型樹、回歸樹以及線性回歸里的其他模型匈辱,哪一種模型更好呢振湾?一個(gè)比較客觀的方法是計(jì)算相關(guān)系數(shù),也稱為R2值亡脸。該相關(guān)系數(shù)可以通過(guò)調(diào)用NumPy庫(kù)中的命令corrcoef(y_hat, y,rowvar=0)來(lái)求解押搪,其中y_hat是預(yù)測(cè)值,y是目標(biāo)變量的實(shí)際值浅碾。

六大州、示例:樹回歸與標(biāo)準(zhǔn)回歸的比較

上面介紹了模型樹、回歸樹和一般的回歸方法垂谢,下面測(cè)試一下哪個(gè)模型最好厦画。這些模型將在某個(gè)數(shù)據(jù)上進(jìn)行測(cè)試,該數(shù)據(jù)涉及人的智力水平和自行車的速度的關(guān)系滥朱。這里的數(shù)據(jù)是非線性的根暑,不能簡(jiǎn)單地使用全局線性模型建模。

R2值越接近1.0越好徙邻,所以從上面的結(jié)果可以看出排嫌,這里模型樹的結(jié)果比回歸樹好,而標(biāo)準(zhǔn)的線性回歸在R2值上的表現(xiàn)上不如上面兩種樹回歸方法缰犁。所以淳地,樹回歸方法在預(yù)測(cè)復(fù)雜數(shù)據(jù)時(shí)會(huì)比簡(jiǎn)單的線性模型更有效怖糊。

七、使用tikinfer創(chuàng)建GUI

python中有很多GUI框架颇象,而tkinder是其中易于使用的一個(gè)伍伤。tkinder的GUI有一些小部件(Widget)組成。包括文本框(Test Box)遣钳,文本輸入框(Entry)按鈕(Button)扰魂,標(biāo)簽(Label)沒和復(fù)選按鈕(Check Button)等對(duì)象。此外耍贾,當(dāng)對(duì)象調(diào)用grid()方法時(shí)阅爽,就等于把該對(duì)象告訴布局管理器,grid()方法將小部件安排在一個(gè)二維的表格中荐开,用戶可以設(shè)定每個(gè)小部件所在的行列位置付翁。

接下來(lái),就結(jié)合matplotlib和tkinder來(lái)將圖像直接放在GUI上晃听,即通過(guò)修改Matplotlib后端百侧,達(dá)到在tkinder的GUI上繪圖的目的。先用畫布來(lái)替換繪制占位符能扒,刪除對(duì)應(yīng)標(biāo)簽并添加以下代碼:

回歸樹效果:

模型樹效果:

八佣渴、應(yīng)用scikit-learn構(gòu)建樹回歸

可以看出,scikit-learn構(gòu)建的回歸樹的R^2更接近1初斑,效果比之前的實(shí)現(xiàn)的代碼更好辛润。

九、小結(jié)见秤、

數(shù)據(jù)集中經(jīng)常包含一些復(fù)雜的相互關(guān)系砂竖,使得輸入數(shù)據(jù)和目標(biāo)變量之間呈現(xiàn)非線性關(guān)系。對(duì)這些復(fù)雜的關(guān)系建模鹃答,一種可行的方式是使用樹來(lái)對(duì)預(yù)測(cè)值分段乎澄,包括分段常數(shù)或分段直線。一般采用樹結(jié)構(gòu)來(lái)對(duì)這種數(shù)據(jù)建模测摔。相應(yīng)地置济,若葉節(jié)點(diǎn)使用的模型是分段常。數(shù)則稱為回歸樹锋八,若葉節(jié)點(diǎn)使用的模型是線性回歸方程則稱為模型樹浙于。

CART算法可以用于構(gòu)建二元樹并處理離散型或連續(xù)型數(shù)據(jù)的切分。若使用不同的誤差準(zhǔn)則挟纱,就可以通過(guò)CART算法構(gòu)建模型樹和回歸樹路媚。該算法構(gòu)建出的樹會(huì)傾向于對(duì)數(shù)據(jù)過(guò)擬合。一棵過(guò)擬合的樹常常十分復(fù)雜樊销,剪枝技術(shù)的出現(xiàn)就是為了解決這個(gè)問題整慎。兩種剪枝方法分別是預(yù)剪枝(在樹的構(gòu)建過(guò)程中就進(jìn)行剪枝)和后剪枝(當(dāng)樹構(gòu)建完畢再進(jìn)行剪枝),預(yù)剪枝更有效但需要用戶定義一些參數(shù)围苫。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末裤园,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子剂府,更是在濱河造成了極大的恐慌拧揽,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腺占,死亡現(xiàn)場(chǎng)離奇詭異淤袜,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)衰伯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門铡羡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人意鲸,你說(shuō)我怎么就攤上這事烦周。” “怎么了怎顾?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵读慎,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我槐雾,道長(zhǎng)夭委,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任募强,我火速辦了婚禮株灸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘钻注。我一直安慰自己蚂且,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布幅恋。 她就那樣靜靜地躺著杏死,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捆交。 梳的紋絲不亂的頭發(fā)上淑翼,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音品追,去河邊找鬼玄括。 笑死,一個(gè)胖子當(dāng)著我的面吹牛肉瓦,可吹牛的內(nèi)容都是我干的遭京。 我是一名探鬼主播胃惜,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼哪雕!你這毒婦竟也來(lái)了船殉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤斯嚎,失蹤者是張志新(化名)和其女友劉穎利虫,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體堡僻,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡糠惫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了钉疫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片硼讽。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖陌选,靈堂內(nèi)的尸體忽然破棺而出理郑,到底是詐尸還是另有隱情,我是刑警寧澤咨油,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布您炉,位于F島的核電站,受9級(jí)特大地震影響役电,放射性物質(zhì)發(fā)生泄漏赚爵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一法瑟、第九天 我趴在偏房一處隱蔽的房頂上張望冀膝。 院中可真熱鬧,春花似錦霎挟、人聲如沸窝剖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)赐纱。三九已至,卻和暖如春熬北,著一層夾襖步出監(jiān)牢的瞬間疙描,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工讶隐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留起胰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓巫延,卻偏偏與公主長(zhǎng)得像效五,于是被迫代替她去往敵國(guó)和親地消。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容