1. 程式人生 > >sklean學習之LogisticRegression(邏輯斯蒂迴歸分類器)【原始碼】

sklean學習之LogisticRegression(邏輯斯蒂迴歸分類器)【原始碼】

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_