Skip to main content

PyTorch로 살펴보는 시간 마스킹 심층 분석

이 글은 PyTorch를 활용한 딥러닝에서의 타임 마스킹 기법을 다루며, 다양한 전략과 모델에 미치는 영향, 그리고 W&B를 통한 모니터링 방법을 살펴봅니다. 이 글은 AI 번역본입니다. 오역이 있다면 댓글로 알려주세요.
Created on September 15|Last edited on September 15
타임 마스킹(또는 시간-주파수 마스킹)은 딥러닝 분야에서 핵심적인 기법으로, 특히 오디오 처리와 같은 순차 데이터 처리 방식에 혁신을 가져왔으며 자연어 처리데이터의 일부를 의도적으로 가려 모델의 견고성과 일반화 성능을 높이는 능력은 무엇보다 중요하며, PyTorch 이러한 기법을 구현하고 실험하기에 훌륭한 플랫폼을 제공합니다. 여기에 고급 실험 추적 도구 Weights & Biases(W&B)와 같은 도구를 활용하면 연구자와 실무자는 모델을 효과적으로 모니터링하고 정교하게 개선할 수 있습니다.
이 글은 PyTorch를 활용한 타임 마스킹의 복잡한 메커니즘을 심층적으로 탐구하며, 다양한 전략과 그로 인한 모델 성능 영향, 그리고 현대 딥러닝 과제에서의 폭넓은 활용 사례를 포괄적으로 다룹니다.

목차



타임 마스킹 기법 이해하기

타임 마스킹이란 무엇인가?

타임 마스킹은 데이터 증강 오디오나 시계열처럼 순차적 데이터를 처리할 때 주로 사용하는 기법입니다. 시간 축의 일부 구간을 선택적으로 가리거나 “마스킹”하여, 모델이 특정 시간 구간에 과도하게 의존하지 않고 더 일반화된 특성을 학습하도록 합니다.
출처

마스킹 예시

가져가기 BERT 예를 들어, BERT는 모든 층에서 왼쪽과 오른쪽 문맥을 동시에 고려하여 라벨이 없는 텍스트로부터 깊은 양방향 표현을 사전 학습하도록 설계되었습니다. 즉, 문장 속 각 단어를 그 앞뒤에 오는 단어들을 바탕으로 예측합니다. 이를 달성하기 위해, 사전 학습 단계에서 BERT는 “마스크드 언어 모델링”이라는 기법을 사용합니다.
전통적인 언어 모델링에서는 모델이 시퀀스에서 다음 단어를 예측할 수 있습니다(예: GPT 모델). 그러나 BERT는 MLM 목표를 사용하여 입력 토큰의 일부 비율을 무작위로 마스킹(숨김)하고, 주변 문맥을 바탕으로 그 마스킹된 토큰을 예측하려고 시도합니다. 이는 깊은 양방향 모델을 학습하기 위해 수행됩니다.
작동 방식:
입력 준비:
  • 문장을 하나 들어 봅시다: “The cat sat on the mat.”
  • 예를 들어, 단어를 무작위로 마스킹합니다: “The cat sat on the [MASK].”
BERT의 과제:
  • 마스킹된 문장이 주어지면, BERT는 [MASK] 자리의 원래 단어를 예측하려고 시도하며, 이 경우 정답은 “mat”입니다.
학습:
  • 이 마스킹은 학습 데이터셋의 각 문장에서 일정 비율의 단어에 대해 수행됩니다. BERT는 마스킹된 단어의 왼쪽과 오른쪽 모두의 문맥을 이해하도록 학습되며, ���간이 지남에 따라 마스킹된 단어를 정확히 예측하는 능력이 향상됩니다.
요컨대, BERT에서의 “마스킹”은 문장 속 일부 단어를 “숨긴” 뒤 주변 문맥을 활용해 모델이 그 단어들을 예측하도록 하는 기법입니다. 이를 통해 BERT는 텍스트의 강력한 양방향 표현을 학습합니다.

일반적인 타임 마스킹 기법 개요

무작위 마스킹

