くじら公園

プログラミングなど学んだことについて書きます

Deep Learning Specialization(Coursera) - Course 3のメモ

CourseraDeep Learning Specializationの受講メモです。Deep Learning SpecializationはMachine Learningコースを提供するAndrew Ng氏、および氏が創設したdeeplearning.aiが提供する深層学習に関する一連の講義です。以下の5つのコースから構成されています。

  • COURSE 1: Neural Networks and Deep Learning
  • COURSE 2: Improving Deep Neural Networks: Hyperparameter tuning, Regularization and Optimizationw
  • COURSE 3: Structuring Machine Learning Projects
  • COURSE 4: Convolutional Neural Networks
  • COURSE 5: Sequence Models

今回はCOURSE 3、Structuring Machine Learning Projectsを受講した際の備忘録的なメモです。Deep Learning Specialization全体を受講したうえでのまとめは別途記載する予定です。


COURSE 3: Structuring Machine Learning Projects

COURSE 3は機械学習システムの開発を効率よく進めるためのノウハウなどについての内容でした。COURSE 3の"About this course"には、本コースの内容は現時点でここでしか学べないものばかりであることが強調されていますが、COURSE 3を終えてみて、実際に機械学習を用いたシステムを開発する際の体系立てられたノウハウなど、他の教材ではあまり扱わない内容を学ぶことができました。

本コースは2週構成でした。Week 1ではまず深層学習システムを改善するときの戦略―複数の改善を見込めるアイデアがあるときに、最も有望なものから手をつけるために問題を解析することについて学んだ後、その解析の手段として、数値目標の設け方、human levelのパフォーマンスと学習データ、検証用データ、テスト用データに対するパフォーマンスの比較の仕方などを学びます。Week 2ではそのパフォーマンスを比較する際の方法の一つであるエラー分析の仕方から始まり、学習データと検証・テストデータが同じ分布から得られているかの検証方法などに加え、パフォーマンスの向上が見込める施策として転移学習、multi-task学習、End-to-end学習について学びます。本コースはプログラミングの宿題はなくクイズのみでした。ただしいつもの単発のクイズではなく特定のシナリオを想定したケーススタディとなっていました。

受講期間の公式の見積もりは"2 weeks of study, 3-4 hours/week"となっており、実際に要した時間は凡そ7時間でした。

以下メモです(ただメモを張り付けているだけなので、体裁が崩れていたらすみません…):

Week 1

ビデオ3時間、クイズ30分

内容

ビデオ
  • Introduction to ML Strategy
    • Why ML Strategu?
      • When trying to improve a deep learning system, you often have a lot of ideas or things you could try. And the problem is thatif you choose poorly, it is entirely possible that end up you spending six months charging in some direction only to realize six months that didn't do good.

      • ML strategies: Waysof analyzing a machine learning problem that will point you in the direction of the most promising things to try.

    • Orthogonalization
      • They're very clear-eyed about what to tune in order to try to achive one effect.

      • 画面を調節するためのノブ(knob)がいくつかついている古いテレビによるアナロジー
        • So in this context, orthogonalization refers to that the TV designers had designed the knobs so that each knob kind of dose only one thing. And this makes it muche eisier to tune the TV.

        • 例えばあるノブが画面の縦比と横比の両方を調節するものである場合と、縦比と横比を調節する別個のノブが用意されている場合を比べると、後者の方が画面を調節しやすい、これがOrthogonalization
      • 車によるアナロジー、普通の車はノブとしてハンドルとアクセル(とブレーキ)がある、架空の車として方向と速度を一変に調節できるジョイスティックを二つ持った車を想定すると、普通の車と架空の車のどちらが御するのが容易?
        • 縦軸速度、横軸方向の二軸のチャート、軸方向にだけ制御できる方が全体としての制御が容易
      • MLの文脈で考えてみる
        • Chain of assumption in ML
          • Fit training set well on cost function
          • Fit dev set well on cost function
          • Fit test set well on cost function
          • Performs wekk in real world
        • 上記4つの問題・ステップで取り組むのがOrthogonalization、逆に言えばこれらを混ぜると制御が難しくなる
        • Early stopping: Orthogonalizationに反する
          • This is one knob that is less orthogonalization, because it simultaneously offers two things: * > It offers how well you fit the training set. * > It improves dev set performance.

  • Setting up your goal
    • Single number evaluation metric
      • PrecissionとRecallの例
        • 二つ以上の指標を用いるより一つの指標を用いた方が効率的、この場合はF値を使うべし
        • Dev set + Single real number evaluation metric -> Speed up iterating

    • Satisficing and Optimizing metric
      • 猫分類器の例、accuracyとrunning timeを向上させたいとする
      • この場合はさっきみたいに二つの指標を一つに組み合わせるする代わりに以下のとおり指標を設定する
        • maximize accuracy

        • subject to runningTime < 100ms

      • accuracyがoptimizing metric、running timeがsatisficing metric
      • N metrics: 1 optimizingとN - 1 satisficing(threshold)
    • Train dev/set distribution
      • Choose a dev set and test set to reflect data you expect to get in the future and consider important to do well on.

    • Size of dev and test set
      • Set your test set to be big enough to give high confidence in the overrall performance of your system.

    • When to change dev/test sets and metrics
      • 猫分類器の例
        • Metric: classification error
        • Algorithm A: 3% error, B: 5% error
        • でもAはポルノ画像を猫と間違えてしまう
        • Metricを変更する
          • 元のError: (1 / m{dev}) \sum{i}^m{dev} I {y{prod}^{(i)} \neq y^{(i)}}
          • 改善後のError: (1 / m{dev}) \sum{i}^m{dev} w^{(i)} I {y{prod}^{(i)} \neq y^{(i)}}
            • w^{(i)} = 1 if x^{i} is non-porn else 10
        • これもorthogonalization
          • So far we've only discussed how to define a metric to evaluate classifiers.

          • Worry separately about how to do well on this metric.

      • So my recommendation is, even if you can't define the perfect evaluation metric and dev set, just set something up quickly and use that to drive the speed of your team iteration. And if later down the line you find out that it wasn't a goode one, you have better idea, change it at that time.

  • Comparingto human-level performance
    • Why human level performance?
      • First is that because of advance in DL ML algorithmsare are suddenly working much better and so it has become much more feasible in a lot of application areas for ML algorithms to actually become aompetitive with human-level performance.

      • Second, it turns out that the workflow of designing and building a ML system, the workflow is much more efficient when you're trying to do something that humans can also do.

      • Bayes optimal error: パフォーマンスの理論的な上限値; the very best theoretical function for mapping from x to y
      • 試行するうちにaccについてhuman-levelよりは向上してもBayes errorは越えられない
      • MLがhuman levelより悪いときは:
        • Get labeled data from humans.
        • Gain insight from manual error analysis.
          • Why did a person get this right?
        • Better analysis of bias/variance.
    • Avoidable bias: Human level error as a proxy for Bayes error
      • 猫分類の問題
        • Human: 1%, Training: 8%, dev: 10%のとき、8 - 1 = 7がAvoidable bias、biasにfocusする
        • Human: 7.5%, Training: 8%, dev: 10%のとき、8 - 1 = 05がAvoidable bias、varianceにfocusする
    • Understanding human-level performance
      • レントゲン診断の分類器を作るとき、human levelが以下の場合Bayes errorhはどれ?
        1. Typical human: 3%
        2. Typical doctor: 1%
        3. Experienced doctor: 0.7%
        4. Teams of experienced doctors 0.5%
      • 答えは4、Baues errorは0.5%だから
      • Having an estimate of human-level performance gives you an estimate of Bayes error. And this allows you to more quickly make decisions as to whether you should focus on trying to reduce a bais or trying to reduce the variance of your algorithm.

    • Surpassing human level performance
      • Online ad, recommendation, load approval等structured dataに対してはすでにMLは人を凌駕している
        • これらは人には見切れないほどのデータがある
      • Not natural, perception problemsは逆に人が得意
Case Study

今回のExerciseは架空の町のために空を飛んでいる鳥を検知するアルゴリズムを開発するというシナリオで、ビデオ講義で魏トンされたような状況に面した際にどう振る舞うのが適切かを問う形式のクイズになっていた。

所感

  • ビデオが中心なのでいつもより時間がかかった
  • 機械学習や深層学習のテクニックや知識を教えてくれる教材は多くあるけれど、このコースのようにそれらテクニックや知識を持っている上で実際に深層学習を用いたシステムを開発する時のノウハウについてこのように体系立てて学べるリソースは他に思い当たらない

Week 2

ビデオ2時間、Case Study 1時間

内容

