{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# # This Python 3 environment comes with many helpful analytics libraries installed\n# # It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# # For example, here's several helpful packages to load\n\n# import numpy as np # linear algebra\n# import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# # Input data files are available in the read-only \"../input/\" directory\n# # For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\n# import os\n# for dirname, _, filenames in os.walk('/kaggle/input'):\n#     for filename in filenames:\n#         print(os.path.join(dirname, filename))\n\n# # You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# # You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2021-06-10T02:36:39.53211Z","iopub.execute_input":"2021-06-10T02:36:39.532505Z","iopub.status.idle":"2021-06-10T02:36:39.536329Z","shell.execute_reply.started":"2021-06-10T02:36:39.532404Z","shell.execute_reply":"2021-06-10T02:36:39.5354Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> # Khai báo thư viện","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt \nimport seaborn as sns\nimport nltk\nimport pickle\nimport tensorflow as tf\n\nfrom nltk import word_tokenize\nfrom nltk.tokenize import word_tokenize\n\nfrom collections import Counter\n\nfrom sklearn.feature_extraction.text import CountVectorizer\nfrom sklearn.model_selection import KFold\nfrom sklearn.metrics import f1_score\nfrom sklearn.metrics import classification_report\nfrom sklearn.model_selection import train_test_split\n\nfrom tensorflow.keras.preprocessing.text import Tokenizer\nfrom tensorflow.keras.preprocessing.sequence import pad_sequences\nfrom tensorflow.keras import regularizers\n\nfrom tensorflow import keras\nfrom keras.models import Model\nfrom keras.layers import Dense, Input, LSTM, GRU, Embedding, Dropout, Activation, Conv1D\nfrom keras.layers import Bidirectional, GlobalMaxPool1D, GlobalMaxPooling1D, GlobalAveragePooling1D\nfrom keras.layers import Input, Embedding, Dense, Conv2D, MaxPool2D, concatenate\nfrom keras.layers import Reshape, Flatten, Concatenate, Dropout, SpatialDropout1D\nfrom keras.optimizers import Adam\nfrom keras.callbacks import EarlyStopping, ReduceLROnPlateau","metadata":{"execution":{"iopub.status.busy":"2021-06-10T16:03:23.842823Z","iopub.execute_input":"2021-06-10T16:03:23.843238Z","iopub.status.idle":"2021-06-10T16:03:23.865269Z","shell.execute_reply.started":"2021-06-10T16:03:23.843184Z","shell.execute_reply":"2021-06-10T16:03:23.863797Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> # Bước chuẩn bị","metadata":{}},{"cell_type":"markdown","source":"**Đọc lại 2 tập text (train, test) đã tiền xử lý trước đó**","metadata":{}},{"cell_type":"code","source":"train_text = pd.read_pickle(r'../input/train-text/train_texts.pkl')\ntest_text = pd.read_pickle(r'../input/test-text/test_texts.pkl')\n\nlen(train_text), len(test_text)","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:44:31.316419Z","iopub.execute_input":"2021-06-10T13:44:31.316715Z","iopub.status.idle":"2021-06-10T13:44:33.114230Z","shell.execute_reply.started":"2021-06-10T13:44:31.316684Z","shell.execute_reply":"2021-06-10T13:44:33.112988Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Lấy ra target từ train.csv để dùng trong huấn luyện","metadata":{}},{"cell_type":"code","source":"train = pd.read_csv('../input/quora-insincere-questions-classification/train.csv')\ntarget = train['target'].values\n\nlen(target), target, train.target.value_counts()","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:44:33.116413Z","iopub.execute_input":"2021-06-10T13:44:33.116731Z","iopub.status.idle":"2021-06-10T13:44:36.657311Z","shell.execute_reply.started":"2021-06-10T13:44:33.116702Z","shell.execute_reply":"2021-06-10T13:44:36.656259Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Đồ thị biểu diễn số từ trong mỗi câu ở tập train_text và test_text**","metadata":{}},{"cell_type":"code","source":"def get_text_len(texts):\n    text_len = []\n    for text in texts:\n        text_len.append(len(text.split(' ')))\n    \n    return text_len\n\ndef show_barchart(text_len, name):\n    text_x = Counter(text_len).keys()\n    text_y = Counter(text_len).values()\n    \n    print(name, len(text_x), text_x)\n    print(name, len(text_y), text_y)\n    plt.bar(text_x, text_y)\n    plt.xlabel('Số từ trong 1 câu') \n    plt.ylabel('Số lượng câu') \n    plt.title('Tập ' + name)\n                        \n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:44:36.659568Z","iopub.execute_input":"2021-06-10T13:44:36.659986Z","iopub.status.idle":"2021-06-10T13:44:36.668391Z","shell.execute_reply.started":"2021-06-10T13:44:36.659941Z","shell.execute_reply":"2021-06-10T13:44:36.666880Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_text_len = get_text_len(train_text)\ntest_text_len = get_text_len(test_text)\n\nshow_barchart(train_text_len, 'train_text')\nshow_barchart(test_text_len, 'test_text')","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:44:36.670743Z","iopub.execute_input":"2021-06-10T13:44:36.671315Z","iopub.status.idle":"2021-06-10T13:44:38.645779Z","shell.execute_reply.started":"2021-06-10T13:44:36.671268Z","shell.execute_reply":"2021-06-10T13:44:38.644662Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Như đã nhận xét ở phần EDA, có thể thấy các câu hỏi thường có độ dài là 1->30 từ, câu dài nhất lần lượt chứa 125 và 120 từ ở tập train, test. Nếu sử dụng độ dài lớn nhất của 1 câu để làm số hàng của ma trận vecto câu thì điều đó là rất bất lợi vì:  \n* Số câu có > 30 từ chiếm số lượng rất ít: <1% => không có nhiều ảnh hưởng đến việc huấn luyện mô hình.\n* Nếu để số chiều embedding quá lớn thì khi đó một ma trận vecto câu sẽ rất thưa và điều này sẽ gây tốn kém chi phí trong việc huấn luyện.  \n==> Vì vậy, em sẽ chỉ lấy độ dài tối đa trong 1 câu là 30 từ.","metadata":{}},{"cell_type":"markdown","source":"Chuyển dạng của train_text, test_text từ list -> np.ndarray để cùng dạng với target và để phục vụ tính toán trên ma trận khi áp dụng vào trong model sau này.","metadata":{}},{"cell_type":"code","source":"train_text = np.array(train_text)\ntest_text = np.array(test_text)","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:44:38.649236Z","iopub.execute_input":"2021-06-10T13:44:38.649595Z","iopub.status.idle":"2021-06-10T13:44:41.241866Z","shell.execute_reply.started":"2021-06-10T13:44:38.649563Z","shell.execute_reply":"2021-06-10T13:44:41.240852Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Khai báo các tham số quan trọng","metadata":{}},{"cell_type":"code","source":"MAX_LEN = 30    # Độ dài của 1 câu \nEMBEDDING_SIZE = 300    # Số chiều embedding khi sử dụng fastText mặc định là (300,) ","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:44:41.243541Z","iopub.execute_input":"2021-06-10T13:44:41.244139Z","iopub.status.idle":"2021-06-10T13:44:41.250143Z","shell.execute_reply.started":"2021-06-10T13:44:41.244088Z","shell.execute_reply":"2021-06-10T13:44:41.248788Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> # Embedding","metadata":{}},{"cell_type":"markdown","source":"**Load mô hình ngôn ngữ word embedding fastText** (1 trong 3 pre-trained word vector được cho phép sử dụng trong challenge này).   \n- fastText là một thư viện để học cách nhúng từ và phân loại văn bản do phòng nghiên cứu AI của Facebook tạo ra. Mô hình cho phép một người tạo ra một thuật toán học không giám sát hoặc học có giám sát để thu được các biểu diễn vectơ cho các từ.\n- Nó hỗ trợ cả mô hình CBOW và Skip-Gram. Sự khác biệt chính giữa các mô hình khác và FastText đó là: nó chia từ thành vài cụm với ý nghĩa riêng cho mỗi cụm. \n- Ví dụ: tri-gram của từ 'orange' là: 'org', 'ran', 'ang', 'nge' (bỏ qua ranh giới bắt đầu và kết thúc của từ).  \n  * Vectơ embedding cho 'orange' sẽ là tổng của tri-gram này. Các từ hiếm hoặc lỗi chính tả có thể được biểu diễn chính xác vì rất có thể một số n-gam của chúng cũng xuất hiện theo cách khác.\n- However, fastText có thể tạo ra các vectơ tốt hơn bằng cách chia từ thành nhiều phần và sử dụng các vectơ cho các phần đó để tạo ra một vectơ cuối cùng cho từ.","metadata":{}},{"cell_type":"code","source":"from gensim.models import FastText \n\ndef load_fasttext():\n    print('loading word embeddings...')\n    embeddings_index = {}\n    f = open('../input/wiki-news-300d-1m/wiki-news-300d-1M.vec',encoding='utf-8')\n    for line in f:\n        values = line.strip().rsplit(' ')\n        word = values[0]\n        coefs = np.asarray(values[1:], dtype='float32')\n        embeddings_index[word] = coefs\n    f.close()\n    print('found %s word vectors' % len(embeddings_index))\n\n    return embeddings_index","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:44:41.254101Z","iopub.execute_input":"2021-06-10T13:44:41.254546Z","iopub.status.idle":"2021-06-10T13:44:41.390699Z","shell.execute_reply.started":"2021-06-10T13:44:41.254495Z","shell.execute_reply":"2021-06-10T13:44:41.389673Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"embeddings_index = load_fasttext()","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:44:41.393350Z","iopub.execute_input":"2021-06-10T13:44:41.393699Z","iopub.status.idle":"2021-06-10T13:46:47.555855Z","shell.execute_reply.started":"2021-06-10T13:44:41.393657Z","shell.execute_reply":"2021-06-10T13:46:47.553994Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Sau khi load xong mô hình fastText, em **kiểm tra xem tập embeddings_index** (là 1 dictionary form {'word':'word's embedding vector'}) đã **cover được bao nhiêu % số từ có trong các câu text từ 2 tập train và test**","metadata":{}},{"cell_type":"code","source":"# Trước tiên, xây dựng 1 vocab chưa tất cả các từ trong các câu text\ndef build_vocab(texts):\n    vocab = {}\n    for sentence in texts:\n        for word in sentence:\n            try:\n                vocab[word] += 1\n            except KeyError:\n                vocab[word] = 1\n    return vocab\n\n# Hàm check độ cover với tất các từ\ndef check_coverage(vocab, embeddings_index):\n    known_words = {}\n    unknown_words = {}\n    nb_known_words = 0\n    nb_unknown_words = 0\n    for word in vocab.keys():\n        if word in embeddings_index:\n            known_words[word] = embeddings_index[word]\n            nb_known_words += vocab[word]\n        else:\n            unknown_words[word] = vocab[word]\n            nb_unknown_words += vocab[word]\n\n    print('Found embeddings for {:.2%} of vocab'.format(len(known_words) / len(vocab)))\n    print('Found embeddings for  {:.2%} of all text'.format(nb_known_words / (nb_known_words + nb_unknown_words)))\n\n    return unknown_words\n\n# Lời gọi hàm\ndef vocab_check_coverage(all_text):\n    vocab = build_vocab(all_text)\n    print(\"FastText : \")\n    oov_fasttext = check_coverage(vocab, embeddings_index)\n    oov_fasttext = {\"oov_rate\": len(oov_fasttext) / len(vocab), 'oov_words': oov_fasttext}\n\n    return oov_fasttext","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:46:47.557329Z","iopub.execute_input":"2021-06-10T13:46:47.557694Z","iopub.status.idle":"2021-06-10T13:46:47.568875Z","shell.execute_reply.started":"2021-06-10T13:46:47.557666Z","shell.execute_reply":"2021-06-10T13:46:47.567553Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Xây dựng all_text là list chứa tất cả các câu trong 2 tập train, test\n# Thể hiện mức độ cover của thư viện fastText, hay dict embeddings_index, với all_text\nall_text = list(train_text) + list(test_text) \nprint(len(all_text))\noov_fasttext = vocab_check_coverage(all_text)","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:46:47.570653Z","iopub.execute_input":"2021-06-10T13:46:47.571548Z","iopub.status.idle":"2021-06-10T13:46:59.681904Z","shell.execute_reply.started":"2021-06-10T13:46:47.571506Z","shell.execute_reply":"2021-06-10T13:46:59.680746Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Như vậy, fastText đã cover được gần đầy đủ tất cả các từ ở trong vocab cho nên em sẽ không sử dụng thêm các pre-trained word embedding khác để lấp nốt những từ chưa được cover lại nữa.  \nMặc dù em có tham khảo qua các bài đã nộp khác, em thấy nhiều người đã sử dụng kết hợp 2, 3 pre-trained WE bằng cách concat các vecto embedding của các từ ở mỗi thư viện embedding lại với nhau để có vecto của 1 từ dài hơn, hay sử dụng các pre-trained WE để bù lấp các từ còn chưa cover được từ 1 pWE. Tuy nhiên em thấy việc làm như vậy sẽ khiến quá trình huấn luyện trở nên rất lâu mà chỉ cải thiện được kết quả ít.  ","metadata":{}},{"cell_type":"markdown","source":"**Các bước sử dụng pre-trained word embedding ở 1 mô hình sử dụng thư viện Keras là như sau:** \n1. Chuẩn bị dữ liệu \n   - Tiền xử lý\n   - Định dạng các câu text thành các tensor để cho vào mạng nơ-ron. Bước này sẽ sử dụng keras.preprocessing.text.Tokenizer.  \n     **Note** em áp dụng trong toàn bộ tất cả các câu text chứ không chỉ áp dụng cho riêng train/test text để tránh trường hợp các từ chưa được gặp trong test text sẽ không có trong train text và điều này sẽ làm mô hình dự đoán kém chính xác đi.\n2. Chuẩn bị Embedding Layer\n   - Thiết lập các ánh xạ từ 1 từ đến vecto embedding của nó.\n   - Có thể sử dụng từ điển embeddings_index và word_index để tính toán ma trận embedding.\n3. Xây dựng mô hình với Keras và khai báo Embedding Layer\n   - Tại tầng Embedding này, ta sẽ set tham số 'trainable=False' để các vecto embedding không được học trong quá trình huấn luyện mạng.\n   \nNguồn: https://blog.keras.io/using-pre-trained-word-embeddings-in-a-keras-model.html","metadata":{}},{"cell_type":"markdown","source":"(Em đã có tự viết 1 hàm để tự xây dựng lên ma trận embedding cho tất cả các câu. Tuy nhiên, việc xậy dựng thủ công như vậy sẽ chiếm hết bộ nhớ cho phép của kaggle nên em đã chuyển sang sử dụng các ở trên)","metadata":{}},{"cell_type":"code","source":"tokenizer = Tokenizer()\ntokenizer.fit_on_texts(all_text)\n\ntrain_sequences = tokenizer.texts_to_sequences(train_text)\ntest_sequences = tokenizer.texts_to_sequences(test_text)\n\ntrain_padded_sequences = pad_sequences(train_sequences, maxlen=MAX_LEN, padding='post', truncating='post')\ntest_padded_sequences = pad_sequences(test_sequences, maxlen=MAX_LEN, padding='post', truncating='post')","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:46:59.683618Z","iopub.execute_input":"2021-06-10T13:46:59.684237Z","iopub.status.idle":"2021-06-10T13:48:06.170743Z","shell.execute_reply.started":"2021-06-10T13:46:59.684194Z","shell.execute_reply":"2021-06-10T13:48:06.169321Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"word_index = tokenizer.word_index\nnum_words = len(word_index)\nprint('Tổng số từ khác nhau trong all_text: ', num_words)","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:48:06.172801Z","iopub.execute_input":"2021-06-10T13:48:06.173230Z","iopub.status.idle":"2021-06-10T13:48:06.183638Z","shell.execute_reply.started":"2021-06-10T13:48:06.173187Z","shell.execute_reply":"2021-06-10T13:48:06.181895Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Trước khi xây dựng lên ma trận embedding cho tất cả các câu, em có muốn áp dụng việc cân bằng dữ liệu với RandomUnderSampling, RandomOverSampling, SMOTE.  \n1. Để RandomUnder/RandomOver Sampling thì chỉ cần gọi hàm sau đó các câu text sẽ được ngẫu nhiên loại bỏ/copy để cho dữ liệu với 2 nhãn âm/dương cân bằng. Tuy nhiên, vì dữ liệu chênh lệch là quá lớn cho nên khi Sampling đến khi dữ liệu cân bằng thì ta có thể bị mất/ hoặc sinh thêm quá nhiều dữ liệu và có thể sinh ra nhiều hiện tượng khác như bias, overfitting, ...\n2. SMOTE\n- Cơ sở: sau bước Chuẩn bị dữ liệu như đã đề cập ở trên, mỗi từ đã có 1 ánh xạ riêng của nó đến tập word_index (như ô code bên dưới), cho nên 1 câu có thể được biểu thành 1 vecto với các giá trị như ở ví dụ bên dưới với train_padded_sequences[0].\n- Tuy nhiên, vì số lượng câu có nhãn '0' là rất lớn, hơn nữa, 1 câu được xác định là nhãn '1' có thể nhờ dựa vào 1 vài từ với ngữ cảnh cụ thể mang tính tiêu cực trong câu đó cho còn các từ khác sẽ có tính tích cực. Khi áp dụng SMOTE cho tập minority có thể sẽ sinh ra các ma trận mà giá trị (lấy phần nguyên) sẽ gồm các giá trị của các từ mang tính tích cực nhưng nhãn lại là nhãn '1' (tiêu cực)  \n==> Vậy nên, em sẽ không thực hiện bước cân bằng dữ liệu mà em sẽ thay đổi class_weight của tập minority trong quá trình mô hình học với mục đích giúp mô hình dự đoán nhãn thiểu số tốt hơn.","metadata":{}},{"cell_type":"code","source":"word_index['name'], word_index['trumph'], train_padded_sequences[0]","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:48:06.185452Z","iopub.execute_input":"2021-06-10T13:48:06.185932Z","iopub.status.idle":"2021-06-10T13:48:06.201095Z","shell.execute_reply.started":"2021-06-10T13:48:06.185888Z","shell.execute_reply":"2021-06-10T13:48:06.199532Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Xây dựng ma trận embedding**","metadata":{}},{"cell_type":"code","source":"def prepare_matrix(embedding_index, emb_size = EMBEDDING_SIZE):\n    embedding_matrix = np.zeros((num_words+1, emb_size))\n\n    for word, i in word_index.items():\n        if i > num_words:\n            continue\n\n        emb_vec = embedding_index.get(word)\n        if emb_vec is not None:\n            embedding_matrix[i] = emb_vec\n\n    return embedding_matrix","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:48:06.203015Z","iopub.execute_input":"2021-06-10T13:48:06.203529Z","iopub.status.idle":"2021-06-10T13:48:06.214552Z","shell.execute_reply.started":"2021-06-10T13:48:06.203483Z","shell.execute_reply":"2021-06-10T13:48:06.213197Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"embedding_matrix = prepare_matrix(embeddings_index)\nembedding_matrix.shape","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:48:06.216450Z","iopub.execute_input":"2021-06-10T13:48:06.217226Z","iopub.status.idle":"2021-06-10T13:48:06.737888Z","shell.execute_reply.started":"2021-06-10T13:48:06.217176Z","shell.execute_reply":"2021-06-10T13:48:06.736548Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Khai báo một số tham số quan trọng trong quá trình training","metadata":{}},{"cell_type":"code","source":"LEARNING_RATE = 0.001\nBATCH_SIZE = 512\nEPOCHS = 10","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:48:06.739851Z","iopub.execute_input":"2021-06-10T13:48:06.740350Z","iopub.status.idle":"2021-06-10T13:48:06.746468Z","shell.execute_reply.started":"2021-06-10T13:48:06.740277Z","shell.execute_reply":"2021-06-10T13:48:06.744635Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Vì dữ liệu bị imbalance nên em sẽ dùng độ đo f1 để đánh giá","metadata":{}},{"cell_type":"code","source":"from keras import backend as K\n\ndef f1(y_true, y_pred):\n    def recall(y_true, y_pred):\n        true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))\n        possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))\n        recall = true_positives / (possible_positives + K.epsilon())\n        return recall\n\n    def precision(y_true, y_pred):\n        true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))\n        predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))\n        precision = true_positives / (predicted_positives + K.epsilon())\n        return precision\n    \n    precision = precision(y_true, y_pred)\n    recall = recall(y_true, y_pred)\n    return 2*((precision*recall)/(precision+recall+K.epsilon()))","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:48:06.750175Z","iopub.execute_input":"2021-06-10T13:48:06.750676Z","iopub.status.idle":"2021-06-10T13:48:06.763218Z","shell.execute_reply.started":"2021-06-10T13:48:06.750610Z","shell.execute_reply":"2021-06-10T13:48:06.762124Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"-------------------------------------------------------------------------------------------------------------","metadata":{}},{"cell_type":"markdown","source":"## **MODEL**","metadata":{}},{"cell_type":"markdown","source":"Một câu text được xác định là có ý tốt hay không thì rất có khả năng là nó sẽ dựa vào ngữ cảnh của cả câu (ngữ cảnh của các từ trong câu) chứ không chỉ đơn thuần phụ thuộc vào một từ hay cụm từ nào đấy.  \nVì vậy, em sử dụng mô hình mạng RNN để ta có thể lưu lại ngắn hạn ý nghĩa của các từ với nhau, cụ thể là từ phía trước tới từ phía sau, để từ đó phần nào lưu lại được ngữ cảnh của cả câu. Thêm nữa,  ","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"def build_model(embedding_matrix, num_words, EMBEDDING_SIZE):\n    inp = Input(shape=(MAX_LEN,))\n    x = Embedding(num_words+1, EMBEDDING_SIZE, weights=[embedding_matrix], trainable=False)(inp)\n    x = SpatialDropout1D(0.125)(x)\n    x1 = Bidirectional(LSTM(256, return_sequences=True))(x)\n    x2 = Bidirectional(GRU(256, return_sequences=True))(x1)\n    max_pool1 = GlobalMaxPooling1D()(x1)\n    max_pool2 = GlobalMaxPooling1D()(x2)\n    conc = Concatenate()([max_pool1, max_pool2])\n    predictions = Dense(1, activation='sigmoid')(conc)\n    model = Model(inputs=inp, outputs=predictions)\n    adam = Adam(lr=LEARNING_RATE)\n    model.compile(optimizer = adam, loss = 'binary_crossentropy', metrics = ['accuracy',f1])\n    return model","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:48:06.765301Z","iopub.execute_input":"2021-06-10T13:48:06.766240Z","iopub.status.idle":"2021-06-10T13:48:06.776900Z","shell.execute_reply.started":"2021-06-10T13:48:06.766195Z","shell.execute_reply":"2021-06-10T13:48:06.775732Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"es = EarlyStopping(monitor=f1, mode='max', patience=3, restore_best_weights=True)\nreduce_lr = ReduceLROnPlateau(monitor='val_f1', factor=0.1, patience=2, min_=0.0005, verbose=1)","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:48:06.778660Z","iopub.execute_input":"2021-06-10T13:48:06.779264Z","iopub.status.idle":"2021-06-10T13:48:06.793706Z","shell.execute_reply.started":"2021-06-10T13:48:06.779221Z","shell.execute_reply":"2021-06-10T13:48:06.792432Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class_weights = {0: 1, 1: 7}","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:48:06.797452Z","iopub.execute_input":"2021-06-10T13:48:06.797802Z","iopub.status.idle":"2021-06-10T13:48:06.805624Z","shell.execute_reply.started":"2021-06-10T13:48:06.797758Z","shell.execute_reply":"2021-06-10T13:48:06.804308Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def plot_train_val_f1score(hist):\n    plt.figure(figsize=(12,8))\n    epoch_list = list(range(1,len(hist.history['f1'])+1))\n    plt.plot(epoch_list, hist.history['f1'],label='f1')\n    plt.plot(epoch_list, hist.history['val_f1'],label='val_f1')\n    plt.xlabel('epoches')\n    plt.ylabel('score')\n    plt.legend()\n    plt.show()\n\ndef tweak_threshold(pred_prob, truth):\n    thresholds = []\n    scores = []\n    for thresh in np.arange(0.1, 0.501, 0.01):\n        thresh = np.round(thresh, 2)\n        thresholds.append(thresh)\n        score = f1_score(truth, (pred_prob>thresh).astype(int))\n        scores.append(score)\n    return np.max(scores), thresholds[np.argmax(scores)]","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:48:06.807046Z","iopub.execute_input":"2021-06-10T13:48:06.807420Z","iopub.status.idle":"2021-06-10T13:48:06.821627Z","shell.execute_reply.started":"2021-06-10T13:48:06.807378Z","shell.execute_reply":"2021-06-10T13:48:06.820401Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.model_selection import StratifiedKFold\nSFold = StratifiedKFold(n_splits=5, random_state=20, shuffle=True)\nSFold","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:48:06.824035Z","iopub.execute_input":"2021-06-10T13:48:06.824385Z","iopub.status.idle":"2021-06-10T13:48:06.837859Z","shell.execute_reply.started":"2021-06-10T13:48:06.824341Z","shell.execute_reply":"2021-06-10T13:48:06.836631Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"best_score = []\ny_test = np.zeros((test_padded_sequences.shape[0], ), dtype=np.float32)\noof_pred = np.zeros((train_padded_sequences.shape[0], ), dtype=np.float32)\nval_list = []\nhist = []\n\nfor tr_index, val_index in SFold.split(train_padded_sequences, target):\n    val_list += list(val_index)\n    X_tr, X_val, y_tr, y_val = train_padded_sequences[tr_index], train_padded_sequences[val_index], \\\n                                target[tr_index], target[val_index]\n    print(X_tr.shape, X_val.shape, y_tr.shape, y_val.shape)\n    model = build_model(embedding_matrix, num_words, EMBEDDING_SIZE)\n    history = model.fit(X_tr, y_tr,\n                       batch_size=BATCH_SIZE, \n                       epochs=EPOCHS, \n                       validation_data=(X_val,y_val), \n                       callbacks=[es, reduce_lr],\n                       class_weight=class_weights,\n                       verbose=1\n                       )\n    plot_train_val_f1score(history)\n    \n    oof_pred[val_index] = np.squeeze(model.predict([X_val], batch_size=BATCH_SIZE))\n    f1score, threshold = tweak_threshold(oof_pred[val_index], np.squeeze(y_val))\n    print(f\"F1score: {round(f1score, 4)} for threshold: {threshold} on validation data\")\n    best_score.append(threshold)\n    val_pred = (np.squeeze(oof_pred[val_index]) > threshold).astype(int)\n    print('Classification Report on validation data: \\n', classification_report(y_val, val_pred))\n\n    y_test += np.squeeze(model.predict([test_padded_sequences], batch_size=BATCH_SIZE))/5\n    \n    hist.append(history)","metadata":{"execution":{"iopub.status.busy":"2021-06-10T13:48:06.841620Z","iopub.execute_input":"2021-06-10T13:48:06.841946Z","iopub.status.idle":"2021-06-10T15:44:51.985762Z","shell.execute_reply.started":"2021-06-10T13:48:06.841901Z","shell.execute_reply":"2021-06-10T15:44:51.984620Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Nhận xét:**  \nSau khi chạy Stratified 5 folds, có thể thấy là:  \n- Mô hình đã học được 93-95% dữ liệu, chưa quá tốt.\n- Mô hình đã bị overfit    \n==> Không tăng số unit trong LSTM và GRU và tăng SpatialDropout lên thành 0.2\n- Mô hình hội tụ rất chậm sau 6 hoặc 7 epoch  \n==> Không tăng số epoch training lên nữa.\n- Điểm precision, recall, f1-score của nhãn '0' là rất cao. Trong khi đó, điểm precision của nhãn '1' lại khá kém, chỉ 52%, điểm recall của nhãn '1' đã khá là tốt. Mặt khác, loss của model giảm ít sau từng epoch, loss vẫn còn khá cao. Điều này có thể là do việc set class_weight cho nhãn '1' của em = 7 là tương đối lớn. Em cũng đã chạy thử model với class_weight cho nhãn '1' là 5 thì model đạt accuracy cao hơn (trên 95%) và loss có thấp hơn 1 chút.  \n==> Vì vậy, em sẽ giảm class_weight của nhãn 1 xuống, từ 7->5 và sử dụng Regularization với l2 (l2 sẽ apply penalty on layer parameter nếu weight quá lớn).  \n- Việc tăng class_weight của nhãn '1' lên có thể khiến cho việc dự đoán nhãn '0' trở nên không tốt bằng như trước, tuy nhiên, điểm đánh giá của nhãn '0' đang là rất tốt nên ta có thể đánh đổi điểm dự đoán nhãn '0' để tăng được điểm dự đoán ở nhãn '1' vì dù sao nhãn '1' cũng là nhãn quan trọng cần dữ đoán chính xác nhất có thể.","metadata":{}},{"cell_type":"code","source":"# In ra threshold tốt nhất cho tập validation test \nf1score, threshold = tweak_threshold(np.squeeze(oof_pred[val_list]), np.squeeze(target[val_list]))\nprint(f\"F1score: {round(f1score, 4)} for threshold: {threshold} on validation data\")","metadata":{"execution":{"iopub.status.busy":"2021-06-10T15:44:51.987564Z","iopub.execute_input":"2021-06-10T15:44:51.988010Z","iopub.status.idle":"2021-06-10T15:45:08.716275Z","shell.execute_reply.started":"2021-06-10T15:44:51.987957Z","shell.execute_reply":"2021-06-10T15:45:08.714367Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"-------------------------------------------------------------------------------------------------------------------------------","metadata":{}},{"cell_type":"markdown","source":"## **New Model**","metadata":{}},{"cell_type":"code","source":"def build_new_model(embedding_matrix, num_words, EMBEDDING_SIZE):\n    inp = Input(shape=(MAX_LEN,))\n    x = Embedding(num_words+1, EMBEDDING_SIZE, weights=[embedding_matrix], trainable=False)(inp)\n    x = SpatialDropout1D(0.2)(x)\n    weight_decay = regularizers.l2(l2=0.01)\n    x1 = Bidirectional(LSTM(256, return_sequences=True, kernel_regularizer=weight_decay))(x)\n    x2 = Bidirectional(GRU(256, return_sequences=True, kernel_regularizer=weight_decay))(x1)\n    max_pool1 = GlobalMaxPooling1D()(x1)\n    max_pool2 = GlobalMaxPooling1D()(x2)\n    conc = Concatenate()([max_pool1, max_pool2])\n    predictions = Dense(1, activation='sigmoid')(conc)\n    model = Model(inputs=inp, outputs=predictions)\n    adam = Adam(lr=LEARNING_RATE)\n    model.compile(optimizer = adam, loss = 'binary_crossentropy', metrics = ['accuracy',f1])\n    return model","metadata":{"execution":{"iopub.status.busy":"2021-06-10T16:14:40.410813Z","iopub.execute_input":"2021-06-10T16:14:40.411166Z","iopub.status.idle":"2021-06-10T16:14:40.421628Z","shell.execute_reply.started":"2021-06-10T16:14:40.411137Z","shell.execute_reply":"2021-06-10T16:14:40.420275Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"new_model = build_new_model(embedding_matrix, num_words, EMBEDDING_SIZE)","metadata":{"execution":{"iopub.status.busy":"2021-06-10T16:14:41.496092Z","iopub.execute_input":"2021-06-10T16:14:41.496476Z","iopub.status.idle":"2021-06-10T16:14:43.492308Z","shell.execute_reply.started":"2021-06-10T16:14:41.496444Z","shell.execute_reply":"2021-06-10T16:14:43.491158Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_tr, X_val, y_tr, y_val = train_test_split(train_padded_sequences, target, test_size=0.2, shuffle=True, stratify=target)","metadata":{"execution":{"iopub.status.busy":"2021-06-10T16:03:49.678325Z","iopub.execute_input":"2021-06-10T16:03:49.678720Z","iopub.status.idle":"2021-06-10T16:03:50.686830Z","shell.execute_reply.started":"2021-06-10T16:03:49.678689Z","shell.execute_reply":"2021-06-10T16:03:50.685637Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"new_class_weights = {0: 1, 1: 5}","metadata":{"execution":{"iopub.status.busy":"2021-06-10T16:03:52.349448Z","iopub.execute_input":"2021-06-10T16:03:52.349782Z","iopub.status.idle":"2021-06-10T16:03:52.354604Z","shell.execute_reply.started":"2021-06-10T16:03:52.349753Z","shell.execute_reply":"2021-06-10T16:03:52.353164Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"new_history = new_model.fit(X_tr, y_tr,\n                           batch_size=BATCH_SIZE, \n                           epochs=EPOCHS, \n                           validation_data=(X_val,y_val), \n                           callbacks=[es, reduce_lr],\n                           class_weight=new_class_weights,\n                           verbose=1\n                           )","metadata":{"execution":{"iopub.status.busy":"2021-06-10T16:14:47.340082Z","iopub.execute_input":"2021-06-10T16:14:47.340472Z","iopub.status.idle":"2021-06-10T16:37:38.860455Z","shell.execute_reply.started":"2021-06-10T16:14:47.340443Z","shell.execute_reply":"2021-06-10T16:37:38.859442Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plot_train_val_f1score(new_history)","metadata":{"execution":{"iopub.status.busy":"2021-06-10T16:41:38.333324Z","iopub.execute_input":"2021-06-10T16:41:38.333672Z","iopub.status.idle":"2021-06-10T16:41:38.510706Z","shell.execute_reply.started":"2021-06-10T16:41:38.333644Z","shell.execute_reply":"2021-06-10T16:41:38.509435Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"val_pred = np.squeeze(new_model.predict([X_val], batch_size=BATCH_SIZE))\nf1score, threshold = tweak_threshold(val_pred, np.squeeze(y_val))\nprint(f\"F1score: {round(f1score, 4)} for threshold: {threshold} on validation data\")\nval_prediction = (val_pred > threshold).astype(int)\nprint('Classification Report on validation data: \\n', classification_report(y_val, val_prediction))","metadata":{"execution":{"iopub.status.busy":"2021-06-10T16:41:39.267440Z","iopub.execute_input":"2021-06-10T16:41:39.267808Z","iopub.status.idle":"2021-06-10T16:41:55.809183Z","shell.execute_reply.started":"2021-06-10T16:41:39.267779Z","shell.execute_reply":"2021-06-10T16:41:55.807328Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Sau khi train với new_model, model bây giờ đã bị underfit và loss vẫn còn lớn. Điều này có thể là do em đã set giá trị của của lambda trong l2 khá lớn.","metadata":{}},{"cell_type":"markdown","source":"Em xin tiếp tục ở notebook sau ạ","metadata":{}},{"cell_type":"markdown","source":"Thưa thầy, vì 2 lí do:  \n1. Do đã chạy nhiều thử nghiệm nên tài khoản kaggle của em đã bị hết thời gian sử dụng GPU để có thể train model nhanh.\n2. Vì đây là challenge đã kết thúc nên để nộp được bài thì không được sử dụng nguồn dữ liệu nào khác bên ngoài bằng '+ Add data', mà phần bài phía trên của em là phần notebook riêng để chạy model nên em phải sử dụng dữ liệu đã preprocess từ notebook trước.  \nNên em xin phép được nộp bài có submission ở 1 notebook khác bằng tài khoản em mượn của bạn em ạ.  \nEm xin cảm ơn thầy ạ!","metadata":{}},{"cell_type":"markdown","source":"--------------------------------------------------------------------------------------------------------------------------","metadata":{}},{"cell_type":"markdown","source":"## Sử dụng GridSearch để tìm hyperparameter tốt nhất","metadata":{}},{"cell_type":"markdown","source":"Vì thời gian chạy GridSearch là rất lâu và hơn nữa, kaggle không cho em chạy với ","metadata":{}},{"cell_type":"code","source":"# from keras.wrappers.scikit_learn import KerasClassifier\n# from sklearn.model_selection import GridSearchCV\n# from sklearn.metrics import f1_score\n\n# BS = [256, 512, 1024]\n# EP = [15, 20, 25, 30]\n# LR = [0.001, 0.01, 0.1, 0.2, 0.3]\n# MOMENTUM = [0.0, 0.2, 0.4, 0.6, 0.8, 0.9]\n\n# model = KerasClassifier(lambda: build_model(embedding_matrix, num_words, EMBEDDING_SIZE))\n\n# param_grid = dict(batch_size=BS, epochs=EP, learn_rate=LR, momentum=MOMENTUM)\n# grid = GridSearchCV(estimator=model, param_grid=param_grid, cv=3, scoring='f1', n_jobs=-1)\n# grid_result = grid.fit(x_tr, y_tr)\n\n# print(\"Best: %f using %s\" % (grid_result.best_score_, grid_result.best_params_))","metadata":{"execution":{"iopub.status.busy":"2021-06-10T02:39:41.952227Z","iopub.status.idle":"2021-06-10T02:39:41.952811Z"},"trusted":true},"execution_count":null,"outputs":[]}]}