# OFDM Autoencoder for Wireless Communications

This example shows how to model an end-to-end orthogonal frequency division modulation (OFDM) communications system with an autoencoder to reliably transmit information bits over a wireless channel.

### Introduction

This example uses an autoencoder together with OFDM modulator and demodulator layers to design and implement a multi-carrier communications system.

In this example, you will learn how to:

Use the

`sequenceInputLayer`

(Deep Learning Toolbox) function to train the network with multiple SNR values.Create OFDM modulation and demodulation layers using the

`ofdmmod`

and`ofdmdemod`

functions.Train a fully connected neural network with embedded OFDM modulation and demodulation.

Separate the neural network into encoder and decoder networks.

Run BLER simulations to compare error rate performance of a conventional OFDM link to an AI-based OFDM link.

For an equivelent single-carrier communications system, see the Autoencoders for Wireless Communications example.

### OFDM-based Autoencoder System

This block diagram shows a wireless autoencoder communications system. The encoder (transmitter) first maps each $\mathit{k}$ set of information bits in a sequence into a message $$s$$ such that $$s\in \{0,\dots ,M-1\}$$, where $$M={2}^{k}$$ to form $$T$$ messages. Each of the $$T$$ messages, $$s$$, is mapped to $$n$$ real-valued channel uses, $$\text{x}=f(s)\in {\mathbb{R}}^{n}$$, which results in an effective coding rate of $$R=k/n$$ data bits per real channel use. Then, two real channel uses are mapped into a complex symbol to create $${\text{x}}_{c}=g(s)\in {\mathbb{C}}^{n/2}$$. The normalization layer of the encoder imposes constraints on $$\text{x}$$ to further restrict the encoded symbols. To illustrate possibilities, these constraints are implemented using the normalization layer:

Energy constraint: $$\Vert {x}_{i}{\Vert}_{2}^{2}=1,\forall i$$

Average power constraint: $$\mathbb{E}[|{x}_{i}{|}^{2}]=1,\forall i$$

Normalized symbols are mapped onto the OFDM subcarriers and passed through an AWGN channel.

The transmitter encodes $$s$$ and outputs encoded symbols, $$x$$. The channel impairs the encoded symbols to generate $$\text{y}\in {\mathbb{R}}^{n/2}$$. The receiver decodes $$y$$ and outputs estimate, $$\underset{}{\overset{\u02c6}{s}}$$, of the transmitted message $$s$$.

The input message is a one-hot vector $${\text{1}}_{s}\in {\mathbb{R}}^{M}$$, whose elements are all zeros except the ${\mathit{s}}^{\mathrm{th}}$ one. The AWGN channel adds noise to achieve the specified signal to noise power ratio, $$SNR$$.

#### Generate and Preprocess Data

The input to the transmitter is a random sequence of $\mathit{k}$ bits. $\mathit{k}$ bits can create $$M={2}^{k}$$ distinct messages or input symbols. The input symbol is a categorical feature from the set of $\left\{0,1,...,\mathit{M}-1\right\}$. As the number of possible input symbols increases, the number of training symbols must increase to give the network a chance to experience a large number of possible input combinations. The same is true for the number of validation symbols. Set number of input bits to 2.

k = 2; % Information bits per symbol M = 2^k; % Size of information symbols set numTrainSymbols = 2560 * M; numValidationSymbols = 128 * M;

The autoencoder neural network best works with one-hot inputs and classifies each input symbol as one of the categorical values, $\left\{0,1,...,\mathit{M}-1\right\}$. Convert random input symbols into a one-hot array using `onehotencode`

(Deep Learning Toolbox) function and create labels of categorical values. Place the one-hot value to the first dimension (rows) and input symbols to the second dimension (columns).

dTrain = randi([0 M-1],1,5)

`dTrain = `*1×5*
3 3 0 3 2

`trainSymbolsTemp = onehotencode(dTrain,1,"ClassNames",0:M-1)`

`trainSymbolsTemp = `*4×5*
0 0 1 0 0
0 0 0 0 0
0 0 0 0 1
1 1 0 1 0

