Open In Colab   Open in Kaggle

チュートリアル 2: ディープラーニング思考 1: コスト関数

第2週、第2日目: 畳み込みネットワークとディープラーニング思考

Neuromatch Academyによる

コンテンツ制作者: Konrad Kording, Lyle Ungar, Ashish Sahoo

コンテンツレビュアー: Kelson Shilling-Scrivo

コンテンツ編集者: Kelson Shilling-Scrivo

制作編集者: Gagana B, Spiros Chavlis


チュートリアルの目標

このチュートリアルでは、ディープラーニングの実践者のように考え、さまざまなシナリオに対してコスト関数を設計する方法を練習します。

このチュートリアルの終了時には、以下のことがよりよくできるようになります:

# @title Tutorial slides
from IPython.display import IFrame
link_id = "szcjn"
print(f"If you want to download the slides: https://osf.io/download/{link_id}/")
IFrame(src=f"https://mfr.ca-1.osf.io/render?url=https://osf.io/{link_id}/?direct%26mode=render%26action=download%26mode=render", width=854, height=480)

セットアップ

# @title Install and import feedback gadget


from vibecheck import DatatopsContentReviewContainer
def content_review(notebook_section: str):
    return DatatopsContentReviewContainer(
        "",  # No text prompt
        notebook_section,
        {
            "url": "https://pmyvdlilci.execute-api.us-east-1.amazonaws.com/klab",
            "name": "neuromatch_dl",
            "user_key": "f379rz8y",
        },
    ).render()


feedback_prefix = "W2D2_T2"

セクション 1: ディープラーニング思考のイントロダクション

# @title Video 1: Intro to DL Thinking
from ipywidgets import widgets
from IPython.display import YouTubeVideo
from IPython.display import IFrame
from IPython.display import display


class PlayVideo(IFrame):
  def __init__(self, id, source, page=1, width=400, height=300, **kwargs):
    self.id = id
    if source == 'Bilibili':
      src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'
    elif source == 'Osf':
      src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'
    super(PlayVideo, self).__init__(src, width, height, **kwargs)


def display_videos(video_ids, W=400, H=300, fs=1):
  tab_contents = []
  for i, video_id in enumerate(video_ids):
    out = widgets.Output()
    with out:
      if video_ids[i][0] == 'Youtube':
        video = YouTubeVideo(id=video_ids[i][1], width=W,
                             height=H, fs=fs, rel=0)
        print(f'Video available at https://youtube.com/watch?v={video.id}')
      else:
        video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,
                          height=H, fs=fs, autoplay=False)
        if video_ids[i][0] == 'Bilibili':
          print(f'Video available at https://www.bilibili.com/video/{video.id}')
        elif video_ids[i][0] == 'Osf':
          print(f'Video available at https://osf.io/{video.id}')
      display(video)
    tab_contents.append(out)
  return tab_contents


video_ids = [('Youtube', 'iEqd0MY5pxI'), ('Bilibili', 'BV1hL4y1P73s')]
tab_contents = display_videos(video_ids, W=854, H=480)
tabs = widgets.Tab()
tabs.children = tab_contents
for i in range(len(tab_contents)):
  tabs.set_title(i, video_ids[i][0])
display(tabs)
# @title Submit your feedback
content_review(f"{feedback_prefix}_Intro_to_DL_Thinking_Video")

このチュートリアルは他と少し異なります — コーディングはありません!代わりに、ニューラルネットワークを使いたいさまざまなシナリオについての短いビネット(小話)を視聴します。このチュートリアルはコスト関数に焦点を当てていますが、後のコースで扱うチュートリアルは似ていますがアーキテクチャ設計に焦点を当てます。

以下の各セクションは、LyleまたはKonradが特定の問題に対してニューラルネットワークをどのように設定するかを考えているビネットから始まります。視聴しながら彼らにどんな質問をしたいか考えてみてください。その後、LyleやKonradがどんな質問をしているかに注目してください。あなたがしたかった質問と同じでしたか?彼らの質問はどのように状況を素早く明確にするのに役立ちましたか?

グループで協力して各例のコスト関数を考え出す作業をします。途中でヒントも利用可能です。これは難しいかもしれません — 実世界のディープラーニングはしばしばそうです!だから最善を尽くしてください、解決に至らなくても落胆しないでください — 試行錯誤の過程から多くを学べます。

あなたはすでに深層ニューラルネットワークのコスト関数(目的関数や損失関数とも呼ばれます)を見たことがあります — 勾配降下法を行いニューラルネットワークを訓練するために必要です。実は、どのコスト関数を最小化するかは非常に重要です — それはネットワークの成功を定義する方法だからです。成功を良い形で定義したいのです!そしてコスト関数は万能ではありません — ニューラルネットワークに何をさせたいかに応じて慎重に選ぶ必要があります — 以下のシナリオでそれがわかります。