이름에서 알 수 있듯이, 경우에 따라서는 무작위 마스킹시간 시퀀스의 임의 구간을 마스킹합니다(값을 0으로 설정하거나 특정 값으로 대체). 이러한 기법은 오디오 데이터 증강에서 흔히 사용되며, 특히 Automatic Speech Recognition (ASR)과 같은 작업을 위한 딥러닝 모델 학습에 널리 쓰입니다. 이 방법은 다양성과 무작위성을 도입해 과적합을 방지할 수 있지만, 반대로 지나치게 공격적으로 적용될 경우 시퀀스의 중요한 부분을 제거해 버릴 위험이 있습니다.

윈도우 기반 마스킹

~의 경우에는 윈도우 기반 마스킹고정 크기의 윈도우(연속 구간)를 선택해, 해당 윈도우 내부의 모든 값을 마스킹합니다. 이러한 타임 마스킹 기법은 국소적인 시간 구조가 중요한 과제에서, 모델이 일정한 구간의 데이터를 의도적으로 무시하도록 하는 데 유용합니다. 이 방법은 마스킹되는 데이터 양의 일관성이 높다는 장점이 있지만, 순수 무작위 마스킹에 비해 무작위성이 낮아 도입되는 다양성이 부족할 수 있습니다.

주파수 인지 마스킹

직접적인 시간 마스킹 기법으로 보기는 어렵지만, 주파수 인지 마스킹은 오디오 데이터에 특화되어 있습니다. 오디오 문맥에서 이 방법은 특정 주파수 채널을 마스킹하는 것을 포함하며, 스펙트로그램.
오디오 분류나 ASR 작업에서 데이터 증강으로 흔히 사용됩니다. 종종 시간 마스킹과 함께 적용됩니다.
출처
주파수 인지 마스킹은 모델이 특정 주파수 대역에 과도하게 의존하지 않도록 유도하며, 이는 실제 오디오 데이터를 학습할 때 절대적으로 필요합니다. 그러나 경우에 따라 중요한 주파수 정보를 잃게 만들 수 있습니다.

시간 마스킹 기법 비교와 모델 성능에 미치는 영향

각 시간 마스킹 기법은 우리가 학습 중인 모델의 성능에 서로 다른 영향을 미칩니다.
예를 들어 랜덤 마스킹의 경우, 이러한 시간 마스킹 기법의 주요 효과는 특정 데이터 구간에 과적합되는 것을 방지하여 일반화 성능을 높이는 데 있습니다. 그러나 과도한 랜덤 마스킹은 중요한 시간적 패턴을 포착하는 데 오히려 방해가 될 수 있습니다. 따라서 이 방법은 전체 시퀀스를 이해하는 데 어느 한 구간이 결정적이지 않고 변동성이 큰 데이터셋에 가장 적합합니다.
또 다른 예로 윈도우 기반 마스킹이 있습니다. 이 방법은 모델이 더 큰 시간적 문맥에 집중하도록 도와주며, 국소적 특징에만 의존하지 않고 예측하도록 강제함으로써 성능을 향상시킬 수 있습니다. 일반적으로 데이터에 강한 국소 시간 구조가 존재하고, 모델이 이를 넘어 일반화할 수 있어야 하는 작업에 가장 적합합니다.
마지막으로 주로 오디오 데이터에서 사용되는 주파수 인지 마스킹이 있습니다. 이 시간 마스킹 기법은 주파수 차원에서의 견고성을 높여 줍니다. 모델이 특정 주파수 대역에 과도하게 의존하지 않도록 학습하게 하며, 이는 테스트 데이터의 주파수 특성이 학습 데이터와 다를 때 특히 유용합니다. 이러한 방법은 입력 출처가 다양한 오디오 분류나 ASR 작업에 가장 적합합니다.

PyTorch에서의 시간 마스킹

PyTorch의 핵심은 GPU 가속을 갖춘 텐서 라이브러리로서, 딥러닝 연산에 매우 적합하다는 점입니다. 다만 오디오 특화 연산인 시간 마스킹과 같은 작업에서는 직접 커스텀 함수를 구현하거나 PyTorch 위에 구축된 특화 라이브러리를 활용해야 하는 경우가 많습니다.
PyTorch는 딥러닝과 텐서 연산에 최적화된 다양한 모듈과 기능을 제공합니다. 그중 핵심적인 구성 요소之一는 torch.nn로, 미리 정의된 레이어를 폭넓게 제공합니다. 손실 함수, 최적화 기법으로, 신경망 아키텍처를 구성하는 데 필수적입니다. 또 하나의 필수 구성 요소는 torch.utils.data.Dataset로, 데이터 로딩과 전처리 파이프라인을 간소화하여 크고 다양한 데이터셋을 매끄럽게 다룰 수 있게 해 줍니다.

