TECH PLAY

キャディ株式会社

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

234

キャディでエンジニア採用を担当しています片渕です。 今回は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(わんこそば)だったかもしれません 笑 おわりに 言葉足らずなところもあったと思いますが、内容が気になった方や「ええやん」と思った方は詳細について こちら でお話する機会がご用意できると嬉しいです。
Tech Blog 初投稿で緊張している、岡野です。(キャディでエンジニア採用を担当しています) 2021年11月11日に開催されたFIndyさん主催のイベント 注目の言語Rust、開発現場でどう使われる?活用企業の現場に聞くVol.2 こちらにキャディのバックエンドエンジニア長田歌菜さん( @ngtkana )が登壇しました。今回は、その内容をまとめましたので当日参加できなかった方にも目を通して頂けると嬉しいです! 注目されている言語の1つとして取り上げられることも多いRust。実際に開発の現場で使う機会は限定的な面もあり、 実際に使っているエンジニア目線でどうなのか ? Rustを業務に取り入れてみたいと考えていらっしゃる方、技術選定の候補として検討されている方の参考になればととても嬉しいです。 [toc] Rustをキャディで採用した背景は? 長田:私自身は、言語選定そのものに関わっていないので、聞いた話や残っている資料ベースでの説明となります。 キャディでは生産管理や原価計算などのプロダクトを内製していくプロジェクトがあり、大きなサービスを展開するのが初めてというフェーズがありました。 それまでは、C++ とか Python など様々な言語を使っていたのですが、堅牢なものを作るにはどのような言語・フレームワーク・技術を使うべきか? 社内でディスカッションを重ねていました。 最終的にはRustを選定したポイントは、 コンパイル時の型や検査に強い こと。 その理由は、数値1つ取っても、 ・長さなのか重さなのか? ・金額であっても「売り」なのか、「買い」なのか? など数値の意味を定義しないと間違ったことが書けてしまうため、計算・検査がしっかりできることが必要でした。 そんな背景から、Rust , Go, Kotlin などが候補に上がるも、 ・キャディのプロダクトによってはスピードが要求されること ・元々キャディに数名Rust使える人がいた ということからRustに決定した背景があるようです(あくまで伝聞です) 例えば、グラム(重さ)とメートル(長さ)が勝手に足し算されてることがあれば、圧倒的に信頼性を失いますからね。 実際にRustを使ってみて特徴を挙げるとすると? 3点あるかなと思っていて、それは 「速い」「安全」「エンジニアに優しい」 です。 「速い」というのは、Rustは 実行時間も速いし、実装時間も速い と思います。 エンジニアによっては、まどろっこしいという先入観もあるかもしれませんが、実装がしやすくなるよう考えられて作られている言語なので、「早い」という特徴がまずありますね。 次に「安全」というのは、 メモリリソースの管理で悩むことがない 点です。 Rustには所有権という概念があり、1つのメモリリソースは1つの変数が持つというものです。 最初はつまづくかもしれませんが、慣れてくるとこの所有権のおかげで実装がしやすくなります。 最後の「エンジニアに優しい」というのは、 コンパイル時に実装の誤りを指摘してくれることが多いから です。 私が Rust を勉強し始めたバージョン1.30くらいからを比較しても、書きやすくバージョンアップしています。 詳しくは Rust公式 や、初心者向けの 入門のまえがき にもありますが、まどろっこしい書き方をしなくて良くなるのがいいです。 フレームワークについてはどうか? キャディが作っているプロダクトはWebサービスがメインです。インフラは共通しているもののチームによってアーキテクチャや使用技術はバラバラです。 私のチームは warp を使っていますが、これの良さはサーバを立てるところがそんなに長くならない点ですね。 設計者は偉いなあと感じるのですが、サーバやFPIやフロントエンドなどがきっちり分かれていて、とても読みやすいと感じました。 どのくらい型の恩恵をうけているか? 直接的なメリットとしては2点。 1つ目は、 ドキュメントが小さくなるところ です。 これは、型が要件を表しているので、関数にわたすものも細かく指定しなければいけません。 ところが Rust だと、 ”こういう意味で実装しました” というのが分かりやすく伝えられるので、コミュニケーションコストが下がる点はメリットですね。 2つ目は、 テストが減ること です。 C++はテンプレートが実体化していないとコンパイルが通る保証がないため、全てのコードポイントを通るように気をつける必要があります。 そのような点で、テスト時に注意する要素が減るのは嬉しいポイントになりますね。 私が以前担当していたプロダクトで原価計算をするときは、とても細かく型をつけていました。 uom というライブラリがありまして、これは物理的なもの(長さ・重さなど)を扱うときに、「長さ」「重さ」を区別できるようになり、掛け算して面接を算出できたりするものです。私たちは、さらにお金の単位も追加して利用していました。 Rust を実際に使ってみてのメリットは? メリットは3つあると思っています。 1つ目は、 環境構築が楽でビルドの再現性が高い 点ですね。 比較対象とする言語にもよりますが、例えば ・コンパイルのプロバージョンが入っている・入っていない ・ライブラリがどのパスに入っている・入っていない などビルドは難しい感じがします。 それと比較すると Rust の難易度は低い方かなと思いますね。 2つ目は、 型が強い ということ。 そして3つ目は、 実行速度が速い ことですね。 オブジェクトを作るとどうしても遅くなったり、抽象化すると抽象化コストがかかるので、コードの読みやすさと実行速度はトレードオフの関係になるのが普通かなと思います。 だけど Rust は”書きやすくて速い”というのを実現できるのがいいですね。 逆にデメリットは? こちらは2点あります。 まず1つ目が、 ビルドに時間がかかる こと。 キャッシュが大きく、ビルドでの待ち時間が長いのはネックですね。 キャディのバックエンドのプロジェクトは、だいたいが Rust を利用しているのですが、様々なプロジェクトを経てビルドを行うと、すぐデータがいっぱいになってしまいます。 なので、泣く泣くプロジェクトを消したり、ローカルから落とすなどで対応しているのですが、もう少しキャッシュが小さくなると嬉しいというのはあります。 2つ目は、まだ 発展途上の言語 ということです。 以前と比較すると書きやすく、機能も増えている事実はあります。 ただ、例えば Rust のエラー処理まわりに関して何のライブラリを使うか?でいうと、 私のチームは anyhow を使っていますが、部分的にちょっとこの辺はまだまだだな・・という点は正直ありますので、そこは我慢して使っているのが実情です。 また乱数を扱うときに、rand というものを使うのですが、数ヶ月前にアップデートがあり、その巻き添えになって大変だったこともありました。 またまだ、発展する余地がある印象です。 人によってデメリットと感じるか、メリットと感じるか個人差があると思いますが、 大きなフレームワークがないのも Rust の特徴 です。 “とりあえずこのフレームワークを使えば何でもできる” というものがないのです。 よって、Rust を使うということはアーキテクチャを自分たちでイチから考えることになるので、これとこれを組み合わせてこうしたプロダクトを作ろうなど、自分たちで構成を考えなければいけません。 これは楽しくもあり、大変なことでもありますよね。 Rust を学習するときの難易度はどうか? 元々 どういう言語ができるエンジニアなのかにもよる と思います。 例えば C++ ができるのであれば、それほど難しくないのではないでしょうか。 逆に Python や JavaScript をメインに扱っている方だと難しいかもしれません。 Rust そのものが特別に難しい言語とは思っていませんが、あらためて所有権という概念は難しいかもしれませんので、その点くらいでしょうか。 ライブラリについては、公式が管理しているものが広範囲で、 crates.io でみると一覧で出てきます。 基本的には ライブラリは1箇所に集まっています。 ドキュメントの生成も、コンパイラと一緒になっているので同じフォーマットでまとまるようになっていますし、ドキュメントを集積する場所はdoc.rsにあり、 crates.io から簡単にジャンプできます。 なので、情報がどこにあるのか?ということで迷わないですし、それは知らなかったということもなく、独学で学習するには向いている言語だと私は思います。 また、 コミュニティの皆さんの面倒見がよかったり もします。 Twitter で質問するといろいろな人が教えてくれたりしますし、rust-jp のslackにも初心者向けチャンネルがありますので、ぜひとも学習してみてください。 採用・育成の観点ではどうか? コンパイルが通りやすいのは、初心者にやさしい点ですよね。レビューする側としても安心できるのかなと。 ただ、Rust はまだマイナー言語なので、元々 Rust がかける人は集まってこないと思います。 なので、これは Rust に限らずですが、書けない言語を書けるようにするために、勉強していく必要があるのはデメリットかもしれません。 Rust そのものが普及すれば、そうしたデメリットも減るでしょう。 日本で Rust を使っている会社一覧 があったりするので、私はそういうのをみて仲間意識を育んだりもしています 笑 Rust 習得の具体的な手順は? Rust-by-Example を一通り読んでいれば、あとはOJTで大丈夫かなと思っています。 そこから興味を持てたら、公式のリファレンスやチュートリアルを見たり、ライブラリは標準のドキュメントを見たり。 いずれにしろ 公式が提供しているコンテンツを読むと勉強が進むし、情報がまとまっているのでチェックしやすい です。 開発に参加するだけであれば、そんなに知識は必要ないかなと思います。 あとは、プログラミングで何をやりたいのか? Tech Blog をご覧になっている皆さんによって、書きたいものや解決したいことは違うと思いますが、それを Rust を使ってやってみるといいのかなと思っています。 新しいメンバーがチームに参加するときの GAP は? 私自身が Rust を使うチームに2週間後に移動となったとき、そこからがんばって勉強しました。 新しいプロジェクトにつくと、このコマンドにはこうしなければいけない、これをインストールしなければ、など環境を整えるのが大変ですよね。 そういう意味で、 Rust のプロジェクトはビルドが簡単なので、オンボーディングは楽 かなと思っていますので、あまり GAP は感じません。 C++ で開発されているところから Rust への切り替えは? C++ でやっていてよかったのは既存のライブラリがあったことですね。 Open CASCADE というライブラリですが、C特有のメモリー管理が難しかったりしました。 Rust に切り替えると、 既存の大きなライブラリを使えなくなるので、自分で作っていくしかない ところは私的にはよかったです。 最後に 今回は、FIndyさん主催のイベントに登壇させていただいた長田歌菜さんの発表内容を記事にしました。 着飾らない等身大のお話ですので、Rust に触れる機会のあるエンジニアにとって有益な情報になっていればとても嬉しいです。 キャディでは、バックエンド、フロントエンドなどのWebアプリを得意とするエンジニアやアルゴリズム、ML、データサイエンスなどの数学の知識を活用したエンジニアリングを得意とされる方にも興味を持っていただきたいなと思っています。 ご興味をお持ちくださった方は、ぜひ こちら からエントリーください!カジュアルにお話できると嬉しいです。 最後までお読みくださり、ありがとうございました。
こんにちは、キャディでソフトウェアエンジニアをしている小倉です。今はフロントエンドを主に触っています。 いきなりまとめ(TL;DR) 本記事は、AgGridのセルの値のReadとWriteについての機構をまとめた記事になります。 記事が長くなってしまったので、触れる内容をまとめた図を先に持ってきました。 やり方が複数あるものについては以下のように考えておくと良いです。 valueSetter,valueGetterは使わなくて良いなら使わないようにして、なるべくfield指定だけですむようにrowDataを設計する。 文字列処理だけで住むときはvalueFormatter, それ以上の処理( CSS で飾り付ける、リッチな機能をつける、etc...)が必要ならcellRenderer プロローグ キャディで作っているTechプロダクトの半数は、人力で回していたオペレーションを代替する立ち位置のものです。どうすればうまくいくのかわからない製造業の課題に対してまずは人力で試して、手応えがあったら、効率化や規模拡大のためにTechプロダクト化する、というよくある流れですね。 さて、このようなプロダクト開発に携わっている方が必ずといって良いほどユーザーから聞くフィードバックがあります。 「 Excel ・ スプレッドシート の方が便利 」 人力で回すオペレーションはUIとDBの部分を 表計算 ソフトで間に合わせていることが多いです。プロダクト化しても良さそうだぞとなるオペレーションはだいたい強力な Excel ・スプシが出来上がっていることでしょう。それと比較されて出てくるフィードバックです。 表計算 ソフトはUIとデザインを一緒くたにして使ってしまうと甚大な アンチパターン を踏んでしまいがちですが、そこをちゃんとすれば、洗練された迅速なオペーレーションを回す手助けをしてくれます。とくに 表計算 UIは大量のデータを扱うのに強かったりします。 表計算 ソフトの アンチパターン を踏まれないようにFEエンジニアがうまくUIを設計すれば強力なツールとなるでしょう。 そういうわけで、私達のチームでは 表計算 コンポーネント をプロダクト内に導入することにしました。 表計算 っぽい コンポーネント を提供するライブラリには WijmoのFlexGrid , Handsortable , cheetahGrid など様々ありますが、私達は AgGrid を使うことにしました。 この記事はなに? AgGridは機能が多く、同じことを実現するにしても様々な書き方ができてしまいます。とくにセルへの入出力まわりはその傾向が顕著です。 公式ページをよく読めばどういうときにどの機能を使うのかのポリシーが書いてあるのですが、開発のたびに参照しに行くのも面倒なので、一覧性を重視したまとめドキュメントを作ることにしました。この記事はそのドキュメントをブログ向けにアレンジしたものです。 用語 Grid:表全体 Row:行 Cell:マス一つ 行は「どのオブジェクトか」、列は「オブジェクトをどう料理するか」 AgGirdでは各行(Row)は1つのオブジェクトと対応します。つまりGrid全体としてはオブジェクトの配列になります。その配列がしばしばrowDataと呼ばれるものです。 各列(Column)は、対応するオブジェクトをどう料理するかに対応します。ここでいう「料理」は大抵の場合は「オブジェクトのこのフィールドの値をtoStringした値を表示」になります。それ以外にも、複数フィールドの合計値を計算して黄色い背景色で表示、などの複雑なパターンにも対応可能です。 つまり、$i$行目$j$列目の場所にあるCellの値は rowData[i] をj列目共通の料理の仕方で得られた値になります。(UI上で列や行の並びを変えられるので厳密な説明ではありませんが、、、) コンポーネント の例 基本的には AgGridReact コンポーネント に行データのArray(rowData)をPropsで与えると、Gridが出来上がります。 AgGridReact のchildrenとして AgGridColumn を追加すると列に関する情報を入れることができます。 AgGridColumn の field 属性にフィールド名を入れておくと i 行目の当該列は rowData[i] のそのフィールドの値を toString した値が表示されます。 ▼素朴なAgGrid コンポーネント import { AgGridColumn, AgGridReact } from "ag-grid-react"; import "ag-grid-community/dist/styles/ag-grid.css"; import "ag-grid-community/dist/styles/ag-theme-alpine.css"; const rowData = [ { firstName: "Taro", lastName: "Tanaka", age: 10 }, { firstName: "Hanako", lastName: "Yamada", age: 25 }, { firstName: "Jiro", lastName: "Suzuki", age: 32 }, ]; export const MyGrid: React.VFC = () => { return ( <div className="ag-theme-alpine" > <AgGridReact rowData={rowData}> <AgGridColumn field="firstName" editable={true} /> <AgGridColumn field="lastName" editable={true} /> <AgGridColumn field="age" /> </AgGridReact> </div> ); }; ▼見え方 表示 上で説明した「料理」は、おおまかに以下の2パートに分かれます。 - オブジェクト(rowData)から値を計算する部分 - 値を表示する部分 オブジェクトから値を計算する部分 オブジェクトから値を計算する部分は以下の2種類のパターンがあります。 ①フィールドを指定する <AgGridColumn field="lastName" /> のようにGridCloumnのfield属性を指定すると、その列のセルの値は、オブジェクトのそのフィールドの値になります。次に紹介するvalueGetterが特に指定されていないときは、フィールド指定だと思って値を取ってこようとします。たとえfield属性が指定されていなかったり、存在しないフィールド名が指定されていたとしても、そのフィールドにアクセスして値を取得しようとするので、undefinedがそのセルの値になります。AgGridは全体的にフィールド指定で済むならばその方がコードがスッキリするようになっていますから、 なるべくフィールド指定で済むようにrowDataの設計をするとよいでしょう。 ②valueGetterを指定する <AgGridColumn valueGetter={myValueGetter} /> のようにGridCloumnのvalueGetter属性に関数を指定すると、その列のセルの値は、その関数が返す値になります。ここでvalueGetter関数には、引数としてその行のオブジェクト(rowDataの要素)が渡されるだけでなく、Grid全体を操作できる API などいろいろおまけがついてきます。そのため実質Grid全体の情報を使ってなんでも計算できることにります。詳細な引数は 公式ドキュメント を参照してください。 どうしても複数フィールドにアクセスしないといけない場合や、複数のセルで同じ値を共通して参照しなければならない場合にvalueGetterを使うとよいでしょう。 値を表示する部分 値を表示する部分は以下の3種類のパターンがあります。 ①toStringして得られる文字列を表示する とくに何も指定がない場合は、その列のセルは、セルの値を Object.toString して得られる文字列が表示されます。 値そのものがテキストや数値で、そのまま表示していいときは、このパターンで実装するとよいでしょう。 ②valueFormatter関数によってフォーマットした文字列を表示する <AgGridColumn valueFormatter={myValueFormatter} /> のようにGridCloumnのvalueFormatterk属性に関数を指定すると、その列のセルは、その関数が返す文字列を表示するようになります。なお、次に説明するcellRendererが指定されている場合はそちらが優先されます。 ValueFormatterr関数に渡される引数の詳細は 公式ドキュメント を参照してください。 値そのものがテキストや数値で、フォーマットし文字列を表示するだけでよい場合にこのパターンで実装するとよいでしょう。 ③cellRendererを指定してJSX/ TSX を描画する <AgGridColumn cellRenderer={myCellRenderer} /> のようにGridCloumnのcellRenderer属性に関数などを指定すると、その列のセルは、それによって計算されるJSXやHTMLを表示するようになります。cellRendererはJSX/HTMLを返す関数、その関数名(string)、レンダラーの コンポーネント 、を指定することができます。 もしReactのFunctionalComponentを渡したい場合はcellRenderer属性ではなくcellRendererFramework属性にFunctionalComponentを渡してあげなければならないことに注意してください。 cellRenderer関数に渡される引数の詳細は 公式ドキュメント を参照してください。 値そのものがテキストや数値で、フォーマットし文字列を表示するだけでよい場合にこのパターンで実装するとよいでしょう。 編集 <AgGridColumn editable={true} /> のようにGridColumnのeditable属性をtrueにするとその列のセルは編集可能になります。 編集は以下の3パートに分かれます - Editorが編集後の値を返す部分 - 編集後の値をパースしてセルの値を更新する部分 - 更新されたセルの値に応じて、オブジェクト(rowData)の値を更新する部分 Editorが編集後の値を返す部分 <AgGridColumn cellEditor="agSelectCellEditor"> のようにGridColumnのcellEditor属性を指定すると、その列のセルを指定されたEditor コンポーネント を通じて編集できるようになります。このEditorはgetValueという メンバ関数 を持っている必要があります。これは編集終了時に呼び出される関数であり、編集後の値を返すことが期待されます。 AgGridは公式に 何種類かのcellEditor を用意していますが、自前でEditorを実装することも可能です。そのときはgetValueメソッドを実装してあげることを忘れないように、、、 もしReactのFunctionalComponentを渡したい場合はcellEditor属性ではなくcellEditorFramework属性にFunctionalComponentを渡してあげなければならないことに注意してください。 また筆者は今回AgGridを使って初めて知ったのですが、FunctionalComponentに メンバ関数 を追加したい場合はuseImerativeHandleフックを使うとよいです。 編集後の値をパースしてセルの値を更新する部分 EditorのgetValueメソッドを通じて得られた値は通常そのままセルの値としてみなされます。しかし、何かしらパースしたい場合は <AgGridColumn valueParser={myValueParser}> のようにGridColumnのvalueParser属性にパース関数を指定することで、パース処理をはさむことができます。valueParser関数に渡される引数の詳細は 公式ドキュメント を参照してください。 更新されたセルの値に応じて、オブジェクト(rowData)の値を更新する部分 更新されたセルの値に応じて、オブジェクト(rowData)の値を更新する部分には2種類のパターンがあります。 ①フィールドを指定する <AgGridColumn field="lastName" /> のようにGridCloumnのfield属性を指定すると、その列のセルの値は、オブジェクトのそのフィールドの値を上書きします。次に紹介するvaluSetterが指定されていないときは、フィールド指定だと思って値を書き込もうとします。 ②valueGetterを指定する <AgGridColumn valueSetter={myValueSetter} /> のようにGridCloumnのvalueSetter属性に関数を指定すると、その列のセルの値(編集後の値)が引数として関数に渡されセルの値が更新する処理が走ります。valueSetterの詳細な引数は 公式ドキュメント を参照してください。 有償プランの機能 AgGridの有償版にはたくさんの便利機能がついています。この記事ではそのうちコピーペーストと補完について触れます。ここで扱う機能は有償ライセンスがなくても試すことは可能です(ただし、 ウォーターマーク が入る)。 どの機能もファイルの先頭に import "ag-grid-enterprise"; を追加しないと有効にならないので注意です。 コピー コピー時の挙動はカラムではなくAgGrid全体に共通のコールバック関数を指定する必要があります。 <AgGridReact processCellForClipboard={myCallBack}/> のようにGridのprocessCellForClipboard属性に関数を指定すると、選択したセルがコピーされたときに、その関数の返り値(string)が クリップボード にコピーされるようになります。複数セル選択でコピーしたときは各セルごとにこの関数が計算した文字列がTSV形式で クリップボード にコピーされます( \t 以外のデリミタを指定することも可能 )。 この関数はどの列も共通なので、どの列から呼び出されたかは関数内で判定しないといけません。以下のようにcolIdで判定するとよいでしょう。 const processCellForClipboard = (params: ProcessCellForExportParams) => { const {column} = params; const colId = column.getColId(); if(colId === 'lastName')return "LastName" + params.value; return params.value; }; ペースト ペースト時の挙動もカラムではなくAgGrid全体に共通のコールバック関数を指定する必要があります。 <AgGridReact processCellFromClipboard={myCallBack}/> のようにGridのprocessCellFromClipboard属性に関数を指定すると、選択したセルにペーストされたときに、 クリップボード の文字列がその関数に渡され、返り値をセルの新しい値として更新します。複数セル選択でペーストしたときは クリップボード の文字列をTSVだと思って、よしなに文字列を分割してくれます。 この関数はどの列も共通なので、どの列から呼び出されたかは関数内で判定しないといけません。以下のようにcolIdで判定するとよいでしょう。 const processCellFromClipboard = (params: ProcessCellForExportParams) => { const {column} = params; const colId = column.getColId(); if(colId === 'lastName')return "LastName" + params.value; return params.value; }; 補完 補完はセルの選択範囲の右下の小さな四角をドラッグしながら選択範囲を広げることで、よしなに値を産めてくれる機能です。言葉で説明するより以下のGIFを見たほうが早いかもしれません。 この機能は <AgGridReact enableRangeSelection={true} enableFillHandle={true}/> のようにenableFillHandleをtrueにすると有効になります。 デフォルトだと、数値の場合は 多項式 補完、そうでない場合は周期的に補完してくれるようです。もしそれ以外の方法で補完したい場合は <AgGridReact fillOperation={myFillOperation}/> のようにGridのfillOperation属性に関数を指定するとよいでしょう。 この関数はどの列も共通なので、どの列から呼び出されたかは関数内で判定しないといけません。以下のようにcolIdで判定するとよいでしょう。 const fillOperation = (params: FillOperationParams) => { const { column, initialValues } = params; const colId = column.getColId(); if (colId === "age") return initialValues.reduce((accum, val) => accum + val, 0); return ""; }; まとめ(再掲) 本記事で触れた内容をまとめた図です。 やり方が複数あるものについては以下のように考えておくと良いです。 - valueSetter,valueGetterは使わなくて良いなら使わないようにして、なるべくfield指定だけですむようにrowDataを設計する。 - 文字列処理だけで住むときはvalueFormatter, それ以上の処理( CSS で飾り付ける、リッチな機能をつける、etc...)が必要ならcellRenderer 最後に AGGridにはほかにもたくさんの機能があります。 表計算 っぽいUIが欲しくなったらぜひ一度使用を検討してい見てください!
はじめに 先日行われた atmaCup #12 にて、「CADDiチーム立ち上げ期MLE・DS積極採用中」チームが 245チーム中 9位 になりました。 惜しくも入賞は逃してしまいましたが、 コンペティション 内でチームとして参加していた中では最も良い成績を残す事ができました。 コンペティション の詳細な内容には触れる事はできないのですが、1週間という短期コンペにチームで参加した経験を振り返り、今後についての考察をまとめて何かの学びになればと思い、本記事を執筆しています。 atmaCup × Sansan戦略 今回は社内から猿田( @srt_taka )と竹原( @myaunraitau )、社外から河合( @vaaaaanquish )を加え、3人での参加しました。 前提として3者とも、 機械学習 エンジニアとしての業務経験、 コンペティション 参加経験を持っていたため、最初の数日はいくつかの特徴量 + LightGBM + 5fold CVで各々が進んでいました。 いくつか特徴量に関する知見を交換しながらも、各々submitする形でした。 中間日辺りから、LightGBMが伸び悩みはじめ、互いの特徴量作りも手数が減ってきたため、河合がtabtransfomer、テー ブルデー タを画像化した上でのCNN、竹原がtransformer等を試しはじめました。 最終日2日前にそれらをマージした上で最終スコアを出し、post processの処理を書き始め最適化を行う事で最終スコアとなりました。 良かったこと 今回短期の コンペティション にチームで参加するにあたって、手数が他チームより多かったと思います。 特に短期のチーム戦では、各々の知見を集約しながらも、「手数を増やす」ことが重要だったと感じます。 今回上位陣は、多くがDeep Neural Networkに関連した手法を用いており、私達もCNN、Transformerを試す事で大きくスコアを向上させる事ができました。 仕事や家庭もある中、短期間の コンペティション では、多くの論文を サーベイ したり、新しいライブラリをガツガツ導入するといった事は単独では難しい部分も多くあります。 その点において、今回チームメンバーの知見を集約し、様々なツール、手法に取り組めた事が、互いの知見向上の意味でも非常に良い体験でした。 利用したツール 具体的な手製の特徴量については触れられませんが、特徴量生成、特徴量選択、 モデリング においては、LightGBMやsklearn、PyTorchに加えて以下のツールを利用しました。 https://github.com/Ynakatsuka/kaggle_utils KaggleMasterである Yuki Nakatsuka( @ynktk1 )さんが公開しているkaggle_utilsです。 前処理で有用な2次特徴量変換が多く実装されており、過去のatmaCupでもKaggle Grand Masterである @takuoko さんがソリューションで利用していました。 以下のように、pandas.DataFrameを入力とし、簡易にCategoryEmbeddingやTargetEncoding、Aggregationを実装できます。 # pip install https://github.com/Ynakatsuka/kaggle_utils from kaggle_utils.kaggle_utils.features.groupby import GroupbyTransformer from kaggle_utils.kaggle_utils.features.groupby import DiffGroupbyTransformer param_dict = [ {'key': ['id'], 'var': box_feat_for_agg, 'agg': ['mean', 'max', 'min', 'std', 'nunique', 'rank']}, ... ] gt = GroupbyTransformer(param_dict = param_dict) gt.fit(df) df = gt.transform(df) gt = DiffGroupbyTransformer(param_dict = param_dict) gt.fit(df) df = gt.transform(df) ... https://github.com/pfnet-research/xfeat 特徴量が増えてくると、Aggregateの実行時間がかかるため、PFNさんが公開しているxfeatを利用しました。 こちらもkaggle_utils同様に、TargetEncodingやAggregationを簡易に実装できるものですが、いくつかのFeatureSelectionの アルゴリズム が実装されているだけでなく、入力をcudfにする事で高速に前処理を行う事ができます。 事前に作った1次特徴量をcudfに変換し、AggregationやFeatureSelectionを行いました。 import cudf from xfeat import aggregation from xfeat.pipeline import Pipeline from xfeat.selector import (ConstantFeatureEliminator, DuplicatedFeatureEliminator, SpearmanCorrelationEliminator) cdf = cudf.DataFrame.from_pandas(df) cdf, agg_cols = aggregation(cdf, group_key=key, group_values=cols, agg_methods=['mean', 'max', 'min', 'sum', 'std', 'rank']) selector = Pipeline([ DuplicatedFeatureEliminator(), ConstantFeatureEliminator(), SpearmanCorrelationEliminator(threshold=0.9), ]) cdf = selector.fit_transform(cdf[cols]) GPU に乗せる事で、半日程掛かっていた前処理が15分前後で終わるようになり、 コンペティション 中の作業効率促進に繋がりました。 https://github.com/awslabs/autogluon 竹原がLightGBM単体でsubmitを繰り返し様々な特徴量の実験を行っている間に、河合が AWS Labsが公開しているAutoGlounを用いてstacking、bagging、Out of Folds(oof)の生成を実行していました。 from autogluon.tabular import TabularPredictor # 利用するmodelの指定 models = ['GBM', 'CAT', 'XGB', 'NN', 'LR', 'KNN'] predictor = TabularPredictor( label=label, eval_metric='f1_macro', # 今回のコンペティションに合わせて problem_type='multiclass', groups=groups # group fold ) predictor = predictor.fit( train_df, # validationを自前で設定する場合は tuning_data=val_df time_limit=time_limit, # N sec以内にfitが終わるようモデル選択が行われる (default=0) presets='best_quality', num_bag_folds=num_bag_folds, num_bag_sets=num_bag_sets, num_stack_levels=num_stack_levels, hyperparameters={x:{} for x in models} ) # oofの取得 oof = predictor.get_oof_pred_proba(train_data=train_df) # predの取得 pred = predictor.predict_proba(df) # 各modelファイルはfit毎に別ディレクトリにあり # predictor自体のsave/loadはpickleで行える import pickle with open('./predictor.pkl', 'wb') as f: pickle.dump(predictor, f) 今回の コンペティション では、周囲のデータのoofを用いた特徴量が順位に大きく響きました。 AutoGlounは、modelごとのoof抽出などの コンペティション に必須な機能を備えているだけでなく、 GPU を自動検知してLightGBM等を GPU ベースで処理したり、ハイパパラーメタチューニングや、timeoutを指定して「指定時間内に終わるようにstacking、emsembleモデルを作る」ことを自動で行ってくれるため、特徴量の追加、削除などの側面でかなり PDCA を回しやすかったです。 今回は自前で特徴量加工を行いましたが、自動での特徴量加工も実装されているようです。 また、LightGBMやCatBoost、XGBoost、NeuralNetなど、実際によく使われる アルゴリズム を利用する実装になっているため、AutoMLとしての信頼度も高かったと言えます。 https://github.com/optuna/optuna コンペティション 終盤では、Optunaによる複合ルールベースのpost process最適化を実施しました。 今回の コンペティション では、あるclass Xの処理を行うかどうかで大きく順位が変動したのですが、河合がtestとtarin間のXの検出量の差の最小化問題として捉えたpost processを実装し、大きく順位を上昇させました。 最小化問題が目の前にある時、Optunaが非常に使い勝手が良く、これが最後のTop10争いに効きました。 次はこうしたい 良い点が多かった反面、最終的に入賞できなかったのが非常に残念な所です。 短期の 機械学習 コンペティション をチームで戦う上での反省点として、以下のように振り返りました。 誰かがリーダーシップを取り役割を割り振る 利用するパイプラインを決めておく データのやり取り等のフォーマットを固めておく 今回各々が簡易コードで特徴量加工部などを共有するのみで終始参加していたため、お互いの進捗が見えづらいだけでなく、前半同じような特徴量生成を全員が書くことに時間を使ってしまいました。 個々がデータをよく見る時間としては有用だったかもしれませんが、全員でディスカッションしていればすぐ出たもので、実装も1つで済んだ所です。 3人のうち1人を挑戦的なモデルチューニングに割り当てる事で、入賞が狙えた位置にいると思うと、実際悔しい限りです。 また、短い コンペティション かつ、互いの開発のバックグラウンドが分かっていない状況だったため、パイプラインツールの統一やデータのやり取りも雑多に行っており、途中特徴量データの受け渡しに失敗して時間をロスしたりしていました。 時間が限られた コンペティション では、事前にチームを組むメンバーで共同開発の練習をしておくと良さそうです。 全体振り返り チーム名の通り、現在CADDiでは 機械学習 ・データサイエンスのチームを立ち上げようとしている所です。 既に数人メンバーが社内で活躍していますが、まだまだプロダクトを推し進めてくれるメンバー探しをしています。 そこで、業界内でのプレゼンスを高め実績を残す意味でも「絶対優勝するぞ!」と応募しました。 atmaCup #12は、一週間という非常に短い期間かつオンラインで開かれました。 タスクもテー ブルデー タを扱うもので、長期間の コンペティション に比べ、立ち上げ期の我々にとっては非常に参加しやすいものでした。 実際参加してみて、運営からのベースライン、 チュートリアル などのヒントや、guruguru *1 上でのディスカッションの盛り上がりが、一週間で行われるものとは思えない程濃密で質が高く、非常に楽しかったです。 チームメンバーが互いに信頼する意味合いでも、同一のタスクをオンラインかつ短期間という制約のある状態で解く事自体に、価値があったと感じています。 各々仕事の隙間を縫っての参加となりましたが、特徴量や手法に関するディスカッションは普段の業務以上に行う事ができました。 この経験を活かし、次回はより良い結果を残したい所です。 おわりに 今回はSansan様のホストする コンペティション で、名刺に関連した内容でした。 CADDiでは、図面を多く扱うプロダクトを作っていますが、今回の コンペティション の課題に似たテー ブルデー タ、画像認識の課題が山積みとなっています。 是非、新しい 機械学習 ・データサイエンスチームへジョインして、より強い力で一緒に前に進んでくれるメンバーを募集しています。 We are hiring! *1 : atmaCupがホストされているWebサイト
こんにちは。Platform チームの飯迫 ( @minato128 )です。 CADDi ではこれまで Hosted Redash(app.redash.io) を利用していたのですが、残念ながら 2021/11/30 に End of Life になるので、10 月末に Self-Hosted Redash 環境を構築して移行しました。今回はそのときやったことを紹介します。 移行の流れ 新しい Redash 環境を v10 で構築する 公式の移行ツールを利用してデータ移行する 監視を追加する 新しい Redash 環境を v10 で構築する まず、 移行ツールは移行先として v10 を前提としている ので、新しい環境は v10 である必要があります。ちなみに、 v10 は 10/2 にリリースされた現時点の最新版 です。 v10 であればどんな方法で構築しても問題ないのですが、今回は社内用 GKE Cluster に入れることにしました。監視もリソースも集約でき、Helm Chart を使うことで初期導入コストも低そうだったからです。 移行対象 GKE Cluster は、このような構成となっており、 基幹システム(社内システム)のほとんどをマルチテナントで運用している ArgoCD で GitOps している Secret は External Secrets + GCP Secret Manager で管理している Datadog で監視、ログを参照できるようにしている 次のように進めていきました。 (1) Terraform に必要な GCP リソースを追加してデプロイ GKE に新しい Node pool の追加 基幹システムに出来る限り影響を与えないようにしたかったので、いったん Node pool ごと分離 移行後のリソース状況をみながら見直す想定 CloudSQL( PostgreSQL ) の追加 DB の管理はできるだけしたくなかったため バックアップもおまかせ (2) Redash Helm Chart の ArgoCD Application Manifest を追加してデプロイ 現在の Chart version は 2.3.1 で Redash version は v8 となっています。ほしいのは v10 だったため、まず v10 に対応する PR を作成しました。(Redash ほど有名なツールの Chart が最新バージョンに対応してないとは思っておらず、これは想定外の作業でした) https://github.com/getredash/contrib-helm-chart/pull/102 追加したファイルの一覧はこれらで、 ├── applications │   ├── redash-assets │      └── overlays │      └── prod │      ├── kustomization.yaml │      ├── redash-postgres-secret.yaml │      └── redash-secret.yaml ├── argocd │   ├── overlays │      ├── prod │         ├── pj-name │            ├── helm-redash.yaml 以下が helm-redash.yaml のイメージです。(実際のものではなくいろいろ端折っています) apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : redash namespace : argocd finalizers : - resources-finalizer.argocd.argoproj.io spec : project : project-name source : repoURL : "https://minato128.com/contrib-helm-chart/" chart : redash targetRevision : 2.4.1 helm : valueFiles : - values.yaml values : |- # env -- Redash global envrionment variables - applied to both server and worker containers. env : PYTHONUNBUFFERED : 0 REDASH_RATELIMIT_ENABLED : "false" REDASH_MAIL_SERVER : "smtp.sendgrid.net" REDASH_MAIL_PORT : "587" REDASH_MAIL_USE_TLS : "true" REDASH_MAIL_USERNAME : "apikey" REDASH_MAIL_DEFAULT_SENDER : "redash@caddi.jp" ## Redash application configuration redash : # -- REQUIRED `REDASH_SECRET_KEY` value. Secret key used for data encryption. Stored as a Secret value. # helm template 通らないので入れているが、実際はexistingSecretが採用される secretKey : "DUMMY" # -- `REDASH_STATSD_HOST` value. # @default -- 127.0.0.1 statsdHost : "datadog-statsd-service.datadog" # -- `REDASH_GOOGLE_CLIENT_ID` value. googleClientId : "XXXXXXXXXX" # -- REQUIRED `REDASH_COOKIE_SECRET` value. Stored as a Secret value. # helm template 通らないので入れているが、実際はexistingSecretが採用される cookieSecret : "DUMMY" # redash.existingSecret -- Name of existing secret to use instead of either the values above ## This secret must contain keys matching the items marked "Stored as a Secret value" above. existingSecret : "redash-secret" # we dont use ingress ingress : # ingress.enabled -- Enable ingress controller resource enabled : false # CloudSQL # externalPostgreSQLSecret -- Read external PostgreSQL configuration from a secret. This should point at a secret file with a single key which specifyies the connection string. externalPostgreSQLSecret : name : redash-postgres-secret key : connectionString ## we dont use this postgresql postgresql : # postgresql.enabled -- Whether to deploy a PostgreSQL server to satisfy the applications database requirements. To use an external PostgreSQL set this to false and configure the externalPostgreSQL parameter. enabled : false # helm template 通らないので入れているが、実際はexternalPostgreSQLSecretが採用される postgresqlPassword : "DUMMY" ## Configuration values for the redis dependency. This Redis instance is used by default for caching and temporary storage [ref](https://github.com/kubernetes/charts/blob/master/stable/redis/README.md) redis : # redis.enabled -- Whether to deploy a Redis server to satisfy the applications database requirements. To use an external Redis set this to false and configure the externalRedis parameter. enabled : true destination : server : "https://kubernetes.default.svc" namespace : redash syncPolicy : syncOptions : - CreateNamespace= true v10 対応の PR はまだマージできていないので、fork した Repository の Package を参照している v10 の スキーマ に対応した DB で運用していれば、あとで Chart を入れ替えても問題ない 移行のブロック要因にはならない 移行データが多いと API 制限に当たってしまうので REDASH_RATELIMIT_ENABLED を false にする Secret は existingSecret と externalPostgreSQLSecret で指定する redash-assets という名前で、ArgoCD Application を追加して External Secrets をデプロイ REQUIRED な設定には、実際には使わなくても空でない値を入れておかないと helm template コマンドが通らない Redash は Statsd に対応しているが、現在の Chart では REDASH_STATSD_HOST に status.hostIP を指定できないので、Proxy Service を作って指定している 公式の移行ツールを利用してデータ移行する 概要は @ariarijp さんのスライドがわかりやすいです。 https://speakerdeck.com/ariarijp/you-should-know-about-hosted-redash-eol-and-redash-migrate 基本的には 公式ドキュメント に書いてある通りにやっただけです。 redash-toolbelt をインストールする redash-migrate init 移行に必要な設定が meta.json として生成される redash-migrate --help の結果のコマンドを上から順に実行していく データに依存関係があるため 移行の from/to 情報が meta.json に蓄積される こちらが実行後の meta.json の一部抜粋で、データタイプごとに移行元と移行先の id が状態管理されていることがわかります。 " queries ": { " 233300 ": 1 , " 233440 ": 2 , " 234065 ": 3 , } 以下がポイントです。 redash-migrate は最新バージョンを使う 初期バージョンだと group の重複など致命的なバグがある 現時点でも細かいバグは残っている https://github.com/getredash/redash-toolbelt/issues セキュリティ上の都合で、Data Source の Secret は移行されない Redash UI 上で再設定が必要 冪等性があり何度でも実行可能 リソースの id 単位で移行するため、移行済みリソースを origin 側で変更しても再同期はできない Origin 側も Destination 側も Redash 本体の Web API でアクセスする 接続周りに気を使う必要がない 移行量が多い場合は、 Destination 側の API Limit を変えておく 環境変数 REDASH_RATELIMIT_ENABLED 監視を追加する モニタリングのためにこれらを追加しました。 Datadog Logs の Pipeline 追加 一部 Parse しづらいログがあるので手動で Parser を書いてレベル(status)を正しく Remap する Datadog Monitor の追加 GKE や CloudSQL は既存の監視を利用 Terraform で管理しており、Module を参照 Error log 数 Synthetics(外形監視) https ://[REDASH_HOST]/ ping を監視 Queue 監視 前提として、Redash v10 は RQ(Redis Queue) を利用しており、job(message)を処理できているか監視する必要がある 個人的に Queue は Message Age で監視する派ですが、既存 metrics にはないので、message がたまり続けてないか観測することにした redash.rq.jobs.created が enqueue 時、 redash.rq.jobs.started が処理開始時に 記録されている ので、 created - started が n (正の整数)より大きい状態が一定期間続くと worker が message をさばけていないことになる n は traffic や worker count に依るが 0 に近いほうがよい Datadog Dashboard の追加 俯瞰して状況把握できるように まとめとポイント 新しい環境は v10 で作る 移行ツールが v10 しかサポートしていないため Redash Helm Chart は現時点では Redash v8 データ移行は、公式の移行ツールで簡単にできる 移行データに依存関係があるので、help コマンドの順番通りに実行していく セキュリティ上の都合で、Data Source の Secret は移行されないので UI 上で再設定が必要 冪等性があり何度でも実行可能 Origin 側も Destination 側も Redash 本体の Web API でアクセスするので、接続周りに気を使う必要がない 移行量が多い場合は、 Destination 側の API Limit を変えておく
Summary This post is my hobby and has nothing to do with work. I have wanted Extensible Records (a library in Haskell ) for a long time. The time has finally come. The language features we need to implement it are there in C++ 20! Therefore, this post will show you how to emulate row polymorphism in C++ 20. The latest, complete code can be found in this repository . Row Polymorphism Row polymorphism is a kind of polymorphism that allows one to write programs that are polymorphic on record field types (also known as rows, hence row polymorphism). Here is a TypeScript example: type foo = { first: string, last: string }; const o = { first: "Foo", last: "Oof", age: 30 }; const p = { first: "Bar", last: "Rab", age: 45 }; const q = { first: "Baz", last: "Zab", gender: "m" }; const main = <T extends foo>(o: T) => (p: T) => o.first + o.last main(o) (p); // type checks main(o) (q); // type error Mitama.Data.Extensible.Record In TypeScript, it is implemented as a language feature, but in C++ 20, there is no such feature, so we need to emulate it somehow. The reference is the famous Haskell library extensible which emulates the same feature. In the end I succeeded in making a library which allows the following syntax. import Mitama.Data.Extensible.Record; #include <iostream> #include <format> using namespace mitama::literals; using namespace std::literals; void print(mitama::has<"name"_, "age"_> auto person) { std::cout << std::format("name = {}, age = {}\n", person["name"_], person["age"_]); } int main() { using mitama::as; // declare record type using Person = mitama::record < mitama::named<"name"_, std::string> , mitama::named<"age"_, int> >; // make record Person john = Person{ "name"_v = "John"s, "age"_v = 42, }; // access to rows john["name"_]; // "John" john["age"_]; // 42 print(john); // OK auto tom = mitama::empty += as<"name"_>("Tom"s) ; print(tom); // ERROR: constraints not satisfied } Guide-level explanation mitama::named The syntax "name" and "age" is a UDL (User-Defined Literal). These literals create a type mitama::static_string which is a structual type (non-type template enabled class). Thus, mitama::named<"age"_, int> is a wrapped type of int named with mitama::static_string . You can construct mitama::named<_, T> by applying operator%(static_string, T) . mitama::named age = "age"_v = 42; mitama::named has some interfaces like std::optional . mitama::named age = "age"_v = 42; age.value(); // 42 mitama::named name = "name"_v = "Mitama"s; name->length(); // calls std::string::length and returns 6 mitama::record mitama::record<Rows... > is constrained to only take mitama::named in Rows... and strings in mitama::named must be distinct. When initializing a particular record type, the order of the initializers is free, because the Row strings are distinct. using Person = mitama::record < mitama::named<"name"_, std::string> , mitama::named<"age"_, int> >; // OK Person john = Person { "name"_v = "John"s, "age"_v = 42, }; // Also OK Person tom = Person { "age"_v = 42, "name"_v = "Tom"s, }; We can make mitama::record with CTAD (Class template argument deduction). In this case, Rows... of the record is inferred in the order of the initializers, so different record types are inferred depending on the order of the initializers. // john: record< named<"name"_, std::string>, named<"age"_, int> > auto john = mitama::record { "name"_v = "John"s, "age"_v = 42, }; // tom: record< named<"age"_, int>, named<"name"_, std::string> > auto tom = mitama::record { "age"_v = 42, "name"_v = "Tom"s, }; Use mitama::shrink to convert between records that have the same rows but in a different order. // decltype(john) _ = tom; // ERROR decltype(john) _ = mitama::shrink(tom); // OK In fact, a = shrink(b); converts b: B to a: A where A ⊆ B . using Person = mitama::record < mitama::named<"name"_, std::string> , mitama::named<"age"_, int> >; auto tom = mitama::record { "name"_v = "Tom"s, "age"_v = 42, "gender"_v = "m", // an extra row }; Person tom2 = mitama::shrink(tom); // OK Reference-level explanation How to specify string literals as a non-type template parameter Basic idea In order to specify string literals as a non-type template parameter, we first create a structural type class that holds const CharT [N] . CharT is a structural, and an array of a structural type is also structural. And the class such that all base classes and non-static data members are public and non-mutable and the types of all bases classes and non-static data members are structural types or (possibly multi-dimensional) array thereof is structural. Thus, fixed_string below is a structural type. template<std::size_t N, class CharT> struct fixed_string { static constexpr std::size_t size = N; using char_type = CharT; constexpr fixed_string(CharT const (&s)[N]) : fixed_string(s, std::make_index_sequence<N>{}) {} template<std::size_t ...Indices> constexpr fixed_string(CharT const (&s)[N], std::index_sequence<Indices...>) : s{ s[Indices]... } {} CharT const s[N]; }; We can use fixed_string as a non-type template parameter, and CTAD will automatically infer CharT and N from string literals. template <fixed_string S> struct static_string { using char_type = typename decltype(S)::char_type; static constexpr auto size = decltype(S)::size; }; int main() { using ss1 = static_string<"test">; static_assert(std::same_as<char, typename ss1::char_type>); using ss2 = static_string<u"test">; static_assert(std::same_as<char16_t, typename ss2::char_type>); using ss3 = static_string<U"test">; static_assert(std::same_as<char32_t, typename ss3::char_type>); } Complete idea In order to be able to handle std::string_view at compile time, static_string should have static constexpr std::string_view . template<fixed_string S> struct static_string { using char_type = typename decltype(S)::char_type; static constexpr std::basic_string_view<char_type> const value = { S.s, decltype(S)::size }; }; Furthermore, creating a static_string with UDL gives an appearance like "name" . The operator "" () allows us to pass string literals directly to the template parameter, so that we can construct a static_string using the fixed_string deduced by CATD. namespace mitama:: inline literals:: inline static_string_literals{ template <fixed_string S> inline constexpr auto operator ""_() noexcept { return static_string<S>{}; } } How to access to rows in a record mitama::named has a protected member operator[] for record. template <static_string Tag, class T> class named { public: static constexpr std::string_view str = decltype(Tag)::value; // ... protected: template <auto S> requires (static_string<S>::value == str) constexpr delctype(auto) operator[](static_string<S>) const noexcept { return storage::deref(); } }; Rows... in mitama::record<Rows... > should all be mitama::named . mitama::record inherits Rows... and make operator[] visible by using declaration. template <named_any ...Rows> class record : protected Rows... { public: // ... using Rows::operator[]...; }; This will enable to access rows through operator[] by overload resolution. How to check the equivalence of rows in two records In C++ 20, - lots of features can be used at compile time, - lots of classes and functions can be used at compile time, - template syntax for generic lambdas is available and - consteval is available. However, due to lot of compiler bugs, some lack of implementation and some Core Issues, it has become difficult to deliver accurate and elegant code to you. So I leave you with the challenge of the modern metaprogramming techniques in C++ 20. The code, with workarounds everywhere, can be found here . Appendix A: development environment Visual Studio 2022 Version 17.0.0 Preview 5.0 References ISO/IEC 14882:2020 Programming Languages -- C++ extensible