python学习打卡day50

DAY 50 预训练模型+CBAM模块

知识点回顾:

  1. resnet结构解析
  2. CBAM放置位置的思考
  3. 针对预训练模型的训练策略
    1. 差异化学习率
    2. 三阶段微调

ps:今日的代码训练时长较长,3080ti大概需要40min的训练时长

作业:

  1. 好好理解下resnet18的模型结构
  2. 尝试对vgg16+cbam进行微调策略

1.CBMA定义

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import numpy as np# 定义通道注意力
class ChannelAttention(nn.Module):def __init__(self, in_channels, ratio=16):"""通道注意力机制初始化参数:in_channels: 输入特征图的通道数ratio: 降维比例,用于减少参数量,默认为16"""super().__init__()# 全局平均池化,将每个通道的特征图压缩为1x1,保留通道间的平均值信息self.avg_pool = nn.AdaptiveAvgPool2d(1)# 全局最大池化,将每个通道的特征图压缩为1x1,保留通道间的最显著特征self.max_pool = nn.AdaptiveMaxPool2d(1)# 共享全连接层,用于学习通道间的关系# 先降维(除以ratio),再通过ReLU激活,最后升维回原始通道数self.fc = nn.Sequential(nn.Linear(in_channels, in_channels // ratio, bias=False),  # 降维层nn.ReLU(),  # 非线性激活函数nn.Linear(in_channels // ratio, in_channels, bias=False)   # 升维层)# Sigmoid函数将输出映射到0-1之间,作为各通道的权重self.sigmoid = nn.Sigmoid()def forward(self, x):"""前向传播函数参数:x: 输入特征图,形状为 [batch_size, channels, height, width]返回:调整后的特征图,通道权重已应用"""# 获取输入特征图的维度信息,这是一种元组的解包写法b, c, h, w = x.shape# 对平均池化结果进行处理:展平后通过全连接网络avg_out = self.fc(self.avg_pool(x).view(b, c))# 对最大池化结果进行处理:展平后通过全连接网络max_out = self.fc(self.max_pool(x).view(b, c))# 将平均池化和最大池化的结果相加并通过sigmoid函数得到通道权重attention = self.sigmoid(avg_out + max_out).view(b, c, 1, 1)# 将注意力权重与原始特征相乘,增强重要通道,抑制不重要通道return x * attention #这个运算是pytorch的广播机制## 空间注意力模块
class SpatialAttention(nn.Module):def __init__(self, kernel_size=7):super().__init__()self.conv = nn.Conv2d(2, 1, kernel_size, padding=kernel_size//2, bias=False)self.sigmoid = nn.Sigmoid()def forward(self, x):# 通道维度池化avg_out = torch.mean(x, dim=1, keepdim=True)  # 平均池化:(B,1,H,W)max_out, _ = torch.max(x, dim=1, keepdim=True)  # 最大池化:(B,1,H,W)pool_out = torch.cat([avg_out, max_out], dim=1)  # 拼接:(B,2,H,W)attention = self.conv(pool_out)  # 卷积提取空间特征return x * self.sigmoid(attention)  # 特征与空间权重相乘## CBAM模块
class CBAM(nn.Module):def __init__(self, in_channels, ratio=16, kernel_size=7):super().__init__()self.channel_attn = ChannelAttention(in_channels, ratio)self.spatial_attn = SpatialAttention(kernel_size)def forward(self, x):x = self.channel_attn(x)x = self.spatial_attn(x)return ximport torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import numpy as np# 设置中文字体支持
plt.rcParams["font.family"] = ["SimHei"]
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题# 检查GPU是否可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")# 数据预处理(与原代码一致)
train_transform = transforms.Compose([transforms.RandomCrop(32, padding=4),transforms.RandomHorizontalFlip(),transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),transforms.RandomRotation(15),transforms.ToTensor(),transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])test_transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])# 加载数据集(与原代码一致)
train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=train_transform)
test_dataset = datasets.CIFAR10(root='./data', train=False, transform=test_transform)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)