PyTorch로 시간 마스킹 적용하기

이 글에서 PyTorch를 선택한 주된 이유는, 동적 계산 그래프와 강력한 텐서 연산 덕분에 시간 마스킹과 같은 연산을 매우 높은 수준으로 커스터마이즈하고 유연하게 구현할 수 있기 때문입니다.

시간 마스킹을 위한 Weights & Biases

Weights & Biases (W&B) 은 머신러닝 실무자를 위해 맞춤 설계된 고급 플랫폼으로, 다음을 위한 필수 도구입니다. 실험 추적 그리고 모델 관리이는 사용된 하이퍼파라미터, 모델의 구조적 세부 사항, 정확도나 손실과 같은 핵심 지표 등 머신러닝 실험의 복잡한 세부 정보를 효율적으로 기록합니다.
이 내용이 여기에서 중요한 이유는, 이러한 로그가 서로 다른 모델 변형들을 비교하는 데 핵심적인 역할을 하여 연구자와 개발자가 최적의 설정을 식별하는 데 도움을 주기 때문입니다. 이 플랫폼은 또한 포괄적인 시각화 기능을 제공하여, 에포크에 따른 지표를 그래프로 보여 주고 모델 성능 분석을 용이하게 합니다. 통합 기능 역시 W&B의 강점으로, TensorFlow와 PyTorch를 포함한 인기 있는 머신러닝 프레임워크와 매끄럽게 연동됩니다.

시간 마스킹 실험을 위한 W&B와 PyTorch 통합의 실전 적용

아래에는 PyTorch와 Weights & Biases를 사용한 간단한 시간 마스킹 예제가 있습니다. 이 예제의 목적은 PyTorch로 시간 마스킹을 더 명확하게 설명하고, 해당 실험을 Weights & Biases의 추적 도구와 통합하는 방법을 함께 보여 주는 것입니다.

1단계: 필요한 라이브러리 가져오기

import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data
import wandb
import numpy as np
import torchvision.transforms as transforms
import matplotlib.pyplot as plt


2단계: Weights & Biases 초기화

wandb.init(project="time_masking_experiments", name="random_masking")

3단계: 신경망 레이어 정의

class SimpleCNN(nn.Module):
def __init__(self, num_classes=5):
super(SimpleCNN, self).__init__()
# Convert the 2D spectrogram data to a 3D tensor: [batch_size, 1, freq_bins, time_frames]
self.unsqueeze = lambda x: x.unsqueeze(1)
self.conv1 = nn.Conv2d(1, 16, 3, padding=1)
self.conv2 = nn.Conv2d(16, 32, 3, padding=1)
self.relu = nn.ReLU()
self.maxpool = nn.MaxPool2d(2)
self.flatten = nn.Flatten()
# Calculate the output shape of the conv layers to adjust the FC layers
self.sample_shape_after_convs = self._get_shape_after_convs(torch.zeros((1, 65, 77)).float())

self.fc1 = nn.Linear(self.sample_shape_after_convs, 128)
self.fc2 = nn.Linear(128, num_classes)

def _get_shape_after_convs(self, x):
x = self.unsqueeze(x)
x = self.conv1(x)
x = self.maxpool(x)
x = self.conv2(x)
x = self.maxpool(x)
return np.prod(x.shape[1:]) # Multiply all dimensions except batch_size

def forward(self, x):
x = self.unsqueeze(x) # Add channel dimension
x = self.relu(self.conv1(x))
x = self.maxpool(x)
x = self.relu(self.conv2(x))
x = self.maxpool(x)
x = self.flatten(x)
x = self.relu(self.fc1(x))
x = self.fc2(x)
return x

4단계: 시간 마스킹 함수 정의

랜덤 시간 마스킹

The random_masking 이 함수는 주어진 스펙트로그램의 시간 차원에 무작위 마스크를 적용합니다. 먼저 원본 스펙트로그램을 복제해 인플레이스 수정이 일어나지 않도록 합니다. 그런 다음 시간 마스크의 폭과 시작 지점을 무작위로 결정합니다. 이후 해당 범위의 값을 0으로 설정해 사실상 해당 구간을 “마스킹”합니다. 마지막으로 마스킹된 스펙트로그램을 반환합니다.
def random_masking(spec, T=40):
# Clone the spectrogram to avoid in-place modifications
masked_spectrogram = spec.clone()

