Kaggle competition: Conway's Reverse Game of Life 2020
<SOS> metric: PostProcessorKernel Kaggle data source: conways-reverse-game-of-life-2020<count_missing_values>
class OneIterationReverseNet(nn.Module):
def __init__(self, info_ch, ch):
super().__init__()
self.relu = nn.ReLU()
self.conv1 = nn.Conv2d(info_ch, ch, 5, padding=4, padding_mode='circular')
 self.conv2 = nn.Conv2d(ch, ch, 3,)
 self.conv3 = nn.Conv2d(ch, info_ch, 3)
 def forward(self, inp):
x = self.relu(self.conv1(inp))
x = self.relu(self.conv2(x))
x = self.relu(self.conv3(x))
return x
class ReverseModel(nn.Module):
def __init__(self, info_ch=64, ch=128):
super().__init__()
self.relu = nn.ReLU()
self.encoder = nn.Conv2d(1, info_ch, 7, padding=3, padding_mode='circular')
 self.reverse_one_iter = OneIterationReverseNet(info_ch, ch)
 self.decoder = nn.Conv2d(info_ch, 1, 3, padding=1, padding_mode='circular')
 def forward(self, stop, delta):
x = self.relu(self.encoder(stop-0.5))
for i in range(delta.max().item()):
y = self.reverse_one_iter(x)
 mask =(delta > i ).reshape(-1,1,1,1)
 x = x*(~mask ).float() + y*mask.float()
x = self.decoder(x)
 return x
train.isnull().sum()<count_missing_values>
import pandas as pd
from sklearn.model_selection import train_test_split
train.isnull().sum()<data_type_conversions>
train_val = pd.read_csv('/kaggle/input/conways-reverse-game-of-life-2020/train.csv', index_col='id')
 test = pd.read_csv('/kaggle/input/conways-reverse-game-of-life-2020/test.csv', index_col='id')
 train, val = train_test_split(train_val, test_size=0.2, shuffle=True, random_state=42, stratify=train_val['delta'] )
train['Cabin'] = train['Cabin'].fillna('X')
 test['Cabin']=test['Cabin'].fillna('X' )<categorify>
def line2grid_tensor(data, device='cuda'):
grid = data.to_numpy().reshape(( data.shape[0], 1, 25, 25))
return FloatTensor(grid ).to(device)
 class TaskDataset(Dataset):
def __init__(self, data, device='cuda'):
self.delta = LongTensor(data['delta'].to_numpy() ).to(device)
 if data.shape[1] == 1251:
self.start = line2grid_tensor(data.iloc[:,1:626], device)
 self.stop = line2grid_tensor(data.iloc[:,626:], device)
 else:
self.start = None
self.stop = line2grid_tensor(data.iloc[:,1:], device)
 def __len__(self):
return len(self.delta)
 def __getitem__(self, idx):
if self.start is None:
return {'stop': self.stop[idx], 'delta': self.delta[idx]}
return {'start': self.start[idx], 'stop': self.stop[idx], 'delta': self.delta[idx]}
train.loc[train.Age.isnull() ,'Age']=train.groupby("Pclass" ).Age.transform('median')
 test.loc[test.Age.isnull() ,'Age']=test.groupby("Pclass" ).Age.transform('median')
 print(train['Age'].isnull().sum() )<categorify>
dataset_train = TaskDataset(train)
 dataloader_train = DataLoader(dataset_train, batch_size=128, shuffle=True)
 dataset_val = TaskDataset(val)
 dataloader_val = DataLoader(dataset_val, batch_size=128, shuffle=False)
 dataset_test = TaskDataset(test)
 dataloader_test = DataLoader(dataset_test, batch_size=128, shuffle=False )
category = {'A':1, 'B':2, 'C':3, 'D':4, 'E':5, 'F':6, 'G':7, 'X':8, 'T':9}
train['Cabin'] = train['Cabin'].map(category)
 train['Cabin'].unique().tolist()<count_missing_values>
runner = SupervisedRunner(device='cuda', input_key=['stop', 'delta'],)
 loaders = {'train': dataloader_train, 'valid': dataloader_val}
