當前位置: 首頁>>技術教程>>正文


TensorFlow線性模型教程

在本教程中,我們將使用TensorFlow中的tf.estimator API來解決二元分類問題:給定有關某人(如年齡,教育程度,婚姻狀況和職業(特征))的人口普查數據,嘗試預測這個人每年是否能掙5萬多美元(目標標簽)。我們將訓練一個邏輯回歸模型,給定個人的信息,我們的模型將輸出0到1之間的數字,這可以解釋為個人年收入超過5萬美元的概率。

開始

要嘗試本教程的代碼,先:

  1. 安裝TensorFlow

  2. 下載教程代碼

  3. 執行我們提供給您的數據下載腳本:

    $ python data_download.py
    
  4. 使用以下命令執行教程代碼以訓練本教程中描述的線性模型:

    $ python wide_deep.py --model_type=wide
    

繼續閱讀以了解此代碼如何構建線性模型。

讀取人口普查數據

我們將使用的數據集是人口普查收入數據集。我們提供了用於下載代碼並執行一些額外的清理。

由於該任務是一個二元分類問題,因此我們將構造一個名為”label”的標簽列,如果收入超過50K,則值為1,否則為0。相關信息,請參閱input_fn,在

接下來,我們來看看數據情況,看看可以使用哪些列來預測目標標簽。這些列可以分為兩類:類別和連續值:

  • 類別數據:如果它的價值隻能是有限集合中的一個類別。例如,一個人(妻子,丈夫,未婚等)或教育水平(高中,大學等)的關係狀況是類別數據。
  • 連續數據:如果其值可以是連續範圍內的任何數值。例如,一個人的資本收益(例如$ 14,084)是一個連續的值。

以下是人口普查收入數據集中可用列的列表:

列名稱 類型 描述
age 連續 個人的年齡
workclass 類別 個人擁有的雇主類型(政府,軍隊,私人等)。
fnlwgt 連續 普查員認為觀察所代表的人數(樣本權重)。最終的權重將不會被使用。
education 類別 這個人達到的最高教育水平。
education_num 連續 數值形式的最高教育水平。
marital_status 類別 個人的婚姻狀況。
occupation 類別 個人的職業。
relationship 類別 妻子,Own-child,丈夫,Not-in-family,Other-relative,未婚。
race 類別 白色,Asian-Pac-Islander,Amer-Indian-Eskimo,其他,黑色。
gender 類別 女人男人。
capital_gain 連續 資本收益記錄。
capital_loss 連續 記錄資本損失。
hours_per_week 連續 每周工作時間。
native_country 類別 個人原籍國。
income 類別 “>50K”或“

將數據轉換為張量

在構建tf.estimator模型時,輸入數據通過輸入構建器函數來指定。此構建函數在稍後傳遞給tf.estimator.Estimator方法(例如,trainevaluate),之前不會被調用。這個函數的目的是構造輸入數據,它的形式表示為tf.Tensor或者tf.SparseTensor。具體來說,輸入生成器函數將成對返回以下內容:

  1. features:一個dict, 存儲從特征列名到TensorsSparseTensors的映射。
  2. labels: 一個Tensor包含標簽列。

features的鍵將被用於在下一節中構建列。因為我們想在不同的數據上調用trainevaluate方法,我們定義一個方法,返回一個基於給定數據的輸入函數。請注意,返回的輸入函數將在構建TensorFlow圖形時調用,而不是在運行圖形時調用。它返回的是輸入數據的表示,即Tensor(或SparseTensor)。

訓練或者測試數據中的每個連續列將被轉換成一個Tensor,這通常是表示密集數據的良好格式。對於類別數據,我們必須將數據表示為一個SparseTensor,這種數據格式適合表示稀疏數據。我們的input_fn使用tf.dataAPI,可以很容易地將轉換應用於我們的數據集:

def input_fn(data_file, num_epochs, shuffle, batch_size):
  """Generate an input function for the Estimator."""
  assert tf.gfile.Exists(data_file), (
      '%s not found. Please make sure you have either run data_download.py or '
      'set both arguments --train_data and --test_data.' % data_file)

  def parse_csv(value):
    print('Parsing', data_file)
    columns = tf.decode_csv(value, record_defaults=_CSV_COLUMN_DEFAULTS)
    features = dict(zip(_CSV_COLUMNS, columns))
    labels = features.pop('income_bracket')
    return features, tf.equal(labels, '>50K')

  # Extract lines from input files using the Dataset API.
  dataset = tf.data.TextLineDataset(data_file)

  if shuffle:
    dataset = dataset.shuffle(buffer_size=_SHUFFLE_BUFFER)

  dataset = dataset.map(parse_csv, num_parallel_calls=5)

  # We call repeat after shuffling, rather than before, to prevent separate
  # epochs from blending together.
  dataset = dataset.repeat(num_epochs)
  dataset = dataset.batch(batch_size)

  iterator = dataset.make_one_shot_iterator()
  features, labels = iterator.get_next()
  return features, labels

模型的特征選擇和特征工程

選擇和構造正確的特征列是學習有效模型的關鍵。一個特征列可以是原始數據中的原始列之一(我們稱它們為“基本特征列),或者基於在一個或多個基本列上定義的一些轉換創建的任何新列(我們稱之為“派生特征列)。基本上,”feature column”是可以用來預測目標標簽的任何原始或衍生變量的抽象概念。

基本類別特征列

要為類別特征定義特征列,我們可以使用tf.feature_column API創建一個CategoricalColumn。如果您知道列的所有可能的特征值的集合,並且特征值集合較小,您可以使用categorical_column_with_vocabulary_list。列表中的每個鍵都將被分配一個從0開始的自增ID。例如,對於relationship列,我們可以將特征字符串”Husband”分配給一個整數ID為0,”Not-in-family”分配給1,等等:

relationship = tf.feature_column.categorical_column_with_vocabulary_list(
    'relationship', [
        'Husband', 'Not-in-family', 'Wife', 'Own-child', 'Unmarried',
        'Other-relative'])

如果我們事先不知道可能的值怎麽辦?這不是問題。我們可以用categorical_column_with_hash_bucket代替:

occupation = tf.feature_column.categorical_column_with_hash_bucket(
    'occupation', hash_bucket_size=1000)

特征列中的每個可能的值occupation將被散列為整數ID。看下麵的一個例子:

ID 特征
9 "Machine-op-inspct"
103 "Farming-fishing"
375 "Protective-serv"

無論我們選擇哪種方式來定義一個SparseColumn,每個特征字符串將通過查找固定映射或直接哈希映射到整數ID。請注意,散列衝突是可能的,但可能不會顯著影響模型質量。在底層實現中,LinearModel類負責管理映射和創建tf.Variable存儲每個特征ID的模型參數(也稱為模型權重)。模型參數將通過稍後提到的模型訓練過程來學習。

我們使用類似的技巧來定義其他的類別特征:

education = tf.feature_column.categorical_column_with_vocabulary_list(
    'education', [
        'Bachelors', 'HS-grad', '11th', 'Masters', '9th', 'Some-college',
        'Assoc-acdm', 'Assoc-voc', '7th-8th', 'Doctorate', 'Prof-school',
        '5th-6th', '10th', '1st-4th', 'Preschool', '12th'])

marital_status = tf.feature_column.categorical_column_with_vocabulary_list(
    'marital_status', [
        'Married-civ-spouse', 'Divorced', 'Married-spouse-absent',
        'Never-married', 'Separated', 'Married-AF-spouse', 'Widowed'])

relationship = tf.feature_column.categorical_column_with_vocabulary_list(
    'relationship', [
        'Husband', 'Not-in-family', 'Wife', 'Own-child', 'Unmarried',
        'Other-relative'])

workclass = tf.feature_column.categorical_column_with_vocabulary_list(
    'workclass', [
        'Self-emp-not-inc', 'Private', 'State-gov', 'Federal-gov',
        'Local-gov', '?', 'Self-emp-inc', 'Without-pay', 'Never-worked'])

# To show an example of hashing:
occupation = tf.feature_column.categorical_column_with_hash_bucket(
    'occupation', hash_bucket_size=1000)

基本連續特征列

同樣,我們可以定義一個NumericColumn,對於我們要在模型中使用的每個連續特征列:

age = tf.feature_column.numeric_column('age')
education_num = tf.feature_column.numeric_column('education_num')
capital_gain = tf.feature_column.numeric_column('capital_gain')
capital_loss = tf.feature_column.numeric_column('capital_loss')
hours_per_week = tf.feature_column.numeric_column('hours_per_week')

通過分桶將連續性特征的轉為類別特征

有時連續特征和標簽之間的關係不是線性的。作為一個假設的例子,一個人的收入在職業生涯初期可能會隨著年齡的增長而增長,然後增長可能會放緩,最終退休後收入會減少。在這種情況下,使用原始的age作為實值特征列可能不是一個好的選擇,因為模型隻能學習三種情況之一:

  1. 收入總是隨著年齡增長而增長(正相關),
  2. 收入總是隨著年齡的增長而減少(負相關),或者
  3. 不論年齡多少,收入都保持不變(不相關)

如果我們想分別學習收入與各年齡段之間的細粒度相關性,我們可以利用分桶(bucketization)。分桶是將連續特征的整個範圍劃分為一組連續的桶,然後根據該值落入哪個桶將原始數值特征轉換成桶ID(作為分類特征)的過程。所以,我們可以定義一個bucketized_column過度age如:

age_buckets = tf.feature_column.bucketized_column(
    age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])

其中boundaries是桶邊界的列表。在這種情況下,有10個邊界,從而形成11個年齡組(從17歲以下,18-24,25-29,…,到65歲以上)。

用CrossedColumn交叉多列

單獨使用每個基本特征列可能不足以解釋數據。例如,對於不同的職業,教育與標簽(賺取50000美元)之間的相關性可能不同。因此,如果我們隻學習單一的模型權重education="Bachelors"education="Masters",我們將無法捕獲每一個education-occupation組合(例如區分education="Bachelors" AND occupation="Exec-managerial"education="Bachelors" AND occupation="Craft-repair")。要了解不同的功能組合之間的差異,我們可以添加交叉的特征列到模型。

education_x_occupation = tf.feature_column.crossed_column(
    ['education', 'occupation'], hash_bucket_size=1000)

我們也可以使用多個列創建一個CrossedColumn。每個組成列可以是基本類別特征列(SparseColumn),bucketized實值特征列(BucketizedColumn),甚至另一個CrossColumn。這是一個例子:

age_buckets_x_education_x_occupation = tf.feature_column.crossed_column(
    [age_buckets, 'education', 'occupation'], hash_bucket_size=1000)

定義Logistic回歸模型

處理輸入數據並定義所有特征列後,我們現在準備將它們放在一起並構建一個Logistic回歸模型。在上一節中,我們已經看到了幾種類型的基本和派生特征列,包括:

  • CategoricalColumn
  • NumericColumn
  • BucketizedColumn
  • CrossedColumn

所有這些都抽象FeatureColumn類的子類,可以添加到模型的feature_columns字段:

base_columns = [
    education, marital_status, relationship, workclass, occupation,
    age_buckets,
]
crossed_columns = [
    tf.feature_column.crossed_column(
        ['education', 'occupation'], hash_bucket_size=1000),
    tf.feature_column.crossed_column(
        [age_buckets, 'education', 'occupation'], hash_bucket_size=1000),
]

model_dir = tempfile.mkdtemp()
model = tf.estimator.LinearClassifier(
    model_dir=model_dir, feature_columns=base_columns + crossed_columns)

該模型還自動學習一個偏置項,它控製著沒有任何特征的預測(更多解釋請參見“邏輯回歸的工作原理”一節)。學習的模型文件將被存儲在model_dir

訓練和評估我們的模型

現在讓我們看看如何實際訓練模型。使用tf.estimator API訓練模型隻是一個簡單的命令:

model.train(input_fn=lambda: input_fn(train_data, num_epochs, True, batch_size))

