Khi nào nên sử dụng log1p và expm1?


30

Tôi có một câu hỏi đơn giản rất khó đối với Google (bên cạnh tiêu chuẩn mà mọi nhà khoa học máy tính nên biết về bài toán số học dấu phẩy động ).

Khi nào các chức năng như log1phoặc expm1được sử dụng thay vì logexp? Khi nào chúng không nên được sử dụng? Làm thế nào để thực hiện khác nhau của các chức năng đó khác nhau về cách sử dụng của họ?


2
Chào mừng bạn đến với Scicomp.SE! Đó là một câu hỏi rất hợp lý, nhưng sẽ dễ trả lời hơn nếu bạn giải thích một chút log1p bạn đang đề cập (đặc biệt là cách nó được thực hiện, vì vậy chúng tôi không phải đoán).
Christian Clason

4
Đối với các đối số có giá trị thực, log1p và expm1 nên được sử dụng khi nhỏ, ví dụ: khi với độ chính xác của dấu phẩy động. Xem, ví dụ: docs.scipy.org/doc/numpy/reference/generated/numpy.Exm1.htmldocs.scipy.org/doc/numpy/reference/generated/numpy.log1p.html . (x)(x)x1+x=1
GoHokies

@ChristianClason cảm ơn, tôi chủ yếu đề cập đến C ++ std hoặc R, nhưng khi bạn hỏi tôi bắt đầu nghĩ rằng việc tìm hiểu về sự khác biệt trong việc triển khai cũng sẽ rất thú vị.
Tim


1
@ user2186862 "khi nhỏ" là chính xác, nhưng không chỉ "khi về độ chính xác của dấu phẩy động" (xảy ra với trong số học chính xác kép thông thường). Các trang tài liệu bạn liên kết cho thấy rằng chúng đã hữu ích cho , chẳng hạn. x1+x=1x1016x1010
Federico Poloni

Câu trả lời:


25

Chúng ta đều biết rằng ngụ ý rằng cho , chúng ta có . Điều này có nghĩa là nếu chúng ta phải đánh giá theo dấu phẩy động , cho hủy bỏ thảm khốc có thể xảy ra.

exp(x)=n=0xnn!=1+x+12x2+
|x|1exp(x)1+xexp(x)1|x|1

Điều này có thể dễ dàng chứng minh trong python:

>>> from math import (exp, expm1)

>>> x = 1e-8
>>> exp(x) - 1
9.99999993922529e-09
>>> expm1(x)
1.0000000050000001e-08

>>> x = 1e-22
>>> exp(x) - 1
0.0
>>> expm1(x)
1e-22

Các giá trị chính xác là

exp(108)1=0.000000010000000050000000166666667083333334166666668exp(1022)1=0.000000000000000000000100000000000000000000005000000

Nói chung, việc triển khai "chính xác" expexpm1phải chính xác không quá 1ULP (tức là một đơn vị của vị trí cuối cùng). Tuy nhiên, vì việc đạt được độ chính xác này dẫn đến mã "chậm", đôi khi có triển khai nhanh, kém chính xác hơn. Ví dụ, trong CUDA chúng ta có expfexpm1f, fviết tắt của từ nhanh. Theo hướng dẫn lập trình CUDA C, ứng dụng. D các expfcó lỗi của 2ULP.

Nếu bạn không quan tâm đến các lỗi theo thứ tự một vài ULPS, thông thường các cách triển khai khác nhau của hàm số mũ là tương đương, nhưng hãy cẩn thận rằng các lỗi có thể bị ẩn ở đâu đó ... (Bạn có nhớ lỗi Pentium FDIV không?)

Vì vậy, khá rõ ràng rằng expm1nên được sử dụng để tính toán cho nhỏ . Sử dụng nó cho chung không có hại, vì dự kiến ​​sẽ chính xác trong phạm vi đầy đủ của nó:exp(x)1xxexpm1

>>> exp(200)-1 == exp(200) == expm1(200)
True

(Trong ví dụ ở trên thấp hơn 1ULP của , vì vậy cả ba biểu thức trả về chính xác cùng một số dấu phẩy động.)1exp(200)

Một cuộc thảo luận tương tự giữ cho các hàm nghịch đảo loglog1pvì cho .log(1+x)x|x|1


1
Câu trả lời này đã được chứa trong các bình luận cho câu hỏi OP. Tuy nhiên tôi cảm thấy hữu ích khi cung cấp một tài khoản dài hơn (mặc dù cơ bản) chỉ để rõ ràng, với hy vọng nó sẽ hữu ích cho một số độc giả.
Stefano M

OK, nhưng sau đó người ta có thể kết luận một cách đơn giản "vì vậy tôi luôn có thể sử dụng expm1 thay vì exp" ...
Tim

1
@tim kết luận của bạn là sai: bạn luôn có thể sử dụng expm1(x)thay vì exp(x)-1. Tất nhiên exp(x) == exp(x) - 1không nói chung.
Stefano M

OK, điều đó là rõ ràng. Và có bất kỳ tiêu chí cắt giảm rõ ràng nào cho không? x1
Tim

1
@Tim không có ngưỡng cắt rõ ràng và câu trả lời phụ thuộc vào độ chính xác của việc thực hiện dấu phẩy động (và vấn đề đang được giải quyết). Mặc dù expm1(x)phải chính xác đến 1ULP trong toàn bộ phạm vi , dần dần mất độ chính xác từ một vài ULP khi đến sự cố hoàn toàn khi , trong đó là máy epsilon. 0x1exp(x) - 1x1x<ϵϵ
Stefano M

1

Để mở rộng sự khác biệt giữa loglog1pcó thể giúp nhớ lại biểu đồ nếu logarit:

Logarit

Nếu dữ liệu của bạn chứa số 0, thì có lẽ bạn không muốn sử dụng logvì nó không được xác định ở mức 0. Và khi tiến đến , giá trị của tiếp cận . Vì vậy, nếu giá trị của bạn gần bằng , thì giá trị của có khả năng là một số âm lớn. Ví dụ: và , v.v. Điều này có thể hữu ích, nhưng nó cũng có thể làm biến dạng dữ liệu của bạn thành các số âm lớn, đặc biệt nếu tập dữ liệu của bạn cũng chứa các số lớn hơn nhiều so với không.x0ln(x)x0ln(x)ln(1e)=1ln(1e10)=10

Mặt khác, khi tiến đến , giá trị của tiếp cận từ hướng tích cực. Ví dụ: và . Vì vậy, chỉ tạo ra các giá trị dương và loại bỏ 'nguy hiểm' của số âm lớn. Điều này thường đảm bảo phân phối đồng nhất hơn khi tập dữ liệu chứa các số gần bằng không.x0ln(x+1)0ln(1+1e)0.31ln(1+1e10)0.000045log1p

Nói tóm lại, nếu tập dữ liệu lớn hơn , thì thường là ổn. Nhưng, nếu tập dữ liệu có các số từ đến , thì thường tốt hơn.10 1log01log1p

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.