我正在一个人工智能项目上使用redis。

我们的想法是让多个环境模拟器在很多cpu核心上运行策略。 模拟器将经验(状态/行动/奖励图元的列表)写入redis服务器(重放缓冲器)。 然后一个训练过程将经验作为数据集读取,以生成新的策略。 新的策略被部署到模拟器上,之前运行的数据被删除,然后这个过程继续。

大部分的经验都被记录在 "状态 "中。 这通常表示为一个大的numpy数组,其尺寸为80 x 80。 模拟器以CPU允许的速度生成这些数据。

为此,有没有人有好的想法或经验,能把大量的numpy数组写到redis上的最好/最快/最简单的方法。 这都是在同一台机器上,但以后可能是在一组云服务器上。 欢迎提供代码样本!

python
numpy
redis
artificial-intelligence
Duane
Duane
发布于 2019-03-23
5 个回答
Mark Setchell
Mark Setchell
发布于 2019-09-05
已采纳
0 人赞同

我不知道这是否是最快的,但你可以试试这样的方法...

将Numpy数组存储到Redis是这样的--见函数 toRedis()

  • get shape of Numpy array and encode
  • append the Numpy array as bytes to the shape
  • store the encoded array under supplied key
  • 检索Numpy数组是这样的--见函数 fromRedis()

  • retrieve from Redis the encoded string corresponding to supplied key
  • extract the shape of the Numpy array from the string
  • extract data and repopulate Numpy array, reshape to original shape
  • #!/usr/bin/env python3
    import struct
    import redis
    import numpy as np
    def toRedis(r,a,n):
       """Store given Numpy array 'a' in Redis under key 'n'"""
       h, w = a.shape
       shape = struct.pack('>II',h,w)
       encoded = shape + a.tobytes()
       # Store encoded data in Redis
       r.set(n,encoded)
       return
    def fromRedis(r,n):
       """Retrieve Numpy array from Redis key 'n'"""
       encoded = r.get(n)
       h, w = struct.unpack('>II',encoded[:8])
       # Add slicing here, or else the array would differ from the original
       a = np.frombuffer(encoded[8:]).reshape(h,w)
       return a
    # Create 80x80 numpy array to store
    a0 = np.arange(6400,dtype=np.uint16).reshape(80,80) 
    # Redis connection
    r = redis.Redis(host='localhost', port=6379, db=0)
    # Store array a0 in Redis under name 'a0array'
    toRedis(r,a0,'a0array')
    # Retrieve from Redis
    a1 = fromRedis(r,'a0array')
    np.testing.assert_array_equal(a0,a1)
    

    你可以通过对Numpy数组的dtype与形状进行编码来增加灵活性。我没有这么做,因为你可能已经知道你所有的数组都是一个特定的类型,那么代码就会毫无理由地变得更大,更难读。

    在现代iMac上进行粗略的基准测试:

    80x80 Numpy array of np.uint16   => 58 microseconds to write
    200x200 Numpy array of np.uint16 => 88 microseconds to write
    

    Keywords:Python, Numpy, Redis, array, serialise, serialize, key, incr, unique

    谢谢马克,真的很有帮助
    目前我在一个多进程的模拟/训练设置中实现了这个描述。 一旦我完成了测试,就会接受,前提是性能良好。
    虽然我最后改用了射线,但还是成功了。 github.com/ray-project/ray 对于这个用例
    很好!谢谢你提供的 ray 的链接,祝你的项目好运。
    结果是,在用Ray乱搞了2个星期后,我又回到了这个答案! Ray很好,但我认为仍然需要更多的工作。 (在我写这篇文章时,它只有0.7版本)
    telegraphic
    telegraphic
    发布于 2019-09-05
    0 人赞同

    你也可以考虑使用 味精包装-Numpy ,它提供了 "编码和解码例程,使numpy提供的数字和数组数据类型能够使用高效的msgpack格式进行序列化和反序列化。"--见 https://msgpack.org/ .

    Quick proof-of-concept:

    import msgpack
    import msgpack_numpy as m
    import numpy as np
    m.patch()               # Important line to monkey-patch for numpy support!
    from redis import Redis
    r = Redis('127.0.0.1')
    # Create an array, then use msgpack to serialize it 
    d_orig = np.array([1,2,3,4])
    d_orig_packed = m.packb(d_orig)
    # Set the data in redis
    r.set('d', d_orig_packed)
    # Retrieve and unpack the data
    d_out = m.unpackb(r.get('d'))
    # Check they match
    assert np.alltrue(d_orig == d_out)
    assert d_orig.dtype == d_out.dtype
    

    在我的机器上,msgpack的运行速度要比使用struct快得多。

    In: %timeit struct.pack('4096L', *np.arange(0, 4096))
    1000 loops, best of 3: 443 µs per loop
    In: %timeit m.packb(np.arange(0, 4096))
    The slowest run took 7.74 times longer than the fastest. This could mean that an intermediate result is being cached.
    10000 loops, best of 3: 32.6 µs per loop
        
    虽然我很欣赏使用 msgpack 的简单性和优雅性,但我不确定你的样本时间试图说明什么。你似乎在比较 msgpack 和结构化包装的时间,但是如果你仔细阅读我的答案,我只对尺寸进行结构化包装,而不是使用 np.tobytes() 的阵列数据本身。如果你在我的机器上比较 np.tobytes() msgpack ,至少它要快50倍,即314ns对17.3微秒。
    @MarkSetchell 啊,是的,你说的完全正确,不是一个公平的比较。如果我从你的答案中只拿出打包逻辑来测试速度,并称其为 def pack(a) ,那么在80x80的数组上, %timeit pack(a) 给出4.62us,而 %timeit m.packb(a) 需要12us,所以慢了2.5倍。不过,msgpack-numpy是一个很好的软件包!
    Jadiel de Armas
    Jadiel de Armas
    发布于 2019-09-05
    0 人赞同

    你可以查看Mark Setchell的答案,了解如何实际将字节写入Redis中。下面我重写了 fromRedis toRedis 这两个函数,以考虑可变维度大小的数组,同时也包括数组的形状。

    def toRedis(arr: np.array) -> str:
        arr_dtype = bytearray(str(arr.dtype), 'utf-8')
        arr_shape = bytearray(','.join([str(a) for a in arr.shape]), 'utf-8')
        sep = bytearray('|', 'utf-8')
        arr_bytes = arr.ravel().tobytes()
        to_return = arr_dtype + sep + arr_shape + sep + arr_bytes
        return to_return
    def fromRedis(serialized_arr: str) -> np.array:
        sep = '|'.encode('utf-8')
        i_0 = serialized_arr.find(sep)
        i_1 = serialized_arr.find(sep, i_0 + 1)
        arr_dtype = serialized_arr[:i_0].decode('utf-8')
        arr_shape = tuple([int(a) for a in serialized_arr[i_0 + 1:i_1].decode('utf-8').split(',')])
        arr_str = serialized_arr[i_1 + 1:]
        arr = np.frombuffer(arr_str, dtype = arr_dtype).reshape(arr_shape)
        return arr
        
    Abhishek Sharma
    Abhishek Sharma
    发布于 2019-09-05
    0 人赞同

    试试Plasma吧,因为它避免了序列化/反序列化的开销。

    使用 pip install pyarrow 来安装plasma

    文件。 https://arrow.apache.org/docs/python/plasma.html

    首先,启动具有1GB内存的等离子体[终端]。

    plasma_store -m 1000000000 -s /tmp/plasma

    import pyarrow.plasma as pa
    import numpy as np
    client = pa.connect("/tmp/plasma")
    temp = np.random.rand(80,80)
    

    写入时间:130微秒对782微秒(Redis实现:Mark Setchell的回答)

    写入时间可以通过使用等离子体的巨大页面来改善,但只适用于Linux机器。https://arrow.apache.org/docs/python/plasma.html#using-plasma-with-huge-pages

    获取时间。 31.2微秒对99.5微秒(Redis的实现:Mark Setchell的回答)。

    PS:代码是在MacPro上运行的

    谢谢你给我们提供了这个例子。 一个受欢迎的贡献!
    有趣的是--我没有意识到等离子体/焦耳。不过有几件事。1)你的代码根本没有显示如何写入或读取 plasma 2)你的代码在不同的机器上使用了不同的 dtype 和不同的数据,所以时间上完全没有可比性 3)如果我使用plasma和 client.put() ,与我在答案中创建的阵列相同。 Redis 需要大约70us,等离子体需要196us--尽管我不得不说我对等离子体或优化它没有经验。
    guenthermi
    guenthermi
    发布于 2019-09-05
    0 人赞同

    tobytes() 函数的存储效率不高。 为了减少必须写入redis服务器的存储,你可以使用base64包。

    def encode_vector(ar):
        return base64.encodestring(ar.tobytes()).decode('ascii')