コンユウメモ @kon_yu

作ったガラクタとか、旅行とかの話

1ヶ月毎日オンライン英会話を工夫して受け続けたらスピーキングが向上した

概要

2020年12月は1日も毎日休むことなくオンライン英会話をしてみた。その際にいろんなサービスやツールを組み合わせて効率をあげだり工夫や、どの程度英語力がアップしたか共有したい。

きっかけ

2019年はTOEICをちゃんと受けてみようブームが到来して2回受けていた。2020年はコロナの影響で中止になったり抽選になったりしたため、進学や大学の単位、就職に必要な人のためにも、ただいたずらに受けているだけのオレは枠を埋めるのも気がひけるのでTOEICの受験を控えていた。

TOEICも受けられないし何をしようか思案していたところ、1年ぐらい英語の音読はしても自分発信の英語を喋ってない事に気づき、10月ぐらいにオンライン英会話の大手レアジョブを始めた。

10月の時点で週に4-5回オンライン英会話をしてみた。25分のレッスンをすると英語をアウトプットした達成感があった。

さてこの達成感が曲者で、たった25分の勉強だけで満足してしまってオンライン英会話をした日はそれ以上英語を勉強しなくなってしまった。これはどう考えても勉強時間が少なすぎる。

毎日英語を25分でも会話するのは語学学習としてとても良いので続けたい。しかし他の英語の勉強量を多くしたい。これを満たすためにオンライン英会話だけて感じる大きすぎる達成感を下げたい。

そこでたくさんこなしてオンライン英会話に慣れしまえば1回あたりの達成感を下げられるだろうではないかと仮設を立てた。

つまりオンライン英会話を受けるのは特別なことではなく日常的な習慣にしてしまおうということだ。

この仮説のもと目標を以下の2つに決めた

  • オンライン英会話を1ヶ月毎日休むことなく受講して習慣化する
  • オンライン英会話の内容を復習する

次にこの目標を達成するためにやった工夫点をの述べていく

オンライン英会話を1ヶ月毎日休むことなく受講して習慣化する

1ヶ月やりきるための工夫

壁にカレンダーを貼って達成したらシールを張る

いくつか習慣化の書籍を読んで「デジタルなものよりリアルなものの方よい」というのをそのまま鵜呑みにして実施した。幼児向けのトイレトレーニングが台紙とシールがセットになったなったものが安かったのでこれを使った。もっとおしゃれなのもAmazonにあったが安いしまぁいいかと使っている。

レッスンが終わったらすぐ次のレッスンを予約する

「習慣化するには先に予定を入れてしまいましょう」と大体のオンライン英会話のサイトに書いてあるので素直にこちらもそのまま鵜呑みにして真似した。たしかに当日だとグダグダと何時にやろうかと。どんどん予約する時間が遅くなってしまい、良さそうな講師の予約が埋まってしまいがちである。

できるだけ早い時間にレッスンを受ける

レッスンでは話した英語を直してもらったり、理解できるように手加減して話してもらいたい。講師も人間なので疲れてくるとこれらのことの精度が下がってしまう。23時以降のレッスンだと明らかに講師が疲れていることが多く、どうしても元気たっぷりな場合よりも質が落ちてしまう。午前中の早い時間も夜よりマシだけど眠そうだ。

そこで仕事が終わったらすぐレッスンをするようにした。18:30-19:30時ぐらいが多かった。

定型型のレッスンを受ける

習慣化するためにできるだけ意思決定をしないようにオンライン英会話をしたい。 フリーカンバセーションやニュースを読んで話すものでもなく、レアジョブで言う実用英会話という定型のレッスンを一ずつすすめることにした。 こうすることで、どれを選択しようかと悩まななくて済む。

この実用英会話はレベルごとに章別れており、8レッスンのうち半分をやったなど、進捗している感じを得られるのが良いところだ。なぜかというと英語や英会話って成果が目に見えにくいので自分が成長を感じにくく、勉強していると賽の河原で石を積むような感覚になってしまうからだ。

クリスマス問題

このチャレンジは12月に行ったので大概のオンライン英会話はクリスマスはお休みである。また大概のオンライン英会話は無料で体験レッスンができる。そこでクリスマスもレッスンできるオンライン英会話を探して体験レッスンすることで12/25も無事にレッスンを行うことができた。クリスマス当日はボスニアに講師とボスニアコロナウイルスの状況について話をした。

