Programowanie w języku C# » Artykuły

Regułowy System ekspercki / Rule-driven expert system

Uwaga:
Jeśli nie chcesz tego czytać, a zobaczyć, jak to działa, pobierz pełny spakowany (.zip) projekt C# z kodem źródłowym,
klikając tutaj ExpertSystem.zip (1,61 MB)
Treść udostępniona na zasadach licencji Creative Commons Attribution
Autor uznaje również załączony projekt C# za integralną część poniższego artykułu
i rezerwuje sobie prawo do modyfikacji tego projektu na rzecz poprawienia jego jakości
bez dokonywania zmian w poniższym artykule, w szczególności bez informowania czytelników o tym fakcie.

Note:
If you do not want to read it, but you want to see how it works, please, download full zipped C# project with enclosed source code
by clicking here  ExpertSystem.zip (1,61 MB)
Content shared under license Creative Commons Attribution
Author also considers enclosed C# project as integral part of article below,
and reserves himself the right to modify this project for purposes of increasing its quality
without making changes to the rest of article, especially without informing readers about this fact.

Wstęp / Introduction


W tym temacie pokazuję jak rozpocząć budowę systemu eksperckiego.In this topic I show how to start building an expert system.
System ekspercki zawiera wiedzę ekspertów z jednej wąskiej dziedziny. Wiedza ta jest umieszczona w bazie wiedzy w postaci sformatowanej.Expert system provides expert knowledge in one small area. This knowledge is contained in a knowledge base in formatted form.
Wiedzę ekspertów do systemu eksperckiego wprowadza inżynier wiedzy, który z jednej strony częściowo zna dziedzinę reprezentowaną przez ekspertów, a z drugiej strony potrafi ją sformatować do zapisów z dziedziny logiki.Knowledge engineer introduces expert knowledge into an expert system. He is on the one hand partially familiar with the field represented by the experts, on the other hand is able to format it in the field of logic.
System ekspercki jest programem, któremu zadajemy pytania i od którego otrzymujemy odpowiedzi. Jeżeli odpowiedzi udzielone przez program są identyczne z odpowiedziami, jakich udzieliliby eksperci (w 70-80 lub więcej procentach), system uznaje się za działający prawidłowo.Expert system is a program which we ask questions and get answers from it. If the answers given by the program are the same answers, which experts ones are (of 70-80 percent or more), the system is considered running properly.
Ostateczną decyzję podejmuje człowiek, a system ekspercki mu tylko pomaga.The final decision is made by man, and an expert system only helps him.
Ważną cechą systemu eksperckiego jest udzielanie odpowiedzi na pytanie dlaczego podjął taką a nie inną decyzję. Pozwala to na weryfikację poprawności odpowiedzi systemu. Mechanizm uzasadniania odpowiedzi warto zaimplementować już na początku tworzenia systemu. To ułatwi testowanie poprawności wnioskowania.An important feature of an expert system is to answer the question why it made this decision not a different one. This allows the verification of the correctness of system responses. We implement mechanism to justify the response at the beginning of the creation of the system. This facilitates testing the correctness of reasoning.
Składnikami systemu eksperckiego są: baza wiedzy złożona z bazy faktów i bazy reguł, mechanizmy wnioskowania, mechanizmy uzasadniania odpowiedzi oraz baza danych.Components of an expert system are: a knowledge base consisting of a database of facts and database of rules, reasoning mechanism, mechanism of responses to question why.
Systemy eksperckie znalazły największe zastosowanie w medycynie i ekonomii. Pokazuję działanie systemu eksperckiego na prostym i zrozumiałym przykładzie szukania kandydata na męża dla Alicji. Przykład z medycyny lub ekonomii nie byłby dla wszystkich zrozumiały, dlatego wybrałem coś, co każdy zna z życia.Expert systems have found greatest use in medicine and economics. I show the action of an expert system on a simple and understandable example of finding a candidate for a husband for Alice. An example from medicine or economics would not be to understand for everyone why I chose something that everyone knows from life.
Mój system jest sterowany regułami to znaczy, że reguły decydują o wnioskowaniu. Można spotkać systemy eksperckie zawierające tylko mechanizmy wnioskowania i pustą bazę wiedzy. Są to systemy szkieletowe, gotowe do przyjęcia wiedzy z dowolnej dziedziny.My system is controlled by rules (rule-driven) that mean that the rules affect the reasoning. There can be found expert systems containing only reasoning mechanism and an empty knowledge base.
W przykładzie przyjąłem następujące założenia: Alicja jest heteroseksualna, mówimy o papieżu rzymsko-katolickim, jest rok 2013 (przed 28 lutego, kiedy to Benedykt XVI oficjalnie abdykował).In this example, I assume that: Alice is heterosexual, we are talking about the Roman Catholic Pope, the year is 2013 (before February 28, when Benedict XVI officially abdicated).

