[ 개인 미니 프로젝트 ] 선형 신경망 딥러닝 학습 라이브러리 개발 - Whitmem
[ 개인 미니 프로젝트 ] 선형 신경망 딥러닝 학습 라이브러리 개발
Software Portfolios
2025-03-31 00:16 게시 179fb5ce415df14a62b9

0
0
71
이 페이지는 외부 공간에 무단 복제할 수 없으며 오직 있는 그대로 게시되며 부정확한 내용을 포함할 수 있습니다. 법률이 허용하는 한 가이드 라인에 맞춰 게시 내용을 인용하거나 출처로 표기할 수 있습니다.
This page is not to be distributed to external services; it is provided as is and may contain inaccuracies.
이 소프트웨어는 개인적인 목적으로, 공부 용도로 개발되었습니다. 따라서 내부망에서 개발되었을 뿐, 외부 서비스, 제공 목적으로 서비스하지 않습니다. 따라서 라이선스 등 내부적인 사항들은 조사되지 않았습니다. 이 게시물은 단순히 Whitmem의 개발 기록을 남긴 게시물이며 업으로서 제공하지 않습니다.
요약
개발 요약
외부 인공지능 라이브러리 없이 신경망을 생성하고 여러 데이터를 학습 및 추론할 수 있는 라이브러리입니다. GPU 기반이 아닌 CPU 기반에서 동작되며, 단순 선형 신경망을 활용하며, 원하는 레이어 구조로 모델을 정의할 수 있습니다. 기본적으로 Gradient 경사 하강을 사용하며, 기타 기능은 추가 중에 있는 미니 프로젝트입니다.
개발 언어
주요 언어 : C#
개발 환경 : Windows
개발 기간 : 약 5일
개발 배경
보통 파이썬 언어에서 인공지능을 구현하기 위해 Tensorflow, Keras, Pytorch 라이브러리를 사용하는데, 이러한 외부 라이브러리 없이 C# 언어와 기본적인 처리 알고리즘만으로 딥러닝 처리 도구를 개발하고자 하였습니다. 이 과정을 통해서 인공지능의 기본적인 편미분 과정과 전파 기법을 익히고 다양한 환경에서 활용할 수 있는 역량을 기르고자 합니다.
기능 스냅샷
사용자는 기본적으로 입력 데이터와 레이블 데이터를 쌍으로 특정 에폭 만큼 반복하여 학습하고 그 Loss 결과를 확인할 수 있습니다.
원하는 횟수만큼 반복하여 신경망에 batchLearn을 수행하여 배치 만큼 학습을 수행할 수 있습니다. 배치를 수행하고 임의 predict 를 수행하여 올바르게 예측하는지 확인할 수 있습니다. 한편 이미지도 학습할 수 있는데, 이미지 바이트 배열을 float 단위로 변환 한 뒤 인공 신경망에 펼쳐 넣어 학습할 수 있습니다.
먼저 이미지 세트 불러오기를 통해 학습할 이미지 세트를 로드합니다.
예를 들어 원 그림이 그려진 이미지 파일과, X 모양이 그려진 이미지 파일 세트를 한 번에 로드합니다. 파일 명은 o_number, x_number 형태로 split 작업을 통해 o, x 를 레이블로 로드합니다. 파일을 불러올 때 자동으로 원 핫 인코딩 형태로 읽어들입니다.
이제, 러닝 레이트와 에폭을 지정하여 학습을 수행합니다.
아무래도 확률적 경사 하강법과 같은 옵티마이저를 사용하는 것이 아니라, 직접 구현한 경사 하강 클래스를 사용하기 때문에 초기 Learning Rate 를 매우 적게 지정하여 학습을 수행합니다.
학습을 수행하면 수행할수록 로스가 낮아지는 것을 확인할 수 있습니다.
원 핫 인코드이기 때문에 2개의 float으로 내보냅니다. 0번째 float이 0일 확률, 1번째 float이 1일 확률로 볼 수 있습니다. 다만, 마지막 신경망의 출력 부분을 Softmax 로 구현하지 않았기 때문에 총 합이 1이되지는 않습니다.
--------------------------- 0 확률 0.48057556~ 1 확률 0.89738464 --------------------------- 확인 ---------------------------
--------------------------- 0 확률 1.0938263 ~ 1 확률 0.1727448 --------------------------- 확인 ---------------------------
학습이 어느정도 수행된 것을 확인할 수 있습니다.
구현 과정
클래스 구조
기본적인 클래스 구조입니다. 아무래도 프레임워크가 아니고 간단하게 인공지능 딥러닝을 공부하고자 구현한 프로젝트이므로, 다양한 기능이 존재하지는 않습니다.
- ImageToArray
이미지 메모리 데이터를 바이트 배열로 변환하는 클래스입니다.
- LinearLayer
한 개의 층을 관리하며 이 층에 포함되는 뉴런 인스턴스를 관리합니다. 이 층의 뉴런에 총괄적으로 명령을 보내거나 요청할 수 있습니다.
- Loss
기본적인 로스 함수를 정의할 수 있는 클래스입니다. 아웃풋 신경망 다음에 연결되는 로스 함수로 기본적인 추상 메서드를 구현합니다.
- MSELoss
Loss 의 구현체로 다양한 손실 함수중 MSE Loss를 구현하고 있습니다.
- LinearNeuron
하나의 뉴런을 나타내는 것으로 다른 뉴런에 직접 연결하고 각 가중치 정보를 보관하는 클래스입니다.
- CalculatorNode
계산을 처리하는 노드로 계산을 수행하고 역으로 미분을 수행하는 함수를 정의해놓은 인터페이스, 추상 정보를 포함하는 부모 클래스입니다.
- AddNode
기본적인 덧셈 계산을 처리하고 일부 미분을 수행할 수 있는 처리 노드입니다.
- LinearNode
선형 뉴런 계산을 위해 기본적인 이전 입력들을 가중치와 함께 곱하고 총합을 구하며, 이를 미분할 수 있는 노드입니다.
- MultiplyNode
곱셈을 수행하고 미분할 수 있는 노드입니다.
- PowNode
제곱을 수행하고 미분할 수 있는 노드입니다.
- ReluNode
RELU 활성화 함수를 처리하고 미분할 수 있는 노드입니다.
- SENode
MSE 계산을 위해 각 실제 값과 차 값을 빼고 제곱을 취하며 이를 미분할 수 있는 노드입니다.
- Sigmoid
시그모이드 함수를 처리하고 미분할 수 있는 노드입니다.
- FloatArrayView
Float 배열을 String으로 변환하는 클래스입니다.
- LayerWorker
총괄적으로 레이어 마다 뉴런 처리를 수행하고 끝에 원하는 손실 함수를 붙여 학습을 수행할 수 있는 도구입니다. 배치 단위로 학습을 수행할 수 있습니다.
기본적인 구현 과정
기본적으로 하나의 뉴런 정보를 담고 있는 LinearNeuron은 순전파 과정에서 기록된 아웃풋 정보, 인풋 정보, 그리고 역전파 과정에서 기록된 기울기 정보를 보관합니다. 이러한 기울기 정보는 각각 모든 변수마다 기록되며, 이전 층의 아웃풋에 미분 정보가 역전달되어야 하기 때문에, 각 인풋 정보를 조회할 수 있도록 getter을 구현하며, 역전파 과정에서 이전 층의(즉 입력에 가까운 층) 아웃풋에 입력 미분의 총합을 구할 수 있도록 합니다.
각 뉴런은 원하는 다른 뉴런과 연결할 수 있습니다. 특정 뉴런의 출력에 어떤 다른 뉴런을 연결하거나, 특정 뉴런의 특정 입력 인덱스에 다른 출력 뉴런을 연결할 수 있습니다. 한 뉴런은 여러 입력을 가질 수 있고, 각 입력마다 가중치 정보를 가지고 있기 때문에 인덱스로 관리합니다.
각 입력 사이즈, 출력 사이즈, 순전파후 출력 값, 역전파후 출력 단의 기울기 총합, 각 인풋의 기울기 값, 각 입력 라인의 가중치 값, 활성화 함수 지정, 미분 출력 값을 수동으로 정의하거나, 가중치를 수동으로 정의하거나, 특정 가중치 값을 가져올 수 있는 세터, 게터 메서드입니다.
각 뉴런의 순전파 구현
기본적으로 각 뉴런은 순전파 과정에서 입력 가중치 정보와 입력 정보를 곱하여 총합을 구합니다. 그 과정에서 사용되는 각 입력 정보는 추후 역전파 과정에서 사용되기 때문에 메모리에 기록하고 역전파 과정에서 사용할 미분 정보도 연산해야 합니다. 즉 gridCallbacks는 순전파 과정에서 기록되고, 역전파시 순전파의 역순대로 미분이 수행되어야 하기 때문에 콜백을 순차적으로 스택에 쌓고 거꾸로 스택을 빼내면서 역전파를 수행합니다.
그리고 기본적인 뉴런 가중치 처리가 완료된 경우 출력 값에 활성화 함수를 1대1로 거칩니다. 이 또한 추후 미분할 수 있도록 internalForwardNodes에 기록합니다. 다만, 활성화 함수는 미분 값을 다시 조회할 필요는 없기 때문에 단순히 미분만 수행하여 다른 계산 처리자에 미분 기록을 넘길 수 있도록만 구현합니다.
아무튼 생성된 계산 처리자를 순서대로 수행하면서 순전파를 수행하고, 이 뉴런의 출력 값을 가져와 내부 메모리 변수에 기록합니다.
각 뉴런의 역전파 구현
역전파는 현재 뉴런의 출력 미분 값이 임의로 지정되지 않은 경우, 다음 뉴런의 입력 라인들의 미분 값들을 모두 가져와 totalOutputGrid에 더합니다. 만약 다음 뉴런이 없는 경우 마지막 출력 뉴런으로 보고 미분 역전파를 1로 시작합니다.
출력 단의 미분 값 가져오기가 완료된 경우, 순전파 당시 계산 작업을 역으로 수행하고, 마지막 gridCallbacks를 사용해 계산된 미분 정보를 각 메모리 변수에 알맞게 배치하는 작업을 수행합니다. 즉 internalForwadNodes 를 통해 역전파를 하는 과정에서 각 값들은 미분되었으며, gridCallbacks를 통해 미분 값을 뉴런의 메모리 정보에 배치하는 여할을 수행합니다.
참고로, 각 뉴런이 순전파, 역전파를 1회 수행하면서 기록되는 미분 정보는 memoryWeightGrid에 누적되며, reset 하기 전까지는 총합된 기울기 정보를 가져옵니다.
데이터를 배치 단위로 학습할 때 미분 정보를 일괄적으로 가져와야 하므로, 한 배치 작업을 완료한 뒤에서야 메모리를 정리하여 미분 정보를 초기화합니다. 배치단위로 수행하는데 각 데이터를 삽입할 때 미분 정보를 초기화하는 경우 특정 데이터에 대해서만 미분하여 경사 하강이 수행되는 문제가 발생할 수 있습니다.
각 신경망을 층 단위로 관리하는 클래스
상기 설명된 뉴런 클래스는 하나의 뉴런에 대한 처리만을 담당합니다. 따라서 이 뉴런들을 층단위로 보관하고 다음 층과 연결, 관리하기 위해 별도의 층 관리 클래스가 필요합니다.
이는 LinearLayer 가 수행합니다. 이 클래스를 생성할 때 입력 뉴런 개수, 출력 뉴런 개수를 지정하여 내부적으로 뉴런을 생성하고 정의합니다. 그 과정에서 뉴런을 서로 거미줄 처럼 연결하며 상호작용할 수 있는 관계를 정의합니다.
즉 각 층에 forward 또는 backward를 수행하는 경우 존재하는 내부 뉴런들에게 모두 일괄적으로 전달됩니다.
이외, 내부 배치 메모리를 일괄적으로 리셋하거나, 각 가중치를 랜덤으로 초기화하거나, 1회 에폭 학습을 수행하는 메서드가 존재합니다.
기본적으로 배치 개수를 단순히 평균내어 기울기를 구하고 기울기에 대해서 learning Rate를 곱한 값을 빼는식으로 경사 하강 및 학습을 수행합니다.
로스 함수
학습을 수행하기 위해 손실 함수 값이 0에 가까워지는 식을 생성하고 계산하는 클래스가 필요합니다.
기본적으로 로스 클래스는 내부 아웃풋 뉴런들을 보관하고, 로스 결과를 내보내며, forward, backward를 수행하는 가상 메서드가 존재합니다.
MSELoss는 이 클래스를 구현하여 MSE를 계산하고, 역으로 미분한 값을 각 뉴런에 전달하여 Output에 기입하는 기능을 구현하고 있습니다.
즉 MSELoss 의 forward에서는 실제 레이블(정답)데이터를 받아와 뉴런의 추론 결괏값realValues값의 차 제곱을 계산하고 미분을 수행할 수 있는 SENode인스턴스를 생성하여 순전파를 수행합니다. 이 과정에서 기록된 모든 아웃풋 정보를 더하고 1/n 하여 로스 결과로 내보냅니다.
이 과정에서 아웃풋 값은 1/n로 지정하는데, MSE 는 최종적으로 총합을 1/n하기 때문에 역으로 미분할 때 1/n 부터 시작한다는 점을 고려하여, 출력 미분 값을 1이 아닌 1/n로 지정해둡니다.
미분 역시 순전파시 생성한 seNode 를 backward 하고, 미분 값을 가져와 각 뉴런의 출력 미분 값에 강제적으로 지정하여 뉴런의 역전파 과정에 로스 미분이 전달되도록 합니다.
최종적으로 이 모든 작업을 합쳐 학습을 수행하고 추론을 수행할 수 있는 클래스
마지막으로 위 레이어들을 생성하고, 로스 클래스를 연결하여 학습을 수행하고, 추론을 수행할 수 있는 클래스가 필요합니다.
LayerWorker의 생성자에는 신경망 구조, learning Rate, loss 함수 정보를 기입합니다. 신경망 구조는 int[] 형태로, 입력 개수, 히든 층 개수, 아웃풋 개수를 지정하면 그 개수에 따라 LinearLayer을 생성하고 신경망 모델을 생성하도록 구현하였습니다.
손실 값을 가져오는 getLoss는 1회 순전파를 수행하기 위해 predict를 수행하고, loss 역시 순전파를 진행하여 최종 로스 값을 구합니다.
학습 메서드는 getLoss를 수행한뒤 로스 함수 및 레이어를 모두 일괄적으로 역전파하고, 그 과정에서 생성된 미분 값을 사용하여 러닝 레이트만큼 경사 하강을 수행합니다.
기본적으로 로스 함수를 계산한 뒤, 미분 값만 존재하면 되므로, loss 값이 직접적으로 필요한 것은 아닙니다. 다만 연산 과정에서 나오는 최종 결과물을 학습 지표로 사용할 수 있기 때문에, loss 변수를 보관하면 좋습니다.
최종적으로 배치 단위로 학습하는 메서드입니다. 먼저 각 배치 단위로 들어온 입력, 레이블 값의 차원이 동일한지 확인 후, 각 층에 배치 메모리를 모두 리셋합니다. 배치 작업이 시작되면 배치 단위만큼 반복하면서 신경망에 getLoss를 수행합니다. 즉 각 데이터에 대해서 순전파를 수행하고, 역전파를 수행하는 행위를 배치 내 데이터 개수만큼 반복합니다. 그러면 각 뉴런 안에서는 데이터의 미분 값의 총합이 계산되었을 것이고, 이를 epoch을 통해 한번에 경사하강을 수행합니다. 이 때 각 데이터의 중요도를 정의하지 않고 일괄적으로 평균내기 위해 배치 내 데이터 개수를 epoch메서드에 넘겨주어 평평하게 학습하도록 합니다. 그리고 계산된 로스 결괏 값을 반환하여 배치 학습시 지표로 사용할 수 있도록 하였습니다.
사용 과정
사용 방법은 매우 간단한데, LayerWorker을 생성한 다음, 인자로 먼저 신경망 구조, Learning Rate, Loss 클래스를 넘기면 됩니다. 위 예시에서는 이미지 크기를 1차원으로 편 크기를 가져오고, 히든 10, 히든 10, 출력 2 정도로 내보내게끔 구현하였습니다.
그리고 배치 데이터를 buffers 와 labels 형태로 데이터를 로드하여 에폭 횟수만큼 반복하며 작업을 수행합니다.
추론 역시 predict 를 사용하여 결과를 가져올 수 있도록 구현하였습니다.
고찰 사항
인공지능 딥러닝을 구현하면서 기본적인 미분 원리를 제대로 익혔다고 생각합니다. 물론 다양한 학습 방법과 이론, 내용을 적용하기에는 무리가 있겠으나... 충분히 큰 도움이 되었다고 생각합니다.
앞으로도 다양한 라이브러리로 배우는 인공지능 기능들을 직접 손수 구현함으로써 AI 개발 실력을 향상해야겠다고 다짐하였습니다.
댓글 0개
댓글은 일회용 패스워드가 발급되며 사이트 이용 약관에 동의로 간주됩니다.
확인
Whitmemit 개인 일지 블로그는 개인이 운영하는 정보 공유 공간으로 사용자의 민감한 개인 정보를 직접 요구하거나 요청하지 않습니다. 기본적인 사이트 방문시 처리되는 처리 정보에 대해서는 '사이트 처리 방침'을 참고하십시오. 추가적인 기능의 제공을 위하여 쿠키 정보를 사용하고 있습니다. Whitmemit 에서 처리하는 정보는 식별 용도로 사용되며 기타 글꼴 및 폰트 라이브러리에서 쿠키 정보를 사용할 수 있습니다.
이 자료는 모두 필수 자료로 간주되며, 사이트 이용을 하거나, 탐색하는 경우 동의로 간주합니다.