7.1 데이터 및 모델 세팅(Setting up the data and the model)

이전 장에서 내적(dot product)과 비선형성(non-linearity)을 계산하는 뉴런 모델과 뉴런을 레이어로 배열하는 Neural Network를 알아보았습니다. 이 둘을 합치면, 단순한 선형 매핑(linear mapping)을 확장하며 새로운 형태의 score function이 정의됩니다. 특히, Neural Network는 연속적(sequence)인 선형 매핑(linear mapping)에 비선형성(non-linearities)을 뒤섞어(interwoven)줍니다. 이번 장에서는 데이터 전처리, 가중치 초기화(weight initialization), 손실함수(loss function)에 걸쳐 추가적인 디자인 선택에 대해 다룰것입니다.

7.1.1 데이터 전처리(Data Preprocessing)

데이터 행렬 X에 대한 3가지 일반적인 데이터 전처리(data preprocessing)의 형태가 있습니다. 여기서 X[N x D]의 크기를 가진다고 가정합니다. (N은 데이터의 수, D는 그 차원수(dimensionality)입니다.)

Mean subtraction은 가장 흔한 형태의 전처리 기법입니다. 이는 데이터의 모든 개별 피쳐(feature)에 대해 평균(mean)을 빼는 작업으로, 기하학적(geometric)으로는 모든 차원에 대한 데이터 클라우드(data cloud)의 중심을 원점 근처로 모으는(centering)것을 의미합니다. numpy에서 이 연산은 다음과 같이 구현됩니다: X -= np.mean(X, axis = 0). 특히 이미지의 경우, 편의상 모든 픽셀에 대해 하나의 값(평균)을 빼거나(X -= np.mean(X)) 세 개의 색 채널에 대하여 이를 각각 수행하는것이 일반적입니다.

정규화(Normalization)는 데이터 차원(dimensions)을 정규화함으로써 데이터가 거의 비슷한 스케일(scale)를 가지도록 하는 것이 목적입니다. 이 정규화를 하는데는 두 가지 일반적인 방법이 있습니다. 첫 번째는, 각 차원을 0-중심(zero-centered)으로 만들고 표준편차(standard deviation)로 나누는 것입니다: (X /= np.std(X, axis = 0)). 두 번째는, 각 차원을 정규화함으로써 차원의 최솟값(min)을 -1, 최댓값(max)을 1로 만드는 것입니다. 이 전처리를 적용하려면, 서로 다른 입력 피쳐가 서로 다른 스케일(또는 유닛)을 가지면서 학습 알고리즘에 있어서는 거의 동일한 중요도를 가져야합니다. 그러나 이미지의 경우, 픽셀의 상대적 스케일은 이미 근사적으로 동일하기 때문에(범위 0에서 255까지), 이 추가 전처리 과정이 반드시 필요한것은 아닙니다.

일반적인 데이터 전처리 파이프라인. 왼쪽: 2차원 원본 입력 데이터(toy) 중간: 각 차원에 대해 평균을 뺌으로써 데이터가 0-중심(zero-centered)이 됩니다. 즉, 데이터 클라우드의 중심이 원점에 위치하게 됩니다. 오른쪽: 각 차원은 표준편차에 의해 추가적으로 크기가 조정(scaled)됩니다. 빨간색 선을 데이터의 분포 범위(extent)를 표시합니다 - 중간 그림에서는 길이가 같지 않지만, 오른쪽 그림에서는 같은 길이를 가집니다.

PCA와 Whitening은 또 다른 형태의 전처리 기법입니다. 이 과정에서, 데이터는 먼저 위의 그림처럼 중심이 원점에 위치하게(centered) 됩니다. 그리고 데이터의 상관관계(correlation) 구조를 알 수 있는 공분산(covariance) 행렬을 계산합니다:

# Assume input data matrix X of size [N x D]
X -= np.mean(X, axis = 0) # zero-center the data (important)
cov = np.dot(X.T, X) / X.shape[0] # get the data covariance matrix

데이터 공분산(covariance) 행렬의 (i,j) 원소는 데이터의 i번째와 j번째 차원 간의 공분산(covariance)을 의미합니다. 특히, 이 행렬의 대각선(diagonal)은 분산(variances)를 나타냅니다. 또한, 공분산(covariance) 행렬은 대칭적(symmetric)이며 positive semi-definite합니다. 따라서 데이터 공분산 행렬의 SVD(특이값 분해: Singular Value Decomposition) 인수분해(factorization)를 계산할 수 있습니다:

U,S,V = np.linalg.svd(cov)

U의 각 열은 고유벡터(eigenvector)를 의미하고, S은 특이값(singular value)의 1차원 배열입니다. 데이터의 상관관계를 없애기(decorrelate) 위해, (0-중심이 된) 원본 데이터를 eigenbasis에 사영(project)합니다:

Xrot = np.dot(X, U) # decorrelate the data

U의 각 열은 정규 직교 벡터(orthonormal vector: norm이 1이고 서로 직교)의 집합이기 때문에, 기저 벡터(basis vector)로 간주할 수 있습니다. 따라서 사영(projection)은 X 데이터의 회전에 해당하기 때문에 새로운 축(axes)은 고유벡터(eigenvectors)가 됩니다. Xrot의 공분산 행렬을 계산하는 경우, 이를 대각행렬(diagonal)으로 볼 수 있습니다. np.linalg.svd의 훌륭한 특성은 반환값 U의 각 열인 고유벡터(eigenvector)가 그 고윳값(eigenvalue)에 의해 정렬된다는 것입니다. 상위 몇 개의 고유벡터(eigenvector)만을 사용해서 데이터의 분산(variance)이 없는 차원을 버림으로써(discarding), 데이터의 차원수(dimensionality)를 줄일 수 있습니다. 이는 간혹 Principal Component Analysis (PCA) 차원수 감소(dimensionality reduction)라고도 부릅니다:

