ユニファ開発者ブログ

ユニファ株式会社システム開発部メンバーによるブログです。

Review of CTO Night & Day 2019 Fall

 みなさんこんにちは。ユニファでCTOをしてます赤沼です。先日 AWS さん主催の招待制オフサイトカンファレンス CTO Night & Day に参加させていただきました。昨年初めて参加させていただいて今回で2回目だったのですが、名だたる企業の大先輩CTOや、同じような規模・フェーズのスタートアップのCTOまで100人以上が参加していて、セッションの内容だけでなくネットワーキングやディスカッションの時間も多く、とても学びの多い2日間でした。全ての内容について書くと長くなりすぎてしまうので、その中で特に学びのあったことなどについて書きたいと思います。

f:id:akanuma-hiroaki:20191013170253j:plain:w300 f:id:akanuma-hiroaki:20191013170437j:plain:w300

自分なりのテーマ

 今回参加するにあたって自分なりに特に何か気づきを得たいと思っていたこととして、「CTOとしての経営への関わり方」をテーマとしていました。もともと技術畑でエンジニアをしていた自分としては、取締役として経営に関わっていく中で、技術やプロダクト、ビジネス、ファイナンスなどにおいて、どのようなバランスでどのように価値を出していくかを悩んでいるところでもあり、セッションやディスカッションの中で他社の CTO の方々がどのように取り組まれているのかを参考にさせていただき、自分なりの気づきを得たいと思っていました。

Unconference: 経営者としてCTOがすべきこと

 そんなところにうってつけだったのが、今回アンカンファレンスのテーマの一つであった「経営者としてCTOがすべきこと」。6〜7人で一つのグループを作りディスカッションするという形で、私もこのディスカッションに参加させていただきました。その中でたまたま VOYAGE GROUP の CTO の小賀さんとご一緒させていただいたのですが、もっとも考えさせられたのが冒頭で小賀さんが投げかけられた「みなさん経営してますか?」という問いでした。経営にどう関わるかという視点はあったものの、そもそも経営とはなんなのかという点は深く突き詰めたことはなかったので、これが自分なりにでも定まっていないとどう関わっていくかもはっきりしてこないなと。また、その後のディスカッションの中でも小賀さんからお話いただいた、ビジネスやファイナンスにおいてCTOがどのように関わっていくかという点でも、やはりまず経営をする立場という点を考えれば、技術的な内容だけでなく、ビジネスやファイナンスについても勉強しておく必要があり、他の CxO に技術的な内容を話していく上でも、共通言語としてビジネスやファイナンスを学んだ上で、理解してもらえる形で話していく必要があるという点も自分として今後強く意識していこうと思った内容でした。

CTO 1,000本ノック!

 2日間のプログラムの最後として行われたこのセッションは、Keynote等で登壇した CTO(メルカリ 名村さん、Sansan 藤倉さん、 グリー 藤本さん、DMM 松本さん、SORACOM 安川さん、カーディナル 安武さん)が会場からの sli.do での質問にどんどん答えていくというものでした。テーマは特に限定されていないので様々な質問がありましたが、その中で、どんな質問だったかは失念しましたが、安武さんが回答されていた「経営者であれば経営に必要なことはなんでもやって当然」というような内容はとても印象に残るものでした。CTOとしてはどうしても技術的な領域のみに関わる意識になってしまいがちなのですが、CTOである以前に経営陣の1人であると考えれば、領域にとらわれることなく、必要なことはなんでもやって当然と改めて考え直させられるものでした。

自分のセッションのことも少し

 CTO Dojo というコーナーでは事前に登壇者の募集があり、私も応募させていただきまして、「開発チーム 1人 -> 40人 になるまでにやってきたこと」というタイトルで、私が1人目の正社員エンジニアとしてユニファに入社して、現在40名ほどの開発チームになるまでに取り組んできたことについてお話させていただきました。オーディエンスもみなさんCTOということで、なかなか緊張する場ではありましたが、多くの方に参加いただき、共感いただいた方も多かったようです。その後の懇親会等でもお声がけいただくこともあり、やっぱり発表する場があるなら発表しておくものだなと思った次第です。

prezi.com

 セッションの中でもご紹介させていただいたユニファの Podcast や Meetup 等、ゲスト参加や合同開催に興味のある方はぜひお声がけいただければと思います。

podcast.unifa-e.com

unifa.connpass.com

