从零掌握逻辑回归:吴恩达《神经网络与深度学习》第二周核心精要
1. 逻辑回归:二分类问题的数学本质
第一次接触逻辑回归时,很多人会被"回归"二字误导——这明明是个分类算法啊!其实逻辑回归的核心思想,是用线性回归的变体来解决二分类问题。想象你正在教小朋友区分猫和狗:给每张图片打分,分数越高越可能是猫,最后设定一个阈值(比如0.5)来判断。
Sigmoid函数是这个过程的魔法转换器。当线性输出z=w^Tx+b穿过这个S形曲线时,任意实数都被压缩到(0,1)区间,正好对应概率值。我常把它比作温度计:z值极大时概率接近1(确定是猫),极小时接近0(确定不是猫),中间过渡平滑。
关键细节:Sigmoid导数σ'=σ(1-σ)这个特性在反向传播时特别有用,就像自带加速器的刹车系统,能快速调整参数方向。
损失函数的设计更是精妙。为什么不用简单的平方误差?因为那会导致优化地形坑坑洼洼(非凸),梯度下降容易卡在局部低谷。交叉熵损失就像智能导航仪,总能找到通往全局最优的平滑路径:
def loss(y_hat, y):
return - (y * np.log(y_hat) + (1-y) * np.log(1-y_hat))
当y=1时,-log(y_hat)惩罚低估;y=0时,-log(1-y_hat)惩罚高估。这种不对称设计让模型对错误预测非常敏感,学习效率大幅提升。
2. 梯度下降:参数优化的登山指南
理解梯度下降最形象的比喻是蒙眼下山:每次用脚试探周围坡度最陡的方向(梯度),然后迈出一小步(学习率α)。在猫狗分类任务中,参数w和b的更新公式看似简单:
w = w - α * dw # dw是损失对w的偏导
b = b - α * db # db是损失对b的偏导
但这里有三个实战经验值得分享:
- 学习率选择:α=0.01是常见起点,但我在猫狗数据集测试发现,0.0002收敛更快。太大(如0.1)会导致损失值震荡,太小(如0.00001)训练像蜗牛爬。
- 向量初始化:虽然w初始化为零在逻辑回归可行,但在深层网络中会引发对称性问题。后续课程会讲到Xavier初始化等高级技巧。
- 特征缩放:如果输入特征量纲差异大(如像素值0-255),建议归一化到[0,1],否则梯度下降路线会扭曲。
计算图是理解反向传播的利器。以J=3(a+bc)为例,从右向左逐层求导的链式法则,就像多米诺骨牌的反向连锁反应。在逻辑回归中,关键导数关系dz=a-y如此简洁,堪称数学之美。
3. 向量化编程:告别for循环的慢时代
在实现逻辑回归时,新手最容易犯的错误就是用for循环逐个处理样本。我曾在数据集上对比测试:当m=10,000时,向量化实现仅需1.9毫秒,而for循环版本要531毫秒——相差近300倍!
NumPy的广播机制是提速秘诀。比如计算Z=w^TX+b时:
Z = np.dot(W.T, X) + b # W.T是转置,X是(n_x,m)矩阵
这里b作为标量,会自动扩展成(1,m)向量。同样,求dw时用矩阵乘法代替循环:
dw = np.dot(X, dZ.T)/m # X是(n_x,m), dZ是(1,m)
广播规则的核心是维度对齐:
- (m,n) ±/* (1,n) → 复制m次
- (m,n) ±/* (m,1) → 复制n次
- (m,1) ±/* (1) → 双向复制
踩坑提醒:np.random.randn(5)生成的是秩为1的数组(既非行向量也非列向量),应用reshape(5,1)明确形状,否则可能引发难以调试的广播错误。
4. 实战:从零构建猫狗分类器
让我们用Python实现完整流程。首先加载数据:
def load_data():
cat_imgs = [cv2.resize(cv2.imread(f)/255, (64,64))
for f in glob('cats/*.jpg')[:100]]
dog_imgs = [cv2.resize(cv2.imread(f)/255, (64,64))
for f in glob('dogs/*.jpg')[:100]]
X = np.array([img.flatten() for img in cat_imgs + dog_imgs]).T
Y = np.array([[1]*100 + [0]*100])
return X, Y
接着实现核心算法:
def logistic_regression(X, Y, iterations=1000, lr=0.01):
n_x, m = X.shape
w, b = np.zeros((n_x,1)), 0
for i in range(iterations):
Z = np.dot(w.T, X) + b
A = 1/(1+np.exp(-Z))
dZ = A - Y
dw = np.dot(X, dZ.T)/m
db = np.sum(dZ)/m
w -= lr * dw
b -= lr * db
if i%100 == 0:
print(f"Iter {i}, cost: {np.mean(-Y*np.log(A)-(1-Y)*np.log(1-A))}")
return w, b
测试时发现准确率约57%,看似不高,但比随机猜测的50%已有提升。这是因为:
- 原始像素特征区分度低
- 逻辑回归是线性分类器
- 未进行超参数精细调优
这正为后续学习埋下伏笔:如何用更复杂的网络结构和优化技巧提升性能?答案将在接下来的课程中揭晓。
更多推荐
所有评论(0)