hiroportation

ITの話だったり、音楽の話、便利なガジェットの話題などを発信しています

アジャイル開発の進め方

私が経験したアジャイル開発手法(スクラム)の進め方についてまとめてみました。
基本的に上から読み進めれば、アジャイル開発の流れを理解できるように書いています。
一部自己解釈が含まれている可能性があるためご了承ください。


1. そもそもアジャイル開発とは?

  • 設計は最初からガッチリさせず、小さな単位で開発とテストを繰り返し、少しずつ不明点を解消しながら全体を明確にする感じと思っている
  • アジャイルとは「素早い」という意味ですが、必ずしも最短で開発を完了させる、ということではないです
  • 早い段階で開発フェーズを始められるため、やってみてわかるエラーや不明確部分などの想定外に早期対応でき、大きな手戻りを減らせます
  • 徐々に仕様を固めていくため突如の仕様変更などにも応えやすい


2. 用語解説

用語 説明
スプリントmtg 前スプリント期間での反省や次スプリント期間でのやることなどを決めるためのmtgです。1週間〜2週間の期間を設けることが多いです。
デイリースクラム 進捗状況を確認するためのmtgです。毎日メンバーが集まれる時間に実施します。メンバに遅延が発生していないか、共有事項等はないかなどを確認します。
ストーリー ユーザーストーリーとも呼び、「要求事項」のようなものです。開発者はこの要求事項を元に開発に必要なタスクを決めていきます。
ストーリーポイント ストーリーを解決するための工数で、基準ストーリーから相対的に決めていく。
基準ストーリー ストーリーポイントを決めるために使用する基準で、大抵は単純な作業に設定します。
スクラムマスター スプリントmtgスクラムmtgでの司会を務める人物を指します。
アクティブバックログ スプリント内でやるべきストーリー群のこと。
ベロシティ 開発、作業スピードのこと。


3. アジャイル開発のやり始め

  • まず抽出した要件定義からどれくらいの期間がかかるか予測する
    • プロジェクトに必要なトータルストーリーポイントをあらかじめ予想して算出する(これが大変だと思う)
      • ベロシティを考慮していないため、何人かでポイント数については議論しておいた方が良いと思います
    • トータルストーリーポイントから何スプリントでプロジェクトが完了できるかを見積もる
    • 規模によりますが、2、3スプリントくらいはバッファを積んでおいた方が良いかも
    • このとき、1スプリントのストーリーポイントも算出しておいて、後々のスプリントmtgで遅延予測などに使用しましょう
    • 以下のようなイメージですね(やっぱり図にした方がわかりやすい)

規模を見積り、ベロシティを計測して、期間を予測する - YouTube

  • アジャイル開発のルールを決める
    • チケットシステムは何を使うか?(Jira?Redmine?など)
    • スプリント単位はどうするか?(何日?何週間?)  * スプリント中はmtgで決めた目標に向かってチームで最大限に動く期間とする感じ
      • つまり仕様変更や開発が活発な場合は、定期的なスプリントmtgを増やすためスプリント単位を小さくすると良い
    • スプリントmtgはいつ何時にやるのか?何を話すのか?
    • デイリースクラムは何時にやるか?何を話すのか?
    • チケットには何を書くのか、どう運用するのか?
    • スクラムマスターは誰が担当するのか?
  • 決めたらチーム内でルールについて議論をし合う
  • チケットシステムを準備しておく


4. プロジェクトを進めてみる

日常のプロジェクト内では毎日デイリースクラムで進捗確認をし、一定期間毎にスプリントmtgで開発等に関する反省や次スプリントでやること等を考えます。
全体としては以下のような流れです。

f:id:thelarklife1021:20211201024520p:plain:h300


4.1. スプリントmtg前にやること

  • 要件事項や、やりたいことをにもとにしてストーリーチケットを作成しておく
  • ストーリーチケットには具体的にやることを記載する
    • チケットの内容を抽象的なままで先に進めてしまうと、後々のストーリーポイント決めがブレるし作業遅延の原因になる
    • やることが不明確なタスクは、調査ストーリー又は調査タスクとして扱い、優先的に解決させましょう


4.2. 最初のスプリントmtg

  • 各メンバーはスプリント期間中の稼働時間を提示する
  • 作成したストーリーチケットを元にチームで必要なタスクが十分かを確認する

  • 基準ストーリーを決めておく

    • メンバー全員が理解できる単純な作業が良いと思います
  • ストーリーポイントを決める
    • 1スプリント内で実施できそうな分のストーリーポイントを以下の流れで決めていきます
    • 各メンバーは基準ストーリーから相対的にポイントがいくつになるのかを決める
    • メンバー全員で一斉にポイントを出し合う(やり方は自由)
    • ポイントがバラけた場合は高く設定した人のポイントを採用する
      • ただしポイントの低い人と高い人に大きな開きがある場合はその場でお互いの認識を話し合ってポイントをどっちに寄せるのかを決める
    • 詳細不明なタスクはストーリーポイントが不透明になるため、調査タスクとしておき、ポイント見積りはしないようにする
      • 調査タスクはボリュームが不透明なためなるべく優先させて解決させましょう
      • また、あとで完了した調査タスクはスプリントmtg前までに対応者がおおよそのストーリーポイントを設定しておきましょう
  • ストーリーで優先したいものはアクティブバックログバックログの上部に持っていきましょう
  • 各ストーリーの担当はmtg内で決めても良いですし、各々でやりたいストーリーチケットを決めても良いですが優先順位は意識しましょう