中身の濃い2日間

 ここまでに書いたセッション以外にも、オムロンさんにお邪魔してSINIC理論のワークショップに参加させていただいたり、グローバルスタートアップのCxOの Keynote があったり、1日目の夜のパーティーはなかなかすごかったりと中身の濃い二日間でした。また、私の今回のテーマに関するところでも、公開CTOメンタリングでの DMM 松本さんや CTO Dojo での freee 横路さんのお話を聞いていて、ビジネスについてもしっかり数字で語れるという点は自分にも必要だなと思った点です。

 それと改めて、今回お話させていただいた方やセッションに参加いただいた方、運営いただいたAWSの皆さま、ありがとうございました。また来年お声がけいただけましたら、ぜひ参加させていただき、今回と比べて自分がアップデートできていると思えるように取り組んで行きたいと思います。

f:id:akanuma-hiroaki:20191013170813j:plain:w500

f:id:akanuma-hiroaki:20191013171047j:plain:w500

f:id:akanuma-hiroaki:20191013171306j:plain:w500

仲間も募集中

 ユニファでは開発メンバーも募集してますのでご興味ある方はぜひどうぞ!!

【Dev】QAエンジニア - ユニファ株式会社

【Dev】Rubyエンジニア - ユニファ株式会社

【Dev】アプリデザイナー - ユニファ株式会社

【Dev】ディレクター - ユニファ株式会社

【Dev】社内インフラエンジニア - ユニファ株式会社

MAG part II

By Matthew Millar R&D Scientist at ユニファ

Purpose:

This is part II of the MAG (Multi-Model Attribute Generator) paper I am working on. You can see part 1 here
Multi-Model Attribute Generator - ユニファ開発者ブログ
This post will focus on defining what clothing the lower half of a person is wearing. This will not look at color right now as that will follow in the next few posts. This model will allow for the classification of three different clothing types; skirts/dresses, shorts, and pants.

Processing Images:

So, from my previous post, I was only getting around 56% accuracy which is ok but not good enough. I altered my code to use a fine turned Xception model trained on the Market1501 dataset. I then used this as the base feature extractor which gave very good results in Keras. My experiments showed that the Resent50 did not produce as good results compared to Xception pre-trained models for this dataset. The data argumentation consists of rotation, cropping, vertical and horizontal shifts, and horizontal flipping. I also added a preprocessing script into the data augmentation which processes each image using the Xception preprocessing input which greatly helps in the accuracy of the model as well as keeping the handling of input consistent between this model and the base model as the Xception preprocessing was performed there as well. This aids in keeping the handling of input consistent between models and limits errors that could occur due to inconsistent preprocessing.

Clothing Makes the Man:

The first step is to separate the images into their classes for Keras to use in the data generator. This will consist of three classes, Dress/skirts, shorts, and pants.
The next step is to create the base feature extractor by importing the pre-trained model and creating a new base model using Keras Function API.

base_model = load_model('pre_trained_model.ckpt')
base_extractor = Model(inputs=base_model.input, outputs=base_model.get_layer('glb_avg_pool').output)
for layer in base_extractor.layers:
    layer.trainable = True

Note the output should be the last layer before the Softmax Fully connected layer. This will give you a feature vector over a prediction.
This will prime the base model to be used for the feature extractor. Remember you want to set each layer to trainable to allow for the base model to be retrained for the specific task. The next step is to actually build out the new model for classification.

def build():
    img = Input(shape=(224,224,3))
    # Get the base of the image
    x = base_extractor(img)    
    x = Dense(2048, activation='relu')(x)
    x = Dropout(0.2)(x)
    x = Dense(2048, activation='relu')(x)
    x = Dropout(0.2)(x)
    x = BatchNormalization()(x)
    print(x.shape)
    pred = Dense(3, activation='sigmoid')(x)
    return Model(inputs = img, outputs = pred)

Seeing that we are only looking at the lower portion of the body we will need to crop the image using a custom image generator.

# Custom cropping method for preprocessing
def crop_lower_part(img):
    # xception preprocessing an image should be called in the datagen not here in the prefrocess function
    y,x,_ = img.shape
    startx = 0
    starty = y//2
    return img[starty:y,startx:startx+x]

def crop_generator(batches):
    while True:
        batch_x, batch_y = next(batches)
        batch_crops = np.zeros((batch_x.shape[0], 112, 224, 3))
        for i in range(batch_x.shape[0]):
            batch_crops[i] = crop_lower_part(batch_x[i])
        yield (batch_crops, batch_y)

This basically cuts the image in half (the lower portion only) and create new images and send the batch to the model when needed.

Binary over Categorical loss:

