zoukankan      html  css  js  c++  java
  • 《机器学习》周志华西瓜书习题参考答案:第5章

    欢迎关注WX公众号:【程序员管小亮】

    【机器学习】《机器学习》周志华西瓜书 笔记/习题答案 总目录

    ——————————————————————————————————————————————————————

    习题

    在这里插入图片描述

    使用线性函数作为激活函数时,无论是在隐藏层还是在输出层(无论传递几层),其单元值(在使用激活函数之前)都还是输入 xx 的线性组合,这个时候的神经网络其实等价于逻辑回归(即原书中的对率回归,输出层仍然使用Sigmoid函数)的,若输出层也使用线性函数作为激活函数,那么就等价于线性回归 。

    在这里插入图片描述
    在这里插入图片描述
    使用Sigmoid激活函数,每个神经元几乎和对率回归相同,只不过对率回归在 sigmoid(x)>0.5sigmoid(x) > 0.5 时输出为1,而神经元直接输出 sigmoid(x)sigmoid(x)

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

    用一张网上找到的图来说明吧。
    在这里插入图片描述
    简单说就是学习率太高会导致误差函数来回震荡,无法收敛;而学习率太低则会收敛太慢,影响训练效率,在原书p104也提到过。

    学习率 η 控制着梯度下降法的搜索步长(相关内容可参考书p408-附录B.4的梯度下降法的内容):

    从公式去看的话,如下:
    在这里插入图片描述
    对于固定的 η,参考书p109页: η 过大,收敛过程易振荡, η 过小,收敛速度过慢。

    常把学习率 η 设置为随迭代次数变化的量,使其随着训练的要求变化而变化(一般是减小)。如刚开始 η 大以快速到达到目标值附近, 后期 η 小以保证收敛稳定。

    详细的学习率衰减见博客——【超分辨率】10分钟入门学习率衰减

    在这里插入图片描述

    标准 BP 算法和累积 BP 算法在原书(P105)中也提到过,就是对应标准梯度下降和随机梯度下降,差别就是后者每次迭代用全部数据计算梯度,前者用一个数据计算梯度。

    代码在:

    import numpy as np
    import copy
    import pandas as pd
    import bpnnUtil
    from sklearn import datasets
    
    class BpNN(object):
        def __init__(
                self,
                layer_dims_,
                learning_rate=0.1,
                seed=16,
                initializer='he',
                optimizer='gd'):
    
            self.layer_dims_ = layer_dims_
            self.learning_rate = learning_rate
            self.seed = seed
            self.initializer = initializer
            self.optimizer = optimizer
    
        def fit(self, X_, y_, num_epochs=100):
            m, n = X_.shape
            layer_dims_ = copy.deepcopy(self.layer_dims_)
            layer_dims_.insert(0, n)
    
            if y_.ndim == 1:
                y_ = y_.reshape(-1, 1)
    
            assert self.initializer in ('he', 'xavier')
    
            if self.initializer == 'he':
                self.parameters_ = bpnnUtil.xavier_initializer(
                    layer_dims_, self.seed)
            elif self.initializer == 'xavier':
                self.parameters_ = bpnnUtil.xavier_initializer(
                    layer_dims_, self.seed)
    
            assert self.optimizer in ('gd', 'sgd', 'adam', 'momentum')
            if self.optimizer == 'gd':
                parameters_, costs = self.optimizer_gd(
                    X_, y_, self.parameters_, num_epochs, self.learning_rate)
            elif self.optimizer == 'sgd':
                parameters_, costs = self.optimizer_sgd(
                    X_, y_, self.parameters_, num_epochs, self.learning_rate, self.seed)
            elif self.optimizer == 'momentum':
                parameters_, costs = self.optimizer_sgd_monment(
                    X_, y_, self.parameters_, beta=0.9, num_epochs=num_epochs, learning_rate=self.learning_rate, seed=self.seed)
            elif self.optimizer == 'adam':
                parameters_, costs = self.optimizer_sgd_adam(X_, y_, self.parameters_, beta1=0.9, beta2=0.999, epsilon=1e-7,
                                                             num_epochs=num_epochs, learning_rate=self.learning_rate,
                                                             seed=self.seed)
    
            self.parameters_ = parameters_
            self.costs = costs
    
            return self
    
        def predict(self, X_):
            if not hasattr(self, "parameters_"):
                raise Exception('you have to fit first before predict.')
    
            a_last, _ = self.forward_L_layer(X_, self.parameters_)
            if a_last.shape[1] == 1:
                predict_ = np.zeros(a_last.shape)
                predict_[a_last >= 0.5] = 1
            else:
                predict_ = np.argmax(a_last, axis=1)
            return predict_
    
        def compute_cost(self, y_hat_, y_):
            if y_.ndim == 1:
                y_ = y_.reshape(-1, 1)
            if y_.shape[1] == 1:
                cost = bpnnUtil.cross_entry_sigmoid(y_hat_, y_)
            else:
                cost = bpnnUtil.cross_entry_softmax(y_hat_, y_)
            return cost
    
        def backward_one_layer(self, da_, cache_, activation_):
            # 在activation_ 为'softmax'时, da_实际上输入是y_, 并不是
            (a_pre_, w_, b_, z_) = cache_
            m = da_.shape[0]
    
            assert activation_ in ('sigmoid', 'relu', 'softmax')
    
            if activation_ == 'sigmoid':
                dz_ = bpnnUtil.sigmoid_backward(da_, z_)
            elif activation_ == 'relu':
                dz_ = bpnnUtil.relu_backward(da_, z_)
            else:
                dz_ = bpnnUtil.softmax_backward(da_, z_)
    
            dw = np.dot(dz_.T, a_pre_) / m
            db = np.sum(dz_, axis=0, keepdims=True) / m
            da_pre = np.dot(dz_, w_)
    
            assert dw.shape == w_.shape
            assert db.shape == b_.shape
            assert da_pre.shape == a_pre_.shape
    
            return da_pre, dw, db
    
        def backward_L_layer(self, a_last, y_, caches):
    
            grads = {}
            L = len(caches)
    
            if y_.ndim == 1:
                y_ = y_.reshape(-1, 1)
    
            if y_.shape[1] == 1:  # 目标值只有一列表示为二分类
                da_last = -(y_ / a_last - (1 - y_) / (1 - a_last))
                da_pre_L_1, dwL_, dbL_ = self.backward_one_layer(
                    da_last, caches[L - 1], 'sigmoid')
    
            else:  # 经过one hot,表示为多分类
    
                # 在计算softmax的梯度时,可以直接用 dz = a - y可计算出交叉熵损失函数对z的偏导, 所以这里第一个参数输入直接为y_
                da_pre_L_1, dwL_, dbL_ = self.backward_one_layer(
                    y_, caches[L - 1], 'softmax')
    
            grads['da' + str(L)] = da_pre_L_1
            grads['dW' + str(L)] = dwL_
            grads['db' + str(L)] = dbL_
    
            for i in range(L - 1, 0, -1):
                da_pre_, dw, db = self.backward_one_layer(
                    grads['da' + str(i + 1)], caches[i - 1], 'relu')
    
                grads['da' + str(i)] = da_pre_
                grads['dW' + str(i)] = dw
                grads['db' + str(i)] = db
    
            return grads
    
        def forward_one_layer(self, a_pre_, w_, b_, activation_):
            z_ = np.dot(a_pre_, w_.T) + b_
            assert activation_ in ('sigmoid', 'relu', 'softmax')
    
            if activation_ == 'sigmoid':
                a_ = bpnnUtil.sigmoid(z_)
            elif activation_ == 'relu':
                a_ = bpnnUtil.relu(z_)
            else:
                a_ = bpnnUtil.softmax(z_)
    
            cache_ = (a_pre_, w_, b_, z_)  # 将向前传播过程中产生的数据保存下来,在向后传播过程计算梯度的时候要用上的。
            return a_, cache_
    
        def forward_L_layer(self, X_, parameters_):
            L_ = int(len(parameters_) / 2)
            caches = []
            a_ = X_
            for i in range(1, L_):
                w_ = parameters_['W' + str(i)]
                b_ = parameters_['b' + str(i)]
                a_pre_ = a_
                a_, cache_ = self.forward_one_layer(a_pre_, w_, b_, 'relu')
                caches.append(cache_)
    
            w_last = parameters_['W' + str(L_)]
            b_last = parameters_['b' + str(L_)]
    
            if w_last.shape[0] == 1:
                a_last, cache_ = self.forward_one_layer(
                    a_, w_last, b_last, 'sigmoid')
            else:
                a_last, cache_ = self.forward_one_layer(
                    a_, w_last, b_last, 'softmax')
    
            caches.append(cache_)
            return a_last, caches
    
        def optimizer_gd(self, X_, y_, parameters_, num_epochs, learning_rate):
            costs = []
            for i in range(num_epochs):
                a_last, caches = self.forward_L_layer(X_, parameters_)
                grads = self.backward_L_layer(a_last, y_, caches)
    
                parameters_ = bpnnUtil.update_parameters_with_gd(
                    parameters_, grads, learning_rate)
                cost = self.compute_cost(a_last, y_)
    
                costs.append(cost)
    
            return parameters_, costs
    
        def optimizer_sgd(
                self,
                X_,
                y_,
                parameters_,
                num_epochs,
                learning_rate,
                seed):
            '''
            sgd中,更新参数步骤和gd是一致的,只不过在计算梯度的时候是用一个样本而已。
            '''
            np.random.seed(seed)
            costs = []
            m_ = X_.shape[0]
            for _ in range(num_epochs):
                random_index = np.random.randint(0, m_)
    
                a_last, caches = self.forward_L_layer(
                    X_[[random_index], :], parameters_)
                grads = self.backward_L_layer(
                    a_last, y_[[random_index], :], caches)
    
                parameters_ = bpnnUtil.update_parameters_with_sgd(
                    parameters_, grads, learning_rate)
    
                a_last_cost, _ = self.forward_L_layer(X_, parameters_)
    
                cost = self.compute_cost(a_last_cost, y_)
    
                costs.append(cost)
    
            return parameters_, costs
    
        def optimizer_sgd_monment(
                self,
                X_,
                y_,
                parameters_,
                beta,
                num_epochs,
                learning_rate,
                seed):
            '''
            :param X_:
            :param y_:
            :param parameters_: 初始化的参数
            :param v_:          梯度的指数加权移动平均数
            :param beta:        冲量大小,
            :param num_epochs:
            :param learning_rate:
            :param seed:
            :return:
            '''
            np.random.seed(seed)
            costs = []
            m_ = X_.shape[0]
            velcoity = bpnnUtil.initialize_velcoity(parameters_)
            for _ in range(num_epochs):
                random_index = np.random.randint(0, m_)
    
                a_last, caches = self.forward_L_layer(
                    X_[[random_index], :], parameters_)
                grads = self.backward_L_layer(
                    a_last, y_[[random_index], :], caches)
    
                parameters_, v_ = bpnnUtil.update_parameters_with_sgd_momentum(
                    parameters_, grads, velcoity, beta, learning_rate)
                a_last_cost, _ = self.forward_L_layer(X_, parameters_)
                cost = self.compute_cost(a_last_cost, y_)
                costs.append(cost)
    
            return parameters_, costs
    
        def optimizer_sgd_adam(
                self,
                X_,
                y_,
                parameters_,
                beta1,
                beta2,
                epsilon,
                num_epochs,
                learning_rate,
                seed):
            '''
            :param X_:
            :param y_:
            :param parameters_: 初始化的参数
            :param v_:          梯度的指数加权移动平均数
            :param beta:        冲量大小,
            :param num_epochs:
            :param learning_rate:
            :param seed:
            :return:
            '''
            np.random.seed(seed)
            costs = []
            m_ = X_.shape[0]
            velcoity, square_grad = bpnnUtil.initialize_adam(parameters_)
            for epoch in range(num_epochs):
                random_index = np.random.randint(0, m_)
    
                a_last, caches = self.forward_L_layer(
                    X_[[random_index], :], parameters_)
                grads = self.backward_L_layer(
                    a_last, y_[[random_index], :], caches)
    
                parameters_, velcoity, square_grad = bpnnUtil.update_parameters_with_sgd_adam(
                    parameters_, grads, velcoity, square_grad, epoch + 1, learning_rate, beta1, beta2, epsilon)
                a_last_cost, _ = self.forward_L_layer(X_, parameters_)
                cost = self.compute_cost(a_last_cost, y_)
                costs.append(cost)
    
            return parameters_, costs
    
    
    if __name__ == '__main__':
        # 5.5
        # data_path = r'C:UsershanmiDocumentsxiguabookwatermelon3_0_Ch.csv'
        # data3 = pd.read_csv(data_path, index_col=0)
        # data = pd.get_dummies(data3, columns=['色泽', '根蒂', '敲声', '纹理', '脐部', '触感'])
        # data['好瓜'].replace(['是', '否'], [1, 0], inplace=True)
        # X_test = data.drop('好瓜', axis=1)
        # y_test = data['好瓜']
        #
        # bp = BpNN([3, 1], learning_rate=0.1, optimizer='gd')
        # bp.fit(X_test.values, y_test.values, num_epochs=200)
    
        # bp1 = BpNN([3, 1], learning_rate=0.1, optimizer='sgd')
        # bp1.fit(X_test.values, y_test.values, num_epochs=200)
        #
        # bpnnUtil.plot_costs([bp.costs, bp1.costs], ['gd_cost', 'sgd_cost'])
    
        # 5.6
        iris = datasets.load_iris()
        X = pd.DataFrame(iris['data'], columns=iris['feature_names'])
        X = (X - np.mean(X, axis=0)) / np.var(X, axis=0)
    
        y = pd.Series(iris['target_names'][iris['target']])
        y = pd.get_dummies(y)
    
        bp = BpNN([3, 3], learning_rate=0.003, optimizer='adam')
        bp.fit(X.values, y.values, num_epochs=2000)
    
        bp1 = BpNN([3, 3], learning_rate=0.003, optimizer='sgd')
        bp1.fit(X.values, y.values, num_epochs=2000)
    
        bpnnUtil.plot_costs([bp.costs, bp1.costs], ['adam_cost', 'sgd_cost'])
    

    具体两种情况的结果如下图:可以看出来gd的成本函数收敛过程更加稳定,而sgd每次迭代并不一定向最优方向前进,但总体方向是收敛的,且同样是迭代200次,最后结果相差不大,但由于sgd每次迭代只使用一个样本,计算量大幅度下降,显然sgd的速度会更快。

    ps.关于随机梯度下降的实现,好像有两种方式,一种是每次将样本打乱,然后遍历所有样本,而后再次打乱、遍历;另一种是每次迭代随机抽取样本。这里采取的是后一种方式,貌似两种方式都可以。

    此外,BP神经网络代码在以前学吴恩达老师深度学习课程的时候就写过,这次整理了一下正好放上来,所以很多代码和课程代码类似,添加了应用多分类的情况的代码。下面的5.6题也一并在这里实现。
    在这里插入图片描述

    在这里插入图片描述

    动态调整学习率有很多现成的算法,RMSProp、Adam、NAdam等等。也可以手动实现一个简单指数式衰减在这里插入图片描述rr 是一个超参。这里代码实现了Adam,代码和5.5一同实现,在上面。

    这里只尝试了sklearn 中自带的iris数据集试了一下。同样学习率下,两者训练时损失函数如下:
    在这里插入图片描述
    可以明显看出adam的速度更快的。

    在这里插入图片描述

    这里可以使用X = array([[1, 0], [0, 1], [0, 0], [1, 1]]),y = array([[1], [1], [0], [0]])作为数据,训练一个RBF神经网络。

    这里使用均方根误差作为损失函数;输出层和书上一致,为隐藏层的线性组合,且另外加上了一个偏置项(这是书上没有)。

    代码在:

    '''
    这里使用均方根误差作为损失函数的RBF神经网络。
    '''
    import numpy as np
    import matplotlib.pyplot as plt
    
    def RBF_forward(X_, parameters_):
        m, n = X_.shape
        beta = parameters_['beta']
        W = parameters_['W']
        c = parameters_['c']
        b = parameters_['b']
    
        t_ = c.shape[0]
        p = np.zeros((m, t_))  # 中间隐藏层的激活值     对应书上5.19式
        x_c = np.zeros((m, t_))  # 5.19式中 x - c_{i}
        for i in range(t_):
            x_c[:, i] = np.linalg.norm(X_ - c[[i], ], axis=1) ** 2
    
            p[:, i] = np.exp(-beta[0, i] * x_c[:, i])
    
        a = np.dot(p, W.T) + b
        return a, p, x_c
    
    def RBF_backward(a_, y_, x_c, p_, parameters_):
        m, n = a_.shape
        grad = {}
        beta = parameters_['beta']
        W = parameters_['W']
    
        da = (a_ - y_)      # 损失函数对输出层的偏导 ,这里的a其实对应着  输出层的y_hat
    
        dw = np.dot(da.T, p_) / m
        db = np.sum(da, axis=0, keepdims=True) / m
        dp = np.dot(da, W)   # dp即损失函数对隐藏层激活值的偏导
    
        dbeta = np.sum(dp * p_ * (-x_c), axis=0, keepdims=True) / m
    
        assert dbeta.shape == beta.shape
        assert dw.shape == W.shape
        grad['dw'] = dw
        grad['dbeta'] = dbeta
        grad['db'] = db
    
        return grad
    
    def compute_cost(y_hat_, y_):
        m = y_.shape[0]
        loss = np.sum((y_hat_ - y) ** 2) / (2 * m)
        return np.squeeze(loss)
    
    def RBF_model(X_, y_, learning_rate, num_epochs, t):
        '''
        :param X_:
        :param y_:
        :param learning_rate:  学习率
        :param num_epochs:     迭代次数
        :param t:   隐藏层节点数量
        :return:
        '''
        parameters = {}
        np.random.seed(16)
        # 定义中心点,本来这里的中心点应该由随机采用或者聚类等非监督学习来获得的,这里为了简单就直接定义好了
    
        parameters['beta'] = np.random.randn(1, t)  # 初始化径向基的方差
        parameters['W'] = np.zeros((1, t))  # 初始化
        parameters['c'] = np.random.rand(t, 2)
        parameters['b'] = np.zeros([1, 1])
        costs = []
    
        for i in range(num_epochs):
            a, p, x_c = RBF_forward(X_, parameters)
            cost = compute_cost(a, y_)
            costs.append(cost)
            grad = RBF_backward(a, y_, x_c, p, parameters)
    
            parameters['beta'] -= learning_rate * grad['dbeta']
            parameters['W'] -= learning_rate * grad['dw']
            parameters['b'] -= learning_rate * grad['db']
    
        return parameters, costs
    
    def predict(X_, parameters_):
        a, p, x_c = RBF_forward(X_, parameters_)
    
        return a
    
    X = np.array([[1, 0], [0, 1], [0, 0], [1, 1]])
    y = np.array([[1], [1], [0], [0]])
    #
    
    parameters, costs = RBF_model(X, y, 0.003, 10000, 8)
    
    plt.plot(costs)
    plt.show()
    
    print(predict(X, parameters))
    
    # 梯度检验
    # parameters = {}
    # parameters['beta'] = np.random.randn(1, 2)  # 初始化径向基的方差
    # parameters['W'] = np.random.randn(1, 2)  # 初始化
    # parameters['c'] = np.array([[0.1, 0.1], [0.8, 0.8]])
    # parameters['b'] = np.zeros([1, 1])
    # a, p, x_c = RBF_forward(X, parameters)
    #
    # cost = compute_cost(a, y)
    # grad = RBF_backward(a, y, x_c, p, parameters)
    #
    #
    # parameters['b'][0, 0] += 1e-6
    #
    # a1, p1, x_c1 = RBF_forward(X, parameters)
    # cost1 = compute_cost(a1, y)
    # print(grad['db'])
    #
    # print((cost1 - cost) / 1e-6)
    

    在这里插入图片描述

    最后输出是:

    [[ 9.99944968e-01]
     [ 9.99881045e-01]
     [ 8.72381056e-05]
     [ 1.26478454e-04]]
    

    感觉分类的时候在输出层使用sigmoid作为激活函数也可以。

    在这里插入图片描述

    周志华《机器学习》课后习题解答系列(六):Ch5.8 - SOM网络实验

    在这里插入图片描述

    Elman 网络在西瓜书原书上说的是“递归神经网络”,但是在网上找资料说的

    • “递归神经网络”是空间维度的展开,是一个树结构。
    • “循环神经网络”是时间维度的展开,代表信息在时间维度从前往后的的传递和积累。

    从书上p111描述来看感觉更像“循环神经网络”。最近时间不多(lan…),就不去啃原论文了。关于“循环神经网络”或者递归神经网络的BP可以参考下面链接。

    1、零基础入门深度学习(5) - 循环神经网络 ,网上大神写了。

    另外关于循环神经网络也可以看看吴恩达老师的深度学习课程“序列模型”那部分。

    在这里插入图片描述

    正好前段时间做过Kaggle上手写数字识别的题目。这里正好放上来,CNN是用Tensorflow实现的,之前看吴恩达老师深度学习课程的时候也拿numpy实现过(课程作业),等以后有时间再整理放上来吧。

    https://github.com/han1057578619/kaggle_competition/tree/master/Digit_Recogniz

    参考文章

  • 相关阅读:
    alpha 冲刺 —— 十分之一
    福大软工 · 第七次作业
    福大软工 · 第八次作业(课堂实战)- 项目UML设计(团队)
    2018软工实践——团队答辩
    福大软工1816 · 第五次作业
    福大软工1816 · 第四次作业
    软工实践第四次作业--结队的第一次合作
    软工实践第二次作业--思索
    华中农业大学第五届程序设计大赛网络同步赛解题报告2(转)
    华中农业大学第五届程序设计大赛网络同步赛解题报告(转)
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13302738.html
Copyright © 2011-2022 走看看