sklean学习之LogisticRegression(逻辑斯蒂回归分类器)【源码】

本文是根据sklean官方文档进行翻译和学习的,如果理解有误欢迎留言指正,谢谢。

def fit(self, X, y, sample_weight=None):
        """根据给定的训练数据拟合模型.
       参数
        ----------
        X : {array-like, sparse matrix}, shape (n_samples, n_features)
           训练向量, 其中n_samples 表示样本数量,n_features特征数量.
        y : array-like, shape (n_samples,)
           相对于X的目标向量
        sample_weight : array-like, shape (n_samples,) optional
            分配给单个样本的权重数组。
如果没有提供,那么每个样本都具有单位权重
           
        返回值
        -------
        self : object
            Returns self.
        """

#判断self.C的是否是正数。self.C是正则项系数的倒数,一个常数C,默认:C=1.0
        if not isinstance(self.C, numbers.Number) or self.C < 0:
            raise ValueError("Penalty term must be positive; got (C=%r)"
                             % self.C)
#判断self.max_iter是否是正数。self.max_iter是算法收敛时的最大迭代次数
        if not isinstance(self.max_iter, numbers.Number) or self.max_iter < 0:
            raise ValueError("Maximum number of iteration must be positive;"
                             " got (max_iter=%r)" % self.max_iter)
#判断self.tol是否是正数。self.tol是容差
        if not isinstance(self.tol, numbers.Number) or self.tol < 0:
            raise ValueError("Tolerance for stopping criteria must be "
                             "positive; got (tol=%r)" % self.tol)
 
#self.solver参数决定了我们对逻辑回归损失函数的优化方法,有四种算法可以选择
liblinear:使用了开源的liblinear库实现,内部使用了坐标轴下降法来迭代优化损失函数。
lbfgs:拟牛顿法的一种,利用损失函数二阶导数矩阵即海森矩阵来迭代优化损失函数。
newton-cg:也是牛顿法家族的一种,利用损失函数二阶导数矩阵即海森矩阵来迭代优化损失函数。
sag:即随机平均梯度下降,是梯度下降法的变种,和普通梯度下降法的区别是每次迭代仅仅用一部分的样本来计算梯度
        
#定义数据类型
if self.solver in ['newton-cg']:
            _dtype = [np.float64, np.float32]
        else:
            _dtype = np.float64

#标准estimator器的输入验证
        X, y = check_X_y(X, y, accept_sparse='csr', dtype=_dtype,
                         order="C")
#验证目标y属于非回归类型.  
        check_classification_targets(y)

#去除y中的重复数字,并排序
        self.classes_ = np.unique(y)

#输入数据的样本数和维度
        n_samples, n_features = X.shape

#验证算法的相关选项是否符合要求
        _check_solver_option(self.solver, self.multi_class, self.penalty,
                             self.dual)
 
