ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

睡眠✕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を見るともっと詳しい使い方が書いてあります。

最後に

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

コンテキストスイッチをいかにして減らしたか?

スクラムマスターの渡部です。

以前、とあるイベントに参加した際、「スクラムをやり始めたチーム向けの、ネタ帳的な情報ってあまり見かけないよね」という話をしていたのですが、それならばと思い、私たちのチームで実際にやったことの一部を紹介していこうと思います。

今回のテーマは、コンテキストスイッチのコスト削減です。

※今になって思えば、「スクラム現場ガイド」がまさにネタ帳的な内容でしたので、興味がある方はぜひお手に取っていただければと思います。

book.mynavi.jp

本記事で解説する内容
  • コンテキストスイッチとは?
  • コンテキストスイッチを体感してみよう!
  • コンテキストスイッチを減らすためにやったこと3つ
想定読者
  • (スクラムを導入しているか否かに関わらず)個人・チームのパフォーマンスを上げたいと考えている方

コンテキストスイッチとは?

コンテキストスイッチとは一言でいうと、作業Aから、異なる文脈の作業Bに思考を切り替えることです。

ただ作業を切り替えているだけのようにも思えますが、作業Bを終えて作業Aに戻ったとき、「作業Aがどこまで進んでいたのか?」、「この後何をするべきだったのか?」を思い出して復帰する工程が必要になり、ムダが発生してしまっているのです。

思い出すことができればまだ良いのですが、酷い場合は全く思い出すことができず、「昔話したアレ…結局何をすれば良かったんだっけ…?」と確認するハメに…

皆さんの中にも、「たくさんミーティングが入っている日は、他の作業が全然進まないなぁ…」というシチュエーションに身に覚えがある方はいませんか?

恐らくその時、「ミーティング」と「作業」の間でコンテキストスイッチが発生することにより、「元々の作業を思い出して復帰する」ことに思考のリソースを割いてしまっている可能性があります。

コンテキストスイッチを体感してみよう!

そうは言ったものの、「簡単な作業なら、少しくらい平気でしょ!」という気持ちも分かるので、簡単なワークでコストを体感してみたいと思います。

私がチームで実施しているのは下記の方法です。5分あれば体験できるので、良ければ是非試してみてください。

準備するもの
  • ペン
  • ストップウォッチ(スマホでOK)
概要

アルファベット10文字「a〜j」、ひらがな10文字「あ〜こ」、数字10文字「0〜9」を2通りの順番で書いていただき、書き終えるまでの時間を計測・比較します。

「アルファベットを書く作業」、「ひらがなを書く作業」、「数字を書く作業」の、3種類の作業が存在するイメージですね。

手順
  1. まずは、3種類の文字を1文字ずつ順番に書きます。(a → あ → 1 → b → い → 2…)
  2. 次に、1つの文字種をまとめて書いて、次の文字種に移ります。(a〜j → あ〜こ → 0 → 9)
  3. 1,2で書き終えるまでの時間を比較します。

いかがでしたでしょうか。

「文字を書く」という作業は日常で慣れているはずですし、特に難しい文字を書いている訳でも無いはずですが、書きにくさを感じませんでしたか?

簡単に思える作業ですらそうなのですから、当然、コードを書いたり、テストをしたり、その他開発以外のあらゆる作業も影響を受けます。

ですので、個人やチームのパフォーマンスを向上させるためには、できる限りコンテキストスイッチを減らし、同じコンテキストの作業を継続できる状況を作ることが大切になります。

次のセクションでは、私たちのチームがコンテキストスイッチ減らすために実際にやってみたことを、エンジニアからのラフな感想付きでいくつか紹介していきます。

やったこと①:運用系作業(差し込み作業)のコントロール

困っていたこと
  • 運用系作業(差し込み作業)と、それに関連する確認相談が頻繁に発生していた
  • スプリント内の全工数の内、3〜6割ほどが運用系作業で占められていた
何をしたのか
  • 運用系作業を専門で行うスタッフをアサインして、他スタッフが確認・対応する時間を減らした
  • 複数の作業依頼が発生することもあったので、運用系作業のためだけのカンバンを作成し、着手すべき優先順位順に1列で並べるルールにした
どうだったか

エンジニア曰く「これは本当に良かった、助かった」とのこと。

差し込み(コンテキストスイッチ)を減らしたことと、単純に運用系作業に対応できる量にも制限が出来たことで、もともとは3〜6割程度を占めていた運用系作業の割合が、多くて1割程度に落ち着きました。

この施策は、チームで実施してきたカイゼンの中でも特にインパクトが大きいものでした。が、その分、チームに適したフローを整えるのに頭を悩ませましたし、影響範囲も大きいので、沢山の方にご理解ご協力をいただきました。

プロジェクトとチームの状況をご理解いただき、ご協力いただいた関係者の皆様、本当にありがとうございます。 特に、フロー検討時に相談に乗っていただいたY岸さん、K林さん、グループ全体にスムーズに展開していただいたT中さん、そして何より、一手に引き受けてくださったSさん、本当にありがとうございます。

実施に際してチームごとに課題はあるかと思いますが、同じような問題に悩まれているチームは多いかと思いますので、是非試してみていただければと思います。

因みに、スクラム現場ガイド 14章では「専任チーム」として同様の事例が紹介されています。

やったこと②:プロダクトオーナーの席移動

困っていたこと
  • (詳しい事情は皆様のご想像にお任せしますが)不明なことが非常に多い中で探りながら開発を進める必要があり、都度の確認・相談のために作業の手が止まっていた
何をしたのか
  • プロダクトオーナーにコンテキストスイッチによるコストを説明・理解いただき、チームの近くに移動してもらい、気軽に確認相談できる環境を整えた
どうだったか

本当にややこしいものは、口頭のみで済ませてしまうと後で困るので、結局テキストに残すことになるのですが、「簡単な相談や、作ってその場で方向性のジャッジができることは良かった」と、エンジニアからはまずまずの評価でした。

因みに、プロダクトオーナーが近くにいることで過干渉のリスクがあると言われていましたが、私たちのチームではそのような問題はありませんでした。

やったこと③:MTGの調整

困っていたこと
  • MTGがたくさんある
  • MTGの開催時間が点在していて、長時間集中できる時間が無い
何をしたのか
  • 参加マストなMTG以外は、欠席 or 任意参加にしてもらえるよう、関係者へ交渉した
  • 参加マストなMTGで、時間調整可能なものは、朝に移動して午後はできる限り空けた
どうだったか

エンジニア曰く「MTGまであと30分くらいだから簡単な作業をしよう…とムダに考えなくて良くなったので進めやすくなった」とのこと。

ちなみに

コンテキストスイッチによる作業効率の低下は、作業単位のみならず、プロジェクト単位でも発生することがわかっています。

ざっくりとした例えですが、次のようなプロダクトA,B,Cのための3つのプロジェクトがあったとします。(必要な作業 A1,A2,A3が達成できれば、プロダクトAが出来上がるイメージです)

f:id:unifa_tech:20190903175619p:plain

まずは、全てを優先、つまり、チームが複数のプロジェクトを掛け持つ場合のスケジュールを見てみましょう。

作業間でコンテキストスイッチが発生するため、作業間に余白を入れて、下記のようなスケジュールになります。

f:id:unifa_tech:20190903170856p:plain

次に、1つずつ順番に対応する場合のスケジュールを見てみましょう。

コンテキストスイッチが発生するのは、A→Bの切り替え時、B→Cの切り替え時のみとなるので、そこに余白を入れて、下記のスケジュールとなります。

f:id:unifa_tech:20190903174725p:plain

可能な限りコンテキストスイッチを抑え、1つずつ順番に対応した場合の方がムダ(余白)が無いため、トータルで早く完了しそうだということがわかります。

(コンテキストスイッチの話とは少し脱線しますが、各プロダクトA,B,Cが早くリリースでき、多くの価値を提供できる利点もあります)

