Instructions to use canalan/MalwareDatasetClassification with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Scikit-learn
How to use canalan/MalwareDatasetClassification with Scikit-learn:
from huggingface_hub import hf_hub_download import joblib model = joblib.load( hf_hub_download("canalan/MalwareDatasetClassification", "sklearn_model.joblib") ) # only load pickle files from sources you trust # read more about it here https://skops.readthedocs.io/en/stable/persistence.html - Notebooks
- Google Colab
- Kaggle
docs: pruning section + README.tr.md on Hub
Browse files- README.tr.md +283 -0
README.tr.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
library_name: sklearn
|
| 4 |
+
tags:
|
| 5 |
+
- malware
|
| 6 |
+
- multi-class-classification
|
| 7 |
+
- stacking
|
| 8 |
+
- sban
|
| 9 |
+
pipeline_tag: text-classification
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# MalwareDatasetClassification (SBAN)
|
| 13 |
+
|
| 14 |
+
**[Türkçe dokümantasyon](https://github.com/berkecanalan/MalwareDatasetClassification/blob/main/README.tr.md)** (Hub’da yalnızca İngilizce `README.md`; Türkçe metin GitHub’da)
|
| 15 |
+
|
| 16 |
+
Multiclass pipeline for **malware dataset origin classification** on [SBAN](https://github.com/ma-soreto/SBAN): four synchronized text views per sample → predict which sub-corpus it belongs to (`bodmas`, `dike`, `malwarebazaar`, `sorel20m`).
|
| 17 |
+
|
| 18 |
+
This repository contains **code, notebooks, and `sban_weighted_stacking_model.joblib`**. **No SBAN parquet or raw JSON** is distributed; obtain SBAN separately.
|
| 19 |
+
|
| 20 |
+
| Resource | Location |
|
| 21 |
+
|----------|----------|
|
| 22 |
+
| Source | [github.com/berkecanalan/MalwareDatasetClassification](https://github.com/berkecanalan/MalwareDatasetClassification) |
|
| 23 |
+
| Weights | Repo root + [huggingface.co/canalan/MalwareDatasetClassification](https://huggingface.co/canalan/MalwareDatasetClassification) |
|
| 24 |
+
| License | Apache-2.0 ([LICENSE](LICENSE)) |
|
| 25 |
+
|
| 26 |
+
---
|
| 27 |
+
|
| 28 |
+
## Task and labels
|
| 29 |
+
|
| 30 |
+
| | |
|
| 31 |
+
|---|---|
|
| 32 |
+
| **Input** | `assembly_code`, `binary_code`, `source_code`, `NLD` for one sample |
|
| 33 |
+
| **Output** | `dataset_name` ∈ {`bodmas`, `dike`, `malwarebazaar`, `sorel20m`} |
|
| 34 |
+
| **Scope** | Dataset **provenance** classification, not generic malware detection |
|
| 35 |
+
|
| 36 |
+
---
|
| 37 |
+
|
| 38 |
+
## Data preparation pipeline (scripts `01`–`11`)
|
| 39 |
+
|
| 40 |
+
End-to-end flow on local SBAN exports:
|
| 41 |
+
|
| 42 |
+
1. **`01_make_a_dataframe.py`** — Merge JSON shards under `data/M1/SBAN-MA-JUN25` into `SBAN.parquet` (four representations aligned by `ID`).
|
| 43 |
+
2. **`02_validate_data.py`** — Schema, missing values, duplicates, cross-dataset `ID` overlap, content fingerprints (see [Data quality](#data-quality-findings)).
|
| 44 |
+
3. **`03_make_clean_dataframe.py`** — Cleaning rules → `SBAN_clean.parquet`.
|
| 45 |
+
4. **`04_analyze_prompt_residue.py`** — Count LLM/prompt boilerplate phrases per representation ([Prompt residue](#prompt-residue-analysis)).
|
| 46 |
+
5. **`05_split_dataframe.py`** — Stratified train / validation / test parquet files.
|
| 47 |
+
6. **`06_make_features.py`** — Optional TF-IDF `.npz` features for alternate experiments.
|
| 48 |
+
7. **`07`–`11`** — Per-representation audits and source cleaning (`08_clean_source_code.py` uses `07_audit_source_code.py`).
|
| 49 |
+
|
| 50 |
+
Notebooks:
|
| 51 |
+
|
| 52 |
+
- **`baseline.ipynb`** — Early fusion / baseline stacking comparisons.
|
| 53 |
+
- **`svc_sban.ipynb`** — **Production model**: per-representation TF-IDF + numeric features, ID-based feature pruning, class-weight search, weighted `LinearSVC` bases, `HistGradientBoostingClassifier` meta learner, **joblib export**.
|
| 54 |
+
- **`inference.ipynb`** — Load exported bundle; validation/test metrics; synthetic **demo** row.
|
| 55 |
+
|
| 56 |
+
Canonical runtime entrypoint: **`inference.py`** (CLI + `StackingPredictor`).
|
| 57 |
+
|
| 58 |
+
---
|
| 59 |
+
|
| 60 |
+
## Data quality findings
|
| 61 |
+
|
| 62 |
+
Summaries below come from running the numbered scripts on the full merged SBAN table (before train/val/test split). Reproduce with your own copy of the data.
|
| 63 |
+
|
| 64 |
+
### Cross-dataset `ID` overlap (content match rate)
|
| 65 |
+
|
| 66 |
+
Shared `ID`s across corpus pairs; percentages = share of common IDs where that column’s text is **byte-identical** (`02_validate_data.py`, section 9).
|
| 67 |
+
|
| 68 |
+
| Pair | Common IDs | assembly | binary | source |
|
| 69 |
+
|------|------------|----------|--------|--------|
|
| 70 |
+
| bodmas × sorel20m | 806 | 72% | 74% | 91% |
|
| 71 |
+
| bodmas × malwarebazaar | 520 | 27% | 25% | 0% |
|
| 72 |
+
| bodmas × dike | 201 | 30% | 24% | 0% |
|
| 73 |
+
| dike × malwarebazaar | 82 | 23% | 22% | 4% |
|
| 74 |
+
| malwarebazaar × sorel20m | 99 | 41% | 47% | 0% |
|
| 75 |
+
| dike × sorel20m | 46 | 44% | 48% | 0% |
|
| 76 |
+
|
| 77 |
+
High overlap for **bodmas × sorel20m** (especially source) motivates careful splitting and explains why the classifier must use subtle cues, not only exact string identity across corpora.
|
| 78 |
+
|
| 79 |
+
### Rows with four aligned representations
|
| 80 |
+
|
| 81 |
+
After merge / alignment (`01_make_a_dataframe.py`):
|
| 82 |
+
|
| 83 |
+
| Dataset | Rows | Matched (4 repr.) |
|
| 84 |
+
|---------|------|-------------------|
|
| 85 |
+
| bodmas | 82,032 | 757 |
|
| 86 |
+
| dike | 5,342 | 669 |
|
| 87 |
+
| malwarebazaar | 6,048 | 905 |
|
| 88 |
+
| sorel20m | 71,319 | 726 |
|
| 89 |
+
|
| 90 |
+
“Matched” = samples where all four representation fields are present for labeling and training.
|
| 91 |
+
|
| 92 |
+
### Prompt residue analysis
|
| 93 |
+
|
| 94 |
+
`04_analyze_prompt_residue.py` scans fixed English phrases (e.g. “your code”, “here”, “additional”) across columns. Illustrative totals on cleaned data:
|
| 95 |
+
|
| 96 |
+
| Phrase | assembly | binary | source | NLD | Total |
|
| 97 |
+
|--------|----------|--------|--------|-----|-------|
|
| 98 |
+
| your code | 1 | 0 | 572 | 1 | 573 |
|
| 99 |
+
| add main function | 0 | 0 | 69 | 0 | 69 |
|
| 100 |
+
| code goes | 0 | 0 | 140 | 0 | 140 |
|
| 101 |
+
| implementation goes | 0 | 0 | 49 | 0 | 49 |
|
| 102 |
+
| corrected | 0 | 0 | 168 | 16 | 169 |
|
| 103 |
+
| here | 128 | 0 | 1,493 | 323 | 1,798 |
|
| 104 |
+
| no comments | 0 | 0 | 53 | 0 | 53 |
|
| 105 |
+
| additional | 37 | 0 | 76 | 1,103 | 1,185 |
|
| 106 |
+
|
| 107 |
+
Most residue sits in **source** and **NLD**; source cleaning scripts (`07`/`08`) target audit failures before modeling.
|
| 108 |
+
|
| 109 |
+
---
|
| 110 |
+
|
| 111 |
+
## Model architecture
|
| 112 |
+
|
| 113 |
+
Artifact: **`sban_weighted_stacking_model.joblib`** (`bundle_version: 1`, trained with **scikit-learn 1.6.1**).
|
| 114 |
+
|
| 115 |
+
```text
|
| 116 |
+
For each r ∈ {asm, binary, source, nld}:
|
| 117 |
+
text → TF-IDF (binary: hex → byte tokens + instsep)
|
| 118 |
+
+ 6 numeric stats (length, tokens, entropy, …)
|
| 119 |
+
→ StandardScaler
|
| 120 |
+
→ sparse hstack → column subset (selected_indices from ID pruning)
|
| 121 |
+
→ LinearSVC (tuned class weights) → decision_function (4 scores)
|
| 122 |
+
|
| 123 |
+
Meta:
|
| 124 |
+
hstack(all base decision scores + all scaled numeric blocks)
|
| 125 |
+
→ HistGradientBoostingClassifier
|
| 126 |
+
→ class probabilities
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
Bundle keys: `representation_order`, `representation_columns`, `numeric_feature_names`, `label_encoder`, `meta_model`, `representations` (vectorizer, scaler, indices, base model), `selected_class_weight_configs`, metadata.
|
| 130 |
+
|
| 131 |
+
Training details and ablations: **`svc_sban.ipynb`**.
|
| 132 |
+
|
| 133 |
+
### Feature pruning (TF-IDF columns)
|
| 134 |
+
|
| 135 |
+
Implemented in **`svc_sban.ipynb`** (cells after the first per-representation `LinearSVC` bases):
|
| 136 |
+
|
| 137 |
+
1. **Importance** — For each representation, mean `|coef_|` over classes from `final_base_models` (TF-IDF tokens + six numeric stats).
|
| 138 |
+
2. **Sort ascending** — Lowest-importance names are dropped first.
|
| 139 |
+
3. **Ratio sweep** — Validation macro-F1 was plotted for many removal ratios (roughly **5–60%** and **65–80%** in the analysis figures); the exported model uses a single setting.
|
| 140 |
+
4. **Production choice** — **`feature_pruning_ratio = 0.65`**: remove the lowest **65%** of the ranked feature list for TF-IDF vocabulary entries. The six numeric columns (`char_count`, `line_count`, `token_count`, `avg_line_length`, `unique_token_ratio`, `char_entropy`) are **always kept** and re-appended via fixed column indices after TF-IDF subsetting.
|
| 141 |
+
|
| 142 |
+
Validation macro-F1 at **65%** feature removal (same notebook run):
|
| 143 |
+
|
| 144 |
+
| Representation | Macro F1 (val) | Columns after prune |
|
| 145 |
+
|----------------|----------------|---------------------|
|
| 146 |
+
| asm | 0.7009 | 26,259 |
|
| 147 |
+
| binary | 0.6609 | 26,259 |
|
| 148 |
+
| nld | 0.4870 | 26,259 |
|
| 149 |
+
| source | 0.8802 | 26,257 |
|
| 150 |
+
|
| 151 |
+
These pruned column sets are stored in the joblib bundle as `representations[r]["selected_indices"]` (feature step only; ID pruning below may reuse the same index vector).
|
| 152 |
+
|
| 153 |
+
### ID pruning (training samples)
|
| 154 |
+
|
| 155 |
+
Overlapping **bodmas** vs **sorel20m** IDs motivate dropping ambiguous training rows before refitting bases:
|
| 156 |
+
|
| 157 |
+
1. Fix **feature pruning at 65%** and fit a temporary `LinearSVC` on pruned features.
|
| 158 |
+
2. **Score** each training row in **`bodmas`** and **`sorel20m`** only: sparse TF-IDF presence (binary) dotted with pruned-model TF-IDF coefficient magnitudes → `importance_score`.
|
| 159 |
+
3. **Grid** — For each representation, remove the lowest-scoring **`id_prune_ratios`** fraction **per class** (5%, 10%, …, 70%), refit on remaining train rows, measure validation macro-F1 → `id_pruning_summary` in the notebook.
|
| 160 |
+
4. **Production choice** — `selected_id_prune_ratios`:
|
| 161 |
+
|
| 162 |
+
| Representation | ID remove ratio | Val macro F1 | Train rows kept | Removed bodmas / sorel20m |
|
| 163 |
+
|----------------|-----------------|--------------|-----------------|---------------------------|
|
| 164 |
+
| asm | **10%** | 0.6978 | 102,538 | 5,689 / 4,941 |
|
| 165 |
+
| binary | **5%** | 0.6625 | 107,854 | 2,844 / 2,470 |
|
| 166 |
+
| source | **40%** | 0.8712 | 70,646 | 22,756 / 19,766 |
|
| 167 |
+
| nld | **40%** | 0.4804 | 70,646 | 22,756 / 19,766 |
|
| 168 |
+
|
| 169 |
+
Final stacking retrains ID-pruned bases (5-fold OOF decision scores), then class-weight search and meta learner on top of that pipeline. **`dike`** and **`malwarebazaar`** rows are never removed by this step.
|
| 170 |
+
|
| 171 |
+
### Split sizes used in training notebook
|
| 172 |
+
|
| 173 |
+
| Split | Rows | bodmas | dike | malwarebazaar | sorel20m |
|
| 174 |
+
|-------|------|--------|------|---------------|----------|
|
| 175 |
+
| Train | 113,168 | 56,892 | 3,267 | 3,594 | 49,415 |
|
| 176 |
+
| Validation | 16,167 | 8,128 | 467 | 513 | 7,059 |
|
| 177 |
+
| Test | 32,334 | 16,255 | 933 | 1,027 | 14,119 |
|
| 178 |
+
|
| 179 |
+
(Test counts from `inference.ipynb` evaluation on exported bundle.)
|
| 180 |
+
|
| 181 |
+
### Base models on validation (`svc_sban.ipynb`)
|
| 182 |
+
|
| 183 |
+
Single-representation `LinearSVC` decision scores, validation set:
|
| 184 |
+
|
| 185 |
+
| Representation | Accuracy | Macro F1 | Weighted F1 |
|
| 186 |
+
|----------------|----------|----------|---------------|
|
| 187 |
+
| asm | 0.8983 | 0.7099 | 0.8902 |
|
| 188 |
+
| binary | 0.8426 | 0.6607 | 0.8345 |
|
| 189 |
+
| source | 0.9253 | 0.8811 | 0.9251 |
|
| 190 |
+
| nld | 0.6621 | 0.4997 | 0.6539 |
|
| 191 |
+
|
| 192 |
+
**Source** is the strongest single view; **nld** alone is weakest but adds complementary signal in the stack.
|
| 193 |
+
|
| 194 |
+
### Final exported model — validation & test
|
| 195 |
+
|
| 196 |
+
Metrics from **`inference.ipynb`** with `sban_weighted_stacking_model.joblib` (matches weighted meta validation in `svc_sban.ipynb` before export).
|
| 197 |
+
|
| 198 |
+
**Validation (n = 16,167)**
|
| 199 |
+
|
| 200 |
+
| | Accuracy | Macro F1 | Weighted F1 |
|
| 201 |
+
|---|----------|----------|-------------|
|
| 202 |
+
| Overall | **0.9413** | **0.9097** | **0.9412** |
|
| 203 |
+
|
| 204 |
+
| Class | Precision | Recall | F1 | Support |
|
| 205 |
+
|-------|-----------|--------|-----|---------|
|
| 206 |
+
| bodmas | 0.9551 | 0.9398 | 0.9474 | 8,128 |
|
| 207 |
+
| dike | 0.9125 | 0.8266 | 0.8674 | 467 |
|
| 208 |
+
| malwarebazaar | 0.8986 | 0.8635 | 0.8807 | 513 |
|
| 209 |
+
| sorel20m | 0.9306 | 0.9562 | 0.9433 | 7,059 |
|
| 210 |
+
|
| 211 |
+
**Test (n = 32,334)**
|
| 212 |
+
|
| 213 |
+
| | Accuracy | Macro F1 | Weighted F1 |
|
| 214 |
+
|---|----------|----------|-------------|
|
| 215 |
+
| Overall | **0.9379** | **0.9012** | **0.9378** |
|
| 216 |
+
|
| 217 |
+
| Class | Precision | Recall | F1 | Support |
|
| 218 |
+
|-------|-----------|--------|-----|---------|
|
| 219 |
+
| bodmas | 0.9532 | 0.9364 | 0.9447 | 16,255 |
|
| 220 |
+
| dike | 0.8909 | 0.8489 | 0.8694 | 933 |
|
| 221 |
+
| malwarebazaar | 0.8885 | 0.8150 | 0.8502 | 1,027 |
|
| 222 |
+
| sorel20m | 0.9272 | 0.9545 | 0.9407 | 14,119 |
|
| 223 |
+
|
| 224 |
+
Minority classes (`dike`, `malwarebazaar`) remain the hardest; weighted class tuning in `svc_sban.ipynb` targets that imbalance.
|
| 225 |
+
|
| 226 |
+
---
|
| 227 |
+
|
| 228 |
+
## Inference schema
|
| 229 |
+
|
| 230 |
+
| Column | Required for predict | Notes |
|
| 231 |
+
|--------|-------------------|--------|
|
| 232 |
+
| `assembly_code`, `binary_code`, `source_code`, `NLD` | Yes | |
|
| 233 |
+
| `ID` | No | Preserved in output |
|
| 234 |
+
| `dataset_name` | No | For `--evaluate` / notebook metrics |
|
| 235 |
+
|
| 236 |
+
---
|
| 237 |
+
|
| 238 |
+
## Installation
|
| 239 |
+
|
| 240 |
+
```bash
|
| 241 |
+
pip install -r requirements-inference.txt # predict only
|
| 242 |
+
pip install -r requirements.txt # full pipeline + notebooks
|
| 243 |
+
```
|
| 244 |
+
|
| 245 |
+
Use **scikit-learn 1.6.1** when loading the joblib bundle.
|
| 246 |
+
|
| 247 |
+
---
|
| 248 |
+
|
| 249 |
+
## Running inference
|
| 250 |
+
|
| 251 |
+
```bash
|
| 252 |
+
python inference.py \
|
| 253 |
+
--model-path sban_weighted_stacking_model.joblib \
|
| 254 |
+
--input /path/to/SBAN_test.parquet \
|
| 255 |
+
--output predictions.parquet \
|
| 256 |
+
--evaluate
|
| 257 |
+
```
|
| 258 |
+
|
| 259 |
+
```python
|
| 260 |
+
from inference import load_predictor
|
| 261 |
+
import pandas as pd
|
| 262 |
+
|
| 263 |
+
predictor = load_predictor("sban_weighted_stacking_model.joblib")
|
| 264 |
+
out = predictor.predict(pd.read_parquet("/path/to/samples.parquet"))
|
| 265 |
+
```
|
| 266 |
+
|
| 267 |
+
**Notebook:** `inference.ipynb` — Colab or local setup → demo row → validation/test cells (update parquet paths).
|
| 268 |
+
|
| 269 |
+
---
|
| 270 |
+
|
| 271 |
+
## Reproducing the production model
|
| 272 |
+
|
| 273 |
+
1. Obtain SBAN and build parquets via `01`–`05` (and cleaning/audit scripts as needed).
|
| 274 |
+
2. Open **`svc_sban.ipynb`** (Colab or local), point to `SBAN_train/val/test.parquet`.
|
| 275 |
+
3. Run training cells; export **`sban_weighted_stacking_model.joblib`** to the repo root.
|
| 276 |
+
4. Verify with **`inference.py`** or **`inference.ipynb`**.
|
| 277 |
+
|
| 278 |
+
---
|
| 279 |
+
|
| 280 |
+
## Citation and security
|
| 281 |
+
|
| 282 |
+
- Cite the **SBAN** dataset authors; this repo does not redistribute their files.
|
| 283 |
+
- **`joblib.load` uses pickle** — only load bundles from this project or your own exports.
|