2.VGG16模型结构

VGG16 的模型结构如下2:

 
  1. 输入层:输入图片的尺寸(imagenet)是 224×224×3,即彩色图像,通道为 3。
  2. 第一层卷积层
    • 卷积:使用 64 个 3×3 的卷积核进行卷积,padding=1,stride=1,输出是 224×224×64。
    • ReLU:将卷积层输出的 FeatureMap 输入到 ReLU 函数中。
    • 卷积:使用 64 个 3×3 的卷积核进行卷积,padding=1,stride=1,输出是 224×224×64。
    • ReLU:将卷积层输出的 FeatureMap 输入到 ReLU 函数中。
    • 池化:使用 2×2,stride=2 的池化单元进行最大池化操作,输出为 112×112×64。
  3. 第二层卷积层
    • 卷积:输入是 112×112×64,使用 128 个 3×3 的卷积核进行卷积,padding=1,stride=1,输出是 112×112×128。
    • ReLU:将卷积层输出的 FeatureMap 输入到 ReLU 函数中。
    • 卷积:输入是 112×112×128,使用 128 个 3×3 的卷积核进行卷积,padding=1,stride=1,输出是 112×112×128。
    • ReLU:将卷积层输出的 FeatureMap 输入到 ReLU 函数中。
    • 池化:使用 2×2,stride=2 的池化单元进行最大池化操作,输出为 56×56×128。
  4. 第三层卷积层
    • 卷积:输入是 56×56×128,使用 256 个 3×3 的卷积核进行卷积,padding=1,stride=1,输出是 56×56×256。
    • ReLU:将卷积层输出的 FeatureMap 输入到 ReLU 函数中。
    • 卷积:输入是 56×56×256,使用 256 个 3×3 的卷积核进行卷积,padding=1,stride=1,输出是 56×56×256。
    • ReLU:将卷积层输出的 FeatureMap 输入到 ReLU 函数中。
    • 卷积:输入是 56×56×256,使用 256 个 3×3 的卷积核进行卷积,padding=1,stride=1,输出是 56×56×256。
    • ReLU:将卷积层输出的 FeatureMap 输入到 ReLU 函数中。
    • 池化:使用 2×2,stride=2 的池化单元进行最大池化操作,输出为 28×28×256。
  5. 第四层卷积层
    • 卷积:输入是 28×28×256,使用 512 个 3×3 的卷积核进行卷积,padding=1,stride=1,输出是 28×28×512。
    • ReLU:将卷积层输出的 FeatureMap 输入到 ReLU 函数中。
    • 卷积:输入是 28×28×512,使用 512 个 3×3 的卷积核进行卷积,padding=1,stride=1,输出是 28×28×512。
    • ReLU:将卷积层输出的 FeatureMap 输入到 ReLU 函数中。
    • 卷积:输入是 28×28×512,使用 512 个 3×3 的卷积核进行卷积,padding=1,stride=1,输出是 28×28×512。
    • ReLU:将卷积层输出的 FeatureMap 输入到 ReLU 函数中。
    • 池化:使用 2×2,stride=2 的池化单元进行最大池化操作,输出为 14×14×512。
  6. 第五层卷积层
    • 卷积:输入是 14×14×512,使用 512 个 3×3 的卷积核进行卷积,padding=1,stride=1,输出是 14×14×512。
    • ReLU:将卷积层输出的 FeatureMap 输入到 ReLU 函数中。
    • 卷积:输入是 14×14×512,使用 512 个 3×3 的卷积核进行卷积,padding=1,stride=1,输出是 14×14×512。
    • ReLU:将卷积层输出的 FeatureMap 输入到 ReLU 函数中。
    • 卷积:输入是 14×14×512,使用 512 个 3×3 的卷积核进行卷积,padding=1,stride=1,输出是 14×14×512。
    • ReLU:将卷积层输出的 FeatureMap 输入到 ReLU 函数中。
    • 池化:使用 2×2,stride=2 的池化单元进行最大池化操作,输出为 7×7×512。
  7. 全连接层
    • FC-4096:第一个全连接层,包含 4096 个神经元(工程上的经验)。
    • FC-4096:第二个全连接层,包含 4096 个神经元。
    • FC-1000:第三个全连接层,包含 1000 个神经元,用于输出分类结果。

