還記得上次的口罩辨識嗎?在這次的教程中,我們會教你如何運用威盛Pixetto製作「判別剪刀石頭布」教程。透過人工智慧與威盛Pixetto「神經網路辨識」功能,撰寫Python程式碼並且訓練CNN模型,使威盛 Pixetto 正確偵測出剪刀、石頭、布。
這次的教程是運用Python程式撰寫,製作客製化的「神經網路辨識」,你也可以依據自己的需求撰寫生活上或工作所需的應用。那我們就開始吧!
步驟:
- 開啟機器學習加速器平台
- Python 程式碼撰寫
- 連結威盛 Pixetto 至電腦
- 上傳Tflite至威盛 Pixetto
步驟 1
首先,進入機器學習加速器平台,登入帳號,並點擊「 Python 」 。


在右上角點選「New」,新增 「Python 3」 檔案。
當開啟完成後,我們就可以進入 Python程式撰寫了!


步驟 2
接下來,我們要進行最辛苦的一部分「撰寫Python程式碼」,共可分為以下步驟:
- 匯入套件
- 匯入資料
- 資料視覺化
- 資料處理
- 模型訓練
- 輸出模型
1. 匯入套件
我們這次會使用到的套件包含:
- tensorflow==2.2.0
- tensorflow-addons==0.11.2
- tensorflow_datasets==4.0.1+nightly
- matplotlib==3.1.2
- numpy==1.18.5
import tensorflow as tf import tensorflow_datasets as tfds import tensorflow_addons as tfa %matplotlib inline import matplotlib.pyplot as plt import numpy as np import gc gc.collect()

2. 匯入資料
當匯入完套件後,我們將使用 tensorflow_datasets 裡的 rock_paper_scissors 資料集。利用tfds.load()匯入,分別將資料集和資料集資訊存入dataset、dataset_info。
dataset, dataset_info = tfds.load(
name='rock_paper_scissors',
data_dir='tmp',
with_info=True,
as_supervised=True,
)
dataset_train = dataset['train']
dataset_test = dataset['test']

我們可以檢視rock_paper_scissors 資料集的詳細資料:

並將rock_paper_scissors 資料集的重要資訊把它print出來:
可以看到資料集是由 (300, 300, 3) 的圖片檔組成。訓練資料集有 2,520 張圖片,測試資料集有 372 張圖片。總共有3種類別:石頭、布、剪刀。
train_size = dataset_info.splits['train'].num_examples
test_size = dataset_info.splits['test'].num_examples
dataset_classes = dataset_info.features['label'].num_classes
print('dataset name:', dataset_info.name)
print('train dataset:', dataset_train)
print('test dataset:', dataset_test)
print('train dataset size:', train_size)
print('test dataset size:', test_size)
print('number of classes in train and test dataset:', dataset_classes, dataset_info.features['label'].names)
print('shape of images in train and test dataset:', dataset_info.features['image'].shape)

3. 資料視覺化
簡單運用plot_image()觀察其中一張圖片,你也可以嘗試看看改變plot_image()中n的數字,改變觀看的圖片。
def plot_image(n=1):
for image, label in dataset_train.take(n):
image = image.numpy()
label = label.numpy()
image_label = dataset_info.features['label'].int2str(label)
plt.imshow(image)
plt.title(image_label)
plt.colorbar()
plot_image(5)
gc.collect()

一張一張看太慢了嗎?不如我們一次看5張!
這也會是我們之後再做資料處理後,會使用的視覺化方式。
def plot_dataset(dataset, num=5):
plt.figure(figsize=(15, 15))
plot_index = 0
for image, label in dataset.take(num):
image = image.numpy()
label = label.numpy()
image_label = dataset_info.features['label'].int2str(label)
plot_index+=1
plt.subplot(3, 5, plot_index)
plt.title(image_label)
plt.imshow(image)
plot_dataset(dataset_train, 15)
gc.collect()

你也可以依據喜好選擇顯示5、10、15張。

4. 資料處理
因為訓練成本的關係,我們必須透過資料處理改變圖片的大小,這裡我們將圖片縮小至(64, 64, 3)。同時設置批次大小為64。
batch_size = 64
image_size = 64
def format_image(image, label):
image = tf.cast(image, tf.float32)
image = tf.image.resize(image, (image_size, image_size))
image /= 255
return image, label
dataset_train = dataset_train.map(format_image)
dataset_test = dataset_test.map(format_image)
# Explore preprocessed training dataset images.
plot_dataset(dataset_train)


