一、reactsuper的作用
在进行神经网络的设计时,我们通常会添加一些非线性函数对隐藏层进行激活,这些非线性函数被称为激活函数,这些函数的作用在于增加模型的复杂度,以更好地拟合训练数据。relu激活函数是非常常用的一种,因为其相比于其他函数来说计算速度比较快,同时也相对不容易出现梯度消失的情况。
relu 函数可以定义为 max(0,x),它对非负值返回输入值(直接传输到输出端),而对负值返回0。在整个网络中,relu函数负责非线性转换从而增强网络的表达能力,甚至可以将低维的输入空间映射到高维的特征空间。
def relu(x): return np.maximum(0, x)
二、relu层的作用
在构建卷积神经网络时,经常使用的一个层是 relu 层,这个层的作用就是应用 relu 函数到每一个输入神经元,这样输出就不再是线性的而是非线性的,并能够在跟小的计算负载下进行拟合。由于 relu 可以自动执行去除线性轴大多数部分的工作,因此可以被看作是一个类似降维的操作。
在网络的实现上,relu层往往添加在卷积层或全连接层后面,起到增强层数的效果,并能提高模型表现。其主要的作用是在计算神经元的加权和时,能够将负数直接变为0,这样就能强制网络学习到较为鲁棒的特征。当 relu 激活函数提取的特征能够很好地识别训练数据的特征时,就可以用来对未知数据进行预测。
class ReLU(Layer): def forward(self, x): self.x = x return np.maximum(0, x) def backward(self, grad): grad[self.x < 0] = 0 return grad
三、relu函数使用方法
在使用 relu 函数时,我们可以直接调用 np.maximum(0,x) 函数,其中 x 是输入的一个向量、矩阵或张量等。同时,也可以使用类似 Keras 这样的深度学习框架内置的 relu 层,来简化我们对 relu 函数的使用。例如在 Keras 中,我们可以直接定义一个激活函数层使用 relu 激活函数。
from keras.layers import Activation from keras.layers import Dense model = Sequential([ Dense(units=64, input_shape=(784,)), Activation('relu'), Dense(units=10), Activation('softmax') ])
四、relu函数优缺点
我们知道,使用激活函数的主要原因是为了增强神经网络的非线性能力,以更好地拟合训练数据。而 relu 作为其中的一种激活函数,它的主要优点有两个:
一方面是计算速度相对较快,这是由于 relu 函数计算简单,仅需要一个比较运算和一个取最大值的操作即可;
另一方面是可以有效避免梯度消失的问题,当神经网络的深度增加时,使用 sigmoid 或 tanh 等函数作为激活函数时,往往会出现梯度消失的情况,从而导致无法进行有效的反向传播。而 relu 函数的导数在大于0时为1,在小于等于0时为0,这样就保证了当值为0时,导数仍然为0,但并不会像 sigmoid、tanh 函数那样导致梯度消失。
但是,relu 函数也存在缺点,主要原因在于其输入小于0时对应的导数为0。这种情况称之为“神经元死亡”,如果这种情况发生在大量的神经元中,就可能导致整个网络的学习过程被破坏,无法进行训练。一种改进的方法是使用 LeakyReLU 或 PRelu 等函数替代 relu 函数,这些函数在输入小于0时的导数不再为0,从而避免了“神经元死亡”的情况。
五、对relu改进后的激活函数
经过不断改进,出现了一些可以替代 relu 函数的激活函数,比如参数 ReLU (PReLU) 和 Exponential Linear Units (ELU) 等。PReLU 是基于参数的 ReLU 函数,不同于传统的“输入小于0时输出0”的数值,PReLU 使用学习到的参数来将多数负输入映射到非零输出,以散布有用的梯度。比如这类函数可以解决 relu 导致的神经元死亡问题。
class PreLU(Layer): def __init__(self, dim): self.dim = dim self.alpha = np.random.normal(size=(dim,)) self.alpha[self.alpha<0] = 0 def forward(self, x): self.mask = x>=0 out = np.copy(x) out[out<0] *= self.alpha[np.newaxis,:] return out def backward(self, grad): grad[self.mask] = 1 grad[~self.mask] = self.alpha[~self.mask] return grad
六、卷积神经网络中relu层的作用
在卷积神经网络中,常常使用 relu 函数作为激活函数,同时将其添加在卷积层和池化层之后。这种方式的主要作用在于,在输入层和输出层之间添加非线性映射,以提高模型的表达能力,在卷积层后应用 relu 函数,可以将负数直接变为0,这样就能强制网络学习到较为鲁棒的特征。
在使用 relu 函数的同时,我们还需要对其超参数进行调整,以达到较好的效果。超参数的主要有两个:滑动窗口大小和步幅。其中滑动窗口大小是指单次取出的可能与卷积层大小相同或更大的图像块;而步幅则是指窗口每次滑动的距离。通过调整这两个超参数,我们可以有效地提高卷积神经网络的效果。
class Conv: def __init__(self, in_channel, out_channel, kernel_size, stride=1, bias=True): self.w = np.random.randn(out_channel, in_channel, kernel_size, kernel_size) self.b = np.zeros((out_channel, 1)) if bias else None self.stride = stride self.kernel_size = kernel_size def forward(self, x): n, c, h, w = x.shape hh = int((h - self.kernel_size) / self.stride) + 1 ww = int((w - self.kernel_size) / self.stride) + 1 out = np.zeros((n, self.w.shape[0], hh, ww)) self.x_cache = x for example in range(n): for ch in range(self.w.shape[0]): for i in range(hh): for j in range(ww): out[example, ch, i, j] = np.sum(x[example, :, i*self.stride:i*self.stride+self.kernel_size, j*self.stride:j*self.stride+self.kernel_size] * self.w[ch, :, :, :]) if self.b is not None: out += self.b.reshape(1, self.b.shape[0], 1, 1) return out def backward(self, grad_out): n, c, h_out, w_out = grad_out.shape _, _, h_in, w_in = self.x_cache.shape grad_w = np.zeros_like(self.w) grad_x = np.zeros_like(self.x_cache) for example in range(n): for ch in range(c): for i in range(h_out): for j in range(w_out): grad_x[example, ch, i*self.stride:i*self.stride+self.kernel_size, j*self.stride:j*self.stride+self.kernel_size] += grad_out[example, :, i, j] * self.w[:, ch, :, :] grad_w[:, ch, :, :] += grad_out[example, :, i, j][:, np.newaxis, np.newaxis] * self.x_cache[example, :, i*self.stride:i*self.stride+self.kernel_size, j*self.stride:j*self.stride+self.kernel_size] self.w -= self.lr * grad_w / n if self.b is not None: self.b -= self.lr * np.mean(grad_out, axis=(0, 2, 3), keepdims=True) return grad_x