エクサウィザーズ Engineer Blog

株式会社エクサウィザーズのエンジニアチームブログ

Sketch-RNN でスケッチの自動生成(VAE + LSTM)

こんにちは.エクサウィザーズでインターンをしている川畑です.

視覚によるコミュニケーションというのは人々が相手に何らかのアイデアを伝える際に鍵となります.私たちは小さい頃から物体を描く力を養ってきており,時には感情までもたった複数の線で表現することも可能です.こうした単純な絵というのは,身の回りのものを写真のように捉え忠実に再現したものではなく,どのようにして人間が物体の特徴を認識しそれらを再現するか,ということを教えてくれます.

そこで今回はSketch-RNNと呼ばれるRecurrent Neural Networkモデルでのスケッチの自動生成に取り組んでみました. このモデルは人間がするのと同じように抽象的な概念を一般化し,スケッチを生成することを目的としたものです.このモデルに関しては今の所具体的なアプリケーションが存在するというわけではなく,機械学習がどのようにクリエイティブな分野で活用できるか,という一例を提案したものになります.ソースコードはこちらからダウンロードできます.

f:id:k_kawabata:20181027163327p:plain [1] Google AI Blog: Teaching Machines to Draw

データセット

今回は論文でも用いられていたQuick, Draw!のデータセットを使用しました.これは20秒以内であるお題の絵を描くゲームで,データセットとしてネコやブタ,バスなど数百個のクラスのデータを公開しています.各クラスのデータは,訓練,検証,テストとしてそれぞれ70000,2500,2500のデータに分かれています.

Sketch-RNNモデルを学習させるにあたり,データのフォーマットとしては各点のデータが5つの要素からなるベクトル \left(\Delta x, \Delta y, p_{1}, p_{2}, p_{3} \right)を使用しました.最初の二つの要素は,前の点からの x, yの変位,残りの三つの要素はone-hotベクトルとなっています.一つ目のペン状態 p_{1}はペンが現在紙に接していて,次の点まで線が描かれることを示しており,二つ目のペン状態 p_{2}はそこでペンが紙から離れ,次の点まで線は引かれないことを示しており,三つ目のペン状態 p_{3}はスケッチが終了することを示しています.

Sketch-RNNモデル

それではモデルについて解説していきたいと思います.

[2] A Neural Representation of Sketch Drawings

f:id:k_kawabata:20181027164205p:plain

モデルの大枠はSequence-to-Sequence Variational Autoencoder (VAE) でできています.まず,エンコーダーであるbidirectional RNN (今回は単純なLSTMを使用)にスケッチのシーケンスを入力し,出力として潜在ベクトル zを得ます.具体的には,双方向のRNNから得られた隠れ状態を連結し,全結合層によって連結された隠れ状態から \muおよび \hat{\sigma}を得ます. \hat{\sigma}に関しては,負にならないように \sigma = \exp \left ({\frac{\hat{\sigma}}{2}} \right)の操作を加えます.そしてさらにガウス分布 \mathcal{N}(0, I)と組み合わせることで最終的に潜在ベクトルを計算することができます.式で書くと以下のようになります.

 \displaystyle \mu = W_{\mu}h + b_{\mu}, \;  \hat{\sigma} = W_{\sigma}h + b_{\sigma}, \; \sigma = \exp \left ({\frac{\hat{\sigma}}{2}} \right), \; z = \mu + \sigma\odot\mathcal{N}(0, I)

潜在ベクトルが得られたら,次はデコーダーです.隠れ状態の初期値には潜在ベクトルに tanh関数を掛けたものを用います.

 \displaystyle \left[h_{0}; c_{0} \right] = tanh(W_{z}z + b_{z})

各ステップでのデコーダーの入力には前ステップでのストローク S_{i-1}と潜在ベクトル zを連結したものを与え, S_{0}にはスケッチの開始を意味する (0, 0, 1, 0, 0)を与えるようにします.各ステップでの出力は次のデータ点の確率分布に関するパラメータを返します.Sketch-RNNでは M個の正規分布からなるGaussian mixture model (GMM)により \left(\Delta x, \Delta y \right)を,カテゴリカル分布 \left(q_{1}, q_{2}, q_{3} \right) \left(q_{1} + q_{2} + q_{3} = 1 \right)により真値である \left(p_{1}, p_{2}, p_{3} \right)をモデル化します.

 \displaystyle p(\Delta x, \Delta y) = \sum_{j=1}^{M} \Pi _{j} \mathcal{N} \left(\Delta x, \Delta y \mid \mu_{x, j}, \mu_{y, j}, \sigma_{x, j}, \sigma_{y, j}, \rho_{xy, j} \right), where \sum_{j=1}^{M} \Pi_{j} = 1

上の式において \mathcal{N} \left(x, y \mid \mu_{x}, \mu_{y}, \sigma_{x}, \sigma_{y}, \rho_{xy} \right)は2変量正規分布を表しており,5つのパラメータ \left(\mu_{x}, \mu_{y}, \sigma_{x}, \sigma_{y}, \rho_{xy} \right)で構成されます. \mu_{x}, \mu_{y}は平均,  \sigma_{x}, \sigma_{y}は標準偏差, \rho_{xy} x, yの相関係数です.また, \Piは長さ Mのベクトルであり,Gaussian mixture modelの各正規分布の重みに相当します.ですので,出力のサイズは3つのロジット \left(q_{1}, q_{2}, q_{3} \right)も含めて合計で 5M + M + 3となります.

 \displaystyle x_{i} = \left[S_{i-1}; z \right], \left[h_{i}; c_{i} \right]

 y_{i} = W_{y}h_{i} + b_{y}, \; y_{i} \in \mathbb{R}^{6M+3}

ここで,出力ベクトル y_{i}は次のように分解されます.

 \displaystyle \left[ (\hat{\Pi}_{1} \, \mu_{x} \, \mu_{y} \, \hat{\sigma}_{x} \, \hat{\sigma}_{y} \,  \hat{\rho}_{xy})_{1} \ldots (\hat{\Pi}_{M} \, \mu_{x} \, \mu_{y} \, \hat{\sigma}_{x} \, \hat{\sigma}_{y} \,  \hat{\rho}_{xy})_{M} \; (\hat{q}_{1} \, \hat{q}_{2} \, \hat{q}_{3})  \right] = y_{i}

標準偏差,相関係数に関してはそれぞれ負にならないように,また-1から1の値を取るように exp tanhによる操作を施します.

 \displaystyle \sigma_{x} = \exp\left(\hat{\sigma}_{x} \right), \; \sigma_{y} = \exp\left(\hat{\sigma}_{y} \right), \; \rho_{xy} = tanh\left(\hat{\rho}_{xy}  \right)

カテゴリカル分布に関してはSoftmax関数を適応させ,全ての値が0〜1に収まるようにします.

 \displaystyle q_{k} = \frac{\exp(\hat{q}_{k})}{\sum_{j=1}^{3} \exp(\hat{q}_{j})}, \; k \in \{1, 2, 3\}, \; \Pi_{k} = \frac{\exp(\hat{\Pi}_{k})}{\sum_{j=1}^{M} \exp(\hat{\Pi}_{j})}, \; k \in \{1, \ldots , M\}

