1. 项目简介

本项目是一套基于深度神经网络(DNN)的OFDM信道估计算法仿真代码。在传统的信道估计中,最小二乘(LS)算法计算复杂度低,但在低信噪比下易受噪声影响;最小均方误差(MMSE)算法估计精度高,但需要已知信道的二阶统计特性,且计算复杂度较高。

本项目参考相关文献,采用“先进行LS粗估计,再利用DNN进行误差修正”的方案。通过让神经网络学习多径衰落信道下的误差分布,可以在不依赖信道先验统计信息的情况下,有效提升信道估计的精度。

  1. 主要功能特点
  1. 联合仿真环境:采用MATLAB进行无线通信物理层仿真与原始数据生成,采用Python配合Keras框架进行深度学习网络的构建与训练。
  2. 多网络架构对比:代码内置了4种不同隐藏层和神经元数量的DNN架构,方便对比网络深度与宽度对信道估计性能的影响。
  3. 模型训练优化:在Python训练代码中引入了动态学习率衰减(ReduceLROnPlateau)和早停机制(Early Stopping),以改善模型在高信噪比场景下的收敛情况,降低归一化均方误差(NMSE)。
  1. 环境依赖
  1. MATLAB(用于生成物理层信号、计算传统算法基线及最终绘图)
  2. Python 3.x
  3. Python库:tensorflow (或 keras), numpy, scipy, scikit-learn
  1. 代码运行流程

本项目的完整仿真流程分为以下四个步骤:

    1. 物理层数据生成(MATLAB)

运行Main_Simulation.m。该脚本模拟了基于802.11p协议的OFDM系统,在瑞利时变(RTV)多径信道下生成不同信噪比下的原始数据,并计算传统的LS和MMSE估计结果作为性能参考。建议将样本量设置在10万级别以保证深度学习的训练效果。

    1. 数据格式预处理(MATLAB)

运行Process_Training_Data.m。由于标准神经网络无法直接处理复数,该脚本将生成的复数信道数据(LS估计值与真实信道)拆分为实部和虚部,并重组为Python可直接读取的.mat数据集格式。

    1. 模型训练与预测(Python)

运行Python训练和测试脚本。神经网络以LS估计值为输入特征,以真实信道为标签进行监督学习。训练完成后,对测试集进行预测,并将DNN修正后的信道数据导出为.mat文件。

    1. 性能对比与绘图(MATLAB)

运行最终的绘图脚本。代码会读取Python传回的预测结果,计算各算法的归一化均方误差(NMSE),并绘制随信噪比变化的对比曲线图。

clc;                    % 清除命令行窗口的内容
clearvars;              % 清除工作区中的所有变量
close all;              % 关闭所有打开的图形窗口

ch_func = Channel_functions();        % 创建信道函数对象,调用 Channel_functions 类
est_func = Estimation_functions();    % 创建估计函数对象,调用 Estimation_functions 类

%% Simulation Parameters
% 仿真参数设置
ChType   = 'RTV';                     % 设置信道模型为 'RTV'(Rayleigh Time-Varying 信道)
fs       = 64*156250;                 % 设置采样频率为 64*156250 Hz,基于 802.11p 协议,64 个子载波,每个子载波间距 156250 Hz
fc       = 5.2e9;                     % 设置载波频率为 5.2 GHz
v        = 0;                         % 设置用户移动速度为 0 km/h(静止)
c        = 3e8;                       % 设置光速为 3e8 m/s
fD       = (v/3.6)/c*fc;              % 计算多普勒频率(Hz),公式:fD = (v/3.6)/c*fc,速度单位从 km/h 转换为 m/s
rchan    = ch_func.GenFadingChannel(ChType, fD, fs); % 生成衰落信道,调用 GenFadingChannel 方法,输入信道类型、多普勒频率和采样频率
K        = 64;                        % 设置子载波数量为 64
K_cp     = K/4;                       % 设置循环前缀(Cyclic Prefix)的子载波数为 K/4,即 16

% Pre-defined preamble in frequency domain
% 定义频域中的前导序列
dp       = [ 0  0 0 0 0 0 +1 +1 -1 -1 +1  +1 -1  +1 -1 +1 +1 +1 +1 +1 +1 -1 -1 +1 +1 -1 +1 -1 +1 +1 +1 +1 0 +1 -1 -1 +1 +1 -1 +1 -1 +1 -1 -1 -1 -1 -1 +1 +1 -1 -1 +1 -1 +1 -1 +1 +1 +1 +1 0 0 0 0 0];
                                      % 定义频域前导序列 dp,长度为 64,包含 +1、-1 和 0
