# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# # Text Classification
#
# This Notebook focuses on implementing multi-class text classification on Amazon automotive reviews dataset by choosing any one combination of various data transformation techniques and algorithms.
#
# Rating(1-5) is predicted for each review from the dataset.
#
# Best scoring combinations are listed below. Any single combination out of the following can be chosen for data transformation & model training :
#
# * Creation of word embeddings using gensim's word2vec & subsequent training using Random Forest algorithm.
# * Creation of word embeddings using word2vec and/or Smooth Inverse Frequency (SIF) technique & subsequent training using Random Forest algorithm.
# * Vectorisation using Term frequency-inverse document frequency (Tfidf) technique & subsequent training using Random Forest algorithm.
# * Vectorisation using Tfidf technique & subsequent training using Linear support vector clustering (SVC) algorithm.
#
# | Data Transformation  | Training Algorithm |
# | ------------- | ------------- |
# | Word2vec | Random Forest  |
# | Word2vec + SIF  | Random Forest  |
# | TfIdf Vectorization  | Random Forest  |
# | TfIdf Vectorization  | Linear SVC  |

# ### Clone git repo

BRANCH_NAME="master" #Provide git branch name "master" or "dev"
# ! git clone -b $BRANCH_NAME https://github.com/CiscoAI/cisco-kubeflow-starter-pack.git

# ### Install required packages

# !pip install pandas nltk gensim sklearn scikit-learn==0.20.3 imbalanced-learn==0.4.3

# ### Restart Jupyter notebook kernel

from IPython.display import display_html
display_html("<script>Jupyter.notebook.kernel.restart()</script>",raw=True)

# ### Import required libraries

# +
#General
import pandas as pd
import numpy as np
import pickle
import yaml
from joblib import dump
import re
import nltk as nl
import gensim
import yaml
import os
import requests
from collections import Counter

#sklearn
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import LinearSVC
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.preprocessing import LabelEncoder

#nltk
from nltk.corpus import stopwords
nl.download('punkt')
nl.download('stopwords')

#Over-sampling
from imblearn.over_sampling import SMOTENC
# -

# ### Convert dataset from JSON format to CSV format

path = "cisco-kubeflow-starter-pack/apps/retail/customer-reviews/onprem"
json_data = pd.read_json(os.path.join(path, "data/amazon_automotive_reviews.json"), lines=True)
json_data.to_csv('amazon_automotive_reviews.csv', index=False)

# ### Read data from CSV file

raw_data = pd.read_csv('amazon_automotive_reviews.csv')

raw_data.head()

# ### Clean up initial dataset

raw_data['overallRating'] = raw_data['overall']
raw_data = raw_data.drop(['reviewerID','asin','reviewerName','helpful','overall','summary','unixReviewTime','reviewTime'], axis=1)

# ### Clean review text column and remove punctuations and numericals

raw_data['p_review'] = raw_data['reviewText'].apply(lambda x: re.sub(r'[^a-zA-Z\s]','', str(x)))

# ### Choose data transformation method

# +
# Data transformation can be done using either word2vec or Smooth inverse frequency (sif) technique or Tf-Idf vectorization (tfidf).
# Choose from ==> ['word2vec', 'sif', 'tfidf']

data_transform = ''
# -

# ### Choose model training algorithm

# +
# Model training can be done using either random forest (rf) or Linear Support vector clustering (lsvc) algorithms.
#Choose from ==> ['rf','lsvc']

train_algorithm = ''
# -

# ### Validate data transformation method & training algorithm options

if not data_transform or data_transform not in ['word2vec', 'sif', 'tfidf']:
     raise ValueError("Set a valid method to perform data transformation (word2vec/sif/tfidf)")

if not train_algorithm or train_algorithm not in ['rf','lsvc']:
     raise ValueError("Set a valid algorithm to train your model(rf/lsvc)")

if data_transform in ['word2vec','sif'] and train_algorithm == 'lsvc':
    raise Warning("The combination selected may not be the best scoring one!")

# ### Apply data transformation on data as per selected choice

if data_transform in ['word2vec', 'sif']:

        p_review = raw_data['p_review'].to_list()

        tokens = [nl.word_tokenize(sentences) for sentences in p_review]

        stop_words = stopwords.words('english')

        tokens = [[word for word in tokens[i] if not word in stopwords.words('english')] for i in range(len(tokens))]

        wv_model = gensim.models.Word2Vec(tokens, size=300, min_count=1, workers=4)

        wv_model.train(tokens, total_examples=len(tokens), epochs=50)
        
        print("Word2vec model generated & trained on tokens from review text")

# +
if data_transform == 'word2vec':
        
        print("Preparing training data using word2vec..")
        wv_train = []
        for i in range(len(tokens)):
            wv_train.append(np.mean(np.asarray([wv_model[token] for token in tokens[i]]),axis=0))
        print("Completed")
            
elif data_transform == 'sif':
    
        print("Preparing training data using Smooth inverse frequency(SIF)..")
        vlookup = wv_model.wv.vocab
        Z = 0
        for k in vlookup:
                Z += vlookup[k].count # Compute the normalization constant Z

        a = 0.001
        embedding_size = 300
        wv_sif_train = []
        for i in range(len(tokens)):
                vs = np.zeros(300)
                for word in tokens[i]:
                        a_value = a / (a + (vlookup[word].count/Z))
                        vs = np.add(vs, np.multiply(a_value, wv_model.wv[word]))
                wv_sif_train.append(np.divide(vs, len(tokens[i])))
        print("Completed")
                
elif data_transform == 'tfidf':
         print("Preparing training data using TfIdf vectorization..")
         tfidf = TfidfVectorizer(ngram_range=(1,2),sublinear_tf=True, min_df=5, norm='l2', encoding='latin-1', stop_words='english')
         features = tfidf.fit_transform(raw_data.p_review).toarray()
         print(features.shape)
         print("Completed")
# -

# ### Depict class imbalance issue in dataset using value count for each rating
#
# Here rating 5 has lot more records than others, so the dataset is considered to be highly skewed / imbalanced.

raw_data.overallRating.value_counts()

# ### Initialize target variable to a local variable

y = raw_data.overallRating

# ### Resample dataset to remove class imbalance issue using SMOTENC
#
# Preprocessing of the dataset is done in such a way that the rating categories other than 5 ( which is the majority class) is oversampled accordingly, so as to get a balanced dataset without any prediction output bias.

# +
sm = SMOTENC(sampling_strategy={1: 6000, 2: 6200, 3 : 6800, 4: 11000}, random_state=42, categorical_features=[1])
if data_transform == 'word2vec':
    X_resampled, y_resampled = sm.fit_resample(np.asarray(wv_train), y)
    
elif data_transform == 'sif':
    X_resampled, y_resampled = sm.fit_resample(np.asarray(wv_sif_train), y)
    
elif data_transform == 'tfidf':
    X_resampled, y_resampled = sm.fit_resample(features, y)

print('Resampled dataset samples per class {}'.format(Counter(y_resampled)))
# -

# ### Split train & test data

# +
if data_transform == 'word2vec':
    x_train, x_test, y_train, y_test = train_test_split(X_resampled,y_resampled,test_size=0.3,shuffle=True,random_state=7)

elif data_transform == 'sif':
    x_train, x_test, y_train, y_test = train_test_split(X_resampled,y_resampled,test_size=0.3,shuffle=True,random_state=7)

elif data_transform == 'tfidf':
    x_train, x_test, y_train, y_test = train_test_split(X_resampled,y_resampled,test_size=0.3,shuffle=True,random_state=7)
    
# -

# ### Train model

# +
if train_algorithm == 'rf':
    model = RandomForestClassifier(n_estimators=40, random_state=0)
    model.fit(x_train,y_train)
    
elif train_algorithm == 'lsvc':
    model = LinearSVC()
    model.fit(x_train, y_train)  
# -

# ### Save model

# +
file_path = '/home/jovyan'
file_rel_path = 'model/'
file_abs_path = os.path.join(file_path, file_rel_path)
file_name = 'model.joblib'

if not os.path.exists(file_abs_path):
    os.mkdir(file_abs_path)
dump(model, file_abs_path + file_name)
# -

# ### Define inference service name & model storage URI

# +
svc_name = 'text-classify'

# !kubectl get pods $HOSTNAME -o yaml -n anonymous > podspec
with open("podspec") as f:
    content = yaml.safe_load(f)
    for elm in content['spec']['volumes']:
        if 'workspace-' in elm['name']:
            pvc = elm['name']
os.remove('podspec')
pvc
    
storageURI = "pvc://" + pvc + '/' + file_rel_path
print(storageURI)
# -

# ### Define configuration for inference service creation

# +
wsvol_blerssi_kf = f"""apiVersion: "serving.kubeflow.org/v1alpha2"
kind: "InferenceService"
metadata:
  name: {svc_name}
  namespace: anonymous
spec:
  default:
    predictor:
      sklearn:
        storageUri: {storageURI}
"""
    
kfserving = yaml.safe_load(wsvol_blerssi_kf)
with open('blerssi-kfserving.yaml', 'w') as file:
    yaml_kfserving = yaml.dump(kfserving,file)

# ! cat blerssi-kfserving.yaml
# -

# ### Apply the configuration .yaml file

# !kubectl apply -f blerssi-kfserving.yaml

# ### Check whether inferenceservice is created

# !kubectl get inferenceservice -n anonymous

# ### Note:
#
# Wait for inference service READY="True"

# ### Predict data from serving after setting INGRESS_IP

# +
predictions = []

host_name = svc_name + '.anonymous.example.com'

headers = { 
    'host': host_name
}

for i in range(15):

    formData = {
        'instances': x_test[i:i+1].tolist()
    }
    url = 'http://<<INGRESS IP>>:<<INGRESS PORT>>/v1/models/' + svc_name + ':predict'
    res = requests.post(url, json=formData, headers=headers)
    results = res.json()
    prediction = results['predictions']
    predictions.append(prediction[0])
    
print("Predictions")
print(predictions)
# -

# ## Clean up after predicting

# ### Delete inference service

# !kubectl delete -f blerssi-kfserving.yaml

# ### Delete model folder

# !rm -rf $file_rel_path

StarcoderdataJupyterScriptsDedupFiltered