分段不可微函数上的SGD运行疑问及TensorFlow实现解析
为什么不可微代价函数下TensorFlow的SGD仍能运行?
核心结论
你的代码能正常执行,是因为TensorFlow遇到不可微操作时会将其梯度视为0,而非直接报错。SGD仅基于代价函数中可微分部分的梯度更新变量,不可微的人口密度部分不会参与参数更新。
具体运行逻辑拆解
1. 人口密度函数的梯度特性
你的concentrationLookup函数里,用Python原生int()做坐标离散化、再通过索引取网格值,这两步都是不可微分操作:
int()是阶跃式离散化,网格边界处导数不存在,非边界处导数为0;- 数组索引操作无法被TensorFlow的自动梯度机制追踪,反向传播时梯度直接为0。
因此,这部分对xVar和yVar的梯度始终是0,不会影响参数更新。
2. SGD的实际执行流程
每次调用opt.minimize()时:
- TensorFlow仅计算
tf_jobCost和tf_cityCost这两个二次函数的梯度(完全可微分); - 按照SGD公式更新
xVar和yVar:变量 = 变量 - 学习率 * 梯度; - 当坐标移动到网格边界时,人口密度值会突变,但因为这部分梯度为0,SGD不会停止,只会继续向工作地点+市中心的最优平衡点移动,直到这两部分的梯度接近0。
代码是否符合预期?
这取决于你的模拟目标:
- 如果希望人口密度因素影响居民选址,当前代码不符合预期——居民只会基于工作地点和市中心距离选点,完全不会避开高密度区域;
- 如果仅验证SGD在非光滑函数下的运行逻辑,代码能正常执行,但未体现人口密度的约束作用。
修正方案(让人口密度生效)
要让人口密度部分参与梯度更新,需将离散网格转换为连续可微函数,比如用双线性插值替代硬索引:
def concentrationLookup(self, x, y): grid_w = self.city.total_w / self.city.rows grid_h = self.city.total_h / self.city.cols # 计算浮点型网格坐标,避免直接取整 r_float = x / grid_w c_float = y / grid_h # 获取周围四个网格的索引(防止越界) r0 = tf.cast(tf.floor(r_float), tf.int32) r1 = tf.minimum(r0 + 1, self.city.rows - 1) c0 = tf.cast(tf.floor(c_float), tf.int32) c1 = tf.minimum(c0 + 1, self.city.cols - 1) # 计算插值权重 w_r = r_float - tf.cast(r0, tf.float32) w_c = c_float - tf.cast(c0, tf.float32) # 双线性插值计算连续密度值 val00 = self.city.grid[r0, c0] val01 = self.city.grid[r0, c1] val10 = self.city.grid[r1, c0] val11 = self.city.grid[r1, c1] val0 = val00 * (1 - w_c) + val01 * w_c val1 = val10 * (1 - w_c) + val11 * w_c return val0 * (1 - w_r) + val1 * w_r
修改后,人口密度函数变为连续可微,SGD会同时考虑工作距离、市中心距离、人口密度三个因素的梯度来更新居民选址。
内容的提问来源于stack exchange,提问作者savagekoala
相关产品推荐
相关产品推荐

