コネヒト開発者ブログ

コネヒト開発者ブログ

FloatingPanelに表示するViewをSwiftUIにする

こんにちは!コネヒトでiOSエンジニアをやっていますyanamuraです。

コネヒトで開発しているママリのiOSアプリでもSwiftUIを使い始めました。 なるべく新しい部分はSwiftUIでつくっていくぞ!ということで、こちらのハーフモーダルの部分をSwiftUIで実装しました。

f:id:yanamura:20211116002056p:plain:w200

ハーフモーダルといえばiOS15からはUISheetPresentationControllerで実装できますが、まだiOS13をサポートしているのでFloatingPanelを利用しました。

導入自体はとても簡単でFloatingPanelControllerに表示したいSwiftUIのViewをUIHostingControllerでラップしたものをset(contentViewController: )して表示するだけです。 しかし、ただ一点、FloatingPanelControllerにSwiftUIのscrollViewをどうやって渡すかという問題が発生します。

import SwiftUI
import FloatingPanel

final class ViewController: UIViewController {
    @IBAction func buttonTap(_ sender: Any) {
        let fpc = FloatingPanelController()
        let contentView = ContentView() // ContentViewはSwiftUI
        let contentViewController = UIHostingController(rootView: contentView)
        fpc.set(contentViewController: contentViewController)

       // fpc.track(scrollView: scrollView) // SwiftUIからscrollViewをどうやって取得する?!

        present(fpc, animated: true)
    }
}

解決方法は色々あると思います。 FloatingPanelのExampleにもあったり、手段を問わずにscrollViewを取得するのであればintrospectを使うという手もあります。

ここでは、シンプルに自前でScrollViewをつくるやり方でやってみます。

まず、このように自前でUIScrollViewを使ってSwiftUI版のScrollViewを自作します。 ポイントとしては引数のcallbackでscrollViewを呼び出し元に渡せるようになっています。

struct CustomScrollView<Content: View>: UIViewRepresentable {
    private let scrollView = UIScrollView()
    private let content: UIView

    init(callback: (UIScrollView) -> Void, @ViewBuilder content: () -> Content) {
        self.content = UIHostingController(rootView: content()).view
        self.content.backgroundColor = .clear
        callback(scrollView)
    }

    func makeUIView(context: Context) -> UIView {
        content.translatesAutoresizingMaskIntoConstraints = false
        scrollView.addSubview(content)
        let constraints = [
            content.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            content.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            content.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
            content.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
        ]
        scrollView.addConstraints(constraints)
        return scrollView
    }

    func updateUIView(_ uiView: UIView, context: Context) {}
}

FloatingPanelに表示するViewをつくる場合はこのようにScrollViewの代わりに自作のCustomScrollViewを使うようにします。

struct ContentView: View {
    let scrollViewCallback: (UIScrollView) -> Void

    private let animalList = ["dog", "cat", "lion", "snake", "wolf", "bird", "gorilla", "zebra", "koala"]

    var body: some View {
        CustomScrollView(callback: scrollViewCallback) {
            VStack {
                ForEach(animalList, id: \.self) { animal in
                    Text(animal)
                        .font(.system(size: 18))
                        .padding(20)
                }
            }
        }
    }
}

あとはContentViewを生成するときに渡すcallback内でtrack(scrollView:)を呼ぶようにすれば完了です。

final class ViewController: UIViewController {
    @IBAction func buttonTap(_ sender: Any) {
        let fpc = FloatingPanelController()

        let contentView = ContentView { scrollView in
            fpc.track(scrollView: scrollView) // ここでscrollViewをセットする
        }

        let contentViewController = UIHostingController(rootView: contentView)
        fpc.set(contentViewController: contentViewController)
        present(fpc, animated: true)
    }
}

SwiftUIを部分的にとりいれるときは、UIKit,SwiftUIのそれぞれのView間での相互の依存が発生しないようなところでやるとハマりどころが少ないかもですね!

ケンオールとNatural Language APIを使って地域登録を実現してみた!

こんにちは。バックエンドエンジニアの田中ことTOCです。 今回は、最近ママリアプリでリリースされた地域カテゴリのリニューアルについて、ケンオールとNatural Language APIを用いて実装したので、その内容や工夫ポイントなどを紹介していこうと思います。


目次


はじめに

ママリではQ&Aが様々なカテゴリに分かれているのですが、その中に「地域」というカテゴリが存在しています。このカテゴリでは自身が登録する地域に特化したQ&Aが見られるのですが、以前までは都道府県単位でしか見れず、自身が住んでいる地域とは関係ない質問も多く表示されてしまっていました。 そこで、今回の改善では都道府県だけでなく、市区町村まで登録できるようにして、カテゴリ内で絞り込みができるようになりました。これにより、自分の地域に特化した情報を探しやすくなるなど使いやすさの向上をはかりました。

