Post

[AI Day 15] nn.Linear의 여러 출력과 뉴런, Shape 이해하기

[AI Day 15] nn.Linear의 여러 출력과 뉴런, Shape 이해하기

[AI 수학] Day 15 - nn.Linear의 여러 출력과 뉴런, Shape 이해하기

들어가며

지난 시간에는 입력 Feature가 여러 개인 선형 모델을 공부했다.

입력이 두 개이고 출력이 하나라면

1
model = nn.Linear(2, 1)

을 사용했다.

내부 계산은 다음과 같다.

1
ŷ = w₁x₁ + w₂x₂ + b

이번에는 여기서 한 단계 더 나아가 출력도 여러 개로 만들어보았다.

1
model = nn.Linear(2, 3)

이를 통해 Weight와 Bias가 몇 개 필요한지 알아보고, nn.Linear가 뉴런과 Layer의 구조로 어떻게 연결되는지도 살펴보았다.

특히 처음 헷갈렸던

1
2
3
4
5
입력 Shape [4, 2]
        ↓
nn.Linear(2, 3)
        ↓
출력 Shape [4, 3]

이 왜 성립하는지도 정리해보았다.


1. nn.Linear(2, 3)의 의미

nn.Linear의 기본적인 형태는 다음과 같다.

1
nn.Linear(입력 Feature 수, 출력 Feature 수)

따라서

1
model = nn.Linear(2, 3)

은

1
2
입력 Feature = 2개
출력 Feature = 3개

라는 뜻이다.

즉, 하나의 데이터에 값 2개를 넣으면 결과로 값 3개를 만들어준다.

1
2
3
4
5
6
7
8
9
[x₁, x₂]

    ↓

nn.Linear(2, 3)

    ↓

[y₁, y₂, y₃]

2. 출력 하나였을 때

먼저 이전에 사용했던

1
nn.Linear(2, 1)

을 다시 생각해보자.

입력이

1
2
x₁
x₂

두 개라면 하나의 출력은

1
y = w₁x₁ + w₂x₂ + b

로 계산할 수 있다.

예를 들어

1
2
3
4
5
6
7
x₁ = 2
x₂ = 3

w₁ = 4
w₂ = 5

b = 1

이라면

1
2
3
y = 4×2 + 5×3 + 1
  = 8 + 15 + 1
  = 24

가 된다.

따라서 출력 하나를 만들기 위해서는

1
2
Weight 2개
Bias 1개

가 필요하다.


3. 출력이 3개라면?

이번에는 출력이 세 개이다.

1
2
3
y₁
y₂
y₃

각각의 출력은 서로 다른 계산 결과를 만들어야 한다.

따라서 각 출력마다 자신의 Weight와 Bias가 필요하다.

첫 번째 출력:

1
y₁ = w₁₁x₁ + w₁₂x₂ + b₁

두 번째 출력:

1
y₂ = w₂₁x₁ + w₂₂x₂ + b₂

세 번째 출력:

1
y₃ = w₃₁x₁ + w₃₂x₂ + b₃

즉 하나의 입력

1
[x₁, x₂]

에 대해 세 번의 서로 다른 선형 계산이 이루어진다.


4. Weight와 Bias는 몇 개일까?

nn.Linear(2, 3)에서는

1
2
입력 = 2개
출력 = 3개

이다.

출력 하나를 만들 때 Weight가 2개 필요하다.

출력이 3개이므로

1
2
Weight = 2 × 3
       = 6개

가 필요하다.

Bias는 출력마다 하나씩 필요하므로

1
Bias = 3개

이다.

따라서 전체 학습 Parameter는

1
2
3
4
5
Weight 6개
+
Bias 3개
=
총 9개

가 된다.

일반화하면

1
nn.Linear(A, B)

의 Parameter 개수는

1
2
3
4
Weight = A × B
Bias   = B

전체 Parameter = A×B + B

이다.

예를 들어

1
nn.Linear(10, 5)

라면

1
2
3
4
Weight = 10 × 5 = 50
Bias   = 5

전체 = 55개

의 Parameter를 가진다.


5. Weight의 Shape

다음 모델을 만들어보자.

1
model = nn.Linear(2, 3)

그리고 Weight의 Shape을 확인한다.

1
print(model.weight.shape)

결과는

1
torch.Size([3, 2])

이다.

처음에는 조금 이상하게 느껴졌다.

모델은

1
nn.Linear(2, 3)

인데 Weight Shape은

1
[3, 2]

이기 때문이다.

PyTorch의 Linear Weight는

1
[출력 Feature 수, 입력 Feature 수]

형태로 저장된다.

따라서

1
2
3
4
nn.Linear(2, 3)

입력 = 2
출력 = 3

이면

1
Weight Shape = [3, 2]

가 된다.

Weight를 구조적으로 보면 다음과 같다.

1
2
3
4
5
[
    [w₁₁, w₁₂],
    [w₂₁, w₂₂],
    [w₃₁, w₃₂]
]

각 줄이 하나의 출력을 만들기 위한 Weight라고 생각할 수 있다.


6. Bias의 Shape

Bias도 확인할 수 있다.

1
print(model.bias.shape)

결과는

1
torch.Size([3])

이다.

출력이 3개이므로 각각의 출력에

1
2
3
b₁
b₂
b₃

가 필요하기 때문이다.

따라서

1
2
3
4
nn.Linear(2, 3)

Weight Shape = [3, 2]
Bias Shape   = [3]

이 된다.


7. 가장 헷갈렸던 Shape 이해하기

이번 학습에서 가장 헷갈렸던 부분은 다음 내용이었다.

1
2
3
4
5
6
7
8
9
입력 Shape = [4, 2]

        ↓

nn.Linear(2, 3)

        ↓

출력 Shape = [4, 3]

왜 [4, 3]이 되는지 하나씩 살펴보자.

먼저 다음과 같은 입력 데이터가 있다고 하자.

1
2
3
4
5
6
x = torch.tensor([
    [1.0, 2.0],
    [3.0, 4.0],
    [5.0, 6.0],
    [7.0, 8.0]
])

데이터를 나누어보면

1
2
3
4
[1, 2] → 첫 번째 데이터
[3, 4] → 두 번째 데이터
[5, 6] → 세 번째 데이터
[7, 8] → 네 번째 데이터

이다.

따라서

1
2
데이터 = 4개
각 데이터의 Feature = 2개

이다.

Shape은

1
2
3
4
5
[4, 2]
 ↑  ↑
 │  └── 각 데이터의 Feature 개수
 │
 └───── 데이터 개수

가 된다.


8. nn.Linear가 바꾸는 것은 무엇일까?

모델은

1
model = nn.Linear(2, 3)

이다.

이 모델은 각각의 데이터에 들어 있는 값 2개를 받아 출력값 3개를 만든다.

예를 들어 첫 번째 데이터

1
[1, 2]

를 넣으면

1
2
3
4
5
6
7
8
9
[1, 2]

   ↓

nn.Linear(2, 3)

   ↓

[출력1, 출력2, 출력3]

가 된다.

즉,

1
2
3
2개의 Feature
↓
3개의 출력 Feature

로 변한다.

하지만 데이터 자체가 사라지거나 늘어나는 것은 아니다.

원래 데이터가 4개였으므로 네 개 모두 각각 변환된다.

1
2
3
4
5
6
7
[1, 2] → [?, ?, ?]

[3, 4] → [?, ?, ?]

[5, 6] → [?, ?, ?]

[7, 8] → [?, ?, ?]

따라서 최종 결과는

1
2
3
4
5
6
[
    [?, ?, ?],
    [?, ?, ?],
    [?, ?, ?],
    [?, ?, ?]
]

형태가 된다.

4개의 데이터가 있고 각 데이터마다 출력값이 3개이므로

1
Shape = [4, 3]