ビデオ
  • Error Analysis: Manually examining mistakes that your algorithm is making, can give you insights into what to do next.
    • Look at dev examples to evaluate ideas
      • 例として猫分類器を考える、分類器が犬の写真を誤って猫と分類した。90% acc, 10% err
        • Should you try to make your cat classifier do better on dogs?

        • Error analysis:
          1. Get ~100 mislabeled dev set examples
          2. Count up how many are dogs
        • 結果、5%が犬だった→犬問題に取り組むことで10%のerrを9.5%にできる
      • ceiling(天井)を見つける
        • Upper bound on how much you could improve performance by working on the dog problem

      • Evaluate multiple ideas in parallel
        • 猫分類のideaとして例えば犬を間違える、ライオンとかを間違える、ぼやけた画像を間違える、インスタの画像で間違えるの四つの問題―改善案があるときに、スプレッドシートを開いて画像100枚についてこの四つの問題のどれを被っているか一覧にして、四つのそれぞれが何割くらい発生しているか確認する
    • Cleaning up incorrectly labeled data
      • If you find that your data has some incorrectly labeled examples, what should you do?

      • DL algorithm are quite robust to random errors in the training set. * > If the errors are reasonably random, then it's probably okay to just leave the errors.

      • They are less robust to systematic errors.

      • dev/test setのときは前述のスプレッドシートに"Incorrectly labeld"という問題も加えて吟味する、例えば
        • Overroll dev set error: 10%, errors due incorrect labels: 6%
          • →0.1 * 0.06 * 100でこれを改善することで10%のerrを9.4%に改善できると見積もることができる
        • Goal of dev set is to help you select between two classifiers A and B.

        • もしdev setのIncorrectly labeled dataの割合が大きいときはこの本来の目的を達成できない可能性がある
    • Build your first system quickly, then iterate. 1. Setup dev/test set and metric 2. Build initial system quickly 3. Use bias/variance analysis and error analysis to prioritize next steps

  • Mismatched training and dev/set set
    • Training and testing on different distributions
      • 猫分類器の例、Data from webpagesが200000枚、Data from mobile app(より実環境に近いデータ)が10000枚のとき
        • Option 1: 2つのデータを合わせてシャッフルし、train dev/testに分ける
          • メリット: train, dev/test set from the same distribution
          • デメリット: dev setの多くがwebpage由来
          • So remember that setting up your dev set is telling yout team where to aim the target. And the way you're aiming your target, you're saying spend most of the time optimizing for the web page distribution of images, which is really not what you want.

        • Option 2: trainはweb 200000 + app 5000、devはapp 2500、testはapp 2500
          • こちらのほうがOption 1より推奨される
          • You're telling your team, my dev set has data uploaded from the mobile app and that's the distribution of images you really care about, so let's try to build a ML system that does really well on the mobile app distribution of images.

    • Bias and variance with mismatched data distributions
      • 猫分類器の例、人が分類すると凡そ0%のerr、学習時のerrは1%、devに対するerrが10%のとき、これはvariance problem?
      • 可能性として、training and dev data comes from a different distributions
      • this just refrects that the dev set contains images that are much more difficult to classify accurately.

      • 対策、もう一つデータセットを準備: Training-dev set, same distribution as training set, but not used for training.
      • 再度比較してみたとき、human: 0%, training err: 1%, training-dev err: 9%, dev err: 10%だったらvariance problem
      • 再度比較してみたとき、human: 0%, training err: 1%, training-dev err: 1.5%, dev err: 10%だったらdata mismatch problem
      • 再度比較してみたとき、human: 0%, training err: 10%, training-dev err: 11%, dev err: 12%だったらavoidable bias problem
      • 再度比較してみたとき、human: 0%, training err: 10%, training-dev err: 11%, dev err: 20%だったらavoidable bias problem + data mismatch
      • まとめ
        • Human level <-> Training set err: avoidable bias
        • Training set err <-> Training-dev set err: variance
        • Training-dev set err <-> Dev set err: data mismatch
        • Dev set err <-> Test set err: degree of overfitting to the dev set
      • より一般的なタスクに対する分類器と比較してみるのも一手
        • 例えば車のReaview mirrror speech recognitionを開発している時に、General speech recognitionの分類器と上記四つの尺度で比較してみる
    • Adressing data mismatch
      • Carrying out manual error analysis to try to understand difference between training and dev/test sets.
      • Make training data more similar; or collect more data similar to dev/test sets.
      • データをaugumentするのは手の一つだが、合成データにoverfitしてしまう危険性は含まれている
  • Learning from multiple tasks
    • Transfer learning
      • One of the most powerful ideas in DL is that sometimes you can take knowledge the NN has learned from one task and apply that knowledge to a separate task.

      • pre-trainingとfine-tuning
      • So, when does transfer learning make sense? Transfer learning makes sense when you have a lot of data for the problem you're transfering from and usually relatively less data for the problem you're transfering to. * Task A and B have the same input x. * You have a lot more data for Task A than Task B. * Low level features from A could be helpful for learning B.

    • Multi task learning
      • 例として、写真に車、標識、信号機、...が写っているか検知したい
      • 出力層に検知したいオブジェクトの数だけ(例えば4)ユニットを並べる
      • 損失関数: \sum{i=1}^m \sum{j=4} L(\hat{y}_ji, y_ji)
        • Lは通常のlogistic loss
        • unlike softmax one image can have multiple labels

      • If some of the earlier features in NN can be shared between these different types of objects, then you find that training one NN to do four things results in better performance thant training four completely separate NN to do the four tasks separately.

      • When multi-task learning makes sense? * > Training on a set of tasks that could benefit from having shared lower-level features. * > Usually amount of data you have for each task is quite similar. * > Can train a big enough NN to do well on all the tasks.

      • Multi-task learning enables you to train one NN to do many tasks and this can give you better performance than if you were to do the tasks in isolation.

  • End-to-end deep learning
    • Briefly, there have been some data processing systems or learning systems that require multiple stages of processing, and what E2E DL dose, is it can take all those multiple stages and replae it usually with just a single NN.

    • Pros and cons of E2E DL
      • Pros:
        • Let the data speak
          • (音声認識器の音素(phoneme)を例にとって) I think that phonemes are an artifact created by human linguist. And if you let your learning algorithm whatever representation it wants to learn rather than forcing your learning algorithm to use phonemes as a representation, then its overall performance might end up being better.

        • Less hand-designing of components needed
      • Cons:
        • May need large amount of data.
        • Excludes potentially useful hand-designed components.
          • When you have a ton of data it's less important to hand design things. But when you don't have much data, then having a carefully hand-designed system can actually allow humans to inject a lot of knowledge about the problem into an algorithm deck and that should be very helpful.

    • Key question: Do you have sufficient data to learn a function of the complexity needed to map x to y?
Case Study

自動運転を例に取ったクイズ

Deep Learning Specialization(Coursera) - Course 2のメモ

CourseraDeep Learning Specializationの受講メモです。Deep Learning SpecializationはMachine Learningコースを提供するAndrew Ng氏、および氏が創設したdeeplearning.aiが提供する深層学習に関する一連の講義です。以下の5つのコースから構成されています。

  • COURSE 1: Neural Networks and Deep Learning
  • COURSE 2: Improving Deep Neural Networks: Hyperparameter tuning, Regularization and Optimizationw
  • COURSE 3: Structuring Machine Learning Projects
  • COURSE 4: Convolutional Neural Networks
  • COURSE 5: Sequence Models

今回はCOURSE 2、Improving Deep Neural Networks: Hyperparameter tuning, Regularization and Optimizationwを受講した際の備忘録的なメモです。Deep Learning Specialization全体を受講したうえでのまとめは別途記載する予定です。


COURSE 2: Improving Deep Neural Networks: Hyperparameter tuning, Regularization and Optimizationw

COURSE 2はニューラルネットワークの各種正則化手法や最適化手法、ハイパーパラメーターチューニングなど学習を効率的に行うための手法とその理解が主な内容でした。Week 1で過学習とこれを避けるための正則化手法―L2正則化ドロップアウトや勾配爆発・消失問題について学び、続いてmomentum、RMSProp、Adam optimizationなど最適化手法を理解・実装し(Week 2)、最終週で落ち葉拾いとしてハイパーパラメーターチューニング、バッチ正則化、多クラス分類について学んだあと、以降のCOURSEで使用することになるTensorflowの基本について学ぶ(Week 3)のが大まかな流れとなります。

受講期間の公式の見積もりは"3 weeks, 3-6 hours per week"となっており、実際に要した時間は凡そ12時間でした。

COURSE 2は深層学習の初心者、および(私のような)普段TensorflowやChainerなどフレームワークを通してのみ正則化や最適化手法を利用している人間が、それらの直感的な理解をビデオを通して学び、またProgramming Assignmentsを通して実際に手を動かして体験できるようになっていました。

COURSE 1から引き続き一貫してモデル改善のイテレーション-Idea→Code→Experimentを効率的に回すことが成功への近道という主張が繰り返し提示され、COURSE 2はそのための各種手法を、ビデオ抗議で直感的に理解し、Programming Assignmentsで自分で実装し目で見てさらに理解を深めるという形式でした。

個人的には指数加重移動平均を学んだ後に、これをパラメーターの更新に適用するmomentum、momentumを拡張したRMSProp、momentumのアイデアとRMSPropのアイデアを合わせたAdam optimizerという一連の最適化手法の説明がわかりやすく面白かったです。また、RMSPropの生い立ちに言及しているときのAndrew先生の笑顔がみていてうれしかった:

One fan fact about RMSprop, it was actually first proposed not in on academic research paper, but in a Coursera course that Jeff Hinton had taught on Coursera many years ago.

また(これは私が不勉強なだけですが)最近の潮流として、学習時に問題となるのはlocal optimaではなくsaddle pointsを抜け出すのに時間がかかることと考えられていることや、ハイパーパラメーターの探索時はグリッド探索よりrandom samplingが有効であるなど、本COURSEが割と新鮮(公開が3ヶ月前)であるがゆえに新しく学ぶことが多かったです。

以下メモです(ただメモを張り付けているだけなので、体裁が崩れていたらすみません…):

Week 1

2017/10/09

ビデオ+クイズで3時間。Programming Assignmentsで2時間

内容

