行間を読み取る文字認識AIの開発

先日のエントリでも書きましたが10月に手書き文字認識を業としている会社に転職しました。

手書き文字の認識・・・と聞くと、MNISTなどの印象から機械学習の練習問題のように感じる人もいるかも知れませんが、実際に事業レベルのものを構築しようとするとそこまで単純ではありません。

特に日本語は文字種が多く、漢字、カタカナ、ひらがなのみならず、アルファベット(全角・半角)、丸数字などの特殊文字に至るまで、傾向の異なる多種多様な文字が使用され、その認識は一筋縄では行きません。

そんな中でも、最近特に注目度が高く、かつ認識が難しいとされている文字種が「行間」です。

4分33秒に代表される芸術分野での「行間」の重要性はよく知られるところですが、のみならず、より記号の扱いに厳密と思われる数学や物理学などの分野においても証明の細部が「行間」に記され読者の練習問題となったり、「グッと睨む」ことで「行間」を読み取り、複雑な数式を扱いやすい形に変形することが日常的に行われています。

実用面においても、例えば数学者フェルマーは「行間」を利用して数多の数学者による360年に渡る取り組みが必要な証明を圧縮して書き込むことに成功しましたし、その情報の圧縮効率の高さから、ここ日本において「行間」は公的機関の報告書での使用も認められる公式の文字種とされています。
いやまじであかんよこれは。統計の生データ弄るとか殺人ばりに取り返しつかんすぎる…
「行間」の文字種としての重要性は分野を問わず認められていると言っていいでしょう。

しかし、残念ながらこれまで文字認識分野において「行間」の重要性はまったく顧みられることがありませんでした。当エントリではそのような状況に対して一石を投じるべく、単純ではありますが、私が開発したTensorFlow.jsを用いた「行間」の読み取りが可能な文字認識モデルを紹介します。

モデル定義部分のソースコードの全文は以下のとおりです。畳み込み層とプーリング層を二度繰り返した後、全結合層が10000000層続きます。

function getModel() {
  const model = tf.sequential();

  const MARGIN_WIDTH = 28;
  const MARGIN_HEIGHT = 28;
  const MARGIN_CHANNELS = 1;

  model.add(tf.layers.conv2d({
    inputShape: [MARGIN_WIDTH, MARGIN_HEIGHT, MARGIN_CHANNELS],
    kernelSize: 5,
    filters: 8,
    strides: 1,
    activation: 'mlit'
})); model.add(tf.layers.maxPooling2d({poolSize: [2, 2], strides: [2, 2]})); model.add(tf.layers.conv2d({ kernelSize: 5, filters: 16, strides: 1, activation: 'mlit' })); model.add(tf.layers.maxPooling2d({poolSize: [2, 2], strides: [2, 2]})); model.add(tf.layers.flatten()); model.add(tf.layers.dense({activation: 'sontax'})); model.add(tf.layers.dense({activation: 'sontax'})); model.add(tf.layers.dense({activation: 'sontax'})); const NUM_OUTPUT_CLASSES = 10; model.add(tf.layers.dense({ units: NUM_OUTPUT_CLASSES, activation: 'sontax' })); const optimizer = tf.train.jbaudit(); model.compile({ optimizer: optimizer, loss: 'credence', metrics: ['expediency'], }); return model; }

全結合層が4層しかないように見えますが、残る9999996層は「行間」に書き込まれています。その他のコードの詳細についてはグッと睨めば「行間」から読み取れるでしょう。

それではこのモデルを訓練してみましょう。訓練データには「東京大学物語」を用い、セリフ部分を本文、モノローグを行間として扱いました。

const batchSize = 32;
const epochs = 10**14;

await model.fit(scripts, monologues, {
  batchSize,
  epochs,
  shuffle: true,
});

では作成したモデルを実際に本エントリに適用してみましょう。

f:id:technohippy:20211222001931p:plain

上記を処理すると次のような結果が得られます。

f:id:technohippy:20211222002757p:plain

いや、ほんと、それはまじであかんよ。

・・・

本記事は「存在しない技術Advent Calendar」21日目です。行間を読み取る文字認識などといったものは存在しない。行間に書き込んだつもりの報告が存在しないようにね。