Skip to content
Snippets Groups Projects
test_class1_ligandome_predictor.py 12 KiB
Newer Older
"""

Idea:

- take an allele where MS vs. no-MS trained predictors are very different. One
    possiblility is DLA-88*501:01 but human would be better
- generate synethetic multi-allele MS by combining single-allele MS for differnet
   alleles, including the selected allele
- train ligandome predictor based on the no-ms pan-allele models on theis
  synthetic dataset
- see if the pan-allele predictor learns the "correct" motif for the selected
  allele, i.e. updates to become more similar to the with-ms pan allele predictor.


"""

Tim O'Donnell's avatar
Tim O'Donnell committed
import logging
logging.getLogger('tensorflow').disabled = True
logging.getLogger('matplotlib').disabled = True

import pandas
import argparse
import sys

Tim O'Donnell's avatar
Tim O'Donnell committed
from numpy.testing import assert_, assert_equal, assert_allclose
import numpy
from random import shuffle

Tim O'Donnell's avatar
Tim O'Donnell committed
from sklearn.metrics import roc_auc_score

Tim O'Donnell's avatar
Tim O'Donnell committed
from mhcflurry import Class1AffinityPredictor, Class1NeuralNetwork
Tim O'Donnell's avatar
Tim O'Donnell committed
from mhcflurry.allele_encoding import MultipleAlleleEncoding
from mhcflurry.class1_ligandome_predictor import Class1LigandomePredictor
Tim O'Donnell's avatar
Tim O'Donnell committed
from mhcflurry.encodable_sequences import EncodableSequences
from mhcflurry.downloads import get_path
Tim O'Donnell's avatar
Tim O'Donnell committed
from mhcflurry.regression_target import from_ic50
Tim O'Donnell's avatar
Tim O'Donnell committed
from mhcflurry.common import random_peptides, positional_frequency_matrix
from mhcflurry.testing_utils import cleanup, startup
from mhcflurry.amino_acid import COMMON_AMINO_ACIDS

COMMON_AMINO_ACIDS = sorted(COMMON_AMINO_ACIDS)

PAN_ALLELE_PREDICTOR_NO_MASS_SPEC = None
PAN_ALLELE_MOTIFS_WITH_MASS_SPEC_DF = None
PAN_ALLELE_MOTIFS_NO_MASS_SPEC_DF = None


def setup():
    global PAN_ALLELE_PREDICTOR_NO_MASS_SPEC
    global PAN_ALLELE_MOTIFS_WITH_MASS_SPEC_DF
    global PAN_ALLELE_MOTIFS_NO_MASS_SPEC_DF
    startup()
    PAN_ALLELE_PREDICTOR_NO_MASS_SPEC = Class1AffinityPredictor.load(
Tim O'Donnell's avatar
Tim O'Donnell committed
        get_path("models_class1_pan", "models.no_mass_spec"),
        optimization_level=0,
        max_models=1)

    PAN_ALLELE_MOTIFS_WITH_MASS_SPEC_DF = pandas.read_csv(
        get_path(
            "models_class1_pan",
            "models.with_mass_spec/frequency_matrices.csv.bz2"))
    PAN_ALLELE_MOTIFS_NO_MASS_SPEC_DF = pandas.read_csv(
        get_path(
            "models_class1_pan",
            "models.no_mass_spec/frequency_matrices.csv.bz2"))


def teardown():
    global PAN_ALLELE_PREDICTOR_NO_MASS_SPEC
    global PAN_ALLELE_MOTIFS_WITH_MASS_SPEC_DF
    global PAN_ALLELE_MOTIFS_NO_MASS_SPEC_DF

    PAN_ALLELE_PREDICTOR_NO_MASS_SPEC = None
    PAN_ALLELE_MOTIFS_WITH_MASS_SPEC_DF = None
    PAN_ALLELE_MOTIFS_NO_MASS_SPEC_DF = None
    cleanup()


