TFRecord 포맷
- tensorflow는 대용량 데이터를 저장하기 위해 tfrecord라는 포맷을 사용한다.
- tfrecord는 크기가 다른 여러가지 레코드를 저장하는 이진 포맷이다.
- 각 레코드는 레코드 길이 CRC checksum(길이가 올바른지 체크하는), 실제 데이터, 데이터를 위한 CRC checksum으로 구성된다.
with tf.io.TFRecordWriter("my_data.tfrecord") as f:
f.write(b"This is the first record")
f.write(b"And this is the second record")
- 위 코드와 같은 방법으로 tfrecord를 작성할 수 있다.
filepaths = ["my_data.tfrecord"]
dataset = tf.data.TFRecordDataset(filepaths)
for item in dataset:
print(item)
#tf.Tensor(b'This is the first record', shape=(), dtype=string)
#tf.Tensor(b'And this is the second record', shape=(), dtype=string)
- tfrecord를 불러올 때는 filepath를 인자로 사용해
tf.data.TFRecordDataset()
을 통해 불러올 수 있다.
filepaths = ["my_test_{}.tfrecord".format(i) for i in range(5)]
for i, filepath in enumerate(filepaths):
with tf.io.TFRecordWriter(filepath) as f:
for j in range(3):
f.write("File {} record {}".format(i, j).encode("utf-8"))
dataset = tf.data.TFRecordDataset(filepaths, num_parallel_reads=3)
for item in dataset:
print(item)
- list_files()와 interleave()를 사용했던 것 처럼 여러 파일에서 레코드를 위 코드처럼 번갈아가며 읽을 수도 있다.
13.2.1 압축된 TFRecord 파일
- tfrecord file을 압축하여 저장할 수 있다.
options = tf.io.TFRecordOptions(compression_type="GZIP")
with tf.io.TFRecordWriter("my_compressed.tfrecord", options) as f:
f.write(b"This is the first record")
f.write(b"And this is the second record")
- 저장하는 코드이다. options만 지정해주면 압축이 가능하다.
dataset = tf.data.TFRecordDataset(["my_compressed.tfrecord"],
compression_type="GZIP")
for item in dataset:
print(item)
13.2.2 프로토콜 버퍼 개요
프로토콜 버퍼
직렬화
- 데이터를 파일로 저장하거나 네트워크 통신이 가능하도록 형식을 바꾸어 주는 것.
프로토콜 버퍼
- 프로토콜 버퍼는 google이 개발한 이진 포맷으로 파일 저장이나 네트워크 전송 등을 위해 사용한다.
- 직렬화 된 데이터를 이진 포맷으로 저장하기 때문에 더 적은 용량으로 데이터 전송이 가능하다.
- 데이터를 직렬화 하기 때문에 Language Neutral하다.
- jpg, png파일과 같은 이미지 파일들을 사용할 때 필요한 인코딩, 디코딩 작업이 필요 없이 직렬화된 데이터를 읽으면 되므로 편리하다.
- 보통 데이터를 보관할 때 data, target을 분리하여 보관하게 되는데, 프로토콜 버퍼를 사용하면 직렬화 하여 하나의 파일로 보관할 수 있기 때문에 data와 target을 매칭해주는 코드를 추가적으로 작성할 필요가 없어져 불필요한 코드를 줄일 수 있다.
프로토콜 버퍼 생성
#person.proto로 파일 저장
%%writefile person.proto
syntax = "proto3"; #protocol buffer version3
message Person {
string name = 1;
int32 id = 2;
repeated string email = 3;
}
- C언어의 구조체 형식과 비슷하게 프로토콜 버퍼를 만들 수 있다.
- 1, 2, 3은 각각 필드 식별자로 데이터의 이진 표현에 사용된다.
프로토콜 버퍼 컴파일
!protoc person.proto --python_out=. --descriptor_set_out=person.desc --include_imports
- 프로토콜 버퍼는 protoc라는 c언어 기반 컴파일러를 통해 컴파일을 진행하고 --python_out=. 옵션을 통해 python 클래스를 생성해야 사용할 수 있다.
!ls
# person.desc person_pb2.py person.proto
- 컴파일이 끝나면 디렉토리에 person.desc, person_pb2.py 2개의 파일이 추가된다. 이중 pb2(protocol buffer 2)가 붙은 파일을 import하여 클래스를 사용할 수 있다.
from person_pb2 import Person
person=Person(name='AI', id=123, email=['a@b.com'])
person.name='Alice' #필드는 수정 가능하다.
person.email.append('c@d.com')
s = person.SerializeToString()
s
#b'\n\x05Alice\x10{\x1a\x07a@b.com\x1a\x07c@d.com'
- 프로토콜 버퍼를 사용한 Person 클래스를 사용해 person객체를 만들었다.
- person 객체의 각 필드들은 수정이 가능하고, 반복 필드(배열)의 경우 인덱싱을 통해 참조 또한 가능하다.
- person객체는
ParseFromString()
메소드를 통해 직렬화 할 수 있다. - 직렬화 한 데이터는 네트워크를 통해 전송이 가능하다.
from person_pb2 import Person
person2=Person() #객체 생성
person2.ParseFromString(s) #파싱
- 네트워크를 통해 전송받은 직렬화된 데이터는
ParseFromString()
메소드를 통해 파싱이 가능하다. - 혹은 직렬화된 데이터를 TFRecord파일로 저장한 후 읽고 파싱하는 것도 가능하다.
- 하지만,
SerializeToString()
과ParseFromString()
은 텐서플로우 연산이 아니기 때문에 텐서플로우 함수에 포함할 수 없다. - 텐서플로우에서는 이러한 문제를 해결하기 위해 특별한 프로토콜 버퍼 정의를 가지고 있다. 이를 13.2.3에서 살펴본다.
13.2 텐서플로 프로토콜 버퍼
- TFRecord파일에서 주로 사용하는 프로토콜 버퍼는 Example 프로토코 버퍼이다.
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; };
- Example 프로토콜 버퍼의 구조는 위와 같다.
Example 클래스를 사용해 객체 생성
from tensorflow.train import BytesList, FloatList, Int64List
from tensorflow.train import Feature, Features, Example
person_example=Example(
features=Features(
feature={
"name" : Feature(bytes_list=BytesList(value=[b"Alice"])),
"id" : Feature(int64_list=Int64List(value=[1,2,3])),
"emails" : Feature(bytes_list=BytesList(value=[b"a@b.com",
b"c@d.com"]))
}
)
)
데이터 직렬화와 TFRecord형식으로 저장
with tf.io.TFRecordWrite("my_contacts.tfrecord") as f:
f.write(person_example.SerializeToString())
13.2.3 Example 프로토콜 버퍼를 읽고 파싱하기
feature_description = {
"name": tf.io.FixedLenFeature([], tf.string, default_value=""),
"id": tf.io.FixedLenFeature([], tf.int64, default_value=0),
"emails": tf.io.VarLenFeature(tf.string),
}
for serialized_example in tf.data.TFRecordDataset(["my_contacts.tfrecord"]):
parsed_example = tf.io.parse_single_example(serialized_example, #파싱
feature_description)
- Example 프로토콜 버퍼를 파싱하기 위해서는
parse_single_example()
메소드를 사용하여야 한다. - Example 프로토콜 버퍼를 읽기 위해서는 feature description을 정의해서
parse_single_example()
메소드의 인자로 넣어줘야 한다.
dataset=tf.data.TFRecordDataset(["my_contacts.tfrecord"]).batch(10)
for serialized_examples in dataset:
parsed_examples=tf.io.parse_example(serialized_examples, feature_description)
parse_exmple()
메소드를 사용하면 데이터를 하나씩 파싱하는 것이 아니라 배치 단위로 파싱하는 것이 가능해진다.
13.2.4 SequenceExample 프로토콜 버퍼를 사용해 리스트의 리스트 다루기
SequenceExample 프로토콜 버퍼의 정의
message FeatureList { repeated Feature feature=1;};
message FeatureLists { map<string, FeatureList> feature_list=1;};
message SequenceExample{
Feature context = 1;
FeatureLists feature_lists=2;
}
- Features 객체는 문맥 데이터를 정의한다.
- FeatureLists에는 한 개 이상의 FeatureList가 포함된다.
- Feature 객체는 바이트 스트링의 리스트나 64비트 정수의 리스트, 실수의 리스트일 수 있다.
parsed_context, parsed_feature_lists=tf.io.parse_single_sequence_example(
serialized_seqence_example, xontext_feature_descriptions,
seqence_feature_descriptions)
parsed_context=tf.RaggedTensor.from_sparse(parsed_feature_lists["context"])
- Sequence 프로토콜 버퍼를 파싱하는 방법은 Example 프로토콜 버퍼를 파싱하는 방법과 동일하다.
'Deep Learning > Hands On Machine Learning' 카테고리의 다른 글
15.1 순환 뉴런과 순환 층 (0) | 2021.11.21 |
---|---|
13.3 입력 특성 전처리 (0) | 2021.11.12 |
13.1 데이터 API (0) | 2021.11.12 |
12.4 텐서플로 함수와 그래프 (0) | 2021.11.11 |
12.3 사용자 정의 모델과 훈련 알고리즘 (0) | 2021.11.11 |