TECH PLAY

BASE株式会社

BASE株式会社 の技術ブログ

587

この記事はBASE Advent Calendar 2019の20日目の記事です。 devblog.thebase.in PAY株式会社でテックリードを務める東と申します。 主にバックエンド全般に広く携わっています。最近はサーバーアプリばかり書いていますがインフラもわりとやります。 当ブログの読者の方には弊社のことをご存じない方もたくさんいらっしゃるかと思いますので、簡単に社の紹介をさせていただきます。 PAY株式会社はBASE株式会社の100%子会社で、オンライン決済サービス「PAY.JP」とID決済サービス「PAY ID」などの決済サービスを開発・運営している会社です。 「支払いのすべてをシンプルに」をミッションに掲げ、お金を扱うすべての事業者・個人がもっと豊かな生活ができることを目指しています。 さて、決済というミッションクリティカルなテーマを扱うにあたって、品質保証は最も重要な課題です。 弊社のメインプロダクトたるオンライン決済サービス「PAY.JP」にも5000ケース程度の自動テストが記述されており、常時CIやローカルで実行され続けています。 自動テストはその実行時間がネックとなることが多々あります。今年の9月頃まで、PAYのテストはCircleCIで15分程度かかっていました。 テストが遅いと開発のテンポが落ち、CIが詰まりはじめ、そのストレスが限界を超えればお金で解決することを余儀なくされます。 しかもいくらお金を積んだところでcpu1コアあたりのクロックは頭打ちであり、1テストあたりの実行時間はそう簡単には短くはなりません。 そのままテスト実行時間が育ち続ければ、早晩 一人三勤制 の世界が到来することでしょう。 幸いにして昨今は個人の開発マシンですら8core16coreの時代です。これを生かさない手はありません。 今回は、並列化を軸にしたPAYのテスト高速化の取り組みについてお話させていただきます。 構成 言語 python3 フレームワーク pyramid テストランナー pytest 主要なミドルウェア PostgreSQL, Redis ローカルテスト実行環境 Docker, docker-compose 今回の内容にはpyramidフレームワーク固有の事情はほぼ出てこないため、python3+pytest全般のtipsとして利用できるかと思います。 テストランナーを並列実行に対応させる まずはpytestの並列化拡張を探すところから始めました。 2年ほど前に検討した時点では pytest-xdist という拡張が存在していましたが、並列タスクを実行する場所を色々選べる反面制約が多く、弊社の用途では使い物になりませんでした。 一方去年現れた pytest-parallel はpytestの実行をpythonのthreading/multiprocessingで分散するシンプルなもので、まさに我々の求めているものでした。 pytest-parallelの導入 pytest-parallel自体はインストールして--workers=NUMのようなオプションをつけるだけで並列化できるのですが、とりあえずテスト実行をしてみたところ実行が10倍以上遅くなりました。 調べたところfixtureのscope(どのような単位でfixtureを再生成するかの設定)機能が効かなくなり、すべてfunction scope扱いになってしまっているようでした。 PAYのテストでは、FunctionalテストのためにpyramidのWSGIアプリケーションインスタンスを作る巨大なfixtureがsession scopeで用意されています。そしてscope判定が壊れているせいでこれがテストケース実行のたびに再生成されているようです。 さすがにこれが直らないことには話がはじまらないということで、直したものがこちらになります。 https://github.com/feiz/pytest-parallel/tree/pass-nextitem-to-runtest_protocol これを導入することでテストランナーの並列化対応は解決しました。 1 multithread vs multiprocess pytest-parallelはマルチスレッド/マルチプロセスともに対応していますが、SQLAlchemyがスレッドセーフでない、そもそも後付けでスレッドセーフにするのは難易度が高いなどの問題により、マルチプロセスのみを使うことにしました。 割り切ることはたいせつです。 データベース 次にデータベースです。自動テスト用のデータベースを準備する機能自体はもとからsession fixtureとして実装されていましたが、マルチプロセス化するとこれらが各プロセスでそれぞれ実行されてしまい、同じ名前のデータベースを生成しようとしてクラッシュします。 また、同一のdbに複数のプロセスからテストを実行する場合、トランザクションで詰まってパフォーマンスに悪影響があるということも考えられます。 そこで、db名末尾にpidを付与してプロセスごとに独立したデータベースを参照するように手を加えました。 - DB_URI = "postgres://db:5432/test_payjp" + import os + DB_URI = f"postgres://db:5432/test_payjp_{os.getpid()}" これは簡略化した擬似コードですが、PAYの設定ファイルはpythonファイルになっているため、割と簡単に実現できました。 また、テスト環境のPostgresイメージをpostgres-ramに変更することで、すこしばかりのパフォーマンス改善も行いました。 本当はin-memory SQLiteに差し替え可能にできれればよかったのですが、PAYはPostgresに依存しているところが多く、残念ながら実行できていません。 ソケット枯渇問題 テストのために64coreマシンで128並列実行などをして遊んでいたところ、テスト実行中に突然DBコネクションが張れなくなる問題が発生しました。 察しのいい方ならピンときそうなトラブルですが、TIME_WAITなコネクションが増殖してアプリケーションコンテナ側のポートが食いつぶされてしまったのが原因でした。 2 これはテスト中だけSQLAlchemy側のコネクションプールを有効にすることで解消できました。 余談 並列化の実験にはGCPのプリエンプティブルインスタンスが安くて手軽で非常に便利でした。個人のアカウントで64coreぐらいのマシンを立ち上げて実験してすぐ落とすぐらいの使い方であれば月300円ぐらいで十分に遊べます。 並列化後にボトルネックがどういうところに現れるのかの肌感を掴んでおくためにも、一度はやっておくとよいでしょう。 Redis ストレージ整備の一環としてRedisのテスト環境も見直しました。これまではdocker-composeで用意された本物のRedisにredis-pyでそのまま接続していましたが、これをすべてin-memory実装である FakeRedis に置き換えられるように改修しました。 cpuコアと同様、昨今の環境ではメモリも大量に余っていることが多いため、やれるものをinmemoryにしてしまうのは手っ取り早いテスト高速化の手法です。 3 過去の話ですが、djangoを使っていた頃は storage api と inmemorystorage でファイルシステムを触るテストコードを高速化する手法はお気に入りでした。 テスタビリティ全般に通じる話ですが、重要なのはミドルウェアや外部環境との接続部分をラップして差し替えが自由にできる構造を最初から組んでおくことです。 テストコードの順番依存 テスト環境の改善により実行はできるようになりましたが、不可解なFAILがランダムかつ大量に発生するようになりました。 これまでのテストは直列かつ同一の実行順で実行されていましたが、マルチプロセス分散されたことで実行順が不安定になり、前提条件が満たされていない実行が発生してしまうようになってしまったようです。 PAYではテストケースごとにDBをrollbackするようになっていないため、このような問題が起こりえます。実行の前提条件構築がテストケース間で正しく分離されていない悪いテストを書いてしまっているということです。これはひどい。 2ヶ月程度をかけて、これをしらみ潰しに改善しました。 残念ながらここについては一般的な解法はなさそうに思います。参考までに、原因を特定する手順として社内ドキュメントに書いた内容をかいつまんでご紹介します。 1. エラーの直接の原因を把握する 大抵、常識的に考えて当然(ある|ない)はずのデータが(ない|ある)といった不可解なエラーとして表出します。混乱せずにまずは何が起こったのかを正しく読み解きます。 2. できるだけ狭い範囲で再現させる テストランナーの個別実行機能を使ってできるだけ狭い範囲で100%再現できるように発生条件を絞り込みます。 調査中は並列実行をやめる エラー内容から原因にアタリをつけ、標準の実行ケース指定機能で範囲を絞る pytest tests/integration/api/test_charge.py::TestCharge randomize拡張(pytest-randomなど)でランダム実行を繰り返す 失敗するパターンを見つけたら、ランダムシードを記録して追試する(どこのrandomize拡張にもあると思います) 再現したら、デバッガを仕込んで個別に追う -> 原因特定 3. 対処する 多くの場合、前提データの前処理や後処理が悪いことが原因のため、テストデータ生成基盤に手を加えて改善することになります。 pytestの[yield fixture]( https://docs.pytest.org/en/latest/fixture.html#fixture-finalization-executing-teardown-code ) はsetUp/tearDownメソッドのような手法よりもfixture個別の前後処理の記述がやりやすいため、積極的に活用するとよいでしょう。 本来の文章では、実際に直したケースを再現できるよう、私が実際に調査・修正した過程をchangeset idを添えて記載していました。 完成 以上のような改善を経て、pytest-parallelの導入を決意してからおよそ7ヶ月、並列化を志したときから数えれば実に1年と1ヶ月をかけてちゃんと動く並列化を実現できました。 結果として、CircleCIの自動テストタスク実行時間が15分34秒 -> 2分53秒に改善しました。(5.3倍!) before ↓ after テスト基盤を整えよう。今すぐ。 私の経験上、テストはアプリケーション本体以上にコードが腐敗しやすい場所に思えます。 もし、あなたがこれからテストコードを書き始めるか、プロジェクトのテストコードがまだ育っていないなら、今すぐにでもお使いのテストランナーのrandomizeオプションをオンにしてみるべきです。それだけで今回一番つらかった問題(順番依存)が起きてしまうリスクを大幅に低減できます。 必要に駆られる前から並列化を行っておくこともおすすめできます。本来テストケースは分離されていて当然のものであり、きれいなテストを書いていれば簡単に並列化できるものだからです。 これらは不健全なテストをふるいにかけ、テストコードを健全に保つ大きな助けになります。 ミドルウェアへの直接的な依存もよく吟味すべきです。昨今はdockerの台頭でテスト環境にミドルウェアそのものを用意することのハードルが非常に低くなっているため、手を抜きがちな部分ではあります。 しかし、テストダブルで依存を自由に管理できないとそもそもテストを書くのが面倒極まりないですし、書くのが面倒なテストは書かれなくなるか、コピペが横行して加速度的に腐敗します。 残念ながらすでに問題が起きているのなら、諦めて粛々と改善しましょう。それが一番の近道です。 おわりに テストに実行速度の問題が表出するようなタイミングでは、対処しようにも他の問題と複合していて手が出せない状態になっている可能性が高いです。このような問題に拘って本来やるべき開発に支障がでたり、CircleCIに無限に予算を吸われてしまうようになる前に、ぜひテスト実行基盤を見直してみることをおすすめします。 この記事が、みなさんの快適な開発の助けになれば幸いです。 明日は 明日は BASE Owners Growthチームの大木さんと、PAY セキュリティエンジニアのクリスです。 使用には耐えるものですが、魔改造の粋を超えられていない(スレッド実行機能が死んでいる)こともあり、本家にPRは送れていません。すみません。;( ↩ PAYの本番環境にはPgBouncerが居るため、アプリケーション側ではコネクションプーリングは行わない設定になっていました。 ↩ 手っ取り早いのは確かなのですが、「本来使うものとは違うもの」でテストしているのもまた確かなので、場合によっては速度を度外視して本来のものに近い環境でテストするステージを設けることも検討するべきです。 ↩
はじめに この記事は BASE Advent Calendar 2019 の19日目の記事です。 devblog.thebase.in こんにちは、BASE株式会社 Native Application Groupの小林です。 主にAndroidのアプリ開発をしています。 「BASE」のAndroidアプリは3種類リリースしています。 その中でも、ショッピングアプリ「BASE」のレイアウトをより見やすく、使いやすくしていく活動をデザイナーと共に行っています。 その中で、レイアウトがちょっとずれている原因を調べる時に使っている手段を紹介したいと思います。 Android StudioのLayout Inspector 「画像の高さが違う」、「データは来てるはずなのに画面に文字が表示されない」といったレイアウト違いを調べたいときに便利な機能です。 起動中のアプリで表示している画面で、Viewの設定値を知りたい場合に、Android StudioのLayout Inspectorを使って知ることができます。 使用方法 1.Android StudioのメニューバーからTools → Layout Inspectorを選択 2.デバッグ起動しているアプリの一覧が表示されます。 本番用に起動しているアプリは基本的に選択肢に表示されないので、テスト版のアプリで試しましょう。 (root化していたりエミュレータの場合は本番用のアプリも表示されます) 3.起動中のアプリにスタックされているActiivtyが表示されます。 画面に表示されているActivityを選択します。 余談ですがこの機能、プロジェクトに参画したての時に画面に紐づくActivity名がわからない時によく使ってます 4.暫く待つと、このように画面の構成要素が表示されます この機能でできること Layout Inspector ではxmlに記述していた高さや幅、動的に変更している値の結果を確認することができます。 (画像の高さを確認しています) よく使うのは、判定式で表示/非表示を切り替えてる動的な高さを持つViewが表示されない原因調査です。 ・そもそもViewは表示されてるなのか? →Visibility=INVISIBLE/GONEになってないか ・VISIBLEの上で高さが0dpなのか? →height weight が0dpになってないか ・高さもあるが、透明な状態なのか? →Textが入っているか、backgroundcolorが設定されているか、x,yが0やマイナスになっていないか こういった調査をこの機能1回で調べられます。 Live Layout Inspector 現在のLayout Inspectorは、取得時点のレイアウト情報を表示していて、端末側で操作しても反映されません。 Android Studio 4.0ではLive Layout Inspectorという機能が追加され、リアルタイムに反映されるそうです。 リンク [Android Developers] Layout Inspector https://developer.android.com/studio/debug/layout-inspector [Android Developers] Live Layout Inspector https://developer.android.com/studio/preview/features?hl=en#4.0-live-layout-inspector 開発者オプションのレイアウト境界を表示 View間の余白がずれていてどちらのViewが幅を取っているのか知りたいときに便利なAndroid OSの機能です。 Viewが確保しているスペースやmarginをわかりやすくするための「レイアウト境界を表示」という開発者オプションがあります。 使用方法 設定するには、開発者オプション→描画項目にある「レイアウト境界を表示」をONにします ショッピングアプリ「BASE」の場合、このように表示されます 通常 レイアウト境界ON Viewの高さや幅の設定で確保されているスペースを赤と青角の線、merginで確保されているスペースが赤く塗りつぶされて表示されています。 Android 9からはトップのクイックタイルに登録してワンタップで切り替えができるようになり、より気軽にチェックができるようになっています 開発者オプション→クイック設定開発者用タイル→レイアウト場所を表示をON→クイックタイルから切り替え リンク [Android Developers] デバイスの開発者オプションを設定する https://developer.android.com/studio/debug/dev-options#drawing さいごに 今回はAndroid Studioからレイアウト構成を調べる方法と、端末の開発者オプションから余白を調べる方法を記載しました。 どちらかでも新しく知っていただいた方がいればとても嬉しいです。 レイアウトがずれてる原因は様々で、一つの方法ですべてが分かることがない事が多いです。 いろんな調べ方を組み合わせて、素早くレイアウトの改善につなげていきたいです。 明日はDataStrategyの岡さんとPAY株式会社のテックリードの東さんです。お楽しみに!
この記事はBASEアドベントカレンダー 19日目の記事です。 devblog.thebase.in Webアプリの解体 こんにちは。フロントエンドエンジニアの松原( @simezi9 )です 近年、Webアプリはクラウドの発展とともにそのあり方を大きく変質させてきました。 具体的にはXaaSの発展により、Webアプリはその構成要素をあらゆるレイヤにおいて細かく分解され、それらを開発者が組み合わせることで作られるようになりました。 このアプローチによりシステムはプラガブルになり可用性の面でも品質面でも大きく進化しました。 その反面で当然このアプローチにも問題があります。その最たるものが組み合わせの複雑度の増加だと思います。 あらゆるものがプラガブルになった反面、それぞれのサービスをどのように組み合わせてWebアプリを構築していくのか自体が一つの知見・分野となり、「昔はWebアプリフレームワーク一個さえあればそれでよかったのに」というような声が出てくる一因となっているようにも思います。 そうした流れの中にあっては、必然的にプラガブルになったサービスをまとめること自体をサポートするサービスやフレームワークが出てきます。それはかつてのRailsであり、AWS Elastic Beanstalkであり、最近で言えばNuxt.jsのようなものであったりします。 Stackbit はそうした「Webアプリ開発にレールを敷く」サービスの1つであり、 近年急激に隆盛した「JAMstack」アプリを一気通貫に作成することができます。 JAMStackの登場 JAMstack とはJavaScript+APIs+Markupの頭文字をとった概念で、 アプリとしての動作や機能はすべてクライアントサイドのJSに集約し、 データやロジックはすべてAPI経由で取得し、 そのデータを表示するテンプレートをデプロイ時に事前にビルドした静的ファイルで返却する ようなWebアプリを指す概念です。 こうした構成を取るメリットはいくつもありますが、とりわけ「静的ファイル(HTML,CSS,JS)配信を中心にした構成」によるサイトパフォーマンスの向上やスケーリングの楽さが多く取り上げられています。 JAMStackは特にWordpressなどのCMSが担ってきた領域のアプリを実現するための手法としてよく登場します。 というよりも個人的な印象で言えば、Webアプリで多く使われていたCMSを分解しプラガブルなサービス群へと変換していく中で生まれたベタープラクティスがJAMstackという形になったというような気もします。 JAMstackなCMSを構築する過程では機能とそれを提供するサービスは大きく以下のように分類されます HTML/CSSで静的ページを出力する( 例:Hugo, GatsBy, VuePress ・・・etc) クライアントサイドでのルーティングやAPIとの連携を行う機能 (JavaScript) コンテンツの配信・管理を行い、APIでデータを提供する機能(例:Netlify CMS, Contentful・・・etc) 生成したファイルをホスティングする機能(例:Netlify, Github Pages・・・etc) これらの組み合わせをあれこれ試しつつサイトを構築するのは一人のエンジニアとしては腕の見せ所でもありとても楽しい部分ではありますが、実際にサービスを作る上では一定の知見や検証が必要になります。 その部分を代わりに担保してくれるのが Stackbit のようなサービスになります。 実際にこのStackbitを使ってサイトを生成してみようと思います。 Stackbitを試してみる Stackbitでは以下の事を自動で設定してくれます(ただし本記事執筆時点ではBETAのため、組み合わせるサービスの選択肢は多くがCOMING SOONの状態) - 生成されるサイトのビジュアルテーマの設定 - サイト生成に使用する制定サイトジェネレータの決定 - Headless CMSとして仕様するサービスの設定 - Gitによるコードのホスティングとデプロイフローの整備 サイトの生成までは特に難しいことは有りません サイトの生成を始める サイトのテーマを選ぶ 用途にあわせて標準のテンプレートからサイトのスタイルを選びます。 ひとまず一番先頭にある「Exto」を選びます サイトジェネレータを選ぶ サイトの構築に利用する静的サイトジェネレータを選びます。 ここではとりあえずGatsbyを選択します CMSを選ぶ サイトのコンテンツを管理するためのCMSを選びます。 ここではサイトのデプロイにNetlifyを使うので、NetlifyCMSを選ぶことにしてみます githubとの接続 生成されたサイトのバージョン管理をするために、GitHubアカウントと生成するサイトの紐付けを行います。 サイトの生成 これで最初の作業は完了し、サイトの構築・ビルド・デプロイまでが開始されます Github サイト生成のタイミングでGithub上にリポジトリが作られて、サイトの開発・記事の投稿、デプロイフローなどの整備が全て整った状態でリポジトリが用意されます。 ローカル開発を開始するための手順なども用意されていて、静的サイトジェネレータを触ったことがある方にはすぐに開発できるような状態が整っています 実際に出来上がったサイト 標準状態で、一通りの機能を備えたサイトがWebサイトとして公開されるところまで自動実行されます。 これらはソースはGithub、サイトはNetlify上に展開されており、自前で管理するサーバーが一切ないという広義でのサーバレスが実現されています。 Lighthouse Lighthouseを使ってページのメトリクスを測定してみます。 性能にスロットルをかけたモバイル環境の設定で実行してもご覧の通り、かなり高いパフォーマンスを持ったサイトになっています 記事の投稿 今回はNetlify CMSを利用しているため、記事の投稿はそちらを経由して行います。 ページ右上部にStackBitが提供するコントロールパネルがあるのでそこからアクセスをします するとCMSの管理画面が出てくるのでここから記事を投稿します。 このあたりは、HeadlessCMSらしく、コンテンツの管理に必要な機能だけが搭載されたシンプルなものになっています。 記事を投稿すると、その投稿がGithubリポジトリにコミットされ、静的サイトジェネレータが実行されサイトの再ビルドが動き始めます。その様子はサイトのコントロールパネルからも確認できるようになっています。 まとめ StackbitはWebアプリ構築の第一歩を強烈に加速してくれるサービスでした。 そして不要になったらGithub上にコードがあるのですぐにサービスを切り離すことも可能です。 カスタマイズ性の高さと構築の定形作業のコストを軽くしてくれるという意味でとてもバランスのよいサービスだと思います。 このサイトを作るまでの工程は、非エンジニアの方でもおそらく数十分〜数時間程度あれば可能なほどに容易です。 ただし、あくまでWeb開発のベタープラクティスを集合させたサイトの雛形を提供してくれるものなので、 Webサイトの構成がどうなっているのかを把握できていないレベルの方ではサイトのレイアウトを変えたり、機能を追加開発することは厳しいのではないかと思われます。 そういう意味ではある程度フロントエンド側の構築に理解がある方がパパっとポートフォリオだったりブログサイトを作るのにはとても便利なサービスかと思います。もちろん技術構成自体は普通に大規模サイトでも通用するようなものなので、その第一歩として活用することも十分可能だと思います。 自分でWordpressを構築して、デプロイできるサーバーを探して・・・とやっていた時代がそれほど昔でないことを思うと、 この生産性の高さは強烈だなあ、すごい時代だなあと思わずにはいられません。 サービス自体はまだベータ版ですが、正式版のリリースが待ち遠しいサービスだと思います。 明日はData Strategy Sectionの岡さんとPAY株式会社 テックリードの東さんです。お楽しみに!
この記事は BASE Advent Calendar 2019 の 18 日目の記事です。 devblog.thebase.in はじめまして、Owners Marketingグループの栗田です。 現在は主にサーバーサイドエンジニアとしてオーナーズ(ショップオーナーさん)を支援できるような機能の開発をしております。 最近では、ネットショップ作成サービス「BASE」の拡張機能であるBASE Appsの一つである予約販売 App の開発や増税対策のための機能改修などを行っておりました。 この記事ではBASE社内の部活動のひとつ、スパルタン部で日本初開催のスパルタン・ビーストを攻略した話を紹介します。 下記記事にてランニング部も紹介されてますので BASEのランニング部をきっかけにフルマラソンに挑戦 この記事と合わせてBASEの部活動の雰囲気を感じていただければと思います。 スパルタン部とは みんなでスパルタンレースに出る部活です。 日々各自トレーニングをしたりスパルタンレース情報交換や攻略のノウハウを共有しています。 スパルタンレースとは 世界最高峰の障害物レースといわれています。 今回出場したのはその中でも非常に過酷と歌われているスパルタン・ビースト ※ スパルタン・ビーストの説明 21キロの道に30以上の障害物が待ち構えるスパルタン・ビーストでは、筋力、持久力、揺るがない決意が試されます。予測不可能かつ巧みに設計されたコースや障害物の数々は、肉体・精神ともに追い込み、自身の限界を打ち破らなければ達成することのできない非常に過酷なレースです。 ( https://spartanracejapan.info より) 障害物レース??個人戦?? 障害物競走と聞きますと個人戦のようですが、チームでエントリーして協力・助け合いながら攻略していきました。 1人では超える事のできない障害物を協力して乗り越えたり、1人では持ち上げる事のできない重りを協力して持ち上げたりしました。 また、障害物を攻略できないとペナルティのジャンピング腕立て伏せを数十回行わなければいけないのですが、時には分担する事より攻略していきました。 参加してきました! レース中はチームワークで乗り切るところもあったり、時には個人の力で乗り切るところもありました。 20kmを超えるレースですが、スタート直後から過酷な旅が待っておりました。 炎天下の中、普段はゲレンデの下山コースとして使用されている2km以上にも及ぶ山道の登りから始まり 時には数十キログラムを超えるサンドバッグを持って山道を歩いたり、張り巡らされた有刺鉄線の下を匍匐前進で進んだり 力を合わせてロープを引っ張り数十キログラムを超える重りを頂点まで上げたりなど、辛いシーンが多々ありました。 (※レース中に協力している様子です。 https://www.youtube.com/watch?v=juit1t9akds&feature=youtu.be&t=2235) 合計時間も7時間を超え、辛さから気持ちや身体の浮き沈みもありまるで短期決戦のプロジェクトのようでしたが 個人個人が自分と向き合い、助け合いながら諦めずに無事に全員でスパルタン・ビーストを攻略する事ができました! (※ ゴール後にメンバーのみんなで記念写真) まとめ 普段乗り越えた事が無い壁を乗り越える事は格別ですね。 集中して濃密な時間を経験する事ができました。 今回色々な部署のメンバーと共通の課題を乗り越える事ができ 仕事でのプロジェクトをプロジェクトメンバーと乗り越えた時と同じような感覚がありました。 超える壁は大きいほど達成感はありますし、それがチームで達成できるのはとても嬉しいですね。 来年は海外のスパルタンレースに挑戦しようという声が上がってます。 スパルタン部の挑戦は来年も続きます。 明日は、Frontendグループの松原さんとNative Applicationグループの小林さんです!
この記事はBASE Advent Calender 2019の18日目の記事です。 こんにちは。 Product Management Groupでディレクションを担当している、藤井と申します。 ふだんはネットショップ作成サービス「BASE」やショッピングアプリ「BASE」の新機能および改善のディレクション業務を行っています。 そのディレクションのなかでも、個人的にとくに強い関心を持って取り組んでいるのが「UXライティング」という分野です。とはいえ、「ん、UXデザインってのは聞いたことがあるけれど、UXライティングってなんぞや?」という方もきっと多いのではないでしょうか? そもそも、UXライティングってなんぞや? UXデザインを「グラフィックによってユーザー体験をデザインすること」と定義するならば、UXライティングとは「テキストを設計することによってプロダクトとユーザーのコミュニケーションをデザインすること」と定義されているようです。つまり文章を最適化することによって、何を伝えようとしているのかがストレスなく伝わるようにする、いわばテキストによってコミュニケーションをデザインすること、なのです。 (※参照  「シリコンバレーのUXライターが語る、UXライティングの重要性」参加レポート ) UXライティングに興味を持ったきっかけ 自分たちが世の中にECのプラットフォームを提供している、というのはさておき。日常的にネット上で買い物をするのが当たり前になってきているなか、たとえばお問い合わせは電話ではなくメールやチャットで行っていたり、結局は商品の写真と紹介文で判断していたりと、直接のやりとりではなくテキストでのやりとりがほとんどになってきています。だからこそ、文章の意味が理解できなかったり、敬語がおかしかったり、そもそも日本語が怪しかったりするサイトでの買い物はためらってしまいます。つまりテキスト・コミュニケーションもデザイン同様、コミュニケーションのための必須要素であり、トンマナ(トーン&マナー・一貫性のこと)とはテキストのデザインでありUXなんだなあ、と。そんなことを考えているうちに、がぜん「BASE」におけるテキストのあり方が気になって仕方なくなってきたのです。 時代背景からも見て取れる、テキスト重視の機運 でも、そんなことを考えているのは自分だけ?なんて思いつつ、「UX」「テキスト」なんてキーワードであれこれ検索してみると、同じようなことを感じている先人は数多くいたようで。 「『なぜUXライティングが必要なのか?』答えはシンプル、『Words are everywhere』、つまり『言葉はいたるところで使われているから』」 「マネジメントはマネジメントの言葉、エンジニアはエンジニアの言葉、PM、デザイナーなどプロダクトに関わるメンバーが、各々の価値観をベースにした異なる言葉をプロダクトに注ぎ込んでしまうと、ユーザーの体験は断片化され、プロダクトのあちこちにに矛盾が生じる、ユーザーはまるで様々な方向から沢山の人に話しかけられているような感覚に陥り、混乱してしまう」 「洗練されたワードほど自然で違和感を与えない、結果としてユーザに意識されない。ビジュアルを作るとき、無駄のないデザインほど難易度が高いのと同じ」–All Turtles/Jessica Collier 「私たちが触れる様々なプロダクトやサービスは多くの“言葉”で構成されていますので、肝心の言葉を後回しにするなんて間違っていました。ことさらAI時代においては、プロダクトとのコミュニケーションが会話形式で行われるなど、“言葉”はますます重要な要素になるはず」 –All Turtles CEO/Phil Libin (※参照  All Turtles 創業メンバーが語る、UXライティングの重要性 ) かくしてデジタルプロダクト市場が成熟し、よりよいユーザー体験が求められるなかで、言語表現にもボトルネックが存在していることは明らかなようで。2010年代中盤からは、デザインプロセスにおいて言葉のデザインを重要視する流れも実際に起きはじめているようです。なんでもGoogle, Spotifyなどの企業では、“言葉”のプロフェッショナルをデザイン・チームに加える、という取り組みもすでに生まれているのだとか。 「BASE」におけるUXライティングって、どうあるべき? では、「テキストを設計することによってプロダクトとユーザーのコミュニケーションをデザインすること」って、じゃあ具体的にどんなことをするの?というと、シンプルにまとめると次の3つに集約されるように思います。 ユーザー体験をロジカルに分析すること(どう言われると、どんな感情を抱く?) 課題を見つけること(言いたいことが、額面通りに伝わっている?) 言葉をブラッシュアップすること(言いたい、ではなく伝えたい) そして「BASE」にとってUXライティングのあるべき姿としては、現段階ではこんなふうに考えています。 「BASE」というプラットフォームに接するときに目にするワード構成を設計(誰が/どこで/どのように使うのか)する それによって、プラットフォームとショップオーナー様/お客様の間のコミュニケーションをデザインする 書き出してみるとなんてことないことかもしれませんが(ごめんなさい!)、じゃあ実際にできているかというと、取り組みはじめて約1年、目指すべき場所はまだまだずっと先にあるように思えます……。 いま「BASE」が取り組んでいる、UXライティングにおける具体的アクション/視点 そんな「BASE」が現在取り組んでいる、UXライティングにおける具体的アクション/視点とはどのようなものか、その一部をご紹介します。 用語リストを作成/運用する  →誰もが参照できる用語リストを作り、プロダクト内に言葉の矛盾が起こるのを防いでいます。 既存の言葉を使う  →ユーザーがふだん使っている言葉に合わせるべきであり、すでに理解されている表現があるときに、いたずらに新しい言葉を作り出さないよう心がけています。 ダミーテキストを使わない  →デザインするさいにダミーの文章を流し込まず、最初から限りなくリアルなコンテンツやテキストを当てることで、UXが迷子にならないようにしています。 設計を修正するごとにコンテンツも修正する  →デザインと同じく、ライティングの大半は下書きと修正であり、削り落とす作業だと考えています。 形を機能に従わせる  →美しさ<機能。言葉のほうが伝わるのか、画像のほうが伝わるのか、目的達成のために効果的な方を選んでいます。 ゴール・オリエンテッドである  →その画面において必要な情報のみを、その都度提供するようにしています。 これから というわけで、「BASE」のUXライティングの取り組みは、まだまだはじまったばかり。社内にもその必要性/重要性を懸命に訴えつつ、ひいては「BASE」というプラットフォームを使ってくださるショップオーナー様/お客様の体験を、さらに一つ上のステージへと導きたく日々格闘しています。すべてはわかりやすく、正確に、そして行動できるようにーー 「The details are not the details. They make the design」 – Charles Eames(チャールズ・イームズ/デザイナー)・神は細部に宿る (※参照  https://www.brainyquote.com/quotes/charles_eames_169188 ) ーーそう、細部への気配り(一つひとつの言葉選び)だけがプロダクトに差をつけると、「BASE」は本気で信じています。 明日はFrontendの松原さんと、Native Applicationの小林真さんです!
この記事は BASE Advent Calendar 2019 の 17 日目の記事です。 devblog.thebase.in こんにちは。Platform Dev Section マネージャーの大窪( @bonnu )と申します。 BASE には 2018 年の夏に入社し、現在はバックエンド基盤や SRE(Site Reliability Engineering)、フロントエンド基盤を担う各グループのマネージャーを務めさせてもらっています。 今回のアドベントカレンダーでは実用的なネタを思いつくことができませんでしたので、ちょっとしたコラムのようなものを綴らせていただこうかと思います。 私が愛した言語・Perl 突然ですが、私がこれまでエンジニアとして仕事をしてきた中で一番長く書いていた言語は Perl です。 現在は日本のWeb業界においてのシェアを近代的な言語(※ Golang、Java、Scala、その他 LL 等)に奪われ、かつて Perl で作られていたサービスが別言語にリプレイスされた話を聞く事もあります。なのでよく採用されている言語とは言えませんが… テキスト処理に対して強力なユーティリティが多く、また豊かな表現力(TMTOWTDI 1 )と低レイヤーへのアクセスの良さ 2 から機能実現力が高く、コマンドラインツールに限らず掲示板や小規模サイトからブログシステム、大規模ウェブサービスまで様々なシーンで利用されていました。 (かつては C や Java、C++ など、今もよく使われている言語に肉迫していたようですね。2000年代は頑張った…) https://youtu.be/Og847HVwRSI Larry Wall が定義した「プログラマの三大美徳」 そんな Perl の生みの親、 Larry Wall 氏はかつて Perl のラクダ本(プログラミング Perl 3 。Perl の実用的な解説書)でプログラマの三大美徳を定義しています。 laziness, impatience, and hubris 怠惰(怠慢) Laziness 短気 Impatience 傲慢 Hubris これは Perl に限らずプログラマ界隈においてとても有名でよく引用されている言葉であるため、現在エンジニアとして活躍されている方にとっては今更説明の必要もないかもしれませんが、今回は改めてこの美徳をテーマにしたいと思います。 それぞれの美徳について 怠惰(怠慢) Laziness The quality that makes you go to great effort to reduce overall energy expenditure. It makes you write labor-saving programs that other people will find useful, and document what you wrote so you don't have to answer so many questions about it. Hence, the first great virtue of a programmer. 全体的なエネルギー消費を削減するために多大な努力を払う気質。他の人が役に立つと思う省力プログラムを作成し、あなたが書いたものを文書化するので、それほど多くの質問に答える必要はありません。したがって、プログラマーの最初の大きな美徳。 例えば繰り返し行われる作業、同一の処理を表現するコード、同一概念を扱う多数の関数など、それらの抽象度を見極めて適切な粒度・単位でカプセル化することで同じ事を繰り返さないようにしたり、再開発しなくてよいように工夫する気質と読み取っています。 さらにそれにはドキュメントを付属させることで、他者に伝えるための時間や手間といったものを省くことができるとまとめられています。 私はエンジニアであった当時よりドキュメントを書くのがあまり得意ではなかったので(現在も目下の課題です)、この点については片手落ちだったと反省があります。 短気 Impatience The anger you feel when the computer is being lazy. This makes you write programs that don't just react to your needs, but actually anticipate them. Or at least that pretend to. Hence, the second great virtue of a programmer. コンピューターが怠けているときに感じる怒り。これにより、ニーズに対応するだけでなく、実際にそれらを予測するプログラムを作成できます。または少なくともそのふり。したがって、プログラマの2番目の大きな美徳。 機械翻訳した上でですが、私の解釈として「怒り」という表現は「不便な事を思い通りにしようと直感的に思う」事を指すと考えています。 ビジネス要件、機能要件に対して先々も応えられる処理として仕上げるべく熱量をもてたり、すぐにロジックの整理に頭が向かう様がイメージできます。 また、短期的なニーズだけをただ満たすだけでは足りず、同一パターンの処理が想定できるならばそれらを予め想定しておけという意味合いがあるようです。 小飼弾さんの記事 ではテンプレート処理に関しての言及がありますね。 短気の裏に潜む焦りや不安から、先んじて検討して対処しておけ、というところでしょうか。(Impatience に「焦り」「苦痛に耐えられない」という意味を見つけましたため) 傲慢 Hubris Excessive pride, the sort of thing Zeus zaps you for. Also the quality that makes you write (and maintain) programs that other people won't want to say bad things about. Hence, the third great virtue of a programmer. 過度のプライド、ゼウスがあなたを驚かせる(神罰のような)もの。また、他の人が悪いことを言いたくないプログラムを作成(および保守)できる気質。したがって、プログラマーの3番目の大きな美徳。 プログラマとして品質に妥協せず、またその品質を示すべく説明できること。 プログラムであればテストをはじめとして、その成果物の妥当性・正当性を担保する仕組みを整えることで、その自尊心を守りなさいという美徳と言えます。 これもまた 小飼弾さんの記事 を参考にしますが、傲慢さは他者に対して発揮されうる気質であり、互いに正当性を示し合うことで議論や競争を生み、それが結果として発展を促すという捉え方ができます。 何を「愛して」いたのか 美徳は必ずしも「態度」に表れることを指していない 広くインターネットでの三大美徳に関する議論を眺めてみると、それぞれの美徳についての定義と定義名( Laziness 、等)について意味が対照的でなく、ひねくれていると捉えられる事があるようです。それら定義名はどれもネガティブな印象を持ってしまいそうなものばかりですが、その理由は Larry 氏のユーモアから来ていると私は考えます。 美徳それぞれの意味はどれも高い視座を表現していますが、そのまま定義名を厳格に表してしまっては極論、あまりついてくる人はいないでしょう。まずは「なんだそんな事でいいのか」と思わせるキャッチーなところから入り、その上で解釈が進むことで本来の定義が意識に刻み込まれるような、そんな美徳になっていると感じています。 なのでこの定義名をそのまま態度に表してしまうと… 特に組織活動において、様々なしくじりが生じてしまいます。私は自分の失敗の数を数えていませんが。 オープンソースの原点的考え方として捉える 今でこそどんなプログラミング言語でもソースコードを共有するエコシステム(CPAN、npm、Packagist、Golang+GitHub、etc…)が当たり前になりましたが、当時 Larry 氏が Unix のツールを開発していた時代は一体どんな状況だったのでしょうか。 ネットがそれほど速くない時代(そもそも商用 Internet がなかった)、今よりも手元だったり、企業や大学のイントラ内にだけコードがあることが殆どで、プログラムが人の目に触れる機会はとても少なかったのではないでしょうか。 そういった状況で開発された OSS が安心して使えて高い品質を保つ事を担保するためにも、草の根活動的な呼びかけの一種としてこの美徳があったのではないか、と私は勝手に考えてしまいます。 いま、マネージメント業務をする立場としてどう捉えるか ここまで懐古厨として話を進めてきましたが、そろそろ現在の私の立場に話を戻します。 私がこれまでプログラミングを公私ともにやってきて美徳を正しく体現できたとはとても言えず、その視座の高さに打ちひしがれる思いがありますが、やはりこの美徳は改めて自分の内に秘めて持っておきたいと感じました。 マネージメント業務をする立場においては、各種業務改善や日々の運用をしていくにあたってこれら美徳を判断の参考にすることができます。会議量やフローの適切化やコミュニケーション・パスの整理など、 怠惰 と 短気 の美徳に従えばどうすべきか…。 また今回挙げた三大美徳を一種の宗教だと捉えた場合には当然その他の宗教を持つ方もいると思いますが、各々がその教えや意志を大事にしながらエンジニアリングしていくことで、 傲慢 の美徳に従ってコラボレーションを活発にさせることができます。 最後に 日々の業務でこれらの美徳をそこまで意識する事はありませんが、たまにでも思い出してみる事で自身の振る舞いや判断を振り返るきっかけにできると感じました。 最近はコードを書く機会が減ったことで純粋にプログラマーとしての価値観や観点を忘れてしまうことがあり、今回は自戒の意味を含んで、この三大美徳をテーマとさせていただきました。 明日のアドベントカレンダーは Owners Marketing グループの栗田さんと Product Management グループの藤井さんです。 刮目して見よ。 There’s More Than One Way To Do It:やり方はひとつじゃない ↩ XS:C のコード(または C ライブラリ)との間の拡張インターフェースを作るのに使われるインターフェース記述ファイルフォーマット ↩ https://en.wikipedia.org/wiki/Programming_Perl ↩
この記事はBASE Advent Calendar 2019 17日目の記事です。 devblog.thebase.in こんにちは、DataStrategyの杉です。 DataStrategyではデータを用いて問題解決を行なっていたり、より使いやすいサービスのための改善をしています。10日目の記事として 類似商品APIについて がありましたが、このようにテキストや画像の特徴量からレコメンドの作成なども行なっています。今回は私がテキストの特徴量を用いて試してみたことについて書きたいと思います。 概要 Eコマースプラットフォーム「BASE」には様々なカテゴリが存在します。 例えばアパレルのショップであれば「トップス」「ボトムス」などショップごとに設定されているカテゴリもあれば、「アパレル」などといったショップ自身を表すカテゴリなど、多いものでは約1000種類の分類がされているカテゴリもあります。 これらのカテゴリを自動で分類できるようになることで、レコメンドの精度向上や入力項目の削減などができるようになります。 しかし、このカテゴリをテキスト特徴量を用いて分類しようと思うと以下のような問題が出てきました。 カテゴリによって偏りがでてきてしまう 精度が上がりにくい カテゴリによって偏りがでてきてしまうことはどのようなタスクでも存在する問題ではないかと思います。しかし、少ないカテゴリに数を合わせてしまうと全体の量が減ってしまい、より精度が悪くなってしまうということも考えられます。 この問題に対して、テキストのDataAugumentationを用いて解決できないかということを試してみました。 具体的には、CutMixと呼ばれる、主に画像のDataAugumentationで使用されている手法を試してみたので、その内容について書きたいと思います。 また、詳しくは下で書かせてもらいますが、このCutMixをテキストに使用するということはあまり推奨されていない手法な可能性もあります。そのため、 こんなこと試してみたんだ という気持ちで読んでいただけると嬉しいです。 テキストのDataAugumentationについて 画像のDataAugumentationといえば画像を回転させたりノイズをのせたりなど様々なことが考えられます。 しかし、テキストでは順序なども影響するため回転などは推奨されていません。 例えば 今日 は 良い 天気 です ね 。 という文章を回転させてしまうと 。 ね です 天気 良い は 今日 となってしまい、日本語として伝わらない文章になってしまいます。 そのため、テキストのDataAugumentationとしては - 類似の単語で置き換える - ルールベースで単語を置き換える などの手法が使われることが多くなっています。 例えば 朝ごはん に パン を 食べ まし た 。 という文章は 朝ごはん に サンドイッチ を 食べ まし た 。 に置き換えても違和感がなく伝わる文章になります。 このようにしてテキストのDataAugumentationが行われています。 CutMixとは 今回試してみたCutMixはこれらのDataAugumentationの手法のひとつです。 論文: https://arxiv.org/pdf/1905.04899.pdf MixupやCutoutを組み合わせたような手法です。 論文の図がとてもわかりやすいと思います。 画像でこれらのことを行うとふたつの画像を半分ずつ合成するMixupとある区間をカットしてしまうCutoutを組み合わせて ある部分を違う画像で置き換える という手法がCutMixです。 Mixupでは合成をすることで不自然になってしまうことや、Cutoutではカットした部分に重要な情報がはいっていたなどということがあるため、CutMixはそれらの問題を解決でき、精度も向上する結果を出しています。 これらは画像での紹介になっていますが、Mixupはすでにテキストでの実装例もあり 論文: https://arxiv.org/pdf/1905.08941.pdf こちらではMixupを使うことで精度向上ができたとの記載があります。 では、CutMixをテキストで表現するとどうなるでしょうか? 例えば以下のふたつの文章があったとします。 文章1: 明日 は 遊園地 に 遊び に 行こ う と 思い ます 。 文章2: 動物 の 中 で は 犬 が 1番 好き です 。 これをCutMixすると 動物 の   遊園地 に 遊び に   が 1番 好き です 。 となります。 これに対して混ぜた割合を出力とすることで学習することが可能となります。 文章としてよくわからない内容になってしまっているため、上で書いたような回転と同様の現象が起きてしまっている可能性があり、推奨されていない可能性があると書きました。 内容 今回は約1,000カテゴリを分類してみました。 データはBASEの商品データのテキストを使用しました。また、これらのデータに前処理を行いMeCabで形態素解析されたものを使用しました。 入力を400ワードで固定し、足りない部分は0埋めしてあります。 CutMixの実装は どの範囲を切るのか どのくらいの単語を切るのか について考える必要がありますが、今回は0埋めしている部分などもあるため 0埋めされていない部分で入れ替えを行う 0-20 wordsでの中でランダムに入れ替えを行う 出力の割合は0埋めを除いた部分で行う としました。以下はkerasのgeneratorを使った場合の実装例です。embeddingを行いCNNで学習をしました。出力は1-hot-vectorです。 ▼CutMix実装例 CATEGORY_NUM = 1000 def generate_input_cutmix (x_data, y_data, batch_size= 32 ): max_len = x_data.shape[ 0 ] seq_len = x_data.shape[ 1 ] while True : x = np.zeros((batch_size,seq_len)) y = np.zeros((batch_size, CATEGORY_NUM)) cnt = 0 while cnt < batch_size: try : r = random.randint( 0 , max_len- 1 ) r_mix = random.randint( 0 , max_len- 1 ) if y_data[r]!=y_data[r_mix]: target_idx = np.where((x_data[r,:]> 0 )&(x_data[r_mix,:]> 0 ))[ 0 ] r_num = random.randint( 0 , 20 ) r_idx = random.randint( 0 , len (target_idx)-r_num) x[cnt,:] = x_data[r,:] x[cnt,r_idx:r_idx+r_num] = x_data[r_mix,r_idx:r_idx+r_num] rate = r_num/ len (np.where(x_data[r,:]> 0 )[ 0 ]) one_hot_r = np_utils.to_categorical(y_data[r], CATEGORY_NUM) one_hot_rmix = np_utils.to_categorical(y_data[r_mix], CATEGORY_NUM) y[cnt,:] = ( 1 -rate)*one_hot_r + rate*one_hot_rmix cnt+= 1 except : # 単語数が足りていないこともあるため pass yield x, y もしword数を変えたい場合には r_num = random.randint( 0 , 20 ) ここの数字をいじることで可能です。 また、ここを固定しない場合は r_num = random.randint(0, target_idx) でランダムに決めることができます ▼学習 model.fit_generator(generate_input_cutmix(x_train, y_train), verbose= 1 , steps_per_epoch=x_train.shape[ 0 ] // batch_size, validation_data = generate_input_cutmix(x_validation, y_validation), validation_steps = x_validation.shape[ 0 ] // batch_size, epochs= 30 ) 結果 (1) loss なにもしていない場合とCutMixを使用した場合のlossは以下になりました。 lossを見るとCutMixよりも何もしないバージョンの方が良い落ち具合となっています。画像の場合のCutMixでは、CutMixを使うことでlossの落ち具合が早くなるなどあるため、想定と異なる結果となりました。 これだけをみていると何もしないバージョンの方がよく見えますが、もっと詳細な正答率について調べてみましょう。 ※何もしないバージョンの方は11epochあたりからtestのlossが上昇してしまっているため 過学習をしている可能性も考え、10epochのmodelを使用していきます。 (2) カテゴリの正解率 まず、予測結果の中で最も高い値となったカテゴリが、正解としているカテゴリと一致しているかについてみてみました。 テストには1000データを使用しました。 import numpy as np pred = model.predict(x_test) pred_argmax = np.argmax(pred, axis= 1 ) print (np.sum(pred_argmax==y_test)) ▼結果 何もしない: 21.8% CutMix: 25.0% カテゴリをどれだけ当てられたかで考えると、CutMixの方が良い結果となりました。 (全体的に精度は低いのでですが1,000クラス分類なのでおおめに見てください...) また、何番目に正解データがでたかを累計してみた結果です。 この結果からも、CutMixを使用した方がうまく予測ができている結果となりました。 今回何もしていない方は過学習をしている可能性を考えて10epochを使用していますが、loss的にはとても落ちている20epochを使用しても大きく変わりはしませんでした。 (3) 新しい文章に適用してみる 私が架空の商品説明文を作ってみました。これに適用してみるとどのような結果となるでしょうか? カテゴリ名はそのまま使用できないため、ふわっとしたカテゴリ名に置き換えています。 ①ぬいぐるみ系 ペンギンのぬいぐるみ! 布から作っているペンギンのぬいぐるみです。 ほどよい綿の詰め具合でなんとも言えないもふもふ感を味わえると思います。 ペンギンの種類はアデリーペンギンをイメージしており、癒しの動物を目指しています。 大きさは30cmほどとなっているため部屋にちょっと飾るにもちょうどいいです。 また、安全に配慮して作成しているため 小さいお子様の遊び相手にもぴったりだと思います。 大きさ 縦: 30cm程度 幅: 10cm程度 また、大きさは手ではかっているため多少ずれていることもあります。 ▼結果 なにもしないバージョン: キャラクタ CutMix: 手作り作品 ②食品系 りんごのたくさんはいったアップルパイ20cm りんごの収穫も当園で行なっています。 今年のりんごもとても美味しい出来上がりになりました。 そのまま食べても美味しいりんごですが 今回はお店でも販売をしているアップルパイが期間限定で登場しました! コーヒーにも紅茶にも合う仕上がりとなっています。 贈り物としても選ばれるおすすめの商品です。 ぜひこの機会にいかがでしょうか。 ▼結果 なにもしないバージョン: コーヒー CutMix: 食べ物 今回2作品を創作してみましたがどちらもCutMixではうまく特徴を捉えることができているのではないかという結果となりました。 (4) 可視化 CutMixでは可視化も行うとおもしろい結果をみることができます。可視化では、そのmodelが これが答え! とだした際にどこを判断してそのような答えになったかを確認することができます。 例えば犬と猫をCutMixしたものに対して出力"犬"として入力の可視化をすると犬の部分のみが判断できているなどが可能です。 同様のことがテキストでもできないかと思い試してみました。上の2作品を合体します。形態素解析後なので文章として読みにくいのですが、以下の文章を使用しました。 青が①ぬいぐるみ系の文章で赤が②食品系の文章です。 りんご たくさん いっ アップルパイ cm りんご  ペンギン ぬいぐるみ よい 綿 詰め 具合 なんとも 言え ふも  なり そのまま 食べ 美味しい りんご 今回 店 販売 し いる アップルパイ 期間 限定 登場 し コーヒー 紅茶 合う 仕上がり なっ い 贈り物 選ば れる おすすめ 商品 ぜひ 機会 いかが 今回modelとしてCNNを使用していたため、grad-camを使用して可視化を行なってみました。可視化では、それぞれ ①手作り作品 ②食べ物 と出力した際に注目されていたwordを上位5つピックアップしてみます。 ▼①手作り作品 りんご たくさん いっ アップルパイ cm りんご ペンギン ぬいぐるみ よい 棉   詰め 具合  なんとも 言え  ふも なり  そのまま 食べ 美味しい りんご 今回 店 販売 し いる アップルパイ 期間 限定  登場  し コーヒー 紅茶 合う 仕上がり なっ い 贈り物 選ば れる おすすめ 商品 ぜひ 機会 いかが ▼②食べ物 りんご たくさん いっ アップルパイ cm りんご ペンギン ぬいぐるみ よい 綿 詰め 具合 なんとも 言え ふも なり そのまま 食べ 美味しい  りんご 今回 店  販売 し いる アップルパイ 期間 限定  登場  し コーヒー 紅茶 合う  仕上がり  なっ い 贈り物 選ば れる おすすめ 商品 ぜひ 機会 いかが ①手作り作品では綿を"詰め"るという部分に注目されていますが②食べ物といれると"りんご"に反応しています。 これらの結果からも、CutMixで学習ができているのではないかという結果となりました。 今回はデータも少なく、学習もepoch決め打ちでやっているので条件を変えていくことでCutMixの良さを引き出せる可能性もあるかもしれません。 まとめ 今回はテキストでのカテゴリ分類に対してCutMixを使用してみました。 様々な工夫をすることでテキストにCutMixを使用するということは効果がある可能性がわかりました。 明日はOwners Marketingの栗田さんとProduct Managementの藤井さんです!
この記事は、「 BASEアドベントカレンダー2019 」16日目の記事です。 devblog.thebase.in こんにちは。Owners Growthチームの宮川です。 BASEでは、ショップオーナーさんのことを「Owners(オーナーズ)」と呼んでおり、私たちオーナーズの成長を支援するチームはOwners Growthといいます(オーナーズと呼ぶことになった経緯は こちら )。 今回はOwners Growthで実際どのように支援しているか簡単にご紹介します。 目次 オーナーズとは? ネットショップ運営の構成要素 ショップ運営でやること オーナーズの成長を後押しする 情報提供手段 まとめ オーナーズとは? 一言でオーナーズと言っても、80万ショップの様々なオーナーズがいらっしゃいます。商品のカテゴリだとアパレル・インテリア・コスメ・食べ物など多岐に渡り、個人でモノづくりをされている方から、企業として運用されている場合も。 また、実店舗を持っているのか、ネットショップだけなのか。オリジナル商品なのか、セレクト商品なのかなど、ショップの特性は幅広いです。 すこし前の情報ですが、ネットショップ作成サービス「BASE」をお使いのオーナーズについての調査結果が下記に掲載されています。 「BASE」が初のオーナーズ調査を実施 – 個人・法人を問わずブランドを立ち上げる時代の流れが顕著に - https://binc.jp/press-room/news/press-release/pr_20190514 「BASE」のショップオーナーさんの中には、初めてネットショップを開設・運営される方もたくさんいらっしゃいます。また上述の調査でもあるように半分以上が個人で運営されており、ネットショップの運営を周囲に相談できる方も多くはないように思います。 Owners Growthではそんなオーナーズに、ショップ運営のパートナーとして寄り添い、ショップを継続的に運営していただくための支援ができればと思っています。 具体的には下記のような施策を通じて、ショップごとの運用プロセス最適化のサポートを行っております。 ショップの状況に応じた効果的なメール配信 ネットショップ作成サービス「BASE」のショップオーナーさんが使う管理画面におけるアドバイス機能の提供 オウンドメディア( BASE U )におけるショップ運営・販促ノウハウの紹介 ネットショップ運営の構成要素 はじめに、ネットショップの運営はどのような要素でなりたっているか確認しましょう。 訪問者数 ネットショップに来ていただいているお客様のことです。「新規」と「リピーター」にわけることができます。“一見さん”で来ていただいたお客様を、いかに“常連さん”として再訪問していただくかが重要となります。 購入率 来ていただいたお客様のうち、実際何名に購入されたかの割合です。ここは、「ショップデザイン」「商品画像」「決済方法」と他にもたくさんの要素にわけられます。 購入単価 「商品価格」「まとめ買い」といった、お客様の合計注文額となります。 したがって、ショップ運営はこれらの要素を最適化する必要があります。 ショップ運営でやること それでは、ショップの成長支援をするためには何をすれば良いか。さきほどの構成をもとにして、運営に必要な内容を一部ですがまとめてみました。 訪問者数は「集客」として、「SNS(Instagram・Twitter・YouTubeなど)」「SEO」「広告」などを活用して獲得することできます。 一方、購入率は「ショップ運営」としての「ショップデザイン」「商品画像」「決済方法」などが重要な内容となります。 ショップデザインであれば、訪問していただいたお客様にとってショッピングしやすく、楽しくワクワクするようなデザインになっているのか。また、商品画像は商品の魅力が伝わるような分かりやすい画像になっているのか。というように、各項目ごとの運営内容を検討していきます。 オーナーズの成長を後押しする 上述のように、ショップ運営の成長要素は幾つもあり、Owners Growthではこれらを適切に実施してもらうための情報を、下記のような手段で提供しています。 情報提供手段 「BASE」のショップオーナーさんが使う管理画面の「お知らせ」 メール BASE Creator アプリ(ショップ管理を行えるアプリ)のプッシュ通知 SNS(Instagram・Twitter・Facebook) 以上の4つがあり、下記のような目的で活用しております。 「管理画面」「メール」「プッシュ通知」はショップのカテゴリ・売上・「BASE」の拡張機能であるAppsの活用状況など、ショップのセグメントごとに配信内容を変更できるため、ショップのステータスにあった細かくより深い情報を送ることが可能です。そのため、実行して欲しいアクションをうながすために効果的です。ただし、この3つの中でも、セグメント条件や配信内容によって効果はさまざまあります。 一方、SNSでは配信先をしぼることはできない(Facebookは年齢や地域などで絞ることは可能)かつ、オーナーズ以外のユーザーもいるため、イベント情報や新機能リリースなどライトな情報を送るのに適しています。また、お困りごとや機能に関するフィードバックなどをコメントでいただく可能性があるので、オーナーズとのコミュニケーションの場として有効です。 実際の運用方法としてそれぞれ異なる部分はありますが、基本的には下記のような流れで進めています。 仮説立て 例えば、Instagramのショッピング機能を使ったショップの売上は訪問者数によって変わるのか、等 仮説検証のためのデータ抽出 これを、Re:dash・Google Analytics(Google スプレッドシートにアドオンを入れて連携)などの分析ツールを用いてデータを抽出 施策の立案 仮説が合っていれば、施策への落とし込みを行う この場合、「Instagramのショッピング機能を使い、訪問数が◯◯数以上のショップだと、それ以外にショップに比べると◯◯%売上が高い」というデータが見つかれば、これにもとづく施策を立案 特にデータの変化が見えなければ、別の仮説を検討 施策の実行 この仮説を実証するために、「Instagramのショッピング機能」を利用していないショップには利用を促し、すでに利用しているショップには訪問者数を上げるための施策を提供 効果検証 施策を実施後、実際に効果があったか上述の分析ツールを使って検証し、改善 ちなみに、「管理画面」「メール」「プッシュ通知」のうち、施策によらず高い開封率をあげているのが「メール」です。世間では「古いツール」「開封率が低い」というイメージが先行しがちなメールですが、オーナーズ向けの施策としては効果的な結果が出ています。 最初に、オーナーズは十人十色という内容を記載いたしましたが、本当にショップの運営状況はさまざまです。 ショップがどのような設定内容で何を登録しているかやどういう行動をしているのかなど、多様なデータと照らし合わせる定量分析はもちろん、ときには定性分析も用いながら最適な情報を適切なタイミングで提供する必要があります。 情報が決まれば適切なツールは、メールなのか管理画面内でのお知らせなのか、それともBASE Creator アプリのプッシュ通知なのか、なども決めなければいけません。 「オーナーズが何に困っているかを見つけてそれを解決したい!」という一心で日々の業務に励んでおります。 まとめ 80万ショップの支援を通じて、これまで売上がなかったショップがはじめて売上ができたり、毎月コンスタントに売上があがっていなかったショップが毎月売上が上がるようになったり、とオーナーズのショップ運営を後押しできたときはすごく嬉しいので、大変やりがいのあるお仕事だと感じています。 “ネットショップは顔が見えない”とは良く言われますが、日々膨大な定量データとアンケートなどの定性データの両面と向き合いながら、オーナーズの顔を想像して日々業務に取り組んでいます。 80万ショップの成長を支援するという、他にはないユニークな業務に少しでも興味を持ってくださった方がいれば、 採用ページ で会社の雰囲気や実際はたらいているメンバーの様子などを見ていただければと思います。 オーナーズの未来を一緒に支えていきませんか? 明日は、Platform Devマネージャーの大窪さんとData Strategy所属の杉さんです!
この記事は BASE Advent Calendar 2019 の16日目の記事です。 devblog.thebase.in エンジニアの田中( @tenkoma )です。 あなたのマシンにインストールされているPHPのバージョンは何ですか? 仮想マシンやコンテナで開発環境を作ることが増えているので、ホストOSにはPHPが入ってない・気に掛けたことがない、ということも多いかもしれません。 僕は、新しいバージョンを試すために php-build を使ってmacOSでビルド・インストールしています。(また、プロジェクト毎にバージョンの切り替えがしやすいよう direnv を使っています) 今回はphp-buildを使った複数バージョンビルドを、コードを書いて少し省力化してみたので紹介します。 多くのバージョンのPHPをそろえてみました。ただし、Catalinaでは7.0.19未満の動作が実現できていません 前提 この記事で紹介するコードは以下の環境で実行しています。 OS: macOS 10.15 Catalina 依存ライブラリのインストールはHomebrew php-buildは motemen/ghq でローカル環境にclone php-buildの導入・トラブルシュートは以下の記事が参考になります。 複数バージョンの PHP をインストールして使う - OTOBANK Engineering Blog Macのphpenv(php-build)でビルドしようとしたら出るエラーと解決まとめ - Qiita 作った理由 php-buildを使うと、自分でソースコードをダウンロードしてビルドするよりは楽に、ビルド・インストールができます。(ただし、ビルドエラー時に必要な依存ライブラリについて調査したりするので、導入時にある程度の知識や調査の時間が必要です) 例えば、以下のようなコマンドでビルド+インストールします。 $ php-build -i development 7 . 3 . 12 ~/ local /php/ 7 . 3 . 12 / これでインストールできたらめでたいのですが、macOS をアップグレードしていくと、なぜか依存ライブラリが見つからなくなるようになってきたので、ビルドのためのオプションを付けて以下のように実行しています。(macOS Catalina 10.15.1にて実行) $ PHP_BUILD_CONFIGURE_OPTS = " --with-zlib-dir= $( brew --prefix zlib ) --with-bz2= $( brew --prefix bzip2 ) --with-iconv= $( brew --prefix libiconv ) --with-libedit= $( brew --prefix libedit ) --with-openssl= $( brew --prefix openssl ) --with-libxml-dir= $( brew --prefix libxml2 ) --with-curl= $( brew --prefix curl ) --without-tidy " YACC = $( brew --prefix bison ) /bin/bison PHP_BUILD_EXTRA_MAKE_ARGUMENTS =-j4 php-build -i development 7 . 3 . 12 ~/ local /php/ 7 . 3 . 12 / さて、PHP の新しいバージョン(ポイントリリース)はだいたい1〜2ヶ月に1度リリースされているようですが、このとき、7.3と7.2と7.1の新しいバージョンがほぼ同時にリリースされるという感じなので、そのたびに以下のようなコマンドを実行することになります。 $ ghq look php-build $ git pull $ ./install.sh $ exit $ export PHP_BUILD_CONFIGURE_OPTS= " --with-zlib-dir= $( brew --prefix zlib ) --with-bz2= $( brew --prefix bzip2 ) --with-iconv= $( brew --prefix libiconv ) --with-libedit= $( brew --prefix libedit ) --with-openssl= $( brew --prefix openssl ) --with-libxml-dir= $( brew --prefix libxml2 ) --with-curl= $( brew --prefix curl ) --without-tidy " $ export YACC= $( brew --prefix bison ) /bin/bison $ export PHP_BUILD_EXTRA_MAKE_ARGUMENTS= -j4 $ php-build -i development 7 . 3 . 12 ~/ local /php/ 7 . 3 . 12 / $ php-build -i development 7 . 2 . 25 ~/ local /php/ 7 . 2 . 25 / $ php-build -i development 7 . 1 . 33 ~/ local /php/ 7 . 1 . 33 / php-build コマンドを打つこと自体は対して大変ではありませんが、マイナーバージョンごとの最新バージョン番号を確認するのが面倒ですし、自動化出来そうだったのでやってみました。 複数のPHPバージョンをビルドするスクリプト 以下のスクリプトを作りました。 php-build-auto.sh #!/usr/bin/env bash function usage_exit() { echo " Usage: $0 [OPTIONS] <version1> [<version2> [...]] " echo echo " Options: " echo " -h, --help " echo " --parallel num (default: CPU physical core number) " echo " --install-root-path path (default: \$ HOME/src/local/php " echo " --override " echo " --show-versions " echo exit 1 } # option defalut PARALLEL = $( sysctl -n hw.physicalcpu_max ) INSTALL_ROOT_PATH = " $HOME /local/php " OVERRIDE = false SHOW_VERSIONS = false param = () for OPT in " $@ " do case $OPT in -h | --help ) usage_exit exit 1 ;; --parallel ) PARALLEL = $2 shift 2 ;; --install-root-path ) INSTALL_ROOT_PATH = $2 shift 2 ;; --override ) OVERRIDE = true shift 1 ;; --show-versions ) SHOW_VERSIONS = true shift 1 ;; * ) if [[ -n " $1 " ]] && [[ ! " $1 " =~ ^-+ ]] ; then param+ = ( " $1 " ) shift 1 fi ;; esac done if [ -p /dev/stdin ]; then IFS =$' \n ' for line in $( cat - ) do param+ = ( " $line " ) done fi BUILD_VERSIONS = () SKIP_VERSIONS = () if [ $OVERRIDE = true ]; then BUILD_VERSIONS = $param else for VERSION in " ${param[ @ ]} " ; do if [ -e " $INSTALL_ROOT_PATH / $VERSION /bin/php " ]; then SKIP_VERSIONS+ = ($VERSION) else BUILD_VERSIONS+ = ($VERSION) fi done fi echo " skip versions: " echo " ${SKIP_VERSIONS[ @ ]} " echo " build versions: " echo " ${BUILD_VERSIONS[ @ ]} " if [ $SHOW_VERSIONS = true ]; then exit 0 fi export PHP_BUILD_CONFIGURE_OPTS= " --with-zlib-dir= $( brew --prefix zlib ) --with-bz2= $( brew --prefix bzip2 ) --with-iconv= $( brew --prefix libiconv ) --with-libedit= $( brew --prefix libedit ) --with-openssl= $( brew --prefix openssl ) --with-libxml-dir= $( brew --prefix libxml2 ) --with-curl= $( brew --prefix curl ) --without-tidy " export YACC= " $( brew --prefix bison ) /bin/bison " export " PHP_BUILD_EXTRA_MAKE_ARGUMENTS=-j $PARALLEL " echo " ${BUILD_VERSIONS[ @ ]} " | xargs -n 1 -t -I@ php-build -i development @ " $INSTALL_ROOT_PATH " /@/ 使い方ですが、引数でPHPバージョンを指定すると、まとめてビルドしてくれます。 $ ./php-build-auto.sh 7 . 0 . 33 7 . 1 . 33 7 . 3 . 12 skip versions: 7 . 0 . 33 build versions: 7 . 1 . 33 7 . 3 . 12 [ Info ] : Loaded extension plugin [ Info ] : Loaded apc Plugin. ( 以下略 ) すでにインストール済みのバージョンがあれば、ビルドはスキップされます。もし再ビルドしたい場合は --override オプションを付けます。 $ ./php-build-auto.sh --override 7 . 0 . 33 7 . 1 . 33 7 . 3 . 12 このスクリプトですが、作りはじめたときは xargs -P を使って php-build コマンドを並列実行させるのが最大の特徴でした。しかし、PHPビルド後のXdebugビルドは、同じディレクトリで実行されるので、複数のXdebugビルドを1ディレクトリで同時にやってしまい、エラーになってしまったのでその機能を削除しています。 マイナーバージョン毎の最新バージョン番号を列挙する php-build-auto.sh は、新しいポイントリリースが出たときにまとめてビルドしたいときに使います。そこで、最新のポイントリリースバージョンを列挙するスクリプトを別途作りました。 listversion.php #!/usr/bin/env php <?php declare ( strict_types = 1 ) ; /** * usage: php listversion.php [--filter stable|minor-head] [--oldest-version version] [--definitions-path path] */ class PhpVersion { const VERSION_PATTERN = '/(?P<major>\d+)\.(?P<minor>\d+)\.(?P<point>\d+)/' ; public static function getMinorVersion ( string $ version ) { preg_match ( self :: VERSION_PATTERN, $ version , $ matches ) ; return sprintf ( '%s.%s' , $ matches [ 'major' ] , $ matches [ 'minor' ]) ; } public static function isStable ( string $ version ) { return preg_match ( self :: VERSION_PATTERN, $ version ) === 1 ; } } $ argvOptions = getopt ( '' , [ 'filter:' , 'oldest-version:' , 'definitions-path:' ]) ; $ options = [ 'filter' => !empty ( $ argvOptions [ 'filter' ]) ? $ argvOptions [ 'filter' ] : 'minor-head' , 'oldest_version' => !empty ( $ argvOptions [ 'oldest-version' ]) ? $ argvOptions [ 'oldest-version' ] : '5.6.0' , 'definitions_path' => !empty ( $ argvOptions [ 'definitions-path' ]) ? $ argvOptions [ 'definitions-path' ] : '/usr/local/share/php-build/definitions/' , ] ; $ definitionsIter = new DirectoryIterator ( $ options [ 'definitions_path' ]) ; $ versions = [] ; foreach ( $ definitionsIter as $ definition ) { if ( $ definition -> isDot ()) { continue ; } $ version = $ definition -> getFilename () ; if ( ! PhpVersion :: isStable ( $ version )) { continue ; } if ( version_compare ( $ version , $ options [ 'oldest_version' ] , '<' )) { continue ; } if ( $ options [ 'filter' ] === 'minor-head' ) { $ minorVersion = PhpVersion :: getMinorVersion ( $ version ) ; if ( !isset ( $ versions [ $ minorVersion ]) || version_compare ( $ version , $ versions [ $ minorVersion ] , '>' )) { $ versions [ $ minorVersion ] = $ version ; } } else { $ versions [] = $ version ; } } echo implode ( " \n " , $ versions ) . PHP_EOL; 実行すると、各マイナーバージョン毎の最新バージョンを列挙します。 $ ./listversion.php 7 . 2 . 25 7 . 3 . 12 7 . 1 . 33 7 . 0 . 33 僕の環境だと 7.0.19 未満のバージョンはビルドエラーになったので、列挙しないようにしています。列挙したい場合は --oldest-version オプションを使います。 $ ./listversion.php --oldest-version=5.3 7 . 2 . 25 5 . 4 . 45 7 . 3 . 12 7 . 1 . 33 5 . 3 . 29 5 . 5 . 38 7 . 0 . 33 5 . 6 . 40 php-build-auto.sh は引数と標準入力の両方でビルド対象を指定できるので、 listversion.php と組み合わせて、「マイナーバージョン毎の最新バージョンをまとめてビルド」、が実現できます。 $ ./listversion.php | ./php-build-auto.sh skip versions: 7 . 1 . 33 7 . 0 . 33 build versions: 7 . 2 . 25 7 . 3 . 12 php-build -i development 7 . 2 . 25 /Users/kojitanaka/ local /php/ 7 . 2 . 25 / [ Info ] : Loaded extension plugin [ Info ] : Loaded apc Plugin. ( 以下略 ) 2つのスクリプトを作った結果、PHPのバージョンアップ時の作業は以下のように単純化できました。 $ ghq look php-build $ git pull $ ./install.sh $ exit $ cd ~/src/github.com/tenkoma/php-build-tools $ ./listversion.php | ./php-build-auto.sh 今回実装したスクリプトは tenkoma/php-build-tools にて公開しています。 まとめ 複数のPHPバージョンを手元にそろえるときに使えるphp-buildの運用を多少楽にするためにコードを書き、バージョンアップ時に考える要素を減らしました。最初やりたかった複数バージョンの並列ビルドはできていません。 また、Homebrew でライブラリをアップグレードすると、php-build でビルドしたPHPが動作しなくなることもあります。2年前は5.3〜7.1まで揃えられましたが、Catalinaではまだ5.5, 5.6 のビルドができていません。 最新のmacOSで古いPHPを動かしにくくなってきている気がするので、PHPの古いバージョンを揃えたい場合は、 phpallのDocker版を作ってみた話 - hamacoの日記 のように、Docker を使った方がいいかもしれません。 明日はPlatform Devマネージャーの大窪さんとData Strategyの杉さんです。
この記事はBASE Advent Calendar 2019の15日目の記事です。 devblog.thebase.in DataStrategyの齋藤( @pigooosuke )が担当します。 ONNXの概要 Open Neural Network Exchange(ONNX)とは、機械学習モデルを表現するフォーマット形式のことです。ONNXを活用すると、PyTorch, Tensorflow, Scikit-learnなどの各種フレームワークで学習したモデルを別のフレームワークで読み込めるようになり、学習済みモデルの管理/運用が楽になります。今回の記事では、よく利用されているLightGBMモデルからONNXへの出力方法の確認と、ONNXの推論を行う実行エンジンであるONNX Runtime上での推論速度の改善がどれほどなのかを検証していきたいと思います。 https://onnx.ai 学習モデルの用意 今回は、KaggleのTitanicデータを使用して、binary classificationの予測モデルを作成します。 Dataset: https://www.kaggle.com/c/titanic import pandas as pd from sklearn.model_selection import train_test_split import lightgbm as lgb data = pd.read_csv( "path/train.csv" ) y = data[ 'Survived' ] X = data.drop([ 'Survived' , 'PassengerId' , 'Name' , 'Ticket' , 'Cabin' ], axis= 1 ) # カテゴリー変数をbooleanに展開 # 現在、LightGBMのカテゴリー変数を直接ONNXに変換することが出来ないため category_cols= X.select_dtypes( 'O' ).columns.tolist() X = pd.get_dummies(X, columns=category_cols, drop_first= True , dtype= bool ) X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size= 0.1 , random_state= 2019 ) # training train_data = lgb.Dataset(X_train, label=y_train) valid_data = lgb.Dataset(X_valid, label=y_valid) train_params = { 'task' : 'train' , 'boosting_type' : 'gbdt' , 'objective' : 'binary' , 'num_leaves' : 28 , 'learning_rate' : 0.01 , 'verbose' : 0 , } gbm = lgb.train( train_set=train_data, params=train_params, num_boost_round= 1000 , valid_sets=[train_data, valid_data], early_stopping_rounds= 10 , verbose_eval= 10 ) # Training until validation scores don't improve for 10 rounds # [10] training's binary_logloss: 0.625936 valid_1's binary_logloss: 0.582429 # [20] training's binary_logloss: 0.588612 valid_1's binary_logloss: 0.550251 # ... # [240] training's binary_logloss: 0.331639 valid_1's binary_logloss: 0.346977 # [250] training's binary_logloss: 0.327381 valid_1's binary_logloss: 0.346515 # Early stopping, best iteration is: # [248] training's binary_logloss: 0.328348 valid_1's binary_logloss: 0.346271 かなり雑ですが、モデルの用意が出来ました。 # 型の確認 X.info() # <class 'pandas.core.frame.DataFrame'> # RangeIndex: 891 entries, 0 to 890 # Data columns (total 8 columns): # Pclass 891 non-null int64 # Age 714 non-null float64 # SibSp 891 non-null int64 # Parch 891 non-null int64 # Fare 891 non-null float64 # Sex_male 891 non-null bool # Embarked_Q 891 non-null bool # Embarked_S 891 non-null bool # dtypes: bool(3), float64(2), int64(3) # memory usage: 37.5 KB # データの確認 X.head() # Pclass Age SibSp Parch Fare Sex_male Embarked_Q Embarked_S # 0 3 22.0 1 0 7.2500 True False True # 1 1 38.0 1 0 71.2833 False False False # 2 3 26.0 0 0 7.9250 False False True # 3 1 35.0 1 0 53.1000 False False True # 4 3 35.0 0 0 8.0500 True False True ONNX変換 ONNXに変換するためには、事前にinputの型を定義する必要があります。 用意されている型は以下の通りです。 整数型: Int32TensorType, Int64TensorType 真偽型: BooleanTensorType 浮動小数数型: FloatTensorType, DoubleTensorType 文字列型: StringTensorType 辞書型: DictionaryType 配列型: SequenceType 今回は、全てnumpyのfloat32でinputを受け付けるようにします。 この設定は活用している学習モデルなどによって変わってきます。 例えば、scikit-learnのPipelineを活用して、テキスト入力をtfidfで変換する処理などを含めてONNX化したい場合は、 inputにStringTensorTypeを設定する必要があります。 参考URL: http://onnx.ai/sklearn-onnx/auto_examples/plot_tfidfvectorizer.html#tfidfvectorizer-with-onnx LightGBMをONNXに変換するために onnxmltools が必要になるので、事前にライブラリをインストールします。 https://github.com/onnx/onnxmltools import onnxmltools from onnxmltools.convert.common.data_types import FloatTensorType, BooleanTensorType, Int32TensorType, DoubleTensorType, Int64TensorType # 入力の型定義 initial_types = [[ 'inputs' , FloatTensorType([ None , len (X.columns)])]] # LightGBM to ONNX onnx_model = onnxmltools.convert_lightgbm(gbm, initial_types=initial_types) # save onnxmltools.utils.save_model(onnx_model, "lgb.onnx" ) # モデルをvizualize可能 onnxmltools.utils.visualize_model(onnx_model) inputs は、入力のラベル名です。 入力のshapeは [None, 特徴量数] のFloatTensorを指定しています。 参考までに、LightGBMのclassifierのモデルは下図のような構成になっています。(visualize_modelで生成) 入力値を決定木を通じて、予測ラベルと予測確度を出力しています。 推論 ONNX用の実行環境として、Microsoftが出しているonnxruntimeを使います。 こちらもインストールします。 https://github.com/microsoft/onnxruntime import onnxruntime session = onnxruntime.InferenceSession( "lgb.onnx" ) # 入力のラベル名の確認 print ( "input:" ) for session_input in session.get_inputs(): print (session_input.name, session_input.shape) # 出力のラベル名の確認 print ( "output:" ) for session_output in session.get_outputs(): print (session_output.name, session_output.shape) # vizualizeした図と一致 # input: # inputs [None, 8] # output: # label [None] # probabilities [] # 推論実行 preds = session.run([ "probabilities" ], { "inputs" : X_train.values[ 0 ].astype( "float32" ).reshape( 1 , - 1 )}) print (preds) # [[{0: 0.0961046814918518, 1: 0.9038953185081482}]] # LightGBMの予測 preds = gbm.predict(X_train.values[ 0 ].reshape( 1 , - 1 )) print (preds) # array([0.90389532]) 第1引数に出力ラベル名(今回はprobabilitiesのみを出力)。 第2引数に入力ラベル名と値をセットして推論を実行します。 予測結果もLightGBMの予測とONNXの予測がちゃんと一致していました。 速度計測 # onnx %%timeit -r 30 for v in X_train.values: pred = session.run([ "probabilities" ], { "inputs" : v.astype( "float32" ).reshape( 1 , - 1 )}) # 43.3 ms ± 7.86 ms per loop (mean ± std. dev. of 30 runs, 10 loops each) # lightgbm %%timeit -r 30 for v in X_train.values: pred = gbm.predict(v.reshape( 1 , - 1 )) # 84.4 ms ± 8.96 ms per loop (mean ± std. dev. of 30 runs, 10 loops each) MacOS 10.14.6 Intel Core i5 3.1 GHz python=3.7.3 numpy=1.15.2 lightgbm=2.3.1 onnx=1.6.0 onnxconverter-common=1.6.0 onnxmltools=1.6.0 onnxruntime=1.0.0 上記の条件で計測したところ、ONNXモデルはpureなLightGBMに比べて約半分ほどの時間で推論が出来ているのが確認できました。 ONNXは途中で型変換を入れているので厳密に平等な比較とは言えませんが、それでも十分早かったです。 モデルファイルサイズ計測 import pickle with open ( "lgb.pkl" , "wb" ) as f: pickle.dump(gbm, f, protocol=pickle.HIGHEST_PROTOCOL) !du -h lgb.pkl # 740K lgb.pkl !du -h lgb.onnx # 500K lgb.onnx モデルファイルサイズに関しても、pickleでの圧縮に比べ、68%まで軽量化することが出来ました。 今回は、LightGBMでの手順を確認しましたが、 https://github.com/onnx では、各種フレームワークの対応が次々に進んでいます。 独自カスタムした計算をしていない限り対応出来ると思うので、学習モデル運用でONNXを検討してみてはいかがでしょうか。 まとめ 今回、LightGBMのモデルからONNX形式でモデル出力をする手順の紹介と、ONNX上での推論速度の検証を行いました。 ONNXを利用することで学習フレームワークに依存せず、高速な推論ができる環境を作ることが出来そうですね。 明日は基盤グループの id:tenkoma さんとOwners Growthの id:MiyaMasa です!お楽しみに!
この記事はBASE Advent Calendar 2019の15日目の記事です。 こんにちは。フロントエンドグループの加藤です。 私達は、「 Payment to the People,Power to the People. 」というミッションを掲げ、日々サービスづくりを頑張っています。 Peopleとは誰か このミッションにある、 People とは誰のことを指すのでしょうか? 自分の周りの環境を想像しても、実に多様な人がいることがわかります。 また、日々ショップオーナーさんや購入者さんからいただく様々なお問い合わせの内容を見ていると、ほんとに様々な背景を持った方々に使っていただいているんだなと思います。 Webフロントエンド開発者としては、自分の力で出来ることがあれば、出来る限り多様な使われ方に対応できるプロダクトにしていきたいという思いがあります。 何を指針とするか では、まず何をどうすればいいのでしょうか。よくわかりません。 調べると、どうやらWeb技術の標準化を行う非営利団体であるW3C(World Wide Web Consortium)が勧告しているガイドラインが存在するようです。 Web Content Accessibility Guidelines (WCAG) 2.0 また、WCAG2.0に関しては実際に対応する時に参考にできる解説書もありました。どちらも日本語化されています。大変ありがたいです。 WCAG 2.0 解説書 細かく言うと、この項目をどれだけ対応するかによってレベルA~AAAなどのレベル付けがあるようです。今回は絶対どのレベルを厳守するんだ!というよりも、自分の中で実装時に意識すべきことを掴みたい、まずは慣れたい、といった動機で始めているので、まずはAの中でも対応できそうなものからやってみたいと思います。 練習してみよう ということで、実際に弊社でも利用しているフレームワーク、Vue.jsを使って、かんたんなウェブページをよりアクセシブルにしていく素振りをしてみます。 まずは何も考えずに作っていく 作るものはなんでもよいので、「自由に投稿できる動物ずかん」をイメージして作ります。一覧画面と詳細ページ、そしてモーダルで開いて入力する画面があるとしましょう。 できました。 CodeSandboxのリンク 特筆すべき点はないですが、 モーダルとボタンをそれぞれ共通のコンポーネントとして切り出した vue-routerを利用してクライアントで一覧と詳細画面をそれぞれルーティングさせている というところで、非常に簡単ではありますが実際のフロントエンド開発でよくあるシーンを再現してみました。(今回はVue.jsやその他のライブラリの詳しい説明に関しては省きます) 課題を発見していこう とにかく動くものを作ったのですが、そもそも課題が何なのかわかっていません。 今回はWCAG2.0をバイブルとして進めていくので、一つ一つ目を通して、これは守れてないなと思ったものを地道にクリアしていくことにしましょう。 原則 1: 知覚可能 - 情報及びユーザインタフェース コンポーネントは、利用者が知覚できる方法で利用者に提示可能でなければならない。 1.1.1 非テキストコンテンツ: 利用者に提示されるすべての 非テキストコンテンツ には、同等の目的を果たす テキストによる代替 が提供されている。 ただし、次の場合は除く (!) コントロール、入力: 非テキストコンテンツが、コントロール又は利用者の入力を受け付けるものであるとき、その目的を説明する 名前 (name) を提供している。 これは、ひとまず作ったボタンコンポーネントに問題があります。 <template> <div @click="$emit('click')" class="button"> <slot></slot> </div> </template> divですね。 コントロール又は利用者の入力を受け付けるもの だと全く伝わりません。初歩的ですが、気を抜くと似たようなことはよくやってしまいます。 この項目に リンクされている解説書の項目 では、 アクセシブルなウェブコンテンツ技術の標準コントロールを使用する場合、このプロセスは簡単である。ユーザインタフェース要素が仕様に準じて使用される場合、この条件に条項は満たされる。 とされており、セマンティックなマークアップを守れば特別な工夫をせずとも条件を満たすことができるようです。以下のようにしてみました。 <template> <button @click="$emit('click')" class="button"> <slot></slot> </button> </template> よさそう。しかしこれもまだ問題があります。 これはアクセシビリティの問題ではなく、このボタンというコンポーネントは、機能としてのボタンを切り出したいのではなく、単なるプレゼンテーションとしてのボタンぽい見た目をただ切り出したいのです。ボタンの見た目を提供するのに、buttonタグとしてしか使えないと、使われる文脈によっては正しくないマークアップを強制してしまう可能性があります。 ここは、デフォルトはbuttonタグで、必要に応じてprops経由でタグを指定出来るようにしてみましょう。こういったケース(ルートエレメントを動的に変えたい)は、jsxで書くことで実現できます。詳しくは、 弊社松原のVue.js+JSX基本文法最速入門 という記事を見ていただくとよく理解できるかと思います。 export default { props: { tag: { type: String, default: "button" } }, render(h, context) { const tag = this.$props.tag; return ( <tag {...this.$attrs} class="button" onClick={() => this.$emit("click")}> {this.$slots.default} </tag> ); } }; こうすることで、以下のようにaタグをボタンにしたい場合でも対応することが可能になりました。 <custom-button tag="a" href="/hoge" /> また、実はもう一つ課題があります(インタラクティブな要素の実装は本当に大変ですね)。 よくあるケースなのですが、マウスでクリックしたあと、その要素がフォーカスされるので、フォーカスインジケータが表示されるのですが、これをポインティングデバイスによるフォーカスでは出さないようにしたいのです。 しかし、これを素朴に .button:focus{outline: none;} としてしまうと、ポインティングデバイス以外の入力によってフォーカスされた場合、視覚でそのことを伝えることができません。先程の達成基準 4.1.2 の解説にも 特に重要なユーザインタフェース コントロールの状態は、フォーカスを持つかどうかである。 とありますが、マウス以外の入力では、フォーカスされた状態をユーザーに伝えることは非常に重要そうです。 幸いにも、CSSの *:focus-visible 疑似クラスによってこれは達成できます。これは、ユーザーエージェントが要素にフォーカスを明示するべきであるとした場合にのみスタイルを適用することが出来る便利な擬似クラスです。しかしながら、まだこれは草案の段階であり、ほとんどのブラウザで実装されていませんので、今回はpolyfill( focus-visible )を導入し、該当のフォーカス時にのみ要素に適用されるdata属性に対してフォーカスのスタイルを当ててみましょう。 .button:focus { outline: none; } .button[data-focus-visible-added] { outline: 2px solid #000; } これでbuttonコンポーネントは一旦大丈夫そうです。 tableタグ captionをつける 今回、動物の一覧を並べるのにtableタグを使用しました。CSSの表現力が高まるにつれて使う機会が徐々に減ってはいますが、管理画面などで一覧性を担保しながら要素を表に並べるという用途では未だによく使うタグでもあります。 今回は以下の達成方法にある、caption要素を使用するとより何の表なのかがわかりやすいのではないかと思いました。 H39: データテーブルのキャプションとデータテーブルを関連付けるために、caption 要素を使用する | WCAG 2.0 達成方法集 行全体をクリッカブルにする <tbody> <tr :key="animal.id" v-for="animal in animals" @click="onRowClick(animal.id)"> <td>{{ animal.name }}</td> <td>{{ animal.emoji }}</td> </tr> </tbody> 行全体をクリッカブルにして、押されたらその行を詳しく見る/操作できる詳細画面が開く、ようなアプリケーションはよくあると思いますが、今回もそうしてみました。ただ、trのクリックイベントでページ遷移させていて、先ほどのようにセマンティックなマークアップでないがために、押せることもわからないし、キーボードで操作することもできません。困った。 一度、全体をクリッカブルにしたい!というところから一歩引いて、そもそもこれは何ができればいいのかを考えてみます。詳細ページに飛ばしたいんですよね。であれば、aタグでマークアップされるべきです。ただ今回はtableでマークアップしていて、trをaタグで囲うことができません。ただ、cellの中には当然aタグを置くことはできます。という発想から、以下のようにしてみました。 <table class="table"> <caption>現在登録されている動物の一覧</caption> <thead> <tr> <td>名前</td> <td>emoji</td> <td class="util-hidden">リンク</td> </tr> </thead> <tbody> <tr class="row" :key="animal.id" v-for="animal in animals" @click="onRowClick(animal.id)"> <td>{{ animal.name }}</td> <td>{{ animal.emoji }}</td> <td class="util-hidden"> <router-link :to="{name: 'detail', params: { id: animal.id }}">{{ animal.name }}の詳細を見る</router-link> </td> </tr> </tbody> </table> export default { // ... methods: { onRowClick: function(id) { this.$router.push({ name: "detail", params: { id } }); } } }; .row { cursor: pointer; } .row:hover { background-color: #ddd; } .row:focus-within { background-color: #ddd; } .util-hidden { position: absolute !important; clip: rect(1px, 1px, 1px, 1px); } 思い切って、テキストリンクを配置した列を新たに追加しました。ただ、その列を表示上は非表示にします。( .util-hidden という名前のユーティリティクラスを付与していますが、これはアクセシビリティの対応でよく使われるCSSです。スクリーンリーダーなどの支援技術で利用してもらいたいのですが、視覚上からは非表示にしたほうが都合が良いケースで使われます。) これにより、リンクにキーボードでフォーカスすること自体は可能になりました。その際に視覚上で行全体のフォーカスを表現するため、focus-within疑似クラスを使用します。これは、内包する要素がフォーカスされていた場合にスタイルを適用することが出来る便利な擬似クラスです(これもfocus-visibleと同じように全てのブラウザで対応しているわけではないので、 focus-within-polyfill を利用しています)。 trタグにfocus-withinで内包する要素がフォーカスされている場合にスタイルを適用することにより、キーボードでも現在フォーカスしている行を視認しながら移動することが可能になりました。 最後に、マウスでクリックできることを表現するために、hover擬似クラスでカーソルをpointerに設定し、trがクリックされたら、詳細画面へのルーティングを実行するようにします。 こんな感じでしょうか…? モーダルダイアログ モーダルはWCAGを見る前から何かあるだろうな…と思いましたが非常に問題が多そうです。ただ、モーダルをアクセシブルに作る方法はある程度約束事が決まっており、少し前ですが ヤフー株式会社の福本さんによるスライド がわかりやすく非常に参考になります。 端的に言えば以下の対応になります。 マシンリーダブルなコードにするため、WAI-ARIA属性によりマークアップの情報を増やす キーボード操作に対応する フォーカストラップを実装する escキーで閉じれるようにする 開いた際に最初のインタラクティブな要素にフォーカスを移す 閉じた際に元々フォーカスしていた要素にフォーカスを戻す まるっと勢いで実装してみます。 マシンリーダブルなコードにするため、WAI-ARIA属性によりマークアップの情報を増やす <div role="dialog" aria-modal="true" :aria-labelledby="titleId" :data-show="`${show}`" @click="$emit('cancel')" class="wrapper" > </div> --- props: { titleId: String, rootId: String }, methods: { onShow: function() { this.rootId && document.getElementById(this.rootId).setAttribute('aria-hidden', true); }, onHide: function() { this.rootId && document.getElementById(this.rootId).setAttribute('aria-hidden', false); }, role属性によりダイアログであることと、aria-modal属性により現在のダイアログの下にあるウィンドウは不活性であることを支援技術に伝えます。 また、モーダルダイアログのタイトルとなる要素のidと、本文のコンテンツをラップしている要素のidをprops経由で渡します。前者はaria-labelledbyでモーダルコンテンツのタイトルを伝え、後者はモーダルを開いている際に、裏側のメインコンテンツが非表示になっていることを伝えるために使用します。 開いた際に最初のインタラクティブな要素にフォーカスを移す 今回は、props経由で最初にフォーカスすべき要素のidを受け取るシンプルな作りにしました。 props: { //... initialFocus: String }, methods: { onShow: function() { this.initialFocus && document.getElementById(this.initialFocus).focus() }, //... ---- <modal initialFocus="animal-name" :show="modalShow" @cancel="closeModal"> 閉じた際にフォーカスを戻す data: function() { return { lastActiveElement: null }; }, watch: { show: function(next) { if (next === true) { this.onShow(); } else { this.onHide(); } } }, methods: { onShow: function() { this.lastActiveElement = document.activeElement; }, onHide: function() { this.lastActiveElement && this.lastActiveElement.focus(); }, フォーカストラップを実装する フォーカストラップとは、特にモーダルな状態においてそのコンテンツ内でフォーカスをループできるようにすることで、コンテンツ内での操作性を向上させるための機能です。 今回は実装についての詳細な説明を省きたいので、 vue-focus-lock というライブラリの力を借りました。モーダルのコンテンツを包むだけで上記の機能を実現してくれます。 <template> <div :data-show="`${show}`" @click="$emit('cancel')" class="wrapper"> <div class="contents" @click.stop> <focus-lock> <slot></slot> </focus-lock> </div> </div> </template> escキーで閉じれるようにする props: { escExit: { type: Boolean, default: true } }, methods: { onShow: function() { document.addEventListener("keydown", this.checkKeyDown); }, onHide: function() { document.removeEventListener("keydown", this.checkKeyDown); }, checkKeyDown: function(event) { if ( this.escExit && (event.key === "Escape" || event.key === "Esc" || event.keyCode === 27) ) { this.$emit("cancel"); } } } キーコードを愚直に見て、エスケープキーであれば親にキャンセルイベントをemitします。 完成 完成版のCodeSandboxのリンク 振り返って 率直に言えば、普段何気なく実装している機能でも、まだまだやれることがたくさんあったというのは技術者としては少しショックではありましたし、単純に時間がとてもかかったので、普段の開発でどのようにこういった取り組みを持続的に進めていくかは、深く考える必要があると感じました。 ただ、とても良いと感じたことが3つあります。 コンテンツやサービスの中身が何であるか/どうあるべきかを深く考えるきっかけとなる 実装の手法以前に、そもそもこのコンテンツはどういったユーザーがアクセスし、どういった特性があるのかなど、アクセシビリティのガイドラインを意識しよう/セマンティックなマークアップを実現しようと思うと、それを深く考えることを避けて通れなくなるように感じました。これはとてもよいことではないでしょうか。 結果的にどんな人にとっても使いやすい機能やサービスになる より多様な使われ方に対応していく過程の中で、「これをこうすることでこういう使われ方をした際の利便性が下がる」といったトレードオフが発生したケースは今回一つもなく、これからもなさそうに思いました。それどころか、今まで対応してきた使われ方での利便性もより向上しています(例えば、モーダルダイアログを開いた際に最初のinput要素にフォーカスを当てるようにしましたが、これはどんな使われ方をされたとしてもすぐ入力できるので使いやすくなっています)。 今回はあまり触れませんでしたが、カラーコントラストや、テキストの表現の仕方など、基本的にはどのようなユーザーにとってもよりわかりやすい/使いやすいものになるような改善もまだまだたくさんありそうです。 インターネットっぽくていい どんな人がどんな使い方をしても、平等に情報やサービスにアクセスできるというのはとてもインターネットっぽい感じがあります。 最後に まだまだ課題は多くありますが、会社のミッションを実現できるサービスづくりを進めていくために、引き続きできることからやっていきたいと思います。もしご興味ある方は、まずは素振りから始めてみてはいかがでしょうか。 参考 最後に、参考となった記事を羅列になりますが以下に記します。 https://waic.jp/docs/WCAG20/Overview.html https://waic.jp/docs/UNDERSTANDING-WCAG20/Overview.html https://www.w3.org/TR/wai-aria-practices/examples/dialog-modal/dialog.html https://a11yproject.com/ https://qiita.com/simezi9/items/ec9dfbb3c7af09088898 https://www.slideshare.net/techblogyahoo/scripty05 https://www.w3.org/TR/wai-aria-practices/examples/dialog-modal/dialog.html 明日は基盤グループの田中さんとOwners Growthの宮川さんです。お楽しみに!
本稿は BASE Advent Calendar 2019 🎅🎄 の14日目です! こんにちは、Product Divisionの金城( @o0h_ )と申します。 10月にBASEに入社しまして、ブログ投稿は本稿が初となります。よろしくおねがいします。 さて、早速掲題の件に入らせていただきます。 PSR, PHP-FIG 「PSRはPHP-FIGが策定し運用する一連の規格であり、その19号はPHPDoc Tagsについての決め事」です。 PSRには色々なレイヤーのルールが ─時には 「本来の主旨を超えている」という批判 をもさえ巻き起こしながら─ 策定されています。 1 PHP-FIGは、元々はオートローディング活用のための取り組み 2 を契機として作られた団体です 3 。つまり、元来から「PHPの書き方」に強い関心を持っていたとも言えます。 PSRの「0番目がAutoloading Standard」という事だけに留まらず、「続く1番目はBasic Coding Standard」「2番目がCoding Style Guide」であることからも「初期にフォーカスしていた領域がどこであるか?」というのを伺い知れるのではないでしょうか。 更に言い加えると、PSR-0,2のみが現状でDeprecatedステータスに配置されています 4 。 すなわち、「より洗練した規格を後から作り、採択している」のが、AutoloadingとConding Styleに関する話題であるという事です。 5 そして、2019年12月現在で「Draft」にあるのが「PSR-5: PHPDoc Standard」と「PSR-19: PHPDoc tags」です。 ある意味で「実装のし易さ」と同じくらいに「コードを読みやすく・違和感なく」運用していく事に重きを置いていた団体が、「doccomment/tagの書き方について未だに標準化できていない」・・というのは、ちょっと面白いと思いませんか?一体どんな経緯があったのでしょうね。 そんな訳で、本稿では「PSR-19なんなのよ」という事について見ていきたいと思います。 PHPと「doc comment」 一昔前まで、PHPでdoc commentというと「いろんな方言がある」という印象を抱いた方も多いと思います。私もその1人です。 (以下、doc commentについて「PHPDoc」 6 と表記することとします。) 「phpDocumentorのそれに従おう」「いやPhpStormに合わせりゃよくない?」「Phanが」「PHPStanが」といったソレです。 もちろん、それぞれ目的が違うものであり、自分達の目的を果たすために独自の拡張的な記法なども導入していく事には必然性があります。完全に統一され得るものではないでしょう。・・とはいえ、いち利用者的な観点からすると「似たような事は同じ書き方で出来ればいいのにな」と思う訳です。まさに「相互運用性のための決まり事」があると良かろうなぁという場面で、PSRに目が向きます。 なるほど、それがPSR-5です。 しかしこのPSR-5、提案自体は数年前になされたものの、「広範に支持されるような勧告」といった地位にまでは至りませんでした。結局は「色々な書き方がある内のいち方言」のような雰囲気のままです。そうして遂には議論が停止、草案は「STATUS:ABANDONED」 7 に・・・・ なったのですが、これが再提案されて「DRAFT」化 8 、今に至ります。そういった訳で「5番」などという非常に若い番号が未だに議論されている訳ですね。 そして新生PSR-5はEditor/Working group membersとして「phpDocumentetor」「PhpStorm」「Psalm」「PHPStan」の関係者をそれぞれ迎えています。これは現実的な落とし所を見つけつつ、各ツールのサポートの観点からも実用性を伴う状況が実現できるのではないでしょうか。期待ですね! 昨年時点でのPSR-5の状況については、こちらの記事に大変よくまとまっておりました。ご参照ください。 qiita.com PSR-19 PSR-5の「復活」にあたって、一緒に提出されているのがPSR-19です。 これは、PSR-5にまとまっていた内容を2つに分けようという提案 9 に基づくものになります。 PSR-5についてはPHPDocのフォーマットを、PSR-19についてはタグの種類を決める内容になります。二者が分割された動機については「PHPdocのフォーマット(PSR-5)を一般的な内容にしつつ、タグの内容(PSR-19)は拡充しやすくする。また、将来的にはPHPDoc外でも利用可能になることを見越して」と説明されています。 The rationale behind the splitting is to make it easier to add new tag dictionary PSRs in the future and also make PHPDoc more generic so it can be used for non-docblock standard such as Doctrine-style annotations. 例として Doctrine-style annotations が挙げられていますが、例えば <?php /** * @MyAnnotation(myProperty="value") */ private $ bar ; のようなものでしょうか。 ということで、本稿は(既にその内容には見慣れているであろうPSR-5よりも)PSR-19の中身はどうなっているのかな?と興味を持って調べてみるものです。 中身を見てみる 現時点でのPSR-19のproposalはこちらから確認できます。 fig-standards/phpdoc-tags.md at 2668020622d9d9eaf11d403bc1d26664dfc3ef8e · php-fig/fig-standards · GitHub また、GitHub上での議論(PR)についてはラベルで辿ってみるのが良いでしょう。 Pull Requests · php-fig/fig-standards · GitHub 中身を見てみると、5つのセクションに分かれています。 PHPDoc standard(PSR-5)を補完するのが主目的であること MUSTやSHOULDなどの語についてはRFC 2119に準ずること (いつものやつですね) 用語の定義等はPSR-5に準ずること inheritDocの扱いについて 改変無しで全体を引き継ぐ場合は @inheritDoc を、インラインで埋め込む場合は {@inheritDoc} を用いることなど そして第5セクションが「タグ」の定義です。 現時点では以下のリストが掲載されています @api @author @copyright @deprecated @internal @link @method @package @param @property @return @see @since @throws @todo @uses @var @version このうち、 @method @param @property @return @throws @var などは、すっかりお馴染みなのではないでしょうか。クラスやメソッドの機能に関連するもので、PhpStormなどのIDEやPHPStan・Phanといった静的解析を利用している場合には欠かせません。 残る内容について、その書式や意味を掻い摘んで見ていきましょう。 独断にて、関連するタグごとに以下の4つの群にまとめながら取り上げていきます。 利用者の想定・提供範囲に関わるもの 著作者・権利に関わるもの 関連情報・参照情報に関わるもの バージョン・更新に関わるもの 利用者の想定・提供範囲に関わるもの 論理的な空間・括りと、誰に向けて提供しているものか?に関する情報です @package @package は、「論理上の区分」を示すために用いるものと説明されています。 PHPの持つ言語機能であるNamespaceと近い概念を取り扱うものであり、実際に「階層は(Namespaceと同様に) \ で区切って記述する」と規定していたり、「Namespaceと完全に一致する場合に@packageタグを利用することは推奨しない」と言及されています。 あくまで Namespaceで表現される「機能的な区分」とは別に、論理上の区分を明らかにしておきたいという場合に利用される ものになります。 @api @api は、 「primary public API of a package」に付与されるものと説明されています。 publicなメソッドの中でも、 そのパッケージ外から利用される ようなものを区別するためのものです。 @internal @internal は、@apiと対象的に「only for use within the application, library or package to which it belongs.」と説明されています。 「primary public API of a package」に付与されるものと説明されています。 このタグが規定されている動機についても説明していて、 1つには「ライブラリの作者が、 ユーザーに対して"破壊的な変更を含む可能性がある"ということを示す ため」 2つには「静的解析時に、 パッケージ外からの利用を警告できるようにする ため」 というポイントを取り上げています。 実際に、PhpStormでは違反的な利用に対して警告を出すようになっています。 著作者・権利に関わるもの 該当箇所に関する権利等に関する情報です @author, @copyright @author は、その作者や重大な改修を行った人を示すものと説明されています。 @copyright はそのコードに対する著作権と説明されています。 (特異な内容はないので、あとは割愛) 関連情報・参照情報に関わるもの 該当箇所の実装の理解を助ける情報です @link @link は関連する情報を示すものと説明されています。 ( a custom relation between the associated "Structural Element" and a website, which is identified by an absolute URI. と書かれていますが、ここは「Webサイト」というより「情報の掲載されているWebサイト」といったニュアンスで解釈するほうが意味する所に近いのかな?と思います) @linkタグを利用する場合には、絶対URIで示すように指示されています。 また、インラインでの利用も想定されています。 <?php /** * This method counts the occurrences of Foo. * * When no more Foo ( {@link http://example.com/my/bar} ) are given this * function will add one as there must always be one Foo. * * @return int Indicates the number of items. */ function count () { <...> } @see @see は関連するリソースを示すものと説明されています。 @linkはURIだったのに対し、こちらはURI以外のリソースを利用することも可能 です。例えば他のクラスやメソッドがあり、そのような要素を指す場合には「FQSEN」を利用するようにと規定されています 10 。 また、関連先のリソースとの関連性(=なぜ see なのか?)という説明を載せるべきだとされています(SHOULD)。 @link が対象リソースの説明について「してもよい」とされているのに対して( The @link tag MAY have a description )、こちらが「べき」なのは、「ドキュメント」以外のリソースも扱えるからでしょうか。 @uses @uses は利用される要素やファイル名を示すものと説明されています。 ただし、記述できるのは同じプロジェクト内のリソースに限るとされています。そのために、URI等の利用は禁止です。(システム外部のリソースに言及する場合は、 @see を利用することが出来ます) 例として、「コントローラーのメソッドに対して、描画されるView/templateファイル名を示す」といった内容が挙げられています。 これは、どういった場合に嬉しいのでしょうか? 例えば 動的な関数の呼び出しを行っている場合やマジックメソッド経由で利用しているメソッド について、@usesを用いてエディタや静的解析ツールに「実際に使っているよ!」ということを知らせることが出来ます。 余談ですが、 @used-by については、現時点ではPSR上での規定はありませんでした。 バージョン・更新に関わるもの 現行の状態や更新履歴といった情報です @version @version は、現在のバージョン情報とそのバージョンで提供された内容を示すものと説明されています。 セマンティックバージョンを利用して言及すること、説明文を加えることを推奨されています(RECOMMENDED)。 @since @since は提供が開始された(もしくは内容が現行の状態に変更された)バージョンを示すものと説明されています。 (@versionと同様に)セマンティックバージョンを利用して言及すること、説明文を加えることを推奨されています(RECOMMENDED)。 例えば「2.0.4から、第3引数が追加された」といった内容などは@sinceで示すのが良さそうです。 「提供のタイミング」「バージョニング」に関わるということで、@versionと混同することもあるかもしれませんが、 「今がどのバージョンであるか」といった情報には@sinceによって提供されるべきではない(@versionを利用する) と説明されています。 @deprecated @deprecated は廃止予定・将来のバージョンにて削除されるものを示すものと説明されています。 廃止予定とされた理由について、説明文を加えることも可能とされています(MAY)。また、代替となる手段が用意されている場合には @see タグによって示すことが推奨されています(RECOMMENDED) @todo @todo は実施される(開発・改修される)べきことを示すものと説明されています。 このタグには必ず説明を入れなければならない(MUST)とのことです。 (todoが「バージョンに関するもの」か?というと、「将来的な変更に関するもの」という意味では、この群に属するのではないでしょうか) その他の提案内容について ここに列挙したタグは既にproposalにコミットされているもので、ある程度の同意を得たものという扱いになります。 もちろん、「今はリストに含まれている」というだけであり、最終的には削除されるかもしれないし他のタグが追加される可能性もあります。(そもそもPSR19自体が採択されていないですからね) また、興味深い議論としては、PSR-5の領域ですが PR#1191にて「 no-return type」についての提案がなされています。 github.com これは、TypeScriptでいう never に相当するもので、exit()やtrigger_error()の発火により「呼び出し元に処理を戻さない」性質を説明するものです。PHPStanやPsalmでも、それぞれ @return never , @return no-return という表記によって扱われています。 PSR-19についても、いくつかのタグの追加に関するPRが作成されている状況です。 まとめ ざっくりと、現時点でPSR-19として規定されているPHPDocのタグについて見ていきました。 多くのタグは「定番化」しているもので、既存のコードに対して衝撃を与える事はないように思います。実際に、PSR自らがInterfaceの定義等において既に利用している状況が見受けられます。 PSR自体は決して「PHPを書く際に従わなければならない」ものではなく、「フレームワークやツールを作る時に準拠しておくことで、何か良いことがあると良いよね」程度のものです。そのため、実質的なデファクトスタンダード(今だとphpDocumentorになるでしょうか)が存在すれば、大きく困ることもありません。実際に、過去に議論が停滞したのも「困らなかったから」という側面はあるでしょう。 ですが、個人的には、先述の通り「PSR自体がPHPDocを利用している」こともあり、やはり明確な定義を持つべきなのではないかな?と考えています。 繰り返しになりますが、PSR-5,PSR-19はその内容がまだ確定しておらず、現在進行系で議論がなされている状況にあります。なので、ここにまとめた内容も採択時には変更されているかもしれません。 それでも、その内容や議論に興味を持つことは、今っぽいPHPを考える上で多少なり意味のあることではないかと考えます。 PSRはその性質的に「最大公約数」を示す力も持っていると捉えているからです。 皆さんもぜひ、PSR-5/PSR-19の内容を眺めながら「ドキュメント性能の高いソースコード」について想いを馳せてみるのはいかがでしょうか! ここまで読んでいただきありがとうございました。 明日は、Data Strategy Groupの齋藤とFrontend Group加藤が更新予定です!✨ 前身となる「PHP Standards Group」は「PHP本体への提案」を目指して結成されたグループです。しかし「標準化」に際しては考慮すべき点が多く困難性があったため、「より柔軟な提案をするために、言語仕様そのものではなく、その上に乗るプラクティスとして規約を求めよう」と方向転換をして今のPHP-FIGが生まれました。そのため、「広い範囲を扱う」というのは、サジ加減はあれど、FIGにおける本質的な振る舞いだと私は解釈しています。 ↩ https://wiki.php.net/rfc/splclassloader ↩ 歴史については、こちらの記事に詳しいです The Past, Present and Future of the PHP-FIG — SitePoint ↩ PSRのステータスや議論の進行については、 https://www.php-fig.org/bylaws/psr-workflow/ をご覧ください ↩ と言いつつ、「PSR-3を踏まえて簡素化した(利便性を高めた)PSR-16:Simple Cache」や「PSR-7をコアとした一連のHTTP関連Interface(15,17,18)」もあるので、「特定の領域について深める・広める」という事自体は他にも積極的に行われています。 ↩ https://github.com/php-fig/fig-standards/blob/2668020622d9d9eaf11d403bc1d26664dfc3ef8e/proposed/phpdoc.md#3-definitions ↩ GItHub上でステータスを変更されたソースはこちらです https://github.com/mcneely/fig-standards/pull/1 ↩ https://github.com/php-fig/fig-standards/pull/1078 ↩ https://groups.google.com/d/msg/php-fig/5Yd0XGd349Q/w-uTRA2nEgAJ ↩ FQSENについては、PSR-5を参照してください https://github.com/Chofoteddy/fig-standards/blob/e89924d320c269678fe3f5822c9bf9ef95db1af0/proposed/phpdoc.md#3-definitions ↩
はじめまして、Owners Marketingグループの小林です。 Owners Marketingグループは、ショップオーナーさん向けに新規機能などを常に開発しているエンジニアのチームで、最近ではネットショップ作成サービス「BASE」の拡張機能である「BASE Apps」のInstagram販売App、予約販売Appや顧客管理Appなどを開発/リリースしました。 アドベントカレンダー14日目のこちらの記事では、BASE社内の部活動のひとつ、ランニング部の活動について紹介したいと思います。 devblog.thebase.in BASEの部活動 BASE社内には多くの部活動があり、5名以上のメンバーが集まれば、部活動として認定され、会社から活動費の一部を補助する制度があります。 (ランニング部以外に社内にある部活動) ビール部/ヨーガ部/日本酒部/筋トレ部/ボードゲーム部/ボルダリング部/サウナ部/LLVM部など 社内にあるコミュニティは多種多様で気軽に参加できるため、仕事ではあまり関わりのない人とも気軽にコミュニケーションできる場として、積極的に活用されている印象です。 過去には、ボードゲーム部が、ランサーズさんと合同大会などを実施し、社外交流の場としても活用されています。 basebook.binc.jp ランニング部 ランニング部では日々の活動として、週一回、仕事後にオフィスの近く走る活動を開催しています。皇居一周をメインコースとして、その日の参加メンバーの走力や体調に合わせて、6〜13kmくらいの距離を走っています。 ランニング部に参加する前まで、日々の業務では座りっぱなしで運動不足を感じる事が多く、また「運動したい。走りたい。」と思ってもなかなか継続できず三日坊主になりがちでした。ランニング部は、雨で中止になる日もあるのですが、基本的には毎週活動しているため、継続的に運動できる環境になり運動不足が解消できると思い、参加していました。 皇居周回コース 有名な皇居ラン。 オフィスがある六本木グランドタワーから皇居に向かい、皇居の周りを1周してグランドタワーまで戻るコースです。 皇居周辺は多少のアップダウンがあり、1周5kmで信号で止まる事がなく走りやすいコースです。 桜の時期では、夜桜を楽しみながら走れます。 マラソン大会 私がランニング部に参加し始めて1年半ですが、週一回の活動以外に、休日に開催されるマラソン大会に何度か出場してきました。それまでマラソン大会に参加したことがなかったので、最初は10kmのマラソン大会に出場することにしました。 10kmという目標と他のメンバーに負けたくないという気持ちもあり、週一回の活動以外でも、仕事後や休日にも自主練習。 日々のランニングとは違いマラソン大会に出場することで、各メンバーの目標設定ができ、Slack上でもランニング部のチャンネルでは話が尽きません。 小さい規模のマラソン大会でも、ゴールするとメンバー全員に達成感があり、大会後の打ち上げでは一体感が生まれます。 ただ、大会が終わるとすぐに次の目標を設定したくなり、終了後すぐに次の大会に申込みをするを繰り返し、出場を重ねる毎に自然と距離を延ばしてきました。 以下、私が出場したマラソン大会 2018年10月21日 10km 2018年10月27日 16km 2019年01月20日 21km 2019年06月23日 30km 2019年09月28日 21km 2019年11月10日 42.195km 2019年11月17日 42.195km 2019年12月09日 42.195km 2019年11月10日に参加した横浜マラソンでは、高速道路の一部区間が一部規制となり、高速道路を走る事ができました。奥にはベイブリッジも見えてとても気持ちがよかったです。 まとめ 仕事を一生懸命するのは勿論ですが、職場のメンバーと他の事にも挑戦できる環境であることは素晴らしいと思います。 最初は運動不足解消やコミュニケーションの場として、ランニング部を活用していましたが、少しずつ小さな達成を積み重ね、いつのまにかフルマラソンを走れるくらいの走力がついていました。 何事にも当てはまると思いますが、 THERE IS NO FINISH LINE. (そこにゴールはありません)大会後の打ち上げを楽しみつつ、継続した挑戦をしていきたいと思います。 最後まで読んでいただきありがとうございました! 明日は、Frontendグループの加藤さんとData Strategyチームの齋藤さんです!お楽しみに。
この記事はBASE Advent Calendar 2019 13日目の記事です。 devblog.thebase.in こんにちは。UIデザイナーの野村です。 2019年4月にBASEに入社し、主にショップオーナーさんが使う管理画面のUIデザインに携わっています。 デザインワークの下敷きになるようなトライをいくつか行っておりまして、今回はそのあたりの話をさせていただきたいと思います。 [これまでに実施したトライ] 2度のデザイン思考ワークショップ 非デザイナー向けのデザインツール講習会 サービスデザインの勉強会で組織デザインについて学ぶ(社外) 開発プロジェクト内での実践(途中) なぜそんなトライをしてるのか、という話 受託デザインの世界からインハウスへ移行して 私はBASE入社前はデザイン制作会社に勤めたりフリーランスで働いたりしていたのですが、携わった業務はほぼ全て受託案件でした。 業務委託の形で事業会社内で勤めたことはあったものの、インハウスデザイナーとしての経験はとても浅い状態で入社して参りました。 受託での仕事と、事業会社内(インハウス)でのデザインワークには色々と違いがあります。 受託デザイナーをしていると、基本的には「お客さん(依頼者)が提示する情報をベースにモノを作る」という形をとります。 実際のところ、プロジェクト開始段階でお客さんから十分な情報を提示されることは稀でして、追加で色々と調査・検討を行うこととなります。 もらった情報を精査した上で、不足を補うためにお客さんにヒヤリングをしたり、内外への調査を提案し計画したり、技術や周辺事情についての勉強会や講習会を行ったり... などなど、良いアウトプットに到達するための施策を打っていくわけですが、とはいえ基本的には情報収拾と取りまとめはお客さん側で担当することが主で、受託デザイナーとしての自分は「 お客さんの情報収拾活動をサポートしつつ、制作に注力する 」というのが基本スタンスとなります。 そうなると、情報の深いところまではイマイチ理解が浅いまま納期優先で業務を進行することになったりするのですが、それは「 仕方ないこと 」と飲み込んできました。 インハウスデザイナーとして仕事をしていると、自ら社内で動いて事業やユーザの深い情報を掻き集めながらプロダクトを作っていくことができます。 マネージャーが取りまとめてくれた情報のみから業務を進めていくことも可能ではありますが、社内の各部署で持っている知見や体験を掻き集めて自ら消化・整理して業務に反映することで、アウトプットの質は目に見えて向上します。大変ではありますが、インハウスデザイナーとして働くにあたっての面白いポイントの1つです。 効率よく、かつ深く情報を共有するには 自ら組織内のナレッジ掻き集めようとした時、新参者は 誰がどんな知識をもっているかわからない という事態に陥りがちです。 それらはドキュメントとして蓄積されてたりしますが、全てが明文化されているわけではありません。 また、ドキュメントには文章や図の形で知識を溜めていくことはできますが、そこに各人の想いや体験(ひっくるめて ナラティブ 、と呼ぶことにします)まで込めるのは難しいです。文章書くの苦手な人も居ますし。 ナレッジは有形(文章・図)化して蓄積可能だけど、ナラティブは無形のまま流動し続けることしかできない、とでも申しましょうか。 そして、ナラティブのようなバックグラウンドを踏まえてないと、せっかくの知識もその真の意味、深い意味が抜け落ちた上っ面だけのものになってしまうことがあります。非常に勿体ない事態ですね。 そんな想いから、部署の垣根を超えて各人のナラティブをそれとなく共有できるような、そんな場を組織内に作れないものか、と考えました。 プライベートなことを掘り下げようというものでなく、仕事やプロダクトについてのナラティブを共有するのが主なので、 プロダクトの企画をテーマにしたワークショップ をするのが良いかな、という思いに至り、企画と実施をしました。他、非デザイナー向けにデザインツールの講習会をしたり、デザイン組織醸成に関する社外の勉強会に出たり、といったトライをしました。 2度のワークショップ 「デザイン思考を体験するワークショップ」という名目で、2時間程度のショートワークショップを社内で2度実施しました。2回とも、参加人数は10名前後の、小規模なものです。 第1回ワークショップ まずはやってみよう、ということで7月にパイロット版的な形で実施しました。 内容としては、私が過去に参加した1DAYワークショップを下敷きに、2時間程度の尺で収まるよう圧縮したものです。 いくらかバタついたものの、10名強の参加者には楽しんでいただけたようで、パイロット版としては成功を納めたと思っています。 その時の様子は こちら 。 第2回ワークショップ 第2回のワークショップは、流れとしては概ね第1回と同じなのですが、第1回での反省点や、いただいたフィードバックなどを踏まえ、かつ段取りのバタついた箇所を整理するなど、いくつかのアレンジをしました。 その時の様子は以下の通り↓ ワークショップ振り返り 2回のワークショップの後には、参加者からはそれなりにポジティブなフィードバックをいただけました。(「楽しかった」「普段話せない人と話せてよかった」「良い頭の運動になった」など) 90〜120分程度のワークショップをスムーズに進めるための段取りも作れたと思っています。 自身のファシリテーション力向上にもつながり、良いトライができました。 他、ナラティブ共有についてのトライ 「他部署メンバーのナラティブを得る」という主旨からは少々逸れるのですが、以下のようなトライを行いました。 非デザイナー向けの、デザインツール講習会 これは私が企画したわけではなく、デザインチームメンバーの小山が主導したものなのですが、デザイナー以外へのデザインツール講習を行いました。業務の効率化を意図しての施策ですが、デザイナーから他部署メンバーへの良いナレッジ共有・ナラティブ(デザイナーの作業姿勢とか)共有になったと考えています。 組織ナラティブに関して勉強会参加(社外) 社外で行われたサービスデザインに関する勉強会にて、組織におけるナラティブ構築に関して整理・発表したりもしました。(この勉強会の影響で、ナラティブという単語を多用するようになった私です。口には出してないけど。) 開発プロジェクトの中にナラティブ収集のフェーズを組み込んでみるトライ ここまででご紹介したのは開発プロジェクトの外側で実施してきた内容なのですが、現在は「 プロトタイプ検証のための社内ヒヤリング 」という名目で、プロジェクト初期段階で他部署から意見を収集するフェーズを作ることにトライしています。 単なる意見収集に留まらず、ナラティブ共有の一助になっている、と感じています。 この件についてはまだプロジェクト進行中につき詳しくは書けないのですが、いずれ整理してまた記事化したいと思います。 整理途中のものをチラ見せすると以下のような具合です。 トライのまとめと今後 業務の傍でいくつか施策をトライしてみました。 それぞれ多くの知見を得られましたが、一方で、拡大していく組織の中で個々のメンバーの想いや体験を収集することの難しさを感じています。デザインワークに本当に役立っているのかは、正直まだよくわかりません。 最後に挙げたデザインプロセスのトライについては、進行中の開発プロジェクトに取り込みつつ試してるため、デザインワークに対してそれなりの効果があることを実感できています。まずはこのプロセスを整理して社内で共有し、トライを重ねていきたい次第です。 ワークショップについても参加者からはポジティブな感想をいただいていますので、こちらも続けていきたい想いでいます。 ただ、これまでに実施したワークショップよりも、もう少し業務との繋がりをイメージしやすいような形のワークショップを行いたいと思っています。 具体的に何かモノ(プロトタイプ)を作って検証するような形をとれたら良いのではないかと思い、現在はプロトタイピングにフォーカスしたワークショップについて学習・研究しているところです。 なんやかんやと、 チームを超えて存在(潜在)している集合知をデザインワークに繋げることを意図したトライをしています 、というお話でした。最後までお読みいただきありがとうございます。 明日はPlatform Devの金城さんとService Devの小林さんです!
はじめに この記事はBASE Advent Calendar 2019 13日目の記事です! devblog.thebase.in こんにちは。BASE株式会社 Product Design Divison でデザインリードをしている北村 ( @naomi_kun )です。 ふだんはオーナーさま向けの管理画面やショッピングアプリの改善、新機能の開発までいろんな部分でUI設計を担当しています。直近では、既存機能のフルリニューアルプロジェクトを進めており、てんてこ舞いになりながらもとても楽しみながら開発しています。 実は、9月にデザインマーケットのテーマ作者さん向けの機能が少しアップデートされました。デザインマーケットでテーマを作成しようと思っているデザイナーさんは、ぜひ BASE Developers からデザイナー申請してみてください。 https://developers.thebase.in/ 今日は、BASEに入社して2年半を迎えたので、BASEの考える「Move Fast」をデザイナー視点で振り返りながら、いろいろ書いてみようと思います。 BASEのMove Fastという文化 入社当初からBASEには「Move Fast」という文化が浸透しており、今現在のプロダクトの開発にもその考え方が強く根付いています。自分が携わってきたプロジェクトも、まずリリースしてみよう、ファーストリリースを恐れるな、という考えが強く、チャレンジングな会社の雰囲気を感じとったのを覚えています。 前Qのはじめにも、あらためて「Move Fastを体現したプロダクト作りを正とする」ということが全社的に語られ、各プロジェクトの開発現場では、「DAY1にこだわるために何をすべきか」という議論が飛び交っています。 プロジェクトが煮詰まるあまり、当初の要件から膨れ上がったり、リリースが遅れがちになるといったプロジェクトを経験していたこともあるので、このスピード感と意思決定の方法はとても「強い」なと思いました。(強い、という表現が適切かはわからないんですが、強さを持っている会社だなと感じてます。) 日々感じていることですが、「BASE」のショップオーナーさんは本当に成長速度が早く、気づいたらとてもおおきなブランドになっている、ということも多いです。 そんなオーナーさんたちの成長速度についていくための方法として、「DAY1にこだわる」という意思決定が言語化されたことは、(私個人としては)、腑に落ちた気がします。 入社当初を振り返りながら影響されたこと 入社した当初、私はアプリの開発チームにアサインされましたが、振り返ってみると現在のBASEのMove Fastを体現するような現場だったなあと思います。 その頃のチームにはディレクターがいなかったため、開発メンバー全員がオーナーシップをもって取り組んでおり、仕様決めやUI設計を一緒に進められたこと、不具合時の対応もリアルタイムで議論していたことなど、いろんなところに首をつっこめる環境のおかげもあって、結果的にサービスへの理解がとても深まったと思っています。 また、現在のVP of Product・CTO・テックリードなど、現在のBASEの開発の根幹を担っているメンバーが集まっていたため、彼らの決定力とスピード感にも多くの影響を受けたように思います。 このスピード感を保つには、粗が出ることもあるし、完璧なデザインや実装にはならなかった部分もあるのかもしれないですが、そのときにくだした意思決定は圧倒的に正しくて(あとから状況が変わってそれが最適ではなくなることももちろんありますが)、現在のBASEはそれを積み重ねてきた結果なんだなと思っています。 あとは、「Be Hopeful」な文化もあり、気負わず自然にお互いを後押しできるような空気があったのも、Move Fastを進められる大きな要因だと思いました。 Move Fastは難しい 上に書いたようなスピード感のある開発は、入社当初の規模感だからこそ実現できていたもので、今はそれが以前に比べて難しくなっているのも事実です。あとは単純にプロジェクトで開発する影響範囲の粒度が大きく、機能改修や新機能を考えるときにはやはり慎重になってしまう場面が増えました。ショップの数も増え、社内の開発メンバーも増え、考慮すべき部分も増えた今では、いろいろなパターンを考慮するあまり、仕様が過剰になり、時間もかかってしまうのは、この規模だとある程度仕方ないと思っています。 それを削ぎ落とすための判断軸として、「DAY1にこだわる」というスローガンがあるのですが、個人によって考え方や持っている背景が違ったりするので、軸として機能させるのもなかなか難しいなあと。 BASEのデザイナーとして「Day1にこだわる」という意思決定をするためには、仕様も、ブランディングも、市場での立ち位置も含め、「BASE」への理解が深くないと行えない、という、当たり前のところに立ち返っています。 これは自省として書くのですが、いつかのMTGで、VP of Productの神宮司 が「デザイナーが不安になると、プロジェクトマネージャーも、開発メンバーもみんな不安になる」ということを話していました。 今でもその言葉が残っていて、デザイナーが慎重に・不安になってしまうのには色々な要因があると思うのですが、私自身がチャレンジングな環境のBASEに助けられてきた部分があるので、自分のいるプロジェクトでは、なるべく「すごい!」とか「めっちゃいい!」とか「はやくリリースしたい!」っていうアクションをするように意識するようになりました。 Be HopefulになれればMove Fastもついてくる、という空気を、デザイナーが持てると良いよなあと思っています。 Move Fastとは、未来のオーナーズに選ばれること 3Qのはじめに、デザインチームのミッションを皆で考える時間がありました。 「BASE」の価値をMove Fastにサービスに具現化し、未来のオーナーズに選ばれ続ける 「はじめに」でも書きましたが、「BASE」のショップオーナーさんの成長速度に追い越されないよう、「BASE」ももっと成長していく必要があり、Move Fastとは、未来のオーナーズに選ばれるための最速手段だと感じています。 おわりに 2年半を振り返りながらいろいろ書きましたが、この先の未来にも、もっと新しい構想や、やりたいことなどが山積みで、その全てが未来のオーナーズのみなさんに選ばれるための決断なんだという部分は揺るぎないので、ワクワクしています。 いま開発中の機能もはやくリリースしたい!DAY1!という気持ちのほうが大きいです。 おわり 明日は基盤グループの金城さんと 、Owners Marketingグループの誠さんです!
Product Management Groupの坂東( @7auto )です。 この記事は BASE Advent Calendar 2019 の12日目の記事になります。 devblog.thebase.in 私はアートが好きでしょっちゅうギャラリーや美術館を巡っています。 会社のお昼休みや、フレックスなのでたまに17時頃など早めに退勤していそいそとギャラリーを巡っているのですが、昼休みにぶらりと行ってみるととても良い気分転換になりますよ! みなさんは行きますか?ギャラリー。 最近ではアート思考の書籍が多数発売されたり美術館が60万人を超える動員をしていたりと、アートに関する情報を耳にすることも多くなっているように感じます。 アートが話題に上がりやすくなったことはとても嬉しいことではあるのですが、 美術館などに展示されるアーティストはトップオブトップな人たちです。 その裏には今まさに制作活動を行い切磋琢磨しているアーティストやアーティストを支えるギャラリーがあります。 大変ありがたいことに、Eコマースプラットフォーム「BASE」はたくさんのそうしたギャラリーやアーティストの方々にもご利用いただいています。 ただ、そんなギャラリーやアーティストはまだまだ一般によく知られる存在ではないのかなと感じています。 そこでよりよく知ってもらうため、本稿ではその魅力をご紹介したいと思います。 ギャラリーってなに? そもそもギャラリーとはなんでしょう。 ざっくりと言えばアーティストの作品などを展示してる場所になります! ※ギャラリーにもレンタルギャラリーとコマーシャルギャラリーがありますが、ここでは後者を指します。 作品の展示と言われて思い浮かぶのは美術館ではないでしょうか? 123億円の作品が展示されていた展示が六本木ヒルズで開催されて話題になっていましたね。 そんな美術館とギャラリーの大きな違いは収益構造です。 美術館は入場料を取るのに対し、ギャラリーは(基本)入場料を取りません。 ではどこで収益を得るのかと言うと作品の売買によって収益を得ています。 なのでそれぞれで目的が大きく違っています。 ・美術館:入場してもらう ・ギャラリー:作品を販売する 目的は作品の販売ですがギャラリーには作家を露出させて価値を育てるという側面もあると思っているので、個人的には昼休みにぶらりと見に行き価値を感じるだけでも良いのではないかと思っています。 そんな作家の価値を見出し育てていく関係性は、起業家と投資家の関係に少し似ているのかなと感じたりします。 最近ちょうど「 ほぼ日刊イトイ新聞 」さんが作家とギャラリーの関係性についての記事を書かれています。 見たことや聞いたことのある作家の名前も出ているかもしれません。 天才もしくは狂人と、その伴走者 ギャラリーやアートの世界が少し身近になったでしょうか? 何が楽しいの? 私は若手作家のいわゆる現代アートにカテゴライズされる作品を好んで見に行きます。 難解だと言われがちな現代アートですが、とにかく表現に対する多種多様なアプローチや切り口おもしろいです。 小林健太 さんや 岡田舜 さんら好きな作家を上げ始めるときりがないのですが、いろいろな目線やアプローチで作られた表現の価値とは何なのか、情報の伝達とは何なのかみたいなことを考える良いきっかけになっています。 難しく考えすぎず、何か刺激を得られるといいな、くらいの気持ちで足を運んでみてはいかがでしょうか? ギャラリーってどこにあるの? そんな楽しいギャラリーですが、どこにであるのでしょう? 個人的に数えたことがあるのですが、東京都内で500件以上、オフィスのある六本木にも50件弱くらいありました。 ビルの地下など目立たないところにあったりするので、Googleマップで「ギャラリー」などのワードで調べてみると意外と身近なところに見つかるかもしれません。 Google マップで「ギャラリー」で検索してみる ギャラリーの営業日は火曜~土曜で19時頃には閉まることが多い印象です。 作品っていくらぐらいなの? 売買が目的だとすれば気になるのは値段です。 もちろん、アーティストの知名度や作品のサイズなどによって値段も様々ですが、私がこれまで作品を見てきた感覚でいうとPCやスマホと同じくらいという印象です。 作品は流行り廃りのあるものではないのでワクワクが持続するように感じます。(個人の印象です。) そう考えると案外手に入れてみるのもいいかも、と思えるかもしれません! また今はアート売買やレンタル、分散保有などのサービスも生まれてきていて、作品の所有に対する障壁は低くなってるように感じます。 私も AND ART というサービスを通して↓のKAWSの作品1点の0.23%を所有してみました。 KAWSを(0.23%だけ)所有していると言えることは、とてもプライスレスな体験です。 購入方法や所有方法も多種多様になってきているので、自分にあったスタイルで一度購入してみてはいかがでしょうか? 何か新しい発見があるかもしれません! 最後に ギャラリーや作家に少し興味を持っていただけましたでしょうか? BASEのミッション には下記のような記載があります。 ひとりひとりに眠る、想いが、感性が、才能が。 世界中の、必要な人に届くように。 そこから生まれる、作品に、アイデアに、活動に。 正当な対価を、受け取れるように。 今回ご紹介したような切磋琢磨して作品を世に送り出している作家やギャラリーの方々をサポートしていけるよう、「BASE」というサービスを磨き続けていきます。 明日はProduct Design Divison でデザインリードの北村さんとUIデザイナーの野村さんです!
この記事はBASE Advent Calendar 2019の12日目の記事です。 devblog.thebase.in こんにちは、BASE BANKインターンの河越( @heart_breakers2 )です。 この記事では、去年と同様に「鶴岡さん観察日記」と題して、BASE株式会社 代表取締役CEOおよびBASE BANK株式会社 代表取締役CEOである鶴岡の普段の様子をカジュアルに伝えていきたいと思います。 (みなさんにもカジュアルな雰囲気で読んでいただきたいので、記事内ではあえて「鶴岡さん」と表記しています。) 私がインターンとしてBASE BANKに入社してから、1年半が経ちました。 私の席は変わらず、鶴岡さんのななめ前にあります。 いつも近くにいるからこそ発見できた、鶴岡さんの素顔や、みなさんに知ってもらいたいBASEの好きなところをまとめてみました。 この記事を読んでいただき鶴岡さんやBASEに興味を持っていただけると嬉しいです。 ※ 去年の観察日記をまだ読んでない!もう一度読みたい!というかたはこちらからどうぞ! devblog.thebase.in はじめに 「鶴岡さん」 この記事を読んでいるみなさんなら、その名前を一度は聞いたことがあるのではないでしょうか? 鶴岡さんは、80万以上ものショップが利用する、ネットショップ作成サービス「BASE」を運営するBASE株式会社の代表です。 鶴岡さんと一度でも話したことがある方だと必ず、温和でコミュニケーション能力が高くて、優しい鶴岡さんに惹かれた経験があると思います。 また、先日BASE株式会社が東京証券取引所マザーズに新規上場をしたことから、代表である鶴岡さんがどんな人なのか気になっている方も多いかもしれません。 この記事では、鶴岡さんのことが大好きで、鶴岡さんみたいになりたい〜!と思っている私が書く「鶴岡さん観察日記」を5日分、みなさんにシェアしたいと思います。 みなさんにも鶴岡さんと、BASEの魅力が伝わりますように! 3/12 (火):鶴岡さんはなんでも知ってる 鶴岡さんは世の中で起こっていることのほとんどを知っているんじゃないかな?と思うことがあります。 顔を合わせるたび聞かれるこの質問ですが、最初は「え、特にないんだけどな、、」と思っていました。 鶴岡さんはとっても忙しいはずなのに、『テラスハウス』も毎週必ず火曜0時から見るし、『グランメゾン東京』も、『オオカミちゃんには騙されない』も、YouTubeも、女子高生に人気なインフルエンサーのInstagramも、IT界隈のTwitterも、スポーツマンガも、「BASE」を利用してくださっているショップさんの情報も、全てにおいてとっても詳しいんです。 BASEの哲学は Stay Geek ですが、鶴岡さんはインターネット上で起こっていること全てに興味が止まらない、「インターネットGeek」な人だな、と思います。 ちなみに今年の春、鶴岡さんはkemioさんにハマっていて、いつも「アゲアゲ修学旅行がやばいんですよ!」とえふしんさん(BASE株式会社 取締役EVP of Development)に教えていました。 今もkemioさん著書『ウチら棺桶まで永遠のランウェイ』は鶴岡さんのデスクに大切そうに飾ってあります。 今は、鶴岡さんが知らないことを見つけると、すごく嬉しくなります。 5/1 (水):鶴岡さんの笑い方 鶴岡さんがミーティングからデスクに帰ってきた時や、ごはんの場にいる時は、周りの雰囲気がパッと明るくなるのを感じます。鶴岡さんはよく笑う人で、鶴岡さんが見えない位置にいても鶴岡さんがいるのがわかるのです。 ちなみに鶴岡さんの笑い方は結構独特で、いつも「カカカカー」と笑います。 最近は、鶴岡さんのことが大好きな神宮司さんも、ちょっと鶴岡さんの笑い方を真似している気がします。 8/8(木):「食べて〜」 鶴岡さんはよく、「BASE」で販売されている、みんなでシェアできるスイーツやフルーツを差し入れしてくれます。 【秀品】ニューピオーネ 2kgを購入しました! https://t.co/ntUQpdOhyu #BASEec @BASEec より — 鶴岡 裕太 (@0Q7) 2019年8月28日 (こういうツイートがあるともうすぐスイーツ・フルーツシェアのチャンスです) ちなみにこの日はGAZTAさんのバスクチーズケーキ。 信じられないくらい美味しくて、鶴岡さんも終始にこにこ笑顔でした! また美味しいスイーツを買ってきてくれますように。 9/23 (月):鶴岡さんは聞き上手 鶴岡さんのすごいところは、なんと言っても傾聴力ではないでしょうか? 鶴岡さんは今まで会った人の中で一番聞き上手で、話していて(こちらは)とっても楽しいのです...! この聞き上手っぷりは今までも多くの人を魅了してきたはず。 誰と話していても気づいたら聞き役に回っているし、鶴岡さんのInstagramをみる限り、友達も多そうです。 鶴岡さんは、「人に興味があるし、そこから何か学べるかもしれないから、自分のために人の話を聞いてるんだよ」とビジネスライクなことを言っていました。大人ですね。 が、そんな鶴岡さんも話が長くて飽きてくるとこんな感じになります。 すご〜〜い。 オンとオフの切り替えがわかりやすいのも魅力ですね!😂 ずっと興味を持ってもらえるように話し上手になりたいなと思います! 11/8 (金):さくさくわいわい BASEのSlackには #sakusakuwaiwai というチャンネルがあります。 #sakusakuwaiwai は、仕事終わりにメンバーとサクッとご飯に行きたくなった時に声をかけるチャンネルです。 この日は、鶴岡さんがチャンネルに声をかけていました。 多忙そうだし、社員とプライベートで過ごす時間はないのでは? 社長とごはんだなんて、社員が萎縮してしまうのでは? と思うかたもいるかもしれませんが、鶴岡さんは違います。どんなに忙しくても、2週に1度くらいのペースで社員とのごはんの時間を大切にしてくれています。 やっぱりごはんに鶴岡さんが来てくださるのは嬉しいし、聞き上手な鶴岡さんが必ず、和やかでフランクな雰囲気を作ってくれます。そんな場にいるからこそ、社員同士も楽しく、"Speak Openly"に話しやすくなっているとも感じます。 鶴岡さんに「人数が増えても忙しくなっても、今もみんなとご飯に行くってすごいですね」って言うと、「僕が寂しくて、みんなとご飯食べたいだけだよ」って言ってました。 本当に素敵な人だなって思います。 おわりに 鶴岡さん観察日記第二弾、いかがだったでしょうか? 日記を書いていて、鶴岡さんみたいになるにはまだ遠いなと感じる日々です。 この記事を通して、鶴岡さんの優しい人柄が伝われば嬉しいです。 (一番気に入ってる鶴岡さんとのツーショット) 今日は坂東さんの記事「 昼休みに行くギャラリーは楽しい 」も公開されています!ぜひ読んでみてください〜!
この記事は BASE Advent Calendar 2019 の11日目の記事です。 devblog.thebase.in はじめまして!こんにちは! BASEのCommerve Dev Group所属の白数です! サーバサイドをメインで開発しています。 私は現在、新卒2年目で18卒と呼ばれる代となります。 BASEには今年の9月に入社しました。今月で4ヶ月目です。 入社してからは、Eコマースプラットフォーム「BASE」の社内向け管理画面の新規機能を開発しています。 そんな若手エンジニアの私がどのようにしてBASEでの働き方をキャッチアップしているのかについてご紹介しようと思います! 特に私と同世代や学生の方にBASEで若手がどのようにして働いているのかを少しでも伝わると幸いです。 BASEで働く上で意識していること・実践していること 1. タスクと進捗の共有 BASE では入社時に、日々の業務でのちょっとしたお困りごとについて質問したり、オフィス生活に関する質問対応をしてもらえるメンターが付く期間が1ヶ月間用意されています。 この時に社内では timesチャンネル と呼ばれる新入社員とメンターがメインでやり取りを行うチャンネルを作るのですが、メンターの他にグループのメンバー、他グループのメンバーが参加しています。 メンター期間はこのチャンネルで日報や困っている事を書くのですが、私の場合、メンター期間後もこのチャンネルを利用し続けています。 やっていることは出勤時には、 今日やること 退勤時には 今日やったこと いわゆるタスクと進捗を共有することです。 自分のタスクの内容をグループのメンバーに共有することで、 もし作業の方針や順序が間違っていたりしたらその場で指摘をもらうことができています。 同時に自分が作業するTODOリストとしても機能しており、タスクが明確化していて「次は何をやるんだっけ...?」といった状況にならず効率が良いです。 毎週、上長と 1on1 の面談をする時間が設定されているのですが、 毎日タスクを共有していることで上長は自分が今どんな事をしているのかを把握してくれていて、1on1が設定されている理由の本質である 思っていること 困っていること などの話に100%の時間を使うことができています。 また、作業内容のログとして後日確認する事もできるので週末に振り返りを行う際、より詳細な観点で良かった事、悪かった事について考えることができていると感じます。 2. 技術的な問題で詰まったときは、とりあえず共有 私は大学生の時に長期インターンで働いていた頃はRuby、そして新卒で入社した会社ではJavaを書いていました。 現在のBASEでは PHP で開発しています。 PHPは大学時代の授業で触ったことがある程度で実務で利用するのは初めてです。 書いている言語が頻繁に変わっていることもあり、 入社2ヶ月ぐらいはBASEのサーバサイドにおける仕様面やCakePHPの細かい使い方が理解できていないこと、 PHPUnitを書いた経験がない事などが理由で頻繁に原因不明のエラーに詰まっていました。 30分ほど悩んでも一向に進まない時は、自分のtimesチャンネルに投げるようにしていました。 BASEにはPHPに詳しいエンジニアがたくさんいます。 解決策や助け舟を出してもらい、開発をしていく上で必要となる最低限の知識を爆速でキャッチアップして実装に入ることができました。 3. タスクで困ったときや、方針を決めるときは直接話す タスクを進めていて進め方などに困ることがあります。 その時は当日、遅くて翌日中には上長へ直接相談するように心がけています。 Commerce Dev Groupでは、毎日12:00からスタンディング形式で タスク内容や困っている事を共有する場として昼会を実施しています。 いろんな人の見解を聞きたいときはこの時に共有して相談するようにしています。 Slack等で話すことも可能ですが、 直接相談した方が早いのと、より細かいところまで認識を擦り合わせることができます。 4. 糖分を摂取 オフィス内にBASEの3つの行動指針のうちの1つである「Be Hopeful」が描かれた飴が置いてあります。 プログラムを書いていて糖分が欲しいな〜という時はこの飴を摂取しています。 デザインが可愛いくて美味しいです。 おわりに BASEの若手エンジニアの私が働く上で意識していることを、あくまで一例ではありますが紹介してみました。 一番は大切なのは、 常に自分の現状を共有することで多方面からアドバイスを貰いやすくなっている印象です。 BASEは若手が成長できる場があると思います。 実際私もまだ入社4ヶ月ですが、エンジニアとして大きくレベルアップしている実感を持っており、 これからもプロダクトと共に成長してBASEを盛り上げていきます。 明日はBASE BANKの河越さんとProduct Managementの坂東さんです!お楽しみにー!
この記事はBASE Advent Calendar 2019の11日目の記事です。 devblog.thebase.in こんにちは!BASEのProduct Management Groupの船坂です。 2019年9月にBASEに入社し、ようやく4ヶ月目に入ったところです。プロジェクト単位でアサインされ、ディレクションを中心に業務を行っています。 今回は、BASEに入社してからEコマースプラットフォーム「BASE」というサービスをいろいろな角度から理解するためにしてきたことをご紹介します。 カスタマーサポート研修 最初は、自主的なものではなく研修ですが、、、 BASEには入社直後にカスタマーサポート業務を体験する研修があります。 この研修には、 まずはショップオーナーとしてネットショップ作成サービス「BASE」を実際に利用してみる。 次に、よくお問い合わせいただく質問内容に対し、模擬的に回答を作成する。(その後CSのメンバーに回答を添削してもらいます。) という2つのステップがあります。 これによって、「BASE」というサービスの利用シーンを深く理解すると同時に、現時点で初めてサービスを使う方がどの部分でつまづく可能性があるのかを短期間で把握することが出来ます。 また、1. の過程で気になったことを社内ドキュメントにまとめて記載するというステップが含まれています。利用に慣れてない視点だからこそ気づくことができる新鮮なフィードバックを社内にすることができて、とても良い仕組みだと感じています。 実地見学 僕自身、IT業界に携わっていると基本的なことは画面の中で出来てしまうのでおろそかにしてしまいがちなのですが、何かを理解するときはその場所に行き、自分の目、耳、触覚など、五感で経験することが一番大事だと思っています。現地に訪問し、オーナーさんやそのショップ、買ってくださるお客様などの様子を実際に感じることで、自分たちが作るサービスの画面の向こうにいる人たちの存在を確かめます。サービス理解という意味だけでなく、普段のモチベーションに直結します。結果、アウトプットの質も段違いに高くなると思っているので、とてもおすすめです。 BASEの場合、オフラインに展開されているものとして、渋谷マルイにある常設店舗「 SHIBUYA BASE 」を始めとする実店舗や、先日行われたオーナーさん向けイベント「 BASE OWNERS DAY 2019 」などのオフラインイベントがあります。入社直後から、これらの場所には実際に足を運んでいました。 経営陣や執行役員の考えを知る 経営陣や執行役員陣がこれまでどういう考えで事業やサービスづくりと組織づくりに携わっているかをなるべく深く理解するために、いろいろな情報をチェックしました。社内に向けて発信されているメッセージや外部で話しているインタビュー記事、普段の何気ない言動やSlackへの投稿などです。どういった人たちがどういうモチベーションでプロダクトを作っているのかを理解することは、どういったサービスなのかを理解することに直結すると思っています。 ちなみに、自分の役割はそこにスムーズに乗っかりつつ、僕にしか出来ないことを全力でやって更にプロダクト成長を加速させることなのかなと思っています。 社内プロジェクト調査 BASEはプロジェクト単位でチームが組まれ、サービスの開発が進んでいきます。当然、社内全体ではどういうプロジェクトを走らせるかが入念に検討されているのですが、一個人としては、自分のプロジェクトに入ってしまうと、意外と実際の業務の中では全体が見えにくくなってしまいます。 そこで、入社したタイミングで、社内ではどういうプロジェクトが走っていて、それはだれが、なぜ今やっているのかをヒアリングしながら考えていました。この作業で、大きな流れの中で今サービスがどういうフェーズなのか、理解できた気がします。 知り合いへのヒアリング ありがたいことにEコマースプラットフォーム「BASE」はたくさんの方にご利用いただいており、自分の知り合いでもネットショップを運営してくださっている方や、ショッピングアプリ「BASE」で商品を購入したことがある方が思った以上にいらっしゃいます。 生の声を聞ける方々は僕にとってとても貴重な存在です。BASEに入社してからは、「BASE」を利用したことがある知り合いと何度か飲みに行き、なぜ使っているのか、どんなところが好きか、どこが使いにくいのか、いろいろな話を聞かせてもらいました。 こういうことを繰り返していくと、なんとなく「サービスを形成しているコミュニティ」全体の雰囲気が見えてきます。 これがわかってくると、どのような施策が効果的なのかを感覚的につかめてくるのではないかと思っています。 データ分析 とはいえもちろん感覚だけでは話は進まず、詳細かつ確実な現状把握にいちばん効くのはデータだと思っています。Google Analyticsから自社で独自に蓄えているデータまで、どんどん調べてみています。BASEでは僕の所属するProduct Management Group含め、必要な従業員はRedash等で業務に必要なデータの分析を自由に行うことができます。全体像は上記のような方法で調べて、細部はデータで確証を得ながら理解を進めている形です。 まとめ 今回この内容にしたのは、経験上、理解が進めば進むほどそのサービスは"じぶんごと"になっていき、それがサービスを作るのに一番大事なことだと考えているからです。 極論、どんなスキルよりも当事者意識こそが、サービスに本当に必要な価値提案や、小さくても確信をついた修正案を産み出すのだと僕は信じています。 最後まで読んでいただきありがとうございました! 明日は、BASE BANKの河越さんとProduct Managementの坂東さんです!楽しみ!
この記事は BASE Advent Calendar 2019 の10日目の記事です。 devblog.thebase.in お久しぶりです。 BASEビール部部長(兼Data Strategyチーム)の氏原です。 1年ちょっと前に Yahoo!の近傍探索ツールNGTを使って類似商品APIをつくる という記事を書きました。あれからだいぶ経ちましたがその間に類似商品APIはコツコツと改善を続けています。例えばファッション系以外で精度が良くないという話を前の記事にも書きましたが、画像以外に商品のタイトルや説明文の特徴量も使うようにするなどしてそれも改善されています。 そうした取り組みのなかで近傍探索に使っていた NGT を別のものに切り替えましたので、それを紹介したいと思います。 類似商品APIについて 類似商品APIはBASEのアプリで各商品の詳細ページを開いたときに表示される「関連する商品」という部分などで利用されています。 その時開いた商品と似た商品を提示することで良い商品探しに役立つようにとAPIを開発しています。 NGTについて NGT はYahoo!Japanさんが開発している近傍探索用のツールです。 NGTは任意の密ベクトルに対して事前に登録した(同次元の)ベクトルから最も距離が近いベクトルの上位数件(k件)を高速に近似k最近傍探索(k-Nearest Neighbor Search)するためのソフトウエアです。 高次元ベクトルデータ検索技術「NGT」の性能と使い方の紹介 以前はこれで画像の特徴量vectorを保存して、商品ページが開かれた際に似た画像をもつ商品を関連商品として提示していました。 NGTはとても性能がよく、1024次元の特徴量vector350万個を登録しても数十msecでレスポンスを返してくれてオンデマンドで関連商品を出すのにとても重宝してました。 問題点 ただ運用を続けていると色々と問題もでてきました。特に辛いのはindexの構築にかかる時間とindexのサイズです。 NGTは登録したvectorを全てそのまま持っています。そのため1024次元の特徴量vector350万個を登録した時点でindexのサイズは20G程になります。これがメモリに全部載るので、AWSでサーバを立てると結構なサイズのメモリを持ったインスタンスが必要になります。 また、NGTは必要なvectorを全てinsertした後にbuild_indexでindexingをする必要があります。登録したvectorをdeleteするメソッドがなく、差分更新が(おそらく)できないため、daily batchで毎度indexを作り直していました。(※私の理解不足の可能性もあります。間違ってたらごめんなさい。) faiss 上記の問題を解決するために、現在では別のツールで近傍探索を行っています。 faiss です。 faissはNGTと同じくvectorの近傍探索のためのツールでFacebookが開発しています。 NGTと比べてなにが嬉しいかと言いますと、最終的に出来上がるindexのサイズです。 faissは高速化やindexの圧縮のための仕組みが用意されていて、いろいろ試した結果indexのサイズがNGTでやっていた時の1/200くらいになりました。衝撃です。 もちろん圧縮するがゆえにある程度の精度低下は覚悟しなくてはいけないんですが、目立つほどでもありませんでした。 使い方はこんな感じです。 index作成 import faiss import numpy as np import random dim = ... # 特徴量の次元数 nlist = ... # 特徴量が何個の空間に分割されるか m = ... # 近傍探索するときに対象の空間以外に隣の空間を何個まで利用するか nbits = ... # 特徴量をここで指定したbit数で表現するように圧縮する quantizer = faiss.IndexFlatL2(dim) index = faiss.IndexIVFPQ(quantizer, dim, nlist, m, nbits) vecs = [(item_id, vec), ...] # 商品IDと特徴量のセットのリスト # 圧縮のために特徴量の分布を学習する必要がある # 実際の運用ではここは事前に学習しておいて学習済みのindexをloadする assert not index.is_trained train_data = [v for _, v in vecs if random.random() < 0.01 ] index.train(train_data) assert index.is_trained # indexへの追加はまとめてではなくこんな感じで細切れでもよいので # vecsが大きすぎて全部をメモリに乗せられない場合は # generatorで必要な分だけ読み込みながらやったりしても大丈夫 batch_size = 10000 for i in range ( 0 , len (vecs), batch_size): input_vecs = [] input_ids = [] for item_id, vec in vecs[i:i+batch_size]: input_vecs.append(vec) input_ids.append(item_id) input_vecs = np.array(input_vecs, dtype=np.float32) input_ids = np.array(input_ids, dtype=np.int64) index.add_with_ids(input_vecs, input_ids) # 保存 faiss.write_index(index, "features.index" ) indexの利用 import faiss index = faiss.read_index( "features.index" ) vec = ... # 近傍探索のターゲット vecs = np.array([vec], dtype=np.float32) D, I = index.search(vecs, 1000 ) # 近傍1000個とってくる D = D[ 0 ] I = I[ 0 ] for item_id, distance in zip (I, D): # add_with_idsでindex作ってるとここでIDが返ってくる ... wiki が充実してるので使うにあたってあまり困ることはありませんでした。 差分更新 faissで作成したindexは、(サポートしているindexなら)登録されたvectorの部分削除が可能です。 import faiss index = faiss.read_index( "features.index" ) target_id = ... # 消したいitem_id index.remove_ids(np.array([target_id]) つまりdaily batchで差分更新ができるため、index構築時間を激減させることができます。これならdailyと言わずもっと更新頻度を上げられます。 ちなみにindexのmergeとかもできるそうなので、vectorが大量にある場合はN個に分けて、N個のjobでindexを作成して後でmergeするみたいな使い方もできるようです。 まとめ faissはFacebookが使ってるだけあって1兆個のvectorを登録して動かすなんてスケールまで想定しているようです。もちろんそのためには 色々工夫 が必要なようですが。 faissを導入したことでindexの構築やAPIの運用がかなり楽になり、インフラやアルゴリズムの改善に力をいれることができるようになりました。リコメンドのシステムを作る際などで近傍探索用のツールが必要であればfaissを検討してみてはいかがでしょうか。 明日はプロダクトマネジメントグループの船坂さんとエンジニアの白数さんです。お楽しみに。