3.VGG16_CBAM的定义

class VGG16_CBAM(nn.Module):def __init__(self, num_classes=10, pretrained=True, cbam_ratio=16, cbam_kernel=7):super().__init__()# 加载预训练VGG16vgg16 = models.vgg16(pretrained=pretrained)# 提取特征部分self.features = vgg16.features# 修改首层卷积以适应32x32输入(CIFAR10)self.features[0] = nn.Conv2d(3, 64, kernel_size=3, padding=1)# 在每个最大池化层后添加CBAM模块self.cbam1 = CBAM(in_channels=64, ratio=cbam_ratio, kernel_size=cbam_kernel)self.cbam2 = CBAM(in_channels=128, ratio=cbam_ratio, kernel_size=cbam_kernel)self.cbam3 = CBAM(in_channels=256, ratio=cbam_ratio, kernel_size=cbam_kernel)self.cbam4 = CBAM(in_channels=512, ratio=cbam_ratio, kernel_size=cbam_kernel)self.cbam5 = CBAM(in_channels=512, ratio=cbam_ratio, kernel_size=cbam_kernel)# 分类部分self.classifier = nn.Sequential(nn.Linear(512 * 1 * 1, 4096),  # 因为输入尺寸为32x32,经过多次下采样后变为1x1nn.ReLU(True),nn.Dropout(),nn.Linear(4096, 4096),nn.ReLU(True),nn.Dropout(0.5),nn.Linear(4096, num_classes),)def forward(self, x):# 第一组卷积 + 池化 + CBAMx = self.features[:5](x)  # 到第一个MaxPool2dx = self.cbam1(x)# 第二组卷积 + 池化 + CBAMx = self.features[5:10](x)  # 到第二个MaxPool2dx = self.cbam2(x)# 第三组卷积 + 池化 + CBAMx = self.features[10:17](x)  # 到第三个MaxPool2dx = self.cbam3(x)# 第四组卷积 + 池化 + CBAMx = self.features[17:24](x)  # 到第四个MaxPool2dx = self.cbam4(x)# 第五组卷积 + 池化 + CBAMx = self.features[24:31](x)  # 到第五个MaxPool2dx = self.cbam5(x)# 全局平均池化x = nn.AdaptiveAvgPool2d((1, 1))(x)# 展平并分类x = torch.flatten(x, 1)x = self.classifier(x)return x
# 初始化模型并移至设备
model = VGG16_CBAM().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', patience=3, factor=0.5)

在每一组卷积后+CBAM模块

4.冻结和微调策略

3阶段解冻策略:

  • 阶段 1:epoch=1 时,解冻注意力模块 (cbam) 和分类头 (classifier),使用较高的学习率 (1e-3)
  • 阶段 2:epoch=6 时,解冻高层特征提取层 (features.24 及之后),学习率降低 (1e-4)
  • 阶段 3:epoch=21 时,解冻所有层,进行全局微调,学习率进一步降低 (1e-5)
  • 阶段解冻时机(epoch)解冻模块学习率代码对应逻辑
    阶段 11cbam、classifier1e-3if epoch == 1分支,调用set_trainable_layers并设置优化器
    阶段 26新增 features.24 及之后的高层卷积层1e-4elif epoch == 6分支,扩展解冻范围并降低学习率
    阶段 321所有层1e-5elif epoch == 21分支,设置param.requires_grad = True并进一步降低学习率
