독서 리마인더/핸즈온 머신러닝

13장

hwijin97 2021. 11. 30. 19:04

텐서플로에서 데이터 적재와 전처리하기

메모리에 적재되지 않는 데이터를  사용할때 효율적으로 로드하고 전처리하기위해 데이터 API를 만들어 데이터셋 객체로 멀티스레딩, 큐, 배치, 프리페치prefetch 같은 상세한 사항을 처리한다.

 

기본적으로 데이터 API는 텍스트 파일, 고정길이 레코드 이진 파일, TFrecord 이진파일, SQL 데이터 베이스 파일 읽기, 구글 빅쿼리 등을 지원한다.

 

대용량 데이터는 정규화같은 전처리가 필요하고, 수치형 필드로만 구성되지않고 텍스 특성, 범주형 특성등을 원-핫 인코딩, Bow인코딩, 임베딩embedding 등을 사용하여 인코딩되어야한다. 이런 전처리를 사용자 정의 층을 만들거나 표준 층을 만드는 방법이 있다. 

 

TF 변환 ( tf.Transform )

훈련전에 전체 훈련세트에 대한 전처리함수를 작성가능하고 텐서플로 함수로 변환한다. 배포된 훈련 모델의 새로운 샘플에 대해 동적으로 전처리를 가능하게 해준다.

TF 데이터셋 ( TFDS )

각종 데이터셋을 다운할 수 있다.

 

13.1  데이터 API

데이터셋dataset 연속된 데이터 샘플을 나타내고, 디스크에서 데이터를 점진적으로 읽는다. 

 

tf.data.Dataset.from_tensor.slices() - 텐서를 데이터셋으로 변환

 

13.1.1 연쇄 변환

dataset.repeat(3).batch(7) - 3번반복 데이터셋 -> 7개씩 batch 생성

repeat() 호출시 무한 반복, batch(, drop_remainder=True) 매개변수 지정시 남는것은 버림

데이터셋 메서드는 새로운 데이터를 생성하는 메서드이여서 반환한 값을 저장하지않으면 변환을 수행한 데이터셋의 데이터가 다날라감.

.map( ) : 데이터셋 각 샘플에 특정 함수를 수행함, num_parallel_calls 매개변수 지정시 여러 쓰레드로 나눠서 작업 가능.

.apply( ) : 데이터셋 전체에 변환을 적용함.

.fillter( )  : 데이터셋 필터링

.take( ) : 몇개의 데이터만 추출

 

13.1.2 데이터 셔플링

경사하강법은 샘플이 독립적이고 동일한 분포일 때 최고의 성능을 낸다.

.shuffle( ) : 샘플을 무작위로 섞음.

buffer_size 매개변수로 버퍼 개수만큼 추출하여 버퍼에 채우고, 새로운 아이템이 요청되면 버퍼에서 랜덤하게 반환한다. 그후 비워진만큼 원본데이터에서 버퍼를 채운다. 버퍼 크기를 충분히 크게해야 셔플의 효과가 좋아진다.

셔플된 데이터셋에 repeat() 메서드를 수행하면, 데이터셋이 반복될때마다 순서가 달라진다. 근데 반복마다 같은 순서를 사용하려면 reshuffle_each_iteration=False 를 지정한다.

 

메모리보다 큰 대용량 데이터셋의 경우에 버퍼가 크기때문에 셔플링방법보단, 원본 데이터 자체를 섞는 방법을 쓴다. 원본 데이터를 섞어도 에폭마다 한번 더 섞는 과정이 있긴하다. 원본 데이터를 여러 파일로 나누고 훈련하는 동안 무작위로 읽는다. 동일한 파일에 있는 샘플들을 shuffle() 메서드로 셔플링하면 적당히 섞인 샘플들을 얻을 수 있다.

 

여러 파일에서 한 줄씩 번갈아 읽기

https://colab.research.google.com/drive/1j5lPyFXXfgKNWuBTnBoalakPKsjgFWsw#scrollTo=Split_Dataset

 

tf.data.Dataset.list_files( ) : 파일 경로의 파일들을 섞은 데이터셋을 생성한다.

.interleave( ) : 파일 경로의 데이터셋의 특정개수를 한번에 묶어서 InterLeaveDataset 객체를 만든다. 반복구문에서 내부 데이터셋을 순회하고, 아이템을 소진하면 새로 읽는다. 여러파일에서 병렬적으로 아이템을 읽으려면 num_parallel_calls에 쓰레드 개수를 지정한다.

