adverSCarial, generate and analyze the vulnerability of scRNA-seq classifiers to adversarial attacks

Ghislain FIEVET ghislain.fievet@gmail.com

Introduction

adverSCarial is a package for generating and evaluating vulnerability to adversarial attacks on single-cell RNA sequencing classifiers. Single cell analysis are on the way to be used in clinical routine. As a critical use, algorithms must address the challenge of ensuring reliability, one concern being the susceptibility to adversarial attacks.

Zhang, J., Wang, W., Huang, J., Wang, X., & Zeng, Y. (2020). How far is single-cell sequencing from clinical application?. Clinical and translational medicine, 10(3), e117. https://doi.org/10.1002/ctm2.117

de Hond, A.A.H., Leeuwenberg, A.M., Hooft, L. et al. Guidelines and quality criteria for artificial intelligence-based prediction models in healthcare: a scoping review. npj Digit. Med. 5, 2 (2022). https://doi.org/10.1038/s41746-021-00549-7

The package is designed to generate and analyze the vulnerability of scRNA-seq classifiers to adversarial attacks. The package is versatile and provides a format for integrating any type of classifier. It offers functions for studying and generating two types of attacks, single gene attack and max change attack. The single gene attack involves making a small modification to the input to alter the classification. This is an issue when the change of classification is made on a gene that is not known to be biologicaly involved in the change of this cell type. The max change attack involves making a large modification to the input without changing its classification. This is an issue when a significant percentage of genes, sometimes as high as 99%, can be modified with the classifier still predicting the same cell type.

Installation

## Install BiocManager is necessary
if (!require("BiocManager")) {
    install.packages("BiocManager")
}
BiocManager::install('adverSCarial')

Generate an adversarial attack

There are two types of adversarial attacks: single gene attack which modifies slightly the input in order to alter the classification, and max change attack which introduce the largest possible perturbations to the input while still keeping the same classification.

Load libraries

library(adverSCarial)
library(LoomExperiment)

Load a pbmc raw data

pbmcPath <- system.file("extdata", "pbmc_short.loom", package="adverSCarial")
lfile <- import(pbmcPath, type="SingleCellLoomExperiment")
matPbmc <- counts(lfile)
matPbmc[1:5, 1:5]
## <5 x 5> DelayedMatrix object of type "integer":
##      NEK11 IL5RA OR4C5 KHDRBS3 OPA3
## [1,]     0     0     0       0    0
## [2,]     0     0     0       0    0
## [3,]     0     0     0       0    0
## [4,]     0     0     0       0    0
## [5,]     0     0     0       0    0

and its cell type classification based on Seurat documentation

cellTypes <- rowData(lfile)$cell_type
head(cellTypes)
## [1] "Naive CD4 T"  "Naive CD4 T"  "FCGR3A+ Mono" "Naive CD4 T"  "B"           
## [6] "Naive CD4 T"

The package contains a marker based classifier working on the pbmc3k dataset: MClassifier. We verify it is classifying properly according to the cellTypes.

for ( cell_type in unique(cellTypes)){
    resClassif <- MClassifier(matPbmc, cellTypes, cell_type)
    print(paste0("Expected: ", cell_type, ", predicted: ", resClassif[1]))
}
## [1] "Expected: Naive CD4 T, predicted: Naive CD4 T"
## [1] "Expected: FCGR3A+ Mono, predicted: FCGR3A+ Mono"
## [1] "Expected: B, predicted: B"
## [1] "Expected: Memory CD4 T, predicted: Memory CD4 T"
## [1] "Expected: CD14+ Mono, predicted: CD14+ Mono"
## [1] "Expected: CD8 T, predicted: CD8 T"
## [1] "Expected: UNDETERMINED, predicted: UNDETERMINED"
## [1] "Expected: NK, predicted: NK"
## [1] "Expected: Platelet, predicted: Platelet"
## [1] "Expected: DC, predicted: DC"

We want to run attacks on the “DC” cluster but without modifying the genes used by human to make manual classification. We can find this list on Seurat documentation

# Known markers for each cell type
markers <- c("IL7R", "CCR7", "CD14", "LYZ", "S100A4", "MS4A1", "CD8A", "FCGR3A", "MS4A7",
              "GNLY", "NKG7", "FCER1A", "CST3", "PPBP")

Single Gene attack

Modify the value of a gene inside a cell cluster in order to change its classification.

