From 1610c9e03a6c70167eb551777dfd7a1030f980b9 Mon Sep 17 00:00:00 2001 From: Dabiao Zhang Date: Tue, 28 Mar 2023 10:19:57 +0800 Subject: [PATCH] del file --- .../.ipynb_checkpoints/models-checkpoint.py | 257 ---------------- .../.ipynb_checkpoints/modules-checkpoint.py | 276 ----------------- .../.ipynb_checkpoints/pipeline-checkpoint.py | 287 ------------------ .../.ipynb_checkpoints/utils-checkpoint.py | 192 ------------ stamarker/stamarker/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 115 -> 0 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 185 -> 0 bytes .../__pycache__/dataset.cpython-38.pyc | Bin 4250 -> 0 bytes .../__pycache__/dataset.cpython-39.pyc | Bin 4302 -> 0 bytes .../__pycache__/models.cpython-38.pyc | Bin 11060 -> 0 bytes .../__pycache__/models.cpython-39.pyc | Bin 11136 -> 0 bytes .../__pycache__/modules.cpython-38.pyc | Bin 9861 -> 0 bytes .../__pycache__/modules.cpython-39.pyc | Bin 9878 -> 0 bytes .../__pycache__/pipeline.cpython-38.pyc | Bin 10165 -> 0 bytes .../__pycache__/pipeline.cpython-39.pyc | Bin 11634 -> 0 bytes .../__pycache__/utils.cpython-38.pyc | Bin 6380 -> 0 bytes .../__pycache__/utils.cpython-39.pyc | Bin 6736 -> 0 bytes stamarker/stamarker/dataset.py | 111 ------- stamarker/stamarker/models.py | 257 ---------------- stamarker/stamarker/modules.py | 276 ----------------- stamarker/stamarker/pipeline.py | 287 ------------------ .../.ipynb_checkpoints/models-checkpoint.py | 257 ---------------- .../.ipynb_checkpoints/modules-checkpoint.py | 276 ----------------- .../.ipynb_checkpoints/pipeline-checkpoint.py | 246 --------------- stamarker/stamarker/stamarker/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 115 -> 0 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 185 -> 0 bytes .../__pycache__/dataset.cpython-38.pyc | Bin 4250 -> 0 bytes .../__pycache__/dataset.cpython-39.pyc | Bin 4302 -> 0 bytes .../__pycache__/models.cpython-38.pyc | Bin 11060 -> 0 bytes .../__pycache__/models.cpython-39.pyc | Bin 11156 -> 0 bytes .../__pycache__/modules.cpython-38.pyc | Bin 9861 -> 0 bytes .../__pycache__/modules.cpython-39.pyc | Bin 9878 -> 0 bytes .../__pycache__/pipeline.cpython-38.pyc | Bin 10165 -> 0 bytes .../__pycache__/pipeline.cpython-39.pyc | Bin 10275 -> 0 bytes .../__pycache__/utils.cpython-38.pyc | Bin 6380 -> 0 bytes .../__pycache__/utils.cpython-39.pyc | Bin 6470 -> 0 bytes stamarker/stamarker/stamarker/dataset.py | 111 ------- stamarker/stamarker/stamarker/models.py | 257 ---------------- stamarker/stamarker/stamarker/modules.py | 276 ----------------- stamarker/stamarker/stamarker/pipeline.py | 286 ----------------- stamarker/stamarker/stamarker/utils.py | 190 ------------ stamarker/stamarker/utils.py | 192 ------------ 43 files changed, 4034 deletions(-) delete mode 100644 stamarker/stamarker/.ipynb_checkpoints/models-checkpoint.py delete mode 100644 stamarker/stamarker/.ipynb_checkpoints/modules-checkpoint.py delete mode 100644 stamarker/stamarker/.ipynb_checkpoints/pipeline-checkpoint.py delete mode 100644 stamarker/stamarker/.ipynb_checkpoints/utils-checkpoint.py delete mode 100644 stamarker/stamarker/__init__.py delete mode 100644 stamarker/stamarker/__pycache__/__init__.cpython-38.pyc delete mode 100644 stamarker/stamarker/__pycache__/__init__.cpython-39.pyc delete mode 100644 stamarker/stamarker/__pycache__/dataset.cpython-38.pyc delete mode 100644 stamarker/stamarker/__pycache__/dataset.cpython-39.pyc delete mode 100644 stamarker/stamarker/__pycache__/models.cpython-38.pyc delete mode 100644 stamarker/stamarker/__pycache__/models.cpython-39.pyc delete mode 100644 stamarker/stamarker/__pycache__/modules.cpython-38.pyc delete mode 100644 stamarker/stamarker/__pycache__/modules.cpython-39.pyc delete mode 100644 stamarker/stamarker/__pycache__/pipeline.cpython-38.pyc delete mode 100644 stamarker/stamarker/__pycache__/pipeline.cpython-39.pyc delete mode 100644 stamarker/stamarker/__pycache__/utils.cpython-38.pyc delete mode 100644 stamarker/stamarker/__pycache__/utils.cpython-39.pyc delete mode 100644 stamarker/stamarker/dataset.py delete mode 100644 stamarker/stamarker/models.py delete mode 100644 stamarker/stamarker/modules.py delete mode 100644 stamarker/stamarker/pipeline.py delete mode 100644 stamarker/stamarker/stamarker/.ipynb_checkpoints/models-checkpoint.py delete mode 100644 stamarker/stamarker/stamarker/.ipynb_checkpoints/modules-checkpoint.py delete mode 100644 stamarker/stamarker/stamarker/.ipynb_checkpoints/pipeline-checkpoint.py delete mode 100644 stamarker/stamarker/stamarker/__init__.py delete mode 100644 stamarker/stamarker/stamarker/__pycache__/__init__.cpython-38.pyc delete mode 100644 stamarker/stamarker/stamarker/__pycache__/__init__.cpython-39.pyc delete mode 100644 stamarker/stamarker/stamarker/__pycache__/dataset.cpython-38.pyc delete mode 100644 stamarker/stamarker/stamarker/__pycache__/dataset.cpython-39.pyc delete mode 100644 stamarker/stamarker/stamarker/__pycache__/models.cpython-38.pyc delete mode 100644 stamarker/stamarker/stamarker/__pycache__/models.cpython-39.pyc delete mode 100644 stamarker/stamarker/stamarker/__pycache__/modules.cpython-38.pyc delete mode 100644 stamarker/stamarker/stamarker/__pycache__/modules.cpython-39.pyc delete mode 100644 stamarker/stamarker/stamarker/__pycache__/pipeline.cpython-38.pyc delete mode 100644 stamarker/stamarker/stamarker/__pycache__/pipeline.cpython-39.pyc delete mode 100644 stamarker/stamarker/stamarker/__pycache__/utils.cpython-38.pyc delete mode 100644 stamarker/stamarker/stamarker/__pycache__/utils.cpython-39.pyc delete mode 100644 stamarker/stamarker/stamarker/dataset.py delete mode 100644 stamarker/stamarker/stamarker/models.py delete mode 100644 stamarker/stamarker/stamarker/modules.py delete mode 100644 stamarker/stamarker/stamarker/pipeline.py delete mode 100644 stamarker/stamarker/stamarker/utils.py delete mode 100644 stamarker/stamarker/utils.py diff --git a/stamarker/stamarker/.ipynb_checkpoints/models-checkpoint.py b/stamarker/stamarker/.ipynb_checkpoints/models-checkpoint.py deleted file mode 100644 index d7457ec..0000000 --- a/stamarker/stamarker/.ipynb_checkpoints/models-checkpoint.py +++ /dev/null @@ -1,257 +0,0 @@ -from abc import ABC -from typing import Any, List -import numpy as np -import pytorch_lightning as pl -import torch -import torch.nn.functional as F -from torch.utils.data import DataLoader, WeightedRandomSampler -from torch_geometric.data import Data -from sklearn.metrics import adjusted_rand_score, confusion_matrix -from .modules import STAGATEModule, StackMLPModule -from .dataset import RepDataset, Batch -from .utils import Timer - -def get_optimizer(name): - if name == "ADAM": - return torch.optim.Adam - elif name == "ADAGRAD": - return torch.optim.Adagrad - elif name == "ADADELTA": - return torch.optim.Adadelta - elif name == "RMS": - return torch.optim.RMSprop - elif name == "ASGD": - return torch.optim.ASGD - else: - raise NotImplementedError - - -def get_scheduler(name): - if name == "STEP_LR": - return torch.optim.lr_scheduler.StepLR - elif name == "EXP_LR": - return torch.optim.lr_scheduler.ExponentialLR - else: - raise NotImplementedError - -class BaseModule(pl.LightningModule, ABC): - def __init__(self): - super(BaseModule, self).__init__() - self.optimizer_params = None - self.scheduler_params = None - self.model = None - self.timer = Timer() - self.automatic_optimization = False - - def set_optimizer_params(self, - optimizer_params: dict, - scheduler_params: dict): - self.optimizer_params = optimizer_params - self.scheduler_params = scheduler_params - - def configure_optimizers(self): - optimizer = get_optimizer(self.optimizer_params["name"])( - self.model.parameters(), - **self.optimizer_params["params"]) - scheduler = get_scheduler(self.scheduler_params["name"])(optimizer, **self.scheduler_params["params"]) - return [optimizer], [scheduler] - - def on_train_epoch_start(self) -> None: - self.timer.tic('train') - - -class intSTAGATE(BaseModule): - """ - intSTAGATE Lightning Module - """ - def __init__(self, - in_features: int = None, - hidden_dims: List[int] = None, - gradient_clipping: float = 5.0, - **kwargs): - super(intSTAGATE, self).__init__() - self.model = STAGATEModule(in_features, hidden_dims) - self.auto_encoder_epochs = None - self.gradient_clipping = gradient_clipping - self.pred_labels = None - self.save_hyperparameters() - - def configure_optimizers(self) -> (dict, dict): - auto_encoder_optimizer = get_optimizer(self.optimizer_params["name"])( - list(self.model.parameters()), - **self.optimizer_params["params"]) - auto_encoder_scheduler = get_scheduler(self.scheduler_params["name"])(auto_encoder_optimizer, - **self.scheduler_params["params"]) - return [auto_encoder_optimizer], [auto_encoder_scheduler] - - def forward(self, x, edge_index) -> Any: - return self.model(x, edge_index) - - def training_step(self, batch, batch_idx): - batch = batch.to(self.device) - opt_auto_encoder = self.optimizers() - z, x_hat = self.model(batch.x, batch.edge_index) - loss = F.mse_loss(batch.x, x_hat) - opt_auto_encoder.zero_grad() - self.manual_backward(loss) - torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.gradient_clipping) - opt_auto_encoder.step() - self.log("Training auto-encoder|Reconstruction errors", loss.item(), prog_bar=True) - self.logger.experiment.add_scalar('auto_encoder/loss', loss.item(), self.current_epoch) - - def on_train_epoch_end(self) -> None: - time = self.timer.toc('train') - sch_auto_encoder = self.lr_schedulers() - sch_auto_encoder.step() - self.logger.experiment.add_scalar('train_time', time, self.current_epoch) - - def validation_step(self, batch, batch_idx): - pass - - def validation_epoch_end(self, outputs): - pass - - -def _compute_correct(scores, target_y): - _, pred_labels = torch.max(scores, axis=1) - correct = (pred_labels == target_y).sum().item() - return pred_labels, correct - - -class CoordTransformer(object): - def __init__(self, coord): - self.coord = coord - - def transform(self): - factor = np.max(np.max(self.coord, axis=0) - np.min(self.coord, axis=0)) - return (self.coord - np.min(self.coord, axis=0)) / factor - - -class StackClassifier(BaseModule): - def __init__(self, in_features: int, - n_classes: int = 7, - batch_size: int = 1000, - shuffle: bool = False, - hidden_dims: List[int] = [30], - architecture: str = "MLP", - sta_path: str = None, - **kwargs): - super(StackClassifier, self).__init__() - self.in_features = in_features - self.architecture = architecture - self.batch_size = batch_size - self.shuffle = shuffle - if architecture == "MLP": - self.model = StackMLPModule(in_features, n_classes, hidden_dims, **kwargs) - else: - raise NotImplementedError - self.dataset = None - self.train_dataset = None - self.val_dataset = None - self.automatic_optimization = False - self.sampler = None - self.test_prop = None - self.confusion = None - self.balanced = None - self.save_hyperparameters() - - def prepare(self, - stagate: intSTAGATE, - dataset: Data, - target_y, - test_prop: float = 0.5, - balanced: bool = True): - self.balanced = balanced - self.test_prop = test_prop - with torch.no_grad(): - representation, _ = stagate(dataset.x, dataset.edge_index) - if hasattr(dataset, "ground_truth"): - ground_truth = dataset.ground_truth - else: - ground_truth = None - if isinstance(target_y, np.ndarray): - target_y = torch.from_numpy(target_y).type(torch.LongTensor) - elif isinstance(target_y, torch.Tensor): - target_y = target_y.type(torch.LongTensor) - else: - raise TypeError("target_y must be either a torch tensor or a numpy ndarray.") - self.dataset = RepDataset(representation, target_y, ground_truth=ground_truth) - n_val = int(len(self.dataset) * test_prop) - self.train_dataset, self.val_dataset = torch.utils.data.random_split( - self.dataset, [len(self.dataset) - n_val, n_val]) - if balanced: - target_y = target_y[self.train_dataset.indices] - class_sample_count = np.array([len(np.where(target_y == t)[0]) for t in np.unique(target_y)]) - weight = 1. / class_sample_count - samples_weight = np.array([weight[t] for t in target_y]) - samples_weight = torch.from_numpy(samples_weight) - samples_weight = samples_weight.double() - self.sampler = WeightedRandomSampler(samples_weight, len(samples_weight)) - - def forward(self, x, edge_index=None) -> Any: - if self.architecture == "MLP": - return self.model(x) - elif self.architecture == "STACls": - _, output = self.model(x, edge_index) - return output - - def training_step(self, batch, batch_idx): - batch = Batch(**batch) - batch = batch.to(self.device) - opt = self.optimizers() - opt.zero_grad() - output = self.model(batch.x) - loss = F.cross_entropy(output["score"], batch.y) - self.manual_backward(loss) - opt.step() - _, correct = _compute_correct(output["score"], batch.y) - self.log(f"Training {self.architecture} classifier|Cross entropy", loss.item(), prog_bar=True) - return {"loss": loss, "correct": correct} - - def training_epoch_end(self, outputs): - time = self.timer.toc('train') - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/train_time', time, self.current_epoch) - all_loss = torch.stack([x["loss"] for x in outputs]) - all_correct = np.sum([x["correct"] for x in outputs]) - train_acc = all_correct / len(self.train_dataset) - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/loss', - torch.mean(all_loss), self.current_epoch) - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/train_acc', - train_acc, self.current_epoch) - - def validation_step(self, batch, batch_idx): - batch = Batch(**batch) - batch = batch.to(self.device) - with torch.no_grad(): - output = self.model(batch.x) - loss = F.cross_entropy(output["score"], batch.y) - pred_labels, correct = _compute_correct(output["score"], batch.y) - return {"loss": loss, "correct": correct, "pred_labels": pred_labels, "true_labels": batch.y} - - def validation_epoch_end(self, outputs): - all_loss = torch.stack([x["loss"] for x in outputs]) - all_correct = np.sum([x["correct"] for x in outputs]) - pred_labels = torch.cat([x["pred_labels"] for x in outputs]).cpu().detach().numpy() - true_labels = torch.cat([x["true_labels"] for x in outputs]).cpu().detach().numpy() - confusion = confusion_matrix(true_labels, pred_labels) - val_acc = all_correct / len(self.val_dataset) - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/val_loss', - torch.mean(all_loss), self.current_epoch) - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/val_acc', - val_acc, self.current_epoch) - print("\n validation ACC={:.4f}".format(val_acc)) - self.confusion = confusion - - def train_dataloader(self): - loader = DataLoader(self.train_dataset, batch_size=self.batch_size, sampler=self.sampler) - return loader - - def val_dataloader(self): - loader = DataLoader(self.val_dataset, batch_size=self.batch_size, shuffle=self.shuffle, drop_last=False) - return loader - - def test_dataloader(self): - raise NotImplementedError - - def predict_dataloader(self): - raise NotImplementedError \ No newline at end of file diff --git a/stamarker/stamarker/.ipynb_checkpoints/modules-checkpoint.py b/stamarker/stamarker/.ipynb_checkpoints/modules-checkpoint.py deleted file mode 100644 index 08d9d81..0000000 --- a/stamarker/stamarker/.ipynb_checkpoints/modules-checkpoint.py +++ /dev/null @@ -1,276 +0,0 @@ -import abc -import copy -from torch.autograd import Variable -import torch -from torch import Tensor -import torch.nn.functional as F -from torch.nn import Parameter -import torch.nn as nn -from torch_sparse import SparseTensor, set_diag -from torch_geometric.nn.conv import MessagePassing -from torch_geometric.utils import remove_self_loops, add_self_loops, softmax -from typing import Union, Tuple, Optional -from torch_geometric.typing import (OptPairTensor, Adj, Size, NoneType, - OptTensor) - - -class GATConv(MessagePassing): - r"""The graph attentional operator from the `"Graph Attention Networks" - `_ paper - .. math:: - \mathbf{x}^{\prime}_i = \alpha_{i,i}\mathbf{\Theta}\mathbf{x}_{i} + - \sum_{j \in \mathcal{N}(i)} \alpha_{i,j}\mathbf{\Theta}\mathbf{x}_{j}, - where the attention coefficients :math:`\alpha_{i,j}` are computed as - .. math:: - \alpha_{i,j} = - \frac{ - \exp\left(\mathrm{LeakyReLU}\left(\mathbf{a}^{\top} - [\mathbf{\Theta}\mathbf{x}_i \, \Vert \, \mathbf{\Theta}\mathbf{x}_j] - \right)\right)} - {\sum_{k \in \mathcal{N}(i) \cup \{ i \}} - \exp\left(\mathrm{LeakyReLU}\left(\mathbf{a}^{\top} - [\mathbf{\Theta}\mathbf{x}_i \, \Vert \, \mathbf{\Theta}\mathbf{x}_k] - \right)\right)}. - Args: - in_channels (int or tuple): Size of each input sample, or :obj:`-1` to - derive the size from the first input(s) to the forward method. - A tuple corresponds to the sizes of source and target - dimensionalities. - out_channels (int): Size of each output sample. - heads (int, optional): Number of multi-head-attentions. - (default: :obj:`1`) - concat (bool, optional): If set to :obj:`False`, the multi-head - attentions are averaged instead of concatenated. - (default: :obj:`True`) - negative_slope (float, optional): LeakyReLU angle of the negative - slope. (default: :obj:`0.2`) - dropout (float, optional): Dropout probability of the normalized - attention coefficients which exposes each node to a stochastically - sampled neighborhood during training. (default: :obj:`0`) - add_self_loops (bool, optional): If set to :obj:`False`, will not add - self-loops to the input graph. (default: :obj:`True`) - bias (bool, optional): If set to :obj:`False`, the layer will not learn - an additive bias. (default: :obj:`True`) - **kwargs (optional): Additional arguments of - :class:`torch_geometric.nn.conv.MessagePassing`. - """ - _alpha: OptTensor - - def __init__(self, in_channels: Union[int, Tuple[int, int]], - out_channels: int, heads: int = 1, concat: bool = True, - negative_slope: float = 0.2, dropout: float = 0.0, - add_self_loops: bool = True, bias: bool = True, **kwargs): - kwargs.setdefault('aggr', 'add') - super(GATConv, self).__init__(node_dim=0, **kwargs) - - self.in_channels = in_channels - self.out_channels = out_channels - self.heads = heads - self.concat = concat - self.negative_slope = negative_slope - self.dropout = dropout - self.add_self_loops = add_self_loops - self.lin_src = nn.Parameter(torch.zeros(size=(in_channels, out_channels))) - nn.init.xavier_normal_(self.lin_src.data, gain=1.414) - self.lin_dst = self.lin_src - # The learnable parameters to compute attention coefficients: - self.att_src = Parameter(torch.Tensor(1, heads, out_channels)) - self.att_dst = Parameter(torch.Tensor(1, heads, out_channels)) - nn.init.xavier_normal_(self.att_src.data, gain=1.414) - nn.init.xavier_normal_(self.att_dst.data, gain=1.414) - self._alpha = None - self.attentions = None - - def forward(self, x: Union[Tensor, OptPairTensor], edge_index: Adj, - size: Size = None, return_attention_weights=None, attention=True, tied_attention=None): - # type: (Union[Tensor, OptPairTensor], Tensor, Size, NoneType) -> Tensor # noqa - # type: (Union[Tensor, OptPairTensor], SparseTensor, Size, NoneType) -> Tensor # noqa - # type: (Union[Tensor, OptPairTensor], Tensor, Size, bool) -> Tuple[Tensor, Tuple[Tensor, Tensor]] # noqa - # type: (Union[Tensor, OptPairTensor], SparseTensor, Size, bool) -> Tuple[Tensor, SparseTensor] # noqa - r""" - Args: - return_attention_weights (bool, optional): If set to :obj:`True`, - will additionally return the tuple - :obj:`(edge_index, attention_weights)`, holding the computed - attention weights for each edge. (default: :obj:`None`) - """ - H, C = self.heads, self.out_channels - - # We first transform the input node features. If a tuple is passed, we - # transform source and target node features via separate weights: - if isinstance(x, Tensor): - assert x.dim() == 2, "Static graphs not supported in 'GATConv'" - # x_src = x_dst = self.lin_src(x).view(-1, H, C) - x_src = x_dst = torch.mm(x, self.lin_src).view(-1, H, C) - else: # Tuple of source and target node features: - x_src, x_dst = x - assert x_src.dim() == 2, "Static graphs not supported in 'GATConv'" - x_src = self.lin_src(x_src).view(-1, H, C) - if x_dst is not None: - x_dst = self.lin_dst(x_dst).view(-1, H, C) - - x = (x_src, x_dst) - - if not attention: - return x[0].mean(dim=1) - # return x[0].view(-1, self.heads * self.out_channels) - - if tied_attention == None: - # Next, we compute node-level attention coefficients, both for source - # and target nodes (if present): - alpha_src = (x_src * self.att_src).sum(dim=-1) - alpha_dst = None if x_dst is None else (x_dst * self.att_dst).sum(-1) - alpha = (alpha_src, alpha_dst) - self.attentions = alpha - else: - alpha = tied_attention - - if self.add_self_loops: - if isinstance(edge_index, Tensor): - # We only want to add self-loops for nodes that appear both as - # source and target nodes: - num_nodes = x_src.size(0) - if x_dst is not None: - num_nodes = min(num_nodes, x_dst.size(0)) - num_nodes = min(size) if size is not None else num_nodes - edge_index, _ = remove_self_loops(edge_index) - edge_index, _ = add_self_loops(edge_index, num_nodes=num_nodes) - elif isinstance(edge_index, SparseTensor): - edge_index = set_diag(edge_index) - - # propagate_type: (x: OptPairTensor, alpha: OptPairTensor) - out = self.propagate(edge_index, x=x, alpha=alpha, size=size) - - alpha = self._alpha - assert alpha is not None - self._alpha = None - - if self.concat: - out = out.view(-1, self.heads * self.out_channels) - else: - out = out.mean(dim=1) - - # if self.bias is not None: - # out += self.bias - - if isinstance(return_attention_weights, bool): - if isinstance(edge_index, Tensor): - return out, (edge_index, alpha) - elif isinstance(edge_index, SparseTensor): - return out, edge_index.set_value(alpha, layout='coo') - else: - return out - - def message(self, x_j: Tensor, alpha_j: Tensor, alpha_i: OptTensor, - index: Tensor, ptr: OptTensor, - size_i: Optional[int]) -> Tensor: - # Given egel-level attention coefficients for source and target nodes, - # we simply need to sum them up to "emulate" concatenation: - alpha = alpha_j if alpha_i is None else alpha_j + alpha_i - - alpha = F.leaky_relu(alpha, self.negative_slope) - alpha = softmax(alpha, index, ptr, size_i) - self._alpha = alpha # Save for later use. - alpha = F.dropout(alpha, p=self.dropout, training=self.training) - return x_j * alpha.unsqueeze(-1) - - def __repr__(self): - return '{}({}, {}, heads={})'.format(self.__class__.__name__, - self.in_channels, - self.out_channels, self.heads) - - -class STAGATEModule(nn.Module): - def __init__(self, in_features, hidden_dims): - super(STAGATEModule, self).__init__() - [num_hidden, out_dim] = hidden_dims - self.conv1 = GATConv(in_features, num_hidden, heads=1, concat=False, - dropout=0, add_self_loops=False, bias=False) - self.conv2 = GATConv(num_hidden, out_dim, heads=1, concat=False, - dropout=0, add_self_loops=False, bias=False) - self.conv3 = GATConv(out_dim, num_hidden, heads=1, concat=False, - dropout=0, add_self_loops=False, bias=False) - self.conv4 = GATConv(num_hidden, in_features, heads=1, concat=False, - dropout=0, add_self_loops=False, bias=False) - - def forward(self, features, edge_index): - h1 = F.elu(self.conv1(features, edge_index)) - h2 = self.conv2(h1, edge_index, attention=False) - self.conv3.lin_src.data = self.conv2.lin_src.transpose(0, 1) - self.conv3.lin_dst.data = self.conv2.lin_dst.transpose(0, 1) - self.conv4.lin_src.data = self.conv1.lin_src.transpose(0, 1) - self.conv4.lin_dst.data = self.conv1.lin_dst.transpose(0, 1) - h3 = F.elu(self.conv3(h2, edge_index, attention=True, - tied_attention=self.conv1.attentions)) - h4 = self.conv4(h3, edge_index, attention=False) - - return h2, h4 - - -class StackClsModule(nn.Module, abc.ABC): - def __init__(self, in_features, n_classes): - super(StackClsModule, self).__init__() - self.in_features = in_features - self.n_classes = n_classes - - -class STAGATEClsModule(nn.Module): - def __init__(self, - stagate: STAGATEModule, - stack_classifier: StackClsModule): - super(STAGATEClsModule, self).__init__() - self.stagate = copy.deepcopy(stagate) - self.classifier = copy.deepcopy(stack_classifier) - - def forward(self, x, edge_index, mode="classifier"): - z, x_recon = self.stagate(x, edge_index) - z = torch.clone(z) - if mode == "classifier": - return z, self.classifier(z) - elif mode == "reconstruction": - return z, x_recon - else: - raise NotImplementedError - - def get_saliency_map(self, x, edge_index, target_index="max", save=None): - """ - Get saliency map by backpropagation. - :param x: input tensors - :param edge_index: graph edge index - :param target_index: target index to compute final scores - :param save: - :return: gradients - """ - x_var = Variable(x, requires_grad=True) - _, output = self.forward(x_var, edge_index, mode="classifier") - scores = output["last_layer"] - if target_index == "max": - target_score_indices = Variable(torch.argmax(scores, 1)) - elif isinstance(target_index, int): - target_score_indices = Variable(torch.ones(scores.shape[0], dtype=torch.int64) * target_index) - else: - raise NotImplementedError - target_scores = scores.gather(1, target_score_indices.view(-1, 1)).squeeze() - loss = torch.sum(target_scores) - loss.backward() - gradients = x_var.grad.data - if save is not None: - torch.save(gradients, save) - return gradients, scores - - -class StackMLPModule(StackClsModule): - name = "StackMLP" - - def __init__(self, in_features, n_classes, hidden_dims=[30, 40, 30]): - super(StackMLPModule, self).__init__(in_features, n_classes) - self.classifier = nn.ModuleList() - mlp_dims = [in_features] + hidden_dims + [n_classes] - for ind in range(len(mlp_dims) - 1): - self.classifier.append(nn.Linear(mlp_dims[ind], mlp_dims[ind + 1])) - - def forward(self, x): - for layer in self.classifier: - x = layer(x) - score = F.softmax(x, dim=0) - return {"last_layer": x, "score": score} diff --git a/stamarker/stamarker/.ipynb_checkpoints/pipeline-checkpoint.py b/stamarker/stamarker/.ipynb_checkpoints/pipeline-checkpoint.py deleted file mode 100644 index c24592a..0000000 --- a/stamarker/stamarker/.ipynb_checkpoints/pipeline-checkpoint.py +++ /dev/null @@ -1,287 +0,0 @@ -import pytorch_lightning as pl -import copy -import torch -import os -import shutil -import logging -import glob -import sys -import numpy as np -import scipy -import scanpy as sc -from pytorch_lightning.loggers import TensorBoardLogger -from scipy.cluster import hierarchy -from .models import intSTAGATE, StackClassifier -from .utils import plot_consensus_map, consensus_matrix, Timer -from .dataset import SpatialDataModule -from .modules import STAGATEClsModule -import logging - - -FORMAT = "%(asctime)s %(levelname)s %(message)s" -logging.basicConfig(format=FORMAT, datefmt='%Y-%m-%d %H:%M:%S') -def make_spatial_data(ann_data): - """ - Make SpatialDataModule object from Scanpy annData object - """ - data_module = SpatialDataModule() - ann_data.X = ann_data.X.toarray() - data_module.ann_data = ann_data - return data_module - - -class STAMarker: - def __init__(self, n, save_dir, config, logging_level=logging.INFO): - """ - n: int, number of graph attention auto-econders to train - save_dir: directory to save the models - config: config file for training - """ - self.n = n - self.save_dir = save_dir - if not os.path.exists(save_dir): - os.mkdir(save_dir) - logging.info("Create save directory {}".format(save_dir)) - self.version_dirs = [os.path.join(save_dir, f"version_{i}") for i in range(n)] - self.config = config - self.logger = logging.getLogger("STAMarker") - self.logger.setLevel(logging_level) - self.consensus_labels = None - - def load_from_dir(self, save_dir, ): - """ - Load the trained models from a directory - """ - self.version_dirs = glob.glob(os.path.join(save_dir, "version_*")) - self.version_dirs = sorted(self.version_dirs, key=lambda x: int(x.split("_")[-1])) - # check if all version dir have `checkpoints/stagate.ckpt` - version_dirs_valid = [] - for version_dir in self.version_dirs: - if not os.path.exists(os.path.join(version_dir, "checkpoints/stagate.ckpt")): - self.logger.warning("No checkpoint found in {}".format(version_dir)) - else: - version_dirs_valid.append(version_dir) - self.version_dirs = version_dirs_valid - self.logger.info("Load {} autoencoder models from {}".format(len(version_dirs_valid), save_dir)) - # check if all version dir have `cluster_labels.npy` raise warning if not - missing_cluster_labels = False - for version_dir in self.version_dirs: - if not os.path.exists(os.path.join(version_dir, "cluster_labels.npy")): - missing_cluster_labels = True - msg = "No cluster labels found in {}.".format(version_dir) - self.logger.warning(msg) - if missing_cluster_labels: - self.logger.warning("Please run clustering first.") - # check if save_dir has `consensus.npy` raise warning if not - if not os.path.exists(os.path.join(save_dir, "consensus.npy")): - self.logger.warning("No consensus labels found in {}".format(save_dir)) - else: - self.consensus_labels = np.load(os.path.join(save_dir, "consensus.npy")) - # check if all version dir have `checkpoints/mlp.ckpt` raise warning if not - missing_clf = False - for version_dir in self.version_dirs: - if not os.path.exists(os.path.join(version_dir, "checkpoints/mlp.ckpt")): - self.logger.warning("No classifier checkpoint found in {}".format(version_dir)) - missing_clf = True - if missing_clf: - self.logger.warning("Please run classifier training first.") - if not missing_cluster_labels and not missing_clf: - self.logger.info("All models are trained and ready to use.") - - def train_auto_encoders(self, data_module): - for seed in range(self.n): - self._train_auto_encoder(data_module, seed, self.config) - self.logger.info("Finished training {} auto-encoders".format(self.n)) - - def clustering(self, data_module, cluster_method, cluster_params): - """ - Cluster the latent space of the trained auto-encoders - Cluster method should be "louvain" or "mclust" - """ - for version_dir in self.version_dirs: - self._clustering(data_module, version_dir, cluster_method, cluster_params) - self.logger.info("Finished {} clustering with {}".format(self.n, cluster_method)) - - def consensus_clustering(self, n_clusters, name="cluster_labels.npy"): - sys.setrecursionlimit(100000) - label_files = glob.glob(self.save_dir + f"/version_*/{name}") - labels_list = list(map(lambda file: np.load(file), label_files)) - cons_mat = consensus_matrix(labels_list) - row_linkage, _, figure = plot_consensus_map(cons_mat, return_linkage=True) - figure.savefig(os.path.join(self.save_dir, "consensus_clustering.png"), dpi=300) - consensus_labels = hierarchy.cut_tree(row_linkage, n_clusters).squeeze() - np.save(os.path.join(self.save_dir, "consensus"), consensus_labels) - self.consensus_labels = consensus_labels - self.logger.info("Save consensus labels to {}".format(os.path.join(self.save_dir, "consensus.npz"))) - - def train_classifiers(self, data_module, n_clusters, name="cluster_labels.npy"): - for i, version_dir in enumerate(self.version_dirs): - # _train_classifier(self, data_module, version_dir, target_y, n_classes, seed=None) - self._train_classifier(data_module, version_dir, self.consensus_labels, - n_clusters, self.config, seed=i) - self.logger.info("Finished training {} classifiers".format(self.n)) - - def compute_smaps(self, data_module, return_recon=True, normalize=True): - smaps = [] - if return_recon: - recons = [] - for version_dir in self.version_dirs: - if return_recon: - smap, recon = self._compute_smap(data_module, version_dir, return_recon=return_recon) - smaps.append(smap) - recons.append(recon) - else: - smap = self._compute_smap(data_module, version_dir, return_recon=return_recon) - smaps.append(smap) - if return_recon: - return smaps, recons - else: - return smaps - self.logger.info("Finished computing {} smaps".format(self.n)) - - - def _compute_smap_zscore(self, smap, labels, logtransform=False): - scores = np.log(smap + 1) if logtransform else copy.copy(smap) - unique_labels = np.unique(labels) - for l in unique_labels: - scores[labels == l, :] = scipy.stats.zscore(scores[labels == l, :], axis=1) - return scores - - - def _clustering(self, data_module, version_dir, cluster_method, cluster_params): - """ - Cluster the latent space of the trained auto-encoder - """ - if cluster_method == "louvain": - run_louvain(data_module, version_dir, cluster_params) - elif cluster_method == "mclust": - run_mclust(data_module, version_dir, cluster_params) - else: - raise ValueError("Unknown clustering method") - - def _train_auto_encoder(self, data_module, seed, config): - """ - Train a single graph attention auto-encoder - """ - pl.seed_everything(seed) - version = f"version_{seed}" - version_dir = os.path.join(self.save_dir, version) - if os.path.exists(version_dir): - shutil.rmtree(version_dir) - os.makedirs(version_dir, exist_ok=True) - logger = TensorBoardLogger(save_dir=self.save_dir, name=None, - default_hp_metric=False, - version=seed) - model = intSTAGATE(**config["stagate"]["params"]) - model.set_optimizer_params(config["stagate"]["optimizer"], config["stagate"]["scheduler"]) - trainer = pl.Trainer(logger=logger, **config["stagate_trainer"]) - timer = Timer() - timer.tic("fit") - trainer.fit(model, data_module) - fit_time = timer.toc("fit") - with open(os.path.join(version_dir, "runtime.csv"), "w+") as f: - f.write("{}, fit_time, {:.2f}, ".format(seed, fit_time / 60)) - trainer.save_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - del model, trainer - if config["stagate_trainer"]["gpus"] > 0: - torch.cuda.empty_cache() - logging.info(f"Finshed running version {seed}") - - def _train_classifier(self, data_module, version_dir, target_y, n_classes, config, seed=None): - timer = Timer() - pl.seed_everything(seed) - rep_dim = config["stagate"]["params"]["hidden_dims"][-1] - stagate = intSTAGATE.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - classifier = StackClassifier(rep_dim, n_classes=n_classes, architecture="MLP") - classifier.prepare(stagate, data_module.train_dataset, target_y, - balanced=config["mlp"]["balanced"], test_prop=config["mlp"]["test_prop"]) - classifier.set_optimizer_params(config["mlp"]["optimizer"], config["mlp"]["scheduler"]) - logger = TensorBoardLogger(save_dir=self.save_dir, name=None, - default_hp_metric=False, - version=seed) - trainer = pl.Trainer(logger=logger, **config["classifier_trainer"]) - timer.tic("clf") - trainer.fit(classifier) - clf_time = timer.toc("clf") - with open(os.path.join(version_dir, "runtime.csv"), "a+") as f: - f.write("\n") - f.write("{}, clf_time, {:.2f}, ".format(seed, clf_time / 60)) - trainer.save_checkpoint(os.path.join(version_dir, "checkpoints", "mlp.ckpt")) - target_y = classifier.dataset.target_y.numpy() - all_props = class_proportions(target_y) - val_props = class_proportions(target_y[classifier.val_dataset.indices]) - if self.logger.level == logging.DEBUG: - print("All class proportions " + "|".join(["{:.2f}%".format(prop * 100) for prop in all_props])) - print("Val class proportions " + "|".join(["{:.2f}%".format(prop * 100) for prop in val_props])) - np.save(os.path.join(version_dir, "confusion.npy"), classifier.confusion) - - def _compute_smap(self, data_module, version_dir, return_recon=True): - """ - Compute the saliency map of the trained auto-encoder - """ - stagate = intSTAGATE.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - cls = StackClassifier.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "mlp.ckpt")) - stagate_cls = STAGATEClsModule(stagate.model, cls.model) - smap, _ = stagate_cls.get_saliency_map(data_module.train_dataset.x, - data_module.train_dataset.edge_index) - smap = smap.detach().cpu().numpy() - if return_recon: - recon = stagate(data_module.train_dataset.x, data_module.train_dataset.edge_index)[1].cpu().detach().numpy() - return smap, recon - else: - return smap - - -def run_louvain(data_module, version_dir, resolution, name="cluster_labels"): - """ - Run louvain clustering on the data_module - """ - stagate = intSTAGATE.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - embedding = stagate(data_module.train_dataset.x, data_module.train_dataset.edge_index)[0].cpu().detach().numpy() - ann_data = copy.copy(data_module.ann_data) - ann_data.obsm["stagate"] = embedding - sc.pp.neighbors(ann_data, use_rep='stagate') - sc.tl.louvain(ann_data, resolution=resolution) - save_path = os.path.join(version_dir, "{}.npy".format(name)) - np.save(save_path, ann_data.obs["louvain"].to_numpy().astype("int")) - print("Save louvain results to {}".format(save_path)) - - -def mclust_R(representation, n_clusters, r_seed=2022, model_name="EEE"): - """ - Clustering using the mclust algorithm. - The parameters are the same as those in the R package mclust. - """ - np.random.seed(r_seed) - import rpy2.robjects as ro - from rpy2.robjects import numpy2ri - numpy2ri.activate() - ro.r.library("mclust") - r_random_seed = ro.r['set.seed'] - r_random_seed(r_seed) - rmclust = ro.r['Mclust'] - res = rmclust(representation, n_clusters, model_name) - mclust_res = np.array(res[-2]) - numpy2ri.deactivate() - return mclust_res.astype('int') - - -def run_mclust(data_module, version_dir, n_clusters, name="cluster_labels"): - stagate = intSTAGATE.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - embedding = stagate(data_module.train_dataset.x, data_module.train_dataset.edge_index)[0].cpu().detach().numpy() - labels = mclust_R(embedding, n_clusters) - save_path = os.path.join(version_dir, "{}.npy".format(name)) - np.save(save_path, labels.astype("int")) - print("Save MClust results to {}".format(save_path)) - -def class_proportions(target): - n_classes = len(np.unique(target)) - props = np.array([np.sum(target == i) for i in range(n_classes)]) - return props / np.sum(props) - - - - - - - diff --git a/stamarker/stamarker/.ipynb_checkpoints/utils-checkpoint.py b/stamarker/stamarker/.ipynb_checkpoints/utils-checkpoint.py deleted file mode 100644 index 10a4c14..0000000 --- a/stamarker/stamarker/.ipynb_checkpoints/utils-checkpoint.py +++ /dev/null @@ -1,192 +0,0 @@ -import time -import yaml -import os -import seaborn as sns -import numpy as np -import pandas as pd -import scanpy as sc -import itertools -import scipy -from scipy.spatial import distance -from scipy.cluster import hierarchy -import sklearn.neighbors -from typing import List - - -def plot_consensus_map(cmat, method="average", return_linkage=True, **kwargs): - row_linkage = hierarchy.linkage(distance.pdist(cmat), method=method) - col_linkage = hierarchy.linkage(distance.pdist(cmat.T), method=method) - figure = sns.clustermap(cmat, row_linkage=row_linkage, col_linkage=col_linkage, **kwargs) - if return_linkage: - return row_linkage, col_linkage, figure - else: - return figure - - -class Timer: - - def __init__(self): - self.timer_dict = {} - self.stop_dict = {} - - def tic(self, name): - self.timer_dict[name] = time.time() - - def toc(self, name): - assert name in self.timer_dict - elapsed = time.time() - self.timer_dict[name] - del self.timer_dict[name] - return elapsed - - def stop(self, name): - self.stop_dict[name] = time.time() - - def resume(self, name): - if name not in self.timer_dict: - del self.stop_dict[name] - return - elapsed = time.time() - self.stop_dict[name] - self.timer_dict[name] = self.timer_dict[name] + elapsed - del self.stop_dict[name] - - -def save_yaml(yaml_object, file_path): - with open(file_path, 'w') as yaml_file: - yaml.dump(yaml_object, yaml_file, default_flow_style=False) - - print(f'Saving yaml: {file_path}') - return - - -def parse_args(yaml_file): - with open(yaml_file, 'r') as stream: - try: - cfg = yaml.safe_load(stream) - except yaml.YAMLError as exc: - print(exc) - return cfg - - -def mclust_R(representation, n_clusters, r_seed=2022, model_name="EEE"): - """ - Clustering using the mclust algorithm. - The parameters are the same as those in the R package mclust. - """ - np.random.seed(r_seed) - import rpy2.robjects as ro - from rpy2.robjects import numpy2ri - numpy2ri.activate() - ro.r.library("mclust") - r_random_seed = ro.r['set.seed'] - r_random_seed(r_seed) - rmclust = ro.r['Mclust'] - res = rmclust(representation, n_clusters, model_name) - mclust_res = np.array(res[-2]) - numpy2ri.deactivate() - return mclust_res.astype('int') - - -def labels_connectivity_mat(labels: np.ndarray): - _labels = labels - np.min(labels) - n_classes = np.unique(_labels) - mat = np.zeros([labels.size, labels.size]) - for i in n_classes: - indices = np.squeeze(np.where(_labels == i)) - row_indices, col_indices = zip(*itertools.product(indices, indices)) - mat[row_indices, col_indices] = 1 - return mat - - -def consensus_matrix(labels_list: List[np.ndarray]): - mat = 0 - for labels in labels_list: - mat += labels_connectivity_mat(labels) - return mat / float(len(labels_list)) - - -def compute_spatial_net(ann_data, rad_cutoff=None, k_cutoff=None, - max_neigh=50, model='Radius', verbose=True): - """ - Construct the spatial neighbor networks. - - Parameters - ---------- - ann_data - AnnData object of scanpy package. - rad_cutoff - radius cutoff when model='Radius' - k_cutoff - The number of nearest neighbors when model='KNN' - model - The network construction model. When model=='Radius', the spot is connected to spots whose distance is less than rad_cutoff. When model=='KNN', the spot is connected to its first k_cutoff nearest neighbors. - - Returns - ------- - The spatial networks are saved in adata.uns['Spatial_Net'] - """ - - assert (model in ['Radius', 'KNN']) - if verbose: - print('------Calculating spatial graph...') - coor = pd.DataFrame(ann_data.obsm['spatial']) - coor.index = ann_data.obs.index - coor.columns = ['imagerow', 'imagecol'] - - nbrs = sklearn.neighbors.NearestNeighbors( - n_neighbors=max_neigh + 1, algorithm='ball_tree').fit(coor) - distances, indices = nbrs.kneighbors(coor) - if model == 'KNN': - indices = indices[:, 1:k_cutoff + 1] - distances = distances[:, 1:k_cutoff + 1] - if model == 'Radius': - indices = indices[:, 1:] - distances = distances[:, 1:] - KNN_list = [] - for it in range(indices.shape[0]): - KNN_list.append(pd.DataFrame(zip([it] * indices.shape[1], indices[it, :], distances[it, :]))) - KNN_df = pd.concat(KNN_list) - KNN_df.columns = ['Cell1', 'Cell2', 'Distance'] - Spatial_Net = KNN_df.copy() - if model == 'Radius': - Spatial_Net = KNN_df.loc[KNN_df['Distance'] < rad_cutoff,] - id_cell_trans = dict(zip(range(coor.shape[0]), np.array(coor.index), )) - cell1, cell2 = Spatial_Net['Cell1'].map(id_cell_trans), Spatial_Net['Cell2'].map(id_cell_trans) - Spatial_Net = Spatial_Net.assign(Cell1=cell1, Cell2=cell2) - # Spatial_Net.assign(Cell1=Spatial_Net['Cell1'].map(id_cell_trans)) - # Spatial_Net.assign(Cell2=Spatial_Net['Cell2'].map(id_cell_trans)) - if verbose: - print('The graph contains %d edges, %d cells.' % (Spatial_Net.shape[0], ann_data.n_obs)) - print('%.4f neighbors per cell on average.' % (Spatial_Net.shape[0] / ann_data.n_obs)) - ann_data.uns['Spatial_Net'] = Spatial_Net - - -def compute_edge_list(ann_data): - G_df = ann_data.uns['Spatial_Net'].copy() - cells = np.array(ann_data.obs_names) - cells_id_tran = dict(zip(cells, range(cells.shape[0]))) - G_df['Cell1'] = G_df['Cell1'].map(cells_id_tran) - G_df['Cell2'] = G_df['Cell2'].map(cells_id_tran) - G = scipy.sparse.coo_matrix((np.ones(G_df.shape[0]), (G_df['Cell1'], G_df['Cell2'])), - shape=(ann_data.n_obs, ann_data.n_obs)) - G = G + scipy.sparse.eye(G.shape[0]) - edge_list = np.nonzero(G) - return edge_list - - -def stats_spatial_net(ann_data): - import matplotlib.pyplot as plt - Num_edge = ann_data.uns['Spatial_Net']['Cell1'].shape[0] - Mean_edge = Num_edge / ann_data.shape[0] - plot_df = pd.value_counts(pd.value_counts(ann_data.uns['Spatial_Net']['Cell1'])) - plot_df = plot_df / ann_data.shape[0] - fig, ax = plt.subplots(figsize=[3, 2]) - plt.ylabel('Percentage') - plt.xlabel('') - plt.title('Number of Neighbors (Mean=%.2f)' % Mean_edge) - ax.bar(plot_df.index, plot_df) - - -def select_stmaker_svgs(df, sd_id, alpha=1.5, top=None): - scores = df[f"score_{sd_id}"] - mu, std = np.mean(scores), np.std(scores) - return df.index[scores > mu + alpha * std].tolist() diff --git a/stamarker/stamarker/__init__.py b/stamarker/stamarker/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stamarker/stamarker/__pycache__/__init__.cpython-38.pyc b/stamarker/stamarker/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 275509ff24cfa05c04242f1222a5501ba22f4fb7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 115 zcmWIL<>g`k0_Rs+$sqbMh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2BkPcNppBr!L! kC_A+%CO$qhFS8^*Uaz3?7Kcr4eoARhsvStjXCP((0LNt(4FCWD diff --git a/stamarker/stamarker/__pycache__/__init__.cpython-39.pyc b/stamarker/stamarker/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index ab2035076574feca8bd27cda72dc131d5f781742..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 185 zcmYe~<>g`k0_Rs+$sqbMh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10QKeRZts8~Na zqbegYFJ0fSw4}HszbI9~Ei)&zSU)&K*CWi`Q$IOBB~`yDH77N(I29=9=$lxSom!+{ wQVLX?nV6%mr>9?Bl9&q-jgQaF%PfhH*DI*J#bJ}1pHiBWY6r6SGY~TX0ND*MQ~&?~ diff --git a/stamarker/stamarker/__pycache__/dataset.cpython-38.pyc b/stamarker/stamarker/__pycache__/dataset.cpython-38.pyc deleted file mode 100644 index e38dc8d2c12820e1daf12e69bb20f109a91edff6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4250 zcmaJ^TW=gm6|U;rTs$)#-{VzSCDF1li(~=IbqT><93zk!q)kAm2{fIl@_5EQ-IMB? zY;28R)l z_hZpnYclpvYFxfNG&b?%{{Rt8@PrL`$T{&&;)Je!d!cXNK^WL~DJD&HOu=+eb5LSmJgEoK{ITEFNt!pFjx#12TS47U^!go>=#T_MD;lnRk?EFhO21L zi5l89Sw(vd?Yd~7-H_$}4fL!F=K*WYzlN-=#f`Vy%XJ$}X*ETp29wBR+TZCta|$p*ubmQg;8buUh$RBE%3>saSk zyQcG`=*lR;w>u=r<;y{16Hopwh-4ubEOeyXhXFx@umoBTD}oE>ISZ@8g*u+`Ka6!} zze9_smk)+~^K(4;Hi%{??ARHzQ+~#_*&p9~u4hjyz)%*bg^#sl8uRo--} z{HSb|-OyRPHD}6El*R)YMWz}>gG`JP;%ia#WE3aGh^k`)<96YbxhTSmM8zaD;BnI6 zym2uiWO6ZrMln(-NP=7G3$jX0QkuS#mqE%97q?@3uesR8j7^$)*~OwP9N|9ah!;=z zaIGf-QNr67WcP}xek6yJL)z|;W2YDKw54_Qdtfvh^Ais0obUrDU)KB+KIUTFg0-SUQ|qd1l!{2Jk=}243OK;j zJ4C)tg!GuP2l=I!_H-0|4TIJw%v-i*Ia<2VWMew3LyW-J z>YTA-7_pBS5x};;ID5`IiRF#?I;1^DG@%8QJE*7~;;8@FO9LJBOUE{rDAHcK$Nt#2 zk<@an28JJ63%1I}*O}_+{9Bvi^u+3t(09wy&XJPF7nDIo_rr<3W#D=y3&)r^wyC@ zg#WUL79ZJV4gCN~^j6VJ*}zAxu~`7HIWL+7n{rMpyyRk0EIs$a+N8BCR?w6fR6oQ_Ge?4coDt~eraHvzfgEJ&(3In}SUEXbcH`m`v{3zo z$Xy~Aezh(ouahJ>fi?>)02VML6bYlMVi9#l zI@{YbjeI}*m@;c*6RY|W=3lXF2_1yKHh%vNMguhZfV{p->GFlciyqh#QS1qqdd5Y> z-m`OIUyS&|vvUJs+x^nXbY2Rk{8p2n&dKTc#9os9kUvrXD`k}UGkpvBb| z`W3;rBB!)mL>yH-y;R6&1(s2wYONV3?-*w|G|Qbh>5PzNW%Q(jv#3?CldGR%lBpHK zO-{tLWtB1h5JIC2$@k--G%GXmO_sQj*UYDx8XyjiB#xJk6XPdY_d7#H2S!zhP@+*3 zo$6a4rn;M@-G>&1)dqF=u~PA&ncGRSSQi6^|6ctVW35t=gf_t!x_?OAHj(=vrgC2J z&4i7q4V4_mO4{(;!QA`~2y+(sA`p0yH=JdDlis*JUPb+L6L{?W@96F8@pO5sD+!hG zhU!5Z85VO`pQ$^V@|h!emut#hPTxDBIB}18ilTvf@K6i{Mei61_?6o(eCk*rvB&`R z34RoP!rJ2^Jzq?E)C~0t~t38 z+8|;N%Ie*((B9KNl}so%ND3e!+3btdGfVAE|B|xqK&Dy>TRSPL9cJ}w0EDfN3+*V) zO|*kwN1yr` z$d0A$R%50lCX1pNVPO&W(}oCHV&BedS_Fc>CIHJEf<4GT*>Q zWo|VLSBpKnGAodGV-lkj(BbmzpxvK<()jj+vlvRhLH5tb7Ifj#?o`z6^HR!c{zc|A e<-5gC+&wx`ITe*P;9zmrfP8c>yUqFL`u_o2|LJ=G diff --git a/stamarker/stamarker/__pycache__/dataset.cpython-39.pyc b/stamarker/stamarker/__pycache__/dataset.cpython-39.pyc deleted file mode 100644 index 4b46d88ba7b731dc15e945f015df3260bf93e3a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4302 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 diff --git a/stamarker/stamarker/__pycache__/models.cpython-38.pyc b/stamarker/stamarker/__pycache__/models.cpython-38.pyc deleted file mode 100644 index dd9d50b8882770d75e12a8fcc35d638745edac71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11060 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 diff --git a/stamarker/stamarker/__pycache__/models.cpython-39.pyc b/stamarker/stamarker/__pycache__/models.cpython-39.pyc deleted file mode 100644 index 4ec959afa04d9341a52cd555c1a02f25495fb462..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11136 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 NTRc{H)O8B!{{je^00aO4 diff --git a/stamarker/stamarker/__pycache__/modules.cpython-38.pyc b/stamarker/stamarker/__pycache__/modules.cpython-38.pyc deleted file mode 100644 index 39452b5c406c9d5a25d698e1d79f3ebe0dcee0db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9861 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 Q5qNkr-`381vqXaa59a6&K>z>% diff --git a/stamarker/stamarker/__pycache__/pipeline.cpython-39.pyc b/stamarker/stamarker/__pycache__/pipeline.cpython-39.pyc deleted file mode 100644 index 0dd6347836a29b15224251d86998b9a9d9595d24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11634 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 diff --git a/stamarker/stamarker/__pycache__/utils.cpython-38.pyc b/stamarker/stamarker/__pycache__/utils.cpython-38.pyc deleted file mode 100644 index c1e4cfd8b716ceb71c8ed73c5da4531c5168e732..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6380 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 diff --git a/stamarker/stamarker/__pycache__/utils.cpython-39.pyc b/stamarker/stamarker/__pycache__/utils.cpython-39.pyc deleted file mode 100644 index 44823dbcdd4652998af33169f8933288d05bc903..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6736 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 None: - self.batch_size = batch_size - self.full_batch = full_batch - self.has_y = False - self.train_dataset = None - self.valid_dataset = None - self.num_neighbors = num_neighbors - self.num_workers = num_workers - self.pin_memory = pin_memory - self.ann_data = None - - def prepare_data(self, n_top_genes: int = 3000, rad_cutoff: float = 50, - show_net_stats: bool = False, min_cells=50, min_counts=None) -> None: - sc.pp.calculate_qc_metrics(self.ann_data, inplace=True) - sc.pp.filter_genes(self.ann_data, min_cells=min_cells) - if min_counts is not None: - sc.pp.filter_cells(self.ann_data, min_counts=min_counts) - print("After filtering: ", self.ann_data.shape) - # Normalization - sc.pp.highly_variable_genes(self.ann_data, flavor="seurat_v3", n_top_genes=n_top_genes) - self.ann_data = self.ann_data[:, self.ann_data.var['highly_variable']] - sc.pp.normalize_total(self.ann_data, target_sum=1e4) - sc.pp.log1p(self.ann_data) - compute_spatial_net(self.ann_data, rad_cutoff=rad_cutoff) - if show_net_stats: - stats_spatial_net(self.ann_data) - # ---------------------------- generate data --------------------- - edge_list = compute_edge_list(self.ann_data) - self.train_dataset = Data(edge_index=torch.LongTensor(np.array([edge_list[0], edge_list[1]])), - x=torch.FloatTensor(self.ann_data.X), - y=None) - - def train_dataloader(self): - if self.full_batch: - loader = NeighborLoader(self.train_dataset, num_neighbors=[1], - batch_size=len(self.train_dataset.x)) - else: - loader = NeighborLoader(self.train_dataset, num_neighbors=self.num_neighbors, batch_size=self.batch_size) - return loader - - def val_dataloader(self): - if self.valid_dataset is None: - loader = NeighborLoader(self.train_dataset, num_neighbors=[1], - batch_size=len(self.train_dataset.x)) - else: - raise NotImplementedError - return loader - - def test_dataloader(self) -> EVAL_DATALOADERS: - raise NotImplementedError - - def predict_dataloader(self) -> EVAL_DATALOADERS: - raise NotImplementedError diff --git a/stamarker/stamarker/models.py b/stamarker/stamarker/models.py deleted file mode 100644 index d7457ec..0000000 --- a/stamarker/stamarker/models.py +++ /dev/null @@ -1,257 +0,0 @@ -from abc import ABC -from typing import Any, List -import numpy as np -import pytorch_lightning as pl -import torch -import torch.nn.functional as F -from torch.utils.data import DataLoader, WeightedRandomSampler -from torch_geometric.data import Data -from sklearn.metrics import adjusted_rand_score, confusion_matrix -from .modules import STAGATEModule, StackMLPModule -from .dataset import RepDataset, Batch -from .utils import Timer - -def get_optimizer(name): - if name == "ADAM": - return torch.optim.Adam - elif name == "ADAGRAD": - return torch.optim.Adagrad - elif name == "ADADELTA": - return torch.optim.Adadelta - elif name == "RMS": - return torch.optim.RMSprop - elif name == "ASGD": - return torch.optim.ASGD - else: - raise NotImplementedError - - -def get_scheduler(name): - if name == "STEP_LR": - return torch.optim.lr_scheduler.StepLR - elif name == "EXP_LR": - return torch.optim.lr_scheduler.ExponentialLR - else: - raise NotImplementedError - -class BaseModule(pl.LightningModule, ABC): - def __init__(self): - super(BaseModule, self).__init__() - self.optimizer_params = None - self.scheduler_params = None - self.model = None - self.timer = Timer() - self.automatic_optimization = False - - def set_optimizer_params(self, - optimizer_params: dict, - scheduler_params: dict): - self.optimizer_params = optimizer_params - self.scheduler_params = scheduler_params - - def configure_optimizers(self): - optimizer = get_optimizer(self.optimizer_params["name"])( - self.model.parameters(), - **self.optimizer_params["params"]) - scheduler = get_scheduler(self.scheduler_params["name"])(optimizer, **self.scheduler_params["params"]) - return [optimizer], [scheduler] - - def on_train_epoch_start(self) -> None: - self.timer.tic('train') - - -class intSTAGATE(BaseModule): - """ - intSTAGATE Lightning Module - """ - def __init__(self, - in_features: int = None, - hidden_dims: List[int] = None, - gradient_clipping: float = 5.0, - **kwargs): - super(intSTAGATE, self).__init__() - self.model = STAGATEModule(in_features, hidden_dims) - self.auto_encoder_epochs = None - self.gradient_clipping = gradient_clipping - self.pred_labels = None - self.save_hyperparameters() - - def configure_optimizers(self) -> (dict, dict): - auto_encoder_optimizer = get_optimizer(self.optimizer_params["name"])( - list(self.model.parameters()), - **self.optimizer_params["params"]) - auto_encoder_scheduler = get_scheduler(self.scheduler_params["name"])(auto_encoder_optimizer, - **self.scheduler_params["params"]) - return [auto_encoder_optimizer], [auto_encoder_scheduler] - - def forward(self, x, edge_index) -> Any: - return self.model(x, edge_index) - - def training_step(self, batch, batch_idx): - batch = batch.to(self.device) - opt_auto_encoder = self.optimizers() - z, x_hat = self.model(batch.x, batch.edge_index) - loss = F.mse_loss(batch.x, x_hat) - opt_auto_encoder.zero_grad() - self.manual_backward(loss) - torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.gradient_clipping) - opt_auto_encoder.step() - self.log("Training auto-encoder|Reconstruction errors", loss.item(), prog_bar=True) - self.logger.experiment.add_scalar('auto_encoder/loss', loss.item(), self.current_epoch) - - def on_train_epoch_end(self) -> None: - time = self.timer.toc('train') - sch_auto_encoder = self.lr_schedulers() - sch_auto_encoder.step() - self.logger.experiment.add_scalar('train_time', time, self.current_epoch) - - def validation_step(self, batch, batch_idx): - pass - - def validation_epoch_end(self, outputs): - pass - - -def _compute_correct(scores, target_y): - _, pred_labels = torch.max(scores, axis=1) - correct = (pred_labels == target_y).sum().item() - return pred_labels, correct - - -class CoordTransformer(object): - def __init__(self, coord): - self.coord = coord - - def transform(self): - factor = np.max(np.max(self.coord, axis=0) - np.min(self.coord, axis=0)) - return (self.coord - np.min(self.coord, axis=0)) / factor - - -class StackClassifier(BaseModule): - def __init__(self, in_features: int, - n_classes: int = 7, - batch_size: int = 1000, - shuffle: bool = False, - hidden_dims: List[int] = [30], - architecture: str = "MLP", - sta_path: str = None, - **kwargs): - super(StackClassifier, self).__init__() - self.in_features = in_features - self.architecture = architecture - self.batch_size = batch_size - self.shuffle = shuffle - if architecture == "MLP": - self.model = StackMLPModule(in_features, n_classes, hidden_dims, **kwargs) - else: - raise NotImplementedError - self.dataset = None - self.train_dataset = None - self.val_dataset = None - self.automatic_optimization = False - self.sampler = None - self.test_prop = None - self.confusion = None - self.balanced = None - self.save_hyperparameters() - - def prepare(self, - stagate: intSTAGATE, - dataset: Data, - target_y, - test_prop: float = 0.5, - balanced: bool = True): - self.balanced = balanced - self.test_prop = test_prop - with torch.no_grad(): - representation, _ = stagate(dataset.x, dataset.edge_index) - if hasattr(dataset, "ground_truth"): - ground_truth = dataset.ground_truth - else: - ground_truth = None - if isinstance(target_y, np.ndarray): - target_y = torch.from_numpy(target_y).type(torch.LongTensor) - elif isinstance(target_y, torch.Tensor): - target_y = target_y.type(torch.LongTensor) - else: - raise TypeError("target_y must be either a torch tensor or a numpy ndarray.") - self.dataset = RepDataset(representation, target_y, ground_truth=ground_truth) - n_val = int(len(self.dataset) * test_prop) - self.train_dataset, self.val_dataset = torch.utils.data.random_split( - self.dataset, [len(self.dataset) - n_val, n_val]) - if balanced: - target_y = target_y[self.train_dataset.indices] - class_sample_count = np.array([len(np.where(target_y == t)[0]) for t in np.unique(target_y)]) - weight = 1. / class_sample_count - samples_weight = np.array([weight[t] for t in target_y]) - samples_weight = torch.from_numpy(samples_weight) - samples_weight = samples_weight.double() - self.sampler = WeightedRandomSampler(samples_weight, len(samples_weight)) - - def forward(self, x, edge_index=None) -> Any: - if self.architecture == "MLP": - return self.model(x) - elif self.architecture == "STACls": - _, output = self.model(x, edge_index) - return output - - def training_step(self, batch, batch_idx): - batch = Batch(**batch) - batch = batch.to(self.device) - opt = self.optimizers() - opt.zero_grad() - output = self.model(batch.x) - loss = F.cross_entropy(output["score"], batch.y) - self.manual_backward(loss) - opt.step() - _, correct = _compute_correct(output["score"], batch.y) - self.log(f"Training {self.architecture} classifier|Cross entropy", loss.item(), prog_bar=True) - return {"loss": loss, "correct": correct} - - def training_epoch_end(self, outputs): - time = self.timer.toc('train') - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/train_time', time, self.current_epoch) - all_loss = torch.stack([x["loss"] for x in outputs]) - all_correct = np.sum([x["correct"] for x in outputs]) - train_acc = all_correct / len(self.train_dataset) - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/loss', - torch.mean(all_loss), self.current_epoch) - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/train_acc', - train_acc, self.current_epoch) - - def validation_step(self, batch, batch_idx): - batch = Batch(**batch) - batch = batch.to(self.device) - with torch.no_grad(): - output = self.model(batch.x) - loss = F.cross_entropy(output["score"], batch.y) - pred_labels, correct = _compute_correct(output["score"], batch.y) - return {"loss": loss, "correct": correct, "pred_labels": pred_labels, "true_labels": batch.y} - - def validation_epoch_end(self, outputs): - all_loss = torch.stack([x["loss"] for x in outputs]) - all_correct = np.sum([x["correct"] for x in outputs]) - pred_labels = torch.cat([x["pred_labels"] for x in outputs]).cpu().detach().numpy() - true_labels = torch.cat([x["true_labels"] for x in outputs]).cpu().detach().numpy() - confusion = confusion_matrix(true_labels, pred_labels) - val_acc = all_correct / len(self.val_dataset) - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/val_loss', - torch.mean(all_loss), self.current_epoch) - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/val_acc', - val_acc, self.current_epoch) - print("\n validation ACC={:.4f}".format(val_acc)) - self.confusion = confusion - - def train_dataloader(self): - loader = DataLoader(self.train_dataset, batch_size=self.batch_size, sampler=self.sampler) - return loader - - def val_dataloader(self): - loader = DataLoader(self.val_dataset, batch_size=self.batch_size, shuffle=self.shuffle, drop_last=False) - return loader - - def test_dataloader(self): - raise NotImplementedError - - def predict_dataloader(self): - raise NotImplementedError \ No newline at end of file diff --git a/stamarker/stamarker/modules.py b/stamarker/stamarker/modules.py deleted file mode 100644 index 08d9d81..0000000 --- a/stamarker/stamarker/modules.py +++ /dev/null @@ -1,276 +0,0 @@ -import abc -import copy -from torch.autograd import Variable -import torch -from torch import Tensor -import torch.nn.functional as F -from torch.nn import Parameter -import torch.nn as nn -from torch_sparse import SparseTensor, set_diag -from torch_geometric.nn.conv import MessagePassing -from torch_geometric.utils import remove_self_loops, add_self_loops, softmax -from typing import Union, Tuple, Optional -from torch_geometric.typing import (OptPairTensor, Adj, Size, NoneType, - OptTensor) - - -class GATConv(MessagePassing): - r"""The graph attentional operator from the `"Graph Attention Networks" - `_ paper - .. math:: - \mathbf{x}^{\prime}_i = \alpha_{i,i}\mathbf{\Theta}\mathbf{x}_{i} + - \sum_{j \in \mathcal{N}(i)} \alpha_{i,j}\mathbf{\Theta}\mathbf{x}_{j}, - where the attention coefficients :math:`\alpha_{i,j}` are computed as - .. math:: - \alpha_{i,j} = - \frac{ - \exp\left(\mathrm{LeakyReLU}\left(\mathbf{a}^{\top} - [\mathbf{\Theta}\mathbf{x}_i \, \Vert \, \mathbf{\Theta}\mathbf{x}_j] - \right)\right)} - {\sum_{k \in \mathcal{N}(i) \cup \{ i \}} - \exp\left(\mathrm{LeakyReLU}\left(\mathbf{a}^{\top} - [\mathbf{\Theta}\mathbf{x}_i \, \Vert \, \mathbf{\Theta}\mathbf{x}_k] - \right)\right)}. - Args: - in_channels (int or tuple): Size of each input sample, or :obj:`-1` to - derive the size from the first input(s) to the forward method. - A tuple corresponds to the sizes of source and target - dimensionalities. - out_channels (int): Size of each output sample. - heads (int, optional): Number of multi-head-attentions. - (default: :obj:`1`) - concat (bool, optional): If set to :obj:`False`, the multi-head - attentions are averaged instead of concatenated. - (default: :obj:`True`) - negative_slope (float, optional): LeakyReLU angle of the negative - slope. (default: :obj:`0.2`) - dropout (float, optional): Dropout probability of the normalized - attention coefficients which exposes each node to a stochastically - sampled neighborhood during training. (default: :obj:`0`) - add_self_loops (bool, optional): If set to :obj:`False`, will not add - self-loops to the input graph. (default: :obj:`True`) - bias (bool, optional): If set to :obj:`False`, the layer will not learn - an additive bias. (default: :obj:`True`) - **kwargs (optional): Additional arguments of - :class:`torch_geometric.nn.conv.MessagePassing`. - """ - _alpha: OptTensor - - def __init__(self, in_channels: Union[int, Tuple[int, int]], - out_channels: int, heads: int = 1, concat: bool = True, - negative_slope: float = 0.2, dropout: float = 0.0, - add_self_loops: bool = True, bias: bool = True, **kwargs): - kwargs.setdefault('aggr', 'add') - super(GATConv, self).__init__(node_dim=0, **kwargs) - - self.in_channels = in_channels - self.out_channels = out_channels - self.heads = heads - self.concat = concat - self.negative_slope = negative_slope - self.dropout = dropout - self.add_self_loops = add_self_loops - self.lin_src = nn.Parameter(torch.zeros(size=(in_channels, out_channels))) - nn.init.xavier_normal_(self.lin_src.data, gain=1.414) - self.lin_dst = self.lin_src - # The learnable parameters to compute attention coefficients: - self.att_src = Parameter(torch.Tensor(1, heads, out_channels)) - self.att_dst = Parameter(torch.Tensor(1, heads, out_channels)) - nn.init.xavier_normal_(self.att_src.data, gain=1.414) - nn.init.xavier_normal_(self.att_dst.data, gain=1.414) - self._alpha = None - self.attentions = None - - def forward(self, x: Union[Tensor, OptPairTensor], edge_index: Adj, - size: Size = None, return_attention_weights=None, attention=True, tied_attention=None): - # type: (Union[Tensor, OptPairTensor], Tensor, Size, NoneType) -> Tensor # noqa - # type: (Union[Tensor, OptPairTensor], SparseTensor, Size, NoneType) -> Tensor # noqa - # type: (Union[Tensor, OptPairTensor], Tensor, Size, bool) -> Tuple[Tensor, Tuple[Tensor, Tensor]] # noqa - # type: (Union[Tensor, OptPairTensor], SparseTensor, Size, bool) -> Tuple[Tensor, SparseTensor] # noqa - r""" - Args: - return_attention_weights (bool, optional): If set to :obj:`True`, - will additionally return the tuple - :obj:`(edge_index, attention_weights)`, holding the computed - attention weights for each edge. (default: :obj:`None`) - """ - H, C = self.heads, self.out_channels - - # We first transform the input node features. If a tuple is passed, we - # transform source and target node features via separate weights: - if isinstance(x, Tensor): - assert x.dim() == 2, "Static graphs not supported in 'GATConv'" - # x_src = x_dst = self.lin_src(x).view(-1, H, C) - x_src = x_dst = torch.mm(x, self.lin_src).view(-1, H, C) - else: # Tuple of source and target node features: - x_src, x_dst = x - assert x_src.dim() == 2, "Static graphs not supported in 'GATConv'" - x_src = self.lin_src(x_src).view(-1, H, C) - if x_dst is not None: - x_dst = self.lin_dst(x_dst).view(-1, H, C) - - x = (x_src, x_dst) - - if not attention: - return x[0].mean(dim=1) - # return x[0].view(-1, self.heads * self.out_channels) - - if tied_attention == None: - # Next, we compute node-level attention coefficients, both for source - # and target nodes (if present): - alpha_src = (x_src * self.att_src).sum(dim=-1) - alpha_dst = None if x_dst is None else (x_dst * self.att_dst).sum(-1) - alpha = (alpha_src, alpha_dst) - self.attentions = alpha - else: - alpha = tied_attention - - if self.add_self_loops: - if isinstance(edge_index, Tensor): - # We only want to add self-loops for nodes that appear both as - # source and target nodes: - num_nodes = x_src.size(0) - if x_dst is not None: - num_nodes = min(num_nodes, x_dst.size(0)) - num_nodes = min(size) if size is not None else num_nodes - edge_index, _ = remove_self_loops(edge_index) - edge_index, _ = add_self_loops(edge_index, num_nodes=num_nodes) - elif isinstance(edge_index, SparseTensor): - edge_index = set_diag(edge_index) - - # propagate_type: (x: OptPairTensor, alpha: OptPairTensor) - out = self.propagate(edge_index, x=x, alpha=alpha, size=size) - - alpha = self._alpha - assert alpha is not None - self._alpha = None - - if self.concat: - out = out.view(-1, self.heads * self.out_channels) - else: - out = out.mean(dim=1) - - # if self.bias is not None: - # out += self.bias - - if isinstance(return_attention_weights, bool): - if isinstance(edge_index, Tensor): - return out, (edge_index, alpha) - elif isinstance(edge_index, SparseTensor): - return out, edge_index.set_value(alpha, layout='coo') - else: - return out - - def message(self, x_j: Tensor, alpha_j: Tensor, alpha_i: OptTensor, - index: Tensor, ptr: OptTensor, - size_i: Optional[int]) -> Tensor: - # Given egel-level attention coefficients for source and target nodes, - # we simply need to sum them up to "emulate" concatenation: - alpha = alpha_j if alpha_i is None else alpha_j + alpha_i - - alpha = F.leaky_relu(alpha, self.negative_slope) - alpha = softmax(alpha, index, ptr, size_i) - self._alpha = alpha # Save for later use. - alpha = F.dropout(alpha, p=self.dropout, training=self.training) - return x_j * alpha.unsqueeze(-1) - - def __repr__(self): - return '{}({}, {}, heads={})'.format(self.__class__.__name__, - self.in_channels, - self.out_channels, self.heads) - - -class STAGATEModule(nn.Module): - def __init__(self, in_features, hidden_dims): - super(STAGATEModule, self).__init__() - [num_hidden, out_dim] = hidden_dims - self.conv1 = GATConv(in_features, num_hidden, heads=1, concat=False, - dropout=0, add_self_loops=False, bias=False) - self.conv2 = GATConv(num_hidden, out_dim, heads=1, concat=False, - dropout=0, add_self_loops=False, bias=False) - self.conv3 = GATConv(out_dim, num_hidden, heads=1, concat=False, - dropout=0, add_self_loops=False, bias=False) - self.conv4 = GATConv(num_hidden, in_features, heads=1, concat=False, - dropout=0, add_self_loops=False, bias=False) - - def forward(self, features, edge_index): - h1 = F.elu(self.conv1(features, edge_index)) - h2 = self.conv2(h1, edge_index, attention=False) - self.conv3.lin_src.data = self.conv2.lin_src.transpose(0, 1) - self.conv3.lin_dst.data = self.conv2.lin_dst.transpose(0, 1) - self.conv4.lin_src.data = self.conv1.lin_src.transpose(0, 1) - self.conv4.lin_dst.data = self.conv1.lin_dst.transpose(0, 1) - h3 = F.elu(self.conv3(h2, edge_index, attention=True, - tied_attention=self.conv1.attentions)) - h4 = self.conv4(h3, edge_index, attention=False) - - return h2, h4 - - -class StackClsModule(nn.Module, abc.ABC): - def __init__(self, in_features, n_classes): - super(StackClsModule, self).__init__() - self.in_features = in_features - self.n_classes = n_classes - - -class STAGATEClsModule(nn.Module): - def __init__(self, - stagate: STAGATEModule, - stack_classifier: StackClsModule): - super(STAGATEClsModule, self).__init__() - self.stagate = copy.deepcopy(stagate) - self.classifier = copy.deepcopy(stack_classifier) - - def forward(self, x, edge_index, mode="classifier"): - z, x_recon = self.stagate(x, edge_index) - z = torch.clone(z) - if mode == "classifier": - return z, self.classifier(z) - elif mode == "reconstruction": - return z, x_recon - else: - raise NotImplementedError - - def get_saliency_map(self, x, edge_index, target_index="max", save=None): - """ - Get saliency map by backpropagation. - :param x: input tensors - :param edge_index: graph edge index - :param target_index: target index to compute final scores - :param save: - :return: gradients - """ - x_var = Variable(x, requires_grad=True) - _, output = self.forward(x_var, edge_index, mode="classifier") - scores = output["last_layer"] - if target_index == "max": - target_score_indices = Variable(torch.argmax(scores, 1)) - elif isinstance(target_index, int): - target_score_indices = Variable(torch.ones(scores.shape[0], dtype=torch.int64) * target_index) - else: - raise NotImplementedError - target_scores = scores.gather(1, target_score_indices.view(-1, 1)).squeeze() - loss = torch.sum(target_scores) - loss.backward() - gradients = x_var.grad.data - if save is not None: - torch.save(gradients, save) - return gradients, scores - - -class StackMLPModule(StackClsModule): - name = "StackMLP" - - def __init__(self, in_features, n_classes, hidden_dims=[30, 40, 30]): - super(StackMLPModule, self).__init__(in_features, n_classes) - self.classifier = nn.ModuleList() - mlp_dims = [in_features] + hidden_dims + [n_classes] - for ind in range(len(mlp_dims) - 1): - self.classifier.append(nn.Linear(mlp_dims[ind], mlp_dims[ind + 1])) - - def forward(self, x): - for layer in self.classifier: - x = layer(x) - score = F.softmax(x, dim=0) - return {"last_layer": x, "score": score} diff --git a/stamarker/stamarker/pipeline.py b/stamarker/stamarker/pipeline.py deleted file mode 100644 index c24592a..0000000 --- a/stamarker/stamarker/pipeline.py +++ /dev/null @@ -1,287 +0,0 @@ -import pytorch_lightning as pl -import copy -import torch -import os -import shutil -import logging -import glob -import sys -import numpy as np -import scipy -import scanpy as sc -from pytorch_lightning.loggers import TensorBoardLogger -from scipy.cluster import hierarchy -from .models import intSTAGATE, StackClassifier -from .utils import plot_consensus_map, consensus_matrix, Timer -from .dataset import SpatialDataModule -from .modules import STAGATEClsModule -import logging - - -FORMAT = "%(asctime)s %(levelname)s %(message)s" -logging.basicConfig(format=FORMAT, datefmt='%Y-%m-%d %H:%M:%S') -def make_spatial_data(ann_data): - """ - Make SpatialDataModule object from Scanpy annData object - """ - data_module = SpatialDataModule() - ann_data.X = ann_data.X.toarray() - data_module.ann_data = ann_data - return data_module - - -class STAMarker: - def __init__(self, n, save_dir, config, logging_level=logging.INFO): - """ - n: int, number of graph attention auto-econders to train - save_dir: directory to save the models - config: config file for training - """ - self.n = n - self.save_dir = save_dir - if not os.path.exists(save_dir): - os.mkdir(save_dir) - logging.info("Create save directory {}".format(save_dir)) - self.version_dirs = [os.path.join(save_dir, f"version_{i}") for i in range(n)] - self.config = config - self.logger = logging.getLogger("STAMarker") - self.logger.setLevel(logging_level) - self.consensus_labels = None - - def load_from_dir(self, save_dir, ): - """ - Load the trained models from a directory - """ - self.version_dirs = glob.glob(os.path.join(save_dir, "version_*")) - self.version_dirs = sorted(self.version_dirs, key=lambda x: int(x.split("_")[-1])) - # check if all version dir have `checkpoints/stagate.ckpt` - version_dirs_valid = [] - for version_dir in self.version_dirs: - if not os.path.exists(os.path.join(version_dir, "checkpoints/stagate.ckpt")): - self.logger.warning("No checkpoint found in {}".format(version_dir)) - else: - version_dirs_valid.append(version_dir) - self.version_dirs = version_dirs_valid - self.logger.info("Load {} autoencoder models from {}".format(len(version_dirs_valid), save_dir)) - # check if all version dir have `cluster_labels.npy` raise warning if not - missing_cluster_labels = False - for version_dir in self.version_dirs: - if not os.path.exists(os.path.join(version_dir, "cluster_labels.npy")): - missing_cluster_labels = True - msg = "No cluster labels found in {}.".format(version_dir) - self.logger.warning(msg) - if missing_cluster_labels: - self.logger.warning("Please run clustering first.") - # check if save_dir has `consensus.npy` raise warning if not - if not os.path.exists(os.path.join(save_dir, "consensus.npy")): - self.logger.warning("No consensus labels found in {}".format(save_dir)) - else: - self.consensus_labels = np.load(os.path.join(save_dir, "consensus.npy")) - # check if all version dir have `checkpoints/mlp.ckpt` raise warning if not - missing_clf = False - for version_dir in self.version_dirs: - if not os.path.exists(os.path.join(version_dir, "checkpoints/mlp.ckpt")): - self.logger.warning("No classifier checkpoint found in {}".format(version_dir)) - missing_clf = True - if missing_clf: - self.logger.warning("Please run classifier training first.") - if not missing_cluster_labels and not missing_clf: - self.logger.info("All models are trained and ready to use.") - - def train_auto_encoders(self, data_module): - for seed in range(self.n): - self._train_auto_encoder(data_module, seed, self.config) - self.logger.info("Finished training {} auto-encoders".format(self.n)) - - def clustering(self, data_module, cluster_method, cluster_params): - """ - Cluster the latent space of the trained auto-encoders - Cluster method should be "louvain" or "mclust" - """ - for version_dir in self.version_dirs: - self._clustering(data_module, version_dir, cluster_method, cluster_params) - self.logger.info("Finished {} clustering with {}".format(self.n, cluster_method)) - - def consensus_clustering(self, n_clusters, name="cluster_labels.npy"): - sys.setrecursionlimit(100000) - label_files = glob.glob(self.save_dir + f"/version_*/{name}") - labels_list = list(map(lambda file: np.load(file), label_files)) - cons_mat = consensus_matrix(labels_list) - row_linkage, _, figure = plot_consensus_map(cons_mat, return_linkage=True) - figure.savefig(os.path.join(self.save_dir, "consensus_clustering.png"), dpi=300) - consensus_labels = hierarchy.cut_tree(row_linkage, n_clusters).squeeze() - np.save(os.path.join(self.save_dir, "consensus"), consensus_labels) - self.consensus_labels = consensus_labels - self.logger.info("Save consensus labels to {}".format(os.path.join(self.save_dir, "consensus.npz"))) - - def train_classifiers(self, data_module, n_clusters, name="cluster_labels.npy"): - for i, version_dir in enumerate(self.version_dirs): - # _train_classifier(self, data_module, version_dir, target_y, n_classes, seed=None) - self._train_classifier(data_module, version_dir, self.consensus_labels, - n_clusters, self.config, seed=i) - self.logger.info("Finished training {} classifiers".format(self.n)) - - def compute_smaps(self, data_module, return_recon=True, normalize=True): - smaps = [] - if return_recon: - recons = [] - for version_dir in self.version_dirs: - if return_recon: - smap, recon = self._compute_smap(data_module, version_dir, return_recon=return_recon) - smaps.append(smap) - recons.append(recon) - else: - smap = self._compute_smap(data_module, version_dir, return_recon=return_recon) - smaps.append(smap) - if return_recon: - return smaps, recons - else: - return smaps - self.logger.info("Finished computing {} smaps".format(self.n)) - - - def _compute_smap_zscore(self, smap, labels, logtransform=False): - scores = np.log(smap + 1) if logtransform else copy.copy(smap) - unique_labels = np.unique(labels) - for l in unique_labels: - scores[labels == l, :] = scipy.stats.zscore(scores[labels == l, :], axis=1) - return scores - - - def _clustering(self, data_module, version_dir, cluster_method, cluster_params): - """ - Cluster the latent space of the trained auto-encoder - """ - if cluster_method == "louvain": - run_louvain(data_module, version_dir, cluster_params) - elif cluster_method == "mclust": - run_mclust(data_module, version_dir, cluster_params) - else: - raise ValueError("Unknown clustering method") - - def _train_auto_encoder(self, data_module, seed, config): - """ - Train a single graph attention auto-encoder - """ - pl.seed_everything(seed) - version = f"version_{seed}" - version_dir = os.path.join(self.save_dir, version) - if os.path.exists(version_dir): - shutil.rmtree(version_dir) - os.makedirs(version_dir, exist_ok=True) - logger = TensorBoardLogger(save_dir=self.save_dir, name=None, - default_hp_metric=False, - version=seed) - model = intSTAGATE(**config["stagate"]["params"]) - model.set_optimizer_params(config["stagate"]["optimizer"], config["stagate"]["scheduler"]) - trainer = pl.Trainer(logger=logger, **config["stagate_trainer"]) - timer = Timer() - timer.tic("fit") - trainer.fit(model, data_module) - fit_time = timer.toc("fit") - with open(os.path.join(version_dir, "runtime.csv"), "w+") as f: - f.write("{}, fit_time, {:.2f}, ".format(seed, fit_time / 60)) - trainer.save_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - del model, trainer - if config["stagate_trainer"]["gpus"] > 0: - torch.cuda.empty_cache() - logging.info(f"Finshed running version {seed}") - - def _train_classifier(self, data_module, version_dir, target_y, n_classes, config, seed=None): - timer = Timer() - pl.seed_everything(seed) - rep_dim = config["stagate"]["params"]["hidden_dims"][-1] - stagate = intSTAGATE.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - classifier = StackClassifier(rep_dim, n_classes=n_classes, architecture="MLP") - classifier.prepare(stagate, data_module.train_dataset, target_y, - balanced=config["mlp"]["balanced"], test_prop=config["mlp"]["test_prop"]) - classifier.set_optimizer_params(config["mlp"]["optimizer"], config["mlp"]["scheduler"]) - logger = TensorBoardLogger(save_dir=self.save_dir, name=None, - default_hp_metric=False, - version=seed) - trainer = pl.Trainer(logger=logger, **config["classifier_trainer"]) - timer.tic("clf") - trainer.fit(classifier) - clf_time = timer.toc("clf") - with open(os.path.join(version_dir, "runtime.csv"), "a+") as f: - f.write("\n") - f.write("{}, clf_time, {:.2f}, ".format(seed, clf_time / 60)) - trainer.save_checkpoint(os.path.join(version_dir, "checkpoints", "mlp.ckpt")) - target_y = classifier.dataset.target_y.numpy() - all_props = class_proportions(target_y) - val_props = class_proportions(target_y[classifier.val_dataset.indices]) - if self.logger.level == logging.DEBUG: - print("All class proportions " + "|".join(["{:.2f}%".format(prop * 100) for prop in all_props])) - print("Val class proportions " + "|".join(["{:.2f}%".format(prop * 100) for prop in val_props])) - np.save(os.path.join(version_dir, "confusion.npy"), classifier.confusion) - - def _compute_smap(self, data_module, version_dir, return_recon=True): - """ - Compute the saliency map of the trained auto-encoder - """ - stagate = intSTAGATE.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - cls = StackClassifier.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "mlp.ckpt")) - stagate_cls = STAGATEClsModule(stagate.model, cls.model) - smap, _ = stagate_cls.get_saliency_map(data_module.train_dataset.x, - data_module.train_dataset.edge_index) - smap = smap.detach().cpu().numpy() - if return_recon: - recon = stagate(data_module.train_dataset.x, data_module.train_dataset.edge_index)[1].cpu().detach().numpy() - return smap, recon - else: - return smap - - -def run_louvain(data_module, version_dir, resolution, name="cluster_labels"): - """ - Run louvain clustering on the data_module - """ - stagate = intSTAGATE.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - embedding = stagate(data_module.train_dataset.x, data_module.train_dataset.edge_index)[0].cpu().detach().numpy() - ann_data = copy.copy(data_module.ann_data) - ann_data.obsm["stagate"] = embedding - sc.pp.neighbors(ann_data, use_rep='stagate') - sc.tl.louvain(ann_data, resolution=resolution) - save_path = os.path.join(version_dir, "{}.npy".format(name)) - np.save(save_path, ann_data.obs["louvain"].to_numpy().astype("int")) - print("Save louvain results to {}".format(save_path)) - - -def mclust_R(representation, n_clusters, r_seed=2022, model_name="EEE"): - """ - Clustering using the mclust algorithm. - The parameters are the same as those in the R package mclust. - """ - np.random.seed(r_seed) - import rpy2.robjects as ro - from rpy2.robjects import numpy2ri - numpy2ri.activate() - ro.r.library("mclust") - r_random_seed = ro.r['set.seed'] - r_random_seed(r_seed) - rmclust = ro.r['Mclust'] - res = rmclust(representation, n_clusters, model_name) - mclust_res = np.array(res[-2]) - numpy2ri.deactivate() - return mclust_res.astype('int') - - -def run_mclust(data_module, version_dir, n_clusters, name="cluster_labels"): - stagate = intSTAGATE.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - embedding = stagate(data_module.train_dataset.x, data_module.train_dataset.edge_index)[0].cpu().detach().numpy() - labels = mclust_R(embedding, n_clusters) - save_path = os.path.join(version_dir, "{}.npy".format(name)) - np.save(save_path, labels.astype("int")) - print("Save MClust results to {}".format(save_path)) - -def class_proportions(target): - n_classes = len(np.unique(target)) - props = np.array([np.sum(target == i) for i in range(n_classes)]) - return props / np.sum(props) - - - - - - - diff --git a/stamarker/stamarker/stamarker/.ipynb_checkpoints/models-checkpoint.py b/stamarker/stamarker/stamarker/.ipynb_checkpoints/models-checkpoint.py deleted file mode 100644 index d7457ec..0000000 --- a/stamarker/stamarker/stamarker/.ipynb_checkpoints/models-checkpoint.py +++ /dev/null @@ -1,257 +0,0 @@ -from abc import ABC -from typing import Any, List -import numpy as np -import pytorch_lightning as pl -import torch -import torch.nn.functional as F -from torch.utils.data import DataLoader, WeightedRandomSampler -from torch_geometric.data import Data -from sklearn.metrics import adjusted_rand_score, confusion_matrix -from .modules import STAGATEModule, StackMLPModule -from .dataset import RepDataset, Batch -from .utils import Timer - -def get_optimizer(name): - if name == "ADAM": - return torch.optim.Adam - elif name == "ADAGRAD": - return torch.optim.Adagrad - elif name == "ADADELTA": - return torch.optim.Adadelta - elif name == "RMS": - return torch.optim.RMSprop - elif name == "ASGD": - return torch.optim.ASGD - else: - raise NotImplementedError - - -def get_scheduler(name): - if name == "STEP_LR": - return torch.optim.lr_scheduler.StepLR - elif name == "EXP_LR": - return torch.optim.lr_scheduler.ExponentialLR - else: - raise NotImplementedError - -class BaseModule(pl.LightningModule, ABC): - def __init__(self): - super(BaseModule, self).__init__() - self.optimizer_params = None - self.scheduler_params = None - self.model = None - self.timer = Timer() - self.automatic_optimization = False - - def set_optimizer_params(self, - optimizer_params: dict, - scheduler_params: dict): - self.optimizer_params = optimizer_params - self.scheduler_params = scheduler_params - - def configure_optimizers(self): - optimizer = get_optimizer(self.optimizer_params["name"])( - self.model.parameters(), - **self.optimizer_params["params"]) - scheduler = get_scheduler(self.scheduler_params["name"])(optimizer, **self.scheduler_params["params"]) - return [optimizer], [scheduler] - - def on_train_epoch_start(self) -> None: - self.timer.tic('train') - - -class intSTAGATE(BaseModule): - """ - intSTAGATE Lightning Module - """ - def __init__(self, - in_features: int = None, - hidden_dims: List[int] = None, - gradient_clipping: float = 5.0, - **kwargs): - super(intSTAGATE, self).__init__() - self.model = STAGATEModule(in_features, hidden_dims) - self.auto_encoder_epochs = None - self.gradient_clipping = gradient_clipping - self.pred_labels = None - self.save_hyperparameters() - - def configure_optimizers(self) -> (dict, dict): - auto_encoder_optimizer = get_optimizer(self.optimizer_params["name"])( - list(self.model.parameters()), - **self.optimizer_params["params"]) - auto_encoder_scheduler = get_scheduler(self.scheduler_params["name"])(auto_encoder_optimizer, - **self.scheduler_params["params"]) - return [auto_encoder_optimizer], [auto_encoder_scheduler] - - def forward(self, x, edge_index) -> Any: - return self.model(x, edge_index) - - def training_step(self, batch, batch_idx): - batch = batch.to(self.device) - opt_auto_encoder = self.optimizers() - z, x_hat = self.model(batch.x, batch.edge_index) - loss = F.mse_loss(batch.x, x_hat) - opt_auto_encoder.zero_grad() - self.manual_backward(loss) - torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.gradient_clipping) - opt_auto_encoder.step() - self.log("Training auto-encoder|Reconstruction errors", loss.item(), prog_bar=True) - self.logger.experiment.add_scalar('auto_encoder/loss', loss.item(), self.current_epoch) - - def on_train_epoch_end(self) -> None: - time = self.timer.toc('train') - sch_auto_encoder = self.lr_schedulers() - sch_auto_encoder.step() - self.logger.experiment.add_scalar('train_time', time, self.current_epoch) - - def validation_step(self, batch, batch_idx): - pass - - def validation_epoch_end(self, outputs): - pass - - -def _compute_correct(scores, target_y): - _, pred_labels = torch.max(scores, axis=1) - correct = (pred_labels == target_y).sum().item() - return pred_labels, correct - - -class CoordTransformer(object): - def __init__(self, coord): - self.coord = coord - - def transform(self): - factor = np.max(np.max(self.coord, axis=0) - np.min(self.coord, axis=0)) - return (self.coord - np.min(self.coord, axis=0)) / factor - - -class StackClassifier(BaseModule): - def __init__(self, in_features: int, - n_classes: int = 7, - batch_size: int = 1000, - shuffle: bool = False, - hidden_dims: List[int] = [30], - architecture: str = "MLP", - sta_path: str = None, - **kwargs): - super(StackClassifier, self).__init__() - self.in_features = in_features - self.architecture = architecture - self.batch_size = batch_size - self.shuffle = shuffle - if architecture == "MLP": - self.model = StackMLPModule(in_features, n_classes, hidden_dims, **kwargs) - else: - raise NotImplementedError - self.dataset = None - self.train_dataset = None - self.val_dataset = None - self.automatic_optimization = False - self.sampler = None - self.test_prop = None - self.confusion = None - self.balanced = None - self.save_hyperparameters() - - def prepare(self, - stagate: intSTAGATE, - dataset: Data, - target_y, - test_prop: float = 0.5, - balanced: bool = True): - self.balanced = balanced - self.test_prop = test_prop - with torch.no_grad(): - representation, _ = stagate(dataset.x, dataset.edge_index) - if hasattr(dataset, "ground_truth"): - ground_truth = dataset.ground_truth - else: - ground_truth = None - if isinstance(target_y, np.ndarray): - target_y = torch.from_numpy(target_y).type(torch.LongTensor) - elif isinstance(target_y, torch.Tensor): - target_y = target_y.type(torch.LongTensor) - else: - raise TypeError("target_y must be either a torch tensor or a numpy ndarray.") - self.dataset = RepDataset(representation, target_y, ground_truth=ground_truth) - n_val = int(len(self.dataset) * test_prop) - self.train_dataset, self.val_dataset = torch.utils.data.random_split( - self.dataset, [len(self.dataset) - n_val, n_val]) - if balanced: - target_y = target_y[self.train_dataset.indices] - class_sample_count = np.array([len(np.where(target_y == t)[0]) for t in np.unique(target_y)]) - weight = 1. / class_sample_count - samples_weight = np.array([weight[t] for t in target_y]) - samples_weight = torch.from_numpy(samples_weight) - samples_weight = samples_weight.double() - self.sampler = WeightedRandomSampler(samples_weight, len(samples_weight)) - - def forward(self, x, edge_index=None) -> Any: - if self.architecture == "MLP": - return self.model(x) - elif self.architecture == "STACls": - _, output = self.model(x, edge_index) - return output - - def training_step(self, batch, batch_idx): - batch = Batch(**batch) - batch = batch.to(self.device) - opt = self.optimizers() - opt.zero_grad() - output = self.model(batch.x) - loss = F.cross_entropy(output["score"], batch.y) - self.manual_backward(loss) - opt.step() - _, correct = _compute_correct(output["score"], batch.y) - self.log(f"Training {self.architecture} classifier|Cross entropy", loss.item(), prog_bar=True) - return {"loss": loss, "correct": correct} - - def training_epoch_end(self, outputs): - time = self.timer.toc('train') - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/train_time', time, self.current_epoch) - all_loss = torch.stack([x["loss"] for x in outputs]) - all_correct = np.sum([x["correct"] for x in outputs]) - train_acc = all_correct / len(self.train_dataset) - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/loss', - torch.mean(all_loss), self.current_epoch) - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/train_acc', - train_acc, self.current_epoch) - - def validation_step(self, batch, batch_idx): - batch = Batch(**batch) - batch = batch.to(self.device) - with torch.no_grad(): - output = self.model(batch.x) - loss = F.cross_entropy(output["score"], batch.y) - pred_labels, correct = _compute_correct(output["score"], batch.y) - return {"loss": loss, "correct": correct, "pred_labels": pred_labels, "true_labels": batch.y} - - def validation_epoch_end(self, outputs): - all_loss = torch.stack([x["loss"] for x in outputs]) - all_correct = np.sum([x["correct"] for x in outputs]) - pred_labels = torch.cat([x["pred_labels"] for x in outputs]).cpu().detach().numpy() - true_labels = torch.cat([x["true_labels"] for x in outputs]).cpu().detach().numpy() - confusion = confusion_matrix(true_labels, pred_labels) - val_acc = all_correct / len(self.val_dataset) - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/val_loss', - torch.mean(all_loss), self.current_epoch) - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/val_acc', - val_acc, self.current_epoch) - print("\n validation ACC={:.4f}".format(val_acc)) - self.confusion = confusion - - def train_dataloader(self): - loader = DataLoader(self.train_dataset, batch_size=self.batch_size, sampler=self.sampler) - return loader - - def val_dataloader(self): - loader = DataLoader(self.val_dataset, batch_size=self.batch_size, shuffle=self.shuffle, drop_last=False) - return loader - - def test_dataloader(self): - raise NotImplementedError - - def predict_dataloader(self): - raise NotImplementedError \ No newline at end of file diff --git a/stamarker/stamarker/stamarker/.ipynb_checkpoints/modules-checkpoint.py b/stamarker/stamarker/stamarker/.ipynb_checkpoints/modules-checkpoint.py deleted file mode 100644 index 08d9d81..0000000 --- a/stamarker/stamarker/stamarker/.ipynb_checkpoints/modules-checkpoint.py +++ /dev/null @@ -1,276 +0,0 @@ -import abc -import copy -from torch.autograd import Variable -import torch -from torch import Tensor -import torch.nn.functional as F -from torch.nn import Parameter -import torch.nn as nn -from torch_sparse import SparseTensor, set_diag -from torch_geometric.nn.conv import MessagePassing -from torch_geometric.utils import remove_self_loops, add_self_loops, softmax -from typing import Union, Tuple, Optional -from torch_geometric.typing import (OptPairTensor, Adj, Size, NoneType, - OptTensor) - - -class GATConv(MessagePassing): - r"""The graph attentional operator from the `"Graph Attention Networks" - `_ paper - .. math:: - \mathbf{x}^{\prime}_i = \alpha_{i,i}\mathbf{\Theta}\mathbf{x}_{i} + - \sum_{j \in \mathcal{N}(i)} \alpha_{i,j}\mathbf{\Theta}\mathbf{x}_{j}, - where the attention coefficients :math:`\alpha_{i,j}` are computed as - .. math:: - \alpha_{i,j} = - \frac{ - \exp\left(\mathrm{LeakyReLU}\left(\mathbf{a}^{\top} - [\mathbf{\Theta}\mathbf{x}_i \, \Vert \, \mathbf{\Theta}\mathbf{x}_j] - \right)\right)} - {\sum_{k \in \mathcal{N}(i) \cup \{ i \}} - \exp\left(\mathrm{LeakyReLU}\left(\mathbf{a}^{\top} - [\mathbf{\Theta}\mathbf{x}_i \, \Vert \, \mathbf{\Theta}\mathbf{x}_k] - \right)\right)}. - Args: - in_channels (int or tuple): Size of each input sample, or :obj:`-1` to - derive the size from the first input(s) to the forward method. - A tuple corresponds to the sizes of source and target - dimensionalities. - out_channels (int): Size of each output sample. - heads (int, optional): Number of multi-head-attentions. - (default: :obj:`1`) - concat (bool, optional): If set to :obj:`False`, the multi-head - attentions are averaged instead of concatenated. - (default: :obj:`True`) - negative_slope (float, optional): LeakyReLU angle of the negative - slope. (default: :obj:`0.2`) - dropout (float, optional): Dropout probability of the normalized - attention coefficients which exposes each node to a stochastically - sampled neighborhood during training. (default: :obj:`0`) - add_self_loops (bool, optional): If set to :obj:`False`, will not add - self-loops to the input graph. (default: :obj:`True`) - bias (bool, optional): If set to :obj:`False`, the layer will not learn - an additive bias. (default: :obj:`True`) - **kwargs (optional): Additional arguments of - :class:`torch_geometric.nn.conv.MessagePassing`. - """ - _alpha: OptTensor - - def __init__(self, in_channels: Union[int, Tuple[int, int]], - out_channels: int, heads: int = 1, concat: bool = True, - negative_slope: float = 0.2, dropout: float = 0.0, - add_self_loops: bool = True, bias: bool = True, **kwargs): - kwargs.setdefault('aggr', 'add') - super(GATConv, self).__init__(node_dim=0, **kwargs) - - self.in_channels = in_channels - self.out_channels = out_channels - self.heads = heads - self.concat = concat - self.negative_slope = negative_slope - self.dropout = dropout - self.add_self_loops = add_self_loops - self.lin_src = nn.Parameter(torch.zeros(size=(in_channels, out_channels))) - nn.init.xavier_normal_(self.lin_src.data, gain=1.414) - self.lin_dst = self.lin_src - # The learnable parameters to compute attention coefficients: - self.att_src = Parameter(torch.Tensor(1, heads, out_channels)) - self.att_dst = Parameter(torch.Tensor(1, heads, out_channels)) - nn.init.xavier_normal_(self.att_src.data, gain=1.414) - nn.init.xavier_normal_(self.att_dst.data, gain=1.414) - self._alpha = None - self.attentions = None - - def forward(self, x: Union[Tensor, OptPairTensor], edge_index: Adj, - size: Size = None, return_attention_weights=None, attention=True, tied_attention=None): - # type: (Union[Tensor, OptPairTensor], Tensor, Size, NoneType) -> Tensor # noqa - # type: (Union[Tensor, OptPairTensor], SparseTensor, Size, NoneType) -> Tensor # noqa - # type: (Union[Tensor, OptPairTensor], Tensor, Size, bool) -> Tuple[Tensor, Tuple[Tensor, Tensor]] # noqa - # type: (Union[Tensor, OptPairTensor], SparseTensor, Size, bool) -> Tuple[Tensor, SparseTensor] # noqa - r""" - Args: - return_attention_weights (bool, optional): If set to :obj:`True`, - will additionally return the tuple - :obj:`(edge_index, attention_weights)`, holding the computed - attention weights for each edge. (default: :obj:`None`) - """ - H, C = self.heads, self.out_channels - - # We first transform the input node features. If a tuple is passed, we - # transform source and target node features via separate weights: - if isinstance(x, Tensor): - assert x.dim() == 2, "Static graphs not supported in 'GATConv'" - # x_src = x_dst = self.lin_src(x).view(-1, H, C) - x_src = x_dst = torch.mm(x, self.lin_src).view(-1, H, C) - else: # Tuple of source and target node features: - x_src, x_dst = x - assert x_src.dim() == 2, "Static graphs not supported in 'GATConv'" - x_src = self.lin_src(x_src).view(-1, H, C) - if x_dst is not None: - x_dst = self.lin_dst(x_dst).view(-1, H, C) - - x = (x_src, x_dst) - - if not attention: - return x[0].mean(dim=1) - # return x[0].view(-1, self.heads * self.out_channels) - - if tied_attention == None: - # Next, we compute node-level attention coefficients, both for source - # and target nodes (if present): - alpha_src = (x_src * self.att_src).sum(dim=-1) - alpha_dst = None if x_dst is None else (x_dst * self.att_dst).sum(-1) - alpha = (alpha_src, alpha_dst) - self.attentions = alpha - else: - alpha = tied_attention - - if self.add_self_loops: - if isinstance(edge_index, Tensor): - # We only want to add self-loops for nodes that appear both as - # source and target nodes: - num_nodes = x_src.size(0) - if x_dst is not None: - num_nodes = min(num_nodes, x_dst.size(0)) - num_nodes = min(size) if size is not None else num_nodes - edge_index, _ = remove_self_loops(edge_index) - edge_index, _ = add_self_loops(edge_index, num_nodes=num_nodes) - elif isinstance(edge_index, SparseTensor): - edge_index = set_diag(edge_index) - - # propagate_type: (x: OptPairTensor, alpha: OptPairTensor) - out = self.propagate(edge_index, x=x, alpha=alpha, size=size) - - alpha = self._alpha - assert alpha is not None - self._alpha = None - - if self.concat: - out = out.view(-1, self.heads * self.out_channels) - else: - out = out.mean(dim=1) - - # if self.bias is not None: - # out += self.bias - - if isinstance(return_attention_weights, bool): - if isinstance(edge_index, Tensor): - return out, (edge_index, alpha) - elif isinstance(edge_index, SparseTensor): - return out, edge_index.set_value(alpha, layout='coo') - else: - return out - - def message(self, x_j: Tensor, alpha_j: Tensor, alpha_i: OptTensor, - index: Tensor, ptr: OptTensor, - size_i: Optional[int]) -> Tensor: - # Given egel-level attention coefficients for source and target nodes, - # we simply need to sum them up to "emulate" concatenation: - alpha = alpha_j if alpha_i is None else alpha_j + alpha_i - - alpha = F.leaky_relu(alpha, self.negative_slope) - alpha = softmax(alpha, index, ptr, size_i) - self._alpha = alpha # Save for later use. - alpha = F.dropout(alpha, p=self.dropout, training=self.training) - return x_j * alpha.unsqueeze(-1) - - def __repr__(self): - return '{}({}, {}, heads={})'.format(self.__class__.__name__, - self.in_channels, - self.out_channels, self.heads) - - -class STAGATEModule(nn.Module): - def __init__(self, in_features, hidden_dims): - super(STAGATEModule, self).__init__() - [num_hidden, out_dim] = hidden_dims - self.conv1 = GATConv(in_features, num_hidden, heads=1, concat=False, - dropout=0, add_self_loops=False, bias=False) - self.conv2 = GATConv(num_hidden, out_dim, heads=1, concat=False, - dropout=0, add_self_loops=False, bias=False) - self.conv3 = GATConv(out_dim, num_hidden, heads=1, concat=False, - dropout=0, add_self_loops=False, bias=False) - self.conv4 = GATConv(num_hidden, in_features, heads=1, concat=False, - dropout=0, add_self_loops=False, bias=False) - - def forward(self, features, edge_index): - h1 = F.elu(self.conv1(features, edge_index)) - h2 = self.conv2(h1, edge_index, attention=False) - self.conv3.lin_src.data = self.conv2.lin_src.transpose(0, 1) - self.conv3.lin_dst.data = self.conv2.lin_dst.transpose(0, 1) - self.conv4.lin_src.data = self.conv1.lin_src.transpose(0, 1) - self.conv4.lin_dst.data = self.conv1.lin_dst.transpose(0, 1) - h3 = F.elu(self.conv3(h2, edge_index, attention=True, - tied_attention=self.conv1.attentions)) - h4 = self.conv4(h3, edge_index, attention=False) - - return h2, h4 - - -class StackClsModule(nn.Module, abc.ABC): - def __init__(self, in_features, n_classes): - super(StackClsModule, self).__init__() - self.in_features = in_features - self.n_classes = n_classes - - -class STAGATEClsModule(nn.Module): - def __init__(self, - stagate: STAGATEModule, - stack_classifier: StackClsModule): - super(STAGATEClsModule, self).__init__() - self.stagate = copy.deepcopy(stagate) - self.classifier = copy.deepcopy(stack_classifier) - - def forward(self, x, edge_index, mode="classifier"): - z, x_recon = self.stagate(x, edge_index) - z = torch.clone(z) - if mode == "classifier": - return z, self.classifier(z) - elif mode == "reconstruction": - return z, x_recon - else: - raise NotImplementedError - - def get_saliency_map(self, x, edge_index, target_index="max", save=None): - """ - Get saliency map by backpropagation. - :param x: input tensors - :param edge_index: graph edge index - :param target_index: target index to compute final scores - :param save: - :return: gradients - """ - x_var = Variable(x, requires_grad=True) - _, output = self.forward(x_var, edge_index, mode="classifier") - scores = output["last_layer"] - if target_index == "max": - target_score_indices = Variable(torch.argmax(scores, 1)) - elif isinstance(target_index, int): - target_score_indices = Variable(torch.ones(scores.shape[0], dtype=torch.int64) * target_index) - else: - raise NotImplementedError - target_scores = scores.gather(1, target_score_indices.view(-1, 1)).squeeze() - loss = torch.sum(target_scores) - loss.backward() - gradients = x_var.grad.data - if save is not None: - torch.save(gradients, save) - return gradients, scores - - -class StackMLPModule(StackClsModule): - name = "StackMLP" - - def __init__(self, in_features, n_classes, hidden_dims=[30, 40, 30]): - super(StackMLPModule, self).__init__(in_features, n_classes) - self.classifier = nn.ModuleList() - mlp_dims = [in_features] + hidden_dims + [n_classes] - for ind in range(len(mlp_dims) - 1): - self.classifier.append(nn.Linear(mlp_dims[ind], mlp_dims[ind + 1])) - - def forward(self, x): - for layer in self.classifier: - x = layer(x) - score = F.softmax(x, dim=0) - return {"last_layer": x, "score": score} diff --git a/stamarker/stamarker/stamarker/.ipynb_checkpoints/pipeline-checkpoint.py b/stamarker/stamarker/stamarker/.ipynb_checkpoints/pipeline-checkpoint.py deleted file mode 100644 index 13c4188..0000000 --- a/stamarker/stamarker/stamarker/.ipynb_checkpoints/pipeline-checkpoint.py +++ /dev/null @@ -1,246 +0,0 @@ -import pytorch_lightning as pl -import copy -import torch -import os -import shutil -import logging -import glob -import sys -import numpy as np -import scipy -import scanpy as sc -from pytorch_lightning.loggers import TensorBoardLogger -from scipy.cluster import hierarchy -from .models import intSTAGATE, StackClassifier -from .utils import plot_consensus_map, consensus_matrix, Timer -from .dataset import SpatialDataModule -from .modules import STAGATEClsModule -import logging - - -FORMAT = "%(asctime)s %(levelname)s %(message)s" -logging.basicConfig(format=FORMAT, datefmt='%Y-%m-%d %H:%M:%S') -def make_spatial_data(ann_data): - """ - Make SpatialDataModule object from Scanpy annData object - """ - data_module = SpatialDataModule() - ann_data.X = ann_data.X.toarray() - data_module.ann_data = ann_data - return data_module - - -class STAMarker: - def __init__(self, n, save_dir, config, logging_level=logging.INFO): - """ - n: int, number of graph attention auto-econders to train - save_dir: directory to save the models - config: config file for training - """ - self.n = n - self.save_dir = save_dir - if not os.path.exists(save_dir): - os.mkdir(save_dir) - logging.info("Create save directory {}".format(save_dir)) - self.version_dirs = [os.path.join(save_dir, f"version_{i}") for i in range(n)] - self.config = config - self.logger = logging.getLogger("STAMarker") - self.logger.setLevel(logging_level) - self.consensus_labels = None - - def train_auto_encoders(self, data_module): - for seed in range(self.n): - self._train_auto_encoder(data_module, seed, self.config) - self.logger.info("Finished training {} auto-encoders".format(self.n)) - - def clustering(self, data_module, cluster_method, cluster_params): - """ - Cluster the latent space of the trained auto-encoders - Cluster method should be "louvain" or "mclust" - """ - for version_dir in self.version_dirs: - self._clustering(data_module, version_dir, cluster_method, cluster_params) - self.logger.info("Finished {} clustering with {}".format(self.n, cluster_method)) - - def consensus_clustering(self, n_clusters, name="cluster_labels.npy"): - sys.setrecursionlimit(100000) - label_files = glob.glob(self.save_dir + f"/version_*/{name}") - labels_list = list(map(lambda file: np.load(file), label_files)) - cons_mat = consensus_matrix(labels_list) - row_linkage, _, figure = plot_consensus_map(cons_mat, return_linkage=True) - figure.savefig(os.path.join(self.save_dir, "consensus_clustering.png"), dpi=300) - consensus_labels = hierarchy.cut_tree(row_linkage, n_clusters).squeeze() - np.save(os.path.join(self.save_dir, "consensus"), consensus_labels) - self.consensus_labels = consensus_labels - self.logger.info("Save consensus labels to {}".format(os.path.join(self.save_dir, "consensus.npz"))) - - def train_classifiers(self, data_module, n_clusters, name="cluster_labels.npy"): - for version_dir in self.version_dirs: - # _train_classifier(self, data_module, version_dir, target_y, n_classes, seed=None) - self._train_classifier(data_module, version_dir, self.consensus_labels, n_clusters, self.config) - self.logger.info("Finished training {} classifiers".format(self.n)) - - def compute_smaps(self, data_module, return_recon=True, normalize=True): - smaps = [] - if return_recon: - recons = [] - for version_dir in self.version_dirs: - if return_recon: - smap, recon = self._compute_smap(data_module, version_dir, return_recon=return_recon) - smaps.append(smap) - recons.append(recon) - else: - smap = self._compute_smap(data_module, version_dir, return_recon=return_recon) - smaps.append(smap) - if return_recon: - return smaps, recons - else: - return smaps - self.logger.info("Finished computing {} smaps".format(self.n)) - - - def _compute_smap_zscore(self, smap, labels, logtransform=False): - scores = np.log(smap + 1) if logtransform else copy.copy(smap) - unique_labels = np.unique(labels) - for l in unique_labels: - scores[labels == l, :] = scipy.stats.zscore(scores[labels == l, :], axis=1) - return scores - - - def _clustering(self, data_module, version_dir, cluster_method, cluster_params): - """ - Cluster the latent space of the trained auto-encoder - """ - if cluster_method == "louvain": - run_louvain(data_module, version_dir, cluster_params) - elif cluster_method == "mclust": - run_mclust(data_module, version_dir, cluster_params) - else: - raise ValueError("Unknown clustering method") - - def _train_auto_encoder(self, data_module, seed, config): - """ - Train a single graph attention auto-encoder - """ - pl.seed_everything(seed) - version = f"version_{seed}" - version_dir = os.path.join(self.save_dir, version) - if os.path.exists(version_dir): - shutil.rmtree(version_dir) - os.makedirs(version_dir, exist_ok=True) - logger = TensorBoardLogger(save_dir=self.save_dir, name=None, - default_hp_metric=False, - version=seed) - model = intSTAGATE(**config["stagate"]["params"]) - model.set_optimizer_params(config["stagate"]["optimizer"], config["stagate"]["scheduler"]) - trainer = pl.Trainer(logger=logger, **config["stagate_trainer"]) - timer = Timer() - timer.tic("fit") - trainer.fit(model, data_module) - fit_time = timer.toc("fit") - with open(os.path.join(version_dir, "runtime.csv"), "w+") as f: - f.write("{}, fit_time, {:.2f}, ".format(seed, fit_time / 60)) - trainer.save_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - del model, trainer - if config["stagate_trainer"]["gpus"] > 0: - torch.cuda.empty_cache() - logging.info(f"Finshed running version {seed}") - - def _train_classifier(self, data_module, version_dir, target_y, n_classes, config, seed=None): - timer = Timer() - pl.seed_everything(seed) - rep_dim = config["stagate"]["params"]["hidden_dims"][-1] - stagate = intSTAGATE.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - classifier = StackClassifier(rep_dim, n_classes=n_classes, architecture="MLP") - classifier.prepare(stagate, data_module.train_dataset, target_y, - balanced=config["mlp"]["balanced"], test_prop=config["mlp"]["test_prop"]) - classifier.set_optimizer_params(config["mlp"]["optimizer"], config["mlp"]["scheduler"]) - logger = TensorBoardLogger(save_dir=self.save_dir, name=None, - default_hp_metric=False, - version=seed) - trainer = pl.Trainer(logger=logger, **config["classifier_trainer"]) - timer.tic("clf") - trainer.fit(classifier) - clf_time = timer.toc("clf") - with open(os.path.join(version_dir, "runtime.csv"), "a+") as f: - f.write("\n") - f.write("{}, clf_time, {:.2f}, ".format(seed, clf_time / 60)) - trainer.save_checkpoint(os.path.join(version_dir, "checkpoints", "mlp.ckpt")) - target_y = classifier.dataset.target_y.numpy() - all_props = class_proportions(target_y) - val_props = class_proportions(target_y[classifier.val_dataset.indices]) - if self.logger.level == logging.DEBUG: - print("All class proportions " + "|".join(["{:.2f}%".format(prop * 100) for prop in all_props])) - print("Val class proportions " + "|".join(["{:.2f}%".format(prop * 100) for prop in val_props])) - np.save(os.path.join(version_dir, "confusion.npy"), classifier.confusion) - - def _compute_smap(self, data_module, version_dir, return_recon=True): - """ - Compute the saliency map of the trained auto-encoder - """ - stagate = intSTAGATE.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - cls = StackClassifier.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "mlp.ckpt")) - stagate_cls = STAGATEClsModule(stagate.model, cls.model) - smap, _ = stagate_cls.get_saliency_map(data_module.train_dataset.x, - data_module.train_dataset.edge_index) - smap = smap.detach().cpu().numpy() - if return_recon: - recon = stagate(data_module.train_dataset.x, data_module.train_dataset.edge_index)[1].cpu().detach().numpy() - return smap, recon - else: - return smap - - -def run_louvain(data_module, version_dir, resolution, name="cluster_labels"): - """ - Run louvain clustering on the data_module - """ - stagate = intSTAGATE.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - embedding = stagate(data_module.train_dataset.x, data_module.train_dataset.edge_index)[0].cpu().detach().numpy() - ann_data = copy.copy(data_module.ann_data) - ann_data.obsm["stagate"] = embedding - sc.pp.neighbors(ann_data, use_rep='stagate') - sc.tl.louvain(ann_data, resolution=resolution) - save_path = os.path.join(version_dir, "{}.npy".format(name)) - np.save(save_path, ann_data.obs["louvain"].to_numpy().astype("int")) - print("Save louvain results to {}".format(save_path)) - - -def mclust_R(representation, n_clusters, r_seed=2022, model_name="EEE"): - """ - Clustering using the mclust algorithm. - The parameters are the same as those in the R package mclust. - """ - np.random.seed(r_seed) - import rpy2.robjects as ro - from rpy2.robjects import numpy2ri - numpy2ri.activate() - ro.r.library("mclust") - r_random_seed = ro.r['set.seed'] - r_random_seed(r_seed) - rmclust = ro.r['Mclust'] - res = rmclust(representation, n_clusters, model_name) - mclust_res = np.array(res[-2]) - numpy2ri.deactivate() - return mclust_res.astype('int') - - -def run_mclust(data_module, version_dir, n_clusters, name="cluster_labels"): - stagate = intSTAGATE.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - embedding = stagate(data_module.train_dataset.x, data_module.train_dataset.edge_index)[0].cpu().detach().numpy() - labels = mclust_R(embedding, n_clusters) - save_path = os.path.join(version_dir, "{}.npy".format(name)) - np.save(save_path, labels.astype("int")) - print("Save MClust results to {}".format(save_path)) - -def class_proportions(target): - n_classes = len(np.unique(target)) - props = np.array([np.sum(target == i) for i in range(n_classes)]) - return props / np.sum(props) - - - - - - - diff --git a/stamarker/stamarker/stamarker/__init__.py b/stamarker/stamarker/stamarker/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stamarker/stamarker/stamarker/__pycache__/__init__.cpython-38.pyc b/stamarker/stamarker/stamarker/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 275509ff24cfa05c04242f1222a5501ba22f4fb7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 115 zcmWIL<>g`k0_Rs+$sqbMh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2BkPcNppBr!L! kC_A+%CO$qhFS8^*Uaz3?7Kcr4eoARhsvStjXCP((0LNt(4FCWD diff --git a/stamarker/stamarker/stamarker/__pycache__/__init__.cpython-39.pyc b/stamarker/stamarker/stamarker/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index ab2035076574feca8bd27cda72dc131d5f781742..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 185 zcmYe~<>g`k0_Rs+$sqbMh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10QKeRZts8~Na zqbegYFJ0fSw4}HszbI9~Ei)&zSU)&K*CWi`Q$IOBB~`yDH77N(I29=9=$lxSom!+{ wQVLX?nV6%mr>9?Bl9&q-jgQaF%PfhH*DI*J#bJ}1pHiBWY6r6SGY~TX0ND*MQ~&?~ diff --git a/stamarker/stamarker/stamarker/__pycache__/dataset.cpython-38.pyc b/stamarker/stamarker/stamarker/__pycache__/dataset.cpython-38.pyc deleted file mode 100644 index e38dc8d2c12820e1daf12e69bb20f109a91edff6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4250 zcmaJ^TW=gm6|U;rTs$)#-{VzSCDF1li(~=IbqT><93zk!q)kAm2{fIl@_5EQ-IMB? zY;28R)l z_hZpnYclpvYFxfNG&b?%{{Rt8@PrL`$T{&&;)Je!d!cXNK^WL~DJD&HOu=+eb5LSmJgEoK{ITEFNt!pFjx#12TS47U^!go>=#T_MD;lnRk?EFhO21L zi5l89Sw(vd?Yd~7-H_$}4fL!F=K*WYzlN-=#f`Vy%XJ$}X*ETp29wBR+TZCta|$p*ubmQg;8buUh$RBE%3>saSk zyQcG`=*lR;w>u=r<;y{16Hopwh-4ubEOeyXhXFx@umoBTD}oE>ISZ@8g*u+`Ka6!} zze9_smk)+~^K(4;Hi%{??ARHzQ+~#_*&p9~u4hjyz)%*bg^#sl8uRo--} z{HSb|-OyRPHD}6El*R)YMWz}>gG`JP;%ia#WE3aGh^k`)<96YbxhTSmM8zaD;BnI6 zym2uiWO6ZrMln(-NP=7G3$jX0QkuS#mqE%97q?@3uesR8j7^$)*~OwP9N|9ah!;=z zaIGf-QNr67WcP}xek6yJL)z|;W2YDKw54_Qdtfvh^Ais0obUrDU)KB+KIUTFg0-SUQ|qd1l!{2Jk=}243OK;j zJ4C)tg!GuP2l=I!_H-0|4TIJw%v-i*Ia<2VWMew3LyW-J z>YTA-7_pBS5x};;ID5`IiRF#?I;1^DG@%8QJE*7~;;8@FO9LJBOUE{rDAHcK$Nt#2 zk<@an28JJ63%1I}*O}_+{9Bvi^u+3t(09wy&XJPF7nDIo_rr<3W#D=y3&)r^wyC@ zg#WUL79ZJV4gCN~^j6VJ*}zAxu~`7HIWL+7n{rMpyyRk0EIs$a+N8BCR?w6fR6oQ_Ge?4coDt~eraHvzfgEJ&(3In}SUEXbcH`m`v{3zo z$Xy~Aezh(ouahJ>fi?>)02VML6bYlMVi9#l zI@{YbjeI}*m@;c*6RY|W=3lXF2_1yKHh%vNMguhZfV{p->GFlciyqh#QS1qqdd5Y> z-m`OIUyS&|vvUJs+x^nXbY2Rk{8p2n&dKTc#9os9kUvrXD`k}UGkpvBb| z`W3;rBB!)mL>yH-y;R6&1(s2wYONV3?-*w|G|Qbh>5PzNW%Q(jv#3?CldGR%lBpHK zO-{tLWtB1h5JIC2$@k--G%GXmO_sQj*UYDx8XyjiB#xJk6XPdY_d7#H2S!zhP@+*3 zo$6a4rn;M@-G>&1)dqF=u~PA&ncGRSSQi6^|6ctVW35t=gf_t!x_?OAHj(=vrgC2J z&4i7q4V4_mO4{(;!QA`~2y+(sA`p0yH=JdDlis*JUPb+L6L{?W@96F8@pO5sD+!hG zhU!5Z85VO`pQ$^V@|h!emut#hPTxDBIB}18ilTvf@K6i{Mei61_?6o(eCk*rvB&`R z34RoP!rJ2^Jzq?E)C~0t~t38 z+8|;N%Ie*((B9KNl}so%ND3e!+3btdGfVAE|B|xqK&Dy>TRSPL9cJ}w0EDfN3+*V) zO|*kwN1yr` z$d0A$R%50lCX1pNVPO&W(}oCHV&BedS_Fc>CIHJEf<4GT*>Q zWo|VLSBpKnGAodGV-lkj(BbmzpxvK<()jj+vlvRhLH5tb7Ifj#?o`z6^HR!c{zc|A e<-5gC+&wx`ITe*P;9zmrfP8c>yUqFL`u_o2|LJ=G diff --git a/stamarker/stamarker/stamarker/__pycache__/dataset.cpython-39.pyc b/stamarker/stamarker/stamarker/__pycache__/dataset.cpython-39.pyc deleted file mode 100644 index 4b46d88ba7b731dc15e945f015df3260bf93e3a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4302 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 diff --git a/stamarker/stamarker/stamarker/__pycache__/models.cpython-38.pyc b/stamarker/stamarker/stamarker/__pycache__/models.cpython-38.pyc deleted file mode 100644 index dd9d50b8882770d75e12a8fcc35d638745edac71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11060 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 diff --git a/stamarker/stamarker/stamarker/__pycache__/models.cpython-39.pyc b/stamarker/stamarker/stamarker/__pycache__/models.cpython-39.pyc deleted file mode 100644 index 14b5f9cd1a016e8416390f497e2b4278fdba308a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11156 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 diff --git a/stamarker/stamarker/stamarker/__pycache__/modules.cpython-38.pyc b/stamarker/stamarker/stamarker/__pycache__/modules.cpython-38.pyc deleted file mode 100644 index 39452b5c406c9d5a25d698e1d79f3ebe0dcee0db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9861 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 Q5qNkr-`381vqXaa59a6&K>z>% diff --git a/stamarker/stamarker/stamarker/__pycache__/pipeline.cpython-39.pyc b/stamarker/stamarker/stamarker/__pycache__/pipeline.cpython-39.pyc deleted file mode 100644 index 9693383b06f2eac4cc11e8f053293b1a7964207f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10275 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 diff --git a/stamarker/stamarker/stamarker/__pycache__/utils.cpython-38.pyc b/stamarker/stamarker/stamarker/__pycache__/utils.cpython-38.pyc deleted file mode 100644 index c1e4cfd8b716ceb71c8ed73c5da4531c5168e732..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6380 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 diff --git a/stamarker/stamarker/stamarker/__pycache__/utils.cpython-39.pyc b/stamarker/stamarker/stamarker/__pycache__/utils.cpython-39.pyc deleted file mode 100644 index d01882995553889fd89f72665d2af5ae5a2cb40a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6470 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 diff --git a/stamarker/stamarker/stamarker/dataset.py b/stamarker/stamarker/stamarker/dataset.py deleted file mode 100644 index 14398d6..0000000 --- a/stamarker/stamarker/stamarker/dataset.py +++ /dev/null @@ -1,111 +0,0 @@ -from typing import List -import scanpy as sc -import numpy as np -import torch -import pytorch_lightning as pl -from torch.utils.data import Dataset -from pytorch_lightning.utilities.types import EVAL_DATALOADERS -from torch_geometric.loader import NeighborLoader -from torch_geometric.data import Data -from .utils import compute_spatial_net, stats_spatial_net, compute_edge_list - - -class Batch(dict): - __getattr__ = dict.get - __setattr__ = dict.__setitem__ - __delattr__ = dict.__delitem__ - - def to(self, device): - res = dict() - for key, value in self.items(): - if hasattr(value, "to"): - res[key] = value.to(device) - else: - res[key] = value - return Batch(**res) - - -class RepDataset(Dataset): - def __init__(self, - x, - target_y, - ground_truth=None): - assert (len(x) == len(target_y)) - self.x = x - self.target_y = target_y - self.ground_truth = ground_truth - - def __len__(self): - return len(self.x) - - def __getitem__(self, idx): - if torch.is_tensor(idx): - idx = idx.tolist() - sample_x, sample_y = self.x[idx, :], self.target_y[idx] - if self.ground_truth is not None: - sample_gt = self.ground_truth[idx] - else: - sample_gt = np.nan - sample = {"x": sample_x, "y": sample_y, "ground_truth": sample_gt} - return sample - - -class SpatialDataModule(pl.LightningDataModule): - def __init__(self, - full_batch: bool = True, - batch_size: int = 1000, - num_neighbors: List[int] = None, - num_workers=None, - pin_memory=False) -> None: - self.batch_size = batch_size - self.full_batch = full_batch - self.has_y = False - self.train_dataset = None - self.valid_dataset = None - self.num_neighbors = num_neighbors - self.num_workers = num_workers - self.pin_memory = pin_memory - self.ann_data = None - - def prepare_data(self, n_top_genes: int = 3000, rad_cutoff: float = 50, - show_net_stats: bool = False, min_cells=50, min_counts=None) -> None: - sc.pp.calculate_qc_metrics(self.ann_data, inplace=True) - sc.pp.filter_genes(self.ann_data, min_cells=min_cells) - if min_counts is not None: - sc.pp.filter_cells(self.ann_data, min_counts=min_counts) - print("After filtering: ", self.ann_data.shape) - # Normalization - sc.pp.highly_variable_genes(self.ann_data, flavor="seurat_v3", n_top_genes=n_top_genes) - self.ann_data = self.ann_data[:, self.ann_data.var['highly_variable']] - sc.pp.normalize_total(self.ann_data, target_sum=1e4) - sc.pp.log1p(self.ann_data) - compute_spatial_net(self.ann_data, rad_cutoff=rad_cutoff) - if show_net_stats: - stats_spatial_net(self.ann_data) - # ---------------------------- generate data --------------------- - edge_list = compute_edge_list(self.ann_data) - self.train_dataset = Data(edge_index=torch.LongTensor(np.array([edge_list[0], edge_list[1]])), - x=torch.FloatTensor(self.ann_data.X), - y=None) - - def train_dataloader(self): - if self.full_batch: - loader = NeighborLoader(self.train_dataset, num_neighbors=[1], - batch_size=len(self.train_dataset.x)) - else: - loader = NeighborLoader(self.train_dataset, num_neighbors=self.num_neighbors, batch_size=self.batch_size) - return loader - - def val_dataloader(self): - if self.valid_dataset is None: - loader = NeighborLoader(self.train_dataset, num_neighbors=[1], - batch_size=len(self.train_dataset.x)) - else: - raise NotImplementedError - return loader - - def test_dataloader(self) -> EVAL_DATALOADERS: - raise NotImplementedError - - def predict_dataloader(self) -> EVAL_DATALOADERS: - raise NotImplementedError diff --git a/stamarker/stamarker/stamarker/models.py b/stamarker/stamarker/stamarker/models.py deleted file mode 100644 index d7457ec..0000000 --- a/stamarker/stamarker/stamarker/models.py +++ /dev/null @@ -1,257 +0,0 @@ -from abc import ABC -from typing import Any, List -import numpy as np -import pytorch_lightning as pl -import torch -import torch.nn.functional as F -from torch.utils.data import DataLoader, WeightedRandomSampler -from torch_geometric.data import Data -from sklearn.metrics import adjusted_rand_score, confusion_matrix -from .modules import STAGATEModule, StackMLPModule -from .dataset import RepDataset, Batch -from .utils import Timer - -def get_optimizer(name): - if name == "ADAM": - return torch.optim.Adam - elif name == "ADAGRAD": - return torch.optim.Adagrad - elif name == "ADADELTA": - return torch.optim.Adadelta - elif name == "RMS": - return torch.optim.RMSprop - elif name == "ASGD": - return torch.optim.ASGD - else: - raise NotImplementedError - - -def get_scheduler(name): - if name == "STEP_LR": - return torch.optim.lr_scheduler.StepLR - elif name == "EXP_LR": - return torch.optim.lr_scheduler.ExponentialLR - else: - raise NotImplementedError - -class BaseModule(pl.LightningModule, ABC): - def __init__(self): - super(BaseModule, self).__init__() - self.optimizer_params = None - self.scheduler_params = None - self.model = None - self.timer = Timer() - self.automatic_optimization = False - - def set_optimizer_params(self, - optimizer_params: dict, - scheduler_params: dict): - self.optimizer_params = optimizer_params - self.scheduler_params = scheduler_params - - def configure_optimizers(self): - optimizer = get_optimizer(self.optimizer_params["name"])( - self.model.parameters(), - **self.optimizer_params["params"]) - scheduler = get_scheduler(self.scheduler_params["name"])(optimizer, **self.scheduler_params["params"]) - return [optimizer], [scheduler] - - def on_train_epoch_start(self) -> None: - self.timer.tic('train') - - -class intSTAGATE(BaseModule): - """ - intSTAGATE Lightning Module - """ - def __init__(self, - in_features: int = None, - hidden_dims: List[int] = None, - gradient_clipping: float = 5.0, - **kwargs): - super(intSTAGATE, self).__init__() - self.model = STAGATEModule(in_features, hidden_dims) - self.auto_encoder_epochs = None - self.gradient_clipping = gradient_clipping - self.pred_labels = None - self.save_hyperparameters() - - def configure_optimizers(self) -> (dict, dict): - auto_encoder_optimizer = get_optimizer(self.optimizer_params["name"])( - list(self.model.parameters()), - **self.optimizer_params["params"]) - auto_encoder_scheduler = get_scheduler(self.scheduler_params["name"])(auto_encoder_optimizer, - **self.scheduler_params["params"]) - return [auto_encoder_optimizer], [auto_encoder_scheduler] - - def forward(self, x, edge_index) -> Any: - return self.model(x, edge_index) - - def training_step(self, batch, batch_idx): - batch = batch.to(self.device) - opt_auto_encoder = self.optimizers() - z, x_hat = self.model(batch.x, batch.edge_index) - loss = F.mse_loss(batch.x, x_hat) - opt_auto_encoder.zero_grad() - self.manual_backward(loss) - torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.gradient_clipping) - opt_auto_encoder.step() - self.log("Training auto-encoder|Reconstruction errors", loss.item(), prog_bar=True) - self.logger.experiment.add_scalar('auto_encoder/loss', loss.item(), self.current_epoch) - - def on_train_epoch_end(self) -> None: - time = self.timer.toc('train') - sch_auto_encoder = self.lr_schedulers() - sch_auto_encoder.step() - self.logger.experiment.add_scalar('train_time', time, self.current_epoch) - - def validation_step(self, batch, batch_idx): - pass - - def validation_epoch_end(self, outputs): - pass - - -def _compute_correct(scores, target_y): - _, pred_labels = torch.max(scores, axis=1) - correct = (pred_labels == target_y).sum().item() - return pred_labels, correct - - -class CoordTransformer(object): - def __init__(self, coord): - self.coord = coord - - def transform(self): - factor = np.max(np.max(self.coord, axis=0) - np.min(self.coord, axis=0)) - return (self.coord - np.min(self.coord, axis=0)) / factor - - -class StackClassifier(BaseModule): - def __init__(self, in_features: int, - n_classes: int = 7, - batch_size: int = 1000, - shuffle: bool = False, - hidden_dims: List[int] = [30], - architecture: str = "MLP", - sta_path: str = None, - **kwargs): - super(StackClassifier, self).__init__() - self.in_features = in_features - self.architecture = architecture - self.batch_size = batch_size - self.shuffle = shuffle - if architecture == "MLP": - self.model = StackMLPModule(in_features, n_classes, hidden_dims, **kwargs) - else: - raise NotImplementedError - self.dataset = None - self.train_dataset = None - self.val_dataset = None - self.automatic_optimization = False - self.sampler = None - self.test_prop = None - self.confusion = None - self.balanced = None - self.save_hyperparameters() - - def prepare(self, - stagate: intSTAGATE, - dataset: Data, - target_y, - test_prop: float = 0.5, - balanced: bool = True): - self.balanced = balanced - self.test_prop = test_prop - with torch.no_grad(): - representation, _ = stagate(dataset.x, dataset.edge_index) - if hasattr(dataset, "ground_truth"): - ground_truth = dataset.ground_truth - else: - ground_truth = None - if isinstance(target_y, np.ndarray): - target_y = torch.from_numpy(target_y).type(torch.LongTensor) - elif isinstance(target_y, torch.Tensor): - target_y = target_y.type(torch.LongTensor) - else: - raise TypeError("target_y must be either a torch tensor or a numpy ndarray.") - self.dataset = RepDataset(representation, target_y, ground_truth=ground_truth) - n_val = int(len(self.dataset) * test_prop) - self.train_dataset, self.val_dataset = torch.utils.data.random_split( - self.dataset, [len(self.dataset) - n_val, n_val]) - if balanced: - target_y = target_y[self.train_dataset.indices] - class_sample_count = np.array([len(np.where(target_y == t)[0]) for t in np.unique(target_y)]) - weight = 1. / class_sample_count - samples_weight = np.array([weight[t] for t in target_y]) - samples_weight = torch.from_numpy(samples_weight) - samples_weight = samples_weight.double() - self.sampler = WeightedRandomSampler(samples_weight, len(samples_weight)) - - def forward(self, x, edge_index=None) -> Any: - if self.architecture == "MLP": - return self.model(x) - elif self.architecture == "STACls": - _, output = self.model(x, edge_index) - return output - - def training_step(self, batch, batch_idx): - batch = Batch(**batch) - batch = batch.to(self.device) - opt = self.optimizers() - opt.zero_grad() - output = self.model(batch.x) - loss = F.cross_entropy(output["score"], batch.y) - self.manual_backward(loss) - opt.step() - _, correct = _compute_correct(output["score"], batch.y) - self.log(f"Training {self.architecture} classifier|Cross entropy", loss.item(), prog_bar=True) - return {"loss": loss, "correct": correct} - - def training_epoch_end(self, outputs): - time = self.timer.toc('train') - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/train_time', time, self.current_epoch) - all_loss = torch.stack([x["loss"] for x in outputs]) - all_correct = np.sum([x["correct"] for x in outputs]) - train_acc = all_correct / len(self.train_dataset) - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/loss', - torch.mean(all_loss), self.current_epoch) - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/train_acc', - train_acc, self.current_epoch) - - def validation_step(self, batch, batch_idx): - batch = Batch(**batch) - batch = batch.to(self.device) - with torch.no_grad(): - output = self.model(batch.x) - loss = F.cross_entropy(output["score"], batch.y) - pred_labels, correct = _compute_correct(output["score"], batch.y) - return {"loss": loss, "correct": correct, "pred_labels": pred_labels, "true_labels": batch.y} - - def validation_epoch_end(self, outputs): - all_loss = torch.stack([x["loss"] for x in outputs]) - all_correct = np.sum([x["correct"] for x in outputs]) - pred_labels = torch.cat([x["pred_labels"] for x in outputs]).cpu().detach().numpy() - true_labels = torch.cat([x["true_labels"] for x in outputs]).cpu().detach().numpy() - confusion = confusion_matrix(true_labels, pred_labels) - val_acc = all_correct / len(self.val_dataset) - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/val_loss', - torch.mean(all_loss), self.current_epoch) - self.logger.experiment.add_scalar(f'classifier-{self.architecture}/val_acc', - val_acc, self.current_epoch) - print("\n validation ACC={:.4f}".format(val_acc)) - self.confusion = confusion - - def train_dataloader(self): - loader = DataLoader(self.train_dataset, batch_size=self.batch_size, sampler=self.sampler) - return loader - - def val_dataloader(self): - loader = DataLoader(self.val_dataset, batch_size=self.batch_size, shuffle=self.shuffle, drop_last=False) - return loader - - def test_dataloader(self): - raise NotImplementedError - - def predict_dataloader(self): - raise NotImplementedError \ No newline at end of file diff --git a/stamarker/stamarker/stamarker/modules.py b/stamarker/stamarker/stamarker/modules.py deleted file mode 100644 index 08d9d81..0000000 --- a/stamarker/stamarker/stamarker/modules.py +++ /dev/null @@ -1,276 +0,0 @@ -import abc -import copy -from torch.autograd import Variable -import torch -from torch import Tensor -import torch.nn.functional as F -from torch.nn import Parameter -import torch.nn as nn -from torch_sparse import SparseTensor, set_diag -from torch_geometric.nn.conv import MessagePassing -from torch_geometric.utils import remove_self_loops, add_self_loops, softmax -from typing import Union, Tuple, Optional -from torch_geometric.typing import (OptPairTensor, Adj, Size, NoneType, - OptTensor) - - -class GATConv(MessagePassing): - r"""The graph attentional operator from the `"Graph Attention Networks" - `_ paper - .. math:: - \mathbf{x}^{\prime}_i = \alpha_{i,i}\mathbf{\Theta}\mathbf{x}_{i} + - \sum_{j \in \mathcal{N}(i)} \alpha_{i,j}\mathbf{\Theta}\mathbf{x}_{j}, - where the attention coefficients :math:`\alpha_{i,j}` are computed as - .. math:: - \alpha_{i,j} = - \frac{ - \exp\left(\mathrm{LeakyReLU}\left(\mathbf{a}^{\top} - [\mathbf{\Theta}\mathbf{x}_i \, \Vert \, \mathbf{\Theta}\mathbf{x}_j] - \right)\right)} - {\sum_{k \in \mathcal{N}(i) \cup \{ i \}} - \exp\left(\mathrm{LeakyReLU}\left(\mathbf{a}^{\top} - [\mathbf{\Theta}\mathbf{x}_i \, \Vert \, \mathbf{\Theta}\mathbf{x}_k] - \right)\right)}. - Args: - in_channels (int or tuple): Size of each input sample, or :obj:`-1` to - derive the size from the first input(s) to the forward method. - A tuple corresponds to the sizes of source and target - dimensionalities. - out_channels (int): Size of each output sample. - heads (int, optional): Number of multi-head-attentions. - (default: :obj:`1`) - concat (bool, optional): If set to :obj:`False`, the multi-head - attentions are averaged instead of concatenated. - (default: :obj:`True`) - negative_slope (float, optional): LeakyReLU angle of the negative - slope. (default: :obj:`0.2`) - dropout (float, optional): Dropout probability of the normalized - attention coefficients which exposes each node to a stochastically - sampled neighborhood during training. (default: :obj:`0`) - add_self_loops (bool, optional): If set to :obj:`False`, will not add - self-loops to the input graph. (default: :obj:`True`) - bias (bool, optional): If set to :obj:`False`, the layer will not learn - an additive bias. (default: :obj:`True`) - **kwargs (optional): Additional arguments of - :class:`torch_geometric.nn.conv.MessagePassing`. - """ - _alpha: OptTensor - - def __init__(self, in_channels: Union[int, Tuple[int, int]], - out_channels: int, heads: int = 1, concat: bool = True, - negative_slope: float = 0.2, dropout: float = 0.0, - add_self_loops: bool = True, bias: bool = True, **kwargs): - kwargs.setdefault('aggr', 'add') - super(GATConv, self).__init__(node_dim=0, **kwargs) - - self.in_channels = in_channels - self.out_channels = out_channels - self.heads = heads - self.concat = concat - self.negative_slope = negative_slope - self.dropout = dropout - self.add_self_loops = add_self_loops - self.lin_src = nn.Parameter(torch.zeros(size=(in_channels, out_channels))) - nn.init.xavier_normal_(self.lin_src.data, gain=1.414) - self.lin_dst = self.lin_src - # The learnable parameters to compute attention coefficients: - self.att_src = Parameter(torch.Tensor(1, heads, out_channels)) - self.att_dst = Parameter(torch.Tensor(1, heads, out_channels)) - nn.init.xavier_normal_(self.att_src.data, gain=1.414) - nn.init.xavier_normal_(self.att_dst.data, gain=1.414) - self._alpha = None - self.attentions = None - - def forward(self, x: Union[Tensor, OptPairTensor], edge_index: Adj, - size: Size = None, return_attention_weights=None, attention=True, tied_attention=None): - # type: (Union[Tensor, OptPairTensor], Tensor, Size, NoneType) -> Tensor # noqa - # type: (Union[Tensor, OptPairTensor], SparseTensor, Size, NoneType) -> Tensor # noqa - # type: (Union[Tensor, OptPairTensor], Tensor, Size, bool) -> Tuple[Tensor, Tuple[Tensor, Tensor]] # noqa - # type: (Union[Tensor, OptPairTensor], SparseTensor, Size, bool) -> Tuple[Tensor, SparseTensor] # noqa - r""" - Args: - return_attention_weights (bool, optional): If set to :obj:`True`, - will additionally return the tuple - :obj:`(edge_index, attention_weights)`, holding the computed - attention weights for each edge. (default: :obj:`None`) - """ - H, C = self.heads, self.out_channels - - # We first transform the input node features. If a tuple is passed, we - # transform source and target node features via separate weights: - if isinstance(x, Tensor): - assert x.dim() == 2, "Static graphs not supported in 'GATConv'" - # x_src = x_dst = self.lin_src(x).view(-1, H, C) - x_src = x_dst = torch.mm(x, self.lin_src).view(-1, H, C) - else: # Tuple of source and target node features: - x_src, x_dst = x - assert x_src.dim() == 2, "Static graphs not supported in 'GATConv'" - x_src = self.lin_src(x_src).view(-1, H, C) - if x_dst is not None: - x_dst = self.lin_dst(x_dst).view(-1, H, C) - - x = (x_src, x_dst) - - if not attention: - return x[0].mean(dim=1) - # return x[0].view(-1, self.heads * self.out_channels) - - if tied_attention == None: - # Next, we compute node-level attention coefficients, both for source - # and target nodes (if present): - alpha_src = (x_src * self.att_src).sum(dim=-1) - alpha_dst = None if x_dst is None else (x_dst * self.att_dst).sum(-1) - alpha = (alpha_src, alpha_dst) - self.attentions = alpha - else: - alpha = tied_attention - - if self.add_self_loops: - if isinstance(edge_index, Tensor): - # We only want to add self-loops for nodes that appear both as - # source and target nodes: - num_nodes = x_src.size(0) - if x_dst is not None: - num_nodes = min(num_nodes, x_dst.size(0)) - num_nodes = min(size) if size is not None else num_nodes - edge_index, _ = remove_self_loops(edge_index) - edge_index, _ = add_self_loops(edge_index, num_nodes=num_nodes) - elif isinstance(edge_index, SparseTensor): - edge_index = set_diag(edge_index) - - # propagate_type: (x: OptPairTensor, alpha: OptPairTensor) - out = self.propagate(edge_index, x=x, alpha=alpha, size=size) - - alpha = self._alpha - assert alpha is not None - self._alpha = None - - if self.concat: - out = out.view(-1, self.heads * self.out_channels) - else: - out = out.mean(dim=1) - - # if self.bias is not None: - # out += self.bias - - if isinstance(return_attention_weights, bool): - if isinstance(edge_index, Tensor): - return out, (edge_index, alpha) - elif isinstance(edge_index, SparseTensor): - return out, edge_index.set_value(alpha, layout='coo') - else: - return out - - def message(self, x_j: Tensor, alpha_j: Tensor, alpha_i: OptTensor, - index: Tensor, ptr: OptTensor, - size_i: Optional[int]) -> Tensor: - # Given egel-level attention coefficients for source and target nodes, - # we simply need to sum them up to "emulate" concatenation: - alpha = alpha_j if alpha_i is None else alpha_j + alpha_i - - alpha = F.leaky_relu(alpha, self.negative_slope) - alpha = softmax(alpha, index, ptr, size_i) - self._alpha = alpha # Save for later use. - alpha = F.dropout(alpha, p=self.dropout, training=self.training) - return x_j * alpha.unsqueeze(-1) - - def __repr__(self): - return '{}({}, {}, heads={})'.format(self.__class__.__name__, - self.in_channels, - self.out_channels, self.heads) - - -class STAGATEModule(nn.Module): - def __init__(self, in_features, hidden_dims): - super(STAGATEModule, self).__init__() - [num_hidden, out_dim] = hidden_dims - self.conv1 = GATConv(in_features, num_hidden, heads=1, concat=False, - dropout=0, add_self_loops=False, bias=False) - self.conv2 = GATConv(num_hidden, out_dim, heads=1, concat=False, - dropout=0, add_self_loops=False, bias=False) - self.conv3 = GATConv(out_dim, num_hidden, heads=1, concat=False, - dropout=0, add_self_loops=False, bias=False) - self.conv4 = GATConv(num_hidden, in_features, heads=1, concat=False, - dropout=0, add_self_loops=False, bias=False) - - def forward(self, features, edge_index): - h1 = F.elu(self.conv1(features, edge_index)) - h2 = self.conv2(h1, edge_index, attention=False) - self.conv3.lin_src.data = self.conv2.lin_src.transpose(0, 1) - self.conv3.lin_dst.data = self.conv2.lin_dst.transpose(0, 1) - self.conv4.lin_src.data = self.conv1.lin_src.transpose(0, 1) - self.conv4.lin_dst.data = self.conv1.lin_dst.transpose(0, 1) - h3 = F.elu(self.conv3(h2, edge_index, attention=True, - tied_attention=self.conv1.attentions)) - h4 = self.conv4(h3, edge_index, attention=False) - - return h2, h4 - - -class StackClsModule(nn.Module, abc.ABC): - def __init__(self, in_features, n_classes): - super(StackClsModule, self).__init__() - self.in_features = in_features - self.n_classes = n_classes - - -class STAGATEClsModule(nn.Module): - def __init__(self, - stagate: STAGATEModule, - stack_classifier: StackClsModule): - super(STAGATEClsModule, self).__init__() - self.stagate = copy.deepcopy(stagate) - self.classifier = copy.deepcopy(stack_classifier) - - def forward(self, x, edge_index, mode="classifier"): - z, x_recon = self.stagate(x, edge_index) - z = torch.clone(z) - if mode == "classifier": - return z, self.classifier(z) - elif mode == "reconstruction": - return z, x_recon - else: - raise NotImplementedError - - def get_saliency_map(self, x, edge_index, target_index="max", save=None): - """ - Get saliency map by backpropagation. - :param x: input tensors - :param edge_index: graph edge index - :param target_index: target index to compute final scores - :param save: - :return: gradients - """ - x_var = Variable(x, requires_grad=True) - _, output = self.forward(x_var, edge_index, mode="classifier") - scores = output["last_layer"] - if target_index == "max": - target_score_indices = Variable(torch.argmax(scores, 1)) - elif isinstance(target_index, int): - target_score_indices = Variable(torch.ones(scores.shape[0], dtype=torch.int64) * target_index) - else: - raise NotImplementedError - target_scores = scores.gather(1, target_score_indices.view(-1, 1)).squeeze() - loss = torch.sum(target_scores) - loss.backward() - gradients = x_var.grad.data - if save is not None: - torch.save(gradients, save) - return gradients, scores - - -class StackMLPModule(StackClsModule): - name = "StackMLP" - - def __init__(self, in_features, n_classes, hidden_dims=[30, 40, 30]): - super(StackMLPModule, self).__init__(in_features, n_classes) - self.classifier = nn.ModuleList() - mlp_dims = [in_features] + hidden_dims + [n_classes] - for ind in range(len(mlp_dims) - 1): - self.classifier.append(nn.Linear(mlp_dims[ind], mlp_dims[ind + 1])) - - def forward(self, x): - for layer in self.classifier: - x = layer(x) - score = F.softmax(x, dim=0) - return {"last_layer": x, "score": score} diff --git a/stamarker/stamarker/stamarker/pipeline.py b/stamarker/stamarker/stamarker/pipeline.py deleted file mode 100644 index 98255ae..0000000 --- a/stamarker/stamarker/stamarker/pipeline.py +++ /dev/null @@ -1,286 +0,0 @@ -import pytorch_lightning as pl -import copy -import torch -import os -import shutil -import logging -import glob -import sys -import numpy as np -import scipy -import scanpy as sc -from pytorch_lightning.loggers import TensorBoardLogger -from scipy.cluster import hierarchy -from .models import intSTAGATE, StackClassifier -from .utils import plot_consensus_map, consensus_matrix, Timer -from .dataset import SpatialDataModule -from .modules import STAGATEClsModule -import logging - - -FORMAT = "%(asctime)s %(levelname)s %(message)s" -logging.basicConfig(format=FORMAT, datefmt='%Y-%m-%d %H:%M:%S') -def make_spatial_data(ann_data): - """ - Make SpatialDataModule object from Scanpy annData object - """ - data_module = SpatialDataModule() - ann_data.X = ann_data.X.toarray() - data_module.ann_data = ann_data - return data_module - - -class STAMarker: - def __init__(self, n, save_dir, config, logging_level=logging.INFO): - """ - n: int, number of graph attention auto-econders to train - save_dir: directory to save the models - config: config file for training - """ - self.n = n - self.save_dir = save_dir - if not os.path.exists(save_dir): - os.mkdir(save_dir) - logging.info("Create save directory {}".format(save_dir)) - self.version_dirs = [os.path.join(save_dir, f"version_{i}") for i in range(n)] - self.config = config - self.logger = logging.getLogger("STAMarker") - self.logger.setLevel(logging_level) - self.consensus_labels = None - - def load_from_dir(self, save_dir, ): - """ - Load the trained models from a directory - """ - self.version_dirs = glob.glob(os.path.join(save_dir, "version_*")) - self.version_dirs = sorted(self.version_dirs, key=lambda x: int(x.split("_")[-1])) - # check if all version dir have `checkpoints/stagate.ckpt` - version_dirs_valid = [] - for version_dir in self.version_dirs: - if not os.path.exists(os.path.join(version_dir, "checkpoints/stagate.ckpt")): - self.logger.warning("No checkpoint found in {}".format(version_dir)) - else: - version_dirs_valid.append(version_dir) - self.version_dirs = version_dirs_valid - self.logger.info("Load {} autoencoder models from {}".format(len(version_dirs_valid), save_dir)) - # check if all version dir have `cluster_labels.npy` raise warning if not - missing_cluster_labels = False - for version_dir in self.version_dirs: - if not os.path.exists(os.path.join(version_dir, "cluster_labels.npy")): - missing_cluster_labels = True - msg = "No cluster labels found in {}.".format(version_dir) - self.logger.warning(msg) - if missing_cluster_labels: - self.logger.warning("Please run clustering first.") - # check if save_dir has `consensus.npy` raise warning if not - if not os.path.exists(os.path.join(save_dir, "consensus.npy")): - self.logger.warning("No consensus labels found in {}".format(save_dir)) - else: - self.consensus_labels = np.load(os.path.join(save_dir, "consensus.npy")) - # check if all version dir have `checkpoints/mlp.ckpt` raise warning if not - missing_clf = False - for version_dir in self.version_dirs: - if not os.path.exists(os.path.join(version_dir, "checkpoints/mlp.ckpt")): - self.logger.warning("No classifier checkpoint found in {}".format(version_dir)) - missing_clf = True - if missing_clf: - self.logger.warning("Please run classifier training first.") - if not missing_cluster_labels and not missing_clf: - self.logger.info("All models are trained and ready to use.") - - def train_auto_encoders(self, data_module): - for seed in range(self.n): - self._train_auto_encoder(data_module, seed, self.config) - self.logger.info("Finished training {} auto-encoders".format(self.n)) - - def clustering(self, data_module, cluster_method, cluster_params): - """ - Cluster the latent space of the trained auto-encoders - Cluster method should be "louvain" or "mclust" - """ - for version_dir in self.version_dirs: - self._clustering(data_module, version_dir, cluster_method, cluster_params) - self.logger.info("Finished {} clustering with {}".format(self.n, cluster_method)) - - def consensus_clustering(self, n_clusters, name="cluster_labels.npy"): - sys.setrecursionlimit(100000) - label_files = glob.glob(self.save_dir + f"/version_*/{name}") - labels_list = list(map(lambda file: np.load(file), label_files)) - cons_mat = consensus_matrix(labels_list) - row_linkage, _, figure = plot_consensus_map(cons_mat, return_linkage=True) - figure.savefig(os.path.join(self.save_dir, "consensus_clustering.png"), dpi=300) - consensus_labels = hierarchy.cut_tree(row_linkage, n_clusters).squeeze() - np.save(os.path.join(self.save_dir, "consensus"), consensus_labels) - self.consensus_labels = consensus_labels - self.logger.info("Save consensus labels to {}".format(os.path.join(self.save_dir, "consensus.npz"))) - - def train_classifiers(self, data_module, n_clusters, name="cluster_labels.npy"): - for version_dir in self.version_dirs: - # _train_classifier(self, data_module, version_dir, target_y, n_classes, seed=None) - self._train_classifier(data_module, version_dir, self.consensus_labels, n_clusters, self.config) - self.logger.info("Finished training {} classifiers".format(self.n)) - - def compute_smaps(self, data_module, return_recon=True, normalize=True): - smaps = [] - if return_recon: - recons = [] - for version_dir in self.version_dirs: - if return_recon: - smap, recon = self._compute_smap(data_module, version_dir, return_recon=return_recon) - smaps.append(smap) - recons.append(recon) - else: - smap = self._compute_smap(data_module, version_dir, return_recon=return_recon) - smaps.append(smap) - if return_recon: - return smaps, recons - else: - return smaps - self.logger.info("Finished computing {} smaps".format(self.n)) - - - def _compute_smap_zscore(self, smap, labels, logtransform=False): - scores = np.log(smap + 1) if logtransform else copy.copy(smap) - unique_labels = np.unique(labels) - for l in unique_labels: - scores[labels == l, :] = scipy.stats.zscore(scores[labels == l, :], axis=1) - return scores - - - def _clustering(self, data_module, version_dir, cluster_method, cluster_params): - """ - Cluster the latent space of the trained auto-encoder - """ - if cluster_method == "louvain": - run_louvain(data_module, version_dir, cluster_params) - elif cluster_method == "mclust": - run_mclust(data_module, version_dir, cluster_params) - else: - raise ValueError("Unknown clustering method") - - def _train_auto_encoder(self, data_module, seed, config): - """ - Train a single graph attention auto-encoder - """ - pl.seed_everything(seed) - version = f"version_{seed}" - version_dir = os.path.join(self.save_dir, version) - if os.path.exists(version_dir): - shutil.rmtree(version_dir) - os.makedirs(version_dir, exist_ok=True) - logger = TensorBoardLogger(save_dir=self.save_dir, name=None, - default_hp_metric=False, - version=seed) - model = intSTAGATE(**config["stagate"]["params"]) - model.set_optimizer_params(config["stagate"]["optimizer"], config["stagate"]["scheduler"]) - trainer = pl.Trainer(logger=logger, **config["stagate_trainer"]) - timer = Timer() - timer.tic("fit") - trainer.fit(model, data_module) - fit_time = timer.toc("fit") - with open(os.path.join(version_dir, "runtime.csv"), "w+") as f: - f.write("{}, fit_time, {:.2f}, ".format(seed, fit_time / 60)) - trainer.save_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - del model, trainer - if config["stagate_trainer"]["gpus"] > 0: - torch.cuda.empty_cache() - logging.info(f"Finshed running version {seed}") - - def _train_classifier(self, data_module, version_dir, target_y, n_classes, config, seed=None): - timer = Timer() - pl.seed_everything(seed) - rep_dim = config["stagate"]["params"]["hidden_dims"][-1] - stagate = intSTAGATE.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - classifier = StackClassifier(rep_dim, n_classes=n_classes, architecture="MLP") - classifier.prepare(stagate, data_module.train_dataset, target_y, - balanced=config["mlp"]["balanced"], test_prop=config["mlp"]["test_prop"]) - classifier.set_optimizer_params(config["mlp"]["optimizer"], config["mlp"]["scheduler"]) - logger = TensorBoardLogger(save_dir=self.save_dir, name=None, - default_hp_metric=False, - version=seed) - trainer = pl.Trainer(logger=logger, **config["classifier_trainer"]) - timer.tic("clf") - trainer.fit(classifier) - clf_time = timer.toc("clf") - with open(os.path.join(version_dir, "runtime.csv"), "a+") as f: - f.write("\n") - f.write("{}, clf_time, {:.2f}, ".format(seed, clf_time / 60)) - trainer.save_checkpoint(os.path.join(version_dir, "checkpoints", "mlp.ckpt")) - target_y = classifier.dataset.target_y.numpy() - all_props = class_proportions(target_y) - val_props = class_proportions(target_y[classifier.val_dataset.indices]) - if self.logger.level == logging.DEBUG: - print("All class proportions " + "|".join(["{:.2f}%".format(prop * 100) for prop in all_props])) - print("Val class proportions " + "|".join(["{:.2f}%".format(prop * 100) for prop in val_props])) - np.save(os.path.join(version_dir, "confusion.npy"), classifier.confusion) - - def _compute_smap(self, data_module, version_dir, return_recon=True): - """ - Compute the saliency map of the trained auto-encoder - """ - stagate = intSTAGATE.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - cls = StackClassifier.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "mlp.ckpt")) - stagate_cls = STAGATEClsModule(stagate.model, cls.model) - smap, _ = stagate_cls.get_saliency_map(data_module.train_dataset.x, - data_module.train_dataset.edge_index) - smap = smap.detach().cpu().numpy() - if return_recon: - recon = stagate(data_module.train_dataset.x, data_module.train_dataset.edge_index)[1].cpu().detach().numpy() - return smap, recon - else: - return smap - - -def run_louvain(data_module, version_dir, resolution, name="cluster_labels"): - """ - Run louvain clustering on the data_module - """ - stagate = intSTAGATE.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - embedding = stagate(data_module.train_dataset.x, data_module.train_dataset.edge_index)[0].cpu().detach().numpy() - ann_data = copy.copy(data_module.ann_data) - ann_data.obsm["stagate"] = embedding - sc.pp.neighbors(ann_data, use_rep='stagate') - sc.tl.louvain(ann_data, resolution=resolution) - save_path = os.path.join(version_dir, "{}.npy".format(name)) - np.save(save_path, ann_data.obs["louvain"].to_numpy().astype("int")) - print("Save louvain results to {}".format(save_path)) - - -def mclust_R(representation, n_clusters, r_seed=2022, model_name="EEE"): - """ - Clustering using the mclust algorithm. - The parameters are the same as those in the R package mclust. - """ - np.random.seed(r_seed) - import rpy2.robjects as ro - from rpy2.robjects import numpy2ri - numpy2ri.activate() - ro.r.library("mclust") - r_random_seed = ro.r['set.seed'] - r_random_seed(r_seed) - rmclust = ro.r['Mclust'] - res = rmclust(representation, n_clusters, model_name) - mclust_res = np.array(res[-2]) - numpy2ri.deactivate() - return mclust_res.astype('int') - - -def run_mclust(data_module, version_dir, n_clusters, name="cluster_labels"): - stagate = intSTAGATE.load_from_checkpoint(os.path.join(version_dir, "checkpoints", "stagate.ckpt")) - embedding = stagate(data_module.train_dataset.x, data_module.train_dataset.edge_index)[0].cpu().detach().numpy() - labels = mclust_R(embedding, n_clusters) - save_path = os.path.join(version_dir, "{}.npy".format(name)) - np.save(save_path, labels.astype("int")) - print("Save MClust results to {}".format(save_path)) - -def class_proportions(target): - n_classes = len(np.unique(target)) - props = np.array([np.sum(target == i) for i in range(n_classes)]) - return props / np.sum(props) - - - - - - - diff --git a/stamarker/stamarker/stamarker/utils.py b/stamarker/stamarker/stamarker/utils.py deleted file mode 100644 index c4f6182..0000000 --- a/stamarker/stamarker/stamarker/utils.py +++ /dev/null @@ -1,190 +0,0 @@ -import time -import yaml -import os -import seaborn as sns -import numpy as np -import pandas as pd -import scanpy as sc -import itertools -import scipy -from scipy.spatial import distance -from scipy.cluster import hierarchy -import sklearn.neighbors -from typing import List - - -def plot_consensus_map(cmat, method="average", return_linkage=True, **kwargs): - row_linkage = hierarchy.linkage(distance.pdist(cmat), method=method) - col_linkage = hierarchy.linkage(distance.pdist(cmat.T), method=method) - figure = sns.clustermap(cmat, row_linkage=row_linkage, col_linkage=col_linkage, **kwargs) - if return_linkage: - return row_linkage, col_linkage, figure - else: - return figure - - -class Timer: - - def __init__(self): - self.timer_dict = {} - self.stop_dict = {} - - def tic(self, name): - self.timer_dict[name] = time.time() - - def toc(self, name): - assert name in self.timer_dict - elapsed = time.time() - self.timer_dict[name] - del self.timer_dict[name] - return elapsed - - def stop(self, name): - self.stop_dict[name] = time.time() - - def resume(self, name): - if name not in self.timer_dict: - del self.stop_dict[name] - return - elapsed = time.time() - self.stop_dict[name] - self.timer_dict[name] = self.timer_dict[name] + elapsed - del self.stop_dict[name] - - -def save_yaml(yaml_object, file_path): - with open(file_path, 'w') as yaml_file: - yaml.dump(yaml_object, yaml_file, default_flow_style=False) - - print(f'Saving yaml: {file_path}') - return - - -def parse_args(yaml_file): - with open(yaml_file, 'r') as stream: - try: - cfg = yaml.safe_load(stream) - except yaml.YAMLError as exc: - print(exc) - return cfg - - -def mclust_R(representation, n_clusters, r_seed=2022, model_name="EEE"): - """ - Clustering using the mclust algorithm. - The parameters are the same as those in the R package mclust. - """ - np.random.seed(r_seed) - import rpy2.robjects as ro - from rpy2.robjects import numpy2ri - numpy2ri.activate() - ro.r.library("mclust") - r_random_seed = ro.r['set.seed'] - r_random_seed(r_seed) - rmclust = ro.r['Mclust'] - res = rmclust(representation, n_clusters, model_name) - mclust_res = np.array(res[-2]) - numpy2ri.deactivate() - return mclust_res.astype('int') - - -def labels_connectivity_mat(labels: np.ndarray): - _labels = labels - np.min(labels) - n_classes = np.unique(_labels) - mat = np.zeros([labels.size, labels.size]) - for i in n_classes: - indices = np.squeeze(np.where(_labels == i)) - row_indices, col_indices = zip(*itertools.product(indices, indices)) - mat[row_indices, col_indices] = 1 - return mat - - -def consensus_matrix(labels_list: List[np.ndarray]): - mat = 0 - for labels in labels_list: - mat += labels_connectivity_mat(labels) - return mat / float(len(labels_list)) - - -def compute_spatial_net(ann_data, rad_cutoff=None, k_cutoff=None, - max_neigh=50, model='Radius', verbose=True): - """ - Construct the spatial neighbor networks. - - Parameters - ---------- - ann_data - AnnData object of scanpy package. - rad_cutoff - radius cutoff when model='Radius' - k_cutoff - The number of nearest neighbors when model='KNN' - model - The network construction model. When model=='Radius', the spot is connected to spots whose distance is less than rad_cutoff. When model=='KNN', the spot is connected to its first k_cutoff nearest neighbors. - - Returns - ------- - The spatial networks are saved in adata.uns['Spatial_Net'] - """ - - assert (model in ['Radius', 'KNN']) - if verbose: - print('------Calculating spatial graph...') - coor = pd.DataFrame(ann_data.obsm['spatial']) - coor.index = ann_data.obs.index - coor.columns = ['imagerow', 'imagecol'] - - nbrs = sklearn.neighbors.NearestNeighbors( - n_neighbors=max_neigh + 1, algorithm='ball_tree').fit(coor) - distances, indices = nbrs.kneighbors(coor) - if model == 'KNN': - indices = indices[:, 1:k_cutoff + 1] - distances = distances[:, 1:k_cutoff + 1] - if model == 'Radius': - indices = indices[:, 1:] - distances = distances[:, 1:] - KNN_list = [] - for it in range(indices.shape[0]): - KNN_list.append(pd.DataFrame(zip([it] * indices.shape[1], indices[it, :], distances[it, :]))) - KNN_df = pd.concat(KNN_list) - KNN_df.columns = ['Cell1', 'Cell2', 'Distance'] - Spatial_Net = KNN_df.copy() - if model == 'Radius': - Spatial_Net = KNN_df.loc[KNN_df['Distance'] < rad_cutoff,] - id_cell_trans = dict(zip(range(coor.shape[0]), np.array(coor.index), )) - cell1, cell2 = Spatial_Net['Cell1'].map(id_cell_trans), Spatial_Net['Cell2'].map(id_cell_trans) - Spatial_Net = Spatial_Net.assign(Cell1=cell1, Cell2=cell2) - # Spatial_Net.assign(Cell1=Spatial_Net['Cell1'].map(id_cell_trans)) - # Spatial_Net.assign(Cell2=Spatial_Net['Cell2'].map(id_cell_trans)) - if verbose: - print('The graph contains %d edges, %d cells.' % (Spatial_Net.shape[0], ann_data.n_obs)) - print('%.4f neighbors per cell on average.' % (Spatial_Net.shape[0] / ann_data.n_obs)) - ann_data.uns['Spatial_Net'] = Spatial_Net - - -def compute_edge_list(ann_data): - G_df = ann_data.uns['Spatial_Net'].copy() - cells = np.array(ann_data.obs_names) - cells_id_tran = dict(zip(cells, range(cells.shape[0]))) - G_df['Cell1'] = G_df['Cell1'].map(cells_id_tran) - G_df['Cell2'] = G_df['Cell2'].map(cells_id_tran) - G = scipy.sparse.coo_matrix((np.ones(G_df.shape[0]), (G_df['Cell1'], G_df['Cell2'])), - shape=(ann_data.n_obs, ann_data.n_obs)) - G = G + scipy.sparse.eye(G.shape[0]) - edge_list = np.nonzero(G) - return edge_list - - -def stats_spatial_net(ann_data): - import matplotlib.pyplot as plt - Num_edge = ann_data.uns['Spatial_Net']['Cell1'].shape[0] - Mean_edge = Num_edge / ann_data.shape[0] - plot_df = pd.value_counts(pd.value_counts(ann_data.uns['Spatial_Net']['Cell1'])) - plot_df = plot_df / ann_data.shape[0] - fig, ax = plt.subplots(figsize=[3, 2]) - plt.ylabel('Percentage') - plt.xlabel('') - plt.title('Number of Neighbors (Mean=%.2f)' % Mean_edge) - ax.bar(plot_df.index, plot_df) - - -# def select_svgs(smaps, sd_id, labels, alpha=1.5): - diff --git a/stamarker/stamarker/utils.py b/stamarker/stamarker/utils.py deleted file mode 100644 index 10a4c14..0000000 --- a/stamarker/stamarker/utils.py +++ /dev/null @@ -1,192 +0,0 @@ -import time -import yaml -import os -import seaborn as sns -import numpy as np -import pandas as pd -import scanpy as sc -import itertools -import scipy -from scipy.spatial import distance -from scipy.cluster import hierarchy -import sklearn.neighbors -from typing import List - - -def plot_consensus_map(cmat, method="average", return_linkage=True, **kwargs): - row_linkage = hierarchy.linkage(distance.pdist(cmat), method=method) - col_linkage = hierarchy.linkage(distance.pdist(cmat.T), method=method) - figure = sns.clustermap(cmat, row_linkage=row_linkage, col_linkage=col_linkage, **kwargs) - if return_linkage: - return row_linkage, col_linkage, figure - else: - return figure - - -class Timer: - - def __init__(self): - self.timer_dict = {} - self.stop_dict = {} - - def tic(self, name): - self.timer_dict[name] = time.time() - - def toc(self, name): - assert name in self.timer_dict - elapsed = time.time() - self.timer_dict[name] - del self.timer_dict[name] - return elapsed - - def stop(self, name): - self.stop_dict[name] = time.time() - - def resume(self, name): - if name not in self.timer_dict: - del self.stop_dict[name] - return - elapsed = time.time() - self.stop_dict[name] - self.timer_dict[name] = self.timer_dict[name] + elapsed - del self.stop_dict[name] - - -def save_yaml(yaml_object, file_path): - with open(file_path, 'w') as yaml_file: - yaml.dump(yaml_object, yaml_file, default_flow_style=False) - - print(f'Saving yaml: {file_path}') - return - - -def parse_args(yaml_file): - with open(yaml_file, 'r') as stream: - try: - cfg = yaml.safe_load(stream) - except yaml.YAMLError as exc: - print(exc) - return cfg - - -def mclust_R(representation, n_clusters, r_seed=2022, model_name="EEE"): - """ - Clustering using the mclust algorithm. - The parameters are the same as those in the R package mclust. - """ - np.random.seed(r_seed) - import rpy2.robjects as ro - from rpy2.robjects import numpy2ri - numpy2ri.activate() - ro.r.library("mclust") - r_random_seed = ro.r['set.seed'] - r_random_seed(r_seed) - rmclust = ro.r['Mclust'] - res = rmclust(representation, n_clusters, model_name) - mclust_res = np.array(res[-2]) - numpy2ri.deactivate() - return mclust_res.astype('int') - - -def labels_connectivity_mat(labels: np.ndarray): - _labels = labels - np.min(labels) - n_classes = np.unique(_labels) - mat = np.zeros([labels.size, labels.size]) - for i in n_classes: - indices = np.squeeze(np.where(_labels == i)) - row_indices, col_indices = zip(*itertools.product(indices, indices)) - mat[row_indices, col_indices] = 1 - return mat - - -def consensus_matrix(labels_list: List[np.ndarray]): - mat = 0 - for labels in labels_list: - mat += labels_connectivity_mat(labels) - return mat / float(len(labels_list)) - - -def compute_spatial_net(ann_data, rad_cutoff=None, k_cutoff=None, - max_neigh=50, model='Radius', verbose=True): - """ - Construct the spatial neighbor networks. - - Parameters - ---------- - ann_data - AnnData object of scanpy package. - rad_cutoff - radius cutoff when model='Radius' - k_cutoff - The number of nearest neighbors when model='KNN' - model - The network construction model. When model=='Radius', the spot is connected to spots whose distance is less than rad_cutoff. When model=='KNN', the spot is connected to its first k_cutoff nearest neighbors. - - Returns - ------- - The spatial networks are saved in adata.uns['Spatial_Net'] - """ - - assert (model in ['Radius', 'KNN']) - if verbose: - print('------Calculating spatial graph...') - coor = pd.DataFrame(ann_data.obsm['spatial']) - coor.index = ann_data.obs.index - coor.columns = ['imagerow', 'imagecol'] - - nbrs = sklearn.neighbors.NearestNeighbors( - n_neighbors=max_neigh + 1, algorithm='ball_tree').fit(coor) - distances, indices = nbrs.kneighbors(coor) - if model == 'KNN': - indices = indices[:, 1:k_cutoff + 1] - distances = distances[:, 1:k_cutoff + 1] - if model == 'Radius': - indices = indices[:, 1:] - distances = distances[:, 1:] - KNN_list = [] - for it in range(indices.shape[0]): - KNN_list.append(pd.DataFrame(zip([it] * indices.shape[1], indices[it, :], distances[it, :]))) - KNN_df = pd.concat(KNN_list) - KNN_df.columns = ['Cell1', 'Cell2', 'Distance'] - Spatial_Net = KNN_df.copy() - if model == 'Radius': - Spatial_Net = KNN_df.loc[KNN_df['Distance'] < rad_cutoff,] - id_cell_trans = dict(zip(range(coor.shape[0]), np.array(coor.index), )) - cell1, cell2 = Spatial_Net['Cell1'].map(id_cell_trans), Spatial_Net['Cell2'].map(id_cell_trans) - Spatial_Net = Spatial_Net.assign(Cell1=cell1, Cell2=cell2) - # Spatial_Net.assign(Cell1=Spatial_Net['Cell1'].map(id_cell_trans)) - # Spatial_Net.assign(Cell2=Spatial_Net['Cell2'].map(id_cell_trans)) - if verbose: - print('The graph contains %d edges, %d cells.' % (Spatial_Net.shape[0], ann_data.n_obs)) - print('%.4f neighbors per cell on average.' % (Spatial_Net.shape[0] / ann_data.n_obs)) - ann_data.uns['Spatial_Net'] = Spatial_Net - - -def compute_edge_list(ann_data): - G_df = ann_data.uns['Spatial_Net'].copy() - cells = np.array(ann_data.obs_names) - cells_id_tran = dict(zip(cells, range(cells.shape[0]))) - G_df['Cell1'] = G_df['Cell1'].map(cells_id_tran) - G_df['Cell2'] = G_df['Cell2'].map(cells_id_tran) - G = scipy.sparse.coo_matrix((np.ones(G_df.shape[0]), (G_df['Cell1'], G_df['Cell2'])), - shape=(ann_data.n_obs, ann_data.n_obs)) - G = G + scipy.sparse.eye(G.shape[0]) - edge_list = np.nonzero(G) - return edge_list - - -def stats_spatial_net(ann_data): - import matplotlib.pyplot as plt - Num_edge = ann_data.uns['Spatial_Net']['Cell1'].shape[0] - Mean_edge = Num_edge / ann_data.shape[0] - plot_df = pd.value_counts(pd.value_counts(ann_data.uns['Spatial_Net']['Cell1'])) - plot_df = plot_df / ann_data.shape[0] - fig, ax = plt.subplots(figsize=[3, 2]) - plt.ylabel('Percentage') - plt.xlabel('') - plt.title('Number of Neighbors (Mean=%.2f)' % Mean_edge) - ax.bar(plot_df.index, plot_df) - - -def select_stmaker_svgs(df, sd_id, alpha=1.5, top=None): - scores = df[f"score_{sd_id}"] - mu, std = np.mean(scores), np.std(scores) - return df.index[scores > mu + alpha * std].tolist()