これは, 人工知能技術戦略会議等主催 第1回AIチャレンジコンテスト[1]に引き続き, ユニクロを展開しているファーストリテイリング主催で今年4月~7月に開催された「第1回 FR FRONTIER :ファッション画像における洋服の「色」分類」[2][3]にチャレンジしたときの取組みについて, 数回に分けて紹介するものである.
関連記事:
・第1回FR FRONTIER:ファッション画像における洋服の「色」分類にチャレンジ!!(1)
・第1回FR FRONTIER:ファッション画像における洋服の「色」分類にチャレンジ!!(2)
・第1回FR FRONTIER:ファッション画像における洋服の「色」分類にチャレンジ!!(3)
・第1回FR FRONTIER:ファッション画像における洋服の「色」分類にチャレンジ!!(4)
・第1回FR FRONTIER:ファッション画像における洋服の「色」分類にチャレンジ!!(5)
前回に続き, 今回もモデリングについて試したことの一つを紹介する.
4. モデリング(2)
今回は, RGB画像と「2. データ利用方法」[4]で検討したストグラム画像の両方を使って「色」識別する方法を試してみた.
4.1 モデル
領域の「色」識別を行うモデルの構造は, 以下のような2種類の入力を扱うCNNモデルとした.
コード:
# CNNモデル left = Sequential() left.add(Conv2D(64, (3, 3), padding='same', input_shape=(IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS), activation='relu')) left.add(MaxPooling2D(pool_size=(2, 2))) left.add(Conv2D(128, (3, 3), padding='same', activation='relu')) left.add(MaxPooling2D(pool_size=(2, 2))) left.add(Conv2D(256, (3, 3), padding='same', activation='relu')) left.add(MaxPooling2D(pool_size=(2, 2))) left.add(Dropout(0.5)) left.add(Flatten()) right = Sequential() right.add(Conv2D(64, (3, 3), padding='same', input_shape=(IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS), activation='relu')) right.add(MaxPooling2D(pool_size=(2, 2))) right.add(Conv2D(128, (3, 3), padding='same', activation='relu')) right.add(MaxPooling2D(pool_size=(2, 2))) right.add(Conv2D(256, (3, 3), padding='same', activation='relu')) right.add(MaxPooling2D(pool_size=(2, 2))) right.add(Dropout(0.5)) right.add(Flatten()) model = Sequential() model.add(Merge([left, right], mode='concat')) model.add(Dense(512, activation='relu')) model.add(Dense(512, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(24, activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer=Adam(lr=5e-4, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.), metrics=['accuracy'])
4.2 学習
学習する際に2入力の画像を受け付けるように, keara/preprocessing/image.py内のImageDataGeneratorクラス及びDirectoryIteratorクラスの一部を修正して使用した.
ただし, 簡便に実装するため, 以下を前提条件とした.
・2入力の画像のファイル名は同一とする.
・2入力の学習条件等は同一とする.
修正内容:
# <ImageDataGenerator> # # 2つのディレクトリを受け付ける # def flow_from_directory(self, directory1, directory2, target_size=(256, 256), color_mode='rgb', classes=None, class_mode='categorical', batch_size=32, shuffle=True, seed=None, save_to_dir=None, save_prefix='', save_format='png', follow_links=False): return DirectoryIterator( directory1, directory2, self, target_size=target_size, color_mode=color_mode, classes=classes, class_mode=class_mode, data_format=self.data_format, batch_size=batch_size, shuffle=shuffle, seed=seed, save_to_dir=save_to_dir, save_prefix=save_prefix, save_format=save_format, follow_links=follow_links) # <DirectoryIterator> # # 2つのディレクトリからそれぞれるファイル名をリスト化する. # def __init__(self, directory1, directory2, image_data_generator, target_size=(256, 256), color_mode='rgb', classes=None, class_mode='categorical', batch_size=32, shuffle=True, seed=None, data_format=None, save_to_dir=None, save_prefix='', save_format='png', follow_links=False): if data_format is None: data_format = K.image_data_format() self.directory1 = directory1 self.directory2 = directory2 self.image_data_generator = image_data_generator self.target_size = tuple(target_size) if color_mode not in {'rgb', 'grayscale'}: raise ValueError('Invalid color mode:', color_mode, '; expected "rgb" or "grayscale".') self.color_mode = color_mode self.data_format = data_format if self.color_mode == 'rgb': if self.data_format == 'channels_last': self.image_shape = self.target_size + (3,) else: self.image_shape = (3,) + self.target_size else: if self.data_format == 'channels_last': self.image_shape = self.target_size + (1,) else: self.image_shape = (1,) + self.target_size self.classes = classes if class_mode not in {'categorical', 'binary', 'sparse', 'input', None}: raise ValueError('Invalid class_mode:', class_mode, '; expected one of "categorical", ' '"binary", "sparse", "input"' ' or None.') self.class_mode = class_mode self.save_to_dir = save_to_dir self.save_prefix = save_prefix self.save_format = save_format white_list_formats = {'png', 'jpg', 'jpeg', 'bmp'} # first, count the number of samples and classes self.samples = 0 if not classes: classes = [] # set classes from directory1 for subdir in sorted(os.listdir(directory1)): if os.path.isdir(os.path.join(directory1, subdir)): classes.append(subdir) self.num_class = len(classes) self.class_indices = dict(zip(classes, range(len(classes)))) def _recursive_list(subpath): return sorted(os.walk(subpath, followlinks=follow_links), key=lambda tpl: tpl[0]) pool = multiprocessing.pool.ThreadPool() function_partial = partial(_count_valid_files_in_directory, white_list_formats=white_list_formats, follow_links=follow_links) # set samples from directory1 self.samples = sum(pool.map(function_partial, (os.path.join(directory1, subdir) for subdir in classes))) print('Found %d images belonging to %d classes.' % (self.samples, self.num_class)) # second, build an index of the images in the different class subfolders results1 = [] results2 = [] self.filenames1 = [] self.filenames2 = [] self.classes = np.zeros((self.samples,), dtype='int32') i = 0 for dirpath in (os.path.join(directory1, subdir) for subdir in classes): results1.append(pool.apply_async(_list_valid_filenames_in_directory, (dirpath, white_list_formats, self.class_indices, follow_links))) for res in results1: clses, filenames = res.get() self.classes[i:i + len(clses)] = clses self.filenames1 += filenames i += len(clses) for dirpath in (os.path.join(directory2, subdir) for subdir in classes): results2.append(pool.apply_async(_list_valid_filenames_in_directory, (dirpath, white_list_formats, self.class_indices, follow_links))) for res in results2: clses, filenames = res.get() # self.classes[i:i + len(clses)] = clses self.filenames2 += filenames # i += len(clses) pool.close() pool.join() super(DirectoryIterator, self).__init__(self.samples, batch_size, shuffle, seed) # # 2つのファイル名リストの中からミニバッチ用のファイルを抽出する. # def next(self): """For python 2.x. # Returns The next batch. """ with self.lock: index_array, current_index, current_batch_size = next(self.index_generator) # The transformation of images is not under thread lock # so it can be done in parallel batch_x1 = np.zeros((current_batch_size,) + self.image_shape, dtype=K.floatx()) batch_x2 = np.zeros((current_batch_size,) + self.image_shape, dtype=K.floatx()) grayscale = self.color_mode == 'grayscale' # build batch of image data for i, j in enumerate(index_array): fname = self.filenames1[j] img = load_img(os.path.join(self.directory1, fname), grayscale=grayscale, target_size=self.target_size) x = img_to_array(img, data_format=self.data_format) x = self.image_data_generator.random_transform(x) x = self.image_data_generator.standardize(x) batch_x1[i] = x fname = self.filenames2[j] img = load_img(os.path.join(self.directory2, fname), grayscale=grayscale, target_size=self.target_size) x = img_to_array(img, data_format=self.data_format) x = self.image_data_generator.random_transform(x) x = self.image_data_generator.standardize(x) batch_x2[i] = x # optionally save augmented images to disk for debugging purposes if self.save_to_dir: for i in range(current_batch_size): img = array_to_img(batch_x1[i], self.data_format, scale=True) fname = '{prefix}_{index}_{hash}.{format}'.format(prefix=self.save_prefix, index=current_index + i, hash=np.random.randint(1e4), format=self.save_format) img.save(os.path.join(self.save_to_dir, fname)) # build batch of labels if self.class_mode == 'input': batch_y = batch_x.copy() elif self.class_mode == 'sparse': batch_y = self.classes[index_array] elif self.class_mode == 'binary': batch_y = self.classes[index_array].astype(K.floatx()) elif self.class_mode == 'categorical': batch_y = np.zeros((len(batch_x1), self.num_class), dtype=K.floatx()) for i, label in enumerate(self.classes[index_array]): batch_y[i, label] = 1. else: return [batch_x1, batch_x2] return [batch_x1, batch_x2], batch_y
また, 学習時の呼び出し方は以下の通り.
# 学習データ準備 train_data_generator = im.ImageDataGenerator( rescale = 1.0 / 255) train_data = train_data_generator.flow_from_directory( TRAIN_DIR1, TRAIN_DIR2, target_size = (IMG_HEIGHT, IMG_WIDTH), color_mode = 'rgb', classes = classes, class_mode = 'categorical', batch_size = BATCH_SIZE, seed = seed, shuffle = True)
4.3 評価
テストデータで, 評価を行った.
テストデータ:9801画像
モデル | 入力画像 | 学習(epoch) | 精度 |
---|---|---|---|
2入力CNN | RGB画像+hist画像 | 5 | 0.666 |
(参考)VGG19 | RGB画像 | 5 | 0.655 |
(参考)VGG19 | hist画像 | 5 | 0.670 |
RGB画像とヒストグラム画像の2つの画像を入力とすることにより, 少ない層数のモデルで学習済モデル+Fine Tuningとほぼ同じ識別精度を得ることができた.
これをベースにもう少し工夫したいところだが, コンテスト締め切り間近で時間がな~い.
残り数日は, これまでの結果を組み合わせて少しでもコンテストの上位を目指すことに...
(コンテストへの分類結果提出は1日に3回までなので)
今回は, RGB画像とヒストグラム画像の2つの画像を用いた識別方法について紹介した.
次回は, コンテストの結果や表彰式の状況(もし参加できれば)などについて紹介する予定.
----
参照URL:
[1] 人工知能技術戦略会議等主催 第1回AIチャレンジコンテスト
[2] 第1回 FR FRONTIER :ファッション画像における洋服の「色」分類
[3] 最先端のビジネス課題にチャレンジ!
[4] 第1回FR FRONTIER:ファッション画像における洋服の「色」分類にチャレンジ!!(2)
プログラマのためのGoogle Cloud Platform入門 サービスの全体像からクラウドネイティブアプリケーション構築まで
| さわってわかる機械学習 Azure Macine Learning 実践ガイド
|
| 仕事で使える!Google Cloud Platform 最新クラウドインフラ導入マニュアル (仕事で使える!シリーズ(NextPublishing))
|