<jupyter_start><jupyter_text>ML Pipeline Preparation 1. Import libraries and load data from database<jupyter_code># import libraries
import numpy as np
import pandas as pd
from sqlalchemy import create_engine

import re
import time
import nltk
from nltk.corpus import stopwords
from nltk.stem.wordnet import WordNetLemmatizer
from nltk.tokenize import word_tokenize

nltk.download('punkt')
nltk.download('stopwords')
nltk.download('wordnet')

from sklearn.metrics import classification_report
from sklearn.ensemble import RandomForestClassifier
from sklearn.multioutput import MultiOutputClassifier
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.pipeline import Pipeline, FeatureUnion
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
import pickle
# load data from database
engine = create_engine('sqlite:///data/DisasterResponse.db')
df = pd.read_sql_table('cleaned_data', engine)
X = df['message'].values
Y = df.iloc[:,4:]

# split to training and testing sets
X_train, X_test, Y_train, Y_test = train_test_split(X, Y)<jupyter_output><empty_output><jupyter_text>2. Write tokenization function to process text data<jupyter_code>def tokenize(text):

    '''
    The function tokenizes input text.

    Input:
    text: text to be tokenized

    Output:
    tokens: tokens of input text, transfomations include:
        1) replacing urls with placeholder
        2) normalization
        3) removing punctuations
        4) tokenize
        5) removing stopwords
        6) lemmatization
    '''
    
    # replace urls with urlplaceholder
    url_regex = 'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
    detected_urls = re.findall(url_regex, text)
    for url in detected_urls:
        text = text.replace(url, "urlplaceholder")    
    
    # normalize, remove puntuations, tokenize, remove stopwords and lemmatize
    text = re.sub(r"[^a-zA-Z0-9]", " ", text.lower())
    tokens = word_tokenize(text)
    tokens = [WordNetLemmatizer().lemmatize(word) for word in tokens if word not in stopwords.words("english")]
    
    return tokens<jupyter_output><empty_output><jupyter_text>3. Build a machine learning pipeline<jupyter_code># build ML pipeline that consists of count vectorizer and tf-idf transformers, and random forest classifier
pipeline = Pipeline([
    ('vect', CountVectorizer(tokenizer=tokenize)),
    ('tfidf', TfidfTransformer()),
    ('clf', MultiOutputClassifier(RandomForestClassifier()))
])<jupyter_output><empty_output><jupyter_text>4. Train pipeline<jupyter_code># train and time pipeline
t0 = time.time()
pipeline.fit(X_train, Y_train)
Y_pred = pipeline.predict(X_test)
print(time.time() - t0)<jupyter_output>430.1894052028656<jupyter_text>5. Test model<jupyter_code># print precision, recall, and f1-score of predicted values
print(classification_report(Y_test, Y_pred, target_names = Y.columns))<jupyter_output>precision    recall  f1-score   support

               related       0.84      0.95      0.89      5021
               request       0.84      0.50      0.63      1104
                 offer       0.00      0.00      0.00        29
           aid_related       0.75      0.70      0.72      2666
          medical_help       0.62      0.06      0.11       491
      medical_products       0.86      0.09      0.16       329
     search_and_rescue       0.47      0.04      0.08       190
              security       0.00      0.00      0.00       116
              military       0.70      0.08      0.14       183
           child_alone       0.00      0.00      0.00         0
                 water       0.88      0.32      0.47       417
                  food       0.83      0.65      0.73       714
               shelter       0.82      0.37      0.51       602
              clothing       1.00      0.08      0.15       110
                 money       0.80      [...]<jupyter_text>6. Improve the model using grid search<jupyter_code># print pipeline parameters
pipeline.get_params()
# specify parameter grid to be searched on, and define cv pieline
parameters = {
    'clf__estimator__n_estimators': [25, 50], #50
    'clf__estimator__max_depth': [300, 400], #400
    'clf__estimator__min_samples_split': [4, 6], #6
}