f:id:toc8:20211110045435p:plain
登録した地域でQ&Aの絞り込みができるようになりました(開発環境の画面)

今回はこの地域カテゴリ改善で市区町村を登録する際にケンオールとNatural Language APIを合わせて使ってみました。

ケンオールを使ってみた!

まずは市区町村をどうやって取得してこようかと考えました。候補として、位置情報、郵便番号、フリーワード検索などが挙がりましたが、社内でのアンケートなどをもとに、郵便番号検索をまず初めに実装しようという話になりました。 そこで真っ先に候補に挙がったのがケンオールでした。

ケンオールは郵便番号APIというものが用意されており、郵便番号をリクエストとして送ると、レスポンスとして都道府県名や市区町村名、市区町村コードなど郵便番号に紐づく情報が簡単に取得することができます。 今回は、これっ!といったライブラリが見つからなかったことや、そもそもAPI自体がシンプルな設計になっていたので、CakePHPのHttpClientを用いて自前で実装を行いました。

<?php
public function postalcode(string $postalCode): array           
{                                                               
    $response = $this->http->get(                               
        "https://api.kenall.jp/v1/postalcode/$postalCode",      
        [],                                                     
        ['headers' => ['Authorization' => "Token $this->token"]]
    );                                                          
    
    // レスポンスの処理
}                                             

これにより、常に最新の情報を取得できるので、データの更新コストなどを考えずに実装できて魅力的ですね! ただ、ケンオールでは「神奈川県横浜市青葉区」を取得した場合、下記のように市区町村データが分割されずに取得できます。

{
    "jisx0402": "14117",
    "postal_code": "2270000",
    "prefecture": "神奈川県",
    "city": "横浜市青葉区",
    .
    .
    .
}

今回は「横浜市」と「青葉区」は分割して取りたい...でも市区町村ってたくさんありすぎてパターン化は難しそう...となったため、GoogleのNatural Language APIを利用することを検討しました。

Natural Language APIで市区町村をいい感じに分割するぞ!

Natural Language APIはGoogleが提供するAPIで、与えた単語について自然言語処理を使って、感情分析、エンティティ分析、エンティティ感情分析など、さまざまな分析を簡単に行うことができます。今回はこのAPIの中でも「エンティティ分析」を用いて単語分割をしてみました。 実装としてはGoogleが公式で提供しているgoogle-cloud-phpというライブラリを用いて実装しました。

まずはドキュメントを参考にクライアントライブラリを設定などをしていきます。 そしてライブラリが使えるようになったら、実際に単語を与えて分割をしてみます。

<?php

public function analyzeEntities(string $content): Collection                         
{                                                                                    
    $annotation = $this->client->analyzeEntities($content, ['language' => 'ja']);                                                   
                                                                     
    // レスポンスの処理                                      
}                                    

上記に「横浜市青葉区」を与えると、以下のようなデータ(配列に変換済み)が取得できます。

Array
(
    [0] => Array
        (
            [name] => 横浜市
            [type] => LOCATION
            [metadata] => Array
                (
                    [wikipedia_url] => https://en.wikipedia.org/wiki/Yokohama
                    [mid] => /m/0kstw
                )

            [salience] => 0.5485816
            [mentions] => Array
                (
                    [0] => Array
                        (
                            [text] => Array
                                (
                                    [content] => 横浜市
                                    [beginOffset] => 0
                                )

                            [type] => PROPER
                        )

                )

        )

    [1] => Array
        (
            [name] => 青葉区
            [type] => LOCATION
            [metadata] => Array
                (
                    [mid] => /m/08ddxr
                    [wikipedia_url] => https://en.wikipedia.org/wiki/Aoba-ku,_Yokohama
                )

            [salience] => 0.45141837
            [mentions] => Array
                (
                    [0] => Array
                        (
                            [text] => Array
                                (
                                    [content] => 青葉区
                                    [beginOffset] => 9
                                )

                            [type] => PROPER
                        )

                )

        )

)

うまく分割されていることがわかります。 salience というのは文章中の関連度みたいなもので、分割された単語はこの salience スコアが高い順に並べられています。そのまま配列の順番で抽出すると、単語によっては青葉区→横浜市のような順番で抽出されてしまうため、beginOffset を起点に並び替えてから抽出するようにしています。

このNatural Language APIを用いることで、こちら側で全パターンを網羅するような分岐処理を書かなくても、市区町村を分割した状態で取得できるようになりました!

このようにケンオールとNatural Language APIを組み合わせることで各地域を分割した状態で登録できる状態が実現できました。

工夫した点

ここまでで各APIを用いて、どうやって市区町村登録を実現したのかを述べてきました。 ただ市区町村はいろんなパターンがあり、個別で対応した点もいくつかあったので紹介します。

