在Rstudio中安装本节课所有需要的包,运行以下指令:

# 安装基础包
install.packages(c(
  "keras",        # Keras R接口
  "tensorflow",   # TensorFlow R接口
  "torch",        # PyTorch R接口
  "reticulate",   # R与Python交互
  "ggplot2",      # 数据可视化
  "dplyr",        # 数据处理
  "tidyr",        # 数据整理
  "purrr"         # 函数式编程
))

# 安装TensorFlow和Keras Python环境
# 注意:这需要先安装Python或Anaconda
# keras::install_keras()
# tensorflow::install_tensorflow()

目录

章节 主题 核心内容
第一章 深度学习基础与R环境搭建 基本概念、框架概览、环境配置
1.1 深度学习基本概念 神经元、层、激活函数、损失函数、优化器
1.2 R中深度学习框架概览 keras、tensorflow、torch、mxnet
1.3 环境配置 TensorFlow安装与GPU支持
1.4 安装PyTorch for R torch包安装与配置
1.5 框架选择与项目结构 框架对比、项目组织
第二章 神经网络核心组件 张量、层、激活函数、损失函数、优化器
2.1 张量操作与数据格式 tf$constant、torch_tensor
2.2 层概念 全连接层、激活层、Dropout、BatchNormalization
2.3 激活函数 ReLU、sigmoid、tanh、softmax、LeakyReLU、ELU
2.4 损失函数 MSE、交叉熵、二元交叉熵、自定义损失
2.5 优化器 SGD、Adam、RMSprop、Adagrad、学习率调度
2.6 评估指标 准确率、AUC、自定义指标
第三章 Keras与TensorFlow基础建模 模型构建、编译、训练、评估、保存
3.1 顺序模型构建 Sequential API
3.2 函数式API 多输入/多输出模型
3.3 模型编译 优化器、损失、指标配置
3.4 模型训练 epochs、batch_size、验证集、回调
3.5 模型评估与预测 evaluate()、predict()
3.6 模型保存与加载 save_model_hdf5()、load_model_hdf5()
3.7 可视化模型结构 plot_model()
第四章 正则化与训练技巧 过拟合诊断、正则化方法、训练优化
4.1 过拟合与欠拟合诊断 诊断方法与可视化
4.2 Dropout层原理与实现 随机失活机制
4.3 权重正则化 L1、L2正则化设置
4.4 批归一化 BatchNormalization的作用与位置
4.5 早停法回调 EarlyStopping配置
4.6 模型检查点 保存最佳模型
4.7 学习率调度 ReduceLROnPlateau、自定义调度
4.8 数据增强 ImageDataGenerator应用

第一章 深度学习基础与R环境搭建

深度学习是机器学习的一个重要分支,它通过构建多层神经网络来学习数据的层次化表示。本章将介绍深度学习的基本概念,并指导如何在R环境中配置深度学习框架。

1.1 深度学习基本概念

1.1.1 神经元(Neuron)

神经元是神经网络的基本计算单元,它模拟生物神经元的工作方式。

数学定义

\[y = f(\sum_{i=1}^{n} w_i x_i + b)\]

参数说明: - \(x_i\):输入信号(第i个特征) - \(w_i\):权重(第i个特征的权重) - \(b\):偏置(bias) - \(f\):激活函数 - \(y\):输出

# 神经元的简单实现
# 假设有3个输入特征
inputs <- c(0.5, 0.3, 0.8)

# 对应的权重
weights <- c(0.2, -0.4, 0.6)

# 偏置
bias <- 0.1

# 计算加权和
weighted_sum <- sum(inputs * weights) + bias
cat("加权和:", weighted_sum, "\n")

# 使用ReLU激活函数
relu <- function(x) {
  max(0, x)
}

output <- relu(weighted_sum)
cat("神经元输出:", output, "\n")

意义与使用场景

核心意义:神经元是深度学习最基本的计算单元,通过加权求和与非线性激活,实现对输入特征的变换和特征提取。

举例:在医学影像诊断中,一个神经元可以用于判断某个局部区域是否存在异常特征。

为什么这种数据适合用神经元处理?

医学影像数据具有高度的复杂性和非线性特征。单个神经元可以学习到特定的模式,比如边缘、纹理或特定形状。通过调整权重,神经元可以自动学习哪些特征对诊断更重要。例如,在肺部CT影像中,神经元可以学习识别结节边缘的特征。

研究目的是什么?

  1. 特征提取:从原始像素中提取有意义的诊断特征。
  2. 异常检测:识别影像中可能存在病变的区域。
  3. 辅助诊断:为医生提供初步的筛查建议。

可能得到什么样的结果?

神经元输出一个数值,表示对特定特征的响应强度。高输出值表示该特征明显存在,低输出值表示特征不明显或不存在。多个神经元的组合可以形成更复杂的特征表示。

需要注意什么?

单个神经元的能力有限,只能学习线性可分的模式。实际应用中需要多层神经元的组合,即深度神经网络,才能处理复杂的医学影像问题。

1.1.2 层(Layer)

层是神经网络的构建模块,由多个神经元组成。常见的层类型包括:

层类型 功能 典型应用
全连接层(Dense) 每个神经元与上一层所有神经元连接 分类任务、特征组合
卷积层(Conv2D) 提取局部特征 图像识别
循环层(RNN/LSTM) 处理序列数据 时间序列、文本
池化层(Pooling) 降维、提取主要特征 图像处理
# 使用Keras创建不同类型的层

# 全连接层示例
dense_layer <- keras::layer_dense(
  units = 64,           # 64个神经元
  activation = "relu",  # ReLU激活函数
  input_shape = c(100)  # 输入维度为100
)

# Dropout层(防止过拟合)
dropout_layer <- keras::layer_dropout(rate = 0.5)

# 批归一化层
batchnorm_layer <- keras::layer_batch_normalization()

1.1.3 激活函数(Activation Function)

激活函数为神经网络引入非线性,使网络能够学习复杂的模式。

常用激活函数对比

激活函数 公式 特点 适用场景
ReLU \(f(x) = \max(0, x)\) 计算简单,缓解梯度消失 隐藏层首选
Sigmoid \(f(x) = \frac{1}{1+e^{-x}}\) 输出0-1,适合概率 二分类输出层
Tanh \(f(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}\) 输出-1到1,零中心化 隐藏层
Softmax \(f(x_i) = \frac{e^{x_i}}{\sum_j e^{x_j}}\) 输出和为1的概率分布 多分类输出层
# 激活函数的可视化比较
# 创建输入数据
x <- seq(-5, 5, length.out = 100)

# 定义各种激活函数
relu <- function(x) pmax(0, x)
sigmoid <- function(x) 1 / (1 + exp(-x))
tanh_func <- function(x) (exp(x) - exp(-x)) / (exp(x) + exp(-x))
leaky_relu <- function(x, alpha = 0.01) ifelse(x > 0, x, alpha * x)

# 创建数据框用于绘图
activation_df <- data.frame(
  x = rep(x, 4),
  y = c(relu(x), sigmoid(x), tanh_func(x), leaky_relu(x)),
  type = rep(c("ReLU", "Sigmoid", "Tanh", "Leaky ReLU"), each = 100)
)

# 绘制激活函数对比图
ggplot2::ggplot(activation_df, ggplot2::aes(x = x, y = y, color = type)) +
  ggplot2::geom_line(linewidth = 1) +
  ggplot2::facet_wrap(~ type, scales = "free_y") +
  ggplot2::labs(
    title = "常用激活函数对比",
    x = "输入 x",
    y = "输出 f(x)",
    color = "激活函数"
  ) +
  ggplot2::theme_minimal() +
  ggplot2::theme(legend.position = "none")

1.1.4 损失函数(Loss Function)

损失函数衡量模型预测值与真实值之间的差距,是模型优化的目标。

常用损失函数

损失函数 公式 适用场景
均方误差(MSE) \(L = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2\) 回归问题
二元交叉熵 \(L = -\frac{1}{n}\sum_{i=1}^{n}[y_i\log(\hat{y}_i) + (1-y_i)\log(1-\hat{y}_i)]\) 二分类
分类交叉熵 \(L = -\frac{1}{n}\sum_{i=1}^{n}\sum_{j=1}^{k}y_{ij}\log(\hat{y}_{ij})\) 多分类
# 损失函数的简单实现

# 均方误差(MSE)
mse_loss <- function(y_true, y_pred) {
  mean((y_true - y_pred)^2)
}

# 二元交叉熵
binary_crossentropy <- function(y_true, y_pred) {
  # 添加小常数避免log(0)
  epsilon <- 1e-15
  y_pred <- pmax(pmin(y_pred, 1 - epsilon), epsilon)
  -mean(y_true * log(y_pred) + (1 - y_true) * log(1 - y_pred))
}

# 示例:计算损失
y_true <- c(1, 0, 1, 1, 0)
y_pred <- c(0.9, 0.1, 0.8, 0.7, 0.2)

cat("MSE损失:", mse_loss(y_true, y_pred), "\n")
cat("二元交叉熵损失:", binary_crossentropy(y_true, y_pred), "\n")

1.1.5 优化器(Optimizer)

优化器负责根据损失函数的梯度更新模型参数。

常用优化器对比

优化器 特点 适用场景
SGD 最基础的优化器,沿负梯度方向更新 简单任务、调试
SGD with Momentum 加入动量,加速收敛 深层网络
Adam 自适应学习率,结合Momentum和RMSprop 通用首选
RMSprop 自适应学习率,适合非平稳目标 RNN、强化学习
Adagrad 自适应学习率,适合稀疏数据 NLP任务
# Keras中优化器的使用示例

# SGD优化器
optimizer_sgd <- keras::optimizer_sgd(
  learning_rate = 0.01,
  momentum = 0.9
)

# Adam优化器(推荐)
optimizer_adam <- keras::optimizer_adam(
  learning_rate = 0.001,
  beta_1 = 0.9,
  beta_2 = 0.999
)

# RMSprop优化器
optimizer_rmsprop <- keras::optimizer_rmsprop(
  learning_rate = 0.001,
  rho = 0.9
)

1.2 R中深度学习框架概览

R语言提供了多个深度学习框架的接口,让R用户也能利用强大的深度学习能力。

1.2.1 主要框架对比

框架 后端 特点 适用场景
Keras TensorFlow 高级API,简单易用 快速原型开发、教学
TensorFlow TensorFlow 底层控制,灵活强大 生产环境、研究
Torch PyTorch 动态计算图,调试友好 研究、复杂模型
MXNet MXNet 分布式训练,效率高 大规模训练

1.2.2 Keras

Keras是一个高级神经网络API,以TensorFlow为后端,提供了简洁的接口。

# Keras的特点示例

# 1. 简洁的模型定义
model <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 64, activation = "relu", input_shape = c(100)) %>%
  keras::layer_dropout(rate = 0.5) %>%
  keras::layer_dense(units = 10, activation = "softmax")

# 2. 一行代码编译
model %>% keras::compile(
  optimizer = "adam",
  loss = "categorical_crossentropy",
  metrics = c("accuracy")
)

# 查看模型结构
summary(model)

1.2.3 TensorFlow

TensorFlow提供了更底层的控制,适合需要精细调整的场景。

# TensorFlow底层操作示例

# 创建常量张量
a <- tensorflow::tf$constant(c(1, 2, 3))
b <- tensorflow::tf$constant(c(4, 5, 6))

# 张量运算
c <- tensorflow::tf$add(a, b)
d <- tensorflow::tf$multiply(a, b)

# 打印结果
print(c)
print(d)

1.2.4 Torch

Torch(PyTorch的R接口)采用动态计算图,更适合研究和复杂模型开发。

# Torch示例

# 创建张量
x <- torch::torch_tensor(c(1, 2, 3, 4, 5), dtype = torch::torch_float())

# 张量运算
y <- x * 2 + 1
print(y)

# 创建简单的神经网络模块
simple_net <- torch::nn_module(
  initialize = function(input_size, hidden_size, output_size) {
    self$fc1 <- torch::nn_linear(input_size, hidden_size)
    self$fc2 <- torch::nn_linear(hidden_size, output_size)
  },
  forward = function(x) {
    x %>%
      self$fc1() %>%
      torch::nnf_relu() %>%
      self$fc2()
  }
)

意义与使用场景

核心意义:选择合适的深度学习框架可以大大提高开发效率,不同框架各有优势,需要根据具体任务选择。

举例:在医学影像诊断项目中,需要从简单的分类任务开始,逐步过渡到复杂的分割任务。

为什么需要了解多个框架?

不同的医学AI项目有不同的需求。Keras适合快速验证想法,比如判断CT影像是否异常;TensorFlow适合部署到生产环境,需要优化推理速度;Torch适合研究新算法,比如开发新的分割网络架构。了解多个框架可以根据项目阶段灵活选择。

如何选择框架?

  1. 初学者或快速原型:选择Keras,API简洁,文档丰富。
  2. 生产部署:选择TensorFlow,支持TensorRT优化、TensorFlow Serving。
  3. 学术研究:选择Torch,动态图便于调试和实现新想法。
  4. 大规模分布式训练:考虑MXNet或TensorFlow。

需要注意什么?

框架之间迁移成本较高,建议在项目初期就确定主要框架。同时,不同框架的R接口更新频率不同,Keras和Torch的R接口维护较好。

1.3 环境配置:安装 TensorFlow

1.3.1 安装前的准备

在安装TensorFlow之前,需要确保系统已安装Python环境。

# 检查Python环境
reticulate::py_available()

# 查看Python版本
reticulate::py_version()

# 查看Python路径
reticulate::py_config()

1.3.2 安装TensorFlow

# 方法1:使用keras包安装(推荐)
# 这会同时安装TensorFlow和Keras
# keras::install_keras()

# 方法2:使用tensorflow包安装
# tensorflow::install_tensorflow()

# 方法3:指定版本安装
# tensorflow::install_tensorflow(version = "2.10")

# 方法4:使用conda环境
# tensorflow::install_tensorflow(method = "conda", conda = "auto")

1.3.3 验证安装

# 加载TensorFlow库
library(tensorflow)

# 检查TensorFlow版本
cat("TensorFlow版本:", tf$version$VERSION, "\n")

# 简单测试
a <- tf$constant(1.0)
b <- tf$constant(2.0)
c <- tf$add(a, b)
print(c)

1.4 配置 GPU 支持与设备检测

1.4.1 检测可用设备

# 列出所有物理设备
physical_devices <- tf$config$list_physical_devices()
print(physical_devices)

# 列出GPU设备
gpus <- tf$config$list_physical_devices("GPU")
cat("检测到", length(gpus), "个GPU\n")

# 列出CPU设备
cpus <- tf$config$list_physical_devices("CPU")
cat("检测到", length(cpus), "个CPU\n")

1.4.2 GPU内存配置

# 设置GPU内存增长模式(推荐)
# 避免一次性占用所有GPU内存
gpus <- tf$config$list_physical_devices("GPU")
if (length(gpus) > 0) {
  for (gpu in gpus) {
    tf$config$experimental$set_memory_growth(gpu, TRUE)
  }
  cat("已启用GPU内存增长模式\n")
}

# 设置GPU内存限制(可选)
# tf$config$experimental$set_virtual_device_configuration(
#   gpus[[1]],
#   list(tf$config$experimental$VirtualDeviceConfiguration(memory_limit = 4096))
# )

1.4.3 指定设备运行

# 使用CPU运行
with(tf$device("/CPU:0"), {
  a <- tf$constant([[1, 2], [3, 4]])
  b <- tf$constant([[5, 6], [7, 8]])
  c <- tf$matmul(a, b)
  print(c)
})

# 使用GPU运行(如果可用)
if (length(gpus) > 0) {
  with(tf$device("/GPU:0"), {
    a <- tf$constant([[1, 2], [3, 4]])
    b <- tf$constant([[5, 6], [7, 8]])
    c <- tf$matmul(a, b)
    print(c)
  })
}

意义与使用场景

核心意义:GPU加速可以显著提高深度学习模型的训练速度,特别是对于大规模数据和复杂模型。

举例:在训练一个医学影像分类模型时,使用GPU可以将训练时间从数天缩短到数小时。

为什么GPU适合深度学习?

深度学习的核心运算是矩阵乘法和卷积运算,这些运算可以高度并行化。GPU拥有数千个计算核心,可以同时处理大量简单的数学运算。相比之下,CPU只有几十个核心,更适合处理复杂的逻辑控制。对于医学影像这种高维数据,GPU的优势更加明显。

如何配置GPU环境?

  1. 安装CUDA Toolkit和cuDNN(NVIDIA GPU必需)
  2. 安装支持GPU的TensorFlow版本
  3. 配置GPU内存管理策略

需要注意什么?

Windows系统配置GPU环境相对复杂,建议使用Linux系统或WSL2。如果GPU内存不足,可以减小batch_size或使用梯度累积。

1.5 安装 PyTorch for R

1.5.1 安装torch包

# 安装torch包
# install.packages("torch")

# 安装PyTorch后端
# torch::install_torch()

# 验证安装
library(torch)
cat("Torch版本:", torch_version(), "\n")

1.5.2 检查CUDA支持

# 检查CUDA是否可用
cuda_available <- torch::cuda_is_available()
cat("CUDA可用:", cuda_available, "\n")

# 查看CUDA设备数量
if (cuda_available) {
  device_count <- torch::cuda_device_count()
  cat("CUDA设备数量:", device_count, "\n")
  
  # 查看当前设备
  current_device <- torch::cuda_current_device()
  cat("当前设备:", current_device, "\n")
  
  # 查看设备名称
  device_name <- torch::cuda_get_device_name()
  cat("设备名称:", device_name, "\n")
}

1.5.3 设备选择

# 自动选择最佳设备
device <- if (torch::cuda_is_available()) {
  torch::torch_device("cuda")
} else {
  torch::torch_device("cpu")
}
cat("使用设备:", device, "\n")

# 将张量移动到指定设备
x <- torch::torch_randn(c(3, 3), device = device)
print(x)

1.6 框架选择与项目结构建议

1.6.1 框架选择指南

场景 推荐框架 理由
深度学习入门 Keras API简洁,文档丰富
快速原型开发 Keras 一行代码定义模型
生产环境部署 TensorFlow 支持TensorFlow Serving
学术研究 Torch 动态图,调试方便
自然语言处理 Torch/Transformers Hugging Face生态
计算机视觉 Keras/Torch 都有丰富的预训练模型

1.6.2 推荐的项目结构

project/
├── data/                    # 数据目录
│   ├── raw/                 # 原始数据
│   ├── processed/           # 处理后的数据
│   └── external/            # 外部数据
├── R/                       # R脚本
│   ├── data_preprocessing.R # 数据预处理
│   ├── model_definition.R   # 模型定义
│   └── utils.R              # 工具函数
├── notebooks/               # R Markdown笔记本
│   └── exploration.Rmd      # 探索性分析
├── models/                  # 保存的模型
├── output/                  # 输出结果
│   ├── figures/             # 图表
│   └── reports/             # 报告
├── config/                  # 配置文件
│   └── config.yml           # 超参数配置
└── README.md                # 项目说明

1.6.3 配置管理建议

# 使用配置文件管理超参数
config <- list(
  model = list(
    input_dim = 100,
    hidden_units = c(128, 64, 32),
    output_dim = 10,
    dropout_rate = 0.5
  ),
  training = list(
    epochs = 100,
    batch_size = 32,
    learning_rate = 0.001,
    early_stopping_patience = 10
  ),
  data = list(
    train_ratio = 0.7,
    val_ratio = 0.15,
    test_ratio = 0.15
  )
)

# 打印配置
str(config)

第二章 神经网络核心组件

本章深入介绍神经网络的核心组件,包括张量操作、层、激活函数、损失函数和优化器。这些是构建深度学习模型的基础。

2.1 张量(Tensor)操作与数据格式

张量是深度学习中的基本数据结构,可以理解为多维数组。

2.1.1 张量的维度

维度 名称 示例
0D 标量(Scalar) 一个数值,如 5
1D 向量(Vector) 一维数组,如 [1, 2, 3]
2D 矩阵(Matrix) 二维数组,如 [[1,2], [3,4]]
3D 3D张量 彩色图像(高×宽×通道)
4D 4D张量 图像批次(批次×高×宽×通道)
5D 5D张量 视频数据(批次×帧×高×宽×通道)

2.1.2 TensorFlow张量操作

# 创建张量

# 标量(0D张量)
scalar <- tf$constant(5)
cat("标量形状:", as.integer(tf$shape(scalar)), "\n")
print(scalar)

# 向量(1D张量)
vector <- tf$constant(c(1, 2, 3, 4, 5))
cat("向量形状:", as.integer(tf$shape(vector)), "\n")
print(vector)

# 矩阵(2D张量)
matrix <- tf$constant(matrix(1:6, nrow = 2, ncol = 3))
cat("矩阵形状:", as.integer(tf$shape(matrix)), "\n")
print(matrix)

# 3D张量(模拟彩色图像)
image <- tf$random$normal(shape = c(64, 64, 3))  # 64x64 RGB图像
cat("图像张量形状:", as.integer(tf$shape(image)), "\n")

# 4D张量(批次图像)
batch_images <- tf$random$normal(shape = c(32, 64, 64, 3))  # 32张64x64 RGB图像
cat("批次图像张量形状:", as.integer(tf$shape(batch_images)), "\n")

2.1.3 张量运算

# 基本运算
a <- tf$constant(c(1, 2, 3))
b <- tf$constant(c(4, 5, 6))

# 加法
c_add <- tf$add(a, b)
cat("加法结果:", as.numeric(c_add), "\n")

# 乘法(逐元素)
c_mul <- tf$multiply(a, b)
cat("乘法结果:", as.numeric(c_mul), "\n")

# 矩阵乘法
m1 <- tf$constant(matrix(1:6, nrow = 2, ncol = 3, byrow = TRUE))
m2 <- tf$constant(matrix(1:6, nrow = 3, ncol = 2, byrow = TRUE))
m_matmul <- tf$matmul(m1, m2)
cat("矩阵乘法结果:\n")
print(m_matmul)

# 形状变换
x <- tf$constant(1:12)
x_reshaped <- tf$reshape(x, shape = c(3, 4))
cat("原始形状:", as.integer(tf$shape(x)), "\n")
cat("变换后形状:", as.integer(tf$shape(x_reshaped)), "\n")

# 转置
x_transposed <- tf$transpose(x_reshaped)
cat("转置后形状:", as.integer(tf$shape(x_transposed)), "\n")

2.1.4 Torch张量操作

# Torch张量创建

# 从R向量创建
x <- torch::torch_tensor(c(1, 2, 3, 4, 5))
print(x)

# 创建特定形状的张量
zeros <- torch::torch_zeros(c(3, 4))  # 全零张量
ones <- torch::torch_ones(c(3, 4))    # 全一张量
randn <- torch::torch_randn(c(3, 4))  # 正态分布随机张量

cat("全零张量:\n")
print(zeros)

# 张量属性
cat("数据类型:", x$dtype, "\n")
cat("设备:", x$device, "\n")
cat("形状:", x$shape, "\n")

# 张量运算
a <- torch::torch_tensor(c(1, 2, 3))
b <- torch::torch_tensor(c(4, 5, 6))

# 加法
c <- a + b
print(c)

# 逐元素乘法
d <- a * b
print(d)

# 点积
dot_product <- torch::torch_dot(a, b)
print(dot_product)

# 矩阵乘法
m1 <- torch::torch_randn(c(2, 3))
m2 <- torch::torch_randn(c(3, 4))
result <- torch::torch_matmul(m1, m2)
cat("矩阵乘法结果形状:", result$shape, "\n")

意义与使用场景

核心意义:张量是深度学习中数据表示和计算的基础,理解张量操作是构建和调试神经网络的关键。

举例:在处理医学影像数据时,一张CT图像可以表示为3D张量(高×宽×通道),一个批次的CT图像则是4D张量。

为什么医学影像适合用张量表示?