Dodatkowe artykuły na tym portalu (tylko po polsku) / Additional articles at this site (Polish only)


Metody i Style Zarządzania, LOSMARCELOS, 4programmers.net
Ekstrakcja reguł z danych / Extraction of rules from data, Artur Protasewicz, 4programmers.net


Baza danych i postać regułyDatabase and form of rule
Fakty i reguły tworzące bazę wiedzy będziemy przechowywać w bazie danych. Moja baza danych nie zawiera kluczy. Nie jest to przedmiotem tego artykułu. Zakładam, że je stworzysz.Facts and rules for creating a knowledge base will be stored in a database. My database does not contain primary keys. This is not the subject of this article. I assume that you will create them.
Jako dodatek znajdziesz w kodzie na końcu funkcje odczytujące i zapisujące dane do bazy danych.As a supplement you can find in the code at the end reading and inserting functions for the database.
Najpierw pokazuję poniżej jaką sformatowaną postać reguły przyjąłem, a potem ogólne postaci reguł stosowane zwykle w systemach eksperckich.First, I show below a formatted form of rule I use, then the general form of rules typically used in expert systems.


Moja postać regułyMy form of rule
wniosek := (faktA and|or faktB) and (wniosekX and|or wniosekY)conclusion := (factA and|or factB) and (conclusionX and|or conclusionY)


  

Obrazek 1

Figure 1

Tabele bazy danych

Database tables



Stosowane postaci reguł
wniosekA or wniosekB or ... or wniosekM := [not] fakt1|wniosek1 and [not] fakt2|wniosek2 and ... and [not] faktN|wniosekN
wniosekA and wniosekB and ... and wniosekM := [not] fakt1|wniosek1 or [not] fakt2|wniosek2 or ... or [not] faktN|wniosekN
Used forms of rules
conclusionA or conclusionB or ... or conclusionM := [not] fact1|conclusion1 and [not] fact2|conclusion2 and ... and [not] factN|conclusionN
conclusionA and conclusionB and ... and conclusionM := [not] fact1|conclusion1 or [not] fact2|conclusion2 or ... or [not] factN|conclusionN


Operatory AND, ORAND, OR operators
Aby poprawnie wprowadzić reguły, musisz wiedzieć, co to jest relacja AND i relacja OR. Obrazują to poniższe tabele dla dwóch parametrów relacji.To correctly implement the rules, you need to know what are the AND and OR relationships. The following tables illustrate them for two parameters.
Zakładam, że wiesz, co to jest zaprzeczenie NOT (0 daje 1, 1 daje 0).I assume you know what it is the negation NOT (0 gives 1, 1 gives 0).
Liczba 0 w tabeli oznacza wartość false, liczba 1 wartość true.Number 0 in the table means false, number 1 true.
Możesz rozbudować moją postać reguły tak, aby zawierała więcej parametrów np.You can extend my form of a rule to include more parameters such as:
wynik = (x1 and (x2 and (x3 and x4))) lub wynik = (x1 or (x2 or (x3 or x4)))result = (x1 and (x2 and (x3 and x4))) or result = (x1 or (x2 or (x3 or x4)))
Mój kod operuje na dwóch parametrach relacji, ale jeżeli zrobisz jak powyżej, nic w nim nie będziesz musiał zmieniać oprócz treści uzasadnień, bo nadal maszyna wnioskująca będzie działać prawidłowo.My code operates on two parameters of the relationship, but if you do as above, you will have nothing to change except justifying texts, because the reasoning machine will continue to work properly.




Tabela 1

Table 1

Operator AND

AND operator





Tabela 2