一つの郵便番号が複数の市区町村に紐づいている

「432-8053」この郵便番号は 「静岡県 浜松市 中区」と「静岡県 浜松市 南区」両方に紐づいています。なのでケンオールでこの郵便番号を検索すると複数のオブジェクトが返ってきます。この場合、どちらか一つの市区町村に寄せてもよかったのですが、今回私たちが行いたい絞り込み検索では中区と南区を区別した方がユーザビリティも上がると考えたので、複数取得できるようにして、選択できる形式にしました。

f:id:toc8:20211110043442j:plain
一つの郵便番号が複数の市区町村に紐づいている場合

分割できない市区町村が存在する

Natural Language APIはいい感じに単語を分割してくれますが、それでもうまく認識されない単語も存在します。 例えば「919-0212」の郵便番号は「福井県 南条郡 南越前町」に紐づきますが、これをNatural Language APIを利用すると下記のように分割されずに返ってきます。同じような状態になる市区町村は何個かあったので、こちらは個別対応でロジックを書くようにしました。

Array
(
    [0] => Array
        (
            [name] => 南条郡南越前町
            [type] => LOCATION
            [metadata] => Array
                (
                )

            [salience] => 1
            [mentions] => Array
                (
                    [0] => Array
                        (
                            [text] => Array
                                (
                                    [content] => 南条郡南越前町
                                    [beginOffset] => 0
                                )

                            [type] => PROPER
                        )

                )

        )

)

日本語指定をしないと別言語として認識される場合がある

「778-0003」という郵便番号は「徳島県 三好市」に紐づきますが、三好市が中国語と認識されてしまいうまく分割できないケースも発生しました。今回利用したライブラリはオプションで言語の指定ができるので、日本語を扱う場合は下記のように日本語指定をすることでうまく分割できるようになります。

<?php

$this->client->analyzeEntities($content, ['language' => 'ja']);

まとめ

以上のような工夫もあり、ママリの地域カテゴリがリニューアルされました。 これで自分の地域を登録して、知りたい情報にいち早く辿り着けるようになりました\(^^)/ 地域カテゴリでは自身が住んでいる地域の病院や子供と遊べる施設などに関する質問が多いです。 今回の改善で利用者がさらに増えていって、自分の地域の子育て情報を得られる有用な場になっていくのが楽しみです!

ちなみにケンオールの利用に伴い、導入事例として掲載していただきました。 もしよければこちらもご覧ください。

最後に、今回紹介したママリアプリを一緒に開発してくれるエンジニアを募集しています。 少しでも興味をもたれた方は、ぜひ一度お話させてもらえるとうれしいです。

hrmos.co

PyCon JP 2021で登壇してきました!

こんにちは!MLエンジニアのたかぱい(@takapy0210)です。

本エントリでは、先日行われたPyCon JP 2021に登壇してきた様子をお届けしようと思います!

PyCon JPとは

Python や Python を使ったソフトウェアについて情報交換、交流をするためのカンファレンスです。
今回はオンラインとオンサイトの同時開催で、私はオンラインで参加しました。

  • 概要:日本最大級のPythonユーザカンファレンス
  • 日時:2021年10月15日(土)~ 2021年10月16日(日)
  • 会場:オンライン(ZOOM + Discord)/オンサイト(ベルサール神田)

2021.pycon.jp

トーク内容

コミュニティサービスにおけるレコメンデーションの変遷とMLパイプラインについて」というタイトルでトークしました。

弊社が運営している「ママリ」というサービスにおいて、レコメンデーションエンジンを0から構築していった話を、各フェーズで用いたアルゴリズムや機械学習パイプラインと共に紹介しました。

アルゴリズムに関しては

  • TensorFlowを用いた行列分解によるレコメンデーション
  • トピックモデルを活用したレコメンデーション
  • ニューラルネットワークを活用したSoftmax Recommendation

などについて話しました。

機械学習パイプラインについては時間の都合上割愛した部分も多いので、こちらの詳細が気になる形は、先日登壇したAWS Dev Dayの資料を見ていただけると嬉しいです!

カンファレンスの感想

私は今回初めてPyConに参加しましたが、今まで登壇したオンラインイベントの中でも登壇体験がすごい良かったです。

PyCon公式のdiscordで各トークごとに部屋が用意されており、トーク中はその部屋の中でオーディエンスの人たちがコメントや質問を投稿してくれる、という設計でした。
そのおかげもあり、オンライン登壇でよくある「誰に向かって話しているんだワイは...」といった感情や、「オーディエンスの雰囲気が全然ワカラネェ・・・」という状態にならずに話すことができました!