# Get the length of the time dimension
time_length = masked_spectrogram.shape[2]

# Determine the width of the mask
mask_width = torch.randint(0, T + 1, (1,)).item()

# Determine the starting point of the mask
mask_start = torch.randint(0, time_length - mask_width + 1, (1,)).item()

# Apply the mask
masked_spectrogram[:, :, mask_start:mask_start + mask_width] = 0

return masked_spectrogram

주파수·시간 마스킹

The frequency_time_masking 함수는 주어진 스펙트로그램의 주파수 차원과 시간 차원 모두에 무작위 마스킹을 적용합니다. 먼저 주파수 빈의 일부분을 무작위로 선택해 해당 값들을 0으로 설정하여 주파수 마스크를 적용합니다. 그다음 시간 프레임의 일부분을 무작위로 선택해 0으로 설정하여 시간 마스크를 적용합니다. 마지막으로, 이렇게 마스크가 적용된 스펙트로그램을 반환합니다.
def frequency_time_masking(spectrogram, max_freq_mask_width=15, max_time_mask_width=40):
# Clone the spectrogram to avoid in-place modifications
masked_spectrogram = spectrogram.clone()

# Apply frequency masking
num_frequency_bins = masked_spectrogram.shape[1]
freq_mask_width = torch.randint(0, max_freq_mask_width + 1, (1,)).item()
freq_mask_start = torch.randint(0, num_frequency_bins - freq_mask_width + 1, (1,)).item()
masked_spectrogram[:, freq_mask_start:freq_mask_start + freq_mask_width, :] = 0

# Apply time masking
num_time_frames = masked_spectrogram.shape[2]
time_mask_width = torch.randint(0, max_time_mask_width + 1, (1,)).item()
time_mask_start = torch.randint(0, num_time_frames - time_mask_width + 1, (1,)).item()
masked_spectrogram[:, :, time_mask_start:time_mask_start + time_mask_width] = 0

return masked_spectrogram

5단계: 사용자 정의 데이터셋 만들기

명확성과 보다 간결한 접근을 위해 우리는 처음부터 사용자 정의 데이터셋을 만들었습니다. 이 데이터셋에는 무작위로 생성된 스펙트로그램 1,000개가 포함되어 있습니다.
무작위 사인파 생성 함수 정의
def generate_sine_wave(freq, time, sample_rate=5000):
"""Generate a sine wave of a given frequency."""
t = np.linspace(0, time, int(time * sample_rate), endpoint=False)
return np.sin(2 * np.pi * freq * t)

def generate_spectrogram(signal, sample_rate=5000, window_size=128, step_size=64):
"""Generate a spectrogram from a signal."""
window = np.hanning(window_size)
return plt.specgram(signal, NFFT=window_size, Fs=sample_rate, noverlap=window_size - step_size, window=window, mode='magnitude')[0]

class StructuredSpectrogramDataset(data.Dataset):
def __init__(self, num_samples=100): # Note the change here
self.num_samples = num_samples
self.data = []
self.labels = []

for i in range(num_samples):
freq = np.random.choice(np.arange(10, 2000, 10)) # Frequency up to 2000Hz

# Remap the frequencies to 5 classes
label = freq // 200 # This will automatically map to 0-4 for freq from 10-1000, and then 5-9 for freq from 1000-2000
label %= 5 # This will make sure label is between 0-4

sine_wave = generate_sine_wave(freq, 1) # 1-second sine wave
spectrogram = generate_spectrogram(sine_wave)
self.data.append(spectrogram)
self.labels.append(label)

def __len__(self):
return self.num_samples

def __getitem__(self, idx):
return torch.tensor(self.data[idx], dtype=torch.float32).float(), self.labels[idx]

6단계: 데이터셋을 학습용과 평가용으로 분할하기

combined_dataset = StructuredSpectrogramDataset()

# Randomly shuffle and split
train_size = int(0.8 * len(combined_dataset))
eval_size = len(combined_dataset) - train_size

train_dataset, eval_dataset = torch.utils.data.random_split(combined_dataset, [train_size, eval_size])

