news 2026/8/28 15:57:28

从零实现逻辑回归:理解神经网络基础与梯度下降实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零实现逻辑回归:理解神经网络基础与梯度下降实战

1. 从零开始:为什么逻辑回归是深度学习的“第一块砖”

如果你刚开始接触吴恩达老师的深度学习课程,学完第一周的理论,面对第二周的编程作业“实现简单逻辑回归”时,心里可能会犯嘀咕:这听起来像是机器学习入门的内容,和“深度”有什么关系?我当年也有同样的困惑,但真正动手实现一遍后,才恍然大悟:逻辑回归,恰恰是理解神经网络一切奥秘的起点

你可以把它想象成乐高积木中最基础的那块2x4的砖。单看它,功能简单,就是实现一个二分类(是/否,猫/非猫)。但它的内部结构——输入、权重、偏置、激活函数、损失计算、梯度下降——构成了神经网络单个神经元(或者说感知机)的完整工作流程。后续无论多么复杂的网络,无论是10层、100层的全连接网络,还是卷积神经网络(CNN)、循环神经网络(RNN),其最基本的计算单元,其前向传播和反向传播的核心思想,都脱胎于这个简单的逻辑回归模型。

所以,这个作业的目的绝不是让你“复习”机器学习。它的核心价值在于:强迫你脱离高级框架(如TensorFlow、PyTorch)的“黑箱”,用最原始的NumPy,从数学公式到代码,亲手搭建并训练一个“神经元”。这个过程会让你对“权重如何更新”、“损失如何计算并反向传播”、“梯度下降究竟在做什么”产生肌肉记忆般的理解。很多人在后续学习中出现“梯度消失”、“爆炸”等问题时感到抽象,根源往往就在于跳过了这个亲手搭建的环节,对数据流动和梯度计算缺乏直观感受。

接下来,我将带你一步步拆解这个作业,不仅给出能跑通的代码,更重要的是解释每一个变量为什么这样定义,每一步计算背后的数学原理是什么,以及我在初次实现时踩过的那些“坑”。我们会用识别猫的图片这个经典二分类任务作为贯穿始终的例子。你需要准备的是:基本的Python编程知识、对NumPy数组操作有初步了解,以及一份渴望理解底层原理的好奇心。

2. 环境搭建与数据初探:别在第一步就卡住

在动手写模型之前,一个干净、可复现的环境和清晰的数据认知是成功的基石。很多新手在这里就会遇到版本冲突、路径错误等问题,导致兴致勃勃开始,灰头土脸结束。

2.1 构建专属的Python环境

我强烈建议你使用condavenv创建一个独立的虚拟环境。这能确保你使用的库版本与课程要求一致,避免和系统中其他项目冲突。

# 使用 conda 创建环境(假设环境名为 dl_lr) conda create -n dl_lr python=3.7 -y conda activate dl_lr # 或者使用 venv python -m venv dl_lr_env # Windows 激活 dl_lr_env\Scripts\activate # Linux/Mac 激活 source dl_lr_env/bin/activate

接下来安装核心依赖。吴恩达的课程作业通常基于 Jupyter Notebook,并使用h5py来读取课程提供的.h5格式数据集。

pip install numpy matplotlib jupyter h5py

注意:NumPy版本不宜过高。一些较新的NumPy版本在广播机制或函数接口上有细微变化,可能导致与课程示例代码不兼容。使用pip install numpy==1.19.5是一个比较稳妥的选择。

2.2 理解你的数据:猫 vs 非猫

课程提供的数据集通常是一个名为train_catvnoncat.h5test_catvnoncat.h5的文件。我们用h5py加载它,并看看里面到底有什么。

import h5py import numpy as np # 加载训练集 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"][:]) # 测试集标签 # 查看数据形状 print ("训练集特征形状: " + str(train_set_x_orig.shape)) print ("训练集标签形状: " + str(train_set_y_orig.shape)) print ("测试集特征形状: " + str(test_set_x_orig.shape)) print ("测试集标签形状: " + str(test_set_y_orig.shape))

你可能会看到类似这样的输出:

