具有神经网络思维的逻辑回归算法


本次作业,将会构建一个逻辑回归分类器用以识别猫, 通过完成这次作业,也能了解一些神经网络的思维,并且能够建立一个对神经网络的基本认识。

deeplearning.ai官网地址:https://www.deeplearning.ai/
coursera地址:https://www.coursera.org/specializations/deep-learning
网易视频地址:https://163.lu/nPtn42
课程一第二周课后作业1-2

本次作业,将会构建一个逻辑回归分类器用以识别猫, 通过完成这次作业,也能了解一些神经网络的思维,并且能够建立一个对神经网络的基本认识。

1. 导入相关包

首先,导入,本此作业实现中所需要的所有相关包,具体实现如下:

1
2
3
4
5
6
7
import numpy as np
import matplotlib.pyplot as plt
import h5py
import scipy
from PIL import Image
from scipy import ndimage
from lr_utils import load_dataset
  • 一些库的简单介绍:
    h5py是一个处理h5文件的交互包
    scipy是一个科学计算库
    PIL是一个图像处理库

2.数据集的概述

作业所用到的数据集存储在data.h5文件中,对于数据集,有以下介绍:

  • 数据集中的训练集m_train图像,被标注为cat (y=1)non-cat (y=0)
  • 数据集中的测试集m_test图像,同样,被标注为catnon-cat
  • 每一幅图像的都是(num_px,num_px,3)的形式,其中3表示图像是3通道的RGB形式,而长和宽都是num_pxnum_px的正方形图片。
  • 加载数据前,可以利用python查看数据集的存储形式,具体实现代码如下所示.
1
2
3
4
5
6
f = h5py.File('dataset/train_catvnoncat.h5','r')   #打开h5文件  
#可以查看所有的主键
for key in f.keys():
print(f[key].name) #输出数据集标签值,train_set_x,train_set_y
print(f[key].shape) #输出数据集train_set_x,train_set_y的形式
print(f[key].value) #输出数据集trian_set_x和train_set_y的具体值

根据以上数据形式,编写loadset()函数加载数据集的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def load_dataset():
train_dataset = h5py.File('datasets/train_catvnoncat.h5', "r")
train_set_x_orig = np.array(train_dataset["train_set_x"][:]) #训练集数据特征
train_set_y_orig = np.array(train_dataset["train_set_y"][:]) # 训练集数据标签

test_dataset = h5py.File('datasets/test_catvnoncat.h5', "r")
test_set_x_orig = np.array(test_dataset["test_set_x"][:]) # 测试集数据特征
test_set_y_orig = np.array(test_dataset["test_set_y"][:]) #测试集数据标签

classes = np.array(test_dataset["list_classes"][:]) # 数据类别构成的列表

train_set_y_orig = train_set_y_orig.reshape((1, train_set_y_orig.shape[0]))
test_set_y_orig = test_set_y_orig.reshape((1, test_set_y_orig.shape[0]))

return train_set_x_orig, train_set_y_orig, test_set_x_orig, test_set_y_orig, classes

简单的测试下加载好的数据集,可以看到相关的输出信息如下所示:

1
2
3
index = 5
plt.imshow(train_set_x_orig[index])
print ("y = " + str(train_set_y[:, index]) + ", it's a '" + classes[np.squeeze(train_set_y[:, index])].decode("utf-8") + "' picture.")

许多深度学习代码存在bug的原因之一就是矩阵或者向量维数不匹配,在算法实现之前,先进行数据集的维度输出是一个和明智的做法,具体实现算法如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
m_train = train_set_x_orig.shape[0]
m_test = test_set_x_orig.shape[0]
num_px = train_set_x_orig.shape[1]


print ("Number of training examples: m_train = " + str(m_train))
print ("Number of testing examples: m_test = " + str(m_test))
print ("Height/Width of each image: num_px = " + str(num_px))
print ("Each image is of size: (" + str(num_px) + ", " + str(num_px) + ", 3)")
print ("train_set_x shape: " + str(train_set_x_orig.shape))
print ("train_set_y shape: " + str(train_set_y.shape))
print ("test_set_x shape: " + str(test_set_x_orig.shape))
print ("test_set_y shape: " + str(test_set_y.shape))

上述代码实现结果如下所示:

在图像处理过程中,数据集中的图像数据是一个(num_px,num_px,3)的矩阵,需要将其转换为(num_px×num_px×3,1)的矩阵,在python中有一个实现技巧如下所示,可轻松实现矩阵形式的转化。

