2023.4.16 第四十九次周报

目录

矢量化运算

通过循环遍历所有指定点来解决kriging系统

使用了移动窗口技术来减少计算

Ordinary Kriging"的插值方法


矢量化运算

def _exec_vector(self, a, bd, mask):
        """Solves the kriging system as a vectorized operation. This method
        can take a lot of memory for large grids and/or large datasets."""

        npt = bd.shape[0]
        n = self.X_ADJUSTED.shape[0]
        zero_index = None
        zero_value = False

        # use the desired method to invert the kriging matrix
        if self.pseudo_inv:
            a_inv = P_INV[self.pseudo_inv_type](a)
        else:
            a_inv = scipy.linalg.inv(a)

        if np.any(np.absolute(bd) <= self.eps):
            zero_value = True
            zero_index = np.where(np.absolute(bd) <= self.eps)

        b = np.zeros((npt, n + 1, 1))
        b[:, :n, 0] = -self.variogram_function(self.variogram_model_parameters, bd)
        if zero_value and self.exact_values:
            b[zero_index[0], zero_index[1], 0] = 0.0
        b[:, n, 0] = 1.0

        if (~mask).any():
            mask_b = np.repeat(mask[:, np.newaxis, np.newaxis], n + 1, axis=1)
            b = np.ma.array(b, mask=mask_b)

        x = np.dot(a_inv, b.reshape((npt, n + 1)).T).reshape((1, n + 1, npt)).T
        zvalues = np.sum(x[:, :n, 0] * self.Z, axis=1)
        sigmasq = np.sum(x[:, :, 0] * -b[:, :, 0], axis=1)

        return zvalues, sigmasq

方法_exec_vector将kriging系统作为矢量化操作进行求解。它接收三个参数:abdmask

变量a表示kriging矩阵,它是一个大小为n的正方形矩阵,表示n个数据点之间的协方差。变量bd是一个数组,其形状为(npt,),其中包含npt个预测点与构建kriging系统所使用的n个数据点之间的距离。变量mask是一个形状为(npt,)的布尔值数组,指示每个预测点是否被屏蔽。

该方法首先使用由self.pseudo_invself.pseudo_inv_type指定的所需方法计算kriging矩阵a的逆。然后,它构造了kriging系统的右侧向量b。如果bd中任何距离小于或等于self.eps,则相应的b值将设置为0(除非self.exact_values为False,在这种情况下该值将保持不变)。最后,该方法通过将a的逆与b相乘来计算kriging系统的解solution,并返回每个预测点的预测值zvalues和方差sigmasq

请注意,由于使用了矢量化操作,因此该方法在处理大网格和/或大数据集时可能会使用大量内存。

通过循环遍历所有指定点来解决kriging系统

 def _exec_loop(self, a, bd_all, mask):
        """Solves the kriging system by looping over all specified points.
        Less memory-intensive, but involves a Python-level loop."""

        npt = bd_all.shape[0]
        n = self.X_ADJUSTED.shape[0]
        zvalues = np.zeros(npt)
        sigmasq = np.zeros(npt)

        # use the desired method to invert the kriging matrix
        if self.pseudo_inv:
            a_inv = P_INV[self.pseudo_inv_type](a)
        else:
            a_inv = scipy.linalg.inv(a)

        for j in np.nonzero(~mask)[
            0
        ]:  # Note that this is the same thing as range(npt) if mask is not defined,
            bd = bd_all[j]  # otherwise it takes the non-masked elements.
            if np.any(np.absolute(bd) <= self.eps):
                zero_value = True
                zero_index = np.where(np.absolute(bd) <= self.eps)
            else:
                zero_index = None
                zero_value = False

            b = np.zeros((n + 1, 1))
            b[:n, 0] = -self.variogram_function(self.variogram_model_parameters, bd)
            if zero_value and self.exact_values:
                b[zero_index[0], 0] = 0.0
            b[n, 0] = 1.0
            x = np.dot(a_inv, b)
            zvalues[j] = np.sum(x[:n, 0] * self.Z)
            sigmasq[j] = np.sum(x[:, 0] * -b[:, 0])

        return zvalues, sigmasq

