OpenAI の Speech to Text を試す
はじめに
この記事では OpenAI の Speech to Text を試してみます。
Speech to Text とは
Speech to Text とは音声をテキストに変換する技術です。略して STT などと呼ばれたり、音声認識と呼ばれることがあります。
Speech to Text は、Google の Speech-to-Text や AWS の Transcribe などが有名です。本記事では、OpenAI の Speech to Text を利用して音声をテキストに変換してみます。
OpenAI の Speach to Text は音声をテキストに変換する API です。2つの機能を提供しています。
-
transcriptions
- 音声ファイルをテキストに変換します。
-
translations
- 音声ファイルを翻訳します。
音声認識させるためにはファイルアップロードが必要です。ファイルのアップロードは現在 25 MB に制限されており、サポートされているファイルはこちらです。
- mp4
- mpeg
- mpga
- m4a
- wav
- webm
サポートされている言語が記載されています。
作業環境の準備
以降利用する作業環境を構築しておきます。
長いので折りたたんでおきます。
package.json を作成
package.json
を作成します。
$ mkdir -p node-stt-sample
$ cd node-stt-sample
$ pnpm init
package.json
を変更します。
{
"name": "mute-sample",
"version": "1.0.0",
"description": "",
- "main": "index.js",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "keywords": [],
- "author": "",
- "license": "ISC"
+ "main": "index.ts",
+ "scripts": {
+ "typecheck": "tsc --noEmit",
+ "dev": "vite-node index.ts",
+ "build": "tsc"
+ },
+ "keywords": [],
+ "author": "",
}
TypeScript & vite-node をインストール
TypeScript と vite-node をインストールします。補足としてこちらの理由のため ts-node
ではなく vite-node
を利用します。
$ pnpm install -D typescript vite-node @types/node
TypeScriptの設定ファイルを作成
tsconfig.json
を作成します。
$ npx tsc --init
tsconfig.json
を上書きします。
{
"compilerOptions": {
/* Base Options: */
"esModuleInterop": true,
"skipLibCheck": true,
"target": "ES2022",
"allowJs": true,
"resolveJsonModule": true,
"moduleDetection": "force",
"isolatedModules": true,
/* Strictness */
"strict": true,
"noUncheckedIndexedAccess": true,
"checkJs": true,
/* Bundled projects */
"noEmit": true,
"outDir": "dist",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "preserve",
"incremental": true,
"sourceMap": true,
},
"include": ["**/*.ts", "**/*.js"],
"exclude": ["node_modules", "dist"]
}
git
を初期化します。
$ git init
.gitignore
を作成します。
$ touch .gitignore
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
dist/
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
.env
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
動作確認コードを作成
動作を確認するためのコードを作成します。
$ touch index.ts
console.log('Hello, World');
型チェック
型チェックします。
$ pnpm run typecheck
動作確認
動作確認を実施します。
$ pnpm run dev
Hello, World
コミットします。
$ git add .
$ git commit -m "作業環境構築"
OpenAI API キーを取得
OpenAI API キーの取得方法はこちらを参照してください。
環境変数の設定
環境変数に OpenAI キーを追加します。<your-api-key>
に自身の API キーを設定してください。
$ touch .env
# OPENAI_API_KEY は OpenAI の API キーです。
OPENAI_API_KEY='<your-api-key>'
Node.js で環境変数を利用するために dotenv
をインストールします。
$ pnpm i -D dotenv
コミットします。
$ touch .env.example
# OPENAI_API_KEY は OpenAI の API キーです。
OPENAI_API_KEY='<your-api-key>'
$ git add .
$ git commit -m "環境変数を設定"
OpenAI のパッケージをインストール
openai
パッケージをインストールします。
$ pnpm install -D openai
コミットします。
$ git add .
$ git commit -m "パッケージをインストール"
Transcriptions
Transcriptions API は入力された音声ファイルをテキストに変換します。
簡易的な利用
ここでは簡易的に Speech to Text を利用します。
$ touch transcriptions-demo01.ts
import fs from "fs";
import OpenAI from "openai";
const openai = new OpenAI();
async function main() {
const transcription = await openai.audio.transcriptions.create({
file: fs.createReadStream("audio/demo1.mp3"),
model: "whisper-1",
});
console.log(transcription);
}
main();
音声ファイルを audio/demo1.mp3
に保存しておきます。
動作確認のための音声は自身で録音してもよいですし、こちらから取ってきても良いです。
今回は「軽くひねってあげましょう」という音声を利用します。
$ mkdir audio
動作確認します。しっかり認識しています。
$ pnpm vite-node transcriptions-demo01.ts
{ text: '軽くひねってあげましょう' }
パラメータの追加
さらにパラメータを追加してみます。
-
response_format
は出力形式を指定します。デフォルトはjson
です。今回はverbose_json
を設定し詳細データを吐き出すようにします。 -
temperature
はランダム性を指定します。デフォルトは0
です。1.0
に近づくほどランダム性が増します。
$ touch transcriptions-demo02.ts
import fs from "fs";
import OpenAI from "openai";
const openai = new OpenAI();
async function main() {
const transcription = await openai.audio.transcriptions.create({
file: fs.createReadStream("audio/demo1.mp3"),
model: "whisper-1",
response_format: "verbose_json", // デフォルトはJSON。
temperature: 0.8 // デフォルトは0。1.0に近づくほどランダム性が増す
});
console.log(transcription);
}
main();
動作確認します。
-
verbose_json
により詳細データが出力されています。 -
temperature
が0.8
になっていますが、今回は内容が簡単なため変化は見えません。
$ pnpm vite-node transcriptions-demo02.ts
{
task: 'transcribe',
language: 'japanese',
duration: 1.7400000095367432,
text: '軽くひねってあげましょう',
segments: [
{
id: 0,
seek: 0,
start: 0,
end: 2,
text: '軽くひねってあげましょう',
tokens: [Array],
temperature: 0.800000011920929,
avg_logprob: -0.22753798961639404,
compression_ratio: 0.7659574747085571,
no_speech_prob: 0.07101698219776154
}
]
}
更に別のパラメーtimestamp_granularities
を追加してみます。すると、時間単位での情報が追加されています。
$ touch transcriptions-demo03.ts
import fs from "fs";
import OpenAI from "openai";
const openai = new OpenAI();
async function main() {
const transcription = await openai.audio.transcriptions.create({
file: fs.createReadStream("audio/demo1.mp3"),
model: "whisper-1",
response_format: "verbose_json", // デフォルトはJSON。
temperature: 0.8, // デフォルトは0。1.0に近づくほどランダム性が増す
timestamp_granularities: ["word"]
});
console.log(transcription);
}
main();
動作確認すると、単語単位での時間情報が追加されています。
pnpm vite-node transcriptions-demo03.ts
{
task: 'transcribe',
language: 'japanese',
duration: 1.7400000095367432,
text: '軽くひねってあげましょう',
words: [
{ word: '軽', start: 0, end: 0.2800000011920929 },
{ word: 'く', start: 0.2800000011920929, end: 0.5400000214576721 },
{ word: 'ひ', start: 0.5400000214576721, end: 0.7200000286102295 },
{ word: 'ね', start: 0.7200000286102295, end: 0.8999999761581421 },
{ word: 'って', start: 0.8999999761581421, end: 1.059999942779541 },
{ word: 'あ', start: 1.059999942779541, end: 1.1799999475479126 },
{ word: 'げ', start: 1.1799999475479126, end: 1.3200000524520874 },
{ word: 'ましょう', start: 1.3200000524520874, end: 1.659999966621399 }
]
}
API リファレンス
Transcriptions API の利用についてはこちらに詳しく記載されています。
コミットします。
誤って音声データアップしないように .gitignore
に追加します。
audio/*
$ git add .
$ git commit -m "Transcriptions API を試す"
Translations
Translations API は音声ファイルを翻訳します。現状では英語にしか翻訳できないようです。
使ってみる
ここでは実際に使ってみます。
$ touch translations-demo01.ts
import fs from "fs";
import OpenAI from "openai";
const openai = new OpenAI();
async function main() {
const translation = await openai.audio.translations.create({
file: fs.createReadStream("audio/demo1.mp3"),
model: "whisper-1",
response_format: "verbose_json", // デフォルトはJSON。
});
console.log(translation);
}
main();
動作確認します。「I'll give you a light twist.」に翻訳されました。あってますね。
$ pnpm vite-node translations-demo01.ts
{
task: 'translate',
language: 'english',
duration: 1.7400000095367432,
text: "I'll give you a light twist.",
segments: [
{
id: 0,
seek: 0,
start: 0,
end: 2,
text: " I'll give you a light twist.",
tokens: [Array],
temperature: 0,
avg_logprob: -0.6929762959480286,
compression_ratio: 0.7777777910232544,
no_speech_prob: 0.07101698219776154
}
]
}
Chat Completion API を利用した翻訳方法
翻訳する方法としてChat Completion APIを利用する方法もあります。処理の流れはこちらです。
- Transcriptions API を利用し音声データをテキスト化し音声認識する
- 音声認識の結果を Chat Completion API により指定した言語で翻訳
このポイントのメリットは、Chat Completion API により後処理を行うことで柔軟な処理ができます。デメリットは複数回 API を実行することになるため、処理時間がかかることです。
では、コードで実装してみます。
$ touch translations-demo02.ts
import fs from "fs";
import OpenAI from "openai";
const openai = new OpenAI();
async function main() {
const transcription = await openai.audio.transcriptions.create({
file: fs.createReadStream("audio/demo1.mp3"),
model: "whisper-1",
response_format: "verbose_json", // デフォルトはJSON。
});
const language = "英語";
console.log('音声認識結果\n', transcription?.text ?? "");
// 専門用語を追加
const systemPrompt = `あなたは有能なアシスタントです。あなたの任務は入力されたテキストを${language}に翻訳してください。翻訳結果のみを出力死てください。`;
const completion = await openai.chat.completions.create({
model: "gpt-4o",
temperature: 0,
messages: [
{
role: "system",
content: systemPrompt
},
{
role: "user",
content: transcription.text
}
]
});
console.log('翻訳結果\n', completion?.choices[0]?.message.content);
}
main();
動作確認します。「I'll give you a light twist.」に翻訳されました。あってますね。
$ pnpm vite-node translations-demo02.ts
音声認識結果
軽くひねってあげましょう
翻訳結果
Let's give it a little twist.
API リファレンス
Translations API の利用についてはこちらに詳しく記載されています。
コミットします。
$ git add .
$ git commit -m "Translations API を試す"
専門用語の追加
Whisper がデフォルトでテキストに変換できない専門用語を認識できるようにする方法があります。
prompt を利用し修正
1 つ目の方法は prompt
を活用する方法です。prompt
の引数を追加することで最大 244 トークンまでですが、専門用語を追加できます。例えば以下のような言葉を読み上げて音声データを作成します。音声データの中身は意味の無い内容です。
出版会社の株式会社ホゲホゲMAXが灼熱でぃあぼろすという本を出版しました。この本では魔界の王であるディアボロスが登場し、勇者ポンぽこポコぽんと戦いながら人間界を支配していく物語です。
ここで追加したい専門用語はこちらです。
- 株式会社ホゲホゲ MAX
- 灼熱でぃあぼろす
- 勇者ポンぽこポコぽん
では、実際に prompt
を利用し専門用語を追加して音声認識させてみます。
$ touch correction-demo01.ts
import fs from "fs";
import OpenAI from "openai";
const openai = new OpenAI();
async function main() {
const transcription = await openai.audio.transcriptions.create({
file: fs.createReadStream("audio/demo2.wav"),
model: "whisper-1",
response_format: "verbose_json", // デフォルトはJSON。
prompt:"株式会社ホゲホゲMAX, 灼熱でぃあぼろす, 勇者ポンぽこポコぽん", // 専門用語を追加
});
console.log(transcription);
}
main();
動作確認をします。専門用語をしっかり認識しています。
$ pnpm vite-node correction-demo01.ts
{
task: 'transcribe',
language: 'japanese',
duration: 14.399999618530273,
text: '出版会社の株式会社ホゲホゲMAXが 灼熱でぃあぼろすという本を出版しました。 この本では魔界の王であるでぃあぼろすが登場し、 勇者ポンぽこポコポンと戦いながら人間界を支配していく物語です。',
segments: [
{
id: 0,
seek: 0,
start: 0,
end: 6,
text: '出版会社の株式会社ホゲホゲMAXが 灼熱でぃあぼろすという本を出版しました。',
tokens: [Array],
temperature: 0,
avg_logprob: -0.1343606412410736,
compression_ratio: 1.4010416269302368,
no_speech_prob: 0.007006216328591108
},
{
id: 1,
seek: 0,
start: 6,
end: 16,
text: 'この本では魔界の王であるでぃあぼろすが登場し、 勇者ポンぽこポコポンと戦いながら人間界を支配していく物語です。',
tokens: [Array],
temperature: 0,
avg_logprob: -0.1343606412410736,
compression_ratio: 1.4010416269302368,
no_speech_prob: 0.007006216328591108
}
]
}
ちなみに、専門用語を追加しない場合の認識結果も確認します。
$ touch correction-demo02.ts
import fs from "fs";
import OpenAI from "openai";
const openai = new OpenAI();
async function main() {
const transcription = await openai.audio.transcriptions.create({
file: fs.createReadStream("audio/demo2.wav"),
model: "whisper-1",
response_format: "verbose_json", // デフォルトはJSON。
});
console.log(transcription);
}
main();
専門用語を認識してくれません。
$ pnpm vite-node correction-demo02.ts
{
task: 'transcribe',
language: 'japanese',
duration: 14.399999618530273,
text: '出版会社の株式会社ホゲホゲマックスが 灼熱ディアブロスという本を出版しました この本では魔界の王であるディアブロスが登場し 勇者ポンポコポコポンと戦いながら人間界を支配していく物語です',
segments: [
{
id: 0,
seek: 0,
start: 0,
end: 6.119999885559082,
text: '出版会社の株式会社ホゲホゲマックスが 灼熱ディアブロスという本を出版しました',
tokens: [Array],
temperature: 0,
avg_logprob: -0.11454080790281296,
compression_ratio: 1.4308511018753052,
no_speech_prob: 0.007779653649777174
},
{
id: 1,
seek: 0,
start: 6.119999885559082,
end: 16.040000915527344,
text: 'この本では魔界の王であるディアブロスが登場し 勇者ポンポコポコポンと戦いながら人間界を支配していく物語です',
tokens: [Array],
temperature: 0,
avg_logprob: -0.11454080790281296,
compression_ratio: 1.4308511018753052,
no_speech_prob: 0.007779653649777174
}
]
}
Chat Completion API による後処理
2 つ目の方法は、Chat Completion API
を利用し、後処理を行う形で認識結果を修正する方法です。
$ touch correction-demo03.ts
import fs from "fs";
import OpenAI from "openai";
const openai = new OpenAI();
async function main() {
const transcription = await openai.audio.transcriptions.create({
file: fs.createReadStream("audio/demo2.wav"),
model: "whisper-1",
response_format: "verbose_json", // デフォルトはJSON。
});
console.log('音声認識結果(補正前)\n', transcription?.text ?? "");
// 専門用語を追加
const systemPrompt = "あなたは株式会社ほげほげMAXの有能なアシスタントです。あなたの任務は作成されたテキストのスペルの不一致を修正することです。次の言葉が正しく綴られていることを確認してください。株式会社ホゲホゲMAX, 灼熱でぃあぼろす, 勇者ポンぽこポコぽん。必要な句読点(ピリオド、カンマ、大文字など)のみを追加し、提供された文脈のみを使用してください。";
const completion = await openai.chat.completions.create({
model: "gpt-4o",
temperature: 0,
messages: [
{
role: "system",
content: systemPrompt
},
{
role: "user",
content: transcription.text
}
]
});
console.log('音声認識結果(補正後)\n', completion?.choices[0]?.message.content);
}
main();
$ pnpm vite-node correction-demo03.ts
音声認識結果(補正前)
出版会社の株式会社ホゲホゲマックスが 灼熱ディアブロスという本を出版しました この本では魔界の王であるディアブロスが登場し 勇者ポンポコポコポンと戦いながら人間界を支配していく物語です
音声認識結果(補正後)
出版会社の株式会社ホゲホゲMAXが『灼熱でぃあぼろす』という本を出版しました。この本では魔界の王であるディアブロスが登場し、勇者ポンぽこポコぽんと戦いながら人間界を支配していく物語です。
コミットします。
$ git add .
$ git commit -m "専門用語を追加"
まとめ
この記事では OpenAI の Speech to Text を試してみました。
作業リポジトリ
Discussion