File size: 4,835 Bytes
3f1124e
 
 
98667f6
3f1124e
 
 
 
 
 
 
 
98667f6
3f1124e
98667f6
3f1124e
98667f6
3f1124e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98667f6
3f1124e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98667f6
3f1124e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98667f6
3f1124e
98667f6
3f1124e
 
 
 
98667f6
3f1124e
 
98667f6
3f1124e
98667f6
 
 
3f1124e
 
 
98667f6
 
 
 
 
 
3f1124e
98667f6
3f1124e
 
98667f6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3f1124e
98667f6
 
3f1124e
 
98667f6
3f1124e
98667f6
 
 
3f1124e
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import torch


def extract_text_feature(prompt, model, processor, device="cpu"):
    """Extract text features

    Args:
        prompt: a single text query
        model: OwlViT model
        processor: OwlViT processor
        device (str, optional): device to run. Defaults to 'cpu'.
    """
    device = "cpu"
    if torch.cuda.is_available():
        device = "cuda"
    with torch.no_grad():
        input_ids = torch.as_tensor(processor(text=prompt)["input_ids"]).to(device)
        print(input_ids.device)
        text_outputs = model.owlvit.text_model(
            input_ids=input_ids,
            attention_mask=None,
            output_attentions=None,
            output_hidden_states=None,
            return_dict=None,
        )
        text_embeds = text_outputs[1]
        text_embeds = model.owlvit.text_projection(text_embeds)
        text_embeds /= text_embeds.norm(p=2, dim=-1, keepdim=True) + 1e-6
        query_embeds = text_embeds
    return input_ids, query_embeds


def prompt2vec(prompt: str, model, processor):
    """Convert prompt into a computational vector

    Args:
        prompt (str): Text to be tokenized

    Returns:
        xq: vector from the tokenizer, representing the original prompt
    """
    # inputs = tokenizer(prompt, return_tensors='pt')
    # out = clip.get_text_features(**inputs)
    input_ids, xq = extract_text_feature(prompt, model, processor)
    input_ids = input_ids.detach().cpu().numpy()
    xq = xq.detach().cpu().numpy()
    return input_ids, xq


def tune(clf, X, y, iters=2):
    """Train the Zero-shot Classifier

    Args:
        X (numpy.ndarray): Input vectors (retreived vectors)
        y (list of floats or numpy.ndarray): Scores given by user
        iters (int, optional): iterations of updates to be run
    """
    assert len(X) == len(y)
    # train the classifier
    clf.fit(X, y, iters=iters)
    # extract new vector
    return clf.get_weights()

class Classifier:
    """Multi-Class Zero-shot Classifier
    This Classifier provides proxy regarding to the user's reaction to the probed images.
    The proxy will replace the original query vector generated by prompted vector and finally
    give the user a satisfying retrieval result.

    This can be commonly seen in a recommendation system. The classifier will recommend more
    precise result as it accumulating user's activity.

    This is a multiclass classifier. For N queries it will set the all queries to the first-N classes
    and the last one takes the negative one.
    """

    def __init__(self, client, obj_db:str, xq: list):
        init_weight = torch.Tensor(xq)
        self.num_class = xq.shape[0]
        self.DIMS = xq.shape[1]
        # convert initial query `xq` to tensor parameter to init weights
        self.weight = init_weight
        self.client = client
        self.obj_db = obj_db

    def fit(self, X: list, y: list, iters: int = 5):
        # convert X and y to tensor
        xq_s = [
            f"[{', '.join([str(float(fnum)) for fnum in _xq + [1]])}]"
            for _xq in self.get_weights().tolist()
        ]
        
        for _ in range(iters):
            # zero gradients
            grad = []
            # Normalize the weight before inference
            # This will constrain the gradient or you will have an explosion on query vector
            self.weight.data /= torch.norm(
                self.weight.data, p=2, dim=-1, keepdim=True
            )
            for n in range(self.num_class):
                # select all training sample and create labels
                labels, objs = list(map(list, zip(*[[1 if y[i]==n else 0, x] for i, x in enumerate(X) if y[i] in [n, self.num_class+1]])))
                
                # NOTE from @fangruil
                # Use SQL to calculate the gradient
                # For binary cross entropy we have 
                #   g = (1/(1+\exp(-XW))-Y)^TX
                # To simplify the query, we separated 
                # the calculation into class numbers
                grad_q_str = f"""
                    SELECT sumForEachArray(arrayMap((x,y,gt)->arrayMap(i->i*(y-gt), x), X, Y, GT)) AS grad
                    FROM (
                        SELECT groupArray(arrayPopBack(prelogit)) AS X, 
                            groupArray(1/(1+exp(-arraySum(arrayMap((x,y)->x*y, prelogit, {xq_s[n]}))))) AS Y, {labels} AS GT
                        FROM {self.obj_db} WHERE obj_id IN {objs})"""
                grad.append(torch.as_tensor(self.client.fetch(grad_q_str)[0]['grad']))
            # update weights
            grad = torch.stack(grad, dim=0)
            self.weight -= 0.1 * grad

    def get_weights(self):
        xq = self.weight.detach().numpy()
        return xq



class SplitLayer(torch.nn.Module):
    def forward(self, x):
        return torch.split(x, 1, dim=-1)