医学影像本质上是数值矩阵。CT图像的每个像素值代表组织的密度(HU值),MRI图像代表信号强度。将这些数据组织成张量,可以充分利用GPU的并行计算能力。例如,处理一个包含100张512×512的CT图像批次,使用4D张量(100×512×512×1)可以一次性完成所有图像的预处理和特征提取。

研究目的是什么?

  1. 数据标准化:将不同来源的医学影像统一为张量格式。
  2. 批量处理:同时处理多张图像,提高效率。
  3. 特征提取:通过张量运算提取影像特征。

需要注意什么?

医学影像数据量大,需要注意内存管理。使用数据生成器(generator)可以避免一次性加载所有数据。

2.2 层(Layer)概念

层是神经网络的基本构建模块,每层接收输入、进行变换、产生输出。

2.2.1 全连接层(Dense)

全连接层是最基本的层类型,每个输入神经元都与每个输出神经元相连。

数学表达

\[y = f(Wx + b)\]

参数说明: - \(W\):权重矩阵 - \(x\):输入向量 - \(b\):偏置向量 - \(f\):激活函数

# 创建全连接层
dense <- keras::layer_dense(
  units = 32,              # 输出维度
  activation = "relu",     # 激活函数
  use_bias = TRUE,         # 是否使用偏置
  kernel_initializer = "glorot_uniform",  # 权重初始化方法
  bias_initializer = "zeros"              # 偏置初始化
)

# 在模型中使用
model <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 64, activation = "relu", input_shape = c(100)) %>%
  keras::layer_dense(units = 32, activation = "relu") %>%
  keras::layer_dense(units = 10, activation = "softmax")

summary(model)

2.2.2 激活层(Activation)

激活层单独添加激活函数,有时比在Dense层中直接指定更灵活。

# 方式1:在Dense层中指定激活函数
model1 <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 32, activation = "relu", input_shape = c(100))

# 方式2:使用单独的激活层
model2 <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 32, input_shape = c(100)) %>%
  keras::layer_activation("relu")

# 方式3:使用高级激活层(如LeakyReLU)
model3 <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 32, input_shape = c(100)) %>%
  keras::layer_activation_leaky_relu(alpha = 0.01)

2.2.3 Dropout层

Dropout在训练时随机”丢弃”一部分神经元,防止过拟合。

# Dropout层示例
model <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 128, activation = "relu", input_shape = c(100)) %>%
  keras::layer_dropout(rate = 0.5) %>%  # 丢弃50%的神经元
  keras::layer_dense(units = 64, activation = "relu") %>%
  keras::layer_dropout(rate = 0.3) %>%  # 丢弃30%的神经元
  keras::layer_dense(units = 10, activation = "softmax")

# 注意:Dropout只在训练时生效,推理时自动关闭

2.2.4 BatchNormalization层

批归一化层对每个批次的数据进行标准化,加速训练并提高稳定性。

# BatchNormalization层示例
model <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 128, input_shape = c(100)) %>%
  keras::layer_batch_normalization() %>%  # 批归一化
  keras::layer_activation("relu") %>%     # 激活函数
  keras::layer_dense(units = 64) %>%
  keras::layer_batch_normalization() %>%
  keras::layer_activation("relu") %>%
  keras::layer_dense(units = 10, activation = "softmax")

# BatchNormalization的位置建议:
# 1. 在激活函数之前(推荐)
# 2. 或在激活函数之后

意义与使用场景

核心意义:不同的层类型适合不同的任务,合理组合层是构建有效神经网络的关键。

举例:在构建一个医学影像分类模型时,需要选择合适的层组合。

如何选择层类型?

  1. 全连接层:用于最终分类或回归,处理提取的特征。
  2. 卷积层:用于图像特征提取,捕捉局部模式。
  3. Dropout层:防止过拟合,特别是在数据量有限时。
  4. BatchNormalization:加速训练,允许使用更大的学习率。

医学影像项目的层组合建议

对于CT影像分类任务,典型的层组合是:卷积层→批归一化→ReLU激活→池化层→Dropout。这种组合可以有效提取特征同时防止过拟合。

需要注意什么?

Dropout率不宜过高(一般0.2-0.5),否则可能导致欠拟合。BatchNormalization在训练和推理时行为不同,需要正确设置。

2.3 激活函数

激活函数为神经网络引入非线性,是深度学习能够学习复杂模式的关键。

2.3.1 ReLU及其变体

# ReLU(Rectified Linear Unit)
# f(x) = max(0, x)
# 优点:计算简单,缓解梯度消失
# 缺点:存在"死亡ReLU"问题

# 在Keras中使用ReLU
model_relu <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 64, activation = "relu", input_shape = c(100))

# LeakyReLU
# f(x) = max(αx, x),其中α通常为0.01
# 优点:解决"死亡ReLU"问题
model_leaky <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 64, input_shape = c(100)) %>%
  keras::layer_activation_leaky_relu(alpha = 0.01)

# ELU(Exponential Linear Unit)
# 优点:输出均值接近0,加速收敛
model_elu <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 64, activation = "elu", input_shape = c(100))

# SELU(Scaled ELU)
# 优点:自归一化特性
model_selu <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 64, activation = "selu", input_shape = c(100))

2.3.2 Sigmoid和Tanh

# Sigmoid
# f(x) = 1 / (1 + e^(-x))
# 输出范围:(0, 1)
# 适用场景:二分类输出层、门控机制

model_sigmoid <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 1, activation = "sigmoid", input_shape = c(100))

# Tanh
# f(x) = (e^x - e^(-x)) / (e^x + e^(-x))
# 输出范围:(-1, 1)
# 优点:零中心化
# 适用场景:隐藏层(较少使用)、RNN

model_tanh <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 64, activation = "tanh", input_shape = c(100))

2.3.3 Softmax

# Softmax
# f(x_i) = e^(x_i) / Σe^(x_j)
# 输出:概率分布,所有输出和为1
# 适用场景:多分类输出层

model_softmax <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 10, activation = "softmax", input_shape = c(100))

# 多分类示例:10类疾病分类
# 输出:每个类别的概率

2.3.4 激活函数选择指南

层位置 推荐激活函数 理由
隐藏层(首选) ReLU 计算快,效果好
隐藏层(替代) LeakyReLU/ELU 避免死亡神经元
二分类输出层 Sigmoid 输出概率
多分类输出层 Softmax 输出概率分布
回归输出层 无/Linear 直接输出数值
RNN隐藏层 Tanh 梯度流动稳定

意义与使用场景

核心意义:激活函数决定了神经元的输出特性,选择合适的激活函数可以加速训练并提高模型性能。

举例:在医学影像二分类任务(正常/异常)中,输出层使用Sigmoid函数可以直接输出患病概率。

为什么隐藏层首选ReLU?

ReLU的计算非常简单(max(0, x)),只需要判断输入是否大于0。这使得训练速度比Sigmoid和Tanh快很多。同时,ReLU在正区间的梯度恒为1,不会出现梯度消失问题。对于深层网络,这一点尤为重要。在医学影像分析中,网络通常很深(几十甚至上百层),ReLU的优势更加明显。

什么时候需要其他激活函数?

  1. 输出层需要根据任务选择:二分类用Sigmoid,多分类用Softmax。
  2. 如果发现大量神经元”死亡”(输出恒为0),可以尝试LeakyReLU或ELU。
  3. 在RNN中,Tanh仍然常用,因为它可以保持梯度稳定。

需要注意什么?

使用ReLU时,权重初始化很重要。推荐使用He初始化(Keras中为”he_normal”),这是专门为ReLU设计的初始化方法。

2.4 损失函数

损失函数衡量模型预测与真实值之间的差距,是模型优化的目标。

2.4.1 回归损失函数

# 均方误差(MSE - Mean Squared Error)
# L = (1/n) * Σ(y_true - y_pred)^2
# 适用场景:回归问题,对异常值敏感

model_mse <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 1, input_shape = c(10))

model_mse %>% keras::compile(
  optimizer = "adam",
  loss = "mse"
)

# 平均绝对误差(MAE - Mean Absolute Error)
# L = (1/n) * Σ|y_true - y_pred|
# 适用场景:回归问题,对异常值不敏感

model_mae <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 1, input_shape = c(10))

model_mae %>% keras::compile(
  optimizer = "adam",
  loss = "mae"
)

# Huber Loss
# 结合MSE和MAE的优点
# 对异常值鲁棒,同时在零附近有良好的梯度

model_huber <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 1, input_shape = c(10))

model_huber %>% keras::compile(
  optimizer = "adam",
  loss = keras::loss_huber(delta = 1.0)
)

2.4.2 分类损失函数

# 二元交叉熵(Binary Cross-Entropy)
# L = -(1/n) * Σ[y*log(ŷ) + (1-y)*log(1-ŷ)]
# 适用场景:二分类问题

model_binary <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 1, activation = "sigmoid", input_shape = c(10))

model_binary %>% keras::compile(
  optimizer = "adam",
  loss = "binary_crossentropy",
  metrics = c("accuracy")
)

# 分类交叉熵(Categorical Cross-Entropy)
# L = -(1/n) * ΣΣ y_ij * log(ŷ_ij)
# 适用场景:多分类问题(标签已one-hot编码)

model_categorical <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 5, activation = "softmax", input_shape = c(10))

model_categorical %>% keras::compile(
  optimizer = "adam",
  loss = "categorical_crossentropy",
  metrics = c("accuracy")
)

# 稀疏分类交叉熵(Sparse Categorical Cross-Entropy)
# 适用场景:多分类问题(标签为整数编码)

model_sparse <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 5, activation = "softmax", input_shape = c(10))

model_sparse %>% keras::compile(
  optimizer = "adam",
  loss = "sparse_categorical_crossentropy",
  metrics = c("accuracy")
)

2.4.3 自定义损失函数

# 自定义损失函数示例:加权MSE
# 用于处理不平衡数据,给少数类更高的权重

weighted_mse <- function(y_true, y_pred) {
  # 假设我们想给较大的目标值更高的权重
  weights <- tensorflow::tf$where(
    tensorflow::tf$greater(y_true, 0.5),
    tensorflow::tf$ones_like(y_true) * 2.0,  # 大于0.5的样本权重为2
    tensorflow::tf$ones_like(y_true)         # 其他样本权重为1
  )
  
  squared_error <- tensorflow::tf$square(y_true - y_pred)
  weighted_error <- weights * squared_error
  return(tensorflow::tf$reduce_mean(weighted_error))
}

# 使用自定义损失函数
model_custom <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 1, input_shape = c(10))

model_custom %>% keras::compile(
  optimizer = "adam",
  loss = weighted_mse
)

2.4.4 损失函数选择指南

任务类型 标签格式 推荐损失函数
回归 连续值 MSE / MAE / Huber
二分类 0/1 binary_crossentropy
多分类 one-hot编码 categorical_crossentropy
多分类 整数编码 sparse_categorical_crossentropy
不平衡分类 - 加权损失 / focal loss

意义与使用场景

核心意义:损失函数定义了模型优化的目标,选择合适的损失函数对模型性能至关重要。

举例:在预测患者住院天数的回归任务中,如果数据存在异常值(极长住院时间),应该使用MAE或Huber损失而非MSE。

为什么回归任务常用MSE?

MSE对误差进行平方,这意味着大误差会受到更大的惩罚。在很多情况下,这是我们想要的——让模型更关注那些预测偏差大的样本。但在医学数据中,异常值很常见(比如个别患者的极端情况),这时MSE可能过于关注这些异常值,导致模型整体性能下降。

如何处理医学数据中的不平衡问题?

医学数据往往存在类别不平衡,比如罕见病的阳性样本很少。这时可以使用加权损失函数,给少数类更高的权重。例如,在癌症筛查任务中,可以将阳性样本的损失权重设为阴性样本的5-10倍。

需要注意什么?

损失函数必须可微分,这样优化器才能计算梯度。自定义损失函数时要注意数值稳定性,比如避免log(0)。

2.5 优化器

优化器负责根据损失函数的梯度更新模型参数,是模型训练的核心组件。

2.5.1 SGD及其变体

# 随机梯度下降(SGD)
# 最基础的优化器
# θ = θ - lr * ∇L

optimizer_sgd <- keras::optimizer_sgd(
  learning_rate = 0.01
)

# SGD with Momentum
# 加入动量,加速收敛,减少震荡
# v = γv + lr * ∇L
# θ = θ - v

optimizer_momentum <- keras::optimizer_sgd(
  learning_rate = 0.01,
  momentum = 0.9  # 动量系数,通常设为0.9
)

# Nesterov Accelerated Gradient (NAG)
# 先预测下一步位置,再计算梯度
optimizer_nag <- keras::optimizer_sgd(
  learning_rate = 0.01,
  momentum = 0.9,
  nesterov = TRUE
)

2.5.2 自适应学习率优化器

# Adam(Adaptive Moment Estimation)
# 结合Momentum和RMSprop的优点
# 最常用的优化器,适合大多数任务

optimizer_adam <- keras::optimizer_adam(
  learning_rate = 0.001,
  beta_1 = 0.9,    # 一阶矩估计的衰减率
  beta_2 = 0.999,  # 二阶矩估计的衰减率
  epsilon = 1e-7   # 数值稳定性常数
)

# RMSprop
# 自适应学习率,适合非平稳目标
# 常用于RNN

optimizer_rmsprop <- keras::optimizer_rmsprop(
  learning_rate = 0.001,
  rho = 0.9
)

# Adagrad
# 对频繁更新的参数使用较小的学习率
# 适合稀疏数据(如NLP)

optimizer_adagrad <- keras::optimizer_adagrad(
  learning_rate = 0.01
)

# Adamax
# Adam的变体,对梯度范数更鲁棒

optimizer_adamax <- keras::optimizer_adamax(
  learning_rate = 0.002
)

# Nadam
# Adam + Nesterov动量

optimizer_nadam <- keras::optimizer_nadam(
  learning_rate = 0.001
)

2.5.3 学习率调度

# 学习率衰减
# 随着训练进行,逐渐减小学习率

# 方法1:指数衰减
lr_schedule_exponential <- keras::learning_rate_schedule_exponential_decay(
  initial_learning_rate = 0.1,
  decay_steps = 1000,
  decay_rate = 0.96
)

optimizer_exp <- keras::optimizer_sgd(learning_rate = lr_schedule_exponential)

# 方法2:余弦衰减
lr_schedule_cosine <- keras::learning_rate_schedule_cosine_decay(
  initial_learning_rate = 0.1,
  decay_steps = 1000
)

optimizer_cosine <- keras::optimizer_sgd(learning_rate = lr_schedule_cosine)

# 方法3:自定义学习率调度函数
custom_lr_schedule <- function(epoch, lr) {
  if (epoch > 50) {
    return(lr * 0.1)
  }
  return(lr)
}

# 在回调中使用(稍后详细介绍)
lr_callback <- keras::callback_learning_rate_scheduler(custom_lr_schedule)

2.5.4 优化器选择指南

优化器 优点 缺点 适用场景
SGD 简单、可控 收敛慢、需调参 简单任务、调试
SGD+Momentum 加速收敛 仍需调学习率 深层网络、CV
Adam 自适应、快速 可能不收敛 通用首选
RMSprop 适合非平稳 需调参数 RNN、强化学习
Adagrad 适合稀疏数据 学习率递减 NLP

意义与使用场景

核心意义:优化器决定了模型如何从数据中学习,选择合适的优化器可以加速收敛并提高最终性能。

举例:在训练医学影像分类模型时,Adam通常是首选,因为它对初始学习率不敏感,且收敛快。

为什么Adam是首选?

Adam结合了Momentum(加速收敛)和RMSprop(自适应学习率)的优点。它会为每个参数维护一个独立的学习率,频繁更新的参数使用较小的学习率,不常更新的参数使用较大的学习率。这种自适应特性使得Adam对超参数不太敏感,在大多数任务上都能取得不错的效果。对于医学影像分析这种复杂任务,Adam可以减少调参时间。