该方法_exec_loop通过循环遍历所有指定点来解决kriging系统。这种方法对内存的需求较低,但涉及Python级别的循环。

变量a表示kriging矩阵,它是一个大小为n的正方形矩阵,表示n个数据点之间的协方差。变量bd_all是一个数组,其形状为(npt, n),其中包含了npt个预测点与构建kriging系统所使用的所有n个数据点之间的距离。变量mask是一个形状为(npt,)的布尔值数组,指示每个预测点是否被屏蔽。

该方法首先初始化一个具有零值的zvaluessigmasq数组,分别用于存储每个预测点的预测值和方差。然后,它使用由self.pseudo_invself.pseudo_inv_type指定的所需方法计算kriging矩阵a的逆。接下来,对于没有被屏蔽的每个预测点,该方法计算右侧向量b并使用a的逆来计算kriging系统的解solution。最后,该方法将预测值和方差存储在相应的数组中,并返回这些数组。

请注意,由于使用循环遍历所有点,因此该方法对内存的需求较低。但是,由于具有Python级别的循环,因此它可能比_exec_vector方法慢得多。

使用了移动窗口技术来减少计算

def _exec_loop_moving_window(self, a_all, bd_all, mask, bd_idx):
        """Solves the kriging system by looping over all specified points.
        Less memory-intensive, but involves a Python-level loop."""
        import scipy.linalg.lapack

        npt = bd_all.shape[0]
        n = bd_idx.shape[1]
        zvalues = np.zeros(npt)
        sigmasq = np.zeros(npt)

        for i in np.nonzero(~mask)[
            0
        ]:  # Note that this is the same thing as range(npt) if mask is not defined,
            b_selector = bd_idx[i]  # otherwise it takes the non-masked elements.
            bd = bd_all[i]

            a_selector = np.concatenate((b_selector, np.array([a_all.shape[0] - 1])))
            a = a_all[a_selector[:, None], a_selector]

            if np.any(np.absolute(bd) <= self.eps):
                zero_value = True
                zero_index = np.where(np.absolute(bd) <= self.eps)
            else:
                zero_index = None
                zero_value = False
            b = np.zeros((n + 1, 1))
            b[:n, 0] = -self.variogram_function(self.variogram_model_parameters, bd)
            if zero_value and self.exact_values:
                b[zero_index[0], 0] = 0.0
            b[n, 0] = 1.0

            x = scipy.linalg.solve(a, b)

            zvalues[i] = x[:n, 0].dot(self.Z[b_selector])
            sigmasq[i] = -x[:, 0].dot(b[:, 0])

        return zvalues, sigmasq

该方法_exec_loop_moving_window通过循环遍历所有指定点来解决kriging系统。这种方法对内存的需求较低,但涉及Python级别的循环。与_exec_loop方法不同,该方法使用了移动窗口技术来减少计算量。

变量a_all是一个大小为(n + 1, n + 1)的矩阵数组,其中包含所有数据点之间的协方差和距离。变量bd_all是一个数组,其形状为(npt, n),其中包含了npt个预测点与构建kriging系统所使用的所有n个数据点之间的距离。变量mask是一个形状为(npt,)的布尔值数组,指示每个预测点是否被屏蔽。变量bd_idx是一个形状为(npt, m)的整数数组,其中包含了每个预测点的最近邻索引。

该方法首先初始化一个具有零值的zvaluessigmasq数组,分别用于存储每个预测点的预测值和方差。然后,对于没有被屏蔽的每个预测点,该方法使用移动窗口技术选择与该点最近邻的数据点,并构造kriging系统的矩阵和右侧向量。最后,该方法使用LAPACK库函数来求解kriging系统,并将预测值和方差存储在相应的数组中,并返回这些数组。

请注意,由于使用循环遍历所有点,因此该方法对内存的需求较低。但是,由于具有Python级别的循环,因此它可能比_exec_vector方法慢得多。 

Ordinary Kriging"的插值方法

 def execute(
        self,
        style,
        xpoints,
        ypoints,
        mask=None,
        backend="vectorized",
        n_closest_points=None,
    ):

        if self.verbose:
            print("Executing Ordinary Kriging...\n")

        if style != "grid" and style != "masked" and style != "points":
            raise ValueError("style argument must be 'grid', 'points', or 'masked'")

        if n_closest_points is not None and n_closest_points <= 1:
            # If this is not checked, nondescriptive errors emerge
            # later in the code.
            raise ValueError("n_closest_points has to be at least two!")

        xpts = np.atleast_1d(np.squeeze(np.array(xpoints, copy=True)))
        ypts = np.atleast_1d(np.squeeze(np.array(ypoints, copy=True)))
        n = self.X_ADJUSTED.shape[0]
        nx = xpts.size
        ny = ypts.size
        a = self._get_kriging_matrix(n)

这是一个Python函数的定义,名为execute,它包含6个参数:stylexpointsypointsmaskbackendn_closest_points。函数主要用于执行一种叫做"Ordinary Kriging"的插值方法。

函数中进行了一些参数合法性检查,以确保传入的参数正确。其中,如果传入的style参数既不是"grid"也不是"masked"也不是"points",则会raise一个ValueError异常。如果传入的n_closest_points小于等于1,则也会raise一个ValueError异常。

接下来,对xpointsypoints进行了转换和处理,然后调用了一个名为_get_kriging_matrix的方法,返回一个矩阵a,该矩阵将在接下来的计算中使用。

if style in ["grid", "masked"]:
            if style == "masked":
                if mask is None:
                    raise IOError(
                        "Must specify boolean masking array when style is 'masked'."
                    )
                if mask.shape[0] != ny or mask.shape[1] != nx:
                    if mask.shape[0] == nx and mask.shape[1] == ny:
                        mask = mask.T
                    else:
                        raise ValueError(
                            "Mask dimensions do not match specified grid dimensions."
                        )
                mask = mask.flatten()
            npt = ny * nx
            grid_x, grid_y = np.meshgrid(xpts, ypts)
            xpts = grid_x.flatten()
            ypts = grid_y.flatten()

        elif style == "points":
            if xpts.size != ypts.size:
                raise ValueError(
                    "xpoints and ypoints must have "
                    "same dimensions when treated as "
                    "listing discrete points."
                )
            npt = nx
        else:
            raise ValueError("style argument must be 'grid', 'points', or 'masked'")

这段代码主要是根据传入的style参数不同,对一些变量进行初始化。如果style为"grid"或者"masked",则会进行下面的处理:

  • 如果style是"masked",则需要传入一个boolean类型的遮罩数组mask,否则就会raise一个IOError异常。
  • 如果mask的行数和列数与nynx不相等,则需要对mask进行转置。最终将mask按行展平为一个一维数组。
  • 根据xpointsypoints生成网格点坐标,将xpointsypoints按行展平为两个一维数组。

如果style为"points",则需要确保xpointsypoints的大小一致,即必须表示相同数量的离散点。

如果style既不是"grid"也不是"masked"也不是"points",则会raise一个ValueError异常。

 if self.coordinates_type == "euclidean":
            xpts, ypts = _adjust_for_anisotropy(
                np.vstack((xpts, ypts)).T,
                [self.XCENTER, self.YCENTER],
                [self.anisotropy_scaling],
                [self.anisotropy_angle],
            ).T
            # Prepare for cdist:
            xy_data = np.concatenate(
                (self.X_ADJUSTED[:, np.newaxis], self.Y_ADJUSTED[:, np.newaxis]), axis=1
            )
            xy_points = np.concatenate(
                (xpts[:, np.newaxis], ypts[:, np.newaxis]), axis=1
            )
        elif self.coordinates_type == "geographic":
            # In spherical coordinates, we do not correct for anisotropy.
            # Also, we don't use scipy.spatial.cdist, so we do not have to
            # format the input data accordingly.
            pass

