Простое приложение Semantic Web на C#

Эндрю Мэттьюс
Оригинал: A Simple Semantic Web Application In C#

Последнее обновление библиотеки SemWeb от Джоша Тауберера включает C# реализацию механизма вывода Эйлера. Этот механизм способен идти дальше простого RDFS вывода (т.е. элементарного следования взаимоотошениям между классами), и позволяет воспользоваться правилами. Для того, чтобы разобраться с подходами, которые предлагает эта среда, я воспользовался онтологией моделирующей простой конечный автомат. Онтология — проще некуда. Вот N3 файл, в котором определены классы и взаимоотношения между ними.

@prefix daml:  .
@prefix rdfs:  .
@prefix owl:  .
@prefix :  .
 
#Classes
:State a owl:Class;
daml:comment "states the system can be in";
daml:disjointUnionOf ( :S1 :S2 :S3 ).
 
:InputToken a owl:Class;
daml:comment "inputs to the system";
daml:disjointUnionOf ( :INil :I1 :I2 ).
 
:Machine a owl:Class.
:System a owl:Class.
 
#properties
:isInState
rdfs:domain :Machine;
rdfs:range :State;
owl:cardinality "1".
 
:hasInput
rdfs:domain :System;
rdfs:range :InputToken;
owl:cardinality "1".
 
#Instances
:Machine1
a :Machine;
:isInState :S1.
 
:This a :System;
:hasInput :INil.
 

Как в любом детерминированном конечном автомате, здесь работают два ключевых класса: :State (Состояние) и :InputToken (ВходнойСимвол). Состояние — это попарно непересекающиеся множество классов :S1, :S2 и :S3. Это значит, что :S1 — не является :S2, и не является :S3. Если вы явно не специфицируете такое разделение механизм вывода не сможет предположить его наличие. Если нет правила утверждающего, что классы не пересекаются, то механизм вывода не сможет предположить, что эти классы различны. То есть если Автомат1 находится в состоянии S1, то это не означает, что он не может одновременно находиться и в состоянии S2. Вы должны четко определить, что S1 — это не S2. Педантичность всегда имеет значение, когда вы имеете дело с онтологиями. И в то время как я превращался в законченного педанта за годы программирования я был шокирован тем уровнем семантической поддержки, которую я получал от языков программирования. OWL обеспечивает вас очень богатой палитрой для работы, но не слишком удобной поддержкой. Вы чувствуете новую степень свободы, когда проектируете библиотеку классов на OWL по сравнению с объектно-ориентированными языками. Примерно как в случае перехода от командной строки DOS на Bash.

Так или иначе, правила для нашей небольшой онтологии определяют таблицу переходов конечного автомата:

#@prefix log: .
#@prefix rdfs: .
#@prefix owl: .
@prefix :   .
 
# ~>
{ :Machine1 :isInState :S1. :This :hasInput :I1. }
=>
{ :Machine1 :isInState :S1. :This :hasInput :INil. }.
 
# ~>
{ :Machine1 :isInState :S1. :This :hasInput :I2. }
=>
{ :Machine1 :isInState :S2. :This :hasInput :INil. }.
 
# ~>
{ :Machine1 :isInState :S1. :This :hasInput :I3. }
=>
{ :Machine1 :isInState :S3. :This :hasInput :INil.}.
 
 

Первоначально я натолкнулся на проблему, в силу того, что я думал о задаче с точки зрения императивного программирования. Я подошел к проектированию как будто я собираюсь присваивать значения переменным. Это неверный подход. Рассматривайте проектирование правил как добавление фактов к тому, что вы уже знаете. Так что, вместо того, чтобы говорить «если X, то Y», используйте фразу «если я знаю X, то я также знаю Y». Программа для работы с этим набором правил выглядит так:


internal
class Program
{
private
static
readonly
string ontologyLocation
=
@"C:/dev/prototypes/semantic-web/ontologies/20074/states/";
private
static
string baseUri
=
@"file:///C:/dev/prototypes/semantic-web/ontologies/2007/04/states/states.rdf#";
private
static MemoryStore store
=
new MemoryStore
(
);
private
static Entity Machine1
=
new Entity
(baseUri
+
"Machine1"
);
private
static Entity Input1
=
new Entity
(baseUri
+
"I1"
);
private
static Entity Input2
=
new Entity
(baseUri
+
"I2"
);
private
static Entity theSystem
=
new Entity
(baseUri
+
"This"
);
private
static
string hasInput
= baseUri
+
"hasInput";
private
static
string isInState
= baseUri
+
"isInState";
private
static
void Main
(
string
[
] args
)
{ InitialiseStore
(
); DisplayCurrentStates
(
); SetNewInput
(Input2
); DisplayCurrentStates
(
);
}
private
static
void DisplayCurrentStates
(
)
{ SelectResult ra
= store.
Select
(
new Statement
(Machine1,
new Entity
(isInState
),
null
)
); Debug.
Write
(
"Current states: "
);
foreach
(Statement resource
in ra.
ToArray
(
)
)
{ Debug.
Write
(resource.
Object.
Uri
);
} Debug.
WriteLine
(
""
);
}
private
static
void InitialiseStore
(
)
{
string statesLocation
= Path.
Combine
(ontologyLocation,
"states.n3"
);
string rulesLocation
= Path.
Combine
(ontologyLocation,
"rules.n3"
); Euler engine
=
new Euler
(
new N3Reader
(File.
OpenText
(rulesLocation
)
)
); store.
Import
(
new N3Reader
(File.
OpenText
(statesLocation
)
)
); store.
AddReasoner
(engine
);
}
private
static
void SetNewInput
(Entity newInput
)
{ Resource
[
] currentInput
= store.
SelectObjects
(theSystem, hasInput
); Statement input
=
new Statement
(theSystem, hasInput, Input1
); store.
Remove
(
new Statement
(theSystem, hasInput, currentInput
[
0
]
)
); store.
Add
(
new Statement
(theSystem, hasInput, newInput
)
); Resource
[
] subsequentState
= store.
SelectObjects
(Machine1, isInState
); Statement newState
=
new Statement
(Machine1, isInState, subsequentState
[
0
]
); store.
Replace
(
new Statement
(Machine1, isInState,
null
), newState
);
}
}

Задача была проста. Я хотел установить конечный автомат в состояние :S1 с помощью входного символа :INil, затем подать на вход символ :I1 и увидеть как статус изменится с :S1 на :S2. Поступая таким образом я пытаюсь сделать нечто необычное по сравнению с тем, для чего предполагается использовать онтологии. По большей части онтология — это статическая декларация знаний, а не динамически изменяемый набор фактов. Это значит, что онтологии допускают расширение. Среды и механизмы вывода позволяют вам добавлять информацию и, тем самым, увеличивать количество знаний. Поэтому мы можем полагаться на Semantic Web и повторно использовать данные хранящиеся там [1, 2]. Если я могу взять вашу онтологию и изменить ее так, что она будет означать нечто иное, нежели то, что вы имели ввиду, тогда результат окажется непредсказуемым. Онтологии должны быть последовательными. Если вы хотите описывать некоторые данные опираясь на онтологию — это ваше дело, и вы можете легко это организовать. На практике это означает, что вы должны вручную изменять входные сигналы и состояния. Что касается онтологии, так она просто создает среду для представления данных, и набор правил для определения того, каким должно быть следующее состояние. Это довольно мощное средство, но меня интересует вопрос: как хорошо оно будет масштабироваться.

Что нужно отметить

Существует несколько вещей на которые вы должны обратить особое внимание если вы хотите разрабатывать приложения Semantic Web используя библиотеку SemWeb. Прежде всего, это определение в онтологии пространства имен по умолчанию, и файлы определяющие правила. Обычно примеры файлов в формате N3 на сайте W3C используют следующий формат для определения пространства имен по умолчанию:

@prefix :  

К сожалению такая декларация оставляет слишком много пространства для маневра в SemWeb, и не всегда можно определенно сказать каким будет реальный URI в этом случае. Обычно SemWeb опирается на местоположение, откуда был получен файл. Поэтому лучше использовать более точно определенный URL. Например:

@prefix : .

Этот URL не является адресом файла, это просто URL который я раньше использовал для файлов в формате N3. Важно то, что вы должны предоставить недвусмысленный адрес, чтобы SemWeb и ее механизм вывода могли правильно отличать ресурсы друг от друга, когда вы задаете ей вопросы. Я использовал тот же самый URL в файле rules.n3, т.к. большинство объектов, на которые я ссылался, были определены в этом пространстве имен. Я так же мог бы определить новый префикс для файла states.n3 и предварить все элементы в правилах этим префиксом. Главное — не иметь URL по умолчанию, чтобы SemWeb всегда точно знала URL ресурсов на которые вы ссылаетесь.

Далее, запомните, что вам придется погрузиться глубоко в хранилище, и вручную менять состояние объектов — также как в любом приложении имеющем дело с реляционными базами данных. Хотя первоначально я был немного расстроен, т.к. я надеялся, что механизм вывода произведет все необходимые изменения самостоятельно. Увы, очевидно это не в духе Semantic Web, так что приготовьтесь интенсивно управлять системой.

Я полагал, что будет очень просто использовать OWL онтологии для приложений способных отвечать на вопросы, но видимо потребуется немного больше работы для того, чтобы создать такое приложение на базе механизма вывода. Конечно, я могу ошибаться на этот счет, и возможно следует винить мой неисправимый процедурный склад ума.

Перевод: Михаил Навернюк