顔認識ソフトウェア Face API

今年で最後となる大学入試センター試験が終わりましたね。受験生のみなさまお疲れ様でした。
1日目の倫理では「人間は人工知能(AI)に仕事を奪われると思うか」についての問題が出ました。
人工知能(AI)は

  • 計算できないこと
  • 統計処理ができないこと

を不得意とします。
つまり「クリエイティブ」なことが不得意です。

来年からは、思考力・判断力・表現力を一層重視する「大学入学共通テスト」に変わります。資料・データから必要な情報を読み取る力や、読み取った情報を比較・組み合わせて課題解決力を問うことを意識した問題も出題されそうです。

将来、AIは今ある仕事の多くを代替するようになるでしょう。しかし、AIが普及することによって、新たに必要となる仕事もあります。そのような仕事に臆せず飛び込むことができるチャレンジ力も大事です。

Face API

さて、話は変わって今回は、Microsoft Azureが提供しているFace APIで顔認識をしてみます。

Face APIは、人の顔を特定するだけでなく、感情認識ができます。人間でも場合によっては読み取ることが難しい感情の状態(幸福、悲しみ、怒り等)を取得することができます。顔認証での出退勤と感情認識を組み合わせて、社員の健康状態管理をするサービスなども出始めています。

また、Face APIは、年齢、性別、眼鏡の有無、ヒゲの有無、姿勢などの分析も行えます。

顔の登録

まずは、認識をする顔の登録をしていきましょう。

1. ライブラリと定数の定義
import json
import requests
import time
import httplib2
import os

BASE_URL = 'https://japaneast.api.cognitive.microsoft.com/face/v1.0/'
SUBSCRIPTION_KEY = 'サブスクリプションキー'
GROUP_ID = 'グループID'
2. Person Groupの作成

API Reference: PersonGroup - Create

# Person Groupの作成
def createPersonGroup():
    end_point = BASE_URL + 'persongroups/' + GROUP_NAME 
    headers = {
        'Content-Type': 'application/json',
        'Ocp-Apim-Subscription-Key': SUBSCRIPTION_KEY,
    }
    body = dict()
    body["name"] = "グループ名"
    body = str(body)
    
    result = requests.put(end_point, data=body, headers=headers)
    print("Person Group:" + str(json.loads(result.text)))
3. Personの作成

API Reference: PersonGroup Person - Create

# Personの作成
def createPerson(personName):
    end_point = BASE_URL + 'persongroups/' + GROUP_NAME + '/persons'
    result = requests.post(
        end_point,
        headers = {
            'Content-Type': 'application/json',
            'Ocp-Apim-Subscription-Key': SUBSCRIPTION_KEY
        },
        json = {
            'name': personName
        }
    )
    print("Person:" + str(json.loads(result.text)))
    personId = json.loads(result.text)['personId']
    return personId
4. Faceの追加

API Reference: PersonGroup Person - Add Face

# Faceの追加
def addDataFaceToPerson(personId, folder):
    valid_images = [".jpg",".gif",".png"]
    for f in os.listdir(folder):
        ext = os.path.splitext(f)[1]
        if ext.lower() not in valid_images:
            continue
        with open(os.path.join(folder,f), 'rb') as f:
            img = f.read()
            addFaceImage(personId, img)
            f.close()

def addFaceImage(personId, img):
    end_point = BASE_URL + 'persongroups/' + GROUP_ID + '/persons/' + personId  + '/persistedFaces'
    result = requests.post(
        end_point,
        headers = {
            'Content-Type': 'application/octet-stream',
            'Ocp-Apim-Subscription-Key': SUBSCRIPTION_KEY
        },
        data=img
    )
    print("Face:" + str(json.loads(result.text)))
5. 学習の実施

API Reference: PersonGroup - Train

# 学習の実施
def trainGroup():
    end_point = BASE_URL + 'persongroups/' + GROUP_ID + '/train'
    result = requests.post(
        end_point,
        headers = {
            'Ocp-Apim-Subscription-Key': SUBSCRIPTION_KEY
        },
        json = {
            'personGroupId': GROUP_ID
        }
    )
    print("Train:" + str(result))
6. 実行

f:id:KenjiU:20200119192024p:plain

今回も弊社の取締役(陽治郎さん)の写真を使わせていただきます!
3枚程度学習すれば認識ができるようになります。

if __name__ == '__main__':
    # Person Groupの作成
    createPersonGroup()
    # Personの作成
    personId = createPerson('Yojiro')
    # Faceの追加
    addDataFaceToPerson(personId, os.path.join("data", "Yojiro"))
    time.sleep(5)
    # 学習の実施
    trainGroup()

顔の認識

次は顔の認識をしてみましょう。

1. ライブラリと定数の定義
import json
import requests
import os

BASE_URL = 'https://japaneast.api.cognitive.microsoft.com/face/v1.0/'
SUBSCRIPTION_KEY = 'サブスクリプションキー'
GROUP_ID = 'グループID'
2. 顔検出

API Reference: Face - Detect

