본 포스팅은 아래 선행 작업들이 있다.

- OpenCV 4.2 가 설치되 있고, 아래 명령어를 사용 가능한 상태여야한다.

     - opencv_createsamples

     - opencv_traincascade

- 학습 데이터가 수집 되어 있는 상태이다.

 

진행 순서

1. opencv_createsamples 실행 파일을 이용해 학습 데이터 생성하기 (out : .vec 파일)

2. opencv_traincascade 실행 파일을 이용해 검출기 생성하기 (out : .xml 파일)

3. .xml 파일을 이용해 객체 검출 해보기

 

1. opencv_createsamples 실행 파일을 이용해 학습 데이터 생성하기

파라미터 설명

opencv_createsamples -info <description_file_name> -vec <vec_file_name>

<description_file_name> : 검출을 원하는 이미지 데이터들이 기록되어있는 텍스트 file 이름 (positive 샘플이라한다.)

 

<vec> : 생성된 학습 데이터를 저장할 파일명 (확장자 : vec)

 

<background_file_name> : 검출할 이미지가 없는 이미지들이 기록되어있는 텍스트 file 이름 (negative 샘플이라한다.)

 

명령어 입력 예시

opencv_createsamples -info positive_list.txt -vec tr.vec -bg negative_list.txt

 

positive_list.txt 파일 구성은 아래와 같이 구성해야한다.

c:\positives\img1.jpg 1 140 100 45 45
c:\positives\img2.jpg 2 100 20 50 50 0 30 25 25
c:\positives\img3.jpg 1 0 0 20 20
....

첫번째 인자 : 이미지 파일 경로

두번째 인자 : 이미지에 구성되어있는 positive 샘플의 갯수

세번째 인자 : x, y, width, height  

positive 샘플의 갯수가 2개 이상이면 세번째 인자를 주르륵 이어서 쓰면됨

 

 

 

2. opencv_traincascade 실행 파일을 이용해 학습 모델 생성하기

opencv_traincascade -data <cascade_dir_name> -vec <vec_file_name> -bg <background_file_name> [-numStages <number_of_stages = 20>] [-featureType <HAAR(default), LBP, HOG] [-baseFormatSave]

파라미터 설명 

<cascade_dir_name> : 최종 검출기가 저장될 파일 이름 (최종 출력 확장자 .xml)

(일례 '-data result' 로 실행 시 result라는 폴더가 생성되고 result 밑에 cascade classifier들이 저장됨. 또한 현재 실행폴더에 result.xml이라는 이름으로 학습된 최종 검출기가 저장됨.) 

 

<vec_file_name> : 앞서 생성 했던 학습 데이터 파일명 (확장자 : vec)

 

<background_file_name> : 검출할 이미지가 없는 이미지들이 기록되어있는 텍스트 file 이름 (negative 샘플이라한다.)

 

[<HARR, LBP, HOG> (default : HARR)] : 검출 모드 설정(옵션)

 

[<number_of_stages> (default : 20)] : 학습할 cascade 단계의 수. Cascade 검출기는 일련의 기본 검출기들로 구성되는데, Cascade 검출 방식은 먼저 1단계 classifier로 false들을 걸러내고, 나머지에 대해서는 2단계 classifier로 false들을 걸러내고, ... 이런식으로 해서 최종 단계까지 살아남으면 물체 검출에 성공한 것으로 간주함. -nstages는 이 단계수 즉, 기본 검출기들의 개수를 조절하는 것임. 학습된 각 단계별 기본 검출기들은 result\ 밑에 0, 1, 2, ... 밑에 텍스트 파일 형태로 저장됨. (옵션)

 

[-baseFormatSave (default : false)] : -featureType이 HAAR일 경우에만 의미를 가지는 파라미터임. 이 파라미터를 명시해 주면 훈련 결과를 예전의 Haar training 방식의 데이터 포맷으로 저장해줌. (옵션)

 

명령어 입력 예시

