English
zarus03 commited on
Commit
a3c8a6a
·
verified ·
1 Parent(s): e88be54

Upload folder using huggingface_hub

Browse files
README.md ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: cc-by-nc-sa-4.0
3
+ language:
4
+ - en
5
+ ---
6
+ # What's New
7
+
8
+ [2024/10] A new [VideoCLIP-XL-v2](https://huggingface.co/alibaba-pai/VideoCLIP-XL-v2) model has been released.
9
+
10
+ [2024/10] Initial commit for the [VideoCLIP-XL](https://huggingface.co/alibaba-pai/VideoCLIP-XL) model, the [VILD](https://huggingface.co/alibaba-pai/VILD) dataset, and the [LVDR](https://huggingface.co/alibaba-pai/LVDR) benchmark.
11
+
12
+ # VideoCLIP-XL (eXtra Length)
13
+
14
+ This model is proposed from [VideoCLIP-XL paper](https://arxiv.org/abs/2410.00741).
15
+ It aims to advance long description understanding for video CLIP Models.
16
+
17
+ # Install
18
+ ~~~
19
+ # 1. Create your environment
20
+ # 2. Install torch
21
+ # 3. Then:
22
+ pip install -r requirements.txt
23
+ ~~~
24
+
25
+ # Usage
26
+ Please refer to ```demo.py```.
27
+
28
+ # Source
29
+ ~~~
30
+ @misc{wang2024videoclipxladvancinglongdescription,
31
+ title={VideoCLIP-XL: Advancing Long Description Understanding for Video CLIP Models},
32
+ author={Jiapeng Wang and Chengyu Wang and Kunzhe Huang and Jun Huang and Lianwen Jin},
33
+ year={2024},
34
+ eprint={2410.00741},
35
+ archivePrefix={arXiv},
36
+ primaryClass={cs.CL},
37
+ url={https://arxiv.org/abs/2410.00741},
38
+ }
39
+ ~~~
VideoCLIP-XL.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4ebcd95db24c21858d7505e296c482617ad8eb82d7352cf99440a17abbb117dd
3
+ size 135
data_dataloaders.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch.utils.data import DataLoader
3
+ from dataloaders.dataloader_msvd_retrieval import MSVD_DataLoader
4
+
5
+
6
+ def dataloader_msvd_train(args, tokenizer):
7
+ msvd_dataset = MSVD_DataLoader(
8
+ subset="train",
9
+ data_path=args.data_path,
10
+ features_path=args.features_path,
11
+ csv_path=args.train_csv,
12
+ max_words=args.max_words,
13
+ feature_framerate=args.feature_framerate,
14
+ tokenizer=tokenizer,
15
+ max_frames=args.max_frames,
16
+ frame_order=args.train_frame_order,
17
+ slice_framepos=args.slice_framepos,
18
+ )
19
+
20
+ train_sampler = torch.utils.data.distributed.DistributedSampler(msvd_dataset)
21
+ dataloader = DataLoader(
22
+ msvd_dataset,
23
+ batch_size=args.batch_size // args.n_gpu,
24
+ num_workers=args.num_thread_reader,
25
+ pin_memory=False,
26
+ shuffle=(train_sampler is None),
27
+ sampler=train_sampler,
28
+ drop_last=True,
29
+ )
30
+
31
+ return dataloader, len(msvd_dataset), train_sampler
32
+
33
+ def dataloader_msvd_test(args, tokenizer, subset="test"):
34
+ msvd_testset = MSVD_DataLoader(
35
+ subset=subset,
36
+ data_path=args.data_path,
37
+ features_path=args.features_path,
38
+ csv_path=args.val_csv,
39
+ max_words=args.max_words,
40
+ feature_framerate=args.feature_framerate,
41
+ tokenizer=tokenizer,
42
+ max_frames=args.max_frames,
43
+ frame_order=args.eval_frame_order,
44
+ slice_framepos=args.slice_framepos,
45
+ )
46
+ dataloader_msrvtt = DataLoader(
47
+ msvd_testset,
48
+ batch_size=args.batch_size_val,
49
+ num_workers=args.num_thread_reader,
50
+ shuffle=False,
51
+ drop_last=False,
52
+ )
53
+ return dataloader_msrvtt, len(msvd_testset)
54
+
55
+
56
+ DATALOADER_DICT = {}
57
+ DATALOADER_DICT["msvd"] = {"train":dataloader_msvd_train, "val":dataloader_msvd_test, "test":dataloader_msvd_test}
dataloader_msvd_retrieval.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import absolute_import
2
+ from __future__ import division
3
+ from __future__ import unicode_literals
4
+ from __future__ import print_function
5
+
6
+ import os
7
+ from torch.utils.data import Dataset
8
+ import numpy as np
9
+ import pickle
10
+ from dataloaders.tubeletvideo_util import TubeletVideoExtractor
11
+
12
+ class MSVD_DataLoader(Dataset):
13
+ """MSVD dataset loader."""
14
+ def __init__(
15
+ self,
16
+ subset,
17
+ data_path,
18
+ features_path,
19
+ csv_path,
20
+ tokenizer,
21
+ max_words=30,
22
+ feature_framerate=1.0,
23
+ max_frames=100,
24
+ image_resolution=224,
25
+ frame_order=0,
26
+ slice_framepos=0,
27
+ ):
28
+ self.data_path = data_path
29
+ self.features_path = features_path
30
+ self.feature_framerate = feature_framerate
31
+ self.max_words = max_words
32
+ self.max_frames = max_frames
33
+ self.tokenizer = tokenizer
34
+ # 0: ordinary order; 1: reverse order; 2: random order.
35
+ self.frame_order = frame_order
36
+ assert self.frame_order in [0, 1, 2]
37
+ # 0: cut from head frames; 1: cut from tail frames; 2: extract frames uniformly.
38
+ self.slice_framepos = slice_framepos
39
+ assert self.slice_framepos in [0, 1, 2]
40
+
41
+ self.subset = subset
42
+ assert self.subset in ["train", "val", "test"]
43
+ video_id_path_dict = {}
44
+ video_id_path_dict["train"] = os.path.join(self.data_path, "train_list.txt")
45
+ video_id_path_dict["val"] = os.path.join(self.data_path, "val_list.txt")
46
+ video_id_path_dict["test"] = os.path.join(self.data_path, "test_list.txt")
47
+ caption_file = os.path.join(self.data_path, "raw-captions.pkl")
48
+
49
+ with open(video_id_path_dict[self.subset], 'r') as fp:
50
+ video_ids = [itm.strip() for itm in fp.readlines()]
51
+
52
+ with open(caption_file, 'rb') as f:
53
+ captions = pickle.load(f)
54
+
55
+ video_dict = {}
56
+ for root, dub_dir, video_files in os.walk(self.features_path):
57
+ for video_file in video_files:
58
+ video_id_ = ".".join(video_file.split(".")[:-1])
59
+ if video_id_ not in video_ids:
60
+ continue
61
+ file_path_ = os.path.join(root, video_file)
62
+ video_dict[video_id_] = file_path_
63
+ self.video_dict = video_dict
64
+
65
+ self.sample_len = 0
66
+ self.sentences_dict = {}
67
+ self.cut_off_points = []
68
+ for video_id in video_ids:
69
+ assert video_id in captions
70
+ for cap in captions[video_id]:
71
+ cap_txt = " ".join(cap)
72
+ self.sentences_dict[len(self.sentences_dict)] = (video_id, cap_txt)
73
+ self.cut_off_points.append(len(self.sentences_dict))
74
+
75
+ ## below variables are used to multi-sentences retrieval
76
+ # self.cut_off_points: used to tag the label when calculate the metric
77
+ # self.sentence_num: used to cut the sentence representation
78
+ # self.video_num: used to cut the video representation
79
+ self.multi_sentence_per_video = True # !!! important tag for eval
80
+ if self.subset == "val" or self.subset == "test":
81
+ self.sentence_num = len(self.sentences_dict)
82
+ self.video_num = len(video_ids)
83
+ assert len(self.cut_off_points) == self.video_num
84
+ print("For {}, sentence number: {}".format(self.subset, self.sentence_num))
85
+ print("For {}, video number: {}".format(self.subset, self.video_num))
86
+
87
+ print("Video number: {}".format(len(self.video_dict)))
88
+ print("Total Paire: {}".format(len(self.sentences_dict)))
89
+
90
+ self.sample_len = len(self.sentences_dict)
91
+ self.rawVideoExtractor = TubeletVideoExtractor(
92
+ csv_path=csv_path,
93
+ framerate=feature_framerate, size=image_resolution)
94
+ self.SPECIAL_TOKEN = {"CLS_TOKEN": "<|startoftext|>", "SEP_TOKEN": "<|endoftext|>",
95
+ "MASK_TOKEN": "[MASK]", "UNK_TOKEN": "[UNK]", "PAD_TOKEN": "[PAD]"}
96
+
97
+ def __len__(self):
98
+ return self.sample_len
99
+
100
+ def _get_text(self, video_id, caption):
101
+ k = 1
102
+ choice_video_ids = [video_id]
103
+ pairs_text = np.zeros((k, self.max_words), dtype=np.long)
104
+ pairs_mask = np.zeros((k, self.max_words), dtype=np.long)
105
+ pairs_segment = np.zeros((k, self.max_words), dtype=np.long)
106
+
107
+ for i, video_id in enumerate(choice_video_ids):
108
+ words = self.tokenizer.tokenize(caption)
109
+
110
+ words = [self.SPECIAL_TOKEN["CLS_TOKEN"]] + words
111
+ total_length_with_CLS = self.max_words - 1
112
+ if len(words) > total_length_with_CLS:
113
+ words = words[:total_length_with_CLS]
114
+ words = words + [self.SPECIAL_TOKEN["SEP_TOKEN"]]
115
+
116
+ input_ids = self.tokenizer.convert_tokens_to_ids(words)
117
+ input_mask = [1] * len(input_ids)
118
+ segment_ids = [0] * len(input_ids)
119
+ while len(input_ids) < self.max_words:
120
+ input_ids.append(0)
121
+ input_mask.append(0)
122
+ segment_ids.append(0)
123
+ assert len(input_ids) == self.max_words
124
+ assert len(input_mask) == self.max_words
125
+ assert len(segment_ids) == self.max_words
126
+
127
+ pairs_text[i] = np.array(input_ids)
128
+ pairs_mask[i] = np.array(input_mask)
129
+ pairs_segment[i] = np.array(segment_ids)
130
+
131
+ return pairs_text, pairs_mask, pairs_segment, choice_video_ids
132
+
133
+ def _get_rawvideo(self, choice_video_ids):
134
+ video_mask = np.zeros((len(choice_video_ids), self.max_frames), dtype=np.long)
135
+ max_video_length = [0] * len(choice_video_ids)
136
+
137
+ # Pair x L x T x 3 x H x W
138
+ video = np.zeros((len(choice_video_ids), self.max_frames, 1, 3,
139
+ self.rawVideoExtractor.size, self.rawVideoExtractor.size), dtype=np.float)
140
+
141
+ for i, video_id in enumerate(choice_video_ids):
142
+ video_path = self.video_dict[video_id]
143
+
144
+ raw_video_data = self.rawVideoExtractor.get_video_data(video_path)
145
+ raw_video_data = raw_video_data['video']
146
+
147
+ if len(raw_video_data.shape) > 3:
148
+ raw_video_data_clip = raw_video_data
149
+ # L x T x 3 x H x W
150
+ raw_video_slice = self.rawVideoExtractor.process_raw_data(raw_video_data_clip)
151
+ if self.max_frames < raw_video_slice.shape[0]:
152
+ if self.slice_framepos == 0:
153
+ video_slice = raw_video_slice[:self.max_frames, ...]
154
+ elif self.slice_framepos == 1:
155
+ video_slice = raw_video_slice[-self.max_frames:, ...]
156
+ else:
157
+ sample_indx = np.linspace(0, raw_video_slice.shape[0] - 1, num=self.max_frames, dtype=int)
158
+ video_slice = raw_video_slice[sample_indx, ...]
159
+ else:
160
+ video_slice = raw_video_slice
161
+
162
+ video_slice = self.rawVideoExtractor.process_frame_order(video_slice, frame_order=self.frame_order)
163
+
164
+ slice_len = video_slice.shape[0]
165
+ max_video_length[i] = max_video_length[i] if max_video_length[i] > slice_len else slice_len
166
+ if slice_len < 1:
167
+ pass
168
+ else:
169
+ video[i][:slice_len, ...] = video_slice
170
+ else:
171
+ print("video path: {} error. video id: {}".format(video_path, video_id))
172
+
173
+ for i, v_length in enumerate(max_video_length):
174
+ video_mask[i][:v_length] = [1] * v_length
175
+
176
+ return video, video_mask
177
+
178
+ def __getitem__(self, idx):
179
+ video_id, caption = self.sentences_dict[idx]
180
+
181
+ pairs_text, pairs_mask, pairs_segment, choice_video_ids = self._get_text(video_id, caption)
182
+ video, video_mask = self._get_rawvideo(choice_video_ids)
183
+ return pairs_text, pairs_mask, pairs_segment, video, video_mask
demo.py ADDED
@@ -0,0 +1,435 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os
3
+ from typing import List
4
+ import cv2
5
+ import numpy as np
6
+ import torch
7
+ from torch import nn
8
+ from PIL import Image
9
+ import torch.nn.functional as F
10
+ from tqdm import tqdm
11
+ import threading
12
+ from torch._utils import ExceptionWrapper
13
+
14
+ from utils.text_encoder.simple_tokenizer import SimpleTokenizer as ClipTokenizer
15
+ from modeling import VideoCLIP_XL
16
+ from utils.text_encoder import text_encoder
17
+ import argparse
18
+ from data_dataloaders import DATALOADER_DICT
19
+
20
+ args_parser = argparse.ArgumentParser()
21
+ args_parser.add_argument("--datatype", type=str, default="msvd", help="dataset name")
22
+ args_parser.add_argument("--local-rank", default=0, type=int, help="distribted training")
23
+
24
+ args = args_parser.parse_args()
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ def get_a_var(obj):
30
+ if isinstance(obj, torch.Tensor):
31
+ return obj
32
+
33
+ if isinstance(obj, list) or isinstance(obj, tuple):
34
+ for result in map(get_a_var, obj):
35
+ if isinstance(result, torch.Tensor):
36
+ return result
37
+ if isinstance(obj, dict):
38
+ for result in map(get_a_var, obj.items()):
39
+ if isinstance(result, torch.Tensor):
40
+ return result
41
+ return None
42
+
43
+
44
+ def parallel_apply(fct, model, inputs, device_ids):
45
+ modules = nn.parallel.replicate(model, device_ids)
46
+ assert len(modules) == len(inputs)
47
+ lock = threading.Lock()
48
+ results = {}
49
+ grad_enabled = torch.is_grad_enabled()
50
+
51
+ def _worker(i, module, input):
52
+ torch.set_grad_enabled(grad_enabled)
53
+ device = get_a_var(input).get_device()
54
+ try:
55
+ with torch.cuda.device(device):
56
+ # this also avoids accidental slicing of `input` if it is a Tensor
57
+ if not isinstance(input, (list, tuple)):
58
+ input = (input,)
59
+ output = fct(module, *input)
60
+ with lock:
61
+ results[i] = output
62
+ except Exception:
63
+ with lock:
64
+ results[i] = ExceptionWrapper(where="in replica {} on device {}".format(i, device))
65
+
66
+ if len(modules) > 1:
67
+ threads = [threading.Thread(target=_worker, args=(i, module, input))
68
+ for i, (module, input) in enumerate(zip(modules, inputs))]
69
+
70
+ for thread in threads:
71
+ thread.start()
72
+ for thread in threads:
73
+ thread.join()
74
+ else:
75
+ _worker(0, modules[0], inputs[0])
76
+
77
+ outputs = []
78
+ for i in range(len(inputs)):
79
+ output = results[i]
80
+ if isinstance(output, ExceptionWrapper):
81
+ output.reraise()
82
+ outputs.append(output)
83
+ return outputs
84
+
85
+
86
+ def parallel_apply(fct, model, inputs, device_ids):
87
+ modules = nn.parallel.replicate(model, device_ids)
88
+ assert len(modules) == len(inputs)
89
+ lock = threading.Lock()
90
+ results = {}
91
+ grad_enabled = torch.is_grad_enabled()
92
+
93
+ def _worker(i, module, input):
94
+ torch.set_grad_enabled(grad_enabled)
95
+ device = get_a_var(input).get_device()
96
+ try:
97
+ with torch.cuda.device(device):
98
+ # this also avoids accidental slicing of `input` if it is a Tensor
99
+ if not isinstance(input, (list, tuple)):
100
+ input = (input,)
101
+ output = fct(module, *input)
102
+ with lock:
103
+ results[i] = output
104
+ except Exception:
105
+ with lock:
106
+ results[i] = ExceptionWrapper(where="in replica {} on device {}".format(i, device))
107
+
108
+ if len(modules) > 1:
109
+ threads = [threading.Thread(target=_worker, args=(i, module, input))
110
+ for i, (module, input) in enumerate(zip(modules, inputs))]
111
+
112
+ for thread in threads:
113
+ thread.start()
114
+ for thread in threads:
115
+ thread.join()
116
+ else:
117
+ _worker(0, modules[0], inputs[0])
118
+
119
+ outputs = []
120
+ for i in range(len(inputs)):
121
+ output = results[i]
122
+ if isinstance(output, ExceptionWrapper):
123
+ output.reraise()
124
+ outputs.append(output)
125
+ return outputs
126
+
127
+
128
+ def tensor_video_to_text_sim(sim_tensor):
129
+ if not torch.is_tensor(sim_tensor):
130
+ sim_tensor = torch.tensor(sim_tensor)
131
+ # Code to avoid nans
132
+ sim_tensor[sim_tensor != sim_tensor] = float('-inf')
133
+ # Forms a similarity matrix for use with rank at k
134
+ values, _ = torch.max(sim_tensor, dim=1, keepdim=True)
135
+ return torch.squeeze(values).T
136
+
137
+
138
+ def tensor_text_to_video_metrics(sim_tensor, top_k = [1,5,10]):
139
+ if not torch.is_tensor(sim_tensor):
140
+ sim_tensor = torch.tensor(sim_tensor)
141
+
142
+ # Permute sim_tensor so it represents a sequence of text-video similarity matrices.
143
+ # Then obtain the double argsort to position the rank on the diagonal
144
+ stacked_sim_matrices = sim_tensor.permute(1, 0, 2)
145
+ first_argsort = torch.argsort(stacked_sim_matrices, dim = -1, descending= True)
146
+ second_argsort = torch.argsort(first_argsort, dim = -1, descending= False)
147
+
148
+ # Extracts ranks i.e diagonals
149
+ ranks = torch.flatten(torch.diagonal(second_argsort, dim1 = 1, dim2 = 2))
150
+
151
+ # Now we need to extract valid ranks, as some belong to inf padding values
152
+ permuted_original_data = torch.flatten(torch.diagonal(sim_tensor, dim1 = 0, dim2 = 2))
153
+ mask = ~ torch.logical_or(torch.isinf(permuted_original_data), torch.isnan(permuted_original_data))
154
+ valid_ranks = ranks[mask]
155
+ # A quick dimension check validates our results, there may be other correctness tests pending
156
+ # Such as dot product localization, but that is for other time.
157
+ #assert int(valid_ranks.shape[0]) == sum([len(text_dict[k]) for k in text_dict])
158
+ if not torch.is_tensor(valid_ranks):
159
+ valid_ranks = torch.tensor(valid_ranks)
160
+ results = {f"R{k}": float(torch.sum(valid_ranks < k) * 100 / len(valid_ranks)) for k in top_k}
161
+ results["MedianR"] = float(torch.median(valid_ranks + 1))
162
+ results["MeanR"] = float(np.mean(valid_ranks.numpy() + 1))
163
+ results["Std_Rank"] = float(np.std(valid_ranks.numpy() + 1))
164
+ results['MR'] = results["MedianR"]
165
+ return results
166
+
167
+
168
+ def _run_on_single_gpu(model, batch_list_t, batch_list_v, batch_sequence_output_list, batch_visual_output_list):
169
+ sim_matrix = []
170
+ for idx1, b1 in enumerate(batch_list_t):
171
+ input_mask, segment_ids, *_tmp = b1
172
+ sequence_output = batch_sequence_output_list[idx1]
173
+ each_row = []
174
+ for idx2, b2 in enumerate(batch_list_v):
175
+ video_mask, *_tmp = b2
176
+ visual_output = batch_visual_output_list[idx2]
177
+ b1b2_logits, *_tmp = model.get_similarity_logits(sequence_output, visual_output, input_mask, video_mask,
178
+ loose_type=model.loose_type)
179
+ b1b2_logits = b1b2_logits.cpu().detach().numpy()
180
+ each_row.append(b1b2_logits)
181
+ each_row = np.concatenate(tuple(each_row), axis=-1)
182
+ sim_matrix.append(each_row)
183
+ return sim_matrix
184
+
185
+
186
+ def compute_metrics(x):
187
+ sx = np.sort(-x, axis=1)
188
+ d = np.diag(-x)
189
+ d = d[:, np.newaxis]
190
+ ind = sx - d
191
+ ind = np.where(ind == 0)
192
+ ind = ind[1]
193
+ metrics = {}
194
+ metrics['R1'] = float(np.sum(ind == 0)) * 100 / len(ind)
195
+ metrics['R5'] = float(np.sum(ind < 5)) * 100 / len(ind)
196
+ metrics['R10'] = float(np.sum(ind < 10)) * 100 / len(ind)
197
+ metrics['MR'] = np.median(ind) + 1
198
+ metrics["MedianR"] = metrics['MR']
199
+ metrics["MeanR"] = np.mean(ind) + 1
200
+ metrics["cols"] = [int(i) for i in list(ind)]
201
+ return metrics
202
+
203
+
204
+ def eval_epoch(args, model, test_dataloader, device, n_gpu):
205
+
206
+ if hasattr(model, 'module'):
207
+ model = model.module.to(device)
208
+ else:
209
+ model = model.to(device)
210
+
211
+ # #################################################################
212
+ ## below variables are used to multi-sentences retrieval
213
+ # multi_sentence_: important tag for eval
214
+ # cut_off_points: used to tag the label when calculate the metric
215
+ # sentence_num: used to cut the sentence representation
216
+ # video_num: used to cut the video representation
217
+ # #################################################################
218
+ multi_sentence_ = False
219
+ cut_off_points_, sentence_num_, video_num_ = [], -1, -1
220
+ if hasattr(test_dataloader.dataset, 'multi_sentence_per_video') \
221
+ and test_dataloader.dataset.multi_sentence_per_video:
222
+ multi_sentence_ = True
223
+ cut_off_points_ = test_dataloader.dataset.cut_off_points
224
+ sentence_num_ = test_dataloader.dataset.sentence_num
225
+ video_num_ = test_dataloader.dataset.video_num
226
+ cut_off_points_ = [itm - 1 for itm in cut_off_points_]
227
+
228
+ if multi_sentence_:
229
+ logger.warning("Eval under the multi-sentence per video clip setting.")
230
+ logger.warning("sentence num: {}, video num: {}".format(sentence_num_, video_num_))
231
+
232
+ model.eval()
233
+ with torch.no_grad():
234
+ batch_list_t = []
235
+ batch_list_v = []
236
+ batch_sequence_output_list, batch_visual_output_list = [], []
237
+ total_video_num = 0
238
+
239
+ # ----------------------------
240
+ # 1. cache the features
241
+ # ----------------------------
242
+ for bid, batch in enumerate(tqdm(test_dataloader)):
243
+ batch = tuple(t.to(device) for t in batch)
244
+ input_ids, input_mask, segment_ids, video, video_mask = batch
245
+
246
+ if multi_sentence_:
247
+ # multi-sentences retrieval means: one clip has two or more descriptions.
248
+ b, *_t = video.shape
249
+ sequence_output = model.get_sequence_output(input_ids, segment_ids, input_mask)
250
+ batch_sequence_output_list.append(sequence_output)
251
+ batch_list_t.append((input_mask, segment_ids,))
252
+
253
+ s_, e_ = total_video_num, total_video_num + b
254
+ filter_inds = [itm - s_ for itm in cut_off_points_ if itm >= s_ and itm < e_]
255
+
256
+ if len(filter_inds) > 0:
257
+ video, video_mask = video[filter_inds, ...], video_mask[filter_inds, ...]
258
+ visual_output = model.get_visual_output(video, video_mask)
259
+ batch_visual_output_list.append(visual_output)
260
+ batch_list_v.append((video_mask,))
261
+ total_video_num += b
262
+ else:
263
+ sequence_output, visual_output = model.get_sequence_visual_output(input_ids, segment_ids, input_mask, video, video_mask)
264
+
265
+ batch_sequence_output_list.append(sequence_output)
266
+ batch_list_t.append((input_mask, segment_ids,))
267
+
268
+ batch_visual_output_list.append(visual_output)
269
+ batch_list_v.append((video_mask,))
270
+
271
+ print("{}/{}\r".format(bid, len(test_dataloader)), end="")
272
+
273
+ # ----------------------------------
274
+ # 2. calculate the similarity
275
+ # ----------------------------------
276
+ if n_gpu > 1:
277
+ device_ids = list(range(n_gpu))
278
+ batch_list_t_splits = []
279
+ batch_list_v_splits = []
280
+ batch_t_output_splits = []
281
+ batch_v_output_splits = []
282
+ bacth_len = len(batch_list_t)
283
+ split_len = (bacth_len + n_gpu - 1) // n_gpu
284
+ for dev_id in device_ids:
285
+ s_, e_ = dev_id * split_len, (dev_id + 1) * split_len
286
+ if dev_id == 0:
287
+ batch_list_t_splits.append(batch_list_t[s_:e_])
288
+ batch_list_v_splits.append(batch_list_v)
289
+
290
+ batch_t_output_splits.append(batch_sequence_output_list[s_:e_])
291
+ batch_v_output_splits.append(batch_visual_output_list)
292
+ else:
293
+ devc = torch.device('cuda:{}'.format(str(dev_id)))
294
+ devc_batch_list = [tuple(t.to(devc) for t in b) for b in batch_list_t[s_:e_]]
295
+ batch_list_t_splits.append(devc_batch_list)
296
+ devc_batch_list = [tuple(t.to(devc) for t in b) for b in batch_list_v]
297
+ batch_list_v_splits.append(devc_batch_list)
298
+
299
+ devc_batch_list = [b.to(devc) for b in batch_sequence_output_list[s_:e_]]
300
+ batch_t_output_splits.append(devc_batch_list)
301
+ devc_batch_list = [b.to(devc) for b in batch_visual_output_list]
302
+ batch_v_output_splits.append(devc_batch_list)
303
+
304
+ parameters_tuple_list = [(batch_list_t_splits[dev_id], batch_list_v_splits[dev_id],
305
+ batch_t_output_splits[dev_id], batch_v_output_splits[dev_id]) for dev_id in device_ids]
306
+ parallel_outputs = parallel_apply(_run_on_single_gpu, model, parameters_tuple_list, device_ids)
307
+ sim_matrix = []
308
+ for idx in range(len(parallel_outputs)):
309
+ sim_matrix += parallel_outputs[idx]
310
+ sim_matrix = np.concatenate(tuple(sim_matrix), axis=0)
311
+ else:
312
+ sim_matrix = _run_on_single_gpu(model, batch_list_t, batch_list_v, batch_sequence_output_list, batch_visual_output_list)
313
+ sim_matrix = np.concatenate(tuple(sim_matrix), axis=0)
314
+
315
+ if multi_sentence_:
316
+ logger.info("before reshape, sim matrix size: {} x {}".format(sim_matrix.shape[0], sim_matrix.shape[1]))
317
+ cut_off_points2len_ = [itm + 1 for itm in cut_off_points_]
318
+ max_length = max([e_-s_ for s_, e_ in zip([0]+cut_off_points2len_[:-1], cut_off_points2len_)])
319
+ sim_matrix_new = []
320
+ for s_, e_ in zip([0] + cut_off_points2len_[:-1], cut_off_points2len_):
321
+ sim_matrix_new.append(np.concatenate((sim_matrix[s_:e_],
322
+ np.full((max_length-e_+s_, sim_matrix.shape[1]), -np.inf)), axis=0))
323
+ sim_matrix = np.stack(tuple(sim_matrix_new), axis=0)
324
+ logger.info("after reshape, sim matrix size: {} x {} x {}".
325
+ format(sim_matrix.shape[0], sim_matrix.shape[1], sim_matrix.shape[2]))
326
+
327
+ tv_metrics = tensor_text_to_video_metrics(sim_matrix)
328
+ vt_metrics = compute_metrics(tensor_video_to_text_sim(sim_matrix))
329
+ else:
330
+ logger.info("sim matrix size: {}, {}".format(sim_matrix.shape[0], sim_matrix.shape[1]))
331
+ tv_metrics = compute_metrics(sim_matrix)
332
+ vt_metrics = compute_metrics(sim_matrix.T)
333
+ logger.info('\t Length-T: {}, Length-V:{}'.format(len(sim_matrix), len(sim_matrix[0])))
334
+
335
+ logger.info("Text-to-Video:")
336
+ logger.info('\t>>> R@1: {:.1f} - R@5: {:.1f} - R@10: {:.1f} - Median R: {:.1f} - Mean R: {:.1f}'.
337
+ format(tv_metrics['R1'], tv_metrics['R5'], tv_metrics['R10'], tv_metrics['MR'], tv_metrics['MeanR']))
338
+ logger.info("Video-to-Text:")
339
+ logger.info('\t>>> V2T$R@1: {:.1f} - V2T$R@5: {:.1f} - V2T$R@10: {:.1f} - V2T$Median R: {:.1f} - V2T$Mean R: {:.1f}'.
340
+ format(vt_metrics['R1'], vt_metrics['R5'], vt_metrics['R10'], vt_metrics['MR'], vt_metrics['MeanR']))
341
+
342
+ R1 = tv_metrics['R1']
343
+ return R1
344
+
345
+
346
+ def _frame_from_video(video):
347
+ while video.isOpened():
348
+ success, frame = video.read()
349
+ if success:
350
+ yield frame
351
+ else:
352
+ break
353
+
354
+
355
+ v_mean = np.array([0.485, 0.456, 0.406]).reshape(1,1,3)
356
+ v_std = np.array([0.229, 0.224, 0.225]).reshape(1,1,3)
357
+ def normalize(data):
358
+ return (data / 255.0 - v_mean) / v_std
359
+
360
+
361
+ def video_preprocessing(video_path, fnum=8):
362
+ video = cv2.VideoCapture(video_path)
363
+ frames = [x for x in _frame_from_video(video)]
364
+ step = len(frames) // fnum
365
+ frames = frames[::step][:fnum]
366
+
367
+ vid_tube = []
368
+ for fr in frames:
369
+ fr = fr[:,:,::-1]
370
+ fr = cv2.resize(fr, (224, 224))
371
+ fr = np.expand_dims(normalize(fr), axis=(0, 1))
372
+ vid_tube.append(fr)
373
+ vid_tube = np.concatenate(vid_tube, axis=1)
374
+ vid_tube = np.transpose(vid_tube, (0, 1, 4, 2, 3))
375
+ vid_tube = torch.from_numpy(vid_tube)
376
+
377
+ return vid_tube
378
+
379
+
380
+ videoclip_xl = VideoCLIP_XL()
381
+ state_dict = torch.load("./VideoCLIP-XL.bin", map_location="cpu")
382
+ videoclip_xl.load_state_dict(state_dict)
383
+ videoclip_xl.cuda().eval()
384
+
385
+
386
+ videos = [
387
+ "/path/to/video-1.mp4",
388
+ "/path/to/video-2.mp4",
389
+ ]
390
+
391
+ texts = [
392
+ "text-1",
393
+ "text-2",
394
+ "text-3",
395
+ ]
396
+
397
+ # with torch.no_grad():
398
+ # video_inputs = torch.cat([video_preprocessing(video) for video in videos], 0).float().cuda()
399
+ # video_features = videoclip_xl.vision_model.get_vid_features(video_inputs).float()
400
+ # video_features = video_features / video_features.norm(dim=-1, keepdim=True)
401
+
402
+ # text_inputs = text_encoder.tokenize(texts, truncate=True).cuda()
403
+ # text_features = videoclip_xl.text_model.encode_text(text_inputs).float()
404
+ # text_features = text_features / text_features.norm(dim=-1, keepdim=True)
405
+
406
+ # Tmp = 100.
407
+
408
+ # sim_matrix = (text_features @ video_features.T) * Tmp
409
+
410
+ # print(f"{type(sim_matrix)=}")
411
+
412
+ # tv_metrics = compute_metrics(sim_matrix)
413
+
414
+ # print("Text-to-Video:")
415
+ # print(f'\t>>> R@1: {tv_metrics['R1']:.1f} - R@5: {tv_metrics['R5']:.1f} - R@10: {tv_metrics['R10']:.1f} - Median R: {tv_metrics['MR']:.1f} - Mean R: {tv_metrics['MeanR']:.1f}')
416
+
417
+
418
+ tokenizer = ClipTokenizer()
419
+
420
+ test_dataloader, test_length = None, 0
421
+ if DATALOADER_DICT[args.datatype]["test"] is not None:
422
+ test_dataloader, test_length = DATALOADER_DICT[args.datatype]["test"](args, tokenizer)
423
+
424
+ if DATALOADER_DICT[args.datatype]["val"] is not None:
425
+ val_dataloader, val_length = DATALOADER_DICT[args.datatype]["val"](args, tokenizer, subset="val")
426
+ else:
427
+ val_dataloader, val_length = test_dataloader, test_length
428
+
429
+
430
+
431
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu", args.local_rank)
432
+ n_gpu = torch.cuda.device_count()
433
+
434
+ if args.local_rank == 0:
435
+ eval_epoch(args, videoclip_xl, test_dataloader, device, n_gpu)
modeling.py ADDED
@@ -0,0 +1,586 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import List
3
+
4
+ import cv2
5
+ import numpy as np
6
+ import torch
7
+ import torch.nn as nn
8
+ import logging
9
+ from PIL import Image
10
+
11
+ from utils.text_encoder import text_encoder
12
+ from utils.vision_encoder import get_vision_encoder
13
+
14
+ from __future__ import absolute_import
15
+ from __future__ import division
16
+ from __future__ import print_function
17
+
18
+ from modules.until_module import PreTrainedModel, AllGather, CrossEn
19
+ from modules.module_cross import CrossModel, CrossConfig, Transformer as TransformerClip
20
+
21
+ from modules.module_clip import CLIP, convert_weights
22
+ from torch.nn.utils.rnn import pad_packed_sequence, pack_padded_sequence
23
+
24
+ logger = logging.getLogger(__name__)
25
+ allgather = AllGather.apply
26
+
27
+ class CLIP4ClipPreTrainedModel(PreTrainedModel, nn.Module):
28
+ """ An abstract class to handle weights initialization and
29
+ a simple interface for dowloading and loading pretrained models.
30
+ """
31
+ def __init__(self, cross_config, *inputs, **kwargs):
32
+ super(CLIP4ClipPreTrainedModel, self).__init__(cross_config)
33
+ self.cross_config = cross_config
34
+ self.clip = None
35
+ self.cross = None
36
+
37
+ @classmethod
38
+ def from_pretrained(cls, cross_model_name, state_dict=None, cache_dir=None, type_vocab_size=2, *inputs, **kwargs):
39
+
40
+ task_config = None
41
+ if "task_config" in kwargs.keys():
42
+ task_config = kwargs["task_config"]
43
+ if not hasattr(task_config, "local_rank"):
44
+ task_config.__dict__["local_rank"] = 0
45
+ elif task_config.local_rank == -1:
46
+ task_config.local_rank = 0
47
+
48
+ if state_dict is None: state_dict = {}
49
+ pretrained_clip_name = "ViT-B/32"
50
+ if hasattr(task_config, 'pretrained_clip_name'):
51
+ pretrained_clip_name = task_config.pretrained_clip_name
52
+ clip_state_dict = CLIP.get_config(pretrained_clip_name=pretrained_clip_name)
53
+ for key, val in clip_state_dict.items():
54
+ new_key = "clip." + key
55
+ if new_key not in state_dict:
56
+ state_dict[new_key] = val.clone()
57
+
58
+ cross_config, _ = CrossConfig.get_config(cross_model_name, cache_dir, type_vocab_size, state_dict=None, task_config=task_config)
59
+
60
+ model = cls(cross_config, clip_state_dict, *inputs, **kwargs)
61
+
62
+ ## ===> Initialization trick [HARD CODE]
63
+ if model.linear_patch == "3d":
64
+ contain_conv2 = False
65
+ for key in state_dict.keys():
66
+ if key.find("visual.conv2.weight") > -1:
67
+ contain_conv2 = True
68
+ break
69
+ if contain_conv2 is False and hasattr(model.clip.visual, "conv2"):
70
+ cp_weight = state_dict["clip.visual.conv1.weight"].clone()
71
+ kernel_size = model.clip.visual.conv2.weight.size(2)
72
+ conv2_size = model.clip.visual.conv2.weight.size()
73
+ conv2_size = list(conv2_size)
74
+
75
+ left_conv2_size = conv2_size.copy()
76
+ right_conv2_size = conv2_size.copy()
77
+ left_conv2_size[2] = (kernel_size - 1) // 2
78
+ right_conv2_size[2] = kernel_size - 1 - left_conv2_size[2]
79
+
80
+ left_zeros, right_zeros = None, None
81
+ if left_conv2_size[2] > 0:
82
+ left_zeros = torch.zeros(*tuple(left_conv2_size), dtype=cp_weight.dtype, device=cp_weight.device)
83
+ if right_conv2_size[2] > 0:
84
+ right_zeros = torch.zeros(*tuple(right_conv2_size), dtype=cp_weight.dtype, device=cp_weight.device)
85
+
86
+ cat_list = []
87
+ if left_zeros != None: cat_list.append(left_zeros)
88
+ cat_list.append(cp_weight.unsqueeze(2))
89
+ if right_zeros != None: cat_list.append(right_zeros)
90
+ cp_weight = torch.cat(cat_list, dim=2)
91
+
92
+ state_dict["clip.visual.conv2.weight"] = cp_weight
93
+
94
+ if model.sim_header == 'tightTransf':
95
+ contain_cross = False
96
+ for key in state_dict.keys():
97
+ if key.find("cross.transformer") > -1:
98
+ contain_cross = True
99
+ break
100
+ if contain_cross is False:
101
+ for key, val in clip_state_dict.items():
102
+ if key == "positional_embedding":
103
+ state_dict["cross.embeddings.position_embeddings.weight"] = val.clone()
104
+ continue
105
+ if key.find("transformer.resblocks") == 0:
106
+ num_layer = int(key.split(".")[2])
107
+
108
+ # cut from beginning
109
+ if num_layer < task_config.cross_num_hidden_layers:
110
+ state_dict["cross."+key] = val.clone()
111
+ continue
112
+
113
+ if model.sim_header == "seqLSTM" or model.sim_header == "seqTransf":
114
+ contain_frame_position = False
115
+ for key in state_dict.keys():
116
+ if key.find("frame_position_embeddings") > -1:
117
+ contain_frame_position = True
118
+ break
119
+ if contain_frame_position is False:
120
+ for key, val in clip_state_dict.items():
121
+ if key == "positional_embedding":
122
+ state_dict["frame_position_embeddings.weight"] = val.clone()
123
+ continue
124
+ if model.sim_header == "seqTransf" and key.find("transformer.resblocks") == 0:
125
+ num_layer = int(key.split(".")[2])
126
+ # cut from beginning
127
+ if num_layer < task_config.cross_num_hidden_layers:
128
+ state_dict[key.replace("transformer.", "transformerClip.")] = val.clone()
129
+ continue
130
+ ## <=== End of initialization trick
131
+
132
+ if state_dict is not None:
133
+ model = cls.init_preweight(model, state_dict, task_config=task_config)
134
+
135
+ return model
136
+
137
+ def show_log(task_config, info):
138
+ if task_config is None or task_config.local_rank == 0:
139
+ logger.warning(info)
140
+
141
+ def update_attr(target_name, target_config, target_attr_name, source_config, source_attr_name, default_value=None):
142
+ if hasattr(source_config, source_attr_name):
143
+ if default_value is None or getattr(source_config, source_attr_name) != default_value:
144
+ setattr(target_config, target_attr_name, getattr(source_config, source_attr_name))
145
+ show_log(source_config, "Set {}.{}: {}.".format(target_name,
146
+ target_attr_name, getattr(target_config, target_attr_name)))
147
+ return target_config
148
+
149
+ def check_attr(target_name, task_config):
150
+ return hasattr(task_config, target_name) and task_config.__dict__[target_name]
151
+
152
+ class CLIP4Clip(CLIP4ClipPreTrainedModel):
153
+ def __init__(self, cross_config, clip_state_dict, task_config):
154
+ super(CLIP4Clip, self).__init__(cross_config)
155
+ self.task_config = task_config
156
+ self.ignore_video_index = -1
157
+
158
+ assert self.task_config.max_words + self.task_config.max_frames <= cross_config.max_position_embeddings
159
+
160
+ self._stage_one = True
161
+ self._stage_two = False
162
+
163
+ show_log(task_config, "Stage-One:{}, Stage-Two:{}".format(self._stage_one, self._stage_two))
164
+
165
+ self.loose_type = False
166
+ if self._stage_one and check_attr('loose_type', self.task_config):
167
+ self.loose_type = True
168
+ show_log(task_config, "Test retrieval by loose type.")
169
+
170
+ # CLIP Encoders: From OpenAI: CLIP [https://github.com/openai/CLIP] ===>
171
+ vit = "visual.proj" in clip_state_dict
172
+ assert vit
173
+ if vit:
174
+ vision_width = clip_state_dict["visual.conv1.weight"].shape[0]
175
+ vision_layers = len(
176
+ [k for k in clip_state_dict.keys() if k.startswith("visual.") and k.endswith(".attn.in_proj_weight")])
177
+ vision_patch_size = clip_state_dict["visual.conv1.weight"].shape[-1]
178
+ grid_size = round((clip_state_dict["visual.positional_embedding"].shape[0] - 1) ** 0.5)
179
+ image_resolution = vision_patch_size * grid_size
180
+ else:
181
+ counts: list = [len(set(k.split(".")[2] for k in clip_state_dict if k.startswith(f"visual.layer{b}"))) for b in
182
+ [1, 2, 3, 4]]
183
+ vision_layers = tuple(counts)
184
+ vision_width = clip_state_dict["visual.layer1.0.conv1.weight"].shape[0]
185
+ output_width = round((clip_state_dict["visual.attnpool.positional_embedding"].shape[0] - 1) ** 0.5)
186
+ vision_patch_size = None
187
+ assert output_width ** 2 + 1 == clip_state_dict["visual.attnpool.positional_embedding"].shape[0]
188
+ image_resolution = output_width * 32
189
+
190
+ embed_dim = clip_state_dict["text_projection"].shape[1]
191
+ context_length = clip_state_dict["positional_embedding"].shape[0]
192
+ vocab_size = clip_state_dict["token_embedding.weight"].shape[0]
193
+ transformer_width = clip_state_dict["ln_final.weight"].shape[0]
194
+ transformer_heads = transformer_width // 64
195
+ transformer_layers = len(set(k.split(".")[2] for k in clip_state_dict if k.startswith(f"transformer.resblocks")))
196
+
197
+ show_log(task_config, "\t embed_dim: {}".format(embed_dim))
198
+ show_log(task_config, "\t image_resolution: {}".format(image_resolution))
199
+ show_log(task_config, "\t vision_layers: {}".format(vision_layers))
200
+ show_log(task_config, "\t vision_width: {}".format(vision_width))
201
+ show_log(task_config, "\t vision_patch_size: {}".format(vision_patch_size))
202
+ show_log(task_config, "\t context_length: {}".format(context_length))
203
+ show_log(task_config, "\t vocab_size: {}".format(vocab_size))
204
+ show_log(task_config, "\t transformer_width: {}".format(transformer_width))
205
+ show_log(task_config, "\t transformer_heads: {}".format(transformer_heads))
206
+ show_log(task_config, "\t transformer_layers: {}".format(transformer_layers))
207
+
208
+ self.linear_patch = '2d'
209
+ if hasattr(task_config, "linear_patch"):
210
+ self.linear_patch = task_config.linear_patch
211
+ show_log(task_config, "\t\t linear_patch: {}".format(self.linear_patch))
212
+
213
+ # use .float() to avoid overflow/underflow from fp16 weight. https://github.com/openai/CLIP/issues/40
214
+ cut_top_layer = 0
215
+ show_log(task_config, "\t cut_top_layer: {}".format(cut_top_layer))
216
+ self.clip = CLIP(
217
+ embed_dim,
218
+ image_resolution, vision_layers-cut_top_layer, vision_width, vision_patch_size,
219
+ context_length, vocab_size, transformer_width, transformer_heads, transformer_layers-cut_top_layer,
220
+ linear_patch=self.linear_patch
221
+ ).float()
222
+
223
+ for key in ["input_resolution", "context_length", "vocab_size"]:
224
+ if key in clip_state_dict:
225
+ del clip_state_dict[key]
226
+
227
+ convert_weights(self.clip)
228
+ # <=== End of CLIP Encoders
229
+
230
+ self.sim_header = 'meanP'
231
+ if hasattr(task_config, "sim_header"):
232
+ self.sim_header = task_config.sim_header
233
+ show_log(task_config, "\t sim_header: {}".format(self.sim_header))
234
+ if self.sim_header == "tightTransf": assert self.loose_type is False
235
+
236
+ cross_config.max_position_embeddings = context_length
237
+ if self.loose_type is False:
238
+ # Cross Encoder ===>
239
+ cross_config = update_attr("cross_config", cross_config, "num_hidden_layers", self.task_config, "cross_num_hidden_layers")
240
+ self.cross = CrossModel(cross_config)
241
+ # <=== End of Cross Encoder
242
+ self.similarity_dense = nn.Linear(cross_config.hidden_size, 1)
243
+
244
+ if self.sim_header == "seqLSTM" or self.sim_header == "seqTransf":
245
+ self.frame_position_embeddings = nn.Embedding(cross_config.max_position_embeddings, cross_config.hidden_size)
246
+ if self.sim_header == "seqTransf":
247
+ self.transformerClip = TransformerClip(width=transformer_width, layers=self.task_config.cross_num_hidden_layers,
248
+ heads=transformer_heads, )
249
+ if self.sim_header == "seqLSTM":
250
+ self.lstm_visual = nn.LSTM(input_size=cross_config.hidden_size, hidden_size=cross_config.hidden_size,
251
+ batch_first=True, bidirectional=False, num_layers=1)
252
+
253
+ self.loss_fct = CrossEn()
254
+
255
+ self.apply(self.init_weights)
256
+
257
+ def forward(self, input_ids, token_type_ids, attention_mask, video, video_mask=None):
258
+ input_ids = input_ids.view(-1, input_ids.shape[-1])
259
+ token_type_ids = token_type_ids.view(-1, token_type_ids.shape[-1])
260
+ attention_mask = attention_mask.view(-1, attention_mask.shape[-1])
261
+ video_mask = video_mask.view(-1, video_mask.shape[-1])
262
+
263
+ # T x 3 x H x W
264
+ video = torch.as_tensor(video).float()
265
+ b, pair, bs, ts, channel, h, w = video.shape
266
+ video = video.view(b * pair * bs * ts, channel, h, w)
267
+ video_frame = bs * ts
268
+
269
+ sequence_output, visual_output = self.get_sequence_visual_output(input_ids, token_type_ids, attention_mask,
270
+ video, video_mask, shaped=True, video_frame=video_frame)
271
+
272
+ if self.training:
273
+ loss = 0.
274
+ sim_matrix, *_tmp = self.get_similarity_logits(sequence_output, visual_output, attention_mask, video_mask,
275
+ shaped=True, loose_type=self.loose_type)
276
+ sim_loss1 = self.loss_fct(sim_matrix)
277
+ sim_loss2 = self.loss_fct(sim_matrix.T)
278
+ sim_loss = (sim_loss1 + sim_loss2) / 2
279
+ loss += sim_loss
280
+
281
+ return loss
282
+ else:
283
+ return None
284
+
285
+ def get_sequence_output(self, input_ids, token_type_ids, attention_mask, shaped=False):
286
+ if shaped is False:
287
+ input_ids = input_ids.view(-1, input_ids.shape[-1])
288
+ token_type_ids = token_type_ids.view(-1, token_type_ids.shape[-1])
289
+ attention_mask = attention_mask.view(-1, attention_mask.shape[-1])
290
+
291
+ bs_pair = input_ids.size(0)
292
+ sequence_hidden = self.clip.encode_text(input_ids).float()
293
+ sequence_hidden = sequence_hidden.view(bs_pair, -1, sequence_hidden.size(-1))
294
+
295
+ return sequence_hidden
296
+
297
+ def get_visual_output(self, video, video_mask, shaped=False, video_frame=-1):
298
+ if shaped is False:
299
+ video_mask = video_mask.view(-1, video_mask.shape[-1])
300
+ video = torch.as_tensor(video).float()
301
+ b, pair, bs, ts, channel, h, w = video.shape
302
+ video = video.view(b * pair * bs * ts, channel, h, w)
303
+ video_frame = bs * ts
304
+
305
+ bs_pair = video_mask.size(0)
306
+ visual_hidden = self.clip.encode_image(video, video_frame=video_frame).float()
307
+ visual_hidden = visual_hidden.view(bs_pair, -1, visual_hidden.size(-1))
308
+
309
+ return visual_hidden
310
+
311
+ def get_sequence_visual_output(self, input_ids, token_type_ids, attention_mask, video, video_mask, shaped=False, video_frame=-1):
312
+ if shaped is False:
313
+ input_ids = input_ids.view(-1, input_ids.shape[-1])
314
+ token_type_ids = token_type_ids.view(-1, token_type_ids.shape[-1])
315
+ attention_mask = attention_mask.view(-1, attention_mask.shape[-1])
316
+ video_mask = video_mask.view(-1, video_mask.shape[-1])
317
+
318
+ video = torch.as_tensor(video).float()
319
+ b, pair, bs, ts, channel, h, w = video.shape
320
+ video = video.view(b * pair * bs * ts, channel, h, w)
321
+ video_frame = bs * ts
322
+
323
+ sequence_output = self.get_sequence_output(input_ids, token_type_ids, attention_mask, shaped=True)
324
+ visual_output = self.get_visual_output(video, video_mask, shaped=True, video_frame=video_frame)
325
+
326
+ return sequence_output, visual_output
327
+
328
+ def _get_cross_output(self, sequence_output, visual_output, attention_mask, video_mask):
329
+
330
+ concat_features = torch.cat((sequence_output, visual_output), dim=1) # concatnate tokens and frames
331
+ concat_mask = torch.cat((attention_mask, video_mask), dim=1)
332
+ text_type_ = torch.zeros_like(attention_mask)
333
+ video_type_ = torch.ones_like(video_mask)
334
+ concat_type = torch.cat((text_type_, video_type_), dim=1)
335
+
336
+ cross_layers, pooled_output = self.cross(concat_features, concat_type, concat_mask, output_all_encoded_layers=True)
337
+ cross_output = cross_layers[-1]
338
+
339
+ return cross_output, pooled_output, concat_mask
340
+
341
+ def _mean_pooling_for_similarity_sequence(self, sequence_output, attention_mask):
342
+ attention_mask_un = attention_mask.to(dtype=torch.float).unsqueeze(-1)
343
+ attention_mask_un[:, 0, :] = 0.
344
+ sequence_output = sequence_output * attention_mask_un
345
+ text_out = torch.sum(sequence_output, dim=1) / torch.sum(attention_mask_un, dim=1, dtype=torch.float)
346
+ return text_out
347
+
348
+ def _mean_pooling_for_similarity_visual(self, visual_output, video_mask,):
349
+ video_mask_un = video_mask.to(dtype=torch.float).unsqueeze(-1)
350
+ visual_output = visual_output * video_mask_un
351
+ video_mask_un_sum = torch.sum(video_mask_un, dim=1, dtype=torch.float)
352
+ video_mask_un_sum[video_mask_un_sum == 0.] = 1.
353
+ video_out = torch.sum(visual_output, dim=1) / video_mask_un_sum
354
+ return video_out
355
+
356
+ def _mean_pooling_for_similarity(self, sequence_output, visual_output, attention_mask, video_mask,):
357
+ text_out = self._mean_pooling_for_similarity_sequence(sequence_output, attention_mask)
358
+ video_out = self._mean_pooling_for_similarity_visual(visual_output, video_mask)
359
+
360
+ return text_out, video_out
361
+
362
+ def _loose_similarity(self, sequence_output, visual_output, attention_mask, video_mask, sim_header="meanP"):
363
+ sequence_output, visual_output = sequence_output.contiguous(), visual_output.contiguous()
364
+
365
+ if sim_header == "meanP":
366
+ # Default: Parameter-free type
367
+ pass
368
+ elif sim_header == "seqLSTM":
369
+ # Sequential type: LSTM
370
+ visual_output_original = visual_output
371
+ visual_output = pack_padded_sequence(visual_output, torch.sum(video_mask, dim=-1).cpu(),
372
+ batch_first=True, enforce_sorted=False)
373
+ visual_output, _ = self.lstm_visual(visual_output)
374
+ if self.training: self.lstm_visual.flatten_parameters()
375
+ visual_output, _ = pad_packed_sequence(visual_output, batch_first=True)
376
+ visual_output = torch.cat((visual_output, visual_output_original[:, visual_output.size(1):, ...].contiguous()), dim=1)
377
+ visual_output = visual_output + visual_output_original
378
+ elif sim_header == "seqTransf":
379
+ # Sequential type: Transformer Encoder
380
+ visual_output_original = visual_output
381
+ seq_length = visual_output.size(1)
382
+ position_ids = torch.arange(seq_length, dtype=torch.long, device=visual_output.device)
383
+ position_ids = position_ids.unsqueeze(0).expand(visual_output.size(0), -1)
384
+ frame_position_embeddings = self.frame_position_embeddings(position_ids)
385
+ visual_output = visual_output + frame_position_embeddings
386
+
387
+ extended_video_mask = (1.0 - video_mask.unsqueeze(1)) * -1000000.0
388
+ extended_video_mask = extended_video_mask.expand(-1, video_mask.size(1), -1)
389
+ visual_output = visual_output.permute(1, 0, 2) # NLD -> LND
390
+ visual_output = self.transformerClip(visual_output, extended_video_mask)
391
+ visual_output = visual_output.permute(1, 0, 2) # LND -> NLD
392
+ visual_output = visual_output + visual_output_original
393
+
394
+ if self.training:
395
+ visual_output = allgather(visual_output, self.task_config)
396
+ video_mask = allgather(video_mask, self.task_config)
397
+ sequence_output = allgather(sequence_output, self.task_config)
398
+ torch.distributed.barrier()
399
+
400
+ visual_output = visual_output / visual_output.norm(dim=-1, keepdim=True)
401
+ visual_output = self._mean_pooling_for_similarity_visual(visual_output, video_mask)
402
+ visual_output = visual_output / visual_output.norm(dim=-1, keepdim=True)
403
+
404
+ sequence_output = sequence_output.squeeze(1)
405
+ sequence_output = sequence_output / sequence_output.norm(dim=-1, keepdim=True)
406
+
407
+ logit_scale = self.clip.logit_scale.exp()
408
+ retrieve_logits = logit_scale * torch.matmul(sequence_output, visual_output.t())
409
+ return retrieve_logits
410
+
411
+ def _cross_similarity(self, sequence_output, visual_output, attention_mask, video_mask):
412
+ sequence_output, visual_output = sequence_output.contiguous(), visual_output.contiguous()
413
+
414
+ b_text, s_text, h_text = sequence_output.size()
415
+ b_visual, s_visual, h_visual = visual_output.size()
416
+
417
+ retrieve_logits_list = []
418
+
419
+ step_size = b_text # set smaller to reduce memory cost
420
+ split_size = [step_size] * (b_text // step_size)
421
+ release_size = b_text - sum(split_size)
422
+ if release_size > 0:
423
+ split_size += [release_size]
424
+
425
+ # due to clip text branch retrun the last hidden
426
+ attention_mask = torch.ones(sequence_output.size(0), 1)\
427
+ .to(device=attention_mask.device, dtype=attention_mask.dtype)
428
+
429
+ sequence_output_splits = torch.split(sequence_output, split_size, dim=0)
430
+ attention_mask_splits = torch.split(attention_mask, split_size, dim=0)
431
+ for i in range(len(split_size)):
432
+ sequence_output_row = sequence_output_splits[i]
433
+ attention_mask_row = attention_mask_splits[i]
434
+ sequence_output_l = sequence_output_row.unsqueeze(1).repeat(1, b_visual, 1, 1)
435
+ sequence_output_l = sequence_output_l.view(-1, s_text, h_text)
436
+ attention_mask_l = attention_mask_row.unsqueeze(1).repeat(1, b_visual, 1)
437
+ attention_mask_l = attention_mask_l.view(-1, s_text)
438
+
439
+ step_truth = sequence_output_row.size(0)
440
+ visual_output_r = visual_output.unsqueeze(0).repeat(step_truth, 1, 1, 1)
441
+ visual_output_r = visual_output_r.view(-1, s_visual, h_visual)
442
+ video_mask_r = video_mask.unsqueeze(0).repeat(step_truth, 1, 1)
443
+ video_mask_r = video_mask_r.view(-1, s_visual)
444
+
445
+ cross_output, pooled_output, concat_mask = \
446
+ self._get_cross_output(sequence_output_l, visual_output_r, attention_mask_l, video_mask_r)
447
+ retrieve_logits_row = self.similarity_dense(pooled_output).squeeze(-1).view(step_truth, b_visual)
448
+
449
+ retrieve_logits_list.append(retrieve_logits_row)
450
+
451
+ retrieve_logits = torch.cat(retrieve_logits_list, dim=0)
452
+ return retrieve_logits
453
+
454
+ def get_similarity_logits(self, sequence_output, visual_output, attention_mask, video_mask, shaped=False, loose_type=False):
455
+ if shaped is False:
456
+ attention_mask = attention_mask.view(-1, attention_mask.shape[-1])
457
+ video_mask = video_mask.view(-1, video_mask.shape[-1])
458
+
459
+ contrastive_direction = ()
460
+ if loose_type:
461
+ assert self.sim_header in ["meanP", "seqLSTM", "seqTransf"]
462
+ retrieve_logits = self._loose_similarity(sequence_output, visual_output, attention_mask, video_mask, sim_header=self.sim_header)
463
+ else:
464
+ assert self.sim_header in ["tightTransf"]
465
+ retrieve_logits = self._cross_similarity(sequence_output, visual_output, attention_mask, video_mask, )
466
+
467
+ return retrieve_logits, contrastive_direction
468
+
469
+
470
+ class VideoCLIP_XL(nn.Module):
471
+ def __init__(self):
472
+ super(VideoCLIP_XL, self).__init__()
473
+ self.text_model = text_encoder.load().float()
474
+ self.vision_model = get_vision_encoder().float()
475
+
476
+ def get_sequence_output(self, input_ids, token_type_ids, attention_mask, shaped=False):
477
+ if not shaped:
478
+ input_ids = input_ids.view(-1, input_ids.shape[-1])
479
+
480
+ # (B, D)
481
+ text_feat = self.text_model.encode_text(input_ids)
482
+
483
+ # match CLIP4Clip shape: (B, 1, D)
484
+ text_feat = text_feat.unsqueeze(1)
485
+
486
+ return text_feat
487
+
488
+ def get_visual_output(self, video, video_mask, shaped=False, video_frame=-1):
489
+ if not shaped:
490
+ video_mask = video_mask.view(-1, video_mask.shape[-1])
491
+
492
+ video = torch.as_tensor(video).float()
493
+ b, pair, bs, ts, c, h, w = video.shape
494
+
495
+ # reshape to (B, C, T, H, W)
496
+ video = video.view(b * pair, bs * ts, c, h, w)
497
+ video = video.permute(0, 2, 1, 3, 4).contiguous()
498
+
499
+ bs_pair = video_mask.size(0)
500
+
501
+ # forward through vision encoder
502
+ visual_feat = self.vision_model.get_vid_features(video)
503
+
504
+ # ---- normalize shape ----
505
+ if visual_feat.dim() == 2:
506
+ # (B, D) → (B, 1, D)
507
+ visual_feat = visual_feat.unsqueeze(1)
508
+
509
+ elif visual_feat.dim() == 3:
510
+ # already (B, T or tokens, D)
511
+ pass
512
+
513
+ else:
514
+ raise ValueError(f"Unexpected vision output shape: {visual_feat.shape}")
515
+
516
+ return visual_feat
517
+
518
+ def get_sequence_visual_output(
519
+ self,
520
+ input_ids,
521
+ token_type_ids,
522
+ attention_mask,
523
+ video,
524
+ video_mask,
525
+ shaped=False,
526
+ video_frame=-1
527
+ ):
528
+ if not shaped:
529
+ input_ids = input_ids.view(-1, input_ids.shape[-1])
530
+ attention_mask = attention_mask.view(-1, attention_mask.shape[-1])
531
+ video_mask = video_mask.view(-1, video_mask.shape[-1])
532
+
533
+ video = torch.as_tensor(video).float()
534
+ b, pair, bs, ts, c, h, w = video.shape
535
+ video = video.view(b * pair * bs * ts, c, h, w)
536
+ video_frame = bs * ts
537
+
538
+ sequence_output = self.get_sequence_output(
539
+ input_ids, token_type_ids, attention_mask, shaped=True
540
+ )
541
+
542
+ visual_output = self.get_visual_output(
543
+ video, video_mask, shaped=True, video_frame=video_frame
544
+ )
545
+
546
+ return sequence_output, visual_output
547
+
548
+ def get_similarity_logits(
549
+ self,
550
+ sequence_output,
551
+ visual_output,
552
+ attention_mask,
553
+ video_mask,
554
+ shaped=False,
555
+ loose_type=False
556
+ ):
557
+ if not shaped:
558
+ attention_mask = attention_mask.view(-1, attention_mask.shape[-1])
559
+ video_mask = video_mask.view(-1, video_mask.shape[-1])
560
+
561
+ # --- normalize frame features ---
562
+ visual_output = visual_output / visual_output.norm(dim=-1, keepdim=True)
563
+
564
+ # mean pooling over frames
565
+ video_mask_un = video_mask.unsqueeze(-1).float()
566
+ visual_output = visual_output * video_mask_un
567
+
568
+ denom = video_mask_un.sum(dim=1)
569
+ denom[denom == 0] = 1.0
570
+
571
+ video_feat = visual_output.sum(dim=1) / denom
572
+ video_feat = video_feat / video_feat.norm(dim=-1, keepdim=True)
573
+
574
+ # --- text ---
575
+ if sequence_output.dim() == 3:
576
+ text_feat = sequence_output[:, 0] # CLS or pooled
577
+ else:
578
+ text_feat = sequence_output
579
+
580
+ text_feat = text_feat / text_feat.norm(dim=-1, keepdim=True)
581
+
582
+ # --- similarity ---
583
+ logit_scale = 1.0 # or learnable like CLIP
584
+ logits = logit_scale * torch.matmul(text_feat, video_feat.t())
585
+
586
+ return logits, ()
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ opencv-python
2
+ ftfy
3
+ regex
4
+ timm
5
+ decord
6
+ einops
utils/__init__.py ADDED
File without changes
utils/text_encoder/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .text_encoder import *
utils/text_encoder/bpe_simple_vocab_16e6.txt.gz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b74bc90eaf4e663f1c0a9a01ffae52a90b2d22af1e733d15b2a1ea049b45ad37
3
+ size 132
utils/text_encoder/model_text_encoder.py ADDED
@@ -0,0 +1,395 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections import OrderedDict
2
+ from typing import Tuple, Union
3
+
4
+ import numpy as np
5
+ import torch
6
+ import torch.nn.functional as F
7
+ from torch import nn
8
+
9
+
10
+ class Bottleneck(nn.Module):
11
+ expansion = 4
12
+
13
+ def __init__(self, inplanes, planes, stride=1):
14
+ super().__init__()
15
+
16
+ # all conv layers have stride 1. an avgpool is performed after the second convolution when stride > 1
17
+ self.conv1 = nn.Conv2d(inplanes, planes, 1, bias=False)
18
+ self.bn1 = nn.BatchNorm2d(planes)
19
+ self.relu1 = nn.ReLU(inplace=True)
20
+
21
+ self.conv2 = nn.Conv2d(planes, planes, 3, padding=1, bias=False)
22
+ self.bn2 = nn.BatchNorm2d(planes)
23
+ self.relu2 = nn.ReLU(inplace=True)
24
+
25
+ self.avgpool = nn.AvgPool2d(stride) if stride > 1 else nn.Identity()
26
+
27
+ self.conv3 = nn.Conv2d(planes, planes * self.expansion, 1, bias=False)
28
+ self.bn3 = nn.BatchNorm2d(planes * self.expansion)
29
+ self.relu3 = nn.ReLU(inplace=True)
30
+
31
+ self.downsample = None
32
+ self.stride = stride
33
+
34
+ if stride > 1 or inplanes != planes * Bottleneck.expansion:
35
+ # downsampling layer is prepended with an avgpool, and the subsequent convolution has stride 1
36
+ self.downsample = nn.Sequential(OrderedDict([
37
+ ("-1", nn.AvgPool2d(stride)),
38
+ ("0", nn.Conv2d(inplanes, planes * self.expansion, 1, stride=1, bias=False)),
39
+ ("1", nn.BatchNorm2d(planes * self.expansion))
40
+ ]))
41
+
42
+ def forward(self, x: torch.Tensor):
43
+ identity = x
44
+
45
+ out = self.relu1(self.bn1(self.conv1(x)))
46
+ out = self.relu2(self.bn2(self.conv2(out)))
47
+ out = self.avgpool(out)
48
+ out = self.bn3(self.conv3(out))
49
+
50
+ if self.downsample is not None:
51
+ identity = self.downsample(x)
52
+
53
+ out += identity
54
+ out = self.relu3(out)
55
+ return out
56
+
57
+
58
+ class AttentionPool2d(nn.Module):
59
+ def __init__(self, spacial_dim: int, embed_dim: int, num_heads: int, output_dim: int = None):
60
+ super().__init__()
61
+ self.positional_embedding = nn.Parameter(torch.randn(spacial_dim ** 2 + 1, embed_dim) / embed_dim ** 0.5)
62
+ self.k_proj = nn.Linear(embed_dim, embed_dim)
63
+ self.q_proj = nn.Linear(embed_dim, embed_dim)
64
+ self.v_proj = nn.Linear(embed_dim, embed_dim)
65
+ self.c_proj = nn.Linear(embed_dim, output_dim or embed_dim)
66
+ self.num_heads = num_heads
67
+
68
+ def forward(self, x):
69
+ x = x.flatten(start_dim=2).permute(2, 0, 1) # NCHW -> (HW)NC
70
+ x = torch.cat([x.mean(dim=0, keepdim=True), x], dim=0) # (HW+1)NC
71
+ x = x + self.positional_embedding[:, None, :].to(x.dtype) # (HW+1)NC
72
+ x, _ = F.multi_head_attention_forward(
73
+ query=x[:1], key=x, value=x,
74
+ embed_dim_to_check=x.shape[-1],
75
+ num_heads=self.num_heads,
76
+ q_proj_weight=self.q_proj.weight,
77
+ k_proj_weight=self.k_proj.weight,
78
+ v_proj_weight=self.v_proj.weight,
79
+ in_proj_weight=None,
80
+ in_proj_bias=torch.cat([self.q_proj.bias, self.k_proj.bias, self.v_proj.bias]),
81
+ bias_k=None,
82
+ bias_v=None,
83
+ add_zero_attn=False,
84
+ dropout_p=0,
85
+ out_proj_weight=self.c_proj.weight,
86
+ out_proj_bias=self.c_proj.bias,
87
+ use_separate_proj_weight=True,
88
+ training=self.training,
89
+ need_weights=False
90
+ )
91
+ return x.squeeze(0)
92
+
93
+
94
+ class ModifiedResNet(nn.Module):
95
+ """
96
+ A ResNet class that is similar to torchvision's but contains the following changes:
97
+ - There are now 3 "stem" convolutions as opposed to 1, with an average pool instead of a max pool.
98
+ - Performs anti-aliasing strided convolutions, where an avgpool is prepended to convolutions with stride > 1
99
+ - The final pooling layer is a QKV attention instead of an average pool
100
+ """
101
+
102
+ def __init__(self, layers, output_dim, heads, input_resolution=224, width=64):
103
+ super().__init__()
104
+ self.output_dim = output_dim
105
+ self.input_resolution = input_resolution
106
+
107
+ # the 3-layer stem
108
+ self.conv1 = nn.Conv2d(3, width // 2, kernel_size=3, stride=2, padding=1, bias=False)
109
+ self.bn1 = nn.BatchNorm2d(width // 2)
110
+ self.relu1 = nn.ReLU(inplace=True)
111
+ self.conv2 = nn.Conv2d(width // 2, width // 2, kernel_size=3, padding=1, bias=False)
112
+ self.bn2 = nn.BatchNorm2d(width // 2)
113
+ self.relu2 = nn.ReLU(inplace=True)
114
+ self.conv3 = nn.Conv2d(width // 2, width, kernel_size=3, padding=1, bias=False)
115
+ self.bn3 = nn.BatchNorm2d(width)
116
+ self.relu3 = nn.ReLU(inplace=True)
117
+ self.avgpool = nn.AvgPool2d(2)
118
+
119
+ # residual layers
120
+ self._inplanes = width # this is a *mutable* variable used during construction
121
+ self.layer1 = self._make_layer(width, layers[0])
122
+ self.layer2 = self._make_layer(width * 2, layers[1], stride=2)
123
+ self.layer3 = self._make_layer(width * 4, layers[2], stride=2)
124
+ self.layer4 = self._make_layer(width * 8, layers[3], stride=2)
125
+
126
+ embed_dim = width * 32 # the ResNet feature dimension
127
+ self.attnpool = AttentionPool2d(input_resolution // 32, embed_dim, heads, output_dim)
128
+
129
+ def _make_layer(self, planes, blocks, stride=1):
130
+ layers = [Bottleneck(self._inplanes, planes, stride)]
131
+
132
+ self._inplanes = planes * Bottleneck.expansion
133
+ for _ in range(1, blocks):
134
+ layers.append(Bottleneck(self._inplanes, planes))
135
+
136
+ return nn.Sequential(*layers)
137
+
138
+ def forward(self, x):
139
+ def stem(x):
140
+ x = self.relu1(self.bn1(self.conv1(x)))
141
+ x = self.relu2(self.bn2(self.conv2(x)))
142
+ x = self.relu3(self.bn3(self.conv3(x)))
143
+ x = self.avgpool(x)
144
+ return x
145
+
146
+ x = x.type(self.conv1.weight.dtype)
147
+ x = stem(x)
148
+ x = self.layer1(x)
149
+ x = self.layer2(x)
150
+ x = self.layer3(x)
151
+ x = self.layer4(x)
152
+ x = self.attnpool(x)
153
+
154
+ return x
155
+
156
+
157
+ class LayerNorm(nn.LayerNorm):
158
+ """Subclass torch's LayerNorm to handle fp16."""
159
+
160
+ def forward(self, x: torch.Tensor):
161
+ orig_type = x.dtype
162
+ ret = super().forward(x.type(torch.float32))
163
+ return ret.type(orig_type)
164
+
165
+
166
+ class QuickGELU(nn.Module):
167
+ def forward(self, x: torch.Tensor):
168
+ return x * torch.sigmoid(1.702 * x)
169
+
170
+
171
+ class ResidualAttentionBlock(nn.Module):
172
+ def __init__(self, d_model: int, n_head: int, attn_mask: torch.Tensor = None):
173
+ super().__init__()
174
+
175
+ self.attn = nn.MultiheadAttention(d_model, n_head)
176
+ self.ln_1 = LayerNorm(d_model)
177
+ self.mlp = nn.Sequential(OrderedDict([
178
+ ("c_fc", nn.Linear(d_model, d_model * 4)),
179
+ ("gelu", QuickGELU()),
180
+ ("c_proj", nn.Linear(d_model * 4, d_model))
181
+ ]))
182
+ self.ln_2 = LayerNorm(d_model)
183
+ self.attn_mask = attn_mask
184
+
185
+ def attention(self, x: torch.Tensor):
186
+ self.attn_mask = self.attn_mask.to(dtype=x.dtype, device=x.device) if self.attn_mask is not None else None
187
+ return self.attn(x, x, x, need_weights=False, attn_mask=self.attn_mask)[0]
188
+
189
+ def forward(self, x: torch.Tensor):
190
+ x = x + self.attention(self.ln_1(x))
191
+ x = x + self.mlp(self.ln_2(x))
192
+ return x
193
+
194
+
195
+ class Transformer(nn.Module):
196
+ def __init__(self, width: int, layers: int, heads: int, attn_mask: torch.Tensor = None):
197
+ super().__init__()
198
+ self.width = width
199
+ self.layers = layers
200
+ self.resblocks = nn.Sequential(*[ResidualAttentionBlock(width, heads, attn_mask) for _ in range(layers)])
201
+
202
+ def forward(self, x: torch.Tensor):
203
+ return self.resblocks(x)
204
+
205
+
206
+ class VisionTransformer(nn.Module):
207
+ def __init__(self, input_resolution: int, patch_size: int, width: int, layers: int, heads: int, output_dim: int):
208
+ super().__init__()
209
+ self.input_resolution = input_resolution
210
+ self.output_dim = output_dim
211
+ self.conv1 = nn.Conv2d(in_channels=3, out_channels=width, kernel_size=patch_size, stride=patch_size, bias=False)
212
+
213
+ scale = width ** -0.5
214
+ self.class_embedding = nn.Parameter(scale * torch.randn(width))
215
+ self.positional_embedding = nn.Parameter(scale * torch.randn((input_resolution // patch_size) ** 2 + 1, width))
216
+ self.ln_pre = LayerNorm(width)
217
+
218
+ self.transformer = Transformer(width, layers, heads)
219
+
220
+ self.ln_post = LayerNorm(width)
221
+ self.proj = nn.Parameter(scale * torch.randn(width, output_dim))
222
+
223
+ def forward(self, x: torch.Tensor):
224
+ x = self.conv1(x) # shape = [*, width, grid, grid]
225
+ x = x.reshape(x.shape[0], x.shape[1], -1) # shape = [*, width, grid ** 2]
226
+ x = x.permute(0, 2, 1) # shape = [*, grid ** 2, width]
227
+ x = torch.cat([self.class_embedding.to(x.dtype) + torch.zeros(x.shape[0], 1, x.shape[-1], dtype=x.dtype, device=x.device), x], dim=1) # shape = [*, grid ** 2 + 1, width]
228
+ x = x + self.positional_embedding.to(x.dtype)
229
+ x = self.ln_pre(x)
230
+
231
+ x = x.permute(1, 0, 2) # NLD -> LND
232
+ x = self.transformer(x)
233
+ x = x.permute(1, 0, 2) # LND -> NLD
234
+
235
+ x = self.ln_post(x[:, 0, :])
236
+
237
+ if self.proj is not None:
238
+ x = x @ self.proj
239
+
240
+ return x
241
+
242
+
243
+ class CLIP(nn.Module):
244
+ def __init__(self,
245
+ embed_dim: int,
246
+ # vision
247
+ image_resolution: int,
248
+ vision_layers: Union[Tuple[int, int, int, int], int],
249
+ vision_width: int,
250
+ vision_patch_size: int,
251
+ # text
252
+ context_length: int,
253
+ vocab_size: int,
254
+ transformer_width: int,
255
+ transformer_heads: int,
256
+ transformer_layers: int,
257
+ load_from_clip: bool
258
+ ):
259
+ super().__init__()
260
+
261
+ self.context_length = 248
262
+
263
+ self.transformer = Transformer(
264
+ width=transformer_width,
265
+ layers=transformer_layers,
266
+ heads=transformer_heads,
267
+ attn_mask=self.build_attention_mask()
268
+ )
269
+
270
+ self.vocab_size = vocab_size
271
+ self.token_embedding = nn.Embedding(vocab_size, transformer_width)
272
+
273
+ if load_from_clip == False:
274
+ self.positional_embedding = nn.Parameter(torch.empty(248, transformer_width))
275
+ self.positional_embedding_res = nn.Parameter(torch.empty(248, transformer_width))
276
+
277
+ else:
278
+ self.positional_embedding = nn.Parameter(torch.empty(77, transformer_width))
279
+
280
+ self.ln_final = LayerNorm(transformer_width)
281
+
282
+ self.text_projection = nn.Parameter(torch.empty(transformer_width, embed_dim))
283
+ self.logit_scale = nn.Parameter(torch.ones([]) * np.log(1 / 0.07))
284
+
285
+ self.initialize_parameters()
286
+ self.mask1 = torch.zeros([248, 1])
287
+ self.mask1[:20, :] = 1
288
+ self.mask2 = torch.zeros([248, 1])
289
+ self.mask2[20:, :] = 1
290
+
291
+
292
+ def initialize_parameters(self):
293
+ nn.init.normal_(self.token_embedding.weight, std=0.02)
294
+ nn.init.normal_(self.positional_embedding, std=0.01)
295
+
296
+ proj_std = (self.transformer.width ** -0.5) * ((2 * self.transformer.layers) ** -0.5)
297
+ attn_std = self.transformer.width ** -0.5
298
+ fc_std = (2 * self.transformer.width) ** -0.5
299
+ for block in self.transformer.resblocks:
300
+ nn.init.normal_(block.attn.in_proj_weight, std=attn_std)
301
+ nn.init.normal_(block.attn.out_proj.weight, std=proj_std)
302
+ nn.init.normal_(block.mlp.c_fc.weight, std=fc_std)
303
+ nn.init.normal_(block.mlp.c_proj.weight, std=proj_std)
304
+
305
+ if self.text_projection is not None:
306
+ nn.init.normal_(self.text_projection, std=self.transformer.width ** -0.5)
307
+
308
+ def build_attention_mask(self):
309
+ # lazily create causal attention mask, with full attention between the vision tokens
310
+ # pytorch uses additive attention mask; fill with -inf
311
+ mask = torch.empty(self.context_length, self.context_length)
312
+ mask.fill_(float("-inf"))
313
+ mask.triu_(1) # zero out the lower diagonal
314
+ return mask
315
+
316
+ @property
317
+ def dtype(self):
318
+ return self.token_embedding.weight.dtype
319
+
320
+ def encode_text(self, text):
321
+ x = self.token_embedding(text).type(self.dtype) # [batch_size, n_ctx, d_model]
322
+
323
+ x = x + (self.positional_embedding.to(x.device) * self.mask1.to(x.device)).type(self.dtype).to(x.device) + (self.positional_embedding_res.to(x.device) * self.mask2.to(x.device)).type(self.dtype).to(x.device)
324
+
325
+ x = x.permute(1, 0, 2) # NLD -> LND
326
+ x = self.transformer(x)
327
+ x = x.permute(1, 0, 2) # LND -> NLD
328
+ x = self.ln_final(x).type(self.dtype)
329
+
330
+ # x.shape = [batch_size, n_ctx, transformer.width]
331
+ # take features from the eot embedding (eot_token is the highest number in each sequence)
332
+ x = x[torch.arange(x.shape[0]), text.argmax(dim=-1)] @ self.text_projection
333
+
334
+ return x
335
+
336
+ def encode_text_full(self, text):
337
+ x = self.token_embedding(text).type(self.dtype) # [batch_size, n_ctx, d_model]
338
+
339
+ x = x + (self.positional_embedding.to(x.device) * self.mask1.to(x.device)).type(self.dtype).to(x.device) + (self.positional_embedding_res.to(x.device) * self.mask2.to(x.device)).type(self.dtype).to(x.device)
340
+
341
+ x = x.permute(1, 0, 2) # NLD -> LND
342
+ x = self.transformer(x)
343
+ x = x.permute(1, 0, 2) # LND -> NLD
344
+ x = self.ln_final(x).type(self.dtype)
345
+
346
+ return x
347
+
348
+
349
+ def convert_weights(model: nn.Module):
350
+ """Convert applicable model parameters to fp16"""
351
+
352
+ def _convert_weights_to_fp16(l):
353
+ if isinstance(l, (nn.Conv1d, nn.Conv2d, nn.Linear)):
354
+ l.weight.data = l.weight.data.half()
355
+ if l.bias is not None:
356
+ l.bias.data = l.bias.data.half()
357
+
358
+ if isinstance(l, nn.MultiheadAttention):
359
+ for attr in [*[f"{s}_proj_weight" for s in ["in", "q", "k", "v"]], "in_proj_bias", "bias_k", "bias_v"]:
360
+ tensor = getattr(l, attr)
361
+ if tensor is not None:
362
+ tensor.data = tensor.data.half()
363
+
364
+ for name in ["text_projection", "proj"]:
365
+ if hasattr(l, name):
366
+ attr = getattr(l, name)
367
+ if attr is not None:
368
+ attr.data = attr.data.half()
369
+
370
+ model.apply(_convert_weights_to_fp16)
371
+
372
+
373
+ def build_model(load_from_clip: bool):
374
+
375
+ vision_width = 1024
376
+ vision_layers = 24
377
+ vision_patch_size = 14
378
+ grid_size = 16
379
+ image_resolution = 224
380
+
381
+ embed_dim = 768
382
+ context_length = 248
383
+ vocab_size = 49408
384
+ transformer_width = 768
385
+ transformer_heads = 12
386
+ transformer_layers = 12
387
+
388
+ model = CLIP(
389
+ embed_dim,
390
+ image_resolution, vision_layers, vision_width, vision_patch_size,
391
+ context_length, vocab_size, transformer_width, transformer_heads, transformer_layers, load_from_clip
392
+ )
393
+
394
+ convert_weights(model)
395
+ return model.eval()
utils/text_encoder/simple_tokenizer.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gzip
2
+ import html
3
+ import os
4
+ from functools import lru_cache
5
+
6
+ import ftfy
7
+ import regex as re
8
+
9
+
10
+ @lru_cache()
11
+ def default_bpe():
12
+ return os.path.join(os.path.dirname(os.path.abspath(__file__)), "bpe_simple_vocab_16e6.txt.gz")
13
+
14
+
15
+ @lru_cache()
16
+ def bytes_to_unicode():
17
+ """
18
+ Returns list of utf-8 byte and a corresponding list of unicode strings.
19
+ The reversible bpe codes work on unicode strings.
20
+ This means you need a large # of unicode characters in your vocab if you want to avoid UNKs.
21
+ When you're at something like a 10B token dataset you end up needing around 5K for decent coverage.
22
+ This is a signficant percentage of your normal, say, 32K bpe vocab.
23
+ To avoid that, we want lookup tables between utf-8 bytes and unicode strings.
24
+ And avoids mapping to whitespace/control characters the bpe code barfs on.
25
+ """
26
+ bs = list(range(ord("!"), ord("~")+1))+list(range(ord("¡"), ord("¬")+1))+list(range(ord("®"), ord("ÿ")+1))
27
+ cs = bs[:]
28
+ n = 0
29
+ for b in range(2**8):
30
+ if b not in bs:
31
+ bs.append(b)
32
+ cs.append(2**8+n)
33
+ n += 1
34
+ cs = [chr(n) for n in cs]
35
+ return dict(zip(bs, cs))
36
+
37
+
38
+ def get_pairs(word):
39
+ """Return set of symbol pairs in a word.
40
+ Word is represented as tuple of symbols (symbols being variable-length strings).
41
+ """
42
+ pairs = set()
43
+ prev_char = word[0]
44
+ for char in word[1:]:
45
+ pairs.add((prev_char, char))
46
+ prev_char = char
47
+ return pairs
48
+
49
+
50
+ def basic_clean(text):
51
+ text = ftfy.fix_text(text)
52
+ text = html.unescape(html.unescape(text))
53
+ return text.strip()
54
+
55
+
56
+ def whitespace_clean(text):
57
+ text = re.sub(r'\s+', ' ', text)
58
+ text = text.strip()
59
+ return text
60
+
61
+
62
+ class SimpleTokenizer(object):
63
+ def __init__(self, bpe_path: str = default_bpe()):
64
+ self.byte_encoder = bytes_to_unicode()
65
+ self.byte_decoder = {v: k for k, v in self.byte_encoder.items()}
66
+ merges = gzip.open(bpe_path).read().decode("utf-8").split('\n')
67
+ merges = merges[1:49152-256-2+1]
68
+ merges = [tuple(merge.split()) for merge in merges]
69
+ vocab = list(bytes_to_unicode().values())
70
+ vocab = vocab + [v+'</w>' for v in vocab]
71
+ for merge in merges:
72
+ vocab.append(''.join(merge))
73
+ vocab.extend(['<|startoftext|>', '<|endoftext|>'])
74
+ self.encoder = dict(zip(vocab, range(len(vocab))))
75
+ self.decoder = {v: k for k, v in self.encoder.items()}
76
+ self.bpe_ranks = dict(zip(merges, range(len(merges))))
77
+ self.cache = {'<|startoftext|>': '<|startoftext|>', '<|endoftext|>': '<|endoftext|>'}
78
+ self.pat = re.compile(r"""<\|startoftext\|>|<\|endoftext\|>|'s|'t|'re|'ve|'m|'ll|'d|[\p{L}]+|[\p{N}]|[^\s\p{L}\p{N}]+""", re.IGNORECASE)
79
+
80
+ def bpe(self, token):
81
+ if token in self.cache:
82
+ return self.cache[token]
83
+ word = tuple(token[:-1]) + ( token[-1] + '</w>',)
84
+ pairs = get_pairs(word)
85
+
86
+ if not pairs:
87
+ return token+'</w>'
88
+
89
+ while True:
90
+ bigram = min(pairs, key = lambda pair: self.bpe_ranks.get(pair, float('inf')))
91
+ if bigram not in self.bpe_ranks:
92
+ break
93
+ first, second = bigram
94
+ new_word = []
95
+ i = 0
96
+ while i < len(word):
97
+ try:
98
+ j = word.index(first, i)
99
+ new_word.extend(word[i:j])
100
+ i = j
101
+ except:
102
+ new_word.extend(word[i:])
103
+ break
104
+
105
+ if word[i] == first and i < len(word)-1 and word[i+1] == second:
106
+ new_word.append(first+second)
107
+ i += 2
108
+ else:
109
+ new_word.append(word[i])
110
+ i += 1
111
+ new_word = tuple(new_word)
112
+ word = new_word
113
+ if len(word) == 1:
114
+ break
115
+ else:
116
+ pairs = get_pairs(word)
117
+ word = ' '.join(word)
118
+ self.cache[token] = word
119
+ return word
120
+
121
+ def encode(self, text):
122
+ bpe_tokens = []
123
+ text = whitespace_clean(basic_clean(text)).lower()
124
+ for token in re.findall(self.pat, text):
125
+ token = ''.join(self.byte_encoder[b] for b in token.encode('utf-8'))
126
+ bpe_tokens.extend(self.encoder[bpe_token] for bpe_token in self.bpe(token).split(' '))
127
+ return bpe_tokens
128
+
129
+ def decode(self, tokens):
130
+ text = ''.join([self.decoder[token] for token in tokens])
131
+ text = bytearray([self.byte_decoder[c] for c in text]).decode('utf-8', errors="replace").replace('</w>', ' ')
132
+ return text
utils/text_encoder/text_encoder.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib
2
+ import os
3
+ import urllib
4
+ import warnings
5
+ from typing import Any, Union, List
6
+ from pkg_resources import packaging
7
+ from torch import nn
8
+ import torch
9
+ from PIL import Image
10
+ from torchvision.transforms import Compose, Resize, CenterCrop, ToTensor, Normalize
11
+
12
+ from .model_text_encoder import build_model
13
+ from .simple_tokenizer import SimpleTokenizer as _Tokenizer
14
+
15
+ try:
16
+ from torchvision.transforms import InterpolationMode
17
+ BICUBIC = InterpolationMode.BICUBIC
18
+ except ImportError:
19
+ BICUBIC = Image.BICUBIC
20
+
21
+
22
+ _tokenizer = _Tokenizer()
23
+
24
+
25
+ def _convert_image_to_rgb(image):
26
+ return image.convert("RGB")
27
+
28
+
29
+ def load():
30
+ model = build_model(load_from_clip = False)
31
+
32
+ return model
33
+
34
+
35
+ def tokenize(texts: Union[str, List[str]], context_length: int = 77*4-60, truncate: bool = False) -> Union[torch.IntTensor, torch.LongTensor]:
36
+ """
37
+ Returns the tokenized representation of given input string(s)
38
+
39
+ Parameters
40
+ ----------
41
+ texts : Union[str, List[str]]
42
+ An input string or a list of input strings to tokenize
43
+
44
+ context_length : int
45
+ The context length to use; all CLIP models use 77 as the context length
46
+
47
+ truncate: bool
48
+ Whether to truncate the text in case its encoding is longer than the context length
49
+
50
+ Returns
51
+ -------
52
+ A two-dimensional tensor containing the resulting tokens, shape = [number of input strings, context_length].
53
+ We return LongTensor when torch version is <1.8.0, since older index_select requires indices to be long.
54
+ """
55
+ if isinstance(texts, str):
56
+ texts = [texts]
57
+
58
+ sot_token = _tokenizer.encoder["<|startoftext|>"]
59
+ eot_token = _tokenizer.encoder["<|endoftext|>"]
60
+ all_tokens = [[sot_token] + _tokenizer.encode(text) + [eot_token] for text in texts]
61
+ if packaging.version.parse(torch.__version__) < packaging.version.parse("1.8.0"):
62
+ result = torch.zeros(len(all_tokens), context_length, dtype=torch.long)
63
+ else:
64
+ result = torch.zeros(len(all_tokens), context_length, dtype=torch.int)
65
+
66
+ for i, tokens in enumerate(all_tokens):
67
+ if len(tokens) > context_length:
68
+ if truncate:
69
+ tokens = tokens[:context_length]
70
+ tokens[-1] = eot_token
71
+ else:
72
+ raise RuntimeError(f"Input {texts[i]} is too long for context length {context_length}")
73
+ result[i, :len(tokens)] = torch.tensor(tokens)
74
+
75
+ return result
utils/vision_encoder/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import numpy as np
3
+ import cv2
4
+ import os
5
+
6
+ from .model_vision_encoder import VisionEncoder
7
+
8
+ def get_vision_encoder():
9
+ vision_encoder = VisionEncoder()
10
+
11
+ return vision_encoder
utils/vision_encoder/clip_vision.py ADDED
@@ -0,0 +1,327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ import os
3
+ import logging
4
+ from collections import OrderedDict
5
+
6
+ import torch
7
+ from torch import nn
8
+ from einops import rearrange
9
+ from timm.models.layers import DropPath
10
+ from timm.models.registry import register_model
11
+
12
+ import torch.utils.checkpoint as checkpoint
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ def load_temp_embed_with_mismatch(temp_embed_old, temp_embed_new, add_zero=True):
17
+ """
18
+ Add/Remove extra temporal_embeddings as needed.
19
+ https://arxiv.org/abs/2104.00650 shows adding zero paddings works.
20
+
21
+ temp_embed_old: (1, num_frames_old, 1, d)
22
+ temp_embed_new: (1, num_frames_new, 1, d)
23
+ add_zero: bool, if True, add zero, else, interpolate trained embeddings.
24
+ """
25
+ # TODO zero pad
26
+ num_frms_new = temp_embed_new.shape[1]
27
+ num_frms_old = temp_embed_old.shape[1]
28
+ logger.info(f"Load temporal_embeddings, lengths: {num_frms_old}-->{num_frms_new}")
29
+ if num_frms_new > num_frms_old:
30
+ if add_zero:
31
+ temp_embed_new[
32
+ :, :num_frms_old
33
+ ] = temp_embed_old # untrained embeddings are zeros.
34
+ else:
35
+ temp_embed_new = interpolate_temporal_pos_embed(temp_embed_old, num_frms_new)
36
+ elif num_frms_new < num_frms_old:
37
+ temp_embed_new = temp_embed_old[:, :num_frms_new]
38
+ else: # =
39
+ temp_embed_new = temp_embed_old
40
+ return temp_embed_new
41
+
42
+
43
+ class QuickGELU(nn.Module):
44
+ def forward(self, x):
45
+ return x * torch.sigmoid(1.702 * x)
46
+
47
+
48
+ class ResidualAttentionBlock(nn.Module):
49
+ def __init__(self, d_model, n_head, drop_path=0., attn_mask=None, dropout=0.):
50
+ super().__init__()
51
+
52
+ self.drop_path1 = DropPath(drop_path) if drop_path > 0. else nn.Identity()
53
+ self.drop_path2 = DropPath(drop_path) if drop_path > 0. else nn.Identity()
54
+ # logger.info(f'Droppath: {drop_path}')
55
+ self.attn = nn.MultiheadAttention(d_model, n_head, dropout=dropout)
56
+ self.ln_1 = nn.LayerNorm(d_model)
57
+ self.mlp = nn.Sequential(OrderedDict([
58
+ ("c_fc", nn.Linear(d_model, d_model * 4)),
59
+ ("gelu", QuickGELU()),
60
+ ("drop1", nn.Dropout(dropout)),
61
+ ("c_proj", nn.Linear(d_model * 4, d_model)),
62
+ ("drop2", nn.Dropout(dropout)),
63
+ ]))
64
+ self.ln_2 = nn.LayerNorm(d_model)
65
+ self.attn_mask = attn_mask
66
+
67
+ def attention(self, x):
68
+ self.attn_mask = self.attn_mask.to(dtype=x.dtype, device=x.device) if self.attn_mask is not None else None
69
+ return self.attn(x, x, x, need_weights=False, attn_mask=self.attn_mask)[0]
70
+
71
+ def forward(self, x):
72
+ x = x + self.drop_path1(self.attention(self.ln_1(x)))
73
+ x = x + self.drop_path2(self.mlp(self.ln_2(x)))
74
+ return x
75
+
76
+
77
+ class Transformer(nn.Module):
78
+ def __init__(self, width, layers, heads, drop_path=0., checkpoint_num=0, dropout=0.):
79
+ super().__init__()
80
+ dpr = [x.item() for x in torch.linspace(0, drop_path, layers)]
81
+ self.resblocks = nn.ModuleList()
82
+ for idx in range(layers):
83
+ self.resblocks.append(ResidualAttentionBlock(width, heads, drop_path=dpr[idx], dropout=dropout))
84
+ self.checkpoint_num = checkpoint_num
85
+
86
+ def forward(self, x):
87
+ for idx, blk in enumerate(self.resblocks):
88
+ if idx < self.checkpoint_num:
89
+ x = checkpoint.checkpoint(blk, x)
90
+ else:
91
+ x = blk(x)
92
+ return x
93
+
94
+
95
+ class VisionTransformer(nn.Module):
96
+ def __init__(
97
+ self, input_resolution, patch_size, width, layers, heads, output_dim=None,
98
+ kernel_size=1, num_frames=8, drop_path=0, checkpoint_num=0, dropout=0.,
99
+ temp_embed=True,
100
+ ):
101
+ super().__init__()
102
+ self.output_dim = output_dim
103
+ self.conv1 = nn.Conv3d(
104
+ 3, width,
105
+ (kernel_size, patch_size, patch_size),
106
+ (kernel_size, patch_size, patch_size),
107
+ (0, 0, 0), bias=False
108
+ )
109
+
110
+ scale = width ** -0.5
111
+ self.class_embedding = nn.Parameter(scale * torch.randn(width))
112
+ self.positional_embedding = nn.Parameter(scale * torch.randn((input_resolution // patch_size) ** 2 + 1, width))
113
+ self.ln_pre = nn.LayerNorm(width)
114
+ if temp_embed:
115
+ self.temporal_positional_embedding = nn.Parameter(torch.zeros(1, num_frames, width))
116
+
117
+ self.transformer = Transformer(
118
+ width, layers, heads, drop_path=drop_path, checkpoint_num=checkpoint_num,
119
+ dropout=dropout)
120
+
121
+ self.ln_post = nn.LayerNorm(width)
122
+ if output_dim is not None:
123
+ self.proj = nn.Parameter(torch.empty(width, output_dim))
124
+ else:
125
+ self.proj = None
126
+
127
+ self.dropout = nn.Dropout(dropout)
128
+
129
+ def get_num_layers(self):
130
+ return len(self.transformer.resblocks)
131
+
132
+ @torch.jit.ignore
133
+ def no_weight_decay(self):
134
+ return {'positional_embedding', 'class_embedding', 'temporal_positional_embedding'}
135
+
136
+ def mask_tokens(self, inputs, masking_prob=0.0):
137
+ B, L, _ = inputs.shape
138
+
139
+ # This is different from text as we are masking a fix number of tokens
140
+ Lm = int(masking_prob * L)
141
+ masked_indices = torch.zeros(B, L)
142
+ indices = torch.argsort(torch.rand_like(masked_indices), dim=-1)[:, :Lm]
143
+ batch_indices = (
144
+ torch.arange(masked_indices.shape[0]).unsqueeze(-1).expand_as(indices)
145
+ )
146
+ masked_indices[batch_indices, indices] = 1
147
+
148
+ masked_indices = masked_indices.bool()
149
+
150
+ return inputs[~masked_indices].reshape(B, -1, inputs.shape[-1])
151
+
152
+ def forward(self, x, masking_prob=0.0):
153
+ x = self.conv1(x) # shape = [*, width, grid, grid]
154
+ B, C, T, H, W = x.shape
155
+ x = x.permute(0, 2, 3, 4, 1).reshape(B * T, H * W, C)
156
+
157
+ x = torch.cat([self.class_embedding.to(x.dtype) + torch.zeros(x.shape[0], 1, x.shape[-1], dtype=x.dtype, device=x.device), x], dim=1) # shape = [*, grid ** 2 + 1, width]
158
+ x = x + self.positional_embedding.to(x.dtype)
159
+
160
+ # temporal pos
161
+ cls_tokens = x[:B, :1, :]
162
+ x = x[:, 1:]
163
+ x = rearrange(x, '(b t) n m -> (b n) t m', b=B, t=T)
164
+ if hasattr(self, 'temporal_positional_embedding'):
165
+ if x.size(1) == 1:
166
+ # This is a workaround for unused parameter issue
167
+ x = x + self.temporal_positional_embedding.mean(1)
168
+ else:
169
+ x = x + self.temporal_positional_embedding
170
+ x = rearrange(x, '(b n) t m -> b (n t) m', b=B, t=T)
171
+
172
+ if masking_prob > 0.0:
173
+ x = self.mask_tokens(x, masking_prob)
174
+
175
+ x = torch.cat((cls_tokens, x), dim=1)
176
+
177
+ x = self.ln_pre(x)
178
+
179
+ x = x.permute(1, 0, 2) #BND -> NBD
180
+ x = self.transformer(x)
181
+
182
+ x = self.ln_post(x)
183
+
184
+ if self.proj is not None:
185
+ x = self.dropout(x[0]) @ self.proj
186
+ else:
187
+ x = x.permute(1, 0, 2) #NBD -> BND
188
+
189
+ return x
190
+
191
+
192
+ def inflate_weight(weight_2d, time_dim, center=True):
193
+ logger.info(f'Init center: {center}')
194
+ if center:
195
+ weight_3d = torch.zeros(*weight_2d.shape)
196
+ weight_3d = weight_3d.unsqueeze(2).repeat(1, 1, time_dim, 1, 1)
197
+ middle_idx = time_dim // 2
198
+ weight_3d[:, :, middle_idx, :, :] = weight_2d
199
+ else:
200
+ weight_3d = weight_2d.unsqueeze(2).repeat(1, 1, time_dim, 1, 1)
201
+ weight_3d = weight_3d / time_dim
202
+ return weight_3d
203
+
204
+
205
+ def load_state_dict(model, state_dict, input_resolution=224, patch_size=16, center=True):
206
+ state_dict_3d = model.state_dict()
207
+ for k in state_dict.keys():
208
+ if k in state_dict_3d.keys() and state_dict[k].shape != state_dict_3d[k].shape:
209
+ if len(state_dict_3d[k].shape) <= 2:
210
+ logger.info(f'Ignore: {k}')
211
+ continue
212
+ logger.info(f'Inflate: {k}, {state_dict[k].shape} => {state_dict_3d[k].shape}')
213
+ time_dim = state_dict_3d[k].shape[2]
214
+ state_dict[k] = inflate_weight(state_dict[k], time_dim, center=center)
215
+
216
+ pos_embed_checkpoint = state_dict['positional_embedding']
217
+ embedding_size = pos_embed_checkpoint.shape[-1]
218
+ num_patches = (input_resolution // patch_size) ** 2
219
+ orig_size = int((pos_embed_checkpoint.shape[-2] - 1) ** 0.5)
220
+ new_size = int(num_patches ** 0.5)
221
+ if orig_size != new_size:
222
+ logger.info(f'Pos_emb from {orig_size} to {new_size}')
223
+ extra_tokens = pos_embed_checkpoint[:1]
224
+ pos_tokens = pos_embed_checkpoint[1:]
225
+ pos_tokens = pos_tokens.reshape(-1, orig_size, orig_size, embedding_size).permute(0, 3, 1, 2)
226
+ pos_tokens = torch.nn.functional.interpolate(
227
+ pos_tokens, size=(new_size, new_size), mode='bicubic', align_corners=False)
228
+ pos_tokens = pos_tokens.permute(0, 2, 3, 1).flatten(0, 2)
229
+ new_pos_embed = torch.cat((extra_tokens, pos_tokens), dim=0)
230
+ state_dict['positional_embedding'] = new_pos_embed
231
+
232
+ message = model.load_state_dict(state_dict, strict=False)
233
+ logger.info(f"Load pretrained weights: {message}")
234
+
235
+
236
+ @register_model
237
+ def clip_joint_b16(
238
+ pretrained=False, input_resolution=224, kernel_size=1,
239
+ center=True, num_frames=8, drop_path=0., checkpoint_num=0,
240
+ dropout=0.,
241
+ ):
242
+ model = VisionTransformer(
243
+ input_resolution=input_resolution, patch_size=16,
244
+ width=768, layers=12, heads=12, output_dim=512,
245
+ kernel_size=kernel_size, num_frames=num_frames,
246
+ drop_path=drop_path, checkpoint_num=checkpoint_num,
247
+ dropout=dropout,
248
+ )
249
+ if pretrained:
250
+ if isinstance(pretrained, str):
251
+ model_name = pretrained
252
+ else:
253
+ model_name = "ViT-B/16"
254
+
255
+ logger.info('load pretrained weights')
256
+ state_dict = torch.load(_MODELS[model_name], map_location='cpu')
257
+ load_state_dict(model, state_dict, input_resolution=input_resolution, patch_size=16, center=center)
258
+ return model.eval()
259
+
260
+
261
+ @register_model
262
+ def clip_joint_l14(
263
+ pretrained=False, input_resolution=224, kernel_size=1,
264
+ center=True, num_frames=8, drop_path=0., checkpoint_num=0,
265
+ dropout=0.,
266
+ ):
267
+ model = VisionTransformer(
268
+ input_resolution=input_resolution, patch_size=14,
269
+ width=1024, layers=24, heads=16, output_dim=768,
270
+ kernel_size=kernel_size, num_frames=num_frames,
271
+ drop_path=drop_path, checkpoint_num=checkpoint_num,
272
+ dropout=dropout,
273
+ )
274
+
275
+ if pretrained:
276
+ if isinstance(pretrained, str):
277
+ model_name = pretrained
278
+ else:
279
+ model_name = "ViT-L/14"
280
+ logger.info('load pretrained weights')
281
+ state_dict = torch.load(_MODELS[model_name], map_location='cpu')
282
+ load_state_dict(model, state_dict, input_resolution=input_resolution, patch_size=14, center=center)
283
+ return model.eval()
284
+
285
+
286
+ @register_model
287
+ def clip_joint_l14_336(
288
+ pretrained=True, input_resolution=336, kernel_size=1,
289
+ center=True, num_frames=8, drop_path=0.
290
+ ):
291
+ raise NotImplementedError
292
+ model = VisionTransformer(
293
+ input_resolution=input_resolution, patch_size=14,
294
+ width=1024, layers=24, heads=16, output_dim=768,
295
+ kernel_size=kernel_size, num_frames=num_frames,
296
+ drop_path=drop_path,
297
+ )
298
+ if pretrained:
299
+ logger.info('load pretrained weights')
300
+ state_dict = torch.load(_MODELS["ViT-L/14_336"], map_location='cpu')
301
+ load_state_dict(model, state_dict, input_resolution=input_resolution, patch_size=14, center=center)
302
+ return model.eval()
303
+
304
+
305
+ def interpolate_pos_embed_vit(state_dict, new_model):
306
+ key = "vision_encoder.temporal_positional_embedding"
307
+ if key in state_dict:
308
+ vision_temp_embed_new = new_model.state_dict()[key]
309
+ vision_temp_embed_new = vision_temp_embed_new.unsqueeze(2) # [1, n, d] -> [1, n, 1, d]
310
+ vision_temp_embed_old = state_dict[key]
311
+ vision_temp_embed_old = vision_temp_embed_old.unsqueeze(2)
312
+
313
+ state_dict[key] = load_temp_embed_with_mismatch(
314
+ vision_temp_embed_old, vision_temp_embed_new, add_zero=False
315
+ ).squeeze(2)
316
+
317
+ key = "text_encoder.positional_embedding"
318
+ if key in state_dict:
319
+ text_temp_embed_new = new_model.state_dict()[key]
320
+ text_temp_embed_new = text_temp_embed_new.unsqueeze(0).unsqueeze(2) # [n, d] -> [1, n, 1, d]
321
+ text_temp_embed_old = state_dict[key]
322
+ text_temp_embed_old = text_temp_embed_old.unsqueeze(0).unsqueeze(2)
323
+
324
+ state_dict[key] = load_temp_embed_with_mismatch(
325
+ text_temp_embed_old, text_temp_embed_new, add_zero=False
326
+ ).squeeze(2).squeeze(0)
327
+ return state_dict
utils/vision_encoder/model_vision_encoder.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ import torch
4
+ from torch import nn
5
+ import math
6
+
7
+ from .clip_vision import clip_joint_l14, clip_joint_b16
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class VisionEncoder(nn.Module):
13
+
14
+ def __init__(self):
15
+ super(VisionEncoder, self).__init__()
16
+
17
+ self.vision_encoder_name = 'vit_l14'
18
+ self.vision_encoder_pretrained = False
19
+ self.inputs_image_res = 224
20
+ self.vision_encoder_kernel_size = 1
21
+ self.vision_encoder_center = True
22
+ self.video_input_num_frames = 8
23
+ self.vision_encoder_drop_path_rate = 0.1
24
+ self.vision_encoder_checkpoint_num = 24
25
+
26
+ self.vision_width = 1024
27
+ self.embed_dim = 768
28
+ self.masking_prob = 0.9
29
+
30
+ self.vision_encoder = self.build_vision_encoder()
31
+
32
+ self.temp = nn.parameter.Parameter(torch.ones([]) * 1 / 100.0)
33
+ self.temp_min = 1 / 100.0
34
+
35
+ def no_weight_decay(self):
36
+ ret = {"temp"}
37
+ ret.update(
38
+ {"vision_encoder." + k for k in self.vision_encoder.no_weight_decay()}
39
+ )
40
+
41
+ return ret
42
+
43
+
44
+ def encode_vision(self, image, test=False):
45
+ if image.ndim == 5:
46
+ image = image.permute(0, 2, 1, 3, 4).contiguous()
47
+ else:
48
+ image = image.unsqueeze(2)
49
+
50
+ if not test and self.masking_prob > 0.0:
51
+ return self.vision_encoder(
52
+ image, masking_prob=self.masking_prob
53
+ )
54
+
55
+ return self.vision_encoder(image)
56
+
57
+
58
+ @torch.no_grad()
59
+ def clip_contrastive_temperature(self, min_val=0.001, max_val=0.5):
60
+ """Seems only used during pre-training"""
61
+ self.temp.clamp_(min=self.temp_min)
62
+
63
+ def build_vision_encoder(self):
64
+ """build vision encoder
65
+ Returns: (vision_encoder, vision_layernorm). Each is a `nn.Module`.
66
+
67
+ """
68
+ vision_encoder = clip_joint_l14(
69
+ pretrained=self.vision_encoder_pretrained,
70
+ input_resolution=self.inputs_image_res,
71
+ kernel_size=self.vision_encoder_kernel_size,
72
+ center=self.vision_encoder_center,
73
+ num_frames=self.video_input_num_frames,
74
+ drop_path=self.vision_encoder_drop_path_rate,
75
+ checkpoint_num=self.vision_encoder_checkpoint_num,
76
+ )
77
+
78
+ return vision_encoder
79
+
80
+
81
+ def get_vid_features(self, input_frames):
82
+ clip_feat = self.encode_vision(input_frames, test=True).float()
83
+
84
+ return clip_feat