ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

Terraform内でGithubのreleaseをダウンロードして利用する

おはようございます、こんにちは、こんばんわ ユニファでインフラみてますすずきです。

緊急事態宣言も解除されましたがリモートワークで引きこもり中です。

そんな中、TerraformでGithubからパッケージをダウンロードしながらLambdaにアップロードできるのか試してみたのでそれを書いてみます。

今回ダウンロードしてみるものはDatadog Forwarderです。
プライベートな話ですが、DatadogでLambdaのEnhanced Metricsを取得する記事を書いていたりするのですが(リンクは載せない)、それに利用するのがこのDatadog Forwarderです。
Datadog ForwarderをTerraformで導入しようとすると、Terraform使ってるのにCloudFormationを呼び出してYAMLを食わせるパターンになってしまいます。それはやりたくないということでダウンロードしてみる方法を試してみました。

※ 今回の最終的なコードGist

続きを読む

サーバ / アプリ担当者が混在するチームにおける、ベロシティ計測・リファレンスストーリーの考え方

突然ですが、バーンダウンチャートを眺めていると、あれやこれやを考えて、「あぁ、あのときこうしておけば…」「おっしゃ!良い感じ良い感じ!」「次が楽しみだなぁ…」「こう来られたら、こんなアプローチで…」なんて思ったりしますよね。

…これ、まるで恋をしているようではありませんか?

そうなんです、私達はバーンダウンチャートに恋をしているのです。

本題

改めましてこんにちは、スクラムマスターの渡部です!

今回は、サーバ / アプリ担当者が混在するチームにおける、ベロシティ計測・リファレンスストーリーの考え方について説明していきます。

「サーバ / アプリ担当者が混在するチーム」と記載しましたが、要はそれぞれで専門領域があり、お互いの作業を代わることができない場合のことです。

考えれば納得なのですが、自分も迷いましたし、なかなか言及している記事も見つからないので、同じ悩みを抱える方のためにも残しておきます。

  • 本題
  • 結論
    • 前提
  • ベロシティ計測
    • パターン①:同じチームなんだし、サーバ / アプリの見積もりをまとめて表示しちゃおうよ!案
    • パターン②:担当する作業ごとに分けて表示しようよ!案
  • リファレンスストーリー
  • まとめ
  • さいごに

結論

はじめに結論ですが、同じチームと言えど、お互いの作業を代わることができないのであればリファレンスストーリーは別々で用意し、ベロシティ計測も異なるチームとして扱うべきです。

では何故そうすべきなのか?については、以降のセクションで、「ベロシティ計測」「リファレンスストーリー」の順に図を使いながら説明していきます。

前提

なお、以降の説明では下記の前提条件を使用していきます。

  • 人数
    • サーバ担当:3名
    • アプリ担当:2名
  • 合計見積もり
    • サーバ:250pt
    • アプリ:50pt
  • 1 sprintで一人あたり、 5pt を消化できる
    • サーバ:3名 × 5pt = 15pt
    • アプリ:2名 × 5pt = 10pt
続きを読む

Create SlackBot using AWS API Gateway and Lambda

By Maimit Patel, Software Engineer at ユニファ

In this blog I am going to create SlackBot using AWS API Gateway, AWS Lambda and Slack Event API.

What is SlackBot?


A SlackBot is a regular app that is designed to interact with the user via conversation. When user mention the SlackBot app from anywhere in the slack then it will access the APIs and do all of the magical things that Slack App can do.

Let's get started


1. Setup Slack App

  • Go to the Slack apps home page and click Create New App button.

    f:id:unifa_tech:20200519115444p:plain
    Create Slack App

  • Add scopes under the OAuth & Permissions menu

    f:id:unifa_tech:20200519155321p:plain
    Bot scopes

  • Installing App to Workspace

    f:id:unifa_tech:20200519162951p:plain
    Install App to Workspace

  • This will create Bot User, check it on App Home page

    f:id:unifa_tech:20200519163139p:plain
    Bot User on the App Home

2. Configure Docker Image to run the Lambda function in the local environment

  • Pull the lambci/lambda:ruby2.7 docker image
# Open command prompt in your local machine and 
# hit this command to pull the docker image of Lambda with runtime Ruby-2.7 
$ docker pull lambci/lambda:ruby2.7

3. Create a Lambda function that responds to the Slack

  • Setup application directory to store the Lambda ruby function
# Creating directory
$ mkdir greeting-bot 

# Move to directory
$ cd greeting-bot 
  • Create file greeting_bot.rb under greeting-bot directory
# Creating ruby file under greeting-bot directory
$ touch greeting_bot.rb
  • Set environment variables

    • Find Bot User OAuth Access Token under the OAuth & Permissions menu from Slack App

      $ export BOT_OAUTH_TOKEN='bot_oauth_token'

    • Find Verification Token under Basic Information menu from Slack App

      $ export VERIFICATION_TOKEN='verification_token'

  • Update the file greeting_bot.rb

class GreetingBot
  def self.main(event:, context:)
    new.run(event)
  end

  def run(event)
    case event['type']
    when 'url_verification'
      verify(event['token'], event['challenge'])
    when 'event_callback'
      if event['event']['type'] == 'app_mention'
        process(event['event']['text'], event['event']['channel'])
      end
    end
  end

  private

  # Verify request from the slack
  def verify(token, challenge)
    if token == ENV['VERIFICATION_TOKEN']
      { body: { challenge: challenge } }
    else
      { body: 'Invalid token' }
    end
  end

  def process(text, channel)
    body = if text.strip.downcase.include?('hello')
             'Hi, How are you?'
           else
             'How may I help you?'
           end
    send_message(body, channel)
  end

  # Slack API response to the mentioned channel
  def send_message(text, channel)
    uri = URI('https://slack.com/api/chat.postMessage')
    params = {
      token: ENV['BOT_OAUTH_TOKEN'],
      text: text,
      channel: channel
    }
    uri.query = URI.encode_www_form(params)

    Net::HTTP.get_response(uri)
  end
end
  • Run this Lambda function in the local environment and response back to the mentioned channel from request
# Make sure the mentioned channel(i.e greeting-channel) has already invited the greeting-bot that we have created in step 1

 docker run \
  -e BOT_OAUTH_TOKEN=$BOT_OAUTH_TOKEN \
  --rm -v "$PWD":/var/task lambci/lambda:ruby2.7 \
  greeting_bot.GreetingBot.main \
  '{"type": "event_callback","event":{"type":"app_mention","text":"<@U>hello!","channel":"greeting-channel"}}'
  • Result in local f:id:unifa_tech:20200519164324p:plain

4. Upload Lambda function to the AWS Lambda

  • Make sure you have created a Lambda function in AWS.

  • Upload Lambda function from the local machine using AWS CLI

# Creating zip file
zip function.zip greeting_bot.rb

# Upload zip file to the Lambda
$ aws lambda update-function-code --function-name slack-greeting-bot --zip-file fileb://function.zip
# add `--profile profile_name` if you got the AccessDeniedException
  • Set environment variables BOT_OAUTH_TOKEN and VERIFICATION_TOKEN in Lambda

  • After upload to Lambda, it looks like this

    f:id:unifa_tech:20200520180607p:plain
    Lambda Function

5. Create REST API end-point using API Gateway that calls the lambda function

  • I have created API Gateway

    • How to create REST API using API Gateway? read more
  • Add trigger to Lambda function

    • Click the Add trigger button from the designer pane of the Lambda function
    • Select the trigger(i.e API Gateway) from options
    • Select the REST API created in the step 5

f:id:unifa_tech:20200520100910p:plain
Added API Gateway Trigger to Lambda function

6. Set the API Gateway end-point to Slack Event Request URL

  • Go to Event Subscriptions menu from the Slack App and enable the event subscription
  • Enter and Verify the Request URL on same page
    • Request URL is API Gateway end-point that we have done in step 5
      f:id:unifa_tech:20200520111454p:plain
      Enable event subscription & Verify RESR API end-point

7. Test the SlackBot by calling from #greeting-channel

f:id:unifa_tech:20200520173725g:plain

For full implementation refer GitHub repository
Thank you for reading

新型コロナウイルスと多国籍チーム

みなさんこんばんは。

ユニファでエンジニアのマネージャーをしている田渕です。

これまでユニファの開発者ブログでも、何回かに渡り新型コロナウイルスに端を発した 在宅勤務に関する記事やインフラ関連の記事などを公開してきましたが、 今回は新型コロナウイルス による非日常をマネージャー視点で見たときの気付きについて書いてみました。

まずは前提

本日の内容を理解頂くにあたっては、現在私がマネジメントをさせてもらっているメンバー/チームの構成について まずは説明する必要があります。

  • 現在、私を除くと全部で17名のエンジニアが居ます。

  • そのうち、国籍が日本ではない方は、12名です。

  • 参考までに国名を記載しておくと、中国、韓国、台湾、フィリピン、インド、バングラデシュ、スペイン、イギリス、ポーランド、ロシアです。

  • 他の記事でも触れられているように、現在の勤務は基本的に在宅勤務です。

実は日本国籍のメンバーの方が少ないのです。

日頃のコミュニケーションはどうしているかと言うと、人によってまちまちです。 英語で話す人、英語と日本語を混ぜて話す人、日本語で話す人。 同じ国の出身の方々はそれぞれの母国語でお話したりもしています。

会議などはそのときの参加者によって、話す言葉やドキュメントに記載する言語を変えます。 私自身も英語は微妙なのですが、そこはもう開き直って話しますし、書きます。

f:id:unifa_tech:20200515192510j:plain
ミーティングの様子

不穏な空気が流れはじめた1月末

この数ヶ月、色々なことがあり、なんだか既にとても遠い昔のような感覚ですが。。。 日本国内で「新型コロナウイルス 」の話が本格的に聞かれるようになったのは、1月下旬くらいだったでしょうか。 その頃、まだ日本人はどこか対岸の火事のような感覚だった方が大半ではないかと思います。

先に記載した通り、ユニファには中国国籍のメンバーも居ますし、私自身にも中国人の友人が居ることもあり、 かなり早い段階でそれらの方々から「これはヤバイ。」と言うことを具体的なエピソード付きで言われて警戒していたのを覚えています。 その頃から少しずつ、メンバーのそれぞれの国に関しての情報を集めはじめました。

この環境下でやってきたこと

色々な国のメンバーがいる環境下での非日常は、気をつけることがたくさんあります。 この状況下で、具体的にどんな取り組みをしてきたのかを書いていきたいと思います。

なるべく孤立を防ぐ

背景

メンバーの中には「ユニファに入社するために初めて日本に来た」と言う方もいます。(ありがたいことです。) そのような慣れない環境の中で経験したことのないパンデミック、しかも自粛で部屋に籠もらなければならないと言うのは、なかなかに厳しい状況でした。 やはりメンバーからは「寂しい」と言った声を聞くようになりました。

やったこと

日頃やっている1 on 1などで、日々の生活についてこれまで以上に気にかけて聞くようにしています。 また、月に一度やっているBeer bash(エンジニアのLT会)などを通じ、オンラインで他のメンバーと交流できる機会を作っています。 このあたりはまだケアが足りていないと思っているので、もう少し気軽にコミュニケーションが取れないかと模索しているところです。

情報を届ける

背景

新型コロナウイルスに対する警戒度や対策は、国によって大きく異なります。 日本はどのようなスタンスで、どのような対策を行うつもりなのか、と言うことを知りたいとみなさん思っていることでしょう。 ですが、刻々と変わるこれらの情報に対し、当初は外国語による説明が充分ではありませんでした。 日本語が不得手なメンバーにはやはり情報が届きづらい状況が続きました。

基本的にメンバーは自分の母国のニュースを日々見ていますし、そこに書かれている日本に関する情報が本当ではないこともあります。 また、今回の新型コロナウイルスへの対応を通じて多くの方が感じたように、文化、政治、宗教などの要因により、国によっても考え方が全く異なります。 「日本では法律に基づいた強制的なロックダウンが出来ない」と言うことは、この状況になるまで私を含む多くの方は知らなかったことと思います。 そう言う「日本ならでは」の部分は、外国籍メンバーにとってはそれまでの自分の生活環境、常識とのギャップになります。

やったこと

政府や会社から出された取り組み、方針に対して、都度英語で情報をまとめ、発信するようにしました。 また、その背景、他の国々との違いまで丁寧に伝えるように努めました。 例えば、「日本では海外でやっているような強制的なロックダウンは出来ない」と言ったような部分です。

何が出来て、何をしてはいけないのか。 日本人同士では意外と曖昧にできる線引きを、細かく具体的に伝えるようにしました。

情報収集

背景

様々な国のメンバーがいるので、メンバーのケアのため、またそれぞれの国と日本の違いを説明するためにも、 各国がどのような状況かを把握しておく必要がありました。

やったこと

海外のニュースサイトなどを日常的にあちこち見て回りました。 やはり日本のニュースサイトで記事にされている海外の情報は限定的なので、色々な国の情報を集めるためにはあちこちに飛ぶ必要がありました。 また、それぞれのメンバーから、聞ける範囲で母国の状況を聞くようにしました。 それらの情報はサイトなどを見るよりも一層リアリティ、信憑性があり、色々なことを考えるきっかけになりました。

気付き

自分自身にとっても初めての経験で、色々と試行錯誤しながらここまでやってきましたが、気づいたこともありました。

その気になると意外と海外の情報が手に入る

今まではそこまで真面目に海外のニュースサイトを見ることはなかったのですが、 今回改めて見るようになると、国による報道のスタンスの違いや考え方の違いなども感じることが出来ました。 また、思っていた以上にその気になるとちゃんと情報が拾えるんだな、と言うことも感じました。 これはインターネットの環境が整い、文章だけでなく動画まで含めてストレスなく閲覧できる環境になっている、と言うことが大きいと思います。

さいごに

全世界的に、「新型コロナウイルスとの戦いはまだまだ長いだろう」と言う空気が出始めています。 長期化が予想される中、どのようにすればよりストレスを低減し、安心して働くことが出来るのかと言うことについて、 更に模索していかねばならないと感じています。

一日も早く、元のような日常に戻ることを祈りながら、今後も色々と地味だけれど新しい取り組みを続けて行こうと思います。

ユニファでは一緒に働いてくれる仲間を募集中です!