Kset     = find(dp~=0);               % 找到非零元素的位置,即激活的子载波索引
Kon      = length(Kset);              % 计算激活子载波的数量
Ep       = 1;                         % 设置前导序列功率为 1
dp       = sqrt(Ep)*dp.';             % 对前导序列进行归一化,乘以功率的平方根,并转置为列向量
xp       = ifft(dp);                  % 通过逆傅里叶变换(IFFT)将频域前导序列转换为时域
xp_cp    = [xp(end-K_cp+1:end); xp];  % 添加循环前缀,将时域信号末尾 K_cp 个样本添加到信号前面
SNR_p    = (0:5:30)';                % 定义信噪比(SNR)范围,从 0 到 30 dB,步长为 5 dB,转置为列向量
N_SNR    = length(SNR_p);             % 计算 SNR 值的数量
N0       = Ep/K*10.^(-SNR_p/10);      % 计算噪声功率,公式:N0 = Ep/(K*10^(SNR/10)),SNR 以 dB 为单位

N_CH     = 10000;                      % 设置信道实现次数为 1000
Err_Ls   = zeros(N_SNR,1);            % 初始化 LS(最小二乘)估计的归一化均方误差(NMSE)向量
Err_MMSE = zeros(N_SNR,1);            % 初始化 MMSE(最小均方误差)估计的 NMSE 向量
Phf      = zeros(N_SNR,1);            % 初始化平均信道功率向量 E(|hf|^2)

%% Rh estimation
% 信道自相关矩阵估计
release(rchan);                       % 释放信道对象,清除之前的设置
init_seed   = 22;                     % 设置初始随机种子为 22,用于可重复的随机数生成
rchan.Seed  = init_seed;              % 将信道对象的种子设置为初始种子
Rh          = est_func.Estimat_Rh(rchan, K_cp, K, Kset); % 估计信道自相关矩阵 Rh,输入信道对象、循环前缀长度、子载波数和激活子载波索引
release(rchan);                       % 再次释放信道对象
rchan.Seed  = init_seed;              % 重置信道对象的种子

%% Main Simulation 
% 主仿真循环
for n_snr = 1:N_SNR                   % 遍历所有 SNR 值
    disp(['Running Simulation, SNR = ', num2str(SNR_p(n_snr))]); % 显示当前仿真的 SNR 值
    tic;                              % 启动计时器,记录仿真时间
    Hfe_LS = zeros(Kon, N_CH);        % 初始化 LS 估计的频域信道矩阵
    Hfe_MMSE = zeros(Kon, N_CH);      % 初始化 MMSE 估计的频域信道矩阵
    Hf = zeros(Kon, N_CH);            % 初始化真实频域信道矩阵
    % MMSE 滤波器随 SNR 变化
    W = est_func.MMSE_matrix(dp(Kset), Rh, Ep/N0(n_snr)/K); % 计算 MMSE 滤波矩阵,输入激活子载波的前导序列、信道自相关矩阵和噪声功率
    for n_ch = 1:N_CH                 % 遍历所有信道实现
        % 理想信道估计
        [ h, y ] = ch_func.ApplyChannel(rchan, xp_cp, K_cp); % 应用信道,生成时域信道响应 h 和接收信号 y
        release(rchan);               % 释放信道对象
        rchan.Seed = rchan.Seed+1;    % 递增信道种子,生成不同的信道实现
        
        yp = y((K_cp+1):end);         % 移除接收信号的循环前缀,提取有效部分
        h = h((K_cp+1):end);          % 移除信道响应的循环前缀
        yfp = fft(yp);                % 对接收信号进行傅里叶变换,转换为频域
        hf = fft(h);                  % 对信道响应进行傅里叶变换,转换为频域
        Phf(n_snr) = Phf(n_snr) + norm(hf(Kset))^2; % 计算激活子载波的信道功率并累加
        
        % 添加噪声
        yfp_r = yfp + sqrt(K*N0(n_snr))*ch_func.GenRandomNoise([1,1], 1); % 在频域信号上添加高斯白噪声
        
        % LS 估计
        [hfe_ls, err_ls] = est_func.LS(yfp_r(Kset), dp(Kset), hf(Kset)); % 进行 LS 估计,计算估计信道和误差
        Err_Ls(n_snr) = Err_Ls(n_snr) + err_ls; % 累加 LS 估计的误差
        
        % MMSE 估计
        [hfe_mmse, err_mmse] = est_func.MMSE(yfp_r(Kset), W, hf(Kset)); % 进行 MMSE 估计,计算估计信道和误差
        Err_MMSE(n_snr) = Err_MMSE(n_snr) + err_mmse; % 累加 MMSE 估计的误差
        
        % 保存信道数据供后续使用
        Hf(:,n_ch) = hf(Kset);        % 保存真实频域信道
        Hfe_LS(:,n_ch) = hfe_ls;      % 保存 LS 估计的频域信道
        Hfe_MMSE(:,n_ch) = hfe_mmse;  % 保存 MMSE 估计的频域信道
    end
    % 保存仿真数据
    save(['./Dataset_' num2str(n_snr)], 'Hf', 'Hfe_LS'); % 保存真实信道和 LS 估计信道到文件
    toc;                              % 停止计时器,显示仿真时间