# 顔検出
def detectFaceImage(imgPath):
    with open(imgPath, 'rb') as f:
        img = f.read()
    params = {
        'returnFaceId': 'true',
        'returnFaceLandmarks': 'false',
        'returnFaceAttributes': 'age,blur,emotion,exposure,facialHair,gender,glasses,hair,headPose,makeup,noise,occlusion,smile'
    }
    end_point = BASE_URL + "detect"
    result = requests.post(
        end_point,
        params=params,
        headers = {
            'Content-Type': 'application/octet-stream',
            'Ocp-Apim-Subscription-Key': SUBSCRIPTION_KEY
        },
        data=img
    )
    detect = json.loads(result.text)
    print("DetectFace:" + str(detect))
    return detect
3. 顔特定

API Reference: Face - Identify

# 顔特定
def identifyPerson(faceId):
    end_point = BASE_URL + 'identify'
    faceIds = [faceId]
    result = requests.post(
        end_point,
        headers = {
            'Ocp-Apim-Subscription-Key': SUBSCRIPTION_KEY
        },
        json = {
            'faceIds': faceIds,
            'personGroupId': GROUP_ID
        }
    )
    candidates = json.loads(result.text)[0]['candidates']
    return candidates
4. 名前取得

API Reference: PersonGroup Person - List

# 名前取得
def getPersonNameByPersonId(personId):
    end_point = BASE_URL + 'persongroups/' + GROUP_ID + '/persons'
    result = requests.get(
        end_point,
        headers = {
        "Ocp-Apim-Subscription-Key": SUBSCRIPTION_KEY
        },
        json = {
            'personGroupId': GROUP_ID
        }
    )
    persons = json.loads(result.text)
    for person in persons:
        if person['personId'] == personId:
            return person['name']
5. 実行

f:id:KenjiU:20200119195551j:plain
Yojiro-test.jpg

こちらの陽治郎さんの顔写真を認識してみます。

if __name__ == '__main__':
    image = os.path.join("test", "Yojiro-test.jpg")
    # 顔検出
    detect = detectFaceImage(image)
    if len(detect) > 0:
        for per in detect:
            detectedFaceId = per['faceId']
            emotion = per['faceAttributes']['emotion']
            # 顔特定
            identifiedPerson = identifyPerson(detectedFaceId)
            if len(identifiedPerson) > 0 and identifiedPerson[0]['personId']:
                personId = identifiedPerson[0]['personId']
                # 名前取得
                personName = getPersonNameByPersonId(personId)
                print("PersonName:" + str(personName))
            else:
                print("PersonName:Unknown")            
    else:
        print("Nobody")

実行結果は以下の通りです。

DetectFace:[{'faceId': 'eba5d6ab-cb19-4229-8534-dea32b55f8d9', 'faceRectangle': {'top': 45, 'left': 47, 'width': 57, 'height': 57}, 'faceAttributes': {'smile': 1.0, 'headPose': {'pitch': -2.9, 'roll': 3.4, 'yaw': -15.0}, 'gender': 'male', 'age': 34.0, 'facialHair': {'moustache': 0.1, 'beard': 0.1, 'sideburns': 0.1}, 'glasses': 'NoGlasses', 'emotion': {'anger': 0.0, 'contempt': 0.0, 'disgust': 0.0, 'fear': 0.0, 'happiness': 1.0, 'neutral': 0.0, 'sadness': 0.0, 'surprise': 0.0}, 'blur': {'blurLevel': 'low', 'value': 0.15}, 'exposure': {'exposureLevel': 'goodExposure', 'value': 0.72}, 'noise': {'noiseLevel': 'low', 'value': 0.0}, 'makeup': {'eyeMakeup': False, 'lipMakeup': False}, 'occlusion': {'foreheadOccluded': False, 'eyeOccluded': False, 'mouthOccluded': False}, 'hair': {'bald': 0.09, 'invisible': False, 'hairColor': [{'color': 'black', 'confidence': 0.99}, {'color': 'brown', 'confidence': 0.99}, {'color': 'gray', 'confidence': 0.29}, {'color': 'other', 'confidence': 0.09}, {'color': 'red', 'confidence': 0.03}, {'color': 'blond', 'confidence': 0.03}]}}}]

1個1個属性を見ていきましょう。

  1. 年齢(Age): 推定年齢
    'age': 34.0

  2. ぼかし(Blur): 顔のぼかしの程度
    'blur': {'blurLevel': 'low', 'value': 0.15}

  3. 感情(Emotion): 感情値
    'emotion': {'anger': 0.0, 'contempt': 0.0, 'disgust': 0.0, 'fear': 0.0, 'happiness': 1.0, 'neutral': 0.0, 'sadness': 0.0, 'surprise': 0.0}

  4. 露出(Exposure): 画像内の顔の露出の程度
    'exposure': {'exposureLevel': 'goodExposure', 'value': 0.72}

  5. 顔ひげ(Facial hair): 顔ひげの有無と長さ
    'facialHair': {'moustache': 0.1, 'beard': 0.1, 'sideburns': 0.1}

  6. 性別(Gender): 推定される性別
    'gender': 'male'

  7. 眼鏡(Glasses): 眼鏡があるかどうか
    'glasses': 'NoGlasses'

  8. 髪の毛(Hair): 髪質と髪色
    'hair': {'bald': 0.09, 'invisible': False, 'hairColor': [{'color': 'black', 'confidence': 0.99}, {'color': 'brown', 'confidence': 0.99}, {'color': 'gray', 'confidence': 0.29}, {'color': 'other', 'confidence': 0.09}, {'color': 'red', 'confidence': 0.03}, {'color': 'blond', 'confidence': 0.03}]}

  9. 頭部姿勢(Head pose): 3 次元空間での顔の向き
    'headPose': {'pitch': -2.9, 'roll': 3.4, 'yaw': -15.0}

  10. 化粧(Makeup): 化粧があるかどうか
    'makeup': {'eyeMakeup': False, 'lipMakeup': False}

  11. ノイズ(Noise): 顔の画像で検出された視覚ノイズ
    'noise': {'noiseLevel': 'low', 'value': 0.0}

  12. オクルージョン(Occlusion): 顔のパーツをブロックするオブジェクトがあるかどうか
    'occlusion': {'foreheadOccluded': False, 'eyeOccluded': False, 'mouthOccluded': False}

  13. 笑顔(Smile): 笑顔表現
    'smile': 1.0

