転職してデータサイエンティストからデータエンジニアになりました

前回の転職(ゲームプログラマからデータサイエンティストに転職しました - ぴよぴよ.py)から約1年半、再び転職しました。

ゲームプログラマ (2年)→ データサイエンティスト(1年)→ データエンジニア(new)

という感じのキャリアです。

前職の話

前職は職種的には「データサイエンティスト」をしていました。
受託の分析会社でお客さん先にいってミーティングしたり、分析したりする感じです。

いろんな案件を2並列くらいで数ヶ月くらいずつ参加し、テキスト情報の分類・タグ付け/DMや施策の最適化/DMP構築 などに関わりました。

私自身は基本的にアルゴリズムを考えたりツールを作ったりするのが好きなんですが、 データサイエンティストとして働いてみて、ゲームプログラマよりはデータサイエンティストの仕事のほうが楽しいなーと感じました。 競プロで培ったアルゴリズムが使えることも多々あって、嬉しかったです。

ただ、エンジニアリングしか学んでいなかったので、 転職当初はビジネス視点・社内/客前でのプレゼン力の無さには絶望しました。 限られた時間の中で、顧客が求めるものと乖離をしないものを作っていくためにはどうしたらいいのか、 というのを何度も何度も悩みました。

慣れない仕事も多かったですが、同年代のメンバーが多くたくさん友達ができて楽しかったです。

最後の方はマネージャとしてメンバーのアサインを決めたりなんかもしていて、マネジメントの難しさを少しだけ体感することができました。
しばらくはマネジメント側の仕事をする機会はなさそうだけど、次はメンバーとしてマネジメント側の立場も考えた上での振る舞いをしていけたらと思っています。

転職活動の話

仕事内容に関してはとても恵まれていたため、もう少し頑張りたい気持ちはありましたが、 今回は会社の方針と自分の考えの不一致で転職を決めました。

「行きたい会社がある」というベースの転職ではなかったので、 とにかく色んな会社の話を聞いて、面白そうなところをうけてみようという受け身なスタンスでした。

転職エージェントを使わなかったこともあり、

  • 自分に向いてる会社を自分で探す必要があるが、知らない会社にリーチしづらい
  • なかなか返信をいただけない企業に対して自分でステータス確認をする必要がある

という点は仕事しながら転職活動をする上で特に大変でした。

まずは受けてみたいと思えるポジションを探すため、自分の中の希望をとにかく書き出しました。
それから、それらの希望を

  • 本当に求めているもの
  • ただのわがまま
  • 我慢できること
  • やりたくないこと

に分類し、自分がどうしたいのか、ゆっくり向かい合いました。

その上で、

  • (MUST) アルゴリズムや実装が好き(&ちょっと得意)な自分を必要としてくれる
  • (MUST) フィーリングで自分がわくわくできるような会社
  • (BETTER) すでに得意なPythonもしくは(自分が書いてて楽しい)静的型付言語を利用できる

を満たす中で一番自分を高く買ってくれる会社に行きたいという方針に決めました。
かなりふわっとした要望なので、自分でポジションを探すのは難しく、 自分の性質をよく知っている友人たちに面白いポジションははないかと聞いて回りました。

10人ほどのお話を聞いた上で、 会社と内容にワクワクした以下の3つのお仕事のポジションを受けさせていただきました。

  1. ゴリゴリにサービス実装するお仕事(C++)
  2. 分析部門の1人目としてそこそこ大きい分析システムを構築するお仕事 (Python)
  3. AIをサービスに導入済みの会社で分析基盤構築(ミドルウェア開発)&分析をするお仕事 (Go, Python)

上記3つは仕事内容としてはそれぞれ全然違うため、 選んだポジションによって今後の自分の人生も結構かわるなーとワクワクしていました。

2と3のポジションに内定をいただき、迷った結果 3.の会社に行くことにしました。

このポジションを選んだ理由としては、

  • 分析のみに絞るよりもエンジニアリング要素があったほうが自分の強みを活かせると感じた
  • 面接で自分のスキルセットを確認していただくと同時に、丁寧な仕事内容の説明/チームメンバーのほとんどに会わせていただいたことで、面接の時点で自分がそのポジションで仕事をするイメージが明確にできた

という点が大きかったです。

新しい仕事の話

9/1に入社して、1週間たちました。

仕事内容としては上にも書いたようにGoでミドルウェア作ったりPythonでモデル作ったりそれをデプロイしたりする、分析屋/エンジニア/インフラを兼任するみたいな感じです。

分析に関わるエンジニアリングをまるっとやるということで、「データエンジニア」か「MLエンジニア」あたりで名乗っていこうと思います。

転職直後の様子はこんな感じです。

さいごに

転職活動にあたりポジションを紹介してくれた皆様、本当にありがとうございました!
いろんな会社があることを知れてとても面白かったです。

自分の経験を活かしつつ、チームに貢献できるよう頑張っていきたいと思います。

おまけ: データサイエンティストをする上で役に立った本

今まで技術書しか読んでなかったですが、 前職に転職してから、技術書以外の本でいろいろ本を読むようになりました。

他にもいろいろあって紹介する記事書きたかったんだけど、 もう書くこともなさそうなのでここでちょびっとだけ紹介しておきます。

世界最高のリーダー育成機関で幹部候補だけに教えられているプレゼンの基本

世界最高のリーダー育成機関で幹部候補だけに教えられているプレゼンの基本

先輩に「資料作るのに時間をかけすぎ」と言われ、すすめられた本で、 プレゼンの資料作りがめちゃくちゃ苦手な私のはとても勉強になった本でした。 これ読んで資料数個つくってから、資料作りのペースが半分くらいになった気がする。

イシューからはじめよ―知的生産の「シンプルな本質」

イシューからはじめよ―知的生産の「シンプルな本質」

王道の本ですが、仕事のススメ方を考える上でで自分の助けにもなったし、 社会人になりたての新卒に仕事の仕方を伝えるときの表現としてもつかえる内容の多い本でした。

最近追加されたPythonの便利機能とこれからのPython in #ll2018jp

Learn Languages 2018 というイベントで、最近のPythonについて発表してきました。

(一昨年まではLightweight LanguageでLLイベントだったのが、去年からLearn Languagesイベントになったらしい!)

www.slideshare.net

せっかくなのでポイントだけでも書き起こして見ようと思います。

ここ1~2年で便利になった機能

1. The pathlib module (PEP 428)

pathlibはファイルパスに関するモジュールで3.4で導入されました。

ただ、build-inのopenやos.pathモジュールがpathlib.Pathオブジェクトを受け入れられるようになったのがPython3.6(PEP519)となっています。

3.6以降ではぜひos.pathよりもpathlibを優先して使っていきたいです。

使用例: スクリプトのディレクトリを取得

今までは、スクリプトのあるディレクトリを取得するとき

import os
root_path = os.path.dirname(os.path.abspath(__file__))

こんなかんじでos.pathモジュールを駆使していました。

これを、pathlibを使うと

import pathlib
root_path = pathlib.Path(__file__).resolve().parent

というように

  1. 現在のファイルのPathオブジェクトを作る
  2. resolveで相対パスを絶対パスに解決
  3. 親ディレクトリの取得

みたいに直感的にかけるようになりました。

使用例: ディレクトリ以下のファイルの探索

ディレクトリ以下のcsvファイル探索をこんなふうに簡単にかけます。

data_path = pathlib.Path('data')
for filename in data_path.glob('**/*.csv'):
    print(filename)

# data/csv/data1.csv
# data/csv/data2.csv

今までは os.walkを使って書いてたのに比べるとだいぶ楽ちんです。

2. Type hints (PEP 484)

TypeHintっていうのは
引数や関数の返り値に型注釈をつけられる仕様です。

def greeting(name: str) -> str:
    return 'Hello ' + name

print(greeting.__annotations__)
# {'name': <class 'str'>, 'return': <class 'str'>}

このサンプルだと

  • 引数nameはstr型であり
  • 関数greetingはstr型を返す

ということを示しています。

これはあくまで静的解析のための構文で、 実際にビルドするときに型チェック等は行われるわけではありません。 しかし、この構文によってIDE等での解析が可能になります。

ただ、もともと型注釈の評価はコンパイル時に行われていたので
、

class C:
    @classmethod
    def from_string(cls, source: str) -> C:
        …

こんなふうにCというクラスが自分自身のクラスを返すという型注釈をつけると、 その時点ではまだクラスCはクラスCの存在を知らず、実行時にエラーが起こってしまっていました。