損失関数

損失関数はVAEと同じでReconstruction Loss,  L_{R}とKullback-Leibler (KL) Divergence Loss,  L_{KL}の二つの項からなります.

  • Reconstruction Loss,  L_{R}

Reconstruction Loss項は訓練データ Sを説明する確率分布の対数尤度を表しており,これを最大化するように学習します.そして,Reconstruction Loss,  L_{R}はさらに座標に関する項 L_{s}とペン状態に関する項 L_{p} からなっており,それぞれ 以下のように書くことができます.

 \displaystyle L_{s} = -\frac{1}{N_{max}} \sum_{i=1}^{N_{s}}log \left(\sum_{j=1}^{M}\Pi_{j, i} \mathcal{N} \left(\Delta x_{i}, \Delta y_{i} \mid \mu_{x, j, i}, \mu_{y, j, i}, \sigma_{x, j, i}, \sigma_{y, j, i}, \rho_{xy, j, i} \right)\right)

 \displaystyle L_{p} = -\frac{1}{N_{max}} \sum_{i=1}^{N_{max}} \sum_{k=1}^{3} p_{k, i} log\left(q_{k, i} \right)

 L_{R} = L_{s} + L_{p}

  • Kullback-Leibler (KL) Divergence Loss,  L_{KL}

KL Divergence Loss項は潜在ベクトル zが標準正規分布からどれだけ離れているかを表しており,これを最小化するように学習することになります.

 \displaystyle L_{KL} = -\frac{1}{2N_{z}} \left(1 + \hat{\sigma} - \mu^{2} - \exp(\hat{\sigma}) \right)

実際に最適化する損失関数には L_{R} L_{KL}を重み付けして足し合わせたものを用います.

 Loss = L_{R} + w_{KL}L_{KL}

それぞれの損失項にはトレードオフの関係があり, w_{KL} \rightarrow 0の時にはモデルは純粋なオートエンコーダーに近づき,より良いReconstruction Lossを得ることができる一方で,潜在空間における事前分布の強化を犠牲にすることになります.

サンプリング

モデルを学習させた後はいよいよスケッチの生成です.サンプリング過程では各ステップごとにGMMとカテゴリカル分布のパラメータを得,そのステップでの出力 S_{i}'を得ます.訓練過程とは異なり,サンプリング過程では出力 S_{i}'を次のステップの入力とし,このサンプリングステップを p_{3} =1となるかステップ数が N_{max}となるまで繰り返していきます.最終的に出力を得る際にはdeterministicに確率密度関数で最も確率の高い点を選ぶのではなく,下記のようにtemperatureパラメータ \tauを導入することで出力のランダムさを調節できるようにしています. \tauは0〜1の値を取り, \tau = 0の時にはモデルはdeterministicになります.

 \displaystyle \hat{q}_{k} \rightarrow \frac{\hat{q}_{k}}{\tau}, \; \hat{\Pi}_{k} \rightarrow \frac{\hat{\Pi}_{k}}{\tau}, \; \sigma_{x}^{2} \rightarrow \sigma_{x}^{2}\tau, \; \sigma_{y}^{2} \rightarrow \sigma_{y}^{2}\tau

コード

モデルに関する部分のコードを示します.Githubに論文著者によるオリジナルのコードもありますが,オリジナルではtensorflowで書かれていたものをkerasで書き換えました.全コードはこちらからダウンロードできます.

# below is where we need to do MDN (Mixture Density Network) splitting of
# distribution params
def get_mixture_coef(output, n_out):
  """Returns the tf slices containing mdn dist params."""
  # This uses eqns 18 -> 23 of http://arxiv.org/abs/1308.0850.
  z = output
  z = tf.reshape(z, [-1, n_out])
  z_pen_logits = z[:, 0:3]  # pen states
  z_pi, z_mu1, z_mu2, z_sigma1, z_sigma2, z_corr = tf.split(z[:, 3:], 6, 1)

  # process output z's into MDN paramters

  # softmax all the pi's and pen states:
  z_pi = tf.nn.softmax(z_pi)
  z_pen = tf.nn.softmax(z_pen_logits)

  # exponentiate the sigmas and also make corr between -1 and 1.
  z_sigma1 = K.exp(z_sigma1)
  z_sigma2 = K.exp(z_sigma2)
  z_corr = tf.tanh(z_corr)

  r = [z_pi, z_mu1, z_mu2, z_sigma1, z_sigma2, z_corr, z_pen, z_pen_logits]
  return r


