Skip to main content

Keras Dense 레이어: 정확하게 사용하는 방법

이 글에서는 Keras의 Dense 레이어를 살펴봄으로써 Keras에서 사용자 지정 모델을 구축할 때 중요한 내용을 완벽하게 이해할 수 있게 합니다.
Created on March 28|Last edited on May 30
KerasDense 레이어는 오래되고 훌륭한 완전하고/밀도있게 연결된 신경망입니다. 이보다 나은 것은 없습니다! 하지만, Keras에서 사용자 지정 모델을 구축하는 동안 이것을 완전하게 이해하는 데에는 오랜 시간이 걸립니다.
이 글에서 Dense 레이어란 무엇이고 실제 어떻게 작동하는지에 대해 알아봄으로써 필요한 모든 것을 갖출 수 있게 됩니다.
이 글에서 다룰 내용은 다음과 같습니다:


목차



Dense 레이어란?

머신 러닝에서, 완전하게 연결된 레이어는 각 입력 기능을 레이어 내 각 뉴런에 연결시킵니다. Dense 레이어는 기능 추출 블록(합성곱 또는 인코더, 디코더 등), 출력 레이어(최종 레이어) 뒤의 끝에서 두 번째 레이어로, 또한 차원 d0의 벡터를 새로운 차원 d1로 투사하는 데 가장 많이 사용됩니다.
1D 입력 기능에 대해 살펴보겠습니다:
이 입력은 4개의 뉴런으로 완전하게 연결된 레이어를 사용하여 처리됩니다(우리는 단순화를 위해 선형성과 편향은 무시합니다). 예를 들어 3 * 4 = 12를 얻기 위해 몇 개를 연결해야 합니까? 아래 그림에서 보듯, 1D 특징 공간 내 각 값에 4 가중치 벡터(색상 선으로 표시됨)를 곱합니다.

그림1: 완전 연결 신경망.

코드를 보여주세요

우리는 Keras를 사용하여 그림 1에서 보는 완전 연결 신경망을 실행할 것입니다. Dense 레이어는 아래에서 보듯, 이를 달성하기 위해 사용할 수 있습니다:
import tensorflow as tf
from tensorflow.keras import layers

# batch_size가 1인 무작위 입력 특징 모양을 만듭니다.
inputs = tf.random.uniform(shape=(1,3)) # (batch_size, num_features)

# 완전하게 연결된 레이어를 초기화합니다(단순화를 위해 편향을 사용하지 않을 것입니다).
dense_layer = layers.Dense(unit=4, use_bias=False)

# 출력으로 무엇을 기대할 수 있나요?
print(dense_layer(inputs))
>>> tf.Tensor([[-0.07313907 0.31886736 -0.83136106 0.47411117]], shape=(1, 4), dtype=float32)
모양 (1,3)의 입력에 대해, 4개의 뉴런이 완전하게 연결된 레이어 안에 있기 때문에 모양 (1,4)의 출력을 얻습니다. 그림 1에서, 12개의 선은 무게를 나타내기 위해 그려졌습니다. 완전하게 연결된 레이어의 시작 무게를 검사해 봅시다:
print(dense_layer.weights)
>>> [<tf.Variable 'dense_1/kernel:0' shape=(3, 4) dtype=float32, numpy=
array([[-0.18026423, 0.8457761 , 0.20618927, 0.34542954],
[-0.68638337, -0.09881872, -0.891773 , 0.7983763 ],
[ 0.84850013, -0.5968083 , -0.522443 , -0.62690055]],
dtype=float32)>]
예상했듯이, 이것은 모양의 척도입니다 (3, 4) (3*4 = 12).

N차원 입력은 어떠세요?

이제 모양(time/num_frame/arbitrary_feature, features)의 입력을 가지고 있다고 가정해 봅니다. 이 입력은 시퀀스(시계열 또는 비디오) 또는 임의적 특징 공간일 수 있습니다. 사용 사례에 따라, 이 입력을 완전하게 연결된 레이어를 통해 복수의 방식으로 전달(또는 처리)할 수 있습니다. 이상한 상황에 빠지기 전에 몇 가지 고려해 봅시다.

