Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

Ruby on Lambdaで実現する、Eightの大規模画像処理基盤

Eight事業部 Platform Unit / Engineering Manager の 藤井洋太郎(yotaro) です。

私のチームはいわゆる技術基盤を担当するチームで、パフォーマンス改善、アーキテクチャ刷新、セキュリティ対応といった課題と日々向き合っています。

今回の記事では、昨年11月に行われた AWS re:Invent にて発表された「Ruby on Lambda」を活用した、Eightの画像処理バッチ基盤の改善について紹介します。

Eightにおける画像処理

Eightサービス内では名刺画像をはじめ多くの画像を扱っていますが、UX向上のためにそれらの画像に対して様々な処理を行っています。

1つの画像に対してぼかし、サムネイル、フォーマット(jpeg/webp)変換などを行い数十ケースの画像を生成しています。

画像処理は以下の図のように画像追加・更新時にSQSにジョブを追加し、EC2バッチサーバーで稼働する Rails プロセスが非同期でポーリングし実行する構成となっていました。

f:id:yotaro-fujii:20190415033355p:plain

スケーラビリティ/パフォーマンス課題の顕在化

この処理タスクはアクセスが増える昼過ぎにピークを迎えるのですが、Eightのバッチ機構の問題もありスケールアウトするためには運用者の手運用が必要になってしまうという課題があり、対応が後手に回り滞留してしまうことが少なからずありました。

また滞留が発生すると、画像取得APIで同期的な画像処理を行うことになり、レスポンス劣化も発生してしまうという影響がありました。

LambdaがついにRuby対応

過去何度か Lambda などへの移行を試みたものの Ruby を使えないというメンテナンサビリティな点において課題があり踏み切れずにいました。
が、ついに昨年の re:invent2018 で Ruby 対応が発表され、この波に乗らない手はないということでEightのバッチ基盤の Lambda 移行に踏み切ることに!

続きを読む

「クラスはオブジェクトである」に辿り着くまで

始めに

初めまして、DSOC エンジニアの冨田です。

突然ですが、明後日は何の日でしょうか?
そう、明後日は待ちに待った RubyKaigi です!
楽しみですね、実は今まで RubyKaigi に参加したことがなく初参加になるので、個人的には Rubyist として一歩前に進めたような気がしてます。たとえ登壇内容が高度過ぎて理解できなくても、その場に行き刺激を受けてきたいと思います。

Sansanに転職後、 Ruby や Rails を使って開発をすることになったため、日々学びと気づきの連続です。そんな日々を改めて振り返ると Ruby に対する理解が進んだなと感じる瞬間があったように思います。
それは「クラスはオブジェクトである」ことを理解したときです。

ということで、本記事では「クラスはオブジェクトである」ことの内実について解説していきます。世に解説記事は出回っており、すでに理解している人も多いかとは思いますが、改めて整理していきたいと思います。

前提知識

早速本題に行きたいところなのですが、その前に理解しておくべき前提知識について整理します。
それは、そもそも「オブジェクト、クラスとは何なのか」ということです。それに答えるために、オブジェクトの階層構造について説明します。ここで「あるオブジェクトを作った時にその裏側でどのようなことが起きているのか」を把握します。前提知識と書きましたが、この内容が本記事のメインになります。思ったよりも長くなってしまったので、理解している箇所は読み流してもらえればと思います。

説明用として、以下のコードを用意しました。

class Parent
  def p_method
    "#{self} call p_method"
  end
end

class Child < Parent
  def initialize(name)
    @name = name
  end

  def c_method
    "#{self} call c_method"
  end
end

chd = Child.new('taro')


この時、オブジェクトの階層構造はどうなっているのでしょうか?
図で示すと下のようになります。[*1]

f:id:tommy_0:20190410074508p:plain

それでは次に、階層構造を構成する各要素について解説していきます。
他にも説明の仕方はありますが、機能的に整理すると以下のようになるかと思います。

オブジェクト

図のどれが該当するか

