名称: tensorflow-neural-networks 描述: 使用TensorFlow构建和训练神经网络 允许工具: [Bash, Read]
TensorFlow神经网络
使用TensorFlow的高层Keras API和底层自定义实现构建和训练神经网络。这个技能覆盖从简单的顺序模型到复杂的自定义架构,包括多输出、自定义层和高级训练技术。
顺序模型与Keras
顺序API提供了通过线性堆叠层来构建神经网络的最简单方式。
基本图像分类
import tensorflow as tf
from tensorflow import keras
import numpy as np
# 加载MNIST数据集
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
# 预处理数据
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0
x_train = x_train.reshape(-1, 28 * 28)
x_test = x_test.reshape(-1, 28 * 28)
# 构建顺序模型
model = keras.Sequential([
keras.layers.Dense(128, activation='relu', input_shape=(784,)),
keras.layers.Dropout(0.2),
keras.layers.Dense(64, activation='relu'),
keras.layers.Dropout(0.2),
keras.layers.Dense(10, activation='softmax')
])
# 编译模型
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
# 显示模型架构
model.summary()
# 训练模型
history = model.fit(
x_train, y_train,
batch_size=32,
epochs=5,
validation_split=0.2,
verbose=1
)
# 评估模型
test_loss, test_accuracy = model.evaluate(x_test, y_test, verbose=0)
print(f"测试准确率: {test_accuracy:.4f}")
# 进行预测
predictions = model.predict(x_test[:5])
predicted_classes = np.argmax(predictions, axis=1)
print(f"预测类别: {predicted_classes}")
print(f"真实类别: {y_test[:5]}")
# 保存模型
model.save('mnist_model.h5')
# 加载模型
loaded_model = keras.models.load_model('mnist_model.h5')
卷积神经网络
def create_cnn_model(input_shape=(224, 224, 3), num_classes=1000):
"""创建用于图像分类的CNN模型。"""
model = tf.keras.Sequential([
# 块1
tf.keras.layers.Conv2D(64, (3, 3), activation='relu', padding='same',
input_shape=input_shape),
tf.keras.layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
tf.keras.layers.MaxPooling2D((2, 2)),
tf.keras.layers.BatchNormalization(),
# 块2
tf.keras.layers.Conv2D(128, (3, 3), activation='relu', padding='same'),
tf.keras.layers.Conv2D(128, (3, 3), activation='relu', padding='same'),
tf.keras.layers.MaxPooling2D((2, 2)),
tf.keras.layers.BatchNormalization(),
# 块3
tf.keras.layers.Conv2D(256, (3, 3), activation='relu', padding='same'),
tf.keras.layers.Conv2D(256, (3, 3), activation='relu', padding='same'),
tf.keras.layers.MaxPooling2D((2, 2)),
tf.keras.layers.BatchNormalization(),
# 分类头
tf.keras.layers.GlobalAveragePooling2D(),
tf.keras.layers.Dense(512, activation='relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(num_classes, activation='softmax')
])
return model
CIFAR-10 CNN架构
def generate_model():
return tf.keras.models.Sequential([
tf.keras.layers.Conv2D(32, (3, 3), padding='same', input_shape=x_train.shape[1:]),
tf.keras.layers.Activation('relu'),
tf.keras.layers.Conv2D(32, (3, 3)),
tf.keras.layers.Activation('relu'),
tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),
tf.keras.layers.Dropout(0.25),
tf.keras.layers.Conv2D(64, (3, 3), padding='same'),
tf.keras.layers.Activation('relu'),
tf.keras.layers.Conv2D(64, (3, 3)),
tf.keras.layers.Activation('relu'),
tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),
tf.keras.layers.Dropout(0.25),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(512),
tf.keras.layers.Activation('relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(10),
tf.keras.layers.Activation('softmax')
])
model = generate_model()
自定义层
通过子类化 tf.keras.layers.Layer 创建可重用的自定义层。
自定义密集层
import tensorflow as tf
class CustomDense(tf.keras.layers.Layer):
def __init__(self, units=32, activation=None):
super(CustomDense, self).__init__()
self.units = units
self.activation = tf.keras.activations.get(activation)
def build(self, input_shape):
"""创建层权重。"""
self.w = self.add_weight(
shape=(input_shape[-1], self.units),
initializer='glorot_uniform',
trainable=True,
name='kernel'
)
self.b = self.add_weight(
shape=(self.units,),
initializer='zeros',
trainable=True,
name='bias'
)
def call(self, inputs):
"""前向传播。"""
output = tf.matmul(inputs, self.w) + self.b
if self.activation is not None:
output = self.activation(output)
return output
def get_config(self):
"""启用序列化。"""
config = super().get_config()
config.update({
'units': self.units,
'activation': tf.keras.activations.serialize(self.activation)
})
return config
# 使用自定义组件
custom_model = tf.keras.Sequential([
CustomDense(64, activation='relu', input_shape=(10,)),
CustomDense(32, activation='relu'),
CustomDense(1, activation='sigmoid')
])
custom_model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
残差块
import tensorflow as tf
class ResidualBlock(tf.keras.layers.Layer):
def __init__(self, filters, kernel_size=3):
super(ResidualBlock, self).__init__()
self.conv1 = tf.keras.layers.Conv2D(filters, kernel_size, padding='same')
self.bn1 = tf.keras.layers.BatchNormalization()
self.conv2 = tf.keras.layers.Conv2D(filters, kernel_size, padding='same')
self.bn2 = tf.keras.layers.BatchNormalization()
self.activation = tf.keras.layers.Activation('relu')
self.add = tf.keras.layers.Add()
def call(self, inputs, training=False):
x = self.conv1(inputs)
x = self.bn1(x, training=training)
x = self.activation(x)
x = self.conv2(x)
x = self.bn2(x, training=training)
x = self.add([x, inputs]) # 残差连接
x = self.activation(x)
return x
使用TF NumPy的自定义投影层
class ProjectionLayer(tf.keras.layers.Layer):
"""使用TF NumPy的线性投影层。"""
def __init__(self, units):
super(ProjectionLayer, self).__init__()
self._units = units
def build(self, input_shape):
import tensorflow.experimental.numpy as tnp
stddev = tnp.sqrt(self._units).astype(tnp.float32)
initial_value = tnp.random.randn(input_shape[1], self._units).astype(
tnp.float32) / stddev
# 注意TF NumPy可以与tf.Variable互操作。
self.w = tf.Variable(initial_value, trainable=True)
def call(self, inputs):
import tensorflow.experimental.numpy as tnp
return tnp.matmul(inputs, self.w)
# 使用ndarray输入调用
layer = ProjectionLayer(2)
tnp_inputs = tnp.random.randn(2, 4).astype(tnp.float32)
print("输出:", layer(tnp_inputs))
# 使用tf.Tensor输入调用
tf_inputs = tf.random.uniform([2, 4])
print("
输出: ", layer(tf_inputs))
自定义模型
通过子类化 tf.keras.Model 构建复杂架构。
多任务模型
import tensorflow as tf
class MultiTaskModel(tf.keras.Model):
def __init__(self, num_classes_task1=10, num_classes_task2=5):
super(MultiTaskModel, self).__init__()
# 共享层
self.conv1 = tf.keras.layers.Conv2D(32, 3, activation='relu')
self.pool = tf.keras.layers.MaxPooling2D()
self.flatten = tf.keras.layers.Flatten()
self.shared_dense = tf.keras.layers.Dense(128, activation='relu')
# 任务特定层
self.task1_dense = tf.keras.layers.Dense(64, activation='relu')
self.task1_output = tf.keras.layers.Dense(num_classes_task1,
activation='softmax', name='task1')
self.task2_dense = tf.keras.layers.Dense(64, activation='relu')
self.task2_output = tf.keras.layers.Dense(num_classes_task2,
activation='softmax', name='task2')
def call(self, inputs, training=False):
# 共享特征提取
x = self.conv1(inputs)
x = self.pool(x)
x = self.flatten(x)
x = self.shared_dense(x)
# 任务1分支
task1 = self.task1_dense(x)
task1_output = self.task1_output(task1)
# 任务2分支
task2 = self.task2_dense(x)
task2_output = self.task2_output(task2)
return task1_output, task2_output
三层神经网络模块
class Model(tf.Module):
"""一个三层神经网络。"""
def __init__(self):
self.layer1 = Dense(128)
self.layer2 = Dense(32)
self.layer3 = Dense(NUM_CLASSES, use_relu=False)
def __call__(self, inputs):
x = self.layer1(inputs)
x = self.layer2(x)
return self.layer3(x)
@property
def params(self):
return self.layer1.params + self.layer2.params + self.layer3.params
循环神经网络
自定义GRU单元
import tensorflow.experimental.numpy as tnp
class GRUCell:
"""构建具有密集内部变换的传统GRU单元。
门控循环单元论文: https://arxiv.org/abs/1412.3555
"""
def __init__(self, n_units, forget_bias=0.0):
self._n_units = n_units
self._forget_bias = forget_bias
self._built = False
def __call__(self, inputs):
if not self._built:
self.build(inputs)
x, gru_state = inputs
# 在x和h的连接上应用密集层。
y = tnp.dot(tnp.concatenate([x, gru_state], axis=-1), self.w1) + self.b1
# 更新和重置门。
u, r = tnp.split(tf.sigmoid(y), 2, axis=-1)
# 候选。
c = tnp.dot(tnp.concatenate([x, r * gru_state], axis=-1), self.w2) + self.b2
new_gru_state = u * gru_state + (1 - u) * tnp.tanh(c)
return new_gru_state
def build(self, inputs):
# 状态最后一个维度必须为n_units。
assert inputs[1].shape[-1] == self._n_units
# 密集层输入是输入和一半GRU状态。
dense_shape = inputs[0].shape[-1] + self._n_units
self.w1 = tf.Variable(tnp.random.uniform(
-0.01, 0.01, (dense_shape, 2 * self._n_units)).astype(tnp.float32))
self.b1 = tf.Variable((tnp.random.randn(2 * self._n_units) * 1e-6 + self._forget_bias
).astype(tnp.float32))
self.w2 = tf.Variable(tnp.random.uniform(
-0.01, 0.01, (dense_shape, self._n_units)).astype(tnp.float32))
self.b2 = tf.Variable((tnp.random.randn(self._n_units) * 1e-6).astype(tnp.float32))
self._built = True
@property
def weights(self):
return (self.w1, self.b1, self.w2, self.b2)
自定义密集层实现
import tensorflow.experimental.numpy as tnp
class Dense:
def __init__(self, n_units, activation=None):
self._n_units = n_units
self._activation = activation
self._built = False
def __call__(self, inputs):
if not self._built:
self.build(inputs)
y = tnp.dot(inputs, self.w) + self.b
if self._activation != None:
y = self._activation(y)
return y
def build(self, inputs):
shape_w = (inputs.shape[-1], self._n_units)
lim = tnp.sqrt(6.0 / (shape_w[0] + shape_w[1]))
self.w = tf.Variable(tnp.random.uniform(-lim, lim, shape_w).astype(tnp.float32))
self.b = tf.Variable((tnp.random.randn(self._n_units) * 1e-6).astype(tnp.float32))
self._built = True
@property
def weights(self):
return (self.w, self.b)
顺序RNN模型
class Model:
def __init__(self, vocab_size, embedding_dim, rnn_units, forget_bias=0.0, stateful=False, activation=None):
self._embedding = Embedding(vocab_size, embedding_dim)
self._gru = GRU(rnn_units, forget_bias=forget_bias, stateful=stateful)
self._dense = Dense(vocab_size, activation=activation)
self._layers = [self._embedding, self._gru, self._dense]
self._built = False
def __call__(self, inputs):
if not self._built:
self.build(inputs)
xs = inputs
for layer in self._layers:
xs = layer(xs)
return xs
def build(self, inputs):
self._embedding.build(inputs)
self._gru.build(tf.TensorSpec(inputs.shape + (self._embedding._embedding_dim,), tf.float32))
self._dense.build(tf.TensorSpec(inputs.shape + (self._gru._cell._n_units,), tf.float32))
self._built = True
@property
def weights(self):
return [layer.weights for layer in self._layers]
@property
def state(self):
return self._gru.state
def create_state(self, *args):
self._gru.create_state(*args)
def reset_state(self, *args):
self._gru.reset_state(*args)
训练配置
模型参数
# 字符词汇表长度
vocab_size = len(vocab)
# 嵌入维度
embedding_dim = 256
# RNN单元数
rnn_units = 1024
# 批次大小
BATCH_SIZE = 64
# 用于洗牌数据集的缓冲区大小
BUFFER_SIZE = 10000
MNIST的训练常量
# 每个输入图像的大小,28 x 28像素
IMAGE_SIZE = 28 * 28
# 不同的数字标签数量,[0..9]
NUM_CLASSES = 10
# 每个训练批次中的示例数(步)
TRAIN_BATCH_SIZE = 100
# 运行的训练步数
TRAIN_STEPS = 1000
# 加载MNIST数据集。
train, test = tf.keras.datasets.mnist.load_data()
train_ds = tf.data.Dataset.from_tensor_slices(train).batch(TRAIN_BATCH_SIZE).repeat()
# 从原始数据转换为所需数据类型。
def cast(images, labels):
images = tf.cast(
tf.reshape(images, [-1, IMAGE_SIZE]), tf.float32)
labels = tf.cast(labels, tf.int64)
return (images, labels)
训练后量化
# 加载MNIST数据集
mnist = keras.datasets.mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
# 归一化输入图像,使每个像素值在0到1之间。
train_images = train_images / 255.0
test_images = test_images / 255.0
# 定义模型架构
model = keras.Sequential([
keras.layers.InputLayer(input_shape=(28, 28)),
keras.layers.Reshape(target_shape=(28, 28, 1)),
keras.layers.Conv2D(filters=12, kernel_size=(3, 3), activation=tf.nn.relu),
keras.layers.MaxPooling2D(pool_size=(2, 2)),
keras.layers.Flatten(),
keras.layers.Dense(10)
])
# 训练数字分类模型
model.compile(optimizer='adam',
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
model.fit(
train_images,
train_labels,
epochs=1,
validation_data=(test_images, test_labels)
)
何时使用此技能
使用tensorflow-neural-networks技能当您需要:
- 使用CNN构建图像分类模型
- 使用RNN或transformer创建文本处理模型
- 实现特定用例的自定义层架构
- 设计具有共享表示的多任务学习模型
- 为表格数据训练顺序模型
- 实现残差连接或跳过连接
- 为离散输入创建嵌入层
- 构建自动编码器或生成模型
- 使用自定义头微调预训练模型
- 在自定义架构中实现注意力机制
- 创建时间序列预测模型
- 设计强化学习策略网络
- 构建用于相似度学习的孪生网络
- 在层中实现自定义梯度计算
- 创建基于输入的动态架构模型
最佳实践
- 使用Keras顺序API处理简单架构 - 在转向函数式或子类化API之前,先从顺序API开始构建线性层堆叠。
- 利用预建层 - 在创建自定义层之前,先使用tf.keras.layers内置实现。
- 正确初始化权重 - 根据激活函数使用适当的初始化器(如glorot_uniform、he_normal)。
- 添加批归一化 - 在Conv2D/Dense层后放置BatchNormalization层以提高训练稳定性。
- 使用Dropout进行正则化 - 在全连接层应用Dropout层(0.2-0.5)以防止过拟合。
- 训练前编译模型 - 在fit()之前始终调用model.compile(),指定优化器、损失函数和指标。
- 监控验证指标 - 使用validation_split或validation_data在训练期间跟踪过拟合。
- 保存模型检查点 - 实现ModelCheckpoint回调以在训练期间保存最佳模型。
- 使用model.summary() - 在训练前验证架构和参数计数。
- 实现早停 - 添加EarlyStopping回调以防止不必要的训练迭代。
- 归一化输入数据 - 将像素值缩放到[0,1]或将特征标准化为均值=0、标准差=1。
- 使用适当的激活函数 - 隐藏层用ReLU,多分类用softmax,二分类用sigmoid。
- 设置正确的损失函数 - 整数标签用sparse_categorical_crossentropy,独热编码用categorical_crossentropy。
- 实现自定义get_config() - 在自定义层中重写get_config()以支持模型序列化。
- 在call()中使用训练参数 - 传递training标志以启用/禁用Dropout和BatchNorm行为。
常见陷阱
- 忘记归一化数据 - 未归一化的输入导致收敛缓慢和性能差。
- 标签使用错误损失函数 - 对整数标签使用categorical_crossentropy会导致错误。
- 缺少input_shape - 第一层需要input_shape参数以构建模型。
- 在小数据集上过拟合 - 添加Dropout、数据增强或减少模型容量。
- 学习率过高 - 导致训练不稳定和损失发散。
- 未洗牌训练数据 - 导致批次统计偏差和泛化能力差。
- 批次大小过小 - 在大数据集上导致梯度噪声和训练缓慢。
- 参数过多 - 大模型在有限数据上容易过拟合且训练缓慢。
- 深度网络中的梯度消失 - 使用残差连接或批归一化。
- 未使用验证数据 - 无法检测过拟合或调整超参数。
- 忘记设置training=False - Dropout/BatchNorm在推理期间行为不正确。
- 层维度不兼容 - 一个层的输出形状必须与下一层的输入匹配。
- 在访问权重前未调用build() - 自定义层需要适当初始化后才能访问权重。
- 使用错误优化器 - Adam通常表现良好,但某些任务用带动量的SGD。
- 忽略类别不平衡 - 为不平衡数据集实现类别权重或重采样。