We will be using binary cross-entropy over the categorical version for multi-label classification. This can be confusing as most every other model out there uses a categorical version. However, this works by treating each output label as an independent Bernoulli distribution which gives greater accuracy over the traditional approach (Hazewinkel, 2001). This will allow for each output node to be singularly penalized for the wrong answer. Which should in return give better more accurate results overall.
While categorical_crossentropy was getting 75% was decent results, by using binary_crossentropy over categorical_crossentropy, the accuracy increased by 5%.

f:id:unifa_tech:20191001152648p:plain
binary_crossentropy Accuracy
f:id:unifa_tech:20191001152716p:plain
binary_crossentropy Loss

We are still overfitting after the 5 epoch, but this might be managed by cleaning the dataset by adding more samples as well as making a clear definition between shorts and pants as some samples of shorts look very close to pants. For example, some men's shorts are very long and some women's pants are higher which will look the same to the model. So this might be a battle between high water pants and long shorts.

HighWater Pants Long Shorts
f:id:unifa_tech:20191004093857j:plain f:id:unifa_tech:20191004093954j:plain

Conclusion:

Keras score and accuracy for the model look pretty good.
So for target T and network output O, the binary_crossentropy is:

f(T,O) = -(T*log(O) + (1-T)*log(1-O) )

And this model's score and accuracy are:

[0.19746318459510803, 0.8602761030197144]

The score is the evaluation of the loss for a given input. and the accuracy is how accurate the model is for a given input. The lower the score the better and the higher the accuracy the better.
The final evaluation of the results came back pretty decent. The accuracy of the model is about 86% for the evaluation dataset.
We saw some significant improvement in accuracy by using a pre-trained fine-tuned model that works well with Keras. The model itself is not that complex to gain a good deal of accuracy. The most interesting change would be using a binary cross-entropy over a categorical loss. This gave a little more than a 10% increase in accuracy over using a more traditional approach for multiple label classification.

Confusion Matrix
[[249  15  93]
 [ 20 372  11]
 [ 48  70 353]]
Classification Report
              precision    recall  f1-score   support

      Shorts       0.79      0.70      0.74       357
       Dress       0.81      0.92      0.87       403
       Pants       0.77      0.75      0.76       471

    accuracy                           0.79      1231
   macro avg       0.79      0.79      0.79      1231
weighted avg       0.79      0.79      0.79      1231

f:id:unifa_tech:20191004094331p:plain
As you can see from the confusion matrix, the result looks pretty decent. Possible next moves may be to test out different network architectures, look at different labels, add more examples from other datasets, and look at using different losses and optimizers to aid in the training. The overall accuracy of the testing data was 80% so there is some room to improve, but the results are much better than the previous post.
The confusion matrix does confirm the issue with the pants and shorts as there are 93 misclassifications for pants and shorts. If feel that the majority of the classification errors may come from the data mainly as it is subjective as to what a pair of shorts are and what are short pants. To overcome this issue, a better data separation technique should be made to be more strict as to what should and should not be classified as pants and shorts.

References:

Hazewinkel, Michiel, ed. (2001) [1994], "Binomial distribution", Encyclopedia of Mathematics, Springer Science+Business Media B.V. / Kluwer Academic Publishers, ISBN 978-1-55608-010-4

睡眠✕IoT! NeuroSpace & UniFa Engineer Meetup!

こんにちは。エンジニアの田渕です。
暑かった夏もようやく終わりが見えてきて、ちょっとホッとしています。

そんな秋のはじめである9/25に、株式会社ニューロスペース様と共催で、Meetupを開催しました!

unifa.connpass.com

僭越ながら私も登壇させて頂きましたので、本日はその様子について書きたいと思います。

睡眠✕IoT!?

今回は株式会社ラプラス様に会場をお借りし、渋谷で開催。(ラプラス様、ありがとうございます!)

f:id:unifa_tech:20190927161005j:plain
会場の入り口看板
テーマは「睡眠✕IoT!」。立地のおかげか、ニューロスペース様のおかげか!?20名ほどの方にご参加頂くことが出来ました。
私がお話を伺った方の中にも「珍しいテーマだったので来てみました。」とおっしゃる方がいらっしゃいましたし、 他の方のヒアリングの結果を聞いても、「睡眠」に惹かれて来てくださった方が多かったようです。 弊社のエンジニアの中でも興味関心が高く、R&Dチームからは登壇者の志田以外にも複数人が参加しました。

当日の進行

当日は以下のように進みました。

