codingecho

日々の体験などを書いてます

RNNと時系列データを使った気温の予測

時系列データをRNNで扱うサンプルです。過去の気温から未来の気温予想を予測します。

以下のサンプルはGoogle Colaboratory上で実行しています。

気象データセットのダウンロードします。

ちなみに !<command> のようにするとnotebook上でコマンドが使えます。

!wget https://s3.amazonaws.com/keras-datasets/jena_climate_2009_2016.csv.zip
!unzip jena_climate_2009_2016.csv.zip

ロードしてヘッダーとデータ数を確認します。

import os

fname = 'jena_climate_2009_2016.csv'

f = open(fname)
data = f.read()
f.close()

lines = data.split('\n')
header = lines[0].split(',')
lines = lines[1:]

print(header)
print(len(lines))
import numpy as np

float_data = np.zeros((len(lines), len(header) - 1))
for i, line in enumerate(lines):
  values = [float(x) for x in line.split(',')[1:]]
  float_data[i, :] = values

とりあえずプロットしてみます。縦軸は摂氏温度、横軸は10分ごとの時系列です。

from matplotlib import pyplot as plt

temp = float_data[:, 1]
plt.plot(range(len(temp)), temp)

f:id:ytanak:20190130075555p:plain
なんとなく周期性があることがわかりますね!

過去の期間(lookback)を見て、先のポイント(delay)を予測します。

def generator(data, lookback, delay, min_index, max_index, shuffle=False, batch_size=128, step=6):
    if max_index is None:
        max_index = len(data) - delay - 1
    i = min_index + lookback
    while 1:
        if shuffle:
            rows = np.random.randint(min_index + lookback, max_index, size=batch_size)
        else:
            if i + batch_size >= max_index:
                i = min_index + lookback
            rows = np.arange(i, min(i + batch_size, max_index))
            i += len(rows)

        samples = np.zeros((len(rows),
                           lookback // step,
                           data.shape[-1]))
        targets = np.zeros((len(rows),))
        for j, row in enumerate(rows):
            indices = range(rows[j] - lookback, rows[j], step)
            samples[j] = data[indices]
            targets[j] = data[rows[j] + delay][1]
        yield samples, targets

元のデータセットから学習、検証、テスト用のデータに分けます。

lookback = 1440
step = 6
delay = 144
batch_size = 128
train_gen = generator(float_data,
                      lookback=lookback,
                      delay=delay,
                      min_index=0,
                      max_index=200000,
                      shuffle=True,
                      step=step,
                      batch_size=batch_size)
val_gen = generator(float_data,
                    lookback=lookback,
                    delay=delay,
                    min_index=200001,
                    max_index=300000,
                    step=step,
                    batch_size=batch_size)
test_gen = generator(float_data,
                     lookback=lookback,
                     delay=delay,
                     min_index=300001,
                     max_index=None,
                     step=step,
                     batch_size=batch_size)

val_steps = (300000 - 200001 - lookback)

test_steps = (len(float_data) - 300001 - lookback)

Sequential を使ってRNNを構築して学習させます。

from tensorflow.keras.models import Sequential
from tensorflow.keras import layers
from tensorflow.keras.optimizers import RMSprop