import time# ======================================================================
# 4. 结合了分阶段策略和详细打印的训练函数
# ======================================================================
def set_trainable_layers(model, trainable_parts):print(f"\n---> 解冻以下部分并设为可训练: {trainable_parts}")for name, param in model.named_parameters():param.requires_grad = Falsefor part in trainable_parts:if part in name:param.requires_grad = Truebreakdef train_staged_finetuning(model, criterion, train_loader, test_loader, device, epochs):optimizer = None# 初始化历史记录列表all_iter_losses, iter_indices = [], []train_acc_history, test_acc_history = [], []train_loss_history, test_loss_history = [], []for epoch in range(1, epochs + 1):epoch_start_time = time.time()# --- 动态调整学习率和冻结层 ---if epoch == 1:print("\n" + "="*50 + "\n🚀 **阶段 1:解冻注意力模块和分类头**\n" + "="*50)set_trainable_layers(model, ["cbam", "classifier"])optimizer = optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-3)elif epoch == 6:print("\n" + "="*50 + "\n✈️ **阶段 2:解冻高层特征提取层 (features.24及之后)**\n" + "="*50)set_trainable_layers(model, ["cbam", "classifier", "features.24", "features.25", "features.26", "features.27", "features.28", "features.29", "features.30"])optimizer = optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-4)elif epoch == 21:print("\n" + "="*50 + "\n🛰️ **阶段 3:解冻所有层,进行全局微调**\n" + "="*50)for param in model.parameters(): param.requires_grad = Trueoptimizer = optim.Adam(model.parameters(), lr=1e-5)# --- 训练循环 ---model.train()running_loss, correct, total = 0.0, 0, 0for batch_idx, (data, target) in enumerate(train_loader):data, target = data.to(device), target.to(device)optimizer.zero_grad()output = model(data)loss = criterion(output, target)loss.backward()optimizer.step()# 记录每个iteration的损失iter_loss = loss.item()all_iter_losses.append(iter_loss)iter_indices.append((epoch - 1) * len(train_loader) + batch_idx + 1)running_loss += iter_loss_, predicted = output.max(1)total += target.size(0)correct += predicted.eq(target).sum().item()# 每100个batch打印一次if (batch_idx + 1) % 100 == 0:print(f'Epoch: {epoch}/{epochs} | Batch: {batch_idx+1}/{len(train_loader)} 'f'| 单Batch损失: {iter_loss:.4f} | 累计平均损失: {running_loss/(batch_idx+1):.4f}')epoch_train_loss = running_loss / len(train_loader)epoch_train_acc = 100. * correct / totaltrain_loss_history.append(epoch_train_loss)train_acc_history.append(epoch_train_acc)# --- 测试循环 ---model.eval()test_loss, correct_test, total_test = 0, 0, 0with torch.no_grad():for data, target in test_loader:data, target = data.to(device), target.to(device)output = model(data)test_loss += criterion(output, target).item()_, predicted = output.max(1)total_test += target.size(0)correct_test += predicted.eq(target).sum().item()epoch_test_loss = test_loss / len(test_loader)epoch_test_acc = 100. * correct_test / total_testtest_loss_history.append(epoch_test_loss)test_acc_history.append(epoch_test_acc)# 打印每个epoch的最终结果print(f'Epoch {epoch}/{epochs} 完成 | 耗时: {time.time() - epoch_start_time:.2f}s | 训练准确率: {epoch_train_acc:.2f}% | 测试准确率: {epoch_test_acc:.2f}%')# 训练结束后调用绘图函数print("\n训练完成! 开始绘制结果图表...")plot_iter_losses(all_iter_losses, iter_indices)plot_epoch_metrics(train_acc_history, test_acc_history, train_loss_history, test_loss_history)# 返回最终的测试准确率return epoch_test_acc# ======================================================================
# 3. 绘图函数定义
# ======================================================================
def plot_iter_losses(losses, indices):plt.figure(figsize=(10, 4))plt.plot(indices, losses, 'b-', alpha=0.7, label='Iteration Loss')plt.xlabel('Iteration(Batch序号)')plt.ylabel('损失值')plt.title('每个 Iteration 的训练损失')plt.legend()plt.grid(True)plt.tight_layout()plt.show()def plot_epoch_metrics(train_acc, test_acc, train_loss, test_loss):epochs = range(1, len(train_acc) + 1)plt.figure(figsize=(12, 4))plt.subplot(1, 2, 1)plt.plot(epochs, train_acc, 'b-', label='训练准确率')plt.plot(epochs, test_acc, 'r-', label='测试准确率')plt.xlabel('Epoch')plt.ylabel('准确率 (%)')plt.title('训练和测试准确率')plt.legend(); plt.grid(True)plt.subplot(1, 2, 2)plt.plot(epochs, train_loss, 'b-', label='训练损失')plt.plot(epochs, test_loss, 'r-', label='测试损失')plt.xlabel('Epoch')plt.ylabel('损失值')plt.title('训练和测试损失')plt.legend(); plt.grid(True)plt.tight_layout()plt.show()# ======================================================================
# 6. 执行训练
# ======================================================================
model = VGG16_CBAM().to(device)
criterion = nn.CrossEntropyLoss()
epochs = 50print("开始使用带分阶段微调策略的ResNet18+CBAM模型进行训练...")
final_accuracy = train_staged_finetuning(model, criterion, train_loader, test_loader, device, epochs)
print(f"训练完成!最终测试准确率: {final_accuracy:.2f}%")# torch.save(model.state_dict(), 'resnet18_cbam_finetuned.pth')
# print("模型已保存为: resnet18_cbam_finetuned.pth")

@浙大疏锦行

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/83310.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

54、错误处理-【源码流程】异常处理流程

54、错误处理-【源码流程】异常处理流程 #### 异常处理流程概述 1. **执行目标方法**: - 程序执行目标方法,期间若发生异常,会被捕获并记录,标志当前请求结束。 - 将异常信息赋值给 dispatchException 变量。 2. **进入视图解析…

使用 VSCode 开发 FastAPI 项目(1)

一、引言 FastAPI 是一款现代、快速(高性能)的 Web 框架,用于构建 API,使用 Python 3.7 及更高版本。它基于标准 Python 类型提示,具有自动生成文档等出色功能。而 VSCode 凭借其轻量、强大的特性,为开发者…

Bash 脚本中的特殊变量

在 Bash 脚本和命令行中,​特殊变量​(Special Variables)主要用于获取脚本或命令的上下文信息,如参数、进程状态、返回值等。以下是常见的特殊变量及其典型应用场景: ​1. 脚本参数处理​ $0、$1、$2 ... $9、${10}.…

免部署的数字人 API 调用教程:基于 wav2lip模型训练的开放API,附 PHP 代码示例

前言 去年我开始研究数字人模型算法,测试了市面上几乎所有开源数字人模型,过程中踩了不少坑。最大的痛点就是训练太烧显卡了,光租显卡的费用就花了我6个月的薪资,每次看到账单都心疼。不过现在终于把基于wav2lip的数字人API做出来…

力扣HOT100之贪心算法:45. 跳跃游戏 II

这道题刷代码随想录的时候也刷过,本来以为有了上一题55.跳跃游戏的基础,这道题会好做一点,但是依旧想不出来思路,回去看了下自己当时写的博客,没想到今天的感受和当时的感受都一模一样。。。What can I say?看了下代码…

使用Docker申请Let‘s Encrypt证书

1、安装Docker # 安装Docker https://docs.docker.com/get-docker/# 安装Docker Compose https://docs.docker.com/compose/install/# CentOS安装Docker https://mp.weixin.qq.com/s/nHNPbCmdQs3E5x1QBP-ueA 2、申请Lets Encrypt证书 详见: https://docs.linuxse…

算法题(167):FBI树

审题: 本题需要我们将字符串按照题目要求进行递归展开,并按照后序遍历的顺序输出 思路: 方法一:递归 首先我们需要模拟一下题目的意思 其实就是第一步判断属于什么字符,然后将字符串分两半进行下一轮判断。而由于题目要…

从“分散开发”到“智能协同” —— Gitee 如何赋能河南农担构建金融级研发体系?

