TensorFlow에서 경사하강법의 다양한 응용

게시 됨: 2022-03-11

Google의 TensorFlow는 딥 러닝 모델을 교육하고 배포하기 위한 최고의 도구 중 하나입니다. 수억 개의 매개변수를 사용하여 매우 복잡한 신경망 아키텍처를 최적화할 수 있으며 하드웨어 가속, 분산 교육 및 프로덕션 워크플로를 위한 다양한 도구와 함께 제공됩니다. 이러한 강력한 기능으로 인해 딥 러닝 영역 밖에서는 위협적이고 불필요한 것처럼 보일 수 있습니다.

그러나 TensorFlow는 딥 러닝 모델 교육과 직접 관련이 없는 더 간단한 문제에 액세스할 수 있고 사용할 수 있습니다. 핵심적으로 TensorFlow는 텐서 연산(벡터, 행렬 등)과 임의의 계산 시퀀스에서 경사하강법을 수행하는 데 사용되는 미적분 연산에 최적화된 라이브러리입니다. 숙련된 데이터 과학자는 "경사 하강법"을 계산 수학의 기본 도구로 인식하지만 일반적으로 응용 프로그램별 코드 및 방정식을 구현해야 합니다. 여기서 살펴보겠지만 TensorFlow의 현대적인 "자동 차별화" 아키텍처가 여기에 있습니다.

TensorFlow 사용 사례

  • 예 1: TensorFlow 2.0에서 경사하강법을 사용한 선형 회귀
    • 경사하강법이란?
  • 예제 2: 최대로 퍼진 단위 벡터
  • 예 3: 적대적 AI 입력 생성
  • 최종 생각: 경사하강법 최적화
  • TensorFlow의 경사하강법: 최소값 찾기에서 AI 시스템 공격까지

예 1: TensorFlow 2.0에서 경사하강법을 사용한 선형 회귀

예 1 노트북

TensorFlow 코드를 사용하기 전에 경사 하강법 및 선형 회귀에 익숙해지는 것이 중요합니다.

경사하강법이란?

가장 간단한 용어로, 출력을 최소화하는 방정식 시스템에 대한 입력을 찾는 수치적 기술입니다. 머신 러닝의 맥락에서 방정식 시스템은 우리의 모델 이고 입력은 모델의 알려지지 않은 매개변수 이며 출력은 최소화할 손실 함수 로 모델과 데이터 사이에 얼마나 많은 오류가 있는지 나타냅니다. 선형 회귀와 같은 일부 문제의 경우 오류를 최소화하는 매개변수를 직접 계산하는 방정식이 있지만 대부분의 실제 응용 프로그램에서는 만족스러운 솔루션에 도달하기 위해 경사하강법과 같은 수치 기술이 필요합니다.

이 기사의 가장 중요한 점은 경사하강법은 일반적으로 방정식을 배치하고 손실 함수와 매개변수 간의 관계를 도출하기 위해 미적분학을 사용해야 한다는 것입니다. TensorFlow(및 모든 최신 자동 미분 도구)를 사용하면 미적분학이 처리되므로 구현에 시간을 할애할 필요 없이 솔루션 설계에 집중할 수 있습니다.

다음은 단순 선형 회귀 문제에서 보이는 것입니다. 우리는 150명의 성인 남성의 키(h)와 몸무게(w)의 샘플을 가지고 있으며 이 선의 기울기와 표준 편차에 대한 불완전한 추측으로 시작합니다. 경사하강법을 약 15번 반복한 후 거의 최적의 솔루션에 도달합니다.

두 개의 동기화된 애니메이션. 왼쪽은 데이터에서 멀리 떨어진 적합선이 데이터를 향해 빠르게 이동하여 최종 적합을 찾기 전에 속도가 느려지는 높이-가중 산점도를 보여줍니다. 올바른 크기는 각 프레임이 그래프에 새로운 반복을 추가하면서 손실 대 반복의 그래프를 보여줍니다. 손실은 2,000에서 그래프 상단에서 시작하지만 로그 곡선으로 보이는 몇 번의 반복 내에서 최소 손실 선에 빠르게 접근합니다.

TensorFlow 2.0을 사용하여 위의 솔루션을 어떻게 생성했는지 봅시다.

선형 회귀의 경우 높이의 선형 방정식으로 가중치를 예측할 수 있다고 말합니다.

w-subscript-i, pred는 알파 내적 h-subscript-i에 베타를 더한 것과 같습니다.

예측과 실제 값 사이의 평균 제곱 오차(손실)를 최소화하는 매개변수 α 및 β(기울기 및 절편)를 찾고 싶습니다. 따라서 손실 함수 (이 경우 "평균 제곱 오차" 또는 MSE)는 다음과 같습니다.

MSE는 1의 N 곱하기 i의 합이 w-subscript-i,true와 w-subscript-i,pred 간의 차이의 제곱의 1 대 N과 같습니다.

평균 제곱 오차가 몇 개의 불완전한 선을 찾은 다음 정확한 솔루션을 찾는 방법을 볼 수 있습니다(α=6.04, β=-230.5).

각각 다른 적합선이 있는 동일한 높이-가중치 산점도의 복사본 3개. 첫 번째는 w = 4.00 * h + -120.0이고 손실은 1057.0입니다. 선은 데이터 아래에 있고 그보다 덜 가파르다. 두 번째는 w = 2.00 * h + 70.0이고 손실은 720.8입니다. 선은 데이터 포인트의 위쪽 부분에 가깝고 덜 가파르다. 세 번째는 w = 60.4 * h + -230.5이고 손실은 127.1입니다. 선은 데이터 포인트를 통과하여 주변에 고르게 클러스터된 것처럼 보입니다.

TensorFlow를 사용하여 이 아이디어를 실행해 보겠습니다. 가장 먼저 할 일은 텐서와 tf.* 함수를 사용하여 손실 함수를 코딩하는 것입니다.

 def calc_mean_sq_error(heights, weights, slope, intercept): predicted_wgts = slope * heights + intercept errors = predicted_wgts - weights mse = tf.reduce_mean(errors**2) return mse

이것은 매우 간단해 보입니다. 모든 표준 대수 연산자는 텐서에 대해 오버로드되므로 최적화하는 변수가 텐서인지 확인하기만 하면 되며 다른 모든 작업에는 tf.* 메서드를 사용합니다.

그런 다음 우리가 해야 할 일은 이것을 경사 하강 루프에 넣는 것입니다.

 def run_gradient_descent(heights, weights, init_slope, init_icept, learning_rate): # Any values to be part of gradient calcs need to be vars/tensors tf_slope = tf.Variable(init_slope, dtype='float32') tf_icept = tf.Variable(init_icept, dtype='float32') # Hardcoding 25 iterations of gradient descent for i in range(25): # Do all calculations under a "GradientTape" which tracks all gradients with tf.GradientTape() as tape: tape.watch((tf_slope, tf_icept)) # This is the same mean-squared-error calculation as before predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors**2) # Auto-diff magic! Calcs gradients between loss calc and params dloss_dparams = tape.gradient(loss, [tf_slope, tf_icept]) # Gradients point towards +loss, so subtract to "descend" tf_slope = tf_slope - learning_rate * dloss_dparams[0] tf_icept = tf_icept - learning_rate * dloss_dparams[1]

이것이 얼마나 깔끔한지 잠시 감상해 봅시다. 경사하강법은 최적화하려는 모든 변수에 대한 손실 함수의 도함수를 계산해야 합니다. 미적분학이 관련되어야 하지만 실제로는 아무 것도 하지 않았습니다. 마법은 다음과 같은 사실에 있습니다.

  1. TensorFlow는 tf.GradientTape() 에서 수행된 모든 계산의 계산 그래프를 작성합니다.
  2. TensorFlow는 모든 연산의 도함수(기울기)를 계산하는 방법을 알고 있으므로 계산 그래프의 변수가 다른 변수에 미치는 영향을 결정할 수 있습니다.

다른 시작점에서 프로세스가 어떻게 보입니까?

이전과 동일한 동기화된 그래프이지만 비교를 위해 그 아래에 있는 유사한 그래프 쌍에도 동기화됩니다. 낮은 쌍의 손실 반복 그래프는 비슷하지만 더 빨리 수렴하는 것 같습니다. 해당하는 적합선은 데이터 포인트 아래가 아닌 위에서 시작하여 최종 휴지 위치에 더 가깝습니다.

Gradient descent는 최적의 MSE에 놀라울 정도로 가까워지지만 실제로 두 예에서 최적과 실질적으로 다른 기울기와 절편으로 수렴합니다. 어떤 경우에는 이것은 단순히 지역 최소값으로 수렴하는 경사 하강이며, 이는 경사 하강 알고리즘의 고유한 문제입니다. 그러나 선형 회귀는 증명할 수 있는 전역 최소값이 하나만 있습니다. 그렇다면 우리는 어떻게 잘못된 기울기와 절편에 이르렀습니까?

이 경우 문제는 데모를 위해 코드를 지나치게 단순화했다는 것입니다. 우리는 데이터를 정규화하지 않았고, 기울기 매개변수는 절편 매개변수와 다른 특성을 가지고 있습니다. 기울기의 작은 변화는 손실에 큰 변화를 줄 수 있지만 절편의 작은 변화는 거의 영향을 미치지 않습니다. 훈련 가능한 매개변수의 규모에서 이러한 큰 차이는 기울기 계산을 지배하는 기울기로 이어지며 절편 매개변수는 거의 무시됩니다.

따라서 경사 하강법은 초기 절편 추측에 매우 가까운 최상의 기울기를 효과적으로 찾습니다. 그리고 오류가 최적에 매우 가깝기 때문에 주변의 기울기가 작기 때문에 각 연속 반복은 아주 약간만 이동합니다. 먼저 데이터를 정규화하면 이 현상이 크게 개선되었지만 제거되지는 않았을 것입니다.

이것은 비교적 간단한 예였지만 다음 섹션에서 이 "자동 미분" 기능이 꽤 복잡한 것들을 처리할 수 있다는 것을 알게 될 것입니다.

예제 2: 최대로 퍼진 단위 벡터

예 2 노트북

이 다음 예제는 작년에 수강한 딥 러닝 과정의 재미있는 딥 러닝 실습을 기반으로 합니다.

문제의 요점은 32개의 정규 분포 숫자 집합에서 사실적인 얼굴을 생성할 수 있는 "변형 자동 인코더"(VAE)가 있다는 것입니다. 용의자 식별을 위해 VAE를 사용하여 증인이 선택할 다양한 (이론적) 얼굴 세트를 생성한 다음 선택된 얼굴과 유사한 더 많은 얼굴을 생성하여 검색 범위를 좁히고자 합니다. 이 연습에서는 초기 벡터 세트를 무작위화하는 것이 제안되었지만 최적의 초기 상태를 찾고 싶었습니다.

문제를 다음과 같이 표현할 수 있습니다. 32차원 공간이 주어지면 최대로 떨어져 있는 X 단위 벡터 세트를 찾습니다. 2차원에서 이것은 정확하게 계산하기 쉽습니다. 그러나 3차원(또는 32차원!)의 경우 정답은 없습니다. 그러나 목표 상태에 도달했을 때 최소인 적절한 손실 함수를 정의할 수 있다면 경사 하강법이 우리가 거기에 도달하는 데 도움이 될 수 있습니다.

두 개의 그래프. 왼쪽 그래프인 모든 실험의 초기 상태에는 중심점이 다른 점에 연결되어 있으며 거의 ​​모든 점이 주위에 반원을 형성합니다. 한 점은 대략 반원 반대편에 있습니다. 오른쪽 그래프인 대상 상태는 바퀴와 같으며 스포크가 고르게 펼쳐져 있습니다.

우리는 위에 표시된 대로 20개 벡터의 무작위 세트로 시작하고 TensorFlow의 기능을 보여주기 위해 각각 점점 더 복잡해지는 3개의 다른 손실 함수로 실험할 것입니다.

먼저 훈련 루프를 정의합시다. 우리는 모든 TensorFlow 로직을 self.calc_loss() 메서드 아래에 둘 것이며, 이 루프를 재활용하여 각 기술에 대해 해당 메서드를 간단히 재정의할 수 있습니다.

 # Define the framework for trying different loss functions # Base class implements loop, sub classes override self.calc_loss() class VectorSpreadAlgorithm: # ... def calc_loss(self, tensor2d): raise NotImplementedError("Define this in your derived class") def one_iter(self, i, learning_rate): # self.vecs is an 20x2 tensor, representing twenty 2D vectors tfvecs = tf.convert_to_tensor(self.vecs, dtype=tf.float32) with tf.GradientTape() as tape: tape.watch(tfvecs) loss = self.calc_loss(tfvecs) # Here's the magic again. Derivative of spread with respect to # input vectors gradients = tape.gradient(loss, tfvecs) self.vecs = self.vecs - learning_rate * gradients

시도할 첫 번째 기술이 가장 간단합니다. 가장 가까운 벡터의 각도인 확산 메트릭을 정의합니다. 우리는 확산을 최대화하고 싶지만 그것을 최소화 문제로 만드는 것이 관례입니다. 따라서 우리는 단순히 스프레드 메트릭의 음수를 취합니다.

 class VectorSpread_Maximize_Min_Angle(VectorSpreadAlgorithm): def calc_loss(self, tensor2d): angle_pairs = tf.acos(tensor2d @ tf.transpose(tensor2d)) disable_diag = tf.eye(tensor2d.numpy().shape[0]) * 2 * np.pi spread_metric = tf.reduce_min(angle_pairs + disable_diag) # Convention is to return a quantity to be minimized, but we want # to maximize spread. So return negative spread return -spread_metric

일부 Matplotlib 마법은 시각화를 생성합니다.

초기 상태에서 대상 상태로 이동하는 애니메이션입니다. 외로운 점은 고정된 상태로 유지되고 반원의 나머지 스포크는 앞뒤로 흔들리며 천천히 퍼지고 1,200번의 반복 후에도 등거리에 도달하지 않습니다.

이것은 투박하지만(말 그대로!) 작동합니다. 20개 벡터 중 2개만 한 번에 업데이트되어 더 이상 가장 가깝지 않을 때까지 두 벡터 사이의 공간을 늘린 다음 가장 가까운 새로운 두 벡터 사이의 각도를 늘리도록 전환합니다. 주목해야 할 중요한 점은 그것이 작동 한다는 것입니다. TensorFlow가 tf.reduce_min() tf.acos() 를 통해 그라디언트를 전달하여 올바른 작업을 수행할 수 있음을 알 수 있습니다.

좀 더 정교한 것을 시도해 보겠습니다. 우리는 최적의 솔루션에서 모든 벡터가 가장 가까운 이웃에 대해 동일한 각도를 가져야 한다는 것을 알고 있습니다. 손실 함수에 "최소 각도의 분산"을 추가해 보겠습니다.

 class VectorSpread_MaxMinAngle_w_Variance(VectorSpreadAlgorithm): def spread_metric(self, tensor2d): """ Assumes all rows already normalized """ angle_pairs = tf.acos(tensor2d @ tf.transpose(tensor2d)) disable_diag = tf.eye(tensor2d.numpy().shape[0]) * 2 * np.pi all_mins = tf.reduce_min(angle_pairs + disable_diag, axis=1) # Same calculation as before: find the min-min angle min_min = tf.reduce_min(all_mins) # But now also calculate the variance of the min angles vector avg_min = tf.reduce_mean(all_mins) var_min = tf.reduce_sum(tf.square(all_mins - avg_min)) # Our spread metric now includes a term to minimize variance spread_metric = min_min - 0.4 * var_min # As before, want negative spread to keep it a minimization problem return -spread_metric 

초기 상태에서 대상 상태로 이동하는 애니메이션입니다. 고독한 스포크는 고정되어 있지 않고 반원의 나머지 스포크를 향해 빠르게 움직입니다. 고독한 스포크 양쪽에 두 개의 간격을 닫는 대신 지터링은 이제 시간이 지남에 따라 하나의 큰 간격을 닫습니다. 여기에서도 1,200번의 반복 후에 등거리에 도달하지 못했습니다.

가장 가까운 이웃에 대한 각도가 거대하고 현재 최소화되고 있는 분산 항에 스파이크가 발생하기 때문에 고독한 북쪽 벡터는 이제 동료와 빠르게 합류합니다. 그러나 여전히 궁극적으로 증가 속도가 느린 전 세계 최소 각도에 의해 주도됩니다. 이것을 개선해야 하는 아이디어는 일반적으로 이 2D 경우에 작동하지만 더 높은 차원에서는 작동하지 않습니다.

그러나 이 수학적 시도의 질에 너무 많은 초점을 맞추는 것은 요점을 놓치고 있습니다. 평균 및 분산 계산에 얼마나 많은 텐서 연산이 관련되어 있는지, 그리고 TensorFlow가 입력 행렬의 모든 구성요소에 대한 모든 계산을 성공적으로 추적하고 구별하는 방법을 살펴보세요. 그리고 우리는 어떤 수동 계산도 할 ​​필요가 없었습니다. 우리는 몇 가지 간단한 수학을 함께 던졌고 TensorFlow가 우리를 위해 계산을 해주었습니다.

마지막으로 힘 기반 솔루션을 한 가지 더 시도해 보겠습니다. 모든 벡터가 중심점에 연결된 작은 행성이라고 상상해 보세요. 각 행성은 다른 행성에서 그것을 밀어내는 힘을 방출합니다. 이 모델의 물리학 시뮬레이션을 실행하려면 원하는 솔루션에 도달해야 합니다.

내 가설은 경사하강법도 작동해야 한다는 것입니다. 최적의 솔루션에서 다른 모든 행성의 모든 행성에 대한 접선력은 순 0력으로 상쇄되어야 합니다(0이 아닌 경우 행성이 이동할 것입니다). 모든 벡터에 대한 힘의 크기를 계산하고 경사하강법을 사용하여 0으로 밀어봅시다.

먼저 tf.* 방법을 사용하여 힘을 계산하는 방법을 정의해야 합니다.

 class VectorSpread_Force(VectorSpreadAlgorithm): def force_a_onto_b(self, vec_a, vec_b): # Calc force assuming vec_b is constrained to the unit sphere diff = vec_b - vec_a norm = tf.sqrt(tf.reduce_sum(diff**2)) unit_force_dir = diff / norm force_magnitude = 1 / norm**2 force_vec = unit_force_dir * force_magnitude # Project force onto this vec, calculate how much is radial b_dot_f = tf.tensordot(vec_b, force_vec, axes=1) b_dot_b = tf.tensordot(vec_b, vec_b, axes=1) radial_component = (b_dot_f / b_dot_b) * vec_b # Subtract radial component and return result return force_vec - radial_component

그런 다음 위의 force 함수를 사용하여 손실 함수를 정의합니다. 우리는 각 벡터에 순 힘을 축적하고 그 크기를 계산합니다. 최적의 솔루션에서는 모든 힘이 상쇄되어야 하고 힘이 0이어야 합니다.

 def calc_loss(self, tensor2d): n_vec = tensor2d.numpy().shape[0] all_force_list = [] for this_idx in range(n_vec): # Accumulate force of all other vecs onto this one this_force_list = [] for other_idx in range(n_vec): if this_idx == other_idx: continue this_vec = tensor2d[this_idx, :] other_vec = tensor2d[other_idx, :] tangent_force_vec = self.force_a_onto_b(other_vec, this_vec) this_force_list.append(tangent_force_vec) # Use list of all N-dimensional force vecs. Stack and sum. sum_tangent_forces = tf.reduce_sum(tf.stack(this_force_list)) this_force_mag = tf.sqrt(tf.reduce_sum(sum_tangent_forces**2)) # Accumulate all magnitudes, should all be zero at optimal solution all_force_list.append(this_force_mag) # We want to minimize total force sum, so simply stack, sum, return return tf.reduce_sum(tf.stack(all_force_list)) 

초기 상태에서 대상 상태로 이동하는 애니메이션입니다. 처음 몇 프레임은 모든 스포크에서 빠른 움직임을 보고 200번 정도만 반복하면 전체 그림이 이미 대상에 상당히 가깝습니다. 총 700개의 반복만 표시됩니다. 300초 이후부터는 프레임마다 미세하게 각도가 변합니다.

솔루션이 아름답게 작동할 뿐만 아니라(처음 몇 프레임의 혼란을 제외하고) 실제 크레딧은 TensorFlow에 있습니다. 이 솔루션에는 여러 for 루프, if 문, 거대한 계산 웹이 포함되었으며 TensorFlow는 우리를 위해 이 모든 것을 통해 성공적으로 그라디언트를 추적했습니다.

예 3: 적대적 AI 입력 생성

예 3 노트북

이쯤 되면 독자들은 "이봐! 이 글은 딥러닝에 대한 글이 아니었어!"라고 생각할 수도 있다. 그러나 기술적으로 도입은 "딥 러닝 모델 교육 "을 넘어서는 것을 의미합니다. 이 경우 우리는 훈련 이 아니라 사전 훈련된 심층 신경망의 일부 수학적 속성을 이용하여 잘못된 결과를 제공하도록 속입니다. 이것은 상상했던 것보다 훨씬 쉽고 효과적인 것으로 판명되었습니다. TensorFlow 2.0 코드의 또 다른 짧은 덩어리만 있으면 됩니다.

공격할 이미지 분류기를 찾는 것으로 시작합니다. 우리는 Dogs vs. Cats Kaggle Competition에서 최고의 솔루션 중 하나를 사용할 것입니다. 특히 Kaggler가 제시한 솔루션은 "uysimty"입니다. 효과적인 cat-vs-dog 모델을 제공하고 훌륭한 문서를 제공한 데 대한 모든 크레딧입니다. 이것은 18개의 신경망 계층에 걸쳐 1,300만 개의 매개변수로 구성된 강력한 모델입니다. (독자는 해당 노트북에서 이에 대한 자세한 내용을 읽을 수 있습니다.)

여기서 목표는 이 특정 네트워크의 결함을 강조하는 것이 아니라 입력이 많은 표준 신경망이 얼마나 취약한지를 보여주는 것입니다.

관련: 사운드 로직 및 모노토닉 AI 모델

약간의 수정으로 모델을 로드하고 분류할 이미지를 사전 처리하는 방법을 알아낼 수 있었습니다.

개 또는 고양이 각각에 해당하는 분류 및 신뢰 수준이 있는 5개의 샘플 이미지. 표시된 신뢰 수준은 95%에서 100% 사이입니다.

이것은 정말 견고한 분류기처럼 보입니다! 모든 샘플 분류는 정확하고 95% 이상의 신뢰도를 가지고 있습니다. 공격하자!

우리는 분명히 고양이인 이미지를 생성하고 싶지만 분류자가 높은 신뢰도로 개라고 결정하도록 합니다. 어떻게 할 수 있습니까?

올바르게 분류하는 고양이 사진으로 시작하여 주어진 입력 픽셀의 각 색상 채널(값 0-255)의 작은 수정이 최종 분류기 출력에 어떤 영향을 미치는지 알아보겠습니다. 하나의 픽셀을 수정하는 것은 별로 도움이 되지 않을 것입니다. 하지만 모든 128x128x3 = 49,152 픽셀 값의 누적 조정은 우리의 목표를 달성할 것입니다.

각 픽셀을 푸시하는 방법을 어떻게 알 수 있습니까? 정상적인 신경망 훈련 중에 TensorFlow에서 경사 하강법을 사용하여 1,300만 개의 무료 매개변수를 모두 동시에 업데이트하여 대상 레이블과 예측된 레이블 간의 손실을 최소화하려고 합니다. 이 경우 대신 1,300만 매개변수를 고정된 상태로 두고 입력 자체의 픽셀 값을 조정합니다.

우리의 손실 함수는 무엇입니까? 글쎄, 그것은 이미지가 얼마나 고양이처럼 보이는지입니다! 각 입력 픽셀에 대한 cat 값의 도함수를 계산하면 고양이 분류 확률을 최소화하기 위해 각 픽셀을 푸시하는 방법을 알 수 있습니다.

 def adversarial_modify(victim_img, to_dog=False, to_cat=False): # We only need four gradient descent steps for i in range(4): tf_victim_img = tf.convert_to_tensor(victim_img, dtype='float32') with tf.GradientTape() as tape: tape.watch(tf_victim_img) # Run the image through the model model_output = model(tf_victim_img) # Minimize cat confidence and maximize dog confidence loss = (model_output[0] - model_output[1]) dloss_dimg = tape.gradient(loss, tf_victim_img) # Ignore gradient magnitudes, only care about sign, +1/255 or -1/255 pixels_w_pos_grad = tf.cast(dloss_dimg > 0.0, 'float32') / 255. pixels_w_neg_grad = tf.cast(dloss_dimg < 0.0, 'float32') / 255. victim_img = victim_img - pixels_w_pos_grad + pixels_w_neg_grad

Matplotlib 마법은 다시 결과를 시각화하는 데 도움이 됩니다.

각각 "Cat 99.0%", "Cat 67.3%", "Dog 71.7%", "Dog 94.3%" 및 "Dog 99.4%"로 분류된 4개의 반복과 함께 원본 샘플 고양이 이미지.

와! 인간의 눈에는 이 사진들 각각이 동일합니다. 그러나 4번의 반복 후에 우리는 99.4%의 신뢰도로 분류기를 개라고 확신했습니다!

이것이 우연이 아니며 다른 방향으로도 작동하는지 확인합시다.

각각 "Dog 98.4%", "Dog 83.9%", "Dog 54.6%", "Cat 90.4%" 및 "Cat 99.8%"로 분류된 4개의 반복과 함께 원본 샘플 개 이미지. 이전과 마찬가지로 차이점은 육안으로 볼 수 없습니다.

성공! 분류기는 원래 98.4%의 신뢰도로 개로 이것을 정확하게 예측했지만 지금은 99.8%의 신뢰도로 고양이라고 믿습니다.

마지막으로 샘플 이미지 패치를 보고 어떻게 바뀌었는지 봅시다.

각 픽셀의 빨간색 채널에 대한 숫자 값을 표시하는 세 개의 픽셀 행과 열 그리드. 왼쪽 이미지 패치는 대부분 푸르스름한 사각형을 보여주며 218 이하의 값을 강조 표시하고 일부 빨간색 사각형(219 이상)은 오른쪽 하단 모서리에 밀집되어 있습니다. 가운데 "피해를 입은" 이미지 페이지는 매우 유사한 색상과 번호의 레이아웃을 보여줍니다. 오른쪽 이미지 패치는 -4에서 +4까지의 차이와 몇 개의 0을 포함하여 다른 둘 사이의 수치적 차이를 보여줍니다.

예상대로 최종 패치는 원본과 매우 유사하며 각 픽셀은 빨간색 채널의 강도 값에서 -4에서 +4로만 이동합니다. 이 변화는 인간이 그 차이를 구별하기에 충분하지 않지만 분류기의 출력을 완전히 변경합니다.

최종 생각: 경사하강법 최적화

이 기사 전체에서 단순성과 투명성을 위해 훈련 가능한 매개변수에 수동으로 그라디언트를 적용하는 방법을 살펴보았습니다. 그러나 실제 세계에서 데이터 과학자는 코드 부풀림을 추가하지 않고 훨씬 더 효과적인 경향이 있기 때문에 옵티마이저 를 사용하는 데 바로 뛰어들어야 합니다.

RMSprop, Adagrad 및 Adadelta를 포함하여 널리 사용되는 최적화 프로그램이 많이 있지만 가장 일반적인 것은 아마도 Adam 입니다. 때로는 각 매개변수에 대해 다른 학습률을 동적으로 유지하기 때문에 "적응 학습률 방법"이라고 합니다. 그들 중 대부분은 극소값을 탈출하고 더 빠른 수렴을 달성하기 위해 모멘텀 항과 대략적인 고차 도함수를 사용합니다.

Sebastian Ruder에서 빌린 애니메이션에서 다양한 옵티마이저가 손실 표면을 내려가는 경로를 볼 수 있습니다. 우리가 시연한 수동 기술은 "SGD"와 가장 유사합니다. 최고 성능의 옵티마이저는 모든 손실 표면에 대해 동일한 옵티마이저가 아닙니다. 그러나 고급 옵티마이 저는 일반적으로 단순한 옵티마이저보다 성능이 좋습니다.

목표 지점에 수렴하기 위해 6가지 다른 방법으로 이동한 경로를 보여주는 애니메이션 등고선 지도. SGD는 시작점에서 안정된 곡선을 취하면서 가장 느립니다. 모멘텀은 처음에 목표에서 멀어지고 두 번 자체 경로를 교차한 다음 완전히 직접적으로 향하지 않고 목표를 초과한 다음 역추적하는 것처럼 보입니다. NAG는 비슷하지만 목표에서 멀리 벗어나지 않고 한 번만 교차하여 일반적으로 목표에 더 빨리 도달하고 더 적게 오버슈팅합니다. Adagrad는 가장 코스에서 벗어난 직선에서 시작하지만 목표가 있는 언덕을 향해 매우 빠르게 헤어핀 회전을 하고 처음 세 개보다 빠르게 커브를 향해 커브합니다. Adadelta도 비슷한 경로를 가지고 있지만 곡선이 더 부드럽습니다. 그것은 Adagrad를 추월하고 처음 1초 정도 후에 앞서 유지합니다. 마지막으로 Rmsprop은 Adadelta와 매우 유사한 경로를 따르지만 초기에 목표에 약간 더 가깝습니다. 특히, 그 과정은 훨씬 더 안정적이어서 대부분의 애니메이션에서 Adagrad와 Adadelta보다 뒤쳐집니다. 다른 5개와 달리 움직임을 멈추기 전에 애니메이션이 끝날 때 두 개의 다른 방향으로 갑자기 빠르게 두 번 점프하는 것처럼 보이지만 나머지는 마지막 순간에 계속 천천히 대상을 따라 움직입니다.

그러나 인공 지능 개발 서비스를 제공하는 데 열심인 사람들에게도 최적화 도구의 전문가는 거의 유용하지 않습니다. 개발자가 TensorFlow에서 경사 하강법을 개선하는 방법을 이해하기 위해 몇 가지에 익숙해지는 것이 개발자의 시간을 더 잘 활용하는 것입니다. 그 후에는 기본적으로 Adam 을 사용하고 모델이 수렴되지 않는 경우에만 다른 것을 시도할 수 있습니다.

이러한 옵티마이저가 작동하는 방법과 이유에 정말로 관심이 있는 독자를 위해 애니메이션이 나타나는 Ruder의 개요는 해당 주제에 대한 가장 훌륭하고 포괄적인 리소스 중 하나입니다.

최적화 프로그램을 사용하도록 첫 번째 섹션의 선형 회귀 솔루션을 업데이트해 보겠습니다. 다음은 수동 그래디언트를 사용하는 원래 그래디언트 하강 코드입니다.

 # Manual gradient descent operations def run_gradient_descent(heights, weights, init_slope, init_icept, learning_rate): tf_slope = tf.Variable(init_slope, dtype='float32') tf_icept = tf.Variable(init_icept, dtype='float32') for i in range(25): with tf.GradientTape() as tape: tape.watch((tf_slope, tf_icept)) predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors**2) gradients = tape.gradient(loss, [tf_slope, tf_icept]) tf_slope = tf_slope - learning_rate * gradients[0] tf_icept = tf_icept - learning_rate * gradients[1]

이제 옵티마이저를 대신 사용하는 동일한 코드가 있습니다. 추가 코드가 거의 없음을 알 수 있습니다(변경된 줄은 파란색으로 강조 표시됨).

 # Gradient descent with Optimizer (RMSprop) def run_gradient_descent (heights, weights, init_slope, init_icept, learning_rate) : tf_slope = tf.Variable(init_slope, dtype= 'float32' ) tf_icept = tf.Variable(init_icept, dtype= 'float32' ) # Group trainable parameters into a list trainable_params = [tf_slope, tf_icept] # Define your optimizer (RMSprop) outside of the training loop optimizer = keras.optimizers.RMSprop(learning_rate) for i in range( 25 ): # GradientTape loop is the same with tf.GradientTape() as tape: tape.watch( trainable_params ) predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors** 2 ) # We can use the trainable parameters list directly in gradient calcs gradients = tape.gradient(loss, trainable_params ) # Optimizers always aim to *minimize* the loss function optimizer.apply_gradients(zip(gradients, trainable_params))

그게 다야! 기울기 하강 루프 외부에서 RMSprop 옵티마이저를 정의한 다음 각 기울기 계산 후에 optimizer.apply_gradients() 메서드를 사용하여 훈련 가능한 매개변수를 업데이트했습니다. 옵티마이저는 운동량 및 고차 도함수와 같은 추가 항을 계산하기 위해 과거 기울기를 추적하기 때문에 루프 외부에서 정의됩니다.

RMSprop 옵티마이저로 어떻게 보이는지 봅시다.

이전에 동기화된 애니메이션 쌍과 유사합니다. 적합선은 휴지 위치 위에서 시작됩니다. 손실 그래프는 단 5번의 반복 후에 거의 수렴하는 것을 보여줍니다.

좋아 보인다! 이제 Adam 옵티마이저로 시도해 보겠습니다.

또 다른 동기화된 산점도 및 해당 손실 그래프 애니메이션. 손실 그래프는 계속해서 최소값에 가까워지지 않는다는 점에서 다른 그래프보다 두드러집니다. 대신 튀는 공의 경로와 비슷합니다. 산점도에서 해당하는 적합선은 샘플 점 위에서 시작하여 샘플 점의 아래쪽으로 스윙한 다음 위로 올라오지만 높지는 않은 방식으로 진행되며 방향이 바뀔 때마다 중심 위치에 가까워집니다.

와, 여기서 무슨 일이? Adam의 운동량 역학으로 인해 최적의 솔루션을 오버슈트하고 여러 번 코스를 역전시키는 것으로 보입니다. 일반적으로 이 운동량 역학은 복잡한 손실 표면에 도움이 되지만 이 간단한 경우에는 문제가 됩니다. 이는 모델을 훈련할 때 조정할 하이퍼파라미터 중 하나를 최적화 프로그램으로 선택하라는 조언을 강조합니다.

딥 러닝을 탐색하려는 사람은 누구나 이 패턴에 익숙해지기를 원할 것입니다. 이 패턴은 표준 워크플로에 쉽게 포함되지 않는 복잡한 손실 메커니즘이 필요한 사용자 지정 TensorFlow 아키텍처에서 광범위하게 사용되기 때문입니다. 이 간단한 TensorFlow 경사하강법 예제에서는 훈련 가능한 매개변수가 두 개뿐이었지만 최적화하려면 수억 개의 매개변수가 포함된 아키텍처로 작업할 때 필요합니다.

TensorFlow의 경사하강법: 최소값 찾기에서 AI 시스템 공격까지

모든 코드 조각과 이미지는 해당 GitHub 리포지토리의 노트북에서 생성되었습니다. 또한 전체 코드를 보고자 하는 독자를 위해 개별 노트북에 대한 링크와 함께 모든 섹션의 요약이 포함되어 있습니다. 메시지를 단순화하기 위해 광범위한 인라인 문서에서 찾을 수 있는 많은 세부 정보가 생략되었습니다.

이 기사가 통찰력이 있고 TensorFlow에서 경사 하강법을 사용하는 방법에 대해 생각하는 데 도움이 되었기를 바랍니다. 직접 사용하지 않더라도 모든 최신 신경망 아키텍처가 작동하는 방식(모델 생성, 손실 함수 정의, 경사하강법 사용)을 사용하여 모델을 데이터 세트에 맞추는 방식이 더 명확해지기를 바랍니다.


Google Cloud 파트너 배지.

Google Cloud 파트너로서 Toptal의 Google 인증 전문가는 회사의 가장 중요한 프로젝트에 대한 수요가 있을 때 사용할 수 있습니다.