Repository: AstraZeneca/chemicalx (language: python)

Issue:
Add the DeepDDI model
Dear @hzcheney,

- Please read the paper first. It is [here](https://www.pnas.org/content/115/18/E4304).
- After that read the contributing guidelines.
-   If there is an existing open source version of the model please take a look.
-   ChemicalX is built on top of [PyTorch 1.10.](https://pytorch.org/) and [torchdrug](https://torchdrug.ai/).
- A similar model is `` which uses``  to generate drug representations. Take a look at the layer definition [here]().
- The library heavily builds on top on [torchdrug](https://torchdrug.ai/) and molecules in batches are PackedGraphs.
- There is already a model class under `./chemicalx/models/`
- Context features, drug level features and labels are all FloatTensors.
- Look at the examples and tests under `./examples/` and `./tests/`.
- Add auxiliary layers as you see fit - please document these, add tests and add these layers to the main readme.md if needed.
- Add typing to the initialisation and forward pass.
- Non data dependent hyperparameters should have default values. 
- Please add tests under `./tests/` and make sure that your model/layer is tested with real data.
- Write an example under `./examples/`. What is the AUC on the test set? Is it reasonable?



Discussion / hints:
Hi!😊Is this repo welcome for contribution?
Hi @hzcheney,

We are architecting the data loaders in January 2022 and after that, we will have a board with outstanding features and issues. I will get back to you!

Thank you for your interest! We want to hit KDD 2022 Applied Track.

Benedek
That will be great! Good luck on your paper!
Hi @hzcheney ,

Are you interested in contributing?
  @benedekrozemberczki Yeah, I will try.
@YuWVandy what do you think?
Hi! @benedekrozemberczki Sorry about the late response, I have already finished the model part. There is a problem with the input feature named SSP(structural similarity profile), it consists of the drug similarity vector which is based on their fingerprint. The problem is I can't find a straightforward way to calculate the SSP, any idea?
It is the following:

1. For each drug a fingerprint is generated D X n. Where D is the number of drugs and n is the fingerprint dimensionality.
2. Using the fingerprints you define a D X D similarity matrix.
3. Using this Matrix you use PCA to reduce the dimensionality of the similarity matrix.
4. This would require on my side that we add a key to the dataset which we could use to retrieve the SSP vectors.

I would say using the drug feature vectors is sufficient to develop this.

I would say don’t consider the drug featurization as a part of the model. Whether you use maacs, Morgan, or SSP shouldn’t make a difference 
So you could just submit the PR to take in whatever drug features are available from the data loader (currently Morgan fingerprints) and in future work we could add different featurizations to the data loader. 
Completely agree with @cthoyt about this. It should not be on the model side.
@hzcheney Are you going to open a PR with your code?

Fix patch:
diff --git a/chemicalx/models/deepddi.py b/chemicalx/models/deepddi.py
index d820e01..b479ea0 100644
--- a/chemicalx/models/deepddi.py
+++ b/chemicalx/models/deepddi.py
@@ -1,13 +1,17 @@
 """An implementation of the DeepDDI model."""
 
-from .base import UnimplementedModel
+import torch
+from torch import nn
+
+from chemicalx.data import DrugPairBatch
+from chemicalx.models import Model
 
 __all__ = [
     "DeepDDI",
 ]
 
 
-class DeepDDI(UnimplementedModel):
+class DeepDDI(Model):
     """An implementation of the DeepDDI model from [ryu2018]_.
 
     .. seealso:: This model was suggested in https://github.com/AstraZeneca/chemicalx/issues/2
@@ -16,3 +20,60 @@ class DeepDDI(UnimplementedModel):
        of drug–drug and drug–food interactions <https://doi.org/10.1073/pnas.1803294115>`_.
        *Proceedings of the National Academy of Sciences*, 115(18), E4304–E4311.
     """
+
+    def __init__(
+        self,
+        *,
+        drug_channels: int,
+        hidden_channels: int = 2048,
+        hidden_layers_num: int = 9,
+        out_channels: int = 1,
+    ):
+        """Instantiate the DeepDDI model.
+
+        :param drug_channels: The number of drug features.
+        :param hidden_channels: The number of hidden layer neurons.
+        :param hidden_layers_num: The number of hidden layers.
+        :param out_channels: The number of output channels.
+        """
+        super().__init__()
+        assert hidden_layers_num > 1
+        dnn = [
+            nn.Linear(drug_channels * 2, hidden_channels),
+            nn.ReLU(),
+            nn.BatchNorm1d(num_features=hidden_channels, affine=True, momentum=None),
+            nn.ReLU(),
+        ]
+        for _ in range(hidden_layers_num - 1):
+            dnn.extend(
+                [
+                    nn.Linear(hidden_channels, hidden_channels),
+                    nn.ReLU(),
+                    nn.BatchNorm1d(num_features=hidden_channels, affine=True, momentum=None),
+                    nn.ReLU(),
+                ]
+            )
+        dnn.extend([nn.Linear(hidden_channels, out_channels), nn.Sigmoid()])
+        self.dnn = nn.Sequential(*dnn)
+
+    def unpack(self, batch: DrugPairBatch):
+        """Return the context features, left drug features and right drug features."""
+        return (
+            batch.drug_features_left,
+            batch.drug_features_right,
+        )
+
+    def forward(
+        self,
+        drug_features_left: torch.FloatTensor,
+        drug_features_right: torch.FloatTensor,
+    ) -> torch.FloatTensor:
+        """Run a forward pass of the DeepDDI model.
+
+        :param drug_features_left: A matrix of head drug features.
+        :param drug_features_right: A matrix of tail drug features.
+        :returns: A column vector of predicted interaction scores.
+        """
+        input_feature = torch.cat([drug_features_left, drug_features_right], dim=1)
+        hidden = self.dnn(input_feature)
+        return hidden
diff --git a/examples/deepddi_examples.py b/examples/deepddi_examples.py
new file mode 100644
index 0000000..b788afe
--- /dev/null
+++ b/examples/deepddi_examples.py
@@ -0,0 +1,28 @@
+"""Example with DeepDDI."""
+
+from chemicalx import pipeline
+from chemicalx.data import DrugbankDDI
+from chemicalx.models import DeepDDI
+
+
+def main():
+    """Train and evaluate the DeepSynergy model."""
+    dataset = DrugbankDDI()
+    model = DeepDDI(drug_channels=dataset.drug_channels, hidden_layers_num=2)
+    results = pipeline(
+        dataset=dataset,
+        model=model,
+        batch_size=5120,
+        epochs=100,
+        context_features=False,
+        drug_features=True,
+        drug_molecules=False,
+        metrics=[
+            "roc_auc",
+        ],
+    )
+    results.summarize()
+
+
+if __name__ == "__main__":
+    main()


Test patch:
diff --git a/tests/unit/test_models.py b/tests/unit/test_models.py
index e863dd0..a50fa7a 100644
--- a/tests/unit/test_models.py
+++ b/tests/unit/test_models.py
@@ -193,8 +193,22 @@ def test_ssiddi(self):
 
     def test_deepddi(self):
         """Test DeepDDI."""
-        model = DeepDDI(x=2)
-        assert model.x == 2
+        model = DeepDDI(
+            drug_channels=self.loader.drug_channels,
+            hidden_channels=16,
+            hidden_layers_num=2,
+        )
+
+        optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=0.0001)
+        model.train()
+        loss = torch.nn.BCELoss()
+        for batch in self.generator:
+            optimizer.zero_grad()
+            prediction = model(batch.drug_features_left, batch.drug_features_right)
+            output = loss(prediction, batch.labels)
+            output.backward()
+            optimizer.step()
+            assert prediction.shape[0] == batch.labels.shape[0]
 
     def test_deepdrug(self):
         """Test DeepDrug."""