Przez ostatnie 4 miesiące pracowaliśmy wraz z Michaliną nad projektem na konkurs Kaggle CAFA 5 Protein Function Prediction. Było to nasze pierwsze uczestnictwo w konkursie na platformie Kaggle, ale udało nam się ostatecznie zająć 37 miejsce, na 1675 zespołów.
Czego dotyczył konkurs?
https://www.kaggle.com/competitions/cafa-5-protein-function-prediction
W ramach konkursu mieliśmy przewidzieć funkcje (GO) dla określonych białek, wykorzystując jedynie sekwencje aminokwasowe oraz kilka kluczowych informacji, takich jak Taxon ID, subontologia (BP, CCO, MF) i nazwa źródłowego organizmu. To zadanie określiliśmy jako klasyfikację wieloetykietową, ponieważ jednemu białku można przypisać wiele różnych funkcji. Istniało pewne ograniczenie, polegające na tym, że dla jednego białka można było przypisać maksymalnie 500 funkcji w danej subontologii.
Feature Extraction
Po dogłębnym zapoznaniu się z informacjami umieszczonymi w opisie konkursu i danymi, przeszliśmy do wyekstrahowania cech z sekwencji aminokwasowej. Zaczęliśmy od bardzo prostych metod takich jak liczenie długości sekwencji aminokwasowej i zliczanie poszczególnych aminokwasów występujących w łańcuchu. Dodaliśmy kilka fizykochemicznych właściwości liczonych przy użyciu biblioteki BioPython. Następnie użyliśmy publicznych zbiorów danych, które korzystając z modeli T5 / ESM-2 przekształcają sekwencje aminokwasów w osadzenia wektorowe (vector embedding). Do tego dodaliśmy hierarchiczną klasyfikację gatunków z bazy NCBI (National Center for Biotechnology Information) Taxonomy, dzięki temu, na podstawie taxonId każdemu organizmowi przepisaliśmy: królestwo, gromadę, klasę, rząd i gatunek. To wszystko razem połączone stanowiło nasze predyktory.
Modele
Z uwagi na sprzętowe ograniczenia nasz cały dataset zdecydowaliśmy się podzielić. Długo zastanawialiśmy się jak właściwie powinniśmy podzielić nasz zbiór. Próbowaliśmy użyć klastrowania przy pomocy KMeans, dzielić zbiór według długości sekwencji aminokwasów, według superkrólestwa i subontologii, wybieraliśmy tylko białka, które przypisane były do najczęściej występujących top1500 go_id (przy czym liczbę top zmienialiśmy w zakresie 500 – 3000).
Jeżeli chodzi o same modele to zaczynaliśmy od podstaw Machine Learningu, czyli Random Foresta, XGBoosta, lecz szybko doszliśmy do wniosku, że to sieci neuronowe będą dawały lepsze rezultaty. Skupiliśmy się na prostych sieciach pisanych w PyTorchu i Tensorflow. Wypróbowaliśmy sieć CNN i.. koniec końców to połączenie CNN z klasycznymi sieciami gęstymi napisanymi w Tensorflow dało nam najlepsze wyniki.
Sieci CNN
Sieci konwolucyjne (CNN) – znane głównie z zastosowań w dziedzinie Computer Vision (CV) – okazały się niezwykle skuteczne również w analizie sekwencji białek. CNN są znane z ich zdolności do ekstrakcji hierarchicznych cech z obrazów. Podobnie jak w przypadku obrazów, sekwencje aminokwasów również mają pewną hierarchię cech. Na przykład, kilka kolejnych aminokwasów może tworzyć motyw lub domenę białka. CNN są w stanie wykryć te cechy na różnych poziomach abstrakcji, co pozwala na identyfikację istotnych obszarów w sekwencji białka. CNN wykazują inwariantność translacji, co oznacza, że mogą rozpoznawać cechy niezależnie od ich położenia w sekwencji. To jest ważne w przypadku analizy sekwencji białek, gdzie te same cechy występują czasem w różnych miejscach.
Architektura sieci została zaczerpnięta z ogólnodostępnych konkursowych notebooków. Wprowadzona zmiana dotyczyła podziału całego datasetu na trzy mniejsze ze względu na subontologię oraz powiększeniu targetu z 500 go_id na 1500. Każdy z trzech zbiorów został przepuszczony przez sieć, a otrzymane predykcje złączone w jedno.
Wrażenia
Choć nutka żalu wkradła się, gdy nie przekroczyliśmy progu scoru 0.6, to patrząc na całą naszą drogę od początku do końca, na nasz progres, zdobytą wiedzę, doświadczenie, jesteśmy zadowoleni i dumni z zajmowanego obecnie 37 miejsca (na 1675). Ten projekt pokazał, że czas, wytrwałość i konsekwencja są kluczowe do osiągnięcia sukcesu.
Wpis na linkedin: klik
Kod python:
Format FASTA jest powszechnie używany do przechowywania sekwencji biologicznych, takich jak sekwencje białek lub nukleotydów. Funkcja ta używa biblioteki SeqIO do analizy pliku FASTA i zbiera informacje takie jak identyfikator UniProt, opis, sekwencja i dodatkowe metadane z każdej sekwencji FASTA zapisanej w pliku. Następnie zwraca te dane jako ramkę danych pandas, gdzie każda kolumna odpowiada innemu rodzajowi informacji o sekwencjach biologicznych zawartych w pliku FASTA.
NCBI (National Center for Biotechnology Information) Taxonomy to baza danych, która hierarchicznie klasyfikuje organizmy na różnych poziomach taksonomicznych, takich jak królestwo, rodzina, gatunek, itp. Jest ona szeroko wykorzystywana w badaniach biologicznych, do analizy ewolucji i filogenezy organizmów.
Przygotowanie danych:
from Bio import SeqIO from Bio.SeqUtils.ProtParam import ProteinAnalysis def read_fasta(fastaPath): fasta_sequences = SeqIO.parse(open(fastaPath), 'fasta') columns = defaultdict(list) for fasta in fasta_sequences: columns['UniProt ID'].append(fasta.id) columns['description'].append(fasta.description) columns['seq'].append(str(fasta.seq)) for key, value in fasta.annotations.items(): columns[key].append(value) return pd.DataFrame(columns) ############################################################################# # Wczytanie danych: regex = r'OS=(.+?(?=\sOX=|\sGN=|\sPE=|\sSV=|$))' train = pd.read_csv("/kaggle/input/cafa-5-protein-function-prediction/Train/train_terms.tsv", sep="\t", header=None, names=['UniProt ID', 'go_id', 'ontology'], skiprows=1)\ .assign(GO=lambda x: x['go_id'].str.extract(r':(\d+)').astype(int))\ .merge(read_fasta("/kaggle/input/cafa-5-protein-function-prediction/Train/train_sequences.fasta")\ .assign(OS = lambda x: x['description']\ .str\ .extract(regex)), how = 'inner', on='UniProt ID',)\ .merge(pd.read_csv("/kaggle/input/cafa-5-protein-function-prediction/Train/train_taxonomy.tsv", sep="\t", header=None, names=['UniProt ID', 'taxon ID'], skiprows=1), how = 'inner', on = 'UniProt ID')\ .assign(seq_length=lambda x: x["seq"].apply(len))\ .sort_values('UniProt ID')\ .drop(columns = {'GO','description','OS', 'seq_length'}) train_embedding = pd.DataFrame(np.load('/kaggle/input/cafa-5-ems-2-embeddings-numpy/train_embeddings.npy'), index = np.load('/kaggle/input/cafa-5-ems-2-embeddings-numpy/train_ids.npy'))\ .reset_index()\ .rename(columns={'index': 'UniProt ID'})\ .sort_values('UniProt ID') train_physical = pd.read_csv('/kaggle/input/cafa5-features/train_features.csv', sep = ',') test = ( read_fasta("/kaggle/input/cafa-5-protein-function-prediction/Test (Targets)/testsuperset.fasta") .assign(taxon_ID=lambda x: x['description'].str.split('\t').str[1]) .assign(seq_length=lambda x: x["seq"].apply(len)) .drop('description', axis=1) .astype({'taxon_ID': int}) .merge( pd.read_csv("/kaggle/input/cafa-5-protein-function-prediction/Test (Targets)/testsuperset-taxon-list.tsv", sep="\t", header=None, encoding='unicode_escape', names=['taxon_ID', 'OS'], skiprows=1), how='inner', on='taxon_ID' ) ) test_embedding = pd.DataFrame(np.load('/kaggle/input/cafa-5-ems-2-embeddings-numpy/test_embeddings.npy'), index = np.load('/kaggle/input/cafa-5-ems-2-embeddings-numpy/test_ids.npy'))\ .reset_index()\ .rename(columns={'index': 'UniProt ID'})\ .sort_values('UniProt ID') test_physical = pd.read_csv('/kaggle/input/cafa5-features/test_features.csv', sep = ',') ############################################################################# # Predyktory z bazy NCBI: from ete3 import NCBITaxa ncbi = NCBITaxa() taxids = list(train['taxon ID'].unique()) taxid_kingdom_dict = {} for taxid in taxids: try: lineage = ncbi.get_lineage(taxid) lineage_dict = ncbi.get_rank(lineage) taxonomy_dict = {rank: ncbi.get_taxid_translator([id])[id] for id, rank in lineage_dict.items()} taxid_kingdom_dict[taxid] = taxonomy_dict except: taxid_kingdom_dict[taxid] = {'Unknown': 'Unknown'} train_taxon = pd.DataFrame.from_dict(taxid_kingdom_dict, orient='index')[['superkingdom', 'family', 'genus', 'species', 'phylum', 'class', 'order', 'clade']]\ .fillna('Unknown')\ .reset_index()\ .rename(columns = {'index':'taxon ID'}) taxids = list(test['taxon_ID'].unique()) taxid_kingdom_dict = {} for taxid in taxids: try: lineage = ncbi.get_lineage(taxid) lineage_dict = ncbi.get_rank(lineage) taxonomy_dict = {rank: ncbi.get_taxid_translator([id])[id] for id, rank in lineage_dict.items()} taxid_kingdom_dict[taxid] = taxonomy_dict except: taxid_kingdom_dict[taxid] = {'Unknown': 'Unknown'} test_taxon = pd.DataFrame.from_dict(taxid_kingdom_dict, orient='index')[['superkingdom', 'family', 'genus', 'species', 'phylum', 'class', 'order', 'clade']]\ .fillna('Unknown')\ .reset_index()\ .rename(columns = {'index':'taxon_ID'}) ############################################################################# # Połączenie utworzonych predyktorów w jeden zbiór: train = train\ .merge(train_taxon, how = 'left', on = 'taxon ID') for col in ['superkingdom', 'family', 'genus', 'species', 'phylum', 'class', 'order', 'clade']: temp_df = train.groupby(col)['go_id'].nunique().reset_index() temp_df.rename(columns = {'go_id': f'{col}_count'}, inplace = True) train = pd.merge(train, temp_df, on = col, how = 'left') train = train\ .drop_duplicates(subset = 'UniProt ID')\ .merge(train_physical, how = 'left', on = 'UniProt ID')\ .merge(train_embedding, how = 'left', on = 'UniProt ID') test = test\ .merge(test_physical, how = 'left', on = 'UniProt ID')\ .merge(test_taxon, how = 'left', on = 'taxon_ID')\ .merge(test_embedding, how = 'left', on = 'UniProt ID') for column in ['superkingdom', 'family', 'genus', 'species', 'phylum', 'class', 'order', 'clade']: test = test.merge(train[[column, f'{column}_count']].drop_duplicates(), how = 'left', on = column)\ .fillna(0) ############################################################################# # Zapis przygotowanych zbiorów do formatu parquet: test.columns = test.columns.astype(str) train.columns = train.columns.astype(str) test\ .rename(columns = {"taxon_ID":'taxon ID'})\ .to_parquet(f'test.parquet', compression='gzip') train\ .to_parquet(f'train.parquet', compression='gzip')
Model – sieć gęsta
Funkcja create_binary_matrix przyjmuje zestaw danych w postaci ramki danych dataset, gdzie zawarte są informacje na temat przypisania identyfikatorów UniProt i identyfikatorów Gene Ontology (GO). Następnie tworzy macierz binarną, w której każdy wiersz reprezentuje jeden identyfikator UniProt, a każda kolumna reprezentuje jeden identyfikator GO. Wartości w tej macierzy są typu bool, a ich ustawienie na True oznacza przypisanie danego identyfikatora UniProt do identyfikatora GO.
Plik counts używany jest do puszczania modelu w pętli. Nie trenujemy jednego modelu na wszystkich białkach, bierzemy kolejno:
- 1500 białek z największą liczbą funkcji, trenujemy model i przepuszczamy przez niego zbiór testowy,
- białka o liczbie funkcji 1501-3000 z rangkingu najpopularniejszych, trenujemy model i przepuszczamy przez niego zbiór testowy,
- itd.
def create_binary_matrix(dataset): unique_go_ids = dataset['go_id'].unique() unique_uniprot_ids = dataset['UniProt_ID'].unique() go_id_map = {go_id: i for i, go_id in enumerate(unique_go_ids)} uniprot_id_map = {uniprot_id: i for i, uniprot_id in enumerate(unique_uniprot_ids)} matrix = np.zeros((len(unique_uniprot_ids), len(unique_go_ids)), dtype=bool) for _, row in dataset.iterrows(): uniprot_id = row['UniProt_ID'] go_id = row['go_id'] matrix[uniprot_id_map[uniprot_id], go_id_map[go_id]] = True return pd.DataFrame(matrix, index=unique_uniprot_ids, columns=unique_go_ids)\ .reset_index()\ .rename(columns={'index': 'UniProt_ID'}) counts = pd.read_csv("/kaggle/input/cafa-5-protein-function-prediction/Train/train_terms.tsv", sep="\t", header=None, names=['UniProt ID', 'go_id', 'ontology'], skiprows=1)['go_id'].value_counts().reset_index() columns_to_scale = ['taxon ID', 'superkingdom_count', 'family_count', 'genus_count', 'species_count', 'phylum_count', 'class_count', 'order_count', 'clade_count', 'Eprotein', 'IP/mol weight', 'Hphb', 'helix', 'turn', 'sheet', 'instability_index', 'length', 'aromaticity', 'charge_at_pH'] batch_size = 256 epochs = 30 n = 1500 step = 1500 j = 1500 X_test = pd.read_parquet('/kaggle/input/testextendedparquet/test.parquet')\ .drop(columns = {'superkingdom', 'family', 'genus', 'species', 'phylum', 'class', 'order', 'clade'})\ .set_index('UniProt ID') early_stop = EarlyStopping(monitor='val_loss', patience=10, verbose=1, restore_best_weights=True) for i in range(0, n, step): ############################################################################# # WCZYTANIE I SKALOWANIE DANYCH: segment_start = i segment_end = i + step filtr = list(counts.iloc[segment_start:segment_end]['go_id'].unique()) X = pd.read_parquet('/kaggle/input/trainextendedparquet/train.parquet')\ .sort_values(by='UniProt ID')\ .rename(columns={'UniProt ID': 'UniProt_ID'})\ .set_index('UniProt_ID')\ .drop(columns={'ontology'}) y = create_binary_matrix(pd.read_parquet('/kaggle/input/binarymatrixparquet')\ .rename(columns = {'UniProt ID':'UniProt_ID'}))\ .sort_values(by='UniProt_ID')\ .set_index('UniProt_ID')[filtr] X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.1, random_state=42) X_train_scaled = X_train.copy() X_val_scaled = X_val.copy() scaler = MinMaxScaler() X_train_scaled[columns_to_scale] = scaler.fit_transform(X_train[columns_to_scale]) X_val_scaled[columns_to_scale] = scaler.transform(X_val[columns_to_scale]) ############################################################################# # BUDOWA MODELU: model = Sequential() model.add(BatchNormalization(input_shape=(X_train_scaled.shape[1],))) model.add(Dense(512, activation='relu')) model.add(Dropout(0.15)) model.add(Dense(256, activation='relu')) model.add(Dropout(0.15)) model.add(Dense(256, activation='relu')) model.add(Dropout(0.15)) model.add(Dense(y_train.shape[1], activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=[AUC(curve='PR', multi_label=True)]) print(f"\033[1mModel,\033[0m") print('\n') print(model.summary()) print('\n') ############################################################################# # TRENING: history = model.fit(X_train_scaled, y_train, validation_data=(X_val_scaled, y_val), epochs=epochs, batch_size=batch_size, callbacks=[early_stop]) ############################################################################# #PREDYKCJA I ZAPIS DANYCH: print('\n') print('---------------------------------------------------------------') print('\n\n\n') temp_results = pd.DataFrame(columns=y_train.columns) results = [] X_test = X_test.reset_index().rename(columns = {'UniProt ID':'UniProt_ID'}).set_index('UniProt_ID') X_test_scaled = X_test.copy() unique_ids = X_test.reset_index()['UniProt_ID'].unique() batches = [unique_ids[i:i + 500] for i in range(0, len(unique_ids), 500)] X_test_scaled[columns_to_scale] = scaler.transform(X_test[columns_to_scale]) for batch in tqdm(batches, desc='Processing UniProt ID batches'): X_test_batch = X_test_scaled.loc[X_test_scaled.index.isin(batch)].values y_pred_batch = model.predict(X_test_batch, verbose = None) temp = temp_results.copy() for k, uni in enumerate(batch): y_pred_temp = y_pred_batch[k] temp.loc[uni] = y_pred_temp results.append(temp\ .reset_index()\ .melt(id_vars=['index'], var_name='UniProt ID', value_name='value')\ .query('value >= 0.01')) df = pd.concat(results)\ .rename(columns = {'UniProt ID':'go_id', 'index':'UniProt_ID',})\ .assign(value = lambda x:x['value']\ .round(3))\ .to_csv(f"submission_{j}.tsv",header=False, index=False, sep="\t") j+=1500 break
Model – CNN.
def normalize_data(df, columns_to_normalize): scaler = MinMaxScaler() df[columns_to_normalize] = scaler.fit_transform(df[columns_to_normalize]) return df columns_to_normalize = ['Eprotein', 'IP/mol weight', 'Hphb', 'helix', 'turn', 'sheet', 'instability_index', 'length', 'aromaticity', 'charge_at_pH', 'superkingdom_count', 'family_count', 'genus_count', 'species_count', 'phylum_count', 'class_count', 'order_count', 'clade_count', 'A', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'Y'] train = pd.read_parquet('/kaggle/input/train-v3') train = normalize_data(train, columns_to_normalize) train = train.sort_values('UniProt ID') test = pd.read_parquet('/kaggle/input/test-v3') test.rename(columns={'taxon_ID': 'taxon ID'}, inplace=True) test = normalize_data(test, columns_to_normalize) def prepare_data(temp_train, ontology): temp_train = temp_train[temp_train['ontology'] == ontology] temp_train = temp_train.sort_values(by='UniProt ID') temp_train_uniprot_ids = temp_train['UniProt ID'].tolist() temp_binary_matrix = binary_matrix[binary_matrix['UniProt ID'].isin(temp_train_uniprot_ids)].copy() temp_y = create_binary_matrix(temp_binary_matrix) temp_y = temp_y.sort_values(by='UniProt ID') temp_y.set_index('UniProt ID', inplace=True) num_columns_to_select = 1500 selected_columns = temp_y.sum().sort_values(ascending=False).head(num_columns_to_select).index temp_y = temp_y[selected_columns] columns = ['Eprotein', 'IP/mol weight', 'Hphb', 'helix', 'turn', 'sheet', 'instability_index', 'length', 'aromaticity', 'charge_at_pH', 'superkingdom_count', 'family_count', 'genus_count', 'species_count', 'phylum_count', 'class_count','order_count', 'A', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'Y'] for j in range(0, 1279): columns.append(f'{j}') X = temp_train[columns] y = temp_y return X, y, temp_y class CNN1D(nn.Module): def __init__(self, input_dim, num_classes): super(CNN1D, self).__init__() self.conv1 = nn.Conv1d(in_channels=1, out_channels=3, kernel_size=3, dilation=1, padding=1, stride=1) self.pool1 = nn.MaxPool1d(kernel_size=2, stride=2) self.conv2 = nn.Conv1d(in_channels=3, out_channels=8, kernel_size=3, dilation=1, padding=1, stride=1) self.pool2 = nn.MaxPool1d(kernel_size=2, stride=2) self.fc1 = nn.Linear(in_features=int(8 * input_dim/4), out_features=864) self.fc2 = nn.Linear(in_features=864, out_features=num_classes) def forward(self, x): x = x.reshape(x.shape[0], 1, x.shape[1]) x = self.pool1(nn.functional.tanh(self.conv1(x))) x = self.pool2(nn.functional.tanh(self.conv2(x))) x = torch.flatten(x, 1) x = nn.functional.tanh(self.fc1(x)) x = self.fc2(x) return x def train_cnn1d(X, y, num_epochs=5, batch_size=64, learning_rate=0.001): X = np.array(X, dtype=np.float32) y = np.array(y, dtype=np.float32) X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.1, random_state=42) train_dataset = TensorDataset(torch.tensor(X_train), torch.tensor(y_train)) train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) val_dataset = TensorDataset(torch.tensor(X_val), torch.tensor(y_val)) val_loader = DataLoader(val_dataset, batch_size=batch_size) input_dim = X_train.shape[1] num_classes = y_train.shape[1] model = CNN1D(input_dim=input_dim, num_classes=num_classes) criterion = nn.BCEWithLogitsLoss() optimizer = optim.Adam(model.parameters(), lr=learning_rate) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) for epoch in range(num_epochs): model.train() train_loss = 0.0 for inputs, labels in tqdm(train_loader): inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() train_loss += loss.item() train_loss /= len(train_loader) model.eval() val_loss = 0.0 with torch.no_grad(): for inputs, labels in val_loader: inputs, labels = inputs.to(device), labels.to(device) outputs = model(inputs) loss = criterion(outputs, labels) val_loss += loss.item() val_loss /= len(val_loader) print(f"Epoch [{epoch+1}/{num_epochs}] - Train Loss: {train_loss:.4f} - Val Loss: {val_loss:.4f}") print("Training finished.") return model def make_predictions(model, test_data, columns, temp_y): X_test = test_data[columns].values X_test = X_test.astype(np.float32) model.eval() device = torch.device("cuda" if torch.cuda.is_available() else "cpu") X_test_tensor = torch.tensor(X_test).to(device) with torch.no_grad(): probabilities = torch.sigmoid(model(X_test_tensor)) submission_df = pd.DataFrame(probabilities.cpu().numpy(), columns=temp_y.columns, index=test_data['UniProt ID']) submission_df = submission_df.reset_index().melt(id_vars=['UniProt ID'], var_name='GO Term Id', value_name='Prediction') submission_df['Prediction'] = submission_df['Prediction'].round(3) submission_df = submission_df[submission_df['Prediction'] >= 0.01] return submission_df X, y, temp_y = prepare_data(train, 'MFO') trained_model = train_cnn1d(X, y, num_epochs=5, batch_size=64, learning_rate=0.001) predictions_df_MFO = make_predictions(trained_model, test, columns, temp_y) predictions_df_MFO['UniProt ID'].value_counts().head() predictions_df_MFO.sort_values(by=['UniProt ID', 'Prediction'], ascending=[True, False], inplace = True) predictions_df_MFO = predictions_df_MFO.groupby('UniProt ID').head(500).reset_index(drop=True) X, y, temp_y = prepare_data(train, 'BPO') trained_model = train_cnn1d(X, y, num_epochs=5, batch_size=64, learning_rate=0.001) predictions_df_BPO = make_predictions(trained_model, test, columns, temp_y) predictions_df_BPO['UniProt ID'].value_counts().head() predictions_df_BPO.sort_values(by=['UniProt ID', 'Prediction'], ascending=[True, False], inplace = True) predictions_df_BPO = predictions_df_BPO.groupby('UniProt ID').head(500).reset_index(drop=True) X, y, temp_y = prepare_data(train, 'CCO') trained_model = train_cnn1d(X, y, num_epochs=5, batch_size=64, learning_rate=0.001) predictions_df_CCO = make_predictions(trained_model, test, columns, temp_y) predictions_df_CCO['UniProt ID'].value_counts().head() predictions_df_CCO.sort_values(by=['UniProt ID', 'Prediction'], ascending=[True, False], inplace = True) predictions_df_CCO = predictions_df_CCO.groupby('UniProt ID').head(500).reset_index(drop=True) sub = pd.concat([predictions_df_MFO, predictions_df_BPO, predictions_df_CCO])