def scramble_peptide(peptide):
    lst = list(peptide)
    shuffle(lst)
    return "".join(lst)


Tim O'Donnell's avatar
Tim O'Donnell committed
def evaluate_loss(loss, y_true, y_pred):
    import keras.backend as K

    y_true = numpy.array(y_true)
    y_pred = numpy.array(y_pred)
    if y_pred.ndim == 1:
        y_pred = y_pred.reshape((len(y_pred), 1))
    if y_true.ndim == 1:
        y_true = y_true.reshape((len(y_true), 1))

    if K.backend() == "tensorflow":
        session = K.get_session()
        y_true_var = K.constant(y_true, name="y_true")
        y_pred_var = K.constant(y_pred, name="y_pred")
        result = loss(y_true_var, y_pred_var)
        return result.eval(session=session)
    elif K.backend() == "theano":
        y_true_var = K.constant(y_true, name="y_true")
        y_pred_var = K.constant(y_pred, name="y_pred")
        result = loss(y_true_var, y_pred_var)
        return result.eval()
    else:
        raise ValueError("Unsupported backend: %s" % K.backend())


def Xtest_loss():
    # Hit labels
    y_true = [
        1.0,
        0.0,
        1.0,
        1.0,
        0.0
    ]
    y_true = numpy.array(y_true)
    y_pred = [
        [0.3, 0.7, 0.5],
        [0.2, 0.4, 0.6],
        [0.1, 0.5, 0.3],
        [0.1, 0.7, 0.1],
        [0.8, 0.2, 0.4],
    ]
    y_pred = numpy.array(y_pred)

    # reference implementation 1
    contributions = []
    for i in range(len(y_true)):
        if y_true[i] == 1.0:
            for j in range(len(y_true)):
                if y_true[j] == 0.0:
                    tightest_i = max(y_pred[i])
                    contribution = sum(
                        max(0, y_pred[j, k] - tightest_i)**2
                        for k in range(y_pred.shape[1])
                    )
                    contributions.append(contribution)
    contributions = numpy.array(contributions)
    expected1 = contributions.sum()

    # reference implementation 2: numpy
    pos = y_pred[y_true.astype(bool)].max(1)
    neg = y_pred[~y_true.astype(bool)]
    expected2 = (
            numpy.maximum(0, neg.reshape((-1, 1)) - pos)**2).sum()

    numpy.testing.assert_almost_equal(expected1, expected2)

    computed = evaluate_loss(
        Class1LigandomePredictor.loss,
        y_true,
        y_pred.reshape(y_pred.shape + (1,)))
    numpy.testing.assert_almost_equal(computed, expected1)


AA_DIST = pandas.Series(
    dict((line.split()[0], float(line.split()[1])) for line in """
A    0.071732
E    0.060102
N    0.034679
D    0.039601
T    0.055313
L    0.115337
V    0.070498
S    0.071882
Q    0.040436
F    0.050178
G    0.053176
C    0.005429
H    0.025487
I    0.056312
W    0.013593
K    0.057832
M    0.021079
Y    0.043372
R    0.060330
P    0.053632
""".strip().split("\n")))
print(AA_DIST)


def make_random_peptides(num_peptides_per_length=10000, lengths=[9]):
    peptides = []
    for length in lengths:
        peptides.extend(
            random_peptides
                (num_peptides_per_length, length=length, distribution=AA_DIST))
    return EncodableSequences.create(peptides)


def make_motif(allele, peptides, frac=0.01):
    peptides = EncodableSequences.create(peptides)
    predictions = PAN_ALLELE_PREDICTOR_NO_MASS_SPEC.predict(
        peptides=peptides,
        allele=allele,
    )

    random_predictions_df = pandas.DataFrame({"peptide": peptides.sequences})
    random_predictions_df["prediction"] = predictions
    random_predictions_df = random_predictions_df.sort_values(
        "prediction", ascending=True)
    #print("Random peptide predictions", allele)
    #print(random_predictions_df)
    top = random_predictions_df.iloc[:int(len(random_predictions_df) * frac)]
    matrix = positional_frequency_matrix(top.peptide.values)
    #print("Matrix")
    return matrix


