LSTMの文の理解能力

English

LSTMの文の理解能力について、個人的に面白いと思ったので紹介したいと思います。今回使用した学習済みモデルはこちらの記事で紹介しています。

以下の動画は2つの質問を学習済みモデルにしたものです。A(上の質問)は課金に関する質問で、B(下の質問)はアカウント消失に関する質問です。両方は2つのカテゴリー(“課金”と”アカウント”)が混在する質問で、前後のカテゴリーを入れ替えただけのテキストです。

Aの文

この前はアカウントの引き継ぎの問題解決ありがとうございました。今回の不具合はアイテムを購入したのに反映されません。このようなことが続くのは悲しいです。

Bの文

この前はアイテム購入の問題解決ありがとうございました。今回の不具合はアカウントの引き継ぎができない問題です。このようなことが続くのは悲しいです。

これらは先に説明した通り、前後のカテゴリーを入れ替えて意味が逆になるようにしています。このモデルでは両方の質問に対してカテゴリーの分類に成功しています。動画の下の”predictions”の部分が各カテゴリーのスコア(確信度と呼ぶことにします)になっていて、この値が高いカテゴリーほどそのカテゴリーであるということを確信していることになります。

確信度は0はじまりのカンマ区切りで、1列は”その他”、2列目は”アカウント”、3列目は”課金”のカテゴリーであることを表しています。Aのテキストの確信度は以下の通りです。3列目が一番高い確信度になっていることがわかります。つまり課金のカテゴリーに分類していることがわかります。

Aの文

predictions
etc, other, account, payment
0.0038606818, 0.036638796, 0.04247639, 0.46222764

Bのテキストの確信度は以下の通りです。こちらは2列が一番高い確信度が高いことからアカウントのカテゴリーに分類していることがわかります。

Bの文

predictions
etc, other, account, payment
0.0007114554, 0.04938373, 0.72704375, 0.0038164733

これらの一番確信度が高いカテゴリーは、他の確信度より大きな差をつけています。この結果から、LSTMが単純に単語からカテゴリーを分類するのではなく、文からカテゴリーを分類できているのではないかと思います。

今後検証したいこと

  • サンプル数を増やす
  • LSTMの代わりに1-D convolutional networkを使う
  • 学習済みのword embeddingを使う

Understanding sentence with LSTM

I am going to demonstrate LSTM understand a sentence. The model I used explained this blog post.

Below video gives an example classifies the two questions that A is about payment and B is about an account. Both texts are what mix these two categories up and also reverse these sentence before and after each other.

The A (upper question) means in English “Thank you for helping a problem with an account. But, today, I get another problem about payment. I am sad about this happening.”

The B (lower question) means in English “Thank you for helping a problem with payment. But, today, I get another problem about an account. I am sad about this happening.”

These examples flip these means each other. And the A and B succeeded to classify categories. The model is sure of the categories because the score gets higher than the other scores. Let’s look at the score on the video. Below the predictions on the video shows the score, higher is better.
The 1st column (zero-based) express “other” category, 2nd is “account,” and 3rd is “payment.” The score like this:

Sentence A

predictions
etc, other, account, payment
0.0038606818, 0.036638796, 0.04247639, 0.46222764

Sentence B

predictions
etc, other, account, payment
0.0007114554, 0.04938373, 0.72704375, 0.0038164733

In the A, 3rd column is higher more than the other columns. It means the model is sure the A is about “payment” category. B is the same as A; it is certain of “account” category.

Thus, I found this model which uses LSTM may understand the sentence of a text.

Future tasks

  • Use more samples
  • Use 1-D convolutional network instead of LSTM
  • Use pre-trained word embedding

LSTMを使ってテキストの他クラス分類をする

English

Kerasを使ってテキスト分類をするWebアプリケーションのプロトタイプを作ってみました。このプロトタイプはカスタマーサービスで利用することを想定してカスタマーからの質問に自動で返答することを考えます。質問はいくつかのカテゴリーに属していて、アプリケーションがそのカテゴリーを分類できるようにします。

サンプルのソースコードはGitHubを参照してください。

データを集める

分類モデルを作る前にデータセットを集める必要があります。インターネット上にある記事などを見るとIMDBの映画レビューのデータセットを使っていることが多いように思います。今回はこのデータセットではなく質問と回答のデータセットを別で用意しました。

