[Medical AI with Python: P41.1] 系列データの基礎とPyTorchでの準備

系列データの基礎とPyTorchでの準備

医療現場には、心電図や電子カルテなど「順序」が重要な系列データが豊富に存在します。AIで分析するには、これらのデータをPyTorchが理解できる「テンソル」形式に整え、意味を付与する準備が不可欠です。

系列データとは?
「順序」が鍵を握る情報

時系列データ(心電図、バイタルサイン)、テキスト(電子カルテ)、音声、動画など、データの並び順自体が重要な意味を持つデータです。医療現場にはこれらのデータが豊富に存在します。

PyTorchでの準備
テンソル化とデータ供給

データをAIが扱える多次元配列「テンソル」に変換します。長さが異なるデータはパディングで揃え、DataLoaderで効率的にミニバッチとしてモデルに供給します。

埋め込み層 (Embedding)
言葉に「意味」を与える魔法

テキストデータを扱う際、単語IDを単なる記号から、意味的な特徴を捉えた密なベクトル(分散表現)に変換します。これにより、AIは言葉のニュアンスや文脈をより深く理解できます。

系列データの基礎とPyTorchでの準備 1. 系列データとは? – 順序が命のデータたち 時系列データ 心電図, バイタルサイン テキストデータ 電子カルテ, 医学論文 音声データ 心音・呼吸音, 会話 動画データ 内視鏡, 歩行分析 2. 系列データ分析の主要タスク 予測 (Forecasting) 過去から未来を予測 例: 容態急変予測 分類 (Classification) 系列全体のカテゴリを判定 例: 不整脈の種類の判別 生成 (Generation) 新しい系列を創作 例: 診断レポートの補助 3. PyTorchでの準備:AIへのデータ供給パイプライン 1. 元データ (可変長) [70, 72, 75] [65, 66, 64, 68] [80, 82] 2. パディング [70, 72, 75, 0] [65, 66, 64, 68] [80, 82, 0, 0] 3. テンソル化 (Batch, SeqLen, Feat) 4. DataLoader (ミニバッチ供給) Dataset Batch 1 Batch 2 4. 言葉に意味を:埋め込み層 (nn.Embedding) Before: One-hot表現 IDを長く疎なベクトルに変換 ID:2 → [0, 0, 1, 0, … , 0] (語彙サイズが100万なら100万次元) 意味の近さが不明 医師 看護師 リンゴ nn.Embedding After: 埋め込み表現 IDを短く密な意味ベクトルに変換 ID:2 → [0.2, -0.9, 0.4, …] (例: 300次元) 意味の近い単語は空間的に近い 医師 看護師 リンゴ
この章の学習目標:系列データとPyTorchの基礎を固める
✔ 系列データ(時系列、テキスト等)の特性と、AIで何ができるか(予測、分類、生成)を理解する。
✔ PyTorchのテンソルを使い、可変長の系列データをパディング等の手法でコンピュータが扱える形に整形する方法を学ぶ。
✔ テキストデータに「意味」を与える埋め込み層(nn.Embedding)の役割と使い方を理解する。
学習の前提知識
💡 AIや機械学習への基本的な興味・関心。
専門的な事前知識は不要です。「AIで何ができるか」に関心があると、より楽しく学べます。
💡 Pythonプログラミングの初歩的な知識。
変数、リスト、forループなどの基本的な文法が分かっていると、コードの理解がスムーズです。
💡 新しいことを学ぶ意欲。
専門用語も出てきますが、一つ一つ丁寧に解説しますのでご安心ください!
目次

はじめに

最近、ニュースなどでもよく耳にするAI(人工知能)ですが、実は医療の現場でも、その進化には目を見張るものがありますよね。例えば、診断の精度を上げるお手伝いをしたり、新しい治療法を見つけ出すヒントになったり、あるいはこれまで時間のかかっていたお薬の開発に役立ったりと、本当に幅広い分野での活躍が期待されているんです (1, 2)。

この「Medical AI with Python」という一連の記事でお届けするのは、そうした医療AIの世界を、Pythonというプログラミング言語を使いながら一緒に探求していく、そんな試みです。特に今回はシリーズの中の「第15回:PyTorchで系列データ分析超入門」というテーマに焦点を当てています。この回を通じて、医師や医学研究者をはじめとする医療に携わる皆さんが、AIの中でも特に強力なアプローチである深層学習(ディープラーニングという言葉もよく聞かれるかもしれませんね)の基本的な考え方をしっかりと掴み、そして何より、ご自身で実際にコードを書きながら、日々の研究や臨床で直面する課題の解決に役立てられるようなスキルを、共に磨いていけたら素晴らしいな、と思っています。

さて、この「第15回」の中で私たちが特に注目していくのが、「系列データ」と呼ばれる種類のデータです。実は、医療の現場では、この系列データに日々触れていると言っても過言ではないほど、非常に身近な存在なんですよ。

そこで、今回の導入となる「第1章:系列データの基礎とPyTorchでの準備」では、まず「系列データって、一体どういうものなんだろう?」という、素朴だけれども大切な疑問からスタートします。それから、系列データにはどんな種類があって、AIの力を借りることで、これらのデータからどんな面白いこと――つまり、医学的な発見や有用な情報ですね――を引き出せる可能性があるのか、といった点を一緒に概観していきましょう。

そして、実際に手を動かしながら分析を進めていく上での頼もしい相棒として、PyTorchという非常に柔軟で使いやすいライブラリが登場します。このPyTorchという道具を使って、系列データをコンピュータの中でどのように表現し、どう扱っていけば良いのか、いわばその準備運動をこの章でしっかりと行うわけです。

この章を読み終える頃には、系列データというものに対する皆さんの理解がぐっと深まり、これから先の回で学ぶ、より進んだAIモデルに取り組むための、しっかりとした土台ができあがっているはずです。新しいことを学ぶのはワクワクしますよね。一緒に一歩ずつ進んでいきましょう!

1.1 系列データとは(時系列、テキスト、音声、動画など)とその特性

このセクションでは、医療AIで非常に重要な「系列データ」について解説します。

系列データとは、その名の通り「順序」に意味があるデータのこと。例えば、天気予報で過去の天気が重要なように、データの並び順自体が情報となるのです。このような系列データ (Sequential Data) は、データの一つ一つが特定の順番で並び、その順序が重要な情報を含んでいます。医療現場には、この系列データが豊富に存在します。具体例を見ていきましょう。

時系列データ (Time Series Data)

まず、「時系列データ」から見ていきましょう。

定義

時系列データとは、時間の経過と共に観測されるデータ点のことです。時間の流れに沿った記録と考えると良いでしょう。

医療における例

医療現場では多くの時系列データが活用されています。代表例は以下の通りです。

  • 心電図 (ECG/EKG): 心臓の電気活動を時間的に記録したもので、波形パターンから不整脈などを診断します (3)。
  • 脳波 (EEG): 脳の電気的活動を記録したもので、てんかんの診断や睡眠状態の解析などに用いられます (4)。
  • 患者さんのバイタルサイン: 体温、血圧、心拍数、呼吸数、SpO2などの経時的変化は、患者の状態監視や急変予測に不可欠です (5)。
  • 血糖値モニタリング: 糖尿病患者の血糖値の持続的記録データは、インスリン投与量の調整や合併症予防に役立ちます。

特性

時系列データの主な特徴は以下の通りです。

  • 時間依存性: 現在の値が過去の値に影響を受ける性質です。例えば、今日の天気は昨日の天気に左右されます。
  • トレンド: データ全体が長期的に上昇または下降する傾向です。治療効果による検査値の改善などが例です。
  • 季節性・周期性: 特定期間で同様のパターンが繰り返されることです。感染症の年間流行パターンや、短期的な周期的変動(睡眠中の脳波など)が該当します。

これらの特徴を捉えることが、時系列データ分析の鍵となります。

テキストデータ (Text Data)

次に、日常的に最も触れる機会の多い「テキストデータ」です。この文章もテキストデータの一例です。

定義

テキストデータとは、単語や文字が特定の順序で並んで構成されるデータです。言葉が文法ルールに従って並ぶことで、初めて情報として成り立ちます。

医療における例

  • 電子カルテ・診療録: 医師による所見、診断、治療計画など、膨大なテキスト情報が含まれます。解析により診療の質向上や医学研究に貢献できます (6)。
  • 医学論文・学術文献: 最新の研究成果や知見がテキストとして蓄積されており、自然言語処理技術で効率的に情報を抽出・整理できます。
  • 患者さんの問診記録・インタビュー: 患者の訴えや病歴が自然言語で記録され、数値データだけでは分からない主観的情報や背景が含まれます。

特性

テキストデータの最重要特性は、単語の出現頻度だけでなく、その並び順(文法構造や文脈)が意味を決定する点です。「頭痛がする」と「痛い頭をする」では意味が異なります。この「順序」の情報をAIがいかに処理できるかが重要です。


  例1: [医師] → [は] → [患者] → [に] → [問診] → [した]
  例2: [患者] → [は] → [昨日] → [から] → [発熱] → [と] → [頭痛] → [を] → [訴えている]

  図2: テキストデータのイメージ(単語の連なり)
  解説: 単語が特定の順序で連結することで文としての意味を成します。
        この「順序」がAIによる自然言語理解の鍵となります。

音声データ (Audio Data)

「声」や「音」も系列データの一種です。日常会話や音楽、そして医療現場の様々な音が分析対象となります。

定義

音声データとは、時間の経過とともに変化する音の波形データです。空気の振動が時間変化する様子を記録したもので、私たちが「音」として認識するもの全てが該当します。

医療における例

  • 心音・呼吸音: 聴診音をデジタル化しAIで解析することで、弁膜症や喘息などの診断補助が期待されます (7)。
  • 音声対話による診療支援: AIスピーカーやスマートフォンを用いた音声入力による問診や情報提供システムです。
  • 発話障害の評価: 特定疾患(パーキンソン病など)に伴う発話特徴の変化を捉え、診断や重症度評価に用いる研究があります (8)。

特性

音声データの特徴は、音の強さ(音量)、高さ(ピッチ)、音色などが時間とともに複雑に変化する点です。言葉の抑揚や間と同様に、時間的変化のパターン自体が重要な情報です。

動画データ (Video Data)

最後に、情報量の多い「動画データ」です。医療分野でも活用が広がっています。

定義

動画データとは、静止画(「フレーム」)が時間的に連続して並んだデータです。パラパラ漫画のように、多数の静止画が連続表示されることで動きを表現します。

医療における例

  • 内視鏡検査・腹腔鏡手術の映像: AIがリアルタイム解析し、病変検出補助や手術ナビゲーションなどに応用研究されています (9)。
  • 歩行分析: 歩行パターンを動画で記録・解析し、リハビリ効果評価や神経疾患の診断補助に役立てます。
  • 顕微鏡動画: 細胞の動態観察や薬剤応答評価など、基礎研究でも重要です。

特性

動画データの最大の特徴は、各瞬間の静止画情報(空間的情報)と、それらの時間的変化(時間的情報)の両方を含む点です。一枚一枚の画像と、それらがどう繋がって動きを生み出しているかという「時間軸」の理解が不可欠です。

時系列、テキスト、音声、動画と、形は異なりますが、これらは全て「順番」や「時間の流れ」が本質的な意味を持つデータです。

これらのデータが持つ「順序」や「時間的なつながり」といった特性をAIがうまく学習できれば、人間では気づきにくい深い洞察や高精度な予測が実現できる可能性があります。

医療AIにおいて、これら系列データをどう活用するかが、診断・治療・医学研究の進歩の鍵となります。この基礎的理解が、今後のAIモデル学習の重要な土台となるでしょう。

1.2 系列データ分析の主要タスク

さて、前置きが少し長くなりましたが、系列データがどんなものか、なんとなくその姿が見えてきたのではないでしょうか。時間の流れと共に変化するデータ、言葉が連なって意味を成すデータ、音の波形、そして動画…。私たちの身の回り、特に医療の現場には、そういった「順番」に意味が込められたデータがたくさんあるんでしたね。

では、ここからが本番です。AIという強力な道具を使って、これらの系列データから一体どんな価値ある情報を引き出したり、どんな問題を解決したりできるのでしょうか?一口に「分析」と言っても色々なアプローチがありますが、ここでは代表的なタスクを3つほどピックアップして、それぞれの可能性を一緒に探っていきましょう。

graph TD;
    A["系列データ分析の主要タスク"] --> B["文字列生成 (Text Generation)"];
    A --> C["時系列データの予測 (Time Series Forecasting)"];
    A --> D["系列データの分類 (Sequence Classification)"];

文字列生成 (Text Generation)

まず一つ目は、「文字列生成」、つまりAIに新しい文章を作ってもらう、というタスクです。なんだかワクワクしますよね。

どんなことができるの?