The function advSingleGene runs a dichotomic search of one gene attacks, given the cluster to attack and a type of modification. We suggest two main modifications: perc1 replacing the value of the gene by its first percentile, and perc99 replacing the value of the gene by its 99th percentile. The adv_fct argument allows users to choose custom modifications of the gene.

The excl_genes argument allows users to exclude certain genes from being modified. Here we exclude the genes usually used as markers of specific cell types, as defined previously.

Computation times can be lengthy, we use the return_first_found argument to return the result as soon as it is found.

We can specify the change_type = "not_na" argument to indicate that we want the attack to misclassify the cluster as an existing cell type, rather than as NA.

genesMinChange <- advSingleGene(matPbmc, cellTypes, "DC",
                        MClassifier, exclGenes = markers, advMethod = "perc99",
                        returnFirstFound = TRUE, changeType = "not_na",
                        firstDichot = 10)
genesMinChange
## $GZMB
## [1] "NK" "1"

The function found that modifying the single gene GZMB with the perc99 modification on the cluster leads to a new classification, NK.

Let’s run this attack and verify if it is successful.

First we modify the matPbmc matrix on the target cluster.

matAdver <- advModifications(matPbmc, names(genesMinChange@values)[1], cellTypes, "DC")

Then classify the “DC” cluster with MClassifier.

resClassif <- MClassifier(matAdver, cellTypes, "DC")
resClassif
## [1] "NK" "1"

Max change attack

Modify the maximum number of genes inside a cell cluster without altering its classification.

The function advMaxChange runs a dichotomic search of gene subsets, given the cluster to attack and a type of modification, such that the classification does not change.

The maxSplitSize argument is the maximum size of dichotomic slices. Set to 1 to have better results, but it will take longer to compute.

genesMaxChange <- advMaxChange(matPbmc, cellTypes, "Memory CD4 T", MClassifier,
                    exclGenes = markers, advMethod = "perc99")
length(genesMaxChange@values)
## [1] 186

The function found 186 genes that you can modify with the perc99 modification, and the cluster is still classified as Memory CD4 T.

Let’s run this attack and verify if it is successful.

First we modify the matPbmc matrix on the target cluster, on the genes previously determined.

matMaxAdver <- advModifications(matPbmc, genesMaxChange@values, cellTypes, "Memory CD4 T")

Then we verify that classification is still Memory CD4 T.

resClassif <- MClassifier(matMaxAdver, cellTypes, "Memory CD4 T")
resClassif
## [1] "Memory CD4 T" "1"
sessionInfo()
## R version 4.4.0 RC (2024-04-16 r86468)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 22.04.4 LTS
## 
## Matrix products: default
## BLAS:   /home/biocbuild/bbs-3.20-bioc/R/lib/libRblas.so 
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.10.0
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=en_GB              LC_COLLATE=C              
##  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
##  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
##  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
## 
## time zone: America/New_York
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] stats4    stats     graphics  grDevices utils     datasets  methods  
## [8] base     
## 
## other attached packages:
##  [1] LoomExperiment_1.23.0       BiocIO_1.15.0              
##  [3] rhdf5_2.49.0                SingleCellExperiment_1.27.0
##  [5] SummarizedExperiment_1.35.0 Biobase_2.65.0             
##  [7] GenomicRanges_1.57.0        GenomeInfoDb_1.41.0        
##  [9] IRanges_2.39.0              MatrixGenerics_1.17.0      
## [11] matrixStats_1.3.0           S4Vectors_0.43.0           
## [13] BiocGenerics_0.51.0         adverSCarial_1.3.0         
## 
## loaded via a namespace (and not attached):
##  [1] Matrix_1.7-0            jsonlite_1.8.8          compiler_4.4.0         
##  [4] crayon_1.5.2            stringr_1.5.1           rhdf5filters_1.17.0    
##  [7] lattice_0.22-6          R6_2.5.1                XVector_0.45.0         
## [10] S4Arrays_1.5.0          knitr_1.46              DelayedArray_0.31.0    
## [13] GenomeInfoDbData_1.2.12 rlang_1.1.3             stringi_1.8.3          
## [16] HDF5Array_1.33.0        xfun_0.43               cli_3.6.2              
## [19] SparseArray_1.5.0       magrittr_2.0.3          Rhdf5lib_1.27.0        
## [22] zlibbioc_1.51.0         grid_4.4.0              lifecycle_1.0.4        
## [25] glue_1.7.0              evaluate_0.23           abind_1.4-5            
## [28] httr_1.4.7              tools_4.4.0             UCSC.utils_1.1.0