tf.data.experimental.AUTOTUNE 으로지정하면 CPU를 기반으로 텐서플로우가 적절한 스레드 개수를 선택한다.

 

 

13.1.3  데이터 전처리

https://colab.research.google.com/drive/1j5lPyFXXfgKNWuBTnBoalakPKsjgFWsw#scrollTo=DataPipeline

tf.io.decode_csv 함수는 csv파일 형식의 string 텐서를 record_defaults=[column_defaults] 을 기본값으로 지정하고, 각 컬럼을 하나의 벡터로 나타내는 컬럼개수의 1차원 벡터를 반환한다.

이 컬럼개수의 벡터를 tf.stack을 이용해서 하나의 행렬 텐서로 만들고 레이블을 제외한 데이터의 전처리 정규화를 진행한다.

 

13.1.4 데이터 적재와 전처리를 합치기

파일에서 데이터셋을 적재하고 셔플링 반복 배치를 적용한 데이터셋을 만든다.

tf.data.Dataset.list_files(filepaths) 파일 경로지정,

.repeat 으로 데이터 반복수 지정,

interleave( lambda ) 으로 파일데이터를 데이터셋으로 불러오기, 

.shuffle() 으로 TextLineDataset 셔플

.decode_csv 으로 텍스트 데이터를 csv 파일형식으로 디코딩하고, 컬럼합치기.

.prefetch(1) 으로 데이터셋 성능 향상

 

 

13.1.5 프리패치

.prefetch(1) 을 호출하면 1개의 배치를 항상 준비되도록 최선을 다하도록한다. 보통 1개면 충분하고, tf.data.experimental.AUTOTUNE 으로 자동지정하게 할 수 있다.

즉 .prefetch()를 수행하면 한 배치가 훈련하는동안 데이터셋이 동시에 다음 배치를 준비하는 방식으로 성능을 크게향상시킨다. (파이프라인 형식)  멀티쓰레드로 데이터를 적재하고 전처리하면 보통 훈련하는 시간보다 빠르게 데이터 준비를 마칠 수 있다.

데이터를 적재하는데에 메모리 대역폭 도 중요한 수치이다.

 

.cache() : 데이터가 메모리에 모두 들어갈정도록 작다면 이 메서드를 사용해서 셔플링, 반복, 배치, 프리패치하기 전에 데이터를 메모리에 넣어 한번만 데이터를 읽고 전처리를 수행한다. 그리고 에폭마다 다르게 셔플링하고, 다음 배치도 미리 준비시킨다.

 

자주사용하는 데이터셋 메서드에는 

.concatenate(), .zip(), .window(), .reduce(), .shard(), .flat_map(), .padded_batch(), .from_generator(), .from_tensors() 등 이있다.

 

 

13.1.6 tf.keras와 데이터셋 사용하기

 

https://colab.research.google.com/drive/1j5lPyFXXfgKNWuBTnBoalakPKsjgFWsw#scrollTo=Dataset_Train

https://colab.research.google.com/drive/1j5lPyFXXfgKNWuBTnBoalakPKsjgFWsw#scrollTo=Custom_Dataset_Train

데이터셋은 tensorflow 에 종속되고, tf.keras에서만 사용 가능하다.

위에서 만든 데이터 로드 및 전처리함수 csv_reader_dataset() 으로 데이터셋을 만들고 훈련해본다.

나머지는 기존과 동일하고, .fit메서드에 데이터셋을 전달하기만하면 된다.

train_dataset.....repeat(None)으로 하면 많은 에폭수에 데이터가 부족해질 우려가 없다.

 

 

13.2 TFRecord 포맷

https://colab.research.google.com/drive/1j5lPyFXXfgKNWuBTnBoalakPKsjgFWsw#scrollTo=TFRecord

csv파일 등 다른 포맷을 선호하면 TFRecord는 사용할필요는 없지만, 대규모의 복잡한 데이터 구조를 지원해야하는 데이터를 사용해야 한다면 TFRecord로 효율적인 저장과 읽기가 텐서플로에서 가능하다.

TFRecord는 크기가 다른 연속된 이진 레코드를 저장하는 단순한 이진포맷이다. ( 레코드길이, 레코드길이 체크썸, 실제 데이터, 데이터 체크썸 으로 구성된다.)

 

tf.io.TFRecordWriter 으로 생성하고, tf.data.TFRecordDataset 으로 읽을 수 있다.

 

13.2.1 압축된 TFRecord 파일

TFRecord를 압축해야할 때 tf.io.TFRecordOptions() 을 생성하고 매개변수로 지정해서 압축된 TFRecord파일을 만들 수 있다.

compression_type 으로 압축형식을 지정할 수 있고, 읽을때도 매개변수에 압축 형식을 지정해야한다.

 

13.2.2 프로토콜 버퍼 개요

https://colab.research.google.com/drive/1j5lPyFXXfgKNWuBTnBoalakPKsjgFWsw#scrollTo=TFRecord_Protocol_Buffer

TFRecord의 각 레코드는 일반적으로 직렬화된 프로토콜 버퍼protocol buffer(protobuf) 를 담고있다.

syntex = "proto3";
message Person {
	string name = 1;
    int32 id = 2;
    repeated string email = 3;
}

Person 객체는 proto3 버전으로 씌였고, string 타입 name, int32 타입 id, 1개 이상인 string 타입 email 이 있고, 

1,2,3 은 필드식별자로 이진표현에 씌인다. .proto 파일로 정의하고, protoc 컴파일러로 파이썬 클래스를 생성한다.

 

아래 명령어로 .proto 파일을 파이썬클래스로 변환한다.

!protoc person.proto --python_out=. --descriptor_set_out=person.desc --include_imports

 

생성한 파이썬클래스 person_pb2.py를 임포트해서 사용하면되고, 객체를 SerializeToString 으로 직렬화후 TFRecord로 저장하고 읽고 ParseFromstring 으로 파싱할 수 있다. 그런데 위 직렬화와 파싱메서드는 텐서플로 연산이 아니라서 텐서플로 함수에 포함되지 않는다.

 

 

13.2.3 텐서플로 프로토콜 버퍼

TFRecord파일에서 하나의 샘플을 표현하는 프로토콜 버퍼는 아래와 같이 정의된다.

syntax = "proto3";

message BytesList { repeated bytes value = 1; }
message FloatList { repeated float value = 1 [packed = true]; }
message Int64List { repeated int64 value = 1 [packed = true]; }
message Feature {
    oneof kind {
        BytesList bytes_list = 1;
        FloatList float_list = 2;
        Int64List int64_list = 3;
    }
};
message Features { map<string, Feature> feature = 1; };
message Example { Features features = 1; };

 

packed=True는 반복적인 수치필드의 효율적인 인코딩에 사용된다.

 

tensorflow.train 의 ByteList, FloatList, Int64List, Featrue, Features, Example 을 임포트해서 Example의 Features, Feature, ByteList, Int64List, FloatList를 정의해서 Example 인스턴스를 만들고, SerializeToString()으로 직렬화후 TFRecord포멧으로 저장할 수 있다.

 

 

13.2.4 Example 프로토콜 버퍼를 읽고 파싱하기

직렬화된 Example 프로토콜 버퍼를 읽기 위해서, 

tf.data.TFRecordDataset 으로 불러오고, tf.io.parse_single_example 으로 Example 을 하나씩 파싱한다, 이때 컬럼과 타입을 딕셔너리 형식으로 feature_description 매개변수에 지정해야한다.

고정길이 특성은 밀집함수로, 가변길이 특성은 희소 텐서로 파싱된다.

 

BytesList 는 어떤 이진데이터도 포함가능하다. jpeg 이미지같은(BMP, GIF, JPEG, PNG) 데이터를 인코딩하고 BytesList 에 넣고 다시 jpeg로 디코딩할 수 있다. tf.io.serialize_tensor() 으로 어느 텐서든 직렬화해서 BytesList에 저장가능하다.

 

Example 프로토콜 버퍼로 표현하기 힘든 리스트의 리스트구조같은 경우에 대안으로 SequenceExample 이 있다.

 

 

13.2.4 SequenceExample 프로토콜 버퍼를 사용해 리스트의 리스트 다루기