训练集特征形状: (209, 64, 64, 3) 训练集标签形状: (209,) 测试集特征形状: (50, 64, 64, 3) 测试集标签形状: (50,)

数据解读与第一个“坑”:

  • train_set_x_orig形状是(209, 64, 64, 3)。这代表有209张训练图片,每张图片是64像素高、64像素宽、3个颜色通道(RGB)。
  • train_set_y_orig形状是(209,),这是一个一维数组,包含209个标签,1代表“是猫”,0代表“非猫”。
  • 这里的关键点:我们的逻辑回归模型期望的输入是一个二维矩阵,形状为(特征数量, 样本数量)。每个样本应该是一个特征向量,而不是一个三维的图片块。所以,我们需要把每张64x64x3的图片“展平”成一个长度为 64 * 64 * 3 = 12288 的向量。这就是所谓的“预处理”。

2.3 数据预处理:标准化与维度变换

预处理包含两步:展平和标准化。

# 1. 展平数据 # 训练集样本数 m_train = train_set_x_orig.shape[0] m_test = test_set_x_orig.shape[0] # 每张图片展平后的特征数 num_px = train_set_x_orig.shape[1] # 64 num_py = train_set_x_orig.shape[2] # 64 num_channels = train_set_x_orig.shape[3] # 3 train_set_x_flatten = train_set_x_orig.reshape(m_train, -1).T test_set_x_flatten = test_set_x_orig.reshape(m_test, -1).T print ("训练集展平后形状: " + str(train_set_x_flatten.shape)) print ("测试集展平后形状: " + str(test_set_x_flatten.shape)) # 输出应为: (12288, 209) 和 (12288, 50)

为什么是.T(转置)?这是本作业乃至后续神经网络实现中一个至关重要的约定,也是容易混淆的点。展平后,train_set_x_flatten的形状最初是(209, 12288),即每一行是一个样本的所有特征。但在后续的向量化计算中,为了计算效率,我们通常将权重w设计为(12288, 1)的列向量。那么w.T * x(其中x是一个样本)就需要x是列向量。更通用的做法是,让数据矩阵X的每一列代表一个样本。所以我们需要转置,得到(12288, 209),这样第i列就是第i个样本的12288维特征向量。这个约定在后续的Z = w.T X + b计算中会非常自然。

# 2. 标准化数据 # 图片RGB像素值范围是0-255,将其标准化到0-1区间,有助于梯度下降更快更稳定地收敛。 train_set_x = train_set_x_flatten / 255. test_set_x = test_set_x_flatten / 255.

至此,我们的数据train_set_xtest_set_x已经准备好了,形状分别是(12288, 209)(12288, 50)。标签train_set_ytest_set_y保持为(1, 209)(1, 50)的行向量形式(后续需要调整)。

3. 逻辑回归模型的核心架构:前向传播的数学到代码

模型的核心就是两个函数:前向传播(计算预测值和损失)和反向传播(计算梯度并更新参数)。我们先从前向传播开始,把每一步的数学公式和Python代码对应起来。

3.1 模型参数初始化:小随机数的智慧

逻辑回归有两个参数:权重w(形状:(n_x, 1)) 和偏置b(一个标量)。n_x是特征数量,这里是12288。

为什么用随机初始化,而不是全零?对于逻辑回归,其实初始化成全零是可以的,因为只有一个神经元,对称性破坏的问题不严重。但在神经网络中,如果所有权重初始化为相同值(包括全零),那么每个神经元在反向传播时会获得完全相同的梯度,导致所有神经元学习到相同的特征,失去了网络的意义。因此,养成随机初始化的习惯至关重要。我们使用很小的随机数(如0.01),是为了避免在激活函数(如sigmoid)的饱和区(梯度接近0)开始训练,导致学习缓慢。

def initialize_with_zeros(dim): """ 此函数创建一个维度为(dim, 1)的权重向量w,并将b初始化为0。 参数: dim -- 权重向量w的大小(本例中为特征数量) 返回: w -- 初始化的权重向量 (dim, 1) b -- 初始化的偏置(标量) """ w = np.random.randn(dim, 1) * 0.01 # 使用小随机数 b = 0.0 # 为了确保w和b的数据类型正确,我们使用assert来检查 assert(w.shape == (dim, 1)) assert(isinstance(b, float) or isinstance(b, int)) return w, b