end

%% Averaging over channel realizations
% 对信道实现取平均
Phf       = Phf/N_CH;                 % 计算平均信道功率
Err_Ls    = Err_Ls/N_CH;              % 计算 LS 估计的平均 NMSE
Err_MMSE  = Err_MMSE/N_CH;            % 计算 MMSE 估计的平均 NMSE

%% Theorectical LS NMSE Calculation
% 理论 LS NMSE 计算
Err_Ls_th = Kon*K*N0/Ep;              % 计算理论 LS NMSE,公式:Kon*K*N0/Ep

%% Theorectical MMSE NMSE Calculation
% 理论 MMSE NMSE 计算
release(rchan);                       % 释放信道对象
init_seed = 22;                       % 重置初始种子
rchan.Seed = init_seed;               % 设置信道种子
Rh = est_func.Estimat_Rh(rchan, K_cp, K, Kset); % 重新估计信道自相关矩阵
release(rchan);                       % 释放信道对象
rchan.Seed = init_seed;               % 重置信道种子
Sig = real(eig(Rh));                  % 计算信道自相关矩阵的特征值(取实部)
Err_MMSE_th = zeros(N_SNR,1);         % 初始化理论 MMSE NMSE 向量
for n_snr = 1:N_SNR                   % 遍历所有 SNR 值
    Err_MMSE_th(n_snr) = Err_MMSE_th(n_snr) + sum(Sig./(Sig+K*N0(n_snr)./Ep)); % 计算理论 MMSE NMSE
end
Err_MMSE_th = K*N0/Ep .* Err_MMSE_th; % 归一化理论 MMSE NMSE

%% Normalization by Channel Power 
% 按信道功率归一化
Err_LSth    = Err_Ls_th./Phf;         % 理论 LS NMSE 除以平均信道功率
Err_LSsim   = Err_Ls./Phf;            % 仿真 LS NMSE 除以平均信道功率
Err_MMSEth  = Err_MMSE_th./Phf;       % 理论 MMSE NMSE 除以平均信道功率
Err_MMSEsim = Err_MMSE./Phf;          % 仿真 MMSE NMSE 除以平均信道功率

%% Plotting NMSE Results
% 绘制 NMSE 结果
figure,                               % 创建新的图形窗口
semilogy(SNR_p, Err_LSth, 'k--', 'LineWidth', 2); 
hold on;                            
semilogy(SNR_p, Err_LSsim, 'k+', 'LineWidth', 2);
hold on;                              
semilogy(SNR_p, Err_MMSEth, 'k--', 'LineWidth', 2); 
hold on;                            
semilogy(SNR_p, Err_MMSEsim, 'ko', 'LineWidth', 2); 
hold on;                              
grid on;                            
legend('Analytical-Ls', 'sim-LS', 'Analytical-MMSE', 'sim-MMSE') % 添加图例
% xlabel('Preamble SNR')              
% ylabel('Average Error per subcarrier')
xlabel('SNR')                       
ylabel('NMSE')                        
save('ErrSet.mat', 'Err_LSth', 'Err_LSsim', 'Err_MMSEth', 'Err_MMSEsim') 
clc;                    % 清除命令行窗口的所有输出
clearvars;              % 清除工作区中的所有变量,释放内存
close all;              % 关闭所有打开的图形窗口