什么时候考虑其他优化器?

  1. 如果模型在训练后期震荡不收敛,可以尝试SGD with Momentum,配合学习率衰减。
  2. 对于RNN/LSTM模型,RMSprop可能更稳定。
  3. 如果追求最佳性能,可以先用Adam快速找到好的初始点,再用SGD微调。

需要注意什么?

Adam的默认参数(lr=0.001, beta_1=0.9, beta_2=0.999)在大多数情况下都有效,不需要调整。但如果发现模型不收敛,可以尝试降低学习率。

2.6 评估指标

评估指标用于衡量模型性能,与损失函数不同,它们不参与优化过程。

2.6.1 分类指标

# 准确率(Accuracy)
# 正确预测的比例
# 适用场景:类别平衡的分类问题

model <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 10, activation = "softmax", input_shape = c(100))

model %>% keras::compile(
  optimizer = "adam",
  loss = "categorical_crossentropy",
  metrics = c("accuracy")
)

# 二分类准确率
model_binary <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 1, activation = "sigmoid", input_shape = c(100))

model_binary %>% keras::compile(
  optimizer = "adam",
  loss = "binary_crossentropy",
  metrics = c("accuracy")
)

# AUC(Area Under ROC Curve)
# 适用于不平衡数据
# 衡量模型区分正负类的能力

model_auc <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 1, activation = "sigmoid", input_shape = c(100))

model_auc %>% keras::compile(
  optimizer = "adam",
  loss = "binary_crossentropy",
  metrics = c("AUC")
)

# Top-k准确率
# 预测的前k个类别中包含正确答案的比例
# 适用场景:多分类,允许一定容错

model_topk <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 10, activation = "softmax", input_shape = c(100))

model_topk %>% keras::compile(
  optimizer = "adam",
  loss = "categorical_crossentropy",
  metrics = c("accuracy", "top_k_categorical_accuracy")
)

2.6.2 回归指标

# 平均绝对误差(MAE)
model_reg <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 1, input_shape = c(100))

model_reg %>% keras::compile(
  optimizer = "adam",
  loss = "mse",
  metrics = c("mae")
)

# 均方误差(MSE)
model_reg %>% keras::compile(
  optimizer = "adam",
  loss = "mse",
  metrics = c("mse", "mae")
)

2.6.3 自定义指标

# 自定义指标:灵敏度(Sensitivity / Recall)
# 真阳性率 = TP / (TP + FN)
# 在医学诊断中很重要,表示检出阳性样本的能力

sensitivity <- function(y_true, y_pred) {
  # 将预测值转换为二元标签
  y_pred_binary <- tensorflow::tf$cast(
    tensorflow::tf$greater(y_pred, 0.5),
    tensorflow::tf$float32
  )
  
  # 计算真阳性
  true_positives <- tensorflow::tf$reduce_sum(
    y_true * y_pred_binary
  )
  
  # 计算实际阳性数
  actual_positives <- tensorflow::tf$reduce_sum(y_true)
  
  # 返回灵敏度
  return(true_positives / (actual_positives + tensorflow::tf$constant(1e-7)))
}

# 自定义指标:特异度(Specificity)
# 真阴性率 = TN / (TN + FP)
# 表示正确识别阴性样本的能力

specificity <- function(y_true, y_pred) {
  y_pred_binary <- tensorflow::tf$cast(
    tensorflow::tf$greater(y_pred, 0.5),
    tensorflow::tf$float32
  )
  
  # 计算真阴性
  true_negatives <- tensorflow::tf$reduce_sum(
    (1 - y_true) * (1 - y_pred_binary)
  )
  
  # 计算实际阴性数
  actual_negatives <- tensorflow::tf$reduce_sum(1 - y_true)
  
  return(true_negatives / (actual_negatives + tensorflow::tf$constant(1e-7)))
}

# 使用自定义指标
model_custom <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 64, activation = "relu", input_shape = c(100)) %>%
  keras::layer_dense(units = 1, activation = "sigmoid")

model_custom %>% keras::compile(
  optimizer = "adam",
  loss = "binary_crossentropy",
  metrics = c("accuracy", "AUC", sensitivity, specificity)
)

2.6.4 医学AI常用指标

指标 公式 意义 医学场景
灵敏度(Sensitivity) TP/(TP+FN) 检出阳性能力 筛查试验
特异度(Specificity) TN/(TN+FP) 排除阴性能力 确诊试验
阳性预测值(PPV) TP/(TP+FP) 阳性结果的可靠性 诊断报告
阴性预测值(NPV) TN/(TN+FN) 阴性结果的可靠性 排除诊断
F1分数 2×P×R/(P+R) 精确率和召回率的调和平均 不平衡数据

意义与使用场景

核心意义:评估指标反映模型的实际性能,选择与业务目标一致的指标至关重要。

举例:在癌症筛查任务中,灵敏度(不漏诊)比特异度(不误诊)更重要;而在确诊试验中则相反。

为什么医学AI需要关注多个指标?

医学诊断的后果很严重。漏诊可能导致患者错过最佳治疗时机,误诊可能导致患者承受不必要的治疗和心理负担。因此,我们需要同时关注灵敏度和特异度,根据具体应用场景权衡两者。例如,癌症筛查优先保证高灵敏度,而确诊试验则需要高特异度。

如何选择评估指标?

  1. 类别平衡的分类:准确率即可。
  2. 不平衡分类:AUC、F1分数。
  3. 医学筛查:灵敏度优先。
  4. 医学确诊:特异度优先。
  5. 回归任务:MAE、MSE、R²。

需要注意什么?

评估指标和损失函数可能不一致。例如,分类任务常用交叉熵作为损失函数,但评估时看准确率。这是因为交叉熵可微分,而准确率不可微分。

第三章 Keras 与 TensorFlow 基础建模

本章介绍如何使用Keras和TensorFlow构建、训练、评估和保存深度学习模型。

3.1 顺序模型(Sequential)构建方式

顺序模型是最简单的模型构建方式,适用于层的线性堆叠。

3.1.1 基本构建方法

# 方法1:管道符方式(推荐)
model <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 64, activation = "relu", input_shape = c(100)) %>%
  keras::layer_dropout(rate = 0.5) %>%
  keras::layer_dense(units = 32, activation = "relu") %>%
  keras::layer_dropout(rate = 0.3) %>%
  keras::layer_dense(units = 10, activation = "softmax")

# 方法2:逐层添加
model <- keras::keras_model_sequential()
model %>%
  keras::layer_dense(units = 64, activation = "relu", input_shape = c(100)) %>%
  keras::layer_dropout(rate = 0.5) %>%
  keras::layer_dense(units = 32, activation = "relu") %>%
  keras::layer_dense(units = 10, activation = "softmax")

# 查看模型结构
summary(model)

3.1.2 输入形状说明

# input_shape参数说明
# - 不包含批次维度
# - 可以用NA表示可变维度

# 固定长度输入
model_fixed <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 32, input_shape = c(100))  # 输入是长度为100的向量

# 图像输入(64x64 RGB图像)
model_image <- keras::keras_model_sequential() %>%
  keras::layer_conv_2d(
    filters = 32,
    kernel_size = c(3, 3),
    input_shape = c(64, 64, 3)  # 高×宽×通道
  )

# 可变长度序列输入
model_variable <- keras::keras_model_sequential() %>%
  keras::layer_lstm(
    units = 64,
    input_shape = c(NA, 10)  # NA表示序列长度可变,10是特征维度
  )

3.1.3 完整示例:分类模型

# 构建一个完整的分类模型
# 任务:根据100个特征预测10个类别

model <- keras::keras_model_sequential(
  name = "classification_model"
) %>%
  # 第一隐藏层
  keras::layer_dense(
    units = 128,
    activation = "relu",
    input_shape = c(100),
    name = "hidden_1"
  ) %>%
  keras::layer_batch_normalization(name = "bn_1") %>%
  keras::layer_dropout(rate = 0.4, name = "dropout_1") %>%
  
  # 第二隐藏层
  keras::layer_dense(
    units = 64,
    activation = "relu",
    name = "hidden_2"
  ) %>%
  keras::layer_batch_normalization(name = "bn_2") %>%
  keras::layer_dropout(rate = 0.3, name = "dropout_2") %>%
  
  # 第三隐藏层
  keras::layer_dense(
    units = 32,
    activation = "relu",
    name = "hidden_3"
  ) %>%
  
  # 输出层
  keras::layer_dense(
    units = 10,
    activation = "softmax",
    name = "output"
  )

