{"cells":[{"metadata":{},"cell_type":"markdown","source":"<img src=\"https://i.imgur.com/oiG2jZl.png\">\n<center><h1>🧭Indoor Location and Navigation🧭</h1></center>\n\n# 1. Introduction\n> 📌**Goal**: リアルタイムセンサーによるスマートフォン📱の室内位置の予測🎯.\n\n> また、今回のコンペで利用できる**GitHub Repository**を使って、カスタム関数を**ノートにコピーペーストせずに**呼び出す方法も学びます。\n\n### Libraries📚"},{"metadata":{"trusted":true},"cell_type":"code","source":"# CPU libraries\nimport os\nimport json\nimport glob # パターンに一致するすべてのパス名を見つける\nimport cv2  # OpenCV(画像の読み書き・リサイズ・反転などの加工)\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns # データ可視化(中でmatplotlibを使ってる)\nimport matplotlib     # データ可視化\nimport matplotlib.pyplot as plt\nimport plotly.graph_objs as go\n\nfrom PIL import Image, ImageOps # 画像処理(参考：https://note.nkmk.me/python-pillow-basic/)\nfrom skimage import io          # 画像処理(参考：https://qiita.com/supersaiakujin/items/fc54116df9ca6958a68d)\nfrom skimage.color import rgba2rgb, rgb2xyz\nfrom tqdm import tqdm           # プログレスバー\nfrom dataclasses import dataclass  # __init__() のような特殊なクラスを生成\nfrom math import floor, ceil\n\nmycolors = [\"#797D62\", \"#9B9B7A\", \"#D9AE94\", \"#FFCB69\", \"#D08C60\", \"#997B66\"]","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"cell_type":"code","source":"def show_values_on_bars(axs, h_v=\"v\", space=0.4):\n    '''seabornの棒グラフの最後に値をプロットします。\n    axs: プロットの軸\n    h_v: バープロットが垂直／水平かどうか'''\n    \n    def _show_on_single_plot(ax):\n        if h_v == \"v\":  # 垂直の場合\n            for p in ax.patches:\n                _x = p.get_x() + p.get_width() / 2\n                _y = p.get_y() + p.get_height()\n                value = int(p.get_height())\n                ax.text(_x, _y, format(value, ','), ha=\"center\") \n        elif h_v == \"h\": # 水平の場合\n            for p in ax.patches:\n                _x = p.get_x() + p.get_width() + float(space)\n                _y = p.get_y() + p.get_height()\n                value = int(p.get_width())\n                ax.text(_x, _y, format(value, ','), ha=\"left\")\n\n    if isinstance(axs, np.ndarray):\n        for idx, ax in np.ndenumerate(axs):\n            _show_on_single_plot(ax)\n    else:\n        _show_on_single_plot(axs)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### W&Bのセットアップ\n* アカウントの作成 https://wandb.ai (it's free)\n* menu -> Add-ons -> Secrets -> Add a new Secret -> label=wandb-key value=wandb API key\n* Install `wandb`\n* プロジェクトの「パーソナル・キー」を入力してください。 ( 私の場合は機密なので、秘密にしておきます。 :) )"},{"metadata":{"trusted":true},"cell_type":"code","source":"import wandb\nos.environ[\"WANDB_SILENT\"] = \"true\"\n\nfrom kaggle_secrets import UserSecretsClient\nuser_secrets = UserSecretsClient()\npersonal_key_for_api = user_secrets.get_secret(\"wandb-key\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# wandbにログイン\n! wandb login $personal_key_for_api","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 新規プロジェクトの初期化\n### project - overajjプロジェクトの名前（GitHubのリポジトリの名前と同じ)\n### name/experiment - runの名前（1つのプロジェクトで複数のrunを使用)\nrun = wandb.init(project=\"indoor-location-kaggle\", name=\"data-understanding\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> We're all set!\n\n# 2. Data Understanding\n\n* `train` - *site*と*floor*で構成された学習用パスファイル。各パスファイルには、1つのフロアの1つのパスのデータが含まれる。\n* `test` - テスト用のパスファイルで、*site*と*floor*で構成されています。各パスファイルには、1つのフロアの1つのパスのデータが含まれていますが、**waypoint (x, y)データ**は含まれていません。\n* `metadata` - フロアメタデータフォルダ（サイトとフロアごとに整理されており、フロアごとに以下を含む:\n    * `floor_image.png`\n    * `floor_info.json`\n    * `geojson_map.json`\n    \n<img src=\"https://i.imgur.com/EE98923.png\" width=500>\n\n> 📌**Goal**: このコンペの課題は、与えられたsite-pathファイルに対して、`sample_submission.csv`ファイルで与えられた**timestamp**で、**predict the floor**と**waypoint locations**を予測することです。\n\n> ❗**Note on data quality**❗: 学習用ファイルの中には、最後の改行文字がない行があり、次の行に進んでしまうことがあります。この問題をどのように処理するかは、ユーザー次第です。この問題は、テストデータでは見られません。"},{"metadata":{"trusted":true},"cell_type":"code","source":"# ファイル数を確認\ntrain_paths = glob.glob('../input/indoor-location-navigation/train/*/*/*')\ntest_paths = glob.glob('../input/indoor-location-navigation/test/*')\nsites = glob.glob('../input/indoor-location-navigation/metadata/*')\n\nprint(\"No. Files in Train: {:,}\".format(len(train_paths)), \"\\n\" +\n      \"No. Files in Test: {:,}\".format(len(test_paths)), \"\\n\" +\n      \"Total Sites (metadata): {:,}\".format(len(sites)))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 実験的にW&Bの\"data-understanding\"にログを残す。W&Bのサイト参照\n# 参考：https://wandb.ai/honda/indoor-location-kaggle/runs/36e88gwf?workspace=user-honda\nwandb.log({'No. Files in Train': len(train_paths), \n           'No. Files in Test:' : len(test_paths),\n           'Total Sites (metadata)' : len(sites)})","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## データ読み込み\n\n> ❗**Note**: 提示されたデータは`.txt`形式で、多くの情報が含まれています。たった1つの`.txt`ファイル（つまり1つのパス）に、126k行以上の情報がありました。1つのファイルごとに情報は異なるので、すべてを構造化しようとすると面倒なことになります。"},{"metadata":{"trusted":true},"cell_type":"code","source":"# 1ファイルをのぞいてみる\nbase = '../input/indoor-location-navigation'\npath = f'{base}/train/5cd56b5ae2acfd2d33b58549/5F/5d06134c4a19c000086c4324.txt'\n\nwith open(path) as p:\n    lines = p.readlines()\n\nprint(\"No. Lines in 1 example: {:,}\". format(len(lines)), \"\\n\" +\n      \"Example (5 lines): \", lines[0:5])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 2. KaggleでのGitHub repoの使い方?🔗\n\n> 📌**Goal**: このコンペには、[GitHub repo](https://github.com/location-competition/indoor-location-competition-20)が用意されています。io_f`ファイルの`read_data_file`関数を使って情報を読み込めばいいのです（こちら側での追加の苦労はありません＋コピーペーストでコードが乱雑になることもありません :)）\n\n#### *🙏🏻この素晴らしいトリックを教えてくれた[Laura](https://www.kaggle.com/allunia)に感謝します。🙏🏻*\n\n**Steps**:\n* 🦶🏻 - このリンクからrepoをダウンロードする(https://github.com/location-competition/indoor-location-competition-20)\n* 🦶🏻 - パッケージをKaggle環境にコピーします。 (`!cp -r path/* ./`)\n* 🦶🏻 - importして好きなように使う"},{"metadata":{"trusted":true},"cell_type":"code","source":"!cp -r ../input/indoor-locationnavigation-2021/indoor-location-competition-20-master/indoor-location-competition-20-master/* ./","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# リポジトリからカスタム関数をインポート\nfrom io_f import read_data_file\n\n# 1つのランダムなファイルで読む\nsample_file = read_data_file(path)\n\n# 各変数の情報にアクセスできます:\nprint(\"~~~ Example ~~~\")\n# acce:TYPE_ACCELEROMETER:加速度計\n# acacce_uncalice: TYPE_ACCELEROMETER_UNCALIBRATED:加速度計(補正前？)\n# ahrs:TYPE_ROTATION_VECTOR：回転ベクトル\n# gyro:TYPE_GYROSCOPE：ジャイロスコープ(物体の角度（姿勢）や角速度あるいは角加速度を検出する計測器ないし装置)\n# ibeacn:TYPE_BEACON：ビーコン(Bluetooth low energyのブロードキャスト通信を利用したiOSの近接通知機能)\n# magn:TYPE_MAGNETIC_FIELD：磁力計\n# magn_uncali:TYPE_MAGNETIC_FIELD_UNCALIBRATED：磁力計（補正前？）\n# waypoint:TYPE_WAYPOINT：歩いた場所\n# wifi:TYPE_WIFI： wifi\nprint(\"acce: {}\".format(sample_file.acce.shape), \"\\n\" +\n      \"acacce_uncalice: {}\".format(sample_file.acce_uncali.shape), \"\\n\" +\n      \"ahrs: {}\".format(sample_file.ahrs.shape), \"\\n\" +\n      \"gyro: {}\".format(sample_file.gyro.shape), \"\\n\" +\n      \"gyro_uncali: {}\".format(sample_file.gyro_uncali.shape), \"\\n\" +\n      \"ibeacon: {}\".format(sample_file.ibeacon.shape), \"\\n\" +\n      \"magn: {}\".format(sample_file.magn.shape), \"\\n\" +\n      \"magn_uncali: {}\".format(sample_file.magn_uncali.shape), \"\\n\" +\n      \"waypoint: {}\".format(sample_file.waypoint.shape), \"\\n\" +\n      \"wifi: {}\".format(sample_file.wifi.shape))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"よし、今から楽しもう！\n\n## Sites🏢\n\n> **site**はエンコードされており、データが抽出された**建物**を表しています。"},{"metadata":{"trusted":true},"cell_type":"code","source":"def show_site_png(site):\n    '''この機能は、メタデータに含まれる.png画像を可視化して出力します。\n    sites: 1つのサイト（または建物）に対応するコード'''\n    \n    base = '../input/indoor-location-navigation'\n    site_path = f\"{base}/metadata/{site}/*/floor_image.png\"\n    floor_paths = glob.glob(site_path)\n    n = len(floor_paths)\n\n    # カスタムの行と列の数を作成 1行につき3つまで表示できるようにする。\n    ncols = [ceil(n / 3) if n > 4 else 4][0]\n    nrows = [ceil(n / ncols) if n > 4 else 1][0]\n\n    plt.figure(figsize=(16, 10))\n    plt.suptitle(f\"Site no. '{site}'\", fontsize=18)\n\n    # 各階のプロットイメージ\n    for k, floor in enumerate(floor_paths):\n        plt.subplot(nrows, ncols, k+1)\n\n        image = Image.open(floor)\n        image = ImageOps.expand(image, border=15, fill=mycolors[5])\n\n        plt.imshow(image)\n        plt.axis(\"off\")\n        title = floor.split(\"/\")[5]\n        plt.title(title, fontsize=15)\n        \n        # W&Bの\"data-understanding\"に 実験的にログ出力する\n        wandb.log({\"Site Floors Example\": plt})","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 1つの例を見てみよう\n# site = '5cd56b64e2acfd2d33b59246'\nshow_site_png(site='5cd56b64e2acfd2d33b592b3')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## いくつのフロアがある? 🛩\n\n> ❗**Note**: この変数は、予測が必要なターゲット変数の1つであるため、非常に重要です。"},{"metadata":{"trusted":true},"cell_type":"code","source":"all_floors = glob.glob(\"../input/indoor-location-navigation/metadata/*/*\")\nfloor_no = []\n\n# フロア番号のみを抽出\nfor floor in all_floors:\n    no = floor.split(\"/\")[5]\n    floor_no.append(no)\n    \nfloor_no = pd.DataFrame(floor_no, columns=[\"No\"])\nfloor_no = floor_no[\"No\"].value_counts().reset_index()\nfloor_no = floor_no.sort_values(\"No\", ascending=False)\n\n# ~~~~\n# フロアごとに数を表示\n# ~~~~\nplt.figure(figsize=(16, 12))\nax = sns.barplot(data=floor_no, x=\"No\", y=\"index\", palette=\"Greens_r\",\n                 saturation=0.4)\nshow_values_on_bars(ax, h_v=\"h\", space=0.4)\nax.set_title(\"Frequency of Floors\", size = 26, color = mycolors[0], weight='bold')\nax.set_xlabel(\"\")\nax.set_ylabel(\"Floor No.\", size = 18, color = mycolors[0], weight='bold')\nplt.xticks([])\nplt.yticks(fontsize=11)\nsns.despine(left=True, bottom=True);","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# W&Bの\"data-understanding\"に実験的にログ出力する。\n### 現在、seabornのダイレクトログはサポートされていません。\n### カスタム棒グラフを作成します。\ndata = [[index, no] for (index, no) in zip(floor_no[\"index\"], floor_no[\"No\"])]\ntable = wandb.Table(data=data, columns=[\"index\", \"no\"])\nwandb.log({\"Frequency of Floors\" : wandb.plot.bar(table, \"index\", \"no\", title=\"Frequency of Floors\")})","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> W&Bのダッシュボードではこのように表示されます:\n<img src=\"https://i.imgur.com/IpxLc7t.png\" width=700>\n\n## Waypoint\n\n> ❗**Note**: この変数は非常に重要で、我々が予測しなければならないもう一つのターゲット変数です。\n\n> 📌**Waypoint**: 下の図は、1人の人間が1つのサイトの5階を通過する際の*経路*、つまり軌跡を示しています。かなり歩いて往復しています。また、この軌跡の**開始点**と**終了点**を見ることができます。"},{"metadata":{"trusted":true},"cell_type":"code","source":"# GitHub functions\nfrom visualize_f import visualize_trajectory, visualize_heatmap\n\npath = f'{base}/train/5cd56b5ae2acfd2d33b58549/5F/5d06134c4a19c000086c4324.txt'\n\n# サンプルの読み込み\nexample = read_data_file(path)\n\n# ~~~~~~~~~\n\n# タイムスタンプとx,y座標の値を返す\ntrajectory = example.waypoint\n# タイムスタンプの削除（必要なのは座標のみ)\ntrajectory = trajectory[:, 1:3]\n\n# 例題に沿ったfloor_planを用意する\n# ../input/indoor-location-navigation/metadata/5a0546857ecc773753327266/B1/xxxxx.xxx というようなディレクトリ構成\nsite = path.split(\"/\")[4]\nfloorNo = path.split(\"/\")[5]\nfloor_plan_filename = f'{base}/metadata/{site}/{floorNo}/floor_image.png'\n\n# width_meter & height_meterを準備する\n### (taken from the .json file)\njson_plan_filename = f'{base}/metadata/{site}/{floorNo}/floor_info.json'\nwith open(json_plan_filename) as json_file:\n    json_data = json.load(json_file)\n    \n# {\"map_info\": {\"height\": 204.53342955266643, \"width\": 270.34143433711995}} こんなデータ\nwidth_meter = json_data[\"map_info\"][\"width\"]\nheight_meter = json_data[\"map_info\"][\"height\"]\n\n# Title\ntitle = \"Example of Waypoint\"\n\n# ~~~~~~~~~\n\n# Finally, let's plot\nvisualize_trajectory(trajectory = trajectory,\n                     floor_plan_filename = floor_plan_filename,\n                     width_meter = width_meter,\n                     height_meter = height_meter,\n                     title = title,\n                     g_size=755,\n                     point_color='#76C1A0',\n                     start_color='#007B51',\n                     end_color='#9B0000')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 磁力の強さ 🧲\n\n> 📌**Magnetic Strength**: 建物内のどの地点も、**固有の磁力**を受けています。床や壁、あるいは部屋の周囲にある物体は、3次元空間と**磁気の大きさ**の4次元地図を作成します。この空間内の任意の点における磁気の大きさは、その点におけるx、y、zの磁気ベクトルを読み取ることで測定できる。\n\n> ❗**Note**: 携帯電話は、ユーザーが移動すると（携帯電話が回転すると）、磁界の変動を検出します。下の例では、waypointの最初の方では磁場が強く、床の左側では磁場が弱くなっています。\n\n#### 📐mu tesla (1×104 G) - 磁気誘導の派生単位📐"},{"metadata":{"trusted":true},"cell_type":"code","source":"# GitHub functions\nfrom main import calibrate_magnetic_wifi_ibeacon_to_position\nfrom main import extract_magnetic_strength\n\n# 磁力の値を取得する。\nmwi_datas = calibrate_magnetic_wifi_ibeacon_to_position([path])\nmagnetic_strength = extract_magnetic_strength(mwi_datas)\n\nheat_positions = np.array(list(magnetic_strength.keys()))\nheat_values = np.array(list(magnetic_strength.values()))\n\n# ヒートマップの可視化\nvisualize_heatmap(heat_positions, \n                  heat_values, \n                  floor_plan_filename,\n                  width_meter, \n                  height_meter, \n                  colorbar_title='mu tesla', \n                  title='Magnetic Strength',\n                  g_size=755,\n                  colorscale='temps')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## WiFi 📶\n\n> 📌**WiFi Access Points**: 敷地内のフロアには、WiFiアクセスポイントが非常に多く存在します。そのため、エリアによって信号やその強さが大きく異なります。\n> ❗**Note**: 下の例では、ルート上にこのようなアクセスポイントがたくさんあることがわかります。"},{"metadata":{"trusted":true},"cell_type":"code","source":"# GitHub Libraries\nfrom main import extract_wifi_rssi, extract_wifi_count\n\n# Get WiFi data\nwifi_rssi = extract_wifi_rssi(mwi_datas)\nprint(f'This floor has {len(wifi_rssi.keys())} wifi aps (access points).')\n\nwifi_counts = extract_wifi_count(mwi_datas)\nheat_positions = np.array(list(wifi_counts.keys()))\nheat_values = np.array(list(wifi_counts.values()))\n# 無線LANが検出されない位置をフィルタリングする\nmask = heat_values != 0\nheat_positions = heat_positions[mask]\nheat_values = heat_values[mask]\n\n# The heatmap\nvisualize_heatmap(heat_positions, \n                  heat_values, \n                  floor_plan_filename, \n                  width_meter, \n                  height_meter, \n                  colorbar_title='count', \n                  title=f'WiFi Count',\n                  g_size=755,\n                  colorscale='temps')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## IBeacon (Bluetooth) 🔵🦷\n\n> 📌**IBeacon**: ビーコンは、モバイルアプリケーションと連動して、ユーザーがビーコンから一定の距離に近づいたときにプッシュ通知を行うなど、ルールに基づいて特定のメッセージやアクションを起こします。\n\n> ❗**Note**: 下の例の経路には多くのビーコンが設置されていますが、iBeaconが全く設置されていない部分も多くあります。この部分の精度は低くなる可能性があります。\n\n#### 📐dBm (decibel milliwatts) - 絶対的なパワーの指標として使用され、数値が0に近いほど、信号強度が優れていることを意味する📐"},{"metadata":{"trusted":true},"cell_type":"code","source":"# The GitHub function\nfrom main import extract_ibeacon_rssi\n\n# Getting the iBeacon data\nibeacon_rssi = extract_ibeacon_rssi(mwi_datas)\nprint(f'This floor has {len(ibeacon_rssi.keys())} ibeacons.')\nibeacon_ummids = list(ibeacon_rssi.keys())\ntarget_ibeacon = ibeacon_ummids[0]\nheat_positions = np.array(list(ibeacon_rssi[target_ibeacon].keys()))\nheat_values = np.array(list(ibeacon_rssi[target_ibeacon].values()))[:, 0]\n\n# The heatmap\nvisualize_heatmap(heat_positions, \n                  heat_values, \n                  floor_plan_filename, \n                  width_meter, \n                  height_meter, \n                  colorbar_title='dBm', \n                  title='iBeacon RSSE',\n                  g_size=755,\n                  colorscale='temps')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# ~ END of EXPERIMENT ~\nwandb.finish()\n# ~~~~~~~~~~~~~~~~~~~~~","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 3. Understanding the Competition Metric\n\n> 📌**Note**: 多くのノートブックでは、同じ `comp_metric` 関数を使用して *Evaluation metric* を計算していますが（以下のように）、この変数とその意味を理解するのに少し時間がかかりました。そのため、（私のように）コンペの説明書に書かれている情報を早々に読み飛ばしてしまった人のために、少しだけ情報を共有することにしました。\n\n`def comp_metric(xhat, yhat, fhat, x, y, f):\n    intermediate = np.sqrt(np.power(xhat-x, 2) + np.power(yhat-y, 2)) + 15 * np.abs(fhat-f)\n    return intermediate.sum()/xhat.shape[0]`\n    \n### 説明\n\n上記の式は、以下の数学関数のPython版に過ぎません（競技会で提供されたものです）:\n<img src=\"https://i.imgur.com/1Z0s7Cc.png\" width=500>"},{"metadata":{"trusted":true},"cell_type":"code","source":"from numpy import sqrt, power\nfrom numpy import abs as absolute","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def mean_position_error(x_pred, y_pred, f_pred, x_true, y_true, f_true, p=15):\n    '''Mean Position Errorを評価するカスタム関数。\n    x: waypointの位置のx座標; dtype list()\n    y: waypointの位置のy座標; dtype list()\n    f: 正確な階数または建物; dtype list()\n    p: フロアペナルティ'''\n    \n    N = len(x_true)\n    #1\n    formula = sqrt( power(x_pred - x_true, 2) + power(y_pred - y_true, 2) )\n    #2\n    formula = formula + p * absolute(f_pred - f_true)\n    #3\n    formula = formula.sum() / N\n    \n    return formula","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### RAPIDS function\n- RAPIDS：データサイエンスのための様々な処理を一貫してGPUで行うためのプラットフォームです。\n- 参考：https://qiita.com/shin_ishiguro/items/8f39aac45acc8363a42e"},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install cupy-cuda102\n!pip install cupy","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip freeze","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install cupy --no-cache-dir -vvvv","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from cupy import sqrt as sqrt_g\nfrom cupy import power as power_g\nfrom cupy import abs as abs_g","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def mean_position_error_gpu(x_pred, y_pred, f_pred, x_true, y_true, f_true, p=15):\n    '''Same, but Faster ;)\n    ここでRAPIDSを使用することで、後にXGBoostモデルを使用することができます。'''\n    \n    N = len(x_true)\n    formula = sqrt_g( power_g(x_pred - x_true, 2) + power_g(y_pred - y_true, 2) )\n    formula = formula + p * abs_g(f_pred - f_true)\n    formula = formula.sum() / N\n    return formula","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 4. Baseline Model\n\n> 📌**Note**: 前処理されたデータは[Devin Anzelmo](https://www.kaggle.com/devinanzelmo)による[this dataset](https://www.kaggle.com/devinanzelmo/indoor-navigation-and-location-wifi-features)からのものです。"},{"metadata":{"trusted":true},"cell_type":"code","source":"def make_submission(predictions, sample_subm, name=\"base.csv\"):\n    '''予測値のリストをデータフレーム形式で受け取ります。'''\n    final_submission = pd.concat(predictions).reset_index(drop=True)\n    final_submission.index = sample_subm.index\n    final_submission.to_csv(name)\n    print(\"Submission ready.\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## I. Light GBM\n- 決定木アルゴリズムに基づいた勾配ブースティング（Gradient Boosting）の機械学習フレームワーク\n- 参考：https://www.codexa.net/lightgbm-beginner/"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Import Libraries\nimport lightgbm as lgb\n\n# ~~~~\n# Data\n# ~~~~\nbase_dir = \"../input/indoor-navigation-and-location-wifi-features/wifi_features\"\ntrain_dir = \"/train/*_train.csv\"\ntest_dir = \"/test/*_test.csv\"\n\n\n# Paths for train & test files\ntrain_paths = sorted(glob.glob(base_dir + train_dir))\ntest_paths = sorted(glob.glob(base_dir + test_dir))\nsample_subm = pd.read_csv('../input/indoor-location-navigation/sample_submission.csv',\n                          index_col=0)\n\nprint(\"Len Train Files: {}\".format(len(train_paths)), \"\\n\" +\n      \"Len Test Files: {}\".format(len(test_paths)))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 新しい実験の初期化 (LGBM)\nrun = wandb.init(project=\"indoor-location-kaggle\", name=\"lgbm_train\")\n\nwandb.log({'Len Train Files' : len(train_paths),\n           'Len Test Files' : len(test_paths)})","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> Schema of the \"Training loop\":\n<img src=\"https://i.imgur.com/UQmdRcz.png\" width=750>\n\n#### Code Below"},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":false},"cell_type":"code","source":"def train_lgbm(train_perc=0.75, version=1):\n    '''\n    Training loop\n    '''\n\n    f = open(f\"lgbm_logs_{version}.txt\", \"w+\")\n    lgbm_predictions = []\n    \n    k = 1\n    for train_path, test_path in zip(train_paths, test_paths):\n\n\n        # --- Read in data ---\n        # index_col=指定した列をインデックスとして利用\n        # frac=抽出する行・列の割合を指定できる。1だと100%。\n        train_df = pd.read_csv(train_path, index_col=0)\n        train_df = train_df.sample(frac=1, random_state=10)\n\n        # 最後の列を消す (which is \"site_path_timestamp\")\n        test_df = pd.read_csv(test_path, index_col=0).iloc[:, :-1]\n\n        # トレーニングとバリデーションデータのサンプルアウト\n        ### 3つのモデルすべてに同じ情報を選択するように注意する必要があります。\n        ### 1 for x, 1 for y and 1 for floor\n\n        train_size = int(len(train_df) * train_perc)\n\n\n        # --- Data Validation ---\n        # Train features + targets(75％を練習用)\n        X_train = train_df.iloc[:train_size, :-4]\n        y_train_x = train_df.iloc[:train_size, -4]\n        y_train_y = train_df.iloc[:train_size, -3]\n        y_train_f = train_df.iloc[:train_size, -2]\n\n        # Valid features + targets（２５％をテスト用）\n        X_valid = train_df.iloc[train_size:, :-4]\n        y_valid_x = train_df.iloc[train_size:, -4]\n        y_valid_y = train_df.iloc[train_size:, -3]\n        y_valid_f = train_df.iloc[train_size:, -2]\n\n\n        # --- Model Training ---\n        # n_estimators=ツリー（木）の数、num_leaves=最大ツリーリーフ数\n        lgbm_x = lgb.LGBMRegressor(n_estimators=150, num_leaves=127)\n        lgbm_x.fit(X_train, y_train_x)\n\n        lgbm_y = lgb.LGBMRegressor(n_estimators=150, num_leaves=127)\n        lgbm_y.fit(X_train, y_train_y)\n\n        lgbm_f = lgb.LGBMClassifier(n_estimators=150, num_leaves=127)\n        lgbm_f.fit(X_train, y_train_f)\n\n\n        # --- モデル検証 予測 ---\n        preds_x = lgbm_x.predict(X_valid)\n        preds_y = lgbm_y.predict(X_valid)\n        preds_f = lgbm_f.predict(X_valid).astype(int)\n        \n        mpe = mean_position_error(preds_x, preds_y, preds_f,\n                                  y_valid_x, y_valid_y, y_valid_f)\n        print(\"{} | MPE: {}\".format(k, mpe))\n        # Save logs\n        with open(f\"lgbm_logs_{version}.txt\", 'a+') as f:\n            print(\"{} | MPE: {}\".format(k, mpe), file=f)\n        k+=1\n        \n        # Log MPE of this experiment\n        wandb.log({'MPE' : mpe}, step=k)\n\n\n        # --- モデルテストの予測 ---\n        test_preds_x = lgbm_x.predict(test_df)\n        test_preds_y = lgbm_y.predict(test_df)\n        test_preds_f = lgbm_f.predict(test_df).astype(int)\n\n        all_test_preds = pd.DataFrame({'floor' : test_preds_f,\n                                       'x' : test_preds_x, \n                                       'y' : test_preds_y})\n        lgbm_predictions.append(all_test_preds)\n    \n    \n    return lgbm_predictions","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Training"},{"metadata":{"trusted":true},"cell_type":"code","source":"# 独自のモデルを作成する場合は、以下の行をコメントアウトしてください。\nlgbm_predictions = train_lgbm(train_perc = 0.75, version=1)\n\n# Logs from my training:\nprint(open('../input/indoor-locationnavigation-2021/lgbm_logs_1.txt', \"r\").read())","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> ❗**Attention**: *5番目*のデータフレームでは、大きなエラーが発生しました（平均4個から18個に増えました）。このケースは、モデルがアンダーフィッティングしていると思われるので、考慮しなければなりません。10番目、13番目、21番目のデータフレームも同様に大きなMPEを持っています。\n\n### Submission LGBM"},{"metadata":{"trusted":true},"cell_type":"code","source":"# 独自のモデルを作成する場合は、以下の行をコメントアウトしてください。\nmake_submission(lgbm_predictions, sample_subm, name=\"lgbm_base.csv\")\n\n# My submission:\nlgbm_predictions = pd.read_csv(\"../input/indoor-locationnavigation-2021/lgbm_base.csv\")\nlgbm_predictions.to_csv(\"lgbm_base.csv\", index=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# ~ END of EXPERIMENT ~\nwandb.finish()\n# ~~~~~~~~~~~~~~~~~~~~~","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## II. XGBoost - Faster with RAPIDS\n\n> 📌**Note**: 今回は、GPU上の**RAPIDS**ライブラリとXGBoostを組み合わせたものをベースモデルの一つとして使用します。このオープンソースのライブラリ群についての詳細は[こちら](https://rapids.ai/)をご覧ください。"},{"metadata":{"_kg_hide-input":false,"trusted":true},"cell_type":"code","source":"# Libraries\nimport cudf\nimport cupy\nimport cuml\nimport xgboost\n\n# Adjust floor function\n### Multiclass XGBoostは、[0, n]の間のラベルしか取らないので\n### しかし、マイナスのフロア値を持っています\ndef adjust_floor(df, col_name):\n    '''Adjusts the floor to be >= 0.\n    Also returns the number fo classes (also used to complete classification).'''\n    num_classes = df[col_name].nunique()\n    smallest = df[col_name].unique().min()\n    df[col_name] = df[col_name] - smallest\n    \n    return df[col_name], num_classes, smallest","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Initialize new experiment (XGB)\nrun = wandb.init(project=\"indoor-location-kaggle\", name=\"xgb_train\")\n\nwandb.log({'Len Train Files' : len(train_paths),\n           'Len Test Files' : len(test_paths)})","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"def train_xgb(train_perc=0.75, version=1):\n    '''\n    Training loop\n    '''\n\n    f = open(f\"xgb_logs_{version}.txt\", \"w+\")\n    xgb_predictions = []\n    \n    k = 1\n    for train_path, test_path in zip(train_paths, test_paths):\n\n\n        # --- Read in data ---\n        train_df = cudf.read_csv(train_path, index_col=0)\n        train_df = train_df.sample(frac=1, random_state=10)\n        train_df[\"f\"], num_classes, smallest = adjust_floor(train_df, 'f')\n\n        # Erase last column (which is \"site_path_timestamp\")\n        test_df = cudf.read_csv(test_path, index_col=0).iloc[:, :-1]\n\n        # Sample out training and validation data\n        ### we need to be careful to choose same information for ALL 3 models\n        ### 1 for x, 1 for y and 1 for floor\n\n        train_size = int(len(train_df) * train_perc)\n\n\n        # --- Data Validation ---\n        # Train features + targets\n        X_train = train_df.iloc[:train_size, :-4]\n        y_train_x = train_df.iloc[:train_size, -4]\n        y_train_y = train_df.iloc[:train_size, -3]\n        y_train_f = train_df.iloc[:train_size, -2]\n\n        # Valid features + targets\n        X_valid = train_df.iloc[train_size:, :-4]\n        y_valid_x = cupy.asanyarray(train_df.iloc[train_size:, -4])\n        y_valid_y = cupy.asanyarray(train_df.iloc[train_size:, -3])\n        y_valid_f = cupy.asanyarray(train_df.iloc[train_size:, -2])\n        \n        \n        # --- Parameters ---\n        # max_depth=決定木の深さの最大値  max_leaves=葉の最大値　tree_method=木構造アルゴリズム  grow_policy=ツリーに新しいノードを追加する方法\n        regr_params = {'max_depth' : 4, 'max_leaves' : 2**4, \n                       'tree_method' : 'gpu_hist', 'objective' : 'reg:squarederror',\n                       'grow_policy' : 'lossguide', 'colsample_bynode': 0.8}\n        classif_params = {'max_depth' : 4, 'max_leaves' : 2**4,\n                          'tree_method' : 'gpu_hist', 'objective' : 'multi:softmax',\n                          'num_class' : num_classes, 'grow_policy' : 'lossguide',\n                          'colsample_bynode': 0.8, 'verbosity' : 0}\n\n\n        # --- Model Training ---\n        trainMatrix_x = xgboost.DMatrix(data=X_train, label=y_train_x)\n        xgboost_x = xgboost.train(params=regr_params, dtrain=trainMatrix_x)\n\n        trainMatrix_y = xgboost.DMatrix(data=X_train, label=y_train_y)\n        xgboost_y = xgboost.train(params=regr_params, dtrain=trainMatrix_y)\n\n        trainMatrix_f = xgboost.DMatrix(data=X_train, label=y_train_f)\n        xgboost_f = xgboost.train(params=classif_params, dtrain=trainMatrix_f)\n\n\n        # --- Model Validation Predictions ---\n        preds_x = cupy.asanyarray(xgboost_x.predict(xgboost.DMatrix(X_valid)))\n        preds_y = cupy.asanyarray(xgboost_y.predict(xgboost.DMatrix(X_valid)))\n        preds_f = cupy.asanyarray(xgboost_f.predict(xgboost.DMatrix(X_valid)).astype(int))\n\n        mpe = mean_position_error_gpu(preds_x, preds_y, preds_f,\n                                      y_valid_x, y_valid_y, y_valid_f)\n        print(\"{} | MPE: {}\".format(k, mpe))\n        # Save logs\n        with open(f\"xgb_logs_{version}.txt\", 'a+') as f:\n            print(\"{} | MPE: {}\".format(k, mpe), file=f)\n        k+=1\n        # Log MPE of this experiment\n        wandb.log({'MPE' : mpe}, step=k)\n\n\n        # --- Model Test Predictions ---\n        test_preds_x = xgboost_x.predict(xgboost.DMatrix(test_df))\n        test_preds_y = xgboost_y.predict(xgboost.DMatrix(test_df))\n        test_preds_f = xgboost_f.predict(xgboost.DMatrix(test_df)).astype(int) + smallest\n\n        all_test_preds = pd.DataFrame({'floor' : test_preds_f,\n                                       'x' : test_preds_x, \n                                       'y' : test_preds_y})\n        xgb_predictions.append(all_test_preds)\n    \n    \n    return xgb_predictions","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Training"},{"metadata":{"_kg_hide-input":false,"trusted":true},"cell_type":"code","source":"# 独自のモデルを作成する場合は、以下の行をコメントを外してください。\n# xgb_predictions = train_xgb(train_perc=0.75, version=1)\n\n# Logs from my training:\nprint(open('../input/indoor-locationnavigation-2021/xgb_logs_1.txt', \"r\").read())","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Submission"},{"metadata":{"trusted":true},"cell_type":"code","source":"# 下の行のコメントを外して、自分で投稿してください。\n# make_submission(xgb_predictions, sample_subm, name=\"xgb_base.csv\")\n\n# My submission:\nxgb_predictions = pd.read_csv(\"../input/indoor-locationnavigation-2021/xgb_base.csv\")\nxgb_predictions.to_csv(\"xgb_base.csv\", index=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# ~ END of EXPERIMENT ~\nwandb.finish()\n# ~~~~~~~~~~~~~~~~~~~~~","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 5. Save W&B Submissions and Logs\n\n> We can save the predictions and logs to W&B."},{"metadata":{"trusted":true},"cell_type":"code","source":"# Submissions\n### we can save the predictions in W&B\nrun = wandb.init(project='indoor-location-kaggle', name='submissions')\nartifact = wandb.Artifact(name='submissions', \n                          type='dataset')\n\nartifact.add_file(\"../input/indoor-locationnavigation-2021/lgbm_base.csv\")\nartifact.add_file(\"../input/indoor-locationnavigation-2021/xgb_base.csv\")\n\nwandb.log_artifact(artifact)\nwandb.finish()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Logs\n### we can save the predictions in W&B\nrun = wandb.init(project='indoor-location-kaggle', name='training_logs')\nartifact = wandb.Artifact(name='training_logs', \n                          type='dataset')\n\nartifact.add_file(\"../input/indoor-locationnavigation-2021/lgbm_logs_1.txt\")\nartifact.add_file(\"../input/indoor-locationnavigation-2021/xgb_logs_1.txt\")\n\nwandb.log_artifact(artifact)\nwandb.finish()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# モデルの組み合わせ"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"# Read in data\nlgb_preds = pd.read_csv(\"../input/indoor-locationnavigation-2021/lgbm_base.csv\")\nxgb_preds = pd.read_csv(\"../input/indoor-locationnavigation-2021/xgb_base.csv\")\n\nsample_submission = pd.read_csv(\"../input/indoor-location-navigation/sample_submission.csv\")\n\n# Sample Submission\nsample_submission[\"x\"] = lgb_preds[\"x\"] * 0.9 + xgb_preds[\"x\"] * 0.1\nsample_submission[\"y\"] = lgb_preds[\"y\"] * 0.9 + xgb_preds[\"y\"] * 0.1\n\nsample_submission.to_csv(\"blend1.csv\", index=False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<img src=\"https://i.imgur.com/cUQXtS7.png\">\n\n# Specs on how I prepped & trained ⌨️🎨\n### (on my local machine)\n* Z8 G4 Workstation 🖥\n* 2 CPUs & 96GB Memory 💾\n* NVIDIA Quadro RTX 8000 🎮\n* RAPIDS version 0.17 🏃🏾‍♀️"},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"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":4,"nbformat_minor":4}