河南省农业信贷担保有限责任公司(以下简称「河南农担」)成立于 2016 年,是河南省属骨干国有企业,承担破解“三农”融资难题的重要职责。截至 2024 年底,河南农担累计实现担保规模 1037.05 亿元,位居全国农担…

青少年编程与数学 01-011 系统软件简介 14 Foxpro数据库

青少年编程与数学 01-011 系统软件简介 14 Foxpro数据库 一、历史沿革二、技术架构三、主要功能四、应用场景五、产品版本六、使用方法七、技术价值八、历史意义全文总结 **摘要:**FoxPro 是一款经典的桌面数据库管理系统,起源于 1984 年的 FoxBASE&…

android studio向左向右滑动页面

本文演示了Android Studio中使用ViewPager实现页面切换的方法。通过创建包含3个页面的ViewPager示例,详细展示了实现步骤:1)在XML布局中配置ViewPager和切换按钮;2)使用LayoutInflater动态加载页面布局;3)自定义SimplePagerAdapte…

数据可视化新姿势:Altair的声明式魔法

文章目录 一、告别编程式绘图的苦日子二、5分钟极速入门安装篇(记得先备好虚拟环境!)核心三剑客 三、高阶玩法揭秘1. 交互功能秒实现2. 复合图表so easy3. 魔改样式有套路 四、避坑指南(血泪经验)五、Altair vs 其他库…

PostgreSQL --数据库操作

一、基本操作 1、登录 #切换pg用户 su - postgres#重启服务 pg_ctl -D /usr/local/pgsql/data -l logfile restart#进入pg psql2、数据库操作 2.1、列出库 \l\lselect datname from database; \l:输出比\l多了Size,Tablespace 和 Description 列 :扩展输…

树莓派超全系列教程文档--(63)rpicam-apps可用选项介绍之常用选项

rpicam-apps可用选项介绍之常用选项 rpicam-apps 选项参考常用选项helpversionlist-camerascameraconfigtimeoutpreviewfullscreenqt-previewnopreviewinfo-textwidth 和 heightviewfinder-width 和 viewfinder-heightmode打包格式详细信息解压格式详细信息 viewfinder-modelor…

AI的发展过程:深度学习中的自然语言处理(NLP);大语言模型(LLM)详解;Transformer 模型结构详解;大模型三要素:T-P-G 原则

AI的发展过程:深度学习中的自然语言处理(NLP);大语言模型(LLM)详解;Transformer 模型结构详解;大模型三要素:T-P-G 原则 AI的发展过程与大模型原理详解一、AI的发展过程符…

SDXL 和 SDXL-Turbo 的区别

(1) SDXL(Stable Diffusion XL) 标准扩散模型,基于传统的多步去噪(通常 20~50 步)。 训练充分,特征更稳定,适合用于特征提取、方向学习(如 LoRA、SAE)。 计算成本高&am…

PyTorch:让深度学习像搭积木一样简单!!!

文章目录 🚀 一、 PyTorch的王炸:动态图 vs 静态图静态图的“痛苦回忆”(前方高能吐槽!)PyTorch动态图的降维打击🔥 🔥 二、 不只是灵活!PyTorch的三大杀器1. 张量(Tenso…

LeetCode--27.移除元素

解题思路: 1.获取信息: 给定一个数组和一个值,删除数组中等于这个值的值 要求是,返回数组中不等于这个值的数的数目 并且要求在数组上删除,不能使用额外辅助空间 还是给了评测标准(你可以根据它的原理来实现…

WebRTC(二):工作机制

核心组成 GetUserMedia:获取本地音视频设备(摄像头、麦克风)数据流。RTCPeerConnection:实现点对点的媒体流传输和网络连接管理。RTCDataChannel:点对点的任意数据通道(除音视频外传输数据)。 …

机器学习+城市规划第十五期:时空地理加权回归(STGWR)

机器学习城市规划第十五期:时空地理加权回归(STGWR) 引言 随着城市化进程的加速,城市规划面临越来越多复杂的挑战。在传统的城市规划中,通常会考虑到地理位置的影响,但往往忽略了时间维度。而在现代城市的…