セクション 2: ニューロンのためのコスト関数

# @title Video 2: Spiking Neuron Predictions Vignette
from ipywidgets import widgets
from IPython.display import YouTubeVideo
from IPython.display import IFrame
from IPython.display import display


class PlayVideo(IFrame):
  def __init__(self, id, source, page=1, width=400, height=300, **kwargs):
    self.id = id
    if source == 'Bilibili':
      src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'
    elif source == 'Osf':
      src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'
    super(PlayVideo, self).__init__(src, width, height, **kwargs)


def display_videos(video_ids, W=400, H=300, fs=1):
  tab_contents = []
  for i, video_id in enumerate(video_ids):
    out = widgets.Output()
    with out:
      if video_ids[i][0] == 'Youtube':
        video = YouTubeVideo(id=video_ids[i][1], width=W,
                             height=H, fs=fs, rel=0)
        print(f'Video available at https://youtube.com/watch?v={video.id}')
      else:
        video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,
                          height=H, fs=fs, autoplay=False)
        if video_ids[i][0] == 'Bilibili':
          print(f'Video available at https://www.bilibili.com/video/{video.id}')
        elif video_ids[i][0] == 'Osf':
          print(f'Video available at https://osf.io/{video.id}')
      display(video)
    tab_contents.append(out)
  return tab_contents


video_ids = [('Youtube', 'CC4gMRrE31g'), ('Bilibili', 'BV1Jt4y187UU')]
tab_contents = display_videos(video_ids, W=854, H=480)
tabs = widgets.Tab()
tabs.children = tab_contents
for i in range(len(tab_contents)):
  tabs.set_title(i, video_ids[i][0])
display(tabs)
# @title Submit your feedback
content_review(f"{feedback_prefix}_Spiking_Neuron_Predictions_Video")
# @title Video 3: Spiking Neuron Predictions Set-up
from ipywidgets import widgets
from IPython.display import YouTubeVideo
from IPython.display import IFrame
from IPython.display import display


class PlayVideo(IFrame):
  def __init__(self, id, source, page=1, width=400, height=300, **kwargs):
    self.id = id
    if source == 'Bilibili':
      src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'
    elif source == 'Osf':
      src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'
    super(PlayVideo, self).__init__(src, width, height, **kwargs)


def display_videos(video_ids, W=400, H=300, fs=1):
  tab_contents = []
  for i, video_id in enumerate(video_ids):
    out = widgets.Output()
    with out:
      if video_ids[i][0] == 'Youtube':
        video = YouTubeVideo(id=video_ids[i][1], width=W,
                             height=H, fs=fs, rel=0)
        print(f'Video available at https://youtube.com/watch?v={video.id}')
      else:
        video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,
                          height=H, fs=fs, autoplay=False)
        if video_ids[i][0] == 'Bilibili':
          print(f'Video available at https://www.bilibili.com/video/{video.id}')
        elif video_ids[i][0] == 'Osf':
          print(f'Video available at https://osf.io/{video.id}')
      display(video)
    tab_contents.append(out)
  return tab_contents


video_ids = [('Youtube', 'vJ7MixhmDh8'), ('Bilibili', 'BV1X94y1y7SH')]
tab_contents = display_videos(video_ids, W=854, H=480)
tabs = widgets.Tab()
tabs.children = tab_contents
for i in range(len(tab_contents)):
  tabs.set_title(i, video_ids[i][0])
display(tabs)
# @title Submit your feedback
content_review(f"{feedback_prefix}_Spiking_Neuron_Predictions_SetUp_Video")

神経科学者のKonradは、誰かがバイクに乗っているときにその人の運動皮質のニューロンが何をしているかを予測したいと考えています。

Lyleとの議論の結果、加速度、角度、ブレーキ、傾きの度合いなど、バイク乗車の12のパラメータに関するデータがあることがわかりました。これらの入力は時間的にかなり滑らかで、例えばバイクの角度は通常100ミリ秒の間に大きく変化しません。

また、運動皮質のNN個のニューロンのスパイクのタイミングデータも記録されています。基礎となる発火率は滑らかですが、毎ミリ秒のスパイクはランダムかつ独立しています。つまり、短い区間内のスパイク数は、その区間の基礎発火率λ\lambdaを用いたポアソン分布でモデル化できると仮定できます。

ニューロンiiに対して、基礎発火率λi\lambda_{i}が与えられたときに区間内でkik_{i}回のスパイクが観測される確率は:

f(ki:λi)=Pr(X=ki)=λikieλiki!\mathcal{f(k_{i}:λ_{i})} = \mathcal{Pr(X=k_{i})} = \frac {\lambda_{i}^{k_{i}}e^{-\lambda_{i}}}{k_{i}!}

