텐서플로우 2.2 버전부터 Accuracy()/'acc'로 쓰는 정확도의 표현이 match → equal로 Binary Accuracy와 차이를 두었습니다.

(TF ~2.1v) Calculates how often predictions matches labels.
                                     ↓
(TF 2.2v~) Calculates how often predictions equals labels.

 

따라서,

  • Accuracy: 정확히 일치
    (얼마나 같은가; Equal)
  • Binary_Accuracy: 지정해둔 threshold에 따라 Accuracy를 계산
    (얼마나 Match 되는가)

공식 문서에 따르면 Binary Accuracy는 default threshold가 0.5로 지정되어 있습니다.

tf.keras.metrics.BinaryAccuracy

 

TF 2.2 이하 버전에서 짜여진 코드를 가지고 최신 버전으로 학습시킬 때,
정확도 점수가 다른 경우 ['acc', 'binary_accuracy']를 확인해보는 것이 좋을 것 같습니다.

 

acc와 binary_acc의 차이를 알아볼 수 있는 예시입니다.

import tensorflow as tf

y_true = [[1], [1], [0], [0]]
y_pred = [[0.51], [1], [0.49], [0]]

print(tf.keras.metrics.Accuracy()(y_true, y_pred))  # 0.5
print(tf.keras.metrics.BinaryAccuracy()(y_true, y_pred)) # 1.0

 

StringLookup은 엑셀에서 굳이 비슷한 이름을 가진 함수를 찾아보자면, VLookup, HLookup처럼 인덱스를 찾아주는 함수인데, tensorflow version 2.4부터 tf.keras.layers.experimental.preprocessing.StringLookUp으로 만나볼 수 있습니다.

tf.keras.layers.experimental.preprocessing.StringLookup(
    max_tokens=None, num_oov_indices=1, mask_token='',
    oov_token='[UNK]', vocabulary=None, encoding=None, invert=False,
    **kwargs
)

사용 방법은 예제만 봐도 단번에 알 수 있는데요. vocabulary를 만들어서 StringLookUp에게 던져주면, word Embedding처럼 각 data value들이 index로 표현될 수 있도록 도와주는 함수입니다.

vocab = ["a", "b", "c", "d"]
data = tf.constant([["a", "c", "d"], ["d", "z", "b"]])
layer = StringLookup(vocabulary=vocab)
layer(data)
# <tf.Tensor: shape = (2, 3), dtype=int64, numpy =
# array([[2, 4, 5],
#       [5, 1, 3]])>

"a"가 index 2인 이유는 바로 다음 예제를 보면 단번에 알 수 있습니다. OOV 처리 등.

data = tf.constant([["a", "c", "d"], ["d", "z", "b"]])
layer = StringLookup()
layer.adapt(data)

layer.get_vocabulary()
# ['','[UNK]', 'd', 'z', 'c', 'b', 'a']

 

그런데 TesnorFlow 2.4 이전 버전은 StringLookUp 함수가 제공되지 않기 때문에 다른 방법으로 사용해야 합니다.
물론 다양한 방법이 있겠지만, tensorflow가 제공하는 함수를 활용해볼거라면 tensorflow lookup 모듈을 고려해볼 수 있었습니다.

이를 위해선 두 가지를 사용해야 합니다.

  • tf.lookup.StaticHashTable
  • tf.lookup.KeyValueTensorInitializer

tf.lookup.KeyValueTensorInitializer에서 vocab(key)와 data value(value)를 준비해주면, StaticHashTable에 넣어 Table을 만드는 구조인 것으로 보입니다.

이 역시 텐서플로우 공식 홈페이지 예제를 보면 쉽게 이해할 수 있습니다.

keys_tensor = tf.constant(['a', 'b', 'c'])
vals_tensor = tf.constant([7, 8, 9])
input_tensor = tf.constant(['a', 'f'])

init = tf.lookup.KeyValueTensorInitializer(keys_tensor, vals_tensor)
table = tf.lookup.StaticHashTable(
    init,
    default_value=-1)
    
table.lookup(input_tensor).numpy()
# array([7, -1], dtype = int32)

중요하진 않지만, tf 2.2에서는 table.lookup(input_tensor).numpy()가 작동하지 않습니다.
예제가 완벽히 설명해주고 있으니 추가로 설명하진 않겠습니다.

이를 활용하면 StringLookUp처럼 사용할 수 있는데, 형변환 에러가 발생합니다
(tf 2.4에서는 아마 발생하지 않을거에요).

