Timee Product Team Blog

タイミー開発者ブログ

AI開発標準策定の失敗から学ぶ、不確実性があるタスクを推進する技術

この記事はTimee Product Advent Calendar 2025の22日目の記事です。

はじめに

こんにちは!kazzhiraと申します。タイミーでプロダクトエンジニアとして働いています。 この記事では、AIエージェント開発をチームに定着させようと奮闘したストーリーと、そこで得た学びを語ろうと思います。 よってこの記事はAIの技術解説ではありません。 『AI推進』という答えのない課題に直面して空回りしたエンジニアが、どのようにして仕事を前に進めるコツを掴んだのかを記録したものです。

何かを推進する立場の方にとって学びや励みになればと思い、執筆しています。

背景

チームメンバーは全員がプロダクトエンジニアで、AI時代よりも前から開発経験があります。 主にフロントエンド、バックエンド、Androidのケイパビリティがあります。

Cursor、Claude Code、Devin、GitHub Copilot、Geminiを個々人の判断で利用可能です。

プロダクト組織ではAI活用が進んでおり、 私も「AI活用を進めて生産性を上げたい」という強いモチベーションがありました。

上司からもチャンスをいただけたので、即「やっていきます!」と推進をしていくことに。

推進で試行錯誤した3ヶ月間の話

これまで私は開発業務の遂行、個人で生産性を上げる取り組み、短期的な組織運営を経験してきました。 今回引き受けたAI推進は、チーム全体をスコープとする中長期の組織的な取り組みです。 このスコープでの取り組みで成功体験が少なく、私にとって新たな挑戦でした。

当初の状況を整理すると、組織としてはTipsは転がっているものの、AI活用の状況は個々人に閉じていました。 AIの技術進化は近年目まぐるしく、AIを中核にワークフローを組んだりサービスを作ってもすぐに陳腐化してしまうなど、不確実性が高い領域です。

この状況を認識しつつも悶々としたまま、2週間が過ぎました。そう、身動きが取れていない状態だったのです。このときの行動のループをまとめると、

  • 推進って具体的にどうするの?自分の中で良さそうと思っていることを単に広めるってこと?
  • ブレストして、共通項をまとめる…
  • 結局それで何になるの?AI活用が今より進んでいるってどういう状態?

今振り返ると、焦りから完全に空回りしていました。

AI開発標準の作成エピソード

頭を悩ませているうちに時間が経過し、自分自身のモチベーションも下がっていることに気づきました。このことを上司に相談したところ、的確なフィードバックをもらいました。

「情報収集と分析が足りていない」(意訳)

ハッとしました。 チームには情報がありません。当然、情報収集が必要です。 私はテックブログや時事ネタを眺め、それを手元で試したり、試さずに終わってしまったりしている状態でした。 「なぜやるのか」「どうやるのか」をチームに腹落ちさせるだけの材料も、 自分自身の確固たる言葉も持っていなかったのです。

その日から私は情報をかき集め、Geminiと相談しながら内容を深掘りし、知見を深めました。 80%くらいのAI開発標準v0.1を即座に作成し、メンバーからフィードバックを受けてブラッシュアップしました。

次に示すのはドキュメントで定義した開発フロー図の一部です。 前後のPhaseは本質と関係ないため図から除外しています。

核心としては、CursorやClaude Codeなどで実装されているPlanモードで実装計画とレビューを挟むことで、AIの出力を高品質にし、手戻りを減らす開発スタイルを言語化したものです。 コンテキスト用意の部分を解説します。

  • ガードレール設計
    • リポジトリ毎に規約が決まっていることが多いので、その参照を持たせます
  • プロンプティング
    • 役割や制限事項を含めた命令文を書きますが、コンテキストはガードレール、Design Doc、MCPサーバで補完できることが多いため、プロンプトの分量はそれほど多くなりません
  • Design Doc作成
    • DB設計書、API設計書など必要に応じて設計書を作成します
  • MCPサーバ接続
    • Figma MCPやGitHub MCPを利用して外部のコンテキストを取得します

AIエージェント、推論モデルは目まぐるしく変化するため、そこに依存せず中長期的に運用できる内容を目指して作成しました。

AI開発標準v1.0は、フィードバックと改善が活発に行われ、知見が共有されたり目線が揃ったことで、個々人の生産性には多少の変化がありました。 しかしチームで提供する価値の面では大きな成果は上がりませんでした。 なぜならば、資料の説明は抽象的で、かつ現在地を示したに過ぎず、チームも具体的に動きようがなかったからです。 チームの課題や生産性のボトルネック、理想とする開発体験など、「なぜやるのか(Why)」「何をやるのか(What)」「どう実行するのか(How)」が、全くもって抜けていました。 この成果物は開発手法の共通項を抜き出したガードレールになっただけでした。 ガードレールがあるのは良いのですが、チームの状態としては相互理解や知見が少々増えて開発が楽になったに過ぎず、会社が「期待以上だった」と評価を下せるほどの成果ではありません。

スクラムに着想を得た推進の勘所

ここでふと、今まで長期に渡り実践してきたスクラム開発を思い出しました。 スクラム開発ではプロダクトゴールとスプリントゴールを設定して不確実性が高い環境でも継続して価値提供を実行します。

先ほど反省した通り、中長期の組織的な取り組みにもゴールとマイルストーンを設定し、計画的に進めることが大事なのだと気づくことができました。

日々やっているはずなのに、なぜできなかったんだろう、忘れてしまったんだろう…と落ち込みましたが、今では伸びしろだと考え直しています。 気づくといろいろなものが見えてきて、何をすべきかがわかります。 最近、上司から的確なフィードバックをもらいました。

「今のままでも前に進むとは思いますが、ゴールと計画を具体化した方がいいですね」

まさに!です。 まず、理想の開発体験をチームで話し合ってみることに決めました。 議論を進めるうちにチームでは次の結論が出ました。

  • 作業さえ切り出してしまえば、実装はボトルネックになっていない
  • リファインメントで議論が発散する時間が長く、収束に時間がかかる場合がある
  • AIにはビジネス戦略、顧客課題、ユーザーストーリーが渡っていない。 ドメインの知識を詰め込んだGeminiのGem、もしくはNotion AIを用意することで、期待に近い出力を得ることができそうだ
  • AIの出力が微妙な時は、整理されたコンテキストが足りていないことが多い。 仕様の記述もAIを中心に進め、レビューを開発者が行えば、質とスピードが担保できそうだ。 これらを満たすSDD(仕様書駆動開発)に、投機的に取り組んでみる