1
X_flatten = X.reshape(X.shape[0], -1).T

综上所述,具体实现代码可以如下所示:

1
2
3
4
5
6
7
train_set_x_flatten = train_set_x_orig.reshape(train_set_x_orig.shape[0], -1).T
test_set_x_flatten = test_set_x_orig.reshape(test_set_x_orig.shape[0], -1).T
print ("train_set_x_flatten shape: " + str(train_set_x_flatten.shape))
print ("train_set_y shape: " + str(train_set_y.shape))
print ("test_set_x_flatten shape: " + str(test_set_x_flatten.shape))
print ("test_set_y shape: " + str(test_set_y.shape))
print ("sanity check after reshaping: " + str(train_set_x_flatten[0:5,0]))

以上代码结果如下所示:

为了表示图片中的颜色,由RGB通道表示图片颜色,即每个像素的值实际上是由三个0-255的数组成的向量。

机器学习中一种通用的处理数据集的方法称之为标准化,对于图像数据集而言,一种简单而又方便的处理数据的方法是对每一行数据除以255,即其RGB的最大值。具体实现可以如以下代码所示:

1
2
train_set_x = train_set_x_flatten/255.
test_set_x = test_set_x_flatten/255.

3. 学习算法的一般体系架构

有了如上的数据预处理流程,是时候构建一个具有神经网络思维的算法了,其算法实现过程基本如下图所示:

对于一个样本 $x^{(i)}$:
$$z^{(i)} = w^T x^{(i)} + b \tag{1}$$
$$\hat{y}^{(i)} = a^{(i)} = sigmoid(z^{(i)})\tag{2}$$
$$ \mathcal{L}(a^{(i)}, y^{(i)}) = - y^{(i)} \log(a^{(i)}) - (1-y^{(i)} ) \log(1-a^{(i)})\tag{3}$$

所有样本的损失可以由以下公式计算:
$$ J = \frac{1}{m} \sum_{i=1}^m \mathcal{L}(a^{(i)}, y^{(i)})\tag{6}$$

实现这个算法的关键步骤是:

  • 初始化模型的学习参数
  • 通过最小化损失函数得到模型的学习参数
  • 根据模型的学习参数做出相关预测
  • 分析预测结果并得出相关结论

4. 算法的构建过程

构建分类算法的主要步骤可以如下步骤所示:

  • 构建模型架构,例如特征的个数
  • 初始化模型参数
  • 循环:
    • 计算当前损失
    • 利用反向传播算法计算当前的梯度
    • 通过梯度下降更新参数

4.1 sigmoid函数

逻辑回归中的激活函数是sigmoid函数,其实现方式可以由以下代码所示:

1
2
3
def sigmoid(z):
s = 1 / (1 + np.exp(-z))
return s

4.2 初始化参数

初始化权重参数$w$并使其为0,参数$b = 0$,具体实现代码如下所示:

1
2
3
4
5
6
7
8
def initialize_with_zeros(dim): 
w = np.zeros((dim, 1))
b = 0
#确保参数w的维数和数据类型正确
assert(w.shape == (dim, 1))
assert(isinstance(b, float) or isinstance(b, int))

return w, b

4.3 前向和反向传播算法

初始化参数之后,通过传播算法计算学习参数了,关于参数的计算其公式如下是所示:
前向传播算法:
通过输入变量$X$计算:
$A = \sigma(w^T X + b) = (a^{(0)}, a^{(1)}, …, a^{(m-1)}, a^{(m)})\tag{7}$
计算损失函数:
$J = -\frac{1}{m}\sum_{i=1}^{m}y^{(i)}\log(a^{(i)})+(1-y^{(i)})\log(1-a^{(i)})\tag{8}$
可以通过以下两个公式计算相关参数:
$$ \frac{\partial J}{\partial w} = \frac{1}{m}X(A-Y)^T\tag{9}$$
$$ \frac{\partial J}{\partial b} = \frac{1}{m} \sum_{i=1}^m (a^{(i)}-y^{(i)})\tag{10}$$

根据以上公式,算法实现的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
def propagate(w, b, X, Y):
m = X.shape[1]
A = sigmoid(np.dot(w.T, X) + b)
cost = -1 / m * np.sum(Y * np.log(A) + (1 - Y) * np.log(1 - A))
dw = 1 / m * np.dot(X, (A - Y).T)
db = 1 / m * np.sum(A - Y)
assert(dw.shape == w.shape)
assert(db.dtype == float)
cost = np.squeeze(cost)
assert(cost.shape == ())
grads = {"dw": dw, "db": db}
return grads, cost