opencv_traincascade -data result -vec tr.vec -bg negative_list.txt -numPos 400 -numNeg 5000 -featureType HAAR -numState 14 -baseFormatSave

 

 

negative_list.txt 파일 구성은 아래와 같이 구성해야한다.

c:\negatives\img1.jpg
c:\negatives\img2.jpg

첫번째 인자 : 이미지 파일 경로

 

-npos: 각 cascade 학습 단계(stage)에 사용되는 positive 샘플 개수를 설정. 주의할 점은 .vec 파일에 있는 실제 샘플수를 입력하면 안됨.  npos <= (vec파일에 있는 샘플수 - 100)/(1+(nstages-1)*(1-minhitrate))) 정도로 값을 주기 바람.

 

-nneg: 각 cascade 학습 단계(stage)에 사용될 negative 샘플 개수를 설정. -bg로 입력한 negative 이미지들 중에서 다양한 위치 및 크기로 negative 샘플들을 뽑기 때문에 실제 negative 이미지 개수와 관계없이 원하는 값을 주면 됨.

 

-minhitrate: 각 cascade 단계의 기본 classifier들에게 요구되는 최소 검출율. 최종 검출기의 검출율은 minhitrate^nstages가 됨. 예를 들어, 기본값을 그대로 사용하면 최소 0.995^14 = 0.932 정도의 검출율을 가지는 detector를 얻을 수 있게 됨. 하지만 이것은 어디까지나 .vec 파일로 입력한 training 데이터에 대한 검출율이기 때문에 실제 일반적인 입력에 대한 검출율은 훨씬 떨어질 수 있음.

 

3. .xml 파일을 이용해 객체 검출 해보기

#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/videoio.hpp"
#include <iostream>
#include <string>

using namespace std;
using namespace cv;


void detectAndDisplay(const CascadeClassifier& classfier, Mat frame )
{
     Mat frame_gray;
     cvtColor( frame, frame_gray, COLOR_BGR2GRAY );
     
     //-- Detect objects
     std::vector<Rect> objects;
     classfier.detectMultiScale( frame_gray, objects );
     for ( size_t i = 0; i < objects.size(); i++ )
     {
         Point center( objects[i].x + objects[i].width/2, objects[i].y + objects[i].height/2 );
         ellipse( frame, center, Size( objects[i].width/2, objects[i].height/2 ), 0, 0, 360, Scalar( 255, 0, 255 ), 4 );
     }
     //-- Show what you got
     imshow( "Capture - object detection", frame );
}

int main(void)
{

    CascadeClassifier classfier;
    
    //-- 1. Load the cascades
    string cascade_name = "trained.xml";
    if( !classfier.load( cascade_name ) )
    {
    cout << "--(!)Error loading face cascade\n";
    return -1;
    };
    
    Mat frame;
    ... 이미지 읽기
    //-- 2. Apply the classifier to the frame
    detectAndDisplay(classfier,frame);
}

 

참고 사이트 : 

https://darkpgmr.tistory.com/70

 

OpenCV Haar/cascade training 튜토리얼

OpenCV의 Haar classifier, Cascade classifier를 학습시키기 위한 샘플 데이터 생성법 및 training 방법에 대한 상세 메뉴얼입니다. Haar training이나 cascade training에 대한 내용은 OpenCV 웹 매뉴얼이나 Naotoshi Seo의

darkpgmr.tistory.com

https://docs.opencv.org/3.4/db/d28/tutorial_cascade_classifier.html

 

OpenCV: Cascade Classifier

Next Tutorial: Cascade Classifier Training Goal In this tutorial, We will learn how the Haar cascade object detection works. We will see the basics of face detection and eye detection using the Haar Feature-based Cascade Classifiers We will use the cv::Cas

docs.opencv.org

목표

1. kernel 트릭을 이용한 SVM을 이해한다.

2. 학습한 kernel SVM에 대해 Decision boundry 그래프를 그려본다.

 

