参考书籍:(找不到资源可以后台私信我)
《深度学习入门:基于Python的理论与实现 (斋藤康毅)》
《Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow, 2nd Edition (Aurelien Geron [Géron, Aurélien])》
机器学习和深度学习的区别:
神经网络的构造
Perceptron(感知机)
感知机就是一种接收多种输入信号,输出一个信号的原件。输入信号被送往神经元时,会被分别乘以固定的权重,神经元会计算传送来的信号的总和,只有当这个总和超过某个界限值时,才会输出1,此时被称为“神经元被激活”,这个界限值称为阈值。(可以把阈值挪到等式的左边,那只需要比较总和减阈值与0的关系)
感知机的实现就是TLU(Threshold Logic Unit),如下图所示。其中X和W都是向量,Z其实计算的就是加权和。再经过step函数就得到了输出。
常用的step function有两种:
设置不同的权重和阈值,我们可以用感知机表示与门、与非门、或门(此处不再证明)。下图中直线就可以是一个或门,很好地把(0,0)和(1,0)、(0,1)、(1,1)分开了。
但是,单个感知机无法表示异或门,因为异或需要用曲线来分类。也就是说,单层感知机只能表示线性空间。
使用多层感知机即可表示异或:
如果像下图一样,每个神经元都接收前一层的每个输出,那就是全连接层(fully connected layer / dense layer)。
输出的计算公式为:
其中b是bias vector,每个神经元都有一个bias。Φ是激活函数,如果神经元是TLU,那么Φ就是step function。其他的激活函数还有sigmoid、ReLU、softmax等。用上sigmoid就是神经网络了。
Multilayer Perceptron
跟前面的单层perceptron的区别就是加了隐藏层。其中靠近输入层的也叫lower layers,靠近输出层的也叫upper layers。除了输出层都有bias神经元,每一层也都是全连接的。上图也是feedforward neural network(FNN)。
激活函数
神经网络的激活函数必须使用非线性函数。因为如果使用线性函数,那么加深神经网络的层数是没有意义(只是改了权重/参数)。
输出层所用的激活函数,要根据求解问题的性质决定。一般来说,回归问题可以使用恒等函数(直接输出,不作任何处理),二分类问题可以用sigmoid函数,多元分类问题可以使用softmax函数(只是训练阶段,在推理阶段,一般会省略掉sofmax)。
sigmoid函数:
ReLU(Rectified Linear Unit)函数:
softmax函数:
由于指数增长是很恐怖的,所以为了防止溢出,如下图所示修改这个函数,并且将 C’ 改成 -max(x)
python实现:
def step_func(X):
return np.array(X > 0, dtype=int)
def sigmoid_func(X):
return 1 / (1 + np.exp(-X))
def relu(X):
return np.maximum(0, X)
def softmax(X):
c = np.max(X)
exp_X = np.exp(X - c)
sum_exp_X = np.sum(exp_X)
y = exp_X / sum_exp_X
return y
输出层的神经元数量需要根据问题决定。对于分类问题,输出层的神经元数量一般设为类别的数量。比如使用mnist训练手写数字分类器,那可以给输出层设置10个神经元,每个神经元对应一个数字。
输入数据的集合称为批(batch)。因为计算机一般会为批处理做优化,所以以batch为单位进行推理会比较快。
神经网络的学习
损失函数(loss function)
损失函数是表示神经网络性能的“恶劣程度”的指标,即当前的神经网络对监督数据在多大程度上不拟合。在神经网络的学习/训练中,寻找最优参数(权重W和偏置B)时,要寻找使损失函数的值尽可能小的参数,此时需要计算参数的导数,然后以导数为指引,逐步更新参数的值。
可以用作损失函数的有:
均方误差(mean squared error):
def mean_squared_error(y, t):
return 0.5 * np.sum((y - t) ** 2)
交叉熵误差(cross entropy error):其中y是one-hot表示,所以只需要计算正确预测的情况。比如正确标签的索引是2,神经网络的输出是0.6,那么 E = -ln0.6
def cross_entropy_error(y, t):
delta = 1e-7
# 加一个delta是为了避免log(0)的情况
return -np.sum(t * np.log(y + delta))
如果扩展到计算一批的误差,则是
mini-batch学习:从训练数据中选出一批数据(mini-batch),然后对每个mini-batch进行学习。只要计算随机选出的这批数据的损失函数,就可以近似得到所有训练数据的损失。
那么,mini-batch的交叉熵误差这样计算:
def cross_entropy_error_1hot_batch(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
batch_size = y.shape[0]
delta = 1e-7
return -np.sum(t * np.log(y + delta)) / batch_size
def cross_entropy_error(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
batch_size = y.shape[0]
delta = 1e-7
return -np.sum(np.log(y[np.arrange(batch_size), t] + delta)) / batch_size
数值微分(Numerical differentiation)
前面说了,在计算神经网络的参数的时候,需要计算参数的导数。计算导数时,实际计算的是近似值。如下图所示,当h足够小的时候可以认为近似值足够接近真值。
数值微分指的就是用数值方法近似求解导数的过程。如果以x为中心,计算左右两边的差分,就是中心差分:(如果是 x+h 与 x
之间就是前向差分)
如果是基于数学式的推导求导数就称为解析性求导(analytic differentiation)。比如y=x^2的导数y’=2x。解析性求导得到的导数是不含误差的真实导数。
偏导数是在有多个变量的情况下,对某一个变量进行求导。比如
它的偏微分是:
求法就是将其他变量看作常数,只对当前变量做求导。
如果把所有变量的偏导数合在一起变成向量,就称为梯度(gradient)。梯度指示的方向是各点处的函数值减小最多的方向,但并不保证梯度所指的方向就是函数的最小值。
梯度法:从某个位置沿梯度方向前进一段,再重新求梯度,再继续按新梯度方向前进,以此逐渐减小函数值。寻找最小值的梯度法称为梯度下降法(gradient descent method),反之称为梯度上升法(gradient ascent method)。
梯度法想要寻找梯度为0的点,但是梯度为0处不一定是最小值。比如函数的极小值就是局部最小值,而鞍点(saddle point,指从某个方向看是极大值,从另一个方向看是极小值的点)也不是最小值。而且,如果函数很复杂且比较扁平,那么学习可能会进入一个平坦区域(“学习高原”),此时将无法前进。
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x) # 生成和x形状相同的数组
for idx in range(x.size):
tmp_val = x[idx]
# f(x+h)的计算
x[idx] = tmp_val + h
fxh1 = f(x)
# f(x-h)的计算
x[idx] = tmp_val - h
fxh2 = f(x)
# 计算导数
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val # 还原值
return grad
def gradient_descent(f, init_x, lr=0.01, step_num=100):
x = init_x
for i in range(step_num):
grad = numerical_gradient(f, x)
x -= lr * grad
return x
学习率是超参数。权重和偏置可以通过训练获取,但学习率需要人工设定。
总结:神经网络的学习过程
神经网络存在合适的权重和偏置,调整它们以便拟合训练数据,这一过程称为“学习”。学习分为4个步骤:
- mini-batch
从训练数据中随机选出一部分数据称为mini-batch。后续的目标是减小mini-batch的损失函数的值。 - 计算梯度
求出各个权重参数的梯度。 - 更新参数
将权重参数沿着梯度方向进行微小更新。 - 重复前三个步骤
一个epoch表示学习中所有训练数据都被使用过一次时的更新次数。比如有1w个训练数据,每个mini-batch是100个,那么就要做100次随机梯度下降,所以epoch=100。实际操作时会先将所有训练数据随机打乱,然后按指定的batch size,按顺序生成mini-batch。
下面是示意代码。没有跑,因为部分函数需要修改。
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size,
weight_init_std=0.01):
self.params = {}
# 生成服从正态分布的数据,(r, c)
self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)
def sigmoid_func(self, X):
return 1 / (1 + np.exp(-X))
def softmax(self, X):
c = np.max(X)
exp_X = np.exp(X - c)
sum_exp_X = np.sum(exp_X)
y = exp_X / sum_exp_X
return y
def cross_entropy_error(self, y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
batch_size = y.shape[0]
delta = 1e-7
return -np.sum(np.log(y[np.arrange(batch_size), t] + delta)) / batch_size
def predict(self, x):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
a1 = np.dot(x, W1) + b1
z1 = self.sigmoid_func(a1)
a2 = np.dot(z1, W2) + b2
y = self.softmax(a2)
return y
def loss(self, x, t):
y = self.predict(x)
return self.cross_entropy_error(y, t)
def accuracy(self, x, t):
y = self.predict(x)
y = np.argmax(y, axis=1)
t = np.argmax(t, axis=1)
acc = np.sum(y == t) / float(x.shape[0])
return acc
def numerical_gradient(self, f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x) # 生成和x形状相同的数组
for idx in range(x.size):
tmp_val = x[idx]
# f(x+h)的计算
x[idx] = tmp_val + h
fxh1 = f(x)
# f(x-h)的计算
x[idx] = tmp_val - h
fxh2 = f(x)
# 计算导数
grad[idx] = (fxh1 - fxh2) / (2 * h)
x[idx] = tmp_val # 还原值
return grad
def gradient(self, x, t):
loss_W = lambda W: self.loss(x, t)
grads = {}
grads['W1'] = self.numerical_gradient(loss_W, self.params['W1'])
grads['b1'] = self.numerical_gradient(loss_W, self.params['b1'])
grads['W2'] = self.numerical_gradient(loss_W, self.params['W2'])
grads['b2'] = self.numerical_gradient(loss_W, self.params['b2'])
return grads
X_train, y_train, X_test, y_test = get_data()
train_size = X_train.shape[0]
batch_size = 100
train_loss_list = []
train_acc_list = []
test_acc_list = []
iter_per_epoch = max(train_size / batch_size, 1)
# hyperparameters
iters_num = 10000
learning_rate = 0.1
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
for i in range(iters_num):
# get mini-batch
batch_mask = np.random.choice(train_size, batch_size)
x_batch = X_train[batch_mask]
y_batch = y_train[batch_mask]
# calc gradient
grad = network.numerical_gradient(x_batch, y_batch)
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
loss = network.loss(x_batch, y_batch)
train_loss_list.append(loss)
# 计算每个epoch的识别精度
if i % iter_per_epoch == 0:
train_acc = network.accuracy(X_train, y_train)
test_acc = network.accuracy(X_test, y_test)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)