ビデオ
  • Train / Dev / Test sets
    • Intuiton from one domain or from one application area often do not transfer to other application areas.

    • devset: たくさんの異なるモデルの中でどれが手元のdevsetに対し最も良いパフォーマンスを呈するか検証するために用いる
    • testset: 最終的なモデルを評価する、手元のアルゴリズムがどれくらい上手く機能するかunbiasedな見積もりを得るために用いる
  • Biase / Variance
    • high bias: under fitting
      • より大きなネットワークを用いる
      • もう少し長く学習する
      • (別のNNアーキテクチャを模索する)
    • high variance: overfitting
  • Regularization
    • L2 Regularization
      • ロジスティック回帰の場合
        • 正則化項: lambd / (2m) \norm{w}_22 = lambd / (2m) WTW
      • Neural Networksの場合
        • 正則化項: lambd / (2m) \sum_{l=1}^L \norm{w^[l]}^2
          • \norm{w^[l]}^2はForbenius normと呼ばれる
          • wは(n^[l], n^[l-1])だから\norm{w^[l]}^2 = \sum{i=1}^{n^[l-1]} \sum{i=j}^{n^[l-1]} (w_{ij}^[l])2
          • 逆伝播のときは正則化項の微分を忘れずに
      • 正則化項は、wが大きくなりすぎることに対しペナルティを課す
        • lambdを大きくすると更新後のwは0に近づく
        • 直感的には、正則化することでより小さなネットワークとなる
        • もう一つの直感的な捉え方として、活性化関数にtanhを仮定する、tanhは0付近で勾配が線形に近づく→underfittingされる
  • Dropout
    • inverted dropout
      • keep_probの確率でランダムにユニットをzero outした上で、残ったユニットをkeep_probで除算する
        • 例えば a3 /= keep_prob
        • to keep exptected values of a3.

        • これをしとかないとtestデータに対する予測をするときに苦労することになる
          • 「学習時はkeep_probでdropoutしたけどtest時はdropoutしないのでaの値をスケールして…」とか悩まないために
    • Why dose dropout work?
      • Can't rely on any one feature, so have to spread out weights

  • Other regularization methods
    • Data augumentation
    • Early stopping
      • 初期値はwは0に近い、学習が進むとwは大きな値となる、この間で学習を止める
      • Orthogonalization
        • early stoppingはoptimizationとnot overfitの2つを別個に考慮することができない
  • Normarizing inputs
    • 平均ひいて、分散で除する
  • Vanishing / exploding gradiendts
    • a very deep neural network your derivatives of your slopes can sometimes get either very big or small.

    • w > I(単位行列)→深いネットワークでは活性化関数の結果が爆発してしまう→勾配も爆発
  • Weight initialization
    • Vanishing / exploding gradiendtsに対処するための手段の一つ
    • z1 = w_1x_1 + w_2x_2 + \cdots + w_nx_n(nはユニット数)
    • var(w_i) = 1/nだと嬉しい
    • なので、w^[l] = np.random.randn(shape) * np.sqrt(1 / n^[l-1])
      • l-1なのは入力される特徴量(ユニット数)でスケールしたいから
      • ReLUだと、np.sqrt(2 / n^[l-1])が良いらしい
      • Xavier initialization: np.sqrt(1 / n^[l-1])
  • Numerical approximation
  • Gradient check
Programming Assignments

所感

  • inverted dropoutの名前と理由は始めてしった
  • Programming Assignmentsが、実際に各手法を実装して結果を見比べるという内容で理解しやすかった

Week 2 - Optimization algorithms

2017/10/13 ビデオ2時間

2017/10/16 ビデオ+クイズ1時間、Programming Assignment 0.5時間

内容

ビデオ
  • Mini batch gradient descent
    • You can get a faster algorithm if you let gradient descent start to make some progress before you finish processing your entire examples.

    • Epoch: A single pass through the training set.
  • Understanding mini-batch gradient descent
    • Batch gradient descentの学習曲線は、イテレーション毎にコストが必ず減少していく
    • mini-batch gradient descentの学習曲線は、減少傾向にあるけれどノイズが含まれる
    • Choosing your mini-batch size
      • mini-batch size m: Batch gradient descentと同じ
        • too long / iteration
      • mini-batch size 1: Stochastic gradient descent
        • Lose speedup from vectorization
      • 2つの間をmini-batch sizeとして選択する
      • Because of the way computer memory is layed out and accessed, sometimes your code run faster if your mini-batch size is power of 2.

  • Exponentially weighted averages
    • v_t = \beta v_{t-1} + (1 - \beta) \theta_t
      • beta = 0.9のとき、1/(1-0.9) = 10、10回分を通して平均している
      • betaを大きくするとより平滑化される
    • \theta_tのプロットに、exponentially decayng functionをかけ合わせる
    • 実装時はv(v_{\theta})を使いまわす
      • v is computing exponentially weighted average over the parameter theta

  • Bias correction in exponentially weighted averages
    • v_t = \beta v_{t-1} + (1 - \beta) \theta_t、tが小さい範囲で本来の値との剥離が大きい
    • 対策としてv_tを1-\betatで除した値を用いる
  • Gradient descent with momentum
    • The basic idea is to compute an exponentially weighted decay of your gradients, and then use that gradient to update your weights instead.

    • v{dw} = \beta v{dw} + (1 - \beta) dW
    • W := W - \alpha v_{db}
    • smooth out the steps of gradient descent

  • RMSprop; root mean square prop
    • s{dw} = \beta s{dw} + (1 - \beta) dW2
    • W := W - \alpha (dW / s_{dw}^{1/2} + \epsilon)
    • One fan fact about RMSprop, it was actually first proposed not in on academic research paper, but in a Coursera course that Jeff Hinton had taught on Coursera many years ago.

  • Adam optimization: Adaptive momentum estimation
    • v{dw} = \beta_1 v{dw} + (1 - \beta_1) dW; momentumと一緒
    • s{dw} = \beta_2 s{dw} + (1 - \beta_2) dW2; RMSpropと一緒
    • v^{corrected}{dw} = v{dw} / (1 - \beta_12); bias correction of v_{dw}
    • s^{corrected}{dw} = s{dw} / (1 - \beta_22); bias correction of s_{dw}
    • W := W - \alpha (v^{corrected}{dw} / s^{corrected}{dw}^{1/2} + \epsilon)
  • Learning rate decay
    • Slowly reduce your learning rate over time

    • \alpha = 1 / (1 + decay_rate * epoch_num) * \alph_0
  • The problem of local optima
    • Most points of zero gradients are not local optima

    • Most points of zero gradients in a cost function are saddle points.

    • 最近は、次元数が大きい場合、local optimaのような箇所はむしろ稀であり、saddle pointsのような箇所が多く在ると考えられている
    • One of the lessons we learned in history of deep learning is that a lot of our intuitions about low-dimensional spaces, they really don't transfer to the very high dimensional spaces that any other algorithms are operating over.

    • It turns out that plateaus can really slow down learning and plateua is a region where the derivative is close to zero for a long time.

Programming Assignments
  • mini-batchを実装
  • momentumを実装
  • adamを実装
  • 上記3つを比較、adamは明らかにmini-batchとmomentumを凌ぐ

所感

  • momentum、RMSprop、Adamの一連の説明が分かりやすかった
  • AdamやRMSpropは普段tensorflow等を通して利用しているだけなので、今回始めて実装してみて勉強になった

Week 3 - Hyperparameter Tuning, Batch Normalization, Multi-class Classification, Introduction to programming frameworks

2017/10/17 ビデオ2時間40分、Programming assignment 40分

内容

ビデオ
  • Hyperparameter tuning
    • how to systematically organize your hyperparameter tuning process.

    • 重要度: \alpha > \beta, #hidden units, #mini batch size > #layers, learning rate decay > \beta_1, \beta_2, \epislon(Adam)
    • Try random value. Don't use grid
      • it's difficult to know in advance which hyper parameters are going to be the most important for your problem. And some hyperparameters are actually much more important than others.

      • sampling at random rather than in the grid shows that you are more richly exploring set of possible values for the most important hyperparameters, whatever they turn out to be.

    • Corse to fine sampling schema
      • ランダムにパラメーターを選択して学習して、よりものについてズームインしてもう一度探索する
    • Using an appropriate scale to pick hyperparameters
      • layers: uniform sampling

      • learning rate \alpha: log scale sampling
        • 0.0001~0.001、0.001~0.01の間についての探索に同等のリソースを割く
      • exponentially weighted decay \beta: log scale sampling
        • when beta is close to 1, the sensitivity of the results you get changes even with very small changes to beta.

    • Hyperparameters tuning in practice: Pandas vs Caviar
    • cross-fertilization: 画像の領域で開発されたResNetがSpeech等でも良い成績を達成するようなことを言う
    • hyperparameters, i've seen that intuitions do get stale. * 同様のことはhyperparametersの探索では稀

    • Babysitting one model: 学習が複数実に渡る場合、日毎に学習経過に合わせてパラメーターを返る探索の仕方
      • データが大きい、リソースが少ない時の手法
      • パンダ、子供一匹を大事に育てる
    • Training many models in parallel
      • キャビア、子供いっぱいを一度に育てる、どれかがうまくいく
  • Batch Normalization
    • BN makes your hyperparameter search problem much easier, BN makes the neural network much robust to the choice of hyperparameters and enables you to much more easily train even very deep network.

    • inputの正規化は学習を高速化する
      • 正規化する前と正規化した後の2パラメーターの等高線の図
    • can we normalieze a^[l-1] so as to train W^[l], b^[l] faster? * inputの正規化の考えを隠れ層に適用する

    • Implementing Batch Normalization
      • Given some intermediate values in NN z^{(1)}, z^{(2)}, ..., z^{(m)}
        • \mu = \sum_i z^{(i)} / m
        • \sigma2 = \sum_i (z^{(i)} - \mu)2
        • z^{(i)}_{norm} = (z^{(i)} - \mu) / sqrt(\sigma2 + \epsilon)
        • \tild{z^{(i)}} = \gamma z^{(i)}_{norm} + \beta
      • \gammaと\betaは学習するパラメーター
        • 平均0、分散1が常に裁量とは限らない
      • BNだとb^[l]は要らない
        • BN zeros out the means of z^[l] values in the layer there's no point having this parameter b^[l] and instead is sort of replaced by \beta-[l].

    • Why dose BN work?
      • covariate shift: 学習時と予測時のデータの分布が異なると予測はうまくいかない
      • if the distribution of X changes, then you might need to retrain your learning algorithm.

      • 5層ネットワークを想定して、3番目の層の視点で考えてみる
        • job of the third hidden layers is to take these values(a[2]) and find a way to map them to \hat{y}. The network also adapting parameters W^[2], b^[2], W^[1], b^[1] and so as these parameters change, the values a^[2]also change.

        • a^[2]は毎回変化する→3層目がcovariate shift problemを被る
        • so what BN dose, it reduce, the amount that the distribution of these hidden unit values shifted around.

        • But really don't turn to BN as regularization. Use it as a way to normalize your hidden unit activations and therefore speed up learning.

    • BN at test time
      • testするときはexampleの数は1、\muと\sigma2はどのような値を用いる?
        • estimate using exponentially weighted average across mini-batch; \mu^{{1}[l]}
  • Multi class Classification
    • Softmax regression: generalization of logistic regression
    • C: #classes, output layerのunit数
      • what is the probability of each of these c classes; P(C = c|X)

      • t = exp(z^[l])
      • a^[l] = exp(z^[l]) / \sum_j t_j
    • hardmaxは要素が一つだけ1になっているベクトルを出力する処理
Programming Assignments
  • tensorflowの基本

所感

  • 最近はGrid探索よりランダムにサンプリングしたhyperparameterの組み合わせでtuningするらしい
    • Week 2のThe problem of local optima-最近は学習を遅延させる原因はlocal optimaではなくsaddle pointsにあると考えられている、と同様に最近のNN界隈の理解が学べるのが楽しい
  • 入力の正規化と対比させて説明されるBNがわかりやすい

Deep Learning Specialization(Coursera) - Course 1のメモ

CourseraDeep Learning Specializationの受講メモです。Deep Learning SpecializationはMachine Learningコースを提供するAndrew Ng氏、および氏が創設したdeeplearning.aiが提供する深層学習に関する一連の講義です。以下の5つのコースから構成されています。

  • COURSE 1: Neural Networks and Deep Learning
  • COURSE 2: Improving Deep Neural Networks: Hyperparameter tuning, Regularization and Optimization
  • COURSE 3: Structuring Machine Learning Projects
  • COURSE 4: Convolutional Neural Networks
  • COURSE 5: Sequence Models

今回はCOURSE 1、Neural Networks and Deep Learningを受講した際の備忘録的なメモです。Deep Learning Specialization全体を受講したうえでのまとめは別途記載する予定です。


COURSE 1: Neural Networks and Deep Learning

COURSE 1はニューラルネットワークと深層学習の導入という位置づけの内容でした。ロジスティクス回帰を順伝播・逆伝播アルゴリズムで理解・実装し(Week 2)、続いて浅い-2層ニューラルネットワークロジスティクス回帰を拡張する形で理解・実装し(Week 3)、さらに任意の数の隠れ層を持ったニューラルネットワークを実装してロジスティクス回帰とのパフォーマンスを比較する(Week 4)のが大まかな流れとなります。

受講期間の公式の見積もりは"4 weeks of study, 3-6 hours a week"となっており、実際に要した時間は凡そ13時間でした。

COURSE 1を通した感想としては、本コースは深層学習の初心者向けに入念に練られたビデオ講義とProgramming Assignmentsから構成されていて、多少のプログラミング経験があれば誰でも深層学習を体験できるようになっていました。

ビデオ抗議ではロジスティクス回帰及び浅いニューラルネットワーク、任意の隠れ層を持ったニューラルネットワークの順伝播、逆伝播のアルゴリズムが数式、および図式を用いて繰り返し提示されて、講義を終えるころには受講者がこれらを自然と理解できているようになっていました。逆伝播における微分計算については深い微分に関する知識は前提とされておらず、高校数学を忘れてしまっていても取り組めるように意識されているようでした。

Programming Assignmentsではビデオ講義の内容-ロジスティクス回帰~任意の数の隠れ層を持ったニューラルネットワークの順伝播と逆伝播の実装が課題でした。jupyter notebookを用いていて、問題の提示とそれに対するこちらの回答を自然に読むことができるようになっているのはとても好感を持ちました。(Machine Learningのときは、問題の提示がPDF、回答はoctaveスクリプトだったので、これらと比べるととてもスムーズに問題に取り組むことができました。)実装時はnumpyのみを用いており、特定の深層学習向けフレームワークは登場しません。またヒントが多く提示されているため、ビデオ講義を受けたうえでヒントを読み漏らさなければほぼ躓くことはありませんでした。

個人的には「なぜ深層学習が非線形関数を必要とするのか」や「どの活性化関数を選択すべきか」についての説明が直感的で面白かったです。

逆に平時、深層学習を用いている人にとっては少し退屈な内容になるかもしれません。COURSE 2以降でより発展的な講義となるようで、来週から取り組んでまた感想を記載するつもりです。

以下メモです(ただメモを張り付けているだけなので、体裁が崩れていたらすみません…):

Week 1

2017/10/02

最後に10問のクイズに正解するまでに1時間40分要した。Geoffrey Hinton氏のインタビュービデオ(40分)はこれから。

内容

  • Waht is a neural network?
  • Supervised Learning with Neural Networks
    • 教師あり学習のニューラルネットワークとは何か
    • Standard Neural Networks、CNNs、RNNsの紹介、各々はどういった領域で用いられるかについて
  • Why is Deep Learning taking off?
    • ニューラルネットワーク自体は何十年も前から存在するが、なぜ今になってこれほどまでに用いられるようになってきたのか?
    • 横軸データサイズ、縦軸パフォーマンスの図
      • SVMやロジスティック回帰など従来の手法は、データサイズが大きくなったときにパフォーマンスが頭打ちされる」
      • ニューラルネットワークはそのネットワークを大きくすればするほど、またデータサイズを大きくすればするほどパフォーマンスが向上する
      • 近年コンピューターの発展で大量のデータが得られるようになった
      • 上記点から近年ニューラルネットワークが多く用いられるようになってきた
    • ディープラーニングをスケールする要素、Data、Computation、Algorithms
      • AlgorithmsもComputationを促進するようなものがインパクトが大きかった
        • 活性化関数ReLUはsigmoidと比較して勾配計算が高速になり結果として学習時間を短くするもので大きなインパクトを与えた
      • 結局計算時間の短縮が鍵となる
        • ニューラルネットワークを適用する作業は直感に基づいた作業となることが多い
          • Ideaに対してCodeを書いてExperimentして、そこから得られた新しいIdeaで再度このサイクルを回していく作業
          • 一回のイテレーションに要する時間が短ければ短いほど成功に近づける
  • About this Course

所感

  • 日本語の字幕は個人的に苦手なので、英語の字幕でビデオを見た
  • 字幕の表示タイミングがビデオによって違った
    • 「Why is Deep Learning taking off?」では、より自然に字幕が表示されるように鳴っていたけど、ビデオ下部に表示される字幕全文が逆に読みにくくて(文章の切れ間がどこだか分かりにくい)少し困った

Week 2

2017/10/03

まだ途中多分100分くらい

2017/10/04

Python and Vectorizationから、150分

Explanation of logistic regression cost functionとHeros of Deep Learningはまだ

内容

Logistic Regression as a neural network(Video)
  • 二値分類を解くアルゴリズムとしてロジスティック回帰の紹介
  • ロジスティック回帰のCost function
    • 学習データの一つのexampleの誤差を計算するのがLoss function
    • パラメーターを引数にとり全ての学習データの誤差の平均を算出するのがCost function
  • 勾配降下法の紹介
  • 微分について簡単におさらい
  • 計算グラフの紹介
    • f = 3(z + bc)という簡単な数式を例に順伝播と逆伝播を説明
  • ロジスティック回帰の計算グラフ
    • 後のことを考えてあえて計算グラフでロジスティック回帰を表現する
Python and Vectorization(Video)
  • numpyを利用したvectorizationについて
  • vectorizationでロジスティック回帰の順伝播を実装してfor文をなくし効率的な処理とする
  • vectorizationでロジスティック回帰の逆伝播を実装してfor文をなくし効率的な処理とする
  • numpyのbroadcastingについて
  • jupyterについて

Programming Assignments

  • numpyの基礎、reshapeとかbroadcastingについて
  • ロジスティック回帰を順伝播と逆伝播のアルゴリズムで実装する
  • 今回はnumpyで実装
  • タスクは、32 x 32の画像がネコの画像か(1)ネコ以外の画像か(0)の二値分類

所感

  • ビデオが長い
  • 計算グラフとその逆伝播の説明(Derivatives with a Computation Graph)は分かりやすかった
  • Derivatives with a Computation Graphは途中から字幕のズレが酷く字幕は無視した
  • Programming Assignmentsは初回故か簡単だった
  • ロジスティック回帰を順伝播と逆伝播のアルゴリズムで実装するのは導入としてとても分かり易い
  • jupyterさまさま、前のmachine learning(テキストはPDF)と比べてjupyterだとexerciseとテキストが一緒に読めてやりやすい

Week 3

2017/10/05

ビデオ8つ、2時間半

後半ビデオ3つで1時間、クイズとassignmentでたぶん1時間半、上と合わせて5時間

