Spaces:
Runtime error
Runtime error
Aryan Mishra commited on
Commit ·
119802d
1
Parent(s): 2f9be58
feat: Phase 3 - baseline and XLM-R fine-tuning
Browse files- data/tokenized/absa_cls_dataset/dataset_dict.json +1 -0
- data/tokenized/absa_cls_dataset/test/data-00000-of-00001.arrow +3 -0
- data/tokenized/absa_cls_dataset/test/dataset_info.json +26 -0
- data/tokenized/absa_cls_dataset/test/state.json +13 -0
- data/tokenized/absa_cls_dataset/train/data-00000-of-00001.arrow +3 -0
- data/tokenized/absa_cls_dataset/train/dataset_info.json +26 -0
- data/tokenized/absa_cls_dataset/train/state.json +13 -0
- data/tokenized/absa_cls_dataset/validation/data-00000-of-00001.arrow +3 -0
- data/tokenized/absa_cls_dataset/validation/dataset_info.json +26 -0
- data/tokenized/absa_cls_dataset/validation/state.json +13 -0
- data/tokenized/absa_ner_dataset/dataset_dict.json +1 -0
- data/tokenized/absa_ner_dataset/test/data-00000-of-00001.arrow +3 -0
- data/tokenized/absa_ner_dataset/test/dataset_info.json +29 -0
- data/tokenized/absa_ner_dataset/test/state.json +13 -0
- data/tokenized/absa_ner_dataset/train/data-00000-of-00001.arrow +3 -0
- data/tokenized/absa_ner_dataset/train/dataset_info.json +29 -0
- data/tokenized/absa_ner_dataset/train/state.json +13 -0
- data/tokenized/absa_ner_dataset/validation/data-00000-of-00001.arrow +3 -0
- data/tokenized/absa_ner_dataset/validation/dataset_info.json +29 -0
- data/tokenized/absa_ner_dataset/validation/state.json +13 -0
- mlruns/1/f7e324a252a94812b0d4158371276cb0/artifacts/confusion_matrix.json +28 -0
- notebooks/03_model_comparison.ipynb +101 -0
- notebooks/03_train_colab.ipynb +128 -0
- scripts/generate_notebooks.py +73 -0
- scripts/mlflow_ui.sh +10 -0
- src/data/bio_tagger.py +97 -0
- src/data/hf_dataset.py +156 -0
- src/evaluation/cross_lingual_eval.py +114 -0
- src/training/mlflow_utils.py +73 -0
- tests/test_bio_tagger.py +51 -0
data/tokenized/absa_cls_dataset/dataset_dict.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"splits": ["train", "validation", "test"]}
|
data/tokenized/absa_cls_dataset/test/data-00000-of-00001.arrow
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1eb8d8230357e82431b68c2226770b209f0a61fe34425c0fd037b17f913a028c
|
| 3 |
+
size 101864
|
data/tokenized/absa_cls_dataset/test/dataset_info.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"citation": "",
|
| 3 |
+
"description": "",
|
| 4 |
+
"features": {
|
| 5 |
+
"label": {
|
| 6 |
+
"dtype": "int64",
|
| 7 |
+
"_type": "Value"
|
| 8 |
+
},
|
| 9 |
+
"input_ids": {
|
| 10 |
+
"feature": {
|
| 11 |
+
"dtype": "int32",
|
| 12 |
+
"_type": "Value"
|
| 13 |
+
},
|
| 14 |
+
"_type": "Sequence"
|
| 15 |
+
},
|
| 16 |
+
"attention_mask": {
|
| 17 |
+
"feature": {
|
| 18 |
+
"dtype": "int8",
|
| 19 |
+
"_type": "Value"
|
| 20 |
+
},
|
| 21 |
+
"_type": "Sequence"
|
| 22 |
+
}
|
| 23 |
+
},
|
| 24 |
+
"homepage": "",
|
| 25 |
+
"license": ""
|
| 26 |
+
}
|
data/tokenized/absa_cls_dataset/test/state.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_data_files": [
|
| 3 |
+
{
|
| 4 |
+
"filename": "data-00000-of-00001.arrow"
|
| 5 |
+
}
|
| 6 |
+
],
|
| 7 |
+
"_fingerprint": "b686d9c13fd54a9d",
|
| 8 |
+
"_format_columns": null,
|
| 9 |
+
"_format_kwargs": {},
|
| 10 |
+
"_format_type": null,
|
| 11 |
+
"_output_all_columns": false,
|
| 12 |
+
"_split": null
|
| 13 |
+
}
|
data/tokenized/absa_cls_dataset/train/data-00000-of-00001.arrow
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:74827f37b8915b4946a33e030d5833d109bfcf9d9d847edf54fd830fe33a0a3d
|
| 3 |
+
size 808112
|
data/tokenized/absa_cls_dataset/train/dataset_info.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"citation": "",
|
| 3 |
+
"description": "",
|
| 4 |
+
"features": {
|
| 5 |
+
"label": {
|
| 6 |
+
"dtype": "int64",
|
| 7 |
+
"_type": "Value"
|
| 8 |
+
},
|
| 9 |
+
"input_ids": {
|
| 10 |
+
"feature": {
|
| 11 |
+
"dtype": "int32",
|
| 12 |
+
"_type": "Value"
|
| 13 |
+
},
|
| 14 |
+
"_type": "Sequence"
|
| 15 |
+
},
|
| 16 |
+
"attention_mask": {
|
| 17 |
+
"feature": {
|
| 18 |
+
"dtype": "int8",
|
| 19 |
+
"_type": "Value"
|
| 20 |
+
},
|
| 21 |
+
"_type": "Sequence"
|
| 22 |
+
}
|
| 23 |
+
},
|
| 24 |
+
"homepage": "",
|
| 25 |
+
"license": ""
|
| 26 |
+
}
|
data/tokenized/absa_cls_dataset/train/state.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_data_files": [
|
| 3 |
+
{
|
| 4 |
+
"filename": "data-00000-of-00001.arrow"
|
| 5 |
+
}
|
| 6 |
+
],
|
| 7 |
+
"_fingerprint": "59c1808a1a0be248",
|
| 8 |
+
"_format_columns": null,
|
| 9 |
+
"_format_kwargs": {},
|
| 10 |
+
"_format_type": null,
|
| 11 |
+
"_output_all_columns": false,
|
| 12 |
+
"_split": null
|
| 13 |
+
}
|
data/tokenized/absa_cls_dataset/validation/data-00000-of-00001.arrow
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a9da21b3401a16a246766047cda0695dcf07ef7d3c7eb64804ed9f59f0746f08
|
| 3 |
+
size 105136
|
data/tokenized/absa_cls_dataset/validation/dataset_info.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"citation": "",
|
| 3 |
+
"description": "",
|
| 4 |
+
"features": {
|
| 5 |
+
"label": {
|
| 6 |
+
"dtype": "int64",
|
| 7 |
+
"_type": "Value"
|
| 8 |
+
},
|
| 9 |
+
"input_ids": {
|
| 10 |
+
"feature": {
|
| 11 |
+
"dtype": "int32",
|
| 12 |
+
"_type": "Value"
|
| 13 |
+
},
|
| 14 |
+
"_type": "Sequence"
|
| 15 |
+
},
|
| 16 |
+
"attention_mask": {
|
| 17 |
+
"feature": {
|
| 18 |
+
"dtype": "int8",
|
| 19 |
+
"_type": "Value"
|
| 20 |
+
},
|
| 21 |
+
"_type": "Sequence"
|
| 22 |
+
}
|
| 23 |
+
},
|
| 24 |
+
"homepage": "",
|
| 25 |
+
"license": ""
|
| 26 |
+
}
|
data/tokenized/absa_cls_dataset/validation/state.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_data_files": [
|
| 3 |
+
{
|
| 4 |
+
"filename": "data-00000-of-00001.arrow"
|
| 5 |
+
}
|
| 6 |
+
],
|
| 7 |
+
"_fingerprint": "a33b9cfd2402e633",
|
| 8 |
+
"_format_columns": null,
|
| 9 |
+
"_format_kwargs": {},
|
| 10 |
+
"_format_type": null,
|
| 11 |
+
"_output_all_columns": false,
|
| 12 |
+
"_split": null
|
| 13 |
+
}
|
data/tokenized/absa_ner_dataset/dataset_dict.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"splits": ["train", "validation", "test"]}
|
data/tokenized/absa_ner_dataset/test/data-00000-of-00001.arrow
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e1ec218e3b5b0c88fd86358c8e87f44709510cff43c41a81dac98274ce4eb0e7
|
| 3 |
+
size 141712
|
data/tokenized/absa_ner_dataset/test/dataset_info.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"citation": "",
|
| 3 |
+
"description": "",
|
| 4 |
+
"features": {
|
| 5 |
+
"input_ids": {
|
| 6 |
+
"feature": {
|
| 7 |
+
"dtype": "int32",
|
| 8 |
+
"_type": "Value"
|
| 9 |
+
},
|
| 10 |
+
"_type": "Sequence"
|
| 11 |
+
},
|
| 12 |
+
"attention_mask": {
|
| 13 |
+
"feature": {
|
| 14 |
+
"dtype": "int8",
|
| 15 |
+
"_type": "Value"
|
| 16 |
+
},
|
| 17 |
+
"_type": "Sequence"
|
| 18 |
+
},
|
| 19 |
+
"labels": {
|
| 20 |
+
"feature": {
|
| 21 |
+
"dtype": "int64",
|
| 22 |
+
"_type": "Value"
|
| 23 |
+
},
|
| 24 |
+
"_type": "Sequence"
|
| 25 |
+
}
|
| 26 |
+
},
|
| 27 |
+
"homepage": "",
|
| 28 |
+
"license": ""
|
| 29 |
+
}
|
data/tokenized/absa_ner_dataset/test/state.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_data_files": [
|
| 3 |
+
{
|
| 4 |
+
"filename": "data-00000-of-00001.arrow"
|
| 5 |
+
}
|
| 6 |
+
],
|
| 7 |
+
"_fingerprint": "0fa99b895566c7b5",
|
| 8 |
+
"_format_columns": null,
|
| 9 |
+
"_format_kwargs": {},
|
| 10 |
+
"_format_type": null,
|
| 11 |
+
"_output_all_columns": false,
|
| 12 |
+
"_split": null
|
| 13 |
+
}
|
data/tokenized/absa_ner_dataset/train/data-00000-of-00001.arrow
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:76c719d76d95127fc82691bbe1455e988c0c593fcdd510f270deac3e625cb929
|
| 3 |
+
size 1101368
|
data/tokenized/absa_ner_dataset/train/dataset_info.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"citation": "",
|
| 3 |
+
"description": "",
|
| 4 |
+
"features": {
|
| 5 |
+
"input_ids": {
|
| 6 |
+
"feature": {
|
| 7 |
+
"dtype": "int32",
|
| 8 |
+
"_type": "Value"
|
| 9 |
+
},
|
| 10 |
+
"_type": "Sequence"
|
| 11 |
+
},
|
| 12 |
+
"attention_mask": {
|
| 13 |
+
"feature": {
|
| 14 |
+
"dtype": "int8",
|
| 15 |
+
"_type": "Value"
|
| 16 |
+
},
|
| 17 |
+
"_type": "Sequence"
|
| 18 |
+
},
|
| 19 |
+
"labels": {
|
| 20 |
+
"feature": {
|
| 21 |
+
"dtype": "int64",
|
| 22 |
+
"_type": "Value"
|
| 23 |
+
},
|
| 24 |
+
"_type": "Sequence"
|
| 25 |
+
}
|
| 26 |
+
},
|
| 27 |
+
"homepage": "",
|
| 28 |
+
"license": ""
|
| 29 |
+
}
|
data/tokenized/absa_ner_dataset/train/state.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_data_files": [
|
| 3 |
+
{
|
| 4 |
+
"filename": "data-00000-of-00001.arrow"
|
| 5 |
+
}
|
| 6 |
+
],
|
| 7 |
+
"_fingerprint": "535b22b0ddf3661d",
|
| 8 |
+
"_format_columns": null,
|
| 9 |
+
"_format_kwargs": {},
|
| 10 |
+
"_format_type": null,
|
| 11 |
+
"_output_all_columns": false,
|
| 12 |
+
"_split": null
|
| 13 |
+
}
|
data/tokenized/absa_ner_dataset/validation/data-00000-of-00001.arrow
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f94a6981561bcaa124a70fb560b93e355e57df287e9e721a2e6e887b6689b236
|
| 3 |
+
size 144312
|
data/tokenized/absa_ner_dataset/validation/dataset_info.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"citation": "",
|
| 3 |
+
"description": "",
|
| 4 |
+
"features": {
|
| 5 |
+
"input_ids": {
|
| 6 |
+
"feature": {
|
| 7 |
+
"dtype": "int32",
|
| 8 |
+
"_type": "Value"
|
| 9 |
+
},
|
| 10 |
+
"_type": "Sequence"
|
| 11 |
+
},
|
| 12 |
+
"attention_mask": {
|
| 13 |
+
"feature": {
|
| 14 |
+
"dtype": "int8",
|
| 15 |
+
"_type": "Value"
|
| 16 |
+
},
|
| 17 |
+
"_type": "Sequence"
|
| 18 |
+
},
|
| 19 |
+
"labels": {
|
| 20 |
+
"feature": {
|
| 21 |
+
"dtype": "int64",
|
| 22 |
+
"_type": "Value"
|
| 23 |
+
},
|
| 24 |
+
"_type": "Sequence"
|
| 25 |
+
}
|
| 26 |
+
},
|
| 27 |
+
"homepage": "",
|
| 28 |
+
"license": ""
|
| 29 |
+
}
|
data/tokenized/absa_ner_dataset/validation/state.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_data_files": [
|
| 3 |
+
{
|
| 4 |
+
"filename": "data-00000-of-00001.arrow"
|
| 5 |
+
}
|
| 6 |
+
],
|
| 7 |
+
"_fingerprint": "caedb7425c742cc1",
|
| 8 |
+
"_format_columns": null,
|
| 9 |
+
"_format_kwargs": {},
|
| 10 |
+
"_format_type": null,
|
| 11 |
+
"_output_all_columns": false,
|
| 12 |
+
"_split": null
|
| 13 |
+
}
|
mlruns/1/f7e324a252a94812b0d4158371276cb0/artifacts/confusion_matrix.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"confusion_matrix": [
|
| 3 |
+
[
|
| 4 |
+
246,
|
| 5 |
+
26,
|
| 6 |
+
31,
|
| 7 |
+
13
|
| 8 |
+
],
|
| 9 |
+
[
|
| 10 |
+
14,
|
| 11 |
+
120,
|
| 12 |
+
26,
|
| 13 |
+
7
|
| 14 |
+
],
|
| 15 |
+
[
|
| 16 |
+
20,
|
| 17 |
+
29,
|
| 18 |
+
60,
|
| 19 |
+
1
|
| 20 |
+
],
|
| 21 |
+
[
|
| 22 |
+
3,
|
| 23 |
+
7,
|
| 24 |
+
0,
|
| 25 |
+
3
|
| 26 |
+
]
|
| 27 |
+
]
|
| 28 |
+
}
|
notebooks/03_model_comparison.ipynb
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# Model Comparison\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"This notebook connects to the MLflow tracking server and compares the results of our models.\n"
|
| 10 |
+
]
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"cell_type": "code",
|
| 14 |
+
"metadata": {},
|
| 15 |
+
"execution_count": null,
|
| 16 |
+
"outputs": [],
|
| 17 |
+
"source": [
|
| 18 |
+
"import mlflow\n",
|
| 19 |
+
"import pandas as pd\n",
|
| 20 |
+
"import matplotlib.pyplot as plt\n",
|
| 21 |
+
"import seaborn as sns\n",
|
| 22 |
+
"import json\n",
|
| 23 |
+
"\n",
|
| 24 |
+
"mlflow.set_tracking_uri('sqlite:///mlflow/mlflow.db')\n"
|
| 25 |
+
]
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"cell_type": "code",
|
| 29 |
+
"metadata": {},
|
| 30 |
+
"execution_count": null,
|
| 31 |
+
"outputs": [],
|
| 32 |
+
"source": [
|
| 33 |
+
"# Load all runs\n",
|
| 34 |
+
"experiment = mlflow.get_experiment_by_name('multilingual-absa')\n",
|
| 35 |
+
"df = mlflow.search_runs(experiment_ids=[experiment.experiment_id])\n",
|
| 36 |
+
"display(df.head())\n"
|
| 37 |
+
]
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"cell_type": "code",
|
| 41 |
+
"metadata": {},
|
| 42 |
+
"execution_count": null,
|
| 43 |
+
"outputs": [],
|
| 44 |
+
"source": [
|
| 45 |
+
"# Bar chart: macro-F1 comparison\n",
|
| 46 |
+
"metrics = df[['tags.mlflow.runName', 'metrics.eval_macro_f1', 'metrics.test_f1', 'metrics.test_macro_f1', 'metrics.hindi_zero_shot_macro_f1']].fillna(0)\n",
|
| 47 |
+
"metrics['Best F1'] = metrics[['metrics.eval_macro_f1', 'metrics.test_f1', 'metrics.test_macro_f1']].max(axis=1)\n",
|
| 48 |
+
"\n",
|
| 49 |
+
"plt.figure(figsize=(10, 6))\n",
|
| 50 |
+
"sns.barplot(data=metrics, x='tags.mlflow.runName', y='Best F1')\n",
|
| 51 |
+
"plt.title('Model Comparison by Macro-F1 / Span-F1')\n",
|
| 52 |
+
"plt.xticks(rotation=45)\n",
|
| 53 |
+
"plt.show()\n"
|
| 54 |
+
]
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
"cell_type": "code",
|
| 58 |
+
"metadata": {},
|
| 59 |
+
"execution_count": null,
|
| 60 |
+
"outputs": [],
|
| 61 |
+
"source": [
|
| 62 |
+
"# Load confusion matrix for best sentiment classifier\n",
|
| 63 |
+
"# Note: Assuming the confusion_matrix.json artifact was downloaded or parsed.\n",
|
| 64 |
+
"print('Confusion Matrix (Placeholder for artifact loading)')\n"
|
| 65 |
+
]
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"cell_type": "code",
|
| 69 |
+
"metadata": {},
|
| 70 |
+
"execution_count": null,
|
| 71 |
+
"outputs": [],
|
| 72 |
+
"source": [
|
| 73 |
+
"# 5 Example Predictions\n",
|
| 74 |
+
"print('Example 1: The food was great but service was slow.')\n",
|
| 75 |
+
"print('Example 2: El sistema operativo es muy estable.')\n",
|
| 76 |
+
"print('... (Load pipeline and infer here)')\n"
|
| 77 |
+
]
|
| 78 |
+
}
|
| 79 |
+
],
|
| 80 |
+
"metadata": {
|
| 81 |
+
"kernelspec": {
|
| 82 |
+
"display_name": "Python 3",
|
| 83 |
+
"language": "python",
|
| 84 |
+
"name": "python3"
|
| 85 |
+
},
|
| 86 |
+
"language_info": {
|
| 87 |
+
"codemirror_mode": {
|
| 88 |
+
"name": "ipython",
|
| 89 |
+
"version": 3
|
| 90 |
+
},
|
| 91 |
+
"file_extension": ".py",
|
| 92 |
+
"mimetype": "text/x-python",
|
| 93 |
+
"name": "python",
|
| 94 |
+
"nbconvert_exporter": "python",
|
| 95 |
+
"pygments_lexer": "ipython3",
|
| 96 |
+
"version": "3.11.0"
|
| 97 |
+
}
|
| 98 |
+
},
|
| 99 |
+
"nbformat": 4,
|
| 100 |
+
"nbformat_minor": 4
|
| 101 |
+
}
|
notebooks/03_train_colab.ipynb
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# Google Colab Training Notebook\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"This notebook is intended to be run on Google Colab with a T4 GPU. It clones the repo, installs dependencies, and runs the training scripts.\n"
|
| 10 |
+
]
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"cell_type": "code",
|
| 14 |
+
"metadata": {},
|
| 15 |
+
"execution_count": null,
|
| 16 |
+
"outputs": [],
|
| 17 |
+
"source": [
|
| 18 |
+
"!git clone https://github.com/Aryanmishra-dev/Multilingual-Absa.git\n",
|
| 19 |
+
"%cd Multilingual-Absa\n",
|
| 20 |
+
"!pip install -r requirements.txt\n"
|
| 21 |
+
]
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"cell_type": "code",
|
| 25 |
+
"metadata": {},
|
| 26 |
+
"execution_count": null,
|
| 27 |
+
"outputs": [],
|
| 28 |
+
"source": [
|
| 29 |
+
"# Mount Google Drive to save models and MLflow logs persistently\n",
|
| 30 |
+
"from google.colab import drive\n",
|
| 31 |
+
"drive.mount('/content/drive')\n"
|
| 32 |
+
]
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"cell_type": "code",
|
| 36 |
+
"metadata": {},
|
| 37 |
+
"execution_count": null,
|
| 38 |
+
"outputs": [],
|
| 39 |
+
"source": [
|
| 40 |
+
"# Create symlinks or copy data if needed\n",
|
| 41 |
+
"# Assuming data is in the repo for now\n",
|
| 42 |
+
"!mkdir -p /content/drive/MyDrive/ABSA_models\n"
|
| 43 |
+
]
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"cell_type": "code",
|
| 47 |
+
"metadata": {},
|
| 48 |
+
"execution_count": null,
|
| 49 |
+
"outputs": [],
|
| 50 |
+
"source": [
|
| 51 |
+
"# Prepare dataset\n",
|
| 52 |
+
"!PYTHONPATH=. python src/data/hf_dataset.py\n"
|
| 53 |
+
]
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"cell_type": "code",
|
| 57 |
+
"metadata": {},
|
| 58 |
+
"execution_count": null,
|
| 59 |
+
"outputs": [],
|
| 60 |
+
"source": [
|
| 61 |
+
"# Run Aspect Extraction Training\n",
|
| 62 |
+
"!PYTHONPATH=. python src/models/train_aspect_extraction.py\n"
|
| 63 |
+
]
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"cell_type": "code",
|
| 67 |
+
"metadata": {},
|
| 68 |
+
"execution_count": null,
|
| 69 |
+
"outputs": [],
|
| 70 |
+
"source": [
|
| 71 |
+
"# Run Sentiment Classification Training\n",
|
| 72 |
+
"!PYTHONPATH=. python src/models/train_sentiment.py\n"
|
| 73 |
+
]
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"cell_type": "code",
|
| 77 |
+
"metadata": {},
|
| 78 |
+
"execution_count": null,
|
| 79 |
+
"outputs": [],
|
| 80 |
+
"source": [
|
| 81 |
+
"# Run Baseline as well\n",
|
| 82 |
+
"!PYTHONPATH=. python src/models/baseline.py\n"
|
| 83 |
+
]
|
| 84 |
+
},
|
| 85 |
+
{
|
| 86 |
+
"cell_type": "code",
|
| 87 |
+
"metadata": {},
|
| 88 |
+
"execution_count": null,
|
| 89 |
+
"outputs": [],
|
| 90 |
+
"source": [
|
| 91 |
+
"# Cross-lingual Evaluation\n",
|
| 92 |
+
"!PYTHONPATH=. python src/evaluation/cross_lingual_eval.py\n"
|
| 93 |
+
]
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"cell_type": "code",
|
| 97 |
+
"metadata": {},
|
| 98 |
+
"execution_count": null,
|
| 99 |
+
"outputs": [],
|
| 100 |
+
"source": [
|
| 101 |
+
"# Copy models back to Drive\n",
|
| 102 |
+
"!cp -r models/* /content/drive/MyDrive/ABSA_models/\n",
|
| 103 |
+
"!cp -r mlflow /content/drive/MyDrive/ABSA_models/\n"
|
| 104 |
+
]
|
| 105 |
+
}
|
| 106 |
+
],
|
| 107 |
+
"metadata": {
|
| 108 |
+
"kernelspec": {
|
| 109 |
+
"display_name": "Python 3",
|
| 110 |
+
"language": "python",
|
| 111 |
+
"name": "python3"
|
| 112 |
+
},
|
| 113 |
+
"language_info": {
|
| 114 |
+
"codemirror_mode": {
|
| 115 |
+
"name": "ipython",
|
| 116 |
+
"version": 3
|
| 117 |
+
},
|
| 118 |
+
"file_extension": ".py",
|
| 119 |
+
"mimetype": "text/x-python",
|
| 120 |
+
"name": "python",
|
| 121 |
+
"nbconvert_exporter": "python",
|
| 122 |
+
"pygments_lexer": "ipython3",
|
| 123 |
+
"version": "3.11.0"
|
| 124 |
+
}
|
| 125 |
+
},
|
| 126 |
+
"nbformat": 4,
|
| 127 |
+
"nbformat_minor": 4
|
| 128 |
+
}
|
scripts/generate_notebooks.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
def create_notebook(filename: str, cells_content: list):
|
| 5 |
+
cells = []
|
| 6 |
+
for content, cell_type in cells_content:
|
| 7 |
+
cells.append({
|
| 8 |
+
"cell_type": cell_type,
|
| 9 |
+
"metadata": {},
|
| 10 |
+
"execution_count": None if cell_type == "code" else None,
|
| 11 |
+
"outputs": [] if cell_type == "code" else None,
|
| 12 |
+
"source": [line + "\n" for line in content.split("\n")]
|
| 13 |
+
})
|
| 14 |
+
|
| 15 |
+
# Clean up outputs/execution_count for markdown
|
| 16 |
+
if cell_type == "markdown":
|
| 17 |
+
del cells[-1]["execution_count"]
|
| 18 |
+
del cells[-1]["outputs"]
|
| 19 |
+
|
| 20 |
+
notebook = {
|
| 21 |
+
"cells": cells,
|
| 22 |
+
"metadata": {
|
| 23 |
+
"kernelspec": {
|
| 24 |
+
"display_name": "Python 3",
|
| 25 |
+
"language": "python",
|
| 26 |
+
"name": "python3"
|
| 27 |
+
},
|
| 28 |
+
"language_info": {
|
| 29 |
+
"codemirror_mode": {"name": "ipython", "version": 3},
|
| 30 |
+
"file_extension": ".py",
|
| 31 |
+
"mimetype": "text/x-python",
|
| 32 |
+
"name": "python",
|
| 33 |
+
"nbconvert_exporter": "python",
|
| 34 |
+
"pygments_lexer": "ipython3",
|
| 35 |
+
"version": "3.11.0"
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
"nbformat": 4,
|
| 39 |
+
"nbformat_minor": 4
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
Path("notebooks").mkdir(parents=True, exist_ok=True)
|
| 43 |
+
with open(f"notebooks/{filename}", "w") as f:
|
| 44 |
+
json.dump(notebook, f, indent=2)
|
| 45 |
+
|
| 46 |
+
def main():
|
| 47 |
+
colab_cells = [
|
| 48 |
+
("# Google Colab Training Notebook\n\nThis notebook is intended to be run on Google Colab with a T4 GPU. It clones the repo, installs dependencies, and runs the training scripts.", "markdown"),
|
| 49 |
+
("!git clone https://github.com/Aryanmishra-dev/Multilingual-Absa.git\n%cd Multilingual-Absa\n!pip install -r requirements.txt", "code"),
|
| 50 |
+
("# Mount Google Drive to save models and MLflow logs persistently\nfrom google.colab import drive\ndrive.mount('/content/drive')", "code"),
|
| 51 |
+
("# Create symlinks or copy data if needed\n# Assuming data is in the repo for now\n!mkdir -p /content/drive/MyDrive/ABSA_models", "code"),
|
| 52 |
+
("# Prepare dataset\n!PYTHONPATH=. python src/data/hf_dataset.py", "code"),
|
| 53 |
+
("# Run Aspect Extraction Training\n!PYTHONPATH=. python src/models/train_aspect_extraction.py", "code"),
|
| 54 |
+
("# Run Sentiment Classification Training\n!PYTHONPATH=. python src/models/train_sentiment.py", "code"),
|
| 55 |
+
("# Run Baseline as well\n!PYTHONPATH=. python src/models/baseline.py", "code"),
|
| 56 |
+
("# Cross-lingual Evaluation\n!PYTHONPATH=. python src/evaluation/cross_lingual_eval.py", "code"),
|
| 57 |
+
("# Copy models back to Drive\n!cp -r models/* /content/drive/MyDrive/ABSA_models/\n!cp -r mlflow /content/drive/MyDrive/ABSA_models/", "code")
|
| 58 |
+
]
|
| 59 |
+
|
| 60 |
+
comparison_cells = [
|
| 61 |
+
("# Model Comparison\n\nThis notebook connects to the MLflow tracking server and compares the results of our models.", "markdown"),
|
| 62 |
+
("import mlflow\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport json\n\nmlflow.set_tracking_uri('sqlite:///mlflow/mlflow.db')", "code"),
|
| 63 |
+
("# Load all runs\nexperiment = mlflow.get_experiment_by_name('multilingual-absa')\ndf = mlflow.search_runs(experiment_ids=[experiment.experiment_id])\ndisplay(df.head())", "code"),
|
| 64 |
+
("# Bar chart: macro-F1 comparison\nmetrics = df[['tags.mlflow.runName', 'metrics.eval_macro_f1', 'metrics.test_f1', 'metrics.test_macro_f1', 'metrics.hindi_zero_shot_macro_f1']].fillna(0)\nmetrics['Best F1'] = metrics[['metrics.eval_macro_f1', 'metrics.test_f1', 'metrics.test_macro_f1']].max(axis=1)\n\nplt.figure(figsize=(10, 6))\nsns.barplot(data=metrics, x='tags.mlflow.runName', y='Best F1')\nplt.title('Model Comparison by Macro-F1 / Span-F1')\nplt.xticks(rotation=45)\nplt.show()", "code"),
|
| 65 |
+
("# Load confusion matrix for best sentiment classifier\n# Note: Assuming the confusion_matrix.json artifact was downloaded or parsed.\nprint('Confusion Matrix (Placeholder for artifact loading)')", "code"),
|
| 66 |
+
("# 5 Example Predictions\nprint('Example 1: The food was great but service was slow.')\nprint('Example 2: El sistema operativo es muy estable.')\nprint('... (Load pipeline and infer here)')", "code")
|
| 67 |
+
]
|
| 68 |
+
|
| 69 |
+
create_notebook("03_train_colab.ipynb", colab_cells)
|
| 70 |
+
create_notebook("03_model_comparison.ipynb", comparison_cells)
|
| 71 |
+
|
| 72 |
+
if __name__ == '__main__':
|
| 73 |
+
main()
|
scripts/mlflow_ui.sh
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Script to launch MLflow UI
|
| 3 |
+
# Use sqlite backend as specified
|
| 4 |
+
|
| 5 |
+
# Ensure the directory exists
|
| 6 |
+
mkdir -p mlflow
|
| 7 |
+
|
| 8 |
+
echo "Starting MLflow UI with SQLite backend..."
|
| 9 |
+
echo "Access the UI at http://localhost:5000"
|
| 10 |
+
mlflow ui --backend-store-uri sqlite:///mlflow/mlflow.db --host 0.0.0.0 --port 5000
|
src/data/bio_tagger.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
from typing import List, Dict, Any, Tuple
|
| 3 |
+
|
| 4 |
+
def tokenize(text: str) -> List[Tuple[str, int, int]]:
|
| 5 |
+
"""
|
| 6 |
+
Tokenizes text by words, returning tokens and their start/end character offsets.
|
| 7 |
+
Uses simple regex based tokenization to preserve whitespace semantics for BIO tagging.
|
| 8 |
+
"""
|
| 9 |
+
tokens = []
|
| 10 |
+
# Match non-whitespace characters
|
| 11 |
+
for match in re.finditer(r'\S+', text):
|
| 12 |
+
tokens.append((match.group(), match.start(), match.end()))
|
| 13 |
+
return tokens
|
| 14 |
+
|
| 15 |
+
def convert_to_bio(text: str, aspect_terms: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
| 16 |
+
"""
|
| 17 |
+
Converts text and aspect spans to BIO tagged tokens.
|
| 18 |
+
|
| 19 |
+
Args:
|
| 20 |
+
text: The input review string.
|
| 21 |
+
aspect_terms: List of dictionaries with 'term', 'from', and 'to' keys.
|
| 22 |
+
|
| 23 |
+
Returns:
|
| 24 |
+
List of dictionaries with 'token' and 'label' (B-ASP, I-ASP, O).
|
| 25 |
+
"""
|
| 26 |
+
tokens = tokenize(text)
|
| 27 |
+
|
| 28 |
+
# Sort aspects by start index
|
| 29 |
+
sorted_aspects = sorted(aspect_terms, key=lambda x: x['from'])
|
| 30 |
+
|
| 31 |
+
bio_tags = []
|
| 32 |
+
aspect_idx = 0
|
| 33 |
+
num_aspects = len(sorted_aspects)
|
| 34 |
+
|
| 35 |
+
for token_str, t_start, t_end in tokens:
|
| 36 |
+
label = "O"
|
| 37 |
+
|
| 38 |
+
# Move aspect pointer if we've passed the current aspect completely
|
| 39 |
+
while aspect_idx < num_aspects and sorted_aspects[aspect_idx]['to'] <= t_start:
|
| 40 |
+
aspect_idx += 1
|
| 41 |
+
|
| 42 |
+
if aspect_idx < num_aspects:
|
| 43 |
+
curr_aspect = sorted_aspects[aspect_idx]
|
| 44 |
+
a_start = curr_aspect['from']
|
| 45 |
+
a_end = curr_aspect['to']
|
| 46 |
+
|
| 47 |
+
# Check overlap
|
| 48 |
+
if not (t_end <= a_start or t_start >= a_end):
|
| 49 |
+
# There is overlap
|
| 50 |
+
# If this token overlaps with the start of the aspect
|
| 51 |
+
if t_start <= a_start or (len(bio_tags) > 0 and bio_tags[-1]['label'] == "O" and t_start > a_start):
|
| 52 |
+
label = "B-ASP"
|
| 53 |
+
else:
|
| 54 |
+
# Check if previous tag was B-ASP or I-ASP for the *same* aspect
|
| 55 |
+
if len(bio_tags) > 0 and bio_tags[-1]['label'] in ("B-ASP", "I-ASP"):
|
| 56 |
+
label = "I-ASP"
|
| 57 |
+
else:
|
| 58 |
+
label = "B-ASP"
|
| 59 |
+
|
| 60 |
+
bio_tags.append({"token": token_str, "label": label})
|
| 61 |
+
|
| 62 |
+
return bio_tags
|
| 63 |
+
|
| 64 |
+
def bio_to_aspects(tokens: List[str], labels: List[str]) -> List[str]:
|
| 65 |
+
"""
|
| 66 |
+
Converts BIO tags back to a list of aspect terms.
|
| 67 |
+
|
| 68 |
+
Args:
|
| 69 |
+
tokens: List of string tokens.
|
| 70 |
+
labels: List of BIO labels corresponding to the tokens.
|
| 71 |
+
|
| 72 |
+
Returns:
|
| 73 |
+
List of extracted aspect term strings.
|
| 74 |
+
"""
|
| 75 |
+
aspects = []
|
| 76 |
+
current_aspect = []
|
| 77 |
+
|
| 78 |
+
for token, label in zip(tokens, labels):
|
| 79 |
+
if label == "B-ASP":
|
| 80 |
+
if current_aspect:
|
| 81 |
+
aspects.append(" ".join(current_aspect))
|
| 82 |
+
current_aspect = [token]
|
| 83 |
+
elif label == "I-ASP":
|
| 84 |
+
if current_aspect:
|
| 85 |
+
current_aspect.append(token)
|
| 86 |
+
else:
|
| 87 |
+
# Invalid sequence (I-ASP without B-ASP), treat as B-ASP
|
| 88 |
+
current_aspect = [token]
|
| 89 |
+
else: # O
|
| 90 |
+
if current_aspect:
|
| 91 |
+
aspects.append(" ".join(current_aspect))
|
| 92 |
+
current_aspect = []
|
| 93 |
+
|
| 94 |
+
if current_aspect:
|
| 95 |
+
aspects.append(" ".join(current_aspect))
|
| 96 |
+
|
| 97 |
+
return aspects
|
src/data/hf_dataset.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import numpy as np
|
| 3 |
+
import pandas as pd
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import List, Dict, Any, Tuple
|
| 6 |
+
from datasets import Dataset, DatasetDict
|
| 7 |
+
from transformers import AutoTokenizer
|
| 8 |
+
from sklearn.model_selection import train_test_split
|
| 9 |
+
from src.data.bio_tagger import convert_to_bio
|
| 10 |
+
|
| 11 |
+
np.random.seed(42)
|
| 12 |
+
|
| 13 |
+
def load_data(file_paths: List[Path]) -> List[Dict[str, Any]]:
|
| 14 |
+
data = []
|
| 15 |
+
for path in file_paths:
|
| 16 |
+
with open(path, 'r', encoding='utf-8') as f:
|
| 17 |
+
for line in f:
|
| 18 |
+
if line.strip():
|
| 19 |
+
data.append(json.loads(line))
|
| 20 |
+
return data
|
| 21 |
+
|
| 22 |
+
def prepare_ner_data(data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
| 23 |
+
"""Prepares data for Token Classification (NER)."""
|
| 24 |
+
ner_data = []
|
| 25 |
+
label_map = {"O": 0, "B-ASP": 1, "I-ASP": 2}
|
| 26 |
+
|
| 27 |
+
for item in data:
|
| 28 |
+
text = item['text']
|
| 29 |
+
aspects = item.get('aspect_terms', [])
|
| 30 |
+
bio_tags = convert_to_bio(text, aspects)
|
| 31 |
+
|
| 32 |
+
tokens = [t['token'] for t in bio_tags]
|
| 33 |
+
ner_tags = [label_map[t['label']] for t in bio_tags]
|
| 34 |
+
|
| 35 |
+
ner_data.append({
|
| 36 |
+
"tokens": tokens,
|
| 37 |
+
"ner_tags": ner_tags,
|
| 38 |
+
"id": item.get("id", str(len(ner_data)))
|
| 39 |
+
})
|
| 40 |
+
return ner_data
|
| 41 |
+
|
| 42 |
+
def prepare_cls_data(data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
| 43 |
+
"""Prepares data for Sequence Classification (Sentiment)."""
|
| 44 |
+
cls_data = []
|
| 45 |
+
sentiment_map = {"positive": 0, "negative": 1, "neutral": 2, "conflict": 3}
|
| 46 |
+
|
| 47 |
+
for item in data:
|
| 48 |
+
text = item['text']
|
| 49 |
+
aspects = item.get('aspect_terms', [])
|
| 50 |
+
|
| 51 |
+
for aspect in aspects:
|
| 52 |
+
term = aspect['term']
|
| 53 |
+
polarity = aspect['polarity']
|
| 54 |
+
|
| 55 |
+
if polarity not in sentiment_map:
|
| 56 |
+
continue
|
| 57 |
+
|
| 58 |
+
cls_data.append({
|
| 59 |
+
"text": text,
|
| 60 |
+
"aspect_term": term,
|
| 61 |
+
"label": sentiment_map[polarity],
|
| 62 |
+
"id": f"{item.get('id', str(len(cls_data)))}_{term}"
|
| 63 |
+
})
|
| 64 |
+
return cls_data
|
| 65 |
+
|
| 66 |
+
def align_labels_with_tokens(labels, word_ids):
|
| 67 |
+
new_labels = []
|
| 68 |
+
current_word = None
|
| 69 |
+
for word_id in word_ids:
|
| 70 |
+
if word_id is None:
|
| 71 |
+
new_labels.append(-100)
|
| 72 |
+
elif word_id != current_word:
|
| 73 |
+
new_labels.append(labels[word_id])
|
| 74 |
+
current_word = word_id
|
| 75 |
+
else:
|
| 76 |
+
new_labels.append(-100)
|
| 77 |
+
return new_labels
|
| 78 |
+
|
| 79 |
+
def main():
|
| 80 |
+
data_dir = Path("data/processed")
|
| 81 |
+
output_dir = Path("data/tokenized")
|
| 82 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 83 |
+
|
| 84 |
+
# Load all English SemEval data
|
| 85 |
+
train_path = data_dir / "semeval_train.jsonl"
|
| 86 |
+
test_path = data_dir / "semeval_test.jsonl"
|
| 87 |
+
all_data = load_data([train_path, test_path])
|
| 88 |
+
|
| 89 |
+
# Prepare datasets
|
| 90 |
+
ner_data = prepare_ner_data(all_data)
|
| 91 |
+
cls_data = prepare_cls_data(all_data)
|
| 92 |
+
|
| 93 |
+
tokenizer = AutoTokenizer.from_pretrained("xlm-roberta-base")
|
| 94 |
+
|
| 95 |
+
# ---------------------------------------------------------
|
| 96 |
+
# 1. Token Classification (NER) Dataset
|
| 97 |
+
# ---------------------------------------------------------
|
| 98 |
+
ner_df = pd.DataFrame(ner_data)
|
| 99 |
+
|
| 100 |
+
# Split 80/10/10
|
| 101 |
+
# For NER, we don't have a single sentiment to stratify on easily, so random split
|
| 102 |
+
train_ner, temp_ner = train_test_split(ner_df, test_size=0.2, random_state=42)
|
| 103 |
+
val_ner, test_ner = train_test_split(temp_ner, test_size=0.5, random_state=42)
|
| 104 |
+
|
| 105 |
+
def tokenize_and_align_labels(examples):
|
| 106 |
+
tokenized_inputs = tokenizer(
|
| 107 |
+
examples["tokens"], truncation=True, is_split_into_words=True, max_length=128
|
| 108 |
+
)
|
| 109 |
+
labels = []
|
| 110 |
+
for i, label in enumerate(examples[f"ner_tags"]):
|
| 111 |
+
word_ids = tokenized_inputs.word_ids(batch_index=i)
|
| 112 |
+
labels.append(align_labels_with_tokens(label, word_ids))
|
| 113 |
+
tokenized_inputs["labels"] = labels
|
| 114 |
+
return tokenized_inputs
|
| 115 |
+
|
| 116 |
+
ner_dataset = DatasetDict({
|
| 117 |
+
"train": Dataset.from_pandas(train_ner, preserve_index=False),
|
| 118 |
+
"validation": Dataset.from_pandas(val_ner, preserve_index=False),
|
| 119 |
+
"test": Dataset.from_pandas(test_ner, preserve_index=False),
|
| 120 |
+
})
|
| 121 |
+
|
| 122 |
+
tokenized_ner = ner_dataset.map(tokenize_and_align_labels, batched=True, remove_columns=["tokens", "ner_tags", "id"])
|
| 123 |
+
tokenized_ner.save_to_disk(str(output_dir / "absa_ner_dataset"))
|
| 124 |
+
print(f"NER Dataset saved to {output_dir / 'absa_ner_dataset'}")
|
| 125 |
+
|
| 126 |
+
# ---------------------------------------------------------
|
| 127 |
+
# 2. Sequence Classification (Sentiment) Dataset
|
| 128 |
+
# ---------------------------------------------------------
|
| 129 |
+
cls_df = pd.DataFrame(cls_data)
|
| 130 |
+
|
| 131 |
+
# Stratified split 80/10/10 based on label
|
| 132 |
+
train_cls, temp_cls = train_test_split(cls_df, test_size=0.2, random_state=42, stratify=cls_df['label'])
|
| 133 |
+
val_cls, test_cls = train_test_split(temp_cls, test_size=0.5, random_state=42, stratify=temp_cls['label'])
|
| 134 |
+
|
| 135 |
+
def tokenize_cls(examples):
|
| 136 |
+
# Format: [CLS] text [SEP] aspect_term [SEP]
|
| 137 |
+
return tokenizer(
|
| 138 |
+
examples["text"],
|
| 139 |
+
examples["aspect_term"],
|
| 140 |
+
truncation=True,
|
| 141 |
+
max_length=128,
|
| 142 |
+
padding=False
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
cls_dataset = DatasetDict({
|
| 146 |
+
"train": Dataset.from_pandas(train_cls, preserve_index=False),
|
| 147 |
+
"validation": Dataset.from_pandas(val_cls, preserve_index=False),
|
| 148 |
+
"test": Dataset.from_pandas(test_cls, preserve_index=False),
|
| 149 |
+
})
|
| 150 |
+
|
| 151 |
+
tokenized_cls = cls_dataset.map(tokenize_cls, batched=True, remove_columns=["text", "aspect_term", "id"])
|
| 152 |
+
tokenized_cls.save_to_disk(str(output_dir / "absa_cls_dataset"))
|
| 153 |
+
print(f"CLS Dataset saved to {output_dir / 'absa_cls_dataset'}")
|
| 154 |
+
|
| 155 |
+
if __name__ == "__main__":
|
| 156 |
+
main()
|
src/evaluation/cross_lingual_eval.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import torch
|
| 4 |
+
import numpy as np
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from transformers import (
|
| 7 |
+
AutoTokenizer,
|
| 8 |
+
AutoModelForTokenClassification,
|
| 9 |
+
AutoModelForSequenceClassification,
|
| 10 |
+
pipeline
|
| 11 |
+
)
|
| 12 |
+
from sklearn.metrics import f1_score
|
| 13 |
+
import mlflow
|
| 14 |
+
|
| 15 |
+
from src.training.mlflow_utils import setup_mlflow
|
| 16 |
+
from src.data.bio_tagger import bio_to_aspects
|
| 17 |
+
|
| 18 |
+
def load_data(file_path: Path):
|
| 19 |
+
data = []
|
| 20 |
+
with open(file_path, 'r', encoding='utf-8') as f:
|
| 21 |
+
for line in f:
|
| 22 |
+
if line.strip():
|
| 23 |
+
data.append(json.loads(line))
|
| 24 |
+
return data
|
| 25 |
+
|
| 26 |
+
def main():
|
| 27 |
+
setup_mlflow()
|
| 28 |
+
|
| 29 |
+
# Check if models exist (might not if trained on Colab)
|
| 30 |
+
aspect_model_path = Path("models/aspect_extraction/best")
|
| 31 |
+
sentiment_model_path = Path("models/sentiment/best")
|
| 32 |
+
|
| 33 |
+
if not aspect_model_path.exists() or not sentiment_model_path.exists():
|
| 34 |
+
print("Models not found locally. Skipping cross-lingual evaluation until models are trained.")
|
| 35 |
+
return
|
| 36 |
+
|
| 37 |
+
print("Loading models...")
|
| 38 |
+
tokenizer = AutoTokenizer.from_pretrained("xlm-roberta-base")
|
| 39 |
+
|
| 40 |
+
aspect_model = AutoModelForTokenClassification.from_pretrained(str(aspect_model_path))
|
| 41 |
+
sentiment_model = AutoModelForSequenceClassification.from_pretrained(str(sentiment_model_path))
|
| 42 |
+
|
| 43 |
+
device = 0 if torch.cuda.is_available() else -1
|
| 44 |
+
|
| 45 |
+
ner_pipeline = pipeline("token-classification", model=aspect_model, tokenizer=tokenizer, device=device, aggregation_strategy="simple")
|
| 46 |
+
|
| 47 |
+
# Load Hindi Data
|
| 48 |
+
hindi_path = Path("data/processed/amazon_hindi.jsonl")
|
| 49 |
+
hindi_data = load_data(hindi_path)
|
| 50 |
+
|
| 51 |
+
print(f"Evaluating zero-shot on {len(hindi_data)} Hindi samples...")
|
| 52 |
+
|
| 53 |
+
sentiment_map_rev = {0: "positive", 1: "negative", 2: "neutral", 3: "conflict"}
|
| 54 |
+
sentiment_map = {"positive": 0, "negative": 1, "neutral": 2, "conflict": 3}
|
| 55 |
+
|
| 56 |
+
true_labels = []
|
| 57 |
+
pred_labels = []
|
| 58 |
+
|
| 59 |
+
for item in hindi_data:
|
| 60 |
+
text = item["text"]
|
| 61 |
+
aspects = item.get("aspect_terms", [])
|
| 62 |
+
|
| 63 |
+
for aspect in aspects:
|
| 64 |
+
term = aspect["term"]
|
| 65 |
+
true_polarity = aspect["polarity"]
|
| 66 |
+
if true_polarity not in sentiment_map:
|
| 67 |
+
continue
|
| 68 |
+
|
| 69 |
+
true_labels.append(sentiment_map[true_polarity])
|
| 70 |
+
|
| 71 |
+
# Inference Sentiment
|
| 72 |
+
inputs = tokenizer(text, term, return_tensors="pt", truncation=True, max_length=128)
|
| 73 |
+
if device == 0:
|
| 74 |
+
inputs = {k: v.to("cuda") for k, v in inputs.items()}
|
| 75 |
+
sentiment_model.to("cuda")
|
| 76 |
+
|
| 77 |
+
with torch.no_grad():
|
| 78 |
+
logits = sentiment_model(**inputs).logits
|
| 79 |
+
pred_idx = torch.argmax(logits, dim=1).item()
|
| 80 |
+
|
| 81 |
+
pred_labels.append(pred_idx)
|
| 82 |
+
|
| 83 |
+
hindi_macro_f1 = f1_score(true_labels, pred_labels, average="macro") if len(true_labels) > 0 else 0.0
|
| 84 |
+
print(f"Hindi Zero-Shot Macro-F1: {hindi_macro_f1}")
|
| 85 |
+
|
| 86 |
+
# We retrieve the best English test score from MLflow
|
| 87 |
+
# For now, let's just log the cross lingual gap if we know English F1
|
| 88 |
+
client = mlflow.tracking.MlflowClient()
|
| 89 |
+
experiment = client.get_experiment_by_name("multilingual-absa")
|
| 90 |
+
|
| 91 |
+
en_macro_f1 = 0.0
|
| 92 |
+
if experiment:
|
| 93 |
+
runs = client.search_runs(
|
| 94 |
+
experiment_ids=[experiment.experiment_id],
|
| 95 |
+
filter_string="metrics.test_macro_f1 > 0",
|
| 96 |
+
max_results=1,
|
| 97 |
+
order_by=["metrics.test_macro_f1 DESC"]
|
| 98 |
+
)
|
| 99 |
+
if runs:
|
| 100 |
+
en_macro_f1 = runs[0].data.metrics.get("test_macro_f1", 0.0)
|
| 101 |
+
|
| 102 |
+
print(f"Best English Test Macro-F1: {en_macro_f1}")
|
| 103 |
+
gap = en_macro_f1 - hindi_macro_f1
|
| 104 |
+
print(f"Cross-Lingual Gap: {gap}")
|
| 105 |
+
|
| 106 |
+
with mlflow.start_run(run_name="cross_lingual_eval"):
|
| 107 |
+
mlflow.log_metrics({
|
| 108 |
+
"hindi_zero_shot_macro_f1": float(hindi_macro_f1),
|
| 109 |
+
"english_test_macro_f1": float(en_macro_f1),
|
| 110 |
+
"cross_lingual_gap": float(gap)
|
| 111 |
+
})
|
| 112 |
+
|
| 113 |
+
if __name__ == "__main__":
|
| 114 |
+
main()
|
src/training/mlflow_utils.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import mlflow
|
| 2 |
+
from typing import Dict, Any, Optional
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
# Default configuration
|
| 7 |
+
MLFLOW_TRACKING_URI = "sqlite:///mlflow/mlflow.db"
|
| 8 |
+
EXPERIMENT_NAME = "multilingual-absa"
|
| 9 |
+
|
| 10 |
+
def setup_mlflow():
|
| 11 |
+
"""Initializes MLflow tracking URI and experiment."""
|
| 12 |
+
# Ensure the directory exists
|
| 13 |
+
Path("mlflow").mkdir(parents=True, exist_ok=True)
|
| 14 |
+
|
| 15 |
+
mlflow.set_tracking_uri(MLFLOW_TRACKING_URI)
|
| 16 |
+
mlflow.set_experiment(EXPERIMENT_NAME)
|
| 17 |
+
|
| 18 |
+
def log_training_run(params: Dict[str, Any], metrics: Dict[str, float], model_path: Optional[str | Path] = None, run_name: Optional[str] = None) -> str:
|
| 19 |
+
"""
|
| 20 |
+
Logs parameters, metrics, and optionally a model artifact to MLflow.
|
| 21 |
+
|
| 22 |
+
Args:
|
| 23 |
+
params: Dictionary of hyperparameters or configuration.
|
| 24 |
+
metrics: Dictionary of evaluation metrics.
|
| 25 |
+
model_path: Path to the saved model directory or file.
|
| 26 |
+
run_name: Optional name for the run.
|
| 27 |
+
|
| 28 |
+
Returns:
|
| 29 |
+
The ID of the created MLflow run.
|
| 30 |
+
"""
|
| 31 |
+
setup_mlflow()
|
| 32 |
+
|
| 33 |
+
with mlflow.start_run(run_name=run_name) as run:
|
| 34 |
+
mlflow.log_params(params)
|
| 35 |
+
mlflow.log_metrics(metrics)
|
| 36 |
+
|
| 37 |
+
if model_path:
|
| 38 |
+
model_path_obj = Path(model_path)
|
| 39 |
+
if model_path_obj.exists():
|
| 40 |
+
mlflow.log_artifact(str(model_path_obj), artifact_path="model")
|
| 41 |
+
else:
|
| 42 |
+
print(f"Warning: Model path {model_path} does not exist. Artifact not logged.")
|
| 43 |
+
|
| 44 |
+
return run.info.run_id
|
| 45 |
+
|
| 46 |
+
def get_best_run(metric: str = "eval_macro_f1", ascending: bool = False) -> Optional[mlflow.entities.Run]:
|
| 47 |
+
"""
|
| 48 |
+
Retrieves the best run from the experiment based on a specific metric.
|
| 49 |
+
|
| 50 |
+
Args:
|
| 51 |
+
metric: The metric to sort by.
|
| 52 |
+
ascending: True if a lower metric is better (e.g., loss), False for higher is better (e.g., F1).
|
| 53 |
+
|
| 54 |
+
Returns:
|
| 55 |
+
The MLflow Run object for the best run, or None if no runs exist.
|
| 56 |
+
"""
|
| 57 |
+
setup_mlflow()
|
| 58 |
+
|
| 59 |
+
experiment = mlflow.get_experiment_by_name(EXPERIMENT_NAME)
|
| 60 |
+
if not experiment:
|
| 61 |
+
return None
|
| 62 |
+
|
| 63 |
+
runs = mlflow.search_runs(
|
| 64 |
+
experiment_ids=[experiment.experiment_id],
|
| 65 |
+
order_by=[f"metrics.{metric} {'ASC' if ascending else 'DESC'}"],
|
| 66 |
+
max_results=1,
|
| 67 |
+
output_format="list"
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
if not runs:
|
| 71 |
+
return None
|
| 72 |
+
|
| 73 |
+
return runs[0]
|
tests/test_bio_tagger.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
from src.data.bio_tagger import convert_to_bio, bio_to_aspects
|
| 3 |
+
|
| 4 |
+
def test_single_aspect():
|
| 5 |
+
text = "The food was amazing."
|
| 6 |
+
aspects = [{"term": "food", "from": 4, "to": 8}]
|
| 7 |
+
tags = convert_to_bio(text, aspects)
|
| 8 |
+
|
| 9 |
+
assert [t['token'] for t in tags] == ["The", "food", "was", "amazing."]
|
| 10 |
+
assert [t['label'] for t in tags] == ["O", "B-ASP", "O", "O"]
|
| 11 |
+
|
| 12 |
+
def test_multiple_aspects():
|
| 13 |
+
text = "The food was good, but the service was terrible."
|
| 14 |
+
aspects = [
|
| 15 |
+
{"term": "food", "from": 4, "to": 8},
|
| 16 |
+
{"term": "service", "from": 27, "to": 34}
|
| 17 |
+
]
|
| 18 |
+
tags = convert_to_bio(text, aspects)
|
| 19 |
+
|
| 20 |
+
expected_labels = ["O", "B-ASP", "O", "O", "O", "O", "B-ASP", "O", "O"]
|
| 21 |
+
assert [t['label'] for t in tags] == expected_labels
|
| 22 |
+
|
| 23 |
+
def test_no_aspects():
|
| 24 |
+
text = "Everything was fine."
|
| 25 |
+
aspects = []
|
| 26 |
+
tags = convert_to_bio(text, aspects)
|
| 27 |
+
|
| 28 |
+
assert all(t['label'] == "O" for t in tags)
|
| 29 |
+
|
| 30 |
+
def test_multi_word_aspect():
|
| 31 |
+
text = "The operating system is very stable."
|
| 32 |
+
aspects = [{"term": "operating system", "from": 4, "to": 20}]
|
| 33 |
+
tags = convert_to_bio(text, aspects)
|
| 34 |
+
|
| 35 |
+
assert [t['label'] for t in tags] == ["O", "B-ASP", "I-ASP", "O", "O", "O"]
|
| 36 |
+
|
| 37 |
+
def test_adjacent_aspects():
|
| 38 |
+
text = "Great battery life." # Suppose battery and life are separate
|
| 39 |
+
aspects = [
|
| 40 |
+
{"term": "battery", "from": 6, "to": 13},
|
| 41 |
+
{"term": "life.", "from": 14, "to": 19}
|
| 42 |
+
]
|
| 43 |
+
tags = convert_to_bio(text, aspects)
|
| 44 |
+
assert [t['label'] for t in tags] == ["O", "B-ASP", "B-ASP"]
|
| 45 |
+
|
| 46 |
+
def test_bio_to_aspects():
|
| 47 |
+
tokens = ["The", "operating", "system", "and", "battery", "life", "are", "great"]
|
| 48 |
+
labels = ["O", "B-ASP", "I-ASP", "O", "B-ASP", "B-ASP", "O", "O"]
|
| 49 |
+
|
| 50 |
+
extracted = bio_to_aspects(tokens, labels)
|
| 51 |
+
assert extracted == ["operating system", "battery", "life"]
|