OSCR

Sleep awake detection from leg-worn wearables using deep sensor fusion.

Code ↔ Paper

8 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 8 matches
  1. [1] § Methods › Model architecture ↔ Train/model.py, lines 11–86 · score 0.83 · fully connected layers, global max, Conv2D, BiLSTM, channel, binary
  2. [2] § Methods › Model architecture › Regularization and overfitting control ↔ Train/model.py, lines 110–159 · score 0.82 · Global Max, LeakyReLU, dense layers, Batch Normalization, dropout rate, block
  3. [3] § Methods › Model training and evaluation › Learning rate and loss function ↔ Train/model.py, lines 11–86 · score 0.75 · cosine decay, learning rate scheduler, decay step, Adam, optimized, loss
  4. [4] § Methods › Signal preprocessing ↔ Train/ppg_preprocess.py, lines 136–218 · score 0.74 · noise power, Signal power, detected peak, spectral, SNR, band
  5. [5] § Methods › Model architecture ↔ Train/Late_Fusion.ipynb, lines 1–38 · score 0.58 · late fusion architecture, separate sub, tensor, concatenating, classification, sensor
  6. [6] § Methods › Model architecture ↔ Train/ppg_preprocess.py, lines 422–484 · score 0.56 · standard deviation, heart rate, interval, HRV, signals
  7. [7] § Results ↔ Train/Late_Fusion.ipynb, lines 269–332 · score 0.56 · Receiver Operating Characteristic, ROC AUC, Curve, sensitivity, fold, windows
  8. [8] § Methods › Neighborhood majority voting-based label smoothing ↔ Train/Late_Fusion.ipynb, lines 269–332 · score 0.51 · neighboring window, refine, majority, probabilities, smoothing, threshold

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 701 lines · 27 KB · no license · 3 matches

  1. import tensorflow as tf
  2. from tensorflow.keras import layers, models, Input, Model
  3. import numpy as np
  4. from tensorflow.keras.layers import Input, Lambda
  5. from tensorflow.keras.optimizers import Adam
  6. #import tensorflow_addons as tfa
  7. from tensorflow.keras.utils import plot_model
  8. from tensorflow import keras
  9. def build_stacked_model(input_shape, num_classes, model_config):
  10. """
  11. Builds a classification model using early fusion with BiLSTM for sensor data.
  12. Args:
  13. input_shape (tuple): Shape of the input data after stacking (e.g., (1500, 3, 3)).
  14. model_config (dict): Dictionary containing all the model configurations and hyperparameters.
  15. Returns:
  16. tf.keras.Model: The compiled classification model.
  17. """
  18. inputs = Input(shape=input_shape)
  19. x = layers.Conv2D(model_config['num_filters_1'], (3, 3), padding='same',
  20. kernel_regularizer=tf.keras.regularizers.l2(model_config['l2_regularization']),
  21. kernel_initializer='he_normal')(inputs)
  22. x = layers.BatchNormalization()(x)
  23. x = layers.LeakyReLU()(x)
  24. x = layers.Conv2D(model_config['num_filters_2'], (3, 3), padding='same',
  25. kernel_regularizer=tf.keras.regularizers.l2(model_config['l2_regularization']),
  26. kernel_initializer='he_normal')(x)
  27. x = layers.BatchNormalization()(x)
  28. x = layers.LeakyReLU()(x)
  29. x = layers.MaxPooling2D(pool_size=(2, 2))(x)
  30. x = layers.Dropout(model_config['dropout_rate'])(x)
  31. x = layers.Conv2D(model_config['num_filters_3'], (3, 3), padding='same',
  32. kernel_regularizer=tf.keras.regularizers.l2(model_config['l2_regularization']),
  33. kernel_initializer='he_normal')(x)
  34. x = layers.BatchNormalization()(x)
  35. x = layers.LeakyReLU()(x)
  36. x = layers.MaxPooling2D(pool_size=(2, 1))(x)
  37. x = layers.Dropout(model_config['dropout_rate'])(x)
  38. # Reshape for BiLSTM
  39. # Convert to shape compatible with LSTM input: (batch_size, time_steps, features)
  40. # _, time_steps, features, channels = x.shape.as_list()
  41. _, time_steps, features, channels = list(x.shape)
  42. reshaped = layers.Reshape((time_steps, features * channels))(x)
  43. # BiLSTM layers for temporal dependency extraction
  44. x = layers.Bidirectional(layers.LSTM(model_config['lstm_units'], return_sequences=True))(reshaped)
  45. x = layers.GlobalMaxPooling1D()(x)
  46. # Fully connected layers
  47. x = layers.Dense(model_config['dense_units'], activation='relu')(x)
  48. x = layers.Dropout(model_config['dropout_rate'])(x)
  49. output = layers.Dense(1, activation='sigmoid', dtype='float32')(x)
  50. # Compile the model
  51. initial_learning_rate = model_config['lr']
  52. decay_steps = model_config['lr_decay_steps']
  53. # Learning rate schedule
  54. lr_schedule = tf.keras.optimizers.schedules.CosineDecay(
  55. initial_learning_rate=initial_learning_rate,
  56. decay_steps=decay_steps,
  57. alpha=model_config['lr_alpha']
  58. )
  59. optimizer = Adam(learning_rate=lr_schedule)
  60. # Loss function
  61. loss_fn = tf.keras.losses.BinaryFocalCrossentropy(gamma=model_config['loss_gamma'], alpha=model_config['loss_alpha'])
  62. # Create and compile the model
  63. model = Model(inputs=inputs, outputs=output)
  64. model.compile(optimizer=optimizer,
  65. loss=loss_fn,
  66. metrics=[keras.metrics.AUC(),
  67. keras.metrics.SpecificityAtSensitivity(0.99),
  68. keras.metrics.SensitivityAtSpecificity(0.99)])
  69. return model
  70. def attention_block(inputs):
  71. attention_probs = layers.Dense(inputs.shape[-1], activation='softmax')(inputs)
  72. attention_mul = layers.multiply([inputs, attention_probs])
  73. return attention_mul
  74. def transformer_block(inputs, num_heads, ff_dim, dropout_rate):
  75. # Multi-head self-attention
  76. attention_output = layers.MultiHeadAttention(num_heads=num_heads, key_dim=inputs.shape[-1])(inputs, inputs)
  77. # Add & normalize
  78. attention_output = layers.Dropout(dropout_rate)(attention_output)
  79. attention_output = layers.LayerNormalization(epsilon=1e-6)(attention_output + inputs)
  80. # Feed-forward network
  81. ff_output = layers.Dense(ff_dim, activation="relu")(attention_output)
  82. ff_output = layers.Dense(inputs.shape[-1])(ff_output)
  83. # Add & normalize
  84. ff_output = layers.Dropout(dropout_rate)(ff_output)
  85. output = layers.LayerNormalization(epsilon=1e-6)(ff_output + attention_output)
  86. return output
  87. def build_sensor_model_2d_TRANS(input_shape, model_config):
  88. """
  89. Builds a sub-model for processing sensor data.
  90. Args:
  91. input_shape (tuple): Shape of the input data for the sensor.
  92. model_config (dict): Dictionary containing all the model configurations and hyperparameters.
  93. Returns:
  94. tf.keras.Model: The sub-model for the sensor.
  95. """
  96. inputs = Input(shape=input_shape)
  97. x = layers.Conv2D(model_config['num_filters_1'], model_config['kernel_size_1'], padding='same',
  98. kernel_regularizer=tf.keras.regularizers.l2(model_config['l2_regularization']),
  99. kernel_initializer='he_normal')(inputs)
  100. x = layers.BatchNormalization()(x)
  101. x = layers.LeakyReLU()(x)
  102. x = layers.Conv2D(model_config['num_filters_2'], model_config['kernel_size_2'], padding='same',
  103. kernel_regularizer=tf.keras.regularizers.l2(model_config['l2_regularization']),
  104. kernel_initializer='he_normal')(x)
  105. x = layers.BatchNormalization()(x)
  106. x = layers.LeakyReLU()(x)
  107. x = layers.MaxPooling2D(model_config['pooling_size'])(x)
  108. x = layers.Dropout(model_config['dropout_rate'])(x)
  109. x = layers.Conv2D(model_config['num_filters_3'], model_config['kernel_size_2'], padding='same',
  110. kernel_regularizer=tf.keras.regularizers.l2(model_config['l2_regularization']),
  111. kernel_initializer='he_normal')(x)
  112. x = layers.BatchNormalization()(x)
  113. x = layers.LeakyReLU()(x)
  114. x = layers.MaxPooling2D(model_config['pooling_size'])(x)
  115. x = layers.Dropout(model_config['dropout_rate'])(x)
  116. batch_size, height, width, channels = x.shape.as_list()
  117. new_shape = (height, width * channels)
  118. x = layers.Reshape(new_shape)(x)
  119. # Transformer block
  120. x = transformer_block(x, num_heads=model_config['num_heads'],
  121. ff_dim=model_config['ff_dim'], dropout_rate=model_config['dropout_rate'])
  122. # LSTM block or dense layers
  123. x = layers.Bidirectional(layers.LSTM(model_config['lstm_units'], return_sequences=True))(x)
  124. x = layers.GlobalMaxPooling1D()(x)
  125. return Model(inputs, x)
  126. def build_sensor_model_2d_2(input_shape, model_config):
  127. """
  128. Builds a sub-model for processing sensor data.
  129. Args:
  130. input_shape (tuple): Shape of the input data for the sensor.
  131. model_config (dict): Dictionary containing all the model configurations and hyperparameters.
  132. Returns:
  133. tf.keras.Model: The sub-model for the sensor.
  134. """
  135. inputs = Input(shape=input_shape)
  136. x = layers.Conv2D(model_config['num_filters_1'], model_config['kernel_size_1'], padding='same',
  137. kernel_regularizer=tf.keras.regularizers.l2(model_config['l2_regularization']),
  138. kernel_initializer='he_normal')(inputs)
  139. x = layers.BatchNormalization()(x)
  140. x = layers.LeakyReLU()(x)
  141. x = layers.Conv2D(model_config['num_filters_2'], model_config['kernel_size_2'], padding='same',
  142. kernel_regularizer=tf.keras.regularizers.l2(model_config['l2_regularization']),
  143. kernel_initializer='he_normal')(x)
  144. x = layers.BatchNormalization()(x)
  145. x = layers.LeakyReLU()(x)
  146. x = layers.MaxPooling2D(model_config['pooling_size'])(x)
  147. # Removed Dropout here to avoid applying it on a 4D tensor
  148. x = layers.Conv2D(model_config['num_filters_3'], model_config['kernel_size_2'], padding='same',
  149. kernel_regularizer=tf.keras.regularizers.l2(model_config['l2_regularization']),
  150. kernel_initializer='he_normal')(x)
  151. x = layers.BatchNormalization()(x)
  152. x = layers.LeakyReLU()(x)
  153. x = layers.MaxPooling2D(model_config['pooling_size'])(x)
  154. # Removed Dropout here as well
  155. # Attention block
  156. # x = attention_block(x)
  157. # Use Reshape to prepare for LSTM layer
  158. shape_before_lstm = x.shape[1] * x.shape[2] # Combine height and width
  159. x = layers.Reshape((shape_before_lstm, x.shape[-1]))(x)
  160. x = layers.Dropout(model_config['dropout_rate'])(x) # Apply Dropout after Reshape
  161. x = layers.Bidirectional(layers.LSTM(model_config['lstm_units'], return_sequences=True))(x)
  162. x = layers.GlobalMaxPooling1D()(x)
  163. return Model(inputs, x)
  164. def build_sensor_model_2d_sm_2(input_shape, model_config):
  165. """
  166. Builds a sub-model for processing sensor data with simpler structure.
  167. Args:
  168. input_shape (tuple): Shape of the input data for the sensor.
  169. model_config (dict): Dictionary containing all the model configurations and hyperparameters.
  170. Returns:
  171. tf.keras.Model: The sub-model for the sensor.
  172. """
  173. inputs = Input(shape=input_shape)
  174. x = layers.Conv2D(model_config['num_filters_1_sm'], model_config['kernel_size_1_sm'], padding='same',
  175. kernel_regularizer=tf.keras.regularizers.l2(model_config['l2_regularization']),
  176. kernel_initializer='he_normal')(inputs)
  177. x = layers.BatchNormalization()(x)
  178. x = layers.LeakyReLU()(x)
  179. x = layers.MaxPooling2D(model_config['pooling_size'])(x)
  180. # Removed Dropout here as well
  181. # Flatten and Reshape to prepare for Dense and GlobalMaxPooling1D
  182. x = layers.Flatten()(x)
  183. x = layers.Dense(32, activation='relu')(x)
  184. # Reshape to add a dimension for compatibility with GlobalMaxPooling1D
  185. x = layers.Reshape((32, 1))(x) # Reshaping to (batch, 32, 1)
  186. x = layers.GlobalMaxPooling1D()(x)
  187. return Model(inputs, x)
  188. def build_sensor_model_2d(input_shape, model_config):
  189. """
  190. Builds a sub-model for processing sensor data.
  191. Args:
  192. input_shape (tuple): Shape of the input data for the sensor.
  193. model_config (dict): Dictionary containing all the model configurations and hyperparameters.
  194. Returns:
  195. tf.keras.Model: The sub-model for the sensor.
  196. """
  197. inputs = Input(shape=input_shape)
  198. x = layers.Conv2D(model_config['num_filters_1'], model_config['kernel_size_1'], padding='same',
  199. kernel_regularizer=tf.keras.regularizers.l2(model_config['l2_regularization']),
  200. kernel_initializer='he_normal')(inputs)
  201. x = layers.BatchNormalization()(x)
  202. x = layers.LeakyReLU()(x)
  203. x = layers.Conv2D(model_config['num_filters_2'], model_config['kernel_size_2'], padding='same',
  204. kernel_regularizer=tf.keras.regularizers.l2(model_config['l2_regularization']),
  205. kernel_initializer='he_normal')(x)
  206. x = layers.BatchNormalization()(x)
  207. x = layers.LeakyReLU()(x)
  208. x = layers.MaxPooling2D(model_config['pooling_size'])(x)
  209. x = layers.Dropout(model_config['dropout_rate'])(x)
  210. x = layers.Conv2D(model_config['num_filters_3'], model_config['kernel_size_2'], padding='same',
  211. kernel_regularizer=tf.keras.regularizers.l2(model_config['l2_regularization']),
  212. kernel_initializer='he_normal')(x)
  213. x = layers.BatchNormalization()(x)
  214. x = layers.LeakyReLU()(x)
  215. x = layers.MaxPooling2D(model_config['pooling_size'])(x)
  216. x = layers.Dropout(model_config['dropout_rate'])(x)
  217. # Attention block
  218. #x = attention_block(x)
  219. batch_size, height, width, channels = x.shape.as_list()
  220. new_shape = (height, width * channels)
  221. x = layers.Reshape(new_shape)(x)
  222. x = layers.Dropout(model_config['dropout_rate'])(x)
  223. x = layers.Bidirectional(layers.LSTM(model_config['lstm_units'], return_sequences=True))(x)
  224. x = layers.GlobalMaxPooling1D()(x)
  225. return Model(inputs, x)
  226. def build_sensor_model_2d_sm(input_shape, model_config):
  227. """
  228. Builds a sub-model for processing sensor data.
  229. Args:
  230. input_shape (tuple): Shape of the input data for the sensor.
  231. model_config (dict): Dictionary containing all the model configurations and hyperparameters.
  232. Returns:
  233. tf.keras.Model: The sub-model for the sensor.
  234. """
  235. inputs = Input(shape=input_shape)
  236. x = layers.Conv2D(model_config['num_filters_1_sm'], model_config['kernel_size_1_sm'], padding='same',
  237. kernel_regularizer=tf.keras.regularizers.l2(model_config['l2_regularization']),
  238. kernel_initializer='he_normal')(inputs)
  239. x = layers.BatchNormalization()(x)
  240. x = layers.LeakyReLU()(x)
  241. x = layers.MaxPooling2D(model_config['pooling_size'])(x)
  242. x = layers.Dropout(model_config['dropout_rate'])(x)
  243. # Attention block
  244. #x = attention_block(x)
  245. batch_size, height, width, channels = x.shape.as_list()
  246. new_shape = (height, width * channels)
  247. x = layers.Reshape(new_shape)(x)
  248. x = layers.Dropout(model_config['dropout_rate'])(x)
  249. x = layers.Dense(32, activation='relu')(x)
  250. #x = layers.Bidirectional(layers.LSTM(model_config['lstm_units_sm'], return_sequences=True))(x)
  251. x = layers.GlobalMaxPooling1D()(x)
  252. return Model(inputs, x)
  253. def build_combined_model(input_shapes, num_classes, model_config): # late fusion model architecture
  254. """
  255. Builds a classification model that combines data from three sensors.
  256. Args:
  257. input_shapes (list of tuples): Shapes of the input data for each sensor.
  258. num_classes (int): Number of output classes.
  259. model_config (dict): Dictionary containing all the model configurations and hyperparameters.
  260. Returns:
  261. tf.keras.Model: The compiled classification model.
  262. """
  263. # Create sub-models for each sensor
  264. # sensor1_model = build_sensor_model_2d(input_shapes[0], model_config)
  265. # sensor2_model = build_sensor_model_2d(input_shapes[1], model_config)
  266. # sensor3_model = build_sensor_model_2d(input_shapes[2], model_config)
  267. # sensor4_model = build_sensor_model_2d_sm(input_shapes[3], model_config)
  268. sensor1_model = build_sensor_model_2d_2(input_shapes[0], model_config)
  269. sensor2_model = build_sensor_model_2d_2(input_shapes[1], model_config)
  270. sensor3_model = build_sensor_model_2d_2(input_shapes[2], model_config)
  271. sensor4_model = build_sensor_model_2d_sm_2(input_shapes[3], model_config)
  272. #sensor1_model = build_sensor_model_2d_TRANS(input_shapes[0], model_config)
  273. #sensor2_model = build_sensor_model_2d_TRANS(input_shapes[1], model_config)
  274. #sensor3_model = build_sensor_model_2d_TRANS(input_shapes[2], model_config)
  275. #sensor4_model = build_sensor_model_2d_sm(input_shapes[3], model_config)
  276. # Define inputs for each sensor
  277. sensor1_input = Input(shape=input_shapes[0])
  278. sensor2_input = Input(shape=input_shapes[1])
  279. sensor3_input = Input(shape=input_shapes[2])
  280. sensor4_input = Input(shape=input_shapes[3])
  281. # Get the outputs from each sub-model
  282. sensor1_output = sensor1_model(sensor1_input)
  283. sensor2_output = sensor2_model(sensor2_input)
  284. sensor3_output = sensor3_model(sensor3_input)
  285. sensor4_output = sensor4_model(sensor4_input)
  286. # Concatenate the outputs
  287. concatenated = layers.concatenate([sensor1_output, sensor2_output, sensor3_output, sensor4_output])
  288. # Add Dense and Dropout layers
  289. x = layers.Dense(model_config['dense_units'], activation='relu')(concatenated)
  290. x = layers.Dropout(model_config['dropout_rate'])(x)
  291. output = layers.Dense(1, activation='sigmoid', dtype='float32')(x)
  292. #output = layers.Dense(1)(x)
  293. initial_learning_rate = model_config['lr']
  294. decay_steps = model_config['lr_decay_steps']
  295. # Create the learning rate schedule
  296. lr_schedule = tf.keras.optimizers.schedules.CosineDecay(
  297. initial_learning_rate=initial_learning_rate,
  298. decay_steps=decay_steps,
  299. alpha=model_config['lr_alpha']
  300. )
  301. optimizer = Adam(learning_rate=lr_schedule)
  302. # Create and compile the model
  303. model = Model(inputs=[sensor1_input, sensor2_input, sensor3_input, sensor4_input], outputs=output)
  304. #loss_fn = tf.keras.losses.BinaryCrossentropy()
  305. loss_fn = tf.keras.losses.BinaryFocalCrossentropy(gamma=model_config['loss_gamma'], alpha=model_config['loss_alpha'])
  306. model.compile(optimizer=optimizer,
  307. loss=loss_fn,
  308. metrics=[keras.metrics.AUC(),
  309. keras.metrics.SpecificityAtSensitivity(0.99),
  310. keras.metrics.SensitivityAtSpecificity(0.99)]
  311. )
  312. return model
  313. def build_combined_model_removed_1(input_shapes, num_classes, model_config):
  314. """
  315. Builds a classification model that combines data from three sensors.
  316. Args:
  317. input_shapes (list of tuples): Shapes of the input data for each sensor.
  318. num_classes (int): Number of output classes.
  319. model_config (dict): Dictionary containing all the model configurations and hyperparameters.
  320. Returns:
  321. tf.keras.Model: The compiled classification model.
  322. """
  323. # Create sub-models for each sensor
  324. # sensor1_model = build_sensor_model_2d(input_shapes[0], model_config)
  325. # sensor2_model = build_sensor_model_2d(input_shapes[1], model_config)
  326. # sensor3_model = build_sensor_model_2d(input_shapes[2], model_config)
  327. # sensor4_model = build_sensor_model_2d_sm(input_shapes[3], model_config)
  328. sensor1_model = build_sensor_model_2d_2(input_shapes[0], model_config)
  329. sensor2_model = build_sensor_model_2d_2(input_shapes[1], model_config)
  330. sensor3_model = build_sensor_model_2d_2(input_shapes[2], model_config)
  331. sensor4_model = build_sensor_model_2d_sm_2(input_shapes[3], model_config)
  332. #sensor1_model = build_sensor_model_2d_TRANS(input_shapes[0], model_config)
  333. #sensor2_model = build_sensor_model_2d_TRANS(input_shapes[1], model_config)
  334. #sensor3_model = build_sensor_model_2d_TRANS(input_shapes[2], model_config)
  335. #sensor4_model = build_sensor_model_2d_sm(input_shapes[3], model_config)
  336. # Define inputs for each sensor
  337. sensor1_input = Input(shape=input_shapes[0])
  338. sensor2_input = Input(shape=input_shapes[1])
  339. sensor3_input = Input(shape=input_shapes[2])
  340. sensor4_input = Input(shape=input_shapes[3])
  341. # Get the outputs from each sub-model
  342. sensor1_output = sensor1_model(sensor1_input)
  343. # sensor2_output = sensor2_model(sensor2_input)
  344. # sensor3_output = sensor3_model(sensor3_input)
  345. # sensor4_output = sensor4_model(sensor4_input)
  346. # Concatenate the outputs
  347. # concatenated = layers.concatenate([sensor1_output, sensor2_output, sensor3_output, sensor4_output])
  348. concatenated = sensor1_output
  349. # Add Dense and Dropout layers
  350. x = layers.Dense(model_config['dense_units'], activation='relu')(concatenated)
  351. x = layers.Dropout(model_config['dropout_rate'])(x)
  352. output = layers.Dense(1, activation='sigmoid', dtype='float32')(x)
  353. #output = layers.Dense(1)(x)
  354. initial_learning_rate = model_config['lr']
  355. decay_steps = model_config['lr_decay_steps']
  356. # Create the learning rate schedule
  357. lr_schedule = tf.keras.optimizers.schedules.CosineDecay(
  358. initial_learning_rate=initial_learning_rate,
  359. decay_steps=decay_steps,
  360. alpha=model_config['lr_alpha']
  361. )
  362. optimizer = Adam(learning_rate=lr_schedule)
  363. # Create and compile the model
  364. model = Model(inputs=[sensor1_input, sensor2_input, sensor3_input, sensor4_input], outputs=output)
  365. #loss_fn = tf.keras.losses.BinaryCrossentropy()
  366. loss_fn = tf.keras.losses.BinaryFocalCrossentropy(gamma=model_config['loss_gamma'], alpha=model_config['loss_alpha'])
  367. model.compile(optimizer=optimizer,
  368. loss=loss_fn,
  369. metrics=[keras.metrics.AUC(),
  370. keras.metrics.SpecificityAtSensitivity(0.99),
  371. keras.metrics.SensitivityAtSpecificity(0.99)]
  372. )
  373. return model
  374. def feature_model_1D(input_dim,model_config):
  375. inputs = Input(shape=(input_dim,), name="input_features")
  376. # First Dense Layer
  377. x = tf.keras.layers.Dense(128, activation='relu', name="dense_1")(inputs)
  378. x = tf.keras.layers.BatchNormalization(name="batch_norm_1")(x)
  379. x = tf.keras.layers.Dropout(0.3, name="dropout_1")(x)
  380. # Second Dense Layer
  381. x = tf.keras.layers.Dense(64, activation='relu', name="dense_2")(x)
  382. x = tf.keras.layers.BatchNormalization(name="batch_norm_2")(x)
  383. x = tf.keras.layers.Dropout(0.3, name="dropout_2")(x)
  384. # Third Dense Layer
  385. x = tf.keras.layers.Dense(32, activation='relu', name="dense_3")(x)
  386. x = tf.keras.layers.BatchNormalization(name="batch_norm_3")(x)
  387. x = tf.keras.layers.Dropout(0.3, name="dropout_3")(x)
  388. # Output Layer
  389. outputs = tf.keras.layers.Dense(1, activation='sigmoid', name="output")(x)
  390. # Create the model
  391. model = tf.keras.Model(inputs=inputs, outputs=outputs, name="sleep_awake_classifier")
  392. loss_fn = tf.keras.losses.BinaryFocalCrossentropy(gamma=3, alpha=0.9)
  393. model.compile(
  394. optimizer=tf.keras.optimizers.Adam(learning_rate=model_config['lr']),
  395. loss=loss_fn,
  396. metrics=[keras.metrics.AUC(),
  397. keras.metrics.SpecificityAtSensitivity(0.99),
  398. keras.metrics.SensitivityAtSpecificity(0.99)]
  399. )
  400. return model
  401. def wrap_model_for_single_input(original_model, input_shapes):
  402. """
  403. Wraps a multi-input model to accept a single concatenated input tensor.
  404. Args:
  405. original_model (tf.keras.Model): The original multi-input model.
  406. input_shapes (list of tuples): Shapes of the inputs for the original model.
  407. Returns:
  408. tf.keras.Model: The wrapped model with a single input tensor.
  409. """
  410. # Calculate the total number of features in the concatenated input
  411. total_features = sum(np.prod(shape) for shape in input_shapes)
  412. # Define a new single input tensor
  413. single_input = Input(shape=(total_features,))
  414. # Split the single input tensor into the original input tensors
  415. split_tensors = []
  416. start_idx = 0
  417. for shape in input_shapes:
  418. num_features = np.prod(shape)
  419. end_idx = start_idx + num_features
  420. # Extract the slice corresponding to this input and reshape
  421. slice_tensor = single_input[..., start_idx:end_idx]
  422. reshaped_tensor = tf.reshape(slice_tensor, [-1, *shape]) # Ensure correct reshaping
  423. split_tensors.append(reshaped_tensor)
  424. start_idx = end_idx
  425. # Pass the split inputs to the original model
  426. output = original_model(split_tensors)
  427. # Create and return the wrapped model
  428. wrapped_model = Model(inputs=single_input, outputs=output)
  429. return wrapped_model
  430. if __name__ == '__main__':
  431. # input_shapes = (1500,3,3)
  432. # num_classes = 2 # Adjust as per your number of classes
  433. # model_config = {
  434. # 'lr': 0.001,
  435. # 'num_filters_1': 8,
  436. # 'num_filters_2': 16,
  437. # 'num_filters_3':32,
  438. # 'num_filters_4':64,
  439. # 'num_filters_1_sm': 8,
  440. # 'num_filters_2_sm': 16,
  441. # 'kernel_size_1': (3, 1),
  442. # 'kernel_size_2': (3, 1),
  443. # 'kernel_size_1_sm': (2, 1),
  444. # 'lstm_units': 32,
  445. # 'dropout_rate': 0.2,
  446. # 'dense_units': 64,
  447. # 'l2_regularization': 0.001,
  448. # 'pooling_size': (2, 1),
  449. # 'num_heads': 8, # Number of heads in Transformer block
  450. # 'ff_dim': 128, # Feed-forward layer size in Transformer block
  451. # 'loss_gamma': 4,
  452. # 'loss_alpha': 0.2,
  453. # 'lr_alpha': 0.5,
  454. # 'lr_decay_steps': 12100
  455. # }
  456. # model_config = {
  457. # 'lr': 0.001,
  458. # 'num_filters_1': 16,
  459. # 'num_filters_2': 32,
  460. # 'num_filters_3':64,
  461. # 'dropout_rate': 0.3,
  462. # 'dense_units': 128,
  463. # 'l2_regularization': 0.0001,
  464. # 'loss_gamma': 4,
  465. # 'loss_alpha': 0.2,
  466. # 'lr_alpha': 0.1,
  467. # 'lr_decay_steps': 12100
  468. # }
  469. # model = build_stacked_model(input_shapes, num_classes, model_config)
  470. # model.summary()
  471. # random_data = np.random.rand(1, 1500,3,3).astype(np.float32)
  472. # output = model.predict(random_data)
  473. # # Define input shape and number of classes
  474. ppg_input_shape = (1500, 3, 1) # Adjust as per your data
  475. gyro_input_shape = (1500, 3, 1) # Adjust as per your data
  476. acc_input_shape = (1500, 3, 1) # Adjust as per your data
  477. temp_input_shape = (1500, 3, 1)
  478. input_shapes = [ppg_input_shape, gyro_input_shape, acc_input_shape, temp_input_shape]
  479. num_classes = 2 # Adjust as per your number of classes
  480. model_config = {
  481. 'lr': 0.001,
  482. 'num_filters_1': 8,
  483. 'num_filters_2': 16,
  484. 'num_filters_3':32,
  485. 'num_filters_4':64,
  486. 'num_filters_1_sm': 8,
  487. 'num_filters_2_sm': 16,
  488. 'kernel_size_1': (3, 1),
  489. 'kernel_size_2': (3, 1),
  490. 'kernel_size_1_sm': (2, 1),
  491. 'lstm_units': 32,
  492. 'dropout_rate': 0.2,
  493. 'dense_units': 64,
  494. 'l2_regularization': 0.001,
  495. 'pooling_size': (2, 1),
  496. 'num_heads': 8, # Number of heads in Transformer block
  497. 'ff_dim': 128, # Feed-forward layer size in Transformer block
  498. 'loss_gamma': 4,
  499. 'loss_alpha': 0.2,
  500. 'lr_alpha': 0.5,
  501. 'lr_decay_steps': 12100
  502. }
  503. # Build and summarize the model
  504. model = build_combined_model(input_shapes, num_classes, model_config)
  505. # Wrap the model for a single concatenated input
  506. wrapped_model = wrap_model_for_single_input(
  507. model,
  508. input_shapes=input_shapes
  509. )
  510. # Save the wrapped model
  511. wrapped_model.save('wrapped_model')
  512. converter = tf.lite.TFLiteConverter.from_saved_model('wrapped_model')
  513. converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS, tf.lite.OpsSet.SELECT_TF_OPS]
  514. converter._experimental_lower_tensor_list_ops = False
  515. tflite_model = converter.convert()
  516. try:
  517. tflite_model = converter.convert()
  518. # Save the TensorFlow Lite model
  519. with open('wrapped_model.tflite', 'wb') as f:
  520. f.write(tflite_model)
  521. print("TensorFlow Lite model conversion successful.")
  522. except Exception as e:
  523. print(f"Error during TFLite conversion: {e}")
  524. plot_model(model, to_file='model_plot.png', show_shapes=True, show_layer_names=True,expand_nested=True)
  525. model.summary()
  526. # # Create synthetic test input data
  527. # ppg_data = np.random.rand(1, *ppg_input_shape).astype(np.float32) # Batch size of 1
  528. # gyro_data = np.random.rand(1, *gyro_input_shape).astype(np.float32)
  529. # acc_data = np.random.rand(1, *acc_input_shape).astype(np.float32)
  530. # temp_data = np.random.rand(1, *temp_input_shape).astype(np.float32)
  531. # # Print shapes of the synthetic data
  532. # print("PPG data shape:", ppg_data.shape)
  533. # print("Gyro data shape:", gyro_data.shape)
  534. # print("Acc data shape:", acc_data.shape)
  535. # print("Temp data shape:", temp_data.shape)
  536. # # Pass the synthetic data through the model to get the output
  537. # output = model.predict([ppg_data, gyro_data, acc_data, temp_data])
  538. # #output = model.predict([ppg_data, gyro_data, acc_data])
  539. # # Print the model output
  540. # print("Model output:", output)
  541. # input_dim = 345 # Replace with the number of features in your dataset
  542. # model = feature_model_1D(input_dim, model_config)
  543. # model.summary()

