Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

2023年 研究開発部 新卒技術研修 ~ 実践編 ~

こんにちは、研究開発部 Architectグループの藤岡です。 4/26(水)〜 4/28(金)で研究開発部内の技術研修を行ったので、その内容を公開します。

目次

研修の目的

この研修の主な目的は、新卒社員がスムーズに業務に入れるようにすることです。

研究開発部にはさまざまなバックグラウンドを持つ研究員が入社するため、チーム開発の経験がない方もいます。 そのため、Gitの操作やプルリクエストの出し方など、チーム開発における基本的なところからしっかりとカバーすることで、チームに配属されてからスムーズに開発に加わってもらえるように研修を実施しています。

研修の概要

本研修は座学パートと実践パートに分かれており、座学パートでは以下のような内容の講義を行いました。

  • Git, GitHubの使い方
    • Git操作の基本
    • プルリクエストの出し方、お作法など
    • コードレビューのやり方
  • テストコードの書き方
    • テストの重要性について
    • Pythonでテストを書く際の便利ツールについて(Parametrize, Fixture, Mock ...)
    • VSCodeでテストを実行する方法
  • VSCodeの設定について
    • VSCodeでssh接続する方法
    • 入れておきたい拡張機能(Python, isort, Git Graph, ...)
  • 機密情報の取り扱いに配慮した開発手法について
  • 社内のアプリケーション基盤(Circuit)についての説明

GitHubやテストに関する資料は、後日公開予定です。

これらの講義を聞いてもらった後に、実践編としてアプリ開発からリリースまで一通り体験してもらう研修を行いました。 今回はこちらの実践編の内容について詳しく説明していこうと思います。

実践編の概要

実践編では、Webアプリケーションを作ってもらい、それを社内のアプリケーション基盤にデプロイするまでをやってもらいました。 どのようなアプリを作ってもらったかというと、Sansan社員の名刺交換履歴から共起ネットワークを作り、類似人物を推薦するWebアプリケーションです。 このアプリを構成する要素は3つあって、

  1. データソースからデータを取得し、モデルの学習 & 予測を行うバッチ
  2. バッチが出力したデータを提供するAPI
  3. APIから類似人物データを取得し、それを表示するWebアプリ

から成ります。これらは全てPythonで書かれており、それぞれ 1. gokart、2. FastAPI、3. Streamlit が使われています(それぞれどういったライブラリなのかは後述します)。これらの技術は部として標準化しているため、配属前のタイミングで一通り触ってもらうことで慣れてもらう目的も兼ねています。

アプリケーションの完成形はこのようになります(表示されているデータはダミーデータです)。

アプリケーションの完成形

アプリケーションを作成

それでは、1つずつ詳しくみていきましょう。

バッチを作成

gokartとは

gokart は、 M3 が開発するパイプラインライブラリであり、機械学習に特化するように Luigi をラップしたライブラリです。社内では多くのバッチで使われています。

下記はgokartのコード例です。

import gokart
import pandas as pd

class SampleTask(gokart.TaskOnKart):
    data_task = gokart.TaskInstanceParameter()

    def requires(self):
        return dict(data=self.data_task)

    def run(self):
        df = self.load("data")
        self.dump(df)

gokartでは処理を Task という単位で管理しています。 Taskの実行はgokartの スケジューラ が管理し、gokart.runもしくは、gokart.buildにより実行します。 また、requiresメソッドで前段に実行を担保したいTaskを定義し、runメソッドで本筋の処理を行うことができます。

Taskのrunメソッドでdumpしたデータは個別のハッシュ値を割り当てられ、pklファイルとして resources 配下に出力されます(出力先は変更可能です。gokartはS3やGCSもサポートしています)。この機能のおかげで実験の中間データがキャッシュされ、同じ処理を何度も実行する必要がなくなり、またデータを意識的に管理する必要がなくなります。 luigiでは output メソッドにより出力を記述する必要がありますが、gokartでは dump メソッドにより出力の記述の簡略化を実現しています。