# Create the dataloaders
train_loader = data.DataLoader(train_dataset, batch_size=16, shuffle=True)
eval_loader = data.DataLoader(eval_dataset, batch_size=16, shuffle=False)

def log_first_n_samples_to_wandb(n, data_loader):
spectrogram_images = []
for i, (data, target) in enumerate(data_loader):
if i >= n:
break
# Convert tensor to numpy for visualization
image = np.squeeze(data[0].numpy())
spectrogram_images.append(wandb.Image(image, caption=f"Label: {target[0]}"))

wandb.log({"First 5 Spectrograms": spectrogram_images})

log_first_n_samples_to_wandb(5, train_loader)
데이터셋을 더 잘 이해할 수 있도록, W&B에 기록된 첫 5개 데이터 포인트의 스펙트로그램을 보여드립니다.

각 그래프가 순수한 주파수만을 나타내기 때문에 눈에 띄는 변동이 크지 않습니다. 그 결과, 그래프는 곧은 수평선으로 표시됩니다.

7단계: 학습 함수 만들기

이 섹션에서는 원하는 시간 마스킹 기법을 자연스럽게 통합한 전용 학습 함수를 설계했습니다.
def train_one_epoch(model, data_loader, optimizer, criterion):
model.train()
total_loss = 0.0

for batch_idx, (data, target) in enumerate(data_loader):
# print("Shape of data:", data.shape) # Add this line to print shape
# Apply random time masking
masked_data = random_masking(data)
masked_data = masked_data.float()
target = target.long() # CrossEntropyLoss expects targets to be long type



# Log masked spectrograms to W&B (logging first sample in each batch)
if batch_idx % 10 == 0: # Log every 10 batches
original_img = np.squeeze(data[0].numpy())
masked_img = np.squeeze(masked_data[0].numpy())
wandb.log({
"Original Spectrogram": [wandb.Image(original_img, caption="Original")],
"Masked Spectrogram": [wandb.Image(masked_img, caption="Masked")]
})

optimizer.zero_grad()
outputs = model(masked_data)
loss = criterion(outputs, target)
loss.backward()
optimizer.step()
total_loss += loss.item()

avg_loss = total_loss / len(data_loader)
return avg_loss

8단계: 평가 함수 만들기

def evaluate(model, eval_loader, criterion):
model.eval()
total_loss = 0.0
correct = 0
total = 0
with torch.no_grad():
for batch_idx, (data, target) in enumerate(eval_loader):
outputs = model(data)
loss = criterion(outputs, target)
total_loss += loss.item()

_, predicted = outputs.max(1)
total += target.size(0)
correct += predicted.eq(target).sum().item()

avg_loss = total_loss / len(eval_loader)
accuracy = 100. * correct / total
return avg_loss, accuracy

9단계: 모델, 옵티마이저, 학습 손실 정의하기

model = SimpleCNN(num_classes=5)
model = model.float()
optimizer = optim.Adam(model.parameters(), lr=0.001)
criterion = nn.CrossEntropyLoss()

10단계: 모델을 10에폭 동안 학습하고 W&B에 로깅하기

num_epochs = 10
for epoch in range(num_epochs):
train_loss = train_one_epoch(model, train_loader, optimizer, criterion)
eval_loss, eval_accuracy = evaluate(model, eval_loader, criterion)
print(f"Epoch [{epoch+1}/{num_epochs}], Train Loss: {train_loss:.4f}, Eval Loss: {eval_loss:.4f}, Eval Accuracy: {eval_accuracy:.2f}%")
# Log both Training and Evaluation Metrics
wandb.log({
"Train Loss": train_loss,
"Eval Loss": eval_loss,
"Eval Accuracy": eval_accuracy
})

11단계: 로깅 마무리하기

wandb.finish()

W&B를 활용한 PyTorch 시간 마스킹 실험 로깅과 시각화

우리는 PyTorch로 세 가지 서로 다른 시간 마스킹 기법을 구현하고 검토했으며, 각 실험을 W&B 플랫폼에 로깅했습니다. 각 기법마다 학습 손실의 변화를 그래프로 기록했을 뿐만 아니라, 원본 스펙트럼과 마스킹된 스펙트럼을 나란히 비교 시각화하여 기법별 영향을 명확히 보여주었습니다. 이를 통해 시간 마스킹과 모델 성능 간 상호 작용을 더 깊이 이해할 수 있도록 했습니다.