登壇後に行われたask the speakerでも「発表内容、とても興味深かったです」や「このような方法を試してみても面白そうですね!」といったコメントをいただくことができ、大変有意義な時間を過ごすことができました。

また、他のトークも面白いものばかりでした。
個人的には「Loggingモジュールではじめるログ出力入門」がとても勉強になりました。
Logging雰囲気で使っていました()が、このスライド読むとLoggingモジュール全体像を掴むことができ、とても分かりやすかったです。

各セッションのスライドなどはタイムテーブルから飛べるようになっているので、気になる方は覗いてみてください。

最後に

PyCon運営スタッフの皆様、当日コメントや質問をしてくださった皆様、speakerの皆様、ありがとうございました & お疲れ様でした!

来年も登壇側からイベントを盛り上げられるように日頃のインプットを頑張っていこうと思います!

CakePHPの国際カンファレンス「CakeFest Virtual 2021」に登壇してきました!

こんにちは。CTOをやっている@itoshoです。コネヒトではここ1ヶ月でiOSDCやAWS Dev Day、PHPカンファレンス、PyCon JPと大きなカンファレンスへの登壇が続いています( ・∀・)イイ!!

というわけで、僕も負けじ?と「CakeFest Virtual 2021」に登壇してきたのでその様子をお届けしたいと思います。

CakeFest Virtual 2021とは?

CakeFestはPHPのフレームワークであるCakePHPの国際カンファレンスで、今年は昨年に引き続きオンラインで開催されました。構成としては初日にCakePHPのコアメンバーによるワークショップが開催され、2日目にカンファレンス形式で世界各国のエンジニアからの発表が行われました。

イベントの規模は、公式発表ではないので推測になりますが、2日目のカンファレンスは常時100人前後の方が参加していたと思います。雰囲気としては、オンラインということもあり参加者同士のコミュニケーションはそこまで多くはありませんでしたが、それでも2019年に日本で開催された時と同様にアットホームな空気が終始流れており、CakePHPのコミュニティの温かさを改めて感じることが出来ました。

tech.connehito.com

登壇内容「Components Reconsidered」

僕は「Components Reconsidered」(邦題: Component再考)というテーマで登壇をしました。CakePHPにはComponentと呼ばれるMVCモデルにおけるController層の共通処理をまとめたり、拡張したりする仕組みがあります。Component自体は非常に便利なのですが、CakePHP4.2からDIコンテナが実装されたこともあり、今後Componentの出番はより限定的になっていくのではないか?という問題意識がありました。

ですので、このセッションではComponentの機能やユースケースをおさらいしながら、代替方法の検討(具体的にはTraitやDIを取り上げました)を行い、その上でそれぞれの使い分けをどうするかといったことをお話させていただきました。

詳しくは以下のスライドをご覧ください。

speakerdeck.com

疎い英語で恐縮ですが、動画も公開されています。*1

www.youtube.com

登壇後の反応

内容的に明確な答えがあるテーマではなく、個々人のスタンスや環境に依る部分も大きいため、ドキドキしていたのですが、当日Slackに寄せられた反応をみる限り、概ね好評だったように思います。*2

特にCakePHPのLead DeveloperであるMark StoryさんとCore MemberであるAdmadさんからポジティブなフィードバックをいただけたのは自信になりました。

f:id:itosho525:20211016002114p:plainf:id:itosho525:20211016002046p:plain
コアコミッターからのフィードバック

英語での発表

英語での登壇では2年前のCakeFestで一度経験済みなのですが、今回は前回以上に緊張しました。前回は日本開催で翻訳者の方もいたので何とかなる感があったのですが、今回はそういった助け舟もない状態だったので、登壇が決まったタイミングから当日までずっと変なプレッシャーがありました。

その中でもCakePHPのコミュニティマネージャーの方が事前にリハーサルを丁寧に行ってくださったのと、同じ日本人である @yakitori009 さんが登壇仲間だったのは心強かったです。

準備としては、①サンプルコードの作成→②スライドの作成(英語)→③トークスクリプトの作成(日本語)→④トークスクリプトの翻訳(英語)→⑤ひたすら練習という流れで行いました。少なく見積もっても、国内のカンファレンスで登壇するときの5倍は準備に時間がかかりました。

ちなみにこのあたりは前回ブログにまとめたものがあるので、興味がある方はご覧ください。

itosho525.hatenablog.com

前回との差分で言うと、前回よりも技術的に突っ込んだ内容だったので、④の翻訳作業は識者(英語とソフトウェア開発双方に詳しい方)の方にご協力をいただきました。*3

また、英会話についてはコロナ渦ということもあり、数ヶ月からCAMBLYを頑張っています。あとは、DeepLに死ぬほどお世話になりました。ありがとうDeepL。

面白かったセッション3選

