인공 신경망
퍼셉트론(perceptron), 다층 퍼셉트론 (MLP)
이전 내용
심층 신경망 훈련
◆ 심층 신경망(Deep Neural Network, DNN)
[인공 신경망 실습을 통해 알게 된 두 가지]
1. 퍼셉트론은 인간의 뉴런을 모방한 '인공신경망'이다.
2. 다층 퍼셉트론은 퍼셉트론을 직렬로, 병렬로 연결하여 1개 이상의 은닉층을 가진 구조이다.
XOR문제, 기울기 소실 문제 등으로 인공신경망의 인기는 빠르게 식어갔다. 그러다 2006년 인공신경망 대신 심층 신경망(Deep Neural Network, DNN)이라는 용어가 사용되기 시작했다. 가중치 초기화 설정, ReLU 함수, 컴퓨터 성능 발전 등 다양한 방법으로 기울기 소실의 문제를 어느정도 해결하였다고 한다.
다층 퍼셉트론에서 은닉층을 많이 늘린 구조를 심층 신경망이라고 말할 수 있는데, 일반적으로 은닉층이 3개 이상인 구조를 심층 신경망이라고 하며, 이러한 심층 신경망을 이용하여 컴퓨터가 문제를 해결하는 과정을 딥러닝(Deep learning)이라고 한다. 즉 딥러닝은 컴퓨터가 데이터를 스스로 학습하여 문제를 해결하는 머신러닝(Machine learning) 방법 중 하나이다.
[심층 신경망 훈련 중 마주할 수 있는 문제들]
수백 개의 뉴런으로 구성된 10개 이상의 층을 수십만 개의 가중치로 연결해 훨씬 더 깊은 인공 신경망을 훈련한다고 생각해보면, 심층 신경망을 훈련하는 것은 쉬운 일이 아니며, 훈련 중에 아래와 같은 문제를 마주할 수 있다.
- 심층 신경망의 출력 층에서 멀어질수록 그레이디언트가 점점 더 작아지거나 커지는 문제가 나타날 수 있으며, 두 문제 모두 하위 층을 매우 훈련하기 어렵게 만든다.
- 대규모 신경망을 위한 훈현 데이터가 충분하지 않거나 레이블을 만드는 작업에 비용이 너무 많이 들어갈 수 있다.
- 훈련이 극단적으로 느려질 수 있다.
- 수백만 개의 파라미터를 가진 모델은 훈련 세트에 과대적합될 위험이 매우 크며, 특히 훈련 샘플이 충분치 않거나 잡음이 많은 경우에는 과대적합 될 확률이 더 크다.
해결책3 - 다양한 최적화 방법
대규모 모델의 훈련 속도를 크게 높여주는 다양한 최적화 방법이 있다.
1. 고속 옵티마이저
1-1. 모멘텀 최적화
- 개념
모멘텀은 물리학의 운동량 개념을 가져온 것으로, 경사 하강법에서 현재 기울기뿐만 아니라 이전의 업데이트 방향을 고려하여 학습 방향을 정한다. 이는 학습을 더 빠르게 진행하면서 불필요한 진동을 줄여준다. 예를 들어, 공이 높은 경사면을 굴러 내려갈 때, 처음에는 천천히 움직이지만 점차 가속이 붙으며 더 빠르게 내려오는 현상과 유사하다.
- 특징:
- 빠른 수렴 (특히, 길고 평탄한 지역에서는 효과적).
- 진동 감소.
- 이전 그레이디언트가 얼마였는지를 상당히 중요하게 생각하여 매 반복에서 현재 그레이디언트를 모멘텀 벡터 m에 더하고 이 값을 빼는 방식으로 가중치를 갱신 ▶ 그레이디언트를 속도가 아니라 가속도로 사용.
- 일종의 마찰 저항을 표현하고 모멘텀이 커지는 것을 막기 위해 모멘텀이라는 새로운 하이퍼파라미터가 등장하며 이 값은 0(높은 마찰 저항)과 1(마찰 저항 없음) 사이로 설정되어야 한다. 일반적인 값은 0.9.
- 지역 최적점을 건너뛰도록 하는 데도 도움.
- 한 가지 단점은 튜닝할 하이퍼파라미터가 하나 증가하는 것.
- 케라스에서 모멘텀 최적화 구현
SGD 옵티마이저를 사용하고 momentum 매개변수를 지정하고 기다리면 된다.
optimizer = tf.keras.optimizers.SGD(learning_rate=0.001, momentum=0.9)
1-2. 네스테로프 가속 경사(Nesterov accelerated gradient, NAG)
- 개념:
네스테로프 가속 기법은 모멘텀 최적화의 개선된 버전으로, 단순히 현재 위치의 그래디언트를 계산하는 것이 아니라, "미리 예측된 위치"에서 그래디언트를 계산한다. 이를 통해 보다 정확한 방향으로 이동할 수 있다. 예를 들어, 목표지점을 향해 뛰어갈 때 현재 위치가 아닌, 예상 이동 지점을 미리 고려하여 움직이는 것과 유사하다.
- 특징:
- 기본 모멘텀 최적화보다 더 빠른 수렴.
- 오버슈팅(목표를 지나치는 현상) 감소.
- 케라스에서 NAG 사용
SGD 옵티마이저를 만들 때 nesterov=True 라고 설정하면 된다.
optimizer = tf.keras.optimizers.SGD(learning_rate=0.001, momentum=0.9, nesterov=True)
1-3. AdaGrad
- 개념:
AdaGrad는 학습률을 각 파라미터에 대해 개별적으로 조정하는 방법으로, 자주 업데이트되는 파라미터는 학습률을 줄이고, 드물게 업데이트되는 파라미터는 학습률을 유지하거나 증가시킨다. 이는 희소한 데이터(드문 데이터를 다루는 상황)에서 매우 효과적이다. 하지만 학습률이 시간이 지남에 따라 너무 빠르게 감소하는 단점이 있어, 장기간 학습에는 적합하지 않을 수 있다.
- 특징:
- NLP, 이미지 처리 등 희소 데이터에 적합.
- 학습률 감소로 인해 수렴 속도가 느려질 수 있음.
- 케라스에서 AdaGrad 사용
케라스에 AdaGrad 옵티마이저가 있다. 그러나 AdaGrad는 간단한 2차 방정식 문제에 대해서는 잘 작동하지만 신경망을 훈련할 때 너무 일찍 멈추는 경우가 종종 있다. 학습률이 너무 감소하여 전역 최적점에 도착하기 전에 알고리즘이 완전히 멈춘다. 따라서 선형 회귀 같은 간단한 작업에는 효과적일 수 있으나, 심층 신경망에는 사용하지 말아야 한다.
optimizer = tf.keras.optimizers.Adagrad(learning_rate=0.001)
1-4. RMSProp
- 개념:
RMSProp은 AdaGrad의 단점인 너무 빨리 느려져서 전역 최적점에 수렴하지 못하는 위험을 해결하기 위해 제안된 방법으로, RMSProp 알고리즘은 훈련 시작부터의 모든 그레이디언트가 아닌 가장 최근 반복에서 비롯된 그레이디언트만 누적함으로써 이 문제를 해결했다. 이렇게 하기 위해 알고리즘의 첫 번째 단계에서 지수 감소(지수 이동 평균)를 사용하는 데, 지난 그레이디언트 제곱의 지수 감소된 평균을 따른다. 이렇게 하면 학습률이 지나치게 감소하지 않아 안정적인 학습이 가능하다. 이는 딥러닝에서 매우 자주 사용된다.
- 특징:
- 안정적인 학습.
- 딥러닝 네트워크(특히 RNN)에서 효과적.
- 간단한 문제를 제외하고는 언제나 AdaGrad 보다 성능이 좋음.
- 케라스에서 RMSProp 사용
optimizer = tf.keras.optimizers.RMSprop(learning_rate=0.001, rho=0.9)
※ rho는 이전 단계의 평균 값이 현재 값에 얼마나 영향을 미칠지를 결정하는 가중치 역할을 한다. 값은 일반적으로 0.9로 설정되며, 이는 과거 정보에 더 큰 가중치를 부여한다는 의미이다.
- 큰 값 (0.9~1.0): 이전 기울기의 정보를 더 많이 반영 → 안정적이지만 반응 속도가 느림.
- 작은 값 (0.1~0.5): 최근 기울기의 정보를 더 많이 반영 → 반응 속도가 빠르지만 노이즈에 민감.
1-5. Adam(적응적 모멘트 추정, adaptive moment estimation)
- 개념:
Adam은 모멘텀과 RMSProp을 결합한 방식으로, 기울기의 1차 모멘텀(평균)과 2차 모멘텀(분산)을 동시에 활용하여 학습률을 조정한다. 이는 대부분의 딥러닝 문제에서 기본적으로 사용되는 최적화 알고리즘이다. 초기 단계에서의 빠른 학습과 안정성을 모두 제공한다.
- 특징:
- 모멘텀 최적화 + RMSProp ▶ 모멘텀 최적화처럼 지난 그레이디언트의 지수 감소 평균을 따르고, RMSProp 처럼 지난 그레이디언트 제곱의 지수 감소된 평균을 따름. (그레이디언트 평균과 평균이 0이 아닌 분산에 대한 예측)
- 평균을 첫 번째 모멘트, 분산을 두 번째 모멘트라 부름.
- 자동으로 학습률 조정. ▶ 학습률 하이퍼파라미터 튜닝 불필요.
- 학습률은 기본값 0.001을 일반적으로 사용하므로 경사 하강법보다 더 사용하기 쉬움.
- 다양한 데이터셋에서 높은 성능을 보임.
- 케라스에서 Adam 옵티마이저 사용
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001, beta_1=0.9, beta_2=0.999)
1-6. AdaMax
- 개념:
AdaMax는 Adam의 변형으로, 기울기 분산을 계산할 때 𝐿∞ 노름(최대값)을 사용한다. 이렇게 하면 계산이 더 간단해지고 안정적인 업데이트가 가능하다.
- 특징:
- 큰 기울기 변동에도 강함.
- 계산 안정성 증가.
- 작업에서 Adam이 잘 작동하지 않는다면 시도할 수 있는 옵티마이저
- 케라스에서 AdaMax 사용
optimizer = tf.keras.optimizers.Adamax(
learning_rate=0.001, # 학습률
beta_1=0.9, # 1차 모멘텀 계수
beta_2=0.999, # 2차 모멘텀 계수
epsilon=1e-07 # 수치 안정성을 위한 작은 값
)
1-7. Nadam
- 개념:
Nadam은 Adam에 네스테로프 가속 기법을 결합한 알고리즘으로, 더 빠르고 부드러운 수렴을 제공한다. 이는 초기에 학습이 안정적이고, 복잡한 문제에서도 성능이 우수하다.
- 특징:
- Adam + NAG(네스테로프 가속 기법)
- 초기 학습 안정성 증가.
- 빠르고 정확한 수렴.
- 케라스에서 Nadam 사용
optimizer = tf.keras.optimizers.Nadam(
learning_rate=0.001, # 학습률
beta_1=0.9, # 1차 모멘텀 계수
beta_2=0.999, # 2차 모멘텀 계수
epsilon=1e-07 # 수치 안정성을 위한 작은 값
)
1-8. AdamW
- 개념:
AdamW는 Adam 알고리즘에 L2 정규화를 더한 방식으로, 학습률 업데이트와 가중치 감소(weight decay)를 분리한다. 이를 통해 과적합을 방지하고, 트랜스포머 모델과 같은 최신 모델에서 많이 사용한다.
- 특징:
- 가중치 감쇠라는 규제 기법을 통합한 Adam의 변형
- 가중치 감쇠: 각 훈련 반복에서 모델의 가중치에 0.99와 같은 감쇠 계수를 곱하여 가중치의 크기 줄임.
- 일반화 성능 개선.
- 과적합 방지.
- 케라스에서 AdamW 사용
optimizer = tf.keras.optimizers.AdamW(
learning_rate=0.001, # 학습률
beta_1=0.9, # 1차 모멘텀 계수
beta_2=0.999, # 2차 모멘텀 계수
epsilon=1e-07, # 수치 안정성을 위한 작은 값
weight_decay=0.01 # 가중치 감소 (L2 정규화)
)
<옵티마이저 비교표> - A: 좋음, B: 보통, C: 나쁨
클래스 | 수렴 속도 | 수렴 품질 |
SGD | C | A |
SGD(momentum=..) 모멘텀 최적화 | B | A |
SGD(momentum=..., nesterov=True) 네스테로프 가속 기법(NAG) |
B | A |
Adagrad | A | C (너무 일찍 멈춤) |
RMSprop | A | A or B |
Adam | A | A or B |
AdaMax | A | A or B |
Nadam | A | A or B |
AdamW | A | A or B |
<모든 옵티마이저의 학습 곡선>
2. 학습률 스케줄링
좋은 학습률을 찾는 것은 매우 중요하다.
- 학습률을 너무 크게 잡을 경우: 훈련 발산 위험
- 학습률을 너무 작게 잡을 경우: 최적점에 수렴하나 시간이 매우 오래 걸림.
- 학습률을 조금 높게 잡을 경우: 처음에는 매우 빠르게 진행하나, 최적점 근처에서는 요동이 심해져 수렴 불가.
- 한정적인 컴퓨팅 자원일 경우: 차선의 솔루션을 만들기 위해 완전히 수렴하기 전에 중지.
▶ 좋은 학습률을 찾기 위해서는 매우 작은 값에서 매우 큰 값까지 지수적으로 학습률을 증가시키면서 모델 훈련을 수백 번 반복하는 방식을 사용할 수 있다. 그 다음 학습 곡선을 살펴보고 다시 상승하는 곡선보다 조금 더 작은 학습률을 선택하고, 모델을 다시 초기화하고 이 학습률로 훈련한다.
일정한 학습률보다 더 나은 방법은, 큰 학습률로 시작하고 학습 속도가 낮아질 때 학습률을 낮추면 고정 학습률보다 좋은 솔루션을 더 빨리 발견할 수 있다. 아래는 훈련하는 동안 학습률을 감소시키는 다양한 학습 스케줄 전략이다.
2-1. 거듭제곱 기반 스케줄링
- 개념:
학습률(learning rate)을 학습 과정 동안 점진적으로 감소시키는 방식으로, 감소 속도가 1/(1+t)^p 형태를 따른다. 여기서
t는 현재 에포크(epoch) 또는 스텝(step)이고, p는 감소 속도를 제어하는 지수이다.
초기에는 높은 학습률로 빠르게 학습하다가, 시간이 지남에 따라 학습률을 천천히 줄여 안정적인 수렴을 유도한다.
- 장점:
- 안정적인 학습.
- 과적합 방지.
- 케라스에서 거듭제곱 기반 스케줄링 구현
옵티마이저를 만들 때 decay 매개변수만 저장하면 된다.
optimizer = tf.keras.optimizers.SGD(learning_rate=0.001, weight_decay=1e-4)
2-2. 지수 기반 스케줄링
- 개념:
학습률을 학습 스텝(step) 또는 에포크(epoch)에 따라 미리 정해진 값으로 조정하는 방식이다. 특정 단계마다 학습률을 바꾸기 때문에 간단하고 직관적인 접근법이다.
- 방법:
예를 들어, 학습률을 에포크 5, 10, 20에서 각각 00.01, 0.001, 0.0001로 변경하도록 미리 설정할 수 있다.
- 장점:
- 구현이 간단.
- 학습률 변경 타이밍을 세밀하게 조정 가능.
- 단점:
- 적절한 변경 시점을 수동으로 설정해야 함.
- 비효율적일 가능성.
- 지수 기반 스케줄링 구현
lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(
initial_learning_rate=0.01,
decay_steps=20_000,
decay_rate=0.1,
staircase=False
)
optimizer = tf.keras.optimizers.SGD(learning_rate=lr_schedule)
[콜백 클래스를 사용해 지수 기반 감쇠 구현]
케라스는 사용자 정의 스케줄링 함수를 위해 LearningRateScheduler 콜백 클래스를 제공합니다. 이를 사용해 지수 기반 감쇠를 구현하는 방법을 살펴 본다. 여기에서는 스텝이 아니라 에포크마다 학습률이 바뀐다.
먼저 에포크를 받아 학습률을 반환하는 함수를 정의해야 한다.
def exponential_decay_fn(epoch):
return 0.01 * 0.1**(epoch / 20)
학습률(lr0)과 스텝(s)을 하드코딩하고 싶지 않다면 이 변수를 설정한 클로저를 반환하는 함수를 만들 수 있다.
def exponential_decay(lr0, s):
def exponential_decay_fn(epoch):
return lr0 * 0.1**(epoch / s)
return exponential_decay_fn
exponential_decay_fn = exponential_decay(lr0=0.01, s=20)
패션 MNIST용 모델 빌드 및 컴파일을 하고,
tf.random.set_seed(42)
model = build_model()
optimizer = tf.keras.optimizers.SGD(learning_rate=0.001)
model.compile(loss="sparse_categorical_crossentropy", optimizer=optimizer,
metrics=["accuracy"])
그다음 이 스케줄링 함수를 전달하여 LearningRateScheduler 콜백을 만들고, 이 콜백을 fit() 메서드에 전달한다.
LearningRateScheduler는 에포크를 시작할 때마다 옵티마이저의 learning_rate 속성을 업데이트 하는 데, 에포크마다 한 번씩 스케줄을 업데이트해도 충분하다. 만약 더 자주 업데이트하고 싶다면 스텝마다 사용자 정의 콜백을 만들 수 있다.
만약 에포크마다 스텝이 많다면 스텝마다 학습률을 업데이트하는 것이 좋다.
n_epochs = 20
lr_scheduler = tf.keras.callbacks.LearningRateScheduler(exponential_decay_fn)
history = model.fit(X_train, y_train, epochs=n_epochs,
validation_data=(X_valid, y_valid),
callbacks=[lr_scheduler])
스케줄 함수는 두 번째 매개변수로 현재 학습률을 받을 수 있다.
아래와 같은 스케줄 함수는 이전 학습률에 0.1^1/20 을 곱하여 동일한 지수 감쇠 효과를 내는데, 여기서는 에포크 1이 아니라 0에서부터 감쇠가 시작된다.
def exponential_decay_fn(epoch, lr):
return lr * 0.1 ** (1 / 20)
▶ 이 구현은 이전 구현과 달리 옵티마이저의 초기 학습률에만 의존하므로 이를 적절히 설정해야 한다.
모델을 저장할 때 옵티마이저와 학습률이 함께 저장되며, 새로운 스케줄 함수를 사용할 때도 아무 문제없이 훈련된 모델을 로드하여 중지된 지점부터 훈련을 계속 진행할 수 있다. 하지만 스케줄 함수가 epoch 매개변수를 사용하면 문제가 조금 복잡해지는 데, 에포크는 저장되지 않고 fit() 메서드를 호출할 때마다 0으로 초기화된다. 중지된 지점부터 모델 훈련을 이어가려 한다면 매우 큰 학습률이 만들어져 모델의 가중치를 망가뜨릴 가능성이 높다.
이때에는 epoch에서 시작하도록 fit() 메서드의 initial_epoch 매개변수를 수동으로 지정하면 좋다.
def piecewise_constant_fn(epoch):
if epoch < 5:
return 0.01
elif epoch < 15:
return 0.005
else:
return 0.001
tf.keras는 학습률 스케줄링을 위해 tf.keras.optimizers.schedules에 있는 스케줄 중 하나를 사용해 학습률을 정의하고 옵티마이저에 전달할 수 있다. 이렇게 하면 에포크가 아니라 매 스텝마다 학습률을 업데이트한다.
다음은 앞서 정의한 exponential_decay_fn()과 동일한 지수 기반 스케줄링을 구현하는 방법이다.
import math
batch_size =32
n_epochs = 25
n_steps = n_epochs * math.ceil(len(X_train) / batch_size)
scheduled_learning_rate = tf.keras.optimizers.schedules.ExponentialDecay(
initial_learning_rate=0.01, decay_steps=n_steps, decay_rate=0.1)
optimizer = tf.keras.optimizers.SGD(learning_rate=scheduled_learning_rate)
▶ 간결하고 이해하기 좋으며, 모델을 저장할 때 학습률과 현재 상태를 포함한 스케줄도 함께 저장된다.
2-3. 구간별 고정 스케줄링
- 개념:
학습을 여러 섹션으로 나누고, 각 섹션에서 일정한 학습률을 사용하는 방식이다. 섹션이 변경될 때 학습률을 감소시키거나 변경한다. 일정 횟수의 에포크 동안 일정한 학습률을 사용하고 그다음 또 다른 횟수의 에포크 동안 작읍 학습률을 사용하는 식인데, 이 방법이 잘 작동할 수 있지만 적절한 학습률과 에포크 횟수의 조합을 찾으려면 이리저리 바꿔봐야 한다.
예를 들어, 110 에포크 동안 0.01, 1120 에포크 동안 0.001과 같이 특정 구간에서 고정된 학습률을 사용한다.
- 장점:
- 섹션별로 최적의 학습률을 설정할 수 있음.
- 직관적이고 설정이 쉬움.
- 단점:
- 학습률을 미리 고정하기 때문에 데이터에 따른 유연성이 떨어질 수 있음.
- 구간별 고정 스케줄링 구현
# 구간 별 고정 스케줄링
lr_schedule = tf.keras.optimizers.schedules.PiecewiseConstantDecay(
boundaries=[50_000, 80_000],
values=[0.01, 0.005, 0.001]
)
optimizer = tf.keras.optimizers.SGD(learning_rate=lr_schedule)
[구간별 고정 스케줄링 수동 구현]
def piecewise_constant(boundaries, values):
boundaries = np.array([0] + boundaries)
values = np.array(values)
def piecewise_constant_fn(epoch):
return values[(boundaries > epoch).argmax() - 1]
return piecewise_constant_fn
piecewise_constant_fn = piecewise_constant([5, 15], [0.01, 0.005, 0.001])
n_epochs = 25
lr_scheduler = tf.keras.callbacks.LearningRateScheduler(piecewise_constant_fn)
model = build_model()
optimizer = tf.keras.optimizers.Nadam(learning_rate=lr0)
model.compile(loss="sparse_categorical_crossentropy", optimizer=optimizer,
metrics=["accuracy"])
history = model.fit(X_train, y_train, epochs=n_epochs,
validation_data=(X_valid, y_valid),
callbacks=[lr_scheduler])
2-4. 성능 기반 스케줄링
- 개념:
모델의 학습 성능(예: 검증 손실 또는 정확도)에 따라 학습률을 동적으로 조정하는 방식이며, N 스텝마다 조기 종료처럼 검증 오차를 측정하고 검증 손실이 개선되지 않으면 학습률을 감소시킨다.
-장점:
- 성능 기반으로 학습률을 조정하기 때문에 데이터와 문제 유형에 유연하게 대응 가능.
- 과적합 방지 효과.
- 단점:
- 추가적인 검증 단계가 필요해 시간이 더 걸릴 수 있음.
- 성능 기반 스케줄링 구현
model = build_model()
optimizer = tf.keras.optimizers.SGD(learning_rate=lr0)
model.compile(loss="sparse_categorical_crossentropy", optimizer=optimizer,
metrics=["accuracy"])
lr_scheduler = tf.keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=5)
history = model.fit(X_train, y_train, epochs=n_epochs,
validation_data=(X_valid, y_valid),
callbacks=[lr_scheduler])
2-5. 1사이클 스케줄링
- 개념:
1-Cycle 학습률 스케줄링은 학습 과정 동안 학습률을 먼저 점진적으로 증가시키고(업스윙), 이후 다시 감소시키는(다운스윙) 방식이다. 이 방식은 학습 초기에 빠르게 최적의 방향을 찾고, 학습 후반에는 미세 조정을 통해 최종 수렴을 돕는다. 학습률뿐만 아니라 모멘텀(momentum)도 함께 조정하는 경우가 많다.
- 단계:
- 학습 초반: 학습률 증가, 모멘텀 감소.
- 학습 후반: 학습률 감소, 모멘텀 증가.
- 장점:
- 빠른 학습과 안정적인 수렴을 동시에 달성.
- 과적합 방지 효과.
- 단점:
- 초기 설정이 다소 복잡할 수 있음.
- 케라스에서 1사이클 스케줄링은 지원하지 않음.
다음 내용
[출처]
핸즈 온 머신러닝
'[파이썬 Projects] > <파이썬 딥러닝, 신경망>' 카테고리의 다른 글
[딥러닝] 텐서플로를 사용한 데이터 적재와 전처리 (0) | 2024.11.25 |
---|---|
[딥러닝] 텐서플로 함수와 그래프 (0) | 2024.11.25 |
[딥러닝] 텐서플로를 사용한 사용자 정의 모델과 훈련 (0) | 2024.11.24 |
[딥러닝] 심층 신경망 훈련 (1) | 2024.11.24 |
[딥러닝] 심층 신경망 훈련 - 2 (2) | 2024.11.24 |