![見出し画像](https://assets.st-note.com/production/uploads/images/76663712/rectangle_large_type_2_9b4e6cd7630b73279f9dba7f267dcc07.png?width=1200)
【Python/JavaScript/Tensorflow】僕も性別と年齢判断AIを学習させたい
とりあえずモデル作成(Python)🐍
上記の記事のコードをパクって参考にして学習してきます
(imdbはデータが多かったので使ってません🙇♂️)
具体的には
wikidbから顔画像を取得 -> 顔のトリミング -> 学習済みモデルから転移学習
モデルの出力の可視化
しっかり二つの出力がありますね!
![](https://assets.st-note.com/img/1650212880701-bNukwLvwX3.png?width=1200)
モデル箇所のコード
EfficientNetB3から転移学習をやってみようと思います。
簡単に言うと、モデルの最後に二つの出力を足しただけです🙇♂️
# 学習済みモデル
base_model = EfficientNetB3(
weights = "imagenet",
include_top=False,
input_shape=x.shape[1:],
)
base_model.trainable = False
# 性別用の層を追加
gender_model = base_model.output
gender_model = GlobalAveragePooling2D()(gender_model)
gender_model = Dense(1024,activation='relu')(gender_model)
gender_model = Dense(gender_range, activation="softmax", name="gender")(gender_model)
# 年齢の層を追加
age_model = base_model.output
age_model = GlobalAveragePooling2D()(age_model)
age_model = Dense(1024,activation='relu')(age_model)
age_model = Dense(age_range, activation="softmax", name="age")(age_model)
# 結合
model = Model(
inputs=base_model.input,
outputs=[
gender_model,
age_model,
]
)
model.compile(
optimizer=optimizer,
loss={
"age" : loss,
"gender" : loss,
},
metrics=["accuracy"],
)
全体のソースコード
import os
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
import cv2
from sklearn.model_selection import train_test_split
from tensorflow import keras
from tensorflow.keras.applications.resnet50 import ResNet50
from tensorflow.keras.applications.vgg16 import VGG16
from tensorflow.keras.applications.efficientnet import EfficientNetB3
from tensorflow.keras.preprocessing.image import img_to_array, load_img
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dense, Dropout, Flatten, GlobalAveragePooling2D
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau
from src.util import loadCacheFunction, loadCacheVariable
from src.face_gender_age import mat
sys.modules['mat'] = mat
cascade = cv2 .CascadeClassifier("./definition/haarcascade_frontalface_alt.xml")
eye_cascade = cv2.CascadeClassifier("./definition/haarcascade_eye.xml")
def train(args):
with tf.device("/gpu:0"):
model, history = train()
model.save('model.h5')
def train():
gender_range = 2
age_range = 100
batch_size = 128
epochs = 30
image_size = 64
loss = "categorical_crossentropy"
optimizer = Adam(learning_rate=0.001)
def build_model(
x,
optimizer=Adam(learning_rate=0.001),
loss="categorical_crossentropy",
) -> Model:
base_model = EfficientNetB3(
weights = "imagenet",
include_top=False,
input_shape=x.shape[1:],
)
base_model.trainable = False
gender_model = base_model.output
gender_model = GlobalAveragePooling2D()(gender_model)
gender_model = Dense(1024,activation='relu')(gender_model)
gender_model = Dense(gender_range, activation="softmax", name="gender")(gender_model)
age_model = base_model.output
age_model = GlobalAveragePooling2D()(age_model)
age_model = Dense(1024,activation='relu')(age_model)
age_model = Dense(age_range, activation="softmax", name="age")(age_model)
model = Model(
inputs=base_model.input,
outputs=[
gender_model,
age_model,
]
)
for i in model.layers:
print(i.name, i.trainable)
model.compile(
optimizer=optimizer,
loss={
"age" : loss,
"gender" : loss,
},
metrics=["accuracy"],
)
return model
def createVariables():
x, age, gender = [], [], []
files = loadCacheFunction('cache/face_file.b', mat.getFaceFilePaths)
X_train, sX_train = loadCacheVariable('cache/v_X_train')
X_test, sX_test = loadCacheVariable('cache/v_X_test')
age_train, sage_train = loadCacheVariable('cache/v_age_train')
age_test, sage_test = loadCacheVariable('cache/v_age_test')
gender_train, sgender_train = loadCacheVariable('cache/v_gender_train')
gender_test, sgender_test = loadCacheVariable('cache/v_gender_test')
if X_train is not None:
return np.array(X_train), np.array(X_test), np.array(age_train), np.array(age_test), np.array(gender_train), np.array(gender_test)
for i, file in enumerate(files):
if int(file.age) >= age_range:
continue
if i % 100 == 0:
print(i, "...")
img = cv2.imread(file.path)
face_list = cascade.detectMultiScale(img, scaleFactor=1.1, minNeighbors=3, minSize=(60, 60))
for _x, _y, weight, height in face_list:
try:
img = img[_y:_y+height, _x:_x+weight]
eyes = eye_cascade.detectMultiScale(img)
if len(eyes) != 2:
continue
img = cv2.resize(img, (image_size, image_size))
cv2.imwrite('./gray/%s_%s_%s.jpg' % (i, file.gender, file.age), img)
img = img[...,::-1]
arr = img_to_array(img)
x.append(arr)
age.append(int(file.age))
gender.append(0 if file.gender == 'male' else 1)
except:
print('err', file.path)
age, gender = to_categorical(age, age_range), to_categorical(gender, gender_range)
X_train, X_test, age_train, age_test, gender_train, gender_test = train_test_split(x, age, gender, test_size=0.25, random_state=111)
X_train = sX_train(X_train)
X_test = sX_test(X_test)
age_train = sage_train(age_train)
age_test = sage_test(age_test)
gender_train = sgender_train(gender_train)
gender_test = sgender_test(gender_test)
return np.array(X_train), np.array(X_test), np.array(age_train), np.array(age_test), np.array(gender_train), np.array(gender_test)
# X_train, X_test, age_train, age_test, gender_train, gender_test = loadCacheVariable('cache/variables.b', createVariables)
X_train, X_test, age_train, age_test, gender_train, gender_test = createVariables()
print(X_train.shape, X_test.shape, age_train.shape, age_test.shape, gender_train.shape, gender_test.shape)
model = build_model(X_train, optimizer=optimizer, loss=loss)
model.summary()
early_stopping = EarlyStopping(
monitor='val_loss',
min_delta=0.0,
patience=1,
)
reduce_lr = ReduceLROnPlateau(
monitor='val_loss',
factor=0.5,
patience=2,
min_lr=0.0001
)
history = model.fit(
X_train, {
"age" : age_train,
"gender" : gender_train,
},
batch_size=batch_size,
epochs=epochs,
validation_data = (X_test, {
"age" : age_test,
"gender" : gender_test,
}),
# callbacks=[early_stopping, reduce_lr],
)
model.evaluate(X_test, {
"age" : age_test,
"gender" : gender_test,
})
plt.plot(history.history['gender_accuracy'])
plt.plot(history.history['age_accuracy'])
plt.plot(history.history['val_gender_accuracy'])
plt.plot(history.history['val_age_accuracy'])
plt.title('Model accuracy')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['gender_accuracy', 'age_accuracy', 'val_gender_accuracy', 'val_age_accuracy'], loc='upper left')
plt.show()
# Plot training & validation loss values
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Train', 'Test'], loc='upper left')
plt.show()
return model, history
成果
GitHub Pagesでページとして公開(JavaScript)🟡
PCでお願いします🙇♂️
pico.jsを使ってシンプルかつ軽量に顔検出しました。
顔を切り取りTensorflow.jsにて作成したモデルで年齢・性別を判定してみました。
全体のソースコード
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="./pico/camvas.js"></script>
<script src="./pico/pico.js"></script>
<script src="./pico/lploc.js"></script>
</head>
<body>
<div id="load_tensorflow">downloading Tensorflow.js...</div>
<div id="load_pico">downloading pico.js...</div>
<canvas width=640 height=480></canvas>
<canvas width=64 height=64 id="target" style="position: fixed; top: -1000px; left: 1000px;"></canvas>
<p>Age: <span id="age"></span></p>
<p>Gender: <span id="gender"></span></p>
</div>
</body>
<script>
let do_puploc = () => [-1.0, -1.0];
let facefinder_classify_region = () => -1.0;
const update_memory = pico.instantiate_detection_memory(5);
const faceCanvasElem = document.getElementById('target');
const ageElem = document.getElementById('age');
const genderElem = document.getElementById('gender');
const loadTensorflowElem = document.getElementById('load_tensorflow');
const loadPicoElem = document.getElementById('load_pico');
// Tesnsorflowが読み込まれた時に実行
const onLoadedTensorflow = () => {
const fps = 2;
const kerasModel = `model/model.json`;
const process = (model) => {
loadTensorflowElem.textContent = '';
let inputImage = tf.browser.fromPixels(faceCanvasElem, 3).toFloat();
inputImage = tf.expandDims(inputImage, 0);
const result = model.predict(inputImage);
result[0].data()
.then(arr => {
tf.argMax(arr).data().then((r) => {
genderElem.textContent = r[0] === 0 ? 'Man' : 'Woman';
});
});
result[1].data()
.then((arr) => tf.argMax(arr).data().then((r) => {
ageElem.textContent = JSON.stringify(r[0]);
}));
};
tf.loadGraphModel(kerasModel)
.then(model => {
setInterval(() => process(model), 1000 / fps);
})
.catch(console.log);
};
const loadCascade = () => {
const cascadeurl = 'https://raw.githubusercontent.com/nenadmarkus/pico/c2e81f9d23cc11d1a612fd21e4f9de0921a5d0d9/rnt/cascades/facefinder';
return fetch(cascadeurl).then(function(response) {
response.arrayBuffer().then(function(buffer) {
facefinder_classify_region = pico.unpack_cascade(new Int8Array(buffer));
});
})
};
const loadPuploc = () => {
const puplocurl = 'https://drone.nenadmarkus.com/data/blog-stuff/puploc.bin'
return fetch(puplocurl).then(function(response) {
response.arrayBuffer().then(function(buffer) {
do_puploc = lploc.unpack_localizer(new Int8Array(buffer));
});
});
};
(async () => {
await loadCascade();
await loadPuploc();
loadPicoElem.textContent = '';
})();
const ctx = document.getElementsByTagName('canvas')[0].getContext('2d');
const tCtx = faceCanvasElem.getContext('2d');
new camvas(ctx, (video, dt) => {
const rgba = ctx.getImageData(0, 0, 640, 480).data;
const rgba_to_grayscale = (rgba, nrows, ncols) => {
let gray = new Uint8Array(nrows*ncols);
for(let r = 0; r < nrows; ++r)
for(let c = 0; c < ncols; ++c)
gray[r*ncols + c] = (2*rgba[r*4*ncols+4*c+0]+7*rgba[r*4*ncols+4*c+1]+1*rgba[r*4*ncols+4*c+2])/10;
return gray;
};
ctx.drawImage(video, 0, 0, 640, 480);
image = {
pixels : rgba_to_grayscale(rgba, 480, 640),
nrows : 480,
ncols : 640,
ldim : 640,
}
params = {
shiftfactor : 0.1,
minsize : 100,
maxsize : 1000,
scalefactor : 1.1,
}
dets = pico.run_cascade(image, facefinder_classify_region, params);
dets = update_memory(dets);
dets = pico.cluster_detections(dets, 0.2);
if (dets.length > 0) {
const det = dets[0];
const x = det[1];
const y = det[0];
const diameter = det[2];
ctx.beginPath();
ctx.rect(x-diameter/2, y-diameter/2, diameter+5, diameter+5);
ctx.lineWidth = 3;
ctx.strokeStyle = 'red';
ctx.stroke();
tCtx.drawImage(video, x*1.5, y/1.5, diameter*2, diameter*2, 0, 0, 64, 64);
}
});
</script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.8.0/dist/tf.min.js" onload="onLoadedTensorflow()"></script>
</html>