# 查看模型摘要
summary(model)

意义与使用场景

核心意义:顺序模型提供了最简洁的模型构建方式,适合大多数常见的深度学习任务。

举例:在构建一个基于临床指标的疾病预测模型时,顺序模型可以快速实现从输入特征到输出预测的映射。

什么时候使用顺序模型?

顺序模型适合层的线性堆叠,即每一层只有一个输入和一个输出。这在医学数据分析中很常见,比如: 1. 基于血液指标预测疾病风险 2. 基于基因表达谱进行癌症分类 3. 基于电子病历特征预测住院时长

什么时候需要函数式API?

当模型结构更复杂时,需要使用函数式API: 1. 多输入模型:同时处理影像和临床数据 2. 多输出模型:同时预测疾病类型和严重程度 3. 跳跃连接:ResNet风格的残差连接 4. 共享层:多个输入共享同一特征提取层

需要注意什么?

顺序模型要求输入形状在第一层就确定。如果需要更灵活的输入处理,考虑函数式API。

3.2 函数式 API(Functional API)

函数式API提供了更灵活的模型构建方式,支持多输入、多输出和复杂的网络拓扑。

3.2.1 基本用法

# 函数式API基本示例
# 定义输入层
input_layer <- keras::layer_input(shape = c(100), name = "input")

# 定义隐藏层
hidden1 <- keras::layer_dense(units = 64, activation = "relu", name = "hidden1")(input_layer)
hidden2 <- keras::layer_dense(units = 32, activation = "relu", name = "hidden2")(hidden1)

# 定义输出层
output_layer <- keras::layer_dense(units = 10, activation = "softmax", name = "output")(hidden2)

# 创建模型
model <- keras::keras_model(
  inputs = input_layer,
  outputs = output_layer,
  name = "functional_model"
)

summary(model)

3.2.2 多输入模型

# 多输入模型示例
# 场景:同时处理影像特征和临床指标

# 影像特征输入(假设已提取为向量)
image_input <- keras::layer_input(shape = c(512), name = "image_features")

# 临床指标输入
clinical_input <- keras::layer_input(shape = c(20), name = "clinical_features")

# 影像特征处理分支
image_branch <- image_input %>%
  keras::layer_dense(units = 256, activation = "relu") %>%
  keras::layer_dropout(rate = 0.3) %>%
  keras::layer_dense(units = 128, activation = "relu")

# 临床指标处理分支
clinical_branch <- clinical_input %>%
  keras::layer_dense(units = 64, activation = "relu") %>%
  keras::layer_dropout(rate = 0.2) %>%
  keras::layer_dense(units = 32, activation = "relu")

# 合并两个分支
concatenated <- keras::layer_concatenate(c(image_branch, clinical_branch))

# 共享的全连接层
shared <- concatenated %>%
  keras::layer_dense(units = 64, activation = "relu") %>%
  keras::layer_dropout(rate = 0.3)

# 输出层
output <- keras::layer_dense(units = 5, activation = "softmax", name = "diagnosis")(shared)

# 创建多输入模型
multi_input_model <- keras::keras_model(
  inputs = c(image_input, clinical_input),
  outputs = output,
  name = "multimodal_diagnosis"
)

summary(multi_input_model)

3.2.3 多输出模型

# 多输出模型示例
# 场景:同时预测疾病类型和严重程度

# 输入层
input_layer <- keras::layer_input(shape = c(100), name = "patient_features")

# 共享特征提取层
shared_features <- input_layer %>%
  keras::layer_dense(units = 128, activation = "relu") %>%
  keras::layer_dropout(rate = 0.3) %>%
  keras::layer_dense(units = 64, activation = "relu")

# 输出1:疾病类型分类(5类)
disease_output <- keras::layer_dense(
  units = 5,
  activation = "softmax",
  name = "disease_type"
)(shared_features)

# 输出2:严重程度回归(0-10分)
severity_output <- keras::layer_dense(
  units = 1,
  activation = "linear",
  name = "severity_score"
)(shared_features)

# 创建多输出模型
multi_output_model <- keras::keras_model(
  inputs = input_layer,
  outputs = c(disease_output, severity_output),
  name = "multi_task_model"
)

summary(multi_output_model)

# 编译多输出模型(每个输出可以有不同的损失函数)
multi_output_model %>% keras::compile(
  optimizer = "adam",
  loss = list(
    disease_type = "categorical_crossentropy",
    severity_score = "mse"
  ),
  loss_weights = list(
    disease_type = 1.0,
    severity_score = 0.5  # 可以调整不同任务的权重
  ),
  metrics = list(
    disease_type = c("accuracy"),
    severity_score = c("mae")
  )
)

3.2.4 残差连接(Skip Connection)

# 残差连接示例(ResNet风格)
# 解决深层网络的梯度消失问题

# 输入层
input_layer <- keras::layer_input(shape = c(64))

# 第一个残差块
x <- keras::layer_dense(units = 64, activation = "relu")(input_layer)
x <- keras::layer_batch_normalization()(x)
x <- keras::layer_dropout(rate = 0.2)(x)
x <- keras::layer_dense(units = 64, activation = "relu")(x)
x <- keras::layer_batch_normalization()(x)

# 残差连接:将输入直接加到输出
residual1 <- keras::layer_add(c(x, input_layer))
residual1 <- keras::layer_activation("relu")(residual1)

# 第二个残差块
x <- keras::layer_dense(units = 64, activation = "relu")(residual1)
x <- keras::layer_batch_normalization()(x)
x <- keras::layer_dropout(rate = 0.2)(x)
x <- keras::layer_dense(units = 64, activation = "relu")(x)
x <- keras::layer_batch_normalization()(x)

# 残差连接
residual2 <- keras::layer_add(c(x, residual1))
residual2 <- keras::layer_activation("relu")(residual2)

# 输出层
output <- keras::layer_dense(units = 10, activation = "softmax")(residual2)

# 创建残差网络模型
resnet_model <- keras::keras_model(
  inputs = input_layer,
  outputs = output,
  name = "residual_network"
)

summary(resnet_model)

意义与使用场景

核心意义:函数式API提供了构建复杂神经网络架构的能力,是现代深度学习开发的核心技能。

举例:在多模态医学诊断中,需要同时处理影像、文本和结构化数据,函数式API可以轻松实现这种复杂架构。

为什么医学AI常需要多输入模型?

医学诊断通常需要综合多种信息。例如,肺癌诊断可能需要: 1. CT影像特征(高维向量) 2. 临床指标(年龄、吸烟史、家族史等) 3. 实验室检查结果(血液指标)

这些数据的维度和特征各不相同,需要分别处理后再融合。函数式API可以轻松实现这种”分而治之”再”合而为一”的架构。

多输出模型有什么优势?

多输出模型(多任务学习)可以利用任务之间的相关性提高泛化能力。例如,预测疾病类型和预测严重程度是相关的任务,共享特征提取层可以让模型学习到更通用的特征表示。

需要注意什么?

多输出模型需要为每个输出指定损失函数和权重,确保各任务之间的平衡。

3.3 模型编译(compile)

编译模型是配置学习过程的关键步骤,需要指定优化器、损失函数和评估指标。

3.3.1 基本编译

# 创建模型
model <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 64, activation = "relu", input_shape = c(100)) %>%
  keras::layer_dense(units = 10, activation = "softmax")

# 基本编译(使用字符串指定)
model %>% keras::compile(
  optimizer = "adam",
  loss = "categorical_crossentropy",
  metrics = c("accuracy")
)

3.3.2 详细配置

# 详细配置优化器
model %>% keras::compile(
  optimizer = keras::optimizer_adam(
    learning_rate = 0.001,
    beta_1 = 0.9,
    beta_2 = 0.999,
    epsilon = 1e-7
  ),
  loss = "categorical_crossentropy",
  metrics = c("accuracy", "AUC")
)