全てが該当します。「なぜそう言えるのか」を説明するのが本題になります。この内容については後ほど説明します。

どんな機能を持つか
  • インスタンス変数の保持

オブジェクトごとに専用のインスタンス変数の一覧を持つことで、オブジェクトごとに状態を管理できます。また、インスタンス変数はインスタンスメソッドの中で定義されるため、同じクラスのオブジェクトであってもインスタンスメソッド呼び出し有無によってインスタンス変数が異なることがあります。

  • メソッドの呼び出し

オブジェクトに対して唯一可能な操作であり、クラスで定義されたメソッドを呼び出すことができます。
メソッドを呼び出した時には、まずメソッドの探索が行われます。メソッド自体はオブジェクトに存在しないため、メソッドを呼び出したオブジェクト(レシーバ)の特異クラスから順番に、該当のメソッドを見つけるまで継承階層を登っていきます。そして、メソッドを実行する時には、そのレシーバをカレントオブジェクトとして処理が行われます。例えば、図にある赤線で示されているのは、chd をレシーバとして p_method を呼び出した時の流れになります。

カレントオブジェクト

インタプリタが追跡している実行環境となるオブジェクトを指します。Rubyのコードは常にカレントオブジェクトの内部で実行されます。
カレントオブジェクト は self で取得することができ、メソッド内で取得した場合はそのレシーバ、メソッド外(クラス/モジュール内)で取得した場合はそのクラス/モジュールを返します。先程「インスタンス変数はインスタンスメソッドの中で定義される」と書きましたが、それは言い換えれば「レシーバ内でインスタンス変数を定義する」ということになります。

クラス

図のどれが該当するか

大文字から始まるものが該当します。例えば、Child などです。

どんな機能を持つか
  • インスタンス化

クラスからオブジェクトを生成できます。全てのオブジェクトは何かしらのクラスから生成されます。

  • インスタンスメソッドの定義

オブジェクトから呼び出せるインスタンスメソッドを定義できます。
クラスはオブジェクトの振る舞いを規定する役割があるため、メソッド自体はオブジェクトではなくクラスに存在します。

  • 親クラスの継承

クラスには継承元となる親クラスが存在します。メソッド探索範囲に親クラスも含まれるため、オブジェクトから親クラスのインスタンスメソッドを呼び出すことができます。

カレントクラス

カレントオブジェクトと同様に、インタプリタが追跡している情報です。メソッド定義を行うと、それはカレントクラスのインスタンスメソッドになります。
カレントオブジェクトは self で参照できますが、カレントクラスを参照するキーワードはありません。カレントクラスは、メソッド内ではカレントオブジェクトのクラス、メソッド外(クラス/モジュール内)ではそのクラス/モジュールになります。

特異クラス

図のどれが該当するか

#から始まるものが該当します。例えば、#Child などです。
本題を説明する際に重要な概念となるので、いくつか補足したいと思います。
特異クラスとは、特定のオブジェクトのみに適用されるクラスのことであり、オブジェクトを一つしか持てないため別名シングルトンクラスと呼ばれます。
普段はその存在を意識することはないかもしれませんが、以下のようにその実態を確認できます。

chd.singleton_class
=> #<Class:#<Child:0x00007fb0bba21138>>

class Child
  class << self
    self
  end
end
=> #<Class:Child>
どんな機能を持つか
  • 特異メソッドの定義

特定のオブジェクトからのみ呼び出せる特異メソッドを定義できます。

  • 親クラスの継承

特異クラスには継承元となる親クラスが存在します。特異クラスの適用対象によって親クラスは異なります。オブジェクトの特異クラスの場合はそのオブジェクトのクラス、クラスの特異クラスの場合はそのクラスの親クラスの特異クラスになります。メソッド探索範囲に特異クラスも含まれるため、オブジェクトから特異メソッドを呼び出すことができます。

f:id:tommy_0:20190410075013p:plain

モジュール

図のどれが該当するか

