minminの備忘録

フルスタックになりたいエンジニアの備忘録 → https://zuminblog.com/ へ引越し中

転職して8ヶ月経ったが、今更ながら「転職エントリ」とやらを書いてみる

f:id:minmin_21:20200705125732j:plain
Pexels.comより引用

新卒3年目である2019年11月に転職をしてから8ヶ月程経ちました

いわゆる、「転職エントリ」というものは転職が決まった直後であったり、実際に退職をするあたりで書かれることが多いように思いますが、転職して8ヶ月という今更感半端ない転職エントリというものを書いてみます

(転職して半年くらいで書ければな〜とは思っていたのですが、いつの間にこんな時期になっていました笑)


なぜ今更転職エントリを書こうと思い立ったかというと、理由は主に2つあります

  • 転職に至った動機・目的を振り返ることで、その目的を達成できているか(できそうか)を確認したかったから
  • 単純に時間が経つとなぜ自分が転職をしたのか忘れてしまいそうだったから


今回は、

  • なぜ転職をしようと思ったのか
  • どのような軸で転職活動をしていたか
  • 転職してみて自分の求めていたことが実現できているのか

について書いていきたいと思います。

なぜ転職活動をしようと思ったのか

私は新卒でいわゆる大手SIerと呼ばれる企業に入社し、機械学習関連の新規事業を立ち上げる部署でエンジニアとして働いていました。

その部署では、ビジネスアイデアを考え、プロトタイプの実装をし、それに価値があるかを確かめるためのPoCを行うということをやっていました。

SIerの会社とは言いつつも、理不尽にレガシーな言語で書かなければいけなかったり、、、(以下略)ということはほとんどなく、言語やライブラリなどの技術選定も自分たちで行い、ドキュメントよりも動くものを優先するといったように、割と自由にプロダクトを開発をさせてもらっていました。

人間関係も非常に良く、働くのは非常に楽しかった思い出があります。


しかし働くのが楽しい反面、勉強会などで社外のエンジニアの方々と交流することが増えてきた際に、エンジニアとしてすごい人たちが世の中にこんなにいるのかと痛感しました。そこで、自分はこのまま今の会社にいて、この人たちに追いつけるのかという"もやもや"とした感情を持つようになりました。

当時いた会社のエンジニアは、エンジニア→マネージャーというキャリアパスが一般になっており、割と早い段階から"中間管理職としてのマネージャー"としてのスキルが重視されるような環境だったため、今後5年くらいのスパンで見たときに、純粋にエンジニアとしての技術力を伸ばすのには向いていないのではないかと漠然と思うようになりました。

そこで、自分がエンジニアとしてどんなスキルを身につけたいかを改めて考え直すことにしました。どういうスキルを身につけたいかを明確にすることで、今の会社に残るべきか・転職するべきかをはっきりさせることができると思ったからです。


そこで自分なりに考えてみた結果、エンジニアとして主に2つのスキルを身につけていきたいと思いました。

  • プロダクト・事業を推進するために最適な技術選定をし、素早くプロダクトに還元できるスキル
  • プロダクトのユーザー体験を良くすることや、マーケットの需要を満たすプロダクトとは何かを常に考えながら、素早くプロダクトを実装するスキル

1つ目に関しては、新しい技術を日々キャッチアップし、プロダクトに合わせて技術選定を行えるような環境だったので、このまま当時の会社にいても身につけることはある程度できるなとは思いました。ただし、長い間同じ部署にいることはほとんどないような会社だったので、長い目で見ると微妙かなという感じでしたが、、

2つ目に関しては、プロダクトを改善→ユーザーのフィードバックをもらう→フィードバックをもとにプロダクトを改善→ ...というトライ&エラーを繰り返すことで得ることができるものだと思うのですが、SIerという業界の構造上それを実現するのは難しいのではないかと思うようになりました。SIerのようにクライアントが使用するシステムの開発をするよりも、自社でプロダクトを持っている企業の方がエンドユーザーのフィードバックをもらいやすく、プロダクト開発の自由度が高いためトライ&エラーのサイクルを早く回せると思ったからです。

そこで、この2つのスキルを伸ばしていくために、自分にとってより良い環境があるのではないかと思い本格的に転職活動を開始することにしました。

どのような軸で転職活動をしていたのか

続いて、上に書いたような転職の動機を踏まえた上で、実際にどのような軸で転職活動をしていたかについてお話しします。主に4つの軸で転職活動をしていたので、1つ1つ説明していければと思います。

自社でプロダクトを持っている企業か

こちらは先ほども書いたように、自社でプロダクトを持っている企業の方がプロダクトに対する決定権や自由度が大きいため、プロダクトの改善→フィードバック→更なる改善が行いやすく、トライ&エラーの回数が増えることで、自分が身につけたいスキルを早く身につけられるだろうという理由です。

フロントエンドエンジニア、バックエンドエンジニアみたいな職種ごとの境界が曖昧か

ここを軸にしたのは大きく理由が2つあります。

まず1つ目は、ソフトウェアエンジニアリングに関する幅広い技術に触れるのが楽しかった・自分に合っていると感じたというのがあります。

当時いた会社ではデータの前処理などを含めた機械学習モデリングから、プロトタイプ作成のためのフロントエンド/バックエンド実装をチーム内の誰もができるようにやっていました。チーム内で得意不得意はあれど、それぞれがお互いの得意を補完し合って1つのプロダクトを作り上げていくのがとても楽しかったという体験が非常に大きかったのかなと思います。

(あとは、誰にも負けない得意分野を作れるわけではないけど、大体平均点近く取って総合力で勝負するという自分の性格にも影響を受けているのかもしれません)


2つ目は、「良いプロダクトとは何かを常に考え、素早く実装するスキルを身につける」という観点からは、今の段階でフロントエンド/バックエンドなどのどこかの専門領域に狭めることは、リスクだと感じたためです。

(ここで言っている良いプロダクトというのは、「ユーザーを幸せにでき世の中にインパクトを与えるプロダクト」くらいのざっくりとした認識で聞いてもらえればと思います)

良いプロダクトを作るためには、フロントエンドもバックエンドも機械学習も手段の1つでしかなく、その手段を多く持っている方が良いプロダクトを作る確度を高めることができるのではないかと感じていました。