開催時間が日本時間の深夜ということもあり、全てのセッションをリアルタイムで観れたわけではないのですが、その中で個人的に特に面白かったセッションを3つ紹介します。

  • Workshop 3 - Mark Story
    • CakePHP4.3からfixtureの仕様が変わる(スキーマ管理とテストデータが分離される)のですが、具体的な変更内容を知ることが出来てよかったです
  • Fighting COVID-19 with Contact Tracing in Czech Republic
    • チェコでのCOVID-19対策事例の話で、CakePHP製の接触者追跡システムがダイレクトに社会の役に立っている話がエモくて最高でした
  • Our Standards and Why We Use Them
    • Jump24社で採用されている規約の話なのですが、コード例が多くて、1コントローラー1アクションにしてるんだとかリポジトリパターン採用してるんだとかが垣間みれて参考になりました

全てのセッションのスライドや動画はこちらにまとまっています。

cakefest.org

まとめ

正直、英語での発表は準備含めてとても大変ではあるのですが、その分学びも多かったです。やはり一人のエンジニアとして広い世界で勝負していきたいので、引き続きチャンスがあればどんどん登壇していきたいなと思っていますし、コロナが落ち着いたら実際に海外に行って、各国のエンジニアともっと対話したいなぁと改めて思いました。

また、冒頭にも述べた通り、CakePHPのコミュニティの温かさは本当に素晴らしく、海外カンファレンスの参加や登壇はCakeFestからはじめるのが一番よいのでは?と思えるぐらいですので、皆さんも是非来年のCakeFestに参加してみてください!

そして、僕も微力ながら、CakePHPのコミュニティに貢献していければと思っています。

技術コミュニティに貢献したいエンジニア募集中!

コネヒトでは「Beyond a Tech Company」というテックビジョンを掲げているのですが、テックカンパニーの条件の一つとして「技術コミュニティの発展に貢献している」ということを重要視しています。

というわけで、コネヒトではプロダクト開発だけではなく、OSS貢献やカンファレンス登壇などアウトプットが好きなエンジニアを大大大募集しております!

1mmでも興味がありましたら、まずはカジュアルにお話させてください。

meety.net

*1:機会があれば、日本語でも再演したいなと思っています…!

*2:CakePHPの公式Slackは http://cakesf.herokuapp.com から参加出来ます。また、今回のイベントは #cakefest チャンネルからその様子を伺うことが出来ます。

*3:前回も社内外からご協力をいただいたのですが、前回はどちらかというと英語のレビューが主でした。

PHP Conference Japan 2021に登壇してきました

皆さん、こんにちは。バックエンドエンジニアの山田(@rukiadia)です。

10/2、3に開催されたPHP Conference Japan 2021で参加・登壇してきたので、登壇内容の紹介とイベントの参加メモを書かせてもらいました。

登壇について

弊社からはスポンサーツアーでの登壇を含め、3名が登壇しました。

スポンサーツアーでは、コネヒトの会社のことや作っているサービスのこと、技術組織について紹介させていただきました。残念ながら資料の公開は難しいのですが、カジュアル面談などでお話できると思いますので、お気軽にご連絡ください!

今こそ深堀りする、PHPのDockerイメージ

speakerdeck.com

こちらは私の登壇資料です。コネヒトではCakePHPやPHPのバージョンアップに注力しており、その過程で学んだ内容を盛り込んだ内容となります。

PHPerがISUCON11にチャレンジした時のあれこれ

speakerdeck.com

こちらはチームメンバーの@takobaが登壇したLTです。@takobaと私を含めた3名でISUCON11の予選に参加してきたので、そのふりかえりをした内容となります。

資料でも振れられていますが、ISUCONはコードを見ているだけではダメで、アプリケーションエンジニアの総合力を問われる内容でした。普段の業務ではなかなか味わえないスピード感と緊張感を体験できる、とても楽しい経験となりました。

カンファレンスの感想

PHP Conferenceでは参加者のコミュニケーションの場として、公式のdiscordが提供されています。PHP Conferenceには去年も参加させていただきましたが、discordを介したコミュニケーションが去年よりも活発だった印象です。表では登壇がおこなわれつつ、舞台裏で内容に関するディスカッションが盛り上がるという構図はdiscordならであり、お祭りっぽい雰囲気を感じることができました。

また、今年からYouTubeのコメント欄が無効になったのも印象的ですね。YouTube Liveはラグがあって登壇中に見るのが難しいのですが、今年はdiscordに集中するだけで良くなり登壇側の負担が減りました。

補足ですが、PHP ConferenceはYoutubeのアーカイブが配信されているので、気になるセッションがあれば見返すことが可能です。

www.youtube.com

まとめ

まずはカンファレンスの運営に携わっている皆様、今年も本当にお疲れ様でした!登壇前の接続テストもあったおかげで、登壇に対する不安は全くない状態で登壇ができました。

