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

docs: pruning section + README.tr.md on Hub

Browse files
Files changed (1) hide show
  1. 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.