1. kernel 트릭을 이용한 SVM 을 이해한다.

kernel 이란?

-  기존 SVM은 선형 SVM이라 불린다. 이 뜻은 아래 그림처럼 선형특성을 가지는 Decision boundry를 만든다는 것이다.

선형적인 Decision boundry (빨간색 사각형선 표시)

- 비선형성을 가지는 데이터를 분류 할 때는 어떻게 해야 할까?? 아래는 선형 적인 Decision boundry로 구분하기 힘든 XOR 형식의 데이터이다. 

XOR 형식의 데이터

- XOR 형식의 데이터에 대해 기존 SVM을 적용했을 때 그래프이다.

 

XOR 형식의 데이터를 기존 SVM으로 학습 시켰을때

- 이렇게 특징 데이터가 비선형성을 가지는 경우 비선형성의 데이터를 선형적인 특성으로 변환시킬만한 트릭이 필요하다.

- 이때 사용하는 기법이 Kernel 트릭이다.

- Kernel 트릭은 기존의 특징 데이터를 SVM이 구분 가능한 데이터의 형태로 변환 시키는 것이다. 아래는 널리 사용되는 Kernel 중 하나 인 방사 기저 함수(Radial Basis functionl; RBF) 이다.

RBF 필터 계산식

- RBF Kernel 트릭은 샘플 간의 유사도 함수(similarity function)로 사용한다고 한다.

- 이렇게 지수 함수로 얻은 값이 1에 가까우면 두 샘플 간 거의 비슷한 값, 0에 가까우면 매우 다른 샘플이라 한다.

 

 

2. 학습한 kernel SVM에 대해 Decision boundry 그래프를 그려본다.

 

- 아래는 RBF Kernel 트릭을 이용해 데이터를 변환시키고 선형 SVM을 적용하는 코드와 그래프이다.

from sklearn.svm import SVC
svm = SVC(kernel='rbf', C=1.0,gamma=0.2, random_state=1)
svm.fit(X_xor, y_xor)

- 이 코드에서 gamma 값은 크면 클수록 일반화에 약해짐으로 학습시킬 때 주의하자.

RBF kerenel을 이용해 데이터를 변환시키고 SVM을 학습 시켰을때

- 마지막으로 gamma 값을 100.0으로 했을 때 그래프이다. 

gamma 값은 100으로 지정하고&nbsp; SVM을 학습 시켰을 때

목표

1. (Support Vector Machine; SVM)을 이해하고 꽃 분류 모델을 만든다.

2. 학습한 SVM에 대해 Decision boundry 그래프를 그려본다.

 

1. (Support Vector Machine; SVM) 을 이해하고 꽃 분류 모델을 만든다.

SVM 이란?

-  SVM을 이해하려면  Decision boundry, 서포트 벡터, 마진을 알아야한다.

1. decision boundry

  - 직역하면 결정 경계선을 의미한다. 

Decision boundry (빨간색 사각형선 표시)

2. support vector

  - 결정 경계선에서 가장 가까운 데이터를 의미한다. 가깝다는 것은 간단하게 L2 Norm으로 계산했을 때 나오는 값을 봤을 때 다른 데이터 보다 작은 값을 의미한다.

3. margin 

 -  서포트 벡터와 결정 경계선간의 거리

 - 마진을 최대화했다 -> 일반화 오차를 낮췄다. 

 - 마진을 최소화했다 -> 일반화 오차가 크다. (오버피팅)

서포트 벡터(동그라미 표시), 마진(직선)

- SVM은 학습을 통해 서로 다른 레이블과 매칭하는 특성 데이터의 support vector와 생성할 decision boudry 간에 margin값을 최대화하는 것이 목표이다. 

- soft margin classification : 이전의 SVM에서 슬랙 변수를 이용하여 선형적으로 구분되지 않는 데이터에서 선형 제약 조건을 완화시킨 분류기이다. 

 

 

