numpy 搭建一个浅层神经网络来对数据进行简单分类
一个浅层神经网络一般只有两层,输入层并不算在内
数据集: sklearn.datasets 里面的 make_moons 构造数据
numpy 实现
目录:
1、构建数据
这里我们用的是 sklearn.datases 里面的 make_moons 来构造一个月牙形的数据
1.1、引入相应的库
import numpy as np
import matplotlib.pyplot as plt
import sklearn
import sklearn.datasets
%matplotlib inline
np.random.seed(1)
后面的代码都用这里引入的库
1.2、把数据以散点图的方式展现出来
这里我们产生的数据的维度是 X: (400, 2),我们通过转置给它转换成 (2, 400)。Y: (400,),先把它转化成标准的 (400, 1),再把它转换成 (1, 400) 的维度
make_moons 函数参数:
n_samples
: 样本数量,400
shuffle
: 是否打乱,类似于将数据集 random 一下
noise
: 默认是false,数据集是否加入高斯噪声
random_state
:生成随机种子,给定一个int型数据,能够保证每次生成数据相同。
X, Y = sklearn.datasets.make_moons(400, shuffle=True, noise=0.2, random_state=50)
X = X.T
Y = Y.reshape(Y.shape[0], 1).T
print (X.shape)
print (Y.shape)
# 画图
# X[0, :] 代表 x 轴
# X[1, :] 代表 y 轴
# Y.rabel 代表这些点的颜色,1 代表的是蓝色,0 代表红色,前提 cmap = plt.cm.Spectral
# c 参数接受的是一个 (m,) 的数据,而 Y 被我们转换成 (1, m) 型的,所以用 Y.ravel()
# s = 40 代表的是点的大小
plt.scatter(X[0, :], X[1, :], c=Y.ravel(), s=40, cmap=plt.cm.Spectral)
输出:
(2, 400)
(1, 400)
2、构建网络
1.1、初始化参数
# 随机初始化参数
def init_params(x_dim, h_dim, y_dim):
'''
参数
x_dim: 输入单元数,即特征数
h_dim: 隐藏单元个数
y_dim: 输出单元个数
返回值
params: 初始化后的参数字典
'''
np.random.seed(3)
# W1 的维度是 (h_dim, x_dim), b: shape = (h_dim, 1)
W1 = np.random.randn(h_dim, x_dim) / np.sqrt(x_dim)
b1 = np.zeros((h_dim, 1))
# W2 的维度是 (y_dim, h_dim), b: shape = (y_dim, 1)
W2 = np.random.randn(y_dim, h_dim) / np.sqrt(h_dim)
b2 = np.zeros((y_dim, 1))
params = {'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}
return params
这里我们可以输出看一下它的维度
# 检验维度
params = init_params(2, 4, 1)
print ('W1', params['W1'].shape)
print ('b1', params['b1'].shape)
print ('W2', params['W2'].shape)
print ('b2', params['b2'].shape)
输出:
W1 (4, 2)
b1 (4, 1)
W2 (1, 4)
b2 (1, 1)
1.2、前向传播
这里第一层用的激活函数为 tanh,第二层激活函数用的是 sigmoid
Z
1
=
W
1
⋅
X
+
b
1
Z1 = W1 · X + b1
Z1=W1⋅X+b1
A
1
=
g
(
Z
1
)
A1 = g(Z1)
A1=g(Z1)
Z
2
=
W
2
⋅
A
1
+
b
2
Z2 = W2 · A1 + b2
Z2=W2⋅A1+b2
A
2
=
g
(
Z
2
)
A2 = g(Z2)
A2=g(Z2)
# sigmoid
def sigmoid(Z):
return 1 / (1 + np.exp(-Z))
# 前向传播
def forward_propagation(X, params):
'''
参数
X: shape=(2, m)
params: 存放 W, b 的字典
返回值
A2: 前向传播结束之后得到的预测值
caches: 存放 Z1,A1 的缓存
'''
W1 = params['W1']
b1 = params['b1']
W2 = params['W2']
b2 = params['b2']
Z1 = np.dot(W1, X) + b1
A1 = np.tanh(Z1)
Z2 = np.dot(W2, A1) + b2
A2 = sigmoid(Z2)
caches = {'Z1': Z1, 'A1': A1, 'Z2': Z2, 'A2': A2}
return A2, caches
1.3、计算损失
J ( c o s t ) = − 1 m [ Y ⋅ l o g ( Y _ h a t ) . T + ( 1 − Y ) ⋅ l o g ( 1 − Y _ h a t ) . T ] J(cost) = -\frac{1}{m} [Y·log(Y\_hat).T + (1-Y)·log(1-Y\_hat).T] J(cost)=−m1[Y⋅log(Y_hat).T+(1−Y)⋅log(1−Y_hat).T]
def get_cost(Y_hat, Y):
m = Y_hat.shape[1]
cost = -1 / m * (np.dot(Y, np.log(Y_hat).T) + np.dot(1 - Y, np.log(1 - Y_hat).T))
# 把计算后的 cost 转换成一个数字
cost = np.squeeze(cost)
return cost
我们可以检验一下它的维度
cost = get_cost(Y_hat, Y)
print (cost)
输出:
0.7582110023564143
1.4、反向传播
d
Z
[
2
]
=
A
[
2
]
−
Y
dZ^{[2]} = A^{[2]} - Y
dZ[2]=A[2]−Y
d
W
[
2
]
=
d
Z
[
2
]
⋅
A
[
1
]
m
dW^{[2]} = \frac{dZ^{[2]} · A^{[1]}}{m}
dW[2]=mdZ[2]⋅A[1]
d
b
[
2
]
=
∑
i
=
0
m
d
Z
[
2
]
m
db^{[2]} = \frac{\sum_{i=0}^mdZ^{[2]}}{m}
db[2]=m∑i=0mdZ[2]
d
A
[
1
]
=
W
[
2
]
⋅
d
Z
[
2
]
dA^{[1]} = W^{[2]} · dZ^{[2]}
dA[1]=W[2]⋅dZ[2]
d
W
[
1
]
=
d
Z
[
1
]
⋅
A
[
1
]
m
dW^{[1]} = \frac{dZ^{[1]} · A^{[1]}}{m}
dW[1]=mdZ[1]⋅A[1]
d
b
[
1
]
=
∑
i
=
0
m
d
Z
[
1
]
m
db^{[1]} = \frac{\sum_{i=0}^mdZ^{[1]}}{m}
db[1]=m∑i=0mdZ[1]
# 反向传播
def backward_propagation(X, Y_hat, Y, params, caches):
'''
参数
Y_hat: 预测,也就是前面的 A2
Y: 真实值
params: W, b
caches; Z, A
返回值
grads: 梯度值,存放计算后的 dW, db
'''
m = X.shape[1]
W1 = params['W1']
W2 = params['W2']
Z1 = caches['Z1']
A1 = caches['A1']
Z2 = caches['Z2']
dZ2 = Y_hat - Y
dW2 = np.dot(dZ2, A1.T) / m
db2 = np.sum(dZ2, axis=1, keepdims=True) / m
dA1 = np.dot(W2.T, dZ2)
dZ1 = dA1 * (1 - np.power(A1, 2))
dW1 = np.dot(dZ1, X.T) / m
db1 = np.sum(dZ1, axis=1, keepdims=True) / m
grads = {'dW1': dW1, 'db1': db1, 'dW2': dW2, 'db2': db2}
return grads
1.5、更新参数
α \alpha α为学习率,代码中的 learning_rate
W
[
1
]
=
W
[
1
]
−
α
⋅
d
W
[
1
]
W^{[1]} = W^{[1]} - \alpha · dW^{[1]}
W[1]=W[1]−α⋅dW[1]
b
[
1
]
=
b
[
1
]
−
α
⋅
d
b
[
1
]
b^{[1]} = b^{[1]} - \alpha · db^{[1]}
b[1]=b[1]−α⋅db[1]
W
[
2
]
=
W
[
2
]
−
α
⋅
d
W
[
2
]
W^{[2]} = W^{[2]} - \alpha · dW^{[2]}
W[2]=W[2]−α⋅dW[2]
b
[
2
]
=
b
[
2
]
−
α
⋅
d
b
[
2
]
b^{[2]} = b^{[2]} - \alpha · db^{[2]}
b[2]=b[2]−α⋅db[2]
def update_params(X, params, grads, learning_rate):
'''
参数
X: shape = (2, m)
params: 参数
grads: 梯度值
learning_rate: 学习率
返回值
params: 新的参数
'''
params['W1'] -= learning_rate * grads['dW1']
params['b1'] -= learning_rate * grads['db1']
params['W2'] -= learning_rate * grads['dW2']
params['b2'] -= learning_rate * grads['db2']
return params
1.6、构建完整模型
def nn_model(X, Y, h_dim=4, epochs=1000, learning_rate=1.2):
'''
参数
X: shape = (2, m)
Y: shape = (1, m)
h_dim: 隐藏层的单元数
epochs: 迭代次数
learning_rate: 学习率
返回值
params: 训练后的参数,也就是训练后的模型
'''
# 输入维度是 2, 输出维度是 1
x_dim, y_dim = 2, 1
# 初始化参数
params = init_params(x_dim, h_dim, y_dim)
for i in range(1, epochs + 1):
# 前向传播
Y_hat, caches = forward_propagation(X, params)
# 计算损失
cost = get_cost(Y_hat, Y)
# 反向传播
grads = backward_propagation(X, Y_hat, Y, params, caches)
# 更新参数
params = update_params(X, params, grads, learning_rate)
# 迭代 1000 就打印 cost
if i % 1000 == 0:
print ('Iter: {:04}, cost: {:.5f}'.format(i, cost))
return params
3、预测
预测即利用训练好的参数进行一次前向传播后的 Y_hat
# 预测
def predict(params, X):
Y_hat, caches = forward_propagation(X, params)
# 解释一下 np.round() 函数,有点像四舍五入函数,小于等于 0.5 的就归为 0,反之为 1
preds = np.round(Y_hat)
return preds
4、开始训练
先定义一个函数用来描出边界
# 画出边界
# 画出决策边界
def plot_decision_boundary(model, X, y):
# 指定范围
#
x_min, x_max = X[0, :].min() - 1, X[0, :].max() + 1
y_min, y_max = X[1, :].min() - 1, X[1, :].max() + 1
h = 0.01
# 利用 np.meshgrid 生成一个坐标矩阵
# np.arange(x_min, x_max, h) 生成 x 的坐标,返回的是一个数组,范围为 x_min - x_max,步长为 0.01
# np.arange(y_min, y_max, h) 生成 y 的坐标,返回的是一个数组
# 假如这里输出 xx: (435, 581),yy: (435, 581)
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h ))
# xx.ravel() 方法是使高维变为一维,即从 (435, 581) -> (252735, )
# xx.ravel() 变成了一维的数组(一列),那么 yy.ravel() 同理
# np.c_[xx.ravel(), yy.ravel()]方法把两列组成一个两列的矩阵 (252735, 2)
# 这就和我们的输入数据 X 差不多了,2 个特征的数据
# 然后我们又借助 model 方法来进行这些点的预测,就是这些点该是 0,还是 1。
# 因为我们需要的输入维度是 (2, m) 的,所以 lambda x: predict(params, x.T) 我们要加上个转置
Z = model(np.c_[xx.ravel(), yy.ravel()])
# 预测好了之后,我们又把它归为,转为坐标矩阵,只是现在它们都有了一个身份,也就是下面的图内是红色还是蓝色
Z = Z.reshape(xx.shape)
# 下面的参数中,xx, yy 组成坐标矩阵,Z 则是预测值,即标志它们是 0 还是 1,cmap 则指定配色版
plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
plt.ylabel('x2')
plt.xlabel('x1')
plt.scatter(X[0, :], X[1, :], c=y, cmap=plt.cm.Spectral)
# 训练
params = nn_model(X, Y, h_dim=4, epochs=10000)
predictions = predict(params, X)
accuracy = float((np.dot(Y, predictions.T) + np.dot(1 - Y, 1 - predictions.T)) / float(Y.size))
print ('\n预测准确率是: {:.2f}'.format(accuracy))
# 画出决策边界
plot_decision_boundary(lambda x: predict(params, x.T), X, Y.ravel())
输出:
Iter: 1000, cost: 0.06048
Iter: 2000, cost: 0.05541
Iter: 3000, cost: 0.05331
Iter: 4000, cost: 0.05229
Iter: 5000, cost: 0.05167
Iter: 6000, cost: 0.05122
Iter: 7000, cost: 0.05073
Iter: 8000, cost: 0.04988
Iter: 9000, cost: 0.04914
Iter: 10000, cost: 0.04845
预测准确率是: 0.98