来年のPHP Conferenceがどういう形式(オンライン or オフライン)になるかは分かりませんが、次回も登壇側からイベントを盛り上げられるように日頃からのインプットを頑張っていこうと思います。

AWS Dev Day Online Japan 2021に登壇してきました!

f:id:taxa_program:20211004165400p:plain

みなさんこんにちは。 MLエンジニアのたかぱい(@takapy0210)です。

本エントリでは、先日行われたAWS Dev Day Online Japan 2021にインフラエンジニアの永井(@shnagai)と私が登壇してきましたので、簡単に内容を補足しながら紹介しようと思います。

Step Functions × AWS SAMで実現する家族ノートの底運用コストETL基盤

永井からは、弊社の新サービスである「家族ノート」で運用しているETL基盤の構築・運用方法について話しました。

データが価値となる新サービスにおいてデータ基盤の意思決定から、属人化しないためのAWS SAMを使ったコード管理など、実際のコードなどを交えて紹介しています。

データ基盤を構築する上でアーキテクチャに悩んでいる方や、AWS SAMを使ったサーバレスリソースの管理方法について知りたい方の参考になる発表内容でした。

自分も新しいETL処理を追加する際にコード管理のありがたみを体感しました!(とても簡単に処理を追加することができました)

SageMaker StudioとStep Functionsを用いてMLOpsへの一歩を踏み出そう

私からは、AWSのサービスを組み合わせて、MLOps基盤を構築してきた事例について話しました。

PoCフェーズ・運用フェーズそれぞれの関心事や、各フェーズでMLOpsに求められる役割、課題を整理したのち、それぞれの課題についてAWSのサービスを用いて一歩ずつ解消してきた方法を紹介しています。

MLOpsやりたいけど最初どこから手をつけたら良いだろう、と悩んでいる方の参考になれば良いなと思います!

電笑戦が面白かった(お笑い的にも、技術的にも)

他のセッションは時間の関係であまり覗くことができなかったのですが、2日目の17時55分〜に行われた「ボケて電笑戦 ~AIは人を笑わせられるのか~」がとても面白かったです。

"ボケて"に関しては、こちらの動画を見ていただけるとイメージしやすいかと思います。

www.youtube.com

ボケる対象となる画像を入力として、その画像に対する「ボケ」を出力するのですが、人間が思いつく様なオーソドックスなボケ(この時点でも結構すごいと思いました)から、人間では思いつかない様な壮大なボケまで登場していて、技術の進化はすごいな、と楽しく参加させていただきました。

当時の状況やどんなボケが披露されたのかは、#電笑戦 でTwitterを検索するといくつか見れるので、興味のある方は見てみてください!

個人的には、特急ラピート*1という電車の画像がお題として与えられた時の

「車検の代車がこれ」

というボケが最高でした(笑)

最後に

オンラインカンファレンスは、現地での交流や雰囲気を味わいづらい反面、居場所にとらわれずに参加できるのがメリットかなと思っています。そのおかげもあってか、今回は登録者数・参加者数ともに過去最大のAWS Dev Dayだったようです。
(個人的にはオンサイト開催の方が好きなので、来年あたりはどこかの会場で現地開催しつつ、リモート配信も同時に行えるような情勢になっていれば嬉しいな〜と思います)

また、動画のアーカイブも1か月後程度を目処に公開されるようなので、当日視聴できなかった〜〜という方はこちらの公開をお待ちいただければと思います。

当日ご視聴いただいた方、素敵なカンファレンスを主催してくださったAWSの運営の皆様、ありがとうございました!

ML Test Scoreを使って現状の機械学習システムをスコアリングしました

皆さん,こんにちは!機械学習エンジニアの柏木(@asteriam)です.
コネヒトでは,テクノロジー推進部に所属し,組織横断的に機械学習(ML)施策の実施・推進を通してサービスグロースする役割を担っています.

はじめに

MLチームでは,少人数ながらレコメンドエンジンの開発*1やカテゴリ類推*2などの機械学習を用いたサービス開発を実施しています.一方でプロダクション環境に投入するMLシステムの数が増えると,それら1つ1つが属人的になったり,テストが不十分だったり,運用が疎かになったり,それ以外に技術的にも負債が蓄積するケースがあります.私たちのチームでもこれらが課題の1つとなっています.

f:id:connehito-mkashiwagi:20210928015818p:plain

上図はよく目にするMLシステムの技術的負債の図*3ですが,MLシステムはモデル開発だけでなく,MLシステムを支える周辺のインフラや各種メトリクスのモニタリングなど考慮すべき項目が多くあります.加えてMLシステムの特性でもあるリリース後の継続的な学習・デプロイにも対応していく必要があり,これに対して場当たり的な対応をするとますます負債が溜まっていくことが考えられます.これらを解消していくためにもMLOpsが重要になってきます.

