一种比较省内存的稀疏矩阵Python存储方案

原文:http://www.pythontab.com/html/2014/pythonhexinbiancheng_0522/788.html

推荐系统中经常需要处理类似user_id, item_id, rating这样的数据,其实就是数学里面的稀疏矩阵,scipy中提供了sparse模块来解决这个问题,但scipy.sparse有很多问题不太合用:1、不能很好的同时支持data[i, ...]、data[..., j]、data[i, j]快速切片;2、由于数据保存在内存中,不能很好的支持海量数据处理。

要支持data[i, ...]、data[..., j]的快速切片,需要i或者j的数据集中存储;同时,为了保存海量的数据,也需要把数据的一部分放在硬盘上,用内存做buffer。这里的解决方案比较简单,用一个类Dict的东西来存储数据,对于某个i(比如9527),它的数据保存在dict['i9527']里面,同样的,对于某个j(比如3306),它的全部数据保存在dict['j3306']里面,需要取出data[9527, ...]的时候,只要取出dict['i9527']即可,dict['i9527']原本是一个dict对象,储存某个j对应的值,为了节省内存空间,我们把这个dict以二进制字符串形式存储,直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
'''
Sparse Matrix
'''
import  struct
import  numpy as np
import  bsddb
from  cStringIO  import  StringIO
  
class  DictMatrix():
     def  __init__( self , container  =  {}, dft  =  0.0 ):
         self ._data   =  container
         self ._dft    =  dft
         self ._nums   =  0
  
     def  __setitem__( self , index, value):
         try :
             i, j  =  index
         except :
             raise  IndexError( 'invalid index' )
  
         ik  =  ( 'i%d'  %  i)
         # 为了节省内存,我们把j, value打包成字二进制字符串
         ib  =  struct.pack( 'if' , j, value)
         jk  =  ( 'j%d'  %  j)
         jb  =  struct.pack( 'if' , i, value)
  
         try :
             self ._data[ik]  + =  ib
         except :
             self ._data[ik]  =  ib
         try :
             self ._data[jk]  + =  jb
         except :
             self ._data[jk]  =  jb
         self ._nums  + =  1
  
     def  __getitem__( self , index):
         try :
             i, j  =  index
         except :
             raise  IndexError( 'invalid index' )
  
         if  ( isinstance (i,  int )):
             ik  =  ( 'i%d'  %  i)
             if  not  self ._data.has_key(ik):  return  self ._dft
             ret  =  dict (np.fromstring( self ._data[ik], dtype  =  'i4,f4' ))
             if  ( isinstance (j,  int )):  return  ret.get(j,  self ._dft)
  
         if  ( isinstance (j,  int )):
             jk  =  ( 'j%d'  %  j)
             if  not  self ._data.has_key(jk):  return  self ._dft
             ret  =  dict (np.fromstring( self ._data[jk], dtype  =  'i4,f4' ))
  
         return  ret
  
     def  __len__( self ):
         return  self ._nums
  
     def  __iter__( self ):
         pass
  
     '''
     从文件中生成matrix
     考虑到dbm读写的性能不如内存,我们做了一些缓存,每1000W次批量写入一次
     考虑到字符串拼接性能不太好,我们直接用StringIO来做拼接
     '''
     def  from_file( self , fp, sep  =  't' ):
         cnt  =  0
         cache  =  {}
         for  in  fp:
             if  10000000  = =  cnt:
                 self ._flush(cache)
                 cnt  =  0
                 cache  =  {}
             i, j, v  =  [ float (i)  for  in  l.split(sep)]
  
             ik  =  ( 'i%d'  %  i)
             ib  =  struct.pack( 'if' , j, v)
             jk  =  ( 'j%d'  %  j)
             jb  =  struct.pack( 'if' , i, v)
  
             try :
                 cache[ik].write(ib)
             except :
                 cache[ik]  =  StringIO()
                 cache[ik].write(ib)
  
             try :
                 cache[jk].write(jb)
             except :
                 cache[jk]  =  StringIO()
                 cache[jk].write(jb)
  
             cnt  + =  1
             self ._nums  + =  1
  
         self ._flush(cache)
         return  self ._nums
  
     def  _flush( self , cache):
         for  k,v  in  cache.items():
             v.seek( 0 )
             =  v.read()
             try :
                 self ._data[k]  + =  s
             except :
                 self ._data[k]  =  s
  
if  __name__  = =  '__main__' :
     db  =  bsddb.btopen( None , cachesize  =  268435456 )
     data  =  DictMatrix(db)
     data.from_file( open ( '/path/to/log.txt' 'r' ),  ',' )

测试4500W条rating数据(整形,整型,浮点格式),922MB文本文件导入,采用内存dict储存的话,12分钟构建完毕,消耗内存1.2G,采用示例代码中的bdb存储,20分钟构建完毕,占用内存300~400MB左右,比cachesize大不了多少,数据读取测试:

1
2
import  timeit
timeit.Timer( 'foo = __main__.data[9527, ...]' 'import __main__' ).timeit(number  =  1000 )

消耗1.4788秒,大概读取一条数据1.5ms。

采用类Dict来存储数据的另一个好处是你可以随便用内存Dict或者其他任何形式的DBM,甚至传说中的Tokyo Cabinet….

好的,码完收工。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值