trainLabelsTemp = categorical(dTrain)

`trainLabelsTemp = `*1x5 categorical*
3 3 0 3 2

Training the neural network at several SNR levels ensures that the autoencoder can handle a range of SNR values without retraining. Set training SNR values as an array between -1 dB and 9 dB. Generate multiple batches of training sequences, where each batch (cell) experiences a different SNR. Set random number generator state for repeatable results for demonstration purposes only.

trainSNRVec = -1:2:9; % Training SNR (dB) rng(1234) trainSymbols = cell(1,length(trainSNRVec)); trainLabels = cell(1,length(trainSNRVec)); validationSymbols = cell(1,length(trainSNRVec)); validationLabels = cell(1,length(trainSNRVec)); for p=1:length(trainSNRVec) dTrain = randi([0 M-1],1,numTrainSymbols); dValid = randi([0 M-1],1,numValidationSymbols); trainSymbols{p} = onehotencode(dTrain,1,"ClassNames",0:M-1); trainLabels{p} = categorical(dTrain); validationSymbols{p} = onehotencode(dValid,1,"ClassNames",0:M-1); validationLabels{p} = categorical(dValid); end

Size of training symbols is $$M\times {N}_{Sym}$$. Size of training labels is $$1\times {N}_{Sym}$$.

numBatches = length(trainSymbols)

numBatches = 6

sizeTrainSymbols = size(trainSymbols{1})

`sizeTrainSymbols = `*1×2*
4 10240

sizeTrainLabels = size(trainLabels{1})

`sizeTrainLabels = `*1×2*
1 10240

### Define and Train Neural Network Model

The second step of designing an AI-based system is to define and train the neural network model.

#### Define Neural Network

This example uses a modified version of the autoencoder neural network proposed in [2]. Set the number of subcarriers, $${N}_{fft}$$, to 256. The two fully connected layers map $$k$$ bits (in the form of length $$M$$ one-hot arrays) into $$n$$ real numbers, resulting in a rate $$R=k/n$$ communications system. After normalization, the OFDM modulator layer maps these $$n$$ real numbers into $$n/2$$ complex valued symbols and assigns each symbol to a subcarrier. To ensure that OFDM modulator layer outputs full OFDM symbols, set minimum input length, `MinLength`

, of the sequence input layer in the third dimension (T) to $${N}_{fft}$$. Therefore, the input to the neural network is a sequence of one-hot values with size $$M\times \phantom{\rule{0.5em}{0ex}}{N}_{fft}$$. This network uses the `sequenceInputLayer`

function with $$M$$ number of features and $${N}_{fft}$$ sequence length.

The reliability of the communication link can be increased through multiple uses of the channel for the same information symbol, which is also known as coding gain. An autoencoder can learn to leverage this increased number of channel uses, $$n>k$$. The following trains an OFDM-based (6,2) autoencoder, which is equivalent to having a coding rate, $$R$$, of 1/3. Set $$n$$ to 6.

Nfft = 256; % Number of OFDM subcarriers n = 6; % (n/2) is the number of complex channel uses CPLength = 4; % Samples normalization = "Energy"; % Normalization "Energy" | "Average power" ofdmAELayerGraph = [ sequenceInputLayer(M,Name="One-hot input",MinLength=Nfft) fullyConnectedLayer(M,Name="fc_1") reluLayer(Name="relu_1") fullyConnectedLayer(n,Name="fc_2",BiasInitializer="narrow-normal") helperAEWOFDMNormalizationLayer(Method=normalization) helperAEWOFDMModLayer(Nfft,CPLength,Name="OFDM Mod"); helperAEWOFDMAWGNLayer(SNR=trainSNRVec,SignalPower=1) helperAEWOFDMDemodLayer(Nfft,CPLength,Name="OFDM Demod"); fullyConnectedLayer(M,Name="fc_3") reluLayer(Name="relu_2") fullyConnectedLayer(M,Name="fc_4") softmaxLayer(Name="softmax") classificationLayer(Name="classoutput")];

