sklearn 神经网络_DeepLearning学习(二):浅层神经网络
神经网络是一种计算模型,由大量的节点(或神经元)直接相互关联而构成。其中有输入层,隐藏层和输出层,这三个层可以相互叠加后产生一个庞大的神经网络。比如当前的输出层可能是下一个单元的输入层以此类推。在Python实现的那段,我们将使用浅层神经网络来对圆点(蓝色和红色)来进行分类,看看这些圆点的分布是怎么样的。Logistic回归这张图我们比较熟悉,是上一文中写到...
神经网络是一种计算模型,由大量的节点(或神经元)直接相互关联而构成。其中有输入层,隐藏层和输出层,这三个层可以相互叠加后产生一个庞大的神经网络。比如当前的输出层可能是下一个单元的输入层以此类推。
在Python实现的那段,我们将使用浅层神经网络来对圆点(蓝色和红色)来进行分类,看看这些圆点的分布是怎么样的。
Logistic回归
这张图我们比较熟悉,是上一文中写到的Logistic回归模型的计算式。其中有一个x特征量,w权重(向量)和标量b。使用线性函数来对w、x和b进行函数计算后得出z,对z进行sigmoid化后,我们得到了a。然后对a和y来计算损失函数,这是正向传播。正向传播我们可以计算出cost function(L(a, y)),之后我们通过对损失函数对w和b的求导后计算出dw和db(反向传播)。
浅层神经网络
上图为一个神经网络单元,其中含有一个输入层,一个隐藏层和一个输出层。我们将有单个隐藏层的神经网络称之为浅层神经网络。这里需要注意的是,这个神经网络是双层网络,因为输入层为第0层,一般不计算在网络层数内。隐藏层和输出层的维度取决于前一层特征量和当前层节点数。
以上图为例:
- w的维度为(4,3),4为当前隐藏层的节点数,3为输入层的特征量
- b的维度为(4,1),4为当前隐藏层的节点数
- a的维度为(1,4),1为当前层的节点数,4为前一层的特征量
神经网络的输出
其中每一层都有一个计算输出的过程,即计算z和计算a,z=sigmoid(a)。为了去除loop的显式循环,我们在Python中使用向量化来计算。计算方式如下:
通过x、w和b计算出z[1],然后sigmoid(z[1])计算出a[1](隐藏层的输出),通过a[1](变成了输出层的输入)计算出z[2],然后再使用sigmoid函数计算出a[2](最终输出)
向量化实现的多个例子
实现向量化的解释:
多样本的向量化实现方程:Z[i][1] = W[i]X + b[i],其他方程也是类似的。其中由于b[i]是一个偏置项,可以通过python的广播机制去自动计算。
- 向量化实现和非向量化实现
从上图中我们就可以看出,通过向量化处理后的计算方式要远远优于非向量化的显式循环计算方式。
激活函数
浅层神经网络的输出计算公式:
图中的g[i][1]就是激活函数,之前我们一直使用sigmoid函数作为我们的激活函数。其实激活函数可以有多种选择,而且不同层使用的激活函数还可以不同。
- 几种常见的激活函数
- sigmoid function
值域:(0,1) ;z=0时,a=0.5
在实际使用中,我们几乎不适用sigmoid函数作为激活函数,因为tanh激活函数的表现几乎各个方面都优于sigmoid函数,不过有一种情况是例外,那就是输出结果只有0和1的场合,即当使用神经网络进行2分类时我们使用sigmoid函数作为激活函数。
- tanh function
值域:(-1,1); x=0时,a=0
在上面介绍sigmoid函数时我们说了,除了进行2分类外,tanh函数的表现都要优于sigmoid函数。原因在于tanh取值范围为
tanh函数优点:输出值以0为中心,有数据中心化的效果。方便下一层的学习。
tanh函数缺点:和sigmoid函数类似,当输入z太大或太小时,梯度会接近于0,算法优化速度会非常慢。
- Relu function
当z>=0时,a=z;当z<0时,a=0
ReLu函数优点:克服了sigmoid/tanh的缺点,不存在梯度接近于0的情况,算法优化速度比sigmoid/tanh快很多。
ReLu缺点:当输入z<0时,梯度=0,但是这影响不大,对于大部分隐藏层的单元来说,输入z>0,此时梯度都是不为0的。
- Leaky relu function
当z>=0时,a=z;当z<0时,a=0.01z
Leaky-ReLu优点:ReLu的改进版本,克服了ReLu的缺点,当输入z<0
时,a=0,梯度=0。
Leaky-ReLu缺点:增加了一个超参数,z<0时的系数需要手动设置,一般设为0.01,实际操作中,可以进行尝试,选一个最优的。
神经网络的梯度下降算法
随机初始化
为什么要随机初始化,如果将权重w初始化为0会如何?
从上图我们可以看到,如果将参数初始化成0后,a(1)[1] = a(2)[1],dz(1)[1]=dz(2)[1]无论隐藏层有几个节点,他们的输出和下降梯度都是一样的,因此就没有隐藏层的意义了。
正确的初始化方式:W = np.random.rand((2,2))* 0.01[2],b为偏置量可以初始化为0。
应用例子
使用浅层神经网络模型,希望解决散点的分类问题。现在有一些数据(X和Y),这些数据用matplotlib来看的话像一朵花,其中输出值(Y)有2种,0和1。0代表红色,1代表蓝色。样子可以参考下图:
目标:我们希望使用某种方法来把这两种圆点的颜色标记出来,蓝色的点用蓝色标记,红色的点用红色标记。
Python实现
导入所需要的包,并且设置一个随机数的种子
import matplotlib.pyplot as plt
import sklearn.linear_model
from testCases_V2 import *
from planar_utils import plot_decision_boundary, sigmoid, load_planar_dataset
np.random.seed(1) # set a seed so that the results are consistent
其中load_planar_dataset可以产生训练用的数据X,Y。plot_decision_boundary可以根据散列点的数据参数来设置决策边界并赋颜色。sigmoid为sigmoid函数的实现。这些代码的实现可以参考下面planar_utils.py的内容。
import matplotlib.pyplot as plt
import numpy as np
import sklearn
import sklearn.datasets
import sklearn.linear_model
def plot_decision_boundary(model, X, y):
# Set min and max values and give it some padding
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
# Generate a grid of points with distance h between them
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
# Predict the function value for the whole grid
Z = model(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
# Plot the contour and training examples
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)
def sigmoid(x):
"""
Compute the sigmoid of x
Arguments:
x -- A scalar or numpy array of any size.
Return:
s -- sigmoid(x)
"""
s = 1/(1+np.exp(-x))
return s
def load_planar_dataset():
np.random.seed(1)
m = 400 # number of examples
N = int(m/2) # number of points per class
D = 2 # dimensionality
X = np.zeros((m,D)) # data matrix where each row is a single example
Y = np.zeros((m,1), dtype='uint8') # labels vector (0 for red, 1 for blue)
a = 4 # maximum ray of the flower
for j in range(2):
ix = range(N*j,N*(j+1))
t = np.linspace(j*3.12,(j+1)*3.12,N) + np.random.randn(N)*0.2 # theta
r = a*np.sin(4*t) + np.random.randn(N)*0.2 # radius
X[ix] = np.c_[r*np.sin(t), r*np.cos(t)]
Y[ix] = j
X = X.T
Y = Y.T
return X, Y
def load_extra_datasets():
N = 200
noisy_circles = sklearn.datasets.make_circles(n_samples=N, factor=.5, noise=.3)
noisy_moons = sklearn.datasets.make_moons(n_samples=N, noise=.2)
blobs = sklearn.datasets.make_blobs(n_samples=N, random_state=5, n_features=2, centers=6)
gaussian_quantiles = sklearn.datasets.make_gaussian_quantiles(mean=None, cov=0.5, n_samples=N, n_features=2, n_classes=2, shuffle=True, random_state=None)
no_structure = np.random.rand(N, 2), np.random.rand(N, 2)
return noisy_circles, noisy_moons, blobs, gaussian_quantiles, no_structure
首先,我们需要使用上面提供的load_planar_dataset函数来获得训练集,并且使用plot_decision_boundary来打印一下由这些训练集数据显示的图像是怎么样的。
X, Y = load_planar_dataset()
print("X.shape=" + str(X.shape))
print("Y.shape=" + str(Y.shape))
# 显示获得的数据
plt.scatter(X[0, :], X[1, :], c=Y.flatten(), s=40, cmap=plt.cm.Spectral)
plt.show()
首先打印出来的结果来看,X.shape=(2, 400),Y.shape=(1, 400),打印出来的训练集组成的图片为
然后我们就需要使用我们知道的知识来对它进行分类。
m = X.shape[1] # 获得测试数据总数,这里是400个训练用例
我们先使用简单的Logistic回归来分类一下这些数据,并验证训练出来的结果正确率是多少
# Train the logistic regression classifier
clf = sklearn.linear_model.LogisticRegressionCV()
clf.fit(X.T, Y.T)
# Plot the decision boundary for logistic regression
plot_decision_boundary(lambda x: clf.predict(x), X, Y.reshape(X[0, :].shape))
plt.title("Logistic Regression")
plt.show()
# Print accuracy
LR_predictions = clf.predict(X.T)
print('Accuracy of logistic regression:%d ' % float((np.dot(Y, LR_predictions)
+ np.dot(1 - Y, 1 - LR_predictions))
/ float(Y.size) * 100) + '% '
+ '(percentage of correctly labelled data points)')
执行后,打印出了一张使用Logistic回归进行分类后的结果图片
从这张图片我们可以看到,分类后的结果很不理想,差不多只有一半的正确率。让我们来看看程序执行后打印出来的实际准确率为多少。
打印结果:Accuracy of logistic regression:47 % (percentage of correctly labelled data points),连50%的准确率都不到。原因是这里训练集数据比较离散,并不是线性分布的,因此使用Logistic回归模型来进行分类貌似行不通。
那如果我们尝试着使用浅层神经网络模型来分类上面的数据,会不会好一些呢?让我们来试一下。
1、定义输入层、隐藏层、输出层的节点数
def layer_sizes(X, Y, n_layer):
"""
根据训练用例X和Y定义输入层、隐藏层还有输出层的节点数
:param X: 训练输入用例(2,400)
:param Y: 训练输出用例(1,400)
:return:
n_x -- the size of the input layer
n_h -- the size of the hidden layer
n_y -- the size of the output layer
"""
n_x = X.shape[0]
n_h = n_layer
n_y = Y.shape[0]
return n_x, n_h, n_y
2、通过各个层的节点数来初始化参数(W1,b1,W2,b2)。
注意:这里W1和W2不能初始化为0,需要使用随机数来初始化,原因在上面的文档中已经说明
def initialize_parameters(n_x, n_h, n_y):
"""
初始化参数
X的维度为(n_x, m)
W1(n_h(当前层级数),n_x(前一个层级数)),b1(n_h(当前层级数),1)
W2(n_y(当前层级数),n_h(前一个层级数)),b2(n_y(当前层级数),1)
将W1、b1还有W2、b2放入字典params里
还需要注意:
初始化参数的获取的随机数不宜太大,太大会使得之后得到的梯度较小,
训练过程因此会变得很慢。因此,这里获取随机数后要乘以0.01,
使得之后得到的梯度较大,提高算法的更新速度
:param n_x: the size of the input layer
:param n_h: the size of the hidden layer
:param n_y: the size of the output layer
:return:
params -- python dictionary containing your parameters:
W1 -- weight matrix of shape (n_h, n_x)
b1 -- bias vector of shape (n_h(当前层级数), 1)
W2 -- weight matrix of shape (n_y(当前层级数), n_h)
b2 -- bias vector of shape (n_y(当前层级数), 1)
"""
np.random.seed(2)
W1 = np.random.randn(n_h, n_x) * 0.01
b1 = np.zeros((n_h, 1))
W2 = np.random.randn(n_y, n_h) * 0.01
b2 = np.zeros((n_y, 1))
assert (W1.shape == (n_h, n_x))
assert (b1.shape == (n_h, 1))
assert (W2.shape == (n_y, n_h))
assert (b2.shape == (n_y, 1))
params = {
"W1": W1,
"b1": b1,
"W2": W2,
"b2": b2
}
return params
3、正向传播,计算Z1,A1,Z2,A2。计算公式可以参考上面文档中“激活函数“的“浅层神经网络的输出计算公式“部分
注意:这里使用tanh函数作为隐藏层输出的激活函数,使用sigmoid函数作为输出层的激活函数
def forward_propagation(X, params):
"""
正向传播
计算Z1,A1,Z2,A2
:param X: 训练用例,维度为(n_x, m)
:param params: 从initialize_parameters输出的字典
里面包含了各种初始化后的参数(W1,b1,W2,b2)
:return:
包含了计算后Z1,A1,Z2,A2的字典cache
"""
# 从数据字典里获取参数(W1,b1,W2,b2)
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)
# 断言上面几个结果的维度是否正常
assert (Z1.shape == (W1.shape[0], X.shape[1])) # Z1.shape() = (n_h, n_x) * (n_x, m) = (n_h, m)
assert (A1.shape == Z1.shape)
assert (Z2.shape == (1, X.shape[1])) # Z2.shape() = (n_y, n_h) * (n_h, m) = (n_y, m) = (1, m)
assert (A2.shape == Z2.shape)
cache = {
"Z1": Z1,
"A1": A1,
"Z2": Z2,
"A2": A2
}
return cache
4、计算cost function,有两种方式可以计算,计算公式如下
def compute_cost(A2, Y, params):
"""
计算cost
计算的公式请参考文档
:param A2: forward_propagation函数里计算出的输出
:param Y: 训练用例的训练结果集
:param params: 参数(W1,b1,W2,b2)
:return:
计算后的cost
"""
m = Y.shape[1] # 训练用例数
# Compute the cross-entropy cost
logprobs = np.multiply(np.log(A2), Y) + np.multiply(np.log(1 - A2), 1 - Y)
# 断言计算出cost的维度是否正常 A2(1, m),Y(1, m)
assert (logprobs.shape == (1, m))
cost = - np.sum(logprobs) / m
# use directly np.dot()) 计算方式不一样,直接使用np.dot(),需要变成下面的计算
# cost=-(np.dot(Y,np.log(A2.T))+np.dot(np.log(1-A2),(1-Y).T))/m
# 断言计算出cost的维度是否正常
assert (cost.shape == ())
# 降维,将1的维度去掉
cost = np.squeeze(cost)
assert (cost.shape == ())
return cost
5、反向传播,计算出dW1,db1,dW2,db2,公式参考文档中“神经网络的梯度下降算法”部分
def backward_propagation(params, cache, X, Y):
"""
反向传播
计算dW1,db1,dW2,db2
计算公式请参考文档
:param params: 保存参数W1,b1,W2,b2的字典
:param cache: 保存Z1,A1,Z2,A2的字典
:param X: 训练集
:param Y: 训练结果集
:return:
grads字典,里面存放计算出的dW1,db1,dW2,db2
"""
# 训练集数
m = X.shape[1]
# 从params里获取W1,W2
W1 = params["W1"]
W2 = params["W2"]
# 从cache里获取A1,A2
A1 = cache["A1"]
A2 = cache["A2"]
# Backward propagation: calculate dW1, db1, dW2, db2.
dZ2 = A2 - Y
dW2 = np.dot(dZ2, A1.T) / m
db2 = np.sum(dZ2, axis=1, keepdims=True) / m
dZ1 = np.dot(W2.T, dZ2) * (1 - np.power(A1, 2))
dW1 = np.dot(dZ1, X.T) / m
db1 = np.sum(dZ1, axis=1, keepdims=True) / m
grads = {
"dW2": dW2,
"db2": db2,
"dW1": dW1,
"db1": db1
}
return grads
6、通过梯度下降规则,计算出的dW1,db1,dW2,db2来反向更新W1,b1,W2,b2的值,公式如下:
def update_parameters(params, grads, learning_rate=1.2):
"""
更新参数W1,b1,W2,b2
计算公式请参考文档
:param params: 保存参数W1,b1,W2,b2的字典
:param grads: backward_propagation函数输出的字典,里面保存了dW1,db1,dW2,db2
:param learning_rate: 学习率
:return:
计算后的参数字典parameters
"""
# 获取参数W1,b1,W2,b2
W1 = params["W1"]
b1 = params["b1"]
W2 = params["W2"]
b2 = params["b2"]
# 获取参数dW1,db1,dW2,db2
dW1 = grads["dW1"]
db1 = grads["db1"]
dW2 = grads["dW2"]
db2 = grads["db2"]
# 重新计算参数W1,b1,W2,b2的值
W1 = W1 - learning_rate * dW1
b1 = b1 - learning_rate * db1
W2 = W2 - learning_rate * dW2
b2 = b2 - learning_rate * db2
parameters = {
"W1": W1,
"b1": b1,
"W2": W2,
"b2": b2
}
return parameters
7、建立一个浅层神经网络模型
def nn_model(X, Y, n_h, num_iterations=10000, print_cost=False):
"""
建立一个neural network(神经网络)模型
:param X: 训练集
:param Y: 训练结果集
:param n_h: 神经网络中隐藏层的节点数量
:param num_iterations: 梯度下降次数
:param print_cost: 是否打印cost值
:return:
经过模型学习后的参数值,一般来讲是获得最优结果时的参数值
这个参数值保存在parameters字典里
"""
np.random.seed(3)
# 将之前写的函数组装起来
# 输入层、隐藏层还有输出层的层数
n_x, n_h, n_y = layer_sizes(X, Y, n_h)
# 初始化参数
pvs = initialize_parameters(n_x, n_h, n_y)
for i in range(0, num_iterations):
# 正向传播,计算Z1,A1,Z2,A2
cache = forward_propagation(X, pvs)
# 计算出cost
A2 = cache["A2"]
cost = compute_cost(A2, Y, pvs)
# 反向传播,计算dW1,db1,dW2,db2
grads = backward_propagation(pvs, cache, X, Y)
# 更新参数W1,b1,W2,b2
pvs = update_parameters(pvs, grads, 1.2)
# Print the cost every 1000 iterations
if print_cost and i % 1000 == 0:
print("Cost after iteration %i: %f" % (i, cost))
return pvs
8、创建一个预测结果函数,来预测之前建立的浅层神经网络模型的输出结果,当输出结果>0.5时返回1,当结果<=0.5时,返回0
def predict(params, X):
"""
预测函数
使用之前nn_model函数学习的参数params来预测输入测试集X
:param params: 参数params
:param X: 测试集X
:return:
输入测试集的预测结果,red: 0 / blue:1
我们得到的值其实为A2,当A2的值>0.5则返回1,如果当A2的值<0.5则返回0
"""
cache = forward_propagation(X, params)
A2 = cache["A2"]
predictions = np.round(A2)
# 当A2的值>0.5则返回1,如果当A2的值<0.5则返回0
for i in range(predictions.shape[1]):
if predictions[0][i] <= 0.5:
predictions[0][i] = 0
else:
predictions[0][i] = 1
return predictions
9、定义一个隐藏层有4层的浅层神经网络模型
# Build a model with a n_h-dimensional hidden layer
params = nn_model(X, Y, n_h=4, num_iterations=10000, print_cost=True)
到此为止,一个浅层神经网络模型就建立完毕了。我们来看一下使用这个模型对训练数据进行分类的结果如何。
# Plot the decision boundary
plot_decision_boundary(lambda x: predict(params, x.T), X, Y.reshape(X[0, :].shape))
plt.title("Decision Boundary for hidden layer size " + str(4))
# 显示图片
plt.show()
# Print accuracy
predictions = predict(params, X)
print('Accuracy: %d' % float((np.dot(Y, predictions.T) + np.dot(1 - Y, 1 - predictions.T)) / float(Y.size) * 100) + '%')
分类后的结果图片如下:
这个图片看上去结果是差不多了,基本都分对了。我们来看一下输出的结果如何。
输出结果:
梯度下降算法计算出来的cost function值在不断减小,通过下降了10000次之后的参数来算出准确率为90%。由此可见通过浅层神经网络模型来分类训练集数据的准确率比Logistic 回归要好很多。
上面的例子中,我们将隐藏层节点数固定为4。
让我们再尝试着使用不同的隐藏层节点数(1, 2, 3, 4, 5, 20, 50;使用7种数量),来看下结果的变化。
# plt.figure(figsize=(16, 32))
hidden_layer_sizes = [1, 2, 3, 4, 5, 20, 50]
for i, n_h in enumerate(hidden_layer_sizes):
# plt.subplot(5, 2, i + 1)
parameters = nn_model(X, Y, n_h, num_iterations=5000)
plot_decision_boundary(lambda x: predict(parameters, x.T), X, Y.reshape(X[0, :].shape))
plt.title('Hidden Layer of size %d' % n_h)
plt.show()
predictions = predict(parameters, X)
accuracy = float((np.dot(Y, predictions.T) + np.dot(1 - Y, 1 - predictions.T)) / float(Y.size) * 100)
print("Accuracy for {} hidden units: {} %".format(n_h, accuracy))
输出结果:
由此可见,下降次数以及隐藏层节点数量(还有一个学习率)对结果还是有一些影响的。这些需要外界决定的参数我们称之为“超级参数”。这些超级参数的值应该使用什么值来得到最优结果,这个还需要好好探讨一番。个人觉得这里是一个非常难的地方。
参考资料
- 吴恩达-神经网络与深度学习-网易云课堂
- https://blog.csdn.net/sdu_hao/article/details/84677003
- 大树先生:吴恩达 DeepLearning.ai 课程提炼笔记(1-3)神经网络和深度学习 --- 浅层神经网络
本文算是本人学习吴恩达教授的DeepLearning系列课程的第二个学习笔记,仅供自己以后复习使用,并无其他目的。对于人工智能来讲本人还是一个小白,文中可能有些地方写的有错误,如果朋友发现了还请不吝赐教。另外,文中有很多地方引用了其他博主的内容,这给我自己的学习提供了很多帮助,在这里非常感谢。后续还会继续学习恩达教授的DeepLearning系列课程,以后应该还会继续更新学习博客。
参考
- ^ab[i]代表第神经网络单元中的第i层。一般来说输入层为第0层不做计算,因此是从第一个隐藏层开始计算。
- ^这里我们将W的值乘以0.01(0.01为超参数,需要手动设置,一般设为0.01)是为了使得权重W初始化为较小的值,这是因为如果使用sigmoid函数或者tanh函数作为激活函数时,W比较小,则Z=WX + b所得的值也比较小,处在0的附近,0点区域的附近梯度较大,能够大大提高算法的更新速度。
更多推荐
所有评论(0)