{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nfrom math import ceil\n\nimport torch\nimport torch.nn as nn\nfrom torch.nn import init\nimport torch.optim as optim\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\nfrom torch.utils.data import Dataset, DataLoader, TensorDataset, ConcatDataset, Sampler\n\nimport line_profiler\n%load_ext line_profiler\n\nDATASET_PATH = '../input/trackml-model/'\n\nfrom tqdm import tqdm_notebook\nprint(os.listdir(\"../input\"))\nprint(os.listdir(\"../input/trackml/\"))\nprint(os.listdir(DATASET_PATH))\nprefix='../input/trackml-particle-identification/'","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"c4d2d5e5dd576002fc170d7244005d5f49cc4b04"},"cell_type":"code","source":"from contextlib import contextmanager\nfrom timeit import default_timer\n\n@contextmanager\ndef elapsed_timer():\n    start = default_timer()\n    elapser = lambda: default_timer() - start\n    yield lambda: elapser()\n    end = default_timer()\n    elapser = lambda: end-start","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"524f103d1fc55d22f79a56bd2245fbe6ae91e7a4"},"cell_type":"code","source":"def get_event(event, filter=None):\n    hits = pd.read_csv(prefix+'train_1/%s-hits.csv'%event)\n    cells = pd.read_csv(prefix+'train_1/%s-cells.csv'%event)\n    truth = pd.read_csv(prefix+'train_1/%s-truth.csv'%event)\n    particles = pd.read_csv(prefix+'train_1/%s-particles.csv'%event)\n    return hits, cells, truth, particles\n\ndef create_model(fs = 10):\n    return nn.Sequential(\n        nn.Linear(fs, 800),\n        nn.SELU(),\n        nn.Linear(800, 400),\n        nn.SELU(),\n        nn.Linear(400, 400),\n        nn.SELU(),\n        nn.Linear(400, 400),\n        nn.SELU(),\n        nn.Linear(400, 200),\n        nn.SELU(),\n        nn.Linear(200, 1),\n        nn.Sigmoid()\n    )","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"22ca78487ca459c532750dd133c7ed401e592e3d"},"cell_type":"markdown","source":"# Step 1 - Prepare training data\n* use 10 events for training\n* input: hit pair\n* output: 1 if two hits are the same particle_id, 0 otherwise.\n* feature size: 10 (5 per hit)"},{"metadata":{"trusted":true,"_uuid":"a286d6c7c82564b627886323f27bafad3ae47d1e"},"cell_type":"code","source":"USE_GPU = True\n\nTRAIN_1 = False\nTRAIN_2 = False\nTRAIN = TRAIN_1 or TRAIN_2\nREDUCE_ON_PLATEAU = True\n\nLOADING_MODEL = True\nLOADING_MODEL_H = True\n\nPRE_PROCESS = False\nPRE_PROCESS_H = False\n\nSAVING = False\nLOADING_PREFIX = DATASET_PATH\nEVENT_SIZE_PATH = 'event_rows.csv'\nEVENT_SIZE_H_PATH = 'event_rows-h.csv'\n\nif USE_GPU and torch.cuda.is_available():\n    device = torch.device('cuda')\nelse:\n    device = torch.device('cpu')\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"d400d7b6cb79d223452ac9a8af50750b9e4498e5"},"cell_type":"code","source":"def get_features(event_name):\n    hits, cells, truth, particles = get_event(event_name)\n    \n    # Filter out un-used columns early\n    hits = hits[['hit_id', 'x', 'y', 'z']]\n    truth = truth[['particle_id', 'hit_id']]    \n    \n\n    # as_index=False so the group by retain the column name\n    cell_by_hit_id = cells.groupby(['hit_id'], as_index=False)\n    cell_count = cell_by_hit_id.value.count().rename(columns={'value':'cell_count'})\n    charge_value = cell_by_hit_id.value.sum().rename(columns={'value':'charge_value'})\n    \n    # Scaling\n    hits[['x', 'y', 'z']] /= 1000\n    cell_count['cell_count'] /= 10\n    \n    truth = pd.merge(truth, cell_count, on='hit_id')\n    truth = pd.merge(truth, charge_value, on='hit_id')\n    truth = pd.merge(truth, hits, on='hit_id')\n    # The columns of truth are as follow\n    # ['particle_id', 'hit_id', 'x', 'y', 'z', 'cell_count', 'charge_value']\n    return truth\n\ndef pre_process(event_name, print_size=True):\n    features = get_features(event_name)\n    \n    columns_needed = ['x', 'y', 'z', 'cell_count', 'charge_value']\n    columns_needed_all = [c + '_x' for c in columns_needed] + [c + '_y' for c in columns_needed] + ['label']\n\n    # Get all the hits that's identified with a particle\n    true_pairs = features[features.particle_id != 0]\n    # Merge to create all hit pairs that's identified with the same particle\n    true_pairs = pd.merge(true_pairs, true_pairs, on='particle_id')\n    # Filter all the pairs that has the same hit_id\n    true_pairs = true_pairs[true_pairs.hit_id_x != true_pairs.hit_id_y]\n    # Add a new column to indicate this dataset is the true dataset\n    true_pairs['label'] = 1\n    # Filter the only columns needed\n    true_pairs = true_pairs[columns_needed_all]\n    \n    FALSE_PAIR_RATIO = 3\n    size = len(true_pairs) * FALSE_PAIR_RATIO\n    p_id = features.particle_id.values\n    # Generated random hit idx pairs\n    i = np.random.randint(len(features), size=size)\n    j = np.random.randint(len(features), size=size)\n    # Get the hit idx pair that's either assoicated with particle id 0 or different particle id\n    hit_idx = (p_id[i]==0) | (p_id[i]!=p_id[j])\n    i, j = i[hit_idx], j[hit_idx]\n    # Filter and create features with the correct order of the columns\n    features = features[columns_needed]\n    false_pairs = pd.DataFrame(\n        np.hstack((features.values[i], features.values[j], np.zeros((len(i),1)))),\n        columns=columns_needed_all)\n\n    processed = pd.concat([true_pairs, false_pairs], axis=0)\n    processed = processed.sample(frac=1).reset_index(drop=True)\n    \n    if print_size:\n        # Create a DataFrame just to pretty-print ;)\n        print(event_name)\n        print(pd\n              .DataFrame(data={\n                  'True': ['{:,}'.format(len(true_pairs))],\n                  'False': ['{:,}'.format(len(false_pairs))],\n                  'Total': ['{:,}'.format(len(processed))]\n              })\n              .to_string(index=False))\n    return processed","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"21e9a3725a76c5c6fc2b1a52b1af91cfa59d1376"},"cell_type":"code","source":"if PRE_PROCESS:\n    event_rows = []\n    for i in tqdm_notebook(range(10, 20)):\n        event_name = 'event0000010%02d'%i\n        file_name = '%s.feather' % event_name\n        processed = pre_process(event_name)\n        event_rows.append((file_name, len(processed.index)))\n        processed.to_feather(file_name) # Save to disk\n        print('saved %s' % file_name)\n\n    pd.DataFrame(event_rows).to_csv(EVENT_SIZE_PATH, index=False)\n    print('event rows saved')\n    del processed\nelse:\n    print('load event rows')\n    event_rows = list(pd.read_csv(LOADING_PREFIX + EVENT_SIZE_PATH).itertuples(index=False, name=None))\n    event_rows = [(LOADING_PREFIX + r[0], r[1]) for r in event_rows]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"fbd0b08a9020aa3eada8a1fa33b1b825702aeee1"},"cell_type":"code","source":"from datetime import datetime\nfrom feather import read_dataframe as feather_read\nfrom multiprocessing import current_process\nfrom threading import current_thread\nimport bisect\n\nclass FeatherCache():\n    @staticmethod\n    def cumsum(processed_rows):\n        r, s = [], 0\n        for row in processed_rows:\n            l = row[1]\n            r.append(l + s)\n            s += l\n        return r\n    \n    def __init__(self, processed_rows, cache_size=2, print_proc=False):\n        self.processed_rows = processed_rows\n        self.cumulative_sizes = self.cumsum(processed_rows)\n        self.cache_size = cache_size\n        self.print_proc = print_proc\n        \n        # warm up the loading by having two processed events loaded\n        self.cache = {}\n        for file_name, size in processed_rows[0:cache_size]:\n            self.cache[file_name] = feather_read(file_name)\n        \n        self.time_stamps = {}\n        \n\n    def __len__(self):\n        return self.cumulative_sizes[-1]\n    \n    @property\n    def LRU_filename(self):\n        least = None\n        for file_name, time_stamp in self.time_stamps.items():\n            if least is None:\n                least = (file_name, time_stamp)\n            elif time_stamp < least[1]:\n                least = (file_name, time_stamp)\n        return least[0]\n    \n    #TODO prefetch in another process when the file is loaded\n    # https://stackoverflow.com/questions/45394783/multiprocess-reading-from-file\n    def get_file_dataframe(self, file_name):\n        if file_name in self.cache:\n            # If in the cache, just get it\n            self.time_stamps[file_name] = datetime.now()\n            return self.cache[file_name]\n        else:\n            if self.print_proc:\n                process_name = current_process().name\n                thread_name = current_thread().name\n                print('reading %s from thread %s, and process %s' % (file_name, thread_name, process_name))\n            if len(self.cache) > self.cache_size:\n                key = self.LRU_filename\n                if self.print_proc:\n                    print('delete %s' % key)\n                del self.cache[key]\n                del self.time_stamps[key]\n\n            self.cache[file_name] = feather_read(file_name)\n            self.time_stamps[file_name] = datetime.now()\n            return self.cache[file_name]\n                \n    def get_map(self, indcies):\n        # Map the indices back to to file_name and its corrsponding indcies\n        file_dict = {}\n        # Optimize for single dataset\n        if (len(indcies) >= 2):\n            front_idx = bisect.bisect_right(self.cumulative_sizes, indcies[0])\n            back_idx = bisect.bisect_right(self.cumulative_sizes, indcies[-1])\n            if front_idx == back_idx:\n                file_name = self.processed_rows[front_idx][0]\n                if front_idx == 0:\n                    return {file_name : indcies}\n            \n        #else:\n        for idx in indcies:\n            dataset_idx = bisect.bisect_right(self.cumulative_sizes, idx)\n            if dataset_idx == 0:\n                sample_idx = idx\n            else:\n                sample_idx = idx - self.cumulative_sizes[dataset_idx - 1]\n            file_name = self.processed_rows[dataset_idx][0]\n            file_dict.setdefault(file_name, []).append(sample_idx)\n        return file_dict\n    \n    def get_items(self, indcies):\n        d = self.get_map(indcies)\n        ds = None\n        for file_name in d:\n            sample_idxs = d[file_name]\n            f_ds = self.get_file_dataframe(file_name).iloc[sample_idxs]\n            if ds is None:\n                ds = f_ds\n            else:\n                ds = ds.append(f_ds, ignore_index=True)\n        return ds\n            \nclass FeatherDataset(Dataset):\n    def __init__(self, feather_cache, to_items_fn=None):\n        self.cache = feather_cache\n        self.to_items_fn = to_items_fn\n        \n    def __len__(self):\n        return len(self.cache)\n\n    def __getitem__(self, idx):\n        return idx;\n    \n    def get_items(self, indcies):\n        ds = self.cache.get_items(indcies)\n        rows = torch.as_tensor(ds.values)\n        return rows if self.to_items_fn is None else self.to_items_fn(rows)\n    \n    @property\n    def collate_fn(self):\n        return self.get_items\n\nclass DataframeDataset(Dataset):\n    def __init__(self, dataframe, opti_seq=False, to_items_fn=None, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.dataframe = dataframe\n        self.opti_seq = opti_seq\n        self.to_items_fn = to_items_fn\n        \n    def __len__(self):\n        return len(self.dataframe)\n    \n    def __getitem__(self, idx):\n        return idx\n    \n    def get_items(self, indcies):\n        if self.opti_seq and len(indcies) >= 2:\n            rows = torch.as_tensor(self.dataframe.values[indcies[0]:indcies[-1]+1])\n        else:\n            rows = torch.as_tensor(self.dataframe.iloc[indcies].values)\n        return rows if self.to_items_fn is None else self.to_items_fn(rows)\n    \n    @property\n    def collate_fn(self):\n        return self.get_items","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"506fe3dd02bf1949dc0111d7530df826b1c870fb"},"cell_type":"code","source":"class SequentialRangeSampler(Sampler):\n    def __init__(self, data_source, num_samples=None):\n        self.data_source = data_source\n        self.num_samples = range(len(self.data_source)) if num_samples is None else num_samples\n\n    def __iter__(self):\n        return iter(self.num_samples)\n\n    def __len__(self):\n        return len(self.data_source)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"17badacb2e7c17d15ae6ba368ee40609cf0dfe5d"},"cell_type":"code","source":"def create_loaders(dataset, batch_size, validation_split):\n    dataset_size = len(dataset)\n    num_val = int(validation_split * dataset_size)\n    num_train = dataset_size - num_val\n\n    loader_train = DataLoader(dataset, batch_size=batch_size, num_workers=0, pin_memory=True,\n                              sampler=SequentialRangeSampler(range(num_train)),\n                              collate_fn=dataset.collate_fn)\n    loader_val = DataLoader(dataset, batch_size=batch_size, num_workers=0, pin_memory=True,\n                            sampler=SequentialRangeSampler(range(num_train, dataset_size)),\n                            collate_fn=dataset.collate_fn)\n    return loader_train, loader_val","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"ac809d0ae97a75d8e1d52d6856b5905950b1afd9"},"cell_type":"code","source":"if TRAIN_1:\n    batch_size = 8000\n    validation_split = .05 # 5%\n    cache = FeatherCache(event_rows)\n    dataset = FeatherDataset(cache, lambda rows : (rows[:, :-1], rows[:, -1].view(-1, 1)))\n    loader_train, loader_val = create_loaders(dataset, batch_size, validation_split)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"9f38fbd32829f9ca2a18f8b00bf3db26f57f4297"},"cell_type":"markdown","source":"# Step 2 - Train model"},{"metadata":{"trusted":true,"_uuid":"81384ee15d418ecb454d4b78b8e1ebd351f78ed3"},"cell_type":"code","source":"def check_accuracy(loader, model, thr=0.5):\n    num_correct = 0\n    num_samples = 0\n    model.eval()  # set model to evaluation mode\n    with torch.no_grad():\n        for x, y in loader:\n            x = x.to(device=device, dtype=torch.float)  # move to device, e.g. GPU\n            y = y.view(-1).to(device=device, dtype=torch.uint8)\n            scores = model(x)\n            scores = (scores > thr).view(-1)\n            num_correct += (scores == y).sum()\n            num_samples += scores.size(0)\n        acc = float(num_correct) / num_samples\n        return (num_correct, num_samples, acc)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"287159410b61fe0d91390e5450bf628beda1641c"},"cell_type":"code","source":"def train_model(model, optimizer, criterion, loader_train, loader_val, epochs=1, reduce_on_plateau=False, epoch_callback=None):\n    \"\"\"\n    Train a model using the PyTorch Module API.\n    \n    Inputs:\n    - model: A PyTorch Module giving the model to train.\n    - optimizer: An Optimizer object we will use to train the model\n    - criterion: A loss function\n    - epochs: (Optional) A Python integer giving the number of epochs to train for\n    \n    Returns: Nothing, but prints model accuracies during training.\n    \"\"\"\n    with elapsed_timer() as elapser:\n        model = model.to(device=device)  # move the model parameters to CPU/GPU\n        total_second = 0\n        if reduce_on_plateau:\n            scheduler = ReduceLROnPlateau(optimizer, 'max', patience=5, threshold=1e-3, verbose=True)\n        for e in tqdm_notebook(range(epochs)):\n            begin_epoch = elapser()\n            for t, (x, y) in enumerate(tqdm_notebook(loader_train, desc='Epoch %d' % e, leave=False)):\n                model.train()  # put model to training mode\n                x = x.to(device=device, dtype=torch.float)  # move to device, e.g. GPU\n                y = y.view(-1, 1).to(device=device, dtype=torch.float) # BCELoss only support float as y\n\n                # Zero out all of the gradients for the variables which the optimizer\n                # will update.\n                optimizer.zero_grad()\n\n                scores = model(x)\n                loss = criterion(scores, y)\n\n                # This is the backwards pass: compute the gradient of the loss with\n                # respect to each  parameter of the model.\n                loss.backward()\n\n                # Actually update the parameters of the model using the gradients\n                # computed by the backwards pass.\n                optimizer.step()\n\n            num_correct, num_samples, acc = check_accuracy(loader_val, model)\n            end_epoch = elapser()\n            print('%.2fs - Epoch %d, Iteration %d, loss = %.4f, %d / %d correct (%.2f %%)' % (end_epoch - begin_epoch, e, t, loss.item(), num_correct, num_samples, acc * 100))\n            if epoch_callback is not None:\n                epoch_callback(num_correct, num_samples, acc, loss)\n            if reduce_on_plateau:\n                scheduler.step(acc)\n    print('Total time: %.2fs' % elapser())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"197bbb13286aad38526e66ae7738518f042d05cf"},"cell_type":"code","source":"model_torch = create_model().to(device)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"6afc7c09d7fb68853cc616e4b4fa32007f6fb383"},"cell_type":"code","source":"if TRAIN_1 and REDUCE_ON_PLATEAU:\n    lr = -3\n    optimizer = optim.Adam(model_torch.parameters(), lr=10**lr)\n    criterion = nn.BCELoss()\n    train_model(model_torch, optimizer, criterion, loader_train, loader_val, epochs=50, reduce_on_plateau=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"c14028370c7892fe2fbc901bdbdbcdbc17e958ff","scrolled":false},"cell_type":"code","source":"if TRAIN_1 and not REDUCE_ON_PLATEAU:\n    lr = -5\n    optimizer = optim.Adam(model_torch.parameters(), lr=10**lr)\n    criterion = nn.BCELoss()\n    train_model(model_torch, optimizer, criterion, loader_train, loader_val, epochs=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"a6a9f21fe99f8196ca999f4f3685ec649de590ae","scrolled":true},"cell_type":"code","source":"if TRAIN_1 and not REDUCE_ON_PLATEAU:\n    lr = -4\n    optimizer = optim.Adam(model_torch.parameters(), lr=10**lr)\n    criterion = nn.BCELoss()\n    train_model(model_torch, optimizer, criterion, loader_train, loader_val, epochs=20)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"scrolled":true,"_uuid":"b9674efc34f20b572c2d7c6598a2848a4d95060f"},"cell_type":"code","source":"if TRAIN_1 and not REDUCE_ON_PLATEAU:\n    lr = -5\n    optimizer = optim.Adam(model_torch.parameters(), lr=10**lr)\n    criterion = nn.BCELoss()\n    train_model(model_torch, optimizer, criterion, loader_train, loader_val, epochs=3)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"ae9036c2ec28f16aa3027e12dae5b14099d860cc"},"cell_type":"code","source":"if TRAIN_1 and SAVING:\n    print('saving model')\n    torch.save(model_torch.state_dict(), './torch_model.pt')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"185872dc6a27b54f7b810c66a5076796c8c8ec01"},"cell_type":"markdown","source":"# Step 3 - Hard Negative Mining"},{"metadata":{"trusted":true,"_uuid":"73f15df6a16f5837fbcdf0d49fce6bfabb4db2d6"},"cell_type":"code","source":"def predict(dataframe, model, batch_size=8000, num_worker=0):\n    rows = torch.as_tensor(dataframe.values)\n    num_elements = len(rows)\n    num_batches = -(-num_elements // batch_size) # Round up\n    model.eval()  # set model to evaluation mode\n    scores = torch.zeros(num_elements, dtype=torch.float)\n    with torch.no_grad():\n        for i in range(num_batches):\n            start = i * batch_size\n            end = num_elements if i == num_batches - 1 else start + batch_size\n            x_batch = rows[start:end]\n            x_batch = x_batch.to(device=device, dtype=torch.float)  # move to device, e.g. GPU\n            scores[start:end] = model(x_batch).view(-1)\n\n    return scores\n\ndef predict_true(dataframe, model, batch_size=8000, thr=0.5):\n    scores = predict(dataframe, model, batch_size)\n    indices = (scores > thr).nonzero()[:, 0]\n    return dataframe.iloc[indices]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"4dddcfc030bfe654e8032e478c29b4e9b74df9fd"},"cell_type":"code","source":"def negative_mine(model, event_name, smaple_size=30000000, print_size=True):\n    with elapsed_timer() as elapser: \n        hits, cells, truth, particles = get_event(event_name)\n\n        # Filter out un-used columns early\n        hits = hits[['hit_id', 'x', 'y', 'z']]\n        truth = truth[['particle_id', 'hit_id']]    \n\n\n        # as_index=False so the group by retain the column name\n        cell_by_hit_id = cells.groupby(['hit_id'], as_index=False)\n        cell_count = cell_by_hit_id.value.count().rename(columns={'value':'cell_count'})\n        charge_value = cell_by_hit_id.value.sum().rename(columns={'value':'charge_value'})\n\n        # Scaling\n        hits[['x', 'y', 'z']] /= 1000\n        cell_count['cell_count'] /= 10\n\n        features = pd.merge(truth, cell_count, on='hit_id')\n        features = pd.merge(features, charge_value, on='hit_id')\n        features = pd.merge(features, hits, on='hit_id')\n        # The columns of truth are as follow\n        # ['particle_id', 'hit_id', 'x', 'y', 'z', 'cell_count', 'charge_value']\n\n        columns_needed = ['x', 'y', 'z', 'cell_count', 'charge_value']\n        columns_needed_all = [c + '_x' for c in columns_needed] + [c + '_y' for c in columns_needed]\n\n        p_id = features.particle_id.values\n        # Generated random hit idx pairs\n        i = np.random.randint(len(features), size=smaple_size)\n        j = np.random.randint(len(features), size=smaple_size)\n        # Get the hit idx pair that's either assoicated with particle id 0 or different particle id\n        hit_idx = (p_id[i]==0) | (p_id[i]!=p_id[j])\n        i, j = i[hit_idx], j[hit_idx]\n        # Filter and create features with the correct order of the columns\n        features = features[columns_needed]\n        false_pairs = pd.DataFrame(\n            np.hstack((features.values[i], features.values[j])),\n            columns=columns_needed_all)\n\n        before_size = len(false_pairs)\n        false_pairs = predict_true(false_pairs, model_torch).reset_index(drop=True)\n        false_pairs['label'] = 0\n        after_size = len(false_pairs)\n        if print_size:\n            print(event_name)\n            print('%.2fs - Before: %s, After: %s, Percent Pass: %d%%' % (elapser(), '{:,}'.format(before_size), '{:,}'.format(after_size), after_size/before_size*100))\n        return false_pairs","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"2d4d11c929200b31498b005b41a87eec6adf477c"},"cell_type":"code","source":"def preprocess_h():\n    event_rows_h = []\n    for idx, i in enumerate(tqdm_notebook(range(10,20))):\n        event_name = 'event0000010%02d' % i\n        file_name = '%s.feather' % event_name\n        processed_negative = negative_mine(model_torch, event_name)\n        with elapsed_timer() as elapser:\n            processed = feather_read(event_rows[idx][0]) # read the path from event_rows loaded\n            processed = processed.append(processed_negative, ignore_index=True)\n            processed = processed.sample(frac=1).reset_index(drop=True)\n            print('Read, append and re-sample: %.2fs' % elapser())\n        event_rows_h.append((file_name, len(processed.index)))\n        processed.to_feather(file_name) # Save to disk\n        print('saved %s' % file_name)\n\n    pd.DataFrame(event_rows_h).to_csv(EVENT_SIZE_H_PATH, index=False)\n    print('event rows h saved')\n    return event_rows_h\n\n# if you skip step2, you still need to run step1 to get training data.\nif LOADING_MODEL:\n    print('load model')\n    model_torch.load_state_dict(torch.load('../input/trackml-model/torch_model.pt'))\n\n# Preprocess\nif PRE_PROCESS_H:\n    event_rows_h = preprocess_h()\nelse:\n        print('load event rows hard')\n        event_rows_h = list(pd.read_csv(LOADING_PREFIX + EVENT_SIZE_H_PATH).itertuples(index=False, name=None))\n        event_rows_h = [(LOADING_PREFIX + r[0], r[1]) for r in event_rows_h]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"7d82dea265c632c7f7b734449b1a2a99ea93028b","scrolled":true},"cell_type":"code","source":"if TRAIN_2:\n    batch_size = 8000\n    validation_split = .05 # 5%\n    cache = FeatherCache(event_rows_h[::-1]) # invert to switch it up?\n    dataset = FeatherDataset(cache, lambda rows : (rows[:, :-1], rows[:, -1].view(-1, 1)))\n    loader_train, loader_val = create_loaders(dataset, batch_size, validation_split)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"26c88b7ce0fa96f7ff7d97696561cc2eadf67cf7"},"cell_type":"code","source":"if TRAIN_2 and REDUCE_ON_PLATEAU:\n    lr = -4\n    optimizer = optim.Adam(model_torch.parameters(), lr=10**lr)\n    criterion = nn.BCELoss()\n    train_model(model_torch, optimizer, criterion, loader_train, loader_val, epochs=50, reduce_on_plateau=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"a1cc559f08c550d288a4b191dde298a3ab42ef75"},"cell_type":"code","source":"if TRAIN_2 and not REDUCE_ON_PLATEAU:\n    lr = -4\n    optimizer = optim.Adam(model_torch.parameters(), lr=10**lr)\n    criterion = nn.BCELoss()\n    train_model(model_torch, optimizer, criterion, loader_train, loader_val, epochs=30)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"6e531ceaea4da5601ab8283c2d0900dfdc87f9b3"},"cell_type":"code","source":"if TRAIN_2 and not REDUCE_ON_PLATEAU:\n    lr = -5\n    optimizer = optim.Adam(model_torch.parameters(), lr=10**lr)\n    criterion = nn.BCELoss()\n    train_model(model_torch, optimizer, criterion, loader_train, loader_val, epochs=10)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"389ff2f3d9980f117236de6f8e1b667a9cbce856","scrolled":false},"cell_type":"code","source":"if TRAIN_2 and not REDUCE_ON_PLATEAU:\n    lr = -6\n    optimizer = optim.Adam(model_torch.parameters(), lr=10**lr)\n    criterion = nn.BCELoss()\n    train_model(model_torch, optimizer, criterion, loader_train, loader_val, epochs=3)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"793dc0cfc765eccdd9297c14f1af2e337a189870"},"cell_type":"code","source":"if TRAIN_2 and not REDUCE_ON_PLATEAU:\n    lr = -7\n    optimizer = optim.Adam(model_torch.parameters(), lr=10**lr)\n    criterion = nn.BCELoss()\n    train_model(model_torch, optimizer, criterion, loader_train, loader_val, epochs=3)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b4f40ace4294695b6efeee9e8492d81239b1c26a"},"cell_type":"code","source":"if TRAIN_2 and SAVING:\n    torch.save(model_torch.state_dict(), './torch_model_h.pt')\nif TRAIN_2:\n    del dataframe\n    del loader_train\n    del loader_val","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"1e5db9b7e283f01e8016e919c0e4d4c4731bf247"},"cell_type":"markdown","source":"# Step 4 - Test event 1001"},{"metadata":{"trusted":true,"_uuid":"ba7f35bbd13b606908207e4dbea86190a681f8cc"},"cell_type":"code","source":"if LOADING_MODEL_H:\n    print('load model_h')\n    model_torch.load_state_dict(torch.load('../input/trackml-model/torch_model_h.pt'))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"9f5316a8ba149dde753c6a4daec5c0923f21f26a"},"cell_type":"code","source":"batch_size = 8000\nvalidation_split = .05 # 5%\ncache = FeatherCache(event_rows_h[::-1]) # invert to switch it up?\ndataset = FeatherDataset(cache, lambda rows : (rows[:, :-1], rows[:, -1].view(-1, 1)))\nloader_train, loader_val = create_loaders(dataset, batch_size, validation_split)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"215b0242d4faec3e743aad766f685477025b3cdc"},"cell_type":"code","source":"event = 'event000001001'\nfeatures = get_features(event)\nhits, cells, truth, particles = get_event(event)\n\n# Count number of hits of each group 'volume_id','layer_id','module_id'\n# The hit_id is also sorted by \n# Use the line below is see for yourself\n# (hits.groupby(['volume_id','layer_id','module_id'])['hit_id'].head(len(hits)) == np.arange(1, len(hits)+1)).all()\n# Also, the sum of counts is same as the number of all hits. np.sum(count) == len(hits)\ncount = hits.groupby(['volume_id','layer_id','module_id'])['hit_id'].count().values\n# Map hits index to the individual identifible module\nmodule_id = np.zeros(len(hits), dtype='int32')\n\n# for each individual identifible module\nfor i in range(len(count)):\n    si = np.sum(count[:i])\n    module_id[si:si+count[i]] = i\n    #print('%d:%d(%d + %d) = %d' % (si, si+count[i], si, count[i], i))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"c782febc36f0a06c42bd4f8fa6d942b188543090"},"cell_type":"code","source":"def predict_pairs_test(model, hit_idx, features, thr=0.7):\n    num_hits = len(features)\n    columns_needed = ['x', 'y', 'z', 'cell_count', 'charge_value']\n    features = features[columns_needed]\n\n    # Load the features of a single hit to fill the \"features\"\n    # Effectively, we are preparing the features pairs where\n    # the second pair of the feature set is the hit we are\n    # looking at. The \"features\" looks as as follow:\n    # [f(0), f(3)]\n    # [f(1), f(3)]\n    # [    ...   ]\n    # [f(n), f(3)]\n    target = features.iloc[[hit_idx]]\n    columns = features.columns\n    features = features.add_suffix('_x')\n    for c in columns:\n        # Broadcast to the whole column\n        features[c + '_y'] = target[c][hit_idx]\n    \n    scores = predict(features, model, batch_size=num_hits)\n    indices = (scores > thr).nonzero()[:, 0]\n    # Re-eval the feature pairs that give scores higher than thr\n    # by flipping the order of the pair\n    swaped_features = features.iloc[indices]\n    swaped_columns = [c + '_y' for c in columns_needed] + [c + '_x' for c in columns_needed]\n    swaped_scores = predict(swaped_features[swaped_columns], model, batch_size=len(indices))\n    \n    # Average the scores of selected indices\n    scores[indices] = (scores[indices] + swaped_scores)/2\n    \n    return scores\n\ndef get_path(hit, features, module_id, model, mask, thr):\n    path = [hit]\n    a = 0\n    while True:\n        # Use the last hit in the path to predict the next hit\n        c = predict_pairs_test(model, path[-1], features, thr/2)\n        c = c.numpy()\n        # Update the mask to the hits that passed the thr and mask\n        mask = (c > thr) * mask\n        # Set the mask so we can't use the same hit as the hit next\n        mask[path[-1]] = 0\n        \n        if 1: # ???\n            # Get hit_index of all the predictions that passed thr\n            cand = np.where(c > thr)[0]\n            # If there's at least on hit that passed the thr\n            if len(cand) > 0:\n                # Mask any hit index that is in the module group of\n                # any of hit in the current path\n                mask[cand[np.isin(module_id[cand], module_id[path])]] = 0\n        # a is the accumulated scores\n        # len(a) should get smaller over iterations before mask will keep limiting\n        # the possible next hit.\n        # The a.max of the last iteration is eliminated by the mask, so that\n        # next hit will be based on the sum of the scores of the previous scores\n        # and the current scores that's base on the last hit given they are premitted\n        # by the mask.\n        a = (c + a) * mask\n        if a.max() < thr * len(path):\n            break\n        path.append(a.argmax())\n    return path\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"98400d020f1339b0a467047d3acff43925f39080","scrolled":true},"cell_type":"code","source":"# select one hit to construct a track\nfor hit in range(3):\n#     path = %lprun -m torch.utils.data get_path(hit, features, module_id, model_torch, np.ones(len(truth)), 0.95)\n#     path = %lprun -f DataframeDataset.get_items get_path(hit, features, module_id, model_torch, np.ones(len(truth)), 0.95)\n#     path = %lprun -f predict get_path(hit, features, module_id, model_torch, np.ones(len(truth)), 0.95)\n    path = get_path(hit, features, module_id, model_torch, np.ones(len(truth)), 0.95)\n    # Get all the hit index of particle_id that's assoicated with 'hit'\n    gt = np.where(truth.particle_id == truth.particle_id[hit])[0]\n    path.sort()\n    print('hit_id = ', hit+1)\n    print('reconstruct :', path)\n    print('ground truth:', gt.tolist())","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"b218782577435528ac3e131eac33e7d4b8f64d75"},"cell_type":"markdown","source":"# Step 5 - Predict and Score\n"},{"metadata":{"trusted":true,"_uuid":"658c94a96616e383a4e786d09e23b4533360ff0c"},"cell_type":"code","source":"def predict_all_pairs(model, features):\n    num_hits = len(features)\n    columns_needed = ['x', 'y', 'z', 'cell_count', 'charge_value']\n    features = features[columns_needed]\n    \n    preds = [0] * num_hits\n    for i in tqdm_notebook(range(len(features)-1)):\n        # Load the features of a single hit to fill the \"features\"\n        # Effectively, we are preparing the features pairs where\n        # the second pair of the feature set is the hit we are\n        # looking at. The \"features\" looks as as follow:\n        # [f(0), f(3)]\n        # [f(1), f(3)]\n        # [    ...   ]\n        # [f(n), f(3)]\n        target = features.iloc[[i]]\n        features_predict = features.iloc[i+1:]\n        columns = features_predict.columns\n        features_predict = features_predict.add_suffix('_x')\n        for c in columns:\n            features_predict[c + '_y'] = target[c][i]\n\n        scores = predict(features_predict, model, batch_size=num_hits)\n        indices = (scores > 0.2).nonzero()[:, 0]\n\n        if len(indices) > 0:\n            # Re-eval the feature pairs that give scores higher than thr\n            # by flipping the order of the pair\n            swaped_features = features_predict.iloc[indices]\n            swaped_columns = [c + '_y' for c in columns_needed] + [c + '_x' for c in columns_needed]\n            swaped_scores = predict(swaped_features[swaped_columns], model, batch_size=len(indices))\n\n            # Average the scores of selected indices\n            scores[indices] = (scores[indices] + swaped_scores)/2\n        \n        indices = (scores > 0.5).nonzero()[:, 0]\n        # Append the result of hit i as two pairs as numpy array of hit_index and scores\n        preds[i] = [(indices+i+1).numpy(), scores[indices].numpy()]\n        \n    preds[-1] = [np.array([], dtype='int64'), np.array([], dtype='float32')]\n\n    # rebuild to NxN\n    # for ii in reversed(range(len(preds))):\n    for i in range(len(preds)): # for each hit\n        ii = len(preds)-i-1 # looping backward\n        for j in range(len(preds[ii][0])): # for each prediction of a hit\n            jj = preds[ii][0][j] # get the hit index of the prediction\n            # Build symmetry between the prediction and the hit\n            # like TTA above.\n            preds[jj][0] = np.insert(preds[jj][0], 0 ,ii)\n            preds[jj][1] = np.insert(preds[jj][1], 0 ,preds[ii][1][j])\n    return preds","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"e6376a9dc3e7bc9a5d4f875cfc225e90b1361871","scrolled":true},"cell_type":"code","source":"# Predict all pairs for reconstruct by all hits. (takes 2.5hr but can skip)\nskip_predict = True\n\nif skip_predict is False:\n    preds = predict_all_pairs(model_torch, features)\n    if SAVING:\n        np.save('my_%s.npy'%event, preds)\nelse:\n    print('load predicts')\n    preds = np.load('../input/trackml/my_%s.npy'%event)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"0d34e0bcdc6ba971b4bc7bab1e8c185e17523956"},"cell_type":"code","source":"def get_path2(hit, mask, thr):\n    path = [hit]\n    a = 0\n    while True:\n        c = get_predict2(path[-1])\n        mask = (c > thr)*mask\n        mask[path[-1]] = 0\n        \n        if 1:\n            cand = np.where(c>thr)[0]\n            if len(cand)>0:\n                mask[cand[np.isin(module_id[cand], module_id[path])]]=0\n                \n        a = (c + a)*mask\n        if a.max() < thr*len(path):\n            break\n        path.append(a.argmax())\n    return path\n\ndef get_predict2(p):\n    c = np.zeros(len(preds))\n    c[preds[p, 0]] = preds[p, 1]\n    return c","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"0a1af92b508896ec7fc6da0d5f89f153246f73b1"},"cell_type":"code","source":"# reconstruct by all hits. (takes 0.6hr but can skip)\nskip_reconstruct = True\n\nif skip_reconstruct == False:\n    tracks_all = []\n    thr = 0.85\n    # This is probably the optimization to get the best path.\n    x4 = True\n    for hit in tqdm_notebook(range(len(preds))):\n        m = np.ones(len(truth)) # all ones as mask\n        path  = get_path2(hit, m, thr)\n        if x4 and len(path) > 1:\n            # ban the second hit from the frist run on the second run\n            m[path[1]]=0\n            path2  = get_path2(hit, m, thr)\n            if len(path) < len(path2):\n                # If the path from the second run is longer than the frist\n                # run it again with the second hit from second run blocked\n                path = path2\n                m[path[1]]=0\n                path2  = get_path2(hit, m, thr)\n                if len(path) < len(path2):\n                    # if The path from the thrid run is longer than\n                    # the second run, use it\n                    path = path2\n            elif len(path2) > 1:\n                # if the path from the second is small or equal to\n                # the frist run, try rerunning with\n                # second hit from the frist path re-enabled, and the \n                # second hit from the second path banned.\n                m[path[1]] = 1\n                m[path2[1]] = 0\n                path2  = get_path2(hit, m, thr)\n                if len(path) < len(path2):\n                    # If the path from the thrid run is longer than the frist,\n                    # use it.\n                    path = path2\n        tracks_all.append(path)\n    #np.save('my_tracks_all', tracks_all)\nelse:\n    print('load tracks')\n    tracks_all = np.load('../input/trackml/my_tracks_all.npy')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"ce9865fb97fd16066e062bc375027cb405cf1f51"},"cell_type":"code","source":"# This is like to get the weight of each track\ndef get_track_score(tracks_all, n=4):\n    scores = np.zeros(len(tracks_all))\n    for i, path in enumerate(tracks_all):\n        count = len(path) # number of hits in the path\n        if count > 1:\n            tp = 0 # true positive\n            fp = 0 # false positive\n            for p in path:\n                # Check to see how many hits of varible path are \n                # in the track that originated from\n                # each hit (p) in the varible path\n                tp = tp + np.sum(np.isin(tracks_all[p], path, assume_unique=True))\n                # Optimization?\n                # s = np.sum(np.isin(tracks_all[p], path, assume_unique=True))\n                # fp = fp + np.invert(s)\n                fp = fp + np.sum(np.isin(tracks_all[p], path, assume_unique=True, invert=True))\n            # ??\n            # 1) Give extra weight to the the fp\n            # 2) Subtrack the count is to subtract tp off of\n            # first hit of the tracks that originated from\n            # each hit of vraible path.\n            # 3) Divide count which gives the the average score of track searched\n            # 4) Divide count-1 which gives the average socre of the each prediction\n            # after the hit hit of the path\n            scores[i] = (tp-fp*n-count)/count/(count-1)\n        else: # if path has less than 2 points, the scores is negative inf\n            scores[i] = -np.inf\n    return scores\n\n# A faster scoring function\n# https://www.kaggle.com/cpmpml/a-faster-python-scoring-function\ndef score_event_fast(truth, submission):\n    truth = truth[['hit_id', 'particle_id', 'weight']].merge(submission, how='left', on='hit_id')\n    df = truth.groupby(['track_id', 'particle_id']).hit_id.count().to_frame('count_both').reset_index()\n    truth = truth.merge(df, how='left', on=['track_id', 'particle_id'])\n    \n    df1 = df.groupby(['particle_id']).count_both.sum().to_frame('count_particle').reset_index()\n    truth = truth.merge(df1, how='left', on='particle_id')\n    df1 = df.groupby(['track_id']).count_both.sum().to_frame('count_track').reset_index()\n    truth = truth.merge(df1, how='left', on='track_id')\n    truth.count_both *= 2\n    score = truth[(truth.count_both > truth.count_particle) & (truth.count_both > truth.count_track)].weight.sum()\n    particles = truth[(truth.count_both > truth.count_particle) & (truth.count_both > truth.count_track)].particle_id.unique()\n\n    return score, truth[truth.particle_id.isin(particles)].weight.sum(), 1-truth[truth.track_id>0].weight.sum()\n\ndef evaluate_tracks(tracks, truth):\n    # use truth.hit_id, so the hit_ids is 1-index to len(hits)\n    submission = pd.DataFrame({'hit_id': truth.hit_id, 'track_id': tracks})\n    score = score_event_fast(truth, submission)[0]\n    track_id = tracks.max() # number of tracks predicted\n    print('Score:%.4f\\nHits/Tracks:%2.2f\\nTracks identified:%4d\\nHits not id:%5d\\nScore missed:%.4f\\nTotal weight of unidenified tracks:%.4f' %(\n        score,\n        # number of hits that's identified with a tracks / number of tracks predicted\n        # average hits / track identified\n        np.sum(tracks>0)/track_id,\n        track_id, # number of tracks predicted\n        np.sum(tracks==0), # number of hits that's not assoicated with a track\n        # Truth weights of the tracks we didn't identify\n        1-score-np.sum(truth.weight.values[tracks==0]),\n        # Sum of the weights of unidenified tracks\n        np.sum(truth.weight.values[tracks==0])))\n\ndef extend_path(path, mask, thr, last = False):\n    a = 0\n    # To recreate the a value\n    for p in path[:-1]: # for each hit in the path except the last one\n        c = get_predict2(p)\n        if last == False:\n            mask = (c > thr)*mask\n        mask[p] = 0\n        cand = np.where(c>thr)[0]\n        mask[cand[np.isin(module_id[cand], module_id[path])]]=0\n        a = (c + a)*mask\n\n    while True:\n        c = get_predict2(path[-1])\n        if last == False: # Only add one more hit in the path\n            mask = (c > thr)*mask\n        mask[path[-1]] = 0\n        cand = np.where(c>thr)[0]\n        mask[cand[np.isin(module_id[cand], module_id[path])]]=0\n        a = (c + a)*mask\n            \n        # No more hit above the thr\n        if a.max() < thr * len(path):\n            break\n\n        path.append(a.argmax())\n        if last: break\n    \n    return path","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"d4670b7668b14f70b2c64727ae48ff319e2c4552"},"cell_type":"code","source":"# calculate track's confidence (about 2 mins)\nscores = get_track_score(tracks_all, 8)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"6d5bde61fd95aba5e09536d81360003742857062"},"cell_type":"code","source":"# merge tracks by confidence and get score\n# get all index that would sort from large to small\nidx = np.argsort(scores)[::-1]\ntracks = np.zeros(len(hits))\ntrack_id = 0\n\nfor hit in idx: # for each initial hit in the tracks that has the highest score\n    path = np.array(tracks_all[hit]) # convert from list to np.array\n    # Remove all the hits in the path that's been part of other tracks' hits\n    path = path[np.where(tracks[path] == 0)[0]]\n\n    if len(path) > 3: # If the path has at least 3 hits\n        # prioritize the track number by its weight\n        # and make sure the id starts with 1\n        track_id = track_id + 1\n        # set the hit index to the all the hits in the path\n        tracks[path] = track_id\n\nevaluate_tracks(tracks, truth)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"caa0cbbee67c1b4a6eb749d495f865436f3b25eb"},"cell_type":"code","source":"# multistage\n\n# Get the idx of the scores sorted by big to small,\n# which is the hit index\nidx = np.argsort(scores)[::-1]\n# Remake the tracks\ntracks = np.zeros(len(hits))\ntrack_id = 0\n\nfor hit in idx: # for each hit index sorted by scores\n    # get the path where it's originated from hit\n    path = np.array(tracks_all[hit])\n    # Remove all the hits in the path that's been part of other tracks' hits\n    path = path[np.where(tracks[path]==0)[0]]\n\n    if len(path) > 6:\n        track_id = track_id + 1  \n        tracks[path] = track_id\n\nevaluate_tracks(tracks, truth)\n\n# for each of the track_id that's identified,\n# try to extend the existing path with the un-identified hits\nfor track_id in range(1, int(tracks.max())+1):\n    # Get the path/hit_ids with track_id\n    path = np.where(tracks == track_id)[0]\n    # extend the path with the hits with no track identified\n    path = extend_path(path.tolist(), 1*(tracks==0), 0.6)\n    # Update the racks\n    tracks[path] = track_id\n\nevaluate_tracks(tracks, truth)\n\n# Try to extened the paths that didn't pass the threshold before\nfor hit in idx: # for each hit index sorted by scores\n    # get the path where it's originated from hit\n    path = np.array(tracks_all[hit])\n    # Remove all the hits in the path that's been part of other tracks' hits\n    path = path[np.where(tracks[path]==0)[0]]\n\n    if len(path) > 3:\n        path = extend_path(path.tolist(), 1*(tracks==0), 0.6)\n        track_id = track_id + 1  \n        tracks[path] = track_id\n        \nevaluate_tracks(tracks, truth)\n\n# for each of the track_id that's identified,\n# try to extend the existing path with the un-identified hits\n# But this time with a smaller threshold\nfor track_id in range(1, int(tracks.max())+1):\n    path = np.where(tracks == track_id)[0]\n    path = extend_path(path.tolist(), 1*(tracks==0), 0.5)\n    tracks[path] = track_id\n        \nevaluate_tracks(tracks, truth)\n\n# Try to extend the small path\nfor hit in idx:\n    path = np.array(tracks_all[hit])\n    path = path[np.where(tracks[path]==0)[0]]\n\n    if len(path) > 1:\n        # Try extend the path at least has 2 hits\n        path = extend_path(path.tolist(), 1*(tracks==0), 0.5)\n    if len(path) > 2:\n        # if the extended path has at least 3 hits\n        track_id = track_id + 1\n        tracks[path] = track_id\n        \nevaluate_tracks(tracks, truth)\n\n# for each of the track_id that's identified,\n# try to extend the existing path with the un-identified hits\n# But this time with a smaller threshold\nfor track_id in range(1, int(tracks.max())+1):\n    # Get the path/hit_ids with track_id\n    path = np.where(tracks == track_id)[0]\n    # if the number of hits is even\n    if len(path) % 2 == 0:\n        # try extend them\n        path = extend_path(path.tolist(), 1*(tracks==0), 0.5, True)\n        tracks[path] = track_id\n        \nevaluate_tracks(tracks, truth)","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}