内容

  • Week 2のロジスティック回帰を拡張する形でニューラルネットワークの紹介
    • 例えば入力が3次元、4ユニットの隠れ層を1層持つ2層ニューラルネットワークの場合、隠れ層はそれぞれ別のパラメーターを持った4つのロジスティック回帰ユニットと考えることができる
  • ニューラルネットワークのvectorization
    • W^[1]は各ユニットのwを転置したものを水平方向にスタックしたもの; [1]は1層目を表す、(j, n_x); jは隠れ層のユニット数
    • Xは各training exampleのベクトルを垂直方向にスタックしたもの、(n_x, m)
    • Z^[1] = W^[1]X、A^[1] = G(Z^[1])、いずれも(j, m)
    • W^[2]、(1, j)
    • Z^[2] = W^[2]A^[1]、A^[2] = G(Z^[2])、いずれも(1, m)
  • 活性化関数について
    • sigmoidは基本的には二値分類の出力層以外では使わない
    • tanhは平均が0であるという点でsigmoidより優れている、但し入力が大きく/小さくなったときにその勾配が0に近づくため勾配降下法が遅くなるという欠点がある
    • ReLUは多くの場合最も良い選択肢、入力が0より大きい場合に勾配が1となるため学習が早い
    • ReLUの亜種としてLeaky ReLU、使っている人を見るのは稀
  • ニューラルネットワークに活性化関数が必要となる理由
    • 以下の2層ネットワークについて
      • z^[1] = W^[1]x + b^[1]
      • a^[1] = g^1 = z^[1]
      • z^[2] = W^[2]a^[1] + b^[2]
      • a^[2] = g^2 = z^[2]
    • もし活性化関数Gを取り除く/活性化関数Gとしてlinear活性化関数(identity)を使った場合:
      • a^[1] = z^[1] = W^[1]x + b^[1]
      • a^[2] = z^[2] = W^[2]a^[1] + b^[2] = W^2 + b^[2] = (W^[2]W^[1])x + (W^[2]b^[1] + b^[2]) = W'x + b'
      • 入力に対する単なる線形関数となる
      • どんなに層を深くしても結局この最後の線形関数を計算しているだけ→いかなる隠れ層ももっていないネットワークになってしまう
    • 回帰問題を扱う時の出力層に対しては活性化関数としてlinear活性化関数を用いることがある
  • 活性化関数の微分について
  • ニューラルネットワークの逆伝播について

Programming Assignments

所感

  • 日本語の字幕がここらへんからなくなっていた
  • Machin learningのときもそうだけど、基本的に受講者に数学の知識は求めないスタイルで、微分の計算式も初めから答えが紹介される
  • なんだかんだ言って深層学習を網羅的に扱った教科書は青本しか読んでいないので、なぜ活性化関数が必要なのかなどは勉強になった
    • 現時点ではすごく初心者向けと感じる部分もあるが、Andrew先生の観点で大事なところは詳しく教えてくれるので現時点でも得るものは結構ある
  • Programming Assignments、Week 2もそうだけど、forward、compute_cost、backword、update_parametersの4つにプログラムを分類するスタイルはわかりやすい
    • モデル(forward、backword)とコストの計算(compute_cost)、パラメータ-の更新(update_parameters)は明確に分離しやすい
  • やはりnumpyでforward、backword書くのは楽しい、assignmentsだとヒントが多すぎるので、scratchで書きたくなってきた

Week 4

2017/10/07

ビデオ1時間20分、programming assignments1時間30分

内容

  • これまでのまとめ
  • このコースでの記法の整理
  • なぜ深い表現(deep representation)なのか?

    • 例えば人の顔の検知器の場合、頭のほうの層は特徴検知器/edge検知器、お尻の方の層はより複雑な関数を表している
    • Circuit theory
    • But when I'm starting out a new problem, I'll often really start out with neuro-logistic regression then try something with one or two hidden layers and use that as hyper parameters.

  • ForwardとBackward整理
  • ハイパーパラメーターについて
  • 深層学習と人の脳のアナロジーについて   深層学習のforwardとbackwardの仕組みについてよく"It's like a brain."と言及されることがある     これは実際には単純化しすぎた説明だ
    • So when I think of deep learning, I think of it as being very good and learning very flexible functions, very complex functions to learn X to y mappings.

Programming Assignments

所感

  • ForwardとBackwardの図がわかりやすかった
  • Programming exersice、猫分類器の作成において、2層より5層のほうが制度が良かった、隠れ層を増やすと過学習しやすいわけでもない

出力する理由

 もっと世間に自分の文章なりを出力すべきだと思っている。

 理由は短絡的には、「プログラマーはアウトプットすべし」という世間の意見があるから。でももう少し、自分の文脈でなぜ出力すべきと考えるのか考えてみる。

 最近会社の採用活動の一環で他人のSNS等をいくつも読む機会があった。当然だけれど出力の少ない人の人となりだったり、能力はわかりづらかった。またこれは今回初めて学んだことだけれど自分は、兎に角出力する人を他の人より相対的に高く評価するようだった、この場合の「兎に角」は中途半端な出力ではなく本当に色々なSNSにこれでもかというくらい自分自身の色も含めて出力することを指している。以上を踏まえると、より多く出力することで他人はその人の人となりや能力を把握しやすくなり、また兎に角出力することで、自分と似通った価値観の人間に興味を持たれる可能性があるということがわかる。

 ではなぜ積極的に出力することで、他人から自分の人となりや能力を把握しやすい状態にすべきなのか?また、なぜ自分と似通った価値観の人間が興味を持つ状態にすべきなのか?そうすることで誰がどんな利益を得るのか?

 まずわかりやすいところで自分が得られる利益として、自分の人となりや能力に価値を感じてくれる他人と接点を持つ可能性を上げることができる。そういった他人との接点が増えれば、例えば自分が所属する会社の採用活動時の候補者を推薦できるかもしれないし、例えば自分が転職するときの相談役になってもらえるかもしれない。もっと単純に自分の人となりや能力に価値を感じてくれる他人と楽しく話をする機会も増えるだろう。

 自分が所属する会社が得られる利益としては、前述のとおり採用時に社員の知り合いというきっかけで候補を得ることができる。前提としてその社員を会社が信頼できていることが条件となるが、これを満たしている場合その社員の知り合いをもし雇えた場合の会社としての利益が、見ず知らずの人間を雇えた場合と比べて見えやすいのは一つ利点だと思う。ただ社員が積極的に出力することの副次的な効果として、その社員が転職を行いやすい状況にある可能性はある、前述のとおり積極的に出力することでその社員は転職するときの相談役を多く得ている可能性があるからだ。だけれど、いざ転職しようと思ったときにうまく運べない社員よりも、相談役を通して本人にとってより魅力的な会社に転職してしまう社員のほうがもしかしたら価値はあるのではないか?他社が欲しいと思う社員はそれだけ市場価値が高いのだし、そういった社員を抱えている会社ははたから見て魅力的な会社なのではないか?もしこの疑問が間違っていなければ、積極的に出力する社員がいて、その社員は他社からの引き抜きも多いが、そういう社員がいるからこそ魅力的に見える会社に応募してきてくれる人も増えると予想できる。

 僕は今の会社も好きだし、過去に勤めた会社も全部好きだけど、なぜそうするのかを自分で説明できない状態にいたくない。これまで全くと言っていいほどできていないけれど、もっと世間に自分の文章なりを出力すべき理由については上記のとおり自分の中で整理ができたので、これからはこのブログかはたまたそれ以外の場所で積極的に出力していこうと思う。…「思う」は決断の結果として弱すぎるので、少なくとも週に一回はこのブログを更新することを目標にしたいなぁ

Promiseアンチパターン

Promise Anti-patternsを翻訳させて頂きました。著者のtaoofcodeから許可を頂いて翻訳、投稿しています。



Promiseは一度理解してしまえば簡単だが、いくつか頭を抱えさせるパターンがある。ここにあるのは私が経験したいくつかのアンチパターンだ。

ネストされたPromise

君は複数のPromiseをネストする:

loadSomething().then(function(something) {
    loadAnotherthing().then(function(another) {
                    DoSomethingOnThem(something, another);
    });
});

君がこれをする理由は、両方のPromiseの結果で何かをする必要があるからだ。then()は一つ前のPromiseの結果しかコールバックに渡せないのでここでチェインを用いることはできない。

だが、君がこうする本当の理由は君がall()メソッドを知らないからだ。

修正は:

q.all([loadSomething(), loadAnotherThing()])  
    .spread(function(something, another) {
        DoSomethingOnThem(something, another);
});

よりシンプルになった。q.allによって返されるPromiseは、2つのPromiseの結果の配列として解決されてthen()に渡される。spread()メソッドはこの配列をコールバックの仮引数に対して分配する。

壊れたチェイン

君は以下のようなコードを書く:

function anAsyncCall() {  
    var promise = doSomethingAsync();
    promise.then(function() {
        somethingComplicated();
    });

    return promise;
}

ここでの問題はsomethingComplicated()メソッドの中で発生したエラーを捕まえることができないことだ。Promiseはチェインされることを意図されて作られている、そのためそれぞれのthen()呼び出しは新しいPromiseを返却する。次のthen()はこの新しいPromiseに対しコールすべきだ。一般的に最後のコールはcatch()にする、そうすればチェインのどこで発生したエラーでもcatch()の中でハンドルできる。

上のコードでは君が最後のthen()の結果の代わりに最初のPromiseを返した時点でチェインが壊れている。

修正は:

function anAsyncCall() {  
    var promise = doSomethingAsync();
    return promise.then(function() {
        somethingComplicated()
    });   
}

常に最後のthen()の結果を返すべきだ。

コレクションのバカ騒ぎ

君の手元には要素の配列があり、要素個々に対し何かを非同期に実行したいとする。君は再帰を用いた以下のようなコードを書いている自分に気付く。

function workMyCollection(arr) {  
    var resultArr = [];
    function _recursive(idx) {
        if (idx >= resultArr.length) return resultArr;

        return doSomethingAsync(arr[idx]).then(function(res) {
            resultArr.push(res);
            return _recursive(idx + 1);
        });
    }

    return _recursive(0);
}

