ユニファ開発者ブログ

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

Pytorch Tiny Edge Detection

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

Purpose

This blog will show how to make a custom edge detector using a very small Convolutional Network and Pytorch. This will be a very simple approach to making a fast and accurate Edge detector which is better than Opencv Canny Edge Detector.

Edge Detection

Edge detection is a basic computer vision technique that allows for the edges or boundaries of an image to be found. This is one of the basic methods for CV that most people do when they start using OpenCV as their Canny edge is fairly good, but will have to be worked over several times to find the optimal parameters per image which cannot be applied to every image that is processed. That is where CNN can help out.

CNN for edge detection

CNNs are very good at extracting features from an image. These features can then be used to generate edges of the image. The process starts by reading in an image then converting it to a grayscale image. To get good results, building out your own filters would be best, but for ok results using a very basic filter can be used. Then using these filters in CNN as the kernel size will allow for the extraction of features from the image. The activation layer (normally Relu) will give you the black and white extraction that you would expect.

Let's walk through the Code.

The imports you will need

import cv2
import matplotlib.pyplot as plt
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F

Using a photo I took from an American Air Show in Japan we will show how to work with Pytorch and edge detection from scratch.

f:id:unifa_tech:20200129153649j:plain
F-18
Next we will read in the image and transform it into a grayscale image.

