From 877c519aeb50357c60b275d7228d48dd6ba841ac Mon Sep 17 00:00:00 2001 From: Igor Kholopov Date: Wed, 5 Dec 2018 03:54:24 +0300 Subject: [PATCH] Hometask --- IgorKholopov/fca.ipynb | 365 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 365 insertions(+) create mode 100644 IgorKholopov/fca.ipynb diff --git a/IgorKholopov/fca.ipynb b/IgorKholopov/fca.ipynb new file mode 100644 index 0000000..fb53807 --- /dev/null +++ b/IgorKholopov/fca.ipynb @@ -0,0 +1,365 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": 142, + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.preprocessing import OneHotEncoder\n", + "from sklearn.tree import DecisionTreeClassifier\n", + "from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score\n", + "from sklearn.model_selection import KFold\n", + "from sklearn.model_selection import GridSearchCV" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Подготовка данных" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [], + "source": [ + "def prepare_data(filename):\n", + " X_df = pd.read_csv(filename)\n", + " y = X_df['V10'].replace(to_replace='positive', value=1).replace(to_replace='negative', value=-1).values\n", + " encoder = OneHotEncoder(dtype='bool')\n", + " X = encoder.fit_transform(X_df[['V{}'.format(i) for i in range(1, 10)]]).toarray()\n", + " return X, y" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def context_split(X_train, y_train):\n", + " return X_train[y_train==1], X_train[y_train==-1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Признаки для объекта, используемые классификатором" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "def avg_intersect(sample, context):\n", + " return np.sum(context & sample) / len(context)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def avg_clojure(sample, context, context_clojure):\n", + " intersect = context & sample\n", + " return np.array([(np.sum(context_clojure & inters, axis=1) == np.sum(inters)).sum() \n", + " for inters in intersect]).mean()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Подсчёт признаков" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [], + "source": [ + "def get_features(dataset, X_train, y_train):\n", + " X_pos, X_neg = context_split(X_train, y_train)\n", + " return np.array([[\n", + " avg_intersect(sample, X_pos), avg_intersect(sample, X_neg)] for sample in dataset])\n", + "\n", + "def get_features_bad(dataset, X_train, y_train):\n", + " X_pos, X_neg = context_split(X_train, y_train)\n", + " return np.array([[avg_clojure(sample, X_pos, X_pos), avg_clojure(sample, X_pos, X_neg),\n", + " avg_clojure(sample, X_neg, X_pos), avg_clojure(sample, X_neg, X_neg),\n", + " avg_intersect(sample, X_pos), avg_intersect(sample, X_neg)] for sample in dataset])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Для улучшения качества, заменим критерий порога по значениям признаков на решающие деревья над теми же признаками, тогда:\n", + "\n", + "Классификатор - ансамбль k деревьев, обучающийся на разделении обучающей выборки на k частей. На k-й обучаем, остальные используются как контекст. Признаки на вход в дерево получаются из \"ленивых\" признаков обучающей выборки и контекста.\n", + "\n", + "Предсказание - знак среднего предсказания ансамбля деревьев на признаках из запрашиваемого примера и всей обучающей выборки в качестве контекстов.\n", + "\n", + "3 параметра:\n", + " - n_train_splits - кол-во разделений при обучении (==кол-во деревьев в ансамбле)\n", + " - max_depth - максимальная глубина дерева\n", + " - features - источники признаков" + ] + }, + { + "cell_type": "code", + "execution_count": 137, + "metadata": {}, + "outputs": [], + "source": [ + "class MyClassifier:\n", + " def __init__(self, n_train_splits = 5, max_depth = 4, features=get_features):\n", + " self.trees=[]\n", + " self.n_train_splits = n_train_splits\n", + " self.max_depth = max_depth\n", + " self.features=features\n", + " self.X_train = None\n", + " self.y_train = None\n", + " \n", + " def fit(self, X, y):\n", + " k_fold = KFold(n_splits=self.n_train_splits, shuffle=True)\n", + " for train_index, test_index in k_fold.split(X):\n", + " X_train, X_to_learn = X[train_index], X[test_index]\n", + " y_train, y_to_learn = y[train_index], y[test_index]\n", + " X_to_learn = self.features(X_to_learn, X_train, y_train)\n", + " tree = DecisionTreeClassifier(max_depth=self.max_depth)\n", + " tree.fit(X_to_learn, y_to_learn)\n", + " self.trees.append(tree)\n", + " self.X_train = X\n", + " self.y_train = y\n", + " return self\n", + " \n", + " def predict(self, X):\n", + " X = self.features(X, self.X_train, self.y_train)\n", + " tree_predictions = np.array([classifier.predict(X) for classifier in self.trees])\n", + " return (tree_predictions.mean(axis=0) > 0) * 2 -1\n", + " \n", + " def get_params(self, **kwargs):\n", + " return {'n_train_splits':self.n_train_splits, 'max_depth':self.max_depth}\n", + " \n", + " def set_params(self, **kwargs):\n", + " self.n_train_splits = kwargs['n_train_splits']\n", + " self.max_depth = kwargs['max_depth']\n", + " return self" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Рассмотрим в качестве признаков средние $|g'\\cap g_i^+|$, $|g'\\cap g_i^-|$,\n", + " $|(g'\\cap g_i^+)^+|$, $|(g'\\cap g_i^+)^-|$, $|(g'\\cap g_i^-)^-|$, $|(g'\\cap g_i^-)^+|$:" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 : acc. 0.6129032258064516, prec. 0.8048780487804879, rec. 0.5409836065573771\n", + "2 : acc. 0.6436781609195402, prec. 0.717391304347826, rec. 0.6470588235294118\n", + "3 : acc. 0.64, prec. 0.8717948717948718, rec. 0.5230769230769231\n", + "4 : acc. 0.5056179775280899, prec. 0.7272727272727273, rec. 0.4067796610169492\n", + "5 : acc. 0.6741573033707865, prec. 0.8666666666666667, rec. 0.6290322580645161\n", + "6 : acc. 0.611764705882353, prec. 0.8108108108108109, rec. 0.5357142857142857\n", + "7 : acc. 0.6578947368421053, prec. 0.7924528301886793, rec. 0.6\n", + "8 : acc. 0.6915887850467289, prec. 0.8125, rec. 0.7123287671232876\n", + "9 : acc. 0.7475728155339806, prec. 0.8666666666666667, rec. 0.7428571428571429\n", + "10 : acc. 0.5934065934065934, prec. 0.7391304347826086, rec. 0.576271186440678\n" + ] + } + ], + "source": [ + "n_split = 5\n", + "max_depth = 3\n", + "for i in range(1, 11):\n", + " file_train = 'train{}.csv'.format(i)\n", + " file_test = 'test{}.csv'.format(i)\n", + " X_train, y_train = prepare_data(file_train)\n", + " X_test, y_test = prepare_data(file_test)\n", + " classifier = MyClassifier(n_split, max_depth, features=get_features_bad)\n", + " classifier.fit(X_train, y_train)\n", + " pred = classifier.predict(X_test)\n", + " print('{} : acc. {}, prec. {}, rec. {}'.format(i, accuracy_score(y_test, pred),\n", + " precision_score(y_test, pred), recall_score(y_test, pred)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Результат не впечатляет. Попробуем ограничиться средними $|g'\\cap g_i^+|$, $|g'\\cap g_i^-|$:" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 : acc. 0.7634408602150538, prec. 0.7746478873239436, rec. 0.9016393442622951\n", + "2 : acc. 0.7471264367816092, prec. 0.6986301369863014, rec. 1.0\n", + "3 : acc. 0.79, prec. 0.8055555555555556, rec. 0.8923076923076924\n", + "4 : acc. 0.8314606741573034, prec. 0.8142857142857143, rec. 0.9661016949152542\n", + "5 : acc. 0.7752808988764045, prec. 0.8, rec. 0.9032258064516129\n", + "6 : acc. 0.8235294117647058, prec. 0.8360655737704918, rec. 0.9107142857142857\n", + "7 : acc. 0.7631578947368421, prec. 0.7263157894736842, rec. 0.9857142857142858\n", + "8 : acc. 0.7757009345794392, prec. 0.7951807228915663, rec. 0.9041095890410958\n", + "9 : acc. 0.8446601941747572, prec. 0.8292682926829268, rec. 0.9714285714285714\n", + "10 : acc. 0.7472527472527473, prec. 0.725, rec. 0.9830508474576272\n" + ] + } + ], + "source": [ + "n_split = 5\n", + "max_depth = 3\n", + "for i in range(1, 11):\n", + " file_train = 'train{}.csv'.format(i)\n", + " file_test = 'test{}.csv'.format(i)\n", + " X_train, y_train = prepare_data(file_train)\n", + " X_test, y_test = prepare_data(file_test)\n", + " classifier = MyClassifier(n_split, max_depth, features=get_features)\n", + " classifier.fit(X_train, y_train)\n", + " pred = classifier.predict(X_test)\n", + " print('{} : acc. {}, prec. {}, rec. {}'.format(i, accuracy_score(y_test, pred),\n", + " precision_score(y_test, pred), recall_score(y_test, pred)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Уже лучше. Подберём параметры по сетке, оптимизируя точность:" + ] + }, + { + "cell_type": "code", + "execution_count": 139, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "parameters = {'n_train_splits':np.arange(3,15), 'max_depth':np.arange(3, 15)}\n", + "classifier = MyClassifier(2, 2, features=get_features)\n", + "gs = GridSearchCV(classifier, param_grid=parameters, scoring='accuracy', cv=5, n_jobs=-1)\n", + "params = []\n", + "X_train_all = np.empty((0, 27), dtype='bool')\n", + "y_train_all = np.empty(0, dtype='bool')\n", + "for i in range(1, 11):\n", + " file_train = 'train{}.csv'.format(i)\n", + " X_train, y_train = prepare_data(file_train)\n", + " X_train_all = np.vstack((X_train_all, X_train))\n", + " y_train_all = np.hstack((y_train_all, y_train))\n", + "\n", + "best_params = gs.fit(X_train_all, y_train_all)" + ] + }, + { + "cell_type": "code", + "execution_count": 140, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'max_depth': 11, 'n_train_splits': 7}\n" + ] + } + ], + "source": [ + "best_params_val = best_params.best_params_\n", + "print(best_params_val)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Итоговые значения метрик на тесте (при обучении на соответствующем train):" + ] + }, + { + "cell_type": "code", + "execution_count": 161, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Average: acc. 0.9410423092812241, prec. 0.9394644632821766, rec. 0.9724699566254443, f1. 0.9552805361560927\n" + ] + } + ], + "source": [ + "metrics_avg = []\n", + "for i in range(1, 11):\n", + " file_train = 'train{}.csv'.format(i)\n", + " file_test = 'test{}.csv'.format(i)\n", + " X_train, y_train = prepare_data(file_train)\n", + " X_test, y_test = prepare_data(file_test)\n", + " classifier = MyClassifier(n_split, max_depth, features=get_features)\n", + " classifier.set_params(**best_params_val)\n", + " classifier.fit(X_train, y_train)\n", + " pred = classifier.predict(X_test)\n", + " metrics_avg.append(np.array([accuracy_score(y_test, pred),\n", + " precision_score(y_test, pred), recall_score(y_test, pred), f1_score(y_test, pred)]))\n", + "metrics_avg = np.array(metrics_avg).mean(axis=0)\n", + "print('Average: acc. {}, prec. {}, rec. {}, f1. {}'.format(metrics_avg[0], metrics_avg[1], metrics_avg[2], metrics_avg[3]))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}