2013-08-16 30 views
7

Giả sử tôi đại diện cho vectơ tính năng bằng từ điển (tại sao? Vì tôi biết các tính năng rất thưa thớt, nhưng hơn thế nữa).Cách tính hiệu quả sản phẩm bên trong của hai bộ từ điển

Làm thế nào tôi nên thực hiện các sản phẩm bên trong của hai bộ từ điển này (ký hiệu là A, B)

Tôi đã thử các cách tiếp cận ngây thơ:

for k in A: 
    if k in B: 
    sum += A[k] * B[k] 

nhưng nó hóa ra là chậm.

Một số chi tiết:

  • Tôi đang sử dụng một từ điển để đại diện cho các tính năng vì

    1. Các phím chức năng là chuỗi
    2. Có ~ 20K phím có thể
    3. Mỗi vector là thưa thớt (nói, khoảng 1000 phần tử khác 0).
  • Tôi thực sự quan tâm đến việc tính toán sản phẩm bên trong cặp của N = 2000 từ điển khác nhau (nghĩa là hạt nhân tuyến tính của chúng).

+0

Bạn có thể thử 'cho k, v trong A.iteritems(): sum + = v * B.get (k, 0) ' – val

+0

Đây có thể là hiển nhiên, nhưng tôi figured tôi muốn thêm nó anyway: Bạn có chắc chắn rằng "A" có nhỏ nhất 'len()' của hai từ điển? Tùy thuộc vào hoàn cảnh, điều đó có thể có tác động rất lớn đến tốc độ. (Và kể từ khi các hoạt động là giao hoán nó sẽ không có bất kỳ tác động trên câu trả lời.) – Noah

Trả lời

1

Dưới đây là câu trả lời của tôi (Tiếp theo là một gợi ý bởi @ valentin-ôn):

Trước tiên tôi quấn một dok_matrix scipy.sparse. Ý tưởng là chỉ định từng tính năng có thể có một chỉ mục.

import scipy.sparse as sps 
import numpy as np 

class MSK: 
    # DD is a dict of dict, whose values are of type float. 
    # features - the set of possible features keys 
    def __init__(self, DD, features): 
     self.features = {k: j for (j, k) in enumerate(features)} 
     self.strings = DD.keys() 
     n = len(self.strings) 
     d = len(self.features) 
     self.M = sps.dok_matrix((n, d), dtype=np.float64) 
     for (i, s) in enumerate(self.strings): 
      v = DD[s] 
      for k in v: 
       j = self.features[k] 
       self.M[i, j] = v[k] 

Và chúng tôi thử nghiệm bằng mã sau, trong đó số lượng phần tử là 800, chiều rộng cũng là 800, nhưng độ lệch là 200 (chính xác là 200 phần tử khác 0).

np.random.seed(1) 
N = 800 
DD = dict() 
R = range(N) 
for i in xrange(N): 
    DD[i] = dict() 
    S = np.random.permutation(R) 
    S = S[:N/4] 
    for j in S: 
     DD[i][j] = np.random.randn(1)[0] 

K = MSK(DD, R) 
import cProfile 
cProfile.runctx("A = K.M * K.M.T", globals(), locals()) 
print A.todense() 

Đầu ra là:

2080520 function calls (2080519 primitive calls) in 2.884 seconds 

phép nói rằng 3 giây. Việc triển khai ngây thơ của tôi (sử dụng câu lệnh if @ Joowani) mất khoảng 19 giây.

(MSK là viết tắt của MatrixSparseKeys)

+0

FYI, thay thế dok_matrix() bằng lil_matrix(), chúng tôi có thể có hiệu suất tốt hơn nữa. –

6

Không chắc về nhanh hơn, nhưng đây là cách tiếp cận khác:

keys = A.viewkeys() & B.viewkeys() 
the_sum = sum(a[k] * b[k] for k in keys) 
+2

Bạn có thể sử dụng 'A.keys() & B.keys()' trên Python 3, và 'A.viewkeys() & B. viewkeys() 'trên Python 2.7. –

+0

Thực ra 'set (A) .intersection (B)' xuất hiện nhanh hơn ở một số điểm chuẩn mà tôi vừa làm. –

1

Bạn nên cố gắng sử dụng namedtuples thay vì dict.

from collections import namedtuple 
A = dict 
B = dict 
_A = namedtuple('_A', A.keys()) 
_B = namedtuple('_B', B.keys()) 
DictA = _A(**A) 
DictB = _B(**B) 

và sau đó sử dụng chúng làm dict. biết thêm trên namedtuples đây: What are "named tuples" in Python?

+0

Làm cách nào để tăng tốc độ tính toán? –

+0

thực sự têntuples nhanh hơn và hiệu quả hơn dict (http://stackoverflow.com/questions/1336791/dictionary-vs-object-which-is-more-efficient-and-why), bằng cách sử dụng thay vì dict có thể cải thiện tốc độ tính toán –

+0

Nếu bạn tìm kiếm các trường theo tên, nó vẫn là một tra cứu từ điển dưới mui xe. –

7

Hmm, có vẻ như cách tiếp cận của bạn thực sự là tốt nhất cho vectơ dày đặc:

>>> # Eric's answer 
>>> timeit.timeit('sum([A[k]*B[k] for k in set(A.keys()) & set(B.keys())])', setup='A=dict((i,i) for i in xrange(100));B=dict((i,i) for i in xrange(100))', number=10000) 
0.4360210521285808 

>>> # My comment 
>>> timeit.timeit('for k,v in A.iteritems(): sum += v*B.get(k,0)', setup='A=dict((i,i) for i in xrange(100));B=dict((i,i) for i in xrange(100));sum=0', number=10000) 
0.4082838999682963 

# My comment, more compact 
>>> timeit.timeit('sum(v*B.get(k,0) for k,v in A.iteritems())', setup='A=dict((i,i) for i in xrange(100));B=dict((i,i) for i in xrange(100))', number=10000) 
0.38053266868496394 

>>> #Your approach 
>>> timeit.timeit('for k in A: sum += A[k]*B[k] if k in B else 0.', setup='A=dict((i,i) for i in xrange(100));B=dict((i,i) for i in xrange(100));sum=0', number=10000) 
0.35574231962510794 

>>> # Your approach, more compact 
>>> timeit.timeit('sum(A[k]*B[k] for k in A if k in B)', setup='A=dict((i,i) for i in xrange(100));B=dict((i,i) for i in xrange(100))', number=10000) 
0.3400850549682559 

Đối với những người thưa thớt, câu trả lời của Eric thực hiện tốt hơn nhưng bạn vẫn là nhanh nhất:

# Mine 
>>> timeit.timeit('sum(v*B.get(k,0) for k,v in A.iteritems())', setup='import random;A=dict((i,i) for i in xrange(100) if random.random() < 0.3);B=dict((i,i) for i in xrange(100) if random.random() < 0.2)', number=10000) 
0.1390782696843189 

# Eric's 
>>> timeit.timeit('sum([A[k]*B[k] for k in set(A.keys()) & set(B.keys())])', setup='import random;A=dict((i,i) for i in xrange(100) if random.random() < 0.3);B=dict((i,i) for i in xrange(100) if random.random() < 0.2)', number=10000) 
0.11702822992151596 

# Yours 
>>> timeit.timeit('sum(A[k]*B[k] for k in A if k in B)', setup='import random;A=dict((i,i) for i in xrange(100) if random.random() < 0.3);B=dict((i,i) for i in xrange(100) if random.random() < 0.2)', number=10000) 
0.07878250570843193 

EDIT

Sau rối tung xung quanh f hoặc một chút, có vẻ như sum([x for x ...]) nhanh hơn đáng kể so với sum(x for x in ...). Rebenchmarking với điều này và nhận xét Janne cho các phím trong câu trả lời của Eric, bạn vẫn là trên đầu trang (với Joowani của đưa ra một sự cải thiện nhẹ):

>>> timeit.timeit('sum([v*B.get(k,0) for k,v in A.items()])', setup='import random;A=dict((i,i) for i in xrange(100) if random.random() < 0.3);B=dict((i,i) for i in xrange(100) if random.random() < 0.2)', number=100000) 
1.1604375791416714 
>>> timeit.timeit('sum([A[k]*B[k] for k in A.viewkeys() & B.viewkeys()])', setup='import random;A=dict((i,i) for i in xrange(100) if random.random() < 0.3);B=dict((i,i) for i in xrange(100) if random.random() < 0.2)', number=100000) 
0.9234189571552633 
>>> timeit.timeit('sum([A[k]*B[k] for k in A if k in B])', setup='import random;A=dict((i,i) for i in xrange(100) if random.random() < 0.3);B=dict((i,i) for i in xrange(100) if random.random() < 0.2)', number=100000) 
0.5411289579401455 
>>> timeit.timeit('sum([A[k]*B[k] for k in A if k in B]) if len(A)<len(B) else sum([A[k]*B[k] for k in B if k in A])', setup='import random;A=dict((i,i) for i in xrange(100) if random.random() < 0.3);B=dict((i,i) for i in xrange(100) if random.random() < 0.2)', number=100000) 
0.5198972138696263 

tỷ lệ để kích thước rất lớn, bạn thấy chính xác cùng một khuôn mẫu:

>>> #Mine 
>>> timeit.timeit('sum([v*B.get(k,0) for k,v in A.iteritems()])', setup='import random;A=dict((i,i) for i in xrange(10000) if random.random() < 0.1);B=dict((i,i) for i in xrange(10000) if random.random() < 0.2)', number=100000) 
45.328807250833506 

>>> #Eric's 
>>> timeit.timeit('sum([A[k]*B[k] for k in A.viewkeys() & B.viewkeys()])', setup='import random;A=dict((i,i) for i in xrange(10000) if random.random() < 0.1);B=dict((i,i) for i in xrange(10000) if random.random() < 0.2)', number=100000) 
28.042937058640973 

>>> #Yours 
>>> timeit.timeit('sum([A[k]*B[k] for k in A if k in B])', setup='import random;A=dict((i,i) for i in xrange(10000) if random.random() < 0.1);B=dict((i,i) for i in xrange(10000) if random.random() < 0.2)', number=100000) 
16.55080344861699 

>>> #Joowani's 
>>> timeit.timeit('sum([A[k]*B[k] for k in A if k in B]) if len(A)<len(B) else sum([A[k]*B[k] for k in B if k in A])', setup='import random;A=dict((i,i) for i in xrange(10000) if random.random() < 0.1);B=dict((i,i) for i in xrange(10000) if random.random() < 0.2)', number=100000) 
15.485236119691308 

Tôi nghĩ rằng mẹo của Joowani không cải thiện đáng kể ở đây vì vectơ có kích thước gần bằng nhau, nhưng tùy thuộc vào vấn đề của bạn (nếu một số vectơ nhỏ hơn ridiculously nhỏ hơn) thì điều này có thể quan trọng hơn ...

EDIT LẠI

Rất tiếc, có vẻ như tôi nên đã lấy cà phê khác trước khi gửi bài ... Như Eric chỉ ra (mặc dù tôi đã hoàn toàn mất nó ...), xác định các mảng trong setup giữ nó tương tự cho tất cả các thử nghiệm, đó không thực sự là cách tốt nhất để đánh giá. Với vectơ ngẫu nhiên PROPER đang được thử nghiệm, kết quả không khác nhau đáng kể, nhưng vì lợi ích của sự hoàn chỉnh:

>>> timeit.timeit('mine(dict((i,i) for i in xrange(100) if random.random() < 0.3),dict((i,i) for i in xrange(100) if random.random() < 0.2))', setup='import random;joowanis=lambda A,B:sum([A[k]*B[k] for k in A if k in B]) if len(A)<len(B) else sum([A[k]*B[k] for k in B if k in A]);mine=lambda A,B:sum([v*B.get(k,0) for k,v in A.iteritems()]);erics=lambda A,B:sum([A[k]*B[k] for k in A.viewkeys() & B.viewkeys()]);yours=lambda A,B:sum([A[k]*B[k] for k in A if k in B])', number=100000) 
6.294158102577967 
>>> timeit.timeit('erics(dict((i,i) for i in xrange(100) if random.random() < 0.3),dict((i,i) for i in xrange(100) if random.random() < 0.2))', setup='import random;joowanis=lambda A,B:sum([A[k]*B[k] for k in A if k in B]) if len(A)<len(B) else sum([A[k]*B[k] for k in B if k in A]);mine=lambda A,B:sum([v*B.get(k,0) for k,v in A.iteritems()]);erics=lambda A,B:sum([A[k]*B[k] for k in A.viewkeys() & B.viewkeys()]);yours=lambda A,B:sum([A[k]*B[k] for k in A if k in B])', number=100000) 
6.068052507449011 
>>> timeit.timeit('yours(dict((i,i) for i in xrange(100) if random.random() < 0.3),dict((i,i) for i in xrange(100) if random.random() < 0.2))', setup='import random;joowanis=lambda A,B:sum([A[k]*B[k] for k in A if k in B]) if len(A)<len(B) else sum([A[k]*B[k] for k in B if k in A]);mine=lambda A,B:sum([v*B.get(k,0) for k,v in A.iteritems()]);erics=lambda A,B:sum([A[k]*B[k] for k in A.viewkeys() & B.viewkeys()]);yours=lambda A,B:sum([A[k]*B[k] for k in A if k in B])', number=100000) 
5.745110704570834 
>>> timeit.timeit('joowanis(dict((i,i) for i in xrange(100) if random.random() < 0.3),dict((i,i) for i in xrange(100) if random.random() < 0.2))', setup='import random;joowanis=lambda A,B:sum([A[k]*B[k] for k in A if k in B]) if len(A)<len(B) else sum([A[k]*B[k] for k in B if k in A]);mine=lambda A,B:sum([v*B.get(k,0) for k,v in A.iteritems()]);erics=lambda A,B:sum([A[k]*B[k] for k in A.viewkeys() & B.viewkeys()]);yours=lambda A,B:sum([A[k]*B[k] for k in A if k in B])', number=100000) 
5.737499445367575 

Để mở rộng quy mô:

>>> timeit.timeit('mine(dict((i,i) for i in xrange(10000) if random.random() < 0.1),dict((i,i) for i in xrange(10000) if random.random() < 0.2))', setup='import random;joowanis=lambda A,B:sum([A[k]*B[k] for k in A if k in B]) if len(A)<len(B) else sum([A[k]*B[k] for k in B if k in A]);mine=lambda A,B:sum([v*B.get(k,0) for k,v in A.iteritems()]);erics=lambda A,B:sum([A[k]*B[k] for k in A.viewkeys() & B.viewkeys()]);yours=lambda A,B:sum([A[k]*B[k] for k in A if k in B])', number=1000) 
5.0510995368395015 
>>> timeit.timeit('erics(dict((i,i) for i in xrange(10000) if random.random() < 0.1),dict((i,i) for i in xrange(10000) if random.random() < 0.2))', setup='import random;joowanis=lambda A,B:sum([A[k]*B[k] for k in A if k in B]) if len(A)<len(B) else sum([A[k]*B[k] for k in B if k in A]);mine=lambda A,B:sum([v*B.get(k,0) for k,v in A.iteritems()]);erics=lambda A,B:sum([A[k]*B[k] for k in A.viewkeys() & B.viewkeys()]);yours=lambda A,B:sum([A[k]*B[k] for k in A if k in B])', number=1000) 
4.350612399185138 
>>> timeit.timeit('yours(dict((i,i) for i in xrange(10000) if random.random() < 0.1),dict((i,i) for i in xrange(10000) if random.random() < 0.2))', setup='import random;joowanis=lambda A,B:sum([A[k]*B[k] for k in A if k in B]) if len(A)<len(B) else sum([A[k]*B[k] for k in B if k in A]);mine=lambda A,B:sum([v*B.get(k,0) for k,v in A.iteritems()]);erics=lambda A,B:sum([A[k]*B[k] for k in A.viewkeys() & B.viewkeys()]);yours=lambda A,B:sum([A[k]*B[k] for k in A if k in B])', number=1000) 
4.15619379016789 
>>> timeit.timeit('joowanis(dict((i,i) for i in xrange(10000) if random.random() < 0.1),dict((i,i) for i in xrange(10000) if random.random() < 0.2))', setup='import random;joowanis=lambda A,B:sum([A[k]*B[k] for k in A if k in B]) if len(A)<len(B) else sum([A[k]*B[k] for k in B if k in A]);mine=lambda A,B:sum([v*B.get(k,0) for k,v in A.iteritems()]);erics=lambda A,B:sum([A[k]*B[k] for k in A.viewkeys() & B.viewkeys()]);yours=lambda A,B:sum([A[k]*B[k] for k in A if k in B])', number=1000) 
4.185129374341159 

Tôi nghĩ rằng mấu chốt là bạn không thể mong đợi sự tăng tốc đáng kể bằng cách khéo léo sắp xếp lại các biểu thức của bạn cho loại điều này ... Có lẽ bạn có thể thử làm phần số trong C/Cython hoặc sử dụng gói Scipy's Sparse?

+0

Tôi vừa mới cập nhật từ 'set (d.keys())' sang 'd.viewkeys()', có thể tăng tốc độ – Eric

+0

Ngoài ra, tôi không chắc rằng bài kiểm tra này có ý nghĩa nếu từ điển ban đầu khác nhau mỗi đoạn mã – Eric

+0

Đúng, nhưng tôi nghĩ rằng điều này vẫn có thể cho chúng ta ý tưởng sơ bộ về tốc độ của từng mã trung bình. – val

2

Trong trường hợp A dài hơn B nhiều thì điều này có thể giúp ích gì?

if len(A) > len(B): 
    A, B = B, A 

for k in A: 
    if k in B: 
     the_sum += A[k] * B[k] 
+0

Wow đó là một chỉnh sửa nhanh Eric, – Joohwan

Các vấn đề liên quan