今回も前回に引き続き転移学習 (Transfer Learning) を応用した実践的な機械学習のプログラミングに挑戦していきます。
前回はImageNetのデータセットから画像の特徴量を抽出して、その結果から新たな学習を行い画像をクラス分けしてラベルとその確度を計算することができました。今回はさらに単純にラベルづけするのではなく、複数の画像を連続的に入力してその傾向をなめらかなグラデーションとして学習させます。そして、そのデータを元にカメラの映像を分析します。このために今回は回帰分析 (Regression) という手法を用います。
まず機械学習における回帰分析とは何かを解説した上で、転移学習を応用して特徴抽出による画像の回帰分析を行っていきます。
スライド資料
サンプルプログラム
サンプルコード
Image Feature Extractor Regression – sketch.js
let featureExtractor;
let regressor; //classifierをregressorに
let video;
let loss;
let status = ''; //現在の状態を左上に表示
let showResult = ''; //クラス分け結果を中央に表示
let slider; //スライダー
let addImageButton, trainButton, predictButton; //ボタン
function setup() {
createCanvas(windowWidth, windowHeight);
video = createCapture(VIDEO);
video.size(320, 240);
video.hide();
//特徴量抽出
featureExtractor = ml5.featureExtractor('MobileNet', modelReady);
//classificationからrecressionへ
regressor = featureExtractor.regression(video);
//スライダーを配置
slider = createSlider(0.0, 1.0, 0.5, 0.01);
slider.position(20, 40);
//ボタンを配置
addImageButton = createButton('add image');
addImageButton.position(slider.x + slider.width + 20, 40);
addImageButton.mousePressed(addImage);
trainButton = createButton('train');
trainButton.position(addImageButton.x + addImageButton.width + 5, 40);
trainButton.mousePressed(train);
predictButton = createButton('start predict');
predictButton.position(trainButton.x + trainButton.width + 5, 40);
predictButton.mousePressed(predict);
}
function draw(){
background(0);
//ビデオ映像をフルクスリーン表示
image(video, 0, 0, width, height);
//現在の状態(status)を表示
fill(255);
textSize(12);
textAlign(LEFT);
text(status, 20, 20);
//回帰分析の結果を表示
fill(255, 255, 0);
textSize(100);
textAlign(CENTER);
text(showResult, width/2, height/2);
}
//画像を追加
function addImage(){
regressor.addImage(slider.value());
}
//訓練開始
function train(){
regressor.train(function(lossValue) {
if (lossValue) {
loss = lossValue;
status = 'Loss: ' + loss;
} else {
status = 'Done Training! Final Loss: ' + loss;
}
});
}
//回帰分析開始
function predict(){
//結果が出たらgotResultsを実行
regressor.predict(gotResults);
}
//モデルの読み込み完了
function modelReady() {
status = 'MobileNet Loaded!';
}
function gotResults(err, result) {
//エラー表示
if (err) {
console.error(err);
status = err;
}
//クラス分けした結果を表示
if (result && result.value) {
showResult = result.value;
predict();
}
}
これまでやってきたように、openFrameworksで位置と運動のベクトル(glm::vec2)を操作することで、アニメーションを作成することができました。単純な動きの場合はこうした座標の足し算引き算で対応可能です。しかし、ここからさらに発展させて、よりリアルな動きを実現しようとすると、限界があります。今回は、単なる座標操作ではなく、運動の背後にある物理的な原理を理解して、その本質に迫ります。
アイザック・ニュートンは、運動の法則を基礎として構築した、力学体系を構築しました(ニュートン力学)。物体の運動や力、質量といったものは、ニュートンの運動の法則によって説明できます。
第1法則(慣性の法則): 質点は、力が作用しない限り、静止または等速直線運動する 第2法則(ニュートンの運動方程式): 質点の加速度は、そのとき質点に作用する力に比例し、質点の質量に反比例する 第3法則(作用・反作用の法則): 二つの質点の間に相互に力が働くとき、質点 2 から質点 1 に作用する力と、質点 1 から質点 2 に作用する力は、大きさが等しく、逆向きである
今回は、このニュートンの運動の法則を援用しながら、様々な動きや力についてopenFrameworksで実装しながら、動きに関する理解を深めていきます。
スライド
サンプルファイル
今回はp5.jsで生成的(Generative)な形態を生みだすにはどうすればよいのか、試行錯誤しながら実験していきます。まず初めに、コードを用いた生成的な表現の実例をいくつか紹介した後、実際にp5.jsでプログラミングしていきます。
まず始めに、完全にランダムな確率で動きまわる「ランダムウォーク」な動きをする点の動きをつくり、その軌跡を描いてみます。次にこのランダムな動きを増殖させていきます。増殖の際に今回は全てを一つのプログラムに書くのではなく、それぞれの点を細かなプログラムで実装し、その小さなプログラム達を組合せることで一つの機能を生みだすような設計にします。この小さなプログラムを「オブジェクト (Object)」と呼び、オブジェクトを構成単位にしてプログラムを作成していく手法を、オブジェクト指向プログラミング (OOP) と呼びます。このOOPの考え方は今後も重要な内容となってきますので、実例を通して確実に理解していきましょう。
スライド資料
サンプルコード
今回から、さらに実践的にml5.jsを使用した機械学習のプログラミングに挑戦してきます。
今回から数回は「転移学習 (Transfer Learning)」を活用したコンテンツの制作を行っていきます。転移学習とは、機械学習の学習手法の一つで、ある領域で学習させたモデルを別の領域に適応させる技術です。この学習方法は、教師あり学習 (Supervised Learning)、教師なし学習(Unsupervised Learning)、強化学習(Reinforcement Learning) などの従来の学習方法と違い、膨大な学習データを用意できない場合でも他の学習データを活用することが可能なため、とても実用的な技術です。
今回は、あらかじめ膨大な画像のデータセット (NetImage) で学習させたモデルの特徴のデータのみを抽出 (Feature Extraction) し、その特徴データを用いて他の画像をクラス分けしてみます。
スライド資料
サンプルプログラム
前回「データ構造」で解説したリストは、複数の値をひとつにまとめることができる機能でした。リストをplayのnote(音程)に用いることで、複数の異なる音階の音を同時に演奏することができました。つまり和音(chord)を演奏することが可能です。
Sonic Piでは、コードネームからその和音に含まれる音をリストとして生成する「chord」という関数が搭載されています。この機能を用いることで、コードネームを指定するだけで複雑なコード進行をプログラムで指定することが可能となります。
またchord関数とよく似た機能として、scale関数があります。scaleはスケール(旋法)を指定する関数です。スケール名を指定することで複数の音階から構成される音列を生成することが可能です。
スライド資料
サンプルプログラム
base = :C3
live_loop :drums do
sample :bd_haus, cutoff: 80, amp: 2.5
sleep 0.25
if one_in (4)
4.times do
sample :drum_cymbal_closed, rate: 1.2
sleep 0.25/4.0
end
else
2.times do
sample :drum_cymbal_closed, rate: 1.2
sleep 0.25/2.0
end
end
end
live_loop :random_bd do
if one_in (3)
sample :bd_haus,
rate: choose([1, 2, 4]), amp: 1.5
end
sleep 0.125
end
live_loop :harmony do
with_fx :panslicer, phase: 0.25 do
use_synth :dsaw
play chord(base, :m7, num_octaves: 4),
release: 3.0, cutoff: 95, amp: 1.5
sleep 4
end
end
live_loop :live do
with_fx :echo , decay: 0.5, phase: 0.75 do
use_synth :prophet
play scale(base, :dorian, num_octaves: 4).shuffle.take(3),
cutoff: rrand(60, 120), release: 0.5
sleep 0.125
end
end
live_loop :live2 do
with_fx :echo , decay: 0.5, phase: 0.615 do
use_synth :fm
play scale(base + 12, :dorian, num_octaves: 4).shuffle.take(2),
release: 0.07, cutoff: rrand(80, 100), pan: rrand(-1, 1)
sleep 0.125
end
end
これまでのopenFrameworksのプロジェクトは、ofApp.h と ofApp.cpp という2つのファイルに全てのプログラムを記述してきました。しかし、この方法では徐々にプロジェクトが複雑になり巨大化するうちに、扱いが困難になってきます。プログラミングをわかりやすく保つには、役割ごとに内容を分割して記述すべきです。openFrameworksの元となるプログラミング言語であるC++では「オブジェクト」という単位でプログラムを構造化していきます。このオブジェクトを基本単位にしたプログラミング手法のことを「オブジェクト指向プログラミング (Object Oriented Programing = OOP)」と呼びます。OOPはC++だけでなく、Java、Python、Ruby、C#、Objective-C、Swiftなどでも利用されていて、現在のプログラミング言語の主流となっているパラダイムです。
今回は、このOOPをopenFrameworksで実現する方法を「生成的な形態を生成する」というテーマに沿って徐々に発展させていきます。今回の内容が今後のより本格的な作品制作のための重要なテクニックとなっていきます。しっかり理解していきましょう。
スライド資料
サンプルコード
今回からいよいよ動きのある表現(= アニメーション)について扱っていきます。アニメーションを実現するには、まず時間を扱う基本構造を知る必要があります。p5.jsでは、setup(), draw() という2つのブロックにわけて、初期化と更新を行うことでアニメーションを実現しています。まず始めはこの基本構造について理解します。次に、これから動きを扱う際に、向きと大きさをもった「ベクトル」という概念を理解します。ベクトルを理解することで、位置や運動を整理して記述することが可能となります。最後に、この基本構造をベクトルを活用して簡単なアニメーションを作成します。
スライド資料
サンプルコード
今回はまず初めに前回の課題「Image Classifierで何ができるか?」の簡単な講評会を行います。
後半は、これまで行ってきた機械学習による画像のクラス分け (Image Classification) の仕組みを応用して、音声のクラス分けに挑戦します。あらかじめ学習させた単語とそのラベルからなるデータセットを用いて、「up」「down」「left」「right」などの簡単な言葉によってプログラムに指示を与えることが可能となります。
スライド資料
サンプルコード
今回も引き続きSonic Piの基本について実際に音を出して確認しながら学習していきます。今回はSonic Piの言語の構造に注目して様々な機能を紹介していきます。Sonic PiはRubyというプログラミング言語をベースにしています。ですのでRubyの様々な構造をSonic Piで使用することが可能です。今回は、プログラムを構成する3つの大きな構造、順次処理 (Sequence)、繰り返し(Iteration)、条件分岐(Selection)をSonic Piで応用して演奏してみます。また後半はリストというデータ構造について紹介します。最後はここまでの知識を総動員して、Steve Reichの “Piano Phase” という曲をSonic Piで再現してみます。
スライド資料
前回はopenFrameworksで、位置と速度という2つのベクトル (ofVec2f) を使用して図形1つをアニメーションさせるところまで到達できました。今回はこのプログラムを応用して複数の図形を同時に動かす方法について解説していきます。複数の図形を動かすには位置と速度という2つのベクトルを個別の変数にではなくまとめて取り扱うデータの格納方法が必要となります。こうした場合には配列(Array)を使用すると便利です。それぞれのベクトルを配列にまとめることで、大量の図形のアニメーションをシンプルに記述することが可能です。さらに、あらかじめ物体の最大数が決まっていない際には、可変長配列 (std::vector) を使用するという方法もあります。この方法もあわせて解説していきます。
スライド
サンプルファイル