model.py at commit d066920, no license · at the source

Overview

Authors: Yumna Anwar1, Kanika Bansal1,2, Murat Kucukosmanoglu3, Quang Dang1, Cody Feltch4, Justin Brooks1,3,4, Nilanjan Banerjee1,4
ORCID iDs: Cody Feltch
  1. Department of Computer Science and Electrical Engineering, University of Maryland Baltimore County,Baltimore, MD USA
  2. Humans in Complex Systems Division, US Army DEVCOM Army Research Laboratory,Aberdeen, MD USA
  3. D-Prime LLC, McLean, VA 22101 USA
  4. Tanzen Medical Inc.,Severna Park, MD 21146 USA
Journal: Scientific reports, volume 16, issue 1, article 9930
Dates: received 24 June 2025; accepted 25 February 2026; published online 12 March 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1038/s41598-026-42310-8 · PMID 41820476 · PMCID PMC13018206 · OpenAlex W7135013274
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: other (modality), human (organism), ADHD (population), methods / tools (subfield)
Methods: Spectral & time-frequency, Connectivity, Machine learning, Statistics, Preprocessing, Physiology & signal measures
Keywords: Sleep classification, ADHD, Wearable sensors, RestEaze, Deep learning, CNN–BiLSTM, Biomarkers, Health care, Medical research
MeSH: Attention Deficit Disorder with Hyperactivity*, Sleep*, Wakefulness*, Wearable Electronic Devices*, Accelerometry, Child, Convolutional Neural Networks, Deep Learning, Female, Humans, Leg, Male, Photoplethysmography, Polysomnography, Sleep Duration, Support Vector Machine (* major topic)
Topic: Sleep and related disorders (Experimental and Cognitive Psychology, Psychology), according to OpenAlex
Funding: NIH (1R43MH133495-01A1)
Citations: not cited yet (Europe PMC); 27 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repository