在模型被訓練之後,我們可以評估我們的模型在預測剩餘數據標簽上的效果:

results = model.evaluate(input_fn=lambda: input_fn(
    test_data, 1, False, batch_size))
for key in sorted(results):
  print('%s: %s' % (key, results[key]))

最終輸出的第一行應該是這樣的accuracy: 0.83557522,這意味著準確率為83.6%。隨意嘗試更多的特征和轉換,看看你是否能做得更好!

如果你想看到一個可用的端到端的例子,你可以下載我們的示例代碼並設置model_type標誌為wide

加入正則化來防止過度擬合

正規化是一種用來避免過度擬合的技術。過度擬合時,模型在訓練數據上表現良好,但在模型以前從未見過的測試數據(如實時流量)上卻很糟糕。過度擬合通常發生在模型過於複雜的情況下,例如相對於觀察到的訓練數據的參數數量太多。正則化允許你控製你的模型的複雜性,並使模型在看不見的數據上有更好的泛化能力。

在線性模型庫中,可以將L1和L2正則化添加到模型中,如下所示:

model = tf.estimator.LinearClassifier(
    model_dir=model_dir, feature_columns=base_columns + crossed_columns,
    optimizer=tf.train.FtrlOptimizer(
        learning_rate=0.1,
        l1_regularization_strength=1.0,
        l2_regularization_strength=1.0))

L1和L2正則化之間的一個重要區別是,L1正則化傾向於使模型權重保持為零,從而創建更稀疏的模型,而L2正則化也嘗試使模型權重接近於零,但不一定為零。因此,如果您增加L1正則化的強度,您將有一個較小的模型大小,因為許多模型權重將為零。當特征空間非常大但是稀疏時,以及當存在資源限製時,通常需要較小的模型,因為這些資源限製會阻止您提供太大的模型。

在實踐中,你應該嘗試L1,L2正則化強度的各種組合,找到最好的控製過度擬合的最佳參數,並給你一個理想的模型大小。

邏輯回歸如何工作

最後,讓我們花一點時間談一談Logistic回歸模型實際上是什麽。我們將標簽表示為Y,並將觀察到的特征集合表示為特征向量x = [x1, x2, …, xd]。我們定義Y = 1,如果個人獲得>=5萬美元; 否則Y = 0。在Logistic回歸中,給定特征x的標簽為正(Y = 1)的概率為:

其中w = [w1, w2, …, wd]是特征x = [x1,x2, …, xd]的模型權重 。 b是一個常被稱為偏置的常量。該方程由兩部分組成:線性模型和邏輯函數:

  • 線性模型:首先,我們可以看到wT x + b = b + w1 x1 + … + wd xd是一個線性模型,輸出是輸入特征的線性函數X。偏置b是在沒有觀察到任何特征的情況下做出的預測。模型權重wi反映特征xi如何與正標簽相關。如果xi與正標簽正相關,則權重wi增加,並且概率P(Y = 1 | x)將更接近於1。另一方麵,如果xi與正標簽呈負相關,那麽權重wi將減少,並且概率P(Y = 1 | x)將更接近於0 。

  • 邏輯(Logistic)函數:其次,我們可以看到,對線性模型應用了邏輯函數(也稱為S形函數)S(t)= 1 /(1+exp(-t))。邏輯函數用於將線性模型的輸出從任何實數轉換到[0,1]的範圍內,可以被解釋為一個概率。

模型訓練是一個優化問題:目標是找到一組模型權重(即模型參數)來最小化定義在訓練數據上損失函數,如邏輯回歸模型的邏輯損失。損失函數測量實際標簽與模型預測之間的差異。如果預測值非常接近實際標簽,損失值將會很低;如果預測離標簽很遠,那麽損失值就會很高。

深入學習

如果你有興趣了解更多,請查看我們的Wide&Deep深度學習教程在這裏我們將向您展示如何結合使用tf.estimator API聯合訓練線性模型和深度神經網絡。

參考資料

本文由《純淨天空》出品。文章地址: https://vimsky.com/zh-tw/article/3658.html,未經允許,請勿轉載。