model = Sequential()
model.add(layers.Flatten(input_shape=(lookback // step, float_data.shape[-1])))
model.add(layers.Dense(32, activation='relu'))
model.add(layers.Dense(1))
model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen,
                              steps_per_epoch=500,
                              epochs=20,
                              validation_data=val_gen,
                              validation_steps=batch_size)
Epoch 1/20
500/500 [==============================] - 9s 19ms/step - loss: 1.5980 - val_loss: 0.4938
Epoch 2/20
500/500 [==============================] - 9s 17ms/step - loss: 0.5903 - val_loss: 0.3942

予測するには predict を使います。

d = next(train_gen)
x = d[0]
print(x[0:2].shape)
b = np.array(x[0:2])
model.predict(b)

参考

時系列データの扱い方はKeras作者のCholletさんの本に詳しく紹介されています。

Deep Learning with Python

Deep Learning with Python

日本語

PythonとKerasによるディープラーニング

PythonとKerasによるディープラーニング

Google App Engineを使って運用が辛くならない社内向けWebアプリを作る

Google App Engineが社内のWeb Appやちょっとしたツールを公開したいときにとても良さそうだったので紹介したいと思います。

問題点

社内でちょっとWebアプリを提供する環境が欲しいと言ったことはないでしょうか?

Amazon Web ServiceのEC2やGoogle Cloud PlatformのCompute Engine を使っていたり、オンプレミスの環境を用意したりしていますか?

このようなサービスや物理サーバーを使おうとすると、後々になって管理が大変になるだったことがよくあるかもしれません。特にクラウド上で共用で使用しているプロジェクトで、どのインスタンスが使われているかわからなかったり削除してもいいのかわからなくなるなどサーバー管理者にとってとても手間のかかる作業が増えると思います。また少し使うだけなのに、使っていない間も起動しっぱなしになってしまい課金が発生したりします。

社内に公開して外部には公開したくないので認証機能をいれないことも多いと思います。アカウント認証にActive Directoryを使っていることも多い思います。

App Engineを使うとどうなるの?

  • 予算管理の時間を節約できます。リクエストがない間は課金が発生しません。
  • 認証管理や構築の時間を節約できます。G Suiteの認証にActive Directoryを使用しているならApp Engineでも数ステップの設定でActive Directoryによる認証を使うことができます。
  • インフラ管理の時間を節約できます。自動でスケールしてくれたりログをまとめてくれます。

事前に用意すること

  • G Suiteと独自ドメイン
  • Google Cloud Platformのアカウントと支払の設定

公開準備

公開までは

  1. App Engineを課金可能な状態にしておく
  2. ソースコードを書く
  3. アプリケーションを公開する
  4. App Engineの認証設定で特定のドメインのみ許可するように設定する

こんな感じです。

細かく見ていきましょう。

App Engineを課金可能な状態にしておく

App Engineのコンソールを開いて、以下の図のようになっていれば大丈夫です。

f:id:ytanak:20190127085606p:plain
App Engineのコンソール画面

このような画面になっていなく初期設定が必要であればそれに従ってください。

ソースコードを書く

App Engineの画面右上に左から1番目のCloud Shellアイコンをクリックします。

f:id:ytanak:20190127090027p:plain
左から1番目のCloud Shellをクリックする

ローカルで環境整えることも可能なのですがとりあえず手っ取り早くやるには、この方法が良いと思います。Cloud Shell上でアプリケーション作ってみたいと思います。

次に画面の下にクラウドシェルシェルの画面が現れるので、そこの画面の右上にあるCode Editorをクリックします。

f:id:ytanak:20190127085938p:plain
左から2番目のCode Editorを選択する

Cloud Shellが開くので、ここにコードを書いていきます。ちなみにブラウザはSafariだとうまく動かなかったのでGoogle Chromeを使うのがいいと思います。

基本的にやっている事GitHubにあるGoogle Cloud Platformのサンプルと同じです。

必要なのは main.pyrequirements.txtapp.yaml の3つのファイルです。

main.py

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    """Return a friendly HTTP greeting."""
    return 'Hello World!'


if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8080, debug=True)

requirements.txt

runtime: python37

app.yaml

runtime: python37

全てのファイルをコピペすると以下の図のようになると思います。

f:id:ytanak:20190127091819p:plain
必要なファイルを用意した状態

アプリケーションを公開する

後はクラウドする上でコマンドを実行するだけです。

まず初めにgcloudコマンドを使うために認証してアクセストークンを取得しておきます。

$ gcloud auth application-default login

このコマンドを実行するとURLが表示されてGoogleアカウントにログインするように言われるので、リンクをクリックしてG Suiteのユーザーとしてログインします。その後ブラウザにコードが表示されるのでそれをコンソール上に貼り付けます。これでデプロイするための設定が完了しました。

次に以下のコマンドでアプリケーションの作成します。

$ cd my_app
$ gcloud app create

以下のようなエラーが出た場合は既にアプリケーションを作成されている状態なので、特に何もしなくて問題ありません。

$ gcloud app create
ERROR: (gcloud.app.create) The project [...] already contains an App Engine application in region [us-central].  You can deploy your application using `gcloud app deploy`.

次にアプリケーションをデプロイします。

$ gcloud app deploy

以下のように表示されてこの設定でデプロイしていいか聞かれるので、問題なければ実行します。

Services to deploy:

descriptor:      [/home/ysnr_tanaka/my_app/app.yaml]
source:          [/home/ysnr_tanaka/my_app]
target project:  [my-project]
target service:  [default]
target version:  [20190127t092337]
target url:      [https://my-project.appspot.com]


Do you want to continue (Y/n)?

大丈夫です。実行しましょう!

デプロイには数分から10分程度お時間がかかるので、コーヒーを淹れて待ちましょう☕️。

デプロイが完了したら以下のような表示になります。

File upload done.
Updating service [default]...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://my-project-123.appspot.com]

ブラウザで開いて確認してみます。

gcloud app browse

上記のコマンドを実行すると https://my-project-123.appspot.com のようにデプロイ先のリンクが表示されるのでクリックします。

Hello World! が表示されていれば正しくアプリケーションが動いています!

App Engineの認証設定で特定のドメインのみ許可するように設定する

App Engine -> Settings -> Edit ボタンをクリックします。

以下の図のように Google authentication を Google Apps domain に変更して、下のフォームにG Suiteで使用しているドメインを設定します。Save ボタンで設定を保存します。

f:id:ytanak:20190127094936p:plain
認証設定を Google Apps domain に変更する

ちなみに英語が表示されていますが、日本語設定の場合は日本語が表示されると思います。

次にCloud Shell上でアプリケーションの認証設定を以下のように変更します。

app.yaml

runtime: python37

handlers:
  - url: /.*
    script: auto
    login: required

この設定を追加することですべてのURLパスに対して認証を有効にさせます。

設定を有効にするためにもう一度デプロイします。

$ cd my_app
$ gcloud app deploy

これで認証の設定をしたアプリケーションができました。もう一度ログインしていない状態でアプリケーションをブラウザで開いてみると、ログイン画面が表示されると思います。G Suiteのユーザーとしてログインした状態でアクセスすると前回と同じように Hello World! が表示されます。

おわりに

アプリケーションの作成と公開、認証設定を説明しました。今回はPythonを使ってアプリケーションを作成しましたが、Node.jsやJavaなどのサポートされた言語やフレームワークも自由に使うこともできます。また、Dockerコンテナを動かすこともできるのでライブラリや言語に制限はなかったりします。

サーバーや課金、認証管理に多くの時間を使わなくなるといった点でGoogle App Engineは優れていると思います。

Node.jsのrequestでShift JISやバイナリーデータを正しく取得する方法

バージョン

request: 2.88.0

問題と解決策

requestパッケージを使ってShift JISやEUC、ZIPなどのバイナリデータを正しく取得する方法です。

日本語の文字コードをUNICODEに変換したり、zipを解凍したりするときには注意が必要です。

request({ url:'http://example.com/data.zip' }, (err, httpResponse, body) => {
    // bodyにバイナリーが入っているはず
    do something...
});

のようなやり方では勝手に body がUTF-8に変換されてしまってうまくいかないです。

なのでオプションで encoding: null を指定する必要があります。

request({ url:'http://example.com/data.zip', encoding: null }, (err, httpResponse, body) => {
    // bodyには生のバイナリーが入る
    do something...
});

このようにするとbodyはUTF-8に変換されずに生のバイナリーを受け取ることができます。

少しハマりました。。

参考

stackoverflow.com

Cloud Machine Learning Engineを使用した機械学習のシステムアーキテクチャ

Google Cloud PlatformのCloud Machine Learning Engineを使用した機械学習のシステムアーキテクチャについて考える機会があったので紹介したいと思います。

前提条件

  • Google Cloud Platformを使用する
  • オンライン予測が短時間に集中するが頻度は高くない (1予測バースト/day)
  • 使っていないときはシステムを停止させてお金をかけたくない
  • 学習用データは月1回程度増えていく
  • 専属で運用する人がいない
  • オンライン予測するためにREST APIを使いたい
  • 学習用データは人が入力する
  • 学習用データの入力からREST APIの提供までを自動化したい

システム概要

f:id:ytanak:20190117203839p:plain
図1. システムアーキテクチャ

図1はシステム全体の学習パイプラインです。学習用データを人間が入力したあと、REST APIが公開されるまで自動で実行します。

学習して精度の向上があれば新しいモデルに差し替える方法もありますが、 ここでは学習データが追加されるたびに新しいバージョンに更新しています。

処理の流れ

全体の流れは大まかに分けると以下のようになります。

  1. 学習用データの入力
  2. データの結合
  3. 学習
  4. 学習モデルを新しいバージョンにアップデート

以下で細かく見ていきます。

1. 学習用データの入力

前面にバリデーション用のWebサービスをおきます。人間が入力するデータには誤りが発生するので、正しいフォーマットで入力されるようにバリデーション用のWebサービスを入力の入り口とします。

2. データの結合

Cloud StorageにアップロードされたらCloud Functionsでトリガーします。

学習用データが増えることに対応するために、オリジナルのデータと結合されたデータの2つをCloud Storageに別々のバケットとして保存します。

データの結合処理は以下の例のようになります。

main.py

from google.cloud import storage
from google.cloud.storage import Blob


def f(event, context):
    storage_client = storage.Client()
    bucket_name = 'my-origin-bucket'
    bucket = storage_client.get_bucket(event['bucket'])
    blobs = bucket.list_blobs()

    # バケットから学習用CSVデータを取得して結合する
    with open('/tmp/' + "merged.csv", "w") as out_file:
        for blob in blobs:
            print(blob.name)
            with open('/tmp/' + blob.name, "wb") as file_obj:
                blob.download_to_file(file_obj)

            # 最終行に改行文字が無ければ入れたい
            with open('/tmp/' + blob.name) as f:
                for line in f:
                    if line[-1] == '\n':
                        out_file.write(line)
                    else:
                        out_file.write(line + '\n')

    # 結合されたCSVをトレーニング用のバケットに保存する
    store_bucket_name = 'my-bucket'
    store_bucket = storage_client.get_bucket(store_bucket_name)
    blob = Blob("data.csv", store_bucket)
    with open('/tmp/' + "merged.csv", "rb") as f: # working directoryには保存できなので/tmpに保存する
        blob.upload_from_file(file_obj=f)

requirements.txt

google-cloud-storage>=1.13.2,<1.14.0

3. 学習

同じように結合されたデータがCloud FunctionsでトリガーされCloud Machine Learning Engineで学習が実行されます。Cloud Machine Learning Engineのジョブ実行には専用のSDKは提供されていないようなので、 Python Client Library のREST APIを使ってジョブを実行します。

学習処理は以下のようになります。

main.py

import logging
from datetime import datetime

from googleapiclient import discovery
from googleapiclient import errors


def f(event, context):
    try:
        response = train()
        logging.debug(response)
    except errors.HttpError as err:
        logging.error('There was an error creating the training job. Check the details:')
        logging.error(err._get_reason())

def train():
    training_inputs = {'scaleTier': 'BASIC',
                       'packageUris': ['gs://my-ml-bucket/training_package/trainer-0.1.tar.gz'],
                       'pythonModule': 'xgboost_trainer.training',
                       'region': 'us-central1',
                       'jobDir': 'gs://my-ml-bucket/xgboost_job_dir',
                       'runtimeVersion': '1.12',
                       'pythonVersion': '2.7'}

    now =  datetime.now().strftime("%Y%m%d_%H%M%S")
    job_spec = {'jobId': 'grammar_' + now, 'trainingInput': training_inputs}

    project_name = 'my-project-123'
    project_id = 'projects/{}'.format(project_name)
    cloudml = discovery.build('ml', 'v1')
    request = cloudml.projects().jobs().create(body=job_spec, parent=project_id)
    return request.execute()

学習用スクリプト

事前に学習用のスクリプトをPython packageとしてCloud Storageにアップロードしておく必要があります。上記の例では 'packageUris': ['gs://my-ml-bucket/training_package/trainer-0.1.tar.gz'] でパッケージを指定しています。今回は紹介していませんが、この部分も学習用スクリプトが更新されたら自動的にパッケージを作成してアップロードできる仕組みを用意しておくとよいでしょう。

Pythonのパッケージを作成するには

xgboost_trainer/training.py
setup.py

のようなディレクトリ構成を作成したあと

$ python setup.py sdist

を実行します。パッケージは sdist/ に作成されます。詳しくは setup.py について調べてみてください。

学習済みモデルの保存

また、学習用スクリプトで学習した学習済みモデルは自動では保存されません。自前でCloud Storageなどに保存する必要があります。

以下の例では学習済みモデルの my_model.bst をCloud Storageにアップロードしています。

# -*- coding: utf-8 -*-

import os
import subprocess
import sys

import pandas as pd


gcs_model_path = os.path.join('gs://', 'my-bucket', 'data.csv')
subprocess.check_call(['gsutil', 'cp', gcs_model_path, 'data.csv'], stderr=sys.stdout)

df = pd.read_csv('data.csv', sep='\t', encoding='utf-8')

# train something...

# 保存した学習済みモデルをCloud Storageにアップロード
model_filename = 'my_model.bst'
gcs_model_path = os.path.join('gs://', 'my-ml-model-bucket', model_filename)
subprocess.check_call(['gsutil', 'cp', model_filename, gcs_model_path], stderr=sys.stdout)

4. 学習モデルを新しいバージョンにアップデート

学習が終わるとCloud Storageにモデルが保存されます。これをCloud FunctionsでトリガーしてCloud Machine Learning Engineに新しいバージョンとしてデプロイします。デプロイ時に新しいバージョンをデフォルトのバージョンにしておきます。こうすることで、REST APIで特定のバージョンを指定しない限り、常に新しい学習済みモデルを使用して予測ができるようになります。

main.py

import logging
import time
from datetime import datetime

from googleapiclient import discovery
from googleapiclient import errors


def f(event, context):
    try:
        response = deploy()
        logging.debug(response)
    except errors.HttpError as err:
        logging.error('There was an error creating the training job. Check the details:')
        logging.error(err._get_reason())

def deploy():
    project_name = 'my-project-123'
    model_name = 'my-xgboost-model'


    # TODO もしCloud  Machine Learning Engineにモデルが無ければ先に作成するようにしたい

    version_name = 'v' + datetime.now().strftime("%Y%m%d_%H%M%S")
    body = {
        'name': version_name,
        'deploymentUri': 'gs://my-ml-model-bucket',
        'runtimeVersion': '1.12',
        'framework': 'XGBOOST',
        'pythonVersion': '2.7',
    }

    # 新しいバージョンをデプロイする
    cloudml = discovery.build('ml', 'v1')
    parent = 'projects/{}/models/{}'.format(project_name, model_name)
    request = cloudml.projects().models().versions().create(body=body, parent=parent)
    request.execute()

    # バージョンのデプロイが完了するのを待つ
    while True:
        time.sleep(5)
        name = 'projects/{}/models/{}/versions/{}'.format(project_name, model_name, version_name)
        request = cloudml.projects().models().versions().get(name=name)
        response = request.execute()
        logging.info(response)

        if response['state'] == 'READY':
            break

    # 新しいバージョンをデフォルトのバージョンにする
    name = 'projects/{}/models/{}/versions/{}'.format(project_name, model_name, version_name)
    request = cloudml.projects().models().versions().setDefault(name=name)
    return request.execute()

デプロイしたバージョンが if response['state'] == 'READY': でデプロイが完了するまで待っていますが、これには1分ほどかかります。Cloud Functionsのデフォルトの処理のタイムアウト時間が60秒なので120秒ぐらいに変更しておく必要があります。

また、モデルのバージョンの名前には v201901017_092356 のように v + 日時 を使うと管理しやすいです。

これでREST APIとして利用できる準備ができました。あとはApp Engineや外部からCloud Machine Learning Engineのオンライン予測APIにリクエストすれば予測結果が返ってきます。

今後問題となりそうなところ

前提条件の"専属で運用する人がいない"と言うところがまだ解決できていません。ドキュメントの整備はもちろんですが、Google Cloud Platformを構築する時に手間がかかります。この部分もコード化してGoogle Cloud Platform上で全てのサービスをコマンド一発で立ち上げられるようにできると良さそうです。これにはTerraformを使うと良いかな?と思っています。

本の紹介

美術品でたどる マリー・アントワネットの生涯

以前六本木の森美術館で開催されたマリー・アントワネット展に行きました。マリー・アントワネットについて知識がなかったので書籍で知識を得るために、「美術品でたどる マリー・アントワネットの生涯」を読みました。もうだいぶ前になってしまうのであまり覚えてはいなかったりしますが、終盤のページに書いてある"ベルサイユ宮殿内にある王妃の居室"が再現されて美術館に展示されていたのを覚えています。

美術品を見る機会があれば前提知識を持った状態で行ったほうがより楽しめると思います。

 

美術品でたどる マリー・アントワネットの生涯 (NHK出版新書)

美術品でたどる マリー・アントワネットの生涯 (NHK出版新書)

 

 

おカネの教室 僕らがおかしなクラブで学んだ秘密

お金の話についてはあまり今まで興味がなく書籍などを読まなかったのですが、読んでみるようにしました。簡単に言うとお金のあり方みたいなことを理解できました。初めて聞く用語も多く、新鮮でした。表紙からもライトがな感じが出ていますが、ストーリーがあるので高校生や中学生でも簡単に読めてしまうと思います。

 

おカネの教室 僕らがおかしなクラブで学んだ秘密 (しごとのわ)

おカネの教室 僕らがおかしなクラブで学んだ秘密 (しごとのわ)

 

 

儲かる物理 ~人生を変える究極の思考力~

物理の数式を使ってお金を儲ける考え方に繋げていく視点が面白いかなと感じました。当たり前ですがこれをやれば儲かるという正解は無いわけですけど、物理的に考えると言う方法もあるのかと新しい視点が得られました。個人的にも参考になる部分がありました。

 

儲かる物理 ~人生を変える究極の思考力~

儲かる物理 ~人生を変える究極の思考力~

 

 

もうしゃべり下手とは言わせない!

相手の気持ちを考えて返答するみたいことが大事かなと思います。日々の出来事や感じた事をメモして、それを1分ぐらいの話としてまとめてみるといいのかなと思います。そのストーリーは主観的な話ではなく、第三者の視点が入っている表現をするとより良いストーリーが作れるのではと思います。

 

もうしゃべり下手とは言わせない!

もうしゃべり下手とは言わせない!

  • 作者: 川上 政行
  • 出版社/メーカー: ギャラクシーブックス
  • 発売日: 2017/04/16
  • メディア: オンデマンド (ペーパーバック)
  • この商品を含むブログを見る
 

Flutter 1.0.0でCloud Functionsの非同期処理のレスポンスが返ってこないとき

Flutter上でCloud Functionsを使用しするとき非同期で取得しますが、結果が返ってこないときの対処法を紹介します。

Flutterのバージョン: 1.0.0

TL;DR

  • cloud_functionsパッケージを使ったCloud Functionsの呼び出しが完了しない
  • pubspec.yamlのdependenciesにcloud_firestoreを追加する (Cloud Firestoreを使っていなくても)

現象

asyncでcloud_functionsを使ってCloud Functionsのレスポンスを取得しようとしましたが、レスポンス待ちの状態で値が返ってきませんでした。以下のようにFutureBuilderを使いました。

FutureBuilder(
    future: CloudFunctions.instance.call(functionName: 'hello'),
    builder: (BuildContext context, AsyncSnapshot snapshot) {
      if (snapshot.hasData) {
        if (snapshot.data != null) {
          return new Text(snapshot.data['greeting']);
        }
      }

      return Text('none');
    }
)

上記のsnapshot.dataにレスポンスが入るはずですが、非同期処理が完了しないため値が入りません。snapshotのステータスを確認するとwaitingのまま更新されません。

解決策

根本的な解決方法ではないと思いますが、cloud_firestoreのパッケージを追加することで結果を取得できるようになりました。

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  cloud_functions: ^0.0.5
  cloud_firestore: ^0.8.2+3 # これを追加します

Use Cloud Functions on Flutter 1.0.0

When you use Cloud Functions on Flutter, you might add some dependencies in pubspec.yaml.

My Flutter app's version
Flutter: 1.0.0

Below is my code uses FutureBuilder.

FutureBuilder(
    future: CloudFunctions.instance.call(functionName: 'hello'),
    builder: (BuildContext context, AsyncSnapshot snapshot) {
      if (snapshot.hasData) {
        if (snapshot.data != null) {
          return new Text(snapshot.data['greeting']);
        }
      }

      return Text('none');
    }
)

You might add cloud_functions and also cloud_firestore. Because if you only add cloud_functions, your Cloud Functions call status ends up "waiting." I searched this happen, but finally, I didn't get the answer. It might be fixed this error in the future.

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  cloud_functions: ^0.0.5
  cloud_firestore: ^0.8.2+3 # Cloud Functions' depencency might need this depency.