ryubloblog’s diary

宮城県の仙台で働くプログラマーです。

Hagging Face Inference APIをFastAPIで使用してみた

はじめに

startpython.connpass.com

#97のみんなのPython勉強会のトーク1草薙 昭彦さんの
『Postmanで始めるAI・機械学習プラットフォームHugging Face』
の内容が興味深く、実際にAIを利用してみたいと思ったので実際にやってみましたという記事です。

トーク内で使用されていた資料も公開されていました!

Postmanで始めるAI・機械学習プラットフォームHugging Face - Speaker Deck

実装したもの

FastAPIを使用してSwaggerから簡単に利用できるようにしました。

API: https://inference-api-with-fastapi-2nc47pcv3a-an.a.run.app/docs
リポジトリ: GitHub - ryu-0729/inference_api_with_fastapi

使用技術

Python3.12
FastAPI
Docker/Dev Container
Cloud Run
Hagging Face Inference API

Hagging Face Inference APIの利用方法

トーク内の資料でも紹介されてはいますが、一通りの流れを記載いたします。

Step1 Hugging Faceにユーザー登録

メールアドレスとパスワードを入力してユーザー登録をします。
登録したメールアドレスに認証メールが届くので認証を完了します。

Step2 アクセストークンの取得

ユーザーの設定画面から左側の「Access Tokens」を選択します。

「New Token」のボタンからアクセストークンを生成します。
Roleはreadで問題ないです。

Step3 Hagging Face Inference APIの利用

PostmanからのAPIの利用方法は、資料に記載がされているのでここでは省略いたします。
15 ~ 21ページに記載されています。

https://speakerdeck.com/nagix/postmandeshi-meruaiji-jie-xue-xi-puratutohuomuhugging-face?slide=15

Python(FastAPI)からの利用方法

今回はrequestsパッケージを使用してAPIを利用します。

pypi.org

エンドポイントさえ設定できれば簡単にAPIを利用することができます。

huggingface.co

上記のリンクに記載されていますが、エンドポイントの形式は、「共通URL + モデルID」になります。

ENDPOINT = https://api-inference.huggingface.co/models/<MODEL_ID>

利用するモデルは下記リンクを見てみて設定するか、ご自身で調べて設定する形になります。

https://huggingface.co/docs/api-inference/detailed_parameters#detailed-parameters

また、Step2で生成したアクセストークンをAuthorizationヘッダーに渡すことも必要になります。
形式は「Bearer + アクセストークン」になります。
"Authorization": f"Bearer {access_token}"

参考ソース(Postmanのコード生成を利用)

import requests
import json

url = "https://api-inference.huggingface.co/models/SamLowe/roberta-base-go_emotions"

payload = json.dumps({
  "text": "I am not having a great day."
})
headers = {
  'Content-Type': 'application/json',
  'Authorization': 'Bearer xxxxxxxxxxxxxxx'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)

今回実装したソースコードについては下記を参照していただければと思います。

https://github.com/ryu-0729/inference_api_with_fastapi/blob/master/app/core/requestapi.py

https://github.com/ryu-0729/inference_api_with_fastapi/blob/master/app/apis/text.py

※ 注意事項
初回アクセスの際に、モデルの読み込みによってAPIエラーになる場合がありました。
しばらく時間を置いて実行することで正常に動作することが確認できました。

実行例

使用したInference APIについて

今回使用したInference APIについて紹介したいと思います。

テキストから感情分析 Text Classification

https://huggingface.co/docs/api-inference/detailed_parameters#text-classification-task

テキストから感情を分析するAPIです。
分析したいテキストをパラメータとして渡すと感情ごとに一致度を数値としてレスポンスが返ってきます。

https://inference-api-with-fastapi-2nc47pcv3a-an.a.run.app/docs#/Text/text_classification_text_text_classification_get

テキストの問題に答える Question Answering

https://huggingface.co/docs/api-inference/detailed_parameters#question-answering-task

テキストの問題に答えるAPIです。
問題文とテキストをパラメータとして渡すと問題に答えるレスポンスが返ってきます。

https://inference-api-with-fastapi-2nc47pcv3a-an.a.run.app/docs#/Text/question_answer_text_question_answer_get

画像からテキストを生成 Blip Image Captioning Large

https://huggingface.co/Salesforce/blip-image-captioning-large

画像URLからテキストを生成するAPIです。
画像URLをパラメータとして渡すとどのような画像であるかをテキストにしてレスポンスが返ってきます。

https://inference-api-with-fastapi-2nc47pcv3a-an.a.run.app/docs#/Multimodal/image_to_text_multimodal_image_to_text_get

まとめ

Hagging Face Inference APIPython(FastAPI)から使用してみたわけですが、想像以上に簡単に利用することができました。
今回使用したAPI以外にも画像生成や画像の感情分析とかも利用できるので、興味がある方は実際に触ってみると面白いと思います。

「みんなのPython勉強会」には毎月のように参加しているわけですが、毎月興味深い内容やPythonの技術について新しいことを知れる貴重な勉強会だと感じています。
感謝です!!

以上になります、ありがとうございました!

Pythonエンジニア認定実践試験合格備忘録

はじめに

はじめにですが、Pythonエンジニア認定実践試験という資格に合格できたのでなぜ受験したのかや勉強方法等をまとめていきます。
これから受験を考えている人の参考に少しでもなれればと思います。

お前誰よ

簡単に自己紹介をしていきたいと思います。
普段は宮城県仙台市プログラマーをしています。
↑歴としては3年目の年になります。(2023/10時点)
Python推しです!
勉強会にも参加したりしています。

startpython.connpass.com

業務で使用している技術(言語的なところ)

直近で使用しているのはPHPとGoが多いです。(Pythonではない。。。)

Pythonに関しては、競プロで使用するために学習し始めてニンテンドーSwitchの自動化に使用したり、画像処理をしたり、機械学習してみたり、個人開発(FastAPI)に使用したりしています。
業務ではDjangoのプロジェクトに少しヘルプで入ったことがある程度です。

受験理由

Python推しということもありますが、社内で少しずつPythonを使用していく風が吹き始めたので「こいつPythonできるぞ」というのを示したかったのが1番の理由です。(資格があるからできるとは一概に言えませんが)
また、業務ではPythonを触ることは無いに等しいので知識や技術を学ぶにも良い機会になると感じたのも理由になります。

勉強方法

勉強方法は主教材として紹介されているPython実践レシピを使いました。(試験の問題もここから出題されるので)
その他には下記の2つのサイトも使用しました。

python-basic.com

diver.diveintocode.jp

それぞれどんな感じで勉強したかを簡単にまとめます。

1 Python実践レシピ
2~3周読み込みました。
1周目は試験範囲以外も読んでいます。
2周目以降はメソッドの使用用途や戻り値などを重点的に理解できるようにしました。
また実際にメモ程度にですが、ソースを書いてアウトプットするようにしました。
↓参考リポジトリ

github.com

2 ExamApp
実践問題の初級~上級までを100%解けるようにしました。
それぞれ3回程度やりました。
回答を間違った箇所は書籍を読んだりやソースを書いたりして理解できるようにしています。

3 ディープロ
これはExamApp以外の問題もやりたいなーと感じたのと公式サイトに記載されていたので挑戦してみました。(ExamAppのみで過学習しない的な感じです。)
↑無料プランです!
大体3回くらい実施しています。

まとめ

Pythonエンジニア認定実践試験を受験してみての感想は良かったと感じています。
Pythonの基本的なところも含めて、標準ライブラリの深掘りが出来たと個人的に思っています。
ただ、OS周りやファイルとディレクトリの扱い、インターネット上のデータを扱うらへんは理解するまでが大変でした。。。
試験は満点合格(1000点)を目指していたのですが、850点ということで若干悔しかったです😇
合格できたので良しとします!

最後になりますが、Pythonで仕事がしたーーーーい🐍
以上、Pythonエンジニア認定実践試験合格備忘録になります。

【Switch自動化】ポケモンSVの学校最強大会を無限に周回した話

はじめに

ryubloblog.hatenablog.com

前回、ニンテンドーSwitchを自動操作してポケモンSVの学校最強大会を周回するプログラムを作成しました。
その中で課題点/改善点に下記をあげていました。

ここでお気付きの方もいるかと思いますが、現状のプログラムだとニンフィアが途中でリタイアする可能性があるのです。。。
リタイアすると残り時間ポケモンセンターで回復し続けます。
また、時間による設定を行なっているので学校最強大会中に処理が終わることがほとんどになっています。。
よって今後の追加機能としては下記を考えています。

  • 条件判定を行い回復アイテムを使用する。(社内でフィードバックがありニンフィア努力値のH180をSに振ったらあまりリタイアすることはなくなったので要検討)
  • ポケモンセンターに戻った際は学校最強大会にリエントリーできるようにする。
  • 学校最強大会が終了した時点で処理が終わるようにする。
  • レポート機能

今回はこれら課題点/改善点を解決するべく機能追加を行なって学校最強大会を無限に周回できるようにしました。
実装したソースコードは下記にあります。
インストールや実行方法はREADMEに記載してあります。

github.com

追加機能

大きく追加した機能はこちらです!

  • レポート判定機能
  • リエントリー機能

それではそれぞれの機能についてどのような機能でどのように実装したか解説したいと思います。

レポート機能

機能としては学校最強大会をエントリーする場所でレポートを書く機能になっています。

実装方法ですが、レポートを書くかどうかを判定する必要があります。
レポートを書くかどうかの判定には画像処理(OpenCV)を使用しています。
具体的には画像処理を使用して画面上に、「学校最強大会」が表示されているかどうかを判定している感じになります。

主な処理の流れとしては下記になります。

  1. 画面上の画像取得
  2. 取得画像をトリミング
  3. 2でトリミングした画像と比較元画像を2値化*1
  4. 3の2つの画像(正確にはNumPy配列)を比較し一致している要素を取得
  5. 4で取得した一致している要素数閾値を超えているか判定
  6. 閾値を超えている場合はレポートコマンドを実行

3の比較元画像↓

2値化した比較画像↓

このレポート機能を実装することで、課題点/改善点であった下記を達成することができます。

  • 学校最強大会が終了した時点で処理が終わるようにする。
  • レポート機能

この自動化プログラムは終了したい時間を設定するようになっているので、終了時間を過ぎているかつレポートを書いたタイミングで処理を終了させることで学校最強大会が終了した時点で処理を止めることができるようにもなっています。

リエントリー機能

機能としては、学校最強大会でリタイアしてポケモンセンター送りになった際に学校最強大会にリエントリーする機能になっています。
この機能を実現できたのは1番の要因は先程紹介したレポート機能にあります。
レポート機能がなかったら実現できなかったと言っても過言ではありません。
なぜレポート機能が必要だったのかについて説明していきたいと思いますが、まずは処理の流れを紹介していきます。

  1. 画面上の画像取得
  2. 取得画像をトリミング
  3. 2でトリミングした画像と比較元画像を2値化
  4. 3の2つの画像(正確にはNumPy配列)を比較し一致している要素を取得
  5. 4で取得した一致している要素数閾値を超えているか判定
  6. 閾値を超えている場合はゲーム終了、再起動コマンド実行

リタイアしたかどうかの判定処理はレポート機能と同じ方法になります。
なぜレポート機能が必要だったのか気付いた方もいるかもしれませんが、このリエントリー機能はリタイアしたかどうかを判定して、リタイアしていればゲームを終了して再度ゲームを開始する流れになっているため、学校最強大会終了後にレポートを書く機能が必要になっていました。(レポートを書いていないとこれまでの周回が無駄になる。。)

ただしリエントリー機能では比較画像を工夫する必要がありました。
当初、判定用の画像は「目の前が真っ暗になった」やポケモンセンターで回復をしてもらう際の文言で比較しようと思ったのですが、画像の取得が不安定であったり同じような要素が他の箇所にも存在するため、判定がうまくできていませんでした。

なので少し強引ではありますが、ポケモンセンターわざマシンの場所を使って判定するようにしています。

ポケモンセンターにいる時に取得できる画像↓

比較元画像↓

そしてこのリエントリー機能を実現することによって下記の課題点/改善点は達成することができました。

比較元画像についてゲーム内の時間帯それぞれで正しく判定できるの?という疑問を持つ方もいるかもしれませんが、問題なく判定できることは検証済みなのでご安心を!

補足:下記の機能も課題点/改善点で挙げていましたが、リエントリー機能があるのでいらないかなということで実装はしていません。

  • 条件判定を行い回復アイテムを使用する。(社内でフィードバックがありニンフィア努力値のH180をSに振ったらあまりリタイアすることはなくなったので要検討)

ソースコード

学校最強大会無限周回プログラム↓

カスタムクラス↓

最後に

個人的にですが、学校最強大会周回のプログラムは満足のいくものが出来上がったと感じています。
ガンガン稼いでポケモン育成頑張ります!

テラピース難民を救うため自動レイドにも挑戦したいですね。。。

ここまで読んでいただきありがとうございました!

*1:閾値を元に画像を0か1の状態(白黒)にすること

【ポケモンSV】switchを自動操作して学校最強大会を周回してみた話

はじめに

とある勉強会にてswitchを自動操作するという話を聞き、やってみたい気持ちが強すぎたのでやってみました。
作成したものはこちらになります。

github.com

プログラムの簡単な説明ですが、ポケモンSVの学校最強大会でニンフィアを重労働させるプログラムになっています。
毎度のことながらポケモンを育成していくにあたってお金は大切ですので、仕事に行っている間や寝ている間にニンフィアに休まず働いてもらいましょう。
※ 重労働させるポケモンは自由です。

2023/04/03追記

本記事の強化版、学校最強大会を無限に周回するプログラムを作成したのでそちらも是非見てください!

ryubloblog.hatenablog.com

使用技術とシステム構成

GitHubのREADMEにも記載していますが下記になります。

自動化にあたってswitchをBluetooth接続する際に、joycontrolの使用を考えていたのですが、最新のswitchのバージョンでは動作が不安定らしいので下記のnxbtを使用することにしました。

github.com

インストールから使用するまでが簡単だったので感謝してます。
なんといってもデモで動作を見ることができたので実装イメージが持ちやすかったです。

環境構築

環境構築はREADMEに記載してあるので簡単に流れを説明していきます。

Step1

まずはラズパイのセットアップを行なっていきます。
やることとしては下記の2つの記事が参考になります。

Step2

依存関係のアップデート

$ sudo apt update
$ sudo apt upgrade

Step3

重労働プログラムのcloneとnxbtのインストール

$ git clone -b old_making_money https://github.com/ryu-0729/switch-pokemonsv-auto-project.git
$ cd switch-pokemonsv-auto-project
$ sudo pip3 install nxbt

Step4

内部Bluetoothの無効化と外部Bluetoothの接続
ここは地味にハマった箇所になるので意外と重要だと思っています。
こちらの記事の方法で実現しました。
ハマった理由としては、デモの実行の際には特に外部Bluetoothを接続する必要はなく動作することが確認できていたので、原因の特定まで時間がかかってしまいました。(恥ずかしい。。)

Step5

重労働させるポケモンの準備
こちらはポケモンSV側での準備になります。
重労働させたいかつ学校最強大会を1ウェポンで勝ち抜けるポケモンを準備してください。

ここまで準備が終わればあとはプログラムを実行するのみです!

プログラムの解説

では簡単に重労働プログラムの解説をしていきたいと思います。
下記ソースになります。

nxbtのリポジトリに用意してくれているdemo.pyファイルをカスタマイズした形になります。
やっていることはシンプルで、59行目のstart_game(controller_index)でコントローラーの接続画面からゲームを起動までを行なっています。
64行目のafter_hour = datetime.timedelta(hours=0, minutes=30)の引数で労働時間を決めます。(デフォルトでは30分)
あとは70行目以降で指定した時間までひたすらRボタンとAボタンを連打するようになっています。
Rボタンを押している理由は、ニンフィアの育成が中途半端なためテラスタルをして火力を底上げしています。。。。

それでは始業時間です。

$ sudo python makingMoney.py

実際の動作

簡単にではありますが、重労働させている様子を撮影しました。

youtu.be

課題点/改善点

ここでお気付きの方もいるかと思いますが、現状のプログラムだとニンフィアが途中でリタイアする可能性があるのです。。。
リタイアすると残り時間ポケモンセンターで回復し続けます。
また、時間による設定を行なっているので学校最強大会中に処理が終わることがほとんどになっています。。
よって今後の追加機能としては下記を考えています。

  • 条件判定を行い回復アイテムを使用する。(社内でフィードバックがありニンフィア努力値のH180をSに振ったらあまりリタイアすることはなくなったので要検討)
  • ポケモンセンターに戻った際は学校最強大会にリエントリーできるようにする。
  • 学校最強大会が終了した時点で処理が終わるようにする。
  • レポート機能

上記の機能は主に下記の記事を参考にOpenCVOCRを使用して実現したいと考えています。

smdbanana.hatenablog.com

また、将来的には物体検出の技術を使用してなんやかんやできたら面白いかなと密かに思っています。

最後に

ここまで読んでいただきありがとうございます!
初の自動化に挑戦してみたわけですが、nxbtがとても使いやすく比較的簡単に自動化することができました。
また、外部の勉強会にはなりますが自動化に興味を持つきっかけをいただきとても感謝しています。
ありがとうございました。

【Python】bit演算 `&`(論理積, AND)演算子の豆知識

今回はbit演算で使用できる&演算子についての豆知識的なのをまとめていきたいと思います。

&演算子とは

簡単にまとめると比較対象を2進数で表した時に同じ桁数が1であれば1を返し、そうでない場合(0の時)は0を返すといった感じになります。

3 & 2の場合
3 = 011
2 = 001
---------
1 = 001

6 & 4の場合
6 = 110
4 = 100
---------
4 = 100

偶奇判定

偶奇を判定するプログラムで&演算子を使用してみると、こいつ知ってるな感が出ます。
ただし、言語によってはand(かつ)を表す&&があり読み手が勘違いする場合もあるので、使用する場合には自己責任でお願いいたしますー

if x & 1:
  # 奇数処理
else:
  # 偶数処理

シフト演算子との利用

これは主にbit全探索の実装の際に使用する書き方ですが、簡単にまとめると1と0のフラグ判定処理みたいなイメージになります。
下記bit全探索の実装から抜き出したものになります。

if (i >> j) & 1:
  # フラグが立っている場合の処理

具体的な数値で見てみましょう。

・i = 5, j = 1の場合
5(101)を右に1シフトしてANDを取ります。
-> 010になります。
よって010 & 001(1)をすると000になります
つまりフラグが立っていないことがわかります。

・i = 5, j = 2の場合
5(101)を右に2シフトしてANDを取ります。
よって001 & 001をすると001になります。
つまりフラグが立っていることがわかります。

最後に

実際、Web開発現場でbit演算や2進数を意識して実装をしなければならない状況にはなったことがないので、使用することは少ないかと思われますが「へー」くらいで流していただければと思いますー

【Python】期待値の計算

期待値の計算のアルゴリズムを簡単にまとめていきたいと思います。

期待値とは

確率論において、確率変数の期待値(きたいち、英: expected value)とは、確率変数のすべての値に確率の重みをつけた加重平均である。確率分布に対して定義する場合は「平均」と呼ばれることが多い
出典: フリー百科事典『ウィキペディアWikipedia)』

ウィキペディアには上記のように記載されていますが、簡単にまとめると「1回の試行で得られる平均的な値」のことを期待値と言います。
大まかな例を挙げるとすると、ある賭け事があるとします。
その賭け事では、0.1%の確率で10000円、0.3%の確率で5000円、0.6%の確率で0円のリターンがあります。
この場合の期待値の計算方法は、
(10000 * 0.1) + (5000 * 0.3) + (0 * 0.6) = 2500(円)
になります。
よって、この賭け事の期待値は「2500円」という結果になります。
つまり、2500円以上を賭けてしまうと損であるということになります。
また、期待値の計算については「期待値の線形性」という性質があるので下記にまとめたいと思います。

期待値の線形性とは

期待値の線形性というのは、複数の期待値の和を求めたい場合に期待値は複数の期待値の和になる性質のことを言います。
例としては、1回目の施行の期待値をX1とし、2回目の施行の期待値をX2とします。
この場合に1回目と2回目の合計の期待値は「X1 + X2」になります。
1回目と2回目の期待値の合計 = 1回目の期待値(X1) + 2回目の期待値(X2)
これはN回の期待値の合計を求める際にも使用することができます。
1回目、2回目...N回目の期待値の合計 = 1回目の期待値 + 2回目の期待値... + N回目の期待値
では実際に、期待値を求めるプログラムを書いていきます。

2つのサイコロの期待値を求める

atcoder.jp

問題は上記になります。
こちらの問題で考えることとしては、先ほどの「期待値の線形性」を利用して「出目の合計の期待値」を求めることになります。
(出目の合計の期待値) = (青の出目の期待値) + (赤の出目の期待値)
出目の合計の期待値は上記の式のように表せるため、これを求めることで答えを導き出すことができます。

n = int(input())
b = list(map(int, input().split()))
r = list(map(int, input().split()))

blue, red = 0.0, 0.0
for i in range(n):
  blue += b[i] / n
  red += r[i] / n

print(blue + red)

ランダムに選択する場合の期待値

atcoder.jp

問題は上記になります。
こちらの問題でも「期待値の線形性」を利用して「合計点数の期待値」を求めることを考えましょう。
(合計点数の期待値) = (1問目の点数の期待値) + (2問目の点数の期待値) + ... + (N問目の点数の期待値)
合計点数の期待値は上記の式のように表せるため、これを求めます。

n = int(input())
answer = 0.0

for i in range(n):
  p, q = map(int, input().split())
  answer += q / p

print(answer)

最後に

今回は期待値の求め方と考え方について簡単にまとめてみました。
期待値を求めるテクニックとして足された回数を考えるというのもあるみたいなので、別の記事としてまとめたいと思います。

読みやすいコードを書くために気をつけていること~part2

今回は前回の記事の続きになります。

ryubloblog.hatenablog.com

この記事では下記の項目についてまとめていきたいと思います。

  • ネストが深すぎないこと
  • 不要なコメントがないこと/コメントが適切に残されていること
  • 処理の共通化が行われていること

ネストが深すぎないこと

ここではif文のネストについてまとめていきたいと思います。
if文による条件分岐を書くことは多々あると思うのですが、このネストが深くなればなるほど
処理を追うことがとても大変になります。
実務上ではあまりない例にはなりますが、下記ページとボタンの種類によって処理を分ける例になります。

type PageType = '一覧' | '新規登録' | '詳細' | '編集';
type ButtonType = '登録' | '更新' | '削除';