復習するための工夫

基本戦略としてレッスンの内容を復習する。ライティングのトレーニングとして毎日英語の日記書く。この二点をいかに効率的かつ効果的になるよう工夫をした。 復習して振り返りをしないと特に英会話なので喋って内容などすごい勢いで記憶が遠のいて薄ぼんやりとしてしまう。

レッスン内容の復習にOtter.aiを使って文字起こし

Otter.aiと言う英語を録音・書き起こしサービスを利用して、オンライン英会話で喋った内容を書き起こしている。Otter.ai誰が喋ったかわかるようになっており、書き起こされた文字をクリックするとクリック箇所から音声が再生されるので必要な部分を繰り返し聞くことができる。

otter.ai

Otter.aiを利用して復習するポイント

1つ目は、講師が喋った英語がうまく理解できなかったところを聞き直す事ができる。再生速度を変えてゆっくり喋らせることもできるし何度も聞き直しができる。オンライン英会話の実際に何度も何度も聞きかえしてしまうとレッスン時間を浪費してしまう。

2つ目は、講師からの質問にうまく回答できていなかったところや、英語でコミュニケーションが上手く取れている気がしていたけど文字に起こすとひどくいい加減な英語を見つけることができる。発音が悪いところは文字起こしされた英語が無茶苦茶なのですぐわかる。

otter.aiで書き起こした例、通じているけど変な英語を使っているのがわかる。レッスン直後に復習するとこれでももともと何が言いたかったのか自分で把握できる。 f:id:kon_yu:20210217170031p:plain

これを次に述べるライティングの素材として利用する。

Otter.aiの利用料はついては年100ドルくらい。月々600分は無料で文字起こしができるので十分かもしれない。再生速度の変更は有料版だけの機能である。

ライティングは英語日記を機械と人力で添削

よく自分の言いたいことを日記にすると良いと言うが、普通に生活していて英語でわざわざ言いたいことなんてないあってもすぐにネタが尽きる。そこで先述したようにOtter.aiで書き起こした内容から自分がうまく言えなかったところを、英語日記にするようにした。

Idiyという英文添削サービスで、日本人の講師に日本語の解説付きで英語で書いた日記を直してもらうコースで50単語を利用した。価格は月5000円程度。この50ワードは多すぎず少なすぎないので続けることができた。

https://idiy.biz/

Idiyでは文法的には正しいと言うより英語として自然な表現を教えてもらいたいので、しょうもないスペルミスや単純な文法ミスを避けるために文章構成ツールのgrammarlyを使った。

Grammarlyも年額100ドルくらい。無料版でもスペルチェックと訂正をしてくれたはず。

app.grammarly.com

Idiyで直してもらった英文をOtter.aiに利用してしゃべることで発音練習をした。音声認識してテキストにするので、おかしな発音はうまく認識されない。 8-9割ほどきちんと認識されるまで練習した。いまのオレのレベルだと10割を目指すと終わらないし、頑張りすぎないのも継続のコツだ。

Idiyの日記コースはちょっとだけ不満点があり。日本語の解説付きで添削してくれるのはトレも良いが、英語で書いた日記添削の場合、日記判定機能とやらが日記ぽくないと判断すると日記が差し戻されてしまうのだ。そして1万円くらいのコースならこういう制限が無いよというコメントが付く。日記判定機能の根拠も良くわからないしサービスの設計として体験が悪いなあと思う。第一、画面遷移がめちゃめちゃ重い(javascriptのファイルがミニファイされてないとかオレの本業のソフトウェアエンジニア的には気になる)

日本人講師の修正の勘所もわかってきたので、外国人講師でもいいかなと言う気がしてきている。英語で解説してくれるのでいいので海外のサービスを探したい。

1ヶ月続けた成果

さて、結局1ヶ月間毎日オンライン英会話をやった結果どういう伸びがあったのか結果について、

レアジョブには月に一度スピーキングテストをAI判定する機能がついたの能力の伸びを把握できる。この判定する機能で1ヶ月前と1ヶ月後でランクが1つ上がっていたので成果があったと言えるだろう。 このようにスピーキングは伸びを感じにくいものなのでスピーキングテストがサービスに付いているレアジョブは競合サービスより1つ頭が抜けている。