message Feature { repeaded Feature feature = 1; };
message FeatureLists { map<string, FeatureList { map<sting, FeatureList> = 1; };
message SequenceExample {
    Features context = 1;
    FeatureLists feature_lists = 2;
};

위와같이 구현된 SequenceExample 프로토콜 버퍼는 문맥을 위한 Features 와 한개 이상의 FeatureList로 이루어진 FeatureLists,

FeatureList는 여러개의 Feature 로 이루어진다.

 

tf.io.parse_single_sequence_example() 을 사용해서 하나의 객체를 파싱한다.

tf.io.parse_sequence_example() 을 사용해 배치를 파싱한다.

tf.RaggedTensor.from_sparse() 을 사용해서 가변길이의 시퀀스를 변환한다. 리스트의 리스트 (텐서를 받음)

 

13.3 입력 특성 전처리

훈련하기위한 데이터는 범주형 특성이든, 텍스트 특성이든 일단 모두 수치 특성으로 변환되어야 한다. 넘파이, 판다스, 사이킷런같은 도구로 데이터를 수치화 하던지, 데이터 API로 데이터 적재시에 동적으로 처리할 수 있다. 또는 모델에 직접 전처리 층을 추가할 수도 있다.

예를들어 정규화층을 추가한다고 할때, 층의 adapt() 메서드를 호출해 층을 모델에 추가하기전에 데이터샘플의 mean, std를 구해놔야 훈련시에 사용가능하다.

 

13.3.1 원-핫 벡터를 사용해 범주형 특성 인코딩하기

범주개수가 매우 작은 범주형 특성들은 원-핫 인코딩을 수행할 수 있다.

tf.lookup.KeyValueTnsorInitializer( ) 메서드를 이용해서, 매개변수에 범주 리스트와, 범주 인덱스를 전달해서 초기화 객체를 생성한다. 아직 데이터를 변환한 상태는 아님.

tf.lookup.StaticVocabularyTable( ) 메서드에 위의 초기화 객체와 oov out-of-vocabulary 버킷 bucket 을 지정하여 룩업 테이블을 만든다. 이때 oov 버킷은 범주 개수가 가변적일 때, 해시테이블을 만들어서 알려지지 않은 새로운 범주를 oov버킷 인덱스에 추가한다.

관찰되지않은 범주가 oov 버킷 인덱스보다 많으면 충돌이 생겨 두 범주를 구분할 수 없게된다.

이렇게 생성된 Table 객체에서 .lookup( ) 에 데이터를 넣어서 0~n_categorys-1 으로 변환하고,

이 indices를 tf.one_hot( indices , depth=len(vocab) + num_oov_buckets ) 을 통해 원-핫 벡터로 변환한다.

지금은 이 텍스트 범주를 keras.layers.TextVectorization 층을 이용해 카테고리화 시킬 수 있다. 그리고 이 결과에 one_hot 메서드를 적용하는 lambda 층을 추가해 원-핫벡터로 변경하는 것도 가능하다.

 

13.3.2 임베딩을 사용해 범주형 특성 인코딩하기

범주가 너무많은 경우에는 임베딩embedding 을 사용하는 것이 훨씬 효율적이다.

경험적으로 보통 범주개수가 10개이하이면 원-핫, 범주가 50개 이상이면 임베딩을 선호한다.

 

임베딩은 범주를 표현하는 훈련 가능한 밀집 벡터이다. 차원수는 수정가능한 하이퍼파라미터이고, 처음에는 랜덤하게 초기화되지만, 비슷한 범주들은 훈련을 거듭하면 경사 하강법이 더 가깝게 만든다. 표현이 좋을수록 예측을 정확히하기 쉽고, 범주가 유용하게 표현되도록 임베딩이 훈련되는 경향이 있다. 이를 표현 학습representation learning  이라고 한다.(임베딩은 범주를 표현하고, 이 표현이 예측을 정확히 만든다. )

 

단어 임베딩

임베딩은 유용한 표현을 만드는데 이것이, 현재 작업뿐아니라, 다른 작업에도 재사용 가능하다. 자연어처리 작업에서 임베딩을 재사용하는것이 나은 경우가 많다. 주어딘 단어 근처의 단어를 예측하기 위해 신경망을 훈련해서 뛰어난 성능의 단어 임베딩을 얻을 수 있다. 이때 단어의미 사이의 근접도만이 아니라 어떤 의미를 가진 축을 따라 임베딩 공간이 조직된다. ( 성별축, 직업축, ... ) 그런데 단어 임베딩은 종종 크게편향되고 공정성을 부여하는연구는 계속되고있다.

 

임베딩 구현하기 

먼저 각 범주의 임베딩을 담은 임베딩 행렬embedding matrix 를 랜덤하게 초기화한다. 범주 + oov 버킷 개수의 행과, 임베딩 차원개수의 열을 가진다. 임베딩 차원개수는 작업과 어휘 사전 크기에 따라 10~300 차원을 가진다.(튜닝필요)

어휘사전 vocab 과 indices, tf.lookup.KeyValueTensorInitalizer, tf.lookup.StaticVocabularyTable 으로 만든 table을 이용해서,

입력 categories의 cat_indices 를 만들고, 이를 tf.nn.embedding_lookup( embedding_matrix, cat_indices ) 으로 범주에 해당하는 행렬의 임베딩 벡터를 반환한다.

또는 keras.layers.Embedding( input_dim, output_dim ) 층을 이용해서 자동으로 행렬을 초기화하고, 범주 인덱스로 호출될 때 행렬의 인덱스를 반환한다.

 

 

13.3.3 케라스 전처리 층

keras.layers.Normalization : 특성 표준화

keras.layers.TextVectorization : 입력에 각 단어를 어휘사전의 인덱서를 인코딩한다. ( 단어 -> indice ) 혹은 단어 인덱스 대신 카운트 벡터를 출력하는 옵션도 가지고 있다. ex ) vocab = ["and", "more", "hat" ] , input = ["more", "and", "more"] , output = [1, 2, 0] 이는 단어의 순서를 완전히무시하기 때문에 BOWbag of world 라고 한다. 보통 단어카운트가 클수록 단어의 중요도를 줄이는 방향으로 정규화한다. 전체 샘플수를 단어가 등장하는 훈련 샘플 개수로 나눈 로그를 계산하고 단어 카운트와 곱한다. 이를 TF-IDF term frequency-inverse document frequency 라고 부른다.