Its files are read in the Code ↔ Paper reader above, with 8 matches between paragraphs and lines of code.

Madeby7/Sleep-State-Detection

License: none: the authors keep all their rights
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: d0669200bfae7882b6457d48fb3fc41d04e49551, 23 February 2026
Languages: Python (3), Jupyter (1)
Size: 30 files, 4 scripts
Software Heritage: not archived
Found in: “Data availability”
Holds: README, 1 notebook
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (4 files), pandas (3 files), TensorFlow (3 files), Matplotlib (2 files), NeuroKit2 (2 files), scikit-learn (2 files), SciPy (2 files), Keras (1 file), Plotly (1 file), seaborn (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
5 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 4 scripts, each with its path and the digest of its content;
  • 8 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Code and data availability statement

The paper has a code and data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1038/s41598-026-42310-8.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 30 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 7 authors, 9 keywords, 16 MeSH terms, 1 funder, 23 references.

Cite

This paper

Anwar, Y., Bansal, K., Kucukosmanoglu, M., Dang, Q., Feltch, C., Brooks, J., & Banerjee, N. (2026). Sleep awake detection from leg-worn wearables using deep sensor fusion. Scientific reports, 16(1), 9930. https://doi.org/10.1038/s41598-026-42310-8

BibTeX

@article{anwar2026sleep,
author = {Anwar, Yumna and Bansal, Kanika and Kucukosmanoglu, Murat and Dang, Quang and Feltch, Cody and Brooks, Justin and Banerjee, Nilanjan},
title = {{Sleep awake detection from leg-worn wearables using deep sensor fusion}},
journal = {Scientific reports},
year = {2026},
month = mar,
volume = {16},
number = {1},
pages = {9930},
publisher = {Nature Publishing Group},
issn = {2045-2322},
doi = {10.1038/s41598-026-42310-8},
url = {https://doi.org/10.1038/s41598-026-42310-8},
pmid = {41820476},
pmcid = {PMC13018206}
}

RIS

TY - JOUR
AU - Anwar, Yumna
AU - Bansal, Kanika
AU - Kucukosmanoglu, Murat
AU - Dang, Quang
AU - Feltch, Cody
AU - Brooks, Justin
AU - Banerjee, Nilanjan
TI - Sleep awake detection from leg-worn wearables using deep sensor fusion
T2 - Scientific reports
J2 - Sci Rep
PY - 2026
DA - 2026/03/12
VL - 16
IS - 1
SP - 9930
SN - 2045-2322
PB - Nature Publishing Group
DO - 10.1038/s41598-026-42310-8
UR - https://doi.org/10.1038/s41598-026-42310-8
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41598-026-42310-8",
"type": "article-journal",
"title": "Sleep awake detection from leg-worn wearables using deep sensor fusion",
"container-title": "Scientific reports",
"author": [
{
"family": "Anwar",
"given": "Yumna"
},
{
"family": "Bansal",
"given": "Kanika"
},
{
"family": "Kucukosmanoglu",
"given": "Murat"
},
{
"family": "Dang",
"given": "Quang"
},
{
"family": "Feltch",
"given": "Cody"
},
{
"family": "Brooks",
"given": "Justin"
},
{
"family": "Banerjee",
"given": "Nilanjan"
}
],
"container-title-short": "Sci Rep",
"volume": "16",
"issue": "1",
"page": "9930",
"DOI": "10.1038/s41598-026-42310-8",
"PMID": "41820476",
"PMCID": "PMC13018206",
"ISSN": "2045-2322",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41598-026-42310-8",
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
12
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1093/sleepadvances/zpag051 [code]
What matters beyond model choice for wearable sleep staging? How personalization, evaluation choices, and easy-to-classify wake impact performance.
Journal: Sleep advances : a journal of the Sleep Research Society
In common: Keras, TensorFlow, seaborn, 5 other tools, other, methods / tools, 3 references
[2] doi:10.1038/s41597-025-05174-7 [code]
A large-scale MEG and EEG dataset for object recognition in naturalistic scenes
Journal: n/a
In common: Keras, TensorFlow, Plotly, 6 other tools, methods / tools
[3] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: Keras, TensorFlow, Plotly, 6 other tools
[4] doi:10.1038/s41467-026-75455-1 [code]
Shared latent representations of speech production for cross-patient speech decoding.
Journal: Nature communications
In common: Keras, TensorFlow, Plotly, 6 other tools
[5] doi:10.1186/s12880-026-02481-2 [code]
Deep learning-based neuroanatomical profiling reveals population-specific brain changes in multiple sclerosis: a large-scale Middle Eastern study.
Journal: BMC medical imaging
In common: Keras, TensorFlow, Plotly, 6 other tools
[6] doi:10.1016/j.xcrm.2026.102766 [code]
A longitudinal single-cell and spatial multiomic atlas of pediatric high-grade glioma.
Journal: Cell reports. Medicine
In common: Keras, TensorFlow, Plotly, 6 other tools
[7] doi:10.1162/imag.a.1164 [code]
Bias and generalizability of brain age prediction models: A multi-cohort evaluation with anatomical and interpretability insights.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: Keras, TensorFlow, Plotly, 6 other tools
[8] doi:10.1523/eneuro.0023-26.2026 [code]
Real-Time Segmentation and Classification of Birdsong Syllables for Learning Experiments.
Journal: eNeuro
In common: Keras, TensorFlow, Plotly, 6 other tools
[9] doi:10.3389/fnsys.2026.1822122 [code]
Convergence-divergence circuits for multimodal integration of innate and learned opponent valences.
Journal: Frontiers in systems neuroscience
In common: Keras, TensorFlow, Plotly, 6 other tools
[10] doi:10.64898/2026.05.06.26352540 [code]
Generating synthetic tau-PET scans in Alzheimer’s disease from MRI, blood biomarkers and demographics with deep learning
Journal: medRxiv (preprint)
In common: Keras, TensorFlow, seaborn, 5 other tools, other

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.