다층신경망(Multi Layer Perceptron)
단층 퍼셉트론은 XOR논리 연산에서 선형 분리가 불가능한 것을 다층신경망으로 해결할 수 있다
다층 신경망이란 퍼셉트론 뉴런을 여러개의 층으로 쌓은 구조로 입력층과 중간층이라 불리는 은닉층, 출력층으로 이뤄진 신경망이다
여기에 모든 뉴련이 그 다음 층의 모든 뉴런과 연결되어 있는 완전 연결 계층 구조이다(Full Connection Network)
다층 신경망의 구조
전연결 구조(Full-Connected)
- 모든 뉴런들이 다음 층의 모든 뉴런들과 연결되어있는 구조
노드(Node)
- 데이터가 전달되거나 연결되는 연결점
레이어(Layer)
- 여러 개의 노드로 구성된 연결점의 집합
입력층(Input Lyaer)
- 외부에서 입력 받은 데이터를 모든 은닉층으로 보낸다
- 특징의 개수만큼 존재한다
은닉층(Hidden Layer) , = 중간층
- 입력의 특성을 파악하고 목표 출력을 숨긴다
- 은닉층은 1개 이상의 존재한다
출력층(Output Layer)
- 은닉층에서 받은 최종출력을 계산한다
- 구분하려는 범주의 개수
MLP 특징
- 다수의 은닉노드와 은닉층을 가지고 있다
- Full Connected Netowrk 구조
- 순방향 신경망(Feed-Forward Nueral Network)
- 학습 방법으로 오류 역전파 학습(Error Back Propagation Learning)을 한다
- 오류 역전파 알고리즘의 연산량 -> 수렴할 때까지 오류 역전파를 반복해서 연산량이 증가한다
활성화 함수(Activation Function)
- 다층을 사용하기위해 출력층 에러의 일부가 은닉층에 전달 할 수 있도록한다
- 단층 퍼셉트론에서는 계단함수로 활성화 함수를 사용한다
- 다층 퍼셉트론에서는 로지스틱, 시그모이드, 하이퍼볼릭 탄젠트, 소프트플러스를 사용한다
- 딥러닝 같은 경우에는 ReLU를 사용한다
다층신경망(MLP) 구현해보기
XOR 논리 연산하는 단순 MLP 구현
MLP.h(MLP 구조 헤더문)
#pragma once
// 헤더문 선언
#include<malloc.h>
#include<stdlib.h>
#include<time.h>
#include<math.h>
class CMLP
{
public:
CMLP(); // 생성자
~CMLP(); // 소멸자
// 멤버변수 선언
int m_iNumInNodes; // 입력노드의 개수
int m_iNumOutNodes; // 출력 노드의 개수
int m_iNumHiddenLayer; // 히든 레이어(은닉 층)의 수
int m_iNumTotalLayer; // 전체 레이어의 수
int *m_NumNodes; // [0] : InputNode , [1] : HiddenLayer,[히든레이어 수 + 1] : 아웃 레이어, [마지막] : 정답
double ***m_Weight; //가중치 변수 [시작 레이어][시작 노드][연결된 노드]
double **m_NodeOut; //출력 노드 변수 [레이어 번호][노드 번호]
double* pInValue, *pOutValue; // 입력값이 들어오는 위치, 출력값이 들어오는 위치
double *pCorrectOutValue; // 정답
bool Create(int InNode, int* pHiddenNode, int OutNode, int numHiddenLayer); //
private:
void initw(); // 가중치 설정
double ActivationFunc(double weightsum); // 활성호 함수 (시그모이드)
public:
void Forward(); // 계산함수
};
생성자와 소멸자 생성
// 생성자
CMLP::CMLP() {
m_iNumInNodes = 0; // 입력 노드의 수
m_iNumOutNodes = 0; // 출력 노드의 수
m_iNumHiddenLayer = 0; // 은닉층 수
m_iNumTotalLayer = 0; // 전체 레이어 수
m_NumNodes = NULL; //
m_Weight = NULL; //가중치 변수[시작 레이어][시작 노드][연결된 노드]
m_NodeOut = NULL; //출력 노드값 변수 [레이어 번호][노드 번호]
pInValue = NULL; // 입력값이 들어오는 위치
pOutValue = NULL; // 출력값이 들어오는 위치
pCorrectOutValue = NULL; // 정답값
}
// 소멸자
CMLP::~CMLP() {
int layer, snode, enode; // 각각 레이어, 시작노드, 다음 노드
if (m_NodeOut != NULL) { // 출력 노드값이 NULL이 아닐 경우 메모리 해제 메모리 해제
for (layer = 0; layer < (m_iNumTotalLayer+1); layer++)
free(m_NodeOut[layer]);
free(m_NodeOut);
}
if (m_Weight != NULL) { // 가중치 변수가 NULL이 아닐 경우 메모리 해제
for (layer = 0; layer < (m_iNumTotalLayer - 1); layer++) {
for (snode = 0; snode < m_NumNodes[layer] + 1; snode++)
free(m_Weight[layer][snode]);
free(m_Weight[layer]);
}
free(m_Weight);
}
if (m_NumNodes != NULL) // 노드수가 NULL이 아닐 경우 메모리 해제 초기화
free(m_NumNodes);
}
설정값 전달하기 위한 함수 Create() 생성
bool CMLP::Create(int InNode, int* pHiddenNode, int OutNode, int numHiddenLayer){
int layer, snode, enode; // 변수 선언
// 변수 초기화
m_iNumInNodes = InNode;
m_iNumOutNodes = OutNode;
m_iNumHiddenLayer = numHiddenLayer;
m_iNumTotalLayer = numHiddenLayer + 2;
// m_NumInNodes를 위한 메모리 할당
m_NumNodes = (int*)malloc((m_iNumTotalLayer + 1) * sizeof(int)); // m_iNumTotalLayer + 1 : 정답 추가
m_NumNodes[0] = m_iNumInNodes;
for (layer = 0; layer < m_iNumHiddenLayer; layer++)
m_NumNodes[layer+1] = pHiddenNode[layer];
m_NumNodes[m_iNumTotalLayer - 1] = m_iNumOutNodes; // total-1은 출력 레이어의 노드 수
m_NumNodes[m_iNumTotalLayer] = m_iNumOutNodes; // 정답 노드 수 = 출력 노드수
// m_NodeOut의한 메모리할당
m_NodeOut = (double**)malloc((m_iNumTotalLayer + 1) * sizeof(double*)); // +1 : 정답 추가
for (layer = 0; layer < m_iNumTotalLayer + 1; layer++)
m_NodeOut[layer] = (double*)malloc((m_NumNodes[layer] + 1) * sizeof(double));
m_Weight = (double***)malloc((m_iNumTotalLayer-1) * sizeof(double**));
for (layer = 0; layer < m_iNumTotalLayer - 1; layer++) { // -1 : 정답의 weight 없기 때문에 layer-1
m_Weight[layer] = (double**)malloc((m_NumNodes[layer] + 1) * sizeof(double*)); // +1 : 바이어스
for (snode = 0; snode < m_NumNodes[layer] + 1; snode++) {
m_Weight[layer][snode] = (double*)malloc((m_NumNodes[layer+1]+1) * sizeof(double)); // layer+1 : 출력 노드 , +1 : 바이어스
}
}
pInValue = m_NodeOut[0];
pOutValue = m_NodeOut[m_iNumTotalLayer - 1];
pCorrectOutValue = m_NodeOut[m_iNumTotalLayer];
// 바이어스를 위한 출력값 = 1로
for (layer = 0; layer < m_iNumTotalLayer + 1; layer++)
m_NodeOut[layer][0] = 1.0;
return true;
}
가중치 설정을 위한 함수 initw() 생성
void CMLP::initw(){ // 가중치 설정 함수
int layer, snode, enode;
srand(time(NULL));
for (layer = 0; layer < m_iNumTotalLayer - 1; layer++) {
for (snode = 0; snode <= m_NumNodes[layer]; snode++)
for (enode = 1; enode <= m_NumNodes[layer + 1]; enode++)
m_Weight[layer][snode][enode] = (double)rand(); //RAND_MAX-0,5
// 가중치 랜덤함수로 설정!
}
}
활성화 함수 ActivationFunc() (시그모이드 함수로) 생성
double CMLP::ActivationFunc(double weightsum){
return 1.0 / (1.0 + exp(-weightsum));
// 시그모이드 = 1/(1+e^s)
}
순방향 출력 계산 Forward() 생성 -> 먼저 순방향을 구현
void CMLP::Forward(){
int layer, snode, enode;
double weightSum; // 가중치 합
for (layer = 0; layer < (m_iNumTotalLayer - 1); layer++) {
for (enode = 1; enode <= m_NumNodes[layer+1]; enode++) {
weightSum = 0.0; // 가중치 합 0.0으로 초기화
weightSum += m_Weight[layer][0][enode] * 1; // 바이어스값 설정
for (snode = 1; snode <= m_NumNodes[layer]; snode++) {
weightSum += m_Weight[layer][snode][enode] * m_NodeOut[layer][snode];
// 가중치합 = 가중치 * 출력값
}
m_NodeOut[layer + 1][enode] = ActivationFunc(weightSum);
// 출력값은 가중치 합을 활성화 함수로 출력된값을 넣는다
}
}
}
메인 함수 구현
int main(){
int HiddenNodes[1] = { 2 }; // 히든노드의 개수 설정
int numofHiddenLayer = 1; // 히든 레이어 개수 설정
MultiLayer.Create(2, HiddenNodes, 1, numofHiddenLayer); //MLP 값들 설정
double x[4][2] = { {0,0},{0,1}, {1,0}, {1,1} }; // 임의의 입력 값 설정
// [레이어] [시작노드] [끝노드 or 다음 노드] WL0_11 = 입력노드의 첫노드에서 다음 첫 노드로
MultiLayer.m_Weight[0][0][1] = -7.3061; // 은닉층 노드1 바이어스(WL0_b1) 가중치 설정
MultiLayer.m_Weight[0][1][1] = 4.7621; // 은닉층 노드1(WL0_11) 가중치 설정
MultiLayer.m_Weight[0][2][1] = 4.7618; // 은닉층 노드2(WL0_21) 가중치 설정
MultiLayer.m_Weight[0][0][2] = -2.8441; // 은닉층 노드2 바이어스(WL0_b2) 가중치 설정
MultiLayer.m_Weight[0][1][2] = 6.3917; // 은닉층 노드2(WL0_21) 가중치 설정
MultiLayer.m_Weight[0][2][2] = 6.3917; // 은닉층 노드2(WL0_22) 가중치 설정
MultiLayer.m_Weight[1][0][1] = -4.5589; // 출력층 노드 바이어스(WL1_b1) 가중치 설정
MultiLayer.m_Weight[1][1][1] = -10.3788; // 출력층 노드(WL1_11) 가중치 설정
MultiLayer.m_Weight[1][2][1] = 9.7691; // 출력층 노드 (WL1_21) 가중치 설정
// 설정한 입력노드 입력
for (int n = 0; n < 4; n++) {
MultiLayer.pInValue[0] = 1; // 바이어스 값
MultiLayer.pInValue[1] = x[n][0];
MultiLayer.pInValue[2] = x[n][1];
MultiLayer.Forward(); // 순방향 계산 함수
printf("%lf %lf = %lf\n", MultiLayer.pInValue[1], MultiLayer.pInValue[2], MultiLayer.pOutValue[1]);
}
}
xor 연산 했을 때 x: 0, 0 = 0 | x: 0, 1 = 1 | x: 1, 0 = 1 | x: 1, 1 = 0 나와야 한다!
순방향 계산으로 XOR 연산 결과가 잘 나오는 것을 확인 할 수 있다!!
'개인 공부' 카테고리의 다른 글
Malware Analysis-01(정적 분석) (1) | 2023.05.17 |
---|---|
악성코드 (0) | 2023.05.01 |
인공지능P-002 (퍼셉트론 계산 & 한계 ) (1) | 2023.04.22 |
인공지능P-001(Perceptron) (0) | 2023.04.19 |
인공지능P-000(간단한 인공지능 정리) (0) | 2023.04.18 |