4.3. スプリントmtg後にやること

  • 各メンバーは担当するストーリーチケットのやることを元に子チケットを作成してから作業を開始する
    • なるべくストーリーに書いたやることを、そのまま子チケット名として作成するとわかりやすい
    • 着手するタイミングで考えられる子チケットは全て作成しておき、後付けがなるべく発生しないようにする
    • 該当子チケットを開始するタイミングでステータスを開始に変更する
  • チケットのステータス更新は毎日行うようにする
    • 毎日進捗を可視化できる状態にしておく
    • 1日で終わらなかった子チケット(タスク)は分割して、1日でどれだけ進んだのかをわかるようにする
      • これをしないと進捗が不透明になってしまうので、めんどくさがらずやる習慣をつけましょう


4.4. デイリースクラム

  • 各自のスプリントの進捗を確認し、抱えている問題や、自分が解決したこと等を共有し、必要に応じてスプリントバックログを見直します。
  • デイリースクラムでの話す内容
    • 今日やった事の進捗 *「予定通り」か「遅れているか」を話し合う
      • 逆にそれ以外のことはなるべく当事者同士で話し合い、他の人の時間を奪わないように意識する
      • 遅れている場合はスプリント内に追われそうかを軸に報告する
    • 明日やる事
    • 共有するべきこと
  • スプリント内での解決が難しい場合は作業分担をして、最悪次スプリントに伸ばすかどうかも検討してください


4.5. 2回目以降のスプリントmtg

  • 調査タスクなどのストーリーポイントが更新されていることを確認する
  • 2回目以降は前スプリントの反省をして次スプリントに活かしましょう
    • 設定したストーリーが終わり切らなかったのはなぜか?
      • 追加業務が発生してしまった
      • 想定外のエラーが発生してしまった
      • ストーリーポイント低く設定してしまった
      • など
    • ベロシティが大きく変動したのはなぜか?
      • ベロシティが正確ではないから
      • チームで良い開発手法を活用していたから
      • まだチームで開発し始めたばかりだから
      • など
    • ベロシティを求めてチームのパフォーマンスを理解する
      • スプリント内で完了した合計ストーリーポイントとメンバーの合計稼働からベロシティを求める(=実績)
      • 次に、求めたベロシティと次スプリントの稼働予測から次スプリントにおけるストーリーポイントを決めます(=予測)
      • 計算方法は以下です
実績:pt / bd = pt/bd
予測:pt/bd * bd = pt

pt:合計ストーリーポイント(道のり)
bd:全メンバーの稼働時間(時間(日単位))
pt/bd:ベロシティ(速度)
  • 算出した実績ベロシティ(pt/bd)についてチームで分析し意見を言い合う
    • ベロシティの値が予想より大きい場合に考えられること
      • チームが開発について習熟し始めてる
      • 開発に便利なツール類を導入した
      • ストーリーポイントの付け方が甘い
      • など
    • ベロシティの値が予想より小さい場合に考えられること
      • 具体的にタスクが整理できていない
      • 急遽休むメンバーがいた
      • 開発の仕方に問題がある
        • チームで情報共有ができていない
        • メンバーにやる気がない
      • ストーリーポイントの付け方が厳しい
      • など
    • ベロシティの値が予想値に近いまたは等しい
      • ベロシティが連続で予想値に近似しているとチームの開発速度が実際に見えてきていることになるので良い傾向
      • その状態で次スプリントは、少し多めのベロシティを設定してチーム力を上げていきましょう
  • 算出した実績ベロシティから次スプリントで完了できそうな量のストーリーポイント(pt)の予測を算出します
  • 算出したストーリーポイントから実際にプロジェクトがあらかじめ算出した予測期間が完了できそうかを確認しておきます
    • トータルストーリーポイントから分割したストーリポイントよりもかなり低い場合
      • 開発期間延長を検討
      • メンバーの追加アサインを検討
      • プロジェクト内で開発するコンポーネント等を削る
        • 意外とこれが大事な気がする
        • 完成形を最初から作らず最低限のものを作ることに努める
  • 予想したストーリーポイント分、次スプリントで実施するストーリーとそのポイントを決めていきます
    • やり方は1回目と同じです


5. スクラムマスターがやること

  • スプリントmtg
    • チケットが最新であることの確認
    • スプリントmtgの進行
  • デイリースクラム
    • デイリースクラムの進行
    • 進捗していないタスクはその理由を確認する
    • ある人のタスクを別の人がブロックしていないか確認する
  • あとは経験!


6. アジャイル開発の目的を再認識しましょう

  • 手戻りを徹底的に無くしたい

    • チケットは具体的に書いて、チーム内のコミュニケーションミスを極力無くすように努力する
    • スプリント期間が長くなり過ぎないように気をつける
  • プロジェクトを順調に進めるため、毎スプリントのストーリーは全て完了させたい

    • ベロシティから最適な人数、開発期間を早めに算出するようにする
    • ストーリーチケットのやることは具体的に書くようにし、わからないことは人に聞き、生産性を最大限にあげよう
  • ベロシティは実体と合っているのか

    • ベロシティ値は高ければ良いというわけではなく逆に大きく予測よりも高く達成した場合は、ストーリーポイントの見積もりが甘い可能性がある
    • ベロシティはチームの開発スピードを指すので、チームみんなが正直な気持ちでストーリポイントの算出をすることが大事
    • 前スプリントと比較してベロシティ値を無理に高く設定するのはやめた方が良い
  • プロジェクトゴールに向けて気づいた追加タスクややりたい事はチケット化して忘れない内にバックログに溜めておきましょう


7. Q&A

  • ストーリーポイント決めで高めに設定した人の方を採用する理由は?

    • 低いポイントを採用してしまうと対応するメンバーによってはストーリーを予定期間に解決できない可能性が出てくるから
  • チケットは具体的にどこまで書くのが良いのか?

    • 最善はチームメンバー全員が背景からやることまでを全て具体的にイメージできるようになることです
    • これができていないとストーリーポイントがブレたり、メンバーによっては想定以上にタスク完了までの時間がかかってしまう恐れがあります
    • よく手を抜いて省略してしまうことが多いですが、間違って作業して手戻りが発生するよりは遥かにマシなので、やることは具体的に記載し、わからないところは調査タスクとして分割するようにしましょう


以上

Amazon SageMaker入門

Amazon SageMakerを入門向けにいくつかまとめてみました。
(随時 追加していきたいと思います)


1. SageMaker サービス全体像

f:id:thelarklife1021:20211031011627p:plain:h300

