環境:python3.6版本 + TensorFlow 1.6版本
import os
from PIL import Image
import numpy as np
import tensorflow as tf
data_dir = r'E://data/data' # 數據文件夾
train = True # 訓練還是測試
model_path = "model/image_model" # 模型文件路徑
# 從文件夾讀取圖片和標籤到numpy數組中
# 標籤信息在文件名中,例如1_40.jpg表示該圖片的標籤爲1
def read_data(data_dir):
datas = []
labels = []
fpaths = []
for fname in os.listdir(data_dir):
fpath = os.path.join(data_dir, fname)
data = np.array(Image.open(fpath))/255.0 # pillow讀取的圖像像素值在0-255之間,需要歸一化
label = int(fname.split("_")[0])
fpaths.append(fpath) # 在讀取圖像數據、Label信息的同時,記錄圖像的路徑
datas.append(data)
labels.append(label)
datas = np.array(datas)
labels = np.array(labels)
print("shape of datas: {}\tshape of labels: {}".format(datas.shape, labels.shape))
return fpaths, datas, labels
fpaths, datas, labels = read_data(data_dir)
# 'shape of datas: (150, 32, 32, 3) shape of labels: (150,)'
# 計算有多少類圖片
num_classes = len(set(labels))
# Placeholder:TensorFlow中的佔位符,用於傳入外部數據。
# dtype:數據類型;shape:數據的維度。默認爲None,表示沒有限制;name:名稱;返回類型:Tensor
# Tensorflow 1.x 版本提供placeholder,而 2.0版本暫時沒有這個模塊。
datas_placeholder = tf.placeholder(tf.float32, [None, 32, 32, 3])
labels_placeholder = tf.placeholder(tf.int32, [None])
dropout_placeholdr = tf.placeholder(tf.float32) # 存放DropOut參數的容器,訓練時爲0.25,測試時爲0
tf.layers.conv2d (inputs, # 必需,即需要進行操作的輸入數據。
filters, # 必需,是一個數字,代表了輸出通道的個數,即 output_channels
kernel_size, # 必需,卷積核大小,必須是一個數字(高和寬都是此數字)或者長度爲 2 的列表(分別代表高、寬)。
strides=(1, 1), # 可選,默認爲 (1, 1),卷積步長,必須是一個數字(高和寬都是此數字)或者長度爲 2 的列表(分別代表高、寬)
padding='valid', # 可選,默認爲 valid,padding 的模式,有 valid 和 same 兩種,大小寫不區分。
data_format='channels_last',
# 可選,默認 channels_last,分爲 channels_last 和 channels_first 兩種模式,代表了輸入數據的維度類型,
# 如果是 channels_last, 那麼輸入數據的 shape 爲 (batch, height, width, channels),
# 如果是 channels_first,那麼輸入數據的 shape 爲 (batch, channels, height, width)。
dilation_rate=(1, 1), # 可選,默認爲 (1, 1),卷積的擴張率,如當擴張率爲2時,卷積核內部就會有邊距,3×3 的卷積核就會變成 5×5。
activation=None, # 可選,默認爲 None,如果爲 None 則是線性激活。
use_bias=True, # 可選,默認爲 True,是否使用偏置。
kernel_initializer=None, # 可選,默認爲 None,即權重的初始化方法,如果爲 None,則使用默認的 Xavier 初始化方法。
bias_initializer=None, # 可選,默認爲零值初始化,即偏置的初始化方法。
kernel_regularizer=None, # 可選,默認爲 None,施加在權重上的正則項。
bias_regularizer=None, # 可選,默認爲 None,施加在偏置上的正則項。
activity_regularizer=None, # 可選,默認爲 None,施加在輸出上的正則項。
kernel_constraint=None, # 可選,默認爲 None,施加在權重上的約束項。
bias_constraint=None, # 可選,默認爲 None,施加在偏置上的約束項。
trainable=True, # 可選,默認爲 True,布爾類型,如果爲 True,則將變量添加到 GraphKeys.TRAINABLE_VARIABLES 中。
name=None, # 可選,默認爲 None,卷積層的名稱。
reuse=None # 可選,默認爲 None,布爾類型,如果爲 True,那麼如果 name 相同時,會重複利用。
)
# 定義卷積層, 20個卷積核, 卷積核大小爲5,用Relu激活
conv0 = tf.layers.conv2d(inputs=datas_placeholder, filters=20, kernel_size=5, activation=tf.nn.relu)
# 定義max-pooling層,pooling窗口爲2x2,步長爲2x2
pool0 = tf.layers.max_pooling2d(conv0, [2, 2], [2, 2])
# 定義卷積層, 40個卷積核, 卷積核大小爲4,用Relu激活
conv1 = tf.layers.conv2d(pool0, 40, 4, activation=tf.nn.relu)
# 定義max-pooling層,pooling窗口爲2x2,步長爲2x2
pool1 = tf.layers.max_pooling2d(conv1, [2, 2], [2, 2])
# 將3維特徵轉換爲1維向量
flatten = tf.layers.flatten(pool1)
# 全連接層,轉換爲長度爲100的特徵向量
fc = tf.layers.dense(flatten, 400, activation=tf.nn.relu)
# 加上DropOut,防止過擬合
dropout_fc = tf.layers.dropout(fc, dropout_placeholdr)
# 未激活的輸出層
logits = tf.layers.dense(dropout_fc, num_classes)
predicted_labels = tf.arg_max(logits, 1)
# 利用交叉熵定義損失
losses = tf.nn.softmax_cross_entropy_with_logits(
labels=tf.one_hot(labels_placeholder, num_classes),
logits=logits
)
# 平均損失
mean_loss = tf.reduce_mean(losses)
# 定義優化器,指定要優化的損失函數
optimizer = tf.train.AdamOptimizer(learning_rate=1e-2).minimize(losses)
# 用於保存和載入模型
saver = tf.train.Saver()
with tf.Session() as sess:
if train:
print("訓練模式")
# 如果是訓練,初始化參數
sess.run(tf.global_variables_initializer())
# 定義輸入和Label以填充容器,訓練時dropout爲0.25
train_feed_dict = {
datas_placeholder: datas,
labels_placeholder: labels,
dropout_placeholdr: 0.25
}
for step in range(150):
_, mean_loss_val = sess.run([optimizer, mean_loss], feed_dict=train_feed_dict)
if step % 10 == 0:
print("step = {}\tmean loss = {}".format(step, mean_loss_val))
saver.save(sess, model_path)
print("訓練結束,保存模型到{}".format(model_path))
else:
print("測試模式")
# 如果是測試,載入參數
saver.restore(sess, model_path)
print("從{}載入模型".format(model_path))
# label和名稱的對照關係
label_name_dict = {
0: "飛機",
1: "汽車",
2: "鳥"
}
# 定義輸入和Label以填充容器,測試時dropout爲0
test_feed_dict = {
datas_placeholder: datas,
labels_placeholder: labels,
dropout_placeholdr: 0
}
predicted_labels_val = sess.run(predicted_labels, feed_dict=test_feed_dict)
# 真實label與模型預測label
for fpath, real_label, predicted_label in zip(fpaths, labels, predicted_labels_val):
# 將label id轉換爲label名
real_label_name = label_name_dict[real_label]
predicted_label_name = label_name_dict[predicted_label]
print("{}\t{} => {}".format(fpath, real_label_name, predicted_label_name))