The following shows the output sizes for each layer in the autoencoder layer.

To see the full analysis of the network, check the box in the `if`

statement.

if false analyzeNetwork(ofdmAELayerGraph) %#ok<UNRCH> end

#### Train Neural Network

Set the training options for the autoencoder neural network and train the network using the `trainNetwork`

(Deep Learning Toolbox) function. Training takes about 15 seconds on an AMD EPYC 7262 3.2 GHz 8C/16T.

% Set training options options = trainingOptions('adam', ... InitialLearnRate=0.02, ... MaxEpochs=10, ... OutputNetwork="best-validation-loss", ... Shuffle='every-epoch', ... ValidationData={validationSymbols,validationLabels}, ... LearnRateSchedule="piecewise", ... LearnRateDropPeriod=5, ... LearnRateDropFactor=0.1, ... ExecutionEnvironment="cpu", ... Plots='none', ... SequenceLength=Nfft); % Train the autoencoder network [trainedNet,trainInfo] = trainNetwork(trainSymbols,trainLabels,ofdmAELayerGraph,options);

|======================================================================================================================| | Epoch | Iteration | Time Elapsed | Mini-batch | Validation | Mini-batch | Validation | Base Learning | | | | (hh:mm:ss) | Accuracy | Accuracy | Loss | Loss | Rate | |======================================================================================================================| | 1 | 1 | 00:00:00 | 13.22% | 24.67% | 1.6775 | 1.4967 | 0.0200 | | 2 | 50 | 00:00:02 | 74.15% | 72.56% | 0.4678 | 0.4751 | 0.0200 | | 3 | 100 | 00:00:05 | 95.18% | 95.67% | 0.1513 | 0.1256 | 0.0200 | | 4 | 150 | 00:00:08 | 97.14% | 96.65% | 0.0893 | 0.1177 | 0.0200 | | 5 | 200 | 00:00:11 | 96.48% | 96.29% | 0.1010 | 0.1127 | 0.0200 | | 7 | 250 | 00:00:13 | 96.29% | 96.48% | 0.0968 | 0.1106 | 0.0020 | | 8 | 300 | 00:00:17 | 97.53% | 96.26% | 0.1039 | 0.1224 | 0.0020 | | 9 | 350 | 00:00:20 | 96.61% | 96.39% | 0.1090 | 0.1210 | 0.0020 | | 10 | 400 | 00:00:25 | 96.88% | 96.06% | 0.0999 | 0.1272 | 0.0020 | |======================================================================================================================| Training finished: Max epochs completed.

trainInfo.n = n; trainInfo.k = k; trainInfo.Normalization = normalization;

Plot the training progress. The validation accuracy quickly reaches more than 90% while the validation loss keeps slowly decreasing. This behavior shows that the training $$SNR$$ value was low enough to cause some errors but not too low to avoid convergence. If $$SNR$$ is too high that the network does not experience any errors, then the autoencoder does not learn how to correct channel impairments. A rule of thumb is to keep the validation accuracy between 85% and 95%. For definitions of validation accuracy and validation loss, see Monitor Deep Learning Training Progress (Deep Learning Toolbox) section.

figure helperAEWPlotTrainingPerformance(trainInfo)

Separate the network into encoder and decoder parts. Encoder starts with the input layer and ends after the OFDM modulator layer. Since the OFDM modulator changes the number of time samples (adds cyclic-prefix), use `dlnetwork`

for the encoder network.

for idxOFDMLayer = 1:length(trainedNet.Layers) if isa(trainedNet.Layers(idxOFDMLayer), 'helperAEWOFDMModLayer') break end end lgraph = layerGraph(trainedNet.Layers(1:idxOFDMLayer)); txNet = dlnetwork(lgraph);

Decoder starts with the OFDM demodulator layer and ends with the classification layer. Add a feature input layer at the beginning. Since the OFDM demodulator changes the number of time samples (removes cyclic-prefix), use `dlnetwork`

for the decoder network.

