🐿️

GoogleのAIファイル判定ツールMagikaを試した感想(js版)

2024/02/20に公開
2

Magikaとは

MagikaはGoogleの深層学習ベースのファイルタイプ検出ライブラリで、ベースはPythonです。現在非公式かつ実験的なプロジェクトとなっていますが、2024年2月にオープンソースとして公開されました。高速な動作と高精度な検出がウリとのことです。GMailやGoogle Driveなどのファイルチェックには利用されているそう。

Python製ツールなのですが、公式のデモを見れば分かる通り、Javascript版が実験的に提供されています。
ここではPythonではなく、Javascript版ライブラリを利用してみたことについて記載します。

まず、このツールの基本的に想定される要素としては、Webバックエンドなどでの外部データ入力の判定等と言えます。
この分野でのjavascriptライブラリとしては、古典的かつ普及しているものとしてfile-typeのような読み込んだチャンクに対して内部的にハードコードして条件分岐し、対応するライブラリがあります。
このような既存ツールに対して、サイバー脅威の観点からより高速で高い精度を追求するために、深層学習を活用するアプローチで登場したツールと言えます。Google公式の説明はオープンソースブログにある通り。

サポートされているファイル種別は公式のリストの通りで、画像などのマルチメディアデータ、データ構造テキスト、プログラミング言語、その他バイナリデータなどのメジャーなものは大体揃っている感があります。

javascriptライブラリについて

単純にnpmからインストールすれば利用できるようになります。
現在Javascript版はピュアなjsで記述されており、Typescriptサポートはされていません(ただし、実行は可能です)。(2024/03/02追記: DefinitelyTypedで型定義が追加されました!下部のglassonion1さんのコメント参照。
利用法としてはシンプルで、Magikaクラスを生成し、identifyBytesないしidentifyBytesFullメソッドを利用することで判定します。引数のデータにはUint8Array(またはそれを継承した型、いわゆるNode.jsではBuffer)を与えます。

前提として内部的にはデフォルトでは設定用のjsonが存在し、それを使うのですが、通常GitHubにホストされているものを使う為、インターネットに接続されている必要があります。

以下にNode.jsでのスクリプト例を示します。

import { readFile } from "fs/promises";
import { Magika } from "magika";

const data = await readFile("./public/vite.svg");
const magika = new Magika();
// v0.2.5では引数に{}を与える必要あり(最新コミットでは修正済)
await magika.load({});

// 上位可能性の高い1位を読み込む
const prediction1 = await magika.identifyBytes(data);
// 全てのスコアを読み込む
const prediction2 = await magika.identifyBytesFull(data);
console.log(prediction1);
console.log(prediction2);

出力結果は、labelscoreを持つオブジェクトです。identifyBytesFullを叩くと全てのスコアを持つlabelsが付与されます。

出力結果例
// magika.identifyBytes
{ label: 'svg', score: 0.9999988079071045 }
// magika.identifyBytesFull
{
  label: 'svg',
  score: 0.9999988079071045,
  labels: {
    ai: 2.8179203219978743e-13,
    apk: 1.6923782254069097e-12,
    appleplist: 4.184471847257405e-14,
    asm: 4.1449833149131976e-11,
    asp: 1.100027180456209e-9,
    batch: 3.155384009634332e-10,
    bmp: 8.318443230210506e-16,
    bzip: 5.114069500451908e-14,
    c: 2.1869633881621553e-10,
    cab: 1.5013684839476616e-18,
    cat: 1.153533453148926e-28,
    chm: 9.924413630802492e-21,
    coff: 8.728140043665848e-15,
    crx: 2.8311055587273547e-15,
    cs: 5.476230146433947e-12,
    css: 8.734295064982689e-9,
    csv: 4.678442529215676e-12,
    deb: 1.0598967109565525e-18,
    dex: 6.380092292559937e-15,
    dmg: 4.2725979677119764e-18,
    doc: 1.0902307945315215e-9,
    docx: 3.2749764705686246e-11,
    elf: 4.296141282891623e-14,
    emf: 1.7004518876561877e-15,
    eml: 6.137205760953224e-12,
    epub: 1.0640354699761878e-13,
    flac: 4.077345706431591e-18,
    gif: 8.1882555897065e-17,
    go: 2.772054721184025e-12,
    gzip: 1.0130648515641809e-15,
    hlp: 8.174398660085012e-17,
    html: 7.122262104530819e-7,
    ico: 6.715368261873923e-15,
    ini: 1.5783151452453126e-9,
    internetshortcut: 3.4819694350041175e-10,
    iso: 3.722099939460627e-15,
    jar: 3.576700025563484e-13,
    java: 9.419185049985845e-12,
    javabytecode: 4.561570619575233e-18,
    javascript: 2.4295678713315283e-7,
    jpeg: 1.2236758481498633e-11,
    json: 4.856270496844672e-9,
    latex: 7.132008178478699e-11,
    lisp: 3.3319040479125664e-14,
    lnk: 1.011486186942324e-21,
    m3u: 2.366740921218866e-15,
    macho: 1.2256974677837124e-13,
    makefile: 2.837284747425617e-11,
    markdown: 4.892846572346343e-9,
    mht: 1.686096227915268e-11,
    mp3: 2.4465791128051514e-14,
    mp4: 2.3446861288011647e-17,
    mscompress: 4.040820636585638e-17,
    msi: 7.098658611440374e-16,
    mum: 2.1409225949379406e-30,
    odex: 4.1554216135456336e-16,
    odp: 1.1931813256001243e-15,
    ods: 3.185492370100587e-14,
    odt: 5.272969018886575e-13,
    ogg: 1.3001844898151428e-17,
    outlook: 1.1060811988539143e-11,
    pcap: 5.9578126160759534e-18,
    pdf: 2.3794907016982947e-12,
    pebin: 3.9266614455411505e-14,
    pem: 7.255403417817663e-13,
    perl: 3.252220159843944e-10,
    php: 3.243859936219451e-8,
    png: 1.3288663985289903e-17,
    postscript: 3.659021436536181e-12,
    powershell: 3.3691311962513737e-9,
    ppt: 6.292120145225122e-13,
    pptx: 2.8008417807257047e-10,
    python: 4.662537023136792e-10,
    pythonbytecode: 6.332325563565425e-15,
    rar: 3.269835696999258e-17,
    rdf: 9.584825337929925e-13,
    rpm: 3.678912244109192e-18,
    rst: 7.359279852181544e-9,
    rtf: 6.549275344269745e-9,
    ruby: 7.389907435984e-11,
    rust: 2.1347465462007165e-10,
    scala: 2.0486050193464084e-13,
    sevenzip: 1.2197859422591122e-16,
    shell: 6.810844332960642e-9,
    smali: 9.075907885479351e-13,
    sql: 7.623151415669938e-11,
    squashfs: 4.673186649120382e-17,
    svg: 0.9999988079071045,
    swf: 1.8842895097783173e-16,
    symlinktext: 1.3225409270489497e-23,
    tar: 1.2495010451654687e-12,
    tga: 8.136522597172735e-13,
    tiff: 7.494467280786632e-18,
    torrent: 3.115735522300875e-16,
    ttf: 5.610075399135705e-19,
    txt: 0,
    unknown: 0,
    vba: 4.862739988453768e-9,
    wav: 8.903741673274248e-17,
    webm: 2.2022327233019775e-18,
    webp: 1.5043889856531872e-19,
    winregistry: 3.9666388229608884e-11,
    wmf: 2.2752096988687027e-18,
    xar: 2.3995827146157977e-18,
    xls: 2.214396548072539e-11,
    xlsb: 1.5593921557576992e-16,
    xlsx: 6.189261603228857e-11,
    xml: 5.09618525157407e-9,
    xpi: 8.944573055315319e-14,
    xz: 7.308389527834255e-17,
    yaml: 5.805607383724265e-11,
    zip: 7.808399482556183e-10,
    zlibstream: 3.9286089436934776e-14,
    empty: 0
  }
}

ブラウザでの利用

このライブラリをjavascriptで利用することを考えた場合、良い点はデモページで分かる通り、クライアントで容易に動作可能なところだと感じています。同様のものではguesslang.jsがありますが、元のライブラリの開発自体が停滞していることもあり、非常に組み込みにくいです。
公式の説明の模倣になりますが、以下に手早く書いたReactアプリでドロップしたファイルを判定する簡単な例を示します。

//@ts-expect-error - magika has no type definitions
import { Magika } from "magika";
import { useCallback } from "react";
import { useDropzone } from "react-dropzone";

function App() {
  const onDrop = useCallback(async ([file]: File[]) => {
    const fileBytes = new Uint8Array(await file.arrayBuffer());
    const magika = new Magika();
  // v0.2.5では引数に{}を与える必要あり(最新コミットでは修正済)
    await magika.load({});
    const prediction = await magika.identifyBytesFull(fileBytes);

    console.log(prediction);
  }, []);

  const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
  return (
    <div
      style={{
        width: "500px",
        height: "300px",
        border: "2px dashed black",
        backgroundColor: "lightgray",
      }}
      {...getRootProps()}
    >
      <input {...getInputProps()} />
      {isDragActive ? <p>Dropping...</p> : <p>Drop Here</p>}
    </div>
  );
}

おわりに

現状はライブラリとしては相当実験的な感が強いですが、本来のPythonだとCLIツールとして利用し、jsonで吐くなど色々な使い方が出来ます。

個人的にこのライブラリが良いと思った点は、単にバイナリデータだけでなく、テキストデータ(いわゆるプログラミング言語)も主要なものは高精度で判定可能な点だと感じます。また、既存のツールではファイル名などの拡張子が文面よりも優先的に判定されしまい、基本的な拡張子のすり替えだけでも誤検出に繋がってしまった、なんてことが多かった印象があります。Magikaは素のデータのみを判定するので、高度な誤検出例は充分あり得ますが、そのようなことはありません。

これにより、選択肢が広がった所がポイントと言えると思います。もちろん、例えば単にプログラミング言語判定のみの目的ならGuessLangベースのvscode-languagedetectionなどの方がサポート範囲が広く、取り回しが効きやすいというのはある(例えばTypescriptかJavascriptかのような判定はしません)のですが、外部入力に対して「ブラウザ・クライアント双方」で高精度に様々なデータを判定することができるものとして、取り込みやすいものが登場した、という点では、良いポイントだと思います。

他言語のバインディングが出来る可能性もあるらしいので、今後の発展に期待ですね。

Discussion

glassonion1glassonion1

型定義が追加されたのでTypeScriptでもエラーなしで使えるようになってます。

npm install --save-dev @types/magika
shimarisu_121shimarisu_121

glassonion1さん、コメントありがとうございます!
若干記事追記しました。素晴らしい貢献!👍