PyTorch로 실행하고 W&B에 로깅한 랜덤 시간 마스킹

세 가지 마스킹 기법 각각에 대해 학습 손실, 평가 손실, 평가 정확도 그래프를 W&B에 함께 로깅했습니다. 이와 같은 포괄적인 로깅을 통해 학습 전 과정에서 각 모델의 성능을 명확하게 파악할 수 있습니다.

또한 세 가지 마스킹 기법 각각을 적용하기 전후의 스펙트로그램도 함께 로깅했습니다.

위에서 보인 것처럼, 랜덤 마스킹 기법은 표시된 선의 한 구간을 효과적으로 가려냈습니다.

PyTorch로 실행하고 W&B에 로깅한 주파수-시간 마스킹

마찬가지로 주파수-시간 마스킹 기반 모델의 그래프도 저장했습니다.

원본 스펙트로그램과 마스킹된 스펙트로그램과 함께


랜덤 마스킹 및 주파수-시간 마스킹(둘 다 W&B에 로깅됨)

보조 조치로, 랜덤 마스킹을 적용한 뒤 주파수-시간 마스킹을 적용해 얻은 모델 그래프를 아카이브해 두었습니다. 이러한 결합 방식은 특정 상황에서 더 우수한 결과를 낼 수 있습니다.

평가 설명

마스킹 기법을 적용한 후 스펙트로그램 시각화에서 관찰되는 현상은 시간 마스킹과 주파수 마스킹의 기대 동작과 일치합니다:
  • 시간 마스킹(랜덤 마스킹): 이 기법은 연속된 시간 구간을 마스킹하여, 특정 시간 동안 모든 주파수 채널에 영향을 주기 때문에 스펙트로그램에서 “수직” 블랙아웃이 나타납니다. 그래서 하나의 수직선이 보이는 것입니다.
  • 주파수 마스킹: 연속된 주파수 채널 범위를 마스킹하여, 특정 주파수에 대��� 모든 시간 스텝에 영향을 주기 때문에 스펙트로그램에서 “수평” 블랙아웃이 발생합니다. 그 결과 더 두꺼운 수평선이 나타납니다.
  • 둘 다 결합하기: 시간 마스킹과 주파수 마스킹을 순차적으로 모두 적용하면, 스펙트로그램에서 수직 방향(시간 마스킹)과 수평 방향(주파수 마스킹)으로 모두 가려진 영역이 생깁니다. 이는 때때로 문자 “T”와 비슷하게 보일 수 있는데, 수직선은 시간 마스킹에서, 수평선(또는 가로 막대)은 주파수 마스킹에서 비롯됩니다.
“T” 모양은 두 마스킹 기법이 결합되어 나타나는 효과를 시각적으로 표현한 것입니다. 모델이 학습 중에 이러한 “T” 형태의 마스킹 영역을 접하면, 주변의 마스킹되지 않은 구간에서 특징을 학습하도록 유도되어 견고성과 일반화 성능이 향상됩니다.
또한 세 가지 마스킹 기법 각각에 대해 학습 손실, 평가 손실, 평가 정확도 그래프를 Weights & Biases에 기록했습니다. 이러한 포괄적인 로깅을 통해 학습 전반에 걸친 각 모델의 성능을 명확하게 파악할 수 있습니다. 세 방법 중에서는 Random Masking 접근이 가장 우수한 학습 손실과 평가 정확도를 보여 다른 기법들에 비해 더 효과적임을 확인했습니다.

결론

이 글에서는 랜덤 마스킹부터 주파수 인지 마스킹까지 다양한 시간 마스킹 전략을 살펴보며, 데이터 증강과 모델 탄력성을 높이는 여러 접근법을 제시했습니다. PyTorch의 유연성과 Weights & Biases의 정교한 추적을 결합하면, 연구자들은 순차 데이터 처리에서 가능한 범위를 한층 더 확장할 수 있습니다.
이 분야가 계속 발전함에 따라, 이러한 기법을 이해하고 적응하며 혁신하는 것이 필수적입니다. 이를 통해 모델이 단순히 정확할 뿐 아니라 다양한 실제 과제 전반에서 견고하고 잘 일반화되도록 보장해야 합니다.

이 글은 AI 번역본입니다. 오역이 의심되는 부분은 댓글로 알려주세요. 원문 링크는 다음과 같습니다: 원문 보고서 보기