Keras入門筆記(一)-從二分類問題到多分類問題、圖像識別問題

Keras是一個Python深度學習框架,是個高層的API庫。它不同於TensorFlow、PyTorch等微分器,Keras高度封裝了底層的微分計算等操作,提供了用戶友好的API,並且支持在TensorFlow、Theano和CNTK這三個底層微分庫之間切換。目前,Keras已被欽定爲TensorFlow的用戶接口,其地位相當於TorchVision之於PyTorch

  • 本文主要基於Keras2作介紹。

Keras的結構

Keras中我們

  • utils
  • activations:提供了常見的激活函數,如relu等。
  • applications
  • backend:提供了TensorFlow、Theano和CNTK的底層接口。
  • datasets:提供了一些常用的小數據集,用於上手。
  • engine
  • layers:提供了常見的網絡結構層,是Keras最重要的包。
  • preprocessing:數據預處理模塊,也提供了數據增強的常用包。
  • wrappers
  • callbacks
  • constraints
  • initializers:提供了參數初始化的方法。
  • metrics
  • models:模型是Keras對網絡層、優化器等結構的組織模塊。
  • losses:提供了常見的損失函數/目標函數,如Mean-Squared-Error等。
  • optimizers: 優化器,包含了一些優化的方法。
  • regularizers:提供了一系列正則器。

Keras的常見結構

① 模型

Keras的核心數據結構是“模型”,模型指的是對網絡層的組織方式。目前,Keras的模型只有兩種形式:

  1. Sequential
  2. Functional
from keras.models import Sequential
network = Sequential()

就構建了一個序列模型,接下下來就是不斷往這個網絡結構中添加不同的網絡層並構建它們的連接次序。

② 網絡層

網絡層由keras.layers進行定義:

from keras.layers import Dense

就引入了一個全連接層,接着我們無返回地把它添加到網絡結構中:

network.add(Dense(units=512, activation='relu', inputshap=(1024, )))
network.add(Dense(units=10, activation='softmax'))

就爲網路結構添加了一個512個神經元的全鏈接層和10個神經元的輸出層。

注意到:只有第一層需要指定輸入數據的尺寸;此後的每一層將會自動進行推導。

③ 模型編譯和訓練

現在我們要指定模型的訓練方式,我們需要重點關心的有:

  1. optimizer:優化器,可能會影響到模型的收斂速度
  2. loss:模型的目標函數/損失函數
  3. metrics:指標列表,訓練時將會根據該列表記錄訓練效果的衡量值
  4. epochsbatch_size:訓練的輪次和梯度下降時每個batch將包含的樣本數。
  5. verbose:控制檯日誌,值爲0|1|2——分別對應”不在控制檯輸出任何消息“、“訓練每輪訓練進度”、“僅輸出訓練輪的信息”
  6. validation_datavalidation_split:驗證集或自動劃分驗證集的比例。

我們調用model.compile方法進行編譯:

network.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

隨後,再對編譯完畢的模型進行訓練:

network.fit(train_data, train_labels, epochs=10, batch_size=1024, validation_split=0.3, verbose=2)

二分類問題:電影評論分類

二分類問題可能是應用最廣泛的機器學習問題。我們可以利用Keras自帶的IMDB數據集進行初步上手練習。

IMDB 數據集它包含來自互聯網電影數據庫(IMDB)的 50 000 條嚴重兩極分化的評論。數據集被分爲用於訓練的 25 000 條評論與用於測試的 25 000 條評論,訓練集和測試集都包含 50% 的正面評論和 50% 的負面評論。

  • imdb_data 是由評論詞組成的索引列表,其前三位爲保留值,即真正的評論詞索引從序數 33 開始。
  • imdb_label 是由 0|1 組成的二值化評價。
from keras.datasets import imdb
from keras.layers import Dense
from keras.models import Sequential
import numpy as np
import matplotlib.pyplot as plt


def ont_hot(data_mat, dim):
    """
    對一個矩陣進行獨熱編碼
    :param data_mat: 數據矩陣
    :param dim: 獨熱編碼的維度
    :return: 返回獨熱編碼後的數據矩陣
    """
    res = np.zeros((len(data_mat), dim))
    for i, index in enumerate(data_mat):
        res[i][index] = 1.0
    return res