上記のTaskは、Pythonのパッケージマネージャである Poetry を使って以下のコマンドで実行できます。

$ poetry run python pipeline/main.py pipeline.SampleTask --local-scheduler

gokartを利用するメリットは

  • 処理の依存関係を把握しやすくなる
  • チームメンバー間での処理・タスクの共通化
  • コードレビューコストの削減

などが挙げられます。詳しくは以下の記事を参照してください。

buildersbox.corp-sansan.com

パイプラインを実装

まずはR&D独自で用意している Cookiecutterというテンプレート生成ツールでプロジェクトの雛形を作成します。 詳しくは以下の記事を参照してください。

buildersbox.corp-sansan.com

自動生成されたテンプレートを修正していき、以下のような処理を実現するパイプラインをつくっていきます。

  1. Athena からSansan社員の名刺交換履歴を取得する
  2. 名刺交換履歴から共起行列を作る
  3. 共起行列からエッジリストを作る
  4. Node Embeddingモデルの学習をする
  5. モデルから類似人物を計算する
  6. 予測結果をS3にアップロードする

完成形のコードは以下です。

randd-engineering-training/python_training/batch at master · sansan-inc/randd-engineering-training · GitHub

また、Athena や S3 など、AWSのサービスに親しみのない方もいるので、実際にAWSマネジメントコンソールにログインしてサービスを触ってもらう体験もしてもらいました。

さらに、開発サイクルを学ぶことを目的としているので、最後まで完了したらプルリクをつくってコードレビュー依頼をしてもらい、研修の運営側に approve されたらマージする方針で進めました。 できるだけ実際のチーム開発と近い形で研修を進めることで、Git操作に慣れてもらったり、分かりやすいプルリクの出し方を学んでもらうことを目的としています。

APIを作成

FastAPI とは

FastAPIの特徴は

  • 簡単に利用、習得できるようにデザインされている
  • パフォーマンスが高い
  • 高速なコーディングが可能
  • デフォルトでAPIドキュメントを出力できる
  • 機械学習との相性が抜群

などが挙げられます(参考:FastAPI)。

R&Dでは機械学習のエコシステムが充実していることからPythonが選択されることが多く、その影響でAPIを立てる際もPythonフレームワークである FastAPI が選ばれています。

APIを実装

こちらもgokart同様、Cookiecutterのテンプレートが用意されているのでそれを使用します。テンプレートを修正していき、最終的には以下の2つのエンドポイントを作ります。

  • /persons
    • 類似人物が計算された人物の一覧を返す
  • /persons/{person_name}
    • person_name の類似人物を最大10人返す

完成形のコードは以下です。

randd-engineering-training/python_training/api at master · sansan-inc/randd-engineering-training · GitHub

ディレクトリ構成

APIのディレクトリ構成は以下のようになっています。

.
├── Dockerfile
├── README.md
├── app
│   ├── __init__.py
│   ├── config.py
│   ├── container.py
│   ├── gunicorn.conf.py
│   ├── log.py
│   ├── logging_context.py
│   ├── main.py
│   ├── models
│   │   └── __init__.py
│   ├── repositories
│   │   ├── __init__.py
│   │   └── batch_result_repository.py
│   ├── routers
│   │   ├── __init__.py
│   │   ├── health.py
│   │   └── person.py
│   ├── schemas
│   │   ├── __init__.py
│   │   └── person.py
│   └── services
│       ├── __init__.py
│       └── batch_result_service.py
├── pyproject.toml
└── tests
    ├── __init__.py
    └── unit_test
        ├── __init__.py
        └── test_sample.py

