[코드 리뷰] Over-the-Air Deep Learning Based Radio Signal Classification

이우준·2021년 8월 16일
1

지난 번에 진행했던 논문 내용 리뷰에 이어서 이번에는 간단히 코드를 리뷰해보려고 한다.

써있는 글을 번역해가며 내용을 정리했던 논문 리뷰와는 달리, 이번 코드 리뷰는 reference 코드를 찾아 알게 된 내용을 내 방식대로 정리한 것이기 때문에 어색하거나 틀린 내용이 있을 수 있다.

이를 감안하고 보면 좋을 것 같고, 읽는 분들로 하여금 조금이나마 이해에 도움이 되었으면 좋겠다.

본 포스팅에서 다룰 코드는 논문에서 언급된 dataset 이 어떤 방식으로 적용되는지 까지이다.

Reference로 참고할 코드를 찾기 위해 paperswithcode에서 해당 논문을 검색해보았고, 그 결과 3개의 코드가 있는 것을 확인했다. 그 중에서 그나마 star가 가장 많은 github 코드를 reference로 삼았는데, 해당 코드는 논문 저자의 github에 올라와 있는 코드와 거의 유사했다. 다만 저자의 코드는 옛날 버전의 python (2.x)을 사용한 것으로 보인다.

Reference 코드에서 사용한 변수를 대부분 그대로 사용하여 구현해 보았으므로, 변수 이름에 너무 많은 의미를 부여하지는 않았으면 한다. 아울러, package를 import하는 방법 관련해서도 따로 설명하지 않겠다.

Code 설명

Dataset은 이 사이트에 들어가서 DEEPSIG DATASET: RADIOML 2016.10A 항목의 Dataset Download를 눌러 압축 해제하는 방식으로 다운 받으면 된다. 그렇게 pickle 형식의 파일을 얻었다면 현재 python 코드가 들어있는 폴더에 추가해 아래와 같이 불러오자.

# Read 버전으로 .pkl 파일을 불러온다는 뜻
Xraw = pickle.load(open("RML2016.10a_dict.pkl",'rb'), encoding='Latin1')

파일을 확인해보면 다음과 같은 dictionary type으로 구성되어있는 것을 확인할 수 있다.

{('modulation type', 'SNR') : (1000, 2, 128) array}

예를 들어, 아래 코드의 결과는 (1000, 2, 128) 이다.

print(Xraw[('QPSK',10)].shape)

Xraw 변수 중에서 원하는 SNR threshold도 설정할 수 있다. 다음은 threshold를 2로 설정한 코드이다.

Xd = {}
SNR_thres = 2
# key 값들 중에서 SNR이 SNR_thres 보다 높다면 저장한다. 
for key in Xraw.keys():
    if key[1] > SNR_thres:
        Xd[key] = Xraw[key]

중복된 것은 제외하고 어떤 modulation type과 SNR이 있는지는 다음과 같이 확인할 수 있다. 앞선 설명으로부터 알 수 있듯이 key[0]는 'modulation type'을, key[1]은 'SNR' 값을 의미한다.

mod, snr = [], []
for key in Xd.keys():
    mod.append(key[0])
    snr.append(key[1])

mod_list = list(dict.fromkeys(mod))
snr_list = list(dict.fromkeys(snr))

앞서 (1000, 2, 128) 형태의 행렬에서 1000은 key 하나당 이루어진 data의 개수를 의미하는 것으로 보인다. 따라서 본격적인 dataset은 아래의 과정으로 만들어진다.

multi_labels, snr = [], []

data_list = list()

# Xd의 key는 ('modulation type', 'SNR')의 형태로 구성되어 있다. 
for key in Xd.keys():
    # Xd[key]의 length는 1000이다. 
    for i in range(len(Xd[key])):
        # (2, 128) 에서 (1, 128, 2)의 모양으로 바뀌어 append 된다. 
        data_list.append(np.expand_dims(Xd[key][i,:].T,0))
        
        # modulation type이 1000번 반복되어 추가되는 과정으로 이해하면 된다.
        # e.g.) 'PAM4' 등의 이름이 (1000번) 반복해서 추가됨. 
        multi_labels.append(key[0])
        # SNR도 마찬가지
        snr.append(key[1])

이제 각 변수의 크기를 알아보자. Xd의 원소 개수를 kk 개 라고 두면 다음과 같다.

data_array = np.asarray(np.vstack(data_list))
# data_array의 shape   -> ( k * 1000, 128, 2 )
# 이는 CNN에 들어갈 예정

multi_labels = np.asarray(multi_labels).reshape(1, -1).T
# multi_labels의 shape -> ( k * 1000, 1 )

하지만 modulation type의 이름을 그대로 dataset으로 사용할 수는 없다. 따라서 sklearn package로부터 OneHotEncoder를 import 하여 아래와 같이 one hot vector로 변환해준다. 쉽게 생각하면 각 modulation type에 따라, 1 값을 갖는 단 하나의 원소를 제외한 모든 원소의 값이 0인 vector를 매칭시켜주는 것이다. e.g.) [0 0 0 1 0 0 0 0]

encode = OneHotEncoder()
ydata_binary = encode.fit_transform(multi_labels).toarray()
# ydata_binary의 shape -> ( k * 1000, modulation type의 개수 )

마지막으로 sklearn에서 train_test_split을 import 한 뒤, train/test set을 나누어주면 dataset이 완성된다.


# random_state는 재현 가능하도록 난수의 초기 값을 설정해주는 과정이다. 
# Reference code에는 다음과 같이 적혀있다. 
'''
This should work because I initialize the random state, 
so it will always come up with the same indexes
'''
xTrain, xTest, yTrain, yTest = \
train_test_split(data_array, ydata_binary, test_size = 0.3, random_state = 0)
# x(Train/Test)의 shape -> ( split_ratio * k * 1000, 128, 2 )
# y(Train/Test)의 shape -> ( split_ratio * k * 1000, 11 )

snr_train, snr_test = train_test_split(np.asarray(snr), test_size = 0.3, random_state = 0) 
# snr_(train/test)의 shape -> ( split_ratio * k * 1000, )

Reference

O’Shea, Timothy James, Tamoghna Roy, and T. Charles Clancy. "Over-the-air deep learning based radio signal classification." IEEE Journal of Selected Topics in Signal Processing 12.1 (2018): 168-179.

0개의 댓글