그래서 예제처럼 사용하기보다는 Custom_Layer로 만든 후, Tensor로 변환시키게끔해서 사용해야 하는 것 같습니다.

다음처럼 구현할 수 있고, 물론 중요한 Embedding Layer와도 연결해서 사용할 수 있습니다.

import tensorflow as tf

key = tf.range(start = 0, limit = 100, delta = 1, dtype = tf.int64)
value = tf.range(start = 0, limit = 100, delta = 1, dtype = tf.int64)
table = tf.lookup.StaticHashTable(initializer=tf.lookup.KeyValueTensorInitializer(keys = key, values = value),
                                  default_value = -1)

class custom_layer(tf.keras.layers.Layer):
  def __init__(self):
    super(custom_layer, self).__init__()

  def call(self, inputs):
    return table.lookup(inputs)

custom_layer = custom_layer()

# layer 연결
inputs = tf.keras.layers.Input(shape = (1, ), dtype = tf.int64)
encoded_feature = custom_layer(inputs)
embedding_feature = tf.keras.layers.Embedding(input_dim = 100, 
                                              output_dim = 100)(encoded_feature)

model = tf.keras.models.Model(inputs = inputs, outputs = embedding_feature)

# Embedding을 사용하지 않는 경우의 test example
# test_data = tf.constant([[50],
#                         [37]], dtype = tf.int64)

# model.predict(test_data) # array([[50], [37]])

- 어쨌든 버전이 높으면 뭐가 많아서 사용하긴 편해보인다...가 결론

시계열 데이터를 다룰 때 사용하면 매우 유용합니다.
시계열 데이터를 다룰 때 다음 함수와 비슷한 것들을 직접 정의하여 sequence를 만들어주어야 하는 번거로움이 있습니다. 

def make_sequence(data, n):
    X, y = list(), list()
    
    for i in range(len(data)):
        _X = data.iloc[i:(i + n), :-1]
        if(i + n) < len(data):
            X.append(np.array(_X))
            y.append(data.iloc[i + n, -1])
        else:
            break
            
    return np.array(X), np.array(y)

tf.data를 사용하면 여러 줄로 구성되어 있는 위의 코드가 단 하나의 함수로 해결됩니다.

dataset = tf.data.Dataset.range(10)
dataset = dataset.window(5, shift=1)
for window_dataset in dataset:
  for val in window_dataset:
    print(val.numpy(), end=" ")
  print()
  • dataset.window의 첫 번째 인자는 window size이고, 두 번째는 shift 크기를 전달합니다.
  • 결과는 다음과 같습니다.

0 1 2 3 4 
1 2 3 4 5 
2 3 4 5 6 
3 4 5 6 7 
4 5 6 7 8 
5 6 7 8 9 
6 7 8 9 
7 8 9 
8 9 

결과에서 window_size = 5만큼의 데이터를 얻다가, 끝 부분에서 [6, 7, 8, 9], [7, 8, 9], ... 의 원치않는 결과를 얻고 있습니다.
이는 가져오려는 window_size가 데이터셋의 크기를 초과했기 때문에 그렇습니다.

이를 방지하기 위해 drop_remainder = True 인자를 사용합니다.

dataset = tf.data.Dataset.range(10)
dataset = dataset.window(5, shift=1, drop_remainder=True)
for window_dataset in dataset:
  for val in window_dataset:
    print(val.numpy(), end=" ")
  print()
  • for-loop를 2중으로 사용하는 이유는 dataset.windowTensor가 아닌 Dataset을 반환하기 때문입니다.
  • 이는 flat_map 함수를 사용해서 window_dataset을 flat해주어 바로 사용할 수 있습니다.
  • 이 말은 쉽게 설명하면 원래 같은 경우 5 -> 4 -> 3 -> 처럼 iter 형식으로 받을 수 있었는데, flat_map을 사용하면 [5, 4, 3, 2, 1]로 바로 받을 수 있습니다.
dataset = tf.data.Dataset.range(10)
dataset = dataset.window(5, shift=1, drop_remainder=True)
dataset = dataset.flat_map(lambda window: window.batch(5))
for window in dataset:
  print(window.numpy())

마지막으로 다음과 같이 사용할 수도 있습니다.