今回は,現状のMLシステムの技術的負債を定量化するために,ガイドラインとしてML Test Scoreを用いてスコアリングした内容になります.これを実施した目的は,MLOpsを実践するために何が足りていないか(AS-IS),そして何が必要なのか(TO-BE)を把握するためです.本来はMLシステムの技術的負債を可視化することを目的にしたものですが,これをMLOps実践の道標へと転用しました.

MLOpsを進めることで,少ない人数でも効率的かつ技術的負債を少なくしてMLシステムを回すことができるので,より施策を検討する時間やユーザーにとって良いサービスを作っていく部分に注力して行きたいと思っています.

※現在,このスコアリングした内容を元にMLOpsをAWSのサービスを用いて進めていますが,その内容については別の機会にご紹介出来たらと思います.


目次


MLOpsについて

最近色々とMLOps関連の話がありますが,包括的な内容としては,「MLOps Principles」や「ゆるふわMLOps入門」がとても参考になります.ここで述べられているMLOpsとは,MLシステムの構築運用に伴い生じる以下の課題を,効率的かつ継続的に取り組んでいくためのフレームワークや考え方のことになります.(MLOpsの定義は文脈により様々なのでご注意下さい)

  • バージョン管理
    • データセット,モデル,パラメータ,設定ファイルやその他生成物のバージョン管理など
  • テスト
    • データのバリデーションテスト,パイプラインテスト,単体/結合テスト,パフォーマンステストなど
  • 自動化
    • モデル作成のワークフローの自動化,CI/CDを使ったシステムの自動化など
  • 再現性
    • 特徴量生成の再現性,モデルの再現性,開発環境とプロダクション環境の環境差分など
  • デプロイ
    • プロダクション環境への継続的なデプロイなど
  • モニタリング
    • データドリフト,モデルの劣化,推論のパフォーマンスなど

加えて,組織的な問題や上記開発プロセスのイテレーティブな実行などもあります.
かなり広範囲でやるべきことが色々とあるなという印象ですが,MLOpsにもレベルがあるのでそちらを参考にしても良いかもしれません.
レベルの参考になるものとして,Microsoftが定義しているMachine Learning Operations maturity modelというMLOpsの成熟度を表すものがあります.これによると下図のレベル0~4までの5段階にまとめられています.

f:id:connehito-mkashiwagi:20210929223356p:plain
MLOps maturity model

MLOps maturity modelは,人/カルチャー・プロセス/構造・オブジェクト/技術を定性的に評価するもので,各レベルで上記課題に関連する内容が整理されています.これを確認することで自分達のチームがどこに位置して何が出来ているのか,また逆に何が出来ていないのかを把握することができます.
ちなみに僕たちのチームは現状レベル2で一部テストに関する項目は不十分なところが存在しています.対応出来ていない部分を把握することで次にどこに手を付ければ良いかを判断したり,もしくは何から手を付けたら良いかわからない場合には一度こちらを参考にして,自分達のチームに足りていない部分を整理し,優先順位を立てて取り組むのが良いと思います.

ML Test Scoreとは

今回のメイントピックになります.MLOps maturity modelと似た内容がこちらにも含まれていますが,MLOps maturity modelが定性的に評価するものに対して,ML Test ScoreはMLシステムを定量的に評価できるものになります.

これはGoogleが発表したThe ML Test Score: A Rubric for ML Production Readiness and Technical Debt Reductionという論文で紹介されているものになります.また日本語訳された記事([抄訳] The ML Test Score: A Rubric for ML Production Readiness and Technical Debt Reduction)もあるので,参考にしてみて下さい.

ここでは,「オンラインで継続的に学習され,推論を行う教師ありの機械学習システム」を前提にしています.論文で言及されているテスト項目は大きく以下の4つになります.

  1. 特徴量とデータのテスト
  2. モデル開発のテスト
  3. 機械学習インフラのテスト
  4. 機械学習のモニタリングテスト

各項目に7つの検査項目があり,スコアリング方法は「結果をドキュメント化し,テストを手動実行している」場合は0.5ポイント,「テストを自動的に繰り返し実行できるシステムがある」場合は1.0ポイントとなっています.4つのテスト項目毎にポイントを合計(7つの検査項目の合計)し,4つのテスト項目のポイントの最小値が最終的なML Test Scoreになります.これはどれか1つだけ高くても良くなく,4つのテスト項目すべてが重要であることを意味しています.ML Test Scoreを上げるためには,4つのテスト項目すべてのポイントを上げる必要があります.

