论文笔记 <交通灯><多智能体>CoLight管理交通灯

今天看的是论文Colight:学习网络级合作进行交通信号控制

论文提出的CoLight模型是一种基于强化学习和图注意力网络的交通信号灯控制方法,旨在解决城市道路网络中的交通信号的写作问题,提升车辆通行效率。

问题定义为:

将交通信号控制问题建模为马尔可夫博弈,每个路口由一个智能体控制,智能体通过观察部分系统状态(当前相位和各车道车辆数),选择动作(下一时间段的相位),目标是最小化路口周围车道的平均队列长度。问题主要由系统状态空间,观察空间,动作集合,转移概率,奖励函数,策略和折扣因子等部分组成。

模型结构为:

观察嵌入层:利用多层感知机(MLP)将路口的原始观察数据(车道车辆数和当前相位)嵌入到一个低维的潜在空间,生成隐藏状态,代表当前路口的交通状况。

图注意力网络层(GAT):通过注意力机制实现智能体之间的通信和协作,学习邻居路口对目标路口的影响,首先计算观察交互分数,再进行归一化得到注意力分数,然后基于此进行无索引的邻居协作,将邻居路口的信息进行加权聚合,还使用了多头注意力机制,从不同子空间关注邻居信息。

Q值预测层:根据前面学习到的邻居表示,通过一系列GAT层和全连接层,预测每个动作的Q值,用于指导智能体的决策,通过最小化损失函数来优化当前策略。

(关键)动态通信和无索引建模:

动态通信:利用图注意力网络学习邻居路口影响的动态变化,避免直接拼接相邻路口交通信息的方式,能更好地捕捉交通的时空变化,例如,在不同时间段,相邻路口见的影响方向和程度不同,模型可根据实时交通状况调整注意力分配。

无索引模型学习:通过学习注意力权重对邻居路口影响进行平均,而不是使用固定索引,解决了多智能体共享模型参数时因邻居索引固定带来的学习冲突问题,减少了学习模型的整体参数。

实验评估部分:

  • 实验设置:论文在 CityFlow 开源交通模拟器上进行实验,使用合成数据(不同规模的动脉和网格网络)和真实世界数据(纽约、杭州和济南的交通数据),将模型与传统交通方法(如 Fixedtime、MaxPressure)和其他强化学习方法(如 CGRL、Individual RL 等)进行对比。
  • 评估指标:论文中采用平均旅行时间作为主要评估指标,衡量不同模型在控制交通信号时的性能。
  • 实验结果:论文得到的结果为,CoLight 在不同路网和交通模式下,相比传统方法和其他 RL 方法,平均旅行时间显著降低,收敛速度更快,在大规模路网中表现出良好的可扩展性。例如,在合成数据上平均提升 6.98% ,在真实世界数据上平均提升 11.69%。

下面是我的理解

传统方法:

1.固定配时,比如红灯60秒,绿灯40秒,不管路上多少车,到店就换,高峰期容易堵,效率低。

2.MaxPressure等传统算法:通过计算"压力"(比如上下游排队长度)来选绿灯方向,但它假设"车道永远能容纳车""车流稳定",但是现实中一堵车就失灵,不会灵活应变。

RL方法:

让每个路口自己学怎么变灯,但是老RL方法也有坑,比如把邻居路口的信息直接堆在一起,不管这些邻居是主干道还是小路,也不管早上和晚上谁的影响更大,就像把所有快递都堆在门口,分不清哪个急哪个缓。

CoLight方法:核心大招:让交通灯会看,会想,会合作。

CoLight就像是给每个交通灯装了个聪明的大脑,主要干三件事:

1.先看清周围路况:观察嵌入层

每个路口的智能体(交通灯控制器)会收集两个信息------当前灯的状态(比如东西方向绿灯)、各个车道的车辆数(比如东进口道有 20 辆车排队)。

然后用一个翻译器--MLP多层感知机把这些数据变成大脑能理解的信号(隐藏状态),就行人看见堵车信号会提高警惕一样。

2.再想明白哪个更重要:图注意力网络(GAT):

这是CoLight最厉害的地方,解决了"邻居影响动态变化"的问题。

