Keras: 多输入及混合数据输入的神经网络模型
目錄
?
摘要
正文
什么是混合數據?
Keras如何接受多個輸入?
房價數據集
獲取房價數據集
項目結構
加載數值和分類數據
加載圖像數據集
定義多層感知器(MLP)和卷積神經網絡(CNN)
使用Keras的多個輸入
多輸入和混合數據結果
總結
翻譯自:Keras: Multiple Inputs and Mixed Data, by Adrian Rosebrock.
摘要
點擊此處下載源代碼:https://jbox.sjtu.edu.cn/l/NHfFZu
以回歸預測房價作為背景。房價數據集不僅包括數值和類別數據,同樣也包括圖像數據,稱之為多種類型的混合數據。模型需要能夠接受多種類型的混合數據輸入,并且計算得出回歸問題的輸出值。
在本教程的其余部分,您將學習如何:
正文
在本教程的第一部分中,我們將簡要回顧混合數據的概念以及Keras如何接受多個類型輸入數據。
什么是混合數據?
圖1:使用靈活的Keras深度學習框架,可以定義一個多輸入模型,其分別包括CNN和MLP分支來分別處理混合數據。
混合數據指的是同時使用不同數據類型的輸入數據。
例如,假設我們是在一家醫院工作的機器學習工程師,要開發一個能夠對病人的健康狀況進行分類的系統。
我們擁有一個病人的多種類型的輸入數據,包括:
我們的機器學習模型必須能夠將這些**“混合數據”**,并對病人的健康狀況做出(準確的)預測。
開發能夠處理混合數據的機器學習系統非常具有挑戰性,因為每種數據類型可能需要單獨的預處理步驟,包括縮放、標準化和特征工程(feature engineering)。
處理混合數據仍然是一個非常開放的研究領域,并且常常嚴重依賴于特定的任務/目標。
Keras如何接受多個輸入?
Keras能夠通過它的函數API處理多個輸入(甚至多個輸出)。
您以前肯定通過Sequential 類使用過順序式API,函數式API與之相反相反,可用于定義非順序的復雜得多的模型,包括:
- 多輸入模型
- 多輸出模型
- 模型包括多個輸入和多個輸出
- 有向無環圖
- 具有共享層的模型
例如,我們可以將一個簡單的序列神經網絡定義為:
model = Sequential() model.add(Dense(8, input_shape=(10,), activation="relu")) model.add(Dense(4, activation="relu")) model.add(Dense(1, activation="linear"))該網絡是一個簡單的前饋神經網絡,有10個輸入,第一個隱層有8個節點,第二個隱層有4個節點,最后一個輸出層用于回歸。
我們可以使用functional API定義樣本神經網絡:
inputs = Input(shape=(10,)) x = Dense(8, activation="relu")(inputs) x = Dense(4, activation="relu")(x) x = Dense(1, activation="linear")(x) model = Model(inputs, x)# define two sets of inputs inputA = Input(shape=(32,)) inputB = Input(shape=(128,))# the first branch operates on the first input x = Dense(8, activation="relu")(inputA) x = Dense(4, activation="relu")(x) x = Model(inputs=inputA, outputs=x)# the second branch opreates on the second input y = Dense(64, activation="relu")(inputB) y = Dense(32, activation="relu")(y) y = Dense(4, activation="relu")(y) y = Model(inputs=inputB, outputs=y)# combine the output of the two branches combined = concatenate([x.output, y.output])# apply a FC layer and then a regression prediction on the # combined outputs z = Dense(2, activation="relu")(combined) z = Dense(1, activation="linear")(z)# our model will accept the inputs of the two branches and # then output a single value model = Model(inputs=[x.input, y.input], outputs=z)可以看到我們定義了Keras神經網絡的兩個輸入:
- inputA: 32維
- inputB: 128維
可視化模型架構為:
圖2:這個模型有兩個輸入分支,它們最終合并并產生一個輸出。Keras函數API允許這種類型的體系結構,你也可以構建任何其他您可以想象的架構。
注意我們的模型有兩個不同的分支。
第一個分支接受128維輸入,而第二個分支接受32維輸入。這些分支在連接之前彼此獨立運行,連接之后輸出一個值。
在本教程的其余部分中,您將學習如何使用Keras創建多輸入的網絡。
房價數據集
圖4:房價數據集包括數值數據,類別數據和圖像數據。使用Keras,我們將構建一個支持多種輸入和混合數據類型的模型,并且通過這個回歸模型預測房屋的價值。
在這一系列文章中,我們使用了Ahmed和Mustafa在2016年發表的論文《從視覺和文本特征估計房價》(House price estimate from visual and text features)中的房價數據集。
這個數據集包括535個示例房屋的數值數據,類別數據以及圖像數據。
數值屬性和分類屬性包括:
每棟房子一共提供了四幅圖片:
在本系列的第一篇文章中,您學習了如何根據數值和分類數據訓練Keras回歸網絡。
在本系列的第二篇文章中,您學習了如何使用Keras CNN進行回歸。
今天我們將使用Keras處理多個輸入和混合數據。
我們將接受數值數據,類別數據和圖像數據,通過定義網絡的兩個分支來處理每種類型的數據,最后將這些分支合并起來,得到我們最終的房價預測。通過這種方式,我們將能夠利用Keras處理多個輸入和混合數據。
獲取房價數據集
點擊此處下載源代碼:https://jbox.sjtu.edu.cn/l/NHfFZu
房價數據集應該在keras-multi-input目錄中,這是我們在這個項目中使用的目錄。
項目結構
$ tree --dirsfirst --filelimit 10 . ├── Houses-dataset │ ├── Houses\ Dataset [2141 entries] │ └── README.md ├── pyimagesearch │ ├── __init__.py │ ├── datasets.py │ └── models.py └── mixed_training.py3 directories, 5 filesHouses-dataset文件夾包含我們在本系列中使用的房價數據集。當我們準備好運行mixed_training.py腳本時,您只需要提供一個路徑作為數據集的命令行參數(我將在結果部分向您詳細說明這是如何完成的)。
今天我們將回顧三個Python腳本:
-
pyimagesearch/datasets.py: 加載和預處理我們的數字數據,類別數據以及圖像數據。
-
pyimagesearch/models.py: 包含多層感知器(MLP)和卷積神經網絡(CNN)。這些組件是我們的多輸入混合數據模型的輸入分支。
-
mixed_training.py: 首先我們的訓練腳本將使用pyimagesearch模塊來加載和分割訓練數據集,添加數據頭,并將兩個分支連接到我們的網絡。然后對模型進行培訓和評估。
加載數值和分類數據
# import the necessary packages from sklearn.preprocessing import LabelBinarizer from sklearn.preprocessing import MinMaxScaler import pandas as pd import numpy as np import glob import cv2 import osdef load_house_attributes(inputPath):# initialize the list of column names in the CSV file and then# load it using Pandascols = ["bedrooms", "bathrooms", "area", "zipcode", "price"]df = pd.read_csv(inputPath, sep=" ", header=None, names=cols)# determine (1) the unique zip codes and (2) the number of data# points with each zip codezipcodes = df["zipcode"].value_counts().keys().tolist()counts = df["zipcode"].value_counts().tolist()# loop over each of the unique zip codes and their corresponding# countfor (zipcode, count) in zip(zipcodes, counts):# the zip code counts for our housing dataset is *extremely*# unbalanced (some only having 1 or 2 houses per zip code)# so let's sanitize our data by removing any houses with less# than 25 houses per zip codeif count < 25:idxs = df[df["zipcode"] == zipcode].indexdf.drop(idxs, inplace=True)# return the data framereturn dfload_house_attributes函數。該函數通過panda的pd以CSV文件的形式從房價數據集中讀取數值和類別數據。
原始數據需要經過過濾以適應樣本分布的不均勻性。如有些郵編僅由1或2所房子表示,因此我們要刪除(第23-30行)來自郵編少于25所房子的任何記錄。這樣郵編樣本數量分布不均勻的問題可以得到緩解,這樣做的結果是得到一個更精確的模型。
定義process_house_attributes函數:
def process_house_attributes(df, train, test):# initialize the column names of the continuous datacontinuous = ["bedrooms", "bathrooms", "area"]# performin min-max scaling each continuous feature column to# the range [0, 1]cs = MinMaxScaler()trainContinuous = cs.fit_transform(train[continuous])testContinuous = cs.transform(test[continuous])# one-hot encode the zip code categorical data (by definition of# one-hot encoding, all output features are now in the range [0, 1])zipBinarizer = LabelBinarizer().fit(df["zipcode"])trainCategorical = zipBinarizer.transform(train["zipcode"])testCategorical = zipBinarizer.transform(test["zipcode"])# construct our training and testing data points by concatenating# the categorical features with the continuous featurestrainX = np.hstack([trainCategorical, trainContinuous])testX = np.hstack([testCategorical, testContinuous])# return the concatenated training and testing datareturn (trainX, testX)這個函數通過scikit-learn的MinMaxScaler(第41-43行)對連續特性應用最小-最大縮放。
然后,通過scikit-learn的LabelBinarizer(第47-49行)計算分類特征的one-hot編碼。
然后將連續的和分類的特性連接起來并返回
加載圖像數據集
圖6:我們模型的一個分支接受一個圖像——來自房屋的四個圖像的拼合圖像。利用拼合圖像結合數字,類別數據,輸入到另一個分支,然后我們的模型使用Keras框架回歸與預測住宅的價值。
下一步是定義一個helper函數來加載輸入圖像。同樣,打開data .py文件并插入以下代碼:
def load_house_images(df, inputPath):# initialize our images array (i.e., the house images themselves)images = []# loop over the indexes of the housesfor i in df.index.values:# find the four images for the house and sort the file paths,# ensuring the four are always in the *same order*basePath = os.path.sep.join([inputPath, "{}_*".format(i + 1)])housePaths = sorted(list(glob.glob(basePath)))load_house_images函數有三個功能:
繼續:
- 初始化圖像列表(第61行)并將用我們創建的所有拼合圖像填充這個列表。
- 循環遍歷數據幀中的房屋(第64行)以獲取當前住宅的四張照片的路徑
循環內部:
# initialize our list of input images along with the output image# after *combining* the four input imagesinputImages = []outputImage = np.zeros((64, 64, 3), dtype="uint8")# loop over the input house pathsfor housePath in housePaths:# load the input image, resize it to be 32 32, and then# update the list of input imagesimage = cv2.imread(housePath)image = cv2.resize(image, (32, 32))inputImages.append(image)# tile the four input images in the output image such the first# image goes in the top-right corner, the second image in the# top-left corner, the third image in the bottom-right corner,# and the final image in the bottom-left corneroutputImage[0:32, 0:32] = inputImages[0]outputImage[0:32, 32:64] = inputImages[1]outputImage[32:64, 32:64] = inputImages[2]outputImage[32:64, 0:32] = inputImages[3]# add the tiled image to our set of images the network will be# trained onimages.append(outputImage)# return our set of imagesreturn np.array(images)到目前為止,代碼已經完成了上面討論的第一個目標(每個房子抓取四個圖像)。
-
在循環中,我們:
-
執行初始化(第72行和第73行)。我們的inputImages將以列表的形式包含每條記錄的四張照片。我們的inputImages將是照片的拼接圖像(如圖6所示)。
-
循環4張照片(第76行):
- 加載、調整大小并將每張照片附加到?inputImages中(第79-81行)。
-
為四個房子的圖片(第87-90行)創建平鋪(拼接圖像):
- 左上方的浴室圖片。
- 右上角的臥室圖片。
- 右下角的正面視圖。
- 廚房在左下角。
-
添加拼接outputImage到images(第94行)。
-
-
跳出循環,我們以NumPy數組的形式返回所有圖像(第97行)。
定義多層感知器(MLP)和卷積神經網絡(CNN)
圖7:Keras多輸入(混合數據)模型有一個分支接受數字/類別數據(左),另一個分支接受4張照片拼接形式的圖像數據(右)。
到目前為止,我們已經使用了多個庫對數據進行了仔細的處理:panda、scikit-learn、OpenCV和NumPy。
我們已經通過datasets.py對數據集的兩種模式進行了組織和預處理。
- 數字和分類數據
- 圖像數據
為了實現這一點,我們所使用的技能是通過經驗和實踐一點點調試開發出來的。請不要忽視我們到目前為止所討論和使用的數據處理技巧,因為它是我們項目成功的關鍵。
讓我們換個話題,討論一下我們將如何使用Keras的函數API構建的多輸入和混合數據網絡。
為了建立我們的多輸入網絡,我們需要兩個分支:
- 第一個分支是一個簡單的多層感知器(MLP),用于處理數值輸入。
- 第二個分支是卷積神經網絡,用于對圖像數據進行操作。
- 然后將這些分支連接在一起,形成最終的多輸入Keras模型。
我們將在下一節中處理構建最終的連接多輸入模型,我們當前的任務是定義這兩個分支。
打開models.py文件,插入如下代碼:
# import the necessary packages from keras.models import Sequential from keras.layers.normalization import BatchNormalization from keras.layers.convolutional import Conv2D from keras.layers.convolutional import MaxPooling2D from keras.layers.core import Activation from keras.layers.core import Dropout from keras.layers.core import Dense from keras.layers import Flatten from keras.layers import Input from keras.models import Modeldef create_mlp(dim, regress=False):# define our MLP networkmodel = Sequential()model.add(Dense(8, input_dim=dim, activation="relu"))model.add(Dense(4, activation="relu"))# check to see if the regression node should be addedif regress:model.add(Dense(1, activation="linear"))# return our modelreturn model我們的類別/數值數據將由一個簡單的多層感知器(MLP)處理。
MLP由create_mlp定義。
我們的MLP很簡單:
- 具有ReLU激活的完全連接(密集)輸入層
- 一個完全連接的隱藏層,也帶有ReLU激活
- 最后,一個線性激活的可選的回歸輸出
雖然我們在第一篇文章中使用了MLP的回歸輸出,但是在這個多輸入混合數據網絡中不會使用它。您很快就會看到,我們將顯式地設置regress=False,即使它也是默認值。稍后將在整個多輸入混合數據網絡的頭部執行回歸。
根據圖7,我們現在已經構建了網絡的左上分支。
現在讓我們來定義我們網絡的右上角分支,CNN:
def create_cnn(width, height, depth, filters=(16, 32, 64), regress=False):# initialize the input shape and channel dimension, assuming# TensorFlow/channels-last orderinginputShape = (height, width, depth)chanDim = -1# define the model inputinputs = Input(shape=inputShape)# loop over the number of filtersfor (i, f) in enumerate(filters):# if this is the first CONV layer then set the input# appropriatelyif i == 0:x = inputs# CONV => RELU => BN => POOLx = Conv2D(f, (3, 3), padding="same")(x)x = Activation("relu")(x)x = BatchNormalization(axis=chanDim)(x)x = MaxPooling2D(pool_size=(2, 2))(x)create_cnn函數處理圖像數據并接受五個參數:
- 寬度:輸入圖像的寬度,單位為像素。
- 高度:輸入圖像的高度,單位為像素。
- 深度:輸入圖像中的通道數。對于RGB彩色圖像,它是3。
- 過濾器:一組逐漸變大的過濾器,使我們的網絡可以學習更多的區分功能。
- 回歸:一個布爾值,指示是否將一個完全連接的線性激活層添加到CNN以進行回歸。
從這里開始,我們開始遍歷過濾器并創建一組CONV => RELU > BN =>POOL 層。循環的每次迭代都會累加這些層。
讓我們完成CNN網絡分支的建設:
# flatten the volume, then FC => RELU => BN => DROPOUTx = Flatten()(x)x = Dense(16)(x)x = Activation("relu")(x)x = BatchNormalization(axis=chanDim)(x)x = Dropout(0.5)(x)# apply another FC layer, this one to match the number of nodes# coming out of the MLPx = Dense(4)(x)x = Activation("relu")(x)# check to see if the regression node should be addedif regress:x = Dense(1, activation="linear")(x)# construct the CNNmodel = Model(inputs, x)# return the CNNreturn model我們將下一層壓平,意味著我們將所有提取到的特征組成一維特征向量,然后添加一個帶有BatchNormalization和Dropout的全連接層。
另一個全連接層用于匹配來自多層感知器的四個節點。匹配節點的數量不是必需的,但它確實有助于平衡分支。
檢查是否添加回歸節點,如果需要就相應地將其添加進來。實際上,我們不會在這個分支的末尾進行回歸。回歸將在多輸入混合數據網絡的頭部執行(圖7的最底部)。
最后,模型由我們的輸入和組裝在一起的所有層組成。我們可以將CNN分支返回到調用函數(第68行)。
現在我們已經定義了多輸入Keras模型的兩個分支,讓我們學習如何組合它們!
使用Keras的多個輸入
現在,我們準備構建最終的Keras模型,該模型能夠處理多個輸入和混合數據。這是分支聚集的地方——“魔法”發生的地方。
訓練也將在這個腳本中進行。
創建一個名為mixed_training.py的新文件,打開它,并插入以下代碼:
# import the necessary packages from pyimagesearch import datasets from pyimagesearch import models from sklearn.model_selection import train_test_split from keras.layers.core import Dense from keras.models import Model from keras.optimizers import Adam from keras.layers import concatenate import numpy as np import argparse import locale import os# construct the argument parser and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-d", "--dataset", type=str, required=True,help="path to input dataset of house images") args = vars(ap.parse_args())首先,讓我們導入必要的模塊并且解析命令行參數。
- datasets: 我們的三個方便的功能,從房屋數據集加載/處理CSV數據和加載/預處理房屋照片。
- models: 我們的MLP和CNN輸入分支,它們將作為我們的多輸入混合數據服務。
- train_test_split: 一個scikit-learn函數,用于構造我們的訓練/測試數據分割。
- concatenate: 一個特殊的Keras函數,它將接受多個輸入。
- argparse: 處理解析命令行參數。
在第15-18行中,我們有一個命令行參數需要解析,即dataset,它是您下載房價數據集的路徑。
接下來,讓我們加載我們的數值/分類數據和圖像數據:
# construct the path to the input .txt file that contains information # on each house in the dataset and then load the dataset print("[INFO] loading house attributes...") inputPath = os.path.sep.join([args["dataset"], "HousesInfo.txt"]) df = datasets.load_house_attributes(inputPath)# load the house images and then scale the pixel intensities to the # range [0, 1] print("[INFO] loading house images...") images = datasets.load_house_images(df, args["dataset"]) images = images / 255.0在這里,我們將房價數據集加載為panda dataframe(第23行和第24行)。然后我們加載圖像并將其縮放到 [0,1] (第29-30行)。
如果需要提醒您這些函數的底層功能,請務必查看上面的load_house_attributes和load_house_images函數。
現在我們的數據已經加載完畢,我們將構建我們的培訓/測試分割,調整價格,并處理房屋屬性:
我們的訓練和測試是在第35行和第36行進行的。我們分配了75%的數據用于培訓,25%的數據用于測試。
在此基礎上,我們從培訓集(第41行)中找到maxPrice,并相應地調整培訓和測試數據(第42行和第43行)。將價值數據調整到[0,1]范圍內,可以更好地訓練和收斂。
最后,我們通過對連續特征執行最小-最大縮放和對分類特征執行一次熱編碼繼續處理我們的房子屬性。
process_house_attributes函數處理這些操作,并將連續的和分類的特性連接在一起,返回結果(第48行和第49行)。
準備好施魔法了嗎?
好吧,我說謊了。在下一個代碼塊中實際上沒有任何“魔力”!但我們將連接我們的網絡分支,完成我們的多輸入Keras網絡:
當您組織好代碼和模型后,使用Keras處理多個輸入是非常容易的。
在第52行和第53行,我們創建mlp和cnn模型。注意regress=False——我們的回歸頭出現在第62行后面。
然后我們將連接mlp輸出和cnn輸出如第57行所示。我將其稱為我們的combinedInput,因為它是網絡其余部分的輸入(從圖3中可以看到,這是concatenate_1,兩個分支在一起)。
網絡中最后一層的組合輸入是基于MLP和CNN分支的 8-4-1 FC層的輸出(因為這兩個分支都輸出4維 FC層,然后我們將它們連接起來創建一個8維向量)。
我們將一個由四個神經元組成的完全連接的層固定在combinedInput上(第61行)。然后我們添加“liner”activation回歸頭(第62行),其輸出為預測價格。
讓我們繼續編譯、培訓和評估我們新形成的模型:
# compile the model using mean absolute percentage error as our loss, # implying that we seek to minimize the absolute percentage difference # between our price *predictions* and the *actual prices* opt = Adam(lr=1e-3, decay=1e-3 / 200) model.compile(loss="mean_absolute_percentage_error", optimizer=opt)# train the model print("[INFO] training model...") model.fit([trainAttrX, trainImagesX], trainY,validation_data=([testAttrX, testImagesX], testY),epochs=200, batch_size=8)# make predictions on the testing data print("[INFO] predicting house prices...") preds = model.predict([testAttrX, testImagesX])我們的模型是用“mean_absolute_percentage_error”損失和一個Adam優化器編譯的,該優化器具有學習率衰減(第72行和第73行)。
訓練在第77-80行開始。這就是所謂的模型擬合(也就是所有權重都由稱為反向傳播的過程進行調優的地方)。
通過對測試數據集調用model.predict(第84行)可以獲取模型預測的房屋價值來評估我們的模型。
現在讓我們進行評估:
# compute the difference between the *predicted* house prices and the # *actual* house prices, then compute the percentage difference and # the absolute percentage difference diff = preds.flatten() - testY percentDiff = (diff / testY) * 100 absPercentDiff = np.abs(percentDiff)# compute the mean and standard deviation of the absolute percentage # difference mean = np.mean(absPercentDiff) std = np.std(absPercentDiff)# finally, show some statistics on our model locale.setlocale(locale.LC_ALL, "en_US.UTF-8") print("[INFO] avg. house price: {}, std house price: {}".format(locale.currency(df["price"].mean(), grouping=True),locale.currency(df["price"].std(), grouping=True))) print("[INFO] mean: {:.2f}%, std: {:.2f}%".format(mean, std))為了評估我們的模型,我們計算了絕對百分比(第89-91行),并使用它得出了最終的度量標準(第95和96行)。
這些度量(價格平均值、價格標準差和絕對百分比的平均值以及標準差)將以合適的格式(第100-103行)打印到終端。
多輸入和混合數據結果
圖8:房地產價格預測是一項困難的任務,但是我們的Keras多輸入和混合輸入回歸模型在我們有限的房價數據集上產生了比較好的結果。
最后,我們在混合數據上訓練我們的多輸入網絡!
確保你準備好了:
在此基礎上,打開終端,執行以下命令,開始網絡訓練:
$ python mixed_training.py --dataset Houses-dataset/Houses\ Dataset/我們的平均絕對百分比誤差開始非常高,但在整個培訓過程中不斷下降。
在訓練結束時,我們得到了22.41%的測試集絕對誤差,這意味著我們的網絡對房價的預測平均會下降22%左右。
我們將這個結果與本系列之前的兩篇文章進行比較:
如你所見,處理混合數據的方法如下:
總結
在本教程中,您學習了如何定義能夠接受多個輸入的Keras網絡。
您還學習了如何使用Keras處理混合數據。
為了實現這些目標,我們定義了一個能夠接受的多輸入神經網絡:
- 數值數據
- 分類數據
- 圖像數據
在訓練前,將數值數據的min-max縮放到[0,1]范圍。我們的類別數據是one-hot編碼的(確保得到的整數向量在[0,1]范圍內)。
然后將數值和類別數據連接成一個特征向量,形成Keras網絡的第一個輸入。
我們的圖像數據也被縮放到范圍[0,1]——這些數據作為Keras網絡的第二個輸入。
模型的一個分支包含嚴格的全連通層(對于連接的數值和類別數據),而多輸入模型的第二個分支本質上是一個小的卷積神經網絡。
將兩個分支的輸出組合起來,定義一個輸出(回歸預測)。
通過這種方式,我們能夠訓練我們的多個輸入網絡端到端,從而獲得比僅使用其中一個輸入更好的準確性。
翻譯自:Keras: Multiple Inputs and Mixed Data, by Adrian Rosebrock.
總結
以上是生活随笔為你收集整理的Keras: 多输入及混合数据输入的神经网络模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: sobol敏感性分析 matlab代码
- 下一篇: dropout+Batch Normal