Open GENZITSU opened 2 years ago
Yahoo!検索において、クエリから正しい拠点情報を抽出する検索モデルを、Deep Metric Learningにより改善した事例の紹介
改善前のモデルであった課題
Deep Metric Leaningによる手法
encoderにはtransformerを用いており、以下のようなコードで情報抽出を行う 最終出力をtoken方向で平均をとっているのが特徴的か
# from https://techblog.yahoo.co.jp/entry/2021120330233760/
import tensorflow as tf
# Token ID列化とTensorflowの公式チュートリアルで定義されている関数やクラスは省略しています
class TransformerEncoder(tf.keras.models.Model):
def __init__(self, num_vocab, dim_embedding, dim_hidden, num_heads, num_layers, maximum_position_encoding):
super().__init__()
self.dim_embedding = dim_embedding
self.num_layers = num_layers
self.embedding = tf.keras.layers.Embedding(num_vocab, dim_embedding)
self.pos_encoding = positional_encoding(maximum_position_encoding, self.dim_embedding)
self.enc_layers = [EncoderLayer(dim_embedding, dim_hidden, num_heads) for _ in range(num_layers)]
self.aggregate = tf.keras.layers.GlobalAveragePooling1D()
self.ffn = point_wise_feed_forward_network(dim_embedding, dim_hidden)
def call(self, x):
seq_len = tf.shape(x)[1]
mask_for_attention = create_padding_mask(x)
x = self.embedding(x)
x *= tf.math.sqrt(tf.cast(self.dim_embedding, tf.float32))
x += self.pos_encoding[:, :seq_len, :]
for i in range(self.num_layers):
x = self.enc_layers[i](x, mask_for_attention)
x = self.aggregate(x)
x = self.ffn(x)
return tf.nn.l2_normalize(x, -1)
学習にはcontrastive learningを用いてるが、以下のような工夫がされている
画像検索だとクエリと検索対象どちらも画像なので、ベクトル化するモデルをそれぞれ用意せず共有するのが一般的だと思います。今回のケースでは検索クエリと拠点側の情報は同じではないため、クエリ×拠点のペアの組み合わせだけではなく、クエリ×クエリ、拠点×拠点の組み合わせに対しても損失を計算し最適化するように工夫しています。
クエリ×ランダムサンプリングした拠点、ランダムサンプリングした拠点同士の組み合わせに対しても損失を計算しています。ミニバッチ数とランダムサンプリングする拠点の数はGPUのメモリが許す限り大きくしたほうが精度がでました(ミニバッチ数は4096、ランダムサンプリングする拠点の数は28672に設定しています)。
# from https://techblog.yahoo.co.jp/entry/2021120330233760/
def contrastive_loss(
query_vectors, query_labels, spot_vectors, spot_labels, margin
):
"""
query_vectors, spot_vectors: それぞれクエリと拠点のベクトルでshapeは[ミニバッチ数, ベクトルの次元数]です
query_labels, spot_labels: それぞれクエリの正解拠点のラベル、拠点のラベルでshapeは[ミニバッチ数] です
margin: マージン(ハイパーパラメータ)です (例えば 0.7)
"""
similarity_matrix = tf.linalg.matmul(query_vectors, spot_vectors, transpose_b=True)
query_labels_expand = tf.transpose(tf.repeat([query_labels], repeats=tf.shape(spot_labels)[0], axis=0))
positive_mask = tf.equal(query_labels_expand, spot_labels)
negative_mask = tf.logical_and(tf.logical_not(positive_mask), tf.greater(similarity_matrix, margin))
return (
tf.reduce_sum(1 - tf.boolean_mask(similarity_matrix, positive_mask))
+ tf.reduce_sum(tf.boolean_mask(similarity_matrix, negative_mask))
) / tf.cast(tf.shape(query_labels)[0], tf.float32)
モデルのデプロイにはTensorflow Servingを用いており、sentencepienceによる前処理も内包できるのが利点なんだとか
最後にうまくいったことといかなかったことの
Transformerのpositonal encodingの代わりに双方向LSTM [文献6] を入れる 多少ではありますが、精度が上がりました。
損失関数を Proxy Anchor Loss [文献7] のようなものにする Contrastive Loss から Proxy Anchor Loss にすると精度が多少上がりました。Proxyといっていますが実際はProxyを使っておらず、Proxyを拠点側のベクトルに置き換えたものを使っています。
Hard Negativeを入れて学習する ミニバッチを構成するクエリと正解拠点のペアはランダムにサンプリングしています。一度そのようにして学習したあと、そのモデルを使って検索結果上位数件(Hard Negative)を取得しミニバッチに加えてさらに学習すると精度が上がりました。その際、クエリ側のモデルと拠点側のモデルのパラメータを同時に更新してしまうと逆に精度が下がってしまったのですが、拠点側のパラメータは固定しクエリ側だけ学習するとことでうまくいきました。
TransformerのLayer数を増やす 最終的にはクエリ側は2層、拠点側は1層になりました。単純に同じミニバッチ数でLayerを増やしてみても精度は上がりませんでした。学習データがもっと膨大にあれば違う結果になるのではないかと思っています。
拠点の情報の使い方を工夫する 拠点側の情報は、拠点名と住所を結合してモデルの入力にしていますが、拠点名用のモデルと住所用のモデルを別々にしそれぞれのモデルの出力をconcatして全結合層を通すものを試してみたのですが、精度は上がりませんでした。
DeepMetric Learningによる事例 * 実装例の紹介がありがたい。
encoderのモデルサイズよりもバッチサイズが大きい方がよいというのは、当然ではあるが、ありがたい知見である。
機械学習やルールベースのアルゴリズムを掛け合わせることで、アプリのスクリーンショットから、UI情報の読み上げを行う研究の紹介。
各UIの検出にはSSD (+Feature Pyramid Network)をもちいており、これに加えて選択/非選択状態の判別やUIのグルーピングをルールベースで行っている。
そこでSSD (Single Shot MultiBox Detector) を使うことで、高速化と使用メモリの削減を行いました。 また、UIは他の物体検出のターゲットに比べ小さいため、FPN (Feature Pyramid Network) を用いることで、その問題を解決しました。 最終的に、計算速度は10ms、メモリ使用量は20MBを実現しています。
技術的に特別なことをしているわけではないが、確実に役に立つ良い研究。
ある画像を与えたときに、それが別の画像のコピー / 別の画像の改変かどうかを検知するコンペティションの2位解放の紹介。
本コンペのデータは、以下の3つのサブセットに分かれていました。
- query: コピー検知の対象画像。画像の枚数は5万枚。
- reference: コピー元の候補の画像のセット。画像の枚数は100万枚。
- training: 学習用セット。画像の枚数は100万枚。分布的にreference setと類似。
加工の種類には以下のようなものがあり、中にはめっちゃ難しいものも含まれていたとのこと
加工は自動/手動の両方がある。
- 画像編集ツールによる手動加工
- テキスト・絵文字・画像のオーバーレイ
- 色の変換(グレースケール化・彩度の変更・フィルター処理など)
- 空間的な変換(切り抜き・回転など)
距離学習をもとに画像のembeddingを学習させる。ロスにはcontrastive lossを用いたとのこと。
画像1枚を1つのクラスとして割り振ることで無理やりclassification lossを適用することもできます。ただ、学習データの数に比例してクラス数が増えるためメモリ消費量が爆発する上に、自分が実験した限りでは精度的にもembedding lossと比較して劣るようでした。
ただし、バッチサイズが大きくした方が学習がうまくいきやすいので、Cross-Batch Memoryという手法を用いたとのこと。 この手法は学習中のモデルが出力したembeddingをためておき、巨大なミニバッチでの学習を模倣するというテクニック
Cross-batch memoryを簡単に説明すると、過去の学習イテレーションで計算したembeddingをmemoryと呼ばれるFIFO形式のキューにどんどん貯めていき、学習用のpositive-pair, negative-pairを作る際にmemory内のembeddingも使用する手法です。メリットとしては、メモリの消費量を抑えつつ、あたかも巨大なミニバッチサイズで学習しているときのような効果が得られることにあり、モデルの学習に有益な難しいペアをより作りやすくなります。「過去のモデルのembeddingなんか使って大丈夫なの?」という気もしますが、モデルの学習最初期を除いて、学習経過によるembeddingのdriftは小さいことは実験的に示されています( cross-batch memoryの論文 のFigure 3)。
Cross-Batch Memoryのメモリサイズは大きければ大きいほど良い
こちらのcross-batch memoryを組み合わせると、モデルの性能が大きく向上しました。また、memoryのサイズは大きければ大きいほど精度的には良くなり、最終的に2万サンプルまで大きくしました。
実装にはPytorch MetricLearningを用いた。
Data Augmentation
自前で正例・負例のペアを作り出す必要があり、自分は画像に加工を施し、加工前後の画像を正例のペア、無関係な画像同士を負例のペアとして学習させました(汎化性能向上をねらいとして、厳密には加工前として正例ペアに使っている画像に対しても、弱めのdata augumentationは適用させています)。
AugLyはFacebook AI謹製のライブラリで、色彩変換などの基本的なaugmentationに加え、絵文字・テキストのオーバーレイや、SNSの画像投稿欄への画像はめ込みなどの変わったaugmentationも用意されています。本コンペのコピー画像もAugLyを使って生成されており、できるだけコンペのデータに近づけるためにもAugLyを使わない理由はありませんでした。
画像加工の種類ですが、データセット内のコピー画像の生成に実際に用いられた加工を使えるだけ使いました。ただ、手動のペイントツールによる加工の再現については流石に今回は諦めています。画像加工に用いる変換のパラメータは、データセットの画像を目で見ながら雰囲気で調節しました。
progressive learningによってaugmentation強度や解像度は徐々に上げていった。
Data augmentationの強度については、 progressive learning を参考に、学習が進むにつれてより激しくするという戦略をとりました。また、入力画像の解像度もそれに応じて徐々に上げていきました。入力画像の解像度を徐々に上げていくというアプローチは画像検索コンペの上位解法( GLR20 1st place , GLR21 1st place )でもよく見られ、有効性が示されています。
augmentationをかける順番もランダムに変更
その他の工夫点としては、とにかくランダム性を重視し、できるだけ多様な画像が生成されるようにaugmentationを適用する順番もシャッフルするようにしました。
モデルはefficientnet V2にFCNとBNを重ねたものを使用
backboneの最終層にfully connected layerとbatch normalizationが続く、といった構成になっています。 近年の自己表現学習のモデル を参考に、backboneの最終層に3層程度のMLPを繋げて実験してみたりもしましたが、効きませんでした。
後処理として、学習データセットに含まれる類似画像のembeddingを引くというテクニックを使っている。
これは学習データセットは負例であるというコンペのルールがあったから効いたもの
raining setのサンプルは、負例であることが保証されているということです。この性質によって、推論対象画像に類似したtraining setの画像は、「これはコピー元の画像だ」と誤検知してしまいがちな典型例として捉えることができます。
そこで、推論対象画像のベクトルを、類似のtraining setの画像のベクトルから遠ざけてあげれば良いのでは無いか?と考えました。実際のところ、推論対象画像のベクトルから類似のtraining setの画像のベクトルを減算するだけで大きく精度向上しました。
意外にもGeM PoolingやTTA, アンサンブルはきかなかったとのこと。
画像検索モデルで用いられることの多い GeM pooling は意外にも効きませんでした。 また、画像コンペではアンサンブル・TTA(test time augmentation)が定番になっていますが、今回のコンペの最終提出には使用していません。 というのも、コンペ初期の段階のモデルの精度が低い段階では効き目があったのですが、ある程度精度が高くなった段階で効かないようになった
画像検索コンペに関する貴重な解法紹介。cross batch memoryというテクは知らなかった。
あとprogressive learningも結構効果あるのは意外だった、今後業務で使ってみたい。
物体検出へのアノテーションを効率化するwebアプリの開発報告 @PFN夏インターン
この特定物体アノテーションにかかる時間を短縮するために,アノテータが囲った物体と画像的に類似する物体を検索するアノテーションアプリケーションの開発に取り組みました. 従来,類似画像検索をロバストに行うのは難しかったですが,近年 Deep learning ベースの手法によって実用的になってきています.中でも今回は OpenAI から発表された高いZero-shot性能を持つ CLIP を画像特徴量抽出器として使用しました.
アノテータがバウンディングボックスで対象商品を囲むと,囲った領域の画像がサーバに送られます.サーバにはあらかじめマッチング対象の商品画像をエンコードしてまとめた行列を持っておき,クライアントから送られてきた画像を CLIP の Image Encoder でエンコードしたベクトルとの内積から類似度の計算を行います. 最後に類似度の上位5位の商品画像と商品名をクライアントに返し,表示します.
これにより、アノテーション時間は2/3程度になる。
実装はFastAPIとnext.js, Typescriptを用いられており、kubernetesを用いてデプロイを行なっている。
clipによるembedding抽出でラベルをレコメンドすることで効率化できているのはすごい。
地味にbboxに十時がついているのは助かる、画像の抽出とかを合わせないといけないときとかあるので。
クライアント側でタイムアウトを設定しても、FastAPIでは処理が打ち切られない事象に対して、Middlewareでtimeout用の処理を作成することで解決。
コードの抜粋
# from https://zenn.dev/ohsawa0515/articles/fastapi-timeout
# main.py
import os
import time
import asyncio
from fastapi import FastAPI
from timeout_middleware import TimeoutMiddleware
# タイムアウト値を環境変数で設定できるように
REQUEST_TIMEOUT = os.getenv("REQUEST_TIMEOUT", 5)
app = FastAPI()
app.add_middleware(TimeoutMiddleware, timeout=REQUEST_TIMEOUT)
@app.get("/sleep/{wait_time}")
def wait_for(wait_time: int):
time.sleep(wait_time)
return {"Wait time(sec)": wait_time}
@app.get("/async_sleep/{wait_time}")
async def async_wait_for(wait_time: int):
await asyncio.sleep(wait_time)
return {"Wait time(sec)": wait_time}
# from https://zenn.dev/ohsawa0515/articles/fastapi-timeout
timeout_middleware.py
import asyncio
from typing import Callable
from fastapi import Request, Response
from fastapi.responses import JSONResponse
from starlette.types import ASGIApp
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.status import HTTP_504_GATEWAY_TIMEOUT
class TimeoutMiddleware(BaseHTTPMiddleware):
"""
Return gateway timeout error (504)
if the request processing time is above a certain threshold
"""
def __init__(self, app: ASGIApp, timeout: int = 10):
super().__init__(app)
self.timeout = int(timeout)
async def dispatch(self, request: Request, call_next: Callable) -> Response:
try:
return await asyncio.wait_for(call_next(request), timeout=self.timeout)
except asyncio.TimeoutError:
return JSONResponse(
content={'detail': 'Request processing time excedeed limit'}, status_code=HTTP_504_GATEWAY_TIMEOUT)
デフォルトだと打ち切られないという設定に驚いたが、対応方法が分かってよかった。
表題の通り、特に重要そうなものだけ抜粋
- 集計方針・指標の定義・アウトプット形式を共有してから集計する
- 必ず定性評価する & 定性評価も基準を設けて定量的に測る
- 必ずクエリ結果のセルフレビューをする
- レポートには再現可能な情報を残す
どれも良い心がけ
タイトル通りの内容。二次元画像の高fps超解像モデルを学習させるためのノウハウがまとめられている。
キャラクターと喋るアプリケーションを作るに際しtaking head anime 2では256x256の画像生成しかできないために、超解像モデルの学習が必要になった。
既存の超解像モデルは高fpsでもリアル画像でしか性能が出ないために、自前で学習を行った。
既存の軽量解像度の選択肢としては、次のようなモデルがあります。()内に書かれている速度は256x256の画像を4倍に拡大した場合の速度で、RTX3070で測定しました。
- Fast SRGAN(30fps)
- Realtime Super Resolution(10fps)
- Real ESRGAN for Anime(10fps)
超解像の学習方法は極めてシンプルで、高解像度画像からcropして劣化させたものとオリジナルをペアにして学習を行うというもの。この劣化手法に関してはReal ESRGANがうまいこと色々やっているとのこと。
超解像では、2K画像くらいの比較的高解像度な画像が使われることが多いです。下記の画像のように高解像度画像から、32x32とか128x128という小さめのサイズ感のパッチを取り出して、それらのパッチを劣化させて学習データを作ります。そして、その学習データを用いて、学習したり評価します。 劣化処理と書いたのは一言に縮小と言っても、縮小の仕方にも種類があったり、ノイズが乗ったりする可能性もあるからです。 この劣化処理をどのように作成するかも非常に重要です。現実には複雑な劣化をするので、その複雑な劣化をうまく再現できなければノイズに弱いモデルになっていまいます Real ESRGANでは、劣化処理を非常にうまく扱っています。
方針① 軽量なモデルをベースに色々改良を加えていく。
Fast SRGANを独自データセットで再学習 → 微妙な結果に
方針② 良いモデルから削っていく
Real ESRGANのブロック数と特徴量次元数を減らすことで高fpsを達成
Real ESRGANのアニメ版では、RRDBのブロックを6個つみあげていて、かくRRDBのブロックの特徴抽出能力に相当する次元数は64次元でした。このブロック数と次元数を減らしていくつか実験してみたところ、3ブロック、32次元くらいまで下げると25fpsになり、画質は落ちるとはいえそこそこ見栄えがいいものとなりました。
ここからさらに高fpsとするために、ESRGANにReal ESRGANのテイストを逆輸入してデータを増やすことで50fpsを達成
ESRGANのAblation Studyを受けて、元々軽量なSRGANでも、Discriminatorやデータ量が多ければ、そこそこいけるのでは?と思い、Real ESRGANのフレームワークでSRGANのモデルを学習させてみたところ、なんと256x256の画像を拡大した場合に50FPSでできて、そこそこ画質がいいモデルができあがりました。パラメータは6ブロックで特徴抽出能力を32としました。
Real ESRGANの画像生成手法は下記のとおり。現実できな超解像問題にするために劣化を2段階に分けている
超解像モデルを作成する際に非常に参考になる話。
世の中のあらゆるものはコモディティ化しており、エンジニアリングスキルも例外ではない。
じゃあどうすればよいか?その一つの答えとして筆者は事業をスケールさせるエンジニアリングを学ぶことを提唱している。
良い話である。これをできる立場に入れる人は数少ないと思うが目指していきたい到達点でもある。
スライド中にでてくる、「エンジニアが1行のコードから財務諸表を意識する世界を作りたい」という言葉がかなり印象的。
LayerX CTO松本さんが綴った組織設計の際に考えるべき4つのポイントの紹介
特にソフトウェアを中心としたスタートアップでは人が最も大切なファクターとなりがちです。やはり、良い仲間を集めて初めて良いプロダクトが生まれるものです。 人とその集合である組織は、プロダクトの進化の起点であり、また文化を守りながらそれを変化させスケールさせるのがとても難しいと感じます。どんなに資本やその他リソースを持ったところで、この難しさは変わりません。 となると、スタートアップにおけるオペレーショナルエクセレンスを保ちスケールするには常に組織設計という観点にフォーカスせざるを得ないのではないでしょうか。
① 人と資本の時間軸
人を採用するのには広報 ~ 応募 ~ 先行 ~ オンボーディングといった手順を踏む必要があり半年以上はかかる。
一方で人を雇用するための資金の確保もそれと同じ程度の時間がかかる。
これを踏まえると、将来が不透明な状態で、採用活動や募集職種などの意思決定が必要となり、経営的な判断が必要となってくる。
② 部分ごとの不確実性の濃淡
事業を形成するそれぞれのモジュールにはそれぞれの不確実性度合いがあり、その濃淡によって必要になる人材が異なるということ。 不確実性が高い時期は専門性よりも手数とバイタリティを持った人材が必要で、不確実が低く・スケール待っただ中という時期だと専門性が高く間違いなくタスクをこなせる人材が必要となる。
③ タスク完結性
個人がタスクの設計から完了までに意思決定を行える指標としてタスク完結せいというものがあるが、これが組織全体のモチベーションにつながる。
組織設計はこのモチベーションに当然ながら大きな影響を与えます。特に意識することの一つとしてタスク完結性というものがあります。タスク完結性というのは、「そのチームや個人の中でタスクの設計から完了まで理解でき、自身で意思決定できること」というふうに捉えています。
タスク完結性は組織内のコミュニケーションプロトコルに影響をうけるので、注意深く設計する必要がある。
つ一つのチームが自身で意思決定して進められるのか、その阻害要因が生まれないかという観点が重要です。LayerXではTrustful Teamというバリューがありますが、お互いを信頼して事業を進めるには、それに適した組織設計が必要です。
④ ソフトウェアとプロダクトの設計的視点
ソフトウェアを考察する上で凝縮度と結合度といった指標がでてくるが、これと同じことが組織にも言える。
狭いキッチンに沢山のコックを詰め込んだ状況となります。互いの作業が邪魔をしあってうまくいい仕事ができません。
そスケールする組織とするためにはソフトウェアをうまく活用し、担当する範囲を分割しながらキッチンを分けて行く必要があります。
そのプロダクトを構成する業務ドメインやその中で行われるプロセスを分析しながら、どのように区分されていくべきか考え、それを反映した組織設計になっているのか。この観点が考慮されている状態を常に保てるよう、CTOとして全ての組織設計に意識的に関わるようにしています。
毎度のことながら、いずれも金言である。
こんな感じでつかえるpytorch用のdata drift検出ライブラリだそうで
# from https://github.com/torchdrift/torchdrift/
drift_detector = torchdrift.detectors.KernelMMDDriftDetector()
torchdrift.utils.fit(train_dataloader, feature_extractor, drift_detector)
features = feature_extractor(inputs)
score = drift_detector(features)
p_val = drift_detector.compute_p_value(features)
if p_val < 0.01:
raise RuntimeError("Drifted Inputs")
メモ
TPU利用に関するtipsがまとめられている。
以下抜粋
- Data Loaderでは最後のバッチを使わない方がよい
- TPUでは使えない演算や、制約のある演算がある
- バッチサイズや特徴のサイズは128/8の倍数にする
- Memory Exhaustedの場合はTPUコネクションを繋ぎ直す
またこれとは別にパーフォーマンスガイドなるものがあるようだ
本質情報だ...
業務で時系列データを可視化する時の話
時系列データをpythonで綺麗に可視化するためのコーディング方法が紹介されている。
コメント
メモ
出典
元記事