本文为笔者学习吴恩达深度学习课程的学习笔记,仅作学习参考使用。
在吴恩达第二周课程中,编程作业为使用逻辑回归进行图像二分类,判断一张图片是否为猫。本文基于这种情景对基于逻辑回归的图像二分类问题进行讨论。
本文代码改编自博主何宽的这篇文章,稍有改动意在使代码更加简洁,让读者更容易将其与网课所讲的原理相联系。
模型训练的技术路线
深度学习中模型训练通常包含以下步骤:
- 数据准备:读取样本数据集(通常包含train集和test集)和预处理;
- 模型初始化:将模型(函数)的参数初始化为极小的数或零;
- 正向传播:模型对给定输入做出预测(将输入代入函数);
- 计算损失:损失函数反映模型的预测与真实标签的差异,损失越小越好;
- 反向传播:计算梯度等,用于参数更新;
- 参数更新:通常使用梯度下降法,更新模型的参数,使损失可以更小;
- 迭代:重复正向传播、计算损失、反向传播和参数更新步骤,直到模型损失足够小或达到一定的迭代次数;
- 评估:通过绘制学习曲线、Loss曲线或分析混淆矩阵等评估模型的效果。
逻辑回归
逻辑回归模型通过Sigmoid函数将线性回归的输出映射到0和1之间,其输出可以被解释为属于某类的概率。
就是说,逻辑回归的目的是得到一个形如下的一个函数来进行预测分类:
A
=
σ
(
Z
)
=
1
1
+
e
−
Z
=
1
1
+
e
−
ω
X
+
b
\ A=\sigma (Z)=\frac{1}{1+{{e}^{-Z}}}=\frac{1}{1+{{e}^{-\omega X+b}}}
A=σ(Z)=1+e−Z1=1+e−ωX+b1
其中A可解释为“图片是猫图的概率”。
而这个函数(Sigmoid)对应的损失函数(Cost function)如下,损失函数越小越好:
J
(
w
,
b
)
=
−
1
m
∑
i
=
1
m
[
y
(
i
)
log
(
a
(
i
)
)
+
(
1
−
y
(
i
)
)
log
(
1
−
a
(
i
)
)
]
\ J(w, b) = -\frac{1}{m} \sum_{i=1}^{m} [y^{(i)} \log(a^{(i)}) + (1 - y^{(i)}) \log(1 - a^{(i)})]
J(w,b)=−m1i=1∑m[y(i)log(a(i))+(1−y(i))log(1−a(i))]
代码实现
事先说明笔者的一个观点,在这里涉及一些库函数的使用,即使你不了解这些库函数也完全没有关系,因为随便一个GPT都写得出来,记忆库函数的使用是没有意义的,重要的是了解以下代码中的深度学习工作模式和原理,知道我们每步要做什么是最重要的。
- 导入所需要的库
import numpy as np
import matplotlib.pyplot as plt
import h5py
- 从文件导入数据集,其包含有:
训练集图像 train_set_x_orig :209张64x64的图像
训练集标签 train_set_y_orig :训练集的图像对应的分类值【0 | 1】
测试集图像 test_set_x_orig :50张64x64的图像
测试集标签 test_set_y_orig : 测试集的图像对应的分类值【0 | 1】
classes : bytes类型的两个字符串数据:[b’non-cat’ b’cat’]
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
- 对数据集进行预处理:展平 & 归一化
def pretreat(train_set_x_orig, test_set_x_orig):
# 把每个样本的特征化成列向量
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
# 归一化
train_set_x = train_set_x_flatten / 255
test_set_x = test_set_x_flatten / 255
return train_set_x, test_set_x
经过展平和归一化:(括号代表shape)
train_set_x_orig(209, 64, 64, 3)变成了train_set_x(12288, 209)
test_set_x_orig(50, 64, 64, 3) 变成了test_set_x(12288, 50)
- 定义sigmoid函数方便后续使用:
def sigmoid(z):
s = 1 / (1 + np.exp(-z))
return s
- 定义优化函数:
正向传播: 计算预测概率A和损失函数J;
反向传播: 计算梯度(dw, db);
梯度下降: 优化权重w和偏置b:
def optimize(w , b , X , Y , num_iterations , learning_rate):
# m为样本数量
m = X.shape[1]
costs = []
# 迭代次数num_iterations即循环次数
for i in range(num_iterations):
# 计算当前参数下的梯度和成本
# 正向传播
A = sigmoid(np.dot(w.T,X) + b) #计算激活函数的输出(预测概率 A)
cost = (- 1 / m) * np.sum(Y * np.log(A) + (1 - Y) * (np.log(1 - A))) #计算成本 J
# 反向传播
# 省略 dz = A - Y
dw = (1 / m) * np.dot(X, (A - Y).T) #请参考视频中的偏导公式。
db = (1 / m) * np.sum(A - Y) #请参考视频中的偏导公式。
# 梯度下降:更新权重w和偏置b
w = w - learning_rate * dw
b = b - learning_rate * db
# 每100次迭代记录一次成本并打印,用于分析模型的收敛过程
if i % 100 == 0:
costs.append(cost)
print("迭代的次数: %i , 误差值: %f" % (i,cost))
params = {
"w" : w,
"b" : b }
grads = {
"dw": dw,
"db": db }
return (params , grads , costs)
- 使用以上函数我们足以训练一个可用的模型,但训练完毕后,我们需要科学地看一下我们训练出来的模型效果如何,故编写下面的函数使用我们训练的模型对图片集进行预测判断:
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]):
#将概率a [0,i]转换为实际预测p [0,i]
Y_prediction[0,i] = 1 if A[0,i] > 0.5 else 0
#使用断言
assert(Y_prediction.shape == (1,m))
return Y_prediction
- 将以上函数集成为一个model函数,使用model函数便可以完成模型训练的完整过程:
def model(X_train , Y_train , X_test , Y_test , num_iterations = 2000 , learning_rate = 0.5):
# 初始化w向量和b
w , b = initialize_with_zeros(X_train.shape[0])
#
params , grads , costs = optimize(w , b , X_train , Y_train,num_iterations , learning_rate )
# 从字典“参数”中检索参数w和b
w , b = params["w"] , params["b"]
# 预测测试/训练集的标签
Y_prediction_test = predict(w , b, X_test)
Y_prediction_train = predict(w , b, X_train)
# 打印训练后的准确性
print("训练集准确性:" , format(100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100) ,"%")
print("测试集准确性:" , format(100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100) ,"%")
d = {
"costs" : costs,
"Y_prediction_test" : Y_prediction_test,
"Y_prediciton_train" : Y_prediction_train,
"w" : w,
"b" : b,
"learning_rate" : learning_rate,
"num_iterations" : num_iterations }
return d
- 相关函数定义完毕,我们便可以用简单的代码实现基于逻辑回归的图像二分类:
# 加载数据集
train_set_x_orig , train_set_y , test_set_x_orig , test_set_y , classes = load_dataset()
# 预处理数据集
train_set_x, test_set_x = pretreat(train_set_x_orig, test_set_x_orig)
# 代入model函数
d = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations = 2100, learning_rate = 0.005)
# 绘制图
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()
GPT总结的关键学习点:
数据预处理:我们学习了如何从文件中导入数据集,以及如何对数据集进行预处理,包括展平和归一化,使其适用于模型训练。
模型构建:通过定义Sigmoid函数和损失函数,我们掌握了逻辑回归模型的基本构成。
训练过程:我们详细了解了模型训练的各个步骤,特别是正向传播和反向传播的原理,以及如何使用梯度下降法进行参数更新。
模型评估:通过绘制学习曲线和计算训练集与测试集的准确率,我们学会了如何评估模型的性能。