# 取前10000個高頻評論詞彙
nb_words = 10000

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=nb_words)

# 對訓練數據、測試數據進行獨熱編碼和numpy向量化
train_data = ont_hot(train_data, nb_words)
test_data = ont_hot(test_data, nb_words)
train_labels = np.asarray(train_labels).astype('float32')
test_labels = np.asarray(test_labels).astype('float32')

network = Sequential()
network.add(Dense(16, activation='relu', input_shape=(nb_words,)))
network.add(Dense(16, activation='relu', input_shape=(16,)))
network.add(Dense(1, activation='sigmoid'))

network.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

history = network.fit(train_data, train_labels, epochs=10, batch_size=1024, validation_split=0.3, verbose=2).history


# --模型的應用--

# 隨機選擇一項進行預測
result = list(map(lambda x: 1 if x > 0.5 else 0, network.predict(test_data)))
print('positive' if result[np.random.randint(len(result))] == 1 else 'negative')


# 畫圖,可以明顯看到過擬合現象
train_loss, val_loss = history['loss'], history['val_loss']

epochs = range(len(train_loss))

plt.plot(epochs, train_loss, 'bo', label='train loss')
plt.plot(epochs, val_loss, 'b-', label='val loss')
plt.xlabel('epochs')
plt.ylabel('loss')
plt.legend()
plt.show()

多分類問題:路透社新聞評論

透社數據集,它包含許多短新聞及其對應的主題,由路透社在 1986 年發佈。它是一個簡單的、廣泛使用的文本分類數據集。

路透社新聞數據集和IMDB數據集類似,只不過其label爲 4646 維的輸出值。

from keras.datasets import reuters
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Dense
import numpy as np
import matplotlib.pyplot as plt


nb_words = 10000
(train_data, train_labels), (test_data, test_labels) = reuters.load_data(num_words=nb_words)


def ont_hot(data_mat, dim):
    """
    對一個矩陣進行獨熱編碼
    :param data_mat: 數據矩陣
    :param dim: 獨熱編碼的維度
    :return: 返回獨熱編碼後的數據矩陣
    """
    res = np.zeros((len(data_mat), dim))
    for i, index in enumerate(data_mat):
        res[i][index] = 1.0
    return res


train_data = ont_hot(train_data, nb_words)
test_data = ont_hot(test_data, nb_words)

# 採用 loss='categorical_crossentropy'
# train_labels = to_categorical(train_labels)
# test_labels = to_categorical(test_labels)

# 採用 loss='sparse_categorical_crossentropy' 只需要編譯成一個numpy張量即可
# train_labels = np.array(train_labels)
# test_labels = np.array(test_labels)


network = Sequential()
network.add(Dense(128, activation='relu', input_shape=(nb_words,)))
network.add(Dense(64, activation='relu'))
network.add(Dense(46, activation='softmax'))  # 與imdb數據集不同,其輸出不是二分類問題

network.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])

history = network.fit(train_data, train_labels, epochs=20, batch_size=1024, validation_split=0.3, verbose=2).history


# --模型的應用--


# 畫圖,可以明顯看到過擬合現象
train_loss, val_loss = history['loss'], history['val_loss']

epochs = range(len(train_loss))

plt.plot(epochs, train_loss, 'bo', label='train loss')
plt.plot(epochs, val_loss, 'b-', label='val loss')
plt.xlabel('epochs')
plt.ylabel('loss')
plt.legend()
plt.show()

迴歸問題:波士頓房價

任何看過西瓜書或吳恩達視頻課程的同學都不會對這個數據集感到陌生。

不同於上兩個問題的做法,我們將在下文采用新的損失函數和評價指標。

首先我們將採用K折法對模型進行評價,所以我們需要多次編譯模型,因此需要構建一個模塊化的模型方法:nn()

from keras.datasets import boston_housing
from keras.models import Sequential
from keras.layers import Dense
import matplotlib.pyplot as plt
import numpy as np


def z_score(data, axis=0):
    """
    :param data: 2-D numpy - array
    :param axis: ...
    :return:
    """
    mean = data.mean(axis=axis)
    std = data.std(axis=axis)
    return (data - mean) / std