1. 이는 임의적 특징 공간입니다

입력은 모양(arbitrary_feature, features)의 임의 특징 공간일 수 있습니다. 입력 평탄화를 고려하고 실험할 수 있습니다. Keras에서 입력 평탄은 어떤 모습일까요? 같이 찾아 보겠습니다!

1.1 입력 평탄화

Keras에서 모든 입력을 1D 벡터로 평탄화하기 위해 Flatten() 레이어를 사용할 수 있습니다. 이 레이어는 예를 들어, 입력에 배치 크기가 32인 모양 (32, 2, 3)이 있으면, 배치 차원을 따라 평탄화되지 않습니다. 평탄 작업은 모양(32, 6)의 벡터를 제공할 것입니다.
# 1이 배치 크기인 모양(1, 2, 3)의 상수 입력을 초기화합니다.
inputs = tf.constant(
[[[1, 1, 1], [2, 2, 2]]]
)
# 평탄 레이어 초기화
flatten = layers.Flatten()
# 출력으로 무엇을 기대할 수 있나요?
outputs = flatten(inputs)
print(outputs)
>>> tf.Tensor([[1 1 1 2 2 2]], shape=(1, 6), dtype=int32)
배치 크기가 1인 모양이 평탄화된 벡터 (1, 6) 를 얻습니다. 이 레이어는 심층 신경망 내 특징 추출 블럭 다음에 일반적으로 사용됩니다. 이것은 또한 arbitrary_feature 차원이 독립적인 경우, 사용하기에 유효하며, 시퀀스의 일부가 아닌 features를 고려할 수 있습니다. 이것을 Dense 레이어를 통해 전달해 보겠습니다:
dense_layer = layers.Dense(
units=4, # 이 ���전하게 연결된 레이어 안에 4개의 뉴런이 있습니다.
use_bias=False,
kernel_initializer=tf.keras.initializers.Constant(value=0.5) # 가중치는 상수값 0.5로 초기화됩니다.
)

# 출력으로 무엇을 기대할 수 있나요?
print(dense_layer(outputs))
>>> tf.Tensor([[4.5 4.5 4.5 4.5]], shape=(1, 4), dtype=float32)
이 값을 어떻게 얻었습니까? 완전하게 연결된 레이어 다음, 각 출력 값 (4.5)은 1*0.5 + 1*0.5 + 1*0.5 + 2*0.5 + 2*0.5 + 2*0.5 = 4.5와 같이 계산됩니다.

2. 이것은 시퀀스입니다

이제, 모양 입력(시간, 특징)을 고려해 봅니다. 여기 각 기능시간 축을 따라 종속되며, 평평한 벡터는 이 종속성을 잃습니다. 우리가 특징 의 차원을 새 차원으로 투영하는 시나리오를 생각해 보십시오. 이렇게 하기 위해 Keras Dense() 레이어를 사용할 수 있나요?
문서에 의거하여:
레이어에 입력에 2보다 큰 순위가 있으면, Dense는 입력의 마지막 축 및 커널의 0축을 따라 입력과 커널 사이의 도트 곱을 계산합니다. 예를 들어, 입력에 차원(batch_size, d0, d1)이 있다면, 우리는 모양 (d1, units)으로 커널을 만들고, 커널은 모양 (1, 1, d1)의 모든 하위 tensor에서 입력의 2축을 따라 작동합니다(batch_size * d0의 하위 tensor가 있습니다). 이 사례에서 출력은 모양(batch_size, d0, 단위)을 갖게 됩니다.
이것을 나눠 코드로 각각의 이동 부분를 이해해 보겠습니다.
# 입력은 배치 크기는 1이고 시간 축은 2인 모양(1, 2, 3)입니다.
inputs = tf.constant(
[[[1, 1, 1], [2, 2, 2]]]
)

