起動直後
early-init.elの先頭に以下を記述する。
(setq byte-compile-warnings '(cl-functions))
early-init.elの先頭に以下を記述する。
(setq byte-compile-warnings '(cl-functions))
Emacsで補完入力を行うためのパッケージとしてcompany-modeがよく知られている。
本記事ではこの設定を紹介し、備忘録として残しておく。
以下の設定を適用する。
コードを表示する
;; -*- mode: emacs-lisp; coding: utf-8 ;; 主なデフォルトのキーバインド ;; ;; M-n 次の候補選択 ;; M-p 前の候補選択 ;; C-g 候補選択中止 ;; RET 候補選択 ;; F1, C-h docを別バッファに表示 ;; C-w 定義場所を表示 (関数) ;; C-s 候補検索; 表示されている範囲で入力クエリにより候補選択 (modelineが変化する) ;; C-M-s 候補フィルタリング ;; [tab] 共通するprefixを挿入 ;; C-sやC-M-sは忘れがちだが使うと便利 (require 'company) (add-hook 'after-init-hook 'global-company-mode) (setq company-idle-delay 0.3) (setq company-minimum-prefix-length 2) ;; キー設定 (define-key company-active-map (kbd "C-n") 'company-select-next) (define-key company-active-map (kbd "C-p") 'company-select-previous) (define-key company-active-map (kbd "M-n") nil) (define-key company-active-map (kbd "M-p") nil) (define-key company-active-map (kbd "C-m") 'company-complete-selection) ;; 候補に番号を付け、 ;; M-1やM-2などM-をつけて押すことで候補選択をショートカットできる (setq company-show-numbers t) ;; 候補の一番下でさらに下に行こうとすると一番上に戻る (setq company-selection-wrap-around t) ;; ツールチップ上の候補を右揃え (setq company-tooltip-align-annotations t) ;; 参考 https://qiita.com/zk_phi/items/9dc373e734d20cd31641 ;; シンボルの途中部分を補完する ;; → company にはこの挙動が実装されておらず、カーソルの直後に (空白以外 ;; の) 文字がある場合は補完が始まらないため (require 'company-anywhere) ;; 参考 https://qiita.com/zk_phi/items/9dc373e734d20cd31641 ;; ac-dwim時の挙動をcompanyで再現 (require 'company-dwim) (define-key company-active-map (kbd "TAB") 'company-dwim) (setq company-frontends '(company-pseudo-tooltip-unless-just-one-frontend company-dwim-frontend company-echo-metadata-frontend)) ;; 参考 https://qiita.com/zk_phi/items/9dc373e734d20cd31641 ;; company-require-match ;; - never: 補完候補の選択を開始後、補完候補にない文字も打てる (setq company-require-match 'never) ;; 参考 https://qiita.com/zk_phi/items/9dc373e734d20cd31641 ;; 『これは「現在編集中のバッファ、またはそのバッファと同じメジャーモー ;; ドになっているバッファ」から補完候補を探してきてくれるものです。』 (require 'company-same-mode-buffers) (company-same-mode-buffers-initialize) (setq company-backends `( company-dabbrev company-capf company-semantic company-cmake company-clang company-files ;; https://qiita.com/zk_phi/items/9dc373e734d20cd31641 ;; 「組み込みのキーワードも words-in-same-mode-buffers ;; の候補もみんないっしょくたに出てくる」 ;; (company-keywords :with company-same-mode-buffers) (company-dabbrev-code company-gtags company-etags company-keywords) company-oddmuse company-bbdb ,@(unless (version< "24.3.51" emacs-version) (list 'company-elisp)) ,@(unless (version<= "26" emacs-version) (list 'company-nxml)) ,@(unless (version<= "26" emacs-version) (list 'company-css)) )) (push 'company-same-mode-buffers company-backends) ;先頭に追加 ;; lowercaseのみで補完されるのを抑制する(case sensitive) (eval-when-compile (require 'company-dabbrev)) (setq company-dabbrev-downcase nil) ;; 参考 https://shwaka.github.io/2017/05/03/company-dabbrev-japanese.html ;; company-dabbrevで日本語文字が補完候補に含まれないようにする (defun edit-category-table-for-company-dabbrev (&optional table) (define-category ?s "word constituents for company-dabbrev" table) (let ((i 0)) (while (< i 128) (if (equal ?w (char-syntax i)) (modify-category-entry i ?s table) (modify-category-entry i ?s table t)) (setq i (1+ i))))) (edit-category-table-for-company-dabbrev) (setq company-dabbrev-char-regexp "\\cs") ;; company-same-mode-buffers.elにおいて "\\sw"が使用されている箇所を "\\cs"に変更する ;; →日本語文字が補完候補に含まれることを抑制する効果 (defun company-same-mode-buffers-query-construct-any-followed-by (str) (cons "\\(?:\\cs\\|\\s_\\)*?" (regexp-quote str))) (defun company-same-mode-buffers-query-construct-any-word-followed-by (str) (cons "\\cs*?" (regexp-quote str))) (defun company-same-mode-buffers-query-to-regex (lst) "Make a regex from a query (list of `query-construct-*' s)." (concat "\\_<" (mapconcat (lambda (pair) (concat (car pair) "\\(" (cdr pair) "\\)")) lst "") "\\(?:\\s_\\|\\cs\\)*")) (defun company-same-mode-buffers-update-cache (&optional buffer) "Put all symbols in the buffer into `company-same-mode-buffers-cache'." (with-current-buffer (or buffer (current-buffer)) (when (and company-same-mode-buffers-cache-is-dirty (derived-mode-p 'prog-mode)) (let ((tree (gethash major-mode company-same-mode-buffers-cache)) (symbols (company-same-mode-buffers-search-current-buffer (concat "\\(:?+\\cs\\|\\s_\\)\\{" (number-to-string company-same-mode-buffers-minimum-word-length) "," (number-to-string company-same-mode-buffers-maximum-word-length) "\\}")))) (dolist (s symbols) (setq tree (company-same-mode-buffers-tree-insert tree s))) (puthash major-mode tree company-same-mode-buffers-cache) (setq company-same-mode-buffers-cache-is-dirty nil))))) ;; 参考 https://qiita.com/sune2/items/b73037f9e85962f5afb7#company-transformers ;; 補完候補のソート。nilが辞書順 (setq company-transformers '(company-sort-by-occurrence company-sort-by-backend-importance)) ;; prescientのアルゴリズム ;; - 直近の選択を上位に移すソーティング ;; - 直近の選択を履歴として保存する (require 'company-prescient) (company-prescient-mode 1) ;; →company-transformersに prescient由来のものを追加 ;; companyで英単語を補完するためのcompany-wordfreq →MELPAからダウンロード ;; 専用の単語辞書をダウンロードするため以下のコマンドを実行しておく ;; M-x company-wordfreq-download-list (add-hook 'text-mode-hook (lambda () (setq-local company-backends '(company-wordfreq company-dabbrev company-capf)) (setq-local company-transformers nil))) ;; irony C/C++ (require 'company-irony-c-headers) (setq company-backends (append company-backends '(company-irony-c-headers))) (setq company-backends (append company-backends '(company-irony))) (setq company-backends (append company-backends '(company-reftex-labels))) (setq company-backends (append company-backends '(company-reftex-citations))) (provide 'init-company)
zk_phi氏の以下の記事を参考にした部分は多い。
qiita.com
同氏の記事では候補のソーティングにcompany-statisticsが使われていたが、本記事ではcompany-prescientを採用した。
また、company-dabbrevで日本語文字が補完候補に含まれないようにするための設定では以下の記事を参考にした。
shwaka.github.io
この記事で導入された正規表現”\\\cs”をzk_phi氏謹製のcompany-same-mode-buffers.elにも適用し、日本語文字の候補出現を抑制する。このため一部の関数を再定義した。
英単語をcompanyで補完するために、company-wordfreqの設定も入れてある。
emacsで英単語の補完をするには、company-wordfreqを使うのがいいのかな?
— HANGYO, Masatsugu (@mhangyo) March 29, 2021
ちょっと触った感じは普通に使えそうhttps://t.co/0xozBmGlbi
ひとまず快適に使えているので良しとする。
Emacsのeinというパッケージを用いることで、EmacsからJupyter Notebookを編集し、表示することができる。
実行イメージは以下の通りである。
ein(emacsからjupyter)は便利なのでぜひ pic.twitter.com/yBeAucc1n7
— mat (@ballforest) August 21, 2020
Emacsから直接Jupyter Notebookが編集できるので、ブラウザにはない各種補完機能やカーソルジャンプ、undoなど充実したEmacsの編集機能を継承して使えるので便利ということである。
本記事ではその設定をメモしておく。
1. jupyterをインストール
pip3 install jupyter
2. MELPAからeinをインストール(必須)
M-x package-install RET ein RET
(eval-when-compile (require 'ein) (require 'ein-notebook) (require 'ein-notebooklist) (require 'ein-markdown-mode) (require 'smartrep)) ;; (add-hook 'ein:notebook-mode-hook 'electric-pair-mode) ;; お好みで ;; (add-hook 'ein:notebook-mode-hook 'undo-tree-mode) ;; お好みで ;; undoを有効化 (customizeから設定しておいたほうが良さげ) (setq ein:worksheet-enable-undo t) ;; 画像をインライン表示 (customizeから設定しておいたほうが良さげ) (setq ein:output-area-inlined-images t) ;; markdownパーサー ;; M-x ein:markdown →HTMLに翻訳した結果を*markdown-output*バッファに出力 (require 'ein-markdown-mode) ;; pandocと markdownコマンドは入れておく ;; brew install pandoc ;; brew install markdown (setq ein:markdown-command "pandoc --metadata pagetitle=\"markdown preview\" -f markdown -c ~/.pandoc/github-markdown.css -s --self-contained --mathjax=https://raw.githubusercontent.com/ustasb/dotfiles/b54b8f502eb94d6146c2a02bfc62ebda72b91035/pandoc/mathjax.js") ;; markdownをhtmlに出力してブラウザでプレビュー (defun ein:markdown-preview () (interactive) (ein:markdown-standalone) (browse-url-of-buffer ein:markdown-output-buffer-name)) ;; smartrepを入れておく。 ;; C-c C-n C-n C-n ... で下のセルに連続で移動、 ;; その途中でC-p C-p C-pで上のセルに連続で移動など ;; セル間の移動がスムーズになってとても便利 (declare-function smartrep-define-key "smartrep") (with-eval-after-load "ein-notebook" (smartrep-define-key ein:notebook-mode-map "C-c" '(("C-n" . 'ein:worksheet-goto-next-input-km) ("C-p" . 'ein:worksheet-goto-prev-input-km))))
1. M-x ein:run を実行
Notebookを作成したいディレクトリを選択できるようになる。ディレクトリを決定するとJupyterがバックグラウンドで起動する。
2. カーネルを選択
要するにPythonのバージョン指定。
3. ファイル/ノートブック一覧から、ノートブックのところの[Open]をRET
既存のNotebookが起動する。
4. Notebook新規作成は[New Notebook]をRET
主なキーバインドとコマンド一覧を以下に示す。
とりあえず、セルに何か書いたらC-c C-cすれば、セルが評価される。
キーバインド/コマンド | 説明 |
---|---|
M-x ein:run | jupyterを起動 |
C-c C-t | 当該セルの評価モードをPython->MarkDown->rawの順に切り替える |
C-c C-n | 1つ下のセルに移動 |
C-c C-p | 1つ上のセルに移動 |
C-c C-e | 当該セルの表示をトグル |
C-c C-c | 当該セルを評価(Pythonのみ; Markdownは不可) →C-uをつけて実行するとすべてのセルを先頭から評価できる |
C-c C-k | 当該セルを削除 |
C-c C-a | 新規セルを当該セルの上に作成 (C-uを先につけるとMarkdownで作成) |
C-c C-b | 新規セルを当該セルの下に作成 (C-uを先につけるとMarkdownで作成) |
C-c C-s | 当該セルを分割(split) |
C-c C-m | 当該セルをマージ(merge) |
C-c C-l | 当該セルの出力をクリア |
C-c C-S-l | 全てのセルの出力をクリア |
C-x C-s | 現在のnotebookを保存 |
C-x C-w | 現在のnotebookを別名保存 |
M-x ein:notebook-save-to-command | 当該ノートのコピーを、名前を付けて保存 |
C-c C-x C-r | notebookのセッションを再起動する →番号をクリアしたいときに使う |
M-x ein:stop | jupyterを終了 |
Markdownのプレビューには、設定ファイルに書いた ein:markdown-preview を使うことでブラウザ上のプレビューが可能である。応用すればewwやemacs-w3m上で表示させ、Emacs内で完結させることもできるだろう。
Markdown中にLatexで記述した数式をプレビューするための設定。インライン表示ではない。
実行イメージはこちら:
だいたいこんな感じ pic.twitter.com/nAl4nu9GlS
— mat (@ballforest) November 12, 2020
M-x my-markdown-preview-latexとするとバッファがポップアップして、そこに数式が表示される。
(defun my-markdown-preview-latex () "Preview LaTeX from the current cell in a separate buffer. Handles only markdown and code cells, but both in a bit different ways: on the former, its input is being rendered, while on the latter - its output." (interactive) (let* ((cell (ein:worksheet-get-current-cell)) (text-to-render (cond ((ein:markdowncell-p cell) (slot-value cell :input)) ((ein:codecell-p cell) (plist-get (car (cl-remove-if-not (lambda (e) (string= (plist-get e :name) "stdout")) (slot-value cell :outputs))) :text)) (t (error "Unsupported cell type")))) (buffer (get-buffer-create " *ein: LaTeX preview*"))) (with-current-buffer buffer (when buffer-read-only (read-only-mode -1)) (unless (= (point-min) (point-max)) (delete-region (point-min) (point-max))) (insert text-to-render) (goto-char (point-min)) (org-mode) (org-toggle-latex-fragment 16) (special-mode) (unless buffer-read-only (read-only-mode 1)) (display-buffer buffer '((display-buffer-below-selected display-buffer-at-bottom) (inhibit-same-window . t))) (fit-window-to-buffer (window-in-direction 'below)))))
EmacsからJupyter Notebook、一度試してみる価値はあると思う。
PythonまわりのEmacsの設定を整理したということ。Company-modeの設定は言語共通の部分が多く長くなるので省略した。Language serverの紹介がメイン。
Emacsからlanguage serverを使うためにeglotを入れる。MELPAからインストール可能。さらにPython用のpython-language-serverをインストールしておく。ターミナルから以下を実行する。
pip3 install python-language-server
関連パッケージをまとめて入れる場合は以下がラク。
pip install 'python-language-server[all]'
flycheckはOFFにして、flymakeを使うようにした。flymake-diagnostic-at-pointはなかなか便利なのでおすすめ。MELPAからインストール可能。
Emacsからblackを呼び出せるようにするため、python-blackを導入する。
https://github.com/wbolster/emacs-python-black
バッファ保存時に「常に」blackをかけたいときは、以下のように設定可能である。
(declare-function python-black-on-save-mode "python-black") (add-hook 'python-mode-hook #'(lambda () (python-black-on-save-mode)))
python-black-on-save-modeを持ち出さない場合は、以下でもOK。
(add-hook 'python-mode-hook #'(lambda () (add-hook 'before-save-hook 'python-black-buffer nil t)))
前者のminor-modeを使うとlighterの文字列が増えてゴチャゴチャするので、個人的には後者が好み。
projectがblackによるフォーマットを必要とするかに応じて振る舞いを変えたい場合はpython-black-on-save-mode-enable-dwimを使う。このようにユーザに選択の余地が残されているのがよい。
;; Python (setq auto-mode-alist (cons '("\\.py$" . python-mode) auto-mode-alist)) (setq interpreter-mode-alist (cons '("python" . python-mode) interpreter-mode-alist)) ;; pip3 install python-language-server ;; https://github.com/palantir/python-language-server ;; ;; 以下は追加で(お好みで)インストール ;; pip install 'python-language-server[all]' としてしまうのが早い ;; ;; pip3 install rope ; completions and renaming ;; pip3 install pyflakes ; linter to detect various errors ;; pip3 install mccabe ; linter for complexity checking ;; pip3 install pycodestyle ; linter for style checking ;; pip3 install pydocstyle ; linter for docstring style checking ;; pip3 install pyls-mypy ; type checker (heavy) ;; pip3 install pyls-isort ; import sort code formatting ;; pip3 install pyls-black ; code formatting using Black (require 'eglot) (add-hook 'python-mode-hook 'eglot-ensure) ;; server として起動 ;; (add-to-list 'eglot-server-programs ;; `(python-mode . ("pyls" "-v" "--tcp" "--host" ;; "localhost" "--port" :autoport))) ;; 保存時に自動整形 (black) ;; python-blackをmelpaからインストールしておく (add-hook 'python-mode-hook #'(lambda () (add-hook 'before-save-hook 'python-black-buffer nil t))) ;; 以下の設定でもOKだが、minor-modeの分、lighterが増えてごちゃごちゃする ;; (declare-function python-black-on-save-mode "python-black") ;; (add-hook 'python-mode-hook ;; #'(lambda () ;; (python-black-on-save-mode))) ;; 保存時にインポート順を自動修正 ;; py-isortをmelpaからインストールしておく (add-hook 'python-mode-hook #'(lambda () (add-hook 'before-save-hook 'py-isort-buffer nil t))) ;; point位置にメッセージを表示する。eldocと干渉しないのが利点 (with-eval-after-load "flymake" (require 'flymake-diagnostic-at-point) (add-hook 'flymake-mode-hook #'flymake-diagnostic-at-point-mode))
ほか、以下は好みで設定した。エコーエリアが複数行になるのを防ぐ効果がある。
(custom-set-variables '(eldoc-echo-area-use-multiline-p nil))
またsmart-jumpやivy-xrefも便利なので使っている。
(require 'smart-jump) (smart-jump-setup-default-registers) ;; pythonはデフォルトで有効になる ;; M-. (smart-jump-go; xref-find-definitions) ;; M-, (smart-jump-back; xref-pop-marker-stack) ;; M-? (smart-jump-references; xref-find-references) (require 'ivy-xref) (setq xref-show-xrefs-function #'ivy-xref-show-xrefs)
もし、blackユーザならばpycodestyleの設定をしておくと良い。
[pycodestyle] ignore = E203, W503, W504 max-line-length = 88
eglotとpython-language-serverの組み合わせがなかなか快適だったので紹介した。lsp-modeを設定するのも悪くはないし、elpyとflycheckの組み合わせで書くのも悪くはないので(特に軽快性が優れる)、このあたりは好みだろう。
音声の振幅スペクトルから位相を推定し、元の音声を復元するためのGriffin-Limアルゴリズムが知られている。
Griffin-Limアルゴリズムはlibrosaパッケージとtorchaudioパッケージの両方に実装されている。
""" PyTorch script for verification of execution time. """ # Commentary: # torch 1.8.1 # torchaudio 0.8.1 # torchlibrosa 0.0.9 # GeForce RTX 2070 # CUDA 11.2 # CUDA Driver 460.32.03 import time import librosa import numpy as np import torch import torchaudio import torchaudio.transforms as T N_FFT = 1024 WIN_LENGTH = None HOP_LENGTH = 512 N_ITER = 50 if __name__ == "__main__": waveform, sample_rate = librosa.load(librosa.ex("trumpet")) # librosa start = time.time() specgram = np.abs( librosa.stft( waveform, n_fft=N_FFT, hop_length=HOP_LENGTH, win_length=WIN_LENGTH ) ) y_inv = librosa.griffinlim( specgram, n_iter=N_ITER, hop_length=HOP_LENGTH, win_length=WIN_LENGTH ) elapsed_time = time.time() - start print("elapsed_time (librosa): {0:.6f}".format(elapsed_time) + "[sec]") # load waveform as torch.Tensor waveform, sample_rate = torchaudio.load(librosa.ex("trumpet")) waveform_cuda = waveform.cuda() # torchaudio (CPU) specgram = T.Spectrogram( n_fft=N_FFT, win_length=WIN_LENGTH, hop_length=HOP_LENGTH, ) griffin_lim = T.GriffinLim( n_fft=N_FFT, n_iter=N_ITER, win_length=WIN_LENGTH, hop_length=HOP_LENGTH, ) start = time.time() reconstructed = griffin_lim(specgram(waveform)) elapsed_time = time.time() - start print("elapsed_time (torchaudio; CPU): {0:.6f}".format(elapsed_time) + "[sec]") # torchaudio (GPU) specgram = T.Spectrogram( n_fft=N_FFT, win_length=WIN_LENGTH, hop_length=HOP_LENGTH, ).cuda() griffin_lim = T.GriffinLim( n_fft=N_FFT, n_iter=N_ITER, win_length=WIN_LENGTH, hop_length=HOP_LENGTH, ).cuda() torch.cuda.synchronize() start = time.time() reconstructed = griffin_lim(specgram(waveform_cuda)) torch.cuda.synchronize() elapsed_time = time.time() - start print("elapsed_time (torchaudio; GPU): {0:.6f}".format(elapsed_time) + "[sec]")
GPUを使ったtorchaudioが一番早い結果となった。
elapsed_time (librosa): 0.264543[sec] elapsed_time (torchaudio; CPU): 0.147203[sec] elapsed_time (torchaudio; GPU): 0.022305[sec]
かつて、音声認識と音声合成を組み合わせて遊んでみるという主旨の記事を書いたことがある。
音声合成には、コマンドラインから音声合成できるOpenJTalkパッケージを用いたのだった。これをPythonから動かす場合には、専用の関数を定義して呼び出す必要があり、ひと手間かかる。
最近、山本りゅういち氏がPyOpenJTalkなるパッケージを整備し、単体でTTS(Text-to-Speech、テキストを音声に変換する)機能を実現した。
https://t.co/Fi7L4SSdeq アップデート終了。デモノートブック作りました。hts_engine_API, open_jtalk を事前にインストールしなくても良いように内部的に変更しています。以前はテキスト処理だけでしたが、波形生成の仕組みも入れて、単体でTTSとして遊べるようになりました。
— 山本りゅういち / Ryuichi Yamamoto (@r9y9) March 21, 2021
これを使って前回記事のPythonスクリプトを少しきれいにしたというのが本記事の主旨である。
音声認識にはSpeechRecognitionパッケージを使う。PyOpenJTalkが今回のキモなので忘れずにインストールする。最新版をインストールするという意味で--upgradeオプションもつけておく。
pip3 install SpeechRecognition
pip3 install --upgrade pyopenjtalk
以下のスクリプトを適当な名前で保存し、実行する。 「話しかけてみましょう!」と表示されたのち、適当な言葉をしゃべると、認識された言葉をオウム返しで音声合成して終了するというシンプルなものである。
import subprocess import numpy as np import pyopenjtalk import speech_recognition as sr from scipy.io import wavfile OUT_WAV = "/tmp/tts.wav" if __name__ == "__main__": # マイクからの音声入力 r = sr.Recognizer() with sr.Microphone() as source: print("話しかけてみましょう!") audio = r.listen(source) try: # 日本語でGoogle音声認識 text = r.recognize_google(audio, language="ja-JP") except sr.UnknownValueError: print("Google音声認識は音声を理解できませんでした。") except sr.RequestError as e: print("Google音声認識サービスからの結果を要求できませんでした;" " {0}".format(e)) else: print(text) # 音声認識結果を音声合成する x, sr = pyopenjtalk.tts(text) # wavファイルに保存 wavfile.write(OUT_WAV, sr, x.astype(np.int16)) # 保存したwavファイルを再生 subprocess.run("afplay " + OUT_WAV, shell=True)
PyOpenJTalkのおかげでTTSが手軽に実現でき、楽しい。PyOpenJTalkには話速変換やピッチ変換のオプションもあるので、調べて使ってみるのが良いだろう。
話速変換とピッチ変換のオプションを使ったnotebookはこちらから。 nbviewer.jupyter.org
File "pyopenjtalk/htsengine.pyx", line 1, in init pyopenjtalk.htsengine ValueError: numpy.ndarray size changed, may indicate binary incompatibility. Expected 88 from C header, got 80 from PyObject
このようなエラーが出たら、numpyを再インストールしてみるのが吉。
ただし、本記事で利用しているGoogle音声認識APIはあくまでテスト/デモ用であり、いつGoogle側にAPI利用を打ち切られても文句は言えない。Google製の音声認識APIを使いたい場合はCould Speech-to-Textの認証情報を取得して使うのが本筋である。ひと月あたり60分の音声までなら利用無料であるが、それ以上を利用するにはお金がかかる。
PyTorchには音声系データを処理するのに便利なtorchaudioというライブラリが存在する。
pytorch.org
一方、音声系データの処理に便利なlibrosaというパッケージが存在する。
librosa.org
さらにtorchlibrosaという、librosa内部の行列計算まわりをPyTorchで置き換えたパッケージが存在する。
github.com
ここで一つ疑問:
「で、結局どれ使えばいいの?(どれが早いの?)」
この疑問が気になったので、CPU実行とGPU実行における実行時間を比較検証するための簡易的なスクリプトを書いて調べてみたということである。
メルスペクトログラムを計算するスクリプトである。
スクリプトを表示する
""" PyTorch script for verification of execution time. """ # Commentary: # torch 1.7.1 # torchaudio 0.7.2 # torchlibrosa 0.0.9 # GeForce RTX 2070 # CUDA 11.2 # CUDA Driver 460.32.03 import time import librosa import librosa.feature import torch import torchaudio import torchaudio.transforms as T import torchlibrosa as tl N_FFT = 1024 WIN_LENGTH = None HOP_LENGTH = 512 N_MELS = 128 if __name__ == "__main__": waveform, sample_rate = librosa.load(librosa.ex("nutcracker")) # librosa start = time.time() mel_spectrogram = librosa.feature.melspectrogram( y=waveform, sr=sample_rate, n_fft=N_FFT, hop_length=HOP_LENGTH, n_mels=N_MELS, power=2.0, ) elapsed_time = time.time() - start print("elapsed_time (librosa): {0:.6f}".format(elapsed_time) + "[sec]") waveform, sample_rate = torchaudio.load(librosa.ex("nutcracker")) waveform_cuda = waveform.cuda() # torchaudio (CPU) start = time.time() mel_spectrogram = T.MelSpectrogram( sample_rate=sample_rate, n_fft=N_FFT, win_length=WIN_LENGTH, hop_length=HOP_LENGTH, power=2.0, n_mels=N_MELS, ) melspec = mel_spectrogram(waveform) elapsed_time = time.time() - start print("elapsed_time (torchaudio; CPU): {0:.6f}".format(elapsed_time) + "[sec]") # torchaudio (GPU) torch.cuda.synchronize() start = time.time() mel_spectrogram = T.MelSpectrogram( sample_rate=sample_rate, n_fft=N_FFT, win_length=WIN_LENGTH, hop_length=HOP_LENGTH, power=2.0, n_mels=N_MELS, ) mel_spectrogram = mel_spectrogram.cuda() melspec = mel_spectrogram(waveform_cuda) torch.cuda.synchronize() elapsed_time = time.time() - start print("elapsed_time (torchaudio; GPU): {0:.6f}".format(elapsed_time) + "[sec]") # torchlibrosa (CPU) # TorchLibrosa feature extractor the same as librosa.feature.melspectrogram() start = time.time() feature_extractor = torch.nn.Sequential( tl.Spectrogram( n_fft=N_FFT, win_length=WIN_LENGTH, hop_length=HOP_LENGTH, power=2.0, ), tl.LogmelFilterBank( n_fft=N_FFT, sr=sample_rate, n_mels=N_MELS, is_log=False, # Default is true ), ) logmel = feature_extractor(waveform) elapsed_time = time.time() - start print("elapsed_time (torchlibrosa; CPU): {0:.6f}".format(elapsed_time) + "[sec]") # torchlibrosa (GPU) # TorchLibrosa feature extractor the same as librosa.feature.melspectrogram() torch.cuda.synchronize() start = time.time() feature_extractor = torch.nn.Sequential( tl.Spectrogram( n_fft=N_FFT, win_length=WIN_LENGTH, hop_length=HOP_LENGTH, power=2.0, ), tl.LogmelFilterBank( n_fft=N_FFT, sr=sample_rate, n_mels=N_MELS, is_log=False, # Default is true ), ) feature_extractor = feature_extractor.cuda() logmel = feature_extractor(waveform_cuda) torch.cuda.synchronize() elapsed_time = time.time() - start print("elapsed_time (torchlibrosa; GPU): {0:.6f}".format(elapsed_time) + "[sec]")
スクリプトを表示する
""" PyTorch script for verification of execution time. """ # Commentary: # torch 1.8.1 # torchaudio 0.8.1 # torchlibrosa 0.0.9 # GeForce RTX 2070 # CUDA 11.2 # CUDA Driver 460.32.03 import time import librosa import librosa.feature import torch import torchaudio import torchaudio.transforms as T import torchlibrosa as tl N_FFT = 1024 WIN_LENGTH = None HOP_LENGTH = 512 N_MELS = 128 if __name__ == "__main__": torchaudio.set_audio_backend("sox_io") waveform, sample_rate = librosa.load(librosa.ex("nutcracker")) # librosa start = time.time() mel_spectrogram = librosa.feature.melspectrogram( y=waveform, sr=sample_rate, n_fft=N_FFT, hop_length=HOP_LENGTH, n_mels=N_MELS, power=2.0, ) elapsed_time = time.time() - start print("elapsed_time (librosa): {0:.6f}".format(elapsed_time) + "[sec]") waveform, sample_rate = torchaudio.load(librosa.ex("nutcracker")) waveform_cuda = waveform.cuda() # torchaudio (cpu) mel_spectrogram = T.MelSpectrogram( sample_rate=sample_rate, n_fft=N_FFT, win_length=WIN_LENGTH, hop_length=HOP_LENGTH, power=2.0, n_mels=N_MELS, ) start = time.time() melspec = mel_spectrogram(waveform) elapsed_time = time.time() - start print("elapsed_time (torchaudio; CPU): {0:.6f}".format(elapsed_time) + "[sec]") # torchaudio (GPU) mel_spectrogram = T.MelSpectrogram( sample_rate=sample_rate, n_fft=N_FFT, win_length=WIN_LENGTH, hop_length=HOP_LENGTH, power=2.0, n_mels=N_MELS, ) torch.cuda.synchronize() start = time.time() mel_spectrogram = mel_spectrogram.cuda() melspec = mel_spectrogram(waveform_cuda) torch.cuda.synchronize() elapsed_time = time.time() - start print("elapsed_time (torchaudio; GPU): {0:.6f}".format(elapsed_time) + "[sec]") # torchlibrosa (CPU) # TorchLibrosa feature extractor the same as librosa.feature.melspectrogram() feature_extractor = torch.nn.Sequential( tl.Spectrogram( n_fft=N_FFT, win_length=WIN_LENGTH, hop_length=HOP_LENGTH, power=2.0, ), tl.LogmelFilterBank( n_fft=N_FFT, sr=sample_rate, n_mels=N_MELS, is_log=False, # Default is true ), ) start = time.time() logmel = feature_extractor(waveform) elapsed_time = time.time() - start print("elapsed_time (torchlibrosa; CPU): {0:.6f}".format(elapsed_time) + "[sec]") # torchlibrosa (GPU) # TorchLibrosa feature extractor the same as librosa.feature.melspectrogram() feature_extractor = torch.nn.Sequential( tl.Spectrogram( n_fft=N_FFT, win_length=WIN_LENGTH, hop_length=HOP_LENGTH, power=2.0, ), tl.LogmelFilterBank( n_fft=N_FFT, sr=sample_rate, n_mels=N_MELS, is_log=False, # Default is true ), ) torch.cuda.synchronize() start = time.time() feature_extractor = feature_extractor.cuda() logmel = feature_extractor(waveform_cuda) torch.cuda.synchronize() elapsed_time = time.time() - start print("elapsed_time (torchlibrosa; GPU): {0:.6f}".format(elapsed_time) + "[sec]")
GPUを使ったtorchaudioが一番早い結果となった。
elapsed_time (librosa): 0.042655[sec] elapsed_time (torchaudio; CPU): 0.021293[sec] elapsed_time (torchaudio; GPU): 0.002648[sec] elapsed_time (torchlibrosa; CPU): 0.237388[sec] elapsed_time (torchlibrosa; GPU): 0.209118[sec]
koukyo1213様のコメントに従って、torch.nn.Sequentialやtorchaudio.transforms.MelSpectrogramのインスタンス化部分を計測対象から外した場合の結果を以下に示す。
elapsed_time (librosa): 0.040965[sec] elapsed_time (torchaudio; CPU): 0.014024[sec] elapsed_time (torchaudio; GPU): 0.001927[sec] elapsed_time (torchlibrosa; CPU): 0.027673[sec] elapsed_time (torchlibrosa; GPU): 0.006538[sec]
今回もGPUを使ったtorchaudioが一番早い結果となった。