笑顔100点満点ですね!

PersonName: Yojiro

顔の認識結果も「Yojiro」となり、正しく認識できています。

変装チャレンジ

ここからは、陽治郎さんすみません。陽治郎さんを色々と変装させてみます。
正しく認識できるでしょうか。

かぶりもの

f:id:KenjiU:20200119201509j:plain
Yojiro-test-1.jpg

PersonName: Yojiro

正しく認識できています。

f:id:KenjiU:20200119201520j:plain
Yojiro-test-2.jpg

PersonName: Yojiro

こちらも正しく認識できています。

ひげ

f:id:KenjiU:20200119201532j:plain
Yojiro-test-3.jpg

PersonName: Unknown

残念ながら、認識できませんでした。

'facialHair': {'moustache': 0.6, 'beard': 0.6, 'sideburns': 0.4}

顔ひげの属性値が高くなっています。

眼鏡

f:id:KenjiU:20200119203905j:plain
Yojiro-test-4.jpg

PersonName: Unknown

こちらも、認識できませんでした。

 'glasses': 'Sunglasses'

サングラスは認識しています。

ジョーカー

f:id:KenjiU:20200119201609j:plain
Yojiro-test-5.jpg

Nobody

もはや顔として認識されませんでした。

Face API の価格

Face APIにはFreeインスタンスとStandardインスタンスがあります。Freeインスタンスは無料で使えますが、トランザクション数は毎月3万回までに制限されます。Standardインスタンストランザクション数の制限はなくなりますが、トランザクション数が増えるにつれて料金が高くなります。お試しで使いたいときはFreeインスタンスを選ぶことがおすすめです。

以上です。
ありがとうございました!

株式会社ブリスウェル

Kaggle チャレンジ

正月なまりを吹っ飛ばすべく、今回はKaggleチャレンジをします。

Kaggleとは

世界中のデータサイエンティスト達がデータ分析の腕を競い合うプラットフォームです。企業や研究者が提供するデータセットに対し、適切な予測モデルを構築しその予測精度を競います。上位入賞者には賞金が出ます。
様々なデータセットが手に入り、他の競技者の解説(カーネル)を見ることができるので、データ分析の勉強にも非常に有用です。

Kaggleを始める方法

  1. Kaggleのサイトに接続してアカウントを作成します。
  2. Jupyter Notebook(Webブラウザで動作するpythonの対話環境)をインストールします。
  3. コンペに参加

これだけです!

住宅価格予測

今回は、Kaggleの回帰問題のチュートリアルである「House Prices: Advanced Regression Techniques」をやってみます。(cybozu02さんのカーネルを参考にしております)

www.kaggle.com

1. ライブラリの準備

今回使用するライブラリをインポートします。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import PowerTransformer
  • numpy: 数値計算を効率的に行うための拡張モジュール
  • pandas: データ解析を支援する機能を提供するライブラリ
  • matplotlib: グラフ描画ライブラリ
  • seaborn: 可視化ライブラリ
  • PowerTransformer: 0や負値を含んだ変数も対数変換ができるライブラリ
2. データの読み込み

こちらのリンクから以下のデータセットをダウンロードして読み込みます。

  • train.csv: 学習データ
  • test.csv: テストデータ
#pandasを使用してcsvデータ読み込み
train = pd.read_csv("train.csv")
test_x = pd.read_csv("test.csv")

#学習データの特徴量を出力
train.columns

f:id:KenjiU:20200114235255p:plain

学習データの特徴量は79個あります。

  • MSSubClass: 住居のタイプ
  • MSZoning: 住居のゾーン
  • LotFrontage: 隣接した道路の長さ
  • LotArea: 敷地面積
  • Street: 道路のタイプ
  • Alley: 路地のタイプ
  • LotShape: 土地の形状
  • LandContour: 土地の平坦さ
  • Utilities: ガス・電気・水の利用
  • LotConfig: 土地の構成
  • LandSlope:土地の傾斜
  • Neighborhood: 近所

等です。
実践さながらのデータでわくわくしてきますね!

3. データ分析

まずは、予測対象の変数(目的変数)である「SalesPrice(住宅価格)」について確認します。