プロジェクトの同時並行に関してはやむを得ない場合もあると思われますので、可能な場合には考慮いただくとよろしいかと思います。

補足

下記ページにある表では、同時並行のプロジェクトが増えるごとに、コンテキストスイッチによってロスが生じ、1つ1つのプロジェクトに使える時間の割合が減っていくことを説明していますので、良ければ見てみてください。

www.scruminc.com

掛け持ちが3つ以上になると、1つ1つのプロジェクトに費やせる割合よりも、コンテキストスイッチによるコスト(ムダ)の割合の方が多くなるのは感慨深いです。

さいごに

いかがでしたでしょうか?

チームのパフォーマンスをできるだけ高めたいと考えている方にとってのヒントとなれば幸いです。

既に何らかの施策を実施されている方は「うちのチームはこんなことをやってみたよ!」とコメントいただけると涙を流して喜びます。

今回は、「コンテキストスイッチのコスト削減」にフォーカスしてお話しましたが、いずれ別のテーマでもネタ帳的な内容で記事を書ければと思います。

このように、私たちのチーム・会社では、効率的に目的を達成するために全員が一丸となって日々カイゼンと繰り返しています。

そんな働き方に少しでも興味を持っていただけるようでしたら、是非下記も覗いてみていただけると嬉しいです。

herp.careers

not 0, but 1

デザインチームの三好です。

今回はデザイナーっぽいこと書きます。

「アートは自己表現、デザインは問題解決」という言葉をよく聞きます。 個人的には100%そうだとは思っていませんが(稀にデザインがアートに、アートが問題解決になることもある)、やはり基本的には明確な問題に対してアプローチしていくものであることに間違い無いかと思います。

一般的にデザイナーとはセンスに長けている必要があると思われがちですが、あくまで感性とは問題解決というゴールに向けてより達成しやすくするための補助であり、最も重要なのは「問題解決能力に長けている」ことだと私は考えます。

オリジナリティを0から生み出す創造性というよりは、培った知識や経験を駆使して1から組み立てていくというほうが正しいかと思います。

では最近作成した開発チームTシャツの制作過程を例に説明してみたいと思います。

以下デザイン案です。

f:id:unifa_tech:20190903170026j:plain

社内デザイン物の中で今回の開発チームのTシャツはかなり自由度の高いデザインが許されるものです。 かといってただビジュアル要素のみ重視して制作しても問題解決にはなりません。 まず最善の答えを設定します。 例の場合、最初に課せられた効果は以下になります。

①エンジニアを感じさせるもの ②イベント登壇時に着るイメージ

まず登壇時に着用する際のアピールとして前面プリントを選択しました。 かつイベント着席時に後方の参加者からも認識してもらえるように社名ロゴを背面に配置。

開発チームのテーマである「保育をハックする」をメインに置いて(いくつか違うものも混ざってますが…)、インパクトの強い言葉に共鳴させるためボディは黒にしました。

後は「ハックする」を軸にしてイメージを具現化していきます。 今回は主に図形を使って効果の強度を高めていきました。

例えば、図形を壁に置き換えてそれを線で打ち破っていく、”既存のルールを壊していくハッカー”を表現してみたり。

f:id:unifa_tech:20190903165826j:plain

最終的には女性が着用することも考慮し、ユニセックスな要素のあるデザインで落ち着きました。 多色なラインのデザインはエンジニアに馴染みのあるターミナルカラーをイメージしています。

デザインは制作した全ての行為を言葉で説明できなければいけません。 明確な目的がある限り全てに意味がなければならない。

デザインは絶対的に他人に向けて発信されるものである為、説得力が必要になります。 自己犠牲なくして成り立たない仕事だと思っています。

1つの制作物を作るまでには思考する時間とチーム内での協力(客観的視点やレビュー)が必須です。 特に私の場合は油断すると抽象的な方向へ飛んでいってしまうので、チームからの冷静な指摘があってようやくデザインとして成り立っていきます。

では、次回はデザインがいかに地味な作業の積み重ねかということをお話ししたいと思います。