Làm cách nào tôi có thể thực hiện nội suy hai chiều bằng scipy?


105

Câu hỏi & Đáp này nhằm mục đích là một phương pháp chuẩn (-ish) liên quan đến phép nội suy hai chiều (và nhiều chiều) bằng cách sử dụng scipy. Thường có những câu hỏi liên quan đến cú pháp cơ bản của các phương pháp nội suy đa chiều khác nhau, tôi hy vọng sẽ giải đáp được những thắc mắc này.

Tôi có một tập hợp các điểm dữ liệu hai chiều nằm rải rác và tôi muốn vẽ chúng như một bề mặt đẹp, tốt hơn là sử dụng thứ gì đó giống contourfhoặc plot_surfacetrong matplotlib.pyplot. Làm cách nào để nội suy dữ liệu hai chiều hoặc nhiều chiều của tôi vào lưới bằng cách sử dụng scipy?

Tôi đã tìm thấy scipy.interpolategói phụ, nhưng tôi vẫn gặp lỗi khi sử dụng interp2dhoặc bisplrephoặc griddatahoặc rbf. Cú pháp thích hợp của các phương thức này là gì?

Câu trả lời:


163

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à griddataRbf) 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, griddataRbf). 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à

  1. 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)
  2. 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à

  1. 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]
  2. 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:

Hình1: chức năng kiểm tra

Đầ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:

fig2: mượt mà upsampling

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 griddatalàm sai lệch dữ liệu nhiều nhất. Lưu ý y==-1ranh giới của biểu đồ (gần xnhãn nhất): hàm phải hoàn toàn bằng 0 (vì y==-1là 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==-1ranh 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ủaRbfthậ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:

Fig3: Ác ma upsampling

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 griddataRbfdườ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 Rbfvượ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ừ griddataphá 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à:

Hình 4: nội suy phân tán mịn

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 griddatatá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ó griddatachỉ 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, Rbfdườ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:

Hình 5: Nội suy phân tán tà ác

Không có gì ngạc nhiên khi interp2dbỏ cuộc. Trên thực tế, trong cuộc gọi đến, interp2dbạn sẽ mong đợi một số người thân thiện RuntimeWarningphà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, Rbfdườ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 Rbflớ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ể Rbflớ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_rbfcó hình dạng giống như x_densey_densekhông có bất kỳ nỗ lực nào. Cũng lưu ý rằng Rbfhỗ 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_platevà 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 Dkí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. zDữ 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á helptrị của griddatalà 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 NearestNDInterpolatorLinearNDInterpolatordướ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 interp2dvà 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, interp2dtươ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 interp2dlà đặ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 interp2dlà 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 đó interp2dlà 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, bisplrepcó 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 interp2dkhô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 cubiclinearhàm cơ sở của Rbfkhô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.


2
Rbf có thể tiêu tốn nhiều bộ nhớ hơn lưới dữ liệu tùy thuộc vào số lượng điểm dữ liệu và số thứ nguyên. Ngoài ra, griddata có đối tượng cơ bản là LinearNDInterpolator có thể được sử dụng như Rbf trong 2 bước.
denfromufa

1
Nội suy khối của Griddata được giới hạn ở 2 (?) Thứ nguyên. Đối với các kích thước cao hơn, các lưới thưa thớt dựa trên chebfun đáng được xem xét.
denfromufa

1
hãy để tôi kết thúc nhận xét của mình bằng liên kết này, nơi tôi đã nghiên cứu tất cả các tùy chọn nội suy rải rác: scicomp.stackexchange.com/questions/19137/…
denfromufa

4
nội suy tuyến tính griddata là cục bộ, nội suy khối griddata là toàn cục. Phép ngoại suy không được hỗ trợ vì tôi không có thời gian để tìm ra cách duy trì tính liên tục / tính khác biệt. Rbf là tốt cho các tập dữ liệu nhỏ, nhưng để nội suy n điểm dữ liệu, nó cần phải đảo ngược nxn ma trận, điều này cuối cùng trở nên bất khả thi sau khi n> 5000. Rbf cũng có thể nhạy cảm với việc phân phối dữ liệu và bạn có thể cần phải tinh chỉnh các thông số của nó bằng tay. Có thể thực hiện Rbf cho các tập dữ liệu lớn, nhưng điều này không được thực hiện trong scipy.
pv.

1
đây là rbf cho bộ dữ liệu lớn: github.com/scipy/scipy/issues/5180
denfromufa
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.