Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reproduzir "Kinship Representation Learning with Face Componential Relation" #67

Closed
vitalwarley opened this issue Mar 2, 2024 · 6 comments
Assignees

Comments

@vitalwarley
Copy link
Owner

vitalwarley commented Mar 2, 2024

https://github.com/wtnthu/FaCoR/

Criei um requirements.txt a partir do environment.yml porque não gosto de usar conda. Abaixo os detalhes

➜  FaCoR git:(main) ✗ workon facor
➜  FaCoR git:(main) ✗ python --version
Python 3.10.12
➜  FaCoR git:(main) ✗ which python
/home/warley/.virtualenvs/facor/bin/python
➜  FaCoR git:(main) ✗ cat requirements.txt
einops
keras
matplotlib
numpy
pandas
scikit_learn
scipy
seaborn
torch
torchvision
tqdm
@vitalwarley vitalwarley self-assigned this Mar 2, 2024
@vitalwarley
Copy link
Owner Author

Consegui executar o run.sh provido pelos autores. No entanto, precisei realizar algumas modificações:

Ver diff
diff --git a/find.py b/find.py
index 32bdd53..d3a242a 100644
--- a/find.py
+++ b/find.py
@@ -3,7 +3,6 @@ from sklearn.metrics import roc_curve,auc
 from dataset import *
 from models import *
 from utils import *
-import torch_resnet101
 import torch
 from torch.optim import SGD
 from losses import *
@@ -30,8 +29,10 @@ def find(args):
     # pdb.set_trace()
     method=args.method

+    generator=torch.Generator(device='cuda')
+
     val_dataset = FIW(os.path.join(args.sample,"val_A.txt"))
-    val_loader = DataLoader(val_dataset, batch_size=batch_size, num_workers=0, pin_memory=False)
+    val_loader = DataLoader(val_dataset, batch_size=batch_size, num_workers=0, pin_memory=False, generator=generator)

     if arch=='ada3':
         model=Net_ada3().cuda()
diff --git a/models.py b/models.py
index 7570092..5c85af6 100644
--- a/models.py
+++ b/models.py
@@ -1,4 +1,5 @@
 import torch, pdb
+from torch import nn
 from inference import load_pretrained_model, to_input


@@ -437,4 +438,4 @@ class CALayer2(nn.Module):
         y=x
         # pdb.set_trace()
         y = self.ca(y)
-        return y
\ No newline at end of file
+        return y
diff --git a/run.sh b/run.sh
index d46bdeb..bb1803b 100644
--- a/run.sh
+++ b/run.sh
@@ -1,11 +1,11 @@
-export CUDA_VISIBLE_DEVICES=0,1
+#export CUDA_VISIBLE_DEVICES=0
 model=ada3
 lr=1e-4
 batch_size=50
 method=cont
 epochs=50
 beta=0.08
-txt=train_sort_A2_m,.txt
+txt=train_sort_A2_m.txt
 all=1

 name=FaCoRNet_Adaface_lr_${lr}_beta
diff --git a/train_p.py b/train_p.py
index 58590a4..746e03a 100644
--- a/train_p.py
+++ b/train_p.py
@@ -1,3 +1,6 @@
+import os
+os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
+
 import gc, pdb
 from random import shuffle
 from sklearn.metrics import roc_curve,auc
@@ -32,15 +35,17 @@ def training(args):
     aug=args.aug
     txt = args.txt

-    train_dataset=FIW2(os.path.join(args.sample,args.txt))
-    val_dataset=FIW2(os.path.join(args.sample,"val_choose_A_m.txt"))
-    train_loader=DataLoader(train_dataset,batch_size=batch_size,num_workers=0,pin_memory=False, shuffle=True)
-    val_loader = DataLoader(val_dataset, batch_size=val_batch_size, num_workers=0, pin_memory=False)
+    generator=torch.Generator(device='cuda')
+
+    train_dataset=FIW(os.path.join(args.sample,args.txt))
+    val_dataset=FIW(os.path.join(args.sample,"val_choose_A.txt"))
+    train_loader=DataLoader(train_dataset,batch_size=batch_size,num_workers=4,pin_memory=False, shuffle=True, generator=generator)
+    val_loader = DataLoader(val_dataset, batch_size=val_batch_size, num_workers=4, pin_memory=False, generator=generator)

     if arch=='ada3':
         model=Net_ada3().cuda()

-    model = torch.nn.DataParallel(model).cuda()
+    model.cuda()
     optimizer_model = SGD(model.parameters(), lr=args.lr, momentum=0.9)

     max_auc=0.0
@@ -53,7 +58,11 @@ def training(args):
         model.train()

         for index_i, data in enumerate(train_loader):
-            image1, image2,  labels, kin_label,_ = data
+            #image1, image2,  labels, kin_label,_ = data
+            image1, image2,  labels, kin_label= data
+
+            image1.cuda()
+            image2.cuda()

             if method=='cont':
                 e1,e2,x1,x2,att= model([image1,image2], aug=False)
@@ -95,8 +104,8 @@ def save_model(model,path):
 def val_model(model, val_loader, aug):
     y_true = []
     y_pred = []
-    # for img1, img2, labels, _ in val_loader:
-    for img1, img2, labels, _, _ in val_loader:
+    for img1, img2, labels, _ in val_loader:
+    #for img1, img2, labels, _, _ in val_loader:
         e1,e2,x1,x2,_=model([img1.cuda(),img2.cuda()])

         if args.method=='sig':

Atualmente na época 41/50 (~30s/época). Até agora, a melhor AUC foi de 0.875248. Ao fim, será feito a busca do melhor limiar e avaliação do modelo no conjunto de teste.

No próximo comentário trago as diferenças entre o que está no paper e o que está no código.

@vitalwarley
Copy link
Owner Author

Precisei também remover o DataParallel do find.py e test.py. Abaixo os resultados

➜  FaCoR git:(main) ✗ sh run.sh
... # find
auc :  0.8768921112720713
threshold : 0.11221975088119507
... # test
bb : 0.8248135593220339
ss : 0.8447885853488777
sibs : 0.8166373755125952
fd : 0.7936272081888723
md : 0.8152701391135555
fs : 0.8485496183206107
ms : 0.7989577069553017
gfgd : 0.7878103837471784
gmgd : 0.7323420074349443
gfgs : 0.6816326530612244
gmgs : 0.5642458100558659
avg : 0.8186850514556022
AUC=0.8979235895647995

A média das 7 relações principais é de 0.820, como reportado no paper.

@vitalwarley
Copy link
Owner Author

Em resumo, o modelo Net_ada3 retorna um mapa de atenção $\beta$ provido por FSNet2 e os vetores de atributos $(\mathbf{x^a_{out}}, \mathbf{x^b_{out}})$ providos juntamente por CAM_Module, FSNet2, e CCA.

A função perda usará do mapa de atenção provido pela FSNet2 para computação do $\psi$ -- uma temperatura adaptável ao batch. Esse mapa contém a similaridade entre as regiões espaciais -- tanto para um mesmo indivíduo, quanto para o outro do par. Podemos ver no código a seguir que há uma redução ao longo das colunas, depois da profundidade, onde no paper foi referenciado global sum pooling operation: beta = (beta**2).sum([1, 2])/500, que creio ser mesma coisa. Há um exponencial quadrático que não foi especificado no paper, no entanto. Adicionalmente, a perda usará das embeddings (x1, x2) que foram computadas usando o mapa de atributos de saída de FSNet2 e CAM_Module da seguinte forma: $(\mathbf{x^a_{out}}, \mathbf{x^b_{out}}) = (\text{CCA}(c_a || O_a), \text{CCA}(c_b || O_b))$. Isso difere do esquema do paper: $(\mathbf{x^a_{out}}, \mathbf{x^b_{out}}) = (\text{CCA}(r_a || \text{CCA}(O_a)), , \text{CCA}(r_b || \text{CCA}(O_b)))$.

  • Esse CCA, acredito eu, é equivalente ao CI do paper, exceto por apenas produz $\mathbf{w}$ em vez de $\mathbf{w}\hat{\mathbf{x}}$, onde $\hat{\mathbf{x}}$ é a entrada. No código, essa multiplicação elemento a elemento é feita no próprio forward da rede Net_ada3.
  • É importante notar que $(\mathbf{c}^a, \mathbf{c}^b)$, produzido por CAM_Module, é gerado a partir de um mapa de atenção entre os mapas de atributos em si ao longo dos canais e entre os indivíduos, o que difere do mapa produzido pela FSNet2.
  • A computação do mapa de atenção de CAM_Module também tem um erro: há uma inversão nos valores de atenção.
energy = torch.bmm(proj_query, proj_key)
energy_new = torch.max(energy, -1, keepdim=True)[0].expand_as(energy)-energy
attention = self.softmax(energy_new)

No fim, o modelo está usando dois tipos de atenção:

  • Atenção espacial para guiar a função de perda via a computação do parâmetro $\psi$.
  • Atenção espacial + atenção de canal para produzir as embeddings a serem usadas na função de perda.

Análise detalhada

train_p.py -- modelo instanciado

    if arch=='ada3':
        model=Net_ada3().cuda()

models.py -- __init__ e forward de Net_ada3, classe FSNet2

  • Podemos ver na inicialização abaixo a definição do modelo, a definição de algumas camadas e variáveis (channel, CA). Dentre as camadas, não usaram projection, CCA2, out ou CA1.

    def __init__(self):
        super(Net_ada3, self).__init__()
        # self.encoder=KitModel("./kit_resnet101.pkl")
        self.Ada_model = load_pretrained_model('ir_101')
        #'''
        self.projection=nn.Sequential(
            torch.nn.Linear(512*6, 256),
            torch.nn.BatchNorm1d(256),
            torch.nn.ReLU(),
            torch.nn.Linear(256, 1),
        )
        #'''
        # self.projection = torch.nn.Linear(1024, 1)
        self.channel=64
        CA = True
        # self.enhance_block = FSFNet(self.channel*8//4, CA)
        self.enhance_block1 = FSFNet2(self.channel*8, CA)
        self.enhance_block2 = CAM_Module(self.channel*8)
        self.CCA=CALayer2(1024)
        self.CCA2=CALayer(1024)
        self.out = nn.Sigmoid()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
    
        self.CA1 = CAM_Module(1536)
    • Logo, temos o código abaixo.

      def __init__(self):
          super(Net_ada3, self).__init__()
          self.Ada_model = load_pretrained_model('ir_101')
          self.channel=64
          CA = True
          self.enhance_block1 = FSFNet2(self.channel*8, CA)
          self.enhance_block2 = CAM_Module(self.channel*8)
          self.CCA=CALayer2(1024)
          self.avg_pool = nn.AdaptiveAvgPool2d(1)
  • Podemos ver na propagação para frente alguns problemas. Eu removi comentários, sentenças e espaços dispensáveis.

    def forward(self, imgs, aug=False):
        img1,img2=imgs
        idx=[2,1,0]
        f1_0, x1_feat = self.Ada_model(img1[:,idx])
        f2_0, x2_feat = self.Ada_model(img2[:,idx])
    
        _, _ ,att_map0 = self.enhance_block1(x1_feat, x2_feat)
    
        f1_0 = l2_norm(f1_0)
        f2_0 = l2_norm(f2_0)
    
        x1_feat = l2_norm(x1_feat)
        x2_feat = l2_norm(x2_feat)
    
        f1_1, f2_1 ,att_map1 = self.enhance_block2(f1_0, f2_0)
        f1_2, f2_2 ,att_map2 = self.enhance_block1(x1_feat, x2_feat)
        
        f1_2 = torch.flatten(self.avg_pool(f1_2),1)
        f2_2 = torch.flatten(self.avg_pool(f2_2),1)
    
        wC = self.CCA(torch.cat([f1_1, f1_2],1).unsqueeze(2).unsqueeze(3))
        wC = wC.view(-1,2,512)[:,:,:,None,None]
        f1s = f1_1.unsqueeze(2).unsqueeze(3) * wC[:,0] + f1_2.unsqueeze(2).unsqueeze(3)* wC[:,1]
    
        wC2 = self.CCA(torch.cat([f2_1, f2_2],1).unsqueeze(2).unsqueeze(3))
        wC2 = wC2.view(-1,2,512)[:,:,:,None,None]
        f2s = f2_1.unsqueeze(2).unsqueeze(3) * wC2[:,0] + f2_2.unsqueeze(2).unsqueeze(3)* wC2[:,1]
    
        f1s = torch.flatten(f1s,1)
        f2s = torch.flatten(f2s,1)
    
        return f1s,f2s,f1s,f2s, att_map0
    • Associações entre variáveis e o paper

      • $(\mathbf{X}^a, \mathbf{X}^b)$ no diagrama do paper podem ser os mapas de atributos não normalizados (x1_feat, x2_feat).

      • $\mathbf{\beta}$ pode ser att_map0

      • $(\mathbf{O}^a, \mathbf{O}^b)$ podem ser f1_2 e f2_2

      • $(\mathbf{r}^a, \mathbf{r}^b)$ podem ser vetores de atributos provido pelo backbone; são devidamente normalizados.

      • $(\mathbf{c}^a, \mathbf{c}^b)$ não tem equivalente

    • enhance_block1 (FSFNet2, a camada de self-attention) é usado duas vezes:

        1. produz $\mathbf{\beta}$
        • Computado em cima de $(\mathbf{X}^a, \mathbf{X}^b)$

        • Retornado ao fim do método.

        1. produz $(\mathbf{O}^a, \mathbf{O}^b)$
        • Computados em cima de $(\mathbf{X}^a, \mathbf{X}^b)$ normalizados.

        • Usados ao longo da propagação.

    • enhance_block2 (CAM_Module, o módulo de channel attention) é usado uma vez

      • Produz $(\mathbf{c}^a, \mathbf{c}^b)$ similarmente à $(\mathbf{O}^a, \mathbf{O}^b)$ [ver diferenças no final desse conteúdo], mas reorganizado em (B, C * H * W)

      • Produz também o mapa de atenção (att_map1), mas que não é usado.

    • Há um processamento de $\mathbf{O}^a$ e $\mathbf{O}^b$ : avg_pool, que os transforma de (B, C, W, H) para (B, C, 1, 1). Depois são achatados para (B, C).

      • Isso não foi apresentado no paper.
    • Módulo Channel Interaction do paper tem seu equivalente em CCA

      • Cada vetor em $(\mathbf{c}^a, \mathbf{c}^b)$ [saída de CAM_Module] é concatenado com $(\mathbf{O}^a, \mathbf{O}^b)$ [saída de FSNet2, mas após avg_pool).

      • O vetor resultante é passado ao módulo CCA, que consiste em (Conv2d1x1, ReLU, Conv2d 1x1, Sigmoid).

      • O resultado, wC, multiplica a entrada que o produziu. Isso é feito para os dois vetores.

      • Diferenças

        • Esse CI difere um pouco do paper, pois apenas produz $\mathbf{w}$ em vez de $\mathbf{w}\hat{\mathbf{x}}$, onde $\hat{\mathbf{x}}$ é a entrada. No código, essa multiplicação elemento a elemento é feita no próprio forward da rede.

        • No paper, é dito que vetor de atributos $(\mathbf{r}^a, \mathbf{r}^b)$ deveriam ser concatenados com $(\mathbf{O}^a, \mathbf{O}^b)$, mas isso não acontece.

        • No paper, CCA é usado duas vezes seguidas.

          • CCA(O_a, O_b) => (cca_O_a, cca_O_b) e CCA(r_a || cca_O_a, r_b || cca_O_b).

            • $(\mathbf{x^a_{out}}, \mathbf{x^b_{out}}) = (\text{CCA}(r_a || \text{CCA}(O_a)), , \text{CCA}(r_b || \text{CCA}(O_b)))$
        • Todavia, temos CCA(CAM_Module(r_a) || O_a), CAM_Module(r_b) || O_b) => (f1s, f2s).

          • $(\mathbf{x^a_{out}}, \mathbf{x^b_{out}}) = (\text{CCA}(c_a || O_a), \text{CCA}(c_b || O_b))$
    • Por fim, há um achatamento de f1s e f2s, que deveriam representar $(\mathbf{x^a_{out}}, \mathbf{x^b_{out}})$. O retorno da propagação repete esses vetores, algo redudante.

    • Em resumo, o modelo retorna um mapa de atenção $\beta$ provido por FSNet2 e os vetores de atributos $(\mathbf{x^a_{out}}, \mathbf{x^b_{out}})$ providos juntamente por CAM_Module, FSNet2, e CCA.

  • FSNet2 contém um código bem similar à CAM_Module. Analiso abaixo, mas antes removo os comentários e sentenças inúteis para diminuir o tamanho do código. Nos comentários, no entanto, há trechos que refletem o paper, mas que aparentemente não foram usados no desenvolvimento do modelo final: # out = self.gamma*out + x. Em outras palavras, gamma não foi usado no modelo final.

    FSNet2

    def __init__(self,in_dim, CA=False):
        super(FSFNet2,self).__init__()
        self.chanel_in = in_dim
        reduction_ratio=16
        self.query_conv = nn.Conv2d(in_channels = in_dim*2 , out_channels = in_dim//reduction_ratio , kernel_size= 1)
        self.key_conv = nn.Conv2d(in_channels = in_dim*2 , out_channels = in_dim//reduction_ratio , kernel_size= 1)
        self.value_conv1 = nn.Conv2d(in_channels = in_dim , out_channels = in_dim , kernel_size= 1)
        self.value_conv2 = nn.Conv2d(in_channels = in_dim , out_channels = in_dim , kernel_size= 1)
    
        self.softmax  = nn.Softmax(dim=-1) #
    def forward(self, x1, x2):
        """
            inputs :
                x : input feature maps( B X C X W X H)
            returns :
                out : self attention value + input feature 
                attention: B X N X N (N is Width*Height)
        """
        m_batchsize,C,width ,height = x1.size()
        x = torch.cat([x1, x2],1)
        proj_query  = self.query_conv(x).view(m_batchsize,-1,width*height).permute(0,2,1) # B X CX(N)
        proj_key =  self.key_conv(x).view(m_batchsize,-1,width*height) # B X C x (*W*H)
        energy =  torch.bmm(proj_query,proj_key) # transpose check
        attention = self.softmax(energy) # BX (N) X (N) 
        proj_value1 = self.value_conv1(x1)
        proj_value2 = self.value_conv2(x2)
    
        proj_value1 = proj_value1.view(m_batchsize,-1,width*height) # B X C X N
        proj_value2 = proj_value2.view(m_batchsize,-1,width*height) # B X C X N
    
        out1 = torch.bmm(proj_value1,attention.permute(0,2,1) )
        out2 = torch.bmm(proj_value2,attention.permute(0,2,1) )
        out1 = out1.view(m_batchsize,-1,width,height) + x1.view(m_batchsize,-1,width,height)
        out2 = out2.view(m_batchsize,-1,width,height) + x2.view(m_batchsize,-1,width,height)
    
        return out1, out2, attention
    • A computação de attention difere levemente do paper: faltou uma transposição de attention. Se considerarmos (proj_query, proj_key) como os mapas de atributos $(\mathbf{F}^a, \mathbf{F}^b)$, então cada linha de de $\beta$ refere-se aos scores de atenção de um mapa $\mathbf{F}^a_i$ para todos maps em $\mathbf{F}^b$. Deveria ser o inverso: $\mathbf{\beta}_i$ representando os scores de atenção de todos os mapas em $\mathbf{F}^a$ para um mapa $\mathbf{F}^b_i$. Faltou uma transposição de energy. Essa transposição é feita, no entanto, ao computar (out1, out2), mas isso muda o resultado porque softmax já foi realizada.

      • Da forma que está, a atenção ao ser transposta pode ser interpretada assim: cada coluna de $\beta$ refere-se aos scores de atenção de todos os mapas $\mathbf{F}^a$ para um mapa $j$ em $\mathbf{F}^b$ ($j$ representa a coluna aqui). Cada coluna tem uma soma total de 1.
    • Mais gravemente, a saída (out1, out2) é computada com proj_value (1 e 2) sobre a atenção, mas no paper usa-se os mapas de atributos originais (x1, x2). Também não se usa $\lambda$ para ponderar out (1 e 2).

      • Originalmente, segundo o paper, temos $\mathbf{O}^a_j = \mathbf{X}^a + \lambda \sum_{i=1}^N \beta_{j,i} \mathbf{X}^a_i$, onde $\mathbf{\beta}_{j,i}$ é um escalar e $\mathbf{X}i \in (1 \times HW)$ [row vector]. A transposição aqui é implícita. $\beta{j,i}$ representa a atenção de $\mathbf{F}^a_i$ para $\mathbf{F}^b_j$, e $\mathbf{X}^a_i$ o $i$-ésimo mapa de atributos de $\mathbf{X}^a$. Creio que $\mathbf{X}^a_i$ e $\mathbf{F}^a_i$ representam o mesmo mapa posicionalmente falando, mas que esse último é o resultado de convoluções 1x1 ao longo dos canais. Da forma que está escrito, penso que a saída $\mathbf{O}^a$ é a soma da agregação de todos os mapas de $\mathbf{F}^a$ ponderados pelo seus respectivos valores de atenção relativo à $\mathbf{F}^b_j$ com $\mathbf{X}^a$.

        • No entanto, eu penso faltou um $j$, tal que $\mathbf{O}^a_j = \mathbf{X}^a_j + \lambda(\beta_{j,1}\mathbf{X}^a_1 + \beta_{j,2}\mathbf{X}^a_2 + ... \beta_{j,HW}\mathbf{X}^a_HW)$, que faz mais sentido. Ainda assim, não entendo porque se soma TODOS os mapas de atributo ponderados pelos seus respectivos valores de atenção.
      • Da forma que está, considerando que cada linha de proj_value é um mapa de atributo (processado por convs 1x1), então cada linha de out é uma representação de proj_value sensível a todo o contexto, pois incorpora informações de todos os mapas de atributos ponderados por suas relevâncias à cada linha de proj_value.

        • Note que para out1, por exemplo, cada coluna de attention ($\beta$) refere-se aos scores de atenção de todos os mapas $\mathbf{F}^a$ para um mapa $j$ em $\mathbf{F}^b$ ($j$ representa a coluna aqui). Em outras palavras, cada linha de out1 é uma representação de um mapa de atributos de proj_value1 sensível a todo o contexto de $\mathbf{F}^a$ e $\mathbf{F}^b$. Especificamente, nessa linha, cada elemento $j$ representa a importância do mapa de atributos $j$ de proj_value relativo a todos os mapas de $\mathbf{F}^a$ e apenas um $\mathbf{F}^b_j$, pois out1_ij = proj_value_row_i * attention_col_j e cada coluna $j$ de attention são os scores de atenção de todo $\mathbf{F}^a$ para um $\mathbf{F}^b_j$.
    • Um detalhe importante é que na atenção aqui computada, há também informações de cada feature map sobre si mesmo, e não somente sobre o outro, pois temos x = torch.cat([x1, x2], 1).

    CAM_Module

    def __init__(self, in_dim):
        super(CAM_Module, self).__init__()
        self.chanel_in = in_dim
    
        self.conv = nn.Conv2d(in_channels = in_dim*2 , out_channels = in_dim , kernel_size= 1)
        self.gamma = nn.Parameter(torch.zeros(1))
        self.softmax  = nn.Softmax(dim=-1)
    def forward(self, x1, x2):
        """
            inputs :
                x : input feature maps( B X C X H X W)
            returns :
                out : attention value + input feature
                attention: B X C X C
        """
        x1 = x1.unsqueeze(2).unsqueeze(3)
        x2 = x2.unsqueeze(2).unsqueeze(3)
        x = torch.cat([x1, x2], 1)
        x = self.conv(x)
        m_batchsize, C, height, width = x.size()
        proj_query = x.view(m_batchsize, C, -1)
        proj_key = x.view(m_batchsize, C, -1).permute(0, 2, 1)
        energy = torch.bmm(proj_query, proj_key)
        energy_new = torch.max(energy, -1, keepdim=True)[0].expand_as(energy)-energy
        attention = self.softmax(energy_new)
    
        proj_value1 = x1.view(m_batchsize, C, -1)
        proj_value2 = x2.view(m_batchsize, C, -1)
    
        out1 = torch.bmm(attention, proj_value1)
        out1 = out1.view(m_batchsize, C, height, width)
    
        out2 = torch.bmm(attention, proj_value2)
        out2 = out2.view(m_batchsize, C, height, width)
    
        out1 = out1 + x1
        out2 = out2 + x2
        # out = self.gamma*out + x
        return out1.reshape(m_batchsize, -1), out2.reshape(m_batchsize, -1), attention
    • É um código mais legível e um pouco diferente daquele de FSNet2. Em vez de uma convolução para cada projeção (proj_query, proj_key), temos uma convolução para as duas. Há diferenças no número de canais também. Aqui temos in_dim canais, enquanto na FSNet2 temos in_dim / 16. Por fim, proj_key é transposta, enquanto que na FSNet2 é proj_query.

      • Antes tínhamos $(B, WH, C) \times (B, C, WH)$, mas agora é o inverso $(B, C, WH) \times (B, WH, C)$. Isso muda completamente a atenção. Antes estávamos atendendo entre as posições espaciais entre si -- atenção sobre a dimensão de atributos/canais -- em busca de obter a similaridade entre todas as regiões espaciais. Agora estamos atendendo mapa à mapa -- atenção sobre a dimensão espacial -- em busca de obter a similaridade entre os mapas de atributos como um todo.

      • Antes cada linha de energy representava a energia de uma região de x (já processada via convs 1x1, logo contendo informações dos demais canais) para todos as outras regiões em x (também já processadas via convs 1x1). Agora cada linha de energy representa a energia de um mapa de x para todos os mapas em x.

    • A atenção também é diferente: aqui subtrai-se os valores máximos de cada linha de energy antes do softmax. Isso inverte os valores da atenção: valores maiores se tornam menores.

    • As saídas (out1, out2) são a partir de uma atenção não transposta, como no paper, bem como corretamente sobre os mapas de atributos originais (x1, x2), que foram apenas reorganizados.

      • No FSNet2, temos proj_value * attention (BxCxN * BxNxN -> BxCxN), tal que cada linha de out seja o respectivo mapa reajustado a partir dos seus valores de atenção (lembre que a coluna de attention representa a atenção de todos mapas $\mathbf{F}^a$ para um específico de $\mathbf{F}^b$). Por exemplo, out_ij é a representação final do mapa proj_value_row_i após ser ponderado por attention_col_j, que informou a importância dos mapas $\mathbf{F}^a$ para $\mathbf{F}^b_j$.

      • Agora temos attention * proj_value (BxCxC * BxCxN -> BxCxN), tal que cada linha $i$ de out seja o mapa $i$ de projet_value reajustado a partir da atenção de um mapa $i$ relativo à todos os outros. Por exemplo, out_ij é a representação final da região proj_value_col_j (pode ser entendido como a região $j$ ao longo de todos os mapas de atributos) após ser ponderado por attention_row_i (pode ser entendido como a similaridade do mapa $i$ relativo a todos os outros mapas).

    • Mais uma vez não há uso do $\lambda$.

  • Qual a diferença entre att_map0 e att_map1?

    • att_map0 tem shape (BxNxN), enquanto que att_map1 tem shape (BxCxC). No primeiro caso temos uma atenção de uma região relativa a todos as outras regiões no mapa de atributos -- isso ao longo da dimensão espacial. No segundo caso, temos uma atenção de um mapa de x para todos os mapas em x -- isso ao longo dos canais.

@vitalwarley
Copy link
Owner Author

Penso que os próximo passos são

  1. Reorganizar e simplificar código funcional (o que executou durante o treinamento)
  2. Reproduzir resultados

Avaliar complementos/extensões/correções no meio tempo.

O que os autores descreveram no paper é bastante diferente do código final. De acordo com o repositório deles, o paper foi para o ICCV 2023 Workshop, todavia no paper lá publicado não há diferenças significativas no método exposto no paper do arxiv.

@vitalwarley
Copy link
Owner Author

#50 segue mais à risca o que foi proposto tanto no #51

https://github.com/garynlfd/KFC/blob/ec488fbb7aa25f8a821a56804192d22b745cdff8/models_multi_task.py#L130-L134

A atenção é computada uma vez e a partir dos vetores de atributos achatados após as convoluções 1x1, como original proposto no paper do FaCoRNet.

@vitalwarley
Copy link
Owner Author

#50 segue mais à risca o que foi proposto tanto no #51

https://github.com/garynlfd/KFC/blob/ec488fbb7aa25f8a821a56804192d22b745cdff8/models_multi_task.py#L130-L134

A atenção é computada uma vez e a partir dos vetores de atributos achatados após as convoluções 1x1, como original proposto no paper do FaCoRNet.

Eu tentei um experimento: permitir que o mapa de atenção seja usado na perda, como originalmente proposto no FaCoRNet. Todavia, o mapa de correlação, que usei como mapa de atenção, possui grandes valores (na casa de 10^5). Se eu escalo pela soma das linhas/colunas, o valor de beta será 1 em vez de estar no intervalo [0.08, 1].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant