Tại sao np.dot không chính xác? (mảng n-dim)


15

Giả sử chúng ta lấy np.dothai 'float32'mảng 2D:

res = np.dot(a, b)   # see CASE 1
print(list(res[0]))  # list shows more digits
[-0.90448684, -1.1708503, 0.907136, 3.5594249, 1.1374011, -1.3826287]

Số. Ngoại trừ, họ có thể thay đổi:


TRƯỜNG HỢP 1 : láta

np.random.seed(1)
a = np.random.randn(9, 6).astype('float32')
b = np.random.randn(6, 6).astype('float32')

for i in range(1, len(a)):
    print(list(np.dot(a[:i], b)[0])) # full shape: (i, 6)
[-0.9044868,  -1.1708502, 0.90713596, 3.5594249, 1.1374012, -1.3826287]
[-0.90448684, -1.1708503, 0.9071359,  3.5594249, 1.1374011, -1.3826288]
[-0.90448684, -1.1708503, 0.9071359,  3.5594249, 1.1374011, -1.3826288]
[-0.90448684, -1.1708503, 0.907136,   3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136,   3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136,   3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136,   3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136,   3.5594249, 1.1374011, -1.3826287]

Kết quả khác nhau, mặc dù lát cắt được lấy từ cùng một số chính xác được nhân lên.


TRƯỜNG HỢP 2 : làm phẳng a, lấy phiên bản 1D b, sau đó cắt a:

np.random.seed(1)
a = np.random.randn(9, 6).astype('float32')
b = np.random.randn(1, 6).astype('float32')

for i in range(1, len(a)):
    a_flat = np.expand_dims(a[:i].flatten(), -1) # keep 2D
    print(list(np.dot(a_flat, b)[0])) # full shape: (i*6, 6)
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]

TRƯỜNG HỢP 3 : kiểm soát mạnh mẽ hơn; đặt tất cả các mục nhập không liên quan về 0 : thêm a[1:] = 0vào mã CASE 1. Kết quả: sự khác biệt vẫn tồn tại.


TRƯỜNG HỢP 4 : kiểm tra các chỉ số khác ngoài [0]; như đối với [0], các kết quả bắt đầu ổn định một số mở rộng mảng cố định từ điểm sáng tạo của chúng. Đầu ra

np.random.seed(1)
a = np.random.randn(9, 6).astype('float32')
b = np.random.randn(6, 6).astype('float32')

for j in range(len(a) - 2):
    for i in range(1, len(a)):
        res = np.dot(a[:i], b)
        try:    print(list(res[j]))
        except: pass
    print()

Do đó, đối với trường hợp 2D * 2D, kết quả khác nhau - nhưng phù hợp với 1D * 1D. Từ một số bài đọc của tôi, điều này dường như xuất phát từ 1D-1D bằng cách sử dụng phép cộng đơn giản, trong khi 2D-2D sử dụng 'fancier', bổ sung tăng hiệu suất có thể kém chính xác hơn (ví dụ: bổ sung cặp đôi thì ngược lại). Tuy nhiên, tôi không thể hiểu tại sao sự khác biệt lại biến mất trong trường hợp 1 một lần ađược cắt qua một 'ngưỡng'; càng lớn ab, ngưỡng này càng muộn, nhưng nó luôn tồn tại.

Tất cả đã nói: tại sao np.dotkhông chính xác (và không nhất quán) cho mảng ND-ND? Git có liên quan


Thông tin bổ sung :

  • Môi trường : HĐH Win-10, Python 3.7.4, Spyder 3.3.6 IDE, Anaconda 3.0 2019/10
  • CPU : i7-7700HQ 2,8 GHz
  • Numpy v1.16.5

Thư viện thủ phạm có thể có : Numpy MKL - cũng là thư viện BLASS; cảm ơn Bi Rico đã chú ý


Mã kiểm tra ứng suất : như đã lưu ý, sự khác biệt làm trầm trọng thêm về tần số w / mảng lớn hơn; nếu ở trên không thể tái tạo, bên dưới nên (nếu không, hãy thử độ mờ lớn hơn). Đầu ra của tôi

np.random.seed(1)
a = (0.01*np.random.randn(9, 9999)).astype('float32') # first multiply then type-cast
b = (0.01*np.random.randn(9999, 6)).astype('float32') # *0.01 to bound mults to < 1

for i in range(1, len(a)):
    print(list(np.dot(a[:i], b)[0]))

Mức độ nghiêm trọng của sự cố : sự khác biệt được hiển thị là 'nhỏ', nhưng không còn như vậy khi hoạt động trên mạng thần kinh với hàng tỷ số được nhân lên trong vài giây và hàng nghìn tỷ trên toàn bộ thời gian chạy; độ chính xác của mô hình được báo cáo khác nhau bởi toàn bộ 10 phần trăm, trên mỗi luồng này .

Dưới đây là một gif của mảng kết quả từ ăn đến một mô hình những gì cơ bản a[0], w / len(a)==1vs len(a)==32:


Kết quả PLATFORMS KHÁC , theo và nhờ vào thử nghiệm của Paul :

Trường hợp 1 được sao chép (một phần) :

  • Google Colab VM - Intel Xeon 2.3 G-Hz - Jupyter - Python 3.6.8
  • Máy tính để bàn Win-10 Pro Docker - Intel i7-8700K - jupyter / scipy-notebook - Python 3.7.3
  • Ubuntu 18.04.2 LTS + Docker - AMD FX-8150 - jupyter / scipy-notebook - Python 3.7.3

Lưu ý : những lỗi này mang lại lỗi thấp hơn nhiều so với hình trên; hai mục trên hàng đầu tiên bị tắt 1 trong chữ số có nghĩa ít nhất từ ​​các mục tương ứng trong các hàng khác.

Trường hợp 1 không được sao chép :

  • Ubuntu 18.04.3 LTS - Intel i7-8700K - IPython 5.5.0 - Python 2.7.15+ và 3.6.8 (2 bài kiểm tra)
  • Ubuntu 18.04.3 LTS - Intel i5-3320M - IPython 5.5.0 - Python 2.7.15+
  • Ubuntu 18.04.2 LTS - AMD FX-8150 - IPython 5.5.0 - Python 2.7.15rc1

Ghi chú :

  • Các môi trường máy tính xách tay và jupyter Colab được liên kết cho thấy sự khác biệt ít hơn nhiều (và chỉ cho hai hàng đầu tiên) so với quan sát trên hệ thống của tôi. Ngoài ra, Trường hợp 2 không bao giờ (chưa) cho thấy sự thiếu chính xác.
  • Trong mẫu rất hạn chế này, môi trường Jupyter (Dockerized) hiện tại dễ bị ảnh hưởng hơn môi trường IPython.
  • np.show_config()quá dài để đăng, nhưng tóm lại: IPython envs dựa trên BLAS / LAPACK; Colab dựa trên OpenBLAS. Trong IPython Linux envs, các thư viện BLAS được cài đặt hệ thống - trong Jupyter và Colab, chúng đến từ / opt / conda / lib

CẬP NHẬT : câu trả lời được chấp nhận là chính xác, nhưng rộng và không đầy đủ. Câu hỏi vẫn mở cho bất kỳ ai có thể giải thích hành vi ở cấp mã - cụ thể là thuật toán chính xác được sử dụng np.dotvà cách giải thích 'sự không nhất quán nhất quán' được quan sát trong các kết quả trên (cũng xem bình luận). Dưới đây là một số triển khai trực tiếp ngoài việc giải mã của tôi: sdot.c - Arraytypes.c.src


Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Samuel Liew

Các thuật toán chung cho việc ndarraysbỏ qua mất chính xác số. Bởi vì để đơn giản chúng reduce-sumdọc theo từng trục, thứ tự của các thao tác có thể không phải là tối ưu ... Lưu ý rằng nếu bạn nhớ lỗi chính xác, bạn cũng có thể sử dụngfloat64
Vitor SRG

Tôi có thể không có thời gian để xem xét vào ngày mai, vì vậy trao giải thưởng tiền thưởng ngay bây giờ.
Paul

@Paul Dù sao nó cũng sẽ được trao cho câu trả lời được bình chọn cao nhất - nhưng không sao, cảm ơn vì đã thông báo
OverLordGoldDragon

Câu trả lời:


7