これは、AIがまるで小説家やレポート作成のアシスタントのように、学習した大量のテキストデータから文法や言い回し、さらには文脈といったものを巧みに学び取って、それに基づいてオリジナルの文章を自動で書き出してくれる、というものです。単に既存の文章をコピー&ペーストするのとは全く違い、AI自身が「考えて」言葉を紡ぎ出そうとするんですね。この文字列生成というタスクで主役を張るのが、「生成モデル (Generative Model)」と呼ばれるタイプのAIです。このモデルは、ただ何かを分類したり予測したりするだけでなく、データそのものを、まるで「無から有を生み出す」かのように新しく作り出す方法を学習するのが得意なんです。

イメージとしては、こんな感じでしょうか。

大量の既存テキストデータ (例: 医療論文、電子カルテ、 患者さんの質問と回答集など) AI (生成モデル) 新しいテキストデータ (例: レポート草案、 説明文) 学習 生成 図5: 文字列生成タスクのイメージ 解説: AIモデルが、入力された大量のテキストデータ(学習データ)から言葉の並び方や 文脈のパターンを学習します。そして、その学習結果をもとに、新たなオリジナルの テキストを生成します。

医療の現場では、どんなふうに役立つの?

この「文章を作り出す」能力、医療の現場でも色々な応用が考えられています。

  • 診断レポートの自動生成支援: 例えば、MRIやCTといった画像検査の結果や、患者さんのバイタルデータといった情報をもとに、AIが所見の要約やレポートのたたき台を作ってくれたらどうでしょう。医師は最終的な確認と医学的判断に集中できますし、文書作成にかかる時間を大幅に減らせるかもしれませんね (10)。医師が画像所見の記述といった定型的な業務から少しでも解放され、より患者さんに向き合う時間を増やせるようになる、そんな期待が持てます。
  • 患者さん一人ひとりに合わせた説明文の作成: 例えば、同じ病気の説明でも、ご高齢の方と若い方、あるいは医療知識の背景が異なる方とでは、分かりやすい言葉遣いや説明のポイントが変わってくるはずです。AIがそういった一人ひとりの状況を汲み取って、最適な説明文を自動でテーラーメイドしてくれたら、患者さんの理解や治療への納得度もぐっと深まるのではないでしょうか。
  • 新しいお薬の候補を見つけるヒントに: ちょっと専門的になりますが、お薬の候補となる化合物の構造は、特定のルールに従った文字列(例えば、SMILES記法というものがあります)で表現することができます。AIが既存の薬の構造データをたくさん学習して、そこから「こんな構造の化合物なら、新しい効果があるかもしれない」といった新しい化合物の文字列を生成してくれたら、創薬研究の最初のステップを大きくスピードアップできるかもしれません (11)。
  • 臨床試験の計画書(プロトコル)作成のサポート: 新しい治療法や薬の効果を確かめるためには臨床試験が不可欠ですが、その計画書作りは非常に手間のかかる作業です。過去の多くの計画書や関連するガイドラインをAIが学習し、新しい試験の骨子となる草案を提案してくれれば、研究者の方々の負担を軽減できる可能性があります。

AIが文章を書くなんて、少し前まではSFの話のようでしたが、もう現実になりつつあるんですね。

時系列データの予測 (Time Series Forecasting)

二つ目は、「時系列データの予測」です。これは、過去から現在までのデータの流れを読み解いて、その先、つまり未来に何が起こるかを当てる、というタスク。天気予報をイメージしていただくと、分かりやすいかもしれませんね。

どんなことができるの?

AIは、過去の時系列データ――例えば、日々の株価の動きや、毎時の気温の変化、あるいは患者さんの心拍数の推移など――の中に潜むパターンを注意深く観察します。そのパターンとは、例えば周期的な変動であったり、徐々に上がっていくトレンドであったり、あるいは特定の出来事が起こる前に決まって現れる予兆のようなものだったりします。AIはこれらの複雑なパターンを学習し、それを基に「このデータの流れからすると、次はおそらくこうなるだろう」という未来の値を計算してくれる、というわけです。

これも図で示すと、こんな感じでしょうか。

過去から現在までの時系列データ (t-2) (t-1) (t) AIモデル 学習 未来の予測値 時間 ? (t+1) ? (t+2) 予測 図6: 時系列データ予測タスクのイメージ 解説: AIモデルが、過去のデータポイント (t-2, t-1, t など) の推移パターンを学習し、 未来のデータポイント (t+1, t+2 など) の値を予測します。「?」の部分がAIによる予測結果です。

医療の現場では、どんなふうに役立つの?

この「未来を読む」力は、医療のさまざまな場面で大きな期待が寄せられています。

  • 患者さんの容態急変予測: 例えば、集中治療室(ICU)などでは、患者さんのバイタルサイン(心拍数、血圧、呼吸数、体温など)が刻一刻と変化します。これらの細かい変化の組み合わせから、AIが「数時間後に敗血症を発症するリスクが高まっています」とか「心停止の危険な兆候が見られます」といった情報を事前に察知できれば、医師や看護師は先手を打って対応できますよね。これは本当に時間との戦いですから、AIによる早期の警告が、文字通り命を救う一手になる可能性を秘めているんです (12)。
  • お薬の効き方や血中濃度の予測: 同じお薬を使っても、その効き方や体の中での濃度変化は、患者さんの体重、年齢、腎臓や肝臓の機能などによって一人ひとり異なります。AIがこれらの個人差や過去の投与データを考慮して、「この患者さんにこの量のお薬を投与すると、血中濃度はこう推移するでしょう」と予測してくれれば、より安全で効果的な、まさにオーダーメイドの投薬計画を立てる手助けになります。
  • 感染症の流行予測: インフルエンザや、記憶に新しいCOVID-19のような感染症が、いつ、どこで、どのくらいの規模で流行するのかを予測することは、公衆衛生上とても重要です。AIが過去の感染者数のデータだけでなく、気象情報、人々の移動データ、さらにはSNS上のつぶやきといった情報も組み合わせて分析することで、より精度の高い流行予測が可能になるかもしれません (13)。そうすれば、医療機関の準備やワクチンの供給計画なども、より効果的に行えるようになるはずです。
  • 血糖値のコントロール支援: 糖尿病の患者さんにとって、血糖値の安定は日々の大きな課題です。持続血糖測定器(CGM)から得られる連続的な血糖データに加えて、食事の内容や運動量といった生活習慣の情報をAIが分析し、「このままだと数時間後に低血糖になるかもしれませんよ」とか「次の食事はこのくらいの量にすると良さそうですね」といった具体的なアドバイスを予測に基づいて提供できれば、患者さんの自己管理を力強くサポートできると思いませんか。

系列データの分類 (Sequence Classification)

三つ目にご紹介するのは、「系列データの分類」です。これは、ある一連の系列データ全体を見て、それが事前に決められたいくつかのカテゴリー(種類やクラスとも言いますね)のうち、どれに当てはまるのかを判定する、というお仕事です。健康診断の結果を見て、医師が「このデータはAタイプですね」と判断するのに少し似ているかもしれません。

どんなことができるの?

例えば、私たちのところに届くメールを考えてみてください。「これは重要な連絡メール」「これは宣伝メール」「これは残念ながら迷惑メール」といったように、私たちは無意識にメールを分類していますよね。AIによる系列データの分類も、これと似たようなことを行います。AIは、系列データ――それが心電図の波形であれ、患者さんの日記の文章であれ、あるいは診察時の会話の音声であれ――全体から、それぞれのカテゴリーに特徴的なパターンや手がかりを学習します。そして、新しい系列データが与えられたときに、「ふむふむ、このデータはこういう特徴があるから、きっとAというカテゴリーに違いない」というように、最も適切だと思われるラベルを付けてくれる、というわけです。

図で表すと、こんな具合です。

入力される系列データ (例: ある患者さんの数秒間の 心電図波形データ全体) AI (分類モデル) ← 事前に学習済み (例: 正常パターン、 不整脈Aのパターン等を学習) 分類結果のラベル (例: 「正常洞調律」 or 「心房細動の疑い」 or 「心室性期外収縮」 …) 図7: 系列データ分類タスクのイメージ 解説: AIモデルが、入力された系列データ全体の特徴を総合的に判断し、事前に定義された 複数のカテゴリー(ラベル)のどれに該当するかを識別します。

医療の現場では、どんなふうに役立つの?

この「仕分け」の能力は、医療の効率化や診断精度の向上に大きく貢献すると期待されています。

  • 心電図の波形から不整脈の種類を自動判別: 健康診断などで記録された心電図の波形データ全体をAIが解析して、「これは正常なリズムですね」とか「これは心房細動の疑いがありますよ」「こちらは心室性期外収縮が頻発していますね」といったように、自動で不整脈の種類を分類してくれる技術です (14)。これにより、専門医でなくてもある程度のスクリーニングが効率的に行えたり、見逃しを防いだりする手助けになることが期待されます。24時間装着するホルター心電図のような長時間のデータから、重要な所見をピックアップするのにも役立ちそうですね。
  • 電子カルテの記述から疾患名を分類・コード付け: 電子カルテに医師が自由な文章で記載した診療録から、AIが自動的に関連する疾患名を特定したり、国際疾病分類(ICD)コードといった標準化されたコードを付与したりする技術も進んでいます (15)。これが実現すれば、膨大なカルテデータを後から集計・分析するのがずっと楽になりますし、医学研究の貴重なデータベースとしても活用しやすくなるでしょう。
  • 患者さんとの会話音声から感情や精神状態を分析: 診察時の患者さんとの会話音声をAIが分析し、声のトーン、抑揚、話す速さ、使われる言葉遣いといった特徴から、その方が感じている不安の度合いや、もしかしたらうつ状態にあるのではないか、といった精神的な状態を客観的に評価する補助ツールとしての研究も進んでいます (16)。医師が患者さんの言葉にならないサインを捉える手助けになるかもしれません。
  • 聴診器で聞いた呼吸音から肺の病気を推測: デジタル聴診器で記録された患者さんの呼吸音のデータをAIが分析し、その音のパターンから「これは肺炎に特徴的な音ですね」「気管支炎の可能性があります」といったように、肺の疾患を推測する試みも行われています。特に遠隔医療や医療資源の乏しい地域でのスクリーニングに役立つのでは、と期待されています。

ここまで、文字列生成、時系列予測、そして系列分類という、AIを使った系列データ分析の代表的なタスクを見てきました。AIが文章を書いたり、未来を予測したり、データにラベルを付けたり…なんだかSFの世界が現実になってきているような感じがしませんか?

もちろん、これらのタスクを実現するためには、それぞれに適したAIモデルの設計や学習方法が必要になってきます。でも、どんなタスクであっても、その根底に流れているのは、やはり「系列データが持つ順序や時間的な文脈をいかにうまく捉えるか」という共通のテーマなんですね。このポイントをしっかり押さえておくことが、これから私たちが深層学習モデルを学んでいく上で、きっと大きな力になってくれるはずです。

1.3 PyTorchにおける系列データの表現方法

さて、前回までは系列データそのものが持つ面白い性質や、AIを使ってどんなことができるか、といった夢の広がるお話をしてきましたね。ここからは、いよいよ実践編、AIに実際にデータを扱ってもらうための準備に入っていきましょう。

AIモデル、特にディープラーニングのモデルにデータを「食べさせて」学習させるには、まずコンピュータが「なるほど、わかった!」とスムーズに理解できる形に、データをきれいに整えてあげる必要があるんです。人間同士でも言葉が通じないと困るように、AIにとっても理解しやすい「共通言語」にデータを翻訳してあげる作業、と考えると良いかもしれません。

そして、その翻訳作業から、その後のAIモデル作りまでを力強くサポートしてくれるのが、今回の主役の一つでもあるPyTorch (パイトーチ) というライブラリです。きっと皆さんの頼もしい相棒になってくれると思いますよ。

ところで、PyTorchって何者?

PyTorchは、FacebookのAI研究チーム(今はMeta AIという名前ですね)によって開発された、オープンソースの機械学習ライブラリです(17)。Pythonというプログラミング言語と非常に相性が良く、まるで普通のPythonコードを書くような感覚で、直感的にAIモデルを組み立てられるのが、多くの人に愛されている人気の秘密の一つかな、と思います。

特に、研究の世界では「こんなこと試してみたいな」「あんなモデルも作れるかも」といった新しいアイデアを、とても柔軟に、そしてスピーディーに形にしやすいんですね。だから、世界中の多くの研究者や開発者に愛用されているわけです。もちろん、私たちがいま一緒に学ぼうとしている医療AIの分野でも、その使いやすさとパワフルさから、例外なく広く活用されています。

1.3.1 まずは基本の「キ」! テンソルを用いたデータの構造化

PyTorchを使ってデータを扱う上で、もう絶対に避けては通れない、そして最初に仲良くなっておきたいのが、「テンソル (Tensor)」という言葉です。初めて聞くと「何だか数学っぽくて難しそう…」と身構えてしまうかもしれませんが、どうぞ安心してください。実はこれ、皆さんもきっと一度は見たことがある「多次元配列」のことなんです。

「配列」というと、例えばエクセルの表計算ソフトを思い浮かべる方もいらっしゃるかもしれませんね。あれはまさに、行と列という2つの次元でデータが並んだ「2次元配列(行列とも言います)」です。もっとシンプルに、ただ数字が1列に並んでいるものは「1次元配列(ベクトル)」、そして数字がたった一つだけなら「0次元配列(スカラー)」と呼べます。テンソルというのは、これらをさらに一般化して、3次元、4次元、…と、もっとたくさんの軸(次元)でデータを整理整頓して扱えるようにした、いわば「よしなにデータをしまっておける魔法の箱」のようなものだと考えてみてください。

ニューラルネットワーク、つまりAIの脳みそに当たる部分は、このテンソルという箱に入った数値データを受け取って、色々な計算をして、そしてまたテンソルという箱に入った結果を返してくれる、そんな仕組みになっているんですね。

系列データは、どんな形のテンソルの箱に入るの?

では、私たちが扱いたい「系列データ」は、どんな形のテンソルの箱に収まるのでしょうか?もちろんデータの種類や目的によって色々な形があり得るのですが、非常によく使われる代表的な形は、次の3つの「軸(次元)」で構成されることが多いです。

(バッチサイズ, 系列長, 特徴数)

それぞれの軸が何を意味しているのか、少し詳しく見ていきましょう。

  • バッチサイズ (Batch Size): これは、AIモデルに一度に見せるデータの「かたまりの大きさ」、もっと平たく言えば「何個のデータをまとめて処理するか」という数です。例えば、100人分の患者さんの心電図データがあったとして、それを10人分ずつのかたまりに分けてモデルに見せる場合、バッチサイズは10になります。少しずつ見せることで、コンピュータのメモリを圧迫しすぎないようにしたり、学習を安定させたりする効果があるんですね。
  • 系列長 (Sequence Length): これは、その名の通り「一つの系列データがどれくらいの長さを持っているか」を表します。例えば、心電図データなら「何秒間の記録で、1秒あたり何点サンプリングされているか」で決まりますし(10秒間で1秒100点なら系列長1000)、テキストデータなら「何文字(または何単語)で構成される文章か」といった具合です。
  • 特徴数 (Number of Features): これは、「系列の中の各時点(各要素)が、いくつの数値で表現されているか」を示します。例えば、心電図が1つの電極(第II誘導とかですね)だけで記録されたものなら特徴数は1ですが、一般的な12誘導心電図であれば、同じ時刻に12種類の異なる波形データが得られるので、特徴数は12になります。また、テキストデータの場合、後で詳しくお話ししますが、各単語や文字を数値のベクトル(埋め込みベクトルと言います)で表現することが多く、そのベクトルの次元数がこの特徴数に相当します。

これらの3つの軸を持つテンソルを視覚的にイメージすると、こんな感じの「直方体のブロック」のようになることが多いです。

テンソルの構造: (バッチサイズ, 系列長, 特徴数) 系列長 (S) 特徴数 (F) バッチ サイズ (B) ← 1つのデータサンプル (バッチの中の1つ) [値, 値, …, 値] ← 系列の1番目の時点 (F個) [値, 値, …, 値] ← 系列の2番目の時点 (F個) [値, 値, …, 値] ← 系列のS番目の時点 (F個) この「1つのデータサンプル」が「バッチサイズ(B)個」積み重なったものが、 AIモデルに入力されるテンソル全体のイメージです。 全体の形状は (B, S, F) となります。 例えば: B = 3 (3人の患者データ) S = 10 (各患者10時点の記録) F = 2 (各時点で心拍数と血圧の2つの特徴) という場合、(3, 10, 2) の形状のテンソルになります。 患者1: [ [70,120], [72,122], …, [71,120] ] (10時点 x 2特徴) 患者2: [ [65,110], [66,112], …, [67,110] ] 患者3: [ [80,130], [82,132], …, [79,130] ] これら全体をまとめて1つのテンソルとして扱います。

なんだか少し難しく感じるかもしれませんが、実際にコードを見ながら手を動かしてみると、「ああ、こういうことか!」と腑に落ちることが多いものです。百聞は一見にしかず、ですよね。早速、Pythonのコードで、どうやって系列データをこのテンソルという形にしていくのか、具体的なステップを追ってみましょう。

Pythonコード例:系列データのテンソル表現


# まずは、これから使う大切な道具箱(ライブラリ)を準備します。
import torch # PyTorch本体です。「トーチ」と読みます。AIモデル作りやテンソル計算の主役です。
import numpy as np # NumPy(ナムパイ)は、Pythonで数値計算、特に配列計算を効率的に行うための定番ライブラリです。PyTorchとも仲良しです。

# --- 例1: 数値の時系列データ (例えば、3人の患者さんの5時点のバイタルサイン) ---
print("--- 例1: 数値の時系列データ ---") # 今から何をするか、分かりやすく表示してみましょう。

# 患者さん3人分のバイタルサイン(ここでは仮に心拍数としましょう)を、Pythonのリストのリストとして用意します。
# 患者Aさんの5時点の心拍数: [70, 72, 75, 73, 71]
# 患者Bさんの5時点の心拍数: [65, 66, 64, 68, 67]
# 患者Cさんの5時点の心拍数: [80, 82, 81, 79, 80]
vital_data_list = [ # このリスト全体が、私たちの「元データ」になります。
    [70, 72, 75, 73, 71],    # 患者Aさんのデータ (1つ目の系列)
    [65, 66, 64, 68, 67],    # 患者Bさんのデータ (2つ目の系列)
    [80, 82, 81, 79, 80]     # 患者Cさんのデータ (3つ目の系列)
]

# Pythonのリストのままでは、AIモデルが扱いにくいので、まずはNumPy配列という形に変換します。
# 同時に、データ型を「32ビット浮動小数点数(float32)」に指定します。
# これは、ニューラルネットワークの計算でよく使われる数値の型で、計算の精度と効率のバランスが良いとされています。
vital_data_np = np.array(vital_data_list, dtype=np.float32) # リストをNumPy配列に変換!
print("NumPy配列:") # 変換後のNumPy配列の中身を見てみましょう。
print(vital_data_np)

# 次に、このNumPy配列を、いよいよPyTorchのテンソルに変換します。
# torch.from_numpy() という命令を使うと、簡単に変換できます。
vital_data_tensor = torch.from_numpy(vital_data_np) # NumPy配列からPyTorchテンソルへ!
print("PyTorchテンソル:") # テンソルの中身も確認してみましょう。
print(vital_data_tensor)

# 最後に、このテンソルがどんな「形」をしているか確認します。
# 形状は .shape という属性で分かります。 (バッチサイズ, 系列長) の形で表示されるはずです。
# この例では、患者3人分 (バッチサイズ=3)、各5時点のデータ (系列長=5) ですね。
# 各時点のデータは1つの数値(心拍数)だけなので、特徴数は1 (暗黙的) となっています。
print("テンソルの形状:") # テンソルの形状の情報をこれから表示することを示します。
print(vital_data_tensor.shape) # vital_data_tensorの形状(各次元のサイズ)を出力します。


# --- 例2: 日本語テキストデータ (簡単な単語リストを、文字ごとのIDに変換してみましょう) ---
print("\n--- 例2: 日本語テキストデータ(簡易的な文字ID化) ---") # 次の例の開始を知らせます。

# 分析したい日本語の単語をリストとして用意します。今回は医療でよく聞く症状の単語にしてみましょう。
word_list_jp = ["発熱", "頭痛", "倦怠感"]
print("元の単語リスト:", word_list_jp) # まずは元のリストを表示します。

# コンピュータは「文字」そのものを直接理解できません。そこで、文字を「数値のID」に置き換える作業が必要です。
# まず、私たちのデータセット(今回は上記の3単語だけですが)に出てくる全てのユニークな文字を集めましょう。
all_chars = set() # setという型は、重複する要素を持たない「集合」を作るのに便利です。初期状態は空の集合です。
for word in word_list_jp: # 各単語("発熱", "頭痛", "倦怠感")について繰り返し処理を行います。
    for char in word:     # 単語の中の各文字(例: "発", "熱")について繰り返し処理を行います。
        all_chars.add(char) # setに文字を追加します。同じ文字が何度出てきても、setの中では1つだけになります。

# 次に、集めたユニークな文字たちに、それぞれ番号(ID)を割り振るための「辞書」を作ります。
# '<PAD>' という特別な文字(トークンと言います)には、IDとして 0 を割り当てておきましょう。
# この '<PAD>' は、後で出てくる「パディング」という処理で使う、いわば「埋め草」のようなものです。
char_to_id = {'<PAD>': 0} # まず、パディング用IDを辞書に入れます。キーが文字、値がIDです。
# 残りの実際の文字には、1から順にIDを振っていきます。
# sorted(list(all_chars)) で文字を一定の順序(通常は文字コード順)に並べてからIDを振ると、毎回同じ辞書が作れるので便利です。
for i, char in enumerate(sorted(list(all_chars))): # enumerateはインデックス(i)と要素(char)をペアで取り出せます。
    char_to_id[char] = i + 1 # 文字をキー、ID(インデックス+1なので1から始まる)を値として辞書に保存します。
print("文字からIDへのマッピング辞書:", char_to_id) # どんな辞書ができたか見てみましょう。

# IDから元の文字に戻せるように、逆引き用の辞書も作っておくと、後々何かと便利です。
id_to_char = {id_val: char_val for char_val, id_val in char_to_id.items()} # char_to_id のキーと値を入れ替えるだけです。
print("IDから文字へのマッピング辞書:", id_to_char) # 逆引き辞書の内容も確認します。

# さあ、いよいよ各単語を、この辞書を使って文字IDのリスト(数値の列)に変換します。
word_ids_list = [] # 変換結果を格納するための空のリストを用意します。
for word in word_list_jp: # 各単語("発熱", "頭痛", "倦怠感")について繰り返し。
    ids = [char_to_id[char] for char in word] # 単語内の各文字を、上で作ったchar_to_id辞書を使ってIDに変換し、そのIDのリストを作ります。
    word_ids_list.append(ids) # できたIDのリストを、全体のリストに追加します。
print("単語ごとの文字IDリスト:", word_ids_list) # これで、文字が数値の列になりました!
# ただし、このままだと、「発熱」は2つの数字、「倦怠感」は3つの数字、というように長さがバラバラですね。
# これをどうするかは、次の「パディング」というお話で。

# === ここから下が上記のprint文による実際の出力の例 ===
# --- 例1: 数値の時系列データ ---
# NumPy配列:
# [[70. 72. 75. 73. 71.]
#  [65. 66. 64. 68. 67.]
#  [80. 82. 81. 79. 80.]]
# PyTorchテンソル:
# tensor([[70., 72., 75., 73., 71.],
#         [65., 66., 64., 68., 67.],
#         [80., 82., 81., 79., 80.]])
# テンソルの形状:
# torch.Size([3, 5])
#
# --- 例2: 日本語テキストデータ(簡易的な文字ID化) ---
# 元の単語リスト: ['発熱', '頭痛', '倦怠感']
# 文字からIDへのマッピング辞書: {'<PAD>': 0, '感': 1, '怠': 2, '発': 3, '熱': 4, '痛': 5, '頭': 6} (注: IDの割り振り順は実行環境や文字のソート順により変わる可能性があります。ここでは一例です)
# IDから文字へのマッピング辞書: {0: '<PAD>', 1: '感', 2: '怠', 3: '発', 4: '熱', 5: '痛', 6: '頭'} (注: 上記と同様です)
# 単語ごとの文字IDリスト: [[3, 4], [6, 5], [2, 1, 1]] (注: 「倦怠感」の最後のIDは上記の辞書例に基づくと[2,1,1]ではなく[2,1,char_to_id['感']] (この例では[2,1,1]) となります。ここでは、単語がIDのリストに変換されたことを示しています。)

どうでしょうか。こんな風に、まずは私たちが普段目にしているデータ(数値のリストや日本語の文字列)を、コンピュータが計算処理しやすい数値の形、多くはテンソルという形に変換してあげることが、AIモデル作りの本当に大切な第一歩になるわけです。特にテキストデータの場合は、文字をIDに置き換えるという一手間が必要でしたが、これでAIも少しは「言葉」を理解する準備ができた、と言えるかもしれませんね。もちろん、実際の自然言語処理では、もっと洗練された方法で単語や文を数値のベクトルに変換することが多いのですが(18)、基本的な考え方はここにあります。

1.3.2 長さがバラバラでも大丈夫! 可変長系列のためのパディングとマスキング

さて、系列データをテンソルという形で扱う上で、一つ、ちょっと厄介だけれども避けては通れない問題が出てきます。それは、多くの場合、実際の系列データというのは、その「長さ」がサンプルごとにバラバラだということです。

例えば、患者さん一人ひとりの「今日の調子はどうですか?」という訴え(テキストデータですね)は、短い一言で終わる方もいれば、長々と詳しく説明される方もいますよね。また、心電図の記録時間も、簡単な検査なら数十秒かもしれませんが、精密検査なら24時間記録することだってあります。このように、系列の長さはまちまちなのが普通です。

ところが、AIモデル、特にたくさんのデータを一度にまとめて処理する(これをミニバッチ処理と言います。後で詳しくお話ししますね)ときには、入力されるデータの形、とりわけ系列の長さが全部揃っていると、とっても都合が良いんですね。まるで、同じサイズの箱にきれいに物を詰め込むようなイメージです。では、この長さの不揃い問題をどう解決するか? そこで登場するのが、「パディング (Padding)」「マスキング (Masking)」という、二つの賢いテクニックです。

パディング (Padding) って何? ~短いものを長く見せる技~

パディングというのは、一言でいうと、バッチ(ひとまとまりのデータ群)の中で一番長い系列データに合わせて、それよりも短い系列データの「お尻」に、特別な「意味のない値」を付け足して、全部の系列の長さを無理やり同じにしちゃうという作戦です。この「意味のない値」のことを「パディングトークン」と呼んだりします。プログラミングの世界では、よく数値の「0」がこのパディングトークンとして使われますね。

例えるなら、クラスで身長測定をするときに、背の低い子の足元にそっと台を置いてあげて、みんな同じ高さの基準線に頭のてっぺんが来るように調整するようなイメージでしょうか。これで、見た目上はみんな同じ「長さ(この場合は身長)」になるわけです。


  元の系列データ (ID化済み、長さはバラバラ):
  系列A (長さ5): [ID1, ID2, ID3, ID4, ID5]
  系列B (長さ3): [IDa, IDb, IDc]
  系列C (長さ4): [IDx, IDy, IDz, IDw]

  このバッチ内の最大系列長は「5」ですね。
  パディングトークンを「0」とすると...

  パディング後の系列データ (全系列の長さが5に揃う):
  系列A: [ID1, ID2, ID3, ID4, ID5]  (変更なし)
  系列B: [IDa, IDb, IDc,  0,   0 ]  (末尾に0を2つ追加)
  系列C: [IDx, IDy, IDz, IDw,  0 ]  (末尾に0を1つ追加)

  図8: パディングのイメージ
  解説: 長さの異なる複数の系列があった場合、バッチ内で最も長い系列の長さに
        合わせて、それより短い系列の末尾に特定のパディングトークン(ここでは0)
        を追加し、すべての系列の長さを強制的に揃えます。
        これにより、データをまとめてテンソルとして扱いやすくなります。

医療の現場では?
例えば、患者さんごとに診察記録の量が違う場合や、インタビューで話してくださる内容の長さが異なる場合など、テキストデータをバッチ処理しようとすると、このパディングはほぼ必須のテクニックになります。

マスキング (Masking) って何? ~「詰め物」は無視してね、と伝える技~

パディングのおかげで、めでたく全系列データの長さは揃いました。でも、ここで一つ注意点があります。パディングで付け足した部分(例えばさっきの例の「0」)は、あくまで長さを揃えるための「詰め物」であって、元々のデータが持っていた情報ではありませんよね。

だから、この後AIが計算をするときに、この詰め物の部分を間違って「おっ、何か重要な情報があるぞ!」と勘違いしてしまわないように、「ごめんね、この部分はただの詰め物だから、計算には使わないでねー」とAIにそっと教えてあげる必要があります。この「ここは詰め物ですよ」という目印を付けて、AIに無視してもらうようにする工夫、これがマスキングの役割です。

これを怠ってしまうと、AIが意味のないパディング部分まで一生懸命学習しようとしてしまい、結果としてモデルの性能が下がってしまったり、おかしな動きをしてしまったりする原因になることがあるんです。なので、パディングとマスキングは、いつもセットで考えるのが基本、と覚えておくと良いでしょう。

医療の現場では?
パディングと同様に、可変長の系列データを扱う際には常に意識する必要があります。特に、後々学ぶ「Attention機構」のような、系列内のどの部分に注目すべきかをAI自身が学習するような仕組みでは、このマスキングが非常に重要な役割を果たすんですよ。

では、このパディングとマスキングが、実際のPythonコードではどのように行われるのか、具体的に見ていきましょう。

Pythonコード例:日本語テキストデータのパディングとマスキング

