開発者向け新卒研修のチーム分けをアルゴリズムで最適化する取り組みを始めた話

組み合わせ最適化チャレンジ

こんにちは。
開発者向け新卒技術研修の舞台裏を書いた3月の時点では、研修の準備中だったわけですが、いよいよゴールデンウィークが明ける5/6(木)からいわゆる開発職に特化した新卒技術研修「GMOテクノロジーブートキャンプ(以降GTB)」が始まります。
研修の設計者は1度は悩んだことがあるであろうチーム分け。スキルセットや経験値、レベル感を鑑みて「いい感じにチーム分け」をしたい訳ですが、なかなか難しいという声が多いのではないでしょうか
本エントリーでは、チーム分け手法や設計方法についてご紹介いたします。

そもそもなぜチーム分けするのか?

まず前提としてインプットをした分だけ、アウトプットをしていただきたいという思いがあります。本研修においても最初は座学、徐々にワークショップで手を動かし、最後に身についたものを成果として発表するための開発ワークに取り組んでいただいています。

この一連の流れをチーム単位で行い、みんなでゴールに向かっていきます。例年はプログラミング経験年数のみでソートし、チームバランスの設計を行っていました。このやり方は過去7年間行ってきた手法で一定の成果を上げていましたが、技術やスキルの細分化が進んでいることを踏まえ、もっときめ細やかでデータドリブン化されたチーム分けをしたく、今回の取り組みを行いました。

アルゴリズムを用いたチーム分け

昨年のGTB卒業生で、今年から運営チームに参加したエンジニア(競技プログラマ)の提案で、チーム分けを最適化問題に落とし込み、焼きなまし法で最適なチームを探索しました。

詳しい解説は提案、実装してくれたチームメンバーの記事を御覧ください。
【競プロ×実務】pythonで簡単チーム分け【焼きなまし法】

下準備として行ったこと

  • 新卒の皆さんにスキルセットについてアンケートを実施
  • 上記アンケート結果をパラメーター化

下準備したデータを元に最適解なチーム分けがなされるようにチューニングなどを実行していきます。

  • 現在の状態(=今回はチームの分け方)を、少しだけ変えてみる
  • スコアがよい方向に変化するかをウォッチ
  • 最初の方はスコアが下がっても検証を継続することで多様性を持たせる

上記を繰り返しながら最適解、今回でいえば平均的なスキルレベルでチーム分けができることを目指しチーム分けアルゴリズムが実行されます。

実際に出力されたチーム分けデータを一部抜粋

チューニングしたポイント@目的関数

上記の表はいくつか出力されたチーム分けデータの一つから4チーム分だけを抜粋したものです。ここに至るまで目的関数などいくつかチューニングを行っています。
⽬的関数とは、組み合わせ最適化問題において現在どういう状態なのかを評価するものです。今回は目的関数を”現在のチーム分けに対する、チーム間でのレベルの偏り具合”を表すよう定義します。

この目的関数の出力を減少させるようチーム分けの状態を変化させることで、公平なチーム分けとなることを目指します。

設計で気を付けること

目的関数の設計において重要なのは、何を偏りとみなして、何を偏りとみなさないかはすべて相対的な関係にあるということです。

たとえば、【A】【B】2つのチームをバックエンド、フロントエンド、デザインの3つのレベルがそれぞれ同じくらいになるように分けたいとします。
このとき、すべてのレベルを公平に分けたいという気持ちで以下のように各レベルの差に10をかけて重み付けしたものを目的関数とするとします。

目的関数の定義_改善前

◆目的関数(偏り具合)=
バックエンドレベル差*10
+フロントエンドのレベル差*10
+デザインレベル差*10

このような目的関数を使って偏り具合を比較しても各レベルの重みが同じなので、結局重み付けがされていない、すなわち優先順位がついていないことと同義になってしまいます。
今回のGTBの研修を例に出すと、デザイナーは人数が少ないため優先的に分散させたいです。そこで以下のような重み付けを考えてみます。

目的関数の定義_改善後

◆目的関数(偏り具合)=
バックエンドレベル差
+フロントエンドのレベル差
+デザインレベル差*10

たとえば、バックエンドレベルの差は5増えるが、デザインレベルの差が2減るようチーム分けを変化させる場合、改善前の⽬的関数では偏りが30増加して改悪となりますが、改善後の⽬的関数では偏りが15減少し改善となります。

つまり、バックエンドのレベル差が許容され、デザインレベルの差を⼩さくすることがより重要視されたのです。
このように、⽬的関数ではどこを重み付けし、どこをしないのかを考えることが重要となります。

実際に用いた目的関数

今回実際に用いた目的関数は以下です。

目的関数(偏り具合)= log1p(各チームのバックエンドレベルの合計値の集合の最大値) - log1p(各チームのバックエンドレベルの合計値の集合の最小値) 
+ log1p(各チームのフロントエンドレベルの合計値の集合の最大値) - log1p(各チームのフロントエンドレベルの合計値の集合の最小値)
+ log1p(各チームのデザインレベルの合計値の集合の最大値) - log1p(各チームのデザインレベルの合計値の集合の最小値)
+ log1p(各チームの全レベルの合計値の集合の最大値) - log1p(各チームの全レベルの合計値の集合の最小値)

y=log1p(x)はy=log(1+x)と同じです。真数が0になることを防ぐため、1を足しています。グラフにすると以下のような形状です。xが増加するにしたがって、yの増加量が緩やかになっていきます。xが⼩さいうちの⽅がyに与える影響が⼤きいのです。

これを上記の式にあてはめると、レベルの合計値が低いチームがあるとそれ以上の勢いで偏りが大きくなります。
言い換えると、どのチームもレベルの合計値が⼀定以上あれば偏りは⼩さくなるということになります。

つまり、「レベルの合計値が極端に低いチームが生じてしまうことを避ける」ということに重点を置くことで、最終的にはいい感じに分散する(各レベルの合計の最大値と最小値が狭まる)ことを狙っています。

実行結果

実行はローカル環境で10^6回の試⾏で約30秒かかりました。チーム内のレベルの分散や、チーム間のレベルの合計の分散を毎回計算するよりはずっと効率的で⾼速な出⼒ができました。

具体的なチーム分けの様子はページ冒頭の画像を参照いただきたいですが、各チームのレベルの合計の最大値、最小値はこのようになりました。フロント、デザインの元々の偏りを鑑みるとうまく分散していると言えそうです。しかし本当にバランスが良いチームになっているかどうかは研修が終わるまでわかりません。初めての試みですが、改善を継続してより価値の高い研修にできればと考えています。ここについてはGTBが終了した後の振り返り記事などでご紹介が出来たらと思います。

さいごに

このイメージグラフィックは、昨年開催のGTBクリエイティブ専攻の講義のなかでアウトプットされた優秀作品です。本年度の資料扉絵などGTBのマテリアルパーツとして利用させてもらっています。

新卒の皆さんにとって、本研修での学びが今後の社会人生活における礎となることを願っています。

さいごまでお読みいただきありがとうございました。

ブログの著者欄

稲守 貴久

GMOインターネット株式会社

採用情報

関連記事