这段代码主要是对输入的xptsypts进行坐标系变换。如果原始数据的坐标系为欧几里得坐标系(self.coordinates_type == "euclidean"),则调用一个名为_adjust_for_anisotropy的函数,对xptsypts进行修正, 使他们适应各向异性(anisotropy)。修正后的xptsypts会被赋值回去,同时将训练数据和待插值的点的坐标都拼接到一个数组中。

如果该数据的坐标系为地理坐标系(self.coordinates_type == "geographic"),则略过这一步骤。

 if style != "masked":
            mask = np.zeros(npt, dtype="bool")

        c_pars = None
        if backend == "C":
            try:
                from .lib.cok import _c_exec_loop, _c_exec_loop_moving_window
            except ImportError:
                print(
                    "Warning: failed to load Cython extensions.\n"
                    "   See https://github.com/GeoStat-Framework/PyKrige/issues/8 \n"
                    "   Falling back to a pure python backend..."
                )
                backend = "loop"
            except:
                raise RuntimeError("Unknown error in trying to load Cython extension.")

            c_pars = {
                key: getattr(self, key)
                for key in [
                    "Z",
                    "eps",
                    "variogram_model_parameters",
                    "variogram_function",
                    "exact_values",
                    "pseudo_inv",
                    "pseudo_inv_type",
                ]
            }

这段代码首先初始化一个一维布尔数组mask,在后面的计算中将被用于确定哪些点需要进行插值。

接下来,如果指定了使用Cython后端(backend == "C"),则会尝试导入名为_c_exec_loop_c_exec_loop_moving_window的函数。如果成功,则将变量c_pars初始化为一个字典,包含了模型参数、半方差函数等信息。这些变量将被传递给Cython扩展模块以加速计算。若无法加载Cython扩展,将打印警告,并使用纯Python后端(backend = "loop")。如果出现其他错误,则会raise一个RuntimeError异常。

 if n_closest_points is not None:
            if self.coordinates_type == "geographic":
                # To make use of the KDTree, we have to convert the
                # spherical coordinates into three dimensional Euclidean
                # coordinates, since the standard KDTree cannot handle
                # the periodicity.
                # Do the conversion just for the step involving the KDTree:
                lon_d = self.X_ADJUSTED[:, np.newaxis] * np.pi / 180.0
                lat_d = self.Y_ADJUSTED[:, np.newaxis] * np.pi / 180.0
                xy_data = np.concatenate(
                    (
                        np.cos(lon_d) * np.cos(lat_d),
                        np.sin(lon_d) * np.cos(lat_d),
                        np.sin(lat_d),
                    ),
                    axis=1,
                )
                lon_p = xpts[:, np.newaxis] * np.pi / 180.0
                lat_p = ypts[:, np.newaxis] * np.pi / 180.0
                xy_points = np.concatenate(
                    (
                        np.cos(lon_p) * np.cos(lat_p),
                        np.sin(lon_p) * np.cos(lat_p),
                        np.sin(lat_p),
                    ),
                    axis=1,
                )

            from scipy.spatial import cKDTree

            tree = cKDTree(xy_data)
            bd, bd_idx = tree.query(xy_points, k=n_closest_points, eps=0.0)

这段代码根据传入的n_closest_points对数据进行剪裁,只保留最近的一些点进行计算。如果n_closest_points不为空,则首先判断坐标系是否为地理坐标系。如果是,则需要将地理坐标转换成三维欧几里得坐标来使用KDTree,进而找到距离待插值点最近的一些训练点。如果不是,则直接使用原始数据中的欧几里得坐标。