model = ReverseModel()
optimizer = Lookahead(RAdam(params=model.parameters() , lr=1e-3))
criterion = {"bce": nn.BCEWithLogitsLoss() }
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=0.25, patience=2)
 callbacks = [
CriterionCallback(input_key='start', prefix="loss", criterion_key="bce"),
EarlyStoppingCallback(patience=5),
]
runner.train(
 model=model,
criterion=criterion,
optimizer=optimizer,
scheduler=scheduler,
loaders=loaders,
callbacks=callbacks,
logdir="./logs",
num_epochs=999,
main_metric="loss",
minimize_metric=True,
verbose=True,
)
print(train.isnull().sum() )<feature_engineering>
best_model = ReverseModel().to('cuda')
 best_model.load_state_dict(torch.load('logs/checkpoints/best.pth')['model_state_dict'] )
train["Embarked"] = train["Embarked"].fillna(mode(train["Embarked"]))<count_missing_values>
%load_ext Cython
print(train.isnull().sum() )<feature_engineering>
%%cython
c
@cython.cdivision(True)
 @cython.boundscheck(False)
 @cython.nonecheck(False)
 @cython.wraparound(False)
 cdef int calc_neighs(unsigned char[:, :] field, int i, int j, int n, int k):
cdef:
int neighs = 0;
int i_min = i - 1;
int i_pl = i + 1;
int j_min = j - 1;
int j_pl = j + 1;
neighs = 0
if i_min >= 0:
if j_min >= 0:
neighs += field[i_min, j_min]
neighs += field[i_min, j]
if j_pl < k:
neighs += field[i_min, j_pl]
if j_min >= 0:
neighs += field[i, j_min]
if j_pl < k:
neighs += field[i, j_pl]
if i_pl < n:
if j_min >= 0:
neighs += field[i_pl, j_min]
neighs += field[i_pl, j]
if j_pl < k:
neighs += field[i_pl, j_pl]
return neighs
@cython.cdivision(True)
 @cython.boundscheck(False)
 @cython.nonecheck(False)
 @cython.wraparound(False)
 cpdef make_move_cython(unsigned char[:, :] field, int moves):
cdef:
int _, i, j, neighs;
int n, k;
int switch = 0;
unsigned char[:, :] cur_field;
unsigned char[:, :] next_field;
cur_field = np.copy(field)
 next_field = np.zeros_like(field, 'uint8')
 n = field.shape[0]
k = field.shape[1]
for _ in range(moves):
if switch == 0:
for i in range(n):
for j in range(k):
neighs = calc_neighs(cur_field, i, j, n, k)
 if cur_field[i, j] and neighs == 2:
next_field[i, j] = 1
elif neighs == 3:
next_field[i, j] = 1
else:
next_field[i, j] = 0
else:
for i in range(n):
for j in range(k):
neighs = calc_neighs(next_field, i, j, n, k)
 if next_field[i, j] and neighs == 2:
cur_field[i, j] = 1
elif neighs == 3:
cur_field[i, j] = 1
else:
cur_field[i, j] = 0
switch =(switch + 1)% 2
return np.array(next_field if switch else cur_field)
train["Sex"][train["Sex"] == "male"] = 0
train["Sex"][train["Sex"] == "female"] = 1
test["Sex"][test["Sex"] == "male"] = 0
test["Sex"][test["Sex"] == "female"] = 1
train["Embarked"][train["Embarked"] == "S"] = 0
train["Embarked"][train["Embarked"] == "C"] = 1
train["Embarked"][train["Embarked"] == "Q"] = 2
test["Embarked"][test["Embarked"] == "S"] = 0
test["Embarked"][test["Embarked"] == "C"] = 1
test["Embarked"][test["Embarked"] == "Q"] = 2 
<feature_engineering>
def make_move(board: Union[torch.Tensor, np.ndarray], moves: Union[int, List[int]] = 1):

 if isinstance(board, torch.Tensor):
return torch.tensor(_evolve_board(board.detach().cpu().numpy() , moves),
dtype=board.dtype, device=board.device)
 return np.array(_evolve_board(board, moves), dtype=board.dtype)
 def _evolve_board(board, moves):
board = np.array(board ).astype(np.uint8)
 if len(board.shape)== 4:
assert board.shape[1:] ==(1, 25, 25)
 board_evolved = _move_board_3dim(board[:, 0], moves)[:, None]