이 되는 것이다.


9. [4, 2] → [4, 3]

이 부분을 한 번 더 정리하면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
입력

[
 [1, 2],
 [3, 4],
 [5, 6],
 [7, 8]
]

Shape = [4, 2]
         ↑  ↑
         │  └─ Feature 2개
         └──── 데이터 4개

           ↓

     nn.Linear(2, 3)

           ↓

출력

[
 [?, ?, ?],
 [?, ?, ?],
 [?, ?, ?],
 [?, ?, ?]
]

Shape = [4, 3]
         ↑  ↑
         │  └─ 출력 Feature 3개
         └──── 데이터 4개

여기서 중요한 것은 4는 nn.Linear가 만든 숫자가 아니라는 것이다.

원래 데이터가 4개였기 때문에 그대로 유지된다.

nn.Linear(2, 3)이 바꾸는 것은 각 데이터의 Feature 크기이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
각 데이터

[?, ?]

2개
 ↓

nn.Linear(2, 3)

 ↓

[?, ?, ?]

3개

따라서

1
2
3
4
5
[4, 2]
   ↓
nn.Linear(2, 3)
   ↓
[4, 3]

이 된다.


10. Batch Size

여기서 Shape의 첫 번째 숫자

1
2
3
[4, 2]
 ↑
 4

는 한 번에 처리하고 있는 데이터의 개수이다.

이러한 데이터 묶음을 Batch라고 하고, 데이터 개수를 Batch Size라고 한다.

현재는 데이터가 4개이므로

1
Batch Size = 4

이다.

따라서 일반적으로 입력 Shape을

1
[Batch Size, Feature 수]

라고 생각할 수 있다.

현재는

1
[4, 2]

이고

1
nn.Linear(2, 3)

을 통과하면

1
[4, 3]

이 된다.

즉,

1
2
3
4
5
[Batch Size, 입력 Feature]
             ↓
       nn.Linear(2, 3)
             ↓
[Batch Size, 출력 Feature]

가 된다.


11. 뉴런과 연결하기

nn.Linear(2, 3)을 다른 관점에서 생각해보면 출력 하나를 만드는 계산이 세 개 존재한다.

출력 하나의 계산은

1
y = w₁x₁ + w₂x₂ + b

이다.

이를 하나의 뉴런처럼 생각할 수 있다.

따라서

1
nn.Linear(2, 3)

은 간단하게

1
입력 2개를 받는 뉴런 3개

가 있다고 생각할 수 있다.

구조를 나타내면

1
2
3
        ┌─ 뉴런 1 → y₁
x₁ ─────┼─ 뉴런 2 → y₂
x₂ ─────┴─ 뉴런 3 → y₃

와 같은 형태가 된다.

각 뉴런은 자신만의 Weight와 Bias를 가진다.


12. Layer란?

여러 뉴런이 모여 있는 하나의 계산 단위를 Layer(층)라고 생각할 수 있다.

1
layer = nn.Linear(2, 3)

은

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
입력 2개

x₁  x₂
 ↓   ↓

┌─────────────┐
│ Linear Layer│
│             │
│ 뉴런 1      │
│ 뉴런 2      │
│ 뉴런 3      │
└─────────────┘

 ↓   ↓   ↓

y₁  y₂  y₃

와 같은 구조로 생각할 수 있다.

그리고 이러한 Layer를 여러 개 연결하면 신경망의 형태가 만들어지기 시작한다.

예를 들어

1
2
layer1 = nn.Linear(2, 3)
layer2 = nn.Linear(3, 1)

이라면

1
2
3
4
5
6
7
8
9
입력 2개
↓
Linear(2, 3)
↓
중간 출력 3개
↓
Linear(3, 1)
↓
최종 출력 1개

와 같은 구조가 된다.


13. 실제 코드로 Shape 확인하기

오늘 배운 내용을 직접 확인하기 위한 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import torch
import torch.nn as nn

