Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- data/fairseq/examples/MMPT/mmpt/datasets/__init__.py +10 -0
- data/fairseq/examples/MMPT/mmpt/datasets/fairseqmmdataset.py +57 -0
- data/fairseq/examples/MMPT/mmpt/datasets/mmdataset.py +111 -0
- data/fairseq/examples/MMPT/mmpt/evaluators/evaluator.py +54 -0
- data/fairseq/examples/MMPT/mmpt/evaluators/metric.py +313 -0
- data/fairseq/examples/MMPT/mmpt/losses/__init__.py +16 -0
- data/fairseq/examples/MMPT/mmpt/losses/fairseqmmloss.py +63 -0
- data/fairseq/examples/MMPT/mmpt/losses/loss.py +87 -0
- data/fairseq/examples/MMPT/mmpt/losses/nce.py +156 -0
- data/fairseq/examples/MMPT/mmpt/models/__init__.py +17 -0
- data/fairseq/examples/MMPT/mmpt/models/fairseqmmmodel.py +51 -0
- data/fairseq/examples/MMPT/mmpt/models/mmfusion.py +926 -0
- data/fairseq/examples/MMPT/mmpt/models/mmfusionnlg.py +999 -0
- data/fairseq/examples/MMPT/mmpt/models/transformermodel.py +734 -0
- data/fairseq/examples/MMPT/mmpt/modules/__init__.py +10 -0
- data/fairseq/examples/MMPT/mmpt/modules/mm.py +145 -0
- data/fairseq/examples/MMPT/mmpt/modules/retri.py +429 -0
- data/fairseq/examples/MMPT/mmpt/modules/vectorpool.py +246 -0
- data/fairseq/examples/MMPT/mmpt/processors/__init__.py +23 -0
- data/fairseq/examples/MMPT/mmpt/processors/dedupprocessor.py +242 -0
- data/fairseq/examples/MMPT/mmpt/processors/dsprocessor.py +848 -0
- data/fairseq/examples/MMPT/mmpt/processors/how2processor.py +887 -0
- data/fairseq/examples/MMPT/mmpt/processors/how2retriprocessor.py +100 -0
- data/fairseq/examples/MMPT/mmpt/processors/models/s3dg.py +336 -0
- data/fairseq/examples/MMPT/mmpt/processors/processor.py +274 -0
- data/fairseq/examples/MMPT/mmpt/tasks/__init__.py +22 -0
- data/fairseq/examples/MMPT/mmpt/tasks/fairseqmmtask.py +104 -0
- data/fairseq/examples/MMPT/mmpt/tasks/milncetask.py +27 -0
- data/fairseq/examples/MMPT/mmpt/tasks/retritask.py +253 -0
- data/fairseq/examples/MMPT/mmpt/tasks/task.py +184 -0
- data/fairseq/examples/MMPT/mmpt/tasks/vlmtask.py +27 -0
- data/fairseq/examples/MMPT/mmpt_cli/localjob.py +117 -0
- data/fairseq/examples/MMPT/mmpt_cli/predict.py +113 -0
- data/fairseq/examples/mr_hubert/README.md +187 -0
- data/fairseq/examples/mr_hubert/config/decode/infer.yaml +30 -0
- data/fairseq/examples/mr_hubert/config/decode/infer_lm.yaml +37 -0
- data/fairseq/examples/mr_hubert/config/decode/run/submitit_slurm.yaml +17 -0
- data/fairseq/examples/mr_hubert/config/decode/run/submitit_slurm_8gpu.yaml +17 -0
- data/fairseq/examples/mr_hubert/config/finetune/base_100h.yaml +97 -0
- data/fairseq/examples/mr_hubert/config/finetune/base_100h_large.yaml +97 -0
- data/fairseq/examples/mr_hubert/config/finetune/base_10h.yaml +101 -0
- data/fairseq/examples/mr_hubert/config/finetune/base_10h_large.yaml +101 -0
- data/fairseq/examples/mr_hubert/config/finetune/base_1h.yaml +100 -0
- data/fairseq/examples/mr_hubert/config/finetune/base_1h_large.yaml +99 -0
- data/fairseq/examples/mr_hubert/config/pretrain/mrhubert_base_librispeech.yaml +103 -0
- data/fairseq/examples/mr_hubert/config/pretrain/mrhubert_large_librilight.yaml +107 -0
- data/fairseq/examples/mr_hubert/config/pretrain/run/submitit_reg.yaml +20 -0
- data/fairseq/examples/mr_hubert/decode.sh +46 -0
- data/fairseq/examples/mr_hubert/finetune.sh +46 -0
- data/fairseq/examples/mr_hubert/train.sh +45 -0
data/fairseq/examples/MMPT/mmpt/datasets/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
from .mmdataset import *
|
| 6 |
+
|
| 7 |
+
try:
|
| 8 |
+
from .fairseqmmdataset import *
|
| 9 |
+
except ImportError:
|
| 10 |
+
pass
|
data/fairseq/examples/MMPT/mmpt/datasets/fairseqmmdataset.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
"""
|
| 6 |
+
TODO (huxu): fairseq wrapper class for all dataset you defined: mostly MMDataset.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from collections import OrderedDict
|
| 10 |
+
|
| 11 |
+
from torch.utils.data import Dataset
|
| 12 |
+
from torch.utils.data.dataloader import default_collate
|
| 13 |
+
from fairseq.data import FairseqDataset, data_utils
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class FairseqMMDataset(FairseqDataset):
|
| 17 |
+
"""
|
| 18 |
+
A wrapper class for MMDataset for fairseq.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
def __init__(self, mmdataset):
|
| 22 |
+
if not isinstance(mmdataset, Dataset):
|
| 23 |
+
raise TypeError("mmdataset must be of type `torch.utils.data.dataset`.")
|
| 24 |
+
self.mmdataset = mmdataset
|
| 25 |
+
|
| 26 |
+
def set_epoch(self, epoch, **unused):
|
| 27 |
+
super().set_epoch(epoch)
|
| 28 |
+
self.epoch = epoch
|
| 29 |
+
|
| 30 |
+
def __getitem__(self, idx):
|
| 31 |
+
with data_utils.numpy_seed(43211, self.epoch, idx):
|
| 32 |
+
return self.mmdataset[idx]
|
| 33 |
+
|
| 34 |
+
def __len__(self):
|
| 35 |
+
return len(self.mmdataset)
|
| 36 |
+
|
| 37 |
+
def collater(self, samples):
|
| 38 |
+
if hasattr(self.mmdataset, "collator"):
|
| 39 |
+
return self.mmdataset.collator(samples)
|
| 40 |
+
if len(samples) == 0:
|
| 41 |
+
return {}
|
| 42 |
+
if isinstance(samples[0], dict):
|
| 43 |
+
batch = OrderedDict()
|
| 44 |
+
for key in samples[0]:
|
| 45 |
+
if samples[0][key] is not None:
|
| 46 |
+
batch[key] = default_collate([sample[key] for sample in samples])
|
| 47 |
+
return batch
|
| 48 |
+
else:
|
| 49 |
+
return default_collate(samples)
|
| 50 |
+
|
| 51 |
+
def size(self, index):
|
| 52 |
+
"""dummy implementation: we don't use --max-tokens"""
|
| 53 |
+
return 1
|
| 54 |
+
|
| 55 |
+
def num_tokens(self, index):
|
| 56 |
+
"""dummy implementation: we don't use --max-tokens"""
|
| 57 |
+
return 1
|
data/fairseq/examples/MMPT/mmpt/datasets/mmdataset.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
from collections import OrderedDict
|
| 9 |
+
|
| 10 |
+
from torch.utils.data import Dataset
|
| 11 |
+
from torch.utils.data.dataloader import default_collate
|
| 12 |
+
|
| 13 |
+
from ..utils import set_seed
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class MMDataset(Dataset):
|
| 17 |
+
"""
|
| 18 |
+
A generic multi-modal dataset.
|
| 19 |
+
Args:
|
| 20 |
+
`meta_processor`: a meta processor,
|
| 21 |
+
handling loading meta data and return video_id and text_id.
|
| 22 |
+
`video_processor`: a video processor,
|
| 23 |
+
handling e.g., decoding, loading .np files.
|
| 24 |
+
`text_processor`: a text processor,
|
| 25 |
+
handling e.g., tokenization.
|
| 26 |
+
`aligner`: combine the video and text feature
|
| 27 |
+
as one training example.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def __init__(
|
| 31 |
+
self,
|
| 32 |
+
meta_processor,
|
| 33 |
+
video_processor,
|
| 34 |
+
text_processor,
|
| 35 |
+
align_processor,
|
| 36 |
+
):
|
| 37 |
+
self.split = meta_processor.split
|
| 38 |
+
self.meta_processor = meta_processor
|
| 39 |
+
self.video_processor = video_processor
|
| 40 |
+
self.text_processor = text_processor
|
| 41 |
+
self.align_processor = align_processor
|
| 42 |
+
|
| 43 |
+
def __len__(self):
|
| 44 |
+
return len(self.meta_processor)
|
| 45 |
+
|
| 46 |
+
def __getitem__(self, idx):
|
| 47 |
+
if self.split == "test":
|
| 48 |
+
set_seed(idx)
|
| 49 |
+
video_id, text_id = self.meta_processor[idx]
|
| 50 |
+
video_feature = self.video_processor(video_id)
|
| 51 |
+
text_feature = self.text_processor(text_id)
|
| 52 |
+
output = self.align_processor(video_id, video_feature, text_feature)
|
| 53 |
+
# TODO (huxu): the following is for debug purpose.
|
| 54 |
+
output.update({"idx": idx})
|
| 55 |
+
return output
|
| 56 |
+
|
| 57 |
+
def collater(self, samples):
|
| 58 |
+
"""This collator is deprecated.
|
| 59 |
+
set self.collator = MMDataset.collater.
|
| 60 |
+
see collator in FairseqMMDataset.
|
| 61 |
+
"""
|
| 62 |
+
|
| 63 |
+
if len(samples) == 0:
|
| 64 |
+
return {}
|
| 65 |
+
if isinstance(samples[0], dict):
|
| 66 |
+
batch = OrderedDict()
|
| 67 |
+
for key in samples[0]:
|
| 68 |
+
if samples[0][key] is not None:
|
| 69 |
+
batch[key] = default_collate(
|
| 70 |
+
[sample[key] for sample in samples])
|
| 71 |
+
# if torch.is_tensor(batch[key]):
|
| 72 |
+
# print(key, batch[key].size())
|
| 73 |
+
# else:
|
| 74 |
+
# print(key, len(batch[key]))
|
| 75 |
+
return batch
|
| 76 |
+
else:
|
| 77 |
+
return default_collate(samples)
|
| 78 |
+
|
| 79 |
+
def print_example(self, output):
|
| 80 |
+
print("[one example]", output["video_id"])
|
| 81 |
+
if (
|
| 82 |
+
hasattr(self.align_processor, "subsampling")
|
| 83 |
+
and self.align_processor.subsampling is not None
|
| 84 |
+
and self.align_processor.subsampling > 1
|
| 85 |
+
):
|
| 86 |
+
for key in output:
|
| 87 |
+
if torch.is_tensor(output[key]):
|
| 88 |
+
output[key] = output[key][0]
|
| 89 |
+
|
| 90 |
+
# search tokenizer to translate ids back.
|
| 91 |
+
tokenizer = None
|
| 92 |
+
if hasattr(self.text_processor, "tokenizer"):
|
| 93 |
+
tokenizer = self.text_processor.tokenizer
|
| 94 |
+
elif hasattr(self.align_processor, "tokenizer"):
|
| 95 |
+
tokenizer = self.align_processor.tokenizer
|
| 96 |
+
if tokenizer is not None:
|
| 97 |
+
caps = output["caps"].tolist()
|
| 98 |
+
if isinstance(caps[0], list):
|
| 99 |
+
caps = caps[0]
|
| 100 |
+
print("caps", tokenizer.decode(caps))
|
| 101 |
+
print("caps", tokenizer.convert_ids_to_tokens(caps))
|
| 102 |
+
|
| 103 |
+
for key, value in output.items():
|
| 104 |
+
if torch.is_tensor(value):
|
| 105 |
+
if len(value.size()) >= 3: # attention_mask.
|
| 106 |
+
print(key, value.size())
|
| 107 |
+
print(key, "first", value[0, :, :])
|
| 108 |
+
print(key, "last", value[-1, :, :])
|
| 109 |
+
else:
|
| 110 |
+
print(key, value)
|
| 111 |
+
print("[end of one example]")
|
data/fairseq/examples/MMPT/mmpt/evaluators/evaluator.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
import os
|
| 6 |
+
import glob
|
| 7 |
+
import numpy as np
|
| 8 |
+
|
| 9 |
+
from . import metric as metric_path
|
| 10 |
+
from . import predictor as predictor_path
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class Evaluator(object):
|
| 14 |
+
"""
|
| 15 |
+
perform evaluation on a single (downstream) task.
|
| 16 |
+
make this both offline and online.
|
| 17 |
+
TODO(huxu) saving evaluation results.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
def __init__(self, config, eval_dataloader=None):
|
| 21 |
+
if config.metric is None:
|
| 22 |
+
raise ValueError("config.metric is", config.metric)
|
| 23 |
+
metric_cls = getattr(metric_path, config.metric)
|
| 24 |
+
self.metric = metric_cls(config)
|
| 25 |
+
if config.predictor is None:
|
| 26 |
+
raise ValueError("config.predictor is", config.predictor)
|
| 27 |
+
predictor_cls = getattr(predictor_path, config.predictor)
|
| 28 |
+
self.predictor = predictor_cls(config)
|
| 29 |
+
self.eval_dataloader = eval_dataloader
|
| 30 |
+
|
| 31 |
+
def __call__(self):
|
| 32 |
+
try:
|
| 33 |
+
print(self.predictor.pred_dir)
|
| 34 |
+
for pred_file in glob.glob(
|
| 35 |
+
self.predictor.pred_dir + "/*_merged.npy"):
|
| 36 |
+
outputs = np.load(pred_file)
|
| 37 |
+
results = self.metric.compute_metrics(outputs)
|
| 38 |
+
self.metric.print_computed_metrics(results)
|
| 39 |
+
|
| 40 |
+
outputs = np.load(os.path.join(
|
| 41 |
+
self.predictor.pred_dir, "merged.npy"))
|
| 42 |
+
results = self.metric.compute_metrics(outputs)
|
| 43 |
+
return {"results": results, "metric": self.metric}
|
| 44 |
+
except FileNotFoundError:
|
| 45 |
+
print("\n[missing]", self.predictor.pred_dir)
|
| 46 |
+
return {}
|
| 47 |
+
|
| 48 |
+
def evaluate(self, model, eval_dataloader=None, output_file="merged"):
|
| 49 |
+
if eval_dataloader is None:
|
| 50 |
+
eval_dataloader = self.eval_dataloader
|
| 51 |
+
outputs = self.predictor.predict_loop(
|
| 52 |
+
model, eval_dataloader, output_file)
|
| 53 |
+
results = self.metric.compute_metrics(**outputs)
|
| 54 |
+
return results
|
data/fairseq/examples/MMPT/mmpt/evaluators/metric.py
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
import json
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class Metric(object):
|
| 11 |
+
def __init__(self, config, metric_names):
|
| 12 |
+
self.metric_names = metric_names
|
| 13 |
+
|
| 14 |
+
def best_metric(self, metric):
|
| 15 |
+
return metric[self.metric_names[0]]
|
| 16 |
+
|
| 17 |
+
def save_metrics(self, fn, metrics):
|
| 18 |
+
with open(fn, "w") as fw:
|
| 19 |
+
json.dump(fw, metrics)
|
| 20 |
+
|
| 21 |
+
def print_computed_metrics(self, metrics):
|
| 22 |
+
raise NotImplementedError
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class RetrievalMetric(Metric):
|
| 26 |
+
"""
|
| 27 |
+
this is modified from `howto100m/metrics.py`.
|
| 28 |
+
History of changes:
|
| 29 |
+
refactor as a class.
|
| 30 |
+
add metric_key in __init__
|
| 31 |
+
"""
|
| 32 |
+
|
| 33 |
+
def __init__(self, config, metric_names=["R1", "R5", "R10", "MR"]):
|
| 34 |
+
super().__init__(config, metric_names)
|
| 35 |
+
self.error = False # TODO(huxu): add to config to print error.
|
| 36 |
+
|
| 37 |
+
def compute_metrics(self, outputs, texts, **kwargs):
|
| 38 |
+
x = outputs
|
| 39 |
+
sx = np.sort(-x, axis=1)
|
| 40 |
+
d = np.diag(-x)
|
| 41 |
+
d = d[:, np.newaxis]
|
| 42 |
+
ind = sx - d
|
| 43 |
+
ind = np.where(ind == 0)
|
| 44 |
+
ind = ind[1]
|
| 45 |
+
metrics = {}
|
| 46 |
+
metrics["R1"] = float(np.sum(ind == 0)) / len(ind)
|
| 47 |
+
metrics["R5"] = float(np.sum(ind < 5)) / len(ind)
|
| 48 |
+
metrics["R10"] = float(np.sum(ind < 10)) / len(ind)
|
| 49 |
+
metrics["MR"] = np.median(ind) + 1
|
| 50 |
+
|
| 51 |
+
max_idx = np.argmax(outputs, axis=1)
|
| 52 |
+
if self.error:
|
| 53 |
+
# print top-20 errors.
|
| 54 |
+
error = []
|
| 55 |
+
for ex_idx in range(20):
|
| 56 |
+
error.append((texts[ex_idx], texts[max_idx[ex_idx]]))
|
| 57 |
+
metrics["error"] = error
|
| 58 |
+
return metrics
|
| 59 |
+
|
| 60 |
+
def print_computed_metrics(self, metrics):
|
| 61 |
+
r1 = metrics["R1"]
|
| 62 |
+
r5 = metrics["R5"]
|
| 63 |
+
r10 = metrics["R10"]
|
| 64 |
+
mr = metrics["MR"]
|
| 65 |
+
print(
|
| 66 |
+
"R@1: {:.4f} - R@5: {:.4f} - R@10: {:.4f} - Median R: {}".format(
|
| 67 |
+
r1, r5, r10, mr
|
| 68 |
+
)
|
| 69 |
+
)
|
| 70 |
+
if "error" in metrics:
|
| 71 |
+
print(metrics["error"])
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class DiDeMoMetric(Metric):
|
| 75 |
+
"""
|
| 76 |
+
History of changes:
|
| 77 |
+
python 2.x to python 3.x.
|
| 78 |
+
merge utils.py into eval to save one file.
|
| 79 |
+
reference: https://github.com/LisaAnne/LocalizingMoments/blob/master/utils/eval.py
|
| 80 |
+
Code to evaluate your results on the DiDeMo dataset.
|
| 81 |
+
"""
|
| 82 |
+
def __init__(self, config, metric_names=["rank1", "rank5", "miou"]):
|
| 83 |
+
super().__init__(config, metric_names)
|
| 84 |
+
|
| 85 |
+
def compute_metrics(self, outputs, targets, **kwargs):
|
| 86 |
+
assert len(outputs) == len(targets)
|
| 87 |
+
rank1, rank5, miou = self._eval_predictions(outputs, targets)
|
| 88 |
+
metrics = {
|
| 89 |
+
"rank1": rank1,
|
| 90 |
+
"rank5": rank5,
|
| 91 |
+
"miou": miou
|
| 92 |
+
}
|
| 93 |
+
return metrics
|
| 94 |
+
|
| 95 |
+
def print_computed_metrics(self, metrics):
|
| 96 |
+
rank1 = metrics["rank1"]
|
| 97 |
+
rank5 = metrics["rank5"]
|
| 98 |
+
miou = metrics["miou"]
|
| 99 |
+
# print("Average rank@1: %f" % rank1)
|
| 100 |
+
# print("Average rank@5: %f" % rank5)
|
| 101 |
+
# print("Average iou: %f" % miou)
|
| 102 |
+
|
| 103 |
+
print(
|
| 104 |
+
"Average rank@1: {:.4f} Average rank@5: {:.4f} Average iou: {:.4f}".format(
|
| 105 |
+
rank1, rank5, miou
|
| 106 |
+
)
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
def _iou(self, pred, gt):
|
| 110 |
+
intersection = max(0, min(pred[1], gt[1]) + 1 - max(pred[0], gt[0]))
|
| 111 |
+
union = max(pred[1], gt[1]) + 1 - min(pred[0], gt[0])
|
| 112 |
+
return float(intersection)/union
|
| 113 |
+
|
| 114 |
+
def _rank(self, pred, gt):
|
| 115 |
+
return pred.index(tuple(gt)) + 1
|
| 116 |
+
|
| 117 |
+
def _eval_predictions(self, segments, data):
|
| 118 |
+
'''
|
| 119 |
+
Inputs:
|
| 120 |
+
segments: For each item in the ground truth data, rank possible video segments given the description and video.
|
| 121 |
+
In DiDeMo, there are 21 posible moments extracted for each video so the list of video segments will be of length 21.
|
| 122 |
+
The first video segment should be the video segment that best corresponds to the text query.
|
| 123 |
+
There are 4180 sentence in the validation data, so when evaluating a model on the val dataset,
|
| 124 |
+
segments should be a list of lenght 4180, and each item in segments should be a list of length 21.
|
| 125 |
+
data: ground truth data
|
| 126 |
+
'''
|
| 127 |
+
average_ranks = []
|
| 128 |
+
average_iou = []
|
| 129 |
+
for s, d in zip(segments, data):
|
| 130 |
+
pred = s[0]
|
| 131 |
+
ious = [self._iou(pred, t) for t in d['times']]
|
| 132 |
+
average_iou.append(np.mean(np.sort(ious)[-3:]))
|
| 133 |
+
ranks = [self._rank(s, t) for t in d['times'] if tuple(t) in s] # if t in s] is added for s, e not in prediction.
|
| 134 |
+
average_ranks.append(np.mean(np.sort(ranks)[:3]))
|
| 135 |
+
rank1 = np.sum(np.array(average_ranks) <= 1)/float(len(average_ranks))
|
| 136 |
+
rank5 = np.sum(np.array(average_ranks) <= 5)/float(len(average_ranks))
|
| 137 |
+
miou = np.mean(average_iou)
|
| 138 |
+
|
| 139 |
+
# print("Average rank@1: %f" % rank1)
|
| 140 |
+
# print("Average rank@5: %f" % rank5)
|
| 141 |
+
# print("Average iou: %f" % miou)
|
| 142 |
+
return rank1, rank5, miou
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
class NLGMetric(Metric):
|
| 146 |
+
def __init__(
|
| 147 |
+
self,
|
| 148 |
+
config,
|
| 149 |
+
metric_names=[
|
| 150 |
+
"Bleu_1", "Bleu_2", "Bleu_3", "Bleu_4",
|
| 151 |
+
"METEOR", "ROUGE_L", "CIDEr"
|
| 152 |
+
]
|
| 153 |
+
):
|
| 154 |
+
super().__init__(config, metric_names)
|
| 155 |
+
# please install NLGEval from `https://github.com/Maluuba/nlg-eval`
|
| 156 |
+
from nlgeval import NLGEval
|
| 157 |
+
self.nlg = NLGEval()
|
| 158 |
+
|
| 159 |
+
def compute_metrics(self, outputs, targets, **kwargs):
|
| 160 |
+
return self.nlg.compute_metrics(
|
| 161 |
+
hyp_list=outputs, ref_list=targets)
|
| 162 |
+
|
| 163 |
+
def print_computed_metrics(self, metrics):
|
| 164 |
+
Bleu_1 = metrics["Bleu_1"]
|
| 165 |
+
Bleu_2 = metrics["Bleu_2"]
|
| 166 |
+
Bleu_3 = metrics["Bleu_3"]
|
| 167 |
+
Bleu_4 = metrics["Bleu_4"]
|
| 168 |
+
METEOR = metrics["METEOR"]
|
| 169 |
+
ROUGE_L = metrics["ROUGE_L"]
|
| 170 |
+
CIDEr = metrics["CIDEr"]
|
| 171 |
+
|
| 172 |
+
print(
|
| 173 |
+
"Bleu_1: {:.4f} - Bleu_2: {:.4f} - Bleu_3: {:.4f} - Bleu_4: {:.4f} - METEOR: {:.4f} - ROUGE_L: {:.4f} - CIDEr: {:.4f}".format(
|
| 174 |
+
Bleu_1, Bleu_2, Bleu_3, Bleu_4, METEOR, ROUGE_L, CIDEr
|
| 175 |
+
)
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
class QAMetric(Metric):
|
| 180 |
+
def __init__(
|
| 181 |
+
self,
|
| 182 |
+
config,
|
| 183 |
+
metric_names=["acc"]
|
| 184 |
+
):
|
| 185 |
+
super().__init__(config, metric_names)
|
| 186 |
+
|
| 187 |
+
def compute_metrics(self, outputs, targets, **kwargs):
|
| 188 |
+
from sklearn.metrics import accuracy_score
|
| 189 |
+
return {"acc": accuracy_score(targets, outputs)}
|
| 190 |
+
|
| 191 |
+
def print_computed_metrics(self, metrics):
|
| 192 |
+
print("acc: {:.4f}".format(metrics["acc"]))
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
class COINActionSegmentationMetric(Metric):
|
| 196 |
+
"""
|
| 197 |
+
COIN dataset listed 3 repos for Action Segmentation.
|
| 198 |
+
Action Sets, NeuralNetwork-Viterbi, TCFPN-ISBA.
|
| 199 |
+
The first and second are the same.
|
| 200 |
+
https://github.com/alexanderrichard/action-sets/blob/master/eval.py
|
| 201 |
+
|
| 202 |
+
Future reference for the third:
|
| 203 |
+
`https://github.com/Zephyr-D/TCFPN-ISBA/blob/master/utils/metrics.py`
|
| 204 |
+
"""
|
| 205 |
+
def __init__(self, config, metric_name=["frame_acc"]):
|
| 206 |
+
super().__init__(config, metric_name)
|
| 207 |
+
|
| 208 |
+
def compute_metrics(self, outputs, targets):
|
| 209 |
+
n_frames = 0
|
| 210 |
+
n_errors = 0
|
| 211 |
+
n_errors = sum(outputs != targets)
|
| 212 |
+
n_frames = len(targets)
|
| 213 |
+
return {"frame_acc": 1.0 - float(n_errors) / n_frames}
|
| 214 |
+
|
| 215 |
+
def print_computed_metrics(self, metrics):
|
| 216 |
+
fa = metrics["frame_acc"]
|
| 217 |
+
print("frame accuracy:", fa)
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
class CrossTaskMetric(Metric):
|
| 221 |
+
def __init__(self, config, metric_names=["recall"]):
|
| 222 |
+
super().__init__(config, metric_names)
|
| 223 |
+
|
| 224 |
+
def compute_metrics(self, outputs, targets, **kwargs):
|
| 225 |
+
"""refactored from line 166:
|
| 226 |
+
https://github.com/DmZhukov/CrossTask/blob/master/train.py"""
|
| 227 |
+
|
| 228 |
+
recalls = self._get_recalls(Y_true=targets, Y_pred=outputs)
|
| 229 |
+
results = {}
|
| 230 |
+
for task, rec in recalls.items():
|
| 231 |
+
results[str(task)] = rec
|
| 232 |
+
|
| 233 |
+
avg_recall = np.mean(list(recalls.values()))
|
| 234 |
+
results["recall"] = avg_recall
|
| 235 |
+
return results
|
| 236 |
+
|
| 237 |
+
def print_computed_metrics(self, metrics):
|
| 238 |
+
print('Recall: {0:0.3f}'.format(metrics["recall"]))
|
| 239 |
+
for task in metrics:
|
| 240 |
+
if task != "recall":
|
| 241 |
+
print('Task {0}. Recall = {1:0.3f}'.format(
|
| 242 |
+
task, metrics[task]))
|
| 243 |
+
|
| 244 |
+
def _get_recalls(self, Y_true, Y_pred):
|
| 245 |
+
"""refactored from
|
| 246 |
+
https://github.com/DmZhukov/CrossTask/blob/master/train.py"""
|
| 247 |
+
|
| 248 |
+
step_match = {task: 0 for task in Y_true.keys()}
|
| 249 |
+
step_total = {task: 0 for task in Y_true.keys()}
|
| 250 |
+
for task, ys_true in Y_true.items():
|
| 251 |
+
ys_pred = Y_pred[task]
|
| 252 |
+
for vid in set(ys_pred.keys()).intersection(set(ys_true.keys())):
|
| 253 |
+
y_true = ys_true[vid]
|
| 254 |
+
y_pred = ys_pred[vid]
|
| 255 |
+
step_total[task] += (y_true.sum(axis=0) > 0).sum()
|
| 256 |
+
step_match[task] += (y_true*y_pred).sum()
|
| 257 |
+
recalls = {
|
| 258 |
+
task: step_match[task] / n for task, n in step_total.items()}
|
| 259 |
+
return recalls
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
class ActionRecognitionMetric(Metric):
|
| 263 |
+
def __init__(
|
| 264 |
+
self,
|
| 265 |
+
config,
|
| 266 |
+
metric_names=["acc", "acc_splits", "r1_splits", "r5_splits", "r10_splits"]
|
| 267 |
+
):
|
| 268 |
+
super().__init__(config, metric_names)
|
| 269 |
+
|
| 270 |
+
def compute_metrics(self, outputs, targets, splits, **kwargs):
|
| 271 |
+
all_video_embd = outputs
|
| 272 |
+
labels = targets
|
| 273 |
+
split1, split2, split3 = splits
|
| 274 |
+
accs = []
|
| 275 |
+
r1s = []
|
| 276 |
+
r5s = []
|
| 277 |
+
r10s = []
|
| 278 |
+
for split in range(3):
|
| 279 |
+
if split == 0:
|
| 280 |
+
s = split1
|
| 281 |
+
elif split == 1:
|
| 282 |
+
s = split2
|
| 283 |
+
else:
|
| 284 |
+
s = split3
|
| 285 |
+
|
| 286 |
+
X_pred = all_video_embd[np.where(s == 2)[0]]
|
| 287 |
+
label_test = labels[np.where(s == 2)[0]]
|
| 288 |
+
logits = X_pred
|
| 289 |
+
X_pred = np.argmax(X_pred, axis=1)
|
| 290 |
+
acc = np.sum(X_pred == label_test) / float(len(X_pred))
|
| 291 |
+
accs.append(acc)
|
| 292 |
+
# compute recall.
|
| 293 |
+
sorted_pred = (-logits).argsort(axis=-1)
|
| 294 |
+
label_test_sp = label_test.reshape(-1, 1)
|
| 295 |
+
|
| 296 |
+
r1 = np.mean((sorted_pred[:, :1] == label_test_sp).sum(axis=1), axis=0)
|
| 297 |
+
r5 = np.mean((sorted_pred[:, :5] == label_test_sp).sum(axis=1), axis=0)
|
| 298 |
+
r10 = np.mean((sorted_pred[:, :10] == label_test_sp).sum(axis=1), axis=0)
|
| 299 |
+
r1s.append(r1)
|
| 300 |
+
r5s.append(r5)
|
| 301 |
+
r10s.append(r10)
|
| 302 |
+
|
| 303 |
+
return {"acc": accs[0], "acc_splits": accs, "r1_splits": r1s, "r5_splits": r5s, "r10_splits": r10s}
|
| 304 |
+
|
| 305 |
+
def print_computed_metrics(self, metrics):
|
| 306 |
+
for split, acc in enumerate(metrics["acc_splits"]):
|
| 307 |
+
print("Top 1 accuracy on split {}: {}; r1 {}; r5 {}; r10 {}".format(
|
| 308 |
+
split + 1, acc,
|
| 309 |
+
metrics["r1_splits"][split],
|
| 310 |
+
metrics["r5_splits"][split],
|
| 311 |
+
metrics["r10_splits"][split],
|
| 312 |
+
)
|
| 313 |
+
)
|
data/fairseq/examples/MMPT/mmpt/losses/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
from .loss import *
|
| 6 |
+
from .nce import *
|
| 7 |
+
|
| 8 |
+
try:
|
| 9 |
+
from .fairseqmmloss import *
|
| 10 |
+
except ImportError:
|
| 11 |
+
pass
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
from .expnce import *
|
| 15 |
+
except ImportError:
|
| 16 |
+
pass
|
data/fairseq/examples/MMPT/mmpt/losses/fairseqmmloss.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
"""
|
| 7 |
+
TODO (huxu): a general fairseq criterion for all your pre-defined losses.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from fairseq.criterions import FairseqCriterion, register_criterion
|
| 11 |
+
from fairseq.logging import metrics
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@register_criterion("mmloss")
|
| 15 |
+
class MMCriterion(FairseqCriterion):
|
| 16 |
+
def __init__(self, task):
|
| 17 |
+
super().__init__(task)
|
| 18 |
+
# TODO (huxu): wrap forward call of loss_fn and eval_fn into task.
|
| 19 |
+
self.mmtask = task.mmtask
|
| 20 |
+
|
| 21 |
+
def forward(self, model, sample):
|
| 22 |
+
"""Compute the loss for the given sample.
|
| 23 |
+
Returns a tuple with three elements:
|
| 24 |
+
1) the loss
|
| 25 |
+
2) the sample size, which is used as the denominator for the gradient
|
| 26 |
+
3) logging outputs to display while training
|
| 27 |
+
"""
|
| 28 |
+
outputs = self.mmtask(model, sample)
|
| 29 |
+
|
| 30 |
+
loss, loss_scalar, max_len, batch_size, sample_size = (
|
| 31 |
+
outputs["loss"],
|
| 32 |
+
outputs["loss_scalar"],
|
| 33 |
+
outputs["max_len"],
|
| 34 |
+
outputs["batch_size"],
|
| 35 |
+
outputs["sample_size"],
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
logging_output = {
|
| 39 |
+
"loss": loss_scalar,
|
| 40 |
+
"ntokens": max_len * batch_size, # dummy report.
|
| 41 |
+
"nsentences": batch_size, # dummy report.
|
| 42 |
+
"sample_size": sample_size,
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
return loss, 1, logging_output
|
| 46 |
+
|
| 47 |
+
@staticmethod
|
| 48 |
+
def reduce_metrics(logging_outputs) -> None:
|
| 49 |
+
"""Aggregate logging outputs from data parallel training."""
|
| 50 |
+
"""since we use NCE, our actual batch_size is 1 per GPU.
|
| 51 |
+
Then we take the mean of each worker."""
|
| 52 |
+
loss_sum = sum(log.get("loss", 0.0) for log in logging_outputs)
|
| 53 |
+
sample_size = sum(log.get("sample_size", 0) for log in logging_outputs)
|
| 54 |
+
metrics.log_scalar("loss", loss_sum / sample_size, round=3)
|
| 55 |
+
|
| 56 |
+
@staticmethod
|
| 57 |
+
def logging_outputs_can_be_summed() -> bool:
|
| 58 |
+
"""
|
| 59 |
+
Whether the logging outputs returned by `forward` can be summed
|
| 60 |
+
across workers prior to calling `reduce_metrics`. Setting this
|
| 61 |
+
to True will improves distributed training speed.
|
| 62 |
+
"""
|
| 63 |
+
return True
|
data/fairseq/examples/MMPT/mmpt/losses/loss.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. All Rights Reserved
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from torch import nn
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class Loss(object):
|
| 9 |
+
def __call__(self, *args, **kwargs):
|
| 10 |
+
raise NotImplementedError
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
# Dummy Loss for testing.
|
| 14 |
+
class DummyLoss(Loss):
|
| 15 |
+
def __init__(self):
|
| 16 |
+
self.loss = nn.CrossEntropyLoss()
|
| 17 |
+
|
| 18 |
+
def __call__(self, logits, targets, **kwargs):
|
| 19 |
+
return self.loss(logits, targets)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class DummyK400Loss(Loss):
|
| 23 |
+
"""dummy k400 loss for MViT."""
|
| 24 |
+
def __init__(self):
|
| 25 |
+
self.loss = nn.CrossEntropyLoss()
|
| 26 |
+
|
| 27 |
+
def __call__(self, logits, targets, **kwargs):
|
| 28 |
+
return self.loss(
|
| 29 |
+
logits, torch.randint(0, 400, (logits.size(0),), device=logits.device))
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class CrossEntropy(Loss):
|
| 33 |
+
def __init__(self):
|
| 34 |
+
self.loss = nn.CrossEntropyLoss()
|
| 35 |
+
|
| 36 |
+
def __call__(self, logits, targets, **kwargs):
|
| 37 |
+
return self.loss(logits.reshape(-1, logits.size(-1)), targets.reshape(-1))
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class ArgmaxCrossEntropy(Loss):
|
| 41 |
+
def __init__(self):
|
| 42 |
+
self.loss = nn.CrossEntropyLoss()
|
| 43 |
+
|
| 44 |
+
def __call__(self, logits, targets, **kwargs):
|
| 45 |
+
return self.loss(logits, targets.argmax(dim=1))
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class BCE(Loss):
|
| 49 |
+
def __init__(self):
|
| 50 |
+
self.loss = nn.BCEWithLogitsLoss()
|
| 51 |
+
|
| 52 |
+
def __call__(self, logits, targets, **kwargs):
|
| 53 |
+
targets = targets.squeeze(0)
|
| 54 |
+
return self.loss(logits, targets)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
class NLGLoss(Loss):
|
| 58 |
+
def __init__(self):
|
| 59 |
+
self.loss = nn.CrossEntropyLoss()
|
| 60 |
+
|
| 61 |
+
def __call__(self, logits, text_label, **kwargs):
|
| 62 |
+
targets = text_label[text_label != -100]
|
| 63 |
+
return self.loss(logits, targets)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class MSE(Loss):
|
| 67 |
+
def __init__(self):
|
| 68 |
+
self.loss = nn.MSELoss()
|
| 69 |
+
|
| 70 |
+
def __call__(self, logits, targets, **kwargs):
|
| 71 |
+
return self.loss(logits, targets)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class L1(Loss):
|
| 75 |
+
def __init__(self):
|
| 76 |
+
self.loss = nn.L1Loss()
|
| 77 |
+
|
| 78 |
+
def __call__(self, logits, targets, **kwargs):
|
| 79 |
+
return self.loss(logits, targets)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class SmoothL1(Loss):
|
| 83 |
+
def __init__(self):
|
| 84 |
+
self.loss = nn.SmoothL1Loss()
|
| 85 |
+
|
| 86 |
+
def __call__(self, logits, targets, **kwargs):
|
| 87 |
+
return self.loss(logits, targets)
|
data/fairseq/examples/MMPT/mmpt/losses/nce.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
"""
|
| 7 |
+
softmax-based NCE loss, used by this project.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
from torch import nn
|
| 13 |
+
|
| 14 |
+
from .loss import Loss
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class NCE(Loss):
|
| 18 |
+
def __init__(self):
|
| 19 |
+
# TODO (huxu): define temperature.
|
| 20 |
+
self.loss = nn.CrossEntropyLoss()
|
| 21 |
+
|
| 22 |
+
def __call__(self, align_scores, **kargs):
|
| 23 |
+
# note: we reuse the same shape as cls head in BERT (batch_size, 2)
|
| 24 |
+
# but NCE only needs one logits.
|
| 25 |
+
# (so we drop all weights in the second neg logits.)
|
| 26 |
+
align_scores = align_scores[:, :1]
|
| 27 |
+
# duplicate negative examples
|
| 28 |
+
batch_size = align_scores.size(0) // 2
|
| 29 |
+
pos_scores = align_scores[:batch_size]
|
| 30 |
+
neg_scores = align_scores[batch_size:].view(1, batch_size).repeat(
|
| 31 |
+
batch_size, 1)
|
| 32 |
+
scores = torch.cat([pos_scores, neg_scores], dim=1)
|
| 33 |
+
return self.loss(
|
| 34 |
+
scores,
|
| 35 |
+
torch.zeros(
|
| 36 |
+
(batch_size,),
|
| 37 |
+
dtype=torch.long,
|
| 38 |
+
device=align_scores.device),
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class T2VContraLoss(Loss):
|
| 43 |
+
"""NCE for MM joint space, on softmax text2video matrix.
|
| 44 |
+
"""
|
| 45 |
+
def __init__(self):
|
| 46 |
+
# TODO (huxu): define temperature.
|
| 47 |
+
self.loss = nn.CrossEntropyLoss()
|
| 48 |
+
|
| 49 |
+
def __call__(self, pooled_video, pooled_text, **kargs):
|
| 50 |
+
batch_size = pooled_video.size(0)
|
| 51 |
+
logits = torch.mm(pooled_text, pooled_video.transpose(1, 0))
|
| 52 |
+
targets = torch.arange(
|
| 53 |
+
batch_size,
|
| 54 |
+
dtype=torch.long,
|
| 55 |
+
device=pooled_video.device)
|
| 56 |
+
return self.loss(logits, targets)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class V2TContraLoss(Loss):
|
| 60 |
+
"""NCE for MM joint space, with softmax on video2text matrix."""
|
| 61 |
+
|
| 62 |
+
def __init__(self):
|
| 63 |
+
# TODO (huxu): define temperature.
|
| 64 |
+
self.loss = nn.CrossEntropyLoss()
|
| 65 |
+
|
| 66 |
+
def __call__(self, pooled_video, pooled_text, **kargs):
|
| 67 |
+
batch_size = pooled_video.size(0)
|
| 68 |
+
logits = torch.mm(pooled_video, pooled_text.transpose(1, 0))
|
| 69 |
+
targets = torch.arange(
|
| 70 |
+
batch_size,
|
| 71 |
+
dtype=torch.long,
|
| 72 |
+
device=pooled_video.device)
|
| 73 |
+
return self.loss(logits, targets)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
class MMContraLoss(Loss):
|
| 77 |
+
def __init__(self):
|
| 78 |
+
self.loss = nn.CrossEntropyLoss()
|
| 79 |
+
|
| 80 |
+
def __call__(self, pooled_video, pooled_text, **kwargs):
|
| 81 |
+
logits_per_video = pooled_video @ pooled_text.t()
|
| 82 |
+
logits_per_text = pooled_text @ pooled_video.t()
|
| 83 |
+
|
| 84 |
+
targets = torch.arange(
|
| 85 |
+
pooled_video.size(0),
|
| 86 |
+
dtype=torch.long,
|
| 87 |
+
device=pooled_video.device)
|
| 88 |
+
loss_video = self.loss(logits_per_video, targets)
|
| 89 |
+
loss_text = self.loss(logits_per_text, targets)
|
| 90 |
+
return loss_video + loss_text
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
class MTM(Loss):
|
| 94 |
+
"""Combination of MFM and MLM."""
|
| 95 |
+
|
| 96 |
+
def __init__(self):
|
| 97 |
+
self.loss = nn.CrossEntropyLoss()
|
| 98 |
+
|
| 99 |
+
def __call__(
|
| 100 |
+
self,
|
| 101 |
+
video_logits,
|
| 102 |
+
text_logits,
|
| 103 |
+
video_label,
|
| 104 |
+
text_label,
|
| 105 |
+
**kwargs
|
| 106 |
+
):
|
| 107 |
+
text_logits = torch.cat([
|
| 108 |
+
text_logits,
|
| 109 |
+
torch.zeros(
|
| 110 |
+
(text_logits.size(0), 1), device=text_logits.device)
|
| 111 |
+
], dim=1)
|
| 112 |
+
vt_logits = torch.cat([video_logits, text_logits], dim=0)
|
| 113 |
+
# loss for video.
|
| 114 |
+
video_label = torch.zeros(
|
| 115 |
+
(video_logits.size(0),),
|
| 116 |
+
dtype=torch.long,
|
| 117 |
+
device=video_logits.device
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
# loss for text.
|
| 121 |
+
text_label = text_label.reshape(-1)
|
| 122 |
+
labels_mask = text_label != -100
|
| 123 |
+
selected_text_label = text_label[labels_mask]
|
| 124 |
+
|
| 125 |
+
vt_label = torch.cat([video_label, selected_text_label], dim=0)
|
| 126 |
+
return self.loss(vt_logits, vt_label)
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
class MFMMLM(Loss):
|
| 130 |
+
"""Combination of MFM and MLM."""
|
| 131 |
+
|
| 132 |
+
def __init__(self):
|
| 133 |
+
self.loss = nn.CrossEntropyLoss()
|
| 134 |
+
|
| 135 |
+
def __call__(
|
| 136 |
+
self,
|
| 137 |
+
video_logits,
|
| 138 |
+
text_logits,
|
| 139 |
+
video_label,
|
| 140 |
+
text_label,
|
| 141 |
+
**kwargs
|
| 142 |
+
):
|
| 143 |
+
# loss for video.
|
| 144 |
+
video_label = torch.zeros(
|
| 145 |
+
(video_logits.size(0),),
|
| 146 |
+
dtype=torch.long,
|
| 147 |
+
device=video_logits.device
|
| 148 |
+
)
|
| 149 |
+
masked_frame_loss = self.loss(video_logits, video_label)
|
| 150 |
+
|
| 151 |
+
# loss for text.
|
| 152 |
+
text_label = text_label.reshape(-1)
|
| 153 |
+
labels_mask = text_label != -100
|
| 154 |
+
selected_text_label = text_label[labels_mask]
|
| 155 |
+
masked_lm_loss = self.loss(text_logits, selected_text_label)
|
| 156 |
+
return masked_frame_loss + masked_lm_loss
|
data/fairseq/examples/MMPT/mmpt/models/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
from .mmfusion import *
|
| 6 |
+
from .transformermodel import *
|
| 7 |
+
from .mmfusionnlg import *
|
| 8 |
+
|
| 9 |
+
try:
|
| 10 |
+
from .fairseqmmmodel import *
|
| 11 |
+
except ImportError:
|
| 12 |
+
pass
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
from .expmmfusion import *
|
| 16 |
+
except ImportError:
|
| 17 |
+
pass
|
data/fairseq/examples/MMPT/mmpt/models/fairseqmmmodel.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
from fairseq.models import (
|
| 7 |
+
BaseFairseqModel,
|
| 8 |
+
register_model,
|
| 9 |
+
register_model_architecture
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@register_model("mmmodel")
|
| 14 |
+
class FairseqMMModel(BaseFairseqModel):
|
| 15 |
+
"""a fairseq wrapper of model built by `task`."""
|
| 16 |
+
|
| 17 |
+
@classmethod
|
| 18 |
+
def build_model(cls, args, task):
|
| 19 |
+
return FairseqMMModel(task.mmtask.model)
|
| 20 |
+
|
| 21 |
+
def __init__(self, mmmodel):
|
| 22 |
+
super().__init__()
|
| 23 |
+
self.mmmodel = mmmodel
|
| 24 |
+
|
| 25 |
+
def forward(self, *args, **kwargs):
|
| 26 |
+
return self.mmmodel(*args, **kwargs)
|
| 27 |
+
|
| 28 |
+
def upgrade_state_dict_named(self, state_dict, name):
|
| 29 |
+
|
| 30 |
+
super().upgrade_state_dict_named(state_dict, name)
|
| 31 |
+
|
| 32 |
+
keys_to_delete = []
|
| 33 |
+
|
| 34 |
+
for key in state_dict:
|
| 35 |
+
if key not in self.state_dict():
|
| 36 |
+
keys_to_delete.append(key)
|
| 37 |
+
for key in keys_to_delete:
|
| 38 |
+
print("[INFO]", key, "not used anymore.")
|
| 39 |
+
del state_dict[key]
|
| 40 |
+
|
| 41 |
+
# copy any newly defined parameters.
|
| 42 |
+
for key in self.state_dict():
|
| 43 |
+
if key not in state_dict:
|
| 44 |
+
print("[INFO] adding", key)
|
| 45 |
+
state_dict[key] = self.state_dict()[key]
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
# a dummy arch, we config the model.
|
| 49 |
+
@register_model_architecture("mmmodel", "mmarch")
|
| 50 |
+
def mmarch(args):
|
| 51 |
+
pass
|
data/fairseq/examples/MMPT/mmpt/models/mmfusion.py
ADDED
|
@@ -0,0 +1,926 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2018 The Google AI Language Team Authors and The HuggingFace Inc. team.
|
| 3 |
+
# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved.
|
| 4 |
+
#
|
| 5 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 6 |
+
# you may not use this file except in compliance with the License.
|
| 7 |
+
# You may obtain a copy of the License at
|
| 8 |
+
#
|
| 9 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 10 |
+
#
|
| 11 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 12 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 13 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 14 |
+
# See the License for the specific language governing permissions and
|
| 15 |
+
# limitations under the License.
|
| 16 |
+
# Copyright (c) Facebook, Inc. All Rights Reserved
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
import torch
|
| 20 |
+
|
| 21 |
+
from torch import nn
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
from transformers import AutoConfig, AutoTokenizer
|
| 25 |
+
except ImportError:
|
| 26 |
+
pass
|
| 27 |
+
|
| 28 |
+
from . import transformermodel
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class MMPTModel(nn.Module):
|
| 32 |
+
"""An e2e wrapper of inference model.
|
| 33 |
+
"""
|
| 34 |
+
@classmethod
|
| 35 |
+
def from_pretrained(cls, config, checkpoint="checkpoint_best.pt"):
|
| 36 |
+
import os
|
| 37 |
+
from ..utils import recursive_config
|
| 38 |
+
from ..tasks import Task
|
| 39 |
+
config = recursive_config(config)
|
| 40 |
+
mmtask = Task.config_task(config)
|
| 41 |
+
checkpoint_path = os.path.join(config.eval.save_path, checkpoint)
|
| 42 |
+
mmtask.build_model(checkpoint=checkpoint_path)
|
| 43 |
+
# TODO(huxu): make the video encoder configurable.
|
| 44 |
+
from ..processors.models.s3dg import S3D
|
| 45 |
+
video_encoder = S3D('pretrained_models/s3d_dict.npy', 512)
|
| 46 |
+
video_encoder.load_state_dict(
|
| 47 |
+
torch.load('pretrained_models/s3d_howto100m.pth'))
|
| 48 |
+
from transformers import AutoTokenizer
|
| 49 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 50 |
+
config.dataset.bert_name, use_fast=config.dataset.use_fast
|
| 51 |
+
)
|
| 52 |
+
from ..processors import Aligner
|
| 53 |
+
aligner = Aligner(config.dataset)
|
| 54 |
+
return (
|
| 55 |
+
MMPTModel(config, mmtask.model, video_encoder),
|
| 56 |
+
tokenizer,
|
| 57 |
+
aligner
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
def __init__(self, config, model, video_encoder, **kwargs):
|
| 61 |
+
super().__init__()
|
| 62 |
+
self.max_video_len = config.dataset.max_video_len
|
| 63 |
+
self.video_encoder = video_encoder
|
| 64 |
+
self.model = model
|
| 65 |
+
|
| 66 |
+
def forward(self, video_frames, caps, cmasks, return_score=False):
|
| 67 |
+
bsz = video_frames.size(0)
|
| 68 |
+
assert bsz == 1, "only bsz=1 is supported now."
|
| 69 |
+
seq_len = video_frames.size(1)
|
| 70 |
+
video_frames = video_frames.view(-1, *video_frames.size()[2:])
|
| 71 |
+
vfeats = self.video_encoder(video_frames.permute(0, 4, 1, 2, 3))
|
| 72 |
+
vfeats = vfeats['video_embedding']
|
| 73 |
+
vfeats = vfeats.view(bsz, seq_len, vfeats.size(-1))
|
| 74 |
+
padding = torch.zeros(
|
| 75 |
+
bsz, self.max_video_len - seq_len, vfeats.size(-1))
|
| 76 |
+
vfeats = torch.cat([vfeats, padding], dim=1)
|
| 77 |
+
vmasks = torch.cat([
|
| 78 |
+
torch.ones((bsz, seq_len), dtype=torch.bool),
|
| 79 |
+
torch.zeros((bsz, self.max_video_len - seq_len), dtype=torch.bool)
|
| 80 |
+
],
|
| 81 |
+
dim=1
|
| 82 |
+
)
|
| 83 |
+
output = self.model(caps, cmasks, vfeats, vmasks)
|
| 84 |
+
if return_score:
|
| 85 |
+
output = {"score": torch.bmm(
|
| 86 |
+
output["pooled_video"][:, None, :],
|
| 87 |
+
output["pooled_text"][:, :, None]
|
| 88 |
+
).squeeze(-1).squeeze(-1)}
|
| 89 |
+
return output
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class MMFusion(nn.Module):
|
| 93 |
+
"""a MMPT wrapper class for MMBert style models.
|
| 94 |
+
TODO: move isolated mask to a subclass.
|
| 95 |
+
"""
|
| 96 |
+
def __init__(self, config, **kwargs):
|
| 97 |
+
super().__init__()
|
| 98 |
+
transformer_config = AutoConfig.from_pretrained(
|
| 99 |
+
config.dataset.bert_name)
|
| 100 |
+
self.hidden_size = transformer_config.hidden_size
|
| 101 |
+
self.is_train = False
|
| 102 |
+
if config.dataset.train_path is not None:
|
| 103 |
+
self.is_train = True
|
| 104 |
+
# 0 means no iso; 1-12 means iso up to that layer.
|
| 105 |
+
self.num_hidden_layers = transformer_config.num_hidden_layers
|
| 106 |
+
self.last_iso_layer = 0
|
| 107 |
+
if config.dataset.num_iso_layer is not None:
|
| 108 |
+
self.last_iso_layer = config.dataset.num_iso_layer - 1 + 1
|
| 109 |
+
|
| 110 |
+
if config.model.mm_encoder_cls is not None:
|
| 111 |
+
mm_encoder_cls = getattr(transformermodel, config.model.mm_encoder_cls)
|
| 112 |
+
model_config = AutoConfig.from_pretrained(config.dataset.bert_name)
|
| 113 |
+
model_config.max_video_len = config.dataset.max_video_len
|
| 114 |
+
# TODO: a general way to add parameter for a model.
|
| 115 |
+
model_config.use_seg_emb = config.model.use_seg_emb
|
| 116 |
+
self.mm_encoder = mm_encoder_cls.from_pretrained(
|
| 117 |
+
config.dataset.bert_name, config=model_config)
|
| 118 |
+
elif config.model.video_encoder_cls is not None\
|
| 119 |
+
and config.model.text_encoder_cls is not None:
|
| 120 |
+
video_encoder_cls = getattr(transformermodel, config.model.video_encoder_cls)
|
| 121 |
+
model_config = AutoConfig.from_pretrained(config.dataset.bert_name)
|
| 122 |
+
model_config.max_video_len = config.dataset.max_video_len
|
| 123 |
+
# TODO: make each model a set of config class.
|
| 124 |
+
if hasattr(model_config, "num_layers"):
|
| 125 |
+
model_config.num_layers = config.model.num_hidden_video_layers
|
| 126 |
+
else:
|
| 127 |
+
model_config.num_hidden_layers = config.model.num_hidden_video_layers
|
| 128 |
+
self.video_encoder = video_encoder_cls.from_pretrained(
|
| 129 |
+
config.dataset.bert_name, config=model_config)
|
| 130 |
+
# exact same NLP model from Huggingface.
|
| 131 |
+
text_encoder_cls = getattr(transformermodel, config.model.text_encoder_cls)
|
| 132 |
+
self.text_encoder = text_encoder_cls.from_pretrained(
|
| 133 |
+
config.dataset.bert_name)
|
| 134 |
+
else:
|
| 135 |
+
raise ValueError("the encoder must be either MM or two backbones.")
|
| 136 |
+
|
| 137 |
+
def forward(
|
| 138 |
+
self,
|
| 139 |
+
caps,
|
| 140 |
+
cmasks,
|
| 141 |
+
vfeats,
|
| 142 |
+
vmasks,
|
| 143 |
+
**kwargs
|
| 144 |
+
):
|
| 145 |
+
raise NotImplementedError(
|
| 146 |
+
"Please derive MMFusion module."
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
def _mm_on_the_fly(
|
| 150 |
+
self,
|
| 151 |
+
cmasks,
|
| 152 |
+
vmasks,
|
| 153 |
+
attention_mask
|
| 154 |
+
):
|
| 155 |
+
"""helper function for mask, seg_ids and token_type_ids."""
|
| 156 |
+
if attention_mask is None:
|
| 157 |
+
attention_mask = self._mm_attention_mask(cmasks, vmasks)
|
| 158 |
+
|
| 159 |
+
"""
|
| 160 |
+
0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1
|
| 161 |
+
| first sequence | second sequence |
|
| 162 |
+
"""
|
| 163 |
+
token_type_ids = torch.cat(
|
| 164 |
+
[
|
| 165 |
+
torch.zeros(
|
| 166 |
+
(vmasks.size(0), vmasks.size(1) + 2),
|
| 167 |
+
dtype=torch.long,
|
| 168 |
+
device=vmasks.device,
|
| 169 |
+
),
|
| 170 |
+
torch.ones(
|
| 171 |
+
(cmasks.size(0), cmasks.size(1) - 2),
|
| 172 |
+
dtype=torch.long,
|
| 173 |
+
device=cmasks.device,
|
| 174 |
+
),
|
| 175 |
+
],
|
| 176 |
+
dim=1,
|
| 177 |
+
)
|
| 178 |
+
return attention_mask, token_type_ids
|
| 179 |
+
|
| 180 |
+
def _mm_attention_mask(self, cmasks, vmasks):
|
| 181 |
+
assert cmasks.size(0) == vmasks.size(0), "{}, {}, {}, {}".format(
|
| 182 |
+
str(cmasks.size()),
|
| 183 |
+
str(vmasks.size()),
|
| 184 |
+
str(cmasks.size(0)),
|
| 185 |
+
str(vmasks.size(0)),
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
mm_mask = torch.cat([cmasks[:, :1], vmasks, cmasks[:, 1:]], dim=1)
|
| 189 |
+
if self.last_iso_layer == 0:
|
| 190 |
+
# hard attention mask.
|
| 191 |
+
return mm_mask
|
| 192 |
+
else:
|
| 193 |
+
# a gpu iso mask; 0 : num_iso_layer is isolated;
|
| 194 |
+
# num_iso_layer: are MM-fused.
|
| 195 |
+
# make an iso layer
|
| 196 |
+
batch_size = cmasks.size(0)
|
| 197 |
+
iso_mask = self._make_iso_mask(batch_size, cmasks, vmasks)
|
| 198 |
+
mm_mask = mm_mask[:, None, :].repeat(1, mm_mask.size(-1), 1)
|
| 199 |
+
iso_mm_masks = []
|
| 200 |
+
# hard attention mask.
|
| 201 |
+
iso_mask = iso_mask[:, None, :, :].repeat(
|
| 202 |
+
1, self.last_iso_layer, 1, 1)
|
| 203 |
+
iso_mm_masks.append(iso_mask)
|
| 204 |
+
if self.last_iso_layer < self.num_hidden_layers:
|
| 205 |
+
mm_mask = mm_mask[:, None, :, :].repeat(
|
| 206 |
+
1, self.num_hidden_layers - self.last_iso_layer, 1, 1
|
| 207 |
+
)
|
| 208 |
+
iso_mm_masks.append(mm_mask)
|
| 209 |
+
iso_mm_masks = torch.cat(iso_mm_masks, dim=1)
|
| 210 |
+
return iso_mm_masks
|
| 211 |
+
|
| 212 |
+
def _make_iso_mask(self, batch_size, cmasks, vmasks):
|
| 213 |
+
cls_self_mask = torch.cat(
|
| 214 |
+
[
|
| 215 |
+
torch.ones(
|
| 216 |
+
(batch_size, 1), dtype=torch.bool, device=cmasks.device),
|
| 217 |
+
torch.zeros(
|
| 218 |
+
(batch_size, cmasks.size(1) + vmasks.size(1) - 1),
|
| 219 |
+
dtype=torch.bool, device=cmasks.device)
|
| 220 |
+
], dim=1)
|
| 221 |
+
|
| 222 |
+
iso_video_mask = torch.cat(
|
| 223 |
+
[
|
| 224 |
+
# [CLS] is not used.
|
| 225 |
+
torch.zeros(
|
| 226 |
+
(batch_size, 1), dtype=torch.bool, device=cmasks.device
|
| 227 |
+
),
|
| 228 |
+
vmasks,
|
| 229 |
+
# assume to be 1.
|
| 230 |
+
cmasks[:, 1:2],
|
| 231 |
+
# 2 means [CLS] + [SEP]
|
| 232 |
+
torch.zeros(
|
| 233 |
+
(batch_size, cmasks.size(1) - 2),
|
| 234 |
+
dtype=torch.bool,
|
| 235 |
+
device=cmasks.device,
|
| 236 |
+
),
|
| 237 |
+
],
|
| 238 |
+
dim=1,
|
| 239 |
+
)
|
| 240 |
+
iso_text_mask = torch.cat(
|
| 241 |
+
[
|
| 242 |
+
torch.zeros(
|
| 243 |
+
(batch_size, 2 + vmasks.size(1)),
|
| 244 |
+
dtype=torch.bool,
|
| 245 |
+
device=cmasks.device,
|
| 246 |
+
), # [CLS] is not used.
|
| 247 |
+
cmasks[:, 2:], # assume to be 1.
|
| 248 |
+
],
|
| 249 |
+
dim=1,
|
| 250 |
+
)
|
| 251 |
+
cls_self_mask = cls_self_mask[:, None, :]
|
| 252 |
+
iso_video_mask = iso_video_mask[:, None, :].repeat(
|
| 253 |
+
1, vmasks.size(1) + 1, 1)
|
| 254 |
+
iso_text_mask = iso_text_mask[:, None, :].repeat(
|
| 255 |
+
1, cmasks.size(1) - 2, 1)
|
| 256 |
+
return torch.cat([cls_self_mask, iso_video_mask, iso_text_mask], dim=1)
|
| 257 |
+
|
| 258 |
+
def _pooling_vt_layer(
|
| 259 |
+
self,
|
| 260 |
+
layered_sequence_output,
|
| 261 |
+
cmasks,
|
| 262 |
+
vmasks
|
| 263 |
+
):
|
| 264 |
+
layer_idx = self.last_iso_layer \
|
| 265 |
+
if self.last_iso_layer > 0 else self.num_hidden_layers
|
| 266 |
+
hidden_state = layered_sequence_output[layer_idx]
|
| 267 |
+
# also output pooled_video and pooled_text.
|
| 268 |
+
batch_size = cmasks.size(0)
|
| 269 |
+
# pool the modality.
|
| 270 |
+
text_offset = vmasks.size(1) + 2 # [CLS] + [SEP]
|
| 271 |
+
# video tokens + [SEP]
|
| 272 |
+
video_outputs = hidden_state[:, 1:text_offset]
|
| 273 |
+
video_attention_mask = torch.cat(
|
| 274 |
+
[
|
| 275 |
+
vmasks,
|
| 276 |
+
torch.ones(
|
| 277 |
+
(batch_size, 1), dtype=torch.bool, device=vmasks.device),
|
| 278 |
+
],
|
| 279 |
+
dim=1,
|
| 280 |
+
)
|
| 281 |
+
assert video_outputs.size(1) == video_attention_mask.size(1)
|
| 282 |
+
pooled_video = torch.sum(
|
| 283 |
+
video_outputs * video_attention_mask.unsqueeze(-1), dim=1
|
| 284 |
+
) / video_attention_mask.sum(1, keepdim=True)
|
| 285 |
+
# pooled_video = torch.mean(video_outputs[0], dim=1)
|
| 286 |
+
|
| 287 |
+
# text tokens + [SEP]
|
| 288 |
+
text_attention_mask = cmasks[:, 2:]
|
| 289 |
+
text_outputs = hidden_state[:, text_offset:]
|
| 290 |
+
assert text_outputs.size(1) == text_attention_mask.size(1)
|
| 291 |
+
pooled_text = torch.sum(
|
| 292 |
+
text_outputs * text_attention_mask.unsqueeze(-1), dim=1
|
| 293 |
+
) / text_attention_mask.sum(1, keepdim=True)
|
| 294 |
+
return pooled_video, pooled_text
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
class MMFusionMFMMLM(MMFusion):
|
| 298 |
+
"""forward function for MFM and MLM."""
|
| 299 |
+
def forward(
|
| 300 |
+
self,
|
| 301 |
+
caps,
|
| 302 |
+
cmasks,
|
| 303 |
+
vfeats,
|
| 304 |
+
vmasks,
|
| 305 |
+
attention_mask=None,
|
| 306 |
+
video_label=None,
|
| 307 |
+
text_label=None,
|
| 308 |
+
**kwargs
|
| 309 |
+
):
|
| 310 |
+
output_hidden_states = False if self.is_train else True
|
| 311 |
+
|
| 312 |
+
target_vfeats, non_masked_frame_mask = None, None
|
| 313 |
+
if video_label is not None:
|
| 314 |
+
target_vfeats = vfeats.masked_select(
|
| 315 |
+
video_label.unsqueeze(-1)).view(
|
| 316 |
+
-1, vfeats.size(-1)
|
| 317 |
+
)
|
| 318 |
+
# mask video token.
|
| 319 |
+
vfeats[video_label] = 0.0
|
| 320 |
+
non_masked_frame_mask = vmasks.clone()
|
| 321 |
+
non_masked_frame_mask[video_label] = False
|
| 322 |
+
|
| 323 |
+
attention_mask, token_type_ids = self._mm_on_the_fly(
|
| 324 |
+
cmasks, vmasks, attention_mask)
|
| 325 |
+
|
| 326 |
+
outputs = self.mm_encoder(
|
| 327 |
+
input_ids=caps,
|
| 328 |
+
input_video_embeds=vfeats,
|
| 329 |
+
attention_mask=attention_mask,
|
| 330 |
+
token_type_ids=token_type_ids,
|
| 331 |
+
masked_frame_labels=video_label,
|
| 332 |
+
target_video_hidden_states=target_vfeats,
|
| 333 |
+
non_masked_frame_mask=non_masked_frame_mask,
|
| 334 |
+
masked_lm_labels=text_label,
|
| 335 |
+
output_hidden_states=output_hidden_states,
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
video_logits, text_logits = outputs[0], outputs[1]
|
| 339 |
+
|
| 340 |
+
if self.is_train: # return earlier for training.
|
| 341 |
+
return {
|
| 342 |
+
"video_logits": video_logits,
|
| 343 |
+
"text_logits": text_logits,
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
pooled_video, pooled_text = self._pooling_vt_layer(
|
| 347 |
+
outputs[2], cmasks, vmasks)
|
| 348 |
+
return {"pooled_video": pooled_video, "pooled_text": pooled_text}
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
class MMFusionMTM(MMFusionMFMMLM):
|
| 352 |
+
def __init__(self, config, **kwargs):
|
| 353 |
+
super().__init__(config)
|
| 354 |
+
"""
|
| 355 |
+
For reproducibility:
|
| 356 |
+
self.mm_encoder will be initialized then discarded.
|
| 357 |
+
"""
|
| 358 |
+
from .transformermodel import MMBertForMTM
|
| 359 |
+
model_config = AutoConfig.from_pretrained(config.dataset.bert_name)
|
| 360 |
+
model_config.max_video_len = config.dataset.max_video_len
|
| 361 |
+
model_config.use_seg_emb = config.model.use_seg_emb
|
| 362 |
+
self.mm_encoder = MMBertForMTM.from_pretrained(
|
| 363 |
+
config.dataset.bert_name, config=model_config)
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
class MMFusionShare(MMFusion):
|
| 367 |
+
"""A retrival wrapper using mm_encoder as both video/text backbone.
|
| 368 |
+
TODO: move formally.
|
| 369 |
+
"""
|
| 370 |
+
def forward(
|
| 371 |
+
self,
|
| 372 |
+
caps,
|
| 373 |
+
cmasks,
|
| 374 |
+
vfeats,
|
| 375 |
+
vmasks,
|
| 376 |
+
attention_mask=None,
|
| 377 |
+
video_label=None,
|
| 378 |
+
text_label=None,
|
| 379 |
+
output_hidden_states=False,
|
| 380 |
+
**kwargs
|
| 381 |
+
):
|
| 382 |
+
pooled_video = self.forward_video(
|
| 383 |
+
vfeats,
|
| 384 |
+
vmasks,
|
| 385 |
+
caps,
|
| 386 |
+
cmasks,
|
| 387 |
+
output_hidden_states
|
| 388 |
+
)
|
| 389 |
+
|
| 390 |
+
pooled_text = self.forward_text(
|
| 391 |
+
caps,
|
| 392 |
+
cmasks,
|
| 393 |
+
output_hidden_states
|
| 394 |
+
)
|
| 395 |
+
|
| 396 |
+
return {"pooled_video": pooled_video, "pooled_text": pooled_text}
|
| 397 |
+
|
| 398 |
+
def forward_video(
|
| 399 |
+
self,
|
| 400 |
+
vfeats,
|
| 401 |
+
vmasks,
|
| 402 |
+
caps,
|
| 403 |
+
cmasks,
|
| 404 |
+
output_hidden_states=False,
|
| 405 |
+
**kwargs
|
| 406 |
+
):
|
| 407 |
+
input_ids = caps[:, :2]
|
| 408 |
+
|
| 409 |
+
attention_mask = torch.cat([
|
| 410 |
+
cmasks[:, :1],
|
| 411 |
+
vmasks,
|
| 412 |
+
cmasks[:, 1:2]
|
| 413 |
+
], dim=1)
|
| 414 |
+
|
| 415 |
+
token_type_ids = torch.zeros(
|
| 416 |
+
(vmasks.size(0), vmasks.size(1) + 2),
|
| 417 |
+
dtype=torch.long,
|
| 418 |
+
device=vmasks.device)
|
| 419 |
+
|
| 420 |
+
outputs = self.mm_encoder(
|
| 421 |
+
input_ids=input_ids,
|
| 422 |
+
input_video_embeds=vfeats,
|
| 423 |
+
attention_mask=attention_mask,
|
| 424 |
+
token_type_ids=token_type_ids,
|
| 425 |
+
output_hidden_states=True
|
| 426 |
+
)
|
| 427 |
+
video_outputs = outputs[0]
|
| 428 |
+
|
| 429 |
+
if output_hidden_states:
|
| 430 |
+
return video_outputs
|
| 431 |
+
|
| 432 |
+
batch_size = cmasks.size(0)
|
| 433 |
+
|
| 434 |
+
video_attention_mask = torch.cat(
|
| 435 |
+
[
|
| 436 |
+
torch.zeros(
|
| 437 |
+
(batch_size, 1), dtype=torch.bool, device=vmasks.device),
|
| 438 |
+
vmasks,
|
| 439 |
+
torch.ones(
|
| 440 |
+
(batch_size, 1), dtype=torch.bool, device=vmasks.device),
|
| 441 |
+
],
|
| 442 |
+
dim=1,
|
| 443 |
+
)
|
| 444 |
+
assert video_outputs.size(1) == video_attention_mask.size(1)
|
| 445 |
+
|
| 446 |
+
video_attention_mask = video_attention_mask.type(video_outputs.dtype) \
|
| 447 |
+
/ video_attention_mask.sum(1, keepdim=True)
|
| 448 |
+
|
| 449 |
+
pooled_video = torch.bmm(
|
| 450 |
+
video_outputs.transpose(2, 1),
|
| 451 |
+
video_attention_mask.unsqueeze(2)
|
| 452 |
+
).squeeze(-1)
|
| 453 |
+
return pooled_video # video_outputs
|
| 454 |
+
|
| 455 |
+
def forward_text(
|
| 456 |
+
self,
|
| 457 |
+
caps,
|
| 458 |
+
cmasks,
|
| 459 |
+
output_hidden_states=False,
|
| 460 |
+
**kwargs
|
| 461 |
+
):
|
| 462 |
+
input_ids = torch.cat([
|
| 463 |
+
caps[:, :1], caps[:, 2:],
|
| 464 |
+
], dim=1)
|
| 465 |
+
|
| 466 |
+
attention_mask = torch.cat([
|
| 467 |
+
cmasks[:, :1],
|
| 468 |
+
cmasks[:, 2:]
|
| 469 |
+
], dim=1)
|
| 470 |
+
|
| 471 |
+
token_type_ids = torch.cat([
|
| 472 |
+
torch.zeros(
|
| 473 |
+
(cmasks.size(0), 1),
|
| 474 |
+
dtype=torch.long,
|
| 475 |
+
device=cmasks.device),
|
| 476 |
+
torch.ones(
|
| 477 |
+
(cmasks.size(0), cmasks.size(1) - 2),
|
| 478 |
+
dtype=torch.long,
|
| 479 |
+
device=cmasks.device)
|
| 480 |
+
], dim=1)
|
| 481 |
+
|
| 482 |
+
outputs = self.mm_encoder(
|
| 483 |
+
input_ids=input_ids,
|
| 484 |
+
input_video_embeds=None,
|
| 485 |
+
attention_mask=attention_mask,
|
| 486 |
+
token_type_ids=token_type_ids,
|
| 487 |
+
output_hidden_states=True
|
| 488 |
+
)
|
| 489 |
+
text_outputs = outputs[0]
|
| 490 |
+
|
| 491 |
+
if output_hidden_states:
|
| 492 |
+
return text_outputs
|
| 493 |
+
|
| 494 |
+
batch_size = caps.size(0)
|
| 495 |
+
# text tokens + [SEP]
|
| 496 |
+
text_attention_mask = torch.cat([
|
| 497 |
+
torch.zeros(
|
| 498 |
+
(batch_size, 1), dtype=torch.bool, device=cmasks.device),
|
| 499 |
+
cmasks[:, 2:]
|
| 500 |
+
], dim=1)
|
| 501 |
+
|
| 502 |
+
assert text_outputs.size(1) == text_attention_mask.size(1)
|
| 503 |
+
|
| 504 |
+
text_attention_mask = text_attention_mask.type(text_outputs.dtype) \
|
| 505 |
+
/ text_attention_mask.sum(1, keepdim=True)
|
| 506 |
+
|
| 507 |
+
pooled_text = torch.bmm(
|
| 508 |
+
text_outputs.transpose(2, 1),
|
| 509 |
+
text_attention_mask.unsqueeze(2)
|
| 510 |
+
).squeeze(-1)
|
| 511 |
+
return pooled_text # text_outputs
|
| 512 |
+
|
| 513 |
+
|
| 514 |
+
class MMFusionSeparate(MMFusionShare):
|
| 515 |
+
def forward_video(
|
| 516 |
+
self,
|
| 517 |
+
vfeats,
|
| 518 |
+
vmasks,
|
| 519 |
+
caps,
|
| 520 |
+
cmasks,
|
| 521 |
+
output_hidden_states=False,
|
| 522 |
+
**kwargs
|
| 523 |
+
):
|
| 524 |
+
input_ids = caps[:, :2]
|
| 525 |
+
|
| 526 |
+
attention_mask = torch.cat([
|
| 527 |
+
cmasks[:, :1],
|
| 528 |
+
vmasks,
|
| 529 |
+
cmasks[:, 1:2]
|
| 530 |
+
], dim=1)
|
| 531 |
+
|
| 532 |
+
token_type_ids = torch.zeros(
|
| 533 |
+
(vmasks.size(0), vmasks.size(1) + 2),
|
| 534 |
+
dtype=torch.long,
|
| 535 |
+
device=vmasks.device)
|
| 536 |
+
|
| 537 |
+
outputs = self.video_encoder(
|
| 538 |
+
input_ids=input_ids,
|
| 539 |
+
input_video_embeds=vfeats,
|
| 540 |
+
attention_mask=attention_mask,
|
| 541 |
+
token_type_ids=token_type_ids,
|
| 542 |
+
output_hidden_states=True
|
| 543 |
+
)
|
| 544 |
+
video_outputs = outputs[0]
|
| 545 |
+
|
| 546 |
+
if output_hidden_states:
|
| 547 |
+
return video_outputs
|
| 548 |
+
|
| 549 |
+
batch_size = cmasks.size(0)
|
| 550 |
+
|
| 551 |
+
video_attention_mask = torch.cat(
|
| 552 |
+
[
|
| 553 |
+
torch.zeros(
|
| 554 |
+
(batch_size, 1), dtype=torch.bool, device=vmasks.device),
|
| 555 |
+
vmasks,
|
| 556 |
+
torch.ones(
|
| 557 |
+
(batch_size, 1), dtype=torch.bool, device=vmasks.device),
|
| 558 |
+
],
|
| 559 |
+
dim=1,
|
| 560 |
+
)
|
| 561 |
+
assert video_outputs.size(1) == video_attention_mask.size(1)
|
| 562 |
+
|
| 563 |
+
video_attention_mask = video_attention_mask.type(video_outputs.dtype) \
|
| 564 |
+
/ video_attention_mask.sum(1, keepdim=True)
|
| 565 |
+
|
| 566 |
+
pooled_video = torch.bmm(
|
| 567 |
+
video_outputs.transpose(2, 1),
|
| 568 |
+
video_attention_mask.unsqueeze(2)
|
| 569 |
+
).squeeze(-1)
|
| 570 |
+
return pooled_video # video_outputs
|
| 571 |
+
|
| 572 |
+
def forward_text(
|
| 573 |
+
self,
|
| 574 |
+
caps,
|
| 575 |
+
cmasks,
|
| 576 |
+
output_hidden_states=False,
|
| 577 |
+
**kwargs
|
| 578 |
+
):
|
| 579 |
+
input_ids = torch.cat([
|
| 580 |
+
caps[:, :1], caps[:, 2:],
|
| 581 |
+
], dim=1)
|
| 582 |
+
|
| 583 |
+
attention_mask = torch.cat([
|
| 584 |
+
cmasks[:, :1],
|
| 585 |
+
cmasks[:, 2:]
|
| 586 |
+
], dim=1)
|
| 587 |
+
# different from sharing, we use all-0 type.
|
| 588 |
+
token_type_ids = torch.zeros(
|
| 589 |
+
(cmasks.size(0), cmasks.size(1) - 1),
|
| 590 |
+
dtype=torch.long,
|
| 591 |
+
device=cmasks.device)
|
| 592 |
+
|
| 593 |
+
outputs = self.text_encoder(
|
| 594 |
+
input_ids=input_ids,
|
| 595 |
+
attention_mask=attention_mask,
|
| 596 |
+
token_type_ids=token_type_ids,
|
| 597 |
+
output_hidden_states=True
|
| 598 |
+
)
|
| 599 |
+
text_outputs = outputs[0]
|
| 600 |
+
|
| 601 |
+
if output_hidden_states:
|
| 602 |
+
return text_outputs
|
| 603 |
+
|
| 604 |
+
batch_size = caps.size(0)
|
| 605 |
+
# text tokens + [SEP]
|
| 606 |
+
text_attention_mask = torch.cat([
|
| 607 |
+
torch.zeros(
|
| 608 |
+
(batch_size, 1), dtype=torch.bool, device=cmasks.device),
|
| 609 |
+
cmasks[:, 2:]
|
| 610 |
+
], dim=1)
|
| 611 |
+
|
| 612 |
+
assert text_outputs.size(1) == text_attention_mask.size(1)
|
| 613 |
+
|
| 614 |
+
text_attention_mask = text_attention_mask.type(text_outputs.dtype) \
|
| 615 |
+
/ text_attention_mask.sum(1, keepdim=True)
|
| 616 |
+
|
| 617 |
+
pooled_text = torch.bmm(
|
| 618 |
+
text_outputs.transpose(2, 1),
|
| 619 |
+
text_attention_mask.unsqueeze(2)
|
| 620 |
+
).squeeze(-1)
|
| 621 |
+
return pooled_text # text_outputs
|
| 622 |
+
|
| 623 |
+
|
| 624 |
+
class MMFusionJoint(MMFusion):
|
| 625 |
+
"""fine-tuning wrapper for retrival task."""
|
| 626 |
+
|
| 627 |
+
def forward(
|
| 628 |
+
self,
|
| 629 |
+
caps,
|
| 630 |
+
cmasks,
|
| 631 |
+
vfeats,
|
| 632 |
+
vmasks,
|
| 633 |
+
attention_mask=None,
|
| 634 |
+
video_label=None,
|
| 635 |
+
text_label=None,
|
| 636 |
+
**kwargs
|
| 637 |
+
):
|
| 638 |
+
# TODO (huxu): other ways to do negative examples; move the following
|
| 639 |
+
# into your criterion forward.
|
| 640 |
+
output_hidden_states = True
|
| 641 |
+
|
| 642 |
+
attention_mask, token_type_ids = self._mm_on_the_fly(
|
| 643 |
+
cmasks, vmasks, attention_mask)
|
| 644 |
+
|
| 645 |
+
separate_forward_split = (
|
| 646 |
+
None if self.is_train else vmasks.size(1) + 2
|
| 647 |
+
) # [CLS] + [SEP]
|
| 648 |
+
|
| 649 |
+
outputs = self.mm_encoder(
|
| 650 |
+
input_ids=caps,
|
| 651 |
+
input_video_embeds=vfeats,
|
| 652 |
+
attention_mask=attention_mask,
|
| 653 |
+
token_type_ids=token_type_ids,
|
| 654 |
+
output_hidden_states=output_hidden_states,
|
| 655 |
+
separate_forward_split=separate_forward_split,
|
| 656 |
+
)
|
| 657 |
+
|
| 658 |
+
pooled_video, pooled_text = self._pooling_vt_layer(
|
| 659 |
+
outputs[2], cmasks, vmasks)
|
| 660 |
+
return {"pooled_video": pooled_video, "pooled_text": pooled_text}
|
| 661 |
+
|
| 662 |
+
|
| 663 |
+
class MMFusionActionSegmentation(MMFusion):
|
| 664 |
+
"""Fine-tuning wrapper for action segmentation.
|
| 665 |
+
TODO: rename this for VLM.
|
| 666 |
+
"""
|
| 667 |
+
def forward(
|
| 668 |
+
self,
|
| 669 |
+
caps,
|
| 670 |
+
cmasks,
|
| 671 |
+
vfeats,
|
| 672 |
+
vmasks,
|
| 673 |
+
attention_mask=None,
|
| 674 |
+
**kwargs
|
| 675 |
+
):
|
| 676 |
+
# ActionLocalization assume of batch_size=1, squeeze it.
|
| 677 |
+
caps = caps.view(-1, caps.size(-1))
|
| 678 |
+
cmasks = cmasks.view(-1, cmasks.size(-1))
|
| 679 |
+
vfeats = vfeats.view(-1, vfeats.size(2), vfeats.size(3))
|
| 680 |
+
vmasks = vmasks.view(-1, vmasks.size(-1))
|
| 681 |
+
|
| 682 |
+
# this may not cover all shapes of attention_mask.
|
| 683 |
+
attention_mask = attention_mask.view(
|
| 684 |
+
-1, attention_mask.size(2), attention_mask.size(3)) \
|
| 685 |
+
if attention_mask is not None else None
|
| 686 |
+
|
| 687 |
+
# TODO (huxu): other ways to do negative examples; move the following
|
| 688 |
+
# into your criterion forward.
|
| 689 |
+
output_hidden_states = True
|
| 690 |
+
|
| 691 |
+
# video forwarding, text is dummy; never use attention_mask.
|
| 692 |
+
attention_mask, token_type_ids = self._mm_on_the_fly(
|
| 693 |
+
cmasks, vmasks, attention_mask)
|
| 694 |
+
|
| 695 |
+
logits = self.mm_encoder(
|
| 696 |
+
input_ids=caps,
|
| 697 |
+
input_video_embeds=vfeats,
|
| 698 |
+
attention_mask=attention_mask,
|
| 699 |
+
token_type_ids=token_type_ids,
|
| 700 |
+
output_hidden_states=output_hidden_states,
|
| 701 |
+
)
|
| 702 |
+
return {"logits": logits[0][:, 1:vmasks.size(1)+1]}
|
| 703 |
+
|
| 704 |
+
|
| 705 |
+
class MMFusionActionLocalization(MMFusion):
|
| 706 |
+
"""fine-tuning model for retrival task."""
|
| 707 |
+
|
| 708 |
+
def __init__(self, config, **kwargs):
|
| 709 |
+
super().__init__(config)
|
| 710 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 711 |
+
config.dataset.bert_name)
|
| 712 |
+
self.cls_token_id = tokenizer.cls_token_id
|
| 713 |
+
self.sep_token_id = tokenizer.sep_token_id
|
| 714 |
+
self.pad_token_id = tokenizer.pad_token_id
|
| 715 |
+
|
| 716 |
+
def forward(
|
| 717 |
+
self,
|
| 718 |
+
caps,
|
| 719 |
+
cmasks,
|
| 720 |
+
vfeats,
|
| 721 |
+
vmasks,
|
| 722 |
+
attention_mask=None,
|
| 723 |
+
**kwargs
|
| 724 |
+
):
|
| 725 |
+
# ActionLocalization assume of batch_size=1, squeeze it.
|
| 726 |
+
caps = caps.squeeze(0)
|
| 727 |
+
cmasks = cmasks.squeeze(0)
|
| 728 |
+
vfeats = vfeats.squeeze(0)
|
| 729 |
+
vmasks = vmasks.squeeze(0)
|
| 730 |
+
attention_mask = attention_mask.squeeze(0) if attention_mask is not None else None
|
| 731 |
+
|
| 732 |
+
# TODO (huxu): other ways to do negative examples; move the following
|
| 733 |
+
# into your criterion forward.
|
| 734 |
+
output_hidden_states = True
|
| 735 |
+
|
| 736 |
+
# a len1 dummy video token.
|
| 737 |
+
dummy_vfeats = torch.zeros(
|
| 738 |
+
(caps.size(0), 1, vfeats.size(-1)), device=vfeats.device, dtype=vfeats.dtype)
|
| 739 |
+
dummy_vmasks = torch.ones(
|
| 740 |
+
(caps.size(0), 1), dtype=torch.bool,
|
| 741 |
+
device=vfeats.device)
|
| 742 |
+
|
| 743 |
+
dummy_caps = torch.LongTensor(
|
| 744 |
+
[[self.cls_token_id, self.sep_token_id,
|
| 745 |
+
self.pad_token_id, self.sep_token_id]],
|
| 746 |
+
).to(caps.device).repeat(vfeats.size(0), 1)
|
| 747 |
+
dummy_cmasks = torch.BoolTensor(
|
| 748 |
+
[[0, 1, 0, 1]] # pad are valid for attention.
|
| 749 |
+
).to(caps.device).repeat(vfeats.size(0), 1)
|
| 750 |
+
|
| 751 |
+
# video forwarding, text is dummy; never use attention_mask.
|
| 752 |
+
attention_mask, token_type_ids = self._mm_on_the_fly(
|
| 753 |
+
dummy_cmasks, vmasks, None)
|
| 754 |
+
|
| 755 |
+
outputs = self.mm_encoder(
|
| 756 |
+
input_ids=dummy_caps,
|
| 757 |
+
input_video_embeds=vfeats,
|
| 758 |
+
attention_mask=attention_mask,
|
| 759 |
+
token_type_ids=token_type_ids,
|
| 760 |
+
output_hidden_states=output_hidden_states,
|
| 761 |
+
)
|
| 762 |
+
|
| 763 |
+
layer_idx = self.last_iso_layer \
|
| 764 |
+
if self.last_iso_layer > 0 else self.num_hidden_layers
|
| 765 |
+
|
| 766 |
+
video_seq = outputs[2][layer_idx][:, 1:vmasks.size(1)+1].masked_select(
|
| 767 |
+
vmasks.unsqueeze(-1)
|
| 768 |
+
).view(-1, self.hidden_size)
|
| 769 |
+
|
| 770 |
+
# text forwarding, video is dummy
|
| 771 |
+
attention_mask, token_type_ids = self._mm_on_the_fly(
|
| 772 |
+
cmasks, dummy_vmasks, None)
|
| 773 |
+
|
| 774 |
+
outputs = self.mm_encoder(
|
| 775 |
+
input_ids=caps,
|
| 776 |
+
input_video_embeds=dummy_vfeats,
|
| 777 |
+
attention_mask=attention_mask,
|
| 778 |
+
token_type_ids=token_type_ids,
|
| 779 |
+
output_hidden_states=output_hidden_states,
|
| 780 |
+
)
|
| 781 |
+
|
| 782 |
+
_, pooled_text = self._pooling_vt_layer(
|
| 783 |
+
outputs[2], cmasks, dummy_vmasks)
|
| 784 |
+
# this line is not right.
|
| 785 |
+
logits = torch.mm(video_seq, pooled_text.transpose(1, 0))
|
| 786 |
+
return {"logits": logits}
|
| 787 |
+
|
| 788 |
+
|
| 789 |
+
# --------------- MMFusionSeparate for end tasks ---------------
|
| 790 |
+
|
| 791 |
+
class MMFusionSeparateActionSegmentation(MMFusionSeparate):
|
| 792 |
+
"""Fine-tuning wrapper for action segmentation."""
|
| 793 |
+
def forward(
|
| 794 |
+
self,
|
| 795 |
+
caps,
|
| 796 |
+
cmasks,
|
| 797 |
+
vfeats,
|
| 798 |
+
vmasks,
|
| 799 |
+
attention_mask=None,
|
| 800 |
+
**kwargs
|
| 801 |
+
):
|
| 802 |
+
# ActionLocalization assume of batch_size=1, squeeze it.
|
| 803 |
+
caps = caps.view(-1, caps.size(-1))
|
| 804 |
+
cmasks = cmasks.view(-1, cmasks.size(-1))
|
| 805 |
+
vfeats = vfeats.view(-1, vfeats.size(2), vfeats.size(3))
|
| 806 |
+
vmasks = vmasks.view(-1, vmasks.size(-1))
|
| 807 |
+
logits = self.forward_video(
|
| 808 |
+
vfeats,
|
| 809 |
+
vmasks,
|
| 810 |
+
caps,
|
| 811 |
+
cmasks,
|
| 812 |
+
output_hidden_states=True
|
| 813 |
+
)
|
| 814 |
+
return {"logits": logits[:, 1:vmasks.size(1)+1]}
|
| 815 |
+
|
| 816 |
+
|
| 817 |
+
class MMFusionSeparateActionLocalization(MMFusionSeparate):
|
| 818 |
+
def __init__(self, config, **kwargs):
|
| 819 |
+
super().__init__(config)
|
| 820 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 821 |
+
config.dataset.bert_name)
|
| 822 |
+
self.cls_token_id = tokenizer.cls_token_id
|
| 823 |
+
self.sep_token_id = tokenizer.sep_token_id
|
| 824 |
+
self.pad_token_id = tokenizer.pad_token_id
|
| 825 |
+
|
| 826 |
+
def forward(
|
| 827 |
+
self,
|
| 828 |
+
caps,
|
| 829 |
+
cmasks,
|
| 830 |
+
vfeats,
|
| 831 |
+
vmasks,
|
| 832 |
+
**kwargs
|
| 833 |
+
):
|
| 834 |
+
# ActionLocalization assume of batch_size=1, squeeze it.
|
| 835 |
+
caps = caps.squeeze(0)
|
| 836 |
+
cmasks = cmasks.squeeze(0)
|
| 837 |
+
vfeats = vfeats.squeeze(0)
|
| 838 |
+
vmasks = vmasks.squeeze(0)
|
| 839 |
+
|
| 840 |
+
# TODO (huxu): other ways to do negative examples; move the following
|
| 841 |
+
# into your criterion forward.
|
| 842 |
+
dummy_caps = torch.LongTensor(
|
| 843 |
+
[[self.cls_token_id, self.sep_token_id,
|
| 844 |
+
self.pad_token_id, self.sep_token_id]],
|
| 845 |
+
).to(caps.device).repeat(vfeats.size(0), 1)
|
| 846 |
+
dummy_cmasks = torch.BoolTensor(
|
| 847 |
+
[[0, 1, 0, 1]] # pad are valid for attention.
|
| 848 |
+
).to(caps.device).repeat(vfeats.size(0), 1)
|
| 849 |
+
|
| 850 |
+
outputs = self.forward_video(
|
| 851 |
+
vfeats,
|
| 852 |
+
vmasks,
|
| 853 |
+
dummy_caps,
|
| 854 |
+
dummy_cmasks,
|
| 855 |
+
output_hidden_states=True
|
| 856 |
+
)
|
| 857 |
+
|
| 858 |
+
video_seq = outputs[:, 1:vmasks.size(1)+1].masked_select(
|
| 859 |
+
vmasks.unsqueeze(-1)
|
| 860 |
+
).view(-1, self.hidden_size)
|
| 861 |
+
|
| 862 |
+
pooled_text = self.forward_text(
|
| 863 |
+
caps,
|
| 864 |
+
cmasks,
|
| 865 |
+
output_hidden_states=False
|
| 866 |
+
)
|
| 867 |
+
|
| 868 |
+
# this line is not right.
|
| 869 |
+
logits = torch.mm(video_seq, pooled_text.transpose(1, 0))
|
| 870 |
+
return {"logits": logits}
|
| 871 |
+
|
| 872 |
+
|
| 873 |
+
class MMFusionShareActionLocalization(MMFusionShare):
|
| 874 |
+
def __init__(self, config, **kwargs):
|
| 875 |
+
super().__init__(config)
|
| 876 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 877 |
+
config.dataset.bert_name)
|
| 878 |
+
self.cls_token_id = tokenizer.cls_token_id
|
| 879 |
+
self.sep_token_id = tokenizer.sep_token_id
|
| 880 |
+
self.pad_token_id = tokenizer.pad_token_id
|
| 881 |
+
|
| 882 |
+
def forward(
|
| 883 |
+
self,
|
| 884 |
+
caps,
|
| 885 |
+
cmasks,
|
| 886 |
+
vfeats,
|
| 887 |
+
vmasks,
|
| 888 |
+
**kwargs
|
| 889 |
+
):
|
| 890 |
+
# ActionLocalization assume of batch_size=1, squeeze it.
|
| 891 |
+
caps = caps.squeeze(0)
|
| 892 |
+
cmasks = cmasks.squeeze(0)
|
| 893 |
+
vfeats = vfeats.squeeze(0)
|
| 894 |
+
vmasks = vmasks.squeeze(0)
|
| 895 |
+
|
| 896 |
+
# TODO (huxu): other ways to do negative examples; move the following
|
| 897 |
+
# into your criterion forward.
|
| 898 |
+
dummy_caps = torch.LongTensor(
|
| 899 |
+
[[self.cls_token_id, self.sep_token_id,
|
| 900 |
+
self.pad_token_id, self.sep_token_id]],
|
| 901 |
+
).to(caps.device).repeat(vfeats.size(0), 1)
|
| 902 |
+
dummy_cmasks = torch.BoolTensor(
|
| 903 |
+
[[0, 1, 0, 1]] # pad are valid for attention.
|
| 904 |
+
).to(caps.device).repeat(vfeats.size(0), 1)
|
| 905 |
+
|
| 906 |
+
outputs = self.forward_video(
|
| 907 |
+
vfeats,
|
| 908 |
+
vmasks,
|
| 909 |
+
dummy_caps,
|
| 910 |
+
dummy_cmasks,
|
| 911 |
+
output_hidden_states=True
|
| 912 |
+
)
|
| 913 |
+
|
| 914 |
+
video_seq = outputs[:, 1:vmasks.size(1)+1].masked_select(
|
| 915 |
+
vmasks.unsqueeze(-1)
|
| 916 |
+
).view(-1, self.hidden_size)
|
| 917 |
+
|
| 918 |
+
pooled_text = self.forward_text(
|
| 919 |
+
caps,
|
| 920 |
+
cmasks,
|
| 921 |
+
output_hidden_states=False
|
| 922 |
+
)
|
| 923 |
+
|
| 924 |
+
# this line is not right.
|
| 925 |
+
logits = torch.mm(video_seq, pooled_text.transpose(1, 0))
|
| 926 |
+
return {"logits": logits}
|
data/fairseq/examples/MMPT/mmpt/models/mmfusionnlg.py
ADDED
|
@@ -0,0 +1,999 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2018 The Google AI Language Team Authors, Facebook AI Research authors and The HuggingFace Inc. team.
|
| 3 |
+
# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved.
|
| 4 |
+
#
|
| 5 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 6 |
+
# you may not use this file except in compliance with the License.
|
| 7 |
+
# You may obtain a copy of the License at
|
| 8 |
+
#
|
| 9 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 10 |
+
#
|
| 11 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 12 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 13 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 14 |
+
# See the License for the specific language governing permissions and
|
| 15 |
+
# limitations under the License.
|
| 16 |
+
# Copyright (c) Facebook, Inc. All Rights Reserved
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
import torch
|
| 20 |
+
|
| 21 |
+
from torch.nn import functional as F
|
| 22 |
+
|
| 23 |
+
from typing import Optional, Iterable
|
| 24 |
+
|
| 25 |
+
try:
|
| 26 |
+
from transformers import BertPreTrainedModel
|
| 27 |
+
from transformers.modeling_bert import BertOnlyMLMHead
|
| 28 |
+
|
| 29 |
+
from transformers.file_utils import ModelOutput
|
| 30 |
+
from transformers.modeling_outputs import CausalLMOutput
|
| 31 |
+
from transformers.generation_utils import (
|
| 32 |
+
BeamHypotheses,
|
| 33 |
+
top_k_top_p_filtering
|
| 34 |
+
)
|
| 35 |
+
except ImportError:
|
| 36 |
+
pass
|
| 37 |
+
|
| 38 |
+
from .mmfusion import MMFusion
|
| 39 |
+
from .transformermodel import MMBertModel
|
| 40 |
+
from ..modules import VideoTokenMLP
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class MMFusionNLG(MMFusion):
|
| 44 |
+
def __init__(self, config, **kwargs):
|
| 45 |
+
super().__init__(config)
|
| 46 |
+
if config.model.max_decode_length is not None:
|
| 47 |
+
self.max_length = min(
|
| 48 |
+
config.model.max_decode_length,
|
| 49 |
+
config.dataset.max_len - config.dataset.max_video_len - 3
|
| 50 |
+
)
|
| 51 |
+
else:
|
| 52 |
+
self.max_length = \
|
| 53 |
+
config.dataset.max_len - config.dataset.max_video_len - 3
|
| 54 |
+
self.gen_param = config.gen_param if config.gen_param is not None \
|
| 55 |
+
else {}
|
| 56 |
+
|
| 57 |
+
def forward(
|
| 58 |
+
self,
|
| 59 |
+
caps,
|
| 60 |
+
cmasks,
|
| 61 |
+
vfeats,
|
| 62 |
+
vmasks,
|
| 63 |
+
attention_mask,
|
| 64 |
+
video_label=None,
|
| 65 |
+
text_label=None,
|
| 66 |
+
**kwargs
|
| 67 |
+
):
|
| 68 |
+
"""use pre-trained LM header for generation."""
|
| 69 |
+
attention_mask, token_type_ids = self._mm_on_the_fly(
|
| 70 |
+
cmasks, vmasks, attention_mask)
|
| 71 |
+
|
| 72 |
+
outputs = self.mm_encoder(
|
| 73 |
+
input_ids=caps,
|
| 74 |
+
input_video_embeds=vfeats,
|
| 75 |
+
attention_mask=attention_mask,
|
| 76 |
+
token_type_ids=token_type_ids,
|
| 77 |
+
masked_lm_labels=text_label,
|
| 78 |
+
)
|
| 79 |
+
return {"logits": outputs[0]}
|
| 80 |
+
|
| 81 |
+
@torch.no_grad()
|
| 82 |
+
def generate(
|
| 83 |
+
self,
|
| 84 |
+
caps, cmasks, vfeats, vmasks,
|
| 85 |
+
attention_mask=None,
|
| 86 |
+
bos_token_id=None,
|
| 87 |
+
eos_token_id=None,
|
| 88 |
+
**kwargs
|
| 89 |
+
):
|
| 90 |
+
# a simplified interface from
|
| 91 |
+
# https://huggingface.co/transformers/v3.4.0/_modules/transformers/generation_utils.html#GenerationMixin.generate
|
| 92 |
+
|
| 93 |
+
# caps now only have
|
| 94 |
+
# [CLS], [SEP] (for video) and [CLS] (as bos_token)
|
| 95 |
+
assert caps.size(1) == 3
|
| 96 |
+
|
| 97 |
+
attention_mask, token_type_ids = self._mm_on_the_fly(
|
| 98 |
+
cmasks, vmasks, attention_mask)
|
| 99 |
+
|
| 100 |
+
output = self.mm_encoder.generate(
|
| 101 |
+
input_ids=caps,
|
| 102 |
+
input_video_embeds=vfeats,
|
| 103 |
+
attention_mask=attention_mask,
|
| 104 |
+
token_type_ids=token_type_ids,
|
| 105 |
+
bos_token_id=bos_token_id,
|
| 106 |
+
eos_token_id=eos_token_id,
|
| 107 |
+
max_length=self.max_length,
|
| 108 |
+
**self.gen_param
|
| 109 |
+
)
|
| 110 |
+
return output
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
class MMBertForNLG(BertPreTrainedModel):
|
| 114 |
+
def __init__(self, config):
|
| 115 |
+
super().__init__(config)
|
| 116 |
+
self.bert = MMBertModel(config)
|
| 117 |
+
self.videomlp = VideoTokenMLP(config)
|
| 118 |
+
# we do not use `BertGenerationOnlyLMHead`
|
| 119 |
+
# because we can reuse pretraining.
|
| 120 |
+
self.cls = BertOnlyMLMHead(config)
|
| 121 |
+
self.hidden_size = config.hidden_size
|
| 122 |
+
self.init_weights()
|
| 123 |
+
|
| 124 |
+
def get_output_embeddings(self):
|
| 125 |
+
return self.cls.predictions.decoder
|
| 126 |
+
|
| 127 |
+
def forward(
|
| 128 |
+
self,
|
| 129 |
+
input_ids=None,
|
| 130 |
+
input_video_embeds=None,
|
| 131 |
+
attention_mask=None,
|
| 132 |
+
token_type_ids=None,
|
| 133 |
+
position_ids=None,
|
| 134 |
+
head_mask=None,
|
| 135 |
+
inputs_embeds=None,
|
| 136 |
+
masked_lm_labels=None,
|
| 137 |
+
output_attentions=None,
|
| 138 |
+
output_hidden_states=None,
|
| 139 |
+
return_dict=None,
|
| 140 |
+
):
|
| 141 |
+
# similar to MMBertForMFMMLM without MFM.
|
| 142 |
+
video_tokens = self.videomlp(input_video_embeds)
|
| 143 |
+
outputs = self.bert(
|
| 144 |
+
input_ids,
|
| 145 |
+
video_tokens,
|
| 146 |
+
attention_mask=attention_mask,
|
| 147 |
+
token_type_ids=token_type_ids,
|
| 148 |
+
position_ids=position_ids,
|
| 149 |
+
head_mask=head_mask,
|
| 150 |
+
inputs_embeds=inputs_embeds,
|
| 151 |
+
output_attentions=output_attentions,
|
| 152 |
+
output_hidden_states=output_hidden_states,
|
| 153 |
+
return_dict=return_dict,
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
sequence_output = outputs[0]
|
| 157 |
+
|
| 158 |
+
prediction_scores = None
|
| 159 |
+
if masked_lm_labels is not None:
|
| 160 |
+
text_offset = input_video_embeds.size(1) + 1 # [CLS]
|
| 161 |
+
# recover caps format: [CLS] [SEP] text [SEP]
|
| 162 |
+
text_sequence_output = torch.cat(
|
| 163 |
+
[sequence_output[:, :1], sequence_output[:, text_offset:]],
|
| 164 |
+
dim=1
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
# only compute select tokens to training to speed up.
|
| 168 |
+
hidden_size = text_sequence_output.size(-1)
|
| 169 |
+
# masked_lm_labels = masked_lm_labels.reshape(-1)
|
| 170 |
+
labels_mask = masked_lm_labels != -100
|
| 171 |
+
|
| 172 |
+
selected_text_output = text_sequence_output.masked_select(
|
| 173 |
+
labels_mask.unsqueeze(-1)
|
| 174 |
+
).view(-1, hidden_size)
|
| 175 |
+
prediction_scores = self.cls(selected_text_output)
|
| 176 |
+
|
| 177 |
+
if not return_dict:
|
| 178 |
+
output = (
|
| 179 |
+
prediction_scores,
|
| 180 |
+
) + outputs[2:]
|
| 181 |
+
return output
|
| 182 |
+
|
| 183 |
+
# for generation.
|
| 184 |
+
text_offset = input_video_embeds.size(1) + 2 # [CLS]
|
| 185 |
+
text_sequence_output = sequence_output[:, text_offset:]
|
| 186 |
+
prediction_scores = self.cls(text_sequence_output)
|
| 187 |
+
return CausalLMOutput(
|
| 188 |
+
loss=None,
|
| 189 |
+
logits=prediction_scores,
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
def prepare_inputs_for_generation(
|
| 193 |
+
self,
|
| 194 |
+
input_ids,
|
| 195 |
+
input_video_embeds,
|
| 196 |
+
attention_mask=None,
|
| 197 |
+
token_type_ids=None,
|
| 198 |
+
**model_kwargs
|
| 199 |
+
):
|
| 200 |
+
# must return a dictionary.
|
| 201 |
+
seq_len = input_ids.size(1) + input_video_embeds.size(1)
|
| 202 |
+
if attention_mask is not None:
|
| 203 |
+
if len(attention_mask.size()) == 4:
|
| 204 |
+
attention_mask = attention_mask[:, :, :seq_len, :seq_len]
|
| 205 |
+
elif len(attention_mask.size()) == 3:
|
| 206 |
+
attention_mask = attention_mask[:, :seq_len, :seq_len]
|
| 207 |
+
else:
|
| 208 |
+
attention_mask = attention_mask[:, :seq_len]
|
| 209 |
+
if token_type_ids is not None:
|
| 210 |
+
token_type_ids = token_type_ids[:, :seq_len]
|
| 211 |
+
|
| 212 |
+
return {
|
| 213 |
+
"input_ids": input_ids,
|
| 214 |
+
"input_video_embeds": input_video_embeds,
|
| 215 |
+
"attention_mask": attention_mask,
|
| 216 |
+
"token_type_ids": token_type_ids,
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
@torch.no_grad()
|
| 220 |
+
def generate(
|
| 221 |
+
self,
|
| 222 |
+
input_ids: Optional[torch.LongTensor] = None,
|
| 223 |
+
decoder_input_ids: Optional[torch.LongTensor] = None,
|
| 224 |
+
max_length: Optional[int] = None,
|
| 225 |
+
min_length: Optional[int] = None,
|
| 226 |
+
do_sample: Optional[bool] = None,
|
| 227 |
+
early_stopping: Optional[bool] = None,
|
| 228 |
+
num_beams: Optional[int] = None,
|
| 229 |
+
temperature: Optional[float] = None,
|
| 230 |
+
top_k: Optional[int] = None,
|
| 231 |
+
top_p: Optional[float] = None,
|
| 232 |
+
repetition_penalty: Optional[float] = None,
|
| 233 |
+
bad_words_ids: Optional[Iterable[int]] = None,
|
| 234 |
+
bos_token_id: Optional[int] = None,
|
| 235 |
+
pad_token_id: Optional[int] = None,
|
| 236 |
+
eos_token_id: Optional[int] = None,
|
| 237 |
+
length_penalty: Optional[float] = None,
|
| 238 |
+
no_repeat_ngram_size: Optional[int] = None,
|
| 239 |
+
num_return_sequences: Optional[int] = None,
|
| 240 |
+
attention_mask: Optional[torch.LongTensor] = None,
|
| 241 |
+
decoder_start_token_id: Optional[int] = None,
|
| 242 |
+
use_cache: Optional[bool] = None,
|
| 243 |
+
**model_kwargs
|
| 244 |
+
) -> torch.LongTensor:
|
| 245 |
+
r"""
|
| 246 |
+
Generates sequences for models with a language modeling head. The method currently supports greedy decoding,
|
| 247 |
+
beam-search decoding, sampling with temperature, sampling with top-k or nucleus sampling.
|
| 248 |
+
Adapted in part from `Facebook's XLM beam search code
|
| 249 |
+
<https://github.com/facebookresearch/XLM/blob/9e6f6814d17be4fe5b15f2e6c43eb2b2d76daeb4/src/model/transformer.py#L529>`__.
|
| 250 |
+
Apart from :obj:`input_ids` and :obj:`attention_mask`, all the arguments below will default to the value of the
|
| 251 |
+
attribute of the same name inside the :class:`~transformers.PretrainedConfig` of the model. The default values
|
| 252 |
+
indicated are the default values of those config.
|
| 253 |
+
Most of these parameters are explained in more detail in `this blog post
|
| 254 |
+
<https://huggingface.co/blog/how-to-generate>`__.
|
| 255 |
+
Parameters:
|
| 256 |
+
input_ids (:obj:`torch.LongTensor` of shape :obj:`(batch_size, sequence_length)`, `optional`):
|
| 257 |
+
The sequence used as a prompt for the generation. If :obj:`None` the method initializes
|
| 258 |
+
it as an empty :obj:`torch.LongTensor` of shape :obj:`(1,)`.
|
| 259 |
+
decoder_input_ids (:obj:`torch.LongTensor` of shape :obj:`(batch_size, sequence_length)`, `optional`):
|
| 260 |
+
initial input_ids for the decoder of encoder-decoder type models. If :obj:`None` then only
|
| 261 |
+
decoder_start_token_id is passed as the first token to the decoder.
|
| 262 |
+
max_length (:obj:`int`, `optional`, defaults to 20):
|
| 263 |
+
The maximum length of the sequence to be generated.
|
| 264 |
+
min_length (:obj:`int`, `optional`, defaults to 10):
|
| 265 |
+
The minimum length of the sequence to be generated.
|
| 266 |
+
do_sample (:obj:`bool`, `optional`, defaults to :obj:`False`):
|
| 267 |
+
Whether or not to use sampling ; use greedy decoding otherwise.
|
| 268 |
+
early_stopping (:obj:`bool`, `optional`, defaults to :obj:`False`):
|
| 269 |
+
Whether to stop the beam search when at least ``num_beams`` sentences are finished per batch or not.
|
| 270 |
+
num_beams (:obj:`int`, `optional`, defaults to 1):
|
| 271 |
+
Number of beams for beam search. 1 means no beam search.
|
| 272 |
+
temperature (:obj:`float`, `optional`, defaults tp 1.0):
|
| 273 |
+
The value used to module the next token probabilities.
|
| 274 |
+
top_k (:obj:`int`, `optional`, defaults to 50):
|
| 275 |
+
The number of highest probability vocabulary tokens to keep for top-k-filtering.
|
| 276 |
+
top_p (:obj:`float`, `optional`, defaults to 1.0):
|
| 277 |
+
If set to float < 1, only the most probable tokens with probabilities that add up to ``top_p`` or
|
| 278 |
+
higher are kept for generation.
|
| 279 |
+
repetition_penalty (:obj:`float`, `optional`, defaults to 1.0):
|
| 280 |
+
The parameter for repetition penalty. 1.0 means no penalty. See `this paper
|
| 281 |
+
<https://arxiv.org/pdf/1909.05858.pdf>`__ for more details.
|
| 282 |
+
pad_token_id (:obj:`int`, `optional`):
|
| 283 |
+
The id of the `padding` token.
|
| 284 |
+
bos_token_id (:obj:`int`, `optional`):
|
| 285 |
+
The id of the `beginning-of-sequence` token.
|
| 286 |
+
eos_token_id (:obj:`int`, `optional`):
|
| 287 |
+
The id of the `end-of-sequence` token.
|
| 288 |
+
length_penalty (:obj:`float`, `optional`, defaults to 1.0):
|
| 289 |
+
Exponential penalty to the length. 1.0 means no penalty.
|
| 290 |
+
Set to values < 1.0 in order to encourage the model to generate shorter sequences, to a value > 1.0 in
|
| 291 |
+
order to encourage the model to produce longer sequences.
|
| 292 |
+
no_repeat_ngram_size (:obj:`int`, `optional`, defaults to 0):
|
| 293 |
+
If set to int > 0, all ngrams of that size can only occur once.
|
| 294 |
+
bad_words_ids(:obj:`List[int]`, `optional`):
|
| 295 |
+
List of token ids that are not allowed to be generated. In order to get the tokens of the words that
|
| 296 |
+
should not appear in the generated text, use :obj:`tokenizer.encode(bad_word, add_prefix_space=True)`.
|
| 297 |
+
num_return_sequences(:obj:`int`, `optional`, defaults to 1):
|
| 298 |
+
The number of independently computed returned sequences for each element in the batch.
|
| 299 |
+
attention_mask (:obj:`torch.LongTensor` of shape :obj:`(batch_size, sequence_length)`, `optional`):
|
| 300 |
+
Mask to avoid performing attention on padding token indices. Mask values are in ``[0, 1]``, 1 for
|
| 301 |
+
tokens that are not masked, and 0 for masked tokens.
|
| 302 |
+
If not provided, will default to a tensor the same shape as :obj:`input_ids` that masks the pad token.
|
| 303 |
+
`What are attention masks? <../glossary.html#attention-mask>`__
|
| 304 |
+
decoder_start_token_id (:obj:`int`, `optional`):
|
| 305 |
+
If an encoder-decoder model starts decoding with a different token than `bos`, the id of that token.
|
| 306 |
+
use_cache: (:obj:`bool`, `optional`, defaults to :obj:`True`):
|
| 307 |
+
Whether or not the model should use the past last key/values attentions (if applicable to the model) to
|
| 308 |
+
speed up decoding.
|
| 309 |
+
model_kwargs:
|
| 310 |
+
Additional model specific kwargs will be forwarded to the :obj:`forward` function of the model.
|
| 311 |
+
Return:
|
| 312 |
+
:obj:`torch.LongTensor` of shape :obj:`(batch_size * num_return_sequences, sequence_length)`:
|
| 313 |
+
The generated sequences. The second dimension (sequence_length) is either equal to :obj:`max_length` or
|
| 314 |
+
shorter if all batches finished early due to the :obj:`eos_token_id`.
|
| 315 |
+
Examples::
|
| 316 |
+
tokenizer = AutoTokenizer.from_pretrained('distilgpt2') # Initialize tokenizer
|
| 317 |
+
model = AutoModelWithLMHead.from_pretrained('distilgpt2') # Download model and configuration from S3 and cache.
|
| 318 |
+
outputs = model.generate(max_length=40) # do greedy decoding
|
| 319 |
+
print('Generated: {}'.format(tokenizer.decode(outputs[0], skip_special_tokens=True)))
|
| 320 |
+
tokenizer = AutoTokenizer.from_pretrained('openai-gpt') # Initialize tokenizer
|
| 321 |
+
model = AutoModelWithLMHead.from_pretrained('openai-gpt') # Download model and configuration from S3 and cache.
|
| 322 |
+
input_context = 'The dog'
|
| 323 |
+
input_ids = tokenizer.encode(input_context, return_tensors='pt') # encode input context
|
| 324 |
+
outputs = model.generate(input_ids=input_ids, num_beams=5, num_return_sequences=3, temperature=1.5) # generate 3 independent sequences using beam search decoding (5 beams) with sampling from initial context 'The dog'
|
| 325 |
+
for i in range(3): # 3 output sequences were generated
|
| 326 |
+
print('Generated {}: {}'.format(i, tokenizer.decode(outputs[i], skip_special_tokens=True)))
|
| 327 |
+
tokenizer = AutoTokenizer.from_pretrained('distilgpt2') # Initialize tokenizer
|
| 328 |
+
model = AutoModelWithLMHead.from_pretrained('distilgpt2') # Download model and configuration from S3 and cache.
|
| 329 |
+
input_context = 'The dog'
|
| 330 |
+
input_ids = tokenizer.encode(input_context, return_tensors='pt') # encode input context
|
| 331 |
+
outputs = model.generate(input_ids=input_ids, max_length=40, temperature=0.7, num_return_sequences=3, do_sample=True) # generate 3 candidates using sampling
|
| 332 |
+
for i in range(3): # 3 output sequences were generated
|
| 333 |
+
print('Generated {}: {}'.format(i, tokenizer.decode(outputs[i], skip_special_tokens=True)))
|
| 334 |
+
tokenizer = AutoTokenizer.from_pretrained('ctrl') # Initialize tokenizer
|
| 335 |
+
model = AutoModelWithLMHead.from_pretrained('ctrl') # Download model and configuration from S3 and cache.
|
| 336 |
+
input_context = 'Legal My neighbor is' # "Legal" is one of the control codes for ctrl
|
| 337 |
+
input_ids = tokenizer.encode(input_context, return_tensors='pt') # encode input context
|
| 338 |
+
outputs = model.generate(input_ids=input_ids, max_length=50, temperature=0.7, repetition_penalty=1.2) # generate sequences
|
| 339 |
+
print('Generated: {}'.format(tokenizer.decode(outputs[0], skip_special_tokens=True)))
|
| 340 |
+
tokenizer = AutoTokenizer.from_pretrained('gpt2') # Initialize tokenizer
|
| 341 |
+
model = AutoModelWithLMHead.from_pretrained('gpt2') # Download model and configuration from S3 and cache.
|
| 342 |
+
input_context = 'My cute dog' # "Legal" is one of the control codes for ctrl
|
| 343 |
+
bad_words_ids = [tokenizer.encode(bad_word, add_prefix_space=True) for bad_word in ['idiot', 'stupid', 'shut up']]
|
| 344 |
+
input_ids = tokenizer.encode(input_context, return_tensors='pt') # encode input context
|
| 345 |
+
outputs = model.generate(input_ids=input_ids, max_length=100, do_sample=True, bad_words_ids=bad_words_ids) # generate sequences without allowing bad_words to be generated
|
| 346 |
+
"""
|
| 347 |
+
|
| 348 |
+
# We cannot generate if the model does not have a LM head
|
| 349 |
+
if self.get_output_embeddings() is None:
|
| 350 |
+
raise AttributeError(
|
| 351 |
+
"You tried to generate sequences with a model that does not have a LM Head."
|
| 352 |
+
"Please use another model class (e.g. `OpenAIGPTLMHeadModel`, `XLNetLMHeadModel`, `GPT2LMHeadModel`, `CTRLLMHeadModel`, `T5WithLMHeadModel`, `TransfoXLLMHeadModel`, `XLMWithLMHeadModel`, `BartForConditionalGeneration` )"
|
| 353 |
+
)
|
| 354 |
+
|
| 355 |
+
max_length = max_length if max_length is not None else self.config.max_length
|
| 356 |
+
min_length = min_length if min_length is not None else self.config.min_length
|
| 357 |
+
do_sample = do_sample if do_sample is not None else self.config.do_sample
|
| 358 |
+
early_stopping = early_stopping if early_stopping is not None else self.config.early_stopping
|
| 359 |
+
use_cache = use_cache if use_cache is not None else self.config.use_cache
|
| 360 |
+
num_beams = num_beams if num_beams is not None else self.config.num_beams
|
| 361 |
+
temperature = temperature if temperature is not None else self.config.temperature
|
| 362 |
+
top_k = top_k if top_k is not None else self.config.top_k
|
| 363 |
+
top_p = top_p if top_p is not None else self.config.top_p
|
| 364 |
+
repetition_penalty = repetition_penalty if repetition_penalty is not None else self.config.repetition_penalty
|
| 365 |
+
bos_token_id = bos_token_id if bos_token_id is not None else self.config.bos_token_id
|
| 366 |
+
pad_token_id = pad_token_id if pad_token_id is not None else self.config.pad_token_id
|
| 367 |
+
eos_token_id = eos_token_id if eos_token_id is not None else self.config.eos_token_id
|
| 368 |
+
length_penalty = length_penalty if length_penalty is not None else self.config.length_penalty
|
| 369 |
+
no_repeat_ngram_size = (
|
| 370 |
+
no_repeat_ngram_size if no_repeat_ngram_size is not None else self.config.no_repeat_ngram_size
|
| 371 |
+
)
|
| 372 |
+
bad_words_ids = bad_words_ids if bad_words_ids is not None else self.config.bad_words_ids
|
| 373 |
+
num_return_sequences = (
|
| 374 |
+
num_return_sequences if num_return_sequences is not None else self.config.num_return_sequences
|
| 375 |
+
)
|
| 376 |
+
decoder_start_token_id = (
|
| 377 |
+
decoder_start_token_id if decoder_start_token_id is not None else self.config.decoder_start_token_id
|
| 378 |
+
)
|
| 379 |
+
|
| 380 |
+
if input_ids is not None:
|
| 381 |
+
batch_size = input_ids.shape[0] # overriden by the input batch_size
|
| 382 |
+
else:
|
| 383 |
+
batch_size = 1
|
| 384 |
+
|
| 385 |
+
assert isinstance(max_length, int) and max_length > 0, "`max_length` should be a strictly positive integer."
|
| 386 |
+
assert isinstance(min_length, int) and min_length >= 0, "`min_length` should be a positive integer."
|
| 387 |
+
assert isinstance(do_sample, bool), "`do_sample` should be a boolean."
|
| 388 |
+
assert isinstance(early_stopping, bool), "`early_stopping` should be a boolean."
|
| 389 |
+
assert isinstance(use_cache, bool), "`use_cache` should be a boolean."
|
| 390 |
+
assert isinstance(num_beams, int) and num_beams > 0, "`num_beams` should be a strictly positive integer."
|
| 391 |
+
assert temperature > 0, "`temperature` should be strictly positive."
|
| 392 |
+
assert isinstance(top_k, int) and top_k >= 0, "`top_k` should be a positive integer."
|
| 393 |
+
assert 0 <= top_p <= 1, "`top_p` should be between 0 and 1."
|
| 394 |
+
assert repetition_penalty >= 1.0, "`repetition_penalty` should be >= 1."
|
| 395 |
+
assert input_ids is not None or (
|
| 396 |
+
isinstance(bos_token_id, int) and bos_token_id >= 0
|
| 397 |
+
), "If input_ids is not defined, `bos_token_id` should be a positive integer."
|
| 398 |
+
assert pad_token_id is None or (
|
| 399 |
+
isinstance(pad_token_id, int) and (pad_token_id >= 0)
|
| 400 |
+
), "`pad_token_id` should be a positive integer."
|
| 401 |
+
assert (eos_token_id is None) or (
|
| 402 |
+
isinstance(eos_token_id, int) and (eos_token_id >= 0)
|
| 403 |
+
), "`eos_token_id` should be a positive integer."
|
| 404 |
+
assert length_penalty > 0, "`length_penalty` should be strictly positive."
|
| 405 |
+
assert (
|
| 406 |
+
isinstance(no_repeat_ngram_size, int) and no_repeat_ngram_size >= 0
|
| 407 |
+
), "`no_repeat_ngram_size` should be a positive integer."
|
| 408 |
+
assert (
|
| 409 |
+
isinstance(num_return_sequences, int) and num_return_sequences > 0
|
| 410 |
+
), "`num_return_sequences` should be a strictly positive integer."
|
| 411 |
+
assert (
|
| 412 |
+
bad_words_ids is None or isinstance(bad_words_ids, list) and isinstance(bad_words_ids[0], list)
|
| 413 |
+
), "`bad_words_ids` is either `None` or a list of lists of tokens that should not be generated"
|
| 414 |
+
|
| 415 |
+
if input_ids is None:
|
| 416 |
+
assert isinstance(bos_token_id, int) and bos_token_id >= 0, (
|
| 417 |
+
"you should either supply a context to complete as `input_ids` input "
|
| 418 |
+
"or a `bos_token_id` (integer >= 0) as a first token to start the generation."
|
| 419 |
+
)
|
| 420 |
+
input_ids = torch.full(
|
| 421 |
+
(batch_size, 1),
|
| 422 |
+
bos_token_id,
|
| 423 |
+
dtype=torch.long,
|
| 424 |
+
device=next(self.parameters()).device,
|
| 425 |
+
)
|
| 426 |
+
else:
|
| 427 |
+
assert input_ids.dim() == 2, "Input prompt should be of shape (batch_size, sequence length)."
|
| 428 |
+
|
| 429 |
+
# not allow to duplicate outputs when greedy decoding
|
| 430 |
+
if do_sample is False:
|
| 431 |
+
if num_beams == 1:
|
| 432 |
+
# no_beam_search greedy generation conditions
|
| 433 |
+
assert (
|
| 434 |
+
num_return_sequences == 1
|
| 435 |
+
), "Greedy decoding will always produce the same output for num_beams == 1 and num_return_sequences > 1. Please set num_return_sequences = 1"
|
| 436 |
+
|
| 437 |
+
else:
|
| 438 |
+
# beam_search greedy generation conditions
|
| 439 |
+
assert (
|
| 440 |
+
num_beams >= num_return_sequences
|
| 441 |
+
), "Greedy beam search decoding cannot return more sequences than it has beams. Please set num_beams >= num_return_sequences"
|
| 442 |
+
|
| 443 |
+
# create attention mask if necessary
|
| 444 |
+
# TODO (PVP): this should later be handled by the forward fn() in each model in the future see PR 3140
|
| 445 |
+
if (attention_mask is None) and (pad_token_id is not None) and (pad_token_id in input_ids):
|
| 446 |
+
attention_mask = input_ids.ne(pad_token_id).long()
|
| 447 |
+
elif attention_mask is None:
|
| 448 |
+
attention_mask = input_ids.new_ones(input_ids.shape)
|
| 449 |
+
|
| 450 |
+
# set pad_token_id to eos_token_id if not set. Important that this is done after
|
| 451 |
+
# attention_mask is created
|
| 452 |
+
if pad_token_id is None and eos_token_id is not None:
|
| 453 |
+
print(
|
| 454 |
+
"Setting `pad_token_id` to {} (first `eos_token_id`) to generate sequence".format(eos_token_id)
|
| 455 |
+
)
|
| 456 |
+
pad_token_id = eos_token_id
|
| 457 |
+
|
| 458 |
+
# vocab size
|
| 459 |
+
if hasattr(self.config, "vocab_size"):
|
| 460 |
+
vocab_size = self.config.vocab_size
|
| 461 |
+
elif (
|
| 462 |
+
self.config.is_encoder_decoder
|
| 463 |
+
and hasattr(self.config, "decoder")
|
| 464 |
+
and hasattr(self.config.decoder, "vocab_size")
|
| 465 |
+
):
|
| 466 |
+
vocab_size = self.config.decoder.vocab_size
|
| 467 |
+
else:
|
| 468 |
+
raise ValueError("either self.config.vocab_size or self.config.decoder.vocab_size needs to be defined")
|
| 469 |
+
|
| 470 |
+
# set effective batch size and effective batch multiplier according to do_sample
|
| 471 |
+
if do_sample:
|
| 472 |
+
effective_batch_size = batch_size * num_return_sequences
|
| 473 |
+
effective_batch_mult = num_return_sequences
|
| 474 |
+
else:
|
| 475 |
+
effective_batch_size = batch_size
|
| 476 |
+
effective_batch_mult = 1
|
| 477 |
+
|
| 478 |
+
if self.config.is_encoder_decoder:
|
| 479 |
+
if decoder_start_token_id is None:
|
| 480 |
+
# see if BOS token can be used for decoder_start_token_id
|
| 481 |
+
if bos_token_id is not None:
|
| 482 |
+
decoder_start_token_id = bos_token_id
|
| 483 |
+
elif (
|
| 484 |
+
hasattr(self.config, "decoder")
|
| 485 |
+
and hasattr(self.config.decoder, "bos_token_id")
|
| 486 |
+
and self.config.decoder.bos_token_id is not None
|
| 487 |
+
):
|
| 488 |
+
decoder_start_token_id = self.config.decoder.bos_token_id
|
| 489 |
+
else:
|
| 490 |
+
raise ValueError(
|
| 491 |
+
"decoder_start_token_id or bos_token_id has to be defined for encoder-decoder generation"
|
| 492 |
+
)
|
| 493 |
+
|
| 494 |
+
assert hasattr(self, "get_encoder"), "{} should have a 'get_encoder' function defined".format(self)
|
| 495 |
+
assert callable(self.get_encoder), "{} should be a method".format(self.get_encoder)
|
| 496 |
+
|
| 497 |
+
# get encoder and store encoder outputs
|
| 498 |
+
encoder = self.get_encoder()
|
| 499 |
+
encoder_outputs: ModelOutput = encoder(input_ids, attention_mask=attention_mask, return_dict=True)
|
| 500 |
+
|
| 501 |
+
# Expand input ids if num_beams > 1 or num_return_sequences > 1
|
| 502 |
+
if num_return_sequences > 1 or num_beams > 1:
|
| 503 |
+
# TODO: make this a call-back function.
|
| 504 |
+
# input_ids=caps,
|
| 505 |
+
# input_video_embeds=vfeats,
|
| 506 |
+
# attention_mask=attention_mask,
|
| 507 |
+
# token_type_ids=token_type_ids,
|
| 508 |
+
input_video_embeds = model_kwargs.pop("input_video_embeds", None)
|
| 509 |
+
token_type_ids = model_kwargs.pop("token_type_ids", None)
|
| 510 |
+
|
| 511 |
+
input_ids_len = input_ids.shape[-1]
|
| 512 |
+
input_ids = input_ids.unsqueeze(1).expand(
|
| 513 |
+
batch_size, effective_batch_mult * num_beams, input_ids_len)
|
| 514 |
+
|
| 515 |
+
input_video_embeds_len, input_video_embeds_hidden = input_video_embeds.size(1), input_video_embeds.size(2)
|
| 516 |
+
input_video_embeds = input_video_embeds.unsqueeze(1).expand(
|
| 517 |
+
batch_size, effective_batch_mult * num_beams, input_video_embeds_len, input_video_embeds_hidden)
|
| 518 |
+
|
| 519 |
+
attention_mask_from_len, attention_mask_to_len = attention_mask.size(1), attention_mask.size(2)
|
| 520 |
+
attention_mask = attention_mask.unsqueeze(1).expand(
|
| 521 |
+
batch_size, effective_batch_mult * num_beams, attention_mask_from_len, attention_mask_to_len
|
| 522 |
+
)
|
| 523 |
+
|
| 524 |
+
token_type_ids_len = token_type_ids.size(1)
|
| 525 |
+
token_type_ids = token_type_ids.unsqueeze(1).expand(
|
| 526 |
+
batch_size, effective_batch_mult * num_beams, token_type_ids_len
|
| 527 |
+
)
|
| 528 |
+
|
| 529 |
+
# contiguous ...
|
| 530 |
+
input_ids = input_ids.contiguous().view(
|
| 531 |
+
effective_batch_size * num_beams, input_ids_len
|
| 532 |
+
) # shape: (batch_size * num_return_sequences * num_beams, cur_len)
|
| 533 |
+
|
| 534 |
+
input_video_embeds = input_video_embeds.contiguous().view(
|
| 535 |
+
effective_batch_size * num_beams, input_video_embeds_len, input_video_embeds_hidden)
|
| 536 |
+
|
| 537 |
+
attention_mask = attention_mask.contiguous().view(
|
| 538 |
+
effective_batch_size * num_beams, attention_mask_from_len, attention_mask_to_len
|
| 539 |
+
) # shape: (batch_size * num_return_sequences * num_beams, cur_len)
|
| 540 |
+
|
| 541 |
+
token_type_ids = token_type_ids.contiguous().view(
|
| 542 |
+
effective_batch_size * num_beams, token_type_ids_len
|
| 543 |
+
)
|
| 544 |
+
|
| 545 |
+
model_kwargs["input_video_embeds"] = input_video_embeds
|
| 546 |
+
model_kwargs["token_type_ids"] = token_type_ids
|
| 547 |
+
|
| 548 |
+
if self.config.is_encoder_decoder:
|
| 549 |
+
device = next(self.parameters()).device
|
| 550 |
+
if decoder_input_ids is not None:
|
| 551 |
+
# give initial decoder input ids
|
| 552 |
+
input_ids = decoder_input_ids.repeat(effective_batch_size * num_beams, 1).to(device)
|
| 553 |
+
else:
|
| 554 |
+
# create empty decoder input_ids
|
| 555 |
+
input_ids = torch.full(
|
| 556 |
+
(effective_batch_size * num_beams, 1),
|
| 557 |
+
decoder_start_token_id,
|
| 558 |
+
dtype=torch.long,
|
| 559 |
+
device=device,
|
| 560 |
+
)
|
| 561 |
+
cur_len = input_ids.shape[-1]
|
| 562 |
+
|
| 563 |
+
assert (
|
| 564 |
+
batch_size == encoder_outputs.last_hidden_state.shape[0]
|
| 565 |
+
), f"expected encoder_outputs.last_hidden_state to have 1st dimension bs={batch_size}, got {encoder_outputs.last_hidden_state.shape[0]} "
|
| 566 |
+
|
| 567 |
+
# expand batch_idx to assign correct encoder output for expanded input_ids (due to num_beams > 1 and num_return_sequences > 1)
|
| 568 |
+
expanded_batch_idxs = (
|
| 569 |
+
torch.arange(batch_size)
|
| 570 |
+
.view(-1, 1)
|
| 571 |
+
.repeat(1, num_beams * effective_batch_mult)
|
| 572 |
+
.view(-1)
|
| 573 |
+
.to(input_ids.device)
|
| 574 |
+
)
|
| 575 |
+
|
| 576 |
+
# expand encoder_outputs
|
| 577 |
+
encoder_outputs["last_hidden_state"] = encoder_outputs.last_hidden_state.index_select(
|
| 578 |
+
0, expanded_batch_idxs
|
| 579 |
+
)
|
| 580 |
+
|
| 581 |
+
# save encoder_outputs in `model_kwargs`
|
| 582 |
+
model_kwargs["encoder_outputs"] = encoder_outputs
|
| 583 |
+
|
| 584 |
+
else:
|
| 585 |
+
cur_len = input_ids.shape[-1]
|
| 586 |
+
|
| 587 |
+
assert (
|
| 588 |
+
cur_len < max_length
|
| 589 |
+
), f"The context has {cur_len} number of tokens, but `max_length` is only {max_length}. Please make sure that `max_length` is bigger than the number of tokens, by setting either `generate(max_length=...,...)` or `config.max_length = ...`"
|
| 590 |
+
|
| 591 |
+
if num_beams > 1:
|
| 592 |
+
output = self._generate_beam_search(
|
| 593 |
+
input_ids,
|
| 594 |
+
cur_len=cur_len,
|
| 595 |
+
max_length=max_length,
|
| 596 |
+
min_length=min_length,
|
| 597 |
+
do_sample=do_sample,
|
| 598 |
+
early_stopping=early_stopping,
|
| 599 |
+
temperature=temperature,
|
| 600 |
+
top_k=top_k,
|
| 601 |
+
top_p=top_p,
|
| 602 |
+
repetition_penalty=repetition_penalty,
|
| 603 |
+
no_repeat_ngram_size=no_repeat_ngram_size,
|
| 604 |
+
bad_words_ids=bad_words_ids,
|
| 605 |
+
pad_token_id=pad_token_id,
|
| 606 |
+
eos_token_id=eos_token_id,
|
| 607 |
+
batch_size=effective_batch_size,
|
| 608 |
+
num_return_sequences=num_return_sequences,
|
| 609 |
+
length_penalty=length_penalty,
|
| 610 |
+
num_beams=num_beams,
|
| 611 |
+
vocab_size=vocab_size,
|
| 612 |
+
attention_mask=attention_mask,
|
| 613 |
+
use_cache=use_cache,
|
| 614 |
+
model_kwargs=model_kwargs,
|
| 615 |
+
)
|
| 616 |
+
else:
|
| 617 |
+
output = self._generate_no_beam_search(
|
| 618 |
+
input_ids,
|
| 619 |
+
cur_len=cur_len,
|
| 620 |
+
max_length=max_length,
|
| 621 |
+
min_length=min_length,
|
| 622 |
+
do_sample=do_sample,
|
| 623 |
+
temperature=temperature,
|
| 624 |
+
top_k=top_k,
|
| 625 |
+
top_p=top_p,
|
| 626 |
+
repetition_penalty=repetition_penalty,
|
| 627 |
+
no_repeat_ngram_size=no_repeat_ngram_size,
|
| 628 |
+
bad_words_ids=bad_words_ids,
|
| 629 |
+
pad_token_id=pad_token_id,
|
| 630 |
+
eos_token_id=eos_token_id,
|
| 631 |
+
batch_size=effective_batch_size,
|
| 632 |
+
attention_mask=attention_mask,
|
| 633 |
+
use_cache=use_cache,
|
| 634 |
+
model_kwargs=model_kwargs,
|
| 635 |
+
)
|
| 636 |
+
|
| 637 |
+
return output
|
| 638 |
+
|
| 639 |
+
def _generate_beam_search(
|
| 640 |
+
self,
|
| 641 |
+
input_ids,
|
| 642 |
+
cur_len,
|
| 643 |
+
max_length,
|
| 644 |
+
min_length,
|
| 645 |
+
do_sample,
|
| 646 |
+
early_stopping,
|
| 647 |
+
temperature,
|
| 648 |
+
top_k,
|
| 649 |
+
top_p,
|
| 650 |
+
repetition_penalty,
|
| 651 |
+
no_repeat_ngram_size,
|
| 652 |
+
bad_words_ids,
|
| 653 |
+
pad_token_id,
|
| 654 |
+
eos_token_id,
|
| 655 |
+
batch_size,
|
| 656 |
+
num_return_sequences,
|
| 657 |
+
length_penalty,
|
| 658 |
+
num_beams,
|
| 659 |
+
vocab_size,
|
| 660 |
+
attention_mask,
|
| 661 |
+
use_cache,
|
| 662 |
+
model_kwargs,
|
| 663 |
+
):
|
| 664 |
+
"""Generate sequences for each example with beam search."""
|
| 665 |
+
|
| 666 |
+
# generated hypotheses
|
| 667 |
+
generated_hyps = [
|
| 668 |
+
BeamHypotheses(num_beams, max_length, length_penalty, early_stopping=early_stopping)
|
| 669 |
+
for _ in range(batch_size)
|
| 670 |
+
]
|
| 671 |
+
|
| 672 |
+
# scores for each sentence in the beam
|
| 673 |
+
beam_scores = torch.zeros((batch_size, num_beams), dtype=torch.float, device=input_ids.device)
|
| 674 |
+
|
| 675 |
+
# for greedy decoding it is made sure that only tokens of the first beam are considered to avoid sampling the exact same tokens three times
|
| 676 |
+
if do_sample is False:
|
| 677 |
+
beam_scores[:, 1:] = -1e9
|
| 678 |
+
beam_scores = beam_scores.view(-1) # shape (batch_size * num_beams,)
|
| 679 |
+
|
| 680 |
+
# cache compute states
|
| 681 |
+
past = None
|
| 682 |
+
|
| 683 |
+
# done sentences
|
| 684 |
+
done = [False for _ in range(batch_size)]
|
| 685 |
+
|
| 686 |
+
while cur_len < max_length:
|
| 687 |
+
model_inputs = self.prepare_inputs_for_generation(
|
| 688 |
+
input_ids, past=past, attention_mask=attention_mask, use_cache=use_cache, **model_kwargs
|
| 689 |
+
)
|
| 690 |
+
outputs = self(**model_inputs, return_dict=True) # (batch_size * num_beams, cur_len, vocab_size)
|
| 691 |
+
next_token_logits = outputs.logits[:, -1, :] # (batch_size * num_beams, vocab_size)
|
| 692 |
+
|
| 693 |
+
# if model has past, then set the past variable to speed up decoding
|
| 694 |
+
if "past_key_values" in outputs:
|
| 695 |
+
past = outputs.past_key_values
|
| 696 |
+
elif "mems" in outputs:
|
| 697 |
+
past = outputs.mems
|
| 698 |
+
|
| 699 |
+
if self.config.is_encoder_decoder and do_sample is False:
|
| 700 |
+
# TODO (PVP) still a bit hacky here - there might be a better solution
|
| 701 |
+
next_token_logits = self.adjust_logits_during_generation(
|
| 702 |
+
next_token_logits, cur_len=cur_len, max_length=max_length
|
| 703 |
+
)
|
| 704 |
+
|
| 705 |
+
scores = F.log_softmax(next_token_logits, dim=-1) # (batch_size * num_beams, vocab_size)
|
| 706 |
+
|
| 707 |
+
scores = self.postprocess_next_token_scores(
|
| 708 |
+
scores=scores,
|
| 709 |
+
input_ids=input_ids,
|
| 710 |
+
no_repeat_ngram_size=no_repeat_ngram_size,
|
| 711 |
+
bad_words_ids=bad_words_ids,
|
| 712 |
+
cur_len=cur_len,
|
| 713 |
+
min_length=min_length,
|
| 714 |
+
max_length=max_length,
|
| 715 |
+
eos_token_id=eos_token_id,
|
| 716 |
+
repetition_penalty=repetition_penalty,
|
| 717 |
+
batch_size=batch_size,
|
| 718 |
+
num_beams=num_beams,
|
| 719 |
+
)
|
| 720 |
+
|
| 721 |
+
assert scores.shape == (batch_size * num_beams, vocab_size), "Shapes of scores: {} != {}".format(
|
| 722 |
+
scores.shape, (batch_size * num_beams, vocab_size)
|
| 723 |
+
)
|
| 724 |
+
|
| 725 |
+
if do_sample:
|
| 726 |
+
_scores = scores + beam_scores[:, None].expand_as(scores) # (batch_size * num_beams, vocab_size)
|
| 727 |
+
# Temperature
|
| 728 |
+
if temperature != 1.0:
|
| 729 |
+
_scores = _scores / temperature
|
| 730 |
+
# Top-p/top-k filtering
|
| 731 |
+
_scores = top_k_top_p_filtering(
|
| 732 |
+
_scores, top_k=top_k, top_p=top_p, min_tokens_to_keep=2
|
| 733 |
+
) # (batch_size * num_beams, vocab_size)
|
| 734 |
+
# re-organize to group the beam together to sample from all beam_idxs
|
| 735 |
+
_scores = _scores.contiguous().view(
|
| 736 |
+
batch_size, num_beams * vocab_size
|
| 737 |
+
) # (batch_size, num_beams * vocab_size)
|
| 738 |
+
|
| 739 |
+
# Sample 2 next tokens for each beam (so we have some spare tokens and match output of greedy beam search)
|
| 740 |
+
probs = F.softmax(_scores, dim=-1)
|
| 741 |
+
next_tokens = torch.multinomial(probs, num_samples=2 * num_beams) # (batch_size, num_beams * 2)
|
| 742 |
+
# Compute next scores
|
| 743 |
+
next_scores = torch.gather(_scores, -1, next_tokens) # (batch_size, num_beams * 2)
|
| 744 |
+
# sort the sampled vector to make sure that the first num_beams samples are the best
|
| 745 |
+
next_scores, next_scores_indices = torch.sort(next_scores, descending=True, dim=1)
|
| 746 |
+
next_tokens = torch.gather(next_tokens, -1, next_scores_indices) # (batch_size, num_beams * 2)
|
| 747 |
+
|
| 748 |
+
else:
|
| 749 |
+
next_scores = scores + beam_scores[:, None].expand_as(scores) # (batch_size * num_beams, vocab_size)
|
| 750 |
+
|
| 751 |
+
# re-organize to group the beam together (we are keeping top hypothesis accross beams)
|
| 752 |
+
next_scores = next_scores.view(
|
| 753 |
+
batch_size, num_beams * vocab_size
|
| 754 |
+
) # (batch_size, num_beams * vocab_size)
|
| 755 |
+
|
| 756 |
+
next_scores, next_tokens = torch.topk(next_scores, 2 * num_beams, dim=1, largest=True, sorted=True)
|
| 757 |
+
|
| 758 |
+
assert next_scores.size() == next_tokens.size() == (batch_size, 2 * num_beams)
|
| 759 |
+
|
| 760 |
+
# next batch beam content
|
| 761 |
+
next_batch_beam = []
|
| 762 |
+
|
| 763 |
+
# for each sentence
|
| 764 |
+
for batch_idx in range(batch_size):
|
| 765 |
+
|
| 766 |
+
# if we are done with this sentence, add a pad token
|
| 767 |
+
if done[batch_idx]:
|
| 768 |
+
assert (
|
| 769 |
+
len(generated_hyps[batch_idx]) >= num_beams
|
| 770 |
+
), "Batch can only be done if at least {} beams have been generated".format(num_beams)
|
| 771 |
+
assert (
|
| 772 |
+
eos_token_id is not None and pad_token_id is not None
|
| 773 |
+
), "generated beams >= num_beams -> eos_token_id and pad_token have to be defined"
|
| 774 |
+
next_batch_beam.extend([(0, pad_token_id, 0)] * num_beams) # pad the batch
|
| 775 |
+
continue
|
| 776 |
+
|
| 777 |
+
# next sentence beam content, this will get added to next_batch_beam
|
| 778 |
+
next_sent_beam = []
|
| 779 |
+
|
| 780 |
+
# next tokens for this sentence
|
| 781 |
+
for beam_token_rank, (beam_token_id, beam_token_score) in enumerate(
|
| 782 |
+
zip(next_tokens[batch_idx], next_scores[batch_idx])
|
| 783 |
+
):
|
| 784 |
+
# get beam and token IDs
|
| 785 |
+
beam_id = beam_token_id // vocab_size
|
| 786 |
+
token_id = beam_token_id % vocab_size
|
| 787 |
+
|
| 788 |
+
effective_beam_id = batch_idx * num_beams + beam_id
|
| 789 |
+
# add to generated hypotheses if end of sentence
|
| 790 |
+
if (eos_token_id is not None) and (token_id.item() == eos_token_id):
|
| 791 |
+
# if beam_token does not belong to top num_beams tokens, it should not be added
|
| 792 |
+
is_beam_token_worse_than_top_num_beams = beam_token_rank >= num_beams
|
| 793 |
+
if is_beam_token_worse_than_top_num_beams:
|
| 794 |
+
continue
|
| 795 |
+
generated_hyps[batch_idx].add(
|
| 796 |
+
input_ids[effective_beam_id].clone(),
|
| 797 |
+
beam_token_score.item(),
|
| 798 |
+
)
|
| 799 |
+
else:
|
| 800 |
+
# add next predicted token since it is not eos_token
|
| 801 |
+
next_sent_beam.append((beam_token_score, token_id, effective_beam_id))
|
| 802 |
+
|
| 803 |
+
# once the beam for next step is full, don't add more tokens to it.
|
| 804 |
+
if len(next_sent_beam) == num_beams:
|
| 805 |
+
break
|
| 806 |
+
|
| 807 |
+
# Check if we are done so that we can save a pad step if all(done)
|
| 808 |
+
done[batch_idx] = done[batch_idx] or generated_hyps[batch_idx].is_done(
|
| 809 |
+
next_scores[batch_idx].max().item(), cur_len
|
| 810 |
+
)
|
| 811 |
+
|
| 812 |
+
# update next beam content
|
| 813 |
+
assert len(next_sent_beam) == num_beams, "Beam should always be full"
|
| 814 |
+
next_batch_beam.extend(next_sent_beam)
|
| 815 |
+
assert len(next_batch_beam) == num_beams * (batch_idx + 1), "We should have added num_beams each step"
|
| 816 |
+
|
| 817 |
+
# stop when we are done with each sentence
|
| 818 |
+
if all(done):
|
| 819 |
+
break
|
| 820 |
+
|
| 821 |
+
# sanity check / prepare next batch
|
| 822 |
+
assert len(next_batch_beam) == batch_size * num_beams
|
| 823 |
+
beam_scores = beam_scores.new([x[0] for x in next_batch_beam])
|
| 824 |
+
beam_tokens = input_ids.new([x[1] for x in next_batch_beam])
|
| 825 |
+
beam_idx = input_ids.new([x[2] for x in next_batch_beam])
|
| 826 |
+
|
| 827 |
+
# re-order batch and update current length
|
| 828 |
+
input_ids = input_ids[beam_idx, :]
|
| 829 |
+
input_ids = torch.cat([input_ids, beam_tokens.unsqueeze(1)], dim=-1)
|
| 830 |
+
cur_len = cur_len + 1
|
| 831 |
+
|
| 832 |
+
# re-order internal states
|
| 833 |
+
if past is not None:
|
| 834 |
+
past = self._reorder_cache(past, beam_idx)
|
| 835 |
+
|
| 836 |
+
# extend attention_mask for new generated input if only decoder
|
| 837 |
+
# (huxu): move out since we trim attention_mask by ourselves.
|
| 838 |
+
# if self.config.is_encoder_decoder is False:
|
| 839 |
+
# attention_mask = torch.cat(
|
| 840 |
+
# [attention_mask, attention_mask.new_ones((attention_mask.shape[0], 1))], dim=-1
|
| 841 |
+
# )
|
| 842 |
+
|
| 843 |
+
# finalize all open beam hypotheses and add to generated hypotheses
|
| 844 |
+
for batch_idx in range(batch_size):
|
| 845 |
+
if done[batch_idx]:
|
| 846 |
+
continue
|
| 847 |
+
|
| 848 |
+
# test that beam scores match previously calculated scores if not eos and batch_idx not done
|
| 849 |
+
if eos_token_id is not None and all(
|
| 850 |
+
(token_id % vocab_size).item() != eos_token_id for token_id in next_tokens[batch_idx]
|
| 851 |
+
):
|
| 852 |
+
assert torch.all(
|
| 853 |
+
next_scores[batch_idx, :num_beams] == beam_scores.view(batch_size, num_beams)[batch_idx]
|
| 854 |
+
), "If batch_idx is not done, final next scores: {} have to equal to accumulated beam_scores: {}".format(
|
| 855 |
+
next_scores[:, :num_beams][batch_idx],
|
| 856 |
+
beam_scores.view(batch_size, num_beams)[batch_idx],
|
| 857 |
+
)
|
| 858 |
+
|
| 859 |
+
# need to add best num_beams hypotheses to generated hyps
|
| 860 |
+
for beam_id in range(num_beams):
|
| 861 |
+
effective_beam_id = batch_idx * num_beams + beam_id
|
| 862 |
+
final_score = beam_scores[effective_beam_id].item()
|
| 863 |
+
final_tokens = input_ids[effective_beam_id]
|
| 864 |
+
generated_hyps[batch_idx].add(final_tokens, final_score)
|
| 865 |
+
|
| 866 |
+
# depending on whether greedy generation is wanted or not define different output_batch_size and output_num_return_sequences_per_batch
|
| 867 |
+
output_batch_size = batch_size if do_sample else batch_size * num_return_sequences
|
| 868 |
+
output_num_return_sequences_per_batch = 1 if do_sample else num_return_sequences
|
| 869 |
+
|
| 870 |
+
# select the best hypotheses
|
| 871 |
+
sent_lengths = input_ids.new(output_batch_size)
|
| 872 |
+
best = []
|
| 873 |
+
|
| 874 |
+
# retrieve best hypotheses
|
| 875 |
+
for i, hypotheses in enumerate(generated_hyps):
|
| 876 |
+
sorted_hyps = sorted(hypotheses.beams, key=lambda x: x[0])
|
| 877 |
+
for j in range(output_num_return_sequences_per_batch):
|
| 878 |
+
effective_batch_idx = output_num_return_sequences_per_batch * i + j
|
| 879 |
+
best_hyp = sorted_hyps.pop()[1]
|
| 880 |
+
sent_lengths[effective_batch_idx] = len(best_hyp)
|
| 881 |
+
best.append(best_hyp)
|
| 882 |
+
|
| 883 |
+
# prepare for adding eos
|
| 884 |
+
sent_max_len = min(sent_lengths.max().item() + 1, max_length)
|
| 885 |
+
decoded = input_ids.new(output_batch_size, sent_max_len)
|
| 886 |
+
# shorter batches are padded if needed
|
| 887 |
+
if sent_lengths.min().item() != sent_lengths.max().item():
|
| 888 |
+
assert pad_token_id is not None, "`pad_token_id` has to be defined"
|
| 889 |
+
decoded.fill_(pad_token_id)
|
| 890 |
+
|
| 891 |
+
# fill with hypotheses and eos_token_id if the latter fits in
|
| 892 |
+
for i, hypo in enumerate(best):
|
| 893 |
+
decoded[i, : sent_lengths[i]] = hypo
|
| 894 |
+
if sent_lengths[i] < max_length:
|
| 895 |
+
decoded[i, sent_lengths[i]] = eos_token_id
|
| 896 |
+
|
| 897 |
+
return decoded
|
| 898 |
+
|
| 899 |
+
def _generate_no_beam_search(
|
| 900 |
+
self,
|
| 901 |
+
input_ids,
|
| 902 |
+
cur_len,
|
| 903 |
+
max_length,
|
| 904 |
+
min_length,
|
| 905 |
+
do_sample,
|
| 906 |
+
temperature,
|
| 907 |
+
top_k,
|
| 908 |
+
top_p,
|
| 909 |
+
repetition_penalty,
|
| 910 |
+
no_repeat_ngram_size,
|
| 911 |
+
bad_words_ids,
|
| 912 |
+
pad_token_id,
|
| 913 |
+
eos_token_id,
|
| 914 |
+
batch_size,
|
| 915 |
+
attention_mask,
|
| 916 |
+
use_cache,
|
| 917 |
+
model_kwargs,
|
| 918 |
+
):
|
| 919 |
+
"""Generate sequences for each example without beam search (num_beams == 1).
|
| 920 |
+
All returned sequence are generated independantly.
|
| 921 |
+
"""
|
| 922 |
+
# length of generated sentences / unfinished sentences
|
| 923 |
+
unfinished_sents = input_ids.new(batch_size).fill_(1)
|
| 924 |
+
sent_lengths = input_ids.new(batch_size).fill_(max_length)
|
| 925 |
+
|
| 926 |
+
past = None
|
| 927 |
+
while cur_len < max_length:
|
| 928 |
+
model_inputs = self.prepare_inputs_for_generation(
|
| 929 |
+
input_ids, past=past, attention_mask=attention_mask, use_cache=use_cache, **model_kwargs
|
| 930 |
+
)
|
| 931 |
+
|
| 932 |
+
outputs = self(**model_inputs, return_dict=True)
|
| 933 |
+
next_token_logits = outputs.logits[:, -1, :]
|
| 934 |
+
scores = self.postprocess_next_token_scores(
|
| 935 |
+
scores=next_token_logits,
|
| 936 |
+
input_ids=input_ids,
|
| 937 |
+
no_repeat_ngram_size=no_repeat_ngram_size,
|
| 938 |
+
bad_words_ids=bad_words_ids,
|
| 939 |
+
cur_len=cur_len,
|
| 940 |
+
min_length=min_length,
|
| 941 |
+
max_length=max_length,
|
| 942 |
+
eos_token_id=eos_token_id,
|
| 943 |
+
repetition_penalty=repetition_penalty,
|
| 944 |
+
batch_size=batch_size,
|
| 945 |
+
num_beams=1,
|
| 946 |
+
)
|
| 947 |
+
|
| 948 |
+
# if model has past, then set the past variable to speed up decoding
|
| 949 |
+
if "past_key_values" in outputs:
|
| 950 |
+
past = outputs.past_key_values
|
| 951 |
+
elif "mems" in outputs:
|
| 952 |
+
past = outputs.mems
|
| 953 |
+
|
| 954 |
+
if do_sample:
|
| 955 |
+
# Temperature (higher temperature => more likely to sample low probability tokens)
|
| 956 |
+
if temperature != 1.0:
|
| 957 |
+
scores = scores / temperature
|
| 958 |
+
# Top-p/top-k filtering
|
| 959 |
+
next_token_logscores = top_k_top_p_filtering(scores, top_k=top_k, top_p=top_p)
|
| 960 |
+
# Sample
|
| 961 |
+
probs = F.softmax(next_token_logscores, dim=-1)
|
| 962 |
+
next_token = torch.multinomial(probs, num_samples=1).squeeze(1)
|
| 963 |
+
else:
|
| 964 |
+
# Greedy decoding
|
| 965 |
+
next_token = torch.argmax(next_token_logits, dim=-1)
|
| 966 |
+
|
| 967 |
+
# print(next_token_logits[0,next_token[0]], next_token_logits[0,eos_token_id])
|
| 968 |
+
|
| 969 |
+
# update generations and finished sentences
|
| 970 |
+
if eos_token_id is not None:
|
| 971 |
+
# pad finished sentences if eos_token_id exist
|
| 972 |
+
tokens_to_add = next_token * unfinished_sents + (pad_token_id) * (1 - unfinished_sents)
|
| 973 |
+
else:
|
| 974 |
+
tokens_to_add = next_token
|
| 975 |
+
|
| 976 |
+
# add token and increase length by one
|
| 977 |
+
input_ids = torch.cat([input_ids, tokens_to_add.unsqueeze(-1)], dim=-1)
|
| 978 |
+
cur_len = cur_len + 1
|
| 979 |
+
|
| 980 |
+
if eos_token_id is not None:
|
| 981 |
+
eos_in_sents = tokens_to_add == eos_token_id
|
| 982 |
+
# if sentence is unfinished and the token to add is eos, sent_lengths is filled with current length
|
| 983 |
+
is_sents_unfinished_and_token_to_add_is_eos = unfinished_sents.mul(eos_in_sents.long()).bool()
|
| 984 |
+
sent_lengths.masked_fill_(is_sents_unfinished_and_token_to_add_is_eos, cur_len)
|
| 985 |
+
# unfinished_sents is set to zero if eos in sentence
|
| 986 |
+
unfinished_sents.mul_((~eos_in_sents).long())
|
| 987 |
+
|
| 988 |
+
# stop when there is a </s> in each sentence, or if we exceed the maximul length
|
| 989 |
+
if unfinished_sents.max() == 0:
|
| 990 |
+
break
|
| 991 |
+
|
| 992 |
+
|
| 993 |
+
# extend attention_mask for new generated input if only decoder
|
| 994 |
+
# if self.config.is_encoder_decoder is False:
|
| 995 |
+
# attention_mask = torch.cat(
|
| 996 |
+
# [attention_mask, attention_mask.new_ones((attention_mask.shape[0], 1))], dim=-1
|
| 997 |
+
# )
|
| 998 |
+
|
| 999 |
+
return input_ids
|
data/fairseq/examples/MMPT/mmpt/models/transformermodel.py
ADDED
|
@@ -0,0 +1,734 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2018 The Google AI Language Team Authors and The HuggingFace Inc. team.
|
| 3 |
+
# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved.
|
| 4 |
+
#
|
| 5 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 6 |
+
# you may not use this file except in compliance with the License.
|
| 7 |
+
# You may obtain a copy of the License at
|
| 8 |
+
#
|
| 9 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 10 |
+
#
|
| 11 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 12 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 13 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 14 |
+
# See the License for the specific language governing permissions and
|
| 15 |
+
# limitations under the License.
|
| 16 |
+
# Copyright (c) Facebook, Inc. All Rights Reserved
|
| 17 |
+
|
| 18 |
+
import torch
|
| 19 |
+
|
| 20 |
+
from torch import nn
|
| 21 |
+
|
| 22 |
+
try:
|
| 23 |
+
from transformers.modeling_bert import (
|
| 24 |
+
BertPreTrainedModel,
|
| 25 |
+
BertModel,
|
| 26 |
+
BertEncoder,
|
| 27 |
+
BertPredictionHeadTransform,
|
| 28 |
+
)
|
| 29 |
+
except ImportError:
|
| 30 |
+
pass
|
| 31 |
+
|
| 32 |
+
from ..modules import VideoTokenMLP, MMBertEmbeddings
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
# --------------- fine-tuning models ---------------
|
| 36 |
+
class MMBertForJoint(BertPreTrainedModel):
|
| 37 |
+
"""A BertModel with isolated attention mask to separate modality."""
|
| 38 |
+
|
| 39 |
+
def __init__(self, config):
|
| 40 |
+
super().__init__(config)
|
| 41 |
+
self.videomlp = VideoTokenMLP(config)
|
| 42 |
+
self.bert = MMBertModel(config)
|
| 43 |
+
self.init_weights()
|
| 44 |
+
|
| 45 |
+
def forward(
|
| 46 |
+
self,
|
| 47 |
+
input_ids=None,
|
| 48 |
+
input_video_embeds=None,
|
| 49 |
+
attention_mask=None,
|
| 50 |
+
token_type_ids=None,
|
| 51 |
+
position_ids=None,
|
| 52 |
+
head_mask=None,
|
| 53 |
+
inputs_embeds=None,
|
| 54 |
+
next_sentence_label=None,
|
| 55 |
+
output_attentions=None,
|
| 56 |
+
output_hidden_states=None,
|
| 57 |
+
return_dict=None,
|
| 58 |
+
separate_forward_split=None,
|
| 59 |
+
):
|
| 60 |
+
return_dict = (
|
| 61 |
+
return_dict if return_dict is not None
|
| 62 |
+
else self.config.use_return_dict
|
| 63 |
+
)
|
| 64 |
+
video_tokens = self.videomlp(input_video_embeds)
|
| 65 |
+
|
| 66 |
+
outputs = self.bert(
|
| 67 |
+
input_ids,
|
| 68 |
+
video_tokens,
|
| 69 |
+
attention_mask=attention_mask,
|
| 70 |
+
token_type_ids=token_type_ids,
|
| 71 |
+
position_ids=position_ids,
|
| 72 |
+
head_mask=head_mask,
|
| 73 |
+
inputs_embeds=inputs_embeds,
|
| 74 |
+
output_attentions=output_attentions,
|
| 75 |
+
output_hidden_states=output_hidden_states,
|
| 76 |
+
return_dict=return_dict,
|
| 77 |
+
separate_forward_split=separate_forward_split,
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
return outputs
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
class MMBertForTokenClassification(BertPreTrainedModel):
|
| 84 |
+
"""A BertModel similar to MMJointUni, with extra wrapper layer
|
| 85 |
+
to be fine-tuned from other pretrained MMFusion model."""
|
| 86 |
+
|
| 87 |
+
def __init__(self, config):
|
| 88 |
+
super().__init__(config)
|
| 89 |
+
self.videomlp = VideoTokenMLP(config)
|
| 90 |
+
self.bert = MMBertModel(config)
|
| 91 |
+
self.dropout = nn.Dropout(config.hidden_dropout_prob)
|
| 92 |
+
# TODO(huxu): 779 is the number of classes for COIN: move to config?
|
| 93 |
+
self.classifier = nn.Linear(config.hidden_size, 779)
|
| 94 |
+
self.init_weights()
|
| 95 |
+
|
| 96 |
+
def forward(
|
| 97 |
+
self,
|
| 98 |
+
input_ids=None,
|
| 99 |
+
input_video_embeds=None,
|
| 100 |
+
attention_mask=None,
|
| 101 |
+
token_type_ids=None,
|
| 102 |
+
position_ids=None,
|
| 103 |
+
head_mask=None,
|
| 104 |
+
inputs_embeds=None,
|
| 105 |
+
next_sentence_label=None,
|
| 106 |
+
output_attentions=None,
|
| 107 |
+
output_hidden_states=None,
|
| 108 |
+
return_dict=None,
|
| 109 |
+
separate_forward_split=None,
|
| 110 |
+
):
|
| 111 |
+
return_dict = (
|
| 112 |
+
return_dict if return_dict is not None
|
| 113 |
+
else self.config.use_return_dict
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
video_tokens = self.videomlp(input_video_embeds)
|
| 117 |
+
outputs = self.bert(
|
| 118 |
+
input_ids,
|
| 119 |
+
video_tokens,
|
| 120 |
+
attention_mask=attention_mask,
|
| 121 |
+
token_type_ids=token_type_ids,
|
| 122 |
+
position_ids=position_ids,
|
| 123 |
+
head_mask=head_mask,
|
| 124 |
+
inputs_embeds=inputs_embeds,
|
| 125 |
+
output_attentions=output_attentions,
|
| 126 |
+
output_hidden_states=output_hidden_states,
|
| 127 |
+
return_dict=return_dict,
|
| 128 |
+
separate_forward_split=separate_forward_split,
|
| 129 |
+
)
|
| 130 |
+
|
| 131 |
+
return (self.classifier(outputs[0]),)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
# ------------ pre-training models ----------------
|
| 135 |
+
|
| 136 |
+
class MMBertForEncoder(BertPreTrainedModel):
|
| 137 |
+
"""A BertModel for Contrastive Learning."""
|
| 138 |
+
def __init__(self, config):
|
| 139 |
+
super().__init__(config)
|
| 140 |
+
self.videomlp = VideoTokenMLP(config)
|
| 141 |
+
self.bert = MMBertModel(config)
|
| 142 |
+
self.init_weights()
|
| 143 |
+
|
| 144 |
+
def forward(
|
| 145 |
+
self,
|
| 146 |
+
input_ids=None,
|
| 147 |
+
input_video_embeds=None,
|
| 148 |
+
attention_mask=None,
|
| 149 |
+
token_type_ids=None,
|
| 150 |
+
position_ids=None,
|
| 151 |
+
head_mask=None,
|
| 152 |
+
inputs_embeds=None,
|
| 153 |
+
output_attentions=None,
|
| 154 |
+
output_hidden_states=None,
|
| 155 |
+
return_dict=None,
|
| 156 |
+
):
|
| 157 |
+
return_dict = (
|
| 158 |
+
return_dict if return_dict is not None
|
| 159 |
+
else self.config.use_return_dict
|
| 160 |
+
)
|
| 161 |
+
if input_video_embeds is not None:
|
| 162 |
+
video_tokens = self.videomlp(input_video_embeds)
|
| 163 |
+
else:
|
| 164 |
+
video_tokens = None
|
| 165 |
+
|
| 166 |
+
outputs = self.bert(
|
| 167 |
+
input_ids,
|
| 168 |
+
video_tokens,
|
| 169 |
+
attention_mask=attention_mask,
|
| 170 |
+
token_type_ids=token_type_ids,
|
| 171 |
+
position_ids=position_ids,
|
| 172 |
+
head_mask=head_mask,
|
| 173 |
+
inputs_embeds=inputs_embeds,
|
| 174 |
+
output_attentions=output_attentions,
|
| 175 |
+
output_hidden_states=output_hidden_states,
|
| 176 |
+
return_dict=return_dict,
|
| 177 |
+
)
|
| 178 |
+
return outputs
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
class MMBertForMFMMLM(BertPreTrainedModel):
|
| 182 |
+
"""A BertModel with shared prediction head on MFM-MLM."""
|
| 183 |
+
def __init__(self, config):
|
| 184 |
+
super().__init__(config)
|
| 185 |
+
self.videomlp = VideoTokenMLP(config)
|
| 186 |
+
self.bert = MMBertModel(config)
|
| 187 |
+
self.cls = MFMMLMHead(config)
|
| 188 |
+
self.hidden_size = config.hidden_size
|
| 189 |
+
self.init_weights()
|
| 190 |
+
|
| 191 |
+
def get_output_embeddings(self):
|
| 192 |
+
return self.cls.predictions.decoder
|
| 193 |
+
|
| 194 |
+
def forward(
|
| 195 |
+
self,
|
| 196 |
+
input_ids=None,
|
| 197 |
+
input_video_embeds=None,
|
| 198 |
+
attention_mask=None,
|
| 199 |
+
token_type_ids=None,
|
| 200 |
+
position_ids=None,
|
| 201 |
+
head_mask=None,
|
| 202 |
+
inputs_embeds=None,
|
| 203 |
+
masked_frame_labels=None,
|
| 204 |
+
target_video_hidden_states=None,
|
| 205 |
+
non_masked_frame_mask=None,
|
| 206 |
+
masked_lm_labels=None,
|
| 207 |
+
output_attentions=None,
|
| 208 |
+
output_hidden_states=None,
|
| 209 |
+
return_dict=None,
|
| 210 |
+
):
|
| 211 |
+
return_dict = (
|
| 212 |
+
return_dict if return_dict is not None
|
| 213 |
+
else self.config.use_return_dict
|
| 214 |
+
)
|
| 215 |
+
if input_video_embeds is not None:
|
| 216 |
+
video_tokens = self.videomlp(input_video_embeds)
|
| 217 |
+
else:
|
| 218 |
+
video_tokens = None
|
| 219 |
+
|
| 220 |
+
if target_video_hidden_states is not None:
|
| 221 |
+
target_video_hidden_states = self.videomlp(
|
| 222 |
+
target_video_hidden_states)
|
| 223 |
+
|
| 224 |
+
non_masked_frame_hidden_states = video_tokens.masked_select(
|
| 225 |
+
non_masked_frame_mask.unsqueeze(-1)
|
| 226 |
+
).view(-1, self.hidden_size)
|
| 227 |
+
|
| 228 |
+
outputs = self.bert(
|
| 229 |
+
input_ids,
|
| 230 |
+
video_tokens,
|
| 231 |
+
attention_mask=attention_mask,
|
| 232 |
+
token_type_ids=token_type_ids,
|
| 233 |
+
position_ids=position_ids,
|
| 234 |
+
head_mask=head_mask,
|
| 235 |
+
inputs_embeds=inputs_embeds,
|
| 236 |
+
output_attentions=output_attentions,
|
| 237 |
+
output_hidden_states=output_hidden_states,
|
| 238 |
+
return_dict=return_dict,
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
sequence_output = outputs[0]
|
| 242 |
+
|
| 243 |
+
mfm_scores, prediction_scores = None, None
|
| 244 |
+
if masked_frame_labels is not None and masked_lm_labels is not None:
|
| 245 |
+
# split the sequence.
|
| 246 |
+
text_offset = masked_frame_labels.size(1) + 1 # [CLS]
|
| 247 |
+
video_sequence_output = sequence_output[
|
| 248 |
+
:, 1:text_offset
|
| 249 |
+
] # remove [SEP] as not in video_label.
|
| 250 |
+
text_sequence_output = torch.cat(
|
| 251 |
+
[sequence_output[:, :1], sequence_output[:, text_offset:]],
|
| 252 |
+
dim=1
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
hidden_size = video_sequence_output.size(-1)
|
| 256 |
+
selected_video_output = video_sequence_output.masked_select(
|
| 257 |
+
masked_frame_labels.unsqueeze(-1)
|
| 258 |
+
).view(-1, hidden_size)
|
| 259 |
+
|
| 260 |
+
# only compute select tokens to training to speed up.
|
| 261 |
+
hidden_size = text_sequence_output.size(-1)
|
| 262 |
+
# masked_lm_labels = masked_lm_labels.reshape(-1)
|
| 263 |
+
labels_mask = masked_lm_labels != -100
|
| 264 |
+
|
| 265 |
+
selected_text_output = text_sequence_output.masked_select(
|
| 266 |
+
labels_mask.unsqueeze(-1)
|
| 267 |
+
).view(-1, hidden_size)
|
| 268 |
+
mfm_scores, prediction_scores = self.cls(
|
| 269 |
+
selected_video_output,
|
| 270 |
+
target_video_hidden_states,
|
| 271 |
+
non_masked_frame_hidden_states,
|
| 272 |
+
selected_text_output,
|
| 273 |
+
)
|
| 274 |
+
|
| 275 |
+
output = (
|
| 276 |
+
mfm_scores,
|
| 277 |
+
prediction_scores,
|
| 278 |
+
) + outputs
|
| 279 |
+
return output
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
class BertMFMMLMPredictionHead(nn.Module):
|
| 283 |
+
def __init__(self, config):
|
| 284 |
+
super().__init__()
|
| 285 |
+
self.transform = BertPredictionHeadTransform(config)
|
| 286 |
+
# The output weights are the same as the input embeddings, but there is
|
| 287 |
+
# an output-only bias for each token.
|
| 288 |
+
self.decoder = nn.Linear(
|
| 289 |
+
config.hidden_size, config.vocab_size, bias=False)
|
| 290 |
+
|
| 291 |
+
self.bias = nn.Parameter(torch.zeros(config.vocab_size))
|
| 292 |
+
|
| 293 |
+
# Need a link between the two variables so that the bias is correctly
|
| 294 |
+
# resized with `resize_token_embeddings`
|
| 295 |
+
self.decoder.bias = self.bias
|
| 296 |
+
|
| 297 |
+
def forward(
|
| 298 |
+
self,
|
| 299 |
+
video_hidden_states=None,
|
| 300 |
+
target_video_hidden_states=None,
|
| 301 |
+
non_masked_frame_hidden_states=None,
|
| 302 |
+
text_hidden_states=None,
|
| 303 |
+
):
|
| 304 |
+
video_logits, text_logits = None, None
|
| 305 |
+
if video_hidden_states is not None:
|
| 306 |
+
video_hidden_states = self.transform(video_hidden_states)
|
| 307 |
+
non_masked_frame_logits = torch.mm(
|
| 308 |
+
video_hidden_states,
|
| 309 |
+
non_masked_frame_hidden_states.transpose(1, 0)
|
| 310 |
+
)
|
| 311 |
+
masked_frame_logits = torch.bmm(
|
| 312 |
+
video_hidden_states.unsqueeze(1),
|
| 313 |
+
target_video_hidden_states.unsqueeze(-1),
|
| 314 |
+
).squeeze(-1)
|
| 315 |
+
video_logits = torch.cat(
|
| 316 |
+
[masked_frame_logits, non_masked_frame_logits], dim=1
|
| 317 |
+
)
|
| 318 |
+
|
| 319 |
+
if text_hidden_states is not None:
|
| 320 |
+
text_hidden_states = self.transform(text_hidden_states)
|
| 321 |
+
text_logits = self.decoder(text_hidden_states)
|
| 322 |
+
return video_logits, text_logits
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
class MFMMLMHead(nn.Module):
|
| 326 |
+
def __init__(self, config):
|
| 327 |
+
super().__init__()
|
| 328 |
+
self.predictions = BertMFMMLMPredictionHead(config)
|
| 329 |
+
|
| 330 |
+
def forward(
|
| 331 |
+
self,
|
| 332 |
+
video_hidden_states=None,
|
| 333 |
+
target_video_hidden_states=None,
|
| 334 |
+
non_masked_frame_hidden_states=None,
|
| 335 |
+
text_hidden_states=None,
|
| 336 |
+
):
|
| 337 |
+
video_logits, text_logits = self.predictions(
|
| 338 |
+
video_hidden_states,
|
| 339 |
+
target_video_hidden_states,
|
| 340 |
+
non_masked_frame_hidden_states,
|
| 341 |
+
text_hidden_states,
|
| 342 |
+
)
|
| 343 |
+
return video_logits, text_logits
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
class MMBertForMTM(MMBertForMFMMLM):
|
| 347 |
+
def __init__(self, config):
|
| 348 |
+
BertPreTrainedModel.__init__(self, config)
|
| 349 |
+
self.videomlp = VideoTokenMLP(config)
|
| 350 |
+
self.bert = MMBertModel(config)
|
| 351 |
+
self.cls = MTMHead(config)
|
| 352 |
+
self.hidden_size = config.hidden_size
|
| 353 |
+
self.init_weights()
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
class BertMTMPredictionHead(nn.Module):
|
| 357 |
+
def __init__(self, config):
|
| 358 |
+
super().__init__()
|
| 359 |
+
self.transform = BertPredictionHeadTransform(config)
|
| 360 |
+
self.decoder = nn.Linear(
|
| 361 |
+
config.hidden_size, config.vocab_size, bias=False)
|
| 362 |
+
|
| 363 |
+
def forward(
|
| 364 |
+
self,
|
| 365 |
+
video_hidden_states=None,
|
| 366 |
+
target_video_hidden_states=None,
|
| 367 |
+
non_masked_frame_hidden_states=None,
|
| 368 |
+
text_hidden_states=None,
|
| 369 |
+
):
|
| 370 |
+
non_masked_frame_hidden_states = non_masked_frame_hidden_states.transpose(1, 0)
|
| 371 |
+
video_logits, text_logits = None, None
|
| 372 |
+
if video_hidden_states is not None:
|
| 373 |
+
video_hidden_states = self.transform(video_hidden_states)
|
| 374 |
+
|
| 375 |
+
masked_frame_logits = torch.bmm(
|
| 376 |
+
video_hidden_states.unsqueeze(1),
|
| 377 |
+
target_video_hidden_states.unsqueeze(-1),
|
| 378 |
+
).squeeze(-1)
|
| 379 |
+
|
| 380 |
+
non_masked_frame_logits = torch.mm(
|
| 381 |
+
video_hidden_states,
|
| 382 |
+
non_masked_frame_hidden_states
|
| 383 |
+
)
|
| 384 |
+
video_on_vocab_logits = self.decoder(video_hidden_states)
|
| 385 |
+
video_logits = torch.cat([
|
| 386 |
+
masked_frame_logits,
|
| 387 |
+
non_masked_frame_logits,
|
| 388 |
+
video_on_vocab_logits], dim=1)
|
| 389 |
+
|
| 390 |
+
if text_hidden_states is not None:
|
| 391 |
+
text_hidden_states = self.transform(text_hidden_states)
|
| 392 |
+
# text first so label does not need to be shifted.
|
| 393 |
+
text_on_vocab_logits = self.decoder(text_hidden_states)
|
| 394 |
+
text_on_video_logits = torch.mm(
|
| 395 |
+
text_hidden_states,
|
| 396 |
+
non_masked_frame_hidden_states
|
| 397 |
+
)
|
| 398 |
+
text_logits = torch.cat([
|
| 399 |
+
text_on_vocab_logits,
|
| 400 |
+
text_on_video_logits
|
| 401 |
+
], dim=1)
|
| 402 |
+
|
| 403 |
+
return video_logits, text_logits
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
class MTMHead(nn.Module):
|
| 407 |
+
def __init__(self, config):
|
| 408 |
+
super().__init__()
|
| 409 |
+
self.predictions = BertMTMPredictionHead(config)
|
| 410 |
+
|
| 411 |
+
def forward(
|
| 412 |
+
self,
|
| 413 |
+
video_hidden_states=None,
|
| 414 |
+
target_video_hidden_states=None,
|
| 415 |
+
non_masked_frame_hidden_states=None,
|
| 416 |
+
text_hidden_states=None,
|
| 417 |
+
):
|
| 418 |
+
video_logits, text_logits = self.predictions(
|
| 419 |
+
video_hidden_states,
|
| 420 |
+
target_video_hidden_states,
|
| 421 |
+
non_masked_frame_hidden_states,
|
| 422 |
+
text_hidden_states,
|
| 423 |
+
)
|
| 424 |
+
return video_logits, text_logits
|
| 425 |
+
|
| 426 |
+
|
| 427 |
+
class MMBertModel(BertModel):
|
| 428 |
+
"""MMBertModel has MMBertEmbedding to support video tokens."""
|
| 429 |
+
|
| 430 |
+
def __init__(self, config, add_pooling_layer=True):
|
| 431 |
+
super().__init__(config)
|
| 432 |
+
# overwrite embedding
|
| 433 |
+
self.embeddings = MMBertEmbeddings(config)
|
| 434 |
+
self.encoder = MultiLayerAttentionMaskBertEncoder(config)
|
| 435 |
+
self.init_weights()
|
| 436 |
+
|
| 437 |
+
def forward(
|
| 438 |
+
self,
|
| 439 |
+
input_ids=None,
|
| 440 |
+
input_video_embeds=None,
|
| 441 |
+
attention_mask=None,
|
| 442 |
+
token_type_ids=None,
|
| 443 |
+
position_ids=None,
|
| 444 |
+
head_mask=None,
|
| 445 |
+
inputs_embeds=None,
|
| 446 |
+
encoder_hidden_states=None,
|
| 447 |
+
encoder_attention_mask=None,
|
| 448 |
+
output_attentions=None,
|
| 449 |
+
output_hidden_states=None,
|
| 450 |
+
return_dict=None,
|
| 451 |
+
separate_forward_split=None,
|
| 452 |
+
):
|
| 453 |
+
output_attentions = (
|
| 454 |
+
output_attentions
|
| 455 |
+
if output_attentions is not None
|
| 456 |
+
else self.config.output_attentions
|
| 457 |
+
)
|
| 458 |
+
output_hidden_states = (
|
| 459 |
+
output_hidden_states
|
| 460 |
+
if output_hidden_states is not None
|
| 461 |
+
else self.config.output_hidden_states
|
| 462 |
+
)
|
| 463 |
+
return_dict = (
|
| 464 |
+
return_dict if return_dict is not None
|
| 465 |
+
else self.config.use_return_dict
|
| 466 |
+
)
|
| 467 |
+
|
| 468 |
+
if input_ids is not None and inputs_embeds is not None:
|
| 469 |
+
raise ValueError(
|
| 470 |
+
"You cannot specify both input_ids "
|
| 471 |
+
"and inputs_embeds at the same time"
|
| 472 |
+
)
|
| 473 |
+
elif input_ids is not None:
|
| 474 |
+
if input_video_embeds is not None:
|
| 475 |
+
input_shape = (
|
| 476 |
+
input_ids.size(0),
|
| 477 |
+
input_ids.size(1) + input_video_embeds.size(1),
|
| 478 |
+
)
|
| 479 |
+
else:
|
| 480 |
+
input_shape = (
|
| 481 |
+
input_ids.size(0),
|
| 482 |
+
input_ids.size(1),
|
| 483 |
+
)
|
| 484 |
+
elif inputs_embeds is not None:
|
| 485 |
+
if input_video_embeds is not None:
|
| 486 |
+
input_shape = (
|
| 487 |
+
inputs_embeds.size(0),
|
| 488 |
+
inputs_embeds.size(1) + input_video_embeds.size(1),
|
| 489 |
+
)
|
| 490 |
+
else:
|
| 491 |
+
input_shape = (
|
| 492 |
+
input_ids.size(0),
|
| 493 |
+
input_ids.size(1),
|
| 494 |
+
)
|
| 495 |
+
else:
|
| 496 |
+
raise ValueError(
|
| 497 |
+
"You have to specify either input_ids or inputs_embeds")
|
| 498 |
+
|
| 499 |
+
device = input_ids.device if input_ids is not None \
|
| 500 |
+
else inputs_embeds.device
|
| 501 |
+
|
| 502 |
+
if attention_mask is None:
|
| 503 |
+
attention_mask = torch.ones(input_shape, device=device)
|
| 504 |
+
if token_type_ids is None:
|
| 505 |
+
token_type_ids = torch.zeros(
|
| 506 |
+
input_shape, dtype=torch.long, device=device)
|
| 507 |
+
|
| 508 |
+
# We can provide a self-attention mask of dimensions
|
| 509 |
+
# [batch_size, from_seq_length, to_seq_length]
|
| 510 |
+
# ourselves in which case
|
| 511 |
+
# we just need to make it broadcastable to all heads.
|
| 512 |
+
extended_attention_mask: torch.Tensor = \
|
| 513 |
+
self.get_extended_attention_mask(
|
| 514 |
+
attention_mask, input_shape, device)
|
| 515 |
+
|
| 516 |
+
# If a 2D or 3D attention mask is provided for the cross-attention
|
| 517 |
+
# we need to make broadcastable to
|
| 518 |
+
# [batch_size, num_heads, seq_length, seq_length]
|
| 519 |
+
if self.config.is_decoder and encoder_hidden_states is not None:
|
| 520 |
+
(
|
| 521 |
+
encoder_batch_size,
|
| 522 |
+
encoder_sequence_length,
|
| 523 |
+
_,
|
| 524 |
+
) = encoder_hidden_states.size()
|
| 525 |
+
encoder_hidden_shape = (
|
| 526 |
+
encoder_batch_size, encoder_sequence_length)
|
| 527 |
+
if encoder_attention_mask is None:
|
| 528 |
+
encoder_attention_mask = torch.ones(
|
| 529 |
+
encoder_hidden_shape, device=device)
|
| 530 |
+
encoder_extended_attention_mask = self.invert_attention_mask(
|
| 531 |
+
encoder_attention_mask
|
| 532 |
+
)
|
| 533 |
+
else:
|
| 534 |
+
encoder_extended_attention_mask = None
|
| 535 |
+
|
| 536 |
+
# Prepare head mask if needed
|
| 537 |
+
# 1.0 in head_mask indicate we keep the head
|
| 538 |
+
# attention_probs has shape bsz x n_heads x N x N
|
| 539 |
+
# input head_mask has shape [num_heads] or
|
| 540 |
+
# [num_hidden_layers x num_heads]
|
| 541 |
+
# and head_mask is converted to shape
|
| 542 |
+
# [num_hidden_layers x batch x num_heads x seq_length x seq_length]
|
| 543 |
+
|
| 544 |
+
head_mask = self.get_head_mask(
|
| 545 |
+
head_mask, self.config.num_hidden_layers)
|
| 546 |
+
|
| 547 |
+
embedding_output = self.embeddings(
|
| 548 |
+
input_ids,
|
| 549 |
+
input_video_embeds,
|
| 550 |
+
position_ids=position_ids,
|
| 551 |
+
token_type_ids=token_type_ids,
|
| 552 |
+
inputs_embeds=inputs_embeds,
|
| 553 |
+
)
|
| 554 |
+
|
| 555 |
+
if separate_forward_split is not None:
|
| 556 |
+
split_embedding_output = \
|
| 557 |
+
embedding_output[:, :separate_forward_split]
|
| 558 |
+
split_extended_attention_mask = extended_attention_mask[
|
| 559 |
+
:, :, :, :separate_forward_split, :separate_forward_split
|
| 560 |
+
]
|
| 561 |
+
split_encoder_outputs = self.encoder(
|
| 562 |
+
split_embedding_output,
|
| 563 |
+
attention_mask=split_extended_attention_mask,
|
| 564 |
+
head_mask=head_mask,
|
| 565 |
+
encoder_hidden_states=encoder_hidden_states,
|
| 566 |
+
encoder_attention_mask=encoder_extended_attention_mask,
|
| 567 |
+
output_attentions=output_attentions,
|
| 568 |
+
output_hidden_states=output_hidden_states,
|
| 569 |
+
return_dict=return_dict,
|
| 570 |
+
)
|
| 571 |
+
assert (
|
| 572 |
+
len(split_encoder_outputs) <= 2
|
| 573 |
+
), "we do not support merge on attention for now."
|
| 574 |
+
encoder_outputs = []
|
| 575 |
+
encoder_outputs.append([split_encoder_outputs[0]])
|
| 576 |
+
if len(split_encoder_outputs) == 2:
|
| 577 |
+
encoder_outputs.append([])
|
| 578 |
+
for _all_hidden_states in split_encoder_outputs[1]:
|
| 579 |
+
encoder_outputs[-1].append([_all_hidden_states])
|
| 580 |
+
|
| 581 |
+
split_embedding_output = \
|
| 582 |
+
embedding_output[:, separate_forward_split:]
|
| 583 |
+
split_extended_attention_mask = extended_attention_mask[
|
| 584 |
+
:, :, :, separate_forward_split:, separate_forward_split:
|
| 585 |
+
]
|
| 586 |
+
|
| 587 |
+
split_encoder_outputs = self.encoder(
|
| 588 |
+
split_embedding_output,
|
| 589 |
+
attention_mask=split_extended_attention_mask,
|
| 590 |
+
head_mask=head_mask,
|
| 591 |
+
encoder_hidden_states=encoder_hidden_states,
|
| 592 |
+
encoder_attention_mask=encoder_extended_attention_mask,
|
| 593 |
+
output_attentions=output_attentions,
|
| 594 |
+
output_hidden_states=output_hidden_states,
|
| 595 |
+
return_dict=return_dict,
|
| 596 |
+
)
|
| 597 |
+
|
| 598 |
+
assert (
|
| 599 |
+
len(split_encoder_outputs) <= 2
|
| 600 |
+
), "we do not support merge on attention for now."
|
| 601 |
+
encoder_outputs[0].append(split_encoder_outputs[0])
|
| 602 |
+
encoder_outputs[0] = torch.cat(encoder_outputs[0], dim=1)
|
| 603 |
+
if len(split_encoder_outputs) == 2:
|
| 604 |
+
for layer_idx, _all_hidden_states in enumerate(
|
| 605 |
+
split_encoder_outputs[1]
|
| 606 |
+
):
|
| 607 |
+
encoder_outputs[1][layer_idx].append(_all_hidden_states)
|
| 608 |
+
encoder_outputs[1][layer_idx] = torch.cat(
|
| 609 |
+
encoder_outputs[1][layer_idx], dim=1
|
| 610 |
+
)
|
| 611 |
+
encoder_outputs = tuple(encoder_outputs)
|
| 612 |
+
else:
|
| 613 |
+
encoder_outputs = self.encoder(
|
| 614 |
+
embedding_output,
|
| 615 |
+
attention_mask=extended_attention_mask,
|
| 616 |
+
head_mask=head_mask,
|
| 617 |
+
encoder_hidden_states=encoder_hidden_states,
|
| 618 |
+
encoder_attention_mask=encoder_extended_attention_mask,
|
| 619 |
+
output_attentions=output_attentions,
|
| 620 |
+
output_hidden_states=output_hidden_states,
|
| 621 |
+
return_dict=return_dict,
|
| 622 |
+
)
|
| 623 |
+
|
| 624 |
+
sequence_output = encoder_outputs[0]
|
| 625 |
+
pooled_output = (
|
| 626 |
+
self.pooler(sequence_output) if self.pooler is not None else None
|
| 627 |
+
)
|
| 628 |
+
|
| 629 |
+
return (sequence_output, pooled_output) + encoder_outputs[1:]
|
| 630 |
+
|
| 631 |
+
def get_extended_attention_mask(self, attention_mask, input_shape, device):
|
| 632 |
+
"""This is borrowed from `modeling_utils.py` with the support of
|
| 633 |
+
multi-layer attention masks.
|
| 634 |
+
The second dim is expected to be number of layers.
|
| 635 |
+
See `MMAttentionMaskProcessor`.
|
| 636 |
+
Makes broadcastable attention and causal masks so that future
|
| 637 |
+
and masked tokens are ignored.
|
| 638 |
+
|
| 639 |
+
Arguments:
|
| 640 |
+
attention_mask (:obj:`torch.Tensor`):
|
| 641 |
+
Mask with ones indicating tokens to attend to,
|
| 642 |
+
zeros for tokens to ignore.
|
| 643 |
+
input_shape (:obj:`Tuple[int]`):
|
| 644 |
+
The shape of the input to the model.
|
| 645 |
+
device: (:obj:`torch.device`):
|
| 646 |
+
The device of the input to the model.
|
| 647 |
+
|
| 648 |
+
Returns:
|
| 649 |
+
:obj:`torch.Tensor` The extended attention mask, \
|
| 650 |
+
with a the same dtype as :obj:`attention_mask.dtype`.
|
| 651 |
+
"""
|
| 652 |
+
# We can provide a self-attention mask of dimensions
|
| 653 |
+
# [batch_size, from_seq_length, to_seq_length]
|
| 654 |
+
# ourselves in which case we just need to make it broadcastable
|
| 655 |
+
# to all heads.
|
| 656 |
+
if attention_mask.dim() == 4:
|
| 657 |
+
extended_attention_mask = attention_mask[:, :, None, :, :]
|
| 658 |
+
extended_attention_mask = extended_attention_mask.to(
|
| 659 |
+
dtype=self.dtype
|
| 660 |
+
) # fp16 compatibility
|
| 661 |
+
extended_attention_mask = (1.0 - extended_attention_mask) \
|
| 662 |
+
* -10000.0
|
| 663 |
+
return extended_attention_mask
|
| 664 |
+
else:
|
| 665 |
+
return super().get_extended_attention_mask(
|
| 666 |
+
attention_mask, input_shape, device
|
| 667 |
+
)
|
| 668 |
+
|
| 669 |
+
|
| 670 |
+
class MultiLayerAttentionMaskBertEncoder(BertEncoder):
|
| 671 |
+
"""extend BertEncoder with the capability of
|
| 672 |
+
multiple layers of attention mask."""
|
| 673 |
+
|
| 674 |
+
def forward(
|
| 675 |
+
self,
|
| 676 |
+
hidden_states,
|
| 677 |
+
attention_mask=None,
|
| 678 |
+
head_mask=None,
|
| 679 |
+
encoder_hidden_states=None,
|
| 680 |
+
encoder_attention_mask=None,
|
| 681 |
+
output_attentions=False,
|
| 682 |
+
output_hidden_states=False,
|
| 683 |
+
return_dict=False,
|
| 684 |
+
):
|
| 685 |
+
all_hidden_states = () if output_hidden_states else None
|
| 686 |
+
all_attentions = () if output_attentions else None
|
| 687 |
+
for i, layer_module in enumerate(self.layer):
|
| 688 |
+
if output_hidden_states:
|
| 689 |
+
all_hidden_states = all_hidden_states + (hidden_states,)
|
| 690 |
+
layer_head_mask = head_mask[i] if head_mask is not None else None
|
| 691 |
+
|
| 692 |
+
layer_attention_mask = (
|
| 693 |
+
attention_mask[:, i, :, :, :]
|
| 694 |
+
if attention_mask.dim() == 5
|
| 695 |
+
else attention_mask
|
| 696 |
+
)
|
| 697 |
+
|
| 698 |
+
if getattr(self.config, "gradient_checkpointing", False):
|
| 699 |
+
|
| 700 |
+
def create_custom_forward(module):
|
| 701 |
+
def custom_forward(*inputs):
|
| 702 |
+
return module(*inputs, output_attentions)
|
| 703 |
+
|
| 704 |
+
return custom_forward
|
| 705 |
+
|
| 706 |
+
layer_outputs = torch.utils.checkpoint.checkpoint(
|
| 707 |
+
create_custom_forward(layer_module),
|
| 708 |
+
hidden_states,
|
| 709 |
+
layer_attention_mask,
|
| 710 |
+
layer_head_mask,
|
| 711 |
+
encoder_hidden_states,
|
| 712 |
+
encoder_attention_mask,
|
| 713 |
+
)
|
| 714 |
+
else:
|
| 715 |
+
layer_outputs = layer_module(
|
| 716 |
+
hidden_states,
|
| 717 |
+
layer_attention_mask,
|
| 718 |
+
layer_head_mask,
|
| 719 |
+
encoder_hidden_states,
|
| 720 |
+
encoder_attention_mask,
|
| 721 |
+
output_attentions,
|
| 722 |
+
)
|
| 723 |
+
hidden_states = layer_outputs[0]
|
| 724 |
+
if output_attentions:
|
| 725 |
+
all_attentions = all_attentions + (layer_outputs[1],)
|
| 726 |
+
|
| 727 |
+
if output_hidden_states:
|
| 728 |
+
all_hidden_states = all_hidden_states + (hidden_states,)
|
| 729 |
+
|
| 730 |
+
return tuple(
|
| 731 |
+
v
|
| 732 |
+
for v in [hidden_states, all_hidden_states, all_attentions]
|
| 733 |
+
if v is not None
|
| 734 |
+
)
|
data/fairseq/examples/MMPT/mmpt/modules/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
from .mm import *
|
| 6 |
+
|
| 7 |
+
try:
|
| 8 |
+
from .expmm import *
|
| 9 |
+
except ImportError:
|
| 10 |
+
pass
|
data/fairseq/examples/MMPT/mmpt/modules/mm.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2018 The Google AI Language Team Authors and The HuggingFace Inc. team.
|
| 3 |
+
# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved.
|
| 4 |
+
#
|
| 5 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 6 |
+
# you may not use this file except in compliance with the License.
|
| 7 |
+
# You may obtain a copy of the License at
|
| 8 |
+
#
|
| 9 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 10 |
+
#
|
| 11 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 12 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 13 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 14 |
+
# See the License for the specific language governing permissions and
|
| 15 |
+
# limitations under the License.
|
| 16 |
+
# Copyright (c) Facebook, Inc. All Rights Reserved
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
import torch
|
| 20 |
+
|
| 21 |
+
from torch import nn
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
from transformers.modeling_bert import (
|
| 25 |
+
BertEmbeddings,
|
| 26 |
+
ACT2FN,
|
| 27 |
+
)
|
| 28 |
+
except ImportError:
|
| 29 |
+
pass
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class VideoTokenMLP(nn.Module):
|
| 33 |
+
def __init__(self, config):
|
| 34 |
+
super().__init__()
|
| 35 |
+
input_dim = config.input_dim if hasattr(config, "input_dim") else 512
|
| 36 |
+
self.linear1 = nn.Linear(input_dim, config.hidden_size)
|
| 37 |
+
self.LayerNorm = nn.LayerNorm(config.hidden_size)
|
| 38 |
+
self.activation = ACT2FN[config.hidden_act]
|
| 39 |
+
self.linear2 = nn.Linear(config.hidden_size, config.hidden_size)
|
| 40 |
+
|
| 41 |
+
def forward(self, hidden_states):
|
| 42 |
+
hidden_states = self.linear1(hidden_states)
|
| 43 |
+
hidden_states = self.activation(hidden_states)
|
| 44 |
+
hidden_states = self.LayerNorm(hidden_states)
|
| 45 |
+
hidden_states = self.linear2(hidden_states)
|
| 46 |
+
return hidden_states
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class MMBertEmbeddings(BertEmbeddings):
|
| 50 |
+
def __init__(self, config):
|
| 51 |
+
super().__init__(config)
|
| 52 |
+
self.max_video_len = config.max_video_len
|
| 53 |
+
if hasattr(config, "use_seg_emb") and config.use_seg_emb:
|
| 54 |
+
"""the original VLM paper uses seg_embeddings for temporal space.
|
| 55 |
+
although not used it changed the randomness of initialization.
|
| 56 |
+
we keep it for reproducibility.
|
| 57 |
+
"""
|
| 58 |
+
self.seg_embeddings = nn.Embedding(256, config.hidden_size)
|
| 59 |
+
|
| 60 |
+
def forward(
|
| 61 |
+
self,
|
| 62 |
+
input_ids,
|
| 63 |
+
input_video_embeds,
|
| 64 |
+
token_type_ids=None,
|
| 65 |
+
position_ids=None,
|
| 66 |
+
inputs_embeds=None,
|
| 67 |
+
):
|
| 68 |
+
input_tensor = input_ids if input_ids is not None else inputs_embeds
|
| 69 |
+
if input_video_embeds is not None:
|
| 70 |
+
input_shape = (
|
| 71 |
+
input_tensor.size(0),
|
| 72 |
+
input_tensor.size(1) + input_video_embeds.size(1),
|
| 73 |
+
)
|
| 74 |
+
else:
|
| 75 |
+
input_shape = (input_tensor.size(0), input_tensor.size(1))
|
| 76 |
+
|
| 77 |
+
if position_ids is None:
|
| 78 |
+
"""
|
| 79 |
+
Auto skip position embeddings for text only case.
|
| 80 |
+
use cases:
|
| 81 |
+
(1) action localization and segmentation:
|
| 82 |
+
feed in len-1 dummy video token needs text part to
|
| 83 |
+
skip input_video_embeds.size(1) for the right
|
| 84 |
+
position_ids for video [SEP] and rest text tokens.
|
| 85 |
+
(2) MMFusionShare for two forward passings:
|
| 86 |
+
in `forward_text`: input_video_embeds is None.
|
| 87 |
+
need to skip video [SEP] token.
|
| 88 |
+
|
| 89 |
+
# video_len + 1: [CLS] + video_embed
|
| 90 |
+
# self.max_video_len + 1: [SEP] for video.
|
| 91 |
+
# self.max_video_len + 2: [SEP] for video.
|
| 92 |
+
# self.max_video_len + input_ids.size(1): rest for text.
|
| 93 |
+
"""
|
| 94 |
+
if input_video_embeds is not None:
|
| 95 |
+
video_len = input_video_embeds.size(1)
|
| 96 |
+
starting_offset = self.max_video_len + 1 # video [SEP]
|
| 97 |
+
ending_offset = self.max_video_len + input_ids.size(1)
|
| 98 |
+
else:
|
| 99 |
+
video_len = 0
|
| 100 |
+
starting_offset = self.max_video_len + 2 # first text token.
|
| 101 |
+
ending_offset = self.max_video_len + input_ids.size(1) + 1
|
| 102 |
+
position_ids = torch.cat([
|
| 103 |
+
self.position_ids[:, :video_len + 1],
|
| 104 |
+
self.position_ids[:, starting_offset:ending_offset]
|
| 105 |
+
], dim=1)
|
| 106 |
+
|
| 107 |
+
if token_type_ids is None:
|
| 108 |
+
token_type_ids = torch.zeros(
|
| 109 |
+
input_shape, dtype=torch.long, device=self.position_ids.device
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
"""
|
| 113 |
+
the format of input_ids is [CLS] [SEP] caption [SEP] padding.
|
| 114 |
+
the goal is to build [CLS] video tokens [SEP] caption [SEP] .
|
| 115 |
+
"""
|
| 116 |
+
if inputs_embeds is None:
|
| 117 |
+
inputs_embeds = self.word_embeddings(input_ids)
|
| 118 |
+
if input_video_embeds is not None:
|
| 119 |
+
inputs_mm_embeds = torch.cat([
|
| 120 |
+
inputs_embeds[:, :1], input_video_embeds, inputs_embeds[:, 1:]
|
| 121 |
+
], dim=1)
|
| 122 |
+
else:
|
| 123 |
+
# text only for `MMFusionShare`.
|
| 124 |
+
inputs_mm_embeds = inputs_embeds
|
| 125 |
+
|
| 126 |
+
position_embeddings = self.position_embeddings(position_ids)
|
| 127 |
+
token_type_embeddings = self.token_type_embeddings(token_type_ids)
|
| 128 |
+
embeddings = inputs_mm_embeds + position_embeddings
|
| 129 |
+
embeddings += token_type_embeddings
|
| 130 |
+
|
| 131 |
+
embeddings = self.LayerNorm(embeddings)
|
| 132 |
+
embeddings = self.dropout(embeddings)
|
| 133 |
+
return embeddings
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
class AlignHead(nn.Module):
|
| 137 |
+
"""this will load pre-trained weights for NSP, which is desirable."""
|
| 138 |
+
|
| 139 |
+
def __init__(self, config):
|
| 140 |
+
super().__init__()
|
| 141 |
+
self.seq_relationship = nn.Linear(config.hidden_size, 2)
|
| 142 |
+
|
| 143 |
+
def forward(self, dropout_pooled_output):
|
| 144 |
+
logits = self.seq_relationship(dropout_pooled_output)
|
| 145 |
+
return logits
|
data/fairseq/examples/MMPT/mmpt/modules/retri.py
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
import os
|
| 6 |
+
import numpy as np
|
| 7 |
+
import pickle
|
| 8 |
+
import time
|
| 9 |
+
|
| 10 |
+
try:
|
| 11 |
+
import faiss
|
| 12 |
+
except ImportError:
|
| 13 |
+
pass
|
| 14 |
+
|
| 15 |
+
from collections import defaultdict
|
| 16 |
+
|
| 17 |
+
from ..utils import get_local_rank, print_on_rank0
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class VectorRetriever(object):
|
| 21 |
+
"""
|
| 22 |
+
How2 Video Retriver.
|
| 23 |
+
Reference usage of FAISS:
|
| 24 |
+
https://github.com/fairinternal/fairseq-py/blob/paraphrase_pretraining/fairseq/data/multilingual_faiss_dataset.py
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
def __init__(self, hidden_size, cent, db_type, examples_per_cent_to_train):
|
| 28 |
+
if db_type == "flatl2":
|
| 29 |
+
quantizer = faiss.IndexFlatL2(hidden_size) # the other index
|
| 30 |
+
self.db = faiss.IndexIVFFlat(
|
| 31 |
+
quantizer, hidden_size, cent, faiss.METRIC_L2)
|
| 32 |
+
elif db_type == "pq":
|
| 33 |
+
self.db = faiss.index_factory(
|
| 34 |
+
hidden_size, f"IVF{cent}_HNSW32,PQ32"
|
| 35 |
+
)
|
| 36 |
+
else:
|
| 37 |
+
raise ValueError("unknown type of db", db_type)
|
| 38 |
+
self.train_thres = cent * examples_per_cent_to_train
|
| 39 |
+
self.train_cache = []
|
| 40 |
+
self.train_len = 0
|
| 41 |
+
self.videoid_to_vectoridx = {}
|
| 42 |
+
self.vectoridx_to_videoid = None
|
| 43 |
+
self.make_direct_maps_done = False
|
| 44 |
+
|
| 45 |
+
def make_direct_maps(self):
|
| 46 |
+
faiss.downcast_index(self.db).make_direct_map()
|
| 47 |
+
|
| 48 |
+
def __len__(self):
|
| 49 |
+
return self.db.ntotal
|
| 50 |
+
|
| 51 |
+
def save(self, out_dir):
|
| 52 |
+
faiss.write_index(
|
| 53 |
+
self.db,
|
| 54 |
+
os.path.join(out_dir, "faiss_idx")
|
| 55 |
+
)
|
| 56 |
+
with open(
|
| 57 |
+
os.path.join(
|
| 58 |
+
out_dir, "videoid_to_vectoridx.pkl"),
|
| 59 |
+
"wb") as fw:
|
| 60 |
+
pickle.dump(
|
| 61 |
+
self.videoid_to_vectoridx, fw,
|
| 62 |
+
protocol=pickle.HIGHEST_PROTOCOL
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
def load(self, out_dir):
|
| 66 |
+
fn = os.path.join(out_dir, "faiss_idx")
|
| 67 |
+
self.db = faiss.read_index(fn)
|
| 68 |
+
with open(
|
| 69 |
+
os.path.join(out_dir, "videoid_to_vectoridx.pkl"), "rb") as fr:
|
| 70 |
+
self.videoid_to_vectoridx = pickle.load(fr)
|
| 71 |
+
|
| 72 |
+
def add(self, hidden_states, video_ids, last=False):
|
| 73 |
+
assert len(hidden_states) == len(video_ids), "{}, {}".format(
|
| 74 |
+
str(len(hidden_states)), str(len(video_ids)))
|
| 75 |
+
assert len(hidden_states.shape) == 2
|
| 76 |
+
assert hidden_states.dtype == np.float32
|
| 77 |
+
|
| 78 |
+
valid_idx = []
|
| 79 |
+
for idx, video_id in enumerate(video_ids):
|
| 80 |
+
if video_id not in self.videoid_to_vectoridx:
|
| 81 |
+
valid_idx.append(idx)
|
| 82 |
+
self.videoid_to_vectoridx[video_id] = \
|
| 83 |
+
len(self.videoid_to_vectoridx)
|
| 84 |
+
|
| 85 |
+
hidden_states = hidden_states[valid_idx]
|
| 86 |
+
if not self.db.is_trained:
|
| 87 |
+
self.train_cache.append(hidden_states)
|
| 88 |
+
self.train_len += hidden_states.shape[0]
|
| 89 |
+
if self.train_len < self.train_thres:
|
| 90 |
+
return
|
| 91 |
+
self.finalize_training()
|
| 92 |
+
else:
|
| 93 |
+
self.db.add(hidden_states)
|
| 94 |
+
|
| 95 |
+
def finalize_training(self):
|
| 96 |
+
hidden_states = np.concatenate(self.train_cache, axis=0)
|
| 97 |
+
del self.train_cache
|
| 98 |
+
local_rank = get_local_rank()
|
| 99 |
+
if local_rank == 0:
|
| 100 |
+
start = time.time()
|
| 101 |
+
print("training db on", self.train_thres, "/", self.train_len)
|
| 102 |
+
self.db.train(hidden_states[:self.train_thres])
|
| 103 |
+
if local_rank == 0:
|
| 104 |
+
print("training db for", time.time() - start)
|
| 105 |
+
self.db.add(hidden_states)
|
| 106 |
+
|
| 107 |
+
def search(
|
| 108 |
+
self,
|
| 109 |
+
query_hidden_states,
|
| 110 |
+
orig_dist,
|
| 111 |
+
):
|
| 112 |
+
if len(self.videoid_to_vectoridx) != self.db.ntotal:
|
| 113 |
+
raise ValueError(
|
| 114 |
+
"cannot search: size mismatch in-between index and db",
|
| 115 |
+
len(self.videoid_to_vectoridx),
|
| 116 |
+
self.db.ntotal
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
if self.vectoridx_to_videoid is None:
|
| 120 |
+
self.vectoridx_to_videoid = {
|
| 121 |
+
self.videoid_to_vectoridx[videoid]: videoid
|
| 122 |
+
for videoid in self.videoid_to_vectoridx
|
| 123 |
+
}
|
| 124 |
+
assert len(self.vectoridx_to_videoid) \
|
| 125 |
+
== len(self.videoid_to_vectoridx)
|
| 126 |
+
|
| 127 |
+
# MultilingualFaissDataset uses the following; not sure the purpose.
|
| 128 |
+
# faiss.ParameterSpace().set_index_parameter(self.db, "nprobe", 10)
|
| 129 |
+
queried_dist, index = self.db.search(query_hidden_states, 1)
|
| 130 |
+
queried_dist, index = queried_dist[:, 0], index[:, 0]
|
| 131 |
+
|
| 132 |
+
outputs = np.array(
|
| 133 |
+
[self.vectoridx_to_videoid[_index]
|
| 134 |
+
if _index != -1 else (-1, -1, -1) for _index in index],
|
| 135 |
+
dtype=np.int32)
|
| 136 |
+
outputs[queried_dist <= orig_dist] = -1
|
| 137 |
+
return outputs
|
| 138 |
+
|
| 139 |
+
def search_by_video_ids(
|
| 140 |
+
self,
|
| 141 |
+
video_ids,
|
| 142 |
+
retri_factor
|
| 143 |
+
):
|
| 144 |
+
if len(self.videoid_to_vectoridx) != self.db.ntotal:
|
| 145 |
+
raise ValueError(
|
| 146 |
+
len(self.videoid_to_vectoridx),
|
| 147 |
+
self.db.ntotal
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
if not self.make_direct_maps_done:
|
| 151 |
+
self.make_direct_maps()
|
| 152 |
+
|
| 153 |
+
if self.vectoridx_to_videoid is None:
|
| 154 |
+
self.vectoridx_to_videoid = {
|
| 155 |
+
self.videoid_to_vectoridx[videoid]: videoid
|
| 156 |
+
for videoid in self.videoid_to_vectoridx
|
| 157 |
+
}
|
| 158 |
+
assert len(self.vectoridx_to_videoid) \
|
| 159 |
+
== len(self.videoid_to_vectoridx)
|
| 160 |
+
|
| 161 |
+
query_hidden_states = []
|
| 162 |
+
vector_ids = []
|
| 163 |
+
for video_id in video_ids:
|
| 164 |
+
vector_id = self.videoid_to_vectoridx[video_id]
|
| 165 |
+
vector_ids.append(vector_id)
|
| 166 |
+
query_hidden_state = self.db.reconstruct(vector_id)
|
| 167 |
+
query_hidden_states.append(query_hidden_state)
|
| 168 |
+
query_hidden_states = np.stack(query_hidden_states)
|
| 169 |
+
|
| 170 |
+
# MultilingualFaissDataset uses the following; not sure the reason.
|
| 171 |
+
# faiss.ParameterSpace().set_index_parameter(self.db, "nprobe", 10)
|
| 172 |
+
_, index = self.db.search(query_hidden_states, retri_factor)
|
| 173 |
+
outputs = []
|
| 174 |
+
for sample_idx, sample in enumerate(index):
|
| 175 |
+
# the first video_id is always the video itself.
|
| 176 |
+
cands = [video_ids[sample_idx]]
|
| 177 |
+
for vector_idx in sample:
|
| 178 |
+
if vector_idx >= 0 \
|
| 179 |
+
and vector_ids[sample_idx] != vector_idx:
|
| 180 |
+
cands.append(
|
| 181 |
+
self.vectoridx_to_videoid[vector_idx]
|
| 182 |
+
)
|
| 183 |
+
outputs.append(cands)
|
| 184 |
+
return outputs
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
class VectorRetrieverDM(VectorRetriever):
|
| 188 |
+
"""
|
| 189 |
+
with direct map.
|
| 190 |
+
How2 Video Retriver.
|
| 191 |
+
Reference usage of FAISS:
|
| 192 |
+
https://github.com/fairinternal/fairseq-py/blob/paraphrase_pretraining/fairseq/data/multilingual_faiss_dataset.py
|
| 193 |
+
"""
|
| 194 |
+
|
| 195 |
+
def __init__(
|
| 196 |
+
self,
|
| 197 |
+
hidden_size,
|
| 198 |
+
cent,
|
| 199 |
+
db_type,
|
| 200 |
+
examples_per_cent_to_train
|
| 201 |
+
):
|
| 202 |
+
super().__init__(
|
| 203 |
+
hidden_size, cent, db_type, examples_per_cent_to_train)
|
| 204 |
+
self.make_direct_maps_done = False
|
| 205 |
+
|
| 206 |
+
def make_direct_maps(self):
|
| 207 |
+
faiss.downcast_index(self.db).make_direct_map()
|
| 208 |
+
self.make_direct_maps_done = True
|
| 209 |
+
|
| 210 |
+
def search(
|
| 211 |
+
self,
|
| 212 |
+
query_hidden_states,
|
| 213 |
+
orig_dist,
|
| 214 |
+
):
|
| 215 |
+
if len(self.videoid_to_vectoridx) != self.db.ntotal:
|
| 216 |
+
raise ValueError(
|
| 217 |
+
len(self.videoid_to_vectoridx),
|
| 218 |
+
self.db.ntotal
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
if not self.make_direct_maps_done:
|
| 222 |
+
self.make_direct_maps()
|
| 223 |
+
if self.vectoridx_to_videoid is None:
|
| 224 |
+
self.vectoridx_to_videoid = {
|
| 225 |
+
self.videoid_to_vectoridx[videoid]: videoid
|
| 226 |
+
for videoid in self.videoid_to_vectoridx
|
| 227 |
+
}
|
| 228 |
+
assert len(self.vectoridx_to_videoid) \
|
| 229 |
+
== len(self.videoid_to_vectoridx)
|
| 230 |
+
|
| 231 |
+
# MultilingualFaissDataset uses the following; not sure the reason.
|
| 232 |
+
# faiss.ParameterSpace().set_index_parameter(self.db, "nprobe", 10)
|
| 233 |
+
queried_dist, index = self.db.search(query_hidden_states, 1)
|
| 234 |
+
outputs = []
|
| 235 |
+
for sample_idx, sample in enumerate(index):
|
| 236 |
+
# and queried_dist[sample_idx] < thres \
|
| 237 |
+
if sample >= 0 \
|
| 238 |
+
and queried_dist[sample_idx] < orig_dist[sample_idx]:
|
| 239 |
+
outputs.append(self.vectoridx_to_videoid[sample])
|
| 240 |
+
else:
|
| 241 |
+
outputs.append(None)
|
| 242 |
+
return outputs
|
| 243 |
+
|
| 244 |
+
def search_by_video_ids(
|
| 245 |
+
self,
|
| 246 |
+
video_ids,
|
| 247 |
+
retri_factor=8
|
| 248 |
+
):
|
| 249 |
+
if len(self.videoid_to_vectoridx) != self.db.ntotal:
|
| 250 |
+
raise ValueError(
|
| 251 |
+
len(self.videoid_to_vectoridx),
|
| 252 |
+
self.db.ntotal
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
if not self.make_direct_maps_done:
|
| 256 |
+
self.make_direct_maps()
|
| 257 |
+
if self.vectoridx_to_videoid is None:
|
| 258 |
+
self.vectoridx_to_videoid = {
|
| 259 |
+
self.videoid_to_vectoridx[videoid]: videoid
|
| 260 |
+
for videoid in self.videoid_to_vectoridx
|
| 261 |
+
}
|
| 262 |
+
assert len(self.vectoridx_to_videoid) \
|
| 263 |
+
== len(self.videoid_to_vectoridx)
|
| 264 |
+
|
| 265 |
+
query_hidden_states = []
|
| 266 |
+
vector_ids = []
|
| 267 |
+
for video_id in video_ids:
|
| 268 |
+
vector_id = self.videoid_to_vectoridx[video_id]
|
| 269 |
+
vector_ids.append(vector_id)
|
| 270 |
+
query_hidden_state = self.db.reconstruct(vector_id)
|
| 271 |
+
query_hidden_states.append(query_hidden_state)
|
| 272 |
+
query_hidden_states = np.stack(query_hidden_states)
|
| 273 |
+
|
| 274 |
+
# MultilingualFaissDataset uses the following; not sure the reason.
|
| 275 |
+
# faiss.ParameterSpace().set_index_parameter(self.db, "nprobe", 10)
|
| 276 |
+
_, index = self.db.search(query_hidden_states, retri_factor)
|
| 277 |
+
outputs = []
|
| 278 |
+
for sample_idx, sample in enumerate(index):
|
| 279 |
+
# the first video_id is always the video itself.
|
| 280 |
+
cands = [video_ids[sample_idx]]
|
| 281 |
+
for vector_idx in sample:
|
| 282 |
+
if vector_idx >= 0 \
|
| 283 |
+
and vector_ids[sample_idx] != vector_idx:
|
| 284 |
+
cands.append(
|
| 285 |
+
self.vectoridx_to_videoid[vector_idx]
|
| 286 |
+
)
|
| 287 |
+
outputs.append(cands)
|
| 288 |
+
return outputs
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
class MMVectorRetriever(VectorRetrieverDM):
|
| 292 |
+
"""
|
| 293 |
+
multimodal vector retriver:
|
| 294 |
+
text retrieve video or video retrieve text.
|
| 295 |
+
"""
|
| 296 |
+
|
| 297 |
+
def __init__(self, hidden_size, cent, db_type, examples_per_cent_to_train):
|
| 298 |
+
super().__init__(
|
| 299 |
+
hidden_size, cent, db_type, examples_per_cent_to_train)
|
| 300 |
+
video_db = self.db
|
| 301 |
+
super().__init__(
|
| 302 |
+
hidden_size, cent, db_type, examples_per_cent_to_train)
|
| 303 |
+
text_db = self.db
|
| 304 |
+
self.db = {"video": video_db, "text": text_db}
|
| 305 |
+
self.video_to_videoid = defaultdict(list)
|
| 306 |
+
|
| 307 |
+
def __len__(self):
|
| 308 |
+
assert self.db["video"].ntotal == self.db["text"].ntotal
|
| 309 |
+
return self.db["video"].ntotal
|
| 310 |
+
|
| 311 |
+
def make_direct_maps(self):
|
| 312 |
+
faiss.downcast_index(self.db["video"]).make_direct_map()
|
| 313 |
+
faiss.downcast_index(self.db["text"]).make_direct_map()
|
| 314 |
+
|
| 315 |
+
def save(self, out_dir):
|
| 316 |
+
faiss.write_index(
|
| 317 |
+
self.db["video"],
|
| 318 |
+
os.path.join(out_dir, "video_faiss_idx")
|
| 319 |
+
)
|
| 320 |
+
faiss.write_index(
|
| 321 |
+
self.db["text"],
|
| 322 |
+
os.path.join(out_dir, "text_faiss_idx")
|
| 323 |
+
)
|
| 324 |
+
|
| 325 |
+
with open(
|
| 326 |
+
os.path.join(
|
| 327 |
+
out_dir, "videoid_to_vectoridx.pkl"),
|
| 328 |
+
"wb") as fw:
|
| 329 |
+
pickle.dump(
|
| 330 |
+
self.videoid_to_vectoridx, fw,
|
| 331 |
+
protocol=pickle.HIGHEST_PROTOCOL
|
| 332 |
+
)
|
| 333 |
+
|
| 334 |
+
def load(self, out_dir):
|
| 335 |
+
fn = os.path.join(out_dir, "video_faiss_idx")
|
| 336 |
+
video_db = faiss.read_index(fn)
|
| 337 |
+
fn = os.path.join(out_dir, "text_faiss_idx")
|
| 338 |
+
text_db = faiss.read_index(fn)
|
| 339 |
+
self.db = {"video": video_db, "text": text_db}
|
| 340 |
+
with open(
|
| 341 |
+
os.path.join(out_dir, "videoid_to_vectoridx.pkl"), "rb") as fr:
|
| 342 |
+
self.videoid_to_vectoridx = pickle.load(fr)
|
| 343 |
+
self.video_to_videoid = defaultdict(list)
|
| 344 |
+
|
| 345 |
+
def add(self, hidden_states, video_ids):
|
| 346 |
+
"""hidden_states is a pair `(video, text)`"""
|
| 347 |
+
assert len(hidden_states) == len(video_ids), "{}, {}".format(
|
| 348 |
+
str(len(hidden_states)), str(len(video_ids)))
|
| 349 |
+
assert len(hidden_states.shape) == 3
|
| 350 |
+
assert len(self.video_to_videoid) == 0
|
| 351 |
+
|
| 352 |
+
valid_idx = []
|
| 353 |
+
for idx, video_id in enumerate(video_ids):
|
| 354 |
+
if video_id not in self.videoid_to_vectoridx:
|
| 355 |
+
valid_idx.append(idx)
|
| 356 |
+
self.videoid_to_vectoridx[video_id] = \
|
| 357 |
+
len(self.videoid_to_vectoridx)
|
| 358 |
+
|
| 359 |
+
batch_size = hidden_states.shape[0]
|
| 360 |
+
hidden_states = hidden_states[valid_idx]
|
| 361 |
+
|
| 362 |
+
hidden_states = np.transpose(hidden_states, (1, 0, 2)).copy()
|
| 363 |
+
if not self.db["video"].is_trained:
|
| 364 |
+
self.train_cache.append(hidden_states)
|
| 365 |
+
train_len = batch_size * len(self.train_cache)
|
| 366 |
+
if train_len < self.train_thres:
|
| 367 |
+
return
|
| 368 |
+
|
| 369 |
+
hidden_states = np.concatenate(self.train_cache, axis=1)
|
| 370 |
+
del self.train_cache
|
| 371 |
+
self.db["video"].train(hidden_states[0, :self.train_thres])
|
| 372 |
+
self.db["text"].train(hidden_states[1, :self.train_thres])
|
| 373 |
+
self.db["video"].add(hidden_states[0])
|
| 374 |
+
self.db["text"].add(hidden_states[1])
|
| 375 |
+
|
| 376 |
+
def get_clips_by_video_id(self, video_id):
|
| 377 |
+
if not self.video_to_videoid:
|
| 378 |
+
for video_id, video_clip, text_clip in self.videoid_to_vectoridx:
|
| 379 |
+
self.video_to_videoid[video_id].append(
|
| 380 |
+
(video_id, video_clip, text_clip))
|
| 381 |
+
return self.video_to_videoid[video_id]
|
| 382 |
+
|
| 383 |
+
def search(
|
| 384 |
+
self,
|
| 385 |
+
video_ids,
|
| 386 |
+
target_modality,
|
| 387 |
+
retri_factor=8
|
| 388 |
+
):
|
| 389 |
+
if len(self.videoid_to_vectoridx) != len(self):
|
| 390 |
+
raise ValueError(
|
| 391 |
+
len(self.videoid_to_vectoridx),
|
| 392 |
+
len(self)
|
| 393 |
+
)
|
| 394 |
+
|
| 395 |
+
if not self.make_direct_maps_done:
|
| 396 |
+
self.make_direct_maps()
|
| 397 |
+
if self.vectoridx_to_videoid is None:
|
| 398 |
+
self.vectoridx_to_videoid = {
|
| 399 |
+
self.videoid_to_vectoridx[videoid]: videoid
|
| 400 |
+
for videoid in self.videoid_to_vectoridx
|
| 401 |
+
}
|
| 402 |
+
assert len(self.vectoridx_to_videoid) \
|
| 403 |
+
== len(self.videoid_to_vectoridx)
|
| 404 |
+
|
| 405 |
+
src_modality = "text" if target_modality == "video" else "video"
|
| 406 |
+
|
| 407 |
+
query_hidden_states = []
|
| 408 |
+
vector_ids = []
|
| 409 |
+
for video_id in video_ids:
|
| 410 |
+
vector_id = self.videoid_to_vectoridx[video_id]
|
| 411 |
+
vector_ids.append(vector_id)
|
| 412 |
+
query_hidden_state = self.db[src_modality].reconstruct(vector_id)
|
| 413 |
+
query_hidden_states.append(query_hidden_state)
|
| 414 |
+
query_hidden_states = np.stack(query_hidden_states)
|
| 415 |
+
|
| 416 |
+
# MultilingualFaissDataset uses the following; not sure the reason.
|
| 417 |
+
# faiss.ParameterSpace().set_index_parameter(self.db, "nprobe", 10)
|
| 418 |
+
_, index = self.db[target_modality].search(
|
| 419 |
+
query_hidden_states, retri_factor)
|
| 420 |
+
outputs = []
|
| 421 |
+
for sample_idx, sample in enumerate(index):
|
| 422 |
+
cands = []
|
| 423 |
+
for vector_idx in sample:
|
| 424 |
+
if vector_idx >= 0:
|
| 425 |
+
cands.append(
|
| 426 |
+
self.vectoridx_to_videoid[vector_idx]
|
| 427 |
+
)
|
| 428 |
+
outputs.append(cands)
|
| 429 |
+
return outputs
|
data/fairseq/examples/MMPT/mmpt/modules/vectorpool.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. All Rights Reserved
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import os
|
| 5 |
+
import numpy as np
|
| 6 |
+
import pickle
|
| 7 |
+
|
| 8 |
+
from . import retri
|
| 9 |
+
from ..utils import get_local_rank
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class VectorPool(object):
|
| 13 |
+
"""
|
| 14 |
+
Base class of retrieval space.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
def __init__(self, config):
|
| 18 |
+
from transformers import AutoConfig
|
| 19 |
+
self.hidden_size = AutoConfig.from_pretrained(
|
| 20 |
+
config.dataset.bert_name).hidden_size
|
| 21 |
+
self.retriever_cls = getattr(retri, config.retriever_cls)
|
| 22 |
+
|
| 23 |
+
def __call__(self, sample, **kwargs):
|
| 24 |
+
raise NotImplementedError
|
| 25 |
+
|
| 26 |
+
def build_retriver(
|
| 27 |
+
self,
|
| 28 |
+
retriever_cls=None,
|
| 29 |
+
hidden_size=None,
|
| 30 |
+
centroids=512,
|
| 31 |
+
db_type="flatl2",
|
| 32 |
+
examples_per_cent_to_train=48
|
| 33 |
+
):
|
| 34 |
+
|
| 35 |
+
"""merge results from multiple gpus and return a retriver.."""
|
| 36 |
+
self.retriver = retriever_cls(
|
| 37 |
+
hidden_size, centroids, db_type, examples_per_cent_to_train)
|
| 38 |
+
return self.retriver
|
| 39 |
+
|
| 40 |
+
def __repr__(self):
|
| 41 |
+
if hasattr(self, "retriver"):
|
| 42 |
+
retriver_name = str(len(self.retriver))
|
| 43 |
+
else:
|
| 44 |
+
retriver_name = "no retriver field yet"
|
| 45 |
+
return self.__class__.__name__ \
|
| 46 |
+
+ "(" + retriver_name + ")"
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class VideoVectorPool(VectorPool):
|
| 50 |
+
"""
|
| 51 |
+
average clips of a video as video representation.
|
| 52 |
+
"""
|
| 53 |
+
def __init__(self, config):
|
| 54 |
+
super().__init__(config)
|
| 55 |
+
self.build_retriver(self.retriever_cls, self.hidden_size)
|
| 56 |
+
|
| 57 |
+
def __call__(self, sample, subsampling, **kwargs):
|
| 58 |
+
hidden_states = (
|
| 59 |
+
sample["pooled_video"] + sample["pooled_text"]) / 2.
|
| 60 |
+
hidden_states = hidden_states.view(
|
| 61 |
+
-1, subsampling,
|
| 62 |
+
hidden_states.size(-1))
|
| 63 |
+
hidden_states = torch.mean(hidden_states, dim=1)
|
| 64 |
+
hidden_states = hidden_states.cpu().detach().numpy()
|
| 65 |
+
video_ids = []
|
| 66 |
+
for offset_idx, video_id in enumerate(sample["video_id"]):
|
| 67 |
+
if isinstance(video_id, tuple) and len(video_id) == 3:
|
| 68 |
+
# a sharded video_id.
|
| 69 |
+
video_id = video_id[0]
|
| 70 |
+
video_ids.append(video_id)
|
| 71 |
+
assert len(video_ids) == len(hidden_states)
|
| 72 |
+
self.retriver.add(
|
| 73 |
+
hidden_states.astype("float32"),
|
| 74 |
+
video_ids
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class DistributedVectorPool(VectorPool):
|
| 79 |
+
"""
|
| 80 |
+
support sync of multiple gpus/nodes.
|
| 81 |
+
"""
|
| 82 |
+
def __init__(self, config):
|
| 83 |
+
super().__init__(config)
|
| 84 |
+
self.out_dir = os.path.join(
|
| 85 |
+
config.fairseq.checkpoint.save_dir,
|
| 86 |
+
"retri")
|
| 87 |
+
os.makedirs(self.out_dir, exist_ok=True)
|
| 88 |
+
self.hidden_states = []
|
| 89 |
+
self.video_ids = []
|
| 90 |
+
|
| 91 |
+
def build_retriver(
|
| 92 |
+
self,
|
| 93 |
+
retriever_cls=None,
|
| 94 |
+
hidden_size=None,
|
| 95 |
+
centroids=4096,
|
| 96 |
+
db_type="flatl2",
|
| 97 |
+
examples_per_cent_to_train=48
|
| 98 |
+
):
|
| 99 |
+
if retriever_cls is None:
|
| 100 |
+
retriever_cls = self.retriever_cls
|
| 101 |
+
if hidden_size is None:
|
| 102 |
+
hidden_size = self.hidden_size
|
| 103 |
+
"""merge results from multiple gpus and return a retriver.."""
|
| 104 |
+
if torch.distributed.is_initialized():
|
| 105 |
+
self.save()
|
| 106 |
+
# sync saving.
|
| 107 |
+
torch.distributed.barrier()
|
| 108 |
+
world_size = torch.distributed.get_world_size()
|
| 109 |
+
else:
|
| 110 |
+
world_size = 1
|
| 111 |
+
self.retriver = retriever_cls(
|
| 112 |
+
hidden_size, centroids, db_type, examples_per_cent_to_train)
|
| 113 |
+
# each gpu process has its own retriever.
|
| 114 |
+
for local_rank in range(world_size):
|
| 115 |
+
if get_local_rank() == 0:
|
| 116 |
+
print("load local_rank", local_rank)
|
| 117 |
+
hidden_states, video_ids = self.load(local_rank)
|
| 118 |
+
hidden_states = hidden_states.astype("float32")
|
| 119 |
+
self.retriver.add(hidden_states, video_ids)
|
| 120 |
+
return self.retriver
|
| 121 |
+
|
| 122 |
+
def load(self, local_rank):
|
| 123 |
+
hidden_states = np.load(
|
| 124 |
+
os.path.join(
|
| 125 |
+
self.out_dir,
|
| 126 |
+
"hidden_state" + str(local_rank) + ".npy"
|
| 127 |
+
)
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
with open(
|
| 131 |
+
os.path.join(
|
| 132 |
+
self.out_dir, "video_id" + str(local_rank) + ".pkl"),
|
| 133 |
+
"rb") as fr:
|
| 134 |
+
video_ids = pickle.load(fr)
|
| 135 |
+
return hidden_states, video_ids
|
| 136 |
+
|
| 137 |
+
def save(self):
|
| 138 |
+
hidden_states = np.vstack(self.hidden_states)
|
| 139 |
+
assert len(hidden_states) == len(self.video_ids), "{}, {}".format(
|
| 140 |
+
len(hidden_states),
|
| 141 |
+
len(self.video_ids)
|
| 142 |
+
)
|
| 143 |
+
local_rank = torch.distributed.get_rank() \
|
| 144 |
+
if torch.distributed.is_initialized() else 0
|
| 145 |
+
|
| 146 |
+
np.save(
|
| 147 |
+
os.path.join(
|
| 148 |
+
self.out_dir,
|
| 149 |
+
"hidden_state" + str(local_rank) + ".npy"),
|
| 150 |
+
hidden_states)
|
| 151 |
+
|
| 152 |
+
with open(
|
| 153 |
+
os.path.join(
|
| 154 |
+
self.out_dir,
|
| 155 |
+
"video_id" + str(local_rank) + ".pkl"),
|
| 156 |
+
"wb") as fw:
|
| 157 |
+
pickle.dump(
|
| 158 |
+
self.video_ids,
|
| 159 |
+
fw,
|
| 160 |
+
protocol=pickle.HIGHEST_PROTOCOL
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
class DistributedVideoVectorPool(DistributedVectorPool):
|
| 165 |
+
"""
|
| 166 |
+
average clips of a video as video representation.
|
| 167 |
+
"""
|
| 168 |
+
def __call__(self, sample, subsampling, **kwargs):
|
| 169 |
+
hidden_states = (
|
| 170 |
+
sample["pooled_video"] + sample["pooled_text"]) / 2.
|
| 171 |
+
hidden_states = hidden_states.view(
|
| 172 |
+
-1, subsampling,
|
| 173 |
+
hidden_states.size(-1))
|
| 174 |
+
hidden_states = torch.mean(hidden_states, dim=1)
|
| 175 |
+
hidden_states = hidden_states.cpu().detach().numpy()
|
| 176 |
+
video_ids = []
|
| 177 |
+
for offset_idx, video_id in enumerate(sample["video_id"]):
|
| 178 |
+
if isinstance(video_id, tuple) and len(video_id) == 3:
|
| 179 |
+
# a sharded video_id.
|
| 180 |
+
video_id = video_id[0]
|
| 181 |
+
video_ids.append(video_id)
|
| 182 |
+
assert len(video_ids) == len(hidden_states)
|
| 183 |
+
self.hidden_states.append(hidden_states)
|
| 184 |
+
self.video_ids.extend(video_ids)
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
# ------------ the following are deprecated --------------
|
| 188 |
+
|
| 189 |
+
class TextClipVectorPool(VectorPool):
|
| 190 |
+
def __init__(self, config):
|
| 191 |
+
from transformers import AutoConfig
|
| 192 |
+
hidden_size = AutoConfig.from_pretrained(
|
| 193 |
+
config.dataset.bert_name).hidden_size
|
| 194 |
+
retriever_cls = getattr(retri, config.retriever_cls)
|
| 195 |
+
self.build_retriver(retriever_cls, hidden_size)
|
| 196 |
+
|
| 197 |
+
def __call__(self, sample, **kwargs):
|
| 198 |
+
clip_meta = sample["clip_meta"].cpu()
|
| 199 |
+
assert torch.all(torch.le(clip_meta[:, 4], clip_meta[:, 5]))
|
| 200 |
+
text_meta = [tuple(item.tolist()) for item in clip_meta[:, 3:]]
|
| 201 |
+
|
| 202 |
+
if hasattr(self, "retriver"):
|
| 203 |
+
# build_retriver is called.
|
| 204 |
+
self.retriver.add(
|
| 205 |
+
sample["pooled_text"].cpu().numpy().astype("float32"),
|
| 206 |
+
text_meta
|
| 207 |
+
)
|
| 208 |
+
else:
|
| 209 |
+
raise NotImplementedError
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
class MMClipVectorPool(VectorPool):
|
| 213 |
+
"""
|
| 214 |
+
Multimodal Clip-level vector pool.
|
| 215 |
+
"""
|
| 216 |
+
def __init__(self, out_dir):
|
| 217 |
+
"""use hidden_states to store `(video, text)`."""
|
| 218 |
+
"""use video_ids to store `(video_id, start, end)`."""
|
| 219 |
+
super().__init__(out_dir)
|
| 220 |
+
|
| 221 |
+
def __call__(self, sample, **kwargs):
|
| 222 |
+
pooled_video = sample["pooled_video"].cpu().unsqueeze(1).numpy()
|
| 223 |
+
pooled_text = sample["pooled_text"].cpu().unsqueeze(1).numpy()
|
| 224 |
+
|
| 225 |
+
self.hidden_states.append(
|
| 226 |
+
np.concatenate([pooled_video, pooled_text], axis=1)
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
video_starts = sample["video_start"].cpu()
|
| 230 |
+
video_ends = sample["video_end"].cpu()
|
| 231 |
+
assert torch.all(torch.le(video_starts, video_ends))
|
| 232 |
+
|
| 233 |
+
text_starts = sample["text_start"].cpu()
|
| 234 |
+
text_ends = sample["text_end"].cpu()
|
| 235 |
+
assert torch.all(torch.le(text_starts, text_ends))
|
| 236 |
+
subsample_size = sample["pooled_video"].size(0) // len(sample["video_id"])
|
| 237 |
+
video_ids = [video_id for video_id in sample["video_id"]
|
| 238 |
+
for _ in range(subsample_size)
|
| 239 |
+
]
|
| 240 |
+
for video_id, video_start, video_end, text_start, text_end in zip(
|
| 241 |
+
video_ids, video_starts, video_ends, text_starts, text_ends):
|
| 242 |
+
self.video_ids.append((
|
| 243 |
+
video_id,
|
| 244 |
+
(int(video_start), int(video_end)),
|
| 245 |
+
(int(text_start), int(text_end))
|
| 246 |
+
))
|
data/fairseq/examples/MMPT/mmpt/processors/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
from .processor import *
|
| 6 |
+
|
| 7 |
+
from .how2processor import *
|
| 8 |
+
from .how2retriprocessor import *
|
| 9 |
+
|
| 10 |
+
from .dsprocessor import *
|
| 11 |
+
|
| 12 |
+
try:
|
| 13 |
+
from .rawvideoprocessor import *
|
| 14 |
+
from .codecprocessor import *
|
| 15 |
+
from .webvidprocessor import *
|
| 16 |
+
from .expprocessor import *
|
| 17 |
+
from .exphow2processor import *
|
| 18 |
+
from .exphow2retriprocessor import *
|
| 19 |
+
from .expcodecprocessor import *
|
| 20 |
+
from .expfeatureencoder import *
|
| 21 |
+
from .expdsprocessor import *
|
| 22 |
+
except ImportError:
|
| 23 |
+
pass
|
data/fairseq/examples/MMPT/mmpt/processors/dedupprocessor.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import random
|
| 7 |
+
import json
|
| 8 |
+
import pickle
|
| 9 |
+
from tqdm import tqdm
|
| 10 |
+
import os
|
| 11 |
+
import numpy as np
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class CaptionDedupProcessor(object):
|
| 15 |
+
"""remove overlapping of caption sentences(clip).
|
| 16 |
+
Some statistics:
|
| 17 |
+
caption:
|
| 18 |
+
{'t_clip_len': 246.6448431320854,
|
| 19 |
+
'video_len': 281.09174795676245,
|
| 20 |
+
'clip_tps': 0.8841283727427481,
|
| 21 |
+
'video_tps': 0.7821156477732097,
|
| 22 |
+
'min_clip_len': 0.0,
|
| 23 |
+
'max_clip_len': 398.3,
|
| 24 |
+
'mean_clip_len': 3.196580003006861,
|
| 25 |
+
'num_clip': 77.15897706301081}
|
| 26 |
+
|
| 27 |
+
raw_caption:
|
| 28 |
+
{'t_clip_len': 238.95908778424115,
|
| 29 |
+
'video_len': 267.5914859862507,
|
| 30 |
+
'clip_tps': 2.4941363624267963,
|
| 31 |
+
'video_tps': 2.258989769647173,
|
| 32 |
+
'min_clip_len': 0.0,
|
| 33 |
+
'max_clip_len': 398.3,
|
| 34 |
+
'mean_clip_len': 3.0537954186814265,
|
| 35 |
+
'num_clip': 78.24986779481756}
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
def __init__(self, pkl_file):
|
| 39 |
+
with open(pkl_file, "rb") as fd:
|
| 40 |
+
self.data = pickle.load(fd)
|
| 41 |
+
self.stat = {
|
| 42 |
+
"t_clip_len": [],
|
| 43 |
+
"video_len": [],
|
| 44 |
+
"clip_tps": [],
|
| 45 |
+
"video_tps": [],
|
| 46 |
+
"clip_len": [],
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
def __call__(self):
|
| 50 |
+
for idx, video_id in enumerate(tqdm(self.data)):
|
| 51 |
+
caption = json.loads(self.data[video_id])
|
| 52 |
+
caption = self._dedup(caption)
|
| 53 |
+
if idx < 4096: # for the first 4096 examples, compute the statistics.
|
| 54 |
+
self.save_stat(video_id, caption)
|
| 55 |
+
self.data[video_id] = json.dumps(caption)
|
| 56 |
+
self.print_stat()
|
| 57 |
+
|
| 58 |
+
def single(self, video_id):
|
| 59 |
+
caption = json.loads(self.data[video_id])
|
| 60 |
+
for clip_idx, (start, end, text) in enumerate(
|
| 61 |
+
zip(caption["start"], caption["end"], caption["text"])
|
| 62 |
+
):
|
| 63 |
+
print(start, end, text)
|
| 64 |
+
print("@" * 100)
|
| 65 |
+
caption = self._dedup(caption)
|
| 66 |
+
for clip_idx, (start, end, text) in enumerate(
|
| 67 |
+
zip(caption["start"], caption["end"], caption["text"])
|
| 68 |
+
):
|
| 69 |
+
print(start, end, text)
|
| 70 |
+
print("#" * 100)
|
| 71 |
+
self.save_stat(video_id, caption)
|
| 72 |
+
self.print_stat()
|
| 73 |
+
|
| 74 |
+
def finalize(self, tgt_fn):
|
| 75 |
+
with open(tgt_fn, "wb") as fw:
|
| 76 |
+
pickle.dump(self.data, fw, pickle.HIGHEST_PROTOCOL)
|
| 77 |
+
|
| 78 |
+
def save_stat(self, video_id, caption):
|
| 79 |
+
video_fn = os.path.join(
|
| 80 |
+
"data/feat/feat_how2_s3d", video_id + ".npy"
|
| 81 |
+
)
|
| 82 |
+
if os.path.isfile(video_fn):
|
| 83 |
+
with open(video_fn, "rb", 1) as fr: # 24 is the buffer size. buffered
|
| 84 |
+
version = np.lib.format.read_magic(fr)
|
| 85 |
+
shape, fortran, dtype = np.lib.format._read_array_header(fr, version)
|
| 86 |
+
video_len = shape[0]
|
| 87 |
+
|
| 88 |
+
t_clip_len = 0.0
|
| 89 |
+
t_tokens = 0
|
| 90 |
+
for idx, (start, end, text) in enumerate(
|
| 91 |
+
zip(caption["start"], caption["end"], caption["text"])
|
| 92 |
+
):
|
| 93 |
+
clip_len = (
|
| 94 |
+
(end - max(caption["end"][idx - 1], start))
|
| 95 |
+
if idx > 0
|
| 96 |
+
else end - start
|
| 97 |
+
)
|
| 98 |
+
t_clip_len += clip_len
|
| 99 |
+
t_tokens += len(text.split(" "))
|
| 100 |
+
self.stat["clip_len"].append(clip_len)
|
| 101 |
+
self.stat["t_clip_len"].append(t_clip_len)
|
| 102 |
+
self.stat["video_len"].append(video_len)
|
| 103 |
+
self.stat["clip_tps"].append(t_tokens / t_clip_len)
|
| 104 |
+
self.stat["video_tps"].append(t_tokens / video_len)
|
| 105 |
+
|
| 106 |
+
def print_stat(self):
|
| 107 |
+
result = {
|
| 108 |
+
"t_clip_len": np.mean(self.stat["t_clip_len"]),
|
| 109 |
+
"video_len": np.mean(self.stat["video_len"]),
|
| 110 |
+
"clip_tps": np.mean(self.stat["clip_tps"]),
|
| 111 |
+
"video_tps": np.mean(self.stat["video_tps"]),
|
| 112 |
+
"min_clip_len": min(self.stat["clip_len"]),
|
| 113 |
+
"max_clip_len": max(self.stat["clip_len"]),
|
| 114 |
+
"mean_clip_len": np.mean(self.stat["clip_len"]),
|
| 115 |
+
"num_clip": len(self.stat["clip_len"]) / len(self.stat["video_tps"]),
|
| 116 |
+
}
|
| 117 |
+
print(result)
|
| 118 |
+
|
| 119 |
+
def _dedup(self, caption):
|
| 120 |
+
def random_merge(end_idx, start, end, text, starts, ends, texts):
|
| 121 |
+
if random.random() > 0.5:
|
| 122 |
+
# print(clip_idx, "[PARTIAL INTO PREV]", end_idx)
|
| 123 |
+
# overlapped part goes to the end of previous.
|
| 124 |
+
ends[-1] = max(ends[-1], start) # ?
|
| 125 |
+
rest_text = text[end_idx:].strip()
|
| 126 |
+
if rest_text:
|
| 127 |
+
starts.append(max(ends[-1], start))
|
| 128 |
+
ends.append(max(end, starts[-1]))
|
| 129 |
+
texts.append(rest_text)
|
| 130 |
+
else: # goes to the beginning of the current.
|
| 131 |
+
# strip the previous.
|
| 132 |
+
left_text = texts[-1][:-end_idx].strip()
|
| 133 |
+
if left_text:
|
| 134 |
+
# print(clip_idx, "[PREV PARTIAL INTO CUR]", end_idx)
|
| 135 |
+
ends[-1] = min(ends[-1], start)
|
| 136 |
+
texts[-1] = left_text
|
| 137 |
+
else:
|
| 138 |
+
# print(clip_idx, "[PREV LEFT NOTHING ALL INTO CUR]", end_idx)
|
| 139 |
+
starts.pop(-1)
|
| 140 |
+
ends.pop(-1)
|
| 141 |
+
texts.pop(-1)
|
| 142 |
+
starts.append(start)
|
| 143 |
+
ends.append(end)
|
| 144 |
+
texts.append(text)
|
| 145 |
+
|
| 146 |
+
starts, ends, texts = [], [], []
|
| 147 |
+
for clip_idx, (start, end, text) in enumerate(
|
| 148 |
+
zip(caption["start"], caption["end"], caption["text"])
|
| 149 |
+
):
|
| 150 |
+
if not isinstance(text, str):
|
| 151 |
+
continue
|
| 152 |
+
text = text.replace("\n", " ").strip()
|
| 153 |
+
if len(text) == 0:
|
| 154 |
+
continue
|
| 155 |
+
starts.append(start)
|
| 156 |
+
ends.append(end)
|
| 157 |
+
texts.append(text)
|
| 158 |
+
break
|
| 159 |
+
|
| 160 |
+
for clip_idx, (start, end, text) in enumerate(
|
| 161 |
+
zip(
|
| 162 |
+
caption["start"][clip_idx + 1:],
|
| 163 |
+
caption["end"][clip_idx + 1:],
|
| 164 |
+
caption["text"][clip_idx + 1:],
|
| 165 |
+
)
|
| 166 |
+
):
|
| 167 |
+
if not isinstance(text, str):
|
| 168 |
+
continue
|
| 169 |
+
text = text.replace("\n", " ").strip()
|
| 170 |
+
if len(text) == 0:
|
| 171 |
+
continue
|
| 172 |
+
|
| 173 |
+
# print(clip_idx, texts[-5:])
|
| 174 |
+
# print(clip_idx, start, end, text)
|
| 175 |
+
if texts[-1].endswith(text): # subset of prev caption -> merge
|
| 176 |
+
# print(clip_idx, "[MERGE INTO PREV]")
|
| 177 |
+
ends[-1] = max(ends[-1], end)
|
| 178 |
+
elif text.startswith(texts[-1]): # superset of prev caption -> merge
|
| 179 |
+
# print(clip_idx, "[PREV MERGE INTO CUR]")
|
| 180 |
+
texts[-1] = text
|
| 181 |
+
starts[-1] = min(starts[-1], start)
|
| 182 |
+
ends[-1] = max(ends[-1], end)
|
| 183 |
+
else: # overlapping or non-overlapping.
|
| 184 |
+
for end_idx in range(1, len(text) + 1):
|
| 185 |
+
if texts[-1].endswith(text[:end_idx]):
|
| 186 |
+
random_merge(end_idx, start, end, text, starts, ends, texts)
|
| 187 |
+
break
|
| 188 |
+
else:
|
| 189 |
+
starts.append(start)
|
| 190 |
+
ends.append(end)
|
| 191 |
+
texts.append(text)
|
| 192 |
+
|
| 193 |
+
assert (ends[-1] + 0.001) >= starts[-1] and len(
|
| 194 |
+
texts[-1]
|
| 195 |
+
) > 0, "{} {} {} <- {} {} {}, {} {} {}".format(
|
| 196 |
+
str(starts[-1]),
|
| 197 |
+
str(ends[-1]),
|
| 198 |
+
texts[-1],
|
| 199 |
+
caption["start"][clip_idx - 1],
|
| 200 |
+
caption["end"][clip_idx - 1],
|
| 201 |
+
caption["text"][clip_idx - 1],
|
| 202 |
+
str(start),
|
| 203 |
+
str(end),
|
| 204 |
+
text,
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
return {"start": starts, "end": ends, "text": texts}
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
if __name__ == "__main__":
|
| 211 |
+
import argparse
|
| 212 |
+
|
| 213 |
+
parser = argparse.ArgumentParser(description="dedup how2 caption")
|
| 214 |
+
parser.add_argument('--how2dir', default="data/how2")
|
| 215 |
+
args = parser.parse_args()
|
| 216 |
+
|
| 217 |
+
raw_caption_json = os.path.join(args.how2dir, "raw_caption.json")
|
| 218 |
+
raw_caption_pickle = os.path.join(args.how2dir, "raw_caption.pkl")
|
| 219 |
+
raw_caption_dedup_pickle = os.path.join(args.how2dir, "raw_caption_dedup.pkl")
|
| 220 |
+
|
| 221 |
+
def convert_to_pickle(src_fn, tgt_fn):
|
| 222 |
+
with open(src_fn) as fd:
|
| 223 |
+
captions = json.load(fd)
|
| 224 |
+
|
| 225 |
+
for video_id in captions:
|
| 226 |
+
captions[video_id] = json.dumps(captions[video_id])
|
| 227 |
+
|
| 228 |
+
with open(tgt_fn, "wb") as fw:
|
| 229 |
+
pickle.dump(captions, fw, pickle.HIGHEST_PROTOCOL)
|
| 230 |
+
|
| 231 |
+
if not os.path.isfile(raw_caption_pickle):
|
| 232 |
+
convert_to_pickle(raw_caption_json, raw_caption_pickle)
|
| 233 |
+
|
| 234 |
+
deduper = CaptionDedupProcessor(raw_caption_pickle)
|
| 235 |
+
deduper()
|
| 236 |
+
deduper.finalize(raw_caption_dedup_pickle)
|
| 237 |
+
|
| 238 |
+
"""
|
| 239 |
+
# demo
|
| 240 |
+
deduper = CaptionDedupProcessor("data/how2/raw_caption.pkl")
|
| 241 |
+
deduper.single("HfIeQ9pzL5U")
|
| 242 |
+
"""
|
data/fairseq/examples/MMPT/mmpt/processors/dsprocessor.py
ADDED
|
@@ -0,0 +1,848 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. All Rights Reserved
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
Processors for all downstream (ds) tasks.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import json
|
| 8 |
+
import os
|
| 9 |
+
import pickle
|
| 10 |
+
import random
|
| 11 |
+
import math
|
| 12 |
+
import numpy as np
|
| 13 |
+
import torch
|
| 14 |
+
|
| 15 |
+
from collections import defaultdict
|
| 16 |
+
|
| 17 |
+
from .processor import (
|
| 18 |
+
MetaProcessor,
|
| 19 |
+
VideoProcessor,
|
| 20 |
+
TextProcessor,
|
| 21 |
+
Aligner,
|
| 22 |
+
MMAttentionMask2DProcessor,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
from .how2processor import TextGenerationProcessor
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# ------------- A General Aligner for all downstream tasks-----------------
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class DSAligner(Aligner):
|
| 32 |
+
"""
|
| 33 |
+
Downstream (DS) aligner shared by all datasets.
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
def __call__(self, video_id, video_feature, text_feature, wps=0.7):
|
| 37 |
+
# random sample a starting sec for video.
|
| 38 |
+
video_start = 0
|
| 39 |
+
video_end = min(len(video_feature), self.max_video_len)
|
| 40 |
+
# the whole sequence is a single clip.
|
| 41 |
+
video_clips = {"start": [video_start], "end": [video_end]}
|
| 42 |
+
|
| 43 |
+
text_feature = {
|
| 44 |
+
"cap": [text_feature],
|
| 45 |
+
"start": [video_start],
|
| 46 |
+
"end": [len(text_feature) / wps],
|
| 47 |
+
}
|
| 48 |
+
text_clip_indexs = [0]
|
| 49 |
+
|
| 50 |
+
vfeats, vmasks = self._build_video_seq(
|
| 51 |
+
video_feature, video_clips
|
| 52 |
+
)
|
| 53 |
+
caps, cmasks = self._build_text_seq(
|
| 54 |
+
text_feature, text_clip_indexs
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
return {
|
| 58 |
+
"caps": caps,
|
| 59 |
+
"cmasks": cmasks,
|
| 60 |
+
"vfeats": vfeats,
|
| 61 |
+
"vmasks": vmasks,
|
| 62 |
+
"video_id": video_id,
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class NLGTextProcessor(TextProcessor):
|
| 67 |
+
"""
|
| 68 |
+
Also return the original text as ref.
|
| 69 |
+
"""
|
| 70 |
+
def __call__(self, text_id):
|
| 71 |
+
return super().__call__(text_id), text_id
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class DSNLGAligner(DSAligner):
|
| 75 |
+
"""extend with the capability of 2d mask for generation."""
|
| 76 |
+
def __init__(self, config):
|
| 77 |
+
super().__init__(config)
|
| 78 |
+
self.attnmasker = MMAttentionMask2DProcessor()
|
| 79 |
+
from transformers import AutoTokenizer
|
| 80 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 81 |
+
self.bert_name, use_fast=self.use_fast,
|
| 82 |
+
bos_token="[CLS]", eos_token="[SEP]"
|
| 83 |
+
)
|
| 84 |
+
self.tokenizer = tokenizer
|
| 85 |
+
self.bos_token_id = tokenizer.bos_token_id
|
| 86 |
+
self.eos_token_id = tokenizer.eos_token_id
|
| 87 |
+
self.textgen = TextGenerationProcessor(tokenizer)
|
| 88 |
+
|
| 89 |
+
def __call__(self, video_id, video_feature, text_feature):
|
| 90 |
+
output = super().__call__(video_id, video_feature, text_feature[0])
|
| 91 |
+
if self.split == "test":
|
| 92 |
+
# output.update({"ref": text_feature[1]})
|
| 93 |
+
output.update({"ref": self.tokenizer.decode(
|
| 94 |
+
output["caps"], skip_special_tokens=True)})
|
| 95 |
+
text_label = output["caps"]
|
| 96 |
+
cmasks = torch.BoolTensor([1] * text_label.size(0))
|
| 97 |
+
caps = torch.LongTensor([
|
| 98 |
+
self.cls_token_id,
|
| 99 |
+
self.sep_token_id,
|
| 100 |
+
self.bos_token_id])
|
| 101 |
+
else:
|
| 102 |
+
caps, text_label = self.textgen(output["caps"])
|
| 103 |
+
cmasks = output["cmasks"]
|
| 104 |
+
|
| 105 |
+
attention_mask = self.attnmasker(
|
| 106 |
+
output["vmasks"], cmasks, "textgen")
|
| 107 |
+
|
| 108 |
+
output.update({
|
| 109 |
+
"caps": caps,
|
| 110 |
+
"cmasks": cmasks,
|
| 111 |
+
"text_label": text_label,
|
| 112 |
+
"attention_mask": attention_mask,
|
| 113 |
+
})
|
| 114 |
+
return output
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
# -------------------- MSRVTT ------------------------
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
class MSRVTTMetaProcessor(MetaProcessor):
|
| 121 |
+
"""MSRVTT dataset.
|
| 122 |
+
reference: `howto100m/msrvtt_dataloader.py`
|
| 123 |
+
"""
|
| 124 |
+
|
| 125 |
+
def __init__(self, config):
|
| 126 |
+
super().__init__(config)
|
| 127 |
+
import pandas as pd
|
| 128 |
+
data = pd.read_csv(self._get_split_path(config))
|
| 129 |
+
# TODO: add a text1ka flag.
|
| 130 |
+
if config.split == "train" \
|
| 131 |
+
and config.full_test_path is not None \
|
| 132 |
+
and config.jsfusion_path is not None:
|
| 133 |
+
# add testing videos from full_test_path not used by jfusion.
|
| 134 |
+
additional_data = pd.read_csv(config.full_test_path)
|
| 135 |
+
jsfusion_data = pd.read_csv(config.jsfusion_path)
|
| 136 |
+
|
| 137 |
+
for video_id in additional_data["video_id"]:
|
| 138 |
+
if video_id not in jsfusion_data["video_id"].values:
|
| 139 |
+
data = data.append(
|
| 140 |
+
{"video_id": video_id}, ignore_index=True)
|
| 141 |
+
|
| 142 |
+
if config.dup is not None and config.split == "train":
|
| 143 |
+
data = data.append([data] * (config.dup - 1), ignore_index=True)
|
| 144 |
+
self.data = data
|
| 145 |
+
|
| 146 |
+
def __len__(self):
|
| 147 |
+
return len(self.data)
|
| 148 |
+
|
| 149 |
+
def __getitem__(self, idx):
|
| 150 |
+
"""slightly modify with if condition to combine train/test."""
|
| 151 |
+
vid, sentence = None, None
|
| 152 |
+
vid = self.data["video_id"].values[idx]
|
| 153 |
+
if "sentence" in self.data: # for testing.
|
| 154 |
+
sentence = self.data["sentence"].values[idx]
|
| 155 |
+
else: # for training.
|
| 156 |
+
sentence = vid
|
| 157 |
+
return vid, sentence
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
class MSRVTTTextProcessor(TextProcessor):
|
| 161 |
+
"""MSRVTT dataset.
|
| 162 |
+
reference: `msrvtt_dataloader.py` `MSRVTT_TrainDataLoader`.
|
| 163 |
+
TODO (huxu): add max_words.
|
| 164 |
+
"""
|
| 165 |
+
|
| 166 |
+
def __init__(self, config):
|
| 167 |
+
super().__init__(config)
|
| 168 |
+
self.sentences = None
|
| 169 |
+
if config.json_path is not None and config.split == "train":
|
| 170 |
+
with open(config.json_path) as fd:
|
| 171 |
+
self.data = json.load(fd)
|
| 172 |
+
self.sentences = defaultdict(list)
|
| 173 |
+
for s in self.data["sentences"]:
|
| 174 |
+
self.sentences[s["video_id"]].append(s["caption"])
|
| 175 |
+
|
| 176 |
+
def __call__(self, text_id):
|
| 177 |
+
if self.sentences is not None:
|
| 178 |
+
rind = random.randint(0, len(self.sentences[text_id]) - 1)
|
| 179 |
+
sentence = self.sentences[text_id][rind]
|
| 180 |
+
else:
|
| 181 |
+
sentence = text_id
|
| 182 |
+
caption = self.tokenizer(sentence, add_special_tokens=False)
|
| 183 |
+
return caption["input_ids"]
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
class MSRVTTNLGTextProcessor(MSRVTTTextProcessor):
|
| 187 |
+
"""TODO: change dsaligner and merge to avoid any NLG text processor."""
|
| 188 |
+
def __call__(self, text_id):
|
| 189 |
+
if self.sentences is not None:
|
| 190 |
+
rind = random.randint(0, len(self.sentences[text_id]) - 1)
|
| 191 |
+
sentence = self.sentences[text_id][rind]
|
| 192 |
+
else:
|
| 193 |
+
sentence = text_id
|
| 194 |
+
caption = self.tokenizer(sentence, add_special_tokens=False)
|
| 195 |
+
return caption["input_ids"], sentence
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
class MSRVTTQAMetaProcessor(MetaProcessor):
|
| 199 |
+
"""MSRVTT-QA: retrieval-based multi-choice QA from JSFusion dataset.
|
| 200 |
+
For simplicity, we use the train retrieval model.
|
| 201 |
+
reference: `https://github.com/yj-yu/lsmdc`
|
| 202 |
+
"""
|
| 203 |
+
|
| 204 |
+
def __init__(self, config):
|
| 205 |
+
super().__init__(config)
|
| 206 |
+
import pandas as pd
|
| 207 |
+
csv_data = pd.read_csv(self._get_split_path(config), sep="\t")
|
| 208 |
+
data = []
|
| 209 |
+
for video_id, a1, a2, a3, a4, a5, answer in zip(
|
| 210 |
+
csv_data["vid_key"].values,
|
| 211 |
+
csv_data["a1"].values,
|
| 212 |
+
csv_data["a2"].values,
|
| 213 |
+
csv_data["a3"].values,
|
| 214 |
+
csv_data["a4"].values,
|
| 215 |
+
csv_data["a5"].values,
|
| 216 |
+
csv_data["answer"].values):
|
| 217 |
+
video_id = video_id.replace("msr", "video")
|
| 218 |
+
data.append((video_id, (answer, [a1, a2, a3, a4, a5])))
|
| 219 |
+
self.data = data
|
| 220 |
+
|
| 221 |
+
def __len__(self):
|
| 222 |
+
return len(self.data)
|
| 223 |
+
|
| 224 |
+
def __getitem__(self, idx):
|
| 225 |
+
return self.data[idx]
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
class MSRVTTQATextProcessor(TextProcessor):
|
| 229 |
+
"""MSRVTT-QA dataset.
|
| 230 |
+
text_ans is of format `(answer, [a1, a2, a3, a4, a5])`.
|
| 231 |
+
"""
|
| 232 |
+
|
| 233 |
+
def __call__(self, text_ans):
|
| 234 |
+
for ans_idx, ans in enumerate(text_ans[1]):
|
| 235 |
+
if isinstance(ans, str):
|
| 236 |
+
text_ans[1][ans_idx] = self.tokenizer(ans, add_special_tokens=False)["input_ids"]
|
| 237 |
+
return text_ans
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
class MSRVTTQAAligner(DSAligner):
|
| 241 |
+
"""MSRVTT dataset.
|
| 242 |
+
similar to sample in how2.
|
| 243 |
+
we call __call__ multiple times.
|
| 244 |
+
"""
|
| 245 |
+
|
| 246 |
+
def __call__(self, video_id, video_feature, text_feature, wps=0.7):
|
| 247 |
+
caps = []
|
| 248 |
+
cmasks = []
|
| 249 |
+
answer = text_feature[0]
|
| 250 |
+
for ans_idx, _text_feature in enumerate(text_feature[1]):
|
| 251 |
+
output = super().__call__(
|
| 252 |
+
video_id, video_feature, _text_feature, wps)
|
| 253 |
+
caps.append(output["caps"])
|
| 254 |
+
cmasks.append(output["cmasks"])
|
| 255 |
+
output.update({
|
| 256 |
+
"caps": torch.stack(caps),
|
| 257 |
+
"cmasks": torch.stack(cmasks),
|
| 258 |
+
"answers": torch.LongTensor([answer]),
|
| 259 |
+
})
|
| 260 |
+
return output
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
# -------------------- Youcook -----------------------
|
| 264 |
+
|
| 265 |
+
|
| 266 |
+
class YoucookMetaProcessor(MetaProcessor):
|
| 267 |
+
"""Youcook dataset.
|
| 268 |
+
reference: `howto100m/youcook_dataloader.py`
|
| 269 |
+
note that the data can be different as the
|
| 270 |
+
(1) some videos already in Howto100m are removed.
|
| 271 |
+
(2) stop words are removed from caption
|
| 272 |
+
TODO (huxu): make a flag to load the original caption.
|
| 273 |
+
(see youcookii_annotations_trainval.json).
|
| 274 |
+
|
| 275 |
+
The max_video_len can be 264 and text can be 64 tokens.
|
| 276 |
+
In reality we may not need that long. see projects/task/youcook.yaml
|
| 277 |
+
"""
|
| 278 |
+
|
| 279 |
+
def __init__(self, config):
|
| 280 |
+
super().__init__(config)
|
| 281 |
+
vfeat_dir = config.vfeat_dir
|
| 282 |
+
print(self._get_split_path(config))
|
| 283 |
+
with open(self._get_split_path(config), "rb") as fd:
|
| 284 |
+
data = pickle.load(fd)
|
| 285 |
+
all_valid_video_ids = set(
|
| 286 |
+
[os.path.splitext(fn)[0] for fn in os.listdir(vfeat_dir)]
|
| 287 |
+
)
|
| 288 |
+
recs = []
|
| 289 |
+
video_ids = set()
|
| 290 |
+
valid_video_ids = set()
|
| 291 |
+
for rec in data: # filter videos not available.
|
| 292 |
+
udl_idx = rec["id"].rindex("_")
|
| 293 |
+
video_id = rec["id"][:udl_idx]
|
| 294 |
+
video_ids.add(video_id)
|
| 295 |
+
if video_id in all_valid_video_ids:
|
| 296 |
+
valid_video_ids.add(video_id)
|
| 297 |
+
recs.append(rec)
|
| 298 |
+
print("total video_ids in .pkl", len(video_ids))
|
| 299 |
+
print("valid video_ids in .pkl", len(valid_video_ids))
|
| 300 |
+
print("please verify {train,val}_list.txt")
|
| 301 |
+
data = recs
|
| 302 |
+
self.data = data
|
| 303 |
+
|
| 304 |
+
with open(config.trainval_annotation) as fd:
|
| 305 |
+
self.youcook_annotation = json.load(fd)["database"]
|
| 306 |
+
if config.use_annotation_text is True:
|
| 307 |
+
print("using text in annotation.")
|
| 308 |
+
self.use_annotation_caption = True
|
| 309 |
+
else:
|
| 310 |
+
self.use_annotation_caption = False
|
| 311 |
+
|
| 312 |
+
def __getitem__(self, idx):
|
| 313 |
+
def _get_video_and_caption(rec):
|
| 314 |
+
vid = rec["id"]
|
| 315 |
+
udl_idx = vid.rindex("_")
|
| 316 |
+
video_id, clip_id = vid[:udl_idx], int(vid[udl_idx + 1:])
|
| 317 |
+
clip = self.youcook_annotation[video_id]["annotations"][clip_id]
|
| 318 |
+
start, end = clip["segment"]
|
| 319 |
+
if self.use_annotation_caption:
|
| 320 |
+
caption = clip["sentence"]
|
| 321 |
+
else:
|
| 322 |
+
caption = rec["caption"]
|
| 323 |
+
return (video_id, start, end), caption
|
| 324 |
+
|
| 325 |
+
rec = self.data[idx]
|
| 326 |
+
video_info, text_info = _get_video_and_caption(rec)
|
| 327 |
+
return video_info, text_info
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
class YoucookVideoProcessor(VideoProcessor):
|
| 331 |
+
"""video_fn is a tuple of (video_id, start, end) now."""
|
| 332 |
+
|
| 333 |
+
def __call__(self, video_fn):
|
| 334 |
+
video_id, start, end = video_fn
|
| 335 |
+
feat = np.load(os.path.join(self.vfeat_dir, video_id + ".npy"))
|
| 336 |
+
return feat[start:end]
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
class YoucookNLGMetaProcessor(MetaProcessor):
|
| 340 |
+
"""NLG uses the original split:
|
| 341 |
+
`train_list.txt` and `val_list.txt`
|
| 342 |
+
"""
|
| 343 |
+
|
| 344 |
+
def __init__(self, config):
|
| 345 |
+
super().__init__(config)
|
| 346 |
+
vfeat_dir = config.vfeat_dir
|
| 347 |
+
print(self._get_split_path(config))
|
| 348 |
+
with open(self._get_split_path(config)) as fd:
|
| 349 |
+
video_ids = [
|
| 350 |
+
line.strip().split("/")[1] for line in fd.readlines()]
|
| 351 |
+
print("total video_ids in train/val_list.txt", len(video_ids))
|
| 352 |
+
|
| 353 |
+
all_valid_video_ids = set(
|
| 354 |
+
[os.path.splitext(fn)[0] for fn in os.listdir(vfeat_dir)]
|
| 355 |
+
)
|
| 356 |
+
video_ids = [
|
| 357 |
+
video_id for video_id in video_ids
|
| 358 |
+
if video_id in all_valid_video_ids]
|
| 359 |
+
|
| 360 |
+
print("valid video_ids in train/val_list.txt", len(video_ids))
|
| 361 |
+
with open(config.trainval_annotation) as fd:
|
| 362 |
+
self.youcook_annotation = json.load(fd)["database"]
|
| 363 |
+
|
| 364 |
+
data = []
|
| 365 |
+
for video_id in video_ids:
|
| 366 |
+
for clip in self.youcook_annotation[video_id]["annotations"]:
|
| 367 |
+
start, end = clip["segment"]
|
| 368 |
+
caption = clip["sentence"]
|
| 369 |
+
data.append(((video_id, start, end), caption))
|
| 370 |
+
self.data = data
|
| 371 |
+
|
| 372 |
+
def __getitem__(self, idx):
|
| 373 |
+
return self.data[idx]
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
# --------------------- CrossTask -------------------------
|
| 377 |
+
|
| 378 |
+
class CrossTaskMetaProcessor(MetaProcessor):
|
| 379 |
+
def __init__(self, config):
|
| 380 |
+
super().__init__(config)
|
| 381 |
+
np.random.seed(0) # deterministic random split.
|
| 382 |
+
task_vids = self._get_vids(
|
| 383 |
+
config.train_csv_path,
|
| 384 |
+
config.vfeat_dir,
|
| 385 |
+
config.annotation_path)
|
| 386 |
+
|
| 387 |
+
val_vids = self._get_vids(
|
| 388 |
+
config.val_csv_path,
|
| 389 |
+
config.vfeat_dir,
|
| 390 |
+
config.annotation_path)
|
| 391 |
+
|
| 392 |
+
# filter out those task and vids appear in val_vids.
|
| 393 |
+
task_vids = {
|
| 394 |
+
task: [
|
| 395 |
+
vid for vid in vids
|
| 396 |
+
if task not in val_vids or vid not in val_vids[task]]
|
| 397 |
+
for task, vids in task_vids.items()}
|
| 398 |
+
|
| 399 |
+
primary_info = self._read_task_info(config.primary_path)
|
| 400 |
+
test_tasks = set(primary_info['steps'].keys())
|
| 401 |
+
|
| 402 |
+
# if args.use_related:
|
| 403 |
+
related_info = self._read_task_info(config.related_path)
|
| 404 |
+
task_steps = {**primary_info['steps'], **related_info['steps']}
|
| 405 |
+
n_steps = {**primary_info['n_steps'], **related_info['n_steps']}
|
| 406 |
+
# else:
|
| 407 |
+
# task_steps = primary_info['steps']
|
| 408 |
+
# n_steps = primary_info['n_steps']
|
| 409 |
+
all_tasks = set(n_steps.keys())
|
| 410 |
+
# filter and keep task in primary or related.
|
| 411 |
+
task_vids = {
|
| 412 |
+
task: vids for task, vids in task_vids.items()
|
| 413 |
+
if task in all_tasks}
|
| 414 |
+
# vocab-by-step matrix (A) and vocab (M)
|
| 415 |
+
# (huxu): we do not use BoW.
|
| 416 |
+
# A, M = self._get_A(task_steps, share="words")
|
| 417 |
+
|
| 418 |
+
train_vids, test_vids = self._random_split(
|
| 419 |
+
task_vids, test_tasks, config.n_train)
|
| 420 |
+
print("train_num_videos", sum(len(vids) for vids in train_vids.values()))
|
| 421 |
+
print("test_num_videos", sum(len(vids) for vids in test_vids.values()))
|
| 422 |
+
# added by huxu to automatically determine the split.
|
| 423 |
+
split_map = {
|
| 424 |
+
"train": train_vids,
|
| 425 |
+
"valid": test_vids,
|
| 426 |
+
"test": test_vids
|
| 427 |
+
}
|
| 428 |
+
task_vids = split_map[config.split]
|
| 429 |
+
|
| 430 |
+
self.vids = []
|
| 431 |
+
for task, vids in task_vids.items():
|
| 432 |
+
self.vids.extend([(task, vid) for vid in vids])
|
| 433 |
+
self.task_steps = task_steps
|
| 434 |
+
self.n_steps = n_steps
|
| 435 |
+
|
| 436 |
+
def __getitem__(self, idx):
|
| 437 |
+
task, vid = self.vids[idx]
|
| 438 |
+
n_steps = self.n_steps[task]
|
| 439 |
+
steps = self.task_steps[task]
|
| 440 |
+
assert len(steps) == n_steps
|
| 441 |
+
return (task, vid, steps, n_steps), (task, vid, steps, n_steps)
|
| 442 |
+
|
| 443 |
+
def __len__(self):
|
| 444 |
+
return len(self.vids)
|
| 445 |
+
|
| 446 |
+
def _random_split(self, task_vids, test_tasks, n_train):
|
| 447 |
+
train_vids = {}
|
| 448 |
+
test_vids = {}
|
| 449 |
+
for task, vids in task_vids.items():
|
| 450 |
+
if task in test_tasks and len(vids) > n_train:
|
| 451 |
+
train_vids[task] = np.random.choice(
|
| 452 |
+
vids, n_train, replace=False).tolist()
|
| 453 |
+
test_vids[task] = [
|
| 454 |
+
vid for vid in vids if vid not in train_vids[task]]
|
| 455 |
+
else:
|
| 456 |
+
train_vids[task] = vids
|
| 457 |
+
return train_vids, test_vids
|
| 458 |
+
|
| 459 |
+
def _get_vids(self, path, vfeat_dir, annotation_path):
|
| 460 |
+
"""refactored from
|
| 461 |
+
https://github.com/DmZhukov/CrossTask/blob/master/data.py
|
| 462 |
+
changes: add `vfeat_dir` to check if the video is available.
|
| 463 |
+
add `annotation_path` to check if the video is available.
|
| 464 |
+
"""
|
| 465 |
+
|
| 466 |
+
task_vids = {}
|
| 467 |
+
with open(path, 'r') as f:
|
| 468 |
+
for line in f:
|
| 469 |
+
task, vid, url = line.strip().split(',')
|
| 470 |
+
# double check the video is available.
|
| 471 |
+
if not os.path.exists(
|
| 472 |
+
os.path.join(vfeat_dir, vid + ".npy")):
|
| 473 |
+
continue
|
| 474 |
+
# double check the annotation is available.
|
| 475 |
+
if not os.path.exists(os.path.join(
|
| 476 |
+
annotation_path,
|
| 477 |
+
task + "_" + vid + ".csv")):
|
| 478 |
+
continue
|
| 479 |
+
if task not in task_vids:
|
| 480 |
+
task_vids[task] = []
|
| 481 |
+
task_vids[task].append(vid)
|
| 482 |
+
return task_vids
|
| 483 |
+
|
| 484 |
+
def _read_task_info(self, path):
|
| 485 |
+
titles = {}
|
| 486 |
+
urls = {}
|
| 487 |
+
n_steps = {}
|
| 488 |
+
steps = {}
|
| 489 |
+
with open(path, 'r') as f:
|
| 490 |
+
idx = f.readline()
|
| 491 |
+
while idx != '':
|
| 492 |
+
idx = idx.strip()
|
| 493 |
+
titles[idx] = f.readline().strip()
|
| 494 |
+
urls[idx] = f.readline().strip()
|
| 495 |
+
n_steps[idx] = int(f.readline().strip())
|
| 496 |
+
steps[idx] = f.readline().strip().split(',')
|
| 497 |
+
next(f)
|
| 498 |
+
idx = f.readline()
|
| 499 |
+
return {
|
| 500 |
+
'title': titles,
|
| 501 |
+
'url': urls,
|
| 502 |
+
'n_steps': n_steps,
|
| 503 |
+
'steps': steps
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
def _get_A(self, task_steps, share="words"):
|
| 507 |
+
raise ValueError("running get_A is not allowed for BERT.")
|
| 508 |
+
"""Step-to-component matrices."""
|
| 509 |
+
if share == 'words':
|
| 510 |
+
# share words
|
| 511 |
+
task_step_comps = {
|
| 512 |
+
task: [step.split(' ') for step in steps]
|
| 513 |
+
for task, steps in task_steps.items()}
|
| 514 |
+
elif share == 'task_words':
|
| 515 |
+
# share words within same task
|
| 516 |
+
task_step_comps = {
|
| 517 |
+
task: [[task+'_'+tok for tok in step.split(' ')] for step in steps]
|
| 518 |
+
for task, steps in task_steps.items()}
|
| 519 |
+
elif share == 'steps':
|
| 520 |
+
# share whole step descriptions
|
| 521 |
+
task_step_comps = {
|
| 522 |
+
task: [[step] for step in steps] for task, steps in task_steps.items()}
|
| 523 |
+
else:
|
| 524 |
+
# no sharing
|
| 525 |
+
task_step_comps = {
|
| 526 |
+
task: [[task+'_'+step] for step in steps]
|
| 527 |
+
for task, steps in task_steps.items()}
|
| 528 |
+
# BERT tokenizer here?
|
| 529 |
+
vocab = []
|
| 530 |
+
for task, steps in task_step_comps.items():
|
| 531 |
+
for step in steps:
|
| 532 |
+
vocab.extend(step)
|
| 533 |
+
vocab = {comp: m for m, comp in enumerate(set(vocab))}
|
| 534 |
+
M = len(vocab)
|
| 535 |
+
A = {}
|
| 536 |
+
for task, steps in task_step_comps.items():
|
| 537 |
+
K = len(steps)
|
| 538 |
+
a = torch.zeros(M, K)
|
| 539 |
+
for k, step in enumerate(steps):
|
| 540 |
+
a[[vocab[comp] for comp in step], k] = 1
|
| 541 |
+
a /= a.sum(dim=0)
|
| 542 |
+
A[task] = a
|
| 543 |
+
return A, M
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
class CrossTaskVideoProcessor(VideoProcessor):
|
| 547 |
+
def __call__(self, video_fn):
|
| 548 |
+
task, vid, steps, n_steps = video_fn
|
| 549 |
+
video_fn = os.path.join(self.vfeat_dir, vid + ".npy")
|
| 550 |
+
feat = np.load(video_fn)
|
| 551 |
+
return feat
|
| 552 |
+
|
| 553 |
+
|
| 554 |
+
class CrossTaskTextProcessor(TextProcessor):
|
| 555 |
+
def __call__(self, text_id):
|
| 556 |
+
task, vid, steps, n_steps = text_id
|
| 557 |
+
step_ids = []
|
| 558 |
+
for step_str in steps:
|
| 559 |
+
step_ids.append(
|
| 560 |
+
self.tokenizer(step_str, add_special_tokens=False)["input_ids"]
|
| 561 |
+
)
|
| 562 |
+
return step_ids
|
| 563 |
+
|
| 564 |
+
|
| 565 |
+
class CrossTaskAligner(Aligner):
|
| 566 |
+
"""
|
| 567 |
+
TODO: it's not clear yet the formulation of the task; finish this later.
|
| 568 |
+
"""
|
| 569 |
+
def __init__(self, config):
|
| 570 |
+
super().__init__(config)
|
| 571 |
+
self.annotation_path = config.annotation_path
|
| 572 |
+
self.sliding_window = config.sliding_window
|
| 573 |
+
self.sliding_window_size = config.sliding_window_size
|
| 574 |
+
|
| 575 |
+
def __call__(self, video_id, video_feature, text_feature):
|
| 576 |
+
task, vid, steps, n_steps = video_id
|
| 577 |
+
annot_path = os.path.join(
|
| 578 |
+
self.annotation_path, task + '_' + vid + '.csv')
|
| 579 |
+
video_len = len(video_feature)
|
| 580 |
+
|
| 581 |
+
labels = torch.from_numpy(self._read_assignment(
|
| 582 |
+
video_len, n_steps, annot_path)).float()
|
| 583 |
+
|
| 584 |
+
vfeats, vmasks, targets = [], [], []
|
| 585 |
+
# sliding window on video features and targets.
|
| 586 |
+
for window_start in range(0, video_len, self.sliding_window):
|
| 587 |
+
video_start = 0
|
| 588 |
+
video_end = min(video_len - window_start, self.sliding_window_size)
|
| 589 |
+
video_clip = {"start": [video_start], "end": [video_end]}
|
| 590 |
+
|
| 591 |
+
vfeat, vmask = self._build_video_seq(
|
| 592 |
+
video_feature[window_start: window_start + video_end],
|
| 593 |
+
video_clip
|
| 594 |
+
)
|
| 595 |
+
|
| 596 |
+
target = labels[window_start: window_start + video_end]
|
| 597 |
+
assert len(vfeat) >= len(target), "{},{}".format(len(vfeat), len(target))
|
| 598 |
+
# TODO: randomly drop all zero targets for training ?
|
| 599 |
+
# if self.split == "train" and target.sum() == 0:
|
| 600 |
+
# continue
|
| 601 |
+
vfeats.append(vfeat)
|
| 602 |
+
vmasks.append(vmask)
|
| 603 |
+
targets.append(target)
|
| 604 |
+
|
| 605 |
+
if (video_len - window_start) <= self.sliding_window_size:
|
| 606 |
+
break
|
| 607 |
+
|
| 608 |
+
vfeats = torch.stack(vfeats)
|
| 609 |
+
vmasks = torch.stack(vmasks)
|
| 610 |
+
targets = torch.cat(targets, dim=0)
|
| 611 |
+
|
| 612 |
+
caps, cmasks = [], []
|
| 613 |
+
for step in text_feature:
|
| 614 |
+
step_text_feature = {"start": [0], "end": [1], "cap": [step]}
|
| 615 |
+
step_text_clip_index = [0]
|
| 616 |
+
cap, cmask = self._build_text_seq(
|
| 617 |
+
step_text_feature, step_text_clip_index
|
| 618 |
+
)
|
| 619 |
+
caps.append(cap)
|
| 620 |
+
cmasks.append(cmask)
|
| 621 |
+
caps = torch.stack(caps)
|
| 622 |
+
cmasks = torch.stack(cmasks)
|
| 623 |
+
|
| 624 |
+
return {
|
| 625 |
+
"caps": caps,
|
| 626 |
+
"cmasks": cmasks,
|
| 627 |
+
"vfeats": vfeats, # X for original code.
|
| 628 |
+
"vmasks": vmasks,
|
| 629 |
+
"targets": targets,
|
| 630 |
+
"video_id": vid,
|
| 631 |
+
"task": task,
|
| 632 |
+
"video_len": video_len # for later checking.
|
| 633 |
+
}
|
| 634 |
+
|
| 635 |
+
def _read_assignment(self, T, K, path):
|
| 636 |
+
"""
|
| 637 |
+
refactored from https://github.com/DmZhukov/CrossTask/blob/master/data.py
|
| 638 |
+
Howto interpret contraints on loss that is going to be minimized:
|
| 639 |
+
lambd is a big number;
|
| 640 |
+
self.lambd * C is a big number for all valid position (csv stores invalids)
|
| 641 |
+
|
| 642 |
+
def forward(self, O, Y, C):
|
| 643 |
+
return (Y*(self.lambd * C - self.lsm(O))).mean(dim=0).sum()
|
| 644 |
+
|
| 645 |
+
This will load the csv file and fill-in the step col from start to end rows.
|
| 646 |
+
"""
|
| 647 |
+
|
| 648 |
+
Y = np.zeros([T, K], dtype=np.uint8)
|
| 649 |
+
with open(path, 'r') as f:
|
| 650 |
+
for line in f:
|
| 651 |
+
step, start, end = line.strip().split(',')
|
| 652 |
+
start = int(math.floor(float(start)))
|
| 653 |
+
end = int(math.ceil(float(end)))
|
| 654 |
+
step = int(step) - 1
|
| 655 |
+
Y[start:end, step] = 1
|
| 656 |
+
return Y
|
| 657 |
+
|
| 658 |
+
|
| 659 |
+
# --------------------- COIN -------------------------
|
| 660 |
+
|
| 661 |
+
class MetaTextBinarizer(Aligner):
|
| 662 |
+
def __call__(self, text_feature):
|
| 663 |
+
text_feature = {
|
| 664 |
+
"cap": [text_feature],
|
| 665 |
+
"start": [0.],
|
| 666 |
+
"end": [100.],
|
| 667 |
+
}
|
| 668 |
+
text_clip_indexs = [0]
|
| 669 |
+
|
| 670 |
+
caps, cmasks = self._build_text_seq(
|
| 671 |
+
text_feature, text_clip_indexs
|
| 672 |
+
)
|
| 673 |
+
return {"caps": caps, "cmasks": cmasks}
|
| 674 |
+
|
| 675 |
+
|
| 676 |
+
class COINActionSegmentationMetaProcessor(MetaProcessor):
|
| 677 |
+
split_map = {
|
| 678 |
+
"train": "training",
|
| 679 |
+
"valid": "testing",
|
| 680 |
+
"test": "testing",
|
| 681 |
+
}
|
| 682 |
+
|
| 683 |
+
def __init__(self, config):
|
| 684 |
+
super().__init__(config)
|
| 685 |
+
with open(self._get_split_path(config)) as fr:
|
| 686 |
+
database = json.load(fr)["database"]
|
| 687 |
+
id2label = {}
|
| 688 |
+
data = []
|
| 689 |
+
# filter the data by split.
|
| 690 |
+
for video_id, rec in database.items():
|
| 691 |
+
# always use testing to determine label_set
|
| 692 |
+
if rec["subset"] == "testing":
|
| 693 |
+
for segment in rec["annotation"]:
|
| 694 |
+
id2label[int(segment["id"])] = segment["label"]
|
| 695 |
+
# text_labels is used for ZS setting
|
| 696 |
+
self.text_labels = ["none"] * len(id2label)
|
| 697 |
+
for label_id in id2label:
|
| 698 |
+
self.text_labels[label_id-1] = id2label[label_id]
|
| 699 |
+
|
| 700 |
+
id2label[0] = "O"
|
| 701 |
+
print("num of labels", len(id2label))
|
| 702 |
+
|
| 703 |
+
for video_id, rec in database.items():
|
| 704 |
+
if not os.path.isfile(os.path.join(config.vfeat_dir, video_id + ".npy")):
|
| 705 |
+
continue
|
| 706 |
+
if rec["subset"] == COINActionSegmentationMetaProcessor.split_map[self.split]:
|
| 707 |
+
starts, ends, labels = [], [], []
|
| 708 |
+
for segment in rec["annotation"]:
|
| 709 |
+
start, end = segment["segment"]
|
| 710 |
+
label = int(segment["id"])
|
| 711 |
+
starts.append(start)
|
| 712 |
+
ends.append(end)
|
| 713 |
+
labels.append(label)
|
| 714 |
+
data.append(
|
| 715 |
+
(video_id, {"start": starts, "end": ends, "label": labels}))
|
| 716 |
+
self.data = data
|
| 717 |
+
|
| 718 |
+
def meta_text_labels(self, config):
|
| 719 |
+
from transformers import default_data_collator
|
| 720 |
+
from ..utils import get_local_rank
|
| 721 |
+
|
| 722 |
+
text_processor = TextProcessor(config)
|
| 723 |
+
binarizer = MetaTextBinarizer(config)
|
| 724 |
+
# TODO: add prompts to .yaml.
|
| 725 |
+
text_labels = [label for label in self.text_labels]
|
| 726 |
+
|
| 727 |
+
if get_local_rank() == 0:
|
| 728 |
+
print(text_labels)
|
| 729 |
+
|
| 730 |
+
outputs = []
|
| 731 |
+
for text_label in text_labels:
|
| 732 |
+
text_feature = text_processor(text_label)
|
| 733 |
+
outputs.append(binarizer(text_feature))
|
| 734 |
+
return default_data_collator(outputs)
|
| 735 |
+
|
| 736 |
+
def __getitem__(self, idx):
|
| 737 |
+
return self.data[idx]
|
| 738 |
+
|
| 739 |
+
|
| 740 |
+
class COINActionSegmentationTextProcessor(TextProcessor):
|
| 741 |
+
def __call__(self, text_label):
|
| 742 |
+
return text_label
|
| 743 |
+
|
| 744 |
+
|
| 745 |
+
class COINActionSegmentationAligner(Aligner):
|
| 746 |
+
def __init__(self, config):
|
| 747 |
+
super().__init__(config)
|
| 748 |
+
self.sliding_window = config.sliding_window
|
| 749 |
+
self.sliding_window_size = config.sliding_window_size
|
| 750 |
+
|
| 751 |
+
def __call__(self, video_id, video_feature, text_feature):
|
| 752 |
+
starts, ends, label_ids = text_feature["start"], text_feature["end"], text_feature["label"]
|
| 753 |
+
# sliding window.
|
| 754 |
+
video_len = len(video_feature)
|
| 755 |
+
|
| 756 |
+
vfeats, vmasks, targets = [], [], []
|
| 757 |
+
# sliding window on video features and targets.
|
| 758 |
+
for window_start in range(0, video_len, self.sliding_window):
|
| 759 |
+
video_start = 0
|
| 760 |
+
video_end = min(video_len - window_start, self.sliding_window_size)
|
| 761 |
+
video_clip = {"start": [video_start], "end": [video_end]}
|
| 762 |
+
vfeat, vmask = self._build_video_seq(
|
| 763 |
+
video_feature[window_start: window_start + video_end],
|
| 764 |
+
video_clip
|
| 765 |
+
)
|
| 766 |
+
# covers video length only.
|
| 767 |
+
target = torch.full_like(vmask, -100, dtype=torch.long)
|
| 768 |
+
target[vmask] = 0
|
| 769 |
+
for start, end, label_id in zip(starts, ends, label_ids):
|
| 770 |
+
if (window_start < end) and (start < (window_start + video_end)):
|
| 771 |
+
start_offset = max(0, math.floor(start) - window_start)
|
| 772 |
+
end_offset = min(video_end, math.ceil(end) - window_start)
|
| 773 |
+
target[start_offset:end_offset] = label_id
|
| 774 |
+
vfeats.append(vfeat)
|
| 775 |
+
vmasks.append(vmask)
|
| 776 |
+
targets.append(target)
|
| 777 |
+
if (video_len - window_start) <= self.sliding_window_size:
|
| 778 |
+
break
|
| 779 |
+
|
| 780 |
+
vfeats = torch.stack(vfeats)
|
| 781 |
+
vmasks = torch.stack(vmasks)
|
| 782 |
+
targets = torch.stack(targets)
|
| 783 |
+
video_targets = torch.full((video_len,), 0)
|
| 784 |
+
for start, end, label_id in zip(starts, ends, label_ids):
|
| 785 |
+
start_offset = max(0, math.floor(start))
|
| 786 |
+
end_offset = min(video_len, math.ceil(end))
|
| 787 |
+
video_targets[start_offset:end_offset] = label_id
|
| 788 |
+
|
| 789 |
+
caps = torch.LongTensor(
|
| 790 |
+
[[self.cls_token_id, self.sep_token_id,
|
| 791 |
+
self.pad_token_id, self.sep_token_id]],
|
| 792 |
+
).repeat(vfeats.size(0), 1)
|
| 793 |
+
cmasks = torch.BoolTensor(
|
| 794 |
+
[[0, 1, 0, 1]] # pad are valid for attention.
|
| 795 |
+
).repeat(vfeats.size(0), 1)
|
| 796 |
+
return {
|
| 797 |
+
"caps": caps,
|
| 798 |
+
"cmasks": cmasks,
|
| 799 |
+
"vfeats": vfeats, # X for original code.
|
| 800 |
+
"vmasks": vmasks,
|
| 801 |
+
"targets": targets,
|
| 802 |
+
"video_id": video_id,
|
| 803 |
+
"video_len": video_len, # for later checking.
|
| 804 |
+
"video_targets": video_targets
|
| 805 |
+
}
|
| 806 |
+
|
| 807 |
+
|
| 808 |
+
class DiDeMoMetaProcessor(MetaProcessor):
|
| 809 |
+
"""reference: https://github.com/LisaAnne/LocalizingMoments/blob/master/utils/eval.py
|
| 810 |
+
https://github.com/LisaAnne/LocalizingMoments/blob/master/utils/data_processing.py
|
| 811 |
+
"""
|
| 812 |
+
def __init__(self, config):
|
| 813 |
+
super().__init__(config)
|
| 814 |
+
|
| 815 |
+
assert "test" in self._get_split_path(config), "DiDeMo only supports zero-shot testing for now."
|
| 816 |
+
|
| 817 |
+
with open(self._get_split_path(config)) as data_file:
|
| 818 |
+
json_data = json.load(data_file)
|
| 819 |
+
|
| 820 |
+
data = []
|
| 821 |
+
for record in json_data:
|
| 822 |
+
data.append((record["video"], record["description"]))
|
| 823 |
+
self.data = data
|
| 824 |
+
|
| 825 |
+
def __len__(self):
|
| 826 |
+
return len(self.data)
|
| 827 |
+
|
| 828 |
+
def __getitem__(self, idx):
|
| 829 |
+
return self.data[idx]
|
| 830 |
+
|
| 831 |
+
|
| 832 |
+
class DiDeMoTextProcessor(TextProcessor):
|
| 833 |
+
"""reference: https://github.com/LisaAnne/LocalizingMoments/blob/master/utils/eval.py
|
| 834 |
+
https://github.com/LisaAnne/LocalizingMoments/blob/master/utils/data_processing.py
|
| 835 |
+
"""
|
| 836 |
+
|
| 837 |
+
def __call__(self, text):
|
| 838 |
+
return self.tokenizer(text, add_special_tokens=False)["input_ids"]
|
| 839 |
+
|
| 840 |
+
|
| 841 |
+
class DiDeMoAligner(DSAligner):
|
| 842 |
+
"""
|
| 843 |
+
check video length.
|
| 844 |
+
"""
|
| 845 |
+
|
| 846 |
+
def __call__(self, video_id, video_feature, text_feature):
|
| 847 |
+
# print(video_feature.shape[0])
|
| 848 |
+
return super().__call__(video_id, video_feature, text_feature)
|
data/fairseq/examples/MMPT/mmpt/processors/how2processor.py
ADDED
|
@@ -0,0 +1,887 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2018 The Google AI Language Team Authors and The HuggingFace Inc. team.
|
| 3 |
+
# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved.
|
| 4 |
+
#
|
| 5 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 6 |
+
# you may not use this file except in compliance with the License.
|
| 7 |
+
# You may obtain a copy of the License at
|
| 8 |
+
#
|
| 9 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 10 |
+
#
|
| 11 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 12 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 13 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 14 |
+
# See the License for the specific language governing permissions and
|
| 15 |
+
# limitations under the License.
|
| 16 |
+
# Copyright (c) Facebook, Inc. All Rights Reserved
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
import torch
|
| 20 |
+
import math
|
| 21 |
+
import pickle
|
| 22 |
+
import random
|
| 23 |
+
import os
|
| 24 |
+
import numpy as np
|
| 25 |
+
|
| 26 |
+
from collections import deque
|
| 27 |
+
from typing import Optional, Tuple, List
|
| 28 |
+
from .processor import (
|
| 29 |
+
Processor,
|
| 30 |
+
MetaProcessor,
|
| 31 |
+
TextProcessor,
|
| 32 |
+
Aligner,
|
| 33 |
+
MMAttentionMask2DProcessor
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
from ..utils import ShardedTensor
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class How2MetaProcessor(MetaProcessor):
|
| 40 |
+
def __init__(self, config):
|
| 41 |
+
super().__init__(config)
|
| 42 |
+
path = self._get_split_path(config)
|
| 43 |
+
with open(path) as fd:
|
| 44 |
+
self.data = [line.strip() for line in fd]
|
| 45 |
+
|
| 46 |
+
def __getitem__(self, idx):
|
| 47 |
+
video_id = self.data[idx]
|
| 48 |
+
return video_id, video_id
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class ShardedHow2MetaProcessor(How2MetaProcessor):
|
| 52 |
+
def __init__(self, config):
|
| 53 |
+
super().__init__(config)
|
| 54 |
+
self.split = str(config.split)
|
| 55 |
+
self.vfeat_dir = config.vfeat_dir
|
| 56 |
+
self._init_shard()
|
| 57 |
+
|
| 58 |
+
def _init_shard(self):
|
| 59 |
+
if self.split == "train":
|
| 60 |
+
meta_fn = os.path.join(self.vfeat_dir, "train" + "_meta.pkl")
|
| 61 |
+
with open(meta_fn, "rb") as fr:
|
| 62 |
+
meta = pickle.load(fr)
|
| 63 |
+
elif self.split == "valid":
|
| 64 |
+
meta_fn = os.path.join(self.vfeat_dir, "val" + "_meta.pkl")
|
| 65 |
+
with open(meta_fn, "rb") as fr:
|
| 66 |
+
meta = pickle.load(fr)
|
| 67 |
+
elif self.split == "test":
|
| 68 |
+
print("use how2 val as test.")
|
| 69 |
+
meta_fn = os.path.join(self.vfeat_dir, "val" + "_meta.pkl")
|
| 70 |
+
with open(meta_fn, "rb") as fr:
|
| 71 |
+
meta = pickle.load(fr)
|
| 72 |
+
else:
|
| 73 |
+
raise ValueError("unsupported for MetaProcessor:", self.split)
|
| 74 |
+
video_id_to_shard = {}
|
| 75 |
+
for shard_id in meta:
|
| 76 |
+
for video_idx, video_id in enumerate(meta[shard_id]):
|
| 77 |
+
video_id_to_shard[video_id] = (shard_id, video_idx)
|
| 78 |
+
self.video_id_to_shard = video_id_to_shard
|
| 79 |
+
|
| 80 |
+
def __getitem__(self, idx):
|
| 81 |
+
video_id, video_id = super().__getitem__(idx)
|
| 82 |
+
shard_id, shard_idx = self.video_id_to_shard[video_id]
|
| 83 |
+
meta = (video_id, idx, shard_id, shard_idx)
|
| 84 |
+
return meta, meta
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
class ShardedVideoProcessor(Processor):
|
| 88 |
+
"""
|
| 89 |
+
mmaped shards of numpy video features.
|
| 90 |
+
"""
|
| 91 |
+
|
| 92 |
+
def __init__(self, config):
|
| 93 |
+
self.split = str(config.split)
|
| 94 |
+
self.vfeat_dir = config.vfeat_dir
|
| 95 |
+
|
| 96 |
+
def __call__(self, video_id):
|
| 97 |
+
_, _, shard_id, video_idx = video_id
|
| 98 |
+
if self.split == "train":
|
| 99 |
+
shard = ShardedTensor.load(
|
| 100 |
+
os.path.join(self.vfeat_dir, "train" + "_" + str(shard_id)),
|
| 101 |
+
"r"
|
| 102 |
+
)
|
| 103 |
+
elif self.split == "valid":
|
| 104 |
+
shard = ShardedTensor.load(
|
| 105 |
+
os.path.join(self.vfeat_dir, "val" + "_" + str(shard_id)),
|
| 106 |
+
"r"
|
| 107 |
+
)
|
| 108 |
+
elif self.split == "test":
|
| 109 |
+
shard = ShardedTensor.load(
|
| 110 |
+
os.path.join(self.vfeat_dir, "val" + "_" + str(shard_id)),
|
| 111 |
+
"r"
|
| 112 |
+
)
|
| 113 |
+
else:
|
| 114 |
+
raise ValueError("unknown split", self.split)
|
| 115 |
+
feat = shard[video_idx]
|
| 116 |
+
return feat
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
class ShardedTextProcessor(Processor):
|
| 120 |
+
def __init__(self, config):
|
| 121 |
+
self.tfeat_dir = str(config.tfeat_dir)
|
| 122 |
+
self.split = str(config.split)
|
| 123 |
+
|
| 124 |
+
def __call__(self, video_id):
|
| 125 |
+
_, _, shard_id, shard_idx = video_id
|
| 126 |
+
if self.split == "train":
|
| 127 |
+
target_path = self.tfeat_dir + "train" + "_" + str(shard_id)
|
| 128 |
+
elif self.split == "valid":
|
| 129 |
+
target_path = self.tfeat_dir + "val" + "_" + str(shard_id)
|
| 130 |
+
elif self.split == "test":
|
| 131 |
+
target_path = self.tfeat_dir + "val" + "_" + str(shard_id)
|
| 132 |
+
else:
|
| 133 |
+
raise ValueError("unknown split", self.split)
|
| 134 |
+
|
| 135 |
+
startend = ShardedTensor.load(
|
| 136 |
+
target_path + ".startends", "r")[shard_idx]
|
| 137 |
+
cap_ids = ShardedTensor.load(
|
| 138 |
+
target_path + ".caps_ids", "r")[shard_idx]
|
| 139 |
+
cap = []
|
| 140 |
+
for clip_idx in range(len(cap_ids)):
|
| 141 |
+
clip = cap_ids[clip_idx]
|
| 142 |
+
cap.append(clip[clip != -1].tolist())
|
| 143 |
+
start, end = startend[:, 0].tolist(), startend[:, 1].tolist()
|
| 144 |
+
return {"start": start, "end": end, "cap": cap}
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
class FixedLenAligner(Aligner):
|
| 148 |
+
"""
|
| 149 |
+
In the model we assume text is on the left (closer to BERT formulation)
|
| 150 |
+
and video is on the right.
|
| 151 |
+
We fix the total length of text + video.
|
| 152 |
+
max_video_len is in number of secs.
|
| 153 |
+
max_text_len is in number of tokens.
|
| 154 |
+
|
| 155 |
+
special tokens formats:
|
| 156 |
+
we use the format [CLS] [SEP] text tokens [SEP] [PAD] ...
|
| 157 |
+
[CLS] will be splitted out into:
|
| 158 |
+
[CLS] video tokens [SEP] text tokens [SEP] [PAD] ...
|
| 159 |
+
token_type_ids will be generated by the model (for now).
|
| 160 |
+
0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1
|
| 161 |
+
| first sequence | second sequence |
|
| 162 |
+
so each sequence owns a [SEP] token for no-ops.
|
| 163 |
+
"""
|
| 164 |
+
|
| 165 |
+
def __init__(self, config):
|
| 166 |
+
super().__init__(config)
|
| 167 |
+
self.text_clip_sampler = TextClipSamplingProcessor(
|
| 168 |
+
self.max_len - self.max_video_len - 3
|
| 169 |
+
)
|
| 170 |
+
"""
|
| 171 |
+
decide subsampling:
|
| 172 |
+
`config.subsampling` will change batch_size in trainer.
|
| 173 |
+
`config.clip_per_video` (used by RetriTask) doesn't
|
| 174 |
+
change batch_size in trainer.
|
| 175 |
+
"""
|
| 176 |
+
subsampling = config.subsampling \
|
| 177 |
+
if config.subsampling is not None else None
|
| 178 |
+
if config.clip_per_video is not None:
|
| 179 |
+
subsampling = config.clip_per_video
|
| 180 |
+
self.subsampling = subsampling
|
| 181 |
+
|
| 182 |
+
def _get_text_maxlen(self):
|
| 183 |
+
# use max text len
|
| 184 |
+
return self.text_clip_sampler.max_text_len
|
| 185 |
+
|
| 186 |
+
def __call__(self, video_id, video_feature, text_feature):
|
| 187 |
+
from transformers import default_data_collator
|
| 188 |
+
video_idx = video_id[1]
|
| 189 |
+
if self.subsampling is not None and self.subsampling >= 1:
|
| 190 |
+
batch = []
|
| 191 |
+
for _ in range(self.subsampling):
|
| 192 |
+
centerclip_idx = random.randint(
|
| 193 |
+
0, len(text_feature["start"]) - 1)
|
| 194 |
+
batch.append(
|
| 195 |
+
self.sampling(
|
| 196 |
+
video_idx,
|
| 197 |
+
video_feature,
|
| 198 |
+
text_feature,
|
| 199 |
+
centerclip_idx,
|
| 200 |
+
self._get_text_maxlen()
|
| 201 |
+
))
|
| 202 |
+
batch = self.batch_post_processing(batch, video_feature)
|
| 203 |
+
batch = default_data_collator(batch)
|
| 204 |
+
else:
|
| 205 |
+
raise ValueError(
|
| 206 |
+
"dataset.subsampling must be >= 1 for efficient video loading.")
|
| 207 |
+
batch = self.sampling(video_idx, video_feature, text_feature)
|
| 208 |
+
batch = self.batch_post_processing(batch, video_feature)
|
| 209 |
+
|
| 210 |
+
batch["video_id"] = video_id if isinstance(video_id, str) \
|
| 211 |
+
else video_id[0]
|
| 212 |
+
# e2e: make sure frame ids is into tensor.
|
| 213 |
+
assert torch.is_tensor(batch["vfeats"])
|
| 214 |
+
return batch
|
| 215 |
+
|
| 216 |
+
def sampling(
|
| 217 |
+
self,
|
| 218 |
+
video_idx,
|
| 219 |
+
video_feature,
|
| 220 |
+
text_feature,
|
| 221 |
+
centerclip_idx=None,
|
| 222 |
+
sampled_max_text_len=None,
|
| 223 |
+
):
|
| 224 |
+
text_clip_indexs = self.text_clip_sampler(
|
| 225 |
+
text_feature, centerclip_idx,
|
| 226 |
+
sampled_max_text_len
|
| 227 |
+
)
|
| 228 |
+
if isinstance(video_feature, np.ndarray):
|
| 229 |
+
video_len = len(video_feature)
|
| 230 |
+
else:
|
| 231 |
+
video_len = math.ceil(text_feature["end"][-1])
|
| 232 |
+
|
| 233 |
+
video_end = min(
|
| 234 |
+
math.ceil(text_feature["end"][text_clip_indexs[-1]]),
|
| 235 |
+
video_len
|
| 236 |
+
)
|
| 237 |
+
video_start = max(
|
| 238 |
+
min(
|
| 239 |
+
math.floor(text_feature["start"][text_clip_indexs[0]]),
|
| 240 |
+
video_end),
|
| 241 |
+
0
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
video_clips = {"start": [video_start], "end": [video_end]}
|
| 245 |
+
|
| 246 |
+
# tensorize.
|
| 247 |
+
vfeats, vmasks = self._build_video_seq(
|
| 248 |
+
video_feature, video_clips
|
| 249 |
+
)
|
| 250 |
+
caps, cmasks = self._build_text_seq(
|
| 251 |
+
text_feature, text_clip_indexs
|
| 252 |
+
)
|
| 253 |
+
|
| 254 |
+
text_start = text_clip_indexs[0]
|
| 255 |
+
text_end = text_clip_indexs[-1] + 1
|
| 256 |
+
|
| 257 |
+
return {
|
| 258 |
+
"caps": caps,
|
| 259 |
+
"cmasks": cmasks,
|
| 260 |
+
"vfeats": vfeats,
|
| 261 |
+
"vmasks": vmasks,
|
| 262 |
+
"video_start": video_start,
|
| 263 |
+
"video_end": video_end,
|
| 264 |
+
"text_start": text_start,
|
| 265 |
+
"text_end": text_end,
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
class VariedLenAligner(FixedLenAligner):
|
| 270 |
+
def __init__(self, config):
|
| 271 |
+
super().__init__(config)
|
| 272 |
+
self.sampled_min_len = config.sampled_min_len
|
| 273 |
+
self.sampled_max_len = config.sampled_max_len
|
| 274 |
+
|
| 275 |
+
def _get_text_maxlen(self):
|
| 276 |
+
return random.randint(self.sampled_min_len, self.sampled_max_len)
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
class StartClipAligner(VariedLenAligner):
|
| 280 |
+
def sampling(
|
| 281 |
+
self,
|
| 282 |
+
video_idx,
|
| 283 |
+
video_feature,
|
| 284 |
+
text_feature,
|
| 285 |
+
centerclip_idx=None,
|
| 286 |
+
sampled_max_text_len=None,
|
| 287 |
+
):
|
| 288 |
+
return super().sampling(
|
| 289 |
+
video_idx, video_feature, text_feature, 0)
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
class OverlappedAligner(VariedLenAligner):
|
| 293 |
+
"""video clip and text clip has overlappings
|
| 294 |
+
but may not be the same start/end."""
|
| 295 |
+
def __init__(self, config):
|
| 296 |
+
super().__init__(config)
|
| 297 |
+
self.sampled_video_min_len = config.sampled_video_min_len
|
| 298 |
+
self.sampled_video_max_len = config.sampled_video_max_len
|
| 299 |
+
|
| 300 |
+
self.video_clip_sampler = VideoClipSamplingProcessor()
|
| 301 |
+
|
| 302 |
+
def _get_video_maxlen(self):
|
| 303 |
+
return random.randint(
|
| 304 |
+
self.sampled_video_min_len, self.sampled_video_max_len)
|
| 305 |
+
|
| 306 |
+
def sampling(
|
| 307 |
+
self,
|
| 308 |
+
video_idx,
|
| 309 |
+
video_feature,
|
| 310 |
+
text_feature,
|
| 311 |
+
centerclip_idx=None,
|
| 312 |
+
sampled_max_text_len=None,
|
| 313 |
+
):
|
| 314 |
+
text_clip_indexs = self.text_clip_sampler(
|
| 315 |
+
text_feature, centerclip_idx,
|
| 316 |
+
sampled_max_text_len
|
| 317 |
+
)
|
| 318 |
+
if isinstance(video_feature, np.ndarray):
|
| 319 |
+
video_len = len(video_feature)
|
| 320 |
+
else:
|
| 321 |
+
video_len = math.ceil(text_feature["end"][-1])
|
| 322 |
+
low = math.floor(text_feature["start"][text_clip_indexs[0]])
|
| 323 |
+
high = math.ceil(text_feature["end"][text_clip_indexs[-1]])
|
| 324 |
+
if low < high:
|
| 325 |
+
center = random.randint(low, high)
|
| 326 |
+
else:
|
| 327 |
+
center = int((low + high) // 2)
|
| 328 |
+
center = max(0, min(video_feature.shape[0] - 1, center))
|
| 329 |
+
|
| 330 |
+
assert 0 <= center < video_feature.shape[0]
|
| 331 |
+
|
| 332 |
+
video_clips = self.video_clip_sampler(
|
| 333 |
+
video_len, self._get_video_maxlen(), center
|
| 334 |
+
)
|
| 335 |
+
video_start = video_clips["start"][0]
|
| 336 |
+
video_end = video_clips["end"][0]
|
| 337 |
+
|
| 338 |
+
# tensorize.
|
| 339 |
+
vfeats, vmasks = self._build_video_seq(
|
| 340 |
+
video_feature, video_clips
|
| 341 |
+
)
|
| 342 |
+
caps, cmasks = self._build_text_seq(
|
| 343 |
+
text_feature, text_clip_indexs
|
| 344 |
+
)
|
| 345 |
+
|
| 346 |
+
text_start = text_clip_indexs[0]
|
| 347 |
+
text_end = text_clip_indexs[-1] + 1
|
| 348 |
+
|
| 349 |
+
return {
|
| 350 |
+
"caps": caps,
|
| 351 |
+
"cmasks": cmasks,
|
| 352 |
+
"vfeats": vfeats,
|
| 353 |
+
"vmasks": vmasks,
|
| 354 |
+
"video_start": video_start,
|
| 355 |
+
"video_end": video_end,
|
| 356 |
+
"text_start": text_start,
|
| 357 |
+
"text_end": text_end,
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
class MFMMLMAligner(FixedLenAligner):
|
| 362 |
+
"""
|
| 363 |
+
`FixedLenAligner` with Masked Language Model and Masked Frame Model.
|
| 364 |
+
"""
|
| 365 |
+
|
| 366 |
+
def __init__(self, config):
|
| 367 |
+
super().__init__(config)
|
| 368 |
+
keep_prob = config.keep_prob if config.keep_prob is not None else 1.0
|
| 369 |
+
self.text_clip_sampler = TextClipSamplingProcessor(
|
| 370 |
+
self.max_len - self.max_video_len - 3, keep_prob
|
| 371 |
+
)
|
| 372 |
+
self.sampled_min_len = config.sampled_min_len
|
| 373 |
+
self.sampled_max_len = config.sampled_max_len
|
| 374 |
+
self.masked_token_sampler = TextMaskingProcessor(config)
|
| 375 |
+
self.mm_type = config.mm_type \
|
| 376 |
+
if config.mm_type is not None else "full"
|
| 377 |
+
self.attnmasker = MMAttentionMask2DProcessor() \
|
| 378 |
+
if self.mm_type == "textgen" else None
|
| 379 |
+
self.masked_frame_sampler = FrameMaskingProcessor(config)
|
| 380 |
+
self.lazy_vfeat_mask = (
|
| 381 |
+
False if config.lazy_vfeat_mask is None else config.lazy_vfeat_mask
|
| 382 |
+
)
|
| 383 |
+
self.mm_prob = config.mm_prob if config.mm_prob is not None else 0.
|
| 384 |
+
|
| 385 |
+
def __call__(self, video_id, video_feature, text_feature):
|
| 386 |
+
from transformers import default_data_collator
|
| 387 |
+
if self.subsampling is not None and self.subsampling > 1:
|
| 388 |
+
batch = []
|
| 389 |
+
for _ in range(self.subsampling):
|
| 390 |
+
centerclip_idx = random.randint(
|
| 391 |
+
0, len(text_feature["start"]) - 1)
|
| 392 |
+
sampled_max_text_len = random.randint(
|
| 393 |
+
self.sampled_min_len, self.sampled_max_len
|
| 394 |
+
)
|
| 395 |
+
batch.append(
|
| 396 |
+
self.sampling(
|
| 397 |
+
video_id,
|
| 398 |
+
video_feature,
|
| 399 |
+
text_feature,
|
| 400 |
+
centerclip_idx,
|
| 401 |
+
sampled_max_text_len,
|
| 402 |
+
)
|
| 403 |
+
)
|
| 404 |
+
batch = self.batch_post_processing(batch, video_feature)
|
| 405 |
+
batch = default_data_collator(batch)
|
| 406 |
+
else:
|
| 407 |
+
batch = self.sampling(video_id, video_feature, text_feature)
|
| 408 |
+
batch = self.batch_post_processing(batch, video_feature)
|
| 409 |
+
batch["video_id"] = video_id if isinstance(video_id, str) \
|
| 410 |
+
else video_id[0]
|
| 411 |
+
return batch
|
| 412 |
+
|
| 413 |
+
def sampling(
|
| 414 |
+
self,
|
| 415 |
+
video_id,
|
| 416 |
+
video_feature,
|
| 417 |
+
text_feature,
|
| 418 |
+
centerclip_idx=None,
|
| 419 |
+
sampled_max_text_len=None,
|
| 420 |
+
):
|
| 421 |
+
output = FixedLenAligner.sampling(self,
|
| 422 |
+
video_id, video_feature, text_feature,
|
| 423 |
+
centerclip_idx, sampled_max_text_len)
|
| 424 |
+
|
| 425 |
+
masking_text, masking_video = None, None
|
| 426 |
+
if random.random() < self.mm_prob:
|
| 427 |
+
if random.random() > 0.5:
|
| 428 |
+
masking_text, masking_video = self.mm_type, "no"
|
| 429 |
+
else:
|
| 430 |
+
masking_text, masking_video = "no", "full"
|
| 431 |
+
video_feats = output["vfeats"] if not self.lazy_vfeat_mask else None
|
| 432 |
+
video_label = self.masked_frame_sampler(
|
| 433 |
+
output["vmasks"], masking_video, vfeats=video_feats)
|
| 434 |
+
caps, text_label = self.masked_token_sampler(
|
| 435 |
+
output["caps"], masking_text)
|
| 436 |
+
|
| 437 |
+
output.update({
|
| 438 |
+
"caps": caps,
|
| 439 |
+
"video_label": video_label,
|
| 440 |
+
"text_label": text_label,
|
| 441 |
+
})
|
| 442 |
+
|
| 443 |
+
if self.attnmasker is not None:
|
| 444 |
+
attention_mask = self.attnmasker(
|
| 445 |
+
output["vmasks"], output["cmasks"], masking_text)
|
| 446 |
+
output.update({
|
| 447 |
+
"attention_mask": attention_mask
|
| 448 |
+
})
|
| 449 |
+
return output
|
| 450 |
+
|
| 451 |
+
|
| 452 |
+
class FrameMaskingProcessor(Processor):
|
| 453 |
+
def __init__(self, config):
|
| 454 |
+
self.mfm_probability = 0.15
|
| 455 |
+
if config.mfm_probability is not None:
|
| 456 |
+
self.mfm_probability = config.mfm_probability
|
| 457 |
+
|
| 458 |
+
def __call__(self, vmasks, modality_masking=None, vfeats=None):
|
| 459 |
+
"""
|
| 460 |
+
We perform lazy masking to save data transfer time.
|
| 461 |
+
It only generates video_labels by default and MFM model
|
| 462 |
+
will do actualy masking.
|
| 463 |
+
Return: `video_label` is a binary mask.
|
| 464 |
+
"""
|
| 465 |
+
video_label = vmasks.clone()
|
| 466 |
+
if modality_masking is not None:
|
| 467 |
+
if modality_masking == "full":
|
| 468 |
+
probability_matrix = torch.full(video_label.shape, 1.)
|
| 469 |
+
elif modality_masking == "no":
|
| 470 |
+
probability_matrix = torch.full(video_label.shape, 0.)
|
| 471 |
+
elif modality_masking == "inverse":
|
| 472 |
+
probability_matrix = torch.full(
|
| 473 |
+
video_label.shape, 1. - self.mfm_probability)
|
| 474 |
+
else:
|
| 475 |
+
raise ValueError("unknown modality masking.", modality_masking)
|
| 476 |
+
else:
|
| 477 |
+
probability_matrix = torch.full(
|
| 478 |
+
video_label.shape, self.mfm_probability)
|
| 479 |
+
masked_indices = torch.bernoulli(probability_matrix).bool()
|
| 480 |
+
# We only compute loss on masked tokens
|
| 481 |
+
video_label[~masked_indices] = 0
|
| 482 |
+
if vfeats is not None:
|
| 483 |
+
vfeats[video_label, :] = 0.0
|
| 484 |
+
return video_label
|
| 485 |
+
|
| 486 |
+
|
| 487 |
+
class TextGenerationProcessor(Processor):
|
| 488 |
+
def __init__(self, tokenizer):
|
| 489 |
+
self.bos_token_id = tokenizer.bos_token_id
|
| 490 |
+
self.pad_token_id = tokenizer.pad_token_id
|
| 491 |
+
|
| 492 |
+
def __call__(self, inputs):
|
| 493 |
+
labels = inputs.clone()
|
| 494 |
+
# [CLS] [SEP] for video
|
| 495 |
+
labels[:2] = -100
|
| 496 |
+
# keep [SEP] for text.
|
| 497 |
+
pad_mask = labels == self.pad_token_id
|
| 498 |
+
labels[pad_mask] = -100
|
| 499 |
+
inputs[2:] = torch.cat([
|
| 500 |
+
torch.LongTensor([self.bos_token_id]),
|
| 501 |
+
inputs[2:-1]])
|
| 502 |
+
inputs[pad_mask] = self.pad_token_id
|
| 503 |
+
assert len(inputs) == len(labels)
|
| 504 |
+
return inputs, labels
|
| 505 |
+
|
| 506 |
+
|
| 507 |
+
class TextMaskingProcessor(Processor):
|
| 508 |
+
def __init__(self, config):
|
| 509 |
+
"""this function is borrowed from
|
| 510 |
+
`transformers/data/data_collator.DataCollatorForLanguageModeling`"""
|
| 511 |
+
self.mlm_probability = 0.15
|
| 512 |
+
if config.mlm_probability is not None:
|
| 513 |
+
self.mlm_probability = config.mlm_probability
|
| 514 |
+
self.bert_name = config.bert_name
|
| 515 |
+
# [CLS] is used as bos_token and [SEP] is used as eos_token.
|
| 516 |
+
# https://huggingface.co/transformers/master/model_doc/bertgeneration.html
|
| 517 |
+
from transformers import AutoTokenizer
|
| 518 |
+
self.tokenizer = AutoTokenizer.from_pretrained(
|
| 519 |
+
self.bert_name, bos_token="[CLS]", eos_token="[SEP]")
|
| 520 |
+
self.textgen = TextGenerationProcessor(self.tokenizer)
|
| 521 |
+
|
| 522 |
+
def __call__(
|
| 523 |
+
self, inputs: torch.Tensor,
|
| 524 |
+
modality_masking=None,
|
| 525 |
+
special_tokens_mask: Optional[torch.Tensor] = None
|
| 526 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 527 |
+
"""
|
| 528 |
+
expand modality_masking into
|
| 529 |
+
None: traditional bert masking.
|
| 530 |
+
"no": no masking.
|
| 531 |
+
"full": all [MASK] token for generation.
|
| 532 |
+
"gen": autoregressive generation.
|
| 533 |
+
"""
|
| 534 |
+
"""
|
| 535 |
+
Prepare masked tokens inputs/labels for masked language modeling:
|
| 536 |
+
80% MASK, 10% random, 10% original.
|
| 537 |
+
"""
|
| 538 |
+
labels = inputs.clone()
|
| 539 |
+
# We sample a few tokens in each sequence for MLM training
|
| 540 |
+
# (with probability `self.mlm_probability`)
|
| 541 |
+
if modality_masking is not None:
|
| 542 |
+
if modality_masking == "full":
|
| 543 |
+
probability_matrix = torch.full(labels.shape, 1.)
|
| 544 |
+
elif modality_masking == "no":
|
| 545 |
+
probability_matrix = torch.full(labels.shape, 0.)
|
| 546 |
+
elif modality_masking.startswith("textgen"):
|
| 547 |
+
# [CLS] [SEP] <s> ...
|
| 548 |
+
inputs, labels = self.textgen(inputs)
|
| 549 |
+
if "mask" not in modality_masking:
|
| 550 |
+
return inputs, labels
|
| 551 |
+
inputs = self.mask_input(inputs, special_tokens_mask)
|
| 552 |
+
return inputs, labels
|
| 553 |
+
elif modality_masking == "mask":
|
| 554 |
+
inputs = self.mask_input(inputs, special_tokens_mask)
|
| 555 |
+
labels = torch.full(inputs.shape, -100)
|
| 556 |
+
return inputs, labels
|
| 557 |
+
elif modality_masking == "inverse":
|
| 558 |
+
probability_matrix = torch.full(labels.shape, 1. - self.mlm_probability)
|
| 559 |
+
else:
|
| 560 |
+
raise ValueError("unknown modality masking.", modality_masking)
|
| 561 |
+
else:
|
| 562 |
+
probability_matrix = torch.full(labels.shape, self.mlm_probability)
|
| 563 |
+
|
| 564 |
+
if special_tokens_mask is None:
|
| 565 |
+
special_tokens_mask = self.get_special_tokens_mask(
|
| 566 |
+
labels.tolist(), already_has_special_tokens=True
|
| 567 |
+
)
|
| 568 |
+
special_tokens_mask = torch.tensor(
|
| 569 |
+
special_tokens_mask, dtype=torch.bool)
|
| 570 |
+
else:
|
| 571 |
+
special_tokens_mask = special_tokens_mask.bool()
|
| 572 |
+
|
| 573 |
+
probability_matrix.masked_fill_(special_tokens_mask, value=0.0)
|
| 574 |
+
masked_indices = torch.bernoulli(probability_matrix).bool()
|
| 575 |
+
labels[~masked_indices] = -100 # We only compute loss on masked tokens
|
| 576 |
+
|
| 577 |
+
# 80% of the time,
|
| 578 |
+
# we replace masked input tokens with tokenizer.mask_token ([MASK])
|
| 579 |
+
indices_replaced = (
|
| 580 |
+
torch.bernoulli(
|
| 581 |
+
torch.full(labels.shape, 0.8)).bool() & masked_indices
|
| 582 |
+
)
|
| 583 |
+
inputs[indices_replaced] = self.tokenizer.convert_tokens_to_ids(
|
| 584 |
+
self.tokenizer.mask_token
|
| 585 |
+
)
|
| 586 |
+
|
| 587 |
+
# 10% of the time, we replace masked input tokens with random word
|
| 588 |
+
indices_random = (
|
| 589 |
+
torch.bernoulli(torch.full(labels.shape, 0.5)).bool()
|
| 590 |
+
& masked_indices
|
| 591 |
+
& ~indices_replaced
|
| 592 |
+
)
|
| 593 |
+
random_words = torch.randint(
|
| 594 |
+
len(self.tokenizer), labels.shape, dtype=torch.long
|
| 595 |
+
)
|
| 596 |
+
inputs[indices_random] = random_words[indices_random]
|
| 597 |
+
|
| 598 |
+
# The rest of the time (10% of the time) we keep the masked input
|
| 599 |
+
# tokens unchanged
|
| 600 |
+
return inputs, labels
|
| 601 |
+
|
| 602 |
+
def mask_input(self, inputs, special_tokens_mask=None):
|
| 603 |
+
# the following is new with masked autoregressive.
|
| 604 |
+
probability_matrix = torch.full(
|
| 605 |
+
inputs.shape, self.mlm_probability)
|
| 606 |
+
if special_tokens_mask is None:
|
| 607 |
+
special_tokens_mask = self.get_special_tokens_mask(
|
| 608 |
+
inputs.tolist(), already_has_special_tokens=True
|
| 609 |
+
)
|
| 610 |
+
special_tokens_mask = torch.tensor(
|
| 611 |
+
special_tokens_mask, dtype=torch.bool)
|
| 612 |
+
else:
|
| 613 |
+
special_tokens_mask = special_tokens_mask.bool()
|
| 614 |
+
probability_matrix.masked_fill_(special_tokens_mask, value=0.0)
|
| 615 |
+
masked_indices = torch.bernoulli(probability_matrix).bool()
|
| 616 |
+
indices_replaced = (
|
| 617 |
+
torch.bernoulli(
|
| 618 |
+
torch.full(inputs.shape, 0.8)).bool() & masked_indices
|
| 619 |
+
)
|
| 620 |
+
inputs[indices_replaced] = self.tokenizer.convert_tokens_to_ids(
|
| 621 |
+
self.tokenizer.mask_token
|
| 622 |
+
)
|
| 623 |
+
|
| 624 |
+
# 10% of the time, we replace masked input tokens with random word
|
| 625 |
+
indices_random = (
|
| 626 |
+
torch.bernoulli(torch.full(inputs.shape, 0.5)).bool()
|
| 627 |
+
& masked_indices
|
| 628 |
+
& ~indices_replaced
|
| 629 |
+
)
|
| 630 |
+
random_words = torch.randint(
|
| 631 |
+
len(self.tokenizer), inputs.shape, dtype=torch.long
|
| 632 |
+
)
|
| 633 |
+
inputs[indices_random] = random_words[indices_random]
|
| 634 |
+
return inputs
|
| 635 |
+
|
| 636 |
+
def get_special_tokens_mask(
|
| 637 |
+
self, token_ids_0: List[int],
|
| 638 |
+
token_ids_1: Optional[List[int]] = None,
|
| 639 |
+
already_has_special_tokens: bool = False
|
| 640 |
+
) -> List[int]:
|
| 641 |
+
"""
|
| 642 |
+
Note: the version from transformers do not consider pad
|
| 643 |
+
as special tokens.
|
| 644 |
+
"""
|
| 645 |
+
|
| 646 |
+
if already_has_special_tokens:
|
| 647 |
+
if token_ids_1 is not None:
|
| 648 |
+
raise ValueError(
|
| 649 |
+
"You should not supply a second sequence if"
|
| 650 |
+
"the provided sequence of "
|
| 651 |
+
"ids is already formated with special tokens "
|
| 652 |
+
"for the model."
|
| 653 |
+
)
|
| 654 |
+
return list(map(lambda x: 1 if x in [
|
| 655 |
+
self.tokenizer.sep_token_id,
|
| 656 |
+
self.tokenizer.cls_token_id,
|
| 657 |
+
self.tokenizer.pad_token_id] else 0, token_ids_0))
|
| 658 |
+
|
| 659 |
+
if token_ids_1 is not None:
|
| 660 |
+
return [1] + ([0] * len(token_ids_0)) + [1] + ([0] * len(token_ids_1)) + [1]
|
| 661 |
+
return [1] + ([0] * len(token_ids_0)) + [1]
|
| 662 |
+
|
| 663 |
+
|
| 664 |
+
class TextClipSamplingProcessor(Processor):
|
| 665 |
+
def __init__(self, max_text_len, keep_prob=1.0):
|
| 666 |
+
self.max_text_len = max_text_len
|
| 667 |
+
self.max_video_len = 256 # always hold.
|
| 668 |
+
self.keep_prob = keep_prob
|
| 669 |
+
|
| 670 |
+
def __call__(
|
| 671 |
+
self,
|
| 672 |
+
text_feature,
|
| 673 |
+
centerclip_idx=None,
|
| 674 |
+
sampled_max_text_len=None,
|
| 675 |
+
sampled_max_video_len=None,
|
| 676 |
+
):
|
| 677 |
+
# Let's use all caps for now and see if 256 can cover all of them.
|
| 678 |
+
if sampled_max_text_len is not None:
|
| 679 |
+
max_text_len = sampled_max_text_len
|
| 680 |
+
else:
|
| 681 |
+
max_text_len = self.max_text_len
|
| 682 |
+
if sampled_max_video_len is not None:
|
| 683 |
+
max_video_len = sampled_max_video_len
|
| 684 |
+
else:
|
| 685 |
+
max_video_len = self.max_video_len
|
| 686 |
+
|
| 687 |
+
t_num_clips = len(text_feature["start"])
|
| 688 |
+
|
| 689 |
+
if centerclip_idx is None:
|
| 690 |
+
centerclip_idx = random.randint(0, t_num_clips - 1)
|
| 691 |
+
|
| 692 |
+
start_idx, end_idx = centerclip_idx, centerclip_idx + 1
|
| 693 |
+
text_clip_indexs = deque()
|
| 694 |
+
text_clip_indexs.append(start_idx)
|
| 695 |
+
text_len = len(text_feature["cap"][start_idx])
|
| 696 |
+
|
| 697 |
+
video_len = max(
|
| 698 |
+
0,
|
| 699 |
+
text_feature["end"][start_idx]
|
| 700 |
+
- text_feature["start"][start_idx],
|
| 701 |
+
)
|
| 702 |
+
|
| 703 |
+
while (
|
| 704 |
+
(start_idx > 0 or end_idx < t_num_clips)
|
| 705 |
+
and text_len < max_text_len
|
| 706 |
+
and video_len < max_video_len
|
| 707 |
+
):
|
| 708 |
+
if random.random() > 0.5 and end_idx < t_num_clips:
|
| 709 |
+
# skip the next one?
|
| 710 |
+
if random.random() > self.keep_prob and (end_idx + 1) < t_num_clips:
|
| 711 |
+
end_idx = end_idx + 1
|
| 712 |
+
text_clip_indexs.append(end_idx)
|
| 713 |
+
text_len += len(text_feature["cap"][end_idx])
|
| 714 |
+
end_idx += 1
|
| 715 |
+
elif start_idx > 0:
|
| 716 |
+
if random.random() > self.keep_prob and (start_idx - 1) > 0:
|
| 717 |
+
start_idx = start_idx - 1
|
| 718 |
+
start_idx -= 1
|
| 719 |
+
text_clip_indexs.insert(0, start_idx)
|
| 720 |
+
text_len += len(text_feature["cap"][start_idx])
|
| 721 |
+
else:
|
| 722 |
+
if end_idx < t_num_clips:
|
| 723 |
+
if random.random() > self.keep_prob and (end_idx + 1) < t_num_clips:
|
| 724 |
+
end_idx = end_idx + 1
|
| 725 |
+
text_clip_indexs.append(end_idx)
|
| 726 |
+
text_len += len(text_feature["cap"][end_idx])
|
| 727 |
+
end_idx += 1
|
| 728 |
+
else:
|
| 729 |
+
return text_clip_indexs
|
| 730 |
+
video_len = max(
|
| 731 |
+
0,
|
| 732 |
+
text_feature["end"][text_clip_indexs[-1]]
|
| 733 |
+
- text_feature["start"][text_clip_indexs[0]],
|
| 734 |
+
)
|
| 735 |
+
return text_clip_indexs
|
| 736 |
+
|
| 737 |
+
|
| 738 |
+
class VideoClipSamplingProcessor(Processor):
|
| 739 |
+
def __call__(self, video_len, max_video_len, center):
|
| 740 |
+
"""
|
| 741 |
+
`video_len`: length of the video.
|
| 742 |
+
`max_video_len`: maximum video tokens allowd in a sequence.
|
| 743 |
+
`center`: initial starting index.
|
| 744 |
+
"""
|
| 745 |
+
assert center >= 0 and center < video_len
|
| 746 |
+
t_clip_len = 0
|
| 747 |
+
start, end = center, center
|
| 748 |
+
while (start > 0 or end < video_len) and t_clip_len < max_video_len:
|
| 749 |
+
# decide the direction to grow.
|
| 750 |
+
if start <= 0:
|
| 751 |
+
end += 1
|
| 752 |
+
elif end >= video_len:
|
| 753 |
+
start -= 1
|
| 754 |
+
elif random.random() > 0.5:
|
| 755 |
+
end += 1
|
| 756 |
+
else:
|
| 757 |
+
start -= 1
|
| 758 |
+
t_clip_len += 1
|
| 759 |
+
return {"start": [start], "end": [end]}
|
| 760 |
+
|
| 761 |
+
|
| 762 |
+
class How2MILNCEAligner(FixedLenAligner):
|
| 763 |
+
"""reference: `antoine77340/MIL-NCE_HowTo100M/video_loader.py`"""
|
| 764 |
+
|
| 765 |
+
def __init__(self, config):
|
| 766 |
+
super().__init__(config)
|
| 767 |
+
self.num_candidates = 4
|
| 768 |
+
self.min_time = 5.0
|
| 769 |
+
self.num_sec = 3.2
|
| 770 |
+
# self.num_sec = self.num_frames / float(self.fps) num_frames=16 / fps = 5
|
| 771 |
+
# self.num_frames = 16
|
| 772 |
+
|
| 773 |
+
def sampling(
|
| 774 |
+
self,
|
| 775 |
+
video_id,
|
| 776 |
+
video_feature,
|
| 777 |
+
text_feature,
|
| 778 |
+
centerclip_idx=None, # will be ignored.
|
| 779 |
+
sampled_max_text_len=None # will be ignored.
|
| 780 |
+
):
|
| 781 |
+
text, start, end = self._get_text(text_feature)
|
| 782 |
+
video = self._get_video(video_feature, start, end)
|
| 783 |
+
|
| 784 |
+
vfeats = torch.zeros((self.max_video_len, video_feature.shape[1]))
|
| 785 |
+
vmasks = torch.zeros((self.max_video_len,), dtype=torch.bool)
|
| 786 |
+
vfeats[: video.shape[0]] = torch.from_numpy(np.array(video))
|
| 787 |
+
vmasks[: video.shape[0]] = 1
|
| 788 |
+
|
| 789 |
+
caps, cmasks = [], []
|
| 790 |
+
for words in text:
|
| 791 |
+
cap, cmask = self._build_text_seq(text_feature, words)
|
| 792 |
+
caps.append(cap)
|
| 793 |
+
cmasks.append(cmask)
|
| 794 |
+
caps = torch.stack(caps)
|
| 795 |
+
cmasks = torch.stack(cmasks)
|
| 796 |
+
# video of shape: (video_len)
|
| 797 |
+
# text of shape (num_candidates, max_text_len)
|
| 798 |
+
|
| 799 |
+
return {
|
| 800 |
+
"caps": caps,
|
| 801 |
+
"cmasks": cmasks,
|
| 802 |
+
"vfeats": vfeats,
|
| 803 |
+
"vmasks": vmasks,
|
| 804 |
+
# "video_id": video_id,
|
| 805 |
+
}
|
| 806 |
+
|
| 807 |
+
def _get_video(self, video_feature, start, end):
|
| 808 |
+
start_seek = random.randint(start, int(max(start, end - self.num_sec)))
|
| 809 |
+
# duration = self.num_sec + 0.1
|
| 810 |
+
return video_feature[start_seek : int(start_seek + self.num_sec)]
|
| 811 |
+
|
| 812 |
+
def _get_text(self, cap):
|
| 813 |
+
ind = random.randint(0, len(cap["start"]) - 1)
|
| 814 |
+
if self.num_candidates == 1:
|
| 815 |
+
words = [ind]
|
| 816 |
+
else:
|
| 817 |
+
words = []
|
| 818 |
+
cap_start = self._find_nearest_candidates(cap, ind)
|
| 819 |
+
for i in range(self.num_candidates):
|
| 820 |
+
words.append([max(0, min(len(cap["cap"]) - 1, cap_start + i))])
|
| 821 |
+
|
| 822 |
+
start, end = cap["start"][ind], cap["end"][ind]
|
| 823 |
+
# TODO: May need to be improved for edge cases.
|
| 824 |
+
# expand the min time.
|
| 825 |
+
if end - start < self.min_time:
|
| 826 |
+
diff = self.min_time - end + start
|
| 827 |
+
start = max(0, start - diff / 2)
|
| 828 |
+
end = start + self.min_time
|
| 829 |
+
return words, int(start), int(end)
|
| 830 |
+
|
| 831 |
+
def _find_nearest_candidates(self, caption, ind):
|
| 832 |
+
"""find the range of the clips."""
|
| 833 |
+
start, end = ind, ind
|
| 834 |
+
#diff = caption["end"][end] - caption["start"][start]
|
| 835 |
+
n_candidate = 1
|
| 836 |
+
while n_candidate < self.num_candidates:
|
| 837 |
+
# the first clip
|
| 838 |
+
if start == 0:
|
| 839 |
+
return 0
|
| 840 |
+
# we add () in the following condition to fix the bug.
|
| 841 |
+
elif end == (len(caption["start"]) - 1):
|
| 842 |
+
return start - (self.num_candidates - n_candidate)
|
| 843 |
+
elif (caption["end"][end] - caption["start"][start - 1]) < (
|
| 844 |
+
caption["end"][end + 1] - caption["start"][start]
|
| 845 |
+
):
|
| 846 |
+
start -= 1
|
| 847 |
+
else:
|
| 848 |
+
end += 1
|
| 849 |
+
n_candidate += 1
|
| 850 |
+
return start
|
| 851 |
+
|
| 852 |
+
|
| 853 |
+
class PKLJSONStrTextProcessor(TextProcessor):
|
| 854 |
+
"""`caption.json` from howto100m are preprocessed as a
|
| 855 |
+
dict `[video_id, json_str]`.
|
| 856 |
+
Json parsing tokenization are conducted on-the-fly and cached into dict.
|
| 857 |
+
"""
|
| 858 |
+
|
| 859 |
+
def __init__(self, config, max_clip_text_len=96):
|
| 860 |
+
print("[Warning] PKLJSONStrTextProcessor is slow for num_workers > 0.")
|
| 861 |
+
self.caption_pkl_path = str(config.caption_pkl_path)
|
| 862 |
+
with open(self.caption_pkl_path, "rb") as fd:
|
| 863 |
+
self.data = pickle.load(fd)
|
| 864 |
+
self.max_clip_text_len = max_clip_text_len
|
| 865 |
+
from transformers import AutoTokenizer
|
| 866 |
+
self.tokenizer = AutoTokenizer.from_pretrained(
|
| 867 |
+
str(config.bert_name), use_fast=config.use_fast
|
| 868 |
+
)
|
| 869 |
+
|
| 870 |
+
def __call__(self, video_id):
|
| 871 |
+
caption = self.data[video_id]
|
| 872 |
+
if isinstance(caption, str):
|
| 873 |
+
import json
|
| 874 |
+
caption = json.loads(caption)
|
| 875 |
+
cap = []
|
| 876 |
+
for clip_idx, text_clip in enumerate(caption["text"]):
|
| 877 |
+
clip_ids = []
|
| 878 |
+
if isinstance(text_clip, str):
|
| 879 |
+
clip_ids = self.tokenizer(
|
| 880 |
+
text_clip[: self.max_clip_text_len],
|
| 881 |
+
add_special_tokens=False
|
| 882 |
+
)["input_ids"]
|
| 883 |
+
cap.append(clip_ids)
|
| 884 |
+
caption["cap"] = cap
|
| 885 |
+
caption.pop("text") # save space.
|
| 886 |
+
self.data[video_id] = caption
|
| 887 |
+
return caption
|
data/fairseq/examples/MMPT/mmpt/processors/how2retriprocessor.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
from .how2processor import (
|
| 7 |
+
ShardedHow2MetaProcessor,
|
| 8 |
+
ShardedVideoProcessor,
|
| 9 |
+
ShardedTextProcessor,
|
| 10 |
+
VariedLenAligner,
|
| 11 |
+
OverlappedAligner
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class ShardedHow2VideoRetriMetaProcessor(ShardedHow2MetaProcessor):
|
| 16 |
+
def __init__(self, config):
|
| 17 |
+
super().__init__(config)
|
| 18 |
+
self.num_video_per_batch = config.num_video_per_batch
|
| 19 |
+
self.cands = [
|
| 20 |
+
self.data[batch_offset:batch_offset + self.num_video_per_batch]
|
| 21 |
+
for batch_offset in
|
| 22 |
+
range(0, (len(self.data) // (8 * self.num_video_per_batch)) * 8 * self.num_video_per_batch, self.num_video_per_batch)]
|
| 23 |
+
|
| 24 |
+
def __len__(self):
|
| 25 |
+
return len(self.cands)
|
| 26 |
+
|
| 27 |
+
def set_candidates(self, cands):
|
| 28 |
+
# no changes on num of batches.
|
| 29 |
+
print(len(self.cands), "->", len(cands))
|
| 30 |
+
# assert len(self.cands) == len(cands)
|
| 31 |
+
self.cands = cands
|
| 32 |
+
|
| 33 |
+
def __getitem__(self, idx):
|
| 34 |
+
video_ids = self.cands[idx]
|
| 35 |
+
assert isinstance(video_ids, list)
|
| 36 |
+
sharded_video_idxs = []
|
| 37 |
+
for video_id in video_ids:
|
| 38 |
+
shard_id, video_idx = self.video_id_to_shard[video_id]
|
| 39 |
+
sharded_video_idxs.append((video_id, -1, shard_id, video_idx))
|
| 40 |
+
return sharded_video_idxs, sharded_video_idxs
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class ShardedVideoRetriVideoProcessor(ShardedVideoProcessor):
|
| 44 |
+
"""In retrival case the video_id
|
| 45 |
+
is a list of tuples: `(shard_id, video_idx)` ."""
|
| 46 |
+
|
| 47 |
+
def __call__(self, sharded_video_idxs):
|
| 48 |
+
assert isinstance(sharded_video_idxs, list)
|
| 49 |
+
cand_feats = []
|
| 50 |
+
for shared_video_idx in sharded_video_idxs:
|
| 51 |
+
feat = super().__call__(shared_video_idx)
|
| 52 |
+
cand_feats.append(feat)
|
| 53 |
+
return cand_feats
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class ShardedVideoRetriTextProcessor(ShardedTextProcessor):
|
| 57 |
+
"""In retrival case the video_id
|
| 58 |
+
is a list of tuples: `(shard_id, video_idx)` ."""
|
| 59 |
+
|
| 60 |
+
def __call__(self, sharded_video_idxs):
|
| 61 |
+
assert isinstance(sharded_video_idxs, list)
|
| 62 |
+
cand_caps = []
|
| 63 |
+
for shared_video_idx in sharded_video_idxs:
|
| 64 |
+
caps = super().__call__(shared_video_idx)
|
| 65 |
+
cand_caps.append(caps)
|
| 66 |
+
return cand_caps
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class VideoRetriAligner(VariedLenAligner):
|
| 70 |
+
# Retritask will trim dim-0.
|
| 71 |
+
def __call__(self, sharded_video_idxs, video_features, text_features):
|
| 72 |
+
from transformers import default_data_collator
|
| 73 |
+
batch, video_ids = [], []
|
| 74 |
+
for video_id, video_feature, text_feature in \
|
| 75 |
+
zip(sharded_video_idxs, video_features, text_features):
|
| 76 |
+
sub_batch = super().__call__(video_id, video_feature, text_feature)
|
| 77 |
+
batch.append(sub_batch)
|
| 78 |
+
if isinstance(video_id, tuple):
|
| 79 |
+
video_id = video_id[0]
|
| 80 |
+
video_ids.append(video_id)
|
| 81 |
+
batch = default_data_collator(batch)
|
| 82 |
+
batch["video_id"] = video_ids
|
| 83 |
+
return batch
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
class VideoRetriOverlappedAligner(OverlappedAligner):
|
| 87 |
+
# Retritask will trim dim-0.
|
| 88 |
+
def __call__(self, sharded_video_idxs, video_features, text_features):
|
| 89 |
+
from transformers import default_data_collator
|
| 90 |
+
batch, video_ids = [], []
|
| 91 |
+
for video_id, video_feature, text_feature in \
|
| 92 |
+
zip(sharded_video_idxs, video_features, text_features):
|
| 93 |
+
sub_batch = super().__call__(video_id, video_feature, text_feature)
|
| 94 |
+
batch.append(sub_batch)
|
| 95 |
+
if isinstance(video_id, tuple):
|
| 96 |
+
video_id = video_id[0]
|
| 97 |
+
video_ids.append(video_id)
|
| 98 |
+
batch = default_data_collator(batch)
|
| 99 |
+
batch["video_id"] = video_ids
|
| 100 |
+
return batch
|
data/fairseq/examples/MMPT/mmpt/processors/models/s3dg.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This source code is licensed under the MIT license found in the
|
| 2 |
+
# LICENSE file in the root directory of this source tree.
|
| 3 |
+
|
| 4 |
+
"""Contains a PyTorch definition for Gated Separable 3D network (S3D-G)
|
| 5 |
+
with a text module for computing joint text-video embedding from raw text
|
| 6 |
+
and video input. The following code will enable you to load the HowTo100M
|
| 7 |
+
pretrained S3D Text-Video model from:
|
| 8 |
+
A. Miech, J.-B. Alayrac, L. Smaira, I. Laptev, J. Sivic and A. Zisserman,
|
| 9 |
+
End-to-End Learning of Visual Representations from Uncurated Instructional Videos.
|
| 10 |
+
https://arxiv.org/abs/1912.06430.
|
| 11 |
+
|
| 12 |
+
S3D-G was proposed by:
|
| 13 |
+
S. Xie, C. Sun, J. Huang, Z. Tu and K. Murphy,
|
| 14 |
+
Rethinking Spatiotemporal Feature Learning For Video Understanding.
|
| 15 |
+
https://arxiv.org/abs/1712.04851.
|
| 16 |
+
Tensorflow code: https://github.com/tensorflow/models/blob/master/research/slim/nets/s3dg.py
|
| 17 |
+
|
| 18 |
+
The S3D architecture was slightly modified with a space to depth trick for TPU
|
| 19 |
+
optimization.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
import torch as th
|
| 23 |
+
import torch.nn.functional as F
|
| 24 |
+
import torch.nn as nn
|
| 25 |
+
import os
|
| 26 |
+
import numpy as np
|
| 27 |
+
import re
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class InceptionBlock(nn.Module):
|
| 31 |
+
def __init__(
|
| 32 |
+
self,
|
| 33 |
+
input_dim,
|
| 34 |
+
num_outputs_0_0a,
|
| 35 |
+
num_outputs_1_0a,
|
| 36 |
+
num_outputs_1_0b,
|
| 37 |
+
num_outputs_2_0a,
|
| 38 |
+
num_outputs_2_0b,
|
| 39 |
+
num_outputs_3_0b,
|
| 40 |
+
gating=True,
|
| 41 |
+
):
|
| 42 |
+
super(InceptionBlock, self).__init__()
|
| 43 |
+
self.conv_b0 = STConv3D(input_dim, num_outputs_0_0a, [1, 1, 1])
|
| 44 |
+
self.conv_b1_a = STConv3D(input_dim, num_outputs_1_0a, [1, 1, 1])
|
| 45 |
+
self.conv_b1_b = STConv3D(
|
| 46 |
+
num_outputs_1_0a, num_outputs_1_0b, [3, 3, 3], padding=1, separable=True
|
| 47 |
+
)
|
| 48 |
+
self.conv_b2_a = STConv3D(input_dim, num_outputs_2_0a, [1, 1, 1])
|
| 49 |
+
self.conv_b2_b = STConv3D(
|
| 50 |
+
num_outputs_2_0a, num_outputs_2_0b, [3, 3, 3], padding=1, separable=True
|
| 51 |
+
)
|
| 52 |
+
self.maxpool_b3 = th.nn.MaxPool3d((3, 3, 3), stride=1, padding=1)
|
| 53 |
+
self.conv_b3_b = STConv3D(input_dim, num_outputs_3_0b, [1, 1, 1])
|
| 54 |
+
self.gating = gating
|
| 55 |
+
self.output_dim = (
|
| 56 |
+
num_outputs_0_0a + num_outputs_1_0b + num_outputs_2_0b + num_outputs_3_0b
|
| 57 |
+
)
|
| 58 |
+
if gating:
|
| 59 |
+
self.gating_b0 = SelfGating(num_outputs_0_0a)
|
| 60 |
+
self.gating_b1 = SelfGating(num_outputs_1_0b)
|
| 61 |
+
self.gating_b2 = SelfGating(num_outputs_2_0b)
|
| 62 |
+
self.gating_b3 = SelfGating(num_outputs_3_0b)
|
| 63 |
+
|
| 64 |
+
def forward(self, input):
|
| 65 |
+
"""Inception block
|
| 66 |
+
"""
|
| 67 |
+
b0 = self.conv_b0(input)
|
| 68 |
+
b1 = self.conv_b1_a(input)
|
| 69 |
+
b1 = self.conv_b1_b(b1)
|
| 70 |
+
b2 = self.conv_b2_a(input)
|
| 71 |
+
b2 = self.conv_b2_b(b2)
|
| 72 |
+
b3 = self.maxpool_b3(input)
|
| 73 |
+
b3 = self.conv_b3_b(b3)
|
| 74 |
+
if self.gating:
|
| 75 |
+
b0 = self.gating_b0(b0)
|
| 76 |
+
b1 = self.gating_b1(b1)
|
| 77 |
+
b2 = self.gating_b2(b2)
|
| 78 |
+
b3 = self.gating_b3(b3)
|
| 79 |
+
return th.cat((b0, b1, b2, b3), dim=1)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class SelfGating(nn.Module):
|
| 83 |
+
def __init__(self, input_dim):
|
| 84 |
+
super(SelfGating, self).__init__()
|
| 85 |
+
self.fc = nn.Linear(input_dim, input_dim)
|
| 86 |
+
|
| 87 |
+
def forward(self, input_tensor):
|
| 88 |
+
"""Feature gating as used in S3D-G.
|
| 89 |
+
"""
|
| 90 |
+
spatiotemporal_average = th.mean(input_tensor, dim=[2, 3, 4])
|
| 91 |
+
weights = self.fc(spatiotemporal_average)
|
| 92 |
+
weights = th.sigmoid(weights)
|
| 93 |
+
return weights[:, :, None, None, None] * input_tensor
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class STConv3D(nn.Module):
|
| 97 |
+
def __init__(
|
| 98 |
+
self, input_dim, output_dim, kernel_size, stride=1, padding=0, separable=False
|
| 99 |
+
):
|
| 100 |
+
super(STConv3D, self).__init__()
|
| 101 |
+
self.separable = separable
|
| 102 |
+
self.relu = nn.ReLU(inplace=True)
|
| 103 |
+
assert len(kernel_size) == 3
|
| 104 |
+
if separable and kernel_size[0] != 1:
|
| 105 |
+
spatial_kernel_size = [1, kernel_size[1], kernel_size[2]]
|
| 106 |
+
temporal_kernel_size = [kernel_size[0], 1, 1]
|
| 107 |
+
if isinstance(stride, list) and len(stride) == 3:
|
| 108 |
+
spatial_stride = [1, stride[1], stride[2]]
|
| 109 |
+
temporal_stride = [stride[0], 1, 1]
|
| 110 |
+
else:
|
| 111 |
+
spatial_stride = [1, stride, stride]
|
| 112 |
+
temporal_stride = [stride, 1, 1]
|
| 113 |
+
if isinstance(padding, list) and len(padding) == 3:
|
| 114 |
+
spatial_padding = [0, padding[1], padding[2]]
|
| 115 |
+
temporal_padding = [padding[0], 0, 0]
|
| 116 |
+
else:
|
| 117 |
+
spatial_padding = [0, padding, padding]
|
| 118 |
+
temporal_padding = [padding, 0, 0]
|
| 119 |
+
if separable:
|
| 120 |
+
self.conv1 = nn.Conv3d(
|
| 121 |
+
input_dim,
|
| 122 |
+
output_dim,
|
| 123 |
+
kernel_size=spatial_kernel_size,
|
| 124 |
+
stride=spatial_stride,
|
| 125 |
+
padding=spatial_padding,
|
| 126 |
+
bias=False,
|
| 127 |
+
)
|
| 128 |
+
self.bn1 = nn.BatchNorm3d(output_dim)
|
| 129 |
+
self.conv2 = nn.Conv3d(
|
| 130 |
+
output_dim,
|
| 131 |
+
output_dim,
|
| 132 |
+
kernel_size=temporal_kernel_size,
|
| 133 |
+
stride=temporal_stride,
|
| 134 |
+
padding=temporal_padding,
|
| 135 |
+
bias=False,
|
| 136 |
+
)
|
| 137 |
+
self.bn2 = nn.BatchNorm3d(output_dim)
|
| 138 |
+
else:
|
| 139 |
+
self.conv1 = nn.Conv3d(
|
| 140 |
+
input_dim,
|
| 141 |
+
output_dim,
|
| 142 |
+
kernel_size=kernel_size,
|
| 143 |
+
stride=stride,
|
| 144 |
+
padding=padding,
|
| 145 |
+
bias=False,
|
| 146 |
+
)
|
| 147 |
+
self.bn1 = nn.BatchNorm3d(output_dim)
|
| 148 |
+
|
| 149 |
+
def forward(self, input):
|
| 150 |
+
out = self.relu(self.bn1(self.conv1(input)))
|
| 151 |
+
if self.separable:
|
| 152 |
+
out = self.relu(self.bn2(self.conv2(out)))
|
| 153 |
+
return out
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
class MaxPool3dTFPadding(th.nn.Module):
|
| 157 |
+
def __init__(self, kernel_size, stride=None, padding="SAME"):
|
| 158 |
+
super(MaxPool3dTFPadding, self).__init__()
|
| 159 |
+
if padding == "SAME":
|
| 160 |
+
padding_shape = self._get_padding_shape(kernel_size, stride)
|
| 161 |
+
self.padding_shape = padding_shape
|
| 162 |
+
self.pad = th.nn.ConstantPad3d(padding_shape, 0)
|
| 163 |
+
self.pool = th.nn.MaxPool3d(kernel_size, stride, ceil_mode=True)
|
| 164 |
+
|
| 165 |
+
def _get_padding_shape(self, filter_shape, stride):
|
| 166 |
+
def _pad_top_bottom(filter_dim, stride_val):
|
| 167 |
+
pad_along = max(filter_dim - stride_val, 0)
|
| 168 |
+
pad_top = pad_along // 2
|
| 169 |
+
pad_bottom = pad_along - pad_top
|
| 170 |
+
return pad_top, pad_bottom
|
| 171 |
+
|
| 172 |
+
padding_shape = []
|
| 173 |
+
for filter_dim, stride_val in zip(filter_shape, stride):
|
| 174 |
+
pad_top, pad_bottom = _pad_top_bottom(filter_dim, stride_val)
|
| 175 |
+
padding_shape.append(pad_top)
|
| 176 |
+
padding_shape.append(pad_bottom)
|
| 177 |
+
depth_top = padding_shape.pop(0)
|
| 178 |
+
depth_bottom = padding_shape.pop(0)
|
| 179 |
+
padding_shape.append(depth_top)
|
| 180 |
+
padding_shape.append(depth_bottom)
|
| 181 |
+
return tuple(padding_shape)
|
| 182 |
+
|
| 183 |
+
def forward(self, inp):
|
| 184 |
+
inp = self.pad(inp)
|
| 185 |
+
out = self.pool(inp)
|
| 186 |
+
return out
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
class Sentence_Embedding(nn.Module):
|
| 190 |
+
def __init__(
|
| 191 |
+
self,
|
| 192 |
+
embd_dim,
|
| 193 |
+
num_embeddings=66250,
|
| 194 |
+
word_embedding_dim=300,
|
| 195 |
+
token_to_word_path="dict.npy",
|
| 196 |
+
max_words=16,
|
| 197 |
+
output_dim=2048,
|
| 198 |
+
):
|
| 199 |
+
super(Sentence_Embedding, self).__init__()
|
| 200 |
+
self.word_embd = nn.Embedding(num_embeddings, word_embedding_dim)
|
| 201 |
+
self.fc1 = nn.Linear(word_embedding_dim, output_dim)
|
| 202 |
+
self.fc2 = nn.Linear(output_dim, embd_dim)
|
| 203 |
+
self.word_to_token = {}
|
| 204 |
+
self.max_words = max_words
|
| 205 |
+
token_to_word = np.load(token_to_word_path)
|
| 206 |
+
for i, t in enumerate(token_to_word):
|
| 207 |
+
self.word_to_token[t] = i + 1
|
| 208 |
+
|
| 209 |
+
def _zero_pad_tensor_token(self, tensor, size):
|
| 210 |
+
if len(tensor) >= size:
|
| 211 |
+
return tensor[:size]
|
| 212 |
+
else:
|
| 213 |
+
zero = th.zeros(size - len(tensor)).long()
|
| 214 |
+
return th.cat((tensor, zero), dim=0)
|
| 215 |
+
|
| 216 |
+
def _split_text(self, sentence):
|
| 217 |
+
w = re.findall(r"[\w']+", str(sentence))
|
| 218 |
+
return w
|
| 219 |
+
|
| 220 |
+
def _words_to_token(self, words):
|
| 221 |
+
words = [
|
| 222 |
+
self.word_to_token[word] for word in words if word in self.word_to_token
|
| 223 |
+
]
|
| 224 |
+
if words:
|
| 225 |
+
we = self._zero_pad_tensor_token(th.LongTensor(words), self.max_words)
|
| 226 |
+
return we
|
| 227 |
+
else:
|
| 228 |
+
return th.zeros(self.max_words).long()
|
| 229 |
+
|
| 230 |
+
def _words_to_ids(self, x):
|
| 231 |
+
split_x = [self._words_to_token(self._split_text(sent.lower())) for sent in x]
|
| 232 |
+
return th.stack(split_x, dim=0)
|
| 233 |
+
|
| 234 |
+
def forward(self, x):
|
| 235 |
+
x = self._words_to_ids(x)
|
| 236 |
+
x = self.word_embd(x)
|
| 237 |
+
x = F.relu(self.fc1(x))
|
| 238 |
+
x = th.max(x, dim=1)[0]
|
| 239 |
+
x = self.fc2(x)
|
| 240 |
+
return {'text_embedding': x}
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
class S3D(nn.Module):
|
| 244 |
+
def __init__(self, dict_path, num_classes=512, gating=True, space_to_depth=True):
|
| 245 |
+
super(S3D, self).__init__()
|
| 246 |
+
self.num_classes = num_classes
|
| 247 |
+
self.gating = gating
|
| 248 |
+
self.space_to_depth = space_to_depth
|
| 249 |
+
if space_to_depth:
|
| 250 |
+
self.conv1 = STConv3D(
|
| 251 |
+
24, 64, [2, 4, 4], stride=1, padding=(1, 2, 2), separable=False
|
| 252 |
+
)
|
| 253 |
+
else:
|
| 254 |
+
self.conv1 = STConv3D(
|
| 255 |
+
3, 64, [3, 7, 7], stride=2, padding=(1, 3, 3), separable=False
|
| 256 |
+
)
|
| 257 |
+
self.conv_2b = STConv3D(64, 64, [1, 1, 1], separable=False)
|
| 258 |
+
self.conv_2c = STConv3D(64, 192, [3, 3, 3], padding=1, separable=True)
|
| 259 |
+
self.gating = SelfGating(192)
|
| 260 |
+
self.maxpool_2a = MaxPool3dTFPadding(
|
| 261 |
+
kernel_size=(1, 3, 3), stride=(1, 2, 2), padding="SAME"
|
| 262 |
+
)
|
| 263 |
+
self.maxpool_3a = MaxPool3dTFPadding(
|
| 264 |
+
kernel_size=(1, 3, 3), stride=(1, 2, 2), padding="SAME"
|
| 265 |
+
)
|
| 266 |
+
self.mixed_3b = InceptionBlock(192, 64, 96, 128, 16, 32, 32)
|
| 267 |
+
self.mixed_3c = InceptionBlock(
|
| 268 |
+
self.mixed_3b.output_dim, 128, 128, 192, 32, 96, 64
|
| 269 |
+
)
|
| 270 |
+
self.maxpool_4a = MaxPool3dTFPadding(
|
| 271 |
+
kernel_size=(3, 3, 3), stride=(2, 2, 2), padding="SAME"
|
| 272 |
+
)
|
| 273 |
+
self.mixed_4b = InceptionBlock(
|
| 274 |
+
self.mixed_3c.output_dim, 192, 96, 208, 16, 48, 64
|
| 275 |
+
)
|
| 276 |
+
self.mixed_4c = InceptionBlock(
|
| 277 |
+
self.mixed_4b.output_dim, 160, 112, 224, 24, 64, 64
|
| 278 |
+
)
|
| 279 |
+
self.mixed_4d = InceptionBlock(
|
| 280 |
+
self.mixed_4c.output_dim, 128, 128, 256, 24, 64, 64
|
| 281 |
+
)
|
| 282 |
+
self.mixed_4e = InceptionBlock(
|
| 283 |
+
self.mixed_4d.output_dim, 112, 144, 288, 32, 64, 64
|
| 284 |
+
)
|
| 285 |
+
self.mixed_4f = InceptionBlock(
|
| 286 |
+
self.mixed_4e.output_dim, 256, 160, 320, 32, 128, 128
|
| 287 |
+
)
|
| 288 |
+
self.maxpool_5a = self.maxPool3d_5a_2x2 = MaxPool3dTFPadding(
|
| 289 |
+
kernel_size=(2, 2, 2), stride=(2, 2, 2), padding="SAME"
|
| 290 |
+
)
|
| 291 |
+
self.mixed_5b = InceptionBlock(
|
| 292 |
+
self.mixed_4f.output_dim, 256, 160, 320, 32, 128, 128
|
| 293 |
+
)
|
| 294 |
+
self.mixed_5c = InceptionBlock(
|
| 295 |
+
self.mixed_5b.output_dim, 384, 192, 384, 48, 128, 128
|
| 296 |
+
)
|
| 297 |
+
self.fc = nn.Linear(self.mixed_5c.output_dim, num_classes)
|
| 298 |
+
self.text_module = Sentence_Embedding(num_classes,
|
| 299 |
+
token_to_word_path=dict_path)
|
| 300 |
+
|
| 301 |
+
def _space_to_depth(self, input):
|
| 302 |
+
"""3D space to depth trick for TPU optimization.
|
| 303 |
+
"""
|
| 304 |
+
B, C, T, H, W = input.shape
|
| 305 |
+
input = input.view(B, C, T // 2, 2, H // 2, 2, W // 2, 2)
|
| 306 |
+
input = input.permute(0, 3, 5, 7, 1, 2, 4, 6)
|
| 307 |
+
input = input.contiguous().view(B, 8 * C, T // 2, H // 2, W // 2)
|
| 308 |
+
return input
|
| 309 |
+
|
| 310 |
+
def forward(self, inputs):
|
| 311 |
+
"""Defines the S3DG base architecture."""
|
| 312 |
+
if self.space_to_depth:
|
| 313 |
+
inputs = self._space_to_depth(inputs)
|
| 314 |
+
net = self.conv1(inputs)
|
| 315 |
+
if self.space_to_depth:
|
| 316 |
+
# we need to replicate 'SAME' tensorflow padding
|
| 317 |
+
net = net[:, :, 1:, 1:, 1:]
|
| 318 |
+
net = self.maxpool_2a(net)
|
| 319 |
+
net = self.conv_2b(net)
|
| 320 |
+
net = self.conv_2c(net)
|
| 321 |
+
if self.gating:
|
| 322 |
+
net = self.gating(net)
|
| 323 |
+
net = self.maxpool_3a(net)
|
| 324 |
+
net = self.mixed_3b(net)
|
| 325 |
+
net = self.mixed_3c(net)
|
| 326 |
+
net = self.maxpool_4a(net)
|
| 327 |
+
net = self.mixed_4b(net)
|
| 328 |
+
net = self.mixed_4c(net)
|
| 329 |
+
net = self.mixed_4d(net)
|
| 330 |
+
net = self.mixed_4e(net)
|
| 331 |
+
net = self.mixed_4f(net)
|
| 332 |
+
net = self.maxpool_5a(net)
|
| 333 |
+
net = self.mixed_5b(net)
|
| 334 |
+
net = self.mixed_5c(net)
|
| 335 |
+
net = th.mean(net, dim=[2, 3, 4])
|
| 336 |
+
return {'video_embedding': self.fc(net), 'mixed_5c': net}
|
data/fairseq/examples/MMPT/mmpt/processors/processor.py
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. All Rights Reserved
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import os
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class Processor(object):
|
| 9 |
+
"""
|
| 10 |
+
A generic processor for video (codec, feature etc.) and text.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
def __call__(self, **kwargs):
|
| 14 |
+
raise NotImplementedError
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class MetaProcessor(Processor):
|
| 18 |
+
"""
|
| 19 |
+
A meta processor is expected to load the metadata of a dataset:
|
| 20 |
+
(e.g., video_ids, or captions).
|
| 21 |
+
You must implement the `__getitem__` (meta datasets are rather diverse.).
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
def __init__(self, config):
|
| 25 |
+
self.split = config.split
|
| 26 |
+
|
| 27 |
+
def __len__(self):
|
| 28 |
+
return len(self.data)
|
| 29 |
+
|
| 30 |
+
def __getitem__(self, idx):
|
| 31 |
+
raise NotImplementedError
|
| 32 |
+
|
| 33 |
+
def _get_split_path(self, config):
|
| 34 |
+
splits = {
|
| 35 |
+
"train": config.train_path,
|
| 36 |
+
"valid": config.val_path,
|
| 37 |
+
"test": config.test_path,
|
| 38 |
+
}
|
| 39 |
+
if config.split is not None:
|
| 40 |
+
return splits[config.split]
|
| 41 |
+
return config.train_path
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class TextProcessor(Processor):
|
| 45 |
+
"""
|
| 46 |
+
A generic Text processor: rename this as `withTokenizer`.
|
| 47 |
+
tokenize a string of text on-the-fly.
|
| 48 |
+
Warning: mostly used for end tasks.
|
| 49 |
+
(on-the-fly tokenization is slow for how2.)
|
| 50 |
+
TODO(huxu): move this class as a subclass.
|
| 51 |
+
"""
|
| 52 |
+
|
| 53 |
+
def __init__(self, config):
|
| 54 |
+
self.bert_name = str(config.bert_name)
|
| 55 |
+
self.use_fast = config.use_fast
|
| 56 |
+
from transformers import AutoTokenizer
|
| 57 |
+
self.tokenizer = AutoTokenizer.from_pretrained(
|
| 58 |
+
self.bert_name, use_fast=self.use_fast
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
def __call__(self, text_id):
|
| 62 |
+
caption = self.tokenizer(text_id, add_special_tokens=False)
|
| 63 |
+
return caption["input_ids"]
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class VideoProcessor(Processor):
|
| 67 |
+
"""
|
| 68 |
+
A generic video processor: load a numpy video tokens by default.
|
| 69 |
+
"""
|
| 70 |
+
|
| 71 |
+
def __init__(self, config):
|
| 72 |
+
self.vfeat_dir = config.vfeat_dir
|
| 73 |
+
|
| 74 |
+
def __call__(self, video_fn):
|
| 75 |
+
if isinstance(video_fn, tuple):
|
| 76 |
+
video_fn = video_fn[0]
|
| 77 |
+
assert isinstance(video_fn, str)
|
| 78 |
+
video_fn = os.path.join(self.vfeat_dir, video_fn + ".npy")
|
| 79 |
+
feat = np.load(video_fn)
|
| 80 |
+
return feat
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
class Aligner(object):
|
| 84 |
+
"""
|
| 85 |
+
An alignprocessor align video and text and output a dict of tensors (for a model).
|
| 86 |
+
"""
|
| 87 |
+
def __init__(self, config):
|
| 88 |
+
"""__init__ needs to be light weight for more workers/threads."""
|
| 89 |
+
self.split = config.split
|
| 90 |
+
self.max_video_len = config.max_video_len
|
| 91 |
+
self.max_len = config.max_len
|
| 92 |
+
from transformers import AutoTokenizer
|
| 93 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 94 |
+
str(config.bert_name), use_fast=config.use_fast
|
| 95 |
+
)
|
| 96 |
+
self.cls_token_id = tokenizer.cls_token_id
|
| 97 |
+
self.sep_token_id = tokenizer.sep_token_id
|
| 98 |
+
self.pad_token_id = tokenizer.pad_token_id
|
| 99 |
+
self.mask_token_id = tokenizer.mask_token_id
|
| 100 |
+
|
| 101 |
+
def __call__(self, video_id, video_feature, text_feature):
|
| 102 |
+
raise NotImplementedError
|
| 103 |
+
|
| 104 |
+
def _build_video_seq(self, video_feature, video_clips=None):
|
| 105 |
+
"""
|
| 106 |
+
`video_feature`: available video tokens.
|
| 107 |
+
`video_clips`: video clip sequence to build.
|
| 108 |
+
"""
|
| 109 |
+
if not isinstance(video_feature, np.ndarray):
|
| 110 |
+
raise ValueError(
|
| 111 |
+
"unsupported type of video_feature", type(video_feature)
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
if video_clips is None:
|
| 115 |
+
# this is borrowed from DSAligner
|
| 116 |
+
video_start = 0
|
| 117 |
+
video_end = min(len(video_feature), self.max_video_len)
|
| 118 |
+
# the whole sequence is a single clip.
|
| 119 |
+
video_clips = {"start": [video_start], "end": [video_end]}
|
| 120 |
+
|
| 121 |
+
vfeats = np.zeros(
|
| 122 |
+
(self.max_video_len, video_feature.shape[1]), dtype=np.float32
|
| 123 |
+
)
|
| 124 |
+
vmasks = torch.zeros((self.max_video_len,), dtype=torch.bool)
|
| 125 |
+
video_len = 0
|
| 126 |
+
for start, end in zip(video_clips["start"], video_clips["end"]):
|
| 127 |
+
clip_len = min(self.max_video_len - video_len, (end - start))
|
| 128 |
+
if clip_len > 0:
|
| 129 |
+
vfeats[video_len: video_len + clip_len] = video_feature[
|
| 130 |
+
start: start + clip_len
|
| 131 |
+
]
|
| 132 |
+
vmasks[video_len: video_len + clip_len] = 1
|
| 133 |
+
video_len += clip_len
|
| 134 |
+
vfeats = torch.from_numpy(vfeats)
|
| 135 |
+
|
| 136 |
+
return vfeats, vmasks
|
| 137 |
+
|
| 138 |
+
def _build_text_seq(self, text_feature, text_clip_indexs=None):
|
| 139 |
+
"""
|
| 140 |
+
`text_feature`: all available clips.
|
| 141 |
+
`text_clip_indexes`: clip sequence to build.
|
| 142 |
+
"""
|
| 143 |
+
if text_clip_indexs is None:
|
| 144 |
+
text_clip_indexs = [0]
|
| 145 |
+
|
| 146 |
+
full_caps = []
|
| 147 |
+
if isinstance(text_feature, dict):
|
| 148 |
+
for clip_idx in text_clip_indexs:
|
| 149 |
+
full_caps.extend(text_feature["cap"][clip_idx])
|
| 150 |
+
else:
|
| 151 |
+
full_caps = text_feature
|
| 152 |
+
max_text_len = self.max_len - self.max_video_len - 3
|
| 153 |
+
full_caps = full_caps[:max_text_len]
|
| 154 |
+
full_caps = (
|
| 155 |
+
[self.cls_token_id, self.sep_token_id] + full_caps + [self.sep_token_id]
|
| 156 |
+
)
|
| 157 |
+
text_pad_len = self.max_len - len(full_caps) - self.max_video_len
|
| 158 |
+
padded_full_caps = full_caps + [self.pad_token_id] * text_pad_len
|
| 159 |
+
caps = torch.LongTensor(padded_full_caps)
|
| 160 |
+
cmasks = torch.zeros((len(padded_full_caps),), dtype=torch.bool)
|
| 161 |
+
cmasks[: len(full_caps)] = 1
|
| 162 |
+
|
| 163 |
+
return caps, cmasks
|
| 164 |
+
|
| 165 |
+
def batch_post_processing(self, batch, video_feature):
|
| 166 |
+
return batch
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
class MMAttentionMask2DProcessor(Processor):
|
| 170 |
+
"""text generation requires 2d mask
|
| 171 |
+
that is harder to generate by GPU at this stage."""
|
| 172 |
+
|
| 173 |
+
def __call__(self, vmask, cmask, mtype):
|
| 174 |
+
if mtype == "textgen":
|
| 175 |
+
return self._build_textgeneration_mask(vmask, cmask)
|
| 176 |
+
elif mtype == "videogen":
|
| 177 |
+
return self._build_videogeneration_mask(vmask, cmask)
|
| 178 |
+
else:
|
| 179 |
+
return self._build_mm_mask(vmask, cmask)
|
| 180 |
+
|
| 181 |
+
def _build_mm_mask(self, vmask, cmask):
|
| 182 |
+
mask_1d = torch.cat([cmask[:1], vmask, cmask[1:]], dim=0)
|
| 183 |
+
return mask_1d[None, :].repeat(mask_1d.size(0), 1)
|
| 184 |
+
|
| 185 |
+
def _build_videogeneration_mask(self, vmask, cmask):
|
| 186 |
+
# cls_mask is only about text otherwise it will leak generation.
|
| 187 |
+
cls_text_mask = torch.cat([
|
| 188 |
+
# [CLS]
|
| 189 |
+
torch.ones(
|
| 190 |
+
(1,), dtype=torch.bool, device=cmask.device),
|
| 191 |
+
# video tokens and [SEP] for video.
|
| 192 |
+
torch.zeros(
|
| 193 |
+
(vmask.size(0) + 1,), dtype=torch.bool, device=cmask.device),
|
| 194 |
+
cmask[2:]
|
| 195 |
+
], dim=0)
|
| 196 |
+
|
| 197 |
+
# concat horizontially.
|
| 198 |
+
video_len = int(vmask.sum())
|
| 199 |
+
video_masks = torch.cat([
|
| 200 |
+
# [CLS]
|
| 201 |
+
torch.ones(
|
| 202 |
+
(video_len, 1), dtype=torch.bool, device=cmask.device
|
| 203 |
+
),
|
| 204 |
+
torch.tril(
|
| 205 |
+
torch.ones(
|
| 206 |
+
(video_len, video_len),
|
| 207 |
+
dtype=torch.bool, device=cmask.device)),
|
| 208 |
+
# video_padding
|
| 209 |
+
torch.zeros(
|
| 210 |
+
(video_len, vmask.size(0) - video_len),
|
| 211 |
+
dtype=torch.bool, device=cmask.device
|
| 212 |
+
),
|
| 213 |
+
# [SEP] for video (unused).
|
| 214 |
+
torch.zeros(
|
| 215 |
+
(video_len, 1), dtype=torch.bool, device=cmask.device
|
| 216 |
+
),
|
| 217 |
+
cmask[2:].unsqueeze(0).repeat(video_len, 1)
|
| 218 |
+
], dim=1)
|
| 219 |
+
|
| 220 |
+
text_masks = cls_text_mask[None, :].repeat(
|
| 221 |
+
cmask.size(0) - 2, 1)
|
| 222 |
+
video_padding_masks = cls_text_mask[None, :].repeat(
|
| 223 |
+
vmask.size(0) - video_len, 1)
|
| 224 |
+
|
| 225 |
+
return torch.cat([
|
| 226 |
+
cls_text_mask[None, :],
|
| 227 |
+
video_masks,
|
| 228 |
+
video_padding_masks,
|
| 229 |
+
torch.cat([cmask[:1], vmask, cmask[1:]], dim=0)[None,:],
|
| 230 |
+
text_masks
|
| 231 |
+
], dim=0)
|
| 232 |
+
|
| 233 |
+
def _build_textgeneration_mask(self, vmask, cmask):
|
| 234 |
+
# cls_mask is only about video otherwise it will leak generation.
|
| 235 |
+
cls_video_mask = torch.cat([
|
| 236 |
+
# [CLS]
|
| 237 |
+
torch.ones(
|
| 238 |
+
(1,), dtype=torch.bool, device=cmask.device),
|
| 239 |
+
vmask,
|
| 240 |
+
# [SEP]
|
| 241 |
+
torch.ones((1,), dtype=torch.bool, device=cmask.device),
|
| 242 |
+
torch.zeros(
|
| 243 |
+
(cmask.size(0)-2,), dtype=torch.bool, device=cmask.device)
|
| 244 |
+
], dim=0)
|
| 245 |
+
|
| 246 |
+
# concat horizontially.
|
| 247 |
+
text_len = int(cmask[2:].sum())
|
| 248 |
+
text_masks = torch.cat([
|
| 249 |
+
# [CLS]
|
| 250 |
+
torch.ones(
|
| 251 |
+
(text_len, 1), dtype=torch.bool, device=cmask.device
|
| 252 |
+
),
|
| 253 |
+
vmask.unsqueeze(0).repeat(text_len, 1),
|
| 254 |
+
# [SEP] for video.
|
| 255 |
+
torch.ones(
|
| 256 |
+
(text_len, 1), dtype=torch.bool, device=cmask.device
|
| 257 |
+
),
|
| 258 |
+
torch.tril(
|
| 259 |
+
torch.ones(
|
| 260 |
+
(text_len, text_len),
|
| 261 |
+
dtype=torch.bool, device=cmask.device)),
|
| 262 |
+
# padding.
|
| 263 |
+
torch.zeros(
|
| 264 |
+
(text_len, cmask.size(0) - text_len - 2),
|
| 265 |
+
dtype=torch.bool, device=cmask.device
|
| 266 |
+
)
|
| 267 |
+
], dim=1)
|
| 268 |
+
|
| 269 |
+
cls_video_masks = cls_video_mask[None, :].repeat(
|
| 270 |
+
vmask.size(0) + 2, 1)
|
| 271 |
+
text_padding_masks = cls_video_mask[None, :].repeat(
|
| 272 |
+
cmask.size(0) - text_len - 2, 1)
|
| 273 |
+
return torch.cat([
|
| 274 |
+
cls_video_masks, text_masks, text_padding_masks], dim=0)
|
data/fairseq/examples/MMPT/mmpt/tasks/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
from .task import *
|
| 6 |
+
from .vlmtask import *
|
| 7 |
+
from .retritask import *
|
| 8 |
+
|
| 9 |
+
try:
|
| 10 |
+
from .fairseqmmtask import *
|
| 11 |
+
except ImportError:
|
| 12 |
+
pass
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
from .milncetask import *
|
| 16 |
+
except ImportError:
|
| 17 |
+
pass
|
| 18 |
+
|
| 19 |
+
try:
|
| 20 |
+
from .expretritask import *
|
| 21 |
+
except ImportError:
|
| 22 |
+
pass
|
data/fairseq/examples/MMPT/mmpt/tasks/fairseqmmtask.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
"""
|
| 6 |
+
make a general fairseq task for MM pretraining.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import random
|
| 10 |
+
|
| 11 |
+
from fairseq.tasks import LegacyFairseqTask, register_task
|
| 12 |
+
|
| 13 |
+
from .task import Task
|
| 14 |
+
from .retritask import RetriTask
|
| 15 |
+
from ..datasets import FairseqMMDataset
|
| 16 |
+
from .. import utils
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@register_task("mmtask")
|
| 20 |
+
class FairseqMMTask(LegacyFairseqTask):
|
| 21 |
+
@staticmethod
|
| 22 |
+
def add_args(parser):
|
| 23 |
+
# Add some command-line arguments for specifying where the data is
|
| 24 |
+
# located and the maximum supported input length.
|
| 25 |
+
parser.add_argument(
|
| 26 |
+
"taskconfig",
|
| 27 |
+
metavar="FILE",
|
| 28 |
+
help=("taskconfig to load all configurations" "outside fairseq parser."),
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
@classmethod
|
| 32 |
+
def setup_task(cls, args, **kwargs):
|
| 33 |
+
return FairseqMMTask(args)
|
| 34 |
+
|
| 35 |
+
def __init__(self, args):
|
| 36 |
+
super().__init__(args)
|
| 37 |
+
config = utils.load_config(args)
|
| 38 |
+
self.mmtask = Task.config_task(config)
|
| 39 |
+
self.mmtask.build_dataset()
|
| 40 |
+
self.mmtask.build_model()
|
| 41 |
+
self.mmtask.build_loss()
|
| 42 |
+
|
| 43 |
+
def load_dataset(self, split, **kwargs):
|
| 44 |
+
split_map = {
|
| 45 |
+
"train": self.mmtask.train_data,
|
| 46 |
+
"valid": self.mmtask.val_data,
|
| 47 |
+
"test": self.mmtask.test_data,
|
| 48 |
+
}
|
| 49 |
+
if split not in split_map:
|
| 50 |
+
raise ValueError("unknown split type.")
|
| 51 |
+
if split_map[split] is not None:
|
| 52 |
+
self.datasets[split] = FairseqMMDataset(split_map[split])
|
| 53 |
+
|
| 54 |
+
def get_batch_iterator(
|
| 55 |
+
self,
|
| 56 |
+
dataset,
|
| 57 |
+
max_tokens=None,
|
| 58 |
+
max_sentences=None,
|
| 59 |
+
max_positions=None,
|
| 60 |
+
ignore_invalid_inputs=False,
|
| 61 |
+
required_batch_size_multiple=1,
|
| 62 |
+
seed=1,
|
| 63 |
+
num_shards=1,
|
| 64 |
+
shard_id=0,
|
| 65 |
+
num_workers=0,
|
| 66 |
+
epoch=1,
|
| 67 |
+
data_buffer_size=0,
|
| 68 |
+
disable_iterator_cache=False,
|
| 69 |
+
skip_remainder_batch=False,
|
| 70 |
+
grouped_shuffling=False,
|
| 71 |
+
update_epoch_batch_itr=False,
|
| 72 |
+
):
|
| 73 |
+
random.seed(epoch)
|
| 74 |
+
if dataset.mmdataset.split == "train" and isinstance(self.mmtask, RetriTask):
|
| 75 |
+
if epoch >= self.mmtask.config.retri_epoch:
|
| 76 |
+
if not hasattr(self.mmtask, "retri_dataloader"):
|
| 77 |
+
self.mmtask.build_dataloader()
|
| 78 |
+
self.mmtask.retrive_candidates(epoch)
|
| 79 |
+
|
| 80 |
+
return super().get_batch_iterator(
|
| 81 |
+
dataset,
|
| 82 |
+
max_tokens,
|
| 83 |
+
max_sentences,
|
| 84 |
+
max_positions,
|
| 85 |
+
ignore_invalid_inputs,
|
| 86 |
+
required_batch_size_multiple,
|
| 87 |
+
seed,
|
| 88 |
+
num_shards,
|
| 89 |
+
shard_id,
|
| 90 |
+
num_workers,
|
| 91 |
+
epoch,
|
| 92 |
+
data_buffer_size,
|
| 93 |
+
disable_iterator_cache,
|
| 94 |
+
grouped_shuffling,
|
| 95 |
+
update_epoch_batch_itr,
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
@property
|
| 99 |
+
def source_dictionary(self):
|
| 100 |
+
return None
|
| 101 |
+
|
| 102 |
+
@property
|
| 103 |
+
def target_dictionary(self):
|
| 104 |
+
return None
|
data/fairseq/examples/MMPT/mmpt/tasks/milncetask.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
from .task import Task
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class MILNCETask(Task):
|
| 12 |
+
def reshape_subsample(self, sample):
|
| 13 |
+
if (
|
| 14 |
+
hasattr(self.config.dataset, "subsampling")
|
| 15 |
+
and self.config.dataset.subsampling is not None
|
| 16 |
+
and self.config.dataset.subsampling > 1
|
| 17 |
+
):
|
| 18 |
+
for key in sample:
|
| 19 |
+
if torch.is_tensor(sample[key]):
|
| 20 |
+
tensor = self.flat_subsample(sample[key])
|
| 21 |
+
if key in ["caps", "cmasks"]:
|
| 22 |
+
size = tensor.size()
|
| 23 |
+
batch_size = size[0] * size[1]
|
| 24 |
+
expanded_size = (batch_size,) + size[2:]
|
| 25 |
+
tensor = tensor.view(expanded_size)
|
| 26 |
+
sample[key] = tensor
|
| 27 |
+
return sample
|
data/fairseq/examples/MMPT/mmpt/tasks/retritask.py
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
import os
|
| 6 |
+
import torch
|
| 7 |
+
import pickle
|
| 8 |
+
import random
|
| 9 |
+
|
| 10 |
+
from tqdm import tqdm
|
| 11 |
+
from torch.utils.data import DataLoader
|
| 12 |
+
from torch.utils.data.distributed import DistributedSampler
|
| 13 |
+
|
| 14 |
+
from ..processors import (
|
| 15 |
+
ShardedHow2MetaProcessor,
|
| 16 |
+
ShardedVideoProcessor,
|
| 17 |
+
ShardedTextProcessor,
|
| 18 |
+
VariedLenAligner,
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
from ..datasets import MMDataset
|
| 22 |
+
from .task import Task
|
| 23 |
+
from ..modules import vectorpool
|
| 24 |
+
from ..evaluators.predictor import Predictor
|
| 25 |
+
from ..utils import set_seed, get_local_rank, get_world_size
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class RetriTask(Task):
|
| 29 |
+
"""abstract class for task with retrival."""
|
| 30 |
+
|
| 31 |
+
def reshape_subsample(self, sample):
|
| 32 |
+
for key in sample:
|
| 33 |
+
if torch.is_tensor(sample[key]):
|
| 34 |
+
sample[key] = self.flat_subsample(sample[key])
|
| 35 |
+
return sample
|
| 36 |
+
|
| 37 |
+
def flat_subsample(self, tensor):
|
| 38 |
+
if tensor.size(0) == 1:
|
| 39 |
+
tensor = tensor.squeeze(0)
|
| 40 |
+
return tensor
|
| 41 |
+
|
| 42 |
+
def build_dataloader(self):
|
| 43 |
+
"""called by `get_batch_iterator` in fairseqmmtask. """
|
| 44 |
+
# TODO: hard-code dataloader for retri for now and configurable in .yaml.
|
| 45 |
+
# reuse the `train.lst`.
|
| 46 |
+
self.config.dataset.split = "train"
|
| 47 |
+
meta_processor = ShardedHow2MetaProcessor(self.config.dataset)
|
| 48 |
+
video_processor = ShardedVideoProcessor(self.config.dataset)
|
| 49 |
+
text_processor = ShardedTextProcessor(self.config.dataset)
|
| 50 |
+
|
| 51 |
+
aligner = VariedLenAligner(self.config.dataset)
|
| 52 |
+
aligner.subsampling = self.config.dataset.clip_per_video
|
| 53 |
+
|
| 54 |
+
self.retri_data = MMDataset(
|
| 55 |
+
meta_processor, video_processor, text_processor, aligner
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
retri_sampler = DistributedSampler(self.retri_data)
|
| 59 |
+
infer_scale = 16
|
| 60 |
+
batch_size = self.config.dataset.num_video_per_batch \
|
| 61 |
+
* infer_scale
|
| 62 |
+
|
| 63 |
+
self.retri_dataloader = DataLoader(
|
| 64 |
+
self.retri_data,
|
| 65 |
+
collate_fn=self.retri_data.collater,
|
| 66 |
+
batch_size=batch_size,
|
| 67 |
+
shuffle=False,
|
| 68 |
+
sampler=retri_sampler,
|
| 69 |
+
num_workers=self.config.fairseq.dataset.num_workers
|
| 70 |
+
)
|
| 71 |
+
return self.retri_dataloader
|
| 72 |
+
|
| 73 |
+
def retrive_candidates(self, epoch, dataloader=None):
|
| 74 |
+
if get_local_rank() == 0:
|
| 75 |
+
print("running retrieval model.")
|
| 76 |
+
out_dir = os.path.join(
|
| 77 |
+
self.config.fairseq.checkpoint.save_dir, "retri")
|
| 78 |
+
os.makedirs(out_dir, exist_ok=True)
|
| 79 |
+
|
| 80 |
+
if not os.path.isfile(
|
| 81 |
+
os.path.join(
|
| 82 |
+
out_dir, "batched_e" + str(epoch) + "_videos0.pkl")
|
| 83 |
+
):
|
| 84 |
+
if dataloader is None:
|
| 85 |
+
dataloader = self.retri_dataloader
|
| 86 |
+
|
| 87 |
+
self.model.eval()
|
| 88 |
+
self.model.is_train = False
|
| 89 |
+
|
| 90 |
+
assert self.retri_data.meta_processor.data == \
|
| 91 |
+
self.train_data.meta_processor.data # video_ids not mutated.
|
| 92 |
+
|
| 93 |
+
self._retri_predict(epoch, dataloader)
|
| 94 |
+
|
| 95 |
+
self.model.train()
|
| 96 |
+
self.model.is_train = True
|
| 97 |
+
|
| 98 |
+
torch.distributed.barrier()
|
| 99 |
+
output = self._retri_sync(epoch, out_dir)
|
| 100 |
+
torch.distributed.barrier()
|
| 101 |
+
self.train_data.meta_processor.set_candidates(output)
|
| 102 |
+
return output
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
class VideoRetriTask(RetriTask):
|
| 106 |
+
"""RetriTask on video level."""
|
| 107 |
+
|
| 108 |
+
def reshape_subsample(self, sample):
|
| 109 |
+
if (
|
| 110 |
+
hasattr(self.config.dataset, "clip_per_video")
|
| 111 |
+
and self.config.dataset.clip_per_video is not None
|
| 112 |
+
and self.config.dataset.clip_per_video > 1
|
| 113 |
+
):
|
| 114 |
+
for key in sample:
|
| 115 |
+
if torch.is_tensor(sample[key]):
|
| 116 |
+
sample[key] = self.flat_subsample(sample[key])
|
| 117 |
+
return sample
|
| 118 |
+
|
| 119 |
+
def flat_subsample(self, tensor):
|
| 120 |
+
if tensor.size(0) == 1:
|
| 121 |
+
tensor = tensor.squeeze(0)
|
| 122 |
+
return Task.flat_subsample(self, tensor)
|
| 123 |
+
|
| 124 |
+
def _retri_predict(self, epoch, dataloader):
|
| 125 |
+
set_seed(epoch)
|
| 126 |
+
# save for retrival.
|
| 127 |
+
predictor = VideoPredictor(self.config)
|
| 128 |
+
predictor.predict_loop(
|
| 129 |
+
self.model, dataloader)
|
| 130 |
+
set_seed(epoch) # get the same text clips.
|
| 131 |
+
# retrival.
|
| 132 |
+
retri_predictor = VideoRetriPredictor(
|
| 133 |
+
self.config)
|
| 134 |
+
retri_predictor.predict_loop(
|
| 135 |
+
self.model, predictor.vecpool.retriver, epoch)
|
| 136 |
+
del predictor
|
| 137 |
+
del retri_predictor
|
| 138 |
+
|
| 139 |
+
def _retri_sync(self, epoch, out_dir):
|
| 140 |
+
# gpu do the same merge.
|
| 141 |
+
batched_videos = []
|
| 142 |
+
for local_rank in range(get_world_size()):
|
| 143 |
+
fn = os.path.join(
|
| 144 |
+
out_dir,
|
| 145 |
+
"batched_e" + str(epoch) + "_videos" + str(local_rank) + ".pkl")
|
| 146 |
+
with open(fn, "rb") as fr:
|
| 147 |
+
batched_videos.extend(pickle.load(fr))
|
| 148 |
+
print(
|
| 149 |
+
"[INFO] batched_videos",
|
| 150 |
+
len(batched_videos), len(batched_videos[0]))
|
| 151 |
+
return batched_videos
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
class VideoPredictor(Predictor):
|
| 155 |
+
def __init__(self, config):
|
| 156 |
+
vectorpool_cls = getattr(vectorpool, config.vectorpool_cls)
|
| 157 |
+
self.vecpool = vectorpool_cls(config)
|
| 158 |
+
|
| 159 |
+
def predict_loop(
|
| 160 |
+
self,
|
| 161 |
+
model,
|
| 162 |
+
dataloader,
|
| 163 |
+
early_stop=-1,
|
| 164 |
+
):
|
| 165 |
+
with torch.no_grad():
|
| 166 |
+
if get_local_rank() == 0:
|
| 167 |
+
dataloader = tqdm(dataloader)
|
| 168 |
+
for batch_idx, batch in enumerate(dataloader):
|
| 169 |
+
if batch_idx == early_stop:
|
| 170 |
+
break
|
| 171 |
+
self(batch, model)
|
| 172 |
+
return self.finalize()
|
| 173 |
+
|
| 174 |
+
def __call__(self, sample, model, **kwargs):
|
| 175 |
+
param = next(model.parameters())
|
| 176 |
+
dtype = param.dtype
|
| 177 |
+
device = param.device
|
| 178 |
+
subsample = sample["vfeats"].size(1)
|
| 179 |
+
sample = self.to_ctx(sample, device, dtype)
|
| 180 |
+
for key in sample:
|
| 181 |
+
if torch.is_tensor(sample[key]):
|
| 182 |
+
size = sample[key].size()
|
| 183 |
+
if len(size) >= 2:
|
| 184 |
+
batch_size = size[0] * size[1]
|
| 185 |
+
expanded_size = (
|
| 186 |
+
(batch_size,) + size[2:] if len(size) > 2
|
| 187 |
+
else (batch_size,)
|
| 188 |
+
)
|
| 189 |
+
sample[key] = sample[key].view(expanded_size)
|
| 190 |
+
|
| 191 |
+
outputs = model(**sample)
|
| 192 |
+
sample.update(outputs)
|
| 193 |
+
self.vecpool(sample, subsample)
|
| 194 |
+
|
| 195 |
+
def finalize(self):
|
| 196 |
+
print("[INFO]", self.vecpool)
|
| 197 |
+
if not self.vecpool.retriver.db.is_trained:
|
| 198 |
+
self.vecpool.retriver.finalize_training()
|
| 199 |
+
return self.vecpool.retriver
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
class VideoRetriPredictor(Predictor):
|
| 203 |
+
"""
|
| 204 |
+
Online Retrieval Predictor for Clips (used by RetriTask).
|
| 205 |
+
TODO: merge this with VisPredictor?
|
| 206 |
+
"""
|
| 207 |
+
|
| 208 |
+
def __init__(self, config):
|
| 209 |
+
self.pred_dir = os.path.join(
|
| 210 |
+
config.fairseq.checkpoint.save_dir,
|
| 211 |
+
"retri")
|
| 212 |
+
self.num_cands = config.num_cands
|
| 213 |
+
self.num_video_per_batch = config.dataset.num_video_per_batch
|
| 214 |
+
|
| 215 |
+
def predict_loop(
|
| 216 |
+
self,
|
| 217 |
+
model,
|
| 218 |
+
retriver,
|
| 219 |
+
epoch,
|
| 220 |
+
early_stop=-1
|
| 221 |
+
):
|
| 222 |
+
# a fake loop that only try to recover video vector
|
| 223 |
+
# from video_id.
|
| 224 |
+
batched_videos = []
|
| 225 |
+
# obtain available video_ids.
|
| 226 |
+
video_ids = list(retriver.videoid_to_vectoridx.keys())
|
| 227 |
+
|
| 228 |
+
dataloader = random.sample(
|
| 229 |
+
video_ids,
|
| 230 |
+
len(video_ids) // self.num_video_per_batch
|
| 231 |
+
)
|
| 232 |
+
|
| 233 |
+
if get_local_rank() == 0:
|
| 234 |
+
dataloader = tqdm(dataloader)
|
| 235 |
+
for batch_idx, batch in enumerate(dataloader):
|
| 236 |
+
# batch is one video id.
|
| 237 |
+
if batch_idx == early_stop:
|
| 238 |
+
break
|
| 239 |
+
video_ids = retriver.search_by_video_ids(
|
| 240 |
+
[batch], self.num_cands)[0]
|
| 241 |
+
if len(video_ids) > self.num_video_per_batch:
|
| 242 |
+
# we moved the center to make cluster robust.
|
| 243 |
+
video_ids = random.sample(video_ids, self.num_video_per_batch)
|
| 244 |
+
batched_videos.append(video_ids)
|
| 245 |
+
return self.finalize(batched_videos, epoch)
|
| 246 |
+
|
| 247 |
+
def finalize(self, batched_videos, epoch):
|
| 248 |
+
fn = os.path.join(
|
| 249 |
+
self.pred_dir,
|
| 250 |
+
"batched_e" + str(epoch) + "_videos" + str(get_local_rank()) + ".pkl")
|
| 251 |
+
with open(fn, "wb") as fw:
|
| 252 |
+
pickle.dump(batched_videos, fw, pickle.HIGHEST_PROTOCOL)
|
| 253 |
+
return batched_videos
|
data/fairseq/examples/MMPT/mmpt/tasks/task.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
from .. import tasks
|
| 8 |
+
from .. import models
|
| 9 |
+
from .. import losses
|
| 10 |
+
from ..datasets import MMDataset
|
| 11 |
+
from .. import processors
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class Task(object):
|
| 15 |
+
"""
|
| 16 |
+
A task refers to one generic training task (e.g., training one model).
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
@classmethod
|
| 20 |
+
def config_task(cls, config):
|
| 21 |
+
"""
|
| 22 |
+
determine whether to load a hard-coded task or config from a generic one.
|
| 23 |
+
via if a task string is available in config.
|
| 24 |
+
"""
|
| 25 |
+
if config.task is not None:
|
| 26 |
+
# TODO (huxu): expand the search scope.
|
| 27 |
+
task_cls = getattr(tasks, config.task)
|
| 28 |
+
return task_cls(config)
|
| 29 |
+
else:
|
| 30 |
+
return Task(config)
|
| 31 |
+
|
| 32 |
+
def __init__(self, config):
|
| 33 |
+
self.config = config
|
| 34 |
+
self.train_data = None
|
| 35 |
+
self.val_data = None
|
| 36 |
+
self.test_data = None
|
| 37 |
+
|
| 38 |
+
self.model = None
|
| 39 |
+
self.loss_fn = None
|
| 40 |
+
self.eval_fn = None
|
| 41 |
+
|
| 42 |
+
def build_dataset(self):
|
| 43 |
+
"""TODO (huxu): move processor breakdown to MMDataset."""
|
| 44 |
+
"""fill-in `self.train_data`, `self.val_data` and `self.test_data`."""
|
| 45 |
+
|
| 46 |
+
meta_processor_cls = getattr(
|
| 47 |
+
processors, self.config.dataset.meta_processor)
|
| 48 |
+
video_processor_cls = getattr(
|
| 49 |
+
processors, self.config.dataset.video_processor)
|
| 50 |
+
text_processor_cls = getattr(
|
| 51 |
+
processors, self.config.dataset.text_processor)
|
| 52 |
+
aligner_cls = getattr(
|
| 53 |
+
processors, self.config.dataset.aligner)
|
| 54 |
+
|
| 55 |
+
if self.config.dataset.train_path is not None:
|
| 56 |
+
self.config.dataset.split = "train"
|
| 57 |
+
# may be used by meta processor.
|
| 58 |
+
# meta_processor controls different dataset.
|
| 59 |
+
meta_processor = meta_processor_cls(self.config.dataset)
|
| 60 |
+
video_processor = video_processor_cls(self.config.dataset)
|
| 61 |
+
text_processor = text_processor_cls(self.config.dataset)
|
| 62 |
+
aligner = aligner_cls(self.config.dataset)
|
| 63 |
+
self.train_data = MMDataset(
|
| 64 |
+
meta_processor, video_processor, text_processor, aligner
|
| 65 |
+
)
|
| 66 |
+
print("train_len", len(self.train_data))
|
| 67 |
+
output = self.train_data[0]
|
| 68 |
+
self.train_data.print_example(output)
|
| 69 |
+
if self.config.dataset.val_path is not None:
|
| 70 |
+
self.config.dataset.split = "valid"
|
| 71 |
+
# may be used by meta processor.
|
| 72 |
+
meta_processor = meta_processor_cls(self.config.dataset)
|
| 73 |
+
video_processor = video_processor_cls(self.config.dataset)
|
| 74 |
+
text_processor = text_processor_cls(self.config.dataset)
|
| 75 |
+
aligner = aligner_cls(self.config.dataset)
|
| 76 |
+
self.val_data = MMDataset(
|
| 77 |
+
meta_processor, video_processor, text_processor, aligner
|
| 78 |
+
)
|
| 79 |
+
print("val_len", len(self.val_data))
|
| 80 |
+
output = self.val_data[0]
|
| 81 |
+
self.val_data.print_example(output)
|
| 82 |
+
|
| 83 |
+
if self.config.dataset.split == "test":
|
| 84 |
+
# the following is run via lauching fairseq-validate.
|
| 85 |
+
meta_processor = meta_processor_cls(self.config.dataset)
|
| 86 |
+
video_processor = video_processor_cls(self.config.dataset)
|
| 87 |
+
text_processor = text_processor_cls(self.config.dataset)
|
| 88 |
+
|
| 89 |
+
self.test_data = MMDataset(
|
| 90 |
+
meta_processor, video_processor, text_processor, aligner
|
| 91 |
+
)
|
| 92 |
+
print("test_len", len(self.test_data))
|
| 93 |
+
output = self.test_data[0]
|
| 94 |
+
self.test_data.print_example(output)
|
| 95 |
+
|
| 96 |
+
def build_model(self, checkpoint=None):
|
| 97 |
+
if self.model is None:
|
| 98 |
+
model_cls = getattr(models, self.config.model.model_cls)
|
| 99 |
+
self.model = model_cls(self.config)
|
| 100 |
+
if checkpoint is not None:
|
| 101 |
+
self.load_checkpoint(checkpoint)
|
| 102 |
+
return self.model
|
| 103 |
+
|
| 104 |
+
def load_checkpoint(self, checkpoint):
|
| 105 |
+
if self.model is None:
|
| 106 |
+
raise ValueError("model is not initialized.")
|
| 107 |
+
state_dict = torch.load(checkpoint)
|
| 108 |
+
state_dict = self._trim_state_dict(state_dict)
|
| 109 |
+
self.model.load_state_dict(state_dict, strict=False)
|
| 110 |
+
# if it's a fp16 model, turn it back.
|
| 111 |
+
if next(self.model.parameters()).dtype == torch.float16:
|
| 112 |
+
self.model = self.model.float()
|
| 113 |
+
return self.model
|
| 114 |
+
|
| 115 |
+
def _trim_state_dict(self, state_dict):
|
| 116 |
+
from collections import OrderedDict
|
| 117 |
+
|
| 118 |
+
if "state_dict" in state_dict:
|
| 119 |
+
state_dict = state_dict["state_dict"]
|
| 120 |
+
if "model" in state_dict: # fairseq checkpoint format.
|
| 121 |
+
state_dict = state_dict["model"]
|
| 122 |
+
ret_state_dict = OrderedDict()
|
| 123 |
+
for (
|
| 124 |
+
key,
|
| 125 |
+
value,
|
| 126 |
+
) in state_dict.items():
|
| 127 |
+
# remove fairseq wrapper since this is a task.
|
| 128 |
+
if key.startswith("mmmodel"):
|
| 129 |
+
key = key[len("mmmodel."):]
|
| 130 |
+
ret_state_dict[key] = value
|
| 131 |
+
return ret_state_dict
|
| 132 |
+
|
| 133 |
+
def build_loss(self):
|
| 134 |
+
if self.loss_fn is None and self.config.loss is not None:
|
| 135 |
+
loss_cls = getattr(losses, self.config.loss.loss_cls)
|
| 136 |
+
self.loss_fn = loss_cls()
|
| 137 |
+
return self.loss_fn
|
| 138 |
+
|
| 139 |
+
def flat_subsample(self, tensor):
|
| 140 |
+
size = tensor.size()
|
| 141 |
+
if len(size) >= 2:
|
| 142 |
+
batch_size = size[0] * size[1]
|
| 143 |
+
expanded_size = (
|
| 144 |
+
(batch_size,) + size[2:] if len(size) > 2
|
| 145 |
+
else (batch_size,)
|
| 146 |
+
)
|
| 147 |
+
tensor = tensor.view(expanded_size)
|
| 148 |
+
return tensor
|
| 149 |
+
|
| 150 |
+
def reshape_subsample(self, sample):
|
| 151 |
+
if (
|
| 152 |
+
hasattr(self.config.dataset, "subsampling")
|
| 153 |
+
and self.config.dataset.subsampling is not None
|
| 154 |
+
and self.config.dataset.subsampling > 1
|
| 155 |
+
):
|
| 156 |
+
for key in sample:
|
| 157 |
+
if torch.is_tensor(sample[key]):
|
| 158 |
+
sample[key] = self.flat_subsample(sample[key])
|
| 159 |
+
return sample
|
| 160 |
+
|
| 161 |
+
def __call__(self, model, sample):
|
| 162 |
+
loss = None
|
| 163 |
+
loss_scalar = float("inf")
|
| 164 |
+
|
| 165 |
+
sample = self.reshape_subsample(sample)
|
| 166 |
+
outputs = self.model(**sample)
|
| 167 |
+
sample.update(outputs)
|
| 168 |
+
if self.loss_fn is not None:
|
| 169 |
+
loss = self.loss_fn(**sample)
|
| 170 |
+
loss_scalar = loss.item()
|
| 171 |
+
|
| 172 |
+
batch_size = sample["caps"].size(0)
|
| 173 |
+
sample_size = 1
|
| 174 |
+
return {
|
| 175 |
+
"loss": loss,
|
| 176 |
+
"loss_scalar": loss_scalar,
|
| 177 |
+
"max_len": self.config.dataset.max_len,
|
| 178 |
+
"batch_size": batch_size,
|
| 179 |
+
"sample_size": sample_size,
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
def build_dataloader(self):
|
| 183 |
+
"""only used for trainer that lacks building loaders."""
|
| 184 |
+
raise NotImplementedError
|
data/fairseq/examples/MMPT/mmpt/tasks/vlmtask.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
from .task import Task
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class VLMTask(Task):
|
| 11 |
+
"""A VLM task for reproducibility.
|
| 12 |
+
the collator split subsamples into two sub-batches.
|
| 13 |
+
This has should have no logic changes.
|
| 14 |
+
but changed the randomness in frame masking.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
def flat_subsample(self, tensor):
|
| 18 |
+
size = tensor.size()
|
| 19 |
+
if len(size) >= 2:
|
| 20 |
+
batch_size = size[0] * (size[1] // 2)
|
| 21 |
+
expanded_size = (
|
| 22 |
+
(batch_size, 2) + size[2:] if len(size) > 2
|
| 23 |
+
else (batch_size, 2)
|
| 24 |
+
)
|
| 25 |
+
tensor = tensor.view(expanded_size)
|
| 26 |
+
tensor = torch.cat([tensor[:, 0], tensor[:, 1]], dim=0)
|
| 27 |
+
return tensor
|
data/fairseq/examples/MMPT/mmpt_cli/localjob.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
from mmpt.utils import recursive_config
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class BaseJob(object):
|
| 11 |
+
def __init__(self, yaml_file, dryrun=False):
|
| 12 |
+
self.yaml_file = yaml_file
|
| 13 |
+
self.config = recursive_config(yaml_file)
|
| 14 |
+
self.dryrun = dryrun
|
| 15 |
+
|
| 16 |
+
def submit(self, **kwargs):
|
| 17 |
+
raise NotImplementedError
|
| 18 |
+
|
| 19 |
+
def _normalize_cmd(self, cmd_list):
|
| 20 |
+
cmd_list = list(cmd_list)
|
| 21 |
+
yaml_index = cmd_list.index("[yaml]")
|
| 22 |
+
cmd_list[yaml_index] = self.yaml_file
|
| 23 |
+
return cmd_list
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class LocalJob(BaseJob):
|
| 27 |
+
|
| 28 |
+
CMD_CONFIG = {
|
| 29 |
+
"local_single": [
|
| 30 |
+
"fairseq-train", "[yaml]", "--user-dir", "mmpt",
|
| 31 |
+
"--task", "mmtask", "--arch", "mmarch",
|
| 32 |
+
"--criterion", "mmloss",
|
| 33 |
+
],
|
| 34 |
+
"local_small": [
|
| 35 |
+
"fairseq-train", "[yaml]", "--user-dir", "mmpt",
|
| 36 |
+
"--task", "mmtask", "--arch", "mmarch",
|
| 37 |
+
"--criterion", "mmloss",
|
| 38 |
+
"--distributed-world-size", "2"
|
| 39 |
+
],
|
| 40 |
+
"local_big": [
|
| 41 |
+
"fairseq-train", "[yaml]", "--user-dir", "mmpt",
|
| 42 |
+
"--task", "mmtask", "--arch", "mmarch",
|
| 43 |
+
"--criterion", "mmloss",
|
| 44 |
+
"--distributed-world-size", "8"
|
| 45 |
+
],
|
| 46 |
+
"local_predict": ["python", "mmpt_cli/predict.py", "[yaml]"],
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
def __init__(self, yaml_file, job_type=None, dryrun=False):
|
| 50 |
+
super().__init__(yaml_file, dryrun)
|
| 51 |
+
if job_type is None:
|
| 52 |
+
self.job_type = "local_single"
|
| 53 |
+
if self.config.task_type is not None:
|
| 54 |
+
self.job_type = self.config.task_type
|
| 55 |
+
else:
|
| 56 |
+
self.job_type = job_type
|
| 57 |
+
if self.job_type in ["local_single", "local_small"]:
|
| 58 |
+
if self.config.fairseq.dataset.batch_size > 32:
|
| 59 |
+
print("decreasing batch_size to 32 for local testing?")
|
| 60 |
+
|
| 61 |
+
def submit(self):
|
| 62 |
+
cmd_list = self._normalize_cmd(LocalJob.CMD_CONFIG[self.job_type])
|
| 63 |
+
if "predict" not in self.job_type:
|
| 64 |
+
# append fairseq args.
|
| 65 |
+
from mmpt.utils import load_config
|
| 66 |
+
|
| 67 |
+
config = load_config(config_file=self.yaml_file)
|
| 68 |
+
for field in config.fairseq:
|
| 69 |
+
for key in config.fairseq[field]:
|
| 70 |
+
if key in ["fp16", "reset_optimizer", "reset_dataloader", "reset_meters"]: # a list of binary flag.
|
| 71 |
+
param = ["--" + key.replace("_", "-")]
|
| 72 |
+
else:
|
| 73 |
+
if key == "lr":
|
| 74 |
+
value = str(config.fairseq[field][key][0])
|
| 75 |
+
elif key == "adam_betas":
|
| 76 |
+
value = "'"+str(config.fairseq[field][key])+"'"
|
| 77 |
+
else:
|
| 78 |
+
value = str(config.fairseq[field][key])
|
| 79 |
+
param = [
|
| 80 |
+
"--" + key.replace("_", "-"),
|
| 81 |
+
value
|
| 82 |
+
]
|
| 83 |
+
cmd_list.extend(param)
|
| 84 |
+
|
| 85 |
+
print("launching", " ".join(cmd_list))
|
| 86 |
+
if not self.dryrun:
|
| 87 |
+
os.system(" ".join(cmd_list))
|
| 88 |
+
return JobStatus("12345678")
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class JobStatus(object):
|
| 92 |
+
def __init__(self, job_id):
|
| 93 |
+
self.job_id = job_id
|
| 94 |
+
|
| 95 |
+
def __repr__(self):
|
| 96 |
+
return self.job_id
|
| 97 |
+
|
| 98 |
+
def __str__(self):
|
| 99 |
+
return self.job_id
|
| 100 |
+
|
| 101 |
+
def done(self):
|
| 102 |
+
return False
|
| 103 |
+
|
| 104 |
+
def running(self):
|
| 105 |
+
return False
|
| 106 |
+
|
| 107 |
+
def result(self):
|
| 108 |
+
if self.done():
|
| 109 |
+
return "{} is done.".format(self.job_id)
|
| 110 |
+
else:
|
| 111 |
+
return "{} is running.".format(self.job_id)
|
| 112 |
+
|
| 113 |
+
def stderr(self):
|
| 114 |
+
return self.result()
|
| 115 |
+
|
| 116 |
+
def stdout(self):
|
| 117 |
+
return self.result()
|
data/fairseq/examples/MMPT/mmpt_cli/predict.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
import os
|
| 6 |
+
import glob
|
| 7 |
+
import argparse
|
| 8 |
+
import pprint
|
| 9 |
+
import omegaconf
|
| 10 |
+
|
| 11 |
+
from omegaconf import OmegaConf
|
| 12 |
+
from torch.utils.data import DataLoader
|
| 13 |
+
|
| 14 |
+
from mmpt.utils import load_config, set_seed
|
| 15 |
+
from mmpt.evaluators import Evaluator
|
| 16 |
+
from mmpt.evaluators import predictor as predictor_path
|
| 17 |
+
from mmpt.tasks import Task
|
| 18 |
+
from mmpt import processors
|
| 19 |
+
from mmpt.datasets import MMDataset
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def get_dataloader(config):
|
| 23 |
+
meta_processor_cls = getattr(processors, config.dataset.meta_processor)
|
| 24 |
+
video_processor_cls = getattr(processors, config.dataset.video_processor)
|
| 25 |
+
text_processor_cls = getattr(processors, config.dataset.text_processor)
|
| 26 |
+
aligner_cls = getattr(processors, config.dataset.aligner)
|
| 27 |
+
|
| 28 |
+
meta_processor = meta_processor_cls(config.dataset)
|
| 29 |
+
video_processor = video_processor_cls(config.dataset)
|
| 30 |
+
text_processor = text_processor_cls(config.dataset)
|
| 31 |
+
aligner = aligner_cls(config.dataset)
|
| 32 |
+
|
| 33 |
+
test_data = MMDataset(
|
| 34 |
+
meta_processor,
|
| 35 |
+
video_processor,
|
| 36 |
+
text_processor,
|
| 37 |
+
aligner,
|
| 38 |
+
)
|
| 39 |
+
print("test_len", len(test_data))
|
| 40 |
+
output = test_data[0]
|
| 41 |
+
test_data.print_example(output)
|
| 42 |
+
|
| 43 |
+
test_dataloader = DataLoader(
|
| 44 |
+
test_data,
|
| 45 |
+
batch_size=config.fairseq.dataset.batch_size,
|
| 46 |
+
shuffle=False,
|
| 47 |
+
num_workers=6,
|
| 48 |
+
collate_fn=test_data.collater,
|
| 49 |
+
)
|
| 50 |
+
return test_dataloader
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def main(args):
|
| 54 |
+
config = load_config(args)
|
| 55 |
+
|
| 56 |
+
if isinstance(config, omegaconf.dictconfig.DictConfig):
|
| 57 |
+
print(OmegaConf.to_yaml(config))
|
| 58 |
+
else:
|
| 59 |
+
pp = pprint.PrettyPrinter(indent=4)
|
| 60 |
+
pp.print(config)
|
| 61 |
+
|
| 62 |
+
mmtask = Task.config_task(config)
|
| 63 |
+
mmtask.build_model()
|
| 64 |
+
|
| 65 |
+
test_dataloader = get_dataloader(config)
|
| 66 |
+
checkpoint_search_path = os.path.dirname(config.eval.save_path)
|
| 67 |
+
results = []
|
| 68 |
+
|
| 69 |
+
prefix = os.path.basename(args.taskconfig)
|
| 70 |
+
if prefix.startswith("test"):
|
| 71 |
+
# loop all checkpoint for datasets without validation set.
|
| 72 |
+
if "best" not in config.fairseq.common_eval.path:
|
| 73 |
+
print("eval each epoch.")
|
| 74 |
+
for checkpoint in glob.glob(checkpoint_search_path + "/checkpoint*"):
|
| 75 |
+
model = mmtask.load_checkpoint(checkpoint)
|
| 76 |
+
ckpt = os.path.basename(checkpoint)
|
| 77 |
+
evaluator = Evaluator(config)
|
| 78 |
+
output = evaluator.evaluate(
|
| 79 |
+
model, test_dataloader, ckpt + "_merged")
|
| 80 |
+
results.append((checkpoint, output))
|
| 81 |
+
# use the one specified by the config lastly.
|
| 82 |
+
model = mmtask.load_checkpoint(config.fairseq.common_eval.path)
|
| 83 |
+
evaluator = Evaluator(config)
|
| 84 |
+
output = evaluator.evaluate(model, test_dataloader)
|
| 85 |
+
results.append((config.fairseq.common_eval.path, output))
|
| 86 |
+
|
| 87 |
+
best_result = None
|
| 88 |
+
best_metric = 0.
|
| 89 |
+
for checkpoint, result in results:
|
| 90 |
+
print(checkpoint)
|
| 91 |
+
evaluator.metric.print_computed_metrics(result)
|
| 92 |
+
best_score = evaluator.metric.best_metric(result)
|
| 93 |
+
if best_score > best_metric:
|
| 94 |
+
best_result = (checkpoint, result)
|
| 95 |
+
best_metric = best_score
|
| 96 |
+
print("best results:")
|
| 97 |
+
print(best_result[0])
|
| 98 |
+
evaluator.metric.print_computed_metrics(best_result[1])
|
| 99 |
+
|
| 100 |
+
elif prefix.startswith("vis"):
|
| 101 |
+
model = mmtask.load_checkpoint(config.fairseq.common_eval.path)
|
| 102 |
+
predictor_cls = getattr(predictor_path, config.predictor)
|
| 103 |
+
predictor = predictor_cls(config)
|
| 104 |
+
predictor.predict_loop(model, test_dataloader, mmtask, None)
|
| 105 |
+
else:
|
| 106 |
+
raise ValueError("unknown prefix of the config file", args.taskconfig)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
if __name__ == "__main__":
|
| 110 |
+
parser = argparse.ArgumentParser()
|
| 111 |
+
parser.add_argument("taskconfig", type=str)
|
| 112 |
+
args = parser.parse_args()
|
| 113 |
+
main(args)
|
data/fairseq/examples/mr_hubert/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MR-HuBERT
|
| 2 |
+
|
| 3 |
+
## Pre-trained models
|
| 4 |
+
|
| 5 |
+
### Main models
|
| 6 |
+
Model | Pretraining Data | Model | Paper Reference
|
| 7 |
+
|---|---|---|---
|
| 8 |
+
MR-HuBERT Base (~97M) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/mono_base/mrhubert_mono_base.pt) | mono\_base
|
| 9 |
+
MR-HuBERT Base (~321M) | [Libri-Light](https://github.com/facebookresearch/libri-light) 60k hr | [download](https://dl.fbaipublicfiles.com/mrhubert/mono_large/mrhubert_mono_large.pt) | mono\_large
|
| 10 |
+
Multilingual MR-HuBERT Base (~97M) | [Voxpopuli](https://github.com/facebookresearch/voxpopuli) 100k hr | [download](https://dl.fbaipublicfiles.com/mrhubert/multi_base/multi_base.pt) | multi\_base
|
| 11 |
+
Multilingual MR-HuBERT Large (~321M) | [Voxpopuli](https://github.com/facebookresearch/voxpopuli) 100k hr | [download 400k steps](https://dl.fbaipublicfiles.com/mrhubert/multi_large/multi_large_400k.pt) or [download 600k steps](https://dl.fbaipublicfiles.com/mrhubert/multi_large/multi_large_600k.pt) | Not in the paper
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
### Abalation models
|
| 15 |
+
Model | Pretraining Data | Model | Paper Reference
|
| 16 |
+
|---|---|---|---
|
| 17 |
+
MR-HuBERT Base (2-4-6 lyrs) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b1-a/b1-a.pt) | (B.1)-a
|
| 18 |
+
MR-HuBERT Base (5-2-5 lyrs) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b1-b/b1-b.pt) | (B.1)-b
|
| 19 |
+
MR-HuBERT Base (6-4-2 lyrs) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b1-c/b1-c.pt) | (B.1)-c
|
| 20 |
+
MR-HuBERT Base (3res 3-2-2-2-3 lyrs) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b2-a/b2-a.pt) | (B.2)-a
|
| 21 |
+
MR-HuBERT Base (3res 2-2-4-2-2 lyrs) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b2-b/b2-b.pt) | (B.2)-b
|
| 22 |
+
MR-HuBERT Base (3res 2-2-2-2-2 lyrs) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b2-c/b2-c.pt) | (B.2)-c
|
| 23 |
+
MR-HuBERT Base (Simple sampling) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b3-a/b3-a.pt) | (B.3)-a
|
| 24 |
+
MR-HuBERT Base (Single target) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b4-a/b4-a.pt) | (B.4)-a
|
| 25 |
+
MR-HuBERT Base (Simple Sampling + single target) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b4-b/b4-b.pt) | (B.4)-b
|
| 26 |
+
MR-HuBERT Base (Mono-resolution 20ms) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b5-a/b5-a.pt) | (B.5)-a
|
| 27 |
+
MR-HuBERT Base (3-3-3 lyrs) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b6-a/b6-a.pt) | (B.6)-a
|
| 28 |
+
MR-HuBERT Base (Mono-resolution 20ms, 3-3-3 lyrs) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b6-b/b6-b.pt) | (B.6)-b
|
| 29 |
+
MR-HuBERT Base (HuBERT 20ms&40ms units) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b7-a/b7-a.pt) | (B.7)-a
|
| 30 |
+
MR-HuBERT Base (Encodec 50Hz unit) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b7-b/b7-b.pt) | (B.7)-b
|
| 31 |
+
MR-HuBERT Base (Encodec 50Hz units and 25Hz units) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b7-c/b7-c.pt) | (B.7)-c
|
| 32 |
+
MR-HuBERT Base (Encodec 50Hz units stream 0&1 ) | [Librispeech](http://www.openslr.org/12) 960 hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b7-d/b7-d.pt) | (B.7)-d
|
| 33 |
+
MR-HuBERT Large (no audio norm) | [LibriLight](https://github.com/facebookresearch/libri-light) 60k hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b8-a/b8-a.pt) | (B.8)-a
|
| 34 |
+
MR-HuBERT Large (check paper ) | [LibriLight](https://github.com/facebookresearch/libri-light) 60k hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b8-b/b8-b.pt) | (B.8)-b
|
| 35 |
+
MR-HuBERT Large (check paper ) | [LibriLight](https://github.com/facebookresearch/libri-light) 60k hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b8-c/b8-c.pt) | (B.8)-c
|
| 36 |
+
MR-HuBERT Large (check paper ) | [LibriLight](https://github.com/facebookresearch/libri-light) 60k hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b8-d/b8-d.pt) | (B.8)-d
|
| 37 |
+
MR-HuBERT Large (check paper ) | [LibriLight](https://github.com/facebookresearch/libri-light) 60k hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b8-e/b8-e.pt) | (B.8)-e
|
| 38 |
+
MR-HuBERT Large (check paper ) | [LibriLight](https://github.com/facebookresearch/libri-light) 60k hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b8-f/b8-f.pt) | (B.8)-f
|
| 39 |
+
MR-HuBERT Large (check paper ) | [LibriLight](https://github.com/facebookresearch/libri-light) 60k hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b8-g/b8-g.pt) | (B.8)-g
|
| 40 |
+
MR-HuBERT Large (check paper ) | [LibriLight](https://github.com/facebookresearch/libri-light) 60k hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b8-h/b8-h.pt) | (B.8)-h
|
| 41 |
+
MR-HuBERT Large (check paper ) | [LibriLight](https://github.com/facebookresearch/libri-light) 60k hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b8-i/b8-i.pt) | (B.8)-i
|
| 42 |
+
MR-HuBERT Large (check paper ) | [LibriLight](https://github.com/facebookresearch/libri-light) 60k hr | [download](https://dl.fbaipublicfiles.com/mrhubert/b8-j/b8-j.pt) | (B.8)-j
|
| 43 |
+
Multilingual MR-HuBERT Large (Simple sampling) | [Voxpopuli](https://github.com/facebookresearch/voxpopuli) 100k hr | [download](https://dl.fbaipublicfiles.com/mrhubert/multi_large_simple/multi_large_simple.pt) | Not in paper
|
| 44 |
+
MR-HuBERT xLarge (from HuBERT-base label) | [LibriLight](https://github.com/facebookresearch/libri-light) 60k hr | [download](https://dl.fbaipublicfiles.com/mrhubert/mono_xlarge/v1.pt) | Not in paper
|
| 45 |
+
MR-HuBERT xLarge (from HuBERT-large label) | [LibriLight](https://github.com/facebookresearch/libri-light) 60k hr | [download](https://dl.fbaipublicfiles.com/mrhubert/mono_xlarge/v2.pt) | Not in paper
|
| 46 |
+
|
| 47 |
+
## Load a model
|
| 48 |
+
```
|
| 49 |
+
ckpt_path = "/path/to/the/checkpoint.pt"
|
| 50 |
+
models, cfg, task = fairseq.checkpoint_utils.load_model_ensemble_and_task([ckpt_path])
|
| 51 |
+
model = models[0]
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
## Train a new model
|
| 55 |
+
|
| 56 |
+
### Data preparation
|
| 57 |
+
|
| 58 |
+
Follow the steps in `./simple_kmeans` to create:
|
| 59 |
+
- `{train,valid}.tsv` waveform list files with length information
|
| 60 |
+
```
|
| 61 |
+
/path/to/your/audio/files
|
| 62 |
+
file1.wav\t160000
|
| 63 |
+
file2.wav\t154600
|
| 64 |
+
...
|
| 65 |
+
filen.wav\t54362
|
| 66 |
+
```
|
| 67 |
+
- `{train,valid}.km` frame-aligned pseudo label files (the order is the same as wavefiles in the tsv file).
|
| 68 |
+
```
|
| 69 |
+
44 44 44 48 48 962 962 962 962 962 962 962 962 967 967 967 967 967 967 967 967 370 852 370 ... 18 18 745 745
|
| 70 |
+
44 44 44 48 48 962 962 962 147 147 147 147 147 147 147 147 147 147 147 147 176 176 271 271 ... 27 27 745 745
|
| 71 |
+
...
|
| 72 |
+
44 44 44 48 962 962 962 962 962 962 377 377 377 77 77 852 696 694 433 578 578 82 740 622 ... 27 27 745 745
|
| 73 |
+
```
|
| 74 |
+
- `dict.km.txt` a dummy dictionary (first column is id, the second is dummy one)
|
| 75 |
+
```
|
| 76 |
+
0 1
|
| 77 |
+
1 1
|
| 78 |
+
2 1
|
| 79 |
+
...
|
| 80 |
+
999 1
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
The `label_rate` is the same as the feature frame rate used for clustering,
|
| 84 |
+
which is 100Hz for MFCC features and 50Hz for HuBERT features by default.
|
| 85 |
+
|
| 86 |
+
### Pre-train a MR-HuBERT model
|
| 87 |
+
|
| 88 |
+
Suppose `{train,valid}.tsv` are saved at `/path/to/data`, `{train,valid}.km`
|
| 89 |
+
are saved at `/path/to/labels`, and the label rate is 100Hz.
|
| 90 |
+
|
| 91 |
+
To train a base model (12 layer transformer), run:
|
| 92 |
+
```sh
|
| 93 |
+
$ python fairseq_cli/hydra_train.py \
|
| 94 |
+
--config-dir /path/to/fairseq-py/examples/mr_hubert/config/pretrain \
|
| 95 |
+
--config-name mrhubert_base_librispeech \
|
| 96 |
+
task.data=/path/to/data task.label_dir=/path/to/labels \
|
| 97 |
+
task.labels='["km"]' model.label_rate=100 \
|
| 98 |
+
task.label_rate_ratios='[1, 2]' \
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
Please see sample pre-training scripts `train.sh` for an example script.
|
| 102 |
+
|
| 103 |
+
### Fine-tune a MR-HuBERT model with a CTC loss
|
| 104 |
+
|
| 105 |
+
Suppose `{train,valid}.tsv` are saved at `/path/to/data`, and their
|
| 106 |
+
corresponding character transcripts `{train,valid}.ltr` are saved at
|
| 107 |
+
`/path/to/trans`. A typical ltr file is with the same order of tsv waveform files as
|
| 108 |
+
```
|
| 109 |
+
HOW | ARE | YOU
|
| 110 |
+
...
|
| 111 |
+
THANK | YOU
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
To fine-tune a pre-trained MR-HuBERT model at `/path/to/checkpoint`, run
|
| 115 |
+
```sh
|
| 116 |
+
$ python fairseq_cli/hydra_train.py \
|
| 117 |
+
--config-dir /path/to/fairseq-py/examples/mr_hubert/config/finetune \
|
| 118 |
+
--config-name base_10h \
|
| 119 |
+
task.data=/path/to/data task.label_dir=/path/to/trans \
|
| 120 |
+
model.w2v_path=/path/to/checkpoint
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
Please see sample fine-tuning scripts `finetune.sh` for an example script.
|
| 124 |
+
|
| 125 |
+
### Decode a MR-HuBERT model
|
| 126 |
+
|
| 127 |
+
Suppose the `test.tsv` and `test.ltr` are the waveform list and transcripts of
|
| 128 |
+
the split to be decoded, saved at `/path/to/data`, and the fine-tuned model is
|
| 129 |
+
saved at `/path/to/checkpoint`.
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
We support three decoding modes:
|
| 133 |
+
- Viterbi decoding: greedy decoding without a language model
|
| 134 |
+
- KenLM decoding: decoding with an arpa-format KenLM n-gram language model
|
| 135 |
+
- Fairseq-LM deocding: decoding with a Fairseq neural language model (not fully tested)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
#### Viterbi decoding
|
| 139 |
+
|
| 140 |
+
`task.normalize` needs to be consistent with the value used during fine-tuning.
|
| 141 |
+
Decoding results will be saved at
|
| 142 |
+
`/path/to/experiment/directory/decode/viterbi/test`.
|
| 143 |
+
|
| 144 |
+
```sh
|
| 145 |
+
$ python examples/speech_recognition/new/infer.py \
|
| 146 |
+
--config-dir /path/to/fairseq-py/examples/mr_hubert/config/decode \
|
| 147 |
+
--config-name infer \
|
| 148 |
+
task.data=/path/to/data \
|
| 149 |
+
task.normalize=[true|false] \
|
| 150 |
+
decoding.exp_dir=/path/to/experiment/directory \
|
| 151 |
+
common_eval.path=/path/to/checkpoint
|
| 152 |
+
dataset.gen_subset=test \
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
#### KenLM / Fairseq-LM decoding
|
| 156 |
+
|
| 157 |
+
Suppose the pronunciation lexicon and the n-gram LM are saved at
|
| 158 |
+
`/path/to/lexicon` and `/path/to/arpa`, respectively. Decoding results will be
|
| 159 |
+
saved at `/path/to/experiment/directory/decode/kenlm/test`.
|
| 160 |
+
|
| 161 |
+
```sh
|
| 162 |
+
$ python examples/speech_recognition/new/infer.py \
|
| 163 |
+
--config-dir /path/to/fairseq-py/examples/mr_hubert/config/decode \
|
| 164 |
+
--config-name infer_lm \
|
| 165 |
+
task.data=/path/to/data \
|
| 166 |
+
task.normalize=[true|false] \
|
| 167 |
+
decoding.exp_dir=/path/to/experiment/directory \
|
| 168 |
+
common_eval.path=/path/to/checkpoint
|
| 169 |
+
dataset.gen_subset=test \
|
| 170 |
+
decoding.decoder.lexicon=/path/to/lexicon \
|
| 171 |
+
decoding.decoder.lmpath=/path/to/arpa
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
The command above uses the default decoding hyperparameter, which can be found
|
| 175 |
+
in `examples/speech_recognition/hydra/decoder.py`. These parameters can be
|
| 176 |
+
configured from the command line. For example, to search with a beam size of
|
| 177 |
+
500, we can append the command above with `decoding.decoder.beam=500`.
|
| 178 |
+
Important parameters include:
|
| 179 |
+
- decoding.decoder.beam
|
| 180 |
+
- decoding.decoder.beamthreshold
|
| 181 |
+
- decoding.decoder.lmweight
|
| 182 |
+
- decoding.decoder.wordscore
|
| 183 |
+
- decoding.decoder.silweight
|
| 184 |
+
|
| 185 |
+
To decode with a Fairseq LM, you may check the usage examples in wav2vec2 or hubert examples.
|
| 186 |
+
|
| 187 |
+
Please see sample decoding scripts `decode.sh` for an example script.
|
data/fairseq/examples/mr_hubert/config/decode/infer.yaml
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _group_
|
| 2 |
+
|
| 3 |
+
defaults:
|
| 4 |
+
- model: null
|
| 5 |
+
|
| 6 |
+
hydra:
|
| 7 |
+
run:
|
| 8 |
+
dir: ${common_eval.results_path}/viterbi
|
| 9 |
+
sweep:
|
| 10 |
+
dir: ${common_eval.results_path}
|
| 11 |
+
subdir: viterbi
|
| 12 |
+
|
| 13 |
+
task:
|
| 14 |
+
_name: multires_hubert_pretraining
|
| 15 |
+
single_target: true
|
| 16 |
+
fine_tuning: true
|
| 17 |
+
label_rate_ratios: ???
|
| 18 |
+
data: ???
|
| 19 |
+
normalize: false
|
| 20 |
+
|
| 21 |
+
decoding:
|
| 22 |
+
type: viterbi
|
| 23 |
+
unique_wer_file: true
|
| 24 |
+
common_eval:
|
| 25 |
+
results_path: ???
|
| 26 |
+
path: ???
|
| 27 |
+
post_process: letter
|
| 28 |
+
dataset:
|
| 29 |
+
max_tokens: 1100000
|
| 30 |
+
gen_subset: ???
|
data/fairseq/examples/mr_hubert/config/decode/infer_lm.yaml
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _group_
|
| 2 |
+
|
| 3 |
+
defaults:
|
| 4 |
+
- model: null
|
| 5 |
+
|
| 6 |
+
hydra:
|
| 7 |
+
run:
|
| 8 |
+
dir: ${common_eval.results_path}/beam${decoding.beam}_th${decoding.beamthreshold}_lmw${decoding.lmweight}_wrd${decoding.wordscore}_sil${decoding.silweight}
|
| 9 |
+
sweep:
|
| 10 |
+
dir: ${common_eval.results_path}
|
| 11 |
+
subdir: beam${decoding.beam}_th${decoding.beamthreshold}_lmw${decoding.lmweight}_wrd${decoding.wordscore}_sil${decoding.silweight}
|
| 12 |
+
|
| 13 |
+
task:
|
| 14 |
+
_name: multires_hubert_pretraining
|
| 15 |
+
single_target: true
|
| 16 |
+
fine_tuning: true
|
| 17 |
+
data: ???
|
| 18 |
+
label_rate_ratios: ???
|
| 19 |
+
normalize: ???
|
| 20 |
+
|
| 21 |
+
decoding:
|
| 22 |
+
type: kenlm
|
| 23 |
+
lexicon: ???
|
| 24 |
+
lmpath: ???
|
| 25 |
+
beamthreshold: 100
|
| 26 |
+
beam: 500
|
| 27 |
+
lmweight: 1.5
|
| 28 |
+
wordscore: -1
|
| 29 |
+
silweight: 0
|
| 30 |
+
unique_wer_file: true
|
| 31 |
+
common_eval:
|
| 32 |
+
results_path: ???
|
| 33 |
+
path: ???
|
| 34 |
+
post_process: letter
|
| 35 |
+
dataset:
|
| 36 |
+
max_tokens: 1100000
|
| 37 |
+
gen_subset: ???
|
data/fairseq/examples/mr_hubert/config/decode/run/submitit_slurm.yaml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _global_
|
| 2 |
+
hydra:
|
| 3 |
+
launcher:
|
| 4 |
+
cpus_per_task: ${distributed_training.distributed_world_size}
|
| 5 |
+
gpus_per_node: ${distributed_training.distributed_world_size}
|
| 6 |
+
tasks_per_node: ${hydra.launcher.gpus_per_node}
|
| 7 |
+
nodes: 1
|
| 8 |
+
mem_gb: 200
|
| 9 |
+
timeout_min: 4320
|
| 10 |
+
max_num_timeout: 50
|
| 11 |
+
name: ${hydra.job.config_name}
|
| 12 |
+
submitit_folder: ${hydra.sweep.dir}/submitit
|
| 13 |
+
|
| 14 |
+
distributed_training:
|
| 15 |
+
distributed_world_size: 1
|
| 16 |
+
distributed_no_spawn: true
|
| 17 |
+
distributed_port: 29761
|
data/fairseq/examples/mr_hubert/config/decode/run/submitit_slurm_8gpu.yaml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _global_
|
| 2 |
+
hydra:
|
| 3 |
+
launcher:
|
| 4 |
+
cpus_per_task: ${distributed_training.distributed_world_size}
|
| 5 |
+
gpus_per_node: ${distributed_training.distributed_world_size}
|
| 6 |
+
tasks_per_node: ${hydra.launcher.gpus_per_node}
|
| 7 |
+
nodes: 1
|
| 8 |
+
mem_gb: 200
|
| 9 |
+
timeout_min: 4320
|
| 10 |
+
max_num_timeout: 50
|
| 11 |
+
name: ${hydra.job.config_name}
|
| 12 |
+
submitit_folder: ${hydra.sweep.dir}/submitit
|
| 13 |
+
|
| 14 |
+
distributed_training:
|
| 15 |
+
distributed_world_size: 8
|
| 16 |
+
distributed_no_spawn: true
|
| 17 |
+
distributed_port: 29761
|
data/fairseq/examples/mr_hubert/config/finetune/base_100h.yaml
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _group_
|
| 2 |
+
|
| 3 |
+
common:
|
| 4 |
+
fp16: true
|
| 5 |
+
log_format: json
|
| 6 |
+
log_interval: 200
|
| 7 |
+
tensorboard_logdir: tblog
|
| 8 |
+
seed: 1337
|
| 9 |
+
|
| 10 |
+
checkpoint:
|
| 11 |
+
no_epoch_checkpoints: true
|
| 12 |
+
best_checkpoint_metric: wer
|
| 13 |
+
|
| 14 |
+
distributed_training:
|
| 15 |
+
ddp_backend: c10d
|
| 16 |
+
find_unused_parameters: true
|
| 17 |
+
distributed_world_size: 8
|
| 18 |
+
distributed_port: 29671
|
| 19 |
+
nprocs_per_node: 8
|
| 20 |
+
|
| 21 |
+
task:
|
| 22 |
+
_name: multires_hubert_pretraining
|
| 23 |
+
data: ???
|
| 24 |
+
fine_tuning: true
|
| 25 |
+
label_dir: ???
|
| 26 |
+
label_rate_ratios: ???
|
| 27 |
+
normalize: false # must be consistent with pre-training
|
| 28 |
+
labels: ["ltr"]
|
| 29 |
+
single_target: true
|
| 30 |
+
|
| 31 |
+
dataset:
|
| 32 |
+
num_workers: 0
|
| 33 |
+
max_tokens: 3200000
|
| 34 |
+
validate_after_updates: ${model.freeze_finetune_updates}
|
| 35 |
+
validate_interval: 5
|
| 36 |
+
train_subset: train_100h
|
| 37 |
+
valid_subset: dev_other
|
| 38 |
+
|
| 39 |
+
criterion:
|
| 40 |
+
_name: ctc
|
| 41 |
+
zero_infinity: true
|
| 42 |
+
|
| 43 |
+
optimization:
|
| 44 |
+
max_update: 80000
|
| 45 |
+
lr: [3e-5]
|
| 46 |
+
sentence_avg: true
|
| 47 |
+
update_freq: [1]
|
| 48 |
+
|
| 49 |
+
optimizer:
|
| 50 |
+
_name: adam
|
| 51 |
+
adam_betas: (0.9,0.98)
|
| 52 |
+
adam_eps: 1e-08
|
| 53 |
+
|
| 54 |
+
lr_scheduler:
|
| 55 |
+
_name: tri_stage
|
| 56 |
+
phase_ratio: [0.1, 0.4, 0.5]
|
| 57 |
+
final_lr_scale: 0.05
|
| 58 |
+
|
| 59 |
+
model:
|
| 60 |
+
_name: multires_hubert_ctc
|
| 61 |
+
multires_hubert_path: ???
|
| 62 |
+
apply_mask: true
|
| 63 |
+
mask_selection: static
|
| 64 |
+
mask_length: 10
|
| 65 |
+
mask_other: 0
|
| 66 |
+
mask_prob: 0.75
|
| 67 |
+
mask_channel_selection: static
|
| 68 |
+
mask_channel_length: 64
|
| 69 |
+
mask_channel_other: 0
|
| 70 |
+
mask_channel_prob: 0.5
|
| 71 |
+
layerdrop: 0.1
|
| 72 |
+
dropout: 0.0
|
| 73 |
+
activation_dropout: 0.1
|
| 74 |
+
attention_dropout: 0.0
|
| 75 |
+
feature_grad_mult: 0.0
|
| 76 |
+
freeze_finetune_updates: 10000
|
| 77 |
+
|
| 78 |
+
hydra:
|
| 79 |
+
job:
|
| 80 |
+
config:
|
| 81 |
+
override_dirname:
|
| 82 |
+
kv_sep: '-'
|
| 83 |
+
item_sep: '__'
|
| 84 |
+
exclude_keys:
|
| 85 |
+
- run
|
| 86 |
+
- task.data
|
| 87 |
+
- task.label_dir
|
| 88 |
+
- model.multires_hubert_path
|
| 89 |
+
- dataset.train_subset
|
| 90 |
+
- dataset.valid_subset
|
| 91 |
+
- criterion.wer_kenlm_model
|
| 92 |
+
- criterion.wer_lexicon
|
| 93 |
+
run:
|
| 94 |
+
dir: ???
|
| 95 |
+
sweep:
|
| 96 |
+
dir: ???
|
| 97 |
+
subdir: ${hydra.job.config_name}__${hydra.job.override_dirname}
|
data/fairseq/examples/mr_hubert/config/finetune/base_100h_large.yaml
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _group_
|
| 2 |
+
|
| 3 |
+
common:
|
| 4 |
+
fp16: true
|
| 5 |
+
log_format: json
|
| 6 |
+
log_interval: 200
|
| 7 |
+
tensorboard_logdir: tblog
|
| 8 |
+
seed: 1337
|
| 9 |
+
|
| 10 |
+
checkpoint:
|
| 11 |
+
no_epoch_checkpoints: true
|
| 12 |
+
best_checkpoint_metric: wer
|
| 13 |
+
|
| 14 |
+
distributed_training:
|
| 15 |
+
ddp_backend: c10d
|
| 16 |
+
find_unused_parameters: true
|
| 17 |
+
distributed_world_size: 8
|
| 18 |
+
distributed_port: 29671
|
| 19 |
+
nprocs_per_node: 8
|
| 20 |
+
|
| 21 |
+
task:
|
| 22 |
+
_name: multires_hubert_pretraining
|
| 23 |
+
data: ???
|
| 24 |
+
fine_tuning: true
|
| 25 |
+
label_dir: ???
|
| 26 |
+
label_rate_ratios: ???
|
| 27 |
+
normalize: true # must be consistent with pre-training
|
| 28 |
+
labels: ["ltr"]
|
| 29 |
+
single_target: true
|
| 30 |
+
|
| 31 |
+
dataset:
|
| 32 |
+
num_workers: 0
|
| 33 |
+
max_tokens: 1600000
|
| 34 |
+
validate_after_updates: ${model.freeze_finetune_updates}
|
| 35 |
+
validate_interval: 5
|
| 36 |
+
train_subset: train_100h
|
| 37 |
+
valid_subset: dev_other
|
| 38 |
+
|
| 39 |
+
criterion:
|
| 40 |
+
_name: ctc
|
| 41 |
+
zero_infinity: true
|
| 42 |
+
|
| 43 |
+
optimization:
|
| 44 |
+
max_update: 80000
|
| 45 |
+
lr: [3e-5]
|
| 46 |
+
sentence_avg: true
|
| 47 |
+
update_freq: [2]
|
| 48 |
+
|
| 49 |
+
optimizer:
|
| 50 |
+
_name: adam
|
| 51 |
+
adam_betas: (0.9,0.98)
|
| 52 |
+
adam_eps: 1e-08
|
| 53 |
+
|
| 54 |
+
lr_scheduler:
|
| 55 |
+
_name: tri_stage
|
| 56 |
+
phase_ratio: [0.1, 0.4, 0.5]
|
| 57 |
+
final_lr_scale: 0.05
|
| 58 |
+
|
| 59 |
+
model:
|
| 60 |
+
_name: multires_hubert_ctc
|
| 61 |
+
multires_hubert_path: ???
|
| 62 |
+
apply_mask: true
|
| 63 |
+
mask_selection: static
|
| 64 |
+
mask_length: 10
|
| 65 |
+
mask_other: 0
|
| 66 |
+
mask_prob: 0.75
|
| 67 |
+
mask_channel_selection: static
|
| 68 |
+
mask_channel_length: 64
|
| 69 |
+
mask_channel_other: 0
|
| 70 |
+
mask_channel_prob: 0.5
|
| 71 |
+
layerdrop: 0.1
|
| 72 |
+
dropout: 0.0
|
| 73 |
+
activation_dropout: 0.1
|
| 74 |
+
attention_dropout: 0.0
|
| 75 |
+
feature_grad_mult: 0.0
|
| 76 |
+
freeze_finetune_updates: 10000
|
| 77 |
+
|
| 78 |
+
hydra:
|
| 79 |
+
job:
|
| 80 |
+
config:
|
| 81 |
+
override_dirname:
|
| 82 |
+
kv_sep: '-'
|
| 83 |
+
item_sep: '__'
|
| 84 |
+
exclude_keys:
|
| 85 |
+
- run
|
| 86 |
+
- task.data
|
| 87 |
+
- task.label_dir
|
| 88 |
+
- model.multires_hubert_path
|
| 89 |
+
- dataset.train_subset
|
| 90 |
+
- dataset.valid_subset
|
| 91 |
+
- criterion.wer_kenlm_model
|
| 92 |
+
- criterion.wer_lexicon
|
| 93 |
+
run:
|
| 94 |
+
dir: ???
|
| 95 |
+
sweep:
|
| 96 |
+
dir: ???
|
| 97 |
+
subdir: ${hydra.job.config_name}__${hydra.job.override_dirname}
|
data/fairseq/examples/mr_hubert/config/finetune/base_10h.yaml
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _group_
|
| 2 |
+
|
| 3 |
+
common:
|
| 4 |
+
fp16: true
|
| 5 |
+
log_format: json
|
| 6 |
+
log_interval: 200
|
| 7 |
+
tensorboard_logdir: tblog
|
| 8 |
+
seed: 1337
|
| 9 |
+
|
| 10 |
+
checkpoint:
|
| 11 |
+
save_interval: 5
|
| 12 |
+
keep_interval_updates: 1
|
| 13 |
+
no_epoch_checkpoints: true
|
| 14 |
+
best_checkpoint_metric: wer
|
| 15 |
+
|
| 16 |
+
distributed_training:
|
| 17 |
+
ddp_backend: c10d
|
| 18 |
+
find_unused_parameters: true
|
| 19 |
+
distributed_world_size: 8
|
| 20 |
+
distributed_port: 29671
|
| 21 |
+
nprocs_per_node: 8
|
| 22 |
+
|
| 23 |
+
task:
|
| 24 |
+
_name: multires_hubert_pretraining
|
| 25 |
+
data: ???
|
| 26 |
+
fine_tuning: true
|
| 27 |
+
label_dir: ???
|
| 28 |
+
label_rate_ratios: ???
|
| 29 |
+
normalize: false # must be consistent with pre-training
|
| 30 |
+
labels: ["ltr"]
|
| 31 |
+
single_target: true
|
| 32 |
+
|
| 33 |
+
dataset:
|
| 34 |
+
num_workers: 0
|
| 35 |
+
max_tokens: 3200000
|
| 36 |
+
validate_after_updates: ${model.freeze_finetune_updates}
|
| 37 |
+
validate_interval: 5
|
| 38 |
+
train_subset: train_10h
|
| 39 |
+
valid_subset: dev
|
| 40 |
+
|
| 41 |
+
criterion:
|
| 42 |
+
_name: ctc
|
| 43 |
+
zero_infinity: true
|
| 44 |
+
|
| 45 |
+
optimization:
|
| 46 |
+
max_update: 25000
|
| 47 |
+
lr: [2e-5]
|
| 48 |
+
sentence_avg: true
|
| 49 |
+
update_freq: [1]
|
| 50 |
+
|
| 51 |
+
optimizer:
|
| 52 |
+
_name: adam
|
| 53 |
+
adam_betas: (0.9,0.98)
|
| 54 |
+
adam_eps: 1e-08
|
| 55 |
+
|
| 56 |
+
lr_scheduler:
|
| 57 |
+
_name: tri_stage
|
| 58 |
+
warmup_steps: 8000
|
| 59 |
+
hold_steps: 0
|
| 60 |
+
decay_steps: 72000
|
| 61 |
+
final_lr_scale: 0.05
|
| 62 |
+
|
| 63 |
+
model:
|
| 64 |
+
_name: multires_hubert_ctc
|
| 65 |
+
multires_hubert_path: ???
|
| 66 |
+
apply_mask: true
|
| 67 |
+
mask_selection: static
|
| 68 |
+
mask_length: 10
|
| 69 |
+
mask_other: 0
|
| 70 |
+
mask_prob: 0.75
|
| 71 |
+
mask_channel_selection: static
|
| 72 |
+
mask_channel_length: 64
|
| 73 |
+
mask_channel_other: 0
|
| 74 |
+
mask_channel_prob: 0.5
|
| 75 |
+
layerdrop: 0.1
|
| 76 |
+
dropout: 0.0
|
| 77 |
+
activation_dropout: 0.1
|
| 78 |
+
attention_dropout: 0.0
|
| 79 |
+
feature_grad_mult: 0.0
|
| 80 |
+
freeze_finetune_updates: 10000
|
| 81 |
+
|
| 82 |
+
hydra:
|
| 83 |
+
job:
|
| 84 |
+
config:
|
| 85 |
+
override_dirname:
|
| 86 |
+
kv_sep: '-'
|
| 87 |
+
item_sep: '__'
|
| 88 |
+
exclude_keys:
|
| 89 |
+
- run
|
| 90 |
+
- task.data
|
| 91 |
+
- task.label_dir
|
| 92 |
+
- model.multires_hubert_path
|
| 93 |
+
- dataset.train_subset
|
| 94 |
+
- dataset.valid_subset
|
| 95 |
+
- criterion.wer_kenlm_model
|
| 96 |
+
- criterion.wer_lexicon
|
| 97 |
+
run:
|
| 98 |
+
dir: ???
|
| 99 |
+
sweep:
|
| 100 |
+
dir: ???
|
| 101 |
+
subdir: ${hydra.job.config_name}__${hydra.job.override_dirname}
|
data/fairseq/examples/mr_hubert/config/finetune/base_10h_large.yaml
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _group_
|
| 2 |
+
|
| 3 |
+
common:
|
| 4 |
+
fp16: true
|
| 5 |
+
log_format: json
|
| 6 |
+
log_interval: 200
|
| 7 |
+
tensorboard_logdir: tblog
|
| 8 |
+
seed: 1337
|
| 9 |
+
|
| 10 |
+
checkpoint:
|
| 11 |
+
save_interval: 5
|
| 12 |
+
keep_interval_updates: 1
|
| 13 |
+
no_epoch_checkpoints: true
|
| 14 |
+
best_checkpoint_metric: wer
|
| 15 |
+
|
| 16 |
+
distributed_training:
|
| 17 |
+
ddp_backend: c10d
|
| 18 |
+
find_unused_parameters: true
|
| 19 |
+
distributed_world_size: 8
|
| 20 |
+
distributed_port: 29671
|
| 21 |
+
nprocs_per_node: 8
|
| 22 |
+
|
| 23 |
+
task:
|
| 24 |
+
_name: multires_hubert_pretraining
|
| 25 |
+
data: ???
|
| 26 |
+
fine_tuning: true
|
| 27 |
+
label_dir: ???
|
| 28 |
+
label_rate_ratios: ???
|
| 29 |
+
normalize: true # must be consistent with pre-training
|
| 30 |
+
labels: ["ltr"]
|
| 31 |
+
single_target: true
|
| 32 |
+
|
| 33 |
+
dataset:
|
| 34 |
+
num_workers: 0
|
| 35 |
+
max_tokens: 3200000
|
| 36 |
+
validate_after_updates: ${model.freeze_finetune_updates}
|
| 37 |
+
validate_interval: 5
|
| 38 |
+
train_subset: train_10h
|
| 39 |
+
valid_subset: dev
|
| 40 |
+
|
| 41 |
+
criterion:
|
| 42 |
+
_name: ctc
|
| 43 |
+
zero_infinity: true
|
| 44 |
+
|
| 45 |
+
optimization:
|
| 46 |
+
max_update: 25000
|
| 47 |
+
lr: [2e-5]
|
| 48 |
+
sentence_avg: true
|
| 49 |
+
update_freq: [1]
|
| 50 |
+
|
| 51 |
+
optimizer:
|
| 52 |
+
_name: adam
|
| 53 |
+
adam_betas: (0.9,0.98)
|
| 54 |
+
adam_eps: 1e-08
|
| 55 |
+
|
| 56 |
+
lr_scheduler:
|
| 57 |
+
_name: tri_stage
|
| 58 |
+
warmup_steps: 8000
|
| 59 |
+
hold_steps: 0
|
| 60 |
+
decay_steps: 72000
|
| 61 |
+
final_lr_scale: 0.05
|
| 62 |
+
|
| 63 |
+
model:
|
| 64 |
+
_name: multires_hubert_ctc
|
| 65 |
+
multires_hubert_path: ???
|
| 66 |
+
apply_mask: true
|
| 67 |
+
mask_selection: static
|
| 68 |
+
mask_length: 10
|
| 69 |
+
mask_other: 0
|
| 70 |
+
mask_prob: 0.75
|
| 71 |
+
mask_channel_selection: static
|
| 72 |
+
mask_channel_length: 64
|
| 73 |
+
mask_channel_other: 0
|
| 74 |
+
mask_channel_prob: 0.5
|
| 75 |
+
layerdrop: 0.1
|
| 76 |
+
dropout: 0.0
|
| 77 |
+
activation_dropout: 0.1
|
| 78 |
+
attention_dropout: 0.0
|
| 79 |
+
feature_grad_mult: 0.0
|
| 80 |
+
freeze_finetune_updates: 10000
|
| 81 |
+
|
| 82 |
+
hydra:
|
| 83 |
+
job:
|
| 84 |
+
config:
|
| 85 |
+
override_dirname:
|
| 86 |
+
kv_sep: '-'
|
| 87 |
+
item_sep: '__'
|
| 88 |
+
exclude_keys:
|
| 89 |
+
- run
|
| 90 |
+
- task.data
|
| 91 |
+
- task.label_dir
|
| 92 |
+
- model.multires_hubert_path
|
| 93 |
+
- dataset.train_subset
|
| 94 |
+
- dataset.valid_subset
|
| 95 |
+
- criterion.wer_kenlm_model
|
| 96 |
+
- criterion.wer_lexicon
|
| 97 |
+
run:
|
| 98 |
+
dir: ???
|
| 99 |
+
sweep:
|
| 100 |
+
dir: ???
|
| 101 |
+
subdir: ${hydra.job.config_name}__${hydra.job.override_dirname}
|
data/fairseq/examples/mr_hubert/config/finetune/base_1h.yaml
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _group_
|
| 2 |
+
|
| 3 |
+
common:
|
| 4 |
+
fp16: true
|
| 5 |
+
log_format: json
|
| 6 |
+
log_interval: 200
|
| 7 |
+
tensorboard_logdir: tblog
|
| 8 |
+
seed: 1337
|
| 9 |
+
|
| 10 |
+
checkpoint:
|
| 11 |
+
save_interval: 50
|
| 12 |
+
keep_interval_updates: 1
|
| 13 |
+
save_interval_updates: 1000
|
| 14 |
+
no_epoch_checkpoints: true
|
| 15 |
+
best_checkpoint_metric: wer
|
| 16 |
+
|
| 17 |
+
distributed_training:
|
| 18 |
+
ddp_backend: c10d
|
| 19 |
+
find_unused_parameters: true
|
| 20 |
+
distributed_world_size: 8
|
| 21 |
+
distributed_port: 29671
|
| 22 |
+
nprocs_per_node: 8
|
| 23 |
+
|
| 24 |
+
task:
|
| 25 |
+
_name: multires_hubert_pretraining
|
| 26 |
+
data: ???
|
| 27 |
+
fine_tuning: true
|
| 28 |
+
label_dir: ???
|
| 29 |
+
label_rate_ratios: ???
|
| 30 |
+
normalize: false # must be consistent with pre-training
|
| 31 |
+
labels: ["ltr"]
|
| 32 |
+
single_target: true
|
| 33 |
+
|
| 34 |
+
dataset:
|
| 35 |
+
num_workers: 0
|
| 36 |
+
max_tokens: 3200000
|
| 37 |
+
validate_after_updates: ${model.freeze_finetune_updates}
|
| 38 |
+
validate_interval: 1000
|
| 39 |
+
train_subset: train_1h
|
| 40 |
+
valid_subset: dev_other
|
| 41 |
+
|
| 42 |
+
criterion:
|
| 43 |
+
_name: ctc
|
| 44 |
+
zero_infinity: true
|
| 45 |
+
|
| 46 |
+
optimization:
|
| 47 |
+
max_update: 13000
|
| 48 |
+
lr: [5e-5]
|
| 49 |
+
sentence_avg: true
|
| 50 |
+
update_freq: [4]
|
| 51 |
+
|
| 52 |
+
optimizer:
|
| 53 |
+
_name: adam
|
| 54 |
+
adam_betas: (0.9,0.98)
|
| 55 |
+
adam_eps: 1e-08
|
| 56 |
+
|
| 57 |
+
lr_scheduler:
|
| 58 |
+
_name: tri_stage
|
| 59 |
+
phase_ratio: [0.1, 0.4, 0.5]
|
| 60 |
+
final_lr_scale: 0.05
|
| 61 |
+
|
| 62 |
+
model:
|
| 63 |
+
_name: multires_hubert_ctc
|
| 64 |
+
multires_hubert_path: ???
|
| 65 |
+
apply_mask: true
|
| 66 |
+
mask_selection: static
|
| 67 |
+
mask_length: 10
|
| 68 |
+
mask_other: 0
|
| 69 |
+
mask_prob: 0.75
|
| 70 |
+
mask_channel_selection: static
|
| 71 |
+
mask_channel_length: 64
|
| 72 |
+
mask_channel_other: 0
|
| 73 |
+
mask_channel_prob: 0.5
|
| 74 |
+
layerdrop: 0.1
|
| 75 |
+
dropout: 0.0
|
| 76 |
+
activation_dropout: 0.1
|
| 77 |
+
attention_dropout: 0.0
|
| 78 |
+
feature_grad_mult: 0.0
|
| 79 |
+
freeze_finetune_updates: 10000
|
| 80 |
+
|
| 81 |
+
hydra:
|
| 82 |
+
job:
|
| 83 |
+
config:
|
| 84 |
+
override_dirname:
|
| 85 |
+
kv_sep: '-'
|
| 86 |
+
item_sep: '__'
|
| 87 |
+
exclude_keys:
|
| 88 |
+
- run
|
| 89 |
+
- task.data
|
| 90 |
+
- task.label_dir
|
| 91 |
+
- model.multires_hubert_path
|
| 92 |
+
- dataset.train_subset
|
| 93 |
+
- dataset.valid_subset
|
| 94 |
+
- criterion.wer_kenlm_model
|
| 95 |
+
- criterion.wer_lexicon
|
| 96 |
+
run:
|
| 97 |
+
dir: ???
|
| 98 |
+
sweep:
|
| 99 |
+
dir: ???
|
| 100 |
+
subdir: ${hydra.job.config_name}__${hydra.job.override_dirname}
|
data/fairseq/examples/mr_hubert/config/finetune/base_1h_large.yaml
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _group_
|
| 2 |
+
|
| 3 |
+
common:
|
| 4 |
+
fp16: true
|
| 5 |
+
log_format: json
|
| 6 |
+
log_interval: 200
|
| 7 |
+
tensorboard_logdir: tblog
|
| 8 |
+
seed: 1337
|
| 9 |
+
|
| 10 |
+
checkpoint:
|
| 11 |
+
save_interval: 1000
|
| 12 |
+
keep_interval_updates: 1
|
| 13 |
+
no_epoch_checkpoints: true
|
| 14 |
+
best_checkpoint_metric: wer
|
| 15 |
+
|
| 16 |
+
distributed_training:
|
| 17 |
+
ddp_backend: c10d
|
| 18 |
+
find_unused_parameters: true
|
| 19 |
+
distributed_world_size: 8
|
| 20 |
+
distributed_port: 29671
|
| 21 |
+
nprocs_per_node: 8
|
| 22 |
+
|
| 23 |
+
task:
|
| 24 |
+
_name: multires_hubert_pretraining
|
| 25 |
+
data: ???
|
| 26 |
+
fine_tuning: true
|
| 27 |
+
label_dir: ???
|
| 28 |
+
label_rate_ratios: ???
|
| 29 |
+
normalize: true # must be consistent with pre-training
|
| 30 |
+
labels: ["ltr"]
|
| 31 |
+
single_target: true
|
| 32 |
+
|
| 33 |
+
dataset:
|
| 34 |
+
num_workers: 0
|
| 35 |
+
max_tokens: 1280000
|
| 36 |
+
validate_after_updates: ${model.freeze_finetune_updates}
|
| 37 |
+
validate_interval: 5
|
| 38 |
+
train_subset: train_10h
|
| 39 |
+
valid_subset: dev
|
| 40 |
+
|
| 41 |
+
criterion:
|
| 42 |
+
_name: ctc
|
| 43 |
+
zero_infinity: true
|
| 44 |
+
|
| 45 |
+
optimization:
|
| 46 |
+
max_update: 25000
|
| 47 |
+
lr: [3e-4]
|
| 48 |
+
sentence_avg: true
|
| 49 |
+
update_freq: [5]
|
| 50 |
+
|
| 51 |
+
optimizer:
|
| 52 |
+
_name: adam
|
| 53 |
+
adam_betas: (0.9,0.98)
|
| 54 |
+
adam_eps: 1e-08
|
| 55 |
+
|
| 56 |
+
lr_scheduler:
|
| 57 |
+
_name: tri_stage
|
| 58 |
+
phase_ratio: [0.1, 0.4, 0.5]
|
| 59 |
+
final_lr_scale: 0.05
|
| 60 |
+
|
| 61 |
+
model:
|
| 62 |
+
_name: multires_hubert_ctc
|
| 63 |
+
multires_hubert_path: ???
|
| 64 |
+
apply_mask: true
|
| 65 |
+
mask_selection: static
|
| 66 |
+
mask_length: 10
|
| 67 |
+
mask_other: 0
|
| 68 |
+
mask_prob: 0.75
|
| 69 |
+
mask_channel_selection: static
|
| 70 |
+
mask_channel_length: 64
|
| 71 |
+
mask_channel_other: 0
|
| 72 |
+
mask_channel_prob: 0.5
|
| 73 |
+
layerdrop: 0.1
|
| 74 |
+
dropout: 0.0
|
| 75 |
+
activation_dropout: 0.1
|
| 76 |
+
attention_dropout: 0.0
|
| 77 |
+
feature_grad_mult: 0.0
|
| 78 |
+
freeze_finetune_updates: 10000
|
| 79 |
+
|
| 80 |
+
hydra:
|
| 81 |
+
job:
|
| 82 |
+
config:
|
| 83 |
+
override_dirname:
|
| 84 |
+
kv_sep: '-'
|
| 85 |
+
item_sep: '__'
|
| 86 |
+
exclude_keys:
|
| 87 |
+
- run
|
| 88 |
+
- task.data
|
| 89 |
+
- task.label_dir
|
| 90 |
+
- model.multires_hubert_path
|
| 91 |
+
- dataset.train_subset
|
| 92 |
+
- dataset.valid_subset
|
| 93 |
+
- criterion.wer_kenlm_model
|
| 94 |
+
- criterion.wer_lexicon
|
| 95 |
+
run:
|
| 96 |
+
dir: ???
|
| 97 |
+
sweep:
|
| 98 |
+
dir: ???
|
| 99 |
+
subdir: ${hydra.job.config_name}__${hydra.job.override_dirname}
|
data/fairseq/examples/mr_hubert/config/pretrain/mrhubert_base_librispeech.yaml
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _group_
|
| 2 |
+
|
| 3 |
+
common:
|
| 4 |
+
fp16: true
|
| 5 |
+
log_format: json
|
| 6 |
+
log_interval: 200
|
| 7 |
+
seed: 1337
|
| 8 |
+
tensorboard_logdir: tblog
|
| 9 |
+
min_loss_scale: 1e-8
|
| 10 |
+
|
| 11 |
+
checkpoint:
|
| 12 |
+
save_interval_updates: 25000
|
| 13 |
+
keep_interval_updates: 1
|
| 14 |
+
no_epoch_checkpoints: true
|
| 15 |
+
|
| 16 |
+
distributed_training:
|
| 17 |
+
ddp_backend: no_c10d
|
| 18 |
+
distributed_backend: 'nccl'
|
| 19 |
+
distributed_world_size: 32
|
| 20 |
+
distributed_port: 29671
|
| 21 |
+
nprocs_per_node: 8
|
| 22 |
+
find_unused_parameters: true
|
| 23 |
+
|
| 24 |
+
task:
|
| 25 |
+
_name: multires_hubert_pretraining
|
| 26 |
+
data: ???
|
| 27 |
+
label_dir: ???
|
| 28 |
+
labels: ???
|
| 29 |
+
label_rate: ${model.label_rate}
|
| 30 |
+
label_rate_ratios: ???
|
| 31 |
+
sample_rate: 16000
|
| 32 |
+
max_sample_size: 250000
|
| 33 |
+
min_sample_size: 32000
|
| 34 |
+
pad_audio: false
|
| 35 |
+
random_crop: true
|
| 36 |
+
normalize: false # must be consistent with extractor
|
| 37 |
+
# max_keep_size: 300000
|
| 38 |
+
# max_keep_size: 50000
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
dataset:
|
| 42 |
+
num_workers: 0
|
| 43 |
+
max_tokens: 1000000
|
| 44 |
+
skip_invalid_size_inputs_valid_test: true
|
| 45 |
+
validate_interval: 5
|
| 46 |
+
validate_interval_updates: 10000
|
| 47 |
+
|
| 48 |
+
criterion:
|
| 49 |
+
_name: hubert
|
| 50 |
+
pred_masked_weight: 1.0
|
| 51 |
+
pred_nomask_weight: 0.0
|
| 52 |
+
loss_weights: [10,]
|
| 53 |
+
|
| 54 |
+
optimization:
|
| 55 |
+
max_update: 400000
|
| 56 |
+
lr: [0.0005]
|
| 57 |
+
clip_norm: 10.0
|
| 58 |
+
|
| 59 |
+
optimizer:
|
| 60 |
+
_name: adam
|
| 61 |
+
adam_betas: (0.9,0.98)
|
| 62 |
+
adam_eps: 1e-06
|
| 63 |
+
weight_decay: 0.01
|
| 64 |
+
|
| 65 |
+
lr_scheduler:
|
| 66 |
+
_name: polynomial_decay
|
| 67 |
+
warmup_updates: 32000
|
| 68 |
+
|
| 69 |
+
model:
|
| 70 |
+
_name: multires_hubert
|
| 71 |
+
label_rate: ???
|
| 72 |
+
label_rate_ratios: ${task.label_rate_ratios}
|
| 73 |
+
skip_masked: false
|
| 74 |
+
skip_nomask: false
|
| 75 |
+
mask_prob: 0.80
|
| 76 |
+
extractor_mode: default
|
| 77 |
+
conv_feature_layers: '[(512,10,5)] + [(512,3,2)] * 4 + [(512,2,2)] * 2'
|
| 78 |
+
final_dim: 256
|
| 79 |
+
encoder_layers: 4
|
| 80 |
+
encoder_layerdrop: 0.05
|
| 81 |
+
dropout_input: 0.1
|
| 82 |
+
dropout_features: 0.1
|
| 83 |
+
dropout: 0.1
|
| 84 |
+
attention_dropout: 0.1
|
| 85 |
+
feature_grad_mult: 0.1
|
| 86 |
+
untie_final_proj: true
|
| 87 |
+
activation_dropout: 0.0
|
| 88 |
+
conv_adapator_kernal: 1
|
| 89 |
+
use_single_target: true
|
| 90 |
+
|
| 91 |
+
hydra:
|
| 92 |
+
job:
|
| 93 |
+
config:
|
| 94 |
+
override_dirname:
|
| 95 |
+
kv_sep: '-'
|
| 96 |
+
item_sep: '/'
|
| 97 |
+
exclude_keys:
|
| 98 |
+
- run
|
| 99 |
+
- task.data
|
| 100 |
+
- task.label_dir
|
| 101 |
+
- common.min_loss_scale
|
| 102 |
+
- common.log_interval
|
| 103 |
+
- optimization.clip_norm
|
data/fairseq/examples/mr_hubert/config/pretrain/mrhubert_large_librilight.yaml
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _group_
|
| 2 |
+
|
| 3 |
+
common:
|
| 4 |
+
memory_efficient_fp16: true
|
| 5 |
+
log_format: json
|
| 6 |
+
log_interval: 200
|
| 7 |
+
seed: 1337
|
| 8 |
+
tensorboard_logdir: tblog
|
| 9 |
+
|
| 10 |
+
checkpoint:
|
| 11 |
+
save_interval_updates: 25000
|
| 12 |
+
keep_interval_updates: 1
|
| 13 |
+
no_epoch_checkpoints: true
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
distributed_training:
|
| 17 |
+
ddp_backend: no_c10d
|
| 18 |
+
distributed_backend: 'nccl'
|
| 19 |
+
distributed_world_size: 128
|
| 20 |
+
distributed_port: 29671
|
| 21 |
+
nprocs_per_node: 8
|
| 22 |
+
find_unused_parameters: true
|
| 23 |
+
|
| 24 |
+
task:
|
| 25 |
+
_name: multires_hubert_pretraining
|
| 26 |
+
data: ???
|
| 27 |
+
label_dir: ???
|
| 28 |
+
labels: ???
|
| 29 |
+
label_rate: ${model.label_rate}
|
| 30 |
+
label_rate_ratios: ???
|
| 31 |
+
sample_rate: 16000
|
| 32 |
+
max_sample_size: 250000
|
| 33 |
+
min_sample_size: 32000
|
| 34 |
+
pad_audio: false
|
| 35 |
+
random_crop: true
|
| 36 |
+
normalize: true # must be consistent with extractor
|
| 37 |
+
# max_keep_size: 50000
|
| 38 |
+
|
| 39 |
+
dataset:
|
| 40 |
+
num_workers: 0
|
| 41 |
+
max_tokens: 300000
|
| 42 |
+
skip_invalid_size_inputs_valid_test: true
|
| 43 |
+
validate_interval: 5
|
| 44 |
+
validate_interval_updates: 10000
|
| 45 |
+
|
| 46 |
+
criterion:
|
| 47 |
+
_name: hubert
|
| 48 |
+
pred_masked_weight: 1.0
|
| 49 |
+
pred_nomask_weight: 0.0
|
| 50 |
+
loss_weights: [10,]
|
| 51 |
+
|
| 52 |
+
optimization:
|
| 53 |
+
max_update: 400000
|
| 54 |
+
lr: [0.0015]
|
| 55 |
+
clip_norm: 1.0
|
| 56 |
+
update_freq: [3]
|
| 57 |
+
|
| 58 |
+
optimizer:
|
| 59 |
+
_name: adam
|
| 60 |
+
adam_betas: (0.9,0.98)
|
| 61 |
+
adam_eps: 1e-06
|
| 62 |
+
weight_decay: 0.01
|
| 63 |
+
|
| 64 |
+
lr_scheduler:
|
| 65 |
+
_name: polynomial_decay
|
| 66 |
+
warmup_updates: 32000
|
| 67 |
+
|
| 68 |
+
model:
|
| 69 |
+
_name: multires_hubert
|
| 70 |
+
label_rate: ???
|
| 71 |
+
label_rate_ratios: ${task.label_rate_ratios}
|
| 72 |
+
encoder_layers: 8
|
| 73 |
+
encoder_embed_dim: 1024
|
| 74 |
+
encoder_ffn_embed_dim: 4096
|
| 75 |
+
encoder_attention_heads: 16
|
| 76 |
+
final_dim: 768
|
| 77 |
+
skip_masked: false
|
| 78 |
+
skip_nomask: false
|
| 79 |
+
mask_prob: 0.80
|
| 80 |
+
extractor_mode: layer_norm
|
| 81 |
+
conv_feature_layers: '[(512,10,5)] + [(512,3,2)] * 4 + [(512,2,2)] * 2'
|
| 82 |
+
encoder_layerdrop: 0.0
|
| 83 |
+
dropout_input: 0.0
|
| 84 |
+
dropout_features: 0.0
|
| 85 |
+
dropout: 0.0
|
| 86 |
+
attention_dropout: 0.0
|
| 87 |
+
layer_norm_first: true
|
| 88 |
+
feature_grad_mult: 1.0
|
| 89 |
+
untie_final_proj: true
|
| 90 |
+
activation_dropout: 0.0
|
| 91 |
+
conv_adapator_kernal: 1
|
| 92 |
+
use_single_target: true
|
| 93 |
+
|
| 94 |
+
hydra:
|
| 95 |
+
job:
|
| 96 |
+
config:
|
| 97 |
+
override_dirname:
|
| 98 |
+
kv_sep: '-'
|
| 99 |
+
item_sep: '__'
|
| 100 |
+
exclude_keys:
|
| 101 |
+
- run
|
| 102 |
+
- task.data
|
| 103 |
+
run:
|
| 104 |
+
dir: /checkpoint/wnhsu/w2v/hubert_final/hydra_pt
|
| 105 |
+
sweep:
|
| 106 |
+
dir: /checkpoint/wnhsu/w2v/hubert_final/hydra_pt
|
| 107 |
+
subdir: ${hydra.job.config_name}__${hydra.job.override_dirname}
|
data/fairseq/examples/mr_hubert/config/pretrain/run/submitit_reg.yaml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _global_
|
| 2 |
+
|
| 3 |
+
hydra:
|
| 4 |
+
launcher:
|
| 5 |
+
cpus_per_task: 8
|
| 6 |
+
gpus_per_node: 8
|
| 7 |
+
tasks_per_node: ${hydra.launcher.gpus_per_node}
|
| 8 |
+
nodes: 4
|
| 9 |
+
comment: null
|
| 10 |
+
mem_gb: 384
|
| 11 |
+
timeout_min: 4320
|
| 12 |
+
max_num_timeout: 100
|
| 13 |
+
constraint: volta32gb
|
| 14 |
+
name: ${hydra.job.config_name}/${hydra.job.override_dirname}
|
| 15 |
+
submitit_folder: ${hydra.sweep.dir}/submitit/%j
|
| 16 |
+
|
| 17 |
+
distributed_training:
|
| 18 |
+
distributed_world_size: 32
|
| 19 |
+
distributed_port: 29671
|
| 20 |
+
nprocs_per_node: 8
|
data/fairseq/examples/mr_hubert/decode.sh
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
FAIRSEQ= # Setup your fairseq directory
|
| 4 |
+
|
| 5 |
+
config_dir=${FAIRSEQ}/examples/mr_hubert/config
|
| 6 |
+
config_name=mr_hubert_base_librispeech
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
# Prepared Data Directory
|
| 10 |
+
|
| 11 |
+
data_dir=librispeech
|
| 12 |
+
# -- data_dir
|
| 13 |
+
# -- test.tsv
|
| 14 |
+
# -- test.ltr
|
| 15 |
+
# -- dict.ltr.txt
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
exp_dir=exp # Target experiments directory (where you have your pre-trained model with checkpoint_best.pt)
|
| 19 |
+
ratios="[1, 2]" # Default label rate ratios
|
| 20 |
+
|
| 21 |
+
_opts=
|
| 22 |
+
|
| 23 |
+
# If use slurm, uncomment this line and modify the job submission at
|
| 24 |
+
# _opts="${_opts} hydra/launcher=submitit_slurm +hydra.launcher.partition=${your_slurm_partition} +run=submitit_reg"
|
| 25 |
+
|
| 26 |
+
# If want to set additional experiment tag, uncomment this line
|
| 27 |
+
# _opts="${_opts} hydra.sweep.subdir=${your_experiment_tag}"
|
| 28 |
+
|
| 29 |
+
# If use un-normalized audio, uncomment this line
|
| 30 |
+
# _opts="${_opts} task.normalize=false"
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
PYTHONPATH=${FAIRSEQ}
|
| 35 |
+
python examples/speech_recognition/new/infer.py \
|
| 36 |
+
--config-dir ${config_dir} \
|
| 37 |
+
--config-name infer_multires \
|
| 38 |
+
${_opts} \
|
| 39 |
+
task.data=${data_dir} \
|
| 40 |
+
task.label_rate_ratios='${ratios}' \
|
| 41 |
+
common_eval.results_path=${exp_dir} \
|
| 42 |
+
common_eval.path=${exp_dir}/checkpoint_best.pt \
|
| 43 |
+
dataset.max_tokens=2000000 \
|
| 44 |
+
dataset.gen_subset=test \
|
| 45 |
+
dataset.skip_invalid_size_inputs_valid_test=true
|
| 46 |
+
|
data/fairseq/examples/mr_hubert/finetune.sh
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
FAIRSEQ= # Setup your fairseq directory
|
| 4 |
+
|
| 5 |
+
config_dir=${FAIRSEQ}/examples/mr_hubert/config
|
| 6 |
+
config_name=mr_hubert_base_librispeech
|
| 7 |
+
|
| 8 |
+
# override configs if need
|
| 9 |
+
max_tokens=3200000
|
| 10 |
+
max_sample_size=1000000
|
| 11 |
+
max_update=50000
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
# Prepared Data Directory
|
| 15 |
+
|
| 16 |
+
data_dir=librispeech
|
| 17 |
+
# -- data_dir
|
| 18 |
+
# -- train.tsv
|
| 19 |
+
# -- train.ltr
|
| 20 |
+
# -- valid.tsv
|
| 21 |
+
# -- valid.ltr
|
| 22 |
+
# -- dict.ltr.txt
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
exp_dir=exp # Target experiments directory
|
| 26 |
+
ratios="[1, 2]" # Default label rate ratios
|
| 27 |
+
hubert_path=/path/of/your/hubert.pt
|
| 28 |
+
|
| 29 |
+
_opts=
|
| 30 |
+
|
| 31 |
+
# If use slurm, uncomment this line and modify the job submission at
|
| 32 |
+
# _opts="${_opts} hydra/launcher=submitit_slurm +hydra.launcher.partition=${your_slurm_partition} +run=submitit_reg"
|
| 33 |
+
|
| 34 |
+
# If want to set additional experiment tag, uncomment this line
|
| 35 |
+
# _opts="${_opts} hydra.sweep.subdir=${your_experiment_tag}"
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
python ${FAIRSEQ}/fairseq_cli/hydra_train.py \
|
| 39 |
+
-m --config-dir ${config_dir} --config-name ${config_name} ${_opts} \
|
| 40 |
+
task.data=${data_dir} +task.max_sample_size=${max_sample_size} \
|
| 41 |
+
task.label_dir=${data_dir} \
|
| 42 |
+
task.label_rate_ratios='${ratios}' \
|
| 43 |
+
dataset.max_tokens=${max_tokens} \
|
| 44 |
+
optimization.max_update=${max_update} \
|
| 45 |
+
model.multires_hubert_path=${hubert_path} \
|
| 46 |
+
hydra.sweep.dir=${exp_dir} &
|
data/fairseq/examples/mr_hubert/train.sh
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
FAIRSEQ= # Setup your fairseq directory
|
| 4 |
+
|
| 5 |
+
config_dir=${FAIRSEQ}/examples/mr_hubert/config
|
| 6 |
+
config_name=mr_hubert_base_librispeech
|
| 7 |
+
|
| 8 |
+
# Prepared Data Directory
|
| 9 |
+
data_dir=librispeech
|
| 10 |
+
# -- data_dir
|
| 11 |
+
# -- train.tsv
|
| 12 |
+
# -- valid.tsv
|
| 13 |
+
|
| 14 |
+
label_dir=labels
|
| 15 |
+
# -- label_dir
|
| 16 |
+
# -- train.km
|
| 17 |
+
# -- valid.km
|
| 18 |
+
# -- dict.km.txt
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
exp_dir=exp # Target experiments directory
|
| 22 |
+
ratios="[1, 2]" # Default label rate ratios
|
| 23 |
+
label_rate=50 # Base label rate
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
_opts=
|
| 27 |
+
|
| 28 |
+
# If use slurm, uncomment this line and modify the job submission at
|
| 29 |
+
# _opts="${_opts} hydra/launcher=submitit_slurm +hydra.launcher.partition=${your_slurm_partition} +run=submitit_reg"
|
| 30 |
+
|
| 31 |
+
# If want to set additional experiment tag, uncomment this line
|
| 32 |
+
# _opts="${_opts} hydra.sweep.subdir=${your_experiment_tag}"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
python ${FAIRSEQ}/fairseq_cli/hydra_train.py \
|
| 36 |
+
-m --config-dir ${config_dir} --config-name ${config_name} ${_opts} \
|
| 37 |
+
task.data=${data_dir} \
|
| 38 |
+
task.label_dir=${label_dir} \
|
| 39 |
+
task.labels='["km"]' \
|
| 40 |
+
model.label_rate=${label_rate} \
|
| 41 |
+
task.label_rate_ratios='${ratios}' \
|
| 42 |
+
hydra.sweep.dir=${exp_dir} &
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
|