Вы можете (ab-) использовать структурированные массивы (или, скорее, базовые составные dtypes) следующим образом:
# create example
param_dict = {f'a{i}':np.random.randint(0,10,np.random.randint(1,10,np.random.randint(1,3))) for i in range(1000)}
# make structured array containing a single element
x = np.array((*param_dict.values(),), [(k,(v.dtype,v.shape)) for k,v in param_dict.items()])
# can access elements by key:
x['a7']
# array([[3, 0, 9, 2, 5],
# [4, 7, 2, 7, 6]])
param_dict['a7']
# array([[3, 0, 9, 2, 5],
# [4, 7, 2, 7, 6]])
# data are stored flat
import numpy.lib.recfunctions as nlr
flat_view = nlr.structured_to_unstructured(x)
np.shares_memory(flat_view,x)
# True
flat_view.shape
# (15204,)
# all meta data are preserved in the dtype
x.dtype.fields['a7']
# type shape offset in bytes
# (dtype(('<i8', (2, 5))), 840)
flat_view[840//8:840//8+2*5]
# array([3, 0, 9, 2, 5, 4, 7, 2, 7, 6])
Теперь давайте вернем совместимый плоский массив обратно в исходный формат
# some token processing
processed = flat_view**2
new_x = nlr.unstructured_to_structured(processed,x.dtype)
new_param_dict = {k:new_x[k] for k in new_x.dtype.fields}
new_param_dict['a7']
# array([[ 9, 0, 81, 4, 25],
# [16, 49, 4, 49, 36]])