ファイルフォーマット

ファイルはTSVで質問ID、質問テキスト、返答テキスト、カテゴリーを含んでいます。質問と返答テキストは日本語です。以下のような形式です。

id question answer category

このデータセットは約9000サンプルで、カテゴリーの種類は約15です。

データをロードする

TSVファイルから読み込みます。

import json
import numpy as np
import csv

issues = []

with open("data/issues.tsv", 'r', encoding="utf-8") as tsv:
    tsv = csv.reader(tsv, delimiter='\t')

    for row in tsv:
        row = []
        row.append(row[1]) # question
        row.append(row[2]) # answer
        row.append(row[3]) # category

        issues.append(row)

テキストの前処理

使わない文字を削除

データセットのテキストデータにはe-mailのアドレスや記号など今回使用しない文字列が含まれているのでそれらを削除します。

以下のようなテキストの例を考えます。削除する文字列は単純に正規表現で空文字に置換しています。

filtered_text = []
text = ["お時間を頂戴しております。version 1.2.3 ----------------------------------------"]

for t in issues:
    result = re.compile('-+').sub('', t)
    result = re.compile('[0-9]+').sub('0', result)
    result = re.compile('\s+').sub('', result)
    # ... このような置換処理が複数繋がっています

    # 質問テキストが空文字になることがあるのでその行は含めないようにします
    if len(result) > 0:
        sub_texts.append(result)

    filtered_text.append(result)
    print("text:%s" % result)
    # text:お時間を頂戴しております。

サンプルとラベルを作成します

データセットからサンプルとラベルを作成します。今回は全て使うのではなく15カテゴリーの中から例として”Account”と”Payment”の2カテゴリのみ使用します。それ以外は”その他”としてラベルづけします。サンプルはこの3
つのラベルで同じサイスである必要があります。データ数が偏ってしまうとLSTMでうまく分類できなくなってしまいます。今回は”Payment”のラベルが688サンプルしかなかったので、約700のサンプル数に揃えました。

サンプルとラベルを作成する

labels = []
samples = []
threshold = 700
cnt1 = 0
cnt2 = 0
cnt3 = 0

for i, row in enumerate(filtered_samples):
    if 'Account' in row[2]:
        if cnt2 < threashold:
            cnt1 += 1
            labels.append(2)
            samples.append(row[0])
    elif 'Payment' in row[2]:
        if cnt3 < threashold:
            cnt3 += 1
            labels.append(3)
            samples.append(row[0])
    else:
        if cnt1 < threashold:
            cnt1 += 1
            labels.append(1)
            samples.append(row[0])

filtered_samplesは事前に記号などを削除したデータセットです。

MeCabを使って分かち書きにする

質問テキストは日本語なので分かち書きにする必要があります。例えば以下のようなテキストがあるとします。

お時間を頂戴しております

このテキストをMeCabで分かち書きに変換します。

import MeCab
import re

def tokenize(text):
    wakati = MeCab.Tagger("-O wakati")
    wakati.parse("")
    words = wakati.parse(text)

    # Make word list
    if words[-1] == u"\n":
        words = words[:-1]

    return words

texts = [tokenize(a) for a in samples]

以下のようにスペースで区切られたテキストになります。

お 時間 を 頂戴 し て おり ます

サンプルとラベルを分割する

サンプルとラベルとトレーニングデータと検証データに分割します。

from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as np
from keras.utils.np_utils import to_categorical

maxlen = 1000
training_samples = 1600 # training data 80 : validation data 20
validation_samples = len(texts) - training_samples
max_words = 15000

# word indexを作成
tokenizer = Tokenizer(num_words=max_words)
tokenizer.fit_on_texts(texts)
sequences = tokenizer.texts_to_sequences(texts)

word_index = tokenizer.word_index
print("Found {} unique tokens.".format(len(word_index)))

data = pad_sequences(sequences, maxlen=maxlen)

# バイナリの行列に変換
categorical_labels = to_categorical(labels)
labels = np.asarray(categorical_labels)

print("Shape of data tensor:{}".format(data.shape))
print("Shape of label tensor:{}".format(labels.shape))

# 行列をランダムにシャッフルする
indices = np.arange(data.shape[0])
np.random.shuffle(indices)
data = data[indices]
labels = labels[indices]

