├── .gitignore ├── LICENSE.md ├── README.md ├── radio_autoencoder.py ├── results ├── BLER_VS_SNR_UNCODED_AUTOENCODER.png └── BLER_VS_SNR_UNCODED_HAMMING_AUTOENCODER.png └── src ├── __init__.py ├── autoencoder.py ├── hamming.py └── uncoded.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # Eclipse project settings 98 | .project 99 | .pydevproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------- 2 | 3 | Copyright (C) 2016 CC0 1.0 Universal (CC0 1.0) 4 | 5 | The person who associated a work with this deed has dedicated the work to 6 | the public domain by waiving all of his or her rights to the work 7 | worldwide under copyright law, including all related and neighboring 8 | rights, to the extent allowed by law. 9 | 10 | You can copy, modify, distribute and perform the work, even for commercial 11 | purposes, all without asking permission. 12 | 13 | See the complete legal text at 14 | 15 | 16 | ------------------------------------------------------------------------- 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # py-radio-autoencoder 2 | Python implementation of autoencoder based channel coding over an AWGN link. 3 | 4 | # Description 5 | Simulates a radio link over AWGN channel for Uncoded, Hamming coded and Neural network autoencoded radio links. Reproduces the results obtained in "An Introduction to Deep Learning for the Physical Layer" by Timothy J. O'Shea and Jakob Hoydis [https://arxiv.org/abs/1702.00832]. 6 | 7 | # Run 8 | Install Tensorflow: https://www.tensorflow.org/install/ 9 | Install py-itpp: https://github.com/vidits-kth/py-itpp 10 | Clone the repo 11 | Run 12 | > python3 radio_autoencoder.py 13 | -------------------------------------------------------------------------------- /radio_autoencoder.py: -------------------------------------------------------------------------------- 1 | # File: radio_autoencoder.py 2 | # Brief: Simulates a radio link over AWGN channel for Uncoded, Hamming 3 | # coded and Neural network autoencoded radio links. 4 | # Reproduces the results obtained in 5 | # "An Introduction to Deep Learning for the Physical Layer", 6 | # Timothy J. O'Shea, Jakob Hoydis 7 | # Author: Vidit Saxena 8 | # 9 | # Usage: python radio_autoencoder.py 10 | # 11 | # ------------------------------------------------------------------------- 12 | # 13 | # Copyright (C) 2016 CC0 1.0 Universal (CC0 1.0) 14 | # 15 | # The person who associated a work with this deed has dedicated the work to 16 | # the public domain by waiving all of his or her rights to the work 17 | # worldwide under copyright law, including all related and neighboring 18 | # rights, to the extent allowed by law. 19 | # 20 | # You can copy, modify, distribute and perform the work, even for commercial 21 | # purposes, all without asking permission. 22 | # 23 | # See the complete legal text at 24 | # 25 | # 26 | # ------------------------------------------------------------------------- 27 | 28 | from src import uncoded, hamming 29 | from src import autoencoder 30 | 31 | from matplotlib import pyplot as plt 32 | 33 | ''' Function: _bler_vs_snr_hamming_autoenc(block_size, channel_use, snrs_db) 34 | Description: Block error ratio (BLER) vs Signal to Noise Ratio (SNR) 35 | curves for standard channel coding using Hamming codes, 36 | and for 'learnt' optimal representation that are obtained 37 | by training a neural network based autoencoder. Additive 38 | White Gaussian Noise (AWGN) channel is assumed. 39 | ''' 40 | def _bler_vs_snr_hamming_autoenc(block_size, channel_use, snrs_db): 41 | 42 | channel_use = {4: 7} # Mapping to get length of Hamming codeblock from block length 43 | 44 | '''BLER for uncoded BPSK over AWGN channel''' 45 | print('-------Evaluating BLER for Uncoded (%d,%d) over AWGN-------'%(block_size, block_size)) 46 | bler_unc = [uncoded.block_error_ratio_uncoded_awgn(snr, block_size) for snr in snrs_db] 47 | 48 | '''BLER for Hamming coded bits over AWGN channel''' 49 | print('-------Evaluating BLER for Hamming (%d,%d) over AWGN-------' %(channel_use[block_size], block_size)) 50 | bler_hamming = [hamming.block_error_ratio_hamming_awgn(snr, block_size) for snr in snrs_db] 51 | 52 | '''BLER for Autoencoder coded bits over AWGN channel''' 53 | print('-------Evaluating BLER for Autoencoder (%d,%d) over AWGN-------' %(channel_use[block_size], block_size)) 54 | batch_size = int(20/block_size) 55 | nrof_steps = int(200000/block_size) 56 | bler_autoenc = autoencoder.block_error_ratio_autoencoder_awgn(snrs_db, block_size, channel_use[block_size], batch_size, nrof_steps) 57 | 58 | print('-------Plotting results-------') 59 | plt.figure() 60 | plt.grid(True) 61 | plt.semilogy(snrs_db, bler_unc, ls = '-', c = 'b') 62 | plt.semilogy(snrs_db, bler_hamming, ls = '--', c = 'g') 63 | plt.semilogy(snrs_db, bler_autoenc, ls = '--', c = 'r', marker = 'o') 64 | plt.xlabel('SNR [dB]') 65 | plt.ylabel('Block Error Ratio') 66 | plt.legend(['Uncoded BPSK (%d,%d)'%(block_size, block_size), 67 | 'Hamming (%d,%d)'%(channel_use[block_size], block_size), 68 | 'Autoencoder (%d,%d)'%(channel_use[block_size], block_size)], 69 | loc = 'lower left') 70 | plt.title('BLER vs SNR for Autoencoder and several baseline communication schemes') 71 | 72 | return (bler_hamming, bler_autoenc) 73 | 74 | ''' Function: _bler_vs_snr_hamming_autoenc(block_size, channel_use, snrs_db) 75 | Description: Block error ratio (BLER) vs Signal to Noise Ratio (SNR) curves 76 | for uncoded transmission, and for 'learnt' optimal 77 | representations of the transmitted bits that are obtained by 78 | training a neural network based autoencoder. Additive White 79 | Gaussian Noise (AWGN) channel is assumed. 80 | ''' 81 | def _bler_vs_snr_uncoded_autoenc(block_sizes, snrs_db): 82 | 83 | plt.figure() 84 | plt.grid(True) 85 | legend_strings = [] 86 | colors = iter(list(['b', 'r', 'g', 'b', 'm'])) 87 | for block_size in block_sizes: 88 | '''BLER for uncoded bits over AWGN channel''' 89 | print('-------Evaluating BLER for Uncoded BPSK (%d,%d) over AWGN-------' %(block_size, block_size)) 90 | bler_unc = [uncoded.block_error_ratio_uncoded_awgn(snr, block_size) for snr in snrs_db] 91 | 92 | '''BLER for Autoencoder coded bits over AWGN channel''' 93 | print('-------Evaluating BLER for Autoencoder (%d,%d) over AWGN-------' %(block_size, block_size)) 94 | batch_size = 10/block_size 95 | nrof_steps = 100000/block_size 96 | bler_autoenc = autoencoder.block_error_ratio_autoencoder_awgn(snrs_db, block_size, block_size, batch_size, nrof_steps) 97 | 98 | print('-------Plotting results-------') 99 | color = next(colors) 100 | plt.semilogy(snrs_db, bler_unc, ls = '-', c = color) 101 | plt.semilogy(snrs_db, bler_autoenc, ls = '--', c = color, marker = 'o') 102 | 103 | legend_strings.append('Uncoded BPSK (%d,%d)'%(block_size, block_size)) 104 | legend_strings.append('Autoencoder (%d,%d)'%(block_size, block_size)) 105 | 106 | plt.xlabel('SNR [dB]') 107 | plt.ylabel('Block Error Ratio') 108 | plt.legend(legend_strings, loc = 'lower left') 109 | plt.title('BLER vs SNR for Autoencoder and BPSK') 110 | 111 | if __name__ == '__main__': 112 | snrs_db = range(-4, 9) 113 | 114 | '''BLER for block size 4 with Autoencoder and Hamming''' 115 | block_size = 4 116 | channel_use = 7 117 | _bler_vs_snr_hamming_autoenc(block_size, channel_use, snrs_db) 118 | 119 | '''BLER for block sizes 2 and 8 with uncoded and Autoencoder''' 120 | block_sizes = [2, 8] 121 | _bler_vs_snr_uncoded_autoenc(block_sizes, snrs_db) 122 | 123 | plt.show() 124 | -------------------------------------------------------------------------------- /results/BLER_VS_SNR_UNCODED_AUTOENCODER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vidits-kth/py-radio-autoencoder/0aeb2ca41c9987b040bce536cffe31be8db7e791/results/BLER_VS_SNR_UNCODED_AUTOENCODER.png -------------------------------------------------------------------------------- /results/BLER_VS_SNR_UNCODED_HAMMING_AUTOENCODER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vidits-kth/py-radio-autoencoder/0aeb2ca41c9987b040bce536cffe31be8db7e791/results/BLER_VS_SNR_UNCODED_HAMMING_AUTOENCODER.png -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | from . import autoencoder 2 | from . import hamming 3 | from . import uncoded 4 | -------------------------------------------------------------------------------- /src/autoencoder.py: -------------------------------------------------------------------------------- 1 | # File: autoencoder.py 2 | # Brief: Neural network autoencoded radio link over AWGN channel 3 | # Author: Vidit Saxena 4 | # 5 | # Usage: import autoencoder 6 | # 7 | # ------------------------------------------------------------------------- 8 | # 9 | # Copyright (C) 2016 CC0 1.0 Universal (CC0 1.0) 10 | # 11 | # The person who associated a work with this deed has dedicated the work to 12 | # the public domain by waiving all of his or her rights to the work 13 | # worldwide under copyright law, including all related and neighboring 14 | # rights, to the extent allowed by law. 15 | # 16 | # You can copy, modify, distribute and perform the work, even for commercial 17 | # purposes, all without asking permission. 18 | # 19 | # See the complete legal text at 20 | # 21 | # 22 | # ------------------------------------------------------------------------- 23 | 24 | import tensorflow as tf 25 | import numpy as np 26 | 27 | def block_error_ratio_autoencoder_awgn(snrs_db, block_size, channel_use, batch_size, nrof_steps): 28 | 29 | print('block_size %d'%(block_size)) 30 | print('channel_use %d'%(channel_use)) 31 | 32 | rate = float(block_size)/float(channel_use) 33 | print('rate %0.2f'%(rate)) 34 | 35 | '''The input is one-hot encoded vector for each codeword''' 36 | alphabet_size = pow(2, block_size) 37 | alphabet = np.eye(alphabet_size, dtype = 'float32') # One-hot encoded values 38 | 39 | '''Repeat the alphabet to create training and test datasets''' 40 | train_dataset = np.transpose(np.tile(alphabet, int(batch_size))) 41 | test_dataset = np.transpose(np.tile(alphabet, int(batch_size * 1000))) 42 | 43 | print('--Setting up autoencoder graph--') 44 | input, output, noise_std_dev, h_norm = _implement_autoencoder(alphabet_size, channel_use) 45 | 46 | print( '--Setting up training scheme--') 47 | train_step = _implement_training(output, input) 48 | 49 | print('--Setting up accuracy--') 50 | accuracy = _implement_accuracy(output, input) 51 | 52 | print('--Starting the tensorflow session--') 53 | sess = _setup_interactive_tf_session() 54 | _init_and_start_tf_session(sess) 55 | 56 | print('--Training the autoencoder over awgn channel--') 57 | _train(train_step, input, noise_std_dev, nrof_steps, train_dataset, snrs_db, rate, accuracy) 58 | 59 | print('--Evaluating autoencoder performance--') 60 | bler = _evaluate(input, noise_std_dev, test_dataset, snrs_db, rate, accuracy) 61 | 62 | print('--Closing the session--') 63 | _close_tf_session(sess) 64 | 65 | return bler 66 | 67 | def _setup_tf_session(): 68 | return tf.Session() 69 | 70 | def _setup_interactive_tf_session(): 71 | return tf.compat.v1.InteractiveSession() 72 | 73 | def _init_and_start_tf_session(): 74 | init = tf.compat.v1.global_variables_initializer() 75 | sess = tf.Session() 76 | sess.run(init) 77 | return sess 78 | 79 | def _init_and_start_tf_session(sess): 80 | sess.run(tf.compat.v1.global_variables_initializer()) 81 | 82 | def _close_tf_session(sess): 83 | sess.close 84 | 85 | def _weight_variable(shape): 86 | initial = tf.random.truncated_normal(shape, stddev=0.01) 87 | return tf.Variable(initial) 88 | 89 | def _bias_variable(shape): 90 | initial = tf.constant(0.01, shape=shape) 91 | return tf.Variable(initial) 92 | 93 | def _implement_autoencoder(input_dimension, encoder_dimension): 94 | input = tf.compat.v1.placeholder(tf.float32, [None, input_dimension]) 95 | 96 | '''Densely connected encoder layer''' 97 | W_enc1 = _weight_variable([input_dimension, input_dimension]) 98 | b_enc1 = _bias_variable([input_dimension]) 99 | 100 | h_enc1 = tf.nn.relu(tf.matmul(input, W_enc1) + b_enc1) 101 | 102 | '''Densely connected encoder layer''' 103 | W_enc2 = _weight_variable([input_dimension, encoder_dimension]) 104 | b_enc2 = _bias_variable([encoder_dimension]) 105 | 106 | h_enc2 = tf.matmul(h_enc1, W_enc2) + b_enc2 107 | 108 | '''Normalization layer''' 109 | normalization_factor = tf.math.reciprocal(tf.sqrt(tf.reduce_sum(tf.square(h_enc2), 1))) * np.sqrt(encoder_dimension) 110 | h_norm = tf.multiply(tf.tile(tf.expand_dims(normalization_factor, 1), [1, encoder_dimension]), h_enc2) 111 | 112 | '''AWGN noise layer''' 113 | noise_std_dev = tf.compat.v1.placeholder(tf.float32) 114 | channel = tf.random.normal(tf.shape(h_norm), stddev=noise_std_dev) 115 | h_noisy = tf.add(h_norm, channel) 116 | 117 | '''Densely connected decoder layer''' 118 | W_dec1 = _weight_variable([encoder_dimension, input_dimension]) 119 | b_dec1 = _bias_variable([input_dimension]) 120 | 121 | h_dec1 = tf.nn.relu(tf.matmul(h_noisy, W_dec1) + b_dec1) 122 | 123 | '''Output layer''' 124 | W_out = _weight_variable([input_dimension, input_dimension]) 125 | b_out = _bias_variable([input_dimension]) 126 | 127 | output = tf.nn.softmax(tf.matmul(h_dec1, W_out) + b_out) 128 | 129 | return (input, output, noise_std_dev, h_norm) 130 | 131 | def _implement_training(output, input): 132 | cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits = output, labels = input)) 133 | 134 | # train_step = tf.train.GradientDescentOptimizer(1e-2).minimize(cross_entropy) 135 | train_step = tf.compat.v1.train.AdamOptimizer(1e-3).minimize(cross_entropy) 136 | 137 | return train_step 138 | 139 | def _implement_accuracy(output, input): 140 | correct_prediction = tf.equal(tf.argmax(output, 1), tf.argmax(input, 1)) 141 | accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) 142 | return accuracy 143 | 144 | def _train(train_step, input, noise_std_dev, nrof_steps, training_dataset, snrs_db, rate, accuracy): 145 | print('--Training--') 146 | print('number of steps %d'%(nrof_steps)) 147 | snr = max(snrs_db) 148 | snrs_rev = snrs_db[::-1] 149 | for snr in snrs_rev[0:1]: # Train with higher SNRs first 150 | print('training snr %0.2f db'%(snr)) 151 | noise = np.sqrt(1.0 / (2 * rate * pow(10, 0.1 * snr))) 152 | for i in range(int(nrof_steps)): 153 | batch = training_dataset 154 | np.random.shuffle(batch) 155 | if (i + 1) % (nrof_steps/10) == 0: # i = 0 is the first step 156 | print('training step %d'%(i + 1)) 157 | train_step.run(feed_dict={input: batch, noise_std_dev: noise}) 158 | print('training accuracy %0.4f'%(accuracy.eval(feed_dict={input: batch, noise_std_dev: noise}))) 159 | 160 | def _evaluate(input, noise_std_dev, test_dataset, snrs_db, rate, accuracy): 161 | print('--Evaluating NN performance on test dataset--') 162 | bler = [] 163 | for snr in snrs_db: 164 | noise = np.sqrt(1.0 / (2 * rate * pow(10, 0.1 * snr))) 165 | acc = accuracy.eval(feed_dict={input: test_dataset, noise_std_dev: noise}) 166 | bler.append(1.0 - acc) 167 | return bler 168 | -------------------------------------------------------------------------------- /src/hamming.py: -------------------------------------------------------------------------------- 1 | # File: hamming.py 2 | # Brief: Simulates a Hamming coded radio link over AWGN channel 3 | # Author: Vidit Saxena 4 | # 5 | # Usage: import hamming 6 | # 7 | # ------------------------------------------------------------------------- 8 | # 9 | # Copyright (C) 2016 CC0 1.0 Universal (CC0 1.0) 10 | # 11 | # The person who associated a work with this deed has dedicated the work to 12 | # the public domain by waiving all of his or her rights to the work 13 | # worldwide under copyright law, including all related and neighboring 14 | # rights, to the extent allowed by law. 15 | # 16 | # You can copy, modify, distribute and perform the work, even for commercial 17 | # purposes, all without asking permission. 18 | # 19 | # See the complete legal text at 20 | # 21 | # 22 | # ------------------------------------------------------------------------- 23 | 24 | import itpp 25 | 26 | def block_error_ratio_hamming_awgn(snr_db, block_size): 27 | 28 | mapping_k_m = {4: 3} # Mapping from k (block size) to m. m = 3 implies (7,4) code 29 | m = mapping_k_m[block_size] 30 | 31 | '''Hamming encoder and decoder instance''' 32 | hamm = itpp.comm.Hamming_Code(m) 33 | n = pow(2,m) - 1 # channel use 34 | rate = float(block_size)/float(n) 35 | 36 | '''Generate random bits''' 37 | nrof_bits = 10000 * block_size 38 | source_bits = itpp.randb(nrof_bits) 39 | 40 | '''Encode the bits''' 41 | encoded_bits = hamm.encode(source_bits) 42 | 43 | '''Modulate the bits''' 44 | modulator_ = itpp.comm.modulator_2d() 45 | constellation = itpp.cvec('-1+0i, 1+0i') 46 | symbols = itpp.ivec('0, 1') 47 | modulator_.set(constellation, symbols) 48 | tx_signal = modulator_.modulate_bits(encoded_bits) 49 | 50 | '''Add the effect of channel to the signal''' 51 | noise_variance = 1.0 / (rate * pow(10, 0.1 * snr_db)) 52 | noise = itpp.randn_c(tx_signal.length()) 53 | noise *= itpp.math.sqrt(noise_variance) 54 | rx_signal = tx_signal + noise 55 | 56 | '''Demodulate the signal''' 57 | demodulated_bits = modulator_.demodulate_bits(rx_signal) 58 | 59 | '''Decode the received bits''' 60 | decoded_bits = hamm.decode(demodulated_bits) 61 | 62 | '''Calculate the block error ratio''' 63 | blerc = itpp.comm.BLERC(block_size) 64 | blerc.count(source_bits, decoded_bits) 65 | return blerc.get_errorrate() 66 | -------------------------------------------------------------------------------- /src/uncoded.py: -------------------------------------------------------------------------------- 1 | # File: uncoded.py 2 | # Brief: Simulates uncoded link over AWGN channel 3 | # Author: Vidit Saxena 4 | # 5 | # Usage: import uncoded 6 | # 7 | # ------------------------------------------------------------------------- 8 | # 9 | # Copyright (C) 2016 CC0 1.0 Universal (CC0 1.0) 10 | # 11 | # The person who associated a work with this deed has dedicated the work to 12 | # the public domain by waiving all of his or her rights to the work 13 | # worldwide under copyright law, including all related and neighboring 14 | # rights, to the extent allowed by law. 15 | # 16 | # You can copy, modify, distribute and perform the work, even for commercial 17 | # purposes, all without asking permission. 18 | # 19 | # See the complete legal text at 20 | # 21 | # 22 | # ------------------------------------------------------------------------- 23 | 24 | import itpp 25 | 26 | def block_error_ratio_uncoded_awgn(snr_db, block_size): 27 | '''Generate random bits''' 28 | nrof_bits = 3 * 10000 * block_size 29 | source_bits = itpp.randb(nrof_bits) 30 | rate = 1.0 31 | 32 | '''Modulate the bits''' 33 | modulator_ = itpp.comm.modulator_2d() 34 | constellation = itpp.cvec('-1+0i, 1+0i') 35 | symbols = itpp.ivec('0, 1') 36 | modulator_.set(constellation, symbols) 37 | tx_signal = modulator_.modulate_bits(source_bits) 38 | 39 | '''Add the effect of channel to the signal''' 40 | noise_variance = 1.0 / (rate * pow(10, 0.1 * snr_db)) 41 | noise = itpp.randn_c(tx_signal.length()) 42 | noise *= itpp.math.sqrt(noise_variance) 43 | rx_signal = tx_signal + noise 44 | 45 | '''Demodulate the signal''' 46 | demodulated_bits = modulator_.demodulate_bits(rx_signal) 47 | 48 | '''Calculate the block error ratio''' 49 | blerc = itpp.comm.BLERC(block_size) 50 | blerc.count(source_bits, demodulated_bits) 51 | return blerc.get_errorrate() 52 | --------------------------------------------------------------------------------