def nn(data_shape):
    """
    :param data_shape: (size of samples, num of features)
    :return:
    """
    k_model = Sequential()
    k_model.add(Dense(64, activation='relu', input_shape=(data_shape[1],)))
    k_model.add(Dense(32, activation='relu'))
    k_model.add(Dense(1))
    # MSE 均方誤差
    # MAE 平均絕對誤差 : 預測值與目標值之差的絕對值
    k_model.compile(optimizer='sgd', loss='mse', metrics=['mae'])

    return k_model


(train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()

# 標準化
train_data = z_score(train_data)
test_data = z_score(test_data)

# K折法訓練
k = 4
histories = []

for i in range(k):
    network = nn(train_data.shape)

    k_nums_samples = train_data.shape[0] // k

    k_val_data = train_data[i * k_nums_samples:(i + 1) * k_nums_samples]
    k_val_targets = train_targets[i * k_nums_samples:(i + 1) * k_nums_samples]

    k_train_data = \
        np.concatenate([train_data[: i * k_nums_samples], train_data[(i + 1) * k_nums_samples:]], axis=0)
    k_train_targets = \
        np.concatenate([train_targets[: i * k_nums_samples], train_targets[(i + 1) * k_nums_samples:]], axis=0)

    history = network.fit(k_val_data, k_val_targets, epochs=200, batch_size=256,
                          validation_data=(k_val_data, k_val_targets), verbose=2).history

    histories.append(history['val_loss'])

# --模型的應用--


# 畫圖,可以明顯看到過擬合現象

epochs = len(histories[0])

avg_val_loss = [np.mean([k[i] for k in histories]) for i in range(epochs)]

plt.plot(range(epochs), avg_val_loss, 'r-.', label='val loss')
plt.xlabel('epochs')
plt.ylabel('loss')
plt.legend()
plt.show()

sgd.jpg

如果把上文中的優化器換成adam(滑動平均),會觀察到一個更平緩的函數圖像:

adam.jpg

圖像識別問題:MNIST數據集

MINIST數據集非常經典,不需要進行更多地介紹,採用全連接對MNIST進行訓練也很簡單:

import keras
from keras.datasets import mnist
from keras.layers import Dense
from keras.models import Sequential

# 載入數據集
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

x_train, h, w = train_images.shape
train_images = train_images.reshape((x_train, h * w)) / 255  # 歸一化處理,可以使得其更快收斂

x_test, h, w = test_images.shape
test_images = test_images.reshape((x_test, h * w)) / 255

# 對一個list進行one-hot編碼
train_labels = keras.utils.to_categorical(train_labels)
test_labels = keras.utils.to_categorical(test_labels)

# 構建網絡模型
network = Sequential()
network.add(Dense(512, activation='relu', input_shape=(h * w,)))
network.add(Dense(128, activation='relu', input_shape=(512,)))
network.add(Dense(10, activation='softmax'))

network.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

network.fit(train_images, train_labels, epochs=10, batch_size=1024, verbose=2)

network.summary()
print('loss, acc=', network.evaluate(test_images, test_labels))


# --模型的應用--


import matplotlib.pyplot as plt
import numpy as np

index = np.random.randint(0, x_test)  # random pick a test-image

plt.imshow(test_images.reshape(x_test, h, w)[index], cmap=plt.cm.binary)
plt.show()

res = network.predict(test_images)[index]
# 輸出的是softmax()的值,其中最大值對應的索引即爲預測的數字
print('The digit is %d' % np.where(res == np.max(res))[0][0])

不同的是,我們可以引入CNN來對圖像進行處理,例如VGG-16模型:

from keras import models
from keras import Sequential
from keras.layers import Dense, Activation, Conv2D, MaxPooling2D, Flatten, Dropout
from keras.layers import Input
from keras.optimizers import SGD
import cv2
import numpy as np
import json

from keras.datasets import mnist  # 從keras中導入mnist數據集
from keras import utils  # 從keras中導入mnist數據集

resize_to = 64  # 重塑尺寸
(x_train, y_train), (x_test, y_test) = mnist.load_data()  # 下載mnist數據集

x_train = [cv2.cvtColor(cv2.resize(i, (resize_to, resize_to)), cv2.COLOR_GRAY2RGB) for i in x_train]  # 變成彩色的
x_test = [cv2.cvtColor(cv2.resize(i, (resize_to, resize_to)), cv2.COLOR_GRAY2RGB) for i in x_test]  # 變成彩色的

x_train = np.array(x_train).reshape(60000, resize_to, resize_to, 3)
x_test = np.array(x_test).reshape(10000, resize_to, resize_to, 3)

y_train, y_test = utils.to_categorical(y_train, 10), utils.to_categorical(y_test, 10)  # 獨熱編碼
x_train, x_test = x_train / 255, x_test / 255  # 歸一化處理

model = Sequential()

# BLOCK 1
model.add(Conv2D(filters=64, kernel_size=(3, 3), activation='relu', padding='same', name='block1_conv1',
                 input_shape=(resize_to, resize_to, 3)))
model.add(Conv2D(filters=64, kernel_size=(3, 3), activation='relu', padding='same', name='block1_conv2'))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2), name='block1_pool'))

