{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import math\nimport pandas as pd\nimport numpy as np\nimport torch\nfrom torch import nn\nimport matplotlib.pyplot as plt \nfrom sklearn.preprocessing import normalize\nfrom sklearn.model_selection import train_test_split","metadata":{"gather":{"logged":1637702026331},"execution":{"iopub.status.busy":"2021-12-13T07:39:04.871808Z","iopub.execute_input":"2021-12-13T07:39:04.872655Z","iopub.status.idle":"2021-12-13T07:39:07.149684Z","shell.execute_reply.started":"2021-12-13T07:39:04.872509Z","shell.execute_reply":"2021-12-13T07:39:07.148746Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tracking_2018_df = pd.read_csv(\"../input/nfl-big-data-bowl-2022/tracking2018.csv\")\ntracking_2019_df = pd.read_csv(\"../input/nfl-big-data-bowl-2022/tracking2019.csv\")\ntracking_2020_df = pd.read_csv(\"../input/nfl-big-data-bowl-2022/tracking2020.csv\")\ntracking_df = pd.concat([tracking_2018_df, tracking_2019_df, tracking_2020_df], axis=0)\n\ntracking_df = tracking_df.drop([\"time\", \"displayName\", \"jerseyNumber\", \"playDirection\"], axis=1)\ntracking_df.head()","metadata":{"gather":{"logged":1637702152545},"execution":{"iopub.status.busy":"2021-12-13T07:40:06.734366Z","iopub.execute_input":"2021-12-13T07:40:06.734689Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tracking_df = tracking_df.dropna(subset=['nflId'])\ntracking_df[\"nflId\"] = tracking_df[\"nflId\"].astype(int)\ntracking_single_df = tracking_df.loc[tracking_df[\"event\"] == \"kick_received\"]\ntracking_single_df = tracking_single_df.drop([\"event\"], axis=1)","metadata":{"collapsed":false,"gather":{"logged":1637702156567},"jupyter":{"outputs_hidden":false,"source_hidden":false},"nteract":{"transient":{"deleting":false}},"execution":{"iopub.status.busy":"2021-12-13T07:39:07.285675Z","iopub.status.idle":"2021-12-13T07:39:07.286547Z","shell.execute_reply.started":"2021-12-13T07:39:07.286229Z","shell.execute_reply":"2021-12-13T07:39:07.28626Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"WIDTH = 120\nHEIGHT = 53.3\n\ndef reflect_x(x):\n    return (360 - x)\n\ndef reflect_y(y):\n    return (540 - y) % 360\n\ndef get_train_data(train_kickoff_df):\n    valid_indices = get_valid_indices(train_kickoff_df)\n    \n    X_train = np.empty((len(valid_indices) * 2, 11, 10, 10))\n    Y_train = np.zeros((len(valid_indices) * 2, 209))\n    indices = train_kickoff_df.index\n    for i in range(len(valid_indices)):\n        index = valid_indices[i]\n        play_df = tracking_single_df.loc[(tracking_single_df[\"gameId\"] == indices[index][0]) & \n                                    (tracking_single_df[\"playId\"] == indices[index][1])]\n\n        returner = int(train_kickoff_df.iloc[index][\"returnerId\"].split(\";\")[0])\n        offense = play_df.loc[play_df[\"nflId\"] == returner][\"team\"].values[0]\n        offense_df = play_df.loc[play_df[\"team\"] == offense].reset_index()\n        returner_df = offense_df.loc[offense_df[\"nflId\"] == returner]\n        offense_df = offense_df.drop(offense_df[offense_df['nflId'] == returner].index).reset_index()\n        defense_df = play_df.loc[play_df[\"team\"] != offense].reset_index()\n        r_row = returner_df.iloc[0]\n\n        normal_data = np.empty((11, 10, 10))\n        augmented_y_data = np.empty((11, 10, 10))\n        for j, d_row in defense_df.iterrows():\n            normal = np.empty((10, 10))\n            augmented_y = np.empty((10, 10))\n            for k, o_row in offense_df.iterrows():\n                # Defense Sx,Sy\n                normal[0][k] = math.sin(d_row[\"dir\"] * math.pi/180) * d_row[\"s\"]\n                normal[1][k] = math.cos(d_row[\"dir\"] * math.pi/180) * d_row[\"s\"]\n                augmented_y[0][k] = math.sin(d_row[\"dir\"] * math.pi/180) * d_row[\"s\"]\n                augmented_y[1][k] = math.cos(reflect_y(d_row[\"dir\"]) * math.pi/180) * d_row[\"s\"]\n\n                # Defense X,Y - Returner X,Y\n                normal[2][k] = d_row[\"x\"] - r_row[\"x\"]\n                normal[3][k] = d_row[\"y\"] - r_row[\"y\"]\n                augmented_y[2][k] = d_row[\"x\"] - r_row[\"x\"]\n                augmented_y[3][k] = (HEIGHT - d_row[\"y\"]) - (HEIGHT - r_row[\"y\"])\n\n                # Defense Sx,Sy - Returner Sx,Sy\n                normal[4][k] = math.sin(d_row[\"dir\"] * math.pi/180) * d_row[\"s\"] - math.sin(r_row[\"dir\"] * math.pi/180) * r_row[\"s\"]\n                normal[5][k] = math.cos(d_row[\"dir\"] * math.pi/180) * d_row[\"s\"] - math.cos(r_row[\"dir\"] * math.pi/180) * r_row[\"s\"]\n                augmented_y[4][k] = math.sin(d_row[\"dir\"] * math.pi/180) * d_row[\"s\"] - math.sin(r_row[\"dir\"] * math.pi/180) * r_row[\"s\"]\n                augmented_y[5][k] = math.cos(reflect_y(d_row[\"dir\"]) * math.pi/180) * d_row[\"s\"] - math.cos(reflect_y(r_row[\"dir\"]) * math.pi/180) * r_row[\"s\"]\n                \n                # Offense X,Y - Defense X,Y\n                normal[6][k] = o_row[\"x\"] - d_row[\"x\"]\n                normal[7][k] = o_row[\"y\"] - d_row[\"y\"]\n                augmented_y[6][k] = o_row[\"x\"] - d_row[\"x\"]\n                augmented_y[7][k] = (HEIGHT - o_row[\"y\"]) - (HEIGHT - d_row[\"y\"])\n\n                # Offense Sx,Sy - Defense Sx,Sy\n                normal[8][k] = math.sin(o_row[\"dir\"] * math.pi/180) * o_row[\"s\"] - math.sin(d_row[\"dir\"] * math.pi/180) * d_row[\"s\"]\n                normal[9][k] = math.cos(o_row[\"dir\"] * math.pi/180) * o_row[\"s\"] - math.cos(d_row[\"dir\"] * math.pi/180) * d_row[\"s\"]\n                augmented_y[8][k] = math.sin(o_row[\"dir\"] * math.pi/180) * o_row[\"s\"] - math.sin(d_row[\"dir\"] * math.pi/180) * d_row[\"s\"]\n                augmented_y[9][k] = math.cos(reflect_y(o_row[\"dir\"]) * math.pi/180) * o_row[\"s\"] - math.cos(reflect_y(d_row[\"dir\"]) * math.pi/180) * d_row[\"s\"]\n            \n            normal_data[j] = normal\n            augmented_y_data[j] = augmented_y\n\n        X_train[i] = normal_data\n        X_train[i + 1] = augmented_y_data\n        yards = int(train_kickoff_df.iloc[index][\"kickReturnYardage\"])\n        Y_train[i: i + 2, yards + 99:] = np.ones((1, 110 - yards))\n\n    return torch.DoubleTensor(X_train), torch.DoubleTensor(Y_train)\n\ndef get_kickoff_plays():\n    kickoff_df = all_plays_df.loc[(all_plays_df['specialTeamsPlayType'] == \"Kickoff\") & (all_plays_df[\"specialTeamsResult\"] == \"Return\")]\n    kickoff_df = kickoff_df.set_index([\"gameId\", \"playId\"])\n    kickoff_df = kickoff_df.drop([\"playDescription\", \"down\", \"yardsToGo\", \"kickBlockerId\",\n                              \"yardlineNumber\", \"yardlineSide\", \"gameClock\", \"penaltyCodes\", \"penaltyJerseyNumbers\",\n                              \"penaltyYards\", \"specialTeamsPlayType\", \"specialTeamsResult\", \"preSnapHomeScore\",\n                              \"preSnapVisitorScore\", \"passResult\", \"absoluteYardlineNumber\", \"playResult\"], axis=1)\n    \n    pff_indexed_df = pff_df.set_index([\"gameId\", \"playId\"])\n    kickoff_df = pd.concat([kickoff_df, pff_indexed_df], axis=1, join='inner')\n    kickoff_df = kickoff_df.drop([\"snapDetail\", \"snapTime\", \"operationTime\", \"gunners\", \"puntRushers\", \"vises\", \n                                 \"kickContactType\", \"missedTackler\", \"assistTackler\", \"tackler\",\n                                  \"returnDirectionActual\", \"specialTeamsSafeties\"], axis=1)\n\n    kickoff_df = kickoff_df.dropna(subset=['kickReturnYardage', 'returnerId'])\n    return kickoff_df\n\ndef get_valid_indices(df):\n    indices = df.index\n    valid_indices = []\n    for i in range(len(indices)):\n        play_df = tracking_single_df.loc[(tracking_single_df[\"gameId\"] == indices[i][0]) & \n                                    (tracking_single_df[\"playId\"] == indices[i][1])]\n        if not play_df.empty:\n            valid_indices.append(i)\n    return valid_indices\n\nall_plays_df = pd.read_csv(\"plays.csv\")\npff_df = pd.read_csv(\"PFFScoutingData.csv\")\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nkickoff_df = get_kickoff_plays()\nX, Y = get_train_data(kickoff_df)","metadata":{"gather":{"logged":1637703089677}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train, X_valid, Y_train, Y_valid = train_test_split(X, Y, test_size=0.2)","metadata":{"collapsed":false,"gather":{"logged":1637702708430},"jupyter":{"outputs_hidden":false,"source_hidden":false},"nteract":{"transient":{"deleting":false}}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Lambda(nn.Module):\n    def __init__(self, lambd):\n        super(Lambda, self).__init__()\n        self.lambd = lambd\n    def forward(self, x):\n        return (self.lambd)(x)\n\nclass Add(nn.Module):\n    def __init__(self):\n        super(Add, self).__init__()\n    def forward(self, x, y):\n        return x + y\n    \nclass ERYNet(nn.Module):\n    def __init__(self):\n        super(ERYNet, self).__init__()\n                \n        self.block1 = nn.Sequential(\n            nn.Conv2d(11, 128, kernel_size=(1,1), stride=(1,1)),\n            nn.ReLU(),\n            nn.Conv2d(128, 160, kernel_size=(1,1),  stride=(1,1)),\n            nn.ReLU(),\n            nn.Conv2d(160, 128, kernel_size=(1,1),  stride=(1,1)),\n            nn.ReLU()\n        )\n        \n        self.maxpool2d = nn.MaxPool2d(kernel_size=(1, 10))\n        self.lambda1 = Lambda(lambda x: x * 0.3)\n        self.avgpool2d = nn.AvgPool2d(kernel_size=(1, 10))\n        self.lambda2 = Lambda(lambda x: x * 0.7)\n        self.add = Add()\n        self.norm2d = nn.BatchNorm2d(128)\n        self.squeeze1 = Lambda(lambda x : torch.squeeze(x,3))\n            \n        self.block2 = nn.Sequential(\n            nn.Conv1d(128, 160, kernel_size=1, stride=1),\n            nn.ReLU(),\n            nn.BatchNorm1d(160),\n            nn.Conv1d(160, 96, kernel_size=1,  stride=1),\n            nn.ReLU(),\n            nn.BatchNorm1d(96),\n            nn.Conv1d(96, 96, kernel_size=1,  stride=1),\n            nn.ReLU(),\n            nn.BatchNorm1d(96)\n        )\n        \n        self.maxpool1d = nn.MaxPool1d(kernel_size=10)\n        self.avgpool1d = nn.AvgPool1d(kernel_size=10)\n        self.squeeze2 = Lambda(lambda x : torch.squeeze(x,2))\n        \n        self.block3 = nn.Sequential(\n            nn.Linear(96, 96),\n            nn.ReLU(),\n            nn.BatchNorm1d(96),\n            nn.Linear(96, 256),\n            nn.ReLU(),\n            nn.BatchNorm1d(256),\n            nn.Dropout(0.3),\n            nn.Linear(256, 209),\n            nn.Softmax()\n        )\n    \n    def forward(self, x):\n        x = self.block1(x)\n        xmax = self.maxpool2d(x)\n        xmax = self.lambda1(xmax)\n        xavg = self.avgpool2d(x)\n        xavg = self.lambda2(xavg)\n        x = self.add(xmax, xavg)\n        x = self.norm2d(x)\n        x = self.squeeze1(x)\n\n        x = self.block2(x)\n        xmax = self.maxpool1d(x)\n        xmax = self.lambda1(xmax)\n        xavg = self.avgpool1d(x)\n        xavg = self.lambda2(xavg)\n        x = self.add(xmax, xavg)\n        x = self.squeeze2(x)\n\n        x = self.block3(x)\n        return x\n    \ndef crps(y_true, y_pred):\n    return torch.mean(torch.square(y_true - torch.cumsum(y_pred, axis=1)), dim=1)","metadata":{"collapsed":false,"gather":{"logged":1637702712229},"jupyter":{"outputs_hidden":false,"source_hidden":false},"nteract":{"transient":{"deleting":false}}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def train(model, num_epochs, batch_size, X_train, Y_train, X_valid, Y_valid):\n    optimizer = torch.optim.Adam(model.parameters(), lr=0.0005)\n    scheduler = torch.optim.lr_scheduler.OneCycleLR(optimizer, 0.001, total_steps=num_epochs)\n\n    epochs = []\n    losses = []\n    errors = []\n    r2_scores = []\n    valid_losses = []\n    valid_errors = []\n    valid_r2_scores = []\n    num_samples = X_train.size()[0]\n    for epoch in range(num_epochs):\n        model.train()\n        shuffled_indices = torch.randperm(num_samples)\n        y_true = torch.empty((num_samples, 209)).to(device)\n        y_pred = torch.empty((num_samples, 209)).to(device)\n        for i in range(0, num_samples, batch_size):\n            optimizer.zero_grad()\n            indices = shuffled_indices[i: i + batch_size]\n            batch_x, batch_y = X_train[indices], Y_train[indices]\n            pred = model.forward(batch_x)\n            loss = sum(crps(batch_y, pred))\n            loss.backward()\n            optimizer.step()\n\n            y_true[i: i + batch_size] = batch_y\n            y_pred[i: i + batch_size] = pred\n        \n        epochs.append(epoch + 1)\n        losses.append(torch.mean(crps(y_true, y_pred)).item())\n        expected_yards = calculate_expected_yards(y_pred)            \n        actual_yards = (y_true <= 0).sum(dim=1) - 99.0\n        errors.append(torch.mean(torch.abs(actual_yards - expected_yards)).item())\n        r2_scores.append(r2_score(actual_yards, expected_yards).item())\n        valid_loss, valid_error, valid_r2_score = valid(model, X_valid, Y_valid)\n        valid_losses.append(valid_loss)\n        valid_errors.append(valid_error)\n        valid_r2_scores.append(valid_r2_score)\n        scheduler.step()\n\n    show_results(epochs, losses, errors, r2_scores)\n    show_results(epochs, valid_losses, valid_errors, valid_r2_scores, valid=True)\n\ndef r2_score(y_true, y_pred):\n    target_mean = torch.mean(y_true)\n    ss_tot = torch.sum((y_true - target_mean) ** 2)\n    ss_res = torch.sum((y_true - y_pred) ** 2)\n    r2 = 1 - ss_res / ss_tot\n    return r2\n\ndef calculate_expected_yards(prediction):\n    expected_yards = torch.DoubleTensor([i for i in range(-99, 110)]).to(device)\n    return (expected_yards * prediction).sum(dim=1)\n\ndef valid(model, X_valid, Y_valid):\n    model.eval()\n    with torch.no_grad():            \n        prediction = model(X_valid)\n        loss = torch.mean(crps(Y_valid, prediction)).item()\n        expected_yards = calculate_expected_yards(prediction)            \n        actual_yards = (Y_valid <= 0).sum(dim=1) - 99.0\n        error = torch.mean(torch.abs(actual_yards - expected_yards)).item()\n        score = r2_score(actual_yards, expected_yards).item()\n        return loss, error, score\n\ndef show_results(epochs, losses, errors, r2_scores, valid=False):\n    figure, axis = plt.subplots(3)\n    figure.tight_layout(pad=1.0)\n    axis[0].plot(epochs, losses)\n    axis[0].set_title(\"Loss\")\n    axis[1].plot(epochs, errors)\n    axis[1].set_title(\"Error\")\n    axis[2].plot(epochs, r2_scores)\n    axis[2].set_title(\"R2 Score\")\n    plt.show()\n\n    if valid:\n        print(\"Validation Loss:\", losses[-1])\n        print(\"Validation Error:\", errors[-1])\n        print(\"Validation R2 Score:\", r2_scores[-1])\n    else:\n        print(\"Train Loss:\", losses[-1])\n        print(\"Train Error:\", errors[-1])\n        print(\"Train R2 Score:\", r2_scores[-1])\n\nmodel = ERYNet().to(device).double()\nnum_epochs = 500\nbatch_size = 64\ntrain(model, num_epochs, batch_size, X_train, Y_train, X_valid, Y_valid)","metadata":{"gather":{"logged":1637702747247}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def predict(samples):\n    model.eval()\n    with torch.no_grad():            \n        prediction = model(samples)\n        expected_yards = calculate_expected_yards(prediction)\n        return expected_yards","metadata":{"collapsed":false,"gather":{"logged":1637647747065},"jupyter":{"outputs_hidden":false,"source_hidden":false},"nteract":{"transient":{"deleting":false}}},"execution_count":null,"outputs":[]}]}