深度学习与神经网络实践:从原理到应用
概述
深度学习是机器学习的一个子领域,它使用多层神经网络来学习数据的层次化表示。近年来,深度学习在图像识别、自然语言处理、语音识别等领域取得了突破性进展。
本教程将带你深入理解深度学习的核心原理,并通过 4 个完整的实战案例,掌握使用 Python 和 TensorFlow/Keras 构建神经网络的能力。无论你是机器学习初学者还是有一定经验的开发者,本教程都将帮助你建立扎实的深度学习基础。
学完本教程,你将能够:
- 理解神经网络的工作原理
- 使用 Keras 构建和训练深度学习模型
- 应用 CNN 处理图像数据
- 使用 RNN/LSTM 处理序列数据
- 运用迁移学习解决实际问题
第一章:深度学习基础
1.1 什么是深度学习?
深度学习是机器学习的一个分支,它使用包含多个隐藏层的神经网络来学习数据的抽象表示。"深度"指的是网络的层数。
深度学习 vs 传统机器学习:
| 传统机器学习 | 深度学习 |
|---|---|
| 需要人工特征工程 | 自动学习特征表示 |
| 适合结构化数据 | 擅长非结构化数据(图像、文本、音频) |
| 数据量要求相对较低 | 需要大量训练数据 |
| 模型相对简单 | 模型复杂,参数量大 |
| 可解释性较好 | 可解释性较差(黑盒) |
1.2 神经网络基础
1.2.1 神经元模型
神经元是神经网络的基本单元,它模拟生物神经元的工作方式:
输入 (x1, x2, ..., xn)
↓
权重 (w1, w2, ..., wn)
↓
加权求和:z = w1*x1 + w2*x2 + ... + wn*xn + b
↓
激活函数:a = f(z)
↓
输出数学表达:
z = w·x + b
a = f(z)其中:
w是权重向量x是输入向量b是偏置f是激活函数
1.2.2 常用激活函数
python
import numpy as np
import matplotlib.pyplot as plt
# 定义激活函数
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def relu(x):
return np.maximum(0, x)
def tanh(x):
return np.tanh(x)
def leaky_relu(x, alpha=0.01):
return np.where(x > 0, x, alpha * x)
def softmax(x):
exp_x = np.exp(x - np.max(x, axis=1, keepdims=True))
return exp_x / np.sum(exp_x, axis=1, keepdims=True)
# 可视化
x = np.linspace(-5, 5, 100)
plt.figure(figsize=(15, 10))
plt.subplot(2, 3, 1)
plt.plot(x, sigmoid(x), linewidth=2)
plt.title('Sigmoid')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.grid(True, alpha=0.3)
plt.axhline(y=0, color='k', linestyle='-', linewidth=0.5)
plt.axvline(x=0, color='k', linestyle='-', linewidth=0.5)
plt.subplot(2, 3, 2)
plt.plot(x, relu(x), linewidth=2)
plt.title('ReLU')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.grid(True, alpha=0.3)
plt.axhline(y=0, color='k', linestyle='-', linewidth=0.5)
plt.axvline(x=0, color='k', linestyle='-', linewidth=0.5)
plt.subplot(2, 3, 3)
plt.plot(x, tanh(x), linewidth=2)
plt.title('Tanh')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.grid(True, alpha=0.3)
plt.axhline(y=0, color='k', linestyle='-', linewidth=0.5)
plt.axvline(x=0, color='k', linestyle='-', linewidth=0.5)
plt.subplot(2, 3, 4)
plt.plot(x, leaky_relu(x), linewidth=2)
plt.title('Leaky ReLU')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.grid(True, alpha=0.3)
plt.axhline(y=0, color='k', linestyle='-', linewidth=0.5)
plt.axvline(x=0, color='k', linestyle='-', linewidth=0.5)
plt.tight_layout()
plt.savefig('activation_functions.png', dpi=150)
plt.show()激活函数对比:
| 函数 | 公式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Sigmoid | 1/(1+e^-x) | 输出 0-1,适合概率 | 梯度消失,输出非零中心 | 二分类输出层 |
| Tanh | (e^x-e^-x)/(e^x+e^-x) | 输出 -1 到 1,零中心 | 梯度消失 | 隐藏层 |
| ReLU | max(0,x) | 计算简单,缓解梯度消失 | Dead ReLU 问题 | 默认隐藏层 |
| Leaky ReLU | max(αx,x) | 解决 Dead ReLU | 需要选择α | 隐藏层替代 ReLU |
| Softmax | e^xi/Σe^xj | 输出概率分布 | 仅用于输出层 | 多分类输出层 |
1.2.3 网络结构
典型的前馈神经网络结构:
输入层 → 隐藏层 1 → 隐藏层 2 → ... → 输出层
(x) (h1) (h2) (y)关键概念:
- 输入层:接收原始数据
- 隐藏层:进行特征变换(可以有多个)
- 输出层:产生最终预测
- 深度:隐藏层的数量
- 宽度:每层的神经元数量
1.3 神经网络训练过程
1.3.1 损失函数
损失函数衡量模型预测与真实值之间的差距。
常用损失函数:
python
# 均方误差(回归)
def mse_loss(y_true, y_pred):
return np.mean((y_true - y_pred) ** 2)
# 交叉熵(分类)
def cross_entropy_loss(y_true, y_pred):
epsilon = 1e-15
y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
return -np.mean(y_true * np.log(y_pred))
# 二分类交叉熵
def binary_cross_entropy(y_true, y_pred):
epsilon = 1e-15
y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))1.3.2 反向传播
反向传播是训练神经网络的核心算法,它通过链式法则计算损失函数对每个参数的梯度。
反向传播步骤:
- 前向传播:计算每一层的输出
- 计算损失:比较预测值与真实值
- 反向传播:从输出层向输入层传播误差
- 更新权重:使用梯度下降更新参数
1.3.3 优化算法
python
# 随机梯度下降(SGD)
def sgd_update(w, grad, learning_rate):
return w - learning_rate * grad
# 带动量的 SGD
def momentum_update(w, grad, v, learning_rate, momentum=0.9):
v_new = momentum * v - learning_rate * grad
w_new = w + v_new
return w_new, v_new
# Adam 优化器
def adam_update(w, grad, m, v, t, learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8):
m_new = beta1 * m + (1 - beta1) * grad
v_new = beta2 * v + (1 - beta2) * (grad ** 2)
m_hat = m_new / (1 - beta1 ** t)
v_hat = v_new / (1 - beta2 ** t)
w_new = w - learning_rate * m_hat / (np.sqrt(v_hat) + epsilon)
return w_new, m_new, v_new优化器对比:
| 优化器 | 特点 | 适用场景 |
|---|---|---|
| SGD | 简单,需要手动调学习率 | 基础理解,小数据集 |
| SGD+Momentum | 加速收敛,减少震荡 | 一般场景 |
| Adam | 自适应学习率,收敛快 | 默认选择 |
| AdamW | Adam + 权重衰减 | 需要正则化时 |
| RMSprop | 适合 RNN | 序列模型 |
1.4 防止过拟合技术
1.4.1 Dropout
python
def dropout(x, dropout_rate=0.5, training=True):
if training:
mask = np.random.binomial(1, 1 - dropout_rate, x.shape) / (1 - dropout_rate)
return x * mask
return x1.4.2 批量归一化(Batch Normalization)
python
def batch_normalization(x, gamma, beta, epsilon=1e-5):
mean = np.mean(x, axis=0)
var = np.var(x, axis=0)
x_norm = (x - mean) / np.sqrt(var + epsilon)
return gamma * x_norm + beta1.4.3 早停(Early Stopping)
监控验证集损失,当不再改善时停止训练。
1.4.4 数据增强
通过变换(旋转、翻转、缩放等)增加训练数据多样性。
第二章:环境搭建与 Keras 基础
2.1 安装 TensorFlow 和 Keras
bash
# 安装 TensorFlow(包含 Keras)
pip install tensorflow
# 验证安装
python -c "import tensorflow as tf; print(tf.__version__)"2.2 Keras 快速入门
python
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, models
import numpy as np
print(f"TensorFlow 版本:{tf.__version__}")
print(f"GPU 可用:{tf.config.list_physical_devices('GPU')}")
# 构建简单模型
model = models.Sequential([
layers.Dense(64, activation='relu', input_shape=(100,)),
layers.Dropout(0.5),
layers.Dense(32, activation='relu'),
layers.Dense(10, activation='softmax')
])
# 编译模型
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
# 模型摘要
model.summary()第三章:实战案例
案例 1:手写数字识别(全连接神经网络)
使用 MNIST 数据集,构建全连接神经网络进行手写数字识别。
3.1.1 数据加载与预处理
python
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, models
import numpy as np
import matplotlib.pyplot as plt
# 加载 MNIST 数据集
print("加载 MNIST 数据集...")
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
print(f"训练集形状:{x_train.shape}")
print(f"测试集形状:{x_test.shape}")
print(f"标签形状:{y_train.shape}, {y_test.shape}")
# 数据可视化
fig, axes = plt.subplots(5, 10, figsize=(15, 5))
for i, ax in enumerate(axes.flat):
ax.imshow(x_train[i], cmap='gray')
ax.set_title(f'标签:{y_train[i]}')
ax.axis('off')
plt.tight_layout()
plt.savefig('mnist_samples_fc.png', dpi=150)
plt.show()
# 数据预处理
# 归一化到 0-1
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0
# 展平图像(28x28 → 784)
x_train_flat = x_train.reshape(-1, 28 * 28)
x_test_flat = x_test.reshape(-1, 28 * 28)
print(f"展平后训练集:{x_train_flat.shape}")
print(f"展平后测试集:{x_test_flat.shape}")3.1.2 构建和训练模型
python
# 构建全连接神经网络
model = models.Sequential([
layers.Input(shape=(784,)),
layers.Dense(512, activation='relu'),
layers.BatchNormalization(),
layers.Dropout(0.3),
layers.Dense(256, activation='relu'),
layers.BatchNormalization(),
layers.Dropout(0.3),
layers.Dense(128, activation='relu'),
layers.BatchNormalization(),
layers.Dropout(0.2),
layers.Dense(10, activation='softmax')
])
# 编译模型
model.compile(
optimizer=keras.optimizers.Adam(learning_rate=0.001),
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
# 模型摘要
model.summary()
# 训练模型
print("\n开始训练...")
history = model.fit(
x_train_flat, y_train,
batch_size=128,
epochs=20,
validation_split=0.1,
verbose=1
)
# 保存模型
model.save('mnist_fc_model.h5')
print("模型已保存为 mnist_fc_model.h5")3.1.3 模型评估与可视化
python
# 测试集评估
test_loss, test_acc = model.evaluate(x_test_flat, y_test, verbose=0)
print(f"\n测试集准确率:{test_acc:.4f}")
print(f"测试集损失:{test_loss:.4f}")
# 训练历史可视化
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[0].plot(history.history['accuracy'], label='训练准确率', linewidth=2)
axes[0].plot(history.history['val_accuracy'], label='验证准确率', linewidth=2)
axes[0].set_xlabel('Epoch')
axes[0].set_ylabel('Accuracy')
axes[0].set_title('训练准确率变化')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
axes[1].plot(history.history['loss'], label='训练损失', linewidth=2)
axes[1].plot(history.history['val_loss'], label='验证损失', linewidth=2)
axes[1].set_xlabel('Epoch')
axes[1].set_ylabel('Loss')
axes[1].set_title('训练损失变化')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('mnist_fc_training_history.png', dpi=150)
plt.show()
# 混淆矩阵
from sklearn.metrics import confusion_matrix, classification_report
import seaborn as sns
y_pred = model.predict(x_test_flat)
y_pred_classes = np.argmax(y_pred, axis=1)
cm = confusion_matrix(y_test, y_pred_classes)
plt.figure(figsize=(10, 8))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=range(10), yticklabels=range(10))
plt.xlabel('预测标签')
plt.ylabel('真实标签')
plt.title('混淆矩阵')
plt.tight_layout()
plt.savefig('mnist_fc_confusion_matrix.png', dpi=150)
plt.show()
print("\n分类报告:")
print(classification_report(y_test, y_pred_classes))
# 错误样本分析
errors = y_pred_classes != y_test
error_indices = np.where(errors)[0][:10]
fig, axes = plt.subplots(2, 5, figsize=(15, 4))
for i, ax in enumerate(axes.flat):
if i < len(error_indices):
idx = error_indices[i]
ax.imshow(x_test[idx], cmap='gray')
ax.set_title(f'真实:{y_test[idx]}, 预测:{y_pred_classes[idx]}')
ax.axis('off')
plt.tight_layout()
plt.savefig('mnist_fc_errors.png', dpi=150)
plt.show()案例要点总结:
- 全连接网络适合简单图像分类
- BatchNormalization 加速训练并提高稳定性
- Dropout 有效防止过拟合
- 学习率调优对训练效果影响大
案例 2:猫狗分类(卷积神经网络 CNN)
使用 CNN 进行图像分类,展示卷积神经网络的强大能力。
3.2.1 数据准备
python
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, models
import numpy as np
import matplotlib.pyplot as plt
import os
# 方法 1:使用 TensorFlow 数据集
print("加载猫狗数据集...")
# 如果没有真实数据,使用模拟数据演示
# 实际使用时替换为真实数据路径
# train_dir = 'data/dogs_vs_cats/train'
# test_dir = 'data/dogs_vs_cats/test'
# 使用 CIFAR-10 数据集作为替代(包含动物类别)
(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()
# CIFAR-10 类别
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck']
# 筛选猫和狗(类别 3 和 5)
cat_mask = y_train[:, 0] == 3
dog_mask = y_train[:, 0] == 5
X_train = np.vstack([x_train[cat_mask], x_train[dog_mask]])
y_train = np.vstack([y_train[cat_mask], y_train[dog_mask]])
cat_mask = y_test[:, 0] == 3
dog_mask = y_test[:, 0] == 5
X_test = np.vstack([x_test[cat_mask], x_test[dog_mask]])
y_test = np.vstack([y_test[cat_mask], y_test[dog_mask]])
# 重新标记:猫=0, 狗=1
y_train = (y_train[:, 0] == 5).astype(int)
y_test = (y_test[:, 0] == 5).astype(int)
print(f"训练集:{X_train.shape}, 标签:{y_train.shape}")
print(f"测试集:{X_test.shape}, 标签:{y_test.shape}")
# 数据可视化
fig, axes = plt.subplots(5, 10, figsize=(15, 8))
for i, ax in enumerate(axes.flat):
if i < len(X_train):
ax.imshow(X_train[i])
ax.set_title(f'{"狗" if y_train[i] == 1 else "猫"}')
ax.axis('off')
plt.tight_layout()
plt.savefig('cats_dogs_samples.png', dpi=150)
plt.show()
# 数据预处理
X_train = X_train.astype('float32') / 255.0
X_test = X_test.astype('float32') / 255.0
print(f"数据归一化完成,值范围:[{X_train.min():.2f}, {X_train.max():.2f}]")3.2.2 构建 CNN 模型
python
# 构建 CNN 模型
def create_cnn_model(input_shape=(32, 32, 3)):
model = models.Sequential([
# 第一组卷积层
layers.Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=input_shape),
layers.BatchNormalization(),
layers.Conv2D(32, (3, 3), activation='relu', padding='same'),
layers.BatchNormalization(),
layers.MaxPooling2D((2, 2)),
layers.Dropout(0.25),
# 第二组卷积层
layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
layers.BatchNormalization(),
layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
layers.BatchNormalization(),
layers.MaxPooling2D((2, 2)),
layers.Dropout(0.25),
# 第三组卷积层
layers.Conv2D(128, (3, 3), activation='relu', padding='same'),
layers.BatchNormalization(),
layers.Conv2D(128, (3, 3), activation='relu', padding='same'),
layers.BatchNormalization(),
layers.MaxPooling2D((2, 2)),
layers.Dropout(0.25),
# 全连接层
layers.Flatten(),
layers.Dense(256, activation='relu'),
layers.BatchNormalization(),
layers.Dropout(0.5),
layers.Dense(1, activation='sigmoid')
])
return model
model = create_cnn_model()
# 编译模型
model.compile(
optimizer=keras.optimizers.Adam(learning_rate=0.001),
loss='binary_crossentropy',
metrics=['accuracy', keras.metrics.Precision(), keras.metrics.Recall()]
)
# 模型摘要
model.summary()
# 回调函数
callbacks = [
keras.callbacks.EarlyStopping(
monitor='val_loss',
patience=5,
restore_best_weights=True
),
keras.callbacks.ReduceLROnPlateau(
monitor='val_loss',
factor=0.5,
patience=3,
min_lr=1e-6
),
keras.callbacks.ModelCheckpoint(
'best_cnn_model.h5',
monitor='val_accuracy',
save_best_only=True
)
]3.2.3 训练与评估
python
# 训练模型
print("\n开始训练 CNN 模型...")
history = model.fit(
X_train, y_train,
batch_size=64,
epochs=50,
validation_split=0.2,
callbacks=callbacks,
verbose=1
)
# 加载最佳模型
best_model = keras.models.load_model('best_cnn_model.h5')
# 测试集评估
test_results = best_model.evaluate(X_test, y_test, verbose=0)
print(f"\n测试集结果:")
print(f" 损失:{test_results[0]:.4f}")
print(f" 准确率:{test_results[1]:.4f}")
print(f" 精确率:{test_results[2]:.4f}")
print(f" 召回率:{test_results[3]:.4f}")
# 训练历史可视化
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
axes[0, 0].plot(history.history['accuracy'], label='训练准确率', linewidth=2)
axes[0, 0].plot(history.history['val_accuracy'], label='验证准确率', linewidth=2)
axes[0, 0].set_xlabel('Epoch')
axes[0, 0].set_ylabel('Accuracy')
axes[0, 0].set_title('准确率变化')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)
axes[0, 1].plot(history.history['loss'], label='训练损失', linewidth=2)
axes[0, 1].plot(history.history['val_loss'], label='验证损失', linewidth=2)
axes[0, 1].set_xlabel('Epoch')
axes[0, 1].set_ylabel('Loss')
axes[0, 1].set_title('损失变化')
axes[0, 1].legend()
axes[0, 1].grid(True, alpha=0.3)
axes[1, 0].plot(history.history['precision_1'], label='训练精确率', linewidth=2)
axes[1, 0].plot(history.history['val_precision_1'], label='验证精确率', linewidth=2)
axes[1, 0].set_xlabel('Epoch')
axes[1, 0].set_ylabel('Precision')
axes[1, 0].set_title('精确率变化')
axes[1, 0].legend()
axes[1, 0].grid(True, alpha=0.3)
axes[1, 1].plot(history.history['recall_1'], label='训练召回率', linewidth=2)
axes[1, 1].plot(history.history['val_recall_1'], label='验证召回率', linewidth=2)
axes[1, 1].set_xlabel('Epoch')
axes[1, 1].set_ylabel('Recall')
axes[1, 1].set_title('召回率变化')
axes[1, 1].legend()
axes[1, 1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('cnn_training_history.png', dpi=150)
plt.show()
# 预测可视化
y_pred_proba = best_model.predict(X_test)
y_pred = (y_pred_proba > 0.5).astype(int)
# 查看正确和错误预测
correct_indices = np.where(y_pred[:, 0] == y_test)[0][:5]
wrong_indices = np.where(y_pred[:, 0] != y_test)[0][:5]
fig, axes = plt.subplots(2, 5, figsize=(20, 5))
for i, ax in enumerate(axes[0]):
if i < len(correct_indices):
idx = correct_indices[i]
ax.imshow(X_test[idx])
confidence = y_pred_proba[idx][0] if y_test[idx] == 1 else 1 - y_pred_proba[idx][0]
ax.set_title(f'✓ 正确\n置信度:{confidence:.2f}')
ax.axis('off')
for i, ax in enumerate(axes[1]):
if i < len(wrong_indices):
idx = wrong_indices[i]
ax.imshow(X_test[idx])
confidence = y_pred_proba[idx][0]
ax.set_title(f'✗ 错误\n置信度:{confidence:.2f}')
ax.axis('off')
plt.tight_layout()
plt.savefig('cnn_predictions.png', dpi=150)
plt.show()案例要点总结:
- CNN 通过卷积层自动学习图像特征
- 池化层降低空间维度,增加感受野
- BatchNormalization 加速收敛
- Dropout 防止过拟合
- 数据增强可进一步提升性能
案例 3:电影评论情感分析(RNN/LSTM)
使用循环神经网络处理文本序列,进行情感分类。
3.3.1 数据加载与预处理
python
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, models
import numpy as np
import matplotlib.pyplot as plt
# 加载 IMDB 电影评论数据集
print("加载 IMDB 数据集...")
num_words = 10000 # 只保留最常见的 10000 个词
maxlen = 200 # 只保留每条评论的前 200 个词
(x_train, y_train), (x_test, y_test) = keras.datasets.imdb.load_data(num_words=num_words)
print(f"训练集形状:{x_train.shape}")
print(f"测试集形状:{x_test.shape}")
print(f"标签分布:{np.bincount(y_train)}")
# 数据预处理:填充序列
x_train = keras.preprocessing.sequence.pad_sequences(x_train, maxlen=maxlen)
x_test = keras.preprocessing.sequence.pad_sequences(x_test, maxlen=maxlen)
print(f"填充后训练集:{x_train.shape}")
print(f"填充后测试集:{x_test.shape}")
# 获取词索引
word_index = keras.datasets.imdb.get_word_index()
reverse_word_index = {value: key for key, value in word_index.items()}
# 解码函数
def decode_review(encoded_review):
return ' '.join([reverse_word_index.get(i - 3, '?') for i in encoded_review])
# 查看样本
print("\n样本评论:")
print(decode_review(x_train[0])[:200] + "...")
print(f"情感:{'正面' if y_train[0] == 1 else '负面'}")3.3.2 构建 LSTM 模型
python
# 构建 LSTM 模型
def create_lstm_model(vocab_size=10000, embedding_dim=128, maxlen=200):
model = models.Sequential([
layers.Embedding(vocab_size, embedding_dim, input_length=maxlen),
layers.SpatialDropout1D(0.2),
layers.Bidirectional(layers.LSTM(64, return_sequences=True)),
layers.Dropout(0.5),
layers.Bidirectional(layers.LSTM(32)),
layers.Dropout(0.5),
layers.Dense(64, activation='relu'),
layers.Dropout(0.5),
layers.Dense(1, activation='sigmoid')
])
return model
model = create_lstm_model()
# 编译模型
model.compile(
optimizer=keras.optimizers.Adam(learning_rate=0.001),
loss='binary_crossentropy',
metrics=['accuracy', keras.metrics.Precision(), keras.metrics.Recall()]
)
# 模型摘要
model.summary()
# 回调函数
callbacks = [
keras.callbacks.EarlyStopping(
monitor='val_loss',
patience=3,
restore_best_weights=True
),
keras.callbacks.ReduceLROnPlateau(
monitor='val_loss',
factor=0.5,
patience=2,
min_lr=1e-6
)
]3.3.3 训练与评估
python
# 训练模型
print("\n开始训练 LSTM 模型...")
history = model.fit(
x_train, y_train,
batch_size=128,
epochs=15,
validation_split=0.2,
callbacks=callbacks,
verbose=1
)
# 测试集评估
test_results = model.evaluate(x_test, y_test, verbose=0)
print(f"\n测试集结果:")
print(f" 损失:{test_results[0]:.4f}")
print(f" 准确率:{test_results[1]:.4f}")
print(f" 精确率:{test_results[2]:.4f}")
print(f" 召回率:{test_results[3]:.4f}")
# 训练历史可视化
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[0].plot(history.history['accuracy'], label='训练准确率', linewidth=2)
axes[0].plot(history.history['val_accuracy'], label='验证准确率', linewidth=2)
axes[0].set_xlabel('Epoch')
axes[0].set_ylabel('Accuracy')
axes[0].set_title('LSTM 准确率变化')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
axes[1].plot(history.history['loss'], label='训练损失', linewidth=2)
axes[1].plot(history.history['val_loss'], label='验证损失', linewidth=2)
axes[1].set_xlabel('Epoch')
axes[1].set_ylabel('Loss')
axes[1].set_title('LSTM 损失变化')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('lstm_training_history.png', dpi=150)
plt.show()
# 预测分析
y_pred_proba = model.predict(x_test)
y_pred = (y_pred_proba > 0.5).astype(int)
from sklearn.metrics import confusion_matrix, classification_report
cm = confusion_matrix(y_test, y_pred)
print("\n混淆矩阵:")
print(cm)
print("\n分类报告:")
print(classification_report(y_test, y_pred, target_names=['负面', '正面']))
# 可视化混淆矩阵
plt.figure(figsize=(8, 6))
plt.imshow(cm, cmap='Blues')
plt.colorbar()
plt.xlabel('预测标签')
plt.ylabel('真实标签')
plt.title('LSTM 情感分析混淆矩阵')
for i in range(2):
for j in range(2):
plt.text(j, i, str(cm[i, j]), ha='center', va='center',
color='red' if cm[i, j] < 500 else 'black', fontsize=16)
plt.xticks(range(2), ['负面', '正面'])
plt.yticks(range(2), ['负面', '正面'])
plt.tight_layout()
plt.savefig('lstm_confusion_matrix.png', dpi=150)
plt.show()
# 预测示例
test_indices = np.random.choice(len(x_test), 5, replace=False)
print("\n随机预测示例:")
for idx in test_indices:
review_text = decode_review(x_test[idx])[:100] + "..."
prediction = y_pred[idx][0]
confidence = y_pred_proba[idx][0] if prediction == 1 else 1 - y_pred_proba[idx][0]
true_label = '正面' if y_test[idx] == 1 else '负面'
pred_label = '正面' if prediction == 1 else '负面'
print(f"\n评论:{review_text}")
print(f"真实:{true_label}, 预测:{pred_label}, 置信度:{confidence:.2%}")案例要点总结:
- Embedding 层将词转换为稠密向量
- LSTM 能捕捉长距离依赖关系
- Bidirectional LSTM 利用上下文信息
- Dropout 防止过拟合很重要
案例 4:图像风格迁移(迁移学习)
使用预训练的 VGG16 模型进行图像风格迁移。
3.4.1 加载预训练模型
python
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, models, applications
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.preprocessing.image import load_img, img_to_array
import os
# 加载预训练的 VGG16 模型
print("加载 VGG16 预训练模型...")
vgg16 = applications.VGG16(
include_top=False,
weights='imagenet',
input_shape=(224, 224, 3)
)
vgg16.trainable = False # 冻结预训练权重
print("VGG16 模型已加载")
vgg16.summary()
# 如果没有真实图片,使用示例代码演示
# 实际使用时替换为真实图片路径
# content_image_path = 'content.jpg'
# style_image_path = 'style.jpg'
# 创建示例图像(使用随机噪声模拟)
def create_sample_image(size=(224, 224)):
return np.random.uniform(0, 1, (1, size[0], size[1], 3)).astype(np.float32)
content_image = create_sample_image()
style_image = create_sample_image()
print(f"内容图像形状:{content_image.shape}")
print(f"风格图像形状:{style_image.shape}")3.4.2 定义损失函数
python
# 内容损失
def content_loss(base_content, target):
return tf.reduce_mean(tf.square(base_content - target))
# 风格损失(Gram 矩阵)
def gram_matrix(input_tensor):
channels = int(input_tensor.shape[-1])
a = tf.reshape(input_tensor, [-1, channels])
n = tf.shape(a)[0]
gram = tf.matmul(a, a, transpose_a=True)
return gram / tf.cast(n, tf.float32)
def style_loss(base_style, target_style):
gram_base = gram_matrix(base_style)
gram_target = gram_matrix(target_style)
return tf.reduce_mean(tf.square(gram_base - gram_target))
# 总变分损失(减少噪点)
def total_variation_loss(x):
a = tf.square(x[:, :-1, :-1, :] - x[:, 1:, :-1, :])
b = tf.square(x[:, :-1, :-1, :] - x[:, :-1, 1:, :])
return tf.reduce_sum(tf.pow(a + b, 1.25))
# 定义风格和内容层
style_layer_names = ['block1_conv1', 'block2_conv1', 'block3_conv1', 'block4_conv1', 'block5_conv1']
content_layer_name = 'block5_conv2'
# 创建特征提取模型
style_outputs = [vgg16.get_layer(name).output for name in style_layer_names]
content_output = vgg16.get_layer(content_layer_name).output
style_model = models.Model(vgg16.input, style_outputs)
content_model = models.Model(vgg16.input, content_output)
print("特征提取模型已创建")3.4.3 风格迁移优化
python
# 损失权重
style_weight = 1e-5
content_weight = 1e4
variation_weight = 1e-6
# 获取内容和风格特征
content_target = content_model(content_image)
style_targets = style_model(style_image)
# 创建优化器
optimizer = keras.optimizers.Adam(learning_rate=0.02)
# 风格迁移函数
def style_transfer(content_image, style_image, iterations=100):
# 初始化生成图像(从内容图像开始)
generated_image = tf.Variable(content_image, dtype=tf.float32)
history = {'loss': [], 'style_loss': [], 'content_loss': []}
print(f"开始风格迁移,迭代 {iterations} 次...")
for i in range(iterations):
with tf.GradientTape() as tape:
# 计算当前生成图像的特征
gen_content = content_model(generated_image)
gen_style = style_model(generated_image)
# 计算损失
c_loss = content_loss(content_target, gen_content)
s_loss = sum(style_loss(st, gt) for st, gt in zip(style_targets, gen_style))
tv_loss = total_variation_loss(generated_image)
total_loss = (content_weight * c_loss +
style_weight * s_loss +
variation_weight * tv_loss)
# 计算梯度并更新
grads = tape.gradient(total_loss, generated_image)
optimizer.apply_gradients([(grads, generated_image)])
# 记录历史
history['loss'].append(total_loss.numpy())
history['style_loss'].append(s_loss.numpy())
history['content_loss'].append(c_loss.numpy())
if (i + 1) % 20 == 0:
print(f"迭代 {i+1}/{iterations}, 总损失:{total_loss.numpy():.2f}")
return generated_image.numpy(), history
# 执行风格迁移(减少迭代次数用于演示)
generated_image, history = style_transfer(content_image, style_image, iterations=50)
# 可视化训练过程
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# 损失曲线
axes[0].plot(history['loss'], label='总损失', linewidth=2)
axes[0].set_xlabel('迭代次数')
axes[0].set_ylabel('损失')
axes[0].set_title('训练损失变化')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# 风格损失
axes[1].plot(history['style_loss'], label='风格损失', color='orange', linewidth=2)
axes[1].set_xlabel('迭代次数')
axes[1].set_ylabel('损失')
axes[1].set_title('风格损失变化')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
# 内容损失
axes[2].plot(history['content_loss'], label='内容损失', color='green', linewidth=2)
axes[2].set_xlabel('迭代次数')
axes[2].set_ylabel('损失')
axes[2].set_title('内容损失变化')
axes[2].legend()
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('style_transfer_loss.png', dpi=150)
plt.show()
# 显示结果
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(content_image[0])
axes[0].set_title('内容图像')
axes[0].axis('off')
axes[1].imshow(style_image[0])
axes[1].set_title('风格图像')
axes[1].axis('off')
axes[2].imshow(generated_image[0])
axes[2].set_title('风格迁移结果')
axes[2].axis('off')
plt.tight_layout()
plt.savefig('style_transfer_result.png', dpi=150)
plt.show()
print("风格迁移完成!")案例要点总结:
- 迁移学习利用预训练模型的特征提取能力
- Gram 矩阵捕捉图像风格信息
- 多损失平衡内容和风格
- 总变分损失减少生成图像的噪点
第四章:深度学习最佳实践
4.1 模型调试技巧
python
# 检查梯度
def check_gradients(model, x, y):
with tf.GradientTape() as tape:
predictions = model(x, training=True)
loss = keras.losses.binary_crossentropy(y, predictions)
grads = tape.gradient(loss, model.trainable_variables)
print("梯度检查:")
for i, (var, grad) in enumerate(zip(model.trainable_variables, grads)):
if grad is not None:
print(f" {var.name}: 均值={np.mean(grad):.6f}, 标准差={np.std(grad):.6f}")
else:
print(f" {var.name}: 梯度为 None")4.2 超参数调优
关键超参数:
- 学习率:最重要,使用学习率调度
- Batch Size:影响训练稳定性和速度
- 网络深度和宽度:根据任务复杂度调整
- Dropout 率:0.2-0.5 常用
- 优化器选择:Adam 是安全默认值
4.3 数据增强
python
# Keras 数据增强
data_augmentation = keras.Sequential([
layers.RandomFlip("horizontal"),
layers.RandomRotation(0.1),
layers.RandomZoom(0.1),
layers.RandomTranslation(0.1, 0.1),
])4.4 模型保存与加载
python
# 保存完整模型
model.save('my_model.h5')
# 保存权重
model.save_weights('my_model_weights.h5')
# 保存模型架构
model_json = model.to_json()
# 加载模型
loaded_model = keras.models.load_model('my_model.h5')
# 加载权重
model.load_weights('my_model_weights.h5')第五章:常见问题解答(FAQ)
Q1: 应该选择多深的网络?
答: 取决于任务复杂度:
- 简单任务:2-3 层
- 中等任务:5-10 层
- 复杂任务:使用预训练模型
Q2: 训练不收敛怎么办?
答: 排查步骤:
- 检查学习率(太大或太小)
- 检查数据归一化
- 检查标签是否正确
- 尝试不同的优化器
- 简化网络结构
Q3: 过拟合严重怎么办?
答: 解决方法:
- 增加 Dropout
- 使用数据增强
- 添加 L2 正则化
- 早停(Early Stopping)
- 减少模型复杂度
- 收集更多数据
Q4: GPU 内存不足怎么办?
答: 优化方法:
- 减小 batch size
- 使用混合精度训练
- 梯度累积
- 模型剪枝
Q5: 如何选择预训练模型?
答: 常见选择:
- 图像分类:VGG16, ResNet50, EfficientNet
- 目标检测:YOLO, Faster R-CNN
- 语义分割:U-Net, DeepLab
- NLP:BERT, GPT, T5
第六章:总结与下一步
6.1 本教程要点回顾
- 基础原理:理解了神经网络、激活函数、损失函数、反向传播
- Keras 框架:掌握了模型构建、训练、评估流程
- CNN:学会了卷积神经网络处理图像
- RNN/LSTM:掌握了序列数据处理
- 迁移学习:了解了预训练模型的应用
6.2 学习路线建议
初级(已完成):
- ✅ 理解神经网络基础
- ✅ 掌握 Keras 使用
- ✅ 完成 CNN、RNN 实战
中级(下一步):
- 学习更深的网络架构(ResNet、Transformer)
- 掌握目标检测、语义分割
- 学习生成模型(GAN、VAE)
高级:
- 研究前沿论文
- 学习分布式训练
- 掌握模型部署和优化
6.3 推荐资源
课程:
- Deep Learning Specialization (Coursera)
- fast.ai Practical Deep Learning
- Stanford CS231n (计算机视觉)
书籍:
- 《Deep Learning》(Ian Goodfellow)
- 《Hands-On Deep Learning》
实践:
- Kaggle 竞赛
- Papers With Code
- Hugging Face
作者注: 深度学习领域发展迅速,保持学习和实践是关键。希望本教程能为你打下坚实基础!
更新日期: 2026-03-21
字数统计: 约 9200 字