こんにちは、CCCMKホールディングス TECH LABの三浦です。
すっかり秋めいてきました。この時期は着る服に困ることが多く、着すぎると暑いし、着ないと寒いです。考えるのがだんだん面倒になってしまい、気が付くといつも同じ服を着てしまいます。
さて、人が周囲から情報を得るための基本的な形式として言語(テキスト)と画像が挙げられます。この2つの形式のデータを同時に扱うことが出来ると、たとえば質問文に対して画像で回答したり、画像に対して説明文を生成するタスクに活用が出来ます。
最近、OpenAIがGPT-4 with vision (GPT-4V)というマルチモーダルAIを発表しました。どんなことが出来るのかを紹介した動画を見たのですが、自転車の画像を入力して自転車の修理の方法を教えてもらう、といった内容で、それを見て"ここまで出来るのか"、とビックリしました。
以前このブログでも入力画像に対してその画像を説明するテキストを生成するImage Captioningについて、試したことをご紹介しました。
壁掛け時計の画像を入力すると、ちょっと変な文章ですが時計がある旨のテキストが生成できることを確かめられました。
画像から知りたい情報は人によって、そして状況によって様々だと思います。たとえば時計があるかどうかを知りたい時もあれば、時計が今何時を示しているのかを知りたいこともあります。以前試したImage Captioningモデルは、前者のニーズには答えられているけど、後者のニーズには答えることが出来ていません。
要因として、以前使用したImage Captioningモデルは事前学習済みのものをそのまま使用しており、"時計の画像からその時計が示す時刻を説明する"という特定の用途に特化したモデルではないことが考えられます。しかし時計の画像とその時計が示す時刻をテキストで説明したデータセットを用意し、追加学習(Fine-Tuning)したら、時計の画像からその時刻を答えてくれるモデルが出来るかもしれません。
今回はこのことを確認するため、野菜と果物の画像から、その画像の中にどの野菜/果物が何個あるのかをテキストで答えてくれるモデルを作ることが出来るか、試してみました。
BLIP: Bootstrapping Language-Image Pre-training
今回使用したImage Captioningのモデルは、Hugging Faceに公開されている"Salesforce/blip-image-captioning-large"というモデルです。このモデルはBLIP: Bootstrapping Language-Image Pre-trainingという方法で事前学習したモデルです。
BLIPは"BLIP: Bootstrapping Language-Image Pre-training for Unified Vision-Language Understanding and Generation"という論文で提案された手法で、画像と言語を扱う様々なタスクに柔軟に対応できるモデル構造の観点と、ノイズが多いWebから集めた画像とテキストのペアのデータを事前学習用途に最適化する観点で構成された手法です。
このモデルをFine-Tuningして、野菜と果物の画像からその個数を教えてくれるモデルを作ることが出来るか試していきます。そのために、まずFine-Tuning用のデータセットを用意する必要があります。
Fine-Tuning用のデータセット
入力が想定される画像は以下のような画像です。
それに対し、出力してほしいテキストは"a picture of 3 bell peppers, 2 bananas, 1 apple,and 1 potato."です。
これを実現するために、野菜と果物の色々な画像を集め、Fine-Tuning用のデータセットを用意しなければなりません。しかし残念なことに、我が家にはそんなに野菜と果物がありません。そこで今回、チームのメンバーに3Dモデルを使って色々な野菜と果物のCG画像を作ってもらいました。
以下のようなCG画像です。
大体40枚程度の野菜と果物のCG画像を作ってもらいました。あとは手作業で説明文を書いていきました。
データセットの水増し(Data Augmentation)
40枚程度のデータセットだと少し足りないかな・・・と感じ、不足分はデータセットの水増し(Data Augmentation)で補うことにしました。Data Augmentationは画像と説明文双方に施すことにしました。
画像に対するData Augmentation
画像に対しては以下の4つの変換処理をかけました。
- 画像の明るさ、コントラスト、色相を変える(
ColorJitter
) - 画像を水平方向に反転する(
RandomHorizontalFlip
) - 画像を垂直方向に反転する(
RandomVerticalFlip
) - 画像の傾ける(
RandomRotation
)
説明文に対するData Augmentation
複数の野菜と果物が含まれている説明文の場合、野菜と果物の順番を並べても説明文の意味は変わりません。そこで野菜と果物の並びをランダムに並び変える処理を施しました。
コード
以上の変換処理を施したうえで学習用のデータを提供するPyTorchのDataset
クラスを以下の様に定義しました。
from torch.utils.data import Dataset, DataLoader from torchvision import transforms # 画像変換処理 transform = transforms.Compose( [ transforms.ColorJitter(brightness=0.5, contrast=0.5, hue=0.2), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomVerticalFlip(p=0.5), transforms.RandomRotation(degrees=(-35, 35)) ] ) def caption_transform(caption): """ 野菜と果物の名前をランダムに入れ替える """ import random item_string = caption.replace("A picture of ","").replace(".","").replace("and","") splitted = item_string.split(",") splitted = [s.strip(" ") for s in splitted if len(s.strip(" ")) > 0] if len(splitted) == 1: return caption random.shuffle(splitted) transformed_caption = "A picture of " + ", ".join(splitted[:-1]) + ", and " + splitted[-1] + "." return transformed_caption class YasaiDataset(Dataset): def __init__(self, df, processor): self.processor = processor self.df = df def __len__(self): return self.df.shape[0] def __getitem__(self, idx): img, caption = get_data(idx, self.df) img = transform(img) caption = caption_transform(caption) encoding = self.processor(images=img, text=caption, padding="max_length",return_tensors="pt") encoding = {k:v.squeeze() for k,v in encoding.items()} return encoding def check_transform(self, idx): """ 画像の変換の様子を確認するためのメソッド。 学習処理などのメイン処理では利用しない。 """ img, caption = get_data(idx, self.df) img = transform(img) caption = caption_transform(caption) return img, caption
変換後のデータの例
上記の変換処理を施して取得されるデータの1部は以下の様になります。
ColorJitter
の処理はかけすぎかもしれない・・・と思いました。右下のバナナはキュウリみたいになってしまいました。
Fine-Tuning
Fine-Tuningする際のハイパーパラメータは以下の様にしました。
batch_size
: 2learning_rate
: 5e-6optimizer
: AdamW
batch_sizeはもう少し大きく取りたかったのですが、GPUメモリ不足エラーが発生したため、小さいサイズにしました。データセットのサイズがそれほど大きくないためV100GPU1基の環境にしましたが、もう少しデータセットの数が豊富になったら分散学習などを検討し、より大きなbatch_sizeを取れるようにしたいと思います。
lossの様子は以下の様になり、収束しているようです。
Fine-Tuning前後での説明文の様子を比較
Fine-Tuning前と後で、同じ画像に対してどのような説明文が生成されるかを見てみました。テスト用に取っておいたCG画像の中から、以下の画像をチョイスして試してみます。
この画像に対し、Fine-Tuning前は以下のような説明文が生成されました。
'potato chips on white background [SEP]'
ポテトチップスに見えるんですね・・・。一方Fine-Tuning後はどうなるかというと、
'a picture of 16 potatoes. [SEP]'
良さそうです!良さそうなのですが、実はじゃがいもの個数をちょっと間違えています。正しくは"16個"ではなく"18個"なのです。
次はこちらの画像を試してみます。
Fine-Tuning前は以下のような説明文が生成されました。
'a white wall with a bunch of fruit and vegetables [SEP]'
たくさんの野菜と果物があるので、この説明文自体は正しいです。ただ、より詳細に、どの野菜と果物がどれくらいあるのかを知りたいです。ではFine-Tuningをするとどのようになるのでしょうか?
'a picture of 1 garlic, 1 apple, 1 pumpkin, 1 bell pepper, 1 green apple, 1 pumpkin, and 2 potatoes. [SEP]'
これもすごく良さそうです!良さそうなのですが、やっぱりちょっと惜しいです。"1 pumpkin"が2回出力されてしまっています。
これまでは学習に使用したものと同じくCG画像でテストをしてきました。最後に現実の野菜と果物の画像でどうなるかを確認してみます。
最初はFine-Tuning前のモデルが生成した説明文です。
'a group of fruits and vegetables on a white surface [SEP]'
正しいですね。ではFine-Tuning後のモデルが生成した説明文です。
'a picture of 1 green apple, 1 banana, 1 apple, 1 bell pepper, 1 potato, 1 banana, and 1 pumpkin [SEP]'
"1 banana"が2回出力されているので、合わせて2本と捉えると、バナナ、リンゴ、ジャガイモは正しく個数を説明しています。難しかったのはピーマン(bell pepper)で、3つのうち1つはピーマンと認識できているのですが、1つを青リンゴ、もう1つをカボチャと間違えてしまいました。それでも結構いい線いってる!とちょっと感動してしまいました。
まとめ
ということで、今回はBLIPモデルをFine-Tuningして想定ユースケースに合った説明文を生成することが出来るのかを試してみました。また、現実の画像があまり集まらないという問題に対し、CGで生成した画像で対応出来るのかについても試すことが出来ました。ピーマンを少し間違えてしまいましたが、ピーマンの3Dモデルをもう少し変化に富んだものを用意出来れば、この問題は解決出来そうな気がします。
一方、この方法には別の課題があります。学習時に出現しなかった野菜や果物は認識できない、という課題です。以下の様に、学習データには無かったタマネギ、ニンジン、そしてカキを含んだ画像に対して、Fine-Tuning後のモデルの出力は以下のようになりました。
a picture of 1 bell pepper, 1 apple, 1 green apple, 1 pumpkin, 1 banana, 1 potato, 1 bell pepper, and 1 bell pepper [SEP]
タマネギ、ニンジン、カキを認識することは出来ませんでした。Fine-Tuningによって認識できる物体にも制限がかかってしまいました。Fine-Tuning前の柔軟性を維持しつつ、タスクに特化させる方法がないか、引き続き試してみたいと思いました。