Xrot_reduced = np.dot(X, U[:,:100]) # Xrot_reduced becomes [N x 100]

앞선 과정이 끝나면, [N x D] 크기의 원본 데이터셋은 [N x 100]으로 크기가 줄어들며, 대부분의 분산(variance)를 표현하는 100차원의 데이터는 남겨집니다. 선형 분류기 혹은 neural network를 통해 PCA로 축소된(PCA-reduced) 데이터셋을 학습하면, 대부분의 경우에서 공간과 시간을 절약하는 동시에 우수한 성능을 확인할 수 있습니다.

실전에서 볼 수 있는 마지막 변형(transformation)은 화이트닝(whitening)입니다. 화이트닝은 고유기저(eigenbasis)의 데이터를 가지고, 모든 차원을 고윳값(eigenvalue)으로 나누어 스케일을 정규화하는 기법입니다. 이를 기하학적으로 보면, 입력 데이터가 다변수 가우시안(multivaraible gaussian)일 때, 화이트닝된(whitened) 데이터는 0의 평균과 항등 공분산 행렬(identity covariance matrix)을 가지는 가우시안이 될것입니다. 이 과정은 다음과 같습니다:

# whiten the data:
# divide by the eigenvalues (which are square roots of the singular values)
Xwhite = Xrot / np.sqrt(S + 1e-5)

주의: 노이즈 증폭(Exaggerating noise) 0으로 나누는 것을 방지하기 위해 1e-5 (또는 작은 상수)를 더해주는 것에 유의합니다. 이 변환(transformation)의 한 가지 약점은 입력의 모든 차원(주로 노이즈이며 작은 분산을 가지는 관련없는 차원도 포함)이 동일한 크기로 확장되기 때문에 데이터에서 노이즈를 증폭(exaggerate)시킬 수 있다는 것입니다. 실전에서 이는 더 강력한 평활화(smoothing)에 의해 완화됩니다(즉, 1e-5를 더 큰 숫자로 만듭니다).

PCA / 화이트닝(Whitening). 왼쪽: 2차원 원본 입력 데이터(toy). 중간: PCA를 적용한 후, 데이터는 중심이 0에 맞춰지고 데이터 공분산 행렬(covariance matrix)의 고유기저(eigenbasis)로 회전됩니다. 이는 데이터 간의 상관관계를 없앱니다(decorrelates) (공분산 행렬은 대각행렬(diagonal)이 됩니다). 오른쪽: 각 차원은 고윳값(eigenvalue)에 의해 추가적으로 크기가 조정되며(scaled), 데이터 공분산 행렬을 항등행렬(identity matrix)로 변환합니다. 기하학적으로, 이는 데이터를 등방성 가우시안 덩어리(isotropic gaussian blob)로 늘이거나(stretching) 줄이는(squeezing)것에 해당합니다.

CIFAR-10 이미지를 이용하여 이 변형을 시각화할 수도 있습니다. CIFAR-10의 트레이닝셋은 50000 x 3072의 크기를 가집니다. 이는 모든 이미지가 3072차원의 행 벡터로 늘어뜨린 것을 의미합니다. 따라서 [3072 x 3072] 공분산 행렬(covariance matrix) 연산한 후 (상대적으로 연산적으로 비싼) 그 행렬의 SVD 분해를 계산할 수 있습니다. 계산된 고유벡터(eigenvector)는 시각적으로 어떻게 생겼을까요? 아래의 그림은 그 이해를 도와줄 것입니다:

왼쪽:49개의 이미지 예시. 2번째: 3072개의 상위 144개의 고유벡터(eigenvector). 상위(top) 고유벡터는 데이터의 분산(variance) 대부분을 차지하며, 이미지의 저주파(lower frequencies)에 해당함을 볼 수 있습니다. 3번째t: 144개의 고유벡터를 사용하여 PCA로 축소된(reduced) 49개의 이미지. 즉, 이미지를 특정 지점과 채널에서 특정 픽셀의 밝기를 원소로 갖는 3072차원 벡터로 표현하는 대신, 이미지의 구성에 각각의 고유벡터가 얼만큼 합해졌는지에 대한 값을 원소로 갖는 144차원의 벡터로 표현했습니다. U가 회전(rotation)을 의미하기 때문에, U.transpose()[:144,:]를 곱하면 이 연산을 수행할 수 있고, 그 결과로 나온 3072개의 숫자를 이미지로 시각화할 수 있습니다. 이미지가 상위 고유벡터가 저주파를 포착함으로써 약간 흐려지는(blurrier) 현상을 볼 수 있습니다. 하지만, 대부분의 정보는 그대로 유지됩니다. 오른쪽: 화이트닝의 시각화. 144차원의 각 분산을 같은 길이로 뭉갭니다(squashed). 여기서, 화이트닝된 144개의 숫자들은 U.transpose()[:144,:]를 곱함으로써 다시 이미지 픽셀 기저(basis)로 회전됩니다. (대부분의 분산을 차지하는) 저주파는 이제 무시할 수 있을 만큼 작아지고, (원래는 상대적으로 작은 분산을 차지하는) 고주파가 증폭(exaggerated)됩니다.

실전에서(In practice) 설명의 완전성(completeness)를 위해 PCA/Whitening를 언급하였지만, 이러한 변형(transformations)는 Convolutional Networks에서는 사용되지 않습니다. 하지만, 데이터가 0-중심(zero-center)을 갖게하는 것은 매우 중요하며, 모든 픽셀에 대한 정규화도 흔히 볼 수 있습니다.

흔히 빠지는 함정(Common pitfall) 전처리를 함에 있어 중요한 점은 어떠한 통계치(가령, 데이터 평균)든지 간에 트레이닝 데이터에서만 먼저 계산하고, 그 후에 검증/테스트 데이터에 적용해야 한다는 것입니다. 예를 들어, 모든 이미지에 대한 평균(mean)을 계산하고 이를 전체 데이터셋에 걸쳐 뺀(subtracting) 후에 트레이닝/검증/테스트로 분할하는 것은 올바르지 않습니다. 대신에, 평균은 트레이닝 데이터에 대해서만 계산하고, 그 후 모든 스플릿(트레이닝/검증/테스트)에서 동일하게 빼야합니다(subtracted).

7.1.2 가중치 초기화(Weight Initialization)

Neural Network 아키텍쳐를 구축하고, 데이터를 전처리하는 방법을 알아보았습니다. 네트워크를 학습하기 전에 파라미터를 초기화하는 방법부터 알아보겠습니다.

위험: 모두 0으로 초기화(Pitfall: all zero initialization) 먼저 하지 말아야할 것 부터 알아봅시다. 우리는 학습된 네트워크의 최종 weight가 무엇인지를 알 수 없습니다. 그러나 적절한 데이터 정규화를 통해 대략 절반 정도의 weight는 양수이고 절반은 음수일것이라는 합리적인 추측을 할 수 있습니다. 이 경우 그럴듯한 아이디어는 모든 초기 weight를 0으로 설정하여 “최선의 추측(best guess)”을 하도록 하는 것입니다. 하지만 이는 네트워크의 모든 뉴런이 같은 값을 출력하고, backpropagation을 통해서도 같은 그래디언트를 계산하며 같은 파라미터 업데이트를 진행하게되기 때문에 사실 바람직하지 않습니다. 다시 말해, weight를 모두 같은 값으로 초기화하게 된다면 뉴런 간의 비대칭성(asymmetry)이 사라지게 되는것입니다.

임의의 작은 수(Small random numbers) 따라서, weight를 0에 가깝게 두는 것은 맞지만, 앞서 얘기했듯이 정확히 0으로 둬서는 안됩니다. 이에 대한 해결책으로, 뉴런의 weight를 작은 수로 초기화하는 것이 일반적이며, 이를 대칭성 파괴(symmetry breaking)라고 합니다. 이 아이디어는 뉴런들이 처음에 모두 서로 다른 무작위의 값으로 설정되기 때문에, 서로 다른 방식으로(distinct) 업데이트하며 전체 네트워크의 다양한 부분에 통합(integrate)된다는 것입니다. 하나의 weight 행렬은 다음과 같이 구현됩니다: W = 0.01* np.random.randn(D,H). 여기서 randn은 0의 평균, 표준편차가 1인(unit standard deviation) 가우시안으로 부터 샘플링하는 것을 의미합니다. 이 공식을 통해, 모든 뉴런의 weight 벡터는 다차원 가우시안(multi-dimesional gaussian)에서 샘플링된 랜덤 벡터로 초기화되고, 따라서 뉴런은 입력 공간에서 무작위의 방향을 가리키게 됩니다. 또한 균등한(uniform) 분포로 부터 뽑은(drawn) 작은 숫자들을 사용하는 것도 가능합니다. 하지만 이는 실전에서 최종 성능에 상대적으로 작은 영향을 미칩니다.

주의(Warning) 더 작은 숫자가 반드시 더 잘 동작하지는 않습니다. 예를 들어, 아주 작은 weight를 가진 Neural Network의 레이어는 backpropagation에서 그 데이터에 대해 아주 작은 그래디언트를 계산합니다(그래디언트는 weight의 값에 비례하기 때문). 즉, 작은 weight는 네트워크를 타고 거꾸로 흐르는 “그래디언트 신호”를 상당히 약화시키며 깊은 네트워크에서는 문제가 될 수 있습니다.

1/sqrt(n)을 통한 분산 교정(Calibrating the variances with 1/sqrt(n)) 앞서 제안한 것의 한 가지 문제점은 무작위 초기화(randomly initialized)된 뉴런의 출력 분포(distribution)는 입력의 개수에 따라 커지는 분산(variance)을 가진다는 것입니다. weight 벡터의 크기를 그 fan-in(입력의 개수)의 제곱근(square root)로 나눔으로써 각 뉴런 출력의 분산을 1로 정규화할 수 있습니다. 즉, 권장되는 휴리스틱(heurisitc)은 각 뉴런의 weight 벡터를 다음과 같이 초기화하는것 입니다: w = np.random.randn(n) / sqrt(n), 여기서 n은 입력의 개수입니다. 이는 네트워크의 모든 뉴런들이 처음에 대략적으로 같은 출력 분포를 가지게 하고, 경험에 기인하면(empirically) 수렴 속도(rate of convergence)도 개선합니다.

이는 다음과 같이 유도할 수 있습니다: weight \(w\)와 입력 \(x\)의 내적(inner product) \(s = \sum_i^n w_i x_i\)은 비선형성(non-linearity)을 만나기 전에 뉴런의 활성화(activation)를 의미합니다. 그리고 \(s\)의 분산(variance)을 아래와 같이 구할 수 있습니다:

\[\begin{align} \text{Var}(s) &= \text{Var}(\sum_i^n w_ix_i) \\\\ &= \sum_i^n \text{Var}(w_ix_i) \\\\ &= \sum_i^n [E(w_i)]^2\text{Var}(x_i) + E[(x_i)]^2\text{Var}(w_i) + \text{Var}(x_i)\text{Var}(w_i) \\\\ &= \sum_i^n \text{Var}(x_i)\text{Var}(w_i) \\\\ &= \left( n \text{Var}(w) \right) \text{Var}(x) \end{align}\]

두 번째 줄까지는 분산의 성질(properties of variance)을 이용하여 계산하였습니다. 세 번째 줄에서는 입력과 weight가 0의 평균을 가진다고 가정했으므로, \(E[x_i] = E[w_i] = 0\)가 됩니다. 이것이 일반적인 경우는 아닙니다: 예를 들어, ReLU 유닛의 경우에는 양의 평균을 가집니다. 마지막 단계에서는 모든 \(w_i, x_i\)가 균등하게 분포되어 있다고 가정합니다. 이 유도식에서 \(s\)의 모든 입력 \(x\)와 \(s\)가 같은 분산(variance)을 가지려면, 초기화 중에 모든 weight \(w\)의 분산이 \(1/n\)이 되어야 합니다. 또한 임의의 변수 \(X\)와 스칼라 \(a\)에 대해 \(\text{Var}(aX) = a^2\text{Var}(X)\)를 만족하기 때문에, 분산을 \(1/n\)로 만들기 위해서는 유닛 가우시안에서 임의의 수를 뽑아(draw) 이를 \(a = \sqrt{1/n}\)로 스케일해야합니다. 이는 다음과 같은 초기화를 가져옵니다. w = np.random.randn(n) / sqrt(n)

Understanding the difficulty of training deep feedforward neural networks by Glorot et al.에서도 유사한 분석을 보여줍니다. 논문에서 저자는 \( \text{Var}(w) = 2/(n_{in} + n_{out}) \)와 같은 형태로 초기화 하기를 권장합니다. 여기서 \(n_{in}, n_{out}\)는 이전 레이어와 다음 레이어의 유닛 수를 의미합니다. 이는 backpropagated 그래디언트에 대한 합의와 동일한 분석에 근거합니다. 이 주제에 관한 보다 최신 논문인, Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification by He et al.에서는 ReLU 뉴런에 특화된 초기화를 유도하며, 네트워크 내 뉴런의 분산을 \(2.0/n\)로 해야 한다는 결론에 도달합니다. 현재, ReLU 뉴런을 가지는 neural networks우를 사용할 경우, w = np.random.randn(n) * sqrt(2.0/n)와 같이 초기화하는 것을 권장하고 있습니다.

듬성듬성한 초기화(Sparse initialization) 교정되지 않은 분산(uncalibrated variance) 문제를 해결하기 위한 또 다른 방법은 모든 weight 행렬을 0으로 설정하는 것입니다. 하지만 대칭성(symmetry)을 깨기 위해 모든 뉴런이 무작위로 그 아래 고정된 수의 뉴런과 연결되어 있습니다(위와 같이 작은 가우시안에서 샘플링합니다). 일반적으로 연결되는 뉴런의 수는 10개 정도로 작습니다.

bias 초기화(Initializing the biases) weight는 임의의 작은 수에 의해 비대칭성(asymmeting)을 띄게 되므로, 일반적으로 bias는 0으로 초기화합니다. ReLU 비선형성(non-linearities)의 경우, 모든 bias로 0.01과 같은 작은 상수 값을 사용하면, 모든 ReLU 유닛이 초기에 발사(fire)되어 어느 정도의 그래디언트를 얻고 전파(propagete)되기 때문에 이를 선호되는 경우도 있습니다. 그러나 이것이 일관된 개선을 제공하는지는 불분명하며(사실상 일부 결과는 더 나쁜 성능을 발휘함을 보이기도 합니다), 단순히 bias를 0으로 초기화하는 것이 더 일반적입니다.

실전에서, 현재는 ReLU와 w = np.random.randn(n) * sqrt(2.0/n)를 사용하길 권장합니다(He et al. 참고).

배치 정규화(Batch Normalization) Batch Normalization은 최근 Ioffe와 Szegedy에 의해 개발된 기법으로, 네트워크 전체에 걸쳐 명시적으로 활성화(activations)를 가하여, 학습 시작 시 단위 가우스 분포(unit gaussian distribution)를 취하도록 해줍니다. 이렇게 neural netwroks를 적절히 초기화함으로써 많은 골칫거리를 덜어주었습니다. 이것이 가능한 이유는 정규화가 단순한 미분이기 때문이라는 것이 핵심입니다. 구현을 할 때는 fully connected layer(또는 convolutional layers) 바로 뒤와 비선형성(non-linearities) 앞에 BatchNorm layer를 넣는것이 일반적입니다. 자세한 내용은 링크에 나와있으며, 여기서는 이 기법에 대해 자세히 다루지는 않습니다. Neural Netwrok에 배치 정규화를 사용하는 것은 굉장히 일반적인 관행이 되었습니다. 실전에서 배치정규화를 사용하는 네트워크는 바람직하지 않은 초기화에 대해 훨씬 강건(robust)합니다. 추가로, 배치정규화는 네트워크의 모든 레이어에서의 전처리이면서, 동시에 미분의 방식으로 네트워크에 통합되는것으로 해석될 수 있습니다.

7.1.3 Regularization

오버피팅(overfitting)을 방지하기 위해 Neural Networks의 용량(capacity)를 제어하는 여러 가지 방법들을 제시합니다:

L2 regularization은 가장 보편적인 형태의 regularization 입니다. 이는 objective에서 모든 파라미터의 제곱 크기(squared magnitude)를 직접 제약하는(penalizing) 방식으로 구현됩니다. 즉, 네트워크의 모든 weight \(w\)에 대해, objective에 \(\frac{1}{2} \lambda w^2\) 항을 더해줍니다. 여기서 \(\lambda\)는 regularization 강도(strength)입니다. 일반적으로 앞에 \(\frac{1}{2}\)가 붙는데, 이는 파라미터 \(w\)에 대한 이 항의 그래디언트를 \(2 \lambda w\)가 아닌 \(\lambda w\)로 표시할 수 있기 때문입니다. 직관적으로 L2 regularization은 피크가 있는(peaky) weight 벡터는 강력하게 제약하고, 퍼져있는(diffuse) weight 벡터를 선호한다고 이해할 수 있습니다. Linear Classfication에서도 이야기 했듯이, weight와 입력의 곱 연산은 네트워크가 몇몇개의 입력만 많이 사용하기 보다 모든 입력을 고르게 사용하도록 하는 매력적인 특성을 지닙니다. 마지막으로, gradient descent 파라미터 업데이트에서, 최종적으로 L2 regularization을 사용하는 것은 모든 weight가 선형적으로 감소됨(decayed)을 의미합니다: W += -lambda * W는 0을 향해 줄어듭니다.

L1 regularization 또한 보편적으로 쓰이는 regularization의 형태 중 하나입니다. 여기서는 각각의 weight \(w\)에 대해, objective에 \(\lambda \mid w \mid\) 항을 더해줍니다. L1 regularization과 L2 regularization를 함께 사용하는 방법도 있습니다: \(\lambda_1 \mid w \mid + \lambda_2 w^2\) (이는 Elastic net regularization이라고 불립니다). L1 regularization은 최적화 중에 weight 벡터를 sparse하게(즉, 0에 매우 가깝게) 만들어준다는 아주 흥미로운 특성이 있습니다. 다시 말해, L1 regularization을 적용한 뉴런은 결국 가장 중요한 입력의 sparse한 부분집합만을 사용하게 되며 “노이즈가 있는(noisy)” 입력이 들어와도 거의 변하지 않게(invariant) 됩니다. 반면, L2 regularization의 최종 weight 벡터는 대개 퍼져있고(diffuse) 작은 수입니다. 실전에서, 명시적인(explicit) feature 선택을 고려하지 않아도 된다면, L2 regularization을 사용하는 것이 L1에 비해 더 우수한 성능을 보일것입니다.

Max norm constraints. Max norm은 모든 뉴런의 절대값의 상한치(absolute upper bound)를 weight 벡터의 크기(magnitude)로 설정하고, 제약을 가하기 위해 projected gradient descent를 사용하는 regularization 방법입니다. 이는 일반적인 파라미터 업데이트를 수행하고, 이후에 모든 뉴런의 weight 벡터 \(\vec{w}\)를 clamping함으로써 \(\Vert \vec{w} \Vert_2 < c\)를 만족하도록 제약하는 것과 같습니다. \(c\)는 일반적으로 3 또는 4입니다. 간혹 Max norm을 사용할 때 더 좋은 성능을 보이기도 합니다. 이것의 매력적인 특성 중 하나는 학습속도(learning rate)가 너무 높게 설정되어 있는 경우에도, 업데이트의 상한선이 정해져(bounded) 있기 때문에 네트워크가 “발산(explode)”하지 않는다는 것입니다.

Dropout은 최근에 도입된 regularization 기법으로 매우 효과적이고 단순하면서 다른 방법들을(L1, L2, maxnorm) 보완합니다. Srivastava et al. Dropout: A Simple Way to Prevent Neural Networks from Overfitting (pdf). 드롭아웃(dropout)은 학습과정 동안 단순히 특정 확률 \(p\) (하이퍼파라미터)로 뉴런을 활성화(active)하거나, 비활성화(0)함으로써 구현할 수 있습니다.

드롭아웃(Dropout) 논문에 제시된 그림으로, 아이디어를 간단히 설명합니다. 학습과정 동안, 드롭아웃은 전체 Neural Network에서 Neural Network를 샘플링하는 것으로 해석할 수 있습니다. 그리고 입력 데이터에 근간하여 샘플링된 네트워크의 파라미터만을 업데이트합니다. (하지만, 샘플링된 네트워크의 무수히 많은 경우의 수들이 전부 독립적이지는 않습니다. 이는 파라미터를 공유(share)하기 때문입니다) 테스트 동안에는 드롭아웃이 적용되지 않으며, 엄청난 크기의 부분 네트워크들이 앙상블(ensemble)되고, 그 결과들의 평균으로 예측(averaged prediction)한다고 해석할 수 있습니다. (ensemble에 대해서는 다음 장에서 다룹니다)

3 레이어 Neural Network에서 기본(Vanilla) 드롭아웃은 다음과 같이 구현할 수 있습니다:

""" Vanilla Dropout: Not recommended implementation (see notes below) """

p = 0.5 # probability of keeping a unit active. higher = less dropout

def train_step(X):
  """ X contains the data """
  
  # forward pass for example 3-layer neural network
  H1 = np.maximum(0, np.dot(W1, X) + b1)
  U1 = np.random.rand(*H1.shape) < p # first dropout mask
  H1 *= U1 # drop!
  H2 = np.maximum(0, np.dot(W2, H1) + b2)
  U2 = np.random.rand(*H2.shape) < p # second dropout mask
  H2 *= U2 # drop!
  out = np.dot(W3, H2) + b3
  
  # backward pass: compute gradients... (not shown)
  # perform parameter update... (not shown)
  
def predict(X):
  # ensembled forward pass
  H1 = np.maximum(0, np.dot(W1, X) + b1) * p # NOTE: scale the activations
  H2 = np.maximum(0, np.dot(W2, H1) + b2) * p # NOTE: scale the activations
  out = np.dot(W3, H2) + b3

상단의 코드를 보면, train_step 함수에서 첫 번째, 두 번째 hidden layer에 두 번의 드롭아웃을 수행하였습니다. 입력 레이어 X에서 바로 드롭아웃을 수행하는 것 또한 가능합니다. 이는 입력 X에 대해 이진 마스크(binary mask)를 만드는것과 같은 효과입니다. backward pass는 이전과 같지만, 당연히 마스크 U1,U2를 고려해야 합니다.

결정적으로 predict 함수에서는 더 이상 drop을 하지 않지만, 두 hidden layer 출력을 \(p\)에 의해 스케일링(scaling)합니다. 이는 테스트 시 모든 뉴런은 각 뉴런으로 들어오는 모든 입력을 고려하기 때문에 중요합니다. 따라서 테스트 시 뉴런의 출력이 학습 시 예상되는 출력과 같기를 기대합니다. 예를 들어 \(p = 0.5\)인 경우, 뉴런의 출력을 절반으로 줄여야(halve) 테스트 시 출력이 학습 시 출력 결과와 같아질 수 있습니다. 이를 확인하기 위해, (드롭아웃을 적용하기 전) 뉴런 \(x\)의 출력을 고려합니다. 드롭아웃을 적용하면, 뉴런의 출력이 \(1-p\)의 확률로 0으로 설정되기 때문에, 이 뉴런의 예측 출력은 \(px + (1-p)0\)이 될 것입니다. 테스트 시, 뉴런을 항상 활성화(active)한다면, 같은 예상 출력을 유지하기 위해 \(x \rightarrow px\)를 조정해야 합니다. 테스트 시 이 감쇠(attenuation)를 수행하는 것은 모든 경우의 이진 마스크(binary mask)를 반복하고 그 앙상블(ensemble)된 예측을 계산하는 과정과 비슷합니다.

앞서 보인 dropout은 테스트 시에 활성화(activations)를 \(p\)로 스케일(scale)하기 때문에 테스트 시간이 길어집니다. 테스트 시간 성능은 매우 중요하기 때문에, 학습 시 스케일링을 수행하는 역 드롭아웃(inverted dropout)을 사용하는 것이 바람직하며, 테스트 시 forward pass는 이전과 같습니다. 또한 드롭아웃을 적용할 지점을 수정(tweak)할 때 예측 코드(predict 함수)를 그대로 사용할 수 있는 매력적인 특성이 있습니다. 역 드롭아웃(Inverted dropout)은 다음과 같이 구현할 수 있습니다:

""" 
Inverted Dropout: Recommended implementation example.
We drop and scale at train time and don't do anything at test time.
"""

p = 0.5 # probability of keeping a unit active. higher = less dropout

def train_step(X):
  # forward pass for example 3-layer neural network
  H1 = np.maximum(0, np.dot(W1, X) + b1)
  U1 = (np.random.rand(*H1.shape) < p) / p # first dropout mask. Notice /p!
  H1 *= U1 # drop!
  H2 = np.maximum(0, np.dot(W2, H1) + b2)
  U2 = (np.random.rand(*H2.shape) < p) / p # second dropout mask. Notice /p!
  H2 *= U2 # drop!
  out = np.dot(W3, H2) + b3
  
  # backward pass: compute gradients... (not shown)
  # perform parameter update... (not shown)
  
def predict(X):
  # ensembled forward pass
  H1 = np.maximum(0, np.dot(W1, X) + b1) # no scaling necessary
  H2 = np.maximum(0, np.dot(W2, H1) + b2)
  out = np.dot(W3, H2) + b3

드롭아웃이 처음 도입된 이래로 실전에서 그 성능의 원리에 대한 이해와 다른 regularization기법과의 관계에 대한 많은 연구가 이루어졌습니다. 관심이 있다면 아래 내용을 읽어보시길 바랍니다:

Theme of noise in forward pass 드롭아웃은 네트워크의 forward pass에서 확률의 개념(stochastic behavior)을 도입하는 보다 일반적인 방법으로 분류됩니다. 테스트 시, 노이즈는 분석적으로(analytically)(일반적인 드롭아웃의 경우와 같이 \(p\)를 곱하는 경우), 또는 수치적으로(numerically)(샘플링을 통해, 즉, 무작위의 결정을 통해 여러번의 forward pass를 반복하고 그 평균을 구하는 경우) 무시할 수 있게(marginalized) 처리됩니다. 이와 관련한 다른 연구는 DropConnect가 있습니다. 여기서는 무작위의 weight 집합이 forward pass 중에 0으로 설정됩니다. 예고를 하자면, Convolutional Neural Networks또한 stochastic pooling, fractional pooling, data augmentation과 같은 방법을 통해 이 theme의 장점을 가집니다. 이후에 이러한 방법에 대해 자세히 다루겠습니다.

Bias regularization Linear Classification을 다루면서 이야기 했듯이, bias 파라미터를 regularize하는 일은 흔치 않습니다. bias는 데이터와 곱(multiplicative)으로 연산되지 않기 때문에, 최종 objective의 데이터 차원에 크게 영향을 주지 않습니다. 하지만 실전에서 (적절한 데이터 전처리와 함께) bias를 regularizing하게 되면 드물게 매우 저조한 성능을 보일 때가 있습니다. 이는 weight에 비해 bias항은 매우 적기 때문입니다. 따라서 분류기가 더 좋은 data loss를 얻기 위해 bias를 “사용할 여력이 있다면(afford to)” 사용할 수 있습니다.

레이어 당(Per-layer) regularization 레이어마다 서로 다른 크기(amount)로 regularize하는 것은 매우 흔치 않은 일입니다(출력 레이어를 제외하면). 이 아이디어를 다룬 논문들은 비교적 많지 않습니다.

실전에서 cross-validation을 통해 하나의 글로벌한 L2 regularization 강도(strength)를 사용하는 것이 가장 일반적입니다. 또한 이를 모든 레이어 이후에 적용된 드룹아웃과 결합하는 것이 일반적입니다. \(p = 0.5\)는 합리적인 디폴트(default)이지만, 이 또한 validation data를 통해 조정(tune)될 수 있습니다.

7.1.4 손실함수(Loss functions)

앞서 objective를 구성하는 regularization loss에 대해 다루어보았습니다. 이는 모델 복잡도를 제한(penalizing)하는 것으로 이해할 수 있습니다. objective를 구성하는 두 번째 파트는 data loss입니다. 이는 예측(분류에서의 클래스 점수)과 ground truth label사이의 일치도(compatibility)를 측정하는 지도학습 문제에 해당합니다. data loss는 모든 개별 데이터에 대한 data loss의 평균을 의미합니다. 즉, \(L = \frac{1}{N} \sum_i L_i\)이고, 여기서 \(N\)은 트레이닝 데이터의 개수입니다. \(f = f(x_i; W)\)를 Neural Network 출력 레이어의 활성화(activation)라고 축약해봅시다. 아래는 실전에서 해결해야 할 여러가지 유형의 문제들 입니다:

분류(Classification)에 대해서는 지금까지 계속해서 설명하였습니다. 데이터셋과 각 데이터에 대해 하나의 올바른 라벨이 있다고 가정합니다. 분류에서 가장 흔히 볼 수 있는 두 가지 비용함수(cost function) 중 하나는 SVM입니다(Weston Watkins 공식):

\[L_i = \sum_{j\neq y_i} \max(0, f_j - f_{y_i} + 1)\]

앞서 언급했듯이, (\(\max(0, f_j - f_{y_i} + 1)^2\)를 사용할 때 보다) sqaured hinge loss가 간혹 더 좋은 성능을 보일 때도 있습니다. 두 번째 일반적인 선택은 cross-entropy loss를 사용하는 Softmax classifier입니다:

\[L_i = -\log\left(\frac{e^{f_{y_i}}}{ \sum_j e^{f_j} }\right)\]

클래스의 개수가 매우 많은 경우(Problem: Large number of classes) 라벨 집합이 매우 큰 경우(가령, 영어사전의 단어, 22000개의 카테고리를 가지는 ImageNet), Hierarchical Softmax (pdf)를 사용하는 것이 유용할 수 있습니다. 이는 라벨을 트리(tree)의 형태로 분해(decompose)합니다. 그러면 각 라벨은 트리의 길(path)로 표현되고, Softmax classifier는 트리의 모든 노드에서 학습되어 왼쪽과 오른쪽 가지(branch)를 명확히 구분(disambiguate)합니다. 트리의 구조는 성능에 확실한 영향을 미치고 이는 문제에 따라 다릅니다(problem-dependent).

속성 분류(Attribute classification) 위의 두 가지 loss는 하나의 올바른 정답 \(y_i\)가 있다고 가정합니다. 하지만 만약 \(y_i\)가 모든 데이터가 특정 속성이 있는지, 없는지를 나타내는 이진 벡터(binary vector)이고, 속성이 배제적(exclusive)이지 않다면 어떨까요? 가령, 인스타그램의 이미지를 모든 해시태그(hashtag) 전체집합 중 특정 해시태그의 부분집합으로 라벨링된 것으로 간주할 수 있습니다. 여기서 이미지는 여러 개의 해시태그를 가질 수 있습니다. 이 경우 합리적인 접근은 모든 단일 속성에 대해 이진 분류기(binary classifire)를 만드는 것입니다. 예를 들어, 각 카테고리에 대한 이진 분류기는 다음과 같은 형태를 가집니다:

\[L_i = \sum_j \max(0, 1 - y_{ij} f_j)\]

여기서 \(j\)개의 모든 카테고리에 대해 합계가 되고, \(y_{ij}\)는 i번째 예시가 j번째 속성으로 라벨링 되었는지 여부에 따라 +1 또는 -1입니다. 또한 클래스가 존재하는 것(present)으로 예측된 경우 벡터 \(f_j\) 는 양수이고, 그렇지 않은 경우에는 음수일 것입니다. 양의 예시가 +1보다 작은 점수를 가지거나, 음의 예시가 -1보다 큰 점수를 가지는 경우 loss가 누적된다는 점에 유의합니다.

이 loss를 사용하는 대신 모든 개별 속성에 대하여 로지스틱 회귀 분류기(logistic regression classifier)를 학습시킬 수 있습니다. 이진(binary) 로지스틱 회귀분류기는 두 개 의 클래스 (0,1)만을 가지며, 클래스 1에 대한 확률을 다음과 같이 계산합니다:

\[P(y = 1 \mid x; w, b) = \frac{1}{1 + e^{-(w^Tx +b)}} = \sigma (w^Tx + b)\]

클래스 1과 0에 대한 확률의 합은 1이기 때문에, 클래스 0에 대한 확률 \(P(y = 0 \mid x; w, b) = 1 - P(y = 1 \mid x; w,b)\)이 됩니다. 따라서, 만약 \(\sigma (w^Tx + b) > 0.5\) 또는 \(w^Tx +b > 0\)일 때, 예시는 양의 예시 (y = 1)로 분류됩니다. loss function은 이 확률의 log likelihood를 최대화합니다. 즉, 다음과 같이 단순화됨을 알 수 있습니다:

\[L_i = \sum_j y_{ij} \log(\sigma(f_j)) + (1 - y_{ij}) \log(1 - \sigma(f_j))\]

여기서 라벨 \(y_{ij}\)는 1(양) 또는 0(음)이 되며, \(\sigma(\cdot)\)은 시그모이드 함수를 의미합니다. 위의 식은 다소 무섭게 생기긴 했지만 \(f\)의 그래디언트는 사실 매우 간단하고 직관적입니다: \(\partial{L_i} / \partial{f_j} = y_{ij} - \sigma(f_j)\) (미분을 가지고 혼자서 다시 계산해볼 수도 있습니다).

회귀(Regression)는 실제 값(quantity)을 예측하는 문제입니다. 가령, 집값이나 이미지 내의 길이를 예측하는 것에 해당합니다. 이 문제에서는 일반적으로 예측된 값과 실제 정답간의 loss를 계산한 후에 L2 squared norm, 또는 그 차이의 L1 norm을 계산합니다. L2 norm 제곱은 다음 예시의 loss를 계산합니다:

\[L_i = \Vert f - y_i \Vert_2^2\]

objective에서 L2 norm이 제곱된 이유는, 제곱이 단조연산(monotonic operation)이기 때문에 파라미터를 건드리지 않고도 그래디언트를 훨씬 간단하게 만들기 때문입니다. L1 norm은 각 차원의 절댓값을 합하는 것으로 공식화됩니다:

\[L_i = \Vert f - y_i \Vert_1 = \sum_j \mid f_j - (y_i)_j \mid\]

여기서 합 \(\sum_j\)은 예측값(quantity)이 한 개 보다 많을 경우, 모든 차원에 걸친 기대 예측의 합을 의미합니다. i번째 예시의 j번째 차원만 보았을 때, 참값과 예측값의 차이를 $\delta_{ij}$로 나타낸다면, 이 차원에 대한 그래디언트(\(\partial{L_i} / \partial{f_j}\))는 L2 norm에 의해 \(\delta_{ij}\) 또는\(sign(\delta_{ij})\)로 유도됩니다. 즉, 점수에 대한 그래디언트는 차이에 비례하거나, 값은 고정되고 차이의 부호만 상속받습니다.

주의(Word of caution) L2 loss은 Softmax과 같은 안정적인 loss보다 최적화하기가 훨씬 어렵다는 점에 유의합니다. 직관적으로, 이는 각 입력(과 입력의 증강(augmentation))에 대해 하나의 정확한 정답을 출력하기 위해 네트워크로부터 매우 취약한(fragile) 특정 속성을 필요로 합니다. Softmax의 경우에는 정확한 점수가 덜 중요합니다: 적절한 크기(magnitude)만이 중요합니다. 또한, 동떨어진 값(outlier)이 큰 기울기를 가져올 수 있기 때문에 L2 loss는 덜 강건(robust)하다고 할 수 있습니다. 회귀(regrassion) 문제을 맞닦뜨렸을 때, 먼저 출력을 이진(bin)으로 양자화(quantize)하는 것이 절대적으로 부적절한지 고려한다. 예를 들어, 제품에 대한 별 등급(star rating)을 예측할 때, regression loss 대신 1-5개의 별 등급에 대해 5개의 개별 분류기를 사용하는 것이 훨씬 효과적일 수 있습니다. 분류는 신뢰도(confidence)를 알려주지 않는 단 하나의 출력이 아닌, 회귀 출력에 대한 분포를 줄 수 있다는 추가적인 이점을 가지고 있습니다. 분류가 적절하지 않은 것이 확실하다면, L2를 사용하되 주의해야 합니다: 예를 들어, L2가 더 취약하며, 네트워크(특히 L2 loss 바로 앞의 레이어)에 드롭아웃(dropout)을 적용하는 것은 좋은 아이디어가 아닙니다.

회귀(regression) 문제를 맞닦뜨리면, 절대적으로 필요한지를 먼저 생각해봅니다. 가능하다면 회귀를 적용하는 대신 출력을 이진(bin)으로 이산화(discretizing)하여 분류를 적용하는 것이 강력히 권장됩니다.

구조적 예측(Structured prediction) 구조적 손실(Structured loss)이란 라벨이 그래프, 트리 또는 기타 복잡한 객체와 같은 임의의 구조를 가질 때를 말합니다. 일반적으로 구조의 공간이 매우 커서 쉽게 열거(enumerable)할 수 없다고 가정합니다. Structured SVM loss의 기본 아이디어는 올바른 구조 \(y_i\)와 가장 높은 점수를 받는 틀린 구조 사이의 마진(margin)을 계산하는 것입니다. 일반적으로 이 문제를 단순히 제약되지 않은(unconstrained) gradient descent 최적화 문제로 여기지는 않습니다. 대신에 특수한 해결책을 고안하여 구조 공간을 단순화(simplifying)할 수 있는 특정 가정을 이용합니다. 이 문제에 대해서는 이 정도로만 간단히 언급하고 넘어가겠습니다.

7.2 요약(Summary)

요약하자면:

  • 전처리(preprocessing)를 통해 데이터의 중앙이 0의 평균을 가지게 하고, 이를 각 feature에 대해서 [-1, 1]의 크기로 정규화하는 것을 권장합니다.
  • 표준편차(standard deviation)가 \(\sqrt{2/n}\)인 가우시안 분포(gaussian distribution)에서 임의의 값을 뽑아(draw) weight를 초기화합니다. 여기서 \(n\)은 뉴런으로 들어가는 입력의 개수입니다. 가령, numpy로는 다음과 같이 구현할 수 있습니다: w = np.random.randn(n) * sqrt(2.0/n).
  • L2 regularization과 역 드롭아웃(inverted dropout)을 사용합니다
  • 배치정규화(batch normalization)를 사용합니다.
  • 실전에서 맞닦뜨리는 여러가지 문제와, 그마다 사용되는 가장 일반적인 loss function을 다루었습니다.

지금까지 데이터 전처리(preprocessed)와 모델 초기화(initialized)를 알아보았습니다. 다음장에서는 학습과정과 그 역학(dynamics)에 대해 살펴보겠습니다.