当前位置:首页 > 焦点

基于tensorflow的bp网络模型

用TensorFlow搭一个BP网络,基于最直接的络模方式是继承tf.keras.Model,而不是基于硬写tf.Session那一套。后者在TF 2.x里已经边缘化了,络模除非你维护的基于是五年前的老代码。我见过不少教程还在用placeholder加feed_dict,络模跑一遍全是基于弃用警告,看着就头疼。络模

BP网络的基于核心就两件事:前向算输出,反向调权重。络模TensorFlow把反向传播这件事封装进了GradientTape,基于你只需要定义好层和损失函数,络模梯度怎么求、基于怎么更新,络模框架替你干了。基于下面这段代码是一个三层的BP网络,输入层两个节点,隐藏层八个,输出层一个,做二分类:

import tensorflow as tf class BPNet(tf.keras.Model): def __init__(self): super().__init__() self.hidden = tf.keras.layers.Dense(8, activation='relu') self.out = tf.keras.layers.Dense(1, activation='sigmoid') def call(self, x): return self.out(self.hidden(x)) model = BPNet() optimizer = tf.keras.optimizers.Adam(0.01) loss_fn = tf.keras.losses.BinaryCrossentropy() 

训练循环也很直白。拿一批数据,记录梯度,更新参数,反复迭代。注意GradientTape默认只追踪一次,循环里每步都要重新开上下文,忘了写with就会报NoneType错误——这个坑我踩过不止一次。

for epoch in range(1000): with tf.GradientTape() as tape: pred = model(x_train) loss = loss_fn(y_train, pred) grads = tape.gradient(loss, model.trainable_variables) optimizer.apply_gradients(zip(grads, model.trainable_variables)) 

隐藏层用relu,输出层用sigmoid,这是二分类的标配。有人喜欢输出层不加激活,配合from_logits=True的交叉熵损失,数值上更稳定。两种写法都对,但混着用会出问题——损失函数期望logits,你喂了概率值,梯度方向就偏了。

学习率别一上来就设0.1。Adam优化器默认0.001,对大多数BP网络够用。我试过在鸢尾花数据集上跑,0.01的Adam大概200轮收敛,换成SGD要调半天动量参数。这不是理论推导,是反复试出来的手感。

数据预处理经常被忽视。BP网络对输入尺度敏感,特征值范围差两个数量级,训练就震荡。用sklearn的StandardScaler过一遍,收敛速度肉眼可见地变快。TensorFlow本身不带这个,得自己接。

还有个细节:model.trainable_variables只在第一次call之后才有值。如果没跑前向就取梯度,拿到的是空列表,apply_gradients什么也不做,loss纹丝不动。新手卡在这里半天找不到原因,其实加一行model(x_train[:1])预热就行。

BP网络结构简单,但可调的东西不少。层数、节点数、激活函数、优化器、学习率、批大小,每个都会影响结果。没有万能配置,只能根据具体任务试。TensorFlow的好处是把这些组件都做成了标准模块,换一个参数就是改一行代码的事。

分享到: