```python
%matplotlib inline
```

## Intro

Este tutorial é baseado no tutorial [Atenção é tudo que você precisa](http://nlp.seas.harvard.edu/2018/04/03/attention.html)

O transformador descrito no artigo `Attention is All You Need` tem sido de grande interesse para vários pesquisadores em redes neurais. Além de produzir ganhos significativos em tarefas de tradução, este modelo provê uma nova arquitetura para muitas tarefas de processamento de linguagem natural (`NLP`).

O artigo é extremamente bem escrito, mas é de conhecimento comum que esta arquitetura é bastante difícil de implementar de forma correta. Sendo assim, Alexander Rush escreveu esta versão linha-a-linha de como o modelo é implementado. Ele também teve ajuda de Vincent Nguyen e Guilherme Klein.

De toda forma, eu espero que eu possa capturar de forma correta os argumentos/ideias descritas no artigo e explanadas pelos pesquisadores citados acima.

### Dependências

* torchtext
* spacy
* seaborn
* torchtext
* matplotlib

```python
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import math, copy, time
from torch.autograd import Variable
import matplotlib.pyplot as plt
import seaborn
seaborn.set_context(context="talk")

# Removendo warning por conta da issue: https://github.com/NVIDIA/DeepRecommender/issues/21
import warnings
warnings.filterwarnings('ignore')
```

## Contexto

Intra-atenção (`self-attention`) é um mecanismo de atenção que relaciona diferentes posições de uma única sequência de modo a computar a representação daquela sequência. Intra-antenção foi utilizado com sucesso em uma variedade de tarefas cujos nomes eu não me arrisco a traduzir (reading comprehension, abstractive summarization, textual entailment and learning task-independent sentence representations).

Redes neurais nesta arquitetura são baseadas em atenção recorrente ao invés de alinhamentos sequenciais e foi comprovado que esta arquitetura performa bem em tarefas de perguntas e respostas ou na representação de modelos de linguage.

# Arquitetura

Assim como a maioria dos modelos de sequência mais robustos, a arquiterura deste modelo segue a estrutura de um codificador-decodificador.

Aqui, o codificador mapeia uma sequência de entrada com símbolos ($x_1, ..., x_n$) para uma sequência de representação continua z = ($z_1, ..., z_n$).

Dado z, o decodificador gera então uma sequência de símbolos de saída ($y_1, ..., y_n$) - um símbolo a cada intervalo de tempo. A cada intervalo de tempo, o modelo é auto-regressivo, consumindo a última sequência de símbolos como parte da entrada.

```python
class EncoderDecoder(nn.Module):
    """
    A standard Encoder-Decoder architecture. Base for this and many
    other models.
    """
    def __init__(self, encoder, decoder, src_embed, tgt_embed, generator):
        super(EncoderDecoder, self).__init__()
        self.encoder = encoder
        self.decoder = decoder
        self.src_embed = src_embed
        self.tgt_embed = tgt_embed
        self.generator = generator

    def forward(self, src, tgt, src_mask, tgt_mask):
        "Take in and process masked src and target sequences."
        return self.decode(self.encode(src, src_mask), src_mask,
                            tgt, tgt_mask)

    def encode(self, src, src_mask):
        return self.encoder(self.src_embed(src), src_mask)

    def decode(self, memory, src_mask, tgt, tgt_mask):
        return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)
```

```python
class Generator(nn.Module):
    "Define standard linear + softmax generation step."
    def __init__(self, d_model, vocab):
        super(Generator, self).__init__()
        self.proj = nn.Linear(d_model, vocab)

    def forward(self, x):
        return F.log_softmax(self.proj(x), dim=-1)
```

O transformador segue esta arquitetura empilhando uma série de camadas de intra-atenção, além de camadas densamente conectadas tanto no codificador quanto no decodificador, conforme exibidor no lado esquerdo e lado direito inferior da figura:

`mais detalhes explicando a figura em breve....`

# Pilha de Codificadores e Decodificadores

## Codificador

O codificador é composto de uma pilha de $N = 6$ camadas idênticas

```python
def clones(module, N):
    "Produce N identical layers."
    return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])
```

```python
class Encoder(nn.Module):
    "Core encoder is a stack of N layers"
    def __init__(self, layer, N):
        super(Encoder, self).__init__()
        self.layers = clones(layer, N)
        self.norm = LayerNorm(layer.size)

    def forward(self, x, mask):
        "Pass the input (and mask) through each layer in turn."
        for layer in self.layers:
            x = layer(x, mask)
        return self.norm(x)
```

Aplicamos uma camada residual a cada duas camadas, e em seguida, utilizamos uma camada de normalização:

```python
class LayerNorm(nn.Module):
    "Construct a layernorm module (See citation for details)."
    def __init__(self, features, eps=1e-6):
        super(LayerNorm, self).__init__()
        self.a_2 = nn.Parameter(torch.ones(features))
        self.b_2 = nn.Parameter(torch.zeros(features))
        self.eps = eps

    def forward(self, x):
        mean = x.mean(-1, keepdim=True)
        std = x.std(-1, keepdim=True)
        return self.a_2 * (x - mean) / (std + self.eps) + self.b_2
```

Em outras palavras, a saída de cada uma dessas camadas é uma $CamadaNormalizacao(x + SubCamada(x))$, onde $SubCamada(x)$ é a função implementada pela sub-camada. Também aplicamos dropout na saída de cada sub-camada antes de adicionarmos e normalizarmos os valores de entrada.

Para facilitar a conexão entre estas camadas, todas as sub-camadas do modelo assim como as camadas intermediárias produzem saídas cujas dimensões são $d_{model} = 512$

```python
class SublayerConnection(nn.Module):
    """
    A residual connection followed by a layer norm.
    Note for code simplicity the norm is first as opposed to last.
    """
    def __init__(self, size, dropout):
        super(SublayerConnection, self).__init__()
        self.norm = LayerNorm(size)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, sublayer):
        "Apply residual connection to any sublayer with the same size."
        return x + self.dropout(sublayer(self.norm(x)))
```

Cada camada possui duas sub-camadas. A primeira, implementa um mecanismo de intra-atenção múltipla enquanto que a segunda camada implementa um algoritmo de feed-forward posicional

```python
class EncoderLayer(nn.Module):
    "Encoder is made up of self-attn and feed forward (defined below)"
    def __init__(self, size, self_attn, feed_forward, dropout):
        super(EncoderLayer, self).__init__()
        self.self_attn = self_attn
        self.feed_forward = feed_forward
        self.sublayer = clones(SublayerConnection(size, dropout), 2)
        self.size = size

    def forward(self, x, mask):
        "Follow Figure 1 (left) for connections."
        x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))
        return self.sublayer[1](x, self.feed_forward)
```

## Decodificador

O decodificador também é composto de uma pilha de $N = 6$ camadas idênticas

```python
class Decoder(nn.Module):
    "Generic N layer decoder with masking."
    def __init__(self, layer, N):
        super(Decoder, self).__init__()
        self.layers = clones(layer, N)
        self.norm = LayerNorm(layer.size)

    def forward(self, x, memory, src_mask, tgt_mask):
        for layer in self.layers:
            x = layer(x, memory, src_mask, tgt_mask)
        return self.norm(x)
```

Em adição as duas sub-camadas existentes no codificador, o decodificador também possui uma terceira sub-camada, que computa atenção múltipla na saída da pilha de codificadores. De forma similar ao codificador, também utilizamos conexões residuais em cada subcamada, seguidas por uma camada de normalização.

```python
class DecoderLayer(nn.Module):
    "Decoder is made of self-attn, src-attn, and feed forward (defined below)"
    def __init__(self, size, self_attn, src_attn, feed_forward, dropout):
        super(DecoderLayer, self).__init__()
        self.size = size
        self.self_attn = self_attn
        self.src_attn = src_attn
        self.feed_forward = feed_forward
        self.sublayer = clones(SublayerConnection(size, dropout), 3)

    def forward(self, x, memory, src_mask, tgt_mask):
        "Follow Figure 1 (right) for connections."
        m = memory
        x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask))
        x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, src_mask))
        return self.sublayer[2](x, self.feed_forward)
```

Também é necessário que modifiquemos a camada de intra-atenção na pilha de decodificadores para previnir que uma determinada posição atenda à posições subsequentes. Esta modificação introduz um mecanismo de obfuscação (ou máscara) que combinado com o fato dos vetores estarem deslocados uma posição para a direita assegura que as predições na posição $i$ dependerão só e somente de saídas menores que $i$

```python
def subsequent_mask(size):
    "Mask out subsequent positions."
    attn_shape = (1, size, size)
    subsequent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')
    return torch.from_numpy(subsequent_mask) == 0
```

> Below the attention mask shows the position each tgt word (row) is allowed to look at (column). Words are blocked for attending to future words during training.

```python
plt.figure(figsize=(5,5))
plt.imshow(subsequent_mask(20)[0])
None
```

# Atenção

Uma função de atenção pode ser descrita como uma função que mapeia um consulta (`Q`) e um conjunto de chaves e valores (`K` e `V `) onde a consulta, chaves e valores assim como a saída da função são todos vetores. A saída é computada como uma soma ponderada dos valores, onde os pesos atribuídos a cada valor são computados através de uma função de compatibilidade entre a consulta e a sua correspondente chave.

Cada mecanismo individual de atenção é chamado de **atenção de produto escalar** (`Scaled Dot-Product Attention`).
Os dados de entrada consistem de uma consulta e chaves de dimensão $d_K$ e valores de dimensão $d_v$. Nos executamos o produto de cada termo da consulta com cada chave, dividindo cada um por $\sqrt{d_K}$ e aplicando softmax para obter os pesos de cada valor computado.

___
**nota pessoal:**

Há muita confusão quanto aos valores que `Q`, `K` e `V` podem assumir. No grupo de discussões de redes neurais, o aluno que explicou da seguinte forma:

`TODO`

___

Na prática, computamos a função de atenção em um conjunto de consultas simultaneamente. As consultas são agrupadas em uma matriz `Q` assim como as chaves e valores são agrupados em matrizes `K` e `V`. As matrizes de saída são computadas como:

\begin{equation}
    Attention(Q, K, V) = softmax\Big(\frac{QK^T}{\sqrt{d_k}}\Big)V
\end{equation}

```python
def attention(query, key, value, mask=None, dropout=None):
    "Compute 'Scaled Dot Product Attention'"
    d_k = query.size(-1)
    scores = torch.matmul(query, key.transpose(-2, -1)) \
             / math.sqrt(d_k) # primeira caixa roxa (baixo para cima)
    if mask is not None:
        scores = scores.masked_fill(mask == 0, -1e9)
    p_attn = F.softmax(scores, dim = -1) # caixa verde na figura
    if dropout is not None:
        p_attn = dropout(p_attn)
    return torch.matmul(p_attn, value), p_attn

# são retornados dois valores, o primeiro é o resultado da função de anteção (multiplicação de matrizes)
# o segundo é são os vetores de atenção, obtidos após o softmax.
# TODO: indicar onde o vetor de atenção é utilizado em etapas posteriores do notebook
```

As duas funções mais comuns de atenção são atenção-aditiva e atenção-produto-escalar. A atenção por produto escalar é idêntica ao algoritmo acima exceto por um fator escalar (caixa amarela na imagem?) de $\frac{1}{\sqrt{d_k}}$.

Apesar de teoricamente serem semelhantes, a atenção por produto escalar é mais rápida uma vez que ela pode se beneficiar de códigos que optimizam a multiplicação de matrizes. No entanto, há cenários onde a atenção aditiva é superior. Para uma discussão mais detalhada, direcionamos o leitor ao artigo original.

## Atenção múltipla

Atenção múltipla permite ao modelo "atender" mutuamente a diferentes representações em diferentes posições do mesmo sub-espaço.

\begin{equation}
    MultiHead(Q, K, V) = Concat(head_1, head-2,.... head_h)W^O
\end{equation}

onde :

\begin{equation}
    head_i = Attention(QW_i^Q, KW_i^K, VW_i^V)
\end{equation}

e as projeções são matrizes parametrizadas na forma:

* $W_i^Q \in \mathbb{R}^{d_{model} \times d_k}$

* $W_i^K \in \mathbb{R}^{d_{model} \times d_k}$

* $W_i^V \in \mathbb{R}^{d_{model} \times d_k}$

* $W^O \in \mathbb{R}^{hd_v \times d_{model}}$

No artigo, é utilizado $h = 8$ atenções múltiplas, ou cabeças. Para cada uma, nós utilizamos $d_k = d_v = \frac{d_{model}}{h} = 64$. Devido as reduções de dimensionalidade de cada cabeça, o custo computacional total é relativamente similar ao de um mecanismo de atenção com uma única cabeça utilizando as dimensões originais do modelo (i.e. $d_{model} = 512$).

```python
class MultiHeadedAttention(nn.Module):
    def __init__(self, h, d_model, dropout=0.1):
        "Take in model size and number of heads."
        super(MultiHeadedAttention, self).__init__()
        assert d_model % h == 0
        # We assume d_v always equals d_k
        self.d_k = d_model // h
        self.h = h
        # Não estou certo porque é preciso dessas 4 linear layers aqui
        self.linears = clones(nn.Linear(d_model, d_model), 4)
        self.attn = None
        self.dropout = nn.Dropout(p=dropout)

    def forward(self, query, key, value, mask=None):
        "Implements Figure 2"
        if mask is not None:
            # Same mask applied to all h heads.
            mask = mask.unsqueeze(1)
        nbatches = query.size(0)

        # 1) Do all the linear projections in batch from d_model => h x d_k
        query, key, value = \
            [l(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2)
             for l, x in zip(self.linears, (query, key, value))]

        # 2) Apply attention on all the projected vectors in batch.
        x, self.attn = attention(query, key, value, mask=mask,
                                 dropout=self.dropout)

        # 3) "Concat" using a view and apply a final linear.
        x = x.transpose(1, 2).contiguous() \
             .view(nbatches, -1, self.h * self.d_k)
        return self.linears[-1](x)
```

## Aplicações do mecanismo de atenção em nosso modelo

___
**nota pessoal:** copiando e colando a figura para faciliar a explicação a seguir:

O transformador utiliza atenção múltipla de 3 formas distintas:

**1.** Há um mecanismo de atenção entre o codificador e o decodificador, onde as consultas vem da camada de decodificação anterior e a memória com as chaves e valores vem da saída do codificador. Isto serve para replicar o mecanismo de atenção normalmente utilizado em arquiteturas típicas encontradas em codificadores-decodificadores.

> **nota pessoal**: o codificador é o lado esquerdo da figura, o decodificador o lado direito. Então apesar das duas setas que saem do codificador indicarem que elas seriam (Q e K) na verdade a ordem das setas no decodificador é invertida. No caso as setas da caixa laranja superior do decodificador devem ser lidas na seguinte ordem: V, K, Q

**2.** O codificador contém camadas de intra-atenção. Em uma camada de intra-atenção, todas as consultas, chaves e valores vem do mesmo lugar, neste caso, a saída da camada anterior do codificador.

**3.** De forma análoga as camadas do decodificador usam intra-atenção para que cada posição atenda a si mesma e a posições anteriores. No decodificador, é necessário previnir que informações vindas de posições a esquerda da posição atual possam comprometer a propriedade auto-regrassiva de nosso modelo. Para isto, mascaramos qualquer dado de entrada que são oriundos de conexões ilegais.

> **nota pessoal**: explicar melhor essa propriedade

## Mecanismo de Feed-Forward (`Position-wise Feed-Forward Networks`)

Em adição aos mecanismos de atenção nas sub-camadas do modelo, cada uma das camadas de nosso codificador e decodificador contem uma rede densamente conecta com um mecanismo de feed-forward. Este mecanismo é aplicado a cada posição individualmente e separadamente. Este mecanismo consiste de duas camadas lineares com uma ativação ReLU entre as camadas.

\begin{equation}
    FNN(x) = max(0, xW_1 +b_1)W_2 + b_2
\end{equation}

Apesar das transformações lineares serem sempre as mesmas, elas usam diferentes parâmetros em cada camada.
Outra forma de descrever esse mecanismo é pensar nele como duas camadas convolucionais com kernels de tamanho 1. A dimensionalidade da entrada e saída é $d_{model} = 512$ e cada camada linear tem dimensões $d_{ff} = 2048$

```python
class PositionwiseFeedForward(nn.Module):
    "Implements FFN equation."
    def __init__(self, d_model, d_ff, dropout=0.1):
        super(PositionwiseFeedForward, self).__init__()
        self.w_1 = nn.Linear(d_model, d_ff)
        self.w_2 = nn.Linear(d_ff, d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        return self.w_2(self.dropout(F.relu(self.w_1(x))))
```

## Vetores e Sofmax

Similar a outros modelos de sequência, nós utilizamos representações (`embeddings`) previamente aprendidas para converter sequências de entrada em sequências de saída de dimensão $d_{model}$. Também utilizamos uma camada linear e uma função de softmax na saída do decodificador para predizer a probabilidade do próximo token na sequência.

Em nosso modelo, nós compartilhamos as mesmas matrizes ?intermediárias? (`weight matrix`) entre as duas camadas de representações e a camada linear de softmax. Na camada de representação, nós multiplicamos os valores intermediários por $\sqrt{d_{model}}$

```python
class Embeddings(nn.Module):
    def __init__(self, d_model, vocab):
        super(Embeddings, self).__init__()
        self.lut = nn.Embedding(vocab, d_model)
        self.d_model = d_model

    def forward(self, x):
        return self.lut(x) * math.sqrt(self.d_model)
```

## Vetor de Posição

Nosso modelo não contém nenhuma recorrência ou convolução. Entretanto, precisamos utilizar informações sobre a ordem dos tokens na sequência. Para isto, precisamos inferir algo como a posição relativa ou absoluta de um token em nossa sequência de entrada.

Para isto, nós adicionamos vetores posicionais aos vetores de entrada de nossos codificadores e decodificadores. Estes vetores posicionais tem a mesma dimensionalidade dos vetores de entrada de modo que eles possam ser somados.

Neste artigo, são utilizados duas funções de diferentes frequências:

\begin{equation}
    PE(pos, 2i) = sin(pos/10000^{2i/d_{model}})
    \\
    PE(pos, 2i+1) = cos(pos/10000^{2i/d_{model}})
\end{equation}

Onde $pos$ é a i-ésima posição de um token na sequência. `Mais detalhes no artigo.`

```python
class PositionalEncoding(nn.Module):
    "Implement the PE function."
    def __init__(self, d_model, dropout, max_len=5000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(p=dropout)

        # Compute the positional encodings once in log space.
        pe = torch.zeros(max_len, d_model)

        # https://stackoverflow.com/questions/52922445/runtimeerror-exp-not-implemented-for-torch-longtensor
        # Pequeno fix no artigo original devido ao problema acima
        position = torch.arange(0., max_len).unsqueeze(1)
        div_term = torch.exp(torch.arange(0., d_model, 2) * -(math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        pe = pe.unsqueeze(0)
        self.register_buffer('pe', pe)

    def forward(self, x):
        x = x + Variable(self.pe[:, :x.size(1)],
                         requires_grad=False)
        return self.dropout(x)
```

___
> **nota pessoal:** Eu preferi abstrair toda a discussão sobre as funções de seno e cosseno. Intuitivamente, basta que você compreenda que eu preciso codificar a posição do token na sequência. Isto não pode ser feito de forma hard-coded utilizando apenas um índice $i$ porque as posições relativas terão $i$s que conflitarão. Além disso, no final a rede é apenas uma sequência de funções que são utilizadas para computar as probabilidades. Sendo assim, precisamos de uma função que codifique a posição dos tokens.

> Dado um valor suficientemente grande, i.e. 10000.0, se utilizarmos $i$ como a potência deste valor e duas funções distintas para $i$s pares ou ímpares, é provável que as posições nunca colidam.
___

A intuição acima é exemplificada plotando as funções de seno e cosseno (nesse caso com $d_{model} = 20$):

```python
plt.figure(figsize=(15, 5))
pe = PositionalEncoding(20, 0)
y = pe.forward(Variable(torch.zeros(1, 100, 20)))
plt.plot(np.arange(100), y[0, :, 4:8].data.numpy())
plt.legend(["dim %d"%p for p in [4,5,6,7]])
None
```

## Modelo Completo

Para o modelo completo, precisamos definir uma função que recebe hiper-parâmetros de entrada e produz, como saída, o nosso modelo.

Relembrando todos os parâmetros de entrada:

`EncoderDecoder(encoder, decoder, src_embed, tgt_embed, generator)`

onde:

* `Encoder(layer, N)`
   * `EncoderLayer(size, self_attn, feed_forward, dropout)`

* `Decoder(layer, N)`
   * `DecoderLayer(size, self_attn, src_attn, feed_forward, dropout)`

```python
def make_model(src_vocab, tgt_vocab, N=6,
               d_model=512, d_ff=2048, h=8, dropout=0.1):
    "Helper: Construct a model from hyperparameters."
    c = copy.deepcopy

    # mecanismos utilizados tanto no encoder quanto decoder
    attn = MultiHeadedAttention(h, d_model) # mecanismo de atenção múltipla
    ff = PositionwiseFeedForward(d_model, d_ff, dropout) # feed forward
    position = PositionalEncoding(d_model, dropout) # vetores posicionais

    model = EncoderDecoder(
        Encoder(EncoderLayer(d_model, c(attn), c(ff), dropout), N),
        Decoder(DecoderLayer(d_model, c(attn), c(attn),
                             c(ff), dropout), N),
        nn.Sequential(Embeddings(d_model, src_vocab), c(position)),
        nn.Sequential(Embeddings(d_model, tgt_vocab), c(position)),
        Generator(d_model, tgt_vocab))

    # This was important from their code.
    # Initialize parameters with Glorot / fan_avg.
    for p in model.parameters():
        if p.dim() > 1:
            nn.init.xavier_uniform(p)
    return model
```

> **nota pessoal:** phew! Se você leu tudo até aqui, obrigado pelo esforço e paciência.

```python
# Small example model.
tmp_model = make_model(10, 10, 2)
None
```

# Treino

```python

```

```python

```

```python

```

## Batches e obfuscação

```python
class Batch:
    "Object for holding a batch of data with mask during training."
    def __init__(self, src, trg=None, pad=0):
        self.src = src
        self.src_mask = (src != pad).unsqueeze(-2)
        if trg is not None:
            self.trg = trg[:, :-1]
            self.trg_y = trg[:, 1:]
            self.trg_mask = \
                self.make_std_mask(self.trg, pad)
            self.ntokens = (self.trg_y != pad).data.sum()

    @staticmethod
    def make_std_mask(tgt, pad):
        "Create a mask to hide padding and future words."
        tgt_mask = (tgt != pad).unsqueeze(-2)
        tgt_mask = tgt_mask & Variable(
            subsequent_mask(tgt.size(-1)).type_as(tgt_mask.data))
        return tgt_mask
```

## Loop de treino

```python
def run_epoch(data_iter, model, loss_compute):
    "Standard Training and Logging Function"
    start = time.time()
    total_tokens = 0
    total_loss = 0
    tokens = 0
    for i, batch in enumerate(data_iter):
        out = model.forward(batch.src, batch.trg,
                            batch.src_mask, batch.trg_mask)
        loss = loss_compute(out, batch.trg_y, batch.ntokens)
        total_loss += loss
        total_tokens += batch.ntokens
        tokens += batch.ntokens
        if i % 50 == 1:
            elapsed = time.time() - start
            print("Epoch Step: %d Loss: %f Tokens per Sec: %f" %
                    (i, loss / batch.ntokens, tokens / elapsed))
            start = time.time()
            tokens = 0
    return total_loss / total_tokens
```

```python

```

## Training Data and Batching

```python
global max_src_in_batch, max_tgt_in_batch
def batch_size_fn(new, count, sofar):
    "Keep augmenting batch and calculate total number of tokens + padding."
    global max_src_in_batch, max_tgt_in_batch
    if count == 1:
        max_src_in_batch = 0
        max_tgt_in_batch = 0
    max_src_in_batch = max(max_src_in_batch,  len(new.src))
    max_tgt_in_batch = max(max_tgt_in_batch,  len(new.trg) + 2)
    src_elements = count * max_src_in_batch
    tgt_elements = count * max_tgt_in_batch
    return max(src_elements, tgt_elements)
```

## Hardware

```python

```

## Optimizer

```python
class NoamOpt:
    "Optim wrapper that implements rate."
    def __init__(self, model_size, factor, warmup, optimizer):
        self.optimizer = optimizer
        self._step = 0
        self.warmup = warmup
        self.factor = factor
        self.model_size = model_size
        self._rate = 0

    def step(self):
        "Update parameters and rate"
        self._step += 1
        rate = self.rate()
        for p in self.optimizer.param_groups:
            p['lr'] = rate
        self._rate = rate
        self.optimizer.step()

    def rate(self, step = None):
        "Implement `lrate` above"
        if step is None:
            step = self._step
        return self.factor * \
            (self.model_size ** (-0.5) *
            min(step ** (-0.5), step * self.warmup ** (-1.5)))

def get_std_opt(model):
    return NoamOpt(model.src_embed[0].d_model, 2, 4000,
            torch.optim.Adam(model.parameters(), lr=0, betas=(0.9, 0.98), eps=1e-9))
```

```python

```

```python
# Three settings of the lrate hyperparameters.
opts = [NoamOpt(512, 1, 4000, None),
        NoamOpt(512, 1, 8000, None),
        NoamOpt(256, 1, 4000, None)]
plt.plot(np.arange(1, 20000), [[opt.rate(i) for opt in opts] for i in range(1, 20000)])
plt.legend(["512:4000", "512:8000", "256:4000"])
None
```

```python

```

## Regularização

```python
class LabelSmoothing(nn.Module):
    "Implement label smoothing."
    def __init__(self, size, padding_idx, smoothing=0.0):
        super(LabelSmoothing, self).__init__()
        self.criterion = nn.KLDivLoss(size_average=False)
        self.padding_idx = padding_idx
        self.confidence = 1.0 - smoothing
        self.smoothing = smoothing
        self.size = size
        self.true_dist = None

    def forward(self, x, target):
        assert x.size(1) == self.size
        true_dist = x.data.clone()
        true_dist.fill_(self.smoothing / (self.size - 2))
        true_dist.scatter_(1, target.data.unsqueeze(1), self.confidence)
        true_dist[:, self.padding_idx] = 0
        mask = torch.nonzero(target.data == self.padding_idx)
        if mask.dim() > 0:
            true_dist.index_fill_(0, mask.squeeze(), 0.0)
        self.true_dist = true_dist
        return self.criterion(x, Variable(true_dist, requires_grad=False))
```

```python

```

```python
# Example of label smoothing.
crit = LabelSmoothing(5, 0, 0.4)
predict = torch.FloatTensor([[0, 0.2, 0.7, 0.1, 0],
                             [0, 0.2, 0.7, 0.1, 0],
                             [0, 0.2, 0.7, 0.1, 0]])
v = crit(Variable(predict.log()),
         Variable(torch.LongTensor([2, 1, 0])))

# Show the target distributions expected by the system.
plt.imshow(crit.true_dist)
None
```

```python

```

```python
crit = LabelSmoothing(5, 0, 0.1)
def loss(x):
    d = x + 3 * 1
    predict = torch.FloatTensor([[0, x / d, 1 / d, 1 / d, 1 / d],
                                 ])
    #checar -- https://github.com/NVIDIA/flownet2-pytorch/issues/113
    return crit(Variable(predict.log()),
                 Variable(torch.LongTensor([1]))).data
plt.plot(np.arange(1, 100), [loss(x) for x in range(1, 100)])
None
```

# Exemplo Inicial

## Dados sintéticos

```python
def data_gen(V, batch, nbatches):
    "Generate random data for a src-tgt copy task."
    for i in range(nbatches):
        data = torch.from_numpy(np.random.randint(1, V, size=(batch, 10)))
        data[:, 0] = 1
        src = Variable(data, requires_grad=False)
        tgt = Variable(data, requires_grad=False)
        yield Batch(src, tgt, 0)
```

```python
class SimpleLossCompute:
    "A simple loss compute and train function."
    def __init__(self, generator, criterion, opt=None):
        self.generator = generator
        self.criterion = criterion
        self.opt = opt

    def __call__(self, x, y, norm):
        x = self.generator(x)
        loss = self.criterion(x.contiguous().view(-1, x.size(-1)),
                              y.contiguous().view(-1)) / norm
        loss.backward()
        if self.opt is not None:
            self.opt.step()
            self.opt.optimizer.zero_grad()
        return loss.data * norm
```

```python
# Train the simple copy task.
V = 11
criterion = LabelSmoothing(size=V, padding_idx=0, smoothing=0.0)
model = make_model(V, V, N=2)
model_opt = NoamOpt(model.src_embed[0].d_model, 1, 400,
        torch.optim.Adam(model.parameters(), lr=0, betas=(0.9, 0.98), eps=1e-9))

for epoch in range(10):
    model.train()
    run_epoch(data_gen(V, 30, 20), model,
              SimpleLossCompute(model.generator, criterion, model_opt))
    model.eval()
    print(run_epoch(data_gen(V, 30, 5), model,
                    SimpleLossCompute(model.generator, criterion, None)))
```

```python
def greedy_decode(model, src, src_mask, max_len, start_symbol):
    memory = model.encode(src, src_mask)
    ys = torch.ones(1, 1).fill_(start_symbol).type_as(src.data)
    for i in range(max_len-1):
        out = model.decode(memory, src_mask,
                           Variable(ys),
                           Variable(subsequent_mask(ys.size(1))
                                    .type_as(src.data)))
        prob = model.generator(out[:, -1])
        _, next_word = torch.max(prob, dim = 1)
        next_word = next_word.data[0]
        ys = torch.cat([ys,
                        torch.ones(1, 1).type_as(src.data).fill_(next_word)], dim=1)
    return ys

model.eval()
src = Variable(torch.LongTensor([[1,2,3,4,5,6,7,8,9,10]]) )
src_mask = Variable(torch.ones(1, 1, 10) )
print(greedy_decode(model, src, src_mask, max_len=10, start_symbol=1))
```

```python

```

```python

```

## Outras referências:

* [Jay Alammar blog](http://jalammar.github.io/illustrated-transformer/)
* [The transformer neural network](https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html)


# nbconvert latex test

**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus bibendum felis dictum sodales. Ut suscipit, orci ut interdum imperdiet, purus ligula mollis *justo*, non malesuada nisl augue eget lorem. Donec bibendum, erat sit amet porttitor aliquam, urna lorem ornare libero, in vehicula diam diam ut ante. Nam non urna rhoncus, accumsan elit sit amet, mollis tellus. Vestibulum nec tellus metus. Vestibulum tempor, ligula et vehicula rhoncus, sapien turpis faucibus lorem, id dapibus turpis mauris ac orci. Sed volutpat vestibulum venenatis.

$\LaTeX ~ \TeX$

This is a test list:

1. item 1
   - subitem 1
   - subitem 2
2. item 2
3. item 3

## Printed Using Python

```python
next_paragraph = """
Aenean vitae diam consectetur, tempus arcu quis, ultricies urna. Vivamus venenatis sem
quis orci condimentum, sed feugiat dui porta.
"""

def identity_dec(ob):
    return ob

@identity_dec
def nifty_print(text):
    """Used to test syntax highlighting"""

    print(text * 2)

nifty_print(next_paragraph)
```

    Aenean vitae diam consectetur, tempus arcu quis, ultricies urna. Vivamus venenatis sem
    quis orci condimentum, sed feugiat dui porta.

    Aenean vitae diam consectetur, tempus arcu quis, ultricies urna. Vivamus venenatis sem
    quis orci condimentum, sed feugiat dui porta.

## Pyout (and Text Wrapping)

```python
Text = """
Aliquam blandit aliquet enim, eget scelerisque eros adipiscing quis. Nunc sed metus
ut lorem condimentum condimentum nec id enim. Sed malesuada cursus hendrerit. Praesent
et commodo justo. Interdum et malesuada fames ac ante ipsum primis in faucibus.
Curabitur et magna ante. Proin luctus tellus sit amet egestas laoreet. Sed dapibus
neque ac nulla mollis cursus. Fusce mollis egestas libero mattis facilisis.
"""
Text #Use print(Text) instead to get text wrapping in pdf
```

    '\nAliquam blandit aliquet enim, eget scelerisque eros adipiscing quis. Nunc sed metus \nut lorem condimentum condimentum nec id enim. Sed malesuada cursus hendrerit. Praesent \net commodo justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. \nCurabitur et magna ante. Proin luctus tellus sit amet egestas laoreet. Sed dapibus \nneque ac nulla mollis cursus. Fusce mollis egestas libero mattis facilisis.\n'

```python
print(Text)
```

    Aliquam blandit aliquet enim, eget scelerisque eros adipiscing quis. Nunc sed metus
    ut lorem condimentum condimentum nec id enim. Sed malesuada cursus hendrerit. Praesent
    et commodo justo. Interdum et malesuada fames ac ante ipsum primis in faucibus.
    Curabitur et magna ante. Proin luctus tellus sit amet egestas laoreet. Sed dapibus
    neque ac nulla mollis cursus. Fusce mollis egestas libero mattis facilisis.

```python
import numpy as np

a = np.random.rand(10,10)
print(a)
a
```

    [[0.28568166 0.8747998  0.87645362 0.51011938 0.06167899 0.6253242
      0.21695898 0.35406203 0.76399062 0.38721428]
     [0.59226394 0.23033422 0.11576507 0.0131951  0.34366223 0.96629731
      0.2867491  0.95194302 0.60324146 0.55986092]
     [0.36955543 0.78864789 0.73933855 0.39474922 0.74616752 0.9144543
      0.88600249 0.42611302 0.49375306 0.4260594 ]
     [0.40550295 0.85035162 0.5525894  0.21827199 0.67949174 0.93909704
      0.0331135  0.27240638 0.39332899 0.19852766]
     [0.32876315 0.97305405 0.11060386 0.20685979 0.3897287  0.01538051
      0.44747911 0.99865014 0.89374066 0.5141975 ]
     [0.10450336 0.42284722 0.95628045 0.32792639 0.11370905 0.32150692
      0.28631773 0.58203321 0.21240863 0.87954985]
     [0.62257223 0.79092658 0.72718477 0.0039627  0.61581427 0.28007586
      0.4653752  0.24737437 0.97801711 0.31160009]
     [0.03592867 0.56885907 0.05229575 0.12322391 0.45236765 0.98892923
      0.15013782 0.81404334 0.71795481 0.60145161]
     [0.01582381 0.23420526 0.18574213 0.6497537  0.71730148 0.0068443
      0.32733317 0.81837686 0.58895758 0.37633478]
     [0.64226276 0.77550803 0.23729951 0.9287232  0.14250076 0.23955818
      0.70490581 0.84959453 0.46939408 0.01230405]]

    array([[0.28568166, 0.8747998 , 0.87645362, 0.51011938, 0.06167899,
            0.6253242 , 0.21695898, 0.35406203, 0.76399062, 0.38721428],
           [0.59226394, 0.23033422, 0.11576507, 0.0131951 , 0.34366223,
            0.96629731, 0.2867491 , 0.95194302, 0.60324146, 0.55986092],
           [0.36955543, 0.78864789, 0.73933855, 0.39474922, 0.74616752,
            0.9144543 , 0.88600249, 0.42611302, 0.49375306, 0.4260594 ],
           [0.40550295, 0.85035162, 0.5525894 , 0.21827199, 0.67949174,
            0.93909704, 0.0331135 , 0.27240638, 0.39332899, 0.19852766],
           [0.32876315, 0.97305405, 0.11060386, 0.20685979, 0.3897287 ,
            0.01538051, 0.44747911, 0.99865014, 0.89374066, 0.5141975 ],
           [0.10450336, 0.42284722, 0.95628045, 0.32792639, 0.11370905,
            0.32150692, 0.28631773, 0.58203321, 0.21240863, 0.87954985],
           [0.62257223, 0.79092658, 0.72718477, 0.0039627 , 0.61581427,
            0.28007586, 0.4653752 , 0.24737437, 0.97801711, 0.31160009],
           [0.03592867, 0.56885907, 0.05229575, 0.12322391, 0.45236765,
            0.98892923, 0.15013782, 0.81404334, 0.71795481, 0.60145161],
           [0.01582381, 0.23420526, 0.18574213, 0.6497537 , 0.71730148,
            0.0068443 , 0.32733317, 0.81837686, 0.58895758, 0.37633478],
           [0.64226276, 0.77550803, 0.23729951, 0.9287232 , 0.14250076,
            0.23955818, 0.70490581, 0.84959453, 0.46939408, 0.01230405]])

### Image

```python
from IPython.core.display import Image
Image(data="http://ipython.org/_static/IPy_header.png")
```

```python
print('text')
```

    text

```python
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
```

```python
x = np.arange(1,100)
y = np.sin(x)
plt.plot(x,y)
plt.show()
```

### Operator Highlighing Check

```python
#This is a comment with an operation x @ y in it.
test = 5**9 + 2 - x@ y / (7 % 2) + True * 7
print(test)

a = set([1,2,3,4,5,6,7,8,9,0])
b = set([2,4,6,8,0])
a & b
```

    1953188.1556827284

    {0, 2, 4, 6, 8}

#### Pandas Output

Here we test the output of **Pandas**

First a *markdown* table:

Column 1 | Column 2
---------|-----------
 1       | 3
~~a~~    | b
        4| &

#### Pandas

```python
import pandas as pd
pd.DataFrame(np.random.randn(10,3))
```

      0
      1
      2

      0
      -1.565342
      -0.260043
      -1.427162

      1
      -0.267812
      1.022688
      -0.268030

      2
      0.104852
      0.415075
      0.958796

      3
      0.210758
      -0.500437
      -1.584460

      4
      -0.754263
      -2.317940
      -0.384726

      5
      -0.062044
      -0.804551
      0.914101

      6
      -2.193517
      2.356933
      0.542824

      7
      -1.246683
      0.981807
      -0.216905

      8
      -0.784741
      -0.647911
      0.134776

      9
      0.008086
      1.652312
      -0.468785

##### Sympy output

```python
import sympy
from sympy.abc import x, n, m
sympy.init_printing()
theta = sympy.Symbol('theta')
phi = sympy.Symbol('phi')

sympy.simplify(sympy.Ynm(n,m,theta,phi).expand(func=True))
```

x + y as plain text.

$\frac{P_{n}^{\left(m\right)}\left(\cos{\left (\theta \right )}\right)}{2 \sqrt{\pi}} \sqrt{\frac{\left(- m + n\right)!}{\left(m + n\right)!} \left(2 n + 1\right)} e^{i m \phi}$

##### Line Length

```python
1 3 5 7 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 60 63 66 69 72 75 78 81 84 87 90 93 96 99 103
```


## Initial setup

```python
from google.colab import drive
drive.mount('/content/drive')
```

    Mounted at /content/drive

```python
import tensorflow as tf
print(tf.__version__)
import torch
print(torch.__version__)
import matplotlib
print(matplotlib.__version__)
```

    2.8.0
    1.10.0+cu111
    3.2.2

```python
!nvidia-smi
```

    Fri Jan 21 19:41:32 2022
    +-----------------------------------------------------------------------------+
    | NVIDIA-SMI 495.46       Driver Version: 460.32.03    CUDA Version: 11.2     |
    |-------------------------------+----------------------+----------------------+
    | GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
    | Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
    |                               |                      |               MIG M. |
    |===============================+======================+======================|
    |   0  Tesla T4            Off  | 00000000:00:04.0 Off |                    0 |
    | N/A   34C    P8     9W /  70W |      0MiB / 15109MiB |      0%      Default |
    |                               |                      |                  N/A |
    +-------------------------------+----------------------+----------------------+

    +-----------------------------------------------------------------------------+
    | Processes:                                                                  |
    |  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
    |        ID   ID                                                   Usage      |
    |=============================================================================|
    |  No running processes found                                                 |
    +-----------------------------------------------------------------------------+

```python
# Other imports
! pip install tensorflow_addons
! pip install tensorflow_io

import os
from tensorflow.keras.layers import *
from tensorflow.keras.models import *
from keras.callbacks import Callback, EarlyStopping, ModelCheckpoint
from tensorflow.keras.applications.resnet50 import preprocess_input
from tensorflow.keras.preprocessing.image import load_img

import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
from matplotlib.ticker import MultipleLocator, FormatStrFormatter, AutoMinorLocator
from imutils import paths
from tqdm import tqdm
import tensorflow as tf
import tensorflow_addons as tfa
import tensorflow_datasets as tfds
import tensorflow_io as tfio
import tensorflow_hub as hub
import numpy as np
import cv2
import pandas as pd
import seaborn as sns
from scipy.stats import mannwhitneyu
from sklearn.preprocessing import LabelEncoder
from sklearn.cluster import KMeans
import sklearn.manifold
from sklearn.metrics.pairwise import cosine_similarity as cos
from sympy.utilities.iterables import multiset_permutations
from sklearn.metrics import accuracy_score, f1_score,precision_score, recall_score, roc_auc_score, confusion_matrix
from sklearn.model_selection import *
from sklearn.preprocessing import StandardScaler
from IPython.display import Image, display

import zipfile
import concurrent.futures

# Random seed fix
random_seed = 42
tf.random.set_seed(random_seed)
np.random.seed(random_seed)
```

    Collecting tensorflow_addons
      Downloading tensorflow_addons-0.16.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (1.1 MB)
         |████████████████████████████████| 1.1 MB 6.4 MB/s
    Requirement already satisfied: typeguard>=2.7 in /usr/local/lib/python3.7/dist-packages (from tensorflow_addons) (2.7.1)
    Installing collected packages: tensorflow-addons
    Successfully installed tensorflow-addons-0.16.1
    Collecting tensorflow_io
      Downloading tensorflow_io-0.24.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (23.4 MB)
         |████████████████████████████████| 23.4 MB 1.8 MB/s
    Requirement already satisfied: tensorflow-io-gcs-filesystem==0.24.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow_io) (0.24.0)
    Installing collected packages: tensorflow-io
    Successfully installed tensorflow-io-0.24.0

## Dataset gathering and preparation

```python
%cd /content/drive/MyDrive/nanowire-morphology-classification-project
```

```python
training_batch_size = 4

BATCH_SIZE = training_batch_size

imageSize = 224

category_names = ['bundle', 'dispersed', 'network', 'singular']
color_method = ['C0', 'C1', 'C2', 'C3', 'C4']
color = ['black', 'magenta', 'cyan', 'yellow']
marker = ['o', 's', '<', '>', '^']
seaborn_palette = sns.color_palette("colorblind")
```

```python
np.random.seed(random_seed)
peptide_morph_train_path = "/content/drive/MyDrive/TEM image datasets/2022-nanowire-morphology"
peptide_morph_images_train = list(paths.list_files(basePath=peptide_morph_train_path, validExts='jpg'))
peptide_morph_images_train = np.random.choice(np.array(peptide_morph_images_train), len(peptide_morph_images_train), replace=False)
print(len(peptide_morph_images_train))
```

    400

```python
train_labels = []
for i in range(peptide_morph_images_train.shape[0]):
  train_label = peptide_morph_images_train[i].split("/")[-2]
  train_labels.append(train_label)
le = LabelEncoder()
peptide_morph_train_enc = le.fit_transform(train_labels)
```

```python
# Image preprocessing utils
@tf.function
def parse_images(image_path):
    image_string = tf.io.read_file(image_path)
    image = tf.image.decode_jpeg(image_string, channels=3)
    # image = tfio.experimental.image.decode_tiff(image_string)[:, :, :3]   # in the doc, it transforms tiff to 4 channels, with additional channel of opacity which is not needed.
    image = tf.image.convert_image_dtype(image, tf.float32)
    image = tf.image.resize(image, size=[imageSize, imageSize])

    return image
```

```python
train_ds = tf.data.Dataset.from_tensor_slices(peptide_morph_images_train)
train_ds = (
    train_ds
    .map(parse_images, num_parallel_calls=tf.data.experimental.AUTOTUNE)
    # .shuffle(200)
    .batch(training_batch_size
          #  , drop_remainder=True
           )
    .prefetch(tf.data.experimental.AUTOTUNE)
)

datagen = tf. keras.preprocessing.image.ImageDataGenerator(preprocessing_function=tf.keras.applications.resnet50.preprocess_input)
```

```python
np.random.seed(random_seed)
peptide_morph_seglabel_train_path = "/content/drive/MyDrive/TEM image datasets/2022-nanowire-morphology"
peptide_morph_images_train_seglabel = list(paths.list_files(basePath=peptide_morph_seglabel_train_path, validExts='png'))
peptide_morph_images_train_seglabel = np.random.choice(np.array(peptide_morph_images_train_seglabel), len(peptide_morph_images_train_seglabel), replace=False)
print(len(peptide_morph_images_train_seglabel))
```

```python
def generate_ground_truth_images(image, resolution):
  image_bool = np.ones((resolution, resolution))
  for i in range(image.shape[0]):
    for j in range(image.shape[1]):
      if image[i, j, 1] == image[i, j, 2]:
        image_bool[i, j] = 0            # background is black with code of 0
      else:
        image_bool[i, j] = 1            # nanowire is white with code of 1
  return image_bool
```

```python
segmentation_class_labels = []
for i in range(peptide_morph_images_train_seglabel.shape[0]):
  seg_class_label = peptide_morph_images_train_seglabel[i].split("/")[-2]
  segmentation_class_labels.append(seg_class_label)
le = LabelEncoder()
peptide_morph_train_seg_enc = le.fit_transform(segmentation_class_labels)
```

```python
# image_mask = np.zeros((len(peptide_morph_images_train_seglabel), imageSize, imageSize))
images_no_annotation_directory = []
images_no_annotation = np.zeros((len(peptide_morph_images_train_seglabel), imageSize, imageSize, 3))

for i in range(len(peptide_morph_images_train_seglabel)):
  images_no_annotation_directory.append('%s.jpg' % (peptide_morph_images_train_seglabel[i].split("_")[0] + "_" + peptide_morph_images_train_seglabel[i].split("_")[1]))
  images_no_annotation[i] = parse_images(images_no_annotation_directory[i])

  # these were used to create the ground truth grayscale images from the manual segmentation labels.
  # image_string = tf.io.read_file(peptide_morph_images_train_seglabel[i])
  # image = tf.image.decode_image(image_string, channels=3) / 255
  # image = tf.image.resize(image, (imageSize, imageSize))
  # image = tf.image.convert_image_dtype(image, tf.float32)
  # trans_nd_image_array = image.numpy()
  # image_mask[i] = generate_ground_truth_images(trans_nd_image_array, imageSize)

# np.savez_compressed('seg_mask_res512.npz', mask=image_mask)

# once we have the seg_mask saved, we can directly load from npz file
image_mask = np.load('seg_mask_res%i.npz' % (imageSize), allow_pickle=True)['mask']
```

## Initiate self-supervised models

```python
Resnet50_transfer = tf.keras.applications.ResNet50(
    include_top=False,
    weights="imagenet",
    input_tensor=None,
    input_shape=(imageSize, imageSize, 3),
    pooling=None,
)

Resnet50_transfer.trainable = False
```

    Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/resnet/resnet50_weights_tf_dim_ordering_tf_kernels_notop.h5
    94773248/94765736 [==============================] - 1s 0us/step
    94781440/94765736 [==============================] - 1s 0us/step

```python
# Resnet as backbone
def get_resnet_self_supervise_model(hidden_1, hidden_2, hidden_3):
    base_model = Resnet50_transfer
    base_model.trainable = True
    inputs = Input((imageSize, imageSize, 3))
    h = base_model(inputs, training=True)
    h = GlobalAveragePooling2D()(h)

    projection_1 = Dense(hidden_1)(h)
    projection_1 = Activation("relu")(projection_1)
    projection_1 = BatchNormalization(epsilon=0.001)(projection_1)
    # projection_1 = Dropout(rate=0.5)(projection_1)
    projection_2 = Dense(hidden_2)(projection_1)
    projection_2 = Activation("relu")(projection_2)
    projection_2 = BatchNormalization(epsilon=0.001)(projection_2)
    # projection_2 = Dropout(rate=0.5)(projection_2)
    projection_3 = Dense(hidden_3)(projection_2)
    projection_3 = BatchNormalization(epsilon=0.001)(projection_3)
    # projection_3 = Dropout(rate=0.5)(projection_3)

    resnet_model = Model(inputs, projection_3)

    return resnet_model
```

## Initiate downstream classification model

```python
def get_linear_model(features):
    linear_model = Sequential([
			                              Input(shape=(features,)),
		                                Dense(4, activation="softmax")])
    return linear_model
```

## One-shot learning and tsne visualizations

```python
def visualize_scatter_with_images(X_2d_data, images_directory, true_label, ax, image_zoom=1, boxthickness=2):

    for xy, i, label in zip(X_2d_data, images_directory, true_label):
        x0, y0 = xy
        img = parse_images(i)
        img = OffsetImage(img, zoom=image_zoom)
        ab = AnnotationBbox(img, (x0, y0), xycoords='data', frameon=True,
                            bboxprops =dict(edgecolor=color[label], lw=boxthickness), pad=1)
        artists.append(ax.add_artist(ab))
    ax.update_datalim(X_2d_data)
```

```python
def visualize_anchor_images(X_2d_data, images_directory, true_label, ax, image_zoom=1, boxthickness=2):

    for xy, i, label in zip(X_2d_data, images_directory, true_label):
        x0, y0 = xy
        img = parse_images(i)
        img = OffsetImage(img, zoom=image_zoom)
        ab = AnnotationBbox(img, (x0, y0), xycoords='data', frameon=True,
                            bboxprops =dict(edgecolor=color[label], lw=boxthickness), pad=0)
        artists.append(ax.add_artist(ab))
    ax.update_datalim(X_2d_data)
```

```python
# calculate nanowire pixel density for each segmentation mask from the two morphologies
nanowire_pixel_density = np.zeros((image_mask.shape[0]))
for i in range(image_mask.shape[0]):
  nanowire_pixel_density[i] = np.sum(image_mask[i]) / imageSize / imageSize
fig, ax = plt.subplots(figsize=(5, 5))
plt.hist(nanowire_pixel_density[np.where(peptide_morph_train_seg_enc == 0)], color=color[1], label='dispersed')
plt.hist(nanowire_pixel_density[np.where(peptide_morph_train_seg_enc == 1)], color=color[2], label='network')
plt.legend(fontsize=16)
ax.tick_params(labelsize=16, length=4)
ax.set_xlabel(r'nanowire pixel density', fontsize=20, fontweight='bold')
ax.set_ylabel(r'image count', fontsize=20, fontweight='bold')
ax.xaxis.set_major_locator(MultipleLocator(0.10))
ax.xaxis.set_minor_locator(MultipleLocator(0.05))
ax.xaxis.set_major_formatter(FormatStrFormatter('%1.2f'))
ax.set_xlim(-0.02, 0.52)
```

```python
# this code block is for generating tsne plots and nanowire density plots only for the good and bad anchors for each five encoder.
# Random seed fix
random_seed_list = np.array([42, 43, 44, 45, 46])
random_seed_for_split = np.linspace(42, 42 + 19, 20).astype(int)

# list of models
models = ['barlow_tem']

plt.rcParams['axes.prop_cycle'] = plt.cycler(color=plt.cm.tab10.colors)

classification_result = {}
for i in range(len(models)):
  y = np.load('classify_scores_main_result/%s_classification_result.npz' % (models[i])
      , allow_pickle=True)
  classification_result.update({'%s' % (models[i]): y['scores']})

for i in range(len(random_seed_list)):

  seed_max = np.argmax(classification_result['%s' % (models[0])][-1][i], axis=0)[0]
  seed_min = np.argmin(classification_result['%s' % (models[0])][-1][i], axis=0)[0]
  seed_to_look = np.array([seed_max, seed_min])
  print(np.max(classification_result['%s' % (models[0])][-1][i], axis=0))
  print(np.min(classification_result['%s' % (models[0])][-1][i], axis=0))

  resnet_model = get_resnet_self_supervise_model(128, 64, 1024)
  resnet_model.load_weights('barlow_resnet_batch64_project128_64_1024_seed%i.h5' % (random_seed_list[i]))
  resnet_model.layers[1].trainable = False

  feature_extraction_model = Model(resnet_model.input, resnet_model.layers[-9].output)

  # Extract train and test features
  features = feature_extraction_model.predict(train_ds)

  for j in range(2):

    train_feature, test_feature, train_label, test_label = train_test_split(features, peptide_morph_train_enc, test_size=0.2, shuffle=True, stratify=peptide_morph_train_enc, random_state=random_seed_for_split[seed_to_look[j]])
    train_filename, test_filename, train_label, test_label = train_test_split(peptide_morph_images_train, peptide_morph_train_enc, test_size=0.2, shuffle=True, stratify=peptide_morph_train_enc, random_state=random_seed_for_split[seed_to_look[j]])
    train_label_storage = pd.DataFrame(np.concatenate((train_filename.reshape(1, -1).transpose(), train_label.reshape(1, -1).transpose()), axis=-1), columns=['filename', 'label'])
    test_label_storage = pd.DataFrame(np.concatenate((test_filename.reshape(1, -1).transpose(), test_label.reshape(1, -1).transpose()), axis=-1), columns=['filename', 'label'])
    train_feature_map = train_feature[train_label_storage.groupby('label').sample(n=1, random_state = 42).index.to_numpy()]
    train_images_directory = train_label_storage.groupby('label').sample(n=1, random_state = 42)['filename'].to_numpy().astype(str)
    train_images_label = train_label_storage.groupby('label').sample(n=1, random_state = 42)['label'].to_numpy().astype(int)

    img1 = load_img(train_images_directory[0])
    img2 = load_img(train_images_directory[1])
    img3 = load_img(train_images_directory[2])
    img4 = load_img(train_images_directory[3])
    images_to_plot = np.asarray([np.array(img1), np.array(img2), np.array(img3), np.array(img4)])

    f, ax = plt.subplots(ncols=4, nrows=1, figsize=(10, 10))
    for m in range(4):
        ax[m].set_title(category_names[m], fontsize=14)
        ax[m].imshow(images_to_plot[m])
        ax[m].axis('off')
    plt.tight_layout()
    plt.show()

    # checking the nanowire_pixel_densities of the anchor and test images from dispersed and network morphologies
    dispersed_anchor_filename = train_images_directory[1].split(".")[0] + "_label.png"
    network_anchor_filename = train_images_directory[2].split(".")[0] + "_label.png"

    dispersed_anchor = nanowire_pixel_density[np.where(peptide_morph_images_train_seglabel==dispersed_anchor_filename)]
    network_anchor = nanowire_pixel_density[np.where(peptide_morph_images_train_seglabel==network_anchor_filename)]
    print(dispersed_anchor)
    print(network_anchor)

    dispersed_filenames = [test_filename[np.where(test_label==1)][i].split(".")[0] + "_label.png" for i in range(20)]
    dispersed_pixel_densities = np.array([nanowire_pixel_density[np.where(peptide_morph_images_train_seglabel==dispersed_filenames[i])] for i in range(20)]).reshape(-1)
    network_filenames = [test_filename[np.where(test_label==2)][i].split(".")[0] + "_label.png" for i in range(20)]
    network_pixel_densities = np.array([nanowire_pixel_density[np.where(peptide_morph_images_train_seglabel==network_filenames[i])] for i in range(20)]).reshape(-1)
    fig, ax = plt.subplots(figsize=(5, 5))
    plt.hist(dispersed_pixel_densities, color=color[1], label='dispersed')
    plt.hist(network_pixel_densities, color=color[2], label='network')
    plt.vlines(dispersed_anchor, 0, 4, colors='k', ls='solid')
    plt.vlines(np.median(dispersed_pixel_densities), 0, 4, colors='k', ls='--')
    plt.vlines(network_anchor, 0, 4, colors='k', ls='solid')
    plt.vlines(np.median(network_pixel_densities), 0, 4, colors='k', ls='--')
    plt.legend(fontsize=16)
    ax.tick_params(labelsize=16, length=4)
    ax.set_xlabel(r'nanowire pixel density', fontsize=20, fontweight='bold')
    ax.set_ylabel(r'image count', fontsize=20, fontweight='bold')
    ax.xaxis.set_major_locator(MultipleLocator(0.10))
    ax.xaxis.set_minor_locator(MultipleLocator(0.05))
    ax.xaxis.set_major_formatter(FormatStrFormatter('%1.2f'))
    ax.set_xlim(-0.02, 0.52)

    # unsupervised tsne cluster assignment visualization
    tsne = sklearn.manifold.TSNE(n_components=2, perplexity=50, init='pca', n_jobs=8, random_state=42)
    test_tsne = np.vstack((test_feature, train_feature_map))
    test_tsne = np.array(tsne.fit_transform(test_tsne))
    test_tsne = StandardScaler().fit_transform(test_tsne)

    # plot the tsne plot with true labels
    fig, ax = plt.subplots(figsize=(80, 80))
    artists = []

    visualize_scatter_with_images(test_tsne[:-4], test_filename, test_label, ax, image_zoom=1.00, boxthickness=8)
    visualize_anchor_images(test_tsne[-4:], train_images_directory, train_images_label, ax, image_zoom=1.00, boxthickness=40)

    ax.autoscale()
    plt.show()

    linear_model = get_linear_model(test_feature.shape[1])
    linear_model.load_weights('best_classify_models_main_result/%s_%iper_class_seed%i_seed%i_linear.h5'
                                    % (models[0], 1, random_seed_list[i], random_seed_for_split[seed_to_look[j]]))
    y_pred_linear = np.argmax(linear_model.predict(test_feature), axis=-1)

    # plot the tsne plot with predicted labels
    fig, ax = plt.subplots(figsize=(80, 80))
    artists = []

    visualize_scatter_with_images(test_tsne[:-4], test_filename, y_pred_linear, ax, image_zoom=1.00, boxthickness=8)
    visualize_anchor_images(test_tsne[-4:], train_images_directory, train_images_label, ax, image_zoom=1.00, boxthickness=40)

    ax.autoscale()
    plt.show()

```

```python
# looking for causal effect between nanowire density (anchor - median) distance and classification accuracy for all 100 anchor sets.

# Random seed fix
random_seed_list = np.array([42, 43, 44, 45, 46])
random_seed_for_split = np.linspace(42, 42 + 19, 20).astype(int)

# list of models
models = ['barlow_tem']

plt.rcParams['axes.prop_cycle'] = plt.cycler(color=plt.cm.tab10.colors)

classification_result = {}
for i in range(len(models)):
  y = np.load('classify_scores_main_result/%s_classification_result.npz' % (models[i])
      , allow_pickle=True)
  classification_result.update({'%s' % (models[i]): y['scores']})

distance_anchor_median = np.zeros((len(random_seed_list), len(random_seed_for_split)))
abs_dist_anchor_median = np.zeros((len(random_seed_list), len(random_seed_for_split)))
sign_anchor_median = np.zeros((len(random_seed_list), len(random_seed_for_split)))
for i in range(len(random_seed_list)):

  seed_max = np.argmax(classification_result['%s' % (models[0])][-1][i], axis=0)[0]
  seed_min = np.argmin(classification_result['%s' % (models[0])][-1][i], axis=0)[0]
  seed_to_look = np.array([seed_max, seed_min])
  print(np.max(classification_result['%s' % (models[0])][-1][i], axis=0))
  print(np.min(classification_result['%s' % (models[0])][-1][i], axis=0))

  resnet_model = get_resnet_self_supervise_model(128, 64, 1024)
  resnet_model.load_weights('barlow_resnet_batch64_project128_64_1024_seed%i.h5' % (random_seed_list[i]))
  resnet_model.layers[1].trainable = False

  feature_extraction_model = Model(resnet_model.input, resnet_model.layers[-9].output)

  # Extract train and test features
  features = feature_extraction_model.predict(train_ds)

  for j in range(len(random_seed_for_split)):

    train_feature, test_feature, train_label, test_label = train_test_split(features, peptide_morph_train_enc, test_size=0.2, shuffle=True, stratify=peptide_morph_train_enc, random_state=random_seed_for_split[j])
    train_filename, test_filename, train_label, test_label = train_test_split(peptide_morph_images_train, peptide_morph_train_enc, test_size=0.2, shuffle=True, stratify=peptide_morph_train_enc, random_state=random_seed_for_split[j])
    train_label_storage = pd.DataFrame(np.concatenate((train_filename.reshape(1, -1).transpose(), train_label.reshape(1, -1).transpose()), axis=-1), columns=['filename', 'label'])
    test_label_storage = pd.DataFrame(np.concatenate((test_filename.reshape(1, -1).transpose(), test_label.reshape(1, -1).transpose()), axis=-1), columns=['filename', 'label'])
    train_feature_map = train_feature[train_label_storage.groupby('label').sample(n=1, random_state = 42).index.to_numpy()]
    train_images_directory = train_label_storage.groupby('label').sample(n=1, random_state = 42)['filename'].to_numpy().astype(str)
    train_images_label = train_label_storage.groupby('label').sample(n=1, random_state = 42)['label'].to_numpy().astype(int)

    img1 = load_img(train_images_directory[0])
    img2 = load_img(train_images_directory[1])
    img3 = load_img(train_images_directory[2])
    img4 = load_img(train_images_directory[3])
    images_to_plot = np.asarray([np.array(img1), np.array(img2), np.array(img3), np.array(img4)])

    f, ax = plt.subplots(ncols=4, nrows=1, figsize=(10, 10))
    for m in range(4):
        ax[m].set_title(category_names[m], fontsize=14)
        ax[m].imshow(images_to_plot[m])
        ax[m].axis('off')
    plt.tight_layout()
    plt.show()

    # checking the nanowire_pixel_densities of the anchor and test images from dispersed and network morphologies
    dispersed_anchor_filename = train_images_directory[1].split(".")[0] + "_label.png"
    network_anchor_filename = train_images_directory[2].split(".")[0] + "_label.png"

    dispersed_anchor = nanowire_pixel_density[np.where(peptide_morph_images_train_seglabel==dispersed_anchor_filename)]
    network_anchor = nanowire_pixel_density[np.where(peptide_morph_images_train_seglabel==network_anchor_filename)]
    print(dispersed_anchor)
    print(network_anchor)

    dispersed_filenames = [test_filename[np.where(test_label==1)][i].split(".")[0] + "_label.png" for i in range(20)]
    dispersed_pixel_densities = np.array([nanowire_pixel_density[np.where(peptide_morph_images_train_seglabel==dispersed_filenames[i])] for i in range(20)]).reshape(-1)
    network_filenames = [test_filename[np.where(test_label==2)][i].split(".")[0] + "_label.png" for i in range(20)]
    network_pixel_densities = np.array([nanowire_pixel_density[np.where(peptide_morph_images_train_seglabel==network_filenames[i])] for i in range(20)]).reshape(-1)
    fig, ax = plt.subplots(figsize=(5, 5))
    plt.hist(dispersed_pixel_densities, color=color[1], label='dispersed')
    plt.hist(network_pixel_densities, color=color[2], label='network')
    plt.vlines(dispersed_anchor, 0, 1, colors='k')
    plt.vlines(np.median(dispersed_pixel_densities), 0, 1, colors='g')
    plt.vlines(network_anchor, 0, 1, colors='k')
    plt.vlines(np.median(network_pixel_densities), 0, 1, colors='g')
    plt.legend(fontsize=16)
    ax.tick_params(labelsize=16, length=4)
    ax.set_xlabel(r'nanowire pixel density', fontsize=20, fontweight='bold')
    ax.set_ylabel(r'image count', fontsize=20, fontweight='bold')
    ax.xaxis.set_major_locator(MultipleLocator(0.10))
    ax.xaxis.set_minor_locator(MultipleLocator(0.05))
    ax.xaxis.set_major_formatter(FormatStrFormatter('%1.2f'))
    ax.set_xlim(-0.02, 0.52)
    distance_anchor_median[i, j] = (dispersed_anchor - np.median(dispersed_pixel_densities)) - (network_anchor - np.median(network_pixel_densities))
    abs_dist_anchor_median[i, j] = np.abs(dispersed_anchor - np.median(dispersed_pixel_densities)) + np.abs(network_anchor - np.median(network_pixel_densities))
    sign_anchor_median[i, j] = np.sign((dispersed_anchor - np.median(dispersed_pixel_densities)) * (network_anchor - np.median(network_pixel_densities)))
```

```python
# plot the classification accuracy of anchors on opposite or on the same side of the medians of the two morphologies
nega = np.reshape(classification_result['%s' % (models[0])][-1][:, :, 0], (-1, 1))[np.where(np.reshape(sign_anchor_median, (-1, 1))==-1)[0]]
posi = np.reshape(classification_result['%s' % (models[0])][-1][:, :, 0], (-1, 1))[np.where(np.reshape(sign_anchor_median, (-1, 1))==1)[0]]
fig, ax= plt.subplots()
plt.boxplot([nega, posi], notch=True, positions=[1, 1.3])
ax.set_xticks(([1, 1.3]))
ax.set_xticklabels(['opposite', 'same'])
ax.tick_params(labelsize=16, length=4)
ax.set_xlabel(r'two anchor positions', fontsize=20, fontweight='bold')
ax.set_ylabel(r'accuracy', fontsize=20, fontweight='bold')
```

notebooks/one-shot learning result plots.ipynb


#Extracting text from LaTex file of any research paper

Importing the necessary libraries:

```
import re
import json
```

Reading the latex file:

```
data = open('p1.tex').read()
```

Getting rid of all the unwanted tags before extraction of text:

```
def purge_images(data):
    imgs = re.findall(r'begin{figure}(.*?)end{figure}', data,re.S)
    start = "\\begin{figure}"
    end = "end{figure}"
    imgs = [start + img + end for img in imgs]
    for img in imgs:
        data = data.replace(img," ")
    return data

def purge_table(data):
    tables = re.findall(r'begin{table}(.*?)end{table}', data,re.S)
    start = "\\begin{table}"
    end = "end{table}"
    tables = [start + table + end for table in tables]
    for table in tables:
        data = data.replace(table," ")
    return data

def purge_equation(data):
    equations = re.findall(r'begin{equation}(.*?)end{equation}', data,re.S)
    start = "\\begin{equation}"
    end = "end{equation}"
    equations = [start + equation + end for equation in equations]
    for equation in equations:
        data = data.replace(equation," ")
    return data

data = purge_images(data)
data = purge_table(data)
data = purge_equation(data)
```

Function to convert list to string since the findall function returns a list

```
def listToString(s):
    # initialize an empty string
    str1 = ""
    # traverse in the string
    for ele in s:
        str1 += ele
    # return string
    return str1
```

Extracting the title:

```
title = re.findall(r'title{(.*?)}',data,re.S)
title = listToString(title)
print(title)
```

    A Sample Research Paper

Extracting the authors:

```
author = re.findall(r'author{(.*?)}',data,re.S)
author = listToString(author)
print(author)
```

    I.M. Great and So.R. Yu

Extracting the abstract:

```
abstract = re.findall(r'\\begin{abstract}(.*?)\\end{abstract}', data, re.S)
abstract = listToString(abstract)
print(abstract)
```

    An abstract is a great convenience for the reader and is required by all journals. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam convallis diam at lobortis dapibus. In id efficitur libero. Vestibulum vel ullamcorper neque. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Quisque et felis commodo, rutrum erat at, sodales odio. Nam mi ipsum, imperdiet vitae augue non, convallis accumsan ante. Vivamus in lacus id nisi gravida condimentum vitae convallis tortor. In viverra congue sollicitudin. Quisque eget leo feugiat, tincidunt enim et, pretium orci. Duis condimentum maximus turpis at malesuada. Morbi laoreet metus felis, et varius odio consequat ac. Curabitur rutrum ac sapien ut ultrices. Donec et pretium elit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.

Extracting the introduction:

```
introduction = re.findall(r'\\section{Introduction}(.*?)\\',data,re.S)
introduction = listToString(introduction)
print(introduction)
```

    Using latex is pretty easy if you have a sample document you can follow.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed volutpat ornare odio et faucibus. Donec fringilla massa eget auctor viverra. Mauris a imperdiet est. Cras tincidunt nulla ut elit tristique ultricies. Phasellus nec orci vel mi suscipit maximus at vitae tortor. Vivamus sed libero vel lacus aliquam rhoncus. Ut in lacinia nunc. Nullam quis mauris leo. Phasellus vitae nisl condimentum quam congue volutpat. Quisque et dapibus ipsum. Curabitur fringilla pellentesque elit, non posuere purus malesuada id. Pellentesque rutrum vitae urna eu mattis.

    Maecenas ac congue massa. Quisque a sem turpis. Duis et diam ex. Suspendisse et enim interdum, sodales risus eu, ultrices est. Suspendisse eu odio enim. In vulputate odio porttitor tincidunt vestibulum. Praesent tincidunt ullamcorper purus, quis semper felis volutpat quis.

Extracting the results:

```
results = re.findall(r'\\section{Results}(.*?)\\',data,re.S)
results = listToString(results)
print(results)
```

    Including figures, tables, and equations is easy. Latex also permits easy reference to document elements (figures, tables, sections). Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam tincidunt lorem luctus eros dictum faucibus. Fusce euismod libero et erat pretium dapibus. Pellentesque faucibus hendrerit est, ac fringilla urna. In porta, ante eu dictum vestibulum, nisl nulla euismod purus, ac bibendum nibh ante vel elit. Fusce diam ante, tincidunt id eleifend a, hendrerit vitae tellus. Duis pretium urna ac vestibulum eleifend. Suspendisse potenti. Aliquam varius odio in pretium semper. Ut faucibus lobortis mauris vel sollicitudin. Nullam condimentum, lacus quis mattis pellentesque, massa nulla cursus nisi, aliquet eleifend est tellus ut libero.

Extracting the conclusions:

```
conclusions = re.findall(r'\\section{Conclusions}(.*?)\\',data,re.S)
conclusions = listToString(conclusions)
print(conclusions)
```

    Man, latex is great! Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam tincidunt lorem luctus eros dictum faucibus. Fusce euismod libero et erat pretium dapibus. Pellentesque faucibus hendrerit est, ac fringilla urna. In porta, ante eu dictum vestibulum, nisl nulla euismod purus, ac bibendum nibh ante vel elit. Fusce diam ante, tincidunt id eleifend a, hendrerit vitae tellus. Duis pretium urna ac vestibulum eleifend. Suspendisse potenti. Aliquam varius odio in pretium semper. Ut faucibus lobortis mauris vel sollicitudin. Nullam condimentum, lacus quis mattis pellentesque, massa nulla cursus nisi, aliquet eleifend est tellus ut libero.

Extracting the acknowledgments:

```
acknowledgments = re.findall(r'\\acknowledgments(.*?)\\',data,re.S)
acknowledgments = listToString(acknowledgments)
print(acknowledgments)
```

    The author is grateful to Donald Knuth for inventing tex, and making publication quality typesetting a reality for scientists around the world.

Creating a dictionary of all the extracted text:

```
a_dict = {}

for variable in ["title", "author", "abstract","introduction","results","conclusions","acknowledgments"]:
    a_dict[variable] = eval(variable)
    dict_1.append(a_dict)

print(a_dict)
```

Converting the dictionary into a JSON file:

```
with open("extracted_data.json", "w") as outfile:
    json.dump(a_dict, outfile)
```