From e7d8ae2f100917088fb7365fd024c05244f91571 Mon Sep 17 00:00:00 2001 From: Aousnik Gupta Date: Fri, 2 Oct 2020 09:39:42 +0530 Subject: [PATCH 1/2] text -> sign using transformer decoder --- .../src/NLP/SignGAN/bert_utils.py | 85 +++ .../multi_cased_L-12_H-768_A-12/README.md | 1 + .../NLP/SignGAN/phoenix-2014-T.v3/README.md | 2 + Machine_Learning/src/NLP/SignGAN/readme.md | 1 + .../src/NLP/SignGAN/signgan.ipynb | 589 ++++++++++++++++++ Machine_Learning/src/NLP/SignGAN/train.py | 115 ++++ .../src/NLP/SignGAN/utils/__init__.py | 1 + .../src/NLP/SignGAN/utils/conv_attention.py | 299 +++++++++ .../src/NLP/SignGAN/utils/discriminator.py | 399 ++++++++++++ .../src/NLP/SignGAN/utils/errors.py | 18 + .../src/NLP/SignGAN/utils/generator.py | 184 ++++++ .../src/NLP/SignGAN/utils/losses.py | 33 + .../src/NLP/SignGAN/utils/video.py | 94 +++ 13 files changed, 1821 insertions(+) create mode 100644 Machine_Learning/src/NLP/SignGAN/bert_utils.py create mode 100644 Machine_Learning/src/NLP/SignGAN/models/multi_cased_L-12_H-768_A-12/README.md create mode 100644 Machine_Learning/src/NLP/SignGAN/phoenix-2014-T.v3/README.md create mode 100644 Machine_Learning/src/NLP/SignGAN/readme.md create mode 100644 Machine_Learning/src/NLP/SignGAN/signgan.ipynb create mode 100644 Machine_Learning/src/NLP/SignGAN/train.py create mode 100644 Machine_Learning/src/NLP/SignGAN/utils/__init__.py create mode 100644 Machine_Learning/src/NLP/SignGAN/utils/conv_attention.py create mode 100644 Machine_Learning/src/NLP/SignGAN/utils/discriminator.py create mode 100644 Machine_Learning/src/NLP/SignGAN/utils/errors.py create mode 100644 Machine_Learning/src/NLP/SignGAN/utils/generator.py create mode 100644 Machine_Learning/src/NLP/SignGAN/utils/losses.py create mode 100644 Machine_Learning/src/NLP/SignGAN/utils/video.py diff --git a/Machine_Learning/src/NLP/SignGAN/bert_utils.py b/Machine_Learning/src/NLP/SignGAN/bert_utils.py new file mode 100644 index 00000000..3316b914 --- /dev/null +++ b/Machine_Learning/src/NLP/SignGAN/bert_utils.py @@ -0,0 +1,85 @@ +import tensorflow as tf +import numpy as np +import bert + +#max_sequence_length = 64 + +class Bert(object): + def __init__(self, max_sequence_length=64): + super(Bert, self).__init__() + self.max_seq_length = max_sequence_length + self.model_dir = 'models/multi_cased_L-12_H-768_A-12' + self.model_ckpt = 'models/multi_cased_L-12_H-768_A-12/bert_model.ckpt' + self.vocab_file = 'models/multi_cased_L-12_H-768_A-12/vocab.txt' + + self.model = self.create_bert_model() + self.tokenizer = self.create_bert_tokenizer() + + def create_bert_model(self): + bert_params = bert.params_from_pretrained_ckpt(self.model_dir) + l_bert = bert.BertModelLayer.from_params(bert_params, name="bert") + + l_input_ids = tf.keras.layers.Input(shape=(self.max_seq_length,), dtype='int32') + output = l_bert(l_input_ids) + model = tf.keras.Model(inputs=l_input_ids, outputs=output) + model.build(input_shape=(None, self.max_seq_length)) + + bert.load_stock_weights(l_bert, self.model_ckpt) + + return model + + def create_bert_tokenizer(self): + model_name = 'multi_cased_L-12_H-768_A-12' + do_lower_case = not (model_name.find("cased") == 0 or model_name.find("multi_cased") == 0) + bert.bert_tokenization.validate_case_matches_checkpoint(do_lower_case, self.model_ckpt) + tokenizer = bert.bert_tokenization.FullTokenizer(self.vocab_file, do_lower_case) + + return tokenizer + + def tokenize(self, sequence): + sequence = self.tokenizer.tokenize(sequence) + return ["[CLS]"] + sequence + ["[SEP]"] + + def get_sequence_ids(self, tokenized_sequence): + sequence_ids = self.tokenizer.convert_tokens_to_ids(tokenized_sequence) + sequence_ids = sequence_ids + [0] * (self.max_seq_length - len(sequence_ids)) # padding + return sequence_ids + + def preprocess(self, sequence): + return self.get_sequence_ids(self.tokenize(sequence)) + + def preprocess_batch(self, sequence_list): + if type(sequence_list) != list: + sequence_list = [sequence_list] + + preprocessed_sequence_ids = [] + for sequence in sequence_list: + preprocessed_sequence_ids.append(self.preprocess(sequence)) + return np.array(preprocessed_sequence_ids) + + def __call__(self, sequence_list): + #if type(sequence_list) != list or sequence_list: + # sequence_list = [sequence_list] + preprocessed_sequence_ids = self.preprocess_batch(sequence_list) + output = self.model.predict(preprocessed_sequence_ids) # (?, 64, 768) for all sentences in batch + word_embeddings = output # (?, 64, 768) + + sentence_embeddings = [] + for sentence in output: + sentence_vector = sentence[0] # feature vector for [CLS] is the sentence vector for each sentence + sentence_embeddings.append(sentence_vector) + + sentence_embeddings = np.array(sentence_embeddings) # (?, 768) + + return word_embeddings, sentence_embeddings + +''' +def main(): + bert_model = Bert(64) + word_embeddings, sentence_embeddings = bert_model.predict(['sonst wechselhaft mit schauern und gewittern die uns auch am wochenende begleiten', + 'und nun die wettervorhersage für morgen donnerstag den zwölften august']) + print(word_embeddings.shape, sentence_embeddings.shape) + +if __name__ == "__main__": + main() +''' \ No newline at end of file diff --git a/Machine_Learning/src/NLP/SignGAN/models/multi_cased_L-12_H-768_A-12/README.md b/Machine_Learning/src/NLP/SignGAN/models/multi_cased_L-12_H-768_A-12/README.md new file mode 100644 index 00000000..12c94828 --- /dev/null +++ b/Machine_Learning/src/NLP/SignGAN/models/multi_cased_L-12_H-768_A-12/README.md @@ -0,0 +1 @@ +Multilingual BERT was used as German Text Encoder diff --git a/Machine_Learning/src/NLP/SignGAN/phoenix-2014-T.v3/README.md b/Machine_Learning/src/NLP/SignGAN/phoenix-2014-T.v3/README.md new file mode 100644 index 00000000..55de7852 --- /dev/null +++ b/Machine_Learning/src/NLP/SignGAN/phoenix-2014-T.v3/README.md @@ -0,0 +1,2 @@ +Dataset stored here. +Look at utils/video.py for location specifics diff --git a/Machine_Learning/src/NLP/SignGAN/readme.md b/Machine_Learning/src/NLP/SignGAN/readme.md new file mode 100644 index 00000000..c8648d54 --- /dev/null +++ b/Machine_Learning/src/NLP/SignGAN/readme.md @@ -0,0 +1 @@ +# SignGAN diff --git a/Machine_Learning/src/NLP/SignGAN/signgan.ipynb b/Machine_Learning/src/NLP/SignGAN/signgan.ipynb new file mode 100644 index 00000000..d5b04cd0 --- /dev/null +++ b/Machine_Learning/src/NLP/SignGAN/signgan.ipynb @@ -0,0 +1,589 @@ +{ + "metadata": { + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.4-final" + }, + "orig_nbformat": 2, + "kernelspec": { + "name": "research", + "display_name": "research" + } + }, + "nbformat": 4, + "nbformat_minor": 2, + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# SignGAN" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Importing the Libraries" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import tensorflow as tf\n", + "import numpy as np\n", + "import glob\n", + "import os\n", + "\n", + "from bert_utils import Bert\n", + "from utils.video import Video\n", + "from utils.conv_attention import *\n", + "from utils.generator import *\n", + "from utils.discriminator import *\n", + "from utils.losses import *" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "config = tf.compat.v1.ConfigProto()\n", + "config.gpu_options.allow_growth = True\n", + "sess = tf.compat.v1.Session(config=config)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Pretrained BERT Model\n", + "Multilingual Cased BERT is used" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "Done loading 196 BERT weights from: models/multi_cased_L-12_H-768_A-12/bert_model.ckpt into (prefix:bert). Count of weights not found in the checkpoint was: [0]. Count of weights with mismatched shape: [0]\nUnused weights from checkpoint: \n\tbert/embeddings/token_type_embeddings\n\tbert/pooler/dense/bias\n\tbert/pooler/dense/kernel\n\tcls/predictions/output_bias\n\tcls/predictions/transform/LayerNorm/beta\n\tcls/predictions/transform/LayerNorm/gamma\n\tcls/predictions/transform/dense/bias\n\tcls/predictions/transform/dense/kernel\n\tcls/seq_relationship/output_bias\n\tcls/seq_relationship/output_weights\n(1, 64, 768) (1, 768)\n" + } + ], + "source": [ + "bert = Bert()\n", + "word_embeddings, sentence_embeddings = bert.predict(['sonst wechselhaft mit schauern und gewittern die uns auch am wochenende begleiten'])\n", + "print(word_embeddings.shape, sentence_embeddings.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Global Variables" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "num_clips = 32\n", + "T = 16 # let\n", + "MAX_VIDEO_LENGTH = 512 # 475 is the longest\n", + "FRAME_DIM = (64, 64, 3)\n", + "VIDEO_DIM = (512, 64, 64, 3)\n", + "data_dir = 'phoenix-2014-T.v3/PHOENIX-2014-T-release-v3/PHOENIX-2014-T/features/fullFrame-210x260px/'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Video Object\n", + "with example from training set" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "(640, 64, 64, 3)\n(40, 16, 64, 64, 3)\n" + }, + { + "output_type": "execute_result", + "data": { + "text/plain": "'\\npad_mask = video_obj.padding_mask(current_sequence_length)\\nprint(pad_mask.shape)\\n\\nlook_mask = video_obj.look_ahead_mask()\\nprint(look_mask.shape)\\n'" + }, + "metadata": {}, + "execution_count": 4 + } + ], + "source": [ + "video_obj = Video(T, MAX_VIDEO_LENGTH, FRAME_DIM, VIDEO_DIM, data_dir)\n", + "\n", + "video_real = video_obj.get_video('train', '05January_2010_Tuesday_tagesschau-2664')\n", + "current_sequence_length = video_real.shape[0]\n", + "\n", + "video_real = video_obj.preprocess_video(video_real)\n", + "print(video_real.shape)\n", + "\n", + "video_real = video_obj.divide_sequence(video_real)\n", + "print(video_real.shape)\n", + "\n", + "video_wrong = video_obj.get_video('train', '03June_2011_Friday_tagesschau-7649')\n", + "current_sequence_length = video_wrong.shape[0]\n", + "\n", + "video_wrong = video_obj.preprocess_video(video_wrong)\n", + "print(video_wrong.shape)\n", + "\n", + "video_wrong = video_obj.divide_sequence(video_wrong)\n", + "print(video_wrong.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Attention\n", + "* First, perform convolutions across the whole video, then go for separable self attention for the whole video masked, i.e, (40, 40T, H, W, C), outputs (40, 40T, H, W, C), where T=16 and 40=640//T. 640 being the length of the whole video.\n", + "* Convolution operations will be restricted to only T frames at a time. There will be no intermingling of 2 or more sets of T frames. Thus 3D CONV will take care of extracting local, temporal and spatial features only. Hack : Batched\n", + "* After the conv operations there will be MAX_SEQ_LENGTH // T i.e, 40 (here) elements each of size (T, H, W, C), making the output of convolution ops, to be of dim -> (40, T, H, W, C) converted to (40T, H, W, C). This will be passed through attention blocks and outputs (40, 40T, H, W, C) along with masking.\n", + "* This attention performs masked attention. We will have MAX_SEQ_LENGTH // T i.e, 40 (here) masks in total each for time, height, width. Each mask being of shape (40, 40T, H, W, H*W) for time, W*T for height and H*T for width.\n", + "* Only after the whole video is generated are the losses calculated, and backpropped.\n", + "* During testing just \"start\" token will be provided and the rest of the sequence will be just padding, and each time the generator produces T frames, those T frames will be concatenated along with the \"start\" token and then convoluted again to produce the next T frames.\n", + "\n", + "### Attention mechanism will require residual connections otherwise gradients will vanish\n", + "\n", + "## Word Frame Attention\n", + "* Last dimension of both masked_attention_output and semantic_word_matrix must be same\n", + "* 2nd last dimenstion i.e, 1st dimenstion of semantic_word_matrix should equal to H*W of masked-separable-self-attention output \n", + "* that is, we bring both to a common semantic space using conv for frames and dense for embeddings" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "(4, 16, 8, 8, 3)\n" + } + ], + "source": [ + "attention = Attention(channel_dimension=3)\n", + "\n", + "num_clips = 32\n", + "t, h, w, c = 16, 8, 8, 3\n", + "\n", + "x = tf.random.normal(shape=(t, h, w, c), mean=0.5, stddev=0.5)\n", + "\n", + "# this step is done only before the 1st attention block\n", + "x = tf.reshape(tf.repeat(x, repeats=num_clips, axis=0), (num_clips, t, h, w, c))\n", + "\n", + "mask_t = look_ahead_mask(num_clips, (t, h, w, h*w))\n", + "mask_h = look_ahead_mask(num_clips, (t, h, w, t*w))\n", + "mask_w = look_ahead_mask(num_clips, (t, h, w, t*h))\n", + "\n", + "word_embeddings = tf.squeeze(word_embeddings)\n", + "\n", + "print(x.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Conv-Attention Block\n", + "* The input (640, 64, 64, 3) video will be explicitly split into (40, 16, 64, 64, 3).\n", + "* HACK : This reshaped input will be fed to 3D conv block with batch size being 40(hack), for local spatial and temporal feature extraction\n", + "* Conv Block output will be (40, 16, 8, 8, 64), which will be reshaped to (640, 8, 8, 64) and sent forward for masked-separable-self-attention followed by word-frame-attention for a few number of times (Attention Block), with addition and layer normalisation after each attention block\n", + "* Conv Block : \n", + " * Format : num_filters, kernel, strides, padding\n", + " * {8, (3, 3, 3), (2, 2, 2), same} -> {16, (3, 3, 3), (2, 2, 2), same} -> {32, (3, 3, 3), (2, 2, 2), same} -> {out_channels(64), (3, 3, 3), (2, 2, 2), same}" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "Overall Video : (64, 64, 64, 3)\nInput Video Shape : (4, 16, 64, 64, 3)\nAttention Output Shape : (4, 8, 8, 8, 64)\n" + } + ], + "source": [ + "num_clips = 4 # instead of 32\n", + "t, h, w, c = 16, 64, 64, 3\n", + "print(\"Overall Video : \", (num_clips * t, h, w, c))\n", + "print(\"Input Video Shape : \", (num_clips, t, h, w, c))\n", + "\n", + "x = tf.random.normal(shape=(num_clips, t, h, w, c), mean=0.5, stddev=0.5)\n", + "word_embeddings = tf.squeeze(word_embeddings)\n", + "\n", + "conv_attn = ConvAttn(num_attention_blocks=4)\n", + "x = conv_attn(x, word_embeddings)\n", + "print(\"Attention Output Shape : \", x.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Conditional GAN (3D)\n", + "* The conditions generated by ConvAttn block will be used along ith upscaled randomness to generate a video with T frames for each batch of size num_clips(40)\n", + "* use LayerNorm instead of BatchNorm, because BatchNorm outputs NaN because of the padding and masking\n", + "* In the future if this fails, even this block may have attention blocks in the intermediate layers\n", + "* Upsampling z : \n", + "* Concatenate upsampled z with conv_attn_output\n", + "* upsample to produce the video\n", + "* this 'z' will be upscaled to num_clips x (num_clips x t, h, w, channels) and concatenated with conv_attn_output, along the channel dimension, which together will be upscaled using deconv to produce a 40 x (16, 64, 64, 3) video. Hack : Use 40 as batch size throughout\n", + "* We will feed 'z' from outside. If it is inside it'll stay constant and won't be random" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "Output Shape : (4, 16, 64, 64, 3)\nOutput Video Shape : (64, 64, 64, 3)\n" + } + ], + "source": [ + "z = tf.random.normal(shape=(1, 100))\n", + "cdcgan = CDCGAN()\n", + "z = cdcgan(z, x)\n", + "print(\"Output Shape : \", z.shape)\n", + "print(\"Output Video Shape : \", (z.shape[0] * z.shape[1], z.shape[2], z.shape[3], z.shape[4]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Thus output video is in the shape we wanted" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Full Generator\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "(64, 768)\nConv1 Out Shape : (32, 16, 64, 64, 8)\nConv2 Out Shape : (32, 8, 32, 32, 16)\nConv3 Out Shape : (32, 4, 16, 16, 32)\nConv4 Out Shape : (32, 2, 8, 8, 64)\n(32, 8, 8, 8, 256) (32, 8, 8, 8, 512)\n(32, 16, 64, 64, 3)\n" + } + ], + "source": [ + "num_clips = 32 # instead of 32\n", + "t, h, w, c = 16, 64, 64, 3\n", + "\n", + "x = tf.random.normal(shape=(num_clips, t, h, w, c), mean=0.5, stddev=0.5)\n", + "word_embeddings = tf.squeeze(word_embeddings)\n", + "z = tf.random.normal(shape=(1, 100))\n", + "\n", + "generator = Generator()\n", + "x = generator(x, word_embeddings, z)\n", + "print(x.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Discriminators\n", + "* Batched\n", + "* For discriminators we also consider the batch dimension\n", + "* Before the video goes into the discriminator we have to reshape the video to (1, num_clips * t, h, w, c), 1 -> batch_size" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Video Discriminator\n", + "* Gotta setup proper input pipelines for discriminator training\n", + "* 64, 64, 64, 3 -> 32, 64, 64, 16 -> 16, 64, 64, 32 -> 8, 32, 32, 64 -> 4, 16, 16, 128 -> 2, 8, 8, 256 -> 1, 4, 4, 512" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "tf.Tensor(\n[[0.45877308]\n [0.6192273 ]], shape=(2, 1), dtype=float32)\n" + } + ], + "source": [ + "v = tf.random.normal((2, 32 * 16, 64, 64, 3)) # batch dimension considered \n", + "s = tf.random.normal((2, 768))\n", + "\n", + "video_disc = VideoDiscriminator()\n", + "vid_disc_out = video_disc(v, s)\n", + "print(vid_disc_out) # 2 outputs for batch_size = 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Frame Discriminator\n", + "* 2-fold\n", + "* Outputs \"{0 ... 1}\" for single frame level\n", + "* Outputs temporal (difference between 2 consecutive frames in euclidean norm, for each pair) downscaled as output, i.e, 1 number as output per pair of consecutive frames\n", + "* One part of both the discriminators are shared" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "(2, 8) (2, 7)\n" + } + ], + "source": [ + "# Total frames kept 8 instead of 640 because of ResourceExhaustError\n", + "v = tf.random.normal((2, 8, 64, 64, 3)) # kept batch_size = 2 here\n", + "s = tf.random.normal((2, 768))\n", + "\n", + "frame_disc = FrameDiscriminator()\n", + "frame_disc_out, motion_disc_out = frame_disc(v, s)\n", + "\n", + "print(frame_disc_out.shape, motion_disc_out.shape)\n", + "# Thus 2 outputs of each frame and motion disc, for batch_size=2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Full Discriminator" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "discriminator = Discriminator()\n", + "\n", + "v = tf.random.normal((1, 32 * 16, 64, 64, 3)) # batch dimension considered \n", + "s = tf.random.normal((1, 768)) # keeping batch_size = 1\n", + "\n", + "video_disc_out, frame_disc_out, motion_disc_out = discriminator(v, s)\n", + "print(video_disc_out.shape, frame_disc_out.shape, motion_disc_out.shape)\n", + "print(discriminator.summary())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Losses\n", + "* Matching Aware Losses\n", + "* Output of motion discriminator is a bit high in value (though only used for the generator)\n", + "* Using Scheme 2 of Microsoft" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "tf.Tensor(0.7362369, shape=(), dtype=float32)\ntf.Tensor(0.7079885, shape=(), dtype=float32)\ntf.Tensor(0.68915033, shape=(), dtype=float32)\n" + } + ], + "source": [ + "# Doing with the same ones, but won't be the case\n", + "vid_loss = video_loss(vid_disc_out, vid_disc_out, vid_disc_out)\n", + "print(vid_loss)\n", + "fr_loss = frame_loss(frame_disc_out, frame_disc_out, frame_disc_out)\n", + "print(fr_loss)\n", + "mot_loss = motion_loss(motion_disc_out, motion_disc_out, motion_disc_out)\n", + "print(mot_loss)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "tf.Tensor(0.67633134, shape=(), dtype=float32)\ntf.Tensor(0.20451142, shape=(), dtype=float32)\n" + } + ], + "source": [ + "print(discriminator_loss(vid_loss, fr_loss, mot_loss))\n", + "print(generator_loss(vid_disc_out, frame_disc_out, motion_disc_out))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Train Step" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Takes correct video, wrong video and word embeddings, sentence\n", + "# all of this must be preprocessed (padded and stuff)\n", + "# Video must be explicitly divided into T frames\n", + "def train_step(video_real, video_wrong, w, s):\n", + " num_clips, t, h, w, c = video_real.shape\n", + "\n", + " w = tf.squeeze(w)\n", + " z = tf.random.normal(shape=(1, 100))\n", + " \n", + " with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:\n", + " video_fake = generator(video_real, w, z)\n", + "\n", + " # All frames put together with bs = 1\n", + " video_real = tf.reshape(video_real, (1, num_clips * t, h, w, c))\n", + " video_wrong = tf.reshape(video_wrong, (1, num_clips * t, h, w, c))\n", + " video_fake = tf.reshape(video_fake, (1, num_clips * t, h, w, c))\n", + "\n", + " # Discriminator out\n", + " disc_video_real, disc_frame_real, disc_motion_real = discriminator(video_real, s)\n", + " disc_video_wrong, disc_frame_wrong, disc_motion_wrong = discriminator(video_wrong, s)\n", + " disc_video_fake, disc_frame_fake, disc_motion_fake = discriminator(video_fake, s)\n", + "\n", + " # Losses\n", + " total_video_loss = video_loss(disc_video_real, disc_video_wrong, disc_video_fake)\n", + " total_frame_loss = frame_loss(disc_frame_real, disc_frame_wrong, disc_frame_fake)\n", + " total_motion_loss = motion_loss(disc_motion_real, disc_motion_wrong, disc_motion_fake)\n", + "\n", + " disc_loss = discriminator_loss(total_video_loss, total_frame_loss, total_motion_loss)\n", + " gen_loss = generator_loss(disc_video_fake, disc_frame_fake, disc_motion_fake)\n", + "\n", + " gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)\n", + " gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)\n", + "\n", + " generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))\n", + " discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Optimizers and Checkpoint" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "generator = Generator()\n", + "discriminator = Discriminator()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "learning_rate = 0.0002 # Following microsoft\n", + "generator_optimizer = tf.keras.optimizers.Adam(learning_rate) # rest default\n", + "discriminator_optimizer = tf.keras.optimizers.Adam(learning_rate) # rest default" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "checkpoint_dir = './training_checkpoints'\n", + "checkpoint_prefix = os.path.join(checkpoint_dir, \"ckpt\")\n", + "checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,\n", + " discriminator_optimizer=discriminator_optimizer,\n", + " generator=generator,\n", + " discriminator=discriminator)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "train_step(video_real[:8], video_wrong[:8], word_embeddings, sentence_embeddings)" + ] + } + ] +} \ No newline at end of file diff --git a/Machine_Learning/src/NLP/SignGAN/train.py b/Machine_Learning/src/NLP/SignGAN/train.py new file mode 100644 index 00000000..a8d1b588 --- /dev/null +++ b/Machine_Learning/src/NLP/SignGAN/train.py @@ -0,0 +1,115 @@ +import tensorflow as tf +import numpy as np +import glob +import time +import os + +from bert_utils import Bert +from utils.video import Video +from utils.conv_attention import * +from utils.generator import * +from utils.discriminator import * +from utils.losses import * + +# Optional +config = tf.compat.v1.ConfigProto() +config.gpu_options.allow_growth = True +sess = tf.compat.v1.Session(config=config) + +# Models +bert = Bert() +generator = Generator() +discriminator = Discriminator() + +num_clips = 32 +T = 16 # let +MAX_VIDEO_LENGTH = 512 # 475 is the longest +FRAME_DIM = (64, 64, 3) +VIDEO_DIM = (512, 64, 64, 3) +data_dir = 'phoenix-2014-T.v3/PHOENIX-2014-T-release-v3/PHOENIX-2014-T/features/fullFrame-210x260px/' +video_obj = Video(T, MAX_VIDEO_LENGTH, FRAME_DIM, VIDEO_DIM, data_dir) + +EPOCHS = 10000 + +# Otimizers +learning_rate = 0.000001 +generator_optimizer = tf.keras.optimizers.Adam(learning_rate) +discriminator_optimizer = tf.keras.optimizers.Adam(learning_rate) + +# ckpt +checkpoint_dir = './training_checkpoints' +checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt") +checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer, + discriminator_optimizer=discriminator_optimizer, + generator=generator, + discriminator=discriminator) + +# Takes correct video, wrong video and word embeddings, sentence +# all of this must be preprocessed (padded and stuff) +# Video must be explicitly divided into T frames + +def train_step(video_real, video_wrong, text): + num_clips, t, h, w, c = video_real.shape + word, sentence = bert([text]) + word, sentence = tf.convert_to_tensor(word), tf.convert_to_tensor(sentence) + word = tf.squeeze(word) + z = tf.random.normal(shape=(1, 100)) + + with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape: + video_fake = generator(video_real, word, z) + + # All frames put together with bs = 1 + video_real = tf.reshape(video_real, (1, num_clips * t, h, w, c)) + video_wrong = tf.reshape(video_wrong, (1, num_clips * t, h, w, c)) + video_fake = tf.reshape(video_fake, (1, num_clips * t, h, w, c)) + + # Discriminator out + disc_video_real, disc_frame_real, disc_motion_real = discriminator(video_real, sentence) + disc_video_wrong, disc_frame_wrong, disc_motion_wrong = discriminator(video_wrong, sentence) + disc_video_fake, disc_frame_fake, disc_motion_fake = discriminator(video_fake, sentence) + + # Losses + total_video_loss = video_loss(disc_video_real, disc_video_wrong, disc_video_fake) + total_frame_loss = frame_loss(disc_frame_real, disc_frame_wrong, disc_frame_fake) + total_motion_loss = motion_loss(disc_motion_real, disc_motion_wrong, disc_motion_fake) + + disc_loss = discriminator_loss(total_video_loss, total_frame_loss, total_motion_loss) + gen_loss = generator_loss(disc_video_fake, disc_frame_fake, disc_motion_fake) + + gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables) + gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables) + + generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables)) + discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables)) + + +def main(): + dataset = open('phoenix-2014-T.v3/PHOENIX-2014-T-release-v3/PHOENIX-2014-T/annotations/manual/PHOENIX-2014-T.train.corpus.csv', encoding='utf-8') + dataset = dataset.readlines()[1:] + + text_data = [i.split('|')[-1][:-1] for i in dataset] # [:-1] to remove '\n' at the end + video_data = [i.split('|')[0] for i in dataset] + + for epoch in range(EPOCHS): + start = time.time() + for text, video in zip(text_data, video_data): + video_real = video_obj.get_video('train', video) + video_real = video_obj.preprocess_video(video_real) + video_real = video_obj.divide_sequence(video_real) + + # Random video from dataset as the wrong video + video_wrong = video_obj.get_video('train', video_data[np.random.randint(0, len(video_data))]) + video_wrong = video_obj.preprocess_video(video_wrong) + video_wrong = video_obj.divide_sequence(video_wrong) + + train_step(video_real, video_wrong, text) + + if (epoch + 1) % 10 == 0: + checkpoint.save(file_prefix=checkpoint_prefix) + + print("Epoch {0} :- Time : {1}".format(epoch + 1, time.time() - start)) + + + +if __name__ == '__main__': + main() diff --git a/Machine_Learning/src/NLP/SignGAN/utils/__init__.py b/Machine_Learning/src/NLP/SignGAN/utils/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/Machine_Learning/src/NLP/SignGAN/utils/__init__.py @@ -0,0 +1 @@ + diff --git a/Machine_Learning/src/NLP/SignGAN/utils/conv_attention.py b/Machine_Learning/src/NLP/SignGAN/utils/conv_attention.py new file mode 100644 index 00000000..0ad71cdd --- /dev/null +++ b/Machine_Learning/src/NLP/SignGAN/utils/conv_attention.py @@ -0,0 +1,299 @@ +import tensorflow as tf +import numpy as np + +from .errors import * + +@tf.function +def masking(shape, position): + if position > shape[0]: + raise PaddingError("padding position exceeds limits. position must be < shape[0]") + + if len(shape) != 4: + raise MatrixRankError("Shape must be of Rank 4 i.e, (T, H, W, val), val -> H*W or, T*W or, T*H") + + # mask only for time dimension, size -> (t, h*w, h*w) + ''' + zero = tf.zeros((position, shape[1], shape[2], shape[3]), dtype=tf.float32) + fill = tf.fill((shape[0]-position, shape[1], shape[2], shape[3]), -np.inf) + mask = tf.concat([zero, fill], 0) + ''' + + return tf.concat([tf.zeros((position, shape[1], shape[2], shape[3]), dtype=tf.float32), + tf.fill((shape[0]-position, shape[1], shape[2], shape[3]), -np.inf)], 0) + + #return mask + +@tf.function +def look_ahead_mask(num_mask, shape): + if shape[0] % num_mask != 0: + raise DivisionError("Time dimension must be divisible by number of masks") + if len(shape) != 4: + raise MatrixRankError("Shape must be of Rank 4 i.e, (T, H, W, val), val -> H*W or, T*W or, T*H") + + mask = tf.expand_dims(masking(shape, 1 * shape[0] // num_mask), 0) + for mask_pos in range(1, num_mask): # shape[0] = 500 + mask = tf.concat([mask, tf.expand_dims(masking(shape, (mask_pos+1) * shape[0] // num_mask), 0)], 0) + + return mask # shape -> (50, T, H, W, C) + +@tf.function +def dot_product_attention(q, k, v, mask): + # Number of columns of q must be equal to number of columns of k + # i.e the last dimension must be same + + # This if-else block is for self attention and, word embedding attention + if len(k.shape) == 2: + k_T = tf.transpose(k) + else: + k_T = tf.transpose(k, perm=[0, 1, 3, 2]) # which axis will be at which place specified + + qv_correlations = tf.matmul(q, k_T) + + if mask is not None: + num_clips, qv_1, qv_2, qv_3 = qv_correlations.shape + ''' + try: + mask = tf.reshape(mask, (num_clips, qv_1, qv_2, qv_3)) + except: + raise ValueError("Reshaped Mask does not match 'matmul(Q, K.T)' in shape") + ''' + qv_correlations += mask + #print(qv_correlations) + + return tf.matmul(tf.nn.softmax(qv_correlations, axis=0), v) + + +## Word Frame Attention +# Last dimension of both masked_attention_output and semantic_word_matrix must be same +# 2nd last dimenstion i.e, 1st dimenstion of semantic_word_matrix should equal to H*W of masked-separable-self-attention output because the same mask_t will be used +# that is, we bring both to a common semantic space using conv for frames and dense for embeddings +class Attention(tf.keras.layers.Layer): + # tf.keras.layers.Dense changes the last dimenstion of n-d matrix + def __init__(self, channel_dimension=64): + # downsample to H*W = 64 i.e, H = 8, W = 8, before the attention block. + # common semantic space = 64, i.e, channel_dimension = 64 + # result after tf.Dense Layer : 1. semantic_word_matrix -> (64, 64) 2. Downsampled masked/repeated video -> (50, 500, 8, 8, 64) + + super(Attention, self).__init__() + + self.channel_dimension = channel_dimension + + # For separable self attention + self.sep_wq1 = tf.keras.layers.Dense(self.channel_dimension, use_bias=False, name='sep_wq1') + self.sep_wk1 = tf.keras.layers.Dense(self.channel_dimension, use_bias=False, name='sep_wk1') + self.sep_wv1 = tf.keras.layers.Dense(self.channel_dimension, use_bias=False, name='sep_wv1') + + self.sep_wq2 = tf.keras.layers.Dense(self.channel_dimension, use_bias=False, name='sep_wq2') + self.sep_wk2 = tf.keras.layers.Dense(self.channel_dimension, use_bias=False, name='sep_wk2') + self.sep_wv2 = tf.keras.layers.Dense(self.channel_dimension, use_bias=False, name='sep_wv2') + + self.sep_wq3 = tf.keras.layers.Dense(self.channel_dimension, use_bias=False, name='sep_wq3') + self.sep_wk3 = tf.keras.layers.Dense(self.channel_dimension, use_bias=False, name='sep_wk3') + self.sep_wv3 = tf.keras.layers.Dense(self.channel_dimension, use_bias=False, name='sep_wv3') + + # For word-frame attention + self.word_wq = tf.keras.layers.Dense(self.channel_dimension, use_bias=False, name='word_wq') + self.word_wk = tf.keras.layers.Dense(self.channel_dimension, use_bias=False, name='word_wk') + self.word_wv = tf.keras.layers.Dense(self.channel_dimension, use_bias=False, name='word_wv') + + # batchnorm causes nan because of the padding and masking, so layernorm + self.layernorm1 = tf.keras.layers.LayerNormalization() + self.layernorm2 = tf.keras.layers.LayerNormalization() + + ''' + def separable_attention(self, x, mask_t, mask_h, mask_w): + try: + num_clips, t, h, w, c = x.shape + except: + raise MatrixRankError("x must be of rank 5 i.e, (num_clips, t, h, w, c)") + + if len(mask_t.shape) != 5 or len(mask_h.shape) != 5 or len(mask_h.shape) != 5: + raise MatrixRankError("masks must be of rank 5 i.e, (num_clips, t, h, w, val), where val -> H*W or, T*W or, T*H") + + x = tf.reshape(x, (num_clips, t, h*w, c)) + xq, xk, xv = self.sep_wq1(x), self.sep_wk1(x), self.sep_wv1(x) + x = dot_product_attention(xq, xk ,xv, tf.reshape(mask_t, (num_clips, t, h*w, h*w))) # self Attention + #print(x.shape) + + x = tf.reshape(x, (num_clips, h, t*w, c)) + xq, xk, xv = self.sep_wq2(x), self.sep_wk2(x), self.sep_wv2(x) + x = dot_product_attention(xq, xk ,xv, tf.reshape(mask_h, (num_clips, h, t*w, t*w))) # self Attention + + x = tf.reshape(x, (num_clips, w, t*h, c)) + xq, xk, xv = self.sep_wq3(x), self.sep_wk3(x), self.sep_wv3(x) + x = dot_product_attention(xq, xk ,xv, tf.reshape(mask_w, (num_clips, w, t*h, t*h))) # self Attention + + return tf.reshape(x, (num_clips, t, h, w, c)) + ''' + ''' + def separable_attention(self, x, mask_t, mask_h, mask_w, xq_t, xk_t, xv_t, xq_h, xk_h, xv_h, xq_w, xk_w, xv_w): + try: + num_clips, t, h, w, c = x.shape + except: + raise MatrixRankError("x must be of rank 5 i.e, (num_clips, t, h, w, c)") + + if len(mask_t.shape) != 5 or len(mask_h.shape) != 5 or len(mask_h.shape) != 5: + raise MatrixRankError("masks must be of rank 5 i.e, (num_clips, t, h, w, val), where val -> H*W or, T*W or, T*H") + + x = dot_product_attention(xq_t, xk_t ,xv_t, tf.reshape(mask_t, (num_clips, t, h*w, h*w))) # self Attention + + x = dot_product_attention(xq_h, xk_h ,xv_h, tf.reshape(mask_h, (num_clips, h, t*w, t*w))) # self Attention + + x = dot_product_attention(xq_w, xk_w ,xv_w, tf.reshape(mask_w, (num_clips, w, t*h, t*h))) # self Attention + + return tf.reshape(x, (num_clips, t, h, w, c)) + ''' + ''' + def word_frame_attention(self, frame_features, bert_embeddings, mask_t): # only across time + try: + num_clips, t, h, w, c = frame_features.shape + except: + raise MatrixRankError("frame_features must be of rank 5 i.e, (num_clips, t, h, w, c)") + + if len(mask_t.shape) != 5: + raise MatrixRankError("mask_t must be of rank 5 i.e, (num_clips, t, h, w, val), where val -> H*W or, T*W or, T*H") + + frame_features = tf.reshape(frame_features, (num_clips, t, h*w, c)) + + frame_features, bert_embeddings, bert_embeddings = self.word_wq(frame_features), self.word_wk(bert_embeddings), self.word_wv(bert_embeddings) + + frame_features = dot_product_attention(frame_features, bert_embeddings, bert_embeddings, tf.reshape(mask_t, (num_clips, t, h*w, h*w))) + + return tf.reshape(frame_features, (num_clips, t, h, w, c)) + ''' + + def call(self, frame_features, bert_embeddings, mask_t, mask_h, mask_w): + # make sure that q, k, v have gone through tf.expand_dims, because of the batch simension thing + # for 2nd point above. if q = v that means self attention, hence, q != v + assert (frame_features.shape[-2] * frame_features.shape[-3]) == bert_embeddings.shape[-2] # for 2nd point above. if q = v that means self attention, hence, q != v + # 8*8 == 64 + + #attn_out = self.separable_attention(frame_features, mask_t, mask_h, mask_w) + # Separable self-attn + try: + num_clips, t, h, w, c = frame_features.shape + except: + raise MatrixRankError("frame_features must be of rank 5 i.e, (num_clips, t, h, w, c)") + + x = tf.reshape(frame_features, (num_clips, t, h*w, c)) + xq, xk, xv = self.sep_wq1(x), self.sep_wk1(x), self.sep_wv1(x) + x = dot_product_attention(xq, xk ,xv, tf.reshape(mask_t, (num_clips, t, h*w, h*w))) + + x = tf.reshape(x, (num_clips, h, t*w, c)) + xq, xk, xv = self.sep_wq2(x), self.sep_wk2(x), self.sep_wv2(x) + x = dot_product_attention(xq, xk ,xv, tf.reshape(mask_h, (num_clips, h, t*w, t*w))) + + x = tf.reshape(x, (num_clips, w, t*h, c)) + xq, xk, xv = self.sep_wq3(x), self.sep_wk3(x), self.sep_wv3(x) + x = dot_product_attention(xq, xk ,xv, tf.reshape(mask_w, (num_clips, w, t*h, t*h))) # self Attention + + x = tf.reshape(x, (num_clips, t, h, w, c)) + frame_features = self.layernorm1(frame_features + x) + + #attn_out = self.word_frame_attention(frame_features, bert_embeddings, mask_t) + # Word-frame attention + x = tf.reshape(frame_features, (num_clips, t, h*w, c)) + x, bert_k, bert_v = self.word_wq(x), self.word_wk(bert_embeddings), self.word_wv(bert_embeddings) + x = dot_product_attention(x, bert_k, bert_v, tf.reshape(mask_t, (num_clips, t, h*w, h*w))) + x = tf.reshape(x, (num_clips, t, h, w, c)) + + frame_features = self.layernorm2(frame_features + x) + + return frame_features + + + +# use tf.keras.Model to make it a different model +# Can see summary only after passing an input. Simply calling the model won't work +# Gotta pass a sample input to get going +class ConvAttn(tf.keras.layers.Layer): + def __init__(self, num_attention_blocks=4, out_channels=64): + super(ConvAttn, self).__init__() + + self.num_attention_blocks = num_attention_blocks + self.out_channels = out_channels + + # (16, 64, 64, 8) + self.conv1 = tf.keras.layers.Conv3D(filters=8, + kernel_size=(3, 3, 3), + strides=(1, 1, 1), + padding='same', + use_bias=False) + + # (8, 32, 32, 16) + self.conv2 = tf.keras.layers.Conv3D(filters=16, + kernel_size=(3, 3, 3), + strides=(2, 2, 2), + padding='same', + use_bias=False) + + # (4, 16, 16, 32) + self.conv3 = tf.keras.layers.Conv3D(filters=32, + kernel_size=(3, 3, 3), + strides=(2, 2, 2), + padding='same', + use_bias=False) + + # (2, 8, 8, out_channels) + self.conv4 = tf.keras.layers.Conv3D(filters=self.out_channels, + kernel_size=(3, 3, 3), + strides=(2, 2, 2), + padding='same', + use_bias=False) + + # LayerNorm and ReLU + self.relu = [] + self.layernorm = [] + for i in range(4): + self.relu.append(tf.keras.layers.Activation('relu')) + self.layernorm.append(tf.keras.layers.LayerNormalization()) + + self.attention = [] + for i in range(self.num_attention_blocks): + self.attention.append(Attention(self.out_channels)) + + def call(self, x, bert_embeddings): + if len(x.shape) != 5: + raise MatrixRankError("x was supposed to be a Rank 5 tensor, i.e, (num_clips, T, H, W, C)") + + assert self.out_channels == bert_embeddings.shape[-2] + + x = self.conv1(x) + x = self.layernorm[0](x) + x = self.relu[0](x) + #print("Conv1 Out Shape : ", x.shape) + + x = self.conv2(x) + x = self.layernorm[1](x) + x = self.relu[1](x) + #print("Conv2 Out Shape : ", x.shape) + + x = self.conv3(x) + x = self.layernorm[2](x) + x = self.relu[2](x) + #print("Conv3 Out Shape : ", x.shape) + + x = self.conv4(x) + x = self.layernorm[3](x) + x = self.relu[3](x) + #print("Conv4 Out Shape : ", x.shape) + + #if np.isnan(np.sum(x)) == np.nan: + # print('-----CONV-----') + + num_clips, t, h, w, c = x.shape + x = tf.reshape(x, (num_clips * t, h, w, c)) + t = num_clips * t + + x = tf.repeat(x, repeats=num_clips, axis=0) + x = tf.reshape(x, (num_clips, t, h, w, c)) + + mask_t = look_ahead_mask(num_clips, (t, h, w, h*w)) + mask_h = look_ahead_mask(num_clips, (t, h, w, t*w)) + mask_w = look_ahead_mask(num_clips, (t, h, w, t*h)) + + for i in range(self.num_attention_blocks): + x = self.attention[i](x, bert_embeddings, mask_t, mask_h, mask_w) + + + return x diff --git a/Machine_Learning/src/NLP/SignGAN/utils/discriminator.py b/Machine_Learning/src/NLP/SignGAN/utils/discriminator.py new file mode 100644 index 00000000..ae4e26c3 --- /dev/null +++ b/Machine_Learning/src/NLP/SignGAN/utils/discriminator.py @@ -0,0 +1,399 @@ +import tensorflow as tf +from .errors import MatrixRankError + +# Using scheme 2 of Microsoft + +# 0 < disc_vals < 1 +class VideoDiscriminator(tf.keras.layers.Layer): + def __init__(self): + super(VideoDiscriminator, self).__init__() + + self.dense1 = tf.keras.layers.Dense(4 * 4 * 256) + + # Input -> (512, 64, 64, 3) + # (512, 64, 64, 8) + self.conv0 = tf.keras.layers.Conv3D(filters=8, + kernel_size=(3, 3, 3), + strides=(1, 1, 1), + padding='same', + use_bias=False) + + # (256, 64, 64, 16) + self.conv1 = tf.keras.layers.Conv3D(filters=16, + kernel_size=(3, 3, 3), + strides=(2, 1, 1), + padding='same', + use_bias=False) + + # (128, 64, 64, 32) + self.conv2 = tf.keras.layers.Conv3D(filters=32, + kernel_size=(3, 3, 3), + strides=(2, 1, 1), + padding='same', + use_bias=False) + + # (64, 64, 64, 64) + self.conv3 = tf.keras.layers.Conv3D(filters=64, + kernel_size=(3, 3, 3), + strides=(2, 1, 1), + padding='same', + use_bias=False) + + # (32, 64, 64, 128) + self.conv4 = tf.keras.layers.Conv3D(filters=128, + kernel_size=(3, 3, 3), + strides=(2, 1, 1), + padding='same', + use_bias=False) + + # (16, 64, 64, 256) + self.conv5 = tf.keras.layers.Conv3D(filters=256, + kernel_size=(3, 3, 3), + strides=(2, 1, 1), + padding='same', + use_bias=False) + + # (8, 32, 32, 512) + self.conv6 = tf.keras.layers.Conv3D(filters=512, + kernel_size=(3, 3, 3), + strides=(2, 2, 2), + padding='same', + use_bias=False) + + # (4, 16, 16, 1024) + self.conv7 = tf.keras.layers.Conv3D(filters=1024, + kernel_size=(3, 3, 3), + strides=(2, 2, 2), + padding='same', + use_bias=False) + + # (2, 8, 8, 1024) + self.conv8 = tf.keras.layers.Conv3D(filters=1024, + kernel_size=(3, 3, 3), + strides=(2, 2, 2), + padding='same', + use_bias=False) + + # (1, 4, 4, 1024) + self.conv9 = tf.keras.layers.Conv3D(filters=1024, + kernel_size=(3, 3, 3), + strides=(2, 2, 2), + padding='same', + use_bias=False) + + # After concat + self.conv10 = tf.keras.layers.Conv3D(filters=1024, + kernel_size=(3, 3, 3), + strides=(1, 1, 1), + padding='same', + use_bias=False) + + + self.relu = [] + self.batchnorm = [] + for i in range(11): + self.batchnorm.append(tf.keras.layers.BatchNormalization()) + self.relu.append(tf.keras.layers.Activation('relu')) + + self.flatten = tf.keras.layers.Flatten() + + self.dense2 = tf.keras.layers.Dense(128) + + self.dense3 = tf.keras.layers.Dense(1)#, activation='sigmoid') # output -> [0, 1] + + + def call(self, v, s): # video and sentence embedding + # s -> sentence vector + if len(v.shape) != 5: + raise MatrixRankError("v must be a Rank 5 Tensor i.e, (batch_size, num_clips * T, H, W, C)") + if len(s.shape) != 2: + raise MatrixRankError("s must be a Rank 2 Tensor i.e, (batch_size, dim)") + + batch_size, t, h, w, c = v.shape + + # Sentence semantic space + s = self.dense1(s) + s = tf.reshape(s, (batch_size, 1, 4, 4, 256)) + + # downscale v + v = self.conv0(v) + v = self.batchnorm[0](v) + v = self.relu[0](v) + + v = self.conv1(v) + v = self.batchnorm[1](v) + v = self.relu[1](v) + + v = self.conv2(v) + v = self.batchnorm[2](v) + v = self.relu[2](v) + + v = self.conv3(v) + v = self.batchnorm[3](v) + v = self.relu[3](v) + + v = self.conv4(v) + v = self.batchnorm[4](v) + v = self.relu[4](v) + + v = self.conv5(v) + v = self.batchnorm[5](v) + v = self.relu[5](v) + + v = self.conv6(v) + v = self.batchnorm[6](v) + v = self.relu[6](v) + + v = self.conv7(v) + v = self.batchnorm[7](v) + v = self.relu[7](v) + + v = self.conv8(v) + v = self.batchnorm[8](v) + v = self.relu[8](v) + + v = self.conv9(v) + v = self.batchnorm[9](v) + v = self.relu[9](v) + + # concat with s + v = tf.concat([v, s], axis=-1) + + v = self.conv10(v) + v = self.batchnorm[10](v) + v = self.relu[10](v) + + v = self.flatten(v) + v = self.dense2(v) + v = self.dense3(v) + + return v + + +# 0 < disc_vals < 1 +class FrameDiscriminator(tf.keras.layers.Layer): + def __init__(self): + super(FrameDiscriminator, self).__init__() + + # 3D conv used instead of 2D, to consider batch size i.e, more than 1 video at a time + # Time dimension technically not considered because stride is always = 1 and kernel size along time is always 1, so temporal relation is not taken into account + + self.dense1 = tf.keras.layers.Dense(4 * 4 * 256) + + # (64, 64, 8) + self.conv0 = tf.keras.layers.Conv3D(filters=8, + kernel_size=(1, 3, 3), + strides=(1, 1, 1), + padding='same', + use_bias=False) + + # (32, 32, 16) + self.conv1 = tf.keras.layers.Conv3D(filters=16, + kernel_size=(1, 3, 3), + strides=(1, 2, 2), + padding='same', + use_bias=False) + + # (16, 16, 32) + self.conv2 = tf.keras.layers.Conv3D(filters=32, + kernel_size=(1, 3, 3), + strides=(1, 2, 2), + padding='same', + use_bias=False) + + # (8, 8, 64) + self.conv3 = tf.keras.layers.Conv3D(filters=64, + kernel_size=(1, 3, 3), + strides=(1, 2, 2), + padding='same', + use_bias=False) + + # (4, 4, 128) + self.conv4 = tf.keras.layers.Conv3D(filters=128, + kernel_size=(1, 3, 3), + strides=(1, 2, 2), + padding='same', + use_bias=False) + + # (4, 4, 256) + self.conv5 = tf.keras.layers.Conv3D(filters=256, + kernel_size=(1, 3, 3), + strides=(1, 1, 1), + padding='same', + use_bias=False) + ''' + # (32, 32, 512) + self.conv6 = tf.keras.layers.Conv3D(filters=512, + kernel_size=(1, 3, 3), + strides=(1, 2, 2), + padding='same', + use_bias=False) + + # (16, 16, 1024) + self.conv7 = tf.keras.layers.Conv3D(filters=1024, + kernel_size=(1, 3, 3), + strides=(1, 2, 2), + padding='same', + use_bias=False) + + # (8, 8, 1024) + self.conv8 = tf.keras.layers.Conv3D(filters=1024, + kernel_size=(1, 3, 3), + strides=(1, 2, 2), + padding='same', + use_bias=False) + + # (4, 4, 1024) + self.conv9 = tf.keras.layers.Conv3D(filters=1024, + kernel_size=(1, 3, 3), + strides=(1, 2, 2), + padding='same', + use_bias=False) + ''' + + # After concat (FRAME) + self.conv_frame = tf.keras.layers.Conv3D(filters=512, + kernel_size=(1, 3, 3), + strides=(1, 1, 1), + padding='same', + use_bias=False) + + # After concat (MOTION) + self.conv_motion = tf.keras.layers.Conv3D(filters=512, + kernel_size=(1, 3, 3), + strides=(1, 1, 1), + padding='same', + use_bias=False) + + + self.relu = [] + self.batchnorm = [] + for i in range(8): #12 + self.batchnorm.append(tf.keras.layers.BatchNormalization()) + self.relu.append(tf.keras.layers.Activation('relu')) + + self.frame_flatten = tf.keras.layers.Flatten() + + #self.dense_frame_1 = tf.keras.layers.Dense(128) + + self.dense_frame_2 = tf.keras.layers.Dense(1)#, activation='sigmoid') # output -> [0, 1] + + self.motion_flatten = tf.keras.layers.Flatten() + + #self.dense_motion_1 = tf.keras.layers.Dense(128) + + self.dense_motion_2 = tf.keras.layers.Dense(1)#, activation='sigmoid') # output -> [0, 1] + + + def call(self, v, s): + if len(v.shape) != 5: + raise MatrixRankError("v must be a Rank 5 Tensor i.e, (batch_size, num_clips * T, H, W, C)") + if len(s.shape) != 2: + raise MatrixRankError("s must be a Rank 2 Tensor i.e, (batch_size, dim)") + + batch_size, t, h, w, c = v.shape + + # downscale v + v = self.conv0(v) + v = self.batchnorm[0](v) + v = self.relu[0](v) + + v = self.conv1(v) + v = self.batchnorm[1](v) + v = self.relu[1](v) + + v = self.conv2(v) + v = self.batchnorm[2](v) + v = self.relu[2](v) + + v = self.conv3(v) + v = self.batchnorm[3](v) + v = self.relu[3](v) + + v = self.conv4(v) + v = self.batchnorm[4](v) + v = self.relu[4](v) + + v = self.conv5(v) + v = self.batchnorm[5](v) + v = self.relu[5](v) + ''' + v = self.conv6(v) + v = self.leakyrelu[6](v) + print('7') + + v = self.conv7(v) + v = self.leakyrelu[7](v) + print('8') + + v = self.conv8(v) + v = self.leakyrelu[8](v) + print('9') + + v = self.conv9(v) + v = self.leakyrelu[9](v) # common output + print('10') + ''' + ## Take this output and work for temporal coherence + + ## Frame + # Sentence semantic space + s = self.dense1(s) + s = tf.reshape(s, (batch_size, 1, 4, 4, 256)) + frame_s = tf.repeat(s, repeats=t, axis=1) # time axis next to the batch because 't' frames + # Concat and out + frame_out = tf.concat([v, frame_s], axis=-1) + + frame_out = self.conv_frame(frame_out) + frame_out = self.batchnorm[6](frame_out) + frame_out = self.relu[6](frame_out) + # output 1 for each frame so reshape to (batch_size * t, h, w, c) + frame_out = tf.reshape(frame_out, (batch_size * frame_out.shape[1], frame_out.shape[2], frame_out.shape[3], frame_out.shape[4])) + + frame_out = self.frame_flatten(frame_out) # (bs*t, .., .., ..) + #frame_out = self.dense_frame_1(frame_out) # (bs*t, ..) + frame_out = self.dense_frame_2(frame_out) # (bs*t,) + + # reshaped to (batch_size, t) + frame_out = tf.reshape(frame_out, (batch_size, frame_out.shape[0] // batch_size)) # out + # for each frame of each batch, we get an output + + ## Motion + # Sentence Semantic space + motion_s = tf.repeat(s, repeats=t-1, axis=1) + motion_out = tf.subtract(v[:, 1:], v[:, :-1]) + + # Concat and out + motion_out = tf.concat([motion_out, motion_s], axis=-1) + + motion_out = self.conv_motion(motion_out) + motion_out = self.batchnorm[7](motion_out) + motion_out = self.relu[7](motion_out) + + # Scheme 2 (no norm) + motion_out = tf.reshape(motion_out, (batch_size * motion_out.shape[1], motion_out.shape[2], motion_out.shape[3], motion_out.shape[4])) + + motion_out = self.motion_flatten(motion_out) # (bs*(t-1), .., .., ..) + #motion_out = self.dense_motion_1(motion_out) # (bs*(t-1), ..) + motion_out = self.dense_motion_2(motion_out) # (bs*(t-1),) + + # (bs, t-1) + motion_out = tf.reshape(motion_out, (batch_size, motion_out.shape[0] // batch_size)) # out + + return frame_out, motion_out + + +# 0 < disc_vals < 1 +class Discriminator(tf.keras.Model): + def __init__(self): + super(Discriminator, self).__init__() + + self.video_discriminator = VideoDiscriminator() + self.frame_discriminator = FrameDiscriminator() + + def call(self, v, s): + video_disc_out = self.video_discriminator(v, s) + frame_disc_out, motion_disc_out = self.frame_discriminator(v, s) + + return video_disc_out, frame_disc_out, motion_disc_out + diff --git a/Machine_Learning/src/NLP/SignGAN/utils/errors.py b/Machine_Learning/src/NLP/SignGAN/utils/errors.py new file mode 100644 index 00000000..a68004da --- /dev/null +++ b/Machine_Learning/src/NLP/SignGAN/utils/errors.py @@ -0,0 +1,18 @@ +class Error(Exception): + # base class + pass + +class PaddingError(Error): + def __init__(self, msg): + self.msg = msg + super(PaddingError, self).__init__(self.msg) + +class MatrixRankError(Error): + def __init__(self, msg): + self.msg = msg + super(MatrixRankError, self).__init__(self.msg) + +class DivisionError(Error): + def __init__(self, msg): + self.msg = msg + super(DivisionError, self).__init__(self.msg) diff --git a/Machine_Learning/src/NLP/SignGAN/utils/generator.py b/Machine_Learning/src/NLP/SignGAN/utils/generator.py new file mode 100644 index 00000000..5a0df6b8 --- /dev/null +++ b/Machine_Learning/src/NLP/SignGAN/utils/generator.py @@ -0,0 +1,184 @@ +import numpy as np +import tensorflow as tf +from .conv_attention import ConvAttn + +# '.' added to look outside the directory (else import error) +# We will feed 'z' from outside. If it is inside it'll stay constant and won't be random +class CDCGAN(tf.keras.layers.Layer): + def __init__(self): + super(CDCGAN, self).__init__() + + self.dense = tf.keras.layers.Dense(4 * 4 * 4 * 1024) + #self.layernorm = tf.keras.layers.LayerNormalization() + #self.leakyrelu = tf.keras.layers.LeakyReLU() + + self.reshape = tf.keras.layers.Reshape((4, 4, 4, 1024)) + + # (4, 4, 4, 512) + self.deconv1 = tf.keras.layers.Conv3DTranspose(filters=512, + kernel_size=(3, 3, 3), + strides=(1, 1, 1), + padding='same', + use_bias=False) + + self.layernorm1 = tf.keras.layers.LayerNormalization() + #self.leakyrelu1 = tf.keras.layers.LeakyReLU() + self.relu1 = tf.keras.layers.Activation('relu') + + # (8, 8, 8, 256) + self.deconv2 = tf.keras.layers.Conv3DTranspose(filters=256, + kernel_size=(3, 3, 3), + strides=(2, 2, 2), + padding='same', + use_bias=False) + + self.layernorm2 = tf.keras.layers.LayerNormalization() + #self.leakyrelu2 = tf.keras.layers.LeakyReLU() + self.relu2 = tf.keras.layers.Activation('relu') + + # attn -> (32, 8, 8, 128) + self.conv1 = tf.keras.layers.Conv3D(filters=128, + kernel_size=(3, 3, 3), + strides=(2, 1, 1), + padding='same', + use_bias=False) + + self.attnlayernorm1 = tf.keras.layers.LayerNormalization() + #self.attnleakyrelu1 = tf.keras.layers.LeakyReLU() + self.attnrelu1 = tf.keras.layers.Activation('relu') + + # attn -> (16, 8, 8, 256) + self.conv2 = tf.keras.layers.Conv3D(filters=256, + kernel_size=(3, 3, 3), + strides=(2, 1, 1), + padding='same', + use_bias=False) + + self.attnlayernorm2 = tf.keras.layers.LayerNormalization() + #self.attnleakyrelu2 = tf.keras.layers.LeakyReLU() + self.attnrelu2 = tf.keras.layers.Activation('relu') + + # attn -> (8, 8, 8, 512) + self.conv3 = tf.keras.layers.Conv3D(filters=512, + kernel_size=(3, 3, 3), + strides=(2, 1, 1), + padding='same', + use_bias=False) + + self.attnlayernorm3 = tf.keras.layers.LayerNormalization() + #self.attnleakyrelu3 = tf.keras.layers.LeakyReLU() + self.attnrelu3 = tf.keras.layers.Activation('relu') + + + # (8, 16, 16, 128) + self.deconv3 = tf.keras.layers.Conv3DTranspose(filters=128, + kernel_size=(3, 3, 3), + strides=(1, 2, 2), + padding='same', + use_bias=False) + + self.layernorm3 = tf.keras.layers.LayerNormalization() + #self.leakyrelu3 = tf.keras.layers.LeakyReLU() + self.relu3 = tf.keras.layers.Activation('relu') + + # (16, 32, 32, 64) + self.deconv4 = tf.keras.layers.Conv3DTranspose(filters=64, + kernel_size=(3, 3, 3), + strides=(2, 2, 2), + padding='same', + use_bias=False) + + self.layernorm4 = tf.keras.layers.LayerNormalization() + #self.leakyrelu4 = tf.keras.layers.LeakyReLU() + self.relu4 = tf.keras.layers.Activation('relu') + + # (16, 64, 64, 32) + self.deconv5 = tf.keras.layers.Conv3DTranspose(filters=32, + kernel_size=(3, 3, 3), + strides=(1, 2, 2), + padding='same', + use_bias=False) + + self.layernorm5 = tf.keras.layers.LayerNormalization() + #self.leakyrelu5 = tf.keras.layers.LeakyReLU() + self.relu5 = tf.keras.layers.Activation('relu') + + # (16, 64, 64, 3) + self.deconv6 = tf.keras.layers.Conv3DTranspose(filters=3, + kernel_size=(3, 3, 3), + strides=(1, 1, 1), + padding='same', + use_bias=False, + activation='relu') # values > 0 + + + def call(self, z, conv_attn_output): + assert len(z.shape) == 2 # (1, 100) + + # same z repeated for all the clips + z = tf.repeat(z, repeats=conv_attn_output.shape[0], axis=0) + + z = self.dense(z) + #z = self.layernorm(z) + #z = self.leakyrelu(z) + + z = self.reshape(z) + + # Upscaling z + z = self.deconv1(z) + z = self.layernorm1(z) + z = self.relu1(z) + + z = self.deconv2(z) + z = self.layernorm2(z) + z = self.relu2(z) # (32, 8, 8, 8, 256) + + # Attn out -> (32, 64, 8, 8, 64) + # Downscale Attention output -> (32, 8, 8, 8, 64) + conv_attn_output = self.conv1(conv_attn_output) + conv_attn_output = self.attnlayernorm1(conv_attn_output) + conv_attn_output = self.attnlayernorm1(conv_attn_output) + + conv_attn_output = self.conv2(conv_attn_output) + conv_attn_output = self.attnlayernorm2(conv_attn_output) + conv_attn_output = self.attnlayernorm2(conv_attn_output) + + conv_attn_output = self.conv3(conv_attn_output) + conv_attn_output = self.attnlayernorm3(conv_attn_output) + conv_attn_output = self.attnlayernorm3(conv_attn_output) + + # Concat condition (downscaled attention output) across channel dimension + z = tf.concat([z, conv_attn_output], axis=-1) + + # upconv to produce 40 clips with 40 as bs, thus generating the whole video + z = self.deconv3(z) + z = self.layernorm3(z) + z = self.relu3(z) + + z = self.deconv4(z) + z = self.layernorm4(z) + z = self.relu4(z) + + z = self.deconv5(z) + z = self.layernorm5(z) + z = self.relu5(z) + + z = self.deconv6(z) + + return z + + +class Generator(tf.keras.Model): + def __init__(self, num_attention_blocks=4, out_channels=64): + super(Generator, self).__init__() + + self.attention = ConvAttn(num_attention_blocks, out_channels) + self.cdcgan = CDCGAN() + + def call(self, x, bert_embeddings, z): + x = self.attention(x, bert_embeddings) + x = self.cdcgan(z, x) + #if np.isnan(np.sum(x)) == np.nan: + # print('-----CDCGAN-----') + + return x diff --git a/Machine_Learning/src/NLP/SignGAN/utils/losses.py b/Machine_Learning/src/NLP/SignGAN/utils/losses.py new file mode 100644 index 00000000..a808347e --- /dev/null +++ b/Machine_Learning/src/NLP/SignGAN/utils/losses.py @@ -0,0 +1,33 @@ +import tensorflow as tf + +# disc_video_real -> Disc out for Real video matching with the sentence [single value] +# disc_video_wrong -> Disc out wrong pair [single value] +# disc_video_fake -> Disc out for Generated video [single value] +# Mean of whole batch taken + +# Use BCE loss. Refer tf2-GANs +bce = tf.keras.losses.BinaryCrossentropy(from_logits=True) + +@tf.function +def video_loss(disc_video_real, disc_video_wrong, disc_video_fake): + return (bce(tf.ones_like(disc_video_real), disc_video_real) + bce(tf.zeros_like(disc_video_wrong), disc_video_wrong) + bce(tf.zeros_like(disc_video_fake), disc_video_fake)) / 3. + +# disc_frame_real -> Disc out for Real video frames matching with the sentence [Vector of values for each frame] +# disc_frame_wrong -> Disc out wrong pair frames [Vector of values for each frame] +# disc_frame_fake -> Disc out for Generated frames [Vector of values for each frame] +@tf.function +def frame_loss(disc_frame_real, disc_frame_wrong, disc_frame_fake): + return (bce(tf.ones_like(disc_frame_real), disc_frame_real) + bce(tf.zeros_like(disc_frame_wrong), disc_frame_wrong) + bce(tf.zeros_like(disc_frame_fake), disc_frame_fake)) / 3. + +@tf.function +def motion_loss(disc_motion_real, disc_motion_wrong, disc_motion_fake): # only for generator + return (bce(tf.ones_like(disc_motion_real), disc_motion_real) + bce(tf.zeros_like(disc_motion_wrong), disc_motion_wrong) + bce(tf.zeros_like(disc_motion_fake), disc_motion_fake)) / 3. + +@tf.function +def discriminator_loss(video_loss, frame_loss, motion_loss): + return (video_loss + frame_loss + motion_loss) / 3. + +# reduce_mean for batch +@tf.function +def generator_loss(disc_video_fake, disc_frame_fake, disc_motion_fake): + return (bce(tf.ones_like(disc_video_fake), disc_video_fake) + bce(tf.ones_like(disc_frame_fake), disc_frame_fake) + bce(tf.ones_like(disc_motion_fake), disc_motion_fake)) / 3. diff --git a/Machine_Learning/src/NLP/SignGAN/utils/video.py b/Machine_Learning/src/NLP/SignGAN/utils/video.py new file mode 100644 index 00000000..894e3f66 --- /dev/null +++ b/Machine_Learning/src/NLP/SignGAN/utils/video.py @@ -0,0 +1,94 @@ +import tensorflow as tf +import numpy as np +import glob +import cv2 + +class Video(object): + def __init__(self, T = 16, MAX_VIDEO_LENGTH = 640, FRAME_DIM = (64, 64, 3), VIDEO_DIM = (640, 64, 64, 3), + data_dir = 'phoenix-2014-T.v3/PHOENIX-2014-T-release-v3/PHOENIX-2014-T/features/fullFrame-210x260px/'): + self.T = T + self.MAX_VIDEO_LENGTH = MAX_VIDEO_LENGTH + self.FRAME_DIM = FRAME_DIM + self.VIDEO_DIM = VIDEO_DIM + self.data_dir = data_dir + + def get_video(self, set_name, name, resize=True, scale_down=True): + vid = [] + for frame in glob.glob(self.data_dir + set_name + '/' + name + '/*.png'): + vid_frame = cv2.imread(frame) + if resize: + vid_frame = cv2.resize(vid_frame, (self.FRAME_DIM[0], self.FRAME_DIM[1])) + if scale_down: + vid_frame = vid_frame / 255. # 0 < pixel values < 1, padding = 0 + vid.append(vid_frame) + return tf.convert_to_tensor(np.array(vid, np.float32)) + + def padding(self, video): + pad_length = self.MAX_VIDEO_LENGTH - video.shape[0] + pad = tf.zeros((pad_length, self.FRAME_DIM[0], self.FRAME_DIM[1], self.FRAME_DIM[2]), dtype=tf.float32) + return tf.concat([video, pad], 0) + + def preprocess_video(self, video): + start_token = tf.fill((self.T, self.FRAME_DIM[0], self.FRAME_DIM[1], self.FRAME_DIM[2]), 256./255) # start token -> 4d array of 0.9 + #print(start_token.shape) + end_token = tf.fill((self.T, self.FRAME_DIM[0], self.FRAME_DIM[1], self.FRAME_DIM[2]), 257./255) # end token -> 4d array of 2.1 + #print(end_token.shape) + extra_token = tf.fill((self.T - video.shape[0] % self.T, self.FRAME_DIM[0], self.FRAME_DIM[1], self.FRAME_DIM[2]), 1.) # padding starts only from the nearest 10th, so until the nearest 10, array of 1's + #print(extra_token.shape) + #video = tf.concat([tf.concat([start_token, video], 0), extra_token], 0) + #video = tf.concat([tf.cast(start_token, tf.float32), tf.cast(video, tf.float32), tf.cast(extra_token, tf.float32), tf.cast(end_token, tf.float32)], 0) + video = tf.concat([start_token, video, extra_token, end_token], 0) + #print(video) + + #video = np.append(video, end_token, axis=0) + video = self.padding(video) + return video + + def divide_sequence(self, preprocessed_video): + return tf.reshape(preprocessed_video, (self.MAX_VIDEO_LENGTH//self.T, self.T, preprocessed_video.shape[1], preprocessed_video.shape[2], preprocessed_video.shape[3])) + #return np.array(np.array_split(preprocessed_video, self.MAX_VIDEO_LENGTH//self.T, axis=0)) + + ''' + # may not be needed + def padding_mask(self, current_sequence_length): # so that paddings are not treated as input + division = current_sequence_length // self.T + 2 # includes start_token so 2 + #print(division) + + mask = np.zeros((division, self.T, self.FRAME_DIM[0], self.FRAME_DIM[1], self.FRAME_DIM[2]), dtype=np.float) + mask = np.append(mask, np.ones((self.MAX_VIDEO_LENGTH//self.T - division, self.T, self.FRAME_DIM[0], self.FRAME_DIM[1], self.FRAME_DIM[2]), dtype=np.float), axis=0) + + return mask + + # may not be needed + def look_ahead_mask(self): + mask = np.zeros((1, self.T, self.FRAME_DIM[0], self.FRAME_DIM[1], self.FRAME_DIM[2]), dtype=np.float) + mask = np.append(mask, np.ones((49, self.T, self.FRAME_DIM[0], self.FRAME_DIM[1], self.FRAME_DIM[2]), dtype=np.float), axis=0) + mask = np.expand_dims(mask, axis=0) + #print(mask.shape) + for i in range(0, self.MAX_VIDEO_LENGTH - 2 * self.T + 1, self.T): + mask = np.append(mask, np.expand_dims(self.padding_mask(i), axis=0), axis=0) + return mask + ''' + +''' +def main(): + video_obj = Video() + video = video_obj.get_video('train', '05January_2010_Tuesday_tagesschau-2664') + current_sequence_length = video.shape[0] + + video = video_obj.preprocess_video(video) + print(video.shape) + + video = video_obj.divide_sequence(video) + print(video.shape) + + pad_mask = video_obj.padding_mask(current_sequence_length) + print(pad_mask.shape) + + look_mask = video_obj.look_ahead_mask() + print(look_mask.shape) + + +if __name__ == '__main__': + main() +''' From 26d28bbeb173b41d4972dd632449c33977b6e9e4 Mon Sep 17 00:00:00 2001 From: Aousnik Gupta Date: Fri, 2 Oct 2020 21:38:03 +0530 Subject: [PATCH 2/2] mathematics behind the model --- .../SignGAN/Architecture-1 (Transformer).pdf | Bin 0 -> 115467 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Machine_Learning/src/NLP/SignGAN/Architecture-1 (Transformer).pdf diff --git a/Machine_Learning/src/NLP/SignGAN/Architecture-1 (Transformer).pdf b/Machine_Learning/src/NLP/SignGAN/Architecture-1 (Transformer).pdf new file mode 100644 index 0000000000000000000000000000000000000000..dc052ce452090297d6eed43337d2fe62add5cbe4 GIT binary patch literal 115467 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

0}QMxy#I#=4#+0 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

  • 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