Table 2

Operator AND

AND operator


Przykład / Example


Słownik / Vocabulary

PolskiEnglish
jest kobietąis woman
lubi grać na gitarzelikes play guitar
lubi grać w brydżalikes play bridge
jest papieżemis pope
jest mężczyznąis man
lubi grać na pianinielikes play piano
lubi hazardlikes gambling
mogą być przyjaciółmican be friends
lubi muzykęlikes music
może poślubićcan marry
nie może poślubićcannot marry
lubi grać w kartylikes play cards
lubi pasjansalikes solitaire
wniosekconclusion
jest prawdąis true
nie jest prawdąis false
ktowho
dlaczegowhy
faktyfacts
regułyrules
obiektobject
atrybut, cechaattribute
kto może poślubić Alicję?who can marry Alice?
odpowiedźanswer
faktfact
dlaczego obiekt może poślubić Alicję?why object can marry Alice?
dlaczego obiekt nie może poślubić Alicji?why object cannot marry Alice?


Cel poszukiwańSearch goal
Szukając osoby, która może poślubić Alicję musimy wziąć pod uwagę regułę "może poślubić" i jej zaprzeczenie "nie może poślubić", a następnie stworzyć iloczyn warunków:Searching for a person who can marry Alice, we must consider the rule "can marry" and its denial "cannot marry" and then create a product of conditions:
cel = "może poślubić" and not "nie może poślubić"goal = "can marry" and not "cannot marry"




Obrazek 2

Figure 2

Baza wiedzy i odpowiedź na pytanie "Kogo może poślubić Alicja?

Knowledge base and the answer to the question "Who can marry Alice?"





Obrazek 3

Figure 3

Uzasadnienie odpowiedzi

Answer justification


Kod programu - C# / Program code C#


Założenie wstępnePre-assumption
Kod programu wymaga, abyś stworzył formę, odpowiednio ponazywał kontrolki i dodał do gridów odpowiednie kolumny. Zakładam, że korzystasz z SQL Server Express i stworzyłeś bazę AI_KB2.The code of the program requires you to create form, appropriately named controls and add to the grids corresponding columns. I assume that you are using SQL Server Express and you created a database AI_KB2.
Aby zmienić fakty i reguły, należy wpisać teksty bezpośrednio do grida, choć możesz sobie rozbudować aplikację i robić to w bardziej elegancki sposób.To change facts and rules, type text directly into the grid, but you can upgrade the app and do it in a more elegant way.

UWAGA: Reguły nie mogą zawierać odwołań cyklicznych. / WARNING: Rules shall not be recursive.

using System;
using System.Data;
using System.Windows.Forms;
using System.Data.SqlClient;
using System.Collections;
 