// NOTE: ページとボタンで処理を分ける関数
const buttonHandler = (page: PageType, button: ButtonType) => {
  if (page === '一覧') {
    return;
  } else if (page === '新規登録') {
    if (button === '登録') {
      // 登録処理
    }
  } else if (page === '編集') {
    if (button === '更新') {
      // 更新処理
    } else if (button === '削除') {
      // 削除処理
    } else {
      return;
    }
  } else {
    return
  }
};

見るからに処理を追うのが大変ですし、疲れます。
下記修正の例になります。

const buttonHandler = (page: PageType, button: ButtonType) => {
  if (page === '一覧' || page === '詳細') {
    return;
  }

  if (page === '新規登録' && button === '登録') {
    // 登録処理
    return;
  }

  if (page === '編集') {
    if (button === '登録') return;
    button === '更新'
      ? console.log(button) // 更新処理
      : console.log(button); // 削除処理
  }
};

早期リターンを使用したり、三項演算子を使用したり、条件を整理してあげることで、
処理が比較的わかりやすいソースコードになったかと思います。
switch文を使用してみるのも良い方法だと思っています。
個人的にですが、if文においてelse ifが2回以上出てくる場合は、
記述方法を修正した方が良い場合が多いと感じています。

不要なコメントがないこと/コメントが適切に残されていること

まず不要なコメントとは、ソースコードを見ればわかる内容のコメントのことです。
とてもシンプルな例ですが下記、新規登録か編集かで処理を分ける場合のコードになります。

// NOTE: 新規作成か編集で返す文字列を変更する
const title = isEdit ? '編集' : '新規作成';

明らかにこのソースのコメントは処理を見れば、何をしたいのかわかるので不要なコメントになります。

次にコメントが適切に残されていることについてですが、
最近実際に経験した内容を例に挙げたいと思います。
処理の内容としては、Reactで初回マウント時にのみ実行したい処理があったのですが、
ESLintの設定により記述したコードが怒られるといった場合に行った対処とそのコメントになります。

  useEffect(() => {
    // isEditの状態で行う処理が記述されているとする

    // NOTE: 初回マウント時のみ実行したいため、isEditは第2引数から除外する
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

このように、コメントにはなぜその処理を行ったかやなぜ必要なのかを記載することが、
適切であると考えています。
また、そのコメントの種類を表すためのアノテーションというものをつけると、
よりわかりやすいコメントになるかと思います。

処理の共通化が行われていること

処理の共通化は行うことは当たり前ではあると思うのですが、
通化を行っておくことで同じような処理を使い回すことができるようになるので、
メリットしかないです。むしろ共通化していないと確実にコードレビューで指摘されます。
下記例として、共通化を行っていない日付の開始と終了を更新する処理です。

const onChangeStartAtHandler = (startAt: Date | null) => {
  // startAt更新処理
};

const onChangeEndAtHandler = (endAt: Date | null) => {
  // endAt更新処理
};

それぞれ、受け取る値が違うだけでやりたいこととしてはほぼ同じです。
これを共通化した例が下記になります。

type DateType = 'startAt' | 'endAt';

const onChangeDateHandler = (date: Date | null, dateType: DateType) => {
  dateType === 'startAt'
    ? console.log(date) // startAt更新処理
    : console.log(date); // endAt更新処理
};

受け取った値とそのタイプにより処理を条件分岐しています。
ポイントとしては引数を増やして、条件分岐できるようにしたところになります。
引数を増やして共通化できるようになるパターンは比較的多いかと思います。
また、似たような処理を見つけた場合には、共通化できないかと日頃から考えることが大切だと思います。

最後に

今回は読みやすいコードを書くために気をつけていることについて、
2回に分けてまとめて見ましたが、日々コードレビューをされたり、レビューしたりする中で、
新たな発見があれば改めて記事にしていきたいと思います。
ここまで読んでいただきありがとうございます。