3.2 Sigmoid函数:将线性输出映射为概率

线性计算z = w.T * x + b的结果是一个实数,范围是负无穷到正无穷。我们需要一个函数将其映射到(0, 1)区间,代表“是猫”的概率。这个函数就是Sigmoid。

公式:σ(z) = 1 / (1 + e^(-z))

它的输出特性完美符合概率要求:当z很大时,σ(z)接近1;当z很小时,σ(z)接近0;当z=0时,σ(z)=0.5。

def sigmoid(z): """ 计算z的sigmoid值。 参数: z -- 一个标量或numpy数组。 返回: s -- z的sigmoid结果。 """ s = 1 / (1 + np.exp(-z)) return s

实操心得:np.exp(-z)z是一个非常小的负数时(比如 -1000),可能会因为数值下溢而得到0,但这通常不会导致问题,因为1/(1+0)=1。反之,如果z是一个非常大的正数(比如1000),np.exp(-z)会下溢为0,计算1/(1+0)=1也是正确的。NumPy能很好地处理这些极端情况。但在自己实现时,意识到这些数值稳定性问题是有益的。

3.3 前向传播:计算预测值与损失

前向传播包含两步:

  1. 计算线性输出和激活值(预测值):Z = w.T X + b(向量化后,X形状为(n_x, m), 所以Z形状为(1, m)A = σ(Z), A就是我们对每个样本的预测概率ŷ,形状也是(1, m)

  2. 计算损失(代价):我们使用交叉熵损失函数。对于单个样本:L(a, y) = -[y*log(a) + (1-y)*log(1-a)]。对所有样本取平均得到代价函数J

为什么用交叉熵损失而不是均方误差(MSE)?这是一个关键选择。对于分类问题,特别是输出经过Sigmoid激活后,MSE损失函数会是非凸的,存在很多局部最小值,不利于梯度下降优化。而交叉熵损失是凸函数,能保证梯度下降找到全局最优解(对于逻辑回归)。从信息论角度看,它衡量了预测分布与真实分布的“距离”。

def propagate(w, b, X, Y): """ 实现前向传播和反向传播,计算代价和梯度。 参数: w -- 权重,形状为 (n_x, 1) b -- 偏置,一个标量 X -- 输入数据,形状为 (n_x, m) Y -- 真实标签,形状为 (1, m) 返回: cost -- 逻辑回归的交叉熵代价 dw -- w的损失梯度,形状与w相同 db -- b的损失梯度,形状与b相同 """ m = X.shape[1] # 样本数量 # 前向传播 Z = np.dot(w.T, X) + b # Z形状: (1, m) A = sigmoid(Z) # A形状: (1, m), 即预测值 ŷ # 计算代价(交叉熵损失) # 这里使用了向量化操作,避免for循环。注意np.log可能对0或1输入产生警告,但我们的A理论上不会精确等于0或1。 cost = - (1/m) * np.sum(Y * np.log(A) + (1 - Y) * np.log(1 - A)) # 反向传播(计算梯度) dZ = A - Y # 这是Sigmoid激活函数和交叉熵损失结合后推导出的优美结果,形状: (1, m) dw = (1/m) * np.dot(X, dZ.T) # 形状: (n_x, 1) db = (1/m) * np.sum(dZ) # 标量 # 使用断言确保梯度形状正确 assert(dw.shape == w.shape) assert(db.dtype == float) # 将代价从数组转换为标量(如果它是数组的话) cost = np.squeeze(cost) assert(cost.shape == ()) # 将梯度和代价存入字典 grads = {"dw": dw, "db": db} return grads, cost

反向传播公式推导的直观理解:dZ = A - Y这个公式是核心。它表示预测概率A与真实标签Y之间的误差。对于第i个样本,如果y=1a很小(预测错误),那么dz就是一个很大的负数,意味着需要大幅调整参数。dwdb就是这个误差dZ对参数wb的“贡献”的平均值。np.dot(X, dZ.T)实现了对所有样本梯度的向量化求和,效率远高于for循环。

4. 优化器:梯度下降的迭代艺术

有了计算代价和梯度的函数,我们就可以用梯度下降法来迭代优化参数wb,使代价J最小化。

4.1 梯度下降的更新规则

公式:w = w - α * dwb = b - α * db其中α是学习率,控制每次参数更新的步长。

4.2 实现优化循环

我们将前向传播、反向传播和参数更新封装到一个优化函数中。

def optimize(w, b, X, Y, num_iterations, learning_rate, print_cost=False): """ 此函数通过梯度下降算法优化w和b 参数: w -- 权重,形状为 (n_x, 1) b -- 偏置,一个标量 X -- 输入数据,形状为 (n_x, m) Y -- 真实标签,形状为 (1, m) num_iterations -- 优化迭代次数 learning_rate -- 学习率,梯度下降的步长 print_cost -- 每100次迭代打印一次损失值 返回: params -- 包含优化后的w和b的字典 grads -- 包含最后一次迭代的梯度的字典 costs -- 记录每次迭代损失值的列表,用于绘图 """ costs = [] for i in range(num_iterations): # 计算当前参数下的梯度和代价 grads, cost = propagate(w, b, X, Y) # 从grads字典中取出梯度 dw = grads["dw"] db = grads["db"] # 梯度下降更新规则 w = w - learning_rate * dw b = b - learning_rate * db # 每100次迭代记录一次代价 if i % 100 == 0: costs.append(cost) if print_cost: print ("第 %i 次迭代后的损失值: %f" %(i, cost)) # 将最终参数和梯度存入字典 params = {"w": w, "b": b} grads = {"dw": dw, "db": db} return params, grads, costs

学习率的选择与第二个“坑”:学习率α是梯度下降中最重要的超参数之一。太大(如1.0),代价函数可能会震荡甚至发散;太小(如0.000001),收敛速度会极慢。对于这个猫分类数据集,经过实践,0.0050.01是一个不错的起点。在optimize函数中,我们记录了每100次迭代的损失,并可以打印出来。务必观察损失曲线的变化:一个健康的训练过程,损失值应该随着迭代平稳下降,最终趋于平缓。如果损失值上下跳动或上升,很可能意味着学习率太大了。

4.3 预测与模型评估

训练完成后,我们需要用学到的参数wb对新的数据进行预测,并评估模型在训练集和测试集上的准确率。

def predict(w, b, X): ''' 使用学习到的逻辑回归参数(w, b)预测X的标签 参数: w -- 权重,形状为 (n_x, 1) b -- 偏置,一个标量 X -- 输入数据,形状为 (n_x, m) 返回: Y_prediction -- 包含X中所有样本预测结果(0/1)的numpy数组,形状为(1, m) ''' m = X.shape[1] Y_prediction = np.zeros((1, m)) w = w.reshape(X.shape[0], 1) # 确保w形状正确 # 计算预测概率A A = sigmoid(np.dot(w.T, X) + b) # 将概率转换为0/1预测 # 方法1: 使用for循环(直观但慢) # for i in range(A.shape[1]): # if A[0, i] > 0.5: # Y_prediction[0, i] = 1 # else: # Y_prediction[0, i] = 0 # 方法2: 向量化操作(推荐,速度快) Y_prediction = (A > 0.5).astype(int) return Y_prediction

评估准确率很简单,就是比较预测值Y_prediction和真实标签Y相等的比例。

def model(X_train, Y_train, X_test, Y_test, num_iterations=2000, learning_rate=0.005, print_cost=False): """ 构建完整的逻辑回归模型 参数: X_train -- 训练集,形状为 (n_x, m_train) Y_train -- 训练标签,形状为 (1, m_train) X_test -- 测试集,形状为 (n_x, m_test) Y_test -- 测试标签,形状为 (1, m_test) num_iterations -- 迭代次数 learning_rate -- 学习率 print_cost -- 是否打印训练过程中的损失 返回: d -- 包含模型信息的字典 """ # 1. 初始化参数 w, b = initialize_with_zeros(X_train.shape[0]) # 2. 梯度下降优化 params, grads, costs = optimize(w, b, X_train, Y_train, num_iterations, learning_rate, print_cost) # 3. 获取训练好的参数 w = params["w"] b = params["b"] # 4. 在训练集和测试集上进行预测 Y_prediction_train = predict(w, b, X_train) Y_prediction_test = predict(w, b, X_test) # 5. 计算准确率 train_accuracy = 100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100 test_accuracy = 100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100 # 打印结果 print("训练集准确率: {:.2f} %".format(train_accuracy)) print("测试集准确率: {:.2f} %".format(test_accuracy)) # 将结果存入字典 d = {"costs": costs, "Y_prediction_train": Y_prediction_train, "Y_prediction_test": Y_prediction_test, "w": w, "b": b, "learning_rate": learning_rate, "num_iterations": num_iterations} return d

5. 模型训练、分析与可视化:读懂你的模型

现在,让我们把所有部分组合起来,训练模型并分析结果。

# 调用模型函数 d = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations=2000, learning_rate=0.005, print_cost=True)

运行后,你可能会看到类似下面的输出:

第 0 次迭代后的损失值: 0.693147 第 100 次迭代后的损失值: 0.584508 第 200 次迭代后的损失值: 0.466949 ... 第 1800 次迭代后的损失值: 0.058389 第 1900 次迭代后的损失值: 0.053441 训练集准确率: 99.04 % 测试集准确率: 70.00 %

5.1 解读训练结果:过拟合与欠拟合

这是一个非常典型的结果:

  • 训练集准确率很高(99%):说明模型已经很好地记住了训练数据。
  • 测试集准确率较低(70%):说明模型的泛化能力一般。模型从训练数据中学到的规律,不能完美地推广到没见过的测试数据上。

这指向了机器学习中的一个核心问题:过拟合。我们的模型(一个简单的逻辑回归)可能过于复杂(相对于这个小数据集),或者更常见的是,我们使用的特征(12288个像素值)对于区分猫和非猫来说,并不是最有效的。原始像素包含大量冗余和噪声信息。在深度学习中,我们会使用卷积神经网络(CNN)来自动学习更高级、更鲁棒的特征,从而大幅提升性能。但在这个入门作业中,70%左右的测试准确率是符合预期的,它清晰地展示了使用原始像素的线性分类器的局限性。

5.2 可视化学习过程

绘制损失曲线是诊断训练过程是否健康的重要手段。

import matplotlib.pyplot as plt # 绘制损失曲线 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()

你应该看到一条随着迭代次数增加而单调下降的曲线,最终趋于平缓。如果曲线震荡,说明学习率太大;如果下降极其缓慢,说明学习率太小。

5.3 学习率的影响实验

为了深刻理解学习率的作用,我们可以进行一个简单的实验:用不同的学习率训练模型,并观察损失曲线的变化。

learning_rates = [0.01, 0.005, 0.001, 0.0005] models = {} for lr in learning_rates: print ("使用学习率: " + str(lr)) models[str(lr)] = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations=1500, learning_rate=lr, print_cost=False) print ('\n' + "-------------------------------------------------------" + '\n') # 绘制不同学习率下的损失曲线 for lr in learning_rates: plt.plot(np.squeeze(models[str(lr)]["costs"]), label=str(lr)) plt.ylabel('cost') plt.xlabel('iterations (per hundreds)') plt.legend(loc='upper right', shadow=True) plt.title("不同学习率下的损失曲线") plt.show()