namespace Expert
{
    public partial class FormExpert : Form
    {
        String ConnStr = "Data Source=.\\SQLEXPRESS;Integrated Security=True;
          Initial Catalog=AI_KB2;";
        int orCol = 0;
        int conclusionCol = 1;
        int bracketLeftCol = 2;
        int factACol = 3;
        int abRelationCol = 4;
        int factBCol = 5;
        int andCol = 6;
        int conclusionXCol = 7;
        int xyRelationCol = 8;
        int conclusionYCol = 9;
        int bracketRightCol = 10;
        int objectCol = 0;
        int attributeCol = 1;
        int FactCount;
        int ConclusionCount;
        int Nested;
        RichTextBox why;
        public FormExpert()
        {
            InitializeComponent();
        }
        bool Is(String Conclusion, String Candidate)
        {
            if (Conclusion == "can marry")
            {
                why = WhyYes;
            }
            if (Conclusion == "cannot marry")
            {
                why = WhyNot;
            }
            Nested++;
            for (int ConclusionIndex = 0; ConclusionIndex < ConclusionCount;
              ConclusionIndex++)
            {
                if (Conclusions(conclusionCol, ConclusionIndex) == Conclusion)
                {
                    String sFact = "";
                    String sConclusion = "";
                    String s = "";
                    String FactA = Conclusions(factACol, ConclusionIndex);
                    String FactB = Conclusions(factBCol, ConclusionIndex);
                    String ConclusionX = Conclusions(conclusionXCol,
                      ConclusionIndex);
                    String ConclusionY = Conclusions(conclusionYCol,
                      ConclusionIndex);
                    bool resultORFacts = false;
                    bool resultORConclusions = false;
                    bool resultANDFacts = false;
                    bool resultANDConclusions = false;
                    if (Conclusions(abRelationCol, ConclusionIndex) == "and")
                    {
                        sFact += "(" + FactA + " and " + FactB + ")";
                    }
                    if (Conclusions(abRelationCol, ConclusionIndex) == "or")
                    {
                        sFact += "(" + FactA + " or " + FactB + ")";
                    }
                    if (Conclusions(xyRelationCol, ConclusionIndex) == "and")
                    {
                        sConclusion += "(" + ConclusionX + " and "
                          + ConclusionY + ")";
                    }
                    if (Conclusions(xyRelationCol, ConclusionIndex) == "or")
                    {
                        sConclusion += "(" + ConclusionX + " or "
                          + ConclusionY + ")";
                    }
                    s = sFact + " and " + sConclusion;
                    WhyPath(Conclusion + " if " + s);
                    if (Conclusions(abRelationCol, ConclusionIndex) == "and")
                    {
                        resultANDFacts = (IsFactA(ConclusionIndex, Candidate)
                          && IsFactB(ConclusionIndex, Candidate));
                    }
                    if (Conclusions(abRelationCol, ConclusionIndex) == "or")
                    {
                        resultORFacts = (IsFactA(ConclusionIndex, Candidate)
                          || IsFactB(ConclusionIndex, Candidate));
                    }
                    if (Conclusions(xyRelationCol, ConclusionIndex) == "and")
                    {
                        resultANDConclusions = (IsConclusionX(ConclusionIndex,
                          Candidate) && IsConclusionY(ConclusionIndex,
                          Candidate));
                    }
                    if (Conclusions(xyRelationCol, ConclusionIndex) == "or")
                    {
                        resultORConclusions = (IsConclusionX(ConclusionIndex,
                          Candidate) || IsConclusionY(ConclusionIndex,
                          Candidate));
                    }
                    bool result;
                    result = (resultANDFacts && resultANDConclusions);
                    result = (result || (resultANDFacts
                      && resultORConclusions));
                    result = (result || (resultORFacts
                      && resultANDConclusions));
                    result = (result || (resultORFacts && resultORConclusions));
 
                    if (result)
                    {
                        WhyPath("conclusion " + Conclusion + " is true");
                        Nested--;
                        return true;
                    }
                }
            }
            WhyPath("conclusion " + Conclusion + " is false");
            Nested--;
            return false;
        }
        bool IsFactA(int ConclusionIndex, String Candidate)
        {
            String FactA = Conclusions(factACol, ConclusionIndex);
            if (FactA == "1")
            {
                return true;
            }
            if (FactA == "0")
            {
                return false;
            }
            Nested++;
            for (int iFact = 0; iFact < FactCount; iFact++)
            {
                if (Facts(objectCol, iFact) == Candidate && Facts(attributeCol,
                  iFact) == FactA)
                {
                    WhyPath("fact " + FactA + " is true for " + Candidate);
                    Nested--;
                    return true;
                }
            }
            WhyPath("fact " + FactA + " is false for " + Candidate);
            Nested--;
            return false;
        }
        bool IsFactB(int ConclusionIndex, String Candidate)
        {
            String FactB = Conclusions(factBCol, ConclusionIndex);
            if (FactB == "1")
            {
                return true;
            }
            if (FactB == "0")
            {
                return false;
            }
            Nested++;
            for (int iFact = 0; iFact < FactCount; iFact++)
            {
                if (Facts(objectCol, iFact) == Candidate && Facts(attributeCol,
                  iFact) == FactB)
                {
                    WhyPath("fact " + FactB + " is true for " + Candidate);
                    Nested--;
                    return true;
                }
            }
            WhyPath("fact " + FactB + " is false for " + Candidate);
            Nested--;
            return false;
        }
        bool IsConclusionX(int ConclusionIndex, String Candidate)
        {
            String ConclusionX = Conclusions(conclusionXCol, ConclusionIndex);
            if (ConclusionX == "1")
            {
                return true;
            }
            if (ConclusionX == "0")
            {
                return false;
            }
            return Is(ConclusionX, Candidate);
        }
        bool IsConclusionY(int ConclusionIndex, String Candidate)
        {
            String ConclusionY = Conclusions(conclusionYCol, ConclusionIndex);
            if (ConclusionY == "1")
            {
                return true;
            }
            if (ConclusionY == "0")
            {
                return false;
            }
            return Is(ConclusionY, Candidate);
        }
        ArrayList SolveWhoCanMarryAlice()
        {
            ArrayList result = new ArrayList();
            ArrayList Persons = new ArrayList();
            WhyYes.Clear();
            WhyNot.Clear();
            for (int iFact = 0; iFact < FactCount; iFact++)
            {
                String Person = Facts(objectCol, iFact);
                if (Person != "Alice" && Persons.IndexOf(Person) < 0)
                {
                    Persons.Add(Person);
                }
            }
            foreach (String Person in Persons)
            {
                Nested = -1;
                if (Is("can marry", Person) && !Is("cannot marry", Person))
                {
                    result.Add(Person);
                }
            }
            return result;
        }
        private void btnWhoCanMarryAlice_Click(object sender, EventArgs e)
        {
            Answer.Text = "";
            foreach (String Person in SolveWhoCanMarryAlice())
            {
                Answer.Text += Person + Environment.NewLine;
            }
        }
        void WhyPath(String Text)
        {
            why.Text += strNested() + Text + Environment.NewLine;
        }
        String Facts(int Column, int Row)
        {
            return (String)grdFacts[Column, Row].Value;
        }
        String Conclusions(int Column, int Row)
        {
            return (String)grdConclusions[Column, Row].Value;
        }
        String strNested()
        {
            String s = "";
            for (int j = 0; j < Nested; j++)
            {
                for (int i = 0; i < 10; i++)
                {
                    s += " ";
                }
            }
            return s;
        }
        void LoadConclusions()
        {
            SqlConnection Conn = new SqlConnection(ConnStr);
            Conn.Open();
            SqlCommand CmdSelectConclusions = new SqlCommand("select * from
              Rules");
            CmdSelectConclusions.Connection = Conn;
            SqlDataReader readerConclusions
              = CmdSelectConclusions.ExecuteReader();
            int row = 0;
            ConclusionCount = 0;
            grdConclusions.Rows.Clear();
            while (readerConclusions.Read())
            {
                grdConclusions.Rows.Add();
                ConclusionCount++;
                grdConclusions[conclusionCol, row].Value = readerConclusions[0];
                grdConclusions[factACol, row].Value = readerConclusions[1];
                grdConclusions[abRelationCol, row].Value = readerConclusions[2];
                grdConclusions[factBCol, row].Value = readerConclusions[3];
                grdConclusions[conclusionXCol, row].Value = readerConclusions[4]
                  ;
                grdConclusions[xyRelationCol, row].Value = readerConclusions[5]
                  ;
                grdConclusions[conclusionYCol, row].Value = readerConclusions[6]
                  ;
                if (row == 0)
                {
                    grdConclusions[orCol, row].Value = "";
                }
                else
                {
                    grdConclusions[orCol, row].Value = "or";
                }
                grdConclusions[bracketLeftCol, row].Value = "(";
                grdConclusions[andCol, row].Value = ") and (";
                grdConclusions[bracketRightCol, row].Value = ")";
                row++;
            }
            readerConclusions.Close();
            Conn.Close();
        }
        void LoadFacts()
        {
            SqlConnection Conn = new SqlConnection(ConnStr);
            Conn.Open();
            SqlCommand CmdSelectFacts = new SqlCommand("select * from Facts");
            CmdSelectFacts.Connection = Conn;
            SqlDataReader readerFacts = CmdSelectFacts.ExecuteReader();
            int row = 0;
            FactCount = 0;
            grdFacts.Rows.Clear();
            while (readerFacts.Read())
            {
                grdFacts.Rows.Add();
                FactCount++;
                for (int col = 0; col < grdFacts.ColumnCount; col++)
                {
                    grdFacts[col, row].Value = readerFacts[col];
                }
                row++;
            }
            readerFacts.Close();
            Conn.Close();
        }
        void SaveConclusions()
       {
            SqlConnection Conn = new SqlConnection(ConnStr);
            Conn.Open();
            SqlCommand CmdDeleteAllConclusions = new SqlCommand("delete from
              Rules");
            CmdDeleteAllConclusions.Connection = Conn;
            CmdDeleteAllConclusions.ExecuteNonQuery();
            SqlCommand CmdInsertConclusion = 
              new SqlCommand("insert Rules (Conclusion, FactA, abRelation,
                FactB, ConclusionX, xyRelation, ConclusionY) " +
                "values (@par0, @par1, @par2, @par3, @par4, @par5, @par6)");
            CmdInsertConclusion.Connection = Conn;
            for (int par = 0; par < 7; par++)
            {
                CmdInsertConclusion.Parameters.Add("@par" + par.ToString(),
                  SqlDbType.Text);
            }
            for (int row = 0; row < grdConclusions.RowCount; row++)
            {
                CmdInsertConclusion.Parameters[0].Value
                  = grdConclusions[conclusionCol, row].Value;
                CmdInsertConclusion.Parameters[1].Value
                  = grdConclusions[factACol, row].Value;
                CmdInsertConclusion.Parameters[2].Value
                  = grdConclusions[abRelationCol, row].Value;
                CmdInsertConclusion.Parameters[3].Value
                  = grdConclusions[factBCol, row].Value;
                CmdInsertConclusion.Parameters[4].Value
                  = grdConclusions[conclusionXCol, row].Value;
                CmdInsertConclusion.Parameters[5].Value
                  = grdConclusions[xyRelationCol, row].Value;
                CmdInsertConclusion.Parameters[6].Value
                  = grdConclusions[conclusionYCol, row].Value;
                try
                {
                    CmdInsertConclusion.ExecuteNonQuery();
                }
                catch { }
            }
            Conn.Close();
        }
        void SaveFacts()
        {
            SqlConnection Conn = new SqlConnection(ConnStr);
            Conn.Open();
            SqlCommand CmdDeleteAllFacts = new SqlCommand("delete from Facts");
            CmdDeleteAllFacts.Connection = Conn;
            CmdDeleteAllFacts.ExecuteNonQuery();
            SqlCommand CmdInsertFact = new SqlCommand("insert Facts (Object,
              Attribute) values (@par0, @par1)");
            CmdInsertFact.Connection = Conn;
            for (int par = 0; par < 2; par++)
            {
                CmdInsertFact.Parameters.Add("@par" + par.ToString(),
                  SqlDbType.Text);
            }
            for (int row = 0; row < grdFacts.RowCount; row++)
            {
                for (int par = 0; par < grdFacts.ColumnCount; par++)
                {
                    CmdInsertFact.Parameters[par].Value
                      = grdFacts[par, row].Value;
                }
                try
                {
                    CmdInsertFact.ExecuteNonQuery();
                }
                catch { }
            }
            Conn.Close();
        }
        private void btnLoadData_Click(object sender, EventArgs e)
        {
            LoadFacts();
            LoadConclusions();
        }
        private void btnSaveData_Click(object sender, EventArgs e)
        {
            SaveFacts();
            SaveConclusions();
        }
    }
}


Zrzuty ekranu - uruchomiony projekt zlokalizowany na polski / Screenshots - running project localized to Polish














Prototypowa wersja projektu (pełna) do pobrania / Prototype version of the project (full) to download


ExpertSystem.zip (1,61 MB)



4 komentarze

Varran 2013-11-06 17:13

Bardzo ciekawy artykuł, pozdrawiam.

mikajlo 2013-10-06 21:24

Szacun za artykuł. Temat ciekawy i wykonanie również :)

madmike 2013-10-05 10:54

Bardzo ciekawy ;) Chociaż ze względu na osoby, które chcą się nauczyć języka czy bardziej podszkolić się bardziej podzieliłbym zwarty tekst na pojedyncze paragrafy zawierające maksymalnie trzy, cztery zdania. Wiem, że to może by trochę rozbiło jedność tego artykułu, ale byłoby bardziej czytelne/przydatne dla osób, które chcą w ten sposób poduczyć się takiego języka...

Marooned 2013-10-05 10:48

Bardzo ciekawy pomysł z dwoma językami.