Điều này có vẻ như không thể tránh khỏi số lượng chính xác. Như đã giải thích ở đây , NumPy sử dụng phương pháp BLAS được tối ưu hóa, được điều chỉnh cẩn thận để nhân ma trận . Điều này có nghĩa là có lẽ chuỗi các hoạt động (tổng và sản phẩm) theo sau để nhân 2 ma trận, thay đổi khi kích thước của ma trận thay đổi.

Cố gắng rõ ràng hơn, chúng ta biết rằng, về mặt toán học , mỗi phần tử của ma trận kết quả có thể được tính là tích của hai vectơ (các dãy số có độ dài bằng nhau). Nhưng đây là không phải cách NumPy tính toán một yếu tố của ma trận kết quả. Nguyên vẹn có các thuật toán hiệu quả hơn nhưng phức tạp hơn, như thuật toán Strassen , có được kết quả tương tự mà không cần tính toán trực tiếp sản phẩm chấm hàng cột.

Khi sử dụng các thuật toán như vậy, ngay cả khi phần tử C ij của ma trận kết quả C = AB được định nghĩa toán học là sản phẩm chấm của hàng thứ i của A với cột thứ j của B , nếu bạn nhân một ma trận A2 có cùng hàng thứ i với A với ma trận B2 có cùng cột thứ j với B , phần tử C2 ij sẽ thực sự được tính theo một chuỗi các hoạt động khác nhau (phụ thuộc vào toàn bộ A2B2 ma trận), có thể dẫn đến các lỗi số khác nhau.

Đó là lý do tại sao, ngay cả khi về mặt toán học C ij = C2 ij (như trong CASE 1 của bạn), chuỗi hoạt động khác nhau theo thuật toán trong các phép tính (do thay đổi kích thước ma trận) dẫn đến các lỗi số khác nhau. Lỗi số cũng giải thích các kết quả hơi khác nhau tùy thuộc vào môi trường và thực tế là, trong một số trường hợp, đối với một số môi trường, lỗi số có thể không có.


2
Cảm ơn liên kết, nó dường như chứa thông tin liên quan - tuy nhiên, câu trả lời của bạn có thể chi tiết hơn, cho đến nay nó là một cách diễn giải các bình luận bên dưới câu hỏi. Ví dụ, SO được liên kết hiển thị Cmã trực tiếp và cung cấp các giải thích ở cấp thuật toán, do đó, nó đang đi đúng hướng.
OverLordGoldDragon

Nó cũng không phải là "không thể tránh khỏi", như được hiển thị ở dưới cùng của câu hỏi - và mức độ không chính xác khác nhau giữa các môi trường, vẫn không giải thích được
OverLordGoldDragon

1
@OverLordGoldDragon: (1) Một ví dụ tầm thường với phép cộng: lấy số n, lấy số ksao cho dưới độ chính xác của kchữ số mantissa cuối cùng. Đối với phao bản địa của Python n = 1.0k = 1e-16hoạt động. Bây giờ hãy để ks = [k] * 100. Xem rằng sum([n] + ks) == n, trong khi sum(ks + [n]) > n, đó là, thứ tự tổng quan trọng. (2) Các CPU hiện đại có một số đơn vị thực hiện song song các phép a + b + c + dtoán dấu phẩy động (FP) và thứ tự được tính toán trên CPU không được xác định, ngay cả khi lệnh a + bxuất hiện trước c + dtrong mã máy.
9000

1
@OverLordGoldDragon Bạn nên lưu ý rằng hầu hết các số mà bạn yêu cầu chương trình của bạn xử lý không thể được biểu diễn chính xác bằng một dấu chấm động. Hãy thử format(0.01, '.30f'). Nếu ngay cả một số đơn giản như 0.01không thể được biểu diễn chính xác bằng dấu phẩy NumPy, thì không cần phải biết chi tiết sâu của thuật toán nhân ma trận NumPy để hiểu điểm của câu trả lời của tôi; đó là các ma trận bắt đầu khác nhau dẫn đến các chuỗi hoạt động khác nhau , do đó các kết quả bằng nhau về mặt toán học có thể khác nhau một lượng nhỏ do các lỗi số.
mmj

2
@OverLordGoldDragon re: ma thuật đen. Có một tờ giấy yêu cầu đọc ở một vài MOOCs CS. Tôi nhớ lại không phải là tuyệt vời, nhưng tôi nghĩ rằng đây là: itu.dk/~sestoft/bach Bachelor / NIEE754_article.pdf
Paul
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.