x_train = data[:training_samples]
y_train = labels[:training_samples]
x_val = data[training_samples: training_samples + validation_samples]
y_val = labels[training_samples: training_samples + validation_samples]

data は以下のような整数のシーケンスなデータになっています。

[0, 0, 0, 10, 5, 24]

0以外の整数は分かち書きにした各単語と一致しています。0は単語がないことを意味します。上記の例だと3単語のため左の3列は0で埋められています。

モデルの作成と学習

学習にはKerasを使用しています。KerasにはLSTMとword embeddingが用意されているので、それを使います。LSTMは時系列データの分類や回帰問題などに利用されます。

モデルの作成

from keras.models import Sequential
from keras.layers import Flatten, Dense, Embedding
from keras.layers import LSTM

model = Sequential()
model.add(Embedding(15000, 100, input_length=maxlen))
model.add(LSTM(32))
model.add(Dense(4, activation='sigmoid'))
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['acc'])
model.summary()

このモデルはLSTMの学習の他にEmbedding()を使ってword embeddingも同時に学習します。

学習する

model.fit()を呼ぶだけです。

history = model.fit(x_train, y_train, epochs=15, batch_size=32, validation_split=0.2, validation_data=(x_val, y_val))

結果をプロットする

%matplotlib inline

import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

以下のような結果になりました。

最終的にvalidation accuracyが約90%になりました。

モデルを保存する

モデルと学習した重みを保存します。

model.save('pre_trained_model.h5')

Webアプリケーションを作成する

学習済みモデルをWebアプリケーションに組み込みます。Kerasと同じ言語の方が扱いやすかったのでWebフレームワークにはFlaskを使いました。このアプリケーションをテキストは受け取って、そのテキストのカテゴリーを予測した結果をユーザーに返すだけです。以下のようにテキストエリアと質問ボタンがあり、予測した結果が表示されます。

質問を予測する

カテゴリーを予測する前にword indexを作成する必要があります。このword indexはモデルを作成した時と同じものです。

app.py

# 学習済みモデルをロードする
model = load_model('../pre_trained_model.h5')

# padded_seqは2次元の行列で渡す必要があります
result = model.predict([padded_seq])

予測の結果を取得する。

np.argmax(res[0])

ソースコードはこちらのリポジトリを参照してください。

参考文献

Deep Learning with Python こちらの書籍がとても参考になりました!Keras作者のCholletさんによって書かれているのでとてもオススメです。

Multi-categorical text classification with LSTM

I created the prototype of a web application for customer service that uses sequence classification with Keras. This prototype’s purpose is to reply the proper response of some categories to our customer are based on the questions customer sent to us. The questions relate to some categories, and then the application predicts to which category a question belongs.

If you are looking for the same situation, this sample might be helpful for you.

You can see the whole source code in GitHub.

Collect text data

Before creating a classification model, collect data set for creating it. Many classification’s articles on the internet use the IMDB movie review data set, I think. Instead, I use customer services’ question and its categories in our product. I collected this data and store as TSV file.

File format

The format is TSV, and it consists id, question, answer, and the category of question like this:

id question answer category

This raw data set has about 9000 samples. But they include unusable data and have about 15 categories of question.

Load data

Load data from TSV formatted file.

import json
import numpy as np
import csv

issues = []

with open("data/issues.tsv", 'r', encoding="utf-8") as tsv:
    tsv = csv.reader(tsv, delimiter='\t')

    for row in tsv:
        row = []
        row.append(row[1]) # question
        row.append(row[2]) # answer
        row.append(row[3]) # category

        issues.append(row)

Pre-process text

Remove unnecessary characters

These samples are rough for learning. It means that some sample has no question text, and has an e-mail address and symbol like a hyphen. So We have to remove these unnecessary characters.

I removed these with just regular expression and the
question which is an empty string like this:

filtered_text = []
text = ["長らくお時間を頂戴しております。version: 1.2.3 ----------------------------------------"]

for t in issues:
    result = re.compile('-+').sub('', t)
    result = re.compile('[0-9]+').sub('0', result)
    result = re.compile('\s+').sub('', result)
    # ... and many regular expression substitutions

    # remove empty string question
    if len(result) > 0:
        sub_texts.append(result)

    filtered_text.append(result)
    print("text:%s" % result)
    # text:長らくお時間を頂戴しております。