またオンライン英会話の会話内容をOtter.aiで文字起こしたデータを確認すると、自分がしゃっている英語が英文として長くなっているし、発音が良くなったようで文字起こしされた文字が喋っている内容と合っている英語が増えている印象がある。

感覚的にも前よりはベラベラ喋れるようになった気がする。文字起こしをして復習しているからか、オンライン英会話中にも、これ伝わってるけどいい加減な英語を喋っているなぁと喋りながら気付けるようになった。

だからといって瞬時に修正できるわけではないが、いまこう言ったけどもっとナチュラルに言うには何言いますか?と質問したり、レッスン後に対象箇所をゆっくり英作文することができる用になった。

おまけ 古巣のオンライン英会話を使わなかった理由

オレがかつてCTOをしていたベストティーチャーと言うオンライン英会話は、英語を書いて、その英語を直してもらって、その直してもらった英語を使ってオンライン英会話をするという流れで、先に述べたレアジョブでオンライン英会話をして、Idiyで英語日記を書いて、直してもらうというやり方と似ている。

ベストティーチャーの方式もとてもいいが、オレが使いやすいようにゼロからオンライン英会話を作るならこんなのがいいなと言うのをいくつかのサービスやツールを使って検証してみた。

ベストティーチャーは英語を書くのがチャット形式であり、英文を書く、講師が返信を書く、その返信を元に英文を書く、という流れのため、自分が使いたい表現が使いたいフレーズや単語をチャットの流れによって使えないことがある(ベストティーチャーのチャットのハックとしてはBy the wayを使いまくって会話の流れをぶった切るということもできるけども)

その他, 最近やってる英語関係のこと

英語を勉強するのに飽きていて「英語を使って勉強する」にちょっとずつシフトしている

比較的やさしい英語で書かれた英字新聞Japan Times alphaを紙で購読している。 以前は電子版を購読していたが実物のほうが目につくので読みやすい。

alpha.japantimes.co.jp

10秒ニュースという数行の英語のニュースを聞いてディクテーションをして、得意のotter.aiを使って音読したして正しく文字起こしされるまで発音練習している。 communica.co.jp

Youtubeで興味がある子供向けの教育番組を見ている

子供の頃から歴史や科学の番組が好きだったので、このようなMITが作った子供向けのサイトMIT+K12 VideosTED-Ed それに Crash Course を見ている

例えば、お茶の歴史とか面白かった。東インド会社がお茶を中国から盗み出してインドのダージリンで育て始めたなんてことを知った。

www.ted.com

あとは出来るだけ知りたい情報は英語で調べたり、Twitterのトレンドをアメリカにして日本のゴシップから距離を置くようにしている

今後の課題

ビジネス英語を強化したい

去年から韓国やベトナムのソフトウェアエンジニアと仕事をすることがあり、お互いチャットや設計書(PlantUMLやGoogle Docs)を英語で行うとかなりスムーズに行えている。 なので最近はビジネス英語関係の本を買って必要に応じて使っている。「提案してきてくれたけど、これは却下したい」を敬意を払って言うには?というような困りごとをこれらの本で解決している。

幸い仕事で使うのでモチベーションても保ちやすいため、ビジネス英語を能力をあげようと、4月からのNHKのラジオ英会話のビジネス英語でもやるかなぁと考えている。

ライティングを強化したい ライティングを添削してくれるサービスもIdiy以外の海外のサービスを探していこうと思う

モチベーション維持するコミュニティに参加したい コーチングよりも英語を勉強している人がたくさんいるいい感じのコミュニティーがないかな、大学のESSサークルみたいのないかな?

こんなようなことを考えいる、こういうのあるよって言う情報があれば @kon_yuまで連絡下さい

レアジョブのスピーキングテストの内容がよく知らないけどIELTSのスピーキングテストっぽい気がするのでIELTSの勉強をしてもいいのかなぁっと思っている

おしゃれ用語が難しくて読めない!そんな悩みを解決 Goodbye OSHARE terms

f:id:kon_yu:20201130153602p:plain

(おしゃれ人間のイメージ図、平気でハットをかぶり丸めネガネかける)

毎度おなじみクソアプリアドベントカレンダー1日目の記事でございます(皆勤賞) qiita.com

みんなー!今年も役に立たないものを作ったよ!!

おしゃれ用語が難しすぎて読めない

ファッション系のウェブサイトを見ると用語がわからなすぎて全然頭に入らない。マテリアルなど無駄に横文字にする行為や、プチプラ、マストバイアイテムなど独特の和製英語も我々の認知能力に負荷を掛ける。