19:00 受付開始・ネットワーキング
19:30 オープニング&会社紹介
19:40 LT① 睡眠サービス開発の苦労(ニューロスペース 開発チーム 高田悟史)
19:50 LT② 睡眠指標計算ロジック設計:4つの指針(ニューロスペース 開発チーム 平岡尚)
20:00 LT③ ルクミー午睡チェックができるまで(ユニファ システム開発本部 プロダクト開発部 部長 田渕梢)
20:10 LT④ 午睡チェックアプリ開発で大変だったこと(ユニファ システム開発本部 研究開発部 志田和也)
20:20 懇親会
21:30 片付け・解散(22:00 完全撤収)

冒頭で各社の会社紹介・サービス紹介をした後、ニューロスペース様、弊社の順でのLTです。

LT①睡眠サービス開発の苦労

ニューロスペース・高田さんからは、「睡眠サービス開発の苦労」についてお話しいただきました。

f:id:unifa_tech:20190930151009j:plain
「睡眠サービス開発の苦労」の様子

6月にリリースされた「睡眠習慣デザインプログラム」のシステムの構成概要や、サービスの具体的な内容、 これまで様々なシステム開発に携わっていらした高田さんの視点から、睡眠サービスを開発する時特有の難しさ・課題をどう解決してきたのかについて伺いました。

speakerdeck.com

サービスの開発には開発後の効果測定/評価が必須となりますが、その効果がわかりやすく現れにくいプロダクトであるという点で、様々な工夫をされていることがよくわかりました。 (実は弊社の午睡チェックでも、運用していく中で同じような問題に遭遇しておりまして、勝手に親近感を抱いていました。) 弊社社内でも睡眠について関心の高いメンバーもおり、個人的にはぜひ利用してみたいサービスです。

LT②睡眠指標計算ロジック設計

ニューロスペース・平岡さんからは、睡眠指標の計算ロジック実装での工夫についてお話し頂きました。

f:id:unifa_tech:20190930151210j:plain
「睡眠指標計算ロジック設計」の様子

speakerdeck.com

「睡眠指標」と分類される様々な情報を利用者に対して可視化する上での課題や苦労をベースに、実装上の工夫についてお話しされていました。 他サービスの開発時にも応用が出来そうな内容でしたので、自分が何かを作る際に参考にさせて頂こうと思っています。

LT③ルクミー午睡チェックができるまで

これまでにも何度かイベントにてお話させて頂く機会があったにも関わらず、今回は慣れない土地と参加者の多さで始まる前から緊張(苦笑) 話す直前まで資料を確認しておりました。

f:id:unifa_tech:20190927131828j:plain
直前まで原稿チェック。

今回は、弊社で展開している「ルクミー午睡チェック」について、サーバーサイドエンジニアの視点から、失敗談を含めてご紹介させて頂きました。

内容については、終わった後の懇親会でそれなりにお話に繋げることが出来たので良かったかな、と思っています。 短いLTの時間の中でそれなりに中身のあるお話をするのは難しいものだなと、いつも思います。。。

LT④午睡チェックアプリ開発で大変だったこと

同じく、午睡チェックアプリに関する初期開発時の苦労を、弊社の志田がお話ししました。

f:id:unifa_tech:20190930140050j:plain
アプリ開発の大変だったことを発表中

初期開発の時のことは私も意外と鮮明に覚えておりまして(やったことのないことが多く、試行錯誤の連続だったので)、今更ながらに志田さんお疲れ様!と思います。 実際にこうしてプロダクトが世に出た後、今年になってから外で午睡についてのLTをさせて頂く機会も増えましたが、参加者の皆さんのお声を聞くとそういった試行錯誤・地道な改善を評価してくださっている方も多く、こうした積み重ねが大切なのだなと確認できました。

懇親会

懇親会は、話した内容をもとに色々とご質問を頂いたり、新しい提案を頂いたりと、大変有意義な時間となりました。
「よくこの大変なアプリ作りましたね。」と感心の声を頂くこともありまして、改めて歴代、このアプリのデザイン、ディレクション、開発、テスト(QA)に携わってくれたみなさまに感謝せねばならないなと思いました。

ニューロスペース様からは「質の良い眠りを得るためにはどうしたらいいのか」というお話も伺うことができたので、今後の生活の改善に繋がりそうです。(今までが全然ダメだったことがよくわかりました。。。)

さいごに

イベントではエンジニアやその他の職種の方から色々なご意見やご感想を頂くことが出来、改めて自社のサービスについて客観的に良さや課題を確認することが出来ました。

今回共にイベントを開催頂いた株式会社ニューロスペース様、快く会場を貸してくださった株式会社ラプラス様、イベント開催に向けご協力頂いた人事、広報のみなさま、誠にありがとうございました。