Kernel のみが該当します。
モジュール単体では継承階層の中には存在しないのですが、モジュールが読みこまれる(include 等が実行される)と、インタプリタが指定されたモジュールの無名クラスを作成し継承階層の中に組み込みます。
この無名クラスの存在は superclass では確認できませんが、ancestors で確認できます。

Child.ancestors
=> [Child, Parent, Object, Kernel, BasicObject]
どんな機能を持つか
  • メソッドの取り込み

クラスや他モジュールにメソッドを取り込むことができます。取り込み形式は2種類あり、取り込み元がクラスの場合にはインスタンスメソッド、特異クラスの場合には特異メソッドとして取り込まれます。

  • 名前空間の提供

モジュール定義の中には別モジュールやクラスを定義できます。大文字で始まる参照(クラス名やモジュール名)は定数であり、これら定数はスコープを持つため、ネストすることでスコープ階層を作ることができます。
クラス定義でも同様のことが行えますが、クラスはそれに加えてインスタンス化や継承を行えるので、目的に応じて使い分けその意図を明確にする観点から通常はモジュールを使います。

  • 関数の定義

モジュール単体から呼び出せる関数を定義できます。実態としてはモジュールの特異メソッドになります。


この辺りの内容は、メタプログラミングRubyパーフェクトRuby などの書籍で説明されているので一読することをお勧めします。

*1:厳密には、オブジェクトが作成された時点では特異クラスは存在しない(特異メソッドを定義する/特異クラス式を評価する/特異クラスの存在を確認して初めて特異クラスが作成される)のですがわかりやすさのために図示しています。また、Moduleクラス/Classクラス/特異クラスもまた特異クラスを持つのですがわかりやすさのために省略しています。

続きを読む

【Geek Seek Toolsで買われた、気になるモノ達】第4回「TerraMat (スタンディングデスクマット)」

f:id:hartmann3555:20190411151441j:plain

はじめに

こんにちは。DSOC Data Direction Groupでデータエンジニアをしている、千葉祐大です。

最近は、発表された新AirPodsではなく、前モデルから正当進化したBeatsのPowerbeats Proが気になりすぎている毎日です。

さて、弊社の社内制度であるGeek Seek Tools*1で購入されたガジェットの中から、僕がイケてると感じたものを気の赴くままに紹介していく連載なのですが、今回は過去に僕がGeek Seek Toolsで購入したスタンディングデスク用のマットTerraMatとスタンディングデスクの関係について紹介したいと思います。

腰痛対策とスタンディングデスクへの淡い期待

僕は高校生のときにバレーボールで腰を痛めてから、ずっと腰痛持ちです。年1くらいでぎっくり腰にもなっており、腰の負担をいかに減らすかに、かなり神経を削っていました。

特に仕事中はコーディングや資料作成に夢中になると同じ姿勢になりがちなので、2015年に初代Apple Watchを買ってからは1時間ごとに「立ちましょう」というメンションに従い姿勢を変えたり、ストレッチをしたりするようにしていました。

そんな形でデスクに座ることをベースに腰をいたわり続けていた中、Sansanにジョインして初めてスタンディングデスクと出会いました。

ご存知のとおり、スタンディングデスクは机の高さを自由に変えることのできる机で、通常の座る位置から立ってPCを置いて作業ができる高さまで、任意の高さに変化させることができます。

これは座る生活から脱却できる!と思いすぐに要望を出しました。*2

そして、念願のスタンディングデスクを手に入れ、意気揚々とスタンディング生活をはじめました。

はじめから座るつもりはなかったので、貸与されている椅子をオフィスの端に追いやり、座って仕事をするなどという、前時代的なスタイルとは永遠に決別しようと心に決めて、机に向かいました。

*1:生産性向上に資するガジェット・デバイスその他が購入しやすくなる制度。Geek Seek Toolsの詳しい説明については第1回をご覧ください

*2:一番はじめは通常の机が貸与され、希望に応じてスタンディングデスクに変更できる仕組みでした