このポアソン分布は、ニューロンのスパイクを良くモデル化したい場合に関連してきます。

考えてみよう!1: ニューロン活動を予測するコスト関数の設計

これまでの情報を踏まえて、Konradがバイク乗車パラメータからニューロン活動を予測するために訓練しているニューラルネットワークのコスト関数をどのように設計しますか?すべてのNN個のニューロンの活動を予測していることを忘れずに。数式を書いてみてください!

グループで話し合ってください。行き詰まったら、以下のヒントを一つずつ開けてみてください。ただし次のヒントを開ける前に十分に議論する時間を取ってください!あなたは今まさに本物のディープラーニング科学者です。答えは簡単ではありません。

ヒント 1 を見るにはここをクリック

スパイクのタイムスタンプがあります。50ミリ秒のビンに分ける必要があります。各ニューロンiiと時間ビンttに対してスパイク数ki,tk_{i, t}が得られます。ニューラルネットワークは何を予測するでしょうか?

ヒント 2 を見るにはここをクリック

各ビンに対して、ニューラルネットワークモデルを使ってλi,t\lambda_{i,t}、すなわちニューロンiiのその時間ビンttで期待されるスパイク数の推定値を予測できます。ネットワークの入力は、該当時間(および場合によっては過去の時間)のバイク乗車の関連パラメータです。

ヒント 3 を見るにはここをクリック

λi,t\lambda_{i,t}(モデルの予測)とki,tk_{i, t}(データ)を関連付ける数式が必要です。λi,t\lambda_{i,t}を変化させてこの数式の値を最小化または最大化することで予測が良くなります。λi,t\lambda_{i,t}ki,tk_{i, t}の関係で既に知っていることは何でしょう?

それがわかったら、すべてのニューロンと時間ビンを含めるにはどう拡張しますか?

ヒント 4 を見るにはここをクリック

スパイクは毎ミリ秒ランダムかつ独立なので、ビンは独立とみなせます。

解答を見るにはここをクリック

まず、スパイクのタイミングデータを50ミリ秒の時間ビンごとのスパイク数に変換します。これにより、各ニューロンiiと時間ビンttに対してki,tk_{i,t}が得られます。

スパイクはポアソン分布に従うと仮定します。つまり、基礎発火率λi,t\lambda_{i,t}が与えられたときにスパイク数ki,tk_{i,t}が観測される確率は次の式で表されます:

f(ki,t:λi,t)=Pr(X=ki,t)=λi,tki,teλi,tki,t!\mathcal{f(k_{i,t}:\lambda_{i,t})} = \mathcal{Pr}(X=k_{i,t}) = \frac {\lambda_{i,t}^{k_{i,t}}e^{-\lambda_{i,t}}}{k_{i,t}!}

これは予測をできるだけ良くするために最適化すべき良い指標です!ニューラルネットワークの予測する基礎発火率に対して実際に観測されたスパイク数が高確率であることを目指します。

後で負の値に変換して最小化問題にします。まずはすべてのニューロンと時間ビンを含めて拡張しましょう。

スパイクは毎ミリ秒ランダムかつ独立なので、各時間ビンは独立とみなせます。確率論から、独立事象の集合(すべてのスパイク数)の確率は各事象の確率の積で計算できます。したがって、ニューラルネットワークの予測に対してすべてのデータが観測される確率は、すべてのki,tk_{i,t}の確率の積です:

\begin{align}
\mathcal{Pr}(\text{all_data}) &= i=1Nt=1Pr(X=ki,t)\prod_{i=1}^{N}\prod_{t=1}^\top \mathcal{Pr}(X=k_{i,t})\
&= \prod_{i=1}^{N}\prod_{t=1}^\top \frac {\lambda_{i,t}^{k_{i,t}}e^{-\lambda_{i,t}}}{k_{i,t}!}
\end{align}

これは尤度(likelihood)とも呼ばれます!

数値計算の理由から、最小化や最大化には通常尤度の対数を使います。上の式を対数尤度に変換すると:

\begin{align}
log likelihood\text{log likelihood} &= i=1Nt=1log(Pr(X=ki,t)\sum_{i=1}^N\sum_{t=1}^\top \text{log}(\mathcal{Pr}(X=k_{i,t}) \
&= i=1Nt=1ki,tlog(λi,t)λi,tlog(ki,t\sum_{i=1}^N\sum_{t=1}^\top k_{i,t} \text{log}(\lambda_{i,t}) - \lambda_{i,t} - \text{log}(k_{i,t}!)
\end{align}

最後に、最大化問題を最小化問題に変えるために負の値にします:

negative log likelihood=i=1Nt=1ki,tlog(λi,t)+λi,t+log(ki,t!)\text{negative log likelihood} = \sum_{i=1}^N\sum_{t=1}^\top - k_{i,t} \text{log}(\lambda_{i,t}) + \lambda_{i,t} + \text{log}(k_{i,t}!)
# @title Submit your feedback
content_review(f"{feedback_prefix}_Designing_a_cost_function_to_predict_neural_activities_Discussion")
# @title Video 4: Spiking Neurons Wrap-up
from ipywidgets import widgets
from IPython.display import YouTubeVideo
from IPython.display import IFrame
from IPython.display import display


class PlayVideo(IFrame):
  def __init__(self, id, source, page=1, width=400, height=300, **kwargs):
    self.id = id
    if source == 'Bilibili':
      src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'
    elif source == 'Osf':
      src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'
    super(PlayVideo, self).__init__(src, width, height, **kwargs)


def display_videos(video_ids, W=400, H=300, fs=1):
  tab_contents = []
  for i, video_id in enumerate(video_ids):
    out = widgets.Output()
    with out:
      if video_ids[i][0] == 'Youtube':
        video = YouTubeVideo(id=video_ids[i][1], width=W,
                             height=H, fs=fs, rel=0)
        print(f'Video available at https://youtube.com/watch?v={video.id}')
      else:
        video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,
                          height=H, fs=fs, autoplay=False)
        if video_ids[i][0] == 'Bilibili':
          print(f'Video available at https://www.bilibili.com/video/{video.id}')
        elif video_ids[i][0] == 'Osf':
          print(f'Video available at https://osf.io/{video.id}')
      display(video)
    tab_contents.append(out)
  return tab_contents


video_ids = [('Youtube', 'fb6A03B2U5g'), ('Bilibili', 'BV1K94y117rH')]
tab_contents = display_videos(video_ids, W=854, H=480)
tabs = widgets.Tab()
tabs.children = tab_contents
for i in range(len(tab_contents)):
  tabs.set_title(i, video_ids[i][0])
display(tabs)
# @title Submit your feedback
content_review(f"{feedback_prefix}_Spiking_Neurons_WrapUp_Video")

上のビデオで言及されている論文をチェックしてください:

(ボーナス)考えてみよう!: 非ポアソン型ニューロン

時間があれば以下を議論してください。スパイク分布は完全にポアソン型とは言えません。コスト関数の良い代替案を見つけてみましょう。

# @title Submit your feedback
content_review(f"{feedback_prefix}_NonPoisson_neurons_Bonus_Discussion")

セクション 3: ANNはどのようにして不確実性を知ることができるか

# @title Video 5: ANN Uncertainty Vignette
from ipywidgets import widgets
from IPython.display import YouTubeVideo
from IPython.display import IFrame
from IPython.display import display


class PlayVideo(IFrame):
  def __init__(self, id, source, page=1, width=400, height=300, **kwargs):
    self.id = id
    if source == 'Bilibili':
      src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'
    elif source == 'Osf':
      src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'
    super(PlayVideo, self).__init__(src, width, height, **kwargs)


def display_videos(video_ids, W=400, H=300, fs=1):
  tab_contents = []
  for i, video_id in enumerate(video_ids):
    out = widgets.Output()
    with out:
      if video_ids[i][0] == 'Youtube':
        video = YouTubeVideo(id=video_ids[i][1], width=W,
                             height=H, fs=fs, rel=0)
        print(f'Video available at https://youtube.com/watch?v={video.id}')
      else:
        video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,
                          height=H, fs=fs, autoplay=False)
        if video_ids[i][0] == 'Bilibili':
          print(f'Video available at https://www.bilibili.com/video/{video.id}')
        elif video_ids[i][0] == 'Osf':
          print(f'Video available at https://osf.io/{video.id}')
      display(video)
    tab_contents.append(out)
  return tab_contents


video_ids = [('Youtube', 'b2N2OJ2u4AM'), ('Bilibili', 'BV1UN4y1u7Ws')]
tab_contents = display_videos(video_ids, W=854, H=480)
tabs = widgets.Tab()
tabs.children = tab_contents
for i in range(len(tab_contents)):
  tabs.set_title(i, video_ids[i][0])
display(tabs)
# @title Submit your feedback
content_review(f"{feedback_prefix}_ANN_Uncertainty_Vignette_Video")
# @title Video 6: ANN Uncertainty Set-up
from ipywidgets import widgets
from IPython.display import YouTubeVideo
from IPython.display import IFrame
from IPython.display import display


class PlayVideo(IFrame):
  def __init__(self, id, source, page=1, width=400, height=300, **kwargs):
    self.id = id
    if source == 'Bilibili':
      src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'
    elif source == 'Osf':
      src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'
    super(PlayVideo, self).__init__(src, width, height, **kwargs)


def display_videos(video_ids, W=400, H=300, fs=1):
  tab_contents = []
  for i, video_id in enumerate(video_ids):
    out = widgets.Output()
    with out:
      if video_ids[i][0] == 'Youtube':
        video = YouTubeVideo(id=video_ids[i][1], width=W,
                             height=H, fs=fs, rel=0)
        print(f'Video available at https://youtube.com/watch?v={video.id}')
      else:
        video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,
                          height=H, fs=fs, autoplay=False)
        if video_ids[i][0] == 'Bilibili':
          print(f'Video available at https://www.bilibili.com/video/{video.id}')
        elif video_ids[i][0] == 'Osf':
          print(f'Video available at https://osf.io/{video.id}')
      display(video)
    tab_contents.append(out)
  return tab_contents


