webとモバイルアプリの逆転

webアプリとモバイルアプリの開発に関する話や本・ゲームなどの趣味の話を雑多にしていきたい

Kerasで構築したモデルをTensorflow.jsで動かす際にハマったこと

Tensorflow.jsを使いたくて仕方なくなって試しに動かしてみたところ、意外と動かすのに苦労したのでやった内容をメモ。
チュートリアルを動かしたときには、簡単に動かせる〜〜って思ってたのに自分のModelで動かそうとしたら自分の理解不足で結構苦労してしまいました・・・

もうすこし良い方法があったらぜひともご教授いただきたく

はまったポイント

今回はまって時間を消費したのは以下の点でした。

  1. KerasのモデルをTensorflow.jsで使える形式に変換
  2. Tensorflow.jsでモデルに入力するデータの作成

環境

  • Typescript
  • Tensorflow.js
  • tensorflowjs(Python) 1.2.6

KerasでModelを構築

CNNによるクラス分類のモデルを使いました。 昔ソシャゲ画像とイラスト画像を分類するモデル(参考: CNNを活用しTwitterの画像欄からソシャゲと写真を避けてイラストの抽出を行う - Qiita) でやった内容をKerasに書き直しました。
やった内容自体は上の記事と同じなのでこの記事では省略。
モデルの保存形式は".h5"形式で保存しました。

Tensorflow.jsで使用できるモデルに変換

この工程でかなりはまりました…
内容としては、以下の記事のチュートリアルを実施して変換を実施しました。( Importing a Keras model into TensorFlow.js  |  TensorFlow)