今後も定期的に、弊社取り組みを発信する場を設けていきたいと考えておりますので、少しでもご興味のある方はぜひお立ち寄りください。

ユニファでは、一緒に「保育をハックする」を実現してくれるエンジニアを募集中です!

開発・テクノロジー|ユニファ株式会社 unifa-e.com

保育士さんの行動推移モデルで理解するマルコフ連鎖

はじめに


こんにちわ、研究開発部の島田です。今回はマルコフ連鎖についてのお話です。

マルコフ連鎖は様々なところで応用されており、イメージしやすい例だとWeb広告のアトリビューション解析などでしょうか。これは、ユーザーがインターネット上で何かを購入するまでの間にどんなWeb広告に接触してどんな行動を取るのか、こういった行動ログをマルコフモデルに当てはめることでどのWeb広告が価値が高いかがわかるようになります。他にもベイズ統計学や強化学習などの分野でもマルコフモデルの考え方が重要になってきます。

名前だけ聞くとすごく難しそうなこのマルコフ連鎖ですが、考え方自体はシンプルで理解しやすいです。 今回はこのマルコフ連鎖について理解し、簡単なシミュレーションも行ってみたいと思います。

マルコフ連鎖とは


マルコフ連鎖についてWiki先生のご説明を見てみます。

マルコフ連鎖(マルコフれんさ、英: Markov chain)とは、確率過程の一種であるマルコフ過程のうち、とりうる状態が離散的(有限または可算)なもの(離散状態マルコフ過程)をいう。また特に、時間が離散的なもの(時刻は添え字で表される)を指すことが多い(他に連続時間マルコフ過程というものもあり、これは時刻が連続である)。マルコフ連鎖は、未来の挙動が現在の値だけで決定され、過去の挙動と無関係である(マルコフ性)。各時刻において起こる状態変化(遷移または推移)に関して、マルコフ連鎖は遷移確率が過去の状態によらず、現在の状態のみによる系列である。特に重要な確率過程として、様々な分野に応用される。

引用元:マルコフ連鎖 - Wikipedia

何言ってるか全くわからんよ・・・Wiki先生。。。

ということで、このブログではまず数学的に小難しいことは無視してイメージだけ掴める様にしてみます。 その後一般的な解釈を数式も交えて理解していきたいと思います。

イメージで理解してみるマルコフ連鎖


マルコフ連鎖をイメージで掴むために、今回は保育士さんの行動に当てはめて理解してみたいと思います。

ある保育士さんが「職員室・園庭・保育室」のいずれかに滞在しているとします。 実際はもっと色々な場所を行ったり来たりして、更に他の要因も複雑に絡み合ってるので簡単な確率モデルでは表せないと思いますが、ここではマルコフ連鎖を理解することが目的なので簡単なモデル(「保育士さん行動推移モデル」と呼ぶ)で考えます。

例えば、今ある保育士さんが保育室に園児たちと一緒にいた所、他の保育士さんからのコールなどで10分後に職員室に行く確率が50%だったとします。職員室での仕事を済ませ、次の10分後はそのまま職員室に滞在する確率を20%、園庭に行く確率を80%とします。そして次に・・・・といった様に複数の状態を時系列的に行き来すること、これこそがマルコフ連鎖です。

