Rustによる分散表現

著者: Asher Bond

ステップ1: 分散表現器のRustプロジェクトを立ち上げる

新しいRustプロジェクトを作成し、必要な依存関係を追加します。

cargo new nlu_code_generation
cd nlu_code_generation

Cargo.toml ファイルに以下の依存関係を追加します。

[dependencies]
ndarray = "0.15.4"
ndarray-rand = "0.13.0"
rand = "0.8.5"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

ステップ2: 入力処理を定義する

テキスト入力を扱い、それをベクトルへと変換するモジュールを作成しましょう。

新しいファイル src/input_processing.rs を作成します。

use ndarray::Array2;
use std::collections::HashMap;

pub struct TextProcessor {
 pub vocab: HashMap<String, usize>,
}

impl TextProcessor {
 pub fn new() -> Self {
 TextProcessor {
 vocab: HashMap::new(),
 }
 }

 pub fn build_vocab(&mut self, texts: &[&str]) {
 let mut index = 0;
 for &text in texts {
 for word in text.split_whitespace() {
 if !self.vocab.contains_key(word) {
 self.vocab.insert(word.to_string(), index);
 index += 1;
 }
 }
 }
 }

 pub fn text_to_vector(&self, text: &str) -> Array2<f32> {
 let mut vector = vec![0.0; self.vocab.len()];
 for word in text.split_whitespace() {
 if let Some(&index) = self.vocab.get(word) {
 vector[index] += 1.0;
 }
 }
 Array2::from_shape_vec((1, self.vocab.len()), vector).unwrap()
 }
}

ステップ3: 自己アテンション機構を強化する

自己アテンション機構をより現実的なものへと強化し、シンプルなフィードフォワードネットワークを組み込みます。

src/attention.rs を更新します。

use ndarray::{Array2, Axis};
use ndarray_rand::RandomExt;
use rand::distributions::Uniform;

pub struct SelfAttention {
 pub query: Array2<f32>,
 pub key: Array2<f32>,
 pub value: Array2<f32>,
 pub output_weight: Array2<f32>,
}

impl SelfAttention {
 pub fn new(dim: usize) -> Self {
 let distribution = Uniform::new(-0.1, 0.1);
 SelfAttention {
 query: Array2::random((dim, dim), distribution),
 key: Array2::random((dim, dim), distribution),
 value: Array2::random((dim, dim), distribution),
 output_weight: Array2::random((dim, dim), distribution),
 }
 }

 pub fn attention(&self, x: &Array2<f32>) -> Array2<f32> {
 let q = x.dot(&self.query);
 let k = x.dot(&self.key);
 let v = x.dot(&self.value);

 let scores = q.dot(&k.t()) / (x.shape()[1] as f32).sqrt();
 let weights = scores.mapv(|s| s.exp()) / scores.mapv(|s| s.exp()).sum_axis(Axis(1)).insert_axis(Axis(1));
 let context = weights.dot(&v);
 context.dot(&self.output_weight)
 }
}

ステップ4: 分散表現を管理する

より複雑なシナリオとシリアライゼーションを扱えるよう、分散表現マネージャを強化します。

src/distributed_representation.rs を更新します。

use ndarray::Array2;
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use std::fs::File;
use std::io::{Write, Read};

#[derive(Serialize, Deserialize)]
pub struct DistributedRepresenter {
 pub representations: HashMap<String, Array2<f32>>,
}

impl DistributedRepresenter {
 pub fn new() -> Self {
 DistributedRepresenter {
 representations: HashMap::new(),
 }
 }

 pub fn add_representation(&mut self, key: String, representation: Array2<f32>) {
 self.representations.insert(key, representation);
 }

 pub fn get_representation(&self, key: &str) -> Option<&Array2<f32>> {
 self.representations.get(key)
 }

 pub fn save(&self, path: &str) {
 let file = File::create(path).unwrap();
 let writer = &mut BufWriter::new(file);
 serde_json::to_writer(writer, &self).unwrap();
 }

 pub fn load(path: &str) -> Self {
 let mut file = File::open(path).unwrap();
 let mut data = String::new();
 file.read_to_string(&mut data).unwrap();
 serde_json::from_str(&data).unwrap()
 }
}

ステップ5: main.rs ですべてを統合する

src/main.rs を編集し、強化した各コンポーネントを用いてNLUコード生成のプロセスを実演します。

mod attention;
mod distributed_representation;
mod input_processing;

use attention::SelfAttention;
use distributed_representation::DistributedRepresenter;
use input_processing::TextProcessor;
use ndarray::Array2;

fn main() {
 // Initialize text processor and build vocabulary
 let mut text_processor = TextProcessor::new();
 let texts = vec!["hello world", "machine learning is fun", "rust is great for performance"];
 text_processor.build_vocab(&texts);

 // Process a new text input
 let input_text = "machine learning in rust";
 let input_vector = text_processor.text_to_vector(input_text);
 println!("Input Vector:\n{:?}", input_vector);

 // Initialize self-attention
 let dim = text_processor.vocab.len();
 let self_attention = SelfAttention::new(dim);
 
 // Compute attention
 let attention_output = self_attention.attention(&input_vector);
 println!("Attention Output:\n{:?}", attention_output);
 
 // Initialize distributed representer
 let mut representer = DistributedRepresenter::new();
 
 // Add representation
 representer.add_representation("example".to_string(), attention_output.clone());
 
 // Save representations
 representer.save("representations.json");

 // Load representations
 let loaded_representer = DistributedRepresenter::load("representations.json");
 
 // Retrieve and print representation
 if let Some(retrieved_representation) = loaded_representer.get_representation("example") {
 println!("Retrieved Representation:\n{:?}", retrieved_representation);
 } else {
 println!("Representation not found.");
 }
}

解説とテスト

  1. 入力処理(input_processing.rs):

    • シンプルなバッグ・オブ・ワーズモデルを用いて、テキスト入力をベクトル表現へと変換します。
    • 一連のテキストから語彙を構築し、その語彙に基づいて新しいテキストをベクトルへと変換します。
  2. 自己アテンション機構(attention.rs):

    • クエリ、キー、バリューの各行列を備えた自己アテンション機構を実装し、最終的なコンテキストベクトルを生成するための追加の出力重み行列を含みます。
  3. 分散表現の管理(distributed_representation.rs):

    • HashMap を用いて分散表現を管理し、表現の保存と読み込みのためのシリアライゼーションとデシリアライゼーションに対応します。
  4. 統合(main.rs):

    • テキスト入力の処理、自己アテンションの計算、分散表現の管理、そしてそれらの表現の永続化と読み込みという一連のワークフローを実演します。

コードの実行

実装をテストするには、以下のコマンドを実行します。

cargo run

これにより、入力ベクトル、アテンション出力、そして保存したファイルから取得した表現を示す出力が得られるはずです。このようなコードをさらにご覧になりたい方は、Distillative.AIのGitHubをチェックしてください: https://github.com/Distillative-AI

お問い合わせ

ご連絡は source@distillative.ai までお気軽にどうぞ。

関連記事: