CLIPで不動産画像を分類する

# はじめに
株式会社estieでオフィス募集データのパイプラインを構築するチームのソフトウェアエンジニア兼Product Ownerをしております、平田です！
今回は [Calendar for estie Advent Calendar 2021 | Advent Calendar 2021 - Qiita](https://qiita.com/advent-calendar/2021/estie) 12日目の記事としてVision and Languageの汎用モデルとして有名なCLIPの概要の説明をしたのち、estieが扱っているような不動産画像に対してCLIPを適用してみたいと思います。

# CLIPとは？
CLIPは[Learning Transferable Visual Models From Natural Language Supervision](https://arxiv.org/pdf/2103.00020.pdf)（以下、アルゴリズムの説明に使った画像はこちらから引用しています）というOpenAIから発表された論文で紹介された手法で、特定のタスクに特化していない汎用的な画像とテキストの表現を学習し、さまざまなタスクに適応できることを示しました。簡単にCLIPの論文について紹介していきます。(論文の詳細に興味のない方は読み飛ばしてください)

## 背景
多くのState-of-the-artを達成している画像認識モデルはラベルの付けられた教師ありデータセットを用いて学習され、そのデータセット✖️タスクにおいて突出した結果を出しています。しかしそれらのモデルを他のデータセットやタスクに用いるには、そのタスクに適した教師データつきのデータセットを新たに用意する必要があり、汎用性にかけていました。

## 手法
CLIPは汎用性のあるモデルを構築するためにウェブから得た大量の画像とキャプションのペアを教師データとしてpre-trainingを行います。
画像とテキストをそれぞれImage EncoderとText Encoderに入力し、(batch size, hidden tensor size)のテンソルを出力します。
画像特徴とテキスト特徴は内積(実際には特徴はそれぞれNormalizeされているのでcos similarity)を計算し、ペアである画像とテキストについて内積が大きくなり、ペアではない特徴同士の内積は小さくなるようにCrossEntropyLossを用いて学習します。
![スクリーンショット 2021-12-02 22.44.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316582/8a1ab62d-1fee-28b7-e268-fa77f45f04da.png)

pre-trainされたモデルを特定のタスクに用いる場合にはCLIPの画像特徴とテキスト特徴を用いる判別器を学習させるか、上の図表にあるように"A photo of a {object}"といった特定の形式のテキスト入力に整形してzero-shot predictionを行います。

## 実験
30を超える画像系のデータセットで実験した結果、CLIPを用いた予測はzero-shot predictionであっても教師あり学習で学習させたモデルと同程度の結果を出すことを示しました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316582/b1cf50c4-c0a2-20d5-9822-b3f7e03340b4.png" width="50%" ><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316582/a25cfeb4-ab26-24b8-1fcd-0e2c2769eff3.png" width="50%">


# 不動産画像に使ってみる
さて、とてつもない結果を示しているCLIPを実際に使ってみましょう。CLIPの実装や学習済みモデルは[公式のGithub](https://github.com/openai/CLIP)か[Hugging Face Transformers](https://huggingface.co/transformers/model_doc/clip.html)で公開されています。
今回は後者を使っていきます。まずTransformersのドキュメントにあるようにモデルを準備します。

```python
from PIL import Image
import requests

from transformers import CLIPProcessor, CLIPModel

model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
```
簡単ですね。
estieではestie proという不動産事業者向けのオフィス情報プラットフォームを提供しています。estie proでは個別のオフィスビルの画像も扱っているのですが、不動産データは整備されていない部分も多く、用途の異なるビルの画像が含まれていたり、内観と外観の画像が混ざってしまっていたりします。今回はCLIPを用いてこうした画像のzero-shotでの分類を行なっていきます。

### 建物用途の判別

```python
office_image_url = "http://path/to/office image"
shopping_mall_image_url = "http://path/to/shopping mall image"
house_image_url = "http://path/to/house image"

selection = ["a photo of an office building", "a photo of a shopping mall", "a photo of a house"]
for url in (office_image_url, shopping_mall_image_url, house_image_url):
   image = Image.open(requests.get(url, stream=True).raw).convert("RGB")
   inputs = processor(text=selection, images=image, return_tensors="pt", padding=True)
   outputs = model(**inputs)
   logits_per_image = outputs.logits_per_image
   probs = logits_per_image.softmax(dim=1)
   print(selection[probs.argmax()])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316582/cc119504-ad7b-6d26-4014-e9197ffcf731.png)

```
>>> probs
tensor([[0.9950, 0.0038, 0.0012]])
>>> selection[probs.argmax()]
a photo of an office building
```

<!-- 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316582/530fc337-1d3b-a3c5-297e-6f429eb7bc11.png)
-->
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316582/0eb7a757-5b0e-0400-94a8-dab9d211c5a9.png)


```
>>> probs
tensor([[0.1162, 0.8814, 0.0025]])
>>> selection[probs.argmax()]
a photo of a shopping mall
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316582/190c15c5-4624-3f3e-7a43-8aef252be031.png)

```
>>> probs
tensor([[0.2214, 0.0015, 0.7771]])
>>> selection[probs.argmax()]
a photo of a house
```
うまく判別できていそうですね。

### 建物の内装・外装の判別
上記と同様に建物の内観(inside)か外観(appearance)か判別していきます。

```python
inside_url = "http://path/to/inside image"
appearance_url = "http://path/to/appearance image"

selection = ["a photo of the inside", "a photo of the appearance"]
for url in (inside_url, appearance_url):
   image = Image.open(requests.get(url, stream=True).raw).convert('RGB')
   inputs = processor(text=selection, images=image, return_tensors="pt", padding=True)
   outputs = model(**inputs)
   logits_per_image = outputs.logits_per_image
   probs = logits_per_image.softmax(dim=1)
   print(selection[probs.argmax()])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316582/ee6714c8-a152-c7ba-d4c4-56cde09f99c4.png)

<!--
>>> probs
tensor([[0.8091, 0.1909]])
-->
```
>>> selection[probs.argmax()]
a photo of the inside
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316582/cc119504-ad7b-6d26-4014-e9197ffcf731.png)

```
>>> selection[probs.argmax()]
a photo of the appearance
```

こちらも上手く判別できていそうです。

### 更に知りたい方は
まずはぜひ、[元の論文](https://arxiv.org/pdf/2103.00020.pdf)を読んでみてください。
また、同じような課題を解決しようとしている[ViLBERT](https://arxiv.org/abs/1908.02265)や[ViLT](https://arxiv.org/abs/2102.03334)を読まれたりすると理解が深まるかもしれません。実装詳細については少し複雑ですが[ソースコード](https://github.com/openai/CLIP)が公開されているので見てみて下さい。

# 終わりに
estieは不動産業界をアップデートするために目立たない部分で多くの技術的な難しさに立ち向かっています。事業が拡大する中、解決しなければいけない問題も多く、データ・機械学習エンジニアを求めています。
興味持っていただいた方はぜひお話しましょう！
https://jobs.estie.jp/

#### 参考
[Learning Transferable Visual Models From Natural Language Supervision](https://arxiv.org/pdf/2103.00020.pdf)
https://huggingface.co/transformers/model_doc/clip.html