def METHOD_NAME(self, X, y, init_params=True):
    if init_params:
        self.b_, self.w_ = self._init_params(
            weights_shape=(X.shape[1], 1),
            bias_shape=(1,),
            random_seed=self.random_seed,
        )
        self.cost_ = []
    # Direct analytical method
    if self.method == "direct":
        self.b_, self.w_ = self._normal_equation(X, y)
    # Gradient descent or stochastic gradient descent learning
    elif self.method == "sgd":
        self.init_time_ = time()
        rgen = np.random.RandomState(self.random_seed)
        for i in range(self.epochs):
            for idx in self._yield_minibatches_idx(
                rgen=rgen, n_batches=self.minibatches, data_ary=y, shuffle=True
            ):
                y_val = self._net_input(X[idx])
                errors = y[idx] - y_val
                self.w_ += self.eta * X[idx].T.dot(errors).reshape(self.w_.shape)
                self.b_ += self.eta * errors.sum()
            cost = self._sum_squared_error_cost(y, self._net_input(X))
            self.cost_.append(cost)
            if self.print_progress:
                self._print_progress(
                    iteration=(i + 1), n_iter=self.epochs, cost=cost
                )
    # Solve using QR decomposition
    elif self.method == "qr":
        Xb = np.hstack((np.ones((X.shape[0], 1)), X))
        Q, R = np.linalg.qr(Xb)
        beta = np.dot(np.linalg.inv(R), np.dot(Q.T, y))
        self.b_ = np.array([beta[0]])
        self.w_ = beta[1:].reshape(X.shape[1], 1)
    # Solve using SVD
    elif self.method == "svd":
        Xb = np.hstack((np.ones((X.shape[0], 1)), X))
        beta = np.dot(np.linalg.pinv(Xb), y)
        self.b_ = np.array([beta[0]])
        self.w_ = beta[1:].reshape(X.shape[1], 1)
    return self