生成AIにTOEIC®リスニング問題を無限に生成してもらい、スコア900超え達成した話

# はじめに

今年の夏はTOEICスコア900超えを一つの目標にしていて、そのためにはリスニングが課題でした。
とにかく数をこなそうと、無料で問題を解けるアプリを探して、初めはそれをずっとやっていましたが、一日に解ける問題数が限られていて、徐々に物足りなくなってきました。
前々からTOEICの問題ってAIで生成できるんじゃね？と思っていたので、この際スコア900越えに加えて、AIでリスニング問題生成＆Webサービス化を目標にし、ようやく完成したので公開しました。

# 公開したリスニング問題AI生成サイト

https://equiz-genai.com

※AIで生成し、内容のチェックは行っていないので、正確性は保証できません。

使った主な技術はざっくり以下の通り。
- ChatGPT/GPTs
- Stable Diffusion (SD)
- 音声合成AI

GPTsで問題を生成し、AIで音声合成。Part1のみStable Diffusionで画像も生成。以下、それぞれについて説明します。

# ChatGPT/GPTs

まず、問題を生成するGPTsを作ります。
GPTsとは、ChatGPTに設定や知識を事前に与えておく機能です。これによって、ChatGPTを開く度に毎回「どういう出力にしたいか」を指示する必要がなくなります。以下がGPTsの設定ページです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3892069/b06708c8-146e-c44a-04c3-cf3fac37c102.png "GPTsの設定")

Partいくつの問題を生成するか、難易度を指定/ランダムにするか、何問生成するかなどを、プロンプトで入力する想定にしています。
後続の処理でプログラムで扱いやすいように、TOML形式で出力できるように指示します。
指示の作成中に途中で何度か生成させてみて、所望の出力形式にならなければ、細かい指示を追加し微修正していきます。
最終的にできあがった指示は以下のような形です。

````
まず、どのPartのリスニング問題を生成するか聞きます。
次に、難易度を1~3の数字で聞きます。1はVery Easy、2はEasy, 3はMedium、4はHard, 5はVery Hard, 6はランダムに対応します。
次に問題を何問生成するかを聞きます。
そのあと、問題と正解を生成します。その際、それぞれの問題をどの難易度の問題を生成したかもわかるようにしてください。
Part2の問題は「質問のみから判断して最も適切な回答を選ぶ問題」であることに注意してください。
出力結果は以下のようにTOML形式にしてください。choices配列のインデックスは1から始まることに注意してください。
part3とpart4は一つのproblemにつきquestionは3つあることに注意してください。
```toml
[[problems]]
	part = 1
	level = 4
	description = """
	A group of people are sitting around a conference table with papers and laptops in front of them. 
	One person is standing and pointing at a screen displaying a graph.
	"""
	choices = [
		"The people are eating lunch together.",
		"The people are having a meeting.",
		"The people are exercising in a gym.",
		"The people are waiting at a bus stop."
	]
	answer = 2

[[problems]]
	part = 2
	level = 3
	question = "Could you explain why the meeting was rescheduled?"
	choices = [
		"It was moved to Friday.",
		"Because John had a conflict.",
		"Yes, the minutes are on your desk."
	]
	answer = 2

[[problems]]
	part = 3
	level = 2
	
	[[problems.conversation]]
    	speaker = "Woman A"
    	sex = "Female"
    	speech = "Hello. I'm calling about a coffee machine I purchased from your website. It stopped working even though I haven't had it for very long. I expected it to last much longer than this. "
	[[problems.conversation]]
		speaker = "Man A"
		sex = "Male"
		speech = "Oh, I'm sorry to hear that. Our warranty covers products for up to a year. Do you know when you bought it? "
	[[problems.conversation]]
		speaker = "Woman A"
		sex = "Female"
		speech = "I've had it for a little over a year, so the warranty has probably just expired. This is so disappointing. "
	[[problems.conversation]]
		speaker = "Man A"
		sex = "Male"
		speech = "Well, I'll tell you what we can do. Although we can't replace it, since you're a valued customer, I can offer you a coupon for 40% off your next purchase. "
		
	[[problems.questions]]
		question = "Why is the woman calling?"
		choices = [
			"To cancel an order",
			"To complain about a product",
			"To redeem a gift card",
			"To renew a warranty"
		]
		answer = 2
	[[problems.questions]]
		question = "What does the man ask the woman about?"
		choices = [
			"A model name",
			"A brand of coffee",
			"A catalog number",
			"A date of purchase"
		]
		answer = 4
	[[problems.questions]]
		question = "What does the man offer to do?"
		choices = [
			"Provide a discount",
			"Send a free sample",
			"Extend a warranty",
			"Issue a refund",
		]
		answer = 3

[[problems]]
	part = 4
	level = 3
	talk = """
	Hello, Mr. Lee. This is Thomas from BKS Auto Shop calling with some information about your car repair.
	I know we told you that it would take until next week to get the part we ordered, but we got the part early and I was able to finish the repair. 
	We're going to be closing for the day in a few minutes, but you're welcome to come get your car any time tomorrow. 
	If you need a ride to the shop tomorrow, let me know and I can arrange one for you.
	"""

	[[problems.questions]]
		question = "What does the speaker say about the repair?"
		choices = [
			"It is not required.",
			"It has been finished early.",
			"It will be inexpensive.",
			"It is covered by a warranty."
		]
		answer = 2

	[[problems.questions]]
		question = "When can the listener pick up his car?"
		choices = [
			"Today",
			"Tomorrow",
			"Next week",
			"In two weeks"
		]
		answer = 2

	[[problems.questions]]
		question = "What does the speaker offer to do?"
		choices = [
			"Look for a used part",
			"Refund the cost of a charge",
			"Send an invoice",
			"Arrange a ride"
		]
		answer = 4
```
````

