{"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":"markdown","source":"# An approach with simple K-fold and NLP for Quora Sincerity Prediction","metadata":{}},{"cell_type":"markdown","source":"## 1. Introduction\n* Hiện nay, xử lý những nội dung độc hại và gây chia rẽ là một thách thức đối với bất kỳ trang web nào.\n* Quora muốn giải quyết vấn đề này trực tiếp để giữ cho nền tảng của họ trở thành nơi mà người dùng có thể cảm thấy an toàn khi chia sẻ kiến thức của họ với toàn cộng đồng.\n\n* Quora là một nền tảng cho phép mọi người học hỏi lẫn nhau. Trên Quora, mọi người đều có thể đặt câu hỏi và kết nối với những người khác, mong muốn được giao lưu với những người đóng góp thông tin chi tiết độc đáo và có câu trả lời chất lượng.\n\n* Vậy mục tiêu, thách thức của dự án này là loại bỏ những câu hỏi \"insincere\" - những câu hỏi không mang tính chất đóng góp, thiếu chân thành, thậm chí để đả kích một cá nhân, tập thể hay tổ chức nào đó. Ngoài ra, những câu hỏi có ý định đưa ra một tuyên bố hơn là tìm kiếm những câu trả lời hữu ích cũng cần bị loại bỏ.\n","metadata":{}},{"cell_type":"markdown","source":"## 2. Importing required libraries and files","metadata":{}},{"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\nimport numpy as np # linear algebra\nimport 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\nimport os\nfor 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\nimport nltk\nfrom nltk.corpus import stopwords\nfrom sklearn.metrics import f1_score\nfrom sklearn.model_selection import KFold\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.model_selection import cross_val_score\nfrom sklearn.feature_extraction.text import TfidfVectorizer,CountVectorizer\nfrom sklearn.naive_bayes import GaussianNB,MultinomialNB,ComplementNB,BernoulliNB","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-output":true,"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Getting training and testing files","metadata":{}},{"cell_type":"code","source":"## check files\ndef display_all(df):\n    with pd.option_context(\"display.max_rows\", 1000, \"display.max_columns\", 1000): \n        display(df)\n        \ntrain = pd.read_csv('../input/train.csv')\ntest  = pd.read_csv('../input/test.csv')","metadata":{"_uuid":"e0422554da95f756b2252af69e87c710d9575fae","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Format of data","metadata":{}},{"cell_type":"code","source":"display_all(train.head())\ntrain['target'].value_counts()","metadata":{"_uuid":"0b39c34363c9ab869585dab837ac29a0433d957c","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_text = train['question_text']\ntest_text = test['question_text']\ntrain_target = train['target']\nall_text = train_text.append(test_text)","metadata":{"_uuid":"cdc5349f5c50b230a73220dec5397db0c07a2e9c","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 3. Vectorizing (thay bằng Embedding, đoạn dưới giới thiệu về embedding)\n### Vì dữ liệu đầu vào của ta có dạng text, mỗi text là một tổ hợp của các word có ý nghĩa riêng. Vì vậy, việc cần làm đầu tiên với dữ liệu là xử lý và phân tích text, trích rút đặc trưng từ các word và câu riêng lẻ.\n### Vectorizing là một phương pháp rất mạnh trong word processing và text analysis nên ta sẽ tiến hành nó đầu tiên.","metadata":{}},{"cell_type":"markdown","source":"## 3.1 Problem\nĐể phân loại, nhận biết được các câu hỏi \"sincerity\", ta phải phân biệt, nhận biết được ý nghĩa và chủ ý của từng câu hỏi. Tuy nhiên, trong một câu lại chứa rất nhiều từ, cụm từ nhưng không phải từ nào cũng có giá trị đối với toàn thể ngữ cảnh của câu. Ta dễ thấy rằng trong một câu nói, thường chỉ có một số từ có giá trị cao về mặt ngữ nghĩa của cả câu và ngược lại. Vì vậy, xử lý câu đó ra sao để trích rút được ngữ cảnh, ngữ nghĩa của câu đó là một vấn đề cần thiết nhưng cũng không hề đơn giản. \n\n## 3.2 Solution: TF-IDF\nSau khi tìm tòi, em quyết định dùng TF-IDF (Term Frequency - Inverse Document Frequency), một kĩ thuật thường được dùng trong xử lý ngôn ngữ tự nhiên, khai phá dữ liệu văn bản để trích xuất, đánh trọng số cho các từ trong một câu. Trọng số này được sử dụng để đánh giá tầm quan trọng của một từ trong một văn bản. Giá trị cao thể hiện độ quan trọng cao và nó phụ thuộc vào số lần từ xuất hiện trong văn bản nhưng bù lại bởi tần suất của từ đó trong tập dữ liệu\n* TF: tần suất xuất hiện của từ\n* IDF: đánh giá tầm quan trọng của một từ ","metadata":{}},{"cell_type":"code","source":"tfidf_vectorizer = TfidfVectorizer()\ntfidf_vectorizer.fit(all_text)\n\ntrain_text_features_tf = tfidf_vectorizer.transform(train_text)\ntest_text_features_tf = tfidf_vectorizer.transform(test_text)\n\n# all_text","metadata":{"_uuid":"b57afc7c489358f6f57713328bce1a7d831bf44d","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 3.3 Result\n","metadata":{}},{"cell_type":"code","source":"df = pd.DataFrame(train_text_features_tf[0].T.todense(), index = tfidf_vectorizer.get_feature_names(), columns=[\"TF-IDF\"])\nprint(train_text[0])\ndf = df.sort_values('TF-IDF', ascending=False)\nprint(df.head(25))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 3.4 Review\nNhư ta thấy ở trên, sau khi dùng TF-IDF cho câu đầu tiên của tập train làm ví dụ: \"How did Quebec nationalists see their province as a nation in the 1960s?\", ta thấy các từ lần lượt được đánh trọng số và khách quan mà nói, thuật toán này đã đánh trọng số khá tốt khi nhận biết được các thành phần quan trọng nhất gồm thập kỷ 1960s, \"nationalists\", \"Quebec\",... Nghe có vẻ rất hợp lý. Hơn nữa, thuật toán đã bỏ đi stop word - một từ gần như không mang ý nghĩa gì là mạo từ \"a\".","metadata":{}},{"cell_type":"markdown","source":"# 4. Training model\n## 4.1 Problem\nChúng ta cùng nhìn lại tập training sau khi được import:","metadata":{}},{"cell_type":"code","source":"display_all(train.head())\ntrain['target'].value_counts()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Lượng data chúng ta đưa vào để train không hề thiếu hụt về mặt số lượng. Tuy nhiên, nếu để ý kỹ thì ta có thể thấy label 0 - tức các câu hỏi thông thường nhiều hơn các câu hỏi loại label 1 rất nhiều (nhiều hơn 1144502 dữ liệu và gấp hơn 15 lần).","metadata":{}},{"cell_type":"markdown","source":"Tuy nhiên, khi train một model, ta thường chia data cho training set/validation set theo tỷ lệ 80/20, 70/30 hoặc 90/10 tùy trường hợp. Từ đó, ta có thể thấy dữ liệu tập training set/validation test lệch nhau nhiều nhất là gấp 9 lần. Vấn đề ở đây là 80810 dữ liệu label 1 kia lại có khả năng rơi gần hết vào validation set ta phân chia ra như trên vì số lượng của nó khá bé, đủ để nằm gọn trong tập dữ liệu đó. Nếu xảy ra trường hợp đó, model của chúng ta do không có cơ hội học hỏi từ dữ liệu đó nên nó không thể phân loại được các câu hỏi label 1. Đó là một điều rất rất kinh khủng. Tuy nó các xác suất xảy ra thấp nhưng nó vẫn có thể xảy ra do việc chia dữ liệu cho tập training và validation là hoàn toàn ngẫu nhiên. \n","metadata":{}},{"cell_type":"markdown","source":"Vì vậy, ta phải giải quyết được vấn đề chênh lệch dữ liệu này để model có thể được đảm bảo chất lượng phân loại được cả 2 loại câu hỏi.","metadata":{}},{"cell_type":"markdown","source":"## 4.2 Solution","metadata":{}},{"cell_type":"markdown","source":"## K-Fold Cross Validation\n<img src=\"https://scikit-learn.org/stable/_images/grid_search_cross_validation.png\">","metadata":{}},{"cell_type":"markdown","source":"K-Fold Cross Validation là một phương pháp đánh giá model một cách chính xác khi chúng ta train model nhưng có quá ít dữ liệu hoặc dữ liệu bị chênh lệch quá nhiều. ","metadata":{}},{"cell_type":"markdown","source":"Như hình bên trên, ta thấy dữ liệu được chia thành:\n* Test data ta để nguyên, không đả động gì đến.\n* Training data ta chia thành K phần. Sau đó train model K lần, mỗi lần train sẽ chọn 1 phần làm dữ liệu validation và K-1 phần còn lại làm dữ liệu training. Kết quả đánh giá model cuối cùng là trung bình cộng kết quả đánh giá của K lần train. Từ đó dữ liệu sẽ được đánh giá khách quan và chính xác hơn do được train nhiều lần một cách thông minh hơn trên cùng một tập dữ liệu bé hoặc không cân bằng.","metadata":{}},{"cell_type":"markdown","source":"Em sẽ chia training data thành 5 fold và train với Logistic Regression. Từ đây model sẽ được train lần lượt từng fold.","metadata":{}},{"cell_type":"code","source":"kfold = KFold(n_splits = 5, shuffle = False, random_state = 2021)\ntest_preds = 0\noof_preds = np.zeros([train.shape[0],])\n\nfor i, (train_idx,valid_idx) in enumerate(kfold.split(train)):\n#     print(len(train_idx))\n#     print(len(valid_idx))\n    x_train, x_valid = train_text_features_tf[train_idx,:], train_text_features_tf[valid_idx,:]\n    y_train, y_valid = train_target[train_idx], train_target[valid_idx]\n    classifier = LogisticRegression()\n    print('fitting.......')\n    classifier.fit(x_train,y_train)\n#     print(x_valid)\n    print('predicting......')\n    \n    oof_preds[valid_idx] = classifier.predict_proba(x_valid)[:,1]\n    ## oof_preds ở trên để lưu kết quả mô hình phân loại dữ liệu đó vào nhãn 0 hoặc 1, ví dụ:\n    valid0 = classifier.predict_proba(x_valid)[0]\n    print(valid0[0], valid0[1])\n    print('\\n')\n    \n    test_preds += 0.2*classifier.predict_proba(test_text_features_tf)[:,1]","metadata":{"_uuid":"b74c8f07ea61f13c980a5b0ef23d00785219964b","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"## 4.3 Result\nSau khi train, ta được một model đã có thể dự đoán với 5 ví dụ như trên.","metadata":{}},{"cell_type":"markdown","source":"## 4.4 Review\nTuy nhiên, đây mới là các bước xử lý rất đơn giản và thô sơ. Để muốn cải thiện thêm kết quả, ta cần tiền xử lý thêm dữ liệu kết hợp với sử dụng thử các loại model khác để rút ra được một mô hình tốt nhất.","metadata":{}},{"cell_type":"markdown","source":"# 5. F1 Score","metadata":{}},{"cell_type":"markdown","source":"Ta thử với nhiều threadhole khác nhau để rút ra được F1 score cao nhất.","metadata":{}},{"cell_type":"code","source":"for (i) in range(20,30, 1):\n    pred_train = (oof_preds > i / 100).astype(np.int)\n    print(\"threadhole\", i/100, \"f1_score\", f1_score(train_target, pred_train))","metadata":{"_uuid":"20ec786a41e629db0f3ca06c722dfe98efea7c56","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Vậy F1 Score cao nhất của em trong lần xử lý này là 0.6167 với threadhole 0.23. Kết quả lần này khá tốt, cao hơn so với một số kiểu xử lý data khác tuy đây mới chỉ là các bước xử lý và training cơ bản.","metadata":{}},{"cell_type":"markdown","source":"# submission","metadata":{}},{"cell_type":"code","source":"submission1 = pd.DataFrame.from_dict({'qid': test['qid']})\nsubmission1['prediction'] = (test_preds>0.25).astype(np.int)\nsubmission1.to_csv('submission.csv', index=False)\nsubmission1['prediction'] = (test_preds>0.25)","metadata":{"_uuid":"53d728e51940326983d37573de0a2c0ed7345844","trusted":true},"execution_count":null,"outputs":[]}]}