video_ids = [('Youtube', 'Reh-gNiOwkQ'), ('Bilibili', 'BV1B34y1W7F8')]
tab_contents = display_videos(video_ids, W=854, H=480)
tabs = widgets.Tab()
tabs.children = tab_contents
for i in range(len(tab_contents)):
  tabs.set_title(i, video_ids[i][0])
display(tabs)
# @title Submit your feedback
content_review(f"{feedback_prefix}_ANN_Uncertainty_SetUp_Video")

ライルは、自分の予測に対する不確実性の尺度を持つ人工ニューラルネットワークを構築したいと考えています。彼はニューラルネットワークに、予測・推定値とそれに対する不確実性、つまり標準偏差の測定値を出してほしいと思っています。

例えば、ライルは化学分子中の原子の位置を様々な入力に基づいて推定したいとします。彼は位置の推定値と分散の推定値を得たいのです。ただし、ニューラルネットワークは一度に1つのデータ点だけで訓練するわけではありません。彼はN個のデータ点(入力と原子位置のペア)を受け取るコスト関数を求めています。

ここで、ガウス分布を使ってライルを助けられるかもしれません:

g(x)=1σ2πexp(12(xμ)2σ2)g(x) = \frac{1}{\sigma\sqrt{2\pi}} \text{exp} \left( -\frac{1}{2}\frac{(x-\mu)^2}{\sigma^2} \right)

考えよう!2: 不確実性を測るコスト関数の設計

あなたが知っていることを踏まえて、ライルが訓練しているニューラルネットワークが推定値とその不確実性を得られるようなコスト関数をどう設計しますか?数式を書いてみてください!

グループで話し合ってください。もし行き詰まったら、以下のヒントを一つずつ開けてみてください。ただし、次のヒントを開ける前に十分に議論する時間を取りましょう!あなたは今、真のディープラーニング科学者です。答えは簡単ではありません。

ヒント1はこちらをクリック

ガウスの式を見てみましょう。真の位置はどこですか?位置の推定はどこにありますか?不確実性はどこにありますか?

ニューラルネットワークには、入力に対して1つのデータ点(記録された位置)に対して何を予測してほしいですか?

ヒント2はこちらをクリック

セクション2で学んだことのうち、ここで使えそうなことは何ですか?

ヒント3はこちらをクリック

セクション2では、確率から負の対数尤度へ変換してコスト関数を作ることを学びました。

解答はこちらをクリック

与えられた入力セットに対して、ニューラルネットワークは原子の位置の推定値とその推定の不確実性を予測してほしいです。標準偏差は不確実性の優れた尺度なので、位置の平均と標準偏差を予測できます(通常の平均のみの予測よりも良いです)。

では、平均と標準偏差を含むコスト関数をどう設計するか?位置に対してガウス分布を仮定できます。ニューラルネットワークはそのガウス分布の平均(位置の推定)と標準偏差(不確実性の尺度)を入力に基づいて予測します。

これが分かれば、セクション2でスパイキングニューロンに対して行ったアプローチと非常に似ています。データ点iiに対して、ニューラルネットワークは入力から位置の平均μi\mu_iと標準偏差σi\sigma_iを予測します。次に、これらの予測に基づいて実際の記録位置xix_iが観測される確率を計算できます:

g(x)=1σ2πexp(12(xiμi)2σi2)g(x) = \frac{1}{\sigma\sqrt{2\pi}} \text{exp}\left( -\frac{1}{2}\frac{(x_i-\mu_i)^2}{\sigma_i^2} \right)

原子の位置は各データ点で独立なので、全体の尤度は個々のデータ点の確率の積で求められます。

likelihood=i=1N1σ2πexp(12(xiμi)2σi2)\text{likelihood} = \prod_{i=1}^N\frac{1}{\sigma\sqrt{2\pi}} \text{exp}\left( -\frac{1}{2}\frac{(x_i-\mu_i)^2}{\sigma_i^2} \right)

そして、前回と同様に数値計算のために対数を取り、負の対数尤度に変換します:

negative log likelihood=i=1Nlog(1σ2πexp(12(xiμi)2σi2))\text{negative log likelihood} = \sum_{i=1}^N \text{log} \left( \frac{1}{\sigma\sqrt{2\pi}} \text{exp}\left( -\frac{1}{2}\frac{(x_i-\mu_i)^2}{\sigma_i^2} \right) \right)

ニューラルネットワークのパラメータを変えてμi\mu_iσi\sigma_iをこの式を最小化するように予測させれば、(おそらくかなり正確な)位置の予測とその不確実性を得られます!

# @title Submit your feedback
content_review(f"{feedback_prefix}_ANN_Uncertainty_Discussion")
# @title Video 7: ANN Uncertainty Wrap-up
from ipywidgets import widgets
from IPython.display import YouTubeVideo
from IPython.display import IFrame
from IPython.display import display


class PlayVideo(IFrame):
  def __init__(self, id, source, page=1, width=400, height=300, **kwargs):
    self.id = id
    if source == 'Bilibili':
      src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'
    elif source == 'Osf':
      src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'
    super(PlayVideo, self).__init__(src, width, height, **kwargs)


def display_videos(video_ids, W=400, H=300, fs=1):
  tab_contents = []
  for i, video_id in enumerate(video_ids):
    out = widgets.Output()
    with out:
      if video_ids[i][0] == 'Youtube':
        video = YouTubeVideo(id=video_ids[i][1], width=W,
                             height=H, fs=fs, rel=0)
        print(f'Video available at https://youtube.com/watch?v={video.id}')
      else:
        video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,
                          height=H, fs=fs, autoplay=False)
        if video_ids[i][0] == 'Bilibili':
          print(f'Video available at https://www.bilibili.com/video/{video.id}')
        elif video_ids[i][0] == 'Osf':
          print(f'Video available at https://osf.io/{video.id}')
      display(video)
    tab_contents.append(out)
  return tab_contents


video_ids = [('Youtube', 'QBKAFRaC8SY'), ('Bilibili', 'BV1zv4y1M7C8')]
tab_contents = display_videos(video_ids, W=854, H=480)
tabs = widgets.Tab()
tabs.children = tab_contents
for i in range(len(tab_contents)):
  tabs.set_title(i, video_ids[i][0])
display(tabs)
# @title Submit your feedback
content_review(f"{feedback_prefix}_ANN_Uncertainty_WrapUp_Video")

上記のビデオで紹介された論文をチェックしましょう:

(ボーナス)考えよう!: 負の標準偏差

もし標準偏差が負の値になると、負の対数尤度は負の数の対数を取ることになり計算が失敗します。ニューラルネットワークの訓練中にこれを避けるためにはどうすればよいでしょうか?

# @title Submit your feedback
content_review(f"{feedback_prefix}_Negative_standard_deviations_Bonus_Discussion")

セクション4: 顔の埋め込み

# @title Video 8: Embedding Faces Vignette
from ipywidgets import widgets
from IPython.display import YouTubeVideo
from IPython.display import IFrame
from IPython.display import display


class PlayVideo(IFrame):
  def __init__(self, id, source, page=1, width=400, height=300, **kwargs):
    self.id = id
    if source == 'Bilibili':
      src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'
    elif source == 'Osf':
      src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'
    super(PlayVideo, self).__init__(src, width, height, **kwargs)


def display_videos(video_ids, W=400, H=300, fs=1):
  tab_contents = []
  for i, video_id in enumerate(video_ids):
    out = widgets.Output()
    with out:
      if video_ids[i][0] == 'Youtube':
        video = YouTubeVideo(id=video_ids[i][1], width=W,
                             height=H, fs=fs, rel=0)
        print(f'Video available at https://youtube.com/watch?v={video.id}')
      else:
        video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,
                          height=H, fs=fs, autoplay=False)
        if video_ids[i][0] == 'Bilibili':
          print(f'Video available at https://www.bilibili.com/video/{video.id}')
        elif video_ids[i][0] == 'Osf':
          print(f'Video available at https://osf.io/{video.id}')
      display(video)
    tab_contents.append(out)
  return tab_contents


video_ids = [('Youtube', 'tF0iYBAnyrI'), ('Bilibili', 'BV1NY411K7f6')]
tab_contents = display_videos(video_ids, W=854, H=480)
tabs = widgets.Tab()
tabs.children = tab_contents
for i in range(len(tab_contents)):
  tabs.set_title(i, video_ids[i][0])
display(tabs)
# @title Submit your feedback
content_review(f"{feedback_prefix}_Embedding_Faces_Vignette_Video")
# @title Video 9: Embedding Faces Set-up
from ipywidgets import widgets
from IPython.display import YouTubeVideo
from IPython.display import IFrame
from IPython.display import display