あぁ、なんて非直感的なコードか。問題はいくつチェインすることになるか分からないときのメソッドのチェインの仕方にある。map()reduce()を思い出そう。

修正は:

q.allはPromiseの配列を引数にとり、結果の配列に解決してくれる。q.allを使えば以下のように、簡単に非同期呼び出しをそれらの結果にマップできる:

function workMyCollection(arr) {  
    return q.all(arr.map(function(item) {
        return doSomethingAsync(item);
    }));    
}

再帰を使った(非)解決策と違って、このコードは全ての非同期呼び出しを並列に開始する。当然、時間的により効率的だ。

もしPromiseを連続に実行する必要があれば、reduceを使う。

function workMyCollection(arr) {  
    return arr.reduce(function(promise, item) {
        return promise.then(function(result) {
            return doSomethingAsyncWithResult(item, result);
        });        
    }, q());
}

最高に綺麗なコードではないけれど、間違いなくより綺麗なコードだ。

ゴーストPromise

非同期に何かをするメソッドと、同期に処理するメソッドがある。君は2つのコードを一貫して処理するために、その必要がないときでもPromiseを作成する。

var promise;  
if (asyncCallNeeded)  
    promise = doSomethingAsync();
else  
    promise = Q.resolve(42);

promise.then(function() {  
    doSomethingCool();
});

最悪のアンチパターンというわけではない、しかし絶対的に分かりやすい方法があるー「値かPromise」をQ()でくるめば良い。このメソッドは値かPromiseを引数にとって適宜処理する。

Q(asyncCallNeeded ? doSomethingAsync() : 42)  
    .then(
        function(value){
            doSomethingGood();
        })
    .catch( 
        function(err) {
            handleTheError();
        });

注釈:私は最初ここでQ.whenを使う方法を提示した。ありがたいことにKris Kowal*1がコメントでこれを訂正してくれた。Q.whenではなく、Q()を使うーこちらのほうがより明快だ。

過度に鋭利なエラーハンドラ

thenメソッドは引数を2つとる、fulfilledハンドラとrejectedハンドラである。君は以下のようなコードを書くかもしれない:

somethingAsync.then(  
    function() {
        return somethingElseAsync();
    },
    function(err) {
        handleMyError(err);
});

このコードの問題はfulfilledハンドラの中で発生したエラーがエラーハンドラに渡されないことだ。

修正は、別のthenの中でエラーハンドリングするようにすれば良い:

somethingAsync  
    .then(function() {
        return somethingElseAsync();
    })
    .then(null,
        function(err) {
            handleMyError(err);
        });

またはcatch()を用いて:

somethingAsync  
    .then(function() {
        return somethingElseAsync();
    })
    .catch(function(err) {
        handleMyError(err);
    });

こうすればチェインの中で発生したどんなエラーでもハンドリングされる保証がある。

忘れられたPromise

君はPromiseを返却するメソッドを呼び出す。でも君はこのPromiseのことを忘れて君自身のPromiseを作成する。

var deferred = Q.defer();  
doSomethingAsync().then(function(res) {  
    res = manipulateMeInSomeWay(res);
    deferred.resolve(res);
}, function(err) {
    deferred.reject(err);
});

return deferred.promise; 

このコードはPromiseの効果ーシンプルであることを全く利用できていない。たくさんの無意味なコードが記述されている。

修正は、単純にPromiseを返却するようにする。

return doSomethingAsync().then(function(res) {  
    return manipulateMeInSomeWay(res);
});

*1:Qの作者

JavaScript Promise ... イカした詳細

JavaScript Promises ... In Wicked Detailを翻訳させて頂きました、プロミスについて実装しながらその仕組みを学べるドキュメントです。著者のMatt Greer氏から許可を得て翻訳、公開しています。



私はここしばらく仕事でJavaScriptのプロミスを利用してきました。プロミスを使い始めたときは少し頭を悩ませたりもしましたが、今やかなり効率的にプロミスを利用しています。しかし、結局のところ、私はプロミスがどのように機能しているか理解できていませんでした。この文章はこのことに対する私の解です。この文章を最後まで読めば、プロミスについてあなたもよく理解できるでしょう。

この文章では、 ほぼ Promise/A+specに準拠したプロミスを目標にインクリメンタルにプロミスを実装しながら、プロミスが非同期プログラミングのニーズにいかにマッチしているか理解していきます。この文章はプロミスに対するある程度の理解を前提としています。もしまだプロミスに対する理解が足りない場合は、promisejs.orgをチェックすると良いでしょう。

目次

  1. なぜ
  2. 簡単なユースケース
  3. プロミスは状態を持っている
  4. プロミスのチェイン
  5. プロミスをリジェクトする
  6. プロミスの解決は非同期である必要がある
  7. まとめの前に…then/promise
  8. まとめ
  9. 参考文献

なぜ

なぜプロミスを詳細に理解する必要があるのでしょうか?ある物事がどのように機能しているのか正しく理解することは、これを利用する能力を向上させ、またトラブル時のデバッグをより容易にします。私は同僚と一緒にトリッキーなプロミスの振る舞いに悩まされた時にこの文章を書くことを思いつきました。もし今わかっていることをその時に知っていたなら、私はあの時悩まなかったと思います。

シンプルなユースケース

なるべくシンプルなところからプロミスの実装をはじめていきます。まず以下のコードを

doSomething(function(value) {
  console.log('Got a value:' + value);
});

以下のようにしてみます、

doSomething().then(function(value) {
  console.log('Got a value:' + value);
});

そのためには、doSomething()を以下から

function doSomething(callback) {
  var value = 42;
  callback(value);
}

以下のようにプロミスベースのコードに変更します。

function doSomething() {
  return {
    then: function(callback) {
      var value = 42;
      callback(value);
    }
  };
}

fiddle

これは単にコールバックパターンの些細なシュガーです。このままではとても無意味なシュガーです。しかし出発点としては十分だし、またプロミスの裏側にある核となる考えが見えてきました。

プロミスは結果の値の概念(the notion of an eventual value)をオブジェクトの中に取り込む

プロミスがこれほど興味深いのはこれこそが主な原因です。一度結果の値の概念をオブジェクトに取り込めれば、とても強力なことが実現できます。このことについては後でより詳細に説明します。

プロミス型を定義する

単純なオブジェクトリテラルでは今後の実装に耐えられません。拡張していけるように実際のPromise型を定義します。

function Promise(fn) {
  var callback = null;
  this.then = function(cb) {
    callback = cb;
  };

  function resolve(value) {
    callback(value);
  }

  fn(resolve);
}

そしてdoSomething()をこれを利用するように書き直します

function doSomething() {
  return new Promise(function(resolve) {
    var value = 42;
    resolve(value);
  });
}

ここで問題が一つあります。実行の順番を確認すると、resolve()then()の前に呼ばれているのが分かります、つまりresolve()が呼び出されたタイミングではcallbackがまだnullのままです。setTimeoutを用いた些細なハックでこの問題を隠蔽してみます。

function Promise(fn) {
  var callback = null;
  this.then = function(cb) {
    callback = cb;
  };

  function resolve(value) {
    setTimeout(function() {
      callback(value);
    }, 1);
  }

  fn(resolve);
}

fiddle

ハックを用いることで、我々の実装は何とか動くようになりました。

このコードは脆いし、悪い

ここまでの単純で貧弱なプロミスの実装は機能させるために非同期を用いなければなりません。この実装をもう一度失敗させるのは簡単です、then()を非同期にコールすると、コールバックが再びnullとなってしまいます。ここで、すぐに失敗してしまう実装を一度示したのは、ここまでの実装がthen()resolve()の重要性の理解をとても容易にするためです。これらはプロミスのキーとなるコンセプトです。

プロミスは状態を持っている

上記脆い実装は予期せずしてある事実を明らかにしています。プロミスは状態を持っています。実装を先に進める前に、プロミスがどんな状態を取り得るのか知っておく必要があります、そうすればこれら状態の間を正しく行き来することが可能になります。プロミスの取り得る状態を知ることで、我々の実装から脆さを取り除くことができます。

  • プロミスは値を待つためにpendingになり、また値と共にresolvedになることができる
  • 一度プロミスが値に解決されたら、プロミスはずっとその値を維持する、再度解決されることはない

(プロミスはまた拒否されることもあるが、このことについては後ほどエラーハンドリングの節で触れます)

プロミスの状態を明示的に追跡するよう実装を変更します、こうすることでハックを取り除くことができます

function Promise(fn) {
  var state = 'pending';
  var value;
  var deferred;

  function resolve(newValue) {
    value = newValue;
    state = 'resolved';

    if (deferred) {
      handle(deferred);
    }
  }

  function handle(onResolved) {
    if (state === 'pending') {
      deferred = onResolved;
      return;
    }

    onResolved(value);
  }

  this.then = funcition(onResolved) {
    handle(onResolved);
  };

  fn(resolve);
}

fiddle

実装は複雑になりました、しかし呼び出し元はthen()を好きな時に実行することができ、呼ばれた側はresolve()を好きなときに実行することができるようになりました。この変更で同期または非同期いずれにも対応できるようになっています。

これはstateフラグのおかげです。then()resolve()は共に新しいメソッドhandle()に処理を移譲しています、handle()は状況に応じて以下のいずれかを行います:

  • 呼ばれた側がresolve()を呼ぶ前に呼び出し元がthen()を呼んだ場合、この場合返却すべき値はまだありません。このときstatuspendingであり、呼び出し元のコールバックを後で使えるように保持しておきます。後でresolve()が呼ばれた時に、コールバックを実行し、このコールバック経由で値を渡すことができます。
  • 呼び出し元がthen()を呼ぶ前に呼ばれた側がresolve()を呼んだ場合: この場合は結果の値を保持しておきます。続いてthen()が呼ばれたときには既に値を返す準備ができています。