위의 두층 모두 adapt( ) 메서드를 호출하고 일반적인 층으로 사용된다.

keras.layers.Discretization : 연속적인 데이터를 몇개의 구간으로 나누고 각 구간을 범주로 원-핫 인코딩을 수행한다. 미분가능하지 않아서 모델의 시작부분에만 사용되어야 한다. 

keras.layers.PreprocessingStage( [ ] ) : 여러 전처리 층들을 연결해서 전처리 파이프라인을 만들 수 있다.

keras.layers.PreprocessingLayer() : 층을 상속받아서 자신만의 전처리 층을 만들 수 있다.

 

 

13.4 TF 변환

https://colab.research.google.com/drive/1j5lPyFXXfgKNWuBTnBoalakPKsjgFWsw#scrollTo=TF_Transform

전처리는 계산비용이 크기 때문에 훈련중에 수행하는 것 보다, 사전에 처리하면 속도를 크게 증가시킬 수 있다. 에폭마다 전처리가 수행되기 보다 훈련전 모든 샘플을 한번씩 전처리한다. RAM에 들어갈만큼 데이터가 작으면 cache 메서드를 사용하고, 데이터가 아주 크면 아파치 빔Apache Beam, 스파크spark 같은 도구로 훈련전에 데이터 처리 파이프라인을 구축해서 수행할 수 있다.

그런데 모델을 훈련하고 배포할때, 전처리 과정 또한 배포에 포함되어야한다. 그래서 전처리 과정이 바뀔때마다, 코드를 바꿔줘야하고 유지보수를 어렵게만든다. 그래서 훈련 전 전처리 연산과, 상용화된 환경에서 전처리 연산이 차이가 나는 훈련/서빙 왜곡training/serving skew 은 버그나 성능 감소로 이어진다. 

이보다는 훈련된 모델을 배포하기전에 전처리 담당하는 층을 동적으로 추가하면 전처리 코드가 2가지로만 나뉘기때문에 관리하기 그나마 낫다.

이것보다 전처리 연산을 한번만 수행하는 TF 변환transform 을 소개한다. TF변환은 텐서플로 모델의 엔드투엔드 플랫폼인 TFXtensorflow Extended (https://tensorflow.org/tfx ) 의 일부로 TFX를 먼저 설치하고, TF변환 함수를 사용해서 전처리 함수를 한번만 정의한다.

 

13.5 텐서플로 데이터셋(TFDS) 프로젝트

https://colab.research.google.com/drive/1j5lPyFXXfgKNWuBTnBoalakPKsjgFWsw#scrollTo=Tensorflow_Datasets

텐서플로 데이터셋 (https://tensorflow.org/datasets ) 은 손쉽게 데이터셋을 다운로드 할 수 있다. tensorflow-datasets 라이브러리를 설치하고, load로 데이터셋을 다운한다.

'독서 리마인더 > 핸즈온 머신러닝' 카테고리의 다른 글

14장  (0) 2021.12.05
13장 연습문제  (0) 2021.12.01
12장 연습문제  (0) 2021.11.26
12장  (0) 2021.11.24
11장 연습문제  (0) 2021.11.22