# =========================
# 1. Linear Layer
# =========================

model = nn.Linear(2, 3)

# =========================
# 2. Weight와 Bias
# =========================

print("===== Weight =====")
print(model.weight)

print("\nWeight Shape:")
print(model.weight.shape)

print("\n===== Bias =====")
print(model.bias)

print("\nBias Shape:")
print(model.bias.shape)

# =========================
# 3. 입력 데이터
# =========================

x = torch.tensor([
    [1.0, 2.0],
    [3.0, 4.0],
    [5.0, 6.0],
    [7.0, 8.0]
])

print("\n===== Input =====")
print(x)

print("\nInput Shape:")
print(x.shape)

# =========================
# 4. Forward
# =========================

y = model(x)

print("\n===== Output =====")
print(y)

print("\nOutput Shape:")
print(y.shape)

# =========================
# 5. Parameter 개수
# =========================

weight_count = model.weight.numel()
bias_count = model.bias.numel()

print("\n===== Parameter =====")
print("Weight 개수:", weight_count)
print("Bias 개수:", bias_count)
print("전체 Parameter 개수:", weight_count + bias_count)

실행하면 실제 Weight와 Bias의 숫자는 랜덤하게 초기화되기 때문에 달라질 수 있다.

하지만 Shape은 다음과 같다.

1
2
3
4
5
6
7
Weight Shape = [3, 2]

Bias Shape = [3]

Input Shape = [4, 2]

Output Shape = [4, 3]

Parameter 개수는

1
2
3
4
Weight = 6개
Bias = 3개

전체 = 9개

이다.


오늘 배운 내용 정리

오늘은 nn.Linear에서 출력 Feature가 여러 개인 경우를 공부했다.

가장 중요한 내용은 다음과 같다.

1
2
3
4
nn.Linear(2, 3)

입력 Feature = 2
출력 Feature = 3

출력 하나마다 입력 Feature 각각에 대한 Weight가 필요하기 때문에

1
2
3
4
Weight = 2 × 3 = 6개
Bias = 3개

전체 Parameter = 9개

가 된다.

PyTorch에서 Weight Shape은

1
[출력 Feature, 입력 Feature]

순서이므로

1
2
3
4
5
nn.Linear(2, 3)

↓

Weight Shape = [3, 2]

이다.

그리고 데이터가 4개라면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
입력 Shape

[4, 2]
 ↑  ↑
 │  └─ 입력 Feature
 └──── Batch Size

       ↓

nn.Linear(2, 3)

       ↓

출력 Shape

[4, 3]
 ↑  ↑
 │  └─ 출력 Feature
 └──── Batch Size

가 된다.

nn.Linear는 데이터의 개수를 바꾸는 것이 아니라 각 데이터의 Feature를 변환한다는 점이 오늘 가장 중요했다.


마무리

처음에는

1
nn.Linear(2, 3)

이라는 코드가 단순히 숫자 두 개를 전달하는 것처럼 보였다.

하지만 내부를 살펴보면

1
2
3
4
5
6
7
8
9
입력 Feature
↓
Weight와 Bias
↓
여러 개의 출력
↓
뉴런
↓
Layer

라는 구조로 이어진다는 것을 알 수 있었다.

특히 Tensor의 Shape도 단순히 외우기보다는

1
[데이터 개수, Feature 개수]

라고 해석하면 이해하기 쉽다.

현재까지의 흐름을 정리하면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
미분
↓
편미분
↓
Gradient
↓
Gradient Descent
↓
Backpropagation
↓
Autograd
↓
Weight / Bias
↓
선형회귀
↓
nn.Linear
↓
여러 Feature
↓
여러 출력
↓
뉴런과 Layer

까지 연결되었다.

다음에는 Linear Layer를 여러 개 연결했을 때 발생하는 문제와 이를 해결하기 위해 사용되는 활성화 함수(Activation Function)에 대해 공부해볼 예정이다.

This post is licensed under CC BY 4.0 by the author.