WordNetをC#とPythonで扱う最短手順:wnとLMF(XML)直読、旧WNDB・YAMLまで徹底解説

Windows向けのWordNetアプリを入れたものの、「辞書データにプログラムから直接アクセスしたい」「C#でAPIのように扱いたい」という問い合わせは非常に多いテーマです。本稿では、WordNetの正体(アプリではなく辞書データ)を整理し、最短で動くPython版 wn、そしてC#での実装パターン(LMF XML直読・旧形式DB・RESTラッパー・YAML読取)まで、実用コードと設計判断基準をまとめます。

目次

WordNetは「アプリ」ではなく辞書データ——まずは流通形式を理解する

WordNet(英語語彙ネットワーク)は辞書(語彙・語義・語義間関係)のデータセットです。Windows向けGUIは単なるビューワーであり、プログラムから利用する本丸は「データ形式」と「それを扱うライブラリ」です。主要な系統と流通形式を押さえておくと、C#/Pythonでの実装判断が格段に楽になります。

系統代表版主な配布形式想定ツール/ライブラリライセンス(概要)備考
Princeton WordNet(PWN)3.0/3.1WNDB(ASCII、index.nounやdata.noun等)歴史的なC#ライブラリ(例:WordNet.Net系)、NLTK等独自のWordNetライセンス(緩やかな再配布条件)古典的フォーマット。軽量で直読しやすいが拡張性は低い
Open English WordNet(OEWN/EWN)2019〜2024版などGWN-LMF(XML)、RDF/TTL、(互換用にWNDB)Python wn ライブラリ、任意言語でXML/TTL直読CC BY 4.0(データ)オープン運用。最新版はoewn:2024としてwnから取得可能
編集用内部形式—YAML(リポジトリ作業用)自前パーサ(C#ならYamlDotNet)—公開配布の正式形式はLMF/RDF。YAMLはメンテ工程の内部形式

要点:「最新=YAMLしかない」わけではありません。公開版の正式配布はLMF(XML)やRDFです。YAMLはプロジェクト内の編集ワークフローで使われることがありますが、配布と互換性の観点ではLMF XML(もしくはWNDB)を基準に考えるのが王道です。

最短ルート:Python環境で公式OSS「wn」を使う

実務で最も速く確実にWordNetへアクセスする方法は、PythonのOSSライブラリ wn を用いることです。wn はLMF(XML)などの公式配布物を取得・インデックス化(SQLite)し、語・語義・シソーラス関係へ簡潔なAPIで到達できます。

インストールとデータ取得

pip install wn
# 2024版のOpen English WordNet(OEWN)を取得
python -c "import wn; wn.download('oewn:2024')"
# 2019/2020版のEnglish WordNetを使う場合
python -c "import wn; wn.download('ewn:2020')"

基本クエリ(語→シンスセット→定義・同義語・上位語)

import wn

# 使いたい版を明示(最新版系は oewn)

oewn = wn.Wordnet('oewn:2024')

# 名詞 "dog" のシンスセットを取得

synsets = oewn.synsets('dog', pos='n')

# 上位語(hypernym)、定義、同義語(lemmas)を覗く

for s in synsets[:3]:
print('ID:', s.id)
print('Definition:', s.definition())
print('Lemmas:', [w.lemma() for w in s.words()])
print('Hypernyms:', [h.id for h in s.hypernyms()])
print('Examples:', s.examples())
print('---') 

ポイント:wn はダウンロードした辞書をローカルでSQLiteに格納し、高速検索します。初回はインデックス構築でやや時間がかかりますが、2回目以降は非常に快適です。複数版(例:ewn:2019とoewn:2024)を併存させ、lexicon 引数で明示的に切り替えるのが再現性の観点で安全です。

C#から使う4つのアプローチ(用途別の最適解)

比較表(どれを選べばよい?)

方法概要強み弱み適した用途
① LMF XMLをC#で直読LMF(XML)をLINQ to XMLで解析し、語義・関係を自作クエリ公式配布に忠実/将来互換性よし初期実装コストは中程度(スキーマ理解が必要)オンプレでの組込・長期運用
② 旧形式(WNDB)+既存C#ライブラリPWNのdata.*/index.*を対象とする古参API最小コーディングで試せる最新E(W)N拡張との乖離、NuGet整備にばらつきレガシーデータを素早く試すPoC
③ Python wn にRESTを被せてC#からHTTPFastAPI等で薄いAPIを作成し、C#はHttpClient開発コスト最小/wnの機能をそのまま利用別プロセス・運用が必要小〜中規模サービス、マイクロサービス構成
④ YAMLをC#で直読編集用YAMLをYamlDotNetで解析データ加工が柔軟YAMLは公開配布の正式形式ではなく将来変更に弱い研究や社内ツール(配布前提でない)

① LMF(XML)をC#で直読:LINQ to XMLで堅実に

Open English WordNetなどの公開配布はLMF(GWN-LMF)XMLが正式形式です。C#ではSystem.Xml.Linqでシンプルに扱えます。ネームスペースURIは版で変わる可能性があるため、既定NSを動的取得して要素を辿るのが堅実です。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;

public sealed class LmfWordnet
{
    private readonly XDocument _doc;
    private readonly XNamespace _ns;
    private readonly XElement _lexicon;
    private readonly IReadOnlyDictionary<string, XElement> _synsetById;

    public LmfWordnet(string xmlPath)
    {
        _doc = XDocument.Load(xmlPath);
        _ns = _doc.Root!.GetDefaultNamespace();
        _lexicon = _doc.Root!.Element(_ns + "Lexicon")
            ?? throw new InvalidDataException("Lexicon 要素が見つかりません。");

        _synsetById = _lexicon.Elements(_ns + "Synset")
            .ToDictionary(x => (string)x.Attribute("id")!);
    }

    public IEnumerable<(string SynsetId, string Definition, IReadOnlyList<string> Lemmas)>
        Lookup(string lemma, string? pos = null)
    {
        var entries = _lexicon.Elements(_ns + "LexicalEntry")
            .Where(e => string.Equals(
                (string?)e.Element(_ns + "Lemma")?.Attribute("writtenForm"),
                lemma, StringComparison.OrdinalIgnoreCase));

        foreach (var entry in entries)
        {
            if (pos != null &&
                !string.Equals((string?)entry.Element(_ns + "Lemma")?.Attribute("partOfSpeech"),
                                pos, StringComparison.OrdinalIgnoreCase))
                continue;

            foreach (var sense in entry.Elements(_ns + "Sense"))
            {
                var synId = (string?)sense.Attribute("synset");
                if (synId == null || !_synsetById.TryGetValue(synId, out var syn)) continue;

                var def = (string?)syn.Element(_ns + "Definition")?
                                   .Element(_ns + "Text") ?? string.Empty;

                // 同義語(同一Synsetに属するLemma)を収集
                var lemmas = _lexicon.Elements(_ns + "LexicalEntry")
                    .Select(e2 => new {
                        Lemma = (string?)e2.Element(_ns + "Lemma")?.Attribute("writtenForm"),
                        Senses = e2.Elements(_ns + "Sense")
                    })
                    .Where(e2 => e2.Senses.Any(s2 => (string?)s2.Attribute("synset") == synId))
                    .Select(e2 => e2.Lemma!)
                    .Where(w => !string.IsNullOrWhiteSpace(w))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToList();

                yield return (synId!, def, lemmas);
            }
        }
    }
}

// 使い方例
// var wn = new LmfWordnet(@"C:\dict\oewn-2024.xml");
// foreach (var hit in wn.Lookup("dog", pos: "n")) {
//     Console.WriteLine($"{hit.SynsetId}: {hit.Definition}");
//     Console.WriteLine(string.Join(", ", hit.Lemmas));
// }

実運用のコツ:

  • LMFは巨大な1ファイルになりがちです。読み込みのI/Oを避けるため、アプリ起動時に一度ロードし、アプリ全体で共有するのが定石です。
  • Synset ID → Synset要素のディクショナリ(上例の _synsetById)を前計算しておくと、語→Synset→同義語/関係の解決が高速化します。
  • 上位語・下位語などの関係は、Synset 内の関係要素(<SynsetRelation type="hypernym" target="..."/>など)を辿ればOKです。

② 旧形式(WNDB)をそのまま使う:最短で動かす代替策

Princeton WordNetのWNDB(ASCII)を直接読むC#ライブラリは長い歴史があります(例:WordNet.Netや同系クローン、Syn.WordNetなど)。これらはindex.*/data.*を読み込み、GetSynSets("dog", POS.Noun) のように最短で同義語・定義へ到達できます。

ただし、最新のOEWNに含まれる修正・追加と表現の差異、品詞タグ、関係ラベルの拡張などに追随しにくいのが難点です。レガシーデータでのPoCや、最低限の同義語抽出が目的であれば有用ですが、長期運用や多言語対応、関係の完全性が必要な場合はLMFかwnへの移行を推奨します。

③ Python wnをバックエンドにRESTを被せ、C#からHTTPで叩く

「C#コードで辞書ロジックを一切持ちたくない」「最新の辞書更新に軽く追随したい」場合は、Pythonのwnに薄いRESTを被せるのが最短ルートです。FastAPIの最小実装は以下の通りです。

# app.py
from fastapi import FastAPI
import wn

app = FastAPI()
wn.download("oewn:2024")
db = wn.Wordnet("oewn:2024")

@app.get("/synsets/{lemma}")
def synsets(lemma: str, pos: str | None = None, limit: int = 10):
    items = db.synsets(lemma, pos=pos)
    out = []
    for s in items[:limit]:
        out.append({
            "id": s.id,
            "definition": s.definition(),
            "lemmas": [w.lemma() for w in s.words()],
            "hypernyms": [h.id for h in s.hypernyms()],
        })
    return out

起動(例):uvicorn app:app --reload --port 8000

C#からの呼び出しはHttpClientで十分です。

using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;

public sealed class WnClient
{
private readonly HttpClient _http;
public WnClient(string baseUrl) => _http = new HttpClient { BaseAddress = new Uri(baseUrl) };


public record SynsetDto(string id, string definition, string[] lemmas, string[] hypernyms);

public Task<SynsetDto[]?> SearchAsync(string lemma, string? pos = "n")
    => _http.GetFromJsonAsync<SynsetDto[]>($"/synsets/{Uri.EscapeDataString(lemma)}?pos={pos}");


}

// 使い方例
// var client = new WnClient("[http://localhost:8000](http://localhost:8000)");
// var res = await client.SearchAsync("dog", "n");
// foreach (var s in res ?? Array.Empty()) Console.WriteLine(s.definition); 

利点:辞書の取得・更新・索引はPython側に閉じ込められ、C#はHTTPクライアントだけで済みます。API境界が明確で、テストも容易です。マイクロサービスやサーバーレス(コンテナ)と相性が良い構成です。

④ YAMLをC#で解析する(研究・内製ツール向け)

OEWNプロジェクトでは編集ワークフローでYAMLが使われています。ただし正式配布はLMF/RDF/WNDBである点に注意してください。研究やクレンジングのためにYAMLを読む必要があるなら、YamlDotNetで最低限のスキーマを自作すれば動きます(ファイル構成は版により変化し得ます)。

using System.Collections.Generic;
using System.IO;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

public sealed class SynsetYaml
{
    public string id { get; set; } = "";
    public string? ili { get; set; }
    public string? definition { get; set; }
    public List&lt;string&gt; members { get; set; } = new();
}

public static class YamlLoader
{
    public static IReadOnlyList&lt;SynsetYaml&gt; LoadAll(string rootDir)
    {
        var builder = new DeserializerBuilder()
            .WithNamingConvention(UnderscoredNamingConvention.Instance)
            .IgnoreUnmatchedProperties()
            .Build();

        var list = new List&lt;SynsetYaml&gt;();
        foreach (var file in Directory.EnumerateFiles(rootDir, "*.yml", SearchOption.AllDirectories))
        {
            using var sr = new StreamReader(file);
            var chunk = builder.Deserialize&lt;List&lt;SynsetYaml&gt;&gt;(sr);
            if (chunk != null) list.AddRange(chunk);
        }
        return list;
    }
}

YAMLは変更されやすい内部形式と割り切り、プロダクション用途ではLMF XMLまたはREST経由に寄せるのが安全です。

「ライブラリのインポートは必要?」への答え

  • C#:扱う形式に応じてライブラリが必要です。LMFなら標準のSystem.Xml.Linqで十分ですが、YAMLを直読するならYamlDotNetをNuGetで追加します。旧形式を扱うならWordNet.Net系やSyn.WordNetのようなパッケージを導入します。
  • Python:pip install wn が基本。wn.download()で辞書本体を取得します。以降はローカルDBアクセス(SQLite)でAPIのように扱えます。

サンプル:同義語・上位語・定義をまとめて取得(C# LMF直読)

// 前掲 LmfWordnet を利用
var wn = new LmfWordnet(@"C:\dict\oewn-2024.xml");
foreach (var hit in wn.Lookup("bank", pos: "n"))
{
    Console.WriteLine($"[{hit.SynsetId}] {hit.Definition}");
    Console.WriteLine("Synonyms: " + string.Join(", ", hit.Lemmas));
    Console.WriteLine();
}

実装時の設計チェックリスト

  • 版とIDを固定する:開発・本番で辞書版がズレると結果が変わります。oewn:2024など版を明示しましょう。
  • 品詞を指定する:pos='n'(名詞)などを付けるとノイズが減り、意図が明確になります。
  • 正規化・派生語:wnにはmorphy系の正規化があり、語形変化から基本形へ辿れます。C#で直読する場合はステミング/レンマ化を別途用意すると良いです。
  • キャッシュ戦略:LMF直読は起動時ロード+メモリ常駐、REST方式はPython側のプロセスを常時起動。高頻度クエリはSynset辞書や関係索引をメモリ常駐させてI/Oを避ける。
  • エンコードと改行:WNDB直読ではCRLF変換が壊れやすいのでGitチェックアウト設定に注意。XMLはUTF-8固定を推奨。
  • テストデータ:dogやbankのような多義語でユニットテストを作り、同義語数・代表定義・代表上位語が安定して取れるか確認しましょう。

ライセンスと表記(実務で見落としがちな落とし穴)

  • Open English WordNet:データはCC BY 4.0。製品/サイトに組み込む場合はクレジット表記を用意するのが安全です。
  • Princeton WordNet:独自のWordNetライセンス。クレジット表記などの条件に従ってください。
  • ライブラリ:wnはMIT、旧来のC#ライブラリはLGPL等が混在します。配布形態(動的/静的リンク)と社内ポリシーに合わせて確認しましょう。

よくある質問(FAQ)

Q. Windows向けGUIを入れたのに、C#から使えません

A. GUIは「閲覧ツール」であり、アプリ側がAPIを提供しているわけではありません。辞書データ(LMF/WNDBなど)をダウンロードして、それを扱うコード(XML解析/旧形式ライブラリ/RESTラッパー)を用意する必要があります。

Q. 「YAMLの辞書」を直で使うのが正解?

A. 研究・内製の加工工程としては便利ですが、正式配布はLMF/RDF/WNDBです。将来互換・第三者再利用を考えると、LMF(XML)かwn利用を基軸にしましょう。

Q. 最小工数でC#から使うなら?

A. Python wn+RESTが最短です。C#側はHttpClientで呼ぶだけ。オンプレ1台構成でもDockerで同居できます。

Q. 最速レスポンスを目指すなら?

A. LMFを起動時に読み込み、Synset辞書・関係インデックスをメモリ常駐(C#側)。またはwn(SQLite)を常時起動し、REST越しに固定回線で叩く。いずれも版固定+キャッシュが鍵です。

まとめ:一番楽で将来にも強いのは「Python wnで操作し、必要ならC#から呼び出す」

WordNetは「アプリ」ではなく辞書データであり、正式配布の主役はLMF(XML)やRDFです。素早く・正確に・将来互換も保ちたいなら、Pythonのwnで辞書を扱い、C#からはREST経由で呼び出すのが最もメンテナンス負荷が低い実装パターンです。純C#で閉じたいときはLMF直読(LINQ to XML)を選び、レガシー検証ならWNDB+旧来ライブラリ、研究用途ならYAML直読という順で使い分けてください。こうした分業と版固定(例:oewn:2024)を徹底すれば、辞書更新にも柔軟に追随できます。


付録:実装テンプレート集

FastAPIルータ(語→上位語たどり)

# relations.py
from fastapi import APIRouter
import wn

router = APIRouter()
db = wn.Wordnet("oewn:2024")

@router.get("/hypernyms/{lemma}")
def hypernyms(lemma: str, pos: str | None = None):
res = []
for s in db.synsets(lemma, pos=pos):
res.append({
"id": s.id,
"hypernyms": [{"id": h.id, "def": h.definition()} for h in s.hypernyms()]
})
return res 

C#:RESTのDTOとクライアントをプロジェクトに同梱

public record SynsetResponse(string id, string definition, string[] lemmas, string[] hypernyms);

public interface IWordnetApi {
Task SearchAsync(string lemma, string pos);
}

public sealed class WordnetApi : IWordnetApi
{
private readonly HttpClient _http;
public WordnetApi(HttpClient http) => _http = http;


public async Task<SynsetResponse[]> SearchAsync(string lemma, string pos)
    => await _http.GetFromJsonAsync<SynsetResponse[]>(
           $"/synsets/{Uri.EscapeDataString(lemma)}?pos={pos}") 
       ?? Array.Empty<SynsetResponse>();


} 

LMF XMLスキーマを変えても壊れにくくする小技

  • 既定ネームスペースは doc.Root.GetDefaultNamespace() で取得し、ハードコードを避ける。
  • Element(ns + "X") へのアクセスは?.やnullチェックを徹底し、欠損耐性を持たせる。
  • Synset IDとLemmaの参照は必ずディクショナリで逆引き可能に。

最低限の同義語抽出(WNDB直読系API)

// 代表的なAPIのイメージ(実際の型名はライブラリにより異なります)
var engine = new WordNetEngine(@"C:\dict\WordNet\", inMemory: true);
var senses = engine.GetSynSets("help", WordNetEngine.POS.Noun);
foreach (var s in senses)
{
    Console.WriteLine(s.Gloss);
    Console.WriteLine(string.Join(", ", s.Words));
}

導入〜構築の「勝ちパターン」

  1. 要件を整理:クエリ内容(同義語だけか、関係もか)、レイテンシ、配布条件(オンプレ/クラウド)、ライセンス要件。
  2. 方針選定:迷ったら Python wn を採用し、C#からREST。 長期維持や監査要件が厳しければLMF直読。
  3. 版固定:oewn:2024やewn:2020など明示版で固定。テスト・本番で同一版を配備。
  4. 運用:辞書更新はCI/CDでタスク化。インデックス構築やAPIの健全性を自動検証。

結論:「WordNetはアプリではなくデータ」。最小工数ならPython wn、C#で完結させたいならLMF直読。用途に応じてWNDB・YAMLを補助的に使い分ければ、開発負荷と将来互換性の両立が可能です。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次