dataset = tf.data.Dataset.range(10)
dataset = dataset.window(5, shift=1, drop_remainder=True)
dataset = dataset.flat_map(lambda window: window.batch(5))
dataset = dataset.map(lambda window: (window[:-1], window[-1:]))
for x,y in dataset:
  print(x.numpy(), y.numpy())
  • 결과는 다음과 같습니다.
  • [0 1 2 3] [4]
    [1 2 3 4] [5]
    [2 3 4 5] [6]
    [3 4 5 6] [7]
    [4 5 6 7] [8]
    [5 6 7 8] [9]

 

reference

https://www.tensorflow.org/guide/data

 

tf.data: Build TensorFlow input pipelines  |  TensorFlow Core

The tf.data API enables you to build complex input pipelines from simple, reusable pieces. For example, the pipeline for an image model might aggregate data from files in a distributed file system, apply random perturbations to each image, and merge random

www.tensorflow.org

 

Binary_crossentropyCategorical_crossentropy 함수에선 공통적으로 from_logits 인자를 설정할 수 있습니다.

기본값은 모두 False로 되어있는데요. True, False의 차이점을 보도록 하겠습니다.

먼저 가벼운 예제를 보고 넘어가죠. 다음 두 코드의 차이점은 단순히 from_logits의 차이입니다.

# 1번 코드
cce = tf.keras.losses.CategoricalCrossentropy(from_logits = True)

target = [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]] 
output = [[1., 0., 0.], [.05, .89, .06], [.05, .01, .94]]

loss = cce(target, output)

print(loss.numpy())
  • 0.5889537
# 2번 코드
cce = tf.keras.losses.CategoricalCrossentropy(from_logits = False)

target = [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]] 
output = [[1., 0., 0.], [.05, .89, .06], [.05, .01, .94]]

loss = cce(target, output)

print(loss.numpy())
  • 0.059469786

출력값의 차이가 크게 나는 것을 볼 수 있습니다. 동작하는게 다르다는 것을 보여주네요. 결과는 일단 제쳐두고, 다시 from_logits 인자에 대해 얘기해보죠.(밑에 길게 설명되어있지만, 사실 텐서플로우의 logit은 매우 간단합니다.)


from_logits의 의미는 모델이 출력하는 output이 logit인지 아닌지를 판단하는 것으로 생각할 수 있는데요. 이를 해석하려면 logit의 의미를 또 파헤쳐보아야 합니다.

일반적으로 통계학에서 쓰이는 logit과 딥러닝에서 쓰이는 logit은 의미가 다릅니다. 또, 몇몇 사람들의 글을 찾아보면 굳이 텐서플로우가 이를 함수 이름으로 채택하면서 사용자들에게 혼동을 주고 있다고 말하기도 하네요. 사실 이 글의 작성 동기도 함수 사용 과정에서 조금 헷갈려서...

통계학에서 쓰이는 logit은 Logistic Regression을 이야기할 때 자주 쓰이는 logit입니다.

  1. 이때 자주 이야기하는 오즈(odds)는 (성공 확률 / 실패 확률)을 통해 구해지게 됩니다. 하지만 실제 변수를 확률의 범위에서 해석하기엔 조금 애매합니다. 그래서 log를 사용하는데요. 이를 logit 변환이라고 합니다. log를 사용하게 되면, 값의 범위가 [-inf, inf]로 바뀌게 됩니다.
    (오즈값의 범위는 [0, inf] 입니다)
  2. logit 변환의 의미는 예측값(y)과 예측값을 만들어내는 특성값(x)의 관계를 선형관계로 만들어준다는 것입니다. 따라서 로지스틱 회귀식을 사용할 수 있는거죠! 원래 y는 [0, 1]의 확률의 범위였고, 특성값(x)은 [-inf, inf]이기 때문에 관계를 말할 수 없었습니다. 값의 범위가 동일하지 않으면 관계를 해석하기가 쉽지 않을겁니다.

    그래서 이를 해결하고자 logit 변환을 사용해서 값의 범위를 동일하게 맞춰주고, 값의 해석이 유의미하도록 도와주는거죠. 즉, 다시 말해서 logit 변환은 [0, 1] 범위를 가지는 확률을 [-inf, inf] 범위를 가지는 값으로 변환해주는 변환입니다.
  3. 로그를 씌운 오즈비(Odds Ratio)의 역함수를 구해보세요. 시그모이드 함수가 눈에 보일겁니다.

딥러닝에서 쓰이는 logit은 매우 간단합니다. 모델의 출력값이 문제에 맞게 normalize 되었느냐의 여부입니다. 예를 들어, 10개의 이미지를 분류하는 문제에서는 주로 softmax 함수를 사용하는데요.

이때, 모델이 출력값으로 해당 클래스의 범위에서의 확률을 출력한다면, 이를 logit=False라고 표현할 수 있습니다. logit이 아니라 확률값이니까요(이건 저만의 표현인 점을 참고해서 읽어주세요).

반대로 모델의 출력값이 sigmoid 또는 linear를 거쳐서 확률이 아닌 값이 나오게 된다면, logit=True라고 표현할 수 있습니다. 말 그대로 확률이 아니라 logit이니까요.


다시 코드로 돌아가보죠. 먼저 코드를 해석하려면 두 가지 가정이 필요합니다.

(1) Loss Function이 CategoricalCrossEntropy이기 때문에 클래스 분류인 것을 알 수 있다.
(2) output 배열은 모델의 출력값을 나타내며, softmax 함수를 거쳐서 나온 확률값이다.

이제 우리는 왜 2번 코드에서 from_logits=False를 사용했는지 알 수 있습니다. 문제에 알맞게 normalize된 상태이기 때문입니다(값을 전부 더해보면 1입니다, 확률을 예로 든거에요). 반대로 from_logits=True일 때는 output 배열의 값이 logit 상태가 아니기 때문에 우리가 생각한 값과 다른 값이 나오게 된 것입니다. 

tensorflow github source, categorical_crossentropy function

텐서플로우 깃허브의 코드를 보아도 from_logits 인자에 따라 계산 방식이 다른 것을 볼 수 있습니다. from_logits가 True인 경우에는 nn.softmax_cross_entropy_with_logits 함수로 바로 넘어가는데요. 이를 쉽게 설명하면, 출력값이 logit 형태이기 때문에 softmax 함수(logit을 확률로 변환하는 과정) -> crossentropy의 과정을 거치는 함수입니다.


결론: 클래스 분류 문제에서 softmax 함수를 거치면 from_logits = False(default값),

그렇지 않으면 from_logits = True.

+ 텐서플로우에서는 softmax 함수를 거치지 않고, from_logits = True를 사용하는게 numerical stable하다고 설명하고 있다.

 

Reference

https://ko.wikipedia.org/wiki/%EB%A1%9C%EC%A7%80%EC%8A%A4%ED%8B%B1_%ED%9A%8C%EA%B7%80
https://www.tensorflow.org/api_docs/python/tf/keras/losses/CategoricalCrossentropy

 

 

 

 

1. tf.feature_column.numeric_column

- 단순하게 수치형으로 바꿔준다. 

예를 들어, housing 데이터에서 tf.feature_column.numeric_column('logitude')

2. tf.feature_column.bucketized_column

- 수치형 데이터를 버킷화(구간 분할) 시켜준다. 

예를 들어, tf.feature_column.bucketized_column("col", boundaries = [0., 1., 2.]) 으로 선언하면 데이터의 각 구간을 

(-inf ~ 0), [0~1), [1~2), [2~inf)로 나누어준다.

3. tf.feature_column.categorical_column_with_vocabulary_list("col", list)

- 각 값을 label화 시켜줍니다.

예를 들어, ['man', 'woman'] -> [0, 1]로 ID를 붙여줍니다.

보통 one-hot encoding을 하려고할 때 쓰이기 때문에 tf.feature_column.indicator_column과 같이 쓰입니다.

예를 들어, [0, 1]로 label화 되어있는 것을 [[1, 0], [0, 1]]로 바꾸어줍니다.

4. tf.feature_column.crossed_column([col_a, col_b,...], hash_bucket_size)

- pandas에서 groupby를 통한 feature 생성이라고 생각하면 쉽습니다. 이 함수는 범주형 특성에 대해 특성교차를 제공합니다.

hash_bucket_size는 특성교차시에 얼마나 많은 버킷을 생성할지 입니다. 

5. tf.feature_column_embedding_column(col, dimension)

- 범주형 또는 카테고리형 데이터에 모두 사용할 수 있습니다. 함수 그대로 임베딩 벡터를 제공합니다. 

'# Machine Learning > TensorFlow Function' 카테고리의 다른 글

tf.data.dataset.window 예시  (0) 2020.04.05
tensorflow Loss 함수에 존재하는 from_logits란  (3) 2020.03.06
tf.image.non_max_suppression  (0) 2019.04.16
tf.boolean_mask  (0) 2019.04.15
tf.nn.embedding_lookup 예시  (0) 2019.04.13