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.1 | WNDB(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#からHTTP | FastAPI等で薄い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<string> members { get; set; } = new();
}
public static class YamlLoader
{
public static IReadOnlyList<SynsetYaml> LoadAll(string rootDir)
{
var builder = new DeserializerBuilder()
.WithNamingConvention(UnderscoredNamingConvention.Instance)
.IgnoreUnmatchedProperties()
.Build();
var list = new List<SynsetYaml>();
foreach (var file in Directory.EnumerateFiles(rootDir, "*.yml", SearchOption.AllDirectories))
{
using var sr = new StreamReader(file);
var chunk = builder.Deserialize<List<SynsetYaml>>(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));
}
導入〜構築の「勝ちパターン」
- 要件を整理:クエリ内容(同義語だけか、関係もか)、レイテンシ、配布条件(オンプレ/クラウド)、ライセンス要件。
- 方針選定:迷ったら Python
wnを採用し、C#からREST。 長期維持や監査要件が厳しければLMF直読。 - 版固定:
oewn:2024やewn:2020など明示版で固定。テスト・本番で同一版を配備。 - 運用:辞書更新はCI/CDでタスク化。インデックス構築やAPIの健全性を自動検証。
結論:「WordNetはアプリではなくデータ」。最小工数ならPython wn、C#で完結させたいならLMF直読。用途に応じてWNDB・YAMLを補助的に使い分ければ、開発負荷と将来互換性の両立が可能です。

コメント