#根据不同的算法迭代训练模型
        if self.solver == 'liblinear':
            if self.n_jobs != 1:
                warnings.warn("'n_jobs' > 1 does not have any effect when"
                              " 'solver' is set to 'liblinear'. Got 'n_jobs'"
                              " = {}.".format(self.n_jobs))
            self.coef_, self.intercept_, n_iter_ = _fit_liblinear(
                X, y, self.C, self.fit_intercept, self.intercept_scaling,
                self.class_weight, self.penalty, self.dual, self.verbose,
                self.max_iter, self.tol, self.random_state,
                sample_weight=sample_weight)
            self.n_iter_ = np.array([n_iter_])
            return self

        if self.solver in ['sag', 'saga']:
            max_squared_sum = row_norms(X, squared=True).max()
        else:
            max_squared_sum = None

        n_classes = len(self.classes_)
        classes_ = self.classes_
        if n_classes < 2:
            raise ValueError("This solver needs samples of at least 2 classes"
                             " in the data, but the data contains only one"
                             " class: %r" % classes_[0])

        if len(self.classes_) == 2:
            n_classes = 1
            classes_ = classes_[1:]

        if self.warm_start:
            warm_start_coef = getattr(self, 'coef_', None)
        else:
            warm_start_coef = None
        if warm_start_coef is not None and self.fit_intercept:
            warm_start_coef = np.append(warm_start_coef,
                                        self.intercept_[:, np.newaxis],
                                        axis=1)

        self.coef_ = list()
        self.intercept_ = np.zeros(n_classes)

        # Hack so that we iterate only once for the multinomial case.
        if self.multi_class == 'multinomial':
            classes_ = [None]
            warm_start_coef = [warm_start_coef]
        if warm_start_coef is None:
            warm_start_coef = [None] * n_classes

        path_func = delayed(logistic_regression_path)

        # The SAG solver releases the GIL so it's more efficient to use
        # threads for this solver.
        if self.solver in ['sag', 'saga']:
            backend = 'threading'
        else:
            backend = 'multiprocessing'
        fold_coefs_ = Parallel(n_jobs=self.n_jobs, verbose=self.verbose,
                               backend=backend)(
            path_func(X, y, pos_class=class_, Cs=[self.C],
                      fit_intercept=self.fit_intercept, tol=self.tol,
                      verbose=self.verbose, solver=self.solver,
                      multi_class=self.multi_class, max_iter=self.max_iter,
                      class_weight=self.class_weight, check_input=False,
                      random_state=self.random_state, coef=warm_start_coef_,
                      penalty=self.penalty,
                      max_squared_sum=max_squared_sum,
                      sample_weight=sample_weight)
            for class_, warm_start_coef_ in zip(classes_, warm_start_coef))

        fold_coefs_, _, n_iter_ = zip(*fold_coefs_)
        self.n_iter_ = np.asarray(n_iter_, dtype=np.int32)[:, 0]

        if self.multi_class == 'multinomial':
            self.coef_ = fold_coefs_[0][0]
        else:
            self.coef_ = np.asarray(fold_coefs_)
            self.coef_ = self.coef_.reshape(n_classes, n_features +
                                            int(self.fit_intercept))

        if self.fit_intercept:
            self.intercept_ = self.coef_[:, -1]
            self.coef_ = self.coef_[:, :-1]

        return self