比如早晚高峰的区别:早上上班时,上游路口A往路口B的车多,A对B的影响大,晚上下班时,车流反方向,B对A的影响大,CoLight会像人一样,根据实时车流给不同邻居"打分"也就是注意力权重,车少的得分低。

那有人就要问了:具体时怎么打分的呢?

1.先算关系分,比如路口i和邻居j,把它们的路况信号相乘,算出j对i的重要新初值:eij.

2.然后再归一化关注度:用softmax函数把初值转化为0-1之间的权重aij,类似在所有邻居李,j的重要性占比多少。

3.最后加权汇总,把所有邻居的路况按权重加起来,得到对i整体影响,比如主干道邻居权重高,小路邻居权重低,避免"捡了芝麻,丢了西瓜"

使用多头注意力:用多个视角看问题

就像找不同的人打听路况(有人关注车流量,有人关注事故)CoLight用5个多头注意力并行计算,最后把结果平均,让判断更准确。

3.最后"合作决策":无所有建模与参数共享

传统方法给邻居排固定顺序(比如:东南西北)但是不同的路口的重要邻居位置不同(比如主干道路口更关注东西方向,小路口更关注南北方向)。如果强行按统一的顺序学,就会打架。

CoLight的解决方法为:

不按固定顺序,只看权重:不管邻居在哪个方向,谁的权重高就听谁的,就行开会时谁的建议靠谱就听谁的。

所有路口共享学习经验:比如路口A和路口B都用同一套"打分规则",但根据自己的邻居动态调整权重,打打减少了需要学的参数,就行全班同用一套教材,但是各自记着不同的笔记。

传统交通灯像 "按剧本演戏",老 RL 方法像 "各顾各的傻瓜",而 CoLight:

  • 能根据实时车流,给不同邻居路口 "动态分配关注度"(比如主干道优先、早高峰关注上游);
  • 不用死记硬背邻居位置,只靠 "谁重要就听谁" 来合作;
  • 能同时管几百个路口,还不怎么增加计算量,最终让车跑得更快、堵得更少。

这就好比一群协管员,每个人都能快速判断周围哪个方向最堵,然后互相 "通气"(按重要性分享信息),一起把车流疏导得明明白白。

示例代码:

复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
from torch_geometric.nn import GATConv  # 或自定义GAT层

class ObservationEmbedding(nn.Module):
    """观察嵌入层:将原始交通数据转换为隐藏状态"""
    def __init__(self, input_dim, embed_dim):
        super(ObservationEmbedding, self).__init__()
        self.mlp = nn.Sequential(
            nn.Linear(input_dim, embed_dim),
            nn.ReLU(),
            nn.Linear(embed_dim, embed_dim),
            nn.ReLU()
        )
        self.input_dim = input_dim
        self.embed_dim = embed_dim
    
    def forward(self, x):
        """
        x: 输入张量,形状为[batch_size, input_dim]
        对应论文公式2:h_i = Embed(o_i^t) = σ(o_i W_e + b_e)
        """
        return self.mlp(x)  # 输出形状[batch_size, embed_dim]


