이 포스트는 [인공지능 보안을 배우다] 책을 참고해 학습한 내용을 정리한 글입니다.
1. 첫번째 코드
첫번째 코드는 필요한 라이브러리와 모듈을 임포트하고, 결과를 출력할 데이터 프레임을 생성하고 특징 데이터를 로드 한다.
1.1. 필요 라이브러리와 모듈을 임포트
import numpy as np : 넘파이를 임포트 한다.
넘파이란 수치계산을 위한 라이브러리로서, 다차원 배열 객체를 생성하고 계산 할 수 있다.
import pandas as pd : 판다스 라이브러리를 임포트한다.
판다스는 데이터 분석을 위한 라이브러리로 데이터를 표로 시각화 한다.
import seaborn as sns : 시본 라이브러리를 임포트한다.
시본은 데이터를 시각화 하는 라이브러리로, 그래프를 쉽게 생성할 수 있도록 한다.
import matplotlib.pyplot as plt : 맷플로립 라이브러리를 임포트 한다.
맷플로립은 그래프를 그리는 라이브러리로 막대그래프, 선형그래프, 산점도등의 다양한 그래프를 그릴 수 있게 한다.
import operator : 파이썬 표준 라이브러리인 operator 를 임포트한다.
산술 연산, 비교 연산, 논리 연산 등과 같은 기본 연산을 함수 형태로 제공한다.
import model : 실습자료인 model.py를 임포트 한다.
import cnn_model : 실습자료인 cmm_model.py를 임포트한다.
1.2. 결과 저장을 위한 DataFrame 생성 :
cols = ["svm", "randomforest", "naivebayes", "dnn"]
cols(column=열) 의 이름을 SVM, 랜덤 포레스트, 나이브 베이즈, DNN으로 정한다. 이는 각 모델의 성능 결과를 저장하기 위함이다.
df = pd.DataFrame(columns=cols)
판다스 라이브러리를 사용하여 데이터프레임을 생성한다. 칼럼의 이름은 방금 정의했던 cols에서 가져오고, 생성한 데이터 프레임의 이름은 "df"로 지정한다.
1.3. 특징 데이터 로드
pe 특징 데이터 로드
pe_nor = pd.read_csv('normal_pe.csv')
noraml_pe.csv파일을 판다스로 읽어와 pe_nor라는 데이터프레임에 저장하며, 해당 데이터프레임(변수) 의 이름은 pe_nor
pe_mal = pd.read_csv('malware_pe.csv')
malware_pe.csv파일을 판다스로 읽어와 pe_mal이라는 데이터프레임에 저장하며, 해당 데이터프레임(변수) 의 이름은 pe_mal로 지정한다.
pe_all = pd.concat([pe_nor, pe_mal])
pe_nor, pe_mal 데이터를 합친다. 기본적으로 가로로 합쳐지며 합친 데이터프레임의 이름은 pe_all로 정한다.
ngram 특징 데이터 로드
gram_all = pd.read_csv('ngram.csv')
판다스로 ngram.csv를 읽어와 gram_all에 저장한다.
데이터프레임의 크기를 출력
print(pe_all.shape, gram_all.shape)
pe_all과 gram_all의 크기를 출력한다.
결과
2. 두번째 코드
print ("[*] Before Filtering NA values: ", pe_all.shape)
- pe_all의 행과 열의 개수를 반환하여 출력한다.
NA_values = pe_all.isnull().values.sum()
- pe_all의 결측 여부 (true/false)를 판단하고 이 값 들 을 합한다. 즉 true =1 false=0 값을 모두 합하면 null값을 가진 개수를 파악할 수 있다.
print ("[*] Missing Values: ", NA_values)
- 결측 치의 총 개수를 출력한다.
pe_all = pe_all.dropna()
- dropna() : 결측 치가 포함된 행을 제거한다.
print ("[*] After Filtering NA values: ", pe_all.shape)
- 결측 치를 제거한 후 다시 행과 열의 개수를 반환하여 출력한다.
결과
null 값이 750개가되며 이 값이 포함된 행을 모두 삭제했을 시 데이터 레코드(행)의 개수는 63개가 줄어들었다.
3. 세번째 코드
세번째 코드는 범주형 데이터를 처리하는 첫번째 방법에 대한 코드이다.
범주형데이터란 수치가 아니며, 우열을 가릴 수 없는 목록인 데이터를 뜻한다. (예시 : 배/바나나/사과)
아래 코드는 범주형 데이터를 그냥 삭제 처리 한다.
pe_all_tmp = pe_all
- pe_all 을 pe_all_tmp로 백업한다.
pe_all = pe_all.drop(['filename', 'MD5', 'packer_type'], axis=1)
- pe_all의 filename, MD5, packer_type 열을 삭제한다.
Y = pe_all['class']
- pe_all의 class 열을 데이터프레임 Y에 저장한다. 이 class열은 악성코드 여부를 판단하는 수치이다. (0이면 정상프로그램)
X = pe_all.drop('class', axis=1)
- class 열을 제거한 pe_all을 X에 저장한다.
Y_bak = Y
- Y를 Y_bak에 백업한다.
md_pe = model.Classifiers(X, Y)
- model.py의 클래스 Classifiers에 X, Y를 전달하여 인스턴스를 초기화 한다. 이름은 md_pe로 지정한다.
- classifier는 x,y를 사용해 "svm", "randomforest", "naivebayes", "dnn" 네가지 모델을 실행하고 그 결과로 정확도를 계산한다.
df.loc['pe'] = md_pe.do_all() # 분류 모델 학습
- md_pe 인스턴스의 do_all()메소드를 호출하여 수행(모델을 학습)하고 그 결과를 df 데이터 프레임의 pe 행에 저장한다.
- 'loc'는 해당되는 열을 가져오거나 없을 경우 생성한다.
print(X.shape, Y.shape)
- x와 y의 크기를 출력한다.
결과
epoch, cost 값이 출력되고있다.
Epoch는 '에포크' 라고 읽으며 딥러닝 학습 과정에서 전체 학습 데이터를 한 번 모두 사용해 모델이 학습하는 과정을 한 번의 에포크라고 부른다.
cost는 손실 값으로 모델의 현재 예측이 정답과 얼마나 차이 나는지 나타낸다. 값이 낮을 수록 차이가 적은 것이다
결과는 매 에포크마다 모델의 손실 값이 어떻게 바뀌는지 표현하고있다. 즉 15번의 에포크를 진행하고 있으며 학습을 할수록 손실 값이 줄어들고 있어 잘 학습 중 이라는 것을 알 수 있다.
4. 네번째 코드
네번째 코드는 이전코드에서 범주형 데이터를 삭제했던 것과 달리 원-핫 인코딩 기법을 사용하여 범주형 데이터를 수치화 하여 모델학습에 적합한 형태로 변환 한다
4.1. 라이브러리 임포트
from sklearn.preprocessing import OneHotEncoder
- OneHotEncoder는 범주형 데이터를 원-핫 이진 벡터로 변환하는 도구이다
from sklearn.preprocessing import LabelEncoder
- 범주형 데이터를 정수형 레이블로 변환하는 도구이다.
4.2. 메소드 정의
def hot_encoding(df):
- 입력데이터프레임 df 를 매개변수로 받는 함수 hot_encoding을 정의한다.
enc = OneHotEncoder(handle_unknown='ignore', sparse_output=False)
- enc라는 이름의 OneHotEncoder 인스턴스를 생성한다. 이때 설정은 다음과 같다.
- handle_unknown='ignore'은 데이터에 없는 새로운 클래스가 들어오더라도 오류를 무시한다.
- sparse_output=False 는 결과를 밀집 행렬(dense matrix)로 반환한다.
lab = LabelEncoder()
- lab이라는 이름의 LabelEncoder 인스턴스를 생성한다.
dat = df['packer_type']
- df 데이터프레임의 packer-type 열을 추출하여 dat 에 저장한다
lab.fit(dat)
- packer-type 열을 추출한 dat 데이터를 lab인스턴스로 학습한다.
- 예) packer_type 열 값이 ['a', 'b', 'c']라면, 각각 0, 1, 2로 매핑한다.
- 이때 lab.classes_속성에 packer_type의 원본데이터와 숫자 매핑 정보를 저장한다.
lab_dat = lab.transform(dat)
- 학습한 매핑을 사용하여 paker_type 데이터를 정수로 변환. lab_dat에 저장한다.
df = df.drop('packer_type', axis=1)
- 기존 데이터프레임의 packer_type 데이터를 삭제한다. 이 데이터 프레임 df는 이전에 정의했던 df와 다른 것이다. 메소드 내에서 정의한 지역변수 같은 것.
lab_dat = lab_dat.reshape(len(lab_dat), 1)
- reshape는 배열의 차원을 바꾼다. lab_date 배열을 1열짜리 2차원 배열로 변환한다.
enc_dat = enc.fit_transform(lab_dat)
- 숫자데이터를 원-핫 벡터 형태로 변환한다.
- 예 [ 0, 1, 2] 이라면
- [[1, 0, 0],
- [0, 1, 0],
- [0, 0, 1]]
enc_dat = pd.DataFrame(enc_dat, columns=lab.classes_)
- 결과를 데이터 프레임으로 변환하여 열 이름은 기존의 열이름으로 지정한다.
df = df.reset_index(drop=True)
enc_dat = enc_dat.reset_index(drop=True)
- df와 enc_dat 데이터프레임의 인덱스를 초기화 한다. 기존의 인덱스를 삭제하고 새로운 연속적인 인덱스를 생성한다. 아래 이미지의 행 번호가 인덱스의 예시이다
df = pd.concat([df, enc_dat], axis=1)
- 원본데이터 프레임과 원-핫 인코딩 된 데이터를 열 단위로 결합한다.(가로로 붙인다.)
return df, lab.classes_
- 결합한 데이터 프레임과 lab.classes_ (=paker_type 열 이름-정수 매핑 정보)를 반환한다.
결과값
print 구문이 없기때문에 출력되는 것은 없다.
df 데이터프레임의 packer_type열이 원 핫 인코딩 방법으로 범주형 데이터에서 정수형 데이터로 변환 되도록 메소드 "hot_encoding"을 정의했다.
5. 다섯 번째 코드
이제 pe_all을 원 - 핫 인코딩하여 모델링하는 과정이다.
pe_all = pe_all_tmp
- 이전에 백업 해 둔 pe_all_tmp를 pe_all로 가져온다.
pe_all = pe_all.drop(['filename', 'MD5'], axis=1) # 파일이름, MD5 열 제거
- 파일이름과 MD5는 학습에 영향을 미치지 않으므로 삭제한다.
pe_all, classes_ = hot_encoding(pe_all) # One-Hot 인코딩 변환
- hot_encoding 메소드를 호출하여 pe_all 원핫 인코딩 변환 후 반환
print ("Found %d Categories in packer-type" % len(classes_))
- packer_type의 카테고리 개수를 출력한다.
# dataset for modeling
pe_all = pd.DataFrame(pe_all)
- pe_all데이터를 데이터프레임 형식으로 변환 (안전하게 재 포맷)
pe_all.to_csv('pe_packer.csv', index=False)
- pe_packer.csv라는 이름의 파일로 pe_all 데이터를 저장한다.
Y = pe_all['class'] # 카테고리 열을 별도로 추출
- class열을 별도로 추출 (악성코드인지 판별하는 수치) 하여 Y에 저장
X = pe_all.drop('class', axis=1)
- class열 만을 삭제해서 X에 저장
md_pe_packer = model.Classifiers(X, Y)
- 학습 모듈 인스턴스를 초기화 한다. 인스턴스의 이름은 md_pe_packer이다.
df.loc['pe_packer'] = md_pe_packer.do_all() # 분류 모델 학습
- 분류 모델 학습을 실행한다. 실행결과를 df프레임의 pe_packer 행에 저장한다.
print (X.shape, Y.shape)
- X와 Y의 크기를 출력한다.
결과
이전에 수행했던 pe_packer 열을 삭제한 학습 버전에 비교하여,
원 핫 인코딩 처리를 했기 때문에 pe_all의 열의 개수는 증가했고, 최종 cost는 약간 감소한 것을 알 수 있다.
즉 원 핫 인코딩 처리를 한 것이 오차가 더 줄어들어 학습이 상대적으로 잘 되었다고 할 수 있다.
6. 여섯 번째 코드
여섯 번째 코드는 ngram.csv 특징 데이터를 사용하여 모델을 학습하기위한 준비를 하는 과정이다.
gram_all = gram_all.drop(['filename', 'MD5'], axis=1) # 파일이름, MD5 열 제거
- 첫번째 코드에서 ngram.csv 를 가져와 gram_all 데이터프레임에 저장했었다. 이 데이터 중 이름이 'filename', 'MD5'인 열을 삭제한다.
# dataset for modeling
# gram_all.to_csv('../3-modeling/ngram.csv', index=False)
Y = gram_all['class'] # 카테고리 열을 별도로 추출
- gram_all에서 class 열을 추출하여 Y에 저장한다.
X = gram_all.drop('class', axis=1) # 카테고리 열 제거
- class열만 제거한 데이터를 X에 저장한다.
7. 일곱 번째 코드
md_gram = model.Classifiers(X, Y) # 학습 모듈 인스턴스 초기화
- 모델 학습 인스턴스를 생성하여 이름은 md-gram으로 한다.
df.loc['ngram'] = md_gram.do_all()# 분류 모델 학습
- ngram 분류 모델 학습을 진행한다. 그리고 각 모델의 정확도를 계산하여 df 데이터프레임의 ngram 행에 저장한다.
df.loc['image'] = [0,0,0,0]
- df 데이터 프레임에 'image' 행을 추가하고, [0,0,0,0]으로 초기화 한다.
-
print (X.shape, Y.shape)
- X와 Y 크기를 출력한다.
결과
총 15번의 에포크 과정과 손실 값을 표시한다. 손실 값은 pe 헤더 특징데이터 보다 약간 더 크다.
8. 여덟 번째 코드
여덟 번째 코드는 cnn_model.py를 사용하여 image 특징데이터를 분류 학습하는 과정이다.
cn = cnn_model.CNN_tensor()
cnn_model.py의 CNN_ten
sor 클래스의 인스턴스를 생성하여 cn으로 명명한다.
cn.load_images()
- load_images() 메소드를 호출하여 이미지를 불러온다.
cnn_acc = cn.do_cnn()
- cnn모델을 학습하고 테스트 데이터를 평가한다.
결과
총 30번의 학습을 진행하였다.
9. 아홉 번째코드
모델학습결과의 평균을 계산 후 출력하는 코드이다.
avg_pe = df.loc['pe'].mean(axis=0)
- df데이터 프레임의 pe 행을 대상으로 평균을 계산하여 avg_pe에 저장한다.
- mean은 평균을 계산하는 판다스의 함수이다.
- axis=0은 열을 기준으로 평균을 계산하라는 의미이다.
avg_pe_packer = df.loc['pe_packer'].mean(axis=0)
- pe_packer행을 대상으로 평균을 계산하여 avg_pe_packer에 저장한다.
avg_ngram = df.loc['ngram'].mean(axis=0)
- ngram행을 대상으로 평균을 계산하여 avg_ngram에 저장한다.
df['cnn'] = [0,0,0,cnn_acc]
- cnn열의 값을 [0,0,0,cnn_acc]로 지정한다. 즉 pe=0 pe_packer=0 ngram=0 image=cnn_acc
df['avg'] = [avg_pe, avg_pe_packer, avg_ngram, cnn_acc]
- avg열에 평균을 계산한 값들을 넣는다.
df
- df 데이터프레임을 출력한다.
결과
1에 근접할 수록 성능이 좋다는 의미이며
pe- packer_type을 삭제한 경우는 랜덤포레스트를 사용했을 경우 가장 성능이 좋았다.
pe - 원 핫 인코딩을 사용한 경우에도 마찬가지로 랜덤포레스트가 가장 성능이 좋았다.
둘의 결과는 유사했으나 packer_type을 삭제한 경우가 오히려 정확도가 높았다. - 책의 결과와 반대로 나왔다.
ngram의 경우 대체적으로 성능이 고른 편이고 평균을 냈을 때 두번째로 높았다.
이미지의 경우 cnn모델 학습만 진행하였고 성능은 준수한 편이다.