def check_X_y(X, y, accept_sparse=False, dtype="numeric", order=None,
              copy=False, force_all_finite=True, ensure_2d=True,
              allow_nd=False, multi_output=False, ensure_min_samples=1,
              ensure_min_features=1, y_numeric=False,
              warn_on_dtype=False, estimator=None):
    """标准estimator器的输入验证。
    检查X和y的长度, 让X是2维的y是1维的.
标准输入检查只适用于y,比如检查y中是否含有np.nan或者np.inf这类数据
    对于多标记类型的y,要通过设置multi_output=True才可以允许使用2维和稀疏数据y.
如果X的类型是object,尝试转换为浮点型或者引发异常
    Parameters
    ----------
    X : nd-array, list or sparse matrix
        输入数据.
    y : nd-array, list or sparse matrix
        标签.
    accept_sparse : string, boolean or list of string (default=False)
        字符串[s]表示允许的稀疏矩阵格式。比如'csc','csr', 等. 
如果输入是稀疏的,但不是允许的格式,它将被转换为第一个列出的格式。
True 表示允许输入为任何格式
        False 表示如果输入是稀疏的则会引发异常
    dtype : string, type, list of types or None (default="numeric")
        结果集的数据类型. 
如果为None,将会保留输入时的数据类型.
        如果为"numeric", 数据类型将被保留除非array.dtype是object.
        如果是一个类型的list, 如果输入的dtype不在列表中,则会且只会转换为列表中的第一个类型。
    order : 'F', 'C' or None (default=None)
数组是否被强制转换为fortran或c风格
    copy : boolean (default=False)
是否触发强制复制
        如果copy=False, 一次转换就会触发一次复制
    force_all_finite : boolean or 'allow-nan', (default=True)
是否对输入数据x中的np.inf 和 np.nan引发异常。
这个参数不影响y是否具有np.inf 和 np.nan值。
        允许的参数有:
        - True: 使所有X的值都是有限的
        - False: 允许X中有np.inf和np.nan值
        - 'allow-nan': 只允许X中有np.nan值. 不允许数据是无穷的
    ensure_2d : boolean (default=True)
        是否使X至少为2维。
    allow_nd : boolean (default=False)
是否允许X的维度大于2.
    multi_output : boolean (default=False)
        是否允许输出y是2维的(数组或者稀疏矩阵). 
如果为false, 就会验证y是不是一个向量.
y中不可以有np.nan或者 np.inf 这样的值如果multi_output=True.
    ensure_min_samples : int (default=1)
确保X在第一个轴中有最小数量的样本。(行是2D数组).
    ensure_min_features : int (default=1)
确保2D数组有一些最小的特性(列)。默认值1拒绝空数据集
        Make sure that the 2D array has some minimum number of features
    y_numeric : boolean (default=False)
是否确保y具有数值类型。如果y的类型是object,则将其转换为float64. 
只用于回归算法中.
    warn_on_dtype : boolean (default=False)
如果输入数据结构的类型和要求的类型不一致,则给出警告DataConversionWarning
    estimator : str or estimator instance (default=None)
如果忽略,请在警告消息中包含estimator的名称。
    返回值
    -------
    X_converted : object
        转换和验证后的X
    y_converted : object
        转换和验证后的y.
    """
    X = check_array(X, accept_sparse, dtype, order, copy, force_all_finite,
                    ensure_2d, allow_nd, ensure_min_samples,
                    ensure_min_features, warn_on_dtype, estimator)
    if multi_output:
        y = check_array(y, 'csr', force_all_finite=True, ensure_2d=False,
                        dtype=None)
    else:
        y = column_or_1d(y, warn=True)
        _assert_all_finite(y)
    if y_numeric and y.dtype.kind == 'O':
        y = y.astype(np.float64)

    check_consistent_length(X, y)

    return X, y


def check_classification_targets(y):
    """确保目标y属于非回归类型.
    只有以下目标类型(在type_of_target中定义的)被允许:
        'binary', 'multiclass', 'multiclass-multioutput',
        'multilabel-indicator', 'multilabel-sequences'
    参数
    ----------
    y : array-like
    """
    y_type = type_of_target(y)
    if y_type not in ['binary', 'multiclass', 'multiclass-multioutput',
                      'multilabel-indicator', 'multilabel-sequences']:
        raise ValueError("Unknown label type: %r" % y_type)

def _check_solver_option(solver, multi_class, penalty, dual):
"""检查算法的相关参数"""
    if solver not in ['liblinear', 'newton-cg', 'lbfgs', 'sag', 'saga']:
        raise ValueError("Logistic Regression supports only liblinear, "
                         "newton-cg, lbfgs, sag and saga solvers, got %s"
                         % solver)

    if multi_class not in ['multinomial', 'ovr']:
        raise ValueError("multi_class should be either multinomial or "
                         "ovr, got %s" % multi_class)

    if multi_class == 'multinomial' and solver == 'liblinear':
        raise ValueError("Solver %s does not support "
                         "a multinomial backend." % solver)

    if solver not in ['liblinear', 'saga']:
        if penalty != 'l2':
            raise ValueError("Solver %s supports only l2 penalties, "
                             "got %s penalty." % (solver, penalty))
    if solver != 'liblinear':
        if dual:
            raise ValueError("Solver %s supports only "
                             "dual=False, got dual=%s" % (solver, dual))
 