f:id:unifa_tech:20190920093726p:plain:w350
(保育士女の子イラスト 出典: かわいいフリー素材集 いらすとや

ここで大事なこととして、マルコフ連鎖は現在の状態だけが次の状態を決めます。先ほどの保育士さんの例で言うと、園庭に行くかどうかはあくまで職員室での状態だけが関係しています。

続きを読む

Multi-Model Attribute Generator

By Matthew Millar R&D Scientist at ユニファ

Purpose:

This blog is to show the development process of a new research paper that I am working on.
The goal of this string of blog posts is to slowly but surely develop a product that can aid in the data attribute labeling for humans and even other types of image data.
This can be used in several products from people identification, tracking, and statistical data analysis.
Are you ready? Try to keep up!

Attribute Recognition:

What is Attribute Recognition? It is the process of identifying what properties are present in an image. This is normally done on humans but can be done on pretty much anything from cities, cars, and even airplanes. The ability to predict the presence or absence of an item can be very beneficial. Tracking people, a safety check of a vehicle (like a bus or a plane) before departure, visual inspection of an assembled computer, even uses in nuclear power plants. A simple scan of an image can yield some very important warning which could be detected before a disaster can occur.

Data set:

The data-set that I will be using will be the Market-1501 data-set (Zheng et al., 2015) which is commonly used for the Re-identification problems. Why use this data-set? I am using this data-set because of the size and variety of people in the images. The image quality is akin to that of a standard security camera. There are varied backgrounds for each image which will only make the program stronger at generalization by avoiding the use of a cleaned, non-noisy data-set. This data-set will give us many attributes to extract over the next few weeks.

Step 1 Battle of the Sexes:

The first and possibly easiest attribute to check is the gender of a person. This will be easy as it can be a binary classification problem, so not that big of a deal. If your reading this then more likely than not have read a Dog and Cat classification post somewhere when you started out learning CNNs. The model that we will build will be similar so I will not go into great detail of the model itself.

Pre-process Steps:

The first step we need to take is the pre-processing of the images. First, we need to separate the images into the two classes (male, female). These will be our classes for training. Then we need to split the data-set into training and testing sets.
I will use Keras’s image generator to do this as it will not only save time, but I can do all the other pre-processing steps at the same time. This is a list of all possible random image augmentations that will be performed on each image along with some pre-processing steps that will always be performed.

f:id:unifa_tech:20190911111019p:plain
Augmentation Table

Here is the code for the generator for both training and validation data-set. By defining the image generators like this, it saved time splitting up the data-set yourself or having to load it into memory directly and use another python library to do the splitting.

train_datagen = ImageDataGenerator(rescale=1./255,
    shear_range=0.2,
    rotation_range=15,
    zoom_range=0.2,
    horizontal_flip=True,                              
    validation_split=0.2) # set validation split

train_generator = train_datagen.flow_from_directory(
    DATA_PATH,
    target_size=(224, 224),
    batch_size=BATCH_SIZE,
    class_mode='binary',
    subset='training') # set as training data

validation_generator = train_datagen.flow_from_directory(
    DATA_PATH, # same directory as training data
    target_size=(224, 224),
    batch_size=BATCH_SIZE,
    class_mode='binary',
    subset='validation') # set as validation data


Now with that defined we can then use this in training the model. The model will be a simple binary classification model. There is no real need to make it too complex as this is just one of many models that will be used in the product.

def build():
    model = Sequential()
    model.add(Conv2D(32, (3, 3), input_shape=(224, 224, 3)))
    model.add(Activation('relu'))
    model.add(Dropout(0.3))
    model.add(MaxPooling2D(pool_size=(2, 2)))

    model.add(Conv2D(64, (3, 3)))
    model.add(Conv2D(64, (3, 3)))
    model.add(Activation('relu'))
    model.add(Dropout(0.3))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    
    model.add(Conv2D(128, (3, 3)))
    model.add(Conv2D(128, (3, 3)))
    model.add(Activation('relu'))
    model.add(Dropout(0.3))
    model.add(MaxPooling2D(pool_size=(2, 2)))

    model.add(Flatten())
    model.add(Dense(128))
    model.add(Dense(128))
    model.add(Activation('relu'))
    model.add(Dropout(0.5))
  
    model.add(Dense(1))
    model.add(Activation('sigmoid'))
    return model

Seeing that this is a binary problem the use sigmoid is an appropriate activation layer here. The model is not that deep as it is only 2 fully connected layers and one fully connected output layer.

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 222, 222, 32)      896       
_________________________________________________________________
activation_1 (Activation)    (None, 222, 222, 32)      0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 222, 222, 32)      0         
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 111, 111, 32)      0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 109, 109, 64)      18496     
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 107, 107, 64)      36928     
_________________________________________________________________
activation_2 (Activation)    (None, 107, 107, 64)      0         
_________________________________________________________________
dropout_2 (Dropout)          (None, 107, 107, 64)      0         
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 53, 53, 64)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 51, 51, 128)       73856     
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 49, 49, 128)       147584    
_________________________________________________________________
activation_3 (Activation)    (None, 49, 49, 128)       0         
_________________________________________________________________
dropout_3 (Dropout)          (None, 49, 49, 128)       0         
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 24, 24, 128)       0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 73728)             0         
_________________________________________________________________
dense_1 (Dense)              (None, 128)               9437312   
_________________________________________________________________
dense_2 (Dense)              (None, 128)               16512     
_________________________________________________________________
activation_4 (Activation)    (None, 128)               0         
_________________________________________________________________
dropout_4 (Dropout)          (None, 128)               0         
_________________________________________________________________
dense_3 (Dense)              (None, 1)                 129       
_________________________________________________________________
activation_5 (Activation)    (None, 1)                 0         
=================================================================
Total params: 9,731,713
Trainable params: 9,731,713
Non-trainable params: 0
__________________________________________

Now with the model defined we will turn our attention to training the model.

