SequenceExample برای Sequenceهای تو در تو
tf.train.SequenceExample برای Dataهایی طراحی شده است که علاوه بر Context ثابت، چند Sequence با طول متغیر دارند. ساختار آن شامل یک Features context و یک مجموعهٔ نامدار از FeatureListهاست:
message FeatureList {
repeated Feature feature = 1;
}
message FeatureLists {
map<string, FeatureList> feature_list = 1;
}
message SequenceExample {
Features context = 1;
FeatureLists feature_lists = 2;
}
برای مثال Context میتواند Author/Title/Date باشد و FeatureListها Sentenceها یا Commentهای Document را نگه دارند. Parse یک نمونه با tf.io.parse_single_sequence_example() و Batch با tf.io.parse_sequence_example() انجام میشود. Sequenceهای با طول متغیر را میتوان از SparseTensor به RaggedTensor تبدیل کرد.
Keras Preprocessing Layerها
آمادهسازی Input معمولاً شامل Normalize کردن Featureهای عددی، Encode کردن Categoryها و Text، Resize/Crop کردن Image و موارد دیگر است. سه Strategy رایج وجود دارد:
- Preprocessing قبل از Training: با NumPy/Pandas/Scikit-Learn؛ ساده اما باید در Production همان Logic دوباره اجرا شود.
- Preprocessing در tf.data: بسیار کارآمد و مناسب CPU Parallelism و Data Pipeline، ولی هنگام Deploy باید Pipeline و Model هماهنگ بمانند.
- Preprocessing داخل Model: با Keras Layerها؛ Raw Data مستقیماً وارد Model میشود و احتمال Training/Serving Skew کاهش مییابد.
میتوان ترکیبی عمل کرد: Layerهای قابلاجرا روی GPU یا ساده را داخل Model گذاشت و Operationهای سنگین I/O/CPU را در tf.data انجام داد.
Normalization Layer
tf.keras.layers.Normalization Featureهای عددی را Standardize میکند. اگر Mean و Variance از قبل معلوم نیست، Layer را روی Sample نمایندهای از Training Data adapt() میکنیم:
norm_layer = tf.keras.layers.Normalization()
norm_layer.adapt(X_train)
پس از Adapt، Layer Mean و Variance را بهعنوان State غیرقابلTrain با Gradient نگه میدارد. میتوان آن را ابتدای Model قرار داد:
model = tf.keras.Sequential([
norm_layer,
tf.keras.layers.Dense(50, activation="relu"),
tf.keras.layers.Dense(1)
])
شکل 13-4. قرار دادن Preprocessing Layerها داخل Model
گزینهٔ دیگر این است که Preprocessing Model جدا باشد و هنگام Dataset Mapping اجرا شود، سپس برای Deployment همان Layerها در ابتدای Model نهایی قرار گیرند. این روش میتواند Training را سریعتر کند، چون Data یکبار Preprocess و Cache میشود.
شکل 13-5. Preprocess در Training Pipeline و Deploy داخل Model نهایی
یک روش مفید برای Embed کردن State Preprocessing در Model، گرفتن Weightهای Normalization و ساخت Layer ثابت در Model نهایی است. نکتهٔ اصلی این است که Statistics فقط از Training Data یاد گرفته شوند، نه Validation/Test.
Discretization Layer
گاهی Feature عددی را میخواهیم به Bucketهای Category تبدیل کنیم؛ مثلاً Age را به بازهها تقسیم کنیم. Discretization با bin_boundaries این کار را انجام میدهد:
age = tf.constant([[10.], [50.], [70.]])
discretize_layer = tf.keras.layers.Discretization(
bin_boundaries=[18., 50.])
discretize_layer(age)
خروجی Index Bucket است و معمولاً بعداً با One-Hot/Multi-Hot یا Embedding Encode میشود.
CategoryEncoding Layer
CategoryEncoding Integer Category IDها را به One-Hot، Multi-Hot یا Count Vector تبدیل میکند. برای یک Feature تکمقداری معمولاً output_mode="one_hot" و برای List از Categoryها "multi_hot" یا "count" استفاده میشود:
encoding_layer = tf.keras.layers.CategoryEncoding(
num_tokens=3, output_mode="one_hot")
این Layer انتظار دارد Category قبلاً به Integer ID تبدیل شده باشد؛ برای String Category، StringLookup این نگاشت را انجام میدهد.
StringLookup Layer
StringLookup Vocabulary را نگه میدارد و Stringها را به Integer Index یا مستقیم به One-Hot/Multi-Hot تبدیل میکند. Vocabulary میتواند دستی داده شود یا با adapt() از Training Data استخراج شود:
cities = ["Auckland", "Paris", "Paris", "San Francisco"]
str_lookup_layer = tf.keras.layers.StringLookup()
str_lookup_layer.adapt(cities)
str_lookup_layer(["Paris", "Auckland", "Unknown"])
Index صفر معمولاً برای Mask و یک یا چند Index برای Out-of-Vocabulary یا OOV رزرو میشود. تعداد OOV Bucketها قابل تنظیم است و String ناشناخته با Hash به یکی از آنها میرود. برای Integer Category نیز IntegerLookup وجود دارد.
Hashing Layer
اگر Vocabulary بسیار بزرگ یا Dynamic است و نمیخواهیم adapt() انجام دهیم، Hashing String یا Integer را مستقیم به تعداد ثابتی Bucket نگاشت میکند:
hashing_layer = tf.keras.layers.Hashing(num_bins=10)
hashing_layer([["Paris"], ["Tokyo"], ["Auckland"]])
مزیت آن عدم نیاز به Vocabulary است؛ عیب آن Collision است، یعنی Categoryهای متفاوت ممکن است در یک Bucket قرار گیرند. اگر Vocabulary قابل مدیریت باشد، StringLookup معمولاً اطلاعات بیشتری حفظ میکند.
Encode کردن Category با Embedding
One-Hot برای Vocabulary بسیار بزرگ Sparse و پرهزینه است. Embedding به هر Category یک Vector Dense و قابلTrain اختصاص میدهد. در شروع Training این Vectorها Random هستند، سپس Backpropagation آنها را به Representationهای مفید تبدیل میکند. Categoryهایی که رفتار مشابهی برای Task دارند معمولاً در Embedding Space به هم نزدیک میشوند.
شکل 13-6. بهبود تدریجی Embeddingها طی Training
Embedding Matrix یک Parameter Trainable است: هر Row Vector یک Category است. بهجای Vector بسیار بزرگ One-Hot، با Index کردن Row مناسب یک Vector کوچک، مثلاً ۸ یا ۳۲ بعدی، گرفته میشود. این موضوع در بخش بعد با Layer استاندارد Embedding و Text Embedding ادامه مییابد.