接下來,進入最重要的圖片預處理;首先要製作圖片處理函數,分為以下部分:
- 圖片轉置:將圖片隨機轉置
- 圖片翻轉:將圖片隨機上下、左右翻轉
- 圖片旋轉:隨機旋轉圖片0-360度
- 圖片顏色調整:隨機改變圖片飽和度、亮度、對比度、色相
- 圖片顏色反轉:將圖片顏色隨機反轉,為了增加機器學習模型的泛化能力,我們增加一點noise(20%),避免過擬合
- 圖片縮放:將圖片隨機縮放
def image_transpose(image):
rand = tf.random.uniform(shape=[], minval=0.0, maxval=1.0, dtype=tf.float32)
image = tf.cond(rand < 0.5,
lambda: tf.identity(image),
lambda: tf.image.transpose(image))
return image
def image_flip(image: tf.Tensor) -> tf.Tensor:
image = tf.image.random_flip_left_right(image)
image = tf.image.random_flip_up_down(image)
return image
def image_rotate(image):
image = tf.image.rot90(image, tf.random.uniform(shape=[], minval=0, maxval=4, dtype=tf.int32))
rand = tf.random.uniform(shape=[], minval=0.0, maxval=1.0, dtype=tf.float32)
def random_rotate(image):
image = tfa.image.rotate(
image, tf.random.uniform(shape=[], minval=0 * np.pi / 180, maxval=360 * np.pi / 180, dtype=tf.float32))
return image
image = tf.cond(rand < 0.5,
lambda: tf.identity(image),
lambda: random_rotate(image))
return image
def image_color(image: tf.Tensor) -> tf.Tensor:
image = tf.image.random_saturation(image, lower=0.5, upper=3)
image = tf.image.random_brightness(image, max_delta=0.2)
image = tf.image.random_contrast(image, lower=0.8, upper=1)
image = tf.image.random_hue(image, max_delta=0.03)
image = tf.clip_by_value(image, clip_value_min=0, clip_value_max=1)
return image
def image_inversion(image: tf.Tensor) -> tf.Tensor:
rand = tf.random.uniform(shape=[], minval=0.0, maxval=1.0, dtype=tf.float32)
image = tf.cond(rand < 0.8,
lambda: tf.identity(image),
lambda: tf.math.add(tf.math.multiply(image, -1), 1))
return image
def image_zoom(image: tf.Tensor, min_zoom=0.8, max_zoom=1.0) -> tf.Tensor:
image_width, image_height, image_colors = image.shape
crop_size = (image_width, image_height)
# Generate crop settings, ranging from a 1% to 20% crop.
scales = list(np.arange(min_zoom, max_zoom, 0.01))
boxes = np.zeros((len(scales), 4))
for i, scale in enumerate(scales):
x1 = y1 = 0.5 - (0.5 * scale)
x2 = y2 = 0.5 + (0.5 * scale)
boxes[i] = [x1, y1, x2, y2]
def random_crop(img):
# Create different crops for an image
crops = tf.image.crop_and_resize(
[img],
boxes=boxes,
box_indices=np.zeros(len(scales)),
crop_size=crop_size
)
# Return a random crop
return crops[tf.random.uniform(shape=[], minval=0, maxval=len(scales), dtype=tf.int32)]
choice = tf.random.uniform(shape=[], minval=0., maxval=1., dtype=tf.float32)
# Only apply cropping 50% of the time
return tf.cond(choice < 0.5, lambda: image, lambda: random_crop(image))


將所有的函數統整後,利用map()就可以對整個資料集進行處理了。
def augment_data(image, label):
image = image_flip(image)
image = image_color(image)
image = image_zoom(image)
image = image_transpose(image)
image = image_inversion(image)
image = image_rotate(image)
return image, label
dataset_train_augmented = dataset_train.map(augment_data)
plot_dataset(dataset_train_augmented)
gc.collect()



5. 模型訓練
在正式訓練模型之前,我們必須將資料進行打亂,增加樣本的隨機性。先前批次大小設置為64,buffer_size設置為推薦的tf.data.experimental.AUTOTUNE。
dataset_train_batches = dataset_train_augmented.shuffle(
buffer_size=train_size).batch(batch_size=batch_size).prefetch(buffer_size=tf.data.experimental.AUTOTUNE)
dataset_test_batches = dataset_test.batch(batch_size)
print(dataset_train_batches)
print(dataset_test_batches)

最後,我們進行CNN模型建立。
TensorFlow的模型建立主要分為兩種,一種是直接在Sequential API中設定不同的卷積層,另一種是利用add()加入不同的卷積層。這裡我們採用第一種。
以下列出不同的卷積層:
- tf.keras.layers.Convolution2D()
- tf.keras.layers.MaxPooling2D()
- tf.keras.layers.Dense()
- tf.keras.layers.Flatten()
- tf.keras.layers.Dropout()
model = tf.keras.Sequential([
tf.keras.layers.Convolution2D(input_shape=(image_size, image_size, 3), filters=64, kernel_size=3, activation='relu'),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Convolution2D(input_shape=(image_size, image_size, 3), filters=64, kernel_size=3, activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2)),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Convolution2D(input_shape=(image_size, image_size, 3), filters=128, kernel_size=3, activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2)),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Convolution2D(input_shape=(image_size, image_size, 3), filters=128, kernel_size=3, activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2)),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Flatten(),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(units=512, activation=tf.keras.activations.relu),
tf.keras.layers.Dense(units=dataset_classes, activation=tf.keras.activations.softmax)
])

我們可以利用model.summary查看完整的CNN模型。除了呈現不同層神經網路以外,還可以查看總參數為2,621,507。
model.summary()