from sklearn.svm import SVC
svm = SVC(kernel='linear', C=1.0, random_state=1)
svm.fit(X_train_std, y_train)

- 위는 SVM을 생성하고 학습시키는 코드이다.

- SVC인 이유는 Support Vector classification 이기 때문이다. (SVM과 학습 원리 동일)

- 이때 C 값은 마진 폭을 조절해 주는 변수이다. C 값을 조절하여 편향-분산 트레이드오프 (bais vs variance trade-off)를 조절한다.

 

2. 학습한 SVM에 대해 Decision boundry 그래프를 그려본다.

plot_decision_regions(X_combined_std, y_combined,classifier=svm, test_idx=range(105, 150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

- 위 코드는 학습한 모델을 아래 챕터에서 정의한 함수를 재사용하여 그리는 코드이다.

https://computervision-is-fun.tistory.com/111

- 아래 그림은 학습한 SVM의 Decision boundry 그래프이다.

목표

1. sklearn API에서 제공하는 꽃 데이터를 받아서 데이터 분포를 정규화한다.

2. sklearn API에서 제공하는 퍼셉트론 모델을 이용하여 꽃 분류 모델 훈련하고 테스트한다.

3. matplotlib를 이용해 입출력 데이터에 대한 꽃 분류 모델의 Decision boudry를 시각화한다.

 

 

1. sklearn API에서 제공하는 꽃 데이터를 받아서 데이터 분포를 정규화 한다.

iris데이터는 아래 3가지 사진에서 보이는 꽃에 대해서 특성데이터(X), 라벨데이터(y)가 주어져 있는 데이터이다.

0 : Iris Versicolor, 1 : Setosa, 2 : Iris Virginica

from sklearn import datasets
import numpy as np

iris = datasets.load_iris()
X = iris.data[:,[2,3]]
y = iris.target
print('클래스 레이블 : ', np.unique(y))

 

여기서 iris.data는 꽃의 샘플 데이터이다. iris.data의 구조는 아래와 같다. 

보통 머신 러닝 프로젝트를 한다면 데이터를 분석하는게 제일 중요한 것 같다. (데이터를 분석한다는 것은 데이터의 차원, 분포도, 평균, 표준편차 등을 예시로 들 수 있다.)

iris.data[:,[2,3]]

위와 같이 구성한 명령어는 꽃 분류 데이터의 2번째와 3번째 특성 데이터만 쓰겠다는 뜻이다. (추 후 Decision boundry로 그리기 위함 + 꽃을 분류하기에 충분히 의미 있는 데이터임)

 

from sklearn.model_selection import train_test_split

#X : 특성 데이터, y : 레이블 데이터, test_size : 데이터에서 테스트에 쓸 데이터 비율, random_state : random seed 값, stratify : 레이블 데이터의 비율 고정
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1, stratify=y)

다음은 퍼셉트론 모델을 학습 하기 위한 데이터 분할이다. 

테스트 셋은 0.3, 학습 데이터 셋은 0.7 비율이다.

그리고 stratify=y 파라미터 의미는 학습용, 테스트용 데이터 셋에 대해서 레이블 값을 균등하게 가지도록 하겠다는 뜻이다.

아래는 stratify=y 파라미터가 있는 것과 없는 것의 차이이다.

 

stratify=y 있을 때
stratify=y 없을 때

위와 같이 학습용 데이터에 대해서 균등하게 나눠지는 것을 볼 수 있다.

 

 

from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
sc.fit(X_train)
X_train_std = sc.transform(X_train)
X_test_std = sc.transform(X_test)

마지막으로 위의 코드는 학습용 특성 데이터 (X) 에 대해서 데이터를 정규화하는 코드이다.

간단하게 StandardScaler Class가 학습용 데이터 X_train을 받아서 데이터의 평균과 분포를 계산하고 이에 맞게 학습용 특성 데이터, 테스트용 특성 데이터를 정규화하는 코드이다.

 

2. sklearn API에서 제공하는 퍼셉트론 모델을 이용하여 꽃 분류 모델 훈련하고 테스트한다.

from sklearn.linear_model import Perceptron

#eta0 : 학습률, random_state : random seed 
ppn = Perceptron(eta0=0.1, random_state=1)
ppn.fit(X_train_std, y_train)

위의 코드는 Perceptron Class를 생성하고 정규화했던 학습용 데이터 셋에 대해서 학습시키는 코드이다.

fit 함수가 입력한 데이터에 대해서 학습하는 코드이다.

y_pred = ppn.predict(X_test_std)
print(f'분류 실패 개수 {(y_test != y_pred).sum()}')
print(f'분류 정확도 {ppn.score(X_test_std, y_test)}')

위의 코드는 학습한 퍼셉트론 모델을 테스트하는 코드이다.  Pytorch나 Tensorflow 였으면 모델 정확도 측정에 대해서 코드량이 좀더 많았겠지만 sclearn은 이런 부분을 모두 구현되어 있어서 편하다.

훈련 결과 정확도 0.97이 나옴

 

3. matplotlib를 이용해 입출력 데이터에 대한 꽃 분류 모델의 Decision boudry를 시각화한다.

from matplotlib.colors import ListedColormap
import matplotlib.pylab as plt

def plot_decision_regions(X, y, classifier, test_idx=None, resolution=0.02):
    markers = ('s', 'x','o','^','v')
    colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
    cmap = ListedColormap(colors=colors[:len(np.unique(y))])

    x1_min, x1_max = X[:,0].min() -1, X[:,0].max() + 1
    x2_min, x2_max = X[:,1].min() -1, X[:,1].max() + 1
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution), np.arange(x2_min, x2_max, resolution))
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
    Z = Z.reshape(xx1.shape) 

    plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap)   
    plt.xlim(xx1.min(), xx1.max())
    plt.ylim(xx2.min(), xx2.max())

    for idx, cl in enumerate(np.unique(y)):
        plt.scatter(x=X[y == cl,0], y=X[y == cl,1],
                    alpha =0.8, c=colors[idx], marker=markers[idx], label=cl, edgecolors='black')
        
    if test_idx:
        X_test, y_test = X[test_idx, :], y[test_idx]

        plt.scatter(X_test[:,0], X_test[:,1], 
        facecolors='none', alpha =1.0, linewidths=1, marker='o', s=100,label='test set', edgecolors='black')
 
