Skip to content

深度学习与神经网络实践:从原理到应用 ​

概述 ​

深度学习是机器学习的一个子领域,它使用多层神经网络来学习数据的层次化表示。近年来,深度学习在图像识别、自然语言处理、语音识别等领域取得了突破性进展。

本教程将带你深入理解深度学习的核心原理,并通过 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()

激活函数对比:

函数公式优点缺点适用场景
Sigmoid1/(1+e^-x)输出 0-1,适合概率梯度消失,输出非零中心二分类输出层
Tanh(e^x-e^-x)/(e^x+e^-x)输出 -1 到 1,零中心梯度消失隐藏层
ReLUmax(0,x)计算简单,缓解梯度消失Dead ReLU 问题默认隐藏层
Leaky ReLUmax(αx,x)解决 Dead ReLU需要选择α隐藏层替代 ReLU
Softmaxe^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. 前向传播:计算每一层的输出
  2. 计算损失:比较预测值与真实值
  3. 反向传播:从输出层向输入层传播误差
  4. 更新权重:使用梯度下降更新参数

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自适应学习率,收敛快默认选择
AdamWAdam + 权重衰减需要正则化时
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 x

1.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 + beta

1.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: 训练不收敛怎么办? ​

答: 排查步骤:

  1. 检查学习率(太大或太小)
  2. 检查数据归一化
  3. 检查标签是否正确
  4. 尝试不同的优化器
  5. 简化网络结构

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 本教程要点回顾 ​

  1. 基础原理:理解了神经网络、激活函数、损失函数、反向传播
  2. Keras 框架:掌握了模型构建、训练、评估流程
  3. CNN:学会了卷积神经网络处理图像
  4. RNN/LSTM:掌握了序列数据处理
  5. 迁移学习:了解了预训练模型的应用

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 字