ここではRepositoryパターンを採用しており、以下のような役割でモジュールを分割しています。

  • Model:エンタープライズビジネスルール担当
    • システムに全く関係なく存在している業務上のビジネスルール
    • 例:サッカーで言うと「2回イエローカードが出たら退場」、「1回レッドカードが出たら退場」「出場できる選手は11人(例外:レッドーカードで1人退場してしまった場合は10人)」みたいなコアなルールの部分
  • Service:アプリケーションビジネスルール担当
    • システムであるがゆえに発生するビジネスルール
    • 例:Viewから送られてきた値をDBに保存できるようにデータを加工したりとか、逆にDBから取得してきた値をViewに返しやすいようにデータを加工したりとか
  • Repository:DB通信・API通信担当

参考:https://zenn.dev/naoki_oshiumi/articles/0467a0ecf4d56a

また、DIコンテナや構造化ログも導入されており、実践的な内容となっているので是非確認してみてください。

実行

上記のコードを以下のコマンドで立ち上げ、

poetry run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

ブラウザで http://localhost:8000/docs#/ にアクセスするとAPIドキュメントが表示されます。

自動生成されたAPIドキュメント

ここから実際にリクエストを投げることもできるので、実際に触ってもらうことでイメージを掴んでもらいました。

リクエストを投げることもできる

Webアプリを作成

Streamlitとは

StreamlitはPythonでさくっとUIを作ることができるライブラリです。R&Dでは実際に Sansan Labs という実験的な機能を提供するサービスで使われています。その他にも Google X や Yelp、Uber などでも使われているようです

Webアプリを実装

まずはUIの骨格部分をつくります。

  • ページのタイトルがある
  • サイドバーとメインカラムがある
  • サイドバー内に、類似人物を調べたい人を選択するselectboxがある
  • selectboxで1人を選んだ後に検索ボタンを押す
  • 検索ボタンが押されたら、メインカラムに人間関係ネットワークを描画する

これらの要件を満たすアプリは以下の短いコードで実現できます。

import streamlit as st
from streamlit_agraph import Config, Edge, Node, agraph


# タイトル
st.title("類似人物検索")

# サイドバー
selected_person_name = st.sidebar.selectbox(
    "1人選んでください",
    ("sato", "suzuki", "tanaka")
)

# 検索ボタン
search_button = st.sidebar.button("検索")

# 検索ボタンが押された場合
if search_button and selected_person_name:
    # 描画するNodeの登録
    nodes = [Node(id="sato"), Node(id="suzuki"), Node(id="tanaka")]

    # 描画するEdgeの登録
    edges = [
        Edge(source="sato", target="suzuki"),
        Edge(source="sato", target="tanaka"),
    ]

    # 描画の設定
    config = Config(
        directed=False,
        physics=True,
    )

    # 描画
    agraph(nodes, edges, config)

このコードを以下のコマンドで実行し、 http://localhost:8080/ にアクセスするとアプリが表示されます。

$ poetry run streamlit run main.py

Webアプリケーション

あとは先ほど作ったAPIからデータを取得し、それを表示するように修正すれば完成です!

randd-engineering-training/python_training/app at master · sansan-inc/randd-engineering-training · GitHub

Docker化

アプリケーションをデプロイするために、Dockerイメージを作る必要があるのですが、Dockerに馴染みのない方もいるので、ハンズオン形式で Dockerイメージを作ってもらいました。 具体的には、ローカルで Dockerを立ち上げ、コンテナ内でコマンドを実行し、問題なく動作すれば Dockerfile に追記していく手法で実施しました。

最終的なDockerfileは以下になります。

randd-engineering-training/Dockerfile at master · sansan-inc/randd-engineering-training · GitHub

デプロイ

ECRにイメージをプッシュ

まずは作成したDockerイメージをECRにプッシュします。今回は事前に用意されたGitHub Actionsを使ってデプロイしてもらいました。実際のワークフローはこのようになっています。

name: "ECR Image Update"

on:
  workflow_dispatch:
    inputs:
      app_type:
        type: choice
        description: "Application type"
        required: true
        options:
          - app
          - api
          - batch

jobs:
  ecr-image-update:
    uses: <社内の共通Actionsを管理するリポジトリ>/.github/workflows/ecr-image-update.yml@v1
    with:
      ecr_repository_name: engineering-training-2023/${{ inputs.app_type }}
      working_dir: python_training/${{ inputs.app_type }}
      environment: development

on: workflow_dispatch を指定すると、以下のように手動でトリガーされるワークフローを作ることができます。ここでデプロイしたいブランチと、アプリケーションタイプ(app, api, batch)を選択して実行するだけでイメージのデプロイが完了します。

手動でトリガーされるワークフロー

このように簡潔なyamlファイルでワークフローが作れているのはなぜかというと、以下の箇所で reusable workflows を使っているからです。

    uses: <社内の共通Actionsを管理するリポジトリ>/.github/workflows/ecr-image-update.yml@v1

reusable workflows とは、名前の通りワークフローの再利用ができるもので、ここでは他のリポジトリのワークフローを指定しています。

2022年12月にprivateリポジトリのワークフローも再利用可能になったので、R&Dでは共通化したいActionsを1つのリポジトリで管理する運用にしています。ECRにpushするActionsだけでなく、PythonのlinterなどのCIツールも共通化しています。

github.blog

アプリケーション基盤 Circuitについて

最後に、社内のアプリケーション基盤にデプロイしていきます。R&Dでは Circuit という名前のKubernetes基盤を運用しており、研究員だけでも簡単にデプロイできるような仕組みが整っています。詳しくは以下の記事を参照してください。

buildersbox.corp-sansan.com

また、Circuit研修の際に使用した資料を、同じ研究開発部 Architectグループの 新井 が公開しています。こちらも是非ご覧ください!

speakerdeck.com

アプリのマニフェストを作成

以下のようなマニフェストを書くことで、Circuitにバッチ, API, Webアプリ用のKubernetesリソースが作成されます。

randd-engineering-training/kubernetes_manifests at master · sansan-inc/randd-engineering-training · GitHub

実装の詳細な説明は省きますが、 Kustomize というマニフェスト管理ツールを使い、全環境共通の設定はbaseディレクトリ配下に記述し、環境ごとに異なる設定をoverlaysディレクトリ配下に記述するルールとなっています。

CircuitのCDは、ArgoCDを採用してGitOpsで行っており、mainブランチにマージするとデプロイ完了となります。 今回の研修ではmainにマージしてアプリケーションをデプロイするまでをやってもらいました。

研修終了後

研修終了後には、この研修を受けてくれた新卒社員のメンバーにアンケートを取り、それをもとに振り返りをKPTで行いました。 よかった点としては、

  • 対面でのサポートが手厚く、分からないことをすぐに聞ける環境を整えられた。
  • Gitを触ってみたり、プルリクを実際に出してもらったことで、開発サイクルに慣れてもらうことができた。
  • リリースまでの全体的なイメージを掴んでもらうことができた。
  • 実際の業務で参考にできるような、実践的な資料をつくることができた。

などが挙げられ、改善点としては

  • 当初予定していた3日間だと、最後まで終わらせるのはスケジュール的に厳しかった。
  • 終わらせなければならないという焦りから、色々と試してもらう余裕がなかった。
  • リモートで作業してもらうときに、いきなりSlackでメンションして質問するのは若干ハードルが高い。

などが挙げられました。これらの反省点を踏まえ、次回以降は以下のような点を取り入れて改善していこうと思います。

  • プルリクを作る時間も踏まえ、もう少し余裕を持ったスケジュールにする。
  • リモートで研修を受けてもらう際に、もっと質問しやすい環境を整える。

終わりに

今回の研修を通して、チーム開発に慣れてもらうという大きな目標は達成できたのではないかと思います。 実際に研修の初期に比べて、最後の方は非常に丁寧でわかりやすいプルリクを出してくれることが多くなったので、研修を実施した成果があったのではないかと思います。

私たちは新たに弊社に入社してくださる皆さんに、実力を十分に発揮して活躍していただきたいと思い、こういった研修を実施しておりますので、もしご興味のある方はどしどしご応募ください。

media.sansan-engineering.com

© Sansan, Inc.