N_CH  = 10000;          % 定义样本数量(信道实现次数)。注意:这里是 1 万个样本
SNR_p = 0:5:30;         % 定义信噪比 (SNR) 的范围:从 0dB 到 30dB,步长为 5dB
k = 52;                 % 定义激活的有效子载波数量 (与论文中的 Kon = 52 对应)

% 初始化深度学习的输入特征矩阵 X 和目标标签矩阵 Y
% 维度为 104 x N_CH (104 是因为实部 52 个 + 虚部 52 个)
X = zeros(k*2, N_CH);   
Y = zeros(k*2, N_CH);   

% 遍历所有的信噪比场景 (n_snr 会从 1 循环到 7)
for n_snr = 1: size(SNR_p,2)

  % 加载主仿真脚本 (Main_Simulation.m) 生成的原始结果
  % Hf: 真实的频域信道响应;Hfe_LS: LS 算法粗略估计的频域信道响应
  load(['./Dataset_',num2str(n_snr),'.mat'], 'Hf', 'Hfe_LS');

  % 构建深度学习的输入特征 X (LS 估计值)
  X(1:k,:)     = real(Hfe_LS);    % 前 52 行存入 LS 估计值的实部
  X(k+1:2*k,:) = imag(Hfe_LS);    % 后 52 行存入 LS 估计值的虚部
  
  % 构建深度学习的目标标签 Y (信道真实值)
  Y(1:k,:)     = real(Hf);        % 前 52 行存入真实信道的实部
  Y(k+1:2*k,:) = imag(Hf);        % 后 52 行存入真实信道的虚部

  % 将 X 和 Y 转置 (从 104 x N_CH 变为 N_CH x 104),以符合 Python Keras 模型的数据输入规范
  % 并将它们打包进一个名为 Preamble_Error_Correction_Dataset 的结构体中
  Preamble_Error_Correction_Dataset.('X') =  X.';
  Preamble_Error_Correction_Dataset.('Y') =  Y.';
  
  % 将打包好的结构体保存为新的 .mat 文件,专门供 Python 脚本读取训练
  save(['./DNN_Dataset_' num2str(n_snr),'.mat'],  'Preamble_Error_Correction_Dataset');

end
from keras import Sequential
from keras.callbacks import ModelCheckpoint
from keras.initializers import TruncatedNormal  # Corrected import
from keras.layers import Dense
from scipy.io import loadmat
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

snr = 7  # SNR Index 1: 0dB, 2: 5dB, 3: 10dB, 4: 20dB, etc...
# Load Matlab DataSets
mat = loadmat('./DNN_Dataset_{}.mat'.format(snr))
Dataset = mat['Preamble_Error_Correction_Dataset']
Dataset = Dataset[0, 0]
X = Dataset['X']
Y = Dataset['Y']
print('Loaded Dataset Inputs: ', X.shape)
print('Loaded Dataset Outputs: ', Y.shape)

# Normalizing Datasets
scalerx = StandardScaler()
scalerx.fit(X)
scalery = StandardScaler()
scalery.fit(Y)
XS = scalerx.transform(X)
YS = scalery.transform(Y)

# Split Data into train and test sets
seed = 7
train_X, test_X, train_Y, test_Y = train_test_split(XS, YS, test_size=0.2, random_state=seed)
print('Training samples: ', train_X.shape[0])
print('Testing samples: ', test_X.shape[0])

# Build the model.
init = TruncatedNormal(mean=0.0, stddev=0.05, seed=None)
model = Sequential([
    Dense(units=104, activation='relu', input_dim=104,
          kernel_initializer=init,
          bias_initializer=init),
    Dense(units=104, activation='relu',
          kernel_initializer=init,
          bias_initializer=init),
    Dense(units=104, kernel_initializer=init,
          bias_initializer=init)
])

# Compile the model.
model.compile(loss='mean_squared_error', optimizer='adam', metrics=['acc'])
print(model.summary())

model_path = './LS_DNN_{}.h5'.format(snr)

# This checkpoint saves the best DNN model with the highest validation accuracy
checkpoint = ModelCheckpoint(model_path, monitor='val_acc',
                             verbose=1, save_best_only=True,
                             mode='max')
callbacks_list = [checkpoint]

# Train the model.
epoch = 500
batch_size = 32

model.fit(train_X, train_Y, epochs=epoch, batch_size=batch_size, verbose=2, validation_split=0.25, callbacks=callbacks_list)

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