これにより、以前よりも一歩前に進むことができ、次の具体的な計画を立てることができます。 明確な成果に紐づけるところまでは達成していませんが、以前よりも議論が活発になり、開発体験をより良くしていく動きができるようになりました。

推進を着実に進める方法

今回の試行錯誤から得た学びを箇条書きにすると、

  1. なぜ、なにを、どのように、を突き詰めて課題の解像度を上げること
  2. 動く前に今わかる範囲での計画を立てること(AIは手段であり、まずは解決したい課題とゴールを定義しないと空回りする
  3. AI技術を定期的に取りこんでチームに適用してみる活動を行い振り返るループを作ること
  4. 何よりも自分自身がワクワクすること

これは推進に限らず、あらゆる仕事で通じることですが、改めて勉強になりました。 抽象的な仕事を具体化して進めるコツが掴めたような気がします。

世には最近読ませていただいた ”**抽象度の高い仕事の進め方”** のように勉強になる記事がたくさんあるので、ぜひご覧になってください。

まとめ

ここに至るまで、チームメンバーと上司、ストーリー上では触れなかった方々も含め、ご助力いただきありがとうございます。ここで感謝の意を表したいと思います。 引き続きワイワイ楽しみつつ一緒に進めていきましょう。

私はまだインパクトの大きい価値を生み出せてはいませんが、 推進のためにやることが明確になったので引き続き取り組んでいきます。

最後に、この記事を読んでくれた方の推進力になることを願っています。 ここまで読んでいただき、ありがとうございました!

タイミーには、こうした挑戦と学び、そして発信を歓迎する文化があります。 ともに挑戦し成長していきたい方、興味があればぜひ1度お話ししましょう!

プロダクト採用サイトTOP

カジュアル面談申込はこちら

データアナリストが趣味のランニングを分析して再確認した"身体性の重要さ"

こんにちは、タイミーのプロダクトアナリティクスチームでプロダクトのデータ分析をしている松下です。 この記事は Timee Advent Calendar 2025 シリーズ 2 の22日目の記事です。

はじめに

私の趣味はランニングで、仕事はデータアナリストです。 ランニングウォッチをつけてランニングをしていると多くのデータが溜まっていきます。

GarminDB という、 Garmin Connectからデータをダウンロードしてパースするライブラリを見つけたので、データベースに入れて改めてデータ分析をしてみました。

すると仕事で大事にしていることが、そのままランニングにも当てはまることに気づきました。

  • 見るべき指標を選ぶこと
  • 平均ではなく分布を見ること
  • データの向こう側にある情報を処理すること

どれもデータ分析の鉄則です。やっぱりそうだよな、と思いながら分析を進めていきました。 この記事では、ランニングデータの分析を通じて改めて実感した、データ分析で大事にすべきことについて書きます。

いつものランニングコース。車が少ない川沿いを走るのが好きです。

ランニングデータを分析してみた

最近のランニングウォッチはデータの宝庫です。 私はGarminのランニングウォッチを使っていますが、走るたびに以下のようなデータが溜まります。

距離、ペース、タイム 心拍数(平均、最大、ゾーン別の時間) ケイデンス(ピッチ) 気温 標高差 トレーニング効果、負荷 etc...

BigQueryに自動同期する仕組みを作った

GarminのデータをBigQueryに入れるために、以下のライブラリを活用しました。

github.com

AIの力を借りながらパイプラインを構築できました。データを集めるハードルは、AI時代にどんどん下がっています。

github.com

データで見えること①:見るべき指標を選ぶ

心拍数ゾーンでトレーニングする方法

ランニングには「心拍数」でトレーニングする方法があります。

心拍数ゾーンとは 心拍数を最大心拍数に対する割合で5段階に分けたもの。

  • ゾーン1(50-60%):ウォーミングアップ、回復走
  • ゾーン2(60-70%):脂肪燃焼、持久力向上。会話できるペース
  • ゾーン3(70-80%):有酸素能力向上
  • ゾーン4(80-90%):乳酸閾値付近、レースペースに近い
  • ゾーン5(90-100%):最大努力、短時間のみ維持可能

「ゾーン2で長く走る」トレーニングは、体への負担を抑えながら持久力を高める方法として人気があります。Garminのウォッチも心拍数ゾーンをリアルタイムで表示してくれます。

でも、私はペースを見ることにしました。

心拍数トレーニングにしっくりこなかった理由はシンプルです。

レースで求めているのは「心拍数」ではなく「タイム」だから。

心拍数は体調、気温、睡眠、ストレスによって大きく変動します。同じゾーン2でも、調子が良い日は5:30/km、悪い日は6:30/kmになることがあります。これでは「今日どれくらい走れたか」がわかりません。

一方、ペース(タイム)は嘘をつきません。5:00/kmで10km走れたら、それは紛れもなく「5:00/kmで10km走れる体」ができているという証拠です。

これは仕事でも同じ

見るべきは「施策を打てる指標」であり、「結果に直結する指標」です。

DAU(デイリーアクティブユーザー)を追っていても、それだけでは何をすればいいかわかりません。一方で、「新規ユーザーの7日後継続率」なら、オンボーディング改善という施策につながります。

心拍数は「体の状態」を表すデータ。ペースは「結果」を表すデータ。アマチュアランナーの私にとって、追うべきは後者でした。

データで見えること②:平均の罠と比較の重要性

練習の平均ペースを比較してみた

「じゃあペースで何を見ればいいのか?」

まず、レース前3ヶ月の練習の平均ペースを比較してみました。

レース 練習の平均ペース レース結果
熊本城マラソン2024 5:19/km 3:17:50(4:38/km)
さいたまマラソン2025 5:08/km 3:16:51(4:36/km)ベスト
つくばマラソン2025 6:27/km 3:29:28(4:56/km)

速く練習したほうが速いタイムで走れそうですが、ここはアナリストなら平均の罠が隠れていることに気がつくと思います。

平均の中身を見る

練習のペースを「速い(5分/km未満)」「中程度(5-6分/km)」「遅い(6分/km以上)」に分類して、その割合を見てみました。

レース レースペース 速い練習(%) 遅い練習(%) 総距離
さいたまマラソン2025 4:36/km 38.6% 2.8% 598km
熊本城マラソン2024 4:38/km 30.8% 0% 844km
つくばマラソン2025 4:56/km 26.2% 29.6% 906km

発見

  • ベストが出た時(さいたまマラソン、熊本城マラソン)は、速い練習の割合が30%以上、遅い練習は0-3%
  • つくばマラソンは練習量906km(最大)だが、遅い練習が29.6%もあり、ベストは出なかった

平均だけ見ても「中身」がわからない

熊本城マラソン2024の練習平均ペースは5:19/km。一見、ゆっくり目に練習しているように見えます。

でも中身を見ると、速い練習30.8%、遅い練習0%。緩急をつけた質の高い練習をしていました。

つくばマラソン2025の平均ペースは6:27/km。こちらも「ゆっくり練習」に見えます。

でも中身は、速い練習26.2%、遅い練習29.6%。遅い練習が多すぎて、平均が下がっていただけでした。

平均だけ見ても「遅い練習が多いのか」「緩急をつけているのか」わからない。分布を見なければ意味がない。

これも仕事で経験済み

CVRの平均が上がっても、新規ユーザーだけ上がって既存が下がっていたら?

売上の平均単価が上がっても、高単価商品だけ売れて低単価商品が売れなくなっていたら?

平均だけを追うと、重要な変化を見落とします。

データだけでは見えないもの

身体性の重要さ

ランニングデータを分析して一番再確認したのは、身体性の重要さでした。

心拍数180の苦しさは、データで見る「180bpm」とは全く違う体験です。

データを見る人と、実際に経験している人では、同じ数字を見ても解釈が全く違う。

これは仕事のデータ分析でも同じです。

  • 自社サービスを自分で使う - ユーザー体験を肌で感じる
  • 生ログを500件くらい見てみる - データの「手触り」を知る
  • ユーザーインタビューに同席する - 数字の向こう側にいる人を想像する
  • 商談・カスタマーサポートに同行する - なぜ契約が決まる/決まらないかを体感する

データに現れる「前の段階」の手触りを知ることが、良い分析の前提条件だと思っています。

これは頭ではわかっていたことです。でも、自分のランニングデータを分析して、改めて強く実感しました。

おわりに AIには難しいこと

身体性は今のところAIが持つのは難しい領域です。

AIはデータを高速に処理し、パターンを見つけ、予測モデルを構築できます。SQLも書けるし、可視化もできる。正直、データを見る作業の多くはAIに代替されつつあります。

しかし、AIは自社サービスを使ったことがありません。ユーザーの不満を直接聞いたことがありません。

データの「手触り」を知っているかどうか。これが、AIと人間のデータアナリストを分ける決定的な違いになると思います。

データの手触りを大事にしながら、プロダクトに向き合いたい方。 ご興味があればぜひお話ししましょう!

プロダクト採用サイトTOP

カジュアル面談申込はこちら

In-Batch Negative Samplingを使ったモデル開発上の課題と対策

この記事はTimee Product Advent Calendar 2025 21日目の記事です。

データエンジニアリング部DSグループ所属の藤井と申します。現在タイミーで推薦エンジンの改善に取り組んでいます!

この記事では、タイミーの推薦エンジンにおける候補生成で導入している ANN(Approximate Nearest Neighbor)と、その ANN用ベクトルを作る Two-Tower モデルを題材にします。そして、このドメインで重要なテーマのひとつである Negative Sampling、特に In-Batch Negative Sampling を使ったモデル開発における課題と対策について議論していきます。

ANNはRAG(Retrieval-Augmented Generation)のバックエンドにおいても使用されることが多いので、ぜひ読んでいただけると嬉しいです!

Two Towerモデル利用の背景

ここからは Two-Tower モデルの話に入っていきます。

推薦エンジンで推薦を行う際、推薦対象となる item v をすべての user u に対して brute force で厳密に計算すると、組合せ爆発により計算量が O(|V|\times|U|) となってしまいます。そのため、件数が増えるにつれて、現実的なコストでは計算することが難しくなっていきます。

例として、日本の大学生300万人に対し、100万冊の本からベストな本を推薦する状況を考えましょう。この場合、「300万人×100万冊」で3兆通りもの組み合わせが生じてしまいます。推薦エンジンでの重たい計算や、LLM への API コールが必要なケースでは、このように全件をそのまま計算すると、非現実的なコストがかかる可能性が高いです。

ここで挙げた例のように、item数とuser数の組み合わせが多くなるケースでは、何らかの方法でうまく候補を抽出し、計算量を削減する必要があります。

この計算量の問題に対処するため、タイミーではANNおよびANNに使う推薦用のベクトル(埋め込み)を生成するために Two-Tower モデルを利用しています。

Two-Tower モデルと InfoNCE Loss のおさらい

Two-Tower モデルでは、損失関数として InfoNCE Loss がよく使われます。

以下の InfoNCE Loss の式は、In-Batch Negative Sampling(「同じミニバッチ内の他アイテムをネガティブとして扱う」設定)を仮定した形になっています。

ここで、

  •  u_i : バッチ内 i 番目のユーザのベクトル
  •  v_i : そのユーザに対する正例(ポジティブ)アイテムのベクトル
  •  v_j : バッチ内に含まれる任意のアイテムのベクトル
  •  sim(⋅,⋅) : コサイン類似度などの類似度関数
  •   τ : 温度パラメータ
  •  B : バッチサイズ

分子は「ユーザ u_iと、そのユーザに対する正例アイテム v_i との類似度」を、

分母は「同じバッチ内に存在するすべてのアイテム v_j との類似度の総和」を表しています。

この式から分かるように、どのサンプルをネガティブとして扱うか は InfoNCE の性能に直結する重要な設計要素であり、これまでに多くの研究が行われています。

本記事では、このうち Two-Tower モデル × In-Batch Negative Sampling という設定にフォーカスして話を進めます。

In-Batch Negative Sampling とは

In-Batch Negative Sampling とは、あるユーザ u_iに対して「同じミニバッチ内の他ユーザの正例アイテム」をネガティブサンプルとして兼用する手法です。

この方法では、ミニバッチ内で既に計算したアイテム埋め込みを負例として流用できるため、負例のために追加でアイテムをサンプルして埋め込み計算(forward)するコストを抑えつつ、多数のネガティブと比較できます。InfoNCEでは一度に複数のサンプルを比較するため、Negative Sampleの計算コストが無視できなくなる傾向にあります。

以下は具体的な手法です。

先ほどの InfoNCE の式の文脈でいうと、ユーザ ui に対して

  • 正例(ポジティブ): v_i
  • ネガティブ: v_j (j≠i)(同一バッチ内のその他のアイテム)

という対応付けになります。(図1参照)

図1.In-Batch Negative Samplingイメージ図。 厳密には、バッチ内の他サンプルが「同一アイテム」または「同一ユーザのペア」である場合、False Negative となるのを避けるために、損失計算から除外(マスキング)する処理を行うのが望ましい

ここからが本題です。

この In-Batch Negative Sampling + InfoNCE の構成は Two-Tower モデルの文脈において非常に一般的な構成ですが、実際のモデル開発においてはいくつか考慮すべき課題があります。

今日は、その課題の一部と対策例を議論していきましょう。

課題1: バッチサイズへの強い依存

InfoNCE Loss には、「相互情報量 I(X;Y) の下限を与える」というよく知られた評価式があります。[1]

ここで N は「1 つの正例と一緒に比較されるサンプルの総数」です。

(* 厳密には独立負例を仮定して導かれる下界であり、in-batch negatives ではその仮定が必ずしも成り立たない点に留意されたい。)

In-Batch Negative Sampling の設定では、1 ユーザあたりの比較対象はほぼ「バッチサイズ B 個」になるため、バッチサイズが小さいと、到達しうる相互情報量の下限も小さくなってしまいます。

その結果、モデルの性能がバッチサイズに強く依存するという問題が生じます。

対策例:

1. MoCo 型のメモリバンク / キュー

この問題への対策一つが、MoCo に代表されるメモリバンク(キュー)方式です[2][3][4]*。

  • 過去バッチのアイテム埋め込みをキューとして保持しておき、
  • 現在バッチのユーザ埋め込みと比較するときに、そのキュー内の多数の埋め込みをネガティブとして利用する

ことで、実効的なネガティブ数 Nを「バッチサイズ」ではなく「キュー長」まで拡張できます。

これにより、大きなミニバッチを組まなくても、多数のネガティブを確保しつつ学習できるようになります。

一方で、実装上のハードルは比較的高く、導入にあたっては慎重に検討する必要があります。

厳密には、最新の MoCo v3 [4] ではモデル構造の単純化(ViTへの適応)のためキュー(Memory Bank)は廃止され、SimCLRと同様の大規模バッチ学習に戻っていますが、同シリーズの重要な発展形として併記しています。*

課題2: 出現頻度に由来するバイアス

In-Batch Negative Sampling は「バッチに出てきたアイテム」をそのまま負例として使うため、バッチに登場しやすい=出現頻度の高いアイテムほどネガティブとして選ばれやすいというバイアスがあります。

その結果、人気アイテムは不当に類似度を押し下げられやすくなります。一方で、低頻度アイテムはそもそもネガティブとして登場しにくいため、学習で受けるノイズ(更新)が弱くなるという問題が生じます。

タイミーの文脈では、単なるアイテムの人気度だけでなく、エリア(地理)や職種によっても出現頻度が大きく異なるため、同様の構造のバイアスがより複雑な形で入り込みます。

対策例:

1. LogQ Correction

In-Batch Negative Sampling では、popularity bias によりスコアがサンプリング確率 q_v(学習時に負例としてサンプルされるアイテムvの周辺確率)に引きずられ、以下の形に収束してしまいます。

ここで r(u, v) は真の関連度を表します。

そこで、ロジットを

としてあらかじめバイアス項を差し引くことで、人気度に依存しない本質的な関連度を学習させます[5]。

2. ランダムサンプリングによるネガティブ補完

In-Batch Negative Sampling だけに依存せず、別途ランダムサンプリングしたネガティブを足す/混ぜることで、人気アイテムだけにネガティブが偏らないようにする、というアプローチもあります。

この対策は比較的実装ハードルが低いです。

課題3: False Negative

In-Batch Negative Sampling では、本来はポジティブであるべきアイテムがネガティブ側に紛れ込む(False Negative) ことが、かなりの確率で起こります。

有効バッチサイズやネガティブ数を増やしていくほどその発生頻度も上がるため、現実的には「避けきれないが、無視もできない」やっかいな問題です。

InfoNCE では、学習が進むと多数の easy negative はほとんど loss(=勾配)に寄与しなくます。そのため、性能を上げるには hard negative をしっかり集めることが重要になります。

しかし、良い hard negative ほどポジティブとの境界付近に位置するため、hard negative を攻めるほど False Negative を混入させやすくなるというトレードオフがあります。

False Negative が多いと、

  • 本来近づけたいユーザ–アイテムのペアを、loss が「離せ」と強く押してしまう
  • 結果として、似たアイテム同士が不自然に遠ざかり、リコールや一般化性能を削ってしまう

といった形で、表現学習全体に悪影響が出ます。

筆者としては、False Negative の発生自体はある程度は仕方ない前提だと考えており、 「False Negative を完全に消しに行く」よりも、それによって勾配が暴れすぎないようにコントロールする仕組みを重視したいスタンスです。

図2. False Negative(=本来はPositiveとすべきSample)とHard Negative/Easy Negativeの位置関係のイメージ。一般的にFalse NegativeとHard Negativeは隣接しており、良質な境界ギリギリのHard Negative得ようとすると、同時にFalse Negativeを取得してしまうリスクが上昇する。

対策例:

1. Debiased InfoNCE (統計的な補正)

「ネガティブサンプルの中には一定確率 π でポジティブが混ざっている」という仮定を置き、Loss の分母からポジティブ成分の期待値を差し引くことでバイアスを除去する手法です [6]。 これにより、FN を無理やり遠ざけようとする誤った勾配の発生を統計的に抑制します。

2. False Negative Masking (高スコアの無視)

学習が進むと、モデルは FN(ラベルは負だが実は正例)に対して高い類似度スコアを出すようになります。 これを利用し、バッチ内のネガティブサンプルのうち、スコアが閾値を超えた(=モデルがポジティブだと確信している)ものを Loss 計算から除外(Masking)します [7]。 「怪しいものは学習に使わない」という割り切りにより、外れ値的な勾配によるモデル崩壊を防ぎます。

一方で、閾値が恣意的になりやすく、この閾値をどのように定義するかに難しさがあります。

おわりに

ここまで、幾つかの課題とその課題に対する対策例を挙げてきましたが、モデルを学習する状況やデータによって、相応しい対策は異なります。手法をきちんと理解した上で運用することで、より良い候補生成用のベクトルを作っていきたいです。

We’re Hiring!

タイミーではデータサイエンティストをはじめ、一緒に働くメンバーを募集しています!

カジュアル面談も行なっておりますので、興味のある方はぜひお気軽にお申し込みください!

product-recruit.timee.co.jp

現在タイミーでは推薦エンジンを一緒に改善していくメンバーを募っております。

参考文献

[1] [1807.03748] Representation Learning with Contrastive Predictive Coding

[2] [1911.05722] Momentum Contrast for Unsupervised Visual Representation Learning

[3] [2003.04297] Improved Baselines with Momentum Contrastive Learning

[4] [2104.02057] An Empirical Study of Training Self-Supervised Vision Transformers

[5] Sampling-Bias-Corrected Neural Modeling for Large Corpus Item Recommendations

[6] [2007.00224] Debiased Contrastive Learning

[7] [2010.08191] RocketQA: An Optimized Training Approach to Dense Passage Retrieval for Open-Domain Question Answering

AIエージェントに本番環境を触れさせないために。Google CloudのPAMで権限を管理する

ogp
こんにちは。データエンジニアリング部 DRE(Data Reliability Engineering)グループのつざき(tsu-san)です。

この記事は「Timee Advent Calendar 2025」シリーズ3の21日目の記事です。

今回はDREグループで導入したGoogle Cloudの PAM(Privileged Access Manager) についてご紹介します。


AIエージェントにGoogle Cloudの「権限」を預ける怖さ

最近DREグループ内では、開発効率を上げるためにローカル実行型AIコーディングエージェント(Claude Code / Cursorなど) を積極的に活用していこうという動きがあります。

ローカル実行型のAIコーディングエージェントは、実行ユーザーのローカル認証情報を使って自律的にコマンドを実行します。つまり、ユーザーが持っている権限の範囲で、AIコーディングエージェントがGoogle Cloudのリソースにアクセスできてしまう状態になります。

人間が毎回チェックすれば良いという話もありますが、AIが生成した複雑なコマンドを完璧にノータイムでチェックし続けるのは、精神的にもかなりの負担です。

「普段は閲覧のみ、必要なときだけ必要な権限を」へ

タイミーのインフラ変更は Terraform + CI/CD が基本であるため、通常時は閲覧以外の権限は不要なはずです。しかし、現実には以下のような「編集権限が必要な瞬間」も存在します。

  • 障害対応時の緊急オペレーション
  • Terraform化されていないリソースの調査や一時的な変更

「AIを安全に使いたい」けれど、「いざという時は動きたい(=権限が必要)」。このニーズを解決するために導入したのが、Google Cloudの PAM(Privileged Access Manager) です。

続きを読む

AI活用したリファクタリングの振り返り

こんにちは!タイミーでバックエンドエンジニアをやっている福島です。この記事は Timee Product Advent Calendar 2025 の20日目の記事です。

みなさんは普段リファクタリングにどれくらい時間を割いていますか?

将来的な品質や開発スピードを担保するためには定期的なリファクタリングが必要ですが、普段の業務が忙しくてなかなか時間を割けない方も多いと思います。

そこで今回は、AIを活用して効率よくリファクタリングできないかを検証してみたので、その振り返りを兼ねてご紹介したいと思います。

リファクタリングの進め方

今回のリファクタリングは、直近取り組んでいた機能開発に関連するコードを対象にしています。

Cursorのチャットスペースを使い、以下の手順で進めました。

  1. リファクタリング候補の洗い出し
  2. リファクタリング箇所の選定
  3. 実装方針を決定
  4. 実装
    1. テストのカバレッジを確認
    2. リファクタリング実装
    3. テストの追加

リファクタリングの実践

先述した進め方に沿って、実行結果をまとめます。

1. リファクタリング候補の洗い出し

AIに以下のプロンプトを投げて、リファクタリング候補を優先度順にまとめてもらいました。

機能Aの中でリファクタリングできそうな箇所、共通化できそうな箇所などを洗い出して、優先度順にまとめてください。

観点に以下を含めてください。
- 問題点
- 改善案
- 期待される効果
- 工数

優先度は以下の順で検討してください。
1. 保守性向上につながるか
2. 対応工数低めで対応できるか

AIの回答(一部抜粋)

### 1. シリアライザー間のXXXロジックの共通化

- 対象ファイル:
    -- 問題点: 同一ロジックが重複
- 対応案: ${クラスA}に${メソッドA}を追加し、シリアライザーから呼び出す
- 効果: 変更箇所が1箇所に集約、保守性向上
- 工数: 1-2時間

...

### 8. 有効期限計算ロジックの集約

- 対象ファイル:
    -- 問題点: 有効期限計算が複数箇所に分散(TODOコメントあり)
- 対応案: ${クラスA}を作成し、計算ロジックを集約
- 効果: 計算ロジックの一元化、テスト容易性向上
- 工数: 3-4時間

...

## 推奨対応順序

1. 優先度:高の1-4を順次対応(合計5-8時間)
2. 優先度:中の5-7を対応(合計4-6時間)
3. 優先度:中-低の8-10は必要に応じて対応

特に優先度:高の1-4は対応工数が少なく、保守性向上の効果が大きいため、優先して対応することを推奨します。

トータルで12箇所ほど挙げてくれました。問題点と改善案も提示してくれたため、調査にかかるコストはかなり削減できたと思います。

2. リファクタリング箇所の選定

リファクタリングの優先度を検討した結果、「8. 有効期限計算ロジックの集約」を最初に着手することを決定しました。

主な観点は以下の通りです。

  • TODOコメントが残り(チーム内で既に問題意識がある)
  • 設計の健全性を大きく損なっている

優先度に関しては、AIと人間の認識にズレが生じる結果になりました。

要因としては以下が考えられます。

  • 対応工数に関する影響が大きくなってしまった
    • AIに実装を任せるので、多少の差分は無視できる前提で指示するべきだった
  • TODOコメントも考慮に加え、顕在的な課題として評価できると良かった

ここは人間の判断基準を詳細に言語化して、プロンプトを磨き込む余地がありそうです。

3. 実装方針を決定

リファクタリング箇所が決まったので、「8. 有効期限計算ロジックの集約」の実装方針についてAIにサンプルコードを出力してもらい、それをレビューする形で方針を固めていきました。

主な観点は以下の通りです。

  • 本当にリファクタリングの価値があるか
    • 保守性向上につながる有効な共通化になっているか
    • 逆にコードの可読性を損なっていないか
  • やりたいことに対して必要最低限の差分になっているか

また、この時点で「本当にリファクタリングの価値があるか」を再度見直すことも重要だと感じました。

例えば「1. シリアライザー間のXXXロジックの共通化」の実装方針を検討する際、いくつかの実装パターンを提示してもらいました。しかし、どれも局所最適な共通化に留まり、長期的な保守性向上にはつながらないことが判明しました。

こういったケースについても、実際のコードを見ながら解像度高く判断することで、無駄な実装を避け、効率的なリファクタリングにつなげることができます。

4. 実装

実装フェーズは3ステップに分けて進めます。

a. テストのカバレッジを確認

リファクタリングは、「ソフトウェアの外部から見たときの動作を変えずに、コードの内部構造を改善する作業」です。外部から見たときの動作が変わらないことを担保するため、まずはテストのカバレッジが十分かどうかをAIにチェックしてもらいます。

AIの回答(一部抜粋)

## テストカバレッジの評価

### 十分にテストされている箇所

1. モデルAのメソッドa: 境界値テストあり
2. モデルBのメソッドa: 複数パターンのテストあり

### テストが不足している箇所

1. モデルAのメソッドb: 直接テストなし(xxx経由で間接的にテスト)
2. モデルBのメソッドb: ユニットテストなし(APIテストのみ)

テストが不足している箇所を挙げてくれましたが、間接的にテストされており、必要なパターンは網羅されていたので、問題なしと判断しました。

b. リファクタリング実装

テストが十分だと確認できたら、安心してリファクタリングを実装できます。

実装方針についても既にすり合わせていたので、ほとんど手戻りなく完了しました。

c. テストの追加

最後に、今回は新規クラスを追加したので、その単体テストを追加してもらいました。

これで実装フェーズはコンプリートです。

結果として、有効期限計算ロジックを1箇所に集約することで、保守性とテスト容易性が向上しました。

クラス 変更前の主な責務(Before) 変更後の主な責務(After) 改善効果
Model A ステータス判定
有効期限の計算・判定
ステータス判定 単一責任の達成。
本質的なロジックに集中。
Model B ログ記録
有効期限の計算・判定
ログ記録 疎結合化。
インフラ層からドメインロジックを分離。
Serializer 表示フォーマット
有効期限の計算・判定
表示フォーマット 表示層の純粋化。
計算ロジックを排除。
Model C(新規) N/A 有効期限計算の集約 ロジックの変更箇所が1箇所に集約。

やってみた感想

調査の工数をほぼ0にできた

  • どこを、なぜリファクタリングするのか
  • 安全にリファクタリングできるか(テストは十分か)

といった点を調査してまとめるのは、本来時間のかかる作業ですが、ここをAIにサクッとやってもらえたのは良かったです。

優先順位は人間の判断が必要

今回、AIが優先度低めに設定していたリファクタリングを最初にやる判断をしました。

先述したように、優先度の判断基準に関する指示は改善の余地がありそうです。

それに加えて、機能開発と並行してリファクタリングを進める場合、「現在進行中の開発とコンフリクトしないか?」という観点も必要になると思います。

リファクタリングの都合で機能開発のスピードを落とすわけにはいかないので、チームの開発事情と照らし合わせて判断できると良いですね。

チームでどう運用していくか

リファクタリングは一度やって終わりではなく、継続してやっていく必要があります。

今回の取り組みを通して、工数をかけず安全にリファクタリングする手順は確認できました。

今後はチームとして安定したコード品質、開発スピードを担保するため、以下の2つに取り組んでいきたいと思います。

  • 技術負債を可視化してチームで共通認識を持てるようにする
  • 機能開発と並行したリファクタリングの実施を、チーム開発のサイクルに落とし込む

さいごに

この記事では、AIを活用したリファクタリングの実践例をご紹介しました。

AIの支援により、調査工数を大幅に削減しながら、安全にリファクタリングを進めることができました。一方で、優先順位の判断やチーム開発との兼ね合いなど、人間の判断が必要な部分も明確になりました。

今後もAIを上手く活用しながら、チーム全体でコード品質を維持・向上させていく取り組みを続けていきたいと思います。

AIを活用した開発改善や、チームでの継続的なコード品質向上に興味がある方、ぜひ一緒にお話ししませんか?

プロダクト採用サイトTOP

カジュアル面談申込はこちら

ダイナミックリチーミングの実践:その確かな有用性と、意外な副産物について

はじめに

こんにちは。タイミーでエンジニアリングマネージャー(EM)をしているshuです。 (本記事はTimee Advent Calendar 2025 シリーズ3の19日目の記事になります。)

最近、社内のEMたちとHeidi Helfand氏の著書『ダイナミックリチーミング(Dynamic Reteaming)』の輪読会を行っています。この本は、従来の「チームは固定化されるべき」という常識を覆し、チーム編成を動的に変えることで組織の健全性と成長を促す手法について書かれた良書です。

読み進める中で、今年自分がアサイン責任者として対応した大規模プロジェクトの記憶が鮮明に蘇ってきました。

「あれ? あの時、必死で判断して実行したあの体制変更、まさにこの本に出てくる『アイソレーションパターン』そのものじゃないか?」と。

今回の記事では、今年発生した緊急プロジェクトでの実践を振り返りつつ、アイソレーションパターンの有効性と、そこで得られた「残された側のチームに起きた意外な副産物」についてシェアしたいと思います。

突発的な大規模プロジェクトと、急造チームの決断

今年、私が担当する領域で、大規模プロジェクトが突発的に立ち上がりました。

その性質は、EMにとっては胃が痛くなるようなものでした。

  • 影響範囲が極めて広範囲に及ぶ
  • 絶対に動かせないハードデッドラインがある
  • 既存チームが片手間でこなせる量ではない

私はこのプロジェクトの立ち上げとチーム組成を担当することになりましたが、プロジェクトの性質に加え、タイミーのシステム特性や現在の開発チーム体制などを総合的に鑑みて、「既存の運用体制のままでは絶対に間に合わない」と判断しました。

そこで断行したのが、あるチームをベースにしつつ、プロジェクトに必要なドメイン知識やケイパビリティを持つメンバーを様々なチームから招集し、専任の「急造チーム」を組成するというリチーミングです。(個人的には、各チームの状況を鑑みながらメンバーを出してもらう、この調整業務が本当に非常に大変でした…)


後から本を読んで整理できたのですが、これは「アイソレーションパターン」と呼ばれる手法そのものでした。

アイソレーションとは、チームを意図的に切り出して分離・隔離し、既存のプロセスやルールから解放された違うやり方で仕事を進める自由を明示的に与えるパターンです。

主な目的:

  1. イノベーション: 新しい大胆なアイデアの追求
  2. 緊急事態: 予期しない問題への集中対処
  3. 停滞の打破: プロセスが重くなった「硬直化の罠」からの脱出
    アイソレーションパターン

このパターンについては、一般的な推奨事項は5つ紹介されています。その中で自分が重要だと考えているのが、以下の3点です。驚いたことに、今回の急造チームは無意識のうちにこれらのセオリーをなぞっており、それぞれがプロジェクトの成功に大きく寄与していました。

1. 専用スペースへの移動

本では物理的な部屋への移動が推奨されていますが、弊社はフルリモート体制のため、これは「専用チャンネルへの集約」として実践されました。

メンバーを既存チームから引き抜くと同時に、専用のSlackチャンネルを作成し、開発に関するすべてのやり取りや意思決定をそこで完結させるようにしました。オンライン上であっても、あたかも隔離された部屋にいるかのような没入環境を作り出すことで、コミュニケーションコストを最小限に抑えました。

2. プロセスの自由

「隔離」されたチームは、既存組織のルールに縛られず、最適なプロセスを選択できます。

タイミーの開発チームの多くは普段スクラム開発を採用していますが、今回のプロジェクトはハードデッドライン必達かつ手戻りが許されない状況でした。そこで、このチームに関しては普段のやり方に固執せず、PjM/PdMによる強力な進行管理のもと、ウォーターフォールに近い開発スタイルを採用しました。

このプロジェクト専用に最適化された独自のリズムで動くことで、迷いなくゴールへ突き進むことができました。

3. 邪魔をさせない

メンバーが目の前の課題に集中できるよう、外部からの干渉を遮断することも重要です。

これについては、プロダクト組織のエグゼクティブ(CXOやVPoXなど)から、このプロジェクトが全イニシアチブの中で最優先事項であると定義・宣言してもらい、全プロダクトメンバーにその認識を持ってもらうことで実現しました。

トップダウンによる強力なバックアップがあったおかげで、他の運用業務や開発などの差し込みが入ることがなく、メンバーは余計なノイズに惑わされることなく開発に集中することができました。


当時は「間に合わせるためにはこれしかない」という必死の判断でしたが、結果としてアイソレーションパターンは極めて有効に機能し、厳しいデッドラインを守り切ることができました。

そして、このリチーミングにはもう一つ、私にとって嬉しい誤算とも呼べるポジティブな副産物がありました。

本には書かれていない「既存チーム」の進化

それは、EMとして最も懸念していた人を引き抜かれた側の状態についてです。

今回のケースでは、複数のチームからドメイン知識を持つメンバーが招集されました。私が管掌するチームからも、フロントエンド領域を専門とするメンバーが1人、プロジェクトにアサインされました。

専門領域を持つメンバーが抜けることで、「残されたチームでの開発は立ち行かなくなるのではないか?」という懸念がありました。

しかし、あくまで私の管掌範囲内での観測にはなりますが、蓋を開けてみると、そこには本には書かれていない意外な化学反応が起きていました。

1. 空白がオーナーシップを醸成

人が減ったことで、物理的にリソースが不足します。すると不思議なことに、「誰かが拾ってくれるだろう」という無意識の前提がなくなりました。

「自分が拾わないと落ちる」

そんな健全な危機感が生まれたように感じます。これはまさに、タイミーが大切にしている新しいバリューの1つ「ジブンゴト」が、極限状態で体現されたと思います。誰かに言われてやるのではなく、構造的な空白が、彼らのオーナーシップを引き出しました。

2. 越境文化がより強まった

ここ1年半ほど、タイミーのSA(StreamAligned)チーム(※顧客価値に向き合う最小単位の開発チーム)では、領域を越境した開発が推奨されており、私自身もチームにそれを求めていました。特に今年は、AIコーディングツールの進化もあり、越境のハードル自体は下がっていました。

今回、フロントエンドの専門メンバーが抜けましたが、半分は意図的に、半分は状況的に、あえて即座にそのケイパビリティを持つバックフィルを用意しませんでした(もちろんうまくいかなかった時にサポートできるオプションは持ちつつ)。代わりに、メンバーにはこれを機に越境してほしいと伝えました。

その結果、移動したメンバーやFrontend Chapter(※職能ごとの横断コミュニティ)の力も借りながらではありますが、チームメンバー全体でフロントエンド領域のタスクを巻き取る動きが加速しました。リソースが足りないという状況が、結果としてチーム全体のフルスタック化を、実践レベルで前進させました。

3. MTGの発言量が爆増した

人数が多いMTGだと、どうしても一人ひとりの発言時間は少なくなってしまいがちです。

今回、リチーミングによりチーム人数が減ったことで、全員がより当事者としてスプリントゴールを目指す状況となりました。その結果、ほぼすべてのMTGでマイクをミュートにすることがなくなるほど、一人ひとりの発言量と議論の密度が劇的に向上しました。


まとめ:変化こそが、組織を強くする

今回の経験を通じて、アイソレーションパターンは単なる緊急時の対症療法ではないと痛感しました。

それは、重要なプロジェクトを成功させるための強力な武器であると同時に、既存チームに健全な揺らぎを与え、メンバーの新たな可能性を強制的に(しかしポジティブに)開花させるきっかけでもありました。

人が動くことで、組織全体の成果の総和は減るどころか、むしろ増える。

誰かが抜けた穴は、決してネガティブな空白ではなく、他の誰かが一歩前に踏み出すための「成長の余白」だったのだと思います。

本で理論を学び、現場で実践し、予想以上の成果を得る。EMとして、非常に学びの深い1年となりました。 組織の変化を恐れず、来年もダイナミックに挑戦していきたいと思います。

本質的な課題解決とメンバーの成長を両立させるダイナミックな組織運営に興味がある方、変化を楽しみながら成長したいと思っている方、ご興味があればぜひお話ししましょう!

プロダクト採用サイトTOP

カジュアル面談申込はこちら

iOSエンジニアがAIと協調してAndroidプロジェクトにコミットしてみた

1. はじめに

本記事は Timee Product Advent Calendar 2025 シリーズ1 19日目の記事です。

こんにちは。株式会社タイミーでiOSエンジニアをしている hayakawa です。 普段はiOSアプリの開発を担当していますが、弊社では職種の垣根を超えて異なる技術領域に挑戦する「越境」が盛んです。また、開発プロセス全体でAI・LLMを活用する流れも加速しています。

今回は、自身の技術領域を広げるためと、チームのケイパビリティ向上のために、Androidの実装を担当しました。本記事では、その際の実践的な知見やAI活用のポイントについて共有します。

2. 筆者のAndroidに関する前提知識

実装開始時点での私のAndroidに関する知識レベルは以下の通りです。

  • 言語: Kotlin(Swiftと似ているという認識程度)
  • UI: Jetpack Compose(SwiftUIに近い宣言的UIフレームワークという認識)。また、従来はXML(iOSのXIBやStoryboardのようなもの)を使って構築する手法があることも、知識としては持っている。
  • 非同期処理: Coroutines(CombineやSwift Concurrencyの概念で理解)
  • 開発環境: Android Studio(基本的な操作方法も不慣れな状態)

「概念は理解しているが、具体的なAPIやIDEの操作は手探り」という状態からのスタートでした。

3. 今回挑戦した実装内容

今回担当したのは、以下の2つの機能実装です。

① 条件に応じた注意書き表示ロジック サーバーレスポンスに含まれる「未来の時間情報」に基づき、UIを出し分ける機能です。

  • 現在時刻から指定時間までの「残り時間」をカウントダウン表示する。
  • 端末の「通知設定(ON/OFF)」を取得し、文言を切り替える。

② 条件に応じたUI制御とデザインシステムの拡張 サーバーレスポンスに含まれる特定の値に基づき、View内のコンポーネントを制御する実装です。

  • デザインシステムに新たなカラーバリエーションを追加定義する。
  • 既存のリスト表示(RecyclerView)の一部に、Jetpack Compose化したViewを組み込む。
  • 組み込んだView内に追加のコンポーネントを表示する。

なお、同様の機能をiOS版でも私が担当しており、そちらは「実装1日、レビュー・マージまで2日」で完了しています。iOS版の実装時点では、Android版はキャッチアップも含めてその1.5倍ほどの工数で完了すると予想していました。

4. AI活用の勘所:メンタルモデルに合わせた翻訳

実装は Claude Code と協調して進めました。 ここで効果的だったのは、単にコードを書かせるのではなく、「自分の持っているiOSの知識とマッピングさせる」というプロンプトの出し方です。

プロンプトの工夫:「iOSエンジニア向けに説明して」

具体的には、以下のような指示を行いました。(※例なので実際に入力したプロンプトとは異なります)

Prompt: 「{SwiftのAPI名}を使って {やりたいこと} を実装したい。これを {KotlinのAPI名} を使ったコードと、Swiftの概念と比較した実装方針とコードを提示して

このように依頼することで、AIは「StateFlowはSwiftでいうCurrentValueSubjectに近い挙動です」といった補足を加えてくれます。これにより、単なるコピペではなく、挙動を正しく理解しながら実装を進めることができました。

5. ぶつかった技術的・文脈的な壁

AIの支援があっても、スムーズにいかない場面がいくつかありました。

① RecyclerViewとComposeの共存

最も苦戦したのは、既存のRecyclerViewの中にJetpack Composeで作ったViewを組み込む部分です。 SwiftUIであれば UIHostingController 等で比較的直感的にブリッジできますが、Androidの既存実装(ViewHolder)の中に、ComposeViewをライフサイクル的にどのように正しく配置するか 、という点は構文の違い以上に「作法」の違いが大きく、AIの出力したコードをそのまま適用するだけでは、ビルドエラーやレイアウト崩れが発生しました。

② モデル名の不一致(ドメイン知識の欠如)

また、プロジェクト固有の「命名規則」の壁もありました。 例えば、iOSで定義されているモデル名が、Androidでは少し違う名前になっているケースがありました。

例)

  • iOS: ServiceRequest
  • Android: RequestedService