setTimeoutがなくなっていることに気づいたでしょうか?setTimeoutは後ほどまた登場しますが、一時に一事です。

プロミスではプロミスのメソッド呼び出しの順序は問題にはなりません。then()resolve()は目的にかなった時にいつでも自由に呼び出すことができます。これは結果の値の概念をオブジェクトに取り込んだことによる強力なメリットの一つです。

まだ実装すべき仕様は多く残っていますが、ここまでで既にとても強力なプロミスの実装ができています。then()を好きなだけ何回でも呼ぶことができ、毎回同じ値を得ることができます。

var promise = doSomething();

promise.then(function(value) {
  console.log('Got a value:', value);
});

promise.then(function(value) {
  console.log('Got the same value again:', value);
});

このプロミスの実装において、このことーthen()を何回でも呼ぶことができるーは完全には正しくはありません。もし反対のことが起きたら、つまりresolve()が呼ばれる前に呼び出し元がthen()を複数回呼んだ場合、then()呼び出しの最後の一回しか信用することはできません。プロミスの中に実行中のdeferredを一つではなく、リストで保持することでこの問題は解決できます。ここでは話をシンプルにするためにこの修正は行なっていません。

プロミスのチェイン

プロミスがオブジェクトの中に非同期の概念を取り込んでいるため、プロミスをチェインして、マップして、並列に又は連続に実行して…数々の便利なことを実現できます。プロミスを利用していると以下のようなコードをよく見かけます

getSomeData()
.then(filterTheData)
.then(processTheData)
.then(displayTheData)

getSomeData()はプロミスを返却しています、これはひき続くthen()呼び出しから読み取れます、しかし最初のthen()の結果もまたプロミスである必要があります、1度目のthen()の結果に対し再度then()が呼ばれているからです(2度も!)。then()でプロミスを返却するように修正できれば、物事はもっと面白くなります。

then()は常にプロミスを返す

以下はチェインをサポートするようアップデートしたプロミスの実装です

function Promise(fn) {
  var state = 'pending';
  var value;
  var deferred = null;

  function resolve(newValue) {
    value = newValue;
    state = 'resolved';

    if (deferred) {
      handle(deferred);
    }
  }

  function handle(handler) {
    if (state === 'pending') {
      deferred = handler;
      return;
    }

    if (!handler.onResolved) {
      handler.resolve(value);
      return;
    }

    var ret = handler.onResolved(value);
    handler.resolve(ret);
  }

  this.then = function(onResolved) {
    return new Promise(function(resolve) {
      handle({
        onResolved: onResolved,
        resolve: resolve
      });
    });
  };

  fn(resolve);
}

fiddle

かなり実装が複雑になって来ました。インクリメンタルにゆっくりとこの実装を組み立ててきてよかったと思いませんか?この変更でのキーポイントはthen()が新しいプロミスを返却していることです。

then()は常に新しいプロミスオブジェクトを返すので、少なくとも常に一つのプロミスは作成され解決され無視されます。これは無駄なことに見えるかもしれません。コールバックによるアプローチはこの問題を孕んでいません。これはプロミスに対し繰り返し言及される問題の一つであり、いくつかのJavaScriptコミュニティがプロミスを避ける理由の一つです。

2つ目のプロミスはどんな値に解決されるのでしょうか? 2つ目のプロミスは最初のプロミスの返却値を受け取ります 。これは実装コードのhandle()の終わりで確認できます。handlerオブジェクトはonResolvedコールバックと一緒にresolve()への参照を持っています。一つ以上のresolve()のコピーが生成されて、各プロミスは自分のためのresolve()のコピーと、そのresolve()のコピーを実行するためのクロージャを持っています。これが最初のプロミスと2つ目のプロミスとの橋渡しとなっています。最初のプロミスは以下の行で終えることができます:

var ret = handler.onResolved(value);

例の中ではhandler.onResolvedは以下です

function(value) {
  console.log("Got a value:", value);
}

別な言い方をすれば、これが最初のthen()呼び出しの時にコールバックの引数として渡されます。この最初のハンドラの戻り値が2つ目のプロミスを解決するために用いられます。こうしてチェインが実現されています。

doSomething().then(function(result) {
  console.log('first result', result);
  return 88;
}).then(function(secondResult) {
  console.log('second result', secondResult);
});

// 出力は
//
// first result 42
// second result 88

doSomething().then(function(result) {
  console.log('first result', result);
  // 明示的に何も返さない
}).then(function(secondResult) {
  console.log('second result', secondResult);
});

// 今度の出力は
//
// first result 42
// second result undefined

then()は常に新しいプロミスを返却するので、このチェインは好きなだけ深くすることができます。

doSomething().then(function(result) {
  console.log('first result', result);
  return 88;
}).then(function(secondResult) {
  console.log('second result', secondResult);
  return 99;
}).then(function(thirdResult) {
  console.log('third result', thirdResult);
  return 200;
}).then(function(fourthResult) {
  // 続く…
});

ではこの例でもし全ての結果が最後に欲しくなったときはどうするのでしょうか?チェインでは手動で結果を持ち回す必要があります。

