
文章目录
-
- 1. 课前导读
-
- 1.1 本节课学习目标
- 1.2 知识重难点
- 1.3 学习前置条件
- 1.4 学完可掌握能力
- 1.5 行业应用场景
- 2. 核心理论精讲
-
- 2.1 TensorBoard 架构与日志
- 2.2 标量仪表板(Scalar)
- 2.3 计算图可视化
- 2.4 直方图仪表板(Histogram)
- 2.5 嵌入投影仪(Embedding Projector)
- 2.6 超参数调优(HParams)
- 2.7 性能分析(Profile)
- 3. 环境搭建与工具配置
- 4. 代码实战教学
-
- 4.1 基本用法:Keras回调
- 4.2 自定义训练循环中手动写入日志
- 4.3 可视化词向量(嵌入投影仪)
- 4.4 超参数调优(HParams)
- 4.5 性能分析(Profile)
- 5. 案例实操演练
-
- 5.1 数据加载与模型构建
- 5.2 超参数搜索与记录
- 5.3 结果分析
- 6. 常见坑点与排错总结
-
- 6.1 日志写入坑点
- 6.2 直方图理解误区
- 6.3 计算图可视化
- 6.4 嵌入投影仪
- 6.5 Profile
- 7. 知识点总结 + 课后作业
-
- 7.1 核心知识点梳理
- 7.2 基础作业
- 7.3 进阶实操作业
- 7.4 思考拓展题
- 🔗《TensorFlow2.x: 深度学习入门到高阶实战教程》系列课程导航
1. 课前导读
1.1 本节课学习目标
- 掌握TensorBoard的启动方式(命令行、Notebook内嵌)和日志目录结构。
- 学会在Keras的model.fit中使用TensorBoard回调,记录损失、自定义指标、学习率等。
- 理解计算图可视化的意义,能够查看模型的结构和每层的输入输出形状。
- 掌握直方图的使用,观察权重、梯度、激活值的分布变化,诊断梯度消失/爆炸。
- 学会使用嵌入投影仪(Embedding Projector)可视化高维特征向量(如词向量)。
- 能够使用HParams仪表板进行超参数调优对比。
- 使用Profile工具分析训练性能瓶颈(数据加载 vs 计算)。
1.2 知识重难点
| 重点 | TensorBoard回调配置;标量监控(损失、准确率);计算图导出;直方图的使用;嵌入投影仪的基本流程 |
| 难点 | 自定义训练循环中手动写入日志(tf.summary);Profile的解读与性能瓶颈定位;直方图与分布的对应关系 |
| 易混淆点 | tf.summary.scalar与tf.summary.histogram的作用域;日志目录每次运行需不同,否则曲线混淆;Profile仅适用于GPU/TPU |
1.3 学习前置条件
- 已完成前几课的模型训练(如CNN、RNN)。
- 能够搭建简单的Keras模型并训练。
- 了解词向量的基本概念(第30课)。
1.4 学完可掌握能力
- 独立使用TensorBoard监控训练过程,快速识别过拟合、欠拟合。
- 通过直方图观察权重初始化是否合理、是否出现梯度消失。
- 可视化模型结构,辅助文档撰写和调试。
- 使用嵌入投影仪分析分类器的特征表示质量。
- 定位数据加载瓶颈,优化训练速度。
1.5 行业应用场景
- 实验管理:对比多组超参数下的模型性能。
- 调试:观察梯度消失/爆炸,调整学习率或初始化。
- 可视化:向非技术人员展示模型结构和特征提取效果。
- 性能优化:Profile分析找出训练中的瓶颈步骤。
2. 核心理论精讲
2.1 TensorBoard 架构与日志
TensorBoard通过读取TensorFlow训练过程中生成的日志文件(events文件)来展示图表。日志中记录了标量、直方图、图结构、嵌入向量等数据。
日志目录结构:建议每次运行使用一个独立的子目录,如logs/run1、logs/run2,以便在TensorBoard中切换对比。
TensorBoard服务的启动:
tensorboard –logdir logs/
之后在浏览器中打开http://localhost:6006即可。
在Jupyter Notebook中可直接内嵌:
%load_ext tensorboard
%tensorboard ––logdir logs/
2.2 标量仪表板(Scalar)
记录随迭代次数(step)变化的数值,如损失、准确率、学习率。Keras的TensorBoard回调自动记录每epoch的损失和指标。自定义训练循环中需手动调用tf.summary.scalar()。
2.3 计算图可视化
TensorBoard可以展示模型的计算图(Graph),帮助理解数据流向、检查各层连接是否正确。对于Keras模型,只需在回调中设置histogram_freq=1(或任意>0)即可记录图。注意:图可视化可能非常复杂,可以使用logdir的write_graph参数控制。
2.4 直方图仪表板(Histogram)
记录张量值随时间的分布,例如权重、偏置、梯度、激活值。直方图可以观察:
- 权重是否趋于零(可能死亡)或过大(梯度爆炸)。
- 激活值是否饱和(Sigmoid/Tanh)。
- 梯度分布是否均匀。
在Keras中设置histogram_freq=1会记录每层权重的直方图;自定义训练循环中使用tf.summary.histogram()。
2.5 嵌入投影仪(Embedding Projector)
用于可视化高维向量(如词嵌入、特征向量)在低维空间(PCA、t-SNE)中的分布。需要提供一个元数据文件(TSV)定义标签。TensorBoard会自动降维并支持交互式探索。
2.6 超参数调优(HParams)
用于记录和对比多组超参数配置(学习率、批次大小、网络层数等)。需要定义超参数空间,并在每次运行时记录。TensorBoard会生成平行坐标图、散点图矩阵等,帮助选择最佳组合。
2.7 性能分析(Profile)
Profile工具可以捕获一个训练步骤中各操作的执行时间和内存分配。通过时间轴图可以识别CPU/GPU的利用率、数据流水线瓶颈(如输入管道空闲)。Profile需要启用tf.profiler。
3. 环境搭建与工具配置
沿用第37课环境,TensorBoard通常随TensorFlow一起安装。若未安装可手动:
pip install tensorboard
导入模块:
import tensorflow as tf
import numpy as np
import datetime
import os
from tensorflow.keras import layers, models, datasets, callbacks
4. 代码实战教学
4.1 基本用法:Keras回调
# 加载MNIST数据
(x_train, y_train), (x_test, y_test) = datasets.mnist.load_data()
x_train = x_train.reshape(–1, 28, 28, 1).astype(np.float32) / 255.0
x_test = x_test.reshape(–1, 28, 28, 1).astype(np.float32) / 255.0
y_train = tf.keras.utils.to_categorical(y_train, 10)
y_test = tf.keras.utils.to_categorical(y_test, 10)
# 简单模型
model = models.Sequential([
layers.Conv2D(32, 3, activation='relu', input_shape=(28,28,1)),
layers.MaxPooling2D(),
layers.Conv2D(64, 3, activation='relu'),
layers.MaxPooling2D(),
layers.Flatten(),
layers.Dense(64, activation='relu'),
layers.Dense(10, activation='softmax')
])
# 日志目录
log_dir = "logs/mnist/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
tensorboard_callback = callbacks.TensorBoard(
log_dir=log_dir,
histogram_freq=1, # 每个epoch记录直方图
write_graph=True, # 记录计算图
write_images=True, # 记录模型权重图片
update_freq='epoch', # 每个epoch更新
profile_batch=0 # 不启用profile(后面单独演示)
)
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
model.fit(x_train, y_train, epochs=5, batch_size=128, validation_split=0.1,
callbacks=[tensorboard_callback], verbose=1)
启动TensorBoard:
tensorboard –logdir logs/
在浏览器中查看Scalars、Graph、Histograms等。
4.2 自定义训练循环中手动写入日志
# 准备数据
train_ds = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(128).shuffle(10000).prefetch(tf.data.AUTOTUNE)
model = models.Sequential([
layers.Conv2D(32, 3, activation='relu', input_shape=(28,28,1)),
layers.MaxPooling2D(),
layers.Flatten(),
layers.Dense(10, activation='softmax')
])
optimizer = tf.keras.optimizers.Adam()
loss_fn = tf.keras.losses.CategoricalCrossentropy()
# 日志目录
current_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
train_log_dir = f'logs/custom/{current_time}/train'
val_log_dir = f'logs/custom/{current_time}/val'
train_summary_writer = tf.summary.create_file_writer(train_log_dir)
val_summary_writer = tf.summary.create_file_writer(val_log_dir)
epochs = 3
global_step = 0
for epoch in range(epochs):
# 训练
epoch_loss = 0.0
epoch_acc = 0.0
n_batches = 0
for x_batch, y_batch in train_ds:
with tf.GradientTape() as tape:
pred = model(x_batch, training=True)
loss = loss_fn(y_batch, pred)
grads = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(grads, model.trainable_variables))
epoch_loss += loss.numpy()
epoch_acc += tf.reduce_mean(tf.cast(tf.equal(tf.argmax(pred, axis=1), tf.argmax(y_batch, axis=1)), tf.float32)).numpy()
n_batches += 1
global_step += 1
train_loss = epoch_loss / n_batches
train_acc = epoch_acc / n_batches
# 写入训练指标
with train_summary_writer.as_default():
tf.summary.scalar('loss', train_loss, step=epoch)
tf.summary.scalar('accuracy', train_acc, step=epoch)
# 记录第一层卷积核直方图
conv1_weights = model.layers[0].get_weights()[0]
tf.summary.histogram('conv1/weights', conv1_weights, step=epoch)
# 验证(简单示例,取一个batch)
x_val, y_val = next(iter(val_ds)) # 实际应单独构建验证集
val_pred = model(x_val, training=False)
val_loss = loss_fn(y_val, val_pred)
val_acc = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(val_pred, axis=1), tf.argmax(y_val, axis=1)), tf.float32))
with val_summary_writer.as_default():
tf.summary.scalar('loss', val_loss, step=epoch)
tf.summary.scalar('accuracy', val_acc, step=epoch)
print(f"Epoch {epoch+1}: train_loss={train_loss:.4f}, val_acc={val_acc.numpy():.4f}")
4.3 可视化词向量(嵌入投影仪)
# 使用第30课的词向量示例
import tensorflow as tf
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
# 简单语料
sentences = ["cat sat on mat", "dog sat on log", "cat and dog"]
tokenizer = Tokenizer(num_words=10)
tokenizer.fit_on_texts(sentences)
sequences = tokenizer.texts_to_sequences(sentences)
# 训练一个简单的词嵌入模型
vocab_size = len(tokenizer.word_index) + 1
embedding_dim = 8
model_emb = models.Sequential([
layers.Embedding(vocab_size, embedding_dim, input_length=4),
layers.GlobalAveragePooling1D(),
layers.Dense(1, activation='sigmoid')
])
model_emb.compile(optimizer='adam', loss='binary_crossentropy')
# 随机标签
labels = np.random.randint(0, 2, size=len(sentences))
model_emb.fit(pad_sequences(sequences, maxlen=4), labels, epochs=1, verbose=0)
# 提取词嵌入矩阵
embedding_matrix = model_emb.layers[0].get_weights()[0] # (vocab_size, 8)
# 创建日志目录和元数据文件
log_dir = "logs/embeddings/"
os.makedirs(log_dir, exist_ok=True)
# 保存元数据(词与标签)
with open(os.path.join(log_dir, 'metadata.tsv'), 'w', encoding='utf-8') as f:
f.write("Word\\tIndex\\n")
for word, idx in tokenizer.word_index.items():
f.write(f"{word}\\t{idx}\\n")
# 保存嵌入向量(TSV格式)
np.savetxt(os.path.join(log_dir, 'vectors.tsv'), embedding_matrix, delimiter='\\t')
# 配置嵌入投影仪(需在TensorBoard中加载)
# 启动TensorBoard后,进入"Projector"页面,点击"Load",选择vectors.tsv和metadata.tsv即可
4.4 超参数调优(HParams)
from tensorboard.plugins.hparams import api as hp
# 定义超参数空间
HP_LEARNING_RATE = hp.HParam('learning_rate', hp.RealInterval(1e-4, 1e-2))
HP_NUM_UNITS = hp.HParam('num_units', hp.IntInterval(32, 128))
HP_DROPOUT = hp.HParam('dropout', hp.RealInterval(0.1, 0.5))
# 日志目录
hparams_log_dir = 'logs/hparams/'
def train_with_hparams(hparams):
model = models.Sequential([
layers.Dense(hparams[HP_NUM_UNITS], activation='relu', input_shape=(784,)),
layers.Dropout(hparams[HP_DROPOUT]),
layers.Dense(10, activation='softmax')
])
model.compile(optimizer=tf.keras.optimizers.Adam(hparams[HP_LEARNING_RATE]),
loss='sparse_categorical_crossentropy', metrics=['accuracy'])
# 使用HParams回调
callback = hp.KerasCallback(hparams_log_dir, hparams)
model.fit(x_train.reshape(–1,784), y_train, epochs=3, batch_size=128, callbacks=[callback], verbose=0)
_, accuracy = model.evaluate(x_test.reshape(–1,784), y_test, verbose=0)
return accuracy
# 网格搜索
session_num = 0
for lr in [0.001, 0.0005]:
for units in [64, 128]:
for dropout in [0.2, 0.4]:
hparams = {
HP_LEARNING_RATE: lr,
HP_NUM_UNITS: units,
HP_DROPOUT: dropout,
}
accuracy = train_with_hparams(hparams)
print(f"Session {session_num}: lr={lr}, units={units}, dropout={dropout}, accuracy={accuracy:.4f}")
session_num += 1
# 启动TensorBoard –logdir logs/hparams 后,点击HParams标签查看对比图
4.5 性能分析(Profile)
# 使用Keras回调的方式启用profile
tensorboard_callback = callbacks.TensorBoard(
log_dir='logs/profile',
profile_batch='10,20' # 对第10-20个batch进行profile
)
model.fit(x_train, y_train, epochs=1, batch_size=128, callbacks=[tensorboard_callback], verbose=0)
# 在TensorBoard中查看Profile标签页
# 或在自定义训练循环中使用tf.profiler
import tensorflow as tf
tf.profiler.experimental.start('logs/profile')
# 训练若干step
for step, (x_batch, y_batch) in enumerate(train_ds):
if step >= 10: break
# 训练代码…
tf.profiler.experimental.stop()
5. 案例实操演练
案例:使用TensorBoard监控ResNet在CIFAR-10上的训练过程,并通过HParams对比不同初始学习率
5.1 数据加载与模型构建
(x_train, y_train), (x_test, y_test) = datasets.cifar10.load_data()
x_train = x_train.astype(np.float32) / 255.0
x_test = x_test.astype(np.float32) / 255.0
y_train = tf.keras.utils.to_categorical(y_train, 10)
y_test = tf.keras.utils.to_categorical(y_test, 10)
def build_resnet20(input_shape=(32,32,3), num_classes=10):
inputs = layers.Input(shape=input_shape)
# 简化版ResNet
x = layers.Conv2D(16, 3, padding='same')(inputs)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
# 添加残差块(省略详细实现)
x = layers.GlobalAveragePooling2D()(x)
outputs = layers.Dense(num_classes, activation='softmax')(x)
model = tf.keras.Model(inputs, outputs)
return model
# 日志目录
log_dir = 'logs/resnet_cifar/'
5.2 超参数搜索与记录
# 使用HParams记录不同学习率的结果
from tensorboard.plugins.hparams import api as hp
HP_LR = hp.HParam('learning_rate', hp.RealInterval(1e-4, 1e-2))
hparams_log_dir = 'logs/hparam_resnet/'
def train_and_log(lr):
model = build_resnet20()
model.compile(optimizer=tf.keras.optimizers.Adam(lr),
loss='categorical_crossentropy', metrics=['accuracy'])
tensorboard_cb = callbacks.TensorBoard(log_dir=hparams_log_dir + f'lr_{lr}', histogram_freq=1)
hparams_cb = hp.KerasCallback(hparams_log_dir, {HP_LR: lr})
history = model.fit(x_train, y_train, epochs=10, batch_size=128, validation_split=0.1,
callbacks=[tensorboard_cb, hparams_cb], verbose=0)
val_acc = history.history['val_accuracy'][–1]
return val_acc
for lr in [0.001, 0.0005, 0.0001]:
acc = train_and_log(lr)
print(f"LR={lr}, val_acc={acc:.4f}")
5.3 结果分析
启动TensorBoard,在HParams页面中可以对比不同学习率的损失/准确率曲线,以及平行坐标图,直观选择最佳超参数。
6. 常见坑点与排错总结
6.1 日志写入坑点
-
坑1:每次训练使用相同的日志目录,导致TensorBoard中曲线混乱(多条曲线重叠)。
- 解决:使用时间戳或运行ID创建子目录:log_dir = f'logs/run_{datetime.datetime.now().strftime("%Y%m%d-%H%M%S")}'。
-
坑2:自定义训练循环中忘记刷新summary writer,导致日志没有及时写入磁盘。
- 解决:使用tf.summary.flush(),或writer默认每120秒自动刷新。
6.2 直方图理解误区
-
坑3:直方图只显示权重的分布,但无法直接看出梯度消失。需结合梯度的直方图(需手动记录梯度)。
- 建议:在自定义循环中记录tf.summary.histogram('gradients/layer', grad)。
-
坑4:直方图横轴取值范围过大,不易观察细节。可点击图表选择对数坐标或调整范围。
6.3 计算图可视化
-
坑5:模型图过于复杂,难以阅读。可以使用tf.keras.utils.plot_model生成静态图,或使用TensorBoard的图面板折叠节点。
-
坑6:write_graph=True导致日志文件过大,尤其是在保存多个checkpoint时。
- 解决:只在需要查看图时开启,平时关闭。
6.4 嵌入投影仪
-
坑7:嵌入投影仪要求向量维度较低(<5000维),否则t-SNE/PCA计算很慢。建议先降维再记录。
-
坑8:元数据文件(TSV)的编码问题,中文可能乱码。使用UTF-8编码,且在TensorBoard中需支持中文。
6.5 Profile
- 坑9:Profile在CPU上意义不大,建议在GPU上运行时使用。
- 坑10:profile_batch范围过大会导致大量额外开销,仅需分析少量batch即可。
7. 知识点总结 + 课后作业
7.1 核心知识点梳理
- 标量仪表板:记录损失、准确率等,Keras回调自动;自定义循环用tf.summary.scalar。
- 计算图:write_graph开启后,可查看模型结构。
- 直方图:记录权重、梯度分布,诊断梯度消失/爆炸。
- 嵌入投影仪:可视化高维向量,需提供元数据和向量文件。
- HParams:对比超参数,辅助调优。
- Profile:分析性能瓶颈。
7.2 基础作业
7.3 进阶实操作业
任务:使用HParams和TensorBoard对比不同网络深度的性能
- 构建3个不同深度的全连接网络(2层、4层、6层),固定其他超参数。
- 使用HParams记录深度(作为分类超参数),并记录每个epoch的验证损失。
- 在TensorBoard中生成平行坐标图,分析深度与最终准确率的关系。
- 同时记录训练时间和参数量,探讨深度与训练速度的权衡。
7.4 思考拓展题
在自定义训练循环中,如果要记录每个batch的损失,而不是每个epoch,应如何实现?这种细粒度记录有何优缺点?
嵌入投影仪中的t-SNE降维结果具有随机性,如何让多次运行结果可比较?尝试固定随机种子。
假设你看到训练过程中权重的直方图逐渐向零收缩,这会是什么问题?如何解决?
下一课预告:自定义层与自定义损失函数——我们将学习如何扩展TensorFlow,编写满足特殊需求的层和损失函数,提升模型的灵活性。
🔗《TensorFlow2.x: 深度学习入门到高阶实战教程》系列课程导航
去订阅
第一部分:基础入门(1-10 课) 第二部分:神经网络核心(11-25 课) 第三部分:进阶网络与框架高阶(26-40 课) 第四部分:企业实战与项目落地(41-50 课)
🌟 感谢您耐心阅读到这里! 💡 如果本文对您有所启发欢迎: 👍 点赞📌 收藏 📤 分享给更多需要的伙伴。 🗣️ 期待在评论区看到您的想法, 共同进步。 🔔 关注我,持续获取更多干货内容~ 🤗 我们下篇文章见~
网硕互联帮助中心


评论前必须登录!
注册