【翻譯】Sklearn 與 TensorFlow 機器學習實用指南 —— 第2章 一個完整的機器學習項目(下)

2021-03-02 Python愛好者社區

作者:ApacheCN   Python機器學習愛好者
Python愛好者社區專欄作者
GitHub:https://github.com/apachecn/hands_on_Ml_with_Sklearn_and_TF

前文傳送門:

【翻譯】Sklearn 與 TensorFlow 機器學習實用指南 —— Chapter 0.前言

【翻譯】Sklearn 與 TensorFlow 機器學習實用指南 —— 第1章 機器學習概覽(上)

【翻譯】Sklearn 與 TensorFlow 機器學習實用指南 —— 第1章 機器學習概覽(下)

【翻譯】Sklearn 與 TensorFlow 機器學習實用指南 —— 第2章  一個完整的機器學習項目(上)

【翻譯】Sklearn 與 TensorFlow 機器學習實用指南 —— 第2章 一個完整的機器學習項目(中)

為機器學習算法準備數據

現在來為機器學習算法準備數據。不要手工來做,你需要寫一些函數,理由如下:

函數可以讓你在任何數據集上(比如,你下一次獲取的是一個新的數據集)方便地進行重複數據轉換。

你能慢慢建立一個轉換函數庫,可以在未來的項目中復用。

在將數據傳給算法之前,你可以在實時系統中使用這些函數。

這可以讓你方便地嘗試多種數據轉換,查看哪些轉換方法結合起來效果最好。

但是,還是先回到乾淨的訓練集(通過再次複製strat_train_set),將預測量和標籤分開,因為我們不想對預測量和目標值應用相同的轉換(注意drop()創建了一份數據的備份,而不影響strat_train_set):

housing = strat_train_set.drop("median_house_value", axis=1)housing_labels = strat_train_set["median_house_value"].copy()

數據清洗

大多機器學習算法不能處理缺失的特徵,因此先創建一些函數來處理特徵缺失的問題。前面,你應該注意到了屬性total_bedrooms有一些缺失值。有三個解決選項:

去掉對應的街區;

去掉整個屬性;

進行賦值(0、平均值、中位數等等)。

用DataFrame的dropna(),drop(),和fillna()方法,可以方便地實現:

housing.dropna(subset=["total_bedrooms"])    # 選項1housing.drop("total_bedrooms", axis=1)       # 選項2median = housing["total_bedrooms"].median()housing["total_bedrooms"].fillna(median)     # 選項3

如果選擇選項 3,你需要計算訓練集的中位數,用中位數填充訓練集的缺失值,不要忘記保存該中位數。後面用測試集評估系統時,需要替換測試集中的缺失值,也可以用來實時替換新數據中的缺失值。

from sklearn.preprocessing import Imputerimputer = Imputer(strategy="median")

Scikit-Learn 提供了一個方便的類來處理缺失值:Imputer。下面是其使用方法:首先,需要創建一個Imputer實例,指定用某屬性的中位數來替換該屬性所有的缺失值:

因為只有數值屬性才能算出中位數,我們需要創建一份不包括文本屬性ocean_proximity的數據副本:

housing_num = housing.drop("ocean_proximity", axis=1)

現在,就可以用fit()方法將imputer實例擬合到訓練數據:

imputer.fit(housing_num)

imputer計算出了每個屬性的中位數,並將結果保存在了實例變量statistics_中。雖然此時只有屬性total_bedrooms存在缺失值,但我們不能確定在以後的新的數據中會不會有其他屬性也存在缺失值,所以安全的做法是將imputer應用到每個數值:

>>> imputer.statistics_array([ -118.51 , 34.26 , 29. , 2119. , 433. , 1164. , 408. , 3.5414])>>> housing_num.median().valuesarray([ -118.51 , 34.26 , 29. , 2119. , 433. , 1164. , 408. , 3.5414])

現在,你就可以使用這個「訓練過的」imputer來對訓練集進行轉換,將缺失值替換為中位數:

X = imputer.transform(housing_num)

結果是一個包含轉換後特徵的普通的 Numpy 數組。如果你想將其放回到 PandasDataFrame中,也很簡單:

housing_tr = pd.DataFrame(X, columns=housing_num.columns)

Scikit-Learn 設計

Scikit-Learn 設計的 API 設計的非常好。它的主要設計原則是:

轉換器(transformer)。一些估計器(比如imputer)也可以轉換數據集,這些估計器被稱為轉換器。API也是相當簡單:轉換是通過transform()方法,被轉換的數據集作為參數。返回的是經過轉換的數據集。轉換過程依賴學習到的參數,比如imputer的例子。所有的轉換都有一個便捷的方法fit_transform(),等同於調用fit()再transform()(但有時fit_transform()經過優化,運行的更快)。