def test_synthetic_allele_refinement():
    refine_allele = "HLA-C*01:02"
    alleles = [
        "HLA-A*02:01", "HLA-B*27:01", "HLA-C*07:01",
        "HLA-A*03:01", "HLA-B*15:01", refine_allele
    ]
    peptides_per_allele = [
        2000, 1000, 500,
        1500, 1200, 800,
    ]

    allele_to_peptides = dict(zip(alleles, peptides_per_allele))

    length = 9

    train_with_ms = pandas.read_csv(
        get_path("data_curated", "curated_training_data.with_mass_spec.csv.bz2"))
    train_no_ms = pandas.read_csv(get_path("data_curated",
        "curated_training_data.no_mass_spec.csv.bz2"))

    def filter_df(df):
        df = df.loc[
            (df.allele.isin(alleles)) &
            (df.peptide.str.len() == length)
        ]
        return df

    train_with_ms = filter_df(train_with_ms)
    train_no_ms = filter_df(train_no_ms)

    ms_specific = train_with_ms.loc[
        ~train_with_ms.peptide.isin(train_no_ms.peptide)
    ]

    train_peptides = []
    train_true_alleles = []
    for allele in alleles:
        peptides = ms_specific.loc[ms_specific.allele == allele].peptide.sample(
            n=allele_to_peptides[allele])
        train_peptides.extend(peptides)
        train_true_alleles.extend([allele] * len(peptides))

    hits_df = pandas.DataFrame({"peptide": train_peptides})
    hits_df["true_allele"] = train_true_alleles
    hits_df["hit"] = 1.0

    decoys_df = hits_df.copy()
    decoys_df["peptide"] = decoys_df.peptide.map(scramble_peptide)
    decoys_df["true_allele"] = ""
    decoys_df["hit"] = 0.0

    train_df = pandas.concat([hits_df, decoys_df], ignore_index=True)

Tim O'Donnell's avatar
Tim O'Donnell committed
    predictor = Class1LigandomePredictor(
        PAN_ALLELE_PREDICTOR_NO_MASS_SPEC,
        max_ensemble_size=1,
Tim O'Donnell's avatar
Tim O'Donnell committed
        max_epochs=10,
        learning_rate=0.00001,
        patience=5,
        min_delta=0.0)
Tim O'Donnell's avatar
Tim O'Donnell committed

    allele_encoding = MultipleAlleleEncoding(
        experiment_names=["experiment1"] * len(train_df),
        experiment_to_allele_list={
            "experiment1": alleles,
        },
        max_alleles_per_experiment=6,
        allele_to_sequence=PAN_ALLELE_PREDICTOR_NO_MASS_SPEC.allele_to_sequence,
    ).compact()

    pre_predictions = predictor.predict(
        peptides=train_df.peptide.values,
        allele_encoding=allele_encoding)

    (model,) = PAN_ALLELE_PREDICTOR_NO_MASS_SPEC.class1_pan_allele_models
    expected_pre_predictions = from_ic50(
        model.predict(
            peptides=numpy.repeat(train_df.peptide.values, len(alleles)),
            allele_encoding=allele_encoding.allele_encoding,
    )).reshape((-1, len(alleles)))

    train_df["pre_max_prediction"] = pre_predictions.max(1)
    pre_auc = roc_auc_score(train_df.hit.values, train_df.pre_max_prediction.values)
    print("PRE_AUC", pre_auc)

    #import ipdb ; ipdb.set_trace()

    assert_allclose(pre_predictions, expected_pre_predictions)

Tim O'Donnell's avatar
Tim O'Donnell committed
    motifs_history = []
    random_peptides_encodable = make_random_peptides(10000, [9])
Tim O'Donnell's avatar
Tim O'Donnell committed
    def update_motifs():
        for allele in alleles:
            motif = make_motif(allele, random_peptides_encodable)
            motifs_history.append((allele, motif))