そこで、特に職種を分けて募集せず「エンジニア」という枠で募集している企業、もしくはそこらへんの職種の境界が曖昧な企業を中心に探すことにしました。


エージェントの方との面談や企業の方とのカジュアル面談で話をしていくうちに、1000人近くの規模の会社になると職種が別れていることが多く、数十人程度の会社になるとエンジニアは何でもやるけど、まだプロダクトにデータが溜まっておらず、当時自分の中では得意だった機械学習の知識がフェーズ的に使えないという感覚を持ったので、100~200人程度の企業を中心に探していました。(必ずしも企業規模で測れるものではありませんが、目安として)

プロダクトが好きか・共感できるか

日々の開発の中ではどのようにコードを実装するかとか、どのような設計にするかのような目の前のことで頭がいっぱいになりがちですが、ふと目線を上げたときにこのプロダクトってどうやってユーザーを幸せにしようとしてるんだっけとか、どんなインパクトを社会に与えたいんだっけって考えることがあると思います。

そのときに、「あ、こういう世界を実現したいんだった。こんな世界になったら面白いな」と思えることで自分のモチベーションが上がるタイプなので、ここは割と重要なポイントでした。

逆にプロダクトが好きではないと、モチベーションが上がらず、こういう風にプロダクトを変えていきたいとかも心から思えないので、働いていて楽しくないと思いますし、良いパフォーマンスが発揮できないと感じています。

会社のビジョン・ミッションに共感できるか

最後のポイントですが、ここが一番大事だったかもしれないです。

プロダクトが好きで共感できたとしても、会社としてのミッションに共感できないと自分が楽しく働けなくなるリスクが大きいと感じたためです。

プロダクトのピボットがそのリスクの1つだと思います。たとえ入社した時のプロダクトが好きだったとしても、ピボットした後のプロダクトが好きだとは限らないからです。プロダクトのピボットというのは通常、会社のミッションを軸に行われるものなので、逆に会社のミッションに共感できていれば、ピボットした後のプロダクトにも共感して携われると思っています。

もう1つのリスクは、開発するプロダクトが1つとは限らないということです。これも同様に、プロダクトが何個に増えたとしても、会社のミッションという軸から派生してくるものなので、程度の差はあれどどのプロダクトにも共感して携われると思っています。

以上の4つの軸を基に転職活動をし、無事第一希望のスタートアップ企業からオファーをいただくことができました。

実際に転職活動でどういう動きをしていたかについては、別の記事でまとめられればと思います。(書く気が起きればですが笑)

転職してみて自分の求めていたことが実現できているのか

実際に転職をして9ヶ月ほど経ったので、転職して自分の求めていたことが実現できているのか/できそうかについて、簡単に話せればと思います。

転職当初自分が伸ばしていきたいスキルはこの2つでした。

  • プロダクト・事業を推進するために最適な技術選定をし、素早くプロダクトに還元できるスキル
  • プロダクトのユーザー体験を良くすることや、マーケットの需要を満たすプロダクトとは何かを常に考えながら、素早くプロダクトを実装するスキル

結論から言うと、現在の仕事で得られるものと概ね方向性は合っており、自分が成長できているなという実感を持つことができています。

特に、開発のスピードが求められるような組織・業界なので、「考えながら素早く実装するスキル」は前よりは成長しているなという実感を持っています。

さらに、一機能をどうするかみたいな話だけでなく、もう少し大きいプロダクトとしての方向性はどうするかというところにも少し携わらせてもらっているので、良い意味で自分が見えていなかったスキルもたくさん見えてきていて、とてもワクワクしています。

一緒に働いているエンジニアの方々は、自分が到底及ばないと思ってしまうほど非常に技術力が高いので(他の能力ももちろん高い)、正直なところ劣等感に苛まれてしまうこともありますが、人と比べるのではなく、過去の自分からどれだけ成長できているのかということに目を向けるようにすることで、刺激的かつ楽しく日々を過ごせています。

まとめ

長々となりましたが、どういう考えのもと転職活動をしたのか、転職してみて実際どうだったかということが少しでも伝われば幸いです。

これは転職活動をしていた当時から現時点にかけての価値観や考え方なので、今後変わっていく可能性もありますが、こんな考えをしている人もいるんだな〜くらいの感じでと見てもらえると嬉しいです。

AWS S3のバケットに対して、特定のIPからのみアクセスできるようにする

最近AWSをいじり始めてみてるので、まずはS3のアクセス制限から備忘録としてまとめてみたいと思います。

やりたいこと

AWS S3に格納されているオブジェクトを、特定のIPからのみアクセスできるようにしたい

今回説明する方法をもとに様々な方法でアクセス制限をアレンジしてもらえればと思います

そもそもAmazon S3って?

Amazon S3とはAmazon Simple Storage Serviceの略で、名前の通りクラウドのストレージサービスです

メリットとしては、所謂「クラウド」の利点である、スケーラビリティや信頼性を利用者は意識する必要がないことなどがあげられます

やり方

大まかな流れ

  1. S3のバケットを作成し、データを格納
  2. デフォルトではパブリックアクセスができないので、バケットポリシーを作成し、特定IPからファイルにアクセスできるようにする

詳細

バケットの作成・データ格納

  1. AWSマネジメントコンソールにサインイン

  2. 左上の「サービス」→「S3」を選択し、S3のサービスに入る

  3. バケットを作成する」をクリックし、バケット名やリージョンを入力する→「作成」をクリック

    • 細かい設定をすることもできますが、今回はデフォルトのままでいきます
  4. 作成したバケットをクリックし、バケットの中に入る

  5. 「アップロード」をクリックし、オブジェクト(今回は画像)をアップロード

  6. ファイル名をクリックすると、オブジェクトURLが下に出てくるのでアクセスする

  7. 以下のようにアクセス拒否されることを確認する

f:id:minmin_21:20191022152229p:plain