unifa-e.com

Manager README ver.20200502

皆さんこんにちは、ユニファでCTOをしてます赤沼です。

最近改めて、自分が何をする人間であり、どういうタイプの人間なのかを見つめ直してみようと思う機会がありました。そんな時に下記の yoshiori さんの manager README を目にしたので、私もそれを真似て README を書いてみました。目的は yoshiori さんと同様で、これを公開することで、みんな(=社内の開発メンバー)に私がどういう人間なのか、どういうところでみんなにも力を借りたいと思っているのかを理解してもらい、一方的なトップダウンではなく、お互いの強みを活かしてより良いチームを作っていけるようにしたいというものです。

github.com

yoshiori さん同様に、これによってみんなに何かを強制するものではないですし、自分としてはこうありたいと思うことを書いていますがまだまだ不十分なところも多いです。

My Role

経営陣の1人としてユニファの提供するシステムの責任を負いますが、基本的にプロダクト開発はみんなに任せます。「CTO = 組織内で一番技術力のある人」ではありません。 Ruby や Rails については毎日プロダクションコードを書いているサーバサイドエンジニアメンバーの方が知っているでしょうし、AWSなどのインフラ技術についてはインフラエンジニアの方が詳しいです。デザインについてはもちろんデザイナーの方がセンスも技術もあります。それ以外の職種のメンバー含め、開発チームはそれぞれの領域のプロが集まっているので、みんなの方がそれぞれの領域において私よりレベルが高いですし、高くあるべきと思っています。なのでどうやってプロダクトを作るかはみんなの判断を尊重します。

では私は何をする人なのかというと、そういったプロ集団がチームとしてより良いアウトプットを出すにはどうすれば良いのかを考え、環境や体制をつくり、アウトプットを最大化するためのことをする人です。また、良いプロダクトを作るという視点に、経営としてのビジネスの視点を加え、チームが目指す方向を考えます。ユニファには VPoE がいないこともあり、要素としては技術面よりもメンバーのマネジメントやチームアップなど、 VPoE の役割にあたる要素が多いと思います。また、クリエイティブなことやドキュメント作成は苦手分野で、ビジネス領域には疎いので、そういった部分はみんなに助けてもらいたいと思ってます。

みんながいないとプロダクトは作れないので、力を貸してください。

市場価値を上げたい

もちろんみんなとはずっとユニファで一緒に仕事をしていきたいと思っていますが、この業界では転職も珍しいことではないですし、いずれはユニファを離れていく人も多いかと思います。そうなったときに、ユニファでやってきたことが無駄ではなく、それぞれ個人としての市場価値を上げて次に移れるようになっていて欲しいと思っています。そのためには極力やりたいことやスキルアップにつながるような仕事のアサインをしたいと思っていますし、みんなにも向上心を持って取り組んで欲しいと思っています。

フィードバック

私に対して思うことがあれば気兼ねなくフィードバックください。これは間違ってる、もっとこうして欲しいなど、意見があればいつでも声をかけてください。私もできるだけみんながどう思ってるかは汲み取りたいと思いますが、どうしても言ってもらえないと気づけないこともあります。また、良いと思ってるところも言ってもらえると、そこは変えずにキープしておくのが良いとわかるので助かります。

リスペクトを欠いた言動は嫌います

ユニファのようなスタートアップに来る人は部署問わずみんな頑張れる人ですし頑張って当たり前です。面接でもそういう人を採っているつもりですので、みんな最大限頑張ってくれていると信じてます。頑張った結果、結果が伴わないことや、進め方が良くなかったこと、他の領域のプロからしたらセオリーを無視した形になることもあり得ます。それに対しての嘲笑やリスペクトを欠いたネガティブな発言やslack等での書き込みは問題視します。表面的な結果だけでなく、その裏にある事情も想像しましょう。もちろん建設的な議論やフィードバックは大歓迎です。

縛りたくないしマイクロマネジメントもしたくない

基本的にみんなには自由にやってもらいたいと思ってますので、それぞれやりやすいやり方で、私が細かいことに口を出さなくてもよしなにやってくれるのがお互い楽で良いと思っています。そのためにもみんなには自主性とアウトプットを求めます。リモートワークも相まって、アウトプットがないと私からはみんながどれぐらい仕事をしてるかは見えません。JIRAコメントによるステータスアップデートや Bitbucket のコミットなど、日々の成果はあまりため込まずこまめに積極的にアウトプットしてアピールしてください。

いつでも声かけてください

日々タイトなスケジュールでプロダクト開発をしてくれているみんなに比べたら私なんて暇なので、いつでも声をかけてください。運悪くミーティングの直前や急ぎの仕事があるときに当たってしまった場合は時間をずらさせて欲しいと言うかもしれませんが、気兼ねなく声をかけてもらってOKです。 Slackでの私へのメンションも夜間や休日でもOKです。むしろメンションしてもらった方が見落としがなくなるので助かります。