Create samples and labels

Create samples and labels from the data set. It has about 15 categories of labels. And I select two label types, ‘Account’ as two and ‘Payment’ as three; they are question’s categories. And add the other all labels as one which includes the other categories excepts Account, Payment. The samples and labels have to be the same size roughly because LSTM learning wouldn’t work well if one of these is more or less. In this case, cap the samples’ size it’s 700 samples because the payment label has only 688 samples.

Create samples and labels

labels = []
samples = []
threshold = 700
cnt1 = 0
cnt2 = 0
cnt3 = 0

for i, row in enumerate(filtered_samples):
    if 'Account' in row[2]:
        if cnt2 < threashold:
            cnt1 += 1
            labels.append(2)
            samples.append(row[0])
    elif 'Payment' in row[2]:
        if cnt3 < threashold:
            cnt3 += 1
            labels.append(3)
            samples.append(row[0])
    else:
        if cnt1 < threashold:
            cnt1 += 1
            labels.append(1)
            samples.append(row[0])

filtered_samples is what we removed some symbols, e-mail address or something like these from the samples.

Separate the words by MeCab

The questions in the samples written in Japanese. So have to separate words into each word with space. Below is a question text in Japanese:

長らくお時間を頂戴しております

I used MeCab to get space-separated words:

import MeCab
import re

def tokenize(text):
    wakati = MeCab.Tagger("-O wakati")
    wakati.parse("")
    words = wakati.parse(text)

    # Make word list
    if words[-1] == u"\n":
        words = words[:-1]

    return words

texts = [tokenize(a) for a in samples]

This tokenize function returns space-separated words:

長らく お 時間 を 頂戴 し て おり ます

Divde the samples and labels

Divide the samples and labels into training data and validation data:

from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as np
from keras.utils.np_utils import to_categorical

maxlen = 1000
training_samples = 1600 # training data 80 : validation data 20
validation_samples = len(texts) - training_samples
max_words = 15000

# create word index
tokenizer = Tokenizer(num_words=max_words)
tokenizer.fit_on_texts(texts)
sequences = tokenizer.texts_to_sequences(texts)

word_index = tokenizer.word_index
print("Found {} unique tokens.".format(len(word_index)))

data = pad_sequences(sequences, maxlen=maxlen)

# to binary class matrix
categorical_labels = to_categorical(labels)
labels = np.asarray(categorical_labels)

print("Shape of data tensor:{}".format(data.shape))
print("Shape of label tensor:{}".format(labels.shape))

# shuffle indices
indices = np.arange(data.shape[0])
np.random.shuffle(indices)
data = data[indices]
labels = labels[indices]

x_train = data[:training_samples]
y_train = labels[:training_samples]
x_val = data[training_samples: training_samples + validation_samples]
y_val = labels[training_samples: training_samples + validation_samples]

The data is integer sequese like this:

[0, 0, 0, 10, 5, 24]

Each non-zero integer relates to a word and the zero stands for “empty word.” Therefore, this words size is just three and the rest of the sequence will be filled with zero.

Create a model and learn features

I used Keras for learning features. It includes LSTM and Word embedding. LSTM is used for a sequence classification problem, sequence regression problem and so on.

Create a model

from keras.models import Sequential
from keras.layers import Flatten, Dense, Embedding
from keras.layers import LSTM

model = Sequential()
model.add(Embedding(15000, 100, input_length=maxlen))
model.add(LSTM(32))
model.add(Dense(4, activation='sigmoid'))
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['acc'])
model.summary()

This model learns with LSTM and also word embedding with Embedding(...) at the same time. We can also use pre-trained word embedding instead learning word embedding.

Learn features

Just call model.fit()

history = model.fit(x_train, y_train, epochs=15, batch_size=32, validation_split=0.2, validation_data=(x_val, y_val))

Plot the result

%matplotlib inline

import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

The result is like this:

Finally, the validation accuracy becomes about 90 percent.

Save the model

Save the model and weights learned.

model.save('pre_trained_model.h5')

Create a web application

I wanted to use the pre-trained model with a web application. So I used Flask this time because its language is the same as Keras. And this application is simple, receives a text, predicts and then responses its category to the user. This application has a text area, an ask button and the result of a prediction.

Predict a certain question

Before predicting a text, we have to calculate the word index the same as we created for creating the pre-trained model.

app.py

# load the pre traind model
model = load_model('../pre_trained_model.h5')

# we have to pass padded_seq as 2-dimentional array
result = model.predict([padded_seq])

Get the classified result:

np.argmax(res[0])

Please see the whole source code in my repository.

Reference

Deep Learning with Python This book helpful for me!

CoreMLを使ったGenerative Adversarial NetworkのiOS Appを作る

English page

iOS上でCoreMLを使って手書き文字を生成するアプリを作成しました

手書き文字の生成からアプリの公開までをまとめておきたいと思います。

使ったソフトウェア

  • Xcode 9.2 (9C40b)
  • Docker 17.09.1-ce
  • TensorFlow docker imagesha256:1bb38d61d261e5c9230a1e60b5d200088eb03014fdb35a91859fa55ea0d2c4d5, https://hub.docker.com/r/tensorflow/tensorflow
  • TensorFlow 1.2.1
  • Keras 2.0.6
  • coremltools 0.7

手書き文字画像の生成からiOS appのリリースまで

図1は手書き文字画像の生成からiOS appのリリースまでの手順を表しています。

the flow of distributing an app
図1. iOS appリリースまでの手順

DockerのインストールしてTensorFlowとKerasが動作するJupyter Notebookを用意する

Kerasモデルを作成するためにDockerをインストールします。

以下のコマンドを実行します。

$ docker run -d --name notebook tensorflow/tensorflow:latest

KerasとCoreML converterをインストールする

$ docker exec -it notebook /bin/bash
$ pip install -U keras
$ pip install -U coremltools

Jupyter Notebookにアクセスできることを確認する

Jupyter NotebookにアクセスするためのURLを取得します。

$ docker logs notebook
# 以下のようなトークンがついたURLが表示されると思います
#
#    Copy/paste this URL into your browser when you connect for the first time,
#    to login with a token:
#        http://localhost:8888/?token=ecf1a2471670eb6863195ab530d6ac1d5cc27511faca0afe

上記のURLをブラウザで開くとアクセスできると思います。
これでKerasのコードを書けるようになります。

Kerasモデルを作成する

Jupyter Notebookに新しいノートを追加してGANモデルのコードを書きます。GANのコードはこちらを参考にしました。このコードをコピーしてcellに貼り付けます。モデルをディレクトリにセーブするために以下のようにコードを追加します。

if __name__ == '__main__':
    gan = GAN()
    gan.train(epochs=30000, batch_size=32, save_interval=200)
    gan.discriminator.save('./discriminator.h5') // discriminatorモデルを保存するディレクトリを指定します
    gan.generator.save('./generator.h5') // generatorモデルを保存するディレクトリを指定します

あとはcellを実行します。MacBook Pro 2016で実行した感じだと25分ほどかかりました。

このcellを実行すると学習済みモデルが指定のディレクトリに”.h5″のファイルが保存されます。

KerasモデルをCoreMLモデルに変換する

$ coremlconverter --srcModelPath ./keras_model.h5 --dstModelPath ./coreml_model.mlmodel --inputNames gunInput --outputNames ganOutput

このコマンドでKerasのモデルをCoreMLのモデルにコンバートします。コンバートが完了すると”.mlmodel”のCoreMLモデルファイルが生成されます。

手書き文字画像を生成するアプリを作成する

手書き文字画像を表示するにはWeb application、iOS、Androidや単純にJupyter Notebookを使う方法がありますが、iOSを触ってみたかったのでiOSにしました。

アプリを作成する

作成したアプリのソースコードはこちらで公開しています。

図2はアプリのスクリーンショットを示しています。


図2. アプリ (GANs generator)のスクリーンショット

このアプリはCoreMLを使用してKerasのモデルから28 x 28の手書き文字画像を生成します。手書き文字画像はUIKitを使用して表示します。また、手書き文字画像を再生成するボタンがあります。

手書き文字画像を生成する

最初にKerasモデルからコンバートしたCoreMLモデルをXcodeのプロジェクトにインポートします。図3のようになっていると思います。


図3. CoreMLモデルのプロパティ

Xcodeにモデルをインポートすると、モデルのクラスが自動的に生成されます。このクラスは手書き文字画像を生成するpredictionメソッドが実装されています。そして、predictionメソッドの第1引数にKerasモデルのinputが指定されます。第1引数のラベルにはCoreML converterの--inputNamesで指定した値が使用されます。この場合だとクラスのpredictionメソッドの第一引数のラベルをganInput、出力された手書き文字画像をganOutputとして定義しています。この定義は図3のように”.mlmodel”ファイルのModel Evaluation ParametersInputsOutputsで確認することができます。

// Create a gan instance
var model = gan()
// Generate a handwritten image
var output = model.prediction()

// render hand-written image
let HIGHT = 27
let WIDTH = 27

for i in 0...HIGHT {
  for j in 0...WIDTH {
    //create the path
    let plusPath = UIBezierPath()

    //set the path's line width to the height of the stroke
    plusPath.lineWidth = Constants.plusLineWidth

    //move the initial point of the path
    //to the start of the horizontal stroke
    plusPath.move(to: CGPoint(
      x: CGFloat(j * 10),
      y: CGFloat(i * 10) + Constants.plusLineWidth / 2
    ))

    //add a point to the path at the end of the stroke
    plusPath.addLine(to: CGPoint(
      x: CGFloat((j * 10) + 10),
      y: CGFloat(i * 10) + Constants.plusLineWidth / 2
    ))

    //set the stroke color
    let index: [NSNumber] = [0 as NSNumber, i as NSNumber, j as NSNumber]
    UIColor(white: CGFloat(truncating: out.gan_out[index]), alpha: CGFloat(1)).setStroke()

    //draw the stroke
    plusPath.stroke()
  }
}

このプロジェクトをXcodeで開いてシミュレーターで確認することができます。

Apple Developer Programに登録する

アプリの動作を確認したら、App Storeにこのアプリを公開します。もし、Apple Developer Programに登録してなけらば、こちらで登録を済ませます。アプリをApp Storeに公開するにはApple Developer Programの登録が必要です。

App Storeに登録する

詳しくは公式ドキュメントを参照してください。

  1. Archiving
    Product > Archive

  2. Validating
    archivingが完了するとArchive organizerValidateができるようになるのでApp Storeにアップロードする前に実行して、アプリに問題がないかチェックしておきます。

  3. App Storeにアプリをアップロードする
    Upload to App Storeをクリックします

アプリを公開する

詳しくは公式ドキュメントを参照してください。

  1. アプリをiTunes connectに登録する
  2. アプリのアイコンとプレビュー、スクリーンショットを追加する
  3. App Reviewに出す
  4. アプリをリリースする

Create a Generative Adversarial Network iOS App with CoreML

I created the app that generates a handwritten image with CoreML on iOS.

I’m going to explain the process to release this app from beginning to end.

The software versions

  • Xcode 9.2 (9C40b)
  • Docker 17.09.1-ce
  • TensorFlow docker imagesha256:1bb38d61d261e5c9230a1e60b5d200088eb03014fdb35a91859fa55ea0d2c4d5, https://hub.docker.com/r/tensorflow/tensorflow
  • TensorFlow 1.2.1
  • Keras 2.0.6
  • coremltools 0.7

The process to make handwritten image generator app on iOS and submit it to the App Store

As shown in figure 1, it illustrates the path of distributing this app through App Store.

  1. Create a GAN (Generative Adversarial Network) model on Keras
  2. Convert a Keras GAN model to CoreML model
  3. Create an app to show handwritten images generated by the CoreML model
  4. Distribute the app

the flow of distributing an app
Figure 1. The path of distributing this app through App Store

Install Docker and run Jupyter Notebook that includes TensorFlow and Keras

At first, To create a Keras model, we have to install Docker.

Install Docker

On Mac, get it in this page at the Stable channel and just install it.

Run Jupiter Notebook with TensorFlow

Fortunately, there is the Docker image that includes TensorFlow.

Just type this command:

$ docker run -d --name notebook tensorflow/tensorflow:latest

Install Keras and CoreML converter

$ docker exec -it notebook /bin/bash
$ pip install -U keras
$ pip install -U coremltools

Check that you can access the Jupyter Notebook

Before access it, you need to get an access token.

