sonithoiningombam commited on
Commit
86f9c92
·
verified ·
1 Parent(s): 2ad935f

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. data/fairseq/examples/MMPT/mmpt/datasets/__init__.py +10 -0
  2. data/fairseq/examples/MMPT/mmpt/datasets/fairseqmmdataset.py +57 -0
  3. data/fairseq/examples/MMPT/mmpt/datasets/mmdataset.py +111 -0
  4. data/fairseq/examples/MMPT/mmpt/evaluators/evaluator.py +54 -0
  5. data/fairseq/examples/MMPT/mmpt/evaluators/metric.py +313 -0
  6. data/fairseq/examples/MMPT/mmpt/losses/__init__.py +16 -0
  7. data/fairseq/examples/MMPT/mmpt/losses/fairseqmmloss.py +63 -0
  8. data/fairseq/examples/MMPT/mmpt/losses/loss.py +87 -0
  9. data/fairseq/examples/MMPT/mmpt/losses/nce.py +156 -0
  10. data/fairseq/examples/MMPT/mmpt/models/__init__.py +17 -0
  11. data/fairseq/examples/MMPT/mmpt/models/fairseqmmmodel.py +51 -0
  12. data/fairseq/examples/MMPT/mmpt/models/mmfusion.py +926 -0
  13. data/fairseq/examples/MMPT/mmpt/models/mmfusionnlg.py +999 -0
  14. data/fairseq/examples/MMPT/mmpt/models/transformermodel.py +734 -0
  15. data/fairseq/examples/MMPT/mmpt/modules/__init__.py +10 -0
  16. data/fairseq/examples/MMPT/mmpt/modules/mm.py +145 -0
  17. data/fairseq/examples/MMPT/mmpt/modules/retri.py +429 -0
  18. data/fairseq/examples/MMPT/mmpt/modules/vectorpool.py +246 -0
  19. data/fairseq/examples/MMPT/mmpt/processors/__init__.py +23 -0
  20. data/fairseq/examples/MMPT/mmpt/processors/dedupprocessor.py +242 -0
  21. data/fairseq/examples/MMPT/mmpt/processors/dsprocessor.py +848 -0
  22. data/fairseq/examples/MMPT/mmpt/processors/how2processor.py +887 -0
  23. data/fairseq/examples/MMPT/mmpt/processors/how2retriprocessor.py +100 -0
  24. data/fairseq/examples/MMPT/mmpt/processors/models/s3dg.py +336 -0
  25. data/fairseq/examples/MMPT/mmpt/processors/processor.py +274 -0
  26. data/fairseq/examples/MMPT/mmpt/tasks/__init__.py +22 -0
  27. data/fairseq/examples/MMPT/mmpt/tasks/fairseqmmtask.py +104 -0
  28. data/fairseq/examples/MMPT/mmpt/tasks/milncetask.py +27 -0
  29. data/fairseq/examples/MMPT/mmpt/tasks/retritask.py +253 -0
  30. data/fairseq/examples/MMPT/mmpt/tasks/task.py +184 -0
  31. data/fairseq/examples/MMPT/mmpt/tasks/vlmtask.py +27 -0
  32. data/fairseq/examples/MMPT/mmpt_cli/localjob.py +117 -0
  33. data/fairseq/examples/MMPT/mmpt_cli/predict.py +113 -0
  34. data/fairseq/examples/mr_hubert/README.md +187 -0
  35. data/fairseq/examples/mr_hubert/config/decode/infer.yaml +30 -0
  36. data/fairseq/examples/mr_hubert/config/decode/infer_lm.yaml +37 -0
  37. data/fairseq/examples/mr_hubert/config/decode/run/submitit_slurm.yaml +17 -0
  38. data/fairseq/examples/mr_hubert/config/decode/run/submitit_slurm_8gpu.yaml +17 -0
  39. data/fairseq/examples/mr_hubert/config/finetune/base_100h.yaml +97 -0
  40. data/fairseq/examples/mr_hubert/config/finetune/base_100h_large.yaml +97 -0
  41. data/fairseq/examples/mr_hubert/config/finetune/base_10h.yaml +101 -0
  42. data/fairseq/examples/mr_hubert/config/finetune/base_10h_large.yaml +101 -0
  43. data/fairseq/examples/mr_hubert/config/finetune/base_1h.yaml +100 -0
  44. data/fairseq/examples/mr_hubert/config/finetune/base_1h_large.yaml +99 -0
  45. data/fairseq/examples/mr_hubert/config/pretrain/mrhubert_base_librispeech.yaml +103 -0
  46. data/fairseq/examples/mr_hubert/config/pretrain/mrhubert_large_librilight.yaml +107 -0
  47. data/fairseq/examples/mr_hubert/config/pretrain/run/submitit_reg.yaml +20 -0
  48. data/fairseq/examples/mr_hubert/decode.sh +46 -0
  49. data/fairseq/examples/mr_hubert/finetune.sh +46 -0
  50. 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
+