《深度学习入门》第3章 - 神经网络

一、从感知机到神经网络


1、神经网络结构:用图来表示的话,把最左一列称为输入层,最右一列称为输出层,中间一列称为中间层(也称隐藏层),隐藏的意思是,隐藏层的神经元肉眼不可见。
2、在神经网络中,激活函数的作用是能够给神经网络加入一些非线性因素,使得神经网络可以更好地解决较为复杂的问题。如在数据线性不可分的情况下,尝试引入非线性的因素,对样本进行分类。
图片说明
图片说明

二、激活函数


1、什么是激活函数:在神经元中,输入的 inputs 通过加权,求和后,还被作用了一个函数,这个函数就是激活函数 Activation Function
图片说明
2、sigmod函数:
Sigmoid函数计算公式:
图片说明
曲线:
图片说明
sigmoid函数也叫 Logistic 函数,用于隐层神经元输出,取值范围为(0,1),它可以将一个实数映射到(0,1)的区间,可以用来做二分类。在特征相差比较复杂或是相差不是特别大时效果比较好。
sigmoid缺点:

激活函数计算量大,反向传播求误差梯度时,求导涉及除法
反向传播时,很容易就会出现梯度消失的情况,从而无法完成深层网络的训练
Sigmoids函数饱和且kill掉梯度。
Sigmoids函数收敛缓慢。

3、RELU函数
Rectified Linear Unit(ReLU) - 用于隐层神经元输出
公式:
图片说明
曲线:
曲线
RELU特点:

输入信号 <0 时,输出都是0,>0 的情况下,输出等于输入

ReLU 的优点:

Krizhevsky et al. 发现使用 ReLU 得到的 SGD 的收敛速度会比 sigmoid/tanh 快很多

ReLU 的缺点:

训练的时候很”脆弱”,很容易就”die”了
例如,一个非常大的梯度流过一个 ReLU 神经元,更新过参数之后,这个神经元再也不会对任何数据有激活现象了,那么这个神经元的梯度就永远都会是 0.
如果 learning rate 很大,那么很有可能网络中的 40% 的神经元都”dead”了。

4、Softmax函数
Softmax - 用于多分类神经网络输出
公式:
图片说明
Sigmod和Softmax比较:

sigmoid将一个real value映射到(0,1)的区间,用来做二分类。
而 softmax 把一个 k 维的real value向量(a1,a2,a3,a4….)映射成一个(b1,b2,b3,b4….)其中 bi 是一个 0~1 的常数,输出神经元之和为 1.0,所以相当于概率值,然后可以根据 bi 的概率大小来进行多分类的任务。
二分类问题时 sigmoid 和 softmax 是一样的,求的都是 cross entropy loss,而 softmax 可以用于多分类问题。

三、3层神经网络的实现



import numpy as np

def sigmoid(X, deriv = False):
    if(deriv == True):        
        return X*(1-X)
    return 1/(1+ np.exp(-X))    

#构造数据集
x = np.array([[0,0,1],
             [0,1,1],
             [1,0,1],
             [1,1,1],
             [0,0,1]])
print(x.shape)

y = np.array([[0],[1],[1],[0],[0]])
print(y.shape)


#指定随机的种子,每次运行产生相同数据
np.random.seed(1)
#L0有三个特征,L1有4个神经元,所以w0为3行4列,取值范围(-1,1)
w0 = 2*np.random.random((3,4))-1
w1 = 2*np.random.random((4,1))-1
print(w0)


#神经网络模型构造及训练
for j in range(1000001):
    #L0层
    l0 = x
    #前向传播,计算后l1为5行4列
    l1 = sigmoid(np.dot(l0,w0))
    #前向传播,计算后l2为5行1列
    l2 = sigmoid(np.dot(l1,w1))
    #计算预测值与标签的差异值
    l2_error = y - l2
    if(j %100000)==0:
        print('Error'+str(np.mean(np.abs(l2_error))))
        print(j)


    #方向传播,计算梯度值
    #如l2_error很大,则需要大力度更新;如果l2_error很小,则只需要更新一点点
    #所以导数乘以l2_error, *为连个5行一列的矩阵对应位置相乘   
    l2_delta = l2_error * sigmoid(l2, deriv=True)
    #l2_delta5行1列,w14行1列,
    l1_error = l2_delta.dot(w1.T)  
    l1_delta = l1_error * sigmoid(l1, deriv=True)

    #更新参数
    #l15行4列,l2_delta5行1列,w1为4行一列
    w1 += l1.T.dot(l2_delta)
    #l05行3列,l2_delta5行4列,w0为3行4列
    w0 +=  l0.T.dot(l1_delta)

