TECH PLAY

株式会社Insight Edge

株式会社Insight Edge の技術ブログ

180

こんにちは。Insight Edgeでデータサイエンティストをしている伊達です。 皆さんの会社では、どのような交流施策を導入されていますか? 新型コロナが5類感染症に移行され、弊社メンバーの出社頻度は徐々に増えてきたものの、現状はハイブリッドワークが基本となっています。 ハイブリッドワークだと、各自が好きな出社頻度を決められる反面、全員が常にオフィスにいるわけではないため、組織内での交流や情報交換の場をうまく作らないと、メンバー同士の会話はプロジェクトの定例やミーティングのみになってしまいます。 今回は、私がこれまでに在籍した分析組織で試した交流施策を紹介します。 大学院の研究室 ゼミ ランチ会(論文紹介) 雑談 前職:金融機関子会社のデータ分析系部署 もくもく会(兼雑談会) 最近の論文/ニュース/講演をチェックする会 現在:総合商社のDX内製化組織 勉強会 分析チームLT会 論文読み会 まとめ 大学院の研究室 ゼミ 大学院で所属していたのはコンピュータビジョン系の研究室で、画像認識、3次元復元、SLAMなどの研究をしている学生が多かったです。 研究室であればどこもやっていると思いますが、私が所属した研究室でも毎週1時間ほど、学生が自分の研究内容を紹介するゼミを開催していました。 特色は、学生の約半数が留学生だったため、発表・議論が英語だったことです。 自分の研究内容を英語で他人に発表し、議論する訓練を日々できたことは、国際学会等に参加するための準備として大いに役立ちました。 一方で、まだ研究を始めたばかりで英語の苦手意識がある学部生は割と辛そうでした。 ランチ会(論文紹介) 週に一度、有志の学生で集まり、先生が提供してくれたお弁当を食べながら、それぞれ最近見つけて気になっている論文を紹介する会です。 当時(2017〜2019年頃)は、画像認識系の深層学習手法の研究が急速に進んでおり、毎週面白そうな論文が出ていてネタに尽きなかった、という思い出があります。 こういう論文読み会/紹介を開く場合は、参加メンバーの興味がある程度近い必要があるかなと思います。 自分たちの場合は全員コンピュータビジョン分野の研究をしていたので、「こういう論文もあるぞ」と話が派生していったり、参加メンバーから手法の価値や実験結果の妥当性についてツッコミが入ることもあり、盛り上がりました。 雑談 当時は学生だったので勤務時間という概念がなく、(集中してなさそうな人の)デスクに行って雑談したり、研究室の共有スペースにあるソファでコーヒーを飲みながら、だらだら雑談していました(これを交流施策と読んで良いのか分かりませんが)。 やはり研究生活ではしんどい日もあるので、誰かと他愛のない話をしたいこともありますよね…? こういう場では、かっちりした研究の話はあまりせず、ぼんやりとした課題意識、人生、就活、趣味といったことをみんなと話せて、割と大事な時間でした。 こういう「ムダ」に見える時間も実は大事だと思っていますが、仕事中に長々と雑談していると怒られそうなので、社会人になるとなかなか難しいところですね。 前職:金融機関子会社のデータ分析系部署 もくもく会(兼雑談会) 同じ部の若手3〜4人程度で、毎日夕方に30分程度web会議でマイクオン&ビデオオフにしたまま、作業しながら雑談をする会を開いていました。 普段のプロジェクトの定例やミーティングだと上司や顧客がいるため、そういった場であまり話せないような柔らかい質問、ぼんやりとした課題意識、その他相談ができる場でした。 目的としては大学院の時の雑談と似ていて、「わざわざ会議を開くほどではないけど、できれば聞いておきたいことや確認しておきたいこと」を話したり、仕事とはあまり関係ない話もできたりすることで、個人的には良い場だったと思っています。 一応もくもく会という体で作業をしている(ように見える)ので、会社の勤務時間中でも、こういう場は開催しやすいかもしれません。 最近の論文/ニュース/講演をチェックする会 週に1回、各自気になった論文・記事・ニュースを共有したり、最近の学会等の講演の動画をみんなで視聴して議論したりする会です。 AIやクラウドは日々新たな手法やツールが発表され、一人での情報収集には限界があるため、こういった場で、自分が見落としていたが社内の他の人が見つけた良い論文や記事などを発見でき、良かったです。 紹介内容については、がっつりスライドを作るというより、論文や記事を画面共有しながら雑談する感じなので、あまり準備に時間がかからなくて良かったです。 一応毎週アウトプットの場があるので、日々目にする情報を意識的にスクリーニングし、紹介するに値するかどうか考える習慣がつきます。 現在:総合商社のDX内製化組織 勉強会 弊社では業務時間の10%を自己研鑽に充てることが認められており、その一環として、特定のトピックに興味を持ったメンバー同士で集まって、定期的に勉強会を開催しています。 トピックは開発系、分析系、デザイン系など色々で、トピックによっては異なるチームからの参加があり、チームの垣根を超えた交流や知見共有の機会にもなっています。 自分が最近参加したのは、デザイン経営勉強会、時系列分析勉強会、Kaggle勉強会、MLOps勉強会でした。 分析チームLT会 これまで分析チーム内のデータサイエンティストの知見共有の場があまりなかったため、社内のメンバーが立ち上げてくれました。 LT会なので時間は30分で、毎週1人、最近の分析案件や興味を持って調べたことなどを共有しています。 組織の中で学びを共有し、最新の技術にみんなでキャッチアップしていくために、こういう場は重要だと思っています。 論文読み会 これは上記の分析チームLT会で、私が大学院の時の論文読み会について紹介したところ、他のメンバーが立ち上げてくれた会です。 毎週ランチの時間に30分程度、発表担当者が調べた論文について発表し、みんなで議論します。 トピックによっては論文だけではなく、実務における分析に役立ちそうなものであれば、Kaggleコンペの解法やブログ記事を紹介することもあります。 まとめ 今回は、私がこれまでに在籍した分析組織で試した交流施策を紹介しました。 私が紹介した交流施策を目的別に整理すると、下記のようになるかなと思います。 最新技術をざっくり知りたい → 論文紹介、ニュースや講演のチェック 特定のトピックや技術を深く知りたい → ゼミ、勉強会 社内交流 → 雑談、もくもく会(兼雑談会) 交流施策は「これをどの組織も導入した方が良い」という絶対的なものはなく、各組織のありたい姿や課題によって適切にデザインし、導入していくべきだと考えています。 今回の記事が皆さんの組織における交流施策の検討・導入のきっかけになれば幸いです。
こんにちは、Insight EdgeでData ScientistのKNです。Insight Edgeでは多種多様なDX案件を手掛けており、その多くでは機械学習や統計解析を用いた分析コードを作成することが求められます。 分析チームの開発言語は基本的にPythonが使われています。PoCの段階ではJupyter Notebookを用いてEDAや可視化、モデル構築がよく行われます。そして、PoCを終え期待した効果が確かめられた場合はPoCで作成したコードをそのまま採用せずに、長期運用を前提としたproduction-readyなコードに書き換える必要があります。 本記事では、主にデータ分析で使われるPythonコードを対象として、adhocな分析コードを実運用を見据えたコードに書き換える際役に立つと考えている取り組みについて紹介します。 また、本記事の内容はリファクタリングに関連はしていますが、設計方針やプログラミングスタイルについては取り上げません。それらは良いコードを書く上では重要ですが、すでに優れた書籍が数多く存在し、習得するのも時間がかかります。今回はPythonに特化して、即効性が高く、始めやすいtipsについての紹介になります。 目次 なぜ分析コードを修正する必要があるのか 型情報の付与、フォーマッターとリンターの活用 バリデータの活用 テストの活用 まとめ なぜ分析コードを修正する必要があるのか 結論から述べると、「コードの信頼性を向上させ、運用コストを下げるため」です。ここでの運用コストとは様々な意味合いが含まれます。 例えば、コードの可読性が低いと、コードの理解に時間がかかり、バグを誘発する原因にになります。また、ロジックの正しさの保証が不十分だと、変更の際に追加の確認作業が発生します。 さらに、データ分析では大量のデータ処理をする特徴があります。もし不正なデータが入力された場合、プログラムの挙動が問題になることがあります。実行過程でエラーが発生するならまだいいのですが、エラーが出ないまま間違った処理をしてしまい、分析の結論に影響を与えた結果、間違った意思決定を促す可能性があります。これらの問題点から、次の観点に沿ってコードを改善することが重要だと考えます。 図1:コードの改善軸と実現するための手段 「コードの可読性」、「データの保証」、「ロジックの保証」という3つの改善軸があり、それぞれに対して、「型情報の付与、フォーマッターとリンターの活用」、「バリデータの活用」、「テストの活用」で対応できると考えます。 もちろん、それぞれの関係は互いに独立ではありません。型情報を与え、型チェックを行うことでロジックやデータに対する保証は向上します。テスト前提のコードを書くことで、適度にコンポーネント化されたプログラムは可読性が高くなります。そういった相乗効果はありますが、寄与度の高さを踏まえると図のような関係になるかと思います。 以下、それぞれの項目について詳しく紹介します。 型情報の付与、フォーマッターとリンターの活用 型情報の付与 Pythonは型が無い動型言語です。それ故に、気軽に始められ、少ない記述量で動作可能なものを作成できます。データ分析現場では大変便利なツールとして、重宝されています。一方で、正しく動作可能かどうかは実際に動かして見るまで分からない面があるので、コードの可読性が低くなりがちです(ダックタイピングと呼ばれる所以です)。 Pythonは3.6以降、「型ヒント」と言って型情報を追記できる機能が追加されました。型ヒントを追加することで、コードの可読性を向上させ、コードの理解にかかる時間を減少させることができます。また、型ヒントを追加することで、IDEの補完機能を活用でき、開発効率も向上します。 # 型ヒント例 # 型ヒント追加前 def convert (texts): ... # 型ヒント追加後 # 関数シグネチャの情報だけでコードの挙動が理解できるようになる。 def convert (texts: List[ str ]) -> Documents: ... また、既存のコードに型情報を追加する際に、もし型情報を書きづらい箇所があった場合、そこにはリファクタリングする余地のある可能性が高いです。 # 型ヒントがうまく書けない例 data = { 1 : 'c' , 'a' : 2 , '1' : [ 'd' , 'e' ]} # Anyは任意の型を許す # これは極端な例だが、辞書型でKeyやValueが異質な型の場合、可読性が著しく落ちる def process (data: Dict[Any, Any]) -> Dict[Any, Any]: ... process(data) ただし、型ヒントは型情報を追加するだけで、実際には型チェックを行いません。型チェックを行うためには、mypyという解析ツール(リンター)を用いる必要があります。mypyは、型ヒントを追加したコードを解析し、型チェックを行うツールです。mypyを用いることで、型ヒントの追加漏れや、型ヒントと異なる型の値を代入している箇所を検出できます。 mypyは大変便利なツールですが、mypyを使うことで静的言語のような型保証が得られるかと言えば、それは疑わしいかと思います。mypyはあくまで型ヒントが互いに矛盾なく使われていることを保証するのみで、実行時に型ヒントとは異なる値が変数に代入されたとしても問題にはならないからです。 また、mypyを厳密に適用すると(オプションで --strict と設定)、かなり冗長に型情報を追加する必要があり、反対に可読性が低下する可能性もあります。シンプルに書きつつPythonを使用するメリットが減少してます。 さらに、自分のコードに厳密に型ヒントを強制したとしても、インポートしているサードバーティ製のライブラリに型ヒントが追加されていない場合がよくあります。代表的なライブラリでは、型情報ファイルをダウンロードできたり、型情報ファイルを自動生成できるものもありますが、手間の割に効果が限定的です。 どの程度の厳密性を適用するかは組織の方針やプロジェクトの性質から判断する必要があると感じます。また、既存のコードに対しいかにmypyを適用していくかは 本家のドキュメント に記述がありますので参考にしてみてください。 フォーマッターとリンターの活用 フォーマッターとは、コードスタイルを整えてくれるツールです。代表的なPythonのフォーマッターは、Blackというツールです。Blackは、PEP8に準拠したコードを自動生成してくれます。PEP8は、Pythonのコーディング規約です。Blackを使うことで、コードのスタイルを統一できます。それによって、コードの可読性を向上させ、コードのフォーマットでの意見の違い等余計なことに悩む必要も無くなります。また、isortと呼ばれるモジュールのインポート順をフォーマットしてくれるツールもあります。 リンターはソースコードを(実行前に)静的解析して、エラー等をチェックしてくれるツールです。先ほど紹介したmypyも型チェックのリンターでした。それ意外の代表的なリンターとしてFlake8があります。Flake8はコードのエラーチェックを行うpyflake、PEP8スタイルに準拠しているかチェックをするpycodestyle, コードの複雑度をチェックするmccabeをバンドルしたものになります。注意点としては、あくまでチェックツールなので自動的に修正してくれるわけではありません。そのため、コードスタイルの補正を先にBlackとisortで行ってから、Flake8でチェックするのが良いかと思います。 基本的にこれらのツールの適用に発生するコストは低いため、極力活用することが望ましいです。 バリデータの活用 PoCの作業中では、データにどのような値が含まれているか観察しながら分析をするので、データの正しさに対する注意、関心が怠りやすくなるかと思います。しかし、実際の本番導入以降でどのようなデータが投入されるかは未知数です。たとえ、データの内容について事前に確約していたとしても、必ずしも守られるとは限りません。 バリデータを活用することで、投入されるデータの正しさについて完全とはいかなくても、十分な保証を得ることができます。特に、実行過程のなるべく早い段階で適用することが望ましいです。早い段階でデータの不当性を見つけることで、副作用を伴う処理(DBへの書き込み等)を防ぐことができます。また、事前にデータの正当性を保証することにより、冗長なデータをチェックする処理を省き、ロジックコードは本質的な処理がメインになリます。その結果、可読性も向上し、データチェックのエラーハンドリングも減少します。 まず紹介するツールは、 pydantic になります。pydanticは型情報やメタ情報を使って、実行時にデータのバリデーションを行ってくれるツールです。実行時のデータチェックを伴うデータクラスと見なすとイメージつきやすいかと思います。 # pydantic例 from pydantic import BaseModel, PositiveInt, ValidationError class User (BaseModel): id : int name: str = 'John Doe' signup_ts: datetime | None tastes: dict [ str , PositiveInt] # この場合、idがintでない、signup_tsが無いという理由でエラーとなる external_data = { 'id' : 'not an int' , 'tastes' : {}} try : User(**external_data) except ValidationError as e: ... 次に、 pandera の紹介になります。panderaは、pandasのデータフレームに対して、スキーマを定義してバリデーションを行うことができます。データ分析の際には、表形式のデータを扱うことが多いので便利です。 # pandera例 schema = DataFrameSchema( { "性別" : Column( str , checks=Check( lambda s: s.isin([ "男性" , "女性" ]))), "年齢" : Column( float , nullable= True , checks=Check.gt( 0 )), "身長" : Column( float , nullable= True , checks=Check.gt( 0 )), } ) df = base_list_schema.validate(df) テストの活用 テストコードを書くことのメリットは色々なところで語り尽くされているので、異論はないかと思います。 ただし、通常のシステム開発と異なり、データ分析や機械学習領域では、テスト駆動開発や実装とテストをほぼ同時に進めるやり方は主流でないと(個人的には)感じています。 データ分析や機械学習のPoCの段階では、ほとんどの場合、より良いKPI(予測精度)の達成が目的となります。その実現手段は何でも良いため、様々な特徴量加工やモデル構築を試し、最も良いアプローチのみを採用し、それ以外のほとんどは無駄になることが多いです。試行錯誤中のコードに対してテストを書いて確認はできますが、結局は無駄になるコードへのテストは中々行われづらいの実情かと思います。 しかしながら、運用が決まった後は、テストコードを必ず書いたほうが良いです。なぜなら、データ加工やモデル構築の処理が確定したので、その処理の正しさを保証する必要があるからです。これは、特にリファクタリングや機能追加を行う際に重要です。本来の機能が壊れていないかことを容易に確認しながら、作業できるため信頼度が高くなります。また、他の人がコードを読む際にも、テストコードがあることでコードの意図を理解しやすくなります。これらの効果はコードの規模が大きくなるほど顕著になります。 一方で、テストはロジックの正しさをある程度保障するものですが、完全ではありません。あくまで限られたテストケースのみにおいて正しさを保障するに過ぎません。また、テストを書くこともコストかかりますし、量が増えてくるとコードの変更ごとのテスト実行の時間も無視できなくなります。さらに、プログラムの仕様を大幅に更する場合(データ分析ではよくありますが)、既存テストも書き直す必要が発生するので、変更への負荷が重くなります。 テストを全く書かないというのはありえないですが、テストをどの程度充実させるかどうかはPJの規模や性質によって変わってくると思います。私は後段の理由もあり、テストの充実度は通常のシステム開発よりも低くなっても良いと思います。データ分析、機械学習領域のテスト方針については私自身十分に結論が出ていないので、今後も検討していきたいと思います。 機械学習の場合の注意点 機械学習の場合の厄介な問題として、データ自体が流動的なため、たとえコードの処理に問題がなくても、精度悪化する場合があります。これは結果としてアプリケーションの本来の機能が達成できていないという点で問題です。 精度劣化の問題に対処するには、継続的にPJのKPIやモデルの精度を監視する体制が必要です。この作業をスポットで行う場合は手動でも問題ないかもしれませんが、日々の業務サイクルに組み込もうとすると、MLOpsの構築が必要なってきます。 テストコードでは精度の保証はできないので、ある程度のテストコードでロジックの正しさを保証しつつ、精度監視をを行うことで機械学習アプリケーションの品質を担保できます。 テストフレームワークについて 代表的なPythonのテストフレームワークは pytest です。標準ライブラリのunittestもありますが、それよりもシンプルで柔軟にテストコードを書くことができます。pytestの使い方については公式ドキュメントを参照してみてください。 最近では、ChatGPTに聞いてもそれなりの回答をしてくるので活用しない手はないです。ただし、業務コードで聞く場合は社内ポリシーに従ってください。 図2: 素数判定のテストコードをchatgptに聞いた結果 まとめ データ分析、機械学習コードの品質を上げるために、「コードの可読性」、「データの保証」、「ロジックの保証」という3つの改善軸があることを紹介しました。 コードの可読性を容易に実現する手段として、「型情報の付与、フォーマッターとリンターの活用」を紹介しました。フォーマッターやリンターは手間が掛からず実行できるので積極的に使うべきです。mypyは厳密に適用すると大変で却って可読性が落ちるので、PJの規模や性質に応じて適用範囲を決めると良いです。 データの保証をするためのツールとして、pydanticとpanderaを紹介しました。両者ともより細かい制約を設定できます。外部データの読み込み時に適用することで、初期段階で不正を検知できますし、コードもよりシンプルになります。 プログラムのロジックを保証するために、テストを書くことが重要であることを述べました。テストを書くことで、早い段階でプログラムのエラーが検知でき、コードの変更に伴うリグレッションも防ぎ、コードの品質を担保することに繋がります。テストの充実度はPJの規模や性質に応じて決めるべきですが、テストだけではデータ分析や機械学習の品質の保証はできないので、そのためにはMLOps等のアプローチを検討することも必要です。 参考文献 ロバストPython ―クリーンで保守しやすいコードを書く
こんにちは!Insight Edge で Developer をしている Kobori です。 本記事では、Amazon Cognito と Nest.js を使用した認証認可について、調査で得た使い方とノウハウを紹介します! 初めて使用される人でも、本記事を見てユーザープールの設定からユーザー認証まで一通りの機能が使えるようになればと思いを込めて書きました。 ぜひ、最後までご覧いただけたらと思います。 この記事でわかること Cognito のユーザープールの作り方 Nest.js の Guard でのユーザー認証方法 Cognito とは? AWS のユーザー認証、ユーザーの管理ができるサービスです。 ユーザー認証に関わる下記のような充実した機能があります。 実装面においても、AWS ライブラリが用意されているので、フロントエンド、バックエンド問わず、工数を削減してセキュリティの高い機能を実装できます。 Cognito のユーザープールとは? Cognito ユーザープールは、Cognito のサービスの一部でありユーザー管理、属性管理、認証するサービスです。 ユーザー情報(ID、パスワード、メールアドレスなど)やグループ情報の保持、トークン管理、アクセス制限などの設定ができます。 開発者は、ユーザー認証に係る機能を代替えして使用することで、よりアプリ開発に注力することが可能となります。 詳しくは、 Amazon Cognito プール 参照 Cognito の主な提供機能 サインアップ サインイン パスワードリセット パスワード変更 ユーザー属性の管理 JWT トークン(アクセストークン、リフレッシュトークン、リフレッシュトークン)発行 ユーザー固有の UUID 発行 多要素認証 リスクの検出 Cognito の利用料金 月間 50,000 アクティブユーザまで無料で使用できます。 ※ リスクの検出の機能である「高度なセキュリティ」については、追加の料金が発生します。 詳しくは、 Amazon Cognito 料金 参照 Cognito ユーザープールの作成 Cognito でユーザー管理をするには、ユーザープールの作成が必要です。 ステップ別の主な設定内容 ステップ 1:サインイン時に使用するメールアドレス、電話番号の有無の設定 ステップ 2:パスワード要件や多要素認証の有無、アカウント復旧方法の設定 ステップ 3:サインアップ時の確認方法やユーザーに付与する属性情報の設定 ステップ 4:Cognito から送信する E メールの設定 ステップ 5:ユーザープール名、認証方式、トークンの有効期限の設定 作成時の各種設定 ステップ 1:サインインエクスペリエンスを設定 ステップ 2:セキュリティ要件を設定 Authenticator アプリケーション、SMS メッセージでの確認も可能 ステップ 3:サインアップエクスペリエンスを設定 デフォルトのまま次へ ステップ 4:メッセージ配信を設定 一時的な設定として、Cognito での動作確認を優先するのであれば「Cognito で E メールを送信」を選択 SES で E メール送信を行う場合は、AWS SES の設定が必要です ステップ 5:アプリケーションを統合 ユーザープール名の入力 「Cognito のホストされた UI を使用」にチェックと「ドメイン」の指定 アプリケーションクライアント名の入力 ユーザープールにユーザー作成 「E メールで招待を送信」を選択すると仮パスワードが送信される ※ 招待を送信しない場合、仮パスワードの設定をする コマンド で認証確認 サインイン 下記コマンド実行でユーザー名とパスワードを使ってサインインできる クライアント ID クライアントアプリケーションの ID を指定 この ID は、Cognito のコンソール画面のアプリケーションの統合のタブで確認できる ユーザー名 サインインするユーザーのユーザー名を指定します。 パスワード サインインするユーザーのパスワードを指定します。 aws cognito-idp initiate-auth --client-id ”クライアントID” --auth-flow USER_PASSWORD_AUTH --auth-parameters USERNAME=”ユーザー名”,PASSWORD=”パスワード” 初期パスワード変更 初回サインイン時に返された「Session」を使って初期パスワードを変更する aws cognito-idp respond-to-auth-challenge --client-id "クライアントID" --challenge-name NEW_PASSWORD_REQUIRED --challenge-responses NEW_PASSWORD="新しいパスワード",USERNAME="ユーザー名" --session "初回サインイン時に返されたsession" ログアウト 下記コマンド実行でログアウトできる aws cognito-idp global-sign-out --access-token "アクセストークン" Nest.js で認証と認可 トークンを使ったアプリ側での認可 Cognito では、コマンドや API でサインインした際に 3 つのトークンが発行され返されます。 サインインでユーザー認証し、レスポンス(ユーザー情報)に含まれるアクセストークンを使用し、ユーザーの有無やトークンの有効期限を判定してアプリ側での認可をします。 リフレッシュトークンを使うと有効期限の切れたアクセストークンを再発行できるので、有効期限が切れた場合に再ログインさせるか、自動で再発行するかアプリ側で使い分けができます。 取得できるトークンの種類 アクセストークン アクセス制限や、ユーザーの認可に使用するトークン グループ情報も含まれている 有効期限は、5分~1日 ID トークン ユーザーの属性情報(例:ユーザー名、メールアドレス)が含まれるトークン 有効期限は、5分~1日 リフレッシュトークン(更新トークン) 再認証なしでアクセストークンと ID トークンの再発行を取得するためのトークン 有効期限は、60分~10年 事前準備 下記をインストール npm install @aws-sdk/client-cognito-identity-provider 下記を import import { AdminInitiateAuthResponse } from 'aws-sdk/clients/cognitoidentityserviceprovider'; サインイン JavaScript でのサインイン処理例 const cognito = new CognitoIdentityServiceProvider(); const data = await cognito .initiateAuth({ AuthFlow: 'USER_PASSWORD_AUTH', ClientId: "クライアントID", AuthParameters: { USERNAME: "ログインユーザー名", PASSWORD: "ログインパスワード", }, }) .promise(); data.AuthenticationResult.AccessToken; ログアウト JavaScript でのログアウト処理例 const cognito = new CognitoIdentityServiceProvider(); await cognito .globalSignOut({ AccessToken: "アクセストークン", }) .promise(); Guard を使ったユーザー検証 Nest.js の Guard でユーザー検証する アクセストークンを使ったユーザー取得をすることで、ユーザーの有無とアクセストークンの有効期限が検証できる ※ユーザーが取得できない場合やアクセストークンの有効期限切れの場合は、例外が発生します const cognito = new CognitoIdentityServiceProvider(); this.user = await cognito .getUser({ AccessToken: "アクセストークン", }) .promise(); Nest.js の Guard でユーザーのグループ検証する グループ情報は、アクセストークンに含まれるのでデコードすることでユーザーに設定したグループ情報が取得できる 下記のように取得後に対象のグループが含まれているか判定をすることで、グループの検証ができる jsonwebtoken のインストール npm install jsonwebtoken アクセストークンをデコードしてグループ情報取得 import jwt from 'jsonwebtoken'; ・・・ const decodedToken = jwt.decode("アクセストークン"); return decodedToken ? decodedToken["cognito:groups"] : []; Cognito を使ってみた感想 難しそうと思っていた認証部分ですが、Cognito の導入により必要な機能を補うことができるので、開発者は、心置きなくアプリ開発に専念できると思いました。 また、ユーザー認証に必要な基本的な機能が揃っており、無料から使える点やライブラリや UI が用意されているため 手軽かつスピーディーな開発に繋がるでしょう。
はじめに  こんにちは。Insight Edge, Data Scientistのnakanoです。  これまで機械学習モデルを使用する際は、データの前処理、特徴量エンジニアリング、モデルの選択、 ハイパーパラメータの調整といった複雑な手順を専門知識を持つデータサイエンティストが手作業で行う必要がありました。 しかし最近は、クラウドベンダーが提供しているAutoMLサービスの認知度も上がり、 サービスに料金を支払うことでモデル構築プロセスを自動化することが身近になりつつあります。 そのため、技術者として機械学習モデルを構築する業務はだいぶ楽になってきた一方で、 サービスへの課金額を考慮しなければならなくなりました。  私自身、Vertex Forecast(Google Cloudが提供する時系列予測向けのAutoMLサービス)を使う際にどれだけ課金すれば良いのか迷った経験があります。 そこで今回は、その経験を元にVertex Forecastの課金額の決定方法やノウハウを説明します。 はじめに Vertex Forecastとは Vertex Forecastの料金体系 ケース:1000店舗の1週間後までの客数予測モデル トレーニング時間の課題 トレーニング時間の決定方法 トレーニング時間の目安 コンテキストウィンドウの目安 私見 経験 所感 結論 Vertex Forecastとは  Vertex Forecastとは、Google Cloudが提供している時系列データ向けのAutoMLサービスです。 2021年5月にプレビュー版が公開され、2022年6月にGA版が公開されました。 AWSのAmazon Forecastが2019年8月にGA版が公開されていることを考えると、比較的新しいサービスといえます。 Amazon Forecastに比べて設定できるパラメータが少ないこともあり、非技術者でも使いやすく作られたサービスと感じます。 Vertex Forecastの料金体系  まずVertex Forecastの料金体系を紹介します。 Vertex Forecastでは、学習と予測の2つのプロセスがあり学習は時間単位、 予測はデータポイント数(予測する値の数)に応じて課金されます。 学習(Training) トレーニング時間1時間分あたり、$21.25 予測(Prediction) 予測値1000個あたり、$0.20 (0-1M points) 予測値1000個あたり、$0.10 (1M-50M points) 予測値1000個あたり、$0.02 (>50M points) 引用:Vertex AI pricing,Vertex AI Forecast ケース:1000店舗の1週間後までの客数予測モデル  具体的なケースで費用を計算してみます。 たとえば1000店舗を運営しているレストランチェーン店のデータサイエンティストとして勤務していたとし、 店舗から仕込みや発注の都合で直近1週間の客数予測を毎週1回把握したいと要望があったとします。 またデータサイエンティストの事前の分析結果から、 客数予測モデルは毎月1回 (ここでは仮に)10時間分のコストをかけて再トレーニング させる必要があるとします。  この場合の費用は以下のとおり、$218.1/月と見積もれます。 学習(Training)費用 $21.25/時間 * 10時間 * 1回/月 = $212.5/月 予測(Prediction)費用 $0.2/1000 * 1000店舗 * 7日/週 * 4週/月 = $5.6/月 トレーニング時間の課題  上記のレストランチェーン店のケースでは、 トレーニング時間を仮に10時間分と設定しましたが、実際はどのように決めればよいのでしょうか? 使用するモデルが既に決定している場合は、学習用データと検証用データの損失関数を参照することでトレーニングが十分であるかどうかを確認できます。 しかし、Vertex Forecastでは損失関数を確認できません。 他のVertex Forecastユーザーもこの点について言及されていたので、学習の進捗がわからない点はユーザーならば認識している課題のようです。 ( ML: Vertex AI Forecast AutoML ではじめる需要予測 )  そのため、トレーニング時間決定にノウハウが必要となります。 トレーニング時間の決定方法 トレーニング時間の目安  まずトレーニング時間の参考値として、公式ドキュメントの表を紹介します。 この表では、行数、特徴量数、予測ホライズン(予測期間の長さ)によってトレーニング時間の目安が記載されています。 留意点として、この表の値はあくまで目安であり、コンテキストウィンドウ(入力期間の長さ)や特徴量の内容によっても必要なトレーニング時間は変わってきます。  コンテキストウィンドウ(入力期間の長さ)は、ユーザーが学習時に設定する値です。 大きいほど、モデルの精度が向上しますが、トレーニング時間も増加します。 ではコンテキストウィンドウは、どのように設定すればよいのでしょうか? 行数 特徴量の数 予測ホライズン(予測期間の長さ) トレーニング時間 1,200 万 10 6 3~6 時間 2,000 万 50 13 6~12 時間 1,600 万 30 365 24~48 時間 引用:Train a forecast model 予測ホライズン、コンテキストウィンドウとは 予測ホライズンとは、モデルがどれくらい先まで予測するかを設定する値です。日ごとの時系列データで1週間先まで予測する場合は7となります。 コンテキストウィンドウとは、モデルがどれくらいの期間を遡ってデータを参照して予測するかを設定する値です。予測に直近4週間の値を入力する場合は、28となります。 コンテキストウィンドウの目安  次に、コンテキストウィンドウの決定方法についてまとめます。 公式ドキュメントに記載された手順をフローチャートにまとめました。 ざっくりとしたプロセスですが、精度に満足するまでコンテキストウィンドウを増やしていくというものです。 このとき、コンテキストウィンドウの増加とともにトレーニング時間も比例的に増やす必要があります。 コンテキストウィンドウの目安 私見  ここまで公式ドキュメントで紹介されているトレーニング時間とコンテキストウィンドウの決定方法について説明しました。 私自身の経験と所感、結論をまとめます。 経験  以前、以下のケースで時系列データ予測をVertex Forecastで検証しました。 特徴量:4個(タイムスタンプ列(日付):1個、カテゴリ変数列2個、ターゲット変数列1個) タイムスタンプ列とターゲット変数列以外の特徴量が2件しか無いのは、トレーニング時間を抑えるため最低限の特徴量だけを特に前処理せずに入れたためです。 コンテキストウィンドウ:50 予測ホライズン:10 行数:約100万行(時系列識別子1000件、データポイント1000件)  このケースでは、12時間のトレーニング時間まで精度が向上しました。 公式ドキュメントのトレーニング時間の目安と比べて、これはやや長めの時間を必要としました。 標準的なトレーニング時間よりも多くの時間を必要とした要因としては、 特徴量のうち2個は数10のカテゴリ数を持つカテゴリ変数だったことや、 今回のデータが特定の日や曜日でイベントを持つデータだったため日付列から作成できる特徴が多かったことなどが、 考えられます。 所感  AutoMLサービスというと、手元のデータをアップロードするだけで簡単にモデルが構築できるというイメージがありました。 実際に使ってみたところ、私のケースでは簡単に精度の高いモデルが作成できました。 一方で、使えそうなデータをやみくもにトレーニングさせるのは、コスト面で現実的ではありません。 精度に寄与しない特徴量を削除するなど、工夫は必要となります。  欲張って特徴量を増やしすぎず、また学習を一度で完結させようとするのではなく適切な課金を確保することが、AutoMLをうまく利用する上でのポイントだと感じました。 結論  最後に推奨方法と私見をあわせて、結論をまとめます。 課金額に余裕があるとき。 公式ドキュメントのベストプラクティスに沿って、コンテキストウィンドウと予測ホライズンは同じ値にして、トレーニング時間6時間から始め適切なコンテキストウィンドウを探す。 適切なコンテキストウィンドウが見つかったら、時間を増減させて、トレーニング時間を調整する。 課金額に余裕がないとき(ちょっと触ってみたいとき) 特徴量を減らす。(計算量を減らすため) コンテキストウィンドウと予測ホライズンは同じ値にする。(計算量を減らすため) トレーニング時間1時間と2時間でトレーニングさせる。( 重要 :節約のため最小単位の1時間分($21.25)だけ試してみたいところですが、2時間分も動かしておいて、精度の伸びしろを確認しておくことは重要だと考えています。)
こんにちは。InsightEdgeのDataScientistのSugaです。以前からテレワークをしていますが、最近は自宅の仕事場環境を改善すべくスタンディングデスクやモニターアームを導入してすごく快適になりました。 今回は昨年に経済産業省が主催しているデジタル推進人材育成プログラムである、「マナビDX Quest」に参加したときの記事を書いたので、お時間がある方は読んでもらえれば幸いです。 目次 プログラムへの参加 マナビDX Questとは? 前身はAI Quest 初心者でも参加OK プログラム参加へのきっかけ お知らせが届く 参加動機 応募するときのポイント スキルアセスメント 時間の確保と参加する周辺環境整備 志望動機の推敲 プログラムキックオフ プログラム概要 キックオフセッション 受講手続き オンボーディング コミニュケーション 第1タームPBL(Project Based Learning) 店舗を運営する企業の収益改善 教材の内容 参加者との交流 交流セッション開催 Slackチャンネル 学び合う文化 第2タームPBL(Project Based Learning) 製造業の工数予測 プレゼン課題 地域企業協働プログラムへの参加 チーム組成と準備期間 チーム募集 開始前準備 企業とのマッチング 地域企業協働プログラムの取り組み ワークショップ開催 提案書作成 現地視察 報告書 まとめ 所感 関係人口の大切さ 今年も開催 プログラムへの参加 マナビDX Questとは? マナビDX Quest は 経済産業省が主催するデジタル推進人材育成プログラム です。ぱっと見ると、 「マイナビ?!」 と読めるのですが、毎日コミュニケーションズとは無関係のようです。 「学び」をカタカナにしたようですね。 DXもデジタルトランスフォーメーション(Digital Transformation)の略だと思っていたら、デラックスのDXみたいですね。てっきりデジタルトランスフォーメーションだと思っていました(笑) 前身はAI Quest 前身のプログラムはAI Questという名前で2019年から開始していますが、2022年からはマナビDX Questとして、AI(人工知能)の分野だけでなく、デジタル技術全般に関わるものとしてより範囲を広げて開始されました。詳細については本家のWebページを参照してもらればと思います。 初心者でも参加OK 以下に詳細の抜粋を紹介します。 このプログラムは特別なデジタルスキルを必要とせず、基本的なリテラシーと学ぶ意欲があれば誰でも参加できます。受講生は実際のDXプロジェクトを体験しながら、デジタル技術とビジネス要素(課題の発見、ゴール設定、コミュニケーション力など)を学びます。さらに、データの活用やビジネス変革といったスキルを習得し、データサイエンスプロフェッショナルやビジネスアーキテクトなどの役割を目指すことが出来るというものです。 世の中にはたくさん教育プログラムがありますが、調べてみると面白いものがたくさんあるんですね。 プログラム参加へのきっかけ 以前にプログラミングや機械学習の講師をやっている時期があり、そのときの生徒さんの一人に、カリキュラムを早々と終わらせてしまって、自分で技術書やBlogを読んで自主的に勉強している方がいました。 あるときに、AI Questというプログラムを見つけたので これに参加してみたいという相談 がありました。勉強になりそうだったので、参加してみたら良いのではという話をしました。 自分「どんな内容のプログラムなんですか?」 生徒「画像での不良品検知をやっているんですが、受講者同士でコンペをやって精度を競うんです」 生徒「その他に、実際の案件みたいにやるのでとてもリアルですよ」 生徒「参加者も初心者の人から、ガチな人までいて幅広いですよ」 自分「なかなか面白い試みですね、普通の研修とは少し違う感じですね」 自分「でも、こういう研修って参加費が高くて、なかなか一般の人は受講できないよね」 生徒「書類選考はありますが、通過したら無料で受講できますよ」 自分「え!?、無料なんだ、それは良いですね」 その時は面白い取り組みをしている程度という感じだったのですが、プログラム内容もしっかりしていて、次にあるときには参加しても良いかなと思っていました。 お知らせが届く その後、すっかりそのことは忘れていました。2022年7月になってから、 データサイエンティスト協会 のメーリングリストやコンペティションサイトである SIGNATE からの案内があり、新たにマナビDX Questとしてプログラム参加を募集するということでした。プログラムの内容を確認すると、6ヶ月に渡る長丁場だったので少し不安な部分もありましたが、名前が変わって内容も刷新されるということで 面白そうだったので参加する ことにしました。 参加動機 参加動機をまとめると、 どんな教育プログラムをするのかが気になる 参加者同士の交流が出来て、学習者にどんなニーズがあるのかが気になる 普段はデータ分析や予測モデル実装をする部分が多いのでコンサルティングもたまにはやりたい 新入社員教育など教育のコンテンツを作っていくときの参考にする ということがありました。 応募するときのポイント スキルアセスメント 応募するときにはスキルアセスメントがあるのですが、事前の心構えが必要になります。 いきなりテストが開始されるのでびっくりしないことが重要です 。もし、Jupiter Notebookを使いたい人は事前に用意しておくと良いと思います。データ集計の課題もありますが、データ量はそれほど多くないのでExcelでも対応できると思います。制限時間があるのでなるべく素早く解答する必要があります。 時間の確保と参加する周辺環境整備 それから、基本的には働きながら参加することになるので、 事前に職場へ報告して同意を得ておくとスムーズ です。ただ、仕事終わりや休日などのプライベートの時間で参加ができるので人によっては必要ないかもしれないです。 志望動機の推敲 それから、全員参加出来るわけではなく、選抜があるのですが、スキルアセスメントの結果よりも 志望動機のほうが重要視される と感じました。ですので、志望動機の内容はよく推敲して投稿するほうが良いと思いました。ただ、選考基準は公開されていないのであくまでそういう印象があるということです。 まとめると以下のようになります。 スキルアセスメントへの心構えとデータ分析課題への準備 時間の確保と参加する周辺環境整備 志望動機の推敲 プログラムキックオフ プログラム概要 詳細なプログラムについては本家のサイトを参照してもらうとして、主に以下のようなプログラムがあります。 * ケーススタディ教育プログラム * 地域企業協働プログラム ケーススタディ教育プログラムは「課題解決のためのAI実装プロジェクト」を擬似的に体験するものです。教材を自分で選択して取り組むことになりますが、教材によって課題整理/問題解決/コンサルティング/ロードマップ策定に重きが置かれているテーマもあれば、実際のAIモデル開発に重きが置かれているものもあります。 サイトにも掲載がありますが、 需要予測・在庫最適化 不良箇所検出 店舗を運営する企業の収益改善 業務最適化 などのテーマがありました。 ケーススタディ教育プログラムはPBL(ProjectBasedLearning)と呼ばれていて、 8月から10月に実施される第1ターム 12月末から2月まで実施される第2ターム に分かれています。 地域企業協働プログラムは受講生がチームを作り、実際の地域の中小企業の課題解決に取り組むものです。約2ヶ月間をかけて具体的な課題に対して解決策を提案して報告していく内容になっています。 キックオフセッション 受講手続き 応募後に受講が決定するとメールで受講案内が送られてきて、手続きをするこになります。PBLでは教材配布やモデル開発をするための インフラとしてSIGNATE を利用することになるのでその登録などを行います。受講期間中は オンラインの教材が使用可能 なのでこれから学び始める人にとってはとても良い環境だと思いました。 オンボーディング 受講者向けのガイダンスがオンラインで開催されるので、オンボーディングも丁寧に行われていると感じました。受講者は1800人の規模なので、説明会をするにしても工夫が必要で主催者側はスタートするまでの準備が大変そうという印象でした。また、 参加できない人向けにZoomの録画も提供されている のでそのあたりも親切でした。 コミニュケーション コンセプトとして受講者同士の学び合いを推奨しているので、その仕組をどのように設計するかも重要なポイントだと思いました。Slackチャンネルが用意されていたり、 コミニュケーションツールを活用したりと受講者同士が学び合う仕組みを準備 していました。開会式後、プログラムがスタートするのでそれまでに準備を進めていきます。 第1タームPBL(Project Based Learning) 店舗を運営する企業の収益改善 参加した Project Based Learning (PBL) では、店舗を運営する企業の収益改善のテーマを選択しました。このテーマは 過去案件として実際にあった現場研修プログラム をもとにして作られたものらしく、実際のビジネスシーンに近いリアルな内容でした。 教材は段階的に進行し、毎週新しい教材が公開されていきます。 まずはヒアリング内容から課題を抽出するところからスタートしました。その後、 実際のデータが提供され、そのデータ分析から課題をさらに深掘りし、デジタルを活用した解決策を探求していきます 。このプロセスは実際のDXプロジェクトでよくあるシナリオを再現しており、現実の問題解決をしていく過程を忠実に模擬していると思いました。 途中の提出物はPPTでの提案資料となり、 他の参加者と相互に評価をする課題 があります。点数の高かった人が成績優秀者に選ばれるという、競争要素も含んだ内容になっていました。この評価プロセスは自分自身の成果を他者と比較し、自分のアウトプットに対してどの程度の価値があるのかを知る良い機会となりました。 プロジェクトの自由度は非常に高く、店舗を運営する企業の収益改善の目的に沿っていれば、どんな課題設定にしてもよいです。ですので、 自分なりに課題をどう解決するかの仮説を考えて、解決策のアイディアを考える 良い時間になりました。 例えば、 * 需要予測 * 在庫最適化 * ダイナミックプライシング * デジタルマーケティング など、さまざまなアプローチを考えました。問題解決という観点ではデジタル技術を使わない方法(業務効率化や業務プロセス改善)もあり、 自由な発想によって多くの方法を出せる ということも感じました。一方で、初めての経験する方や初心者の人は、示されていたガイドラインに従う形で進めることも出来ます。 教材の内容 全体として、PBLは自分の考えを具現化し、それを他者と共有し評価するという、 実践的なビジネススキルを身につけるためのとても有効なプログラム だと感じました。また、店舗を運営する企業の収益改善という具体的なテーマを通して、データ分析から解決策提案までの一連のフローを経験でき、そのプロセスを通じて多くの学びを得ることができる教材だと思いました。 このとき、同じ課題をやっている受講者のSlackチャンネル中で活発に議論がなされていました。 各自が理解したことや提案したいアイディアを共有するための定期的な勉強会が主体的に開催されていて そこでの議論もとても有用なものでした。アイディアを共有してフィードバックを受けることで、より良い解決策になっていく受講者もいて、積極的に参加していく姿勢は素晴らしいと感じました。 参加者との交流 交流セッション開催 プログラム開始後は交流セッションが頻繁に開催されていました。そこでは、 Zoomのブレイクアウトセッションを通じて他の参加者と直接会話を交わす機会 が多く設けられていました。他の参加者の背景や参加理由を聞くだけもとても面白かったです。何か話すきっかけが出来たときはSlackでお礼コメントをするととても丁寧で良いと思いました。 ただ、通常の仕事も一緒だと思いますが、 顔が見えない分とても丁寧なコミニュケーションの必要性 を感じました。事務局からも 「丁寧なコミニュケーションをしてください」 と案内があり、やはり一定の配慮は意識した方が良いと思いました。 また、事務局主催の交流セッションの他にも、有志による交流セッションも多く開催されており、自発的な活動が行われていました。Zoomの有料アカウントを持っている人は良いのですが、持っていない人もいるので コミニュケーションツールが提供されて、常時交流できる環境 が整備されていました。 Slackチャンネル Slackチャンネルが設けられており、かなり多くのチャンネルが作られていました。これらのチャンネルでは、 特定のテーマや分野に関する議論の場として機能し、参加者間の交流の場 になっていました。そもそも、1800人規模のSlackはかなり規模が大きいので一定の運用ルールを作っていかないと収拾がつかなくなるので、運用面でも工夫が必要だと感じました。 プログラムの後半の地域企業協働プログラムではチームを組む必要があり、そのための準備として プログラムの早い段階では参加者同士で交流をしている人 が多かったです。チーム組成の話はこの後でしたいと思います。 学び合う文化 特に良いと思ったのは、 包括的なサポート体制と学び合う文化 です。過去に参加した経験のある人がメンターとして有志でサポートするしくみを作っていて、初心者の方々へのサポートが非常に充実していました。本来であれば、講師を採用して有償でサポートする体制を整えますが、 受講者同士が学び合うことでコストダウン にもなります。 さらに、わからないところは経験者によってすぐにアドバイスを提供するという風土が醸成されます。加えて、 サポートに対するコミュニティ貢献賞や成績優秀賞など表彰 も用意されていて様々な形でモチベーションを与えていることも良い点だと感じました。 第2タームPBL(Project Based Learning) 製造業の工数予測 第2タームに開催されるPBLは任意参加なので、プログラム途中でアンケートが実施されて、希望者が受講することになります。また、地域企業協働プログラムと同時期に行われるので、 企業協働プログラムに集中したい人は受講していない 人もいました。 自分はせっかくの機会なので受講することにして、 製造業の工数予測 のテーマを選択しました。基本的な流れは第1タームで経験しているので、全体のプロセスは同じなのですが、提案資料を作るだけでなく、実際に予測モデルを開発するという点が第1タームと異なるポイントでした。SIGNATEを使用してプライベートなコンペティションが開催されて 参加者で予測精度を競っていく ことになります。精度の良かった上位者には発表の機会が与えて、他の受講者にやった工夫を共有することになります。基本的にはテーブルデータの回帰問題を扱うので、特徴量エンジニアリングをどれだけ出来たかの勝負になっていました。データは実際に地域企業協働プログラムに参加した企業のものをベースに作られていると思っていて、かなり リアルな教材の作り になっていました。 プレゼン課題 教材の最後には意思決定者向けのプレゼンを作る課題もあり、受講者が作った資料もとても参考になりました。成績優秀者で非常にきれいなデータ分析のグラフを作っている方がいて、ぱっと見るとBIツールを使ったようにしか見えないのですが、発表後に聞いてみるとExcelで作っていることがわかり、とてもびっくりしました。 多様な背景を持つ受講者がたくさんいるので、まだまだ 知らない視点 がたくさんあると感じられました。その他、モデリング時のデータに対する見方もたくさんあり、 相互で学び合うことでのメリット があると思いました。 地域企業協働プログラムへの参加 チーム組成と準備期間 チーム募集 地域企業協働プログラムは2022年12月中旬にはじまりました。その前にチーム組成をする必要があり、チーム組成のイベントもたくさんありました。チーム人数は固定で決まっているので、多くなったり少なくなったりすることは出来なかったです。slackでの募集もあり、自分の場合はslackのチーム募集のチャンネルを使ってチームを作りました。募集後に顔合わせの打ち合わせをやって、自己紹介をしたり、どんなことができるかの共有をしてチームを組めるかを決めて行きました。 違うメンバーで顔合わせの打ち合わせは何回かやって、最終的に気が合いそうな人で組むことにしました。プログラムに参加している時点でやる気がある人なのでそこは問題ないのですが、条件をつけて探している人もいました。学生で参加している人もいましたが、社会人で参加している人も多く、時間の都合が合いやすい人でチームを作っている人もいました。 開始前準備 プロジェクトがうまくいくかどうかは良いチームを作れたかどうかにかかっている ので、チーム組成はとても重要だと思いました。営業職、コンサルティングをやっている人からエンジニア、データ分析を仕事にしている人や初学者、学生まで幅広い参加者がいます。チームとしては全体のバランスが大切で、出来ればいろんな多様性をもった人で組む方がうまくいくと思いました。同業者でも良いとは思うのですが、解決策の幅が狭くなってしまう傾向があるように感じました。 また、チームを組んだら、チームの目標と価値観をしっかり決めておくことや顔合わせをしっかりとやってお互いのことを知っておくなど準備をしたほうが良いです。仕事や学業など忙しい中で参加している人が多いので、忙しい時間帯を知ったりして、しっかりコミニュケーションできる関係を事前に作っておくことが大切です。自分が参加したチームも事前に顔合わせをしたり、 自分達のチームのロゴを作ったり 自己紹介資料を準備したりと事前準備をしっかりやりました。 企業とのマッチング チームとしてやりたいジャンルをあらかじめ登録して、プログラム参加企業とのマッチングが行われます。注意点として、必ずしもすべてのチームとのマッチングがされるわけではないので、希望に添えないこともあるということは事前に知っておく必要があります。 マッチングされなかったチームは分析コンペティションに参加する など、自主的に課題に取り組んでいるところもありました。 自分達のチームは四国にある企業とマッチングしました。課題のテーマとしては、 地域活性化とデジタルマーケティング でした。あらかじめ地域企業やりたいテーマを資料でもらって、そこから初回ミーティングをZoomで実施してどんなことがやりたいかをヒアリングしていくことになりました。プロジェクトを進める上でのポイントとして、企業側との技術的なキャップや期待値のギャップはとてもあるので、そのキャップを意識しながら話を進めていくことになります。技術をつかえばどんなことでも出来ると思われているととても大変なので、そのあたりを解消することも大切です。また、企業側のやりたいことが不明確である場合も多く、どんなことがやりたいかを明確にしていくことからスタートして行きました。 企業側はDX推進をやりはじめたばかりなので、 ロードマップ策定をやって今後の方向性を決めることが出来れば ある程度はうまくいったと言えます。企業側とのコミニュケーションをはじめるにあたってはなるべく信頼関係を作れるようにすることを意識しました。各チームメンバーと企業側の担当者の方の自己紹介をお互いにやって、スムーズにやりとり出来るよう心がけました。 地域企業協働プログラムの取り組み ワークショップ開催 企業側との打ち合わせは毎週土曜日に設定して打ち合わせを重ねて行きました。チームで話し合って、 アイディアを広げるワークショップを自主的にやろう ということになり、プロジェクトに参加する全員でアイディア出しをしました。特に、ワークショップはチームから提案してやることにしました。その結果として、企業側と打ち解けることも出来てやって良かったです。ワークショップをやることは、実際の解決につながるかどうかは不明確なところもありますが、シンプルにアイディアをワイワイと話すのは楽しいので仲良くなる目的として成功したと思いました。 その後はより具体的に地域活性化のアイディアを考えていくために、リサーチをしたり、ヒアリングをして行きました。 地域のことは知らないことも多く、観光や産業や歴史など幅広く調査をしました 。また、ドメイン知識は企業側に聞くと早いことがあり、分からないことは素直に聞くことでたくさんの発見がありました。 提案書作成 チーム内の打ち合わせも平日夜に集まって提案内容を作って行きました。Google Slidesを共有してみんなで編集しながらオンラインで会話しながらやっていました。 チームメンバーは地方に住んでいる人もいたので、基本はオンラインでやりとり しながら進めていました。 企業側は年度初めから新しいサービスを提供することになっていて、その サービス提供に合わせて施策が出来るようなスケジュール を組んで検討して行きました。 現地視察 ある程度、提案内容が固まった段階でやはり 実際に現場を見に行かなとわからない ということもあり、2022年12月にチームメンバーと一緒に四国へ行くことにしました。現地に行ってみると気づくことも多くとても良い経験になりました。さらに、地元の市役所の地域振興担当にもヒアリングを行って、地域の課題もヒアリングしました。最終的にはプログラムの最終発表のためにメンバー全員で四国に行って発表もしました。まとめた内容は企業側からの評判も良かったです。 写真は四国に行ったときの風景です。 報告書 最終的な報告書が経済産業省から公開されているので、 プログラムの詳細についてはそちらを参照 してもらえればと思います。かなりの分量があり各地で受講者が奮闘していた軌跡を知ることが出来ます。 令和4年度 地域デジタル人材育成・確保推進事業 2022年度「マナビDX Quest」現場研修プログラム事例集 まとめ 所感 応募当初はプログラム参加に少し迷いもありましたが、最終的にはプログラムに参加してよかったと思っています。平日夜や土日を使うので少し忙しくはなりますが、その分得ることも多かったです。地域企業協働プログラムに参加したチームメンバーはその後も情報交換をしたりしていて、 プログラム参加したことで新しい交流も生まれた と思いました。また、きっかけがなければは四国に行くタイミングもなかったので、新しく地域とつながるきっかけにもなりました。 関係人口の大切さ 市役所の地域振興担当が言っていたことがとても印象的で、 地域活性は関係人口(実際に住んでいる人ではなく、仕事や観光などで地域を訪れる人)が大切 ということでした。プログラムに取り組んだ自分達のチームはまさに関係人口となって地域を訪れたのでそのことを実感しました。地域企業協働プログラムに参加した企業は報告書を見ると、88社もあったので、プログラムに参加したそれぞれのチームごとの経験があったと思います。 今年も開催 今年度(2023年度)も「マナビDX Quest」は開催することが決まっていて7月中旬頃まで募集しているので 興味をもった方はチャレンジしてみる と普段とは違った体験が出来ると思います。 ※ ここに書かれているのは、あくまでも2022年度のプログラムに参加したときの個人の感想なので、人によっては違う経験をしたり、プログラム内容について 多少の差異や勘違いがあることはご了承ください 。また、教材内容や地域企業協働プログラムについては公開されている情報にもとづいて記事を作成しています。
こんにちは!Insight Edgeの小林まさみつです。 以前からChatGPTが様々なところで話題に上がっていて、とても盛り上がっていますね。 弊社でもChatGPTを活用してどのように価値を生み出すことができるかを日々考えており、業務の10%の時間を使って行なっている勉強会の1つのテーマとなっています。 その勉強会の中で「私の代わりにChatGPTがSlackのメッセージを送信する」アプリを作成しましたのでご紹介いたします。 ※本プログラムでは、実行される処理の内容がChatGPTの回答に依存するため、意図しない処理を実行する可能性があります。ご利用の際は自己責任でお願いいたします 目次 1. 概要 2. 構成 2.1. 構成要素 2.2. 処理の流れ 3. 環境 4. 実装 5. 実行結果 5.1. コンソールとSlack 5.2. 実際に処理されたシェルスクリプト 6. まとめ・今後の展望 1. 概要 現在、ChatGPTを活用した文章生成アプリケーションが様々に存在しますが、その大部分は人との対話やチャット用途に焦点を当てています。 一方で、ChatGPTの能力を活かし、生成されたプログラムを実際に動作させるアプリは前述の用途に対するアプリに比べてあまり探求されていない印象です。 そこで今回は、ChatGPTの潜在能力を最大限引き出す新たな試みに取り組みました。具体的には、ChatGTPに送信された文章の回答だけでなく、その回答をSlackに投稿するプログラムも生成させるようにし、文章の送信からそのプログラムの実行までを一括で行うアプリを開発しました。 アプリの動作イメージ 本アプリでは以下のような動作をイメージして作成していきます。 コンソールに、SlackのチャネルIDと投稿したい内容を生成する文章を入力する 投稿したいチャネルIDを入力してください:D03TDUPPQMP 投稿したい内容を入力してください:食事に誘って Slackにメッセージが投稿される 2. 構成 アプリの構成は以下の通りです。 2.1. 構成要素 登場する要素は以下の通りです。 コンソール:今回はコンソール上でアプリを動作させます。 GPTアプリ:コンソールから受け取った文章を元に、OpenAI APIへリクエストを送信し、ChatGPTにより生成されたプログラムを実行します。 OpenAI API: OpenAI社 が提供しているAPIです。今回は LangChain を用いてAPIを呼び出します。 Dockerコンテナ:自身の環境を汚さずに実行したいため、Dockerを用います。 Slack API: Slack のAPIです。メッセージを送信するために用います。 2.2. 処理の流れ 処理の流れとしては以下の通りです。 コンソールでSlackへ投稿したいメッセージの内容をGPTアプリへ入力する。 OpenAI APIへ以下の2つのリクエストを送信する。 コンソールに入力された内容を元に、投稿するメッセージを生成する。 Slackにメッセージを投稿するプログラムを生成する。 OpenAI APIから先ほどの2つの回答を受け取る。 受け取った内容を元に、Dockerコンテナを起動する。 DockerからSlack APIへメッセージ投稿のリクエストを送信する。 Dockerコンテナを削除し、自身の環境を元に戻す。 3. 環境 本システムの環境は以下の通りです。 前述の通り、OpenAI APIを利用するために、LangChainを用います。 MacOS 13.3.1 Python 3.11.1 pip 22.3.1 openai 0.27.7 langchain 0.0.184 Docker 20.10.20 各種APIキーは以下から作成できます。 OpenAI API: https://platform.openai.com/account/api-keys Slack API: https://api.slack.com/tutorials/tracks/getting-a-token ※Slack APIは User Token を作成し、 chat:write をスコープに加えます。 4. 実装 OpenAIは、APIリクエストの内容を保存しない(学習に用いない)としていますが、念の為SlackのAPIキーは送信しません。そのため、環境変数に格納しておき、その環境変数を用いてプログラムを作成するようにしています。 また、プログラムをシェルスクリプトとして生成してもらうことで、Pythonのsubprocessモジュールで直接実行するようにしました。 import subprocess from langchain.chat_models import ChatOpenAI from langchain.schema import ( HumanMessage, SystemMessage ) from langchain import LLMChain from langchain.prompts.chat import ( ChatPromptTemplate, HumanMessagePromptTemplate ) import os import re os.environ[ "OPENAI_API_KEY" ] = "**********" os.environ[ "SLACK_API_KEY" ] = "xoxp-**********" # Slackに投稿するテキスト生成用のLLMChain def create_slack_message (question): chat = ChatOpenAI(temperature= 0 ) human_message_prompt = HumanMessagePromptTemplate.from_template( """相手に対して次の内容を送信してください。 例 Q:今日の挨拶をして おはよう!元気? では、以下の内容でお願いします。 {text}""" ) question_prompt = ChatPromptTemplate.from_messages([human_message_prompt]) question_chain = LLMChain(llm=chat, prompt=question_prompt) answer = question_chain.run(text=question) return answer # Dockerfileを生成し、実行するコード生成用のLLMChain def create_docker_code (): chat = ChatOpenAI(temperature= 0 ) messages = [ SystemMessage(content= "あなたはシェルスクリプト、Docker、Slack APIにとても詳しく親切な人です" ), HumanMessage( content= """ 以下の内容を実行するシェルスクリプトを作成してください。 1. Slack APIを用いて他者にチャットを送るために必要な記述をしたDockerfileをカレントディレクトリに作成してください。 2. また、そのDockerfileを利用してコンテナを起動してください。 3. 2で作成したコンテナを削除してください。 以下の条件を満たしてください。 ・SlackAPIキー:$SLACK_API_KEY(環境変数に設定済み) ・送信先のチャネルID:$CHANNEL_ID(環境変数に設定済み) ・メッセージの内容:$SLACK_MESSAGE(環境変数に設定済み) ・SlackAPIキーはリクエストヘッダに、チャネルIDはリクエストボディに含めてください 回答は、以下の例のように記述してください。 特に、コメントや説明が無くスクリプトのみ記載されていることに気をつけてください。 また、改行コードはエスケープしてください。 ```sh cat << EOF > Dockerfile \\ nFROM alpine:latest \\ nRUN apk add --no-cache curl \\ nCMD curl -X GET -d '{"text":"$QUESTION","channel":"$CHANNEL_ID"}' "https://google.com" \\ nEOF docker build -t google-app . docker run -d -p 80:80 google-app docker rm -f google-app docker rmi google-app ``` """ ), ] return chat(messages).content if __name__ == "__main__" : # Slackに投稿するチャネルIDを環境変数に設定 os.environ[ "CHANNEL_ID" ] = input ( "投稿したいチャネルIDを入力してください:" ) # Slackに投稿するテキストを生成し、環境変数に格納 message_request = input ( "投稿したい内容を入力してください:" ) slack_message = create_slack_message(message_request).replace( " \n " , " \\ n" ) os.environ[ "SLACK_MESSAGE" ] = slack_message # Slackに投稿するためのスクリプトを生成し、その中のコードのみを抽出する ans = create_docker_code() code_block = re.findall( r'```[^\n]*\n(.*)```' , ans, re.DOTALL)[ 0 ] codes = code_block.split( " \n\n " ) # subprocessを用いてコードを1行ずつ実行する for shtxt in codes: subprocess.run(shtxt, shell= True ) ※本アプリはプログラムの生成がOpenAI APIに依存するため、エンジンが更新されたタイミングなどで動かなくなる可能性があります。受け取る回答のフォーマットに左右されないように改善していく必要があります。 5. 実行結果 実行結果は以下の通りです。 Dockerが起動し、実際にSlackへメッセージが投稿されていることを確認できました。 5.1. コンソールとSlack % python gpt.py 投稿したいチャネルIDを入力してください:D03TDUPPQMP 投稿したい内容を入力してください:食事に誘って [+] Building 3.2s (6/6) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 434B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/alpine:latest 3.1s => [1/2] FROM docker.io/library/alpine:latest@sha256:02bb6f428431fbc2809c5d1b41eab5a68350194fb508869a33cb1af4444c9b11 0.0s => CACHED [2/2] RUN apk add --no-cache curl 0.0s => exporting to image 0.0s => => exporting layers 0.0s => => writing image sha256:71d44499df5f6bea3c9bf89a84b59e0a3ceba1042f0dce52b63f4667749cacee 0.0s => => naming to docker.io/library/slack-app 0.0s Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them 15e9ef691c891cffe18016ba92c106288cc7e0abc7f61b670cbfa4fd71275c23 15e9ef691c89 Untagged: slack-app:latest Deleted: sha256:71d44499df5f6bea3c9bf89a84b59e0a3ceba1042f0dce52b63f4667749cacee 5.2. 実際に処理されたシェルスクリプト Slackへメッセージを投稿するためのスクリプトです。 catコマンドでDockerfileを生成し、それを元に起動・削除しています。 cat << EOF > Dockerfile FROM alpine:latest RUN apk add --no-cache curl CMD curl -X POST -H "Authorization: Bearer $SLACK_API_KEY " -H "Content-type: application/json" -d \' {"channel":" $CHANNEL_ID ","text":" $SLACK_MESSAGE "} \' "https://slack.com/api/chat.postMessage" EOF docker build -t slack-app . docker run -d slack-app docker rm -f $( docker ps -aqf " ancestor=slack-app " ) docker rmi slack-app 6. まとめ・今後の展望 今回はChatGPTにSlackのメッセージを送信してもらうアプリを紹介しました。本アプリの作成を通してChatGPTの可能性をより実感できました。今後の展望としては、SlackのChannel IDを指定するのではなく、人名やチャネル名などを元に送信先を決定できるようしたいと考えています。そうすることでより使いやすくなりそうです。具体的には、Alexaアプリにすることでより簡単にメッセージを送信できるようになります。他にも、相手のメッセージに対して自動で返信してくれるようにするのも面白そうです。 本アプリではSlackのメッセージ送信を対象としましたが、他にも様々なコードを実行させることができるので色々試してみたいと思います。
init_mathjax = function() { if (window.MathJax) { // MathJax loaded MathJax.Hub.Config({ TeX: { equationNumbers: { autoNumber: "AMS", useLabelIds: true } }, tex2jax: { inlineMath: [ ['$','$'], ["\\(","\\)"] ], displayMath: [ ['$$','$$'], ["\\[","\\]"] ], processEscapes: true, processEnvironments: true }, displayAlign: 'left', CommonHTML: { linebreaks: { automatic: true } } }); MathJax.Hub.Queue(["Typeset", MathJax.Hub]); } } init_mathjax(); 目次 初めに 提案されたモデルの概要 モデルの問題設定 モデルの意義 提案されたモデル 変数変換後の提案されたモデル 着目している性質 劣モジュラ性 $L^\natural$凸性 証明に必要なLemma 1, Theorem 1 Lemma 1 Theorem 1 最適解が唯一つであることの証明 所感 参考文献 初めに  弊社の分析チームではライトニングトークを社内勉強会の1つとして取り入れており、本稿で解説する論文はその時に私が紹介したものです。勉強会では証明の話はせず、論文の概要をお話しました。 そこで今回は論文で提案されたモデルの最適解が唯一つであることの証明を簡単に解説します。  論文の証明では、まず最適解の候補を2つに絞り、モデルの性質に基づいて1つであることを導いています。着目している性質は$L^\natural$凸性と呼ばれる、極小値と最小値が一致する性質で、劣モジュラ性と関連します。提案されたモデルは$L^\natural$凸性を持つかは明らかでなく、変数を変換したモデルが持っております。本論文では変数変換したモデルの持つ劣モジュラ性により解の更なる絞り込みを行い、変数変換前の提案されたモデルに議論を移すことで証明しています。  以上の内容の解説にあたり、本稿の構成は以下としています。まず提案されたモデルの概要として問題設定やモデル説明をします。次に、本記事で着目している性質($L^\natural$凸性、劣モジュラ性)の定義を述べ、証明に必要なLemma 1やTheorem 1を簡単に説明します。そして最後に、最適解が唯一つであることの証明を見ていきたい思います。 提案されたモデルの概要 モデルの問題設定  問題設定は、著者らが実務で取り組んだ修理部品の在庫管理に由来しています。修理部品は装置の故障が起きなければ使われることはないため在庫は捌きにくいです。そのため長期間保管することになり、保管にかかる費用がかさんでしまいます。しかし、費用を抑えるために在庫量を減らすと、在庫切れを起こす可能性が高まります。万一在庫切れが生じた場合、修理部品を手配できるまでの期間、装置が止まってしまいます。それにより多大な損失が発生することから、在庫切れは避けなければなりません。そこで著者らは必要最小限の在庫量を保管できるよう、装置にセンサを取り付けて解析し、故障予測を行っています。故障予測では、修理部品の必要数と必要になる日にちを予測していますが、予測を外してしまう時もあります。日にちの予測を外すとは、故障する日にちを早め/遅めに予測してしまうことを指しており、保管費用などに影響を及ぼします。また必要数について、少なく予測した場合は在庫切れの可能性が高まり、多く予測した場合は保管費用の増大につながります。特に後者の場合、筆者らは過剰に抱えた在庫を追加料金を支払って発注元に返品し、在庫費用を抑える工夫をしています。本問題は、このような状況における修理部品の管理費用を最小にする発注計画を考えることです。但し、発注には一定のリードタイムはありますが、返品は即日対応であり、修理部品は装置の近くに建設した倉庫に保管しているため修理部品を届ける時間はかかりません。  よって以上をまとめると、本問題は需要予測を用いた在庫管理問題となります。具体的には、需要量や需要のタイミングといった需要予測の情報を援用し、発注量や返却量のタイミングと量を調整して管理費用を最小化する問題に取り組むものです。管理費用とは発注費用、返却費用、在庫の保管費用、そして在庫切れに伴う損失を含みます。また、問題を単純化するために修理物品と倉庫は1種類と仮定しています。    モデルの意義  モデルは、 前節で述べた設定の問題 を解くとともに、不確かであっても需要予測の情報を用いた方が在庫管理にかかる費用が抑えられることを確認するために用いられています。  論文によると、従来のモデルは需要情報が完全に正しいとしていることが多く、需要のタイミングや予測誤差が考慮されることはあまりなかったそうです。また、在庫を大量に抱えてしまった場合、従来は在庫を捌けるのを待つのみで、発注元への返却は考慮されていませんでした。需要のタイミングや予測誤差、発注元への返却を考慮している点が本モデルの新規性となります。  このような在庫最適化はある時刻の発注・返却が次以降の発注・返却に影響を及ぼすため、実行可能解の数が爆発的に増え、解析が難しくなります。最適解を唯一つ持つモデルであること(や本稿でほとんど言及しませんが、効率の良いアルゴリズムを構築できるモデルであること)は解析を簡単にし、運用や費用低減の評価を容易にします。 提案されたモデル 提案されているモデルは以下のようになります(モデルの表記は原文ママ)。 $$ \begin{align} &\begin{split} f_t(\mathbf{a}, \mathbf{z}) = \min_{(z_L, y) \in \mathcal{A}_x} {J_t({\mathbf{a}, \mathbf{z}, z_L, y}) }, \end{split} \\ &\begin{split} J_t({\mathbf{a}, \mathbf{z}, z_L, y}) &= cz_L + c_ry + L(a_{\tau_u}, \dots, a_{\tau_l}, x+z_0-y) \\ &+ E[ f_{t+1}(\mathbf{\bar{a} - \mathbf{\bar{R}}, \it{W}}, (x + z_0 - y - \sum_{\tau = \tau_l}^{\tau_u}R_{\tau} - D^{u} )^{+}, z_1, \ldots, z_L)], \end{split} \\ &\begin{split} \mathrm{where} \end{split} \\ &\begin{split} L(a_{\tau_u}, \dots, a_{\tau_l}, x+z_0-y) &= hE[(x + z_0-y-\sum_{\tau = \tau_l}^{\tau_u}R_{\tau} - D^{u} )^{+}] \\ &+ c_eE[(\sum_{\tau = \tau_l}^{\tau_u}R_{\tau} + D^{u}-x - z_0+y)^{+}]. \end{split} \\ \end{align} $$  $f_t$は時刻$t$から終端時刻までにかかる管理費用の最小値で、この値をとるような発注量と返却量の組み合わせである$(z_L, y) \in \mathbb{Z}_{+} \times \mathbb{Z}_{+}$を求めたいということになります。$z$の添字に使われている$L$はリードタイムを表しており、$z_L$は発注したものが時刻$t+L$に納品されることを示しています。発注や返却は時刻$t$のみで行うわけではないため、実際に求めたい値は各時刻における発注量と返却量になります。 時刻$t$以降の管理費用は、需要量と時刻$t$以前に発注していた物品の納品量に依存するため、それらを表す$\mathbf{a}, \mathbf{z}$の関数になります。$\mathbf{a}={a_{\tau_u}, \dots, a_{\tau_0}}$であり、時刻$t+\tau$における需要量を$a_{\tau}$としています。$\tau$は需要のリードタイムようなものであり、今回の問題設定において$\tau \in [\tau_l, \tau_u]$を満たす$\tau$について、$t+\tau$に需要は発生するとしています。それ以外のタイミングに需要は発生するかもしれませんが、その場合は予測に失敗したことを意味します。また$\mathbf{z}={x, z_{0}, \dots, z_{L-1}}$であり、$x$は時刻$t$における手持ち在庫量としています。  次にモデルの構成要素である$J_t$に着目します。$J_t$はその定義から、時刻$t$にかかる管理費用(最初3項)と、時刻$t+1$から終端時刻までの管理費用$\bar{f}_{t+1}$(最終項)に分けることができます。最初3項は順番に、発注にかかる費用($c$は発注単価)、返却にかかる費用($c_r$は返却単価)、そして在庫に関する費用になります。在庫に関する費用は$L$としてまとめられており、倉庫の保管費用($h$は保管単価)と在庫切れに関する損失($c_e$は損失単価)から成っています。  本稿では上記モデルの最適解が唯一つであることを見ていきます。 変数変換後の提案されたモデル  $v_l = x + \sum_{t=0}^l z_t$、$l = -1, \dots, L$とし、新たに変数$\mathbf{v} = (v_{-1}, v_0, \dots, v_l)$を導入します(変数の表記は原文ママ)。但し、$v_{-1} = x$となります。新しく導入した変数により例えば$z_L = v_L -v_{L-1}$のように記述できるため、 $\mathbf{z}$を$\mathbf{v}$で記述できるようになります。これにより$f_t(\mathbf{a}, \mathbf{z})$から$\bar{f_t}(\mathbf{a}, \mathbf{v})$が、$J_t({\mathbf{a}, \mathbf{z}, z_L, y})$から$\bar{J_t}({\mathbf{a}, \mathbf{v}, v_L, y})$が得られます。 着目している性質 劣モジュラ性  実効定義域が空でない関数$g:\mathbb{Z}^n \rightarrow \mathbb{Z} \cup {\infty}$ が以下を満たす時、$g$は劣モジュラであるといいます。 $$ \begin{align} g(p)+g(q)\ge g(p \vee q)+g(p \land q), \quad p, q \in \mathbb{Z}^n. \end{align} $$ 但し、$\vee, \land$はベクトルの要素ごとに最大値、最小値を出力する演算です。 $L^\natural$凸性  上で定義した関数$g$が以下に示す並進劣モジュラ性を満たす時、関数$g$は$L^\natural$凸であるといいます。 $$ \begin{align} g(p)+g(q)\ge g((p-\alpha \mathbb{1}) \vee q)+g(p \land q+\alpha \mathbb{1}), \quad p, q \in \mathbb{Z}^n, \alpha \in \mathbb{Z}_+. \end{align} $$ 但し、$\mathbb{1}$は全ての要素が$1$のベクトルです。  この性質は離散中点凸性と同値であり、また$g$が整凸性と劣モジュラ性の両方を持つことにも等しくなります。本論文では$L^\natural$凸の持つ劣モジュラ性を用いて、Lemma 1を含め種々のLemmaの証明がなされています。 証明に必要なLemma 1, Theorem 1 Lemma 1 For each $(\mathbf{a}, \mathbf{z}) \in \mathcal{U} \times \mathcal{Z}$ and $t = 1, \dots, T$, the optimal decisions are characterized by $z_L^{*}(a, z) \times y^{*}(a, z) = 0$. このLemmaは最適解の満たす性質を言及したもので、管理費用を最小にする発注量と返却量の積は0になると述べています。この主張をもう少し説明すると、発注量と返却量の両方が正であるときは、返却している分だけ余分に発注していることになり、その分を減らせると言っています。 Theorem 1 a) $\bar{J_t}({\mathbf{a}, \mathbf{v}, v_L, y})$ is $L^\natural-convex$ in $(\mathbf{v}, v_L, y) \in Q$ for each $\mathbf{a} \in \mathcal{U}$ and $t = 1,\dots,T$. b) $\bar{f_t}(\mathbf{a}, \mathbf{v})$ is $L^\natural-convex$ in $\mathbf{v} \in \mathcal{V}$ for each $\mathbf{a} \in \mathcal{U}$ and $t = 1,\dots,T$.  本稿で言及する主張のみ掲載しています。この主張は、提案されたモデルについては不明ですが、変数変換後のモデルは$L^\natural$凸性、つまり劣モジュラ性を持つと言っています。変数変換後のモデルを議論の対象にすると、任意の実行可能解の組に対して、劣モジュラ性が満たす不等式を適用できるため、最適解についての議論ができそうだと分かります。  Theorem 1の証明は$\bar{f}_{t}(\mathbf{a}, \mathbf{v})$の持つ再帰構造に着目した帰納法に基づいています。具体的には、$\bar{f}_{t+1}(\mathbf{a}, \mathbf{v})$が$L^\natural$凸性を持つと仮定し、$\bar{f}_{t}(\mathbf{a}, \mathbf{v})$の内部で再起的に記述される$\bar{f}_{t+1}$が$L^\natural$凸性を持つことと、それに対する期待値や最小化の操作が$L^\natural$凸性を保存することより$\bar{f}_{t}(\mathbf{a}, \mathbf{v})$も$L^\natural$凸性を持つことを導いています。  変数変換は、再帰的に記述されるモデル$\bar{f}_{t+1}$が$L^\natural$凸性を持つことを保証するために行なった操作になります。本稿では言及しませんが、 前に定義した関数$g(p)$ が$L^\natural$凸性を持つならば、$p-\alpha \mathbb{1} \ge \mathbf{0}$となる$\alpha$に対して$g(p-\alpha \mathbb{1})$は$L^\natural$凸性を持ちます(Lemma 2)。変数変換をしなければ、再起的に記述される$f_{t+1}$はイメージ$\mathbf{z}-y\mathbf{e}_1$についての関数となることから$L^\natural$凸性を持つとは限りませんが、$\bar{f}_{t+1}$はイメージ$\mathbf{v}-y\mathbb{1}$についての関数となることから保証されます。 最適解が唯一つであることの証明  Lemma 1より、$J_t$の最適解は「発注量=0」である$(0, y^{*})$または「返却量=0」である$(z_L^{*}(\mathbf{a}, \mathbf{z}), 0)$の2通りになります。両方が同時に満たされる場合は明らかに解が唯一つとなるため、そうならない場合を考えます。最適解が唯一つであることを証明するためには、両方が同時に最適解になり得ないことを証明すればよく、背理法を用います。  同時に最適解になり得ないことを導くために、Theorem 1より得られる変数変換後のモデル$\bar{J_t}$の劣モジュラ性に着目します。劣モジュラ性の式は、実行可能解の組とその組の最小・最大から得られる変数の合計4つを取り扱うことになります。ここで実行可能解の組を上手く選び、「発注量=0」の最適解$(v_{L-1}, y^{*})$と「返却量=0」の最適解$(v_{L-1} + z_L^{*}(\mathbf{a}, \mathbf{z}), 0)$の2つが得られるようにします。そのような実行可能解を劣モジュラ性が満たす式に代入し、以下の式を作ります。 $$ \begin{align} &\begin{split} \bar{J_t}({\mathbf{a}, \mathbf{v}, v_{L-1}, y^{*}}) - \bar{J_t}({\mathbf{a}, \mathbf{v}, v_{L-1}, 0}) \ge \\ \bar{J_t}({\mathbf{a}, \mathbf{v}, v_{L-1} + z_L^{*}(\mathbf{a}, \mathbf{z}), y^{*}}) - \bar{J_t}({\mathbf{a}, \mathbf{v}, v_{L-1} + z_L^{*}(\mathbf{a}, \mathbf{z}), 0}) \end{split} \end{align} $$  ここで代入している変数が最適解であることから、左辺は非正であり、右辺は非負となることから不等式は成立しなくなります。「発注量=0」と「返却量=0」のときは両辺が一致しますが、本節の冒頭で述べたようにその場合を除外しています。よってTheorem 1に反することから、$\bar{J_t}$の最適解は唯一つになります。  $\bar{J_t}$と$J_t$の実行可能解は1対1に対応していることから、$J_t$の最適解も唯一つであることが示されます。 所感  本稿では、不確かな需要情報をもとに在庫管理を行う提案モデルの最適解が一意に定まることの証明を簡単に解説しました。この証明では、変数変換後のモデルが持つ劣モジュラ性と背理法を用いており、比較的シンプルなものになっています。  提案されたモデルの式は、ある時刻の在庫に関する費用の算出にあたって未来の需要を考慮しています。個人的には、これにより在庫の保管費用を過小評価していると思われるため、改良の余地はあるように見えます。また、本モデルの有用性は発注費用や需要の頻度など問題の細かな設定に応じて変わるため、リグレットを使ってモデルを定式化すると面白いと感じています。解の導出は難しくなりますが、バウンドを求められれば、数値実験と合わせることで有用性の見通しが立ちやすくなるように思います。 参考文献 [1]. Topan, Engin, et al. "Using imperfect advance demand information in lost-sales inventory systems with the option of returning inventory." IISE Transactions 50.3 (2018): 246-264. [2]. Fujishige, Satoru, and Kazuo Murota. On the relationship between L-convex functions and submodular integrally convex functions. Kyoto University. Research Institute for Mathematical Sciences [RIMS], 1997. [3]. Kazuo Murota, Discrete Convex Analysis. Hausdorff Institute of Mathematics, Summer School, 2015.
こんにちは!Lead Engineerの筒井です。生成AIが話題ですね。弊社においても生成AI活用の取り組みを進めています。Insight Edgeでは生成AIの活用も含め、住友商事グループ各社の課題解決に取り組む仲間を募集しています!ご興味がある方はぜひ、お気軽に 公式サイトの採用ページ からご連絡ください! さて、ChatGPTを企業で活用する場合、主にAzure OpenAI Appsを通じて利用することになるということもあり、各案件のシステム構築に最近はAzureを活用し始めています。この記事では、Azure Container Apps上にAPIサーバーを構築し、APIキーでアクセス制限をかける方法を紹介します。 概要 今回は前提として、以下のようなケースを想定しています。 Azure上でコンテナをサクッと動かしたい コンテナは2種類あり、相互に通信する。うち1つはAPIサーバーであり、外部からWebアクセスする フロントエンド側を色々試したい事情があり、HTTPヘッダにAPIキーを入れるくらいで簡単にアクセス制御したい 認証機能をアプリ側で実装するのではなく、Azure側に委譲したい Container Appsには組み込みの認証機能があり、とても簡単に各種IdPと連携した認証機能を設定することができます。ただ、APIキーによる認証機能は含まれないため、前段にAzure API Managementを設置することで対応することにしました。また、Azure App Serviceを利用すると、同様の仕組みをもっと簡単に実現することができます。しかし、今回はWebサービスでないコンテナもあるため、Container Appsを採用しました。 というわけで、システム構成はシンプルですが、下図のようになりました。 構築方法 仮想ネットワークを作成する 上図の通り、各通信は仮想ネットワーク経由で行われます。そのため、仮想ネットワークとサブネットを作成し、サブネットにネットワークセキュリティグループを割り当てておきます。ネットワークセキュリティグループには、以下のリンク先を参考に必要なルールを追加します。インターネットから仮想ネットワーク宛のHTTPS通信を許可するルールを追加すれば最低限動作はします。 Container Apps上にコンテナーアプリを立ち上げる Container Apps上にコンテナーアプリを立ち上げます。 まずは、Container Apps環境を作成します。今回はAPI Managementから仮想ネットワーク経由でコンテナーアプリにアクセスさせるため、「自分の仮想ネットワークを使用する」を「はい」とし、仮想IPは「内部」を選びます。 コンテナーアプリ側では、イングレスを下図のように設定します。Container AppsなのでHTTP/TCPやターゲットポートを任意に指定できますね。実際には2種類のコンテナーアプリを作成しましたが、ここではAPIサーバーのみ紹介します。もう一方もやり方は同じです。 API Managementリソースを作成する 事前にAPI Managementに割り当てるパブリックIPアドレスを作成しておき、それからAPI Managementのリソースを作成します。下図のように設定することで、仮想ネットワークに内にリソースを作成し、割り当てたパブリックIPアドレスによりインターネットからアクセスできるようになります。 プライベートDNSゾーン リンク 仮想ネットワーク内でAPI Managementやコンテナーアプリがお互いに名前で通信できるようにするために、プライベートDNSゾーンを作成し、仮想ネットワークに割り当てます。 作成するプライベートDNSゾーンの「名前」には、コンテナーアプリの「アプリケーションURL」のドメイン部分を指定します。 作成が終わったら、先ほど作成した仮想ネットワークを選択して割り当てます。 さらに、レコードセットとして、コンテナーアプリの名前とIPアドレスを対応づけるAレコードを、コンテナーアプリごとに追加します。この時、IPアドレスはどのコンテナーアプリもすべてContainer Apps環境の静的IPアドレスを指定します。 API Managementにコンテナーアプリを割り当てる API Managementの「API」メニューから「Container App」を選択し、コンテナーアプリを割り当てます。さらに、「Subscription required」にチェックすることで、「サブスクリプション」メニューにあるキーをAPIキーとして利用できるようになります。 動作確認 以上で構築が完了しましたので、動作確認をしてみます。 まず、コンテナーアプリに割り当てられているURLは、名前解決もできずインターネットからアクセスすることができません。 次に、APIキーなしでAPI Mangementに割り当てられているURLにアクセスすると、以下のようにエラーが返ってきます。 最後に、APIキーを付与して同じURLにアクセスすると、正常にレスポンスが返ってきます。 まとめ 実際の利用時には、APIキーよりもContainer Appsに組み込みの認証機能を利用することが多いと思いますが、この記事ではAzureの機能を使いつつ、APIキーでアクセス制限をかける方法を紹介しました。なお、App Serviceでは同じことが簡単にできると記載しましたが、その場合はApp Service上にアプリを立ち上げた後、API Managementをそのアプリに紐づけると、ここで紹介したネットワークやDNSなどの設定を全て自動で終わらせてくれるという感じです。この記事が今回紹介したような構成を検討している方の参考になれば幸いです。
梅雨の時期でジメジメしてきましたね。なかなか外にでかけづらいので休日はティアキンに精を出したいと思っているエンジニアリングマネージャの猪子です。 ある程度経験を積んだエンジニアであれば、過去何回か案件や通常業務で利用する技術の選定をしたことがあるかと思います。 本記事ではInsight Edgeにおける技術・アーキテクチャの採用指針や実際に採用した技術について紹介したいと思います。 採用技術・アーキテクチャを決める軸 企業でメインとして利用する技術や案件で採用するアーキテクチャを決める為の基準は非常に重要で、判断を間違うと著しく開発効率が下がったり、更に悪い状況だとサービス継続が危ぶまれる場合もあります。 これまで、幾つかの技術を採用してきましたが、私が新規に技術を採用する際の観点は以下にまとめられるかと思います。 観点 内容 ビジネス目標との整合性 選定する技術は企業のビジネス戦略と一致しているか。例えば、高度なデータ分析を必要とする企業であれば、より高度な機能を持つデータレイク、データストアなどのツール / サービス選定が必要です。 非機能要求 機能だけでなく、アプリケーションのパフォーマンス要件、セキュリティ要件、信頼性要件等、非機能面での技術的な要求に対応できるかという点です 費用対効果 採用する技術のコストは、その技術が提供するビジネス価値に対して適切かどうか。直接的な調達コストだけでなく、メンテナンスやトレーニングのコストも考慮が必要です。 開発者との親和性 開発者との親和性も大切な要素です。親和性においてはスキル・経験があり、効率的に技術を利用できるか、という側面の他に、採用の観点を含め開発者がその技術利用したいというモチベーションがあるかという側面もあります 技術の成熟度とコミュニティのサポート 新しい技術を採用する際に技術がまだ成熟していない場合、成熟しているがEOLが近い場合、業界で利用している開発者が少ない場合は注意が必要です(特に言語・FW等)。又、コミュニティサポートや豊富な情報リソースがあるかも要確認です。 全てを完全に満たすものというのは難しいので(基本存在しない)、その時々の状況に応じた観点の優先度を決めつつ評価・評価していく事が必要です。 過去に自分が見た失敗事例でいうと、選定したプログラム言語としては"ビジネス目標との整合性"、"非機能要求"、"開発者との親和性"については合致していたものの、"技術の成熟度"という側面で使いこなせる人材が業界に少数なため、運用保守出来る人材を採用できずサービス継続のために泣く泣くプログラム言語を移行したというものがあります。。。 Insight Edgeの採用技術・アーキテクチャの方向性 Insight Edgeは元々少数のメンバでアプリ開発・データ分析両方の案件をスピード感を持って推進するという"ビジネス目標との整合性"、"開発者との親和性"や"技術的な成熟度"からPythonを選択していました。 フロントエンドについても同様の観点からReact / TypeScriptを採用しています。 ちなみにGitHubが発表している 2022年で最もGitHub上で利用されている言語 の1-4位は上からJavaScript、Python、Java、TypeScriptとなっています。(4つ中3つをInsight Edge内で利用) 元々PythonはML系のライブラリ・機能の優位性からデータサイエンティストを中心にそのシェアを伸ばしてきていると思いますが、近年、ML系技術がさらにコモディティ化しており、今後もWebエンジニアがML系技術を積極利用することは変わらないと思うのでPythonのシェアは高いまま維持し続けるだろうと予想しています。 これらある程度全社共通的な技術を利用しつつ、Insight Edgeでは今後の案件でさらなる開発効率化が見込めそうなものについては積極的に技術を取り入れるようにしています。(目安として、1つの案件で1つは技術的なチャレンジを取り入れるようにしています) このあたりのチャレンジングな技術要素は案件の主担当者のエンジニアに委ねています。 我々の主な目標はPoCの開発であり、PoCではスピードを出しつつ、検証に必要十分な品質を確保するのが重要な要素になります。如何にこれを満たすための最適な技術・アーキテクチャを選定するかが案件をリードするエンジニアの腕の見せどころであり、常日頃からエンジニアも業務の中で新規技術のキャッチアップを行う事が出来るような全社的な取り組みをしています。 採用技術・アーキテクチャ例 これまでInsight Edgeで採用した具体的な採用技術を幾つか紹介します。 業務共通 通常業務で利用しているツール・サービスについてはInsight Edge設立当初に既存メンバが慣れているもの、という観点で決めたものが多いです。 概ね一般的なものが多いですが、WrikeについてはWBS・ガントチャートのUIが心地よくて個人的に気に入って使ってます。 用途 ツール・サービス名 メッセージング Slack ドキュメント管理 Confluence プロジェクト管理 Wrike レポジトリ GitHub クラウドPlatform AWS / Google Cloud / Azure 業務でよく使うものについては、"費用対効果"も重要ですが、開発者がモチベーション高く気持ちよく仕事が出来るか("開発者との親和性")を重視しています! 開発編 Webアプリ Webアプリを素早く構築技術は多種多様であり、Insight Edgeでもスピード感を持ってPoC開発をするために各種技術を使い分けています。又、必要に応じ、仕様の複雑さ、技術的な制約を鑑みてノーコード・ローコードを活用し、出来るだけ"作らない"開発を目指しています。 その他、各種サービスモジュールも多分に活用しています 用途 ツール・サービス名 ノーコード・ローコード Bubble / Power Apps / AppSheet / GAS / GitHub Copilot etc 認証 Cognito(AWS) / IAP(GCP) CICD GitHub Actions ローカル開発環境 Docker Desktop その他 各種クラウドPlatformのサービス等 IoT IoT機器については要件によって各種センサ / 通信プロトコル / 電池等を使い分けるので案件毎で千差万別です。 通信プロトコル(主にLPWA)については、各種ありますが導入のし易さ(基地局設置不要)、通信コスト等の観点から "LoRaWAN", "Sigfox"を採用することが多いです。 データ分析編 モデル + 簡易IF データ分析系の案件では何らかのモデルを構築し、ユーザが利用するための簡易的なIF(画面 / API)を提供することが多いです。 元々はスクラッチで作ることもあったのですが、最近では Streamlit , Mercury 等を使い、JupyterLab等で記載されたプログラムをWebアプリ化することが多いです BI / DWH BIツールはコスト・機能・デスクトップアプリとしてのレスポンス性能で Looker Studio, Tableauを使い分け、MS Suiteとの統合が必要な場合、Power BIも利用しています。 DWHはBigQueryを基本にしつつ、最近はSnowflakeも採用をし始めています。パフォーマンスや、コスト効率、AWSしか使えないが、BigQueryの利用体験をしたい時に採用する想定です. その他 その他継続的にChatGPT、XR等最新の技術トレンドもキャッチアップし、業務・案件の中に取り入れています。 尚、Insight Edgeでは業務時間の10%を活用し、これらの技術トレンドのキャッチアップ等自己研鑽に取り組む"勉強会"の制度を設けています。 勉強会は業務に直結しないテーマもOKで、エンジニアだけではなく営業やコンサルタントの人も参加し、自由な発想や創発が生まれる場としています。 終わりに 如何だったでしょうか?Insight Edgeは、カイゼンのマインドを持ち日頃から最適な技術を調査・選定するのが好きなメンバが集まっている会社です。 技術を活用した効率化が好き!新規技術にも取り組んでいきたい!という方はカジュアルにお話させて頂ければと思いますので、お気軽に こちら からお申し込みください!
Insight EdgeのLead Engineerの日下です。 弊社ではちょっとしたWebアプリを作るときに、AWSを用いたサーバーレスアーキテクチャで フロントエンド CloudFront + S3 + SPA(React等) バックエンド API Gateway + Lambda という構成をしばしば使います。 今回は、この構成においてありがちなキャッシュによるバージョン不整合の対策について紹介します。 SPAにおけるキャッシュ問題 上記の構成は安価かつスケーラブルにSPAを運用できることが魅力ですが、 フロントエンドの静的ファイルに対してブラウザのキャッシュやCloudFrontのエッジキャッシュが働いてしまい、 アプリの更新がうまく反映されなかったり、フロントエンドとバックエンドのバージョン不整合の原因になることがあります。 jsやcssファイルにはハッシュ値が付与されるものの、呼び出し元であるindex.htmlにキャッシュが効いてしまうとjsやcssも古いファイルが参照されるため、 それによってキャッシュされたものが呼び出されたり、リンク切れが発生したりしてしまいます。 デプロイのたびに CloudFrontのキャッシュを削除(Invalidation)してもブラウザのキャッシュには対策できない ため ユーザー側でフルリロードしなければ最新化されずバージョン不整合などの原因になります。 アプリの処理としてバージョン整合性チェックを実装する方法もありますが、今回はお手軽な方法として、 index.htmlファイルにCache-Controlを設定してキャッシュを無効化する方法を紹介します。 S3のオブジェクトメタデータにキャッシュ動作を設定する CloudFront + S3 で配信するファイルは、S3のオブジェクトメタデータにCache-Control等を設定することでHTTPヘッダに反映できます。 マネジメントコンソールで確認・設定する場合、index.htmlのファイルの詳細画面にて プロパティ タブの メタデータ の項目に設定します。 S3のオブジェクトメタデータ設定 デプロイ時にAPIで設定 自動デプロイに組み込むためにAWS CLIでもやってみます。 CLIにはオブジェクトメタデータのみを編集するコマンドが無いため、以下のように aws s3api copy-object を使用してメタデータを編集します。 その際、Content-Typeなど他のメタデータも明示的に指定しないと失われてしまうため、 --content-type オプション等で指定しています。 # 既存のオブジェクトにメタデータを設定 aws s3api copy-object --bucket your-web-contents-bucket --key index.html --copy-source your-web-contents-bucket/index.html --metadata-directive REPLACE --cache-control "no-cache, no-store" --content-type "text/html" このコマンドは既存オブジェクトのメタデータを編集できますが、 aws s3 sync 等でファイルをアップロードした後にメタデータ編集を実行する手順だと、そのタイムラグの間にキャッシュ設定未反映のファイルがユーザに配信されてしまう可能性があります。 アップロードと同時にキャッシュ設定を反映させるには、先にindex.html以外を aws s3 sync で更新し、 最後にindex.htmlを aws s3 cp コマンドでメタデータ設定とともにアップロードすると良いでしょう。 # index.html以外をアップロード aws s3 sync ./build s3://your-web-contents-bucket --exclude index.html --delete # index.htmlをメタデータ設定とともにアップロード aws s3 cp ./build/index.html s3://your-web-contents-bucket/index.html --metadata-directive REPLACE --cache-control "no-cache, no-store" --content-type "text/html" CloudFront経由で配信されたindex.htmlのヘッダをブラウザの開発者ツールで確認してみます。 ちゃんと設定されていますね。 レスポンスヘッダ
こんにちは。Insight Edge開発チームのntです。前回の投稿 1 では、事業案件や技術的な話について紹介しましたが、今回はInsight Edgeの社風に焦点を当てます。 Insight Edgeのバリューの1つに「みんなでやる」という考え方があります。そのバリューを実現するために、チームワークを高めるための活動のひとつとして、弊社分析チームの善之さん( yoshiyuki555 )らと共に企画して開催したシャッフルランチ会です。 以下、その開催レポートです。 はじめに 仕事上のチームビルディングや新しい人間関係を築くために、飲み会や最近ではランチ会などの社内イベント企画が多いかと思います。ランチ会とは、社員同士でグループを組み、会社がランチ代を補助してランチに行くことができる制度です。 Insight Edgeは2019年に創業して以来、社員が順調に増えています。しかし、創業間もなくコロナ禍に見舞われ、安全第一の観点からテレワーク環境が増えました。成果につながるためには、フィジカルなコミュニケーションの機会を確保する必要があります。そのため、出社によるコミュニケーションの活性化が重要と考え、火曜日と金曜日を出社推奨日とし、その他の日でも出社が可能としています。一方で、同じ事業案件を担当している人や、何らかの用事がある人以外とは、会話が自然に発生することが少ないという課題がありました。 旧ランチ会企画とその課題 以前、コミュニケーション活性化や異なる組織ラインの交流促進のためにランチ会を導入しました。補助利用には条件があり、異なる組織ラインで4名~6名程度のグループを組む必要がありました。開催の流れは、Slackで「今日会社来ている人でランチ行きませんか?」と声をかけ、メンバーでランチに行く形式でした。1カ月間の運用後、次のような課題が浮上しました。 ランチ会を誘う人の心理的負荷が高い ランチ会を利用するメンバーに偏りが生じ、期待するほど活性化の効果が得られなかった この問題を解決するため、新しいランチ会企画を立ち上げました。以下では、企画の流れやアンケート結果について詳しく説明します。 ランチ会企画の流れ ランチ会企画の準備期間は1カ月で、実際にランチ会を3カ月間運用しました。最後にアンケートを実施し、振り返り評価しました。 自身の問題意識 私がこの問題に取り組む理由は、出社時に声かけや雑談をするものの、コミュニケーション範囲に限界を感じていたからです。接点数(=雑談した人数)をシステマチックに拡大するアイディアで効果が得られると考えました。マネージャーと週次の1 on 1でアイデアを提案し、別の組織ラインからのメンバーも参加し、運営チームを立ち上げました。 運営チーム結成・課題認識・打ち手検討(1週目) 運営チームは、開発チーム1名(私)、分析チーム1名(善之さん)、マネージャー1名の合計3名で結成されました。企画効果を定量的に振り返ることができ、形骸化しないよう配慮しました。以下の問題解決のため課題や要件を定義しました。 目的:コミュニケーション活性化 前提:コミュニケーション活性化は、社員同士の接点が増加している状態を意味する。ランチ会に参加することで、社員同士の新たな接点が生まれる。 手段:ランチ会企画の運営により、社員全員が偏りなくランチ会に参加し雑談できる環境を整え、接点数を効率的に最大化する。 企画の要件: 参加対象:社員全員 参加回数:各社員が3回参加(月に1回、3ヶ月間) 取りまとめ:各社員が1回ずつ取りまとめを担当し、負荷を分散させる。 グループ人数:1グループあたり4~5人 組み合わせ方法:最適化アルゴリズムを実装する。 開催期間・組み合わせ案:3ヶ月分の開催時期と組み合わせ案は事前に指定する。 課題認識が一致した後、次週から企画準備タスクに取り組み始めました。 ランチグループ及び参加者の組み合わせ方法(2~3週目) 最適化アルゴリズムを使って、社員同士のランチ会の組み合わせ案を作成しました。 まず、社員の組み合わせ表(接点の有無のマトリックス)を作成。各チームリーダーに協力を得て、接点があるペアには1、ない場合には0と記入。センシティブな情報なため社内公開は見送りました。 次に、最適化アルゴリズムを以下の要件に従って実装: 現状の接点組み合わせを入力 同じグループにマネージャーは1人まで 開催することでグループ内の社員同士の接点を増やす 上記を3回反復して全体の接点数を最大化 実装は善之さんが担当。出力結果をもとにGoogleスプレッドシートでランチグループ管理表を作成し、全員へ展開。調整や記録が容易になります。 社内決裁や、参加者にランチ会について周知するために用いる企画資料の作成も行いました。これで準備が整いました。 企画の承認(3週目) 社内決裁は運営マネージャーが取りまとめ、迅速に承認および経費予算の決裁を得ました。 シャッフルランチ会の運用(4週目~(3ヶ月間)) 運用フェーズでは以下を実施しました。 運営側: 企画案内・周知 月に1回各グループの推進を依頼 参加者側: 合計3回参加(取りまとめ役を最大1回担当) ランチ時間内に雑談や情報交換 他のチームが行ったお店を候補選定に利用 以上で、シャッフルランチ会が3ヶ月間進みました。 振り返りと評価 シャッフルランチ会の効果を調査するため、アンケートを実施しました。 アンケート設計 アンケートで得たい情報は以下です: 効果についての定量的評価 ランチ会の改善ポイント 他のアイディアがあるか Google Formsで1週間の記名式アンケートを行いました。 結果と今後の改善点 回答率は74%。結果サマリは以下の通りです。 質問 結果サマリ ランチ会で新たな接点が作れたか? 全員が新たな接点を作ることができた ランチ会で不安や抵抗感が減ったか? 全員不安や抵抗感が低減、最も多かった回答は「かなり減った」 新たに話せるようになった人数 目標と比べて若干少なかったものの、平均して複数名との接点が作れていた 気軽に話せる人数 全員一定数いるものの、まだ増加余地あり ランチ会の頻度、継続する場合の頻度 月1回がちょうど良い。継続するなら1ヶ月に1回前後が適切 改善点(自由記述) 実施負荷軽減、チーム編成 他の企画アイディア(自由記述) 交流機会増加、社員のプロフィール拡充 具体的な数値や考察はここでは省略しますが、ランチ会が新たな接点作りやコミュニケーションの不安減少に効果がありました。 まとめ ランチ会開催による検証結果は以下のとおりです。 アンケート調査によれば、チームビルディングや社内コミュニケーションが向上しました。 コミュニケーション改善を継続するために、ランチ会を引き続き実施することが望ましいです。 ランチ会を継続する際は月1回程度が理想的であり、実施負荷の軽減やチーム編成の改善も検討が必要です。 Insight Edgeの社内コミュニケーション向上取り組みが、同様の課題を持つ組織にとって参考になれば幸いです。 また、Insight Edgeは社員が自ら意見を述べ、成果向上のために柔軟に組織を変革できる環境です。この柔軟さを活かし、会社を共に盛り上げてくれるメンバを募集しています。 興味がある方はカジュアルにお話させて頂ければと思いますので、お気軽に 公式サイト の採用ページからお申し込みください! Google Sheets + Google Apps Script でローカル開発・本番デプロイ ↩
こんにちは! Lead Data Scientistの梶原(悠)です。 Insight Edgeには商社内の資源系ビジネス部門から市況・需要予測系の相談が多くよせられます。 しかし、この種の案件は予測モデリングの本質的な難易度とユーザーからの期待値のずれが大きく、なかなか有効な活用に繋がりにくい印象があります。 こうした現況の改善に向けて技術的な論点は色々あるのですが、今回は、市況が急変するなどしてデータの分布が大きく変わるケースの対処をテーマに、簡易なツール調査を行います。 目次 ドリフトとは ドリフトの検出ツール 検出デモ まとめ データドリフトとは データドリフトは、時間の経過によって特徴量の分布が変化してしまう現象です。 例えば、金属価格を予測する機械学習モデルを訓練し、継続的に運用するサービスを考えます。 このモデルの説明変数の分布は、イノベーションや地政学的なショックにより変化する可能性があります。 学習時点では現れなかったようなサンプルが頻出すると、予測の信頼性が悪化する可能性があります。 関連する概念として、コンセプトドリフトやラベルドリフトなどが挙げられます。 概念 継続的運用やオンライン学習の文脈 バッチ学習の文脈 説明変数と目的変数の関係の変化 コンセプトドリフト(Concept drift) コンセプトシフト(Concept shift) 目的変数の分布の変化 ラベルドリフト(Label drift) ラベルシフト(Label shift) 説明変数の分布の変化 データドリフト(Data drift) データセットシフト(Dataset shift) / 共変量シフト(Covariate shift) データドリフトの影響を緩和するために、定期的なデータの監視とモデルの再学習が有効です。 運用環境のデータと訓練データとの間で分布の差異を監視することで、ドリフトの早期検出が可能になります。 ドリフトの検出ツール オープンソースのドリフト検出ツールは多数存在します。 使える手法の豊富さや可視化機能の充実度合いなどによる差別化が見られます。 ツール名 ライセンス プログラミング言語 フレームワーク ドキュメンテーション Seldon Alibi-detect Apache 2.0 Python TensorFlow, PyTorch https://docs.seldon.io/projects/alibi-detect/en/latest/ Evidently Drift Detection Apache 2.0 Python scikit-learn https://docs.evidentlyai.com/ TorchDrift MIT Python PyTorch https://torchdrift.org/ Deequ Apache 2.0 Scala, Java Apache Spark https://github.com/awslabs/deequ 検出デモ Seldon Alibi-detectによるシンプルなドリフト検知デモを行います。 確率分布間の距離の指標であるMMD(Maximum Mean Discrepancy)を使ってドリフトを検知するalibi_detect.cd.MMDDriftOnlineクラスを使用します。 ある企業では翌日の金属価格の予測モデルを日次で運用しているとします。 さらにこのユーザーは、説明変数にガス・石油の燃料価格を用いているとします。 これらの変数のドリフトを捉えたいとします。 まず、セントルイス銀行のWebサイトからガスと石油のサンプルデータを取得してみます。 import pandas as pd import pandas_datareader.data as web import datetime tickers = { "PNGASJPUSDM" : "Gas" , "DCOILBRENTEU" : "Oil" } data_df = ( web.DataReader( tickers.keys(), "fred" , start=datetime.datetime( 2016 , 1 , 1 ), end=datetime.datetime( 2023 , 2 , 1 ) ) .interpolate() .dropna() .rename(columns=tickers) ) 可視化してみると、2020年前半の石油価格の落ち込みや2021年からのガスと石油の価格高騰が目立ちます。2020年の落ち込みはコロナショック、2021年からの高騰はウクライナ危機によるものと想像されます。 現在は2019年の1月だとします。過去のデータを参照期間として、ドリフトの検出器を作成します。 検出器の内部では、並べかえ検定によりドリフト検出の閾値が作成されます。 今回のケースでは、並べかえ検定による閾値は小さすぎて実用的でありません。 実際に使う場合は、参照期間のデータの時系列クロスバリデーションなどにより、自分で閾値を作り込む対処が考えられます。 from alibi_detect.cd import MMDDriftOnline # データを参照期間とテスト期間に分割する. now = pd.to_datetime( "2019/1/1" ) ref_df = data_df[data_df.index <= now] test_df = data_df[now < data_df.index] X_ref = ref_df.values.copy() X_test = test_df.values.copy() # ドリフト検出器を作成する. detector = MMDDriftOnline( X_ref, ert= 500 , window_size= 30 , backend= "pytorch" , verbose= True ) 未来のデータをテスト期間とし、検出器を走らせてMMDを算出します。 素朴な参考値として、参照期間で走らせたMMDも算出してみます。 # 参照期間とテスト期間のMMDを算出する. test_df[ "squared_mmd" ] = [detector.score(x) for x in X_test] ref_df[ "squared_mmd" ] = [detector.score(x) for x in X_ref] テスト期間のMMDは、2020年の4月や2021年の10月に参照期間におけるMMDの最大値を超えています。 これは、コロナショックやウクライナ危機による燃料価格の急変を検出していると想像されます。 まとめ この記事では、データドリフトの検出ツールのライトな調査を実施し、燃料価格のデータでMMDによるドリフト検出のデモを行いました。 かなり尻切れとんぼな内容になってしまいましたが、何らかの参考になりましたら幸いです。 学習時と運用時の間でデータの分布が変わってしまう問題周辺は、継続的に調査していきたいと思っています。
  はじめまして! Insight Edge で UI/UX デザイナーとして働いている佐藤と申します。 IT の業界におられる方はご承知の通り、UI デザインツールは様々存在し日々進化をしてきました。 Adobe XD、Sketch、Figma と様々、実務の中で導入し使用してきた中で、コンポーネント作成やプロトタイプ作成、リサイズやレスポンシブデザイン、そしてエンジニアへインスペクト連携する際などの効率性などの視点などから、現時点で最も優れているツールは、Figma が頭ひとつ抜きん出ている印象に感じております。 そこで、この記事では、Autolayout によるデザイン作成などを中心に、使い方や作り方の基礎をお話出来ればと思います。 AutoLayout は業務の効率化に繋がる AutoLayout を使いこなす上での基本的な機能 拡大縮小時に便利なフレーム設定 起点を設定する方法や設定の場所 要素の配置バリエーション バリアント作成をしてさらなる効率化 バリアント機能を使ってボタンの状態変化を簡単に作れる プロトタイプ作成時にも重宝 ChatGPT にも訊いてみた まとめ 参考記事 AutoLayout は業務の効率化に繋がる デザインがある程度決まった後に、或いはプロトタイプを作成する段階で、様々なデバイスでデザインの確認や検証を行うケースがあると思います。共通部品となる要素をコンポーネント化し、かつ中身を AutoLayout によって作成しておくと、部品のリサイズやプロトタイプ作成時において、デザイン業務の効率化や高速化を実現できます。 今回、記事用にサンプルの画面デザインを用意しました。 下記の画像の場合カード部分が共通コンポーネントです。 AutoLayout を使いこなす上での基本的な機能 拡大縮小時に便利なフレーム設定 Figma の インターフェースの項目に「フレーム」というものがあります。 ここで何を基準にして中身を構成するか設定できます。 上のアニメーションで見せたような、横に伸ばして要素を伸び縮みさせる方法はここのメニューを駆使して調整していきます。 上記にある GIF 画像のように、文字量に応じて縦の長さを可変させる場合は「コンテンツをハグ」を、なおかつ横に伸ばす時に本文部分をカード横サイズに合わせて追従させる場合は「コンテナに合わせて拡大」を選択します。固定幅はその名の通りです。これらを応用して組み合わせて、コンポーネントを作成しておけば、いくつも同じ要素が並ぶようなインターフェースを作る際に一気に展開や修正することができます。 起点を設定する方法や設定の場所 先程、見ていた「フレーム」メニューの真下に「オートレイアウト」のメニューがありますね。 ここも作成する上で大変重要で、よく触るメニューです。 「フレーム」&「オートレイアウト」で基本的にはおおよそのレイアウトは完成しますし、要素を揃えたりマージンを共通の数値にしたり、起点を設定したり、とても役立ちます。 余談ですが、私はこの Figma のインターフェース(9つのマトのようなもの)を初めて見た時に、何か操作できるものに直感的には正直見えなくて、気づいた時「え?これ触れるの?」って戸惑った記憶があります。気付くまで、苦戦した記憶があります…。 整列系のメニューが上部にもあるので、感覚で覚えようとしたら遠回りしました。 なので、初心者の方は是非ここを見落とさないようにすると大変良いと思います。 リリース初期から触ってる方や、詳しい方にとっては「そんなの普通に分かるわ!」と言われそうですけど、この仕組みに気付くまで、灯台下暗し状態でした。 気づいた時、これ楽々だな〜と感じたのを思い出します。 ここを理解しておけば、情報設計の構築 → デザイン案用にタタキ台や感覚で作った状態の絵 → 清書を進めながら要素の整列をし精緻化していく → プロダクトの対応デバイスに応じてデザインのリサイズやイメージの作成をする → エンジニアにインスペクト連携する、といった一連の UI デザイン作成の流れを素早く行うことが可能になります。正確な数値で作りたい時や起点をどこにするかの設定にも、一瞬で応えてくれるので便利です。 要素の配置バリエーション 詳細設定の使い方次第で様々な表現が可能になります。ほんの一例ですが画像で紹介しておきます。 バリアント作成をしてさらなる効率化 バリアント機能を使ってボタンの状態変化を簡単に作れる UIデザインを作成していく上で、上記に触れたレイアウトの他に、ボタンの状態の変化やプロトタイプを作ってイメージを確認する時などに役立つのが「バリアント」機能です。 こちらを準備しておくと下記の画像のように、同種のコンポーネントを一つのグループにして、レイヤー名を自動でプロパティ値に設定してくれるので、ボタンの状態変化を表現したりプロトタイプの作成時に便利です。 プロトタイプ作成時にも重宝 プロトタイプやモックを作る時にも非常に便利です。 プロトタイプパネルへ移動した後、対象のコンポーネントを選択して、インタラクションを追加し、挙動に応じてプロパティを変更するだけで、プロトタイプの作成時に、状態変化用に別ページを増やさなくても作ることができます。 ChatGPT にも訊いてみた 昨今もっぱら世の中を賑わせている ChatGPT。 せっかくなので GPT 先生にも、以下のように質問してみました。 解説上手すぎて完全に負けました…(途中で「使っ」で切れてるけど…) 今回は、可視化して説明することに重点を置いたので、自分の言葉で書き始めてまとめており、抜粋して使うこともしなかったのですが、今後は利活用していこうっと…(泣)と感じております。 まとめ 難易度としては、さほど高くない内容ですが、初級の方に向けて参考になればと思っています。 冒頭でも述べましたが、ここ数年のプロトタイピングツール戦国時代の中で、主に私は、並行してAdobe XD や Sketch そして Figma を導入し、実務の現場の中で覚えて参りました。 20年以上デザインの現場にいる者として、Adobe Illustrator や Photoshop のレガシー的なツールに慣れ親しんだ人間としては、ソフトの使い方の観点という意味で最初は難航したものの、実際に手を動かしてマスターするとそれぞれの利点に納得するものです。 そして、その中で「使える」ものはどんどん吸収していくべきだと考えています。 AutoLayout についてもその中の小さな手法に過ぎませんが、日々新しい事を覚えて、面白く感じる、便利に感じる、痒い所に手が届いている、なんて感じる日々を積み重ねる事が、何かを良い方向に変えていく原動力になっていくものと信じています。 大袈裟ですが、それが業務効率化や作成速度の向上に繋がるのであれば、他の新たなキャッチアップに時間を割くことも可能になりますし、時代の流れとして今後は使えるものを上手に使う、Figma の AutoLayout や先に述べた ChatGPT についてもそうですが、そういった「道具」をより上手く使って、効率的に仕事をこなしていくことが求められていくようになっていく気がしています。 本日現在だと、Figma に対応した GPT AI プラグインも出現してきています。便利なモノは積極的に利活用して、良き相棒が自分の側にいるような気分で楽しんで参考にしながら、進化するデザインツールや AI と向き合っていきたいと感じている今日この頃です。 参考記事 Figma auto layout playground (Community) https://www.figma.com/community/file/784448220678228461/Figma-Auto-Layout-playground/Figma-auto-layout-playground Figma tutorial (What’s new in Auto layout) https://www.youtube.com/watch?v=floQKLsWAy4 www.youtube.com  
こんにちは、開発エンジニアの熊田です。入社してから早くも2か月が経ちました。 今回は、AWSに触れる機会の少なかった私が、ある案件のアプリ基盤のリプレイス作業を担当した経験を振り返りながら、運用保守を外部委託するまでの話を書いていきます。 目次 AWSアカウントの分離 背景 - これまで社内共通AWSアカウントで運用していた 新規AWSアカウントを作成 アプリ基盤のリプレイス 課題 - ストレージ不足とデプロイ作業の属人化 対応 - Fargateの導入とドキュメント整備 運用保守の外部委託 ユーザ管理と最小権限 役割の明確化 振り返り ECSではなくLambdaの方がベストだった IaCと自動デプロイ利用による改善余地あり 終わりに AWSアカウントの分離 まず、リプレイス作業するにあたり、既存環境とは別に新環境用のAWSアカウントを用意しました。 AWS Organizationsを利用していましたので、アカウントは「組織の一部であるAWSアカウント」として作成しました。 背景 - これまで社内共通AWSアカウントで運用していた AWSアカウントを分離する背景には、Insight Edge設立初期の案件という特有の事情がありました。 技術検証用AWSアカウントが用意されており、開発メンバは各々そこでAWSサービスを利用して開発作業を行ってきました。 いまでも技術検証目的で利用するアカウントですが、AWS Organizationsを導入する以前ですと、技術検証用AWSアカウントのまま本番利用するものがありました。 今回のリプレイス対象がまさに上記に該当します。これまでは社内メンバが管理していたので特に支障はありませんでしたが、社員の保守工数を下げる目的で外部委託することにしました。 共通アカウント上にあることで問題となるのは、委託先に開示してはいけないシステムとしたいシステムの区別ができないことです。 これに対処するには、別途AWSアカウントを作成する必要がありました。 新規AWSアカウントを作成 そもそもAWS Organizationsは、用途ごとにAWSアカウントを分けるマルチアカウント運用をする際には必須といえるサービスです。 公式サイトによると、AWSアカウントを一元管理するサービスで、1つの管理アカウントと複数のメンバーアカウントで構成されます。 アカウントを組織単位 (OU) にグループ化し、各OUに異なるアクセス ポリシーをアタッチできます。 さらに、メンバーアカウントの一括請求にも対応しています。 「組織の一部であるAWSアカウント」としてアカウント作成する手順は 公式サイト に載っており、簡単に実施できます。 アプリ基盤のリプレイス さてさて、アカウントの準備ができました。 次に必要だったのは、旧環境の構成であるDocker on EC2から、Fargate&ECSへのリプレイスです。 EC2上でコンテナを動かす際に2つ課題が生じていましたので、それらに対応するためリプレイスを実施しました。 課題 - ストレージ不足とデプロイ作業の属人化 まずはストレージ不足による定期的なメンテナンス作業が必要であることがあげられます。サーバレスでない環境ではよく起こる問題です。 Linuxディストリビューションでのバックアップがストレージを逼迫させることがあり、サービスが停止することもありました。 もうひとつの課題は、デプロイ作業の属人化です。 最初にEC2上でコンテナを起動し、そのコンテナ上でソースファイルや学習モデルを更新していくため、初期のDockerイメージと乖離してしまっていました。 また、Dockerfileなども用意されていなかったため、現在使用しているDockerイメージが何をもとに作成されたのか、すぐには把握しきれなくなってしまいました。 最初に構築した人が作業する分にはこれまでの作業を把握しており大きな問題にはなりませんが、他の人が対応する場合はなかなか作業が進められない状況に陥っていました。 対応 - Fargateの導入とドキュメント整備 これら課題に対応するべく、Fargateを使うことにしました。 サーバレスであればストレージ不足に悩む必要もありませんし、諸々のメンテナンス作業からも解放されます。 また、もともとDockerコンテナを利用していたため、オーソドックスなFargate&ECS構成へリプレイスすることにしました。 構成図としては以下のような形になっています。 Application Load Balancer(ALB)の前にNetwork Load Balancer(NLB)を配置しています。 ALBのIPは動的に変わることを考慮して、この構成にしています。NLBにより静的IPを利用できるので、NLBのターゲットグループにALBを設定しています。 今回のアプリは、NLBのIPアドレスを利用してスプレッドシート(GAS)からAPIリクエストするため、上記のようにIPアドレスが変わらないよう対策しています。 構築する際は、こちらのブログを参考にさせていただきました。 Network Load BalancerのターゲットグループにApplication Load Balancerを設定する もうひとつの課題であるデプロイ作業の属人化を解消するために、ドキュメント整備にも力を入れています。 外部委託先の方が困らないようにするためにも、初見の人でもデプロイできる手順書を作成しました。 具体的には、ローカル端末でのイメージ作成、ECRにPush、ECSサービスの更新を実施する手順です。 今回のアプリは本番運用開始から数年経っており、頻繁にデプロイされるアプリでもなかったので、今回はコスト的な観点から自動デプロイは用意しませんでした。 余談ではありますが、ECS Execを利用できるように設定しています。 これにより、ローカル端末からFargateのコンテナへ直接アクセスできるようになります。 SSH接続が不要であり、エラー発生時のトラブルシューティングにも活用できる利点がありますので用意しました。 運用保守の外部委託 新環境の構築が終わりましたので、ようやく運用保守を外部委託する話に移ります。 なお、外部委託するにあたって、NDA(秘密保持契約)は必須ですので、委託先への情報開示は締結後に行います。 マネージャに対応してもらっていましたので、詳細については触れませんが用語の説明を記載しておきます。 NDA(秘密保持契約)とは 相手方に開示する自社の秘密情報について、契約締結時に予定している用途以外で使うことや、他人に開示することを禁止したい場合に締結する契約 (引用元:https://keiyaku-watch.jp/media/keiyakuruikei/himitsuhojikeiyaku/nda/) ユーザ管理と最小権限 システム面の話に戻ります。委託先のメンバが運用保守を行うためには、IAMユーザ、GitHubへのアクセス権限が必要になります。 IAMユーザについては、新しく作成したアカウントでIAMユーザを作成しました。 委託メンバは複数名いたので、グループを作成しポリシーをアタッチしました。 そして、グループにIAMユーザを追加します。 これにより、各メンバのアクセス権限をグループとして一括で管理できるようになります。 なお、グループにアタッチしたポリシーは最小権限に設定しています。 よく目にする用語だと思いますが、「最小特権の原則」に則っています。 セキュリティ強化を目的に推奨されてる原則で、障害や不正による被害を最小限に抑えられる効果があります。 GitHubについては、Organizationを利用しているので、outside collaboratorとして招待しました。 数あるリポジトリのうち一部しか参照してほしくないので、リポジトリごとに招待しています。 実際の作業は、GitHub管理者の方に実施いただきました。 役割の明確化 委託するにあたって、役割を明確にしました。当たり前な話ではありますが、仕事を進める上で役割・責任を明確にすることは重要です。 委託先には何を担っていただきたいか、一方、委託元であるInsight Edgeとしても何を担ったままなのか明確に定義しました。 そのうえで作業フローのイメージを共有することで、双方の仕事が進めやすくなるかと思います。 振り返り 今回の作業ではやらなかったことや、今後やってみたいことがありましたので、それらについてまとめていきます。 ECSではなくLambdaの方がベストだった? コンテナを実行する構成としてオーソドックスなFargate&ECS構成を採用しましたが、リクエストの量、リクエストの時間帯が限定的なことから、Lambda実行にした方が、コストメリットがあったように思われます。 ただし、新環境自体は高スペックではないので、コストの差は微々たるものでした。 それよりもlambdaはコールドスタート問題があり、処理時間が想定より長くなる可能性があったため、今回はUXを優先してLambda利用を見送りました。 今回は検証できませんでしたが、SnapStart等でLambdaのコールドスタートを短縮出来る可能性があるので今後検証してみたいです。 IaCと自動デプロイ利用による改善余地あり AWSサービスの構築はIaCを利用してコード化&自動構築する方法もありましたが、今回はスピード優先としてコンソール画面から環境構築しています。 また、GitHubワークフローなどを用意して、自動デプロイする手もありましたが、今回の案件は開発及びデプロイ頻度が極まれでした。 そのため前述でも触れたとおり、手順書、DockerFileがあれば十分対応できることもあり、コスト観点からGitHubワークフローの用意は行っていません。 今後、追加開発などが始まり、環境構築、デプロイ頻度が増える場合には、それぞれの自動化を検討したいと思います。 終わりに リプレイス作業と外部委託についての話は以上になります。読んでいただきありがとうございました。 Insight Edgeに入社して2か月経ちましたが、非常に充実した日々を過ごしています。弊社には、技術力の高い人たちが集まり、プロジェクトも挑戦的なものばかりです。 最初は私の慣れもかねてスタンダードな作業に取り組んでいました。そして、現在は新規サービスを開発する挑戦的なプロジェクトに関わっています。 前職では会計システム開発にずっと関わっており安定性重視の仕事をしてきましたが、いまは新しいことへ取り組む機会にあふれており非常にやりがいがあります。 いま参画しているプロジェクトでもたくさんの学びがありますので、次回はそのことについての記事を書いてみたいなと思います。
(with the Frontier Development Lab, SETI Institute, Triullium USA, NASA, and Google Cloud) Why go outside the company to build new skills New problems, new solutions, fresh perspective Insight Edge has a unique mission when compared to other DX-oriented startups: we target digital transformation among the overall Sumitomo Corporation group. That may sound like a limited purview -- after all, other Developer Experience and AI-themed startups may serve an entire market of industrial clients, as long as the project is mutually agreeable. While perhaps counterintuitive, our focused mission is actually a source of a wide variety of projects. Our collaborative relationship with our parent SC's DXC means that our projects can take a longer view, rather than fret over meeting strict quarterly profit quotas. We may incur short-term costs, as long as the longer-term mission and output are producing growth for SC and our global operating companies. You can think of us as building DX momentum for the group, rather than trying to maximize profits per project. With all of that being said, there are times when we, as data scientists and engineers at Insight Edge, could benefit from exploring problems outside the group. If all we ever look at are the obvious issues with which our partners are struggling, we could miss chances to be more proactive, inventive, and creative. Part of how we have accomplished this outside grounding is by having a diverse talent pool from the start. IE's technical staff includes those with doctoral degrees in chemistry, planetary science, neuroscience, and even astronomy (that's me). This gives our team a diverse and scientifically savvy background, but what's even better is the opportunity for us to reconnect with scientific-themed projects, or even better, novel challenges requiring a combination of professionals from ML and cloud engineering to physical science. In addition, our members often engage in external events like exhibitions, conferences, and workshops to encounter new issues which at least, on-the-surface, are different from what the SC group is currently struggling with (see the SC Group home page for an overview of our global activities). What is the Frontier Development Lab The Frontier Development Lab is a public-private partnership whose planning is year-round. However, the actual R&D takes place during an intense, adventurous (often challenging) 2-month agile development sprint. From Space to Sustainability FDL started in 2016, and originally focused on challenges combing space science domain knowledge with data science and machine learning practical expertise. Project themes have branched out from space exploration into sustainability-related projects, such as earth observation, disaster prevention and management, and renewable energy. Public-private partnership FDL's core government-side partner has been NASA, with the SETI institute acting as a liaison between NASA and private partners, such as NVIDIA, Google Cloud, and others. As of 2022, the US Department of Energy began sponsoring FDL challenge teams, broadening the scope beyond just projects with a clear space connection. Agile Development in Novel Situations The agile development aspect of FDL is what has always really inspired me. Because here you have a way of working that meshes well with what industry-employed developers and engineers often face: face-paced, incremental agile dev and problem-solving cycles. And at the same time, this is a skill that is also much-needed in academia, where projects can tend to stagnate or be siloed into the domain of a few key experts' labs. Astrobiology tools, and problems relevant to industrial data science From Frontier Science Toward Frontier Industries FDL has sometimes hosted challenges over the years that seem rather avant-guard. Probably at top of the list would be the "Astrobiology" team, which I was part of in 2018 and then again in 2022. You might think: "Wait, that has absolutely nothing to do with industry!" But, as a discipline, Astrobiology connects multiple domains and fundamental questions, including the challenge of understanding even the evolution of the most rudimentary systems of life and their theoretical constituent building blocks, to the future of our planet, and the search for signs of present or past life in our Solar System. The Need for Interdisciplinarity Personally speaking, working through the challenges of the Astrobiology project through FDL is analogous to the challenges I face in my industry job. At Insight Edge, we are constantly taking on new DX challenges from SC subsidiaries and business units from all sorts of domains, from manufacturing to energy grids --- and this has us always diving into new projects trying to learn how that domain handles their problems, how we can help, and how we can connect them to what we've learned from other domains, in completely different projects. This means the Insight Edge Engineers have to be somewhat interdisciplinary by nature. Astrobiology is likewise one of the most interdisciplinary research topics one can address. Even if you build a team, like ours at FDL, with two "Astriobiolgists" -- they will most likely have two very different perspectives and skillsets, e.g. geophysics and bioinformatics. FDL is as much about solving a problem we're given as it is redefining the problem based on our team's talents. Projects at Insight Edge are similar: given sometimes limited resources, we adapt what we can propose and how to accomplish it based on what talent we have available, on the actual situation on the ground. We have to build relationships with the folks actually working in and managing factories, for example. Likewise in FDL, in a very fast timeframe, we have to reach out to many different stakeholders, scientific collaborators, and data repositories in a very short timeframe, so that what we create at FDL is well-tuned to the actual scientific needs: we don't pretend to know everything about our challenge within our small team. Space Science and AI In recent years "AI" has become a kind of catch-all term for so many things. Sometimes the ways that FDL connects Space Science and AI can feel like Science Fiction. But we have more concrete reasons for looking to AI -- more specifically, autonomous spacecraft and robotics, and automated on-site sampling and data analysis techniques. Difficulty of pre-training models for unexpected environments Even the latest language models have difficulty being trained for all the different situations and contexts humans may throw at them. Now imagine you're training a model to explore new worlds: there is a huge risk that we can "overfit" such models to environments we know on Earth. Rather than simply pre-training a classifier on known living organisms and biomolecues, we needed to find a way to both remain open to yet unknown biomolecular structures, but still have something that coud perform inference rapidly and efficienty. Resource Limitations on Space Missions What most AI news these days is talking about is usually deep learning. Very large models of data, with millions, even billions of parameters, that can map inputs to outputs. You might have also heard how incredibly expensive these models are to train. Even so, actually using a trained model is often feasible enough that we can deploy them as web apps on cloud services and GPU servers and so on, such that tech-savvy users are bound to run across these tools on a daily basis (your iPhone auto-categorizing your photos, a web page for anime-izing your homework.) For reference, consider the computers onboard existing missions. The Mars Perserverance rover is equipped with only a single-core CPU, operating at only a few hundred MHz -- very conservative compared to even the standard environment of a Google Colab, or an NVIDIA GTX10 series budget gaming PC. Space hardware is heavily customized for specific environments, power constraints, and the need for redundancy in missions where hardware failure can mean the permamnent loss of a multi-million dollar mission. While it's true future missions will likely have more computing power than Perserverance, which launched in 1998, it is unlikely they will keep pace with the environments that deep-learning developers take for granted. Any comutational techniques developed now for future missions need to take such constraints into acount. Communication delays Space is big. Mission time is often limited. Especially when you consider specific windows of opportunity, like the time between dust storms on Mars, etc. While many calculational or simulational tools exist for assessing samples and deciding on the next steps of an experiment, two major limitations exist: 1) not all tools, but many of them can be very computationally expensive, and the hardware onboard space probes are often very conservative (and sometimes several years old by the time the mission starts!). 2) Mid-mission assessment, experiment planning, and re-planning, typically require human intervention -- meaning a round-trip transmission lag from your favorite planet or moon. Autonomous or at least semi-autonomous experiment guiding could save precious actual sampling time. Also, computationally expensive calculations and simulations could be "compressed" or emulated by a well-trained neural network, which could give us a balanced trade-off between accuracy, computational time, and hardware requirements. Life Creates Complexity: How do we search for it Agnostic detection approaches If we could assume life elsewhere in the universe resembled Earth life, tests for specific molecules and reactions common among all known organisms might apply. But how can we make such an assumption, knowing the vast diversity of environments out there? What our FDL project assumes instead is that life could take on molecular forms we may not recognize. Instead, we consider that life, in whatever form, is likely to assemble simple building blocks into more complex ones. A way of looking at this idea in more concrete terms, is to think in terms of molecular complexity: how likely is it that a sample of molecules detected on some celestial body formed without the involvement of life? Geological and atmospheric processes can definitely result in complex molecules, but statistically speaking, high abundances of increasingly complex molecules may be one of the most agnostic indications of the presence of living processes at work. For the technical discussion, I'll rely mostly on quotes from our workshop abstract , presented at the ML for Physical Sciences workshop , at the 2022 NeurIPS conference in New Orleans. By all means, please have a read, and look into the references therein! This was a very collaborative work, This paper was led by our team's ML-lead, Timothy (Timmy) Gebhard , and describes the work we carried-out over 8 weeks in summer 2022. Also on the core researcher team were Jaden ("J.J.") Hastings , who grounded our bioinformatics and space exploration goals; and Jian Gong , our primary geochemistry/geobiology domain expert, who also took the lead on our data strategy. We go into the details of three commonly cited metrics for defining molecular complexity. I'll quote the descriptions below, but the main point is that it's not an easy question to answer, so we opted to explore multiple metrics instead of favoring a particular one: another approach that tends to appear in industrial data science projects, whenever tackling a novel industry or task. Fundamentally, [molecular complexity, "MC"] measures are numeric features intrinsic to a molecule that represent an abstraction of its structure (or formation process) while also characterizing its information content. Intuitively, one may expect MC to increase with the molecular size, the multiplicity of bonds, or the presence of heteroatoms, while it should decrease with increasing symmetry (Randic, 2005). MC is usually not considered as an end in itself but used in a relative fashion to compare molecules or to characterize chemical reactions. Various definitions of MC have been proposed in the literature (see, e.g., the introduction of (Boettcher, 2016) for an overview), typically building on concepts from graph and information theory. In this work, we focus on the following three definitions: 1) Bertz complexity $C_T$ (Bertz, 1981): The first general index of molecular complexity. It combines concepts from graph and information theory and is defined as $C_T = C(\eta) + C(E)$, where $C(\eta)$ describes the bond structure and $C(E)$ the complexity due to heteroatoms. Calculating $C_T$ is fast and scales linearly with the molecule size. We compute $C_T$ using the BertzCT method from RDKit (The RDKit Team, Landrum et al.) 2) Böttcher complexity $C_m$ (Boettcher; 2016, 2017) An information-theoric measure that is based on the information content in the microenvironments of all atoms; it is additive and simple to calculate even for large molecules. Our computation of $C_m$ uses a freely available open source implementation (Boskovic et al, 2020). 3) Molecular Assembly index (MA) (Marshall et al., 2017): Also known as pathway complexity, the MA represents the minimum number of steps required to assemble a molecule from fundamental building blocks. MA is particularly well-suited to biosignature detection while being experimentally verifiable (Marshall, et al., 2021). It is at least as hard as NP-complete to compute (Liu, et al., 2021), requiring hundreds of CPU hours even for moderately sized molecules. We use a (currently non-public) implementation kindly provided by the authors of Marshall, et al. (2017). While we chose to look at multiple metrics, we also found correlation often exists between them. I mention this primarily because of how often this occurs in industrial data science too! Often in our projects, we receive data sources that at least at first glance, appear to be highly correlated. Only when we explore higher dimensional features can we see which data is more informative. We note that, to first order and at low molecular weights, these three measures are strongly correlated; the mass of the molecule acts as a confounder constraining the maximum complexity. When regressing out the mass, however, the correlation becomes less strong, and it becomes apparent that $C_T$, $C_M$, and MA each capture different aspects of the molecule. Inferring complexity with Machine Learning How to measure molecular complexity is not a settled matter (pun intended.) In our paper, we detail various approaches to train models on molecular complexity data, in hopes that we could eventually achieve a model that can -- at perhaps some accuracy costs -- very quickly infer scores that could otherwise take a very long time to calculate. Figure 1 illustrates where this capability might fit into a broader, automated analysis pipeline of a rover or probe exploring another celestial body. Dataset generation While the above talk of molecules and complexities and life and space might already sound overwhelming, the truth of this project -- not unlike almost every project I have worked on in industry as well -- is that dataset curation was the main obstacle. As often occurs in data science, potentially relevant but fragmented or not well-organized datasets are all around. But actually putting together that data needed, and having it in both a format ready for ML, as well as to be able to describe it easily to stakeholders, is most of the battle. The following text describes some of the technical particulars, but the main hurdle was acquiring mass-spectrometry data for all the molecules we wanted to explore. This is the practical crux of our challenge since mass-spectrometry is very likely the type of data that a future probe would actually sample. As no ready-made dataset for our task---inferring MC from MS data---exists, we created our own. For this, we queried a public database ( the NIST WebBook ), retrieving all molecules below 1000 Dalton for which an MS was available. These molecules were then appended with other basic chemical properties, including our three MC metrics. Empirical MS data on NIST were taken using electron ionization at a $m/z$-resolution of 1 (standard MS as opposed to higher resolution tandem MS that employs a variety of techniques). This is approximately comparable to the target resolution of 0.4-3 of the DraMS instrument onboard the Dragonfly mission (Grubisic, 2021). Our final dataset consists of 17,021 unique molecules with associated MS, randomly split into a training set with 12,000 molecules and an evaluation set with 5,021. [...] The major limitation of the dataset generation was the computation of MA, taking 65,000+ CPU hours over hundreds of compute-optimized nodes on Google Cloud in parallel. Prediction results Apologies in advance for the spoiler: we did not detect aliens by the end of the summer. We did achieve a model that shows promising performance at predicting various molecular complexity scores! The following quote gives some technical details, described visually in the figure thereafter. Unsurprisingly, all models outperform the naïve baseline (reducing the error by more than 50% in best case), and non-linear models perform better than the linear one. More interestingly, we find that there is a consistent trend across all models that MA is easier to predict than Böttcher complexity, which in turn is easier to predict than Bertz complexity (evidenced by respectively lower predicted errors). We speculate that this may have to do with the definition of the MA, which is, in a way, conceptually similar to the idea of mass spectrometry: The MA counts the number of steps to assemble a molecule from smaller pieces, while MS observers the patterns that emerge when a molecule is fragmented. Of course, what really matters is how this is put into use, highlighting another issue in industry: a lot of projects end in PoCs. Often preliminary results, even promising, may not translate to actual performance. This can be for a variety of reasons: the PoC dataset was not representative of production, the stakeholders have limited appetite for production implementation, or the PoC itself did not connect to a real commitment to production (i.e. its main goal was to convince a manager or administrator to pay attention, or allocate resources in a certain way towards long-term goals.) In our case, the project is designed to demonstrate that scientific and space exploration bottlenecks may be resolved with agile science-tech development, and machine learning approaches. The actual implementation depends on so many different factors, but we are extremely hopeful that this work has demonstrated the problem-solving potential of machine learning toward finding new neighbors in our solar system. FDL in 2023 FDL is an ongoing program, with the particular topics addressed varying each year. For the latest news, please see the FDL 2023 homepage ! You'll find the full summary of results for all FDL 2022 teams here . As for me, I'll continue to actively hunt for life beyond earth (with my Insight Edge vacation days); and continue to look for ways to both contribute my industry knowledge beyond Insight Edge -- but also for ways that are extremely distant (pun intended, as usual) topics and challenges can help inspire my work within Insight Edge, and the Sumitomo Corporation Group. References References appearing in the quotes above are listed here, but you can find the full reference list for our project in the workshop paper linked before. Here it is again for convenience! M. Randi´c, X. Guo, Plavši´c, and A. T. Balaban, “On the Complexity of Fullerenes and Nanotubes,” in Complexity in Chemistry, Biology, and Ecology, New York, NY, USA: Springer, pages 1–48. DOI: 10.1007/0-387-25871-x_1. T. Böttcher, “An Additive Definition of Molecular Complexity,” Journal of Chemical Information and Modeling, volume 56(3): 462–470, 2016. DOI: 10.1021/acs.jcim.5b00723. S. H. Bertz, “The first general index of molecular complexity,” Journal of the American Chemical Society, volume 103(12): 3599–3601, 1981. DOI: 10.1021/ja00402a071. The RDKit Team (G. Landrum et al.), RDKit: Open-source cheminformatics. Online Boskovic Research Group, bottchercomplexity, 2020. Online , Commit: a212f96. S. M. Marshall, C. Mathis, E. Carrick, G. Keenan, G. J. Cooper, H. Graham, M. Craven, P. S. Gromski, D. G. Moore, S. Walker, and L. Cronin, “Identifying molecules as biosignatures with assembly theory and mass spectrometry,” Nature Communications, volume 12(1), 2021. DOI: 10.1038/s41467-021-23258-x. S. M.Marshall, A. R. G. Murray, and L. Cronin, “A probabilistic framework for identifying biosignatures using Pathway Complexity,” Philosophical Transactions of the Royal Society A: Mathematical, Physical and Engineering Sciences, volume 375(2109): 20160342, 2017. DOI: 10.1098/rsta.2016.0342. Y. Liu, C. Mathis, M. D. Bajczyk, S. M. Marshall, L. Wilbraham, et al., “Exploring and mapping chemical space with molecular assembly trees,” Science Advances, volume 7(39), 2021. DOI: 10.1126/sciadv.abj2465. A.Grubisic, M. G. Trainer, X. Li, W. B. Brinckerhoff, F. H. van Amerom, et al., “Laser Desorption Mass Spectrometry at Saturn’s moon Titan,” International Journal of Mass Spectrometry, volume 470: 116707, 2021. DOI: 10.1016/j.ijms.2021.116707. Acknowledgements This work would not have been possible without the incredible support from our team's mentors: Atılım Günes Baydin, Kimberley Warren-Rhodes, Michael Phillips, G. Matthew Fricke, Nathalie Cabrol, and Scott Sandford; funding and support from Google Cloud, and our primary scientific stakeholder, the NASA Astrobiology Institute, represented by Mary Voytek who provided critical feedback on scientific milestones of the project.
はじめまして。Insight Edgeでリードプロジェクトマネージャーを務めている加藤です。簡単に自己紹介から始めさせて頂きますと、私は2022年にプロジェクトマネージャーとしてInsight Edgeに参画致しました。後述致しますが、参画後、現時点(執筆時点:2023年3月)でも、これまで経験出来なかった様な多種多様な業界/分野にて業務遂行出来ており、非常に刺激的な日々を送る事が出来ております。今回の記事ではInsight Edgeにおけるプロジェクトマネージャーの具体的な業務内容と、参画後、私なりに重要だと考えるスキルセットやマインドセットについて紹介させて頂きます。 対象となる業界/分野について プロジェクトマネージャーの業務内容について ①案件企画/構想段階での内容具体化に向けたマネジメントについて ②開発段階での開発マネジメントについて プロジェクトマネージャーに必要、且つ重要なスキル/マインドセットについて ①核となる普遍的な業務スキルを獲得/養う事 ②スタンスを取る事 ③当事者意識/主体性を持って業務遂行に臨む事 終わりに 対象となる業界/分野について 当社HP ( https://insightedge.jp/company/ )、及び冒頭でも触れた通り、当社は住友商事グループのデジタルトランスフォーメーション(以下、DXと記す)を加速する為の技術専門会社として設立された経緯から、非常に多岐に亘る業界/分野に活躍出来る機会があります。具体的に申し上げれば、金属、建設、インフラ、生活・不動産、農業、エネルギー等々が挙げられ、定量的な観点で言えば、全世界900社強と実際にコラボレーションし、DXの推進を強力、且つ主体的にリードしています。こうした状況下でのプロジェクトマネージャーの具体的な業務内容について、次セクションにて説明致します。 プロジェクトマネージャーの業務内容について プロジェクトマネージャーの具体的な業務内容について、上記の通り、業界/分野の切り口でも多彩で有り、置かれている状況/目指すべき方向性も千差万別である為に、私が対応した範囲でも数多くの業務があり、その中でも主要な業務を挙げると以下になります。 ① 案件企画/構想段階での内容具体化に向けたマネジメント ② 開発段階での開発マネジメント ③ 推進中案件におけるPMO、及び中立性、且つ専門性を発揮したアドバイザリ ④ ITDD、及び新規事業/技術採用における中長期目線、且つPM目線での評価 今回の記事ではこの中で①・②について詳細を記載致します。 ①案件企画/構想段階での内容具体化に向けたマネジメントについて 当社では住友商事グループのDX推進の加速を、というのは先程も触れた通りですが、実際に案件として相談されるステータスも様々異なります。こうした状況下から、実際に具体的な企画・構想に昇華すべく、現状把握は勿論の事、意思決定者等のキーマンや組織/関連ステークホルダーの整理、主要関係者を中心とした対象業務のヒアリング、ヒアリングを元にした課題設定や仮説となる有用な打ち手の提案を行います。提案内容についても内部の有識者や業界知見の高い専門家の支援も仰ぎつつ、プロジェクトマネジメント観点での実行可能性の精査や、具体的な計画立案、及び費用対効果を意識しながら有用な開発手法・陣容の整理を行います。また、単一の計画策定に留まらず、第二/第三案の立案等、不測の事態に備えつつ、最もフィージビリティが確保出来、実現速度もある程度担保可能な案にて関係者全体で合意形成を図る事を重視しながら行動します。こうした活動、及び関係者宛の説明や合意形成を経て、後述の実開発に進みます。 ②開発段階での開発マネジメントについて 実際の開発段階に進んだ案件については、基本的には事前合意済みの開発手法/陣容/計画に則って開発を推進する事になりますが、ここでは実証実験段階での開発と仮定して内容について説明致します。通常、製品として実際にサービス提供を行う場合は、機能要件、並びに非機能要件共に厳格に定義した上で開発推進すべきですが、実証実験段階ではそもそも利用者/シーンが限定的である事、また、開発対象の製品そのもののリッチさというよりかは、いち早く案件企画/構想段階で仮説定義した要件が業界/分野のゲームチェンジャーとなる有用な施策となり得るかどうかの検証を行いたいニーズも強い為、MVP開発としつつも、機能要件/非機能要件を何処まで精緻に今回開発スコープとするかについても十分に確認と協議が必要になる部分です。一方で開発手法の如何に関わらず、上記内容をコストコントロールも意識した上で、関係者に確認を行い、納期超過とならない様に開発計画に織り込み、品質についても有識者による成果物検証や第三者検証を交えながら推進を行う必要があるという意味では、実証実験段階と通常の製品開発マネジメントと異なる部分は無いです。 プロジェクトマネージャーに必要、且つ重要なスキル/マインドセットについて 最後に私がこれまで過ごした期間で感じた、プロジェクトマネージャーに求められるスキルセット/マインドセットについて説明致します。結論から申し上げると、以下の3点になります。 ①核となる普遍的な業務スキルを獲得/養う事 ②スタンスを取る事 ③当事者意識/主体性を持って業務遂行に臨む事 ここからは該当の各記載事項についての詳細を説明致します。 ①核となる普遍的な業務スキルを獲得/養う事 これまで記載した業務内容を分解すると、単純に列挙したとしても、 プロジェクトマネジメントスキル ITスキル 業界知見 問題解決能力 コミュニケーション能力 等々、幅広い業務スキルが必要となりますが、これらは一朝一夕で身に付けられる物ではなく、日々の業務遂行や泥臭い自己研鑽の繰り返しの積み上げで蓄積される物です。また、特にITスキルで言えば、日進月歩で技術革新が発生している昨今の状況を鑑みても、それらをキャッチアップするのは常にアンテナを張り続けるだけではなく、意欲的に学習を行う姿勢が重要となります。一方、それぞれで共通に言える事は、全て要諦と言える勘所、即ち原理原則に当たる部分が存在するという事です。例えば、プロジェクトマネジメントスキルで言えば、プロジェクトマネジメントにおける各知識領域下での要諦はそれぞれ一意(スケジュール管理で言えば、各タスク間の先行関係の可視化、及び適切な重要マイルストーンの設置も伴ったクリティカルチェーンの図示、不測事態発生時のリカバリ計画を考慮した計画策定等)に定まるものの、実際の管理手法といった方法論は多岐に亘る、といった具合です。最終的には全方位的なスキル獲得・習熟は必要になりますが、先ずは一つでも自分の中で武器となり、核となる業務スキルを身に付ける事、また、身に付ける内容については特定分野/ニッチな領域の知見といった細かな枝葉の部分ではなく、該当スキルの原理原則となり得る幹の領域を取得する事を推奨します。 ②スタンスを取る事 DX推進と一言で表現しても、その内容は一概には決まっていないものの、実際の案件内容は新規事業の創出や既存業務の抜本的な見直しといった、これまでの企業文化/活動を変革させる事が中心となるケースが多々有ります。それは言い換えれば、これまで誰も成し得た事がない、未知の領域に足を踏み込む事と同義になると共に、当然、明確な正解や道筋は存在しません。この「正解や道筋は存在しない」状況の中でも、その状況下で考え得る尤もらしい正解を仮説として、唱え続ける能力こそがスタンスを取るという事であり、非常に重要なポイントの一つです。正解が存在しない以上、何をどう主張したとしても時には反対意見や抵抗勢力と拮抗する場合も当然出てきます。しかし、見方を変えれば、この反対意見や抵抗勢力に対抗出来る対応案や対策を講じられれば、その仮説の確からしさはより確実な物になる上に、仮に当該見解の通りだったとしても、それはその見解を起点に新たにスタンスを取り直せば良い話とも言えます。大事なのは、スタンスを取り続ける事で目指すべき方向性を生み出し、軌道修正しながら、プロジェクトの正解を可視化して具現化していく事なのです。 ③当事者意識/主体性を持って業務遂行に臨む事 最後になりますが、これは純粋なマインドセットの話になるものの、ある意味では一番重要な要素と考えている事項になります。皆さんは自分自身の業務範囲/役割について、明確に回答可能でしょうか?若しくは、アサインされた案件や業務、タスクについて、何処までが自分自身の作業スコープかどうか正しく理解出来ていますでしょうか?改めて問いたい点は、回答可能でも理解出来ていた場合でも、何故自分自身の管轄外(と自身で定義した)の業務は未実施でも問題ないのか、という点です。勿論、これは、過剰な労働やワーカホリックとなる事を推奨する訳でもなければ、職責や役割の観点からあまりにも乖離した業務遂行を奨励する訳でも有りません(極端な例になりますが、入社早々で知見・経験が無い状態のまま特定会社への事業投資判断をする等)。伝えたいのは、自分自身で定義している管轄=壁は本当に壁なのかどうか、という事です。先程の「スタンスを取る事」で触れた通り、正解がない、未知の領域での業務遂行が基本となるDX推進において、方針転換が発生するケースは少なくはなく、突発的に発生するタスクや至急対応を求められる事案も散発します。こうした時に当事者意識/主体性を持ってリード出来る人間と、管轄を厳格に定義し、管轄外業務では遂行を断念する/リードを他者に依存する人間とでは、達成した結果が同じであったとしても、培われる経験値や知見の蓄積数、または得られる信頼感の多寡は異なるケースが多いと感じます。もし、可能であれば明日からでも、且つどんなに小さな事でも構いませんので、自分で定義した管轄を、壁を破ってみて、自分事として業務遂行の範囲を広げてみてはどうでしょうか?上記、自分自身の更なる成長という観点を除いたとしても、きっと素敵な結果が待っていると思います。 終わりに 如何でしたでしょうか?今回はInsight Edgeでのプロジェクトマネージャーの具体的な業務内容の紹介と私が大切にする具体的なスキル・マインドについて説明させて頂きました。Insight Edgeでは今回ご紹介したプロジェクトマネージャーの採用も意欲的に行なっており、今回の記事を通じて私達と是非とも働いてみたいと感じる方がいらっしゃればご応募の上、いつか一緒に私達の最大の目標である住友商事グループの更なるDXの加速を実現出来れば幸いです!
目次 導入 PyMCとは PyMCの最近の動向 コードリーディングの方針とスコープ メインコンテンツ Modelクラスとインスタンス化 with文 メタクラス 実装の確認 確率変数と分布クラスの管理 分布クラスの構造 ベータ分布クラス 分布クラス 観測された確率変数 サンプリング サンプリング手法選定 並列サンプリング NUTS実装 まとめ 導入 こんにちは。InsightEdgeのデータサイエンティストの小柳です。 本記事ではデータ分析の強い味方、MCMCサンプラーの実装を見てみようと思います。 今回取り上げるのは私が普段使っているPyMCというPython用のモジュールです。 実装を読む動機はいくつかあります。教科書で読むHMCの方法は美しいですが、自分で実装しろと言われるとどうすればよいかすぐにはわかりません。そのような不思議なものがどうやって実際に作られているのかはとても気になります。他にも、PyMCに限りませんがサンプリング前にエラーが起きたときに対処ができるようになりたいとか、わずか数行でサンプリングが動く便利さはどうやって実装されているのか解き明かしたいとか、 特殊な使い方をするときにはどこをどう触ればよいのか知りたいとか、ご利益はたくさんあります。また、PyMCはあまり日本語の文献が無いのでこの界隈を盛り上げたい、そんな動機もあります。 PyMCとは おそらくこの記事を読むような方には不要かと思いますが、軽くPyMCについての紹介をします。 PyMCとは簡単に言えばPython向けのベイズ推定用のサンプリングモジュールです。確率的プログラミング言語(PPL)とも呼ばれます。Python向けに限らず同等なものはいくつか存在しています。 Pyro, NumPyro, TensorFlowProbability, Stan(PyStan), WinBUGS, JAGSあたりでしょう。どれもMCMCサンプリングと変分ベイズ(変分推論、ADVI)、プラスアルファで確率的最適化ができるはずです。 PyMCの特徴は開発が盛んなこと、そして使用者が多いことです。以下はGoogle Trendsで調べた上記のPython向けPPLの人気です。 また、国別でいうと、日本ではPyStanがトップ、PyMCとNumPyroが同程度で2位という感じですが、その他のほぼ全ての国ではPyMCが主流といった趣です。 PyMCの最近の動向 2017年にver3系がリリースされたあたりからPyMCはPython向けのPPLの中でもメジャーなものであり続けています。ですが、近年(2022年あたり)の動向をキャッチアップしているものは少ない印象です。PyMCに関することをGoogle検索するとモジュールインポートの際に import pymc3 と書いている記事が多いのですが、これはそのver3系を使ったものです。最新のものをインポートする際にはpymcでOKです。 このver3のときまで、PyMCはTheanoというモジュールをバックエンドとして使っていましたが、Theanoの開発が2018年で終わってしまったようで[1]別のモジュールを使う方向に進んできました。当初はTensorFlowProbabilityを代替として開発を進めていましたが、その方針も結局2020に破棄[2]。ついにTheanoの後継としてAesaraをそしてそのさらに後ろでJAX等を使う方向になったようです。 紆余曲折ありましたが、ついに、2022年にver4.0がリリースされました[3]。その当時に書かれたイラストが以下です。 ですがver4.0はとても短命でした。開発目標の違いなどから、今はAesaraからフォークしたPyTensorを使う仕様になるに伴いver5系となり、現在も開発が進められています。 コードリーディングの方針とスコープ 今回はpymc v5.1.0を読んでいこうと思います。 また、読む範囲としてはPyMCが担当している範囲とします。従って、PyTensorやJAXが担当している範囲はスコープ外です。次回以降でやるかもしれませんが… 全てを理解しようとすると膨大になってしまうので、関係が薄い部分は大胆に削っていくことにします。 また、私のバックグラウンドはPythonを使ったデータ分析はできるけれどモジュール開発等まではできない人間です。なのでデータ分析では使わないようなPythonの使い方に重点を置いて読んでいくことにします。 方針としては、以下のような単純なモデルの挙動を追っていくことにします。 import pymc as pm trials = 10; successes = 5 with pm.Model() as coin_flip_model: p = pm.Beta("p", alpha=1, beta=1) obs = pm.Binomial("obs", p=p, n=trials, observed=successes, ) idata = pm.sample() 二項分布の事前分布にベータ分布をおいたモデルになります。非常に単純です。共役事前分布なんだからMCMCせず手計算で瞬殺だろと言いたくなるようなモデルですね。 変数名から推測するに10回投げたら5回表がでたようなコインの表がでる確率の事後分布を求めるような問題です。 PyMCのWebサイトのInteractive Demoから引用しています。 メインコンテンツ Modelクラスとインスタンス化 まずはmodel.py内のModelクラスの挙動から確認していきましょう。 with文 モデル作成時にwith文が使われています。with文の詳細はPythonの公式ドキュメント[5]に譲るのですが、端的に言えばModelクラスの__enter__()メソッドがwith文の冒頭で実行され、同__exit__()メソッドがwith文の最後に実行されます。従って、まずはModelクラスの__new__(), __init__(), __enter__(), __exit__()メソッドを確認していきます。が、その前に分かりづらいのがModelクラス定義時に指定されているmetaclassです。 メタクラス そもそも論になりますが、Pythonのクラス定義の際には class ExampleClass(ParentClass): A = 1 ... としますが、これはどのような処理がなされているのでしょうか? 実はこのとき行われているのは以下のコードと等価です。 X = type('ExampleClass', (ParentClass), {'A':1}) このようにしてtypeクラスのインスタンスを作成することになるのですが、メタクラスを指定するとtypeの代わりにメタクラスのインスタンスを作成することになります。当然、メタクラスの__new__(),__init__()が実行されることになります。 今回のModelクラスのメタクラスであるContextMetaクラスは__new__()でModelクラスの__enter__()と__exit__()を作成しています。以下が実際のContextMetaクラスの__new__(),__init__()です。 以後コードを貼るときは本質的に理解に不要なコメントやエラーハンドリング等は省略します。 model.py/ContextMeta class ContextMeta(type): """省略""" def __new__(cls, name, bases, dct, **kwargs): # pylint: disable=unused-argument """Add __enter__ and __exit__ methods to the class.""" def __enter__(self): self.__class__.context_class.get_contexts().append(self) # self._pytensor_config is set in Model.__new__ self._config_context = None if hasattr(self, "_pytensor_config"): self._config_context = pytensor.config.change_flags(**self._pytensor_config) self._config_context.__enter__() return self def __exit__(self, typ, value, traceback): # pylint: disable=unused-argument self.__class__.context_class.get_contexts().pop() # self._pytensor_config is set in Model.__new__ if self._config_context: self._config_context.__exit__(typ, value, traceback) dct[__enter__.__name__] = __enter__ dct[__exit__.__name__] = __exit__ def __init__( cls, name, bases, nmspc, context_class: Optional[Type] = None, **kwargs ): # pylint: disable=unused-argument """Add ``__enter__`` and ``__exit__`` methods to the new class automatically.""" if context_class is not None: cls._context_class = context_class super().__init__(name, bases, nmspc) そして、以下がModelクラスの__init__()までです。 model.py/Model class Model(WithMemoization, metaclass=ContextMeta): if TYPE_CHECKING: def __enter__(self: "Model") -> "Model": ... def __exit__(self: "Model", *exc: Any) -> bool: ... def __new__(cls, *args, **kwargs): # resolves the parent instance instance = super().__new__(cls) if kwargs.get("model") is not None: instance._parent = kwargs.get("model") else: instance._parent = cls.get_context(error_if_none=False) instance._pytensor_config = kwargs.get("pytensor_config", {}) return instance """省略""" def __init__( self, name="", coords=None, check_bounds=True, *, pytensor_config=None, model=None, ): del pytensor_config, model # used in __new__ self.name = self._validate_name(name) self.check_bounds = check_bounds if self.parent is not None: self.named_vars = treedict(parent=self.parent.named_vars) """省略""" else: self.named_vars = treedict() """省略""" self.add_coords(coords) from pymc.printing import str_for_model self.str_repr = types.MethodType(str_for_model, self) self._repr_latex_ = types.MethodType( functools.partial(str_for_model, formatting="latex"), self ) 実装の確認 これを踏まえてwith文のところまでの挙動を見てみましょう。 まずはModelクラスの定義時にContextMeta.__new__()が実行され、Model.__enter__()とModel.__exit__()が定義されます。次にContextMeta.__init__()が実行されます。 with文実行時にModelクラスのインスタンスが作成されます。この際にはあまり変なことは起きません。 その次に今作ったインスタンスの__enter__()メソッドが実行されます。ここでのポイントが同メソッドで行われる self.__class__.context_class.get_contexts().append(self) です。ここでModelクラスの「今対象にしている同クラスのリスト」の末尾に今作ったモデルインスタンスが追加されます。 こうなっているため後々with文内でModelインスタンスを意識することなくモデルを組み立てられるようになっています。 確率変数と分布クラスの管理 分布クラスの構造 次に p = pm.Beta("p", alpha=1, beta=1) を見ていきましょう。二項分布のパラメータの事前分布としてベータ分布を与えるところです。 プログラム的にはベータ分布クラスのインスタンスを作っているので、どのようなことが行われているかをみていきます。 ベータ分布クラス まずベータ分布に至るまでの継承とメタクラスの関係を見ていくと、 ベータ分布クラス→台が[0,1]の連続分布クラス→連続分布クラス→分布クラス→(メタクラス)分布メタクラス という関係になっています((子)→(親)の関係性)。 それらのうち、内容があるのはベータ分布クラス、分布クラス、分布メタクラスなので前2つを見ていきます。分布メタクラスの内容は省略します。 ベータ分布のコードが以下です。 distributions/continuous.py/Beta class Beta(UnitContinuous): """省略""" rv_op = pytensor.tensor.random.beta @classmethod def dist(cls, alpha=None, beta=None, mu=None, sigma=None, nu=None, *args, **kwargs): alpha, beta = cls.get_alpha_beta(alpha, beta, mu, sigma, nu) alpha = at.as_tensor_variable(floatX(alpha)) beta = at.as_tensor_variable(floatX(beta)) return super().dist([alpha, beta], **kwargs) def moment(rv, size, alpha, beta): """省略""" return mean @classmethod def get_alpha_beta(self, alpha=None, beta=None, mu=None, sigma=None, nu=None): """省略""" return alpha, beta def logp(value, alpha, beta): """省略""" return check_parameters( res, alpha > 0, beta > 0, msg="alpha > 0, beta > 0", ) def logcdf(value, alpha, beta): """省略""" return check_parameters( logcdf, alpha > 0, beta > 0, msg="alpha > 0, beta > 0", ) 平均、パラメータ、対数密度、累積分布関数の対数値を得るためのメソッドが定義されています。__new__()は後で扱う分布クラスのものを使います。 dist()メソッドも後で扱いますが、これは__new__()実行時に呼び出されます。ベータ分布に従うPyTensorの確率変数クラスにパラメータを与えてインスタンス(rv_out)を得るためのメソッドです。ということで次に分布クラスを見ていきます。 分布クラス 分布クラスには個別の分布のインスタンスを生成するときの__new__()メソッドと、個別の分布クラスのdist()メソッドを内部で呼び出すdist()メソッドが実装されています。__new__()メソッドがdist()メソッドを呼び出すことで確率変数インスタンスrv_outを作り、その後Model.register_rv()メソッドで先程作ったModelインスタンスに登録します。このとき確率変数に付けた名前や対数尤度を計算する時の変形等も登録します。 また、作った確率変数が観測値を持つか否かで処理を変えます。今回は直接観測される値では無いので先程のModelインスタンスのfree_RVsに登録されます。 分布クラスを見て実際にそうなっていることを確認しましょう。 distributions/distributions.py/Distribution class Distribution(metaclass=DistributionMeta): rv_op: [RandomVariable, SymbolicRandomVariable] = None rv_type: MetaType = None def __new__( cls, name: str, *args, rng=None, dims: Optional[Dims] = None, initval=None, observed=None, total_size=None, transform=UNSET, **kwargs, ) -> TensorVariable: try: from pymc.model import Model model = Model.get_context() except TypeError: """省略""" """省略""" rv_out = cls.dist(*args, **kwargs) rv_out = model.register_rv( rv_out, name, observed, total_size, dims=dims, transform=transform, initval=initval, ) # add in pretty-printing support rv_out.str_repr = types.MethodType(str_for_dist, rv_out) rv_out._repr_latex_ = types.MethodType( functools.partial(str_for_dist, formatting="latex"), rv_out ) rv_out.logp = _make_nice_attr_error("rv.logp(x)", "pm.logp(rv, x)") rv_out.logcdf = _make_nice_attr_error("rv.logcdf(x)", "pm.logcdf(rv, x)") rv_out.random = _make_nice_attr_error("rv.random()", "pm.draw(rv)") return rv_out @classmethod def dist( cls, dist_params, *, shape: Optional[Shape] = None, **kwargs, ) -> TensorVariable: """省略""" rv_out = cls.rv_op(*dist_params, size=create_size, **kwargs) rv_out.logp = _make_nice_attr_error("rv.logp(x)", "pm.logp(rv, x)") rv_out.logcdf = _make_nice_attr_error("rv.logcdf(x)", "pm.logcdf(rv, x)") rv_out.random = _make_nice_attr_error("rv.random()", "pm.draw(rv)") _add_future_warning_tag(rv_out) return rv_out 確かに、PyTensorの確率分布クラスにパラメータを与えてインスタンスを作りそれをモデルに登録する、というプロセスはこのような方式であればどの確率変数でも同じなのでこのような構成になるのは納得できます。 では次にモデルクラスのregister_rv()メソッドをみてみましょう、といってもモデルにいろいろ登録するだけですが。 model.py/Model class Model(WithMemoization, metaclass=ContextMeta): """省略""" def register_rv( self, rv_var, name, observed=None, total_size=None, dims=None, transform=UNSET, initval=None ): name = self.name_for(name) rv_var.name = name _add_future_warning_tag(rv_var) """省略""" if observed is None: if total_size is not None: raise ValueError("total_size can only be passed to observed RVs") self.free_RVs.append(rv_var) self.create_value_var(rv_var, transform) self.add_named_variable(rv_var, dims) self.set_initval(rv_var, initval) else: """省略""" rv_var = self.make_obs_var(rv_var, observed, dims, transform, total_size) return rv_var 観測された確率変数 次に追うコードは以下です。 obs = pm.Binomial("obs", p=p, n=trials, observed=successes, ) 先程事前分布にベータ分布をおいたパラメータを使った二項分布です。先程と同様に二項分布に至るまでの継承とメタクラスを見ていくと、 二項分布クラス→離散分布クラス→分布クラス→(メタクラス)分布メタクラス となっていて、構成はもちろんほぼ同じですし実行されることも概ね同じです。異なるところはインスタンス生成時のModel.register_rv()メソッド内の挙動です。引数observedに観測データを渡したことで変化し、Model.make_obs_var()メソッドが実行されます。さらにその中でModel.create_value_var()が実行され、モデルに登録されます。 Model.make_obs_var()メソッドを見てみましょう。 model.py/Model class Model(WithMemoization, metaclass=ContextMeta): """省略""" def make_obs_var( self, rv_var: TensorVariable, data: np.ndarray, dims, transform: Union[Any, None], total_size: Union[int, None], ) -> TensorVariable: name = rv_var.name data = convert_observed_data(data).astype(rv_var.dtype) if data.ndim != rv_var.ndim: raise ShapeError( "Dimensionality of data and RV don't match.", actual=data.ndim, expected=rv_var.ndim ) if pytensor.config.compute_test_value != "off": """省略""" mask = getattr(data, "mask", None) if mask is not None: """省略""" else: if sps.issparse(data): data = sparse.basic.as_sparse(data, name=name) else: data = at.as_tensor_variable(data, name=name) if total_size: from pymc.variational.minibatch_rv import create_minibatch_rv rv_var = create_minibatch_rv(rv_var, total_size) rv_var.name = name rv_var.tag.observations = data self.create_value_var(rv_var, transform=None, value_var=data) self.add_named_variable(rv_var, dims) self.observed_RVs.append(rv_var) return rv_var make_obs_var()の最後の処理は、observedがNoneのときのModel.register_rv()の最後の処理とほとんど同じです。極論すると異なる部分はrv_var.tag.observations = dataを与えているかどうかのようです。 サンプリング 最後の一行、サンプリングの部分です。 idata = pm.sample() サンプリング概観 最近のデータ分析環境であればまず間違いなくマルチコアCPUが使えるので、自動的に並列サンプリングが実行されます。 今回のコードを実行するとpymcで実装されたNUTSサンプリングが並列で走るので、そのケースがたどる部分を見ていこうと思います。 まずはsample()関数を見てみましょう。 sampling/mcmc.py/sample def sample( draws: int = 1000, *, tune: int = 1000, chains: Optional[int] = None, cores: Optional[int] = None, random_seed: RandomState = None, progressbar: bool = True, step=None, nuts_sampler: str = "pymc", initvals: Optional[Union[StartDict, Sequence[Optional[StartDict]]]] = None, init: str = "auto", jitter_max_retries: int = 10, n_init: int = 200_000, trace: Optional[TraceOrBackend] = None, discard_tuned_samples: bool = True, compute_convergence_checks: bool = True, keep_warning_stat: bool = False, return_inferencedata: bool = True, idata_kwargs: Optional[Dict[str, Any]] = None, callback=None, mp_ctx=None, model: Optional[Model] = None, **kwargs, ) -> Union[InferenceData, MultiTrace]: """省略""" model = modelcontext(model) if not model.free_RVs: """省略""" if cores is None: cores = min(4, _cpu_count()) if chains is None: chains = max(2, cores) """省略""" step = assign_step_methods(model, step, methods=pm.STEP_METHODS, step_kwargs=kwargs) if nuts_sampler != "pymc": """省略""" # Create trace backends for each chain run, traces = init_traces( backend=trace, chains=chains, expected_length=draws + tune, step=step, initial_point=ip, model=model, ) sample_args = { "draws": draws + tune, # FIXME: Why is tune added to draws? "step": step, "start": initial_points, "traces": traces, "chains": chains, "tune": tune, "progressbar": progressbar, "model": model, "cores": cores, "callback": callback, "discard_tuned_samples": discard_tuned_samples, } parallel_args = { "mp_ctx": mp_ctx, } sample_args.update(kwargs) """省略""" if parallel: _log.info(f"Multiprocess sampling ({chains} chains in {cores} jobs)") _print_step_hierarchy(step) try: _mp_sample(**sample_args, **parallel_args) except pickle.PickleError: """省略""" if not parallel: """省略""" return _sample_return( run=run, traces=traces, tune=tune, t_sampling=t_sampling, discard_tuned_samples=discard_tuned_samples, compute_convergence_checks=compute_convergence_checks, return_inferencedata=return_inferencedata, keep_warning_stat=keep_warning_stat, idata_kwargs=idata_kwargs or {}, model=model, ) コードからわかるように、ここではassign_step_methods()メソッドを使ってstepすなわちサンプリング手法を決定し、そのあと_mp_sample()に引数を入れて実行しています。実はassign_step_methods()はサンプリング手法を決定するだけの関数ではなく、各サンプリング手法クラスのインスタンスを作って返します。 ということで次はassign_step_methods()の中身を見ていきましょう。 サンプリング手法選定 この関数では、パラメータごとに各サンプリング手法(現在候補はNUTS, HMC, Metropolis,BinaryMetropolis, BinaryGibbsMetropolis, Slice, CategoricalGibbsMetropolis)から最適なものを選びます。各サンプリング手法はクラスとして存在しており、どの手法も.competence()という相性を測るメソッドが実装されています。このメソッドは0-3の点数を返し、大きい方がより良いとされています。NUTSを例に取ると、変数が対数尤度に対して勾配を持つ場合2点を返すようになっています。現状3点を返す手法はかなり限られているので、通常連続変数で勾配があればNUTSが選ばれるようです。 余談ですが3点を返すケースはとても少ないようです。例えば、ベルヌーイ分布に従うような確率変数に対しては、BinaryGibbsMetropolisサンプリングが理想的と判定されます。この辺なにか研究があるのでしょうか?気になるところではあります。 最後にinstantiate_steppersを実行してその返り値を返します。関数名の通り各サンプリング手法クラスのインスタンスを作り、それらを二個目の引数のリスト(steps)に足したものを返します。 sampling/mcmc.py/assign_step_methods def assign_step_methods(model, step=None, methods=None, step_kwargs=None): steps = [] assigned_vars = set() if methods is None: methods = pm.STEP_METHODS if step is not None: """省略""" # Use competence classmethods to select step methods for remaining # variables selected_steps = defaultdict(list) model_logp = model.logp() for var in model.value_vars: if var not in assigned_vars: # determine if a gradient can be computed has_gradient = var.dtype not in discrete_types if has_gradient: try: tg.grad(model_logp, var) except (NotImplementedError, tg.NullTypeGradError): has_gradient = False # select the best method rv_var = model.values_to_rvs[var] selected = max( methods, key=lambda method, var=rv_var, has_gradient=has_gradient: method._competence( var, has_gradient ), ) selected_steps[selected].append(var) return instantiate_steppers(model, steps, selected_steps, step_kwargs) 並列サンプリング 次にサンプリングの本丸、_mp_sample()メソッドをみていきます。 やっていることはParallelSamplerインスタンスを作ったあとにそこからイテレーション結果を引き出してtraceに格納しているだけです。 sampling/mcmc.py/_mp_sample def _mp_sample( *, draws: int, tune: int, step, chains: int, cores: int, random_seed: Sequence[RandomSeed], start: Sequence[PointType], progressbar: bool = True, traces: Sequence[IBaseTrace], model: Optional[Model] = None, callback: Optional[SamplingIteratorCallback] = None, mp_ctx=None, **kwargs, ) -> None: """省略""" import pymc.sampling.parallel as ps # We did draws += tune in pm.sample draws -= tune sampler = ps.ParallelSampler( draws=draws, tune=tune, chains=chains, cores=cores, seeds=random_seed, start_points=start, step_method=step, progressbar=progressbar, mp_ctx=mp_ctx, ) try: try: with sampler: for draw in sampler: strace = traces[draw.chain] strace.record(draw.point, draw.stats) log_warning_stats(draw.stats) if draw.is_last: strace.close() if callback is not None: callback(trace=strace, draw=draw) except ps.ParallelSamplingError as error: """省略""" except KeyboardInterrupt: pass finally: for strace in traces: strace.close() ここ自体はそんなに難しくありませんね。 では次にParallelSampelerクラスのインスタンス作成からイテレーション実行までを見ていきます。 初期化メソッドでまず並列処理の開始方式を決めています。mp_ctx関連の部分です。OSが変わってもきちんと動くようにうまくやっているという認識で大丈夫です。 次にチェーン数だけProcessAdapterインスタンスとそれに伴う子プロセスを作りリスト化した後、それら全てをself._inactiveというまだ動いていないプロセスに登録します。 イテレーション実行時には、ParallelSamplerの__iter__()メソッドが呼び出されます。__iter__()メソッドが呼び出されたときの一般的な挙動を詳細に述べると長くなるのでざっくりした説明が次のようになります。 for i in hoge: を実行するとhoge.__iter__()が実行され、最初のyieldのところまで実行して止まり、iにyeild以後を代入して返します。そしてループが回ってiの値を更新する際には、先程のyieldのところから再開し、次のyieldのところまで実行しiにそれを代入して返す、という挙動になります。 今回のコードの場合だと、最初にParallelSamplerの._make_active()メソッドを実行して各プロセスに対しproc.start()メソッドとproc.write_next()メソッドを実行します。proc.start()メソッドで'start'メッセージを受け取った子プロセス達は各々サンプリングを始め、'write_next'メッセージ=パイプに結果を書き込んで良いよ、のメッセージを受取るまで結果を保持して待ちます。親プロセス側でProcessAdapter.recv_draw()が実行されると、最も早く結果が用意できたプロセスのパイプからサンプリング結果を取得します。その後proc.write_next()メソッドを呼ぶことで、結果を返した子プロセスに対し次のステップのサンプリング結果を計算して良いぞとパイプ越しに命令することになります。 sampling/parallel.py/ParallelSampler class ParallelSampler: def __init__( self, *, draws: int, tune: int, chains: int, cores: int, seeds: Sequence["RandomSeed"], start_points: Sequence[Dict[str, np.ndarray]], step_method, progressbar: bool = True, mp_ctx=None, ): """ 省略""" if mp_ctx is None or isinstance(mp_ctx, str): """ 省略""" mp_ctx = multiprocessing.get_context(mp_ctx) step_method_pickled = None if mp_ctx.get_start_method() != "fork": step_method_pickled = cloudpickle.dumps(step_method, protocol=-1) self._samplers = [ ProcessAdapter( draws, tune, step_method, step_method_pickled, chain, seed, start, mp_ctx, ) for chain, seed, start in zip(range(chains), seeds, start_points) ] self._inactive = self._samplers.copy() self._finished: List[ProcessAdapter] = [] self._active: List[ProcessAdapter] = [] self._max_active = cores """ 省略""" def _make_active(self): while self._inactive and len(self._active) < self._max_active: proc = self._inactive.pop(0) proc.start() proc.write_next() self._active.append(proc) def __iter__(self): if not self._in_context: raise ValueError("Use ParallelSampler as context manager.") self._make_active() if self._active and self._progress: self._progress.update(self._total_draws) while self._active: draw = ProcessAdapter.recv_draw(self._active) proc, is_last, draw, tuning, stats = draw self._total_draws += 1 """ 省略""" # Already called for new proc in _make_active if not is_last: proc.write_next() yield Draw(proc.chain, is_last, draw, tuning, stats, point) """ 省略""" 実際に親子間でやりとりするコードを見てみましょう。主にサンプリングのところだけを抜き出しています。終了処理も省略しています。 親プロセス側で、子プロセスと直接やりとりをするのが以下のProcessAdapterクラスです。 sampling/paralell.py/ProcessAdapter class ProcessAdapter: """Control a Chain process from the main thread.""" def __init__( self, draws: int, tune: int, step_method, step_method_pickled, chain: int, seed, start: Dict[str, np.ndarray], mp_ctx, ): self.chain = chain process_name = "worker_chain_%s" % chain self._msg_pipe, remote_conn = multiprocessing.Pipe() self._shared_point = {} self._point = {} for name, shape, dtype in DictToArrayBijection.map(start).point_map_info: """ 省略""" self._readable = True self._num_samples = 0 if step_method_pickled is not None: step_method_send = step_method_pickled else: if mp_ctx.get_start_method() == "spawn": raise ValueError( "please provide a pre-pickled step method when multiprocessing start method is 'spawn'" ) step_method_send = step_method self._process = mp_ctx.Process( daemon=True, name=process_name, target=_run_process, args=( process_name, remote_conn, step_method_send, step_method_pickled is not None, self._shared_point, draws, tune, seed, ), ) self._process.start() # Close the remote pipe, so that we get notified if the other # end is closed. remote_conn.close() """省略""" def _send(self, msg, *args): try: self._msg_pipe.send((msg, *args)) except Exception: # try to receive an error message message = None try: message = self._msg_pipe.recv() except Exception: pass if message is not None and message[0] == "error": old_error = message[1] if old_error is not None: error = ParallelSamplingError( f"Chain {self.chain} failed with: {old_error}", self.chain ) else: error = RuntimeError(f"Chain {self.chain} failed.") raise error from old_error raise def start(self): self._send("start") def write_next(self): self._readable = False self._send("write_next") """ 省略""" @staticmethod def recv_draw(processes, timeout=3600): if not processes: raise ValueError("No processes.") pipes = [proc._msg_pipe for proc in processes] ready = multiprocessing.connection.wait(pipes) if not ready: raise multiprocessing.TimeoutError("No message from samplers.") idxs = {id(proc._msg_pipe): proc for proc in processes} proc = idxs[id(ready[0])] msg = ready[0].recv() if msg[0] == "error": """ 省略""" elif msg[0] == "writing_done": proc._readable = True proc._num_samples += 1 return (proc,) + msg[1:] else: raise ValueError("Sampler sent bad message.") """ 省略""" def _run_process(*args): _Process(*args).run() 一方で、子プロセスとして動かすのが以下の Processクラスです。 self. recv_msg()でパイプからメッセージを受け取る際には何か受け取れるまで待ち続けます。 これらを見ると大まかな挙動がわかるのではないかと思います。 sampling/parallel.py/_Process class _Process: def __init__( self, name: str, msg_pipe, step_method, step_method_is_pickled, shared_point, draws: int, tune: int, seed, ): self._msg_pipe = msg_pipe self._step_method = step_method self._step_method_is_pickled = step_method_is_pickled self._shared_point = shared_point self._seed = seed self._at_seed = seed + 1 self._draws = draws self._tune = tune def _unpickle_step_method(self): """ 省略""" def run(self): try: self._unpickle_step_method() self._point = self._make_numpy_refs() self._start_loop() except KeyboardInterrupt: pass except BaseException as e: e = ExceptionWithTraceback(e, e.__traceback__) self._msg_pipe.send(("error", e)) self._wait_for_abortion() finally: self._msg_pipe.close() """ 省略""" def _recv_msg(self): return self._msg_pipe.recv() def _start_loop(self): np.random.seed(self._seed) draw = 0 tuning = True msg = self._recv_msg() if msg[0] == "abort": raise KeyboardInterrupt() if msg[0] != "start": raise ValueError("Unexpected msg " + msg[0]) while True: if draw == self._tune: self._step_method.stop_tuning() tuning = False if draw < self._draws + self._tune: try: point, stats = self._step_method.step(self._point) except SamplingError as e: e = ExceptionWithTraceback(e, e.__traceback__) self._msg_pipe.send(("error", e)) else: return msg = self._recv_msg() if msg[0] == "abort": raise KeyboardInterrupt() elif msg[0] == "write_next": self._write_point(point) is_last = draw + 1 == self._draws + self._tune self._msg_pipe.send(("writing_done", is_last, draw, tuning, stats)) draw += 1 else: raise ValueError("Unknown message " + msg[0]) NUTS実装 これまでで概ね実装は追い終わり、PyMCが何を担当しているかがわかってきました。MCMCサンプリングを通じて、PyMCは確率変数とそれらの関係性をモデルとして管理することと、並列サンプリングをしていました。逆に、確率分布そのもの実装や対数尤度の計算はPyTensorにまかせていることもわかってきました。 若干オプショナルになりますが、PyMCによるNUTSの実装も読んでみることにします。オプショナルなのは単純な理由で、PyMCで実装されているNUTSはJAXを使ったNUTSよりも遅いため使う必要性があまり無いからです。実際、公式も最新バージョン(5.1.2)ではsample()関数の引数からNUTSサンプラーにnutpieを選ぶのが最速とアナウンスしています。ですが、今回のコードだとサンプラーを指定していないのでPyMCのものが動くのと、そしてNUTSはどう実装されているのかを確認したいという個人的欲求から見ていくことにします。NUTSやHMCサンプリングについては[6]で確認してください。 これから見ていくところは、 Processクラスの start_loop()メソッド内にある point, stats = self. step_method.step(self. point) を行ったときに呼び出される各サンプリングクラスの.step()とそれに関連する部分です。 まずはNUTSクラスの継承関係を見ていくと、 NUTS→BaseHMC→GradientSharedStep→ArrayStepShared→BlockedStep という関係になっています。 NUTS.step()を実行したときに実際に実行されるのはArrayStepSharedから継承した.step()メソッドですが、さらに詳細に見るとBaseHMC.astep()メソッドが呼ばれています。 その中では初速の生成、対数尤度からのエネルギーの計算、1ステップ分のサンプリング、(ステップサイズを自動更新するなら)ステップサイズの更新、発散状況の警告作成等を行います。統計に近いのでやっていることがわかりやすいです。 コードは以下のようになっています。 step_methods/hmc/base_hmc.py/BaseHMC class BaseHMC(GradientSharedStep): """省略""" def astep(self, q0: RaveledVars) -> tuple[RaveledVars, StatsType]: """Perform a single HMC iteration.""" perf_start = time.perf_counter() process_start = time.process_time() p0 = self.potential.random() p0 = RaveledVars(p0, q0.point_map_info) start = self.integrator.compute_state(q0, p0) warning: SamplerWarning | None = None if not np.isfinite(start.energy): """省略""" raise SamplingError(f"Bad initial energy: {warning}") adapt_step = self.tune and self.adapt_step_size step_size = self.step_adapt.current(adapt_step) self.step_size = step_size if self._step_rand is not None: step_size = self._step_rand(step_size) hmc_step = self._hamiltonian_step(start, p0.data, step_size) perf_end = time.perf_counter() process_end = time.process_time() self.step_adapt.update(hmc_step.accept_stat, adapt_step) self.potential.update(hmc_step.end.q, hmc_step.end.q_grad, self.tune) if hmc_step.divergence_info: info = hmc_step.divergence_info point = None point_dest = None info_store = None if self.tune: kind = WarningType.TUNING_DIVERGENCE else: kind = WarningType.DIVERGENCE self._num_divs_sample += 1 # We don't want to fill up all memory with divergence info if self._num_divs_sample < 100 and info.state is not None: point = DictToArrayBijection.rmap(info.state.q) if self._num_divs_sample < 100 and info.state_div is not None: point_dest = DictToArrayBijection.rmap(info.state_div.q) if self._num_divs_sample < 100: info_store = info warning = SamplerWarning( kind, info.message, "debug", self.iter_count, info.exec_info, divergence_point_source=point, divergence_point_dest=point_dest, divergence_info=info_store, ) self.iter_count += 1 stats: dict[str, Any] = { "tune": self.tune, "diverging": bool(hmc_step.divergence_info), "perf_counter_diff": perf_end - perf_start, "process_time_diff": process_end - process_start, "perf_counter_start": perf_start, "warning": warning, } stats.update(hmc_step.stats) stats.update(self.step_adapt.stats()) stats.update(self.potential.stats()) return hmc_step.end.q, [stats] 1ステップ分のサンプリングだけはNUTS特有ですが、ほかの部分はHMC系?のサンプリングで共通なのでこのようにbaseHMCクラスのような実装になっています。 NUTSのサンプリングと言えば、前後のどちらかに進むかをランダムに決めて1ステップそちらに時間積分で進み、また進む方向を決めて2ステップ進み、次は4ステップ…を繰り返しそれを木としてくっつけていく手法です。1サイクルごとに積分した軌跡がパラメータ空間中でUターンしていないことを確認していくことが名前の由来です。こちらの実装も見てみましょう。論文中の擬似コードをそのまま書き写したといった感じでこちらもわかりやすくなっています。 step_methods/hmc/nuts.py NUTS class NUTS(BaseHMC): """省略""" name = "nuts" default_blocked = True stats_dtypes_shapes = { "depth": (np.int64, []), """省略""" } def __init__(self, vars=None, max_treedepth=10, early_max_treedepth=8, **kwargs): """省略""" super().__init__(vars, **kwargs) self.max_treedepth = max_treedepth self.early_max_treedepth = early_max_treedepth self._reached_max_treedepth = 0 def _hamiltonian_step(self, start, p0, step_size): if self.tune and self.iter_count < 200: max_treedepth = self.early_max_treedepth else: max_treedepth = self.max_treedepth tree = _Tree(len(p0), self.integrator, start, step_size, self.Emax) reached_max_treedepth = False for _ in range(max_treedepth): direction = logbern(np.log(0.5)) * 2 - 1 divergence_info, turning = tree.extend(direction) if divergence_info or turning: break else: reached_max_treedepth = not self.tune stats = tree.stats() accept_stat = stats["mean_tree_accept"] stats["reached_max_treedepth"] = reached_max_treedepth return HMCStepData(tree.proposal, accept_stat, divergence_info, stats) そして以下がサンプリング経路を保持する Treeクラスです。 build_subtree()メソッドは一回実行されるごとに二回再帰的に実行されるため、本体のツリーに追加されるサブツリーがステップごとに倍々になっていきます。 step_methods/hmc/nuts.py _Tree class _Tree: def __init__( self, ndim: int, integrator: integration.CpuLeapfrogIntegrator, start: State, step_size: float, Emax: float, ): """省略""" def extend(self, direction): """省略""" if direction > 0: tree, diverging, turning = self._build_subtree( self.right, self.depth, floatX(np.asarray(self.step_size)) ) leftmost_begin, leftmost_end = self.left, self.right rightmost_begin, rightmost_end = tree.left, tree.right leftmost_p_sum = self.p_sum.copy() rightmost_p_sum = tree.p_sum self.right = tree.right else: """省略""" self.depth += 1 if diverging or turning: return diverging, turning size1, size2 = self.log_size, tree.log_size if logbern(size2 - size1): self.proposal = tree.proposal self.log_size = np.logaddexp(self.log_size, tree.log_size) self.p_sum[:] += tree.p_sum # Additional turning check only when tree depth > 0 to avoid redundant work if self.depth > 0: left, right = self.left, self.right p_sum = self.p_sum turning = (p_sum.dot(left.v) <= 0) or (p_sum.dot(right.v) <= 0) p_sum1 = leftmost_p_sum + rightmost_begin.p.data turning1 = (p_sum1.dot(leftmost_begin.v) <= 0) or (p_sum1.dot(rightmost_begin.v) <= 0) p_sum2 = leftmost_end.p.data + rightmost_p_sum turning2 = (p_sum2.dot(leftmost_end.v) <= 0) or (p_sum2.dot(rightmost_end.v) <= 0) turning = turning | turning1 | turning2 return diverging, turning def _single_step(self, left: State, epsilon: float): """Perform a leapfrog step and handle error cases.""" """省略""" return tree, divergence_info, False def _build_subtree(self, left, depth, epsilon): if depth == 0: return self._single_step(left, epsilon) tree1, diverging, turning = self._build_subtree(left, depth - 1, epsilon) if diverging or turning: return tree1, diverging, turning tree2, diverging, turning = self._build_subtree(tree1.right, depth - 1, epsilon) left, right = tree1.left, tree2.right if not (diverging or turning): p_sum = tree1.p_sum + tree2.p_sum turning = (p_sum.dot(left.v) <= 0) or (p_sum.dot(right.v) <= 0) # Additional U turn check only when depth > 1 to avoid redundant work. if depth - 1 > 0: p_sum1 = tree1.p_sum + tree2.left.p.data turning1 = (p_sum1.dot(tree1.left.v) <= 0) or (p_sum1.dot(tree2.left.v) <= 0) p_sum2 = tree1.right.p.data + tree2.p_sum turning2 = (p_sum2.dot(tree1.right.v) <= 0) or (p_sum2.dot(tree2.right.v) <= 0) turning = turning | turning1 | turning2 log_size = np.logaddexp(tree1.log_size, tree2.log_size) if logbern(tree2.log_size - log_size): proposal = tree2.proposal else: proposal = tree1.proposal else: p_sum = tree1.p_sum log_size = tree1.log_size proposal = tree1.proposal tree = Subtree(left, right, p_sum, proposal, log_size) return tree, diverging, turning def stats(self): self.mean_tree_accept = np.exp(self.log_accept_sum) / self.n_proposals return { """省略""" } まとめ モデル作成からサンプリングまでの挙動を見ることで、PyMCが実際に何をしているのかを明らかにしました。 PyMCが担当しているパートはおおまかに言うと PyTensorの確率変数をインスタンス化 モデルとして確率変数の関係を保持 サンプリングの並列実行 サンプリング手法の実装 でした。理解が深まる一助になれば幸いです。 [1]: https://pymc-devs.medium.com/theano-tensorflow-and-the-future-of-pymc-6c9987bb19d5 [2]: https://pymc-devs.medium.com/the-future-of-pymc3-or-theano-is-dead-long-live-theano-d8005f8a0e9b [3]: https://www.pymc.io/about/history.html [4]: https://www.pymc.io/blog/pytensor_announcement.html#pytensor_announcement [5]: https://docs.python.org/ja/3/reference/compound_stmts.html#with [6]: https://arxiv.org/pdf/1111.4246.pdf
こんにちは!Insight Edgeでコンサルタントとして働いている山田です。 この記事では、AI開発プロジェクトにおいてよく議論になる「知的財産権の帰属」について、 クライアントとの契約時に注意すべきポイントをまとめました。 本記事は経済産業省が策定している「AI・データの利用に関する契約ガイドライン」を参考に、 特にAIモデルの知的財産権に焦点を当てて解説しています。 もしご興味があれば、ガイドラインも併せてご覧頂けると、より理解が深まるかと思います。(全362ページの大作!) 「AI・データの利用に関する契約ガイドライン 1.1版」を策定しました (METI/経済産業省) 1.知的財産権とは? 知的財産権の種類 2. AI開発において発生する知的財産権 何に対して知的財産権が発生するのか 知的財産権の発生と帰属のデフォルトルール (参考)知的財産の発生有無と権利帰属のデフォルトルール 3. 知的財産権に関する契約の勘所 AI開発の知財帰属で、ユーザーとベンダーは対立しがち 知的財産権の利用条件の定め方 プログラムの部分はベンダー側に帰属させる方が良い 4. まとめ 参考記事・資料 1.知的財産権とは? 知的財産権の種類 知的財産権とは、様々な知的創造活動によって生み出されたものを、創作者の財産として保護するために存在する権利です。 知的財産権と一口にいっても、創作意欲の促進を目的とした「知的創造物についての権利」と、使用者の信用維持を目的とした「営業上の標識についての権利」の2つに大別されます。 特許庁「知的財産権について」より抜粋、一部改変 2. AI開発において発生する知的財産権 知的財産権には上記の通りいくつも種類がありますが、AI開発において重要となるのは以下3種類です。 特許権 技術的に新規性のある発明に対する権利。AI開発ではアルゴリズムや機械学習モデル、アーキテクチャ等に適用されます。 著作権 著作物や創作物に対する権利で、AI開発ではソースコードやアルゴリズム、データセット、データベース等が該当します。 営業秘密 企業独自の情報を企業秘密として保持する権利です。研究開発成果やノウハウ等が当てはまります。 何に対して知的財産権が発生するのか そもそも、どのようなデータや成果物が知的財産権の対象となるのでしょうか。 AI開発のフローに沿って考えると、検討する必要のある構成要素として以下の6つが考えられます。 ①生データ 加工や編集がされていない、元々の状態のデータ ②学習用データセット 前処理やクレンジングを行った、AIモデルが学習できる形式に整理されたデータの集合 ③学習用プログラム AIモデルが学習するために使用されるソフトウェアやアルゴリズム ④学習済みモデル 学習用データセットを用いて訓練されたAIモデルで、最適化されたパラメータが組み込まれた推論プログラム ⑤学習済みパラメータ AIモデルが学習用データセットを用いて訓練される過程で最適化された重みなどの値 ⑥ノウハウ AI開発プロセス全体における独自の技術や知識 知的財産権の発生の観点では、これらを 「データ」 、 「プログラム」 、 「ノウハウ」 の3つに大別することができます。 経産省「AI・データの利用に関する契約ガイドライン」より作成 知的財産権の発生と帰属のデフォルトルール 上記6つのどの要素に知的財産権が発生するか、発生する場合誰に帰属するかは、あらかじめ法的に定められています。 「営業秘密」は特定の要件(※1)を満たす場合はいずれも共通して発生するため、以下では各要素が「特許権」「著作権」に該当するかどうかを、それぞれ見ていきたいと思います。 (※1)秘密管理性、有用性、非公知性の三要件 最初に結論を以下の表にまとめました。 それぞれの根拠は後述しますが、少し細かくなるのでざっくり以下のようなイメージです。 データ 特許権も著作権も発生しないことが多い。利用権については契約で定める必要あり。 プログラム 基本的に特許権および著作権は発生し、作成者であるベンダーが保有することが一般的。利用権については契約で定めることでユーザーも柔軟な利活用が可能。 ノウハウ 特許権も著作権も発生しないことが多い。利用権については契約で定める必要あり。 経産省「AI・データの利用に関する契約ガイドライン」より作成 (参考)知的財産の発生有無と権利帰属のデフォルトルール ①生データ ユーザー側が提供する生データは、単なる事実や情報の集積(ログデータや気象データなど)であるケースが多く、その場合は知的財産には該当しません。ただし、生データに著作物性(写真、音声、映像、小説等)が認められる場合は、「著作権」が発生する可能性があります。 帰属については、生データが営業秘密にも該当しない場合は、知的財産が発生しないため、契約によって利用方法を個別に定める必要があります。 ②学習用データセット 生データを加工したとしても単なる情報の集積に過ぎないため、知的財産には該当しないケースが多いです。一方で、情報を体系的に整理し、データ抽出を容易にするような創作性を有する場合は、「データベースの著作物」に該当し、「著作権」が発生する可能性があります。 帰属については、ベンダーのノウハウのみを使用してデータセットを作った場合はベンダーが保有者となり、ユーザーとベンダーが共同で作成した場合は共同所有となることが多いです。 ③学習用プログラム 一般的な「プログラム」と同じく、ソースコード部分は著作物として「著作権」が発生し、アルゴリズム部分は特許法上の要件を満たせば「特許権」を有する可能性があります。 帰属については、著作権を持つのは著作者であり、特許権を持つのは発明者であるため、これら権利は基本的にベンダー側に帰属することが一般的です。 ④学習済みモデル 学習モデルのうち、「推論プログラム」は③の学習用プログラムと同じ整理になるため、ソースコード部分は著作権が、アルゴリズム部分は要件を満たせば特許権が発生します。「学習済みパラメータ」は⑤でも説明しますが、大量の数値データに過ぎず創作性等が認められないため、知的財産権の対象にはならない可能性が高いです。 帰属については、「推論プログラム」部分は、作成したベンダーに帰属することが基本です。 ⑤学習済みパラメータ 学習用プログラムにより自動的に生成される大量の数値データに過ぎないため、創作性が認められず、著作物も特許権も発生しない可能性が高いです。(一方、プログラムに準ずるものや著作性を持つと判断される見解もあり、定まっていないのが実情です) 帰属については、営業秘密にも該当しない場合は、知的財産権が発生しないため、契約によって利用方法を個別に定める必要があります。 ⑥ノウハウ AI開発には様々なノウハウが必要になりますが、ノウハウ自体は無形物であるため著作権の対象にはならないものの、発明の要件を満たす場合は特許権の対象になります。 帰属については、営業秘密にも発明にも該当しない場合は、知的財産が発生しないため、契約によって利用方法を個別に定める必要があります。 3. 知的財産権に関する契約の勘所 AI開発における「データ」、「プログラム」、「ノウハウ」について、知的財産に該当するかどうかは法的に定められており、一般的な解釈については理解できました。 一方で契約で争点になりやすいのが、 「知的財産権がユーザーとベンダーのどちらに帰属するか」 という点です。 先ほどのデフォルトルールのうち、法的に明確なのは「プログラム」で、基本的に作成者であるベンダーに知的財産権は帰属することになります。一方で、「データ」「ノウハウ」については、営業秘密や発明に該当しない場合は、原則ユーザー/ベンダー双方が自由に利用可能となってしまうため、契約によって利用条件を定める必要があります。 AI開発の知財帰属で、ユーザーとベンダーは対立しがち AI開発プロジェクトにおける権利帰属の議論の際、以下のようなやりとりがよく見られます。 双方が権利の帰属を主張し続ける限り、平行線のまま交渉に時間と労力だけを費やすことになります。ここで重要となるのが、 「権利帰属にこだわるのではなく、利用条件で実をとること」 です。 成果物をビジネスで利用する際、知的財産権を所有していなかったとしても、利用に制限がなく自由度が高ければ、実務上不都合が生じることは少ないです。そのため、個人的には権利の有無よりも、いかに自社にとって有利な利用条件とできるかの方が肝と考えます。 知的財産権の利用条件の定め方 利用条件の定め方に正解は無く、実態に応じて都度検討する必要がありますが、ユーザー/ベンダーがそれぞれ何を求めているかを相互によく理解した上で、利用条件を細かに設定することが重要です。 利用条件を定める上で考慮すべきポイントとしては、以下のようなものが考えられます。 利用目的 契約に規定された開発目的に限定するか否か 利用範囲 利用者がAIモデルをどの程度の範囲で使用できるか 利用期間 契約期間や終了条件の明示 第三者への利用許諾・譲渡可否 他社への提供や横展開を認めるか 利益配分 ライセンスフィー、プロフィットシェア プログラムの部分はベンダー側に帰属させる方が良い それぞれの知的財産権は、ユーザー、ベンダーいずれにも権利帰属させることは可能ですが、個人的にはAIモデルのプログラムに関する部分はベンダー側で保有する方がメリットが大きいと感じます。 例えば、AIモデルは技術発展のスピードが著しく、開発したモデルの陳腐化が早いことを踏まえると、ベンダー側に権利帰属させることで、アップデートや再学習を柔軟に行うことができます。ユーザー側には、ビジネス展開上不自由のない利用条件を付与することで双方の利益が最大化する可能性が高いです。 また、ユーザー側の懸念として、ベンダーが知的財産権を利用して競合他社へ横展開することを避けたい場合は、開発後一定期間の目的外利用や協業的利用をベンダーに禁止する等の契約とすることも可能です。 このように、権利帰属にこだわるのではなく、双方にとって不都合のない利用条件となるように、契約内容を協議することが重要になると思います。 ちなみにInsight Edgeでは、知的財産権の帰属は可能な限り自社に保有する契約にすることが多いです。 Insight Edgeの場合、ユーザーのほとんどは住友商事グループの事業会社ですが、対象とする業界や課題が多岐にわたるため、個別開発したAIモデルが他業界や事業会社に展開できるケースが少なくありません。 その際に、Insight Edgeにソリューションやノウハウを集積し、スピーディーに展開できるようにすることで、グループ全体の利益創出が期待できるためです。 4. まとめ AI開発における知的財産権にフォーカスし、契約においてユーザーとベンダーの間で論点となりやすいポイントをまとめました。 AIソリューションをビジネス展開する上で、知財戦略は重要な観点な一方で、AI技術の急速な普及により、AI技術の特性を当事者が理解しきれていないことや、権利関係・責任関係等の法律関係が不明確であるなど、契約に関するベストプラクティスが十分に確立されていないのが実情です。したがって、契約締結においてはビジネスの実態に踏まえた最適な内容となるよう、当事者間でしっかりと認識を合わせることが重要となります。 経産省が発表している「AI・データの利用に関する契約ガイドライン」では、具体事例も交えながら分かりやすくまとめられていますので、こちらも是非参考にして頂ければと思います。 参考記事・資料 AI・データの利用に関する契約ガイドライン | 経済産業省 「AI・データの利用に関する契約ガイドライン」に学ぶAI開発契約の8つのポイント | STORIA法律事務所 知的財産権について | 経済産業省 特許庁
はじめに  こんにちは!Insight Edgeでデータサイエンティストとして働いている五十嵐です!  最近花粉症が大変すぎて飲み薬に目薬に点鼻薬と毎日薬漬けです。鼻うがいも毎日してます!  今回は、AIの公平性について少し調べてみようかなと思い、調査内容を簡単にまとめます。本記事の内容は、基本的に、 A Survey on Bias and Fairness in Machine Learning (Mehrabi et al.) を参考にしています。本論文は、初稿が2019年8月ですが、何度か改修され、last revised が2022年1月となっております。被引用件数が2,000件を超えているので、この分野のsurvey論文としてはかなり有力なものではないでしょうか。  本記事は、様々な人にも興味を持って頂けるよう、技術的内容にはあまり触れずに紹介しようと思います!紹介論文は、34ページなので、今回紹介できる部分は極一部であることをご理解頂ければと思います。また、私が未熟で不勉強な部分も多いため、もし間違った解釈があった場合は優しくご指摘頂けますとありがたいです!! 「公平」とは  そもそも、「公平」とはなんなのでしょうか。AIの公平性についての話をする前に、そもそも「公平」の定義とはどのようなものなのか説明します。  様々な分野や考え方によって「公平」の定義は異なる為、一意に決めることは非常に難しいですが、本論文では、次のように説明されています。 “ absense of any prejudice or favoritism toward an individual or group based on their inherent or acquired characteristics ”.  直訳すると、「個人・グループに対して、先天的、または後天的な特徴によっていかなる偏見や好意がないこと」でしょうか。納得性が高い定義のように思います。 ※ 似たような意味で「公正」という言葉がありますが、「公平」と「公正」の正しい言葉の使い分けに自信があまりないので、本記事ではfairnessを公平、公平性と訳しております。 何故、AIの公平性が重要なのか  人間の判断に度々偏りが生じてしまうように、AIの判断も公平でなければ、我々人間と同じように差別的な判断をしてしまったり、偏った判断をしてしまう可能性があります。  近年は、AIシステムやアプリケーションが日常生活の中で広く使われるようになってきており、人生に大きく関わる分野でAI技術が使われるようになってきています。この為、以前より不公平なAIのもたらす影響が大きくなり得ると言えます。  例えば、不公平なAIシステムの例として頻出なものに、職業推薦システムがあります。同一条件であるにも拘らず、女性というだけで男性より低い評価になってしまう、つまり、性別の違いだけで推薦する職業やその収入が大きく違ってしまうという例は聞いたことがある人も多いのではないでしょうか。 不公平になる原因と不公平なAIが生む悪循環  不公平なAIシステムは一体どのような原因で生み出されてしまうのでしょうか。  それは、データやアルゴリズムに隠れた、あるいは無視されたバイアスです。(具体的なバイアス例については後述します。)  また、万が一、不公平なAIシステムが世の中で使われるとどうなるのでしょうか。  論文では、偏ったアルゴリズムの結果が、ユーザー体験に影響を与え、データ、アルゴリズム、ユーザーの間でフィードバックループが生じてしまい、既存の偏りを永続させ、さらに増幅させる可能性がある、と説明されています。 データ、アルゴリズム、ユーザーインタラクションのフィードバックループに配置されるバイアス定義の例 (A Survey on Bias and Fairness in Machine Learning (Mehrabi et al.))  学習データにバイアスがある場合、それを学習したアルゴリズムはそのバイアスを反映して予測をしてしまいます。また、データにバイアスがなくても、アルゴリズム自体が特定の設計上の仮定によりバイアスを有した挙動を示すことがあります。このようなバイアスを持つアルゴリズムの結果は、実世界のシステムに投入され、ユーザーの意思決定に影響を与え、よりバイアスのあるデータを生み出してしまいます。 バイアスの例  ここまで、不公平なAIシステムの原因としてデータやアルゴリズムに存在するバイアスであることを説明しました。  それでは、どのようなバイアスがあるのでしょうか。  ここでは、論文で紹介されていたバイアスについて、データに関するバイアスの一部を紹介します。 計測バイアス(reporting bias)  特定の特徴をどのように選択、利用、計測するかによって発生するバイアスです。 代表バイアス(representation bias)  データ収集プロセスにおいて、母集団からどのようにデータをサンプリングするかに起因するバイアスです。 (例) 下図に示すように、ImageNetの地理的な多様性の欠如は、西洋文化に対するバイアスに繋がるとされています。 Open ImagesとImageNetの画像データセットに含まれる、2文字のISOコードで表される各国の割合。両データセットとも、米国と英国が上位を占める. (No Classification without Representation: Assessing Geodiversity Issues in Open Data Sets for the Developing World(Shreya Shankar et al.)) 社会的バイアス(social bias)  他人の行動が我々の判断に影響を与える時に生じるバイアスです。 (例) 低い得点で何かを評価・レビューした場合に、他の人が高い評価をしていると、自分の評価が厳しすぎると考えて得点を変更してしまうことで生じるバイアスです。 歴史的バイアス(historical bias)  世の中に既に存在する偏りや社会技術的な問題であり、完璧なサンプリングと特徴選択を行えたとしても、データ生成プロセスから染み込んでくる可能性のあるバイアスです。 (例) 2018年においては、フォーチュン500のCEOのうち女性が5%しかいないことに起因し、CEOの画像検索結果が男性CEOに偏っていました。これは、現実を反映してものではありますが、検索アルゴリズムがこの現実を反映すべきかは検討が必要です。  他にも、様々なバイアスが紹介されていましたが、バイアスを全て紹介するのが本記事の目的ではないので、詳細を知りたい方は論文をご確認頂ければと思います。 どこからが問題なのか  これまで、AIシステムの原因となり得る様々なバイアスについて説明してきましたが、具体的にどこからが問題となるのでしょうか。バイアスではなく、正当な特徴であるかどうかの判断はどのようにすれば良いのか、この議題についても下記のような説明がされています。 差異が正当に説明可能かどうか  異なる集団間の待遇や結果の違いは、場合によってはある属性によって正当に説明されることがあります。このように、「差異が正当化され説明される状況では、それは問題にはならず、説明可能である」と記載されています。  この例として、平均して男性の方が女性より平均年収が高いという場合に、平均して女性の方が労働時間が短いという属性があれば、この男女差は説明可能であり、許容される、と説明されています。  個人的には、労働時間の男女差が何故生じているのかまで踏み込んで考えなければ扱いが難しい問題だとは思いますが、このようにある特徴量によって説明される場合は問題ないと見なされることが多いみたいです。  上記とは違い、どのような属性によっても正当に説明されない差異の場合は問題となります。 不公平なAIを作り出さないために、どうすれば良いのか  それでは、不公平なAIを作り出さないためにはどのようにすれば良いのでしょうか。  これまで紹介してきました、不公平なAIの原因となるバイアスは、大きくデータ由来のものと、アルゴリズム由来のものがあります。これらのバイアスを避けるために紹介されていた方法について、いくつか説明します。 データ由来のバイアス対策 データ由来のバイアスを避けるために、下記の内容が重要であると紹介されています。 「全てのデータセットは、データ管理者によってなされたいくつかの設計上の決定の結果である」ことを理解する. 扱っているデータの生成プロセスを正しく・詳細に理解する. 因果モデルや因果グラフの利用を検討する. それぞれについて説明していきます。 1. 「全てのデータセットは、データ管理者によってなされたいくつかの設計上の決定の結果である」ことを理解する  解析に用いるデータには、データ管理者が存在します。その管理者には何か目的がありデータを作成・収集しています。また、その管理者にもコントロールすることが難しい因子がデータそのもの、または、データ収集環境に存在する可能性があります。このことを正しく理解することで、データに存在するバイアスの調査にも取り掛れますし、後に説明するデータ生成プロセスの理解に時間を掛けることにも繋がります。  本論文では、対策として、データセット作成、特性、動機、偏りを報告するデータシートの作成をルール化する、というようなデータ利用時の良い習慣を提唱するアプローチもいくつか紹介されています。 2. 扱っているデータの生成プロセスを正しく・詳細に理解する  データを扱う際、そのデータだけを見ていても取得できる情報は限られています。データ背景、データ生成プロセスなどを詳細に理解しなければ正しくデータを理解することは非常に難しいです。この対策として、例えば、データドメインを調査し、データ生成プロセスを詳細に理解することが挙げられます。データ生成プロセスを正しく理解することで、前処理によってバイアスを取り除くなどの対処ができる場合があります。また、学習過程においては、目的関数に変更を加えたり、制約を課すなどしてバイアスを取り除ける場合があります。 3. 因果モデルや因果グラフの利用を検討する  2.と同様で、データを扱う際、そのデータだけを見ていても取得できる情報は限られています。この対策として、因果モデルや因果グラフの利用が多数提案されています。因果グラフはデータだけではなく、その背景や生成プロセスなど、交絡因子に関する因果関係を表現することができます。 アルゴリズム由来のバイアス対策  アルゴリズムもバイアスを持つことがあります。その中の一つ、帰納バイアスについて紹介します。  帰納バイアスとは、簡単にいうと「そのアルゴリズムが前提としている仮定により発生するバイアス」です。具体例として、ViT(Vision Transformer)とCNN(Convolutional Neural Network)の学習データ量と精度の関係についての議論で知っている人も多いのではないでしょうか。CNNは「画像データは近傍の(局所的な)情報が重要である」という仮定を持つ、帰納バイアスのあるモデルです。これに対して、ViTは強い仮定をおいていないため、強い帰納バイアスを持ちません。CNNが比較的少ないデータ数の場合は、ViTより高い精度が出やすいのはこの帰納バイアスが上手く機能している為と考えられています。逆に、帰納バイアスの弱いViTは十分なデータセットを用いた場合にはCNNよりも高い精度を誇ります。このように、アルゴリズムの持つバイアスによって使い所が変わりますし、データ構造やその目的によって適切なアルゴリズムを選択することが理想的だと言えます。また、帰納バイアスなど、使用するアルゴリズムの性質を正しく理解していないとその使い所や解釈を間違えてしまうため、アルゴリズムの持つバイアスを理解することは非常に重要です。  他にアルゴリズムの持つ仮定として、データや残差の分布が正規分布を仮定するなど様々ありますが、正しく利用し正しく解釈するには、いずれもデータとアルゴリズムの理解が必要です。 最後に  論文の一部を簡単に紹介してきましたが、正直かなり難しい問題であることを再認識できました。論文を読んだだけでも、そのタイトルにある” Bias and Fairness in Machine Learning ”という議題がいかに難しいかが実感できます。また、バイアスについてもご紹介した以外に様々あり、世の中からあらゆるバイアスを完全に無くすことはかなり難しいことも実感します。しかし、本記事で説明したように、データの生成プロセスを意識したり、使用するアルゴリズムの性質を理解することで、AIシステムにバイアスが入り込む可能性を低くする取り組みができます。 また、対策についても、本記事でご紹介できた以外にも様々なアイデアや取り組みが数多くあるということはご理解頂ければと思います。  今回紹介できた部分は極一部ですが、この記事が誰かがAIの公平性について考える一助になってくれれば幸いです。
こんにちは、花粉症がキツくなってきましたエンジニアリングマネージャの猪子です! 2023/3/1にTech Meetup Eventを弊社、 FastLabel株式会社 、 株式会社ヘッドウォータース の3社で合同開催しました。 テーマは 先端テクノロジー活用によるDX実現を目指す開発組織の中身に潜入 です。 FastLabelのCEO 上田 英介さん司会のもと、弊社 CTO 福井が冒頭挨拶をさせて頂きました。 その後、FastLabel VPoE 植野 晃司さん、ヘッドウォータース コネクテッドテクノロジー部 部長 西川 貴弘さん、私がスピーカーとしてLightning Talkを行い、発表後は30分ほどフリートークセッションが行われました。 スピーカーの皆様 イベントは一般公開はしなかったのですが、当日の参加者は50名を超え、社内のセミナー用slackチャンネルも盛り上がりを見せていました :) 当日の発表 LT① 継続カイゼン!トライ&エラーから学んだコミュニケーションと環境づくり 株式会社Insight Edge Engineering Manager 猪子 徹 speakerdeck.com LT② 急成長を続けるAI×SaaSスタートアップで求められるエンジニアスキル FastLabel株式会社 VPoE 植野 晃司 speakerdeck.com LT③ ”新しいことに挑戦したい組織”が考えるべきこと ~”やりたい”だけでは実現しない~ 株式会社ヘッドウォータース コネクテッドテクノロジー部 部長 西川 貴弘 speakerdeck.com 振り返って 夕方の遅い時間にも関わらず各社から多くの参加者が集まり、盛り上がりを見せていました! 内容としてもコミュニケーション、キャリア/スキル、組織としてのチャレンジ等、各社のフェーズは異なれど何れも今後の成長のヒントになる内容だったと思います。 何より未だセミナー登壇などもオンラインで聴講者の顔が見えずに配信される中、他社のエンジニアの方々と顔を見せ合いながら交流出来たのが個人的に良い刺激でした! 今後も社内外含め定期的に情報発信を続けていきます :) 終わりに Insight Edgeはデータサイエンティスト、エンジニア、UI/UXデザイナ、プロジェクトマネージャ、コンサルタント等、会社を共に盛り上げてくれるメンバを募集しています。 興味がある方はカジュアルにお話させて頂ければと思いますので、お気軽に こちら からお申し込みください!