ファッションサイトの文章とは例えばこんな感じだ。

ファッションの流行はミリタリー&スポーツがアップトレンド。軍物モチーフのジャケットに蛍光フーディーを合わせた旬のミックススタイルの手元には、トーンの近いカーキの「オリジナル・キャンパー」がふさわしい。アクティブでタフな雰囲気に「オリジナル・キャンパー」が同調する。ミリタリーウオッチらしいドーム型のプラスティック風防やナイロンストラップによる軽快な着用感があって、シンプルな文字盤で時間が見やすいのもうれしい。ロープライスなのも僕らの味方だ。

出典: https://www.mensnonno.jp/post/48379/?cx_top=news&shm-vp=3

ちょっと何言ってるかわからない

いや、全然わからないわけじゃないんだけどね、日本語だしね。でもねただただ疲れるんだ。カタカナ語と固有名詞は判断付くようにしたい。

そこでChrome拡張機能を作ってファッション用語の辞書を作ってどんなサイトにも適用できるようにした。

今回作ったもの

先の文章を今回作ったChrome拡張を適用するとこんな感じになる

ファッションの流行は軍用の&スポーツがアップ流行。軍物主題のジャケットに蛍光パーカーを合わせた旬の異なる服装を混ぜた服装服装の手元には、色調の近いカーキの「オリジナル・キャンパー」がふさわしい。アクティブでタフな雰囲気に「オリジナル・キャンパー」が同調する。ミリタリーウオッチらしいドーム型のプラスティック風防やナイロン吊り革による軽快な着用感があって、シンプルな文字盤で時間が見やすいのもうれしい。低いです価格なのも僕らの味方だ。

これでだいぶ読みやすくなった。なったよね? なったに違いない!!

デモ

動かすとこんな感じでファッション用語がわかりやすい言葉に置き換わっていく。

www.youtube.com

Chrome拡張機能のストアに公開されたよ!!!

chrome.google.com

ソースコードはこちら github.com

このソースコード内のchrome_extensionディレクトリを chrome://extensions から読み込むと利用できる

さて、どうやって作ろうか?

さてファッション用語の辞書から対応する言葉を置き換えるとは言っても、辞書はどこにもない。 この辞書をどうやって作ったか、また文字列を置き換えた際にそれぞれのサイトのボタンなど押した際についている動作を失わないようにした工夫を記載していく。

ファッション用語辞書を作る

無い辞書をどうやって作るか、工程は3つ

ファッションサイトをスクレイピング

各サイトから記事のページのリンクをかき集めるにはWeb Scraper を使った。このツールで各サイトの一覧に表示されているリンクを取得してCSV保存した。このツールのおかげで自前でコードを書かずにベースとなるファッションサイトの記事を集められた。

男性向けファッションメディア2つ、女性向けファッションメディア2つの計4メディアから収集

総ページ数は 4792 + 2303 + 700 + 3000 = 10795 ページ

カタカナ語かき集めてそのカタカナ語の頻度を集計

Pythonスクレイピングと集計処理をした。大まかな処理は以下

  • 各サイトのHTMLを取得、正規表現で2文字以上のカタカナの文字列の抜き出してリストする
  • カタカナ語の合成語は一般的な日本語では無いので普通の形態素解析では分割できないので諦めた
  • カタカナ語のリストからpandas.DataFrameを使って頻度を集計
  • 頻度の高すぎる、低すぎるカタカナ語をフィルタリング
    • メディアのBodyから引き抜いているのでサイドバーに表示されているカテゴリー名も集計してしまい頻度が高くなる、またはジーンズみたいな簡単な用語なので除外するため多すぎる用語を除外
    • 少なすぎる用語は単純に少ないので除外
    • フィルタリングのしきい値については完全に感で行っている。なんとなく眺めていてオレがわかりにくいと思ったレイヤードの1344回とダッドの20回を上限値と下限値にした。
    • この絞り込みで3905個に絞り込んだ

集計結果を元に翻訳して辞書を作成

さて3905個もある用語を一個一個翻訳してられないので、こちらもGoogle Spread sheetを使って自動化した。

日本語から英語、英語から日本語に再翻訳してカタカナ語をわかりやすい言葉に

例えばトラウザーというおしゃれ用語がある。これを、日英、英日に翻訳すると

