├── FFDNA.py ├── LICENSE ├── README.md ├── images ├── channel1.png ├── channel2.png ├── channel3.png ├── channel4.png ├── channel5.png ├── channel6.png ├── channel7.png ├── channel8.png └── slides.txt └── process_data.py /FFDNA.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Sun May 19 23:50:15 2019 5 | 6 | @author: Travis 7 | """ 8 | 9 | import pandas as pd 10 | import numpy as np 11 | import tensorflow as tf 12 | import matplotlib.pyplot as plt 13 | from sklearn.metrics import roc_curve,confusion_matrix,f1_score, roc_auc_score 14 | from keras.layers import Concatenate, Dot, Input, LSTM 15 | from keras.layers import RepeatVector, Dense, Activation 16 | from keras.layers import Reshape, Dropout, Add, Subtract, Flatten, Embedding 17 | #from keras.optimizers import Adam 18 | 19 | from keras.models import load_model, Model 20 | import keras.backend as K 21 | import warnings 22 | warnings.filterwarnings("ignore") 23 | 24 | from process_data import * 25 | 26 | 27 | #import feather 28 | #%matplotlib inline 29 | 30 | class FFDNA(object): 31 | 32 | def __init__(self,data, config): 33 | self.time_decay_tr = data[0] 34 | self.time_decay_te = data[1] 35 | self.X_tr = data[2] 36 | self.X_te = data[3] 37 | self.X_tr_lr = data[4] 38 | self.X_te_lr = data[5] 39 | self.Y_train = data[6] 40 | self.Y_test = data[7] 41 | self.all_X = data[8] 42 | self.time_decay = data[9] 43 | self.newlines = data[10] 44 | self.y = data[11] 45 | self.categorical_vars = data[12] 46 | self.paths = data[13] 47 | self.config = config 48 | self.channels = config['channels'] 49 | self.Tx = config['Tx'] 50 | self.n_a = config['n_a'] 51 | self.n_s = config['n_s'] 52 | self.s0 = config['s0'] 53 | self.s1 = config['s1'] 54 | self.vocab_size = config['vocab_size'] 55 | self.epochs = config['epochs'] 56 | self.batch_size = config['batch_size'] 57 | self.learning_rate = config['learning_rate'] 58 | 59 | def save_weight(self,name,model): 60 | model.save_weights(name) 61 | 62 | def load_weight(self,name): 63 | self.model.load_weights(name) 64 | 65 | def one_step_attention(self, a, s_prev,t0): 66 | repeator = RepeatVector(Tx) 67 | concatenator = Concatenate(axis=-1) 68 | densor1 = Dense(10, activation = "tanh") 69 | densor2 = Dense(1, activation = "relu") 70 | activator = Activation(self.softmax, name='attention_weights') # We are using a custom softmax(axis = 1) loaded in this notebook 71 | dotor = Dot(axes = 1) 72 | # Use repeator to repeat s_prev to be of shape (m, Tx, n_s) so that you can concatenate it with all hidden states "a". 73 | s_prev = repeator(s_prev) 74 | # Use concatenator to concatenate a and s_prev on the last axis 75 | concat = concatenator([s_prev,a]) 76 | # Use densor1 to propagate concat through a small fully-connected neural network to compute the "intermediate energies" variable e. 77 | e = densor1(concat) 78 | # Use densor2 to propagate e through a small fully-connected neural network to compute the "energies" variable energies. 79 | energies = densor2(e) 80 | # Use "activator" on "energies" to compute the attention weights "alphas" 81 | energies = Subtract(name='data-time')([energies,t0]) 82 | alphas = activator(energies) 83 | # Use dotor together with "alphas" and "a" to compute the context vector to be given to the next layer 84 | context = dotor([alphas,a]) 85 | return context 86 | 87 | 88 | def build_embedding_network(self, no_of_unique_cat=83, output_shape=32): 89 | inputss = [] 90 | embeddings = [] 91 | for c in self.categorical_vars: 92 | inputs = Input(shape=(1,),name='input_sparse_'+c) 93 | #no_of_unique_cat = data_lr[categorical_var].nunique() 94 | embedding_size = min(np.ceil((no_of_unique_cat)/2), 50 ) 95 | embedding_size = int(embedding_size) 96 | embedding = Embedding(no_of_unique_cat+1, embedding_size, input_length = 1)(inputs) 97 | embedding = Reshape(target_shape=(embedding_size,))(embedding) 98 | inputss.append(inputs) 99 | embeddings.append(embedding) 100 | input_numeric = Input(shape=(1,),name='input_constinuous') 101 | embedding_numeric = Dense(16)(input_numeric) 102 | inputss.append(input_numeric) 103 | embeddings.append(embedding_numeric) 104 | 105 | x = Concatenate()(embeddings) 106 | 107 | x = Dense(10, activation = 'relu')(x) 108 | x = Dropout(.15)(x) 109 | out_control = Dense(output_shape)(x) 110 | return inputss,out_control 111 | 112 | def softmax(self,x,axis=1): 113 | ndim = K.ndim(x) 114 | if ndim==2: 115 | return K.softmax(x) 116 | elif ndim >2: 117 | e = K.exp(x - K.max(x, axis=axis, keepdims=True)) 118 | s = K.sum(e, axis=axis, keepdims =True) 119 | return e/s 120 | else: 121 | raise ValueError('Cannot apply softmax to a tensor that is 1D') 122 | 123 | def model(self): 124 | # Define the inputs of your model with a shape (Tx,) 125 | # Define s0, initial hidden state for the decoder LSTM of shape (n_s,) 126 | input_att = Input(shape=(self.Tx, self.vocab_size), name='input_path') 127 | s0 = Input(shape=(self.n_s,), name='s0') 128 | s = s0 129 | # input time decay data 130 | t0 = Input(shape=(self.Tx,1), name='input_timeDecay') 131 | t = t0 132 | # Step 1: Define pre-attention LSTM. 133 | a = LSTM(self.n_a,return_sequences=True)(input_att) 134 | 135 | # Step 2: import attention model 136 | context = self.one_step_attention(a,s,t) 137 | c = Flatten()(context) 138 | out_att = Dense(32, activation = "sigmoid", name='single_output')(c) 139 | 140 | # Step 3: import embedding data for customer-ralated variables 141 | input_con,out_control = self.build_embedding_network() 142 | added = Add()([out_att, out_control]) 143 | out_all = Dense(1,activation='sigmoid')(added) 144 | # Step 4: Create model instance taking three inputs and returning the list of outputs. 145 | self.model = Model([input_att,s0,t0,input_con[0], 146 | input_con[1],input_con[2],input_con[3]],out_all) 147 | print(self.model.summary()) 148 | #return self.model 149 | 150 | def train_model(self,save_name, loss='binary_crossentropy',opt='adam',metrics=['accuracy']): 151 | self.model.compile(loss=loss,optimizer=opt,metrics=metrics) 152 | self.history = self.model.fit([self.X_tr,self.s0,self.time_decay_tr,self.X_tr_lr.iloc[:,0],self.X_tr_lr.iloc[:,1], 153 | self.X_tr_lr.iloc[:,2],self.X_tr_lr.iloc[:,3] 154 | ], self.Y_train, epochs=self.epochs, batch_size=self.batch_size,verbose=2) 155 | 156 | self.save_weight(save_name,self.model) 157 | 158 | # model performance 159 | def plot_roc_curve(self, fpr, tpr, label=None): 160 | plt.plot(fpr, tpr, linewidth=2, label=label) 161 | plt.plot([0, 1], [0, 1], 'k--') 162 | plt.axis([0, 1, 0, 1]) 163 | plt.xlabel('False Positive Rate') 164 | plt.ylabel('True Positive Rate') 165 | def metric(self, y_valid,prob,cl,label=None): 166 | fpr, tpr, threshold = roc_curve(y_valid, prob) 167 | auc = roc_auc_score(y_valid,prob) 168 | self.plot_roc_curve(fpr,tpr,label=label) 169 | acc = (y_valid==cl).mean() 170 | print('Accuracy: {:.3f}, AUC: {:.3f}'.format(acc,auc)) 171 | 172 | 173 | def plot_loss(self): 174 | ylims = range(1,self.epochs+1,10) 175 | plt.plot(self.history.history['loss'],color='red',label='train loss') 176 | plt.xticks(ylims) 177 | plt.legend(loc=1) 178 | plt.title('train loss vs epochs') 179 | def plot_acc(self): 180 | ylims = range(1,self.epochs+1,10) 181 | plt.plot(self.history.history['acc'],label='acc',c='r') 182 | plt.xticks(ylims) 183 | plt.legend(loc=4) 184 | plt.title('train acc vs epochs') 185 | def auc_score_train(self,threshold): 186 | prob = self.model.predict([self.X_tr,self.s0,self.time_decay_tr,self.X_tr_lr.iloc[:,0],self.X_tr_lr.iloc[:,1], 187 | self.X_tr_lr.iloc[:,2],self.X_tr_lr.iloc[:,3]]) 188 | cl = [1 if p > threshold else 0 for p in prob] 189 | print(confusion_matrix(self.Y_train,cl)) 190 | print(self.metric(self.Y_train,prob,cl,label='train dataset performance')) 191 | def auc_score_test(self,threshold): 192 | prob = self.model.predict([self.X_te,self.s1,self.time_decay_te,self.X_te_lr.iloc[:,0],self.X_te_lr.iloc[:,1], 193 | self.X_te_lr.iloc[:,2],self.X_te_lr.iloc[:,3]]) 194 | cl = [1 if p > threshold else 0 for p in prob] 195 | print(confusion_matrix(self.Y_test,cl)) 196 | print(self.metric(self.Y_test,prob,cl,label='test dataset performance')) 197 | 198 | def test_model(self,threshold,train=False): 199 | if train: 200 | self.auc_score_train(threshold) 201 | else: 202 | self.auc_socre_test(threshold) 203 | 204 | # credits for different channels; as the input data for budget calculation formula 205 | def attributes(self): 206 | layer = self.model.layers[20] 207 | m_all,_,_ = self.all_X.shape 208 | self.s_all = np.zeros((m_all, self.n_s)) 209 | f_f = K.function([self.model.input[0],self.model.input[1],self.model.input[2]], [layer.output]) 210 | r=f_f([self.all_X[self.y==1],self.s_all[self.y==1],self.time_decay[self.y==1]])[0].reshape(self.all_X[self.y==1].shape[0],self.all_X[self.y==1].shape[1]) 211 | 212 | att_f = {m:0 for m in range(1,6)} 213 | att_count_f = {m:0 for m in range(1,6)} 214 | chan_used = self.newlines[self.y==1] 215 | for m in range(chan_used.shape[0]): 216 | for n in range(chan_used.shape[1]): 217 | if chan_used[m,n]!=0: 218 | att_f[chan_used[m,n]] += r[m,n] 219 | att_count_f[chan_used[m,n]] += 1 220 | att_f[self.channels[0]] = att_f.pop(1) 221 | att_f[self.channels[1]] = att_f.pop(2) 222 | att_f[self.channels[2]] = att_f.pop(3) 223 | att_f[self.channels[3]] = att_f.pop(4) 224 | att_f[self.channels[4]] = att_f.pop(5) 225 | 226 | return att_f 227 | 228 | def critical_paths(self): 229 | prob = self.model.predict([self.X_tr,self.s0,self.time_decay_tr,self.X_tr_lr.iloc[:,0],self.X_tr_lr.iloc[:,1], 230 | self.X_tr_lr.iloc[:,2],self.X_tr_lr.iloc[:,3]]) 231 | cp_idx = sorted(range(len(prob)), key=lambda k: prob[k], reverse=True) 232 | #print([prob[p] for p in cp_idx[0:100]]) 233 | cp_p = [self.paths[p] for p in cp_idx[0:100]] 234 | 235 | cp_p_2 = set(map(tuple, cp_p)) 236 | print(list(map(list,cp_p_2))) 237 | 238 | if __name__ == '__main__': 239 | #got data 240 | data = pd.read_csv('df_paths_noblank_purchase.csv') 241 | seq_length = 20 242 | data_all = process_data(data,seq_length = seq_length) 243 | 244 | # hyper parameters 245 | n_a = 32 246 | n_s = 64 247 | m = data_all[2].shape[0] 248 | m_t = data_all[3].shape[0] 249 | s0 = np.zeros((m, n_s)) 250 | s1 = np.zeros((m_t, n_s)) 251 | batch_size = 64 252 | Tx = seq_length 253 | learning_rate = 0.001 254 | channels = ['Natural Search','Email','Paid Search','Media','Social'] 255 | n_channels = len(channels) 256 | vocab_size = n_channels +1 257 | epochs = 120 258 | config = {'channels':channels, 'Tx':Tx, 'n_a':n_a, 'n_s':n_s, 's0':s0,'s1':s1,'vocab_size':vocab_size, 259 | 'epochs':epochs,'batch_size':batch_size,'learning_rate':learning_rate} 260 | 261 | # model 262 | m = FFDNA(data_all, config) 263 | m.model() 264 | m.load_weight('FFDNA_full.h5') 265 | #m.train_model('s.h5') 266 | print('\n\n 1. Test dataset performance:\n') 267 | m.auc_score_test(0.5) 268 | print('\n\n 2. Train performance:\n') 269 | m.auc_score_train(0.5) 270 | att_f = m.attributes() 271 | print('\n\n 3. Channel credits: \n',att_f) 272 | print('\n\n 3. Top critical paths: \n') 273 | m.critical_paths() 274 | 275 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # channel-attribution-model 2 | An attention-based Recurrent Neural Net multi-touch attribution model in a supervised learning fashion of predicting if a series of events leads to conversion (purchase). The trained model can also assign credits to channels. The model also incorporates user-context information, such as user demographics and behavior, as control variables to reduce the estimation biases of media effects. 3 | 4 | # backgroud 5 | It involves morethan 7-13 touches before a customer making a purchase 6 | ![](images/channel1.png) 7 | 8 | # current channel-attribution model 9 | Besides the rule-based model, the Markov and Sharpley are quite famous, but they all failed to consider the influence of old channels or the customer profile 10 | ![](images/channel2.png) 11 | ![](images/channel3.png) 12 | 13 | # a brand-new architecture utilizes LSTM and Attention to deal with sequencial channel data, DNN with customer profile data 14 | LSTM + Attention 15 | ![](images/channel4.png) 16 | ![](images/channel5.png) 17 | 18 | revised softmax function to include the time-decay factor 19 | ![](images/channel6.png) 20 | 21 | Embed customer profile data 22 | ![](images/channel7.png) 23 | 24 | the overall architecture 25 | ![](images/channel8.png) 26 | -------------------------------------------------------------------------------- /images/channel1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremite/channel-attribution-model/9fd16c63b65ffb13c7e3a0a5d03dd84b89044597/images/channel1.png -------------------------------------------------------------------------------- /images/channel2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremite/channel-attribution-model/9fd16c63b65ffb13c7e3a0a5d03dd84b89044597/images/channel2.png -------------------------------------------------------------------------------- /images/channel3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremite/channel-attribution-model/9fd16c63b65ffb13c7e3a0a5d03dd84b89044597/images/channel3.png -------------------------------------------------------------------------------- /images/channel4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremite/channel-attribution-model/9fd16c63b65ffb13c7e3a0a5d03dd84b89044597/images/channel4.png -------------------------------------------------------------------------------- /images/channel5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremite/channel-attribution-model/9fd16c63b65ffb13c7e3a0a5d03dd84b89044597/images/channel5.png -------------------------------------------------------------------------------- /images/channel6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremite/channel-attribution-model/9fd16c63b65ffb13c7e3a0a5d03dd84b89044597/images/channel6.png -------------------------------------------------------------------------------- /images/channel7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremite/channel-attribution-model/9fd16c63b65ffb13c7e3a0a5d03dd84b89044597/images/channel7.png -------------------------------------------------------------------------------- /images/channel8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremite/channel-attribution-model/9fd16c63b65ffb13c7e3a0a5d03dd84b89044597/images/channel8.png -------------------------------------------------------------------------------- /images/slides.txt: -------------------------------------------------------------------------------- 1 | put all the slides in this folder 2 | -------------------------------------------------------------------------------- /process_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Sun May 19 21:28:20 2019 5 | 6 | @author: Travis 7 | """ 8 | import numpy as np 9 | import random 10 | import tensorflow as tf 11 | from keras.utils import to_categorical 12 | from keras.preprocessing.text import Tokenizer 13 | from sklearn.preprocessing import StandardScaler, MinMaxScaler 14 | from pandas.api.types import is_string_dtype, is_numeric_dtype 15 | import warnings 16 | warnings.filterwarnings("ignore") 17 | 18 | def cat(data,field,leng,s=','): 19 | data[field] = data[field].map(lambda x:x.split(s)) 20 | data[leng] = data[field].map(lambda x:len(x)) 21 | 22 | def most_fre(lt): 23 | return max(lt, key=lt.count) 24 | 25 | def sca(col): 26 | col = [int(v) for v in col] 27 | return [(v-min(col))/(max(col)-min(col)) for v in col] 28 | 29 | def train_cats(df): 30 | """Change any columns of strings in a panda's dataframe to a column of 31 | categorical values. This applies the changes inplace. 32 | """ 33 | for n,c in df.items(): 34 | if is_string_dtype(c): df[n] = c.astype('category').cat.as_ordered() 35 | 36 | def process_data(data,seq_length = 20): 37 | #data_a = data_att.loc[:,['path_id','path','total_conversions','last_time_lapse','null_conversion']] 38 | data.dropna(axis=0, how='any', inplace=True) 39 | #merge target variables 40 | cat(data,'path',"leng_path",s='>') 41 | cat(data,'marketing_area',"leng_area",s=',') 42 | cat(data,'tier',"leng_tier",s=',') 43 | cat(data,'customer_type',"leng_type",s=',') 44 | # remove those path with channels less than 3 45 | data_new = data[(data.leng_path >=3)] 46 | data_new = data_new.reset_index() 47 | # leave with the most common value 48 | data_new.marketing_area = data_new.marketing_area.map(lambda x:most_fre(x)) 49 | data_new.tier = data_new.tier.map(lambda x:most_fre(x)) 50 | data_new.customer_type = data_new.customer_type.map(lambda x:most_fre(x)) 51 | data_new.replace('','NA',inplace=True) 52 | 53 | y = data_new.total_conversions 54 | 55 | # got train and test data indices 56 | idx = [x for x in range(data_new.shape[0])] 57 | np.random.seed(111) 58 | random.shuffle(idx) 59 | tr_idx = idx[0:int(0.9*len(idx))] 60 | te_idx = idx[int(0.9*len(idx)):] 61 | 62 | # got data for time decay 63 | cat(data_new,'last_time_lapse',"leng_time_lapse",s=',') 64 | data_new.last_time_lapse=data_new.last_time_lapse.map(lambda x:sca(x)) 65 | pad_sequence = tf.contrib.keras.preprocessing.sequence.pad_sequences 66 | time_decay =pad_sequence(data_new.last_time_lapse,maxlen=seq_length,padding='pre',truncating='pre',dtype='float32') 67 | time_decay = time_decay.reshape(-1,20,1) 68 | time_decay_tr = time_decay[tr_idx] 69 | time_decay_te = time_decay[te_idx] 70 | 71 | # got data for attribution 72 | text = data_new.path 73 | # encoding 74 | t = Tokenizer() 75 | t.fit_on_texts(text) 76 | # vocabulary size 77 | vocab_size = len(t.word_index) + 1 78 | # integer encode the documents 79 | encoded_docs = t.texts_to_sequences(text) 80 | # padding and truncating path data 81 | newlines =pad_sequence(encoded_docs,maxlen=seq_length,padding='pre',truncating='post') 82 | X_train = newlines[tr_idx] 83 | Y_train = y[tr_idx] 84 | X_test = newlines[te_idx] 85 | Y_test = y[te_idx] 86 | all_X = np.array(list(map(lambda x: to_categorical(x, num_classes=vocab_size),newlines)), ndmin=3) 87 | X_tr = np.array(list(map(lambda x: to_categorical(x, num_classes=vocab_size), X_train)), ndmin=3) 88 | X_te = np.array(list(map(lambda x: to_categorical(x, num_classes=vocab_size), X_test)), ndmin=3) 89 | paths = text[tr_idx].reset_index().path 90 | # got customer data (control data) 91 | data_lr = data_new.loc[:,['marketing_area','tier','customer_type']] 92 | train_cats(data_lr) 93 | data_lr['c_type'+'_na'] = [1 if v=='NA' else 0 for v in data_lr['customer_type']] 94 | for col in data_lr.columns: 95 | if not is_numeric_dtype(data_lr[col]): 96 | data_lr[col] = data_lr[col].cat.codes+1 97 | X_tr_lr = data_lr.iloc[tr_idx,:] 98 | X_te_lr = data_lr.iloc[te_idx,:] 99 | scaler = MinMaxScaler() 100 | scaler.fit(X_tr_lr) 101 | X_tr_lr[['marketing_area', 'tier','customer_type']] = scaler.fit_transform(X_tr_lr[['marketing_area', 'tier','customer_type']]) 102 | X_te_lr[['marketing_area', 'tier','customer_type']] = scaler.transform(X_te_lr[['marketing_area', 'tier','customer_type']]) 103 | categorical_vars = data_lr.columns[0:3] 104 | 105 | return [time_decay_tr,time_decay_te, X_tr,X_te, X_tr_lr, X_te_lr, Y_train, Y_test, 106 | all_X,time_decay, newlines, y , categorical_vars, paths] 107 | 108 | 109 | 110 | --------------------------------------------------------------------------------