본문 바로가기

개인 공부

인공지능P-003

다층신경망(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