X_combined_std = np.vstack((X_train_std, X_test_std))
y_combined = np.hstack((y_train, y_test))
plot_decision_regions(X = X_combined_std, y=y_combined, classifier=ppn, test_idx=range(105,150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

위의 에서 정의한 함수는 특성 데이터(x), 라벨 데이터(y), 모델 분류기(퍼셉트론 모델 등..) 을 받아서 Decision boundry를 그려주는 함수이다. 개인적으로 훈련한 모델이 특성 데이터(X)를 어떻게 보고 있는지 확인할 수 있는 그래프를 보는 것은 정말 재밌다.

    x1_min, x1_max = X[:,0].min() -1, X[:,0].max() + 1
    x2_min, x2_max = X[:,1].min() -1, X[:,1].max() + 1
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution), np.arange(x2_min, x2_max, resolution))
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
    Z = Z.reshape(xx1.shape)
    
    plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap)   
    plt.xlim(xx1.min(), xx1.max())
    plt.ylim(xx2.min(), xx2.max())

위의 코드 순서

1. 특성 데이터(X)의 첫번째 특성과 두번째 특성 데이터를 그래프의 X축, Y축으로 구분한다.

2.  np.arange()함수를 이용해 특성 데이터 X에 대해서 X축, Y축을 resolution 만큼의 데이터를 나눈 후 xx1, xx2에 저장한다. 