for idxOFDMDemod = idxOFDMLayer+1:length(trainedNet.Layers) if isa(trainedNet.Layers(idxOFDMDemod), 'helperAEWOFDMDemodLayer') break end end firstLayerName = trainedNet.Layers(idxOFDMDemod).Name; lgraph = addLayers(layerGraph(sequenceInputLayer(2,Name="rxin",MinLength=(Nfft+CPLength)*n/2)), ... trainedNet.Layers(idxOFDMDemod:end)); lgraph = connectLayers(lgraph,'rxin',firstLayerName); lgraph = removeLayers(lgraph, 'classoutput'); rxNet = dlnetwork(lgraph);

Use the plot object function of the trained network objects to show the layer graphs of the full autoencoder, the encoder network, which is the transmitter, and the decoder network, which is the receiver.

figure tiledlayout(2,2) nexttile([2 1]) plot(trainedNet) title('Autoencoder') nexttile plot(txNet) title('Encoder/Tx') nexttile plot(rxNet) title('Decoder/Rx')

### Compare BLER of OFDM-based Autoencoder and Conventional OFDM over AWGN Channel

Set up simulation parameters. The following parameters ensures the simulation runs in about one minute while providing acceptable BLER results. Increase the SNR range and maximum number of frames to get more reliable results for a wider range.

SNRVec = 0:2:8; symbolsPerFrame = Nfft; signalPower = 1;

Generate random integers in the [0 $$M$$-1] range that represents $$k$$ random information bits. Encode these information bits into complex symbols with `helperAEWOFDMEncode`

function. `The helperAEWOFDMEncode`

function runs the encoder part of the autoencoder then maps the real valued $$\text{x}$$ vector into a complex valued $${x}_{c}$$ vector such that the odd and even elements are mapped into the in-phase and the quadrature component of a complex symbol, respectively, where $${\text{x}}_{c}=\text{x}(1:2:end)+j\text{x}(2:2:end)$$. In other words, treat the $$\text{x}$$ array as an interleaved complex array.

Pass the complex symbols through an AWGN channel. Decode the channel impaired complex symbols with the `helperAEWOFDMDecode`

function. The following code runs the simulation for each $$SNR$$ point for at least 100 block errors or at most 2000 frames. If Parallel Computing Toolbox™ is installed and a license is available, uncomment the `parfor`

line to run the simulations on a parallel pool.

minNumErrors = 100; maxNumFrames = 1000; M = 2^k; BLER = zeros(length(SNRVec),2); t = tic; %parfor snrIdx = 1:length(SNRVec) for snrIdx = 1:length(SNRVec) SNR = SNRVec(snrIdx); disp("Simulating for SNR = " + SNR) numBlockErrors = 0; numConvSymbolErrors = 0; frameCnt = 0; while (numBlockErrors < minNumErrors) ... && (frameCnt < maxNumFrames) d = randi([0 M-1],symbolsPerFrame,1); % Random information symbols % Run AE Tx x = helperAEWOFDMEncode(d,txNet); % Encoder % Run Coded OFDM TX coded = repelem(d,round(n/k)); % Simple repetition code xqamCoded = qammod(coded,M,UnitAveragePower=true); xConvCoded = sqrt(Nfft) * ofdmmod(reshape(xqamCoded,round(n/k),[])',Nfft,CPLength); % Put both through the same channel y = awgn(x,SNR,signalPower); yConvCoded = awgn(xConvCoded,SNR,signalPower); % Run AE Rx dHat = helperAEWOFDMDecode(y,rxNet); % Decoder % Run Coded OFDM Rx xqamHatCoded = ofdmdemod(yConvCoded,Nfft,CPLength); dConvHatCoded = qamdemod(xqamHatCoded',M,UnitAveragePower=true); dConvDecoded = mode(dConvHatCoded,1)'; % Compute and contrast error rate numBlockErrors = numBlockErrors + sum(d ~= dHat); numConvSymbolErrors = numConvSymbolErrors + sum(d ~= dConvDecoded); frameCnt = frameCnt + 1; end BLER(snrIdx,:) = [numBlockErrors numConvSymbolErrors] ... ./ (frameCnt*symbolsPerFrame); end

