Upload folder using huggingface_hub
Browse files- README.md +39 -0
- VideoCLIP-XL.bin +3 -0
- data_dataloaders.py +57 -0
- dataloader_msvd_retrieval.py +183 -0
- demo.py +435 -0
- modeling.py +586 -0
- requirements.txt +6 -0
- utils/__init__.py +0 -0
- utils/text_encoder/__init__.py +1 -0
- utils/text_encoder/bpe_simple_vocab_16e6.txt.gz +3 -0
- utils/text_encoder/model_text_encoder.py +395 -0
- utils/text_encoder/simple_tokenizer.py +132 -0
- utils/text_encoder/text_encoder.py +75 -0
- utils/vision_encoder/__init__.py +11 -0
- utils/vision_encoder/clip_vision.py +327 -0
- utils/vision_encoder/model_vision_encoder.py +84 -0
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
|