[学習メモ]機械学習をAWSで実装するまで②ファイル監視を使って画像をアップロードする

こんばんは、座禅いぬです。最近は<strong>花粉によるデバフを受けてきつい限り</strong>でございます。

---

前回から、AWSのS3バケットを使って画像を集積し機械学習による画像分類を行う取り組みをしています。実はそれに至るまでに思い悩んだのが<strong>「GPUを使う仕組みをどうやってクラウドで実装するか？」</strong>という点です。自分には割と技術的ハードルが高かったため、いろいろな試行錯誤をしていました。

その中で検討したのが、「撮った画像を手動でGoogle Driveに入れ、グラボ搭載PCでファイル監視システムを使い画像が増えたのを検出して、PyTorchで画像分類しファイル名を変更してラベル付け」というものでした。

結果として、ラベリングをファイル名の変更だけで行うのは混乱を招きやすいため見送りましたが、「既存の環境ですでにPCへ画像が自動で取り込まれる機能」がある場合、<strong>画像が保存されるフォルダをファイル監視システムで見張り、自動的にS3バケットへアップロードする</strong>という方向で活用できそうなので仕組みを作ってみました。

今回は本題からはややそれるのですが、ファイル監視→AWS S3アップロードの仕組みに焦点を当ててみようと思います。

### 今回解決したい課題

- 複数の異なる機器で画像を撮影する仕組みに組み込みたい
- 既存のソフトウェアを使用して画像をPCに保存する仕組みがすでにある
- 既存ソフトウェアは患者番号ごとに自動でファイル・フォルダを生成
- 生成されるファイル構造の深さや形式が様々

そこで、これらの既存システムを活かしながら、ファイル監視システムを使ってS3バケットに自動アップロードする仕組みを検討しました。

--- 

### ファイル監視システムとは？
ファイル監視システムとは、指定したフォルダにおいて下記のような変化を監視し、何かしらの処理（例：アップロードやファイル名変更など）を自動的に行う仕組みを指します。

- 新しくファイルが追加された
- ファイルが変更された
- ファイルが削除された

Pythonでは代表的に<strong>watchdog</strong>と<strong>watchfiles</strong>というライブラリがあります。違いをまとめてみました。

### watchdogとwatchfilesの比較
|項目|watchdog|watchfiles|
|:--|:--|:--|
|開発者/コミュニティ|歴史が長く(2010~)、利用者が多い|新しめ(2021~)だが活発に開発されている|
|対応OS|Windows, macOS, Linux, BSDなど幅広く対応|Windows, macOS, Linuxに対応 (一部制限あり)|
|インストール|pip install watchdog|pip install watchfiles|
|使い方のシンプルさ|監視対象のObserverやHandlerを自分で定義する必要がある|watch()関数でイベントを反復処理するだけでOK|
|イベント種別の詳細|ファイル作成・更新・削除・移動などを細かくハンドリング可|基本は作成・更新・削除などに対応し、それぞれのイベントをまとめて扱う|
|パフォーマンス|大量ファイル・大規模開発向けに定評があり安定性が高い|軽量で高速に動作するとされる (環境依存)|
|学習コスト|ドキュメント/チュートリアルは多め|設定は比較的シンプルだが情報量はwatchdogより少ない|

今回の用途では、<strong>既存のソフトウェアが作成した任意の深さのディレクトリ構造</strong>を監視し、作成された画像ファイルを自動でS3にアップロードすることが目的です。

両者とも実現は可能ですが、<strong>コードがシンプルなのはwatchfiles</strong>なので、ここではwatchfilesを例にサンプルコードを示します。

### 実際のwatchfilesを使ったサンプルコード
以下に、特定のフォルダ（TARGET_FOLDER_PATH）を監視して新規ファイルが作成されたら、S3バケット（YOUR_S3_BUCKET_NAME）にアップロードするサンプルコードを示します。プログラミング初心者でもわかりやすいように、1行ずつコメントを付けています。