トラウザーを日本語から英語に翻訳するとTrousers

Trousersを英語から日本語に翻訳するとズボン

このようにトラウザーをズボンとわかりやすい言葉に置き換えることができるのだ

Google Spread sheetにはGOOGLETRANSLATEという関数があって下記のように各セルを翻訳することができるのだ

=GOOGLETRANSLATE(A5,"ja","en") =GOOGLETRANSLATE(B5,"en","ja") また、もとの値と再翻訳した値が同じかどうか判断するのにEXACT関数を使って判別するようにした =EXACT(A5, C5)

この処理によってこのようなおしゃれ用語をわかり易い言葉に置き換えられた

デコルテ → (日英翻訳)→ Neckline → (英日翻訳) → ネックライン

フーディ → (日英翻訳)→ Hoody → (英日翻訳) → パーカー

とは言っても再翻訳して変な翻訳になっているものと再翻訳しても同じだったものはどう処理するか?

再翻訳大作戦で全部いい感じにおしゃれ用語をわかり易い言葉に置き換えられるかと思ったが、そうは問屋が卸さない。どうしたかというと根性で自力で一個一個訳した。これは結構へこたれそうになった。いやへこたれた。数字でいうと436個でへこたれたようだ。

再翻訳してもうまく行かないパターン3つ

手動翻訳を強いられたのは3つのパターンだった。

●再翻訳してもおしゃれ用語から脱せない

ボトムス → (日英翻訳)→ Bottoms → (英日翻訳) → ボトムス → (手動翻訳)→ズボン

再翻訳してもボトムスに戻ってしまう。

●再翻訳したら違う意味になる

ストール → (日英翻訳)→ Stall → (英日翻訳) → 失速 → (手動翻訳)→ 幅の広いマフラー

ストールは恐らくstoleのことで女性用の肩掛けのことを指すようだが、英語翻訳時にカタカナ語からだと誤訳してしまった。

●英日翻訳で和製英語が正しくなり再翻訳時に意味が変わる

マフラー → (日英翻訳)→ Scarf → (英日翻訳) → スカーフ → (手動翻訳)→ マフラー

マフラーはスカーフが正しい英語だけど日本語的には変

翻訳に使う辞書を抽出

自力で翻訳したものがある場合はそれを優先し、ない場合は再翻訳した値が元の用語と違うものを表示する、再翻訳した結果が元の用語と同じ場合はerrorの文字を出すようにややトリッキーな関数をセルに仕込んだ

=if(ISTEXT(F3),F3,if(E3 = FALSE,D3,"error"))

Pythonで集計した結果を処理したスプレッドシートのリンクはこちら

https://docs.google.com/spreadsheets/d/1t_qe7LOSiq_i4scZFeBmqHVKzRdFvkxdw79yhUNQHBQ/edit?usp=sharing

スプレッドシートの中身を一部切り出すとこのように、頻度と日英、英日の翻訳と自力で翻訳した単語があるのがわかるだろう

f:id:kon_yu:20201130210037p:plain
集計処理結果

データ分析してわかったこと

日英再翻訳作戦が有効ではないものはこういうのが弱い

● 定着していない和製英語

リアルクローズ → (日英翻訳) → Real close ❌(clothにならない) → (英日翻訳) → リアルタイムに近いです ❌

たぶん普段着のことを言いたいのかな?アメリカのサイトでreal clothes で調べても汎用的な言葉としては出てこなかった

●記事に文字数制限されてるのかな?カタカナ語の略

ノースリ → (日英翻訳) → Nosuri ❌→ (英日翻訳) → Nosuri ❌

イメトレ → (日英翻訳) → Imetore ❌→ (英日翻訳) → Imetore ❌

●読者は漢字を読めないと思ってるのか?装飾用なのかな?無駄にカタカナにした単語

イッキ → (日英翻訳) → Ikki → (英日翻訳) → 一騎当千

アンバイ → (日英翻訳) → Systematics 🔺(体系的)→ (英日翻訳) → 按排 🔺(体系的の英語がどういうわけか塩梅の難しい方の漢字へ)

一方こちらは無駄なカタカナ化をいい感じに訳してくれている

イマドキ → (日英翻訳) → Nowadays⭕ → (英日翻訳) → 最近⭕