(引用先:https://aws.amazon.com/jp/sagemaker/)

SageMakerには様々な機能が存在しますので、今回は一番入門しやすい部分に絞って触ってみたいと思います。


2. SageMaker Studio を準備

SageMaker を始める際にはまずドメインと呼ばれるを専用環境をVPC内に定義する必要があります。
このドメインの中で学習に必要なインスタンスを立ち上げたり、推論器に使うエンドポイントを使ったりなどをします。

f:id:thelarklife1021:20211031023945p:plain:h300

SageMaker用に使う、アクセスロールやVPC、ノートブック共有先、それとJumpStartを設定します(土台の準備)。
JumpStartは事前構築済みモデルなどを使うことができる機能で、機械学習未経験者はまずこれを触っていくのが良いと思います。

f:id:thelarklife1021:20211031031633p:plain:h300

しばらくするとDomainのステータスがReadyになります。
この状態ですと関連リソースの定義を行なっているだけで、デプロイしているわけではないため、料金は発生しません。

f:id:thelarklife1021:20211031033316p:plain:h300

土台が出来上がったので、利用者を設定していきます。

f:id:thelarklife1021:20211031035255p:plain:h300

ユーザ作成が完了するとユーザ毎にSageMaker Studioが割り当てられます

f:id:thelarklife1021:20211031035407p:plain:h150


3. Jumpstart で物体検知(YOLO)を試す

Studioが立ち上がりましたら、Jumpstartにてモデルを選択します。

f:id:thelarklife1021:20211031120655p:plain:h300

今回は物体検知に使われるYOLOを選択します。
既存のモデルを使うため、このタイミングでEndpointを作成することによって、推論器として利用できます。

f:id:thelarklife1021:20211031121224p:plain:h300

Endpoint Status が「In Service」になりましたら、Notebookを開いて、YOLOモデルを実際に使っていきます。

f:id:thelarklife1021:20211031124106p:plain:h150

立ち上げたNotebookのコードを順に実行してトレーニング・モデルの評価を実施します。
今回のモデルセットでは以下の動きをします。

順序 実行内容
1 Jumpstart用のS3からイメージのダウンロード
2 イメージから物体検知を行い、エンドポイントを作成
3 バウンディングボックスにてモデル予測を可視化

f:id:thelarklife1021:20211031131358p:plain:h300

二つ目まで実行すると、下記のように物体検知した座標とそのラベルが決まる
(対象コードにてShift+Enter)

[[[0.3936842679977417,
   0.631632387638092,
   0.5039596160252889,
   0.9389058947563171],
  [0.15203692515691122,
   0.7994152307510376,
   0.29037004709243774,
   0.9981174468994141],
  [0.28708767890930176,
   0.6139858961105347,
   0.3966217041015625,
(~snip~)
 ['chair',
  'chair',
  'chair',
  'chair',
  'diningtable',
  'chair',
(~snip~)

三つ目まで実行すると以下の通り予測結果(バウンディングボックス)が出力される

f:id:thelarklife1021:20211031175403p:plain


4. Jumpstart で自然言語処理(BERT)を試す

BERTにて自然言語処理を行います。ここでは、文書内容からポジティブかネガティブかを理解し、出力します。
こちらもYOLOと同様の手順で進めていきます。

f:id:thelarklife1021:20211101045443p:plain

BERTでは以下の動きをします。

順序 実行内容
1 boto3とjsonのインポート
2 BERTに入力するテキスト2つを定義
3 BERTを使ったエンドポイントを作成

f:id:thelarklife1021:20211101060146p:plain

3ステップ実行すると以下の通り2つの予測結果が出力されます。
結果からは上部は「ポジティブ」、下部は「ネガティブ」

Inference:
Input text: 'astonishing ... ( frames ) profound ethical and philosophical questions in the form of dazzling pop entertainment'
Model prediction: [-4.22542048, 4.55869722]
Model prediction mapped to labels: positive

Inference:
Input text: 'simply stupid , irrelevant and deeply , truly , bottomlessly cynical '
Model prediction: [3.8848784, -4.23237181]
Model prediction mapped to labels: negative

Google翻訳した結果は以下ですが、何となくあってるかも。

text1:驚くべき...(フレーム)まばゆいばかりのポップエンターテインメントの形での深い倫理的および哲学的質問
text2:単に愚かで、無関係で、深く、本当に、底なしに冷笑的です


5. SageMaker Autopilot 使用した AutoMLによる機械学習を試す

これまでは既存モデルを使用してデータを作成していましたが、ここではAutoMLを使用した機械学習を進めます。
以下公式サンプルを使用します。このデータセットは銀行の電話でのダイレクトマーケティングを行った結果、口座開設まで至れるかをyes or noで予測するものとなっています。

github.com

左のGitタブのから /amazon-sagemaker-examples/autopilot/sagemaker_autopilot_direct_marketing.ipynb をクローンしてきます。

f:id:thelarklife1021:20211101012811p:plain

後は上から一つずつ実行していきます。
実行概要は以下になります。

順序 実行内容
1 sagemakerやboto3といった必要なライブラリをインポート
2 機械学習に使うデータセットのダウンロード
3 S3にデータセットをアップロード
4 SageMaker Autopilot ジョブを設定
5 SageMaker Autopilot ジョブを実行
6 SageMaker Autopilot ジョブの進捗をトラッキング
7 結果

6.の進捗で AnalyzingData -> FeatureEngineering -> ModelTuning -> GeneratingExplainabilityReport -> Completed と変化しており、
特徴量エンジニアリングやハイパーパラメータチューニングなどを自動で行なっています。

この時のジョブに関しては以下コンソールの「処理中」から確認できます。

f:id:thelarklife1021:20211101040200p:plain

今回の学習結果で以下の通り口座開設に至った結果を出力しています。

f:id:thelarklife1021:20211101041937p:plain

出力全体を確認したい場合はS3の inference_result.csv に出力されるのでそこを確認します。


6. データ準備について触ってみる (Data Wrangler)

機械学習用のデータ収集、前処理を行う場合は Data Wrangler を使います。
今回は以下鉄板不良のデータセット(CSV)を使用します。

www.openml.org

データセットをにS3コンソールからアップロードします。

f:id:thelarklife1021:20211101051134p:plain

S3から Data Wrangler にインポート。

f:id:thelarklife1021:20211101051229p:plain

Data flow として表示され、データを変換したり分析したりといったことが可能になります。

f:id:thelarklife1021:20211101055042p:plain

データ分析する場合は以下の通りテーブルやグラフなど様々形で可視化できます。

f:id:thelarklife1021:20211101055430p:plain

データ変換の場合は以下の通りデータフローをUIから設定できる

f:id:thelarklife1021:20211101063348p:plain


7. SageMaker Pipelines を試す

f:id:thelarklife1021:20211101072352p:plain

(https://aws.amazon.com/jp/solutions/implementations/aws-mlops-framework/?nc1=h_ls)

主に以下を使っていきます。

github.com

サービスカタログにはJumpStartを有効化ことでMLOps用のテンプレートが使えるようになります。

今回は以下を使用します。

f:id:thelarklife1021:20211101075015p:plain

プロジェクトを開始することによりCloudFormationによるMLOpsに必要な環境が作成されます。 以下はMLパイプライン

f:id:thelarklife1021:20211101080835p:plain

CodeCommitにはMLOpsに必要なレポジトリが格納されます。

f:id:thelarklife1021:20211101080726p:plain

pipelines/abalone/pipeline.py の transform_instances を適当に編集します。

ml.m5.large -> ml.m5.xlarge

そうすると以下の通り Changedが1になります。この状態でpushを行い、パイプラインを回していきます。

f:id:thelarklife1021:20211101082842p:plain

pushすると下記の通り Codebuild が実行されます。

f:id:thelarklife1021:20211101084320p:plain

SageMaker Pipeline もCodePipelineで実行されていることが確認できます。

f:id:thelarklife1021:20211101084546p:plain

入門編として概要レベルで実践してみました。
次回は詳細にMLOps構築を進めていきます。

以上

CNNをゼロから実装

畳み込みニューラルネットワーク(CNN)の復習です。

画像データの配列

まずはデータを準備します。実際は画像データを前処理するところからやりますが、今回は省略します。 高さh、幅w、チャンネル(色)ch、データの個数nとすると、データの形状は(n, ch, h, w)、となるような画像データをランダムで生成します。

import numpy as np

img = np.random.randint(0, 50, (1, 1, 7, 7)) # 任意の範囲の整数の乱数、最小値0、最大値50
# img = np.round(img)
print(img.shape)

print(img[0].shape)
print(img[0])

ランダムで出力される値を確認する

(1, 1, 7, 7)
(1, 7, 7)
[[[ 4 27  8 19 10 29 40]
  [40 40 24 15 12  8 13]
  [27 41  1  7  1  5 42]
  [ 0 37  5 49 30 31  0]
  [ 7 48 17 14 43 34 37]
  [35 13  8 18 24 45 40]
  [16 29  2  6 12 30 10]]]

次に画像データの持ち方を考えます。

import numpy as np

A = np.array(
    [[["000", "001", "002", "003"],
      ["010", "011", "012", "013"],
      ["020", "021", "022", "023"]],
     [["100", "101", "102", "103"],
      ["110", "111", "112", "113"],
      ["120", "121", "122", "123"]]]
)
print(A)

print(A.shape)
[[['000' '001' '002' '003']
  ['010' '011' '012' '013']
  ['020' '021' '022' '023']]

 [['100' '101' '102' '103']
  ['110' '111' '112' '113']
  ['120' '121' '122' '123']]]
(2, 3, 4)

im2colによる展開

畳み込み演算やプーリング演算を、for文を重ねなくても実装できるように、入力データを展開処理するために使用される関数。

# 引数は
# 画像データ、カーネル高さ、カーネル幅、ストライド幅、ストライド高さ、パディング高さ、パディング幅
# ストライド量、パディング量は縦横まとめられる場合あり
def im2col(img, k_h, k_w, s_h, s_w, p_h, p_w):
    n, c, h, w = img.shape 
    # print(img.shape)

    # パディング処理
    img = np.pad(img, [(0,0), (0,0), (p_h, p_h), (p_w, p_w)], 'constant') 
    # print(img[0])
    # print(img.shape)

    # 出力データのサイズ計算
    out_h = (h + 2*p_h - k_h)//s_h + 1
    out_w = (w + 2*p_w - k_w)//s_w + 1

    col = np.ndarray((n, c, k_h, k_w, out_h, out_w), dtype=img.dtype)  # 戻り値となる4次元配列を準備。(データ数、チャンネル数、カーネル高さ、カーネル幅、出力高さ、出力幅)
    # print(col.shape)
    # print(col[0])
    
    # フィルターに対応する画素をスライス(colに代入)
    for y in range(k_h):
        y_lim = y + s_h * out_h # y_lim:最後のフィルターの位置
        # print("y_lim")
        # print(y_lim)
        for x in range(k_w):
            x_lim = x + s_w * out_w # y_lim:最後のフィルターの位置
            # print("x_lim")
            # print(x_lim)
            col[:, :, y, x, :, :] = img[:, :, y:y_lim:s_h, x:x_lim:s_w] # colのy番目、x番目に、yからy_limまでをストライド量ごとにスライスしたものを代入
            # print("col")
            # print(col)
            # print("img")
            # print(img[:, :, y:y_lim:s_h, x:x_lim:s_w])
    
    # transpose: 多次元配列の軸の順番を入れ替え。reshapeしやすいように、順番を並び替え。(データ数、出力高さ、出力幅、チャンネル数、カーネル高さ、カーネル幅、)
    col = col.transpose(0, 4, 5, 1, 2, 3)

    # reshape: -1を指定することで、多次元配列の要素数を自動整形。(データ数×出力高さ×出力幅 , チャンネル数×カーネル高さ×カーネル幅)
    col = col.reshape(n*out_h*out_w, -1) 
    return col

畳み込み

畳み込みに必要な関数を用意します。

class Convolution:
    def __init__(self, W, b, stride=1, pad=0):
        self.W = W # フィルター(カーネル)
        self.b = b
        self.stride = stride
        self.pad = pad

        # 中間データ
        self.x = None   
        self.col = None
        self.col_W = None

        # 重み・バイアスパラメータの勾配
        self.dW = None
        self.db = None

    def forward(self, x):
        k_n, c, k_h, k_w = self.W.shape # k_n:フィルターの数
        n, c, h, w = x.shape
        
        # 出力データのサイズ計算
        out_h = int((h + 2*self.pad - k_h) / self.stride + 1)
        out_w = int((w + 2*self.pad - k_w) / self.stride + 1)
        
        # 展開
        col = im2col(x, k_h, k_w, self.stride, self.stride, self.pad, self.pad) # 画像を2次元配列化 (データ数×出力高さ×出力幅 , チャンネル数×カーネル高さ×カーネル幅)
        col_W = self.W.reshape(k_n, -1).T # フィルターを2次元配列化
        out = np.dot(col, col_W) + self.b #行列積(畳み込み演算)

        # 整形
        out = out.reshape(n, out_h, out_w, -1).transpose(0, 3, 1, 2) # 2次元配列→4次元配列

        return out

プーリング

画像をMax Poolingしていくための関数を用意します。

class Pooling:
    def __init__(self, pool_h, pool_w, stride=1, pad=0):
        self.pool_h = pool_h
        self.pool_w = pool_w
        self.stride = stride
        self.pad = pad

        self.x = None
        self.arg_max = None

    def forward(self, x):
        N, C, H, W = x.shape
        out_h = int(1 + (H - self.pool_h) / self.stride)
        out_w = int(1 + (W - self.pool_w) / self.stride)

        col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
        col = col.reshape(-1, self.pool_h*self.pool_w)

        arg_max = np.argmax(col, axis=1)
        out = np.max(col, axis=1)
        out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)

        self.x = x
        self.arg_max = arg_max

        return out

    def backward(self, dout):
        dout = dout.transpose(0, 2, 3, 1)

        pool_size = self.pool_h * self.pool_w
        dmax = np.zeros((dout.size, pool_size))
        #flattenは構造を1次元配列に入れ直すこと
        dmax[np.arange(self.arg_max.size), self.arg_max.flatten()] = dout.flatten()
        dmax = dmax.reshape(dout.shape + (pool_size,)) 

        dcol = dmax.reshape(dmax.shape[0] * dmax.shape[1] * dmax.shape[2], -1)
        dx = col2im(dcol, self.x.shape, self.pool_h, self.pool_w, self.stride, self.pad)

        return dx

この後CNN実装と画像可視化していきますが、後日追加したいと思います。

機械学習アルゴリズムの復習(SVM)

SVMによるデータ分類を行います。

レーニングデータの準備

必要モジュールのimport

from sklearn import datasets
from sklearn import svm
import matplotlib.pyplot as plt
from sklearn import metrics

レーニングデータの準備

#データの準備
digits = datasets.load_digits()

# データ数の確認
n_samples = len(digits.data)
print("データ数:{}".format(n_samples))

#データの可視化
print(digits.data[0])

images_and_labels = list(zip(digits.images, digits.target))

for index, (image, label) in enumerate(images_and_labels[:10]): # enumerate:リストを順番に処理
    plt.subplot(2, 5, index + 1)
    plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
    plt.axis('off')
    plt.title('Training: %i'% label)
plt.show()

f:id:thelarklife1021:20211001054328p:plain:h300

SVM のロード

clf = svm.SVC(gamma=0.001, C=100.)

6割のデータで学習し、4割のデータでテストする場合

学習実行

# 60%のデータで学習実行
clf.fit(digits.data[:int(n_samples * 6 / 10)], digits.target[:int(n_samples * 6 / 10)])

テストを実行

# 40%のデータでテスト
expected = digits.target[int(n_samples *-4 / 10):]
predicted = clf.predict(digits.data[int(n_samples *-4 / 10):])

print(clf,metrics.classification_report(expected, predicted))
print(metrics.confusion_matrix(expected, predicted))

予測結果を可視化

images_and_predictions = list(zip(digits.images[int(n_samples *-4 / 10):], predicted))
for index,(image, prediction) in enumerate(images_and_predictions[:12]):
 plt.subplot(3, 4, index + 1)
 plt.axis('off')
 plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
 plt.title('Prediction: %i' % prediction)
plt.show()

下記の通り全て予測(Prediction)が(視認する限り)正確に学習されていることがわかる

f:id:thelarklife1021:20211001054918p:plain:h300

1割のデータで学習し、8割のデータでテストする場合

学習実行

# 10%のデータで学習実行
clf.fit(digits.data[:int(n_samples * 1 / 10)], digits.target[:int(n_samples * 1 / 10)])

テストを実行

# 90%のデータでテスト
expected = digits.target[int(n_samples *-9 / 10):]
predicted = clf.predict(digits.data[int(n_samples *-9 / 10):])

print(clf,metrics.classification_report(expected, predicted))
print(metrics.confusion_matrix(expected, predicted))

予測結果を可視化

images_and_predictions = list(zip(digits.images[int(n_samples *-9 / 10):], predicted))
for index,(image, prediction) in enumerate(images_and_predictions[:12]):
 plt.subplot(3, 4, index + 1)
 plt.axis('off')
 plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
 plt.title('Prediction: %i' % prediction)
plt.show()

f:id:thelarklife1021:20211001060348p:plain:h300

このくらいだと1回の学習でもほぼ正確に予測できているように見える。 次はもっと複雑な形を選びたいと思います。

以上

2021年シドニーマラソンのバーチャルラン登録申し込みが開始されたようです

2021年のシドニーラソンのバーチャルラン登録申し込みが開始されました!

www.sydneymarathon.jp

私はフルマラソンで登録しておきました。
そろそろ気合い入れないと、、(最近雨が多いため中々走りずらい。。)

機械学習アルゴリズムの復習(次元削除と主成分分析(PCA))

次元削除にて膨大なデータの主成分を探す。固有値分解はここで使います。


データセット読み込み

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import preprocessing, decomposition

# データセット読み込み
df_wine_all=pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data', header=None)

df_wine_all.head(4)

取り込んだワインデータは前処理済みのもので先頭4行を抽出した結果は以下の通りとなる。

取り込んだワインを表示
   0      1     2     3     4    5   ...    8     9     10    11    12    13
0   1  14.23  1.71  2.43  15.6  127  ...  0.28  2.29  5.64  1.04  3.92  1065
1   1  13.20  1.78  2.14  11.2  100  ...  0.26  1.28  4.38  1.05  3.40  1050
2   1  13.16  2.36  2.67  18.6  101  ...  0.30  2.81  5.68  1.03  3.17  1185
3   1  14.37  1.95  2.50  16.8  113  ...  0.24  2.18  7.80  0.86  3.45  1480

[4 rows x 14 columns]


入力とラベルに分ける

X=df_wine_all.iloc[:,1:].values
Y=df_wine_all.iloc[:,0].values

print(X)

特徴リストの出力

[[1.423e+01 1.710e+00 2.430e+00 ... 1.040e+00 3.920e+00 1.065e+03]
 [1.320e+01 1.780e+00 2.140e+00 ... 1.050e+00 3.400e+00 1.050e+03]
 [1.316e+01 2.360e+00 2.670e+00 ... 1.030e+00 3.170e+00 1.185e+03]
~snip~
]

print(Y)

ラベルリストの出力

[1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3]


データの整形

sc=preprocessing.StandardScaler() # 標準化(平均0、分散1)
sc.fit(X)
X=sc.transform(X)

X

データを平均0、分散1(デフォルト)になるように整形します

array([[ 1.51861254, -0.5622498 ,  0.23205254, ...,  0.36217728,
         1.84791957,  1.01300893],
       [ 0.24628963, -0.49941338, -0.82799632, ...,  0.40605066,
         1.1134493 ,  0.96524152],


PCAを実行

6次元に次元圧縮する

pca = decomposition.PCA(n_components=6)
X_transformed = pca.fit_transform(X)

X_transformed

array([[ 3.18562979e+02,  2.14921307e+01,  3.13073470e+00,
        -2.50113758e-01,  6.77078222e-01,  5.68081040e-01],
       [ 3.03097420e+02, -5.36471768e+00,  6.82283550e+00,
        -8.64034749e-01, -4.86095978e-01,  1.43398712e-02],

結果の表示

print(pca.explained_variance_)
print(pca.components_)

f:id:thelarklife1021:20210901084617p:plain:h30

固有値固有ベクトルを求める

# 固有値λに相当
[4.73243698 2.51108093 1.45424187 0.92416587 0.85804868 0.64528221]

# 固有ベクトルvに相当(6次元)
[[ 0.1443294  -0.24518758 -0.00205106 -0.23932041  0.14199204  0.39466085
   0.4229343  -0.2985331   0.31342949 -0.0886167   0.29671456  0.37616741
   0.28675223]
 [-0.48365155 -0.22493093 -0.31606881  0.0105905  -0.299634   -0.06503951
   0.00335981 -0.02877949 -0.03930172 -0.52999567  0.27923515  0.16449619
  -0.36490283]
]

print(np.cumsum(pca.explained_variance_ratio_))

累積寄与率:どれだけ次元を削減したか(7~8割が目安)

[0.36198848 0.55406338 0.66529969 0.73598999 0.80162293 0.85098116]


結果のプロット

plt.subplot(2, 1, 2)
plt.scatter(X[:,9],X[:,12], c=Y)
plt.xlabel('color')
plt.ylabel('proline')
plt.show

f:id:thelarklife1021:20210901091302p:plain

%matplotlib inline
plt.figure(figsize=(10,10))
plt.subplot(2, 1, 1)
plt.scatter(X_transformed[:,0],X_transformed[:,1], c=Y)
plt.xlabel('PC1')
plt.ylabel('PC2')

PC1:第1成分
PC2:第2成分

f:id:thelarklife1021:20210901091052p:plain

以上

機械学習アルゴリズムの復習(ロジスティック回帰)

ロジスティック回帰による線形クラス分類は、特徴量からクラス分けを行うために使う。


データの用意

method note
np.random.multivariate_normal([平均], [今日分散], 生成数) ランダムな多次元正規乱数の生成
train_test_split(x軸, y軸, 分割の割合) ホールドアウト検証用に各xy配列を訓練用と、検証用に分割


訓練、検証データ作成

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import numpy as np
 
np.random.seed(seed=0)
X_0 = np.random.multivariate_normal( [5,5],  [[5,0],[0,5]],  20 ) # [5,5]:平均、[[5,0],[0,5]]:共分散、20:生成する個数
y_0 = np.zeros(len(X_0)) # 0のリストを生成(赤)
 
X_1 = np.random.multivariate_normal( [9,10],  [[6,0],[0,6]],  20 ) # [9,10]:平均、[[6,0],[0,6]]:共分散、20:生成する個数
y_1 = np.ones(len(X_1)) # 1のリストを生成(青)
 
X = np.vstack((X_0, X_1)) # vstack:縦方向に配列を結合
y = np.append(y_0, y_1)
 

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

X_train

array([[ 2.60572435,  7.35782574],
       [11.0987978 ,  8.40531949],
       [ 7.40193187,  9.04236372],
~snip~

y_train

array([0., 1., 1., 1., 0., 1., 1., 0., 1., 1., 0., 0., 0., 1., 1., 1., 0.,
       1., 1., 1., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 1.,
       0., 0., 1., 1., 1., 1., 1., 0., 1., 0., 1., 1., 0., 1., 0., 0., 1.,
~snip~


正規化

method note
StandardScaler() データの標準化をやるクラス(データの平均や、標準偏差など)
sc.fit_transform() 変換式の計算と変換式を使ったデータ変換を行う

データセットを標準化

# 特徴データを標準化(平均 0、標準偏差 1)
sc = StandardScaler()
X_train_std = sc.fit_transform(X_train) # fit と transform をまとめて行う
X_test_std = sc.transform(X_test) # fitの結果を使ってデータ変換

X_train_std

array([[-1.43501522, -0.15974167],
       [ 1.53555855,  0.17214758],
       [ 0.24252688,  0.37398948],
~snip~

X_test_std

array([[ 1.46270099e+00,  1.31652928e+00],
       [ 1.14335461e+00, -1.47439949e+00],
       [-1.50884298e+00, -2.68581862e-01],
~snip~


プロット

plt.scatter(X_train_std[y_train==0, 0], X_train_std[y_train==0, 1], c='red', marker='x', label='train 0')
plt.scatter(X_train_std[y_train==1, 0], X_train_std[y_train==1, 1], c='blue', marker='x', label='train 1')
plt.scatter(X_test_std[y_test==0, 0], X_test_std[y_test==0, 1], c='red', marker='o', s=60, label='test 0')
plt.scatter(X_test_std[y_test==1, 0], X_test_std[y_test==1, 1], c='blue', marker='o', s=60, label='test 1')
 
plt.legend(loc='upper left')

f:id:thelarklife1021:20210831144912p:plain:h300



学習

from sklearn.linear_model import LogisticRegression # ロジスティック回帰のクラスインポート
 
# 訓練
lr = LogisticRegression()
lr.fit(X_train_std, y_train)

# テストデータ 80個を分類
print (lr.predict(X_test_std))

# 精度を確認
print (lr.score(X_test_std, y_test))

lr.predict(X_test_std)

テストデータを0,1で分類

[1. 0. 0. 0. 1. 1. 0. 0. 0. 1. 1. 1. 1. 0. 1. 0. 0. 1. 0. 0. 1. 0. 0. 1.
 1. 1. 0. 0. 0. 1. 0. 0. 0. 0. 1. 1. 0. 1. 1. 1. 1. 0. 0. 1. 1. 0. 1. 0.]

精度

完全に分類できていない

0.8958333333333334



結果の可視化

# 切片を出力
print (lr.intercept_) 

# 重みを出力
print (lr.coef_) 


w_0 = lr.intercept_[0]
w_1 = lr.coef_[0,0]
w_2 = lr.coef_[0,1]
 
# 重みと切片を使って境界を作る
print(list(map(lambda x: (-w_1 * x - w_0)/w_2, [-2,2])))

# 境界線 プロット
plt.plot([-2,2], list(map(lambda x: (-w_1 * x - w_0)/w_2, [-2,2])))

# データを重ねる
plt.scatter(X_train_std[y_train==0, 0], X_train_std[y_train==0, 1], c='red', marker='x', label='train 0')
plt.scatter(X_train_std[y_train==1, 0], X_train_std[y_train==1, 1], c='blue', marker='x', label='train 1')
plt.scatter(X_test_std[y_test==0, 0], X_test_std[y_test==0, 1], c='red', marker='o', s=60, label='test 0')
plt.scatter(X_test_std[y_test==1, 0], X_test_std[y_test==1, 1], c='blue', marker='o', s=60, label='test 1')
plt.show()

f:id:thelarklife1021:20210831160333p:plain:h300

上記の赤と青はランダム関数で設定した平均と分散により位置が変化する。
現状、線形では完全に分類できていないため、精度も 0.8958333333333334 となってしまっている。
線形で綺麗に分類できるデータを作成したい場合は、 np.random.multivariate_normal の引数を調整してやる。
以下は各平均を離してあげることにより綺麗に分類している。

np.random.seed(seed=0)
X_0 = np.random.multivariate_normal( [1,1],  [[5,0],[0,5]],  20 ) # [5,5]:平均、[[5,0],[0,5]]:共分散、20:生成する個数
y_0 = np.zeros(len(X_0)) # 0のリストを生成
 
X_1 = np.random.multivariate_normal( [9,10],  [[6,0],[0,6]],  20 ) # [9,10]:平均、[[6,0],[0,6]]:共分散、20:生成する個数
y_1 = np.ones(len(X_1)) # 1のリストを生成

f:id:thelarklife1021:20210831160842p:plain:h300

精度も1.0になる
print (lr.score(X_test_std, y_test))

1.0

以上