img_path = 'data/f18.jpg'
img = cv2.imread(img_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = gray.astype('float32')/255
plt.imshow(gray, cmap='gray')
plt.show()

Next we need to define our filters which will do the feature extraction in the CNN

# Vizulization filters
base_filter = np.array([[-1, -1, 1, 1], [-1, -1, 1, 1], [-1, -1, 1, 1], [-1, -1, 1, 1]])
print("Filers: ", base_filter.shape)
# Defining four different filters, 
# all of which are linear combinations of the `filter_vals` defined above

# define four filters
filter_1 = base_filter
filter_2 = -filter_1
filter_3 = filter_1.T
filter_4 = -filter_3
filters = np.array([filter_1, filter_2, filter_3, filter_4])
print('Filter 1: \n', filter_1)

Here is what the filters will look like in Numpy

Filers:  (4, 4)
Filter 1: 
 [[-1 -1  1  1]
 [-1 -1  1  1]
 [-1 -1  1  1]
 [-1 -1  1  1]]

That may not look very nice so lets visualize them a bit better.

# visualize all four filters
fig = plt.figure(figsize=(10, 5))
for i in range(4):
    ax = fig.add_subplot(1, 4, i+1, xticks=[], yticks=[])
    ax.imshow(filters[i], cmap='gray')
    ax.set_title('Filter %s' % str(i+1))
    width, height = filters[i].shape
    for x in range(width):
        for y in range(height):
            ax.annotate(str(filters[i][x][y]), xy=(y,x),
                        horizontalalignment='center',
                        verticalalignment='center',
                        color='white' if filters[i][x][y]<0 else 'black')

f:id:unifa_tech:20200129154136p:plain
Filters
Now that makes sense right. This shows which part of the image the filter will look for sharp contrast in the pixels.
Next we will define a simple CNN and our own weights.

# Conv Arch
class Net(nn.Module):
    def __init__(self, weight):
        super(Net, self).__init__()
        # initializes the weights of the convolutional layer to be the weights of the 4 defined filters
        k_height, k_width = weight.shape[2:]
        self.conv = nn.Conv2d(1, 4, kernel_size=(k_height, k_width), bias=False)
        self.conv.weight = torch.nn.Parameter(weight)
    def forward(self, x):
        # calculates the output of a convolutional layer
        # pre- and post-activation
        conv_x = self.conv(x)
        activated_x = torch.relu(conv_x)
        return conv_x, activated_x

Then we will define the weights (randomly initiazlized)

# instantiate the model and set the weights
weight = torch.from_numpy(filters).unsqueeze(1).type(torch.FloatTensor)
model = Net(weight)

# print out the layer in the network
print(model)

Now to call the model we can do this which will return both the convolutional outputs as well as the activation outputs

# convert the image into an input Tensor
gray_img_tensor = torch.from_numpy(gray).unsqueeze(0).unsqueeze(1)
# get the convolutional layer (pre and post activation)
conv_layer, activated_layer = model(gray_img_tensor)

The pre activation output will be this
f:id:unifa_tech:20200129154656p:plain
and the post activation output will be this
f:id:unifa_tech:20200129154721p:plain
Now this may not look like the common edge detection that you are used to, that is because we need another step to get the final output of edges.
We will have to add each edge together which will give what you would expect.

print(activated_layer.detach().numpy().shape)
# print out (1, 4, 2617, 4653)
edge = activated_layer.detach().numpy()
edge = np.squeeze(edge)
merged_1 = np.add(edge[0], edge[1])
merged_2 = np.add(edge[2], edge[3])
merged_edge = np.add(merged_1, merged_2)
print(merged_edge.shape)
# prints out (2617, 4653)
plt.imshow(merged_edge,cmap='gray')
plt.show()

Which will then give you the final edge detection image.

f:id:unifa_tech:20200129154917p:plain
Edge Detection

Now lets compare to OpenCV built in one

img = cv2.imread('data/f15.jpg',0)
edges = cv2.Canny(img,100,200)
plt.imshow(edges,cmap='gray')
plt.show()

Which will give you this image.

f:id:unifa_tech:20200129155034p:plain
OpenCV

Conclusion

Better results can be obtained by working with both of these two. But using the most basic of filters the CNN outperformed the OpenCV version. Ways you can improve both is by working with the filter of the CNN and adding layers to the CNN to improve the quality of features that are extracted. OpenCV you can adjust its parameters to fine-tune it for the specific image, but this will not work for a different image, while the CNN new filters will. Both of these can be put on as Raspberry Pi, but the Pytorch model will run just as fast as the OpenCV method but will give much better results in the end.

ユニファのシステムの移り変わり(前編)

おはこんにちばんは! ユニファのインフラ見てますすずきです。

私は新年最初のブログになります。 そして春になれば私も入社4年が経つなと思い、ユニファのシステムの遍歴でもまとめてみるかとおもい、ブログに書いてみます。

ただ、入社前の部分も書いてるので、そのあたりが適当なのはご容赦ください。

続きを読む

Swift for TensorFlowで頭部姿勢推定をやってみる

こんにちは、iOSエンジニアのしだです。
前回、Swift-Jupyter について書いたのですが、本当はSwift for TensorFlowを使って頭部姿勢推定(Head pose estimation)をやりたかったのでこちらをやってみたいと思います。

tech.unifa-e.com

頭部姿勢推定(Head pose estimation)

頭部姿勢推定は、カメラからみた頭の向きの推定を行うことです。 そのままですが、画像から頭の向きを pitch、 yaw、 roll の3つの値を予測することになります。

続きを読む

ユニファ開発チームの2020年-今年は洗練の年!

あけましておめでとうございます! エンジニアの田渕です。
みなさま、どんな年末年始を過ごされましたか?

ユニファは海外エンジニアも在籍しておりますので、きっと多種多様な過ごし方が……と思って話を聞いてみると意外と「寒いし混んでるんで出かけません。」と言う返事が多く(笑) 話を聞いてみると、国によっては日本みたいに「お正月だからホテルの料金が高くなる」みたいなことがないところもあるようなのです。 確かにそうなると、わざわざ高い時期に出かけることに躊躇いが出るのは納得です。

さて、まだ今年の仕事も始まって一週間経っておりませんが、早速年始からバタバタバタ……。。。 このブログも、うっかり書き忘れそうになっていたのを思い出し書いているところです。

昨年末のUniFa Advent Calendar 2019は好評のうちに終了。 (読んでくださったみなさま、ありがとうございました!執筆したみなさま、お疲れ様でした!) 最終日25日に書いたCTO赤沼の記事が良かったと内輪で話題になりましたので、 今回はその内容を受ける形で、私の方で今年の展望を書いて行こうと思います。

<プロダクト>「スマート保育園構想」の実現に欠かせないものとは

「スマート保育園構想」とは

最近は色々な場所で、弊社のメンバーが登壇した際に「スマート保育園構想」と言う言葉を口にしていると思います。

改めて、「スマート保育園構想」とはなんなのか。

昨年3月の赤沼のインタビュー記事と、12/28(土)に放送されたBSフジの「この国の行く末2」のリンクです。

job-draft.jp

「この国の行く末2」

konokuni.jp

プロダクトが増えてきた!でもそれだけでいいの?

f:id:unifa_tech:20200109234529p:plain
スマート保育園構想
上の図は、前回のブログで書かせて頂いたRuby Biz Grandprix 2019のプレゼンテーション資料中の図です。 (この俯瞰図はうちのデザイナーが書いてくれたもので、とても好評です。)

当初は写真サービスである「ルクミーフォト」しかなかった弊社ですが、現在ではこの図に記載されていないサービスも含め、保育園・幼稚園・こども園内の業務のより広い範囲をカバー出来るようになっています。 直近では、「ルクミーシフト管理」のリリースも発表されました。

プロダクト/機能が増えていくこと自体は良いことですが、それに伴い新たに生まれてくる問題もあります。

作り手としては「システム構成・アーキテクチャの複雑化」、使い手としては「機能が多すぎて使いこなせない。」 特に後者の「機能が多すぎて使いこなせない。」と言う問題は、利用者の方々にご不便をおかけすることになるので、早急な対策が必要と考えています。

私はもともと地方出身なので、東京に来たばかりの頃は巨大なデパートの中で目的のものが探せずにとても疲れた思い出があります。自分が欲しいものだけが揃ったセレクトショップあったらなーとそんな時に思った訳ですが、弊社サービスをご利用頂く利用者の皆様にはそんなご苦労はおかけしたくない。。。

プロダクトに関しては、今年も昨年同様に新しい物をどんどん世に送り出しながら、プロダクトが増えた次の段階である

「より効率的に利用者の方がやりたいことをやれる様になるには。」

「欲しいものを、欲しいタイミングで見てもらえるには。」

を追求していくのが今年だと考えています。

<チーム>「多様性を受け入れる」ことから、より充実した環境へ

その「当たり前」は本当に「当たり前」ですか?

さて、昨年は赤沼の記事中にもあった通り、エンジニアメンバーが大きく増えた年、多様化が進んだ年でした。 開発チームの19名の新メンバー中、11名は外国籍メンバー。 年末に行った忘年会では、各国の早口言葉で盛り上がったところもあったようです。

日本では当たり前の「お正月のATMのメンテナンス」。 毎年の恒例行事ながら、日本人でも忘れてしまう人がいます。 年末に外国籍メンバーから「他の外国籍メンバーに教えてあげたほうがいいです。」と言うお話を貰って慌ててアナウンスしました。 言われてみれば、日本での年末年始が初めての方は知らなくて当然です。

こんな感じで、日常的に「当たり前」と思っていることが実は当たり前じゃない、と言う経験をしています。 そのお陰で、最近では色々なことに対して「それって当たり前なんだっけ?」と考えられる様になってきた気がしています。

これは、プロダクトを作っていく中ではとても大切な感覚であり、今後役に立っていくといいなと思っています。

去年は精一杯だったけど、今年はもう一歩先へ

昨年は様々な方々を受け入れ、実際に業務をしていくための基盤づくりの年だったと思っています。

コミュニケーションはどうやって取るのが一番いいのか?

何が必要なのか?

色々と、みんなで試行錯誤してきました。 現在では各チームでそれぞれのやり方を見つけ、臨機応変に対応してくれています。

昨年は受け入れるところで精一杯でしたが、今年は「より効率的に、充実した情報を届けるには、質の高いコミュニケーションを取るには」と言う一歩先の観点を持ち、活動していきたいと思っています。

今年もHackします!

ここまで、「今年やっていきたいこと」について書かせて頂きました。

今年は、 「昨年広げた色々な物を整理/綺麗にしてより洗練させていく年」 と考えています。

また一年間、予想していることもしていないことも含め、色々なことがあると思いますが、来年は次のステージの抱負を書けるように、一年頑張っていこうと思います。

引き続き、変わり続けるユニファで一緒に保育をHackしてくれる方を募集中です!

unifa-e.com

今年も宜しくお願いいたします!

できることもできないことも増えていく。ユニファ開発チームの2019年を振り返る

みなさまメリークリスマス、ユニファCTOの赤沼です。

UniFa Advent Calendar 2019 もついに最終日。この記事を読んでいただけているということは私もちゃんと役目を果たせたということで仕事納め気分です(まだ早い)。

さて、2019年ももうすぐ終わりですが、今年もユニファではスタートアップらしく、一年の間で色々なことがありました。その中で今回は開発に関連するトピックを雑に振り返った後で、最近思ったことを書いてみたいと思います。

プロダクトの追加・拡大

プロダクトに関連することとしては、念願の一つであったルクミーフォトのリニューアルを実施しました。2013年創業時のファーストプロダクトであるルクミーフォトは、サービスの立ち上げスピードと初期ユーザ獲得を最優先としていました。リリースから時間も経ち、サービスのフェーズも変わってくるとどんなプロダクトでもレガシーな部分に対するコストが増大していきます。ルクミーフォトも今後のサービス拡大やユーザビリティ向上のためにも今リニューアルすべきという決断のもとリニューアルを実施しました。リニューアル中は大きな機能追加等はできなくなりますので、ビジネスのメンバーからも多大な協力を得て実現することができました。

tech.unifa-e.com

tech.unifa-e.com

tech.unifa-e.com

tech.unifa-e.com

また、今までリクルートマーケティングパートナーズさんが開発・運用されていた保育園向けICTサービスである Kidsly を、弊社の販売パートナーであるフレーベル館さんを経て事業譲渡いただき、弊社での開発・運用が開始しました。すでに多くのユーザに利用いただいている大規模なサービスの移管は弊社でも初めての経験で、手探りなことも多かったのですが、無事に完了し、今後のユニファの保育園向けICTサービスの中核となっていくプロダクトが加わりました。

kidsly.jp

ルクミー午睡チェックも正式リリースから一年が経過し、弊社初のIoTサービスとしてまずは1年間運用が行えたことにホッとしています。導入数も順調に伸びていますので、今後も安心・安全を提供するプロダクトとして安定的な開発・運用を続けていきたいと思います。

unifa-e.com

開発チームのスケール&開発プロセス改善

開発チームとしても体制が大きく変わりました。今年だけでも開発チームに19名の新メンバーが加入し、今までの倍の規模になりました。また、そのうち11名は外国籍メンバーということで、一気に多国籍チーム化が進みました。開発者採用はスタートアップにとっては特に厳しい市場環境である中、これだけのメンバーに入社いただけたのは本当に嬉しい限りです。

エンジニア以外にもディレクターやデザイナーメンバーも増え、各ロールでのチーム体制や、ディレクターを中心としたプロダクト開発体制も以前より充実させることができ、スクラムを主軸としたアジャイル開発プロセスのプラクティスも積極的に導入しています。ビジネスメンバーも含めたプラクティス浸透のための取り組みもメンバーが積極的に実施してくれているので、今後さらにプロセスの改善が進めていけるのではないかと思っています。

tech.unifa-e.com

R&Dのメンバーも増員され、主に Machine Learning / Deep Learning 領域について今まで以上に幅広く取り組むことができてきました。ビジネスメンバーとも連携して、企画案についての技術的な実現可能性の検討なども行なっています。スタートアップでR&Dチームを置いているケースはあまりないと思いますが、我々が目指すスマート保育園の実現のためには欠かせないと思っています。

podcast.unifa-e.com

デザイナーも人数が増え、やっとチームと言える形になってきました。自社プロダクトのデザインはもちろん、外部向けの露出も増やしていくことができそうですし、社内全体においてのチーム作りという点でもデザインが持つ力は大きいと思っています。

podcast.unifa-e.com

ちなみに入社時から現在の規模になるまでのチームのスケールについては、今年の CTO Night & Day での登壇時にお話しさせていただき、下記の資料も公開しています。

prezi.com

技術ブランディング注力

今年多くの開発メンバーが入社してくれたということは、年始の段階でもそれだけの採用を見込んでたということで、今まで以上に外部向けの技術ブランディングに注力した年でした。以前から継続していた開発者ブログもメンバーが増えたことによって投稿数も大幅に増え、今回のような Advent Calendar が成立するまでになりました。昨年末からスタートした Podcast も不定期ながらなんとか継続し、今月で一年が経過しました。またカンファレンスのスポンサーも初めてやらせていただき、RubyKaigi や builderscon でスポンサーさせていただいた他、 RubyWorld Conference や ServerlessDays Tokyo、 プロダクトマネージャーカンファレンス等ではブースも出させていただきました。当日は登壇させていただく機会があったり、ブースにお越しいただいた方と色々とプロダクトについてお話しさせていただいたり、私たちとしても世界が広がっていく感覚を持つことができました。さらには自社単独や他社との共催での Meetup イベントも開始し、参加いただいた方の中から入社が決まった方もいました。

podcast.unifa-e.com

podcast.unifa-e.com

podcast.unifa-e.com

tech.unifa-e.com

tech.unifa-e.com

podcast.unifa-e.com

tech.unifa-e.com

tech.unifa-e.com

tech.unifa-e.com

これらの実行については準備段階から実際の運用まで、今までの規模では行うことが難しかったですが、メンバーが増え、それぞれ積極的に協力してくれたからこそのもので、開発だけではなく人事や広報等の他の部署のメンバーの協力もあったからこそ実行できたものです。その甲斐あって最近他社の方と会って話をする際に、ユニファのことを目にしたことがある方が増えてきた感触がありますし、すでに繋がっていた方からも、露出機会の多さに注目いただくことが増えました。

また、今年はユニファ開発チームから初めて海外のTech系カンファレンスに1人ではありますが参加することができました。AWS re:Invent や Google I/O、 WWDC など、海外のカンファレンスは日本のものとはスケールが違いますし、最新の技術情報のキャッチアップももちろん重要ですが、何よりテクノロジーに対しての盛り上がりを実際に体験してくるということはとても重要なことだと考えています。来年以降どうなるかはまだわかりませんが、ひとまず最初の一歩が踏み出せたという点では前進できたと思っています。

tech.unifa-e.com

できることが増えてきた。でもできないことも増えていく

ここまでは主に今年やったこと、規模が大きくなったことでできるようになったことをお話ししてきましたが、一方で、できないことも増えてきているとも感じています。規模が小さい時には、今後人数が増えてくればどんどんできることも増えていって、できないことは減ってくるイメージをしていましたが、実際に開発チームや会社全体の規模が大きくなってくると、もちろんできることが増えてきてはいるものの、今までとは違ったできないことが増えていっているという感じです。

プロダクト数や開発チーム、会社規模が拡大してくると、工数などの問題以外にも様々な力学が発生してきます。また、人が増えればやりたいことの総数も増え、指向性も多様になっていき、いずれかを選択せざるを得ないケースも増えてきます。さらにスタートアップとしては対象とする業界の情勢や自社の状況などは日々めまぐるしく変わっていきます。

こういった中では、会社も個人も自分を変えていき、向上させていく必要があります。自分が今までと同じアウトプットを出していたとしても、周りが変わっていき向上していく中では、相対的に自分達のバリューは下がっていってしまいます。

一方で、どれだけ変化する状況の中でも変えてはいけないもの、守らなくてはいけない大事なものがあるのも確かだと思っています。この、変えていくべきことと変えてはいけないことの判断は簡単ではありません。痛みを伴ってでも変えるべきことなのか、流されずに抗うべきことなのか、この判断がとても難しいと最近は感じています。経営、チーム、プロダクト、どの面を取ってもこの判断を間違えた場合、リカバリが困難になる可能性があります。

日々の 1 on 1 等でメンバーと話す中で上がってくるボトムアップでの課題、マネージャー陣から上がってくる会社組織としての改善点、経営陣と話す中で論点になる事業上の問題点など、それぞれ違った視点から、みんなもっと現状を改善したいという想いで様々な課題があげられます。意思決定をしていく立場としては、上がってきている課題は適切な内容か、自分の判断は会社の経営者として正しいのか、CTOとして開発視点から通すべきことを主張できているか、単にメンバーの要望に迎合しているだけになっていないか、他の経営陣の声に流されていないか、セクショナリズムを排除してフラットに判断できているか、こういった様々な思いが日々渦巻いています。

自分の判断が正しかったのかどうかはその瞬間にはわかりません。後になってわかるものです。そうした中で意思決定していく上で大切なのは、自分の中で芯のある結論を出せたか、これで間違っていたら仕方ないと思えるような、これならメンバーにどう思われても仕方ないと思えるような決断になっているかなのかなと思っています。なんとなくで決めてしまったものは失敗することも多いですし、その後もモヤモヤしたものが残りがちです。もちろん考え抜いた上で決定したことでも失敗することはあるのですが、その場合には自分の中で整理もつけやすく、次に向けてピボットしていける気がしています。要はそれぞれの意思決定に対して自分にも他者にも真摯に向き合って手を抜かずに考え抜けたかどうかなのだと思っています(できているとは言ってない)。

来年も保育をハックする

さて、とりとめもなく色々と書いてしまいましたが、我々ユニファ開発チームは来年も保育をハックしていきます。この「保育をハックする」というのは我ながら開発チームのマインドを一言で表していて良い感じだなぁと思っています。また、上記で色々と書いたように組織としてももっと強くしていく必要がありますので、チームをハックしていくことも必要だなと思っています。今年は技術ブランディング等で外向けの施策にも注力していましたが、来年はどちらかというとチームを強くしていくための内部的な施策に重点を置きたいなと思っていますので、保育をハックしていくためのより強いチームを作っていけたらと思っています。一緒に保育をハックしていくためにチームに加わっていただける方もまだ募集していますので、興味のある方はぜひご連絡ください。

unifa-e.com

今年の Advent Calendar の記事としてはこれが最終回となりますが、今後もいろんなところで露出していけるように頑張りたいと思います。また、一年後に2020年を振り返った時に、あの決定は正しかったと言える決定を一つでも増やせるように取り組んでいきたいと思いますので、このブログを始め、引き続きユニファをよろしくお願いします。それでは、良いお年を。

f:id:akanuma-hiroaki:20191223222353j:plain

会社でスナックを立ち上げた話

~お品書き~

ご挨拶

あらこんばんは、私「スナック洋子」というスナックのママをやらせていただいております洋子と申します。
昼間は ディレクター 兼 QA をやっておりまして、二足の草鞋を履かせていただいております。 あ、 ディレクター 兼 QA 兼 ママ なので三足ですわねおほほ。(足は三本もない。)

すみませんちょっとつらくなってきたので、ここからはいつもの感じで書きますね。

今回は社内コミュニケーションを活性化させる場としてこういう方法もあるよーってことで紹介できたらいいなーと思っております。

ゆるい話なのに、思ったより長くなりましたすみません。

「スナック洋子」について

8月にオープンしたばかりでして、ママとしてはまだまだ駆け出しです。
「スナック洋子」は社内向けと社外向け、それぞれで開店しています。

今回は「社内向け」のお話がメインです。
(社外向けは最後にチラっとだけ話します。)

社内では月に一度、最終金曜日に開催しています。
開催場所はオフィスの一角でやっています。

やりたかったこと

社内みんなのオフな場所をつくりたい

会社もなかなか大変なフェーズなので、社内はドタバタ。もう本当にドタバタ。

そんな中、みんなにとって「気晴らしの場」でも「ただ楽しい場」でもなんでも良いのでつくりたかったという感じです。

「あー疲れたなーちょっとスナック寄って帰るかー」
「お、なんかやってる楽しそうー」

ってみんなに思ってもらえていたら大成功です。

社内コミュニケーションの活性支援

ユニファはものすごい勢いで社員が増えています。
(私が入社した4年前は15人で、今や150人以上…!10倍やばい…!)

会社の規模がおおきくなってくると一度も話したことがない!という人が出てきます。
私の体感ですが、100人を超えたくらいから発生しはじめる気がします。

部署をまたいでのコミュニケーションが減り「あそこの部署何やってんだ状態」や、「もはや正社員かパートさんか委託さんなのかがわからない状態」や「役員と一度も直接しゃべったことない状態」ですね。

それを少しでも減らすために、会話をするきっかけをつくれたらなーと思っています。

「最近何してんのー?」
「お、はじめましてー!」
「土岐でございます」←CEO

って会話が生まれていたら大成功です。

私が楽しみたい

今までの話全部忘れてもらっていいくらいここです!(笑)

ママが一番楽しまなくてどうするんですかくらいに思っています。
何事も続けるには運営している側が楽しまなきゃ続かないですよ。(仕事も同じだと思う。)

私は会社のみんなが好きなので、みんなが楽しそうにキャッキャウフフしているのを見ながらお酒が飲めればそれが私にとっての最高です。

スナックをつくる

といっても、自分でつくってないんですよねこれが(笑)

私個人的な話をしますと、私は「人と酒が好き」って理由でスナックをやるのが夢だ!ってみんなに宣言していました。
そんな中、オフィス移転をしまして、カウンターが設置されました。
これはスナックいけるのでは…!って思っていたところ、
みんながロゴをつくり、名刺をつくり、看板をつくり、勝手にスナックが出来上がりました(笑)

目的や運用ルールを決める

さて、スナック自体はみんなの力でできました。
次は運用ルールや集客です。

1. Googleドキュメントで目的・ルールを作成

叩きはスナック洋子の後援者の方が作成してくれました。(みんなで作っている感じ良いね。)
叩きを元にこんなのもあったほうがいいよねーって話をして決まった内容が以下です。

趣旨と目的

  • 趣旨(開催日と開催場所など)
  • 運営目的
  • ルール策定の目的

ルール

  • 片付けの徹底
  • 深酒の禁止
  • 残業している方への配慮
  • ボトルキープ方針(誰がどれだけ飲んでもOKなものだけキープできる)
  • 出禁ルール(年間でルール違反3回で1年間出禁)

これができたら役員や部長陣へ「本当にスナックやるよー!」って宣言と総務の了承を得てきます。

ありがたいことに、会社としてGOGOでした。
こういう活動の背中を押してくれる会社は良いなーと思いました。(いつか予算もぎとりたいと思っている。)

2. お客さまへの情報発信の場をつくる

Slackにチャンネルを作成して、お客さまへの連絡や活動報告をする場としました。

開催後にはこんな感じで報告しています。

f:id:unifa_tech:20191220180344p:plain
スナック活動報告

お客さまのお金を預かる以上、ちゃんと報告はしないとですよね。

3. お客さまの集客

全社員がいるSlackチャンネルで活動開始のご連絡をし、チャンネルへ入ってもらいました。

2回目以降も同じ感じで、全社員へ周知→チャンネル誘導→チャンネルで詳細をご連絡、という形です。

いよいよスナック開店!

開店までにやったことを箇条書きします。

  • お客さまの人数把握
  • お客さまのスケジュール登録と、目的・ルールの周知
  • 食べ物・飲み物の確保
  • 開催日が近くなったらリマインドと人数再確認

得られたもの

そりゃあもうみんなの笑顔ですよね!(なんつって)

仕事やプライベートな話から、ボードゲームやったり、そりゃもうキャッキャウフフですよ。
実際みんなの話を聞いていると、狙った部分の会話もしてくれていたりと、最高でした。
また、役員もよく遊びに来てくれるのでみんな役員とたくさん話せて楽しそうでした。

実際の効果がどれくらいあったのかはどうやって測ろうかなぁは考えていますが、
それよりもみんなが楽しんでくれていればもう大成功なのではって正直思っています。

f:id:unifa_tech:20191220183149j:plain
看板をつくった張本人
f:id:unifa_tech:20191220183209j:plain
黒服担当
f:id:unifa_tech:20191220183119j:plain
オープン初日の様子
f:id:unifa_tech:20191220183155j:plain
ロゴをつくった漁師(太刀魚を捌き中)
f:id:unifa_tech:20191220183204j:plain
ボードゲーム盛り上がり中

カイゼン

もちろん課題もあるのでそれは今後運営しながらカイゼンしていき、より良いスナックにしていきたいと思っていきます。

  • 正社員じゃない方への周知方法(チャンネルに気軽に入れないなど)
  • 他部署が何をやっているかわからない問題は解決していないので、LT大会とかやりたい
  • お酒の管理(スナック開催日の夕方から冷蔵庫がパンパンになってしまう)

CEOに大きい冷蔵庫をねだろうかと思っています。
(土岐さーーーん見てますか!?冷蔵庫欲しい!冷蔵庫冷蔵庫!!!)

まとめ

社内でスナックをオープンさせると楽しいよ!

場をつくるのはちょっとたいへんだけど、それ以上に測れない何かを得られるよ!

ということで以上です。
思っていたより長くなりました、最後まで読んでくれたあなたはもうスナック洋子のお客さまです!

ちなみに社外向けの「スナック洋子」については、採用活動の一環としてオープンしております。

  • ユニファのプロダクトを知ってもらう
  • QAチームやディレクターの働き方やプロダクト開発について、こんな感じでやってるよーの紹介

などなど、ユニファに興味があるかたはどうぞご来店ください。
(TwitterとかFacebookとか、なんとかしてママに連絡とってもらえれば日程調整して開催します!)

看板設置するだけでどこでも「スナック洋子」になるので簡単なのです(笑)

それではみなさまのご来店を心よりお待ちしております。ママより

Bloom Filterについて

こんにちは、プロダクト開発部のちょうです。最近だいぶ寒くなってきて、こたつからなかなか出られません。こたつとみかんがあれば一日いきれると思いつつ、普段自分が学んでいる内容をすこし紹介したいと思います。

今回の内容はBloom Filterです。Bloom Filterは一言でいうと、空間効率のいい確率的データ構造です。要素が集合のメンバーであるかどうかのテストに使われます(wikipediaより)。確率ですので、使えるものではないと思う人が多いかもしれないが、Bloom Filterの特徴を理解すれば問題なく利用できます。

要素が集合のメンバーであるかどうかを確認するには、一番普通の方法はHash関数を元にするHashSetというデータ構造を使います。HashSetは基本 O(1) の速さで判断できます。もちろん、Hash関数による衝突の可能性があります。最悪の場合、HashSetの内部tableのサイズを倍にする必要があります。それに、要素数が非常に多い領域では、普通のHashSetだと空間効率が悪いと予想できます。ここで、Bloom Filterの登場です。

Bloom Filterは空間効率のいいデータ構造です。なぜなら、利用するメモリー領域を倍にする必要がありません。最初から固定してもいいです。それと、Bloom Filterの要素の単位は bit です。32bitマシンでinteger型だと最大32個の要素が入れます。

Bloom Filterは複数のHash関数を利用します。一つのHash関数だと衝突による実際集合のメンバーではないのにメンバーですという結果になる可能性が高いので、複数Hash関数で可能性を減らします。

サイズは8のBloom Filterを例にします。

| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |

最初すべてのbitが0です。要素 foo を入れます。Hash関数を3つにしましょう。Hash1、Hash2とHash3。3つのHash関数の結果は2, 4と7とします。

| 0 | 0 | 1 | 0 | 1 | 0 | 0 | 1 |

foo を入れたBloom Filterです。indexが2、4と7の位置を1にセットします。次は要素 bar を入れます。3つのHash関数の結果は1, 2と3とします。

| 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 |

同じく、indexが1、2と3の位置にセットします。

では要素をテストしましょう。要素 foo でテストすると、3つのHash関数の結果で、場所2、4と7にすべて1にセットされ、メンバーであるということになります。同じく要素 bar でテストしてもメンバーであることもわかります。

続いてメンバーではない要素をテストします。 要素 aaa にたいして、Hash関数の結果を2, 5, 7とします。index 2と7に1がセットされましたが、5がセットされないです。ゆえ、 aaa がメンバーではありません。要素 bbb にたいして、Hash関数の結果は2、3と4とします。すべての位置に1がセットされ、メンバーであるという結果になりますが、実際はメンバーではありません!

ここでわかったことは、Bloom Filterはメンバーではない要素でもメンバーであるという結果になる可能性があります。これはfalse positiveということです。こういう問題があって、Bloom Filterは使えものにならないと思う人がいるかもしれないが、Bloom Filterはメンバーではない要素をメンバーであるという結果にならない(つまり、メンバーではない結果でしたら絶対メンバーではない)です。まとめてみると、

要素 事実 テスト
foo メンバー メンバー
bar メンバー メンバー
aaa メンバーではない メンバーではない
bbb メンバーではない メンバー

要素 aaa はメンバーではないの結果なので、メンバーではないということが断言できます。逆に、メンバーであるという結果になったら、メンバーではない可能性があります。

こういう特徴を受け、Bloom Filter + DBの使い例を考えみましょう。

DBに100万のレコードがあり、サイズ200万(メモリーやく244KB)のBloom FilterをDBの前にします。毎回レコードを取得するとき、まずBloom Filterを通して、もし存在しないなら、レコードはかならず存在しないので、存在するレコードだけDBと問い合わせします。

こうすることで、Bloom Filterでいらない問い合わせを減らすことができます。Bloom Filterの確率的という特徴もカバーできます。実際、Bloom Filterを利用するシーンはほとんど、レコードは非常に多く、例えばメールボックスのSpam Filter、Bloom Filterなしだと裏側のデータソースへものすごく負荷をかかることが予想できます。

Bloom Filterの特徴を理解できると、以下のような簡単なBloom Filterが書けます。

import java.util.*

interface Hasher<K> {
    fun apply(key: K): Int
}

class SimpleStringHasher(
        private val seed: Int, 
        private val max: Int
) : Hasher<String> {
    override fun apply(key: String): Int {
        return key.fold(seed) { acc, c -> acc * seed + c.toInt() } and (max - 1)
    }
}

class SimpleBloomFilter<K>(
        private val bitSet: BitSet, 
        private val hashers: Collection<Hasher<K>>
) {
    fun mightContains(key: K): Boolean {
        return hashers.all { bitSet.get(it.apply(key)) }
    }

    fun put(key: K) {
        hashers.forEach { bitSet.set(it.apply(key)) }
    }
}

fun main() {
    val size = 2048
    val bloomFilter = SimpleBloomFilter(BitSet(size), listOf(
            SimpleStringHasher(1, size),
            SimpleStringHasher(7, size),
            SimpleStringHasher(23, size)
    ))
    for (i in 1..1000) {
        bloomFilter.put(i.toString())
    }
    println((1..2000).count { bloomFilter.mightContains(it.toString()) })
    // 1024 -> 1582
    // 2048 -> 1000
}

SimpleBloomFilterのソースコードをすこしみてみましょう。mightContainsはすべてのHash関数が計算した位置に1がセットされればtrueを返します。putはすべてのHash関数が計算した位置に1をセットします。

main関数では、サイズ2048のBloom Filterを作ってました。それと1から1000までという要素をBloom Filterに入れます。最後は1から2000までの要素でテストします。サイズは2048だと、最後はきれいに1000とカウントされますが、サイズ1024の場合は1582の結果になります。ぜひ試してください。

最後に、false positiveつまり誤判断を減らすにはサイズどれぐらいにすればいいでしょうか。その質問にたいして、Bloom Filterの論文に答えがありますが、ここで結論を出します。

Pfpをfalse positiveの確率とします。nは入れようとする要素の数です。mはBloom Filterのサイズです。

m = - (n * ln Pfp) / (ln 2) ^ 2

おまけに、Hash関数の数kも

k = (m / n) * ln 2

いかがでしょうか。すこしBloom Filterに理解していましたでしょうか。普段の開発に使わないかもしれないが、こういうものがあると覚えていただければ幸いです。