(ex : X축 범위 (1~100) 일때 resolution이 1이라면 100개의 데이터가 생성된다. 이때 생성된 데이터는 1씩 증가한 데이터이다. X = [1,2,3,4,5,6])

3. 이렇게 만든 모든 데이터(xx1, xx2)에 대해서 모든 값을 분류기에 입력하고 출력값 Z를 확인한다.

4. Z 값의 차원을 특성 데이터(xx1)의 차원과 동일하게 만든다.(그리기 위함)

5. 위의 과정을 거쳐서 얻은 xx1, xx2에 값에 대해서 contourf 함수를 통해 그래프를 그린다.

xx1 : petal length(X축), xx2 : petal width(Y축),&nbsp; Z : 그래프의 색

for idx, cl in enumerate(np.unique(y)):
        plt.scatter(x=X[y == cl,0], y=X[y == cl,1],
                    alpha =0.8, c=colors[idx], marker=markers[idx], label=cl, edgecolors='black')
        
    if test_idx:
        X_test, y_test = X[test_idx, :], y[test_idx]

        plt.scatter(X_test[:,0], X_test[:,1], 
        facecolors='none', alpha =1.0, linewidths=1, marker='o', s=100,label='test set', edgecolors='black')

위의 코드는 이전 단계에서 그렸던 그래프에서 학습용 데이터와 테스트용 데이터를 입력했을 때 얻은 값을 그래프에 표시하는 것이다.

학습용 데이터, 테스트용 데이터를 입력했을 때 표시