四、输出层的设计


softMax函数设计:


def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y

# optimized
def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c) # 溢出对策
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y

softmax()的结果在0~1之间。所以把softmax()的输出称为概率。
softmax()是单调递增的:
也就是说如果输入数据本来就是最大的,经过softmax()计算之后还是最大。
如果输入数据本来排行老二,经过softmax()计算之后还是排行老二。
所以神经网络在'推理'的过程可以省略softmax()。
'推理'是指用学到的模型对未知的数据进行分类,也称为神经网络的前向传播(forward propagation)。

五、手写数字的识别


首先,将一个手写数字图像分成一个m×n个部分。例如将图像分成28×28个区域,则输出层包含了784(28×28)个输入神经元,其神经网络的架构如图。
图片说明
对于输入神经元,当黑色部分占这个区域超过50%时,则这个神经元的值为1,否则为0。
对于输出神经元,一共有10个。当第一个神经元被激活,它的输出为1时,即识别这个数字为0。
神经网络结构代码如下:


class Network(object):

    def __init__(self, sizes):
        # 获取神经网络的层数
        self.num_layers = len(sizes)
        # sizes即每层神经元的个数
        self.sizes = sizes
        # 赋随机值(服从高斯分布),对权重和偏向进行初始化
        #  bais从第2行开始
        self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
        #  zip从传入的可循环的两组量中取出对应数据组成一个tuple
        self.weights = [np.random.randn(y, x) for x, y in zip(sizes[:-1], sizes[1:])]
        # print self.weights
        # print self.biases

    # 根据当前输入利用sigmoid函数来计算输出
    def feedforward(self, a):
        for b, w in zip(self.biases, self.weights):
            a = sigmoid(np.dot(w, a) + b)
        return a

    # epochs训练多少轮, mini_batch_size抽取多少实例,eta学习率
    def SGD(self, training_data, epochs, mini_batch_size, eta, test_data=None):
        # 测试集的大小
        if test_data:
            n_test = len(test_data)
        n = len(training_data)
        # j代表第几轮
        for j in xrange(epochs):
            # 将training_data中的数据随机打乱
            random.shuffle(training_data)
            # mini_batchs每次抽取mini_batch_size大小的数据作为一小块
            mini_batches = [training_data[k:k + mini_batch_size]
                # 从0到n每次间隔mini_batch_size张图片
                for k in xrange(0, n, mini_batch_size)]
            # 对取出来的mini_batchs逐个进行更新,更新weights和biases
            for mini_batch in mini_batches:
                self.update_mini_batch(mini_batch, eta)
            # 每一轮训练后进行评估
            if test_data:
                print "Epoch {0}: {1} / {2}".format(j, self.evaluate(test_data), n_test)
            else:
                print "Epoch {0} complete".format(j)

    # eta:学习率  传入单个的mini_batch,根据其x.y值,对整个神经网络的wights和biases进行更新
    def update_mini_batch(self, mini_batch, eta):
        # 初始化
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        for x, y in mini_batch:
            # 计算对应的偏导数
            delta_nabla_b, delta_nabla_w = self.backprop(x, y)
            nabla_b = [nb + dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
            nabla_w = [nw + dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
        # 权重weights更新 W'k=Wk-(eta/m)&C/&Wk
        self.weights = [w - (eta / len(mini_batch)) * nw
                        for w, nw in zip(self.weights, nabla_w)]
        # 偏向biases更新  b'k=bk-(ets/m)&C/&bk
        self.biases = [b - (eta / len(mini_batch)) * nb
                       for b, nb in zip(self.biases, nabla_b)]

    # 计算对应的偏导数
    def backprop(self, x, y):
        # 返回一个元组(nabla_b,nabla_w)代表成本函数C_x的渐变。
        # nabla_b和nabla_w是numpy数组np.array的逐层列表,类似于self.biases和self.weights.
        # 分别生成与biases weights等大小的0矩阵
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        # 激活项
        activation = x
        # 列表存储所有的激活,逐层
        activations = [x]
        # 列表存储所有中间向量z,逐层
        zs = []
        for b, w in zip(self.biases, self.weights):
            # 计算中间变量  Z=W*X+b
            z = np.dot(w, activation) + b
            # 列表存储所有中间向量z
            zs.append(z)
            # 激活activation=sigmoid(W*X+b)
            activation = sigmoid(z)
            # 列表存储所有的激活
            activations.append(activation)
        # 反向更新
        # ### 输出层
        # 计算输出层error=Oj(1-Oj)(Tj-Oj);
        # cost_derivative(activations[-1], y)即C对a的梯度:(Tj-Oj)
        # sigmoid_prime(zs[-1])即:Oj(1-Oj)
        delta = self.cost_derivative(activations[-1], y) * sigmoid_prime(zs[-1])
        # 更新输出层的nabla_b,nabla_w
        nabla_b[-1] = delta
        nabla_w[-1] = np.dot(delta, activations[-2].transpose())
        # ### 隐藏层
        # l = 1表示神经元的最后一层,l = 2是第二层,依此类推.反向更新直到初始层
        for l in xrange(2, self.num_layers):
            z = zs[-l]
            sp = sigmoid_prime(z)
            # weights[-l + 1]即下一层的权重,
            delta = np.dot(self.weights[-l + 1].transpose(), delta) * sp
            # 输出C对w,b的偏导
            nabla_b[-l] = delta
            nabla_w[-l] = np.dot(delta, activations[-l - 1].transpose())
        return (nabla_b, nabla_w)

    def evaluate(self, test_data):
        # 返回神经网络输出正确结果的测试输入的数量。 注意,神经网络的输出被假定为最终层中具有最高激活的神经元的指数。
        test_results = [(np.argmax(self.feedforward(x)), y) for (x, y) in test_data]
        return sum(int(x == y) for (x, y) in test_results)

    def cost_derivative(self, output_activations, y):
        # 返回输出激活的偏导数 partial C_x,partial a的向量。
        return (output_activations - y)


# sigmoid函数
def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))

# sigmoid函数的导数
def sigmoid_prime(z):
    return sigmoid(z) * (1 - sigmoid(z))

# 向量化
def vectorized_result(j):
    e = np.zeros((10, 1))
    e[j] = 1.0
    return e

# 数据转换,将数据转换为长度为784的numpy.ndarray组成的list,将对应label向量化
def load_data_wrapper():
    tr_d, va_d, te_d = load_data()

    # # 数据转换
    # tr_d是由50000个长度为784的numpy.ndarray组成的tuple
    # 转换后的training_inputs是由50000个长度为784的numpy.ndarray组成的list
    training_inputs = [np.reshape(x, (784, 1)) for x in tr_d[0]]
    training_results = [vectorized_result(y) for y in tr_d[1]]
    # 训练集 training_data
    # zip()返回一个列表的元组,其中每个元组包含从每个参数序列的第i个元素。
    training_data = zip(training_inputs, training_results)
    validation_inputs = [np.reshape(x, (784, 1)) for x in va_d[0]]
    # 验证集 validation_data
    validation_data = zip(validation_inputs, va_d[1])
    test_inputs = [np.reshape(x, (784, 1)) for x in te_d[0]]
    # 测试集 test_data
    test_data = zip(test_inputs, te_d[1])
    return (training_data, validation_data, test_data)

if __name__ == '__main__':
    training_data, valivation_data, test_data =load_data_wrapper()

    # # 显示图像
    # ShowImage()

    net = Network([784, 30, 10])
    # 训练集training_data,训练10轮,每次取样10个作为mini_batch,学习率为3
    net.SGD(training_data, 10, 10, 3.0, test_data=test_data)

六、小结


1、神经网络中的激活函数使用平滑变化的sigmod函数或ReLU函数。
2、通过巧妙地使用Numpy数组,可以高效地实现神经网络。
3、机器学习的问题大体上可以分为回归问题和分类问题。
4、关于输出层的激活函数,回归问题中一般使用恒等函数,分类问题中一般使用softmax函数。
5、分类问题中,输出层的神经元的数量设置为要分类的类别数。
6、输入数据的集合称为批,通过一批为单位进行推理处理,能够实现高速的运算。

#深度学习#
全部评论

相关推荐

1 7 评论
分享
牛客网
牛客企业服务