アプリとサービスのすすめ

アプリやIT系のサービスを中心に書いていきます。たまに副業やビジネス関係の情報なども気ままにつづります

Terraform × ApexでLambdaとCloudwatchログのスケジュールを管理してみた【機械学習・AWSインフラ】

Terraformが人気のインフラ管理ツールということで是非習得したかったので、TerraformとそのラッパーのApexを使って、LambdaとCloudwatchログのスケジュール管理をしてみた。

Apexを使えばLambda管理がラクにできるので、運用をイメージしてTerraformでlambdaを含めたAWSインフラを管理するまでのログをまとめてく。

目次
1.Terraformで管理するインフラの全体像
2.コード&ファイル
3.実行コマンド
4.まとめ

1.Terraformで管理するインフラの全体像

AWSインフラリソースの全体像


TerraformとApexで管理するAWSインフラの全体像を表すと下の図の通り。
f:id:trafalbad:20190518092716j:plain


Apexを使うと嬉しいことは2つ。

・Lambdaの管理がラクになる
terraformでLambdaを管理するには、

AWSのオブジェクト管理(Lambda Function、IAM、triggerのためのCloudWatch Eventsなど)

②Lambda関数のソースコード

③Lambda関数のソースコードに依存するライブラリたち

ソースコードとライブラリを固めたzip

が必要だけど、Apexなら②~④をまとめてやってくれる。


・Lambda用のコードをS3バケットではなく、直接Lambdaにアップロードできる
Apexが上の②〜④をやってくれるおかげで、zipを入れるS3バケットの管理や、terraformでのzipファイルの管理が必要なくなる。




運用イメージ


運用イメージは
・CloudWatchログのログをLambdaで「他のリソース」に送りつけること
を想定してる。


例1.Datadog docsにCloudWatchログのログをLambdaで送りつける
f:id:trafalbad:20190518083246p:plain



例2.ElasticseachにCloudWatchログのログをLambdaで送りつける
f:id:trafalbad:20190518083306j:plain


2.コード

ディレクトリ構成

./project.json # プロジェクト全体の定義
./functions/datadog_logs/function.json # 関数の定義
./functions/datadog_logs/datadog_logs.py # Lambdaで実行するコード(今回はPythonのサンプル)
./infrastructure/prod/main.tf # Terraformで管理するAWSリソースの定義</div>

functions配下の「datadog_logs」フォルダはLambda関数名に合わせる。
またinfrastructure配下の「prod」フォルダは環境の意味(prod=env)で、環境ごとにLambdaを作り分けられるApexの機能。

f:id:trafalbad:20190518083333p:plain



project.json


{
    "name": "event_driven_job",
    "description": "event driven job",
    "memory": 1024,
    "timeout": 120,
    "role": "arn:aws:iam::{各自のAWS Account ID}:role/datadog_logs",
    "environment": {}
}

fuction.jsonとproject.jsonの共通項目はproject.jsonに書けば上書きできるので、role、memory、timeoutとかの共通項目はproject.jsonに書いた。



function.json


{
    "description": "datadog_logs",
    "runtime": "python3.6",
    "handler": "datadog_logs.lambda_handler",
    "environment":
    {
        "ENV": "dev"
    }
}

“runtime”と”handler"さえ良ければOK。あと、関数名は「datadog_logs」。



datadog_logs.py


def lambda_handler(event, context):
    json = {"statusCode": 200, "body": "hello world"}
    return json

Lambda関数用のpython.3.6のサンプルファイル。今回の記事ではほとんど関係ないのでテキトーに作った。



main.tf(resource、variableとかの解説)


・インフラ管理リソース設定
terraform

provider “aws

・Apexに必要
variable "apex_function_datadog_logs" {}


・IAMロールの作成に必要
resource "aws_iam_role" “datadog_logs_lambda_role"

data "aws_iam_policy_document" "datadog_logs_lambda_policy_doc"

resource "aws_iam_role_policy" “datadog_logs_lambda_policy"


・Cloudwatchログのログの出力先
output “datadog_logs_lambda_role"


・Cloudwatchログのロググループ名を定義
data "aws_cloudwatch_log_group" “log_group"


・Cloudwatchログからのログのストリーム設定
resource "aws_cloudwatch_log_subscription_filter" “datadog_logs_filter"


・Lambdaへのアクセス許可
resource "aws_lambda_permission" “datadog_logs_filter"





3.実行コマンド

1.セットアップ

MacにApexをinstall

$ curl https://raw.githubusercontent.com/apex/apex/master/install.sh | sh