class SketchRNN():
  """SketchRNN model definition."""

  def __init__(self, hps):
    self.hps = hps # hps is hyper parameters
    self.build_model(hps)

  def build_model(self, hps):
    # VAE model = encoder + Decoder
    # build encoder model
    encoder_inputs = Input(shape=(hps.max_seq_len, 5), name='encoder_input')
    # (batch_size, max_seq_len, 5)
    encoder_lstm = LSTM(hps.enc_rnn_size,
                use_bias=True,
                recurrent_initializer='orthogonal',
                bias_initializer='zeros',
                recurrent_dropout=1.0-hps.recurrent_dropout_prob,
                return_sequences=True,
                return_state=True)
    bidirectional = Bidirectional(encoder_lstm)
    (unused_outputs, # (batch_size, max_seq_len, enc_rnn_size * 2)
    last_h_fw, unused_c_fw, # (batch_size, enc_rnn_size) * 2
    last_h_bw, unused_c_bw) = bidirectional(encoder_inputs)
    last_h = concatenate([last_h_fw, last_h_bw], 1)
    # (batch_size, enc_rnn_size*2)

    normal_init = RandomNormal(stddev=0.001)
    self.z_mean = Dense(hps.z_size,
                  activation='linear',
                  use_bias=True,
                  kernel_initializer=normal_init,
                  bias_initializer='zeros')(last_h) # (batch_size, z_size)
    self.z_presig = Dense(hps.z_size,
                  activation='linear',
                  use_bias=True,
                  kernel_initializer=normal_init,
                  bias_initializer='zeros')(last_h) # (batch_size, z_size)

    def sampling(args):
      z_mean, z_presig = args
      self.sigma = K.exp(0.5 * z_presig)
      batch = K.shape(z_mean)[0]
      dim = K.int_shape(z_mean)[1]
      epsilon = K.random_normal((batch, dim), 0.0, 1.0)
      batch_z = z_mean + self.sigma * epsilon

      return batch_z # (batch_size, z_size)

    self.batch_z = Lambda(sampling,
                    output_shape=(hps.z_size,))([self.z_mean, self.z_presig])

    # instantiate encoder model
    self.encoder = Model(
                    encoder_inputs,
                    [self.z_mean, self.z_presig, self.batch_z], name='encoder')
    # self.encoder.summary()

    # build decoder model
    # Number of outputs is 3 (one logit per pen state) plus 6 per mixture
    # component: mean_x, stdev_x, mean_y, stdev_y, correlation_xy, and the
    # mixture weight/probability (Pi_k)
    self.n_out = (3 + hps.num_mixture * 6)

    decoder_inputs = Input(shape=(hps.max_seq_len, 5), name='decoder_input')
    # (batch_size, max_seq_len, 5)
    overlay_x = RepeatVector(hps.max_seq_len)(self.batch_z)
    # (batch_size, max_seq_len, z_size)
    actual_input_x = concatenate([decoder_inputs, overlay_x], 2)
    # (batch_size, max_seq_len, 5 + z_size)

    self.initial_state_layer = Dense(hps.dec_rnn_size * 2,
            activation='tanh',
            use_bias=True,
            kernel_initializer=normal_init)
    initial_state = self.initial_state_layer(self.batch_z)
    # (batch_size, dec_rnn_size * 2)
    initial_h, initial_c = tf.split(initial_state, 2, 1)
    # (batch_size, dec_rnn_size), (batch_size, dec_rnn_size)
    self.decoder_lstm = LSTM(hps.dec_rnn_size,
            use_bias=True,
            recurrent_initializer='orthogonal',
            bias_initializer='zeros',
            recurrent_dropout=1.0-hps.recurrent_dropout_prob,
            return_sequences=True,
            return_state=True
            )

    output, last_h, last_c = self.decoder_lstm(
                          actual_input_x, initial_state=[initial_h, initial_c])
    # [(batch_size, max_seq_len, dec_rnn_size), ((batch_size, dec_rnn_size)*2)]
    self.output_layer = Dense(self.n_out, activation='linear', use_bias=True)
    output = self.output_layer(output)
    # (batch_size, max_seq_len, n_out)

    last_state = [last_h, last_c]
    self.final_state = last_state

    # instantiate SketchRNN model
    self.sketch_rnn_model = Model(
                  [encoder_inputs, decoder_inputs],
                  output,
                  name='sketch_rnn')
    # self.sketch_rnn_model.summary()

  def vae_loss(self, inputs, outputs):
    # KL loss
    kl_loss = 1 + self.z_presig - K.square(self.z_mean) - K.exp(self.z_presig)
    self.kl_loss = -0.5 * K.mean(K.sum(kl_loss, axis=-1))
    self.kl_loss = K.maximum(self.kl_loss, K.constant(self.hps.kl_tolerance))

    # the below are inner functions, not methods of Model
    def tf_2d_normal(x1, x2, mu1, mu2, s1, s2, rho):
      """Returns result of eq # 24 of http://arxiv.org/abs/1308.0850."""
      norm1 = subtract([x1, mu1])
      norm2 = subtract([x2, mu2])
      s1s2 = multiply([s1, s2])
      # eq 25
      z = (K.square(tf.divide(norm1, s1)) + K.square(tf.divide(norm2, s2)) -
           2 * tf.divide(multiply([rho, multiply([norm1, norm2])]), s1s2))
      neg_rho = 1 - K.square(rho)
      result = K.exp(tf.divide(-z, 2 * neg_rho))
      denom = 2 * np.pi * multiply([s1s2, K.sqrt(neg_rho)])
      result = tf.divide(result, denom)
      return result

    def get_lossfunc(z_pi, z_mu1, z_mu2, z_sigma1, z_sigma2, z_corr,
                     z_pen_logits, x1_data, x2_data, pen_data):
      """Returns a loss fn based on eq #26 of http://arxiv.org/abs/1308.0850."""
      # This represents the L_R only (i.e. does not include the KL loss term).

      result0 = tf_2d_normal(x1_data, x2_data, z_mu1, z_mu2, z_sigma1, z_sigma2,
                             z_corr)
      epsilon = 1e-6
      # result1 is the loss wrt pen offset (L_s in equation 9 of
      # https://arxiv.org/pdf/1704.03477.pdf)
      result1 = multiply([result0, z_pi])
      result1 = K.sum(result1, 1, keepdims=True)
      result1 = -K.log(result1 + epsilon)  # avoid log(0)

      fs = 1.0 - pen_data[:, 2]  # use training data for this
      fs = tf.reshape(fs, [-1, 1])
      # Zero out loss terms beyond N_s, the last actual stroke
      result1 = multiply([result1, fs])

      # result2: loss wrt pen state, (L_p in equation 9)
      result2 = tf.nn.softmax_cross_entropy_with_logits_v2(
                        labels=pen_data, logits=z_pen_logits)
      result2 = tf.reshape(result2, [-1, 1])
      result2 = multiply([result2, fs])

      result = result1 + result2
      return result

    # reshape target data so that it is compatible with prediction shape
    target = tf.reshape(inputs, [-1, 5])
    [x1_data, x2_data, eos_data, eoc_data, cont_data] = tf.split(target, 5, 1)
    pen_data = concatenate([eos_data, eoc_data, cont_data], 1)

    out = get_mixture_coef(outputs, self.n_out)
    [o_pi, o_mu1, o_mu2, o_sigma1, o_sigma2, o_corr, o_pen, o_pen_logits] = out

    lossfunc = get_lossfunc(o_pi, o_mu1, o_mu2, o_sigma1, o_sigma2, o_corr,
                            o_pen_logits, x1_data, x2_data, pen_data)

    self.r_loss = tf.reduce_mean(lossfunc)

    kl_weight = self.hps.kl_weight_start
    self.loss = self.r_loss + self.kl_loss * kl_weight
    return self.loss

  def model_compile(self, model):
      adam = Adam(lr=self.hps.learning_rate, clipvalue=self.hps.grad_clip)
      model.compile(loss=self.vae_loss, optimizer=adam)

以下は学習後のモデルからスケッチを生成するサンプリングのコードです.