続きを読む

Sansanプロダクトが生まれ変わります

SansanでChief Product Officerをやっている大津です。
先月のSansan Innovation Project 2019で発表しましたが、この度、法人向けクラウド名刺管理サービス「Sansan」のコンセプトが新しくなりました。
新しいコンセプトは「Sansan, Where Business Starts - 名刺管理から、ビジネスがはじまる」です。
今月はプロダクトリニューアルも予定しており、名実ともに新しいSansanに生まれ変わることになります。
本日はこの新コンセプトの下、プロダクトリニューアルを通してどのような思いで価値を提供していきたいか、改善していきたいかについて少しお話ししたいと思います。

f:id:s_yuka:20190412135743j:plain

新コンセプトにより「活用」を強化

これまでのコンセプトであった「名刺を企業の資産に変える」においては、企業が名刺やそれを通した人脈を資産としてしっかり管理できるように、という目線でプロダクトを提供してきました。
これまでのSansanで実現していた名刺を資産化するための便利な管理機能・運用機能はもちろん今まで通り強化を続けていくのですが、新コンセプトに変わった事で「資産にした名刺データや人脈をどのように活用していくか」によりフォーカスしたプロダクト作りを意識したいと考えています。
今回打ち出している3つの新ソリューション、

  • AI名刺管理
  • 同僚コラボレーション
  • 顧客データHub

これらも「活用」を強く意識しています。
どんな活用をイメージしたソリューションなのか、一つずつご説明します。

f:id:s_yuka:20190412140132j:plain

続きを読む

Doc2Vecによる文書ベクトル推論の安定化について

はじめまして,Sansan DSOC R&Dグループ インターンの小林といいます。
2月下旬から3月末までの間,主に自然言語処理 (NLP) に関連した研究開発に挑戦させて頂きました。大学でNLPを専攻している訳では無いですが,他の研究員の方やインターンの先輩とのディスカッションなど,とにかく刺激的な日々でした。

本稿はNLPブログということで,近年のNLPでスタンダードとなっている,単語・文書の埋め込み手法に言及します。

  • TL; DR
  • Word2Vec / Doc2Vecについて
  • 文書ベクトルによるニュース文書属性判定を試す
    • タスク:スポーツニュースの内容属性の推定
  • Doc2Vecによる文書ベクトル推論の問題点
  • 精度検証実験
    • 実験実行と結果
      • 実験① 以下サンプルテキストに対する独立した2度の文書ベクトル推論(infer_vector()の実行) ×100試行
      • 実験② 複数の文書に対する独立した2度の文書ベクトル推論(infer_vector()の実行) ×100試行でのcos類似度平均と分散の測定
  • 考察・まとめ
  • おわりに

TL; DR

  • Doc2Vecはお手軽に便利に使える
  • 学習済み単語埋め込みを用いたラベル推論をしてみた
  • 文書ベクトルを算出するDoc2Vec.infer_vector()の機能の精度と安定性についての実験と考察をしてみた
  • Doc2Vec.infer_vector()を呼ぶ際は,推論した文書ベクトルのばらつきを減らすために短い文書ほどパラメータepochsを大きめにした方が良さそう

Word2Vec / Doc2Vecについて

Word2Vec とはその名の通り Word-to-Vector を実現する分散表現(単語埋め込み)手法であり,以下のような単語同士の加減算が可能であるとして,5年程前に一躍有名になったとのこと。

マドリード − スペイン + フランス = パリ *1

各単語のベクトル表現により,内積計算で単語間の類似度を数値化することが出来るため,非常に便利です。

参考:コサイン類似度について
参考:Word2Vec のニューラルネットワーク学習過程を理解する

