はじめに
半年間の育休中にスキマ時間で個人開発した内容を振り返ってみます。
(開発の話しかしないので、育児の話に興味がある方は「男性ワンオペ育休6ヶ月間の振り返り」も合わせてお読みいただければと思います)
全体的には、生成AI,音韻・音楽解析、CI/CDなどその時々で興味のある調査や開発をしました。
半年間でQiitaの記事59本(26週連続投稿)、GitHubのプルリクエスト59件、学会ワークショップでの発表1件、RAGコンペ参加1回、OSSパッケージ公開4件をしました。
育休中なので趣味に使える時間は主に子どもの睡眠中に限られていました。スクリーンタイムなどから見積もった実働時間は半年間で540時間ほどでした。4時間半に1回、記事かプルリクエストを出していた計算であり、時間に対する成果で考えると、個人的な感覚としてはまあまあ妥当か、少し頑張れた程度かなと思います。
開発環境にはCursorを使っており、開発加速にかなり貢献してくれました。
以下では主な開発トピックをもう少し詳細に振り返ってみます。
生成AI
音声対話
- 出身研究室の手伝いで、生成AIを用いた高速な音声対話のデモプログラムを開発しました。
- 非同期処理で音声認識や音声合成を多少の精度とのトレードオフはありつつ効率化したことで、ロボットの発話開始までの遅延時間を0.5~1秒程度まで短縮でき、ベースになったシンプルな実装と比べ5倍くらい早くなりました。
- 長期的なメンテナンス性より短時間でサクッと試せることを重視しました。
- 実装の過程で、voicevoxの使い方、threadingによる音声認識とVADの並列実行、pytestにおける非同期socketやtimeoutの挙動などについて学びました。
- 相槌やターンテイキングのタイミングを予測するRealtime-VAPの試用・検証記事を書きました。動かすことはできたものの、1つのマイクによるロボット対話で使うには追加の工夫が必要に感じられました。
キーワード検索の改善
高速化
- langchainのBM25Retrieverなど、RAGなどに使えるキーワード検索ライブラリのマージ(検討1、2、3、4)と検索(検討5、6)の高速化方法を検討しました。
- 最終的に、検証した範囲では、マージを2倍、検索を50倍ほど高速化できました。
- pythonでよく使われるrankbm25のエラー対応やRustベースのBM25Vectorizerの調査を通じて、単語分割の並列処理やスコア計算式のバリエーションについて学びました。
検索精度向上
- 主にBM25のスコア計算方法や単語分割方法を再考(検討1、2、3)し、評価しました。
- クエリの重みベクトル化や文字トリグラム分割によって精度が向上する場合があることを、主要な日本語ベンチマークを用いて確認しました。
- 評価のためにJMTEBをキーワード検索に対応させたフォーク版を作成しました。
- JMTEBインストール時のバグ対応などを通じて、日本語ベンチマークの評価コードの実装や改修方法を学びました。
日本語時間表現の解析
- リマインダチャットボットを作りたくなったので、日本語の時間表現解析技術のサーベイ、既存のルールベースの解析ライブラリ(ja-timex)の精度を調査し、ja-timexがカバーしない表現(「次の」など)への対応方法を検討しました。
- 最終的にはLLMによる日本語時間表現の理解能力を検証し、十分に高いことを確認しました。
- LLMの強力さを学びました。
音韻・音声処理
音韻埋め込み
- 音韻を考慮した埋め込みを作成する方法を調査し、編集距離ベースの疎ベクトルを主成分分析で低次元化する方法、音声とテキストの対応で学習する方法、音素系列でBERT的な学習やWord2Vec的な学習をする手法などがあることを学びました。
- 上記で調査したモデルをいくつか試してみました(XPhoneBERT、Wav2Vecのトークン表現など)。あまり空耳自動生成には向いていないかもしれないことがわかりました。
日本語音韻検索データセット
- 空耳自動生成の評価をしやすくするため「〇〇で歌ってみた」替え歌に基づく音韻検索データセットを作成し、CDLAライセンスで公開しました。
- 同データセットでベースラインとなる複数の編集距離ベースの手法の精度を評価しました。
- 同データセットでLLMの音韻検索性能を評価し、あまり高くないことを確認しました。LLMの韻踏み能力が高くないことも予備的に確認しました。
- 公開にあたって、替え歌歌詞の効率的な解析方法やデータセットの著作権に関する法的な観点を検討しました。
音声テキストアライメント
- 楽曲分析に役立てるため日本語テキストの発話タイミングを音声と対応付ける技術(強制アライメント)の調査と検証をしました。
- torchaudioの強制アライメント関数を用いたサンプルの実行や、日本語モデルの性能検証をしました。
- 強制アライメントでは日本語特化のモデルを使う利点は少ないことがわかりました。
空耳替え歌分析
- 音韻類似性に伴うユーモアへの理解を深めるため、[「〇〇で歌ってみた」と呼ばれる空耳替え歌の歌詞を分析し、コンクールでの高評価につながる音韻的な要因を考察]しました。
- 公開中の空耳自動生成プログラムの辞書を更新しました。
CI/CD、開発環境整備
uvとGitHub ActionsによるCI/CDテンプレート
- uvとGitHub Actionsを用いた効率的なパッケージ管理方法を検討し、テンプレートとして公開しました。
- その過程で、GitHub Actionsでのuvの実行方法、PyPIへのpublish、効率的なバージョン管理方法、sphinxによるドキュメント自動生成やデプロイ、ローカルでのGitHub Actionsのテスト方法などを学びました。
開発振り返り用の集計プログラム(Qiita、GitHub API)
- 開発の振り返りに役立てるため、QiitaやGitHubのAPIの使い方を学びました。
- Qiitaの投稿数やリアクション数を集計し、どのような指標で分析するのがよいか検討しました。また実装をパッケージ化して公開しました。
- GitHub上での活動を集計するプログラムを作りました。
- 副次的にpydantic-settingsの使い方に詳しくなりました(記事1、2、3)
機械学習用パソコンの初期設定
- 機械学習用にNVIDIA GPUを積んだPCを購入したので、設定時の試行錯誤をまとめました。
- ubuntuをインストールして、GPU周りを設定し、リモートデスクトップの実現手段の比較検討などをしました。
- 思いのほか、うまく動かず悩まされたため、GPU周りの設定の勉強になりました。
対外的な活動
学会ワークショップ発表
- 音韻検索データセットの開発と検証についてNLP2025の併設ワークショップNLR2025で口頭発表しました(プログラム、発表資料)
- NLP2025に参加して興味深かった発表のメモを公開しました。
RAGコンペ参加
- Retrieval Augmented Generation(RAG)の国内コンペ(Raggle)に参加し、入賞はしませんでしたが、上位10%程度の成績になりました
- 検討の過程でlangchainによる構造化出力や画像入力の方法を学びました(記事1、2、3、4)
OSSパッケージ
xfmido
kanasim
- 日本語かな文字列の音韻類似度を高精度に計算するライブラリkanasimを開発しました。
soramimi-phonetic-search-dataset
- 「〇〇で歌ってみた」替え歌に基づく日本語音韻検索データセットを評価等に使いやすいようにPyPIパッケージとしても公開しました。
qiitareactioncounter
おわりに
半年間の振り返りをしてみました。
我ながらバラバラなことをやっているなと思いましたが、pydantic-settingsやuv、GitHub Actionsなど新規のライブラリやフレームワークに手を出せたのは良い経験でした。対外発表も密かに目標にしていたので実現できてよかったです。
反省としては、バラバラなことをやりすぎて全体的に浅い成果になってしまったので、せっかく540時間も稼働するならもう少し1つのことを深く掘り下げられればよかったです。子どもの体調や睡眠によって、開発の中断・再開のタイミングが左右されるので、どうしても小ぶりに成果が出るものばかりに取り組みがちになりました。同じ理由で、全く新しい新規のものに手を出すこともあまりできていませんでした。今後、同じような機会があるかはわかりませんが、もし訪れたら、長期的な時間の使い方にも勇気を出して取り組みたいなと思いました。
この記事では、開発トピックについての定性的な振り返りをしましたが、QiitaやGitHub上での活動指標から定量的に振り返ってみるのも面白そうだと思っているので、時間があれば取り組みたいです。特にCursorを本格的に使いだしてから生産性が上がった印象があり、それを量的に示唆できると面白いかなと思っています。
以上、ここまで記事を読んでいただきありがとうございました。
付録
半年間で書いた記事、成果物一覧
-
生成AI
- 音声対話
- キーワード検索
- 高速化
- 【rank_bm25】AttributeError: Can't get attribute 'tokenize' on
- 【python】rank_bm25のインスタンスを高速にマージ
- 【langchain】BM25Retrieverの高速なマージ
- rank_bm25のインスタンスをオーバーライドなしで高速にマージする方法の速度・精度比較
- TfidfVectorizerのマージ方法の違いによる精度比較
- 【langchain】BM25Retrieverの高速化(scikit-learn vs rank_bm25)
- 【langchain】BM25Retrieverの高速化(rank_bm25使用) v0.0.2
- 検索精度向上
- 【疑問】BM25でもTFIDF同様にコサイン類似度に基づいてランキングしてよいのか
- 検索タスクにおけるBM25のコサイン類似度とスコアの精度比較
- jupyter notebookでhuggingfaceのload_datasetが終わらない
- 日本語テキスト埋め込みベンチマークJMTEBのpoetry installでInstalling torch (2.3.1): Failed
- TFIDFとBM25の類似度計算方法の比較:検索ベンチマークMIRACLを用いた検証
- BM25検索でクエリを重みベクトルにしてみる: MIRACLとJMTEBでの検証
- 【Python】RustベースのTfidfVectorizerとBM25Vectorizerを試す【lenlp】
- 高速化
- 日本語時間表現の解析
-
音韻・音声処理
-
音韻埋め込み
-
日本語音韻検索データセット
-
音声テキストアライメント
-
空耳替え歌分析
-
-
CI/CD、開発環境整備
-
uvとGitHub ActionsによるCI/CDテンプレート
- 作業メモ: uvからtestpypiにpublish
- メモ:github actions上でuvでタスク実行するときの最低限のyaml
- 【M1Mac / DockerDesktop / act】Error: failed to start container: Error response from daemon: error while creating mount source path
- uvとgithub actionsによる効率的なpythonパッケージのバージョン更新
- uvとsphinxでパッケージのdocsを自動生成
- uvとgithub actionsによるsphinxのビルドとgithub pagesアップロード
- uvとGitHub ActionsでpythonパッケージとドキュメントのCI/CD
-
開発振り返り用の集計プログラム(Qiita、GitHub API)
-
機械学習用パソコンの初期設定
-
-
対外的な活動
-
学会ワークショップ発表
-
RAGコンペ
-
OSSパッケージ
-
-
その他
- n人をkチームに分ける試行を複数回行う際、できるだけ少ない回数で全員が自分以外のメンバーと1回以上同じチームになるようにする(いわゆるソーシャルゴルファー問題です。家族からの要望でコードを書きました)
記事タイトル一覧の取得に用いたコード
import os
import requests
# アクセストークンとユーザーIDの設定
access_token = os.environ["QIITA_TOKEN"]
user_id = USERNAME
# 取得期間の設定
start_date = "2024-10-01"
end_date = "2025-03-31"
# APIエンドポイントとヘッダーの設定
url = "https://qiita.com/api/v2/items"
headers = {"Authorization": f"Bearer {access_token}"}
# クエリパラメータの設定
query = f"user:{user_id} created:>={start_date} created:<={end_date}"
params = {"query": query, "per_page": 100, "page": 1}
# 記事情報の取得と出力
all_articles = []
while True:
response = requests.get(url, headers=headers, params=params)
if response.status_code != 200:
print(f"Error: {response.status_code}")
break
articles = response.json()
if not articles:
break
all_articles.extend(articles)
params["page"] += 1
# created_atでソートして出力
sorted_articles = sorted(all_articles, key=lambda x: x["created_at"])
for article in sorted_articles:
title = article["title"]
article_url = article["url"]
print(f"[{title}]({article_url})")
% uv run qiita_titles.py
[pydantic-settingsで環境変数とコマンドライン引数を同時管理](https://qiita.com/shimajiroxyz/private/381a8683bd8edb40b24d)
[uvとGitHub ActionsでpythonパッケージとドキュメントのCI/CD](https://qiita.com/shimajiroxyz/private/24e25d9edbe0eb9807aa)
[Qiita記事のリアクション数を自分と全体で比較するプログラム](https://qiita.com/shimajiroxyz/items/a453218349a229d864cc)
[Pydantic BaseSettingsにおけるCLI引数、環境変数、コンストラクタ引数の優先順位](https://qiita.com/shimajiroxyz/private/f6d972411bd584679e6f)
...