def _fit_liblinear(X, y, C, fit_intercept, intercept_scaling, class_weight,
                   penalty, dual, verbose, max_iter, tol,
                   random_state=None, multi_class='ovr',
                   loss='logistic_regression', epsilon=0.1,
                   sample_weight=None):
    """在使用Logistic Regression (and CV) 和LinearSVC是需要该方法.
    在将其提供给liblinear之前,先进行预处理
    Parameters
    ----------
    X : {array-like, sparse matrix}, shape (n_samples, n_features)
        训练向量
    y : array-like, shape (n_samples,)
        与X对应的目标向量y
    C : float
        交叉验证参数的倒数
    fit_intercept : bool
        是否训练截距
    intercept_scaling : float
        
    class_weight : {dict, 'balanced'}, optional
        
    penalty : str, {'l1', 'l2'}
        在正规化中使用的规范
    dual : bool

    verbose : int
        将verbose设置为任意正数.
    max_iter : int
        迭代次数
    tol : float
        停止条件.
    random_state : int, RandomState instance or None, optional (default=None)
       
    multi_class : str, {'ovr', 'crammer_singer'}
      
    loss : str, {'logistic_regression', 'hinge', 'squared_hinge',
                 'epsilon_insensitive', 'squared_epsilon_insensitive}
        用于拟合模型的损失函数。
    epsilon : float, optional (default=0.1)
        Epsilon parameter in the epsilon-insensitive loss function.
    sample_weight : array-like, optional
        分配给每个样本的权重。
    Returns
    -------
    coef_ : ndarray, shape (n_features, n_features + 1)
        通过最小化目标函数得到系数向量。
    intercept_ : float
        向量的截距项
    n_iter_ : int
        所有类的最大迭代次数
    """
    if loss not in ['epsilon_insensitive', 'squared_epsilon_insensitive']:

#在0和n_class -1之间对标签进行编码
        enc = LabelEncoder()
#训练标签编码器和返回训练后的编码标签
        y_ind = enc.fit_transform(y)
#为每个类保存标签
        classes_ = enc.classes_
        if len(classes_) < 2:
            raise ValueError("This solver needs samples of at least 2 classes"
                             " in the data, but the data contains only one"
                             " class: %r" % classes_[0])
#估算不平衡数据集的类权重
        class_weight_ = compute_class_weight(class_weight, classes_, y)
    else:
        class_weight_ = np.empty(0, dtype=np.float64)
        y_ind = y
    liblinear.set_verbosity_wrap(verbose)
    rnd = check_random_state(random_state)
    if verbose:
        print('[LibLinear]', end='')

    # LinearSVC breaks when intercept_scaling is <= 0
    bias = -1.0
    if fit_intercept:
        if intercept_scaling <= 0:
            raise ValueError("Intercept scaling is %r but needs to be greater than 0."
                             " To disable fitting an intercept,"
                             " set fit_intercept=False." % intercept_scaling)
        else:
            bias = intercept_scaling
    libsvm.set_verbosity_wrap(verbose)
    libsvm_sparse.set_verbosity_wrap(verbose)
    liblinear.set_verbosity_wrap(verbose)
    # LibLinear wants targets as doubles, even for classification
    y_ind = np.asarray(y_ind, dtype=np.float64).ravel()
    if sample_weight is None:
        sample_weight = np.ones(X.shape[0])
    else:
        sample_weight = np.array(sample_weight, dtype=np.float64, order='C')
        check_consistent_length(sample_weight, X)
    solver_type = _get_liblinear_solver_type(multi_class, penalty, loss, dual)
    raw_coef_, n_iter_ = liblinear.train_wrap(
        X, y_ind, sp.isspmatrix(X), solver_type, tol, bias, C,
        class_weight_, max_iter, rnd.randint(np.iinfo('i').max),
        epsilon, sample_weight)
    # Regarding rnd.randint(..) in the above signature:
    # seed for srand in range [0..INT_MAX); due to limitations in Numpy
    # on 32-bit platforms, we can't get to the UINT_MAX limit that
    # srand supports
    n_iter_ = max(n_iter_)
    if n_iter_ >= max_iter:
        warnings.warn("Liblinear failed to converge, increase "
                      "the number of iterations.", ConvergenceWarning)
    if fit_intercept:
        coef_ = raw_coef_[:, :-1]
        intercept_ = intercept_scaling * raw_coef_[:, -1]
    else:
        coef_ = raw_coef_
        intercept_ = 0.

    return coef_, intercept_, n_iter_

备注:还没学习完,等有时间了再更新本文。



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值