Doc2Vec は,Word2Vecでの単語のベクトル化手法(CBoW, Skip-Gram)をベースに,単語の羅列である文書もベクトルにしてしまおうというもので,多数の文書の中から,文書IDを入力値に,その文書内からランダムに選択された単語を予測することで文書全体の意味を獲得する手法(DBoW : Distributed Bag of Words)と,文脈窓の単語と文書IDを結合したものから中心の単語を予測することで文書の文脈情報を取得する手法があります。(PV-DM : Paragraph Vector - Distributed Memory) 一般的に後者の方が表現の精度が良いですが,前者の方が省メモリであるとされています。
参考:Distributed Representations of Sentences and Documents
参考:Paragraph Vector DBOWの憂鬱

f:id:atsu_jg43yr:20190329144217p:plain
DBoWの模式図:論文より引用

最近は単一モデルで各種タスクのSoTAを達成したBERT *2,ELMo *3のようなAttention / Transformerベースのモデルが主流となりつつあり,NLPをやっている方は発表から5年も経っているWord2Vec, Doc2Vecに対してレガシー感を覚えるかも知れません。

しかし,Doc2Vecは,

  1. gensimによる実装で早い・使いやすい・学習させやすい
  2. DBoWなどは省メモリ*4である
  3. 技術ブログなど,日本語での文献やエントリが非常に豊富 *5

という点を考慮すると,まだまだ便利に使えるものなのではないでしょうか。特に,Doc2Vecの利点はその実装から簡単に得られるParagraph Vector (文書ベクトル) によって単語と可変長の文書を同次元のベクトル表現にし,同列に扱えることと,モデル利用が簡単であることが利点だと考えます。

Doc2Vecによる文書ベクトルと単語ベクトルを同じ空間で扱い内積計算 (類似度算出) することで,例えば『Webからスクレイピングした大量のテキストデータの中から任意のトピックを含むデータのみを抽出したい』というような,特定の文書に対して教師データ無しで任意のラベルセットの中から一つに分類したい状況に応用できます。

*1:Distributed Representations of Words and Phrases and their Compositionality

*2:arXiv:BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding

*3:arXiv:Deep contextualized word representations

*4:論文Distributed Representations of Sentences and Documentsで言及がある

*5:例えば,Qiitaの記事タグ数ではWord2Vecがニューラル言語モデルの中では圧倒的に多い。各件数は,word2vec : 163 / doc2vec :35 / fastText : 38 / ELMO : 27 / BERT : 17 となっている

続きを読む

【Techの道も一歩から】第18回「アルゴリズムをコンテナ化しWebAPIとして利用可能に」

こんにちは。 DSOC R&D グループの高橋寛治です。

最近はスマブラSPの研究活動に熱心に取り組んでおり、少しずつ成果が出始めておりホッとしているところです。

さて、研究開発活動では何かしらのモデルやアルゴリズムを開発し、それを共有します。 共有する際に、環境の違いにより動作しなかったり、そもそもの利用方法がわからなかったりします。

こういった問題に対して、Docker コンテナにより WebAPI として提供することで、環境を問わず、かつ利用方法も明確にする方法について紹介します。

API提供するコンテナの作成

アルゴリズムを組み込むための Docker コンテナを作成します。

ディレクトリ構成

$ tree
.
├── Dockerfile
├── api.py
└── requirements.txt

Dockerfile

FROM python:3.6

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

CMD ["hug", "-f", "api.py"]

requirements.txt

hug==2.4.1

api.py

import hug


@hug.get("/")
def health_check():
    return {
        "message": "Good."
    }

hug は Python で簡単に API を提供するためのフレームワークです。 デコレータで任意のメソッドについてラップするだけで、APIエンドポイントとして動作します。 アルゴリズムのAPI化においてラップするだけで動作するというのは魅力的です。

ファイルを準備したら、コンテナをビルドして動かしてみましょう。

$ docker build -t tech_blog_18
Sending build context to Docker daemon  4.096kB
Step 1/6 : FROM python:3.6
...
Successfully tagged tech_blog_18:latest
$ docker run -it --rm -p 8000:8000 tech_blog_18
...
Serving on :8000...