接下来,使用scipy库中的cKDTree函数构建一个KDTree,并使用query方法找到距离待插值点最近的k个训练点。其中bd表示距离,bd_idx表示最近的k个点在原始数据中的索引。

  if self.coordinates_type == "geographic":
                # Between the nearest neighbours from Euclidean search,
                # calculate the great circle distance using the standard method:
                x_points = np.tile(xpts[:, np.newaxis], (1, n_closest_points))
                y_points = np.tile(ypts[:, np.newaxis], (1, n_closest_points))
                bd = core.great_circle_distance(
                    x_points, y_points, self.X_ADJUSTED[bd_idx], self.Y_ADJUSTED[bd_idx]
                )

            if backend == "loop":
                zvalues, sigmasq = self._exec_loop_moving_window(a, bd, mask, bd_idx)
            elif backend == "C":
                zvalues, sigmasq = _c_exec_loop_moving_window(
                    a,
                    bd,
                    mask.astype("int8"),
                    bd_idx.astype(int),
                    self.X_ADJUSTED.shape[0],
                    c_pars,
                )
            else:
                raise ValueError(
                    "Specified backend {} for a moving window "
                    "is not supported.".format(backend)
                )
        else:
            if self.coordinates_type == "euclidean":
                bd = cdist(xy_points, xy_data, "euclidean")
            elif self.coordinates_type == "geographic":
                bd = core.great_circle_distance(
                    xpts[:, np.newaxis],
                    ypts[:, np.newaxis],
                    self.X_ADJUSTED,
                    self.Y_ADJUSTED,
                )

这段代码根据前面找到的距离最近的k个训练点,通过移动窗口计算插值结果。如果坐标系为地球坐标系(self.coordinates_type == "geographic"),则需要计算两点之间的大圆距离(即地球上两个点间的最短弧线长度),然后将其转换为欧几里得距离(在投影坐标系下)。如果使用纯Python后端(backend == "loop"),则调用_exec_loop_moving_window函数计算插值结果。如果使用Cython后端(backend == "C"),则调用名为_c_exec_loop_moving_window的函数进行计算。如果指定的后端不支持,则会raise一个ValueError异常。

如果数据的坐标系为欧几里得坐标系(self.coordinates_type == "euclidean"),则直接使用cdist函数计算待插值点与训练点之间的欧几里得距离,并传给移动窗口函数进行计算。

 if backend == "vectorized":
                zvalues, sigmasq = self._exec_vector(a, bd, mask)
            elif backend == "loop":
                zvalues, sigmasq = self._exec_loop(a, bd, mask)
            elif backend == "C":
                zvalues, sigmasq = _c_exec_loop(
                    a, bd, mask.astype("int8"), self.X_ADJUSTED.shape[0], c_pars
                )
            else:
                raise ValueError(
                    "Specified backend {} is not supported for "
                    "2D ordinary kriging.".format(backend)
                )

这是Python中的一个条件语句,它检查变量“backend”的值。根据其值,调用不同的函数来执行某些计算。

如果“backend”等于“vectorized”,则使用参数“a”、“bd”和“mask”调用“_exec_vector”函数。

如果“backend”等于“loop”,则使用相同的参数调用“_exec_loop”函数。

如果“backend”等于“C”,则使用其他参数(例如“self.X_ADJUSTED.shape [0]”和“c_pars”)调用“_c_exec_loop”函数。

如果“backend”具有任何其他值,则引发ValueError,指示不支持指定的后端进行2D普通克里金插值。

   if style == "masked":
            zvalues = np.ma.array(zvalues, mask=mask)
            sigmasq = np.ma.array(sigmasq, mask=mask)

        if style in ["masked", "grid"]:
            zvalues = zvalues.reshape((ny, nx))
            sigmasq = sigmasq.reshape((ny, nx))

        return zvalues, sigmasq

这是一个Python函数,它有三个参数: zvalues, sigmasq和style。

首先,它检查变量style的值。如果style等于"masked",则使用NumPy中的np.ma.array函数创建带有掩码的数组,并将其分配给zvalues和sigmasq。

接下来,如果style的值是"masked"或"grid",则通过reshape函数将zvalues和sigmasq重新构造为ny行、nx列的二维数组。

最后,函数返回重构后的zvalues和sigmasq作为元组。

你可能感兴趣的:(numpy,python,机器学习)