class GraphAttentionLayer(nn.Module):
    """单头部图注意力层:计算邻居路口的注意力权重"""
    def __init__(self, in_dim, out_dim, alpha=0.2):
        super(GraphAttentionLayer, self).__init__()
        self.w_t = nn.Parameter(torch.FloatTensor(in_dim, out_dim))
        self.w_s = nn.Parameter(torch.FloatTensor(in_dim, out_dim))
        self.leaky_relu = nn.LeakyReLU(alpha)
        self.softmax = nn.Softmax(dim=1)
        self.in_dim = in_dim
        self.out_dim = out_dim
    
    def forward(self, h, adj):
        """
        h: 隐藏状态张量,形状为[num_nodes, in_dim]
        adj: 邻接矩阵,形状为[num_nodes, num_nodes],1表示邻居
        1. 计算观察交互分数 e_ij = (h_i W_t) · (h_j W_s)^T 
        2. 归一化得到注意力权重 α_ij = softmax(e_ij) 
        3. 加权聚合邻居表示 h_si = σ(W_q · Σ(α_ij h_j W_c) + b_q) 
        """
        # 特征变换
        h_t = torch.matmul(h, self.w_t)  # [num_nodes, out_dim]
        h_s = torch.matmul(h, self.w_s)  # [num_nodes, out_dim]
        
        # 计算注意力分数
        attn_scores = []
        for i in range(h.size(0)):
            # 对每个节点i,计算与所有邻居j的分数
            e_ij = torch.matmul(h_t[i], h_s.T)  # [num_nodes]
            # 仅保留邻居节点的分数
            e_ij = e_ij * adj[i].float()
            e_ij = self.leaky_relu(e_ij)
            # 归一化
            alpha_ij = self.softmax(e_ij)  # [num_nodes]
            attn_scores.append(alpha_ij)
        
        attn_scores = torch.stack(attn_scores)  # [num_nodes, num_nodes]
        
        # 加权聚合邻居表示(简化版,假设W_c为单位矩阵)
        h_neighbors = torch.matmul(attn_scores, h)  # [num_nodes, in_dim]
        # 论文中还包含W_c和W_q的变换,此处简化为线性层
        h_si = F.relu(torch.matmul(h_neighbors, nn.Parameter(torch.FloatTensor(in_dim, out_dim))))
        
        return h_si, attn_scores


class MultiHeadAttention(nn.Module):
    """多头部注意力机制:并行执行多个注意力层"""
    def __init__(self, in_dim, out_dim, num_heads=5, alpha=0.2):
        super(MultiHeadAttention, self).__init__()
        self.heads = nn.ModuleList([
            GraphAttentionLayer(in_dim, out_dim, alpha) for _ in range(num_heads)
        ])
        self.num_heads = num_heads
        self.out_dim = out_dim
    
    def forward(self, h, adj):
        """
        h: [num_nodes, in_dim]
        adj: [num_nodes, num_nodes]
        执行H个注意力头,输出加权和 
        """
        head_outputs = []
        attn_weights = []
        for head in self.heads:
            h_head, attn = head(h, adj)
            head_outputs.append(h_head)
            attn_weights.append(attn)
        
        # 平均多头输出 
        h_merged = torch.mean(torch.stack(head_outputs), dim=0)
        return h_merged, torch.mean(torch.stack(attn_weights), dim=0)


class CoLightModel(nn.Module):
    """CoLight完整模型:观察嵌入+多层GAT+Q值预测"""
    def __init__(self, obs_dim, embed_dim, num_phases, num_heads=5, num_layers=2):
        super(CoLightModel, self).__init__()
        self.obs_embed = ObservationEmbedding(obs_dim, embed_dim)
        self.gat_layers = nn.ModuleList([
            MultiHeadAttention(embed_dim, embed_dim, num_heads) for _ in range(num_layers)
        ])
        self.q_value = nn.Linear(embed_dim, num_phases)
        self.num_layers = num_layers
    
    def forward(self, obs, adj):
        """
        obs: 观察张量,形状[num_nodes, obs_dim]
        adj: 邻接矩阵,形状[num_nodes, num_nodes]
        前向传播流程:
        1. 观察嵌入 
        2. 多层GAT通信 
        3. Q值预测 
        """
        h = self.obs_embed(obs)  # [num_nodes, embed_dim]
        
        for i in range(self.num_layers):
            h, _ = self.gat_layers[i](h, adj)  # 每层GAT更新隐藏状态
        
        q_values = self.q_value(h)  # [num_nodes, num_phases]
        return q_values