・cloudwatchログにロググループ作成(参考サイト
グループ名→「datadog_logs」



・東京regionのS3バケット「apex-test-bucket」を作る


AWS環境変数の設定

$ export AWS_ACCESS_KEY_ID=****
$ export AWS_SECRET_ACCESS_KEY=****
$ export AWS_DEFAULT_REGION=ap-northeast-1
$ export AWS_REGION=ap-northeast-1


2.terraformのバックグランド初期化


$ apex infra init


3.IAMロールの作成


$ apex infra apply -target aws_iam_role.datadog_logs_lambda_role -target aws_iam_policy_document.datadog_logs_lambda_policy_doc -target aws_iam_role_policy.datadog_logs_lambda_policy

-targetオプションではmain.tfのIAMロール作成に必要なリソースを指定してる。

実行後は、IAMロールで「data_logs (AWS サービス: lambda)」ができてる。

f:id:trafalbad:20190518083430p:plain




4.Lambdaのデプロイ


$ apex deploy

S3やcloudwatchログが紐付いたLambda関数が出来てる。関数名は”{{.Project.Name}}_{{.Function.Name}}”。

f:id:trafalbad:20190518083510p:plain




5.残りのAWSリソース(Lambdaのトリガー)のデプロイ


$ apex infra apply

Lambda関数のトリガーにcloudwatchログが追加されてる
f:id:trafalbad:20190518083548p:plain




6.後片付け


$ apex infra destroy
$ apex delete

IAMロール & Lambda関数がキレイに破壊されてる。

f:id:trafalbad:20190518083805j:plain





4.まとめ

terraformコマンド集

# terraformインストール
$ brew install terraform

# バージョン確認
$ terraform --version
$ terraform init   

# インフラ環境を新しく作るため、変更内容を確認
$ terraform plan

# planで確認した内容が実行
$ terraform apply

# 進行状況の確認
$ terraform show

# リソースの全削除
$ terraform destroy


理解するためにやったこと



・terraformでEC2だけ作ってみる

・Lambdaにzipファイルをアップロードしてみる

GUIでCloudWatch イベントでAWS Lambda 関数をスケジュールしてみる

・サイト「Apex+Terraformでサーバレスアーキテクチャをフルコード化」を一通りやってみる



参考サイト



Apex+Terraformでサーバレスアーキテクチャをフルコード化

Providers - Terraform by HashiCorp(Terraform公式サイト)

Apex公式サイト

Node.jsでサーバを構築し、Tensorflow.jsでCNNの画像分類してみた【機械学習・Javascript】

今回はgoogle colaboratory(colab)で訓練したCNNの訓練済みモデルとtensorflow.jsを使い、Node.jsのサーバ上で画像分類してみた。

Node.jsを使ってjavascript用のサーバを立てたり、javascriptを使ったりと学ぶことが多かった。colab上で訓練→javascript上でmodelの読み込み→予測結果表示までの過程を備忘録もかねてまとめた。


目次
・この記事の概要

1.CNNで訓練・モデルの保存

2.tensorflowjs_converterで保存したモデルの変換

3.Node.jsで立てたサーバ上でモデルの読み込み

4.アップロードした画像を予測し、正解率を表示


この記事の概要


やったことを簡単にまとめると、まず「adidas, nike, new balance, vans」の4つのスニーカーを判別する画像分類モデルをCNNで作った。

自分のPCのスペックがカスなので、google colaboratoryを使って訓練。保存したモデル(h5ファイル)をtensorflowjs_converterでjavascript上で使えるように変換。


Node.jsでサーバを構築し、javascript用のサーバ上で変換した学習したモデルを読み込み、ローカルからアップロードした画像を予測し、各正解率を表示する。
その作業過程の中でまとめたいこと、知識などを時事系列でまとめてく。

ちなみにNode.jsのサーバ上にアクセスした時の画面はこんな感じ。
f:id:trafalbad:20190514234945p:plain



1.CNNで訓練・モデルの保存

自分のPCのスペックが心配なので、CNNの訓練はGPUが無料で使える神ツール Google Colaboratory(以下’colab’) を使った。

colabの使い方記事はこちらの記事を参考にした。


訓練に使ったpythonコード

tf.enable_resource_variables()

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
                        input_shape=(100, 100, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(4, activation='softmax'))

model.compile(loss='categorical_crossentropy',
              optimizer='Adam',
              metrics=['acc'])

# fit_generatorで画像を増幅
train_datagen = ImageDataGenerator(
    rescale=1./255,
    rotation_range=40,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,)

history = History()
callback=[]
callback.append(history)
callback.append(ModelCheckpoint(filepath='/Users/downloads/tfjs/ep{epoch:02d}model.h5', verbose=0, save_best_only=False,
                save_weights_only=False, mode='auto', period=1))


train_generator = train_datagen.flow(image, labels)

init = tf.global_variables_initializer()
sess = tf.InteractiveSession()
sess.run(init)
history = model.fit_generator(train_generator,  
                                  steps_per_epoch=100, callbacks=callback, epochs=50)

keras.CallbackのModelCheckpointメソッドでepochごとに拡張子がh5のファイルを保存する。colabではモデルの訓練・保存のみ行った。

tensorflowjsでのモデル変換はローカルで仮想環境を構築してやった。



2.tensorflowjs_converterで保存したモデルの変換

作業してるとtensorflowとtensorflowjs(以下’tfjs’)のバージョンが合わないとダメとか書いてある記事が多かった。

しかし、今回はtensorflowとtfjs共に、最新versionでモデル変換できた。
ちなみにtfjsを使うには、tfjsセットアップ用サイトでscriptタグのリンクをコピペしてhtmlに貼り付けるだけ。


ググってると、

・訓練モデルをsaved_model形式でpbファイルに保存
javascript上で読み込めるようにtensorflowjs_converterで変換
javascript上ではtf.loadGraphModel()で読み込む

やり方をしてる記事が結構多かった。

自分はそのやり方だと、エラーでつまづいた。結局、次のやり方でやった。

・普通にCNNで訓練してh5ファイルを保存
・tensorflowjs_converterで変換
javascript上ではtf.loadLayersModel()で読み込む



まず、jupyterで仮想環境を作り、tfjsとかをinstall。

$ pip install --upgrade pip
$ pip install tensorflow keras tensorflowjs


その後tfjsのtensorflowjs_converterを使い、以下のコマンドでh5ファイルを変換。

$ tensorflowjs_converter --input_format keras --output_format tfjs_layers_model ep02model.h5 web_model/

input_formatはkeras、 output_formatはtfjs_layers_model。

変換後はh5ファイルがバイナリファイル(group1-shard1of2.bin)とJSONファイル(model.json)に変換される。

f:id:trafalbad:20190514235124p:plain




3.Node.jsで立てたサーバ上でモデルの読み込み

javascriptの作業環境としてはgoogle chromeが人気だけど、モデルの読み込みとかは、エラーが発生し、かなり面倒。

変換モデルを読み込むためには、Google chrome用に「web server for chrome」でサーバを立ててやる必要があったり、拡張機能を追加しなければならなかった。(モデルを読み込む際の拡張機能についてはこのサイトの動画に詳しく載ってる)



結局、Node.jsでローカルにサーバ立てて、safarijavascriptの動作確認をした。

Node.jsはサーバサイドで動くJavaScriptのことで、Javascript用のサーバを構築できる。
Node.jsはNode.jsのサイトからダウンロード。そうすれば、npmコマンドが使えるようになる。



後はサーバ用のファイル(server.js)を用意して、Node.js用パッケージをダウンロードし、サーバを起動。

$ npm install
$ npm start

起動後のサーバには「http://localhost:8080」でアクセスできる。


server.js

let express = require("express");
let app = express();

app.use(express.static("./static"));

app.listen(process.env.PORT || 8080, function(){
    console.log("Serving at 8080")
});


そして、サーバ上ではtf.loadLayersModel()でさっきのJSONファイル(model.json)を読み込んだ。

let model;
async function loadModel() {
	model=await tf.loadLayersModel(`http://localhost:8080/web_model/model.json`);
	document.getElementById('predict-button').classList.remove('is-loading');
};


4.アップロードした画像を予測し、正解率を表示

サーバにアクセスして画像をアップロードしたときの画面はこんな感じ
f:id:trafalbad:20190514235232p:plain

今回は、コード内でtf.tidy()を使い、tfjsで使うGPUの不要なメモリを掃除してる。

まずローカルのnikeのスニーカー画像をアップロード。htmlではcanvasタグに表示

<!--画面表示領域-->
<canvas id="canvas" width="100" height="100" style="border: 2px solid;"></canvas>

<!--アップロードボタン-->
<label for="image"><b>画像</b></label>
<input type="file" accept="image/*" id="getfile">
var file = document.querySelector('#getfile');
const inputWidth = inputHeight = 100;

document.getElementById('getfile').onchange = function(e) {
  var img = new Image();
  img.onload = draw;
  img.src = URL.createObjectURL(this.files[0]);};

function draw() {
  var canvas = document.getElementById('canvas');
  canvas.width = this.width;
  canvas.height = this.height;
  var ctx = canvas.getContext('2d');
  ctx.drawImage(this, 0,0);}


・「Start」ボタンでモデルの読み込み

$("#start-button").click(function(){
	loadModel();
});


・「Predict」ボタンで予測し、各正解率を表示。

function getAccuracyScores() {
	var score = tf.tidy(() => {
		// resize
		const tmpcanvas = document.getElementById('canvas').getContext('2d');
    var image = tmpcanvas.getImageData(0, 0, inputWidth, inputHeight)
		var tensor = tf.browser.fromPixels(image).resizeNearestNeighbor([inputWidth, inputHeight]).toFloat();
		var offset = tf.scalar(255);
		var tensor_iamge = tensor.div(offset).expandDims();
		return model.predict(tensor_iamge);
	  });

	return score;
}

function prediction() {
	var accuracyScores = getAccuracyScores();
	const accuraylists = accuracyScores.data();
	var index = 0
	accuraylists.then(function(e){
		const elements = document.querySelectorAll(".accuracy");
		elements.forEach(el => {
    el.parentNode.classList.remove('is-selected');
    const rowIndex = Number(el.dataset.rowIndex);
    if (index===rowIndex){
			el.parentNode.classList.add('is-selected');
		}
		el.innerText = e[index];
		index++;
	  });
	});
}

・他にも、アップロード前の画面にリセットする「Resetボタン」、画像を左右に90度回転させるボタンも設置



サーバにアクセスしてから、予測して正解率を表示するまではこんな感じ。
f:id:trafalbad:20190515003014g:plain



完成までかなり大変だったけど、colab、Javascript、Node.js、tensorflowjsとか、かなり得るものが多かった。

今回の記事は、上のスキルがペーペーの人間がNode.jsのサーバ上で画像分類できるまでを備忘録を兼ねて書いた。



参考記事
・画像アップロードブラウザでローカル画像をリサイズしてアップロード

・画像のresetClearing an HTML file upload field via JavaScript

大容量データの音声認識(CNN)をCPU上でやった作業ログ【機械学習・ディープラーニング】

今回は音声認識のデータセット「ESC-50」をCNNで分類した。

特にこだわったのが、GPUでも普通にやったらOOMエラーが出るくらいの大容量のデータセットを、kerasのfit_generatorメソッドを使ってCPU上でもできるようにしたこと。

あとは音声認識は触れたことなかったので、前処理から学習するまでの作業ログ。

目次
1.音声データセット(ESC-50)
2.音声データの水増し(Augmentation)
3.水増した音声データの保存と読み込み
4.データ前処理とCPU上で学習(CNN)


1.音声データセット(ESC-50)


今回は音声データセットESC-50」を使う。

ESC-50の音声は環境音・自然音からなる声を含まない音。
動物の鳴き声、雨の音、人間の咳、時計のアラーム、エンジン音など50クラス。それをCNNで分類してみる。

ファイル形式は拡張子が.wavの音声。サイトで動画が音源のmp3やmp4もwavに変換することができる類のやつで、macで普通に再生できる。

自分のPCスペックだと普通にダウンロードできなかったので、linuxコマンドで一晩かけてダウンロード。

$ nohup wget [URL]

nohupコマンドを使えば、PCがスリープモードになっても動き続けてくれる。

import librosa
import librosa.display
import matplotlib.pyplot as plt
import seaborn as sn
from sklearn import model_selection
from sklearn import preprocessing
import IPython.display as ipd

# データのロード
esc_dir = os.path.join(base_dir, "ESC-50-master")
meta_file = os.path.join(esc_dir, "meta/esc50.csv")
audio_dir = os.path.join(esc_dir, "audio/")

# ラベルとその名前のDataFrame
class_dict = {}
for i in range(data_size[0]):
    if meta_data.loc[i,"target"] not in class_dict.keys():
        class_dict[meta_data.loc[i,"target"]] = meta_data.loc[i,"category"]
class_pd = pd.DataFrame(list(class_dict.items()), columns=["labels","classes"])

# ダウンロードした音声データの読み込み用関数
# load a wave data
def load_wave_data(audio_dir, file_name):
    file_path = os.path.join(audio_dir, file_name)
    x, fs = librosa.load(file_path, sr=44100)
    return x,fs

# change wave data to mel-stft
def calculate_melsp(x, n_fft=1024, hop_length=128):
    stft = np.abs(librosa.stft(x, n_fft=n_fft, hop_length=hop_length))**2
    log_stft = librosa.power_to_db(stft)
    melsp = librosa.feature.melspectrogram(S=log_stft,n_mels=128)
    return melsp

# display wave in plots
def show_wave(x):
    plt.plot(x)
    plt.show()
    
    
# display wave in heatmap
def show_melsp(melsp, fs):
    librosa.display.specshow(melsp, sr=fs)
    plt.colorbar()
    plt.show()




2.音声データの水増し(Augmentation)

1stステップでデータを音声認識用に変換し、2ndステップで水増しする。




1stステップ:変換


まずただの音声をメル周波数に変換。

メル周波数は人間の聴覚でも感知できるように考慮された尺度のこと。メル周波数に変換すれば、人間の耳でもなんの音かわかるようになる。

そのあとは分類しやすくするためフーリエ変換で次元圧縮。フーリエ変換での次元圧縮は画像でもよく使われてて、音声でも同じことができる。

フーリエ変換すると、音声を表す2次元グラフの横軸が時間だったのを、周波数のものに変換する。
f:id:trafalbad:20190429132833j:plain


直感的には、N次元の音声を人間にもわかるよう(メル周波数)に変換し、学習しやすいようにフーリエ変換で次元圧縮するイメージ。

# サンプル音声データをロード
x, fs = load_wave_data(audio_dir, meta_data.loc[0,"filename"])

# 音声をメル周波数に変換
melsp = calculate_melsp(x)
print("wave size:{0}\nmelsp size:{1}\nsamping rate:{2}".format(x.shape, melsp.shape, fs))
show_wave(x)
show_melsp(melsp, fs)

# 実際にjupyter上で音声が聞ける
ipd.Audio(x, rate=fs)

>>>output
wave size:(220500,)
melsp size:(128, 1723)
samping rate:44100


音声データ図
f:id:trafalbad:20190429132905j:plain




2nd:水増し


今回は元の音声をホワイトノイズ、シフトサウンド、ストレッチサウンドに変換し、水増し。

f:id:trafalbad:20190429144250j:plain


【ホワイトノイズ】
ホワイトノイズは広い音域に同程度の強度の「シャー」と聞こえるノイズ音を加える。

# add white noise
def add_white_noise(x, rate=0.002):
    return x + rate*np.random.randn(len(x))

x_wn = add_white_noise(x)
melsp = calculate_melsp(x_wn)
print("wave size:{0}\nmelsp size:{1}\nsamping rate:{2}".format(x_wn.shape, melsp.shape, fs))
show_wave(x_wn)
show_melsp(melsp, fs)

>>>output
# shapeは同じ
wave size:(220500,)
melsp size:(128, 1723)


【シフトサウンド
音声の周波数を変える。

def shift_sound(x, rate=2):
    return np.roll(x, int(len(x)//rate))

x_ss = shift_sound(x)
melsp = calculate_melsp(x_ss)


【ストレッチサウンド
周波数はそのままで、テンポ(音声のなる時間)を変える。
例えば、10秒間の音声を8秒間に変換するとか。

def stretch_sound(x, rate=1.1):
    input_length = len(x)
    x = librosa.effects.time_stretch(x, rate)
    if len(x)>input_length:
        return x[:input_length]
    else:
        return np.pad(x, (0, max(0, input_length - len(x))), "constant")
    
x_st = stretch_sound(x)
melsp = calculate_melsp(x_st)





3.水増した音声データの保存と読み込

まず、普通のtrainとtestデータを保存。
(ファイル名:"esc_melsp_train_raw.npz")

freq = 128
time = 1723

# save wave data in npz, with augmentation
def save_np_data(filename, x, y, aug=None, rates=None):
    np_data = np.zeros(freq*time*len(x)).reshape(len(x), freq, time)
    np_targets = np.zeros(len(y))
    for i in range(len(y)):
        _x, fs = load_wave_data(audio_dir, x[i])
        if aug is not None:
            _x = aug(x=_x, rate=rates[i])
        _x = calculate_melsp(_x)
        np_data[i] = _x
        np_targets[i] = y[i]
    np.savez(filename, x=np_data, y=np_targets)

# get training dataset and target dataset
x = list(meta_data.loc[:,"filename"])
y = list(meta_data.loc[:, "target"])

x_train, x_test, y_train, y_test = model_selection.train_test_split(x, y, test_size=0.25, stratify=y)
print("x train:{0}\ny train:{1}\nx test:{2}\ny test:{3}".format(len(x_train), len(y_train), len(x_test), len(y_test)))

>>>output
x train:1500
y train:1500
x test:500
y test:500


次の3つはtrainデータをホワイト・ストレッチ・シフトで水増し保存。(ファイル名:"esc_melsp_train_ss.npz","esc_melsp_train_st.npz", "esc_melsp_train_wn.npz")

# save training dataset with white noise
if not os.path.exists("esc_melsp_train_wn.npz"):
    rates = np.random.randint(1,50,len(x_train))/10000
    save_np_data("esc_melsp_train_wn.npz", x_train,  y_train, aug=add_white_noise, rates=rates)

# save training dataset with sound shift
if not os.path.exists("esc_melsp_train_ss.npz"):
    rates = np.random.choice(np.arange(2,6),len(y_train))
    save_np_data("esc_melsp_train_ss.npz", x_train,  y_train, aug=shift_sound, rates=rates)

# save training dataset with stretch
if not os.path.exists("esc_melsp_train_st.npz"):
    rates = np.random.choice(np.arange(80,120),len(y_train))/100
    save_np_data("esc_melsp_train_st.npz", x_train,  y_train, aug=stretch_sound, rates=rates)

最後はホワイトノイズした後、ストレッチかシフトを組み合わせた水増しデータを保存(”esc_melsp_train_com.npz")。

# save training dataset with combination of white noise and shift or stretch
if not os.path.exists("esc_melsp_train_com.npz"):
    np_data = np.zeros(freq*time*len(x_train)).reshape(len(x_train), freq, time)
    np_targets = np.zeros(len(y_train))
    for i in range(len(y_train)):
        x, fs = load_wave_data(audio_dir, x_train[i])
        x = add_white_noise(x=x, rate=np.random.randint(1,50)/1000)
        if np.random.choice((True,False)):
            x = shift_sound(x=x, rate=np.random.choice(np.arange(2,6)))
        else:
            x = stretch_sound(x=x, rate=np.random.choice(np.arange(80,120))/100)
        x = calculate_melsp(x)
        np_data[i] = x
        np_targets[i] = y_train[i]
    np.savez("esc_melsp_train_com.npz", x=np_data, y=np_targets)



5つのファイルをまとめてtrain_filesにまとめる。ファイル名“esc_melsp_train_com.npz”に保存
テストデータのファイル名は"esc_melsp_test.npz"。

# dataset files
train_files = ["esc_melsp_train_raw.npz",  "esc_melsp_train_ss.npz", "esc_melsp_train_st.npz", 
               "esc_melsp_train_wn.npz", "esc_melsp_train_com.npz"]
test_file = "esc_melsp_test.npz"


4.データ前処理とCPU上で学習(CNN)


前処理


振動数、テンポの設定をして、データ読み込み
振動数(または周波数):freq = 128
テンポ(音のなる時間):time =1723


CNNに読み込ませるデータshapeは
x_train:(7500, 128, 1723, 1):(batch_size, freq, time, 1)
y_train:(7500, 50):(batch_size, classes)

freq = 128
time = 1723

train_num = 1500
test_num = 500

# データセット用placeholderの定義
x_train = np.zeros(freq*time*train_num*len(train_files)).reshape(train_num*len(train_files), freq, time)
y_train = np.zeros(train_num*len(train_files))

# ファイルからtrain/testデータ読み込み
for i in range(len(train_files)):
    data = np.load(train_files[i])
    x_train[i*train_num:(i+1)*train_num] = data["x"]
    y_train[i*train_num:(i+1)*train_num] = data["y"]

test_data = np.load(test_file)
x_test = test_data["x"]
y_test = test_data["y"]

# ラベルをone-hotに変換
classes = 50
y_train = keras.utils.to_categorical(y_train, classes)
y_test = keras.utils.to_categorical(y_test, classes)

# CNN用にデータを(batch_size, freq, time, 1)にreshape
x_train = x_train.reshape(train_num*5, freq, time, 1)
x_test = x_test.reshape(test_num, freq, time, 1)

print("x train:{0}\ny train:{1}\nx test:{2}\ny test:{3}".format(x_train.shape, y_train.shape, x_test.shape, y_test.shape))

>>>output
x train:(7500, 128, 1723, 1)
y train:(7500, 50)
x test:(500, 128, 1723, 1)
y test:(500, 50)


CNNで学習


割と音声認識用でよく使われてるCNNを使用。

f:id:trafalbad:20190429144148j:plain

def cba(inputs, filters, kernel_size, strides):
    x = Conv2D(filters, kernel_size=kernel_size, strides=strides, padding='same')(inputs)
    x = BatchNormalization()(x)
    x = Activation("relu")(x)
    return x

# Callbackとか
model_dir = "~/ESC-50-master/dir"
if not os.path.exists(model_dir):
    os.mkdir(model_dir)

es_cb = EarlyStopping(monitor='val_loss', patience=10, verbose=1, mode='auto')
chkpt = os.path.join(model_dir, 'esc50_.{epoch:02d}_{val_loss:.4f}_{val_acc:.4f}.hdf5')
cp_cb = ModelCheckpoint(filepath = chkpt, monitor='val_loss', verbose=1, save_best_only=True, mode='auto')

# CNN
inputs = Input(shape=(x_train.shape[1:]))

x_1 = cba(inputs, filters=32, kernel_size=(1,8), strides=(1,2))
x_1 = cba(x_1, filters=32, kernel_size=(8,1), strides=(2,1))
x_1 = cba(x_1, filters=64, kernel_size=(1,8), strides=(1,2))
x_1 = cba(x_1, filters=64, kernel_size=(8,1), strides=(2,1))

x_2 = cba(inputs, filters=32, kernel_size=(1,16), strides=(1,2))
x_2 = cba(x_2, filters=32, kernel_size=(16,1), strides=(2,1))
x_2 = cba(x_2, filters=64, kernel_size=(1,16), strides=(1,2))
x_2 = cba(x_2, filters=64, kernel_size=(16,1), strides=(2,1))

x_3 = cba(inputs, filters=32, kernel_size=(1,32), strides=(1,2))
x_3 = cba(x_3, filters=32, kernel_size=(32,1), strides=(2,1))
x_3 = cba(x_3, filters=64, kernel_size=(1,32), strides=(1,2))
x_3 = cba(x_3, filters=64, kernel_size=(32,1), strides=(2,1))

x_4 = cba(inputs, filters=32, kernel_size=(1,64), strides=(1,2))
x_4 = cba(x_4, filters=32, kernel_size=(64,1), strides=(2,1))
x_4 = cba(x_4, filters=64, kernel_size=(1,64), strides=(1,2))
x_4 = cba(x_4, filters=64, kernel_size=(64,1), strides=(2,1))

x = Add()([x_1, x_2, x_3, x_4])

x = cba(x, filters=128, kernel_size=(1,16), strides=(1,2))
x = cba(x, filters=128, kernel_size=(16,1), strides=(2,1))

x = GlobalAveragePooling2D()(x)
x = Dense(classes)(x)
x = Activation("softmax")(x)

model = Model(inputs, x)

# initiate Adam optimizer with amsgrad
opt = keras.optimizers.adam(lr=0.00001, decay=1e-6, amsgrad=True)

model.compile(loss='categorical_crossentropy',
              optimizer=opt,
              metrics=['accuracy'])

model.summary()

>>>output
input_2 (InputLayer) (None, 128, 1723, 1) 0 
~~~~~
activation_38 (Activation)      (None, 50) 




model.fit_generator()で、大容量でCPUでも訓練できるようにした。

# class data generator

class MixupGenerator():
    def __init__(self, x_train, y_train, batch_size=16, alpha=0.2, shuffle=True):
        self.x_train = x_train
        self.y_train = y_train
        self.batch_size = batch_size
        self.alpha = alpha
        self.shuffle = shuffle
        self.sample_num = len(x_train)

    def __call__(self):
        while True:
            indexes = self.__get_exploration_order()
            itr_num = int(len(indexes) // (self.batch_size * 2))

            for i in range(itr_num):
                batch_ids = indexes[i * self.batch_size * 2:(i + 1) * self.batch_size * 2]
                x, y = self.__data_generation(batch_ids)

                yield x, y

    def __get_exploration_order(self):
        indexes = np.arange(self.sample_num)

        if self.shuffle:
            np.random.shuffle(indexes)

        return indexes

    def __data_generation(self, batch_ids):
        _, h, w, c = self.x_train.shape
        _, class_num = self.y_train.shape
        x1 = self.x_train[batch_ids[:self.batch_size]]
        x2 = self.x_train[batch_ids[self.batch_size:]]
        y1 = self.y_train[batch_ids[:self.batch_size]]
        y2 = self.y_train[batch_ids[self.batch_size:]]
        l = np.random.beta(self.alpha, self.alpha, self.batch_size)
        x_l = l.reshape(self.batch_size, 1, 1, 1)
        y_l = l.reshape(self.batch_size, 1)

        x = x1 * x_l + x2 * (1 - x_l)
        y = y1 * y_l + y2 * (1 - y_l)

        return x, y

# 訓練
batch_size = 16
epochs = 10

training_generator = MixupGenerator(x_train, y_train)()
model.fit_generator(generator=training_generator, steps_per_epoch=x_train.shape[0] // batch_size,
                    validation_data=(x_test, y_test), epochs=epochs, 
                    verbose=1, shuffle=True, 
                    callbacks=[es_cb, cp_cb])


今回は訓練を終えて、精度出すところまで行かなかったけど、これと同じやり方なら80%を超えて、人間の精度を超えるらしい。

音声認識は触れたことなかったので、学習までのデータの加工とか、水増し、CPU上で大容量データの訓練方法とかのいいアウトプットになった。

参考:ディープラーニングで音声分類 - Qiita

pythonで強化学習のモデルフリーの手法、学習法・コードまとめ【機械学習】

強化学習はモデルベースとモデルフリーに分類できて、前回はモデルベースの手法をまとめた。
今回はモデルフリーのメインの手法をまとめてく。

モデルベースの手法はこちら。
trafalbad.hatenadiary.jp



目次
1.変数、関数、環境、エージェントの定義
2.モデルフリーにおける3つの問題とその解決法
3.まとめ



1.変数、関数、環境、エージェントの定義

まずモデルフリーの定義について

「モデルフリー」= サッカーのようなスポーツのように、「どんな行動をとれば報酬に繋がるかわからない(遷移関数と報酬関数がわからない)環境」のこと。


モデルフリーは「遷移関数」と「報酬関数」を定義しない代わりに、「エージェント(環境におけるプレイヤー)」を定義する。


モデルフリーはモデルベースとは違い、エージェントが行動しながら、価値を最大化するように学習する仕組みで、メインで定義するのは「行動、状態、環境、エージェント」


モデルベースとモデルフリーの大きな違いは「エージェントを定義するか、してないか」だ。

# モデルフリーの環境(コイントスゲーム)の定義
class CoinToss():

    def __init__(self, head_probs, max_episode_steps=30):
        self.head_probs = head_probs
        self.max_episode_steps = max_episode_steps
        self.toss_count = 0
      # 略
            return reward, done

# エージェントの定義
class Agent():
  # 略


モデルフリーにおける学習の概略図
f:id:trafalbad:20190425221644p:plain



2.モデルフリーにおける3つの問題とその解決法

モデルフリーではエージェントが行動することによって「経験」を蓄積していき、報酬を最大化する仕組み。
そこで、焦点になる問題が3つある。



モデルフリーにおける3つの問題の関係図
f:id:trafalbad:20190425221709p:plain


1.経験を蓄積するか活用するか

モデルフリーでエージェントの「経験」を使い、報酬の総和を最大化するためには

・経験を蓄積して、よりよい遷移状態に行けるかどうか知る(以下「探索」)

・報酬を得るためには、経験を活用する(以下「活用」)

の2つの使い方をバランスよく行う必要がある。


経験における「探索と活用はトレードオフの関係」にあるため、理想は探索しながら経験を活用して報酬を得ること。


そのために「探索と活用のバランス」を上手くとる手法として「epsilon-greedy法」がある。

# epsilon-greedy法での学習

# EpsilonGreedy法のエージェントの定義
class EpsilonGreedyAgent():

    def __init__(self, epsilon):
        self.epsilon = epsilon
        self.V = []
        
 # コイントスゲームを行う処理
    def play(self, env):
        # 略
        return rewards
        


# コイントスゲーム環境と上のエージェントを使う
env = CoinToss([0.1, 0.5, 0.1, 0.9, 0.1])
epsilons = [0.0, 0.1, 0.2, 0.5, 0.8]
game_steps = list(range(10, 310, 10))
result = {}
for e in epsilons:
    agent = EpsilonGreedyAgent(epsilon=e)
    means = []
    for s in game_steps:
        env.max_episode_steps = s
        rewards = agent.play(env)
        means.append(np.mean(rewards))
    result["epsilon={}".format(e)] = means
result["coin toss count"] = game_steps
result = pd.DataFrame(result)
result.set_index("coin toss count", drop=True, inplace=True)
result.plot.line(figsize=(10, 5))
plt.show()


コイントス環境の引数の値を変えて、ベストな値を探し、探索と活用のバランスをとる。

epsilon-greedy法の値による報酬の推移は以下の図の通り。0.1か0.2がベスト値となってる。
f:id:trafalbad:20190425222733p:plain




2.エージェントの行動の修正をどのように行うか

エージェントの行動の修正は

実績で行うか(実績で修正)

実績で修正:エピソードが終わったときの報酬の総和で修正する(主な手法は「モンテカルロ法」)

予測で行うか(予測で修正)

予測で修正:エピソードの終了を待たずに途中で修正する(主な手法は「TD法」)


以下に実績で修正した場合と、予測で修正した場合の報酬獲得の推移をまとめた。



実績で修正した(モンテカルロ法の)エピソード数の獲得報酬平均の推移

f:id:trafalbad:20190425222756p:plain




予測で修正した(TD法を利用した学習法「Q-learning」の)エピソード数の獲得報酬平均の推移

f:id:trafalbad:20190425223428p:plain

どっちも上手く行ってる。



***エージェント行動の修正のメイン手法***

・1回の行動直後に修正する=TD(0)

モンテカルロ法(TD(1))

・2とから3回の行動直後に修正する=Multi-step Learning

・各ステップの値を合成し、誤差を計算する=TD(λ)法

強化学習の深層学習の手法ではMulti-step Learningがよく使われてる。




3.経験を戦略(policyベース)、状態評価(valueベース)、その両方の更新に利用するか

モデルフリーの学習方法には「経験」を

・価値を最大化する行動選択(valueベース)のために更新するか

・戦略に基づく行動選択(policyベース)のために更新するか

・互いに弱点を補いながら、valueベースとpolicyベースの両方(Actor Critic)を更新するか

の3つがある。


モデルフリーの3つの学習法「policyベース」・「valueベース」・「Actor Critic」



valueベース】

valueベースを主に使う学習法はQ-learning。
さっきも載っけたけど、再掲。


Q-learningの学習のエピソード数と獲得平均報酬の推移
f:id:trafalbad:20190425223428p:plain




【policyベース】
policyベースを主に使う学習法はSARSAがある



SARSAの学習のエピソード数と獲得平均報酬の推移
f:id:trafalbad:20190425224343p:plain

今回のコイントス環境では、Q-learningの状態評価の方がいい結果になった。

これはリスクのある行動をとらない状態評価の効果が、今回の環境に上手く反映された結果っぽい。



【Actor Critic法】

もう一つの学習法は、valueベースとpolicyベースを組み合わせた「Actor Critic法」。

戦略担当Actorと状態評価担当Criticを相互に更新して学習する仕組み。


Actor Critic法の学習のエピソード数と獲得報酬平均の推移
f:id:trafalbad:20190425224430p:plain

今までの手法より、エピソード数が長くなってるけど、その分最終的には安定して報酬を獲得できてる。



3.まとめ
モデルフリーの学習の流れをまとめると

①変数、関数、環境、エージェントの定義

②3つの問題を考慮し、手法を考える

1.経験を蓄積するか活用するかのバランスを取る手法「epsilon-greedy法」

2.エージェントの行動の修正をどのように行うか

3.経験を戦略(policyベース)、状態評価(valueベース)、またはそのどちらか(Actor Critic)の更新に利用するか

valueベース、policyベースまたは、両方を組み合わせた「Actor Critic法」で学習する



強化学習は今、すべて深層学習になってるけと、モデルフリーに関して紹介した手法やモデルベースの記事での手法は強化学習の基礎&メインの手法で、深層学習でも使われてる。

強化学習の基礎的かつメインの手法を学習して備忘録としてまとめた。モデルベースの記事とモデルフリーのこの記事で、強化学習の基礎的 & 主要な手法はほぼ網羅してる。

pythonで強化学習のモデルベースの手法とその学習法をまとめてみた【機械学習】

書籍「pythonで学ぶ強化学習」を読んだ。強化学習のアウトプットの機会がないので、とりあえず学んだ内容を備忘録も兼ねてまとめてみた。

主に強化学習は「モデルベース」と「モデルフリー」に分類でき、今回はモデルベースの手法のまとめ。

目次
1.モデルベース強化学習のメイン「変数・関数・環境」
2.モデルベースとモデルフリー
3.強化学習の学習法「policyベース」と「valueベース」
4.まとめ


1.モデルベース強化学習のメイン「変数・関数・環境」

モデルベースの強化学習で主に定義するのは、行動、状態、遷移関数、報酬関数、環境。

例:
環境:12マスのゲーム、緑にたどり着ければ報酬ゲット
f:id:trafalbad:20190425080644p:plain

行動(a):右、左、上、下

状態(s):行列のセルの位置

遷移関数(T(s, a)):行動と状態を引数にとり、次に移動するセル(遷移先:s')とそこへ遷移する確率を返す関数

報酬関数(R(s', s)):状態と遷移先(s')を引数に、赤のセルなら-1と、緑のセルなら+1を返す関数


変数、関数、環境の概略コード

# 状態
class State():
    # 略
     return self.row == other.row and self.column == other.column

# 行動
class Action(Enum):
    UP = 1
    DOWN = -1
    LEFT = 2
    RIGHT = -2

# 環境
class Environment():
    def __init__(self, grid, move_prob=0.8):

   # 遷移関数
   def transit_func(self, state, action):
        # 略
        return transition_probs

   # 報酬関数
   def reward_func(self, state):
        # 略
        return reward, done

   return next_state, reward, done


***ちなみに***
強化学習は「遷移先の状態は直前の状態と、そこでの行動のみに依存する。報酬は直前の状態と遷移先に依存する」という決まり(「マルコフ決定過程(MDP)」)がある。
つまり、強化学習は「行動、状態、遷移関数、報酬関数、環境を与えることで報酬の総和(≒評価値)が決まる」という決まりになってる。


強化学習マルコフ決定過程の図
f:id:trafalbad:20190425080625p:plain



2.モデルベースとモデルフリー

強化学習はモデルベースとモデルフリーに分類できる。

モデルベース




モデルベース=「どんな行動をとれば報酬が得られるか(遷移関数と報酬関数)がはっきりわかってる環境」
のこと

下図の環境は
・緑のセル→プラスの報酬
・オレンジのセル→マイナスの報酬

と「どんな行動をとれば報酬がもらえるのか」はっきりしているからモデルベースの一例。

f:id:trafalbad:20190425080644p:plain


モデルフリー



モデルフリー=「どんな行動をとれば報酬に繋がるかわからない(遷移関数と報酬関数がわからない)環境」のこと。


スポーツなら全般(例えばサッカーとかバドミントンとか)が当てはまり、有名なアルファ碁は対戦相手によって報酬が決まらないからモデルフリー。

モデルフリーは遷移関数と報酬関数を定義せず、エージェント(環境におけるプレイヤー)を定義する。




モデルベース、モデルフリーにおける報酬の総和(価値)の計算



モデルベース、モデルフリーの環境はどちらも
・環境における制限時間(エピソード)終了時の報酬の総和(以下「価値」)

を計算するときは、

・過去の計算値を使う
・(行動)確率をかけて期待値で表す

という2つテク(Bellman Equation)を使って計算する。

強化学習はこの「価値」を最大化することが目的なので、重要な部分。

def V(s, gamma=0.99):
    V = R(s) + gamma * max_V_on_next_state(s)
    return V


def R(s):
    if s == "happy_end":
        return 1
    elif s == "bad_end":
        return -1
    else:
        return 0

def max_V_on_next_state(s):
    # 略
    return max(values)

def transit_func(s, a):
    # 略
        return {
            next_state(s, a): MOVE_PROB,
            next_state(s, opposite): 1 - MOVE_PROB}




3.強化学習の学習法「policyベース」と「valueベース」

強化学習の学習目的で一番の焦点は

・Bellman Equationで表した「価値」をどうやって最大化するか

この目的に合わせて、主に「policyベース」と「valueベース」2つの学習方法がある。(2つ両方使う、二刀流的な学習方法もある)




policyベース
=> どのように行動するか(戦略)を基準に学習する

イメージ

policyベース→参謀長みたいな策略家で現実的な思考で学習する感じ


valueベース
=> 「特定の行動をとる=評価になる」ということだけ学習する。(評価=行動選択)

イメージ
valueベース→いいならいい、ダメならダメと勝手に決めて学んでくので、楽観的な思考で学習する感じ




# valueベースとpolicyベースで共通のPlannerを定義
class Planner():

    def __init__(self, env):
        self.env = env
        self.log = []

    def initialize(self):
        self.env.reset()
        self.log = []

    def plan(self, gamma=0.9, threshold=0.0001):
        raise Exception("Planner have to implements plan method.")

    def transitions_at(self, state, action):
        transition_probs = self.env.transit_func(state, action)
        for next_state in transition_probs:
            prob = transition_probs[next_state]
            reward, _ = self.env.reward_func(next_state)
            yield prob, next_state, reward

    def dict_to_grid(self, state_reward_dict):
        grid = []
        for i in range(self.env.row_length):
            row = [0] * self.env.column_length
            grid.append(row)
        for s in state_reward_dict:
            grid[s.row][s.column] = state_reward_dict[s]

        return grid

# Plannerを継承した「valueベース」
class ValuteIterationPlanner(Planner):
    # 略
    return V_grid


# Plannerを継承した「policyベース」
class PolicyIterationPlanner(Planner):
    # 略

 # 戦略の価値計算用関数
 def plan(self, gamma=0.9, threshold=0.0001):
       # 略
        return V_grid



ちなみさっきのセルのモデルベース環境でvalueベースで訓練した結果。上手く緑にたどり着けてる。

f:id:trafalbad:20190425080711p:plain

環境が単純だからpolicyベースでもほとんど変わらない結果だった。



4.まとめ

モデルベースの強化学習の学習・評価までの流れは

①環境の定義に加えて、行動、状態、遷移関数、報酬関数の定義



②「エピソード終了時の報酬の総和(価値)」の定義

強化学習では学習目的がこの「価値」の最大化であり、この「価値」は、
・過去の計算値を使う
・(行動)確率をかけて期待値で表す

というテク「Bellman Equation」を使って計算される。



③「policyベース」か「valueベース」の手法で学習する
policyベース、valueベースまたは、その両方を使った手法を使って学習・評価する。




今の主流はvalueベースとpolicyベースでお互いの弱点を補いながら併用する手法がメインぽい。強化学習の実用されてるのは全てディープラーニングだけど、基礎的な手法自体はここで書いたやり方と同じ。

今回は「モデルベース」のメインの手法をまとめた。

「モデルフリー」の手法をまとめた次記事はこちら
trafalbad.hatenadiary.jp

個人的なpythonのclassと継承、super、その他*argsとかのアウトプットまとめ

class、継承や*argsとか、pythonでまだまともに使ったことがないメソッドが多かったので、暇がある機会に学習した。なので淡々と書くアウトプットログ。

早く身につけるコツはこの一年で大分学んだ。基礎を学んだら、問題設定でもなんでもして、さっさとアウトプットして使いまくれば、参考書読んでるよりよっぽど早く身についた。


目次
・classのアウトプット
・*argsと**kwargsのアウトプット
・lambdaとmapメソッドのアウトプット

f:id:trafalbad:20190404120328p:plain

classのアウトプット

ワンピースの覇気の計算式

シリーズは「アラバスタ編、空島、エニエスロビー、スリラーバーグ、魚人島、ドフラミンゴ編、ビッグマム編
を対象にした。

覇気の能力upは
・覇気は極限の戦闘状態でこそ強さが増す
・本人の気質が反映される

という二点を考慮して、自分で勝手に変数を以下のように決めた

who:ルフィ、ゾロ、サンジ

kisitu(気質):覇王色=ルフィ、武装色=ゾロ、見聞色=サンジ

battle_rank:シリーズ回毎の対戦相手の強さ:1〜5(5段階rank)

out_limit_count:限界を超えた力を出さざるを得ないシーンがあったか

how_hurt:どのくらい傷ついたか:1〜5(5段階rank)

count:シリーズの回数=7回(アラバスタ編、空島、エニエスロビー、スリラーバーグ、魚人島、ドフラミンゴ編、ビッグマム編)

# ルフィの覇気
class LUFFY:
    def __init__(self, who, battle_rank, how_hurt, out_limit_count, count=7, kisitu=1):
        self.who = who
        self.kisitu=kisitu
        self.battle_rank=battle_rank
        self.out_limit_count=out_limit_count
        self.how_hurt=how_hurt
        self.count=count
    def hao_syoku(self):
        if self.who is 'luffy':   # ルフィにしかないので気質はなし
            limiter_situation = lambda count, hurt:(count+hurt)*self.out_limit_count # 戦闘での極限状態
            hao_level=(self.battle_rank)*limiter_situation(self.count, self.how_hurt)
        else:
            hao_level=0
        return hao_level
            
    def kenbun_syoku(self, *args):
        if self.who is 'sanzi':
            kisitu=self.kisitu*2
        else:
            kisitu=self.kisitu
        limiter_situation =lambda counts, anything:(counts+anything)*self.out_limit_count # 戦鬪での極限状態
        epsil=limiter_situation(self.count, *args)
        kenbun_level=(kisitu+self.battle_rank)*epsil
        return kenbun_level
    def buso_syoku(self, weapon):
        if self.who is 'zoro':
            kisitu=self.kisitu*2
        else:
            kisitu=self.kisitu
        # 全キャラで武装色関連の極限戦闘シーンないので「戦鬪での極限状態」なし
        buso_level=(weapon+kisitu+self.battle_rank)*self.count
        return buso_level
    
    def total_haki(self,weapon, *args):
        total_power=self.hao_syoku() + self.kenbun_syoku(*args) + self.buso_syoku(weapon)
        return total_power
    
# インスタンス化  
luffy=LUFFY('luffy', 5, 5, 4)
print('ルフィ覇王色Lv {}'.format(luffy.hao_syoku()))
print('ルフィ見聞色Lv {}'.format(luffy.kenbun_syoku(8))) # カタクリ戦=8
print('ルフィ武装色Lv {}'.format(luffy.buso_syoku(weapon=6))) # weapon=ゴムゴムの実
print('total 覇気Lv {}'.format(luffy.total_haki(6, 8)))

"""
ルフィ覇王色Lv 240
ルフィ見聞色Lv 360
ルフィ武装色Lv 84
total 覇気Lv 684
"""


# ルフィの計算式用のクラスを継承でゾロとサンジに適用

# 継承用クラス作成
class ZORO(LUFFY):
    def __init__(self, who, battle_rank, how_hurt, out_limit_count, count=7, kisitu=1):
        zoro_class=super(ZORO, self)
        zoro_class.__init__(who, battle_rank, how_hurt, out_limit_count)
        
class SANZI(LUFFY):
    def __init__(self, who, battle_rank, how_hurt, out_limit_count, count=7, kisitu=1):
        sanzi_class=super(SANZI, self)
        sanzi_class.__init__(who, battle_rank, how_hurt, out_limit_count)

# インスタンス化して覇気を計算
# ゾロ
zoro=ZORO('zoro', 3, 4, 2)
print('ゾロ覇王色Lv {}'.format(zoro.hao_syoku()))
print('ゾロ見聞色Lv {}'.format(zoro.kenbun_syoku(0)))
print('ゾロ武装色Lv {}'.format(zoro.buso_syoku(weapon=12))) # weapon=刀3本
print('total 覇気Lv {}'.format(zoro.total_haki(12, 0)))
"""
ゾロ覇王色Lv 0
ゾロ見聞色Lv 56
ゾロ武装色Lv 119
total 覇気Lv 175
"""
# サンジ
sanzi=SANZI('sanzi', 2, 2, 1)
print('サンジ覇王色Lv {}'.format(sanzi.hao_syoku()))
print('サンジ見聞色Lv {}'.format(sanzi.kenbun_syoku(0)))
print('サンジ武装色Lv {}'.format(sanzi.buso_syoku(weapon=2))) # weapon=足
print('total 覇気Lv {}'.format(sanzi.total_haki(2, 0)))

"""
サンジ覇王色Lv 0
サンジ見聞色Lv 28
サンジ武装色Lv 35
total 覇気Lv 63
"""

やっぱりルフィは強敵と戦ってるし、覇気鍛えまくってるから高い。特に見聞色はカタクリ戦で未来見れるようになったから、カタクリ戦を変数に入れた。

ゾロは剣士で、ミホークの修行受けてるので武装色高め。
サンジは覇気をおまけ的な感じで使ってるからtotalで低い。



その他のclass関連アウトプット

# 簡単なクラス
class food:
    imp=34
    def atomosphere(self):
        f=10
        return f
# インスタンス化
rest=food()
# 変数
rest.imp 
# >>>34

# 関数
rest.atomosphere()
# >>>10
# class内の変数使う場合
class food:
    imp=34
    def atomosphere(self):
        f=10+self.imp
        return f
rest=food()
rest.atomosphere()
# >>> 44
# 初期化変数ありclass
class staffmoney:
    def __init__(self, ids):
        self.id=ids
        print('get class') # はじめに行う処理
    def hour(self, hour):
        h=1+hour
        return h
    def money(self, money):
        m=10000+money
        return m
    def rank(self, rank):
        r=1+rank
        return r
    
# インスタンス化する & idsに'A'を代入
df=staffmoney('A')
print(df.id)
print(df.hour(3))
print(df.money(4000))
print(df.rank(3))

"""
get class
A
4
14000
4
"""
# 継承
# ベースclass作成
class naruto:
    def __init__(self, zyutu):
        self.utiha=zyutu
    def madara(self):
        print('susanoo')
    def obito(self):
        ms=lambda x:x*2
        print(ms(2))
    def sasuke(self):
        print(self.utiha)
        
uzumaki=naruto('螺旋丸')
uzumaki.obito()
# >>> 4


# 継承 & zyutu変数を子クラスで書き換えられるようにした
class ninzyutu(naruto):
    def __init__(self, zyutu):
        ninzya=super(ninzyutu, self)
        ninzya.__init__(zyutu)
        
# zyutu変数書き換え
fire=ninzyutu('業火滅失')
fire.obito()
# >>> 4
# inception_resnet_v2とそのinput関数の模擬的なclass化

class inception_resnet_v2:
    def conv2d_bn(self, x):
        return x


    def inception_resnet_block(self, scale):
        a=self.conv2d_bn(5)+scale
        return a

    def InceptionResNetV2(self, image):
        
        x = self.conv2d_bn(10) # 10
       
        g = self.inception_resnet_block(10) # 15

        return g+x+image # 25+image


class INPUT:
    def inputed(self, data_files, batch_size, train=True, num_readers = 60):
        if train==True:
            serialized_example=400
            # self.関数でclass内の関数の呼び出し
            image, label=self.parse_example_proto(serialized_example)
        return image, label
    def parse_example_proto(self, serialized_example):
        image=33+serialized_example
        label=serialized_example+28
        return image, label
    
i=inception_resnet_v2()
print(i.InceptionResNetV2(10))
# >>> 35
train_input=INPUT()
image, label=train_input.inputed(data_files=30, batch_size=22)
print(image)
print(label)

# >>> 433
# >>> 428


*argsと**kwargsのアウトプット

*args

# *args :引数をいくらでも取れる
def mys(*args):
    return args

a=mys(1,4,5,6,7,7,8)
print(a)
# >>> (1, 4, 5, 6, 7, 7, 8)


class animalbody:
    def __init__(self, *args):
        self.b=args
    def body(self):
        print('skin')
    def arm(self):
        print(self.b)
        
dds=animalbody('腕','手', '憎しみ')
dds.arm()
# >>> ('腕', '手', '憎しみ')



**kwargs

# **kargs:dicの引数をいくらでも取れる
def dic_mys(**kargs):
    return kargs

b=dic_mys(b=4, f=2, cc=7)
print(b)
# {'b': 4, 'f': 2, 'cc': 7}

class bomclass:
    def __init__(self, **kargs):
        self.dds=kargs
        self.kd=kargs
    def h2o(self, water):
        return water
    def herium(self):
        return self.kd, self.dds
    
bom=bomclass(v=12, c=45, oroti=45)
print(bom.herium())
# ({'v': 12, 'c': 45, 'oroti': 45}, {'v': 12, 'c': 45, 'oroti': 45})

ガチなclassと**kwargsの使い方

# LSTMみたいにQRNN(64, window_size=12, dropout=0)みたいに使うには、変数を定義したあとでこう書く(らしい:at my github's qrnn.py)
from keras.layers import Layer

class QRNN(Layer):
    def __init__(self, level, kisitu=None, **kwargs):
        self.level=level
        super(QRNN, self).__init__(**kwargs)


lambdaとmapメソッドのアウトプット

# lambda と mapメソッド
ms = lambda a,b: (a - b)**2 # def ms(a, b)
ms(5,2)
# >>> 9 (5-2=3 and 3**2)

l=[1,2,3]
l2=list(map(lambda x:x**2, l))
l2 # [1, 4, 9]


学ぶ必要があれば、その都度追記でアプトプット予定

自然言語処理タスクでいろんなRNN系ニューラルネットでの精度を検証してみた【keras・機械学習】

深層学習を使った自然言語処理のタスクで、ネガポジの2値分類をやった。


その際、自然言語処理向けのいろんなRNN系のニューラルネットワーク(NN)を使ったので、各NNの精度を順にまとめてく。

ちなみに、
・不均衡データに対して損失関数でチューニングする方法

・大容量データ対策

もまとめた


f:id:trafalbad:20190326080327p:plain


目次
・使うデータセット
1.LSTMだけ
2.GRUだけ
3.GRU+dropout + recurrent_dropout
4.GRUベースの双方向RNN
5.1次元CNN
6.不均衡データの精度を損失関数(class-balanced-loss)で上げる方法
・最後に:大容量データ対策にfit_generater()
・追記:LSTM層(GRU層)の多層化で精度再検証


使うデータセット

使うデータは「IMDbデータセット」という映画に関するレビュー文章。

すでに文章はidベクトル化されてて、それのネガポジ判定の2値分類をした際の、NNごとの精度を見ていく。

入力形式は自然言語処理で使うembbedingレイヤーを使うため、ゼロパディングして、ミニバッチ化した。やり方は下記記事を参照。

trafalbad.hatenadiary.jp


IMDbデータセットの「訓練データ数、テストデータ数、共通するパラメータ条件.etc」は以下の通り。

・trainデータ:2000こ(ゼロパディング済み)

・testデータ:1000こ(ゼロパディング済み)

・epoch=5

・batch_size=128

・validation_split=0.2

・ネガポジ判定の2値分類


1.LSTMだけ

まず定番、LSTM。

from keras.datasets import imdb
from keras.preprocessing import sequence
import os
import math
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from keras.optimizers import SGD, RMSprop, Adam
from keras.callbacks import History, LearningRateScheduler, Callback
from keras import layers
from keras.models import Model, Sequential
from keras.layers import Dense, Input, Lambda, LSTM, GRU, Embedding


max_words=88585 # 単語のインデックスの数
model = Sequential()
model.add(Embedding(max_words, 32))
model.add(LSTM(32))
model.add(Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(x_train, y_train,
                    epochs=5, batch_size=128, validation_split=0.2, callbacks=callback)

# 精度検証
_, acc = model.evaluate(x_test, y_test, verbose=1)
print('\nTest accuracy: {0}'.format(acc))


精度は72%。

simple-RNNよりは確実にいい。


2.GRUだけ

max_words=88585 # 単語のインデックスの数
model = Sequential()
model.add(Embedding(max_words, 32))
model.add(GRU(32))
model.add(Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(x_train, y_train,
                    epochs=5, batch_size=128, validation_split=0.2, callbacks=callback)

精度は72.6%。

GRUはLSTMより計算量が少なく、速い。 LSTMとほぼ変わらないけど、計算量少ないからGRU使ってる人が多いのかな。



3.GRU+dropout + recurrent_dropout

今度は過学習防止(dropoutは偶発的な相関関係を破壊する効果がある)のため、dropoutとRNN専用dropoutの「recurrent_dropout」を設定。

max_words=88585 # 単語のインデックスの数
model = Sequential()
model.add(Embedding(max_words, 32))
model.add(GRU(32, dropout=0.1, recurrent_dropout=0.1,))
model.add(Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(x_train, y_train,
                    epochs=5, batch_size=128, validation_split=0.2, callbacks=callback)

精度は73.6%。

dropoutの最適値はいくつか知らないけど、とりあえず0.1あたりに設定。

データ数少ないから、精度にはほとんど反映されてないけど、過学習は確実に防げる。




4.GRUベースの双方向RNN

双方向RNNはBidirectional層を使う。

max_words=88585 # 単語のインデックスの数
model = Sequential()
model.add(Embedding(max_words, 32))
model.add(layers.Bidirectional(GRU(32)))
model.add(Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(x_train, y_train,
                    epochs=5, batch_size=128, validation_split=0.2, callbacks=callback)

精度78.2%。

LSTMやGRUより上がってる。ちなみにLSTMベースの双方向RNNも可能。

LSTMやGRUはベクトルを一方向から計算しないため、一方向の文章構造の特徴しか把握できない。

一方、双方向RNNは逆向きにもベクトルを計算するため、両方向の文章構造の特徴を把握できる。

NN系はベクトル構造による特徴把握が精度に大きく関わるので、逆向きでもベクトル構造の特徴が把握できれば、双方向RNNは普通のRNNより精度はいいことになる。

文章ならベクトルにしてしまえば、逆からでもベクトル構造の特徴に意味が含まれていることがほとんどなので、双方向RNNはより自然言語処理向けのRNNだといえる。



5.1次元CNN

max_words=88585
max_len=1629
model = Sequential()
model.add(layers.Embedding(max_words, 128, input_length=max_len))
model.add(layers.SeparableConv1D(32, 7, activation='relu'))
model.add(layers.MaxPooling1D(5))
model.add(layers.SeparableConv1D(32, 7, activation='relu'))
model.add(layers.GlobalMaxPooling1D())
model.add(layers.Dense(1))

model.compile(optimizer=RMSprop(lr=1e-4),
              loss='binary_crossentropy',
              metrics=['acc'])


精度は49%。

1次元CNNについてわかったこと

1次元CNNは自然言語処理にはあまり向いてない

testデータの2番目の次元をtrainデータと同じにしなければ、予測に使えないため、面倒(x_train shape: (2000, 1629)
x_test shape: (1000, 1629))

気温予測とかの時系列データには、LSTMとかと遜色ない精度出てる

最近では普通の畳み込み層(Conv1Dとか)よりも、dw畳み込み層(SeparableConv1Dとか)を使った方が、「計算量も高く、表現力も高い」。  

なのでCNNでは、普通の畳み込み層より、dw畳み込み層使うべし。


自然言語処理だとRNN系のニューラルネットより、精度低いけど計算量少ない

一次元CNNとRNNを連結させ、長いシーケンスデータを処理(CNNでより汎用的な特徴を抽出した後、それをRNNで処理)するやり方もある



過学習防止のための、L2正規化(& L1正規化 & L2/L1正規化)のkerasでのやり方

# L2正規化 & L1正規化 &  L2/L1正規化
keras.regularizers.l2(0.)
keras.regularizers.l1(0.)
keras.regularizers.l1_l2(l1=0.01, l2=0.01)

# 利用例(https://keras.io/ja/regularizers/)
from keras import regularizers
model.add(Dense(64, input_dim=64, kernel_regularizer=regularizers.l2(0.01),
                activity_regularizer=regularizers.l1(0.01)))


6.不均衡データの精度を損失関数(class-balanced-loss)で上げる方法

今回はほぼ均衡データだけど、不均衡データ(クラス毎のデータ数に偏りがあるデータ)に対してのチューニング方法として、"class-balanced-loss"をがあるので、やってみる。

"class-balanced-loss"はデータ数が少ないクラスの損失関数に重み付けして、データ数が少ないクラスの精度を上げる方法。

やり方

# https://datascience.stackexchange.com/questions/13490/how-to-set-class-weights-for-imbalanced-classes-in-keras (参考記事)

# ラベルのarrayを作成(y_trainはラベル:0 label=506、1 label =494)
np.unique(y_train)
# >>> array([0, 1])

from sklearn.utils import class_weight

class_weights = class_weight.compute_class_weight('balanced', np.unique(y_train),  y_train)

class_weights
# >>> array([1.01071356, 0.98951118])


# model.fit()の引数のclass_weightに格納.
history = model.fit(x_train, y_train,
                    epochs=5, batch_size=128, validation_split=0.2, callbacks=callback, class_weight=class_weights)

本家記事のやり方だと、「現実のデータ数≠リアルのデータ数」として扱っているため、上のコードは本来の"class-balanced-loss"とは少し違う。

けど、チューニング方法としては大体同じ。



結果的に精度は

・双方向RNN => 78.2%

・LSTM => 72%

・GRU => 72.6%

・GRU+dropout + recurrent_dropout => 73.6%

・1次元CNN => 49%


=> 双方向RNN > GRU+dropout + recurrent_dropout > GRU ≒ LSTM > 1次元CNN

結論、「双方向RNNが一番精度が高く、自然言語処理に向いてる」。


ちなみに、データ量が多くてGPUによる分散処理をする場合、one-hotベクトルでNNに入力するとか、パディングなしでミニバッチ化せずにNNに入力すると、GPUの分散処理での学習のとき、文章の依存関係が壊れて精度が悪くなるらしい。

なので、embbedingレイヤーに入るとき、word embbedingでid表現かつゼロパディングしてミニバッチ化するのが、自然言語処理の深層学習テクとしては一般的な手法。


最後に、tree-RNNとか、attentionで拡張したRNNとかは翻訳、応答、文章生成あたりのガチ自然言語処理のタスクで使われてる事例があるけど、kerasで実装してる例がなかったので、今回は割愛した。


最後に:大容量データ対策にfit_generater()

大容量データ対策として、学習時に.fit()の代わりに、.fit_generater()を使う。例えば、画像データをよみこませるとき、一定サイズ毎に区切りながらデータを読み込せて、メモリ問題を解決できる。


.fit_generater()には、Generatorオブジェクト(バッチ単位にデータを提供する仕組みを実装したもの)を渡す(参考:Kerasで大容量データをModel.fit_generatorを使って学習する



自然言語処理でも使ってる事例があるので、データ量がでかいときにオススメ



追記:LSTM層(GRU層)の多層化で精度再検証
GRUベースの双方向RNNと、ただのLSTMを2層に(多層化)して、精度を再検証してみた。
なんか専門用語だとLSTM層の多層化のことを「層のスタック」とか言ってるらしい。

一層目の出力は完全なシーケンスデータを返さなきゃならないので、「return_sequences=True」を指定。

# 2層のLSTM(+ dropout + recurrent_dropout)
max_words=88585 # 単語のインデックスの数
model = Sequential()
model.add(Embedding(max_words, 32))
model.add(LSTM(32, return_sequences=True))
model.add(LSTM(32, dropout=0.1, recurrent_dropout=0.5))
model.add(Dense(1, activation='sigmoid'))


# 2層のGRUベースの双方向RNN( + dropout + recurrent_dropout)
model = Sequential()
model.add(Embedding(max_words, 32))
model.add(layers.Bidirectional(GRU(32, return_sequences=True)))
model.add(layers.Bidirectional(GRU(32, dropout=0.1, recurrent_dropout=0.5)))
model.add(Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(x_train, y_train, epochs=5, batch_size=128, validation_split=0.2, callbacks=callback)

精度は

・2層のLSTM(前の精度72%)
74%

・2層のGRUベース双方向RNN(前の精度78.2%)
78.6%

わかったこと

・多層化はdropoutを使ったとき、性能がボトルネックになるから行うことが多い

自然言語処理では多層でも隠れ層の値(今回は32)は全部の層で同じ

自然言語処理で2層目以降にrelu関数使ったら勾配消失した。(時系列データでは多層でも2層目以降に、relu関数使ってるケースあり。)

結果的に、多層化したら計算量上がるし、精度も上がる。

今回はデータセットが少量だから結果にほとんど反映されなかったけど、わかったことは「LSTMモデルは、精度が高いモデルは多層化してるし、多層化すればたいてい精度は上がる」ということ。

自然言語処理タスクにLSTM層の多層化はdropoutと併用すると、かなり効果あるようだ。


参考記事Kerasで実装するSeq2Seq_その3_多層LSTMとBidirectional