その他やってること

  • システム開発本部予算検討

    • 毎年の予算計画でシステム開発本部として必要な予算(プロダクトのインフラ費用や開発環境、外部への委託費用などなど)を検討し、会社全体の予算計画の中ですり合わせをします。
  • 技術ブランディング

    • ユニファ開発チームがどんなことをやっているか、どんなメンバーがいるかを外向けに露出する機会を増やし、技術的な信頼度の向上や、採用のためのブランディング活動をしています。
  • 採用関連

    • 人材紹介会社の方にどんな人材を求めているかを説明して打ち合わせしたり、応募者の書類選考、面接、オファー面談などを行っています。
  • 社内インフラタスクサポート

    • 社内インフラチームは1人しかいないので、対応可能なタスクはサポートしています。
  • データ基盤構築での技術検討など

    • データ基盤を構築していくためのアーキテクチャ検討にあたって GCP や AWS において何を使っていくのが良いのかの検討を行っています。

パーソナリティー

  • 内向的&人見知りで自分から話すのは苦手なのでパーティー等ではぼっちになりがちですが、話しかけられるのは大歓迎です。
  • 気弱ですがそのくせ時々勢いで色々始めてみたりします。
  • O型なので大雑把で楽観的です。

これは常にWIPです

この内容は 2020/05/02 現在のスナップショットなので、役割や考え方が変われば今後内容の変更や大幅な修正も大いにあり得ます。

Auto-encoder to Neural Network Learning in Pytorch

By Matthew Millar R&D Scientist at ユニファ

Purpose:

This blog will cover a method for combining unsupervised learning with supervised learning. I will show how to use an autoencoder and combine that with a neural network for a classification problem in Pytorch.

Data Processing:

The first step will be easy as the same dataloader can be used for both training the autoencoder and the neural network.
I will be using the cifar10 dataset as this is available to everyone and is easy to deal with.

#Basic Transforms
SIZE = (32,32) # Resize the image to this shape
# Test and basic transform. This will reshape and then transform the raw image into a tensor for pytorch
basic = transforms.Compose([transforms.Resize(SIZE),
                            transforms.ToTensor()])

# Normalized transforms (0.4914, 0.4822, 0.4465), (0.247, 0.243, 0.261) retrived from here https://github.com/kuangliu/pytorch-cifar/issues/19
mean = (0.4914, 0.4822, 0.4465) # Mean
std = (0.247, 0.243, 0.261) # Standard deviation
# This will transform the image to the Size and then normalize the image
norm_tran = transforms.Compose([transforms.Resize(SIZE),
                                transforms.ToTensor(), 
                                transforms.Normalize(mean=mean, std=std)])

#Simple Data Augmentation
# Data augmentations
'''
Randomly flip the images both virtically and horizontally this will cover and orientation for images
Randomly rotate the image by 15. This will give images even more orientation than before but with limiting the black board issue of rotations
Random Resie and crop this will resize the image and remove any excess to act like a zoom feature
Normalize each image and make it a tensor
'''
aug_tran = transforms.Compose([transforms.RandomHorizontalFlip(),
                               transforms.RandomRotation(15),
                               transforms.RandomResizedCrop(SIZE, scale=(0.08, 1.0), ratio=(0.75, 1.3333333333333333), interpolation=3),
                               transforms.ToTensor(),
                               transforms.Normalize(mean=mean, std=std)])

# Create Dataset
train_dataset = datasets.ImageFolder(TRAIN_DIR, transform=aug_tran)
test_dataset  = datasets.ImageFolder(TEST_DIR, transform=norm_tran) #No augmentation for testing sets
# Data loaders
# Parameters for setting up data loaders
BATCH_SIZE = 32
NUM_WORKERS = 4
VALIDATION_SIZE = 0.15

# Validatiaon split
num_train = len(train_dataset) # Number of training samples
indices = list(range(num_train)) # Create indices for each set
np.random.shuffle(indices) # Randomlly sample each of these by shuffling
split = int(np.floor(VALIDATION_SIZE * num_train)) # Create the split for validation
train_idx , val_idx = indices[split:], indices[:split] # Create the train and validation sets
train_sampler = SubsetRandomSampler(train_idx) # Subsample using pytroch
validation_sampler = SubsetRandomSampler(val_idx) # same here but for validation

# Create the data loaders
train_loader = DataLoader(train_dataset, 
                          batch_size=BATCH_SIZE,
                          sampler=train_sampler, 
                          num_workers=NUM_WORKERS)

validation_loader = DataLoader(train_dataset, 
                               batch_size=BATCH_SIZE,
                               sampler=validation_sampler,
                               num_workers=NUM_WORKERS)

test_loader = DataLoader(test_dataset, 
                         batch_size=BATCH_SIZE, 
                         shuffle=False, 
                         num_workers=NUM_WORKERS)