cv = GridSearchCV(pipeline, param_grid=parameters)
# train cv pipeline
t0 = time.time()
cv.fit(X_train,Y_train)
Y_pred_cv = cv.predict(X_test)
print(time.time() - t0)
# print best parameters
cv.best_params_<jupyter_output><empty_output><jupyter_text>7. Test tuned model<jupyter_code># print results of tuned model
print(classification_report(Y_test, Y_pred_cv, target_names = Y.columns))<jupyter_output>precision    recall  f1-score   support

               related       0.83      0.95      0.89      5075
               request       0.86      0.52      0.65      1148
                 offer       0.00      0.00      0.00        28
           aid_related       0.75      0.72      0.73      2752
          medical_help       0.65      0.12      0.20       489
      medical_products       0.73      0.06      0.11       324
     search_and_rescue       0.56      0.05      0.09       189
              security       0.00      0.00      0.00       106
              military       0.60      0.07      0.12       219
           child_alone       0.00      0.00      0.00         0
                 water       0.89      0.33      0.48       427
                  food       0.86      0.53      0.65       766
               shelter       0.83      0.42      0.56       583
              clothing       0.78      0.16      0.26        88
                 money       0.50      [...]<jupyter_text>8. Improve and test model by adding feature<jupyter_code># define method to extract text length
class TextLengthExtractor(BaseEstimator, TransformerMixin):
    
    def fit(self, X, y=None):
        return self
    
    def transform(self, X):
        return pd.DataFrame(pd.Series(X).apply(len))
# modify pipeline to allow for additional feature
pipeline2 = Pipeline([
    ('features', FeatureUnion([
        
        ('text_pipeline', Pipeline([
            ('vect', CountVectorizer(tokenizer=tokenize)),
            ('tfidf', TfidfTransformer())                        
        ])),   
        
        ('text_length', TextLengthExtractor())

    ])),
    ('clf', MultiOutputClassifier(RandomForestClassifier()))
])
# train new pipeline
t0 = time.time()
pipeline2.fit(X_train, Y_train)
Y_pred2 = pipeline2.predict(X_test)
print(time.time() - t0)
# print evaluation results
print(classification_report(Y_test, Y_pred2, target_names = Y.columns))
# look for parameters of improved model using grid search
parameters = {
    'clf__estimator__n_estimators': [25, 50], #50
    'clf__estimator__max_depth': [300, 400], #400
    'clf__estimator__min_samples_split': [4, 6], #4
}

cv2 = GridSearchCV(pipeline2, param_grid=parameters)
t0 = time.time()
cv2.fit(X_train,Y_train)
Y_pred_cv2 = cv2.predict(X_test)
print(time.time() - t0)
cv2.best_params_
print(classification_report(Y_test, Y_pred_cv2, target_names = Y.columns))<jupyter_output>precision    recall  f1-score   support

               related       0.83      0.96      0.89      5075
               request       0.84      0.51      0.63      1148
                 offer       0.00      0.00      0.00        28
           aid_related       0.75      0.72      0.73      2752
          medical_help       0.59      0.11      0.19       489
      medical_products       0.75      0.09      0.16       324
     search_and_rescue       0.52      0.07      0.13       189
              security       0.00      0.00      0.00       106
              military       0.65      0.07      0.12       219
           child_alone       0.00      0.00      0.00         0
                 water       0.91      0.38      0.54       427
                  food       0.83      0.64      0.72       766
               shelter       0.82      0.39      0.53       583
              clothing       0.76      0.15      0.25        88
                 money       0.50      [...]<jupyter_text>9. Export model as a pickle file<jupyter_code>pickle.dump(pipeline2, open('classifier.pkl', 'wb'))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08961352657004831, 'fraction_numerical': 0.07391304347826087, 'mean_word_length': 1.9168721380767877, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '102694', 'license': 'CNRI-Python', 'n_tokens_mistral': 2876, 'n_tokens_neox': 2630, 'n_words': 650, 'path': 'ML_pipeline.ipynb', 'repo_name': 'cmeng94/disaster-response-pipelines', 'size': '8280'}