这篇想聊的是我最近整理的一套混合波束成形仿真项目,基于深度学习做大规模天线阵列的混合波束成形设计,配套了Matlab和Python两套代码。写这篇文章的原因很简单:很多做毫米波通信的同学卡在同一个地方——论文翻了不少,知道深度学习能替代传统算法来设计预编码,但真正动手时发现数据集不知道该按什么格式造、标签到底用什么、训练完怎么在仿真链路里算频谱效率,这几步任何一个没想清楚,项目就得推倒重来。
这套仿真解决的就是上面这一整条链路:从毫米波信道生成、码本构造、训练数据制作,到神经网络训练,再到Matlab和Python各自完成性能验证。无论你是刚接触混合波束成形的研究生,还是已经跑了很久传统迭代算法想换个思路的工程师,这篇文章给出的代码思路和调试经验都可以直接当起点用。
1. 混合波束成形为什么非做不可:毫米波MIMO的硬件账
1.1 全数字预编码的功耗困局
大规模天线阵列的核心卖点是空间自由度足够多,理论上天线数越多,波束越窄、增益越高、系统容量越大。但天线数上去之后,第一个扛不住的不是算法,而是硬件。每根天线如果都配一条完整的射频链路,意味着每个通道都需要独立的DAC、功率放大器、混频器和滤波器。这套东西在sub-6GHz频段勉强能忍,到了毫米波频段,器件功耗和成本几乎是不可接受的。
举个具体账目:64天线全数字架构需要64条射频链路,每条链路只算DAC和功放的功耗,整体射频前端功耗动辄几十瓦往上走,这对基站设备来说是很大的发热和供电压力。更麻烦的是,毫米波频段上高质量ADC非常昂贵,分辨率每增加一个bit,成本和功耗都会明显上涨。全数字波束成形的理论性能确实是最优的,但对硬件预算极不友好。
混合波束成形的思路就是在这个背景下被重新重视起来的:用少量射频链路加移相器网络,把大阵列的高增益和低硬件成本同时拿到手。换句话说,用硬件做"部分数字处理",用模拟移相器做"空域粗调和聚焦",再把基带数字域留出来做多流干扰抑制。
1.2 混合架构的基本形态与两个子矩阵
一个典型的窄带混合预编码系统里,发射机的预编码矩阵被拆成两级:模拟预编码矩阵,记为FRF,维度是Nt×NRF,Nt是发射天线数,NRF是射频链路数;数字预编码矩阵,记为FBB,维度是NRF×Ns,Ns是数据流数。
为什么这样拆?因为FRF通常要用移相器实现,而移相器只能调整相位,不能调整幅度,所以FRF的元素模值恒定,即每个元素都落在恒模约束上。这就决定了模拟部分做不了精细的幅度加权,它主要负责把能量送到正确的空间方向上。FBB则留在了基带数字域,可以随意调整幅度和相位,用于多流传输时的干扰消除。
接收端结构如果也做混合合并,同理拆成WBF和WBB。这套架构之所以成为主流,不只是因为论文里画着方便,而是它确实在硬件可实现性和性能之间找到了一个工程上可以接受的平衡点。和纯模拟波束成形比,它支持多流;和全数字比,它省掉了大量射频链路。所以很多毫米波大规模MIMO的研究都默认采用这种架构。
1.3 三种架构的取舍:不只是复杂度问题
把纯模拟、纯数字、混合三种方案放在一起看,差异比大多数人想象的更大。纯模拟波束成形结构最简单,一个射频链路驱动所有天线,所有数据流共享同一个波束方向,只能做单流传输。全数字性能上限最高,但硬件的成本和功耗随天线数线性上升。混合方案介于中间,硬件成本接近模拟方案,性能接近数字方案,是多流场景下的现实折中。
需要特别说明的是,混合架构的性能并不是随便拆一下就接近全数字了,FRF选得好不好直接决定系统离性能上界有多远。比如用DFT码本把模拟波束限制在几个固定方向上,如果信道能量恰好不在这些方向上,损耗就会非常明显。这个问题恰恰是后面深度学习介入的重要动机:与其用穷举或迭代方式费劲去选FRF,不如训练一个网络直接做映射。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 混合波束成形问题的数学建模与求解难度
2.1 系统模型和毫米波信道的特点
研究混合波束成形,第一件事是把系统模型写清楚。这里用最常见的窄带假设,发射端Nt根天线、NRF条射频链路、发送Ns个数据流,接收端Nr根天线。接收信号可以写成:
y = H·FRF·FBB·s + n
其中s是Ns维的发送符号向量,n是高斯白噪声,H是Nr×Nt的信道矩阵。需要求解的是满足恒模约束和功率约束的FRF、FBB,让系统容量或者说频谱效率尽可能高。
信道模型我推荐用毫米波方向最常用的扩展Saleh-Valenzuela模型,它由若干散射簇组成,每个簇包含多条射线。这个模型的特点是多径数量很少,空间方向性很强,信道矩阵往往呈低秩结构。正因为低秩,少数几条模拟波束就足够捕获大部分信道能量,这也给了混合波束成形可行的物理基础。
2.2 优化目标:频谱效率函数有多难伺候
优化目标是频谱效率,表达式长这样:
R = log2(det(I + (1/Ns) · σ⁻² · H·FRF·FBB·FBBᴴ·FRFᴴ·Hᴴ))
这个函数看着不算复杂,但约束条件极其麻烦。FRF的每个元素模值固定为1,这个恒模约束导致可行域不是凸集,整个问题是一个非凸优化问题,全局最优解在数学上很难求。传统做法是交替优化:先固定FBB优化FRF,再固定FRF设计FBB,或者用正交匹配追踪的思路逐列选择码本向量。
还有一层麻烦是复杂度。当天线数很大、码本很密集时,即使采用贪心策略,每选择一个波束都要做一次矩阵求逆或投影计算,在线实时运行的压力很大。这也是深度学习方案目前被广泛尝试的原因——它把计算量集中到离线训练,在线推理时只需一次前向传播。
2.3 传统算法的天花板在哪里
传统算法里最有代表性的是基于码本的穷举搜索和逐次消去法。穷举搜索的性能最好,但复杂度爆炸:64天线、64码字、选择4个波束,组合数是C(64,4)量级,每个组合还要算频谱效率,纯离线仿真还能接受,做成在线系统完全不现实。逐次消去法复杂度降下来了,但每次选择都只从当前残差角度出发,属于贪心算法,性能会随信道相关性的增加而明显变差。
另一个传统问题是它们都需要比较精确的信道状态信息。混合波束成形系统由于射频链路少,估计完整信道矩阵本来就是一件麻烦事。这就形成了一种尴尬:好的预编码算法依赖好的CSI,好的CSI又依赖于较多射频链路,而混合架构恰恰是为了减少射频链路。深度学习方案可以单独学习从某种信道特征到波束选择的映射,甚至可以和信道压缩反馈联合设计,从而绕开一部分这个问题。
3. 深度学习切入混合波束成形的三种思路
3.1 思路A:把预编码当回归任务
最直接的想法是用神经网络学习一个函数:输入信道矩阵H,输出FRF和FBB。这看起来自然而简单,但实际落地时有两个坑。
第一个坑是恒模约束很难通过普通网络层强制执行。全连接层输出的是任意实数,要满足模值为1需要再做归一化操作,而这个归一化本身会限制网络的表达能力。第二个坑是矩阵元素的顺序问题。天线顺序变了,信道矩阵的列顺序变了,网络输出如果也按固定顺序排列,性能会受到很大影响,这在通信物理层里通常意味着泛化能力差。
所以纯粹的回归方案,除非在结构设计上做很多额外工作,否则效果往往不如分类方案稳定。迭代算法的强大之处是它每步都在优化,而回归网络一锤子买卖,很难逼近同一个结果。
3.2 思路B:把波束选择当分类任务
另一种做法是引入码本,把模拟预编码的设计变成一个选择问题。发射端维护一个大小为Nc的码本,每个码字是一个天线维度的波束方向向量。我们要做的是给每条射频链路选择一个合适的码字,让FRF拼装出来后频谱效率够高。
选码字天然是离散动作,非常适合用分类网络处理。网络输出的是每个码字被选择的概率,训练时可以构造多标签分类问题,也可以用带温度采样的方式做端到端训练。相比回归方案,分类方案的好处是输出稳定在合法码本上,恒模约束天然满足,不需要额外投影。
3.3 思路C:深度学习辅助信道压缩与反馈
第三种思路是把深度学习用在CSI反馈环节。FDD系统中,用户端需要把信道信息反馈给基站,反馈链路带宽有限,于是压缩和重构成为刚需。自编码器是这里的常见选择:编码端在用户侧把高维信道压缩成低维码字,解码端在基站侧重构信道,再用传统算法做混合预编码。
这个思路的好处是可以和已有算法衔接,缺点是需要联合设计两个端的学习目标,训练更复杂。对于刚入门的人来说,我建议先不要碰这条路线,等把前两种思路跑通了再扩展。
3.4 我的推荐组合:码本分类网络加基带迫零
在我这套项目里,实际采用的是"码本分类选FRF + 基带ZF算FBB"的组合。分类网络负责把几条最重要的模拟波束方向选出来,数字部分用等效信道直接计算闭式解。这样组合的逻辑是:模拟部分本来就是对方向的粗选,分类网络和离散选择天然匹配;数字部分交给传统算法,因为FBB的计算不涉及恒模约束,闭式解既快又稳。
训练时只关注FRF的选择是否合理,评估时把FBB也接上算端到端频谱效率。这样网络任务清晰,收敛速度快,最后的仿真结果也比我之前尝试的端到端回归方案明显更好。
4. 仿真流程与数据生成:训练集从哪里来
4.1 信道样本生成的关键细节
做机器学习第一步不是搭网络,而是造一个靠谱的训练数据集。这里最容易犯的错误是信道模型写得过于理想化,训练出来很好看,换个参数全崩。我建议至少在生成时把下面几个要素加进去。
信道由多个散射簇组成,每个簇有扩展角度。簇的数量一般取3到5,每簇的射线数取4到8。发射端的到达角、离开角都要加入角度扩展,也就是每条射线的角度不能是同一个固定值,而要在一个小范围内随机抖动。如果不加角度扩展,信道矩阵会过于稀疏,网络学到的所谓特征实际上只是记住几个固定角度位置,完全失去泛化能力。
天线间距也值得注意。标准均匀线性阵列通常取载波半波长间距。有些同学为了方便把间距设成1波长,波束会出现栅瓣,训练网络时模型会学到错误的周期性,部署到真实场景后性能下降得莫名其妙。
4.2 训练标签:别直接用穷举搜索的原始结果
标签设计是数据准备阶段最花时间的环节。我用穷举搜索生成标签时,发现直接把最优波束组合当分类标签有问题:多个波束组合的性能可能非常接近,但标签只认最优的,网络会对其他同样优秀的组合打出很高的惩罚。收敛会变慢,精度也受影响。
一个更稳的做法是收集多个近优组合作为正样本。比如穷举时把频谱效率排前几位的一组波束组合,以及那些最优组合里发生码字替换但性能损失低于0.5%的组合,都作为合法标签。这种"软标签"思路在通信领域很实用,因为空间方向之间本来就存在相关性,强行要求网络只认唯一答案是违背物理直觉的。
4.3 输入特征:实部虚部、幅度相位还是模值矩阵
信道矩阵是复数矩阵,怎么作为网络输入需要很小心。常见的做法有三种:把实部和虚部分别当通道输入、用幅度和相位做双通道输入、直接输入Gram矩阵或其他变换域特征。我实际对比下来的结论是,实部虚部通道的方案最稳,网络结构也最简单直接,后续想加其他特征信息也方便拼接。
数据标准化也必须在生成阶段就做好。每批数据归一化时用整个训练集的均值和方差,不能直接在跑训练时对单个batch做归一化,否则不同批量之间的分布会抖动,分类精度和稳定性都会受影响。另外,随机信道样本中信噪比的影响可以留到推理阶段再考虑,训练数据统一用单位发射功率生成,这样网络学的是信道本身的空域结构。
5. Matlab代码实现:从信道仿真到网络训练一条龙
5.1 毫米波信道生成与DFT码本构造
Matlab这套代码我按"信道生成—标签制作—训练—验证"的顺序切成了四个脚本,方便单独跑和调参。信道生成函数的核心部分这样写:
matlab复制function H = gen_mmwave_channel(Nt, Nr, Ncl, Nray)
H = zeros(Nr, Nt);
for c = 1:Ncl
AoD = -pi/3 + (2*pi/3)*rand; % 离开角随机
AoA = -pi/3 + (2*pi/3)*rand; % 到达角随机
for r = 1:Nray
aod = AoD + (5*pi/180)*randn;
aoa = AoA + (5*pi/180)*randn;
alpha = (randn + 1i*randn)/sqrt(2);
at = array_response(Nt, aod);
ar = array_response(Nr, aoa);
H = H + alpha * ar * at';
end
end
H = H / sqrt(Ncl * Nray);
end
码本用DFT矩阵生成,每个码字对应一个等间隔空间方向。DFT码本的好处是各波束均匀覆盖角度空间,而且计算简单,适合做穷举标签。
matlab复制function F = dft_codebook(Nt, Nc)
F = zeros(Nt, Nc);
for k = 1:Nc
F(:, k) = exp(1j*2*pi*(0:Nt-1)'*(k-1)/Nc)/sqrt(Nt);
end
end
5.2 穷举标签生成与数据集封装
训练样本数我默认用了20000个信道样本,每个样本用穷举的方式找前K个最优波束组合作为标签。穷举组合的存储要注意压缩,不然20000个样本的标签矩阵会非常占内存。我用的是稀疏索引存储,也就是每个样本只记录选中的码字编号和对应的频谱效率,完整矩阵在训练读取时再展开。
Matlab里深度学习训练的数据存储用arrayDatastore加transform组合最省事,不要一次性把20G的数据全塞进内存跑训练。先写出训练集和验证集两个目录,每个目录里按顺序存.mat文件,再用数据存储读取。
5.3 Deep Learning Toolbox下的网络定义
Matlab里做这个任务的网络可以用普通的全连接层包起来,也可以用卷积层。因为信道矩阵天然是二维结构,我把实部和虚部拼成2×Nr×Nt的输入,中间加了两层卷积再接全连接分类层。
需要强调的是,Matlab的深度学习工具箱虽然能跑,但训练效率比Python环境慢不少。我在项目里更常用的工作流程是:Matlab负责生成信道和标签数据,导出为.mat文件,然后交给Python训练;训练好的网络再转回Matlab做系统级仿真验证。两种工具配合起来,既方便清洗数据,又能利用Python生态里更灵活的调试工具。
5.4 频谱效率计算与仿真曲线
训练完以后,验证部分要单独写。验证脚本做的事情是:对每个测试信道,先做网络前向推理得到波束索引,拼成FRF,然后计算等效信道Heq = H * FRF,再用ZF准则得到数字预编码矩阵,最后代入频谱效率公式统计平均性能。
这个环节最容易出问题的是发射功率约束。不同算法之间做公平对比时,数字预编码矩阵必须归一化,否则频谱效率会虚高。归一化时按总发射功率先在FRF和FBB之间分配,我采用的方式是模拟部分等幅输出、数字部分进行总功率归一化,两种方案的对比结果才有参考价值。
6. Python(PyTorch)实现与Matlab的性能对比
6.1 跨语言数据集交换的格式约定
Python和Matlab联动的关键是数据格式约定。我在做数据交换时统一用.mat格式,规格上只存三个变量:信道复矩阵H、标签索引、码本矩阵F。Matlab写入以后,Python端用scipy.io.loadmat读取。反过来,Python训练完的模型参数我一般导出成.npy文件,再拿回Matlab手动构建同结构网络载入参数推理,避免引入额外的模型转换依赖。
这种跨语言流程看起来多了一步,实际上很值得。Matlab做通信系统仿真方便,Python做模型调试和扩展方便,两个生态各取所长。如果你只用单语言,也能跑通,但后面想加新的深度学习组件时,Python侧会更灵活。
6.2 PyTorch网络结构与训练配置
Python端的网络定义不能太复杂,任务本身是码本分类,深度网络的意义不大。我用的是一个三层的轻量网络:两层卷积提取空间特征,再接两层全连接输出NRF个概率分布。核心代码大致是:
python复制import torch
import torch.nn as nn
class BeamSelector(nn.Module):
def __init__(self, num_ant, num_users, num_codewords, num_rf):
super().__init__()
self.cnn = nn.Sequential(
nn.Conv2d(2, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(32, 16, kernel_size=3, padding=1),
nn.ReLU(),
)
self.fc = nn.Sequential(
nn.Flatten(),
nn.Linear(16 * num_users * num_ant, 256),
nn.ReLU(),
nn.Linear(256, num_rf * num_codewords),
)
self.num_rf = num_rf
self.num_codewords = num_codewords
def forward(self, x):
out = self.cnn(x)
out = self.fc(out)
return out.view(-1, self.num_rf, self.num_codewords)
输出维度是(batch, NRF, Nc),训练时对每条射频链路分别做CrossEntropyLoss,然后取平均。优化器用Adam,初始学习率1e-3,配合StepLR每20个epoch衰减0.5。我试验下来这个配置在20000样本规模下稳定收敛,大概60到80个epoch就能看到验证精度不再明显变化。
6.3 两套代码的实测性能对比
我在64天线、4条射频链路、4个数据流的配置下跑过对比。深度学习方法训练后的平均频谱效率大约能达到穷举搜索理想值的94%到96%,比逐次消去法高3到5个百分点,推理时间则从逐次消去法的毫秒级进一步降到微秒级。在线应用场景里,这个推理效率优势相当明显。
Python和Matlab两端实现的性能几乎一致,差别主要来自于训练完模型的浮点精度处理。Matlab导入npy时默认转成double,PyTorch训练时一般用float32,推理时偶尔有点可忽略的精度损失。如果遇到两端结果差异比较大的情况,先排查是不是输入特征的归一化参数没有一致地搬过去。
7. 容易踩的坑和调参经验
7.1 标签冲突:看似相同的码字组合,性能差很远
我最早用穷举标签训练时碰到过一个奇怪现象:网络在训练集上精度很高,验证集上也不差,但端到端频谱效率忽高忽低。后来排查发现是标签生成脚本里对某一类信道产生了多个等价最优组合,而我只存了第一个找到的,没有把真正稳定的那个组合标记出来。更隐蔽的坑是,码本里某些相邻码字对应的波束方向在角度上几乎重叠,模型在它们之间随机切换都会导致最终性能抖动。
解决方法是每次生成样本时保存前五个次优组合,并在训练时将软标签机制加进去,也就是对性能差距小于1%的多个组合分配相近的概率。这样模型学到的是方向区域,而不是死记硬背某几个码字编号。
7.2 归一化不一致:训练很好,推理全乱
这个问题专门坑跨语言工作流的人。训练时如果用了全局均值和方差做标准化,推理时也要用同一组数值,不能在线对单条样本重新计算。我在Matlab验证脚本里早期就踩过这个坑,网络输入特征的量纲完全变了,分类结果基本等于随机猜测。建议把归一化参数随模型一起导出,并在验证脚本的第一步就强制加载。
7.3 测试场景不能和训练场景差太远
毫米波信道和频率、阵列配置强相关。如果训练数据是64天线、半波长间距,测试时改成128天线,网络基本要重新训练,别指望它能泛化。比较稳妥的做法是把天线数、散射簇数和角度扩展范围这几个参数作为可配置项做数据生成,需要时可以组合出多个场景的混合训练集。混合训练后单模型的泛化能力会好不少,虽然最大性能会略低于单独场景训练的模型。
7.4 频谱效率提升不等于误码率改善
最后提醒一下评价指标的理解。很多项目把频谱效率当唯一指标,但频谱效率高不代表系统在真实的编解码链路里一定好用。波束选出来之后,如果同一波束对内的流间干扰没有处理好,即便频谱效率较高,高阶调制下的误码率仍然会很差。建议验证阶段同时统计误码率曲线,尤其是进行16QAM或64QAM测试时。
8. 后续可以扩展的方向
这套代码框架的扩展路径比大多数人想象的要长。我已经尝试过把单用户场景改成多用户混合预编码,网络结构只需要在输出端增加用户维度的分支,训练数据生成时再加入用户间信道相关性控制,效果也还算稳定。如果做FDD系统的CSI反馈压缩,这个框架里的数据存储和交叉验证脚本可以直接复用,只需要把自编码器接到通道输出两侧。
我个人实际使用这套方案时最喜欢的一点是,数据准备和模型训练解耦得很干净。信道生成脚本换成室内场景模型也好、换成3D空间模型也好,都不影响网络侧的输入输出接口,迭代效率很高。如果你是第一次接触这个方向,我建议先把64天线、4条射频链路的单用户场景完整跑通,再一步步加复杂度。每次加一个变量,性能指标和瓶颈就都清楚了。