$ docker logs notebook
# You will find an access token like this:
#
#    Copy/paste this URL into your browser when you connect for the first time,
#    to login with a token:
#        http://localhost:8888/?token=ecf1a2471670eb6863195ab530d6ac1d5cc27511faca0afe

Copy the URL with an access token and access on your browser.

Now it’s done! You come can execute Keras code!

Make a Keras model

Add a new note to Jupyter Notebook and write a GAN model. I referred to this source code. Copy this code and paste to your Jupyter Notebook’s cell and add the code below. It will save a model in a directory.

if __name__ == '__main__':
    gan = GAN()
    gan.train(epochs=30000, batch_size=32, save_interval=200)
    gan.discriminator.save('./discriminator.h5') // Specify the directory saved the discriminator model
    gan.generator.save('./generator.h5') // Specify the directory saved the generator model

Then just run the cell. It took time about for 25 minutes on my MacBook Pro 2016 to training this model.

Keras create a learned model in the directory where executed the cell. It is followed by “.h5”.

Convert a Keras model to CoreML model

$ coremlconverter --srcModelPath ./keras_model.h5 --dstModelPath ./coreml_model.mlmodel --inputNames gunInput --outputNames ganOutput

This command uses TensorFlow in the background and converts to a CoreML model. After the converting, it makes a CoreML model is followed by .mlmodel in the specified directory. This file is a CoreML converted model from Keras model.

Create an app to show handwritten images

There are some ways to create an app that draws a handwritten image. For instance, as a web application or mobile application. This time, I created an app for iOS.

Create the app

Here is the source code for the app:
https://github.com/yanak/gangen/blob/master/Gangen/HandwrittenImage.swift

figure 2 is the app’s screenshot.


Figure 2. The GANs generator app’s screenshot

This app generates a 28 x 28 handwritten image using the CoreML model converted from Keras, and that renders the image by UIKit. There is the regenerate button that regenerates a handwritten image.

Generate a handwritten image

First, import the CoreML model converted from the Keras model to Xcode project directory. It shows like figure 3:


Figure 3. the CoreML model properties

After Xcode imports, a model, the class of a model is automatically generated by Xcode. this class has the prediction method and it specifies Keras model’s input as the first argument. The first argument’s label uses the name of the CoreML converter’s --inputName option value. In this case, The class defines the label of prediction as ganInput and a generated handwritten image’s shape as ganOutput. You can see its definitions in the Model Evaluation Parameters in the CoreML model(see figure 3.)

// Create a gan instance
var model = gan()
// Generate a handwritten image
var output = model.prediction()

// render hand-written image
let HIGHT = 27
let WIDTH = 27

for i in 0...HIGHT {
  for j in 0...WIDTH {
    //create the path
    let plusPath = UIBezierPath()

    //set the path's line width to the height of the stroke
    plusPath.lineWidth = Constants.plusLineWidth

    //move the initial point of the path
    //to the start of the horizontal stroke
    plusPath.move(to: CGPoint(
      x: CGFloat(j * 10),
      y: CGFloat(i * 10) + Constants.plusLineWidth / 2
    ))

    //add a point to the path at the end of the stroke
    plusPath.addLine(to: CGPoint(
      x: CGFloat((j * 10) + 10),
      y: CGFloat(i * 10) + Constants.plusLineWidth / 2
    ))

    //set the stroke color
    let index: [NSNumber] = [0 as NSNumber, i as NSNumber, j as NSNumber]
    UIColor(white: CGFloat(truncating: out.gan_out[index]), alpha: CGFloat(1)).setStroke()

    //draw the stroke
    plusPath.stroke()
  }
}

Open this project, you can check to run it in a simulator like this:

Enroll Apple Developer Program

After check running the app, submit it to the App Store! If you don’t enroll Apple Developer Program yet, you need to do it in here because it needs to submit the app to the App Store.

Submit to the App Store

In detail, see Submitting Your Apps

  1. Archiving
    To archive, on Xcode, Product > Archive

  2. Validating
    Xcode shows the archived app in the Archives organizer. Validate an archived app before uploading to App Store.

  3. Upload to iTunes connect
    Click Upload to App Store

Publish app

In detail, see iTunes Connect Developer Help page.

  1. Add an app in iTunes Connect
  2. Add app icon, app previews and screenshots.
  3. Submit an app to App Review
  4. Release an app