plt.hist(train_saleprice, bins=100)
plt.show()

f:id:KenjiU:20200114235813p:plain

左に偏っており正規分布になっていません。機械学習において目的変数が正規分布に従ったほうが精度が良くなる、ということが知られているので後ほど正規分布に変換します。

次に、特徴量を確認します。

  • TotalBsmtSF: 地下室の広さ
  • 1stFlrSF: 1階の広さ
  • GrLivArea: リビングの広さ
  • GarageArea: 車庫の広さ
  • LotArea: 土地の広さ

の広さが、住宅価格に影響するのでは、と考えます。
相関関係を見てみましょう。

#地下室の広さと住宅価格の散布図を作成
data = pd.concat([train["TotalBsmtSF"],train["SalePrice"]],axis=1)
plt.figure(figsize=(20, 10))
plt.scatter(train["TotalBsmtSF"],train["SalePrice"])
plt.xlabel("TotalBsmtSF")
plt.ylabel("SalePrice")

#1階の広さと住宅価格の散布図を作成
data = pd.concat([train["1stFlrSF"],train["SalePrice"]],axis=1)
plt.figure(figsize=(20, 10))
plt.scatter(train["1stFlrSF"],train["SalePrice"])
plt.xlabel("1stFlrSF")
plt.ylabel("SalePrice")

#リビングの広さと住宅価格の散布図を作成
data = pd.concat([train["GrLivArea"],train["SalePrice"]],axis=1)
plt.figure(figsize=(20, 10))
plt.scatter(train["GrLivArea"],train["SalePrice"])
plt.xlabel("GrLivArea")
plt.ylabel("SalePrice")

#車庫の広さと住宅価格の散布図を作成
data = pd.concat([train["GarageArea"],train["SalePrice"]],axis=1)
plt.figure(figsize=(20, 10))
plt.scatter(train["GarageArea"],train["SalePrice"])
plt.xlabel("GarageArea")
plt.ylabel("SalePrice")

#土地の広さと住宅価格の散布図を作成
data = pd.concat([train["LotArea"],train["SalePrice"]],axis=1)
plt.figure(figsize=(20, 10))
plt.scatter(train["LotArea"],train["SalePrice"])
plt.xlabel("LotArea")
plt.ylabel("SalePrice")

f:id:KenjiU:20200115000607p:plain f:id:KenjiU:20200115000613p:plain f:id:KenjiU:20200115000628p:plain f:id:KenjiU:20200115000632p:plain f:id:KenjiU:20200115000647p:plain

広くなるにつれて住宅価格が上昇する傾向が見られますね。外れ値がいくつかあるので除外しておきます。

#外れ値を除外
train = train.drop(train[(train['TotalBsmtSF']>6000) & (train['SalePrice']<200000)].index)
train = train.drop(train[(train['1stFlrSF']>4000) & (train['SalePrice']<200000)].index)
train = train.drop(train[(train['GrLivArea']>4000) & (train['SalePrice']<200000)].index)
train = train.drop(train[(train['GarageArea']>1200) & (train['SalePrice']<300000)].index)
train = train.drop(train[(train['LotArea']>80000) & (train['SalePrice']<400000)].index)
4. 特徴量の作成

より良い特徴量を作成する特徴量エンジニアリングを進めていきます。

目的変数(住宅価格)を学習データから切り出します。また、学習データとテストデータをまとめて処理するため一旦マージしておきます。

#学習データを目的変数(住宅価格)とそれ以外に分ける
train_x = train.drop("SalePrice",axis=1)
train_y = train["SalePrice"]

#学習データとテストデータをマージ
all_data = pd.concat([train_x,test_x],axis=0,sort=True)

#IDのカラムは学習に不要なので別の変数に格納
train_ID = train['Id']
test_ID = test_x['Id']

all_data.drop("Id", axis = 1, inplace = True)
5. 欠損値の処理

分析に利用するデータには多くの場合、何らかの理由により記録されなかった値、欠損値が含まれます。欠損値があると統計処理ができなくなるので、削除や0埋め等が必要です。まずは欠損値がどのくらいあるのかを確認します。

#データの欠損値
all_data_na = all_data.isnull().sum()[all_data.isnull().sum()>0].sort_values(ascending=False)

#欠損値の数をグラフ化
plt.figure(figsize=(20,10))
plt.xticks(rotation='90')
sns.barplot(x=all_data_na.index, y=all_data_na)

#欠損値があるカラムをリスト化
na_col_list = all_data.isnull().sum()[all_data.isnull().sum()>0].index.tolist()
all_data[na_col_list].dtypes.sort_values()

f:id:KenjiU:20200115002401p:plain

  • PoolQC: プールの質を表す。プールがない場合にはNAとなる。
  • MiscFeature: 備え付けられている特別な設備(エレベータ等)を表す。何もない場合はNAとなる。
  • Alley: 物件にアクセスするための路地のタイプを表す。該当しない場合はNAとなる。
  • Fence: 敷地などを仕切る囲いの質を表す。囲いがない場合はNAとなる。
  • FireplaceQu: 暖炉の質を表す。暖炉がない場合はNAとなる。

が多く欠損しています。
対象物そのものが存在しない場合、欠損として扱われているようです。

  • float型の場合は「0」
  • object型の場合は「None」

で置換することにしましょう。

物件に隣接した道路の長さ(LotFrontage)については平均値で穴埋めします。

#物件に隣接した道路の長さ(LotFrontage)の欠損値を平均値で穴埋め
all_data['LotFrontage'] = all_data['LotFrontage'].fillna(all_data['LotFrontage'].median())

#欠損値が存在する、かつfloat型の場合は「0」で置換
float_list = all_data[na_col_list].dtypes[all_data[na_col_list].dtypes == "float64"].index.tolist()
all_data[float_list] = all_data[float_list].fillna(0)

#欠損値が存在する、かつobject型の場合は「None」で置換
obj_list = all_data[na_col_list].dtypes[all_data[na_col_list].dtypes == "object"].index.tolist()
all_data[obj_list] = all_data[obj_list].fillna("None")
6. 数値変数の処理
  • MSSubClass: 住宅の種類
  • YrSold: 販売年
  • MoSold: 販売月

については、数値ですが、数や量で測れない変数なので、カテゴリ変数として扱うことにします。

#カテゴリ変数に変換する
all_data['MSSubClass'] = all_data['MSSubClass'].apply(str)
all_data['YrSold'] = all_data['YrSold'].astype(str)
all_data['MoSold'] = all_data['MoSold'].astype(str)
7. 目的変数を対数変換

目的変数(住宅価格)が正規分布になっていないため、対数変換をすることで正規分布にします。

#目的変数を対数変換する
train_y = np.log1p(train_y)

#可視化
plt.figure(figsize=(20, 10))
sns.distplot(train_y)

f:id:KenjiU:20200115003749p:plain

8. 説明変数を対数変換

説明変数についても正規分布になっていない(歪度が0.5よりも大きい)ものは対数変換していきます。0や負値を含んだ変数も対数変換ができる「Yeo-Johnson変換」を使用します。

#数値の説明変数のリスト
num_feats = all_data.dtypes[all_data.dtypes != "object" ].index

#各説明変数の歪度を計算
skewed_feats = all_data[num_feats].apply(lambda x: x.skew()).sort_values(ascending = False)

#歪度の絶対値が0.5より大きい変数だけに絞る
skewed_feats_over = skewed_feats[abs(skewed_feats) > 0.5].index

#Yeo-Johnson変換
pt = PowerTransformer()
pt.fit(all_data[skewed_feats_over])

#変換後のデータで各列を置換
all_data[skewed_feats_over] = pt.transform(all_data[skewed_feats_over])
9. 特徴量の追加

新しい特徴量を追加することで、予測モデルの性能が向上することがあります。以下の特徴量を追加します。

#プールの有無(広さが0より大きい場合は有)
all_data['haspool'] = all_data['PoolArea'].apply(lambda x: 1 if x > 0 else 0)

#2階の有無(広さが0より大きい場合は有)
all_data['has2ndfloor'] = all_data['2ndFlrSF'].apply(lambda x: 1 if x > 0 else 0)

#ガレージの有無(広さが0より大きい場合は有)
all_data['hasgarage'] = all_data['GarageArea'].apply(lambda x: 1 if x > 0 else 0)

#地下室の有無(広さが0より大きい場合は有)
all_data['hasbsmt'] = all_data['TotalBsmtSF'].apply(lambda x: 1 if x > 0 else 0)

#暖炉の有無
all_data['hasfireplace'] = all_data['Fireplaces'].apply(lambda x: 1 if x > 0 else 0)
10. カテゴリ変数の処理

カテゴリ変数を、One-Hotエンコーディングにより「0」と「1」だけの数列に変換します。

#カテゴリ変数となっているカラム
cal_list = all_data.dtypes[all_data.dtypes=="object"].index.tolist()

#pandasのget_dummies関数でOne-Hotエンコーディング
all_data = pd.get_dummies(all_data,columns=cal_list)
11. データの分割

マージした学習データとテストデータを再度分割します。

#学習データとテストデータに再分割
train_x = all_data.iloc[:train_x.shape[0],:].reset_index(drop=True)
test_x = all_data.iloc[train_x.shape[0]:,:].reset_index(drop=True)
12. 予測モデルの作成&実行

勾配ブースティング(アンサンブル学習と決定木を組み合わせた手法)のライブラリ「XGBoost」でモデルを作成します。

  • max_depth: 木の深さの最大値
  • eta: 学習率を調整
  • objective: 損失関数を指定

等のハイパーパラメータについて、設定値を色々試してみたいですね。

from sklearn.ensemble import RandomForestRegressor,  GradientBoostingRegressor
from sklearn.model_selection import KFold, cross_val_score, train_test_split
from sklearn.metrics import mean_squared_error
import xgboost as xgb
from sklearn.model_selection import GridSearchCV

#データの分割
train_x, valid_x, train_y, valid_y = train_test_split(
        train_x,
        train_y,
        test_size=0.3,
        random_state=0)

#特徴量と目的変数をxgboostのデータ構造に変換する
dtrain = xgb.DMatrix(train_x, label=train_y)
dvalid = xgb.DMatrix(valid_x,label=valid_y)

#パラメータを指定して勾配ブースティング
num_round = 5000
evallist = [(dvalid, 'eval'), (dtrain, 'train')]

evals_result = {}

#パラメータ
param = {
    'max_depth': 3,
    'eta': 0.01,
    'objective': 'reg:squarederror',
}

#学習の実行
bst = xgb.train(
    param, dtrain,
    num_round,
    evallist,
    evals_result=evals_result,
)    
#学習曲線を可視化する
plt.figure(figsize=(20, 10))
train_metric = evals_result['train']['rmse']
plt.plot(train_metric, label='train rmse')
eval_metric = evals_result['eval']['rmse']
plt.plot(eval_metric, label='eval rmse')
plt.grid()
plt.legend()
plt.xlabel('rounds')
plt.ylabel('rmse')
plt.ylim(0, 0.3)
plt.show()

f:id:KenjiU:20200115012809p:plain

13. 提出用csvファイルの作成

いよいよ最後です。予測結果をcsvファイルに出力します。予測結果のSalePrice(住宅価格)は対数をとった値なので、Exponentialをかけてあげる必要があります。

#予測結果を出力
dtest = xgb.DMatrix(test_x)
my_submission = pd.DataFrame()
my_submission["Id"] = test_ID
my_submission["SalePrice"] = np.exp(bst.predict(dtest))
my_submission.to_csv('submission.csv', index=False)

f:id:KenjiU:20200115015847p:plain

1998位/5343人でした!

以下は今回参考にさせていただいたcybozu02さんのカーネルです。ありがとうございました。 www.kaggle.com

ふるさと 会津

明けましておめでとうございます。新年も幸多き年でありますよう心よりお祈り申し上げます。本年もどうぞよろしくお願いいたします。

今回は僕のふるさとの福島県会津について少しご紹介いたします。

会津の場所

f:id:KenjiU:20200104171405p:plain
福島県 会津地方
福島県は「会津」「中通り」「浜通り」の3つの地域に分かれていて、会津は西側です。
福島県内では「あづ」と、「い」にアクセントをおきます。
エンヤーあ磐梯山は 宝の山よ〜♪

会津の冬

f:id:KenjiU:20200104172948j:plain
只見線
会津の冬は豪雪で、雪だるま・かまくら作り、雪合戦、つららチャンバラなど何でもやりたい放題です。僕の出身は喜多方ラーメン喜多方市なのですが、小学校の校庭には山があり、冬はスキーを履いて滑りながら登校、体育の時間にその山でスキーをしていました。祖母の家は山奥で、雪が2m以上積もることもあり、家の2階から出入りもできました。屋根からの雪ダイブは最高です。
会津福島県の山間地にあり県内で過疎化がもっとも懸念されている地域でもあります。

会津若松市

f:id:KenjiU:20200104173111j:plain
鶴ヶ城
会津の中心都市は「会津若松市」です。人口は約12万人。
江戸時代には会津藩の城下町として盛え、2013年のNHK大河ドラマ「八重の桜」の舞台にもなりました。主演の綾瀬はるかさんは毎年「会津まつり」に参加しています。

会津若松市には公立大学の「会津大学」があります。1993年に日本初のコンピュータ専門大学として開設され、現在はコンピュータ理工学部・コンピュータ理工学科の一学部一学科で構成されます。 www.u-aizu.ac.jp AIセンターも設置され、AIの研究・人材育成にも力を入れています。

また、会津若松市では、IoT、AI、ビッグデータなどのさまざまな技術を活用して、人々のより良い生活を可能とする都市を実現しよう、という「スマートシティ」構想を進めています。 www4.pref.fukushima.jp 2019年の4月に「スマートシティAiCT」がオープンしました。大手企業も入居しています。地元・入居企業、会津大学会津若松市の「産学官連携」による新たなイノベーションを生み出すことが期待されます。
ぜひ、会津だけで閉じることなく、日本全国に広げることができるモデル都市になって欲しいです。

BRISWELL 2020 Premium

2020年、弊社も
テクノロジーで新しい価値を創造し、より良い社会の実現に貢献する
をミッションに掲げ、色々とチャレンジしていきます!
よろしくお願いいたします。

株式会社ブリスウェル

2019年のAI振り返り

コーンフレークが食べたくなる今日この頃。
今年も残すところあとわずかとなりました。あっという間でしたね!

さて、今年一年AI分野で多くのニュースがありました。弊社のオフィスでは新聞を取っているのですが、2日に1回は新聞の1面にAI関連の記事が載っていた感じです。

2019年のAIを振り返っていきましょう。

自然言語処理

今年は特に自然言語処理領域の発展が目立ちました。自然言語処理とは「私たち人間が日常書いたり話したりしている言語をコンピュータに処理させる」技術を表します。

例えば
「こーんふれーくがねたりない」
この文は
①コーンフレークが寝足りない
②コーンフレークがね、足りない
の2つの解釈ができます。

人間であれば「コーンフレークは寝ない」と考えるので①ではなく②を選ぶでしょう。ただ、機械はどちらを選んで良いかわかりません。このような自然言語の曖昧さを解消させる技術が発展してきています。

www.itmedia.co.jp

のようにAIが人間の読解力に近づいています。(いや超えていますね...)

また、Googleは2019年10月25日に最新の自然言語処理技術「BERT」を検索エンジンに採用したと発表しました。「BERT」は文脈を理解することができます。

www.blog.google

こちらのGoogleブログに以下のサンプルがあります。

2019 brazil traveler to usa need a visa.

訳は「2019年ブラジル人がアメリカに旅行に行く時ビザは必要か」(ブラジル人→アメリカ)になりますが、以前のGoogleは「to」(→)を理解できず、逆の意味「2019年アメリカ人がブラジルに旅行に行く時ビザは必要か」(アメリカ人→ブラジル)と認識していました。

それが「BERT」を導入することで文脈を正しく理解するようになっています。その結果、これまで文脈を理解されず、検索結果で上位に表示されなかったページが、正しく認識され表示されるようにもなります。(日本の検索エンジンにはまだ導入されていないようです)

ちなみに「自然言語処理」は、弊社Uri&Yuki漫才コンビ「下北ラヴァーズ」でもネタに取り込んでいます。来年の進化に期待です。

画像処理

2019年は画像認識より画像生成の技術が話題になりました。特にディープラーニング技術を応用した「GAN(敵対的生成ネットワーク)」というモデルが注目されました。

GANの仕組みは、美術品の贋作者と鑑定士の関係に例えられます。

贋作者の「Generator」と、贋作を見破ろうとする鑑定士の「Discriminator」という2種類のネットワークから構成されています。Generator(贋作者)は本物にできるだけ近い贋作を作り出し、その贋作をDiscriminator(鑑定士)は見破ります。Discriminator(鑑定士)の見破る能力が上がってくると、Generator(贋作者)もより質の高い贋作を作ります。するとDiscriminator(鑑定士)もまたまた能力を上げて... というのを繰り返し、最終的に本物そっくりのものが作られて
スーパー鑑定士:「これは間違いなく本物です。大切になさってくださいね。」
となるのがGANの仕組みです。

このGANを利用して、実在しないデータを生成したり、存在するデータの特徴に沿って変換ができます。

ウマがリアルタイムでシマウマに変換されます、 これは2つのデータソース間の変換を学習するCycleGANという技術を利用しています。

本物そっくりですね。このような偽造動画はディープフェイクと呼ばれます。 GANによってますますディープフェイクの技術は向上しており、社会問題にもなってきています。

www.kaggle.com

のようにディープフェイク検出がkaggleのコンペにもなっています。
来年は弊社AIチームもkaggleチャレンジ予定です。

AIのツール化

2019年はAI領域ではGUIツールがさらに広まった一年でした。今までは、機械学習モデルを構築したり、データ分析をする場合、プログラミング言語、数学など専門的知識が必要でした。GUIツールでは、プログラミングの記述が不要で、画面操作のみで容易に実行できます。その結果、モデル構築から検証までのスピード・サイクルを早めることもできます。

・サーバー・クラウド 不要!
・専門知識・プログラミング 不要!
・位置設定 不要!
のような、映像を撮影するだけで利用できるAI外観検査ツールも出てきました。

しかし、AI構築が全てツールだけで完結できるわけではありません。解決する課題を見極めたり、必要なデータを収集したり、AI構築するためのリソースはまだとても多いです。

最後に

AI関連の技術のアップデートはとても早く、数年前のものでも古典的な技術になったりします。AI業界で活躍するためには勉強し続けることが大事です。来年もナレッジの蓄積と共有を積極的に行っていきます。

みなさま、良いお年をお迎えください!

株式会社ブリスウェル

nginx リダイレクト記載方法

どうも、こんにちわ。

 

AWS ElasticBeansTalk を使ってとあるPJを進行していたところ、

検索画面で 

検索ボタン押す

結果が表示される

URLバーにカーソル合わせてEnter

 

と押すとなぜか

https://xxx.xxxx.xx/v1/order?orderDateFrom='1111-11-11'

https://xxx.xxxx.xx/v1/order?orderDateFrom='1111-11-11'?orderDateFrom='1111-11-11'

こうなるという... orderDateFrom 君はなぜ2回登場してくるの...

 

Localでは発生しなかったので、EBの nginx.config で記載していた

 

server {
listen 81;
rewrite ^ https://$host$request_uri permanent;

 こいつ怪しくない...??

となりました。

 

Google先生に聞いてみると確かに rewrite より return を使え

みたいなのがちらほらあり、試しに

server {
listen 81;
return 301 https://$host$request_uri;

 に変更したところ発生しなくなった。

 

中々難しいですな... 精進します...

クラスタリング勉強会

昨日は社内でAI勉強会(by 大澤さん)を実施しました。

今回は教師なし学習クラスタリングがテーマでした。コンテンツ・シナリオ・説明の仕方、どれもとても分かりやすく、終始惹きつけられました。「試しにやってみよう!」のコーナーは商品色分類という身近なものを題材にしていてとても興味深いものでした。発表後のディスカッションタイムは今までにないくらい白熱していましたね!

来年は外部向けのAIセミナーも開催する予定です。

ビジネス分野での活用例

クラスタリングは、マーケティングの戦略立案・顧客の特性分け・商品のポジショニング分析などに利用されています。

例えば、購買履歴データを分析してお客様のグループ(価格重視型、健康志向型、新商品重視型 等)を作り、そのグループに対して特別なキャンペーンを実施したり、嗜好に合う商品をリコメンドしたり、することで販売促進のための効果的な施作を打てるようになります。

このようなグルーピングをAI(機械学習)の力で行い、新たな気付きを得ることがクラスター分析の魅力です。

医療分野での活用例

最近はどのような分野・用途で利用されているのかな、と色々調べていたところ

昨日のニュース記事

今回研究グループは、複数のディープラーニングと非階層型クラスタリングを用いることで、病理画像から人間が理解できる情報を自動で取得する新たなAI技術の開発に成功。医療分野では今まで、医師が教えた診断をAIが学習する、すなわち教師以上の分類はできない「教師あり学習」が主に使用されてきたが、今回の研究では医師の診断を必要としない「教師なし学習」により獲得した特徴を、人間が理解できるように変換。再発期間のみを用いた最適な重み付けをAIに行わせることで、これまで不可能であったがんの未知なる情報の獲得を目指した。
引用:QLifePro | がんの特徴をAIが自力で獲得、再発診断精度を向上する新たな特徴も発見-理研ほか

のように、医療分野での利用も進められているようです。

AIが人間の理解を深める情報を提供する、さらに新たな気付きを人間に与えてくれるという、AIと人間が補完しあうとても興味深い技術です。

複数のディープラーニングと非階層型クラスタリングを用いる

についてどのようなアルゴリズムが使われているのか気になったので、論文を確認しました。

We then applied a deep autoencoder we had developed for pathology images (Supplementary Fig. 2) to 128 × 128-pixel image patches, clustering the 2048 intermediate-layers to form 100 features (clusters) by k-means clustering.
引用:Nature Communications | Automated acquisition of explainable knowledge from unannotated histopathology images

ディープオートエンコーダで次元圧縮をして、100個のクラスタ数にk-meansのアルゴリズムクラスタリングしているようです。
(「次元圧縮」と「k-meams」は大澤さんの勉強会にも出てきました!)

最後に

AIのアルゴリズムは色々あります。 難しそうな名前のアルゴリズムもその中身を知れば、自分が対象とする問題に適用可能かの判断もできます。 そして、絞り込んだアルゴリズムに対して、問題やデータの特性を見抜くことで実際に使用するアルゴリズムを選択することができるようになります。

理解を深めるために、実際にプログラミングをしてアルゴリズムに触れるのも大事です。
来年予定しているAIセミナーは、ハンズオンも取り込んでいきたいですね。

株式会社ブリスウェル

ブリスウェル ベトナムへの移動方法( NRT -> SGN )

こんにちは。宇佐美です。弊社子会社であるブリスウェル ベトナムへ出張中です。
ベトナムオフィスへの行程をまとめます。
技術とは関係ありませんが、ブリスウェル ベトナム訪問の際の移動時間や費用のベンチマークとしてお役に立てれば幸いです!

フライト

ベトナム航空 (NRT)→(SGN)

  • 2時間前にチェックイン。空席は真ん中の席のみ。
  • 定刻50分前に搭乗口集合。(離陸は定刻20~30分後。)
  • フライト中のMacBook Pro 15inchの使用&充電は禁止と機内放送あり。
  • 機内のディスプレイ不具合で度々操作不能
    • 不具合は周りの5~6席でも発生
    • 解消には電源マーク長押しで再起動 & 5分ほど待つ必要あり。
  • タンソンニャット国際空港(SGN)着陸は到着予定時刻通り。
  • 入国審査通過まで約40分ほど。

両替

直接ホテルへ向かう予定だったので空港内で両替しました。
ググって評判のよい No.5 "EXIMBANK" と記載がある両替所を利用。
レシートも貰えて1 円 = 210.1 ドンで両替しました。
(当日は1円 = 212ドンぐらいのレートだったタイミングでした。)

空港内の両替所でも、レシートなしでいくらか抜かれていたり計算時に数%レートを落とすところもあるようですのでお気をつけください。
No.5の両替所では機械で枚数を数えた紙幣を渡してもらえました。

空港タクシー乗り場

タクシーはボッタクられる場合があるので、評判が良いビナサンタクシーを目指します。

空港到着口を出たら左に突き進みます。
TAXI STANDと記載があるので、深緑色のシャツを着た人がビナサンの係員です。

他の会社の係員も"イエス!ビナサン!"と言いつつ乗せようとするのでお気をつけください。
(当のビナサンの係員はその様子を微笑みながら見守るだけでした。)

空港からブリスウェル ベトナム

空港から20~30分ほど。120,000ドン(=500~600円ほど)で到着です。
なお、タクシーが空港を出る際に料金が10,000ドンかかります。
私は最後の精算時に合わせて請求でしたが、空港で請求されることもあるようです。 

その他 (Grab)

タクシー以外にもGrabというUberのようなサービスもあります。
乗用車かバイクを選べてタクシーより安いです。
スーツケース有りの利用者を見かけたので次回チャレンジしてみたいです。

f:id:usami-t:20191220004522j:plain

スーツケース+ネックピロー+Grab

以上です。