说明
本教程将结合经典的神经网络入门案例,通过绘图剖析网络中传播的细节,并附上详细的求导公式,希望能帮助读者更好地理解神经网络的工作过程,本教程需要你了解一些神经网络的基本知识。如果文中有错误,请各位大佬及时指正
案例
在本案例中,我们将随机生成三类样本点,要求使用分类器将这三类样本点进行分类。首先,我们将会使用普通的线性分类器(不带激活函数)来进行分类,再使用神经网络分类器(带激活函数)进行分类,最后对比两者的分类效果。在整个教程中,我会通过绘图来详细剖析分类器各层的含义和作用,再附上代码,阅读代码时,你可以对比着看分类器的结构图
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = (10.0, 8.0)
# 初始化参数
np.random.seed(0)
N = 100 # 每个类中的样本点
D = 2 # 维度
K = 3 # 类别个数
X = np.zeros((N*K, D)) # (300, 2)
y = np.zeros(N*K, dtype='uint8')
for j in range(K):
ix = range(N*j, N*(j+1))
r = np.linspace(0.0, 1, N) # 在0.0到1.0之间返回间隔均匀的N个数字
t = np.linspace(j*4, (j+1)*4, N) + np.random.randn(N)*0.2
X[ix] = np.c_[r*np.sin(t), r*np.cos(t)] # 按行连接两个矩阵
y[ix] = j
plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral)
线性分类器
神经元
单个神经元的输出
其中 表示样本的第 个特征, 表示连接到该神经元的第 个权重, 表示偏置,
以此类推,可得到整个网络的矩阵表示
梯度
Softmax
其中 是第 个输出单元的输出, 是 经过 Softmax 转换后得到的概率值,表示样本属于第
梯度
若
若
交叉熵损失
损失函数
其中 表示第 个类别,若样本的真实类别为 ,则
梯度
针对分类问题,我们给定的结果
正则化惩罚项
梯度
矩阵形式
其中
# 单层神经网络
# 初始化参数
W = 0.01 * np.random.randn(D, K) # 随机初始化参数 (2, 3)
b = np.zeros((1, K)) # (1, 3)
step_size = 1e-0 # 1.0,学习率
reg = 1e-3 # 0.001,正则化系数
num_examples = X.shape[0] # 样本个数
# 训练
for i in range(1000):
# 前向传播
scores = np.dot(X, W) + b # (300, 3)
# softmax
exp_scores = np.exp(scores)
probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True) # 计算类别概率 (300, 3)
# 交叉熵损失
corect_logprobs = -np.log(probs[range(num_examples), y])
data_loss = np.sum(corect_logprobs) / num_examples # 对所有样本的损失进行求和,取均值
# 正则化惩罚项
reg_loss = 0.5 * reg * np.sum(W*W)
loss = data_loss + reg_loss
if i % 100 == 0:
print('iteration %d: loss %f' % (i, loss))
# 计算损失梯度 dL/dz
dscores = probs
dscores[range(num_examples), y] -= 1
dscores /= num_examples # 将一批数据产生的梯度取平均
# 计算在W,b上的梯度
dW = np.dot(X.T, dscores) # (2, 3)
db = np.sum(dscores, axis=0, keepdims=True) # (1, 3)
# 加上正则项梯度
dW += reg * W
# 参数更新
W += -step_size * dW
b += -step_size * db
scores = np.dot(X, W) + b
predicted_class = np.argmax(scores, axis=1) # 概率最大的类别的下标
print('training accuracy: %.2f' % (np.mean(predicted_class == y))) # 准确率
training accuracy: 0.49
h = 0.02
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
Z = np.dot(np.c_[xx.ravel(), yy.ravel()], W) + b # ravel将多维数组降为一维
Z = np.argmax(Z, axis=1) # 取概率最高的类别
Z = Z.reshape(xx.shape)
fig = plt.figure()
plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral, alpha=0.8) # 绘制三维等高线
plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral)
plt.xlim(xx.min(), xx.max())
plt.ylim(yy.min(), yy.max())
plt.show()
神经网络分类器
ReLU 激活单元
梯度
# 两层神经网络
# 初始化参数
h = 100 # 隐层大小(神经元个数)
W = 0.01 * np.random.randn(D, h)
b = np.zeros((1, h))
W2 = 0.01 * np.random.randn(h, K)
b2 = np.zeros((1, K))
step_size = 1e-0 # 学习率
reg = 1e-3 # 正则化系数
num_examples = X.shape[0] # 样本个数
# 训练
for i in range(10000):
# 前向传播
hidden_layer = np.maximum(0, np.dot(X, W) + b) # 使用 ReLU 激活函数
scores = np.dot(hidden_layer, W2) + b2
# softmax
exp_scores = np.exp(scores)
probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True) # 计算类别概率
# 交叉熵损失
corect_logprobs = -np.log(probs[range(num_examples), y])
data_loss = np.sum(corect_logprobs) / num_examples
# 正则化惩罚项
reg_loss = 0.5*reg*np.sum(W*W) + 0.5*reg*np.sum(W2*W2)
loss = data_loss + reg_loss
if i % 1000 == 0:
print("iteration %d: loss %f" % (i, loss))
# 计算损失梯度 dL/dz
dscores = probs
dscores[range(num_examples), y] -= 1
dscores /= num_examples
# 计算在W2,b2上的梯度
dW2 = np.dot(hidden_layer.T, dscores)
db2 = np.sum(dscores, axis=0, keepdims=True)
dhidden = np.dot(dscores, W2.T)
# ReLU 激活函数梯度
dhidden[hidden_layer <= 0] = 0
# 计算最后在W,b上的梯度
dW = np.dot(X.T, dhidden)
db = np.sum(dhidden, axis=0, keepdims=True)
# 加上正则项梯度
dW2 += reg * W2
dW += reg * W
# 参数更新
W += -step_size * dW
b += -step_size * db
W2 += -step_size * dW2
b2 += -step_size * db2
# 计算分类准确度
hidden_layer = np.maximum(0, np.dot(X, W) + b)
scores = np.dot(hidden_layer, W2) + b2
predicted_class = np.argmax(scores, axis=1)
print('training accuracy: %.2f' % (np.mean(predicted_class == y)))
training accuracy: 0.98
h = 0.02
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
Z = np.dot(np.maximum(0, np.dot(np.c_[xx.ravel(), yy.ravel()], W) + b), W2) + b2
Z = np.argmax(Z, axis=1)
Z = Z.reshape(xx.shape)
fig = plt.figure()
plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral, alpha=0.8)
plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral)
plt.xlim(xx.min(), xx.max())
plt.ylim(yy.min(), yy.max())
plt.show()
可以看到,神经网络由于引入了激活函数从而提升了分类器的非线性分类能力,由此我们可以感受到神经网络的强大魅力
完结,撒花!