バケットポリシーの作成・適用

  1. バケットのページに戻り、「アクセス権限」→「バケットポリシー」をクリック

  2. 下の方にある、「ポリシージェネレーター」をクリックする

  3. ポリシージェネレーターのページが開くので、以下のように情報を入力する

    • Type of policy: S3 bucket policy
    • Effect: Allow
      • アクセス許可を設定するポリシーなのか、拒否を設定するポリシーなのかを設定
    • Principal: *
      • リソースへのアクセスを誰に許可/拒否するかみたいなものを設定します(今回は匿名で良いので 「*」を設定)
      • 詳しくは、以下を参考にしてください
    • AWS Service: Amazon S3
      • どのサービスに対するポリシーかを設定
    • Actions: GetObjectを選択
      • どの操作を許可/拒否するかを設定(今回は、Getだけ許可したい)
    • Amazon Resource Name(ARN): arn:aws:s3:::<bucket_bame>/*
      • どのリソースにポリシーを適用するかを指定する(今回は、バケット内のすべてのリソースに適用する)
    • Add Conditionsをクリックし、追加の条件を入れる→「Add Condistion」→「Add Statement」
      • Condition: IpAdderess
      • Key: aws:SourceIp
      • Value: Your IP Address
  4. 「Generate Policy」をクリックすると、ポリシーが生成されるのでコピーし、「バケットポリシーエディター」に入力する→右上の「保存」

ちなみに生成されたバケットポリシーはこんな感じ

{
    "Id": "XXXXXXXXXXXXXXXXXX",
    "Version": "XXXXXXXXXX",
    "Statement": [
        {
            "Sid": "XXXXXXXXXXX",
            "Action": [
                "s3:GetObject"
            ],
            "Effect": "Allow",
            "Resource": "arn:aws:s3:::<YOUR BUCKET NAME>/*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": "<YOUR IP ADDRESS>"
                }
            },
            "Principal": "*"
        }
    ]
}

もう一度、オブジェクトURLにアクセスしてみると、画像が見えるように!!

他のIPアドレスの端末からアクセスできるか確認してみると、見えない(はず)

参考

dockerのmulti stage buildを理解する

今回は、dockerのmulti stage buildとやらを紹介したいと思います。

(※この機能は、Docker 17.05以降のversionで使用できます。)

multi stage buildとは

そもそもmulti stage buildとはなんぞやという感じだったので、そこから説明したいと思います。

以下のように、1つのDockerfileでFROMを複数使用してbuildを行うことをmulti stage buildと言います。

(詳しいコードの中身については、「使い方」の章で説明したいと思います。)

FROM golang:1.12 as builder
WORKDIR /app
COPY ./sample.go .
RUN GOOS=linux go build -o sample sample.go

FROM alpine:latest
WORKDIR /root/
COPY --from=builder /app/sample .

いつ使うのか

golangなどのコンパイルしてコードを実行するような言語の環境を作成する時に使います。

1つのDockerfileで、コンパイルする環境と実行環境を分けて作成することができます。

なぜ使うのか

最終的に出力イメージのサイズを軽くすることができるからです。

イメージサイズを軽くすることで、配布する側もダウンロードして使用する側も負担が減ります。

なぜ軽くなるかというと、コンパイルで使用した一時的な環境を捨てて、実行環境のみをイメージとして作成することができるためです。

具体的にどれくらい軽くなるかについてですが、「使い方」の章で説明するサンプルコードだとイメージサイズが100倍近く軽くなっています。

使い方

今回はdockerの公式docでも紹介されていたように、golangの環境を作成するコードを書いていきたいと思います。

Dockerfileでは、以下の簡単な.goファイルをコンパイルして実行ファイルを作成し、配置するところまでやっていきたいと思います。

package main

import "fmt"

func main() {
    a := 1
    b := 2
    fmt.Println(a + b)
}

single stage build

まずは、single stage buildの復習からです。

Dockerfileは以下のように書きました。

FROM golang:1.12
WORKDIR /app
COPY ./sample.go .
RUN go build -o sample sample.go

イメージを作成し、コンテナを起動し入り込んでみます。

$docker build -t single_stage_build:latest .
$docker run -it single_stage_build /bin/sh
# ls
sample  sample.go
# ./sample
3

無事、実行できました!

multi stage build

続いて、multi stage buildです。

先ほど書いたDockerfileの再掲です。

FROM golang:1.12 as builder
WORKDIR /app
COPY ./sample.go .
RUN GOOS=linux go build -o sample sample.go

FROM alpine:latest
WORKDIR /root/
COPY --from=builder /app/sample .

上記のコードについて、抜粋して説明していきます。


FROM golang:1.12 as builder

の部分では、as <name>をすることで、ステージに名前を指定することができます。ステージ名は後で使用します。


RUN GOOS=linux go build -o sample sample.go

の部分について、GOOS=linuxで、実行環境をLinuxに指定しています。コンパイルを行う環境と実行環境が違うため指定しています。(クロスコンパイル)

(もしかしたら指定しなくても良いかもしれません。なくても動作することは確認しました)


COPY --from=builder /app/sample .

の部分では、最初のステージからファイルをコピーしています。

どのステージから持ってくるかについては、--from=<stage name>で指定しています。



イメージの作成方法は、通常のDockerfileと同様に行います。

$docker build -t multi_stage_build:latest .

イメージからコンテナを起動して入り込んでみると、コンパイル済みの実行ファイルのみが置かれていることを確認できます。(もちろん実行もできます)

$docker run -it multi_stage_build /bin/sh
~ # ls
sample
~ # ./sample
3

イメージサイズの比較

$docker images
REPOSITORY                   TAG                 IMAGE ID            CREATED             SIZE
multi_stage_build            latest              28c0ead58d63        4 minutes ago       7.58MB
<none>                       <none>              35ed8be9c145        4 minutes ago       824MB
single_stage_build           latest              5438467d2c15        45 minutes ago      816MB

single stage buildのイメージは816MB、multi stage buildのイメージは7.58MBと100倍近く軽いイメージを作成することができました!!


実は中間イメージ(<none>のやつ)が残ってしまっていますがこれについては、以下のサイトでdockerのメンテナーの方がバグではなく仕様だと説明しています。

簡単に説明すると、「buildするたびに毎回0からイメージを作成するなんて無駄だから、キャッシュとして残してるんだよ」みたいな感じで説明しています。

詳しく見たい方は、以下のサイトを参考にしてもらえればと思います。

github.com

おまけ

dockerの公式docにはRUNの部分で、

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o sample sample.go

みたいな感じで書いてあったのですが、以下のサイトによるとgolangのver1.10以降では

CGO_ENABLED=0 go build -a -installsuffix cgo

のオプションはいらないとのことです。

github.com

参考

MapReduceについて

最近、大規模分散処理について学ぼうという機運が高まっており、Hadoopについて学んでいるのでその備忘録です。(勉強を始めたばかりなので、間違っていることもあるかもしれませんが、その場合は指摘していただけると嬉しいです)

今回は、MapReduceに関する記事になります。

一応、HDFS(Hadoop Distributed File System)について書いた記事の続きのつもりで書いているので、HadoopHDFSってなんぞやって人はそこから読んでもらうと良いかもしれません。

https://minmin-21.hatenablog.com/entry/2019/08/19/185459minmin-21.hatenablog.com

MapReduceとは

MapReduceとはHadoopで使われている、大規模データの並列分散処理のアルゴリズムです。

MapReduceの説明に入る前に、まずは前提としてHadoopでの分散処理の概要について説明したいと思います。

Hadoopにおける分散処理概要

Hadoopでは分散処理の仕組みとして、以下のようなマスタースレーブ方式をとっており、大きく3つの要素から構成されています。(HDFSとほとんど似たような感じです。)

  • Hadoopクライアント
    • Hadoopクラスタとクライアント(User)の仲介役として、JobTrackerに対して処理の実行を依頼する
  • JobTracker
    • TaskTrackerに対して、処理タスクの割り当てを行うサーバー
    • TaskTrackerの状況監視も行う
  • TaskTracker
    • JobTrackerからの指示を受けて、実際に処理を行うサーバー
    • JobTrackerに定期的に状況報告を行う(ハートビート)

f:id:minmin_21:20190824150208p:plain
Hadoop分散処理概要

最終的な処理結果はHDFSに書き出して終了という感じです。

アルゴリズム概要

今回は説明のために以下のように、複数の食料品店の在庫データから全ての店舗を合計した各食料品の在庫数を調べる、ということを例にして説明していきたいと思います。

f:id:minmin_21:20190824150306p:plain

MapReduceアルゴリズムは、Map, Shuffle, Reduceの3つのフェーズから構成されています。

各ステップについて、それぞれ詳しく見ていきたいと思います。

Mapフェーズ

このフェーズでは、HDFSに保存されているデータを読み込んで、Key-Valueの形にデータを変換し、データに対して意味づけを行います。

具体的には、以下のような感じです。

f:id:minmin_21:20190824150345p:plain
Mapフェーズ

DataNodeとMapperタスクを行うTaskTrackerは同じにした方が、通信コストがなくなるので処理を早くすることができますが、必ずしも同じにする必要はありません。(データのローカリティと言います)

Shuffleフェーズ

Shuffleフェーズでは、Mapフェーズで意味付けしたデータの整理を行い、Reduceフェーズに向けてどの処理をどのTaskTrackerに割り当てるかを決定します。

具体的には、以下のような感じです。

f:id:minmin_21:20190824150422p:plain
Shuffleフェーズ

「各TaskTrackerにどのデータを処理を担当させるか割り当て」のフェーズでは、実際にデータを動かしてまとめるのではなく、どのTaskTrackerに割り当てるかを決めるだけです。概念をわかりやすく説明するために、図ではまとめているように見せています。(間違ってたらすみません。。)

Reduceフェーズ

このフェーズでは、Shuffleフェーズで割り当てられたタスクを実行するために、担当分のデータを取得し処理を行います。

今回の場合、各データの集計処理を行います。

具体的には以下のような感じです。

f:id:minmin_21:20190824150453p:plain
Reduceフェーズ

Shuffleフェーズではどのデータを担当するか割り当てているだけなので、Reduceフェーズの始めに各TaskTrackerは担当分のデータを他のTaskTrackerからコピーしてきます。

どのTaskTrackerに担当のデータがあるかの情報はJobTrackerに問い合わせることで取得しています。

まとめ

これまでに説明した各フェーズをまとめた図を以下においておきます。

f:id:minmin_21:20190824150527p:plain
MapReduce

最後に

Hadoop(主にMapReduce)について学ぶ時に、以下の書籍・資料が非常に分かりやすかったので、詳しく勉強したい・この記事じゃ分からんって人は、以下の資料を見てみることをお勧めします。

参考

分かってるようで分かってないラムダ式について

コードを書く時だったり、コードレビューをする時にラムダ式を見かけることがちょくちょくあるのですが、毎回こんな感じだろうなって感じで曖昧な理解のままで素通りしていたので、今回はラムダ式をちゃんと理解しようということで、まとめてみました。

ラムダ式とはなんぞや?という人の助けになれば幸いです。

ラムダ式とは

ラムダ式とは、

名前が定義されていない関数を記述するための方法

です。

???って感じですよね。。 (私も最初はこの表現じゃよくわかりませんでした。。)

ということで、早速コードを見ていきましょう。今回はpythonでコードを書いていきます。

ラムダ式の書き方

まずは、基本的な書き方についてです。

(関数名 = )lambda 引数1, 引数2, ...:何らかの処理

print(関数名(引数1, 引数2, ...))と記述すると、処理された結果が出力されます。

※注意事項

lambda式で関数名を定義するのは、PEP8(pythonのコーディング規約)で推奨されていません。

上で「名前が定義されていない関数を記述するための手法」と言っているのに、関数名を定義するのはおかしな話ですよね笑

具体的には、以下のサイトに記載されています。(lambdaでページ内検索すると出てくると思います。)

www.python.org

コード例

掛け算

まずは簡単な例からということで、与えられた2つの変数の積を求める式を、defで関数名を定義した場合とラムダ式を使った場合を比較したいと思います。

#defで関数名を定義した場合
def multiple(a, b):
  return a*b
#ラムダ式
lambda a, b: a*b

うーん、これじゃまだlambda式のうまみが分かりづらいですね、、、

三項演算子を使ってみる

ただ引数を乗算するだけじゃ面白くないので、簡単な条件分岐を使ってみましょう

#defで関数名を定義した場合
def is_over_100(a):
    return True if a > 100 else False
#ラムダ式
lambda a: True if a > 100 else False

個人的には、まだうまみがわからないですね、、

そもそも上記の利用方法でlambdaで定義した関数を使うためには、関数名を定義しなければいけないので、lambda式を使う理由がないですね。。

map関数に適用

ここでよく使われている(個人的な観測範囲内ですが)、map関数にlambda式を組み込む方法についてみていきましょう。

map関数とは、リストなどのイテラブルなオブジェクトの各要素に対して、第一引数に指定した関数を実行する関数です。返すのはイテレーターなので、実際に出力するときはlistにキャストする必要があります。

#書き方
map(適用したい関数, イテラブルなオブジェクト)
#例
sample_list = [1,2,3,4]
print(list(map(str, sample_list))) #['1', '2', '3', '4']

以下がdefラムダ式をmap関数に適用した例です。

sample_list = [1, 2, 3, 4, 5]
#defで関数名を定義した場合
def square_only_even(x):
    return x**2 if x%2==0 else x
print(list(map(square_only_even, sample_list))) #[1, 4, 3, 16, 5]
#ラムダ式
print(list(map(lambda x:x**2 if x%2==0 else x, sample_list))) #[1, 4, 3, 16, 5]

ちょっとコード量が減りました!
しかし、以下のように拡張for文を使えば、lambda式と同じようにかけてしまうので、う〜ん…って感じですね。むしろ拡張for文の方がすっきりしてる気がします

print([x**2 if x%2==0 else x for x in sample_list]) #[1, 4, 3, 16, 5]

ラムダ式が威力を発揮する場面

そこで個人的にうまみがあると思った方法について、紹介したいと思います

sorted関数に適用

sorted関数とは、イテラブルなオブジェクトをソートする関数で、keyとして「何を持ってソートするか」ということを指定することができます。

以下の例は、各要素の2番目を比較してソートするコードなんですが、ここでlambda式が使われています。

sample_list = [[12,3],[10,1],[6,2],[9,6],[1,9]]
#defで関数名を定義した場合
def get_2nd_number(x):
    return x[1]
print(sorted(sample_list, key=get_2nd_number)) #[[10, 1], [6, 2], [12, 3], [9, 6], [1, 9]]
#ラムダ式
print(sorted(sample_list, key=lambda x:x[1])) #[[10, 1], [6, 2], [12, 3], [9, 6], [1, 9]]

map関数で見た時と同様に、関数を定義していない分コード量は減っています。

この場合は他の良い書き方があまり思いつかなかったので、lambda式を使う場面かなと思っています。

min/max関数に適用

min/max関数とは、無名引数が1つのイテラブルなオブジェクトの場合、その中で最小/最大の要素を返す関数です。

(2つ以上の無名引数が与えられた場合は、それらの中から最小/最大の要素を返します。)

min/max関数にlambda式を適用した例は、以下になります。

sample_list = [[12,3],[10,1],[6,2],[9,6],[1,9]]
#defで関数名を定義した場合
def get_2nd_number(x):
    return x[1]
print(max(sample_list, key=get_2nd_number)) #[1, 9]
#ラムダ式
print(max(sample_list, key=lambda x:x[1])) #[1, 9]

これも、sorted関数で見た時と同様で言わずもがなですね。

メリット・デメリット

メリット

  • 扱う変数名が減る

扱う変数名が減ることで、名前衝突のリスクが減りコードが管理しやすくなります。

  • 関数名を定義する場合より、コード量が減る(一般的には)

def hoge(a,b):...return c の部分を書かなくてよくなるので、一般的にはコード量が減り可読性が上がります。

もちろん書き方によってはコード量は増えますし、コードの可読性が下がります。

デメリット

  • コードが見にくくなることがある

以下のような例のラムダ式だと、(個人的には)可読性の悪いコードを生み出しているように感じます。

sample_list = [1, 2, 3, 4, 5]
sample_list2 = [1,4]
print(list(map(lambda x:x**2+10 if x not in sample_list2 and x%2==0 else x**3, sample_list))) #[1, 14, 27, 64, 125]

コードは他人が読めるように書くものなので、処理が長くなってしまうような関数を使用したい場合は、以下のようにdefで定義してあげた方が、見やすくなることもあります。

def sample_func(x):
    if x not in sample_list2 and x%2==0:
        return x**2 + 10
    else:
        return x**3
print(list(map(sample_func, sample_list))) #[1, 14, 27, 64, 125]

もちろん場合によりけりなので、どちらの選択肢も持っておいた上で選択できるようにしておくのがベストです。

参考

HDFSについて

Hadoopとは?

HDFSとはHadoopで採用されているファイル管理システムなんですが、そもそもHadoopとはなんぞやという状況だったので、そこから軽くまとめます。

Hadoopとはざっくり言うと、

非構造な大規模データの処理・格納を高速に行うためのフレームワーク

です。(非構造データとは、画像やテキストなどの定型データとして扱えないデータのことです)

ビッグデータ(ex. Petabytes規模のデータ)を処理するためには単一のサーバーだけでは処理しきれないため、複数のサーバーを使用してデータの保存・処理をすることが考えられました。

しかし、ただ単に複数サーバーに分散しようとすると、データをどのように保存するか・どのサーバーに処理を任せるか・障害の場合はどのように対応するかなど、サーバーの管理が大変です。

そこで、それらの管理・実行を簡単にするフレームワークとして考えられた一つがHadoopです。

どのようにデータを管理しているのか

Hadoopではデータ管理にHDFS(Hadoop Distributed File System)という方式を採用しています。平たく言うと、複数のサーバーにばらばらにデータを配置しています

しかし、ただバラバラに配置しているだけではデータの管理が大変なので、以下のようなマスタースレーブ方式をとっており、大きく三つから構成されています

  • Hadoopクライアント
    • Hadoopクラスタとクライアント(User)の仲介役
    • ここのおかげでクライアントからは複数のサーバーを一つのクラスタのように扱うことができる
  • NameNode
    • データがどこに格納されているかを記録しているサーバー
    • データ自体は保存していない
  • DataNode
    • 実際にデータを格納しているサーバー
    • NameNodeに対して、一定時間ごとに自身の状況を報告している(ハートビート)

f:id:minmin_21:20190819184212p:plain
HDFS

データの分割はキリが良いところではなく、”64MBごと”みたいな感じで分割します。
例えば文章を保存する場合でも、文字の途中で分割するということももちろんあり得ます。

サーバーに障害が起こった場合は?

もちろん、サーバーに障害が起こることを想定しており、障害が起こったサーバーごとに対策方法を見ていきます

DataNodeに障害が起こった場合

Hadoopでは同じデータを複数のDataNodeに保管しているため、一つのDataNodeに障害が起こった場合にも対応できます。
そして、障害が起こったDataNodeに保管されていたデータについては、自動的に新たに違うDataNodeに複製が行われます。(レプリケーション機能)

NameNodeに障害が起こった場合

データをどこに保存しているか記録しているNameNodeに障害が起こるとHDFS自体が動かなくなってしまうので、複製したSecondary NameNodeを配置する方法で、対策を行うこともあります。

HDFSのメリット・デメリット

メリット

  • 大規模なデータを比較的簡単に保存できる
  • 大量のデータを高スループットで読み込める
    • 読み込みは得意ですが、書き込みは得意ではありません
  • 冗長化が簡単にできるため、データの安全性を確保しやすい

デメリット

  • 単一障害点(SPOF)がある
    • 主にNameNodeのことなんですが、通常のファイルサーバーに比べてデータが消滅しやすいです。なので、ファイルサーバーとしては使用しない方が良いそうです
  • データをピンポイントで更新・削除ができない
    • データ容量の区切りが良いところで保存している関係上、複数のサーバーにまたがって一区切りのデータを保存していることがあり得るため、ピンポイントでの更新・削除はできない仕様になっています

次回は、Hadoopで使われている大規模データの並列分散処理のアルゴリズムであるMapReduceについて、まとめてみたいと思います。

最後に

Hadoopについて学ぶ時に、以下の書籍・資料が非常に分かりやすかったので、詳しく勉強したい・この記事じゃ分からんって人は、以下の資料を見てみることをお勧めします。

参考

Coursera deep learning specializationのススメ

Courseraのdeep learningに特化したコースを修了したので、備忘録もかねてどのようなことを学んだのか、どのような点がおススメかということについて、紹介していきたいと思います

www.coursera.org

deep learning specializationの概要

  • deep learningに特化した全16週間のコース
  • 以下の全5コースから構成される
    • Neural Networks and Deep Learning
    • Improving Deep Neural Networks: Hyperparameter tuning, Regularization and Optimization
    • Structuring Machine Learning Projects
    • Convolutional Neural Networks
    • Sequence Models
  • 全部で150hかかる想定
  • 講師はAndrew Ng先生
  • 月額5531円(2019年5月時点)

ざっくりとした概要はこんな感じです
月額5531円っていうのは人によっては高いらしいんですが、かなり網羅的にdeep learningのことを学べたので、個人的にはそこまで高くないな~という印象です


基本的な各コースの構成としては、以下のような感じです
コースによっては、実装課題がなかったりもします

  1. video形式の講義
  2. 講義内容のquiz
  3. 講義内容を実装する課題


ここからは、各コースの詳細について紹介していきたいと思います

Neural Networks and Deep Learning (course 1)

  • 期間
    • 4週間のコース
  • 概要
    • Neural Networkとはどのようなものか
    • forward/back propagationや活性化関数等の説明
    • numpyを用いて、forward/back propagationやcost functionの実装

最初のコースでは、ゆっくり時間をかけてNeural Network(以下、NN)はどのような仕組みで動作しているのかを教えてくれます
NNの計算を効率化するためにどのようにベクトルを扱うかということを丁寧に教えてくれるので、数学に自信のない方にもわかりやすいのではないかと思います(数学が得意な方はかなり飛ばせるところだと思います)


課題は、numpyでNNの大事な計算の部分を実装するものになっているため、KerasやTensorFlowでは意識しない部分まで考えて実装することができ、NNに対する理解を深めることができます

Improving Deep Neural Networks (course 2)

  • 期間
    • 3週間のコース
  • 概要
    • NNをどのように過学習/未学習させないか
      • bias/varianceを判断し、どのような処置を施していけばよいかの説明
      • Dropout, regularizationの説明とそれらがなぜNNの精度に効いてくるのか
    • ミニバッチ学習について
    • SGD,RMSprop,Adamなどの最適化手法の説明
    • dropoutや最適化手法の実装
    • Tensorflowを用いて、簡単なNNの構築

このコースでは、実装したNNの精度が低い時の原因究明とその処置について教えてくれます DropoutやRMSprop, Adamなど用語は聞いたことはあったけど、それがいったい何なのか・それはなぜ必要なのかを知ることができて、非常に楽しかったコースでした


また、課題ではdropoutや最適化手法を実装したり、Tensorflowを用いてNNを実装しました
個人的にはTensorflowなんて難しいんだろうな~と思って手を出せていなかったんですが、事前にTensorflowについての簡単な講義があったので、すんなり実装することができました

Structuring Machine Learning Projects (course 3)

  • 期間
    • 2週間のコース
  • 概要
    • 機械学習プロジェクトをどのように進めていくと良いか
      • 精度の指標をどのように決めるのか
      • 学習/テストデータの分け方
      • エラーをどのように分析するか
      • エラーの分析結果に基づいてどのような方向性で精度を上げていくのか
    • 課題として、機械学習プロジェクトのケーススタディを解く

このコースはこれまでのコースとはちょっと違って、NNの中身に関する内容というよりかは機械学習プロジェクトをどのように進めていくべきかという、少しプロジェクトマネジメント寄りのコースです


実際に機械学習プロジェクトを進めていくうえで悩んでしまうことや、やってはいけないことを学べるので非常に重要なコースだと思いました
通常の技術的なコースだと、このようなことはなかなか学べないような気がするので、さすがAndrew先生!という感じです


また、実装の課題がない代わりに、クイズが通常よりも長めになっています
内容としては、実際に近い機械学習プロジェクトを想定して、プロジェクトをどのように進めていけばよいかのケーススタディを解く感じです

Convolutional Neural Networks (course 4)

  • 期間
    • 4週間のコース
  • 概要
    • CNNに関する基本的な説明
      • CNNの基本的な構成
      • 畳み込み層、プーリング層は何をしているのか
      • Padding, Strideについて
    • ResNetやAlexNet等の紹介
    • 物体検出を行うためのネットワーク(yoloなど)の説明
    • numpyで畳み込み層・プーリング層・paddingの実装
    • kerasを用いてResNetの実装
    • Tensorflowを用いてyoloの一部分を実装

CNNについても、畳み込み層、プーリング層など、言葉を知っていたもののなんなのかよくわからないことが多かったので、実装する際の見通しが非常に良くなったと感じました
また基本的なCNNから最近のNetworkまで触れていたので、かなり勉強になりました


課題に関しても、numpy, Tensorflowだけでなく、このコースからKerasも入ってくるので、実装の幅も広がると思います


実際にこのコースで学んだyoloについてまとめてある記事もあるので、良ければ見てください(笑)

minmin-21.hatenablog.com

Sequence Models (course 5)

  • 期間
    • 3週間のコース
  • 概要
    • RNNに関する基本的な説明
      • LSTM, GRU, bi-directionalなネットワークも紹介
    • word embeddingについて
    • seq2seqについて
    • attention mechanismについて
    • Kerasを用いた、LSTM,attention構造の実装

このコースでは基本的にRNNを用いて、文章の翻訳や音楽生成・文章分類などの問題を解くことを中心に説明したコースになります
個人的に興味があったattention mechanismについての講義があったのは、かなり嬉しかったです


自然言語処理に親しみのない私にとっては、非常にタフなコースだった印象です
課題も設定の倍くらいはかかったので、心が何度も折れかけましたが、最後のコースということもありなんとか終わらせることができました

一度では全部理解できていない箇所がたくさんあったので、またこのコースには戻ってくると思います
(課金が終了しても、講義動画は見られるらしいです)

感じたメリット・デメリット

  • メリット
    • deep learningに対する見通しがだいぶ良くなる
    • tensorflow, kerasの使い方について学べる
    • 課題はjupyter notebookなので、環境構築不要
    • discussionが充実しているので、課題で詰まってもある程度大丈夫
    • 英語の勉強にもなる
    • 修了証がコースごとに発行される

今まで敬遠していたtensorflowについて学べたのは、個人的に大きかったです
資料はすべて英語で、かつ講義についても日本語字幕がついていない動画も多数だったので、英語に触れつつ技術的なことを学べたので一石二鳥な感じが非常に良かったです


また、修了証がコースごとに発行されるため、モチベーションの維持はしやすかったです
全コースが修了した際には、コースの修了証とは別に修了証がもらえるのですが、それをもらった時は、感動ものでした



  • デメリット
    • 月単位での課金方式

月単位での課金方式だと少しさぼってしまうと、お金がただ溶けていくので、そこら辺が少しデメリットだな~とは思いました
逆にそのおかげで、やらなければいけない状況になるので、メリットでもあるのかなという印象です

まとめ

これだけ内容盛りだくさんで、月額5531円は安くないですか??ってくらいな印象です
機械学習初心者の方にはもちろん、ある程度は知っているけどdeep learningについて体系的な知識を身につけたい方にもおすすめのコースだと思います


全部終えるのにはかなり体力のいるコースですが、走りきるとかなりの力がつくと思うので、ぜひ挑戦してみてはいかがでしょうか?


最初の一週間はトライアルで課金なしで受講できるので、興味がある方はぜひ試してみてください

参考

vue-chartjsでいろんなグラフを書いてみる

Chart.jsのvue用のラッパーであるvue-chartjsを使って、いろんなグラフを書いてみたので、その簡単な使い方と詰まったところを紹介したいと思います

実装はgithubに公開してあります

github.com

完成イメージはこんな感じです

f:id:minmin_21:20190503112608p:plain
完成イメージ

コンポーネント構成

今回は、折れ線グラフ(Line chart)・棒グラフ(Bar chart)・円グラフ(Pie chart)を以下のような構成で実装しました

f:id:minmin_21:20190503112633p:plain
コンポーネント構成

コンポーネントごとに.vueファイルを作成したので、一つずつコード+簡単な解説という感じで紹介していきます

App.vue

<template>
    <div style='display:flex;'>
        <line-chart 
            class=chart
        />
        <bar-chart
            class=chart
        />
        <pie-chart
            class=chart
        />
    </div>
</template>

<script>
import LineChart from './LineChart'
import BarChart from './BarChart'
import PieChart from './PieChart'

export default {
    components: { LineChart, BarChart, PieChart },
}
</script>

<style>
.chart {
    width: 500px;
    height: 500px;
}
</style>

各グラフを表示するためのコンポーネントです

グラフのサイズ・位置等を整えている程度で、そんなに難しいことはやっていないのでここの説明は流したいと思います

LineChart.vue

<script>
import { Line } from 'vue-chartjs'

export default {
    name: 'LineChart',
    extends: Line,
    data() {
        return {
            chartData: {
                labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
                datasets: [{
                    label: 'sample',
                    borderColor: '#0000ff',
                    data: [100, 90, 60, 70, 50, 30, 40, 50, 60, 100],
                    fill: false
                }]
            },
            options: {
                title: {
                    display: true,
                    text: 'Line chart'
                },
                legend: {
                    display: false
                },
            }
        }
    },
    mounted: function() {
        this.renderChart(this.chartData, this.options)
    }
}

</script>

折れ線グラフのコンポーネントです

vue-chartjsからLineコンポーネントを継承してLineChartコンポーネントを作成しています

chartDataの書き方については、基本的にvue-chartjsの公式サイトを参考にしました

vue-chartjs.org


折れ線の下を塗りつぶすかどうか・グラフのタイトルを何にするか等のオプションは、vue-chartjsの公式サイトには書いていなかったので、chart.jsのサイトを参考にしました

www.chartjs.org


chartDataoptionsを作成したあとは、mount時にthis.renderChartを呼び出せばグラフが描画されます

BarChart.vue

<script>
import { Bar } from 'vue-chartjs'

export default {
    extends: Bar,
    data() {
        return {
            chartData: {
                labels: ['Janualy', 'Februaly', 'March'],
                datasets: [{
                    label: 'sample',
                    backgroundColor:'rgba(255, 60, 60, 0.3)',
                    data: [20, 40, 60]
                }]
            },
            options: {
                title: {
                    display: true,
                    text: 'Bar chart'
                },
                scales: {
                    yAxes: [{
                            ticks: {
                                beginAtZero: true,
                            }
                    }]
                }
            }
        }
    },
    mounted: function() {
        this.renderChart(this.chartData, this.options)
    }
}
</script>

棒グラフのコンポーネントです

LineChartと同様にvue-chartjsからBarコンポーネントを継承しています

chartDataの書き方についても同様に、vue-chartjsの公式サイトを参考に実装しています

y軸に関する設定を何もしないと以下のようになってしまうので、optionsの中でbegonAtZero: trueに設定し、y軸の開始位置が0になるように設定をしています

f:id:minmin_21:20190503112728p:plain

barchart_fail

PieChart.vue

<script>
import { Pie } from 'vue-chartjs'

export default {
    extends: Pie,
    data () {
        return {
            chartData: {
                labels: ['Janualy', 'Februaly', 'March', 'April'],
                datasets: [{
                    backgroundColor: [
                        'rgba(255, 60, 60, 0.3)',
                        'rgba(60, 60, 60, 0.3)',
                        'rgba(60, 255, 60, 0.3)',
                        'rgba(60, 60, 255, 0.3)',
                    ],
                    data: [100, 120, 30, 70]
                }],
            },
            options: {
                title: {
                    display: true,
                    text: 'Pie chart'
                },
            }
        }
    },
    mounted: function() {
        this.renderChart(this.chartData, this.options)
    }
}
</script>

円グラフのコンポーネントです

上の二つと同様にvue-chartjsからPieコンポーネントを継承しています

chartDataに関しては、vue-chartjsの公式サイトには載っていなかったので、vue-chartjsソースコードを直接見に行ったところ、例が載っていたのでそれを参考にしました


github.com

個人的に詰まったところ

コンポーネントimportの仕方が間違っていて、3時間くらい時間を溶かしてしまったので注意しておくべきところを紹介します


App.vueLineChart.vueでのimportを見てみると、importするコンポーネント{}をつけるかつけていないかの違いがあることが分かると思います

//App.vue
import LineChart from './LineChart'
//LineChart.vue
import { Line } from 'vue-chartjs'

例えば、LineChart.js{}を外すとグラフが表示されなくなってしまいます


正確にはまだ理解できていないのですが、コンポーネントexport方法の違いによって、import方法も変わってくるみたいで、

  • export defaultされているコンポーネント{}なしでimport
  • それ以外は{}ありでimport

なのかな?という解釈です
(有識者の方がいれば教えていただきたいです...)

参考

なぜYOLOはリアルタイムに物体検出ができるのか?

今回は自分の勉強がてら、物体検出アルゴリズムとして有名なYOLOについて簡単に紹介したいと思います



YOLOとは

YOLOとはリアルタイム物体検出アルゴリズムで、「You only look once」の頭文字をとったものになります


以下のサイトを見てもらうと、どんな感じかイメージが掴めると思います

pjreddie.com



CNNについて

YOLOのアルゴリズムについてお話しする前に、画像認識の基本であるCNNについて、画像が車か人かを認識する例について簡単に説明します
(※CNNの詳しい説明については、今回は省略します)


まず始めに、画像に対していくつかの畳み込み層やmaxpooling層をかませて、特徴を抽出します(①)
その後、抽出した特徴量をフラットなベクトルに変換して(②)、全結合層(③)にかけることで画像がどのクラスに属するかを判定します

f:id:minmin_21:20190413135807p:plain
CNN



物体検出に基本的なCNNを適用してみる

それでは次に、今説明したCNNのネットワーク構造をそのまま適用して、物体検出を行う方法をみていきます


単純に考えると、以下のような感じで箱(bounding box)を少しずつずらしていって、それぞれの画像に対して分類確率を求めていき、
分類確率が高いbounding boxを検出結果として採用するという方法が考えられると思います

f:id:minmin_21:20190413135907p:plain
sliding_window

この場合bounding boxの数だけネットワークにかけなくてはいけなくなってしまい、非常に時間がかかってしまいます



YOLOの仕組み

そこで登場したのがYOLOです
(実際にはこの間にいくつかのアイデアが提唱されましたが、今回は省略します)


以下が簡単なYOLOのネットワーク構成になります


f:id:minmin_21:20190413140004p:plain
YOLO

まず始めに、入力画像を任意のセル数(今回は3×3)に分解します(①)
物体は特定の一つのセルに属するという考えのもと設計されているので、今回の場合人は中央のセルに属することになります


次に、基本的なCNNと同様に画像から特徴量を抽出します(②)


最後に出力として3×3×nのベクトルとして出力するようになっています(③)
ここが先ほど見たネットワークとの大きな違いです

出力層の一つのセルについて注目してみると以下のようなベクトルになっており、クラス・大きさ・位置などの情報が入っています



(p_c, b_x, b_y, b_w, b_h, c_1, c_2)

p_c : 検出対象の物体が存在するかどうか\\
b_x : 物体中心のx座標\\
b_y : 物体中心のy座標\\
b_w : 物体の幅\\
b_h : 物体の高さ\\
c_1 : 車かどうか\\
c_2 : 人かどうか\\


具体的には、以下のような出力になります
(セルの左上を(0,0), 右下を(1,1)として、b_x, b_yは算出しています)

f:id:minmin_21:20190413142159p:plain


このように、YOLOは一枚の画像に対して一回だけネットワークにかけることで、画像内の物体を検出できるようになっています
これが「You only look once」の由来です

そして、ネットワークにかける回数を大幅に減らすことで、リアルタイム検出が可能になりました

まとめ

以上が簡単なYOLOの説明になります
検出の時には一回だけネットワークにかければ良いから、時間をあまりかけることなく物体検出ができるということをお分かりいただけたと思います

参考

kerasで簡単に画像データの拡張を行う

画像データを水増しするときに、kerasのImageDataGeneratorとやらを使うと以下のような感じで簡単に水増しできたので、今回はそれについて紹介していきます

画像の読み込み

まずは画像の読み込みを行います

今回はopencvを使って読み込みを行いました

load image with opencv



注意点としては、opencvはデフォルトのままだとBGR形式で読み込まれるので、RGB形式にする必要があります
以下、BGRとRGBの比較

compare loaded image

データ拡張

つづいて、kerasのImageDataGeneratorを使って拡張を行っていきます

ImageDataGeneratorのインスタンスを作成してflowという関数を実行するだけという、超簡単に画像データを拡張できます  

以下のコードでは、ImageDataGeneratorの引数を見ればわかると思いますが、回転や水平方向の反転などは行わずに水平方向・垂直方向の移動だけ行っています

augment data with keras



datagen.flownumpy.ndarray のfloat型で返してくるので、pyplotで表示する際にはint型に変換しています

学習データとして使用する場合は、 そのままで大丈夫です

もう少し遊んでみる

rotation_range を変えると画像の回転もできます

augment data rotation



brightness_range を list もしくは tuple で指定してあげると、画像の明るさも変更できます

augment data brightness

参考