4.4 利用梯度下降优化算法

有了前向传播算法对参数的计算,接下来需要做的就是利用反向传播算法中的梯度下降算法更新参数,具体实现代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def optimize(w, b, X, Y, num_iterations, learning_rate, print_cost = False):
costs = []
for i in range(num_iterations):
grads, cost = propagate(w, b, X, Y)
dw = grads["dw"]
db = grads["db"]
w = w - learning_rate * dw
b = b - learning_rate * db
if i % 100 == 0:
costs.append(cost)
if print_cost and i % 100 == 0:
print ("Cost after iteration %i: %f" %(i, cost))

params = {"w": w,
"b": b}

grads = {"dw": dw,
"db": db}

return params, grads, costs

有了通过反向传播算法得到的参数$w$和$b$,可以根据公式:
$\hat{y} = \delta(w^Tx+b)\tag{11}$做出预测了,具体实现代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
def predict(w, b, X):
m = X.shape[1]
Y_prediction = np.zeros((1,m))
w = w.reshape(X.shape[0], 1)
A = sigmoid(np.dot(w.T, X) + b)
for i in range(A.shape[1]):
if A[0, i] <= 0.5:
Y_prediction[0, i] = 0
else:
Y_prediction[0, i] = 1
assert(Y_prediction.shape == (1, m))
return Y_prediction

5. 将所有函数合并在一起搭建模型

通过以上,已经分模块实现了算法的所有部分,现在可以通过构建一个模型函数将所有的函数合并在一起,用以实现模型的参数更新和预测,具体实现代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def model(X_train, Y_train, X_test, Y_test, num_iterations = 2000, learning_rate = 0.5, print_cost = False):
w, b = initialize_with_zeros(X_train.shape[0])
parameters, grads, costs = optimize(w, b, X_train, Y_train, num_iterations, learning_rate, print_cost)
w = parameters["w"]
b = parameters["b"]
Y_prediction_test = predict(w, b, X_test)
Y_prediction_train = predict(w, b, X_train)
print("train accuracy: {} %".format(100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100))
print("test accuracy: {} %".format(100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100))
d = {"costs": costs,
"Y_prediction_test": Y_prediction_test,
"Y_prediction_train" : Y_prediction_train,
"w" : w,
"b" : b,
"learning_rate" : learning_rate,
"num_iterations": num_iterations}
return d

可以简单绘制模型训练过程中损失函数和梯度的变化曲线,如下所示:

1
2
3
4
5
6
costs = np.squeeze(d['costs'])
plt.plot(costs)
plt.ylabel('cost')
plt.xlabel('iterations (per hundreds)')
plt.title("Learning rate =" + str(d["learning_rate"]))
plt.show()

如上图所示,可以看到,随着迭代次数的增加,模型的损失正在下降,当跌打次数增加,将会看到训练集的损失逐渐上升,而测试集的损失逐渐下降。

6.进一步的分析

学习率的大小对于模型的训练也是至关重要的,当学习率的大小决定着参数更新的速度,当学习率过大时,模型不容易收敛,而当学习率过小时,模型的需要迭代很多次才能收敛,可以用以下代码验证学习率对模型训练的影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
learning_rates = [0.01, 0.001, 0.0001]
models = {}
for i in learning_rates:
print ("learning rate is: " + str(i))
models[str(i)] = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations = 1500, learning_rate = i, print_cost = False)
print ('\n' + "-------------------------------------------------------" + '\n')

for i in learning_rates:
plt.plot(np.squeeze(models[str(i)]["costs"]), label= str(models[str(i)]["learning_rate"]))

plt.ylabel('cost')
plt.xlabel('iterations')

legend = plt.legend(loc='upper center', shadow=True)
frame = legend.get_frame()
frame.set_facecolor('0.90')

综上,可以得到如下结论:

  • 不同的学习率会得到不同的预测结果和损失
  • 如果模型的学习率过大,可能会导致模型的损失上下振荡,如上图所示,学习率取0.001可能是一个不错的选择。
  • 过小的损失不一定意味着该模型是一个好模型,能做出很好的预测,很可能是过拟合原因造成的。这种过拟合情况通常发生在训练集的精确度比测试集高很多的情况下。
  • 对于深度学习,通常有如下建议:
    • 为模型选择一个合适的学习率以得到更好的模型参数
    • 如果模型过拟合,通常要采取其他措施消除这种过拟合,一种行之有效的方法是正则化方法。
-------------本文结束感谢您的阅读-------------