Also, I have a list of dataloaders on my Kaggle page for both Pytorh and Keras if you would like to learn how to build out custom dataloader and datasets with both languages.
https://www.kaggle.com/matthewmillar/pytorchdataloaderexamples
https://www.kaggle.com/matthewmillar/kerasgeneratorexamples

Autoencoder:

An autoencoder is an unsupervised method of learning encodings of data which that can be processed efficiently. This is done through dimension reduction and ignoring noise in the dataset. There are two sides to an autoencoder. The encoder and the decoder. The encoder job is to create a useful encoding that will remove unwanted noise in the dataset while keeping the most import parts of the data. The decoder job is to take the encodings and reassemble it into the original input form. Below is the Autoencoder that we will be using as the feature extraction system in our combination model.

The approach that will be taken is to train the autoencoder separately instead of together with the NN. This will allow for us to check the result of the output of the encoder as well as the decoder and see how well it works.

# define the NN architecture
class ConvAutoencoder(nn.Module):
    def __init__(self):
        super(ConvAutoencoder, self).__init__()
        ## encoder layers ##
        # conv layer (depth from 1 --> 16), 3x3 kernels
        self.conv1 = nn.Conv2d(3, 16, 3, padding=1)  
        # conv layer (depth from 16 --> 4), 3x3 kernels
        self.conv2 = nn.Conv2d(16, 4, 3, padding=1)
        # pooling layer to reduce x-y dims by two; kernel and stride of 2
        self.pool = nn.MaxPool2d(2, 2)
        
        ## decoder layers ##
        ## a kernel of 2 and a stride of 2 will increase the spatial dims by 2
        self.t_conv1 = nn.ConvTranspose2d(4, 16, 2, stride=2)
        self.t_conv2 = nn.ConvTranspose2d(16, 3, 2, stride=2)


    def forward(self, x):
        ## encode ##
        # add hidden layers with relu activation function
        # and maxpooling after
        x = torch.relu(self.conv1(x))
        x = self.pool(x)
        # add second hidden layer
        x = torch.relu(self.conv2(x))
        x = self.pool(x)  # compressed representation
        
        ## decode ##
        # add transpose conv layers, with relu activation function
        x = torch.relu(self.t_conv1(x))
        # output layer (with sigmoid for scaling from 0 to 1)
        x = torch.sigmoid(self.t_conv2(x))
                
        return x

# Loss and optimizers
loss_function = nn.MSELoss()
optimizer = torch.optim.Adam(ae_model.parameters(), lr=0.001)
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer=optimizer, mode='min', factor=0.1, patience=3, verbose=True) # Automatically reduce learning rate on plateau

# number of epochs to train the model
n_epochs = 35
ae_model_filename = 'cifar_autoencoder.pt'
train_loss_min = np.Inf # track change in training loss

ae_train_loss_matrix = []
for epoch in range(1, n_epochs+1):
    # monitor training loss
    train_loss = 0.0
    
    ###################
    # train the model #
    ###################
    for data in train_loader:
        # _ stands in for labels, here
        # no need to flatten images
        
        images, _ = data
        if use_gpu:
            images = images.cuda()
        # clear the gradients of all optimized variables
        optimizer.zero_grad()
        # forward pass: compute predicted outputs by passing inputs to the model
        outputs = ae_model(images)
        # calculate the loss
        loss = loss_function(outputs, images)
        # backward pass: compute gradient of the loss with respect to model parameters
        loss.backward()
        # perform a single optimization step (parameter update)
        optimizer.step()
        # update running training loss
        train_loss += loss.item()*images.size(0)
            
    # print avg training statistics 
    train_loss = train_loss/len(train_loader)
    scheduler.step(train_loss)
    ae_train_loss_matrix.append([train_loss, epoch])
    
    print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))
    
    # save model if validation loss has decreased
    if train_loss <= train_loss_min:
        print('Validation loss decreased ({:.6f} --> {:.6f}).  Saving model ...'.format(
        train_loss_min,
        train_loss))
        torch.save(ae_model.state_dict(), ae_model_filename)
        train_loss_min = train_loss

f:id:unifa_tech:20200424135716p:plain
AE Loss
f:id:unifa_tech:20200424135814p:plain
Encoder Results
Looking at the above image the encoder works ok so we can use this with confidence.

Neural Network.

This will be the classification and supervised learning section of the model. The first this we need to do is freeze the autoencoder to ensure that its weights and bias do not get updated during training. Now we will define the NN using the autoencoder maxpooling layer as the output (the encoder part) and add on top of that Fully connected layers with a dropout layer as well to help normalize the output.
Here is the training code.

class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        image_modules = list(ae_model.children())[:-2] #get only the encoder layers
        self.modelA = nn.Sequential(*image_modules)
        # Shape of max pool = 4, 112, 112
        self.fc1 = nn.Linear(4*16*16, 1024)
        self.fc2 = nn.Linear(1024,512)
        self.out = nn.Linear(512, 10)
        
        self.drop = nn.Dropout(0.2)
        
    def forward(self, x):
        x = self.modelA(x)
        x = x.view(x.size(0),4*16*16)
        x = torch.relu(self.fc1(x))
        x = self.drop(x)
        x = torch.relu(self.fc2(x))
        x = self.drop(x)
        x = self.out(x)
        return x

#Freze the autoencoder layers so they do not train. We did that already
# Train only the linear layers
for child in model.children():
    if isinstance(child, nn.Linear):
        print("Setting Layer {} to be trainable".format(child))
        for param in child.parameters():
            param.requires_grad = True
    else:
        for param in child.parameters():
            param.requires_grad = False

# Optimizer and Loss function
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr= 0.001)
# Decay LR by a factor of 0.1 every 7 epochs
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer=optimizer, mode='min', factor=0.1, patience=3, verbose=True)

model_filename = 'model_cifar10.pt'
n_epochs = 40
valid_loss_min = np.Inf # track change in validation loss
train_loss_matrix = []
val_loss_matrix = []
val_acc_matrix = []

for epoch in range(1, n_epochs+1):

    # keep track of training and validation loss
    train_loss = 0.0
    valid_loss = 0.0
    
    train_correct = 0
    train_total = 0
    
    val_correct = 0
    val_total = 0
    
    
    ###################
    # train the model #
    ###################
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        # move tensors to GPU if CUDA is available
        if use_gpu:
            data, target = data.cuda(), target.cuda()
        # clear the gradients of all optimized variables
        optimizer.zero_grad()
        # forward pass: compute predicted outputs by passing inputs to the model
        output = model(data)
        # calculate the batch loss
        loss = criterion(output, target)
        # backward pass: compute gradient of the loss with respect to model parameters
        loss.backward()
        # perform a single optimization step (parameter update)
        optimizer.step()
        # update training loss
        train_loss += loss.item()*data.size(0)
        
        
    ######################    
    # validate the model #
    ######################
    model.eval()
    val_acc = 0.0
    for batch_idx, (data, target) in enumerate(validation_loader):
        # move tensors to GPU if CUDA is available
        if use_gpu:
            data, target = data.cuda(), target.cuda()
        # forward pass: compute predicted outputs by passing inputs to the model
        output = model(data)
        # calculate the batch loss
        loss = criterion(output, target)
        # update average validation loss 
        valid_loss += loss.item()*data.size(0)
        
        val_acc += calc_accuracy(output, target)
        
        
    
    # calculate average losses
    train_loss = train_loss/len(train_loader.sampler)
    valid_loss = valid_loss/len(validation_loader.sampler)
    #exp_lr_scheduler.step()
    scheduler.step(valid_loss)
    
    # Add losses and acc to plot latter
    train_loss_matrix.append([train_loss, epoch])
    val_loss_matrix.append([valid_loss, epoch])
    val_acc_matrix.append([val_acc, epoch])
        
    # print training/validation statistics 
    print('Epoch: {} \tTraining Loss: {:.6f} \tValidation Loss: {:.6f}\tValidation Accuracy: {:.6f}'.format(
        epoch, train_loss, valid_loss, val_acc))
    
    # save model if validation loss has decreased
    if valid_loss <= valid_loss_min:
        print('Validation loss decreased ({:.6f} --> {:.6f}).  Saving model ...'.format(
        valid_loss_min,valid_loss))
        
        torch.save(model.state_dict(), model_filename)
        valid_loss_min = valid_loss

Training the model will give the final accuracy for each class.

Test Accuracy of airplane: 45% (231/504)
Test Accuracy of automobile: 61% (312/504)
Test Accuracy of  bird: 18% (91/496)
Test Accuracy of   cat: 11% (55/496)
Test Accuracy of  deer: 27% (139/504)
Test Accuracy of   dog: 35% (181/504)
Test Accuracy of  frog: 63% (315/496)
Test Accuracy of horse: 49% (244/496)
Test Accuracy of  ship: 59% (298/504)
Test Accuracy of truck: 46% (234/504)

Test Accuracy (Overall): 41% (2100/5008)

Conclusion:

Looking at the loss and validation accuracy the accuracy is moving up steadily (all be it a little jumpy) while the losses are both decreasing with the validation loss consistently less than training loss. This shows that the model is not overfitting or underfitting, so it is learning well going forward. The accuracy is a little low compared to simply supervised learning, but giving enough time the accuracy could get higher.

f:id:unifa_tech:20200424140004p:plain
Validation Accuracy
f:id:unifa_tech:20200424140027p:plain
Loss

Covid-19が気づかせてくれたやっときゃよかったインフラ作業達

