{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"ab7078dd","cell_type":"code","source":"VERSION = \"k134\"\n\n# ローカル用\n#PATH = '../ignore_dir/input/cmi-detect-behavior-with-sensor-data/'\n#PATH_OUTPUT = 'ignore_dir/'\n\n# Kaggle用\nPATH = '/kaggle/input/cmi-detect-behavior-with-sensor-data/'\nPATH_OUTPUT = ''\n","metadata":{},"outputs":[],"execution_count":null},{"id":"0c85e0b1","cell_type":"code","source":"# general library\nimport datetime\nimport numpy as np\nimport os\nimport pandas as pd\nimport polars as pl\nimport random\nfrom tqdm.notebook import tqdm\n\n# sklearn\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.model_selection import StratifiedKFold, StratifiedShuffleSplit\nfrom sklearn.metrics import f1_score, accuracy_score, precision_score, recall_score\nfrom sklearn.preprocessing import StandardScaler\n\n# ML argorithm\nimport lightgbm as lgb\n\n# torch\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torchinfo import summary\nfrom torch.utils.data import DataLoader, Dataset, random_split\n\n# Matplotlib\nimport matplotlib.pyplot as plt\n%matplotlib inline\n#import japanize_matplotlib\n\n# warnings\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Import evaluation API\nimport kaggle_evaluation.cmi_inference_server","metadata":{},"outputs":[],"execution_count":null},{"id":"0e4a8b9c","cell_type":"code","source":"start = datetime.datetime.now()","metadata":{},"outputs":[],"execution_count":null},{"id":"93128bc4","cell_type":"code","source":"# Check device\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\nprint(device)","metadata":{},"outputs":[],"execution_count":null},{"id":"73a9ff0a","cell_type":"markdown","source":"# Constant","metadata":{}},{"id":"a3e73e57","cell_type":"code","source":"# Target\nBFRB_GESTURE = [\n\t'Above ear - pull hair', 'Forehead - pull hairline', 'Forehead - scratch', 'Eyebrow - pull hair', \n\t'Eyelash - pull hair', 'Neck - pinch skin', 'Neck - scratch', 'Cheek - pinch skin',\n]\nNON_BFRB_GESTURE = [\n\t'Drink from bottle/cup', 'Glasses on/off', 'Pull air toward your face', 'Pinch knee/leg skin', 'Scratch knee/leg skin', \n\t'Write name on leg', 'Text on phone', 'Feel around in tray and pull out an object', 'Write name in air', 'Wave hello', \n]\nGESTURE = BFRB_GESTURE + NON_BFRB_GESTURE\nprint(len(BFRB_GESTURE), len(NON_BFRB_GESTURE), len(GESTURE))","metadata":{},"outputs":[],"execution_count":null},{"id":"5e0b50b4","cell_type":"code","source":"# Feature\nFEATURES_IMU = ['acc_x', 'acc_y', 'acc_z', 'rot_w', 'rot_x', 'rot_y', 'rot_z']\nFEATURES_THM = ['thm_1', 'thm_2', 'thm_3', 'thm_4', 'thm_5']\nFEATURES = FEATURES_IMU # + FEATURES_THM\nprint(f\"Number of explain variables: {len(FEATURES)}\")","metadata":{},"outputs":[],"execution_count":null},{"id":"6e5d1f61","cell_type":"code","source":"VAL_SIZE = 0.2 # 訓練データと検証データを分割するときの割合\nBATCH_SIZE = 64 # 64 # バッチサイズ\nLR = 0.001 # 学習率\nWEIGHT_DECAY = 1e-7 # 重み減衰\nNUM_EPOCHS = 150 #80 # エポック数\n\nTRIM_SEQ_LEN = 103 # 128 # シーケンス長","metadata":{},"outputs":[],"execution_count":null},{"id":"ef2ce37b","cell_type":"markdown","source":"# My funcition","metadata":{}},{"id":"04edd0fe","cell_type":"code","source":"def show_df(df, n_head=3):\n    print(df.shape)\n    display(df.head(n_head))","metadata":{},"outputs":[],"execution_count":null},{"id":"e93c0a06","cell_type":"code","source":"# To calculate the competion score\ndef convert_gesture_num_bin(lis_gesture_num):\n\t\"\"\"0-7: True(BFRB), 8-17: False(Non-BFRB)\"\"\"\n\treturn [True if gn < len(BFRB_GESTURE) else False for gn in lis_gesture_num]\n\ndef convert_gesture_num_macro(lis_gesture_num):\n\t\"\"\"0-7: そのまま, 8-17: -1(Non-BFRB)\"\"\"\t\n\treturn [gn if gn < len(BFRB_GESTURE) else -1 for gn in lis_gesture_num]\n\ndef calculate_scores_bin(y_true, y_pred):\n\taccuracy = accuracy_score(y_true, y_pred)\n\tprecision = precision_score(y_true, y_pred, zero_division=0)\n\trecall = recall_score(y_true, y_pred, zero_division=0)\n\tf1 = f1_score(y_true, y_pred, pos_label=True, zero_division=0, average='binary')\n\treturn accuracy, precision, recall, f1\n\ndef calculate_scores_macro(y_true, y_pred):\n\taccuracy = accuracy_score(y_true, y_pred)\n\tprecision = precision_score(y_true, y_pred, zero_division=0, average='macro')\n\trecall = recall_score(y_true, y_pred, zero_division=0, average='macro')\n\tf1 = f1_score(y_true, y_pred, zero_division=0, average='macro')\n\treturn accuracy, precision, recall, f1\n\ndef get_scores(y_true, y_pred):\n\ty_true_bin = convert_gesture_num_bin(y_true)\n\ty_pred_bin = convert_gesture_num_bin(y_pred)\n\tacc_bin, prc_bin, rec_bin, f1_bin = calculate_scores_bin(y_true_bin, y_pred_bin)\n\n\ty_true_macro = convert_gesture_num_macro(y_true)\n\ty_pred_macro = convert_gesture_num_macro(y_pred)\n\tacc_macro, prc_macro, rec_macro, f1_macro = calculate_scores_macro(y_true_macro, y_pred_macro)\n\t\n\tfinal_score = (f1_bin + f1_macro) * 0.5\n\t#print(f\"Competition Score: {compe_score}\")\n\treturn [acc_bin, acc_macro, prc_bin, prc_macro, rec_bin, rec_macro, f1_bin, f1_macro, final_score]","metadata":{},"outputs":[],"execution_count":null},{"id":"365a9717","cell_type":"markdown","source":"# Seed","metadata":{}},{"id":"bec2f96f","cell_type":"code","source":"SEED = 42\ndef seed_everything(seed=42):\n    random.seed(seed)\n    #np.random.seed(seed)\n    np.random.RandomState(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.Generator().manual_seed(seed)\n    torch.backends.cudnn.daterministic = True\n    torch.use_deterministic_algorithms = True\n    # torch.backends.cudnn.benchmark = False\nseed_everything(SEED)","metadata":{},"outputs":[],"execution_count":null},{"id":"d26421ad","cell_type":"markdown","source":"## Load data","metadata":{}},{"id":"f3a91491","cell_type":"code","source":"train_df = pd.read_csv(f\"{PATH}/train.csv\")\ntest_df = pd.read_csv(f\"{PATH}/test.csv\")\ntrain_demographics_df = pd.read_csv(f\"{PATH}/train_demographics.csv\")\ntest_demographics_df = pd.read_csv(f\"{PATH}/test_demographics.csv\")","metadata":{},"outputs":[],"execution_count":null},{"id":"6070e793","cell_type":"code","source":"show_df(train_df)\nshow_df(test_df)","metadata":{},"outputs":[],"execution_count":null},{"id":"3cc5eb2e","cell_type":"code","source":"#train_df[:1000].to_csv('../ignore_dir/preprocess/train_df_1000.csv', index=False)","metadata":{},"outputs":[],"execution_count":null},{"id":"6573b762","cell_type":"markdown","source":"# Preprocess","metadata":{}},{"id":"c7810f2f","cell_type":"code","source":"def filling(df):\n\t\"\"\"\n\tfill NA\n\t\"\"\"\n\treturn df.ffill().bfill().fillna(0)\n\n\ndef gesture2numeric(df):\n\t\"\"\"\n\tジェスチャー列の値を数値に変換し、列追加する。Convert gesture labels to numeric values and add a new column.\n\n\tParameters:\n\t\tdf (pd.DataFrame): DataFrame containing gesture labels.\n\n\tReturns:\n\t\tdf (pd.DataFrame): DataFrame with an additional column for numeric gesture labels.\n\t\tdic_gesture (dict): Dictionary mapping gesture labels to numeric values.\t  \n\t\"\"\"\n\tdic_gesture = {gesture: idx for idx, gesture in enumerate(GESTURE)}\n\tdf['gesture_numeric'] = df['gesture'].map(dic_gesture)\n\n\treturn df, dic_gesture\n\ndef gesture2ohe(df):\n\t\"\"\"\n\tジェスチャー列の値をワンホットラベルに変換し、列追加する。\n\n\tParameters:\n\t\tdf (pd.DataFrame): Input DataFrame.\n\n\tReturns:\n\t\tpd.DataFrame: DataFrame with additional one-hot encoded columns.\n\t\"\"\"\n\tdf_ohe = pd.get_dummies(df['gesture'], dtype=int) \n\treturn pd.concat([df, df_ohe], axis=1)\n\ndef standardize(df, features, fit=False, scaler=None):\n\t\"\"\"\n\t標準化を行う関数:\n\t\t各特徴量の平均を0、標準偏差を1に変換\n\n\tParameters:\n\t\tdf (pd.DataFrame): 入力データフレーム\n\t\tfeatures (list): 標準化する特徴量のリスト\n\n\tReturns:\n\t\tpd.DataFrame: 標準化されたデータフレーム\n\t\tscaler (StandardScaler): 標準化のためのスケーラーオブジェクト\n\t\"\"\"\n\tif fit:\n\t\tscaler = StandardScaler()\n\t\tscaler.fit(df[features])\n\tdf[features] = scaler.transform(df[features])\n\treturn df, scaler\n\n\ndef data_shaping(df, y_boolean):\n\t\"\"\"\n\tデータの整形を行う関数:\n\t\tsubjectとsequence_idでグループ化\n\t\t各グループの特徴量FEATURESを抽出し、numpy配列に変換（X_ret）\n\t\t各グループのgesture_numericとgestureを抽出\n\t\tgesture_numericが一意でない場合は-1を設定し、gestureを'error'とする(y_ret, ystr_ret)\n\t\t各グループのsubjectとsequence_idをキーとしてmeta_retに格納\n\n\tParameters:\n\t\tdf (pd.DataFrame): 入力データフレーム\n\t\ty_boolean (bool): ターゲット変数を含むかどうかのフラグ\n\n\tReturns:\n\t\tX_ret (list): 各グループの特徴量を格納したリスト\n\t\t\tリストの要素はnumpy配列(n_feature(in_channel), sequence_length)\n\t\tret (list): 各グループのgesture_numericを格納したリスト\n\t\t\tグループ内に複数のgesture_numが存在する場合は-1を設定\n\t\tystr_ret (list)各グループのgestureを格納したリスト\n\t\t\tグループ内に複数のgestureが存在する場合は'error'を設定\n\t\tmeta_ret (list): 各グループのsubjectとsequence_idを格納したリスト\n\n\t\"\"\"\n\tX_ret = []\n\tynum_ret = []\n\tyohe_ret = []\n\tystr_ret = []\n\tmeta_ret =[]\n\n\tdf_gb = df.groupby(['subject', 'sequence_id'])\n\tfor keys, data in tqdm(df_gb):\n\t\tdata_ft = np.array(data[FEATURES].values, dtype=np.float32).reshape(len(FEATURES), -1) # reshape to (n_features, sequence length)\n\t\tX_ret.append(data_ft)\n\t\tmeta_ret.append(keys)\n\t\t\n\t\tif y_boolean:\n\t\t\tgesture_num = data['gesture_numeric']\n\t\t\tgesture_ohe = data[GESTURE]\n\t\t\tgesture_str = data['gesture']\n\t\t\tif gesture_num.nunique() == 1:\n\t\t\t\ttarget_num = gesture_num.iloc[0]\n\t\t\t\ttarget_ohe = gesture_ohe.iloc[0].values.tolist()\n\t\t\t\ttarget_str = gesture_str.iloc[0]\n\t\t\telse:\n\t\t\t\ttarget_num = -1\n\t\t\t\ttarget_ohe = [-1] * len(GESTURE)  # ワンホットエンコーディングのためのリスト\n\t\t\t\ttarget_str = 'error'\n\t\t\tynum_ret.append(target_num)\n\t\t\tyohe_ret.append(target_ohe)\n\t\t\tystr_ret.append(target_str)\n\t\t\n\treturn X_ret, ynum_ret, yohe_ret, ystr_ret, meta_ret\n\n\ndef trim_sequence(X):\n\t\"\"\"\n\tシーケンス長を揃える関数\n\n\tParameters:\n\t\tX(list): 説明変数のリスト\n\t \t\t各要素はシーケンス長が可変のnumpy配列\n\n\tReturns:\n\t\tX_ret (list): 各要素のシーケンス長を揃えたnumpy配列のリスト\n\t\"\"\"\n\tX_ret = []\n\tfor x in tqdm(X):\n\t\t# シーケンス長がTRIM_SEQ_LEN以下の場合はパディングを行い、TRIM_SEQ_LENを超える場合は切り捨て\n\t\tif x.shape[1] <= TRIM_SEQ_LEN:\n\t\t\tpad = np.zeros((len(FEATURES), TRIM_SEQ_LEN - x.shape[1]), dtype=np.float32)\n\t\t\tx_padded = np.concatenate([x, pad], axis=1)\n\t\t\tif x_padded.shape[1] != TRIM_SEQ_LEN:\n\t\t\t\tprint(f\"Warning: うまくパディングできてないかも→{x_padded.shape[1]}\")\n\t\telse:\n\t\t\tx_padded = x[:, :TRIM_SEQ_LEN]\n\t\tX_ret.append(x_padded)\n\treturn X_ret","metadata":{},"outputs":[],"execution_count":null},{"id":"9d0bc30e","cell_type":"code","source":"# コピー\ntrain_pp = train_df.copy()\ntest_pp = test_df.copy()\n# 欠損値補完\ntrain_pp = filling(train_pp)\ntest_pp = filling(test_pp)","metadata":{},"outputs":[],"execution_count":null},{"id":"b6aea6c5","cell_type":"code","source":"# ワンホットエンコーディング\ntrain_pp = gesture2ohe(train_pp) # 訓練のみ\n\n# ラベルエンコーディング\ntrain_pp, dic_gesture = gesture2numeric(train_pp)","metadata":{},"outputs":[],"execution_count":null},{"id":"6e2d82bc","cell_type":"code","source":"# 標準化\ndisplay(train_pp[FEATURES].describe())\ntrain_pp, scaler = standardize(train_pp, FEATURES, fit=True, scaler=None)\ndisplay(train_pp[FEATURES].describe())","metadata":{},"outputs":[],"execution_count":null},{"id":"79bcf1fb","cell_type":"code","source":"# データ整形\nX_train, ynum_train, yohe_train, ystr_train, meta_train = data_shaping(train_pp, y_boolean=True)\nprint(f\"X_train: {len(X_train)}, ynum_train:{len(ynum_train)}, yohe_train:{len(yohe_train)}, ystr_train: {len(ystr_train)}, meta_tr: {len(meta_train)}\")\n# シーケンス長を揃える\nX_train_trim = trim_sequence(X_train)","metadata":{},"outputs":[],"execution_count":null},{"id":"43f5d317","cell_type":"markdown","source":"# Check data","metadata":{}},{"id":"b18a19de","cell_type":"code","source":"# シーケンス長について確認\ndef check_sequence_length(lis_X):\n\tmin, max = lis_X[0].shape[1], lis_X[0].shape[1]\n\tfor record in tqdm(lis_X):\n\t\tif record.shape[1] < min: min = record.shape[1]\n\t\tif record.shape[1] > max: max = record.shape[1]\n\tprint(f\"sequence length >>> min:{min}, max:{max}\")\ncheck_sequence_length(X_train)\ncheck_sequence_length(X_train_trim)","metadata":{},"outputs":[],"execution_count":null},{"id":"5f732e95","cell_type":"code","source":"# ターゲットの頻度分布を可視化\ncount_BFRB = []\ncount_NON_BFRB = []\ncount_error = []\nn_sample = 0\nfor g in GESTURE+['error']:\n\tc = ystr_train.count(g)\n\tn_sample += c\n\tif g in BFRB_GESTURE:\n\t\tcount_BFRB.append(c)\n\t\tcount_NON_BFRB.append(0)\n\t\tcount_error.append(0)\n\telif g in NON_BFRB_GESTURE:\n\t\tcount_BFRB.append(0)\n\t\tcount_NON_BFRB.append(c)\n\t\tcount_error.append(0)\n\telse:\n\t\tcount_BFRB.append(0)\n\t\tcount_NON_BFRB.append(0)\n\t\tcount_error.append(c)\nplt.figure()\nplt.bar(GESTURE+['error'], count_BFRB, label='BFRB')\nplt.bar(GESTURE+['error'], count_NON_BFRB, label='Non-BFRB', bottom=count_BFRB)\nplt.bar(GESTURE+['error'], count_error, label='error', bottom=np.array(count_BFRB)+np.array(count_NON_BFRB))\nplt.xticks(rotation=90)\nplt.title(f\"Gesture's frequency (samples:{n_sample})\")\nplt.ylim(0, 800)\nplt.ylabel(\"frequency\")\nplt.yticks(np.arange(0, 801, 200))\nplt.grid(axis='y')\nplt.legend()\nplt.show()","metadata":{},"outputs":[],"execution_count":null},{"id":"9e5ac663","cell_type":"code","source":"# BFRBとNON-BFRBの割合を確認\nsum_BFRB = sum(count_BFRB)\nsum_NON_BFRB = sum(count_NON_BFRB)\nsum_error = sum(count_error)\n\nplt.figure()\nplt.pie([sum_BFRB, sum_NON_BFRB, sum_error], startangle=90, counterclock=False,\n        labels=['BFRB', 'NON-BFRB', 'error'], labeldistance=None,\n        autopct=\"%.1f%%\", pctdistance=0.5, )\nplt.legend(loc='upper right')\nplt.title(f'Percentage of gesture (samples:{sum_BFRB + sum_NON_BFRB})')\nplt.show()","metadata":{},"outputs":[],"execution_count":null},{"id":"0bbeedf1","cell_type":"markdown","source":"# Split tr/val\n- subject(被験者)毎に分割する\n- そうするとデータ数にばらつきが生じるが（subjectごとにデータ数が違うため）、いったんそこは目をつぶる","metadata":{}},{"id":"89623919","cell_type":"code","source":"# subject列のユニーク要素とその数を取得\nunique_subject = train_df['subject'].unique().tolist()\nnunique_subject = train_df['subject'].nunique()\n\n# random.sampleを使って、VAL_SIZEの割合でval_subjectを選ぶ。残りをtr_subjectとする。\nval_subject = random.sample(unique_subject, int(nunique_subject*VAL_SIZE))\ntr_subject = [s for s in unique_subject if s not in val_subject]\nprint(f\"tr subject length: {len(tr_subject)} ex.{tr_subject[:3]} ... {tr_subject[-3:]}\")\nprint(f\"val subject length: {len(val_subject)} ex. {val_subject[:3]} ... {val_subject[-3:]}\")\n\n# インデックスを取得\nidx_tr  = [i for i in range(len(meta_train)) if meta_train[i][0] in tr_subject]\nidx_val = [i for i in range(len(meta_train)) if meta_train[i][0] in val_subject]\nprint(len(idx_tr), len(idx_val))\n\n# インデックスを使ってデータを抽出する関数\ndef extract(lis, idx):\n\treturn [lis[i] for i in range(len(lis)) if i in idx]\n\nX_tr = extract(X_train_trim, idx_tr)\nX_val = extract(X_train_trim, idx_val)\nynum_tr = extract(ynum_train, idx_tr)\nynum_val = extract(ynum_train, idx_val)\nyohe_tr = extract(yohe_train, idx_tr)\nyohe_val = extract(yohe_train, idx_val)\nystr_tr = extract(ystr_train, idx_tr)\nystr_val = extract(ystr_train, idx_val)\nmeta_tr = extract(meta_train, idx_tr)\nmeta_val = extract(meta_train, idx_val)\nprint(len(X_tr), len(ynum_tr), len(yohe_tr), len(ystr_tr), len(meta_tr))\nprint(len(X_val), len(ynum_val), len(yohe_val), len(ystr_val), len(meta_val))","metadata":{},"outputs":[],"execution_count":null},{"id":"ab61dacb","cell_type":"code","source":"print(ystr_tr[0])\nprint(ynum_tr[0])\nprint(yohe_tr[0])","metadata":{},"outputs":[],"execution_count":null},{"id":"0ff66834","cell_type":"markdown","source":"# Data loader","metadata":{}},{"id":"ab71d370","cell_type":"code","source":"# === 目的変数の設定 ========================\ny_tr = ynum_tr.copy()\ny_val = ynum_val.copy()\n# =========================================\n\n# データセットクラスを定義\nclass TimeSeriesDataset(Dataset):\n    def __init__(self, data, labels):\n        self.data = [torch.tensor(d, dtype=torch.float32) for d in data]\n        self.labels = torch.tensor(labels, dtype=torch.long) # 目的変数が数値の場合\n        #self.labels = [torch.tensor(l, dtype=torch.float32) for l in labels] # 目的変数がワンホットラベルの場合\n\n    def __len__(self):\n        return len(self.labels)\n\n    def __getitem__(self, idx):\n        return self.data[idx], self.labels[idx]\n    \n# データセット作成\ntr_dataset = TimeSeriesDataset(X_tr, y_tr)\nprint(len(tr_dataset))\n\nval_dataset = TimeSeriesDataset(X_val, y_val)\nprint(len(val_dataset))\n\n# データローダーを作成\ntr_loader = DataLoader(tr_dataset, batch_size=BATCH_SIZE, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False)","metadata":{},"outputs":[],"execution_count":null},{"id":"6db93766","cell_type":"code","source":"# データローダーの確認\nfor inputs, labels in val_loader:\n\tbreak\nprint(type(inputs))\nprint(len(inputs))\nprint(inputs[0])\nprint(inputs[0].shape)","metadata":{},"outputs":[],"execution_count":null},{"id":"f5b53e00","cell_type":"code","source":"print(type(labels))\nprint(len(labels))\nprint(labels[0])\nprint(labels[0].shape)","metadata":{},"outputs":[],"execution_count":null},{"id":"9d87358b","cell_type":"markdown","source":"# Get parameters in this task","metadata":{}},{"id":"481410ba","cell_type":"code","source":"in_channel = len(FEATURES) # 入力チャネル数\nsequence_length = TRIM_SEQ_LEN # シーケンス長\nn_output = len(GESTURE) # 出力クラス数\nprint(f'in_channel: {in_channel}, sequence_length: {sequence_length}, n_output: {n_output}')","metadata":{},"outputs":[],"execution_count":null},{"id":"9270d3a2","cell_type":"markdown","source":"# Define model","metadata":{}},{"id":"c61524b7","cell_type":"code","source":"class CNN(nn.Module):\n    def __init__(self, input_channels, num_classes):\n        super(CNN, self).__init__()\n\n        self.conv_block = nn.Sequential(\n            # Block 1\n            nn.Conv1d(input_channels, 512, kernel_size=7),\n            nn.ReLU(),\n            nn.BatchNorm1d(512),\n            nn.MaxPool1d(kernel_size=2),\n            nn.Dropout(0.3),\n\n            # Block 2\n            nn.Conv1d(512, 768, kernel_size=5),\n            nn.ReLU(),\n            nn.BatchNorm1d(768),\n            nn.MaxPool1d(kernel_size=2),\n            nn.Dropout(0.3),\n\n            # Block 3\n            nn.Conv1d(768, 1024, kernel_size=3),\n            nn.ReLU(),\n            nn.BatchNorm1d(1024),\n            nn.MaxPool1d(kernel_size=2),\n            nn.Dropout(0.4),\n\n            # Block 4\n            nn.Conv1d(1024, 1536, kernel_size=3),\n            nn.ReLU(),\n            nn.BatchNorm1d(1536),\n            nn.MaxPool1d(kernel_size=2),\n            nn.Dropout(0.4),\n\n            # Block 5\n            nn.Conv1d(1536, 2048, kernel_size=3),\n            nn.ReLU(),\n            nn.BatchNorm1d(2048),\n            nn.AdaptiveMaxPool1d(1),  # GlobalMaxPooling1D 相当\n            nn.Dropout(0.5),\n        )\n\n        self.fc_block = nn.Sequential(\n            nn.Linear(2048, 2048),\n            nn.ReLU(),\n            nn.Dropout(0.5),\n\n            nn.Linear(2048, 1024),\n            nn.ReLU(),\n            nn.Dropout(0.4),\n\n            nn.Linear(1024, 512),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n\n            nn.Linear(512, num_classes),\n        )\n\n    def forward(self, x):\n        # 入力: [batch_size, channels, sequence_length]\n        x = self.conv_block(x)     # → [batch_size, 2048, 1]\n        x = x.squeeze(-1)          # → [batch_size, 2048]\n        x = self.fc_block(x)       # → [batch_size, num_classes]\n        return x","metadata":{},"outputs":[],"execution_count":null},{"id":"92793354","cell_type":"code","source":"# インスタンス作成\nnet = CNN(in_channel, n_output).to(device)\n\n# 損失関数\ncriterion = nn.CrossEntropyLoss() # 交差エントロピー誤差\n\n# 最適化関数と学習率\noptimizer = optim.Adam(net.parameters(), lr=LR)","metadata":{},"outputs":[],"execution_count":null},{"id":"f9e71764","cell_type":"code","source":"print(net)","metadata":{},"outputs":[],"execution_count":null},{"id":"fc68581b","cell_type":"code","source":"# モデルのサマリー表示\nsummary(net, input_size=(BATCH_SIZE, in_channel, TRIM_SEQ_LEN))","metadata":{},"outputs":[],"execution_count":null},{"id":"fb1f47fd","cell_type":"code","source":"# Early stopping and ReduceLROnPlateau\nclass EarlyStopping:\n    def __init__(self, patience=10, restore_best_weights=True, mode='min'):\n        self.patience = patience\n        self.restore_best_weights = restore_best_weights\n        self.mode = mode\n        self.best_score = float('inf') if mode == 'min' else float('-inf')\n        self.best_state_dict = None\n        self.counter = 0\n\n    def step(self, score, model):\n        if self.mode == 'min':\n            improved = score < self.best_score\n        elif self.mode == 'max':\n            improved = score > self.best_score\n        else:\n            raise ValueError(\"Mode must be either 'min' or 'max'\")\n\n        if improved:\n            self.best_score = score\n            self.best_state_dict = model.state_dict()\n            self.counter = 0\n        else:\n            self.counter += 1\n            if self.counter >= self.patience:\n                if self.restore_best_weights:\n                    model.load_state_dict(self.best_state_dict)\n                return True\n        return False\n\nearly_stopping = EarlyStopping(patience=10, restore_best_weights=True, mode='max')","metadata":{},"outputs":[],"execution_count":null},{"id":"158a18f7","cell_type":"code","source":"# 学習率スケジューラの定義\nreduce_lr_on_plateau = optim.lr_scheduler.ReduceLROnPlateau(\n\toptimizer, \n\tmode='max', \n\tfactor=0.7, \n\tpatience=3, \n\tmin_lr=1e-7, \n\t# verbose=True\n)\n\n# Add a logging function to monitor learning rate reductions\ndef log_reduce_lr(optimizer):\n    for param_group in optimizer.param_groups:\n        print(f\"Learning rate reduced to: {param_group['lr']:.7f}\\n\")","metadata":{},"outputs":[],"execution_count":null},{"id":"8c95fcd8","cell_type":"code","source":"# # ReduceLROnPlateau without the 'verbose' parameter\n# reduce_lr_on_plateau = optim.lr_scheduler.ReduceLROnPlateau(\n#     optimizer, \n#     factor=0.7, \n#     patience=3, \n#     min_lr=1e-7  # Remove verbose\n# )\n\n# # Add a logging function to monitor learning rate reductions\n# def log_reduce_lr(learning_rate):\n#     print(f\"Learning rate reduced to: {learning_rate}\\n\")","metadata":{},"outputs":[],"execution_count":null},{"id":"572adb47","cell_type":"markdown","source":"# Train","metadata":{}},{"id":"3516198a","cell_type":"code","source":" # 繰り返し数、\n # 訓練損失、検証損失、\n # 訓練acc, 検証acc, 訓練acc(Binary), 検証acc(Binary), 訓練acc(Macro), 検証acc(Macro), \n # 訓練f1(Binary), 検証f1(Binary), 訓練f1(Macro), 検証f1(Macro), 訓練最終スコア, 検証最終スコア","metadata":{},"outputs":[],"execution_count":null},{"id":"ed8b21d5","cell_type":"code","source":"history_tr = np.zeros((0, 12))\nhistory_val = np.zeros((0, 12))\n\nfor epoch in tqdm(range(NUM_EPOCHS)):\n\t# ===== 訓練モード =====\n\tnet.train()\n\trunning_loss_tr = 0.0 # 累積損失を格納\n\tpred_tr = [] # 予測値を格納\n\ttrue_tr = [] # 正解ラベルを格納\n\tfor X_tr_batch, y_tr_batch in tr_loader: #tqdm(tr_loader):\n\t\t# データをデバイスに転送\n\t\tX_tr_batch = X_tr_batch.to(device)\n\t\ty_tr_batch = y_tr_batch.to(device)\n\t\t# 勾配を初期化\n\t\toptimizer.zero_grad()\n\t\t# 順伝\n\t\toutput_batch = net(X_tr_batch)\n\t\t# 損失計算\n\t\tloss = criterion(output_batch, y_tr_batch)\n\t\t# 逆伝播\n\t\tloss.backward()\n\t\t# パラメータ更新\n\t\toptimizer.step()\n\t\t# ロスの蓄積\n\t\trunning_loss_tr += loss.item()\n\t\t_, pred_batch = torch.max(output_batch, 1) # 予測値を取得\n\t\tpred_tr.extend(pred_batch.cpu().numpy())\n\t\ttrue_tr.extend(y_tr_batch.cpu().numpy())\n\n\n\t# ===== 検証モード =====\n\tnet.eval()\n\trunning_loss_val = 0.0\n\tpred_val = []\n\ttrue_val = []\n\twith torch.no_grad():\n\t\tfor X_val_batch, y_val_batch in val_loader: #tqdm(val_loader): \n\t\t\t# データをデバイスに結合\n\t\t\tX_val_batch = X_val_batch.to(device)\n\t\t\ty_val_batch = y_val_batch.to(device)\n\t\t\t# 順伝播\n\t\t\toutput_batch = net(X_val_batch)\n\t\t\t# 損失計算\n\t\t\tloss = criterion(output_batch, y_val_batch)\n\t\t\t# 予測値を計算\n\t\t\trunning_loss_val += loss.item()\n\t\t\t_, pred_batch = torch.max(output_batch, 1)\n\t\t\tpred_val.extend(pred_batch.cpu().numpy())\n\t\t\ttrue_val.extend(y_val_batch.cpu().numpy())\n\n\t# 損失+正解率\n\tloss_tr = running_loss_tr / len(tr_loader)\n\tloss_val = running_loss_val / len(val_loader)\n\taccuracy_tr = (np.array(pred_tr)==np.array(true_tr)).mean()\n\taccuracy_val = (np.array(pred_val)==np.array(true_val)).mean()\n\n\t# スコア\n\tscores_tr = get_scores(true_tr, pred_tr)\n\tscores_val = get_scores(true_val, pred_val)\n\n\t# historyに保存\n\th_tr = [epoch+1, loss_tr, accuracy_tr] + scores_tr\n\th_val = [epoch+1, loss_val, accuracy_val] + scores_val\n\thistory_tr = np.vstack([history_tr, h_tr])\n\thistory_val = np.vstack([history_val, h_val])\n\n\t# 結果の表示\n\tprint(f'Epoch:{h_tr[0]}/{NUM_EPOCHS}')\n\tprint(f'[TRAIN]: loss:{h_tr[1]:.3f}, acc:{h_tr[2]:.2f}, acc_bin:{h_tr[3]:.2f}, acc_macro:{h_tr[4]:.2f}, f1_bin:{h_tr[-3]:.2f}, f1_macro:{h_tr[-2]:.2f}, final_score:{h_tr[-1]:.3f}')\n\tprint(f'[VALID]: loss:{h_val[1]:.3f}, acc:{h_val[2]:.2f}, acc_bin:{h_val[3]:.2f}, acc_macro:{h_val[4]:.2f}, f1_bin:{h_val[-3]:.2f}, f1_macro:{h_val[-2]:.2f}, final_score:{h_val[-1]:.3f}')\n\n\t# 学習率の調整\n\told_lr = optimizer.param_groups[0]['lr']\n\treduce_lr_on_plateau.step(h_val[-1]) # 指標がminかmaxか注意。\n\tnew_lr = optimizer.param_groups[0]['lr']\n\n\tif new_lr != old_lr:\n\t\tlog_reduce_lr(optimizer)\n\n\t# Early stopping check\n\tif early_stopping.step(h_val[-1], net):\n\t\tprint(\"Early stopping triggered.\")\n\t\tbreak\n\n# Save the model\ntorch.save(net.state_dict(), f\"{PATH_OUTPUT}trained_model_{VERSION}.pth\")\nprint(\"Training complete.\")","metadata":{},"outputs":[],"execution_count":null},{"id":"2f119d24","cell_type":"code","source":"# # コンペ指標を別の方法（cmi_2025_metric_copy_for_import.py）で計算する\n# from cmi_2025_metric_copy_for_import import CompetitionMetric\n\n# net.eval()  # Set the model to evaluation mode\n# y_val_pred_probs = []\n\n# with torch.no_grad():\n#     for inputs in val_loader:\n#         inputs = inputs[0].to(device)  # Move to the same device as the model\n#         outputs = net(inputs)  # Permute if necessary\n#         y_val_pred_probs.append(outputs.cpu().numpy())\n\n# y_val_pred_probs = np.concatenate(y_val_pred_probs, axis=0)\n# print(y_val_pred_probs.shape)\n\n# y_val_pred = np.argmax(y_val_pred_probs, axis=1)\n# print(y_val_pred[:10])  # Display first 10 predictions\n\n# y_val_true = y_val\n# print(y_val_true[:10])  # Display first 10 true labels\n\n# val_pred_labels = pd.Series(y_val_pred).map(lambda i: GESTURE[i])\n# val_true_labels = pd.Series(y_val_true).map(lambda i: GESTURE[i])\n\n# # Build DataFrames for the metric\n# val_submission = pd.DataFrame({'gesture': val_pred_labels})\n# val_solution = pd.DataFrame({'gesture': val_true_labels})\n\n# # Run competition metric\n# metric = CompetitionMetric()\n# score = metric.calculate_hierarchical_f1(val_solution, val_submission)\n# print(f\"Estimated leaderboard (val) score: {score:.4f}\")","metadata":{},"outputs":[],"execution_count":null},{"id":"b94f57c1","cell_type":"code","source":"def show_learning_curve(history_tr, history_val, ylabel, n_col):\n\t\"\"\"\n\t学習曲線を描画する関数\n\n\tParameters\n\t------------------\n\thistory_tr (np.array): 訓練結果が格納されたnumpy配列\n\thistory_val (np.array): 検証結果が格納されたnumpy配列\n\tylabel (str): y軸のラベル（タイトルにも使われる）\n\tn_col (int): データの列番号\n\n\tReturn\n\t------------------\n\tNone\n\n\tDisplay\n\t------------------\n\t学習曲線を表示\n\t\"\"\"\n\tticks_interval = 10 #num_epochs // 10\n\tnum_epochs = len(history_tr)\n\n\tplt.figure(figsize=(8, 8))\n\tplt.plot(history_tr[:, 0], history_tr[:, n_col], label='Train')\n\tplt.plot(history_val[:, 0], history_val[:, n_col], label='Validation')\n\tplt.xlabel('Epochs')\n\tplt.ylabel(ylabel)\n\tplt.title(f'Learning curve ({ylabel})')\n\tplt.legend()\n\tplt.grid()\n\tplt.show()","metadata":{},"outputs":[],"execution_count":null},{"id":"f8a25dd6","cell_type":"code","source":"show_learning_curve(history_tr, history_val, 'Loss', 1)","metadata":{},"outputs":[],"execution_count":null},{"id":"2d47d2cb","cell_type":"code","source":"show_learning_curve(history_tr, history_val, 'Accuracy(18 classes)', 2)\nshow_learning_curve(history_tr, history_val, 'Accuracy(Binary)', 3)\nshow_learning_curve(history_tr, history_val, 'Accuracy(Macro)', 4)\n\nshow_learning_curve(history_tr, history_val, 'F1(Binary)', -3)\nshow_learning_curve(history_tr, history_val, 'F1(Macro)', -2)\nshow_learning_curve(history_tr, history_val, 'Final Score', -1)","metadata":{},"outputs":[],"execution_count":null},{"id":"693518bf","cell_type":"markdown","source":"# Evaluation API","metadata":{}},{"id":"88e28d97","cell_type":"code","source":"def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n\ttry:\n\t\t# pandasに変換\n\t\tdata = sequence.to_pandas()\n\n\t\t# データの前処理\n\t\tdata = filling(data)\n\t\tdata, _ = standardize(data, FEATURES, fit=False, scaler=scaler)  # 標準化（訓練時にfitしたスケーラーを使用）\n\t\tX_subm, _, _, _, meta_subm = data_shaping(data, y_boolean=False)\n\t\tX_subm_trim = trim_sequence(X_subm)\n\n\t\t# データローダーの作成\n\t\tsubm_dataset = TimeSeriesDataset(X_subm_trim, [0]*len(X_subm_trim))  # ラベルは不要なのでダミーを使用\n\t\tsubm_loader = DataLoader(subm_dataset, batch_size=BATCH_SIZE, shuffle=False)\n\n\t\t# 予測\n\t\tnet.eval()\n\t\twith torch.no_grad():\n\t\t\tfor X_subm_batch, _ in subm_loader:\n\t\t\t\tX_subm_batch = X_subm_batch.to(device)\n\t\t\t\toutput_batch = net(X_subm_batch)\n\t\t\t\t_, pred_batch = torch.max(output_batch, 1)\n\t\t\t\tpred_subm = pred_batch.cpu().numpy()\n\t\t\tpred_gesture = GESTURE[pred_subm[0]]\n\t\t\t\t\n\texcept Exception as e:\n\t\tprint(e.__class__.__name__)\n\t\tprint(e.args)\n\t\tprint(e)\n\t\tprint(f\"{e.__class__.__name__}: {e}\")\n\t\tpred_gesture = 'Text on phone'\n\n\tprint(pred_gesture)\n\treturn pred_gesture\n","metadata":{},"outputs":[],"execution_count":null},{"id":"83d9de12","cell_type":"code","source":"# Launch inference server\ninference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\n    inference_server.run_local_gateway(\n        data_paths=(\n            f'{PATH}/test.csv',\n            f'{PATH}/test_demographics.csv',\n\t\t\t# '/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv',\n\t\t\t# '/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv',\n        )\n    )","metadata":{},"outputs":[],"execution_count":null},{"id":"51fe60e4","cell_type":"code","source":"end = datetime.datetime.now()\nprint(f\"Start time: \\t{start.strftime('%Y-%m-%d %H:%M:%S')}\")\nprint(f\"End time: \\t{end.strftime('%Y-%m-%d %H:%M:%S')}\")","metadata":{},"outputs":[],"execution_count":null},{"id":"5a0e3bfb","cell_type":"code","source":"","metadata":{},"outputs":[],"execution_count":null}]}