참고
tf.estimator 와 효과적인 Data 사용
tf.estimator 는 Tensorflow의 High-level API로 평가, 학습, 예측과 서빙을 위한 모델 저장등을 편하게 수행할 수 있게 해줍니다. Estimators 를 사용하면서 (Estimator 외에도) Tensorflow 에서 데이터를 효과적으로 사용하기 위해서는 데이터를 여러 개의 직렬화된 데이터에 나눠 저장해 이용하는 것이 좋습니다. Tensorflow 에서는 TFRecord 를 통해 데이터를 직렬화하고 읽을 수 있습니다. 데이터를 직렬화하고 TFRecord 형식으로 저장하고 이를 읽기 위해 tf.Example 을 이용할 수 있습니다.
여기서는 먼저 .txt 형식의 파일을 tf.data.TextLineDataset 을 이용해 읽어 오고, tf.Example 로 데이터를 직렬화한 뒤, TFRecord 형식으로 다시 저장하는 과정을 진행해봅니다.
데이터 읽고 파싱하기
먼저 일반적인 .txt 형식의 파일을 tf.data.TextLineDataset 으로 읽어오겠습니다. 예제 데이터로 NSMC (Naver Sentiment Movie Corpus) 를 이용합니다.
tf.data 로 데이터를 읽는 것은 간단합니다. tf.data (여기서는 tf.data.TextLineDataset) 을 선언하고 한 번에 읽어 올 배치 사이즈를 지정한 다음, iterator 를 생성합니다. 구체적인 코드는 아래와 같습니다.
1 | |
Iterator 를 리턴하는 iterate_data(filename) 함수를 정의합니다. 이 함수는 내부에서 텍스트 파일 한 라인을 파싱할 _decode_tsv(line) 을 매핑합니다. tf.decode_csv(records, record_defaults) 의 record_defaults 는 한 라인 각 요소의 데이터 타입을 예시로 지정해줍니다. NSMC 데이터는 각 라인이 \t 으로 분리되어 있고, 각 원소는 id (int), 리뷰 (string), sentiment (int) 로 이루어져 있습니다. 이를 위해 record_defaults 를 [[0], ["null"], [0]] 으로 지정해 주었습니다. 한편, decode 함수는 (feature Dict, label) 튜플을 리턴합니다.
이후 위의 함수를 이용해서 데이터를 불러오는 코드는 다음과 같습니다.
1 | |
데이터를 불러오면 다음과 같습니다.
1 | |
1 | |
tf.data 로 데이터를 불러오면 텍스트는 utf-8로 인코딩 된 상태입니다. 우리가 읽을 수 있고, 여러 가지 전처리를 하기 위해서는 str.decode("utf-8") 로 디코딩 해줍니다. (Tensorflow 1.3 에서는 unicode 인코딩/디코딩 API가 제공됩니다.)
1 | |
위에서는 예시로 들기 위해 첫 번째 배치만 얻고 반복문을 중지했지만 실제로는 while 문으로 데이터를 끝까지 불러옵니다. 유의할 점은, 데이터를 불러오는 .get_next() 를 반복문 안이 아니라 밖에서 선언한다는 점입니다. 실제로는 아래와 같은 형태로 데이터를 읽습니다.
1 | |
tf.Example 로 Example 만들고 직렬화하기
tf.Example 로 Example 만들기
그럼 데이터를 읽어올 수 있으니, TFRecord로 다시 저장하기 위해 Example 을 만들고 직렬화해줍니다. tf.Example 을 이용합니다. tf.Example 메시지는 기본적으로 {"string": value} 매핑이며, Tensorflow에서 유연하게 사용할 수 있도록 설계된 메시지 타입입니다.
또한 여기서는 tf.Example 을 이용할 때 tf.train.Feature 을 사용합니다. tf.train.Feature 도 마찬가지로 프로토콜이며, {value: value} 로 표현됩니다. tf.train.Feature 는 세 가지 타입을 받을 수 있습니다. (한편 non-scalar 타입을 직렬화하는 가장 간단한 방법은 tf.serialize_tensor 를 사용하는 것입니다.)
tf.train.BytesListstringbyte
tf.train.FloatListfloat(float32)double(float64)
tf.train.Int64Listboolenumint32uint32int64uint64
Tensorflow 데이터를 tf.Example-compatible tf.train.Feature 로 변환하기 위해서는 아래와 같은 함수를 이용합니다.
1 | |
그럼 위의 함수를 이용해서 실제로 데이터를 변환해보겠습니다. 우리가 사용하는 NSMC 에는 string 과 int 타입만 있기 때문에 _bytes_featuers 와 _int64_feature 만 사용합니다.
위에서 얻은 feature로 간단히 결과를 얻으면 다음과 같습니다.
1 | |
1 | |
Serialization
이렇게 데이터를 tf.train.Feature 로 변환했으면, 이를 직렬화해주어야 합니다. 이를 위해 tf.Example 프로토콜을 정의하고 직렬화해줍니다. 이제 직렬화의 마지막 단계입니다. 직렬화에는 tf.Example().SerializeToString 을 사용합니다. 구체적인 코드는 아래와 같습니다.
1 | |
위에서 얻은 값을 직렬화하는 과정은 다음과 같습니다.
1 | |
1 | |
그럼 전체 데이터를 tf.train.Feature 로 변환하고 직렬화하겠습니다. 여기서는 학습 시의 편의를 위해 텍스트를 전처리한 뒤 직렬화하겠습니다. 이를 위해 리뷰 텍스트는 간단한 토크나이징을 해주고 tf.train.Feature 로 변환하겠습니다. 또한 tf.train.Feature 를 만들기 위해서는 string 을 다시 utf-8 로 인코딩해주어야 합니다. 이를 위해 아래와 같은 함수를 간단히 정의합니다. 한가지 더 주의할 점은, tf.train.BytesList(value=[value] 처럼, value 는 리스트 형태로 지정해주어야 합니다.
1 | |
전체 과정은 아래와 같습니다. 조금 번거롭습니다. tf.data.TextLineDataset 으로 배치사이즈만큼 데이터를 읽어오고, 세션을 열어 값을 얻은 다음, 전처리를 진행하고 다시 인코딩해서 tf.train.Feature 로 변환합니다. 코드는 아래와 같습니다. 아래 함수는 한 번에 하나의 feature를 리턴하는 제너레이터입니다.
1 | |
위의 코드에 읽어올 파일 위치와 tf.record 저장 위치 등을 인자로 주고 코드를 실행하면 다음과 같습니다. 실행이 완료되면 하나의 TFRecord 파일이 생성됩니다.
1 | |
TFRecord 읽고 파싱하기
TFRecord 생성을 완료했으면 다시 데이터를 읽어올 차례입니다. TFRecord는 크게 tf.python_io.tf_record_iterator 와 tf.data.TFRecordDataset 두 가지 방법으로 읽을 수 있습니다. (참고로 데이터 읽기를 소개하는 Tensorflow 공식 문서 에서는 tf.TFRecordReader 를 이용해서 TFRecord를 읽도록 설명하고 있는데, Queue-based input pipelines 은 tf.data 로 교체되었으므로 여기서는 무시합니다.)
tf.python_io.tf_record_iterator 이용하기
먼저 tf_record_iterator 를 이용하는 방법은 다음과 같습니다. iterator를 정의하고, tf.train.Example 을 이용합니다. 우리가 원하는 feature에 접근하기 위해서는 features.feature 속성을 이용합니다.
1 | |
1 | |
위의 features 에서 각 feature는 데이터 타입으로 접근합니다.
1 | |
1 | |
tf.data.TFRecordDataset 이용하기
앞에서 데이터를 읽을 때 사용했던 tf.data 를 사용하려면 tf.data.TFRecordDataset 을 사용하면 됩니다.
1 | |
tf.data.TFRecordDataset 을 이용할 때는 tf.python_io.tf_record_iterator 를 사용할 때와 달리 데이터를 parsing 해주는 작업이 필요합니다. 이 때는 tf.parse_example() 을 이용하는데, features 인자로 각 feature의 parsing 타입을 명시해줍니다. 간단한 함수로 나타내면 아래와 같습니다.
1 | |
next_elem 을 parsing 하면 tf.python_io.tf_record_iterator 를 사용했을 때와 동일한 결과를 얻을 수 있습니다.
1 | |
1 | |