出力されたtomlファイルを保存し、Pythonのプログラムに読み込ませ、画像生成や音声合成のAIに渡します。
tomlファイルの保存が手作業なのが少し面倒なところ。課金してまでAPIで自動化まではいいかな。。

# Stable Diffusion

Part1の写真に適した記述を選ぶ問題のために画像生成AIを使います。
画像生成AIは、DALL-E、SD1.5、SD2.1、SDXL1.0、SD3-mediumを試し、最終的にSDXL1.0に落ち着きました。
DALL-Eは意図した画像＆顔・手足が崩れにくく悪くなかったのですが、APIは課金が必要、写実的な画像が出にくいので、外しました。
SDXLとSD3-mediumとで悩みましたが、SDXLのほうが若干崩れ等がなく安定している印象でした。

AUTOMATIC1111のstable-diffusion-webuiを使っています。
APIモードで起動し、GPTsで生成した画像のdescriptoinをtomlファイルからプロンプトとして渡し、画像を保存します。

# 音声合成

最初はgTTSを検討しましたが、音声のバリエーションが少なく、候補から外しました。
次にGCPの音声合成サービスを試し、音声のバリエーションは豊富で良かったのですが、何問も生成するには料金が高すぎるので諦めました。
最終的には、huggingfaceやgithubをあさり、良さげな学習済みモデルを探しました。

# おわりに

日頃のルーティン（リスニング問題のアプリ、英語ニュース）に加えて、自分で生成した問題を解くことで、久しぶりに受験したTOEICで1回で見事目標のスコア900越えを達成しました。

|Before|After|
|---|---|
|![1726998405792.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3892069/f19a157d-9575-dc21-b311-336c1db89ad8.jpeg)|![1726998405795.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3892069/bbfefaa0-3b59-1409-10e8-06fc46dab17d.jpeg)|

Webサービス化は途中で忙しくなり先延ばしになっていましたが、なんとか公開までこぎつけてホッとしました。
課題としては、Part1の画像が崩れる、選択肢に正解がない/絞れない、Part3&4の会話やスピーチが短い、などありますが、気が向いたら改善していこうと思います。