본문 바로가기
python/핸즈온 머신러닝

[핸즈온 머신러닝] 12장. 텐서플로우를 사용한 사용자 정의 모델과 훈련

by _avocado_ 2021. 3. 31.

● 텐서플로우(Tensorflow)

 

- 강력한 수치 계산용 라이브러리, 딥러닝 최적화가 되어있다.

 

- 핵심 구조는 넘파이(Numpy)와 비슷하지만 GPU를 지원한다.

 

- 분산 컴퓨팅을 지원한다.

 

- 계산 그래프를 이용하여 함수 최적화 및 중립적 포맷을 유지한다.(리눅스 python 환경에서 훈련 후 안드로이드 자바에서 사용 가능)

 

- 자동 미분 기능 제공, 고성능 옵티마이저 제공

 

- 텐서 플로 허브 등에서 다수의 만들어진 모델을 다운 가능하다. (커뮤니티가 잘 되어 있고 사용자도 많다.)


목차

1. 텐서 플로우 연산(numpy처럼 사용하기)

 

2. 사용자 정의로 모델 만들기

 

    - 손실 함수, 활성화 함수, 규제, 지표, 층 등 사용자 정의로 모델의 구성요소를 만들어 보기

 

3. 텐서 플로우 함수 만들기


1. 텐서 플로우 연산

● 텐서 : 텐서 플로우에서 사용하는 객체

 

- numpy ndarray와 비슷한 구조

 

- 스칼라 값부터 다차원 배열을 가질 수 있다.

 

▶ 텐서 만들기

import tensorflow as tf

tf.constant([[1,2,3],[4,5,6]])

- 인덱스로 자르기, 덧셈, 곱셈, 행렬곱 등 ndarray와 똑같이 작동한다.

 

- dtype을 갖는다. float과 int가 동시에 존재할 수 없다. 신경 쓰자

 

 

▶ 텐서와 넘파이

 

- 텐서와 넘파이 배열은 함께 사용할 수 있다.

 

- 주의) 넘파이는 기본 64비트를 사용하지만 텐서플로우는 32비트를 사용한다. 넘파이 배열을 텐서로 사용하고 싶다면 32로 바꾸자

a = np.array([2.,4.,5.]) # 넘파이 배열

t = tf.constant(a) # 텐서로 변환

t.numpy(), np.array(t) # 넘파이 배열로 변환

tf.square(a) # 텐서플로우 함수로 넘파이 배열 연산 가능, 출력은 텐서로

np.squeare(t) # 텐서를 넘파이 함수로 연산가능, 출력은 넘파이 배열

 

▶ 타입 변환

 

- 타입 변환은 성능을 크게 감소시킬 수 있다. 따라서 텐서플로우에서는 자동으로 타입을 변환하지 않는다.

 

- int와 float은 물론, float32와 float64 끼리도 연산이 불가능하다.

 

- 타입 변환이 필요한 경우 tf.cast() 함수를 사용한다.

t_float32 = tf.constant(40.)

t_float64 = tf.cast(t_float32, tf.float64)

 

▶ 변수

 

- 위에서 살펴본 텐서들은 변경 불가능

 

- 모델의 가중치를 훈련하거나 옵티마이저에서 과거 그레디언트를 갱신하기 위해서는 변하는 텐서가 필요하다.

v = tf.Variable([[1.,2.,3.],[4.,5.,6.]]) # 변수 생성

- 기본적은 동작은 텐서와 비슷하다.

 

- 변수를 갱신하는 방법

v # 결과 [[1.,2.,3.],[4.,5.,6.]]

v.assign(2*v) # 결과 [[2.,4.,6.],[8.,10.,12.]]

v[0,1].assign(42) # 결과 [[2.,42.,6.],[8.,10.,12.]]

v[:,2].assign([0.,1.]) # 결과 [[2.,42.,0.],[8.,10.,1.]]

v.scatter_nd_update(indices=[[0,0],[1,2]], updates=[100.,200.])# 결과 [[100.,42.,0.],[8.,10.,200.]]

- assign_add, assign_sub의 함수로 원하는 만큼 값을 증가, 감소시킬 수 있다.

 

- scatter_update, scatter_nd_update 등의 함수도 사용할 수 있습니다.

 

- tf.Variable은 tf.Tensor로 저장됩니다. 따라서 변수를 갱신하면 새로운 텐서가 만들어진다.

 

 

▶ 다른 여러 가지 데이터 구조

 

- 희소 텐서(tf.SparseTensor) : 대부분 0으로 이루어진 텐서를 효율적으로 나타내는 구조, tf.sparse 패키지로 희소 텐서 연산 가능

 

- 텐서 배열(tf.TensorArray) : 텐서의 리스트, 모든 텐서의 크기와 테이터 타입이 일치해야 한다.

 

- 래그드 텐서(tf.RaggedTensor) : 리스트의 리스트, 데이터 타입은 일치해야 하지만 길이는 달라도 된다. tf.ragged 패키지 사용

 

- 문자열 텐서 : tf.string 타입, 유니코드가 아닌 바이트 문자열로 표시, tf.strings 패키지로 유니코드, 바이트 사이의 변환 가능

 

- 집합 : 일반적인 텐서로 나타난다. ex) tf.constant([[1,2],[3,4]]) 에서 {1,2}, {3,4} 두 개의 집합이 나타난다. tf.sets 패키지 사용

 

- 큐 : 단계별로 텐서를 저장, tf.queue 패키지에 여러 가지 큐가 들어있다.


2. 사용자 정의 모델

 

사용자 정의 손실 함수 : 원하는 함수를 만들어 손실 함수로 사용하기

 

- 예시로 후버 손실 함수를 직접 만들어 사용한다. (사실 tf.keras.losses.Huber 클래스가 존재한다.)

 

- 후버 손실 함수

   

  효율적인 코드를 위해서 tf. 함수를 이용한다.

def huber_fn(y_true, y_pred):
	
    error = y_ture - y_pred
    
    is_small_errer = tf.abs(error) < 1
    
    squared_error = tf.square(error)/2
    
    linear_error = tf.abs(error) - 0.5
    
    return tf.where(is_small_error, squared_error, linear_error)

- 정의한 함수 훈련하기

model.compile(loss=huber_fn, optimizer='nadam')

model.fit(...)

- 사용자 정의를 갖는 모델 저장하기, 로드하기

  

  keras는 모델 저장 시 함수 이름을 저장한다. 따라서 로드할 때 실제 이름과 함수를 연결시킨다.

model.save('my_custom_model.h5') # 모델저장

model = tf.keras.models.load_model('my_custom_model.h5'
				   
                   ,custom_object = {'huber_fn':huber_fn}) # 모델 로드

 

- 매개변수가 있는 함수를 사용할 때

 

  keras.losses.Loss 상속을 받아 활용할 수 있다.

class HuberLoss(tf.keras.losses.Loss):
    
    def __init__(self, threshold=1.0, **kwargs):
    
    	self.threshold = threshold
    
    	super().__init__(**kwargs)
    
    def call(self,y_ture,y_pred):
        
        error = tf.abs(y_true-y_pred)
        
        is_small_error = error < self.threshold
        
        squared_error = error **2
        
        linear_error = 2 * self.threshold * error - self.threshold ** 2
        
        return tf.where(is_small_error,squared_error,linear_error)
    
    def get_config(self):
        
        base_config = super().get_config()
        
        return {**base_config, 'threshold':self.threshold}

get_config 함수를 통해 매개변수도 함께 저장이 가능하다.

model.compile(loss=HuberLoss(2.), optimizer='nadam') # 매개변수 threshold = 2.0

model.save('my_custom_model.h5')

model = tf.keras.models.load_model('my_custom_model.h5'
                                   
                                   ,custom_objects={'HuberLoss':HuberLoss})

● 활성화 함수, 초기화, 규제, 제한을 커스터마이징 하기

 

- softpuls, 글로럿 초기화, L1규제, 양수 가중치로 제한을 예시로 사용한다.

# 사용자 정의로 각각을 만들어 사용해보기

def my_softpuls(z): # softplus 활성화 함수
    
    return tf.math.log(tf.exp(z) +1.0)

def my_glorot_initializer(shape, detype=tf.float32): # 글로럿 초기화
    
    stddv = tf.sqrt(2./(shape[0] + shape[1]))
    
    return tf.random.normal(shape, stddev=stddev,dtype=dtype)

def my_l1_regularizer(weights): # L1 규제
    
    return tf.reduce_sum(tf.abs(0.01*weights))

def my_positive_weights(weights): # 가중치 범위 양수로 제한
    
    return tf.where(weight<0.,tf.zeros_like(weights),weights)

위 조건을 이용한 layer

layer = tf.keras.layers.Dense(30,activation=my_softpuls
                             
                             ,kernel_initializer = my_glorot_initializer
                             
                             ,kernel_regularizer = my_l1_regularizer
                             
                             ,kernel_constraint = my_positive_weights)

손실 함수와 마찬가지로 저장해야 할 매개변수가 있다면 각각 class를 만들어 get_config 함수를 이용한다.

 

아래 예시는 L1 규제의 예시다.

 

활성화 함수는 keras.layers.Layer, 초기화는 keras.initializers.Initializer, 제한은 keras.constrains.Constrain을 상속한다.

class MyL1Regularizer(tf.keras.regularizers.Regularizer):
    
    def __init__(self,factor):
        
        self.factor = factor
    
    def __call__(self, weights):
        
        return tf.reduce_sum(tf.abs(self.factor * weights))
    
    def get_config(self):
        
        return {'factor':self.factor}

● 사용자 정의 지표

 

▶ 손실과 지표

 

- 손실과 지표는 개념적으로 모델을 평가한다는 점에서 같다.

 

- 손실은 훈련에 사용되는 값으로 미분 가능, 그레디언트가 0이 아니어야 한다. 는 특징이 있고 지표의 경우 사람이 모델을 평가할 때 사용하

 

   기 때문에 직관적으로 이해가 쉬워야 한다.

 

- 앞에서 만든 후버 함수를 지표로 사용한 예시

model.compile(loss='mse', optimizer='nadam', metrics=[huber_fn])

 

▶ 스트리밍 지표

 

지표는 배치마다 계산되고 그 평균을 기록하다. 대부분 문제없이 사용이 가능하다.

 

하지만 총배치를 바탕으로 연산이 필요한 경우도 있다. 이때 지표를 스트리밍 지표라고 한다.

정밀도를 지표로 사용하는 경우

배치 1 - 예측 양성: 5, 진짜 양성:4, 정밀도 0.8

배치 2 - 예측 양성: 3 진짜 양성 0, 정밀도 0

단순 평균으로 계산한 정밀도 지표 : (0.8 + 0) /2 = 0.4

스트리밍 지표를 이용하여 전체 결과를 바탕으로 계산한 정밀도 지표 : (4+0) / (5+4) = 0.5

 

- 스트리밍 지표 만들기

  

  keras.metrics.Metric을 상속받아 update_state 함수를 만든다.

class HuberMetric(tf.keras.metrics.Metric):
    
    def __init__(self, threshold=1.0, **kwargs):
        
        super().__init__(**kwargs) # 기본 매개변수 처리
        
        self.threshold = threshold
        
        self.huber_fn = create_huber(threshold)
        
        self.total = self.add_weight('total', initializer='zeros')
        
        self.count = self.add_weight('count', initializer='zeros')
    
    
    def update_state(self, y_true, y_pred, sample_weight=None):
        
        metric = self.huber_fn(y_true,y_pred)
        
        self.total.assign_add(tf.reduce_sum(metric)) # 전체 값 저장
        
        self.count.assign_add(tf.cast(tf.size(y_true), tf.float32)) # 전체 개수 저장
    
    def result(self):
        
        return self.total/self.count
    
    def get_config(self):
        
        base_config = super().get_config()
        
        return {**base_config, 'threshold': self.threshold}

● 사용자 층 만들기

 

- 정의되지 않은 새로운 층을 만들거나 반복되는 층을 묶어 하나의 층으로 만들 때 사용할 수 있는 방법이다.

 

▶ 가중치가 없는 층

 

- Flatten과 ReLU와 같은 층은 가중치가 없다.

 

- keras.layers.Lambda를 이용하여 만들 수 있다.

exponential_layer = keras.layers.Lambda(lambda x: tf.exp(x))

▶ 가중치가 있는 층

 

- keras.layers.Layer를 상속받아 만들 수 있다.

 

1. 간단한 Dense layer 구현

class MyDense(tf.keras.layers.Layer):
    
    def __init__(self, units, activation=None, **kwargs):
        
        super().__init__(**kwargs)
        
        self.units = units
        
        self.activation = keras.activations.get(activation)
    
    
    def build(self, batch_input_shape):
        
        self.kernel = self.add_weight(name='kernel', shape=[batch_input_shape[-1], self.unit]
        
                                      ,initializer = 'glorot_normal')
        
        self.bias = self.add_weight(name='bias',shape=[self.units],initializer='zeros')
        
        super().build(batch_input_shape) # 마지막에 호출해야 한다.
        
    
    def call(self,X):
        
        return self.activation(X @ self.kernel + self.bias)
    
    
    def compute_output_shape(self, batch_input_shape):
        
        return tf.TensorShape(batch_input_shape.as_list()[:-1]+ [self.units])
    
    
    def get_config(self):
        
        base_config = super().get_config()
        
        return {**base_config, 'units':self.units
                
                ,'activation':tf.keras.activations.serialize(self.activation)}

- units와 activation을 입력받는다. (출력층 수와 활성화 함수)

 

- build 메서드에서 커널의 가중치를 설정한다.

 

- call 메소드에서 해당 layer의 연산을 한다.

 

- compute_output_shape()에서 layer의 출력 크기를 반환한다.

 

- get_config 메서드에 필요한 매개변수들을 저장한다.

 

2. 다중 입력, 다중 출력 층

 

- 2개의 입력과 3개의 출력을 갖는 층 만들기

class MyMultilayer(tf.keras.layers.Layer):
    
    def call(self,X):
        
        X1,X2 = X
        
        return [X1 + X2, X1 * X2, X1/X2]
    
    def compute_output_shape(self, batch_input_shape):
        
        b1, b2 = batch_input_shape
        
        return [b1,b1,b1]

 

3. 훈련과 테스트에서 다르게 작동하는 층 만들기

 

- call 메소드에 training 변수를 입력한다.

class MyGaussianNoise(keras.layers.Layer):
    
    def __init__(self, stddev, **kwargs):
        
        super().__init__(**kwargs)
        
        self.stddev = stddev
    
    
    def call(self,X,training = None): # training option을 부여한다.
        
        if training:
            
            noise = tf.random.normal(tf.shape(X),stddev=self.stddev) #훈련중에만 가우시안 잡음을 추가
            
            return X + noise
        
        else:
            return X
    
    def compute_output_shape(self, batch_input_shape):
        
        return bacth_input_shape

● 사용자 정의 모델 만들기

 

- keras.Model을 상속받아 만든다.

 

- 아래 그림의 모델을 ResidualBlock과 함께 구현해 본다.

   1. RegidualBlock 구현 -> 구성 2개의 Dense layer와 스킵 연결된 값을 더하여 출력

class ResidualBlock(keras.layers.Layer):
    
    def __init__(self, n_layer, n_neurons, **kwargs):
        
        super().__init__(**kwargs)
        
        self.hidden = [keras.layers.Dense(n_neurons, activation='elu'
                       
                       ,kernel_iniitalizer='he_normal') for _ in range(n_layer)]
    
    def call(self, inputs):
        
        Z = inputs
        
        for layer in self.hidden: # 2번 Dense 통과
            
            Z = layer(Z)
        
        return inputs + Z # 스킵연결

   2. 그림 순서로 layer를 깔아 모델 제작

class ResidualRegressor(keras.Model):
    
    def __init__(self,output_dim,**kwargs):
        
        super().__init__(**kwargs)
        
        self.hidden1 = keras.layers.Dense(30,activation='elu'
                                          
                                          ,kernel_initializer='he_normal')
        
        self.block1 = ResidualBlock(2,30)
        
        self.block2 = ResidualBlock(2,30)
        
        self.out = keras.layers.Dense(output_dim)
    
    def call(self, inputs):
        
        Z = self.hidden1(inputs)
        
        for _ in range(1+3):
            
            Z = self.block1(Z)
        
        Z = self.block2(Z)
        
        return self.out(Z)

save 메서드를 이용하여 모델을 저장하려면(또는 load 하려면) 두 class 모두 get_config 메소드를 정의해야 한다.

 

Model class는 Layer처럼 사용할 수 있고 compile, fit, evaluate, predict 모두 가능하다. (get_layer, save 도 가능)

 

▶ Model이 Layer 보다 많은 기능을 갖지만 모델로 정의하여 사용하지 않는 이유

 

- 기술적으로는 가능하다. 단 모델의 구성요소로서 모델과 구분하기 위해 다르게 사용하는 것


● 모델 구성 요소에 기반한 손실과 지표

 

- 앞에서 정의한 손실과 지표를 모두 y_ture와 y_pred에 기반한다.

 

- 은닉층의 가중치나 활성화 함수 등에 기반한 손실을 정의해야 할 때가 있다.(ex. 재구성 손실, 17장에서 자세하게 나온다네요)

 

- 주 손실과 재구성 손실을 더함으로 일반화 성능을 향상시킬 수 있다.(규제처럼)

 

- call 메서드에서 add_loss()를 이용한다.

class RecondtructingRegressor(keras.Model):
    
    def __init__(self,output_dim, **kwargs):
        
        super().__init__(**kwargs)
        
        self.hidden = [keras.layers.Dense(30, activation='selu'
                       
                       ,kernel_iniitalizer='lecun_normal') for _ in range(5)]
        
        self.out = keras.layers.Dense(output_dim)
        
    def build(self,bacth_input_shape):
        
        n_input = bacth_input_shape[-1]
        
        self.reconstruct = keras.layers.Dense(n_inputs)
        
        super().build(bacth_input_shape)
        
    def call(self, inputs):
        
        Z = inputs
        
        for layer in self.hidden:
            
            Z = layer(Z)
        
        reconstruction = self.reconstruct(Z)
        
        recon_loss = tf.reduce_mean(tf.square(reconstruction - inputs))
        
        self.add_loss(0.05*recon_loss) # 오류에 reconstruct loss 더하기 
        
        return self.out(Z)

● 자동 미분을 이용한 그레디언트 계산하기

 

- 변수(tf.Variable)에 대해 자동으로 기울기를 계산할 수 있다.

 

- tf.GradientTape()를 이용한다. (너무 큰 값에 대한 미분을 불가능하다. ex. tf.Variable(100.))

def f(w1, w2):
    
    return 3*w1**2 + 2*w1*w2

w1,w2 = tf.Variable(5.), tf.Variable(3.)

with tf.GradientTape() as tape:
    
    z = f(w1,w2)

gradients = tape.gradient(z,[w1,w2]) # 호출 후에 테이프는 사라진다. 2번 호출하면 에러난다.

gradients # 결과 36.0, 10.0

- graient를 2번 이상 호출해야 하는 경우

with tf.GradientTape(persistent=True) as tape: # 여러번 사용하는 방법
    z = f(w1,w2)


dz_dw1 = tape.gradient(z, w1)

dz_dw2 = tape.gradient(z, w2)

del tape # 꼭 삭제를 진행하여 리소스를 해제해야 한다.

dz_dw1, dz_dw2 # 36.0, 10.0

- 변수가 아닌 객체에 대한 미분

  

  기본적으로 변수가 아닌 객체에 대한 자동 미분을 불가능하다. 아래 방법으로 강제할 수 있다.

c1,c2 = tf.constant(5.), tf.constant(3.)

with tf.GradientTape() as tape:
    
    z = f(c1,c2)

tape.gradient(z,[c1,c2]) # None, None

with tf.GradientTape() as tape:
    
    tape.watch(c1)
    
    tape.watch(c2)
    
    z = f(c1,c2)

tape.gradient(z,[c1,c2]) # 36.0, 10.0

- 일부분의 그레디언트가 역전파에서 전달되지 못하도록 하는 방법

 

- 제외하고 싶은 부분을 tf.stop_gradient 함수로 감싼다.

ef f(w1,w2):
    
    return 3*w1**2 + tf.stop_gradient(2 * w1 * w2)

with tf.GradientTape() as tape:
    
    z = f(w1,w2)

gradients = tape.gradient(z,[w1,w2])

gradients # 30.0, None

● 사용자 정의 훈련 반복

 

- fit() 메서드의 유연성이 충분하지 않을 때 사용 가능한 방법이다.

 

장점: 완벽하게 훈련을 제어할 수 있다. ex) 2개의 옵티마이저를 사용하는 경우 compile과 fit 메서드로는 불가능하다.

 

단점: 코드가 길어지고, 코드에 확신이 없을 수 있다. 버그가 발생하기 쉽고 유지보수가 힘들다.

 

- 간단한 모델을 만든 후 훈련시키는 예시(직접 훈련시키기 때문에 compile이 필요 없다.)

l2_reg = keras.regularizers.l2(0.05) # 규제

# 모델
model = keras.models.Sequential([keras.layers.Dense(30,activation='elu'
                                                    ,kernel_initializer='he_normal'
                                                    ,kernel_regularizer=l2_reg)
                                ,keras.layers.Dense(1,kernel_regularizer=l2_reg)])

# 랜덤하게 배치를 추출하는 함수
def random_batch(X,y,batch_size=32):
    
    idx = np.random.randint(len(X), size=batch_size)
    
    return X[idx], y[idx]
    
# 현재 스탭, 에폭, 손실등 상태를 출력하는 함수
def print_status_bar(iteration, total, loss, metrics=None):
    
    metrics = ' - '.join(['{}: {:.4f}'.format(m.name, m.result()) 
                                   
                                   for m in [loss] + (metrics or [])])
    
    end = '' if iteration < total else '\n'
    
    print('\r{}/{} - '.format(iteration, total) + metrics, end = end)

- 훈련 조건

n_epochs = 5

batch_size = 32

n_steps = len(X_train) // batch_size

optimizer = keras.optimizers.Nadam(lr=0.01)

loss_fn = keras.losses.mean_squared_error

mean_loss = keras.metrics.Mean()

metrics = [keras.metrics.MeanAbsoluteError()]

- 훈련

for epoch in range(1, n_epochs+1):
    
    print('epoch {}/{}'.format(epoch,n_epochs))
    
    for step in range(1, n_steps+1):
        
        X_batch, y_batch = random_batch(X_train_scaled, y_train)
        
        with tf.GradientTape() as tape:
            
            y_pred = model(X_batch, training=True)
            
            main_loss = tf.reduce_mean(loss_fn(y_batch,y_pred))
            
            loss = tf.add_n([main_loss]+model.losses)
        
        gradients = tape.gradient(loss,model.trainable_variables)
        
        optimizer.apply_gradients(zip(gradients, model.trainable_variables))
        
        mean_loss(loss)
        
        for metric in metrics:
            
            metric(y_batch, y_pred)
        
        print_status_bar(step*batch_size, len(y_train), mean_loss, metrics)
    
    print_status_bar(len(y_train), len(y_train), mean_loss, metrics)
    
    for metric in [mean_loss] + metrics:
        
        metric.reset_states()

3. 텐서플로우 함수 만들기

- python으로 정의됨 함수를 텐서플로우 함수로 변형할 수 있다. (결과가 텐서로 반환된다.)

 

- 텐서플로우가 함수 내부에 사용하지 않는 노드를 제거하고 표현을 단순화하여 최적화한다.

 

- 파이썬 함수를 빠르게 실행하고 싶다면 텐서플로우 함수로 변환하는 것이 좋다.

 

- 오토그래프와 트레이싱 방법을 통해 python 함수를 텐서플로우 함수로 변경한다.

def cube(x): # python 함수
    
    return x **3
    
cube(tf.constant(2.0)) # 텐서를 입력시 텐서로 결과를 반환한다.

tf_cube = tf.function(cube) # 텐서플로우 함수로 변경


# 데코레이터를 이용하여 텐서플로우 함수로 변경

@tf.function     

def tf_cube(x):

	return x**3

tf_cube.python_function(4) # 텐서플로우 함수에서 python 함수 결과 가져오기

 

▶ 텐서플로우 함수를 만들 때 규칙

 

1. 함수 내에 다른 라이브러리 함수가 포함된다면 트레이싱 과정에서 실행된다. 하지만 가능하다면 tf. 함수를 이용하자.

 

  - 난수 생성 함수에서 문제가 될 수 있다.

 

  - 텐서플로우에서 지원하지 않는 연산의 경우 실행이 불가능하다.

 

2. 그래프 모드에서 처음 함수만 @tf.function으로 감싸주면 된다.

 

  - ex. 사용자 정의 층에서 __call__에만 감싸주면 된다.

 

3. 변수를 만든다면 처음 호출될 때만 만들어야 한다.

 

  - 일반적으로 함수 밖에서 변수를 만드는 것이 좋다.

 

  - 변수에 새로운 값을 할당하려면 assign() 함수를 이용하자.

 

4. 파이썬 코드가 텐서플로우에서 돌아가야 한다.

 

5. 반복문에서 for i in range(x) 대신 for i in tf.range(x)를 사용하여야 한다.

 

6. 성능면에서 반복문 대신 벡터화된 구현을 사용하는 것이 좋다.

댓글