我們採用的優化器是RMSprop,設置完成後,將模型加入compiler。
這裡我們利用訓練資料集大小除去批次大小來獲得我們的訓練回合,回合控制我們模型訓練速度。除此之外,我們也計算出驗證訓練的測試回合。
值得注意的是,我們有設置early stopping的功能,在訓練超過5回val_accuracy沒有提升,模型就會停止訓練。
rmsprop_optimizer = tf.keras.optimizers.RMSprop(learning_rate=0.001)
model.compile(optimizer=rmsprop_optimizer,
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
steps_per_epoch = train_size // batch_size
validation_steps = test_size // batch_size
print('steps_per_epoch:', steps_per_epoch)
print('validation_steps:', validation_steps)
early_stopping = tf.keras.callbacks.EarlyStopping(patience=5, monitor='val_accuracy')

這裡我們開始訓練過程,經過了15回合的訓練可以發現:
- loss 降至0.2103
- accuray提升至0.9287
- val_loss降至0.1569
- val_accuracy提升至0.9500
training_history = model.fit(x=dataset_train_batches.repeat(),
validation_data=dataset_test_batches.repeat(),
epochs=15,
steps_per_epoch=steps_per_epoch,
validation_steps=validation_steps,
callbacks=[early_stopping],
verbose=1)
大家可能會覺得很意外,訓練accuracy回合14到15下降了一個百分點,這樣不是模型預測變差了嗎?但值得注意的是val_accuracy卻因此獲得顯著提升(5個百分點)。

這部分也可以從視覺化圖形觀察出相同的概念。左邊的圖表顯示在回合15時訓練loss達到最低點,同時和測試loss重合。右邊的圖表accuracy也有異曲同工之妙的概念。
def plot_training_history(training_history):
loss = training_history.history['loss']
val_loss = training_history.history['val_loss']
accuracy = training_history.history['accuracy']
val_accuracy = training_history.history['val_accuracy']
plt.figure(figsize=(18, 6))
plt.subplot(1, 2, 1)
plt.title('Training and Test Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.plot(loss, label='Training set')
plt.plot(val_loss, label='Test set', linestyle='--')
plt.legend()
plt.grid(linestyle='--', linewidth=1, alpha=0.5)
plt.subplot(1, 2, 2)
plt.title('Training and Test Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.plot(accuracy, label='Training set')
plt.plot(val_accuracy, label='Test set', linestyle='--')
plt.legend()
plt.grid(linestyle='--', linewidth=1, alpha=0.5)
plt.show()
plot_training_history(training_history)
gc.collect()


train_loss, train_accuracy = model.evaluate(dataset_train.batch(batch_size).take(train_size))
test_loss, test_accuracy = model.evaluate(dataset_test.batch(batch_size).take(test_size))
print('Training Loss: ', train_loss)
print('Training Accuracy: ', train_accuracy)
print('Test Loss: ', test_loss)
print('Test Accuracy: ', test_accuracy)

6. 輸出模型
完成訓練後我們就可以存取CNN模型了!這裡存成能與威盛 Pixetto 相容的Tflite。大家可以看出這裡分別存取了兩個模型,以下簡單說明差異:
- model.tflite是最原始的model,因為有10.5MB所以我們要把它縮小
- quant_model.tflite是縮小版的model.tflite,運用Quantization演算法來縮小資料格式,在可接受範圍內,達到儲存記憶體優化效果,這裡檔案大小轉為2.63MB
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
with open('model.tflite', 'wb') as f:
f.write(tflite_model)
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_quant_model = converter.convert()
with open('quant_model.tflite', 'wb') as f:
f.write(tflite_quant_model)


這樣複雜的模型建立就完成囉!
趕緊將quant_model.tflite下載至電腦中,準備上傳至威盛 Pixetto 。
步驟 3
接下來我們要將威盛 Pixetto 連結至電腦。
使用Micro USB 2.0傳輸線將威盛Pixetto連至個人電腦。當看到綠色、藍色、紅色LED燈點亮時,代表威盛Pixetto已成功連結。


別忘記將鏡頭蓋子取下!
步驟 4
上傳Tflite至威盛 Pixetto 的步驟和先前幾次的教程大同小異,只不過這次我們將會使用「神經網路辨識」功能。
首先,打開威盛 Utility,在功能區選擇「神經網路辨識」。


接下來,選擇下方「模型路徑」,上傳quant_model.tflite。完成後記得點選確認鍵。


上傳完成後,我們要改變標籤名稱。
點選左上角「工具」中的「標籤編輯」,分別在索引0、1、2,輸入石頭、布、剪刀。


Reference:
Rock Paper Scissors (MobilenetV2)
實際操作:
當完成以上步驟,就是開始玩「判別剪刀石頭布辨識」的時候!
將威盛Pixetto對準自己的手,開始出拳吧!你也可以將這項「判別剪刀石頭布」進階應用在自己的生活之中!



恭喜你完成了!
祝你玩得愉快,別忘了分享自己的創作至社群上並標註 #VIAPixetto!
你也可以關注威盛Pixetto粉絲專頁獲得更多第一手資訊!