{"cells":[{"metadata":{"_uuid":"f864ec270ddab501c490f23b9dfb1d0681391573"},"cell_type":"markdown","source":"Welcome. In this kernel we are going to implement a temporal convolutional network (abreviated as TCN - see https://arxiv.org/abs/1608.08242 ) There is a nice keras repository we will use as a starting point ( https://github.com/philipperemy/keras-tcn ) We will furthermore use one of shujians kernel for al code not related to the TCN. Let me illustrate the main idea of a TCN:"},{"metadata":{"_uuid":"27f2a27920584425eee3715d38cc9fc9b68d7fd8"},"cell_type":"markdown","source":"<img src=\"https://github.com/philipperemy/keras-tcn/blob/master/misc/Dilated_Conv.png?raw=true\" width=\"1000px\"/>"},{"metadata":{"_uuid":"98c21498cb47a06a96ba4cf9069c8017d2ca574b"},"cell_type":"markdown","source":"As you can see it has a directional structure, which captures dependencies between the input (in our case words) and aggregates into a numer of units. Similar to what LSTM and GRU does, however with less loops. So get into action."},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"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 in \n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\nimport os\nprint(os.listdir(\"../input\"))\n\n# Any results you write to the current directory are saved as output.","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"e31d6e126881ee56a1de3efe02fcf309e900ef00"},"cell_type":"code","source":"## some config values \nembed_size = 300 # how big is each word vector\nmax_features = 95000 # how many unique words to use (i.e num rows in embedding vector)\nmaxlen = 50 # max number of words in a question to use","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"522d9790478f62193ea5c315372a2ab9cbe9b27f"},"cell_type":"markdown","source":"**Load packages and data**"},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"import os\nimport time\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nfrom tqdm import tqdm\nimport math\nfrom sklearn.model_selection import train_test_split\nfrom sklearn import metrics\nfrom sklearn.model_selection import GridSearchCV, StratifiedKFold\nfrom sklearn.metrics import f1_score, roc_auc_score\n\nfrom keras.preprocessing.text import Tokenizer\nfrom keras.preprocessing.sequence import pad_sequences\nfrom keras.layers import Dense, Input, CuDNNLSTM, Embedding, Dropout, Activation, CuDNNGRU, 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.models import Model\nfrom keras import backend as K\nfrom keras.engine.topology import Layer\nfrom keras import initializers, regularizers, constraints, optimizers, layers\nfrom keras.layers import concatenate\nfrom keras.callbacks import *","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"5cdc95950037613c690c49b27930ae0f59eb23c3"},"cell_type":"code","source":"def load_and_prec():\n    train_df = pd.read_csv(\"../input/train.csv\")\n    test_df = pd.read_csv(\"../input/test.csv\")\n    print(\"Train shape : \",train_df.shape)\n    print(\"Test shape : \",test_df.shape)\n    \n    ## fill up the missing values\n    train_X = train_df[\"question_text\"].fillna(\"_##_\").values\n    test_X = test_df[\"question_text\"].fillna(\"_##_\").values\n\n    ## Tokenize the sentences\n    tokenizer = Tokenizer(num_words=max_features)\n    tokenizer.fit_on_texts(list(train_X))\n    train_X = tokenizer.texts_to_sequences(train_X)\n    test_X = tokenizer.texts_to_sequences(test_X)\n\n    ## Pad the sentences \n    train_X = pad_sequences(train_X, maxlen=maxlen)\n    test_X = pad_sequences(test_X, maxlen=maxlen)\n\n    ## Get the target values\n    train_y = train_df['target'].values\n    \n    #shuffling the data\n    np.random.seed(2018)\n    trn_idx = np.random.permutation(len(train_X))\n\n    train_X = train_X[trn_idx]\n    train_y = train_y[trn_idx]\n    \n    return train_X, test_X, train_y, tokenizer.word_index","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"dba1893c267a1e7536bbf720636647d85c7e349c"},"cell_type":"markdown","source":"**Load embeddings**"},{"metadata":{"trusted":true,"_uuid":"a662716cc5fbbcc0c84019a87c52332ed8912e8d"},"cell_type":"code","source":"def load_glove(word_index):\n    EMBEDDING_FILE = '../input/embeddings/glove.840B.300d/glove.840B.300d.txt'\n    def get_coefs(word,*arr): return word, np.asarray(arr, dtype='float32')\n    embeddings_index = dict(get_coefs(*o.split(\" \")) for o in open(EMBEDDING_FILE))\n\n    all_embs = np.stack(embeddings_index.values())\n    emb_mean,emb_std = all_embs.mean(), all_embs.std()\n    embed_size = all_embs.shape[1]\n\n    # word_index = tokenizer.word_index\n    nb_words = min(max_features, len(word_index))\n    embedding_matrix = np.random.normal(emb_mean, emb_std, (nb_words, embed_size))\n    for word, i in word_index.items():\n        if i >= max_features: continue\n        embedding_vector = embeddings_index.get(word)\n        if embedding_vector is not None: embedding_matrix[i] = embedding_vector\n            \n    return embedding_matrix \n    \ndef load_fasttext(word_index):    \n    EMBEDDING_FILE = '../input/embeddings/wiki-news-300d-1M/wiki-news-300d-1M.vec'\n    def get_coefs(word,*arr): return word, np.asarray(arr, dtype='float32')\n    embeddings_index = dict(get_coefs(*o.split(\" \")) for o in open(EMBEDDING_FILE) if len(o)>100)\n\n    all_embs = np.stack(embeddings_index.values())\n    emb_mean,emb_std = all_embs.mean(), all_embs.std()\n    embed_size = all_embs.shape[1]\n\n    # word_index = tokenizer.word_index\n    nb_words = min(max_features, len(word_index))\n    embedding_matrix = np.random.normal(emb_mean, emb_std, (nb_words, embed_size))\n    for word, i in word_index.items():\n        if i >= max_features: continue\n        embedding_vector = embeddings_index.get(word)\n        if embedding_vector is not None: embedding_matrix[i] = embedding_vector\n\n    return embedding_matrix\n\ndef load_para(word_index):\n    EMBEDDING_FILE = '../input/embeddings/paragram_300_sl999/paragram_300_sl999.txt'\n    def get_coefs(word,*arr): return word, np.asarray(arr, dtype='float32')\n    embeddings_index = dict(get_coefs(*o.split(\" \")) for o in open(EMBEDDING_FILE, encoding=\"utf8\", errors='ignore') if len(o)>100)\n\n    all_embs = np.stack(embeddings_index.values())\n    emb_mean,emb_std = all_embs.mean(), all_embs.std()\n    embed_size = all_embs.shape[1]\n\n    # word_index = tokenizer.word_index\n    nb_words = min(max_features, len(word_index))\n    embedding_matrix = np.random.normal(emb_mean, emb_std, (nb_words, embed_size))\n    for word, i in word_index.items():\n        if i >= max_features: continue\n        embedding_vector = embeddings_index.get(word)\n        if embedding_vector is not None: embedding_matrix[i] = embedding_vector\n    \n    return embedding_matrix","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"5a676c3a275514a3351edf306e02d832a5f39317"},"cell_type":"markdown","source":"**TCN Layer**"},{"metadata":{"trusted":true,"_uuid":"dd22b175fc2a25682d89ad7eba2748105af757fc"},"cell_type":"code","source":"# from https://github.com/philipperemy/keras-tcn\n\n\nimport keras.backend as K\nimport keras.layers\nfrom keras import optimizers\nfrom keras.engine.topology import Layer\nfrom keras.layers import Activation, Lambda\nfrom keras.layers import Conv1D, SpatialDropout1D\nfrom keras.layers import Convolution1D, Dense\nfrom keras.models import Input, Model\nfrom typing import List, Tuple\n\n\ndef channel_normalization(x):\n    # type: (Layer) -> Layer\n    \"\"\" Normalize a layer to the maximum activation\n    This keeps a layers values between zero and one.\n    It helps with relu's unbounded activation\n    Args:\n        x: The layer to normalize\n    Returns:\n        A maximal normalized layer\n    \"\"\"\n    max_values = K.max(K.abs(x), 2, keepdims=True) + 1e-5\n    out = x / max_values\n    return out\n\n\ndef wave_net_activation(x):\n    # type: (Layer) -> Layer\n    \"\"\"This method defines the activation used for WaveNet\n    described in https://deepmind.com/blog/wavenet-generative-model-raw-audio/\n    Args:\n        x: The layer we want to apply the activation to\n    Returns:\n        A new layer with the wavenet activation applied\n    \"\"\"\n    tanh_out = Activation('tanh')(x)\n    sigm_out = Activation('sigmoid')(x)\n    return keras.layers.multiply([tanh_out, sigm_out])\n\n\ndef residual_block(x, s, i, activation, nb_filters, kernel_size, padding, dropout_rate=0, name=''):\n    # type: (Layer, int, int, str, int, int, float, str) -> Tuple[Layer, Layer]\n    \"\"\"Defines the residual block for the WaveNet TCN\n    Args:\n        x: The previous layer in the model\n        s: The stack index i.e. which stack in the overall TCN\n        i: The dilation power of 2 we are using for this residual block\n        activation: The name of the type of activation to use\n        nb_filters: The number of convolutional filters to use in this block\n        kernel_size: The size of the convolutional kernel\n        padding: The padding used in the convolutional layers, 'same' or 'causal'.\n        dropout_rate: Float between 0 and 1. Fraction of the input units to drop.\n        name: Name of the model. Useful when having multiple TCN.\n    Returns:\n        A tuple where the first element is the residual model layer, and the second\n        is the skip connection.\n    \"\"\"\n\n    original_x = x\n    conv = Conv1D(filters=nb_filters, kernel_size=kernel_size,\n                  dilation_rate=i, padding=padding,\n                  name=name + '_dilated_conv_%d_tanh_s%d' % (i, s))(x)\n    if activation == 'norm_relu':\n        x = Activation('relu')(conv)\n        x = Lambda(channel_normalization)(x)\n    elif activation == 'wavenet':\n        x = wave_net_activation(conv)\n    else:\n        x = Activation(activation)(conv)\n\n    x = SpatialDropout1D(dropout_rate, name=name + '_spatial_dropout1d_%d_s%d_%f' % (i, s, dropout_rate))(x)\n\n    # 1x1 conv.\n    x = Convolution1D(nb_filters, 1, padding='same')(x)\n    res_x = keras.layers.add([original_x, x])\n    return res_x, x\n\n\ndef process_dilations(dilations):\n    def is_power_of_two(num):\n        return num != 0 and ((num & (num - 1)) == 0)\n\n    if all([is_power_of_two(i) for i in dilations]):\n        return dilations\n\n    else:\n        new_dilations = [2 ** i for i in dilations]\n        # print(f'Updated dilations from {dilations} to {new_dilations} because of backwards compatibility.')\n        return new_dilations\n\n\nclass TCN:\n    \"\"\"Creates a TCN layer.\n        Args:\n            input_layer: A tensor of shape (batch_size, timesteps, input_dim).\n            nb_filters: The number of filters to use in the convolutional layers.\n            kernel_size: The size of the kernel to use in each convolutional layer.\n            dilations: The list of the dilations. Example is: [1, 2, 4, 8, 16, 32, 64].\n            nb_stacks : The number of stacks of residual blocks to use.\n            activation: The activations to use (norm_relu, wavenet, relu...).\n            padding: The padding to use in the convolutional layers, 'causal' or 'same'.\n            use_skip_connections: Boolean. If we want to add skip connections from input to each residual block.\n            return_sequences: Boolean. Whether to return the last output in the output sequence, or the full sequence.\n            dropout_rate: Float between 0 and 1. Fraction of the input units to drop.\n            name: Name of the model. Useful when having multiple TCN.\n        Returns:\n            A TCN layer.\n        \"\"\"\n\n    def __init__(self,\n                 nb_filters=64,\n                 kernel_size=2,\n                 nb_stacks=1,\n                 dilations=None,\n                 activation='norm_relu',\n                 padding='causal',\n                 use_skip_connections=True,\n                 dropout_rate=0.0,\n                 return_sequences=True,\n                 name='tcn'):\n        self.name = name\n        self.return_sequences = return_sequences\n        self.dropout_rate = dropout_rate\n        self.use_skip_connections = use_skip_connections\n        self.activation = activation\n        self.dilations = dilations\n        self.nb_stacks = nb_stacks\n        self.kernel_size = kernel_size\n        self.nb_filters = nb_filters\n        self.padding = padding\n\n        # backwards incompatibility warning.\n        # o = tcn.TCN(i, return_sequences=False) =>\n        # o = tcn.TCN(return_sequences=False)(i)\n\n        if padding != 'causal' and padding != 'same':\n            raise ValueError(\"Only 'causal' or 'same' paddings are compatible for this layer.\")\n\n        if not isinstance(nb_filters, int):\n            print('An interface change occurred after the version 2.1.2.')\n            print('Before: tcn.TCN(i, return_sequences=False, ...)')\n            print('Now should be: tcn.TCN(return_sequences=False, ...)(i)')\n            print('Second solution is to pip install keras-tcn==2.1.2 to downgrade.')\n            raise Exception()\n\n    def __call__(self, inputs):\n        if self.dilations is None:\n            self.dilations = [1, 2, 4, 8, 16, 32]\n        x = inputs\n        x = Convolution1D(self.nb_filters, 1, padding=self.padding, name=self.name + '_initial_conv')(x)\n        skip_connections = []\n        for s in range(self.nb_stacks):\n            for i in self.dilations:\n                x, skip_out = residual_block(x, s, i, self.activation, self.nb_filters,\n                                             self.kernel_size, self.padding, self.dropout_rate, name=self.name)\n                skip_connections.append(skip_out)\n        if self.use_skip_connections:\n            x = keras.layers.add(skip_connections)\n        x = Activation('relu')(x)\n\n        if not self.return_sequences:\n            output_slice_index = -1\n            x = Lambda(lambda tt: tt[:, output_slice_index, :])(x)\n        return x","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"34bc6b787e54f497bbceb6e76553a1166740c5a2"},"cell_type":"markdown","source":"**F1 score**"},{"metadata":{"trusted":true,"_uuid":"d3c6f9010fe9db1885297a5457b6fa77f56d2b71"},"cell_type":"code","source":"\ndef f1(y_true, y_pred):\n    '''\n    metric from here \n    https://stackoverflow.com/questions/43547402/how-to-calculate-f1-macro-in-keras\n    '''\n    def recall(y_true, y_pred):\n        \"\"\"Recall metric.\n\n        Only computes a batch-wise average of recall.\n\n        Computes the recall, a metric for multi-label classification of\n        how many relevant items are selected.\n        \"\"\"\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        \"\"\"Precision metric.\n\n        Only computes a batch-wise average of precision.\n\n        Computes the precision, a metric for multi-label classification of\n        how many selected items are relevant.\n        \"\"\"\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    precision = precision(y_true, y_pred)\n    recall = recall(y_true, y_pred)\n    return 2*((precision*recall)/(precision+recall+K.epsilon()))\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d96793d88c22274d985436e192f62970c227c324"},"cell_type":"markdown","source":"**Our model**"},{"metadata":{"trusted":true,"_uuid":"05164d541a0c35cae727d0338548d156efe21427"},"cell_type":"code","source":"def model_tcn(embedding_matrix):\n    \n    inp = Input(shape=(maxlen,))\n    x = Embedding(max_features, embed_size, weights=[embedding_matrix], trainable=False)(inp)\n    x = SpatialDropout1D(0.1)(x)\n    x = TCN(128,dilations = [1, 2, 4], return_sequences=True, activation = 'wavenet',name = 'tnc1')(x)\n    x = TCN(64,dilations = [1, 2, 4], return_sequences=True, activation = 'wavenet',name = 'tnc2')(x)\n    avg_pool = GlobalAveragePooling1D()(x)\n    max_pool = GlobalMaxPooling1D()(x)\n    \n    conc = concatenate([avg_pool, max_pool])\n    conc = Dense(16, activation=\"relu\")(conc)\n    conc = Dropout(0.1)(conc)\n    outp = Dense(1, activation=\"sigmoid\")(conc)    \n\n    model = Model(inputs=inp, outputs=outp)\n    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=[f1])\n    \n    return model","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"a8c857424e9c9f1703a71c1c0ade28713314dd29"},"cell_type":"markdown","source":"**Train and predict**"},{"metadata":{"trusted":true,"_uuid":"e8523d876b6eae762e673b777cc7af4d7f085792"},"cell_type":"code","source":"# https://www.kaggle.com/strideradu/word2vec-and-gensim-go-go-go\ndef train_pred(model, train_X, train_y, val_X, val_y, epochs=2, callback=None):\n    for e in range(epochs):\n        model.fit(train_X, train_y, batch_size=512, epochs=1, validation_data=(val_X, val_y), callbacks = callback, verbose=True)\n        pred_val_y = model.predict([val_X], batch_size=1024, verbose=0)\n\n        best_score = metrics.f1_score(val_y, (pred_val_y > 0.33).astype(int))\n        print(\"Epoch: \", e, \"-    Val F1 Score: {:.4f}\".format(best_score))\n\n    pred_test_y = model.predict([test_X], batch_size=1024, verbose=0)\n    print('=' * 60)\n    return pred_val_y, pred_test_y, best_score","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"f79081928ca032fbfe3b90c6d3ce91cf57d443d8"},"cell_type":"markdown","source":"**Main part: load, train, pred and blend**"},{"metadata":{"trusted":true,"_uuid":"99d03d2eb63600f1b222522616eab3fa35819f37"},"cell_type":"code","source":"train_X, test_X, train_y, word_index = load_and_prec()\nembedding_matrix_1 = load_glove(word_index)\n# embedding_matrix_2 = load_fasttext(word_index)\nembedding_matrix_3 = load_para(word_index)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"f0ea9b1468bd7cd3ceead2593c641900dc3a2a77"},"cell_type":"code","source":" \nembedding_matrix = np.mean([embedding_matrix_1, embedding_matrix_3], axis = 0)\nnp.shape(embedding_matrix)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"89b49b33d6204e1695888cf0a5fe683d4d37ef00"},"cell_type":"code","source":"# https://www.kaggle.com/ryanzhang/tfidf-naivebayes-logreg-baseline\n\ndef threshold_search(y_true, y_proba):\n    best_threshold = 0\n    best_score = 0\n    for threshold in [i * 0.01 for i in range(100)]:\n        score = f1_score(y_true=y_true, y_pred=y_proba > threshold)\n        if score > best_score:\n            best_threshold = threshold\n            best_score = score\n    search_result = {'threshold': best_threshold, 'f1': best_score}\n    return search_result","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"7aae47c7ded0b4f4849fe68b8c2f282ea03e1d20"},"cell_type":"code","source":"DATA_SPLIT_SEED = 2018\n\ntrain_meta = np.zeros(train_y.shape)\ntest_meta = np.zeros(test_X.shape[0])\nsplits = list(StratifiedKFold(n_splits=4, shuffle=True, random_state=DATA_SPLIT_SEED).split(train_X, train_y))\nfor idx, (train_idx, valid_idx) in enumerate(splits):\n        X_train = train_X[train_idx]\n        y_train = train_y[train_idx]\n        X_val = train_X[valid_idx]\n        y_val = train_y[valid_idx]\n        model = model_tcn(embedding_matrix)\n        pred_val_y, pred_test_y, best_score = train_pred(model, X_train, y_train, X_val, y_val, epochs = 4)\n        train_meta[valid_idx] = pred_val_y.reshape(-1)\n        test_meta += pred_test_y.reshape(-1) / len(splits)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"ee06d1371ca3df0f9bf43a87859a961c99508956"},"cell_type":"code","source":"thresh = threshold_search(y_val, pred_val_y)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"bb6468836f9e8b3ec81e079b4445fe697a66a720"},"cell_type":"code","source":"thresh","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"d6f6012c790d09b4223d4c91ca4776b7815f8646"},"cell_type":"code","source":"sub = pd.read_csv('../input/sample_submission.csv')\nsub.prediction = test_meta > thresh['threshold']\nsub.to_csv(\"submission.csv\", index=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"44cb47ea63bf480dfe2280d89d8b5a1bb6895c6b"},"cell_type":"code","source":"f1_score(y_true=train_y, y_pred=train_meta > thresh['threshold'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"93799a34d40dec9f9f6193f43da1dd212c5c9966"},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}