見出し画像

【Python/JavaScript/Tensorflow】僕も性別と年齢判断AIを学習させたい

とりあえずモデル作成(Python)🐍

上記の記事のコードをパクって参考にして学習してきます
(imdbはデータが多かったので使ってません🙇‍♂️)
具体的には
wikidbから顔画像を取得 -> 顔のトリミング -> 学習済みモデルから転移学習

モデルの出力の可視化

しっかり二つの出力がありますね!

モデル箇所のコード

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=(6060))
            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(00640480).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, 00640480);
      image = {
        pixels : rgba_to_grayscale(rgba, 480640),
        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*2006464);
      }
    });
</script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.8.0/dist/tf.min.js" onload="onLoadedTensorflow()"></script>
</html>

公開したページ

https://dolkmd.github.io/sample-pico-tensorflow/

成果


この記事が気に入ったらサポートをしてみませんか?