스마트폰 앱에서 글씨를 캡쳐하면 실제 데이터로 변형되어 출력되는 것을 본적이 있을 것이다.
오늘은 이런 것이 어떻게 가능한 것인지 알아보고, 실습하는데 목적을 둔다.
*추후 모델과 기초 개념에 대해서도 다룰 예정이다.
0. 목표
우리는 위 숫자가 '8'이란 것을 안다. 어떻게 알게 되었는가?
어렸을 때 우린 1부터 9까지 숫자들의 모양을 학습하였고,
위 이미지와 우리가 알고있는 모양인 숫자 8과 일치시켜 답을 내놓을 수 있었을 것이다.
컴퓨터로는 어떻게 위 이미지를 인식하여 데이터를 처리할 수 있을까?
우리는 해당 포스팅을 통해 '손글씨 숫자 이미지를 인식하여 예측'해보는 모델을 만들어보고자 한다.
1부터 9까지의 데이터를 학습하여 손글씨 이미지를 예측할 수 있는 '모델'을 만들고
임의의 손글씨 이미지를 만들어 이 그림이 어떠한 숫자를 의미하는지 예측해 볼 것이다.
*참고로 이미지 데이터를 처리할때 높은 정확도를 도출하기 위해 CNN 방식을 많이 사용하지만 본 포스팅에선 CNN을 중점적으로 다루지 않는다. 추후 다룰 예정이다.
1. MNIST 데이터 셋
이번 포스팅에서도 다시 얘기하지만 Deep Learning의 핵심은 '좋은 데이터'이다.
이번은 AI에서 대표적인 손글씨 데이터 셋인 MNIST 데이터 셋에 대해 이야기해보겠다.
MNIST는 다음과 같은 특징을 갖는다.
대표 데이터 셋인 만큼 import하여 손쉽게 데이터를 불러올 수 있다.
60,000개의 학습 데이터, 10,000개의 테스트 데이터로 나뉘어 있다.
각 이미지는 28x28 크기로 784개의 픽셀을 가진다.
(1) 데이터를 불러오는 방법은 다음과 같다.
from keras.datasets import mnist
(2) 데이터 갯수는 다음과 같다.
(X_train, Y_class_train), (X_test, Y_class_test) = mnist.load_data()
print("학습셋 이미지 수 : %d 개" % (X_train.shape[0]))
print("테스트셋 이미지 수 : %d 개" % (X_test.shape[0]))
첫번째 라인에서 X_train에는 학습할 60,000개의 각 784개 픽셀 정보가 배열 형태로 저장되게 되고
Y_class_train에는 60,000개 이미지 정답인 클래스 정보가 담기게 된다.
X_test와 Y_class_test는 테스트 데이터로 10,000개이고 형태는 위 학습데이터와 같다.
(3) 다음은 데이터 형태이다(X_train 데이터의 첫번째 원소).
# 이미지 형태 출력을 위한 pyplot 모듈 import
import matplotlib.pyplot as plt
# 위 60000개 데이터 배열에서 0번째 원소를 흑백으로 출력
plt.imshow(X_train[0], cmap='Greys')
plt.show()
################################
# 0번째 원소의 모든 데이터를 출력
for x in X_train[0]:
for i in x:
sys.stdout.write('%d ' % i)
sys.stdout.write('\n')
위 그림을 보면 가로 28개 픽셀, 세로 28개 필셀의 집합으로 하나의 그림이 만들어 진 것을 볼 수 있다.
흰색은 0, 검정색은 255 이렇게 각 픽셀의 색상을 데이터화하여 X_train의 원소에 담겨져 있는 것이다.
2. Training
# 딥러닝에 필요한 케라스 함수 호출
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers import Dense
# 필요 라이브러리 호출
import numpy
import tensorflow as tf
# 데이터 셋 호출
from keras.datasets import mnist
# 실행 시마다 같은 결과값 도출을 위한 시드 설정
numpy.random.seed(0)
tf.random.set_seed(0)
# 데이터를 불러와서 각 변수에 저장
(X_train, Y_train), (X_test, Y_test) = mnist.load_data()
# 학습에 적합한 형태로 데이터 가공
X_train = X_train.reshape(X_train.shape[0], 784).astype('float32') / 255
X_test = X_test.reshape(X_test.shape[0], 784).astype('float32') / 255
# 클래스를 학습에 이용하기 위해 데이터 가공
Y_train = np_utils.to_categorical(Y_train, 10)
Y_test = np_utils.to_categorical(Y_test, 10)
# 딥러닝 모델 구조 설정(2개층, 512개의 뉴런 연결, 10개 클래스 출력 뉴런, 784개 픽셀 input 값, relu와 softmax 활성화 함수 이용)
model = Sequential()
model.add(Dense(512, input_dim=784, activation='relu'))
model.add(Dense(10, activation='softmax'))
# 딥러닝 구조 설정(loss 옵션을 다중 클래스에 적합한 categorical_crossentropy, 옵티마이저는 adam 설정)
model.compile(loss='categorical_crossentropy', optimizer='adam',
metrics=['accuracy'])
# 모델 실행(X_test, Y_test로 검증, 200개씩 30번 학습)
model.fit(X_train, Y_train, validation_data=(X_test, Y_test), epochs=30, batch_size=200, verbose=2)
# 학습 정확도, 검증 정확도 출력
print('\nAccuracy: {:.4f}'.format(model.evaluate(X_train, Y_train)[1]))
print('\nVal_Accuracy: {:.4f}'.format(model.evaluate(X_test, Y_test)[1]))
# 모델 저장
model.save('Predict_Model.h5')
학습을 위해 각 데이터는 0-255에서 0-1사이의 숫자로 변환해야한다. 그래서 위 연산을 수행한다.
위 함수를 아래 3줄로 풀어 출력하면 다음과 같다. 천천히 이해를 해보기 바란다.
np_utils.to_categorical(Y_train, 10)
해당 클래스 정보도 0-10 값을 0, 1, 2, 3~~ 이렇게 저장할 수 있지만 앞서 말했듯이 0, 1값으로 저장해야 원할한 데이터 처리가 가능하다.
5를 표현하기 위해 클래스 갯수를 가지는 리스트를 만들어 5번째 원소에만 1을 주는 방식으로 one-hot vector화하는 함수다.
이후 학습을 진행한다.
CNN을 사용하지 않았지만 높은 정확도를 보였다. 검증 정확도가 98%이다.
해당 모델은 다음과 같이 잘 저장되었다.
3. Testing
테스트를 위해 이미지를 하나 만들어보았다.
픽셀을 28x28로 만들고(어차피 resize해서 필요없긴하나 편의를 위해), 붓으로 숫자 8을 그려보았다.
이제 jupyter notebook에 업로드하고 다음과 같이 코딩한다.
# 딥러닝에 필요한 케라스 함수 호출
from keras.models import load_model
from keras.utils import np_utils
# 필요 라이브러리 호출(PIL은 이미지파일 처리위함)
from PIL import Image
import numpy as np
# test.png는 그림판에서 붓으로 숫자 8을 그린 이미지 파일
# test.png 파일 열어서 L(256단계 흑백이미지)로 변환
img = Image.open("test.png").convert("L")
# 이미지를 784개 흑백 픽셀로 사이즈 변환
img = np.resize(img, (1, 784))
# 데이터를 모델에 적용할 수 있도록 가공
test_data = ((np.array(img) / 255) - 1) * -1
# 모델 불러오기
model = load_model('Predict_Model.h5')
# 클래스 예측 함수에 가공된 테스트 데이터 넣어 결과 도출
res = model.predict_classes(test_data)
# 2021/10/02 수정 - 오류시 아래 명령어로 대체 가능합니다.
# res =(model.predict(test_data) > 0.5).astype("int32")
print(res)
출력 결과는 다음과 같다.
정확히 예측에 성공하였다.
학습 데이터가 좋아 쉽게 성공하였는데 이후에 CNN을 이용하여 실습해 보겠다.
4. 블랙박스 부수기
현재 우리는 블랙박스를 부수지 못하고있다.
인식, 예측엔 성공했지만 '어떻게' 이것이 가능한지는 원리적으로 알지 못한다.
아직 model.add(Dense(~~)) 부분도 상세히 설명하지 않았다.
해당 포스팅은 딥러닝이란 분야에 흥미를 불러일으키기 위한 목적을 가진다.
이후 글부터 '어떻게'에 초점을 맞추어 활성화 함수부터 모델에 대한 전반적인 개념에 대한 포스팅을 진행할 예정이다.
그리고 다시 해당 포스팅으로 돌아오면, 안보이던 코드(블랙 박스)가 하나하나 보이기 시작할 것이다.
* ThoraricSurgery.csv : 구글에 치면 다운받을 수 있는 깃허브 주소가 나온다.
[ThoraricSurgery.csv] 데이터의 구조를 살펴보자.
데이터에는470명의 환자 정보가 들어있다.
각 '속성' 정보는 [종양 유형, 폐활량, 기침, 흡연 여부 등] 각각의 17개의 환자의 정보가 담겨있다.
'클래스'에는 [수술 후 생존, 사망 여부] 정보가 담겨 있다.
[test.csv] 데이터 구조도 살펴보자.
글쓴이가 만든 가상의 환자 정보이다.
각 '속성' 정보는 [종양 유형, 폐활량, 기침, 흡연 여부 등] 위와 같이 17개의 환자의 정보가 담겨있다.
'클래스'인 [수술 후 생존, 사망 여부] 정보는 우리가 모델을 통해 예측해볼 것이다!
2. Training
# 딥러닝에 필요한 케라스 함수 호출
from keras.models import Sequential, load_model
from keras.layers import Dense
# 필요 라이브러리 호출
import numpy as np
import tensorflow as tf
# 실행 시마다 같은 결과값 도출을 위한 시드 설정
np.random.seed(0)
tf.random.set_seed(0)
# csv 파일을 읽어 ','기준으로 나눠 Dataset에 불러오기
Dataset = np.loadtxt("ThoraricSurgery.csv", delimiter=",")
# 환자 정보는 0-16번(17개)까지이므로 해당 부분까지 X에 담기
X = Dataset[:, 0:17]
# 수술 후 결과는 마지막 17번은 클래스로 Y에 담기
Y = Dataset[:, 17]
# 딥러닝 모델 구조 설정(3개층, 속성이 17개 input 값, relu와 sigmoid 활성화 함수 이용)
model = Sequential()
model.add(Dense(30, input_dim=17, activation='relu'))
model.add(Dense(15, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
# 딥러닝 실행(오차함수는 평균제곱오차, 최적화함수는 adam 이용)
model.compile(loss='mean_squared_error', optimizer='adam', metrics=['accuracy'])
model.fit(X, Y, epochs=30, batch_size=5)
# 만들어진 모델 저장
model.save('Predict_Model.h5')
중간에 Dataset을 출력한 사진이다.
다음과 같이 470명 환자의 17가지 정보가 들어간 이중 리스트가 만들어졌다.
중간 X와 Y를 출력한 사진이다.
X엔 환자 정보가, Y엔 마지막 컬럼인 생존여부부분이 들어간 것을 볼 수 있다.
모델의 정확도이다.
5개 단위(batch-size)로 30번(epochs) 학습시켜 85.74% 정확도를 가진 모델을 만들었다.
그리고 다음과 같이 잘 저장되었다.
3. Testing
# 딥러닝에 필요한 케라스 함수 호출
from keras.models import load_model
from keras.utils import np_utils
# 필요 라이브러리 호출
import numpy as np
# csv 파일을 읽어 ','기준으로 나눠 Dataset에 불러오기
Dataset = np.loadtxt("test.csv", delimiter=",")
# 환자 정보는 0-16번(17개)까지이므로 해당 부분까지 X에 담기
X = Dataset[:, 0:17]
# 수술 후 결과 정보인 예측값 변수 초기화
Y = []
# 모델 불러오기
model = load_model('Predict_Model.h5')
# X 값에 대한 predict_classes 함수 실행결과 Y에 저장
Y = model.predict_classes(X)
print('Predict : ', Y)
중간 Dataset 변수를 출력한 사진이다.
3명의 가상 환자 정보가 잘 들어간 것을 알 수 있다.
마지막 예측 결과 사진이다.
3명의 가상 환자에 대해 다음과 같은 결과를 도출했다.
1번 가상 환자 : 수술 후 사망 예상
2번 가상 환자 : 수술 후 사망 예상
3번 가상 환자 : 수술 후 생존 예상
이라는 결과를 내놓았다.
4. 신뢰성
여기서 한가지 밝힐 사실이 있다.
이 가상 환자 세명은 사실 [ThoraricSurgery.csv] 데이터의 환자의 정보를 참고하여 만들었다.
네트워크 패킷을 캡쳐하게 되면 호스트에선 Ethernet Header의 MAC Frame 부분부터 잡기 시작한다.
아래와 같이 첫 6바이트는 Destination의 MAC 주소를 가리키고, 다음 6바이트는 Source의 MAC 주소를 가리킨다.
다음은 Destination MAC주소와 Source MAC 주소를 출력하는 코드의 예문이다.
import pcap #pypcap 라이브러리
sniffer = pcap.pcap(name=None, promisc=True, immediate=True, timeout_ms=50)
for ts, pkt in sniffer:
print('Dst MAC - ', end='', flush=True)
print(':'.join('%02X' % i for i in pkt[0:6])) #패킷의 0번부터 5번 바이트까지 출력
print('Src MAC - ', end='', flush=True)
print(':'.join('%02X' % i for i in pkt[6:12])) #패킷의 6번부터 11번 바이트까지 출력
print()
sniffer 객체의 첫번째 값은 timestamp, 두번째 값은 packet으로 반복문을 돌겠다는 의미이다.
이후 pkt[idx] 형식으로 네트워크 패킷의 특정 위치 인덱스 값을 가져올 수 있다.
4. 응용
pypcap은 dpkt나 socket 모듈 등과 함께 편리하게 사용 가능하다.
다음은 dpkt 모듈을 이용하여 Ethernet의 정보를 파싱하여 출력하는 예문이다.
import pcap
import dpkt
sniffer = pcap.pcap(name='eth0', promisc=True, immediate=True, timeout_ms=50)
sniffer.setfilter('tcp and port 443')
def mac_addr(address):
return ':'.join('%02X' % dpkt.compat.compat_ord(b) for b in address)
for ts, pkt in sniffer:
eth = dpkt.ethernet.Ethernet(pkt)
#ip = eth.data
#tcp = ip.data
print('Ethernet INFO - ', mac_addr(eth.dst), mac_addr(eth.src), eth.type)
eth0 디바이스를 이용하여 캡쳐를 진행하고,
setfilter() 함수를 통해 tcp 패킷과 443번 포트를 필터링
이후 dpkt의 Ethernet 클래스로 ethernet 부분을 파싱
해당 함수의 변수를 사용해 원하는 값을 출력
*eth.data, ip.data 등을 이용해 IP와 TCP Header에 대한 파싱도 손쉽게 가능하다.
alert tcp any any -> any 80 (msg:"80site.com Access"; content:"GET /"; content:"Host: "; content:"80site.com"; sid:10001; rev:1;)
alert tcp any any -> any 443 (msg:"443site.com Access"; flow:to_server,established; tls_sni; content:"443site.com"; sid:10002; rev:1;)
4. Suricata - 실행
rules 파일까지 작성이 끝났다.
이제 Suricata를 실행시켜보자.
1. suricata -s suri.rules -i eth0 명령어로 인터페이스와 파일을 인자로 넣어 실행한다.
2. 해당 사이트에 접속한다(본인은 80포트는 교수님 사이트, 443포트는 얼굴책 사이트를 지정하였다)
3. 로그파일을 확인한다. > /var/log/suricata/fast.log에 룰 정책에 의한 접속 로그들이 저장된다.