graph TD;
    A["入力: 可変長のID系列リスト
(例: word_ids_list_padding_example)"] --> B["1. バッチ内の最大系列長を特定
(max_lenを計算)"]; B --> C["2. パディング処理の実行
(各系列の末尾にpad_idを追加し
長さをmax_lenに統一)"]; C --> D["中間結果: パディング済みID系列リスト
(padded_word_ids)"]; D --> E["3. PyTorchテンソルへ変換
(padded_tensor = torch.tensor(padded_word_ids, ...))"]; E --> F["4. アテンションマスクの作成
(attention_mask = (padded_tensor == pad_id))
※パディング部分を識別"]; F --> G["出力:
- パディング済みテンソル (padded_tensor)
- アテンションマスク (attention_mask)"];

import torch # PyTorchのメインライブラリをインポートします。

# 前の例2で作成した `char_to_id_padding_example` (文字→ID辞書) と
# `word_ids_list_padding_example` (ID化された単語リスト、パディング前) が
# ここでも使えるように、改めて定義しておきましょう(実際には前のセルの結果をそのまま使います)。
char_to_id_padding_example = {'<PAD>': 0, '発': 1, '熱': 2, '頭': 3, '痛': 4, '倦': 5, '怠': 6, '感': 7} # 文字とIDのマッピング辞書です。
word_ids_list_padding_example = [ # 長さがバラバラのID列のリストです。
    [char_to_id_padding_example['発'], char_to_id_padding_example['熱']], # 長さ2のID列です。
    [char_to_id_padding_example['頭'], char_to_id_padding_example['痛']], # 長さ2のID列です。
    [char_to_id_padding_example['倦'], char_to_id_padding_example['怠'], char_to_id_padding_example['感']] # 長さ3のID列です。
]
print("パディング前の単語IDリスト:", word_ids_list_padding_example) # まずは処理前の状態を確認します。

# まず、このデータのかたまり(バッチと言います)の中で、一番長い系列の長さを調べます。
max_len = 0 # 最大長を記録する変数を0で初期化します。
for ids in word_ids_list_padding_example: # リストの中の各ID列(各単語)について繰り返し処理を行います。
    if len(ids) > max_len:                # 今見ているID列の長さが、これまでの最大長よりも長ければ、
        max_len = len(ids)                # 最大長を更新します。
print("最大系列長:", max_len) # このバッチでの一番長い系列の長さを表示します(この例では3ですね)。

# 次に、パディング処理を行います。
padded_word_ids = [] # パディング後のID列を格納するための、空のリストを用意します。
pad_id = char_to_id_padding_example['<PAD>'] # パディングに使う特別なID(ここでは0)を変数に入れておきます。
for ids in word_ids_list_padding_example:    # 各ID列について繰り返し処理を行います。
    padding_amount = max_len - len(ids)      # このID列を最大長にするために、あといくつパディングが必要か計算します。
    # 元のID列の「後ろ」に、計算した個数分だけ「pad_id」を追加します。
    padded_ids = ids + [pad_id] * padding_amount # Pythonのリストの結合と繰り返しを利用しています。
    padded_word_ids.append(padded_ids)       # パディングされたID列を、結果格納用リストに追加します。
print("パディング後の単語IDリスト:", padded_word_ids) # 全てのID列が同じ長さになったか確認しましょう。

# これで長さが揃ったので、PyTorchのテンソルに変換できます。
# IDのような離散的な値を扱う場合、データ型は整数型 (`torch.long`) を使うのが一般的です。
padded_tensor = torch.tensor(padded_word_ids, dtype=torch.long) # パディング後のリストをPyTorchテンソルに変換します。
print("パディングされたテンソル:\n", padded_tensor) # テンソルの内容を表示します。
print("テンソルの形状:", padded_tensor.shape) # テンソルの形も見てみましょう((データ数, 最大系列長) になっているはずです)。

# 最後に、マスキングのための情報を作ります。
# ここでは、「どこがパディングで埋められた部分か」を示すマスクを作ってみましょう。
# パディングされた部分を True (または1)、元のデータ部分を False (または0) とするブール型のテンソルです。
# (注意: マスクのTrue/Falseや1/0の意味は、使うAIモデルやライブラリによって逆の場合もあるので、都度確認が必要です!)
# padded_tensor の中で、値が pad_id (0) と等しい要素がどこかを調べています。
attention_mask = (padded_tensor == pad_id) # pad_id と同じ値の箇所が True になるブール演算です。
print("アテンション用パディングマスク (Trueがパディング箇所):\n", attention_mask) # マスクの内容を表示します。

# === ここから下が上記のprint文による実際の出力の例 ===
# パディング前の単語IDリスト: [[1, 2], [3, 4], [5, 6, 7]]
# 最大系列長: 3
# パディング後の単語IDリスト: [[1, 2, 0], [3, 4, 0], [5, 6, 7]]
# パディングされたテンソル:
#  tensor([[1, 2, 0],
#          [3, 4, 0],
#          [5, 6, 7]])
# テンソルの形状: torch.Size([3, 3])
# アテンション用パディングマスク (Trueがパディング箇所):
#  tensor([[False, False,  True],
#          [False, False,  True],
#          [False, False, False]])

これで、長さがバラバラだった日本語の単語たちも、きれいに形が揃ったテンソルとしてAIモデルに渡せる準備ができました。そして、どこが元々の情報で、どこが後から付け足した「詰め物」なのかも、マスクという形でAIに伝えられるようになりましたね。ちょっとした下準備ですが、これがスムーズで賢いAIの学習には欠かせない、大切な工夫なんです。

1.3.3 大量のデータも怖くない! ミニバッチ処理のためのDataLoaderの活用

AI、特にディープラーニングのモデルを賢くするためには、ものすごーくたくさんのデータを使って学習させることがよくあります。例えば、何万枚ものレントゲン写真だったり、何十万人分もの電子カルテの記録だったり…。想像しただけでも、データ量が膨大だってことが分かりますよね。

そんな大量のデータを、学習のたびに一度に全部コンピュータのメモリに読み込もうとすると、さすがの高性能なコンピュータも「もうお腹いっぱいです!メモリが足りません!」と音を上げてしまうかもしれません。それに、一度に全部のデータで計算しようとすると、時間もかかりすぎてしまいます。

そこで、もっと賢いやり方として、全データをいくつかの小さなかたまり(これを「ミニバッチ (Mini-batch)」と呼びます)に分割して、AIモデルに少しずつ「見せて」学習を進めていく、という方法が一般的に取られます。全部の料理を一度にテーブルに出すのではなく、コース料理のように前菜から順番に少しずつ出すイメージ、と言ったら分かりやすいでしょうか。

このミニバッチ処理を、とっても簡単かつ効率的に行ってくれるのが、PyTorchが用意してくれている`Dataset` (データセット)`DataLoader` (データローダー) という、これまた便利な道具(クラスと言います)のペアなんです。この二つを使いこなせると、データ周りの面倒な処理がぐっと楽になりますよ。

Datasetクラス ~データを整理整頓するカタログ付き倉庫~

まず `Dataset` クラスですが、これは、私たちがAIモデルに学習させたいデータ全体を、きちんと整理整頓して保管しておくための、いわば「カタログ付きのデータ倉庫」のようなものです。この倉庫には、一つ一つのデータ(例えば、ある患者さんの心電図データとその診断結果のペアなど)がきちんと仕分けられて保管されていて、それぞれのデータには通し番号(インデックスと言います)が振られています。

PyTorchで自分のデータを使って `Dataset` を作るには、`torch.utils.data.Dataset` という親クラスを「継承」して、主に2つの特別な関数(メソッドと言います)を自分で定義する必要があります。

  • __len__(self): 「このデータ倉庫には、全部で何個のデータが入っていますか?」という質問に答える関数です。データセットの総数を返すように作ります。
  • __getitem__(self, idx): 「カタログのidx番のデータを見せてください」というリクエストに応えて、指定された番号(idx)のデータ(多くは入力データと正解ラベルのペア)を取り出して返す関数です。

医療データで考えると?
例えば、たくさんの患者さんの心電図データファイル(画像かもしれませんし、数値データかもしれません)と、それぞれに対応する「正常」「不整脈A」「不整脈B」といった診断ラベルを管理する `MyECGDataset` というクラスを作ることができます。このクラスの `__getitem__` は、指定された番号の患者さんの心電図データをファイルから読み込んで、適切な形に前処理し、診断ラベルと一緒に返す、といった処理を担当することになるでしょう。

DataLoaderクラス ~ミニバッチを運んでくれる賢い配送トラック~

`DataLoader` クラスは、先ほどの `Dataset` というデータ倉庫から、実際にデータをミニバッチ単位で取り出してきて、AIモデルがいる「作業場」まで効率よく運んでくれる、いわば「賢い配送トラック」のような役割を果たします。私たちは、このトラックに「一度に何個ずつデータを運んでください(これがバッチサイズですね)」とお願いするだけでOKです。

しかも、この配送トラック、ただ運ぶだけじゃないんです。

  • データのシャッフル: 学習の際、毎回同じ順番でデータをモデルに見せていると、モデルがその順番に変に依存してしまって、うまく generalisation(未知のデータへの対応力)できないことがあります。`DataLoader` は、各学習サイクル(エポックと言います)の開始時に、データ倉庫の中身をランダムにかき混ぜて(シャッフルして)、いつも違う順番でデータを運んできてくれるんです。これで、モデルがよりロバストに(色々な状況に強く)学習できるようになります。
  • マルチプロセスでのデータ読み込み: データ倉庫からデータを取り出して前処理する作業は、意外と時間がかかることがあります。`DataLoader` は、この作業を複数の「作業員」(プロセスと言います)で分担して並行して行うことで、AIモデルが計算をしている間に次のミニバッチの準備を済ませておく、といった効率化も図ってくれます。これにより、学習全体のスピードアップが期待できるんですね。

どんなメリットが?
一言でいうと、大量のデータを効率的にメモリに読み込み、必要ならGPU(AI計算を高速に行うための特別なプロセッサ)へスムーズに転送するための、データ供給パイプラインを簡単に構築できる、という点が最大のメリットです。

では、この `Dataset` と `DataLoader` が、実際にPythonコードの中でどんな風に使われるのか、簡単な例で体験してみましょう。

Pythonコード例:カスタムDatasetとDataLoaderを用いたミニバッチ生成

graph TD;
    A["入力データ準備
- ID化・パディング済みテンソル (例: padded_tensor_for_dataset)
- 対応するラベルテンソル (例: labels_for_dataset)"] --> B["1. カスタムDatasetクラスの定義
(例: MyMedicalTextDataset)
- __init__(self, data, labels): データとラベルを内部に保存
- __len__(self): データセットの総サンプル数を返す
- __getitem__(self, idx): 指定インデックスの
サンプル(データとラベルのペア)を返す"]; B --> C["2. Datasetのインスタンス(実体)を作成
dataset_instance = MyMedicalTextDataset(IDテンソル, ラベルテンソル)"]; C --> D["3. DataLoaderのインスタンスを作成
dataloader_instance = DataLoader(dataset_instance, batch_size, shuffle)
(バッチサイズやシャッフルの有無を指定)"]; D --> E["4. DataLoaderからミニバッチを反復取得
(例: for token_ids_batch, labels_batch in dataloader_instance:)"]; E --> F["出力/利用: ミニバッチ
(データバッチとラベルバッチの組)
⇒ AIモデルの学習、評価などに利用"];

import torch # PyTorchのメインライブラリをインポート。これがないと始まりません。
from torch.utils.data import Dataset, DataLoader # DatasetクラスとDataLoaderクラスをインポートします。これらが今日の主役です。

# 前のパディングの例で作った `padded_tensor_for_dataset` と `labels_for_dataset` を、
# ここでもう一度使いますね。これらが私たちの「元データ」になります。
# padded_tensor_for_dataset は、パディングされて長さが揃った単語IDのテンソルでした。 (データ数3, 系列長3) の形をしています。
padded_tensor_for_dataset = torch.tensor([[1, 2, 0], [3, 4, 0], [5, 6, 7]], dtype=torch.long)
# labels_for_dataset は、それらに対応する仮のラベル(例えば、症状のカテゴリとか)でした。データ数3に対応する3つのラベルです。
labels_for_dataset = torch.tensor([0, 1, 0], dtype=torch.long)

# まずは、自分たち専用のDatasetクラスを定義します。
# torch.utils.data.Dataset という「設計図」を元にして作ります(これを「継承」と言います)。
class MyMedicalTextDataset(Dataset): # クラス名は分かりやすいものにしましょう。
    # これは「コンストラクタ」と呼ばれる特別な関数で、このクラスの「部品」を作る時に最初に呼ばれます。
    # ここでは、元になるデータ(token_ids_tensor)とラベル(labels_tensor)を受け取って、
    # このクラスの内部に保存しておきます。いわば、データ倉庫に商品をしまう作業です。
    def __init__(self, token_ids_tensor, labels_tensor):
        # self.xxx という形で、クラスの「持ち物」としてデータを保存します。
        self.token_ids = token_ids_tensor # パディング済みのIDテンソルを 'token_ids' という名前で保存します。
        self.labels = labels_tensor       # 対応するラベルテンソルを 'labels' という名前で保存します。

    # これは、このデータセットにデータが全部で何個入っているかを返す関数です。
    # DataLoaderが、あと何個データがあるかを知るために使います。「倉庫に在庫はいくつ?」と聞かれるイメージです。
    def __len__(self):
        return len(self.labels) # ラベルの数(=データの総数)を返します。

    # これが一番重要な関数で、指定された番号(idx)のデータとラベルのペアを1つ取り出して返す役割です。
    # DataLoaderが、順番に「カタログのidx番目の商品ください!」とリクエストしてくるイメージです。
    def __getitem__(self, idx): # idx には 0 から (データ総数-1) までの整数が入ってきます。
        # token_ids の idx番目 と labels の idx番目 をセットにして返します。
        return self.token_ids[idx], self.labels[idx]

# さて、設計図(MyMedicalTextDatasetクラス)ができたので、実際にデータを入れて「データ倉庫」を作ってみましょう。
# これを「インスタンス化」と言います。クラスという設計図から、実体のあるモノを作るイメージです。
dataset_instance = MyMedicalTextDataset(padded_tensor_for_dataset, labels_for_dataset) # データとラベルを渡してインスタンス(実体)を作成!

# データ倉庫(dataset_instance)ができたので、今度はそこからミニバッチを運んでくれる
# 配送トラック(DataLoader)を手配しましょう。
# batch_size=2 なので、「一度に2個ずつデータを運んでね」とお願いしています。
# shuffle=True なので、「運ぶ前に中身をよくかき混ぜてね(順番をランダムにしてね)」とお願いしています。
# (学習時には shuffle=True にするのが一般的ですが、テストの時は False にすることが多いです。なぜなら、テストでは毎回同じ結果を得たいからです。)
dataloader_instance = DataLoader(dataset_instance, batch_size=2, shuffle=True) # DataLoaderのインスタンスも作成します。

# これで準備完了! DataLoaderから実際にミニバッチを取り出してみましょう。
# DataLoaderは「イテレータ」という性質を持っているので、forループで順番に(シャッフルされていればランダムな順番で)取り出せます。
print("DataLoaderからのミニバッチ取り出し (バッチサイズ2):") # これから何をするか表示します。
# enumerate を使うと、何番目のバッチかと、バッチの中身を一緒に取り出せて便利です。
for i, batch_data in enumerate(dataloader_instance): # DataLoaderから一つずつミニバッチを取り出します。
    # batch_data は、__getitem__で返した (データ, ラベル) のペアが、バッチサイズ分まとまったものになっています。
    # なので、データ部分とラベル部分に分けてあげましょう。これを「アンパッキング」と言います。
    token_ids_batch, labels_batch = batch_data

    print(f"ミニバッチ {i+1}:") # 何番目のミニバッチかを表示します。
    print(f"  データバッチ (token_ids_batch):\n{token_ids_batch}") # データバッチの中身を表示します。
    print(f"  ラベルバッチ (labels_batch):\n{labels_batch}") # ラベルバッチの中身を表示します。
    print(f"  データバッチの形状: {token_ids_batch.shape}") # データバッチの形も確認しましょう。 (バッチサイズ, 系列長) になっているはず。
    print(f"  ラベルバッチの形状: {labels_batch.shape}")   # ラベルバッチの形も確認しましょう。 (バッチサイズ) になっているはず。
    # 実際のAIの学習では、この後、token_ids_batch をAIモデルに入力し、
    # 出てきた予測結果と labels_batch(正解ラベル)を比べて、モデルを賢くしていく、という流れになります。

# === ここから下が上記のprint文による実際の出力の例 (shuffle=Trueのため、取り出されるバッチの順序や中身は実行毎に変わる可能性があります) ===
# DataLoaderからのミニバッチ取り出し (バッチサイズ2):
# ミニバッチ 1:
#   データバッチ (token_ids_batch):
# tensor([[1, 2, 0],
#         [5, 6, 7]])
#   ラベルバッチ (labels_batch):
# tensor([0, 0])
#   データバッチの形状: torch.Size([2, 3])
#   ラベルバッチの形状: torch.Size([2])
# ミニバッチ 2:
#   データバッチ (token_ids_batch):
# tensor([[3, 4, 0]])
#   ラベルバッチ (labels_batch):
# tensor([1])
#   データバッチの形状: torch.Size([1, 3])
#   ラベルバッチの形状: torch.Size([1])
# (注: 上記はあくまで一例です。shuffle=True のため、どのデータがどのバッチに、どの順番で入るかは実行のたびに変わります。
#  また、データ総数がバッチサイズで割り切れない場合、最後のバッチのサイズは小さくなります。この例ではデータ総数3、バッチサイズ2なので、
#  1つ目のバッチはサイズ2、2つ目のバッチはサイズ1になっています。)

どうでしょう? まず Dataset クラスという形で「うちのデータはこういう形をしていて、こうやって一つずつ取り出せますよ」というルールを一度定義してしまえば、あとはそれを DataLoader にポンと渡すだけで、よしなにミニバッチを作って、シャッフルまでしてくれる。本当に便利ですよね。これがあるおかげで、私たちはデータの前処理の細かい実装にずっと頭を悩ませる必要がなくなり、AIモデルの設計や学習といった、より本質的な部分に集中できるというわけです。これがPyTorchの強力なサポート機能の一つなんですね。

1.4 (主に自然言語処理向け)埋め込み層(nn.Embedding)の役割と使い方

さて、前回までに、テキストデータを文字ごとのID(数値ですね)の列として表現する方法を見てきました。これでコンピュータも少しは文字を「読める」ようになったわけですが、実はこのIDのままでは、AIが言葉の「意味」を深く理解するには、まだちょっと不十分なんです。

なぜなら、これらのIDは、例えるなら選手に割り振られた背番号のようなもので、IDの数字自体(例えば「2番」と「10番」)に、「この選手は足が速い」とか「あの選手とプレースタイルが似ている」といった情報は直接的には込められていませんよね。それと同じで、文字IDも単なる識別子なので、「この文字は『熱』という漢字に近い意味を持つ」といった情報は、IDの数値からは読み取れないわけです。

そこで登場するのが、今回のテーマである「埋め込み層 (Embedding Layer)」という、言葉に豊かな意味を持たせるための、とっても賢い仕組みです。特に、自然言語処理(NLP:Natural Language Processing)と呼ばれる、コンピュータに私たち人間の言葉を理解させたり、使わせたりする分野では、もはや必須のテクニックと言ってもいいかもしれません。この埋め込み層のおかげで、AIは単語や文字の「表面的な形」だけでなく、その「背後にある意味」に一歩近づくことができるようになるんですよ。

埋め込み層の話の前に… もしIDをそのまま使ったら? (One-hot表現の課題)

埋め込み層がなぜそんなに重要なのかをより深く理解するために、もし私たちが前回作った文字IDをそのままAIモデルの入力として使おうとしたら、どんなことが起こるか、少しだけ寄り道して考えてみましょう。IDのような離散的な値を扱うときによく使われる伝統的な方法の一つに、「One-hot表現 (ワンホットひょうげん)」というものがあります。

これは、例えば私たちの手元にある辞書(これを「語彙 (ごい)」と呼びます)に登録されている単語や文字の種類が全部で1000種類あったとしたら、それぞれの単語や文字を、長さが1000の長ーいベクトル(数値の列のことでしたね)で表現する方法です。そして、そのベクトルの中で、表現したい単語や文字に対応する場所だけを「1」にして、残りの999個の場所は全部「0」にする、という、とってもシンプルなやり方なんですね。

ちょっと図で見てみましょうか。


  仮の語彙 (Vocabulary):
  ID: 0 => "<PAD>" (パディング用トークン)
  ID: 1 => "熱"
  ID: 2 => "頭"
  ID: 3 => "痛"
  ID: 4 => "咳"
  ID: 5 => "薬"
  (語彙サイズは 6 とします)

  例えば、「熱」(ID=1) をOne-hot表現にすると:
  [ 0, 1, 0, 0, 0, 0 ]
    ↑ ID 0 (<PAD>) の位置
      ↑ ID 1 ("熱") の位置 (ここだけが1!)
        ↑ ID 2 ("頭") の位置
          ... という具合です。

  同様に、「痛」(ID=3) をOne-hot表現にすると:
  [ 0, 0, 0, 1, 0, 0 ] (ID 3 の位置だけが1)

  図9: One-hot表現のイメージ
  解説: 各単語(または文字)は、語彙に含まれる総単語数(語彙サイズ)と同じ長さの
        ベクトルで表現されます。その単語自身のIDに対応する位置の要素だけが1になり、
        他のすべての要素は0となります。非常にシンプルで直感的ですね。

このOne-hot表現、考え方はとても分かりやすいんですが、実際に使おうとすると、いくつか頭の痛い問題が出てきてしまうんです。

  1. 次元がものすごく大きくなっちゃう問題(高次元性・スパース性): もし、私たちの語彙数が数万、あるいは医学専門用語まで含めると数百万(実際の医療現場で使われる言葉や、一般的な日本語の単語を考えると、あっという間にこのくらいの数になってしまいます)にもなると、このOne-hotベクトルの長さも、とんでもなく長くなってしまいますよね。しかも、そのとてつもなく長いベクトルの中で、「1」という値を持つのはたった一つの要素だけで、残りは全部「0」…。なんだか、ものすごくスカスカで、無駄が多い感じがしませんか?このような状態を「高次元でスパース(疎:まばら、という意味です)な表現」と呼びます。これは、コンピュータで計算する上でも効率が悪いですし、たくさんのメモリも必要になってしまう、という困った性質を持っています。
  2. 言葉の「意味の近さ」が全く分からない問題(意味的類似性の欠如): そして、もっと深刻かもしれないのが、このOne-hot表現だと、単語同士の「意味の近さ」という、私たち人間にとっては当たり前の感覚が、全く表現できないことです。例えば、「医師」と「看護師」は、どちらも医療従事者であり、意味的にも近い言葉ですよね。また、「頭痛」と「腹痛」も、部位は違えど、どちらも「痛み」という共通の概念を表す言葉です。でも、One-hot表現の世界では、これらの意味的に近い単語ペアのベクトル同士の関連性(例えば、ベクトル間の距離を計算したり、内積を取ったりしても)は、例えば「医師」と「リンゴ」のような、全く無関係に見える単語ペアとの関連性と、数学的には区別がつかないんです。これでは、AIが言葉のニュアンスを細かく理解したり、文脈を読んだりする上で、大きな壁になってしまいます。

「うーん、それは困ったな…」と思われたかもしれません。でも、ご安心ください!

救世主登場! 埋め込み層 (Embedding Layer) とは?

こうしたOne-hot表現が抱えるジレンマを、それはもう華麗に解決してくれるのが、いよいよ本日の主役、「埋め込み層 (Embedding Layer)」なんです。PyTorchを使う私たちにとっては、torch.nn.Embedding という名前で、とても簡単に、そして便利に使えるように用意されています。

この埋め込み層の役割は、一言でいうと、単語や文字といったID(One-hot表現のような離散的な記号ですね)を、もっと低次元(つまり、ベクトルの長さがずっと短い)で、しかも意味情報がギュギュッと詰まった密なベクトル(これを「分散表現 (Distributed Representation)」とか、特に単語の場合は「単語埋め込み (Word Embedding)」と呼んだりします)に賢く変換してくれる、まるで魔法のような仕組みなんです。

埋め込み層のすごいところ ~ただ短くするだけじゃない!~

この埋め込み層、一体何がそんなにすごいのでしょうか?主な役割を2つご紹介しましょう。

  1. ベクトルの長さをギュッと短縮!(次元削減): 先ほどのOne-hot表現だと、語彙数が100万ならベクトルの長さも100万次元…なんてことになっていましたが、埋め込み層を使うと、これを例えば100次元とか300次元とか、ずっと扱いやすい長さに圧縮してくれます。これで、計算効率もメモリ使用量も大幅に改善されるわけです。でも、ただ短くするだけじゃないんですよ。
  2. 言葉に「意味」の化粧を施す!(意味的特徴の獲得): ここが埋め込み層の一番すごいところ、そしてAIが言葉を「理解」する上で非常に重要なポイントなんですが、AIがたくさんの文章(テキストデータ)を学習していく過程で、この埋め込みベクトル自体が「賢く」なっていくんです。どういうことかと言うと、意味が近い単語同士は、この新しい、より短いベクトルたちが存在する空間(埋め込み空間と言います)の中で、自然とお互いに近くの場所に配置されるようになるんですね。 例えば、AIがたくさん医療関連の文章を学習した結果、「医師」という単語を表す埋め込みベクトルと、「看護師」という単語を表す埋め込みベクトルは、数学的に計算すると「近い」位置関係になるように調整されていきます。同様に、「頭痛」と「腹痛」の埋め込みベクトルも、互いに近い存在になるでしょう。一方で、「医師」のベクトルと「リンゴ」のベクトルは、意味が遠いので、埋め込み空間の中でも遠く離れた場所に配置されるようになる、というわけです。これって、なんだかすごいことだと思いませんか? これによって、AIは単語の表面的な文字列(例えば「い」「し」という文字の並び)だけでなく、その背後にある「医療の専門家」といった『意味』や『概念』を、数値のベクトルとして捉えることができるようになる、と考えられています。これが、AIがより人間らしく言葉を操るための、大きな一歩になるんですね。

埋め込み層の舞台裏 ~ルックアップテーブルという名の魔法の対応表~

では、この魔法のような「意味を込めたベクトルへの変換」は、一体どんな仕組みで行われているのでしょうか?実は、nn.Embedding層の内部には、大きな「対応表」のようなものがある、とイメージしてみてください。この表は、専門的な言葉では「ルックアップテーブル (lookup table)」とか「重み行列 (weight matrix)」と呼ばれたりします。

この「対応表」の行数は、私たちが扱う語彙の総数(例えば、私たちの辞書に登録されている単語や文字が全部で10000種類あれば、10000行になります)と同じです。そして、列数は、私たちが「各単語(や文字)を何次元のベクトルで表現したいか」という、埋め込みベクトルの次元数(例えば、これを300次元にしたければ、300列になります)と同じです。つまり、この対応表全体は、(語彙サイズ × 埋め込み次元数)というサイズの行列(数値がたくさん並んだ表)になっているわけです。

そして、この表の各行には、それぞれの単語(や文字)に対応する「埋め込みベクトル」そのものが、あらかじめ(最初はランダムな値かもしれませんが)書き込まれています。

ちょっと図で見てみましょう。

<pre><code>  入力されるもの: 単語(または文字)のID
  例えば、「熱」という文字のIDが「2」だったとします。

  埋め込み層の中にあるもの: ルックアップテーブル(重み行列)
  形状: (語彙サイズ, 埋め込み次元数)
  -------------------------------------------------------------------
  | ID (行番号) | 埋め込みベクトル (各行が1つのベクトル)            |
  -------------------------------------------------------------------
  | ID 0 (<PAD>) | [v0_1, v0_2, v0_3, ..., v0_embedding_dim]        | ← パディング用IDのベクトル
  -------------------------------------------------------------------
  | ID 1 (<UNK>) | [v1_1, v1_2, v1_3, ..., v1_embedding_dim]        | ← 未知語用IDのベクトル
  -------------------------------------------------------------------
  | ID 2 ("熱")  | [v2_1, v2_2, v2_3, ..., v2_embedding_dim]        | ← これが「熱」の埋め込みベクトル!
  -------------------------------------------------------------------
  | ID 3 ("頭")  | [v3_1, v3_2, v3_3, ..., v3_embedding_dim]        |
  -------------------------------------------------------------------
  | ...          | ...                                             |
  -------------------------------------------------------------------
  | ID (N-1)     | [v(N-1)_1, v(N-1)_2, ..., v(N-1)_embedding_dim]  | ← 語彙の最後の単語のベクトル
  -------------------------------------------------------------------
  (N = 語彙サイズ)

  出力されるもの: 対応する埋め込みベクトル
  ID「2」が入力されると、ルックアップテーブルの「ID 2」の行にあるベクトル
  [v2_1, v2_2, v2_3, ..., v2_embedding_dim] が取り出されて出力されます。

  図10: 埋め込み層の仕組み(ルックアップテーブル方式)
  解説: 埋め込み層は、内部に「語彙サイズ × 埋め込み次元数」のサイズの
        大きな数値の表(重み行列、またはルックアップテーブル)を持っています。
        入力として単語(や文字)のIDが与えられると、そのIDを行番号として、
        表から対応する行のベクトルを「見つけて取り出す(lookup)」操作を行います。
        この取り出されたベクトルが、その単語の埋め込み表現(意味が込められたベクトル)となります。
        そして、この表の中身の数値(重み)は、AIモデルが学習データから学習する中で、
        徐々に「より良い」値へと更新されていくのです。
</code></pre>

入力として単語のID(例えば「熱」という文字のIDが「2」だったとします)がこの埋め込み層にやってくると、層はとってもシンプルに、この大きな表の「IDが2の行」に書かれているベクトル(数値の列ですね)をそのまま取り出して、それを「『熱』という文字の埋め込みベクトルですよ」と出力します。まるで、辞書で単語を引くようなイメージでしょうか。

そして、ここがミソなのですが、この大きな表(重み行列)の中身の数値は、最初はランダムな値で始まっても(あるいは事前に学習された値を使うこともあります)、AIがたくさんのデータで学習を進めていくうちに、AIモデルの他の部品(専門用語ではパラメータと言います)と一緒に、勾配降下法という最適化の仕組みによって、徐々に「より良い」値に更新されていくんです。その「より良い」値というのが、まさに先ほどお話ししたように、意味の近い単語同士がベクトル空間で近くに配置されるような、そんな値になっていく、というわけなんですね。なんだか、AIが自分で辞書の意味を書き換えて賢くなっていくみたいで、面白いですよね。

医療への応用を考えると… ワクワクしませんか?

この埋め込み層が持つ、言葉に豊かな意味表現を与える力は、医療分野のテキストデータを扱う上で、本当に大きな可能性を秘めていると思います。

例えば、電子カルテや医学論文には、たくさんの専門用語が出てきますよね。「インフリキシマブ」と「アダリムマブ」は、どちらも「抗TNFα抗体製剤」という同じグループに属するお薬で、似たような作用機序を持っています。もしAIが、これらの専門用語の埋め込みベクトルを、学習を通じてベクトル空間の近い場所にマッピング(配置)できれば、例えば、ある薬剤と相互作用を起こしやすい別の薬剤を予測したり、副作用の報告を分析して未知の副作用のパターンを見つけ出したりする、といったタスクの精度がぐっと上がるかもしれません (19, 20)。

遺伝子の名前や疾患名についても同じことが言えます。例えば、「BRCA1」という遺伝子と「BRCA2」という遺伝子は、どちらも乳がんや卵巣がんの発症リスクに関連が深いことで知られています。これらの遺伝子名が持つ意味的な特徴や、他の遺伝子との関連性を、数値のベクトルとして捉えることができれば、ゲノム医療における個々の遺伝子の機能予測や、精密医療(プレシジョン・メディシン)における患者さん一人ひとりの遺伝的背景に合わせた最適な治療法を選択するといった、非常に先進的な研究開発に不可欠な基盤技術になるはずです。

言葉で説明すると少し長くなってしまいましたが、実際にPyTorchのコードで nn.Embedding を使ってみると、そのシンプルさと便利さがよく分かると思います。早速、試してみましょうか。

Pythonコード例:nn.Embedding の基本的な使い方

graph TD;
    A["1. 語彙(vocab_jp)の定義
- 文字/トークンと整数IDの対応付け
- 特殊トークン(例: <PAD>, <UNK>)も定義"] --> B; B["2. 埋め込み層パラメータの設定
- 語彙サイズ (vocab_size = len(vocab_jp))
- 埋め込みベクトルの次元数 (embedding_dim)"] --> C; C["3. nn.Embedding層の初期化/インスタンス化
embedding_layer = nn.Embedding(num_embeddings=vocab_size, embedding_dim=embedding_dim, padding_idx=vocab_jp['<PAD>'])
(パディング用IDも指定)"] --> D; D["4. 埋め込み対象のID系列を準備
(例: input_ids_list)
- 各要素はIDのリスト
- 必要に応じてパディング済み"] --> E; E["5. ID系列を整数型PyTorchテンソルに変換
input_ids_tensor = torch.tensor(input_ids_list, dtype=torch.long)
(形状例: バッチサイズ, 系列長)"] --> F; F["6. IDテンソルを初期化した埋め込み層に入力
embedded_vectors = embedding_layer(input_ids_tensor)"] --> G; G["出力: 埋め込みベクトル
(embedded_vectors)
形状: (バッチサイズ, 系列長, 埋め込み次元数)"];

import torch # PyTorchのメインライブラリ。テンソル計算やニューラルネットワークの部品が詰まっています。
import torch.nn as nn # nnモジュールはニューラルネットワークを構築するための部品(層や関数など)を提供します。

# --- 埋め込み層を使うための準備をしましょう ---
# まず、ごく簡単な日本語の「語彙(ボキャブラリー)」を文字ベースで定義してみます。
# 実際の応用では、もっと大きな語彙を学習データから作ることが多いです。
# ここでは、特殊な文字として2つ用意します。
# "<PAD>": パディング(長さを揃えるための穴埋め)に使う文字(トークン)。
# "<UNK>": Unknownの略で、辞書にない未知の文字が出てきたときに使う文字(トークン)。
vocab_jp = { # Pythonの辞書型を使って、文字とID(整数)を対応付けます。
    "<PAD>": 0, # パディングトークンにはID 0を割り当てます。
    "<UNK>": 1, # 未知語トークンにはID 1を割り当てます。
    "熱": 2,    # 以下、実際の文字にIDを振っていきます。
    "頭": 3,
    "痛": 4,
    "咳": 5,
    "薬": 6
}
vocab_size = len(vocab_jp) # 語彙の総数を計算します。この場合は7ですね。
embedding_dim = 3          # 各文字を「何次元のベクトル」に変換(埋め込み)したいか指定します。
                           # ここでは練習なので、ごく小さな3次元にしてみましょう。
                           # 実際には、数十~数百次元くらいがよく使われます。

# これで準備ができたので、いよいよ埋め込み層の「実物(インスタンス)」を作ります。
# nn.Embedding() という命令を使います。
# 引数として、主に以下のものを指定します。
#   num_embeddings: 語彙の総数(つまり、いくつの異なるIDを扱うか)。
#   embedding_dim: 変換先のベクトルの次元数。
#   padding_idx: もしパディング用IDがあれば、それを指定します。
#                指定すると、そのIDに対応するベクトルは学習中に更新されず、
#                通常はゼロベクトル(全ての要素が0のベクトル)として扱われることが多いです。
embedding_layer = nn.Embedding(num_embeddings=vocab_size,    # 私たちの語彙サイズを指定します。
                               embedding_dim=embedding_dim,  # 3次元のベクトルに変換してね、と指定。
                               padding_idx=vocab_jp["<PAD>"]) # ID 0 (<PAD>) はパディング用だよ、と教えます。

print(f"語彙サイズ (num_embeddings): {vocab_size}") # 設定した語彙サイズを確認します。
print(f"埋め込みベクトルの次元数 (embedding_dim): {embedding_dim}") # 設定した埋め込み次元数を確認します。
print(f"パディングID (padding_idx): {vocab_jp['<PAD>']}") # 設定したパディングIDを確認します。

# 埋め込み層が内部に持っている「重み(ルックアップテーブル)」を確認してみましょう。
# .weight という属性でアクセスできます。
# この重み行列の形状は (語彙サイズ, 埋め込み次元数) になっているはずです。
# 初期状態では、通常、ランダムな値で初期化されています(ただし、padding_idxの行は除く)。
print("埋め込み層の重み (学習前):\n", embedding_layer.weight) # 重み行列の中身を見てみます。

# --- 実際に埋め込み層を使ってみましょう ---
# まず、埋め込み層に入力するための、文字IDのテンソル(数値の列の集まり)を用意します。
# 通常、複数の系列データをまとめて処理する(バッチ処理)ので、
# 入力テンソルは (バッチサイズ, 系列長) という形になることが多いです。

# 例として、いくつかの「単語」(実際には文字のID列)をリストとして作ってみます。
# 「熱」 (ID:2)
# 「頭痛」 (ID:[3,4])
# 「咳<UNK>薬」 (ID:[5, vocab_jp["<UNK>"], 6]) - ここでは仮に「と」のような接続詞が未知語<UNK>だったとします。
input_ids_list = [ # Pythonのリストのリストとして用意します。
    [vocab_jp["熱"], vocab_jp["<PAD>"], vocab_jp["<PAD>"]],  # 「熱」を表現。長さ3に揃えるためパディング。
    [vocab_jp["頭"], vocab_jp["痛"], vocab_jp["<PAD>"]],     # 「頭痛」を表現。同様にパディング。
    [vocab_jp["咳"], vocab_jp["<UNK>"], vocab_jp["薬"]]       # 「咳<UNK>薬」を表現。これは長さ3なのでパディングなし。
]
# このID列のリストを、PyTorchのテンソルに変換します。
# nn.Embedding に入力するIDは整数型である必要があるので、dtype=torch.long を指定します。
input_ids_tensor = torch.tensor(input_ids_list, dtype=torch.long) # リストをテンソルに変換!
print("\n入力IDテンソル (input_ids_tensor):\n", input_ids_tensor) # 作成した入力テンソルを確認します。
print("入力IDテンソルの形状:", input_ids_tensor.shape) # 形状も確認。(バッチサイズ3, 系列長3) になっているはず。

# さあ、このIDテンソルを埋め込み層に入力してみましょう!
# 使い方はとっても簡単で、作った embedding_layer に input_ids_tensor を渡すだけです。
embedded_vectors = embedding_layer(input_ids_tensor) # 埋め込み処理を実行!
print("\n埋め込み後のベクトル (embedded_vectors):\n", embedded_vectors) # 結果のベクトルを見てみましょう。
# 出力される embedded_vectors の形状は、(バッチサイズ, 系列長, 埋め込み次元数) になります。
# 今回の例だと、(3, 3, 3) という形の3次元テンソルが出てくるはずです。
print("埋め込み後ベクトルの形状:", embedded_vectors.shape) # 形状を確認。

# 具体的に、1番目のサンプル(「熱」という文字)の、0番目の要素(つまり「熱」そのもの)の
# 埋め込みベクトルがどうなっているか、取り出して見てみましょう。
# embedded_vectors[0, 0, :] は、0番目のバッチの、0番目の系列要素の、全ての埋め込み次元、という意味です。
print("\n「熱」の埋め込みベクトル (学習前):\n", embedded_vectors[0, 0, :]) # 特定の文字の埋め込みベクトルを表示します。
# このベクトルは、実は embedding_layer.weight の vocab_jp["熱"] (=2) 番目の行のベクトルと同じ値になっているはずです。
# つまり、ルックアップテーブルから対応する行を引っ張ってきた、ということですね。

# 最後に、パディングID (<PAD>) の埋め込みベクトルも確認しておきましょう。
# padding_idx を指定して embedding_layer を作った場合、
# このIDに対応するベクトルは、通常、全ての要素が0のゼロベクトルになるか、
# あるいは学習中に値が更新されないように扱われます。
pad_id_tensor = torch.tensor([vocab_jp["<PAD>"]], dtype=torch.long) # <PAD>のID (0) だけを持つテンソルを作ります。
pad_vector = embedding_layer(pad_id_tensor) # これを埋め込み層に入力します。
print("\n<PAD> IDの埋め込みベクトル (学習前):\n", pad_vector) # 結果を見てみましょう。

# === ここから下が上記のprint文による実際の出力の例 (埋め込み層の重みは乱数初期化されるため、実行毎に値が変わります) ===
# 語彙サイズ (num_embeddings): 7
# 埋め込みベクトルの次元数 (embedding_dim): 3
# パディングID (padding_idx): 0
# 埋め込み層の重み (学習前):
#  Parameter containing:
# tensor([[ 0.0000,  0.0000,  0.0000],  # ID 0 (<PAD>) のベクトル (padding_idx指定のため、通常は0で初期化されるか、学習されない)
#          [ 0.1234, -0.5678,  0.9012],  # ID 1 (<UNK>) のベクトル (乱数値の例)
#          [-0.4321,  0.8765, -0.2109],  # ID 2 (熱) のベクトル (乱数値の例)
#          [ 0.5555, -0.4444,  0.3333],  # ID 3 (頭) のベクトル (乱数値の例)
#          [-0.9876,  0.1230, -0.6543],  # ID 4 (痛) のベクトル (乱数値の例)
#          [ 0.0101, -0.2020,  0.3030],  # ID 5 (咳) のベクトル (乱数値の例)
#          [-0.7070,  0.6060, -0.5050]], requires_grad=True) # ID 6 (薬) のベクトル (乱数値の例)。requires_grad=Trueは学習対象であることを示す。
#
# 入力IDテンソル (input_ids_tensor):
#  tensor([[2, 0, 0],
#          [3, 4, 0],
#          [5, 1, 6]])
# 入力IDテンソルの形状: torch.Size([3, 3])
#
# 埋め込み後のベクトル (embedded_vectors):
#  tensor([[[-0.4321,  0.8765, -0.2109],  # 「熱」のベクトル
#           [ 0.0000,  0.0000,  0.0000],  # 「<PAD>」のベクトル
#           [ 0.0000,  0.0000,  0.0000]], # 「<PAD>」のベクトル
#
#          [[ 0.5555, -0.4444,  0.3333],  # 「頭」のベクトル
#           [-0.9876,  0.1230, -0.6543],  # 「痛」のベクトル
#           [ 0.0000,  0.0000,  0.0000]], # 「<PAD>」のベクトル
#
#          [[ 0.0101, -0.2020,  0.3030],  # 「咳」のベクトル
#           [ 0.1234, -0.5678,  0.9012],  # 「<UNK>」のベクトル
#           [-0.7070,  0.6060, -0.5050]]], # 「薬」のベクトル
#         grad_fn=<EmbeddingBackward0>) # grad_fnは自動微分に関する情報で、今は気にしなくて大丈夫です。
# 埋め込み後ベクトルの形状: torch.Size([3, 3, 3])
#
# 「熱」の埋め込みベクトル (学習前):
#  tensor([-0.4321,  0.8765, -0.2109], grad_fn=<SliceBackward0>)
#
# <PAD> IDの埋め込みベクトル (学習前):
#  tensor([[0., 0., 0.]], grad_fn=<EmbeddingBackward0>)
# (注: `padding_idx` を指定すると、対応する重みベクトルの初期値が0になり、かつ学習中に更新されなくなるのが一般的です。
#  上記の乱数値の例ではID0の行も0.0000として示していますが、PyTorchのバージョンや初期化の厳密な挙動により、
#  必ずしも初期値が完全に0であるとは限りません。重要なのは、`padding_idx`で指定したIDのベクトルは学習の対象外となるという点です。)

どうでしたか?たった数行のコードで、文字のIDを、何だか意味ありげな(今はまだランダムですが)数値のベクトルに変換できましたよね。もちろん、この例ではまだAIモデル全体を学習させていないので、これらのベクトルの値自体に深い「意味」は込められていません。でも、この「埋め込みベクトル」という形で言葉を表現することが、AIが言葉のニュアンスや文脈を捉え、より高度なタスク(例えば文章の意味を理解したり、質問に答えたり、翻訳したり)をこなすための、非常に重要な出発点になるんだな、という雰囲気は掴んでいただけたのではないでしょうか。

この埋め込み層は、この後の章でより詳しく見ていくことになるRNN(リカレントニューラルネットワーク)やTransformer(トランスフォーマー)といった、より高度な系列データ処理モデルの、まさに「入り口」として、なくてはならない役割を果たすことになります。ぜひ、この「IDを意味のあるベクトルに変換する」という感覚を、頭の片隅に置いておいてくださいね。

まとめと、ここから始まる新たな一歩

いやー、第1章、本当にお疲れ様でした!ここまで一緒に旅をしてくださって、ありがとうございます。振り返ってみると、結構盛りだくさんな内容でしたよね。

「系列データって、一体どんなものなんだろう?」という素朴な疑問から始まり、私たちの身近な医療現場にあふれる具体的な例――例えば、心電図の波形のような「時系列データ」、電子カルテに綴られる「テキストデータ」、さらには患者さんの声や検査時の映像といった「音声データ」や「動画データ」――に触れながら、それぞれのデータが持つ「順番」という名の個性を探求してきました。

そして、これらのデータをAIの力で分析することで、一体どんなことができるのか、「文字列を新しく生み出すタスク」、「未来を予測するタスク」、そして「データを分類するタスク」といった、AIが得意とする代表的なお仕事についてもご紹介しましたね。なんだか、AIにできることの幅広さに、少しワクワクしていただけたのではないでしょうか。

後半では、いよいよ実践に向けて、これらの系列データをAIが「理解」できるようにするための下ごしらえの方法を、PyTorchという強力なツールを使いながら見てきました。数値の集まりである「テンソル」という形でデータを表現することから始まり、長さがバラバラなデータを扱うための「パディング」と「マスキング」という工夫、たくさんのデータを効率よくAIに供給するための「Dataset」と「DataLoader」という便利な仕組み、そして特にテキストデータを扱う上で言葉に豊かな「意味」を与えるための「埋め込み層(nn.Embedding)」…。一つ一つは細かいテクニックかもしれませんが、これらが組み合わさって、AIが賢く学習するための土台が作られていくんですね。

皆さんが日々向き合っている医療の現場を、もう一度思い浮かべてみてください。そこには、患者さん一人ひとりの言葉、検査で得られる数値の推移、内視鏡やエコーの映像…本当に多種多様な「系列データ」が、まるでまだ見ぬ発見を秘めた宝の山のように、存在しているのではないでしょうか。

もし、AIがこれらのデータの中に隠された、人間ではなかなか気づきにくい微細なパターンや、複雑な関連性を見つけ出すことができたとしたら…。それは、もしかすると診断がもっと早く、もっと正確になったり、一人ひとりの患者さんにピッタリ合ったオーダーメイドの治療法が見つかったり、あるいはこれまで謎に包まれていた病気の新しいメカニズムが解き明かされたり…そんな未来に繋がっているかもしれない、と考えると、なんだかものすごい可能性を感じませんか?

ここまで一緒に学んできたことで、皆さんは、こうしたAIモデルに大切なデータを与えるための「下ごしらえ」の技術、その本当に重要な第一歩を、確かに踏み出せたはずです。これは、AIを使って何か新しい価値を生み出すための、とっても大切な一歩なんですよ。ぜひ、ご自身に「よく頑張ったね!」と声をかけてあげてください。

さて、美味しい料理を作るための材料と下ごしらえは、これでだいぶ整ってきました。次はいよいよ、この系列データを専門的に調理するためのAIモデルの主役の一人、「再帰型ニューラルネット(RNN)」の登場です!このRNNが、系列データの命とも言える「順番」という情報を、一体どんな clever な仕組みで捉え、活用していくのか。その基本的な考え方から、実際にPyTorchを使って皆さんの手でRNNを組み立てていくところまで、また一緒にじっくりと探求していきましょう。

この第1章で皆さんが築き上げた知識と経験という土台があれば、きっと次のステップもスムーズに進んでいけるはずです。次回の「第2章:再帰型ニューラルネット(RNN)の理論とPyTorch実装」も、どうぞお楽しみに!

参考文献

  1. Topol EJ. High-performance medicine: the convergence of human and artificial intelligence. Nat Med. 2019 Jan;25(1):44-56.
  2. Esteva A, Robicquet A, Ramsundar B, Kuleshov V, DePristo M, Chou K, et al. A guide to deep learning in healthcare. Nat Med. 2019 Jan;25(1):24-29.
  3. Hannun AY, Rajpurkar P, Haghpanahi M, Tison GH, Bourn C, Turakhia MP, et al. Cardiologist-level arrhythmia detection and classification in ambulatory electrocardiograms using a deep neural network. Nat Med. 2019 Jan;25(1):65-69.
  4. Roy Y, Banville H, Albuquerque I, Gramfort A, Falk TH, Faubert J. Deep learning-based electroencephalography analysis: a systematic review. J Neural Eng. 2019 Oct;16(5):051001.
  5. Kwon JM, Lee Y, Lee Y, Lee S, Park J. An Algorithm Based on Deep Learning for Predicting In-Hospital Cardiac Arrest. J Am Heart Assoc. 2018 Jul;7(13):e008678.
  6. Shickel B, Loftus TJ, Adhikari L, Ozrazgat-Baslanti T, Bihorac A, Rashidi P. DeepSOFA: A Continuous Acuity Score for Critically Ill Patients Using Clinically Interpretable Deep Learning. Sci Rep. 2019 Feb;9(1):1879.
  7. Chambres G, Hanna P, Des αποτελέσματα, J, Chahine G, El Helou M. A systematic review of machine learning in auscultation. J Pers Med. 2023 Jan;13(1):109.
  8. Orozco-Arroyave JR, Hönig F, Arias-Londoño JD, Vargas-Bonilla JF, Daqrouq K, Skodda S, et al. Automatic detection of Parkinson’s disease in running speech spoken in three different languages. J Acoust Soc Am. 2016 Mar;139(3):EL56.
  9. Hashimoto DA, Rosman G, Witkowski ER, Stafford C, Navarette-Welton AJ, Rattner DW, et al. Computer Vision in Surgery: From Potential to Clinical Value. Ann Surg. 2020 Jul;272(1):64-71.
  10. Al-Antari MA, Han SM, Kim TS. Evaluation of deep learning approaches for the detection of breast cancer in mammography and ultrasonography: a review. J Med Imaging Health Inform. 2020 Feb;10(2):307-326.
  11. Segler MHS, Kogej T, Tyrchan C, Waller MP. Generating Focused Molecule Libraries for Drug Discovery with Recurrent Neural Networks. ACS Cent Sci. 2018 Jan;4(1):120-131.
  12. Nemati S, Holder A, Razmi F, Stanley MD, Clifford GD, Buchman TG. An Interpretable Machine Learning Model for Accurate Prediction of Sepsis in the ICU. Crit Care Med. 2018 Apr;46(4):547-553.
  13. Gu Y, Chen Y, Li X, Li K, Zhu Z. A review of a data-driven-based methodology for an influenza forecast. Front Public Health. 2023 Jan;10:1060686.
  14. Ribeiro AH, Ribeiro MH, Paixão GMM, Oliveira DM, Gomes PR, Canazart JA, et al. Automatic diagnosis of the 12-lead ECG using a deep neural network. Nat Commun. 2020 Apr;11(1):1760.
  15. Mullenbach J, Wiegreffe S, Duke J, Sun J, Eisenstein J. Explainable Prediction of Medical Codes from Clinical Text. Proc Conf AAAI Artif Intell. 2018 Apr;2018:3701-3710.
  16. Cummins N, Scherer S, Krajewski J, Schnieder S, Epps J, Quatieri TF. A review of depression and suicide risk assessment using speech analysis. Speech Commun. 2015 Jul;71:10-49.
  17. Paszke A, Gross S, Massa F, Lerer A, Bradbury J, Chanan G, et al. PyTorch: An Imperative Style, High-Performance Deep Learning Library. Adv Neural Inf Process Syst. 2019;32.
  18. Devlin J, Chang MW, Lee K, Toutanova K. BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding. Proc North Am Chapter Assoc Comput Linguist Hum Lang Technol Conf. 2019 Jun;1:4171-4186.
  19. Beam AL, Kompa B, Schmaltz A, Fried I, Weber G, Palmer N, et al. Clinical Concept Embeddings Learned from Large-Scale Electronic Health Records. Pac Symp Biocomput. 2019;24:290-301.
  20. Choi E, Bahadori MT, Searles E, Coffey C, Thompson M, Bost J, et al. Multi-layer Representation Learning for Medical Concepts. Proc ACM SIGKDD Int Conf Knowl Discov Data Min. 2016 Aug;2016:1495-1504.

ご利用規約(免責事項)

当サイト(以下「本サイト」といいます)をご利用になる前に、本ご利用規約(以下「本規約」といいます)をよくお読みください。本サイトを利用された時点で、利用者は本規約の全ての条項に同意したものとみなします。

第1条(目的と情報の性質)

  1. 本サイトは、医療分野におけるAI技術に関する一般的な情報提供および技術的な学習機会の提供を唯一の目的とします。
  2. 本サイトで提供されるすべてのコンテンツ(文章、図表、コード、データセットの紹介等を含みますが、これらに限定されません)は、一般的な学習参考用であり、いかなる場合も医学的な助言、診断、治療、またはこれらに準ずる行為(以下「医行為等」といいます)を提供するものではありません。
  3. 本サイトのコンテンツは、特定の製品、技術、または治療法の有効性、安全性を保証、推奨、または広告・販売促進するものではありません。紹介する技術には研究開発段階のものが含まれており、その臨床応用には、さらなる研究と国内外の規制当局による正式な承認が別途必要です。
  4. 本サイトは、情報提供を目的としたものであり、特定の治療法を推奨するものではありません。健康に関するご懸念やご相談は、必ず専門の医療機関にご相談ください。

第2条(法令等の遵守)
利用者は、本サイトの利用にあたり、医師法、医薬品、医療機器等の品質、有効性及び安全性の確保等に関する法律(薬機法)、個人情報の保護に関する法律、医療法、医療広告ガイドライン、その他関連する国内外の全ての法令、条例、規則、および各省庁・学会等が定める最新のガイドライン等を、自らの責任において遵守するものとします。これらの適用判断についても、利用者が自ら関係各所に確認するものとし、本サイトは一切の責任を負いません。

第3条(医療行為における責任)

  1. 本サイトで紹介するAI技術・手法は、あくまで研究段階の技術的解説であり、実際の臨床現場での診断・治療を代替、補助、または推奨するものでは一切ありません。
  2. 医行為等に関する最終的な判断、決定、およびそれに伴う一切の責任は、必ず法律上その資格を認められた医療専門家(医師、歯科医師等)が負うものとします。AIによる出力を、資格を有する専門家による独立した検証および判断を経ずに利用することを固く禁じます。
  3. 本サイトの情報に基づくいかなる行為によって利用者または第三者に損害が生じた場合も、本サイト運営者は一切の責任を負いません。実際の臨床判断に際しては、必ず担当の医療専門家にご相談ください。本サイトの利用によって、利用者と本サイト運営者の間に、医師と患者の関係、またはその他いかなる専門的な関係も成立するものではありません。

第4条(情報の正確性・完全性・有用性)

  1. 本サイトは、掲載する情報(数値、事例、ソースコード、ライブラリのバージョン等)の正確性、完全性、網羅性、有用性、特定目的への適合性、その他一切の事項について、何ら保証するものではありません。
  2. 掲載情報は執筆時点のものであり、予告なく変更または削除されることがあります。また、技術の進展、ライブラリの更新等により、情報は古くなる可能性があります。利用者は、必ず自身で公式ドキュメント等の最新情報を確認し、自らの責任で情報を利用するものとします。

第5条(AI生成コンテンツに関する注意事項)
本サイトのコンテンツには、AIによる提案を基に作成された部分が含まれる場合がありますが、公開にあたっては人間による監修・編集を経ています。利用者が生成AI等を用いる際は、ハルシネーション(事実に基づかない情報の生成)やバイアスのリスクが内在することを十分に理解し、その出力を鵜呑みにすることなく、必ず専門家による検証を行うものとします。

第6条(知的財産権)

  1. 本サイトを構成するすべてのコンテンツに関する著作権、商標権、その他一切の知的財産権は、本サイト運営者または正当な権利を有する第三者に帰属します。
  2. 本サイトのコンテンツを引用、転載、複製、改変、その他の二次利用を行う場合は、著作権法その他関連法規を遵守し、必ず出典を明記するとともに、権利者の許諾を得るなど、適切な手続きを自らの責任で行うものとします。

第7条(プライバシー・倫理)
本サイトで紹介または言及されるデータセット等を利用する場合、利用者は当該データセットに付随するライセンス条件および研究倫理指針を厳格に遵守し、個人情報の匿名化や同意取得の確認など、適用される法規制に基づき必要とされるすべての措置を、自らの責任において講じるものとします。

第8条(利用環境)
本サイトで紹介するソースコードやライブラリは、執筆時点で特定のバージョンおよび実行環境(OS、ハードウェア、依存パッケージ等)を前提としています。利用者の環境における動作を保証するものではなく、互換性の問題等に起因するいかなる不利益・損害についても、本サイト運営者は責任を負いません。

第9条(免責事項)

  1. 本サイト運営者は、利用者が本サイトを利用したこと、または利用できなかったことによって生じる一切の損害(直接損害、間接損害、付随的損害、特別損害、懲罰的損害、逸失利益、データの消失、プログラムの毀損等を含みますが、これらに限定されません)について、その原因の如何を問わず、一切の法的責任を負わないものとします。
  2. 本サイトの利用は、学習および研究目的に限定されるものとし、それ以外の目的での利用はご遠慮ください。
  3. 本サイトの利用に関連して、利用者と第三者との間で紛争が生じた場合、利用者は自らの費用と責任においてこれを解決するものとし、本サイト運営者に一切の迷惑または損害を与えないものとします。
  4. 本サイト運営者は、いつでも予告なく本サイトの運営を中断、中止、または内容を変更できるものとし、これによって利用者に生じたいかなる損害についても責任を負いません。

第10条(規約の変更)
本サイト運営者は、必要と判断した場合、利用者の承諾を得ることなく、いつでも本規約を変更することができます。変更後の規約は、本サイト上に掲載された時点で効力を生じるものとし、利用者は変更後の規約に拘束されるものとします。

第11条(準拠法および合意管轄)
本規約の解釈にあたっては、日本法を準拠法とします。本サイトの利用および本規約に関連して生じる一切の紛争については、東京地方裁判所を第一審の専属的合意管轄裁判所とします。


For J³, may joy follow you.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

医師・医学博士・AI研究者・連続起業家
元厚生労働省幹部・ハーバード大学理学修士・ケンブリッジ大学MBA・コロンビア大学行政修士(経済)
岡山大学医学部卒業後、内科・地域医療に従事。厚生労働省で複数室長(医療情報・救急災害・国際展開等)を歴任し、内閣官房・内閣府・文部科学省でも医療政策に携わる。
退官後は、日本大手IT企業や英国VCで新規事業開発・投資を担当し、複数の医療スタートアップを創業。現在は医療AI・デジタル医療機器の開発に取り組むとともに、東京都港区で内科クリニックを開業。
複数大学で教授として教育・研究活動に従事し、医療関係者向け医療AIラボ「Medical AI Nexus」、医療メディア「The Health Choice | 健康の選択」を主宰。
ケンブリッジ大学Associate・社会医学系指導医・専門医・The Royal Society of Medicine Fellow

目次