ここのPythonライブラリのバージョンを最新(9.23段階だと1.2.9)にするとAttributeErrorが発生してうまく動かない問題がありました。 調べてみると、1.2.9で発生したバグらしく同じような症状が報告されていました。('EnumTypeWrapper' object has no attribute 'DT_FLOAT' while importing in Python 3 · Issue #2014 · tensorflow/tfjs · GitHub

以下のように、1.26をインストールするように指定すると問題なく変換することができました。

$ pip install tensorflowjs==1.2.6

Issueのページをみていると、この問題を解決したらしき修正がすでにマージされていたので次のバージョンではこの問題は解決しそうです。(Fix access to proto enum fields (#2040) · tensorflow/tfjs@dd5d9ed · GitHub)

Tensorflow.jsで推論

最後に画像データをモデルで使用可能な形式に整形して、モデルにいれることで推論します。 どういうデータにすればよいのかを整形するのに時間がかかってしまいました。 Tensorflowのデータ形式に触れたのが初めてだったせいもある気がするので、なれた人ならそんなにつまらないのかな?と思います。

画像データ(CanvasやImageDataなど)を入力できるデータに変換するのに tf.browser.fromPixelsを使用しました。
ここで、モデルに入力するためには画像サイズを合わせる必要があり、 tf.image.resizeBilinearという関数を利用してサイズを変更する必要があります。 自分はこの関数の存在に気づかなくてここで時間をくってしまいました。
データの次元数があっていないときには、expandDimsを使って合わせる必要もありました。

一応、動作確認のために書いたコードをはっておきます。ただ単体では動きません。

import * as tf from '@tensorflow/tfjs';
function loadImg(id: string): tf.Tensor<tf.Rank> | undefined {
    let img = document.getElementById(id) as HTMLImageElement;
    if( !img ) {
        return;
    }
    const resizeImg = tf.image.resizeBilinear(tf.browser.fromPixels(img,3),[224,224]);
    return resizeImg.expandDims(0).cast('float32').div(tf.scalar(255));
}

tf.loadLayersModel('../model/model.json').then(model => {    
// htmlにあらかじめimgを追加しておいてから動作させている
    const batched = loadImg('img');

    if( !!batched ) {
        console.log('result');
        (model.predict(batched) as tf.Tensor<tf.Rank>).print();
});

やってみて

最先端のモデルとかをブラウザで動作させたい!!でもどんな値いれるのかはあんまり知らん!って感じで始めたのですが、モデルの変換後にどんな値を入れるのは自分で実装する必要があるのでやっぱりちゃんと理解するのは重要だなぁって思いました。 せっかく動かせるようになったので、何かのアプリに組み込んでみようかなって思ってます。

モデリング初心者がBlender2.8で3Dモデルを作るのに覚えてよかった操作

前からやってみたいと思いつつそこまで優先度高たくなかったので後回しにした3Dモデリングだったのですが、Blenderの2.8が公式リリースされたのと3Dプリンターが自分で使えるようになったことから始めるのに最適だと思って始めました。
初めてやってみて思ったことはショートカットを覚えることが作業時間を短縮するためにめちゃめちゃ重要だ…と思ったので、この記事ではモデリングを持ったくやったことない人が試しにやってみたいと思った時に、覚えておくとよいと思った操作をメモしました。
使っていない操作がまだ無数にあるので、今後ももっと覚えていく必要がありそうです。 これ覚えておいた方がもっと便利だよ!って操作があったらぜひとも教えてもらえるとありがたいです。 スカルプトモードってやつも便利との話を聞いたので次回は試してみたいところです。

ちなみにモデリングした結果はこんな感じになりました。

f:id:s-haya:20190907152306p:plain
モデリング結果

モデルの元ネタはメイドインアビス7巻に登場した壺に入ってるミーティです。

f:id:s-haya:20190907155213p:plain
壺ミーティ(メイドインアビス7巻)

そこそこちゃんとした感じになったのかな?と思っていたのですが、ミーティのふわふわしている感じは表現できていなかったな…と反省。
とぎれとぎれに作業していたので正確にはわからないのですが、かかった時間は10時間?くらいだと思います。これはちょっと自信がないです。 ショートカット覚えだしたらちょっと操作が早くなっていった気がします。

環境

  • windows 10
  • Blender 2.8
  • マウス
    ホイール付きのマウスは必須です。めちゃくちゃ使いました。 というのも視点の回転や平行移動のショートカットがマウスホイールだったのでないとかなり不便だと思います。

  • テンキー付きキーボード
    視点を正面、真上などに切り替えるショートカットがテンキーだったのでテンキーはかなり重要でした。

設定したこと

  • 日本語化
    日本語にしたけども基本的にモデリング初心者ゆえによくわからない用語も多く、結果的にググることが多かったです

必須だった操作

  • 編集モード・オブジェクトモードの切り替え : TAB 選択したオブジェクトに対して変形などをしていく、複数のオブジェクトを選択して移動をするモードを切り替えて作業をする必要があり、このショートカットは押しまくりました

  • ビューの拡大・縮小: マウスホイール押し込み+マウス移動

  • ビューの平行移動: マウスホイール押し込み+SHIFT+マウス移動 3Dモデルをいろんな方向からみる際に使いました。 3Dなので多面的に見ないと自分が意図したとおりに配置されているか確認できないので必ず使用する操作だと思います。 ショートカットを知らなかった時にはいちいちビューをクリックして確かめていたのですが、かなりめんどくさかったのでこれは絶対に知っておいた方がよいと感じました。

便利だったショートカット

  • モデルの拡大・縮小: s
  • 辺の拡張: e
  • 辺から面の作成: f
  • 辺・t頂点・モデルの削除など: x
  • モデルの移動: g 上記の操作を使用しながらモデルをかちゃかちゃいじってました。 モデルがよくわかんなくなった時にはxで削除してfで張りなおすみたいな、効率悪そうな操作もやってました。。 この辺はもっとよい操作を見つけていきたいところ

  • オブジェクトなどの追加: Shift+A

  • ループ選択: Shift + Alt + 右クリック
  • 選択範囲の拡大縮小: Ctrl + [+], -
  • オブジェクトの分割 P
  • メッシュ ノーマル Alt+N メッシュの裏返しなどに使う

よく使ったビューの操作

  • 点選択: 1(テンキーではない)
  • 辺選択: 2(テンキーではない)
  • 面選択 : 3(テンキーではない) 1点を削除したい、面を動かしたいといった操作を切り替える際に選択していました。

参考にしたサイト

Youtubeの動画をよく参考にしました。 動きを見れる分かなり参考になるので、今後もわかんないことがあったらYoutubeを見ようかなって思ってます。

エッセンシャル思考

エッセンシャル思考を読んだので読んだ感想をメモ

エッセンシャル思考とは何か、どうすれば実践できるのかが書かれた本。 エッセンシャル思考って聞いたことあるけどどんなもの?って思って買ってみた。

エッセンシャル思考を一言でいうと本質とはなにかを考えるための思考です。 すべての物事をなんでもうまくやるということはできない、だから限られたことに集中してしっかり成果を出していこう!っていう内容でした。

特に個人的にできていないので勉強になったところは、情報をフィルタリングすることの重要さを説明している章でした。 すべての情報を仕入れるのではなく、重要な内容を理解することに集中しようと書かれていました。
自分はどんな話でも漏らさず聞いてすべて理解しようとした結果、大事な部分を理解できないということがあるので、 この内容の重要さを特に噛み締めました。

この本で一番重要なのは「本質」とは何かってことを考えることだと思いました。 これは誰もが同じものではなく、自分にとって何が楽しいか、何が大事なことなのかによるものなので自分で考えるしかないものです。 本を読むことで本質とはなにか、どうすればそれに集中できるのかを学べる内容だったのでよかったです。

自分にとっての本質は、ソフトウェアエンジニアとして開発を続けていくということだな…と思ったので、それに集中することを実践していきたいなと思いました。

本自体は、大事なことを見極める・そのためにはどうすればいいのかということにフォーカスして書かれていた本であったのでとても読みやすかったのでおすすめです。

TypeScriptで配列が含まれているUnion型の変数に対してType Guardを用いて変数を配列として扱う方法

TL;DR

(追記) Array.isArrayを使えば問題なさそう

Union型に対して配列としての処理を記述したい場合、ユーザー定義のTypeGuardをすればよかった。
ユーザー定義のやり方は、返り値の型を<引数> is <配列>とし求めている型ならtrueが返るように関数を記述することで目的を達成できた。

詳細

TypeScriptで配列が含まれているユニオン型に対して配列操作を行おうとした際、 コンパイラが以下のエラーを吐いてしまいうまく動かすことができませんでした。

Property 'map' does not exist on type 'string | string[]'.  Property 'map' does not exist on type 'string'.

原因としてはユニオン型は複数の型を複合させた型であるため 配列のみに存在するメソッドを使おうとしても他のメソッドでは存在しないことでした。 この問題を解決するための方法を調べたところ、Type Guardで変数を配列として扱うという方法を見つけたのでその方法をメモします。

まず、Type Guardのやり方を調べたところ以下の5つの方法がありました。(参考: 型ガード - TypeScript Deep Dive 日本語版

  • typeof
  • instanceof
  • in
  • リテラル型のType Guard
  • ユーザー定義のType Guard

ただ、上記の4つでは配列として扱うことができませんでした。
typeofではobjectとして認識されてしまい目的を達成できませんでした。
instanceofはArrayとして認識することまではできたのですが、配列の中身の型をうまく認識できなかったので断念しました。 もしかしたらうまくできるやり方あるのかもしれないです。
inはオブジェクト内のプロパティの存在を安全にチェックするものなので配列の認識には使えませんでした。 リテラル型のType Guardはリテラル型の判定なのでもちろん使えません。

最後のユーザー定義のTypeGuardを使うことで配列をうまく認識することができました。
やり方は簡単で、関数の返り値を<引数> is <配列>として、求めている型だったらtrueが返すことで配列の認識にすることができました。 今回、stringの配列を認識するために書いた定義は以下のようになりました。

function isStringArray(arg: any): arg is string[] {
    return typeof arg[0] === "string";
}

TypeScriptの型はまだまだしらないことがあるのでもっとDeepDiveしていきたいなと思いました。

Typescriptからトランスパイルして生成した関数をGCPのCloud Functionsにローカルからデプロイする際に関数が見つからないとエラーが出されたときに対処したこと

Typescriptで記述した関数をトランスパイルして用いてCloud Functionsで動作させようとした際に、 対象の関数が見つからず以下のようなエラーが吐かれることがありました。

OperationError: code=3, message=Function failed on loading user code. Error message: File index.js that is expected to define function doesn't exist.

ローカルエミュレーターでは問題なく動いていたので関数は存在しているのですが、見つからないと言われてしまった状況です。 そのとき確認したことをメモ。

原因

原因としては、.gcloudignoreの設定のせいでindex.jsがアップロードされなかったためでした。 なぜこうなってしまったのかとその対処を記述していきたいと思う。

対処

gcloudコマンドからデプロイしようとした際に.gcloudignoreが自動生成されました。 そのファイルには、デフォルトで.gitと.gitignore、node_modulesをデプロイ対象から外すように設定されていました。 通常なら問題なさそうなのですが、今回はTypescriptで記述していたためビルドで生成されるjsファイルも.gitignoreに記載していました。 そのため、jsファイルがデプロイされず、対象の関数が見つからないというエラーが発生していました。

対処としては単純に、今回は以下の記述を.gcloudignoreに加えることでビルドで生成されるjsファイルもデプロイできるようにしました。

!include:.gitignore

jsファイルがアップロードできたことで、Cloud Functionsで関数を動かすことが無事にできました。 自動生成された設定ファイルが何をしているのかをちゃんと理解しておくことが重要だな…と反省しました。

ちなみに今回は.gitignoreの設定が反映されてしまったためが原因でしたが、他にもpackage.jsonの設定が間違っているせいで関数がみつからないこともあるみたいです。 同じようなエラーが出た際にはそちらの設定も見てみると良いみたいです。

Clean Architectureの11章まで読んでの感想

Clean Architectureを完全に理解するために前回の続きから思ったことや気づいたことを引き続き書いていきたい。 ただ、1章ごとに書いていくのやたら時間がかかることに気づいたから次からもう少しまとめて行きたいと思う。

第5章 オブジェクト指向プログラミング

オブジェクト指向プログラミングとは何かを説明せよ!!っていざ言われるとあまりうまく説明できない…と気づいてしまったので、この章をじっくりと読んでみる。 「オブジェクト指向とは何か?」について、この本では「ポリモーフィズムを使用することで、システムにあるすべてのソースコードの依存関係を絶対的に制御できる力」と書かれていた。 制御できることで上位のモジュールと下位のモジュールを独立することができ、個別な開発を可能にすることやソースコード変更した際のデプロイ範囲の独立が可能になるとのこと。

文中にポリモーフィズムはパワーだ!書かれていたけど、確かに考えてみるととこの特性はかなり強力だなと自分の中で納得。 独立した開発が可能になれば並列に開発することができるため開発速度も向上するし、プラグインとして独立すれば別のプロジェクトでも使いまわすことができて生産性が向上するでとても良い。 より細かい原則はこの後出てくると思うので、そこで改めて考えていきたいところ。

アーキテクチャを考える上では何がどのような役割を持つのか、データの制御はどうするのかを考える必要があるためオブジェクト指向の理解はアーキテクチャを学ぶ上で身に着ける必要があるなとこの章を読んで改めて思った。

第6章 関数型プログラミング

みんな大好き関数型!!

関数型言語の変数は変化しない」と書かれており、これはアーキテクトの観点で重要と書かれている。 確かに設計をするうえで堅牢にしたいなら、変更できなくしてしまえば並列処理でも全く問題もないというのも納得。 あと変化できなくなると変な場所で間違えて代入するということもなくなるので、自分のポカも減らせるのでうれしい。

不変性は最近のコーディングの規約でもできるだけ不変な変数で定義することは一般的にやられているのでなじみ深くなってるなと思ったり(KotlinのvalやTypescriptのconstなど)。

この章では最後にこれまであげた3つのプログラミングパラダイムについて改めてまとめられていた。ここでもう一度主張されていたのは、ソフトウェアというのは、順次・選択・反復、そして間接参照で構成されていること。 そして、プログラミングパラダイムは上記を実現するための方法を制限して、安全にソフトウェアを構築するための方法であると主張されていた。 この事実を心に刻みつつ、次の設計の原則についてやっていく。

第3部 設計の原則

SOLID原則についての導入。データ構造や関数をどのようなクラスにまとめればよいか、クラスとの接続をどのように考えればいいかを教えてくれる原則をまとめたもの。 ここの原則の内容を学んだことはあったが、アーキテクチャ的にどういう意味をもつのかといった体系的な知識になっていなかったのでちょうどいい機会だということで以降の章を読んでいく。

第7章 SRP:単一責任の原則

単一責任とは何かということを具体的な例を交えつつ説明する章。 単一責任とは、モジュールはたったひとつのアクターにたいして責任を負うように設計することを指している。 アクターとは変更を望む人達をまとめた単位(例だと、経理部門・人事部門などの1部門がアクター)。 この章を読んで、クリーンなソフトウェアを作る際にはだれが使うのかを意識することが重要であることを再度認識した。 確かに、一つのモジュールにたいして変更を要請する人が複数存在すると、片方の変更がもう片方に影響を与えてしまう可能性が高まってしまって良くないなと納得。

第8章 OCP:オープン・クローズドの原則

思考実験を交えつつオープンクローズドの原則について説明する章。
この原則を達成するために重要なこととして、コンポーネントを適切に分割することとコンポーネントを階層構造にまとめることを挙げていた。

この原則でも適切なクラス分割を行うことを推奨している内容だった。ただ、若干前の章とは分割のためのモチベーションが異なっていた。 SRPではアクターから変更の湯棒があったとしても、他のモジュールに影響を与えないように責務を単一にすることをモチベーションにしていたが、 OCPでは、システムを容易に拡張できるようにするために、影響範囲を限定するために分割することがモチベーションになっていた。 モチベーションは違っていても、どちらも同じように変更による影響範囲を考えて分割しましょうという結論になるのは面白いな。

そのほかにもインターフェースを分離することで、他のクラスの情報を隠蔽し依存しすぎないようにすることで拡張に強くする方法も挙げられていた。詳しい説明はまた別の章で行われるとのことだった。

第9章 LSP:リスコフの置換原則

あるクラスから派生したクラスはもとのクラスと置き換えてもソフトウェアの振る舞いが変わらないときに満たされる法則。 この法則に違反したクラスを作ってしまいシステムに組み込んでしまうと、違反しているクラスをif文で分岐させえて別の処理を記述する必要が出てきてしまい振る舞いに制限が出てしまうとのこと。 クラスだけでなくRESTなどのあらゆるインターフェースで考えることができるため、色んな場所で考慮したほうが良さそうな法則。 特に派生が起きそうなクラスを設計する際にはどういうクラスが使われる可能性があるか?を考慮してアーキを設計することが大切な気がする。

第10章 ISP:インターフェース分離の原則

インターフェースを分離して使ってないメソッドに依存することをさけるためにインターフェースを分離しようと説明している。 ちゃんと分離しておかないと、使っていないメソッドにすら依存してしまうため、依存しているデータベースをデプロイするたびにシステムをすべて再デプロイするはめになってしまうという例が書かれており納得。 コンポーネントの凝集性でさらに細かい話があるとのこと。

第11章 DIP:依存関係逆転の原則

ソースコードの依存関係が具象ではなく抽象だけを参照していることを示した原則。 ここで書かれていた面白いと思ったことは、すべてを抽象に依存させるのではなく、変化しやすい具象要素に対してこの原則を適用するということだった。 StringなどのOSに近いものは非常に安定していて壊れることがないため、抽象化しなくてもよいとのこと(というかできない)。 DIPをどこまで適用する?ということを考えるときに、そのクラスが安定しているかどうか?という判断基準は意識していきたいと思った。

第Ⅲ部では、全体的にクラスの分離に関する話が中心的になっていた。どこまでクラスで責任をもつのか、どのようにクラスを分離すればよいかなど。 次の部からは適切に分離・結合したクラスをどのように扱っていくのか?ということを扱っていくのでまた読んでいきたい。

ブログでやりたいこと

放っておくと全く書かなくなってしまいそうだから、ブログでどんなことを書きたいのか?が自分でわからなくならないために、自分が何を考えているのかを記しておこうかと

  1. 自分がやったことの記録
  2. 思考内容を文章にすること
  3. アウトプットの習慣化

上記3つの理由があって、それぞれについて説明をしたい。

自分がやったことの記録

自分がコーディングしてる中で悩んだり詰まったりした箇所を忘れてしまい、 その都度、「あれ・・・どんなコードで解決したっけ・・・?」と毎回ソースコードを見て何をしているのかを読みとくということをしていました。

でも、毎回読み解いていくことは結構大変だったので自分がどんな対処をしたかを後から見直せるように記録しておきたいと思ったのが一つ目の理由です。 自分メモ的な感じでやりたくて、つらつらと書きだしていく感じでやっていきたいと思ってます。

思考内容を文章にすること

自分が何を考えていたのかを文章にすることをこれまであまりやってこなかったため、自分が何を考えていたのか・なんでその行動をとったのかを文で説明する能力を鍛えたいというのが2つ目の理由です。

考えを文章にすることやわかりやすい文章を書くためには技術が必要だと思っていて、技術を向上するためにブログを書いて練習したいと思っています。 技術ポエム的なのもブログならやりやすそうだから、やっていきたいなと。

アウトプットの習慣化

アウトプット系に関する本を読んでみて、アウトプットが大事ならちゃんとやる習慣をつける必要性がありそうだな…と思ったのが3つ目の理由です。
アウトプットをすることで得たい一番の効果としては、知識の定着をしっかりとしたいというのがあります。

こんな感じのことを意識しながらゆるゆると書いていくことを続けていきたい。