``` python
import os
import boto3
from watchfiles import watch

# S3へアクセスするためのクライアントを生成
s3_client = boto3.client(
    's3',  # 利用するAWSサービスはS3
    aws_access_key_id='YOUR_ACCESS_KEY',       # AWSのアクセスキーを設定
    aws_secret_access_key='YOUR_SECRET_KEY',   # AWSのシークレットキーを設定
    region_name='ap-northeast-1'               # リージョン(例: 東京)を指定
)

# 監視したいフォルダのパス(絶対パスか、スクリプト起点の相対パス)
TARGET_FOLDER_PATH = '/path/to/your/folder'

# アップロード先のS3バケット名を指定
S3_BUCKET_NAME = 'YOUR_S3_BUCKET_NAME'

def upload_to_s3(file_path):
    """
    指定されたローカルファイルをS3にアップロードする関数
    :param file_path: ローカルのファイルパス
    """
    print(f"アップロードを行います: {file_path}")

    # S3にアップロードする際のキー（パス）を定義
    # フォルダ階層をそのまま再現するように、file_pathを相対化するなど工夫が必要

    # ここではシンプルにベース名のみをキー名にする例を示す
    file_name = os.path.basename(file_path)
    print(f"アップロードするファイル名: {file_name}")
    
    try:
        # S3にアップロードを実行
        s3_client.upload_file(
            Filename=file_path,        # ローカルのファイルパス
            Bucket=S3_BUCKET_NAME,     # アップロード先のS3バケット
            Key=file_name             # S3上でのファイル名(今回はシンプルにベース名のみ)
        )
        print(f"S3にアップロード完了: {file_path} -> s3://{S3_BUCKET_NAME}/{file_name}")

    except Exception as e:
        print(f"アップロード中にエラーが発生しました: {e}")

def main():
    """
    watchfilesを使って指定フォルダを監視し、新規ファイルができるとS3にアップロードするメイン関数
    """
    print("ファイル監視を開始します...")

    # watch()に監視パスとオプションを指定する
    # recursive=Trueを指定すると、指定フォルダ以下のサブフォルダも再帰的に監視してくれる
    for changes in watch(TARGET_FOLDER_PATH, recursive=True):
        print(f"変更が検知されました: {changes}")
        # changesはset型であるため、添字アクセスはできず、forループで各要素（タプル）を処理する

        for change_type, changed_file_path in changes: # 変更の種類とファイルパスを取得
            # 変更の種類を確認する
            print(f"変更の種類: {change_type}, 変更されたファイル: {changed_file_path}")
            # change_type が 追加(added):1, 変更(modified):2, 削除(deleted):3 などになる

            if change_type == 1:  # 1はファイルが新しく追加されたことを示す
                # ファイルが追加されたらS3にアップロードする
                print(f"新規ファイルを検知: {changed_file_path}")
                upload_to_s3(changed_file_path)

if __name__ == '__main__':
    # スクリプトが直接実行された場合にmain()を呼び出す
    main()
```
### コードの動作
![スクリーンショット 2025-03-30 175508.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/850275/adc75af6-e99c-4e9b-998c-53d9ea0b2473.png)
1. watch(TARGET_FOLDER_PATH, recursive=True)
   - 指定フォルダ以下を監視し、追加・変更・削除などのイベントを検知
2. 新規ファイル(added)を検知したらupload_to_s3関数を呼び、S3にアップロード
3. 動作確認用にアクションごとにメッセージを表示するようにしています

上記の<strong>コード例では、単に「新しく追加されたファイルをアップロードする」という最低限のロジックだけを記述</strong>しています。ぜひ使用感を確認してみてください。

### 実際の運用では以下の追加が必要

- ファイルが完成する前に検出してアップロードが失敗する可能性を避けるための<strong>待機処理</strong>
- <strong>S3に送るファイルの命名規則を設定</strong>し重複を防ぎ、機械学習に用いやすいようにする
- 想定外のサイズの画像が送られてこないように、<strong>ファイルサイズの調整</strong>

実装では上記の検討事項が必要かと思います。その場合はos.path.join()やtimeライブラリのtime.sleep()で調整したり、イベントハンドリングに条件分岐を追加したり、運用にあわせて適宜カスタマイズしてください。


## まとめ
- ファイル監視システムは色々活用法がありそう！既存PC環境とクラウドの連携に便利
- ファイル監視システムにはwatchdog / watchfilesがあるよ！
- これを使えば<strong>既存ソフトウェアが保存する画像をS3に自動で集積可能！</strong>

次回はSageMakerを使った機械学習の仕組みを作成し、いよいよ画像分類を実際に動かすところまで進めてみたいと思います。<strong>正直記事を書きながら試行錯誤して実装する「Qiita駆動開発」的な状態</strong>なので、どこまで進めるかわかりませんが、初学者なりにどんどんチャレンジしていこうと思います。最後までお読みいただきありがとうございました！

### 参考にした記事
https://qiita.com/inetcpl/items/7e83039d222c3f49c4f0

watchfilesライブラリの情報、マジでなかったので大変勉強になりました！

### 謝辞
どうしたらいいかわからんと頭を抱えていた時にファイル監視システムのことを教えてくださった@steelpipe75さん、いつもありがとうございます！