預測器(predictor)。最後,一些估計器可以根據給出的數據集做預測,這些估計器稱為預測器。例如,上一章的LinearRegression模型就是一個預測器:它根據一個國家的人均 GDP 預測生活滿意度。預測器有一個predict()方法,可以用新實例的數據集做出相應的預測。預測器還有一個score()方法,可以根據測試集(和相應的標籤,如果是監督學習算法的話)對預測進行衡器。

可檢驗。所有估計器的超參數都可以通過實例的public變量直接訪問(比如,imputer.strategy),並且所有估計器學習到的參數也可以通過在實例變量名後加下劃線來訪問(比如,imputer.statistics_)。

類不可擴散。數據集被表示成 NumPy 數組或 SciPy 稀疏矩陣,而不是自製的類。超參數只是普通的 Python 字符串或數字。

可組合。儘可能使用現存的模塊。例如,用任意的轉換器序列加上一個估計器,就可以做成一個流水線,後面會看到例子。

合理的默認值。Scikit-Learn 給大多數參數提供了合理的默認值,很容易就能創建一個系統。


處理文本和類別屬性

前面,我們丟棄了類別屬性ocean_proximity,因為它是一個文本屬性,不能計算出中位數。大多數機器學習算法跟喜歡和數字打交道,所以讓我們把這些文本標籤轉換為數字。

Scikit-Learn 為這個任務提供了一個轉換器LabelEncoder:

>>> from sklearn.preprocessing import LabelEncoder>>> encoder = LabelEncoder()>>> housing_cat = housing["ocean_proximity"]>>> housing_cat_encoded = encoder.fit_transform(housing_cat)>>> housing_cat_encodedarray([1, 1, 4, ..., 1, 0, 3])

譯註:

在原書中使用LabelEncoder轉換器來轉換文本特徵列的方式是錯誤的,該轉換器只能用來轉換標籤(正如其名)。在這裡使用LabelEncoder沒有出錯的原因是該數據只有一列文本特徵值,在有多個文本特徵列的時候就會出錯。應使用factorize()方法來進行操作:

housing_cat_encoded, housing_categories = housing_cat.factorize()housing_cat_encoded[:10]

好了一些,現在就可以在任何 ML 算法裡用這個數值數據了。你可以查看映射表,編碼器是通過屬性classes_來學習的(<1H OCEAN被映射為 0,INLAND被映射為 1,等等):

>>> print(encoder.classes_)['<1H OCEAN' 'INLAND' 'ISLAND' 'NEAR BAY' 'NEAR OCEAN']

這種做法的問題是,ML 算法會認為兩個臨近的值比兩個疏遠的值要更相似。顯然這樣不對(比如,分類 0 和 4 比 0 和 1 更相似)。要解決這個問題,一個常見的方法是給每個分類創建一個二元屬性:當分類是<1H OCEAN,該屬性為 1(否則為 0),當分類是INLAND,另一個屬性等於 1(否則為 0),以此類推。這稱作獨熱編碼(One-Hot Encoding),因為只有一個屬性會等於 1(熱),其餘會是 0(冷)。

Scikit-Learn 提供了一個編碼器OneHotEncoder,用於將整數分類值轉變為獨熱向量。注意fit_transform()用於 2D 數組,而housing_cat_encoded是一個 1D 數組,所以需要將其變形:

>>> from sklearn.preprocessing import OneHotEncoder>>> encoder = OneHotEncoder()>>> housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.reshape(-1,1))>>> housing_cat_1hot<16513x5 sparse matrix of type '<class 'numpy.float64'>'    with 16513 stored elements in Compressed Sparse Row format>

注意輸出結果是一個 SciPy 稀疏矩陣,而不是 NumPy 數組。當類別屬性有數千個分類時,這樣非常有用。經過獨熱編碼,我們得到了一個有數千列的矩陣,這個矩陣每行只有一個 1,其餘都是 0。使用大量內存來存儲這些 0 非常浪費,所以稀疏矩陣只存儲非零元素的位置。你可以像一個 2D 數據那樣進行使用,但是如果你真的想將其轉變成一個(密集的)NumPy 數組,只需調用toarray()方法:

>>> housing_cat_1hot.toarray()array([[ 0.,  1.,  0.,  0.,  0.],       [ 0.,  1.,  0.,  0.,  0.],       [ 0.,  0.,  0.,  0.,  1.],       ...,       [ 0.,  1.,  0.,  0.,  0.],       [ 1.,  0.,  0.,  0.,  0.],       [ 0.,  0.,  0.,  1.,  0.]])