# 使用自定义损失函数
custom_loss <- function(y_true, y_pred) {
  # 加权交叉熵
  weights <- tensorflow::tf$where(
    tensorflow::tf$greater(y_true, 0.5),
    tensorflow::tf$constant(2.0),
    tensorflow::tf$constant(1.0)
  )
  loss <- tensorflow::tf$keras$losses$categorical_crossentropy(y_true, y_pred)
  return(tensorflow::tf$reduce_mean(weights * loss))
}

model %>% keras::compile(
  optimizer = keras::optimizer_adam(learning_rate = 0.001),
  loss = custom_loss,
  metrics = c("accuracy")
)

3.3.3 不同任务类型的编译配置

# 二分类任务
binary_model <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 64, activation = "relu", input_shape = c(100)) %>%
  keras::layer_dense(units = 1, activation = "sigmoid")

binary_model %>% keras::compile(
  optimizer = "adam",
  loss = "binary_crossentropy",
  metrics = c("accuracy", "AUC")
)

# 多分类任务(标签为整数)
multiclass_model <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 64, activation = "relu", input_shape = c(100)) %>%
  keras::layer_dense(units = 5, activation = "softmax")

multiclass_model %>% keras::compile(
  optimizer = "adam",
  loss = "sparse_categorical_crossentropy",  # 标签为整数而非one-hot
  metrics = c("accuracy")
)

# 回归任务
regression_model <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 64, activation = "relu", input_shape = c(100)) %>%
  keras::layer_dense(units = 1)  # 无激活函数

regression_model %>% keras::compile(
  optimizer = "adam",
  loss = "mse",
  metrics = c("mae")
)

# 多标签分类任务
multilabel_model <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 64, activation = "relu", input_shape = c(100)) %>%
  keras::layer_dense(units = 5, activation = "sigmoid")  # 每个类别独立预测

multilabel_model %>% keras::compile(
  optimizer = "adam",
  loss = "binary_crossentropy",  # 每个标签独立计算二元交叉熵
  metrics = c("accuracy")
)

意义与使用场景

核心意义:编译配置决定了模型如何学习,正确配置是模型训练成功的基础。

举例:在医学影像多分类任务中,需要根据标签格式选择正确的损失函数。

如何选择编译配置?

  1. 优化器:大多数情况下Adam即可,学习率可设为0.001。
  2. 损失函数:根据任务类型和标签格式选择。
  3. 指标:选择与业务目标相关的指标。

常见错误

  1. 使用categorical_crossentropy但标签是整数(应使用sparse_categorical_crossentropy)
  2. 多标签分类使用categorical_crossentropy(应使用binary_crossentropy)
  3. 回归任务输出层使用激活函数(通常不需要)

3.4 模型训练(fit)

fit()函数用于训练模型,是最常用的训练方法。

3.4.1 基本训练

# 生成示例数据
set.seed(42)
x_train <- matrix(runif(1000 * 100), nrow = 1000, ncol = 100)
y_train <- keras::to_categorical(sample(0:9, 1000, replace = TRUE), num_classes = 10)

x_val <- matrix(runif(200 * 100), nrow = 200, ncol = 100)
y_val <- keras::to_categorical(sample(0:9, 200, replace = TRUE), num_classes = 10)

# 创建模型
model <- keras::keras_model_sequential() %>%
  keras::layer_dense(units = 64, activation = "relu", input_shape = c(100)) %>%
  keras::layer_dropout(rate = 0.5) %>%
  keras::layer_dense(units = 10, activation = "softmax")

model %>% keras::compile(
  optimizer = "adam",
  loss = "categorical_crossentropy",
  metrics = c("accuracy")
)

# 基本训练
history <- model %>% keras::fit(
  x = x_train,
  y = y_train,
  epochs = 10,
  batch_size = 32,
  validation_data = list(x_val, y_val)
)

# 绘制训练曲线
plot(history)

3.4.2 训练参数详解

# 详细参数配置
history <- model %>% keras::fit(
  # 数据
  x = x_train,
  y = y_train,
  
  # 训练轮次
  epochs = 100,
  
  # 批次大小
  batch_size = 32,  # 每次更新使用的样本数
  
  # 验证数据
  validation_data = list(x_val, y_val),
  # 或者使用验证比例
  # validation_split = 0.2,  # 从训练数据中划分20%作为验证集
  
  # 是否打乱数据
  shuffle = TRUE,
  
  # 详细程度
  verbose = 1,  # 0=静默,1=进度条,2=每轮一行
  
  # 回调函数(稍后详细介绍)
  callbacks = list(
    keras::callback_early_stopping(patience = 10),
    keras::callback_model_checkpoint("best_model.h5", save_best_only = TRUE)
  )
)

3.4.3 使用回调函数

# 常用回调函数

# 1. 早停法
early_stop <- keras::callback_early_stopping(
  monitor = "val_loss",      # 监控指标
  patience = 10,             # 等待轮次
  restore_best_weights = TRUE  # 恢复最佳权重
)

# 2. 模型检查点
checkpoint <- keras::callback_model_checkpoint(
  filepath = "model_{epoch:02d}_{val_loss:.4f}.h5",
  monitor = "val_loss",
  save_best_only = TRUE,
  mode = "min"
)

# 3. 学习率调度
lr_scheduler <- keras::callback_reduce_lr_on_plateau(
  monitor = "val_loss",
  factor = 0.5,      # 学习率乘以0.5
  patience = 5,      # 等待5轮
  min_lr = 1e-6      # 最小学习率
)

# 4. TensorBoard日志
tensorboard <- keras::callback_tensorboard(
  log_dir = "logs",
  histogram_freq = 1
)

# 5. 自定义回调
custom_callback <- keras::callback_lambda(
  on_epoch_end = function(epoch, logs) {
    if (epoch %% 10 == 0) {
      cat(sprintf("Epoch %d: loss = %.4f, accuracy = %.4f\n",
                  epoch, logs$loss, logs$accuracy))
    }
  }
)

# 使用回调函数训练
history <- model %>% keras::fit(
  x_train, y_train,
  epochs = 100,
  batch_size = 32,
  validation_data = list(x_val, y_val),
  callbacks = list(early_stop, lr_scheduler)
)

3.4.4 训练曲线分析

# 分析训练历史
print(history)

# 获取训练指标
train_loss <- history$metrics$loss
val_loss <- history$metrics$val_loss
train_acc <- history$metrics$accuracy
val_acc <- history$metrics$val_accuracy

# 创建训练曲线数据框
history_df <- data.frame(
  epoch = 1:length(train_loss),
  train_loss = train_loss,
  val_loss = val_loss,
  train_acc = train_acc,
  val_acc = val_acc
)

# 绘制损失曲线
loss_plot <- ggplot2::ggplot(history_df, ggplot2::aes(x = epoch)) +
  ggplot2::geom_line(ggplot2::aes(y = train_loss, color = "Train")) +
  ggplot2::geom_line(ggplot2::aes(y = val_loss, color = "Validation")) +
  ggplot2::labs(
    title = "训练与验证损失",
    x = "Epoch",
    y = "Loss",
    color = "数据集"
  ) +
  ggplot2::theme_minimal()

print(loss_plot)

# 绘制准确率曲线
acc_plot <- ggplot2::ggplot(history_df, ggplot2::aes(x = epoch)) +
  ggplot2::geom_line(ggplot2::aes(y = train_acc, color = "Train")) +
  ggplot2::geom_line(ggplot2::aes(y = val_acc, color = "Validation")) +
  ggplot2::labs(
    title = "训练与验证准确率",
    x = "Epoch",
    y = "Accuracy",
    color = "数据集"
  ) +
  ggplot2::theme_minimal()

print(acc_plot)

意义与使用场景

核心意义:fit()函数是模型训练的核心,合理配置训练参数可以加速收敛并提高模型性能。

举例:在训练医学影像分类模型时,需要设置合适的epochs和batch_size,并使用早停法防止过拟合。

如何选择batch_size?

batch_size影响训练速度和模型性能: 1. 小batch(16-32):梯度估计更准确,但训练慢