Tim O'Donnell's avatar
Tim O'Donnell committed

Tim O'Donnell's avatar
Tim O'Donnell committed
    metric_rows = []
Tim O'Donnell's avatar
Tim O'Donnell committed
    def progress():
        predictions = predictor.predict(peptides=train_df.peptide.values,
            allele_encoding=allele_encoding, )
Tim O'Donnell's avatar
Tim O'Donnell committed

Tim O'Donnell's avatar
Tim O'Donnell committed
        train_df["max_prediction"] = predictions.max(1)
        train_df["predicted_allele"] = pandas.Series(alleles).loc[
            predictions.argmax(1).flatten()].values
Tim O'Donnell's avatar
Tim O'Donnell committed
        print(predictions)
Tim O'Donnell's avatar
Tim O'Donnell committed

Tim O'Donnell's avatar
Tim O'Donnell committed
        mean_predictions_for_hit = train_df.loc[
            train_df.hit == 1.0
        ].max_prediction.mean()
        mean_predictions_for_decoy = train_df.loc[
            train_df.hit == 0.0
        ].max_prediction.mean()
        correct_allele_fraction = (
                train_df.loc[train_df.hit == 1.0].predicted_allele ==
                train_df.loc[train_df.hit == 1.0].true_allele
        ).mean()
        auc = roc_auc_score(train_df.hit.values, train_df.max_prediction.values)
Tim O'Donnell's avatar
Tim O'Donnell committed

Tim O'Donnell's avatar
Tim O'Donnell committed
        print("Mean prediction for hit", mean_predictions_for_hit)
        print("Mean prediction for decoy", mean_predictions_for_decoy)
        print("Correct predicted allele fraction", correct_allele_fraction)
        print("AUC", auc)
Tim O'Donnell's avatar
Tim O'Donnell committed
        metric_rows.append((
            mean_predictions_for_hit,
            mean_predictions_for_decoy,
            correct_allele_fraction,
            auc,
        ))

        update_motifs()

        return (predictions, auc)

    print("Pre fitting:")
    progress()
    update_motifs()
    print("Fitting...")

    predictor.fit(
        peptides=train_df.peptide.values,
        labels=train_df.hit.values,
        allele_encoding=allele_encoding,
        progress_callback=progress,
    )

    (predictions, final_auc) = progress()
    print("Final AUC", final_auc)

    update_motifs()

    motifs = pandas.DataFrame(
        motifs_history,
        columns=[
            "allele",
            "motif",
Tim O'Donnell's avatar
Tim O'Donnell committed
    metrics = pandas.DataFrame(
        metric_rows,
        columns=[
            "mean_predictions_for_hit",
            "mean_predictions_for_decoy",
            "correct_allele_fraction",
            "auc"
        ])
Tim O'Donnell's avatar
Tim O'Donnell committed
    #import ipdb ; ipdb.set_trace()

    return (predictor, predictions, metrics, motifs)


parser = argparse.ArgumentParser(usage=__doc__)
parser.add_argument(
Tim O'Donnell's avatar
Tim O'Donnell committed
    "--out-metrics-csv",
    default=None,
    help="Metrics output")
parser.add_argument(
    "--out-motifs-pickle",
    default=None,
Tim O'Donnell's avatar
Tim O'Donnell committed
    help="Metrics output")


if __name__ == '__main__':
    # If run directly from python, leave the user in a shell to explore results.
    setup()
    args = parser.parse_args(sys.argv[1:])
Tim O'Donnell's avatar
Tim O'Donnell committed
    (predictor, predictions, metrics, motifs) = test_synthetic_allele_refinement()

    if args.out_metrics_csv:
        metrics.to_csv(args.out_metrics_csv)
    if args.out_motifs_pickle:
        motifs.to_pickle(args.out_motifs_pickle)

    # Leave in ipython
    import ipdb  # pylint: disable=import-error
    ipdb.set_trace()