Cloudwatchアラームの欠損データの扱い
Cloudwatch アラームは実は評価期間よりも多くの範囲のデータを取得した上でアラーム有無を判定する。
例えば、アラームを発生させるデータポイント数が3・評価期間が3とする。 ここで、実は3以上の期間を取得してきていて(この範囲を評価範囲という)、次の場合分けが発生している。
- 評価範囲にデータが欠落していない場合は、最新のデータポイント3つを用いて評価する
- 評価範囲にデータが欠落しているが、データポイントが評価期間数以上である:実在するデータポイントを用いて評価される。欠損データは無視される
- 評価範囲にデータが欠落しているが、データポイントが評価期間数未満である:ここで初めて欠損データをどう扱うか?が関係してくる(MISSING, IGNORE, BREACHING, NOT BREACHING)
であるので、アラームを作成する際に欠損データが出たことをトリガーにしたい場合には、別途FILL()関数を用いて固定値で埋め込む必要がある(負の値を入れるなど、メトリクスの性質で変えると良い)
欠損データを"BREACHING"で捉えようとすると、タイムラグが発生してしまうので意図したタイミングでは発砲されない。
参考
Pandas Dataframe操作メモ
Pandas Dataframeでの操作メモページ
これどうやるんだっけ?というちょっと工夫がいる操作をメモしておく。
idごとに過去の値を集約して元のデータフレームに連結する
- race_id
- racer_id
- race_time
からなるデータフレームがあるとする
data = {
'race_id': [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3],
'racer_id': [101, 102, 103, 104, 105, 106, 101, 107, 108, 109, 110, 102, 101, 103, 102, 111, 112, 113],
'race_time': [60, 65, 70, 62, 68, 75, 58, 60, 65, 70, 72, 63, 57, 68, 61, 70, 75, 80]
}
df = pd.DataFrame(data)
racer_idごとに、直近3回のレースでのレースタイムの平均を取りたい。ということを考える。
以下の方法では、グループを跨いでshift(1)されてしまうことに注意
df_sorted = df.sort_values(by=['racer_id', 'race_id']).copy() df_sorted['prev_3_race_time_avg'] = df_sorted.groupby('racer_id')['race_time'].rolling(window=3, min_periods=1).mean().shift(1).reset_index(level=0, drop=True)
正しくは、以下のように .transform() を用いてグループごとの操作として各グループに渡す。
df_sorted = df.sort_values(by=['racer_id', 'race_id']).copy() df_sorted['prev_3_race_time_avg'] = df_sorted.groupby('racer_id')['race_time'].transform(lambda x: x.rolling(window=3, min_periods=1).mean().shift(1))
ある1つのグループが複数行に分かれているときに1行にまとめる
- race_id
- racer_id
- race_time
からなるデータフレームがあるとする
data = {
'race_id': [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3],
'course_id': [1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6],
'racer_feature1': [60, 65, 70, 62, 68, 75, 58, 60, 65, 70, 72, 63, 57, 68, 61, 70, 75, 80]
}
df = pd.DataFrame(data)
これをレースごとに1つの行にして、特徴量をコースごとに並べたい。
Amplify GraphQLで[Int]型のフィールドでContainsオペレーターを利用する
結論
AWS Appsyncのスキーマで該当する型(ModelIntInput)を手動でアップデートし、contains: Intを追加する。
はじめに
AWS AmplifyではデータベースとしてGraphQLを利用することができ、ユーザはGraphQLスキーマを定義するだけでAppSyncやDynamoDBの設定をすることなく利用することができます。 しかし、クエリのfilterで、[Int]型のフィールドに対して、本来利用ができるはずのcontainsオペレータが利用ができず苦しんだので備忘録を残しておきます。
Version
"aws-amplify": "^5.3.3"
Example schema
以下のようなUserテーブルがschema.graphqlとして定義されているとします。
type User @model @auth(rules: [{ allow: public }]) { id: ID! favoriteNumbers: [Int]! }
ここで、
id: ユーザに一意に付与されるIDfavoriteNumbers: ユーザが好きな数字のリスト
とします。
こちらのスキーマ定義をamplify push apiすると、User tableに対するqueryやmutationを実行することができます。
Contains operator
ここで、User tableからUserのリストを取得することを考えます。 GraphQLクエリは以下のようになるでしょう。
const listUsers = /* GraphQL */ ` query ListUsers($filter: ModelUserFilterInput, $limit: Int, $nextToken: String) { listUsers(filter: $filter, limit: $limit, nextToken: $nextToken) { items { id favoriteNumbers } } } `;
ここで、「ある特定の数字が好きなユーザのリストを取得する」ことを考えます。すると、各レコードのfavoriteNumbersに格納されている[Int]型の値の中に特定のInt値が含まれているかどうかを確認する必要があります。
AmplifyでGraphQLを利用する時には裏側でDynamoDBを利用することになりますので、DynamoDBでこのような演算子が利用できるかを確認します。
Comparison operator and function reference - Amazon DynamoDB
こちらに、
A List that contains a particular element within the list.
とあることから、containsというオペレータを利用することで、[Int]に特定の要素が含まれているレコードを取得することができそうです。
しかし、上記のlistUsersを用いてクエリを実行しても、うまくfliterが効かないです。
以下は、ある特定の数字numをお気に入りにしているユーザの一覧を取得するための関数fetchUsersです。
import { API } from "aws-amplify"; const listUsers = /* GraphQL */ ` query ListUsers($filter: ModelUserFilterInput, $limit: Int, $nextToken: String) { listUsers(filter: $filter, limit: $limit, nextToken: $nextToken) { items { id favoriteNumbers } } } `; const fetchUsers = async (num)=> { const variables = { filter: { favoriteNumbers: { contains: num } }, }; const response = await API.graphql({ query: listUsers, variables: variables }); }
AppSync
AppSyncは、AWSフルマネージドサーバーレス GraphQL API サーバーです。
該当するAPIの「スキーマ」を確認し、フィールド favoriteNumbersに対応するスキーマを確認すると
input ModelIntInput {
ne: Int
eq: Int
le: Int
lt: Int
ge: Int
gt: Int
between: [Int]
attributeExists: Boolean
attributeType: ModelAttributeTypes
}
となっていることが分かります。どうやら、ここで利用可能なオペレータを定義しているようです。
ここでcontainsオペレータを手で追加してください。そうすればcontains オペレータを用いたfilterが有効になります。
input ModelIntInput {
ne: Int
eq: Int
le: Int
lt: Int
ge: Int
gt: Int
between: [Int]
contains: Int
attributeExists: Boolean
attributeType: ModelAttributeTypes
}
注意
このAppSyncスキーマはamplify push apiを実行するたびにリセットがされるため、containsオペレータを利用するためには毎回手動でAppSyncのスキーマを編集しなければいけません。
なお、Int型以外のString型に対しては自動でcontainsオペレータが追加されますので上記の操作は必要ありません。
「良いコード悪いコードで学ぶ設計入門」メモ
これ何
良いコード悪いコードで学ぶ設計入門を読んで大事だなと思ったことを忘れないようにメモしておく。
良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方:書籍案内|技術評論社
個人的にはリーダブルコードよりも読みやすく、全てのコードを書く人におすすめできる本だと思いました。
各章ごと
3:クラス設計
- クラスは単体で作動するように設計する
- 自己防衛責務がある
- ベストプラクティス
- よい設計パターン
大事なこと:データとデータ操作ロジックを一箇所にまとめておくこと(凝集性が高い状態)。必要な操作だけを後悔すること。
# example: 物体検出問題において正解データとなるbounding boxデータを表現するクラス class BoundingBox: def __init__(self, xmin: int, xmax: int, ymin: int, ymax: int, label: str): if not self._are_valid_positions(xmin, xmax, ymin, ymax): raise Exception("Invalid positions.") if len(label) == 0: raise Exception("Empty string is passed as a label name.") self.xmin = xmin self.xmax = xmax self.ymin = ymin self.ymax = ymax self.label = label return def _are_valid_positions(self, xmin: int, xmax: int, ymin: int, ymax: int) -> bool: # 座標のバリデーション if xmin < 0 or ymin < 0: return False if xmin >= xmax or ymin >= ymax: return False return True def get_bbox_area(self) -> int: # bboxの面積を返すメソッド # クラスに関連する処理はクラスのメソッドとして提供することで凝縮性を高める return (self.xmax - self.xmin) * (self.ymax - self.ymin) def slide_bbox(self, stride_x: int, stride_y: int): # bboxを移動するメソッド # データと同じ場所に操作するメソッドを定義することで凝集性を高める # 副作用を防ぐために、クラス変数を上書きするのではなく新しいインスタンスを作成する return BoundingBox( self.xmin + stride_x, self.xmax + stride_x, self.ymin + stride_y, self.ymax + stride_y, self.label )
4:不変の活用
- 再代入をできるだけ許さない
- 可変であることで生じること
6: 条件分岐
- 条件分岐が入れ子になっているといいことはない
- 早期returnは良いsolution
- switchが色々なところに実装されると、変更に弱いコードになる。
- switchするのは一箇所にまとめる
- 条件が増えていくのであればinterfaceとして共通する構造を定義するのがよい
- 種類ごとに切り替えたい機能をinterfaceのメソッドとして提供するのがよい
- interfaceの型で分岐処理をしたいときはinterface側に実装する
- フラグ引数(0ならこれ、1なら違う処理、みたいな関数の引数)
- 関数を分けましょう
7: コレクション
- 言語がコレクションに対する処理を提供している場合は自前で実装しない。
- ループ中に条件分岐が多くある場合には早期continueやbreakを活用する
- コレクションに関する実装が散らばってくるときにはFirst class collectionを検討する(カプセル化)
- コレクション型のインスタンス変数と、それらを不正状態から防御し正常に制御するためのメソッドを提供する。
@dataclass class Member: name: str hp: int # PartyはFirst Class Collectionとして機能する class Party: def __init__(self, members: List[Member]): self.members = members def add_member(self, new_member: Member) -> Party: # self.membersを上書きするのではなく、新しいPartyインスタンスを返却する new_members = copy.deepcopy(self.members) new_members.append(new_member) return Party(members=new_members)
8:密結合
- 単一責任の原則(クラスが担う責任はただ一つにするべき)を強く意識しておくことが疎結合性を担保するコツ
- 重複コードを恐れない。ほとんど同じコードであっても責務や概念が異なる場合にはコードを分割することは悪いことではない。
12:メソッド
- 他のクラスのインスタンス変数を変更しない。 変更するのは自身のインスタンス変数のみに絞る。
- コマンド・クエリ分離(Command-Query Separation:CQS)の原則を守る
- 状態の取得及び状態の変更のどちらかを責務とするメソッドにする。どちらも同時に行わない。
- 引数が多くなりそうなら別クラスにまとめることを考える
- 概念ごとにクラスに分割すれば引数が多くなりすぎることがなくなるはず
- 戻り値
- プリミティブ型で返すよりも独自の型を定義して返す方が安全
- エラーは戻り値で返すのではなく例外をthrowする
- 負数などでエラーを表現しない
14:リファクタリング
Unbiased learning to rank: introduction
The Web Conference 2020でのチュートリアル: Unbiased Learning to Rank: Counterfactual and Online Approaches
をCounterfactual LTRまで読んで、メモがてら日本語でスライドを作ったので置いておきます。 speakerdeck.com
ヒルベルト空間における凸射影定理・直交射影定理
ヒルベルト空間とは
完備性
点列の収束先が自身に含まれる空間 のことです.
つまり,ある空間が完備である,とは自身の中にある任意のコーシー列
の収束先
について,
が成り立つことです.
内積空間
内積を備えた空間を内積空間と呼びます. 内積 - Wikipedia
内積空間は内積から誘導されるノルムを持つノルム空間でもあり,そのノルムを距離関数として採用した距離空間でもあります.
ヒルベルト空間
完備なノルム空間はバナッハ空間と呼ばれ,完備な距離空間は単に完備距離空間と呼ばれます.
凸射影定理
ヒルベルト空間の空でない閉凸集合
について考えます.この時,
の中で,点
にもっとも近い点は唯一存在するというのが凸射影定理です.
主張1
の任意の点
に対して,
を満たす唯一の点
が存在する.
は
への距離射影・凸射影・または単に射影と呼ばれます.
主張2
また,に対して,
が成立する.
は
の中で
をもっともよく近似する点として,最良近似点と呼ばれる.
直交射影定理
ヒルベルト空間の空でない閉部分空間
について考えます.凸集合ならば部分空間でもあるので,凸射影定理は
についても成り立つことに注意してください.
この時,の中で,点
にもっとも近い点は唯一存在し,そのような点と
を結んだベクトルは
に直交する,というのが直交射影定理です.
主張1
に対して,
を満たす唯一の点が存在する.
主張2
また,に対して,
が成立する.は
への距離射影であるが,この性質から特別に直交射影と呼ばれる.
主張3
直交射影について,以下の一般化されたピタゴラスの定理が成立する.
直交分解
ここで,ヒルベルト空間の任意の点が,ある閉部分空間の1点と,その直交補空間の1点の和の形に一意に分解することができる,ことを紹介します.
以下では柄部分空間をとおきます.
直交補空間
の直交補空間として
を
のように定義すると,も閉部分空間となり,かつ
となります.
直交分解の一意性
は,
のように一意に分解可能です.
密度比推定としてのAdversarial Validation
Adversarial Validationとは
Adversarial Validationとは、主にKaggleの文脈で使われる言葉です。 TrainデータとTestデータの分布が大きく異なる時に、出来るだけTestデータでの密度が高いデータをTrainデータから選んでValidationデータとして使うための技術です。(適当にValidationを作るとPublic LBと相関が低いValidationデータを作ることになってしまう)
手順としては、
- trainデータに仮のラベル-1、testデータに仮のラベル+1をつける。
- trainデータとtestデータを分類する2値分類器を訓練する。(cross-validation等で)
- 先ほどの分類器にtrainデータを推論させ、trainデータの中でtestデータである確率が高いtopN件をValidationデータとして採用する。
というようなものです。
(参考)
密度比(density ratio)
ここで、次のような式で定義される密度比(density ratio) : を導入することにします。
また、密度比は、ベイズの定理を使って以下のように変形できます。
ここで、trainデータに仮のラベル-1、testデータに仮のラベル+1を振れば、 と
はちょうどtrainデータとtestデータを分類する2値分類器によって近似することができます。
また、
と
は定数で、それぞれtrainデータの数とtestデータの数の割合で近似できます。
結局、密度比は
となります。
NとMはそれぞれtrainデータの数、testデータの数です。また、はtrainデータとtestデータを分類する2値分類器が出力する、データ
のクラス+1(targetデータ)の確率です。
ここで重要なのは、密度比推定は結局2値分類問題として帰着できることです。
Density Ratio Estimation for KL Divergence Minimization between Implicit Distributions | Louis Tiao (わかりやすい説明がのっています)
密度比は
に対して単調増加関数で、
で
の値をとります。以下では簡単のため
を想定します。密度比は以下のような形をしています。

Adversarial Validationと密度比の関係
よって、Adversarial Validationが行なっているのは、密度比が高い点をtrainデータから抽出していることになります。
確かに、のようなデータはtestデータの密度が高く、trainデータの密度が低いデータで、「trainデータにはあまり現れないが、testデータにはよく現れるデータ」であると言え、Validationデータとしての価値はかなり高いです。
そして、付近の点はモデルがtrainデータ由来なのかtestデータ由来なのかを見分けることができなかったデータであり、こちらもValidationデータとしての価値があると思われます。
逆に、のようなデータは「trainデータにはよく現れるが、testデータにはあまり現れないデータ」であり、これをValidationデータとして利用するのは危険です。
GANからWasserstein GANへ
generative adversarial network(GAN)からWasserstein generative adversarial network(WGAN)への道の整理をします。 こちらを参考にしました:
目次 まず、確率密度関数の類似度をはかる2つの指標を導入します。 2つの確率密度関数 KL divergenceの性質 JS divergenceは2つの確率密度関数の類似度をはかるもう一つの指標です。また、範囲は JS divergenceはpとqに関して対称です。GANではこちらのJS divergenceによって GANは、現実のデータ集合が与えられたとき、それらに似たデータを生成することを目指します。 GANは2つのモデルからできています。 これらの2つのモデルが互いを見抜く・騙すように訓練されて、十分学習が進めばGeneratorが現実のデータと見分けがつかないようなデータを生成できるようになる、というわけです。欲しいのは良いGeneratorです。 ここで、 とします。 まず、Discriminatorは現実のデータを正しく本物だと識別してほしいです。つまり、
$$ \mathbb{E} _ {x \sim p _ r(x)} \left[ \log D(x) \right] $$
を最大化したいです。一方で、Generatorが生成したデータ 次に、Generatorに関しては生成したデータをDiscriminatorが本物だと誤分類させたいので、
$$ \mathbb{E} _ {z \sim p _ z(z)} \left[ \log \left( 1 - D(G(z) \right) \right] $$
を最小化したいです。 これらを組み合わせると、以下のようなmin-max lossになります。 Discriminatorの学習は、密度比推定と深い関係があります。密度比とは、2つの確率密度関数( $$ r(x) = \frac{p _ r(x)}{p _ g(x)} $$ です。 密度比を推定する方法 現実のデータ集合に仮にラベル+1を割り当て、Generatorが生成したデータに仮にラベル-1を割り当てることにします。
この時、ラベルがgivenという条件下のもとでデータの分布を表すことができて、 $$ p _ r(x) = p (x | y = +1) $$
$$ p _ g(x) = p (x | y = -1) $$ です。 密度比は、ベイズの定理から となります。 似たようなことは以下の論文にも記述されています。 [1610.02920] Generative Adversarial Nets from a Density Ratio Estimation Perspective こちらはDiscriminatorが密度比推定を行なっていることに注目し、f-divergenceを最小化するGANを提案しています。 先ほどの目的関数を最大化するDiscriminatorの最適解をまず求めてみます。
$$ L(G,D) = \int \left( p _ {r} (x) \log(D(x)) + p _ {g}(x) \log(1 - D(x)) \right) dx $$ とかけます。今我々の興味は $$ \hat{x}=D(x), A = p _ r(x), B = p _ {g}(x) $$とおきます。 すると、 $$ f(\hat{x}) = A\log \hat{x} + B \log (1- \hat{x}) $$
とかけて、 $$ \frac{d f(\hat{x})}{d\hat{x}} = \frac{A-(A+B)\hat{x}} {\hat{x} (1- \hat{x})} $$
となります。これを0とおくと、最適な $$ D^{\ast}(x) = \frac{A}{A+B} = \frac{ p _ r(x)} { p _ r(x)+p _ {g}(x)} $$ になります。 さらに、Generatorが最適に学習すれば、 DiscriminatorとGeneratorが最適な学習をすると $$ L(G^{\ast}, D^{\ast}) = \int \left( p _ {r} (x) \log(D^{\ast}(x)) + p _ {g}(x) \log(1 - D^{\ast}(x)) \right) dx \tag{2}\\
= \log \frac{1}{2} \int p _ {r} (x) dx + \log \frac{1}{2} \int p _ {g} (x) dx = -2 \log 2 $$ なお(2)は と変形できて、 と表せます。
この式から、Discriminatorが最適である時、GANの目的関数 ナッシュ均衡を達成するのが困難 low dimensional supports 勾配消失 mode collapse 適切な評価指標が存在しない Wasserstein distanceとは、JS divergenceと同じように2つの確率密度関数の距離をはかる指標です。Wasserstein distanceはEarth Mover's distanceとも呼ばれ、短くEM distanceと呼ばれることもあります。 Wasserstein distanceは、ある確率密度関数を動かしてもう一つの確率密度関数に一致させるときの最小コストです。
以下では、確率密度を「土」として表現し、「土」の最適な輸送としてWasserstein distanceを考えます。 2つの確率密度関数 が成り立ちます。(地点 逆に、 も成り立ちます。(地点 土の量に動かす距離 候補となる土の動かし方戦略 確率密度関数が低次元かつ2つの確率密度関数に重なりが場合でもWasserstein distanceはより滑らかな表現を提供してくれます。
例えば、以下のような2つの2次元の確率密度 一方 このように、KL divergenceは2つの確率密度に重なりがない場合 Wasserstein distanceはKantorovich-Rubinstein双対性を使って、 $$ W(p _ r, p _ g) = \frac{1}{K} \sup _ {||f|| _ {L} \leq K} \mathbb{E} _ {x \sim p _ {r}} [f(x)] - \mathbb{E} _ {x \sim p _ {g}} [f(x)] $$ と変換することができます。 Wasserstein distanceの ある定数 任意の場所で微分可能な関数はリプシッツ連続です。なぜなら WGAN全体としては、こちらのLossを最小化することを目指します。 ここで重要なのが、 Wasserstein lossのGeneratorのパラメータ $$ \frac{\partial}{\partial \theta} L(p _ {r}, p _ {g}) = \frac{\partial}{\partial \theta} - \mathbb{E} _ {z \sim p _ {z}} [f _ {w}(g _ {\theta}(z))] $$ であり、こちらはサンプル近似によって $$ \frac{\partial}{\partial \theta} - \mathbb{E} _ {z \sim p _ {z}} [f _ {w}(g _ {\theta}(z))] = \frac{1}{M} \sum_{m=1}^{M} \frac{\partial}{\partial \theta} - f _ {w}(g _ {\theta}(z_m)) $$ と近似できます。 よって、WGAN全体の学習は Discriminatorのパラメータ Discriminatorのパラメータ Generatorのパラメータ 以上を繰り返します。
Kullback–Leibler Divergence (KL divergence) と Jensen–Shannon Divergence (JS divergence)
Kullback–Leibler Divergence
と
を考えます。KL divergenceはpがqからどれだけ異なるか、をはかる指標です。
)です。すなわち、距離として使うことはできません。
がほぼ0で、
が0でない場所では
の影響が無視されます。
Jensen–Shannon Divergence
]です。
と
の類似度を測ります。
GAN
を入力として受け取り、人工的なデータを出力します。その際、現実のデータの分布と似た分布を学習します。つまり、Discriminatorを騙すような(人工的なデータではあるが、現実のデータだと識別させるような)データを生成することを目指します。

: ノイズzの分布(一様分布を使うことが多いです)
: Generatorが生成するデータの分布
: 現実のデータの分布
: Discriminatorが、入力されたデータ
を実際のデータだと判断する確率
: Generatorが、入力されたノイズ
から生成するデータ
GANの目的関数
を正しく偽物だと識別して欲しいので、
$$ \mathbb{E} _ {z \sim p _ z(z)} \left[ \log \left( 1 - D(G(z) \right) \right] $$
を最大化して欲しいです。
密度比推定との関連
と
)の比で、
と
は任意の2値分類器で求めることができて、それはまさにDiscriminatorです。
はデータ数の比で近似出来ます。
Discriminatorの損失にはBinary Cross Entropyを用いればよくて、それを変形すると(1)の目的関数になります。
つまり、結果としてDiscriminatorの学習は
と
の密度比を推定するように行われることになります。
Discriminatorの最適解
は期待値の部分を書き直せば
を最大化するような
なので、
について微分すれば
は
は
に近しいものになり、
のような状況では
になります。これは、完璧なGeneratorができれば、Discriminatorはもはや機能しなくなる、ということです。
What is global optimal?
、
になることは上で確認しました。
この時、GAN のlossは、
に対応します。
GANの目的関数が意味すること
と
の間のJS divergenceは、
は
と
の間のJS divergenceを定量化します。なお、Generatorが最適である時、JS divergenceは0になって、
と一致します。
GANの問題点
Wasserstein GAN (WGAN)
Wasserstein distance
と
のWasserstein distanceは以下のように与えられます。
は下限で、wasserstein distanceを求めること自体が最適化問題になっています。
は
のある地点
から
のある地点
に動かす土の量です。正確には地点
から、全土の量
のうちどれだけを地点
へ輸送するか、という量です。
土を動かし、
を
に一致させることから、直ちに
へ動かされた土の量を
について和をとると動かし終わった土の量
と一致するはず)
から動かされた土の量を
について和を取るともともと
にあった土の量
と一致するはず)
をかけることでコスト
を算出します。
全ての
についてコストの平均をとると、
のうち、総コストがもっとも小さいものをとればwasserstein distanceが求まります。
Wasserstein GAN がJS divergenceとKL divergenceよりも良い理由
と
を考えます。Pのx成分は0に固定し、y成分は[0,1]の一様分布に従います。一方でQのx成分は
に固定しyは[0,1]の一様分布に従います。

の時
の時、PとQは
で完全に重なっていて、
= 0
に発散してしまいます。
JS divergenceは
で突然ジャンプし、微分不可能になってしまいます。
Wasserstein distanceは
の変化に対して滑らかで、勾配降下法で学習する場合に安定すると考えられます。
GANの損失としてのWasserstein distance
Lipschitz 連続性
には、
という制約がついています。つまり
はK-リプシッツ連続である必要があります。
関数
は以下の条件を満たす時にK-リプシッツ連続です。
が存在して、全ての
に対して、
$$ |f(x _ {1} - f(x _ {2})| \leq K |x _ {1} - x _ {2}| $$
これは直感的には、任意の区間で傾きがある値
で抑えられるということを意味します。(
はリプシッツ定数と呼ばれます)
にはboundが存在するからです。
しかし、リプシッツ連続だからと言って任意の場所で微分可能である訳ではありません。例えば、
は原点で微分不可能です。
Wasserstein loss
がパラメータ
をもつK-リプシッツ関数とします。Wasserstein GANでは、Discriminatorは良い
を求めます。WGANの損失としては
(現実のデータの分布)と
(Generatorが生むデータの分布)間のWasserstein distanceを採用します。つまり、学習が進むにつれてGeneratorは現実のデータの分布に近いデータの分布を出力できるようになります。
が
で近似されています。
のK-リプシッツ性を維持する方法です。簡単かつ強力な方法として、重み
を更新した後、
を[-0.01, 0.01]といった小さな範囲でクリップします。
それにより、パラメータ空間
は小さくなり、
の傾きはboundで抑えられます。WGANの著者らは、clipingよりも良いK-リプシッツ性を維持する方法があるはずだ、とも述べています。
Wasserstein GANの学習
に関する微分は、
はバッチサイズです。
に関して、WGANのLossを微分し、Wasserstein distanceの良い近似を求めるように
を更新する
のリプシッツ連続性を保つため、クリッピングを行う
に関して、Lossを微分し、Wasserstein distanceを小さくするように
を更新する
トポロジカルソートについて
こちらに参加したのですが,D問題で「トポロジカルソート」というテクニックが必要とのことで,調べました.
トポロジカルソート(英: topological sort)とは、グラフ理論において、有向非巡回グラフ(英: directed acyclic graph, DAG)の各ノードを順序付けして、どのノードもその出力辺の先のノードより前にくるように並べることである。有向非巡回グラフは必ずトポロジカルソートすることができる。
とのこと.
非巡回グラフというのは、有向サイクル(矢印の向きにたどってノードを一周できるようなサイクル)が無いことを表します.
わかりやすく言えば, 任意の矢印に対して,
矢印が出ているノードの番号 < 矢印が入ってくるノードの番号
という関係が成り立つようにノードに番号を振り分けることができる, ということですかね。
証明は:cited from
有向非巡回グラフ(DAG)の意味とトポロジカルソート - 具体例で学ぶ数学

直感的でわかりやすい。