Googleのいくつかのチームを対象にした調査では,80%以上のチームが上記4つのテスト項目の多くを実施出来ていなかったということです.下図は調査したチームの4つのテスト項目のスコア平均値になりますが,Googleでも高得点が取れていないことがわかります.やはりMLシステムの構築運用時に生じる課題をクリアすることが難しいと改めて感じます.

f:id:connehito-mkashiwagi:20210930013350p:plain
調査したチームのスコア平均値

また,ML Test Scoreの解釈も記載されています.(下記表は[抄訳] The ML Test Score: A Rubric for ML Production Readiness and Technical Debt Reductionからの引用)

ポイント 解釈
0 プロダクションシステムというよりは研究プロジェクト
(0, 1] 総合的にテストはされていないが,可能な限り信頼性向上に努めている
(1, 2] 基礎的なプロジェクトの要求事項は通過した.しかし,信頼性向上のためのさらなる投資が必要とされる
(2, 3] 適切なテストがされている,だが更に自動化の余地が残っている
(3, 5] 信頼性の高い自動化されたテストとモニタリングレベル.ミッションクリティカルな状況でも問題はない
> 5 卓越したレベルの機械学習システム

どのようにML Test Scoreをスコアリングしたのか

まず代表となる機械学習プロジェクトを選択し,それをベースにスコアリングをするようにしました.実際のプロジェクトに当てはめてスコアを付けていくのがイメージも付けやすいし良いと思います.
スコアリングは日本語訳をしてくれているShunya Uetaさんが公開してくれているGoogle Spread Sheetsを用いて行いました.簡単に計算できるようになっているので,非常にありがたかったです!

各検査項目については論文を見て頂ければわかりますが,検査項目とその内容,さらにどのようにすればいいか(How)が書かれています.しかし,それだけだと少しわかりづらい部分もあるので,検査項目の内容をもう少し噛み砕いて自分達のケースだとどうなるかを議論しました.

例えば,特徴量とデータのテスト項目にある「Data 2: 有効な特徴量かどうかを判別できていること」では,以下のように考えています.

  • 「Data 2: 有効な特徴量かどうかを判別できていること」とは?
    • 不要な特徴量を使わないようにする
    • どういう特徴量かを把握することが大切
    • タスク毎に有効/無効な特徴量がある
    • 自動化するイメージとしては,GBDTのFeature Importanceを確認して,閾値以下の値を切り捨てる

こちらは一例ですが,全ての項目に対して内容を深掘りをして,具体的にイメージしやすいようにしました.また,スコアを付ける上で手動・自動の定義は先に述べたように定義がありますが,この部分もどういった状態であれば自動化された状態なのかと再定義をしています.

機械学習インフラのテストについての手動・自動の定義をサンプルとして載せておきます.いざ実際に自動化をしていく時に,どういった状態になればいいかわからないと進んで行かないと感じたので,チーム内で議論しながら整理して行きました.

f:id:connehito-mkashiwagi:20210930161227p:plain
参考: 機械学習インフラのテスト

項目については,自分達のチームに必要だと思うものは追加したり,中にはピンと来ない項目もあって削除しようかと考えるものもありました,スコアリングはあくまで一つの指標なので拘りすぎなくても良いかなと考えています.最初に書いた目的のためにより良くなるのであればアップデートしていくことが大事だと思っています.(本来は検査項目の数を合わせないとスコアが本来の値と乖離してしまうので,ご注意下さい)

ML Test Scoreでスコアリングした結果

4項目についてスコアリングした結果,OVERALL ML TEST SCORE = 1.5(参考値)になりました.各項目のスコアは以下になります.

  1. 特徴量とデータのテスト: 合計スコア = 2.5
  2. モデル開発のテスト: 合計スコア = 2.0
  3. 機械学習インフラのテスト: 合計スコア = 1.5
  4. 機械学習のモニタリングテスト: 合計スコア = 2.5

解釈によると「基礎的なプロジェクトの要求事項は通過した.しかし,信頼性向上のためのさらなる投資が必要とされる」というレベル感になります.結果を見ると,インフラの部分がまだまだ足りていないことがわかります.また検査項目毎で見ていくと0の項目も多く,まずは手動でも実践しないといけないなと感じる部分が多くありました.

伸びしろも多くあるので,これをベースに足りていない部分を補強して1つずつMLOpsを進めて行きたいと思います.

おわりに

今回は,ML Test Scoreを使って現状の機械学習システムのスコアリングを実施しました.現状把握をすることが出来たので,ようやくスタートラインに立てたかなと思います.まだまだ足りていない部分は多いですが,出来ることが多くあるのでツールなどを活用しながら進めて行きたいと思っています!これからが楽しみですね!!

最後に,私たちのチームではデータを活用したサービス開発を一緒に推進してくれるデータエンジニアを募集しています. 少しでも興味を持たれた方は,ぜひ一度お話させてもらえるとうれしいです.

hrmos.co

参考