私がiOSの感覚で「ServiceRequestを拡張して」とAIに指示すると、AIはプロジェクト内に RequestedService が存在することを知らないため、新しく data class ServiceRequest を定義してしまいました。 これは「AIはプロジェクトの歴史や文脈までは(コンテキストに含めない限り)知らない」という典型的な落とし穴でした。

6. 人間(Androidエンジニア)との協調

AIが出力したコードは「動く」ものの、それが「保守性の高いコード」であるかは別問題です。 そこで、Androidエンジニアのチームメンバーに対して、「同期的な相談」の時間を設けてレビューを依頼しました。 この時、私が意識して聞いたのは「合っていますか?」ではなく、以下の質問です。

「期待通りに動くんですが、Androidの流儀としてもっと良い書き方はありますか?」

AIは汎用的な正解を出しますが、現場のベストプラクティス(例えば、よりモダンなライブラリの選定や、プロジェクト独自の拡張関数の活用など)は、人間の方が詳しいケースが多々あります。このプロセスを経ることで、コードの品質を担保しました。

7. まとめ

今回の挑戦で得られた知見は以下の通りです。

  • AIは「言語の壁」を限りなく低くする Swiftの知識があれば、適切なプロンプトでKotlinのコードを生成・理解することは容易です。
  • ドメイン知識とアーキテクチャの理解は人間が補う必要がある モデル名の違いや、既存コンポーネント(RecyclerView等)との整合性は、AI任せにせず人間がハンドリングする必要があります。
  • 「もっと良い書き方」は人間に聞く AIで「0→80点」まで持っていき、最後の「80→100点(最適化)」を専門家との対話で行うスタイルが非常に効率的でした。

AIという強力なパートナーがいれば、iOSエンジニアにとってAndroid開発(あるいはその逆)は、もはや高いハードルではありません。今後も積極的に技術領域を越境していきたいと思います。


タイミーではiOSエンジニアやその他のポジションを含めて採用活動中です!

プロダクト採用サイトTOP

カジュアル面談申込はこちら