论文及程序来源
论文链接:https://ieeexplore.ieee.org/document/8847377/
程序地址:https://github.com/TianLin0509/BF-design-with-DL
以下程序片段均摘录于上述程序地址。
博客主要对程序进行分析,有少部分的论文分析。由于对很多np函数不熟悉,增加了一些函数的定义,只记录自己的思考过程,供以后参考。
程序分析
使用环境:使用Anaconda进行环境搭建,Python2+Tensorflow1.12
运行顺序:先运行train.py来训练模型,再使用test.py来检验模型
加载并生成模拟数据
H, H_est = mat_load(path)
H_input = np.expand_dims(np.concatenate([np.real(H_est), np.imag(H_est)], 1), 1)
H = np.squeeze(H)
SNR = np.power(10, np.random.randint(-20, 20, [H.shape[0], 1]) / 10)
H以及H_est分别为加载后的完美CSI以及估计的CSI。其中H只在BFNN的offline训练过程中计算loss时用到,在online预测过程中不会使用到。H_est则作为BFNN的输入,计算最终的输出Vrf。
其中,H以及H_est的shape均为(10,1,64)。
由于H_est是复数组成的,而BFNN是一个只计算实数的网络,因此,H_est的实部和虚部需要使用np.concatenate()函数进行级联处理。
numpy.concatenate 函数用于沿指定轴连接相同形状的两个或多个数组,使用格式如下:
numpy.concatenate((a1, a2, …), axis)
参数说明:
a1, a2, …:相同类型的数组
axis:沿着它连接数组的轴,默认为 0 //这里使用的是axis=1
将实部和虚部级联后得到的中间变量的shape为(10,2,64),再使用np.expand_dims()函数将这个中间变量的shape扩展为(10,1,2,64)。
使用numpy.squeeze()从给定数组的形状中删除一维的条目,此时,H的shape为(10,64)。
在SNR的生成部分对源代码做了一些更改,我的环境在运行这一步的时候会报错,原因是无法进行负整数次幂的运算,更改后的代码为:
random_SNR = np.random.randint(-20, 20, [H.shape[0], 1]).astype(float)
SNR = np.power(10, random_SNR / 10)
模型建立
# imperfect CSI is used to output the vrf
imperfect_CSI = Input(name='imperfect_CSI', shape=(H_input.shape[1:4]), dtype=tf.float32)
# perfect_CSI is only used to compute the loss, and not required in prediction
perfect_CSI = Input(name='perfect_CSI', shape=(H.shape[1],), dtype=tf.complex64)
# the SNR is also fed into the BFNN
SNR_input = Input(name='SNR_input', shape=(1,), dtype=tf.float32)
temp = BatchNormalization()(imperfect_CSI)
temp = Flatten()(temp)
temp = BatchNormalization()(temp)
temp = Dense(256, activation='relu')(temp)
temp = BatchNormalization()(temp)
temp = Dense(128, activation='relu')(temp)
phase = Dense(Nt)(temp)
V_RF = Lambda(trans_Vrf, dtype=tf.complex64, output_shape=(Nt,))(phase)
rate = Lambda(Rate_func, dtype=tf.float32, output_shape=(1,))([perfect_CSI, V_RF, SNR_input])
model = Model(inputs=[imperfect_CSI, perfect_CSI, SNR_input], outputs=rate)
# the y_pred is the actual rate, thus the loss is y_pred, without labels
model.compile(optimizer='adam', loss=lambda y_true, y_pred: y_pred)
model.summary()
这一部分比较容易理解,注意H虽然也作为Input,只在计算loss值时使用。模型的三层全联接层(dense layer)分别有256、128和64个神经元,具体参数在论文中列出了。为了增强收敛性,每个dense layer之前都有一个批处理标准化层。
值得注意的是,在最后的一层Lambda层,是自定义的一层,function的实现在utils文件夹中,主要实现了恒模约束。
模型训练
reduce_lr = callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=20, min_lr=0.00005)
checkpoint = callbacks.ModelCheckpoint('./temp_trained.h5', monitor='val_loss',
verbose=0, save_best_only=True, mode='min', save_weights_only=True)
model.fit(x=[H_input, H, SNR], y=H, batch_size=256,
epochs=50000, verbose=2, validation_split=0.1, callbacks=[reduce_lr, checkpoint])
callbacks.ReduceLROnPlateau()函数即当标准评估停止提升时,降低学习速率。当val_loss不再降低时(计算在“存在的issues“中提出),调整学习率。具体函数各个参数含义课可以参考如下Keras文档。
https://keras.io/zh/callbacks/
模型检验
略
存在的issues
- loss的计算
论文自定了一种损失函数,使用Lambda层来计算真正的损失函数,并将之作为输出。
model.fit(x=[H_input, H, SNR], y=H, batch_size=256,
epochs=50000, verbose=2, validation_split=0.1, callbacks=[reduce_lr, checkpoint])
fit函数中的y=H没有任何意义,这里的y只需要扔进一个维度一致避免keras自动检查报错的值就行了,因此方便起见扔入了H, 事实上H并不会被用到。
- 数据集的选取
在相应的github中给出了信道仿真以及信道估计的参考。