Обратимый словарь для отображения массива - PullRequest
1 голос
/ 23 сентября 2019

У меня есть словарь имен переменных, которые мне нужно передать оптимизатору, получить выходные данные и затем вернуть обратно в словарь.Переменные в словаре имеют различные формы, но мне нужно передать их как 1d-массивы.

Довольно просто сделать шаг вперед:

np.concatenate([elem.ravel() for elem in param_dict.values()])

Однако я не уверен, чтолучший способ вернуть измененные параметры обратно в мой словарь.

Я понимаю, что могу вручную найти смещения в массиве гигантских параметров для различных элементов dict и вручную изменить их на правильную форму, но мне интересно, есть ли более надежный способ сделать это (такой, например, какколичество элементов / фигур в словаре может быть динамическим).

Ответы [ 2 ]

2 голосов
/ 26 сентября 2019

Я много раз сталкивался с одной и той же проблемой, и мне еще предстоит найти достойное решение.Следующее - начало, которое нуждается в доработке.Реализованный класс поддерживает только случай, когда все значения словаря являются самими одномерными массивами.

IMO, полный ответ будет поддерживать словари с элементами, которые являются многомерными массивами, целыми числами или числами с плавающей запятой.

#!/usr/bin/env python
"""
/12781634/obratimyi-slovar-dlya-otobrazheniya-massiva

TODO:
 - support dictionary elements that are multi-dimensional arrays
 - support dictionary elements that are not arrays, specifically int or float values
 - corresponding tests
"""

from typing import Union, Iterator
import numpy as np

class NamedVector(object):
    """Extend an ordered dictionary, where all values are 1d arrays, to
    additionally support indexing with integers and slices."""

    def __init__(self, ordered_dict : dict) -> None:
        self.dict = ordered_dict
        self._make_index()

    def _make_index(self):
        """Create a mapping from int ii : str attribute, int jj
        """
        self.index = dict()
        ii = 0
        for (key, values) in self.dict.items():
            for jj, v in enumerate(values):
                self.index[ii] = (key, jj)
                ii += 1

    def __getitem__(self, idx : Union[str, int, slice]) -> Union[float, int, np.ndarray]:
        if isinstance(idx, str):
            return self.dict[idx]

        elif isinstance(idx, int):
            if idx < 0:
                idx = self._get_positive(idx)
            key, item_idx = self.index[idx]
            return self.dict[key][item_idx]

        elif isinstance(idx, slice):
            return np.array([self.dict[key][item_idx] for key, item_idx in self._slice_to_dict_indices(idx)])

    def _get_positive(self, ii : int) -> int:
        return max(self.index) +1 +ii # NB: ii is negative

    def _slice_to_dict_indices(self, slice_object : slice) -> Iterator:
        for ii in self._slice_to_range(slice_object):
            yield self.index[ii]

    def _slice_to_range(self, slice_object : slice) -> range:
        if slice_object.step is not None:
            step = slice_object.step
        else:
            step = 1

        if slice_object.start is not None:
            start = slice_object.start
            if start < 0:
                start = self._get_positive(start)
        else:
            if step > 0:
                start = 0
            else:
                start = max(self.index)

        if slice_object.stop is not None:
            stop = slice_object.stop
            if stop < 0:
                stop = self._get_positive(stop)
        else:
            if step > 0:
                stop = max(self.index) + 1
            else:
                stop = -1

        return range(start, stop, step)

    def __setitem__(self, idx : Union[int, slice], val : Union[int, float, np.ndarray]) -> None:

        if isinstance(idx, str):
            self.dict[idx] = val
            self._make_index()

        elif isinstance(idx, int):
            if idx < 0:
                idx = self._get_positive(idx)
            key, item_idx = self.index[idx]
            self.dict[key][item_idx] = val

        elif isinstance(idx, slice):
            for ii, (key, item_idx) in enumerate(self._slice_to_dict_indices(idx)):
                self.dict[key][item_idx] = val[ii]


def test_NamedVector_getitem():
    a = np.arange(2)
    b = np.arange(2, 5)
    ab = np.concatenate([a, b])
    mydict = dict(a=a, b=b)
    nv = NamedVector(mydict)

    assert np.all(nv['a'] == a)
    assert nv[0] == 0
    assert np.all(nv[0:2] == a)
    assert np.all(nv[:2] == a)
    assert np.all(nv[-1] == b[-1])
    assert np.all(nv[:-1] == ab[:-1])
    assert np.all(nv[::-1] == ab[::-1])


def test_NamedVector_setitem():
    a = np.arange(2)
    b = np.arange(2, 5)
    c = np.arange(5, 9)
    abc = np.concatenate([a, b, c])
    mydict = dict(a=a, b=b)
    nv = NamedVector(mydict)

    nv['c'] = c
    assert np.all(nv[:] == abc)

    nv[0] = 10
    assert nv[0] == 10

    nv[-1] = 11
    assert nv[-1] == 11

    d = np.arange(10,12)
    nv[0:2] = d
    assert np.all(nv[0:2] == d)

    nv[:] = abc[::-1]
    assert np.all(nv[:] == abc[::-1])
1 голос
/ 27 сентября 2019

Вы можете (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]])
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...