# BLOCK2
model.add(Conv2D(filters=128, kernel_size=(3, 3), activation='relu', padding='same', name='block2_conv1'))
model.add(Conv2D(filters=128, kernel_size=(3, 3), activation='relu', padding='same', name='block2_conv2'))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2), name='block2_pool'))

# BLOCK3
model.add(Conv2D(filters=256, kernel_size=(3, 3), activation='relu', padding='same', name='block3_conv1'))
model.add(Conv2D(filters=256, kernel_size=(3, 3), activation='relu', padding='same', name='block3_conv2'))
model.add(Conv2D(filters=256, kernel_size=(3, 3), activation='relu', padding='same', name='block3_conv3'))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2), name='block3_pool'))

# BLOCK4
model.add(Conv2D(filters=512, kernel_size=(3, 3), activation='relu', padding='same', name='block4_conv1'))
model.add(Conv2D(filters=512, kernel_size=(3, 3), activation='relu', padding='same', name='block4_conv2'))
model.add(Conv2D(filters=512, kernel_size=(3, 3), activation='relu', padding='same', name='block4_conv3'))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2), name='block4_pool'))

# BLOCK5
model.add(Conv2D(filters=512, kernel_size=(3, 3), activation='relu', padding='same', name='block5_conv1'))
model.add(Conv2D(filters=512, kernel_size=(3, 3), activation='relu', padding='same', name='block5_conv2'))
model.add(Conv2D(filters=512, kernel_size=(3, 3), activation='relu', padding='same', name='block5_conv3'))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2), name='block5_pool'))

model.add(Flatten())
model.add(Dense(4096, activation='relu', name='fc1'))
# model.add(Dropout(0.5))
model.add(Dense(4096, activation='relu', name='fc2'))
# model.add(Dropout(0.5))
model.add(Dense(1000, activation='relu', name='fc3'))
# model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax', name='prediction'))

model.compile(optimizer=SGD(lr=0.05, decay=1e-5), loss='categorical_crossentropy', metrics=['accuracy'])
model.summary()
model.fit(x_train, y_train, batch_size=64, epochs=5, validation_split=0.16666)

score = model.evaluate(x_test, y_test)
print("loss:", score[0], "acc:", score[1])

在訓練完畢後我們還可以直接保存這個模型,調用的函數是:

  1. model.to_json():把模型的結構寫出爲一個json文件
  2. model.save_weights():把模型的參數(權重)保存爲一個h5文件
  3. model.load_from_json()model.set_weights():載入網絡結構和網絡的參數(權重)

例如,對上文中VGG-16的模型可以採用以下腳本保存腳本:

# save model
with open("./model/model_config.json", "w") as file:
    file.write(model.to_json())

model.save_weights("./model/model_weights.h5")

# model.save("./model/model_config_and_weights.h5")  # 同時保存模型和訓練參數

# 載入方式
# model = keras.models.load_model('model_config_and_weights.h5') # 同時載入
# with open("./model/model_config.json", "r") as file:
#     json_string = json.load(file)
# model = models.model_from_json(json_string)
# model_weights = models.load_model('model_weights.h5')
# model.set_weights(model_weights)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章