これを踏まえ、3.7ではアノテーションの遅延評価(PEP 563) が導入されました。 型注釈の評価が実行時に行われるようになり、このサンプルのような前方参照が可能になりました。

これでTypeHintがかなり使いやすくなったのではないかと思います。

ただ、互換性のない変更になるので利用する際には

from __future__ import annotations

の一文が必要になります。

この挙動は4.0から標準の挙動になる予定です。

3. Data Classes (PEP557)

クラス変数アノテーションを使って変数を定義することで、
__init__, __repr__, __eq__, __ne__, 
__lt__, __le__, __gt__, __ge__
を自動的に生成できるDataClassが3.7で導入されました。

今までname, age, itemsの3つをもった
Userクラスを定義しようとすると、こんな感じでした。

from typing import List

class User:
    name: str
    age: int
    items: List[int] = None

    def __init__(self, name: str, age: int = 0):
        self.name = name
        self.age = age
        self.items = [] 
        
    def __repr__(self):
        return f’{self.__class__.__name__}’
                        ....

   user = User('Taro', 10)
    # procedue User(name='Taro', age=10, items=[])

インスタンス変数が多いクラスを書くときに、 この__init__ で引数と代入部分で
何度も同じ変数名書くのめんどくさい…
と思ったことないですか。
私はよく正規表現駆使して__init__生成してました。

またitemのようにmutableなオブジェクトは
関数の
デフォルト引数にitem = []
というように書くことはできません。

上記サンプルのように__init__で初期化を書くようにしないと、 インスタンス間で
オブジェクトがシェアされるような挙動になってしまうためです。 (これはPythonの落とし穴!)

3.7で導入されたDataclasses(PEP 557)のdataclassデコレータを使うと、 上記コードはこんな感じになります。

from dataclasses import dataclass, field
from typing import List

@dataclass  
class User:
    name: str
    age: int
    items: List[int] = field(default_factory=list)

user = User('Taro', 10)
# procedue User(name=‘Taro’, age=10, items=[])

__init____repr__をかかなくてもよくなり、すっきりしました。
また、このdataclassesモジュールのfield関数を使うと
デフォルト値としてlistのような
mutableなvalueを設定できます!

dataclassは、あくまで
自動で特殊関数をはやしてくれる普通のクラスなので、
どんどんこの機能は使っていけたらいいなと思っています。

Other update

他にも特に面白そうな新規機能としては以下を挙げておきます。

興味がある方は見てみてください。

これからのPython: Python3.8

Python3.8はいまのところ2019年10月にリリース予定です。

組み込まれる予定の機能で一つ大きなのが
「Assignment Expression(代入演算子)の導入」があるのでこれについて少しだけ書いていきます。

Assignment Expressions (PEP 572)

今までなら正規表現のmatchしたオブジェクトを
一度変数に入れてifで評価してたのが

match = pattern.match(data)
if match:
    result = match.group(1)

代入演算子を使うと、こんな感じで代入と評価を同時にすることができます。

if (match := pattern.search(data)) is not None:
    result = match.group(1)

便利ですね。

しかし、これは本当にPythonicなのか?ということで、導入までには議論を呼びました。

Pythonには、 Zen of Python (PEP 20) の

There should be one-- and preferably only one --obvious way to do it.

にもあるように「同じことを複数の書き方ができるようにしない」ということを大切にしています。
Pythonはインクリメント演算子もないくらいその部分を気にしているのに、:=はいいのか・・・という気持ちは個人的にはあります。

ただPythonも色んな人に使われるようになってきたので、 そのあたりは柔軟になっていくのかもしれないです。

(おまけ) Retirements of Guido van Rossum

ところで、最近PythonのBDFL(Benevolent Dictator For Life, 最終意思決定者)であるGuidoがBDFLの引退を発表しました。

... I would like to remove myself entirely from the decision process. I'll still be there for a while as an ordinary core dev, and I'll still be available to mentor people -- possibly more available. But I'm basically giving myself a permanent vacation from being BDFL, and you all will be on your own. ....

[python-committers] Transfer of power

Guidoはこのポジションについて
後継者を決めるつもりはなく、 あとはみんなで決めてね、というように言っています。

今後のPythonの意思決定がどうなっていくかは追っていきたいところです。