class CoLightTrainer:
    """CoLight训练器:实现强化学习训练逻辑"""
    def __init__(self, model, gamma=0.99, lr=1e-4):
        self.model = model
        self.optimizer = torch.optim.Adam(model.parameters(), lr=lr)
        self.gamma = gamma
        self.loss_fn = nn.MSELoss()  # 对应论文公式1和10
    
    def train_step(self, obs, adj, actions, next_obs, rewards, dones):
        """
        单步训练:计算Q值损失并更新参数
        obs: 当前观察 [batch_size, num_nodes, obs_dim]
        adj: 邻接矩阵 [batch_size, num_nodes, num_nodes]
        actions: 执行的动作 [batch_size, num_nodes]
        next_obs: 下一时刻观察 [batch_size, num_nodes, obs_dim]
        rewards: 奖励 [batch_size, num_nodes]
        dones: 结束标志 [batch_size, num_nodes]
        """
        # 展平批量数据
        batch_size, num_nodes = obs.shape[:2]
        obs_flat = obs.reshape(-1, obs.shape[-1])
        adj_flat = adj.reshape(-1, adj.shape[-1])
        actions_flat = actions.reshape(-1)
        rewards_flat = rewards.reshape(-1)
        dones_flat = dones.reshape(-1)
        
        # 预测当前Q值和下一时刻Q值
        current_q = self.model(obs_flat, adj_flat)  # [batch*nodes, num_phases]
        next_q = self.model(next_obs.reshape(-1, next_obs.shape[-1]), 
                           adj_flat).detach()  # 目标网络或冻结参数
        
        # 计算目标Q值:r + γ * max(next_q) 
        max_next_q = next_q.max(dim=1)[0]
        target_q = rewards_flat + self.gamma * max_next_q * (1 - dones_flat)
        
        # 提取当前动作的Q值
        current_action_q = current_q[torch.arange(batch_size*num_nodes), actions_flat]
        
        # 计算损失并优化
        loss = self.loss_fn(current_action_q, target_q)
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()
        
        return loss.item()


# 示例:构建和使用CoLight模型
def build_colight_model(obs_dim=10, num_intersections=20, num_phases=4):
    """构建CoLight模型实例"""
    embed_dim = 64
    model = CoLightModel(
        obs_dim=obs_dim,
        embed_dim=embed_dim,
        num_phases=num_phases,
        num_heads=5,
        num_layers=2
    )
    return model


# 模拟数据生成(实际应用中从模拟器或真实数据获取)
def generate_sample_data(batch_size=32, num_nodes=20, obs_dim=10):
    """生成模拟训练数据"""
    obs = torch.randn(batch_size, num_nodes, obs_dim)
    # 构建邻接矩阵(简化为距离最近的5个邻居)
    adj = torch.zeros(batch_size, num_nodes, num_nodes)
    for i in range(num_nodes):
        # 模拟邻居关系(实际中基于地理距离或路网拓扑)
        neighbors = np.random.choice(num_nodes, 5, replace=False)
        adj[:, i, neighbors] = 1
    actions = torch.randint(0, 4, (batch_size, num_nodes))
    next_obs = torch.randn(batch_size, num_nodes, obs_dim)
    rewards = torch.randn(batch_size, num_nodes)
    dones = torch.zeros(batch_size, num_nodes, dtype=torch.bool)
    return obs, adj, actions, next_obs, rewards, dones

如需更完整的实现,可参考论文提供的开源代码:https://github.com/wingsweihua/colight ,其中包含数据集处理、模拟器对接及完整训练流程。

相关推荐
写代码的小阿帆7 小时前
LDStega论文阅读笔记
论文阅读·笔记
青椒大仙KI117 小时前
论文笔记 <交通灯> <多智能体>DERLight双重经验回放灯机制
论文阅读·人工智能·深度学习
王上上12 小时前
【论文阅读33】滑坡易发性 PINN ( EG2025 )
论文阅读
张较瘦_1 天前
[论文阅读] 人工智能 | Gen-n-Val:利用代理技术革新计算机视觉数据生成
论文阅读·人工智能·计算机视觉
王上上2 天前
【论文阅读34】Attention-ResNet-LSTM(JRMGE2024)
论文阅读·人工智能·lstm
CV-杨帆2 天前
论文阅读:arxiv 2025 Chain of Draft: Thinking Faster by Writing Less
论文阅读
LuH11242 天前
【论文阅读笔记】ICLR 2025 | 解析Ref-Gaussian如何实现高质量可交互反射渲染
论文阅读·笔记·论文笔记
Jamence2 天前
多模态大语言模型arxiv论文略读(118)
论文阅读·人工智能·语言模型·自然语言处理·论文笔记
zsq3 天前
【论文阅读笔记】HaDes幻觉检测benchmark
论文阅读·笔记·nlp·大语言模型幻觉