ポップさを演出したいのか日本語をカタカナにする傾向があるが、読み手の認知負荷を上げるだけで無駄だと思う。どこでライティングの勉強をしたのだろうか?心配になってきた。 アンバイなんて無理に使わずにもっと平易な言葉を使うのもライターの仕事じゃないだろうか?

スタイル、コーデ、テイスト問題

スタイルとコーデとテイストはほぼ同義で使われる。統一してもらいたい。 時にシルエットも同義で使われるが、本来の輪郭の意味のシルエットの場合もあるので注意が必要だ。

まとめ

ファッションサイトを読みづらいので、ファッション用語の辞書をファッションサイトをスクレイピングした結果から頻度を抽出して作成した。

当初はカタカナ語を日本語から英語へ翻訳し、英語から日本語へ再翻訳すれば解決するかと思いきや、敵は我々の想像よりも遥かに手強く、謎の和製英語や無駄に変に略したりと手動での翻訳を強いてきた。400以上の単語を手動で翻訳するもこちらが白旗を上げてしまう事態となった。ファッション業界の奥は深い。

chrome拡張でもHTMLの内容を更新してもボタンやタブのイベントがちゃんと動くようにする工夫をしている。これはまた別の記事にまとめる予定だ。普段ReactやVueだと気にしないけど、外部から既存のDOMをハックするには普段フレームワークを利用して使わないようなJSを使うことになりなかなか勉強になった。

今後の展開

大抵のサイトはそんなことはないが、本当に日本語が破綻している文をちらほら見かけることもあった。しかし画像が豊富だと文章はほぼ読まないから気にならないので問題がないのかもしれない。文字は飾りのようなものなのだろうか?ならば文字は全部Macのフォントに選択時に出てくる宮沢賢治ポラーノの広場でも違和感がないのかもしれない。新しい仮説が生まれてしまった。追って検証していく。

あのイーハトーヴォのすきとおった風、夏でも底に冷たさをもつ青いそら、うつくしい森で飾られたモリーオ市、郊外のぎらぎらひかる草の波。

ロケットビーバーイ!

Rubyで乱数のシードを設定しないでランダムな数値を取得できるかRubyのコードを調査した

概要

Rubyはなぜ乱数のシードを入れないで乱数を生成できるのかRubyの言語自体のソースコードを軽く読んで調査した。

乱数を生成するクラスは乱数のシードを引数に入れないと、乱数のシードを生成するメソッドを呼び出す、その乱数を生成するロジック内ではCPUクロック数やOSの乱数生成を呼び出して利用して乱数のシードを生成していた。

乱数のシードがUNIX timeだけに依存したりしていないので、高アクセスなサービスや並列化したバッチ処理などで同時刻に乱数を生成してもシード値が同じでバラけないってことはなさそうだ。

はじめに

Rubyのランダムな数値を生成する場合はこのような書き方をする。

r=Random.new
r.rand(10)
=> 5
引数に整数を入れると0-9までのランダムな数値(Integer)を出力する

引数を少数を入れるとFloat型でランダムな数値を出力する
r.rand(5.5)
=> 1.81617029321715

randメソッドの仕様

Method: Random.rand — Documentation for core (2.7.0)

Rubyで乱数を生成するは、擬似乱数列生成器にメルセンヌ・ツイスタを利用している。擬似乱数発生装置は乱数のシードと呼ばれる初期値が必要だ。(シードが不要な擬似乱数発生装置もあるかもしれないが今回の主題ではない)

コンパイラを利用するプログラミング言語では、乱数を生成する際には乱数のシードを用意するものが多くインタプリタの場合はRubyのように乱数のシードを使わなくても乱数を生成することができるものが多い印象がある。

Ruby以外の他の言語の乱数生成ロジック

乱数のシードが必要な例としてGo言語の場合

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().Unix())
    number:= rand.Int()
    fmt.Println(number)
}

乱数のシードが不要な例としてJavaScriptの場合

const number = Math.random()
console.log(number)

このJSのMath.random()の定義を見るとJavaScriptでは乱数のシードを入れられない逆にシードを入れさせないっていう仕様も珍しい

The implementation selects the initial seed to the random number generation algorithm; it cannot be chosen or reset by the user.

乱数のシードが不要な言語の場合、乱数のシードはどうしているのかRubyを例に調べてみる

Randomのコンストラクタの仕様を見ると Method: Random#initialize — Documentation for core (2.7.0)

