WonderPlanet Tech Blog

アドバンストテクノロジー部やCTO室のメンバーを中心に最新の技術情報を発信しています。

機械学習で大量のテキストをカテゴリ別に分類してみよう!

こんにちはアドバンストテクノロジー部の@y-matsushitaです。
今回は機械学習を使った取り組みとして、手始めにfastTextを使ったテキストの分類について触れたいと思います。

fasttext.cc

fastTextとはFacebookが提供する単語のベクトル化とテキスト分類をサポートした機械学習ライブラリです。
fastTextという名前の通り動作が軽く早いのが特徴です。試しに使ってみたところ精度も良好で動作も軽かったのでご紹介させていただきます!
今回は試しに様々な情報が入り混じったTwitterの投稿内容を分類して「美容系」「エンタメ系」「暮らし系」情報の3パターンに分類してみます。 なお今回の記事ではPython 3.6.1を使用します。


fastTextを使ってできること

まず最初にfastTextを使った結果をお見せします。
『分類前』が処理前で『分類後』がfastTextを使って分類した結果です。

『分類前』
f:id:y-matsushita:20170929141107p:plain:w600
緑:美容系 , 青:エンタメ系 , 赤:暮らし系

上記のように分類前は色々なジャンルの投稿内容が入り混じった状態です。
これをfastTextで分類すると以下のような形になります。

→『分類後(美容系)』
f:id:y-matsushita:20170929141341p:plain:w600

→『分類後(エンタメ系)』
f:id:y-matsushita:20170929141501p:plain:w600

→『分類後(暮らし系)』
f:id:y-matsushita:20170929141532p:plain:w600

上記のように概ね文章を「美容系」「エンタメ系」「暮らし系」などに分類することができました。 *1
このように文章からカテゴリごとに自動分類したり、スパム的な投稿内容を検知したりもできます。
作成するモデル次第でかなり応用が効きそうな感じです。

また、単語のベクトル表現をつくることで、
SNSユーザの投稿内容からおすすめを紹介するレコメンド機能なども活用事例の代表です。


fastTextをインストール

fastTextはGitHubからダウンロード可能です。
以下のコマンドでダウンロードすると実行できるようになります。

$ git clone https://github.com/facebookresearch/fastText.git
$ cd fastText
$ make
$ pip install cython
$ pip install fasttext

学習用のテキストを用意する

学習用のテキストは過去にTwitterに投稿された「美容系」と「エンタメ系」と「暮らし系」のワードを含むツイートから作成します。

TwitterのAPI Keyを用意

Twitterの内容はAPIから取得します。
TwitterのAPI Keyを持っていない場合は予め用意する必要があります。
Twitter Application Management

MeCabをインストール

日本語は英語とは異なり単語同士がスペースで区切られていないため、分かち書きをする必要があります。
今回はMeCabを利用しました。 mecab-python3

MeCabをインストールしていない場合、以下のコマンドでインストールを行います。

$ brew install mecab mecab-ipadic
$ pip install mecab-python3


投稿の取得からテキスト出力までは以下のコードで行います。
TwitterのAPI Keyと取得したいカテゴリが含まれるキーワードを入力して取得します。

tweet_get.py

import re
import json
import MeCab
from requests_oauthlib import OAuth1Session

CK = "(用意したConsumer Keyを入力)"
CS = "(用意したConsumer Secretを入力)"
AT = "(用意したAccess Tokenを入力)"
AS = "(用意したAccess Token Secretを入力)"

API_URL = "https://api.twitter.com/1.1/search/tweets.json?tweet_mode=extended"
KEYWORD = "芸能 OR アニメ OR 漫画 OR TV OR ゲーム"            #エンタメ系のキーワードを入力
CLASS_LABEL = "__label__1"

def main():
    tweets = get_tweet()                #ツイートを取得
    surfaces = get_surfaces(tweets)     #ツイートを分かち書き
    write_txt(surfaces)                 #ツイートを書き込み

def get_tweet():
    """
    TwitterからKEYWORDに関連するツイートを取得
    """
    params = {'q' : KEYWORD, 'count' : 100}
    twitter = OAuth1Session(CK, CS, AT, AS)
    req = twitter.get(API_URL, params = params)
    results = []
    if req.status_code == 200:
        # JSONをパース
        tweets = json.loads(req.text)
        for tweet in tweets['statuses']:
            results.append(tweet['full_text'])
        return results
    else:
        # エラー
        print ("Error: %d" % req.status_code)

def get_surfaces(contents):
    """
    文書を分かち書きし単語単位に分割
    """
    results = []
    for row in contents:
        content = format_text(row)
        tagger = MeCab.Tagger('')
        tagger.parse('')
        surf = []
        node = tagger.parseToNode(content)
        while node:
            surf.append(node.surface)
            node = node.next
        results.append(surf)
    return results

def write_txt(contents):
    """
    評価モデル用のテキストファイルを作成する
    """
    try:
        if(len(contents) > 0):
            fileNema = CLASS_LABEL + ".txt"
            labelText = CLASS_LABEL + ", "

            f = open(fileNema, 'a')
            for row in contents:
                # 空行区切りの文字列に変換
                spaceTokens = " ".join(row);
                result = labelText + spaceTokens + "\n"
                # 書き込み
                f.write(result)
            f.close()

        print(str(len(contents))+"行を書き込み")

    except Exception as e:
        print("テキストへの書き込みに失敗")
        print(e)

def format_text(text):
    '''
    ツイートから不要な情報を削除
    '''
    text=re.sub(r'https?://[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", text)
    text=re.sub(r'@[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", text)
    text=re.sub(r'&[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", text)
    text=re.sub(';', "", text)
    text=re.sub('RT', "", text)
    text=re.sub('\n', " ", text)
    return text

if __name__ == '__main__':
    main()

取得後のテキストのフォーマットは以下の様に、見出しに__label__*と分類用のラベルを付けています。
この場合、__label__1に「美容系」、__label__2に「エンタメ系」、__label__3に「暮らし系」を設定しています。 分類ごとにCLASS_LABELとキーワードを変えて実行します。

__label__1,美容 室 の カラー と 自宅 で の カラー リング の 違い と は 。 。 。
__label__1,はちみつ に は 、 優れ た 保 湿 力 が あり ます 。 美容 に 優れ た 効果 が あり ます 。
__label__2,【 名 台詞 】 「 あきらめ たら そこ で 試合 終了 です よ … ? 」 某 人気 漫画 より
__label__2,主 に アニメ の 絵 や 漫画 を 描い たり 、 ブログ など の 活動 を し て い ます 。 pixiv で 漫画 を 投稿 し 、 ブログ で 発表 を 繰り返し て ます 良けれ ば 話しかけ て ください
__label__3,うち の ペット の 犬 が 食後 に 家 の 中 で 穴 を 掘ろ う と する ん だ けど 、 どうして だろ う 。
__label__3,もうすぐ 修学旅行 ! 高校 生活 の ひとつ で ある 行事 楽しん で き ま ー す

今回は約3000件*3の投稿内容を用意しました。
上記のコードでは一度に100件までしか取得できないため、
何回か時間を置いて繰り返し実行してデータをためてください。

取得直後のテキストはカテゴリごとにバラバラの状態なので、CATコマンドで結合させましょう。

$ cat __label__1.txt __label__2.txt __label__3.txt > model.txt

テキストからモデルを生成する

取得した「model.txt」からfastTextのモデルを生成します。
生成は以下のコードで行います。変換自体は1行で完結します。

learning.py

import sys
import fasttext as ft

argvs = sys.argv
input_file = argvs[1]
output_file = argvs[2]

classifier = ft.supervised(input_file, output_file)

input_fileに学習用のテキストファイル名、 output_fileに生成後のモデル名を設定します。

$ python learning.py model.txt model

生成が終わるとmodel.binというモデルが出力されます。


文章の分類

fastTextにモデルと新たに判断したいテキストを渡すと、 判定結果の分類とその判定が下される確率が取得できます。
判定結果が入力した文章に近いほど確率の値が高くなります。

prediction.py

import sys
import fasttext as ft
import MeCab

class predict:

    def __init__(self):
        # モデル読み込み
        self.classifier = ft.load_model('model.bin')

    def get_surfaces(self, content):
        """
        文書を分かち書き
        """
        tagger = MeCab.Tagger('')
        tagger.parse('')
        surfaces = []
        node = tagger.parseToNode(content)

        while node:
            surfaces.append(node.surface)
            node = node.next

        return surfaces


    def tweet_class(self, content):
        """
        ツイートを解析して分類を行う
        """
        words = " ".join(self.get_surfaces(content))
        estimate = self.classifier.predict_proba([words], k=3)[0][0]

        if estimate[0] == "__label__1,":
            print('美容系', estimate[1])
        elif estimate[0] == "__label__2,":
            print('エンタメ系', estimate[1])
        elif estimate[0] == "__label__3,":
            print('暮らし系', estimate[1])


if __name__ == '__main__':
    pre = predict()
    pre.tweet_class("".join(sys.argv[1:]))

実行結果

以下のようにコマンド入力し最後に判定するテキスト内容を貼り付けて実行します。

$ python prediction.py "判定するテキスト"

試しにTwitterから学習に使用していない適当なツイートで判定を行います。

python prediction.py "化粧水・乳液・美容液がひとつになった基礎化粧品が本日発売開始"
美容系 0.998047
python prediction.py "10月○日より劇場版公開予定!東京で舞台挨拶が行われました"
エンタメ系 0.654297
python prediction.py "お買い得な生鮮食品を毎日お届け。格安情報はこちら。"
暮らし系 0.8125

結構いい感じに分類してくれている気がします。
また取得した投稿3000件*3を学習用、100件*3を検証用に別で用意し実験したところ、 96.333%の精度で分類できました。*2


分類が難しい点

試していく中で分類がうまくいってないところがあったのでご紹介します。

のび太「あったかいふとんでぐっすりねる。こんな楽しいことがほかにあるか。」
(エンタメ系 0.443359 暮らし系 0.439453 美容系 0.115234)
エンタメ系も入ってますが、かなり暮らし系寄りですね。
登場人物のセリフとかになると分類はかなり難しくなりそうです。
またこのセリフにはありませんが健康などについて触れると、美容系も高くなってきます。

メジャーリーグでも活躍した某野球選手が美容整形をカミングアウト!スタジオは大騒然!
(美容系 0.996094 エンタメ系 1.95313e-08 暮らし系 1.95313e-08)
こちらは美容整形という部分に大きく反応して美容系に寄ってしまってます。
学習に使用したテキスト次第で特定のキーワードが強くなりすぎてしまうのかもしれません。


まとめ

fastTextを使って文章を「美容系」、「エンタメ系」、「暮らし系」に分類しました。
文章の分類だけでなくネガポジ判定や特定の単語に似たワードを抽出するなどにも使えるので、活用の幅は多そうです。
思いの外、簡単に実装できたのでチャレンジしてみてはいかがでしょうか!

*1:公開されているとはいえ一般の方のツイートを使うのは抵抗があったため記事内のツイートは一部架空のものを使用しています。

*2:学習用データと検証用のデータの取得方法が同じ場合での結果なので、実際に運用した場合の精度はもっと下がると思われます。

Core MLを利用した機械学習とVisionでの画像認識

先日リリースされたiOS 11でCore MLとVisionフレームワークが新たに追加されました。
これはWWDC 2017で発表されたもので、Core MLはiOS上で機械学習の学習モデルを利用するための仕組みで、Visionは画像認識に関する機能を集めたフレームワークです。
これらの機能でどんなことが出来るのか紹介します。

Core MLでできること

学習モデルをiOS上で取り扱うための仕組みです。
学習モデルに何かしらの判断をさせたいデータを渡し、その結果を受け取るための橋渡しをしてくれます。
クラウドやサーバー上ではなく、iOS端末内で機械学習モデルを利用します。
あらかじめ用意した学習モデルを端末内で利用するもので、学習モデルの再学習をするものではありません。

学習モデルをiOSアプリで利用するには

coremltools」というPythonで動作するツールが用意されています。
Xcode上で学習モデルを利用するには「.mlmodel」という形式に変換する必要があり、coremltoolsでその変換を行います。

coremltoolsが対応している学習モデルの種類

Model Family Supported Packages
Neural NetworksKeras (1.2.2, 2.0.4+)
Caffe 1.0
Tree EnsemblesXGboost (0.6)
scikit-learn 0.18.1
Generalized Linear Modelsscikit-learn (0.18.1)
Support Vector MachineslibSVM (3.22)
scikit-learn (0.18.1)
Feature Engineeringscikit-learn (0.18.1)
Pipelinesscikit-learn (0.18.1)
coremltoolsの環境構築

大まかな手順は以下の通りです。

  1. virtualenvでPythonが動く環境用意する。
  2. pip install -U coremltoolsでツールをインストールする。
  3. coremltoolsを利用するために必要な下記のツールをpipでインストールする。
    ・numpy (1.12.1+)
    ・protobuf (3.1.0+)
    ・Keras (1.2.2+, 2.0.4+)
    ・Tensorflow (1.0.x, 1.1.x)
    ・Xgboost (0.6+)
    ・scikit-learn (0.15+)
    ・libSVM

詳細なインストール手順はcoremltoosのドキュメントを参照してください。

変換の例(.h5 → .mlmodel)

TensorFlowとKerasで作成した犬と猫の画像をどちらか判別する学習モデル「model.h5」をXcodeで扱える形式「convertedmodel.mlmodel」 に変換する例です。
以下の様なPythonコードをターミナルで実行します。
.mlmodelに変換されたファイルをXcodeのプロジェクト内に取り込むことができます。

from keras import backend as K
from keras.datasets import mnist
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers import Dense, Flatten, Convolution2D, MaxPooling2D
from keras.optimizers import Adadelta
import keras

import coremltools
path = 'model.h5'

# kerasで作成した学習モデルの変換をするメソッド。
# 入力を画像としています。入力した画像が”dog”と”cat”の2種類のラベルで分類されます。
coreml_model = coremltools.converters.keras.convert(
    path, 
    image_input_names='image', 
    is_bgr = True, 
    image_scale = 1.0, 
    predicted_feature_name = "label", 
    class_labels = ['dog', 'cat'])

# 変換後のファイルを出力する
coreml_model.save('convertedmodel.mlmodel')

coremltoolsのドキュメント
https://developer.apple.com/documentation/coreml/converting_trained_models_to_core_ml

Visionでできること

主に画像認識の機能を備えたフレームワークです。
機能としては、以下になります。

  • Face Detection and Recognition(顔の検出)
  • Machine Learning Image Analysis(機械学習での画像分析)
  • Barcode Detection(バーコード認識)
  • Image Alignment Analysis(画像の位置合わせ)
  • Text Detection(文字認識)
  • Horizon Detection(地平線認識)
  • Object Detection and Tracking(物体の移動トラッキング)

それぞれどういった事ができるのか、説明します。

Face Detection and Recognition(顔の検出)

カメラやイメージピッカーで選択した写真の中から人の顔を検出し位置・範囲を特定します。
ランドマーク(目、鼻、口)の位置の検出も可能です。 f:id:HidehikoKondo:20170928192044p:plain f:id:HidehikoKondo:20170928192100p:plain

Machine Learning Image Analysis(機械学習での画像分析)

別途作成したラベル付き画像を学習させたモデルに、画像を認識させ、モデルの学習内容に応じて分類します。
この記事の後半で実装例を紹介します。

Barcode Detection(バーコード認識)

QRコードなどバーコードの読み込みます。 AVFoundationフレームワークにバーコードを読み込む機能はありましたが、同時に複数のバーコードを認識できるなどの違いがあります。

Image Alignment Analysis(画像の位置合わせ)

アフィン変換、ホモグラフィック変換、画像レジストレーションなどの画像処理をしてくれるクラスです。
写真をつなぎ合わせてパノラマ写真の様な画像の作成などができます。
f:id:HidehikoKondo:20170928192340p:plain

Text Detection(文字認識)

画像内の文字を認識し、その位置と範囲の特定をします。
1文字毎と文節毎で位置と範囲の特定ができる。日本語でも認識することが可能です。
※認識した文字が「A」とか「あ」であるとか、OCRの様な機能ではありません。それを実現するには追加で実装が必要となります。 f:id:HidehikoKondo:20170928191958p:plain

Horizon Detection(地平線認識)

風景などの写真でどのくらい傾いているのかを取得できます。
取得した値を使うことで、傾いている写真を回転させて、地平線に対して水平になるように補正できます。 f:id:HidehikoKondo:20171002162340p:plain

Object Detection and Tracking(物体の移動トラッキング)

カメラで撮影している被写体の移動を検知し、位置をトラッキングします。 f:id:HidehikoKondo:20170928192401p:plain

Machine Learning Image Analysisの利用例

ここからは「Machine Learning Image Analysis」の実装例を説明します。
XcodeでVGG16学習モデルを取り込み、iOSアプリで写真に写っている物体が何であるかの認識をさせてみましょう。

動作環境
  • iOS11をインストールしたiOS端末
  • Xcode 9
  • Swift 4.0
  • VGG16学習モデル(.mlmodelに変換したもの)
学習モデルの入手

coremltoolsを使うことで、自分で作成した学習モデルを扱うこともできますが、
今回はお手軽にAppleが公開している.mlmodelに変換済みの学習モデルを用います。
下記のURLからVGG16の学習モデルをダウンロードしてください。
https://developer.apple.com/machine-learning/ f:id:HidehikoKondo:20170929125652p:plain

学習モデルの取り込み

ダウンロードした「VGG16.mlmodel」をXcodeにドラッグ&ドロップします。
Target Membershipのチェックボックスを忘れずにチェックしておきます。
Model Evaluation Parametersの欄を見ると、学習モデルの入力が画像で、出力は文字列でラベルが返ってくることがわかります。
f:id:HidehikoKondo:20170929142843p:plain

コード
import UIKit
import CoreML
import Vision
import ImageIO


class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    @IBOutlet weak var cameraView: UIImageView!
    @IBOutlet weak var resultLabel: UILabel!

    var inputImage: CIImage!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func imagePickerController(_ imagePicker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        self.resultLabel.text = "Analyzing Image…"
        if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
            self.cameraView.contentMode = .scaleAspectFit
            self.cameraView.image = pickedImage
        }
        imagePicker.dismiss(animated: true, completion: {
            guard let uiImage = info[UIImagePickerControllerOriginalImage] as? UIImage
                else { fatalError("no image from image picker") }
            guard let ciImage = CIImage(image: uiImage)
                else { fatalError("can't create CIImage from UIImage") }
            let orientation = CGImagePropertyOrientation(rawValue: UInt32(uiImage.imageOrientation.rawValue))
            self.inputImage = ciImage.oriented(forExifOrientation: Int32(orientation!.rawValue))

            //リクエストハンドラの作成。ここでカメラで撮影した画像を渡します。
            let handler = VNImageRequestHandler(ciImage: self.inputImage)
            do {
                try handler.perform([self.classificationRequest_vgg])
            } catch {
                print(error)
            }
        })
    }

    //VGG16学習モデルで画像を認識させるリクエストの作成
    lazy var classificationRequest_vgg: VNCoreMLRequest = {
        do {
            var model: VNCoreMLModel? = nil
            model = try! VNCoreMLModel(for: VGG16().model)
            return VNCoreMLRequest(model: model!, completionHandler: self.handleClassification)
        } catch {
            fatalError("can't load Vision ML model: \(error)")
        }
    }()

    //学習モデルが持っているラベルに応じて分類した結果の表示。分類結果の1個目(一番confidenceが高いもの)を表示します。
    //分類結果はVNClassificationObservation型のオブジェクトで返ってきます。
    func handleClassification(request: VNRequest, error: Error?) {
        guard let observations = request.results as? [VNClassificationObservation]
            else { fatalError("unexpected result type from VNCoreMLRequest") }
        guard let best = observations.first
            else { fatalError("can't get best result") }

        DispatchQueue.main.async {
            var classification: String = (best.identifier);
            self.resultLabel.text = "Classification: \"\(classification)\" Confidence: \(best.confidence)"
        }
    }

    @IBAction func openCamera(_ sender: Any) {
        let sourceType:UIImagePickerControllerSourceType = UIImagePickerControllerSourceType.camera
        if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera){
            let cameraPicker = UIImagePickerController()
            cameraPicker.sourceType = sourceType
            cameraPicker.delegate = self
            self.present(cameraPicker, animated: true, completion: nil)
        } else {
            print("error")
        }
    }

    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true, completion: nil)
    }
}

storyboardでは撮影した写真用のUIImageView・結果表示のラベル・カメラ希望のボタンを配置して、IBOutletとIBActionの接続をします。

f:id:HidehikoKondo:20170929150243p:plain

カメラでAppleのMagic Mouseを撮影してみました。
ちゃんとマウスとして認識されていますね。

f:id:HidehikoKondo:20170929150937p:plain

まとめ

このようにCore MLで学習モデルをiOSアプリで扱うことが出来るようになりました。
iOSアプリで学習モデルと扱うのにサーバーとの通信が必要なくなったことで、モバイル環境でも利用しやすくなります。
また、Visionフレームワークで今までは困難だった画像認識も手軽に行うことが出来るようになります。
iOSアプリで学習モデルが扱えることや、画像認識の機能を駆使することで、新たなアプリのアイデアやサービスが出てくるのではないでしょうか?
今後もモバイル向けに提供される新機能の動向を見ていかねばと思います。
次回以降の記事で顔認識などの機能の実装例も紹介できればと思っています。
お楽しみに。

ワンダープラネットにおける Zoom の導入背景と使い方

こんにちは、CTO村田です。
初回はどうしてもオンライン会議サービスの Zoom について書きたいので、書きます! zoom.us

長くなるので、3回に分けて書きます。

  1. Zoom の導入背景と簡単な使い方
    → Zoom ってなに? Zoom 聞いたことあるけど実際どうなの?という方向け
  2. Zoom をもっと楽しく、便利に使う方法
    → Zoom 使っているけど、もっと便利に使いたい方向け
  3. Zoom を便利に使う方法の実装方法
    → ここは主にソースコードが並びますので、完全にエンジニア向け

今回は「Zoom の導入背景と簡単な使い方」です。

「ちょっとZoom良いですか?」

ワンダープラネットでは、オンライン会議はかなりライトに行われています。

特にゲーム開発においては、Slackや資料を通じた文字や絵だけでは伝わらないニュアンスも多く、名古屋本社と東京オフィスを跨ぐ場合にはオンライン会議を用いて相手に伝えています。
伝わりにくいなと思ったら Slack で「ちょっと Zoom 良いですか?」と誰もが気軽に行います。
場合によっては、名古屋本社と東京オフィスを常時接続しているモニターの前に立って「〇〇さん、いますかー?」ではじまります。

最初は仕様確認などのやり取りが多かったですが、今では

  • 全体会議
  • 面接、面談
  • 外部の方を交えた会議

など、多岐にわたってオンライン会議が利用されております。

という訳で、ワンダープラネットにおいては無くてはならないオンライン会議。なぜ Zoom を選んだのか...

Zoom は「簡単」「高品質」「安定」

Zoom を使う前からオンライン会議は行われていましたが、以下の問題がありました。

  • 途中で声が途切れる
  • 画面共有が上手く反映されない
  • 会議が突然終わる

結果、会議自体が思うように行かず、無駄な待ち時間の発生や参加者のイライラなどに繋がりました。
会議の内容以前に、良い会議とは言い難いですよね。

そのような状況の中で出会ったのが Zoom です。
試した結果、以下のポイントの評価し、採用に至りました。

  • 簡単: 参加者の招待が簡単、画面共有が簡単で遅延も少ない
  • 高品質: 音声と映像がクリア
  • 安定: 音声や映像が途切れることはなく、アプリの挙動も安定
  • Mac と Windows の両方に対応

もともと「不安定なオンライン会議を何とかしたい!」から はじまりましたので、まず普通に会話できるようになっただけでも満足でした。
それに加えて、画面共有や参加者の招待、サービスそのものの利用が簡単だったのも大きいです。

ここからは、どのように Zoom を利用しているのか、導入初期と現在に分けて書いていきます。

Zoom を導入した頃の使い方

導入初期の頃は、名古屋本社と東京オフィスの間で以下のように使うことが多かったです。

  • 仕様確認などのメンバー同士(1 対 1)の会議
  • 定例的な会議

この頃は参加者も多くはなかったので、以下の構成で Zoom を利用していました。

多少円滑になるように会議室には「カメラ」「マイク/スピーカー」「有線 LAN」を繋いだ USB ハブを置きました。
そして会議のたびに、各自の PC をその USB ハブとディスプレイに繋いではじめるというスタイルです。
Skypeをする感覚と同じですね!

Zoom Client の注意点

無料版と有料版があります、 無料版は、1対1のミーティングでは時間制限がありませんが、3名以上(グループミーティング)で行う場合は40分を経過すると切断されます。
よくグループミーティングのホストをするメンバー*1には、 Pro 版を購入していました。 Plans and Pricing - Zoom

Zoom Client を使ったオンライン会議のはじめ方

※ 今回は、アカウントの作成・ログインやアプリのダウンロードは省略します。

1.Zoom Client を起動
現在では日本語にも対応しているので、アプリの操作がより簡単になったと思います。
f:id:tomo-murata:20170925154219p:plain

2.「ビデオありで開始」または「ビデオなしで開始」で開始
相手の表情が見えた方が話しやすいので、通常「ビデオありで開始」しています。

開始直後にこのようなダイアログが表示されることもありますが、音声を使う場合は「コンピューターでオーディオに参加」を選択で問題ありません。毎回選択が面倒な場合は、ダイアログ下のチェックボックスにチェックしてください。
f:id:tomo-murata:20170925154536p:plain

3.参加者を招待するURLを取得

こちらの「招待」から行います。
f:id:tomo-murata:20170925154931p:plain

ダイアログが表示されますので、「URLのコピー」を選択するとクリップボードにコピーされます。
f:id:tomo-murata:20170925155215p:plain

4. 参加者にURLを送りつけ共有
コピーしたURLをSlackで共有しましょう! f:id:tomo-murata:20170925155623p:plain

5. 参加者はURLをクリック
URLをクリックするとブラウザが立ち上がり、Zoom Client がインストールされていれば Zoom Client が起動し対象の会議に入ります。インストールされていない場合は、対象の会議に入れるように導いてくれます。

Zoom と Slack を連携させている場合はもっと簡単!

Zoom は Slack との連携が可能です。 Setting Up Slack Integration – Zoom Help Center

連携させると /zoom という Slash Commands が追加されます。
f:id:tomo-murata:20170925160824p:plain

/zoom を実行すると、Slackに以下の内容が出力されます。
参加者は「Click here to join」をクリックするだけで済んじゃいます。

f:id:tomo-murata:20170925160921p:plain

コマンドで済むあたりが、エンジニアからしたら超便利ですね!!

しかし、Zoom Client の限界...

ただ、この運用にも限界がやってきました。
何が発生したかと言いますと...

  • 人によっては Zoom 開始までの準備にもたつく
  • オンライン会議が不安定になり、音声や画像の質が下がるときがある

各自のPCを使っていたことが、主な原因です。

基本的に開発者のPCは、重いアプリを複数起動しています。
Unity、Xcode、Photoshop、Mayaなどなど...

どのオンライン会議サービスが良いかを色々調査した結果、Zoom が新しいサービスをリリースしていた事に気づきました。

それが、 Zoom Rooms です。

現在の Zoom の使い方

現在では、 Zoom Rooms を中心としたオンライン会議になっております。
全会議室に Zoom Rooms を導入しておりますので、 Zoom Rooms を使いたくても使えないという状況は発生しません!

Zoom Rooms とは

カンファレンスタイプの Zoom です。 zoom.us

料金は、1ルームにつき月額 $49.00 です。
他社のオンライン会議サービスと比較しても、お手軽な金額です。

Zoom Rooms を利用するには Zoom Rooms 用に機材を用意する必要があります。

  • カメラ
  • マイク/スピーカー
  • タブレット
  • Mac/PC

タブレット!? と思うかもしれませんが、このタブレットの存在が良いんですよ!!

なぜなら、Zoom Rooms はタブレットを使って操作するからです!タブレットの操作例です。

会議をはじめるとき
相手の会議室を選択し「Meet Now」をタップするだけで、オンライン会議がはじまります。*2
f:id:tomo-murata:20170925223250p:plain

会議をはじめたあと
ミュートやビデオ、カメラの操作までできます。もちろん会議の終了も!
f:id:tomo-murata:20170925223558p:plain

タブレット上で行う操作は、すごくシンプルです。
オンライン会議前の準備にもたつくという事も、これならありません。

ちなみに参考までに、ワンダープラネットでは タブレットと Mac/PC は、これらを選択しております。

  • タブレット:中古の iPad mini
  • Mac/PC:Mac mini(256GBのSSDにカスタマイズ)

Apple信者と言われそうですが、 Mac mini にすることで便利な機能が使えます。*3

Zoom Rooms を使ったオンライン会議のはじめ方

ワンダープラネットでは、2パターンの Zoom Rooms の開始方法があります。

さきほどのタブレットの操作例で出したのは、Invite(招待)方式と呼んでおります。
招待先の部屋に誰か人がいる場合に用います。招待された部屋は、Answer(応答)/Decline(拒否)をするまで呼び出し音が鳴り続けるので注意です。
f:id:tomo-murata:20170925230334p:plain

もう1つがJoin方式です。
ホストから招待するのではなく、立ち上がっているホストに乗り込むイメージです。
この方式は、相手の部屋に人がいるかどうか分からない場合に用います。 f:id:tomo-murata:20170925231402p:plain

Join方式で会議をはじめるとき
ホスト側: 単に「Meet Now」をタップするだけで、立ち上げておく。
f:id:tomo-murata:20170925231726p:plain

ゲスト側: 「Join」メニューから、入りたいMeeting IDを入力し「Join」をタップする。
Zoom における Meeting は、必ず11桁の ID を持っています。Zoom Client、Zoom Rooms に限らず持っています。
f:id:tomo-murata:20170925232134p:plain

PCにある資料を共有して映したいとき
ここで、Zoom Client の登場です。 各自のPCにインストールした Zoom Client から Zoom Rooms のホストにJoinします。
あとは画面共有をするのみ!ここは以前から慣れていた操作がそのまま使えます。

f:id:tomo-murata:20170927135455p:plain

ちょっとした工夫

Meeting ID がたびたび出てきますが、このIDが毎回異なるとZoom Rooms からホストへの Join やZoom Client からの画面共有時が大変です。「IDは何ですかー?」と毎回確認しなければなりません。

ワンダープラネットではIDを確認しなくても済むように、各Room の Meeting ID はある命名規則に沿って設定してます。そして「Meet Now」した時は、設定した Meeting ID でルームを作成するように設定しております。

とは言え、全部屋のMeeting IDを全員が覚えているかとそうではありません...

Slack に 部屋名の Slash Commands を作成し、実行するとMeeting ID と URL を知らせてくれるものを作成しました。URL をクリックすると、Meeting に入った状態で Zoom Client が立ち上がるので、画面共有時に大変便利です!

f:id:tomo-murata:20170925233746p:plain

まとめ

Zoom Client も良かったですが、Zoom Rooms に変えたことで 安定性・利便性が格段に上がり快適になりました。
ワンダープラネットでは無くてはならいサービスです!

Zoom Rooms について書かれたものは少ないので、この記事をきっかけに知っていただけると嬉しいです。

ざっと使い方も混ぜて書きましたが、ほんの一例です。より便利な使い方も書きますので、お待ちください!

*1:ホストのみ Pro 版を購入していれば大丈夫です。

*2:ワンダープラネットの会議室は、全て名古屋メシに由来します:笑

*3:Mac mini にすることで、どう便利なのかは次回書きます。

Tech Blog としてリニューアル!

リニューアル後、1エントリー目を担当するCTO村田です。

今回は、ブログのリニューアルにまつわる内容を書きたいと思います。

Tech Blog までの変遷

ワンダープラネットのTech系ブログは「エンジニアブログ」ではじまり、「デベロッパーブログ」としてリニューアル、
そして今回の「Tech Blog」へのリニューアルという変遷を経ています。

今まで『誰をターゲットに、どのような内容を発信していたのか』は、以下になります。

エンジニアブログ
  • 発信者
    • 全エンジニア
  • 対象
    • エンジニア
  • 内容
    • エンジニアが開発中に困り調査 / 解決したこと
    • 新しい技術の調査内容
デベロッパーブログ
  • 発信者
    • エンジニア、デザイナー、プランナー、QA、CS
  • 対象
    • モバイルゲーム開発に携わっている方
    • モバイルゲーム開発に興味を持たれている方
  • 内容
    • モバイルゲーム開発に関する技術情報
    • モバイルゲーム開発での取り組みや改善内容

デベロッパーブログにしたことで発生した問題

デベロッパーブログにしたことで発信する内容の幅は広がりましたが、以下の問題も発生しました。

  • 寄稿者を広げすぎた結果、記事のレベルがまちまちになってしまった。
  • エンジニアブログの時に比べて技術情報の発信が減った結果、創業当初の「技術の会社」感が薄れた。

こういった背景があり、今回「テックブログ」としてリニューアルしました。

Tech Blog で変わったこと

「Tech Blog」の概要は、以下のとおりです。

  • 発信者
    • アドバンストテクノロジー部のメンバー
    • CTO室のメンバー
  • 対象
    • エンジニア
  • 内容
    • 研究開発で利用している技術的な内容
    • 他社サービスの利用事例
    • その他ゲーム開発におけるテクニカルな内容

Tech Blog では、その名の通り “技術” に寄せた内容にしました(戻しました)。

発信者も以前のエンジニアブログの時から少し変わり、CTO管掌の部署とチームのメンバーにしました*1
それぞれの部署、チームがどんな構成かと言いますと、

  • アドバンストテクノロジー部
    会社のプロダクトに対して新しい技術で付加価値を提供するR&Dチームと、最新サービスを活用し社内の働き方や業務フローを改善するInternal IT*2 チームが所属する部署。
  • CTO室*3
    エンジニアを横串でサポートし、会社全体の技術的な駆け込み寺となる少数精鋭のエンジニアチーム。

以上のメンバーが Tech Blog を通して、技術的なこと、最新サービスの活用方法を毎週発信していきます!

エンジニアにとっては読み応えがあり、毎週の更新が楽しみにしていただける内容をCTOが責任を持って届けて参りますので、これからもチェックしてください!!

*1:もちろん上記以外からの立候補も受け付けています。

*2:よく言われる社内SEです。一般的な社内SE業務も担当します。

*3:CTO室については、別途活動内容を記載できればと思います。