使用類LabelBinarizer,我們可以用一步執行這兩個轉換(從文本分類到整數分類,再從整數分類到獨熱向量):

>>> from sklearn.preprocessing import LabelBinarizer>>> encoder = LabelBinarizer()>>> housing_cat_1hot = encoder.fit_transform(housing_cat)>>> housing_cat_1hotarray([[0, 1, 0, 0, 0],       [0, 1, 0, 0, 0],       [0, 0, 0, 0, 1],       ...,       [0, 1, 0, 0, 0],       [1, 0, 0, 0, 0],       [0, 0, 0, 1, 0]])

注意默認返回的結果是一個密集 NumPy 數組。向構造器LabelBinarizer傳遞sparse_output=True,就可以得到一個稀疏矩陣。

譯註:

# Definition of the CategoricalEncoder class, copied from PR #9151.# Just run this cell, or copy it to your code, do not try to understand it (yet).from sklearn.base import BaseEstimator, TransformerMixinfrom sklearn.utils import check_arrayfrom sklearn.preprocessing import LabelEncoderfrom scipy import sparseclass CategoricalEncoder(BaseEstimator, TransformerMixin):    """Encode categorical features as a numeric array.    The input to this transformer should be a matrix of integers or strings,    denoting the values taken on by categorical (discrete) features.    The features can be encoded using a one-hot aka one-of-K scheme    (``encoding='onehot'``, the default) or converted to ordinal integers    (``encoding='ordinal'``).    This encoding is needed for feeding categorical data to many scikit-learn    estimators, notably linear models and SVMs with the standard kernels.    Read more in the :ref:`User Guide <preprocessing_categorical_features>`.    Parameters        encoding : str, 'onehot', 'onehot-dense' or 'ordinal'        The type of encoding to use (default is 'onehot'):        - 'onehot': encode the features using a one-hot aka one-of-K scheme          (or also called 'dummy' encoding). This creates a binary column for          each category and returns a sparse matrix.        - 'onehot-dense': the same as 'onehot' but returns a dense array          instead of a sparse matrix.        - 'ordinal': encode the features as ordinal integers. This results in          a single column of integers (0 to n_categories - 1) per feature.    categories : 'auto' or a list of lists/arrays of values.        Categories (unique values) per feature:        - 'auto' : Determine categories automatically from the training data.        - list : ``categories[i]`` holds the categories expected in the ith          column. The passed categories are sorted before encoding the data          (used categories can be found in the ``categories_`` attribute).    dtype : number type, default np.float64        Desired dtype of output.    handle_unknown : 'error' (default) or 'ignore'        Whether to raise an error or ignore if a unknown categorical feature is        present during transform (default is to raise). When this is parameter        is set to 'ignore' and an unknown category is encountered during        transform, the resulting one-hot encoded columns for this feature        will be all zeros.        Ignoring unknown categories is not supported for        ``encoding='ordinal'``.    Attributes        categories_ : list of arrays        The categories of each feature determined during fitting. When        categories were specified manually, this holds the sorted categories        (in order corresponding with output of `transform`).    Examples    ---    Given a dataset with three features and two samples, we let the encoder    find the maximum value per feature and transform the data to a binary    one-hot encoding.    >>> from sklearn.preprocessing import CategoricalEncoder    >>> enc = CategoricalEncoder(handle_unknown='ignore')    >>> enc.fit([[0, 0, 3], [1, 1, 0], [0, 2, 1], [1, 0, 2]])    ... # doctest: +ELLIPSIS    CategoricalEncoder(categories='auto', dtype=<... 'numpy.float64'>,              encoding='onehot', handle_unknown='ignore')    >>> enc.transform([[0, 1, 1], [1, 0, 4]]).toarray()    array([[ 1.,  0.,  0.,  1.,  0.,  0.,  1.,  0.,  0.],           [ 0.,  1.,  1.,  0.,  0.,  0.,  0.,  0.,  0.]])    See also    ---    sklearn.preprocessing.OneHotEncoder : performs a one-hot encoding of      integer ordinal features. The ``OneHotEncoder assumes`` that input      features take on values in the range ``[0, max(feature)]`` instead of      using the unique values.    sklearn.feature_extraction.DictVectorizer : performs a one-hot encoding of      dictionary items (also handles string-valued features).    sklearn.feature_extraction.FeatureHasher : performs an approximate one-hot      encoding of dictionary items or strings.    """    def __init__(self, encoding='onehot', categories='auto', dtype=np.float64,                 handle_unknown='error'):        self.encoding = encoding        self.categories = categories        self.dtype = dtype        self.handle_unknown = handle_unknown    def fit(self, X, y=None):        """Fit the CategoricalEncoder to X.        Parameters                X : array-like, shape [n_samples, n_feature]            The data to determine the categories of each feature.        Returns        --        self        """        if self.encoding not in ['onehot', 'onehot-dense', 'ordinal']:            template = ("encoding should be either 'onehot', 'onehot-dense' "                        "or 'ordinal', got %s")            raise ValueError(template % self.handle_unknown)        if self.handle_unknown not in ['error', 'ignore']:            template = ("handle_unknown should be either 'error' or "                        "'ignore', got %s")            raise ValueError(template % self.handle_unknown)        if self.encoding == 'ordinal' and self.handle_unknown == 'ignore':            raise ValueError("handle_unknown='ignore' is not supported for"                             " encoding='ordinal'")        X = check_array(X, dtype=np.object, accept_sparse='csc', copy=True)        n_samples, n_features = X.shape        self._label_encoders_ = [LabelEncoder() for _ in range(n_features)]        for i in range(n_features):            le = self._label_encoders_[i]            Xi = X[:, i]            if self.categories == 'auto':                le.fit(Xi)            else:                valid_mask = np.in1d(Xi, self.categories[i])                if not np.all(valid_mask):                    if self.handle_unknown == 'error':                        diff = np.unique(Xi[~valid_mask])                        msg = ("Found unknown categories {0} in column {1}"                               " during fit".format(diff, i))                        raise ValueError(msg)                le.classes_ = np.array(np.sort(self.categories[i]))        self.categories_ = [le.classes_ for le in self._label_encoders_]        return self    def transform(self, X):        """Transform X using one-hot encoding.        Parameters                X : array-like, shape [n_samples, n_features]            The data to encode.        Returns        --        X_out : sparse matrix or a 2-d array            Transformed input.        """        X = check_array(X, accept_sparse='csc', dtype=np.object, copy=True)        n_samples, n_features = X.shape        X_int = np.zeros_like(X, dtype=np.int)        X_mask = np.ones_like(X, dtype=np.bool)        for i in range(n_features):            valid_mask = np.in1d(X[:, i], self.categories_[i])            if not np.all(valid_mask):                if self.handle_unknown == 'error':                    diff = np.unique(X[~valid_mask, i])                    msg = ("Found unknown categories {0} in column {1}"                           " during transform".format(diff, i))                    raise ValueError(msg)                else:                    # Set the problematic rows to an acceptable value and                    # continue `The rows are marked `X_mask` and will be                    # removed later.                    X_mask[:, i] = valid_mask                    X[:, i][~valid_mask] = self.categories_[i][0]            X_int[:, i] = self._label_encoders_[i].transform(X[:, i])        if self.encoding == 'ordinal':            return X_int.astype(self.dtype, copy=False)        mask = X_mask.ravel()        n_values = [cats.shape[0] for cats in self.categories_]        n_values = np.array([0] + n_values)        indices = np.cumsum(n_values)        column_indices = (X_int + indices[:-1]).ravel()[mask]        row_indices = np.repeat(np.arange(n_samples, dtype=np.int32),                                n_features)[mask]        data = np.ones(n_samples * n_features)[mask]        out = sparse.csc_matrix((data, (row_indices, column_indices)),                                shape=(n_samples, indices[-1]),                                dtype=self.dtype).tocsr()        if self.encoding == 'onehot-dense':            return out.toarray()        else:            return out

在原書中使用LabelBinarizer的方式也是錯誤的,該類也應用於標籤列的轉換。正確做法是使用sklearn即將提供的CategoricalEncoder類。如果在你閱讀此文時sklearn中尚未提供此類,用如下方式代替:(來自Pull Request #9151)

轉換方法:

#from sklearn.preprocessing import CategoricalEncoder # in future versions of Scikit-Learncat_encoder = CategoricalEncoder()housing_cat_reshaped = housing_cat.values.reshape(-1, 1)housing_cat_1hot = cat_encoder.fit_transform(housing_cat_reshaped)housing_cat_1hot

Python愛好者社區歷史文章大合集

Python愛好者社區歷史文章列表(每周append更新一次)

福利:文末掃碼立刻關注公眾號,「Python愛好者社區」,開始學習Python課程:

關注後在公眾號內回復課程即可獲取:

小編的Python入門視頻課程!!!

崔老師爬蟲實戰案例免費學習視頻。

丘老師數據科學入門指導免費學習視頻。

陳老師數據分析報告製作免費學習視頻。

玩轉大數據分析!Spark2.X+Python 精華實戰課程免費學習視頻。

丘老師Python網絡爬蟲實戰免費學習視頻。

相關焦點