Simulating for SNR = 0 Simulating for SNR = 2 Simulating for SNR = 4 Simulating for SNR = 6 Simulating for SNR = 8

et = seconds(toc(t)); et.Format = 'mm:ss.SSS'; disp("Total simulation time: " + string(et))

Total simulation time: 01:03.374

Compare the results with that of an uncoded QPSK system with block length $$n=6$$. For this $$n$$ value, the autoencoder can get more coding gain than a simple repetition code. Also, it provides about 5.5 dB gain as compared to an uncoded QPSK system with block length 6.

figure EbNoVec = convertSNR([SNRVec 10],"snr","ebno",BitsPerSymbol=k); semilogy(SNRVec,BLER,'-o') hold on % Calculate uncoded block error rate pskBLER = 1-(1-berawgn(EbNoVec,'psk',2^k,'nondiff')).^n; semilogy([SNRVec 10],pskBLER,'--x') hold off ylim([1e-4 1]) grid on xlabel('E_b/N_o (dB)') ylabel('BLER') legend(sprintf('AE-OFDM (%d,%d)',n,k),sprintf('Conv-OFDM (%d,%d)',n,k),sprintf('QPSK (%d,%d)',n,k))

### Conclusions and Further Exploration

The BLER results show that by inserting the expert knowledge in the form of OFDM modulation and demodulation to the neural network, an OFDM-based autoencoder can be trained. By allowing for multiple channel uses per input symbol ( $$n>k$$ ), the autoencoder can learn to obtain coding gain better than the simple repetition codes.

Change $$n$$, $$k$$, $${N}_{fft}$$, $$CPLength$$, and normalization to train different autoencoders. Try different training $$SNR$$ values to optimize the training performance. See the help for the `helperAEWTrainOFDMAutoencoder`

function and the `helperAEWOFDMAutoencoderBLER`

function.

The results are obtained using the following default settings for training and BLER simulations:

trainParams.Plots = 'none'; trainParams.Verbose = true; trainParams.MaxEpochs = 10; trainParams.InitialLearnRate = 0.08; trainParams.LearnRateSchedule = 'piecewise'; trainParams.LearnRateDropPeriod = 5; trainParams.LearnRateDropFactor = 0.1; trainParams.SequenceLength = Nfft; simParams.SNRVec = 0:2:12; simParams.MinNumErrors = 100; simParams.MaxNumFrames = 3000; simParams.NumSymbolsPerFrame = Nfft; simParams.SignalPower = 1;

Vary these parameters to train different autoencoders and test their BLER performance. Experiment with different $$n$$, $$k$$, normalization, $${N}_{fft}$$ and $$SNR$$ values.

#### List of Helper Functions

### References

[1] T. O’Shea and J. Hoydis, "An Introduction to Deep Learning for the Physical Layer," in IEEE Transactions on Cognitive Communications and Networking, vol. 3, no. 4, pp. 563-575, Dec. 2017, doi: 10.1109/TCCN.2017.2758370.

[2] A. Felix, S. Cammerer, S. Dörner, J. Hoydis and S. Ten Brink, "OFDM-Autoencoder for End-to-End Learning of Communications Systems," *2018 IEEE 19th International Workshop on Signal Processing Advances in Wireless Communications (SPAWC)*, 2018, pp. 1-5, doi: 10.1109/SPAWC.2018.8445920.

## See Also

`ofdmmod`

| `ofdmdemod`

| `classificationLayer`

(Deep Learning Toolbox) | `sequenceInputLayer`

(Deep Learning Toolbox) | `fullyConnectedLayer`

(Deep Learning Toolbox) | `reluLayer`

(Deep Learning Toolbox) | `softmaxLayer`

(Deep Learning Toolbox)

## Related Topics

- Autoencoders for Wireless Communications
- CSI Feedback with Autoencoders
- Deep Learning in MATLAB (Deep Learning Toolbox)