def sample(model, hps, weights, seq_len=250, temperature=1.0,
            greedy_mode=False, z=None):
  """Samples a sequence from a pre-trained model."""

  def adjust_temp(pi_pdf, temp):
    pi_pdf = np.log(pi_pdf) / temp
    pi_pdf -= pi_pdf.max()
    pi_pdf = np.exp(pi_pdf)
    pi_pdf /=pi_pdf.sum()
    return pi_pdf

  def get_pi_idx(x, pdf, temp=1.0, greedy=False):
    """Samples from a pdf, optionally greedily."""
    if greedy:
      return np.argmax(pdf)
    pdf = adjust_temp(np.copy(pdf), temp)
    accumulate = 0
    for i in range(0, pdf.size):
      accumulate += pdf[i]
      if accumulate >= x:
        return i
    tf.logging.info('Error with smpling ensemble.')
    return -1

  def sample_gaussian_2d(mu1, mu2, s1, s2, rho, temp=1.0, greedy=False):
    if greedy:
      return mu1, mu2
    mean = [mu1, mu2]
    s1 *= temp * temp
    s2 *= temp * temp
    cov = [[s1 * s1, rho * s1 * s2], [rho * s1 * s2, s2 * s2]]
    x = np.random.multivariate_normal(mean, cov, 1)
    return x[0][0], x[0][1]

  # load model
  model.sketch_rnn_model.load_weights(weights)

  prev_x = np.zeros((1, 1, 5), dtype=np.float32)
  prev_x[0, 0, 2] = 1 # initially, we want to see beginning of new stroke
  if z is None:
    z = np.random.randn(1, hps.z_size)

  batch_z = Input(shape=(hps.z_size,)) # (1, z_size)
  initial_state = model.initial_state_layer(batch_z)
  # (1, dec_rnn_size * 2)

  decoder_input = Input(shape=(1, 5)) # (1, 1, 5)
  overlay_x = RepeatVector(1)(batch_z) # (1,1, z_size)
  actual_input_x = concatenate([decoder_input, overlay_x], 2)
  # (1, 1, 5 + z_size)

  decoder_h_input = Input(shape=(hps.dec_rnn_size, ))
  decoder_c_input = Input(shape=(hps.dec_rnn_size, ))
  output, last_h, last_c = model.decoder_lstm(
                        actual_input_x,
                        initial_state=[decoder_h_input, decoder_c_input])
  # [(1, 1, dec_rnn_size), (1, dec_rnn_size), (1, dec_rnn_size)]
  output = model.output_layer(output)
  # (1, 1, n_out)

  decoder_initial_model = Model(batch_z, initial_state)
  decoder_model = Model([decoder_input, batch_z,
                        decoder_h_input, decoder_c_input],
                        [output, last_h, last_c])

  prev_state = decoder_initial_model.predict(z)
  prev_h, prev_c = np.split(prev_state, 2, 1)
  # (1, dec_rnn_size), (1, dec_rnn_size)

  strokes = np.zeros((seq_len, 5), dtype=np.float32)
  greedy = False
  temp =  1.0

  for i in range(seq_len):
    decoder_output, next_h, next_c = decoder_model.predict(
                                          [prev_x, z, prev_h, prev_c])
    out = sketch_rnn_model.get_mixture_coef(decoder_output, model.n_out)
    [o_pi, o_mu1, o_mu2, o_sigma1, o_sigma2, o_corr, o_pen, o_pen_logits] = out

    o_pi = K.eval(o_pi)
    o_mu1 = K.eval(o_mu1)
    o_mu2 = K.eval(o_mu2)
    o_sigma1 = K.eval(o_sigma1)
    o_sigma2 = K.eval(o_sigma2)
    o_corr = K.eval(o_corr)
    o_pen = K.eval(o_pen)

    if i < 0:
      greedy = False
      temp = 1.0
    else:
      greedy = greedy_mode
      temp = temperature

    idx = get_pi_idx(random.random(), o_pi[0], temp, greedy)

    idx_eos = get_pi_idx(random.random(), o_pen[0], temp, greedy)
    eos=[0, 0, 0]
    eos[idx_eos] = 1

    next_x1, next_x2 = sample_gaussian_2d(o_mu1[0][idx], o_mu2[0][idx],
                                          o_sigma1[0][idx], o_sigma2[0][idx],
                                          o_corr[0][idx], np.sqrt(temp), greedy)

    strokes[i, :] = [next_x1, next_x2, eos[0], eos[1], eos[2]]

    prev_x = np.zeros((1, 1, 5), dtype=np.float32)
    prev_x[0][0] = np.array(
        [next_x1, next_x2, eos[0], eos[1], eos[2]], dtype=np.float32)
    prev_h, prev_c = next_h, next_c

  # delete model to avoid a memory leak
  K.clear_session()

  return strokes

結果

今回は時間の都合上フクロウのデータセットでのみモデルの学習を行いました. それではまず入力に用いるスケッチをテストセットからランダムに選んできて,どのようなスケッチか見てみましょう.ちなみにこれは人間がフクロウを描いたものです.

f:id:k_kawabata:20181029021841p:plain

正直フクロウっぽくないですが一応生き物っぽいので良しとします.

次にこのスケッチからエンコーダーによって潜在ベクトルを得ます.

f:id:k_kawabata:20181029022225p:plain

そして最後にデコーダーによってスケッチを生成します.

f:id:k_kawabata:20181029022427p:plain

なかなかフクロウっぽいのではないでしょうか?

それでは次に様々なtemperatureパラメータを用いた時にスケッチがどのように変化していくか見ていきましょう.

f:id:k_kawabata:20181029022849p:plain

右にいくほどtemperatureの値は大きくなります.つまり,よりランダムになっていきます.temperatureが0.1の時はどちらかと言うとペンギンぽいですが,temperatureが0.3と0.5の時はかなりフクロウっぽいスケッチになっています.

かなり荒削りな部分もありますが,スケッチとして認識できるレベルまでしっかり学習ができていることがわかります.ただ,一つ気になったこととしては入力画像にかかわらず常にペンギンのようなスケッチを生成してしまっていたことです.ここの例でも示している通り,入力をかなり無視してペンギンのようなスケッチを生成しています.論文著者のコードではRNNセルにLayer Normalization付きのLSTMやHyperLSTMを用いることができるようになっており,またKL Lossのアニーリングも行なっていたのですが今回の実装ではそれらは含まれていなかったためこのような結果になったのではないかと考えています.入力画像に関係なく毎回同じようなスケッチを生成することに関しては,特にKL Lossのアニーリングを行なっていないことで,Reconstruction Lossに比べてKL Lossにばかり重点が置かれたことが原因だと考えています.

参照

最後に

尚,エクサウィザーズは20卒向けのAIエンジニアのポジションで,内定直結型インターンを東京・京都オフィスで募集しています.ご興味を持たれた方はぜひご応募ください.

ご応募はこちらから

Tensorflow.jsを用いたブラウザで動く物体認識

こんにちは。エクサウィザーズAIエンジニアの須藤です。 この度exaBaseの「物体名判別」モデルの紹介ページに、その場で試せるデモ機能を追加しました。

f:id:kentaro-suto:20181029164025p:plain

前回の「写真に写っていないところを復元する」とともに、実装にあたってはTensorflow.jsというフレームワークを使っています。 この記事では、Tensorflow.js導入までの簡単な解説と注意点、および新しいデモの操作方法を紹介したいと思います。

Tensorflow.jsとは

TensorflowもしくはKerasで書かれた機械学習モデルを、JavaScriptで扱えるようにするフレームワークです。 学習済みモデルによる推論が主な応用と考えられますが、モデルの構築や再学習も可能です。 WebGL経由でGPUを利用するので、計算は十分に高速です。

公式サイト

www.tensorflow.org

特徴

Webブラウザ上でAIモデルが動くようになります。 以下の特徴を持ったアプリケーションが作れます。

  • ライブラリ等のインストールが不要
    • (最新の)Webブラウザさえあれば動作します。
  • GUIが利用可能
    • Webアプリの技術がそのまま使えます。
    • ローカルファイルの選択などもOSやブラウザが面倒を見てくれます。
  • スマートフォンでも動作
  • サーバの負荷が少ない
    • 計算はクライアント側で行われるため
  • 細かいカスタマイズはできない

開発環境

pipでインストールできます。

pip install tensorflowjs

モデルの書き出し

モデルデータの書き出しには、コマンドtensorflowjs_converterまたは、Pythonフレームワークtensorflowjsが使えます。 後者で、既存の推論スクリプトに一時的に以下の行を書き足す方法が、手軽でおすすめです。

import tensorflowjs as tfjs
# モデルオブジェクトmodelを、ディレクトリ'tfjs'に、8ビットの量子化を用いて書き出す。
tfjs.converters.save_keras_model(model, 'tfjs', quantization_dtype=np.uint8)

実行すると、書き出し先ディレクトリに次のようなファイル群が生成されます。ファイルの名前と数はモデルのサイズや構成によって変わります。 総ファイルサイズは、重み(.hdf5)ファイルにほぼ比例します。量子化しなければそのまま、16ビットで半分、8ビットで4分の1になります。

model.json
group1-shard1of3
group1-shard2of3
group1-shard3of3

モデルの読み込み

HTML側でフレームワークをインポートします。

<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@0.13.0"></script>

モデルの読み込みを開始するには、JavaScriptで次のようにします。結果はPromiseオブジェクトを通して非同期にハンドラ関数に渡されます。

tf.loadModel('./model.json').then(handleModel).catch(handleError);
function handleModel(model) {
    // 正常に読み込まれた時の処理
    // 必要なら入出力shapeを保存
    height = model.inputs[0].shape[1];
    width = model.inputs[0].shape[2];
    // modelの操作...
}
function handleError(error) {
    // エラー処理
}

実行

Pythonで書かれた前処理や後処理を、頑張ってJavaScriptに翻訳します。実行そのものはモデルのpredictメソッドを呼ぶだけです。 predictを呼び出してから結果データを受け取るまでの流れは、形式上非同期ですが、Mac版Safariの場合、結果を待つ間もJavaScriptに制御が戻りませんでした。

var data = new Float32Array(height*width*3);
// dataの前処理...
var inputs = tf.tensor(data).reshape([1,height,width,3]); // テンソルに変換
var outputs = model.predict(inputs);
outputs.data().then(handleData).catch(handleError);
function handleData(data) { // Float32Arrayを受け取る    
    // dataの後処理...
}

モデルを読み込めない場合

原稿執筆時のバージョン(0.13.0)では、モデルに以下のものが含まれていると、書き出せても読み込めませんでした。 動作に影響しないものなら、model.jsonを書き換えることで解決する場合もあります。 どうしても必要な演算の場合、行列演算や畳み込みで代用するなどの工夫が必要です。

  • Subtract
    • AddやMultiplyは大丈夫なのに何故かこれはダメです。
  • Merge
  • Lambda
  • カスタマイズされたinitializer、regularizer、constraints
  • 名前の重複したレイヤー

物体名判別デモ

Kerasに同梱されている学習済みXceptionモデルを用いたデモです。 同モデルの重みはMITライセンスの下で公開されています。

こちらのページでお試しいただけます。 https://base.exawizards.com/view/modelDetail?id=6

操作方法

f:id:kentaro-suto:20181023183149p:plain 画面左下のリンクをクリックしてください。モデルのダウンロードが始まります。
f:id:kentaro-suto:20181023183216p:plain ダウンロードが終わると、プルダウンとファイル選択ボタンが表示されます。
f:id:kentaro-suto:20181024134942p:plain サンプル写真をプルダウンから選択するか、
f:id:kentaro-suto:20181023183415p:plain ローカルのファイルを選択してください。
f:id:kentaro-suto:20181023183443p:plain 計算が始まります。
f:id:kentaro-suto:20181030165119p:plain 最も確率が高い1〜3カテゴリに関するコメントと、上位10カテゴリの確率が表示されます。

結果

いくつか興味深い結果をご紹介します。なお、この記事で用いた写真およびデモページのサンプル写真は、私が個人的に撮影したものです。

画像 コメント
f:id:kentaro-suto:20181029143853p:plain バターナット・スクウォッシュを調べたら、確かに似ていました。ヒョウタンは学習データにありませんでした。
f:id:kentaro-suto:20181029160544p:plain どうやら本当はキジバトのようです。このように、知らないものに対しては、学習した中で似ているものを返します。
f:id:kentaro-suto:20181029144117p:plain 食べ物というところまでは合っています。
f:id:kentaro-suto:20181029144847p:plain 猫判別能力は概ね高いのですが、これは見破れなかったようです。
f:id:kentaro-suto:20181029144431p:plain メインの被写体について何も分からない場合、小さくても隅っこでも、知っているものに反応する場合があります。
f:id:kentaro-suto:20181030165004p:plain 右下の黒くて四角い何かに強く反応しました。比べると左上の生き物については、いまいち確証が無かったようです。
f:id:kentaro-suto:20181029144914p:plain 見えない何かに反応することも。生き物に関しては、それがいそうな背景でも判断しているようです。
f:id:kentaro-suto:20181029144722p:plain かたくなに犬と言い張ります。どうも縞模様で猫を判別しているようです。
f:id:kentaro-suto:20181030165034p:plain やっぱり。しかしこれだけ抽象化されたデザインに反応するのは珍しいです。
f:id:kentaro-suto:20181030165342p:plain 例えばシマウマは知っています。
f:id:kentaro-suto:20181029161711p:plain しかしこれはシマウマとは判定されません。
f:id:kentaro-suto:20181029144814p:plain 納得しかけましたが、よく見たら貯金箱じゃありませんでした。

まとめ

Tensorflow.jsの使い方と、それを用いたデモを紹介しました。 前処理があまり必要ない場合は、想像より簡単にモデルを動かせるようになります。 exaBaseの他の既存モデルのデモもおいおい追加して行きたいと思っています。

尚、エクサウィザーズは優秀なエンジニア、社会課題を一緒に解決してくれる魔法使い”ウィザーズ”を募集しています。ご興味を持たれた方はぜひご応募ください。 採用情報|株式会社エクサウィザーズ

ExaWizards Engineer Blogでは、定期的にAIなどの技術情報を発信していきます。Twitter (https://twitter.com/BlogExawizards) で更新情報を配信していきますので、ぜひフォローをよろしくお願いします!

写真に写っていないところを復元する

f:id:kentaro-suto:20181024193158p:plain

こんにちは。エクサウィザーズAIエンジニアの須藤です。

みなさんはハイキングの写真でしずちゃんばかり写して、まともに撮られなかったジャイアンに殴られかけたことは無いでしょうか。 そんなとき「万能プリンター」があったら便利ですね。もう撮ってしまった写真の、向きやズームを後から修正して、写ってなかったところを復元して再プリントできるというものです。 しかし持ち主であるドラえもんは、うちにもまだ来ていません。仕方がないのでAIの力でなんとかしましょう。

目的

写真の外側に写っているものを推測し、自然な形で合成します。

f:id:kentaro-suto:20181024190925p:plain:w200

物体の部分画像からその種類ないし位置を推測し、既存画像を本に全体を復元することが、原理的には可能なはずです。 その過程を直接にプログラムすることは現実的ではありません。 代わりに畳み込みニューラルネットワーク(CNN)に、かいつまんで学習させます。

学習モデル

敵対的生成ネットワーク(GAN)のアルゴリズムに従います。目的に書いたとおりのことを行う生成器と、画像の本物らしさを判別する判別器を、交互に学習します。 f:id:kentaro-suto:20181024153648j:plain

  • 生成器を以下のように構成します。
    • 画像を特徴量マップにする多層CNN
    • 特徴量マップを縦横二倍に広げる多層転置CNN
    • 特徴量マップから画像を生成する多層転置CNN
  • 事前学習を行います。
    • 真ん中だけを切り取った画像から元の画像を生成するように、生成器を学習
  • 画像が本物かを判定する判別器を構成します。
  • 以下の学習を交互に繰り返します。
    • 生成器を通した画像に対して0、本物の画像に対して1を返すように、判別器を学習
    • 生成した画像に対して判別器が1を返すように、生成器を学習

データセット

はじめ様々な物体を含む画像データセットで学習を行いましたが、外周部のピクセルの色を拡散させる以上のことをしてくれませんでした。 どんな写真にも対応させるには、学習時間またはモデルの性能が不足しているようです。

そこで顔画像データセットであるLabelled Faces in the Wildを用いて学習することにしました。 処理すべき情報量が減るため、少なくとも顔の部分に関しては精度の高い結果が期待できます。 一方で、使い道が想像つかなくなりましたが、モデルの検証が目的ということでご理解ください。

結果

入出力の例です。本来の全体画像も正解として記載します。 入力画像はエクサウィザーズのメンバー紹介ページから借りました。

入力 出力 正解 コメント
f:id:kentaro-suto:20181024173438p:plain f:id:kentaro-suto:20181023105104p:plain f:id:kentaro-suto:20181023105052p:plain ネクタイができかけています。
f:id:kentaro-suto:20181024173458p:plain f:id:kentaro-suto:20181023105619p:plain f:id:kentaro-suto:20181023105603p:plain なぜか法衣みたいに。
f:id:kentaro-suto:20181024173509p:plain f:id:kentaro-suto:20181023104451p:plain f:id:kentaro-suto:20181023104439p:plain 背景のボケがいい感じに。予測モデル的には失敗です。
f:id:kentaro-suto:20181024173538p:plain f:id:kentaro-suto:20181023104803p:plain f:id:kentaro-suto:20181023104751p:plain これまた作務衣のよう。
f:id:kentaro-suto:20181024173600p:plain f:id:kentaro-suto:20181023105944p:plain f:id:kentaro-suto:20181023105934p:plain ワイルドに。
f:id:kentaro-suto:20181024173618p:plain f:id:kentaro-suto:20181023110015p:plain f:id:kentaro-suto:20181023110003p:plain ムーディーに。
f:id:kentaro-suto:20181024173631p:plain f:id:kentaro-suto:20181023105919p:plain f:id:kentaro-suto:20181023105907p:plain 背景色がきっちり伸ばされているところに注目。
f:id:kentaro-suto:20181024173712p:plain f:id:kentaro-suto:20181023104826p:plain f:id:kentaro-suto:20181023104814p:plain 髪型提案モデルとして使えるかも。
f:id:kentaro-suto:20181024173730p:plain f:id:kentaro-suto:20181023104742p:plain f:id:kentaro-suto:20181023104734p:plain はみ出るのが少ないと変な結果になりにくいです。
f:id:kentaro-suto:20181024173903p:plain f:id:kentaro-suto:20181023104723p:plain f:id:kentaro-suto:20181023104712p:plain 髪の毛が大増量です。
f:id:kentaro-suto:20181024173924p:plain f:id:kentaro-suto:20181023105652p:plain f:id:kentaro-suto:20181023105633p:plain 無地の背景はなんとかして避けようとします。
f:id:kentaro-suto:20181024173949p:plain f:id:kentaro-suto:20181023105721p:plain f:id:kentaro-suto:20181023105706p:plain 首を隠すとなぜか太くなりがちです。
f:id:kentaro-suto:20181024174015p:plain f:id:kentaro-suto:20181023105551p:plain f:id:kentaro-suto:20181023105521p:plain 襟があるかないか決めかねたようです。
f:id:kentaro-suto:20181024174026p:plain f:id:kentaro-suto:20181023111924p:plain f:id:kentaro-suto:20181023111914p:plain 妙にごつくなりました。
f:id:kentaro-suto:20181024174041p:plain f:id:kentaro-suto:20181023105757p:plain f:id:kentaro-suto:20181023105735p:plain やたらきらびやかになりました。
f:id:kentaro-suto:20181024174114p:plain f:id:kentaro-suto:20181023105438p:plain f:id:kentaro-suto:20181023105428p:plain ビシッと黒スーツです。

髪の毛や顎など、画像中に無いものが付け足されています。背景や髪の毛の色もある程度反映されます。手がかりが全くない場合、髪型は丸刈り、服装は黒スーツになるようです。

デモ

ブラウザ上で動作するデモを用意しました。こちらのページですぐに試すことができます。 https://base.exawizards.com/view/modelDetail?id=45

操作説明

f:id:kentaro-suto:20181023102234p:plain ページ下部のリンクをタップしてください。モデルのダウンロードが始まります。12MBほどあるので、通信環境によってはお時間をいただきます。
f:id:kentaro-suto:20181023104008p:plain お手持ちの顔写真を選択してください。計算は全てブラウザ上で行われます。写真データが外部に送信されることはありません。
f:id:kentaro-suto:20181023102321p:plain 写真が読み込まれました。写真を直接ドラッグすることで位置を、スライダーではスケールを調節できます。
f:id:kentaro-suto:20181023102406p:plain 顔が白い枠いっぱいに表示される状態になったら、決定ボタンを押してください。計算が始まります。
f:id:kentaro-suto:20181023102421p:plain 枠内の内容だけから再生成された画像が表示されます。別のファイルを選択するか、位置を変える操作を行うと、再計算ができます。

まとめ

学習に基づき写真の範囲を拡張するAIモデルを作りました。

本手法はこんなときに役に立つ、かもしれません。

  • 不完全な写真からポートレートを作成
  • 構図の良くない写真を修正
  • 写真一枚から環境マップを生成
  • 4:3の映像を16:9のテレビで見る際の余白埋め

尚、エクサウィザーズは優秀なエンジニア、社会課題を一緒に解決してくれる魔法使い”ウィザーズ”を募集しています。ご興味を持たれた方はぜひご応募ください。 採用情報|株式会社エクサウィザーズ

ExaWizards Engineer Blogでは、定期的にAIなどの技術情報を発信していきます。Twitter (https://twitter.com/BlogExawizards) で更新情報を配信していきますので、ぜひフォローをよろしくお願いします!

マルチモーダルAIロボットをGTC Japan 2018に展示しました(中編)

こんにちは、エクサウィザーズAIエンジニアの柳元です。

今回はGTC Japan 2018の参加報告として、いくつかの展示や講演の簡単な紹介をしていきます。

スポンサー展示

株式会社コンピュータマインド f:id:mikuyanagimoto:20180920111246j:plain 物体検出・分類の事前学習モデルを提供するライブラリの展示です。 一般的なライブラリと異なり、学習ではなく推論に特化することで、高速に推論結果を得られます。 主にC/C++で実装されており、依存するライブラリ数が少なく、導入も容易です。 追加学習機能も今後実装予定とのことです。

コマツ f:id:mikuyanagimoto:20180920111340j:plain 大迫力のショベルカーの展示です。 単眼カメラから人を検出し、その距離を検出します。 また、地形を変えるのは本来高い技術を必要とされる作業ですが、 GNSSアンテナから得られる位置情報に基づき、バケットが設計面に沿って自動で動きます。 そのため、アームレバー操作のみで作業ができます。 f:id:mikuyanagimoto:20180920111351j:plain 3つのディスプレイのうち、上2つが人検知、下1つが地形情報を表しています。

ヤマハ様 f:id:mikuyanagimoto:20180920111706j:plain 自動運転ワンマイルモビリティの展示です。 ハンドル・ブレーキ・アクセルがなく、レベル4の自動運転(特定の場所でシステムが全てを操作)を目指しています。 例えば、ゴルフ場やホテルに導入することで、ゲストは施設内を自由に移動することができます。

ピュア・ストレージ様 f:id:mikuyanagimoto:20180920111724j:plain AIインフラストラクチャであるAIRIの展示です。 Deep learningなどの機械学習に用いるAIサーバは高価であるため、サーバを増やすだけ性能が上がることが望ましいと考えられ、
そのためには継続的パラメータチューニングが必要とされます。 AIRIにはコストパフォーマンスを最大化するための最適化が施されています。

パナソニック様 f:id:mikuyanagimoto:20180920111915j:plain 監視カメラから特定の人物を検出する顔認証技術を搭載したセキュリティシステムFacePROの展示です。 事前にある人物の顔情報を登録しておけば、サングラスやマスクをつけた状態であったとしてもその人物を検出できます。

講演

尾形哲也先生 「ディープニューラルネットの力学的構造設計による複数動作の統合」というタイトルで
ロボットの学習方法に関する発表をして頂きました。 モーションプリミティブとは、人間のほとんどの動作を構成する要素のことで、組み合わせたり、並べたりすることで、複雑な動作を生成することができると言われています。 モーションプリミティブは、軌道を構成するそれぞれの値によって表現されるのではなく、
軌道パターンによって表現される、と述べられました。 LeCunのケーキは学習の難しさ(あるいは知能における重要さ)を表しており、そのことがモーションプリミティブの獲得に強化学習が常に最適であるとは言えず、予測学習も重要である、と述べられました。
 f:id:mikuyanagimoto:20180920112236j:plain 尾形先生はエクサウィザーズロボット事業の技術顧問でもあります。 株式会社日立製作所様のブースと並んで
エクサウィザーズブースの秤量デモや昨年の双腕型マルチモーダルAIロボットをご紹介頂きました。

長井隆行先生 「人と共存するこれからのAI x ロボティクス」というタイトルで、
ロボットを通してヒトを理解する取り組みに関する発表をして頂きました。 理解とは予測であり、感情とは自分自身に対する予測であるとし、ロボット工学における感情の研究が必要であることを述べられました。 シミュレーション上で赤ちゃんを模したエージェントに、母親の表情(感情)を模倣させる学習させる研究において、エージェントにとって完全に予測不能な恐怖信号が時折与えられる環境下では、全ての信号が予測可能な環境に比べて複雑な感情表出をするようになるという結果が得られ、その内部表現を低次元に圧縮・プロットすると喜怒哀楽がまるで感情の円環モデルのようになった、というのがとても面白いと思いました。

浅谷学嗣(先生) 「マルチモーダルAIによる協働ロボットの行動獲得・制御の可能性」というタイトルで発表しました。 発表の詳しい内容は、本ブログの後編として後日UP予定ですので、是非ご覧ください!

Dieter Fox先生 “Robotics research at NVIDIA”というタイトルで自身の研究内容について発表頂きました。 研究テーマの1つに、物体の6D姿勢推定(3D translation + 3D rotation)があります。 ロボットが人とインタラクティブに学習したり、協働するためには、実世界という環境を認識できる必要があります。 しかし、物体の6D姿勢推定は簡単な問題ではなく、物体それぞれが異なる形状を有しており、加えて外観も照明条件や周囲環境の乱雑さ、物体間のオクルージョンの影響を大きく受けるからです。 従来的には、特徴点のマッチングやテンプレートベースな手法が使われていますが、それぞれテクスチャとオクルージョンの問題を抱えています。 ここで紹介されたPoseCNNは、6D姿勢推定のための汎用フレームワークとなるend-to-endな畳み込みニューラルネットです。 f:id:mikuyanagimoto:20180921122825p:plain 上図のように、姿勢推定タスクに必要な要素を切り分けることで、ネットワークがそれらの関係性を明示的にモデル化します。 また、DeepIMは推定結果をさらによいものにします。 f:id:mikuyanagimoto:20180921124050p:plain 姿勢推定結果が与えられると、画像中の物体がレンダリングされた視野と一致するSE(3)変換を予測します。 これを反復することにより、正確な姿勢を推定することができます。 液体:https://arxiv.org/pdf/1703.01564.pdf

おわりに

エクサウィザーズのブースにはたくさんの人に見に来て頂き、盛況のうちに終了しました。 f:id:mikuyanagimoto:20180920112737j:plain また、講演にもたくさんの方にお集まり頂きました。 足を運んでくださった皆様、ありがとうございました!

なお、エクサウィザーズは優秀なエンジニア、社会課題を一緒に解決してくれる魔法使い“ウィザーズ”を募集していますので、
ご興味を持たれた方はぜひご応募ください。

採用情報|株式会社エクサウィザーズ

ExaWizards Engineer Blogでは、定期的にAIなどの技術情報を発信していきます。
Twitterで更新情報を配信していきますので、ぜひフォローをよろしくお願いします!

おまけ

f:id:mikuyanagimoto:20181001125913g:plain

  1. 何をしているのでしょうか?
  2. データの収集をしています。カメラのブレ、認識対象物以外の物体(人の手など)の映り込みや移動に対して ロバストな表現を学習できるような訓練データを効率的に集められるのが上の浅谷式です。 動画はこちら

京都大学にてKDDの論文読み会を開催します

京都大学にて、京都大学(人工知能研究ユニット)・理化学研究所革新知能統合研究センター(AIP)・株式会社エクサウィザーズ共催のKDD論文読み会を開催します。

KDDはデータマイニング領域のトップカンファレンスです。

参加はCompassからお願いします。読み手も募集しておりますので、興味のある方はぜひご応募ください!

https://connpass.com/event/102181/

  • 日時:10月11日(木)13:00-18:00(開場12:30)

  • 会場:京都大学 総合研究15号館(参加人数により建物は変更になる可能性があります) http://www.ai.kyoto-u.ac.jp/access-ja/

論文はこちらからダウンロードが可能です。 http://www.kdd.org/kdd2018/accepted-papers

※ 本読み会は、エンジニア、研究者、学生限定のイベントです。

エクサウィザーズは優秀なエンジニア、社会課題を一緒に解決してくれる魔法使い"ウィザーズ"を募集しています。 ご興味を持たれた方はぜひご応募ください。採用情報 | 株式会社エクサウィザーズ 

ExaWizards Engineer Blogでは、定期的にAIなどの技術情報を発信していきます。Twitter (https://twitter.com/BlogExawizards) で更新情報を配信していきますので、ぜひフォローをよろしくお願いします!

マルチモーダルAIロボットをGTC Japan 2018で展示します(前編)

こんにちは、エクサウィザーズAIエンジニアの柳元です。

エクサウィザーズはGTC Japan 2018に参加します!

GTC Japanには昨年もマルチモーダルAIロボットを出展しました(後述)。 今年はエンタープライズや研究における用途を意識した「秤量」と「パレタイジング」のデモを行います。

秤量デモ

youtu.be

※ ナレーションは、GTC Japan 2018に登壇予定の浅谷です。音声にご注意ください。

調剤現場では秤量(はかりで重さを量る)作業が大量にあり、労働力がかかる上、危険な薬品を扱う際のリスクがあるため、ロボットによる秤量の自動化が望まれています。自動秤量は、調剤だけでなく、料理にも活用可能と考えられます。

今回の秤量デモでは、都度与えられる量と等しくなるように、ロボットが塩をスプーンですくい、所定の位置に盛ります。

f:id:taichiroendo:20180910181247j:plain

f:id:mikuyanagimoto:20180907164334p:plain

両サイドのUSBカメラの情報から塩の盛られた場所を認識し、さまざまな量の塩をスプーンですくえるようニューラルネットワークを学習させました。

f:id:mikuyanagimoto:20180908102253p:plain

デモでは、サーバー上の学習済みニューラルネットワークがAPIを通じてデータを受信し、行動(ジョイント角)をリアルタイムで生成します。

f:id:mikuyanagimoto:20180912173805p:plain

パレタイジングデモ

youtu.be

このタスクは、パレットから正しい順序で部品をとり、重ねることで組み付けを行うものです。 一見単純なタスクですが、パレットから部品が抜け落ちるなどの例外処理をロボットプログラムのみで記述するのは手間がかかります。 さらに、人とロボットが協働できるよう条件分岐を書くのは至難の技です。

今回組み付けるのはレゴカーです。手順は、①まず青のパレットからシャーシをとり、黒のワークスペースに置きます。 ②次に、銀のパレットからルーフをとり、黒のワークスペース上のシャーシに組み付けます。

f:id:mikuyanagimoto:20180907173507p:plain

カメラから得られた各パレット情報などに基づいてロボットが状況判断します。 タスクに人が介入したり、誤ってパーツを落としたりした場合などを認識し、適切に行動します。

おわりに

動画内容は実験段階のものであり、当日のデモは大きく変わっております。 また、見に来て頂いた皆さまに楽しんでもらえるよう、インタラクティブなデモを考えております!

GTC Japan 2018は9/13(木), 14(金)の日程でグランドプリンスホテル新高輪 国際館パミールにて開催されます。 エクサウィザーズのブースは1階ロボット展示エリアにあります。 弊社の浅谷は9/14(金) 11:25 - 11:50に1階Room 1 - 3にて講演します。

ぜひ遊びに来てください!

なお、エクサウィザーズは優秀なエンジニア、社会課題を一緒に解決してくれる魔法使い”ウィザーズ”を募集していますので、ご興味を持たれた方はぜひご応募ください。

採用情報|株式会社エクサウィザーズ

ExaWizards Engineer Blogでは、定期的にAIなどの技術情報を発信していきます。Twitter で更新情報を配信していきますので、ぜひフォローをよろしくお願いします!

おまけ: 昨年の出展内容

www.youtube.com GTC Japan 2017では双腕マルチモーダルAIロボットを展示しました。 従来の製造業用ロボットの常識であった「専用」ロボットではなく、「不特定のタスク」を、「汎用のプログラム」と「汎用のアーム・ハンド」によって実行するマルチタスクロボットです。

与えるデータの変更のみで、同一のマシン・同一のプログラムで複数のタスクが実行できます。

f:id:mikuyanagimoto:20180908190213p:plain

論文が米科学誌「Cell」に掲載されました

こんにちは、エクサウィザーズの遠藤太一郎です。

私が参加した研究プロジェクトの成果が米科学誌「Cell」に掲載されましたので、ご報告です。 画像解析の深層学習のところで貢献しました。

論文のポイントは以下になります。

世界初のIntelligent Image-Activated Cell Sorterを開発 〜細胞画像の深層学習により高速細胞選抜を実現〜

  • 本技術「Intelligent Image-Activated Cell Sorter」は細胞の高速イメージングと深層学習を用いた画像解析で細胞を一つ一つ網羅的に高速識別し、その解析結果に応じて所望の細胞を分取する世界初の基盤技術です。

  • 免疫学、病理学、微生物学、分子生物学、遺伝学、再生医学、移植など多岐に渡る分野で基盤技術として不可欠である高速細胞分取技術「Fluorescence-Activated Cell Sorter」(開発者のHerzenberg氏は本貢献により2006年に京都賞を受賞)に顕微イメージング活性化(Image-Activated)と深層学習(Intelligent)を融合する飛躍的な発展であります。

  • 本技術の原理実証として微生物や血液を用いて細胞の内部分子構造や形態などの様々な空間的情報に基づいた高速細胞分取を実現したことから、今後は生命科学(分子生物学、微生物学、医学、薬学など)における科学的発見およびバイオ産業や医療の発展への寄与が期待されます。

論文は以下のURLから確認可能です。https://doi.org/10.1016/j.cell.2018.08.028

エクサウィザーズでは各種大学や研究機関と、共同研究を始めとし様々な連携を行っています。技術顧問の先生方とのディスカッションを始め、京都大学・理研AIPとの機械学習勉強会など、共催のイベントなども積極的に開催しています。

優秀なエンジニア、社会課題を一緒に解決してくれる魔法使い”ウィザーズ”を募集していますので、ご興味を持たれた方はぜひご応募ください。 採用情報|株式会社エクサウィザーズ

ExaWizards Engineer Blogでは、定期的にAIなどの技術情報を発信していきます。Twitter (https://twitter.com/BlogExawizards) で更新情報を配信していきますので、ぜひフォローをよろしくお願いします!