opt = SGD(lr=LR, momentum=0.9, decay=LR / EPOCHS)
model = build(224, 224, 1)
model.compile(loss="binary_crossentropy", optimizer=opt,metrics=["accuracy"])
filepath= "GenderID-{epoch:02d}-{val_acc:.4f}.ckpt"
checkpoint = ModelCheckpoint(filepath, monitor='val_acc', verbose=1, save_best_only=True, mode='max', save_weights_only=False)
callbacks_list = [checkpoint]

model.fit_generator(
    train_generator,
    steps_per_epoch = train_generator.samples // BATCH_SIZE,
    validation_data = validation_generator, 
    validation_steps = validation_generator.samples // BATCH_SIZE,
    epochs = EPOCHS,
    verbose=1,
    callbacks=callbacks_list)

As you can see the model started to produce pretty good results (~80% validation accuracy) after training.

Epoch 1/10
322/322 [==============================] - 139s 432ms/step - loss: 0.6444 - acc: 0.6356 - val_loss: 0.6126 - val_acc: 0.7211

Epoch 00001: val_acc improved from -inf to 0.72109, saving model to GenderID-01-0.7211.ckpt
Epoch 2/10
322/322 [==============================] - 128s 398ms/step - loss: 0.5833 - acc: 0.6987 - val_loss: 0.5848 - val_acc: 0.7490

Epoch 00002: val_acc improved from 0.72109 to 0.74902, saving model to GenderID-02-0.7490.ckpt
Epoch 3/10
322/322 [==============================] - 128s 399ms/step - loss: 0.5459 - acc: 0.7334 - val_loss: 0.5795 - val_acc: 0.7565

Epoch 00003: val_acc improved from 0.74902 to 0.75647, saving model to GenderID-03-0.7565.ckpt
Epoch 4/10
322/322 [==============================] - 125s 388ms/step - loss: 0.5208 - acc: 0.7462 - val_loss: 0.5736 - val_acc: 0.7137

Epoch 00004: val_acc did not improve from 0.75647
Epoch 5/10
322/322 [==============================] - 125s 390ms/step - loss: 0.4986 - acc: 0.7637 - val_loss: 0.5472 - val_acc: 0.7212

Epoch 00005: val_acc did not improve from 0.75647
Epoch 6/10
322/322 [==============================] - 124s 384ms/step - loss: 0.4912 - acc: 0.7667 - val_loss: 0.5136 - val_acc: 0.7851

Epoch 00006: val_acc improved from 0.75647 to 0.78510, saving model to GenderID-06-0.7851.ckpt
Epoch 7/10
322/322 [==============================] - 124s 384ms/step - loss: 0.4674 - acc: 0.7799 - val_loss: 0.5209 - val_acc: 0.7745

Epoch 00007: val_acc did not improve from 0.78510
Epoch 8/10
322/322 [==============================] - 124s 385ms/step - loss: 0.4485 - acc: 0.7925 - val_loss: 0.4978 - val_acc: 0.7643

Epoch 00008: val_acc did not improve from 0.78510
Epoch 9/10
322/322 [==============================] - 123s 381ms/step - loss: 0.4323 - acc: 0.8022 - val_loss: 0.5000 - val_acc: 0.7737

Epoch 00009: val_acc did not improve from 0.78510
Epoch 10/10
322/322 [==============================] - 124s 386ms/step - loss: 0.4277 - acc: 0.8037 - val_loss: 0.5061 - val_acc: 0.7565

Epoch 00010: val_acc did not improve from 0.78510

Testing

Testing on some images of both male and female the model did as expected ok.
For men, the accuracy was 65.17 % correct.
And for women, the accuracy was 48.36 % correct
So the model is a little more accurate for detecting men than women in the end.
With a total accuracy of 58.36% which is ok a little better than guessing randomly so I will take that as a win.

CONCLUSION:

Now we can see the model is accurate for this complex problem. But how can we improve this model? Some improvements can be done by using a pre-train model to aid in the feature extraction of an image along with better data augmentation techniques.
The model can successfully predict if a person in an image is a man or a woman without the use of faces which is a very difficult task. Why is this important? This will allow for telling if someone sex from a distance even if their face is obscured by clothing or a jacket. So you can use lower resolution security cameras and still with a certain accuracy tell if the person is a man or a woman.

Future Improvement:

From here I will add in layer initializers, deepen the network, add in a pre-trained fine turned model, and improve the data augmentation for the model. This should give a little better results and possibly reaching my goal of 65% which would be a very good model for this particular task.

References:

