在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环境中配置深度学习框架。
神经元是神经网络的基本计算单元,它模拟生物神经元的工作方式。
数学定义:
\[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影像中,神经元可以学习识别结节边缘的特征。
研究目的是什么?
可能得到什么样的结果?
神经元输出一个数值,表示对特定特征的响应强度。高输出值表示该特征明显存在,低输出值表示特征不明显或不存在。多个神经元的组合可以形成更复杂的特征表示。
需要注意什么?
单个神经元的能力有限,只能学习线性可分的模式。实际应用中需要多层神经元的组合,即深度神经网络,才能处理复杂的医学影像问题。
层是神经网络的构建模块,由多个神经元组成。常见的层类型包括:
| 层类型 | 功能 | 典型应用 |
|---|---|---|
| 全连接层(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()
激活函数为神经网络引入非线性,使网络能够学习复杂的模式。
常用激活函数对比:
| 激活函数 | 公式 | 特点 | 适用场景 |
|---|---|---|---|
| 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")
损失函数衡量模型预测值与真实值之间的差距,是模型优化的目标。
常用损失函数:
| 损失函数 | 公式 | 适用场景 |
|---|---|---|
| 均方误差(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")
优化器负责根据损失函数的梯度更新模型参数。
常用优化器对比:
| 优化器 | 特点 | 适用场景 |
|---|---|---|
| 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
)
R语言提供了多个深度学习框架的接口,让R用户也能利用强大的深度学习能力。
| 框架 | 后端 | 特点 | 适用场景 |
|---|---|---|---|
| Keras | TensorFlow | 高级API,简单易用 | 快速原型开发、教学 |
| TensorFlow | TensorFlow | 底层控制,灵活强大 | 生产环境、研究 |
| Torch | PyTorch | 动态计算图,调试友好 | 研究、复杂模型 |
| MXNet | MXNet | 分布式训练,效率高 | 大规模训练 |
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)
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)
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适合研究新算法,比如开发新的分割网络架构。了解多个框架可以根据项目阶段灵活选择。
如何选择框架?
需要注意什么?
框架之间迁移成本较高,建议在项目初期就确定主要框架。同时,不同框架的R接口更新频率不同,Keras和Torch的R接口维护较好。
在安装TensorFlow之前,需要确保系统已安装Python环境。
# 检查Python环境
reticulate::py_available()
# 查看Python版本
reticulate::py_version()
# 查看Python路径
reticulate::py_config()
# 列出所有物理设备
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")
# 设置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))
# )
# 使用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环境?
需要注意什么?
Windows系统配置GPU环境相对复杂,建议使用Linux系统或WSL2。如果GPU内存不足,可以减小batch_size或使用梯度累积。
# 安装torch包
# install.packages("torch")
# 安装PyTorch后端
# torch::install_torch()
# 验证安装
library(torch)
cat("Torch版本:", torch_version(), "\n")
# 检查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")
}
| 场景 | 推荐框架 | 理由 |
|---|---|---|
| 深度学习入门 | Keras | API简洁,文档丰富 |
| 快速原型开发 | Keras | 一行代码定义模型 |
| 生产环境部署 | TensorFlow | 支持TensorFlow Serving |
| 学术研究 | Torch | 动态图,调试方便 |
| 自然语言处理 | Torch/Transformers | Hugging Face生态 |
| 计算机视觉 | Keras/Torch | 都有丰富的预训练模型 |
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 # 项目说明
# 使用配置文件管理超参数
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)
本章深入介绍神经网络的核心组件,包括张量操作、层、激活函数、损失函数和优化器。这些是构建深度学习模型的基础。
张量是深度学习中的基本数据结构,可以理解为多维数组。
| 维度 | 名称 | 示例 |
|---|---|---|
| 0D | 标量(Scalar) | 一个数值,如 5 |
| 1D | 向量(Vector) | 一维数组,如 [1, 2, 3] |
| 2D | 矩阵(Matrix) | 二维数组,如 [[1,2], [3,4]] |
| 3D | 3D张量 | 彩色图像(高×宽×通道) |
| 4D | 4D张量 | 图像批次(批次×高×宽×通道) |
| 5D | 5D张量 | 视频数据(批次×帧×高×宽×通道) |
# 创建张量
# 标量(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")
# 基本运算
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")
# 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)可以一次性完成所有图像的预处理和特征提取。
研究目的是什么?
需要注意什么?
医学影像数据量大,需要注意内存管理。使用数据生成器(generator)可以避免一次性加载所有数据。
层是神经网络的基本构建模块,每层接收输入、进行变换、产生输出。
全连接层是最基本的层类型,每个输入神经元都与每个输出神经元相连。
数学表达:
\[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)
激活层单独添加激活函数,有时比在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)
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只在训练时生效,推理时自动关闭
批归一化层对每个批次的数据进行标准化,加速训练并提高稳定性。
# 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. 或在激活函数之后
核心意义:不同的层类型适合不同的任务,合理组合层是构建有效神经网络的关键。
举例:在构建一个医学影像分类模型时,需要选择合适的层组合。
如何选择层类型?
医学影像项目的层组合建议:
对于CT影像分类任务,典型的层组合是:卷积层→批归一化→ReLU激活→池化层→Dropout。这种组合可以有效提取特征同时防止过拟合。
需要注意什么?
Dropout率不宜过高(一般0.2-0.5),否则可能导致欠拟合。BatchNormalization在训练和推理时行为不同,需要正确设置。
激活函数为神经网络引入非线性,是深度学习能够学习复杂模式的关键。
# 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))
# 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))
# 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类疾病分类
# 输出:每个类别的概率
| 层位置 | 推荐激活函数 | 理由 |
|---|---|---|
| 隐藏层(首选) | ReLU | 计算快,效果好 |
| 隐藏层(替代) | LeakyReLU/ELU | 避免死亡神经元 |
| 二分类输出层 | Sigmoid | 输出概率 |
| 多分类输出层 | Softmax | 输出概率分布 |
| 回归输出层 | 无/Linear | 直接输出数值 |
| RNN隐藏层 | Tanh | 梯度流动稳定 |
核心意义:激活函数决定了神经元的输出特性,选择合适的激活函数可以加速训练并提高模型性能。
举例:在医学影像二分类任务(正常/异常)中,输出层使用Sigmoid函数可以直接输出患病概率。
为什么隐藏层首选ReLU?
ReLU的计算非常简单(max(0, x)),只需要判断输入是否大于0。这使得训练速度比Sigmoid和Tanh快很多。同时,ReLU在正区间的梯度恒为1,不会出现梯度消失问题。对于深层网络,这一点尤为重要。在医学影像分析中,网络通常很深(几十甚至上百层),ReLU的优势更加明显。
什么时候需要其他激活函数?
需要注意什么?
使用ReLU时,权重初始化很重要。推荐使用He初始化(Keras中为”he_normal”),这是专门为ReLU设计的初始化方法。
损失函数衡量模型预测与真实值之间的差距,是模型优化的目标。
# 均方误差(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)
)
# 二元交叉熵(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")
)
# 自定义损失函数示例:加权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
)
| 任务类型 | 标签格式 | 推荐损失函数 |
|---|---|---|
| 回归 | 连续值 | 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)。
优化器负责根据损失函数的梯度更新模型参数,是模型训练的核心组件。
# 随机梯度下降(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
)
# 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
)
# 学习率衰减
# 随着训练进行,逐渐减小学习率
# 方法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)
| 优化器 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| SGD | 简单、可控 | 收敛慢、需调参 | 简单任务、调试 |
| SGD+Momentum | 加速收敛 | 仍需调学习率 | 深层网络、CV |
| Adam | 自适应、快速 | 可能不收敛 | 通用首选 |
| RMSprop | 适合非平稳 | 需调参数 | RNN、强化学习 |
| Adagrad | 适合稀疏数据 | 学习率递减 | NLP |
核心意义:优化器决定了模型如何从数据中学习,选择合适的优化器可以加速收敛并提高最终性能。
举例:在训练医学影像分类模型时,Adam通常是首选,因为它对初始学习率不敏感,且收敛快。
为什么Adam是首选?
Adam结合了Momentum(加速收敛)和RMSprop(自适应学习率)的优点。它会为每个参数维护一个独立的学习率,频繁更新的参数使用较小的学习率,不常更新的参数使用较大的学习率。这种自适应特性使得Adam对超参数不太敏感,在大多数任务上都能取得不错的效果。对于医学影像分析这种复杂任务,Adam可以减少调参时间。
什么时候考虑其他优化器?
需要注意什么?
Adam的默认参数(lr=0.001, beta_1=0.9, beta_2=0.999)在大多数情况下都有效,不需要调整。但如果发现模型不收敛,可以尝试降低学习率。
评估指标用于衡量模型性能,与损失函数不同,它们不参与优化过程。
# 准确率(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")
)
# 平均绝对误差(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")
)
# 自定义指标:灵敏度(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)
)
| 指标 | 公式 | 意义 | 医学场景 |
|---|---|---|---|
| 灵敏度(Sensitivity) | TP/(TP+FN) | 检出阳性能力 | 筛查试验 |
| 特异度(Specificity) | TN/(TN+FP) | 排除阴性能力 | 确诊试验 |
| 阳性预测值(PPV) | TP/(TP+FP) | 阳性结果的可靠性 | 诊断报告 |
| 阴性预测值(NPV) | TN/(TN+FN) | 阴性结果的可靠性 | 排除诊断 |
| F1分数 | 2×P×R/(P+R) | 精确率和召回率的调和平均 | 不平衡数据 |
核心意义:评估指标反映模型的实际性能,选择与业务目标一致的指标至关重要。
举例:在癌症筛查任务中,灵敏度(不漏诊)比特异度(不误诊)更重要;而在确诊试验中则相反。
为什么医学AI需要关注多个指标?
医学诊断的后果很严重。漏诊可能导致患者错过最佳治疗时机,误诊可能导致患者承受不必要的治疗和心理负担。因此,我们需要同时关注灵敏度和特异度,根据具体应用场景权衡两者。例如,癌症筛查优先保证高灵敏度,而确诊试验则需要高特异度。
如何选择评估指标?
需要注意什么?
评估指标和损失函数可能不一致。例如,分类任务常用交叉熵作为损失函数,但评估时看准确率。这是因为交叉熵可微分,而准确率不可微分。
本章介绍如何使用Keras和TensorFlow构建、训练、评估和保存深度学习模型。
顺序模型是最简单的模型构建方式,适用于层的线性堆叠。
# 方法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)
# 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是特征维度
)
# 构建一个完整的分类模型
# 任务:根据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。
函数式API提供了更灵活的模型构建方式,支持多输入、多输出和复杂的网络拓扑。
# 函数式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)
# 多输入模型示例
# 场景:同时处理影像特征和临床指标
# 影像特征输入(假设已提取为向量)
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)
# 多输出模型示例
# 场景:同时预测疾病类型和严重程度
# 输入层
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")
)
)
# 残差连接示例(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可以轻松实现这种”分而治之”再”合而为一”的架构。
多输出模型有什么优势?
多输出模型(多任务学习)可以利用任务之间的相关性提高泛化能力。例如,预测疾病类型和预测严重程度是相关的任务,共享特征提取层可以让模型学习到更通用的特征表示。
需要注意什么?
多输出模型需要为每个输出指定损失函数和权重,确保各任务之间的平衡。
编译模型是配置学习过程的关键步骤,需要指定优化器、损失函数和评估指标。
# 创建模型
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")
)
# 详细配置优化器
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")
)
# 二分类任务
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")
)
核心意义:编译配置决定了模型如何学习,正确配置是模型训练成功的基础。
举例:在医学影像多分类任务中,需要根据标签格式选择正确的损失函数。
如何选择编译配置?
常见错误:
fit()函数用于训练模型,是最常用的训练方法。
# 生成示例数据
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)
# 详细参数配置
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)
)
)
# 常用回调函数
# 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)
)
# 分析训练历史
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)