X_combined_std = np.vstack((X_train_std, X_test_std))
y_combined = np.hstack((y_train, y_test))
plot_decision_regions(X = X_combined_std, y=y_combined, classifier=ppn, test_idx=range(105,150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

마지막으로 위의 코드는 그래프를 보여주기 위해 데이터를 구성하고 Decision boundry 함수에 입력해서 그래프를 확인하는 코드이다.

X_combined_std = np.vstack((X_train_std, X_test_std))
y_combined = np.hstack((y_train, y_test))

위의 두 코드에서 vstack은 vertical stack, hstack은 horizental stack을 뜻한다. 이는 모델의 입출력에 대해서 데이터를 추가하는 것이다.

여기서 X 데이터에 대해서 vstack인 이유는 N * M 행렬이기 때문이다. (N : 데이터의 갯수, M : N번째 벡터에 포함되어 있는 특성 데이터의 갯수)

여기서 y 데이터에 대해서 hstack인 이유는 N 벡터이기 때문이다. (N : 데이터의 갯수)

 

끝..

시스템 설계

STATE

자료구조 :  Dictionary

key : snr1, snr2, snr3, snr4 -> 정수형 으로 변환후 저장.

value : Action value function (2개씩 고르는 모든 경우의 수를 가짐)

 

이유 :

Dictionary쓰는 이유 4개의 vector를 좀 더 단순하게 다루기 위해 문자열로 변환 후 저장 하면 그 값 자체가 고유의 key를 의미 하기 때문. (ex:  key : '10,2,3,-1', value : [10,2,0,2,1,3] ).

REWARD

첫번째 시도 -> 특정 값에 수렴하지만 최적의 값은 아니 었음.

Reward : 차분값을 이용한 방법

(select SNR 적용 후 처리율 - 이전 처리율) + (선택)(select SNR 적용 후 fairness - 이전  fairness)

 

두번째 시도 -> 기존의 MR Scheduling과 동일한 성능.

Reward : 해당 상태에서 뽑아냈던 최대 처리율과 현재 선택한 snr의 최대 처리율의 차분값을 이용

선택한 SNR이 이전에 선택했던 최대 처리율 보다 높으면 positive 값 적용

선택한 SNR이 이전에 선택했던 최대 처리율 보다 낮으면 negative 값 적용

(select SNR 적용 후 처리율 - 이전 최대 처리율) + (선택)(select SNR 적용 후 fairness - 이전  fairness)

 

이유 : 목적이 처리율이므로.  선택적으로 fairness를 고려할 수도 있음.

ACTION

epsilon-greedy (1 iteration : epsilon /= 1.5)

P(epsilon) -> greedy

P(1-epsilon) -> random

 

이유 : 잘못된 초기화 값을 대비하여 구성

UPDATE

Q(s,a) = Q(s,a) + alpha * (Reward + gamma * argmax(Q(s',a)) - Q(s,a))

 

 

Q-Learning 기반 ->

빠른 convergence 결과를 볼 수 있음 + 상대적으로 많이 쓰임.

 

학습 및 시뮬레이션 그래프

 

TrainData <X, Y>

Traindata set

영수 철수 영희 도희 찰스 백희 가희 슈렉
32 54 -12 53 254 13 98 41

Traindata set Y

1등

0 0 0 0 1 0 0 0

2등

0 0 0 0 0 0 1 0

DNN Model (Logistic Regression)

  - Node 구성

InputLayer Node HiddenLayer Node OutputLayer Node 
8 16x32x64x32 16

DNN Model

- Hidden Layer Activate Function

    ReLU

   

Relu

  - Output Layer Activate Function

    

Sigmoid function 
Sigmoid graph

     설명 : sigmoid를 통해 output의 결과값을 0~1사이의 값,

             즉 확률로 표현한다.

              ex 0.0  -> 0%

                  0.5  -> 50%

                  1.0  -> 100% 

 

 

 - Loss Function

    BCE(Binary Cross Entropy Error)

   

BCE loss function

    설명 : BCE function은 0~1사이의 결과값을 모두 고려한 Loss를 뽑을 수 있다.

            따라서 OutputLayer에는 sigmoid function 또는 softmax function 등 Output vector값이

            정규화되어 있어야 한다. 물론 Pytorch에서는 Sigmoid function + BCE loss function

            을 제공하는 Function이 있으며,

            나는 설계한 아웃풋 레이어에서 이미 Sigmoid가 포함되어있기 때문에 BCE function만 썼다. 

            

  - Optimization Method

   SGD(Stochastic Gradient Descent)

 

Code

 - DNN Model   

#Neual Netrok nn.Module 상속 
class Net(nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        self.l1 = nn.Linear(8,16)
        self.l2 = nn.Linear(16,32)
        self.l3 = nn.Linear(32,64)          
        self.l4 = nn.Linear(64,32)
        self.l5 = nn.Linear(32,16)
        
    def forward(self, x):
        
        x = x.float()
        h1 = F.relu(self.l1(x))
        h2 = F.relu(self.l2(h1))
        h3 = F.relu(self.l3(h2))   
        h4 = F.relu(self.l4(h3))   
        h5 = F.sigmoid(self.l5(h4))    
       
        return h5

 

 - Train function

#학습 함수    
def train(epochcount, train_loader, validation_loader, aryofLoss, nShowInterval):
       
    running_loss = 0.0        
    
    #traind_loader로 부터 데이터를 랜덤으로 가져온다.          
    for i, data in enumerate(train_loader):
    
    	#i : trn_loader index, data : set <inputs, labes>         
        inputs, labels = data  
        
         #모델 훈련 모드로 전환
        model.train(True)
        
        #최적화 함수 초기화
        optimizer.zero_grad()
           
        #모델 유추 시작
        output = model(inputs)
        
        #추론 결과 손실 값 취득                
        #오차 output, labels 
        test_loss = criterion(output, labels.float())
                              
        #오차 역전파
        test_loss.backward()
        optimizer.step()
                                   
        #오차 값 표기
        lossValue = test_loss.item()
        running_loss += lossValue
        
        
        #nShowInterval번 순회시 누적 train loss 상태 보여주고, Validation 체크 하기
        if i % nShowInterval == (nShowInterval - 1):    # print every 2000 mini-batches
            aryoftempLoss = []
            print('[%d, %5d] loss: %.3f' %(epochcount + 1, i + 1, running_loss / nShowInterval))
            aryoftempLoss.append(running_loss/nShowInterval)
            aryofLoss.append(artoftempLoss)
            running_loss = 0.0
            validationRunning_loss= 0.0
            
            #validation 체크 구간
            for valindex, valdata in enumerate(validation_loader):
                validationinputs, validationlabels = valdata 
                
                #모델 테스트 모드로 전환
                model.train(False)                               
                validationoutput = model(validationinputs) 
        
                #오차 output, labels 
                validationloss = criterion(validationoutput, validationlabels.float())
                          
                validationlossValue = validationloss.item()
                validationRunning_loss += validationlossValue
            
            #validation loss 저장
            validationTempLoss = []
            validationSize = len(validation_loader)
            validationRunning_loss /= validationSize
            validationTempLoss.append(validationRunning_loss)
            aryofValidationLoss.append(validationTempLoss)
            print('Validationloss: %.3f' %(validationRunning_loss))               

 

 - Test function

#테스트 함수    
def test(log_interval, model, test_loader, aryofModelInfo):
    
    #모델 테스트 모드로 전환
    model.eval()
    
    #Test loss 수집용
    test_loss = 0
    
    #correct rate 수집용
    correct = 0
    
    with torch.no_grad():
        for data, target in test_loader:
                      
            #추론 시작
            output = model(data)
                        
            #오차 output, labels 
            test_loss += criterion(output, target.float())
                       
            target1 = target[0][0:8]
            target2 = target[0][8:16]
            
            output1 = output[0][0:8]
            output2 = output[0][8:16]
            
            #추론 결과 직접 비교
            if torch.max(target1,0)[1] == torch.max(output1,0)[1] and torch.max(target2,0)[1] == torch.max(output2,0)[1]:
                correct += 1

    
    #추론 결과 평균 수집
    test_loss /= len(test_loader.dataset)

    #추론 결과 보여줌
    print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format
          (test_loss, correct, len(test_loader.dataset),
        100. * correct / len(test_loader.dataset)))
    
    aryOfTempInfo = []
    aryOfTempInfo.append(test_loss.item())
    aryOfTempInfo.append(correct)
    aryOfTempInfo.append(100. * correct / len(test_loader.dataset))
    aryofModelInfo.append(artOfTempInfo)
   

 

 - Save Model

modelPath = "./model"
createFolder(modelPath)   
MakeCSVFile(modelPath, "ModelLossInfo.csv", ["Loss"], aryofLoss)
MakeCSVFile(modelPath, "ModelValidationLossInfo.csv", ["Validation Loss"], aryofValidationLoss)
MakeCSVFile(modelPath, "ModelSpec.csv",["Average Loss","Correct","Accracy"],aryofModelInfo)
torch.save(model, modelPath + '/model.pt')    

 

Simulation

epoch가 증가할 수록 Loss는 감소, Acrracy는 증가 하는 그래프

 

개인적인 의견

 두 번째로 모델링 한 DNN Model이다. 간단한 1등 찾기 모델은 쉽게 구현했으나, Classification 분야에서 Multi Classification 분야로 활용하는 부분을 구현하기 위해 Output layer의 활성화 함수 그리고 Loss function을 선정하는 부분이 제일 힘들었다. Loss function은 10분쯤 생각해봐야 하는 공식이다. 어찌 보면 당연한 공식이지만 왜 당연한지 생각해 봐야 한다. 그리고 생각보다 걱정했던 오버 피팅 문제는 없었다. 왜 없었을까..? 서적에서 참고 했을때는 모델의 복잡도와 데이터의 수의 관계에 따라 오버피팅 문제가 생길 수 있다고 했는데 흠.. 아직 이런 부분은 센스가 부족한 것 같다.

 

추가로 공부해야 할 부분 : Chain rule, Backpropagation, Bias & Variance, Optimization issue

+ Recent posts