キャディ株式会社のブログ - TECH PLAY

TECH PLAY

キャディ株式会社

キャディ株式会社 の技術ブログ

239

キャディでエンジニア採用を担当しています片渕です。 今回は2022年3月25日に開催したイベント、『 CADDi Be Agile! 〜開発もビジネスもスクラムで回してみた 』の内容をレポート形式で紹介しています。 メインの アジェンダ としては大きく以下3点です。 Tech組織の スクラム について Biz組織の スクラム について パネルディスカッション Tech組織のみならずBiz組織での スクラム 導入の背景や現状、課題と感じている点などについて、Techからは高藤・Bizからは佐野がプレゼンしています。 [ toc ] Tech組織の スクラム 『LeSS』に関して 高藤: LeSSという仕組みを一部のチームで導入しているので、その話を今回はしていきます。 なぜ スクラム という開発手法を用いているのか LeSSをやる時にも考えたことになるのですが、私は「Tech組織は価値を届ける開発組織でありたいな」と思っています。 これはどういうことかというと、エンジニアは技術的に難易度が高いことにも挑戦したいのですが、結局は作ったシステムを使う ステークホルダー の方々が、価値を感じてもらわないといけないのです。 技術的にとても難しいものを作ったとしてもユーザ側に価値が無い、となるとエンジニアとしては何をしていたんだ?ということになってしまいます。 よって、しっかり価値を届ける組織を目指した時に、ビジネスが急成長してきた会社なので解決したい課題も少しずつ変化してきます。 そうした不確実な部分が多くあるものの、常に軌道修正をしながら開発できる組織で、しっかり価値提供できる開発チームでありたいと考えています。 また、上からの指示があったからやりましたというのではなく、ユーザとコミュニケーションして課題を設定し、課題を解いていく組織でありたいですね。 その実現のために必要なことが、 スクラム とマッチしているので、現在も スクラム を継続しています。 スクラム 導入の歴史や背景について 私がキャディに入社して3年ちょっと経過するのですが、入社時はエンジニアは数名ほどで、現在は50名ほどの組織になっています。 最初の頃は、なんちゃって スクラム のような状態で、生産管理プロダクトという我々の受発注の仕組みの中での基幹システムの部分は、数名規模の開発から始まりました。 この頃は アジャイル 開発で、 スクラム という言葉は知っているものの、しっかり学んだ人は私自身も含めていませんでした。 もちろん スクラム マスターという役割の人もいなかったですし、 スクラム イベントがあるようだということを、なんとなくで進めていたのが正直なところです。 当時は、JIRAという仕組みを使ってチケットを切ることをやっていたのですが、何だか合わないなという意見もあり一旦辞めて、 スプレッドシート にやることを書いていったことがあり、結果的に荒れたスプレットシートができあがってしまいました。 本格的に スクラム を知ったきっかけ その後、 スクラム 開発に詳しいエンジニア リングマ ネージャーが入社して、また様々なプロダクトも作られるようになってきました。 エンジニア リングマ ネージャーからは、「Howをどうするか?」を教えられたというよりは、 スクラム 基本原則の理解が重要という話もあり、 スクラム ガイドを輪読して読書会なども開催していました。 結果、「 スクラム では何を大事にしているか?」という原理・原則を理解して、その上で自分たちは何をしなければいけないかを考えるようになりました。 この頃から、JIRAを改めて使うことになり、やや抵抗を感じながらも スプレッドシート からJIRAに移行もしました。 現在ではキャディの中では基本的にはJIRAを使っていますし、Biz側でも使われるようになっています。 そのような歴史があり、チーム全体通して スクラム でやる理由を理解した状態になれたのではないかと思っています。 LeSSの導入の背景 そして最近の話になってきますが、この1年でエンジニアが2倍の数にもなってきて組織が大きくなったので、LeSS(Large Scale Scrum)を導入することになりました。 LeSSを導入した背景としては、 スクラム で推奨される人数は10名以下とガイドには書かれているのですが、我々のチームもそのようなサイズ感になってきた時に、コミュニケーションにやりづらさを感じていました。 例えば、デイリー スクラム や振り返りの場などで時間を定めてコミュニケーションをするのですが、人数が多いと全員がすっきりするまで発言できず、限られたトピックのみしかコメントができなくなったのです。 自分の意見を言い切れなくて消化不良となるケースも多くなってきたり、プロダクトの面でも自分が担当しているプロダクトに近いもの、生産管理でいうと受発注がメインでデータを扱っているシステムと我々が発注するデータをパートナー様に提供するポータルのプロダクトチームとの連携の時に、お互いにどんなことを今後やっていくのかを、クオーターやスプリント単位での可視性・透明性が薄れてきているなと感じました。 そうした点を考慮すると、もう少し大きな人数で スクラム を回す方法がないかなと考えた時に、LeSSという仕組みを導入しました。 1つのプロダクトを開発する人数も増えてきて、プロダクトの価値を考えると一緒に動ける方が良いかなと思っていました。 そのため、2021年の11月からLeSSを導入し、1ヶ月間を準備期間としてそれぞれの開発チームが合流するためのことを考えていました。 LeSS以外にも手法はあるのですが、我々がやっていることをそのままできそうなのがLeSSだと思ったので、こちらを選んだということですね。 プ ラニ ング・リファイメント・レトロスペクティブもLeSSにあり、理解しやすかったです。 LeSS導入当初にやったこと 準備期間にやったことは、ミロというオンラインのホワイトボードでやったのですが、スプリントプ ラニ ングではチームごとにやる前に全体でプ ラニ ングして、その後に各チームごとにプ ラニ ングをやりましょうという話です。 スクラム の代表者、LeSSに参加してくれる各チームのリーダーに集まってもらって、どういうことをやらないといけないのかを書き出していきます。このイベントはどのくらいの時間をかけるのか、などを最初に議論するわけです。 開発者だけではなく、プロダクトオーナーとなるPdMにも理解して欲しかったので、オーナーがやるべきことを議論してすり合わせをして準備を進めて行きました。 実際に去年の11月からスタートして2ヵ月経過して問題も見えてきたので、「クオーターごとにLeSSを導入してどうだったか?」を振り返り、次のクオーターで新しい取り組みをやるということを継続しています。 今後に向けて LeSSを選択して苦労もありましたし、私自身はもちろんですが参加したエンジニアも同じだったと思います。 スクラム やLeSSは、自分たちが経験したことを改善するのにはとてもいい方法で、各組織が経験したことを積み重ねていくことができます。 今後は、LeSSならではの悩みもありまして、チーム同士のコミュニケーションはどこまで担保すべきなのか、などは引き続き考えて行きたいと思っています。 Biz組織の スクラム に関して 佐野: 私の方からは、2022年3月24日にアトラシアンのイベントにてプレゼンさせていただきました資料をもとに、Biz組織での スクラム 導入の話をしていきます。 一般的にはソフトウェア系の会社に スクラム を導入すると思うのですが、どのような経緯でBiz側に導入していったのか、説明してきます。 Biz部門への スクラム 導入経緯 2021年の10月から スクラム を導入しはじめたのですが、ちょうど事業や組織が一気に拡大するフェーズで、プロジェクトマネジメントが急務という状態になりました。 QCD担保はもちろん、どんどん事業や組織が大きくなるものの、まだアーリーステージの会社なので、スケーラビリティを担保するには一定の型も必要になると考えていました。 そのため、何かしらの手法を入れようと検討したところ、 スクラム の話があがり導入することにしました。Tech組織で既に導入していたので、Biz組織でも試験的に3プロジェクトからやってみましょうということでスタートしました。 導入直後のカオスからの改善 導入してみたところ、導入直後はカオスな状態が発生し、メンバーから スクラム やJIRAなどへの不満が続出しました。 バーンダウンチャートも見事にバーンアップしてしまうことにもなり、全然 スクラム として機能していませんでした。 そこでスピードを意識して PDCA 回し、 スクラム マスターを配置して、JIRA警察と称して DoD やストーリーポイントが入っていないことなど、細かくチェックをしていきました。 また、KPIの設定やモニタリングを行い、スプリント達成率などを計算して集計し、100%を目指していくといった取り組みを進めていきました。 このように毎週改善に取り組んでいった結果、少しずつコツが見えてきました。 このプロジェクトだったらバーンアップチャートが良いよね、JIRAオートメーション機能を使うといいよねというコメントが出て、やり切る意識が高まってきました。 導入してから2ヶ月後くらいで、きれいなバーンダウンチャートを示せるようになったのは、劇的なbefore/afterになったなと感じています。 このように結果がついてくると、 スクラム やJIRAを嫌がっていたメンバーから、「とてもいいツールですね」「JIRAと スクラム の親和性がいいね」という話が出てきました。 導入した成果やメリット キャディでは ムーンショット と呼ばれる非連続の成長を実現したり、事業課題の 見える化 も進み、スムーズなリプ ラニ ングも可能になってきました。 また、各チームの フレームワーク が共通 言語化 したことから、議論の土台がそろってきたことは成果の1つと思います。 以上が、2021年の10月から12月までに起きたことです。 Bizやコーポレートも含めた全部門への スクラム 導入 この3ヵ月の成果を踏まえて一気に改善も進んだので、Biz/CPの全部門に入れて組織のOSにしたく、 トップダウン で スクラム をインストールすることにしました。 そのために年始の全社ミーティングで2時間くらい勉強会をやりました。 キャディでは前職がコンサル・ ベンチャー ・商社・メーカーなど様々で、 アジャイル なプロジェクトマネジメントへの慣れ・不慣れの程度さは大きいのですが、改善を高速で回していくのは良いことだよね、と議論を進めながらやってきました。 スクラム 導入の目的を 言語化 キャディの組織のあり方によって、理想的な スクラム の形は異なると思いますが、少なくともBiz組織ではShift Leftな組織づくりを目指したいと考えています。 つまり、計画性や透明性を担保したり、事業スピードを早めたり、メンバーのコミットメントの担保することが大切になります。 さらに スクラム 浸透に向けた取り組みとしては、認定 スクラム マスターを取得したり、 スクラム マスター育成プログラムを作ったり、新人研修で スクラム やJIRAの研修をやったり、型化を推進したり、業務特性に応じてはカンバンと併用することもやってきました。 スクラム マスターへの期待値も 言語化 し、チームの達成やスプリントの PDCA 、チームの 心理的 安全性を高めることなどを定めました。 スクラム 運用において学んだ3つのこと やってみて学んだことは、3つほどあります。 まず1つ目は今やるべきことにフォーカスすることです。Mustでやることにフォーカスして、やるべきタイミングの優先順位をつけることが必要です。 2つ目は、Bizの特徴として社外の ステークホルダー が多く、顧客やサプライパートナーなどに依存する不確実性が多いので、これを先につぶしていくことです。 DoD や担当者は明確になっているか、 ステークホルダー との認識合わせができているか、このあたりを最初に確認しておくことが必要です。 3つ目は、必ず達成することを目標にすることです。コンサバティブになりがちなスプリント計画ばかりでは、 ムーンショット を達成できないので、プロジェクトオーナーや スクラム マスターが連携して対応しています。 見えてきた課題と改善に向けた取り組み このように取り組んできて現時点では半年くらいが経過してきているのですが、スピード感を高めていく点についてはKPI設計を見直したり、 スクラム マスターの育成については、 スクラム マスター横串での連携強化やベストプ ラク ティスの共有などを行いました。 TechでLeSSを導入し始めた話がありましたが、Bizでも10人くらいだったチームが3ヶ月で30人とか40人の規模になってくることもあり、これからLeSS的な取り組みをしようとしているのが、Biz側の状況となります。 パネルディスカッション ここでは以下4つのテーマに関して、パネルディスカッションに参加したTech/Bizのメンバーからのコメントをまとめています。 [1]Biz/Techともに スクラム 導入の背景や、当初解決したかった課題感は何か? 正林: Bizの方で案件のデリバリーをやっていましたが、お客様も大きくなってきて案件が大型化してきています。 そんな中で、業務の全体像を管理したり、進捗を追っていくこと、タイムボックスを切っていく成果管理のところに課題があり、組織ごとのリーダーの力量によって質が異なりました。 プロジェクトマネジメントの手法がいるよねという話も出ておりましたが、JIRAとセットで使える手法がなかった点が課題でした。 高藤: Techでは不確実に対してどう立ち向かうのかが大きいと思っていましたし、規模や求められるスピード感が変わってきているので。 見える化 も難しくなり、そこをスッキリできるといいなと思っていました。 [2] スクラム を導入してみた時の難しさや、想定していなかった困難とそれらへの対処方法は? 木岡: そもそもJIRAに慣れておらず、Biz組織もすごいスピードで組織拡大していく中で、自分自身をマネジメントできる人ばかりではない中で スクラム を運営することが難しかったです。 例えば DoD を適切に設定できないことが挙げられます。 これを設定できるようリーダー層にオーナーを任せ、品質を担保できるよう スクラム マスターとしてサポートしていました。 濱口: LeSS自体の困難さと言っても、2〜3チームくらいの規模でしたし、LeSSの特有のチーム横断的なイベントは最小限で行なっていたので、あまり問題は発生しませんでした。 ただし、 スクラム 自体の浸透度やストーリーポイントの理解度について(他のチームと比較したときに消化ポイントが低いことを気にしてしまうメンバーがいる等)は、課題として出てきました。 [3] スクラム 導入の成果を 言語化 すると何か? 朱: TechとBizで共通言語で議論ができることがよかったです。私が所属しているチームでは、 原価計算 のロジックアップデートをやっているのですが、Biz側で要求を分析していてTech側にシステムに反映するため2つのチームを統合しようとした時に、 スクラム があったのでやりやすかったです。 共通基盤で価値提供がしやすく、スプリントのイベントも一緒にできるので成果の1つかなと感じています。 山下: BizとTechの同じところとして、不確実性が高いので見積るのが難しいことがあります。よって、完了定義を明確化することをみんなが意識し、開発を着手する前に考えておくことができるのは成果だと思います。 違うところとしては、TechとBizではBizの方がチーム一体になっているように見えるのですが、実はプロダクトは1つなのでTechの方がフロントエンドとバックエンドなどお互いがサポートしあっています。 よって、今回の スクラム の目標から、具体的にブレイクダウンしないとBiz側では スクラム を進めるのは難しいと思っています。 正林: 全部が見えるようになったことですね。 第 三者 からみても、 見える化 は進んだと思います。 JIRAと スクラム をセットで運用していたので、プロジェクト管理がそろいました。 キャディは仮説検証を高速で繰り返しているフェーズの会社なので、同じOperating Systemの上で動かせるようになったのは成果です。 [4]今後 スクラム をさらに活用して取り組んでみたいことは何か? 木岡: Bizでは大きな人数・組織に同時多発的に スクラム を導入したので、各 スクラム で独自の運用となっているケースもあります。 そのため、 スクラム 同士を横串で連携強化したり、標準化を進めることが必要です。 こうすることで、新しく入った人や異動で入ってくる人ともスムーズに入ることができますし、やりづらさが減ると考えています。 佐野: キャディでは、様々なバックグラウンドの人が集まっており、国内・グローバルで今後さらに展開するためには、とても良いメンバーに恵まれていると思っています。なので、Biz スクラム のベストプ ラク ティスといえばキャディだよね、と言われるようになりたいですね。 濱口: この4月からキャディでは次の期が始まりますが、3チームがシステム的に連携するようになりますので、LeSSの真果がこれから問われるだろうなと思っています。 高藤: LeSSの枠組みで活動しているチームと、LeSSの外のチームとの取り組みや改善が今後のテーマになるので、ここをやっていきたいと考えています。 最後に いかがでしたでしょうか? Tech組織はもちろんBiz組織も積極的に採用を進めておりますので、まずはカジュアルにもっと話を聞いてみたい!という方は、 こちら より申し込んでいただけますと幸いです。 また、イベントに関する情報は、 キャディのconnpass がありますので、こちらも登録いただけると嬉しいです。 最後までお読みいただきまして、ありがとうございます。
キャディでエンジニア採用を担当しています片渕です。 本記事では、2022年3月29日に開催されたエンジニアの社内勉強会「STUDDi」、Yaoからのプレゼン内容をレポート形式でまとめています。 [toc] PLCを語る上で欠かせないのはFA Yao: 私の方からは今回、PLC(Programmable Logic Controller)に関してお話します。 PLCを語る上で欠かせないのはFA(Factory Automation)で、名前の通り生産現場の生産工程の自動化を図るシステムの総称です。 出典: https://www.denso-fy.co.jp/business/ タッチパネルやワーク投入口、非常停止ボタンなどがあります。 FAを構成するコンポーネント FAを構成するコンポーネントを紹介しますと、まずはスライダーです。 出典:IAI 1軸ロボット  https://www.youtube.com/watch?v=hvGYRJ3Cqys これはFAの中で一番重要なものになりますが、加工するワークを搬送して、加工機に送り込むもので、直線的に移動させるものが必要になります。 シリンダーで動くもので、中にボールネジ(円柱状のネジを切っているもの)が回転して、構造物を動かしている仕組みになっています。 出典:ヤマハリニア搬送  https://www.youtube.com/watch?v=5bQXPJvl01Y リニア搬送は2015年くらいから始まったもので、ヤマハの製品が有名です。 特徴としては移動が速く、ワークを把持したまま移動できることで、ネジを回転する機構ではなく磁石の力で動かしています。 搬送系は、基本的には端にいくと次の搬送機器に取り置きする機構が必要ですが、リニア搬送では乗り継ぎ機能がついており、取り置きなしでワークを搬送することができます。 しかし、実装管理などが大変なので、大量生産ラインかつ資金が潤沢であるところで利用されることが多いです。 インデックステーブルは簡単な機構かつ安価で、ステッピングモータを使って一定角度ずつ回転するものです。 出典:インデックステーブル  https://www.youtube.com/watch?v=4q8fpRYNt2w 場所はとりますが、簡単な機構でワークを順送りできるのが特徴です。 それから直行ロボットは、IAIが使っているものを2つ組み合わせていますが、難易度の要求が高くない時によく使われます。 出典:IAI https://www.youtube.com/watch?v=hvGYRJ3Cqys 搬送して、取り置いていく作業が基本動作になります。 その他には、6軸ロボットに加えて、マシンビジョンをつけていくものがあります。 出典:デンソーランダムピッキング  https://www.youtube.com/watch?v=me1MnsZhwRk 指定されたポイントに移動するものを連続させていくのですが、これはマシンビジョンと組み合わせているので、バラ積みの部品供給などに使われたりします。 本来であれば、パレットに対象物がきちんと並んでいて、定位置で取っていった方がシステム的には安定します。 出典:パーツフィーダー  https://www.youtube.com/watch?v=PsKvgdphEY8 こちらはパーツフィーダーで、金属部品を投入口に運ぶのですが、圧電素子によって微振動を起こして部品を運んでいくものです。 ランダムな姿勢になっているものを、目的とした姿勢の部品だけを取っていきます。 センサー類としては、スイッチやワークの有無、位置計測に使われるエリアセンサーなどがあります。 コンポーネントはどのように制御されるのか? さて、ここまでご紹介してきたコンポーネントは、どのように制御されるのでしょうか。 組み込みで思いつくのはマイコン・FPGA・PCなどが考えられがちですが、実際の現場はPLCによって行われています。 PCLはシーケンサーとも呼ばれ、エレベーターなどリレー回路で制御されてきたものの代替装置となります。 リレーシーケンスは、電磁リレーを利用します。電流を流すと金属が引っ付いて、別の端子がOnになるようなスイッチング素子を組み合わせて、エレベータや自動販売機、信号機や工場の自動生産ラインなどで使われてきました。 一例として、自己保持回路というものがあります。ランプをOn/Offを保持する回路を例に考えてみます。 スイッチ緑がOnするとリレー制御側がオンとなり、ランプがついてリレーのOn状態が保持されるというのが自己保持回路というもので、これがシーケンス制御になります。 PLCとは PLCとは、配線によるリレーシーケンス制御を電子的に再現している制御機器です。 PLCを制御するのはラダー言語。 これはリレーシーケンス回路にGUIが備わっており、GUIで定義された回路図を、1つ1つのニーモニックに置き換えられて処理されています。 回路上の、1つ1つの回路への入力・出力を評価していき、最後の回路に到達するとまた先頭の回路に戻ってきます。 この1ループをスキャンといい、1~10msほどの処理時間になります。 PLCでは、スキャンを無限ループしてIO制御をしています。 なぜPLCが使われるのか? PLC利用がなぜ使われているかというと、プログラミング言語になじみがなくても開発・保守ができる点にあります。 生産設備など複雑なロジックではなくて、センサからの情報と次に起きるアクションを決定するものなので、状態遷移との相性が良いです。 産業機器としても耐環境性あり、機械的・電気的にも強いです。マイコンなどはノイズ対策が必要になりますし、ノイズによってロボットが暴走して意図しない動きをしてしまうと大きなリスクになります。 また、周辺機器が充実しているので、PLCが制御側となりますが、CCリンクで20m先のモータを動かしたり、操作板にてOn/Offが可能です。 PLCは、下位レイヤーに対してはリモートIOを経由し、IO通信に特化したネットワークを経由して、モータやロボットを制御しています。 上位レイヤーに対しては生産ラインの工程管理などIoTなども含め、ロボットの稼働時間や故障時のログを収集するシステムと連携します。 おわりに 以上、Yaoからの社内勉強会の内容をまとめたレポートととなりますが、いかがでしたでしょうか。 記事内容をご覧いただいて、興味持った方は こちら にて、お話する機会をご用意しておりますので、ぜひご活用ください。 また社外向けに一般公開するイベントに関する情報は、 キャディのCONNPASS をご用意しておりますので、こちらもご登録いただけますと幸いです。 最後までお読みいただきまして、ありがとうございます。
キャディでエンジニア採用を担当しております片渕です。 今回は、2022年3月8日に開催されたエンジニアの社内勉強会「STUDDi」、八巻からのプレゼン内容をレポート形式でまとめています。 キャディでは製造業DXに取り組んでおり、社内の受発注オペレーションも内製でシステム開発をしています。 今回のテーマは受発注オペレーションと関わりの深い会計ドメインナレッジの「収益費用対応の原則」になっています。 [toc] 「収益費用対応の原則」について 受発注では取引を通じてお金を扱いますが、このお金の扱い方は会計でルールとして決められています。 その中でも受発注オペレーションと関わりの深い「収益費用対応の原則」を説明します。 用語 「収益費用対応の原則」を説明するために、いくつか会計の用語の説明をします。 仕訳 会計データは仕訳で登録します。会計取引のデータ様式で、取引日・賃貸借の区分・勘定科目・仕訳金額などで構成されます。 仕訳データはイベントデータで、更新はされず常にデータを追加します。 借方・貸方 賃貸借区分とは借方(かりかた)、貸方(かしかた)と呼ばれる区分です。複式簿記の様式で借方が左、貸方が右に記載されます。 勘定科目 勘定科目は取引内容のマスターで、仕入、売上、棚卸し商品などの項目があります。 勘定科目同士が親子関係になることもあります。 勘定科目の「賃貸借区分」は決算で仕分けを集計するときに利用します。 仕訳の「賃貸借区分」と勘定科目の「賃貸借区分」が一致する場合はプラス、異なる場合はマイナスで集計します。 「勘定区分」はどの決算書の項目にまとめるかを決めます。 貸借対照表(BS) 画像の出典 賃借対照表(BS)は、仕訳を資産科目・負債科目・資本科目の3つに分けます。 資産は現金そのものか、売れば現金になるもので、不動産・建物・仕入れてきた商品などです。手形とか売掛金と呼ばれる、いつかお金になる契約形態も資産という扱いになっています。 負債は他から借りているお金で、銀行借入だけではなく、請求書で届くような買掛金なども負債になります。 純資産は会社自体のお金で、資本金や経済活動の結果で利益が出た時の会社に残っているお金になります。 損益計算書(PL) 画像の出典 損益計算書(PL)は、仕訳の収益科目と費用科目を集計したもので、売上高からそれぞれの費用を引いて計算します。 取引データの流れ ここからは「収益費用対応の原則」を理解するために、取引データの流れをみます。 説明の中の図で左側の図形はその取引時点でのBS、右側の図形はその取引の仕訳になります。 事業の立ち上げ 例として、100万円を元手に事業を始めます。 調達 70万を使って商品を仕入れます。 このタイミングではお金は支払わず、後日請求書払いとします。 このタイミングでは会社の資産は商品(70万) + 現金(100万) = 170万になっています。 支払 購入した商品の代金を支払います。 買掛金がなくなり、現金が100万から30万に減っています。 販売 50万円分の商品を100万円で販売します。 ここが今回のポイントになります。 販売の場合には100万円の売上だけでなく、販売した分の商品の仕入の費用を登録しています。 請求・回収 販売した商品の代金を回収します。 売掛金がなくなり、現金が130万になりました。 収益費用対応の原則 取引データの流れのなかで、販売において売上とそれにかかった仕入を登録していました。 これが「収益費用対応の原則」になります。 つまり購入した商品は販売されるまで、費用として計上されないことになります。 例えば、2月に買った商品が3月に売れたとすると、3月になるまで買った商品の費用は計上されません。 これはPLがある期間で集計する際に、損益計算の収益と費用が同じ期間になる必要があるためです。このルールがなければ、収益や費用の登録のタイミングを変更して損益をいじれてしまいます。 ルールとしては「売上の際に費用も計上する」というシンプルなものですが、このルールを知っていれば売上のタイミングでかかった費用が計算できるようになっている必要があると要件を導き出すことができます。 開発に関わる背景を知るひとつの例として、「収益費用対応の原則」のドメインナレッジを紹介しました。 おわりに 八巻からの社内勉強会の内容をまとめたレポートは以上となりますが、いかがでしたでしょうか。 記事内容をご覧いただいて、興味持った方は こちら にて、お話する機会をご用意しておりますので、ぜひご活用ください。 また社外向けに一般公開するイベントに関する情報は、 キャディのCONNPASS をご用意しておりますので、こちらもご登録いただけますと幸いです。 最後までお読みいただきまして、ありがとうございます。
はじめに Rust のマクロ 宣言的マクロ 手続き的マクロ proc_macro_workshop proc_macro_workshop について proc_macro_workshop の進め方 derive(Builder) マクロを作る マクロの処理の流れ マクロを開発する上での Tips dbg!マクロの使用 cargo-expand 01-parse 目標 実装方針 実装 02-create-builder 目標 実装方針 実装 空のbuilder関数をCommand構造体に実装する CommandBuilderを返すようにbuilder関数の実装を変更する 構造体名、フィールド名に応じた Builder 構造体を生成する 03-call-setters 目標 実装方針 実装 04-call-build 目標 実装方針 実装 05-method-chaining 目標 実装方針 実装 まとめ 参考文献 はじめに この記事では proc_macro_workshop というリポジトリを使って Rust の手続き的マクロの作り方を学んでいきます。想定している読者は以下のような方です。 Rust の基本的な文法や概念(トレイトや所有権、ライフタイムなど)を知っている 手続き的マクロの作り方について知りたい この記事では以下のことを説明します。 Rust のマクロの概要 手続き的マクロ( derive マクロ)の作り方 proc_macro_workshop の進め方 また、この記事では以下のことは説明 しません 。 Rust の基本的な文法や概念 宣言的マクロの作り方 Rust のマクロ Rust のマクロには宣言的マクロと手続き的マクロの 2 種類があります。 宣言的マクロ 宣言的マクロは macro_rules! 構文を使用して定義されるマクロで、match 式に似た形で処理内容を定義します。 vec! や println! など普段良く使うマクロも宣言的マクロです。 宣言的マクロについては The Rust Programming Language 日本語版 に vec! マクロを例にした解説があるので、そちらをご覧ください。 手続き的マクロ 手続き的マクロはトレイトのデフォルト実装の導出に用いる derive マクロなどに代表されるマクロです。宣言的マクロよりも複雑な処理を記述できることが特徴で、以下の 3 種類があります。 derive マクロ 構造体やenumに付与することでその構造体やenumに追加の処理を実装できます 関数風マクロ 関数風マクロは宣言的マクロのように関数呼び出しと似た形で使用できるマクロです。宣言的マクロと比較するとより複雑な処理を記述できます 属性風マクロ deriveマクロと同様に付与した対象に対して追加の処理を実装できますが、こちらは構造体やenumだけでなく関数などにも適用できます。テストを書くときに用いる #[test] アトリビュートなどがこれにあたります 手続き的マクロについては The Rust Programming Language 日本語版 により詳しい説明があるのですが、これだけで実際に使えるような手続き的マクロを作り始めるのは難しいかと思います。この記事では proc_macro_workshop というリポジトリを使って実際に手を動かしながら手続的マクロの作り方を学んでいきます。 proc_macro_workshop proc_macro_workshop について proc_macro_workshop には作成するマクロの種類によって 5 つのプロジェクトがあります。 derive マクロ derive(Builder) derive マクロ derive(CustomDebug) 関数風マクロ seq! 属性風マクロ #[sorted] 属性風マクロ #[bitfield] これらのマクロの内容については proc_macro_workshop リポジトリ をご覧ください。 この記事では、Builder パターンに必要な実装を導出する derive(Builder) プロジェクトを進めつつ手続き的マクロの作成方法を学んでいきます。 derive(Builder) プロジェクトを進めると最終的には以下のような使い方のできるマクロが完成します。 use derive_builder :: Builder; #[derive(Builder)] pub struct Command { executable: String , #[builder(each = "arg" )] args: Vec < String > , #[builder(each = "env" )] env: Vec < String > , current_dir: Option < String > , } fn main () { let command = Command :: builder () . executable ( "cargo" . to_owned ()) . arg ( "build" . to_owned ()) . arg ( "--release" . to_owned ()) . build () . unwrap (); } proc_macro_workshop の進め方 proc_macro_workshop の各プロジェクトは以下の流れで進めていきます。 テストケースを追加する tests/progress.rs に記載されているテストケースのコメントアウトを解除する 追加したテストケースをパスするようなマクロを実装する src/lib.rs に実装します テストをパスしたら 1.に戻る proc_macro_workshop の各プロジェクトにはそれぞれのマクロがパスすべきテストが記載されており、これらのテストに通過するようなマクロを実装することが目標となります。 derive(Builder) プロジェクトの場合は builder/tests ディレクトリにテストケースが記載されたファイルが格納されています。各ステップでテストケースを 1 つずつ増やしていき、これに対応した機能を実装してきます。 プロジェクトの進捗は tests/progress.rs を用いて管理します。 tests/progress.rs は以下のようになっており、各行がそれぞれのテストケースに対応しています。各ステップでテストケースのコメントアウトを解除し、それぞれのテストを通過するように実装を進めていきます。 #[test] fn tests () { let t = trybuild :: TestCases :: new (); t. pass ( "tests/01-parse.rs" ); //t.pass("tests/02-create-builder.rs"); //t.pass("tests/03-call-setters.rs"); //t.pass("tests/04-call-build.rs"); //t.pass("tests/05-method-chaining.rs"); //t.pass("tests/06-optional-field.rs"); //t.pass("tests/07-repeated-field.rs"); //t.compile_fail("tests/08-unrecognized-attribute.rs"); //t.pass("tests/09-redefined-prelude-types.rs"); } 各テストケースにおける目標はテストケースが記載されているファイルのコメントに書いてあります。実際に作業する際はこれらのコメントを参考にしつつ実装していくことになります。 テストは builder ディレクトリで cargo test コマンドを実行することで実行できます。 derive(Builder) マクロを作る derive(Builder) プロジェクトでは大まかに以下のような流れでマクロを作成していきます。 マクロのひな型を用意する 01-parse で作業します builder パターンを実現するために必要な機能(setter メソッドなど)を実装する 02-create-builder ~ 05-method-chaining で作業します エラーハンドリングなどを実装する 06-optional-field ~ 09-redefined-prelude-types で作業します それでは実際にマクロを書いていきましょう。以降の各節の名前はテストファイルの名称と対応しています。以降の各節では以下の流れで進めていきます。 ステップの目標を確認する 実際には具体的なテストケースがありますが、すべて記載すると煩雑なので各ステップのゴールのみ記載します 実装方針の説明 実装 各ステップの最後にテストケースをパスする実装例を提示します マクロの処理の流れ Rust の手続き型マクロの処理は基本的に以下のようになっています。本記事ではこの内容を具体的に実装していきます。 トークン 列を入力として受け取る 受け取ったトークン列を構文木に変換する 変換した構文木をもとに処理を行い所望の構文木を得る 得られた構文木をトークン列に変換して返す トークン列と構文木の相互変換には syn クレート と quote クレート がよく使われます。この記事でもこれらのクレートを用いてマクロを実装します。 syn クレートはトークン列から構文木への変換に使用します。また、 syn クレートには構文木に関する構造体が定義されており、パースして得られた構文木はこれらの構造体のインスタンスとして保持されます。 quote クレートは構文木からトークン列への変換に使用します。 マクロを開発する上での Tips dbg! マクロの使用 マクロのデバッグをする際などには構文木の中身が実際にどうなっているかを知りたくなることがあるかもしれません。 syn クレートの extra-traits フィーチャー を有効にするとこれらの構造体に Debug トレイトが実装されて dbg! マクロを使ったデバッグができます。開発する上で便利なので開発中は有効にしておくと良いでしょう。 syn = { version = "1.0.86" , features = [ "extra-traits" ] } cargo-expand cargo expand を使うとマクロによって生成されたコードを出力できます。これもデバッグに便利なので詰まった時は使うと良いでしょう。 proc-macro-workshop の GitHub リポジトリ と cargo-expand の GitHub リポジトリ に使い方の説明があるので、使う場合はこちらを参照してください。 01-parse 目標 空の derive マクロを作る 実装方針 テストが実行されるのは tests/01-parse.rs の main 関数ですが、このステップでは何も記載がありません。ですので、特に何もしなくてもテストをパスする…かというとそうではなく、 #[derive(Builder)] が使われた際に呼ばれる derive マクロが存在している必要があります。 なぜかというと、テストコード中で以下のように構造体に対して derive マクロが呼ばれているためです。 #[derive(Builder)] pub struct Command { executable: String , args: Vec < String > , env: Vec < String > , current_dir: String , } マクロの基本的な処理内容についてはすでに説明しましたが、より具体的には以下のような流れになっています。 トークン列 proc_macro::TokenStream (以降では基本的に TokenStream と記載します)を引数として受け取る syn::parse_macro_input! マクロでパースして構文木にする パースした構文木を元に所望の構文木を生成する 生成した構文木を TokenStream に変換して返す このステップではマクロが存在していればよいので、1.と 4.の処理を実装すればテストをパスします。 実装 それでは実際に実装をしていきます。 まずは derive マクロの定義から始めます。関数に proc_macro_derive アトリビュートをつけることでその関数内に記載された処理が derive マクロの実装になります。 derive マクロのシグネチャは (TokenStream) -> TokenStream になっており、実態は Rust のトークンを TokenStream という構造体で受け取り、内部で処理した後に TokenStream という構造体として返す関数です。 #[proc_macro_derive(Builder)] pub fn derive (input: TokenStream) -> TokenStream {} では関数の中身を実装してきましょう。最低限テストが通るためにはこの関数から TokenStream を返せば良いので、以下のように TokenStream を新しく作って返してやれば OK です。 #[proc_macro_derive(Builder)] pub fn derive (input: TokenStream) -> TokenStream { proc_macro :: TokenStream :: new () } このままでも良いのですが、次のステップへの準備として入力をパースできるようにもしておきましょう。入力のパースには syn::parse_macro_input! マクロを使います。derive マクロの入力は syn::DeriveInput で定義される構造をしているので DeriveInput としてパースします。入力をパースする処理を追加すると最終的なマクロの実装は以下のようになります。 use proc_macro :: TokenStream; use syn :: {parse_macro_input, DeriveInput}; #[proc_macro_derive(Builder)] pub fn derive (input: TokenStream) -> TokenStream { let _input = parse_macro_input! (input as DeriveInput); proc_macro :: TokenStream :: new () } 02-create-builder 目標 CommandBuilder 構造体のインスタンスを返す builder 関数を Command 構造体に実装する 実装方針 最終的には以下のような CommandBuilder 構造体のインスタンスを返す builder 関数を Command 構造体に実装するのが目的です。 pub struct CommandBuilder { executable: Option < String > , args: Option < Vec < String >> , env: Option < Vec < String >> , current_dir: Option < Vec < String >> , } 必要な作業は大きく分けて以下の 2 つです。 builder 関数を Command 構造体に実装する CommandBuilder 構造体の定義を実装する 汎用的に使えるようにするには Builder 構造体の構造体名やフィールドの名前、型は derive マクロが適用される構造体に応じたものにする必要がある これを同時に全部進めるのはたいへんですので、以下の順に進めていきます。 空の builder 関数を Command 構造体に実装する CommandBuilder を返すように builder 関数の実装を変更する 構造体名、フィールド名に応じた Builder 構造体を生成する 実装 空の builder 関数を Command 構造体に実装する 前のステップで説明したように、手続き的マクロの処理の概要は以下の通りです。1.と 2.は前のステップで実装しました。このステップでは 3.と 4.の処理を実装していきます。 まずは空の builder 関数を実装する処理を書いていきましょう。この段階では入力について気にする必要はありません。 proc_macro::TokenStream (以降では基本的に TokenStream と記載します)を引数として受け取る syn::parse_macro_input! マクロでパースして構文木にする パースした構文木を元に所望の構文木を生成する 生成した構文木を TokenStream に変換して返す トークン列を生成するためには quote クレートの quote! マクロを使います。以下のように Rust のコードと(ほぼ)同じ構文で記述でき、この内容がトークン列に変換されます。 #[proc_macro_derive(Builder)] pub fn derive (input: TokenStream) -> TokenStream { let _input = parse_macro_input! (input as DeriveInput); let expand = quote! { impl Command { pub fn builder () {} } }; proc_macro :: TokenStream :: new () } このままだと proc_macro::TokenStream::new() によって生成された空のトークン列が戻り値として返るので、生成したトークン列を返すようにしましょう。 quote! マクロを使うと proc_macro::TokenStream ではなく proc_macro2::TokenStream が生成されます。 proc_macro2::TokenStream は proc_macro2 クレートによって提供されるトークン列です。 proc_macro::TokenStream が Rust コンパイラの使用するトークン列です。 proc_macro2::TokenStream は proc_macro::TokenStream::from で proc_macro::TokenStream に変換できるので、以下のようにして生成したトークン列を返します。 #[proc_macro_derive(Builder)] pub fn derive (input: TokenStream) -> TokenStream { let _input = parse_macro_input! (input as DeriveInput); let expand = quote! { impl Command { pub fn builder () {} } }; proc_macro :: TokenStream :: from (expand) } この段階でのマクロの実装は以下のようになります。 use proc_macro :: TokenStream; use quote :: quote; use syn :: {parse_macro_input, DeriveInput}; #[proc_macro_derive(Builder)] pub fn derive (input: TokenStream) -> TokenStream { let _input = parse_macro_input! (input as DeriveInput); let expand = quote! { impl Command { pub fn builder () {} } }; proc_macro :: TokenStream :: from (expand) } CommandBuilder を返すように builder 関数の実装を変更する 前のステップで実装した builder 関数は空ですので、このままでは何もしてくれません。次の段階として、ハードコードされた CommandBuilder 構造体を返すように builder 関数の実装を変更してみましょう。 まずは CommandBuilder 構造体の定義を quote! マクロ内に追加します。 let expand = quote! { pub struct CommandBuilder { executable: Option < String > , args: Option < Vec < String >> , env: Option < Vec < String >> , current_dir: Option < Vec < String >> , } impl Command { pub fn builder () {} } }; 次に builder 関数内からデフォルト値でフィールドを埋めた CommandBuilder を返すように変更します。 CommandBuilder のフィールドはすべて Optional なので None で埋めておきます。 let expand = quote! { ...略... impl Command { pub fn builder () -> CommandBuilder { CommandBuilder { executable: None , args: None , env: None , current_dir: None , } } } }; 最終的な実装は以下のようになります。 use proc_macro :: TokenStream; use quote :: quote; use syn :: {parse_macro_input, DeriveInput}; #[proc_macro_derive(Builder)] pub fn derive (input: TokenStream) -> TokenStream { let _input = parse_macro_input! (input as DeriveInput); let expand = quote! { pub struct CommandBuilder { executable: Option < String > , args: Option < Vec < String >> , env: Option < Vec < String >> , current_dir: Option < Vec < String >> , } impl Command { pub fn builder () -> CommandBuilder { CommandBuilder { executable: None , args: None , env: None , current_dir: None , } } } }; proc_macro :: TokenStream :: from (expand) } 構造体名、フィールド名に応じた Builder 構造体を生成する さて、ここまでで Command 構造体用の Builder 構造体を返すマクロができました。ただ、このままだとほかの構造体に対して使用できません。derive マクロが適用された構造体の名前やフィールドに応じて適した実装を与えるようにしたいです。Builder 構造体の要件をまとめると以下のようになります。 構造体の名前は derive マクロの適用された構造体名の末尾に Builder を付けたものとする フィールド名は derive マクロの適用された構造体のフィールド名と同じものを使う フィールドの型は derive マクロの適用された構造体のフィールドの型を Optional にしたものとする このような要件を満たすためには derive マクロが適用された構造体の情報を取得する必要があります。では、どこから取得すれば良いのでしょうか。derive マクロの入力をパースすると以下のような syn::DeriveInput 構造体が得られるのですが、実はこの構造体の中に必要な情報が入っています。 pub struct DeriveInput { pub attrs: Vec < Attribute > , pub vis: Visibility, pub ident: Ident, pub generics: Generics, pub data: Data, } このステップに関係する部分は以下の 3 つです。 vis は構造体の可視性の情報を保持しています ident は identifier の略で、変数名などの Rust コード中の識別子の情報を保持しています。ここでは derive マクロが付与された構造体/enum の名前を保持しています data は構造体の保持するフィールドの情報を持っています 構造体名は ident を使えば取得できそうです。derive マクロの使われた構造体名の末尾に Builder を付けたものを Builder 構造体の名前にしたいので、 format_ident! マクロを使って末尾に Builder を結合します。 format_ident! マクロはこのように新たな識別子を作るときに使用します。詳しくは quote クレートの GitHub リポジトリ をご覧ください。 let ident = input.ident; let builder_name = format_ident! ( "{}Builder" , ident); 構造体名についてはこれで良さそうです。次はフィールド名と型を取得しましょう。フィールドの情報は data フィールドに入っているのでした。 syn::Data は以下のような enum です。今回扱うのは構造体なので DataStruct 構造体の中身を見てみましょう。 pub enum Data { Struct (DataStruct), Enum (DataEnum), Union (DataUnion), } syn::DataStruct はこうなっています。 fields がフィールドの情報を持っています。 pub struct DataStruct { pub struct_token: Struct, pub fields: Fields, pub semi_token: Option < Semi > , } Fields は以下のような enum です。 Named が名前のついたフィールドです。今回は Unit 構造体やタプル構造体はサポートしないので Named だけ見てみましょう。 pub enum Fields { Named (FieldsNamed), Unnamed (FieldsUnnamed), Unit, } FieldsNamed はフィールド情報を持っている Field 構造体を複数保持しており、 named.iter() で Field 構造体のインスタンスをイテレートできます。 pub struct FieldsNamed { pub brace_token: Brace, pub named: Punctuated < Field, Comma > , } syn::Field 構造体はこうなっています。 ident がフィールド名で ty が型の情報を持っています。ようやく必要な情報までたどり着きました。 pub struct Field { pub attrs: Vec < Attribute > , pub vis: Visibility, pub ident: Option < Ident > , pub colon_token: Option < Colon > , pub ty: Type, } パターンマッチを使って必要な情報だけを取り出します。サポートしないケースについては今は panic! させておきましょう(後のステップでちゃんとコンパイルエラーが出るようにします)。この後で使いやすいように ident と ty は Vec に格納しておきます。 let (idents, types): ( Vec < Ident > , Vec < Type > ) = match input.data { Data :: Struct (data) => match data.fields { Fields :: Named (fields) => fields .named . into_iter () . map ( | field | { let ident = field.ident; let ty = field.ty; (ident. unwrap (), ty) }) . unzip (), _ => panic! ( "no unnamed fields are allowed" ), }, _ => panic! ( "expects struct" ), }; さて、必要なものはすべて準備できました。これらを使ってマクロの出力として返すトークン列を生成しましょう。 この前のステップで quote! マクロについて Rust のコードと(ほぼ)同じ構文で記述することができ と説明しましたが、 quote! マクロの内部では特殊な記法を使うことができます。たとえば、 quote::ToTokens トレイトを実装する変数に quote! マクロの中で # をつけるとその変数の内容が挿入されます。これを使って適切な名前や型のフィールドを持った構造体を動的に生成します。今までに出てきた syn::Ident などの構造体は quote::ToTokens トレイトを実装しているので特に気にせず使うことができます。 これを使って CommandBuilder 構造体の構造体名を書き直すと以下のようになります。 quote! { #vis struct #builder_name { executable: Option < String > , args: Option < Vec < String >> , env: Option < Vec < String >> , current_dir: Option < Vec < String >> , } } また、 quote! マクロの中では #(...)* のような形で IntoIterator を実装した型の変数を繰り返して挿入できます。これを使って CommandBuilder 構造体のフィールド名も書き換えると以下のようになります。 idents と types はそれぞれフィールド名と型を格納したベクタですので、以下のようにしてベクタの各値について繰り返し展開できます。 ) と * の間に文字を記述するとその文字で区切って展開してくれるので、以下では #(...),* のようにして構造体のフィールドをベクタから生成しています。 quote! { #vis struct #builder_name { #( #idents : Option < #types > ), * } } さて、これでハードコードされた構造体名やフィールド名などをすべて取り除くことができました。 同じように builder 関数も書き換えると以下のようになります。 quote! { #vis struct #builder_name { #( #idents : Option < #types > ), * } impl #ident { pub fn builder () -> #builder_name { #builder_name { #( #idents : None ), * } } } } 以上をまとめると最終的なマクロの実装は以下のようになります。 use proc_macro :: TokenStream; use quote :: {format_ident, quote}; use syn :: {parse_macro_input, Data, DeriveInput, Fields, Ident, Type}; #[proc_macro_derive(Builder)] pub fn derive (input: TokenStream) -> TokenStream { let input = parse_macro_input! (input as DeriveInput); let ident = input.ident; let vis = input.vis; let builder_name = format_ident! ( "{}Builder" , ident); let (idents, types): ( Vec < Ident > , Vec < Type > ) = match input.data { Data :: Struct (data) => match data.fields { Fields :: Named (fields) => fields .named . into_iter () . map ( | field | { let ident = field.ident; let ty = field.ty; (ident. unwrap (), ty) }) . unzip (), _ => panic! ( "no unnamed fields are allowed" ), }, _ => panic! ( "expects struct" ), }; let expand = quote! { #vis struct #builder_name { #( #idents : Option < #types > ), * } impl #ident { pub fn builder () -> #builder_name { #builder_name { #( #idents : None ), * } } } }; proc_macro :: TokenStream :: from (expand) } 03-call-setters 目標 CommandBuilder 構造体の各フィールドに対する setter メソッドを作る 実装方針 具体的な実装から考えるとやりやすいです。 Command 関数の executable フィールドを例にとると setter は以下のようになるので、これを前のステップと同様、変数の展開や繰り返しを使って書き直しましょう。 pub fn executable ( &mut self , executable: String ) -> &mut Self { self .executable = executable self } 実装 実装方針で書いた具体的なフィールドの実装を書き直しましょう。変数の展開や繰り返しの書き方はすでに説明した通りです。 #(...)* の括弧内には変数以外を入れることもできます。今回は関数の実装自体を #(...)* で繰り返してやれば良いでしょう。 impl #builder_name { #( pub fn #idents ( &mut self , #idents : #types ) -> &mut Self { self . #idents = Some ( #idents ); self }) * } 最終的な実装は以下の通りです。 use proc_macro :: TokenStream; use quote :: {format_ident, quote}; use syn :: {parse_macro_input, Data, DeriveInput, Fields, Ident, Type}; #[proc_macro_derive(Builder)] pub fn derive (input: TokenStream) -> TokenStream { let input = parse_macro_input! (input as DeriveInput); let ident = input.ident; let vis = input.vis; let builder_name = format_ident! ( "{}Builder" , ident); let (idents, types): ( Vec < Ident > , Vec < Type > ) = match input.data { Data :: Struct (data) => match data.fields { Fields :: Named (fields) => fields .named . into_iter () . map ( | field | { let ident = field.ident; let ty = field.ty; (ident. unwrap (), ty) }) . unzip (), _ => panic! ( "no unnamed fields are allowed" ), }, _ => panic! ( "expects struct" ), }; let expand = quote! { #vis struct #builder_name { #( #idents : Option < #types > ), * } impl #builder_name { #( pub fn #idents ( &mut self , #idents : #types ) -> &mut Self { self . #idents = Some ( #idents ); self }) * } impl #ident { pub fn builder () -> #builder_name { #builder_name { #( #idents : None ), * } } } }; proc_macro :: TokenStream :: from (expand) } 04-call-build 目標 Command 構造体のインスタンスを返す build メソッドを CommandBuilder 構造体に実装する CommandBuilder の各フィールドが None の場合はエラーを返すようにする 実装方針 これも具体的な実装から考えるとわかりやすいです。 Command 構造体の場合は以下のようになるので、これを変数の展開や繰り返しを使って書き直しましょう。 impl CommandBuilder { ...略... pub fn build () -> Result < Command, Box < dyn Error >> { if self .executable. is_none () { return Err (...略...) } ...略... if self .current_dir. is_none () { return Err (...略...) } Command { executable: self .executable. clone (). unwrap (), args: self .args. clone (). unwrap (), env: self .env. clone (). unwrap (), current_dir: self .current_dir. clone (). unwrap (), } } } 実装 ガード節をまず作ります。このように quote! を使って部分的にトークン列を作って後で組み合わせることもできます。 let checks = idents. iter (). map ( | ident | { let err = format! ( "Required field '{}' is missing" , ident. to_string ()); quote! { if self . #ident . is_none () { return Err ( #err . into ()) } } }); 前のステップと同じようにして build 関数を作ります。上で作ったガード節を #(#checks)* で展開しています。 let expand = quote! { ...略... pub fn build ( &mut self ) -> Result < #ident , Box < dyn std :: error :: Error >> { #( #checks ) * Ok ( #ident { #( #idents : self . #idents . clone (). unwrap ()), * }) } ...略... } 最終的な実装は以下の通りです。 use proc_macro :: TokenStream; use quote :: {format_ident, quote}; use syn :: {parse_macro_input, Data, DeriveInput, Fields, Ident, Type}; #[proc_macro_derive(Builder)] pub fn derive (input: TokenStream) -> TokenStream { let input = parse_macro_input! (input as DeriveInput); let ident = input.ident; let vis = input.vis; let builder_name = format_ident! ( "{}Builder" , ident); let (idents, types): ( Vec < Ident > , Vec < Type > ) = match input.data { Data :: Struct (data) => match data.fields { Fields :: Named (fields) => fields .named . into_iter () . map ( | field | { let ident = field.ident; let ty = field.ty; (ident. unwrap (), ty) }) . unzip (), _ => panic! ( "no unnamed fields are allowed" ), }, _ => panic! ( "expects struct" ), }; let checks = idents. iter (). map ( | ident | { let err = format! ( "Required field '{}' is missing" , ident. to_string ()); quote! { if self . #ident . is_none () { return Err ( #err . into ()) } } }); let expand = quote! { #vis struct #builder_name { #( #idents : Option < #types > ), * } impl #builder_name { #( pub fn #idents ( &mut self , #idents : #types ) -> &mut Self { self . #idents = Some ( #idents ); self }) * pub fn build ( &mut self ) -> Result < #ident , Box < dyn std :: error :: Error >> { #( #checks ) * Ok ( #ident { #( #idents : self . #idents . clone (). unwrap ()), * }) } } impl #ident { pub fn builder () -> #builder_name { #builder_name { #( #idents : None ), * } } } }; proc_macro :: TokenStream :: from (expand) } 05-method-chaining 目標 CommandBuilder 構造体でメソッドチェーンを使えるようにする 実装方針 03-call-setters ステップで実装した setter は &mut Self を返すようになっているので、実はすでにメソッドチェーンを使えるようになっています。そのため、このステップでは追加の実装をする必要はありません。 実装 このステップでは追加の実装は必要ありません。このステップ用のテストを実行して変更なしでテストをパスすることをチェックしてみましょう。 まとめ 前編では Builder パターンを実現するのに最低限必要な機能を持ったマクロを作りました。 しかし、まだ Optional なフィールドの取り扱いに不十分な点があったり、ベクタ型のフィールドの取り扱いに改善の余地があったりします。後編ではそのあたりの機能を実装していきます。 後編へ続く。 参考文献 proc-macro-workshop/builder に取り組む Rust の procedural macro を操って黒魔術師になろう〜proc-macro-workshop の紹介 実践 Rust プログラミング入門 syn - Docs.rs quote - Docs.rs
はじめに こんにちは。2022年に誕生したAI Labというチームで、主に図面解析をしている中村遵介です。 趣味が料理と画像を4倍に拡大することなので、今日は最近読んだ「Revisiting l_1 Loss in Super-Resolution: A Probabilistic View and Beyond[1]」という、画像の拡大で利用される損失関数に関する論文を紹介したいと思います。 趣味以外の理由として、CADDiでは図面画像の解析を行なっておりノイズ除去や画像拡大などの分野に注目しているという点もあります。 畳み込みニューラルネットに関する知識は必要ですが、画像の拡大に関する知識は必要としないように書いたつもりです。 論文の概要 いったん細かい話を置いておいて、論文の概要をざっくりご説明します。 この論文が取り組んだ課題は以下の点になるかと思います。 入力された画像を拡大する超解像分野において、低画質画像と高画質画像を1対1で学習させる既存手法を拡大し、低画質画像から「対応する可能性のある高画質画像の分布」を学習する手法を提案した 提案した損失関数が既存手法で頻繁に使用される l_1 loss を下限に持ち、同時に不可能決定問題としてのランダム性も捉えられることを明らかにした 分布を学習することの副作用として、生成画像に対する画素ごとの不確実性も同時に予測できることを明らかにした それでは詳細についてみていきましょう。 前置き Single Image Super-Resolution について 論文のタイトルは「Super-Resolution」とだけありますが、報告されている内容は Single Image Super-Resolution(SISR) と呼ばれる分野の話になります。 SISRは、日本語で「単一画像超解像」と呼ばれ、 1枚の画像を入力とし、対応する1枚の拡大された画像を出力する タスクを指します。 SISRではバイリニアフィルタによるシンプルな手法もあれば、畳み込みニューラルネット(Convolutional Neural Network: CNN)を用いた手法もあり、画像処理分野では今も盛んに議論が行われる領域です。 これの一つの理由が、SISRが不可能決定問題であるというところにあると思います。つまり、ある低画質画像 x が与えられた際、縮小すると x になる画像、すなわち求めたい綺麗な高画質画像 y は複数存在します。そのため、SISRという問題設計においては正解の手法が存在せず、今後も既存手法より良い結果を得られる手法が登場する可能性が残り続けます。 SISRでよく用いられる損失関数と評価指標 最近では、CNNを使用した教師付き学習を利用した数多くのCNNが提案されています。学習方法やネットワーク構造など様々な点が議論されていますが、学習時に利用する損失関数に着目すると、大きく4つに分けられるかと思います。 生成画像と教師画像の l_2 loss 生成画像と教師画像の l_1 loss 生成画像と教師画像の perceptual loss 生成画像と教師画像の discriminator loss このうち l_2 loss と l_1 loss は非常に似ており、生成画像 y と 教師画像 \hat{y} に対して画素ごとに二乗誤差もしくは絶対誤差を計算して、その合計値を最小化することでより教師画像に近い画像を生成しようと試みます。 3番目のperceptual loss は特徴空間での比較を行います。具体的には、生成画像と教師画像をなんらかの学習済みCNNに通し、途中で得られる深層特徴について、その二乗誤差もしくは絶対誤差を最小化しようと試みます。 最後の discriminator loss はGenerative Adversarial Network(GAN)ベースの手法で利用される損失関数で、生成画像と教師画像を見分けるネットワークを同時に訓練し、生成ネットワークはなるべく見分けがつかないような画像を出力しようと学習していきます。 これらの損失関数は単独で使用されることもあれば、組み合わせて使用されることもあります。 一方で評価指標は大きく分けて3つ存在します。 Peak Signal-to-Noise Ratio(PSNR) 二乗誤差ベースで生成画像と教師画像の近さを比較します。値が大きいほど2つの画像が近いことを意味します Structural Similarity Index Measure(SSIM) パッチの統計情報ベースで生成画像と教師画像の近さを比較します。値が1に近いほど2つの画像が近いことを意味します MOS(Mean Opinion Score) 複数の評価者による5段階評価で画像の綺麗さ・自然さを評価します。5が理想的な高画質画像であることを意味します L1 loss の限界 SISRに対するCNNベースの教師付き学習では、 l_2 loss もしくは l_1 lossが使用されることが多いです。特に近年では l_1 lossがPSNR/SSIM/見た目の点で l_2 lossより優れているというのが実験的に言われ、 l_1 lossが使用される傾向が強まっています。 やや横道に逸れて歴史の話をすると、最初にCNNがSISRに適用されたのがSRCNN[2]という手法でした。これは l_2 lossを損失関数に利用した3層のネットワークで、当時の手法から大きくPSNRを向上させました。その後に登場するESPCN[3]やVDSR[4]・DRCN[5]では高速化やネットワークの層数の増加が行われましたが、やはり l_2 lossを使用する点は変わりませんでした。 PSNRが二乗誤差を元に計算されるため、この傾向は非常に自然な流れであったと思われます。 流れが変わったのが SRGAN[6] と呼ばれるGANベースの手法の登場です。SRGANは discriminator loss を採用することで、不可能決定問題に対してGANで「低画質画像 x に対応し、かつ高画質画像として自然な分布から生成されたかのような綺麗な画像 y 」を生成するという一つの解を提案しました。その中では学習の安定化のために、perceptual loss、そしてやはり l_2 loss が使用されていました。このSRGANの生成部分のネットワークはSRResNetと呼ばれ残差構造を積極的に取り入れた手法だったのですが、このネットワーク構造がSISRに向いていることがわかり、後の多くの手法がこのSRResNetを参考にしています。 その中でも注目を集めたのがEDSR[7]という手法で、EDSRはSRResNetを巨大化させたネットワークです。徒に巨大化させたわけではなく、例えばそれまで使用されてきたバッチ正規化を「実際に学習させてみた結果、ほぼ正規化能力が失われていた」ということで削除したり、より残差構造を取り入れたりといった工夫をしています。そして、はっきりと l_1 lossによる学習がPSNR/SSIMの両者において良い結果を示したことを報告しました。 We train our networks using L1 loss instead of L2. Minimizing L2 is generally preferred since it maximizes the PSNR. However, based on a series of experiments we empirically found that L1 loss provides better convergence than L2. EDSR[7]より引用 以降は多くの場合で l_2 lossの代わりに l_1 lossを損失関数に採用することが増えています。安定的に高パフォーマンスを出せるRCAN[8]も l_1 lossで学習を行なっています。 話を戻します。 l_1 loss が l_2 lossより実験的に優れていること自体は良いのですが、その本質はあまり変わりがありません。縮小したらある低画質画像 x になる高画質画像の集合を 仮に y とすると、 l_2 loss での学習は、これら y の平均値を学習することと等しく、 l_1 lossは中央値を学習することに等しいです。 つまり、 l_2 loss も l_1 loss も解空間のある一点だけを捉えようとする損失関数であり、解空間そのものを捉えるには不十分であると言えます。 そこで、論文ではパラメータ W と入力 x が与えられた時の高画質画像 y の事後分布を明示的にモデリングすることで、解空間を推定することに注力します。 確率的モデリング 多くの既存手法では以下の尤度関数の最大化を目的とします。 \max_{W}{L\left(W\mid\hat{y}\right)} = P\left(\hat{y}\mid x;W\right). W がパラメータで、 \left( x, \hat{y} \right) が学習データとして用意された低画質画像と高画質画像のペアです。論文の根幹は事後分布 P(y\mid x;W) の明示的な推定です。 ここでいう y は高画質画像として可能性のある複数の自然画像を意味しています。このとき、 \hat{y} は P(y\mid x;W) に従って高い確率で生成された画像のうちの1枚とみなすことができます。 つまり、最終的には以下の確率を最大化したいことになります。 P\left(\hat{y}, y\mid x;W\right) = P\left(\hat{y}\mid y\right)P\left(y\mid x;W\right). 日本語で言えば「入力 x とパラメータ W が与えられた条件での、高画質画像の確率分布に対し観測された \hat{y} がその分布から生成される確率」を最大にしたい、という感じでしょうか。 ここでは y が無数に存在するので扱いやすくするため期待値を考えることにしています。 \mathbb{E}_{y\sim P\left(y\mid x;W\right)}\left[P\left(\hat{y}\mid y\right)\right] あとはこれをいくつかの仮定をおきながら分解して考えるだけです。まず第一に「入力 x とパラメータ W が与えられた条件での、高画質画像の確率分布( P\left(y\mid x;W\right) )」を噛み砕いていみます。 論文では、ある画素は似た領域の加重平均で表せるという仮定と中心極限定理から、この分布は多変量正規分布に従うという仮定を置いています。 P\left(\hat{y}, y\mid x;W\right) \sim N\left(\mu_{\left(x;w\right)};\Sigma_{\left(x;w\right)}\right) ここで出てきた \mu と \Sigma はパラメータ W を用いてCNNで実際に推論するものになります。また、 P\left(\hat{y}, y\mid x;W\right) が多変量正規分布に従うという仮定を置きましたが、実際にこれを利用して学習する場合、分布からのサンプリングを行う必要があります。これは微分可能な操作ではないという問題がありますが、これに関してはすでに Variational Auto-Encoder[9] が Reparameterization Trick と呼ばれる方法での回避を提案しているので、そのまま利用することにしています。 すなわち、標準正規分布から生成される z を用いて \mathbb{E}_{y\sim P\left(y\mid x;W\right)}\left[P\left(\hat{y}\mid y\right)\right] = \mathbb{E}_{z\sim N\left(0, 1\right)}\left[P\left(\hat{y}\mid \mu + \sigma \ast z\right)\right]. のように y を捉え直すことで微分可能にします。 P\left(y\mid x;W\right) については考え終わったので、残っている P\left(\hat{y}\mid y\right) をみていきます。 ここでは、[10]に従ってこの分布をボルツマン分布に従うと仮定します。 P\left(\hat{y}\mid y\right) \propto \prod_{i}^{H\times W} \exp \left( – \frac{\|\hat{y}_i – y_i\|_1}{kT} \right) これを先程の式に代入することで最終的な目的関数を得ることができます。 \mathbb{E}_{z\sim N\left(0, 1\right)}\left[\prod_{i}^{H\times W} \exp \left( – \frac{\|\hat{y}_i – \left(\mu_i + \sigma_i \ast z\right)\|_1}{kT} \right)\right] あとは扱いやすいように負の対数尤度関数にして、最大化問題を最小化問題へと変更しています。 \min\mathbb{E}_{z}\left[\frac{1}{kT} \sum_{i}^{H\times W}{\|\hat{y}_i – \left(\mu_i + \sigma_i \ast z\right)\|_1}\right] kT は定数なので無視することにすると、結局のところは上の式を最小化する \mu と \sigma の2つををCNNで推定してあげれば良いことがわかります。その際に z がランダム性を与えてくれるイメージです。 ちなみに z は標準正規分布なのでその期待値は0になります。つまり、上の式は(Jensenの不等式にそのまま代入すると)下限が \hat{y} と \mu の l_1 lossになります。 こうして振り返ってみると、 l_1 lossが分布の1点だけしか捉えられていなかったことがよくわかります。 さて上式の最小化ですが1点だけ困った点があり、それはネットワークとしては \sigma を0としてしまう(ランダム性を排除して l_1 lossと同等にする)のが最も最小化できる戦略になる、ということです。 論文ではこれを実験的にも確かめ、 \sigma が0に落ちる様子をプロットしてくれています。 そこで、 \sigma を0にせず(ランダム性を失わず)良い感じに勾配降下法で常識を最小化するための設計を提案しています。 分散の実践的な設計 論文では、実際に学習させるための \sigma の設計を2パターン挙げており、それぞれを「入力データに依存しない \sigma 」と「入力データに依存する \sigma 」と呼んでいます。 最終的に選択しているのは後者の方ですが、せっかくですので両方見ていきます。 データに依存しない分散 データに依存しない分散 \sigma の設計は実に簡単で、「学習によって \sigma が0に収束してしまうなら、 \sigma を学習しなければいい」というものです。 すなわち、あらかじめ \sigma を非常に小さな定数 k として設定する、という方法です。この場合、最適化対象は以下のように読み替えられます(余計な定数は除外しました)。 \mathbb{E}_{z}\left[\sum_{i}^{H\times W}{\|\hat{y}_i – \left(\mu_i + \sigma_i \ast z\right)\|_1}\right] = \mathbb{E}_{z}\left[\sum_{i}^{H\times W}{\|\left(\hat{y}_i + k \cdot z\right) – \mu_i\|_1}\right]. これは、すなわち教師データである \hat{y} に対して、平均0、分散 k のガウシアンノイズをかけてから l_1 loss で学習させていることと同義です。 この考え方自体は Noise2Noise[11] と似ています。Noise2Noiseは、(ノイズ画像、綺麗な画像)というペアを用いでデノイジングを学習するのではなく、(ノイズ画像、ノイズ画像)のペアを用いても同等の性能が得られる、ということを示した論文です。ただし、事前条件としてガウシアンノイズであれば平均が0の分布に従う必要があります。また、理論的には l_2 lossでの精度を保証したものですが( l_2 lossが平均値を推定するため)、実験では l_1 lossでも良い性能を示すことを報告してくれています。今回は乗せるノイズは平均0のガウシアンノイズなので問題なしです。 しかし、この方法だとノイズによっては \mu が \hat{y} に対して遠ざかるような方向の勾配を産む可能性があります。これは先程の式を \mu で偏微分すれば明らかで、余計な定数項を除くと \frac{\partial \| \hat{y} + k\cdot z – \mu \|_1}{\partial \mu} となりますが、これは \hat{y} と \mu – k \cdot z との大小関係で符号が入れ替わります。 一方で、一般的な l_1 lossに関しては \frac{\partial \| \hat{y} – \mu \|_1}{\partial \mu} なので \hat{y} と \mu との大小関係で符号が入れ替わります。このため、 \mu – k \cdot z と \mu の間に \hat{y} が存在するような z では、 l_1 lossによる学習と、提案する学習では勾配の方向が逆転します。 この勾配の逆転による学習の不安定化を防ぐためには、十分な数の z のサンプリングが必要になりますが、これは学習コストが肥大化を意味し、論文では使用されませんでした。 データに依存する分散 やや天下り的なのですが、 \sigma を定数 k ではなく |\hat{y} – \mu| に設定してみます。 これによって、ネットワークが求めるべき \mu は以下であれば良いということになります。 \min_{\mu}\mathbb{E}_{z}\left[\frac{1}{kT} \sum_{i}^{H\times W}{\|\hat{y}_i – \left(\mu_i + |\hat{y}_i – \mu_i| \ast z\right)\|_1}\right] これを \mu で偏微分すると(余分な定数項は除外) \frac{\partial \mathbb{E}_{z}\left[\sum_{i}^{H\times W}{\|\hat{y}_i – \left(\mu_i + |\hat{y}_i – \mu_i| \ast z\right)\|_1}\right]}{\partial \mu_i} となります。これは \hat{y} と \mu との大小関係でのみ符号が入れ替わるので、勾配の符号だけ見れば l_1 lossで学習するのと同義です。また、 \sigma が0に収束するということは、 \mu と \hat{y} が完全に一致することを意味しますが、それはSISRの不可能決定性に反するので、過学習しない限りは起きないことになります。 論文ではこれを l_\mathbb{E} loss として呼んでいます。 ここで l_\mathbb{E} lossの意味合いを振り返ると、考え方としては y の分布の平均 \mu を予測しつつ、同時に \hat{y} と \mu の誤差も分散として使用することを意味します。 そこで、単に上式を最適化するだけでなく、ネットワーク上では1つのブランチで y の分布の平均 \mu を予測しつつ、同時に別のブランチで \hat{y} と \mu の誤差を予測し、それを \sigma として扱うマルチタスクラーニングとしても良さそうです。 \sigma ブランチでは \min_{\sigma} \| |\hat{y} – \mu| – \sigma \|_1 を求めれば良いことになります。論文ではこれをauxillary lossとして命名しています。 この \sigma を推論しておくメリットとして、 \sigma の値が予測の自信度として使用できる、という点を挙げています。 実験 理論が出来上がったので後は実験して評価をします。 論文では一般的なSISRの実験と同じ設定を用いていました。 学習にはDIV2Kデータセットのtraining setを使用し、評価データセットではSet5 / Set14 / BSD100 / Urban100 / Manga109データセットを使用します。Manga109は比較的新しいデータセットなので、3,4年より前のSISR論文では評価に使用していない場合もあります。 また、Set5 / Set14は慣習的に使用しますが、枚数がそれぞれ5枚 / 14枚なので評価値としてはブレが大きい印象があります。 拡大倍率については、2 / 3 / 4倍について実験を行なっています。最近だと8倍についても実験する論文を見かけることがあります。 評価指標は輝度に対するPSNRとSSIMを使用しています。 学習の詳細についてはあまり触れませんが、入力 x が48px x 48pxになるようにクロップしてリサイズしています。これは、SISRでは広範囲のコンテキストを必要としないという前提に基づくことが多く、他の多くの論文でもあまり大きな画像を入力に用いることはありません。 そのため、RCANのような大きいモデルであってもV100を1枚で十分学習が可能です。 最終的な推論結果としては \mu を使用しています。 提案手法は損失関数を調整し、また最後に \sigma 推論用のブランチを追加するだけで実現できるため、基本的にどのようなCNNにも適用できます。そこで、論文では、VDSR / SRResNet / EDSR / RCAN といった有名どころのCNNに適用し、 l_1 loss との比較を行なっています。 軽量なモデルに対して提案手法を適用した結果の比較(EDSR-baselineはEDSRの中で提案されている比較的軽量なモデルです) 重いモデルに対して提案手法を適用した結果の比較 PSNR/SSIMともに上がり幅としては僅かに見えますが、CNNベースのSISRの精度は年々上がり幅が小さくなっているので、それを踏まえて見ると悪くない結果のように思えます。 比較実験 提案手法の有効性を確認するために、以下の実験を行なっています。 損失を l_1 lossだけにして学習 損失を l_\mathbb{E} loss だけにして学習(論文ではEq. (8)として登場) 損失を auxillary loss だけにして学習(論文ではEq. (10)として登場) 損失を l_1 loss と auxillary loss で学習 損失を l_\mathbb{E} loss と auxillary loss で学習 それぞれについて、ベースネットワークとしてEDSRを使用し、Set14でPSNR/SSIMを評価しています。学習時間はやはり伸びますが、PSNR/SSIMとしては提案手法が最も良い結果を示していました。 予測の自信度の可視化 推測した \sigma を使用してモデルの自信度を画素ごとに表示させています。 当然と言えば当然なのですが、画像のエッジ部分(高周波部分)でモデルが自信をなくしているのが分かります。 感想 l_1 lossは近年のCNNベースのSISRで積極的に利用されてきましたが、きちんと焦点を当てている論文は中々限られているように思っていたので新鮮でした。 一方で、最終的な推論結果を \mu のみから導き出していた点がやや疑問点として残りました。結果として分布の平均値をピックアップせざるを得ないのかな、という印象です。 \sigma の結果を利用して、自信がない領域だけ生成的な手法で推測してあげる、みたいなことが出来たら面白いのかもしれないなぁ…というような未来を感じさせる論文でした。 引用 [1] He, Xiangyu, and Jian Cheng. “Revisiting L1 Loss in Super-Resolution: A Probabilistic View and Beyond.” arXiv preprint arXiv:2201.10084 (2022). [2] Dong, Chao, et al. “Image super-resolution using deep convolutional networks.” TPAMI 2015. [3] Shi, Wenzhe, et al. “Real-time single image and video super-resolution using an efficient sub-pixel convolutional neural network.” CVPR 2016. [4] Kim, Jiwon, Jung Kwon Lee, and Kyoung Mu Lee. “Accurate image super-resolution using very deep convolutional networks.” CVPR 2016. [5] Kim, Jiwon, Jung Kwon Lee, and Kyoung Mu Lee. “Deeply-recursive convolutional network for image super-resolution.” CVPR 2016. [6] Ledig, Christian, et al. “Photo-realistic single image super-resolution using a generative adversarial network.” CVPR 2017. [7] Lim, Bee, et al. “Enhanced deep residual networks for single image super-resolution.” CVPR workshops 2017. [8] Zhang, Yulun, et al. “Image super-resolution using very deep residual channel attention networks.” ECCV 2018. [9] Kingma, Diederik P., and Max Welling. “Auto-encoding variational bayes.” ICLR 2014. [10] Bruna, Joan, Pablo Sprechmann, and Yann LeCun. “Super-resolution with deep convolutional sufficient statistics.” ICLR 2016. [11] Lehtinen, Jaakko, et al. “Noise2Noise: Learning image restoration without clean data.” ICML2018 The post Revisiting L1 Loss in Super-Resolution: A Probabilistic View and Beyond を読んで appeared first on CADDi Tech Blog .
キャディでエンジニア採用を担当しています片渕です。 今回は2022年3月2日に開催したイベント、『【CADDi x ExaWizards】 AIテック組織があるべき姿とエンジニアへの期待 』に登壇したエンジニア、猿田からのプレゼン内容をまとめたものをご紹介していきます。 2つの事業・サービスへのアライン強化 開発チームが取り組む技術的・組織的な課題とは? 未来の技術組織のあり方について 最後に 2つの事業・サービスへのアライン強化 猿田: キャディには開発チームが大きく分けて2つあります。1つは生産管理プロダクト開発チームで、もう1つは図面活用サービス開発チームになります。この2つのチームそれぞれがキャディの2つの事業に強くアラインしています。 前者は、現在のキャディのコア事業である受発注プラットフォームサービス事業です。調達の分野でお客様から受注を受けて、キャディ内でオペレーションしてお客様に納品するビジネスを支えるものです。 キャディは実際の商流に入ることで発注者・受注者の取引コスト・製造コストを下げることや、テクノロジーによる効率化を図っています。 例えば図面から製造原価を計算したり、図面をシステム上で管理したり、どの加工会社に何を発注するのかというサプライチェーンを管理するシステム、加工会社と情報をやりとりできるようなポータルサイトなどもあります。 後者は、図面活用サービスというキャディ初のSaaSプロダクトになります。私はプロダクト全体の開発マネジメント、プロダクト開発チームと図面解析技術のR&Dチームの擦り合わせ部分を担当しています。 開発チームが取り組む技術的・組織的な課題とは? 次にそれぞれのチームが抱える技術的・組織的課題について説明します。生産管理プロダクト開発チームの技術的課題は複数のプロダクト・システムがあるので、それぞれのフェーズの差がある点が挙げられます。 キャディではまず自分達でオペレーションを担えるようにして、それらを型化してシステムに乗せ、AI技術などを活用しながらインテリジェンス化(自動化)してくことを考えているのですが、当然ながらプロダクトごとにその進捗が異なってきます。 オペレーションをやっているので効率化はしなければいけないのですが、短期的改善からあるべき姿を意識した開発のバランスは難しいと感じています。 また、キャディのビジネスはターゲット製品や取り扱える加工種など要件がどんどん広がって変化しているため、アラインメントが複雑になってきます。 開発組織はLeSS(大規模スクラム)を導入しているのですが、大きなチームになってきているので課題も出てきています。また、事業側とのアラインや、製造業のドメイン知識、そしてグローバル対応などの課題もあります。 図面活用サービス開発チームの技術的課題ははまず、プロダクト開発と、図面解析R&Dのバランスが難しい点が挙げられます。 それ以外にもSaaSの開発を進める上で技術的な負債が溜まっていくことへの向き合い方、B2BSaaSは相対的にMVPの要件が小さくできず製造業のお客様に使っていただくレベルへのハードルが高い点が挙げられます。 組織的課題は、プロダクト開発とR&Dのバランスを取る中で、エンジニアスキルセットの違いがあり、リソース配分が臨機応変にできない点があります。 また、R&Dしたものをどのようにテスト・リリースするのか、開発組織のグローバル化のためのコミュニケーション設計なども課題として挙げられます。 未来の技術組織のあり方について 先日キャディはManufacturing DX Summit 2022を主催しまして、その時に代表の加藤がキャディは供給側のDXを推進する会社と解釈できるという話をしました。 それをAI Labとして噛み砕いてみると、未来の製造業のあり方を想像し、キャディが持っているサプライチェーン上の受発注に関する様々なデータをかけあわせることで何ができるかを考えなければいけないと考えています。 10年後の製造業の形を想像すると色々なことが考えられるのですが、ここでは2つだけ取り上げたいと思います。 1つ目はShift Leftmostです。これは図面を解析しただけでサプライチェーンを自動構築できて、QCDのリスクが可視化され生産管理も容易になる未来です。 2つ目はComputable Supply Chainです。現状は人が見て判断しているところが多いのですが、データドリブンで自動的にサプライチェーンの構築や更新ができる未来を示しています。 これらの未来から逆算をしてAI Labとして何ができるかを構想しています。 最後に未来の技術組織の話について。こちらは私個人の意見になります。 キャディの開発組織は構造的なイノベーションが実行できる組織であるべきだと考えています。そのために事業への価値提供スピードは意識しつつ、プラットフォームやAI技術開発などを司る横断チームが必要になると考えています。 組織が大きくなったときに全てのtechメンバーが事業にアラインするのは難しいのですが、事業に近い場所に必要なtechメンバーを配置したいです。 そうすると事業チームと横断チームを連携させるために、強いマネジメントチームが必要と考えています。 最後に いかがでしたでしょうか? 猿田や猿田が所属しているAI Labメンバーなどと、カジュアルにもっと話を聞いてみたい!という方は、 こちら より申し込んでいただけますと幸いです。 また、イベントに関する情報は、 キャディのconnpass がありますので、こちらも登録いただけると嬉しいです。 最後までお読みいただきまして、ありがとうございます。
キャディでエンジニア採用を担当しています片渕です。 今回は2022年2月25日に開催したイベント『【ABEJA × CADDi】Computer Visionのビジネス活用を考える 』に登壇のエンジニア(竹原・中村)からのプレゼン内容をまとめたものを紹介していきます。 図面画像に対する母材形状認識タスク CNNモデリングにおける工夫 結果 図面画像に対するデノイジング技術 図面解析に対するノイズの影響 ノイズ除去モデルの開発 結果の可視化と分析 最後に 図面画像に対する母材形状認識タスク 竹原: 今回の発表では、母材形状認識に対しCNNを適応した内容について話します。 キャディのビジネスでは、発注者から図面データを預かって製品を発注します。その図面データを活用するために図面から様々な情報を抽出するニーズがあります。 母材形状とは、製造における加工前の形状のことで、完成品から逆算して選択されるものです。 例えば、丸棒に対して、旋盤加工をして完成品ができるという流れです。今回紹介する認識タスクでは板金加工と機械加工を含む10種類の母材が対象になります。 今回紹介する認識タスクは、図面からどのような母材が利用されるかを10クラスに分類する問題です。 母材形状認識のシステムは、社内の原価計算プロダクトや現在開発中の図面管理SaaSで活用されています。 CNNモデリングにおける工夫 母材形状認識に対して、CNNを適応しました。図面画像はサイズが大きく、CNNに入力する前にリサイズするのですが、やりすぎると細い線で表現されている情報が失われるので注意が必要でした。 また、図面は発注者により書き方や質が大きく異なっていることがあり、こういった点が母材形状認識する上での難しさになります。 モデルに入力する画像への前処理としては、テーブル(主に図面の右下にある表部分)および外枠を除去しています。また、簡単なAugumentationも適用しています。 データセットとしては、キャディの受発注業務を通して蓄積された正解データを利用しています。 モデルの学習についてですが、入力画像サイズは1024×1024、前処理はテーブル・外枠除去とAugumentation、モデルはEfficientNet、損失関数はCross Entropyで、Label smoothingを用いた学習を行なっています。 結果 認識精度を確認するための検証データは、母材形状ごとにバランスよく用意したデータセットや、顧客ごとに作成した実世界に近いデータセットをそれぞれ作成しました。 厳密な精度はお話できませんが、10クラス分類で正解率で85%~くらいでした。 誤分類のパターンとしてはやはり母材自体が似ているものは間違えやすいことが確認できています。例えば、丸棒と丸パイプのような、形が同じだが中に空洞があるかないかのような違いです。 また、図面がノイジーなもの、発注者側によって書き方が特殊な場合にも間違いが多いことが確認できました。 例えば、六角棒の母材形状分類は通常簡単な部類ですが、ノイズが多い場合丸パイプに間違えるものなどがありました。 ノイズを取り除く話については、この後に登壇する中村さんの話で詳しくさせていただきます。 図面画像に対するデノイジング技術 中村: キャディでは、Computer Visionによる図面解析を行っています。 例えば、図面に書かれている寸法線や、加工指示に関する記号、表に書かれているテキストなど、様々な情報を画像解析によって取得しています。 図面解析に対するノイズの影響 図面解析では、矢印や記号などの小さな領域が、大きな意味を持つことがあります。 例えば、寸法線の中には両端に小さな矢印がついているものがあり、解析のヒントになり得ます。 しかし、矢印自体の大きさは、図面全体が数千pxあるのに対して数〜数十pxほどしかなく、さらに図面に様々なノイズが乗っている事もあり、劣化・変形してしまうことが多々あります。 そのため、図面解析においてはノイズ除去の技術が必要です。 一口にノイズといっても、図面には様々な種類のノイズが乗ることがあり、代表的なものとしてヒゲノイズ・モスキートノイズ・カスレの3つがあります。 ヒゲノイズは線がギザギザになってしまうもので、こちらは現行の図面認識アルゴリズムの中で対処できるものです。一方でモスキートノイズやカスレは、複合的なアルゴリズムでも対処が難しく実際に認識の妨げになることがあります。 ノイズ除去のアイデアとしては2つあります。 シングルモデルとして一括で全てのノイズを除去する方法と、複数手法を扱うエキスパートモデルで対応するノイズを個別に除去していく方法です。 前者は運用が簡単なのですが開発が難しく、後者はその逆になります。 今回はPoCとしての側面もあるので、後者のエキスパートモデルの作成に挑戦しました。 ノイズ除去モデルの開発 具体的なエキスパートモデルとして、モスキートノイズとカスレの除去モデルの2つを作成しました。これらは先に述べた代表的なノイズ・劣化に当たります。 ノイズ除去のアルゴリズムはたくさんあるのですが、図面ごとに異なる傾向を簡単に取り扱える点と、ある程度パラメータフリーであるという点で、畳み込みニューラルネットワーク(CNN)によるノイズ除去を採用しました。 モデル構造にはU-Netベースの構造を用いました。選定理由としては、実装が簡単であること、構造として高解像度画像を扱いやすいこと、これまでの実績や知見が数多くあることが挙げられます。 CNNによる学習ベースのノイズ除去では、大抵の場合綺麗な画像とノイズで劣化した画像のペアが大量に必要になります。 しかし、実際の図面では綺麗な画像とノイズの乗った画像のペアを入手することは非常に難しいという問題があります。 今回はその難しさを、「図面のように見える画像を大量に生成し、ノイズをシミュレーションして乗せる」という方法で解決しています。今回モデリングでは、ノイズ除去に図面の広範囲の情報は必要ないだろうという仮説を立て、画像から小領域を切り抜いて入力しています。そのため、局所的に図面のように見える画像であれば、実際の図面でなくても良いだろうという仮説に基づいています。 結果の可視化と分析 学習は、モデルの出力する綺麗な画像と、実際の綺麗な画像との画素値の絶対誤差で学習させます。 誤差が下がっていけば学習できたと言えるのですが、注意すべきこととして誤差の値と、実際に生成された画像のクオリティが、感覚として結び付きづらいということが挙げられます。 例えば、学習の中盤のモデルと、終盤のモデルを比べると、誤差の値としては大きく差がありますが、実際に出力されたノイズ除去画像は人の目ではほとんど差がわかりません。故に、誤差の値だけでモデルの精度を正確に評価するのは難しいと考え、定性的な評価も行うことにしました。 モデルの傾向を簡単に把握できるようにするため、Web UI上で、学習したモデルを動的に読み込んで、指定した画像のノイズ除去後の結果を可視化するツールを作成しました。このツールでは 狙い通りのノイズが消せているか エキスパートモデルが除去対象としていないノイズや、図面に載っている正しい情報まで消していないか モデル同士が独立していて、適用順序を変えても最終的に得られる綺麗な画像は変わらないか という点を簡単に可視化できるようにしています。 (当日イベント内ではdemoを公開しました) 今回行ったノイズ除去の最終的な目的は、後段の図面解析がうまく進む点にあるので、その観点でも定量評価を実施しました。 まず、評価用の図面データセットとして、図面をランダムに集めたデータセット、そしてノイズが乗った図面のデータセットを作成し、それらに対してノイズを除去した前後での記号認識精度を比較しました。 結果としては、ランダムに集めた図面データセットでの記号認識精度はあまり変わりませんでしたが、ノイズが多く乗ったデータセットでは認識精度が向上しました。 今回はノイズの種類を分析し、エキスパートモデルをCNNで作り、定量的・定性的な観点から評価しました。実際にノイズの乗った図面での記号認識の精度向上を確認できました。 最後に 今回イベントに登壇させていただきました竹原・中村含め、カジュアルにキャディのエンジニアからもっと話を聞いてみたい!という方は、 こちら より申し込んでいただけますと幸いです。 また、イベントに関する情報は、 キャディのconnpass がありますので、こちらも登録いただけると嬉しいです。 最後までお読みいただきまして、ありがとうございます。
キャディでエンジニア採用を担当しております片渕です。 今回は2022年2月22日に開催したイベント『【画面キャプチャ禁止】うちの管理画面フロントエンドほぼ全部見せます! 』に登壇のエンジニア(先山・小倉・桐生)からのプレゼン内容をまとめたものをご紹介していきます。 イベントのアジェンダとしては以下の通りです。 生産管理の管理画面(先山) (実際の管理画面をご覧いただきながらデモ) Kleinのソフトウェアのアーキテクチャ 生産パートナー向け管理画面(小倉) (デモにて見積もり回答の様子を説明) UI/UXデザインについて コーディングルールについて 開発中の物流管理の管理画面(桐生) アーキテクチャやライブラリについて 今後の展望 最後に 生産管理の管理画面(先山) https://speakerdeck.com/caddi_eng/20220222xian-shan-fa-biao-zi-liao-tenhurev2 先山: 私の方からはKlein(クライン)の概要をお話していきます。Kleinは一言でいうとキャディ社内の受発注を管理しているシステムです。冒頭の資料に記載のあった製造工程・サプライチェーン管理プロダクトを指しています。 キャディにおける受発注とは、お客様が作ってほしい製品の図面を受注して、製造原価を計算し、その製品の製造を加工会社さんに発注します。 発注後は実際の製品ができて、キャディの拠点に届き、そこで品質の担保をしてからお客様に納品します。 この加工会社さんのことをSP(Supply Partner)と社内用語として使っています。 それからサプライチェーンとは、製造の原材料・部品の調達から販売に至るまでの一連の流れを指す用語です。 キャディにおけるサプライチェーンとは、お客様から製品を受注したところから始まります。受注製品はいくつかの工程を経て最終的な製品に仕上がります。例えばA社さんには金属を曲げる加工をお願いし、その品質をキャディが検査した後で、今度はB社さんに塗装をお願いし、またキャディにて品質を確認して、最後にお客様へ納品する流れを指します。 これはキャディから見たサプライチェーンですが、お客様からすると、私たちが納品した製品を使って最終的な製品を製造するため、この先にもサプライチェーンが続いていきます。 ちなみにKleinというプロダクトの名前の由来は、クラインの壺から来ています。よろしければ 過去のtech Blog をご覧ください。 (実際の管理画面をご覧いただきながらデモ) Kleinの管理画面にはお客様から受注した案件が一覧で表示されます。 製品のデータは別の原価計算プロダクトから出力されたcsvをアップロードして登録します。BtoBプロダクトでよくありがちな、例えば大量の製品を受注したので一括でデータを取り込みたいという要望があるため、このようなバルクアップロードやダウンロードの機能を実装しています。 製品の登録が完了したら、ユーザーのオペレーションとしては、どこのSPさんに作業をお願いするか決定します。 例えばこのテストデータの製品は、生地加工と表面処理と熱処理の3つの工程がありますが、生地加工をA社さん、表面処理と熱処理をB社さんにお願いすることとして画面から設定してみます。 お願いするSPさんが決まったら、サプライチェーンを設計します。A社さんからB社さんにいつ製品が届けられるかや、私たちの拠点にいつ搬入されるかなどを画面から入力していきます。入力された情報は別の画面でグラフ構造で表現され、製品の流れをわかりやすくしています。 このサプライチェーン設計画面では、数百点以上の図面を一気に扱うこともあるため、Excelのようにコピペができたりするなど工夫を凝らした画面にしています。この機能はAG-Gridというライブラリを使うことで実現しています。 デモ用なのでかなり省略しているのですが、サプライチェーンを構築した後は、このテストデータの製品をSPさんに発注します。 他にもKleinの特徴として、ステークホルダーが多いので、フローを一律に決めるのが難しい場面でも、要件に対応できるようにしています。不確実性をフロントエンドで吸収しています。 Kleinのソフトウェアのアーキテクチャ ユーザーのブラウザ上にてReactのアプリケーションが動き、JavaScriptファイルやHTMLファイルはnginxで配信して、CDNがキャッシュしてくれて配信する流れになります。 BFFとの通信にはGraphQLを採用しています。間にCDNがあります。BFFとBEはgRPCで通信しています。Webに関してはTypeScriptで、BEはRustで書かれています。 フロントエンドのアーキテクチャはClient-Side Renderingで、UIのコンポーネントはAnt-Designを使っています。 AG Gridのようなライブラリを使って、大量の製品でもなるべく高速にレンダリングするような取り組みや、一括編集などの機能も対応できるよう工夫しています。 BFFはNest.jsなどを使っていて、コードファーストでGraphQLスキーマを生成できるようにしています。 リポジトリは、MonorepoでFEとBFFを管理していて、これはNest.jsで生成したGraphQLスキーマファイルを使って、ソースコードを自動生成しているためです。最近は、実験的にturborepoを採用しています。 まとめとして、Kleinはキャディの受発注を支えていて、BtoBの要件に答えるために、大量データをいかに早く画面で見ることができるか、レンダリングが快適だったり、一括登録ができるかなど工夫しています。 また、業務フローを一律に決めにくい点もあるので、それらに対応できるようシステムを作っています。 生産パートナー向け管理画面(小倉) https://speakerdeck.com/caddi_eng/spp-feshao-jie 小倉: パートナー様(製作会社)に使ってもらうのが、私が説明するパートナーポータルになっていますが、キャディからの見積もりに回答したり注文内容を確認するためのサービスになっています。 アーキテクチャは、Next.jsを使ったフレームワークですが、今回はデザインのところなので、フロントエンドでどうデザインを決めたのか?を説明していきたい。 (デモにて見積もり回答の様子を説明) Kleinを通じて、この値段で大丈夫ですか?と打診があり回答をしていきますが、AG Gridを使って作っています。 詳細見たければ図面を見ることができ、値段がが問題なければokをしたり、違う値段を提案するのであればその金額を入力できたり。納期や、依頼を辞退をすることやエクセルで一括編集することもできます。 回答があると、Klein側に情報が反映されます。 例えば、納期が厳しいものから作ろうと考えてそれを選ぶと、納品先や値段・図面などを確認することができます。 一覧をエクセルやcsvの形でDLできたり、Ctrl+Cしたらコピーできるなど、エクセルとシームレスにつながるような仕様にしています。 製品名でも検索ができ、パートナー様がやるべきことが全部できるようになっています。 UI/UXデザインについて コンセプトとしては、今までの作業フローに慣れている人・慣れていない人でも使いやすくすることが必要と考えました。 元々は、メールベースでやり取りをしていた工程なのですが、パートナーポータルを導入して移行してもらうために工夫が必要でした。 最初は全然使ってもらえなかったのですが、表計算ソフトと同じ操作性を実現するためにAG Gridを使って実装したところ、アプリの使用率が26%から90%へと急増しました。 それから、紙での見積もり体験と同じことができるようにも意識したのですが、パートナーポータル導入以前は、パートナー様は図面は印刷したものを見ながらパソコンの画面も見ていることが多かったようです。 パートナーポータルを導入すると、全てパソコン上で確認することになるのですが、大量にタブを開く状態になってしまい大変なことがわかりました。 そのため現在では、別ウィンドウで開くようにしたり、パートナー様は必ずしも広いモニターを持っているわけではないので、いろいろなコンポーネントを折りたためるようにもしています。 デザインのプロセスは、最初はUI/UXに強いデザイナーに専属でチームに入ってもらい、Figmaで紙芝居を作って、FEエンジニアと頻繁にMTGして各コンポーネントの意図を確認をしていました。 ある程度できあがってきたタイミングでは、デザイナーは専属ではなくなり、新しい機能を作るときにエンジニアである程度完成させないといけないので、Antdというコンポーネントライブラリに置き換えて、Miroというホワイドボードアプリを使ってラフなイメージを共有して開発をしています。 コーディングルールについて 最初は、style attributeを禁止しようとか色々あったのですが、現在はファイルサイズは200行未満にしよう、読めるファイルにしましょうという話にしたのが、ワークしているのでシンプルイズベストだなと思っています。 開発中の物流管理の管理画面(桐生) 桐生からの物流拠点プロダクトについての資料 桐生: 物流拠点プロダクトの話をさせていただきますが、拠点で使うプロダクトを絶賛開発中です。 Kleinで入力されたデータで、予定データをインプットして拠点で働く人たちに情報提供して、その後の作業に役立ててもらうものを作っています。 届いた製品がいつ来て、いつ出すのか?検査して出荷する作業がありますが、リーダーと作業担当者の2つを想定しています。 難しさとしては、物理を伴う難しさがあり、例えば予定を過ぎても届かないとか、個数が足りないとか検査でNGが出るなど、イレギュラーパターンが無数にあるので、それがある前提でオペレーションならびにソフトウェアプロダクトの設計が必要になります。 アーキテクチャやライブラリについて FEとBEというレイヤー構成になっていてGraphQLでお互い通信しています。FEはNext.jsやChakura-ui・AntdなどのUIコンポーネントを、BEはNestJSを使っています。 ORMとしてはPrismaを使っていることや、GraphOLのクライアントライブラリとしてURQLというのを使っているのも特徴的です。 全体のリポジトリの構成ですが、Monorepoで管理していて、yarnのワークスペースを使っています。Kleinはyarn v1を使っているのですが、こちらはv3を使うようにしています。インストール速度が断然早いなと感じています。 また、nodeそのもののバージョン管理としてvoltaを使っています。自動でnodeインストールしてくれるので便利です。 FEはUIのコンポーネントとして、Chakra UIを使っていて、CSSを書くというよりはpropertyをセットするところ、バックエンドのメンバーにも受け入れられやすいですし、型の補完が効くので開発体験が良いですね。 Antdを使っている理由として、Chakura-uiはプリミティブなコンポーネントが多く、柔軟性は高いものの高機能なコンポーネントがないため、スクラッチで作るのは大変なのでAntdで補完しています。 URQLは、Apollo Clientと比較してシンプルで使いやすく、Bundle Sizeが1/3くらいなので推しのポイントになりますね。 React Suspenseのサポートや、唯一Offlineのサポートをしているところがいいです。 物流拠点で使うプロダクトになるので、ネットワークが切れるなどの可能性もあると想定すると、使えるものになるのではないかと考えています。 バックエンドは、Prismaを使っています。非常に開発体験がよく、prisma schemaを書くとデータベースのmigration codeと型付きprisma clientを自動生成してくれるので、強力な仕組みが備わっています。 あとは、GraphQLとの相性がよく、GraphQLはN+1問題を引き起こしやすいと言われていますが、それをケアする仕組みもそなわっています。 テストについては、StorybookのCSF3.0を使って、Storyを書くようになっています。2.0よりシンプルで、Storybookの定義をJestで再利用可能になったのは大きいと思っています。具体的には、@storybook/testing-reactというパッケージを使って、StoryをJestに取り込んで結合テストを行っています。 Mock Service Worker、これはAPIの Mocking libraryですが、ネットワークリクエストが発生するComponentも簡単にデータをMokingすることができます。 (ここからは実際の管理画面のVS Codeをご覧いただきながらデモ) 今後の展望 まずMonorepoについては、FEもBEのコードを一括で管理できるのはありがたいのですが、ビルド時間が長くなりがちです。 社内の他のプロダクトで、circleci/path-filteringを使って、差分ビルドを実現しているので、こちらのプロダクトでも使いたいなと考えています。 Testingのところでは、結合テストはStorybookに集約していきたいと思っているのですが、interaction testingでは、現在はCI実行ができずJestを使っている状況です。 それがCIで実行できるようになるというissueもあり、それを期待して今後はStorybookで完結できるのではないかなと期待をしています。 最後に いかがでしたでしょうか? 先山・小倉・桐生など、キャディのフロントエンドエンジニアともっと話を聞いてみたい!という方は、 こちら より申し込んでいただけますと幸いです。 また、イベントに関する情報は、 キャディのconnpass がありますので、こちらも登録いただけると嬉しいです。 最後までお読みいただきまして、ありがとうございます。
こんにちは。Quipu という原価計算システムの開発をしている山田です。 最近まで原価計算システムのバックエンドのアーキテクチャを変更するプロジェクトをチームで進めていて、その中で Python プロジェクトに導入してよかった OpenTelemetry について共有したいと思います。 原価計算システムのアーキテクチャ変更に伴うパフォーマンス懸念 OpenTelemetry について OpenTelemetry の導入 必要になったライブラリ API や SDK 周りのライブラリ トレース情報を任意のサービスに送信するライブラリ 各種ライブラリやモジュールの自動的な設定を行うライブラリ OpenTelemetry を実際に導入する OpenTelemetry を導入した結果 おわりに 参考 原価計算システムのアーキテクチャ変更に伴うパフォーマンス懸念 原価計算システムはいくつかのマイクロサービスによって動いていました。フロントエンド、フロントエンドから GraphQL のリクエストを受ける BFF、ビジネスドメインを扱う Rust の gRPC サーバ。今回この Rust の gRPC サーバのアーキテクチャ変更により、 Python で実装された価格計算システムと「見積」を管理する Rust の gRPC サーバというドメインの境界を設けてアプリケーションを分割しています。 今まで Rust で実装されていた原価計算処理を Python で実装しなおしました。Python で実装しなおした経緯としては、新しく原価計算処理を実装する人にとっても変更がしやすい設計にしたいというのがありました。加えて、開発者のみならず実際に原価計算の中身を変更するようなメンバーでも、実際システムとして動いているアプリケーションを変更できた方が良いという結論になったので学習しやすい Python の方が適していると判断した経緯もあります。 今までは原価計算処理についても Rust で実装されたシステムが動いていたのでサービスとしても一つでしたが、これを Rust と Python との分割されたマイクロサービスに変更したことで、ネットワーク越しに計算処理を実行することになります。新しい原価計算アプリケーションを導入するにあたってのパフォーマンスのボトルネック解消をチームで行っていました。1 つのサービスをデバッガやプロファイラなどを用いてパフォーマンスチューニングすることは十分できますが、マイクロサービス間のリクエストのパフォーマンスをチェックすることは現状難しかったのです。なのでこの部分に分散トレースシステムを導入して、より良いパフォーマンスを目指すための下地を作ることになりました。 OpenTelemetry について OpenTelemetry [1] は分散トレースを実現するためのフレームワークとそれらを提供するライブラリの名称で CNCF の incubating プロジェクトです。 OpenTracing と OpenCensus が合併して OpenTelemetry という名称で進められています。トレース情報やレイテンシなどアプリケーションの実行にまつわるデータの管理のために設計された API や SDK などのフレームワークになっていて、どんなベンダにも依存せずにデータを扱うことができるようになっています。現在僕らのチームではこれらのデータを Google が提供している Cloud Trace に送っています。 僕らのチームでは Python のアプリケーションを本格的に運用するのは初めてでしたが、Node.js で実装された BFF や見積を扱っている Rust で実装された gRPC サービスでは OpenTelemetry を導入してネットワーク間のリクエストの可視化ができていたのでその土台に Python のマイクロサービスも載せることで、一様にリクエストのレイテンシを可視化することができます。 OpenTelemetry の導入 必要になったライブラリ 今回 Python で実装された原価計算処理システムに実際導入するにあたって行ったことを紹介していきます。以降は OpenTelemetry の用語を使っているので、必要に応じて OpenTelemetry が用意している Glossary [2] を参照してください。 API や SDK 周りのライブラリ API や SDK 周りのライブラリを導入してトレース情報として取得したい単位でアプリケーションコードにトレース情報を取得する実装ができます。これらのモジュールにより Span を作成したり、トレース情報を送信し始めることができます。実際には opentelemetry-api や opentelemetry-sdk をインストールして利用します [3] 。 トレース情報を任意のサービスに送信するライブラリ トレース情報を送信するためにいくつかライブラリを導入します。開発の時にうまく送信できているかを確認するため Jaeger を開発環境で使用しているのでその exporter と、実際に本番環境などでトレース情報を送信するため、 Google Cloud Trace に export するためのライブラリ [4] を導入しています。 今回のプロジェクトでは opentelemetry-exporter-jaeger と opentelemetry-exporter-gcp-trace を利用して実現します。 各種ライブラリやモジュールの自動的な設定を行うライブラリ 今回のシステムではウェブアプリケーションフレームワークとして starlette を利用しているので、送られてきた trace context を自動的に解釈してくれるライブラリとして instrumentation ライブラリ opentelemetry-instrumentation-starlette を導入します。 今回は導入しなかったですがそのほかにもさまざまなライブラリに対応した instrumentation が存在します [5] 。 上記のライブラリ群を導入することで Python のアプリケーションでも OpenTelemetry が利用できます。 OpenTelemetry を実際に導入する まずは OpenTelemetry でトレースを始める部分のコードを書いてみましょう。 TracerProvider を使ってトレースを始めることができます。また、開始した TracerProvider を様々な箇所で API 経由で取得できるように trace.set_tracer_provider で設定します。 from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider tracer_provider = TracerProvider() trace.set_tracer_provider(tracer_provider=tracer_provider) トレース情報を全てのリクエストで取得する必要がないケースが存在します。例えば送られるデータ量が多くなってしまうので減らしたり、親のトレースに依存してトレース情報を取得するかどうか判断したいなどさまざまなケースでトレース情報をサンプリングできると嬉しいです。今回のアプリケーションは基本的にリクエストの末端に位置するアプリケーションになるので、サンプリングは親のトレースに任せる形にしました。その挙動がライブラリのデフォルトになっているので今回は指定しませんが、必要があれば sampler という引数を指定するか OTEL_TRACES_SAMPLER という環境変数を利用してトレースするかどうかを制御することができるようになっていました。 from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.samplers import TraceIdRatioBased # 1000 回に 1 回だけトレースする tracer_provider = TracerProvider(sampler=TraceIdRatioBased(1/1000)) アプリケーションがリクエストを受け取った地点からこの Python アプリケーションのトレース情報を取得できるように instrumentation を導入します。僕らは starlette を使っているので opentelemetry-instrumentation-starlette を導入します。このライブラリが提供する StarletteInstrumentor を利用することで自動的にトレース情報の収集ができます。 from opentelemetry.instrumentation.starlette import StarletteInstrumentor from starlette.applications import Starlette routes = [ // ルーティングの定義 ] app = Starlette(routes=routes) StarletteInstrumentor.instrument_app(app) アプリケーションとしてトレース情報が取得できるようになったので、これを Jaeger や Google Cloud Trace に送信するための設定を行いましょう。開発環境では標準出力にトレース情報を出力したり、Jaeger にトレース情報を export しつつ、本番環境では Google Cloud Trace にトレース情報を export します。以下のサンプルコードでは基本的にデフォルトの設定値を使うようになっています。 from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter, SimpleSpanProcessor # 標準出力にトレース情報を出力するための span processor tracer_provider.add_span_processor( span_processor=SimpleSpanProcessor(span_exporter=ConsoleSpanExporter()) ) # Jaeger にトレース情報を export するための span processor tracer_provider.add_span_processor( span_processor=BatchSpanProcessor(span_exporter=JaegerSpanExporter()) ) # Google Cloud Trace にトレース情報を export するための span processor tracer_provider.add_span_processor( span_processor=BatchSpanProcessor(span_exporter=CloudTraceSpanExporter()) ) 基本的には以上のことを行うだけで、アプリケーションに対して in-coming なリクエストがあった場合にそれがトレースされるようになります。しかしこれだけではリクエスト全体のレイテンシがわかるようになるだけで、全ての関数が自動的にトレースされるわけでも重たい処理がどこにあるかどうかなどがわかるわけでもありません。それ自体は自分でトレース情報を作成してあげる必要があります。 OpenTelemetry の API としてトレース情報を自分で作成して紐づける方法があるのでそれを利用して処理のトレース情報を増やしていきましょう。 tracer オブジェクトを取得して、その start_as_current_span メソッドを利用してトレース情報を作成します。このメソッドは @contextmanager デコレータが利用されているため with 文で使います。 例えば以下のようにして実際の処理のトレース情報を作成することができます。 from opentelemetry import trace tracer = trace.get_tracer_provider().get_tracer(__name__) def very_expensive_my_function() -> None: with tracer.start_as_current_span('foo'): # foo span が生成される with tracer.start_as_current_span('bar'): # foo span と同じレベルの bar span が生成される with tracerr.start_as_current_span('baz'): # bar span の子要素として baz span が生成される 上記のような方法でトレースできることがわかりました。実際トレースしたい箇所で毎回 tracer オブジェクトを生成していくのは少し大変なのと、ここまで詳細に処理を span として分割したいというよりもある関数全体でどれくらいのレイテンシなのかがわかるだけで十分なことが多いです。そこで関数のデコレータを用意して、そのデコレータが付与された関数は自動的に全体がトレースされるというようなものを作ってみましょう。ここでは @instrument というようなデコレータを作成します。 from functools import wraps from opentelemetry import trace tracer = trace.get_tracer_provider().get_tracer(__name__) def instrument(fn=None): def decorator(fn): @wraps(fn) def wrapper(*args, **kwargs): # デコレータを付与する関数名・メソッド名の qualified name name = fn.__qualname__ # デコレータが付与された関数名を span の名前として指定する with tracer.start_as_current_span(name=name): return fn(*args, **kwargs) return wrapper # @instrument でも @instrument() でも使えるようにするため if fn is None: return decorator return decorator(fn) @instrument def very_expensive_my_function() -> None: # 何らかの処理 この定義した @instrument デコレータを用いて大まかに関数全体のレイテンシを計測することができるようになりました。これを各種関数やクラスのメソッドに適用することでレイテンシが計測できていなかったもう少し詳しい部分までトレースできるようになりました。 OpenTelemetry を導入した結果 今回原価計算システムを Rust から Python に書き直したことでプロダクション環境に大きなパフォーマンス劣化の懸念が存在していました。今回 OpenTelemetry を導入して、 UI から発行されるリクエストベースで原価計算システムのトレース情報を取得することができるようになりました。これによりプロダクション環境に今回の変更を入れることでパフォーマンスにどう影響があるかが可視化されました。 当初実装していた API ではパフォーマンスに大きな劣化が生じてしまうことが事前にわかり、それを解決するために API の設計を変更し API の粒度を小さくして並列に API を呼び出すように変更しました。非同期で複数の計算結果を処理する形になり、結果としてパフォーマンスは小さな影響だけで済む形でリリース可能だと判断することができました。また Google Cloud Trace ではトレース情報をもとに日次や週次でレポートを作成することができるので、パフォーマンス改善を行ったときに前と比べてベースラインがどのように変化しているのかということも確認できるようになったのが導入した利点として大きかったです。 おわりに Python のプロジェクトで OpenTelemetry の導入をしてみましたが、しっかりライブラリ群は作り込まれていて自分達が利用しているライブラリのトレース情報を自動的に取れるようなライブラリもコミュニティで開発されていたこともありスムーズに導入が完了できました。 僕たちのチームでは Node.js や Rust でも OpenTelemetry が導入されていた背景もあり、サービス全体で分散トレーシングができるようになりました。発行されたリクエスト一つをとってみてシステム全体でどのような処理が行われているのかが確認できるようになったので、これらの資産を活かして開発効率を向上させていければと思っています。 OpenTelemetry の specification が v1.0 になってまだ 1 年です。これからもっと発展させていけるようにコミュニティに還元できることがないか利用しながら調査を進めていければと思っています。 参考 OpenTelemetry: https://opentelemetry.io/ Glossary: https://opentelemetry.io/docs/concepts/glossary/ OpenTelemetry Python API and SDK: https://github.com/open-telemetry/opentelemetry-python OpenTelemetry instrumentation for Python modules: https://github.com/open-telemetry/opentelemetry-python-contrib OpenTelemetry Python exporters for Google Cloud Monitoring and Trace: https://github.com/GoogleCloudPlatform/opentelemetry-operations-python
キャディでエンジニア採用を担当しております片渕です。 今回は、エンジニアの社内勉強会「STUDDi」にてプレゼンがありました一部の情報(2ネタほど)を、レポート形式でお伝えしたいと思います。 STUDDIは、エンジニア全員が持ち回りで発表機会があるものですが、入社してくる方も増えてきたこともあり まだ一度もスピーカーになっていない方(新しく入られた方)の発表する場として優先する 好きな技術について思う存分語ってOK という方針でやっております。 データ分析コンペの紹介 データ分析コンペとは? なぜ私がデータ分析コンペを推すのか?? コンペの種類について キャディでの仕事 ≒ データ分析コンペ Microservicesについて Microservicesとは何か? 実際の企業ではどのように使われているのか? 実社会ではどういう仕組みになるのか? キャディのビジネスモデルをMicroservicesの視点で見ると おわりに データ分析コンペの紹介 今回はまず2022年2月8日に開催された社内勉強会、竹原からのプレゼン内容をまとめております。 データ分析コンペとは? 竹原: キャディ公開の 求人票に記載がある歓迎スキル にもKaggleという言葉が出ていますが、データ分析のコンペを知らない人が多いと思うので、今回は参加したことがない人にどのような内容なのかを知ってもらうことを目的にご紹介していきます。 また、私がキャディに入社してからやってきた仕事が、コンペの内容にも近かったので、合わせて紹介していきます。 まずデータ分析コンペでは予測精度が高い機械学習モデルを開発しているものと考えてもらえると良いです。 ホストとなる企業などからコンペで解いてほしいタスクとデータが提供されて、参加者が分析したり検証して、予測モデルを開発して予測結果を提出します。 それらが随時採点されランキング(リーダーボード)が出ているので、結果を確認しながら予測モデルを2〜3ヶ月かけて磨き続けます。 コンペの上位者は賞金がもらえたり、結果に応じてランキングやTierが上がるのでゲーム的にも楽しいです。 参考文献:Laggleで描く成長戦略 〜個人編・組織編〜 なぜ私がデータ分析コンペを推すのか?? 竹原: まず、先程お話したようにゲームのように楽しく取り組めるというのはあると思います。 また、学習のためのプラットフォームとしても魅力的です。 自分が担当している業務では扱わない様々なデータを手を動かしながら経験、試行錯誤することができます。また、コンペ期間中にプラットフォーム上のフォーラムで活発な議論や解法の共有がされており、技術的にも考え方的にも多くのことを学ぶことができます。 コンペの種類について 竹原: データ分析コンペにも様々な種類のものがあるので、紹介します。 まず、データやタスクの種類、またモデルに課される制約も様々あります。データは一般的には表形式のような構造化データ、画像やテキストや音声のような非構造化データがあります。また、ECサイトの購買履歴、株のチャート、医療画像、スポーツの映像などもっともっとありますが、データのドメインも多岐に渡ります。 制約に関しても、機械学習モデルを作るときに、提供されたタスクやデータに合わせて、推論時間や提供したプラットフォームで動かなければいけない、外部データの利用の可否、など様々な制約条件もあります。また、推論時間が短いものには、特別な賞を与えたりするようなものもあります。 開催期間については、Kaggleなどの一般的なものは2〜3ヶ月です。ただ、期間も長いため期間中にフルコミットする必要はないです。参加者は並列して色んなコンペに参加したり、最後の1ヶ月だけ参加するなど色々な形で参加しています。また、コンペによっては短期間コンペのものもあります。1日以内に終了する超短期のものや、1〜2週間くらいの短期のものもあります。そのようなコンペは、どちらかというと期間中に新しい技術を学ぶというよりは、これまでやってきた経験や知識、技術をその場で試すようなイメージです。 参考文献: 「おすすめコンペは何?」の答え方を真面目に考える 短期間コンペの戦い方 キャディでの仕事 ≒ データ分析コンペ 竹原: 最後に、キャディでの業務もデータ分析コンペにも似た部分もありましたので簡単に紹介します。 最近、図面から図面に含まれる属性を予測する機械学習モデルの初期開発をしていました。 まず、そもそもタスクがどういうものかを理解して、また、社内で保有している図面解析ツールを動かしてみたり、図面を用いた過去の機械学習に関する取り組みを調査して、初期の方針やタスクのドキュメント化を進めました。このプロセスは、コンペでも提供されたタスクやデータを見てから、自分の方針を固め、関連する論文や過去のコンペを調査していく点にとても似ていました。 次に、実際に機械学習モデルに用いるデータセットを用意します。この際には、実際に運用で機能するように適切に学習データやオフライン検証用のテストデータを分割する必要があります。運用で利用する場面での精度が適切に検証できるように、書き方が異なるもの、ノイズの多いものなど性質の違う様々な図面を適切に用意する必要がありました。このプロセスも、コンペでのリーダーボードのスコアとローカルのスコアの相関があるようにデータを分割して分析、検証するプロセスに似ているなと思いました。 そして、用意したデータセットでベースラインとなるモデルを学習し、精度や誤検出結果を観察します。今回は初期の開発だったこともあり、以降の実験が効率的にしやすくなるようにパイプラインの整備をしていくことに力を入れたりなど、こちらもコンペの初期の取り組み方に似ている部分はありました。そして、コンペと同様に観察した精度や誤検出結果をもとに、次の実験や精度改善のためのアイデアを整理して、チームメンバーからのレビューを受け、継続的に改善に取り組みます。 このようにキャディの仕事とコンペの仕組みは近い部分があるので、キャディ内にもコンペのような皆で協調的にモデルを改善する仕組みやプラットフォームができたらいいなと思いました。また、面白いデータやタスクも多いので外部向けのコンペを開催できたら良いなとも思いました。 Microservicesについて 続いて2022年2月15日に開催された社内勉強会、刈部からのプレゼン内容をまとめております。 https://speakerdeck.com/caddi_eng/20220215-karibe-microserviceskohi Microservicesとは何か? 刈部: Microservicesとは、1つのアプリケーションを動かすために、小さなサービスの集合体を設計して開発するアプローチを指します。 正確な定義はなく、各社で採用して設計・提供しているのが実態です。 メリットとしては、サービスが分かれているのでデプロイしやすく、テクノロジースタックが分けてあるので適材適所で使え、チームを分けて開発ができるので組織的な柔軟性がある点ですね。 デメリットは、複雑になりやすいことです。 設計パターンとしては、API Gateway、Choreography、Microservice Chassis、Service Discoveryと4つあるので、順番にご説明していきます。 まずAPI Gatewayですが、これは外部からリクエストがあるとGatewayがすべて受け付け、Backendをたたいて1つのAPIとして提供するものです。 バックエンドの負荷が上がらないように流量制御したり、似たようなリクエストが来たらキャッシュで返したりすることも担いますが、パフォーマンスやスケーラビリティに課題を残します。 Choreographyは、非同期のメッセージによってサービス間で協調して1つのビジネス的操作を実現するものです。 メッセージブローカーが、クラインアントからの要求を関連するサービス系と処理を行うワークフローを実現するのですが、サービス数が増えると、複雑なピタゴラスイッチになってしまう懸念はあります。 Microservice Chassisは各サービス共通で必要な基盤を提供するもので、サービスの新規作成が早く簡単になるのが特徴。 Service Discoveryは、サービスのネットワークロケーションをクライアントがどうやって見つけるのか?という点についてです。 普段キャディではk8sを使用しているため、開発者はあまり意識する必要はありません。 実際の企業ではどのように使われているのか? 刈部: 例えばUberでは、プロダクトやプレゼンテーションのレイヤーで、各サービスでMicroserviceのような位置付けて持っており、API GatewayはEdgeレイヤーにてAPIを構成・提供している形になっています。 またNetflixでは、Orchestratorのパターンとして採用しています。これも非同期による設計パターンで、メッセージブローカーではなくサービスを置き一連の非同期処理を管理してあげるものです。 Wantedlyでは、Microserviceの開発ガバナンスを高めるために共通ライブラリを開発しています。まさにこれはMicroservice Chassisです。 Service Discoveryは、Netflixではeurekaを自作しています。 ここまでいくつか例をあげてきましたが、Micorserviceは名前の通り、どれも複数サービスを協調させて機能提供するものです。 複数のサービスを協調させて全体として1つの系を成すので、ソフトウェアの概念におさまるものではありません。 そこで次に、実社会ではどういう仕組みなのか?を例をあげてみたいと思います。 実社会ではどういう仕組みになるのか? 刈部: こちらはファミレスの写真ですが、ここにもMicroserviceが隠れていると言えます。 スプーンやフォークは食器メーカーが作ったもので、食材もここのファミレスの会社が調達して料理しているだけですよね。水も水道局から、イスなどは家具メーカーから調達されています。 このように1つの料理をテーブルに並べるだけでも、バックエンドは様々なサービスを安く提供できることに集中していて、調達を得意な業者に任せています。 こうした社会的分業は、労働が専門化することで作り出されるものです。現実社会も専門性を組み合わせるものですよ、と考えることができますね。 キャディのビジネスモデルをMicroservicesの視点で見ると キャディのビジネスモデルも同様で、発注者と加工会社の間にキャディがいて、API Gatewayで見たものと同じく内部構造を隠しているように見えてきますし、加工会社に発注して最終的には顧客に納品しています。 パターンについても、顧客から発注というリクエストに対して、複数の加工会社(サービス)に発注し、製品を納品する(レスポンスをする)プロセスとなります。 キャッシュについては、キャディで抱える在庫のようなものと考えることができ、顧客から似たような発注があれば、キャッシュから引き当てることでリードタイムを短縮できます。 Choreographとしても、メッセージを製品と置き換えることができそうで、キャディを介さず加工会社同士でサプライチェーンを構築ができる可能性があります。 ただ、サービスの数が増えると複雑さが増してピタゴラスイッチを組み立てることになるので、Netflixの取り組みを踏まえるとキャディもChoreographyよりもOrchestrationを採用した方が良さそうということも見えてきます。 Microservice Chassisについては、共通する機能を提供することで加工会社共通のプラットフォームに相当すると考えられます。 これは在庫情報や不可状況のObservabilityを上げることにもつながると考えています。 おわりに 今回は、2回分の社内勉強会の内容をまとめてみたのですが、いかがでしたでしょうか。 記事内容をご覧いただいて、興味持った方は こちら にて、お話する機会をご用意しておりますので、ぜひご活用ください。 また社外向けに一般公開するイベントに関する情報は、 キャディのconnpass をご用意しておりますので、こちらもご登録いただけますと幸いです。 最後までお読みいただきまして、ありがとうございます。
こんにちは。ソフトウェアエンジニアの江良です。 普段は Web アプリケーションのコードをせっせと書いて暮らしているのですが、AI Lab の誕生に伴い、 機械学習 を専門とするエンジニアと協業する機会も増えてきました。 今回は、 機械学習 の研究開発プロジェクトで導入した Streamlit という フレームワーク について紹介しようと思います。 Streamlit とは Streamlit は Python で Web アプリケーションを作成するための フレームワーク です。 機械学習 エンジニアやデータサイエンティスト向けに設計されており、 Python のコードを数行書くだけで、可視化のためのカスタムアプリケーションを簡単に構築することができます。 streamlit/streamlit: Streamlit — The fastest way to build data apps in Python 「 機械学習 のモデルを評価するためのデモ用のアプリケーションを作りたい」「あくまでデモ用なので、労力はできるだけかけずに済ませたい」という今回の ユースケース にぴったりのツールだったため、導入してみることになりました。 Streamlit を触ってみよう Streamlit は pip でインストールすることで使えます。 pip install streamlit 詳細は 公式ドキュメント に譲りますが、ほんの数行のコードを書くだけで簡単にグラフィカルなアプリケーションが実装できます。 import streamlit as st x = st.slider('Select a value') st.write(x, 'squared is', x * x) 作成したアプリケーションは以下のコマンドで起動できます。 streamlit run main.py チュートリアル に載っている 30 行ほどのコードを書くだけでこんなアプリケーションも作れます。 Streamlit の威力をなんとなく感じていただけたでしょうか? Streamlit アプリケーションを公開しよう Streamlit Cloud の紹介 Streamlit で書いたアプリケーションは Streamlit Cloud というサービスを使うことで簡単に ホスティング できます。 作成したアプリケーションを全世界に公開することもできますし、有料の Teams プランに加入すれば社内だけに限定して公開することもできます。 公式ドキュメント を参考に Streamlit Cloud にサインアップし、 GitHub リポジトリ 、デプロイしたいブランチ名、main ファイルのファイルパスを入力して少し待つだけで、簡単に Streamlit アプリケーションをデプロイできます。 アプリケーション、インフラ構成の紹介 次に、今回作成したアプリケーションの構成について触れていきます。 構成については、ざっくり以下の通りです。 訓練済みのモデルは GCS に配置し、推論時に取ってくる 推論に使うデー タセット は BigQuery から取ってくる GCP の各サービスにアクセスできるよう Service Account を作成し、credential を参照させる Streamlit の Secrets Management という機能を使うと、credential のようなセンシティブな情報を安全に保存し、Streamlit のアプリケーションから 環境変数 越しにアクセスさせることができます。 Secrets management - Streamlit Docs Connect Streamlit to Google BigQuery - Streamlit Docs st.secrets を参照するようにアプリケーションのコードを書き換え、 def get_credentials(): if "gcp_service_account" in st.secrets: return service_account.Credentials.from_service_account_info( st.secrets["gcp_service_account"] ) else: return None Streamlit Cloud の「Advanced settings」に指定することで Secrets を使用できるようになります。 Streamlit Cloud のハマりどころ ここまで Streamlit、Streamlit Cloud の良いところを紹介してきました。 この勢いのまま Cloud 上にさくっと持っていけると良いのですが、 Streamlit は2018 年に公開されたばかりの比較的新しい フレームワーク のため、実務で利用するにあたってはいくつか落とし穴も存在します。 メモリが足りない Streamlit Cloud は使用可能なメモリに制限があります。 (2022/02/14 現在で、Free プランなら 1G、Teams プランなら 3G が上限となります。) Troubleshooting - Streamlit Docs この制限を超えてしまうと、 Oh no. のメッセージとともに Streamlit アプリがクラッシュしてしまいます。 この問題を解決するには、Streamlit のキャッシュ機能を正しく活用することが鍵になります。 キャッシュ機能は、キャッシュしたい処理の関数に @st.cache を指定することで設定できます。 @st.cache def load_data(nrows): data = pd.read_csv(DATA_URL, nrows=nrows) data[DATE_COLUMN] = pd.to_datetime(data[DATE_COLUMN]) return data Optimize performance with st.cache - Streamlit Docs キャッシュが効かない ところが、この @st.cache を使用するだけでは問題が解決しないケースがあります(つらい)。 Experimental cache primitives - Streamlit Docs によると、 @st.cache の単一 API であまりにも多くの ユースケース をカバーしようとした結果、処理が遅く複雑になってしまったとのこと。 この問題を解決するために、streamlit v1.0 から @st.experimental_singleton と @st.experimental_memo という API が追加されました。 クレデンシャルオブジェクトなど、一回だけ初期化したいもの(かつセッション間で共有されて問題ないもの)については @st.experimental_singleton が使用できます。 @st.experimental_singleton def get_credentials(): if "gcp_service_account" in st.secrets: return service_account.Credentials.from_service_account_info( st.secrets["gcp_service_account"] ) else: return None データを返す関数には @st.experimental_memo が使用できます。 @st.experimental_memo def load_data(nrows): data = pd.read_csv(DATA_URL, nrows=nrows) data[DATE_COLUMN] = pd.to_datetime(data[DATE_COLUMN]) return data これらの問題を解決したところ、無事デモ用のアプリケーションが Streamlit Cloud 上で動作するようになりました :tada: おわりに 以上、Streamlit と Streamlit Cloud の紹介でした。 Streamlit をうまく活用できたおかげで、 機械学習 のモデルの評価も順調に進み、現在は「 機械学習 を実際のアプリケーションにどう組み込むか」を議論するフェーズにたどり着くことができました。 (ぼくが担当したのは Streamlit Cloud を使えるようにするための設定と若干のコード変更だけでしたが)研究開発の成果がきちんと「次」につながっていく場に立ち会えるのは嬉しいですね。 We're hiring キャディでは、 機械学習 のタスクを実装し、結果を可視化して高速に改善することで、モノづくり産業のポテンシャルを解放するプロダクトを開発するエンジニアを募集しています。 ところで、ぼくの専門はバックエンドエンジニアです。こちらのご応募もお待ちしております。
キャディでエンジニア採用を担当しております片渕です。 今回は2022年2月8日に開催されたFindy様のイベントに登壇させていただきました、AI LabのMLEテッ クリード の河合( @vaaaaanquish )からの トーク 内容を中心にまとめたイベントレポートをご紹介していきます。 [ toc ] 以前お互いにマネジメントする側・される側の関係でしたが、今その時の経験がいきていることなどありますか? 河合: (キャディ入社前の前職で上司だった)西場さんも覚えていると思うのですが、壁打ちを毎日やっていました。 技術選定やマネジメントなど、体力と精神力をとても使いますが、途中から本気でこれをやっていて気がついたことがあります。 それは、こちらが本気になっても並走できる人がいるんだなと思ったことで、まだまだ自分も伸び代があるなと、本気で自分も誰かからぶつけられても大丈夫なよう武器をしっかり持ち、旗を立ててその旗をふれるようなリーダーシップを発揮しようかなと思うようになりました。 あと、西場さんとの対話をスムーズにするためにビジネス書を読むようになりましたね。 壁打ちとは具体的にどのようなことをしていたのでしょうか? 河合: 自分のアイディアを聞いてもらって、西場さんに「それはなぜ?」と聞かれ、理由を回答していくことを繰り返していくものです。 思考が深掘りを繰り返すハードなものでした。 (西場さんが1on1で自分が退いても大丈夫な環境作りをしていた事に対して)キャディでも採用をがんばっているし、優秀な人と話をしているのですが、この人がキャディに入社してきたら私もプレッシャーがすごいな、面白い仕事を用意しないとなと感じることがあります。 そうしたノウハウをNoteにまとめたいですね。 これまでどんなキャリアを歩んで来ましたか? 河合: 開発主軸でずっとやってきました。 学生の頃からMLエンジニアのバイトをやっていて、Sansanに1社目で入って、R&Dの部署で開発一辺倒でやっていました。 画像認識の アルゴリズム を作って、本番環境に乗せて運用することですね。 2社目ではチームリーダーもやっていましたが、基本は 機械学習 のモデル開発をずっとやっていました。 M3に入ってからは、かなり道が変わって、チームとかプロジェクトのマネジメントをやってきた、プロダクトとか事業を見るようになって、今ではキャディでサービス開発や会社の未来を考えながらツールや技術、採用、体制作りをフォーカスして動かしています。 なので、開発の時間は減っている感じで、キャリアが少し変わってきたと思います。 現職に転職したきっかけは? 河合: 技術に興味あることをやっていたのでカジュアル面談をさせてもらい、1年くらい経過してから入社しました。 転職においては、ビジョンやミッションを大事にしているところを見ていますね。 これがとても大事と分かっているし、キャディはそれを大事にしていましたので。 M3で色々と視野が広がったので、それを出し切れるような、MLテッ クリード という立場でチームを立ち上げるとか、AIビジネスをキャディは当初は持っていないのでそれを加速させるところですね。 M3で学んだことを、何もないところからトライできるところが、自分の成長に良さそうと思って転職をしました。 エンジニアキャリアの中で悩んだことは? 河合: M3の時は西場さんがいたので、キャディの話を聞き始めたところから、チームから外れた仕事をしていました。 西場さんがやっていた仕事が私にやってきて、とても楽しかったです。 だけどRustとか自分の興味あることはあまりできなかったので、悩みポイントではありましたね。 どっちがいいのかなと、最終的には技術を私は選んだわけですが。 技術を選んだ背景は? 河合: ビックウェーブに乗るイメージですね。私自身MLは10年くらい経験があり、その中で様々な言語で書いています。 Sansanだと C# だったり Python 導入したりとやってきました。 プログラミング言語 によるブレイクポイントはあまりない状況が続いていたと思います。 その中で、可能性がありブレイクポイントになりそうなのがRustと感じて、ここにかけてもいいかな、これは流行るだろうというところに乗っかった感じですね。 悩みが吹っ切れたタイミングありましたか? 河合: 尊敬しているエンジニアやプロダクトマネージャーなど、いろんな人に話を聞いていく中で整理されたイメージです。 PFNでエンジニアをやっていた方に、メンタリングのような形で、「お金など全ての問題が解消されたときに何をやりたい?」と聞かれて、問題はいろいろあるけれど本当は何をやりたいのか?ということを考えさせられる機会がありました。それが一番の”きっかけ”ではあったと思います。 最後は、決めだとは思っているので、えいやで決めたところはありますが。 もし今、学生だったらどんなキャリアを選びますか? 河合: 私は実は学生時代に未練があって、研究をもっとちゃんとやりたかったんですよね。 国際学会に論文を出していたけど落ちた経験が苦く残っていて、今思うともっと上手にできたなと感じているので、研究をがんばると思います。 その先は、研究を活かせるエンジニアリングの会社に就職するようなところかなと。 今後もエンジニアの裾野は広がると思っていて、コンピューターサイエンスや 機械学習 の専門性を高めることがより重要になると思います。 そういった意味でも多分研究に打ち込むだろうと思いますね。 変化の早い業界にいる中で技術面や精神面で心がけていることは? 河合: (西場さんが勉強し続けると答えたことに対して)勉強は大事だと思います。 私は最近は、技術書を執筆したり、グローバルをキャディは目指しているので英語を学習したりしています。 他の回答だと、打っても壊れないものを探したいですね。西場さんとの壁打ちで味を知ってしまったかもしれないのですが、本気でぶつかってもいい人がいるのは、自分が成長できるので、周りにそういう人が集まるように行動しています。 自分より必ずしも技術力などレベルが高い人をというわけではなく、本気で意見しても壊れないメンバーと対話すること。 他の会社とMeetyで1on1をしているが、そういう機会を増やすの大事。 自分の中では今それが熱いです。 MLエンジニアにとって大企業とベンチャ〜の違いは? 河合: 2社目はYahooだったのですが、大きな会社のインフラには一定の正解があったりします。 小さい会社では扱えないデータサイズ・品質・関わる人の数が圧倒的に違うので、技術力は同じだったとしても、MLエンジニアの働き方の違いは出てくるなと思います。小さな改善で大きな成果を生めるのも違います。 どっちがいい・悪いではなく、経験しておくと良いと思います。逆に小さい会社では、大きな改善を自ら考える必要があります。インフラのスケールも自ら考え自ら手を動かします。 そういう意味でも、キャディはいいですよ。笑 役員との距離も近いですし、対等に開発を主導できます。 これからチャレンジしたいことや今後のキャリアの方向性は? 河合: 打倒、西場です。笑 西場さんは、かつてM3でAIチームを立ち上げていましたが、私は現在キャディでAI Labは立ち上げ中です。 西場さんの立ち上げを圧倒するようなスピードで成長するのが目標だし、キャディAI Labの目標でもあります。 特に自分にしか出せないところ、技術的な知識や複数の企業経験、 Twitter フォロワーも多いので採用で圧倒的な成果が出せるのでは・・と思っています。笑 転職をすることについてどう思いますか? 河合: 私は転職に明るい業界ですよということは常に言えるようにしたいと思っています。 人と企業とはライフステージによっては合う・合わないはあると思うのですが、業界全体でそういった意識を持って、より良い人が入って来やすい環境にできればと常に思っています。 キャリアを重ねる上で、お金や仕事、生活のバランスはどう推移させましたか? 河合: M3に入るまでは、とにかくお金のところは上げていこうと思っていました。 最近はそれが落ち着いてきたかもしれないですね、それは子どもがうまれたことが大きいかなと感じています。なるべく未来を作る仕事をしたいなとよく思っています。 社会人になってからエンジニア以外の仕事でやってみたかったことは? 河合: 非常に難しい質問で、エンジニアしか思いつかないんですよね。Youtuberとか人生楽しくなりそうで良いなと思います。リスクはありそうですが。笑 ML以外で興味がある技術ありますか? 河合: 直近だと、WebAssenblyをやっています。これは一部書籍執筆のためにやっているのですが面白いです。 これまでのWeb技術の概念を変えることができるとまでは言いませんが、投資してもよい技術の1つだと思います。 WebAssemblyの代替技術も出てくるとは思いますが、この概念を知ることは重要だと思います。 機械学習 の応用事例も増えていますしね。 仕事で行き詰まった時のリフレッシュ策は? 河合: 行き詰まってもやるしかないとは思っているのですが、強いて言えば娘と遊ぶとかそういう話になりますかね。 あとは Twitter ですね、これは気持ちが切り替わるのでいいですよ。笑 本気で打っても壊れないエピソードを教えてください 河合: 来週までにこれやっておいてくださいと西場さんに言うと、なんでもやってくれたところです。100個くらいの回答が返ってきます。 次の日くらいまでに考えてくれたりとか、毎回そういう感じなので、継続的なそうした対応が思い出に残っているエピソードかなと感じます。 どんな会社に魅力を感じるか? 河合: ビジョン、ミッションがしっかりしている会社ですね。 ビジョン、ミッションは誤解されやすいですが、実はエンジニアとして自分が働きやすい会社かどうかの1つの指標になると思います。 実例を出すと、その会社の評価基準がそこにあったりもするのです。 我々が働いているのはあくまで 営利企業 なので、会社のビジョンやミッションに対してちゃんとできているか?というところは非常に重要です。 「技術力が高い」という指標があったとして、ビジョン・ミッションと少しズレた技術力を高め評価されないといった不幸なシーンもよく見ます。 会社が目指している姿を自分に重ねられるかは重要だと感じています。 最後に いかがでしたでしょうか? 河合からもイベント時に案内がありましたが、カジュアルにキャディのエンジニアと話をしてみたい!という方は、 こちら より申し込んでいただけますと幸いです。 また、イベントに関する情報は、 キャディのconnpass がありますので、こちらも登録いただけると嬉しいです。 最後までお読みいただきまして、ありがとうございます。
物理のモノづくりと比べたソフトウェア開発 物理的なモノだからこそ難しい事: ここまでは 品質の難しさや取り組みの重要性 に関して語ってきましたが、物理的なモノを扱っているからこそ発生するチャレンジを紹介します。 スケールの難しさ: ソフトウェアの世界では処理能力を上げるためにサーバを追加してHorizontal Scalingしたり、マシンスペックを上げてVertical Scalingしたりできます。 ユーザ数やシステム負荷に応じ動的に調整ができます。 しかし、製造するのには材料を購入したり物理的に加工する時間が必要です。 そのため当然加工機も必要ですし在庫する倉庫も必要です。 クラウドと違い簡単に固定費を下げる仕組みが無いため、需要予測が非常に大切になってきます。 リカバリーの難しさ: サーバの障害アラートが上がるとログを遡って事実確認に入るのと同じく、キャディでお客様から不良の連絡があると事実確認を行います。 遠隔で見れるサーバログと違い、事実確認だけのために実物の配送が必要な場合もあります。 製品が大きすぎて検査員を動かす方が製品を動かすより経済的な場面もあります。 修正もサーバに新しくデプロイするのではなく、物理的に追加加工をしたり、出来ない場合は再制作になります。 ソフトウェアの再制作はファイルコピーで終わるものの、物理的なモノを複製するのにはお金と時間が必要です。 そして当然ですがロールバックも出来ないため非常に神経を使います。 アジリティ向上の難しさ: ソフトウェア開発ではロールバックを可能にしたり、git branch毎にコンパイルする技術を導入する事で、よりリスクを小さく早く取りやすくなってきました。 設計変更も小さくデリバリーできるようになりました。 しかし、CI/CD同等の技術はスケールの難しさの関係で経済的に実現しにくく、リカバリーの難しさもあり小さくデリバリーする事自体が難しいです。 品質は物理もソフトも同じに扱える ソフトウェア開発も昔は簡単にロールバックできない、スケールしにくい、アジリティ上げにくい、と数多くの課題に悩まされていました。 ロールバックの仕組みが無いため「最後のチェック」を強化する傾向は製造業と同じくありましたし、デプロイ作業する度に本番環境に直接SSHして汗かきながらコマンドを打っていた時代もありました。 でも、Cloud Native時代の今となっては全く事情が違い、スケールもデプロイの再現性もロールバックも全部実現できる世界になってきました。 物理的なモノづくりとソフトウェアは具体のプロセス観点では違いがあるものの、品質観点から見ると意外と似ている部分もあるかなと思います。 物理のモノとソフトウェアを飛行機や空飛ぶ乗り物の開発文脈で紹介します。 飛行機といえば安全性を意識しますよね。 機内の席の広さや窓の大きさも重要ですが、安全に目的地までたどり着けることが大前提となっています。 実際は法律上数多くの安全性規定があるわけですが、どのように「安全性」という品質要件を考えられているのか深ぼってみましょう。 我々が乗る飛行機は加工部品、電子部品、ソフトウェア、パイロットが組み合わさって初めて機能する仕組みです。 全て重要な要素ですが乗客からしたらそこの詳細は関係なく、安全な空の旅が出来るかが重要で、総合的な結果が一番大切。 仮にジェットエンジンが故障しても嬉しくはないけれども、安全にたどり着けることに越したことはないです。 全ての要素がコントローラブルでは無い中でも、最終的な安全性指標として事故の確率が設けられているのが航空宇宙業界が成熟しているという主張かもしれません。 半世紀以上品質と向き合ってきた業界から、我々純粋なソフトウェア開発者としても参考にできる要素はいくつかあるのではないでしょうか。 モジュール分解しているのはソフトウェアも似ている 安全性というのは確率論なので数値化しやすく見えますが、事故要因は無限大に想像できますよね。 ジェットエンジンに鳥が巻き込まれたり、部品が劣化して壊れたり、そもそも寸法違いの部品が組み込まれていたり。 マニアックな例だと宇宙からの放射能で組み込みソフトの変数が稼働中に変わってしまうとか。 あらゆるリスクを洗い出して発生確率を計算します。 さて、ここで2つのモジュールが同時不具合起こして初めて乗客にインパクトがある事故になる場合、事故率を以下のように表現できるでしょう。(厳密には独立変数前提で記載しています) P(overall) = P(A and B) = P(A) x P(B) 一方で、いずれかのモジュールの不具合により乗客インパクトがある場合は: P(overall) = P(A or B) = P(A) + P(B) – P(A) x P(B) なるべく安全性を上げる上では両方のモジュールが不具合を起こして初めて事故につながるようにレバレッジしたくなり、モジュールに分解して極力独立変数になるように設計する事を意識する方向性になるかなと思います。 書いてみれば当たり前ですが、飛行機の品質を総合的に評価する上で全体を複数の独立したモジュールに分解し、モジュール単位でリスク分析をする事で全体の確率を計算しやすくしています。 機内のビデオ配信システムとエンジン制御のシステムが完全に独立している設計になっているからこそ、ビデオ配信システムはランタイムエラーを起こしても機体の安全性には影響与えない保証があります。 重要なシステムの冗長性を担保するために複数台バックアップ用のシステムを設けることもあります。 ソフトウェア開発する上でもモジュール同士を粗結合にすべきか判断する上で、エラーのトレーサビリティやリスク分析のしやすさも考慮すべきなのかもしれません。 品質考える上でユーザもシステムである 飛行機を設計する上で、全てが広い意味でのシステムとしてモデリングされます。 エンジンも、構造物も、ソフトウェアも、パイロットもシステムです。 何らかのインプットを元にアウトプットを出すものは全てシステム。 そしてシステム毎に不具合の確率や不具合時の挙動を定義していくと、機体だけではなく空を飛ぶ仕組み全体としての不具合の確率が出せます。 ここでパイロットが全体の一部であり、システムでもあるというポイントが特徴的かと思います。 パーフェクトな人間はいないのでパイロットというシステムを定義する上で「◯◯レベルまで訓練したパイロットならX%の確率で操作ミスをする」という前提を設けて、飛行機全体の不具合の確率を計算します。 安全に到着する目的に向けてパイロットのトレーニングを強化する施策もあれば、より操縦を自動化する方法もあります。 ウェブアプリのユーザ幅はパイロットよりは広く定義も曖昧ですが、ユーザに価値提供出来るかはソフトウェアの技術的要件だけではなく、ユーザの思考にも左右されます。 ユーザをトレーニングすべきかそもそも対象外にすべきかはプロダクト次第ですが、ユーザのペルソナや前提知識をしっかりと言語化する事で初めてユーザという「システム」を品質の指標計算に入れられます。 純粋なソフトウェアの世界でもユーザを全体システムの一部だと考えると、最終的に提供したい価値がより言語化できるのかもしれませんね。 品質は継続的に改善する事で単発の何かではない. DevOpsもまさにこれ 航空業界では 「the laws are written in blood」 と言われることもあります。 全ての事件に対して調査が行われ、根本原因が特定され、再発防止対策実施までサイクルが徹底されています。 法律や規定の数が凄まじい事になっていますが、良い意味で改善が続けられてきた証拠とも言えるでしょう。 過去の失敗を元にPDCAを回し続けてきたからこそ、飛行機の事故率は交通事故より低い実績があります。 キャディのソフトウェア品質組織 今の体制: 2022年2月現在、いわゆる「品質」や「QA」や「QC」の専任人材も組織も無いです。 だからといって製造業でいう検査工程が無いわけではないですが、逆に開発者が自らプロセス改善含めて改善に投資する文化になっています。 専任がいないため劣後されるものも多く、最終的なあるべき姿とはいえないかもしれませんが、実は創業から四年間QA組織を意図的に作って来なかった背景があります。 早く専任QA組織を作ると機能を開発する人間として「一旦QAに見てもらう」事が頭を横切ってしまいますよね。 この気持ちは分からなくはないです。 しかし、この思考が根付いてしまうとQAが「テスター部隊」と暗黙知かもしれないが認識されてしまい、本質的にユーザ価値を最大化するための改善活動にリソースが避けられなくなります。 ある意味、開発組織が小さいうちに、皆で多少の障害を起こしたり転びながら成長していくフェーズかなと思っていました。 もちろんテスターのロールを担うリソースは確保していますし、現時点では開発組織も視座高く価値は出せていると思いますが、ぼちぼち限界に近づいています。 開発組織も50人超えて、今後さらに拡大していく必要があるためキャディの開発組織を一段レベルアップさせる施策が必要だと感じています。 ここまで自ら品質と向き合ってきた開発組織だからこそ、最強の品質組織をこれから作れるのではないかと思います。 テスト自体は滑り止めでしか無い 本質的には「検査」でミスに気づくのではなく、そもそもミスが起きないような仕組み作りやプロセス改善が組織の積み上がる資産になると信じています。 最後に意図的にこぼしているものを「検査」で拾うのは健全ですが、意図せぬものがそこで検知されると上流工程の方に目を向けるべきです。 このためにはDevOps的な動きも必要ですし、テストやプロセスの自動化も必要ですし、開発者の目線揃えも重要です。 車輪の再発明も減らして共通化できる部分は横断組織で持つ事で、機能の品質を向上する事もできます。 また価値を最大化する上ではより大胆な施策に挑戦する事も重要で、そこの仕組み作りという意味で、7月にプラットフォームエンジニアングチームを作りました。 開発方針の判断材料として品質指標が重要かも 現状ではSLI・SLO運用はしていません。 厳密に価値や安定性を数値化出来ていないというのもあるのですが、何らかの形で今の品質を表現する判断の軸を設ける事で皆の目線を揃えやすくなるかなと思います。 価値自体を分解し、中間指標にする事で。 今後に向けて 価値提供を一緒に考える仲間を探しています 正直、専属の人を置くことが重要なのかも分からないです。 明確に品質という領域に注力するリソースは必要ですが、それがどういう形でどういうロールなのか、日々の動き方がどうあるべきかまだ分かっていないです。 色々な事例を参考にさせて頂いていますが、一緒に挑戦してみたいなと思う方は TwitterのDM または 応募ページ からぜひカジュアル面談をでも設定下さい。 現状では専任のテスター部隊を求めているのではなくて、ユーザ価値を届ける上でのコスパを考慮し、プロセス改善からテストまでお任せできる人です。 全工程のステークホルダーが価値提供にフォーカスして、コスパやリスクトレードオフの判断ができる組織を目指しています。 ドメインエキスパートの仲間も探しています パフォーマンスやセキュリティ等、普段非機能要件とまとめられる領域も暗黙知の機能要件です。 DevSecOpsという表現がバズる理由も分からなくは無いのですが、これも品質基準の言語化の一種であると考えています。 このような新たなムーブメントを社内で作ったり、専門知識を活かしたい方も是非お声がけ下さい。 絶賛仲間を探しております。 The post 製造業とソフトウェアの品質 Part 2/2 appeared first on CADDi Tech Blog .
キャディでエンジニア採用を担当しております片渕です。 今回は2022年1月26日に開催された MIDAS TECH STUDY に登壇させていただきました、AI LabのMLEテッ クリード の河合( @vaaaaanquish )からの トーク 内容をまとめたイベントレポートになります。 下記のように、イベント全体の動画も公開されておりますが、ブログとしてざっと発言内容を確認したい方は、このままお読みいただけますと幸いです。 https://www.youtube.com/watch?v=YunfRd33pwE [ toc ] 最近起こった変化 コロナ禍の影響で、新しく制度ができたとか組織の変化などありましたでしょうか? 河合: 採用広報のスタイルは変わったと思います。AI領域だと学会や勉強会、論文の読み会などは採用の主軸となりましたね。 以前は、良い人に出会ったらそこからのリファーラルが多かったのですが、最近は減ってきている感じがします。カジュアル面談やWeb広告も出す方向性が変わったなあと感じています。 ワークスタイルの変化としてはいかがでしょうか? 河合: キャディもリモートが多いので、ほとんど会ったことがない人たちも多いですね。地方在住者などリモート前提で採用している人もいますので、この人とは実際に会う機会あるかなあと思いながら仕事しています。リアルで合わないまま仕事して、そのまま転職してしまう・・・というのもありそうだなと。 オンライン中心の仕事で苦労していることはありますか? 河合: 信頼関係構築という意味で、ビジネス理解は課題に感じていますね。特にエンジニア同士はオフラインで集まるのは簡単なのですが、会社の全大会で話があるようなビジネス戦略の話と開発の話はちょっと遠い・・みたいなところがあります。 社内コミュニケーションツールの変化などについてはいかがですか? 河合: slackでのコミュニケーションはさかんですね。Jiraを使って、最近ではBiz側にも スクラム を使おうという動きは増えてきたていますが、エンジニア側が使うツールは変わらないかなと思います。 それから社外という意味では、Meetyが流行ることは予想していなかったですね。カジュアル面談にWelcomeなエンジニアが多いのは驚気でした。 組織マネジメントについて 副業などは、どのように考えていますか? 河合: 自分の裁量でやってくださいというスタンスですね。私が所属しているAI Labでは、比較的まとまったタスクを切り出しやすいので、副業で関わってくる外部の方には依頼しやすいです。 また、面接官をしていて面接の時に副業okかどうか、というのを確認してくるケースが多い印象はあります。 ベンチャー 業界だと、自分はこういうこともやっていきたいというものを複数持っている人もいますので。 研修や人事制度に変化があったケースはありましたか? 河合:そのあたりは変化はしていないのですが、組織設計の意味で外部に切り出しやすいタスクはいつも考えています。やはり副業する人も増えていますからね。 河合さんは会社に出社するケースはどのような感じですか? 河合: キャディに転職してから、入社時研修で少し出社した程度で、今はほとんど出てないですね。なのでコミュニケーションが希薄になるので私は意識的に、1on1をわらしべの形で草の根的に色々な人と積極的に話をするようにしています。 同じチームメンバーとの1on1はどのくらいやっていますか? 河合:1on1の数は多くなっちゃいますけど積極的にやってます。やはりオンラインだと難しいところもあるので、1on1で情報収集しないといけないなあと感じているんですよ。 採用について 報酬面に関して何か意識していることはありますか? 河合: キャディでは給与テーブルを公開していてるので、それを面接やオファー面談などの交渉でも使うことがあります。こういう人はジョブグレードはこうなりますよ、という感じに。 またSOとか、そのようなものも含めてお互いに交渉して話することをやっています。 技術面接の時点で話すケースもあれば、最終のオファー面談の時もありますね。このあたりは本人のWillもあり、柔軟にやっていく必要があると思っています。 副業や フリーランス の採用に関しては、いかがでしょうか? 河合: AI Labでは業務委託でお願いしているケースも多く、課題も切り出しています。ただ、会社が目指すビジョンを一緒にやるところに魅力を感じてくれないと、そういう人を社員に推すようなことはしていませんね。 私も、キャディと面談しはじめたのは2021年の2月ごろからで、何回も面接して、TechやEM、BizやHRなどたくさんの社員と話をさせていただきました。一緒に働きたいと思ったら面談を複数セットしてくれるのはありがたいですね。 最後に、会社紹介などをお願いします。 河合:エンジニア採用は積極的にやっています。国内外問わずですね。 むずかしいということをおもしろがる、というのが私も好きで、キャディではIT改革が起こっていない製造業の課題(調達や加工など)ものづくりと IT技術 を組み合わせるところの難しさがあります。 そこに、いろいろなチャレンジをしているエンジニアがいますので山を登る難しさが楽しいという人は、ぜひカジュアル面談したいですね。 最後に いかがでしたでしょうか? 河合からの話にもありましたが、カジュアルも含めキャディのエンジニアと話をしてみたい!という方は、 こちら より申し込んでいただけますと幸いです。 また、イベントに関する情報は、 キャディのCONNPASS がありますので、こちらも登録いただけると嬉しいです。 最後までお読みいただきまして、ありがとうございます。
キャディでエンジニア採用を担当しております片渕です。 今回は、エンジニアの社内勉強会「STUDDi」にてプレゼンがありました一部の情報(2ネタほど)を、レポート形式でお伝えしたいと思います。 STUDDIは、エンジニア全員が持ち回りで発表機会があるものですが、入社してくる方も増えてきたこともあり まだ一度もスピーカーになっていない方(新しく入られた方)の発表する場として優先する 好きな技術について思う存分語ってOK という方針でやっております。 OAuth 2.0とOpenID Connect 認証と認可の違い OAuto2.0とは? OpenID Connectとは? Kleinでの認可・認証プロセスのフローについて Authorizationヘッダーの中身 認可リクエストの流れ 悪意のある乗っ取り攻撃を防ぐために 地図情報システム入門 緯度経度だけでは一意に場所は定まらない 一意に場所を定めるために 位置情報のデータ形式 地理情報データを解析するとき Web地図描画 おわりに OAuth 2.0とOpenID Connect 今回はまず2022年1月18日に開催された社内勉強会、先山からのプレゼン内容をまとめております。 https://speakerdeck.com/caddi_eng/20220118-sakiyama-oauth2-dot-0-and-oidc-falsekohi 認証と認可の違い 先山: 今回のアジェンダとしては認証・認可の話から、OAuto2.0やOpenID Connect、アクセストークンや認可リクエストについて、説明していきたいと思います。 まず、認証と認可の違いから。 認証とは通信先の相手が誰かを確認するところで、あなたは誰?を証明するためにログインするところを指しています。 一方で認可とは、権限があるかどうかを確認していまして、決裁できるとか特定の画面を閲覧できる権限とかですね。 今回のメイントピックはこの「認可」の話になりまして、最近だとマイクロサービスが流行り、認証や認可をどうしていくかは、アーキテクチャ的には重要になってきています。 OAuto2.0とは? 先山: 認証や認可に関するプロトコルとサービスについてはスライドにある通りなのですが、その中でまずOAuth2.0を取り上げてみたいと思います。 こちらは認証ではなく認可のフレームワークで、アクセストークンを使ってAPIアクセスするための権限を委譲することができるものです。ざっくりいうとアクセストークンの発行手順を策定したものです。 注意点として、2.0の内容ではセキュリティ対応が不十分なものがあります。これは後にOAuth2.0 Security Best Current Practiceの方で補完されています。もしOAuth2.0のドキュメントを読む場合はこの点に注意してください。 現在、仕様策定中のOAuth2.1ではOAuth2.0とSecurity Current Best Practiceの内容を合体させたような仕様となる見込みです。 OpenID Connectとは? 先山: OpenID Connect(OIDC)とは、OAuth2.0を拡張して作成されたフレームワークです。 OpenID Foundationが推進しています。ざっくり説明すると、アクセストークンに加えてIDトークンを発行する手順が書かれた仕様です。 最近、話題になっているトピックとしては、FAPI(Financial-grade API)という金融サービスに適応したものや、CIBA(Client Initiated Backchannel Authentication flow)とよばれる、何かのサービスが権限を要求してきた際に、そのサービス利用端末ではなく他の端末に権限を与えてもいいかどうかを通知してくれるものです。また、最近はeKYCも注目されています。 Kleinでの認可・認証プロセスのフローについて 先山: 認可リクエストはちょっとややこしいので、リクエストにアクセストークンを設定してリソースサーバーにアクセスする方法を説明します。 ここでは、過去のtech Blogでも取り上げられているCADDIのプロダクトの1つである Klein を取り上げて、認可・認証プロセスのフローを解説していきます。この図は今回の説明用に簡易的にしてあります。 まず、ユーザーがログインした時に認可リストを出し、認可サーバーはレスポンスとして認可コードを返します。 認可コードをAuth0の/oauth/tokenへリクエストし、そのレスポンスに含まれたトークンを使ってBFFにアクセスすることが可能になります。 アクセスしたいサーバーにAPIでリクエストする時に使うものが、アクセストークンとよばれるもので、JWT(Json Web Token)という形式になっています。 Authorizationヘッダーの中身 先山: JWTの中身はピリオドで区切られた3つのセクションに分類されます。先頭はヘッダー情報で、次にペイロード、最後に署名です。主に皆さんが参照するのはペイロードだと思います。ここがトークンの本文になります。認可サーバーが設定した値がここにセットされます。 署名はヘッダとペイロードをピリオドで繋げた文字列を署名処理した値が入ります。認可サーバーで秘密鍵により署名されたものなので、BFFサーバーは公開鍵でデコードすることで、認可サーバーから正規に発行されたものだと確認することができます。 上図で説明すると、リクエストに含まれたアクセストークンをBFFサーバーが検証します。先程言った公開鍵はJWKSという認可サーバーのエンドポイントから取得することができます。 デコードが成功したらBFFサーバーはトークンの中身をチェックします。例えば、有効期限が切れていたら401を、権限が足りなかったら403を返すことになります。 認可リクエストの流れ 先山: authorizeというパスが認可サーバー側にあり、クライアントはいくつかのクエリパラメータを設定してリクエストします。 青字の部分はセキュリティ関連のパラメーターです。後ほど説明するPKCEで一部紹介をします。黄色の部分は認可リクエストの方法や手順を表すパラメーターになっています。 response_typeがトークン取得のフローを定義します。codeを指定するのが最もよくあるユースケースです。それをAuthorization Code Flowと言います。それ以外にもtokenを指定するImplicit Flowがあるのですが、これは非推奨なので使わないでください。 また、Hybrid Flowというのもあるんですが、これはユースケースが特殊なので説明は省きます。 response_modeは認可コードをレスポンスのどこに詰めてクライアントに返却するかを指定します。今回はqueryなのでクエリパラメータに認可コードが設定されます。 redirect_uriは認可サーバーにアクセスした後に戻るURLを指定します。scopeは欲しい権限を指定するパラメーターです。例えば決済APIにアクセスする権限が必要な場合はscopeにpaymentを指定する、といった使われ方です。 認可リクエストについて説明をしましたが、実際にこの辺りの開発をするときは、どのような種別のアプリケーションが認可サーバーにアクセスするのかで考慮するべきポイントが変わってきます。 ただし、OAuth0を使うのであれば、それにあったライブラリを使えばいいので、あまり考えなくてもokです。 悪意のある乗っ取り攻撃を防ぐために 先山: ここでPKCEという仕組みについて説明します。 これはある攻撃の対策として、OAuth2.0で追加された仕様です。Auth0 SDKを利用していると目にすることがあると思いますので、説明します。 PKCEは、ピクシーと読みますが、これが利用されるようになったのは、ネイティブアプリに対してとある攻撃があったからです。 ネイティブアプリはCustom URL Schemeという仕組みがあります。これは例えばiPhoneで「caddi://hoge」というURLを指定すれば、インストールされたキャディのネイティブアプリを開くことができる仕組みです。 悪意ある人が別のアプリで「caddi:hoge」という全く同じCustom URL Schemeで競合させたアプリを作ります。 これをユーザーがインストールすると、認可リクエストのredirect_uri=caddi:hogeとなり、後からインストールされた悪いアプリ側の画面が開いてしまいます。 悪いアプリがcodeを受け取り、/oauth/tokenにリクエストしてアクセストークンを取得できてしまうわけです。 これを解消・対策するために出てきたものがPKCEで、code_verifierという任意の文字列を生成しておき認可リクエストを行うと、悪意のあるアプリに戻ってきた時に、code_verifierの情報がわからないので、アクセストークンをもらうことができないことで防ぐものです。 この攻撃以外にもPKCEは有効で、Single Page Applicationやネイティブアプリケーションは必ずPKCEを使うべきです。冒頭で少し紹介したFAPIでは、PKCEは必須であると定義されています。 地図情報システム入門 続いて、2021年1月25日に開催された社内勉強会、岩瀬からのプレゼン内容をまとめております。 https://speakerdeck.com/caddi_eng/20220125-iwase-di-li-qing-bao-sisutemuru-men-dot-pptx 緯度経度だけでは一意に場所は定まらない 岩瀬: 今回は、地理情報システム(GIS)に全く触れたことがない人向けの内容になっています。 内容としては、GISを扱うときに出てきそうな概念やツールなどを簡単に紹介していきます。 はじめに、次の数字をご覧ください。 こちらの数字は何を示しているでしょうか? 正解はキャディの住所になります。Google Mapからとってきた緯度経度の情報ですね。 さて、この情報を友人に教えたとして、この数字でキャディの会社の位置を特定できるはずです。 ところが実際に、別のサイトでやってみると・・なんと違う場所にヒットしまうのです。 これはなぜなのでしょうか? 実はこれ、測地系というものがあるのですが、緯度経度だけでは一意に場所は定まらないのです。 地球は完全な球体ではなく、厳密には楕円体なので、地球上の位置を緯度経度で表そうとすると、日本測地系と世界測地系では位置がズレてしまいます。 日本測地系は、昔から日本で使われてきたもので現在も一部で利用されていますが、GPSなどで使われているものは世界測地系といわれ、現在はほとんどこちらが使われています。 一意に場所を定めるために 岩瀬: 測地系と座標系を合わせて、空間参照系と言うものがあります。 コード体系としてはEPSGとよばれる空間参照系の識別コードがあるので、緯度経度の数値を扱うときは覚えておいてください。 座標系は、地理座標系とGoogle Map に利用されているWebメルカトルがありますが、これら座標系と測地系を組み合わせることで、一意に場所を定めることができるようになります。 位置情報のデータ形式 岩瀬: さて、そうした位置情報の元になるデータ形式には、どのようなものがあるのでしょうか。 今回は、WKT(Well-Kown-Text)とShapefileの2つがあるので、ご紹介していきます。 まずWKTですが、こちらはベクタ形式の幾何学オブジェクトを投影法をもとに変換して、地図上に表現させるマークアップ言語です。 POINT、LINESTRING、POLYGONの3つが主流です。POLYGONは、最初と最後の数字は一致させる必要がある制約があるので注意が必要です。 これらはデータをテキストで保存する場合に利用され、対応ツールとしては、PostgreSQL、MySQL、BigQueryなどがあります。 続いてShapefileですが、こちらはEsri社が提唱したベクター形式のデータフォーマットです。 国土交通省が提供するデータでも使っていたりと、実質的には業界標準のフォーマットになっていて多くのGISツールが対応しています。 私はQGISという無料のソフトしか使用経験がありませんが、簡単に描画などするときには適していると思っています。 無料ながら高機能で、WindowsやMac OS、Linuxで利用可能です。 地理情報データを解析するとき 岩瀬: 続いて、地図上のとある区域でデータ集計することなどがあると思います。 たとえば都道府県ごとの状況を日本地図で表示することは、生活のあらゆるところで利用されていて、私たちもなじみ深いですよね。 ただ、統計情報で利用するときは、大きさがバラバラなので、単一面積など数値的に同じ条件で可視化したい場合には向いていません。 また、基本的には陸地のみで表示されるため、降水量の表示など海上部分を表現することも難しいです。 これを解決するために、地域メッシュという、緯度経度に基づいてほぼ同じ大きさの四角形で分割した領域を使うことがあります。 それぞれのメッシュには地域メッシュコードが付与されています。 1次メッシュがあり、それを8等分したものが2次メッシュ、さらに分割して3次・4次…と細かくあります。 降水量などは、こうした地域メッシュを用いて表現をしています。 また、地図上を六角形で分割したGeoHexというものもあります。 こちらはGoogle Mapと同じズームレベルで分けられていて、日本人が仕様検討したそうで純国産のものなのですが、統計情報よりも一部のゲームで利用されいる印象が強いです。 Web地図描画 岩瀬: それからWeb地図に描画するとき、ライブラリとしてはLeafletなどを用います。 配信データ形式としては、ラスターデータは画像データなのでデータサイズが小さいため、路線図などで利用されています。 ベクターデータは、描画するときにオブジェクトに属性を持たせることができるのですが、数が増えてくるとデータサイズば大きくなってきてしまいます。 ラスタータイル地図は、Web地図では背景として使用されます。 また、XYZ方式はタイル状に分割された地図データ配信をするときによく利用されますので、 タイル座標確認ページ で使ってみてください。 GeoJsonは、JSON形式で空間データを表すフォーマットなのですが、テキスト形式なので人が読み書きしやすいメリットがあります。 MVT(Mapbox Vector Tile)は、Mapbox社が手動でやっているベクトルタイルの地図になっています。 テキスト形式では場合によってはエンコードが遅いので、それを解決するためにバイナリーデータで配信がされていて、通信データ量の削減ができるメリットがある一方で、デバッグが大変というデメリットもあります。 拡張子はmvtが推奨されているのですが、pdfになっているものも見かけたことがあります。 最後になりますが、Vue.js + Leafletを使用した簡単なサンプルをご紹介していきます。 これは、l-mapタグでマップの下地を作成し、その上に地理院地図のタイルレイヤーとキャディ関東物流拠点の場所をマーカーで描画したものになります。 タイル地図などを使用する場合ライセンス表記が必要になりますが、 attribute 属性に指定することで右下に描画する機能もあったりします。 ということで今回は、 ・緯度経度だけでは場所は一位に決まらないので測地系などを考慮する必要がある ・データ形式もさまざまあるので、目的に応じて使い分けてみてください というお話をさせていただきました。 おわりに 今回は、2回分の社内勉強会の内容をまとめてみたのですが、いかがでしたでしょうか。 記事内容をご覧いただいて、興味持った方は こちら にて、お話する機会をご用意しておりますので、ぜひご活用ください。 また社外向けに一般公開するイベントに関する情報は、 キャディのCONNPASS をご用意しておりますので、こちらもご登録いただけますと幸いです。 最後までお読みいただきまして、ありがとうございます。
キャディでエンジニア採用を担当しております片渕です。 今回は、エンジニアの社内勉強会「STUDDi」にてプレゼンがありました一部の情報(3ネタほど)を、レポート形式でお伝えしたいと思います。 STUDDIは、エンジニア全員が持ち回りで発表機会があるものですが、入社してくる方も増えてきたこともあり まだ一度もスピーカーになっていない方(新しく入られた方)の発表する場として優先する 好きな技術について思う存分語ってOK という方針でやっておりまして、今回は Clean Architecture 自律移動ロボット 3D CADのデモンストレーション の3つをご紹介していきます。 [toc] Clean Architecture for UI 今回はまず2021年12月13日に開催された社内勉強会、濱口からのプレゼン内容をまとめております。 なじみがないマイクロサービスのチケットが取れない理由は何か? 濱口: 生産管理プロダクトチームでは、複数のマイクロサービスで構成されたプロダクトを開発しています。 開発優先順位が流動的に変化していく中で、自分のなじみがないとか、得意でない分野(フロント / バック、言語、フレームワークなど)のマイクロサービスのチケットをすぐとりたいと思うものの、なかなか手が出せない状況がありました。 実際に前期の Klein の開発では、フロントエンドが得意なエンジニアが少なかったため、UI のチケットがしばしば滞留してしまう状態がありました。 上記のボトルネックが発生した理由のひとつに、ひとつのマイクロサービスの中でどこに何が書いてあるのかが分かりづらいかった、という点が挙げられるかと思います。 コードの中で迷子になってしまう状態ですね。 よって、言語やフレームワークが違っていてもどこに何が書いてあるのかがわかるようにするためには、どうしたら良いかと考えました。 その答えのひとつとしては、レイヤー分けの仕方を統一すればいい、というのがあるのではないかと考えています。 UIもBEもClean Architectureで作ればいいのではという仮説 濱口: 私が実装してきた、レイヤー分け( Clean Arthitecture )は以下のようなイメージです。 Clean Architecture考えにのっとりレイヤー分けを行うと、抽象と詳細を分離でき、変化に強くなります。 ここで言う抽象とは純粋なビジネスロジックを指し、変わりにくいものを示しています。 ここを守るために usecase の外側を分離します。 具体的には REST インタフェースなど外から入ってきたリクエストを controller が受け付け、usecase が起動されます。 usecase では domain が駆使され、ビジネスロジックが完遂されます。 さらに永続化するとなると domain が gateway を介して、reposigory 層で永続化を実行することになるのですが、これを上記手続き通りに実装すると usecase がgateway に依存してしまいます。 そこは依存性の逆転を使って、インタフェースに依存させることで外部の詳細がビジネスロジックの世界に漏れ出すのを防いでいます。 上記のようにレイヤーで責務を明確に分けることにより何がどこに書いてあるかがあたりがつくようになります。 Clean Architectureで作ってみた 濱口: 上記の例はじめ自分の経験では主にバックエンド API で Clean Architecture を実装してきたきたのですが、これを UI に当てはめたとき、UI にとって domain や usecase はどんな概念になるかと考えてみると、例えば通貨と値の組み合わせを持つ金額というドメインがあるとして、金額をフォーマットにして通過単位やカンマをつけることがドメインの責務になると考えました。 この仮説をもって、実際に React で TODO アプリを作り試してみました。 https://github.com/yujihamaguchi/todo-app まず Done / Undone の状態によってTODO文言の表示を変える、というような責務を持つ Item という domain が考えられます。 usecase はどこに相当するかというと、ドメインのロジックを使うところ、と考えると、コンポーネントととらえました。 port は、コンポーネントのルートがインターフェースをさらしていて、ここに相当すると思っています。 gateway は、実際にさらしたインターフェースに対して実装をうっていく hooks 以下の実装に相当します。 ファイルにアクセスして TODO を保存したり消したりするところですが、driver を集約してインターフェースを実装するという意味では、gateway といえるのではないでしょうか。 このように UI においても Clean Architecture の考え方を当てはめてディレクトリ構成やファイル名を寄せていくなどすると、どこに何が実装してあるかがすぐわかるように構成することも可能かなと考えています。 ロボット掃除機から学ぶ自律移動ロボットを支える技術 続いて2022年1月4日に開催された社内勉強会、上野からのプレゼン内容をまとめております。 https://speakerdeck.com/caddi_eng/20220104-ueno-rohotutosao-chu-ji-karaxue-huzi-lu-yi-dong-rohotutowozhi-eruji-shu-falsekohi-bd47b5f7-c711-4120-af1d-843614f78b96 上野: 今回はロボット掃除機を題材として、自律移動ロボットを構成する要素技術についてお話したいと思います。 ロボット掃除機の主な機能は、部屋を掃除しながら、障害物があればそれらを回避しつつ、掃除が終わったら充電ドックに戻ることです。 これらを達成するための、ロボットが必要とする要素技術としては主に以下の5つがあります。 【1】経路生成 【2】自己位置推定 【3】動作計画 【4】制御 【5】地図の更新 今回は、これらを1つずつ説明していきます。 【1】経路生成 上野: まずは、部屋全体を掃除するために、ロボットが経由すべき点をいくつか作り、そこを経由しながら掃除をしていきます。 このように、指定されたエリアを網羅するような経路を生成する技術はCoverage Path Planningと呼ばれる分野です。 部屋の掃除ロボットだけではなく、芝刈りロボットや、巨大な航空写真を作る(飛行機から写真を撮って画像をつなぎ合わせる)時にも使われます。 ここで生成された経路に従って移動していくのですが、そのためにまず必要となるのが、ロボット自身が今どこにいるのか?という自己位置の推定となります。 【2】自己位置推定 上野: これは、ロボット自身が部屋(環境)のどこにいるか?をセンサの情報から推定するものです。 例えばGPSであれば、その信号を受信していくものになりますし、車輪の回転数センサや車体の大きさから、どのくらい移動しているかを計算するホイールオドメトリと呼ばれるものもあります。 ただし、タイヤが回ってはいるもののスリップして実際には進んでいないケースもあったりするので、IMUなどカメラなどを併用するケースもあります。 他にも自己位置推定の手法には、LiDARというセンサによって得られた点群のスキャンマッチングなどがあります。 これは自動運転車などで用いられているものですが、レーザーによって計測した3次元の点群同士のスキャンマッチングを行い、ロボットがどう移動したかを計算するものです。 これらを用いて、自己位置の推定ができたら、次に必要なのは移動に必要となる速度などの目標値の計算になります。 【3】動作計画 上野: 具体的には動作計画と呼ばれるもので、ロボットが経路間を移動する時の、制御目標値を計算します。 上述した経路計画(ロボットの移動経路を計算するもの)とは異なります。 というのも、ロボットには物理的な制約があるため、経路計画通りの経路をそのまま移動できないことがあります。 例えば車の場合は、スライドp10にあるようなStartとGoalを一直線なPathで移動することができません。 実際にはTrajectoryを移動することになるため、そのためにタイヤをどう動かすか?などを計算するものが動作計画とよばれるもの。 ルンバはその場で旋回して真っ直ぐ進むことができるので、車よりはシンプルに考えることができます。 【4】制御 上野: その上で、与えられた目標値を実際に実現させるために、制御情報の入力が必要となります。 制御情報とは、例えばモータであれば電圧や電流を変化させることで回転速度を制御できるので、右の車輪を1秒間あたり60回転させたいのであれば、こうしようという数値を決めることができます。 上の図にある式をご覧ください。 第2項は例えば進行方向から迎い風がふくことで生じるような定常的な誤差を打ち消す働きをします。 第3項は、急に追い風がふいてくるような、急激な外乱を打ち消す働きをします。 【5】地図の更新 最後は、環境に変化があった場合のお地図の更新についてお話します。 ここでは、カメラや超音波センサーやLiDARなどの観測データをもとに、障害物があった理、部屋のレイアウトに変更があったりした場合に地図をリアルタイムで更新をしていきます。 具体的には、ロボットが認識できるような地図をセルやグリッドを使って表現し、新しい地図を生成することになります。 このことと、【2】の自己位置推定を同時に行うことをSLAM(Simultaneous Localization ans Mapping)というのですが、わりと挫折する人が多かったりもします。 地図が更新された場合には、新しい情報を用いて再度経路生成を行うことで障害物を回避する経路を生成できます。 こうした経路計画の手法は、グラフ探索アルゴリズムを用いるものや、ランダムサンプリングを利用した方法、仮想的なポテンシャル場を想定し、そこに障害物の影響を考慮することで経路を計算するポテンシャル法など、多岐にわたります。 実際にサービスロボットを作る場合には、孔子た自律移動の技術に加えて、音声認識やタスク計画、ロボットアームの動作計画など、幅広い分野の知識が必要になります。 3D CADをライブストリーミングしてみた 最後になりますが、2022年1月11日にCTO小橋より、フュージョン360というオートデスク社が作っているもので、3D CADとはどんな感じなのか?を体験すべく、ライブストリーミングを行いましたので、概要をまとめております。 どのように3次元の図面が出来上がっていくのか? 小橋: 例えば立方体を作るとした時に、まずは2次限で絵を書いていくことになります。 XYZ軸のうち、どの平面に長方形を作るかということですね。 X-Yを選びましたが、ここで描くことができた平面をどちらの方向に伸ばすか指定をすると立方体を作ることができます。 さらに、穴を開けたい場合は、あけたい面を選択して穴をスケッチします。 ただし、穴の場所が固定されていないので、解が複数(無数)にありますよという状態になっています。 そこで、端から50mmの位置などと固定することで、はじめて解が1つ定まったと表現されていきます。 位置が定まった穴に対しても、深さをどのくらいにするかを設定することで図面に表現されるようになります。 それから立方体の端の面取り、ここを丸くしていく加工をフィレットというのですが、例えば1mmのフィレットをかけたいとしたとき、どの辺にかけたいかを選択すると、かけることができます。 コンポーネント設計はCADの世界だとどうなるのか? 小橋: 続いてコンポーネント設計についてですが、ソフトウェアの世界ではこうすべきだ、などという流派がいくつかあったりもしますよね。 これが、CADの世界だとどうなるかというと、例えば似たような形状をパラメーターを使ってたくさん作りたいニーズがあったとします。 下記の図にあるような形状の、アルミフレームを設計するとして、これは長さのパラメーターだけを変化させられるようにしています。 例えば1250mmにすると一気に長くなるわけですが、ディスタンスの所にパラメーターを入れているわけですね。 こうすることで同じ断面のアルミフレームをたくさん作りたい時に使えたりするのですが、残念ながら外から関数を入れてやってもらう、というのはできません。 それから、複数の物体を合体させる場合、ジョイントという機能があります。 2つの物体の端と端どうしを、座標空間の中で合わせにいくような感じでやると、接続できるのです。 どことどこをアンカリングしてきたのかを注意しておかないど、設計変更がおきた時に大変になってきます。 ヒストリーベースで、どんな操作をやってきたのか?など時間軸でスクロールできるのはフュージョンの特徴の1つですね。 CAM(コンピューター支援製造)ソフトでのシミュレーション 小橋: 設計者はモデリングをどうするかを考えるのですが、実際にどう製造できるよう作るかを考えるのは別の思考回路になります。 そのため、例えば立方体に溝を作るときに、エンドミルで削るのですが、どういう工具で加工するかというシミュレーションが難しい。 このとき、CAMを使うことになるのですが、ジグで固定し、どのように削っていくのかをよしなに考えてくれるアダプティブクリアリングというツールパスがあります。 実際に加工する時に選ぶ工具を選択し、上記の図にもあるように、渦をまくような感じで工具が動き、加工していく様子をシミュレートできるものです。 どのくらいの速度で削ることができるのか?加工する時に熱が発生するので、どのように調整していくのか? 加工する材質や、工具の材質に応じて、モノの動きなどを設定しながら動かさないといけないのですが、これをCAMがプログラミングで判断してくれるのです。 一定のインプットをしてあげることで、こうした加工パスを作ってくれるので便利ですよね。 ということで以上、2次元の平面図形から形状をおこし、最後はCAMソフトでシミュレーションしていくデモンストレーションを今回は行ってみました。 おわりに 今回は、3回分の社内勉強会の内容をまとめてみたのですが、いかがでしたでしょうか。 記事内容をご覧いただいて、興味持った方は こちら にて、お話する機会をご用意しておりますので、ぜひご活用ください。 また社外向けに一般公開するイベントに関する情報は、 キャディのCONNPASS をご用意しておりますので、こちらもご登録いただけますと幸いです。 最後までお読みいただきまして、ありがとうございます。
こんにちは。桐生です。久々の投稿となりました。 最近 Next.js + urql + chakra-ui で環境を構築する機会があったのですが、Deno上にも同じような環境が作れないかと思い、Aleph.jsを使っても同じようにやれるのか試してみたので、その内容を共有したいと思います。 そもそも Deno とは?については、以前 ブログ を書きましたので、合わせてご覧ください。 Aleph.jsとは esm.shとは 今回の環境 Aleph.jsでurqlを使う 余談1 Aleph.jsでchakra-uiを使う 余談2 おわりに Aleph.jsとは Doc: https://alephjs.org/ GitHub : https://github.com/alephjs/aleph.js Aleph.js is a fullstack framework in Deno, inspired by Next.js. Aleph.js とは、公式ドキュメントにある通り、Next.jsに着想を得たDeno上で動くReact フレームワーク です。 公開されてから既に1年以上経っており、いろいろな方々がAleph.jsを試して記事にされていたりするので存在を知っている方も多いのではないでしょうか。 使い方はシンプルで、 # Aleph.jsのインストール deno run -A https://deno.land/x/aleph/install.ts # 新規アプリケーション作成 aleph init # `development` mode で実行 aleph dev # `production` mode で実行 aleph start などのコマンドが用意されています。 aleph dev を実行したときに、 esm.sh のロードの挙動にハマったので後述します。 esm.shとは Doc: https://esm.sh/ GitHub : https://github.com/alephjs/esm.sh A fast, global content delivery network to transform NPM packages to standard ES Modules by esbuild. esm.sh とは CDN の一つで、npmパッケージにあるモジュールをesbuildを使ってESMに変換して配信しています。Denoで サードパーティ ライブラリを使用する際によく使われる CDN で、他にも skypack などがあります。 ただし、もともとnpmパッケージはDenoで動かすことを想定していないので、Denoで動かない、Denoでそもそもインポート時にエラーになるといったこともよくあります。が、 esm.sh のバージョンが上がるにつれて解消されていろいろ使えるようになっていっています。 Github Issue を覗いてみると Failed to import - <package name> というタイトルのIssueが多く立っているので、自分の使いたいnpmパッケージでエラーが出るようなことがある場合は、同じようにIssueを立てておくと、今後Fixしてくれるかもしれません。 また Aleph.js のメンバー自体が esm.sh の開発にも携わっていることもあり、 Aleph.sh でも esm.sh 固有の処理があったりします。 今回の環境 > deno --version deno 1.17.0 (release, x86_64-apple-darwin) v8 9.7.106.15 typescript 4.5.2 > aleph -v aleph.js v0.3.0-beta.19 また、検証時の esm.sh の最新 v61 を使用しました。 Aleph.jsでurqlを使う これまで Apollo Client を使っていましたが、他のGraphQLクライアントの知見も貯めておきたいと思い urql を使うことにしました。 https://formidable.com/open-source/urql/docs/comparison/#framework-bindings にある通り React Suspense に対応しているのがいいですね。 Suspense との組み合わせによりロード中の状態を宣言的に記述できるようになって、 コンポーネント 実装がシンプルになることが期待できます。 ただし、 Aleph.js はデフォルトでは SSR モードで動くため、そのままでは Suspense が使えません。そこで aleph.config.ts というファイルを作って( aleph init では作られません)で SSR をオフにセットしておく必要があります。 // aleph.config.ts import { Config } from 'https://deno.land/x/aleph@v0.3.0-beta.19/types.d.ts'; export default <Config>{ ssr: false, }; それでは urql を使っていきましょう。 まずは esm.sh からimportするため、 import_map.json に追加します。 // import_map.json { "imports": { ... "urql": "https://esm.sh/urql" }, } 実は Aleph.js 特有の処理はこれくらいで、あとは普通に実装していくだけです。 続いて、Clientの作成とProviderの設定です。フリーのGraphQLエンドポイントとして SpaceX Land API を使っています(この API で SpaceX の打ち上げデータなどを取得できる)。また、Suspenseを有効にするため suspense: true をセットします。 // app.tsx import React, { FC } from 'react'; import { createClient, Provider } from 'urql'; const client = createClient({ url: 'https://api.spacex.land/graphql/', // enable suspense suspense: true, }); export default function App({ Page, pageProps }: { Page: FC; pageProps: Record<string, unknown> }) { return ( <Provider value={client}> <main> <head> <meta name="viewport" content="width=device-width" /> </head> <Page {...pageProps} /> </main> </Provider> ); } Queryする側の実装です。 SpaceX コンポーネント を作り useQuery を使ってデータ取得する実装を行います。ローディング中状態は Suspense に任せることにし、ここで実装はしません。 コンポーネント 内から分岐処理がなくなりとてもシンプルになりました。素敵ですね。 // components/SpaceX.tsx import React from 'react'; import { useQuery } from 'urql'; const LaunchesPastQuery = ` { launchesPast(limit: 10) { mission_name launch_date_local links { video_link article_link } rocket { rocket_name } details } } `; export function SpaceX() { const [result] = useQuery({ query: LaunchesPastQuery, }); return ( <> {result.data?.launchesPast?.map(({ mission_name, launch_date_local, links, rocket, details }) => { return ( <article key={mission_name}> <h2>Mission: {mission_name}</h2> <section> <p> {new Date(launch_date_local).toLocaleDateString()} | <strong>{rocket?.rocket_name}</strong> </p> <p>{details}</p> <div> <a href="{links.video_link}" target="_blank" rel="noopener"> video </a>{' '} <a href="{links.article_link}" target="_blank" rel="noopener"> article </a> </div> </section> <hr /> </article> ); })} </> ); } 最後に SpaceX コンポーネント の組み込みです。 Suspense でラップしてローディング中の状態を実装します。 // pages/index.tsx import React, { Suspense } from 'react'; import { SpaceX } from '../components/SpaceX.tsx'; export default function Home() { return ( <Suspense fallback={<p>loading...</p>}> <SpaceX /> </Suspense> ); } aleph dev で実行してみると、 loading... としばらく表示されたあと、 SpaceX の打ち上げ情報がリスト表示されました。無事 Aleph.js 上で urql (と Suspense )が動いているのを確認できました。 余談1 実は最初、importするURLを https ではなく http と記述していたために、なぜか useQuery の実行時にエラーになる、という事象に陥りました。 // import_map.json { "imports": { ... "react": "https://esm.sh/react@17.0.2", "react-dom": "https://esm.sh/react-dom@17.0.2", "urql": "http://esm.sh/urql" // http にしてしまっていた }, } Aleph.js の実装を追っかけてみると、 esm.sh 経由でimportしたモジュールについては、 aleph dev(dev mode) と aleph start(production mode) )とで、ロードするモジュールのモードを切り替えている、ということがわかりました。 https://github.com/alephjs/aleph.js/blob/v0.3.0-beta.19/server/aleph.ts#L993-L1001 の実装を見てみてください。 // append `dev` query for development mode if (this.isDev && specifier.startsWith('https://esm.sh/')) { const u = new URL(specifier) if (!u.searchParams.has('dev')) { u.searchParams.set('dev', '') u.search = u.search.replace('dev=', 'dev') specifier = u.toString() } } aleph dev で実行している場合、 https://esm.sh/ から始まるimport urlについては Aleph.js が ?dev というクエリストリングを付与するようになっています。 一方 esm.sh は、urlに dev クエリストリングが含まれている場合、Development modeのモジュールを返すという機能があるので、 aleph dev で実行した場合はDevelopment modeのモジュールがロードされるようになっています。 import_map.json をもう一度確認すると、 // import_map.json { "react": "https://esm.sh/react@17.0.2", "react-dom": "https://esm.sh/react-dom@17.0.2", "urql": "http://esm.sh/urql" } react は https://esm.sh にマッチするので、 aleph dev でDevelopment modeのモジュールがロードされます。 一方で、 urql は http://esm.sh だったので上記条件にマッチせず、Production modeの urql がロードされるようになっていました。さらに、 urql は react を依存モジュールとして持っていたので、同じくProduction modeの react がロードされることになりました(ここがややこしかった)。 これにより、Aleph.js本体は dev mode の react で実行されているにもかかわらず、 urql およびその依存モジュールであるreactは prod mode で同居する形になり、その結果 Context が共有されなくなり useQuery をコールしたタイミングで実行時エラーが出ていた、というわけでした。 本当につまらないミスで、エラー解消までに多大な時間と労力を消費してしまいました。とはいえ、これがきっかけで Aleph.js の内部処理を知ることができたので、良しとしましょう。 Aleph.jsで chakra -uiを使う 気を取り直して chakra-ui を入れてみましょう。まずは import_map.json に以下のように追加します。 // import_map.json { "imports": { ... "chakra-ui": "https://esm.sh/@chakra-ui/react", "emotion/react": "https://esm.sh/@emotion/react", "emotion/styled": "https://esm.sh/@emotion/styled", "framer-motion": "https://esm.sh/framer-motion" }, } Aleph.js固有の処理はこれだけで、あとは通常通り実装していくだけです。 続いて ChakraProvider の設定です。特別なことはありません。 // app.tsx import React, { FC } from 'react'; import { createClient, Provider } from 'urql'; import { ChakraProvider } from 'chakra-ui'; ... export default function App({ Page, pageProps }: { Page: FC; pageProps: Record<string, unknown> }) { return ( <Provider value={client}> <ChakraProvider> <main> <head> <meta name="viewport" content="width=device-width" /> </head> <Page {...pageProps} /> </main> </ChakraProvider> </Provider> ); } 最後に chakra-ui を使って SpaceX コンポーネント をスタイリングしていきます。こちらも特別なことはなし。 import React from 'react'; import { useQuery } from 'urql'; import { Badge, Flex, Heading, HStack, Link, Text, VStack } from 'chakra-ui'; ... export function SpaceX() { const [result] = useQuery({ query: LaunchesPastQuery, }); return ( <VStack spacing={4} align="stretch" p={4}> {result.data?.launchesPast?.map(({ mission_name, launch_date_local, links, rocket, details }) => { return ( <Flex as="article" direction="column" gap={2} p="4" borderWidth="1px" borderRadius="lg" key={mission_name}> <Heading as="h2" size="lg"> {mission_name} </Heading> <Flex direction="column" gap={2}> <HStack spacing={2}> <Text fontSize="sm">{new Date(launch_date_local).toLocaleDateString()}</Text> <Badge colorScheme="blue" borderRadius="full"> {rocket?.rocket_name} </Badge> </HStack> <Text>{details}</Text> <HStack spacing={2}> <Link href={links.video_link} isExternal color="blue"> video </Link> <Link href={links.article_link} isExternal color="blue"> article </Link> </HStack> </Flex> </Flex> ); })} </VStack> ); } 以上で実装終わりで、 aleph dev で実行してみると、きちんとスタイリングされた状態でUIが表示されました。素晴らしい。 余談2 実は年末に何度か chakra-ui の適用にチャレンジしていたのですが、断念していました。その時は、当時の最新 esm.sh v58 の chakra-ui を使っていたのですが、どうやってもうまくいかずでした。 Aleph.js には Plugin という機能があり、 Aleph.js の各ライフサイクルのタイミングで処理をHookすることができるので、 chakra-ui 用のPluginを書けばうまくいくのかも、なんてぼんやりと思っていたのですが、今年に入って esm.sh の v61 が出たのでそちらで改めてトライしたら、見事動くようになっていました。 Denoで esm.sh や skypack 経由のモジュールを使ってエラーが発生した場合は、まずそれら CDN のIssueを確認してみたり、該当するものがなければIssueを登録するなどしていくのが良さそうですね。 また、使う CDN によっても結果は違ってきたりするので、諦めずに別の CDN からインポートしてみると良さそうです(ちなみに年末は skypack からのインポートも試しましたがダメでした、そういうこともありますよね)。 おわりに 今回やってみて、 urql と chakra-ui が割とすんなり使えることがわかりました。特に言及していませんでしたが、ビルドも速くHMRなども効いており、そこまでストレスなく開発できる感触を得ました。 今回の検証Repoは以下のリンクから見れますので、興味ある方は覗いてみてください。 https://github.com/tkiryu/evaluate-aleph ところで、 Aleph.js の将来性はどうなんでしょうか? 実は、 GitHub の最終コミットが 20 Oct 2021 となっており、3ヶ月近く更新がない状態です。開発がアクティブでなければ安心して使っていくのは難しいところですが、どうやらリデザイン中のようで、 GitHub Issue でコメントされていました。 https://github.com/alephjs/aleph.js/issues/429#issuecomment-967794820 at alephjs side, i decided to re-design the framework, the new system will be powdered by wasm that can run any edge network, for example deno deploy, and it will support any UI frameworks like react/vue/sevlte... i almost finish the compiler layer MVP, will publish it soon. https://github.com/alephjs/aleph.js/issues/409#issuecomment-979803656 i am redesigning the framework to support deno deploy, in fact it will support any edge worker for example cloudflear 今後大きく変わる可能性があるため、今すぐ実践投入するのはやめておいたほうがよさそうですが、個人的には今後の動向に注目していきたい フレームワーク です。何かアップデートがあれば、またブログにしたためようかと思います。 今回は以上です。
キャディでエンジニア採用を担当しております片渕です。 今回は2021年12月21日にキャディ主催にて開催したイベント こちらに登壇した、テクニカルプロダクトマネージャーの今井( @imaimai0 )とMLEテックリードの河合( @vaaaaanquish )がプレゼンテーションした内容を、イベントレポートという形でまとめております。 今月よりAI Labを創設しておりますキャディ、その創設背景や現状そして作り上げたい未来について、気鋭のエンジニア2人が語り尽くしたのが今回のイベント。 当日参加できなかった方はもちろん、参加された方も振り返りの意味でもご覧いただけると嬉しいです。 [toc] キャディの現状とAI Lab 創設の背景 今月に誕生したAI Lab創設の背景ということで、まずキャディという会社の設立背景や事業概要・現状を、今井の方からお話しできたらと思います。 キャディとは 今井: ミッションとしてはモノづくり産業のポテンシャルを開放することを目指していて、モノを作る側も設計する側双方の課題解決にチャレンジしています、 具体的には調達というところ、100年以上のイノベーションが起きていないです。 設計についてはCADだったり、製造だったらFA(Factory Automation)などもありますが、調達は人が手作業でやっているところもまだまだあるため、ここを起点に変革を起こしたいのがキャディという会社です。 事業概要 具体的に事業としてやっていることは、メーカーである発注者が作りたいものの図面をキャディに提供し、キャディがそれを加工会社に依頼する流れになります。 これだけでは、単なるマッチングサービスと変わりがないのですが、キャディでは検査・品質保証などの商流に入り込んでいます。 そのため、発注者側からみればキャディが製造・納品してくれるのと変わらないので、ファブレスメーカーという立ち位置にいるのが特徴です。 プロダクト エンジニアが開発しているプロダクトは、発注側から設計図面をいただき、社内システムに入ることでユニークIDが発行されデータとなっていきます。 まず原価の計算を行うわけですね、どのくらいの価格で発注できるかを、車内の原価計算システムで算出します。 そしてどの加工会社に発注するかを定め、発注を行います。製造工程のサプライチェーンを管理するシステムも社内で内製しているのですが、いつ・誰が・何を作るのかを見ています。 その後、実際に作られたモノは、キャディでも船橋と大阪の2つに物流拠点を持っているのですが、そこに集めて検査を行い、最終的に発注元のメーカーに納品されます。 データの流れ よって様々なデータをキャディが取っていて、それが図面ごとのユニークIDにひもづいているわけですね。 例えば価格を出すときに、部品や製造工程の情報を図面データから読み取ります。 発注したあとは、製造側の納期などの情報がひもづき、トラックできます。 なので、実際に納品する際の、物流の情報や品質(不良率など)のデータもわかります。 こうした一連の流れによって図面情報のユニークIDとQCD(品質・コスト・納期)情報が結びつきます。 私(今井)が入社した2021年1月ごろは、こうしたシステムが浸透しはじめ、データがたまるようになってきた頃でした。はじめの頃は1人でデータを探索していたのですが、4月ごろから有志で社内データを見るチームを作りました。 その時の様子を 私(今井)のNote にまとめ、河合が興味を持ってくれたのが、河合との最初の接点でした。 さて、我々が扱っている図面というものをみなさんはあまり見たことがないと思うので、少しご紹介します。 設計の図面というのは例として下記にも載せていますが、 文字情報から意味を抽出したり、図面の右下のNOTEというところ 有害なバリ・エッジなきこと とあります。 こんな風に、製造業には定性的な情報もかなり多く含まれています。 有害とは何か?バリ・エッジはどの程度まで許容なのか?こうした箇所を言語化・定量化していくことでスムーズにデータとつなげることができます。 これから取り組みたい課題について 河合: 前提としてAI Labは今月立ち上がったばかりなのでまだ実績はなく、First Winを作りたいと考えています。 私は12月に入社したばかりで、あれこれ社内データを見ているところでしたが、待機問題に落とし込めば良さそうとか最適化すれば解決できそうというシナリオは想定しつつ、ヒアリングも重ねています。 図面といっても、人間が作っていますので曖昧さなどは製造業の中でも残っています。 例えば、とある部品加工を依頼したい時、何%の不良が出るのか。納期遅れの可能性なども事前に察知できれば、業界全体で不要な在庫を抱えなくて済むことになります。 要望とは異なるケースだったり、図面との差分というデータを持っているので、MLで使えたらと思っています。 今井: キャディの中での大きなテーマとしては、シフトレフトですね。 リスクを前工程でさばき、図面をもらった段階でリスクなど分かるようにしたいです。 そのためにはQCD情報が製造側にとっては大事になるので、図面からQCDまでの連動とリスク洗い出しができ、その情報を持って交渉などができると理想です。 データの標準化 河合: 製造業ならではの課題の1つとして、データが標準化されていないことがあります。 とある会社とサプライヤーさんとのやりとり1つとっても、会社間でズレがあります。 例えば5mmのズレは許容という会社もあれば、1mmでもダメというケースもあります。 キズについても会社によって許容度が異なりますね。 精度1つとっても標準化されていないので、そこから関わっていくことを私はやっていきたいです。 社内にはざっくりとBigQueryのデータはありますが、標準化されたスコアづけなど、これからやっていきたいので。 また、図面もフォーマットに従ってくれる会社もあれば、半分くらい手書きだったりする会社もあります。 AI Labの目指す未来とは? 河合: CTOの小橋とも話をしていたのは、図面の標準化がされていないので、図面のlinterのようなものが作れたらいいよね、ということでした。 3DCADのソフトがあり、製図という課題に対してアラートするような機能はあるが、MLでもっと先の工程を見越して流通の時になりそうな課題とか、コストなどにリンクするような図面linterを作ってみたいですね。 今井: それは究極の姿ですね。Manufactureing-Oriented CADと言っていますが、データは集まりつつあるので、それらをどうつなげていくか?というのが大きなテーマになりますね。 河合: 製造業の業界上の課題になるが、特定の大きな企業から加工を毎年受けている会社だと、意思疎通ができればいいので図面情報を端折ってもいいんですよね。 だけど担当者が変わった時や、横展開するのが難しいという課題があるので、標準化できるかどうか。 CEOの加藤もそこに課題意識があり、その課題解決に取り組むのがキャディの未来の姿だと思っています。 キャディのデータとは 河合: キャディのBigQueryのテーブルを見ていた時に、データそのものはきれいな方だと感じました。 おそらく先人のエンジニアたちが、どう構造化するかを必死に考えてきた結果なのだと思います。 ただ、物理世界を扱うデータだけあって「ノイズ」は多そうです。例えば2mmのずれなのか3mmなのかは、測定者や測定する機械で異なってくるので、そうしたノイズへの対処はAL Labの課題になるかなと。 今井: 図面解析プロダクトをやってきたので図面の話になるが、読み取れる情報が線画なので、あまり多くないんですよね。 パターンマッチングなど古典的な技術と、DLの技術が使えるところの中間にありそうな感覚です。 ドメイン知識については、書かれている1つ1つの情報にしっかり意味があるし、そこは難しさでもあり面白さでもあるなあと。 ちょうど5月くらいに、私(今井)がプロダクトマネージャーとして駆け出しだったころ、図面1万本ノックというものを社内でやっていました。 これはBiz側でどのように図面を見ているかをか解説しながら、読み方を教えてくれるというものなのですが、この経験で自分の解像度が上がりましたね。 図面を認識する順番があって、次はここを見ていくというような流れがある話です。 ドメイン知識を吸収するところはしっかりやらないといけないなあと思っていましたので。 河合: ドメイン知識を重要視する雰囲気はキャディにあるなあと感じています。 実際に私も入社時の研修で船橋の拠点に行きましたが、加工品がどのように入ってきて、どのように審査した上で、どのように流通させるのか。 こうしたところを私のようなエンジニアにも、丁寧に説明してくれるところがありました。 また、AL Labでは課題を吸い上げることをやっているが、こういうことができそうだけどどうですか?と発信するとリアクションがきっちりつく文化がありますよね。 ヒアリングするとさらにドメイン知識を詳しく教えてくれたりするので、私ももっと社内・社外に出ることをやっていきたいです。 今井: BizとTechが良い関係なので、様々なアイディアを双方が出してくれるので、クイックWinを出しながら、AI Labを成長させていきたいです。 一度に取り扱う図面規模の質問がきていましたのでお答えすると、先述した 私のノート にも書いたのですが、10万とかそのくらいの規模を扱っています。 オーダーレベルで処理量が変わってくると、ちょっとした修正ではなく根本的な改善が必要になってきます。 アルゴリズムの高速化、インフラの面でスケーラブルなものが必要だなあというのは、河合と相談しながら決めてやっています。 河合: データが意外と大きいというエンジニアリングの課題があると思っています。 今井: 1年前では図面解析のプロダクトがうまれたばっかりだったのですが、今では10万枚をさばくことがミッションになっています。 キャディのテクノロジーは技術ありきではなく、課題が難しいので最先端の技術を組み込むような、R&Dの先端にいること感じています。 無限に投資してもらうネタを作っていきたい 今井: AL Labと並行して、テクノロジーのビジョンを描いているところなのですが、やはりシフトレフトを推進していきたいです。 Computable Supply Chainという構想もあります。 作る側はそれほどデータが取れていないので、製造の情報をリアルタイムで取ることができれば、QCD情報から提案ができるので、そうしたデータ分析をしたいと思っています。 そうすることで、設計から製造までのデータが標準化できて、シフトレフトの究極系ができます。 河合: 私もそのような提案をして、業界標準が動くところをやっていきたいですね。 そのためには要素技術、First Winにつながる提案基盤が必要だなあと。 今井: 製造と設計の真ん中にいるので、こういう入力をするとこういうものが出ますよ、というような夢をAI Labで見せていきたいですね。 また、CEOの加藤が無限に投資したいと言っているので、投資できるネタを作っていきたいです。 https://twitter.com/yushirodesu1/status/1470749166825795590 河合: 無限に投資してほしい人を集め、無限に投資されるようにしたいですよね。 AL Labが実験的にできる拠点などがあると、流通やオペレーションの最適化、ロボット自動化なども含めてできるんじゃないでしょうか。 それらによってデータが集まるサイクルも作ってみたいです。 投資されるためにも、あれこれやっていきたいと思っています。 今井: MLはお金がないとできないので、クラスターを作ったり、ソフトエンジニアの採用なども積極的に進めたいですね。 AI Labの3年後には10名を超えていたら嬉しいですし、10年後は専門ごとに30人チームで動けたら楽しいなあと思っているので、巨額投資されるようなテーマをやっていきたいです。 また、技術的な話だと製造業はあまり論文になっていないのですよね。 特許などはあるが、製造業のデータがないので、学術的な貢献もできると思っています。 今回のようなイベントも、どんどんキャディが引っ張っていって、技術を引き上げていきたいです。 また、機械学習のコンペは主催者側になりたいですね、MLやDSへの業界貢献もできるといいですし、研究やOSSに貢献ができたらと考えています。 QA ここでは寄せられて質問に対して、以下3点の質問と回答をまとめていきます。 データで解決できない点はどういうところだとイメージされていますか? 今井: 設計側で属人化されたノウハウは最後まで残りそうだなと思っています。 標準化は進め、暗黙知をナレッジにしていくことはやっていくが、個々人にひもづく技術的なところは残りそうだなと。 河合: 調達の領域を攻めているのですが、他の領域とつながることで解決できそうだなと思っています。 設計から考えるプロダクトがあれば良いのですが、標準化されていない課題があるので、バリューチェーンを通しで考えていく。 CEOの加藤の中ではイメージがあると思うので、それをどうモノにしていくのかがポイントになりそう。 今井: どうしてもデータで予測はできるけれども不確実性は残ります。 物流は外部要因で影響を受けるので、例えば渋滞などで絵すね。 100%の予測は難しいですが、そうしたリスクを飲み込みながらモデルを作る必要があると思っています。 CADがデータ化されると楽になるのでしょうか? 今井: そもそも調達がもらえる図面データが画像であるというのは製造業の特徴です。 調達の時に、知財の面からCADデータがもらえないシーンが多くて。 CADデータがもらえるとこんなことができますよ・標準化するとコストが下がりますよ、ということを示すようなものを作れると、CADデータをもらえるようになるのでは?と思っています。 河合: 知財まわりは確かに難しいかもしれませんね 設計側からデータをもらえないのであれば、先ほども出たように5年後にAI Lab工場のような、キャディがデータを作っていく話になってくると論文も書けたりするでしょう。 データをもらって加工していくプロダクトもあるし、我々がデータを作ってシステムを作り、そこで生み出したデータを使って改善を進めるなど、無限に投資してもらえたらできるかなと思うのでやるだけです。 BEでGraphDBを使っているがAIへの活用はどうですか?またRustは? 河合: データ活用に使えそうです。グラフモデルとの接続はイメージできているのですが、扱うためには専門性が必要になるので、そういったデータに興味がある方とぜひカジュアル面談をやりたいですね。 Rustについては、機械学習モデルでTrainするようなことは直近では生まれてこないと思います。 Rustを使わなくてもPythonでも高速なのでメリットが薄い面もあります。 一方キャディの既存の資産、例えば図面解析だったりAPIがRustで書かれているので、そことのつなぎこみ。 そして、ONNXにモデル変換する深層学習モデルのプロダクトを立てるとかにチャレンジするのは既に出てきていますし、今後もありだと思っています。 最後に いかがでしたでしょうか? イベントの最後に、どんな人と一緒に仕事をしたいですか?と司会から質問があり、河合からは 河合: キャディの採用サイトにある文言が私は好きなんですよね、難しいことを面白がる、というもの。 キャディはこれまで難しく敬遠されていた課題に対して、MLやDSだとその折り合いをつけるところが面白いと感じる人と一緒に仕事ができるといいなあと思います。 というコメントが。 今井からは 今井: 私も同じですね。 キャディのエンジニアが、キャディはRPGのチームのようなもので、レベルアップしながら、そこで手に入れた武器を使って敵を倒していく、ということを言っていました。 都度向き合う課題に対して、都度勉強しながらやっていくことが必要になるので、これまでの課題が課題ではなくなってきますので、これまでの武器が使えなくなるということが普通に起きうるので、逆にそこに対してワクワクできる人がいいですね。 とコメントを残してくれました。 今後は、AI Labの成果発表ができるようなイベントも積極的にやっていきたいと考えています。 そうしたイベントに関する情報は、 キャディのCONNPASS があるので、登録をお願いできますでしょうか。 また、カジュアルに今井や河合と話をしてみたい!という方は、 こちら より申し込んでいただけますと幸いです。 最後までお読みいただきまして、ありがとうございます。
はじめに こんにちは、小橋です。前回は シリーズBの調達後のキャディの進化 について書かせて頂きました。その中で開発組織のアジリティ向上やイノベーション推進のためのプラットフォームチームに関して触れましたが、今回は新たな組織横断課題についてお話出来ればと思います。 品質です。 ここ数年で沢山の用語や職種が飛び交うようになりましたね。QAとかQCとかSETとか。クラウドやインターネットの浸透でSaaS等の継続的な価値を提供するビジネスモデルが可能になったからこそ、ソフトウェアの品質保証組織も進化している証拠かもしれません。品質の概念と長年向き合ってきたモノづくり産業のポテンシャルを解放するキャディとして、物理のモノづくりとソフトウェアを比較しようと思います。 物理的なモノの品質 オーダーメイドの洋服を購入したりする時に、皆さん「品質」をどう評価していますか?オプションも色々ありますし、メーカーによって生地、機能性、見た目も違います。意識しやすい品質もありますが、使ってみないと分からない機能性もあります。 その洋服を高品質で低価格ですばやくお届け出来る事が理想であり、QCD (Quality, Cost, Delivery) という表現も聞いたことがあるかもしれません。品質良く製造する事も重要ですが、この洋服は消費者の自宅に配達されて初めて価値になります。配送中に汚れると製造された商品の品質は変わらなくても消費者が感じる品質は下がります。カスタマーサクセスとかユーザエクスペリエンスとか、色々な用語がありますが物理的なモノを提供する事業においては最終的に納品して初めて顧客価値に繋がるため、QCDの概念には製造から梱包、輸送まで含まれます。 キャディは産業装置業界を含む多品種少量から中量産のモノづくりをしております。部品点数が多く、加工の種類も多い中で、調達の課題を解決しています。ペットボトルを作る装置から半導体設備まで幅広く調達支援をさせていただいています。大半の部品が通販やカタログで購入出来るものではなく特注品です。設計図があり部品ごとに寸法や表面処理が違います。キャディでは各部品が用途に適した品質で作られることを心がけており、そのために社内で製造業のドメイン知見を蓄積しています。 この品質というものをキャディで担保するために検査拠点も関西と関東に設けており、お客様が求めている品質に製品が出来上がったか確認しているわけですが、これが非常に大きなチャレンジです。 製造業の「検査」工程 普段出荷直前に設計図通りに製造されたかを確認する検査工程が設けられます。産業や会社によって検査のしかたは違いますが、なんらか「ユーザに届く前の最後の確認」をする認識はどこも共通でしょう。このステップで「穴の直径が間違っている」とか、「表面にキズがある」とか、「塗装が剥がれている」等と、色々アラートが上がってきます。 面白いことに穴の直径が違っても、キズが付いていても、必ずしもダメな訳ではない。ユーザが穴の直径を気にしないなら良いわけです。誰の目にも付かずキズだらけでも良い部品なら、キズがあっても良いです。ソフトウェアの世界でもアイコンの位置が数ピクセルズレていても、本番へのリリースを続行する事もある。全てコンテキスト次第でややこしいですね。肉眼でキズが無くても顕微鏡で見ればいくらでもキズは見つかります。あらゆる品質の基準に強弱を付けて製造の現場に伝える事が重要で、そのための標準として日本国内ではJIS規格が設けられています。 家具の金具を作りたいのにジェットエンジンと同じ過剰な品質を求めると価格に大きな影響が出てしまう。だからといって、「キズだらけに作ってOK」とか「寸法は適当でOK」という設計者はいないでしょう。大体設計部門は品質を良くしたく、購買部門は購入価格を抑えたいため、機能要件と価格インパクトを上手くバランスするのが製造業の難しさともいえるでしょう。 そのせめぎ合いで部品が調達される状況下で検査では合否判断が求められます。検査というコンテキストだけでは判断しきれなく担当者に確認が必要になる場面も少なくないです。ソフトウェアの世界でもリリース直前に不具合に気付き、リリース続行判断をプロダクトマネージャに求める場面に似ています。一定不確実性が検査工程まで残ってしまうのは仕方がないですが、それをラーニングとして次回に活かし、上流にフィードバック出来る事が最終的な姿かもしれません。 出荷直前の検査工程でミスに気が付かずにお客様に迷惑かけるよりはマシですが、根本的には何も解決していない。不良の検出単体はその場の事実確認だけであり、継続的な改善に向かうには更に原因追跡や再発防止対策が必要。何もしないで再発のリスクを飲み込むのも全然ありですが、その共通認識を随時アップデートするきっかけとなるのが検査のあるべき姿なのではないでしょうか。 品質の継続的改善 結局正解は無いし、完全に白黒の世界は作れないので如何にユーザや業界に合わせ続けられるかが肝になります。品質は一回確認して「完了」するものではなくて、何が求められているのかを把握した上で、継続的にそれをお届け出来るようにコミットする事です。ミスの無い世界は存在しないですし、不確実性がゼロの世界も存在しないですが、トレードオフを常に意識して改善する事は出来ます。 求められている基準を定期的にアップデートして、それに向けて改善し続ける事を言語化したのが国際基準である ISO9001 です。こちらは「品質バッチリ!」の認定ではなく、「継続的にPDCAを回す」体制と実態があり、それにコミットする経営からの意思表明です。キャディでもISO9001認定を受けており、QMS (Quality Management System)の構築と継続的な改善に取り組んでいます。検査の結果をフィードバックのメカニズムとして活用する事を記述させて頂いています。 加工品の品質の継続改善と同時に、テクノロジー本部ではソフトウェアの継続的な改善にも取り組んでおります。物理的な加工品とパソコンの中にしか存在しないソフトウェアとで基質は違いますが、似ているところも複数あります。キャディの受発注事業部とは全く別ですがSaaSの事業部ではISO27001認定を受けており、ISMS (Information security management system)の構築を運用をしております。この世に100%の保証は無いものの、極力お客様に寄り添っていき、変化し続ける要求に対して適切なプロダクトを提供し続けるための意思表明とも言えます。 今回ご紹介させていただいた物理の品質を踏まえて、次回 Part 2 ではセキュリティに限らず物理のモノづくりとソフトウェアのモノづくりを比較出来ればと思います。 The post 製造業とソフトウェアの品質 Part 1/2 appeared first on CADDi Tech Blog .
たびたび登場失礼します、キャディ HR for Tech の岡野です。 過去にも Tech Blog の中で振れられたこともある 、社内勉強会の「STUDDi」。 エンジニアの知的好奇心を刺激するこのイベントについて一部の情報(3ネタほど)を社外の方にもお伝え出来ればとおもい筆をとりました。 エンジニアの皆さんにとって、日頃の スクラム 開発とブログ執筆の両立が簡単ではないこともあり、たまにはこうして近くでみている僕からの目線でもお伝え出来ればと思っています。 ちなみに上記の記事は2020年8月のものですが、それから1年以上経った今でも形を変えながら脈々と運営されています。 エンジニア全員の持ち回り発表機会が何周かしたことに加えて新しいメンバーのジョインが続いた事をきっかけに、組織としてもまだまだ発展途上であるキャディらしく、この勉強会のあり方について意見があがりました。 Tech組織の中でも多様性が増してきて得意分野が異なるメンバーが増えたことも考えを巡らすよいタイミングだったと思います。 そうした環境のもとで”全員に刺さるテーマ”を探して話すというのがハードルになり始めていたこともあり、検討された結果・・・最近再定義されたのが以下のコンセプトです。(ぱちぱち) まだ一度もスピーカーになっていない方(新しく入られた方)の発表する場として優先する 好きな技術について思う存分語ってOK ちなみに、既に所属していたエンジニアの発表機会についても合わせて検討しています。 前置きが長くなってしまいましたが、レポートを始めていきたいと思います。 Scala つまみぐい キャディではメイン言語として Rust を活用していますが、 Rust に執着しているわけではなく課題を解決できる技術はないか、エンジニアの皆さんは日頃から探求しています。このお話はそんな好奇心を刺激する回です。 ※Rust を選定したときのお話は こちら をご覧ください。 2.13.x系をベースに基本的な特徴について、参加していたエンジニアから気付きの投稿もあがっていました。 発表終盤では、生徒の特定の科目の平均点を計算することをお題にOption型のコンビネーションについて触れられました。 SeqのmapやflatMapを使い、さらにそれらをfor式で書くことで格段に見やすくなる点など学びの声が上がっていました。 普段使っている言語以外の発表は便利な用途を知ることで課題解決の選択肢にもなるためとても好評です。 今後特長を活かすシーンがあれば Scala を採用する事もあるかもしれません。 30分で見るここ6年くらいの単一画像 超解像 Computer Vision 及び Deep Learning の専門性を持たないメンバーに向けた入門編の発表です。 キャディでは、Webアプリケーション開発と研究開発のチームが各々活動しており、こういった勉強会の場などでナレッジの共有がなされる点が魅力の一つになっていると思います。 古典的な方法( ヒューリスティック , 辞書式)と最新の手法(SRCNNの登場~)について解説を交えた発表は入門編という主題通りにわかりやすく理解できました。 ニューラルネットワーク 登場後の発展の歴史においては、 PSNR の大小を競い合う時期を経て、精度以外の観点(見た目, 速度, 実用性)が評価観点に加わってきている事から技術の活用先である「目的」が前提となってくるのだと伝わりました。 論文を読み解く際のワンポイントアド バイス も添えられており、これから 超解像 の分野の勉強を始めるメンバーも出てきそうな予感が・・・! ※ちなみにこの発表のあと、聞いていたエンジニアから「画像コンペにでません?」というお誘いがあるなどコミュニティの広がりにもなっている点が素晴らしいなと思いました。 メモリボ トルネックの概要とその影響について CPUの性能とメモリ性能の関係について、大学院時代のディープな研究内容についておすそ分けの発表です。 理論性能と実効性能についての前提をあわせたあとに、CPUにおけるメモリI/O ボトルネック について深く掘り下げたお話が聞けました。 ここで卓越なのは、わかりやすい比喩表現にWS(わんこそば)をつかっていたこと。 食べる人(CPU)と供給する人(メモリ)の関係で、あくまでWSをベースにお話が進むのでチャット欄がすごい勢いで盛り上がることになります 笑 これを微塵も笑わず発表されているところに尊敬の念を抱かざるを得ない わんこそばをWSって書いてるスライド初めて見たかもしれない いや、わんこそばをここまで語っているスライド初めて (^^; ポーカーフェイスすごい websocket はわんこそばだったか! だめだじわじわくるww スライドのパンチが強い さっきのグラフしか勝たん バランスが大事なんですね つまりみんな早食い 一方で富岳のすごさ、キャッシュヒット率と早さの関係など、きっちりと業務にも役立てられる知識も得られてしまうのがすごい。 「現代はメモリよりCPUがはるかに高速な時代であるが、そのCPU性能を活かすためには(キャッシュヒット率を高める)コードが大切になってくるよ。」というまとめは、パフォーマンスを考える上でも必要不可欠な観点としてエンジニアリング業務にも活かされることでしょう。 ただし、最大の インパク トは残したのはWS(わんこそば)だったかもしれません 笑 おわりに 言葉足らずなところもあったと思いますが、内容が気になった方や「ええやん」と思った方は詳細について こちら でお話する機会がご用意できると嬉しいです。