@@ -1117,171 +1117,6 @@ def test_bidirectional_lstm_saving(self):
11171117 out = new_model (x )
11181118 self .assertAllClose (out , ref_out )
11191119
1120- @parameterized .named_parameters (
1121- ("list" , list , lambda b : b ),
1122- ("tuple" , tuple , lambda b : b ),
1123- ("dict" , dict , lambda b : b .values ()),
1124- )
1125- def test_nested_container_layer_saving (self , container_type , get_values ):
1126- """Test that layers stored in nested containers are saved/loaded."""
1127-
1128- package = f"test_nested_{ container_type .__name__ } "
1129-
1130- @keras .saving .register_keras_serializable (package = package )
1131- class NestedContainerModel (keras .Model ):
1132- def __init__ (self , ** kwargs ):
1133- super ().__init__ (** kwargs )
1134- inner1 = [keras .layers .Dense (8 ), keras .layers .Dense (8 )]
1135- inner2 = [keras .layers .Dense (8 ), keras .layers .Dense (8 )]
1136- if container_type is dict :
1137- self .blocks = {
1138- "block_a" : inner1 ,
1139- "block_b" : inner2 ,
1140- }
1141- elif container_type is list :
1142- self .blocks = [inner1 , inner2 ]
1143- else :
1144- self .blocks = (tuple (inner1 ), tuple (inner2 ))
1145- self .out_layer = keras .layers .Dense (2 )
1146-
1147- def call (self , x ):
1148- for block in get_values (self .blocks ):
1149- for layer in block :
1150- x = layer (x )
1151- return self .out_layer (x )
1152-
1153- def get_config (self ):
1154- return super ().get_config ()
1155-
1156- model = NestedContainerModel ()
1157- x = np .random .random ((2 , 4 ))
1158- model (x ) # build weights
1159-
1160- # Assign distinct constant kernels so that any layer-swapping
1161- # on reload would produce a detectable mismatch.
1162- for i , block in enumerate (get_values (model .blocks )):
1163- for j , layer in enumerate (block ):
1164- val = float (i * 2 + j + 1 ) # 1.0, 2.0, 3.0, 4.0
1165- layer .kernel .assign (np .full_like (layer .kernel , val ))
1166- layer .bias .assign (np .zeros_like (layer .bias ))
1167-
1168- ref_out = model (x )
1169- suffix = container_type .__name__
1170- temp_filepath = os .path .join (
1171- self .get_temp_dir (), f"nested_{ suffix } .keras"
1172- )
1173- model .save (temp_filepath )
1174- new_model = keras .saving .load_model (temp_filepath )
1175-
1176- # Verify each nested layer's weights were individually restored.
1177- for i , (orig_block , new_block ) in enumerate (
1178- zip (get_values (model .blocks ), get_values (new_model .blocks ))
1179- ):
1180- for j , (orig_layer , new_layer ) in enumerate (
1181- zip (orig_block , new_block )
1182- ):
1183- self .assertAllClose (
1184- orig_layer .kernel ,
1185- new_layer .kernel ,
1186- msg = f"Kernel mismatch at blocks[{ i } ][{ j } ]" ,
1187- )
1188-
1189- out = new_model (x )
1190- self .assertAllClose (ref_out , out )
1191-
1192- def test_deeply_nested_container_layer_saving (self ):
1193- """Test 3+ levels of nested containers."""
1194-
1195- @keras .saving .register_keras_serializable (package = "test_deeply_nested" )
1196- class DeeplyNestedModel (keras .Model ):
1197- def __init__ (self , ** kwargs ):
1198- super ().__init__ (** kwargs )
1199- self .blocks = [[[keras .layers .Dense (4 ), keras .layers .Dense (4 )]]]
1200- self .out_layer = keras .layers .Dense (2 )
1201-
1202- def call (self , x ):
1203- for block_list in self .blocks :
1204- for block in block_list :
1205- for layer in block :
1206- x = layer (x )
1207- return self .out_layer (x )
1208-
1209- def get_config (self ):
1210- return super ().get_config ()
1211-
1212- model = DeeplyNestedModel ()
1213- x = np .random .random ((2 , 4 ))
1214- model (x )
1215- ref_out = model (x )
1216-
1217- temp_filepath = os .path .join (self .get_temp_dir (), "deeply_nested.keras" )
1218- model .save (temp_filepath )
1219- new_model = keras .saving .load_model (temp_filepath )
1220- out = new_model (x )
1221- self .assertAllClose (ref_out , out )
1222-
1223- def test_mixed_nested_container_layer_saving (self ):
1224- """Test mixed container types (list + tuple)."""
1225-
1226- @keras .saving .register_keras_serializable (package = "test_mixed_nested" )
1227- class MixedNestedModel (keras .Model ):
1228- def __init__ (self , ** kwargs ):
1229- super ().__init__ (** kwargs )
1230- self .blocks = [(keras .layers .Dense (4 ), keras .layers .Dense (4 ))]
1231- self .out_layer = keras .layers .Dense (2 )
1232-
1233- def call (self , x ):
1234- for block in self .blocks :
1235- for layer in block :
1236- x = layer (x )
1237- return self .out_layer (x )
1238-
1239- def get_config (self ):
1240- return super ().get_config ()
1241-
1242- model = MixedNestedModel ()
1243- x = np .random .random ((2 , 4 ))
1244- model (x )
1245- ref_out = model (x )
1246-
1247- temp_filepath = os .path .join (self .get_temp_dir (), "mixed_nested.keras" )
1248- model .save (temp_filepath )
1249- new_model = keras .saving .load_model (temp_filepath )
1250- out = new_model (x )
1251- self .assertAllClose (ref_out , out )
1252-
1253- def test_cyclic_container_saving (self ):
1254- """Test that cyclic container references do not cause RecursionError."""
1255-
1256- @keras .saving .register_keras_serializable (
1257- package = "test_cyclic_container"
1258- )
1259- class CyclicContainerModel (keras .Model ):
1260- def __init__ (self , ** kwargs ):
1261- super ().__init__ (** kwargs )
1262- self .blocks = []
1263- self .blocks .append (self .blocks )
1264- self .out = keras .layers .Dense (2 )
1265-
1266- def call (self , x ):
1267- return self .out (x )
1268-
1269- def get_config (self ):
1270- return super ().get_config ()
1271-
1272- model = CyclicContainerModel ()
1273- x = np .random .random ((2 , 4 ))
1274- model (x )
1275- ref_out = model (x )
1276-
1277- temp_filepath = os .path .join (
1278- self .get_temp_dir (), "cyclic_container.keras"
1279- )
1280- model .save (temp_filepath )
1281- new_model = keras .saving .load_model (temp_filepath )
1282- out = new_model (x )
1283- self .assertAllClose (ref_out , out )
1284-
12851120 def test_remove_weights_only_saving_and_loading (self ):
12861121 def is_remote_path (path ):
12871122 return True
0 commit comments