通过这个对比图,你可以直观地看到:

  • 学习率过大(如0.01):损失曲线可能震荡,甚至无法收敛(代价上升)。
  • 学习率过小(如0.0005):损失下降非常缓慢,需要更多迭代才能达到相同的效果。
  • 学习率适中(如0.005):损失平稳快速下降。

这个实验虽然简单,但它体现了深度学习调参的基本方法论:通过控制变量,观察模型性能(损失、准确率)的变化,从而找到合适的超参数。

6. 调试与常见问题排查:当你代码不工作时

第一次实现时,几乎一定会遇到各种错误。下面是我总结的几个最常见的“坑”及其解决方法。

6.1 维度不匹配错误

这是NumPy向量化操作中最常见的错误。错误信息通常是ValueError: shapes (a,b) and (c,d) not aligned

排查清单:

  1. 检查X,Y,w,b的初始形状。

    • X_train应该是(n_x, m_train),例如(12288, 209)。检查你是否做了转置.T
    • Y_train应该是(1, m_train),例如(1, 209)。如果原始标签是(209,),需要用Y = Y.reshape(1, -1)进行重塑。
    • w应该是(n_x, 1)
    • b应该是一个标量(float)。
  2. propagate函数中,逐行检查矩阵运算的维度。

    • Z = np.dot(w.T, X) + bw.T(1, n_x)X(n_x, m),点积结果是(1, m)b通过广播机制加到每个元素上。
    • dZ = A - YAY都必须是(1, m)
    • dw = (1/m) * np.dot(X, dZ.T)X(n_x, m)dZ.T(m, 1),点积结果是(n_x, 1),与w形状一致。
    • db = (1/m) * np.sum(dZ):对(1, m)dZ求和,得到一个标量。

