Kaggle competition: Santa's Workshop Tour 2019
<SOS> metric: SantaWorkshopSchedule2019 Kaggle data source: santas-workshop-tour-2019<drop_column>
import numpy as np
import pandas as pd
from numba import njit
from itertools import product
from ortools.linear_solver import pywraplp
columns = importances[importances['Average']<.03]['Feature'].values
train_features.drop(columns = columns, inplace = True)
 test_features.drop(columns = columns, inplace = True )<split>
def get_penalty(n, choice):
penalty = None
if choice == 0:
penalty = 0
elif choice == 1:
penalty = 50
elif choice == 2:
penalty = 50 + 9 * n
elif choice == 3:
penalty = 100 + 9 * n
elif choice == 4:
penalty = 200 + 9 * n
elif choice == 5:
penalty = 200 + 18 * n
elif choice == 6:
penalty = 300 + 18 * n
elif choice == 7:
penalty = 300 + 36 * n
elif choice == 8:
penalty = 400 + 36 * n
elif choice == 9:
penalty = 500 + 36 * n + 199 * n
else:
penalty = 500 + 36 * n + 398 * n
return penalty
def GetPreferenceCostMatrix(data):
cost_matrix = np.zeros(( N_FAMILIES, N_DAYS), dtype=np.int64)
 for i in range(N_FAMILIES):
desired = data.values[i, :-1]
cost_matrix[i, :] = get_penalty(FAMILY_SIZE[i], 10)
 for j, day in enumerate(desired):
cost_matrix[i, day-1] = get_penalty(FAMILY_SIZE[i], j)
 return cost_matrix
def GetAccountingCostMatrix() :
ac = np.zeros(( 1000, 1000), dtype=np.float64)
 for n in range(ac.shape[0]):
for n_p1 in range(ac.shape[1]):
diff = abs(n - n_p1)
 ac[n, n_p1] = max(0,(n - 125)/ 400 * n**(0.5 + diff / 50.0))
return ac
@njit(fastmath=True)
 def pcost(prediction):
daily_occupancy = np.zeros(N_DAYS+1, dtype=np.int64)
 penalty = 0
for(i, p)in enumerate(prediction):
n = FAMILY_SIZE[i]
penalty += PCOSTM[i, p]
daily_occupancy[p] += n
return penalty, daily_occupancy
@njit(fastmath=True)
 def acost(daily_occupancy):
accounting_cost = 0
n_out_of_range = 0
daily_occupancy[-1] = daily_occupancy[-2]
for day in range(N_DAYS):
n_p1 = daily_occupancy[day + 1]
n = daily_occupancy[day]
n_out_of_range +=(n > MAX_OCCUPANCY)or(n < MIN_OCCUPANCY)
 accounting_cost += ACOSTM[n, n_p1]
return accounting_cost, n_out_of_range
@njit(fastmath=True)
 def cost_function(prediction):
penalty, daily_occupancy = pcost(prediction)
 accounting_cost, n_out_of_range = acost(daily_occupancy)
 return penalty + accounting_cost + n_out_of_range*100000000
X = train_features
y = traina['Survived']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4, random_state=101 )<train_on_grid>
def findBetterDay4Family(pred):
fobs = np.argsort(FAMILY_SIZE)
 score = cost_function(pred)
 original_score = np.inf
while original_score>score:
original_score = score
for family_id in fobs:
for pick in range(10):
day = DESIRED[family_id, pick]
oldvalue = pred[family_id]
pred[family_id] = day
new_score = cost_function(pred)
 if new_score<score:
score = new_score
else:
pred[family_id] = oldvalue
print(score, end='\r')
 print(score)
 def stochastic_product_search(top_k, fam_size, original,
verbose=1000, verbose2=50000,
n_iter=500, random_state=2019):

 best = original.copy()
best_score = cost_function(best)
 np.random.seed(random_state)
 for i in range(n_iter):
fam_indices = np.random.choice(range(DESIRED.shape[0]), size=fam_size)
 changes = np.array(list(product(*DESIRED[fam_indices, :top_k].tolist())))
 for change in changes:
new = best.copy()
new[fam_indices] = change
new_score = cost_function(new)
 if new_score < best_score:
best_score = new_score
best = new
if verbose and i % verbose == 0:
print(f"Iteration
if verbose2 and i % verbose2 == 0:
print(f"Iteration
print(f"Final best score is {best_score:.2f}")
 return best
ran = RandomForestClassifier(random_state=1)
 knn = KNeighborsClassifier()
log = LogisticRegression()
xgb = XGBClassifier(random_state=1)
 gbc = GradientBoostingClassifier(random_state=1)
 svc = SVC(probability=True)
 ext = ExtraTreesClassifier(random_state=1)
 ada = AdaBoostClassifier(random_state=1)
 gnb = GaussianNB()
gpc = GaussianProcessClassifier()
bag = BaggingClassifier(random_state=1)
 models = [ran, knn, log, xgb, gbc, svc, ext, ada, gnb, gpc, bag]
scores_v2 = []
for mod in models:
mod.fit(X_train, y_train)
 acc = cross_val_score(mod, X_train, y_train, scoring = "accuracy", cv = 10)
 scores_v2.append(acc.mean() )<create_dataframe>
def seed_finding(seed, prediction_input):
prediction = prediction_input.copy()
np.random.seed(seed)
 best_score = cost_function(prediction)
 original_score = best_score
best_pred = prediction.copy()
print("SEED: {} ORIGINAL SCORE: {}".format(seed, original_score))
for t in range(100):
for i in range(5000):
for j in range(10):
di = prediction[i]
prediction[i] = DESIRED[i, j]
cur_score = cost_function(prediction)
 KT = 1
if t < 5:
KT = 1.5
elif t < 10:
KT = 4.5
else:
if cur_score > best_score + 100:
KT = 3
elif cur_score > best_score + 50 :
KT = 2.75
elif cur_score > best_score + 20:
KT = 2.5
elif cur_score > best_score + 10:
KT = 2
elif cur_score > best_score:
KT = 1.5
else:
KT = 1
prob = np.exp(-(cur_score - best_score)/ KT)
 if np.random.rand() < prob:
best_score = cur_score
else:
prediction[i] = di
if best_score < original_score:
print("NEW BEST SCORE on seed {}: {}".format(seed, best_score))
original_score = best_score
best_pred = prediction.copy()
return prediction
results = pd.DataFrame({
'Model': ['Random Forest', 'K Nearest Neighbour', 'Logistic Regression', 'XGBoost', 'Gradient Boosting', 'SVC', 'Extra Trees', 'AdaBoost', 'Gaussian Naive Bayes', 'Gaussian Process', 'Bagging Classifier'],
'Score': scores,
'Score w/Feature Selection': scores_v2})
 result_df = results.sort_values(by='Score w/Feature Selection', ascending=False ).reset_index(drop=True)
 result_df<train_on_grid>
def solveSantaLP() :
S = pywraplp.Solver('SolveAssignmentProblem', pywraplp.Solver.GLOP_LINEAR_PROGRAMMING)
 x = {}
candidates = [[] for _ in range(N_DAYS)]
for i in range(N_FAMILIES):
for j in DESIRED[i, :]:
candidates[j].append(i)
 x[i, j] = S.BoolVar('x[%i,%i]' %(i, j))
daily_occupancy = [S.Sum([x[i, j] * FAMILY_SIZE[i] for i in candidates[j]])
 for j in range(N_DAYS)]
family_presence = [S.Sum([x[i, j] for j in DESIRED[i, :]])
 for i in range(N_FAMILIES)]
preference_cost = S.Sum([PCOSTM[i, j] * x[i,j] for i in range(N_FAMILIES)
 for j in DESIRED[i, :] ])
 S.Minimize(preference_cost)
 for j in range(N_DAYS-1):
S.Add(daily_occupancy[j] - daily_occupancy[j+1] <= 23)
 S.Add(daily_occupancy[j+1] - daily_occupancy[j] <= 23)
 for i in range(N_FAMILIES):
S.Add(family_presence[i] == 1)
 for j in range(N_DAYS):
S.Add(daily_occupancy[j] >= MIN_OCCUPANCY)
 S.Add(daily_occupancy[j] <= MAX_OCCUPANCY)
 res = S.Solve()
resdict = {0:'OPTIMAL', 1:'FEASIBLE', 2:'INFEASIBLE', 3:'UNBOUNDED',
4:'ABNORMAL', 5:'MODEL_INVALID', 6:'NOT_SOLVED'}
print('LP solver result:', resdict[res])
 l = [(i, j, x[i, j].solution_value())for i in range(N_FAMILIES)
 for j in DESIRED[i, :]
if x[i, j].solution_value() >0]
df = pd.DataFrame(l, columns=['family_id', 'day', 'n'])
 return df
penalty = ['l1', 'l2']
C = np.logspace(0, 4, 10)
 hyperparams = {'penalty': penalty, 'C': C}
lrgd=GridSearchCV(estimator = LogisticRegression() , param_grid = hyperparams,
verbose=True, cv=5, scoring = "accuracy", n_jobs =-1)
 lrgd.fit(X_train, y_train)
 print(lrgd.best_score_)
 print(lrgd.best_estimator_ )<train_on_grid>
def solveSantaIP(families, min_occupancy, max_occupancy):
S = pywraplp.Solver('SolveAssignmentProblem', pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)
 n_families = len(families)
 x = {}
candidates = [[] for _ in range(N_DAYS)]
for i in families:
for j in DESIRED[i, :]:
candidates[j].append(i)
 x[i, j] = S.BoolVar('x[%i,%i]' %(i, j))
daily_occupancy = [S.Sum([x[i, j] * FAMILY_SIZE[i] for i in candidates[j]])
 for j in range(N_DAYS)]
family_presence = [S.Sum([x[i, j] for j in DESIRED[i, :]])
 for i in families]
preference_cost = S.Sum([PCOSTM[i, j] * x[i,j] for i in families
for j in DESIRED[i, :] ])
 S.Minimize(preference_cost)
 for i in range(n_families):
S.Add(family_presence[i] == 1)
 for j in range(N_DAYS):
S.Add(daily_occupancy[j] >= min_occupancy[j])
 S.Add(daily_occupancy[j] <= max_occupancy[j])
 res = S.Solve()
resdict = {0:'OPTIMAL', 1:'FEASIBLE', 2:'INFEASIBLE', 3:'UNBOUNDED',
4:'ABNORMAL', 5:'MODEL_INVALID', 6:'NOT_SOLVED'}
print('MIP solver result:', resdict[res])
 l = [(i, j)for i in families
for j in DESIRED[i, :]
if x[i, j].solution_value() >0]
df = pd.DataFrame(l, columns=['family_id', 'day'])
 return df
n_neighbors = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20]
algorithm = ['auto']
weights = ['uniform', 'distance']
leaf_size = [1, 2, 3, 4, 5, 10, 15, 20, 25, 30]
hyperparams = {'algorithm': algorithm, 'weights': weights, 'leaf_size': leaf_size,
'n_neighbors': n_neighbors}
kngd=GridSearchCV(estimator = KNeighborsClassifier() , param_grid = hyperparams,
verbose=True, cv=5, scoring = "accuracy", n_jobs =-1)
 kngd.fit(X_train, y_train)
 print(kngd.best_score_)
 print(kngd.best_estimator_ )<train_on_grid>
def solveSanta() :
df = solveSantaLP()
THRS = 0.999
assigned_df = df[df.n>THRS].copy()
unassigned_df = df[(df.n<=THRS)&(df.n>1-THRS)]
unassigned = unassigned_df.family_id.unique()
print('{} unassigned families'.format(len(unassigned)))
 assigned_df['family_size'] = FAMILY_SIZE[assigned_df.family_id]
occupancy = assigned_df.groupby('day' ).family_size.sum().values
min_occupancy = np.array([max(0, MIN_OCCUPANCY-o)for o in occupancy])
 max_occupancy = np.array([MAX_OCCUPANCY - o for o in occupancy])
 rdf = solveSantaIP(unassigned, min_occupancy, max_occupancy)
 df = pd.concat(( assigned_df[['family_id', 'day']], rdf)).sort_values('family_id')
 return df.day.values
n_estimators = [10, 25, 50, 75, 100]
max_depth = [3, None]
max_features = [1, 3, 5, 7]
min_samples_split = [2, 4, 6, 8, 10]
min_samples_leaf = [2, 4, 6, 8, 10]
hyperparams = {'n_estimators': n_estimators, 'max_depth': max_depth, 'max_features': max_features,
'min_samples_split': min_samples_split, 'min_samples_leaf': min_samples_leaf}
rfgd=GridSearchCV(estimator = RandomForestClassifier() , param_grid = hyperparams,
verbose=True, cv=5, scoring = "accuracy", n_jobs =-1)
 rfgd.fit(X_train, y_train)
 print(rfgd.best_score_)
 print(rfgd.best_estimator_ )<train_on_grid>
N_DAYS = 100
N_FAMILIES = 5000
MAX_OCCUPANCY = 300
MIN_OCCUPANCY = 125
data = pd.read_csv('/kaggle/input/santa-workshop-tour-2019/family_data.csv', index_col='family_id')
 FAMILY_SIZE = data.n_people.values
DESIRED = data.values[:, :-1] - 1
PCOSTM = GetPreferenceCostMatrix(data)
 ACOSTM = GetAccountingCostMatrix()
Cs = [0.001, 0.01, 0.1, 1, 5, 10, 15, 20, 50, 100]
gammas = [0.001, 0.01, 0.1, 1]
hyperparams = {'C': Cs, 'gamma' : gammas}
svgd=GridSearchCV(estimator = SVC(probability=True), param_grid = hyperparams,
verbose=True, cv=5, scoring = "accuracy", n_jobs =-1)
 svgd.fit(X_train, y_train)
 print(svgd.best_score_)
 print(svgd.best_estimator_ )<train_on_grid>
%%time
prediction = solveSanta()
pc, occ = pcost(prediction)
 ac, _ = acost(occ)
 print('{}, {:.2f},({}, {})'.format(pc, ac, occ.min() , occ.max()))
n_restarts_optimizer = [0, 1, 2, 3]
max_iter_predict = [1, 2, 5, 10, 20, 35, 50, 100]
warm_start = [True, False]
hyperparams = {'n_restarts_optimizer': n_restarts_optimizer, 'max_iter_predict': max_iter_predict, 'warm_start': warm_start}
gpgd=GridSearchCV(estimator = GaussianProcessClassifier() , param_grid = hyperparams,
verbose=True, cv=5, scoring = "accuracy", n_jobs =-1)
 gpgd.fit(X_train, y_train)
 print(gpgd.best_score_)
 print(gpgd.best_estimator_ )<train_on_grid>
new = prediction.copy()
findBetterDay4Family(new )
n_estimators = [10, 25, 50, 75, 100, 125, 150, 200]
learning_rate = [0.001, 0.01, 0.1, 0.5, 1, 1.5, 2]
hyperparams = {'n_estimators': n_estimators, 'learning_rate': learning_rate}
adgd=GridSearchCV(estimator = AdaBoostClassifier() , param_grid = hyperparams,
verbose=True, cv=5, scoring = "accuracy", n_jobs =-1)
 adgd.fit(X_train, y_train)
 print(adgd.best_score_)
 print(adgd.best_estimator_ )<train_on_grid>
final = stochastic_product_search(
 top_k=2,
fam_size=8,
original=new,
n_iter=500000,
verbose=1000,
verbose2=50000,
random_state=2019
)
learning_rate = [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.2]
n_estimators = [100, 250, 500, 750, 1000, 1250, 1500]
hyperparams = {'learning_rate': learning_rate, 'n_estimators': n_estimators}
gbgd=GridSearchCV(estimator = GradientBoostingClassifier() , param_grid = hyperparams,
verbose=True, cv=5, scoring = "accuracy", n_jobs =-1)
 gbgd.fit(X_train, y_train)
 print(gbgd.best_score_)
 print(gbgd.best_estimator_ )<choose_model_class>
final = seed_finding(2019, final )
n_estimators = [10, 25, 50, 75, 100]
max_depth = [3, None]
max_features = [1, 3, 5, 7]
min_samples_split = [2, 4, 6, 8, 10]
min_samples_leaf = [2, 4, 6, 8, 10]
hyperparams = {'n_estimators': n_estimators, 'max_depth': max_depth, 'max_features': max_features,
'min_samples_split': min_samples_split, 'min_samples_leaf': min_samples_leaf}
etgd=GridSearchCV(estimator = ExtraTreesClassifier() , param_grid = hyperparams,
verbose=True, cv=5, scoring = "accuracy", n_jobs =-1)
 etgd.fit(X_train, y_train)
 print(etgd.best_score_)
 print(etgd.best_estimator_ )<train_on_grid>
sub = pd.DataFrame(range(N_FAMILIES), columns=['family_id'])
 sub['assigned_day'] = final+1
sub.to_csv('submission.csv', index=False )