elif len(board.shape)== 3:
board_evolved = _move_board_3dim(board, moves)
 else:
assert board.shape ==(25, 25)
 board_evolved = make_move_cython(board, moves)
 return np.array(board_evolved, dtype=np.float32)
 def _move_board_3dim(board, moves):
if board.shape ==(1, 25, 25):
board_evolved = make_move_cython(board[0], moves)[:, None]
else:
assert board.shape[1:] ==(25, 25)
 if isinstance(moves, int):
moves = [moves] * len(board)
 assert len(moves)== len(board)
 board_evolved = np.array([make_move_cython(b, move)for b, move in zip(board, moves)])
 return board_evolved
train['fam']=train['SibSp']+train['Parch']+1
test['fam']=test['SibSp']+test['Parch']+1<data_type_conversions>
def batch_accuracy(x, y_pred):
return torch.sum(torch.abs(x - y_pred), dim=(1, 2, 3))
def postprocessor(y_pred, x, moves):

 thresholds = torch.linspace(0, 1, 11, device=x.device)
 errors = torch.cat([batch_accuracy(x.float() ,
make_move(( y_pred > threshold ).float() , moves)
)[None]
for threshold in thresholds],
0)
 best_thresholds = thresholds[torch.argmin(errors, 0)][:, None, None, None]
return(y_pred > best_thresholds ).float()
train['Age']=train['Age'].astype(str)
 test['Age']=test['Age'].astype(str )<feature_engineering>
def predict_batch(model, batch):
model.eval()
with torch.no_grad() :
prediction = model(batch['stop'], batch['delta'])
 prediction = torch.sigmoid(prediction ).detach().cpu().numpy()
return prediction
def predict_loader(model, loader):
predict = [predict_batch(model, batch)for batch in loader]
predict = np.concatenate(predict)
 return predict
def validate_loader(model, loader, lb_delta=None, threshold=0.5):
prediction_val = predict_loader(best_model, loader)
 y_val = loader.dataset.start.detach().cpu().numpy()
score_unoptimized =(( prediction_val > threshold)== y_val ).mean(axis=(1,2,3))
delta_val = loader.dataset.delta.detach().cpu().numpy()
prediction_val = postprocessor(torch.tensor(prediction_val),
x=loader.dataset.stop.cpu() ,
moves=delta_val ).cpu().numpy()
score =(prediction_val == y_val ).mean(axis=(1,2,3))
print(f'All data accuracy(global threshold): {score_unoptimized.mean() }')
 print(f'All data accuracy(optimized threshold): {score.mean() }')
 delta_score = {}
for i in range(1, 6):
delta_score[i] = score[delta_val==i].mean()
print(f'delta={i} accuracy: {delta_score[i]}')
 if lb_delta is not None:
lb_delta = lb_delta.value_counts(normalize=True)
 test_score = sum([lb_delta[i]*delta_score[i] for i in range(1,6)])
 print(f'VAL score : {1-score.mean() }')
 print(f'LB score estimate: {1-test_score}')
 def make_submission(prediction, x, moves, sample_submission_path='/kaggle/input/conways-reverse-game-of-life-2020/sample_submission.csv'):
prediction = postprocessor(prediction, x, moves ).numpy().astype(int ).reshape(-1, 625)
 sample_submission = pd.read_csv(sample_submission_path, index_col='id')
 sample_submission.iloc[:] = prediction
return sample_submission
cat={'2':1, '3':2 , '5':3, '1':4 ,'4':5,'8':6,'6':7,'7':8,'0':9,'9':10}
test['Age']=test['Age'].map(cat)
 test['Age'].unique().tolist()<feature_engineering>
validate_loader(best_model, dataloader_val, test['delta'] )
train['Title'] = train['Name'].map(lambda x: re.compile("([A-Za-z]+)\." ).search(x ).group())
 test['Title'] = test['Name'].map(lambda x: re.compile("([A-Za-z]+)\." ).search(x ).group())
 print(train['Title'].unique())
<feature_engineering>
prediction_test = predict_loader(best_model, dataloader_test)
 submission = make_submission(torch.tensor(prediction_test),
x=dataloader_test.dataset.stop.cpu() ,
moves=dataloader_test.dataset.delta.detach().cpu().numpy())
 submission.to_csv('submission.csv')
 submission.head()