Tuyên bố từ chối trách nhiệm: Tôi chủ yếu viết bài này với cân nhắc về cú pháp và hành vi chung. Tôi không quen thuộc với khía cạnh bộ nhớ và CPU của các phương pháp được mô tả và tôi nhắm câu trả lời này vào những người có bộ dữ liệu nhỏ hợp lý, sao cho chất lượng của phép nội suy có thể là khía cạnh chính cần xem xét. Tôi biết rằng khi làm việc với các tập dữ liệu rất lớn, các phương pháp hoạt động tốt hơn (cụ thể là griddata
và Rbf
) có thể không khả thi.
Tôi sẽ so sánh ba loại phương pháp nội suy đa chiều ( interp2d
/ splines, griddata
và Rbf
). Tôi sẽ đặt chúng vào hai loại nhiệm vụ nội suy và hai loại chức năng cơ bản (các điểm sẽ được nội suy). Các ví dụ cụ thể sẽ chứng minh nội suy hai chiều, nhưng các phương pháp khả thi có thể áp dụng trong các kích thước tùy ý. Mỗi phương pháp cung cấp nhiều loại nội suy khác nhau; trong mọi trường hợp, tôi sẽ sử dụng phép nội suy khối (hoặc một cái gì đó gần bằng 1 ). Điều quan trọng cần lưu ý là bất cứ khi nào bạn sử dụng phép nội suy, bạn sẽ đưa ra độ lệch so với dữ liệu thô của mình và các phương pháp cụ thể được sử dụng sẽ ảnh hưởng đến các tạo tác mà bạn sẽ kết thúc. Luôn nhận thức được điều này và nội suy một cách có trách nhiệm.
Hai nhiệm vụ nội suy sẽ là
- lấy mẫu ngược (dữ liệu đầu vào nằm trên lưới hình chữ nhật, dữ liệu đầu ra nằm trên lưới dày đặc hơn)
- nội suy dữ liệu phân tán vào lưới thông thường
Hai chức năng (trên miền [x,y] in [-1,1]x[-1,1]
) sẽ là
- một chức năng mượt mà và thân thiện :
cos(pi*x)*sin(pi*y)
; phạm vi trong[-1, 1]
- một hàm ác (và đặc biệt, không liên tục):
x*y/(x^2+y^2)
với giá trị 0,5 gần gốc; phạm vi trong[-0.5, 0.5]
Đây là cách họ trông:
Đầu tiên tôi sẽ trình bày cách thức hoạt động của ba phương thức trong bốn bài kiểm tra này, sau đó tôi sẽ trình bày chi tiết về cú pháp của cả ba. Nếu bạn biết những gì bạn nên mong đợi từ một phương thức, bạn có thể không muốn lãng phí thời gian của mình để học cú pháp của nó (nhìn vào bạn, interp2d
).
Dữ liệu thử nghiệm
Vì mục đích rõ ràng, đây là đoạn mã mà tôi đã tạo dữ liệu đầu vào. Mặc dù trong trường hợp cụ thể này, tôi rõ ràng là đã biết về chức năng bên dưới dữ liệu, tôi sẽ chỉ sử dụng chức năng này để tạo đầu vào cho các phương pháp nội suy. Tôi sử dụng numpy để thuận tiện (và chủ yếu là để tạo dữ liệu), nhưng một mình scipy cũng đủ.
import numpy as np
import scipy.interpolate as interp
# auxiliary function for mesh generation
def gimme_mesh(n):
minval = -1
maxval = 1
# produce an asymmetric shape in order to catch issues with transpositions
return np.meshgrid(np.linspace(minval,maxval,n), np.linspace(minval,maxval,n+1))
# set up underlying test functions, vectorized
def fun_smooth(x, y):
return np.cos(np.pi*x)*np.sin(np.pi*y)
def fun_evil(x, y):
# watch out for singular origin; function has no unique limit there
return np.where(x**2+y**2>1e-10, x*y/(x**2+y**2), 0.5)
# sparse input mesh, 6x7 in shape
N_sparse = 6
x_sparse,y_sparse = gimme_mesh(N_sparse)
z_sparse_smooth = fun_smooth(x_sparse, y_sparse)
z_sparse_evil = fun_evil(x_sparse, y_sparse)
# scattered input points, 10^2 altogether (shape (100,))
N_scattered = 10
x_scattered,y_scattered = np.random.rand(2,N_scattered**2)*2 - 1
z_scattered_smooth = fun_smooth(x_scattered, y_scattered)
z_scattered_evil = fun_evil(x_scattered, y_scattered)
# dense output mesh, 20x21 in shape
N_dense = 20
x_dense,y_dense = gimme_mesh(N_dense)
Chức năng mượt mà và upsampling
Hãy bắt đầu với nhiệm vụ dễ dàng nhất. Dưới đây là cách lấy mẫu từ một lưới có hình dạng [6,7]
thành một trong những [20,21]
hoạt động cho chức năng kiểm tra trơn tru:
Mặc dù đây là một nhiệm vụ đơn giản, nhưng đã có sự khác biệt nhỏ giữa các đầu ra. Thoạt nhìn cả ba đầu ra đều hợp lý. Có hai đặc điểm cần lưu ý, dựa trên kiến thức trước đây của chúng tôi về chức năng cơ bản: trường hợp chính giữa griddata
làm sai lệch dữ liệu nhiều nhất. Lưu ý y==-1
ranh giới của biểu đồ (gần x
nhãn nhất): hàm phải hoàn toàn bằng 0 (vì y==-1
là một đường nút cho hàm trơn), nhưng đây không phải là trường hợp của griddata
. Cũng lưu ý x==-1
ranh giới của các ô (phía sau, bên trái): hàm cơ bản có cực đại cục bộ (ngụ ý gradient bằng không gần đường biên) tại [-1, -0.5]
, nhưng griddata
đầu ra hiển thị rõ ràng gradient khác 0 trong vùng này. Hiệu quả là tinh tế, nhưng đó là một sự thiên vị không hơn không kém. (Sự trung thực củaRbf
thậm chí còn tốt hơn với lựa chọn mặc định của các hàm xuyên tâm, được lồng tiếng multiquadratic
.)
Chức năng ác và upsampling
Một nhiệm vụ khó hơn một chút là thực hiện lấy mẫu trên chức năng ác của chúng ta:
Sự khác biệt rõ ràng đang bắt đầu hiển thị giữa ba phương pháp. Nhìn vào các biểu đồ bề mặt, có các điểm cực trị giả rõ ràng xuất hiện trong đầu ra từ interp2d
(lưu ý hai bướu ở phía bên phải của bề mặt được vẽ). Trong khi griddata
và Rbf
dường như tạo ra kết quả tương tự ở cái nhìn đầu tiên, sau này dường như để tạo ra một tối thiểu sâu hơn gần [0.4, -0.4]
đó là vắng mặt trong các chức năng cơ bản.
Tuy nhiên, có một khía cạnh quan trọng Rbf
vượt trội hơn nhiều: nó tôn trọng tính đối xứng của hàm cơ bản (điều này tất nhiên cũng có thể thực hiện được nhờ tính đối xứng của lưới mẫu). Đầu ra từ griddata
phá vỡ tính đối xứng của các điểm mẫu, vốn đã yếu trong trường hợp mịn.
Chức năng mượt mà và dữ liệu phân tán
Thông thường người ta muốn thực hiện nội suy trên dữ liệu phân tán. Vì lý do này, tôi hy vọng những bài kiểm tra này sẽ quan trọng hơn. Như được trình bày ở trên, các điểm mẫu được chọn giả đồng nhất trong miền quan tâm. Trong các tình huống thực tế, bạn có thể có thêm nhiễu với mỗi phép đo và bạn nên cân nhắc xem liệu có hợp lý khi nội suy dữ liệu thô của bạn khi bắt đầu hay không.
Đầu ra cho chức năng mượt mà:
Bây giờ đã có một chút chương trình kinh dị đang diễn ra. Tôi đã cắt bớt đầu ra từ đầu interp2d
đến giữa [-1, 1]
dành riêng cho việc vẽ biểu đồ, để lưu giữ ít nhất một lượng thông tin tối thiểu. Rõ ràng là trong khi một số hình dạng cơ bản hiện diện, có những vùng nhiễu rất lớn nơi phương pháp hoàn toàn bị phá vỡ. Trường hợp thứ hai griddata
tái tạo hình dạng khá đẹp, nhưng lưu ý các vùng trắng ở biên giới của ô đường bao. Điều này là do thực tế là nó griddata
chỉ hoạt động bên trong vỏ lồi của các điểm dữ liệu đầu vào (nói cách khác, nó không thực hiện bất kỳ phép ngoại suy nào ). Tôi đã giữ giá trị NaN mặc định cho các điểm đầu ra nằm bên ngoài vỏ lồi. 2 Xem xét các tính năng này, Rbf
dường như hoạt động tốt nhất.
Chức năng ác và dữ liệu phân tán
Và khoảnh khắc mà tất cả chúng ta đang chờ đợi:
Không có gì ngạc nhiên khi interp2d
bỏ cuộc. Trên thực tế, trong cuộc gọi đến, interp2d
bạn sẽ mong đợi một số người thân thiện RuntimeWarning
phàn nàn về việc không thể thi công đường ống. Đối với hai phương pháp còn lại, Rbf
dường như tạo ra kết quả tốt nhất, ngay cả khi gần biên giới của miền nơi kết quả được ngoại suy.
Vì vậy, hãy để tôi nói vài lời về ba phương pháp, theo thứ tự ưu tiên giảm dần (sao cho điều tồi tệ nhất là ít có khả năng được mọi người đọc nhất).
scipy.interpolate.Rbf
Các Rbf
lớp viết tắt của "hàm cơ sở xuyên tâm". Thành thật mà nói, tôi chưa bao giờ xem xét cách tiếp cận này cho đến khi tôi bắt đầu nghiên cứu cho bài đăng này, nhưng tôi khá chắc chắn rằng tôi sẽ sử dụng chúng trong tương lai.
Cũng giống như các phương thức dựa trên spline (xem phần sau), cách sử dụng có hai bước: đầu tiên tạo một cá thể Rbf
lớp có thể gọi dựa trên dữ liệu đầu vào, sau đó gọi đối tượng này cho một lưới đầu ra nhất định để thu được kết quả nội suy. Ví dụ từ thử nghiệm lấy mẫu ngược mượt mà:
import scipy.interpolate as interp
zfun_smooth_rbf = interp.Rbf(x_sparse, y_sparse, z_sparse_smooth, function='cubic', smooth=0) # default smooth=0 for interpolation
z_dense_smooth_rbf = zfun_smooth_rbf(x_dense, y_dense) # not really a function, but a callable class instance
Lưu ý rằng cả hai điểm đầu vào và đầu ra đều là mảng 2d trong trường hợp này và đầu ra z_dense_smooth_rbf
có hình dạng giống như x_dense
và y_dense
không có bất kỳ nỗ lực nào. Cũng lưu ý rằng Rbf
hỗ trợ các kích thước tùy ý để nội suy.
Vì thế, scipy.interpolate.Rbf
- tạo ra đầu ra hoạt động tốt ngay cả đối với dữ liệu đầu vào điên rồ
- hỗ trợ nội suy trong các kích thước cao hơn
- ngoại suy bên ngoài vỏ lồi của các điểm đầu vào (tất nhiên ngoại suy luôn là một canh bạc và bạn thường không nên dựa vào nó)
- tạo một bộ nội suy như một bước đầu tiên, vì vậy việc đánh giá nó ở các điểm đầu ra khác nhau sẽ ít tốn công sức hơn
- có thể có các điểm đầu ra có hình dạng tùy ý (trái ngược với việc bị giới hạn trong các mắt lưới hình chữ nhật, hãy xem sau)
- dễ bảo toàn tính đối xứng của dữ liệu đầu vào
- hỗ trợ nhiều loại chức năng bố trí hình tròn cho từ khóa
function
: multiquadric
, inverse
, gaussian
, linear
, cubic
, quintic
, thin_plate
và tùy ý người dùng định nghĩa
scipy.interpolate.griddata
Yêu thích trước đây của tôi griddata
, là một workhorse chung để nội suy theo các chiều tùy ý. Nó không thực hiện phép ngoại suy ngoài việc thiết lập một giá trị đặt trước duy nhất cho các điểm bên ngoài vỏ lồi của các điểm nút, nhưng vì phép ngoại suy là một việc rất hay thay đổi và nguy hiểm, nên điều này không nhất thiết phải là một kẻ lừa đảo. Ví dụ sử dụng:
z_dense_smooth_griddata = interp.griddata(np.array([x_sparse.ravel(),y_sparse.ravel()]).T,
z_sparse_smooth.ravel(),
(x_dense,y_dense), method='cubic') # default method is linear
Lưu ý cú pháp hơi kludgy. Các điểm đầu vào phải được chỉ định trong một mảng hình dạng [N, D]
theo D
kích thước. Để làm được điều này, trước tiên chúng ta phải làm phẳng các mảng tọa độ 2d (sử dụng ravel
), sau đó nối các mảng và chuyển vị kết quả. Có nhiều cách để làm điều này, nhưng tất cả chúng đều có vẻ cồng kềnh. z
Dữ liệu đầu vào cũng phải được làm phẳng. Chúng ta có một chút tự do hơn khi nói đến các điểm đầu ra: vì một số lý do, chúng cũng có thể được chỉ định như một bộ nhiều mảng đa chiều. Lưu ý rằng giá help
trị của griddata
là gây hiểu lầm, vì nó cho thấy điều này cũng đúng với các điểm đầu vào (ít nhất là đối với phiên bản 0.17.0):
griddata(points, values, xi, method='linear', fill_value=nan, rescale=False)
Interpolate unstructured D-dimensional data.
Parameters
----------
points : ndarray of floats, shape (n, D)
Data point coordinates. Can either be an array of
shape (n, D), or a tuple of `ndim` arrays.
values : ndarray of float or complex, shape (n,)
Data values.
xi : ndarray of float, shape (M, D)
Points at which to interpolate data.
Tóm lại, scipy.interpolate.griddata
- tạo ra đầu ra hoạt động tốt ngay cả đối với dữ liệu đầu vào điên rồ
- hỗ trợ nội suy trong các kích thước cao hơn
- không thực hiện phép ngoại suy, một giá trị duy nhất có thể được đặt cho đầu ra bên ngoài vỏ lồi của các điểm đầu vào (xem
fill_value
)
- tính toán các giá trị nội suy trong một lệnh gọi, do đó, việc thăm dò nhiều bộ điểm đầu ra bắt đầu từ đầu
- có thể có các điểm đầu ra có hình dạng tùy ý
- hỗ trợ nội suy tuyến tính và láng giềng gần nhất trong các kích thước tùy ý, lập phương trong 1d và 2d. Tương ứng, sử dụng nội suy tuyến tính và láng giềng gần nhất
NearestNDInterpolator
và LinearNDInterpolator
dưới mui xe. Phép nội suy khối 1d sử dụng một spline, phép nội suy khối 2d sử dụng CloughTocher2DInterpolator
để xây dựng một bộ nội suy khối vuông phân biệt liên tục.
- có thể vi phạm tính đối xứng của dữ liệu đầu vào
scipy.interpolate.interp2d
/scipy.interpolate.bisplrep
Lý do duy nhất tôi đang thảo luận interp2d
và họ hàng của nó là nó có một cái tên lừa đảo, và mọi người có thể sẽ thử sử dụng nó. Cảnh báo spoiler: không sử dụng nó (kể từ phiên bản scipy 0.17.0). Nó đã đặc biệt hơn các môn học trước ở chỗ nó được sử dụng đặc biệt cho nội suy hai chiều, nhưng tôi nghi ngờ đây là trường hợp phổ biến nhất cho nội suy đa biến.
Về mặt cú pháp, interp2d
tương tự như Rbf
ở chỗ trước tiên nó cần xây dựng một thể hiện nội suy, có thể được gọi để cung cấp các giá trị nội suy thực tế. Tuy nhiên, có một điểm khó khăn: các điểm đầu ra phải được đặt trên một lưới hình chữ nhật, vì vậy các đầu vào đi vào lệnh gọi đến bộ nội suy phải là vectơ 1d trải rộng lưới đầu ra, như thể từ numpy.meshgrid
:
# reminder: x_sparse and y_sparse are of shape [6, 7] from numpy.meshgrid
zfun_smooth_interp2d = interp.interp2d(x_sparse, y_sparse, z_sparse_smooth, kind='cubic') # default kind is 'linear'
# reminder: x_dense and y_dense are of shape [20, 21] from numpy.meshgrid
xvec = x_dense[0,:] # 1d array of unique x values, 20 elements
yvec = y_dense[:,0] # 1d array of unique y values, 21 elements
z_dense_smooth_interp2d = zfun_smooth_interp2d(xvec,yvec) # output is [20, 21]-shaped array
Một trong những sai lầm phổ biến nhất khi sử dụng interp2d
là đặt các mắt lưới 2d đầy đủ của bạn vào lệnh gọi nội suy, điều này dẫn đến tiêu thụ bộ nhớ bùng nổ và hy vọng là quá vội vàng MemoryError
.
Bây giờ, vấn đề lớn nhất interp2d
là nó thường không hoạt động. Để hiểu được điều này, chúng ta phải xem xét bên dưới. Hóa ra đó interp2d
là trình bao bọc cho các chức năng cấp thấp hơn bisplrep
+ bisplev
, lần lượt là trình bao bọc cho các quy trình FITPACK (được viết bằng Fortran). Lệnh gọi tương đương với ví dụ trước sẽ là
kind = 'cubic'
if kind=='linear':
kx=ky=1
elif kind=='cubic':
kx=ky=3
elif kind=='quintic':
kx=ky=5
# bisplrep constructs a spline representation, bisplev evaluates the spline at given points
bisp_smooth = interp.bisplrep(x_sparse.ravel(),y_sparse.ravel(),z_sparse_smooth.ravel(),kx=kx,ky=ky,s=0)
z_dense_smooth_bisplrep = interp.bisplev(xvec,yvec,bisp_smooth).T # note the transpose
Bây giờ, đây là nội dung về interp2d
: (trong phiên bản scipy 0.17.0), có một nhận xét hayinterpolate/interpolate.py
cho interp2d
:
if not rectangular_grid:
# TODO: surfit is really not meant for interpolation!
self.tck = fitpack.bisplrep(x, y, z, kx=kx, ky=ky, s=0.0)
và thực sự trong interpolate/fitpack.py
, bisplrep
có một số thiết lập và cuối cùng
tx, ty, c, o = _fitpack._surfit(x, y, z, w, xb, xe, yb, ye, kx, ky,
task, s, eps, tx, ty, nxest, nyest,
wrk, lwrk1, lwrk2)
Và đó là nó. Các quy trình cơ bản interp2d
không thực sự dùng để thực hiện nội suy. Chúng có thể đủ cho dữ liệu hoạt động tốt, nhưng trong hoàn cảnh thực tế, bạn có thể sẽ muốn sử dụng thứ khác.
Chỉ để kết luận, interpolate.interp2d
- có thể dẫn đến hiện tượng tạo tác ngay cả với dữ liệu được xử lý tốt
- dành riêng cho các vấn đề lưỡng biến (mặc dù có giới hạn
interpn
đối với các điểm đầu vào được xác định trên lưới)
- thực hiện phép ngoại suy
- tạo một bộ nội suy như một bước đầu tiên, vì vậy việc đánh giá nó ở các điểm đầu ra khác nhau sẽ ít tốn công sức hơn
- chỉ có thể tạo ra đầu ra trên một lưới hình chữ nhật, đối với đầu ra phân tán, bạn sẽ phải gọi bộ nội suy trong một vòng lặp
- hỗ trợ nội suy tuyến tính, khối và ngũ vị
- có thể vi phạm tính đối xứng của dữ liệu đầu vào
1 Tôi khá chắc chắn rằng loại cubic
và linear
hàm cơ sở của Rbf
không hoàn toàn tương ứng với các bộ nội suy khác cùng tên.
2 NaN này cũng là lý do giải thích tại sao biểu đồ bề mặt có vẻ rất kỳ lạ: matplotlib trong lịch sử gặp khó khăn trong việc vẽ các vật thể 3d phức tạp với thông tin độ sâu thích hợp. Các giá trị NaN trong dữ liệu gây nhầm lẫn cho trình kết xuất đồ họa, vì vậy các phần của bề mặt đáng lẽ phải ở phía sau sẽ được vẽ ở phía trước. Đây là một vấn đề với trực quan hóa, chứ không phải nội suy.