Repository: GlobalMaksimum/sadedegel (language: python)

Issue:
Movie Review Sentiment Prebuilt Model
For the purpose of adding different use cases of our prebuilt function we could implement a movie review sentiment analysis model. There is a decent dataset [available here](https://www.kaggle.com/mustfkeskin/turkish-movie-sentiment-analysis-dataset)  over 80k samples.

For this, these steps going be taken:

- Prepare the dataset,
- Built and test model accuracy,
- Publish the prebuilt model,



Discussion / hints:

Fix patch:
diff --git a/sadedegel/prebuilt/README.md b/sadedegel/prebuilt/README.md
index 1ed48cc8..78b5a5fa 100644
--- a/sadedegel/prebuilt/README.md
+++ b/sadedegel/prebuilt/README.md
@@ -77,4 +77,26 @@ y_pred = model.predict([])
 
 Current prebuilt model has 
 * 3-fold cross validation F1 macro score of `mean 0.7254, std 0.0034)`.
-* 5-fold cross validation F1 macro score of `mean 0.7271, std 0.0094)` 
\ No newline at end of file
+* 5-fold cross validation F1 macro score of `mean 0.7271, std 0.0094)` 
+
+### Turkish Movie Review Sentiment Classification
+
+Classifier assigns each Turkish movie review text into two classes ('NEGATIVE', POSITIVE') by using sadedegel built-in pipeline.
+
+#### Loading and Predicting with the Model:
+
+```python
+from sadedegel.prebuilt import movie_reviews
+# We load our prebuilt model:
+model = movie_reviews.load()
+
+# Here we feed our text to get predictions:
+y_pred = model.predict([<Your text goes here>])
+
+# You can check original test results on holdout set:
+movie_reviews.evaluate()
+```
+
+#### Accuracy
+
+* Current prebuilt movie review model has a **macro-F1** score of `0.825` on holdout test set model never seen before.
\ No newline at end of file
diff --git a/sadedegel/prebuilt/model/movie_sentiment.joblib b/sadedegel/prebuilt/model/movie_sentiment.joblib
new file mode 100644
index 00000000..6f4d19e4
--- /dev/null
+++ b/sadedegel/prebuilt/model/movie_sentiment.joblib
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:eb3348d6f1e1725ccd75eda7446c62efc882cf0cab54d02530e92440ca4d87b7
+size 335083
diff --git a/sadedegel/prebuilt/movie_reviews.py b/sadedegel/prebuilt/movie_reviews.py
new file mode 100644
index 00000000..598a0a94
--- /dev/null
+++ b/sadedegel/prebuilt/movie_reviews.py
@@ -0,0 +1,83 @@
+from os.path import dirname
+from pathlib import Path
+
+from joblib import dump
+from rich.console import Console
+from sklearn.linear_model import LogisticRegression
+from sklearn.metrics import f1_score
+from sklearn.utils import shuffle
+from sklearn.pipeline import Pipeline
+
+from ..extension.sklearn import TfidfVectorizer, Text2Doc
+from .util import load_model
+from ..dataset.movie_sentiment import load_movie_sentiment_train, load_movie_sentiment_test, \
+    load_movie_sentiment_target, CORPUS_SIZE
+
+console = Console()
+
+
+def empty_model():
+    return Pipeline(
+        [('text2doc', Text2Doc('icu')),
+         ('tfidf', TfidfVectorizer(tf_method='log_norm', idf_method='smooth', drop_punct=True,
+                                   lowercase=True, show_progress=True)),
+         ('logreg', LogisticRegression(penalty='l2', C=0.1905922481841914, fit_intercept=True, solver='liblinear'))
+         ]
+    )
+
+def build(max_rows=-1):
+    try:
+        import pandas as pd
+    except ImportError:
+        console.log(("pandas package is not a general sadedegel dependency."
+                     " But we do have a dependency on building our prebuilt models"))
+
+    raw = load_movie_sentiment_train()
+    df = pd.DataFrame.from_records(raw)
+
+    df = shuffle(df, random_state=42)
+
+    if max_rows > 0:
+        df = df.sample(max_rows, random_state=42)
+
+    console.log(f"Corpus Size: {CORPUS_SIZE}")
+
+    pipeline = empty_model()
+
+    pipeline.fit(df.text, df.sentiment_class)
+
+    console.log("Model build [green]DONE[/green]")
+
+    model_dir = Path(dirname(__file__)) / 'model'
+
+    model_dir.mkdir(parents=True, exist_ok=True)
+
+    pipeline.steps[0][1].Doc = None
+
+    dump(pipeline, (model_dir / 'movie_sentiment.joblib').absolute(), compress=('gzip', 9))
+
+
+def load(model_name="movie_sentiment"):
+    return load_model((model_name))
+
+    return pipeline
+
+def evaluate():
+    try:
+        import pandas as pd
+    except ImportError:
+        console.log(("pandas package is not a general sadedegel dependency."
+                     " But we do have a dependency on building our prebuilt models"))
+    model = load()
+
+    raw_test = load_movie_sentiment_test()
+    test = pd.DataFrame.from_records(raw_test)
+    true_labels = pd.DataFrame.from_records(load_movie_sentiment_target())
+
+    y_pred = model.predict(test.text)
+
+    console.log(f"Model test accuracy (f1-macro): {f1_score(true_labels.sentiment_class, y_pred, average='macro')}")
+
+
+if __name__ == '__main__':
+    build(-1)


Test patch:
diff --git a/tests/prebuilt/context.py b/tests/prebuilt/context.py
index 74df77f1..e0b6bef0 100644
--- a/tests/prebuilt/context.py
+++ b/tests/prebuilt/context.py
@@ -7,6 +7,7 @@
 from sadedegel.prebuilt import news_classification, tweet_profanity  # noqa # pylint: disable=unused-import, wrong-import-position
 from sadedegel.dataset.tscorpus import CATEGORIES # noqa # pylint: disable=unused-import, wrong-import-position
 from sadedegel.dataset.profanity import CLASS_VALUES # noqa # pylint: disable=unused-import, wrong-import-position
-from sadedegel.prebuilt import tweet_sentiment  # noqa # pylint: disable=unused-import, wrong-import-position
+from sadedegel.prebuilt import tweet_sentiment , movie_reviews # noqa # pylint: disable=unused-import, wrong-import-position
 from sadedegel.dataset.tweet_sentiment import CLASS_VALUES as SENTIMENT_VALUES  # noqa # pylint: disable=unused-import, wrong-import-position
+from sadedegel.dataset.movie_sentiment import CLASS_VALUES as SENTIMENT_VALUES_M  # noqa # pylint: disable=unused-import, wrong-import-position
 
diff --git a/tests/prebuilt/test_movie_reviews.py b/tests/prebuilt/test_movie_reviews.py
new file mode 100644
index 00000000..7ceea753
--- /dev/null
+++ b/tests/prebuilt/test_movie_reviews.py
@@ -0,0 +1,14 @@
+import pytest
+from .context import movie_reviews, SENTIMENT_VALUES_M
+
+def test_model_load():
+    model = movie_reviews.load()
+    pred_neg = model.predict(['çok sıkıcı bir filmdi'])
+    pred_pos = model.predict(['süper aksiyon, tavsiye ederim'])
+
+    prob_pos = model.predict_proba(['süper aksiyon, tavsiye ederim'])
+
+    assert SENTIMENT_VALUES_M[pred_pos[0]] == 'POSITIVE'
+    assert SENTIMENT_VALUES_M[pred_neg[0]] == 'NEGATIVE'
+    assert prob_pos[0][1] >= 0.5
+    assert prob_pos.shape == (1, 2)
\ No newline at end of file