doSomething().then(function(result) {
  var results = [result];
  results.push(88);
  return results;
}).then(function(results) {
  results.push(99);
  return results;
}).then(function(results) {
  console.log(results.join(', ');
});

// 出力は
//
// 42, 88, 99

プロミスは常にひとつの値に解決される。もし一つ以上の値を渡したい場合、何らかの形で多値を作る必要がある(配列、オブジェクト文字列の連結、など)

より良い解決策としてはプロミスライブラリのall()メソッドか、プロミスの利便性を向上させるその他の数多くのユーティリティメソッドを用いることです、これらライブラリのAPIの探索はあなたに譲ります。

コールバックはオプション

then()の引数に渡すコールバックは必須ではありません。もし与えられなかった場合、プロミスはひとつ前のプロミスと同じ値に解決されます。

doSomething().then().then(function(result) {
  console.log('got a result', result);
});

// 出力は
//
// got a result 42

この部分の処理はhandle()の中で確認できます、コールバックが無い場合、handle()は単純にプロミスを解決して処理を終えています。この時のvalueは前のプロミスの値のままです。

if(!handler.onResolved) {
  handler.resolve(value);
  return;
}

チェインの中でプロミスを返却する

ここまでのチェインの実装は少し愚直です。渡されたresolvedな値を闇雲にひき続く処理に渡していっています。解決される値の一つがプロミスの場合どうなるでしょうか。例えば

doSomething().then(result) {
  // doSomethingElseはプロミスを返却する
  return doSomethingElse(result)
}.then(function(finalResult) {
  console.log("the final result is", finalResult);
});

現状では、上記コードは期待する通りに動きません、finalResultは実際には完全に解決された値にはならず、プロミスのままでしょう。期待される結果を得るためには以下の様に記述する必要があります

doSomething().then(result) {
  // doSomethingElseはプロミスを返却する
  return doSomethingElse(result)
}.then(function(anotherPromise) {
  anotherPromise.then(function(finalResult) {
    console.log("the final result is", finalResult);
  });
});

このような冗長なコードを書きたたがるプログラマがいるでしょうか?プロミスの実装でシームレスにこれをハンドルするよう修正してみます。修正は単純で、resolve()の中で解決された値がプロミスの場合を特別扱いするようにします

function resolve(newValue) {
  if (newValue && typeof newValue.then === 'function') {
    newValue.then(resolve);
    return;
  }
  state = 'resolved';
  value = newValue;

  if (deferred) {
    handle(deferred);
  }
}

fiddle

プロミスが返却される限り再帰的にresolve()を呼び出し続けます、一度プロミスではないものが返却されたら元の通り処理が続きます。

これは無限ループになる可能性を孕んでいます。Promises/A+ specでは実装に無限ループを検知することを推奨していますが、要求はしていません。


また、この実装は仕様に準拠していません。またこの文章中の実装全てこの点について仕様に完全に準拠しているわけではありません。さらなる理解のためにはプロミスの解決処理を読むことをおすすめします。

newValueがプロミスかどうか判定するための処理がとてもルーズなものであることに気づいたでしょうか?then()メソッドの存在だけを確認しています。このダックタイピングは意図的なものです。こうすることで異なるプロミスの実装が相互に対話することが可能になります。実際に複数のプロミスライブラリの混合は普通に起こり得ることです、あなたが利用するサードパーティ製のライブラリは、それぞれが異なるプロミスの実装を用いている可能性があるためです。

異なるプロミスの実装はそれらが仕様に準拠している限り相互に対話することができる

チェインを手に入れて、我々のプロミスの実装はかなり完成に近づきました。でもこれまでエラーハンドリングについて一切考慮できていません。

プロミスをリジェクトする

プロミスは処理中に都合が悪くなったら 理由 と共に 拒否 する必要があります。呼び出し元はこれが発生した時にどのようにしてこのことを知ることができるでしょうか?呼び出し元はthen()の第二引数にコールバックを渡すことでこれを知ることができます。

前に言及したとおり、プロミスは pending から resolved 又は rejected のいずれかの状態に遷移する、両方には遷移しない。言い換えれば、上記コールバックのいずれか一つのみが呼ばれる。

プロミスはreject()によって拒否を可能にします、reject()resolve()の邪悪な双子です(evil twin)。以下はdoSomething()にエラーハンドリングのサポートを加えたものです

function doSomething() {
  return new Promise(function(resolve, reject) {
    var result = somehowGetTheValue();
    if (result.error) {
      reject(result.error);
    } else {
      resolve(result.value);
    }
  });
}

プロミスの実装では拒否をハンドリングする必要があります。プロミスが拒否されたらすぐに、それ以降の全ての下流の(downstream)プロミスもまた拒否される必要があります。

以下は拒否をサポートした完全なプロミスの実装です

function Promise(fn) {
  var state = 'pending';
  var value;
  var deferred = null;

  function resolve(newValue) {
    if (newValue && typeof newValue.then === 'function') {
      newValue.then(resolve, reject);
      return;
    }
    state = 'resolved';
    value = newValue;

    if (deferred) {
      handle(deferred);
    }
  }

  function reject(reason) {
    state = 'rejected';
    value = reason;

    if (deferred) {
      handle(deferred);
    }
  }

  function handle(handler) {
    if (state === 'pending') {
      deferred = handler;
      return;
    }

    var handlerCallback;

    if (state === 'resolved') {
      handlerCallback = handler.onResolved;
    } else {
      handlerCallback = handler.onRejected;
    }

    if (!handlerCallback) {
      if (state === 'resolved') {
        handler.resolve(value);
      } else {
        handler.reject(value);
      }

      return;
    }

    var ret = handlerCallback(value);
    handler.resolve(ret);
  }

  this.then = function(onResolved, onRejected) {
    return new Promise(function(resolve, reject) {
      handle({
        onResolved: onResolved,
        onRejected: onRejected,
        resolve: resolve,
        reject: reject
      });
    });
  };

  fn(resolve, reject);
}

fiddle

reject()関数自信の追加に加えて、handle()内部で拒否をハンドルする必要があります。handle()の中で、stateの値に依存して拒否するかそれとも解決するか決定しています。引き続くプロミスのresolve()又はreject()呼び出しが自分自身のstate値を適宜に設定できるように、このstateの値は次のプロミスに渡されていきます。

プロミスを利用する際、エラーのコールバックを渡し忘れるのは容易なことです。もしそうした場合、プロミスの処理中に都合の悪いことが起こっている兆候を 一切 知ることができなくなります。せめて、チェインの最後のプロミスにはエラーコールバックを指定するべきです。飲み込まれたエラーについては以降の説で詳しく説明します。

予期せぬエラーが発生した場合も拒否されること

ここまでのプロミスの実装は既知のエラーに対してしか責任をとっていません。unandled exceptionは発生し得るし、その場合全てがクラッシュしてしまいます。プロミスの実装はこれらの例外をキャッチして適宜拒否することが必要不可欠です。

そのためにはresolve()をtry/catchブロックでくるむ必要があります

function resolve(newValue) {
  try {
    // ... 前の通り
  } catch(e) {
    reject(e);
  }
}

また呼び出し元から渡されたコールバックがunandled exceptionを発生させないことを保証する必要もあります。これらコールバックはhandle()の中で呼ばれています、最終的な実装は以下のようになります

function handle(handler) {
  // 前と同じ

  var ret;
  try {
    ret = handlerCallback(value);
  } catch (e) {
    handler.reject(e);
    return;
  }

  handler.resolve(e);
}

プロミスはエラーを飲み込む

プロミスを正しく理解していないとエラーがプロミスに飲み込まれてしまう、これはよく陥る罠である

以下の例を考えてみます

function getSomeJson() {
  return new Promise(function(resolve, reject) {
    var badJson = "<div>uh oh, this is not JSON at all!</div>";
    resolve(badJson);
  });
}

getSomeJson().then(function(json) {
  var obj = JSON.parse(json);
  console.log(obj);
}, function(error) {
  console.log('uh oh', error);
});

fiddle

何がおきたのでしょうか?then()の中のコールバックは有効なJSONを期待しています。そのため単純に引数にもらったjsonをパースしようとしていますが、ここで例外が発生します。でもエラーコールバックはちゃんと指定して有ります、そのためこのコードは問題ないのではないでしょうか?

いいえ、 このエラーコールバックは実行されないだろう fiddleでこの例を実行してみると、出力は何も得られないはず。エラーは置きないし、何も置きない。純粋に 寒気のする 静けさだけが残る(Pure chilling silence)

なぜでしょうか?unhandled exceptionthen()のコールバックの中で発生しているため、これはhandle()の中でcatchされます。そしてhandle()は今対応中のプロミスではなくthen()の返却したプロミスを拒否します、今対応中のプロミスは既に解決済みのため拒否されません。

then()のコールバックの中では、対応中のプロミスは既に解決されていることを常に覚えているべきだ。コールバックの結果はこのプロミスに対し何の影響も与えない

上記エラーを捕まえたい場合、さらに下流でエラーコールバックを渡す必要があります。

getSomeJson().then(function(json) {
  var obj = JSON.parse(json);
  console.log(obj);
}).then(null, function(error) {
  console.log("an error occured: ", error);
});

これで正しくエラーをログできるようになりました。

筆者の経験では、これがプロミスの最も大きな落とし穴であす。より良い解決策について次の節で説明します。

レスキューのためのdone()

ほとんどの(全部ではないが)プロミスライブラリはdone()メソッドを持っています。これは上記then()の落とし穴を回避する点を除いて、then()にとても良く似ています。

done()then()が呼べるタイミングではいつでも呼べます。異なる点はdone()はプロミスを返却しないこと、そしてdone()の中で発生したunhandled exceptionはプロミスの実装側ではcatchされないことです。別な言い方をすれば、done()は全体のプロミスチェインが完全に解決されたタイミングを表現しています。先ほどのgetSomeJson()の例はdone()を用いることでよりロバストなコードに修正できます。

getSomeJson().done(function(json) {
  // これが投げるとき、それは飲み込まれない
  var obj = JSON.parse(json);
  console.log(obj);
})

done()もまたthen()と同じくエラーコールバックを引数に取りますーdone(callback, errback)ーそして全てのプロミスが解決ーdoneされているので、発生したどんなエラーでも知ることができます。

done()は(少なくとも今は)Promises/A+ specの一部ではない、なのであなたの選択するライブラリはこれを持っていないかもしれない

プロミスの解決は非同期である必要がある

最初の方の実装でsetTimeoutを用いたハックを行いました。一度このハックを取り除いた後これまでsetTimeoutは登場してきませんでした。しかし実際には、Promises/A+ specではプロミスの解決は非同期に行うことを要求しています。単純にhandle()の実装をsetTimeoutコールにくるめばこの要求を満たすことができます。

function handle(handler) {
  if(state === 'pending') {
    deferred = handler;
    return;
  }
  setTimeout(function() {
    // ... as before
  }, 1);
}

これで要求に準拠することができました。実際には現実のプロミスライブラリはsetTimeoutを使わない傾向にあります。ライブラリがNodeJS向けの場合はprocess.nextTick、ブラウザ向けであれば新しいsetImmediatesetImmediate shim(setImmediateこれまでIEのみがを具備していました)、又はKris Kowalのasapのような非同期ライブラリを利用している可能性があります(Kris Kowalは有名なプロミスライブラリであるQの作者です)。

なぜ仕様はプロミスの解決に非同期を要求するのか

プロミスの解決を非同期にすることで、実行フローに一貫性と信頼性を保証することができるようになります。以下の混みいった例を考えてみます

var promise = doAnOperation();
invokeSomething();
promise.then(wrapItAllUp);
invokeSomethingElse();

ここでのコールフローはどうなっているでしょうか?関数の名前から、invokeSomething() -> invokeSomethingElse() -> wrapItAllUp()のフローが推測できます。しかし今の我々の実装では、コールフローはプロミスが同期に解決されるか非同期に解決されるかに依存して変わります。もしdoAnOperation()が非同期に動く場合、コールフローは推測通りです。でももし同期に動いた場合、実際のコールフローはinvokeSomething() -> wrapItAllUp() -> invokeSomethingElse()であり、期待したものにはならないでしょう。

上記のような状況に対応するために、プロミスはたとえ必要なくとも常に非同期に解決されます。プロミスを非同期にすることで、利用者の驚きを減らし、利用者が自分のコードを理解するときに非同期性について考えずにプロミスを利用することを可能にします。

まとめの前に … then/promise

多くのフル機能のプロミスライブラリが存在します。then organizationのpromiseライブラリはシンプルなアプローチをとっています。このライブラリは仕様に準拠すること、それ以上を具備しないことを目標としています。このライブラリの実装を見れば、見慣れたコードに見えると思います。then/promiseはこの文章中で実装したプロミスの基礎であり、我々は ほとんど 同じプロミス実装を組み立ててきました。Nathan ZadoksとForbes Lindsayに、彼らの素晴らしいライブラリとJavaScriptのプロミスへの働きかけについて感謝します。Forbes Lindsayはまた冒頭で言及したpromise.orgサイトの貢献者でもあります。

実際の実装とこの文章での実装では幾つかの相違点があります。これはPromises/A+ specの中にまだこの文章中で触れていない詳細がもっとあるためです。仕様を読んで見ることをお勧めします、この仕様は短く直感的です。

まとめ

ここまで読んでくれてありがとうございます。私達はプロミスの核となる部分をカバーしました、そしてこの核のみが仕様に記載されている内容です。多くの実装はもっとたくさんの機能を提供しています、all()spread()race()denodeify()、など他にも多くの機能があります。プロミスで実現可能な事を知るためにBluebirdのAPIドキュメントを見てみることをお勧めします。

一度、プロミスがどう機能しているのか、そしてプロミスを利用する際の注意点を理解したら、私はプロミスを本当に好きになりました。プロミスは私のプロジェクトにおけるコードをとてもクリーンでエレガントなものにしてくれました。まだ話すべきことはたくさんあります、この文章は始まりに過ぎません。

もしこの文章が面白かったら。私をTwitter上でフォローしてください、このようなガイドをまた書いた時にツイートします。

参考文献

プロミスに関し優れた文献が多く有ります

誤記を見つけましたか?指摘は歓迎です、emailissueを通じて教えてください。