# 입력의 순위가 2 이상입니까?
print(tf.rank(inputs))
>>> <tf.Tensor: shape=(), dtype=int32, numpy=3>
Dense 레이어를 사용하여 3부터 4까지 마지막 차원을 투영해 보도록 하겠습니다.
# Dense 레이어를 4개의 출력 뉴런과 상수 가중치 0.5로 초기화합니다.
dense_layer = layers.Dense(
units=4,
use_bias=False,
kernel_initializer=tf.keras.initializers.Constant(value=0.5)
)

# 예측되는 출력은 무엇이어야 합니까?
print(dense_layer(inputs))
>>> tf.Tensor(
[[[1.5 1.5 1.5 1.5]
[3. 3. 3. 3. ]]], shape=(1, 2, 4), dtype=float32)
보시다시피, Dense 레이어는 모양 (1, 2, 3) 에서 (1, 2, 4)까지의 입력을 투영합니다. 이 계산때문에 1.5의 출력값을 얻었습니다: 1*0.5 + 1*0.5 + 1*0.5 = 1.5. 이와 유사하게, 이 계산때문에 3.0의 출력값을 얻었습니다: 2*0.5 + 2*0.5 + 2*0.5 = 3. 문서화에 따라, 비중 척도의 모양은 (3, 4)이어야 합니다.
print(dense_layer.weights)
>>> [<tf.Variable 'dense_5/kernel:0' shape=(3, 4) dtype=float32, numpy=
array([[0.5, 0.5, 0.5, 0.5],
[0.5, 0.5, 0.5, 0.5],
[0.5, 0.5, 0.5, 0.5]], dtype=float32)>]

2.1 TimeDistributed 레이어

우리는 우리의 (time, features) 입력을 다양한 차원에 투영하는 관점에서 보았지만, Dense 작업(도트 곱)을 각 기능에 순차적으로 적용하기를 원하면 어떻게 될까요?
이 논의에 좀 더 많은 뉘앙스를 추가하려면, 모양 (num_frames, height, width, 3)의 비디오 데이터 샘플을 상상해 보십시오. 사전에 학습된 이미지 모델을 사용하여 각 프레임에서 순차적으로 정보를 추출하고 싶어할 겁니다. TimeDistributed 레이어를 통해 레이어(여기에서 추출기 기능)를 입력(여기서 비디오)의 모든 일시적 슬라이스(여기서 프레임)에 적용할 수 있습니다.
Dense 연산을 모양 (time, features)의 입력에 어떻게 적용할 수 있는지를 살펴보겠습니다. 이전의 예에서 inputs를 사용하겠습니다:
# 이 dense 레이어는 매 ‘시간’마다 적용됩니다.
dense_layer = layers.Dense(
units=4,
use_bias=False,
kernel_initializer=tf.keras.initializers.Constant(value=0.5)
)
# `TimeDistributed` 레이어를 초기화하세요.
timedistributed = layers.TimeDistributed(dense_layer)
# 예측되는 출력은 무엇이어야 합니까?
print(timedistributed(inputs))
>>> tf.Tensor(
[[[1.5 1.5 1.5 1.5]
[3. 3. 3. 3. ]]], shape=(1, 2, 4), dtype=float32)
이 출력이 이전 섹션의 것과 동일하지 않습니까? 이를 통해 TimeDistributed(Dense(...))Dense(...)가 이 시나리오에서 서로 동일해집니다.

실습

좋아요! 이제 Keras Dense 레이어를 정확하게 사용하는 법을 알았습니다. 그러면 이 레이어로 실험하여 더 자세히 알아보도록 하겠습니다. 단순하게 한다음 Scikit Learn의 make_classification을 이용하여 만든 합성 데이터 집합을 이용해보겠습니다. 데이터 집합에는 10가지의 기능과 10가지의 클래스가 있게 됩니다. 이 기능들은 신경망이 어떠한 도움 없이도 기능을 얼마나 잘 추출할 수 있는 지를 보기 위해 정상화되지 않습니다.
데이터 집합은 10000가지의 샘플로 일정하게 유지됩니다. 우리의 모델을 배치 크기 256으로 100개의 에포크에 대해 학습시키겠습니다.


