def _last_layer_extraction(self, x: np.ndarray, ftol: float) -> None:
        """
        Extract weights and biases of the second layer.

        :param x: Samples of input data of shape `(num_samples, num_features)`.
        :param ftol: Tolerance for termination by the change of the cost function.
        """
        logger.info("Extract second layer.")

        if self.num_neurons is None:
            raise ValueError("The value of `num_neurons` is required for critical point search.")

        predictions = self._o_l(x)
        w_1_b_1_0 = np.random.normal(0, 1, ((self.num_neurons + 1) * self.num_classes)).astype(dtype=NUMPY_DTYPE)

        def f_w_1_b_1(w_1_b_1_i):
            layer_0 = np.maximum(np.matmul(self.w_0.T, x.T) + self.b_0, 0.0)

            w_1 = w_1_b_1_i[0 : self.num_neurons * self.num_classes].reshape(self.num_neurons, self.num_classes)
            b_1 = w_1_b_1_i[self.num_neurons * self.num_classes :].reshape(self.num_classes, 1)

            layer_1 = np.matmul(w_1.T, layer_0) + b_1

            return np.squeeze((layer_1.T - predictions).flatten())

        result_a1_b1 = least_squares(f_w_1_b_1, w_1_b_1_0, ftol=ftol)

        self.w_1 = result_a1_b1.x[0 : self.num_neurons * self.num_classes].reshape(self.num_neurons, self.num_classes)
        self.b_1 = result_a1_b1.x[self.num_neurons * self.num_classes :].reshape(self.num_classes, 1)

        Extract weights and biases of the second layer.

        :param x: Samples of input data of shape `(num_samples, num_features)`.
        :param ftol: Tolerance for termination by the change of the cost function.
        
art/attacks/extraction/functionally_equivalent_extraction.py