Signed-off-by: András Schmelczer <andras@schmelczer.dev>
This commit is contained in:
Andras Schmelczer 2022-04-02 13:57:16 +02:00
parent 889e79174b
commit 60cd55c0cd
No known key found for this signature in database
GPG key ID: 39260B5B0614A13E
13 changed files with 168 additions and 116 deletions

View file

@ -1 +1 @@
model_key =
model_key = "domain-prediction-v2"

View file

@ -1,12 +1,10 @@
import re
from sus.clean import clean
from sus.lemmatize_text import lemmatize_text
import re
def preprocess(text: str) -> str:
cleaned = clean(text, convert_to_ascii=True)
lemmas = [
re.sub(r'\d+', 'NUM', lemma)
for lemma in lemmatize_text(cleaned)
]
lemmas = [re.sub(r"\d+", "NUM", lemma) for lemma in lemmatize_text(cleaned)]
return " ".join(lemmas)

View file

@ -1,8 +1,9 @@
from pydantic import BaseModel
from typing import List
from pydantic import BaseModel
class DomainPrediction(BaseModel):
domain: str
probability: float
explanation: List[str]
explanation: List[str]

View file

@ -1,56 +1,65 @@
from models import DomainPrediction
from typing import Iterable, List, Dict
from sklearn.pipeline import Pipeline
from helper import preprocess
import re
from typing import Dict, Iterable, List
from helper import preprocess
from models import DomainPrediction
from sklearn.pipeline import Pipeline
# from sus.use_model import use_model
from config import model_key
# @use_model(model_key, version="latest")
def predict(text: str, model: Pipeline, cut_off_probability: float=0.2) -> List[DomainPrediction]:
def predict(
text: str, model: Pipeline, cut_off_probability: float = 0.2
) -> List[DomainPrediction]:
assert 0 <= cut_off_probability <= 1
feature_names = model.named_steps['vectorizer'].get_feature_names_out()
feature_names = model.named_steps["vectorizer"].get_feature_names_out()
token_mapping = {
preprocess(original): original
for original in re.sub(r'[^a-zA-Z0-9]', ' ', text).split(' ')
for original in re.sub(r"[^a-zA-Z0-9]", " ", text).split(" ")
}
features = model.named_steps['vectorizer'].transform([text])
prediction = model.named_steps['classifier'].predict_proba(features)[0]
features = model.named_steps["vectorizer"].transform([text])
prediction = model.named_steps["classifier"].predict_proba(features)[0]
best_classes = sorted(enumerate(prediction), key=lambda v: v[1], reverse=True)
results: List[DomainPrediction] = []
for class_index, probability in best_classes:
weights = model.named_steps['classifier'].feature_log_prob_[class_index]
results.append(DomainPrediction(
domain=model.named_steps['classifier'].classes_[class_index],
probability=round(probability * 100),
explanation=_get_explanation(
feature_names=feature_names,
features=features.A[0],
weights=weights,
token_mapping=token_mapping
weights = model.named_steps["classifier"].feature_log_prob_[class_index]
results.append(
DomainPrediction(
domain=model.named_steps["classifier"].classes_[class_index],
probability=round(probability * 100),
explanation=_get_explanation(
feature_names=feature_names,
features=features.A[0],
weights=weights,
token_mapping=token_mapping,
),
)
))
)
if sum(r.probability for r in results) >= cut_off_probability:
break
return results
def _get_explanation(feature_names: Iterable[str], features: Iterable[float], weights: Iterable[float], token_mapping: Dict[str, str]) -> List[str]:
def _get_explanation(
feature_names: Iterable[str],
features: Iterable[float],
weights: Iterable[float],
token_mapping: Dict[str, str],
) -> List[str]:
influential = [
(value * weight, name)
for name, value, weight in zip(feature_names, features, weights)
if value
]
most_influential = sorted(influential, reverse=True)[:5]
return [token_mapping[v[1]] for v in most_influential]

View file

@ -85,10 +85,7 @@
"source": [
"def preprocess(text: str) -> str:\n",
" cleaned = clean(text, convert_to_ascii=True)\n",
" lemmas = [\n",
" re.sub(r'\\d+', 'NUM', lemma)\n",
" for lemma in lemmatize_text(cleaned)\n",
" ]\n",
" lemmas = [re.sub(r\"\\d+\", \"NUM\", lemma) for lemma in lemmatize_text(cleaned)]\n",
" return \" \".join(lemmas)"
]
},
@ -157,14 +154,14 @@
"source": [
"corpora = list(SS_CORPUS_PATH.glob(f\"{PREFIX}*.json\"))\n",
"shuffle(corpora)\n",
"print(f'Found {len(corpora)} files')\n",
"print(f\"Found {len(corpora)} files\")\n",
"\n",
"data = []\n",
"for p in corpora[:MAX_FILE_COUNT]:\n",
" with open(p) as f:\n",
" data.extend(json.load(f).items())\n",
"\n",
"print(f'Found {len(data)} documents')"
"print(f\"Found {len(data)} documents\")"
]
},
{
@ -174,23 +171,12 @@
"outputs": [],
"source": [
"X_train, X_test, y_train, y_test = train_test_split(\n",
" [d[0] for d in data],\n",
" [d[1] for d in data],\n",
" test_size=0.1, \n",
" random_state=SEED\n",
" [d[0] for d in data], [d[1] for d in data], test_size=0.1, random_state=SEED\n",
")\n",
"\n",
"X_train = [\n",
" x\n",
" for x, y in zip(X_train, y_train)\n",
" for domain in y\n",
"]\n",
"X_train = [x for x, y in zip(X_train, y_train) for domain in y]\n",
"\n",
"y_train = [\n",
" domain\n",
" for x, y in zip(X_train, y_train)\n",
" for domain in y\n",
"]"
"y_train = [domain for x, y in zip(X_train, y_train) for domain in y]"
]
},
{
@ -207,23 +193,23 @@
"outputs": [],
"source": [
"classifier = GridSearchCV(\n",
" Pipeline(steps=[\n",
" ('vectorizer', TfidfVectorizer()),\n",
" ('classifier', ComplementNB())\n",
" ]),\n",
" Pipeline(steps=[(\"vectorizer\", TfidfVectorizer()), (\"classifier\", ComplementNB())]),\n",
" {\n",
" \"vectorizer__max_df\": [0.05, 0.1, 0.3],\n",
" \"vectorizer__min_df\": [5, 10, 30, 100],\n",
" \"vectorizer__sublinear_tf\": [True, False],\n",
" \"classifier__alpha\": [0.001, 0.1, 0.5, 1],\n",
" \"classifier__fit_prior\": [True, False]\n",
" \"classifier__fit_prior\": [True, False],\n",
" },\n",
" scoring=\"f1_macro\",\n",
" cv=3,\n",
" n_jobs=12,\n",
" verbose=7,\n",
" cv=3,\n",
" n_jobs=12,\n",
" verbose=7,\n",
")\n",
"classifier.fit(\n",
" X_train[:HYPERPARAMETER_OPTIMISATION_SIZE],\n",
" y_train[:HYPERPARAMETER_OPTIMISATION_SIZE],\n",
")\n",
"classifier.fit(X_train[:HYPERPARAMETER_OPTIMISATION_SIZE], y_train[:HYPERPARAMETER_OPTIMISATION_SIZE])\n",
"\n",
"results = pd.DataFrame(classifier.cv_results_)\n",
"results.sort_values(\"rank_test_score\")"
@ -247,10 +233,12 @@
}
],
"source": [
"classifier = Pipeline(steps=[\n",
" ('vectorizer', TfidfVectorizer(min_df=10, max_df=0.05)),\n",
" ('classifier', ComplementNB(alpha=0.5, fit_prior=False))\n",
"])\n",
"classifier = Pipeline(\n",
" steps=[\n",
" (\"vectorizer\", TfidfVectorizer(min_df=10, max_df=0.05)),\n",
" (\"classifier\", ComplementNB(alpha=0.5, fit_prior=False)),\n",
" ]\n",
")\n",
"\n",
"classifier.fit(X_train, y_train)"
]
@ -310,7 +298,11 @@
"\n",
"print(metrics.classification_report(y_test_aligned, predicted))\n",
"metrics.ConfusionMatrixDisplay.from_predictions(\n",
" y_true=y_test_aligned, y_pred=predicted, xticks_rotation=\"vertical\", normalize=\"pred\", values_format='.2f'\n",
" y_true=y_test_aligned,\n",
" y_pred=predicted,\n",
" xticks_rotation=\"vertical\",\n",
" normalize=\"pred\",\n",
" values_format=\".2f\",\n",
")\n",
"None"
]
@ -333,7 +325,7 @@
"outputs": [],
"source": [
"for X, y in zip(X_test[:50], y_test):\n",
" print(', '.join(y))\n",
" print(\", \".join(y))\n",
" pprint(predict(X))\n",
" print()"
]