class PlayVideo(IFrame):
  def __init__(self, id, source, page=1, width=400, height=300, **kwargs):
    self.id = id
    if source == 'Bilibili':
      src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'
    elif source == 'Osf':
      src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'
    super(PlayVideo, self).__init__(src, width, height, **kwargs)


def display_videos(video_ids, W=400, H=300, fs=1):
  tab_contents = []
  for i, video_id in enumerate(video_ids):
    out = widgets.Output()
    with out:
      if video_ids[i][0] == 'Youtube':
        video = YouTubeVideo(id=video_ids[i][1], width=W,
                             height=H, fs=fs, rel=0)
        print(f'Video available at https://youtube.com/watch?v={video.id}')
      else:
        video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,
                          height=H, fs=fs, autoplay=False)
        if video_ids[i][0] == 'Bilibili':
          print(f'Video available at https://www.bilibili.com/video/{video.id}')
        elif video_ids[i][0] == 'Osf':
          print(f'Video available at https://osf.io/{video.id}')
      display(video)
    tab_contents.append(out)
  return tab_contents


video_ids = [('Youtube', 'JrzicfOxqP0'), ('Bilibili', 'BV1fv4y1M7eQ')]
tab_contents = display_videos(video_ids, W=854, H=480)
tabs = widgets.Tab()
tabs.children = tab_contents
for i in range(len(tab_contents)):
  tabs.set_title(i, video_ids[i][0])
display(tabs)
# @title Submit your feedback
content_review(f"{feedback_prefix}_Embedding_Faces_SetUp_Video")

コンラッドは顔認識の助けが必要です。彼は、同じ人物の写真が埋め込み空間で近くにあり、異なる人物の写真が埋め込み空間で遠くにあるような顔写真を埋め込むネットワークを構築したいと考えています。単にピクセル空間を使うことはできません。なぜなら、正面から撮った写真と横から撮った写真ではピクセルが大きく異なるからです!

画像のピクセルから埋め込み空間へ変換するためにニューラルネットワークを使います。最後の層に m 個のユニットを持つ畳み込みニューラルネットワーク(CNN)があるとしましょう。顔写真 ii を CNN に入力すると、最後の層のユニットの活動は mm 次元のベクトル yˉi\bar{y}_i を形成します。これがその顔写真の mm 次元空間での埋め込みです。

ここでユークリッド距離を活用できるかもしれません。2つのベクトル間のユークリッド距離は次の通りです:

d(yˉi,yˉj)=c=1m(yˉicyˉjc)2d(\bar{y}_i, \bar{y}_j) = \sqrt{\sum_{c=1}^m(\bar{y}_{i_c} - \bar{y}_{j_c})^2}

注意: ここで小さな指摘ですが、ビデオ内で ii と言うべきところを jj と誤って表記しています。

考えよう!3: 顔埋め込みのためのコスト関数設計

これまでの知識を踏まえて、コンラッドが顔の有用な埋め込みを得るために訓練しているニューラルネットワークのコスト関数をどのように設計しますか?数式を書いてみてください!

グループで議論してください。もし行き詰まったら、以下のヒントを一つずつ開けてみてください。ただし、次のヒントを開ける前に十分議論する時間を取りましょう!今あなたは本物のディープラーニング研究者です。答えは簡単ではありません。

ヒント1はこちらをクリック

同じ顔に対してはどう扱いたいですか?似た顔だけに基づくコスト関数を作ることはできますか?それだとどうなりますか?

ヒント2はこちらをクリック

異なる顔も含める必要があります。異なる顔はどう扱いたいですか?

ヒント3はこちらをクリック

似た顔は埋め込みのユークリッド距離が小さく、異なる顔は大きくなるべきです。3つの顔でこれを表現できますか?

解答はこちらをクリック

同じ顔は似た埋め込みを持つべきです。例えば、ライルの写真 aa ともう一枚のライルの写真 pp があるとします。これらの写真の埋め込みは非常に似ていてほしいので、yˉa\bar{y}_ayˉp\bar{y}_p(写真 aapp を CNN に通したときの最後の層の活動)のユークリッド距離は小さいほうが良いです。

したがって、可能なコスト関数の一つは:

コスト関数=d(yˉa,yˉp)\text{コスト関数} = d(\bar{y}_a, \bar{y}_p)

しかし、同じ顔のペアだけを入力してこれを最小化するとどうなるでしょうか?異なる埋め込みを持つ動機がなくなり、距離を最小化するだけになるので、CNNが賢ければすべての写真に同じ埋め込みを与えてしまい、コスト関数は0になります!

これは明らかに望ましくありません。CNNには同じ顔のときだけ似た埋め込みを持つように動機付けたいのです。つまり、異なる顔のときは距離を大きくするように訓練する必要があります。

異なる人物の2枚の写真を選んで距離を最大化することもできますが、そうするとライルの2枚の写真の埋め込みとの関係がありません。代わりにもう1枚写真を加えます:コンラッドの写真 nn です。この写真はライルの写真 aapp から遠くなるようにしたいのです。つまり、aapp の距離は小さく、aann の距離は大きくしたい:

コスト関数=d(yˉa,yˉp)d(yˉa,yˉn)\text{コスト関数} = d(\bar{y}_a, \bar{y}_p) - d(\bar{y}_a, \bar{y}_n)

nnaapp 両方と比較することもできます:

コスト関数=d(yˉa,yˉp)d(yˉa,yˉn)d(yˉp,yˉn)\text{コスト関数} = d(\bar{y}_a, \bar{y}_p) - d(\bar{y}_a, \bar{y}_n) - d(\bar{y}_p, \bar{y}_n)

しかし、この場合コスト関数が少しアンバランスになり、異なる顔の項が2つあるためそちらが支配的になり、類似性を達成することが重要でなくなるかもしれません。なので、異なる顔の項は1つだけ含めることにします。

これは確立されたコスト関数で、トリプレットロスと呼ばれます!添字 aa, pp, nn を選んだのは理由があります:アンカー画像、ポジティブ画像(アンカーと同じ人物の顔)、ネガティブ画像(アンカーと異なる人物の顔)です。これを N 個のデータ点にわたって合計します。各データ点は3枚の画像のセットです:

コスト関数=i=1N[d(yˉa,i,yˉp,i)d(yˉa,i,yˉn,i)]\text{コスト関数} = \sum_{i=1}^N [d(\bar{y}_{a, i}, \bar{y}_{p, i}) - d(\bar{y}_{a, i}, \bar{y}_{n, i})]

トリプレットロスにはもう一つ小さな追加があります。上記のコスト関数に定数 α\alpha を加え、負になった場合はコスト関数を0にします。なぜでしょうか?

コスト関数=max(i=1N[d(yˉa,i,yˉp,i)d(yˉa,i,yˉn,i)+α],0)\text{コスト関数} = \text{max} \left( \sum_{i=1}^N \left[ d(\bar{y}_{a, i}, \bar{y}_{p, i}) - d(\bar{y}_{a, i}, \bar{y}_{n, i}) + \alpha \right], 0 \right)
# @title Submit your feedback
content_review(f"{feedback_prefix}_Embedding_Faces_Discussion")
# @title Video 10: Embedding Faces Wrap-up
from ipywidgets import widgets
from IPython.display import YouTubeVideo
from IPython.display import IFrame
from IPython.display import display


class PlayVideo(IFrame):
  def __init__(self, id, source, page=1, width=400, height=300, **kwargs):
    self.id = id
    if source == 'Bilibili':
      src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'
    elif source == 'Osf':
      src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'
    super(PlayVideo, self).__init__(src, width, height, **kwargs)


def display_videos(video_ids, W=400, H=300, fs=1):
  tab_contents = []
  for i, video_id in enumerate(video_ids):
    out = widgets.Output()
    with out:
      if video_ids[i][0] == 'Youtube':
        video = YouTubeVideo(id=video_ids[i][1], width=W,
                             height=H, fs=fs, rel=0)
        print(f'Video available at https://youtube.com/watch?v={video.id}')
      else:
        video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,
                          height=H, fs=fs, autoplay=False)
        if video_ids[i][0] == 'Bilibili':
          print(f'Video available at https://www.bilibili.com/video/{video.id}')
        elif video_ids[i][0] == 'Osf':
          print(f'Video available at https://osf.io/{video.id}')
      display(video)
    tab_contents.append(out)
  return tab_contents


video_ids = [('Youtube', 'mVk1W7x6Nps'), ('Bilibili', 'BV1nf4y1f7oL')]
tab_contents = display_videos(video_ids, W=854, H=480)
tabs = widgets.Tab()
tabs.children = tab_contents
for i in range(len(tab_contents)):
  tabs.set_title(i, video_ids[i][0])
display(tabs)
# @title Submit your feedback
content_review(f"{feedback_prefix}_Embedding_Faces_WrapUp_Video")

上のビデオで紹介された論文をチェックしてください:


まとめ

今日はさまざまなコスト関数を見てきました。これらの演習から皆さんに持ち帰ってほしいことを少しまとめたいと思います。いくつかのコスト関数を見ました:

  • ニューロンの対数ポアソン尤度
  • モデル化された不確実性
  • 顔の埋め込み

これらすべてのケースで見られたのは、コスト関数が問題領域の洞察から生まれるということです。問題領域の専門家から洞察を引き出す必要があること、そして同時にコスト関数は計算的な洞察からも生まれることを見ました。適切なコスト関数を考案するには、専門家の話をよく聞き、彼らが言わないかもしれないことを探ることが必要です。