canalan commited on
Commit
cf31fe3
·
verified ·
1 Parent(s): 71bfe8d

Sync expanded model card from GitHub

Browse files
Files changed (1) hide show
  1. README.md +167 -116
README.md CHANGED
@@ -13,182 +13,233 @@ pipeline_tag: text-classification
13
 
14
  **[Türkçe dokümantasyon](README.tr.md)**
15
 
16
- Multiclass machine learning pipeline for **malware dataset origin classification** on the [SBAN](https://github.com/ma-soreto/SBAN) benchmark: given four synchronized representations of the same sample, predict which source corpus it belongs to among `bodmas`, `dike`, `malwarebazaar`, and `sorel20m`.
17
 
18
- This repository ships **code, notebooks, and the trained model weights**. It does **not** redistribute SBAN data or train/validation/test parquet files. You must acquire SBAN under its own terms and run the pipeline locally.
19
 
20
  | Resource | Location |
21
  |----------|----------|
22
- | Source code | GitHub: [MalwareDatasetClassification](https://github.com/berkecanalan/MalwareDatasetClassification) |
23
- | Model weights | Same repo root + Hugging Face: [MalwareDatasetClassification](https://huggingface.co/canalan/MalwareDatasetClassification) |
24
  | License | Apache-2.0 ([LICENSE](LICENSE)) |
25
 
26
  ---
27
 
28
- ## Problem formulation
29
 
30
- - **Input:** One malware sample described by four text fields (assembly listing, binary hex dump, source code, natural-language description NLD).
31
- - **Output:** One of four dataset labels (which SBAN sub-corpus the sample comes from).
32
- - **Note:** This is **dataset / provenance classification**, not a generic “malware vs benign” detector.
 
 
33
 
34
- Reported metrics on held-out splits (from `inference.ipynb` / Colab run with exported bundle):
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
- | Split | Accuracy | Macro F1 |
37
- |-------|----------|----------|
38
- | Validation | 0.9413 | 0.9097 |
39
- | Test | (run locally with your SBAN test parquet) | |
40
 
41
- Per-class validation F1 (macro average 0.9097): strong on `bodmas` and `sorel20m`; smaller classes `dike` and `malwarebazaar` are harder (typical class imbalance).
 
 
 
 
42
 
43
  ---
44
 
45
- ## Model architecture (production bundle)
46
 
47
- The released artifact `sban_weighted_stacking_model.joblib` is produced in `svc_sban-temiz.ipynb`. High-level flow:
48
 
49
- ```text
50
- For each representation r in {asm, binary, source, nld}:
51
- raw text → TF-IDF (+ binary byte-tokenization for hex)
52
- → numeric stats (char/line/token counts, entropy, …)
53
- → StandardScaler on numeric block
54
- → sparse hstack → feature selection (selected_indices)
55
- → LinearSVC base model → decision_function scores
56
-
57
- Meta layer:
58
- concat(all base decision scores + all scaled numeric blocks)
59
- → HistGradientBoostingClassifier (weighted / tuned per representation)
60
- → argmax → label_encoder inverse → prediction + predict_proba
61
- ```
62
 
63
- The joblib dict (`bundle_version: 1`) includes:
64
 
65
- - `representation_order`, `representation_columns`, `numeric_feature_names`
66
- - `label_encoder`, `meta_model`
67
- - per-representation: `vectorizer`, `numeric_scaler`, `selected_indices`, `base_model`
68
- - metadata: `sklearn_version`, `random_state`, `selected_class_weight_configs`
 
 
 
 
69
 
70
- **Binary representation:** hex lines are parsed into space-separated byte tokens with `instsep` line separators before TF-IDF (see `inference.py`).
71
 
72
- ---
73
 
74
- ## Data schema (inference)
75
 
76
- Parquet or DataFrame rows must include:
 
 
 
 
 
77
 
78
- | Column | Role |
79
- |--------|------|
80
- | `ID` | Sample identifier (optional for inference, kept in output) |
81
- | `dataset_name` | Ground-truth label (only for evaluation) |
82
- | `assembly_code` | Assembly text |
83
- | `binary_code` | Hex dump text |
84
- | `source_code` | Source text |
85
- | `NLD` | Natural language description |
86
 
87
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
- ## Repository layout
90
-
91
- | Path | Purpose |
92
- |------|---------|
93
- | `01_make_a_dataframe.py` | Build unified SBAN parquet from raw JSON layout under `data/` |
94
- | `02_validate_data.py` | Validation checks |
95
- | `03_make_clean_dataframe.py` | Cleaning |
96
- | `04_analyze_prompt_residue.py` | Prompt residue analysis |
97
- | `05_split_dataframe.py` | Train/val/test split |
98
- | `06_make_features.py` | TF-IDF feature export to `features/` (sparse npz) |
99
- | `07_audit_source_code.py` | Source audit |
100
- | `08_clean_source_code.py` | Source cleaning |
101
- | `09_audit_assembly_code.py` | Assembly audit |
102
- | `10_audit_binary_code.py` | Binary audit |
103
- | `11_audit_nld.py` | NLD audit |
104
- | `baseline.ipynb` | Baseline stacking experiments |
105
- | `svc_sban-temiz.ipynb` | Final weighted stacking training + **model export** |
106
- | `inference.ipynb` | Colab/local inference, metrics, **demo row** |
107
- | `inference.py` | Canonical Python inference + CLI |
108
- | `sban_weighted_stacking_model.joblib` | Exported model (~6.4 MB) — add to clone if not using Git LFS |
109
-
110
- Generated paths (`data/`, `*.parquet`, `features/`, `reports/`) are gitignored.
111
 
112
  ---
113
 
114
- ## Installation
115
 
116
- **Inference only** (recommended for downstream use):
117
 
118
- ```bash
119
- python -m venv .venv
120
- source .venv/bin/activate
121
- pip install -r requirements-inference.txt
 
 
 
 
 
 
 
 
122
  ```
123
 
124
- **Full pipeline + notebooks:**
125
 
126
- ```bash
127
- pip install -r requirements.txt
128
- ```
129
 
130
- Use **scikit-learn 1.6.1** (pinned) when loading the bundle to match training.
131
 
132
- ---
 
 
 
 
133
 
134
- ## Inference
135
 
136
- ### Command line
137
 
138
- ```bash
139
- python inference.py \
140
- --model-path sban_weighted_stacking_model.joblib \
141
- --input /path/to/SBAN_val.parquet \
142
- --output predictions.parquet \
143
- --evaluate
144
- ```
145
 
146
- Output columns: all input columns plus `prediction` and `prob_<class>` for each label.
 
 
 
 
 
147
 
148
- ### Python API
149
 
150
- ```python
151
- import pandas as pd
152
- from inference import load_predictor
153
 
154
- predictor = load_predictor("sban_weighted_stacking_model.joblib")
155
- df = pd.read_parquet("/path/to/samples.parquet")
156
- result = predictor.predict(df)
157
- ```
 
 
 
 
 
 
 
 
 
 
158
 
159
- ### Notebook
160
 
161
- Open `inference.ipynb`: run **Colab** or **local** setup, then the demo cell (single synthetic row). Point `VAL_PATH` / `TEST_PATH` to your SBAN splits for full evaluation.
 
 
 
 
 
 
 
 
 
 
 
162
 
163
  ---
164
 
165
- ## Reproducing training
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
- 1. Place SBAN raw files under `data/M1/SBAN-MA-JUN25` (see `01_make_a_dataframe.py`).
168
- 2. Run numbered scripts through `06_make_features.py` as needed for your experiment.
169
- 3. Train and export with `svc_sban-temiz.ipynb` (Colab-oriented; expects parquet splits and writes `sban_weighted_stacking_model.joblib`).
170
 
171
  ---
172
 
173
- ## Hugging Face
174
 
175
- Upload the model repository with:
 
 
 
 
 
 
176
 
177
- - `sban_weighted_stacking_model.joblib`
178
- - `README.md` (this file — YAML front matter is model-card compatible)
179
- - `inference.py`, `requirements-inference.txt`, `LICENSE`
 
 
 
 
180
 
181
- Select **Apache-2.0** as the Hub license to match this repo.
182
 
183
  ---
184
 
185
- ## Citation and data
186
 
187
- - **SBAN dataset:** cite and obtain data from the official SBAN project; this repo does not host samples.
188
- - **This model/code:** if you use the weights or pipeline, cite this repository and respect Apache-2.0.
 
 
189
 
190
  ---
191
 
192
- ## Security note
193
 
194
- `joblib.load` executes pickled objects. Only load `sban_weighted_stacking_model.joblib` from this project or your own trusted export.
 
 
13
 
14
  **[Türkçe dokümantasyon](README.tr.md)**
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
+ ### Split sizes used in training notebook
134
 
135
+ | Split | Rows | bodmas | dike | malwarebazaar | sorel20m |
136
+ |-------|------|--------|------|---------------|----------|
137
+ | Train | 113,168 | 56,892 | 3,267 | 3,594 | 49,415 |
138
+ | Validation | 16,167 | 8,128 | 467 | 513 | 7,059 |
139
+ | Test | 32,334 | 16,255 | 933 | 1,027 | 14,119 |
140
 
141
+ (Test counts from `inference.ipynb` evaluation on exported bundle.)
142
 
143
+ ### Base models on validation (`svc_sban.ipynb`)
144
 
145
+ Single-representation `LinearSVC` decision scores, validation set:
 
 
 
 
 
 
146
 
147
+ | Representation | Accuracy | Macro F1 | Weighted F1 |
148
+ |----------------|----------|----------|---------------|
149
+ | asm | 0.8983 | 0.7099 | 0.8902 |
150
+ | binary | 0.8426 | 0.6607 | 0.8345 |
151
+ | source | 0.9253 | 0.8811 | 0.9251 |
152
+ | nld | 0.6621 | 0.4997 | 0.6539 |
153
 
154
+ **Source** is the strongest single view; **nld** alone is weakest but adds complementary signal in the stack.
155
 
156
+ ### Final exported model — validation & test
 
 
157
 
158
+ Metrics from **`inference.ipynb`** with `sban_weighted_stacking_model.joblib` (matches weighted meta validation in `svc_sban.ipynb` before export).
159
+
160
+ **Validation (n = 16,167)**
161
+
162
+ | | Accuracy | Macro F1 | Weighted F1 |
163
+ |---|----------|----------|-------------|
164
+ | Overall | **0.9413** | **0.9097** | **0.9412** |
165
+
166
+ | Class | Precision | Recall | F1 | Support |
167
+ |-------|-----------|--------|-----|---------|
168
+ | bodmas | 0.9551 | 0.9398 | 0.9474 | 8,128 |
169
+ | dike | 0.9125 | 0.8266 | 0.8674 | 467 |
170
+ | malwarebazaar | 0.8986 | 0.8635 | 0.8807 | 513 |
171
+ | sorel20m | 0.9306 | 0.9562 | 0.9433 | 7,059 |
172
 
173
+ **Test (n = 32,334)**
174
 
175
+ | | Accuracy | Macro F1 | Weighted F1 |
176
+ |---|----------|----------|-------------|
177
+ | Overall | **0.9379** | **0.9012** | **0.9378** |
178
+
179
+ | Class | Precision | Recall | F1 | Support |
180
+ |-------|-----------|--------|-----|---------|
181
+ | bodmas | 0.9532 | 0.9364 | 0.9447 | 16,255 |
182
+ | dike | 0.8909 | 0.8489 | 0.8694 | 933 |
183
+ | malwarebazaar | 0.8885 | 0.8150 | 0.8502 | 1,027 |
184
+ | sorel20m | 0.9272 | 0.9545 | 0.9407 | 14,119 |
185
+
186
+ Minority classes (`dike`, `malwarebazaar`) remain the hardest; weighted class tuning in `svc_sban.ipynb` targets that imbalance.
187
 
188
  ---
189
 
190
+ ## Inference schema
191
+
192
+ | Column | Required for predict | Notes |
193
+ |--------|-------------------|--------|
194
+ | `assembly_code`, `binary_code`, `source_code`, `NLD` | Yes | |
195
+ | `ID` | No | Preserved in output |
196
+ | `dataset_name` | No | For `--evaluate` / notebook metrics |
197
+
198
+ ---
199
+
200
+ ## Installation
201
+
202
+ ```bash
203
+ pip install -r requirements-inference.txt # predict only
204
+ pip install -r requirements.txt # full pipeline + notebooks
205
+ ```
206
 
207
+ Use **scikit-learn 1.6.1** when loading the joblib bundle.
 
 
208
 
209
  ---
210
 
211
+ ## Running inference
212
 
213
+ ```bash
214
+ python inference.py \
215
+ --model-path sban_weighted_stacking_model.joblib \
216
+ --input /path/to/SBAN_test.parquet \
217
+ --output predictions.parquet \
218
+ --evaluate
219
+ ```
220
 
221
+ ```python
222
+ from inference import load_predictor
223
+ import pandas as pd
224
+
225
+ predictor = load_predictor("sban_weighted_stacking_model.joblib")
226
+ out = predictor.predict(pd.read_parquet("/path/to/samples.parquet"))
227
+ ```
228
 
229
+ **Notebook:** `inference.ipynb` Colab or local setup demo row → validation/test cells (update parquet paths).
230
 
231
  ---
232
 
233
+ ## Reproducing the production model
234
 
235
+ 1. Obtain SBAN and build parquets via `01`–`05` (and cleaning/audit scripts as needed).
236
+ 2. Open **`svc_sban.ipynb`** (Colab or local), point to `SBAN_train/val/test.parquet`.
237
+ 3. Run training cells; export **`sban_weighted_stacking_model.joblib`** to the repo root.
238
+ 4. Verify with **`inference.py`** or **`inference.ipynb`**.
239
 
240
  ---
241
 
242
+ ## Citation and security
243
 
244
+ - Cite the **SBAN** dataset authors; this repo does not redistribute their files.
245
+ - **`joblib.load` uses pickle** — only load bundles from this project or your own exports.