Random.new_seedでランダムなシードの値を生成してインスタンス化しているしている。

Random.new_seedを実行してみるとこのような結果が返ってくる

Random.new_seed
=> 115032730400174366788466674494640623225

乱数のシード生成するのに乱数を生成している? ではその乱数を生成するシードはどう生成するというのだろうか?おそらく擬似乱数発生装置を利用しない方法で乱数を生成しているはずだ。

この対象のRubyのコード、Ruby本体のコードなのでC言語のコードで、Random.new_seedのコードは以下のように作られている。これを1行ずつ読み解く

Method: Random.new_seed — Documentation for core (2.7.0)

static VALUE
random_seed(VALUE _)
{
    VALUE v;
    uint32_t buf[DEFAULT_SEED_CNT+1];
    fill_random_seed(buf, DEFAULT_SEED_CNT);
    v = make_seed_value(buf, DEFAULT_SEED_CNT);
    explicit_bzero(buf, DEFAULT_SEED_LEN);
    return v;
}

1行目のVALUE v;は6行目return vで関数の返り値としているので生成されてたランダムな数値が格納される変数。

2行目uint32_t buf[DEFAULT_SEED_CNT+1];はDEFAULT_SEED_CNTより1大きいサイズの配列を宣言、固定値DEFAULT_SEED_CNTのサイズは4

3行目fill_random_seed(buf, DEFAULT_SEED_CNT); fill_random_seedで乱数のシードを作るための乱数のシードを作ってる。

このメソッドのロジックをざっくり読むと配列のサイズ4のseedの各要素にfill_random_bytesでランダムなバイト数値を入れ、その上にCPU時間からナノ秒やマイクロ秒を取得してXORで上書きしている用に見える。 fill_random_bytesではちらっと見る限りLinuxシステムコールで乱数を読んでるように見えが、今回は深追いしていない

static void
fill_random_seed(uint32_t *seed, size_t cnt)
{
    static int n = 0;
#if defined HAVE_CLOCK_GETTIME
    struct timespec tv;
#elif defined HAVE_GETTIMEOFDAY
    struct timeval tv;
#endif
    size_t len = cnt * sizeof(*seed);

    memset(seed, 0, len);

    fill_random_bytes(seed, len, FALSE);

#if defined HAVE_CLOCK_GETTIME
    clock_gettime(CLOCK_REALTIME, &tv);
    seed[0] ^= tv.tv_nsec;
#elif defined HAVE_GETTIMEOFDAY
    gettimeofday(&tv, 0);
    seed[0] ^= tv.tv_usec;
#endif
    seed[1] ^= (uint32_t)tv.tv_sec;
#if SIZEOF_TIME_T > SIZEOF_INT
    seed[0] ^= (uint32_t)((time_t)tv.tv_sec >> SIZEOF_INT * CHAR_BIT);
#endif
    seed[2] ^= getpid() ^ (n++ << 16);
    seed[3] ^= (uint32_t)(VALUE)&seed;
#if SIZEOF_VOIDP > SIZEOF_INT
    seed[2] ^= (uint32_t)((VALUE)&seed >> SIZEOF_INT * CHAR_BIT);
#endif
}

4行目v = make_seed_value(buf, DEFAULT_SEED_CNT);でbufに格納されている乱数のシードから乱数を生成している make_seed_valueではbufに入っている配列の数値をrb_integer_unpackで結合して乱数を生成していると思う。

static VALUE
make_seed_value(uint32_t *ptr, size_t len)
{
    VALUE seed;

    if (ptr[len-1] <= 1) {
        /* set leading-zero-guard */
        ptr[len++] = 1;
    }

    seed = rb_integer_unpack(ptr, len, sizeof(uint32_t), 0,
        INTEGER_PACK_LSWORD_FIRST|INTEGER_PACK_NATIVE_BYTE_ORDER);

    return seed;
}

上記のロジックをまとめると、random_seedのロジックは配列を用意して、その配列にCPU時間など使ってランダムな数値を格納したあとに、配列を結合して乱数のシードを作っていると言える

まとめ

  • Rubyの乱数の生成でシードデータが必要か否かを考えると、Rubyのコードを読む限り不要だと言える。必要なパターンがある気がするが今のところ思いつかない
  • 普段何気なく使っている乱数がどうやって実装されているのか垣間見ることができた
  • C言語読むの5年ぶりぐらいな気がする