본 내용은 밑바닥부터 시작하는 딥러닝 1도서를 참고하여 작성되었습니다.
밑바닥부터 시작하는 딥러닝 1 - 예스24
딥러닝 분야 부동의 베스트셀러!머리로 이해하고 손으로 익히는 가장 쉬운 딥러닝 입문서이 책은 딥러닝의 핵심 개념을 ‘밑바닥부터’ 구현해보며 기초를 한 걸음씩 탄탄하게 다질 수 있도록
www.yes24.com
우리는 지난 시간, 퍼셉트론을 이용해 비선형의 영역까지 문제를 해결하는 성과를 이룰 수 있었다!
하지만, 여전히 가중치를 설정하는 작업은 여전히 우리가 수동으로 해야만 했다.
학습 과정도 스스로 학습할 수 있도록 할 수 있는 방법이 없을까?
신경망이 이 문제에 대한 해결의 실마리를 제공해준다.
가중치 매개변수의 적절한 값을 데이터로부터 자동으로 학습하는 능력은 신경망의 중요한 성질이다.
퍼셉트론에서 신경망으로
퍼셉트론과 신경망은 유사한 점이 상당히 많다.
신경망과 퍼셉트론의 차이점을 이해해보며 신경망을 공부해보자.
신경망의 예
신경망은 다음과 같은 그림으로 표현된다.
여기서 가장 왼쪽 줄을 입력층, 맨 오른쪽 줄을 출력층, 중간 층을 은닉층이라고 한다.
은닉층의 뉴런은 사람에게 보이지 않는 블랙박스 형태이기 때문에 "은닉층"이라고 부른다.
지금부터 입력층을 0층으로 세며, 이 신경망 전체를 "2층 신경망"이라고 부르도록 하겠다.
은닉층, 출력층과 달리, 입력층에는 보통 가중치가 없다.(word2vec 과 같은 입력층 자체 임베딩 파라미터는 제외) 그래서 입력층을 층의 수에 포함하지 않는 경우도 있다. 이 책에서는 입력층을 층의 수에 포함하지 않는다.
그렇다면 신경망에서는 어떤 방식으로 신호를 전달할까?
퍼셉트론을 되돌아보며 차근차근 학습해보자.
퍼셉트론 복습
우리는 x1과 x2라는 두 신호를 입력받아 y를 출력하는 퍼셉트론 하나를 표현했다.
이를 수식으로 나타내면 다음과 같다.
그런데, 위 네트워크에서는 편향(bias) b가 보이질 않는다.
만약 편향을 명시한다면 아래 그림과 같이 표현될 것이다.
여기서 편향의 입력 신호는 항상 1이기 때문에, 그림에서는 해당 뉴런을 회색으로 채워 다른 뉴련과 구별했다.
위 식을 퍼셉트론으로 표현하면 다음과 같을 것이다.
출력 시 값을 조정하는 h(x)라는 함수가 명시적으로 눈에 보이게 되었다.
이 함수에 대해 좀 더 자세히 알아보자.
활성화 함수의 등장
조금 전 h(x)라는 함수가 등장했는데, 이처럼 입력 신호의 총합을 출력 신호로 변환하는 함수를 일반적으로 활성화 함수(activatiuon function) 라고 한다.
'활성화' 라는 이름이 말해주듯이 활성화 함수는 입력 신호의 총합이 활성화를 일으키는지 정하는 역할을 한다.
위에서 표현한 f(x)식을 다시 써보자.
먼저 가중치가 달린 입력 신호와 편향의 총합을 계산하고, 이를 a라고 하자.
그리고 함수 h는 a를 입력으로 받아 y를 출력하는 흐름을 갖는다.
이를 그림으로 표현하면 다음과 같다.
좌측 뉴런의 경우, 활성화 함수가 보이지 않는다.
하지만 우측 뉴런의 경우, 활성화 함수를 명시적으로 표현한 형태로 그려졌다.
이와 같이, 뉴런 내에는 활성화 함수가 존재할 수 있다.
활성화 함수
이와 같은 활성화 함수는 임계값을 경계로 출력이 바뀌는데, 이런 함수를 계단 함수라고 한다. 그래서 아래와 같이 정의할 수 있다.
퍼셉트론에서는 활성화 함수로 계단 함수를 이용한다.
그렇다면, 활성화 함수로는 계단 함수만 사용하나?
신경망과 퍼셉트론의 차이의 핵심은 이 활성화 함수에 있다.
신경망에선 활성화 함수에서 계단 함수 뿐만 아니라 "시그모이드(Sigmoid) 함수"를 사용하기도 하는데, 지금부터 이 활성화 함수에 대해 자세히 알아가보자.
시그모이드 함수
일반적으로 신경망에서 이야기하는 시그모이드 함수는 다음 형태의 함수를 의미한다.
통계에서 이야기하는 시그모이드와는 약간 정의가 다른 듯 하다.
통계에서 이야기하는 시그모이드가 좀 더 광범위한 정의가 되는 느낌?
신경망에서는 활성화 함수로 시그모이드 함수를 이용하여 신호를 변환하고, 그 변환된 신호를 다음 뉴런에 전달한다.
사실 앞장에서 본 퍼셉트론과 신경망의 차이는 이 활성화 함수 뿐이다.
그 외에 뉴런이 여러 층으로 이어지는 구조와 신호를 전달하는 방법은 기본적으로 퍼셉트론과 같다.
그럼 지금부터 시그모이드 함수를 계단 함수와 비교하며 자세히 알아보자.
계단 함수 구현하기
가장 간단하게 계단하면 다음과 같은 형태로 함수를 구현할 수 있을 것이다.
하지만 현재 이 함수는 numpy 배열을 입력으로 받을 수 없다.
"numpy 배열을 못받으면 따로따로 계산하면 되는 거 아닌가?" 싶을 수 있지만
컴퓨터 공학적으로 "캐시 히트 레이트 감소"와 같은 아쉬운 점이 있기 때문에, 이왕이면 numpy 배열을 받을 수 있는 계단 함수를 만들고 싶다.
그러므로 지금부터 간단한 트릭을 소개해볼까 한다.
우리는 numpy 배열에 그대로 부등식 연산을 수행하고, 이를 int 형으로 형변환을 수행했다.
이 코드는 이전 코드와 뭐가 다른걸까?
넘파이의 브로드캐스트
numpy에는 "브로드캐스트" 라는 기능이 있어, 스칼라 연산이 들어올 경우, 이 연산을 모든 원소에 적용하는 기능을 갖추고 있다.
그렇기 때문에 아래와 같이 numpy 배열을 선언하고,
다음과 같이 해당 배열에 스칼라 연산을 수행하면,
위와 같이 각 원소에 해당 스칼라 연산이 수행된 결과가 numpy 배열에 담겨 반환되게 된다.
거기에 프로그래밍 언어에서 True는 정수 1, False는 정수 0으로 대치된다는 특성을 적용시키면?
우리가 원하는 최종 신호값을 반환하는 적절한 값을 반환받을 수 있게 된다!
그렇게 우리는 위와 같은 형태로 계단 함수를 구현할 수 있었다.
계단 함수의 그래프
이제 앞에서 정의한 계단 함수를 matplotlib을 이용해 그래프로 그려보자.
그래프로 그려보면 다음과 같을 것이다.
위 계단 모양이 보이는가? 이것이 이 함수가 "계단 함수"라고 불리는 이유이다.
시그모이드 함수 구현하기
시그모이드 함수를 구현하면 다음과 같이 구현이 가능할 것이다.
해당 함수 또한 넘파이의 브로드캐스트 연산이 적용되기에, numpy의 배열을 바로 받을 수 있다.
그럼, 이제 시그모이드 함수의 그래프를 그려보자.
이번에도 matplotlib을 이용해 그려보도록 하겠다.
시그모이드 란 S자 형태를 이야기하고, 그래서 해당 함수를 "시그모이드 함수"라고 부른다.
시그모이드 함수와 계단 함수 비교
두 함수는 어떠한 공통점과 차이점이 있을까?
한번 곰곰이 생각해보자.
공통점
- 0에서 1 사이의 값을 반환한다.
- 둘 다 비선형 함수이다.
차이점
- 시그모이드는 (0, 1), 계단함수는 [0, 1] 값을 반환한다.
- 시그모이드 함수는 매끄럽고 미분 가능하다.
비선형 함수
위에서 비선형 함수라고 이야기했는데, 선형 함수와 비선형 함수에 대해 간단히 알아보자.
선형 함수
- y = ax + b 형태의 함수를 의미한다.
- 직선 하나로 그래프를 그릴 수 있는 함수이다.
비선형 함수
- y = ax + b 형태가 아닌 함수를 의미한다.
- 직선 하나로 그래프를 그릴 수 없는 함수이다.
신경망과 퍼셉트론의 결정적인 차이 - 비선형의 활성 함수
신경망에서는 활성화 함수로 비선형 함수를 사용해야 한다.
달리 말하면, 선형 함수를 사용해서는 안된다!
왜 선형 함수를 사용하면 안되는 걸까?
그 이유는 선형 함수를 사용하면 신경망의 층을 깊게 하는 의미가 없어지기 때문이다.
선형함수의 문제는 층을 아무리 깊게 해도 '은닉층이 없는 네트워크'로도 똑같은 기능을 수행할 수 있다는 데 있다.
간단한 예시를 통해 해당 문제를 살펴보자.
위와 같은 활성화 함수를 사용하는 3층 네트워크를 상상해보자.
이를 식으로 나타내면 아래와 같이 나올 것이다.
이 계산은 이렇게 변경될 것이다.
위 함수는 척 보기에는 3-layer 네트워크처럼 보이지만, 임의의 수이기 때문에 치환을 하면 다음과 똑같아진다.
따라서 3층 네트워크가 단층 네트워크, 즉 퍼셉트론으로 변하게 된다.
그렇기 때문에 층을 쌓는 혜택을 얻고 싶다면 활성화 함수로는 반드시 비선형 함수를 사용해야 한다.
ReLU 함수
지금까지 활성화 함수로 계단 함수와 시그모이드 함수를 학습했다.
하지만, 최근에는 은닉층의 활성화 함수로 위 두 함수보다 자주 쓰이는 ReLU(렐루) 함수가 각광받고 있다.
ReLU 함수는 다음과 같다.
수식으로 표현하면 다음과 같다.
코드로 표현하면 아래와 같을 것이다.
이 함수는 미분 시 "계단 함수 형태"가 되어 존재하지 않는 신호에 대한 역전파 시 학습 반영을 제거하는 기능을 갖고 있기 때문에 자주 쓰인다. 추후 역전파 학습 시 다시 다루도록 하겠다.
역전파 시 신호가 꺼지면 학습 데이터가 전부 소멸하는 것이 아쉬울 때 사용하는 Leaky ReLU 라는 활성화 함수도 존재하는데, 이는 지금 설명하기 어려우므로 이 또한 추후 Leaky ReLU 다루는 글에서 다시 다루도록 하겠다.
다차원 배열의 계산
넘파이의 다차원 배열을 사용한 계산법을 숙달하면 신경망을 효율적으로 구현할 수 있다.
그래서 이번 절에서는 넘파이의 다차원 배열 계산에 대해서 설명한 뒤 신경망을 구현해보겠다.
다차원 배열
우리는 지금까지 numpy의 다차원 배열을 이용해왔다.
numpy의 배열의 차원 수는 np.ndim() 메소드를 이용해 확인 가능하다.
또한, 배열의 형상은 인스턴스 변수인 shape로 알 수 있다.
위 배열 A의 경우, 1차원 배열이라는 것을 확인할 수 있었다.
또한, A.shape가 튜플을 반환한다는 점에 유의하자.
이는 1차원 배열이라도 다차원 배열일때와 동일한 형태로 결과를 반환하기 위함이다.
이어서 2차원 배열을 살펴보자.
위 배열 B의 경우, 2차원 배열이라는 것을 확인할 수 있었다.
또한 B.shape를 통해 3행 2열의 형태로 존재하는 것을 확인할 수 있었다.
numpy 행렬의 내적(행렬 곱)
numpy는 행렬의 내적 및 외적을 지원한다.
지금부터 numpy를 이용해 위 연산 - 행렬의 내적을 구해보자.
행렬-벡터 곱
np.dot()은 입력이 1차원 배열이면 벡터의 내적을, 2차원 배열이면 행렬 곱(내적)을 계산한다.
이 예시를 구현해보면 다음과 같다.
신경망 연산에 행렬 곱 도입하기
그럼 넘파이 행렬을 써서 신경만을 구현해보자.
아래와 같은 간단한 신경망을 가정한다.
이 신경망은 편향과 활성화 함수를 생략하고 가중치만 갖는다.
코드로 구현하면 다음과 같다.
이로써 Y의 원소가 100개든 1,000개든 한번의 연산으로 계산할 수 있게 되었다.
만약 np.dot을 사용하지 않으면 for문으로 Y의 원소를 하나씩 따져봐야 했을 것이다.
만약 그렇게 구현했다면, 가독성은 위 코드에 비해 장황했을 것이다.
3층 신경망 구현하기
이제 더 그럴싸한 신경망을 구현해보자.
지금부터 3층 신경망에서 수행되는, 입력부터 출력까지의 순방향 처리를 구현하겠다.
numpy 배열을 잘 사용하면 아주 적은 코드만으로 신경망의 순방향 처리를 완성할 수 있다.
표기법 설명
먼저 들어가기 전에, 표기법을 간단하게 설명하겠다.
위 그림은 입력층의 뉴런 x_2에서 다음층의 뉴런 a_1^{(1)}으로 향하는 선 위에 가중치를 표현하고 있다.
밑첨자에 들어있는 뉴런 순서의 경우, 이 순서를 반대로 표기하는 경우도 많으니, 다른 자료를 볼 때는 순서를 꼭 확인하자.
각 층의 신호 전달 구현하기
먼저 1층의 1번째 뉴런으로 가는 신호를 먼저 살펴보자.
아래 그림과 같은 상황이다.
이 내용을 수식으로 표현하면, 다음과 같을 것이다.
여기서 행렬의 내적을 이용하면 1층의 '가중치 부분'을 다음 식처럼 간소화할 수 있다.
여기서 각 원소는 다음과 같다.
그럼 넘파이의 다차원 배열을 이용해서 위를 구현해보자.
무사히 가중치 처리를 마칠 수 있었다.
이젠 1층의 활성화 함수의 처리를 수행해보자.
이 처리를 그림으로 나타내면 다음과 같을 것이다.
위 그림에서 사용된 활성화 함수를 시그모이드 함수로 가정하고 파이썬 코드를 작성하면, 다음과 같을 것이다.
이어서 1층에서 2층으로 가는 과정과 그 구현을 살펴보자.
훨씬 간단하게 구현할 수 있었다.
마지막으로 2층에서 출력층으로의 신호 전달을 살펴보자.
출력층의 구현도 그동안의 구현과 거의 같고, 활성화 함수만 지금까지의 은닉층과 다르다.
여기에서는 항등 함수인 identity_function()을 정의하고, 이를 출력층의 활성화 함수로 이용했다.
항등 함수는 입력을 그대로 출력하는 함수이다.
또한 그림에서는 출력층의 활성화 함수를 시그마(σ)로 표기하여 기존 은닉층의 활성화 함수와 다름을 명시했다.
일반적으로 출력층의 활성화 함수는 풀고자 하는 문제의 성질에 맞게 결정한다.
회귀에는 항등 함수를, 2클래스 분류에는 시그모이드 함수를, 다중 클래스 분류에는 소프트맥스 함수를 사용한다.
추후 더욱 자세히 알아보도록 하겠다.
구현 정리
여태까지 구현한 내용을 간단하게 정리해보겠다.
일반적으로 predict 시 사용하는 순전파 함수를 forward() 라고 정의하고, 학습 시 사용하는 역전파 함수를 backward() 라고 정의한다. 추후 더욱 자세히 알아보자.
출력층 설계하기
신경망은 분류와 회귀 모두에 이용할 수 있다.
다만 둘 중 어떤 문제냐에 따라 출력층에서 사용하는 활성화 함수가 달라진다.
일반적으로 회귀에는 항등 함수를, 분류에는 소프트맥스 함수를 사용한다.
항등 함수와 소프트맥스 함수 구현하기
항등 함수(identity function)는 입력을 그대로 출력한다.
입력과 출력이 같다는 뜻의 항등이다.
항등 함수에 의한 변환은 은니긍에서의 활성화 함수와 마찬가지로 화살표로 그린다.
한편, 다중 분류에 사용하는 소프트맥스 함수(softmax function) 의 식은 다음과 같다.
이 소프트맥스 함수를 그림으로 나타내면 다음과 같다.
그림과 같이, 소프트맥스의 출력은 모든 입력 신호로부터 화살표를 받는다.
식 분모에서 보듯, 출력층의 각 뉴런이 모든 입력 신호에서 영향을 받기 때문이다.
그럼 소프트맥스 함수를 간단하게 구현해보자.
잘 구현한 것 같다.
... 사실 치명적인 문제점이 있다.
그걸 지금부터 알아보자.
소프트맥스 함수 구현 시 주의점 - 오버플로우
앞에서 구현한 softmax() 함수의 코드는 소프트맥스를 잘 표현하고 있지만, 오버플로우 문제를 내포하고 있다.
파이썬이 다른 프로그래밍 언어에 비해 큰 수의 연산에 안전한 편이지만, 그래도 a_k 값이 1000만 넘어가도 무한대를 뜻하는 inf 를 반환한다.
그럼 이 오버플로우 문제를 해결하기 위한 우아한 수학적 테크닉을 알아보자.
위 식의 전개 과정을 순서대로 나열하면 다음과 같다.
- C라는 임의의 정수를 분자와 분모 양쫍에 곱했다.
- 그 다음으로 C를 e의 지수 안으로 옮겨 lnC로 만들었다.
- 마지막으로 lnC를 C'라는 새로운 기호로 치환했다.
위 식이 의미하는 바는, 소프트맥스의 지수 함수를 계산할 때 어떤 정수를 더해도(혹은 빼도) 결과는 바뀌지 않는다는 것이다.
따라서, 입력 신호 중 최댓값을 빼주면 정수 오버플로우를 막을 수 있게 된다.
이를 코드로 표현하면 다음과 같다.
소프트맥스 함수의 특징
위 그림을 자세히 살펴보면, 소프트맥스에는 중요한 특징이 2가지 있다.
- 소프트맥스 함수의 출력은 0에서 1 사이의 실수이다.
- 소프트맥스 함수 출력의 총합은 1이다.
이를 통해 우리는 소프트맥스의 출력을 '확률'로 해석할 수 있다.
가령 앞의 예시에서, y[1]의 확률은 24.51%, y[2]의 확률은 73.65%로 볼 수 있는 것이다.
이와 출력층의 뉴런 수를 적절히 정하면, 우리는 분류 문제를 훌륭히 풀 수 있다.
신경망을 이용한 분류에서는 일반적으로 가장 큰 출력을 내는 뉴런에 해당하는 클래스로만 인식한다. 그리고 소프트맥스 함수를 적용하든 적용하지 않든, 가장 큰 뉴런의 위치는 달라지지 않는다.
결과적으로 추론 과정에서 신경망으로 분류할 때는 출력층의 소프트맥스 함수를 생략해도 된다.
현업에서도 지수 함수 계산에 드는 자원 낭비를 줄이고자 출력층의 소프트맥스 함수는 생략하는 것이 일반적이다.
(위 예시는 '학습' 과정이 아닌 '추론'과정에 해당함에 유의하자.)
출력층의 뉴런 수 정하기
출력층의 뉴런 수는 풀려는 문제에 맞게 적절히 정해야 한다.
분류 문제에서는 분류하고 싶은 클래스 수로 설정하는 것이 일반적이다.
그림으로 표현하면 아래와 같다.
위 그림에선 가장 짙은 색(높은 값)을 띄는 y2 뉴런이 출력될 것이다. 즉 해당 신경망은 입력층의 그림을 숫자 2로 판단한 것이다.
손글씨 숫자 인식 - MNIST
신경망의 구조를 배웠으니, 실전 예에 적용해보자.
이번 절에서는 이미 학습된 매개변수를 사용하여 학습 과정은 생략하고, 추론 과정만 구현한다.
이 추론 과정을 신경망의 순전파(foward propagation)라고도 한다.
MNIST 데이터셋
MNIST 데이터셋은 "손글씨 숫자 이미지 집합"으로, 간단한 실험부터 논문으로 발표되는 연구까지 다양한 곳에서 이용하고 있는 데이터셋이다.
이미지 인식이나 머신러닝 논문들을 읽다 보면 실험용 데이터로 자주 등장하는 데이터셋이다.
MNIST 데이터셋의 구성은 다음과 같다.
- 0부터 9까지의 숫자 이미지로 구성되어 있다.
- 훈련 이미지 6만장과, 시험 이미지 1만장이 준비되어 있다.
- 28x28 크기의 회색조 이미지이며, 각 픽셀은 0에서 255까지의 값을 취한다.
밑바닥부터 시작하는 딥러닝 교재에서 MNIST 데이터셋을 내려받는 파이썬 코드가 존재하며, mnist.py 파일에 정의된 load_mnist() 함수를 이용하면 MNIST 데이터를 다음과 같이 쉽게 가져올 수 있다.
본 글은 책과 달리, colab 환경에서의 MNIST 숫자 인식을 다룹니다.
먼저 wget을 통해 해당 파이썬 소스코드를 다운받았다.
이후 다운받은 해당 소스 파일이 어디있는지 "!()" 형태의 리눅스 명령어를 입력한 결과, 현재 코랩이 보고 있는 디렉터리에 그대로 존재함을 알 수 있었다.
그 다음으로는 현재 파이썬 노트북이 보고 있는 현재 디렉터리가 어딘지 확인해보았는데, 둘이 일치함을 확인할 수 있었다.
따라서 아래와 같이 sys.path에 현재 디렉토리를 추가하여 mnist.py를 import할 수 있도록 설정을 추가해주고, 해당 load_mnist() 코드를 실행해주었다.
제공된 mnist.py 파일 내의 load_mnist 함수는 읽은 MNIST 데이터를 "(훈련 이미지, 훈련 레이블), (시험 이미지, 시험 레이블)" 형식으로 변환한다
작가가 파이썬 docstring으로 각 매개변수에 대한 설명을 잘 해두었으므로, 해당 내용으로 갈음한다.
그럼 데이터도 확인할 겸 MNIST 이미지를 화면으로 불러보자.
이미지 표시에는 PIL(Python Image Library) 모듈과 IPython(Interactive Python) 모듈을 이용했다.
flatten=True 로 설정했기 때문에, 데이터가 평탄화되어 1차원 배열로 저장되어 있었다.
따라서 img.reshape() 메소드를 이용해 28x28 2차원 넘파이 배열 형태로 바꾸어주었다.
신경망의 추론 처리
드디어 이 MNIST 데이터셋을 가지고 추론을 수행하는 신경망을 구현해보자.
이 신경망에선 뉴런을 다음과 같이 구성한다.
- 입력층 뉴런 = 28x28 = 784개
- 첫 번째 은닉층 뉴런 = 50개
- 두 번째 은닉층 뉴런 = 100개
- 출력층 뉴런 = 10개 (0~9까지의 숫자)
이제 순서대로 작업을 처리해줄 함수인 get_data(), init_network(), predict() 함수를 정의해보자.
init_network()에서는 pickle 파일인 sample_weight.pkl을 다운받고, 그 안에 저장된 '학습된 가중치 매개변수'를 읽는다.
해당 파일에는 가중치와 편향 매개변수와 딕셔너리 변수로 저장되어 있다.
그럼 위 세 함수를 사용해 신경망에 의한 추론을 수행해보고, 정확도(accuracy, 분류가 얼마나 올바른가)도 평가해보자.
해당 코드는 모든 테스트 데이터에 대하여 하나씩 순회하면서, 일치할 때마다 accuracy_cnt를 1씩 증가시키며 정답을 맞춘 데이터의 수를 저장했다.
이후 (정답을 맞춘 개수 / 전체 데이터 개수)로 정확도를 계산하였다.
또한 이 예에선 load_mnist 함수의 인수인 normalize를 True로 설정했다.
normalize를 True로 설정하면 0~255 범위인 각 픽셀의 값을 0~1 범위로 변환하도록 동작한다.
이처럼 데이터를 특정 범위로 변환하는 처리를 정규화라고 하고, 신경망의 입력 데이터에 특정 변환을 가하는 작업을 전처리라고 한다.
여기에서는 입력 이미지 데이터에 대한 전처리 작업으로 정규화를 수행한 셈이다.
배치 처리 - 한걸음 더
이 연산 속도를 좀 더 "컴퓨터 공학"적으로 효율적으로 만들 수 있는 방법이 없을까?
현재 시스템은 실시간 처리 시스템이 아니기 때문에, 배치 처리로 효율화를 할 수 있을 것 같다.
여기서 배치 처리를 하게 되면, 디스크 I/O 작업을 연속적으로 하게 되어 CPU가 컨텍스트 스위칭보다 연산에 집중할 수 있게 된다.
현재 신경망의 각 층의 배열 형상을 살펴보자.
하나의 이미지에 784개의 원소를 담은 데이터를 담으면, 10개의 값을 반환한다.
이를 행렬을 이용해 한꺼번에 처리할 수 없을까?
다음과 같은 아이디어가 가능하다.
입력부터 100개의 이미지를 한꺼번에 입력해 predict 함수에 넣어도, 행렬 연산 특성 상 100x10 형태의 행렬이 반환될 뿐, 결과의 가치 자체는 달라지지 않는다.
이를 이용해 현재 주어진 연산을 최적화해보자.
먼저, 기존의 실행시간을 측정해보자.
약 0.5초가 걸렸다.
이번엔 배치 처리로, batch_size를 100으로 두고 실행 시간을 측정해보자.
0.15초로, 약 3배가 개선된 것을 확인할 수 있었다.
이번 장에서 배운 것
드디어 "학습"을 위한 신경망을 학습했다.
이번 장에선 무엇을 배웠는지 되돌아보며 마무리 지어보자.
- 신경망에서는 활성화 함수로 시그모이드 함수와 ReLU 함수 같은 매끄럽게 변화하는 함수를 이용한다.
- 넘파이의 다차원 배열을 잘 사용하면 신경망을 효율적으로 구현할 수 있다.
- 기계학습 문제는 크게 회귀와 분류로 나눌 수 있다.
- 출력층의 활성화 함수로는 회귀에서는 주로 항등 함수를, 분류에서는 주로 소프트맥스 함수를 이용한다.
- 분류에서는 출력층의 뉴런 수로 분류하려는 클래스 수와 같게 설정한다.
- 입력 데이터를 묶은 것을 배치(batch)라 하며, 추론 처리를 이 배치 단위로 진행하면 결과를 훨씬 빠르게 얻을 수 있다.
'CS Repository > 기초 딥러닝' 카테고리의 다른 글
[밑바닥부터 시작하는 딥러닝] 퍼셉트론 (4) | 2025.08.05 |
---|