さてコンテナが稼働中ですので、実際に動作確認をしてみます。 curl コマンドで localhost:8000 を叩くことで、メッセージが返ってくるはずです。

$ curl -XGET "http://localhost:8000"
{"message": "Good."}

WebAPI としての動作をしていることが確認できました。 この関数内で任意のアルゴリズムを動作させることで、コンテナ化します。

続きを読む

壁を登るエンジニアたちについての考察

f:id:s_yuka:20190404141033j:plain
Sansan事業部マーケティング部フロントエンドエンジニアの吉田です。

弊社には「よいこ」*1という社内の部活動制度があり、筆者はボルダリング部に所属しています。
ボルダリング部は部長である弊社CTOの藤倉を筆頭に、プログラミング等を生業にしているITエンジニアの占める割合が非常に多い。

実際、筆者もボルダリングにはまって、なるほどこのスポーツはエンジニアに向いている箇所が多分にあるなと思ったので考察してみました。

ボルダリングとは?

ボルダリングは壁についたホールドと呼ばれる石を使って壁を登っていくスポーツです。
壁一面にホールドが散りばめられているのですが、どのホールドを使ってもよいというものではなく、「スタート」の印があるホールドから決められたホールドを伝って行き、「ゴール」の印があるホールドを両手でつかめばクリアという、ゲーム要素のあるスポーツです。

伝っていくホールドの数や形によって難易度が変わってくるため、初心者から上級者までが同じ壁で楽しめるスポーツです。

ボルダリングとは

オブザベーション

ボルダリングは課題と呼ばれる各コースがあり、上級者になるほど、いきなり登り始めることはしません。
スタートホールドと伝っていくホールド、そしてゴールホールドを観察し、どのように体を使って登っていけばその課題をクリアできそうかを、まずは頭の中でシミュレーションします。
その行為を「オブザベーション」といいます。

一方、エンジニアは日々の仕事で、設計書(仕様書)を元に機能を実装していくということをしています。
いきなりプログラムを書き始めるということはなく、まずはどのようにプログラミングして組み立てていけば仕様どおりのものができあがるかを、脳内や紙に書き出すなどしてシミュレーションします。

ムーブ

やさしい課題はハシゴを上る要領で難なくクリアできてしまいます。しかし難易度が上がると様々な「ムーブ」といわれるテクニックを駆使してひとつひとつホールドを掴み登攀(とうはん)して行かなければなりません。

一方、エンジニアも「Hello World!」を表示するだけ、というような簡単なことであればさっくりと実現できてしまいますが、基本的に仕事は容易くないのが常です。
エンジニアとして自分の持つ全ての知識とテクニックを振り絞って、求められるものをキーボードを打って創り出して行かなければなりません。

トライアル・アンド・エラー

簡単な課題ならサラリとクリアできてしまうのですが、実力以上の課題に臨むと一度で成功するとは限りません。途中で落下してしまいます。
失敗してもその原因を考え、どのようにすれば今までの失敗を繰り返さずにゴールまでたどり着けるかを考察し、再チャレンジし、成功するまで繰り返します。

エンジニアも複雑な要件を実装する場合、頭で考えたようにプログラミングしても、途中でエラーにあたったりして一度で上手く行くとは限りません。
試行錯誤を繰り返し、コードを正解へと導き創りあげます。

成功、喜び

そして、失敗し、試行錯誤を繰り返し、ついに課題がクリアできたときの喜びは格別です。
それはまたエンジニアの仕事もしかり。


というわけで、ボルダリングとプログラミングの共通点を列挙してみましたが、ボルダリングはエンジニアであろうがなかろうが楽しいし、運動不足は解消できるし筋力はつくし痩せるしでいいところしか見当たりません。
一度体験してみることをおすすめします。

*1:「よいこ」とは「よいコミュニティ」の略で、メンバー同士のコミュニケーションの活性化や一体感の向上など、より良いコミュニティを作るために必要な各種施策を従業員発で企画し、実施していく制度です。

© Sansan, Inc.