Colab 메모장을 확인하세요 \rightarrow

완전 연결 신경망은 다음과 같아 보입니다:
def MLPModel():
inputs = layers.Input(shape=(10,))
x = layers.Dense(configs["hidden_units"], activation="relu")(inputs)
outputs = layers.Dense(10, activation="softmax")(x)

return models.Model(inputs, outputs)

파라미터의 효과 - units

Dense 레이어의 첫 번째 인수는 해당 레이어의 뉴런 수를 제어할 수 있는 units입니다. 단위의 수는 10, 100 또는 1000이 됩니다. 아래 패널은 다른 단위를 사용한 결과를 보여줍니다.
명확하게, 파라미터(units)의 수를 늘림으로써 모델이 개선됩니다.

Run set
3

학습과 검증 손실을 살펴보면, 단위가 1000인 것은 빠르게 과적합되는 반면, 10개의 뉴런만 있는 것은 아직 발산을 시작하지도 않았습니다.

Run set
3


커널 정규화의 효과

해당 모델의 역량을 낮추지 않고도 이 과적합을 계산할 수 있을까요? 커널 정규화를 들어가면, 최적화 중에 레이어 파라미터 페널티를 적용할 수 있게 됩니다. 세부 사항은 살펴보지 않으며 동작의 효과를 보겠습니다.
어떠한 정규화없이 1000개의 숨겨진 단위로 학습한 모델과 동일한 구성이지만 kernel_regularization으로 학습한 모델을 비교할 것입니다. l2 인수를 취한L2 정규화 페널티를 사용합니다. 이는 [0-1] 범위의 플로팅 값입니다. 0에 가까운 값은 정규화가 없다는 의미입니다.
이전 섹션에서, 정규화가 없이 학습된 모델은 금방 과적합되기 시작했음을 확인했습니다. 다른 l2 값과 전체적 정규화의 효과를 보겠습니다.

Run set
4

no-reg 실행에 대해 lossval_loss 사이의 발산에 주목하십시오. 과적합을 나타내는 가장 큰 발산이 있는 반면, reg-0.1 실행은 가장 적게 발산했습니다. 정규화는 모델의 최종 정확도에 어떻게 영향을 미칠까요?

Run set
4

l2 값이 0.1인 L2 정규화는 비록 이것이 과적합의 카운터에 도움을 주었지만, 모델이 val_acc의 동일한 수준을 달성하는 데는 도움을 주지 못했습니다. 하지만, 따라서 더 오랜 시간 동안 정규화된 모델을 학습시킬 수 있습니다. 실제, 우리는 보통 l2 값 1e-3으로 정규화합니다.

결론

이 짧은 리포트는 완전하게 연결된 레이어가 Keras를 사용하여 어떻게 초기화되고 생성될 수 있는지를 보여줍니다. 또한 그 사용을 더 잘 이해하기 위해 비교하는 연구를 일부 수행했습니다.
Dense 레이어는 딥 러닝에서 기본 레이어이며 보통 어텐션 레이어, MLP 블록, 프로젝터 등에 사용됩니다. dense를 연결하면, 파라미터의 수는 높아지므로, 이미지와 같이 고차원적인 입력을 직접 처리하는 데에는 이상적이지 않습니다.
더 복잡한 사용 사례가 있다면, EinsumDense 레이어를 대신 사용할 것을 고려해 보십시오. 이는 einsum 식을 사용하며, DenseEinsumDense의 특별 케이스입니다. 이것은 다른 리포트에서 곧 다루도록 하겠습니다!
Iterate on AI agents and models faster. Try Weights & Biases today.