L. Zheng, L. Shen, L. Tian, S. Wang, J. Wang and Q. Tian, "Scalable Person Re-identification: A Benchmark," 2015 IEEE International Conference on Computer Vision (ICCV), Santiago, 2015, pp. 1116-1124.
doi: 10.1109/ICCV.2015.133

AWS LambdaでTensorFlow 2.0を使った画像分類

こんにちは、R&Dの宮崎です。 普段はTensorFlowを使った画像の認識モデルの開発を行なっています。 無事に精度の高いモデルができると、次は実際にサービスとして運用するための基盤を準備しなければいけません。 そこで今回は、AWS LambdaでTensorFlow 2.0を動かせるか検証してみましたので、紹介したいと思います。

TensorFlowで推論するための基盤

TensorFlowの運用基盤としてはいくつかの選択肢が考えられます。 AWSではTensorFlow ServingのコンテナをECSやEKSで動かしたり、SageMakerを使ったりするなどです。 これらに対し、AWS Lambdaはサーバのプロビジョニングが不要で運用の手間がかからず、使った時間しか課金が発生しないため、推論リクエストが散発的に発生する場合はコストを抑えられるといったメリットがあります。 一方で、AWS Lambdaはコールドスタートからの起動時間がかかったり、使えるコンピュータリソースが少ないというデメリットもあります。 特に、TensorFlowを扱う上ではその大きなパッケージやモデルを、いかに限られたディスクサイズに収めるかが鍵となります。本記事ではサイズの削減方法を中心に検証していきます。

続きを読む

fastlaneを利用してdSYMファイルを自動アップロードしてみる

こんにちは。iOSエンジニアのキムです。今回はアプリのクラッシュに関する内容になります。

アプリが良い評価をされるためには動作の安定性が必要不可欠です。コンテンツの内容やデザインなども大事ですが、アプリの動作が不安定でよくクラッシュしたりすると、そのアプリは使われなくなリます。そのため、エンジニアたちはアプリの安定性をモニタリングし、安定性の向上のために絶えずに取り組みを行っています。

アプリの品質を低下させるクラッシュですが、クラッシュログを生のデータのままで解析するのはほぼ不可能です。そのため、dSYM(デバッグシンボル)ファイルというのが必要になります。dSYMファイルを用いてシンボル化(Symbolicate)することで、解析可能なスタックトレースへと復元することができるようになります。

クラッシュレポートツール(Firebase Crashlyticsなど)を使うと、dSYMファイルを自動的にアップロードしてくれるとかクラッシュしたデバイスの情報などの追跡もできるので、クラッシュの原因を調査する時間も節約できます。

背景

以前Firebase Crashlyticsでクラッシュログを調べる際に「Missing dSYM」というアラートが表示されて、クラッシュログが正常に表示されないことがありました。

調べると、アプリのBitcode設定を有効にしていたのが原因でした。アプリのBitcode設定を有効にしてAppleに送信すると、Appleによって特定のデバイスとアーキテクチャ向けに再コンパイルされ、dSYMファイルも新しく生成されるので、それをAppleから手動でダウンロードして、Firebase Crashlyticsにアップロードする必要がありました。

しかし、新しく生成されたdSYMファイルは数十個もありましたし、それを毎回アップロードするのはなかなか手間がかかる作業でした。

今回はそのdSYMファイルをfastlaneを利用して自動でアップロードする方法について紹介したいと思います。

*ちなみに、dSYMファイルはXcodeのOrganizerのDownload Debug Symbolsからもダウンロードできます。

準備

Firebase Crashlyticsfastlaneについてある程度の知識があることを前提で話します。

処理内容

fastlaneでdSYMアップロード用のlaneを作ってみます。

lane : refresh_dsyms do |options|
    # バージョンの指定がなければ、最新バージョンのdSYMファイルをダウンロードする
    version = options[:version] || 'latest'
    download_dsyms(
      version: version
    )
    # dSYMファイルをcrashlyticsにアップロードする
    upload_symbols_to_crashlytics
    # ローカルのdSYMファイルを削除する
    clean_build_artifacts
end
利用方法
fastlane refresh_dsyms version:1.0.0
結果

上記のlaneを実行すると、dSYMファイルをAppleからダウンロードして、crashlyticsにアップロードしてくれます。

f:id:unifa_tech:20190909093639p:plain

fastlaneのdocumentを見るともっと詳しい使い方が書いてあります。

最後に

これでクラッシュログも正常に表示されるようになりました。 これからはクラッシュログを解析して、どのクラッシュから解決するかの優先順位を決めて、一つずつ再現させながら対応するだけですね。