调试技巧:在函数的关键步骤后使用print(X.shape)打印数组形状,确保与你的预期一致。

6.2 损失值为 NaN 或无限大

这通常是由于数值计算问题引起的。

  1. 检查 Sigmoid 函数的输入Z如果Z的值非常大(正或负),np.exp(-z)可能会溢出(得到inf)或下溢(得到0)。虽然NumPy的sigmoid实现通常能处理,但如果你自己写的sigmoid没有做数值稳定处理,就可能出问题。确保使用1 / (1 + np.exp(-z))
  2. 检查交叉熵损失计算中的log当预测概率A非常接近0或1时,np.log(A)np.log(1-A)可能得到负无穷(-inf)。一个常见的稳定化技巧是“裁剪”:
    # 在计算损失前,将A限制在一个很小的范围内,如[1e-15, 1-1e-15] A = np.clip(A, 1e-15, 1 - 1e-15) cost = - (1/m) * np.sum(Y * np.log(A) + (1 - Y) * np.log(1 - A))
  3. 检查学习率。过大的学习率可能导致参数更新步伐太大,使得损失“爆炸”成NaN。尝试将学习率降低一个数量级(例如从0.1降到0.01)。

6.3 模型性能不佳(准确率接近50%)

如果训练集和测试集准确率都只在50%左右徘徊,说明模型没有学到任何东西,相当于随机猜测。

  1. 检查数据预处理。确保标签Y是正确的(0或1)。确保数据标准化(/255.)已经完成。
  2. 检查参数初始化。确保权重w是用小随机数初始化的(如np.random.randn(dim,1)*0.01)。如果初始化为全零,对于逻辑回归虽然可以工作,但养成好习惯很重要。如果初始值太大,可能导致Sigmoid饱和,梯度消失,学习停滞。
  3. 检查梯度计算是否正确。这是最可能的原因。实现一个梯度检查函数是深度学习入门阶段的必备技能。梯度检查通过数值方法(使用极限定义)近似计算梯度,并与你反向传播计算的解析梯度进行比较。如果两者差异很大,说明你的反向传播实现有bug。由于篇幅所限,梯度检查的实现细节较多,但其核心思想是:对于每个参数θ,计算J(θ+ε) - J(θ-ε) / (2ε)作为数值梯度,并与你的比较。相对误差应在1e-7量级。
  4. 检查学习率和迭代次数。学习率可能太小,或者迭代次数不够。尝试增加num_iterations到5000或10000,观察损失是否还在下降。

7. 从逻辑回归到神经网络:思维的跃迁

完成这个简单的逻辑回归实现,其意义远不止于完成一次作业。它为你搭建了理解神经网络的完整思维框架。

单个神经元就是逻辑回归:你现在实现的,就是一个没有隐藏层的神经网络。输入层(12288个神经元)直接连接到输出层(1个神经元,使用Sigmoid激活)。

前向传播的通用模式:无论网络多复杂,前向传播都是Z[l] = W[l]A[l-1] + b[l],A[l] = g[l](Z[l])的重复。你刚刚实现了l=1的这一层。

反向传播的基石:你推导并实现了dZ = A - Y,以及dwdb的计算。在多层网络中,反向传播无非是从输出层开始,将这个误差一层层往回传递,利用链式法则计算每一层参数的梯度。你现在的理解,是解开反向传播黑盒的第一把钥匙。

向量化的重要性:你体验了用np.dot代替for循环带来的效率提升。在真正的深度网络中,面对百万级的数据,向量化是唯一可行的道路。

当你后续学习真正的神经网络时,你会看到:

  • 初始化:从initialize_with_zeros扩展到HeXavier初始化。
  • 激活函数:从Sigmoid扩展到ReLU,Tanh
  • 损失函数:从二分类交叉熵扩展到多分类交叉熵、均方误差等。
  • 优化器:从基础的梯度下降扩展到Momentum,RMSprop,Adam

但万变不离其宗,核心的数据流动(前向/反向)、梯度计算、参数更新的逻辑,你已经在这次“简单”的逻辑回归实现中亲手搭建并运行过了。这份从零开始构建的体验,是直接调用model.fit()所无法替代的。它赋予你一种“手感”和“直觉”,让你在后续面对更复杂的模型和更诡异的问题时,能有章法地进行分析和调试,而不是停留在盲目调参的层面。这就是这个作业,这块深度学习的“第一块砖”,真正想交给你的东西。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/28 15:55:27

LSTM与AutoML结合的时间序列预测实战:从数据爬取到模型优化

简介:时间序列预测是机器学习与数据分析领域的核心课题,旨在基于历史数据预测未来趋势。其核心原理在于挖掘数据点之间的时间依赖关系,传统统计方法在处理复杂非线性时序模式时往往受限。深度学习技术,特别是长短期记忆网络&#…

作者头像 李华
网站建设 2026/8/28 15:51:36

AI智能体如何读取Slack公开频道:从私信迁移到数据管道的工程实践

最近在帮团队落地 AI 智能体时,遇到一个很现实的问题:模型本身能力再强,读不到业务数据也等于零。老板们在推进智能化过程中,最常提出的一个要求就是把员工的工作信息从私密 Slack 私信迁到公开频道,理由是“AI 智能体…

作者头像 李华
网站建设 2026/8/28 15:50:03

Superpowers 技能框架完整指南:让编码代理按流程干活

Superpowers 技能框架完整指南:让编码代理按流程干活 【免费下载链接】superpowers An agentic skills framework & software development methodology that works. 项目地址: https://gitcode.com/GitHub_Trending/su/superpowers Superpowers 技能框架是给编码代理…

作者头像 李华
网站建设 2026/8/28 15:47:50

图模型盲图像去模糊:原理、实现与工程落地

简介:盲图像去模糊是计算机视觉中经典的病态逆问题,核心挑战在于未知模糊核条件下同步恢复清晰图像与退化过程。其技术本质依赖对图像结构的先验建模——从传统TV正则、CNN数据驱动,演进到基于图信号处理的自适应结构表征。图模型将像素建模为…

作者头像 李华
网站建设 2026/8/28 15:46:52

阴阳师自动化脚本:免费高效的百鬼夜行全自动方案

阴阳师自动化脚本:免费高效的百鬼夜行全自动方案 【免费下载链接】OnmyojiAutoScript Onmyoji Auto Script | 阴阳师脚本 项目地址: https://gitcode.com/gh_mirrors/on/OnmyojiAutoScript 百鬼夜行刷式神碎片,是阴阳师里为数不多让你离不开屏幕的…

作者头像 李华
网站建设 2026/8/28 15:46:48

MATLAB神经网络工具箱实战:数学建模中的快速预测模型构建与调优

1. 项目概述:从数学建模到神经网络预测 每年暑假,对于参加数学建模竞赛的同学来说,都是一段既紧张又充实的时光。集训的核心,就是把平时课本里那些抽象的理论,变成手里能解决实际问题的“武器”。在众多“武器”中&…

作者头像 李华