はじめましてこんにちは。
ユニファでインフラエンジニアをしているすずきけいたです。
フルネームで書くのは同じインフラエンジニアのすずきがもう一名いるためです。
入社して1年半経ちました。本当に早い。。
現在200名前後いる全社員のPC周り、アプリで使用している全てのAppleデバイスのProfile Manager(MDM)を対応をしております。
※別すずき参考
ユニファのシステムの移り変わり(後編) - ユニファ開発者ブログ


世界中に広がりを見せるCovid-19によるパンデミック。
皆様ご無事にお過ごしでしょうか。
弊社でも原則リモートワークが始まってから早3週間。
今回のCovid-19は私にとってもインフラエンジニアとしての、新たな気付きをさせてもらっている最中です。
インフラエンジニアとしてCovid-19問題発生以前にやっておけばよかった事、また発生後対応した事に関してご紹介させていただきたいと思います。
仕事の合間の箸休めにでもなれば幸甚です。


menu

1.お知らせ

タイトルと相反してまず初めにお知らせをさせて下さい。
弊社では以下の10都道府県2000施設の保育園の先生にマスクを届けるプロジェクトを実行中です。
北海道、埼玉県、千葉県、東京都、神奈川県、愛知県、京都府、大阪府、兵庫県、福岡県
readyfor.jp

ぜひご協力をお願いいたします。

2.個人的リモート環境に関して

・リモートワークは既に7年ほど対応経験があるので、基本慣れている。
・基本狭さ1畳半程度の書斎に籠ってる。(この広さが気に入って家を決めた)
・運動不足解消のために朝の4時に起きて4キロ走っている。
・家事の積極的な参加をしている。気分転換にもなるが何より嫁さんの機嫌がよくなります!皆さんも、ぜひ!!


3.やっときゃよかった4選

1位 VDI(Virtual Desktop Infrastructure)
・VDIなどの物理的デバイスのワークアラウンドは本当にやっておけばよかった事の一つです。
VDIとは専用サーバー上に端末が利用する仮想デスクトップ環境を構築し、ユーザーは専用のコンソール(ブラウザ)からそこにアクセスすることで業務が行えます。(Amazon WorkSpaces、Microsoft Hyper-V、VMwareなど)

f:id:unifa_tech:20200424133742p:plain
VDIイメージ

弊社ではエンジニアを除くと9割近くがWindowsPCになり、後述しますがPCのやりくりが大変でした。。
一時的な金額はかかりますが、増減が容易で、セキュリティもサーバ側で管理が行えるので非常に便利です。
今後このCovid-19が長引くにつれ必要性が増えていくかと思われます。


2位 RDP(Remote Desktop Protocol)
・RDPは遠隔地にあるWindowsの画面を、直接インフラエンジニアが見ながら修正や対応をしていくことができる技術になります。Macで言う所のVNCです。
技術的な問題が発生しても、slackやconfluenceなどで詳細を聞く必要がなくなり遠隔で直接操作しながら対応することができるため、お互いにスマートに対応ができます。
RDPに関しては既にポート、Firrewall開閉のバッチは作成済なので、テストをしながら今後導入予定になっております。
しかし、RDPに関しては脆弱性なども少なからず存在するので、chromeリモートデスクトップなど代替案もテストしている最中です。
monokoto68.com



 3位 紙の書類
・紙の書類の撤廃は弊社ではかなり進んでいるかと思っておりましたが、やはりまだ一部の請求書やシステムのアウトプットが紙の部分があり、そのために出社すると言う事が発生しておりました。
今後長期化するであろう自粛、リモートワークに向けて完全になくせる様に対応していきたいと考えております。


 4位 NASのGoogle Drive移行
・弊社は業務データをNASに蓄積をしていたのですが、2018年には一部の部署でGoogle Driveの使用を開始しておりました。
しかしながら全部署に徹底されておらず、移行もしくは同期をもっと早く進めておけばよかったと思っております。
Covid-19発生時を境に、皆さんにGoogle Driveを使用して頂くようにご協力いただきながら、少しずつ移行を進めております。

4.対応した事

・デスクトップユーザーをリモート対応
これが一番対応に苦労した。
弊社はPCをレンタルをしているのだが、社員、パートナーの20人はデスクトップで、代替機が必要になりました。
レンタル業者のストックは底をついており、次に入荷されるのが1か月半後…
レンタルリースアップのPCを10台返却を取りやめ、
使えそうなPCを15台かき集めたが、絶対数が足りなかったので
デスクトップ自体を自宅配送するなどの対応しました。


・VPNを設定
弊社システムではIP制限をかけているものが多く、VPN接続が必要な方が多かったため総勢30名程度のVPN接続設定などをしました。


・自宅のネット環境ないor乏しい
自宅で作業をする環境が整っていない人が多く、モバイルのWifiなどを急遽用意する。



皆さんも長期化するであろうこの難局を、改善しながら上手く乗り越えていきましょう。